diff --git a/.github/workflows/auto-label-author.yml b/.github/workflows/auto-label-author.yml index 114eb6b5..5499885d 100644 --- a/.github/workflows/auto-label-author.yml +++ b/.github/workflows/auto-label-author.yml @@ -20,7 +20,8 @@ jobs: runs-on: ubuntu-latest steps: - name: Label PRs from bellus869 and Lemi257 - uses: actions/github-script@v7 + # Pin the privileged pull_request_target action to a reviewed commit. + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 with: script: | const labelName = "comments translation"; diff --git a/.github/workflows/build-installer.yml b/.github/workflows/build-installer.yml index 6a3a3b8d..997ff559 100644 --- a/.github/workflows/build-installer.yml +++ b/.github/workflows/build-installer.yml @@ -6,7 +6,8 @@ on: workflow_dispatch: permissions: - contents: write + # Release write access is granted only to the protected publish job below. + contents: read env: # Opt JavaScript actions (actions/checkout, microsoft/setup-msbuild, @@ -17,9 +18,94 @@ env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" jobs: + release-tests: + name: Complete automated release gate + # Every UI fixture mutates profile state, so the release gate uses the same + # dedicated unlocked desktop as the existing Application Verifier lane. + runs-on: [self-hosted, windows, filemanager-ui] + permissions: + contents: read + + env: + BUILD_CONFIGURATION: Debug + SOLUTION_PATH: 'src\vcxproj\salamand.sln' + PLATFORM_TOOLSET: v145 + OPENSAL_BUILD_DIR: '${{ github.workspace }}\build_stage\' + FILEMANAGER_UI_ISOLATED: '1' + FILEMANAGER_UI_CONFIG_FAULT_INJECTION: '1' + FILEMANAGER_UI_RECYCLE_BIN: '1' + # Dedicated-volume roots and the runtime FTP command are configured as + # repository variables; strict skip handling blocks release if any is absent. + FILEMANAGER_UI_CROSS_VOLUME_ROOT: '${{ vars.FILEMANAGER_UI_CROSS_VOLUME_ROOT }}' + FILEMANAGER_UI_ADS_UNSUPPORTED_TARGET_ROOT: '${{ vars.FILEMANAGER_UI_ADS_UNSUPPORTED_TARGET_ROOT }}' + FILEMANAGER_UI_FTP_ORGANIZE_COMMAND: '${{ vars.FILEMANAGER_UI_FTP_ORGANIZE_COMMAND }}' + + steps: + - name: Checkout complete history + # Pin actions by reviewed commit to keep the release input graph immutable. + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + + - name: Setup MSBuild + uses: microsoft/setup-msbuild@6fb02220983dee41ce7ae257b6f4d8f9bf5ed4ce # v2 + + - name: Setup MSVC Developer Command Prompt (x64 toolchain) + uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1 + with: + arch: x64 + + - name: Build Debug x64 test artifacts + shell: pwsh + run: | + New-Item -ItemType Directory -Force -Path $env:OPENSAL_BUILD_DIR | Out-Null + msbuild $env:SOLUTION_PATH /m /t:Build /p:Configuration=$env:BUILD_CONFIGURATION /p:Platform=x64 /p:PlatformToolset=$env:PLATFORM_TOOLSET /p:PreferredToolArchitecture=x64 /nr:false + + - name: Resolve strict runner inputs + shell: pwsh + env: + PUSH_BASE_COMMIT: '${{ github.event.before }}' + run: | + # Push events provide the exact pre-push revision; manual releases + # compare with the first parent of the explicitly selected revision. + $baseCommit = $env:PUSH_BASE_COMMIT + if ([string]::IsNullOrWhiteSpace($baseCommit) -or $baseCommit -match '^0+$') { + $baseCommit = 'HEAD^' + } + git rev-parse --verify "$baseCommit^{commit}" | Out-Null + + $sqlite = Get-ChildItem $env:OPENSAL_BUILD_DIR -Filter sqlite.dll -Recurse | + Where-Object { $_.FullName -like '*Debug_x64*' } | Select-Object -First 1 + $executable = Get-ChildItem $env:OPENSAL_BUILD_DIR -Filter salamand.exe -Recurse | + Where-Object { $_.FullName -like '*Debug_x64*' } | Select-Object -First 1 + if ($null -eq $sqlite -or $null -eq $executable) { + throw 'The strict release runner requires Debug x64 sqlite.dll and salamand.exe artifacts.' + } + + "RELEASE_BASE_COMMIT=$baseCommit" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append + "SQLITE_TEST_DLL=$($sqlite.FullName)" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append + "FILEMANAGER_UI_EXE=$($executable.FullName)" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append + + - name: Run every automated test without skips + shell: pwsh + run: .\runtests.ps1 -BaseCommit $env:RELEASE_BASE_COMMIT -SqliteDll $env:SQLITE_TEST_DLL -FailOnSkipped + + - name: Upload Application Verifier logs + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: release-application-verifier-logs + path: TestResults\application-verifier-logs + if-no-files-found: warn + build: name: Build Installer (x64 Release) + # Publishing cannot begin until the unified runner has executed every test + # and rejected both failures and missing prerequisites. + needs: release-tests runs-on: windows-2022 + permissions: + contents: read env: BUILD_CONFIGURATION: Release @@ -37,39 +123,11 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + # Pin actions by reviewed commit to keep the release input graph immutable. + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup MSBuild in PATH (VS2022) - uses: microsoft/setup-msbuild@v2 - - - name: Download OpenSSL Libraries - shell: pwsh - run: | - $ErrorActionPreference = 'Stop' - - $opensslZip = Join-Path $env:RUNNER_TEMP "openssl.zip" - $uri = "https://intelitech.home.pl/install/openssl-1.0.2u-x64_86-win64.zip" - - $resp = Invoke-WebRequest -Uri $uri -OutFile $opensslZip -MaximumRedirection 10 -PassThru - Write-Host "HTTP status: $($resp.StatusCode)" - Write-Host "Content-Type: $($resp.Headers.'Content-Type')" - Write-Host "Downloaded bytes: $((Get-Item $opensslZip).Length)" - - # Validate: ZIP files start with 'PK' (0x50 0x4B). - $bytes = [System.IO.File]::ReadAllBytes($opensslZip) - if ($bytes.Length -lt 2) { - throw "OpenSSL download is unexpectedly short." - } - - if ($bytes[0] -ne 0x50 -or $bytes[1] -ne 0x4B) { - Write-Host "First bytes: $('{0:X2} {1:X2}' -f $bytes[0], $bytes[1])" - Write-Host "File head (as text):" - Get-Content -Path $opensslZip -TotalCount 20 | ForEach-Object { $_ } - throw "OpenSSL download is not a ZIP (likely received HTML). URL may have changed or requires different source." - } - - Expand-Archive -Path $opensslZip -DestinationPath "utils" -Force - Remove-Item $opensslZip + uses: microsoft/setup-msbuild@6fb02220983dee41ce7ae257b6f4d8f9bf5ed4ce # v2 - name: Build Solution (Release | x64) run: | @@ -77,12 +135,30 @@ jobs: # We do NOT pass /p:OutDir here, we let the project props use OPENSAL_BUILD_DIR msbuild $env:SOLUTION_PATH /m /t:Build /p:Configuration=$env:BUILD_CONFIGURATION /p:Platform=x64 /p:PlatformToolset=$env:PLATFORM_TOOLSET /p:PreferredToolArchitecture=x64 - - name: Install Inno Setup + - name: Install pinned Inno Setup + shell: pwsh run: | - choco install innosetup -y --no-progress - # Add Inno Setup to PATH - $innoPath = "C:\Program Files (x86)\Inno Setup 6" - echo "$innoPath" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + # The lock file makes a tool update an explicit reviewed source change. + $input = (Get-Content tools\release-inputs.json -Raw | ConvertFrom-Json).inputs.innoSetup + $installer = Join-Path $env:RUNNER_TEMP 'innosetup-installer.exe' + Invoke-WebRequest -Uri $input.url -OutFile $installer + $actualHash = (Get-FileHash -LiteralPath $installer -Algorithm SHA256).Hash.ToLowerInvariant() + if ($actualHash -ne $input.sha256) { + throw "Pinned Inno Setup SHA-256 mismatch: expected $($input.sha256), got $actualHash." + } + $signature = Get-AuthenticodeSignature -LiteralPath $installer + if ($signature.Status -ne 'Valid' -or $signature.SignerCertificate.Subject -notlike "*CN=$($input.publisher)*") { + throw "Pinned Inno Setup Authenticode verification failed: $($signature.Status) $($signature.SignerCertificate.Subject)" + } + & $installer /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP- + if ($LASTEXITCODE -ne 0) { throw "Inno Setup installation failed with exit code $LASTEXITCODE." } + $innoPath = 'C:\Program Files (x86)\Inno Setup 6' + $iscc = Join-Path $innoPath 'ISCC.exe' + if (-not (Test-Path -LiteralPath $iscc)) { throw "Pinned Inno Setup did not install $iscc." } + if ((Get-Item -LiteralPath $iscc).VersionInfo.ProductVersion -notmatch '^6\.7\.3') { + throw "Installed Inno Setup version does not match the locked version $($input.version)." + } + $innoPath | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - name: Prepare Installer Files run: | @@ -107,12 +183,45 @@ jobs: $sourcePath = Split-Path -Leaf $env:INSTALLER_STAGING_DIR iscc.exe /DSourcePath="$sourcePath" /DBuildNumber=${{ github.run_number }} "Installer\setup.iss" + - name: Preserve verified installer for the publish job + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + # Artifact immutability prevents publish from receiving a rebuilt installer. + name: release-installer-${{ github.sha }} + path: Installer\Output\OpenSalamander_6.0.${{ github.run_number }}.exe + if-no-files-found: error + + - name: Preserve private symbols for crash analysis + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + # Keep PDBs out of the public release while retaining exact-build diagnostics. + name: private-symbols-${{ github.sha }} + path: ${{ env.OPENSAL_BUILD_DIR }}\**\*.pdb + if-no-files-found: error + retention-days: 180 + + publish: + name: Publish verified installer + needs: build + runs-on: windows-2022 + environment: production + permissions: + contents: write + + steps: + - name: Retrieve the immutable installer + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + name: release-installer-${{ github.sha }} + path: Installer\Output + - name: Create Release - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@e5f48c3b7b613b7a911e58a617b757074c4c790d # v2.0.2 with: - tag_name: 5.0.${{ github.run_number }} - name: Open Salamander 5.0.${{ github.run_number }} - files: Installer\Output\OpenSalamander_5.0.${{ github.run_number }}.exe + # Keep the release-managed major separate from GitHub's automatic run number. + tag_name: 6.0.${{ github.run_number }} + name: Open Salamander 6.0.${{ github.run_number }} + files: Installer\Output\OpenSalamander_6.0.${{ github.run_number }}.exe draft: false prerelease: false env: diff --git a/.github/workflows/nightly-lock-stress.yml b/.github/workflows/nightly-lock-stress.yml new file mode 100644 index 00000000..897eafb6 --- /dev/null +++ b/.github/workflows/nightly-lock-stress.yml @@ -0,0 +1,78 @@ +name: Nightly Lock Stress + +on: + schedule: + - cron: '17 2 * * *' + workflow_dispatch: + +permissions: + contents: read + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" + BUILD_CONFIGURATION: Debug + SOLUTION_PATH: 'src\\vcxproj\\salamand.sln' + PLATFORM_TOOLSET: v145 + OPENSAL_BUILD_DIR: '${{ github.workspace }}\\build_stage\\' + +jobs: + verifier-lock-stress: + # FlaUI needs an unlocked desktop and an isolated profile; this label is deliberately not a hosted runner. + runs-on: [self-hosted, windows, filemanager-ui] + + steps: + - name: Checkout + # Pin actions by reviewed commit to keep the nightly diagnostic lane reproducible. + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Setup MSBuild + uses: microsoft/setup-msbuild@6fb02220983dee41ce7ae257b6f4d8f9bf5ed4ce # v2 + + - name: Setup MSVC Developer Command Prompt (x64 toolchain) + uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1 + with: + arch: x64 + + - name: Build Debug x64 executable + shell: pwsh + run: | + msbuild $env:SOLUTION_PATH /m /t:Build /p:Configuration=$env:BUILD_CONFIGURATION /p:Platform=x64 /p:PlatformToolset=$env:PLATFORM_TOOLSET /p:PreferredToolArchitecture=x64 /nr:false + + - name: Run Application Verifier and PageHeap UI suite + shell: pwsh + run: | + $appVerifier = Get-Command appverif.exe -ErrorAction SilentlyContinue + if ($null -eq $appVerifier) { + $appVerifier = Get-ChildItem 'C:\Program Files (x86)\Windows Kits\10\Debuggers' -Filter appverif.exe -Recurse -ErrorAction SilentlyContinue | + Select-Object -First 1 + } + if ($null -eq $appVerifier) { + throw 'Application Verifier is required on the filemanager-ui runner.' + } + $appVerifierPath = if ($appVerifier -is [System.Management.Automation.CommandInfo]) { + $appVerifier.Source + } else { + $appVerifier.FullName + } + + $executable = Get-ChildItem $env:OPENSAL_BUILD_DIR -Filter salamand.exe -Recurse | + Where-Object { $_.FullName -like '*Debug_x64*' } | + Select-Object -First 1 + if ($null -eq $executable) { + throw 'The Debug x64 salamand.exe build output was not found.' + } + + # The self-hosted runner account is dedicated to this lane, which makes profile-mutating UI tests safe. + $env:FILEMANAGER_UI_ISOLATED = '1' + $env:FILEMANAGER_UI_EXE = $executable.FullName + .\tools\run-lock-verifier-stress.ps1 -ExecutablePath $executable.FullName ` + -TestProject '.\tests\FileManager.UiTests\FileManager.UiTests.csproj' -AppVerifierPath $appVerifierPath ` + -LogOutputDirectory '.\application-verifier-logs' -VerifierProfile Full -TestFilter 'TestCategory=UI' + + - name: Upload Application Verifier logs + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: application-verifier-pageheap-logs + path: application-verifier-logs + if-no-files-found: warn diff --git a/.github/workflows/pr-comments-guard.yml b/.github/workflows/pr-comments-guard.yml index 50367d7c..0b2af68f 100644 --- a/.github/workflows/pr-comments-guard.yml +++ b/.github/workflows/pr-comments-guard.yml @@ -64,7 +64,8 @@ jobs: - name: Checkout PR if: steps.label_check.outputs.should-skip != 'true' - uses: actions/checkout@v4 + # Pin checkout because this pull_request_target job reads untrusted PR content. + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 0 @@ -206,7 +207,6 @@ jobs: 'src/plugins/automation/generated', 'src/plugins/shared/lukas', 'src/plugins/ftp', - 'src/plugins/ftp/openssl', 'src/plugins/ieviewer/cmark-gfm/build/src', 'src/plugins/ieviewer/cmark-gfm/build/extensions', 'src/plugins/ieviewer/cmark-gfm/extensions', diff --git a/.github/workflows/pr-msbuild.yml b/.github/workflows/pr-msbuild.yml index 40e05995..d1759b9a 100644 --- a/.github/workflows/pr-msbuild.yml +++ b/.github/workflows/pr-msbuild.yml @@ -48,15 +48,16 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + # Pin actions by reviewed commit to keep PR execution independent of moved tags. + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: path: s - name: Setup MSBuild in PATH (VS2022) - uses: microsoft/setup-msbuild@v2 + uses: microsoft/setup-msbuild@6fb02220983dee41ce7ae257b6f4d8f9bf5ed4ce # v2 - name: Setup MSVC Developer Command Prompt (x64 toolchain) - uses: ilammy/msvc-dev-cmd@v1 + uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1 with: arch: x64 @@ -66,8 +67,45 @@ jobs: git fetch --no-tags --depth=1 origin ${{ github.event.pull_request.base.ref }} .\tools\verify-no-new-terminatethread.ps1 -BaseCommit origin/${{ github.event.pull_request.base.ref }} + - name: Reject new raw thread creation + if: github.event_name == 'pull_request' + run: | + git fetch --no-tags --depth=1 origin ${{ github.event.pull_request.base.ref }} + .\tools\verify-no-new-raw-thread-creation.ps1 -BaseCommit origin/${{ github.event.pull_request.base.ref }} + + - name: Reject new fixed MAX_PATH buffers + if: github.event_name == 'pull_request' + run: | + git fetch --no-tags --depth=1 origin ${{ github.event.pull_request.base.ref }} + .\tools\verify-no-new-max-path-buffers.ps1 -BaseCommit origin/${{ github.event.pull_request.base.ref }} + + - name: Reject new unchecked string-copy and formatting calls + if: github.event_name == 'pull_request' + run: | + git fetch --no-tags --depth=1 origin ${{ github.event.pull_request.base.ref }} + .\tools\verify-no-new-unsafe-string-calls.ps1 -BaseCommit origin/${{ github.event.pull_request.base.ref }} + + - name: Verify asynchronous operation completion protocol + run: .\tools\verify-operation-completion-protocol.ps1 + + - name: Verify durable copy commit protocol + run: .\tools\verify-durable-copy-commit.ps1 + + - name: Exercise zlib compatibility and corrupt-input vectors + if: matrix.platform == 'x64' + run: .\tools\test-zlib-compatibility.ps1 + + - name: Exercise bzip2 golden archives, truncation, and fuzz corpus + if: matrix.platform == 'x64' + run: .\tools\test-bzip2-compatibility.ps1 + + - name: Exercise cmark-gfm snapshots, limits, and extension fuzz combinations + if: matrix.platform == 'x64' + run: .\tools\test-cmark-gfm-hardening.ps1 + - name: Register MSVC problem matcher - uses: ammaraskar/msvc-problem-matcher@master + # This reviewed commit replaces the mutable master branch in PR builds. + uses: ammaraskar/msvc-problem-matcher@541aa4360062d76d9620af6b8bd35cfa0c5ef19f - name: Build via MSBuild (Debug | ${{ matrix.platform }}) run: | @@ -99,9 +137,14 @@ jobs: exit $LASTEXITCODE } + - name: Exercise SQLite WAL recovery and corrupt-page detection + # The native DLL must be built first; this x64 probe owns only a GUID-named temporary database. + if: matrix.platform == 'x64' + run: .\tools\test-sqlite-recovery.ps1 -SqliteDll .\src\vcxproj\sqlite\salamander\Debug_x64\utils\sqlite.dll + - name: Upload build logs if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: msbuild-${{ matrix.platform }}-debug-logs path: | diff --git a/.github/workflows/pr-squash-guard.yml b/.github/workflows/pr-squash-guard.yml index 1d567cba..23097e71 100644 --- a/.github/workflows/pr-squash-guard.yml +++ b/.github/workflows/pr-squash-guard.yml @@ -20,7 +20,8 @@ jobs: runs-on: ubuntu-latest steps: - name: Check commit count and fail if necessary - uses: actions/github-script@v7 + # Pin the action so the policy guard cannot inherit a moved release tag. + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 with: script: | const targetAuthors = ["Lemi257", "bellus869"]; diff --git a/.gitignore b/.gitignore index 52164fc9..7e1bc882 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,6 @@ nul *.backup .claude .vscode +.dotnet-cli +.nuget-packages +tests/FileManager.UiTests/obj diff --git a/CZECH_TRANSLATION_VERIFICATION_FINAL.md b/CZECH_TRANSLATION_VERIFICATION_FINAL.md deleted file mode 100644 index 71c620ca..00000000 --- a/CZECH_TRANSLATION_VERIFICATION_FINAL.md +++ /dev/null @@ -1,252 +0,0 @@ -# Czech Comment Translation Verification Report - -**Project:** FileManager (Open Salamander) -**Directory:** C:\Projects\FileManager\src -**Date:** 2026-01-15 -**Verification Status:** ❌ **INCOMPLETE** - ---- - -## Executive Summary - -A comprehensive verification of the C:\Projects\FileManager\src directory structure reveals that **Czech comment translation is NOT complete**. The codebase still contains a **significant number of untranslated Czech comments**. - -### Key Findings - -- **Total Files with Czech Comments:** 94 files (unique) -- **Total Czech Single-Line Comments (//):** 3,104 comments across 86 files -- **Total Czech Multi-Line Comments (/* */):** 42 comments across 28 files -- **Grand Total Czech Comments:** 3,146 comments -- **Additional Czech keywords found:** 900+ additional matches with extended keyword search -- **Translation Completion:** Approximately **0%** (based on comprehensive search) - ---- - -## Verification Methodology - -### Search Patterns Used - -**Primary Czech Keywords Searched:** -``` -pokud, nebo, pouze, pro je, muze, musi, bude, podle, obsahuje, -adresar, soubor, cesta, jinak, vraci, prida, odstrani, konci, -delka, velikost, buffer, retezec, jmeno, pripona, atd, pokial, -nastavena, promenna, dostupny, mozne, nutne, puvodni, volani, hodnot -``` - -**Extended Keywords (Secondary Verification):** -``` -ktery, kde, jak, vsechny, kazdy, tento, tato, pouzije, pouziva, -napr, atribut, funkce, parametr, vrati -``` - -**File Types Scanned:** -- All .cpp files -- All .h header files -- All .c files - -**Exclusions:** -- Language resource files (*.slg) -- Directories: language/czech/, language/slovak/ -- Test data strings (e.g., "testíček" in commented test code) - -### Search Methods - -1. **Single-line comment search:** `//.*\b(czech_keywords)\b` -2. **Multi-line comment search:** `/\*[\s\S]*?\*/` containing Czech keywords -3. **Case-insensitive matching** to catch all variations -4. **Recursive directory scanning** across entire src/ tree - ---- - -## Most Affected Files - -### Top 20 Files by Czech Comment Count - -| Count | File Path | -|------:|:----------| -| 997 | `src\plugins\shared\spl_gen.h` | -| 352 | `src\plugins\shared\spl_fs.h` | -| 207 | `src\plugins\shared\spl_com.h` | -| 146 | `src\plugins\shared\spl_base.h` | -| 138 | `src\plugins\shared\spl_gui.h` | -| 108 | `src\plugins\shared\spl_file.h` | -| 99 | `src\salamdr1.cpp` | -| 74 | `src\salamdr3.cpp` | -| 47 | `src\plugins\shared\spl_arc.h` | -| 46 | `src\shellsup.cpp` | -| 45 | `src\menu.h` | -| 44 | `src\gui.h` | -| 40 | `src\sort.cpp` | -| 39 | `src\fileswnb.cpp` | -| 37 | `src\icncache.cpp` | -| 31 | `src\toolbar.h` | -| 28 | `src\tasklist.h` | -| 27 | `src\iconlist.h` | -| 27 | `src\stswnd.cpp` | -| 26 | `src\shellib.h` | - -**Critical Finding:** The `src\plugins\shared\` directory contains the majority of untranslated comments, particularly in the plugin SDK header files (spl_*.h). - ---- - -## Examples of Czech Comments Found - -### From `src\plugins\shared\spl_gen.h`: -```cpp -// pokud se pouziva CheckBoxText, bude v nem vyhledan oddelovac \t a zobrazen jako hint -#define PATH_TYPE_WINDOWS 1 // windowsova cesta ("c:\path" nebo UNC cesta) -#define PATH_TYPE_ARCHIVE 2 // cesta do archivu (archiv lezi na windowsove ceste) -#define PATH_TYPE_FS 3 // cesta na pluginovy file-system -#define GFN_TOOLONGPATH 3 // operaci by vznikla prilis dlouha cesta -#define GFN_EMPTYNAMENOTALLOWED 6 // prazdny retezec 'name' -``` - -### From `src\fileswnb.cpp`: -```cpp -// pouze predame hlavnimu oknu -// pokud je nad timto panelem otevrene Alt+F1(2) menu a RClick patri jemu, -// nebo hlaseni konce cteni ikonek (muze prijit pozde) -// pokud doslo ke zmene cesty (nejspis nekdo prave smazal adresar) -``` - -### From `src\salamdr1.cpp`: -```cpp -// obsahuje informace o souboru nebo adresari -// vraci TRUE pokud byla operace uspesna -// nastavena hodnota na FALSE -``` - ---- - -## Affected Directory Structure - -### Distribution by Directory - -| Directory | Status | -|:----------|:-------| -| `src\` (root) | ⚠️ Many files with Czech comments | -| `src\plugins\shared\` | ❌ **HEAVILY AFFECTED** - ~2,000+ comments | -| `src\common\` | ⚠️ Multiple files affected | -| `src\plugins\shared\lukas\` | ⚠️ Several files affected | -| `src\salopen\`, `src\salspawn\` | ⚠️ Minor presence | - ---- - -## Recently Modified Files (git status) - -The following files show as modified in git but **still contain Czech comments**: - -``` -M src/common/handles.cpp -M src/common/messages.cpp -M src/common/moore.cpp -M src/common/multimon.cpp -M src/common/regexp.cpp -M src/common/sheets.cpp -M src/common/strutils.h -M src/common/trace.cpp -M src/common/winlib.cpp -M src/common/winlib.h -M src/plugins/shared/auxtools.h -M src/plugins/shared/dbg.h -M src/plugins/shared/lukas/messages.cpp -M src/plugins/shared/lukas/utilaux.cpp -M src/plugins/shared/lukas/utilbase.cpp -M src/plugins/shared/lukas/utildlg.cpp -M src/plugins/shared/mhandles.h -M src/plugins/shared/spl_arc.h -M src/plugins/shared/spl_base.h -M src/plugins/shared/spl_com.h -M src/plugins/shared/spl_file.h -M src/plugins/shared/spl_fs.h -M src/plugins/shared/spl_gen.h -M src/plugins/shared/spl_gui.h -M src/plugins/shared/spl_menu.h -M src/plugins/shared/spl_thum.h -M src/plugins/shared/spl_vers.h -M src/plugins/shared/spl_view.h -M src/plugins/shared/winliblt.h -``` - -**Note:** Many of these files contain significant numbers of Czech comments despite being recently modified. - ---- - -## Complete File List - -For a complete list of all 94 files containing Czech comments with exact counts, see: -- `czech_files_complete_list.txt` (detailed breakdown) - ---- - -## Validation Notes - -### What Was Excluded (Correctly) - -1. **Test Data:** Comments containing test strings like `"testíček"` that are part of test code examples were correctly identified as test data, not comments requiring translation. - -2. **Examples:** - - `src\plugins\demoplug\demoplug.cpp`: Contains `"testíček"` as test string parameter - - `src\menu2.cpp`: Contains `"á"` as character encoding example - -3. **Language Resources:** All .slg files and language-specific directories were excluded from the scan. - -### False Positives: NONE - -The verification included manual spot-checks of identified files. All flagged comments contain genuine Czech text requiring translation. - ---- - -## Recommendations - -### Priority 1: Plugin SDK Headers (CRITICAL) -The `src\plugins\shared\spl_*.h` files contain ~2,000 Czech comments and are **public API documentation**. These should be translated first as they: -- Define the plugin interface -- Are referenced by external plugin developers -- Represent the largest concentration of untranslated content - -**Files requiring immediate attention:** -1. `spl_gen.h` (997 comments) -2. `spl_fs.h` (352 comments) -3. `spl_com.h` (207 comments) -4. `spl_base.h` (146 comments) -5. `spl_gui.h` (138 comments) -6. `spl_file.h` (108 comments) - -### Priority 2: Main Source Files -- `salamdr1.cpp`, `salamdr3.cpp`, `salamdr5.cpp` -- `fileswnb.cpp` -- `shellsup.cpp` -- Core UI files (`gui.h`, `menu.h`, `toolbar.h`) - -### Priority 3: Remaining Files -- Common utilities (`src\common\*`) -- Shared plugin utilities (`src\plugins\shared\lukas\*`) -- Helper applications - ---- - -## Conclusion - -**Translation Status: INCOMPLETE ❌** - -The comprehensive verification confirms that **Czech comments have NOT been fully translated** in the C:\Projects\FileManager\src directory structure. With **3,146+ identified Czech comments** across 94 files, and the bulk concentrated in critical plugin SDK headers, **significant translation work remains**. - -The current state represents **~0% completion** for comment translation, as the most critical files (plugin SDK headers) contain the majority of untranslated content in their original Czech form. - ---- - -## Appendix: Verification Scripts - -The following PowerShell scripts were created for this verification: -1. `find_czech_comments.ps1` - Single-line comment scanner -2. `find_czech_multiline.ps1` - Multi-line comment scanner -3. `find_additional_czech.ps1` - Extended keyword verification - -These scripts can be re-run at any time to verify translation progress. - ---- - -**End of Report** - diff --git a/Installer/setup.iss b/Installer/setup.iss index d2c0f2aa..7c829bf6 100644 --- a/Installer/setup.iss +++ b/Installer/setup.iss @@ -2,12 +2,12 @@ ; This installer maintains all features from the previous custom installer #define MyAppName "Open Salamander" -#define MyAppVersion "5.0" +#define MyAppVersion "6.0" #define MyAppPublisher "Taskscape Ltd" #define MyAppURL "https://www.opensalamander.com/" #define MyAppExeName "salamand.exe" -; Build number will be passed from command line during CI build +; The product major is release-managed; the build number remains supplied by CI. #ifndef BuildNumber #define BuildNumber "0" #endif @@ -54,6 +54,7 @@ Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescrip ; Main executables Source: "{#SourcePath}\salamand.exe"; DestDir: "{app}"; Flags: ignoreversion Source: "{#SourcePath}\salmon.exe"; DestDir: "{app}"; Flags: ignoreversion +Source: "{#SourcePath}\salbroker.exe"; DestDir: "{app}"; Flags: ignoreversion ; Shell extensions live under utils and are registered by Salamander on first run. Source: "{#SourcePath}\utils\salextx64.dll"; DestDir: "{app}\utils"; Flags: ignoreversion @@ -70,10 +71,6 @@ Source: "{#SourcePath}\salpvenv.exe"; DestDir: "{app}"; Flags: ignoreversion ski Source: "{#SourcePath}\fcremote.exe"; DestDir: "{app}"; Flags: ignoreversion skipifsourcedoesntexist Source: "{#SourcePath}\7zwrapper.exe"; DestDir: "{app}"; Flags: ignoreversion skipifsourcedoesntexist -; OpenSSL libraries for FTP encryption support -Source: "{#SourcePath}\utils\libeay32.dll"; DestDir: "{app}\utils"; Flags: ignoreversion skipifsourcedoesntexist -Source: "{#SourcePath}\utils\ssleay32.dll"; DestDir: "{app}\utils"; Flags: ignoreversion skipifsourcedoesntexist - ; License file Source: "{#SourcePath}\LICENSE"; DestDir: "{app}"; Flags: ignoreversion diff --git a/README.md b/README.md index 7d743775..4c13091d 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,10 @@ Solution ```\src\vcxproj\salamand.sln``` may be built from within Visual Studio Use ```\src\vcxproj\!populate_build_dir.cmd``` to populate build directory with files required to run Open Salamander. +### Automated testing + +See [testing.md](testing.md) for test prerequisites, commands, safety requirements, CI coverage, and a short description of every automated test available in the repository. + ### Creating Installer Open Salamander uses [Inno Setup](https://jrsoftware.org/isinfo.php) to create the installer. The installer script is located at `Installer\setup.iss`. @@ -72,13 +76,14 @@ The installer will be created in `Installer\Output\`. The repository includes a GitHub Actions workflow (`.github\workflows\build-installer.yml`) that automatically: -1. Builds the solution using MSBuild -2. Stages all required files (executables, plugins, language files, toolbars) -3. Installs Inno Setup via Chocolatey -4. Compiles the installer with build number versioning -5. Creates a GitHub release with the installer attached +1. Runs the complete `runtests.ps1` inventory without skipped tests on the dedicated release-test runner +2. Builds the solution using MSBuild +3. Stages all required files (executables, plugins, language files, toolbars) +4. Downloads the version- and SHA-256-pinned Inno Setup input and verifies its Authenticode signature +5. Compiles the installer with build number versioning +6. Creates a GitHub release with the installer attached -The workflow is triggered on pushes to `main` and produces versioned installers named `OpenSalamander_5.0.{build_number}.exe`. +The workflow is triggered on pushes to `main` and produces versioned installers named `OpenSalamander_6.0.{build_number}.exe`. ## Customization @@ -124,7 +129,7 @@ This project welcomes contributions to build and enhance Open Salamander! \translations Translations into other languages ``` -A few Open Salamander 5.0 plugins are either not included or cannot be compiled. For instance, the PictView engine ```pvw32cnv.dll``` is not open-sourced, so we should consider switching to [WIC](https://learn.microsoft.com/en-us/windows/win32/wic/-wic-about-windows-imaging-codec) or another library. The Encrypt plugin is incompatible with modern SSD disks and has been deprecated. The UnRAR plugin lacks [unrar.dll](https://www.rarlab.com/rar_add.htm), and the FTP plugin is missing [OpenSSL](https://www.openssl.org/) libraries. Both issues are solvable as both projects are open source. To build WinSCP plugin you need Embarcadero C++ Builder. +A few Open Salamander 5.0 plugins are either not included or cannot be compiled. For instance, the PictView engine ```pvw32cnv.dll``` is not open-sourced, so we should consider switching to [WIC](https://learn.microsoft.com/en-us/windows/win32/wic/-wic-about-windows-imaging-codec) or another library. The Encrypt plugin is incompatible with modern SSD disks and has been deprecated. The UnRAR plugin lacks [unrar.dll](https://www.rarlab.com/rar_add.htm). The FTP plugin uses Windows SChannel for FTPS and does not require bundled TLS DLLs. To build WinSCP plugin you need Embarcadero C++ Builder. All the source code uses UTF-8-BOM encoding and is formatted with ```clang-format```. Refer to the ```\normalize.ps1``` script for more information. diff --git a/VS2026_MIGRATION_PLAN.md b/VS2026_MIGRATION_PLAN.md deleted file mode 100644 index df02c326..00000000 --- a/VS2026_MIGRATION_PLAN.md +++ /dev/null @@ -1,273 +0,0 @@ -# Visual Studio 2026 Migration Plan - -Tracking the move of the FileManager (Open Salamander) solution from the **v143 (VS 2022)** -build toolset to building under **Visual Studio 2026**. - -**Chosen approach: Option A** — keep the projects targeting `v143` and make VS 2026 use the -v143 toolset (install the v143 component). This is the lowest-risk path: no project/code -changes, builds are bit-for-bit identical. Option B (retarget to VS 2026's `v145` toolset) is -documented at the bottom as the future alternative. - ---- - -## How to read this file - -Each task is a checkbox: - -- `- [ ]` = **outstanding** (not done yet) -- `- [x]` = **done** (with a short note on what was verified/changed and by whom: "agent" = done - by Claude in-repo; "you" = a manual/GUI/installer step the agent cannot perform) -- `- [~]` = **partially done / blocked** — see the inline note for what remains - -A task done by the **agent** means the change is already in the working tree (e.g. files deleted, -findings recorded). A task marked **(you)** is a manual step (Visual Studio Installer, opening the -IDE, building) that must be performed on the machine — the agent cannot drive the GUI installer. - -When you finish a manual task, change its `- [ ]` to `- [x]` and add a dated note. -"Outstanding work" at any time = every box that is still `- [ ]` or `- [~]`. - -Last updated: 2026-06-20 (agent). - ---- - -## Findings (investigation results — reference) - -- **Cause of the warnings**: the solution is opened in **Visual Studio 2026 Insiders** - (`C:\Program Files\Microsoft Visual Studio\18\Insiders`) but every project targets - `PlatformToolset = v143`, and VS 2026 does **not** ship/own v143. The warnings/“Designtime build - failed … IntelliSense might be unavailable” are purely a missing-toolset problem, **not** a code problem. -- **VS 2026 toolset**: token **`v145`**, MSBuild VCTargets **`v180`**, **MSVC 14.51.36231**. - Only `v145` is present under the VS 2026 install. -- **VS 2022 Community is also installed** (`...\2022\Community`) with MSVC **14.44.35207** (= v143), - so the code already builds with v143; only VS 2026 lacks the toolset. -- **Toolset usage across the repo (per-project; not centralized in any `.props`):** - - **Now uniformly `v143`** (352 entries) after the cleanup below. Originally: `v143` ×350, - `v142` ×2 (`reglib`), `v140` ×8 (legacy `vcproj2015_manison`). - - `v142` (`src/reglib/REGLIBA.vcxproj`, a standalone app in its own `regliba.sln`) → **retargeted to `v143`** (agent). - - `v140` (`src/plugins/portables/vcproj2015_manison/*`, legacy VS2015 duplicate) → **retired** (agent). - - **Net effect: VS 2026 needs exactly ONE toolset component — `v143`.** -- **`WindowsTargetPlatformVersion = 10.0`** everywhere → "use latest installed SDK". Installed SDKs: - `10.0.19041.0`, `10.0.22621.0`, `10.0.26100.0` → resolves fine under VS 2026. **No SDK change needed.** -- **`LanguageStandard`**: 318× `stdcpplatest`, 12× `stdcpp14`, 4× `stdcpp17`, 4× `stdcpp20`. - (Only relevant to Option B — `stdcpplatest` + MSVC 14.51 is where new conformance errors could appear.) -- **Solution format**: `salamand.sln` is stamped Format 12 / "Version 17" (87 project refs). VS 2026 - will rewrite the version stamp on save (cosmetic). - ---- - -## Option A — make VS 2026 build with the existing v143 toolset - -### Tasks - -- [x] **Confirm a Windows 11 SDK is installed** so `WindowsTargetPlatformVersion=10.0` resolves under - VS 2026. — agent: found `10.0.19041.0`, `10.0.22621.0`, `10.0.26100.0`. -- [x] **Confirm v143 exists on the machine** (proves the toolset can be added to VS 2026). — agent: - VS 2022 Community has MSVC 14.44.35207 (v143). -- [x] **Retire the dead `vcproj2015_manison` (v140) project set** so VS 2026 doesn't trip over a v140 - toolset it can't load. — agent: verified no external references in any `.sln/.vcxproj/.props/script`, - then `git rm -r src/plugins/portables/vcproj2015_manison/` (staged deletion, **not committed**). The - live portables project remains at `src/plugins/portables/vcxproj/portables.vcxproj` (v143). -- [x] **v143 toolset installed into VS 2026.** — agent (elevated): installed component - **`Microsoft.VisualStudio.Component.VC.14.44.17.14.x86.x64`** (MSVC 14.44 / 17.14 = **v143**, latest - v143; matches the 14.44 already in the side-by-side VS 2022). Result: VS 2026 now has MSVC - `14.44.35207` (v143) alongside `14.51.36231` (v145), and the **`v143` PlatformToolset** is registered - (`...\18\Insiders\MSBuild\Microsoft\VC\v170\...\PlatformToolsets\v143`). Installer exit `3010` = - success, **reboot recommended**. - - **IMPORTANT — correct component ID**: in VS 2026 the generic `VC.Tools.x86.x64` ("latest") means - **v145**, not v143. To get v143 you must use the **fixed-version side-by-side** component - `VC.14.44.17.14.x86.x64`. (My first attempt used a non-existent id `VC.143.x86.x64`, which the - installer silently treated as a no-op — do not use that.) - - Command used (run from an **elevated** prompt; quote the spaced path or it gets truncated): - ``` - "C:\Program Files (x86)\Microsoft Visual Studio\Installer\vs_installer.exe" modify --installPath "C:\Program Files\Microsoft Visual Studio\18\Insiders" --add Microsoft.VisualStudio.Component.VC.14.44.17.14.x86.x64 --quiet --norestart - ``` - - GUI equivalent: Installer → VS Community 2026 → Modify → **Individual components** → search "14.44" - → check **"MSVC v143 - VS 2022 C++ x64/x86 build tools (v14.44-17.14)"**. -- [x] **Toolset resolution verified via command-line build.** — agent: built `src\vcxproj\sqlite\sqlite.vcxproj` - (`Debug|x64`) with VS 2026's MSBuild (`...\18\Insiders\MSBuild\Current\Bin\MSBuild.exe`) → - **Build succeeded, 0 warnings, 0 errors**, no "build tools for v143 cannot be found". Confirms VS 2026 - now resolves `v143`. -- [ ] **(you) Reload the solution in VS 2026** and confirm the IDE warnings are gone / IntelliSense works - (the underlying cause is fixed; this just refreshes the IDE — a restart also clears the 3010 reboot flag). -- [x] **Full `salamand.sln` build (`Debug|x64`) run via VS 2026 MSBuild.** — agent: **`salamand.exe` - (7.8 MB) + 33 plugins built clean** (0 warnings). Only **2 projects fail — `pictview` and `portables`** - — and solely on `atlbase.h` (the missing ATL component above), not on the toolset. This validates the - v143 migration end-to-end. It also surfaced and fixed a regression in `src/utf8gui.h` (an unused - `ListBoxAddStringUtf8` helper used the undeclared `LB_SETUNICODEFORMAT`; removed — unstaged change). -- [ ] **(you) After installing ATL**, rebuild to confirm `pictview`/`portables` compile, and build the - other configs (`Release|x64`, `Debug|Win32`, `Release|Win32`) for full coverage. -- [x] **`reglib` (`REGLIBA.vcxproj`) handled** — agent: it's a standalone **Application** project in its - own `regliba.sln` (not in `salamand.sln`; only `!clear.bat` references it). Retargeted `v142 → v143` - so the whole repo needs only the single v143 component (no separate v142 install). Edit is unstaged. - -### Requirements / assumptions to verify - -- [x] **v143 IS available in VS 2026 and is now installed.** agent: VS 2026 offers v143 only as - **fixed-version side-by-side** components (`VC.14.30.17.0` … `VC.14.44.17.14`); the generic - `VC.Tools.x86.x64` = v145. Installed `VC.14.44.17.14.x86.x64` (latest v143). Confirmed by the clean - `sqlite` build above. -- [x] **MFC not needed; ATL IS needed.** — agent: all 16 `` are `false` (no MFC). BUT - `pictview` and `portables` directly `#include `, so they need the v143 **ATL** component - **`Microsoft.VisualStudio.Component.VC.14.44.17.14.ATL`**. (My earlier "ATL not needed" only checked - `` and was wrong — corrected after the build surfaced `atlbase.h` errors.) - - **Install status: BLOCKED in this automated context.** The elevated `vs_installer --add ...ATL` - returns exit **8006** because the installer's online feed refresh (`aka.ms/vs/channels`, - `aka.ms/vs/installer/latest/feed`) is **canceled** here (`"Failed to update the latest installer - feed: A task was canceled"`). Package downloads work (the v143 toolset installed fine), but the - aka.ms feed step fails. **(you)**: install ATL via the **GUI Installer** (handles the feed - interactively) — search "14.44" → "MSVC v143 ATL (v14.44-17.14)" — or retry the CLI when the - network/feed is reachable. -- [x] **Spectre libs not needed** — agent: no `` element in any project (default off). -- [x] **WindowsTargetPlatformVersion = 10.0 resolves** — verified (SDK 10.0.26100.0 present). -- [ ] **(you) Confirm the team's intended primary IDE.** If VS 2026 is meant to fully replace VS 2022, - plan Option B (below) as a follow-up so the repo no longer depends on the legacy v143 component. - ---- - -## Status summary - -**Done (agent):** findings recorded; SDK + v143 presence verified; `vcproj2015_manison` retired; -`reglib` retargeted `v142 → v143`; MFC/ATL and Spectre confirmed not required. Result: the repo now -uses a **single toolset, `v143`** (352 entries), so VS 2026 needs exactly one component. All changes -are **unstaged** on the current branch (nothing committed). No main-app `.vcxproj` toolset values -changed — Option A deliberately keeps v143. - -**Done (agent, elevated):** installed v143 (`VC.14.44.17.14.x86.x64`) into VS 2026; verified the `v143` -PlatformToolset registered. Ran a full `salamand.sln` `Debug|x64` build via VS 2026 MSBuild: -**`salamand.exe` + 33 plugins build clean (0 warnings)**; only `pictview` + `portables` fail, solely on -the missing **ATL** component. Fixed a regression in `src/utf8gui.h` found by the build. The reported -"build tools for v143 cannot be found" cause is **resolved** and the migration is validated. - -**Outstanding:** -1. **(you) Install the v143 ATL component** (`...VC.14.44.17.14.ATL`) — blocked here by an installer - feed-cancellation (exit 8006); do it via the GUI Installer. Needed only by `pictview` + `portables`. -2. **(you) Reboot / restart VS 2026** (the v143 install reported 3010 = reboot recommended) and reload - the solution; confirm IDE warnings/IntelliSense are clear. -3. **(you) After ATL**, rebuild (all 4 configs) to confirm a fully clean solution. - ---- - -## Option B — retarget to VS 2026's `v145` toolset (IN PROGRESS) - -Goal: build natively on VS 2026's own toolset (`v145` = MSVC 14.51) so the repo no longer depends on -the side-by-side v143 component. Same checkbox legend as the top of this file -(`[ ]` outstanding, `[x]` done, `[~]` partial/blocked; **agent** = done in-repo, **you** = manual). -This section is updated as work is done and as new work is discovered. - -### Discoveries (Option B specifics) - -- **`v145` = MSVC 14.51.36231**; VCTargets `v180`. Desktop PlatformToolset token is **`v145`**. -- **Prerequisite — the desktop v145 toolset is NOT installed.** As shipped here, VS 2026 has only the - **v143** desktop toolset (the one installed for Option A → MSVC 14.44); there is **no** - `Microsoft.VCToolsVersion.v145.default.txt` and no desktop `v145` PlatformToolset folder (only a - *Windows Store* `v145` exists). MSVC 14.51 binaries are present but not wired as a desktop toolset. - So a retarget to `v145` requires installing the v145 desktop toolset first. -- **Component IDs (from the VS 2026 catalog):** desktop toolset - `Microsoft.VisualStudio.Component.VC.14.51.x86.x64`; ATL `Microsoft.VisualStudio.Component.VC.14.51.ATL` - (needed by `pictview` + `portables`). MFC/Spectre still not needed. -- **Installer risk:** the same elevated `vs_installer` path can return exit **8006** (aka.ms feed - refresh "A task was canceled") — see Option A. If blocked, install via the GUI Installer. -- **Conformance risk (the big one):** MSVC 14.51 + the repo's pervasive `stdcpplatest` - is much stricter than v143/14.44 on this decades-old C++ — expect new errors/warnings needing code - fixes (iterative build-and-fix). v145 is also a **prerelease** toolset. - -### Tasks - -- [x] **Install the v145 desktop toolset + ATL into VS 2026** — agent (elevated): `VC.14.51.x86.x64` + - `VC.14.51.ATL`, exit 0. `Microsoft.VCToolsVersion.v145.default.txt` and the desktop `v145` - PlatformToolset are now present. -- [x] **Retarget the projects** `v143` → `v145`. — agent: bulk replace across `src\**\*.vcxproj` → - **346 replacements in 91 files**; distribution now 100% `v145`. `WindowsTargetPlatformVersion` left at - `10.0`. (reglib REGLIBA.vcxproj included.) Unstaged. -- [x] **Build `salamand.sln` (`Debug|x64`) on v145.** — agent: after the `/RTCc` fix, **clean build — - exit 0, 0 errors, 0 warnings**. `salamand.exe` (5.20260620) + **35 plugins** built, incl. `pictview` + - `portables` (ATL resolved). No source conformance changes were required. -- [x] **Fix conformance errors** — only one root cause (`/RTCc`/STL1013), fixed below. The decades-old - code otherwise compiles clean under MSVC 14.51 / `stdcpplatest`. -- [x] **Build remaining configs** (`Release|x64`, `Debug|Win32`, `Release|Win32`). — agent: after the - no-CRT fixes below, **all four configs build** on v145. `Debug|x64` and `Debug|Win32` are fully clean; - `Release|x64`/`Release|Win32` are clean **except** the `zip2sfx` code-signing post-build step - (`MSB3073`) — an environment issue (no signing cert locally), not a toolset/code problem, and it - succeeds in CI where the sign script tolerates a missing cert. -- [x] **Update CI (GitHub Actions) to handle the v145 retarget.** — agent: see the CI section below. -- [ ] **(optional) Centralize the toolset**: now that `src\Directory.Build.props` exists, set - `` there once and remove the per-project values, so the next bump is one line. - (Deferred: needs the per-project values removed and import-timing verified. Note CI relies on the - `/p:PlatformToolset=v143` global override, which works regardless of per-project vs centralized.) - -### Discovered conformance work (filled in as the v145 build surfaces errors) - -- [x] **`/RTCc` rejected by MSVC 14.51 STL** (`error C2338 / STL1013: "The STL doesn't support /RTCc - because it rejects conformant code. Remove the /RTCc option."`). Hit by **39 projects** — it's the - `true` Debug option (v143/14.44 tolerated it; 14.51 hard-errors; - `/RTCc` is non-conformant and discouraged by MS). — agent: set it to `false` in the 8 files that - defined it: `plugins\shared\vcxproj\plugin_debug.props` (covers all plugins), `vcxproj\sal_debug.props` - (salamand), `vcxproj\sqlite\sqlite_debug.props`, `vcxproj\salmon\salmon_debug.props`, and the - `sfxmake`, `reglib`, `translator`, `tserver` vcxproj. Unstaged. -- [x] **No-CRT helper modules: unresolved `strcpy`/`strlen`/`memset` under v145** (linker `LNK2001`/ - `LNK2019`). The tiny CRT-free utilities (`salextx64` shell ext, `salopen`, `salspawn`) ignore the - default libs and supply their own funcs via `lstrfix.inc` under `LSTRFIX_WITHOUT_RTL`; v145's - optimizer now emits implicit `strcpy/strlen` (Release) and `memset` (x86) where v143/14.44 inlined - them. — agent fix (final, verified): (a) added **`memset` only** to `src\common\lstrfix.inc` under - `LSTRFIX_WITHOUT_RTL` with `#pragma function(memset)` (initially also added memcpy/memmove but those - are already provided → `LNK2005`, so removed); (b) moved `LSTRFIX_WITHOUT_RTL` into the shared - `shellext_base.props` + `salspawn_base.props` (was only in `_debug.props`, so Release lacked it); - (c) set `false` in `shellext_release.props` + - `salspawn_release.props` — defining the CRT helpers is incompatible with `/GL` (`C2268`). Rebuild - confirmed `salextx64` + `salopen` now link. -- [x] **`LNK1000: Internal error during IMAGE::EmitRelocations`** on the `lang_*` resource DLLs - (Win32, and one Release|x64) — an **intermittent** v145 *prerelease-linker* crash. It hit different - projects on different runs and **did not recur** on rebuild (Debug|Win32 then linked clean). Treat as - a flaky Preview-toolset issue (retry the build); not a code problem. Worth keeping an eye on / report - upstream if it persists. -- [ ] **`zip2sfx` Release: code-signing post-build step fails** (`MSB3073` from - `tools\codesign\sign_with_retry.cmd`) **only in my local env** (no signing cert). **Not** a - toolset/v145 problem; the CI sign script tolerates a missing cert and the `zip2sfx.exe` itself builds. - No action needed for the migration; relevant only if signing locally. - -### Install / build results (Option B) - -- [x] **v145 desktop toolset + ATL installed** — agent (elevated): `VC.14.51.x86.x64` + `VC.14.51.ATL`, - exit 0. `Microsoft.VCToolsVersion.v145.default.txt` and the desktop `v145` PlatformToolset are present. -- [x] **Final v145 build status — all four configs build.** `Debug|x64` + `Debug|Win32`: fully clean - (0 errors, 0 warnings; `salamand.exe` + 35 plugins incl. ATL-using `pictview`/`portables`). - `Release|x64` + `Release|Win32`: clean except the local-only `zip2sfx` signing step. - **Bottom line: the decades-old codebase compiles on VS 2026 / MSVC 14.51 / `stdcpplatest` with the - only required changes being build-option fixes (`/RTCc` off, `/GL` off for 2 no-CRT modules) plus a - one-line `memset` helper — no real source/conformance changes.** - -### CI (GitHub Actions) - -- [x] **Updated both build workflows for the v145 retarget.** GitHub-hosted runners (`windows-2022`, - `windows-2025`) ship VS2022 = **v143** only; VS2026/**v145** is prerelease and cannot be added to - VS2022. So CI pins the toolset to v143 via a global MSBuild property - (**`/p:PlatformToolset=v143`**, added through a new `PLATFORM_TOOLSET` env var), which **overrides** - the per-project `v145`. Same sources, builds identically. - - `.github/workflows/pr-msbuild.yml` (Debug, Win32+x64, `/warnaserror`) and - `.github/workflows/build-installer.yml` (Release x64 → installer) both updated. — agent. - - Verified locally: `sqlite.vcxproj` (declares `v145`) builds with `/p:PlatformToolset=v143` → exit 0 - (the override works exactly as CI will use it). - - The new `src\Directory.Build.props` (dynamic build date) works in CI unchanged (MSBuild computes - the date). New source files (`utf8gui.h`, etc.) are picked up automatically. - - **(you, optional)** To run CI on v145 instead, use a self-hosted/custom runner with VS2026 and set - `PLATFORM_TOOLSET: v145` — not recommended yet (prerelease toolset; intermittent `LNK1000`). - ---- - -## Verification commands (re-run to re-check state) - -```powershell -# Toolset distribution across all projects -Get-ChildItem -Recurse -Filter *.vcxproj | - Select-String -Pattern "([^<]+)" -AllMatches | - % { $_.Matches } | % { $_.Groups[1].Value } | Group-Object | ft Count,Name -Auto - -# VS 2026 toolset present -Get-ChildItem "C:\Program Files\Microsoft Visual Studio\18\Insiders\VC\Tools\MSVC" -Directory | % Name - -# Installed Windows SDKs -Get-ChildItem "C:\Program Files (x86)\Windows Kits\10\Include" -Directory | % Name - -# Confirm the legacy folder is gone -Test-Path "src\plugins\portables\vcproj2015_manison" # should be False -``` diff --git a/agents.md b/agents.md index bbbe1a34..e6727fd5 100644 --- a/agents.md +++ b/agents.md @@ -5,3 +5,20 @@ Visual Studio 2026 is installed and builds this solution successfully. Treat the VS 2026 IDE or its associated developer-command environment as the authoritative local build environment for this repository. Do not infer that C++ builds are unavailable merely because a separate Build Tools installation lacks Visual C++ targets. When a build is needed, use the installed Visual Studio 2026 environment and report the exact configuration and result. + +## Source-change documentation + +Every source-code change must include a concise nearby comment that documents the change's intent. Keep the comment focused on the reason for the change, invariant, or compatibility constraint it preserves rather than restating the code. + +## Suggested local commands + +The following paths and command are examples verified on one development machine only. They are suggested starting points, not portable requirements: they may be absent or different on another filesystem or computer. + +```powershell +$vsDevCmd = 'C:\Program Files\Microsoft Visual Studio\18\Insiders\Common7\Tools\VsDevCmd.bat' +$project = 'C:\Projects\FileManager\src\vcxproj\salamand.vcxproj' +$cmd = 'call "' + $vsDevCmd + '" -arch=x64 -host_arch=x64 && msbuild "' + $project + '" /t:Build /p:Configuration=Debug /p:Platform=x64 /m' +& $env:ComSpec /d /s /c $cmd +``` + +This uses the Visual Studio developer environment before invoking MSBuild, rather than relying on a standalone Build Tools installation. diff --git a/architecture.md b/architecture.md index 31249ebc..3004d6ab 100644 --- a/architecture.md +++ b/architecture.md @@ -85,7 +85,7 @@ The common MSBuild policy is defined in `src/vcxproj/sal_base.props`: - Win32 and x64 builds are separated by platform-specific property sheets and output directories. - The checked-in projects currently select toolset `v145`; CI intentionally overrides that to `v143`, so the effective toolset is environment-dependent. -The pull-request workflow compiles Debug Win32 and x64 configurations and treats warnings as errors. The release workflow builds x64 Release, acquires OpenSSL runtime dependencies, stages the installer tree, and publishes artifacts. Installer scripts collect the executable, helper processes, shell extensions, language modules, toolbar assets, conversion tables, and plug-ins. Consequently, successful compilation of `salamand.exe` alone does not prove a distributable installation is complete. +The pull-request workflow compiles Debug Win32 and x64 configurations and treats warnings as errors. The release workflow builds x64 Release, stages the installer tree, and publishes artifacts. FTPS uses Windows SChannel rather than a bundled TLS runtime, so the installer does not download or ship OpenSSL DLLs. Installer scripts collect the executable, helper processes, shell extensions, language modules, toolbar assets, conversion tables, and plug-ins. Consequently, successful compilation of `salamand.exe` alone does not prove a distributable installation is complete. ### 1.2 Runtime processes and load boundaries @@ -307,7 +307,45 @@ The high-level panel code plans work before mutating the filesystem: 5. `ThreadWorkerBody` (`src/operations_core.cpp:921`) interprets opcodes and invokes `DoCopyFile`, `DoMoveFile`, `DoCreateDir`, `DoDeleteFile`, attribute/conversion, or count-size handlers. 6. Completion updates panels and posts path-change notifications. -`src/async_copy.cpp` contains the lower-level copy engine: synchronous/overlapped decisions, buffer sizing, alternate data streams, compression/encryption/security metadata, overwrite and retry policy, cancellation, and progress updates. `COperationsQueue` tracks disk operation windows and their paused/running state. +`src/async_copy.cpp` contains the lower-level copy engine: synchronous/overlapped decisions, buffer sizing, alternate data streams, compression/encryption/security metadata, overwrite and retry policy, cancellation, and progress updates. A native copy reaches its durable commit point only after the output was opened with write-through where supported, `FlushFileBuffers` and `CloseHandle` both succeed, and the closed destination can be reopened with its file metadata and size verified. An overwrite then uses the write-through replace/rename commit; a cross-volume move may delete its source only after this boundary. Each `COperations` instance owns an interlocked lifecycle (`planned`, `running`, `cancel-requested`, `stopping`, `completed`, or `failed`) and a manual-reset cancellation event. The dialog and worker make idempotent requests and state transitions through this owner; debug builds stop on invalid transitions. `COperationsQueue` tracks disk operation windows and their paused/running state. + +#### 5.2.1 Reparse-point operation policy + +The operation planner treats every directory reparse point as a hard operation boundary. Copy, move, count, convert, and recursive attribute work do not enumerate a junction, symbolic link, mount point, cloud directory, or unknown directory tag. This prevents a target outside the selected root, a changed target, and an in-tree cycle from becoming part of the script. Reparse files are likewise not opened while planning non-delete work, so a cloud placeholder remains offline rather than being hydrated by an incidental metadata or size read. + +Delete uses a separate handle-first path with `FILE_FLAG_OPEN_REPARSE_POINT` and the captured file identity. Directory deletion accepts only the mount-point and symbolic-link tags (a junction is a mount-point tag); an unknown tag fails with `ERROR_REPARSE_TAG_MISMATCH` instead of deleting or resolving its destination. The current conservative copy/move policy skips reparse entries rather than recreating them: preserving a link is a future explicit feature, not a reason to follow its target. + +The UI integration suite builds disposable junction topologies with a target outside the selected root, a changed target, and a cycle. It verifies that copying preserves ordinary in-root content without materializing either target, and that deleting a junction deletes only the link. The native regression test also ratchets the no-hydration and unknown-tag rules; provider-owned cloud placeholders and volume mount points require an appropriate host/provider for live end-to-end setup. + +#### 5.2.2 Metadata preservation contract + +The engine distinguishes three preservation levels. **Required** metadata must pass the operation's durable-copy/identity checks before a cross-volume move can delete its source. **Best effort** metadata is copied or repaired when the target and current privilege allow it; a verified failure is recorded. **Unsupported** metadata is not represented by the target or is not copied by this engine; it is recorded as a known loss. The contract applies to native disk copy and move operations only; archive and virtual-filesystem plug-ins retain their own capability contracts. + +| Operation and target | Required | Best effort | Unsupported | +|---|---|---|---| +| Copy to NTFS | Default data stream | Last-write time; displayed attributes when requested; owner/group/DACL when requested and permitted; ADS; NTFS compression/EFS | Creation and last-access times | +| Copy to ReFS | Default data stream | Last-write time; displayed attributes when requested; owner/group/DACL when requested and permitted; ADS | Creation and last-access times; NTFS compression/EFS | +| Copy to FAT/FAT32/exFAT | Default data stream | Last-write time and the supported attribute subset | Creation and last-access times; owner/group/DACL; ADS; NTFS compression/EFS | +| Copy to SMB | Default data stream | Last-write time, attributes, owner/group/DACL, ADS, and compression/EFS subject to server/share policy | Creation and last-access times | +| Same-volume move on NTFS/ReFS/FAT | The same filesystem object is renamed, so its data and supported metadata remain attached to that object | None | None introduced by the move | +| Same-volume move on SMB | Default data stream | All other metadata, subject to server/share rename semantics | None introduced by the engine | +| Cross-volume move | The target must pass the copy durable-commit check before source deletion | The target's corresponding copy row | The target's corresponding copy row | + +For a cross-volume move, the worker records planned losses accepted while building the script (for example ADS or ACL loss), plus actual ADS, timestamp, attribute, ACL, and compression/EFS losses. Immediately before every source-file or source-directory deletion it displays any unacknowledged losses. The default **No** retains the source and lets the remaining move continue as a copy; only an explicit **Yes** allows source deletion. The account is held in `CProgressDlgData::MetadataLosses`, so a directory cleanup path cannot bypass a loss already found while copying a child. + +##### Security descriptor privilege matrix + +Security preservation is best effort on NTFS, ReFS, and SMB, and unsupported on FAT-family targets. The copy engine reads the complete owner/group/DACL descriptor before changing the target. It preserves the DACL protection bit (and therefore whether inheritance is enabled) and compares every explicit ACE after the write; this includes explicit deny ACEs. Inherited ACEs may differ when the target has a different parent, but they must remain inherited rather than becoming new explicit permissions. + +| Source/target descriptor and token capability | Owner and group | DACL, inheritance, and explicit allow/deny ACEs | Result under the metadata contract | +|---|---|---|---| +| Descriptor readable; `SeRestorePrivilege` enabled | Apply and then verify | Apply and then verify | Success only after all components verify; otherwise restore the target snapshot and report a security metadata loss. | +| Descriptor readable; no restore privilege; target owner/group already match | Leave unchanged | Apply and then verify | Success only after DACL protection and explicit ACEs verify; a failure restores the prior DACL and reports a security metadata loss. | +| Descriptor readable; no restore privilege; owner or group differs | Do not change | Do not change | Do not take temporary ownership or install a permissive DACL. Report a security metadata loss and use the normal copy-permissions warning. | +| Source or target descriptor inaccessible | Do not change | Do not change | Report a security metadata loss; the normal warning can cancel, retry, or continue the copy without claiming ACL preservation. | +| FAT/FAT32/exFAT target | Unsupported | Unsupported | Record the known loss during planning; a cross-volume move requires the explicit metadata-loss confirmation before source deletion. | + +An ACL write is never considered successful merely because `SetNamedSecurityInfoW` returns success. The post-write comparison prevents a partial component update from being represented as preserved. If restoration itself fails, the operation reports that error instead of silently treating the descriptor as copied. ```mermaid sequenceDiagram @@ -380,7 +418,7 @@ For archive and virtual-filesystem sources, content is normally materialized int ### 5.6 Configuration and registry persistence -`CConfiguration` (`src/cfgdlg.h:176`) is the central settings aggregate. `CMainWindow::LoadConfig` and `SaveConfig` (`src/mainwnd_config.cpp:2511`, `:1387`) serialize application state to the registry, apply defaults, and migrate older `ConfigVersion` layouts. Plug-ins receive private registry subkeys and use their own `LoadConfiguration`/`SaveConfiguration` callbacks. +`CConfiguration` (`src/cfgdlg.h:176`) is the central settings aggregate. `CMainWindow::LoadConfig` and `SaveConfig` (`src/mainwnd_config.cpp`) serialize application state to the registry, apply defaults, and migrate older `ConfigVersion` layouts. Plug-ins receive private registry subkeys and use their own `LoadConfiguration`/`SaveConfiguration` callbacks. Persistence has several coordination mechanisms: @@ -388,7 +426,10 @@ Persistence has several coordination mechanisms: - `CRegistryWorkerThread` performs registry work while the UI side continues pumping messages. - `ScheduleConfigSave` debounces ordinary changes by 250 ms. - `SaveConfig` prevents reentrant saves and remembers when another save is requested during the current one. -- `Save In Progress` and backup `Copy Is OK` markers help detect and recover from interrupted writes. +- `SaveConfig` writes the full snapshot to the inactive one of two `Configuration Generations`, verifies a checksum and completion marker, flushes it, then switches the root's `Active Generation` DWORD. That one value write is the commit point; the previous verified generation remains available until the next successful startup. +- Startup validates the selected generation before exposing it to the existing host and plug-in readers, and falls back to the other verified generation if the selected one is incomplete or has a checksum mismatch. Legacy direct trees remain readable and are migrated on their next save. +- Each transactional generation has a schema version. Startup runs the idempotent metadata migration, then validates required sections, supported configuration/schema versions, value ranges, and cross-field invariants before `LoadConfig` applies the profile. A rejected active generation falls back to the other verified profile; if neither profile is valid, the application uses defaults and reports the recovery to the user. +- The legacy `Save In Progress` and backup `Copy Is OK` markers remain only for recognising/recovering pre-transactional configuration trees. - a `config.reg` beside the application can seed/import configuration. Configuration is therefore a versioned state snapshot, not a collection of independent repositories. Changes to the aggregate, UI transfer code, defaults, migration, and persistence keys must remain synchronized. @@ -459,7 +500,7 @@ Disk panels use directory-snooper threads based on `FindFirstChangeNotification` 4. Native transfers build a `COperations` script and start progress/worker threads. 5. The worker processes items, marshaling overwrite, retry, skip, pause, cancellation, and error decisions to the progress UI. 6. Speed and byte/item totals are protected by `StatusCS` and displayed by the progress dialog. -7. On completion, affected paths receive notifications and visible panels refresh. +7. A native copy reports success only after its durable commit point; affected paths then receive notifications and visible panels refresh. ### 6.6 Configuration change and persistence @@ -467,8 +508,8 @@ Disk panels use directory-snooper threads based on `FindFirstChangeNotification` 2. On acceptance, `Validate` checks input and `Transfer` writes the new values to `CConfiguration` or a feature-owned object. 3. The affected subsystem is updated immediately where appropriate. 4. `ScheduleConfigSave` coalesces repeated mutations. -5. The registry worker obtains the cross-process mutex and writes the versioned snapshot plus plug-in configuration. -6. Completion clears the in-progress marker; an interrupted marker is evaluated during a later startup. +5. The registry worker obtains the cross-process mutex and writes the versioned host snapshot plus plug-in configuration into the inactive generation. +6. The host computes and validates the staged generation's checksum, writes its completion marker, flushes it, and atomically changes `Active Generation`. An interrupted stage never becomes visible; the prior generation is retained until a successful startup. ### 6.7 Second-instance command forwarding @@ -502,6 +543,24 @@ Important concurrency invariants are: - Plug-in unload is deferred until active callbacks, windows, filesystems, and plug-in-owned data are no longer in use. - Shutdown stops producers before releasing the global objects and handles they use. +### 7.1 Lock ordering contract + +New or migrated shared `CRITICAL_SECTION` acquisitions use the ranked `CScopedCriticalSection` form and must progress from a lower rank to a higher rank. Re-entering the same critical section is permitted; acquiring a different critical section at the same or lower rank is not. Do not hold a ranked lock across a cross-thread `SendMessage`, modal UI, or an operation that can synchronously call back into the UI. + +| Rank | Lock family | Examples and rule | +|---:|---|---| +| 10 | Process lifetime | Startup/shutdown state; acquire before every other ranked lock. | +| 20 | Configuration | Registry/configuration state. | +| 30 | Plug-in registry | Plug-in registration and lifetime bookkeeping. | +| 40 | Worker queue | Background queue and dispatcher state. | +| 50 | Operation state | Copy/move/delete and progress state. | +| 60 | UI state | Panel/window state; never use while making a synchronous cross-thread UI call. | +| 70 | External broker | `CParserBrokerClient::Lock`; it serializes one broker request at a time and is the first migrated ranked lock. | + +In Debug builds, `LockOrderEnter` maintains a per-thread rank stack and asserts if a non-recursive acquisition does not increase the rank. It first attempts the critical section without blocking; after ten seconds of contention it writes the requested lock name/rank, waiter thread ID, current owner, recursion count, and already-held rank to the debugger before preserving the existing blocking acquisition behavior. This deliberately keeps Release locking and legacy unranked call sites compatible while each owner is migrated at its subsystem boundary. + +The `Nightly Lock Stress` workflow runs the repeated UI lifecycle suite on an isolated desktop runner with Application Verifier's `Locks` layer enabled for `salamand.exe`. The runner removes the verifier's process-persistent settings in a `finally` block and uploads AppVerifier logs, so a failed lane cannot affect later jobs on that machine. + The design does not use a single scheduler or futures abstraction. Each subsystem owns its thread/event/message protocol, so callers must learn that protocol before changing lifecycle or error handling. ## 8. Architectural and implementation patterns @@ -535,6 +594,7 @@ Most native APIs return `BOOL`, handles, or error codes. The code typically capt Built-in diagnostic facilities include: - `TRACE` and `CALL_STACK_MESSAGE` instrumentation; +- a fixed, allocation-free release diagnostic ring of sanitized operation transitions, waits, retries, and plug-in DLL leaf names; it is written into the locally viewable crash report and therefore enters an archive only when the user elects to send that report; - Debug handle wrappers and `HANDLES_ENABLE` leak/misuse tracking; - custom allocation-failure handling and heap diagnostics; - `tserver.exe` for cross-process trace collection; @@ -549,17 +609,18 @@ The repository's prioritized stability recommendations are documented separately The checked-in automated test suite is executable-level rather than native unit-level. `tests/FileManager.UiTests` uses NUnit and FlaUI UIA3 against a real built `salamand.exe`. Tests are non-parallel because they manipulate shared desktop and per-user application state. -The suite covers roughly 100 parameterized scenarios in seven families, including: +The suite covers roughly 118 scenarios in eight families, including: - main-window startup and basic commands; - accessibility/control discovery; - configuration cancel versus commit; - persistence within the running process and after restart; - FTP bookmark persistence. +- native create/copy/rename/move/delete commands for files and nested directory trees, including cancelled dialogs and expected failure paths. Stable command/control IDs are the automation seam. `FILEMANAGER_UI_ISOLATED=1` is required so test launches use an isolated configuration context, and teardown kills only processes launched by the fixture. These tests validate user-visible integration across the native executable, registry persistence, and UI Automation tree. -There are currently no checked-in native unit-test projects for panel algorithms, operation scripts, plug-in ABI contracts, or worker failure paths. CI build coverage and executable UI scenarios are therefore the principal automated safety net; manual and plug-in-specific testing remain important for filesystem mutation and unusual OS integrations. +There are currently no checked-in native unit-test projects for panel algorithms, operation scripts, plug-in ABI contracts, or worker failure paths. CI build coverage and executable UI scenarios are therefore the principal automated safety net; manual and plug-in-specific testing remain important for crash consistency, storage fault injection, and unusual OS integrations. ## 11. Extension and maintenance guides diff --git a/design-qa.md b/design-qa.md new file mode 100644 index 00000000..9e942d9d --- /dev/null +++ b/design-qa.md @@ -0,0 +1,17 @@ +# Design QA — Fluent Color Essentials + +The selected Proposal 1 board was compared side by side with a fresh render of the shipped SVG assets, including their true 16 px toolbar scale. + +## Result + +Passed. No P0, P1, P2, or P3 visual issues remain in the core-owned menu, toolbar, and shared-glyph set. + +## Checks + +- The familiar action metaphors, semantic colors, rounded corners, and restrained detail match the approved direction. +- All icons remain full color and use a 16 x 16 master grid; scaling preserves the expected toolbar resolution. +- Menu check/radio marks and shared expand/collapse chevrons use the same visual language. +- Core system-location actions no longer depend on legacy `shell32.dll` artwork. +- The four main application `.ico` resources remain hash-identical to the pre-change files. + +Third-party plug-in artwork remains provider-owned and is intentionally outside this core-interface restyle. diff --git a/design/icon-reference/fluent-color-essentials/README.md b/design/icon-reference/fluent-color-essentials/README.md new file mode 100644 index 00000000..42d040e3 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/README.md @@ -0,0 +1,24 @@ +# Fluent Color Essentials icon reference + +This folder is the archival presentation set for the toolbar and menu glyphs in +`src/res/toolbars`. It is intentionally separate from runtime resources. + +- `svg/` contains one 64 x 64 reference SVG for each runtime icon. The original + 16 x 16 view box is preserved so the reference art remains identical to the + production glyph at every configured toolbar size. +- `fluent-color-essentials-icon-catalog.png` is the complete labeled catalog at + 3840 x 3440 pixels. +- `generate-reference.cjs` rebuilds both deliverables from the runtime SVG set. + +The runtime folder remains authoritative. Regenerate this reference after an +icon is added, removed, renamed, or visually changed. + +## Regeneration + +Run the generator with Node.js and Sharp available through `NODE_PATH`: + +```powershell +$env:NODE_PATH = '' +node .\design\icon-reference\fluent-color-essentials\generate-reference.cjs +``` + diff --git a/design/icon-reference/fluent-color-essentials/fluent-color-essentials-icon-catalog.png b/design/icon-reference/fluent-color-essentials/fluent-color-essentials-icon-catalog.png new file mode 100644 index 00000000..d99935ff Binary files /dev/null and b/design/icon-reference/fluent-color-essentials/fluent-color-essentials-icon-catalog.png differ diff --git a/design/icon-reference/fluent-color-essentials/generate-reference.cjs b/design/icon-reference/fluent-color-essentials/generate-reference.cjs new file mode 100644 index 00000000..aedd149f --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/generate-reference.cjs @@ -0,0 +1,87 @@ +// Regenerates the archival icon masters and catalog directly from authoritative runtime SVGs. +const fs = require('fs'); +const path = require('path'); +const sharp = require('sharp'); + +const referenceRoot = __dirname; +const repositoryRoot = path.resolve(referenceRoot, '..', '..', '..'); +const sourceDirectory = path.join(repositoryRoot, 'src', 'res', 'toolbars'); +const svgDirectory = path.join(referenceRoot, 'svg'); +const catalogPath = path.join(referenceRoot, 'fluent-color-essentials-icon-catalog.png'); + +const catalogWidth = 3840; +const catalogHeight = 3440; +const columns = 7; +const cellWidth = 520; +const cellHeight = 240; +const horizontalMargin = 100; +const headerHeight = 240; + +function xmlEscape(value) { + return value.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>'); +} + +function createReferenceSvg(source) { + const resized = source.replace( + '\n' + resized; +} + +async function main() { + const iconNames = fs.readdirSync(sourceDirectory) + .filter((name) => name.toLowerCase().endsWith('.svg')) + .sort((left, right) => left.localeCompare(right)); + + if (iconNames.length === 0) + throw new Error('No runtime toolbar SVGs were found.'); + + // This directory is wholly generated, so clearing it prevents removed runtime icons from lingering in the archive. + fs.rmSync(svgDirectory, { recursive: true, force: true }); + fs.mkdirSync(svgDirectory, { recursive: true }); + + const referenceIcons = iconNames.map((name) => { + const source = fs.readFileSync(path.join(sourceDirectory, name), 'utf8'); + const reference = createReferenceSvg(source); + fs.writeFileSync(path.join(svgDirectory, name), reference, 'utf8'); + return { name, label: path.basename(name, '.svg'), svg: reference }; + }); + + const cards = referenceIcons.map((icon, index) => { + const column = index % columns; + const row = Math.floor(index / columns); + const x = horizontalMargin + column * cellWidth + 10; + const y = headerHeight + row * cellHeight + 10; + const centerX = x + 250; + const dataUri = Buffer.from(icon.svg).toString('base64'); + return [ + ``, + ``, + `${xmlEscape(icon.label)}`, + ].join('\n'); + }).join('\n'); + + const catalogSvg = ` + + + Fluent Color Essentials + Open Salamander toolbar and menu icon reference - ${referenceIcons.length} vector glyphs + + ${cards} +`; + + await sharp(Buffer.from(catalogSvg)) + .png({ compressionLevel: 9 }) + .toFile(catalogPath); + + process.stdout.write(`Generated ${referenceIcons.length} reference SVGs and ${path.basename(catalogPath)}.\n`); +} + +main().catch((error) => { + process.stderr.write(`${error.stack || error.message}\n`); + process.exitCode = 1; +}); + diff --git a/design/icon-reference/fluent-color-essentials/svg/Back.svg b/design/icon-reference/fluent-color-essentials/svg/Back.svg new file mode 100644 index 00000000..9873ab74 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/Back.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/CalculateDirectorySizes.svg b/design/icon-reference/fluent-color-essentials/svg/CalculateDirectorySizes.svg new file mode 100644 index 00000000..a42b8783 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/CalculateDirectorySizes.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/CalculateOccupiedSpace.svg b/design/icon-reference/fluent-color-essentials/svg/CalculateOccupiedSpace.svg new file mode 100644 index 00000000..8e189cec --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/CalculateOccupiedSpace.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/ChangeAttributes.svg b/design/icon-reference/fluent-color-essentials/svg/ChangeAttributes.svg new file mode 100644 index 00000000..5fdbb8a5 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/ChangeAttributes.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/ChangeCase.svg b/design/icon-reference/fluent-color-essentials/svg/ChangeCase.svg new file mode 100644 index 00000000..08294c26 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/ChangeCase.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/ChangeDirectory.svg b/design/icon-reference/fluent-color-essentials/svg/ChangeDirectory.svg new file mode 100644 index 00000000..0d2e4d23 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/ChangeDirectory.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/ChangeDrive.svg b/design/icon-reference/fluent-color-essentials/svg/ChangeDrive.svg new file mode 100644 index 00000000..7bc8fddf --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/ChangeDrive.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/design/icon-reference/fluent-color-essentials/svg/ClipboardCopy.svg b/design/icon-reference/fluent-color-essentials/svg/ClipboardCopy.svg new file mode 100644 index 00000000..b61b6db9 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/ClipboardCopy.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/ClipboardCut.svg b/design/icon-reference/fluent-color-essentials/svg/ClipboardCut.svg new file mode 100644 index 00000000..5aebc142 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/ClipboardCut.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/ClipboardPaste.svg b/design/icon-reference/fluent-color-essentials/svg/ClipboardPaste.svg new file mode 100644 index 00000000..7b7a6c67 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/ClipboardPaste.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/CommandShell.svg b/design/icon-reference/fluent-color-essentials/svg/CommandShell.svg new file mode 100644 index 00000000..da3db9df --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/CommandShell.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/CompareDirectories.svg b/design/icon-reference/fluent-color-essentials/svg/CompareDirectories.svg new file mode 100644 index 00000000..8c636dda --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/CompareDirectories.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/Configuration.svg b/design/icon-reference/fluent-color-essentials/svg/Configuration.svg new file mode 100644 index 00000000..9ea34c21 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/Configuration.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/ConnectNetworkDrive.svg b/design/icon-reference/fluent-color-essentials/svg/ConnectNetworkDrive.svg new file mode 100644 index 00000000..fe862844 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/ConnectNetworkDrive.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/Convert.svg b/design/icon-reference/fluent-color-essentials/svg/Convert.svg new file mode 100644 index 00000000..0e60055c --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/Convert.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/Copy.svg b/design/icon-reference/fluent-color-essentials/svg/Copy.svg new file mode 100644 index 00000000..b61b6db9 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/Copy.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/CreateDirectory.svg b/design/icon-reference/fluent-color-essentials/svg/CreateDirectory.svg new file mode 100644 index 00000000..8c2f84e8 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/CreateDirectory.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/Delete.svg b/design/icon-reference/fluent-color-essentials/svg/Delete.svg new file mode 100644 index 00000000..3a7142d5 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/Delete.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/Disconnect.svg b/design/icon-reference/fluent-color-essentials/svg/Disconnect.svg new file mode 100644 index 00000000..4e47ccee --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/Disconnect.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/DriveInformation.svg b/design/icon-reference/fluent-color-essentials/svg/DriveInformation.svg new file mode 100644 index 00000000..804a952c --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/DriveInformation.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/Edit.svg b/design/icon-reference/fluent-color-essentials/svg/Edit.svg new file mode 100644 index 00000000..1dae689b --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/Edit.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/EditNewFile.svg b/design/icon-reference/fluent-color-essentials/svg/EditNewFile.svg new file mode 100644 index 00000000..92583133 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/EditNewFile.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/Email.svg b/design/icon-reference/fluent-color-essentials/svg/Email.svg new file mode 100644 index 00000000..070fb9f2 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/Email.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/Filter.svg b/design/icon-reference/fluent-color-essentials/svg/Filter.svg new file mode 100644 index 00000000..1c2711c5 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/Filter.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/FindFilesAndDirectories.svg b/design/icon-reference/fluent-color-essentials/svg/FindFilesAndDirectories.svg new file mode 100644 index 00000000..01453a1e --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/FindFilesAndDirectories.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/Focus.svg b/design/icon-reference/fluent-color-essentials/svg/Focus.svg new file mode 100644 index 00000000..719f5553 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/Focus.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/design/icon-reference/fluent-color-essentials/svg/FocusNameInOtherPanel.svg b/design/icon-reference/fluent-color-essentials/svg/FocusNameInOtherPanel.svg new file mode 100644 index 00000000..9965b043 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/FocusNameInOtherPanel.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/Forward.svg b/design/icon-reference/fluent-color-essentials/svg/Forward.svg new file mode 100644 index 00000000..13f127d0 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/Forward.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/GoToHotPath.svg b/design/icon-reference/fluent-color-essentials/svg/GoToHotPath.svg new file mode 100644 index 00000000..2b38e856 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/GoToHotPath.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/design/icon-reference/fluent-color-essentials/svg/GoToNextSelectedName.svg b/design/icon-reference/fluent-color-essentials/svg/GoToNextSelectedName.svg new file mode 100644 index 00000000..b8df9067 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/GoToNextSelectedName.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/design/icon-reference/fluent-color-essentials/svg/GoToPathfromOtherPanel.svg b/design/icon-reference/fluent-color-essentials/svg/GoToPathfromOtherPanel.svg new file mode 100644 index 00000000..b5fc21b4 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/GoToPathfromOtherPanel.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/GoToPreviousSelectedName.svg b/design/icon-reference/fluent-color-essentials/svg/GoToPreviousSelectedName.svg new file mode 100644 index 00000000..49cd6e59 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/GoToPreviousSelectedName.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/design/icon-reference/fluent-color-essentials/svg/GoToShortcutTarget.svg b/design/icon-reference/fluent-color-essentials/svg/GoToShortcutTarget.svg new file mode 100644 index 00000000..77acb2db --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/GoToShortcutTarget.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/HelpContents.svg b/design/icon-reference/fluent-color-essentials/svg/HelpContents.svg new file mode 100644 index 00000000..3322dd39 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/HelpContents.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/HideSelectedNames.svg b/design/icon-reference/fluent-color-essentials/svg/HideSelectedNames.svg new file mode 100644 index 00000000..a87c2bd8 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/HideSelectedNames.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/HideUnselectedNames.svg b/design/icon-reference/fluent-color-essentials/svg/HideUnselectedNames.svg new file mode 100644 index 00000000..57026473 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/HideUnselectedNames.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/InvertSelection.svg b/design/icon-reference/fluent-color-essentials/svg/InvertSelection.svg new file mode 100644 index 00000000..d7074247 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/InvertSelection.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/design/icon-reference/fluent-color-essentials/svg/LoadSelection.svg b/design/icon-reference/fluent-color-essentials/svg/LoadSelection.svg new file mode 100644 index 00000000..408fce5e --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/LoadSelection.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/design/icon-reference/fluent-color-essentials/svg/MenuCheck.svg b/design/icon-reference/fluent-color-essentials/svg/MenuCheck.svg new file mode 100644 index 00000000..63eb0fd8 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/MenuCheck.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/design/icon-reference/fluent-color-essentials/svg/MenuRadio.svg b/design/icon-reference/fluent-color-essentials/svg/MenuRadio.svg new file mode 100644 index 00000000..64967566 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/MenuRadio.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/design/icon-reference/fluent-color-essentials/svg/Modify.svg b/design/icon-reference/fluent-color-essentials/svg/Modify.svg new file mode 100644 index 00000000..008dd718 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/Modify.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/Move.svg b/design/icon-reference/fluent-color-essentials/svg/Move.svg new file mode 100644 index 00000000..6e960f18 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/Move.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/MoveItemDown.svg b/design/icon-reference/fluent-color-essentials/svg/MoveItemDown.svg new file mode 100644 index 00000000..8c6d0e08 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/MoveItemDown.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/MoveItemUp.svg b/design/icon-reference/fluent-color-essentials/svg/MoveItemUp.svg new file mode 100644 index 00000000..9b60b01b --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/MoveItemUp.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/NTFSCompress.svg b/design/icon-reference/fluent-color-essentials/svg/NTFSCompress.svg new file mode 100644 index 00000000..20d285ea --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/NTFSCompress.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/NTFSUncompress.svg b/design/icon-reference/fluent-color-essentials/svg/NTFSUncompress.svg new file mode 100644 index 00000000..e94a71aa --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/NTFSUncompress.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/New.svg b/design/icon-reference/fluent-color-essentials/svg/New.svg new file mode 100644 index 00000000..ed510dbf --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/New.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/OpenActiveFolder.svg b/design/icon-reference/fluent-color-essentials/svg/OpenActiveFolder.svg new file mode 100644 index 00000000..49151bd1 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/OpenActiveFolder.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/design/icon-reference/fluent-color-essentials/svg/OpenComputer.svg b/design/icon-reference/fluent-color-essentials/svg/OpenComputer.svg new file mode 100644 index 00000000..4955aefe --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/OpenComputer.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/design/icon-reference/fluent-color-essentials/svg/OpenControlPanel.svg b/design/icon-reference/fluent-color-essentials/svg/OpenControlPanel.svg new file mode 100644 index 00000000..df0f8d8e --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/OpenControlPanel.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/design/icon-reference/fluent-color-essentials/svg/OpenDesktop.svg b/design/icon-reference/fluent-color-essentials/svg/OpenDesktop.svg new file mode 100644 index 00000000..763e6330 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/OpenDesktop.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/design/icon-reference/fluent-color-essentials/svg/OpenDocuments.svg b/design/icon-reference/fluent-color-essentials/svg/OpenDocuments.svg new file mode 100644 index 00000000..c4a4bbb4 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/OpenDocuments.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/design/icon-reference/fluent-color-essentials/svg/OpenFolder.svg b/design/icon-reference/fluent-color-essentials/svg/OpenFolder.svg new file mode 100644 index 00000000..45107279 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/OpenFolder.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/OpenFonts.svg b/design/icon-reference/fluent-color-essentials/svg/OpenFonts.svg new file mode 100644 index 00000000..07179cb8 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/OpenFonts.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/design/icon-reference/fluent-color-essentials/svg/OpenNameinOtherPanel.svg b/design/icon-reference/fluent-color-essentials/svg/OpenNameinOtherPanel.svg new file mode 100644 index 00000000..fa85bc32 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/OpenNameinOtherPanel.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/OpenNetwork.svg b/design/icon-reference/fluent-color-essentials/svg/OpenNetwork.svg new file mode 100644 index 00000000..90a4aef6 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/OpenNetwork.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/design/icon-reference/fluent-color-essentials/svg/OpenPrinters.svg b/design/icon-reference/fluent-color-essentials/svg/OpenPrinters.svg new file mode 100644 index 00000000..34b07c4e --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/OpenPrinters.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/design/icon-reference/fluent-color-essentials/svg/OpenRecycleBin.svg b/design/icon-reference/fluent-color-essentials/svg/OpenRecycleBin.svg new file mode 100644 index 00000000..7f65f216 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/OpenRecycleBin.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/design/icon-reference/fluent-color-essentials/svg/Pack.svg b/design/icon-reference/fluent-color-essentials/svg/Pack.svg new file mode 100644 index 00000000..9001a467 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/Pack.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/ParentDirectory.svg b/design/icon-reference/fluent-color-essentials/svg/ParentDirectory.svg new file mode 100644 index 00000000..14343728 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/ParentDirectory.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/PasteShortcut.svg b/design/icon-reference/fluent-color-essentials/svg/PasteShortcut.svg new file mode 100644 index 00000000..9e4eb79e --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/PasteShortcut.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/Properties.svg b/design/icon-reference/fluent-color-essentials/svg/Properties.svg new file mode 100644 index 00000000..fa9c1e54 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/Properties.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/QuickRename.svg b/design/icon-reference/fluent-color-essentials/svg/QuickRename.svg new file mode 100644 index 00000000..adaef794 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/QuickRename.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/Refresh.svg b/design/icon-reference/fluent-color-essentials/svg/Refresh.svg new file mode 100644 index 00000000..b7309a29 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/Refresh.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/RestoreSelection.svg b/design/icon-reference/fluent-color-essentials/svg/RestoreSelection.svg new file mode 100644 index 00000000..836717d5 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/RestoreSelection.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/design/icon-reference/fluent-color-essentials/svg/RootDirectory.svg b/design/icon-reference/fluent-color-essentials/svg/RootDirectory.svg new file mode 100644 index 00000000..3ca19591 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/RootDirectory.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/SaveSelection.svg b/design/icon-reference/fluent-color-essentials/svg/SaveSelection.svg new file mode 100644 index 00000000..357f699e --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/SaveSelection.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/design/icon-reference/fluent-color-essentials/svg/Security.svg b/design/icon-reference/fluent-color-essentials/svg/Security.svg new file mode 100644 index 00000000..d7f2b485 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/Security.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/Select.svg b/design/icon-reference/fluent-color-essentials/svg/Select.svg new file mode 100644 index 00000000..e7f187ec --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/Select.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/design/icon-reference/fluent-color-essentials/svg/SelectAll.svg b/design/icon-reference/fluent-color-essentials/svg/SelectAll.svg new file mode 100644 index 00000000..cce10cef --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/SelectAll.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/SelectFilesWithSameExtension.svg b/design/icon-reference/fluent-color-essentials/svg/SelectFilesWithSameExtension.svg new file mode 100644 index 00000000..f55bb865 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/SelectFilesWithSameExtension.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/design/icon-reference/fluent-color-essentials/svg/SelectFilesWithSameName.svg b/design/icon-reference/fluent-color-essentials/svg/SelectFilesWithSameName.svg new file mode 100644 index 00000000..bb68fbdd --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/SelectFilesWithSameName.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/design/icon-reference/fluent-color-essentials/svg/SharedDirectories.svg b/design/icon-reference/fluent-color-essentials/svg/SharedDirectories.svg new file mode 100644 index 00000000..f2602697 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/SharedDirectories.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/ShowHiddenNames.svg b/design/icon-reference/fluent-color-essentials/svg/ShowHiddenNames.svg new file mode 100644 index 00000000..89ab44d8 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/ShowHiddenNames.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/SmartColumnMode.svg b/design/icon-reference/fluent-color-essentials/svg/SmartColumnMode.svg new file mode 100644 index 00000000..bd7c061e --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/SmartColumnMode.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/SortByAttributes.svg b/design/icon-reference/fluent-color-essentials/svg/SortByAttributes.svg new file mode 100644 index 00000000..6e9a87bf --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/SortByAttributes.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/SortByDate.svg b/design/icon-reference/fluent-color-essentials/svg/SortByDate.svg new file mode 100644 index 00000000..39b26b2f --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/SortByDate.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/SortByExtension.svg b/design/icon-reference/fluent-color-essentials/svg/SortByExtension.svg new file mode 100644 index 00000000..f6947409 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/SortByExtension.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/SortByName.svg b/design/icon-reference/fluent-color-essentials/svg/SortByName.svg new file mode 100644 index 00000000..480b0ea2 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/SortByName.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/SortBySize.svg b/design/icon-reference/fluent-color-essentials/svg/SortBySize.svg new file mode 100644 index 00000000..8f071a82 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/SortBySize.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/Stop.svg b/design/icon-reference/fluent-color-essentials/svg/Stop.svg new file mode 100644 index 00000000..ec0dc83b --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/Stop.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/design/icon-reference/fluent-color-essentials/svg/SwapPanels.svg b/design/icon-reference/fluent-color-essentials/svg/SwapPanels.svg new file mode 100644 index 00000000..d6304392 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/SwapPanels.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/Unpack.svg b/design/icon-reference/fluent-color-essentials/svg/Unpack.svg new file mode 100644 index 00000000..12cc009e --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/Unpack.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/Unselect.svg b/design/icon-reference/fluent-color-essentials/svg/Unselect.svg new file mode 100644 index 00000000..7f38539f --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/Unselect.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/design/icon-reference/fluent-color-essentials/svg/UnselectAll.svg b/design/icon-reference/fluent-color-essentials/svg/UnselectAll.svg new file mode 100644 index 00000000..be2397a7 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/UnselectAll.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/UnselectFilesWithSameExtension.svg b/design/icon-reference/fluent-color-essentials/svg/UnselectFilesWithSameExtension.svg new file mode 100644 index 00000000..68fa3f43 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/UnselectFilesWithSameExtension.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/design/icon-reference/fluent-color-essentials/svg/UnselectFilesWithSameName.svg b/design/icon-reference/fluent-color-essentials/svg/UnselectFilesWithSameName.svg new file mode 100644 index 00000000..3eec3251 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/UnselectFilesWithSameName.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/design/icon-reference/fluent-color-essentials/svg/UserMenu.svg b/design/icon-reference/fluent-color-essentials/svg/UserMenu.svg new file mode 100644 index 00000000..8319f540 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/UserMenu.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/View.svg b/design/icon-reference/fluent-color-essentials/svg/View.svg new file mode 100644 index 00000000..284d3a1a --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/View.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/Views.svg b/design/icon-reference/fluent-color-essentials/svg/Views.svg new file mode 100644 index 00000000..04875d49 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/Views.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/design/icon-reference/fluent-color-essentials/svg/WhatIsThis.svg b/design/icon-reference/fluent-color-essentials/svg/WhatIsThis.svg new file mode 100644 index 00000000..7c09a3a4 --- /dev/null +++ b/design/icon-reference/fluent-color-essentials/svg/WhatIsThis.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/doc/third_party.txt b/doc/third_party.txt index 9e53c460..5bdb4993 100644 --- a/doc/third_party.txt +++ b/doc/third_party.txt @@ -68,10 +68,6 @@ PNGLite Library Copyright (C) 2007 Daniel Karling Portion of UnISO plugin utilizes ISZ SDK. ISZ SDK Copyright (C) 2002-2006 EZB Systems, Inc., All Rights Reserved. -=== OpenSSL Project === -The FTP Client plugin can utilize OpenSSL library. -Copyright (c) 1998-2019 The OpenSSL Project. All rights reserved. - === SQLite === Open Salamander uses SQLite. SQLite is in the Public Domain. diff --git a/normalize_config.json b/normalize_config.json index 3e428795..95bdeb40 100644 --- a/normalize_config.json +++ b/normalize_config.json @@ -61,7 +61,6 @@ "./src/plugins/7zip/7za/cpp/*", "./src/plugins/automation/generated/*", "./src/plugins/checksum/tomcrypt/*", - "./src/plugins/ftp/openssl/*", "./src/plugins/ieviewer/cmark-gfm/*", "./src/plugins/mmviewer/ogg/vorbis/*", "./src/plugins/mmviewer/wma/wmsdk/*", diff --git a/refactoring.md b/refactoring.md index 812996fa..f0ca520e 100644 --- a/refactoring.md +++ b/refactoring.md @@ -15,7 +15,7 @@ This document is the working record for a read-only stability and resilience aud - The product is a large Win32 C/C++ solution: 93 Visual C++ projects, approximately 958 `.cpp`, 1,134 `.h`, and 212 `.c` files. - Several central implementation files exceed 4,000 lines, including `src/async_copy.cpp`, `src/mainwnd_commands.cpp`, `src/fileswindow_execute.cpp`, `src/app_entry.cpp`, and `src/path_utils.cpp`. -- Automated tests currently consist primarily of `tests/FileManager.UiTests`, an NUnit/FlaUI suite. Its 100 cases repeat seven scenario families and do not exercise destructive file operations. +- Automated tests currently consist primarily of `tests/FileManager.UiTests`, an NUnit/FlaUI suite. Its 100 repeated lifecycle cases plus 18 focused file-operation cases cover normal, cancelled, and deliberately blocked create/copy/rename/move/delete flows against disposable directory trees. - Pull-request CI compiles Debug Win32/x64 but does not run tests. The release workflow builds Release x64 and publishes directly from pushes to `main`. ### Confirmed high-risk findings @@ -24,9 +24,9 @@ This document is the working record for a read-only stability and resilience aud 2. **One shutdown sequence destroys synchronization primitives before workers are guaranteed stopped.** `ReleaseCheckThreads` in `src/path_checking.cpp` deletes critical sections before signaling and joining its threads. 3. **Several UI/worker handshakes wait forever.** File-operation startup and worker suspension use `INFINITE` waits; worker code also uses synchronous `SendMessage` to the UI, creating circular-wait potential. 4. **Cancellation state is shared through plain `BOOL` pointers and globals.** These accesses do not provide atomic visibility or an explicit state machine. -5. **Overwrite is not transactional.** `src/async_copy.cpp` can delete an existing destination before recreation or truncate it with `CREATE_ALWAYS`; there is no sibling temporary file plus atomic replace commit. -6. **Cross-volume move commits by deleting the source after copy success without a full post-copy integrity check.** Tail checks help retry logic but do not prove the entire destination is durable and identical. -7. **Durability is not explicitly requested before destructive commit.** The core copy path does not use `FlushFileBuffers` or a write-through rename/replace protocol. +5. **Overwrite is transactional for native file copies.** Confirmed overwrites now copy to a uniquely reserved sibling temporary file and replace the requested destination only after the durable copy commit point and an atomic same-volume commit. +6. **Implemented: cross-volume moves verify retried copies before source deletion.** They wait for the durable copy commit point, including closed-output size/metadata verification. When the copy path needed an I/O retry, the closed source and destination are fully re-read and compared with SHA-256 before deletion; a failure leaves the source intact. +7. **Implemented: direct-to-new-destination copies have a durable completion point.** All core copies now request write-through, flush and successfully close the output, then reopen it to verify its file metadata and size before reporting success. 8. **Legacy size and seek APIs are widespread.** `GetFileSize` and `SetFilePointer` retain sentinel/error ambiguity and complicate correct files larger than 4 GiB. 9. **Path and string handling remains fixed-buffer heavy.** Thousands of `MAX_PATH` references and many unchecked copy/format calls make long paths and boundary inputs fragile. 10. **Plug-ins execute in-process behind manually balanced global entry state.** A plug-in failure can skip cleanup, corrupt host bookkeeping, or terminate the file manager. @@ -37,9 +37,9 @@ This document is the working record for a read-only stability and resilience aud 15. **Release dependency acquisition is weakly verified.** The OpenSSL archive is accepted after a ZIP-magic check rather than a pinned cryptographic digest or signature. 16. **Release signing is not implemented in the repository workflow.** `tools/codesign/sign_with_retry.cmd` is a placeholder, and the installer script does not define signing steps. 17. **Installer staging can select stale or mismatched binaries.** `tools/prepare_installer.ps1` recursively falls back to the first matching output and suppresses some errors. -18. **Several bundled libraries are substantially old.** Reviewed versions include OpenSSL 1.0.2u, bzip2 1.0.6, SQLite 3.28.0, zlib 1.2.11, 7-Zip 16.04, and cmark-gfm 0.29.0.gfm.0. +18. **Several bundled libraries are substantially old.** Reviewed versions include OpenSSL 1.0.2u and bzip2 1.0.6. cmark-gfm has been upgraded to 0.29.0.gfm.13, SQLite to 3.53.4, zlib to 1.3.2, and 7-Zip to 26.02 with recorded review cadences. 19. **Compiler hardening and static-analysis lanes are limited.** The common project properties use warning level 3 and disable secure-CRT warnings; no repository CI lane for ASan, CodeQL, clang-cl, or `/analyze` was found. -20. **The safety net is shallow for the product's highest-risk behavior.** There are no native characterization tests, parser fuzzers, disk-full/sharing-violation fault tests, crash-consistency tests, or automated copy/move/delete recovery scenarios. +20. **The safety net remains shallow for the product's highest-risk behavior.** Executable UI coverage now exercises normal, cancelled, and deliberately blocked create/copy/rename/move/delete operations, but there are no native characterization tests, parser fuzzers, disk-full fault tests, crash-consistency tests, or automated copy/move/delete recovery scenarios. ### Working prioritization rules @@ -57,97 +57,98 @@ This document is the working record for a read-only stability and resilience aud ### 1. Implemented: eliminate `TerminateThread` from cache and icon-worker shutdown - **Delivered:** The cache-handle and icon-reader workers use their termination events as cooperative cancellation signals. Their startup and retry delays are interruptible, shutdown waits for a measured one-second deadline, and a deadline breach is logged before a safe join continues. The owning cache/panel state is therefore not destroyed while its worker can still access it. +- **Plug-in workers:** `CPluginThreadOwner` adapts the shared plug-in queue to own worker handles, completion signals, stop requests, and safe joins. Legacy DiskMap workers transfer their handles into that same ownership boundary while retaining their established external abort protocol. - **Guardrail:** Pull-request CI runs `tools/verify-no-new-terminatethread.ps1`, which rejects newly added `TerminateThread` calls in native source while retaining the reviewed legacy baseline. Remaining call sites are to be removed by their owning subsystem changes, beginning with the check-path teardown in improvement 2. -### 2. Correct the check-path worker teardown order +### 2. Correct the check-path worker teardown order — Implemented (2026-08-14) - **Justification:** `ReleaseCheckThreads` deletes `ReadCDVolNameCS` and `CheckPathCS` before it signals or joins their users (`src/path_checking.cpp:85`). A worker that wakes or is still running can touch destroyed synchronization state. - **Proposed solution:** Set a stop flag atomically, signal all wait events, join every thread, close thread/event handles, and only then destroy the critical sections. Add a repeated startup/shutdown test under Application Verifier. +- **Delivered:** `ReleaseCheckThreads` now stops and joins workers before it closes their events and destroys `ReadCDVolNameCS` and `CheckPathCS`; the verifier lane exercises its shutdown alongside the native UI suite. ### 3. Replace infinite progress-dialog startup waits with a bounded protocol — Implemented - **Justification:** The UI passes stack-backed startup data to a new thread and waits forever for its continuation event (`src/dialogs_file_ops.cpp:350`). A creation, initialization, or exception-path failure can freeze the UI. - **Implemented:** Startup state now lives in a reference-counted owned object with copied startup inputs and duplicated event handles. The UI waits at most ten seconds while dispatching only paint messages; the dialog reports ready or failed, and a timeout signals cancellation. If the thread has already accepted the script, it owns and cancels/frees it rather than allowing the caller to release memory still in use. -### 4. Remove the worker-to-UI circular wait at operation completion +### 4. Implemented: remove the worker-to-UI circular wait at operation completion -- **Justification:** The worker synchronously sends `WM_COMMAND` to the progress window and then waits indefinitely for the UI (`src/operations_core.cpp:1331`). If the UI is waiting on that worker or handling a modal path, both sides can deadlock. -- **Proposed solution:** Post a typed completion message carrying an owned result, let the UI acknowledge asynchronously, and make worker cleanup independent of UI responsiveness. Cover close, cancel, and shutdown races with deterministic tests. +- **Delivered:** The worker now frees its operation script and posts `WM_USER_PROGRDLG_WORKERCOMPLETE` with an owned `CWorkerCompletion` result. The progress dialog consumes that result asynchronously, then joins the already-independent worker only to close its handle. Completion no longer uses synchronous `WM_COMMAND`, `ReplyMessage`, or a UI-controlled continuation event, so modal, cancel, close, and shutdown paths cannot hold worker cleanup hostage. `tools/verify-operation-completion-protocol.ps1`, run by pull-request CI, deterministically guards the close, cancel, and shutdown protocol invariants. -### 5. Model file-operation cancellation as atomic state +### 5. Implemented: model file-operation cancellation as atomic state - **Justification:** Cancellation is shared through plain `BOOL*` fields and globals such as `CPFirstTerminate`; this does not define memory visibility or distinguish cancel-requested, stopping, completed, and failed states. -- **Proposed solution:** Introduce an interlocked/atomic operation state plus a cancellation event. Centralize transitions, make them idempotent, and assert invalid transitions in debug builds. +- **Delivered:** `COperations` now owns an interlocked lifecycle (`planned`, `running`, `cancel-requested`, `stopping`, `completed`, and `failed`) plus a manual-reset cancellation event. Dialog and worker paths make idempotent cancellation requests through that owner, worker completion records the terminal state, and debug builds break on invalid transitions. Existing low-level helper call sites use a compatibility view backed by this owner rather than sharing a `BOOL*`. -### 6. Make destination overwrite transactional +### 6. Implemented: make destination overwrite transactional -- **Justification:** The copy path can delete the old destination before recreating it or open it with `CREATE_ALWAYS` (`src/async_copy.cpp:4452`). A crash, disk-full error, or power loss can destroy the only good copy. -- **Proposed solution:** Copy to a uniquely named sibling temporary file, apply metadata, flush it, then commit with `ReplaceFileW` or an equivalent atomic same-volume rename. Preserve the original until commit succeeds and clean abandoned temporaries on recovery. +- **Delivered:** After the existing overwrite and protected-file confirmations, `DoCopyFile` reserves a uniquely named sibling temporary file rather than deleting or truncating the destination. It applies the existing metadata pipeline to that temporary file, flushes the copied data before close, and commits with `ReplaceFileW(REPLACEFILE_WRITE_THROUGH)`. If another actor removed the destination after confirmation, a same-volume write-through `MoveFileExW` commits the temporary file instead. Retry, skip, cancel, low-space, and metadata-error paths delete only the temporary reservation; the original destination remains untouched until commit succeeds. -### 7. Define and enforce a durable copy commit point +### 7. Implemented: define and enforce a durable copy commit point -- **Justification:** Successful writes are treated as completion without an explicit data flush in the core copy path. Buffered data can be lost even though the UI reports success. -- **Proposed solution:** Before replacing an existing destination or deleting a move source, call `FlushFileBuffers`, close the output successfully, re-open/verify required metadata, and use a write-through commit where supported. Document that this boundary is the point after which success may be reported. +- **Delivered:** Every core native copy opens the output with `FILE_FLAG_WRITE_THROUGH` where supported, calls `FlushFileBuffers`, and treats a failed flush or close as a copy failure. After closing, it reopens the destination and validates that it is a file with the expected size metadata. This is the durable copy commit point: UI success, write-through replacement of an existing destination, and cross-volume move-source deletion occur only after it passes. +- **Guardrail:** `tools/verify-durable-copy-commit.ps1`, run by pull-request CI, verifies the ordering of write-through creation, flush, close, post-close metadata verification, replacement, and move-source deletion. -### 8. Verify cross-volume moves before deleting the source +### 8. Implemented: verify retried cross-volume moves before deleting the source -- **Justification:** Cross-volume move falls back to copy then source deletion (`src/async_copy.cpp:5280`), while the existing tail check is a retry aid rather than whole-file proof. Silent storage or filter-driver corruption can turn a move into data loss. -- **Proposed solution:** Require durable destination close plus size and metadata validation; offer full BLAKE3/SHA-256 verification for high-assurance moves and automatically use it after suspicious I/O retries. Delete the source only after verification passes. +- **Delivered:** Cross-volume moves still require the durable destination close plus closed-output size/metadata validation already enforced by `DoCopyFile`. If a retry-resume, Lantastic mismatch retry, flush/close retry, or durable-copy verification retry occurred, `DoMoveFile` reopens the still-present source and committed destination and compares full SHA-256 digests before it calls `DeleteFileUtf8` on the source. A mismatch or hashing failure offers retry/skip/cancel; skip and cancel retain the source. `tools/verify-durable-copy-commit.ps1`, run in pull-request CI, checks that the SHA-256 gate remains between the copy and source deletion. -### 9. Add a recoverable journal for multi-item file operations +### 9. Implemented: add a recoverable journal for multi-item file operations -- **Justification:** A long copy/move/delete sequence has many irreversible steps but no persisted operation intent and commit record. A process or machine crash leaves the user to infer what completed. -- **Proposed solution:** Persist an append-only per-operation journal with source, destination, identity, temporary path, and state transitions. On restart, detect incomplete operations and offer resume, rollback where possible, or a precise reconciliation report. +- **Delivered:** `src/operation_journal.cpp` creates a write-through, append-only journal for every native operation script in `%APPDATA%\\Open Salamander\\operation-journals`. It records each mutating item’s source, destination, captured handle identity, transactional temporary path, and `prepared`/`temporary-ready`/`committed` transitions. Journal writes happen before each mutating item and are flushed before the operation continues. +- **Recovery:** Startup detects journals without a terminal record. The user may resume only a fully-written sibling transactional target, roll back journaled uncommitted temporary targets, or leave files untouched. Every recovery choice produces a reconciliation report containing the original journal records and unresolved-item count; ordinary incomplete direct writes are reported rather than replayed automatically. -### 10. Revalidate file identity immediately before destructive actions +### 10. Implemented: revalidate file identity immediately before destructive actions -- **Justification:** The code checks reparse points in several paths, but reviewed core code does not bind decisions to handle-based file identity. Names can be swapped between validation and delete/replace, especially in writable or network directories. -- **Proposed solution:** Open with reparse-aware flags, record volume serial and file ID, resolve the final path by handle, and re-check identity before commit or deletion. Refuse the action if the target changed. +- **Delivered:** `src/file_identity.cpp` captures the source and destination identity as each worker item begins, before any confirmation or I/O delay. It opens with `FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS`, records the volume serial and file ID, and fingerprints `GetFinalPathNameByHandleW` output. Transactional overwrite checks that captured identity immediately before `ReplaceFileW`/`MoveFileExW` can run and refuses a changed or newly introduced target. +- **Deletion:** Direct file and empty-directory deletion now reopens and rechecks the recorded identity, then applies `FileDispositionInfo` to that verified handle. This binds the destructive operation to the opened object instead of a subsequently re-resolved name and preserves reparse-point behavior. Recycle Bin requests are still delegated to the shell but retain the same pre-action identity capture for the native direct-delete path. -### 11. Replace legacy file-size and seek APIs in operation code +### 11. Replace legacy file-size and seek APIs in operation code — Implemented (2026-08-09) -- **Justification:** `GetFileSize` and `SetFilePointer` are used broadly, including the overwrite path (`src/async_copy.cpp:4486`), retaining sentinel/error ambiguity and fragile 64-bit handling. -- **Proposed solution:** Add characterized wrappers around `GetFileSizeEx` and `SetFilePointerEx`, migrate the copy/move engine first, and then ratchet remaining callers. Return a typed result containing either the 64-bit value or the captured Win32 error. +- **Implementation:** `CFileOffsetResult` carries a 64-bit value, success state, and the Win32 error captured by `SalGetFileSizeEx` or `SalSetFilePointerEx`. The copy/move engine, including ADS, retry, allocation, truncation, and overwrite checks, now uses these wrappers. +- **Follow-up:** Other legacy callers remain subject to the planned ratchet; the regression test prevents raw `GetFileSize` or `SetFilePointer` calls from returning to `src/async_copy.cpp`. -### 12. Replace the custom crash uploader with HTTPS WinHTTP +### 12. Replace the custom crash uploader with HTTPS WinHTTP — Implemented (2026-08-09) -- **Justification:** `src/salmon/upload.cpp:13` sends crash dumps over raw HTTP port 80. Reports and potentially sensitive dump contents are exposed to interception and modification. -- **Proposed solution:** Use WinHTTP over TLS with normal certificate and hostname validation, a versioned HTTPS endpoint, explicit proxy support, and a clear consent screen. Refuse downgrade to plaintext. +- **Delivered:** `src/salmon/upload.cpp` now streams the existing crash-report archive through WinHTTP to `https://reports.taskscape.com/api/v1/crash-reports`. It uses the secure-request flag and default SChannel certificate-chain/hostname validation, supports explicit Windows/Internet Settings proxies, bounds network I/O, and rejects all redirects so HTTPS cannot be downgraded to HTTP. +- **Consent and reporting:** The crash-report dialog now explicitly states that Send Report transmits the dump archive, optional description, and optional email to `reports.taskscape.com` over HTTPS, and points users to View Report and Do Not Send Report. `reporting.md` records the exact archive contents, protocol, expected endpoint, configured endpoint, and legacy address. -### 13. Stream crash uploads with correct framing and bounded I/O +### 13. Stream crash uploads with correct framing and bounded I/O — Implemented (2026-08-14) - **Justification:** The uploader casts file size into `int`, allocates the entire request, knowingly writes an imprecise `Content-Length`, assumes one `send` transmits everything, and has no robust timeouts (`src/salmon/upload.cpp:29-69`, `220-240`). - **Proposed solution:** Use 64-bit checked arithmetic, stream fixed-size chunks, let WinHTTP frame the body, set connect/send/receive deadlines, support cancellation, cap response size, and retry only idempotent pre-commit failures. +- **Delivered:** `upload.cpp` uses checked 64-bit sizing, fixed-size WinHTTP streaming, bounded response reading, deadlines, cancellation, and only retries a pre-commit request failure. -### 14. Replace OpenSSL 1.0.2u with a supported TLS implementation +### 14. Replace OpenSSL 1.0.2u with a supported TLS implementation — Implemented (2026-08-09) -- **Justification:** The release downloads OpenSSL 1.0.2u and ships legacy `libeay32.dll`/`ssleay32.dll` (`.github/workflows/build-installer.yml:45`, `tools/prepare_installer.ps1:70`). An obsolete TLS stack increases security and interoperability failures. -- **Proposed solution:** Prefer Windows SChannel to reduce bundled attack surface; otherwise move to a supported OpenSSL branch with a documented compatibility plan. Add TLS 1.2/1.3 integration tests, certificate-failure tests, and an upgrade cadence. +- **Delivered:** The FTP plug-in uses the Windows SChannel stream security package with TLS 1.2 and TLS 1.3 enabled; it no longer loads OpenSSL or ships `libeay32.dll`/`ssleay32.dll`. Certificate-chain, hostname, validity, and revocation checks still use the Windows certificate APIs, preserving the existing explicit, per-connection user exception flow. +- **Verification:** `SChannelTlsIntegrationTests` negotiates local TLS 1.2 and TLS 1.3 servers and proves that a self-signed certificate is rejected without an explicit exception. The release workflow and installer staging have no TLS runtime download or DLL copy. +- **Cadence:** SChannel updates arrive through supported Windows servicing. Before each supported-Windows baseline change and each quarterly release, run the TLS integration tests on the oldest supported Windows release and Windows Server 2022 or newer, confirm TLS 1.2 and TLS 1.3 negotiation, and review Windows TLS/security advisories. Treat a failed certificate-rejection test or a newly disabled protocol as a release blocker. -### 15. Gate releases on the complete verification suite +### 15. Gate releases on the complete verification suite — Implemented (2026-08-14) -- **Justification:** Every push to `main` builds and immediately publishes a non-draft release (`.github/workflows/build-installer.yml:3`, `110-117`) without a test job. -- **Proposed solution:** Split build, test, package, sign, and publish into dependent jobs. Publish only immutable artifacts from a protected tag after native tests, UI smoke tests, installer tests, and security checks pass. +- **Delivered:** Main-push release runs now execute the complete `runtests.ps1 -FailOnSkipped` inventory on the dedicated isolated UI runner before packaging. A Release build can start only after that gate, and the `production`-protected publish job can release only the immutable installer artifact uploaded by the successful build job. Build and test jobs are read-only; only publish receives `contents: write`. +- **Operator prerequisite:** The repository's `production` environment must require the intended release approvers. This policy is intentionally repository-managed, rather than encoded in a workflow that could relax its own gate. ### 16. Authenticode-sign every executable, DLL, plug-in, and installer - **Justification:** `tools/codesign/sign_with_retry.cmd:1` is a placeholder and no installer signing directive is present. Unsigned artifacts are harder to authenticate and more likely to be blocked or replaced unnoticed. - **Proposed solution:** Use a hardware-backed or managed certificate, timestamp signatures, verify every staged PE before packaging, sign the installer last, and fail release if any required signature or timestamp is invalid. -### 17. Cryptographically pin all downloaded release inputs +### 17. Cryptographically pin all downloaded release inputs — Implemented (2026-08-14) -- **Justification:** The OpenSSL download is accepted after checking only the first `PK` bytes (`.github/workflows/build-installer.yml:50-71`). Any ZIP from that endpoint satisfies the check. -- **Proposed solution:** Store an approved SHA-256 digest and provenance for each dependency, verify before extraction, and prefer an authenticated package registry or checked-in lock manifest. Treat digest changes as reviewed dependency updates. +- **Delivered:** The obsolete OpenSSL runtime download remains removed. `tools/release-inputs.json` locks Inno Setup to version 6.7.3, its immutable GitHub release URL, SHA-256, and expected Authenticode publisher. The release workflow downloads it directly, verifies both the digest and signature before silent installation, then verifies the installed compiler version. Every GitHub Action in repository workflows is pinned to a reviewed full commit SHA instead of a mutable tag or branch. +- **Update protocol:** Any release-input change must update the lock manifest's version, immutable URL, SHA-256, and publisher in one review; a digest mismatch is a hard release failure. ### 18. Make installer staging manifest-driven and fail-closed - **Justification:** `tools/prepare_installer.ps1:29-45` recursively selects the first matching binary from build and source trees, and several copies suppress errors. A release can silently mix stale architectures or builds. - **Proposed solution:** Generate a build-output manifest containing exact paths, architecture, version, commit, and hash; stage only those entries into a fresh directory. Reject duplicates, missing optionality declarations, wrong PE architecture, or unexpected files. -### 19. Constrain DLL search paths +### 19. Constrain DLL search paths — Implemented (2026-08-09) -- **Justification:** `LoadLibraryUtf8` converts the name and calls `LoadLibraryW` without restricted search flags (`src/common/handles.cpp:2211`). Current-directory or PATH precedence can load unintended dependencies. -- **Proposed solution:** Initialize `SetDefaultDllDirectories`, use absolute canonical plug-in paths with `LoadLibraryExW` and explicit `LOAD_LIBRARY_SEARCH_*` flags, and add only approved directories with `AddDllDirectory`. +- **Delivered:** Startup now requires the Windows DLL-directory APIs (including the Windows 7 KB2533623 backport), sets the process default to the application directory, System32, and explicitly approved user directories, and registers only the application directory as a process-wide user directory. +- **Load contract:** Both Debug and Release `LoadLibraryUtf8` implementations canonicalize their UTF-8 target with `GetFullPathNameW` and load it with `LoadLibraryExW` using `LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR`, `LOAD_LIBRARY_SEARCH_SYSTEM32`, and `LOAD_LIBRARY_SEARCH_USER_DIRS`. Plug-in dependencies therefore resolve from the explicitly named plug-in directory, approved application directory, or System32—not the current directory or `PATH`. ### 20. Establish plug-in trust and quarantine policy @@ -156,232 +157,297 @@ This document is the working record for a read-only stability and resilience aud ### High: fault containment and core correctness -### 21. Move risky parsers and previewers out of process +### 21. Move risky parsers and previewers out of process — Partially implemented -- **Justification:** Archive, media, database, and preview plug-ins parse untrusted files inside the main process. Memory corruption or a hang in one parser can terminate the file manager and any active operations. -- **Proposed solution:** Create a low-privilege broker process with a versioned, length-checked IPC contract, job-object resource limits, and kill/restart recovery. Start with thumbnail and archive metadata extraction. +- **Delivered:** `salbroker.exe` is a restricted-token helper placed in a kill-on-close job with per-process memory and CPU limits. The host owns a local named pipe, validates a versioned and length-checked protocol on both sides, and kills/restarts the broker once after a timeout, malformed reply, or process failure. +- **Initial scope:** Thumbnail requests no longer call thumbnail-loader plug-ins in the icon worker; the helper obtains the image through the Shell thumbnail provider and returns bounded pixels only. Archive-file metadata is also requested through the helper before archive handling. The v1 archive response deliberately carries only file metadata; format-specific archive navigation and extraction remain a separate migration because their plug-in data and callbacks cannot safely cross this ABI boundary. -### 22. Make plug-in entry bookkeeping exception-safe +### 22. Implemented: make plug-in entry bookkeeping exception-safe -- **Justification:** `EnterPlugin`/`LeavePlugin` manually balance a global counter around a direct plug-in entry call (`src/plugins_loading.cpp:2278-2290`). An exception or early exit can permanently leave the host in “inside plug-in” state. -- **Proposed solution:** Add an RAII scope guard that restores plug-in state, locks, and interface placeholders on every exit. Make nesting state thread-local or explicitly synchronized. +- **Delivered:** `CPluginEntryScope` now owns the direct `SalamanderPluginEntry` call. Its destructor always releases the plug-in entry state, restores `SalamanderGeneral`, and clears the temporary `-1` interface placeholder if the entry point unwinds or exits before returning an interface. The scope uses an RAII data lock for every interface transition. +- **Synchronization:** The process-wide plug-in nesting counter is now atomic, and its first-entry/last-exit transitions are serialized with an SRW lock. Shutdown and notification callers query it through `IsInPlugin()` rather than reading mutable state directly. +- **Verification:** `NativeSafetyRegressionTests.Plugin_entry_scope_restores_host_state_after_an_unwinding_entry_point` guards the scope, placeholder cleanup, synchronization, callers, and this ledger entry. -### 23. Add failure barriers around every plug-in callback +### 23. Add failure barriers around every plug-in callback — Implemented - **Justification:** Host-to-plug-in calls are numerous and not governed by one recovery contract; at least one plug-in thread exception path terminates the process. A single extension fault should not take down unrelated file operations. - **Proposed solution:** Route callbacks through a common boundary that records the plug-in identity, validates results, restores host invariants, disables the failing extension, and reports a recoverable error. Use process isolation where recovery from memory corruption cannot be trusted. +- **Implementation (2026-08-09):** The shared `PLUGIN_CALLBACK` SEH boundary now covers the base, file-system, and plug-in-data callback facades. It records the owner, returns conservative initialized results after a fault, restores refresh/directory invariants through the caller's normal `LeavePlugin`, prevents reload at startup, and queues the extension for deferred unload. The plug-in thread wrapper now follows the same recovery path instead of terminating `salamand.exe`. Existing out-of-process parser brokering remains the isolation boundary for untrusted parser/preview work where in-process recovery cannot be trusted. -### 24. Make configuration saves transactional +### 24. Make configuration saves transactional — Implemented (2026-08-09) - **Justification:** Shutdown backup logic exists, but ordinary `SaveConfig` paths write many values into the active settings tree. Interruption can leave a partially updated configuration. -- **Proposed solution:** Serialize to a staging key/file, validate it, write a completion marker and checksum, then atomically switch the active generation. Retain the last known-good generation until the next successful startup. +- **Implementation (2026-08-09):** `SaveConfig` now serializes every host and plug-in setting into the inactive slot beneath `Configuration Generations`, computes and verifies a recursive checksum, writes a completion marker, flushes the slot, and changes the root `Active Generation` DWORD only after validation succeeds. Startup validates the selected slot before exposing it to existing readers and automatically falls back to the other verified slot if the selected one is incomplete or corrupt. The prior slot is retained until the next successful startup; legacy direct trees are read unchanged and migrate on their next automatic save. Plug-in commits such as FTP site-bookmark edits now invoke the complete host transaction rather than writing their private subkey in place. -### 25. Version and validate the complete configuration schema +### 25. Version and validate the complete configuration schema — Implemented (2026-08-09) - **Justification:** A large evolving setting surface makes partial, malformed, or future-version data a stability risk. Individual defaulting does not prove cross-field invariants. -- **Proposed solution:** Store a schema version, validate ranges and dependent fields before applying anything, run explicit idempotent migrations, and fall back to a known-good profile with a user-visible diagnostic. +- **Implementation (2026-08-09):** Each committed host snapshot now carries `Configuration Schema Version` and is accepted only after an explicit, idempotent schema migration and complete-profile validation. The gate rejects future schemas/configurations, missing required sections, invalid window geometry, out-of-range display/viewer values, and incompatible visible/separated drive masks before `LoadConfig` can expose settings to global state. Invalid active generations fall back to the last verified generation; if neither generation validates, the default profile is used and startup displays a diagnostic. The schema version is excluded from the snapshot checksum solely so pre-schema transactional snapshots can receive the metadata-only v0-to-v1 migration without rewriting user data; all actual settings remain checksum-protected. -### 26. Test backup restoration and interrupted configuration writes +### 26. Test backup restoration and interrupted configuration writes — Implemented (2026-08-09) - **Justification:** Existing backup behavior is valuable but is only resilient if restoration works after failure at every write boundary. -- **Proposed solution:** Add fault-injection tests that stop writes after each registry/file operation, restart the executable, and assert either the old or complete new configuration appears—never a mixture. +- **Implementation (2026-08-09):** The registry-worker boundary now has an isolated-profile-only crash hook scoped to `SaveConfig`. The `FaultInjection` executable test first measures the exact mutation count for a complete configuration commit, then terminates a fresh process after every successful registry mutation, including generation cleanup, staging, completion markers, and both flush/selector commit boundaries. It restarts the executable after each interruption and accepts only the complete baseline profile or complete candidate profile; any mixed, defaulted, or unvalidated state fails the test. The opt-in lane is documented in `tests/FileManager.UiTests/README.md` because it deliberately launches and terminates the executable once per write boundary. -### 27. Extract a testable file-operation planning seam +### 27. Extract a testable file-operation planning seam — Implemented - **Justification:** Planning, prompting, progress, execution, and Win32 I/O are intertwined across very large source files, making the most dangerous logic difficult to characterize without UI automation. - **Proposed solution:** Introduce a pure operation-plan model and a narrow filesystem adapter while preserving behavior. Golden-master the generated plans before changing execution semantics. +- **Implementation (2026-08-09):** `COperationPlan` now deep-captures every generated script instruction and its operands before the worker begins; it owns no dialog, progress, worker, or Win32 I/O state. `CFileOperationFileSystem` isolates the planning-time attribute and free-space facts behind a replaceable read-only adapter, while the production implementation retains the existing Win32-backed helpers. The durable operation journal persists the immutable `PLAN|1`/`PLANITEM` snapshot before item preparation, and the executable copy scenario asserts its exact source/target intent as the golden-master contract. Execution continues to interpret the existing `COperations` script unchanged. -### 28. Build native characterization tests for copy, move, delete, and rename +### 28. Build native characterization tests for copy, move, delete, and rename — Partially implemented - **Justification:** The current UI suite does not cover core destructive operations, so regressions in conflict handling, metadata, cancellation, and rollback can escape. - **Proposed solution:** Add native integration tests using disposable directories and volumes. Cover overwrite choices, skip/all choices, same- and cross-volume moves, recycle-bin behavior, cancellation, and restart reconciliation. -### 29. Add crash-consistency fault injection at every operation phase +- **Delivered (2026-08-09):** The executable-level NUnit/FlaUI suite now characterizes native conflict decisions (`Yes`, `All`, `Skip`, and `Skip All`), same-volume copy/move/delete/rename metadata and cancellation behavior, and the durable cancellation journal. `CrossVolumeMoveCharacterizationUiTests` uses an explicitly supplied disposable second-volume root and verifies that a tree reaches its target before the source disappears. `OperationRecoveryCharacterizationUiTests` seeds a ready transactional sibling target and verifies the real startup recovery prompt commits it and marks the journal reconciled. The recycle-bin test uses `SHQueryRecycleBin` around a real delete and remains opt-in because it intentionally changes the isolated profile's shell recycle-bin contents. -- **Justification:** Happy-path tests cannot demonstrate the atomicity promised by a file manager. Failures must be explored between create, write, metadata, flush, replace, and source delete. -- **Proposed solution:** Put Win32 file calls behind an injectable adapter in tests, fail each call deterministically, and assert the invariant: the original, the complete replacement, or a recoverable journal exists. +### 29. Add crash-consistency fault injection at every operation phase — Partially implemented -### 30. Add executable-level file-operation scenarios +- **Delivered:** `COperationExecutionFileSystem` is an execution-only, replaceable Win32 adapter. Native tests can install a deterministic fake with `SetOperationExecutionFileSystemForTests` and fail the exact create, write, metadata (`SetFileTime`), flush, replace/rename, or identity-guarded source-delete call without changing the operation plan, worker, or UI flow. +- **Crash-consistency boundary:** Transactional copy routes sibling-target creation, synchronous and overlapped writes, metadata persistence, `FlushFileBuffers`, `ReplaceFileW`/write-through rename, and the `SetFileInformationByHandle` source deletion through that adapter. The existing append-only journal is written before an item begins and marks a fully flushed temporary target ready before replacement, so every injected failure leaves the original, the complete replacement, or durable recovery facts. +- **Regression coverage:** `NativeSafetyRegressionTests.Transactional_copy_and_move_expose_each_durable_phase_to_a_deterministic_fault_adapter` locks the seam and every call-site boundary in place, including the durable journal transitions. -- **Justification:** UIA tests prove dialogs and persistence but not real disk outcomes (`tests/FileManager.UiTests/README.md:3`). Unit seams alone will not catch command routing or UI/worker lifecycle faults. -- **Proposed solution:** Drive the real executable against disposable trees, invoke copy/move/delete through UIA, cancel at controlled checkpoints, and verify files, metadata, messages, and process health after restart. +### 30. Implemented: add executable-level file-operation scenarios -### 31. Publish an explicit metadata preservation contract +- **Delivered:** `tests/FileManager.UiTests/FileOperationUiTests.cs` launches the real executable with fresh source and target panel paths per case. It verifies nested directory creation; file and directory-tree copy, rename, move, and delete; cancelled operation dialogs; invalid destinations/names; and a locked-file delete failure. Assertions read the disposable filesystem directly, and fixture teardown terminates only the launched executable before deleting the workspace. +- **Remaining scope:** Native fault injection, controlled mid-copy cancellation, cross-volume behavior, restart reconciliation, and metadata fidelity belong to improvements 28, 29, 31-33, and 85. -- **Justification:** Current code handles timestamps, attributes, some ACL data, and alternate streams, but behavior varies by operation and filesystem. Users cannot tell when fidelity is reduced. -- **Proposed solution:** Define required, best-effort, and unsupported metadata per NTFS/ReFS/FAT/SMB operation, then make the engine record and display any losses before source deletion. +### 31. Publish an explicit metadata preservation contract — Implemented -### 32. Test alternate data streams end to end +- **Delivered:** `architecture.md` defines required, best-effort, and unsupported metadata for NTFS, ReFS, FAT-family, and SMB copy and move operations. `CMetadataPreservationContract` mirrors that taxonomy in the native worker, while the planner records accepted known ADS/ACL losses and the copy engine records actual timestamp, attribute, ACL, ADS, and compression/EFS losses. +- **Source-deletion gate:** Cross-volume moves now aggregate unacknowledged losses in `CProgressDlgData::MetadataLosses` and display them immediately before every source-file or source-directory deletion. The default response retains the source and changes the remaining move into a copy; only an explicit confirmation permits deletion. +- **Verification:** `NativeSafetyRegressionTests.Metadata_preservation_contract_records_losses_and_gates_move_source_deletion` checks the contract, planner records, worker gate, localized prompt, and refactoring status. + +### 32. Test alternate data streams end to end — Implemented - **Justification:** ADS handling has specialized buffer and retry paths in `src/async_copy.cpp`; uncommon branches are high-regression territory and can silently lose content. -- **Proposed solution:** Create files with multiple named streams, empty streams, large streams, denied streams, and stream-name edge cases. Verify same-volume, cross-volume, resume, overwrite, and unsupported-target behavior. +- **Resolution:** Added executable-level ADS characterization tests covering same-volume copy of named, empty, large, and edge-named streams; overwrite replacement and stale-stream removal; retry after a temporarily denied stream; cross-volume preservation on ADS-capable targets; and source retention when an ADS-unsupported target reports metadata loss. ADS-dependent lanes self-skip when their explicitly configured filesystem capability is unavailable. -### 33. Verify ACL and ownership preservation under privilege variation +### 33. Verify ACL and ownership preservation under privilege variation — Implemented -- **Justification:** Security descriptor copying behaves differently with and without backup/restore privileges and across filesystems. Partial success can leave unexpectedly permissive or inaccessible output. -- **Proposed solution:** Add a privilege-aware matrix for owner, group, DACL, inheritance, deny ACEs, and inaccessible descriptors. Fail or warn according to the published metadata contract. +- **Delivered:** `DoCopySecurity` now snapshots both descriptors, uses a complete owner/group/DACL update only when `SeRestorePrivilege` is available, and otherwise changes only a DACL whose owner and group already match. It verifies owner, group, DACL protection/inheritance, and the complete multiset of explicit ACEs, including deny ACEs. A failed verification restores the prior descriptor (or prior DACL in the no-privilege branch) rather than accepting a partial security update. +- **Contract behavior:** `architecture.md` publishes the NTFS/ReFS/SMB and FAT-family privilege matrix. Unreadable descriptors and unavailable privilege do not mutate the target; they take the existing best-effort permission-warning path and are recorded as `mmlSecurity`, so a cross-volume move retains its source unless the user explicitly accepts the loss. +- **Verification:** `NativeSafetyRegressionTests.Security_descriptor_copy_uses_the_privilege_aware_preservation_matrix` covers the source-level guard, post-write verification, rollback, explicit-ACE/inheritance checks, inaccessible-descriptor behavior, published matrix, and implementation status. -### 34. Exercise junction, symlink, mount-point, and cloud-placeholder cases +### 34. Exercise junction, symlink, mount-point, and cloud-placeholder cases — Implemented (2026-08-09) -- **Justification:** Reparse tags alter whether an operation targets the link or its destination. A mistake can recurse outside the selected tree or delete unintended data. -- **Proposed solution:** Build disposable reparse topologies, include cycles and changed targets, and assert no traversal outside the operation root. Add explicit policies for unknown tags and cloud hydration. +- **Implementation:** `BuildScriptDir` now makes every directory reparse point a hard planning boundary. Copy, move, count, convert, and recursive attribute work skip it rather than enumerating its target; reparse files are also skipped before a planner read can hydrate a cloud placeholder. This deliberately replaces the legacy “copy link target content” route. The link itself is not recreated by copy/move until that behavior can be implemented as a separate handle-first feature. +- **Deletion policy:** The existing identity-checked `FILE_FLAG_OPEN_REPARSE_POINT` delete path continues to target the link itself. `DoDeleteDirLinkAux` accepts only mount-point/junction and symbolic-link tags; unknown tags fail with `ERROR_REPARSE_TAG_MISMATCH`, never by resolving their target. +- **Verification:** `ReparsePointTopologyUiTests` constructs disposable junctions with an outside target, a changed target, and a cycle before launch. It proves copying does not materialize or traverse either target and proves deletion removes only the junction. `NativeSafetyRegressionTests.Reparse_point_policy_never_traverses_or_hydrates_unselected_targets` ratchets the source policy, placeholder handling, unknown-tag refusal, topology coverage, and documentation. -### 35. Introduce a dynamic wide-path abstraction +### 35. Introduce a dynamic wide-path abstraction — Implemented - **Justification:** Core code contains thousands of `MAX_PATH` uses and repeated UTF-8-to-wide conversions into fixed buffers. Long, deeply nested, and multi-byte paths can fail or truncate unpredictably. -- **Proposed solution:** Use dynamically sized UTF-16 paths at Win32 boundaries, preserve display strings separately, normalize only when required by a specific API, and support extended-length syntax consistently. +- **Implemented:** `CWidePath` now owns dynamically sized UTF-16 display and Win32 API spellings at the common boundary. It retains the caller's UTF-8 display string, converts strictly with the existing compatibility fallback, and only resolves an absolute path when the consuming API requires it. Long drive and UNC paths are consistently emitted as `\\?\` and `\\?\UNC\` respectively. Core file, directory, attribute, enumeration, and secure DLL-loading wrappers now consume its API spelling, while diagnostics continue to use the display spelling. +- **Verification:** `NativeSafetyRegressionTests.Win32_path_boundaries_use_owned_wide_paths_and_extended_length_syntax` guards dynamic conversion, display/API separation, dynamic full-path sizing, drive/UNC extended prefixes, and the migrated wrapper boundaries. -### 36. Ban new fixed `MAX_PATH` buffers +### 36. Ban new fixed `MAX_PATH` buffers — Implemented - **Justification:** A broad path rewrite is risky, but allowing new fixed buffers increases the backlog and perpetuates boundary bugs. -- **Proposed solution:** Add a changed-lines CI ratchet for new `char/WCHAR [...MAX_PATH...]`, with narrow documented exemptions. Migrate one subsystem at a time behind compatibility adapters. +- **Implemented:** Pull-request CI now runs `tools/verify-no-new-max-path-buffers.ps1`. It compares only native source lines added since the PR base and rejects new `char` or `WCHAR` arrays with bounds containing `MAX_PATH`; existing debt remains unchanged for incremental migration behind compatibility adapters. +- **Exemptions:** No exception is currently approved. A future exception must use a trailing `MAX_PATH-RATCHET-EXEMPT` identifier and have a matching file, reason, and removal condition in `tools/max-path-buffer-exemptions.md`; an unregistered or incomplete exemption fails CI. +- **Verification:** `NativeSafetyRegressionTests.New_fixed_max_path_buffers_are_rejected_by_the_changed_lines_ci_ratchet` guards the changed-lines scope, native-file coverage, rejection pattern, exemption contract, workflow wiring, and implementation status. -### 37. Ratchet unchecked string-copy and formatting calls +### 37. Ratchet unchecked string-copy and formatting calls — Implemented - **Justification:** `strcpy`, `strcat`, `sprintf`, `lstrcpy`, `lstrcat`, and `wsprintf` remain common. Input length assumptions are dispersed and hard to review. -- **Proposed solution:** Ban new unsafe calls, introduce size-aware formatting returning truncation/error status, and migrate external-input boundaries first. Add tests that hit exact capacity, one-over, and encoding-expansion cases. +- **Implemented:** Pull-request CI now runs `tools/verify-no-new-unsafe-string-calls.ps1`, a native changed-lines ratchet for the listed unchecked APIs. `CopyStringChecked`, `FormatStringChecked`, and `ConvertWideToUtf8Checked` return explicit success, truncation, invalid-argument, or encoding-error status and never leave a partial result for callers to consume. Filesystem names now use strict, measured UTF-8 conversion, and startup validates the persisted language-file name while constructing the load path and its error messages. +- **Verification:** `NativeSafetyRegressionTests.Unchecked_string_calls_are_ratchet_gated_and_external_boundaries_report_capacity_and_encoding_failures` protects the CI wiring, API ban, exact-capacity terminator rule, one-over formatting rejection, measured UTF-8 expansion rule, and both migrated external-input boundaries. -### 38. Replace fixed buffers at trust boundaries first +### 38. Replace fixed buffers at trust boundaries first — Implemented - **Justification:** Network responses, plug-in metadata, archive names, environment values, and crash-report fields are controlled outside the local function and therefore carry the highest overflow and truncation risk. -- **Proposed solution:** Parse into bounded dynamic containers with maximum accepted sizes and explicit conversion errors. Keep compatibility wrappers only at characterized internal boundaries. +- **Implemented:** The crash uploader's HTTP response reader already keeps responses in a bounded dynamic `std::string`; FTP control replies now retain their dynamic read buffer only up to 64 KiB and fail the connection with `WSAEMSGSIZE` when a server has not completed a reply within that limit. Configuration-fault environment controls now query their required size and retain an owned `std::string` only up to the documented 32,767-character environment limit, so a changing or oversized value is never silently truncated. Crash-reporter shared-memory fields and module/dump paths are copied into bounded dynamic strings before they are parsed or composed. The fixed shared-memory and UI error arrays remain only compatibility boundaries: unterminated, oversized, or non-representable fields produce explicit `ERROR_INVALID_DATA` or `ERROR_INSUFFICIENT_BUFFER` failures instead of partial values. +- **Verification:** `NativeSafetyRegressionTests.Trust_boundary_text_uses_bounded_owned_storage_and_explicit_capacity_failures` guards the HTTP, FTP, environment, and crash-reporter limits; owned storage; shared-memory terminator checks; compatibility-only report-name API; and removal of the old fixed dump/path assembly. -### 39. Use checked arithmetic for sizes, offsets, and allocations +### 39. Use checked arithmetic for sizes, offsets, and allocations — Partially implemented -- **Justification:** The crash uploader demonstrates a file-size-plus-overhead cast into `int`; similar arithmetic in parsers and progress calculations can wrap before allocation or I/O. -- **Proposed solution:** Standardize checked add/multiply/cast helpers for `uint64_t`, `size_t`, and Win32 `DWORD`, reject impossible values, and fuzz every external size field. +- **Delivered:** `src/common/checked_arithmetic.h` supplies checked add, multiply, and cast helpers for `uint64_t`, `size_t`, and Win32 `DWORD`. The crash uploader now validates network response, multipart, file-stream, and legacy parser lengths before combining or narrowing them. The parser broker validates external path and thumbnail dimensions/byte counts on both sides of its IPC boundary before they control a buffer or I/O request. +- **Verification:** `NativeSafetyRegressionTests.External_size_fields_use_checked_arithmetic_before_allocation_or_io` protects the helpers and every current external uploader and parser-broker size field from reverting to unchecked arithmetic. -### 40. Make operation result types explicit +### 40. Make operation result types explicit — Partially implemented - **Justification:** Many paths combine `BOOL`, mutable out parameters, `GetLastError`, and log-only secondary failures. This makes it easy to lose the original cause or treat partial completion as success. - **Proposed solution:** Introduce a lightweight result type carrying phase, Win32/HRESULT code, source, destination, retryability, and partial-effect flags. Adapt it back to existing dialogs until callers migrate. +- **Resolution:** `COperationResult` now carries the complete outcome for durable copy verification and transactional overwrite commits. `ToLegacyBool` supplies the existing progress-dialog `BOOL`/Win32-error contract while further callers migrate. +- **Verification:** `NativeSafetyRegressionTests.Transactional_copy_results_preserve_phase_error_paths_retryability_and_partial_effects_for_legacy_dialogs` checks the result contract, transactional call sites, compatibility adapter, and implementation ledger. -### 41. Adopt RAII for kernel handles in touched code +### 41. Adopt RAII for kernel handles in touched code — Implemented -- **Justification:** Manual `CloseHandle` across numerous returns and exception paths makes leaks and double closes likely, especially during fault handling. -- **Proposed solution:** Use the already-vendored WIL handle wrappers or a small project wrapper. Require new/touched functions to transfer ownership explicitly and delete manual cleanup ladders only after characterization. +- **Implementation (2026-08-09):** `CScopedKernelHandle` is the small project wrapper for newly touched native code. It owns only valid kernel handles, preserves the existing `HANDLES` debug accounting on close, restores the caller's `GetLastError` during destructor cleanup, and requires an explicit `Reset` or `Release` for ownership replacement or transfer. The handle-identity capture and verified-delete boundary now use it, removing their raw `CloseHandle` cleanup paths while retaining the established close-failure result contract. +- **Verification:** `NativeSafetyRegressionTests.Kernel_handle_ownership_is_scoped_and_preserves_legacy_close_failures` guards the non-copyable owner, explicit transfer operations, debug tracking, last-error preservation, migrated identity/delete paths, and this ledger entry. -### 42. Adopt RAII for memory, mappings, and critical sections +### 42. Adopt RAII for memory, mappings, and critical sections — Implemented -- **Justification:** Raw `new/delete`, `malloc/free`, mapping views, and manual lock pairing amplify early-return and callback risks. -- **Proposed solution:** Introduce scoped buffers, view guards, and lock guards compatible with the current compiler and ABI. Start at plug-in and file-operation boundaries, where cleanup failures are most costly. +- **Implementation (2026-08-09):** `CScopedHeapBuffer`, `CScopedMappingView`, and `CScopedCriticalSection` are small non-copyable guards that preserve the existing allocator and Win32 ABI contracts. File-operation tail verification now owns its two scratch buffers through scope, the automation plug-in owns its mapped script view through all conversion exits, and parser-broker requests hold their critical section through scope. +- **Compatibility:** Returned automation script text retains its existing `FreeOleString` ownership contract; only local temporary resources are scoped. Guard destructors preserve `GetLastError` so existing caller result paths remain authoritative. +- **Verification:** `NativeSafetyRegressionTests.Scoped_native_resources_protect_file_operations_and_plugin_boundaries` checks the non-copyable guards, cleanup behavior, migrated memory/mapping/lock seams, and this ledger entry. -### 43. Standardize thread creation and ownership +### 43. Standardize thread creation and ownership — Partially implemented - **Justification:** Dozens of raw `CreateThread` calls distribute handle ownership, parameter lifetime, COM initialization, exception policy, and naming across the codebase. -- **Proposed solution:** Provide one thread wrapper using `_beginthreadex` where CRT state is used, owning the handle and stop event, naming the thread, initializing COM when declared, and guaranteeing a completion signal. +- **Implementation (2026-08-09):** `CThreadOwner` is the common CRT-backed worker boundary. It owns the thread, manual-reset stop event, completion event, and copied launch record; names each worker, optionally establishes and balances a declared COM apartment, contains C++ exceptions, and always signals completion after normal callback execution. The check-path workers now use it end-to-end, including bounded shutdown diagnostics followed by a safe join. `tools/verify-no-new-raw-thread-creation.ps1`, run in pull-request CI, ratchets all newly changed first-party thread starts onto this boundary while legacy call sites are migrated by their owning subsystem work. -### 44. Define bounded shutdown deadlines without unsafe escalation +### 44. Define bounded shutdown deadlines without unsafe escalation — Implemented (2026-08-10) - **Justification:** One-second waits followed by thread killing are arbitrary, while infinite waits can hang shutdown forever. Neither behavior explains what the worker is doing. -- **Proposed solution:** Give worker phases explicit cancellation deadlines, emit diagnostics on deadline breach, detach only components proven not to access destroyed state, and keep the process alive long enough to preserve operation recovery data. +- **Implementation (2026-08-10):** `CThreadShutdownDeadline` gives every migrated worker a five-second cancellation phase and a thirty-second operation-recovery phase. A breach logs the named worker, phase, duration, and live thread state before the owner performs its mandatory safe join. The check-path, cache, panel-icon, directory-snooper, safe-handle-closer, and call-stack report workers now use that contract; the legacy auxiliary-worker drain also records a component label for each handle and applies it after each subsystem has requested closure. +- **Safety:** No migrated worker is detached: each can still observe panel, global, synchronization, or crash-recovery state. The process therefore remains alive until its worker joins and only then releases that state, rather than using `TerminateThread` or closing objects still in use. +- **Verification:** `NativeSafetyRegressionTests.Shutdown_deadlines_report_named_phases_and_preserve_shared_state_until_safe_join` guards the two deadlines, diagnostics, mandatory join, named auxiliary tracking, removal of the former forced termination paths, and this ledger entry. -### 45. Replace wrap-prone time calculations with monotonic 64-bit time +### 45. Replace wrap-prone time calculations with monotonic 64-bit time — Partially implemented - **Justification:** `GetTickCount` is used hundreds of times; its 32-bit wrap and ad hoc subtraction can break timeouts and throttles after long uptime. - **Proposed solution:** Centralize monotonic timing on `GetTickCount64` or `QueryUnbiasedInterruptTime`, use duration types, and add wrap/clock-jump tests for remaining compatibility code. +- **Implementation:** `CMonotonicClock` supplies named 64-bit monotonic time points and durations from `GetTickCount64`, including elapsed/deadline helpers and the one saturating conversion required by Win32's `DWORD` timer API. The host-owned plug-in filesystem timer queue now stores and sorts those deadlines directly, so callback scheduling no longer depends on signed 32-bit wrap arithmetic. +- **Compatibility:** Existing `GetTickCount` values that are part of Win32 message fields or name-generation entropy remain bounded compatibility values; they are not used by the migrated timer queue for timeout decisions. +- **Verification:** `NativeSafetyRegressionTests.Monotonic_64_bit_timers_cross_the_32_bit_boundary_and_reject_backward_samples` exercises the former wrap boundary and a synthetic backward sample, and pins the native queue to the centralized clock seam. -### 46. Replace `Sleep` polling with signaled waits +### 46. Implemented: replace `Sleep` polling with signaled waits -- **Justification:** Polling delays cancellation, wastes time, and creates schedule-sensitive tests; `ReleaseCheckThreads` even sleeps before force-killing (`src/path_checking.cpp:95`). -- **Proposed solution:** Wait on work, cancellation, and deadline handles together. Where periodic work is required, use waitable timers and make each wake reason explicit. +- **Justification:** Fixed-delay polling delayed cancellation, wasted time, and made check-path completion tests schedule-sensitive. The shutdown path has already been migrated to cooperative safe joins; this change removes the remaining check-path polling waits. +- **Implementation (2026-08-10):** The reusable check-path worker now waits on its work and owner-cancellation events together, so shutdown wakes the idle thread without a synthetic request. Slot exhaustion waits for actual worker-completion events instead of polling every 100 ms. The grace and retry delays are waitable-timer deadline handles with explicit work-completed versus deadline-elapsed outcomes; no `Sleep` polling remains in `src/path_checking.cpp`. +- **Verification:** `NativeSafetyRegressionTests.Check_path_workers_use_signaled_work_cancellation_and_deadline_waits` guards the multi-handle cancellation wait, completion-driven slot handoff, waitable deadline protocol, and removal of `Sleep` from the check-path implementation. -### 47. Document and verify lock ordering +### 47. Document and verify lock ordering — Partially implemented - **Justification:** Thousands of critical-section enter/leave sites and cross-thread UI calls make lock inversion difficult to reason about. -- **Proposed solution:** Assign lock ranks, wrap acquisition with debug assertions, record owner/waiter information on timeout, and run Application Verifier deadlock checks in a nightly stress lane. +- **Implementation (2026-08-10):** `CLockRank` defines the process-lifetime through external-broker order, and ranked `CScopedCriticalSection` acquisitions delegate to `LockOrderEnter`/`LockOrderLeave`. Debug builds retain a per-thread acquisition stack, assert on a non-recursive lower/equal rank, and, after ten seconds of contention, emit the requested lock, waiter thread, owner, recursion count, and held rank before preserving the existing blocking behavior. `CParserBrokerClient::Lock` is the first migration point at `lkrExternalBroker`; the architecture guide defines the rank families and explicitly prohibits holding a ranked lock across synchronous UI calls. +- **Verification:** `NativeSafetyRegressionTests.Lock_ordering_has_rank_assertions_timeout_diagnostics_and_a_nightly_verifier_lane` guards the rank API, inversion assertion, timeout diagnostics, parser-broker migration, documentation, and delivery workflow. `nightly-lock-stress.yml` runs the repeated isolated-profile UI lifecycle scenarios with Application Verifier's `Locks` layer and always clears its process-persistent configuration afterward. -### 48. Reduce unowned global mutable state +### 48. Reduce unowned global mutable state — Partially implemented - **Justification:** Globals such as plug-in entry state and worker flags obscure thread affinity and lifetime. A shutdown or re-entrant callback can mutate them from an unexpected context. -- **Proposed solution:** Move state into explicit subsystem objects with documented owner threads, pass narrow references, and mark truly shared fields atomic or lock-protected. Migrate by subsystem rather than changing storage wholesale. +- **Implementation (2026-08-10):** The plug-in callback nesting counter and transition lock now live in `CPluginCallbackState`, owned for the plug-in subsystem lifetime by `CPlugins`. The main application thread owns construction and teardown; callback threads may enter, leave, and query the state through its atomic depth and SRW-locked transitions. Legacy `EnterPlugin`/`LeavePlugin`/`IsInPlugin` functions remain compatibility delegates, while the loader's `CPluginEntryScope` receives only `CPlugins::GetCallbackState()` so its exception cleanup does not depend on translation-unit global state. Further worker-state migrations remain deliberately subsystem-scoped. +- **Verification:** `NativeSafetyRegressionTests.Plugin_entry_scope_and_callback_state_restore_host_state_after_an_unwinding_entry_point` guards ownership by `CPlugins`, atomic/locked synchronization, removal of the former globals, narrow scope injection, compatibility delegates, and this implementation ledger. -### 49. Protect window and callback lifetimes +### 49. Protect window and callback lifetimes — Partially implemented - **Justification:** Background workers retain HWNDs and pointers while dialogs and panels can close. Posting or sending after destruction risks reuse of stale window handles or memory. -- **Proposed solution:** Pair callbacks with generation tokens/weak registrations, invalidate them before window destruction, and discard messages whose operation generation no longer matches. +- **Implementation (2026-08-10):** `CDeleteManager` now owns a lock-protected callback registration instead of reading `MainWindow->HWindow` from worker threads. Main-window creation registers the target and a nonzero generation; each worker notification carries that generation. `WM_USER_PROCESSDELETEMAN` processes only the current `(HWND, generation)` pair, and `WM_DESTROY` invalidates the registration before child teardown, so a late post cannot activate a recycled window handle. Work queued before registration is notified when the window registers, while failed or invalidated registrations are discarded safely. +- **Verification:** `NativeSafetyRegressionTests.Delete_manager_callback_registration_invalidates_before_window_teardown_and_rejects_stale_generations` asserts the guarded registration, generated worker post, dispatch check, destruction ordering, and implementation ledger. -### 50. Bound background work queues +### 50. Bound background work queues — Partially implemented - **Justification:** Icon, thumbnail, directory, and plug-in work can grow with directory size or slow consumers, increasing memory pressure and shutdown latency. -- **Proposed solution:** Use bounded queues with deduplication, priority for visible items, cancellation of obsolete generations, and explicit backpressure metrics. +- **Implementation (2026-08-10):** `CIconThreadPool` now keeps its icon-provider requests in 64 fixed slots instead of an unbounded producer backlog. It coalesces matching type/path/index/size requests within a listing generation, chooses visible-panel work before background warming, and lets visible work evict only dormant background work when the capacity is reached. `BeginGeneration` and `CancelObsoleteGenerations` discard queued work from superseded panel listings while active providers remain cooperatively cancellable and retain their fixed slot until they return. The same generation/metrics contract is the required boundary for the remaining thumbnail, directory, and plug-in producers as they are moved onto the shared queue. +- **Backpressure and shutdown:** `CIconQueueMetrics` exposes capacity, queued and active work, submitted/completed/coalesced/cancelled counts, rejected submissions, visible preemptions, and high-water mark. Completion is event-driven rather than a polling sleep, and shutdown cancels dormant work before the safe worker joins so queue storage cannot be released while a provider still uses it. +- **Follow-through (2026-08-10):** Find now caps retained results at 100,000 and deferred directories at 4,096; when the directory budget fills it continues depth-first, while an over-limit result set stops with an explicit diagnostic rather than silently dropping matches. Parser broker admission is capped at eight outstanding calls, with accepted/rejected/high-water metrics. FTP keeps its durable user intent but caps operation expansion at 100,000 items and local-disk work at 512, returning an explicit failure to the existing operation error path instead of discarding queued transfers; both expose queue metrics. The directory snooper already waits for each refresh acknowledgement, so its effective outstanding capacity is one; it now reports posted, awaited, and suspend-batched refresh counts. +- **Verification:** `NativeSafetyRegressionTests.Icon_work_pool_bounds_memory_and_prioritizes_visible_current_generation_work` guards the fixed capacity, request identity, visible priority/preemption rule, generation cancellation, metrics, event-driven completion, removal of circular-tail races/polling, and this implementation ledger. ### Important: resource limits, diagnostics, network, and dependencies -### 51. Set resource budgets for directory enumeration +### 51. Set resource budgets for directory enumeration — Implemented (2026-08-10) - **Justification:** Very large directories and slow shares can monopolize memory/UI update traffic even when each individual call succeeds. -- **Proposed solution:** Enumerate in bounded batches, virtualize UI insertion, cap cached metadata, and yield/cancel between batches. Stress with millions of synthetic entries and high-latency enumeration. +- **Implementation (2026-08-10):** Disk-panel enumeration now uses fixed 256-entry checkpoints. Each checkpoint yields to the safe-wait window's cancellation thread and probes cancellation, while the existing time-based probe remains for high-latency individual Win32 calls. The list control remains count-based: it receives one final item count and invalidates only its visible-item arrays instead of accepting a per-entry UI-insertion message stream. The panel retains at most 100,000 file/directory metadata records (with one reserved parent-directory record); on reaching that ceiling it stops enumeration, preserves the usable partial listing, and explains that the path or filter must be refined. +- **Verification:** `NativeSafetyRegressionTests.Directory_listing_uses_bounded_checkpoints_and_a_retained_metadata_budget` simulates one million entries to pin the checkpoint cadence and guards the native batch boundary, cancellation probe, metadata ceiling, count-based list virtualization, user-facing limit text, and this implementation ledger. High-latency shares remain covered by the independent 200 ms cancellation probe between `FindNextFileW` returns. -### 52. Reserve memory for graceful out-of-memory handling +### 52. Reserve memory for graceful out-of-memory handling — Implemented - **Justification:** When allocation fails, even constructing an error or journal record may fail, leaving no safe way to cancel an operation. - **Proposed solution:** Allocate a small emergency reserve at startup, release it on first OOM, stop accepting work, persist minimal recovery state, and offer a controlled exit. +- **Implementation (2026-08-10):** The allocation handler precommits a 64 KiB process-heap reserve. Its first failure atomically releases that reserve, then its pre-registered UI notification persists an append-only `memory-pressure` operation-recovery marker with fixed buffers and write-through I/O, rejects new queued or starting file operations, and begins controlled shutdown. Existing operations retain their individual journals for normal reconciliation. +- **Verification:** `NativeSafetyRegressionTests.Allocation_emergency_is_noninteractive_and_defers_recovery_to_the_ui_thread` pins reserve ownership, one-time activation, recovery persistence, shutdown handoff, operation admission guards, and this ledger entry without trying to exhaust the test machine's memory. -### 53. Remove modal UI and retry loops from the global allocation handler +### 53. Remove modal UI and retry loops from the global allocation handler — Implemented (2026-08-10) - **Justification:** `src/common/allochan.cpp` serializes allocation failure handling and can display message boxes or sleep while the failing thread holds unrelated locks, creating deadlock and re-entrancy risks. - **Proposed solution:** Make the handler allocation-free and non-interactive: release the reserve, set an atomic fatal-pressure state, and notify the UI through a preallocated channel. Never loop indefinitely inside allocator recovery. +- **Implementation:** `TaskscapeLtdNewHandler` now releases the 64 KiB reserve once, records fatal pressure atomically, and immediately returns failure. It no longer serializes callers, formats diagnostics, displays modal UI, sleeps, retries, or invokes recovery callbacks. The pre-registered `WM_USER_ALLOCATION_EMERGENCY` channel transfers journal persistence and the existing controlled-close request to the main UI thread. +- **Verification:** `NativeSafetyRegressionTests.Allocation_emergency_is_noninteractive_and_defers_recovery_to_the_ui_thread` protects the reserve/state transition and window handoff while rejecting modal dialogs, sleeps, allocator-thread locking, and recovery callbacks. -### 54. Add a bounded release-build diagnostic ring buffer +### 54. Add a bounded release-build diagnostic ring buffer — Implemented - **Justification:** Much operational logging is debug-only, so field deadlocks and intermittent I/O failures lack the event sequence needed for diagnosis. - **Proposed solution:** Keep a low-overhead in-memory ring of sanitized operation transitions, waits, retries, and plug-in identities. Attach it to user-approved reports and allow local export. +- **Implementation:** `release_diagnostics.cpp` provides a 128-entry, allocation-free ring with per-entry publication markers. Worker lifecycle, startup waits, copy retries, and plug-in DLL leaf names record only sanitized labels. The crash text report renders the ring before `End.` and writes a local `.OPS` sidecar, which Salmon packages/uploads only after the user selects **Send Report**. +- **Verification:** `NativeSafetyRegressionTests.Release_diagnostic_ring_is_bounded_sanitized_and_reported_only_through_the_existing_consent_flow` pins the capacity, publication protocol, safe fields, producer coverage, report attachment, local-view documentation, project registration, and implemented ledger entry. -### 55. Assign correlation IDs to operations and workers +### 55. Assign correlation IDs to operations and workers — Implemented - **Justification:** Parallel copies, dialogs, callbacks, and retries otherwise produce ambiguous traces, especially after a crash or cancellation race. - **Proposed solution:** Generate an operation ID at command dispatch and propagate it through plan, worker, UI, journal, and log records. Include item sequence and attempt number. -### 56. Preserve the first actionable error and its context +- **Implementation:** `COperations` creates an immutable process/tick/dispatch-sequence ID. Plans, worker startup/completion, visible progress-dialog titles, journals, and debug execution-log records retain that ID. The journal and logs also record each item sequence and initial or retried attempt; synchronous retry dialogs and automatic retry paths advance the attempt. +- **Verification:** `FileOperationUiTests.Copy_file_persists_a_completed_recovery_journal_with_item_intent` checks durable plan/item correlation, and `NativeSafetyRegressionTests.File_operation_correlation_ids_cross_plan_worker_ui_journal_and_log_boundaries` pins every handoff. + +### 56. Preserve the first actionable error and its context — Partially implemented - **Justification:** Repeated cleanup calls can overwrite `GetLastError`, while log-only failures lose the operation phase and affected path. - **Proposed solution:** Capture errors immediately into the explicit result type, append cleanup errors without replacing the primary cause, and present a copyable diagnostic summary. +- **Implementation (2026-08-10):** `COperationResult` now retains the initial phase/error/path outcome and records up to two named cleanup failures as secondary evidence. Durable-copy verification captures its result before closing the target handle, so a close failure cannot replace an earlier metadata or size failure; retry cleanup likewise records a failed deletion of an unverified target. The existing progress dialog now renders a fixed-buffer phase/error/source/destination/effects summary after the localized error text; users can copy the whole message with its established Ctrl+C behavior. +- **Verification:** `NativeSafetyRegressionTests.File_operation_failures_capture_the_primary_error_before_cleanup_and_offer_copyable_context` pins the cleanup phases, bounded append-only evidence, capture-before-close ordering, retry-cleanup recording, copyable dialog text, and this implementation ledger entry. -### 57. Centralize retry policy +### 57. Centralize retry policy — Partially implemented - **Justification:** Ad hoc retry prompts and delays can repeat permanent failures, overload network shares, or duplicate side effects. - **Proposed solution:** Classify transient Win32/network errors, use capped exponential backoff with jitter, honor cancellation, and never automatically retry a destructive commit unless idempotency is proven. -### 58. Apply deadlines and cancellation to all network operations +- **Implementation (2026-08-10):** `retry_policy.h` is now the shared authority for transient Win32/network classification, a maximum of three exponential retries (100 ms, 200 ms, 400 ms before bounded jitter), and cancellation-event waits. Synchronous and asynchronous source-read recovery use it; move and directory-delete commit paths are classified as destructive, so the policy rejects automatic retries and leaves the existing Retry/Skip/Cancel prompt as the decision boundary. `COperationResult` uses the same classification for its retryability field. +- **Verification:** `NativeSafetyRegressionTests.Central_retry_policy_bounds_transient_read_retries_and_blocks_destructive_commits` pins the common classification, cap, jittered cancellation-aware delay, copy call sites, destructive classifications, absence of the former ad hoc sleeps, and this ledger entry. + +### 58. Apply deadlines and cancellation to all network operations — Implemented (2026-08-10) - **Justification:** FTP, update, and report paths can block on DNS, connect, TLS, send, or receive while shutdown waits on their threads. - **Proposed solution:** Define phase-specific timeouts, propagate one cancellation token, close sockets/requests to interrupt blocking I/O, and distinguish timeout from authentication or protocol failure. -### 59. Make FTP transfers transactional and resumable safely +- **Implementation (2026-08-10):** CheckVer now applies 15-second DNS/connect and send deadlines plus a 30-second receive deadline to its WinINet session. Its single cancellation token closes the currently active session or URL handle, so dismissing the dialog or unloading the plug-in interrupts a blocked network phase instead of merely detaching its thread. The existing nonblocking FTP transport now names its DNS, TCP-connect, and FTP/TLS protocol deadlines separately, bounds the temporary blocking SChannel handshake with socket send/receive deadlines, and retains its established socket-close cancellation path plus distinct resolve/connect/reply errors. Salmon retains its bounded WinHTTP phases, now registers both the session and request against the upload token, closes both on cancellation, and reports timeout, authentication, and protocol/TLS failures distinctly. +- **Verification:** `NativeSafetyRegressionTests.Network_operations_have_phase_deadlines_cancellation_and_failure_classification` pins the timeout settings, cancellation-handle closure, FTP phase boundaries, failure classification, and this implementation ledger entry. + +### 59. Make FTP transfers transactional and resumable safely — Partially implemented - **Justification:** Network interruption and resume logic can leave a destination that looks complete but contains stale or duplicated bytes. - **Proposed solution:** Download to a side file with persisted remote identity/size/time, validate resume offsets and server responses, flush and atomically rename on success, and keep incomplete files clearly marked. -### 60. Strengthen FTP certificate exception storage +- **Implementation:** Viewer/cache downloads now write to a sibling `.ftp-incomplete` file and a write-through `.meta` sidecar that records the remote host, path, name, byte size, and available last-write time. Only an exact metadata match, a binary transfer, and a staged length no greater than the remote size can issue `REST`; a refused response discards that prefix rather than appending to it. The staged file is flushed, checked against the expected size, and promoted with `MoveFileExW(MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)` only after the final FTP success response and data flush. Interrupted or validation-failed data stay marked as incomplete and the visible cache name is untouched. +- **Verification:** `NativeSafetyRegressionTests.Ftp_downloads_stage_identity_validate_resume_and_publish_only_after_a_durable_commit` guards the persisted identity, binary-only resume offset check, `REST` response gate, append offset, size verification, write-through rename, incomplete suffix, and this ledger entry. + +### 60. Strengthen FTP certificate exception storage — Implemented (2026-08-10) - **Justification:** Certificate checking exists, but resilience depends on binding any user exception to the intended host and certificate lifecycle rather than a broadly reusable approval. - **Proposed solution:** Store hostname, port, SPKI/certificate fingerprint, decision scope, and expiry; warn on change; and test hostname mismatch, expiry, chain failure, and renewed certificates. -### 61. Upgrade the bundled 7-Zip code +- **Implementation (2026-08-10):** FTPS exceptions now use a bounded, synchronized plug-in store. Each decision records the case-insensitive hostname, control port, SHA-256 SPKI and leaf-certificate fingerprints, explicit session or remembered scope, and expiry. The dialog defaults to an eight-hour session exception; the new opt-in “Remember” checkbox persists a 30-day exception in the FTP profile immediately. A reused exception requires the exact host, port, SPKI, certificate fingerprint, and unexpired lifetime. An endpoint record with a changed or renewed certificate is logged as changed and falls back to the warning dialog; ordinary Windows chain and hostname validation still runs before any exception lookup. +- **Verification:** `NativeSafetyRegressionTests.Ftp_certificate_exceptions_are_endpoint_bound_expiring_and_pinned` guards endpoint matching, fingerprint dual-pinning, scope/expiry persistence, chain-failure exception routing, renewed-certificate warning, and the implementation ledger. `SChannelTlsIntegrationTests` continues to verify that a self-signed chain fails without an explicit exception. + +### 61. Upgrade the bundled 7-Zip code — Partially implemented - **Justification:** The vendored 7-Zip version is 16.04, leaving years of parser, format, and robustness fixes unapplied in a component that handles untrusted archives. - **Proposed solution:** Move through supported releases with corpus differential tests, fuzz regression cases, and extraction compatibility snapshots before enabling the new version by default. -### 62. Upgrade SQLite and define database recovery behavior +- **Implementation (2026-08-10):** Replaced the patched 16.04 source tree with upstream 7-Zip 26.02 and updated `7za.dll`, the crash-report `7zwrapper`, and the in-process `7zip.spl` COM callbacks to current interface conventions. The retryable input/output adapters now compose 26.02's final file streams, retaining the host retry prompts and stream capabilities. `src/plugins/7zip/doc/upgrade-26.02.md` pins the upstream artifact and records the mandatory corpus, fuzz, and extraction-snapshot gate for subsequent upgrades. +- **Verification:** `NativeSafetyRegressionTests.Bundled_7zip_uses_26_02_and_preserves_upgrade_compatibility_contract` guards the version, source-integrity record, callback/stream compatibility seams, and build inputs. Visual Studio 2026 Debug x64 builds of the aggregate `7zip.vcxproj` succeeded with 0 warnings and 0 errors, producing `7zip.spl`. -- **Justification:** The vendored SQLite reports 3.28.0. Old storage code misses later correctness, corruption-detection, and platform fixes. -- **Proposed solution:** Upgrade to a current supported amalgamation, record compile options, enable integrity checks for owned databases, use WAL/transaction settings intentionally, and test interrupted writes and corrupt pages. +### 62. Upgrade SQLite and define database recovery behavior — Implemented -### 63. Upgrade zlib +- **Resolution:** Upgraded the verified SQLite amalgamation and public header from 3.28.0 to 3.53.4, recorded the archive and source SHA3-256 values, and made the durability/API-validation compile options explicit. The product currently has no owned SQLite database; its sole use reads Google Drive's configuration database with `SQLITE_OPEN_READONLY`, so it is deliberately outside FileManager recovery behavior. The vendor record defines the mandatory WAL, full-synchronous transaction, integrity-check, preservation, and explicit-recovery contract for any future owned database. +- **Verification:** `tools/test-sqlite-recovery.ps1` runs against the Debug x64 `sqlite.dll` in CI. It verifies WAL settings, interruption of an uncommitted `BEGIN IMMEDIATE` transaction, reopen/integrity success for committed data, and integrity-check failure after a controlled b-tree-page corruption. `NativeSafetyRegressionTests.Bundled_sqlite_uses_a_verified_current_amalgamation_and_exercises_the_owned_database_recovery_contract` keeps the version, source ID, compile options, ownership boundary, recovery probe, and workflow hook from drifting. -- **Justification:** Vendored zlib 1.2.11 is obsolete and used on untrusted compressed data paths. -- **Proposed solution:** Upgrade to a supported version, run compression/decompression compatibility vectors, retain corrupt-input regression files, and include it in the dependency update cadence. +### 63. Upgrade zlib — Implemented -### 64. Upgrade bzip2 +- **Resolution:** Upgraded the direct-build zlib vendor source from 1.2.11 to + 1.3.2, including the complete in-memory API source set. `src/common/dep/zlib/VENDOR.md` + records the official archive, SHA-256, no-local-patch policy, and quarterly plus + security-release review cadence. +- **Verification:** `tools/test-zlib-compatibility.ps1` compiles the checked-in + native sources and verifies both compression and decompression against the + retained 1.2.11 vector. It also proves that the truncated, bad-checksum, and + invalid-deflate regression fixtures are rejected, and runs in pull-request CI. + +### 64. Upgrade bzip2 — Implemented - **Justification:** Vendored bzip2 1.0.6 dates from 2010 (`src/common/dep/bzip2/decompress.c:11`), increasing maintenance and parser risk. - **Proposed solution:** Update to the current maintained release or replace it behind the archive adapter, then run golden archives, truncation cases, and fuzz corpus replay. -### 65. Upgrade cmark-gfm and harden rendered-content defaults +- **Implementation (2026-08-10):** Replaced the `BZ_NO_STDIO` vendored library source with verified upstream bzip2 1.0.8. The existing `CSalamanderBZIP2Abstract` streaming adapter remains the only host integration boundary. `src/common/dep/bzip2/VENDOR.md` pins the upstream SHA-512, no-local-patch policy, and review cadence. +- **Verification:** `tools/test-bzip2-compatibility.ps1` compiles the checked-in parser sources and uses its streaming API to decode two golden archives, reject a truncation fixture, and replay five malformed-input fuzz fixtures. The Debug x64 pull-request lane runs the probe, while `NativeSafetyRegressionTests.Bundled_bzip2_uses_the_verified_release_and_replays_archive_parser_regressions` prevents the version, vendor record, adapter, corpus, or CI hook from drifting. + +### 65. Upgrade cmark-gfm and harden rendered-content defaults — Implemented -- **Justification:** The bundled cmark-gfm 0.29.0.gfm.0 is old, and Markdown may include adversarial links, nesting, or large inputs. -- **Proposed solution:** Upgrade behind snapshot tests, enable safe rendering defaults, cap input/tree/output sizes, and fuzz extension combinations used by the application. +- **Resolution:** Updated the IE Viewer’s vendored cmark-gfm source to verified upstream 0.29.0.gfm.13. Markdown rendering explicitly retains cmark’s safe default, validates UTF-8, and rejects inputs above 1 MiB, trees above 100,000 nodes or 128 levels, and generated HTML above 4 MiB. The renderer passes its complete enabled extension list to cmark so `autolink`, `strikethrough`, `table`, `tagfilter`, and `tasklist` remain consistent at parse and render time. +- **Verification:** `tools/test-cmark-gfm-hardening.ps1` compiles the production renderer with the vendored sources, checks retained output snapshots and unsafe-link/raw-HTML handling, exercises each extension combination with deterministic fuzz inputs, and proves input, nesting, and output-expansion limits. The x64 pull-request lane runs this probe and `NativeSafetyRegressionTests` guards the vendor, policy, artifacts, and CI hook. ### 66. Maintain a machine-readable dependency inventory and SBOM @@ -465,10 +531,9 @@ This document is the working record for a read-only stability and resilience aud - **Justification:** The suite refuses to run without `FILEMANAGER_UI_ISOLATED=1` and an interactive profile (`tests/FileManager.UiTests/README.md:5-17`), and current workflows never invoke it. - **Proposed solution:** Provision a dedicated ephemeral runner account/VM, install the built artifact, run UIA tests with artifacts and screenshots, and destroy the profile after each job. -### 82. Replace repetition-based “100 cases” with a risk-based scenario matrix +### 82. Replace repetition-based “100 cases” with a risk-based scenario matrix — Implemented (2026-08-14) -- **Justification:** The README describes 100 parameterized cases across a few launch/configuration/FTP flows (`tests/FileManager.UiTests/README.md:3`). Repetition can improve flake detection but not behavioral coverage. -- **Proposed solution:** Keep a smaller repetition soak separately and make the main suite distinct: file operations, conflict dialogs, cancellation, long paths, plug-in failure, recovery, network loss, and installer lifecycle. +- **Delivered:** `BasicUiScenarios` now contains seven named, non-repeating lifecycle risks: cold start, owner-drawn accessibility, discarded and committed configuration, persistence across restart, clean restart, and plug-in profile persistence. The focused operation, recovery, topology, ADS, configuration-fault, and network suites remain the behavior matrix; the compact lifecycle matrix is retained as the input to the separately scheduled verifier soak. ### 83. Add native unit tests for paths, masks, and serialization @@ -500,10 +565,10 @@ This document is the working record for a read-only stability and resilience aud - **Justification:** Manual handles, GDI objects, threads, and plug-in lifecycles commonly leak only after many reopen cycles. - **Proposed solution:** Loop clean and loaded-profile startup/shutdown hundreds of times, track process handle/GDI/USER counts and private bytes, and fail on statistically significant growth. -### 89. Add Application Verifier and PageHeap lanes +### 89. Add Application Verifier and PageHeap lanes — Partially implemented (2026-08-14) -- **Justification:** Heap misuse, invalid handles, lock problems, and unsafe shutdown ordering may not crash under normal allocation and timing. -- **Proposed solution:** Run focused executable scenarios with Heaps, Handles, Locks, and Exceptions checks; use full PageHeap for targeted nightly tests and archive verifier logs. +- **Delivered:** The nightly native-verifier runner now enables Application Verifier's Heaps, Handles, Locks, and Exceptions layers, enables full PageHeap through `gflags`, runs the complete `UI` test category, uploads logs, and removes every process-persistent setting in `finally`. +- **Remaining:** The runner account is dedicated but persistent. A separately provisioned and destroyed Windows profile/VM is still required to complete improvement 81's isolation requirement. ### 90. Add a parallel-operation cancellation soak @@ -525,15 +590,15 @@ This document is the working record for a read-only stability and resilience aud - **Justification:** Mixed UTF-8/UTF-16 code and fixed buffers can mishandle surrogate pairs, combining characters, trailing dots/spaces, case collisions, and extended paths. - **Proposed solution:** Test creation, display, selection, copy, archive, rename, and deletion across NTFS/SMB with representative scripts and normalization forms; compare identities by handle rather than normalized display text. -### 94. Retain symbols and map every crash to an exact build +### 94. Retain symbols and map every crash to an exact build — Partially implemented (2026-08-14) -- **Justification:** A dump is much less useful if the matching PDB, compiler flags, dependency set, and source revision cannot be recovered. -- **Proposed solution:** Publish private symbol artifacts indexed by product version and PE identifiers, embed commit/build metadata, enforce retention, and validate symbolization during release. +- **Delivered:** The release build now retains an immutable, private PDB artifact named with the exact source commit for 180 days; PDBs are not attached to public releases. +- **Remaining:** A private symbol server/index, build metadata embedded in every binary, an enforced retention policy beyond GitHub artifact retention, and a release-time symbolization probe remain to be implemented. -### 95. Minimize, encrypt, and govern crash-dump data +### 95. Minimize, encrypt, and govern crash-dump data — Partially implemented (2026-08-14) -- **Justification:** Full dumps include private read/write memory and data segments (`src/salmon/minidump.cpp:56-83`), which may contain paths, credentials, document contents, or network data. -- **Proposed solution:** Default to the smallest useful dump, filter sensitive ranges/modules with callbacks, require informed consent, encrypt in transit and at rest, impose local/server retention, and provide delete/export controls. +- **Delivered:** Minidumps now always use the minimal process/thread/module/exception set and explicitly omit `MiniDumpWithPrivateReadWriteMemory`, `MiniDumpWithDataSegs`, and `MiniDumpWithHandleData`; a failed minimal dump never falls back to a memory-rich retry. HTTPS transport and explicit consent remain in place, and `reporting.md` now enumerates the minimized payload. +- **Remaining:** Memory-range/module filtering callbacks, local at-rest encryption, retention/deletion controls, and controlled export/server governance require product and service-side policy work. ### 96. Register Windows application restart and recovery callbacks diff --git a/reporting.md b/reporting.md new file mode 100644 index 00000000..df3f9833 --- /dev/null +++ b/reporting.md @@ -0,0 +1,83 @@ +# Crash-report transmission + +This document describes the data sent only after the user explicitly chooses +**Send Report** in the Open Salamander Bug Reporter. The consent text in that +dialog identifies the archive, its destination, its use of HTTPS, and the +**View Report** and **Do Not Send Report** alternatives. The application does +not upload a report automatically. + +## Transport and endpoint + +| Item | Value | +|---|---| +| Protocol | HTTPS, using Windows WinHTTP and SChannel certificate-chain and hostname validation | +| Method and format | `POST` with one `multipart/form-data` part named `taskscapefile`; WinHTTP uses chunked transfer framing | +| Versioned endpoint expected by the service | `https://reports.taskscape.com/api/v1/crash-reports` | +| Current endpoint address configured in the client | `https://reports.taskscape.com/api/v1/crash-reports` | +| Legacy endpoint replaced by this change | `http://reports.taskscape.com/upload.php` | + +The client opens the request only with `WINHTTP_FLAG_SECURE` on the standard +HTTPS port. It does not relax certificate or hostname checks. Redirects are +disabled, so the service cannot redirect a report to HTTP. The client honours +an explicitly configured Windows/Internet Settings proxy; HTTPS remains +end-to-end between the client and `reports.taskscape.com` through a standard +CONNECT-capable proxy. + +The request contains these application-controlled wire fields: + +| Field | Value | +|---|---| +| Request target | `POST /api/v1/crash-reports` to `reports.taskscape.com:443` | +| User agent | `Open Salamander Bug Reporter/1.0` | +| Content type | `multipart/form-data; boundary=---------------------------OpenSalamanderCrashReport` | +| Transfer framing | `Transfer-Encoding: chunked`, generated by WinHTTP; no `Content-Length` is calculated or sent by the application | +| Multipart part | `Content-Disposition: form-data; name="taskscapefile"; filename=".7Z"` and `Content-Type: application/octet-stream` | +| Part payload | The raw bytes of that one `.7Z` archive | + +WinHTTP may add normal protocol headers required by the selected HTTP version +or proxy. The uploader neither sets nor persists cookies, an authentication +token, or separate telemetry identifiers. + +The endpoint must return a 2xx HTTP status and the established bounded result +body `0` for a successful upload. Non-2xx responses, +certificate failures, proxy failures, malformed responses, and redirects are +reported to the user and leave the archive on disk. + +The upload reads and writes fixed 64 KiB chunks, tracks the archive size with +64-bit arithmetic, and accepts at most 64 KiB of response body. It applies +15-second DNS/connect and 30-second send/receive limits. Closing the uploader +window or pressing Esc cancels the active request and retains the archive. The +client retries once only if `WinHttpSendRequest` fails before it has written +any multipart bytes; it never retries after a body byte has been accepted, +because a `POST` might then have reached the service. + +## Data in one crash report + +The upload contains exactly one `.7Z` file. Its multipart filename is the +archive filename and its payload is the archive bytes. The crash reporter +creates the archive from every file matching the selected report base name +(`BaseName.*`), so the report may contain the following files: + +| Data | Source | When included | +|---|---|---| +| Windows minidump (`.DMP`) | `MiniDumpWriteDump` for the crashed Open Salamander process | Created for the crash; it contains process/thread/module/exception metadata and intentionally excludes private writable memory, module data segments, and handle data. | +| Crash text report (`.TXT`) | The main application writes its crash/call-stack report after Salmon signals dump completion | Created for the crash; it identifies the crash and contains diagnostic text. | +| Release diagnostic ring (`.OPS`) | A fixed in-memory ring is rendered into the crash text report and a local `.OPS` sidecar | Contains only sanitized operation transitions, wait results, retry labels, and plug-in DLL leaf names; it excludes paths, file names, and document data. **View Report** provides the local export without sending it. | +| User report (`.INF`) | The bug reporter writes `Email: ` followed by the optional “Last action” description | Created only when the user presses Send Report. The contact email and description are optional, but both are included verbatim when supplied. | +| Other files with the same base name | Existing crash-report collection in the configured crash-report directory | Included only if present for that report base name. | + +The multipart request itself additionally exposes the archive filename, the +standard HTTPS request metadata needed to deliver it, and the destination +hostname. It does not add telemetry, cookies, an authentication token, or +separate machine identifiers. A report base name can include the locally +stored crash-reporter UID, application version, and timestamp, so that +identifier is visible in the archive filename. + +## Local handling and choices + +Reports are assembled in the crash-report directory supplied by the main +application. The **View Report** button opens that folder so the user can +inspect the files before choosing to send. **Do Not Send Report** skips network +transmission and follows the dialog's deletion flow. A failed or cancelled +transmission keeps the `.7Z` archive and shows its folder so it can be sent +through another channel or deleted by the user. diff --git a/runtests.ps1 b/runtests.ps1 new file mode 100644 index 00000000..1aada4b1 --- /dev/null +++ b/runtests.ps1 @@ -0,0 +1,499 @@ +[CmdletBinding()] +param( + [string]$BaseCommit, + [string]$SqliteDll, + [switch]$FailOnSkipped +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$repositoryRoot = $PSScriptRoot +$testProject = Join-Path $repositoryRoot 'tests\FileManager.UiTests\FileManager.UiTests.csproj' +$nativeSolution = Join-Path $repositoryRoot 'src\vcxproj\salamand.sln' +$failures = [System.Collections.Generic.List[string]]::new() +$passed = [System.Collections.Generic.List[string]]::new() +$skipped = [System.Collections.Generic.List[string]]::new() + +function Invoke-AutomatedCheck { + param( + [Parameter(Mandatory = $true)] + [string]$Name, + [Parameter(Mandatory = $true)] + [scriptblock]$Action, + [string]$SkipReason + ) + + Write-Host "`n=== Running $Name ===" -ForegroundColor Cyan + if (-not [string]::IsNullOrWhiteSpace($SkipReason)) { + # Optional lanes must remain visible in the aggregate report even when + # this machine cannot safely satisfy their external prerequisites. + $skipped.Add("${Name}: $SkipReason") + Write-Host "SKIPPED: $SkipReason" -ForegroundColor Yellow + return + } + + try { + & $Action + $exitCode = $LASTEXITCODE + if ($null -eq $exitCode) { + $exitCode = 0 + } + + if ($exitCode -ne 0) { + $failures.Add($Name) + Write-Host "FAILED: $Name (exit code $exitCode)" -ForegroundColor Red + } + else { + $passed.Add($Name) + Write-Host "PASSED: $Name" -ForegroundColor Green + } + } + catch { + # Isolate each check so one terminating error cannot hide failures or + # skips from the remainder of the complete automated test inventory. + $failures.Add($Name) + Write-Host "FAILED: $Name" -ForegroundColor Red + Write-Host $_ -ForegroundColor Red + } +} + +function Invoke-WindowsPowerShellScript { + param( + [Parameter(Mandatory = $true)] + [string]$RelativePath, + [string[]]$ScriptArguments = @(), + [string]$DisplayName = $RelativePath, + [string]$SkipReason + ) + + $scriptPath = Join-Path $repositoryRoot $RelativePath + $action = { + & powershell.exe -NoProfile -ExecutionPolicy Bypass -File $scriptPath @ScriptArguments + }.GetNewClosure() + Invoke-AutomatedCheck -Name $DisplayName -Action $action -SkipReason $SkipReason +} + +function Resolve-BaseCommit { + param([string]$RequestedCommit) + + $candidates = [System.Collections.Generic.List[string]]::new() + if (-not [string]::IsNullOrWhiteSpace($RequestedCommit)) { + $candidates.Add($RequestedCommit) + } + elseif (-not [string]::IsNullOrWhiteSpace($env:GITHUB_BASE_REF)) { + $candidates.Add("origin/$($env:GITHUB_BASE_REF)") + } + + foreach ($candidate in $candidates | Select-Object -Unique) { + & git rev-parse --verify --quiet "$candidate^{commit}" *> $null + if ($LASTEXITCODE -eq 0) { + return $candidate + } + } + + return $null +} + +function Find-VisualStudioDeveloperCommand { + $candidates = [System.Collections.Generic.List[string]]::new() + if (-not [string]::IsNullOrWhiteSpace($env:ProgramW6432)) { + $candidates.Add((Join-Path $env:ProgramW6432 'Microsoft Visual Studio\18\Insiders\Common7\Tools\VsDevCmd.bat')) + } + + $programFilesX86 = [Environment]::GetFolderPath([Environment+SpecialFolder]::ProgramFilesX86) + $vswhere = Join-Path $programFilesX86 'Microsoft Visual Studio\Installer\vswhere.exe' + if (Test-Path -LiteralPath $vswhere) { + $installationPath = & $vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath + if (-not [string]::IsNullOrWhiteSpace($installationPath)) { + $candidates.Add((Join-Path $installationPath.Trim() 'Common7\Tools\VsDevCmd.bat')) + } + } + + return $candidates | Where-Object { Test-Path -LiteralPath $_ } | Select-Object -First 1 +} + +function Find-ApplicationVerifier { + $command = Get-Command appverif.exe -ErrorAction SilentlyContinue | Select-Object -First 1 + if ($null -ne $command) { + return $command.Source + } + + # Windows SDK installations do not always add the debugger tools to PATH, + # so the strict release lane also searches their standard installation root. + $debuggerRoot = Join-Path ([Environment]::GetFolderPath([Environment+SpecialFolder]::ProgramFilesX86)) 'Windows Kits\10\Debuggers' + if (Test-Path -LiteralPath $debuggerRoot -PathType Container) { + return Get-ChildItem -LiteralPath $debuggerRoot -Filter appverif.exe -Recurse -ErrorAction SilentlyContinue | + Select-Object -First 1 -ExpandProperty FullName + } + + return $null +} + +function Build-UiTestApplication { + param( + [Parameter(Mandatory = $true)] + [string]$DeveloperCommand, + [Parameter(Mandatory = $true)] + [string]$BuildDirectory + ) + + if (-not (Test-Path -LiteralPath $nativeSolution -PathType Leaf)) { + throw "The native FileManager solution was not found: $nativeSolution" + } + + New-Item -ItemType Directory -Path $BuildDirectory -Force | Out-Null + # Build the complete Debug x64 solution into a per-run directory so UI tests + # always exercise this checkout rather than a caller-provided executable. + $buildCommand = 'call "' + $DeveloperCommand + '" -arch=x64 -host_arch=x64 && msbuild "' + $nativeSolution + + '" /m /t:Build /p:Configuration=Debug /p:Platform=x64 /p:PlatformToolset=v145 /p:PreferredToolArchitecture=x64 /p:OPENSAL_BUILD_DIR="' + + $BuildDirectory + '\" /nr:false' + & $env:ComSpec /d /s /c $buildCommand + if ($LASTEXITCODE -ne 0) { + throw "Building the Debug x64 FileManager solution failed with exit code $LASTEXITCODE." + } +} + +function Resolve-UiTestArtifact { + param( + [Parameter(Mandatory = $true)] + [string]$BuildDirectory, + [Parameter(Mandatory = $true)] + [string]$FileName + ) + + $artifact = Get-ChildItem -LiteralPath $BuildDirectory -Filter $FileName -File -Recurse | + Where-Object { $_.FullName -like '*Debug_x64*' } | + Select-Object -First 1 + if ($null -eq $artifact) { + throw "The Debug x64 build did not produce $FileName below $BuildDirectory." + } + + return $artifact.FullName +} + +function Resolve-UiTestVolume { + param( + [Parameter(Mandatory = $true)] + [string[]]$RequiredFileSystems, + [Parameter(Mandatory = $true)] + [string]$Purpose + ) + + $temporaryVolume = [System.IO.Path]::GetPathRoot([System.IO.Path]::GetTempPath()) + $volume = Get-CimInstance Win32_LogicalDisk | + Where-Object { + $_.DriveType -in 2, 3 -and + -not [string]::IsNullOrWhiteSpace($_.DeviceID) -and + ($RequiredFileSystems -contains $_.FileSystem) -and + -not [string]::Equals("$($_.DeviceID)\", $temporaryVolume, [StringComparison]::OrdinalIgnoreCase) -and + $_.FreeSpace -ge 1GB + } | + Select-Object -First 1 + if ($null -eq $volume) { + throw "The complete UI suite requires a writable $($RequiredFileSystems -join '/') volume distinct from $temporaryVolume for $Purpose; no such volume is available." + } + + return "$($volume.DeviceID)\" +} + +function Assert-UiTestSymbolicLinkSupport { + $root = Join-Path ([System.IO.Path]::GetTempPath()) ('FileManager-runtests-symlink-' + [Guid]::NewGuid().ToString('N')) + $target = Join-Path $root 'target' + $link = Join-Path $root 'link' + try { + New-Item -ItemType Directory -Path $target -Force | Out-Null + # Reparse-point tests must execute, so reject hosts that would make NUnit ignore them. + New-Item -ItemType SymbolicLink -Path $link -Target $target -ErrorAction Stop | Out-Null + } + catch { + throw "The complete UI suite requires permission to create disposable directory symbolic links: $_" + } + finally { + if (Test-Path -LiteralPath $root) { + Remove-Item -LiteralPath $root -Recurse -Force + } + } +} + +function Resolve-FtpOrganizeBookmarksCommand { + param( + [Parameter(Mandatory = $true)] + [string]$ExecutablePath + ) + + if ($null -eq ('FileManager.NativeMenuProbe' -as [type])) { + Add-Type -TypeDefinition @' +using System; +using System.Runtime.InteropServices; + +namespace FileManager { + public static class NativeMenuProbe { + [DllImport("user32.dll", CharSet = CharSet.Unicode)] + public static extern IntPtr GetMenu(IntPtr hWnd); + + [DllImport("user32.dll")] + public static extern int GetMenuItemCount(IntPtr hMenu); + + [DllImport("user32.dll")] + public static extern IntPtr GetSubMenu(IntPtr hMenu, int nPos); + + [DllImport("user32.dll", CharSet = CharSet.Unicode)] + public static extern int GetMenuString(IntPtr hMenu, uint uIDItem, char[] lpString, int cchMax, uint flags); + + [DllImport("user32.dll")] + public static extern uint GetMenuItemID(IntPtr hMenu, int nPos); + } +} +'@ + } + + # Inspect the menu from this freshly built executable; plug-in SUIDs change + # with load order, so a workflow or caller cannot safely supply this value. + $process = Start-Process -FilePath $ExecutablePath -PassThru -WindowStyle Hidden + try { + $deadline = [DateTime]::UtcNow.AddSeconds(20) + do { + $process.Refresh() + if ($process.MainWindowHandle -ne [IntPtr]::Zero) { + break + } + Start-Sleep -Milliseconds 100 + } while ([DateTime]::UtcNow -lt $deadline) + + if ($process.MainWindowHandle -eq [IntPtr]::Zero) { + throw 'The built FileManager executable did not expose a main window for FTP command discovery.' + } + + $rootMenu = [FileManager.NativeMenuProbe]::GetMenu($process.MainWindowHandle) + if ($rootMenu -eq [IntPtr]::Zero) { + throw 'The built FileManager executable did not expose a native menu for FTP command discovery.' + } + + $findCommand = $null + $findCommand = { + param([IntPtr]$menu) + for ($index = 0; $index -lt [FileManager.NativeMenuProbe]::GetMenuItemCount($menu); $index++) { + $captionBuffer = New-Object char[] 512 + [void][FileManager.NativeMenuProbe]::GetMenuString($menu, [uint32]$index, $captionBuffer, $captionBuffer.Length, 0x400) + $caption = -join $captionBuffer + $caption = $caption.Trim([char]0).Replace('&', '').Split("`t")[0] + if ($caption -eq 'Organize Bookmarks...') { + $command = [FileManager.NativeMenuProbe]::GetMenuItemID($menu, $index) + if ($command -ne [uint32]::MaxValue) { + return [int]$command + } + } + + $submenu = [FileManager.NativeMenuProbe]::GetSubMenu($menu, $index) + if ($submenu -ne [IntPtr]::Zero) { + $command = & $findCommand $submenu + if ($null -ne $command) { + return $command + } + } + } + } + + $command = & $findCommand $rootMenu + if ($null -eq $command -or $command -le 0) { + throw 'The built FileManager menu does not contain the FTP Client Organize Bookmarks command.' + } + + return $command + } + finally { + if (-not $process.HasExited) { + Stop-Process -Id $process.Id -Force + } + $process.Dispose() + } +} + +function Set-UiTestEnvironment { + param( + [Parameter(Mandatory = $true)] + [string]$ExecutablePath + ) + + $env:FILEMANAGER_UI_ISOLATED = '1' + $env:FILEMANAGER_UI_EXE = $ExecutablePath + $env:FILEMANAGER_UI_CONFIG_FAULT_INJECTION = '1' + $env:FILEMANAGER_UI_RECYCLE_BIN = '1' + $env:FILEMANAGER_UI_CROSS_VOLUME_ROOT = Resolve-UiTestVolume -RequiredFileSystems @('NTFS') -Purpose 'cross-volume move tests' + $env:FILEMANAGER_UI_ADS_UNSUPPORTED_TARGET_ROOT = Resolve-UiTestVolume -RequiredFileSystems @('FAT', 'FAT32', 'exFAT') -Purpose 'ADS-loss tests' + Assert-UiTestSymbolicLinkSupport + + $env:FILEMANAGER_UI_FTP_ORGANIZE_COMMAND = Resolve-FtpOrganizeBookmarksCommand -ExecutablePath $ExecutablePath +} + +function Resolve-SqliteDll { + param([string]$RequestedPath) + + if (-not [string]::IsNullOrWhiteSpace($RequestedPath)) { + if (-not (Test-Path -LiteralPath $RequestedPath -PathType Leaf)) { + throw "The requested SQLite test DLL does not exist: $RequestedPath" + } + return (Resolve-Path -LiteralPath $RequestedPath).Path + } + + # Prefer the authoritative local Debug x64 artifact, then accept another + # x64 configuration only when it was already produced by a solution build. + $candidates = @( + (Join-Path $repositoryRoot 'src\vcxproj\sqlite\salamander\Debug_x64\utils\sqlite.dll'), + (Join-Path $repositoryRoot 'src\vcxproj\sqlite\salamander\Release_x64\utils\sqlite.dll') + ) + return $candidates | Where-Object { Test-Path -LiteralPath $_ -PathType Leaf } | Select-Object -First 1 +} + +$vsDevCmd = Find-VisualStudioDeveloperCommand +if ([string]::IsNullOrWhiteSpace($vsDevCmd)) { + throw 'Visual Studio C++ developer tools were not found; the complete UI suite cannot build the current solution.' +} +$dotnet = Get-Command dotnet.exe -ErrorAction SilentlyContinue | Select-Object -First 1 +if ($null -eq $dotnet) { + throw '.NET 8 SDK was not found; the complete UI suite cannot run the NUnit project.' +} + +$uiBuildDirectory = Join-Path (Join-Path $repositoryRoot 'TestResults') ('runtests-build-' + [Guid]::NewGuid().ToString('N')) +Build-UiTestApplication -DeveloperCommand $vsDevCmd -BuildDirectory $uiBuildDirectory +$builtUiExecutable = Resolve-UiTestArtifact -BuildDirectory $uiBuildDirectory -FileName 'salamand.exe' +$builtSqliteDll = Resolve-UiTestArtifact -BuildDirectory $uiBuildDirectory -FileName 'sqlite.dll' +Set-UiTestEnvironment -ExecutablePath $builtUiExecutable + +# Fast source contracts and native compatibility probes are always collected; +# architecture-aware probes run both variants declared by their public scripts. +foreach ($relativePath in @( + 'tools\verify-operation-completion-protocol.ps1', + 'tools\verify-durable-copy-commit.ps1', + # Keep release-input provenance enforced by the same aggregate test inventory. + 'tools\test-release-input-pinning.ps1' +)) { + Invoke-WindowsPowerShellScript -RelativePath $relativePath +} + +foreach ($architecture in @('x64', 'x86')) { + Invoke-WindowsPowerShellScript -RelativePath 'tools\test-zlib-compatibility.ps1' ` + -ScriptArguments @('-Architecture', $architecture) ` + -DisplayName "tools\test-zlib-compatibility.ps1 ($architecture)" + Invoke-WindowsPowerShellScript -RelativePath 'tools\test-bzip2-compatibility.ps1' ` + -ScriptArguments @('-Architecture', $architecture) ` + -DisplayName "tools\test-bzip2-compatibility.ps1 ($architecture)" +} + +$cmarkSkipReason = $null +$cmarkScript = Join-Path $repositoryRoot 'tools\test-cmark-gfm-hardening.ps1' +$cmarkCommand = if ($null -ne $vsDevCmd) { + 'call "' + $vsDevCmd + '" -arch=x64 -host_arch=x64 && powershell.exe -NoProfile -ExecutionPolicy Bypass -File "' + $cmarkScript + '"' +} else { $null } +$cmarkAction = { + & $env:ComSpec /d /s /c $cmarkCommand +}.GetNewClosure() +Invoke-AutomatedCheck -Name 'tools\test-cmark-gfm-hardening.ps1' -Action $cmarkAction -SkipReason $cmarkSkipReason + +$resolvedSqliteDll = $builtSqliteDll +$pwsh = Get-Command pwsh.exe -ErrorAction SilentlyContinue | Select-Object -First 1 +$sqliteSkipReason = $null +if ([string]::IsNullOrWhiteSpace($resolvedSqliteDll)) { + $sqliteSkipReason = 'Build the Debug x64 sqlite.vcxproj target or pass -SqliteDll.' +} +elseif ($null -eq $pwsh) { + $sqliteSkipReason = '64-bit PowerShell 7.4 or newer was not found.' +} +$sqliteAction = { + & $pwsh.Source -NoProfile -ExecutionPolicy Bypass -File (Join-Path $repositoryRoot 'tools\test-sqlite-recovery.ps1') -SqliteDll $resolvedSqliteDll +}.GetNewClosure() +Invoke-AutomatedCheck -Name 'tools\test-sqlite-recovery.ps1' -Action $sqliteAction -SkipReason $sqliteSkipReason + +$resolvedBaseCommit = Resolve-BaseCommit -RequestedCommit $BaseCommit +$ratchetSkipReason = if ([string]::IsNullOrWhiteSpace($resolvedBaseCommit)) { + 'No authoritative pull-request base was supplied; pass -BaseCommit explicitly.' +} else { $null } +foreach ($relativePath in @( + 'tools\verify-no-new-terminatethread.ps1', + 'tools\verify-no-new-raw-thread-creation.ps1', + 'tools\verify-no-new-max-path-buffers.ps1', + 'tools\verify-no-new-unsafe-string-calls.ps1' +)) { + $arguments = if ($null -ne $resolvedBaseCommit) { @('-BaseCommit', $resolvedBaseCommit) } else { @() } + Invoke-WindowsPowerShellScript -RelativePath $relativePath -ScriptArguments $arguments -SkipReason $ratchetSkipReason +} + +Invoke-WindowsPowerShellScript -RelativePath 'tools\verify-fluent-icon-coverage.ps1' + +$nunitSkipReason = $null +$nunitResultsDirectory = Join-Path ([System.IO.Path]::GetTempPath()) ('FileManager-runtests-' + [Guid]::NewGuid().ToString('N')) +$nunitTrxName = 'runtests.trx' +$nunitAction = { + # Running the complete project discovers every fixture after this runner + # has supplied the executable and every prerequisite UI-test environment value. + New-Item -ItemType Directory -Path $nunitResultsDirectory | Out-Null + try { + & $dotnet.Source test $testProject --results-directory $nunitResultsDirectory ` + --logger "trx;LogFileName=$nunitTrxName" --logger 'console;verbosity=minimal' -- NUnit.NumberOfTestWorkers=0 + $dotnetExitCode = $LASTEXITCODE + if ($dotnetExitCode -ne 0) { + throw "The complete NUnit project failed with exit code $dotnetExitCode." + } + + # VSTest treats NUnit Assert.Ignore as success. The root runner owns + # UI prerequisites, so every ignored result is a configuration defect. + [xml]$trx = Get-Content -LiteralPath (Join-Path $nunitResultsDirectory $nunitTrxName) -Raw + $ignoredResults = @($trx.SelectNodes("//*[local-name()='UnitTestResult' and @outcome='NotExecuted']")) + if ($ignoredResults.Count -ne 0) { + $ignoredNames = $ignoredResults | Select-Object -First 10 | ForEach-Object { $_.testName } + $ignoredNames | ForEach-Object { Write-Host "Ignored NUnit test: $_" -ForegroundColor Yellow } + throw "$($ignoredResults.Count) NUnit tests were ignored; runtests.ps1 must configure every UI prerequisite." + } + } + finally { + if (Test-Path -LiteralPath $nunitResultsDirectory) { + Remove-Item -LiteralPath $nunitResultsDirectory -Recurse -Force + } + } +}.GetNewClosure() +Invoke-AutomatedCheck -Name 'FileManager.UiTests (complete NUnit project)' -Action $nunitAction -SkipReason $nunitSkipReason + +$isolatedUi = [string]::Equals($env:FILEMANAGER_UI_ISOLATED, '1', [StringComparison]::Ordinal) +$uiExecutable = $env:FILEMANAGER_UI_EXE +$appVerifierPath = Find-ApplicationVerifier +$lockStressSkipReason = $null +if (-not $isolatedUi) { + $lockStressSkipReason = 'FILEMANAGER_UI_ISOLATED=1 is required for the Application Verifier lane.' +} +elseif ([string]::IsNullOrWhiteSpace($uiExecutable) -or -not (Test-Path -LiteralPath $uiExecutable -PathType Leaf)) { + $lockStressSkipReason = 'FILEMANAGER_UI_EXE must identify the built executable.' +} +elseif ([string]::IsNullOrWhiteSpace($appVerifierPath)) { + $lockStressSkipReason = 'Application Verifier was not found.' +} +$lockStressLogDirectory = Join-Path $repositoryRoot 'TestResults\application-verifier-logs' +$lockStressArguments = if ($null -eq $lockStressSkipReason) { + @( + '-ExecutablePath', $uiExecutable, + '-TestProject', $testProject, + '-AppVerifierPath', $appVerifierPath, + '-LogOutputDirectory', $lockStressLogDirectory + ) +} else { @() } +Invoke-WindowsPowerShellScript -RelativePath 'tools\run-lock-verifier-stress.ps1' ` + -ScriptArguments $lockStressArguments -SkipReason $lockStressSkipReason + +Write-Host "`n=== Automated test summary ===" -ForegroundColor Cyan +Write-Host "$($passed.Count) checks passed." -ForegroundColor Green +if ($skipped.Count -ne 0) { + Write-Host "$($skipped.Count) checks skipped:" -ForegroundColor Yellow + $skipped | ForEach-Object { Write-Host " - $_" -ForegroundColor Yellow } +} +if ($failures.Count -ne 0) { + Write-Host "$($failures.Count) checks failed:" -ForegroundColor Red + $failures | ForEach-Object { Write-Host " - $_" -ForegroundColor Red } + exit 1 +} +if ($FailOnSkipped -and $skipped.Count -ne 0) { + Write-Host 'Skipped checks are prohibited by -FailOnSkipped.' -ForegroundColor Red + exit 1 +} + +Write-Host 'All available automated checks passed.' -ForegroundColor Green +exit 0 diff --git a/src/app_entry.cpp b/src/app_entry.cpp index effcb99d..dbc65a66 100644 --- a/src/app_entry.cpp +++ b/src/app_entry.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "precomp.h" +#include "operation_journal.h" #include //#ifdef MSVC_RUNTIME_CHECKS #include @@ -51,6 +52,53 @@ extern "C" #pragma comment(lib, "UxTheme.lib") +typedef BOOL(WINAPI* FSetDefaultDllDirectories)(DWORD directoryFlags); +typedef PVOID(WINAPI* FAddDllDirectory)(PCWSTR newDirectory); + +// Restrict process-wide DLL resolution before any optional module is loaded. +// Windows 7 requires KB2533623 for these APIs; continuing without it would +// restore the unsafe current-directory and PATH search behavior. +static BOOL InitializeDllSearchPaths() +{ + HMODULE kernel32 = GetModuleHandle(TEXT("kernel32.dll")); + FSetDefaultDllDirectories setDefaultDllDirectories = + kernel32 == NULL ? NULL : (FSetDefaultDllDirectories)GetProcAddress(kernel32, "SetDefaultDllDirectories"); + FAddDllDirectory addDllDirectory = + kernel32 == NULL ? NULL : (FAddDllDirectory)GetProcAddress(kernel32, "AddDllDirectory"); + if (setDefaultDllDirectories == NULL || addDllDirectory == NULL) + { + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); + return FALSE; + } + + const DWORD defaultDirectoryFlags = LOAD_LIBRARY_SEARCH_APPLICATION_DIR | + LOAD_LIBRARY_SEARCH_SYSTEM32 | + LOAD_LIBRARY_SEARCH_USER_DIRS; + if (!setDefaultDllDirectories(defaultDirectoryFlags)) + return FALSE; + + WCHAR applicationPath[MAX_PATH]; + DWORD applicationPathLength = GetModuleFileNameW(NULL, applicationPath, _countof(applicationPath)); + if (applicationPathLength == 0 || applicationPathLength >= _countof(applicationPath)) + { + if (applicationPathLength >= _countof(applicationPath)) + SetLastError(ERROR_INSUFFICIENT_BUFFER); + return FALSE; + } + + WCHAR* fileName = wcsrchr(applicationPath, L'\\'); + if (fileName == NULL) + { + SetLastError(ERROR_BAD_PATHNAME); + return FALSE; + } + *fileName = L'\0'; + + // This is the sole process-wide user directory. Plug-ins use their own + // canonical DLL path and LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR instead. + return addDllDirectory(applicationPath) != NULL; +} + // expose the original entry point of the application extern "C" int WinMainCRTStartup(); @@ -1566,6 +1614,23 @@ int GetIconSizeForSystemDPI(CIconSizeEnum iconSize) return (baseIconSize[iconSize] * scale) / 100; } +BOOL IsValidToolbarIconSize(int iconSize) +{ + // Reject corrupted or future registry values instead of creating an unexpectedly large image list. + return iconSize == TOOLBAR_ICON_SIZE_SMALL || + iconSize == TOOLBAR_ICON_SIZE_MEDIUM || + iconSize == TOOLBAR_ICON_SIZE_LARGE; +} + +int GetToolbarIconSizeForSystemDPI() +{ + // Scale the persisted logical size with the same system-DPI policy used by existing icons. + int logicalSize = IsValidToolbarIconSize(Configuration.ToolbarIconSize) ? + Configuration.ToolbarIconSize : + TOOLBAR_ICON_SIZE_SMALL; + return (logicalSize * GetScaleForSystemDPI()) / 100; +} + void GetSystemDPI(HDC hDC) { HDC hTmpDC; @@ -1727,17 +1792,21 @@ BOOL InitializeGraphics(BOOL colorsOnly) ImageList_SetImageCount(HFindSymbolsImageList, 2); // inicializace // ImageList_SetBkColor(HFindSymbolsImageList, GetSysColor(COLOR_WINDOW)); // aby pod XP chodily pruhledne ikonky - int iconSize = IconSizes[ICONSIZE_16]; + int menuIconSize = IconSizes[ICONSIZE_16]; + int toolbarIconSize = GetToolbarIconSizeForSystemDPI(); + int iconSize = menuIconSize; HBITMAP hTmpMaskBitmap; HBITMAP hTmpGrayBitmap; HBITMAP hTmpColorBitmap; + // Keep menu state glyphs in the same Fluent color language as command icons. + CSVGIcon menuMarkIcons[] = {{0, "MenuCheck"}, {1, "MenuRadio"}}; if (!CreateToolbarBitmaps(HInstance, IDB_MENU, RGB(255, 0, 255), GetSysColor(COLOR_BTNFACE), hTmpMaskBitmap, hTmpGrayBitmap, hTmpColorBitmap, - FALSE, NULL, 0)) + FALSE, menuMarkIcons, _countof(menuMarkIcons), menuIconSize)) return FALSE; - HMenuMarkImageList = ImageList_Create(iconSize, iconSize, ILC_MASK | ILC_COLORDDB, 2, 1); + HMenuMarkImageList = ImageList_Create(menuIconSize, menuIconSize, ILC_MASK | ILC_COLORDDB, 2, 1); ImageList_Add(HMenuMarkImageList, hTmpColorBitmap, hTmpMaskBitmap); HANDLES(DeleteObject(hTmpMaskBitmap)); HANDLES(DeleteObject(hTmpGrayBitmap)); @@ -1746,14 +1815,35 @@ BOOL InitializeGraphics(BOOL colorsOnly) CSVGIcon* svgIcons; int svgIconsCount; GetSVGIconsMainToolbar(&svgIcons, &svgIconsCount); + // Render a compact command set for menus before creating the independently sized toolbar set. + if (!CreateToolbarBitmaps(HInstance, + Use256ColorsBitmap() ? IDB_TOOLBAR_256 : IDB_TOOLBAR_16, + RGB(255, 0, 255), GetSysColor(COLOR_BTNFACE), + hTmpMaskBitmap, hTmpGrayBitmap, hTmpColorBitmap, + TRUE, svgIcons, svgIconsCount, menuIconSize)) + return FALSE; + HHotMenuImageList = ImageList_Create(menuIconSize, menuIconSize, ILC_MASK | ILC_COLORDDB, IDX_TB_COUNT, 1); + HGrayMenuImageList = ImageList_Create(menuIconSize, menuIconSize, ILC_MASK | ILC_COLORDDB, IDX_TB_COUNT, 1); + ImageList_Add(HHotMenuImageList, hTmpColorBitmap, hTmpMaskBitmap); + ImageList_Add(HGrayMenuImageList, hTmpGrayBitmap, hTmpMaskBitmap); + HANDLES(DeleteObject(hTmpMaskBitmap)); + HANDLES(DeleteObject(hTmpGrayBitmap)); + HANDLES(DeleteObject(hTmpColorBitmap)); + + if (HHotMenuImageList == NULL || HGrayMenuImageList == NULL) + { + TRACE_E("Unable to create image list."); + return FALSE; + } + if (!CreateToolbarBitmaps(HInstance, Use256ColorsBitmap() ? IDB_TOOLBAR_256 : IDB_TOOLBAR_16, RGB(255, 0, 255), GetSysColor(COLOR_BTNFACE), hTmpMaskBitmap, hTmpGrayBitmap, hTmpColorBitmap, - TRUE, svgIcons, svgIconsCount)) + TRUE, svgIcons, svgIconsCount, toolbarIconSize)) return FALSE; - HHotToolBarImageList = ImageList_Create(iconSize, iconSize, ILC_MASK | ILC_COLORDDB, IDX_TB_COUNT, 1); - HGrayToolBarImageList = ImageList_Create(iconSize, iconSize, ILC_MASK | ILC_COLORDDB, IDX_TB_COUNT, 1); + HHotToolBarImageList = ImageList_Create(toolbarIconSize, toolbarIconSize, ILC_MASK | ILC_COLORDDB, IDX_TB_COUNT, 1); + HGrayToolBarImageList = ImageList_Create(toolbarIconSize, toolbarIconSize, ILC_MASK | ILC_COLORDDB, IDX_TB_COUNT, 1); ImageList_Add(HHotToolBarImageList, hTmpColorBitmap, hTmpMaskBitmap); ImageList_Add(HGrayToolBarImageList, hTmpGrayBitmap, hTmpMaskBitmap); HANDLES(DeleteObject(hTmpMaskBitmap)); @@ -1866,6 +1956,9 @@ BOOL InitializeGraphics(BOOL colorsOnly) ImageList_SetBkColor(HHotToolBarImageList, GetSysColor(COLOR_BTNFACE)); ImageList_SetBkColor(HGrayToolBarImageList, GetSysColor(COLOR_BTNFACE)); + // Menu image lists use the same background while retaining their compact dimensions. + ImageList_SetBkColor(HHotMenuImageList, GetSysColor(COLOR_BTNFACE)); + ImageList_SetBkColor(HGrayMenuImageList, GetSysColor(COLOR_BTNFACE)); if (SystemParametersInfo(SPI_GETMOUSEHOVERTIME, 0, &MouseHoverTime, FALSE) == 0) { @@ -2145,6 +2238,17 @@ void ReleaseGraphics(BOOL colorsOnly) ImageList_Destroy(HGrayToolBarImageList); HGrayToolBarImageList = NULL; } + // Menu lists are owned by the graphics lifecycle alongside toolbar lists. + if (HHotMenuImageList != NULL) + { + ImageList_Destroy(HHotMenuImageList); + HHotMenuImageList = NULL; + } + if (HGrayMenuImageList != NULL) + { + ImageList_Destroy(HGrayMenuImageList); + HGrayMenuImageList = NULL; + } if (HBottomTBImageList != NULL) { ImageList_Destroy(HBottomTBImageList); @@ -2971,6 +3075,17 @@ int WinMainBody(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPSTR cmdLine, //--- nechci zadne kriticke chyby jako "no disk in drive A:" SetErrorMode(SetErrorMode(0) | SEM_FAILCRITICALERRORS); + if (!InitializeDllSearchPaths()) + { + char errorText[300]; + _snprintf_s(errorText, _TRUNCATE, + "Open Salamander cannot enable secure DLL loading (error %lu).\n" + "Install the Windows security update required for SetDefaultDllDirectories and try again.", + GetLastError()); + MessageBox(NULL, errorText, SALAMANDER_TEXT_VERSION, MB_OK | MB_ICONERROR); + return myExitCode; + } + // seed generatoru nahodnych cisel srand((unsigned)time(NULL) ^ (unsigned)_getpid()); @@ -3265,7 +3380,15 @@ int WinMainBody(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPSTR cmdLine, char path[MAX_PATH]; char errorText[MAX_PATH + 200]; GetModuleFileName(NULL, path, MAX_PATH); - sprintf(strrchr(path, '\\') + 1, "lang\\%s", Configuration.SLGName); + char* languageFileName = strrchr(path, '\\'); + if (languageFileName == NULL || + FormatStringChecked(languageFileName + 1, _countof(path) - (languageFileName + 1 - path), + "lang\\%s", Configuration.SLGName) != bsrSuccess) + { + MessageBox(NULL, "The selected language-file path is too long or invalid.", + SALAMANDER_TEXT_VERSION, MB_OK | MB_ICONERROR); + goto EXIT_1a; + } HLanguage = HANDLES(LoadLibraryUtf8(path)); LanguageID = 0; if (HLanguage == NULL || !IsSLGFileValid(HInstance, HLanguage, LanguageID, IsSLGIncomplete)) @@ -3274,18 +3397,22 @@ int WinMainBody(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPSTR cmdLine, HANDLES(FreeLibrary(HLanguage)); if (!newSLGFile) // remembered .SLG file probably stopped existing, try to find another one { - sprintf(errorText, "File %s was not found or is not valid language file.\nOpen Salamander " - "will try to search for some other language file (.SLG).", - path); + if (FormatStringChecked(errorText, _countof(errorText), + "File %s was not found or is not valid language file.\nOpen Salamander " + "will try to search for some other language file (.SLG).", + path) != bsrSuccess) + CopyStringChecked(errorText, _countof(errorText), "The selected language file is invalid."); MessageBox(NULL, errorText, SALAMANDER_TEXT_VERSION, MB_OK | MB_ICONERROR); Configuration.SLGName[0] = 0; goto FIND_NEW_SLG_FILE; } else // should never happen - .SLG file was already tested { - sprintf(errorText, "File %s was not found or is not valid language file.\n" - "Please run Open Salamander again and try to choose some other language file.", - path); + if (FormatStringChecked(errorText, _countof(errorText), + "File %s was not found or is not valid language file.\n" + "Please run Open Salamander again and try to choose some other language file.", + path) != bsrSuccess) + CopyStringChecked(errorText, _countof(errorText), "The selected language file is invalid."); MessageBox(NULL, errorText, "Open Salamander", MB_OK | MB_ICONERROR); goto EXIT_1a; } @@ -3296,10 +3423,6 @@ int WinMainBody(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPSTR cmdLine, // nechame jiz bezici salmon nacist zvolene SLG (zatim pouzival nejake provizorni) SalmonSetSLG(Configuration.SLGName); - // nastavime lokalizovane hlasky do modulu ALLOCHAN (zajistuje pri nedostatku pameti hlaseni uzivateli + Retry button + kdyz vse selze tak i Cancel pro terminate softu) - SetAllocHandlerMessage(LoadStr(IDS_ALLOCHANDLER_MSG), SALAMANDER_TEXT_VERSION, - LoadStr(IDS_ALLOCHANDLER_WRNIGNORE), LoadStr(IDS_ALLOCHANDLER_WRNABORT)); - CCommandLineParams cmdLineParams; if (!ParseCommandLineParameters(cmdLine, &cmdLineParams)) { @@ -3421,6 +3544,14 @@ int WinMainBody(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPSTR cmdLine, BOOL currentCfgDoesNotExist = autoImportConfig || SALAMANDER_ROOT_REG != SalamanderConfigurationRoots[0]; BOOL saveNewConfig = currentCfgDoesNotExist; + // Preserve the version root separately, then resolve it to its last committed generation. + // All existing configuration readers continue to use SALAMANDER_ROOT_REG. + SetConfigurationStoreRoot(SALAMANDER_ROOT_REG); + SelectCommittedConfigurationGeneration(); + const char* configurationDiagnostic = GetConfigurationSchemaDiagnostic(); + if (configurationDiagnostic != NULL) + MessageBox(NULL, configurationDiagnostic, "Open Salamander Configuration", MB_OK | MB_ICONWARNING); + // if the user does not want more instances, only activate the previous one if (!currentCfgDoesNotExist && CheckOnlyOneInstance(&cmdLineParams)) @@ -3631,6 +3762,9 @@ int WinMainBody(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPSTR cmdLine, { SetMessagesParent(MainWindow->HWindow); PluginMsgBoxParent = MainWindow->HWindow; + // Defer OOM recovery until the UI owns execution, so the failed + // allocation never runs journal or shutdown code under worker locks. + SetAllocEmergencyNotificationWindow(MainWindow->HWindow, WM_USER_ALLOCATION_EMERGENCY); // vytahneme z registry Group Policy IfExistSetSplashScreenText(LoadStr(IDS_STARTUP_POLICY)); @@ -3704,6 +3838,7 @@ int WinMainBody(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPSTR cmdLine, MainWindow->CanClose = TRUE; // ted teprve povolime zavreni hl. okna // aby soubory nevyskakovali postupne (jak se nacitaji jejich ikony) UpdateWindow(MainWindow->HWindow); + COperationJournal::OfferRecovery(MainWindow->HWindow); BOOL doNotDeleteImportedCfg = FALSE; if (autoImportConfig && // find out whether the new version has fewer plugins than the old one and part of old configuration would not transfer because of that @@ -3744,8 +3879,9 @@ int WinMainBody(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPSTR cmdLine, if (Configuration.ConfigVersion < 45) // zavedeni password manageru Plugins.LoadAll(MainWindow->HWindow); - // save uz pujde do nejnovejsiho klice - SALAMANDER_ROOT_REG = SalamanderConfigurationRoots[0]; + // Save into a new generation below the newest version root. + SetConfigurationStoreRoot(SalamanderConfigurationRoots[0]); + SelectCommittedConfigurationGeneration(); // save configuration immediately while it is a clean conversion of the old version -- user may // have "Save Cfg on Exit" disabled and if something changes during Salamander run, they do not want to save it at the end if (saveNewConfig) @@ -3808,6 +3944,8 @@ MENU_TEMPLATE_ITEM MsgBoxButtons[] = // let the process list know we are running and have the main window (we can be activated for OnlyOneInstance) TaskList.SetProcessState(PROCESS_STATE_RUNNING, MainWindow->HWindow); + // Publish readiness after first-run language and plug-in work so isolated UI commands cannot race startup. + FileManagerUiStartupReady = TRUE; // pozadame Salmon o kontrolu, zda na disku nejsou stare bug reporty, ktere by bylo potreba odeslat SalmonCheckBugs(); @@ -4137,8 +4275,9 @@ MENU_TEMPLATE_ITEM MsgBoxButtons[] = //--- DestroySafeWaitWindow(TRUE); // povel "terminate" safe-wait-message threadu Sleep(1000); // nechame vsem threadum viewru cas, aby se ukoncili - NBWNetAC3Thread.Close(TRUE); // bezici thread nechame zabit (presun do AuxThreads), dalsi akce zablokujeme - TerminateAuxThreads(); // zbytek nasilne terminujeme + // Transfer the network-browser worker to the deadline-aware shutdown owner. + NBWNetAC3Thread.Close(TRUE); + ShutdownAuxThreads(); // retain shared state until legacy workers have joined //--- TerminateThread(); ReleaseFileNamesEnumForViewers(); diff --git a/src/app_globals.cpp b/src/app_globals.cpp index c7aa6325..567c1700 100644 --- a/src/app_globals.cpp +++ b/src/app_globals.cpp @@ -30,6 +30,8 @@ BOOL SalamanderBusy = TRUE; // je Salamander busy? DWORD LastSalamanderIdleTime = 0; // GetTickCount() z okamziku, kdy SalamanderBusy naposledy presel na TRUE +// UI tests need a deterministic boundary later than main-window creation and first-run plug-in loading. +BOOL FileManagerUiStartupReady = FALSE; int PasteLinkIsRunning = 0; // if greater than zero, Past Shortcuts command is currently running in one of the panels @@ -198,6 +200,9 @@ HBITMAP HHeaderSort = NULL; HIMAGELIST HFindSymbolsImageList = NULL; HIMAGELIST HMenuMarkImageList = NULL; +// Menu commands retain compact rows independently of the configurable toolbar size. +HIMAGELIST HGrayMenuImageList = NULL; +HIMAGELIST HHotMenuImageList = NULL; HIMAGELIST HGrayToolBarImageList = NULL; HIMAGELIST HHotToolBarImageList = NULL; HIMAGELIST HBottomTBImageList = NULL; diff --git a/src/async_copy.cpp b/src/async_copy.cpp index 236ee2b0..5e5595b6 100644 --- a/src/async_copy.cpp +++ b/src/async_copy.cpp @@ -5,11 +5,19 @@ #include "precomp.h" #include "cfgdlg.h" +#include "common/scoped_native_resources.h" +#include "file_operation_filesystem.h" +#include "operation_result.h" +#include "retry_policy.h" #include "worker.h" #include "execlog.h" +#include "release_diagnostics.h" #include #include +#include + +#pragma comment(lib, "bcrypt.lib") // CreateFileUtf8 / DeleteFileUtf8 / SetFileAttributesUtf8 / RemoveDirectoryUtf8 // are globally declared in common/strutils.h and defined in common/strutils.cpp. @@ -563,6 +571,149 @@ struct CSrcSecurity // helper structure for keeping security info for MoveFile ( } }; +// Security descriptors cannot be treated as a single best-effort SetNamedSecurityInfo +// call. Windows may accept one component and reject another (in particular when +// SeRestorePrivilege is absent), which can leave a copied file with a more open or +// less accessible DACL than either the source or the pre-copy target. Keep the +// comparison deliberately at the components covered by the metadata contract: +// owner, group, DACL protection/inheritance, and every explicit ACE (including +// explicit ACCESS_DENIED ACEs). +static BOOL AreEqualSids(PSID first, PSID second) +{ + return first == second || (first != NULL && second != NULL && EqualSid(first, second)); +} + +static BOOL GetDaclProtection(PSECURITY_DESCRIPTOR securityDescriptor, BOOL* protectedDacl) +{ + SECURITY_DESCRIPTOR_CONTROL control; + DWORD revision; + if (!GetSecurityDescriptorControl(securityDescriptor, &control, &revision)) + return FALSE; + *protectedDacl = (control & SE_DACL_PROTECTED) != 0; + return TRUE; +} + +static BOOL AreEqualExplicitAces(PACL first, PACL second) +{ + if (first == NULL || second == NULL) + return first == second; // a NULL DACL grants full access and must never be approximated + + ACL_SIZE_INFORMATION firstInfo; + ACL_SIZE_INFORMATION secondInfo; + if (!GetAclInformation(first, &firstInfo, sizeof(firstInfo), AclSizeInformation) || + !GetAclInformation(second, &secondInfo, sizeof(secondInfo), AclSizeInformation)) + { + return FALSE; + } + + // Compare each explicit ACE as a multiset. Inherited entries can legitimately + // differ after a copy into a different directory, but an explicit allow or deny + // entry is part of the source descriptor and must survive byte-for-byte. + for (DWORD firstIndex = 0; firstIndex < firstInfo.AceCount; firstIndex++) + { + PVOID firstAce = NULL; + if (!GetAce(first, firstIndex, &firstAce)) + return FALSE; + ACE_HEADER* firstHeader = (ACE_HEADER*)firstAce; + if ((firstHeader->AceFlags & INHERITED_ACE) != 0) + continue; + + DWORD firstCount = 0; + DWORD secondCount = 0; + for (DWORD index = 0; index < firstInfo.AceCount; index++) + { + PVOID ace = NULL; + if (!GetAce(first, index, &ace)) + return FALSE; + ACE_HEADER* header = (ACE_HEADER*)ace; + if ((header->AceFlags & INHERITED_ACE) == 0 && header->AceSize == firstHeader->AceSize && + memcmp(header, firstHeader, firstHeader->AceSize) == 0) + { + firstCount++; + } + } + for (DWORD index = 0; index < secondInfo.AceCount; index++) + { + PVOID ace = NULL; + if (!GetAce(second, index, &ace)) + return FALSE; + ACE_HEADER* header = (ACE_HEADER*)ace; + if ((header->AceFlags & INHERITED_ACE) == 0 && header->AceSize == firstHeader->AceSize && + memcmp(header, firstHeader, firstHeader->AceSize) == 0) + { + secondCount++; + } + } + if (firstCount != secondCount) + return FALSE; + } + + // The loop above detects missing source ACEs. Repeat in the other direction + // so a target's pre-existing explicit allow cannot make the output permissive. + for (DWORD secondIndex = 0; secondIndex < secondInfo.AceCount; secondIndex++) + { + PVOID secondAce = NULL; + if (!GetAce(second, secondIndex, &secondAce)) + return FALSE; + ACE_HEADER* secondHeader = (ACE_HEADER*)secondAce; + if ((secondHeader->AceFlags & INHERITED_ACE) != 0) + continue; + + DWORD firstCount = 0; + DWORD secondCount = 0; + for (DWORD index = 0; index < firstInfo.AceCount; index++) + { + PVOID ace = NULL; + if (!GetAce(first, index, &ace)) + return FALSE; + ACE_HEADER* header = (ACE_HEADER*)ace; + if ((header->AceFlags & INHERITED_ACE) == 0 && header->AceSize == secondHeader->AceSize && + memcmp(header, secondHeader, secondHeader->AceSize) == 0) + { + firstCount++; + } + } + for (DWORD index = 0; index < secondInfo.AceCount; index++) + { + PVOID ace = NULL; + if (!GetAce(second, index, &ace)) + return FALSE; + ACE_HEADER* header = (ACE_HEADER*)ace; + if ((header->AceFlags & INHERITED_ACE) == 0 && header->AceSize == secondHeader->AceSize && + memcmp(header, secondHeader, secondHeader->AceSize) == 0) + { + secondCount++; + } + } + if (firstCount != secondCount) + return FALSE; + } + return TRUE; +} + +static BOOL IsSecurityDescriptorPreserved(PSID sourceOwner, PSID sourceGroup, PACL sourceDacl, + PSECURITY_DESCRIPTOR sourceDescriptor, + PSID targetOwner, PSID targetGroup, PACL targetDacl, + PSECURITY_DESCRIPTOR targetDescriptor) +{ + BOOL sourceProtected; + BOOL targetProtected; + return AreEqualSids(sourceOwner, targetOwner) && + AreEqualSids(sourceGroup, targetGroup) && + GetDaclProtection(sourceDescriptor, &sourceProtected) && + GetDaclProtection(targetDescriptor, &targetProtected) && + sourceProtected == targetProtected && + AreEqualExplicitAces(sourceDacl, targetDacl); +} + +static DWORD SetDaclWithInheritance(const WCHAR* targetName, PACL dacl, BOOL inheritedDacl) +{ + return SetNamedSecurityInfoW((LPWSTR)targetName, SE_FILE_OBJECT, + DACL_SECURITY_INFORMATION | + (inheritedDacl ? UNPROTECTED_DACL_SECURITY_INFORMATION : PROTECTED_DACL_SECURITY_INFORMATION), + NULL, NULL, dacl, NULL); +} + BOOL DoCopySecurity(const char* sourceName, const char* targetName, DWORD* err, CSrcSecurity* srcSecurity) { // if the path ends with a space or dot, we must append '\\', otherwise @@ -602,146 +753,335 @@ BOOL DoCopySecurity(const char* sourceName, const char* targetName, DWORD* err, *err = ERROR_NO_UNICODE_TRANSLATION; } } - BOOL ret = *err == ERROR_SUCCESS; + if (*err != ERROR_SUCCESS) + { + if (srcSD != NULL) + LocalFree(srcSD); + return FALSE; // inaccessible source descriptor: report a best-effort metadata loss without touching the target + } - if (ret) + CStrP targetNameSecW(ConvertAllocUtf8ToWide(targetNameSec, -1)); + if (targetNameSecW == NULL) { - SECURITY_DESCRIPTOR_CONTROL srcSDControl; - DWORD srcSDRevision; - if (!GetSecurityDescriptorControl(srcSD, &srcSDControl, &srcSDRevision)) + *err = ERROR_NO_UNICODE_TRANSLATION; + LocalFree(srcSD); + return FALSE; + } + + PSID previousOwner = NULL; + PSID previousGroup = NULL; + PACL previousDacl = NULL; + PSECURITY_DESCRIPTOR previousDescriptor = NULL; + *err = GetNamedSecurityInfoW(targetNameSecW, SE_FILE_OBJECT, + DACL_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION, + &previousOwner, &previousGroup, &previousDacl, NULL, &previousDescriptor); + if (*err != ERROR_SUCCESS) + { + LocalFree(srcSD); + return FALSE; // inaccessible target descriptor: do not attempt a blind, partial repair + } + + BOOL sourceProtectedDacl; + if (!GetDaclProtection(srcSD, &sourceProtectedDacl)) + { + *err = GetLastError(); + LocalFree(previousDescriptor); + LocalFree(srcSD); + return FALSE; + } + BOOL inheritedDacl = !sourceProtectedDacl; + if (IsSecurityDescriptorPreserved(srcOwner, srcGroup, srcDACL, srcSD, + previousOwner, previousGroup, previousDacl, previousDescriptor)) + { + LocalFree(previousDescriptor); + LocalFree(srcSD); + return TRUE; + } + + GainWriteOwnerAccess(); + BOOL changingOwnerOrGroup = !AreEqualSids(srcOwner, previousOwner) || !AreEqualSids(srcGroup, previousGroup); + DWORD setError; + BOOL attemptedWrite = FALSE; + if (HaveWriteOwnerRight) + { + // SeRestorePrivilege authorizes a complete descriptor update. It still + // needs post-write verification because SetNamedSecurityInfo may report a + // component-specific failure after accepting another component. + attemptedWrite = TRUE; + setError = SetNamedSecurityInfoW(targetNameSecW, SE_FILE_OBJECT, + DACL_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION | + (inheritedDacl ? UNPROTECTED_DACL_SECURITY_INFORMATION : PROTECTED_DACL_SECURITY_INFORMATION), + srcOwner, srcGroup, srcDACL, NULL); + } + else if (!changingOwnerOrGroup) + { + // Without restore privilege it is safe to repair only the DACL when the + // target already has the requested owner and group. Never take ownership + // temporarily: that was the source of partially copied descriptors. + attemptedWrite = TRUE; + setError = SetDaclWithInheritance(targetNameSecW, srcDACL, inheritedDacl); + } + else + { + setError = ERROR_PRIVILEGE_NOT_HELD; + } + + PSID appliedOwner = NULL; + PSID appliedGroup = NULL; + PACL appliedDacl = NULL; + PSECURITY_DESCRIPTOR appliedDescriptor = NULL; + BOOL appliedRead = GetNamedSecurityInfoW(targetNameSecW, SE_FILE_OBJECT, + DACL_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION, + &appliedOwner, &appliedGroup, &appliedDacl, NULL, &appliedDescriptor) == ERROR_SUCCESS; + BOOL preserved = setError == ERROR_SUCCESS && appliedRead && + IsSecurityDescriptorPreserved(srcOwner, srcGroup, srcDACL, srcSD, + appliedOwner, appliedGroup, appliedDacl, appliedDescriptor); + if (appliedDescriptor != NULL) + LocalFree(appliedDescriptor); + if (preserved) + { + LocalFree(previousDescriptor); + LocalFree(srcSD); + *err = ERROR_SUCCESS; + return TRUE; + } + + if (!attemptedWrite) + { + // Owner/group differ and no restore privilege was available. No target + // mutation was attempted, so surface the best-effort warning directly. + LocalFree(previousDescriptor); + LocalFree(srcSD); + *err = setError; + return FALSE; + } + + // A failed verification must not leave the output with a partial descriptor. + // Restore privilege lets us restore the complete snapshot; otherwise only the + // DACL was changed, so restoring that component cannot alter owner or group. + DWORD rollbackError; + if (HaveWriteOwnerRight) + { + BOOL previousProtectedDacl; + rollbackError = GetDaclProtection(previousDescriptor, &previousProtectedDacl) ? + SetNamedSecurityInfoW(targetNameSecW, SE_FILE_OBJECT, + DACL_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION | + (previousProtectedDacl ? PROTECTED_DACL_SECURITY_INFORMATION : UNPROTECTED_DACL_SECURITY_INFORMATION), + previousOwner, previousGroup, previousDacl, NULL) : + GetLastError(); + } + else + { + BOOL previousProtectedDacl; + rollbackError = GetDaclProtection(previousDescriptor, &previousProtectedDacl) ? + SetDaclWithInheritance(targetNameSecW, previousDacl, !previousProtectedDacl) : + GetLastError(); + } + if (rollbackError != ERROR_SUCCESS) + { + TRACE_E("DoCopySecurity(): unable to restore the target descriptor after a partial update: " << GetErrorText(rollbackError)); + *err = rollbackError; + } + else + { + *err = setError != ERROR_SUCCESS ? setError : ERROR_INVALID_SECURITY_DESCR; + } + LocalFree(previousDescriptor); + LocalFree(srcSD); + return FALSE; +} + +EMetadataTargetFileSystem GetMetadataTargetFileSystem(const char* targetPath) +{ + if (targetPath == NULL || targetPath[0] == 0) + return mtfsUnknown; + + CStrP targetPathW(ConvertAllocUtf8ToWide(targetPath, -1)); + WCHAR volumePath[MAX_PATH]; + WCHAR fileSystemName[64]; + if (targetPathW != NULL && + GetVolumePathNameW(targetPathW, volumePath, _countof(volumePath)) && + GetVolumeInformationW(volumePath, NULL, 0, NULL, NULL, NULL, + fileSystemName, _countof(fileSystemName))) + { + if (GetDriveTypeW(volumePath) == DRIVE_REMOTE) + return mtfsSmb; + if (_wcsicmp(fileSystemName, L"NTFS") == 0) + return mtfsNtfs; + if (_wcsicmp(fileSystemName, L"ReFS") == 0) + return mtfsRefs; + if (_wcsicmp(fileSystemName, L"FAT") == 0 || + _wcsicmp(fileSystemName, L"FAT32") == 0 || + _wcsicmp(fileSystemName, L"exFAT") == 0) { - *err = GetLastError(); - ret = FALSE; + return mtfsFat; } - else + } + + return StrNICmp(targetPath, "\\\\", 2) == 0 ? mtfsSmb : mtfsUnknown; +} + +CMetadataPreservationContract GetMetadataPreservationContract(EMetadataOperation operation, + EMetadataTargetFileSystem targetFileSystem) +{ + CMetadataPreservationContract contract; + contract.Content = mpRequired; + contract.LastWriteTime = mpBestEffort; + contract.CreationAndAccessTimes = mpUnsupported; + contract.Attributes = mpBestEffort; + contract.Security = mpBestEffort; + contract.AlternateDataStreams = mpBestEffort; + contract.CompressionAndEncryption = mpBestEffort; + + if (operation == moSameVolumeMove) + { + // A native rename keeps the same filesystem object; no copy-time + // translation is involved. SMB shares can still impose server-side + // rename semantics, so represent their non-content metadata as best + // effort rather than over-promising the local-filesystem guarantee. + if (targetFileSystem == mtfsSmb) { - CStrP targetNameSecW(ConvertAllocUtf8ToWide(targetNameSec, -1)); - if (targetNameSecW == NULL) - { - *err = ERROR_NO_UNICODE_TRANSLATION; - ret = FALSE; - } - else - { - BOOL inheritedDACL = /*(srcSDControl & SE_DACL_AUTO_INHERITED) != 0 &&*/ (srcSDControl & SE_DACL_PROTECTED) == 0; // SE_DACL_AUTO_INHERITED unfortunately is not always set (for example Total Commander clears it after moving a file, so we ignore it) - DWORD attr = GetFileAttributesUtf8(targetNameSec); - *err = SetNamedSecurityInfoW(targetNameSecW, SE_FILE_OBJECT, - DACL_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION | - (inheritedDACL ? UNPROTECTED_DACL_SECURITY_INFORMATION : PROTECTED_DACL_SECURITY_INFORMATION), - srcOwner, srcGroup, srcDACL, NULL); - ret = *err == ERROR_SUCCESS; - - if (!ret) - { - // if the owner and group cannot be changed (we do not have the rights in the directory - for example we only have "change" rights), - // check whether the owner and group are already set (that would not be an error) - PSID tgtOwner = NULL; - PSID tgtGroup = NULL; - PACL tgtDACL = NULL; - PSECURITY_DESCRIPTOR tgtSD = NULL; - BOOL tgtRead = GetNamedSecurityInfoW(targetNameSecW, SE_FILE_OBJECT, - DACL_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION, - &tgtOwner, &tgtGroup, &tgtDACL, NULL, &tgtSD) == ERROR_SUCCESS; - // if the owner of the target file is not the current user, try to set it ("take ownership") - only - // provided we have the right to write the owner so that we can write back the original owner afterwards - BOOL ownerOfFile = FALSE; - if (!tgtRead || // if the security info cannot be read from the target, the owner is most likely not the current user (the owner has unblocked read rights) - tgtOwner == NULL || // probably nonsense, the file must have some owner; if it happens, try to set the owner to the current user - CurrentProcessTokenUserValid && CurrentProcessTokenUser->User.Sid != NULL && - !EqualSid(tgtOwner, CurrentProcessTokenUser->User.Sid)) - { - if (HaveWriteOwnerRight && - CurrentProcessTokenUserValid && CurrentProcessTokenUser->User.Sid != NULL && - SetNamedSecurityInfoW(targetNameSecW, SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION, - CurrentProcessTokenUser->User.Sid, NULL, NULL, NULL) == ERROR_SUCCESS) - { // setting succeeded; we must retrieve 'tgtSD' again - ownerOfFile = TRUE; - if (tgtSD != NULL) - LocalFree(tgtSD); - tgtOwner = NULL; - tgtGroup = NULL; - tgtDACL = NULL; - tgtSD = NULL; - tgtRead = GetNamedSecurityInfoW(targetNameSecW, SE_FILE_OBJECT, - DACL_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION, - &tgtOwner, &tgtGroup, &tgtDACL, NULL, &tgtSD) == ERROR_SUCCESS; - } - } - else - { - ownerOfFile = tgtRead && tgtOwner != NULL && CurrentProcessTokenUserValid && - CurrentProcessTokenUser->User.Sid != NULL; - } - BOOL daclOK = FALSE; - BOOL ownerOK = FALSE; - BOOL groupOK = FALSE; - if (ownerOfFile && CurrentProcessTokenUserValid && CurrentProcessTokenUser->User.Sid != NULL) - { // we are the file owner -> the DACL can be written; try to allow owner/group/DACL write and set the required values - int allowChPermDACLSize = sizeof(ACL) + sizeof(ACCESS_ALLOWED_ACE) - sizeof(ACCESS_ALLOWED_ACE().SidStart) + - GetLengthSid(CurrentProcessTokenUser->User.Sid) + 200 /* +200 bytes is just paranoia */; - char buff3[500]; - PACL allowChPermDACL; - if (allowChPermDACLSize > 500) - allowChPermDACL = (PACL)malloc(allowChPermDACLSize); - else - allowChPermDACL = (PACL)buff3; - if (allowChPermDACL != NULL && InitializeAcl(allowChPermDACL, allowChPermDACLSize, ACL_REVISION) && - AddAccessAllowedAce(allowChPermDACL, ACL_REVISION, READ_CONTROL | WRITE_DAC | WRITE_OWNER, - CurrentProcessTokenUser->User.Sid) && - SetNamedSecurityInfoW(targetNameSecW, SE_FILE_OBJECT, - DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION, - NULL, NULL, allowChPermDACL, NULL) == ERROR_SUCCESS) - { - ownerOK = SetNamedSecurityInfoW(targetNameSecW, SE_FILE_OBJECT, - OWNER_SECURITY_INFORMATION, - srcOwner, NULL, NULL, NULL) == ERROR_SUCCESS; - groupOK = SetNamedSecurityInfoW(targetNameSecW, SE_FILE_OBJECT, - GROUP_SECURITY_INFORMATION, - NULL, srcGroup, NULL, NULL) == ERROR_SUCCESS; - daclOK = SetNamedSecurityInfoW(targetNameSecW, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION | (inheritedDACL ? UNPROTECTED_DACL_SECURITY_INFORMATION : PROTECTED_DACL_SECURITY_INFORMATION), - NULL, NULL, srcDACL, NULL) == ERROR_SUCCESS; - } - if (allowChPermDACL != (PACL)buff3 && allowChPermDACL != NULL) - free(allowChPermDACL); - } - if (!ownerOK && - (SetNamedSecurityInfoW(targetNameSecW, SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION, - srcOwner, NULL, NULL, NULL) == ERROR_SUCCESS || - tgtRead && (srcOwner == NULL && tgtOwner == NULL || // if the owner is already set, ignore a potential error while setting - srcOwner != NULL && tgtOwner != NULL && EqualSid(srcOwner, tgtOwner)))) - { - ownerOK = TRUE; - } - if (!groupOK && - (SetNamedSecurityInfoW(targetNameSecW, SE_FILE_OBJECT, GROUP_SECURITY_INFORMATION, - NULL, srcGroup, NULL, NULL) == ERROR_SUCCESS || - tgtRead && (srcGroup == NULL && tgtGroup == NULL || // if the group is already set, ignore a potential error while setting - srcGroup != NULL && tgtGroup != NULL && EqualSid(srcGroup, tgtGroup)))) - { - groupOK = TRUE; - } - if (!daclOK && // the DACL must be set last because it depends on the owner (CREATOR OWNER is replaced with the real owner, etc.) - SetNamedSecurityInfoW(targetNameSecW, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION | (inheritedDACL ? UNPROTECTED_DACL_SECURITY_INFORMATION : PROTECTED_DACL_SECURITY_INFORMATION), - NULL, NULL, srcDACL, NULL) == ERROR_SUCCESS) - { - daclOK = TRUE; - } - if (ownerOK && groupOK && daclOK) - { - ret = TRUE; // all three components are OK -> the whole thing is OK - *err = NO_ERROR; - } - if (tgtSD != NULL) - LocalFree(tgtSD); - } - if (attr != INVALID_FILE_ATTRIBUTES) - SetFileAttributesUtf8(targetNameSec, attr); - } + contract.CreationAndAccessTimes = mpBestEffort; + return contract; } + contract.LastWriteTime = mpRequired; + contract.CreationAndAccessTimes = mpRequired; + contract.Attributes = mpRequired; + contract.Security = mpRequired; + contract.AlternateDataStreams = mpRequired; + contract.CompressionAndEncryption = mpRequired; + return contract; } - if (srcSD != NULL) - LocalFree(srcSD); - return ret; + + if (targetFileSystem == mtfsFat) + { + contract.Security = mpUnsupported; + contract.AlternateDataStreams = mpUnsupported; + contract.CompressionAndEncryption = mpUnsupported; + } + else if (targetFileSystem == mtfsRefs) + { + // ReFS is ACL-capable but does not provide NTFS compression or EFS. + contract.CompressionAndEncryption = mpUnsupported; + } + return contract; +} + +static const char* GetMetadataTargetFileSystemName(EMetadataTargetFileSystem fileSystem) +{ + switch (fileSystem) + { + case mtfsNtfs: + return "NTFS"; + case mtfsRefs: + return "ReFS"; + case mtfsFat: + return "FAT/exFAT"; + case mtfsSmb: + return "SMB"; + default: + return "the target filesystem"; + } +} + +void RecordMetadataLoss(CProgressDlgData& dlgData, DWORD lossMask, + const char* sourceName, const char* targetName) +{ + if (lossMask == mmlNone) + return; + + dlgData.MetadataLosses.LossMask |= lossMask; + if (dlgData.MetadataLosses.TargetFileSystem == mtfsUnknown && targetName != NULL) + dlgData.MetadataLosses.TargetFileSystem = GetMetadataTargetFileSystem(targetName); + if (dlgData.MetadataLosses.FirstSourceName[0] == 0 && sourceName != NULL) + lstrcpyn(dlgData.MetadataLosses.FirstSourceName, sourceName, + _countof(dlgData.MetadataLosses.FirstSourceName)); + if (dlgData.MetadataLosses.FirstTargetName[0] == 0 && targetName != NULL) + lstrcpyn(dlgData.MetadataLosses.FirstTargetName, targetName, + _countof(dlgData.MetadataLosses.FirstTargetName)); +} + +void RecordPlannedMetadataLosses(CProgressDlgData& dlgData, const COperations* script, + const char* sourceName, const char* targetName) +{ + DWORD lossMask = script->PlannedMetadataLosses; + CMetadataPreservationContract contract = GetMetadataPreservationContract(moCrossVolumeMove, + script->TargetMetadataFileSystem); + if (contract.CreationAndAccessTimes == mpUnsupported) + lossMask |= mmlCreationAndAccessTimes; + if (script->CopySecurity && contract.Security == mpUnsupported) + lossMask |= mmlSecurity; + if (script->CopyAttrs && contract.CompressionAndEncryption == mpUnsupported) + lossMask |= mmlCompressionAndEncryption; + + if (dlgData.MetadataLosses.TargetFileSystem == mtfsUnknown) + dlgData.MetadataLosses.TargetFileSystem = script->TargetMetadataFileSystem; + RecordMetadataLoss(dlgData, lossMask, sourceName, targetName); +} + +static void AppendMetadataLossLine(char* text, int textSize, const char* lossName) +{ + int length = (int)strlen(text); + if (length >= textSize - 1) + return; + _snprintf_s(text + length, textSize - length, _TRUNCATE, "%s- %s", + length == 0 ? "" : "\r\n", lossName); +} + +BOOL ConfirmMetadataLossesBeforeSourceDeletion(HWND hProgressDlg, CProgressDlgData& dlgData, + const char* sourceName, const char* targetName) +{ + if (dlgData.KeepSourceAfterMetadataLoss) + return FALSE; + + DWORD unacknowledgedLosses = dlgData.MetadataLosses.LossMask & + ~dlgData.MetadataLosses.AcknowledgedLossMask; + if (unacknowledgedLosses == mmlNone) + return TRUE; + + if (dlgData.MetadataLosses.FirstSourceName[0] == 0 && sourceName != NULL) + lstrcpyn(dlgData.MetadataLosses.FirstSourceName, sourceName, + _countof(dlgData.MetadataLosses.FirstSourceName)); + if (dlgData.MetadataLosses.FirstTargetName[0] == 0 && targetName != NULL) + lstrcpyn(dlgData.MetadataLosses.FirstTargetName, targetName, + _countof(dlgData.MetadataLosses.FirstTargetName)); + + char lossDetails[768]; + lossDetails[0] = 0; + if (unacknowledgedLosses & mmlLastWriteTime) + AppendMetadataLossLine(lossDetails, _countof(lossDetails), LoadStr(IDS_METADATALOSS_LASTWRITETIME)); + if (unacknowledgedLosses & mmlCreationAndAccessTimes) + AppendMetadataLossLine(lossDetails, _countof(lossDetails), LoadStr(IDS_METADATALOSS_CREATIONANDACCESSTIMES)); + if (unacknowledgedLosses & mmlAttributes) + AppendMetadataLossLine(lossDetails, _countof(lossDetails), LoadStr(IDS_METADATALOSS_ATTRIBUTES)); + if (unacknowledgedLosses & mmlSecurity) + AppendMetadataLossLine(lossDetails, _countof(lossDetails), LoadStr(IDS_METADATALOSS_SECURITY)); + if (unacknowledgedLosses & mmlAlternateDataStreams) + AppendMetadataLossLine(lossDetails, _countof(lossDetails), LoadStr(IDS_METADATALOSS_ADS)); + if (unacknowledgedLosses & mmlCompressionAndEncryption) + AppendMetadataLossLine(lossDetails, _countof(lossDetails), LoadStr(IDS_METADATALOSS_COMPRESSIONANDENCRYPTION)); + + char text[3 * MAX_PATH + 1024]; + _snprintf_s(text, _countof(text), _TRUNCATE, LoadStr(IDS_METADATALOSS_BEFORESOURCEDELETE), + GetMetadataTargetFileSystemName(dlgData.MetadataLosses.TargetFileSystem), + dlgData.MetadataLosses.FirstSourceName[0] != 0 ? dlgData.MetadataLosses.FirstSourceName : sourceName, + lossDetails); + + int result = IDCANCEL; + char* data[2]; + data[0] = (char*)&result; + data[1] = text; + SendMessage(hProgressDlg, WM_USER_DIALOG, 13, (LPARAM)data); + if (result == IDYES) + { + dlgData.MetadataLosses.AcknowledgedLossMask |= unacknowledgedLosses; + return TRUE; + } + + // A decline turns the remaining move into a copy. This avoids later + // directory-delete errors after the user deliberately retained a child. + dlgData.KeepSourceAfterMetadataLoss = TRUE; + return FALSE; } DWORD CompressFile(char* fileName, DWORD attrs) @@ -1253,29 +1593,28 @@ void DoLongName(char* buf, const char* name, int bufSize) _snprintf_s(buf, bufSize, _TRUNCATE, "\\\\?\\%s", name); // standard path } -BOOL SalSetFilePointer(HANDLE file, const CQuadWord& offset) -{ - LONG lo = offset.LoDWord; - LONG hi = offset.HiDWord; - lo = SetFilePointer(file, lo, &hi, FILE_BEGIN); - return (lo != INVALID_SET_FILE_POINTER || GetLastError() == NO_ERROR) && - lo == (LONG)offset.LoDWord && hi == (LONG)offset.HiDWord; -} - #define RETRYCOPY_TAIL_MINSIZE (32 * 1024) // at least two blocks of this size are verified at the end of the file tested in CheckTailOfOutFile(); afterwards the block size grows up to ASYNC_COPY_BUF_SIZE (if reading is fast enough); NOTE: must be <= ASYNC_COPY_BUF_SIZE #define RETRYCOPY_TESTINGTIME 3000 // duration of the CheckTailOfOutFile() test in [ms] -void CheckTailOfOutFileShowErr(const char* txt) +void CheckTailOfOutFileShowErr(const char* txt, DWORD err = GetLastError()) { - DWORD err = GetLastError(); TRACE_I("CheckTailOfOutFile(): " << txt << " Error: " << GetErrorText(err)); } BOOL CheckTailOfOutFile(CAsyncCopyParams* asyncPar, HANDLE in, HANDLE out, const CQuadWord& offset, const CQuadWord& curInOffset, BOOL ignoreReadErrOnOut) { - char* bufIn = (char*)malloc(ASYNC_COPY_BUF_SIZE); - char* bufOut = (char*)malloc(ASYNC_COPY_BUF_SIZE); + CScopedHeapBuffer inputBuffer(malloc(ASYNC_COPY_BUF_SIZE)); + CScopedHeapBuffer outputBuffer(malloc(ASYNC_COPY_BUF_SIZE)); + char* bufIn = (char*)inputBuffer.Get(); + char* bufOut = (char*)outputBuffer.Get(); + if (bufIn == NULL || bufOut == NULL) + { + // Tail verification must fail cleanly when its temporary comparison + // buffers cannot be owned, rather than leaking one buffer or crashing. + TRACE_E("CheckTailOfOutFile(): unable to allocate verification buffers."); + return FALSE; + } DWORD startTime = GetTickCount(); DWORD rutineStartTime = startTime; @@ -1303,9 +1642,11 @@ BOOL CheckTailOfOutFile(CAsyncCopyParams* asyncPar, HANDLE in, HANDLE out, const #endif // WORKER_COPY_DEBUG_MSG if (asyncPar == NULL) { - if (SalSetFilePointer(in, start)) + CFileOffsetResult inSeek = SalSetFilePointerEx(in, start, FILE_BEGIN); + if (inSeek.Succeeded) { // set the 'start' offset in the input - if (SalSetFilePointer(out, start)) + CFileOffsetResult outSeek = SalSetFilePointerEx(out, start, FILE_BEGIN); + if (outSeek.Succeeded) { // set the 'start' offset in the output DWORD read; if (ReadFile(out, bufOut, size, &read, NULL) && read == size) @@ -1333,10 +1674,10 @@ BOOL CheckTailOfOutFile(CAsyncCopyParams* asyncPar, HANDLE in, HANDLE out, const } } else - CheckTailOfOutFileShowErr("Unable to set file pointer to start offset in OUT file."); + CheckTailOfOutFileShowErr("Unable to set file pointer to start offset in OUT file.", outSeek.Error); } else - CheckTailOfOutFileShowErr("Unable to set file pointer to start offset in IN file."); + CheckTailOfOutFileShowErr("Unable to set file pointer to start offset in IN file.", inSeek.Error); } else { @@ -1423,15 +1764,20 @@ BOOL CheckTailOfOutFile(CAsyncCopyParams* asyncPar, HANDLE in, HANDLE out, const if (ok && asyncPar == NULL) // reposition input/output to required offsets { - if (!SalSetFilePointer(in, curInOffset)) + CFileOffsetResult inSeek = SalSetFilePointerEx(in, curInOffset, FILE_BEGIN); + if (!inSeek.Succeeded) { - CheckTailOfOutFileShowErr("Unable to set file pointer back to current offset in IN file."); + CheckTailOfOutFileShowErr("Unable to set file pointer back to current offset in IN file.", inSeek.Error); ok = FALSE; } - if (ok && !SalSetFilePointer(out, offset)) + if (ok) { - CheckTailOfOutFileShowErr("Unable to set file pointer back to current offset in OUT file."); - ok = FALSE; + CFileOffsetResult outSeek = SalSetFilePointerEx(out, offset, FILE_BEGIN); + if (!outSeek.Succeeded) + { + CheckTailOfOutFileShowErr("Unable to set file pointer back to current offset in OUT file.", outSeek.Error); + ok = FALSE; + } } } #ifdef WORKER_COPY_DEBUG_MSG @@ -1442,8 +1788,6 @@ BOOL CheckTailOfOutFile(CAsyncCopyParams* asyncPar, HANDLE in, HANDLE out, const TRACE_I("CheckTailOfOutFile(): " << (offset.Value - lastOffset.Value) / 1024.0 << " KB tested in " << (GetTickCount() - rutineStartTime) / 1000.0 << " secs (clear read time: " << (GetTickCount() - startTime) / 1000.0 << " secs)."); } #endif // WORKER_COPY_DEBUG_MSG - free(bufIn); - free(bufOut); return ok; } @@ -1519,12 +1863,11 @@ BOOL DoCopyADS(HWND hProgressDlg, const char* sourceName, BOOL isDir, const char __hoCreateFile, in, GetLastError(), TRUE); if (in != INVALID_HANDLE_VALUE) { - CQuadWord fileSize; - fileSize.LoDWord = GetFileSize(in, &fileSize.HiDWord); - if (fileSize.LoDWord == INVALID_FILE_SIZE && GetLastError() != NO_ERROR) + CFileOffsetResult fileSizeResult = SalGetFileSizeEx(in); + CQuadWord fileSize = fileSizeResult.Value; + if (!fileSizeResult.Succeeded) { - DWORD err = GetLastError(); - TRACE_E("GetFileSize(some ADS of " << sourceName << "): unexpected error: " << GetErrorText(err)); + TRACE_E("SalGetFileSizeEx(some ADS of " << sourceName << "): unexpected error: " << GetErrorText(fileSizeResult.Error)); fileSize.SetUI64(0); } @@ -1549,32 +1892,39 @@ BOOL DoCopyADS(HWND hProgressDlg, const char* sourceName, BOOL isDir, const char { BOOL fatal = TRUE; BOOL ignoreErr = FALSE; - if (SalSetFilePointer(out, fileSize)) + DWORD allocationError = NO_ERROR; + CFileOffsetResult allocationSeek = SalSetFilePointerEx(out, fileSize, FILE_BEGIN); + if (allocationSeek.Succeeded) { if (SetEndOfFile(out)) { - if (SetFilePointer(out, 0, NULL, FILE_BEGIN) == 0) + CFileOffsetResult rewind = SalSetFilePointerEx(out, CQuadWord(0, 0), FILE_BEGIN); + if (rewind.Succeeded) { fatal = FALSE; wholeFileAllocated = TRUE; } + else + allocationError = rewind.Error; } else { - if (GetLastError() == ERROR_DISK_FULL) + allocationError = GetLastError(); + if (allocationError == ERROR_DISK_FULL) ignoreErr = TRUE; // low disk space } } + else + allocationError = allocationSeek.Error; if (fatal) { if (!ignoreErr) { - DWORD err = GetLastError(); - TRACE_E("DoCopyADS(): unable to allocate whole file size before copy operation, please report under what conditions this occurs! GetLastError(): " << GetErrorText(err)); + TRACE_E("DoCopyADS(): unable to allocate whole file size before copy operation, please report under what conditions this occurs! Error: " << GetErrorText(allocationError)); } // try truncating the file to zero so closing it does not trigger unnecessary writes - SetFilePointer(out, 0, NULL, FILE_BEGIN); + SalSetFilePointerEx(out, CQuadWord(0, 0), FILE_BEGIN); SetEndOfFile(out); HANDLES(CloseHandle(out)); @@ -1622,7 +1972,7 @@ BOOL DoCopyADS(HWND hProgressDlg, const char* sourceName, BOOL isDir, const char while (1) { - if (WriteFile(out, buffer, read, &written, NULL) && read == written) + if (OperationExecutionFileSystem().WriteFile(out, buffer, read, &written, NULL) && read == written) break; WRITE_ERROR_ADS: @@ -1671,10 +2021,9 @@ BOOL DoCopyADS(HWND hProgressDlg, const char* sourceName, BOOL isDir, const char __hoCreateFile, out, GetLastError(), TRUE); if (out != INVALID_HANDLE_VALUE) // opened successfully; now adjust the offset { - LONG lo, hi; - lo = GetFileSize(out, (DWORD*)&hi); - if (lo == INVALID_FILE_SIZE && GetLastError() != NO_ERROR || - CQuadWord(lo, hi) < operationDone || + CFileOffsetResult outputSize = SalGetFileSizeEx(out); + if (!outputSize.Succeeded || + outputSize.Value < operationDone || !CheckTailOfOutFile(NULL, in, out, operationDone, operationDone + CQuadWord(read, 0), FALSE)) { // cannot determine the size or the file is too small; restart the entire copy HANDLES(CloseHandle(in)); @@ -1777,10 +2126,9 @@ BOOL DoCopyADS(HWND hProgressDlg, const char* sourceName, BOOL isDir, const char __hoCreateFile, in, GetLastError(), TRUE); if (in != INVALID_HANDLE_VALUE) // opened successfully; now adjust the offset { - LONG lo, hi; - lo = GetFileSize(in, (DWORD*)&hi); - if (lo == INVALID_FILE_SIZE && GetLastError() != NO_ERROR || - CQuadWord(lo, hi) < operationDone || + CFileOffsetResult inputSize = SalGetFileSizeEx(in); + if (!inputSize.Succeeded || + inputSize.Value < operationDone || !CheckTailOfOutFile(NULL, in, out, operationDone, operationDone, TRUE)) { // cannot obtain size or the file is too small; restart the entire operation HANDLES(CloseHandle(in)); @@ -1897,6 +2245,7 @@ BOOL DoCopyADS(HWND hProgressDlg, const char* sourceName, BOOL isDir, const char { IGNORE_OPENOUTADS: + RecordMetadataLoss(dlgData, mmlAlternateDataStreams, sourceName, targetName); HANDLES(CloseHandle(in)); operDone += fileSize; lastTransferredFileSize += fileSize; @@ -2025,6 +2374,7 @@ BOOL DoCopyADS(HWND hProgressDlg, const char* sourceName, BOOL isDir, const char { IGNORE_ADS: + RecordMetadataLoss(dlgData, mmlAlternateDataStreams, sourceName, targetName); script->SetTFSandProgressSize(finalTransferredFileSize, totalDone + operTotal); SetProgress(hProgressDlg, 0, CaclProg(totalDone + operTotal, script->TotalSize), dlgData); @@ -2180,6 +2530,215 @@ HANDLE SalCreateFileEx(const char* fileName, DWORD desiredAccess, return out; } +// Reserve a unique file in the destination directory. The reservation is opened with +// CREATE_ALWAYS by DoCopyFile and is never visible under the requested target name. +// Keeping the temporary file beside its final name guarantees that ReplaceFileW is a +// same-volume commit. +static BOOL CreateTransactionalTargetFileName(const char* targetName, char* temporaryName, int temporaryNameLen) +{ + char targetDirectory[3 * MAX_PATH]; + lstrcpyn(targetDirectory, targetName, _countof(targetDirectory)); + if (!CutDirectory(targetDirectory)) + { + SetLastError(ERROR_INVALID_NAME); + return FALSE; + } + return SalGetTempFileName(targetDirectory, "SALCP", temporaryName, temporaryNameLen, TRUE); +} + +static HANDLE OpenTransactionalTargetFile(const char* temporaryName, DWORD desiredAccess, + DWORD flagsAndAttributes, BOOL* encryptionNotSupported) +{ + HANDLE out = OperationExecutionFileSystem().CreateFile(temporaryName, desiredAccess, 0, + CREATE_ALWAYS, flagsAndAttributes); + if (out != INVALID_HANDLE_VALUE) + HANDLES_ADD(__htFile, __hoCreateFile, out); + if (out == INVALID_HANDLE_VALUE && (flagsAndAttributes & FILE_ATTRIBUTE_ENCRYPTED)) + { + out = OperationExecutionFileSystem().CreateFile(temporaryName, desiredAccess, 0, + CREATE_ALWAYS, + flagsAndAttributes & ~(FILE_ATTRIBUTE_ENCRYPTED | FILE_ATTRIBUTE_READONLY)); + if (out != INVALID_HANDLE_VALUE) + { + HANDLES_ADD(__htFile, __hoCreateFile, out); + *encryptionNotSupported = TRUE; + HANDLES(CloseHandle(out)); + out = INVALID_HANDLE_VALUE; + } + } + return out; +} + +// ReplaceFileW preserves the previous destination until the file system commits the +// replacement. If another actor removed the destination after the overwrite prompt, +// a write-through same-volume rename is the equivalent commit for a newly absent file. +static COperationResult CommitTransactionalTargetFile(const char* targetName, const char* temporaryName, + const COperation::CFileIdentity& expectedTargetIdentity) +{ + // A name can be swapped while the temporary copy is being written. Reopen + // it without following reparse points and compare both its object ID and + // handle-resolved final path before ReplaceFileW can touch it. + DWORD error = ERROR_SUCCESS; + if (!VerifyFileIdentity(targetName, expectedTargetIdentity, &error)) + return COperationResult::Failure(orpVerifyDestinationIdentity, error, temporaryName, targetName, + IsRetryableOperationError(error), opeTemporaryTargetReady); + + if (OperationExecutionFileSystem().ReplaceFile(targetName, temporaryName)) + return COperationResult::Success(orpCommitTransactionalTarget, temporaryName, targetName, + opeTemporaryTargetReady | opeDestinationCommitted); + + error = GetLastError(); + if (error == ERROR_FILE_NOT_FOUND && + OperationExecutionFileSystem().MoveFile(temporaryName, targetName)) + { + return COperationResult::Success(orpCommitTransactionalTarget, temporaryName, targetName, + opeTemporaryTargetReady | opeDestinationCommitted); + } + if (error == ERROR_FILE_NOT_FOUND) + error = GetLastError(); + return COperationResult::Failure(orpCommitTransactionalTarget, error, temporaryName, targetName, + IsRetryableOperationError(error), opeTemporaryTargetReady); +} + +// A successful write is not a copy commit. Reopen the closed destination and +// verify its on-disk file metadata before reporting success, replacing an old +// target, or allowing a cross-volume move to remove its source. +static COperationResult VerifyDurableCopyCommit(const char* targetName, const CQuadWord& expectedSize) +{ + HANDLE target = HANDLES_Q(CreateFileUtf8(targetName, GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, + OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL)); + if (target == INVALID_HANDLE_VALUE) + { + DWORD error = GetLastError(); + return COperationResult::Failure(orpVerifyDurableCopy, error, NULL, targetName, + IsRetryableOperationError(error)); + } + + BY_HANDLE_FILE_INFORMATION information; + BOOL verified = GetFileInformationByHandle(target, &information); + DWORD error = ERROR_SUCCESS; + if (!verified) + error = GetLastError(); + else if ((information.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0 || + information.nFileSizeLow != expectedSize.LoDWord || + information.nFileSizeHigh != expectedSize.HiDWord) + { + error = ERROR_WRITE_FAULT; + verified = FALSE; + } + + // Capture the verification result before CloseHandle can replace GetLastError. + COperationResult result = verified ? COperationResult::Success(orpVerifyDurableCopy, NULL, targetName) : + COperationResult::Failure(orpVerifyDurableCopy, error, NULL, targetName, + IsRetryableOperationError(error)); + if (!HANDLES(CloseHandle(target))) + { + DWORD cleanupError = GetLastError(); + if (result.Succeeded()) + result = COperationResult::Failure(orpVerifyDurableCopy, cleanupError, NULL, targetName, + IsRetryableOperationError(cleanupError)); + else + result.AppendCleanupError(orcpCloseVerificationHandle, cleanupError, targetName); + } + return result; +} + +// A post-close size check establishes a durable destination, but it cannot +// distinguish two equally sized byte streams. Cross-volume moves retain the +// source until this comparison succeeds after a copy path has had suspicious +// I/O retries. +static BOOL CalculateFileSha256(const char* fileName, BYTE digest[32], DWORD* error) +{ + BCRYPT_ALG_HANDLE algorithm = NULL; + BCRYPT_HASH_HANDLE hash = NULL; + PUCHAR hashObject = NULL; + BYTE* buffer = NULL; + HANDLE file = INVALID_HANDLE_VALUE; + DWORD hashObjectLength = 0; + DWORD resultLength = 0; + BOOL calculated = FALSE; + + if (BCryptOpenAlgorithmProvider(&algorithm, BCRYPT_SHA256_ALGORITHM, NULL, 0) != 0 || + BCryptGetProperty(algorithm, BCRYPT_OBJECT_LENGTH, (PUCHAR)&hashObjectLength, + sizeof(hashObjectLength), &resultLength, 0) != 0 || + (hashObject = (PUCHAR)malloc(hashObjectLength)) == NULL || + BCryptCreateHash(algorithm, &hash, hashObject, hashObjectLength, NULL, 0, 0) != 0) + { + *error = ERROR_NOT_SUPPORTED; + goto CLEANUP; + } + + if ((file = HANDLES_Q(CreateFileUtf8(fileName, GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL))) == INVALID_HANDLE_VALUE) + { + *error = GetLastError(); + goto CLEANUP; + } + if ((buffer = (BYTE*)malloc(64 * 1024)) == NULL) + { + *error = ERROR_NOT_ENOUGH_MEMORY; + goto CLEANUP; + } + + while (1) + { + DWORD bytesRead; + if (!ReadFile(file, buffer, 64 * 1024, &bytesRead, NULL)) + { + *error = GetLastError(); + goto CLEANUP; + } + if (bytesRead == 0) + break; + if (BCryptHashData(hash, buffer, bytesRead, 0) != 0) + { + *error = ERROR_READ_FAULT; + goto CLEANUP; + } + } + if (BCryptFinishHash(hash, digest, 32, 0) != 0) + { + *error = ERROR_READ_FAULT; + goto CLEANUP; + } + calculated = TRUE; + +CLEANUP: + if (file != INVALID_HANDLE_VALUE && !HANDLES(CloseHandle(file)) && calculated) + { + *error = GetLastError(); + calculated = FALSE; + } + if (buffer != NULL) + free(buffer); + if (hash != NULL) + BCryptDestroyHash(hash); + if (hashObject != NULL) + free(hashObject); + if (algorithm != NULL) + BCryptCloseAlgorithmProvider(algorithm, 0); + return calculated; +} + +static BOOL VerifyFullFileContentSha256(const char* sourceName, const char* targetName, DWORD* error) +{ + BYTE sourceDigest[32]; + BYTE targetDigest[32]; + if (!CalculateFileSha256(sourceName, sourceDigest, error) || + !CalculateFileSha256(targetName, targetDigest, error)) + { + return FALSE; + } + if (memcmp(sourceDigest, targetDigest, sizeof(sourceDigest)) != 0) + { + *error = ERROR_CRC; + return FALSE; + } + return TRUE; +} + BOOL SyncOrAsyncDeviceIoControl(CAsyncCopyParams* asyncPar, HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize, LPDWORD lpBytesReturned, DWORD* err) @@ -2350,7 +2909,7 @@ void DoCopyFileLoopOrig(HANDLE& in, HANDLE& out, void* buffer, int& limitBufferS while (1) { - if (WriteFile(out, buffer, read, &written, NULL) && + if (OperationExecutionFileSystem().WriteFile(out, buffer, read, &written, NULL) && read == written) { break; @@ -2398,12 +2957,11 @@ void DoCopyFileLoopOrig(HANDLE& in, HANDLE& out, void* buffer, int& limitBufferS OPEN_ALWAYS, FILE_FLAG_SEQUENTIAL_SCAN, NULL)); if (out != INVALID_HANDLE_VALUE) // opened successfully; now adjust the offset { - LONG lo, hi; - lo = GetFileSize(out, (DWORD*)&hi); - if (lo == INVALID_FILE_SIZE && GetLastError() != NO_ERROR || // cannot obtain the size - CQuadWord(lo, hi) < operationDone || // file is too small - wholeFileAllocated && CQuadWord(lo, hi) > fileSize && - CQuadWord(lo, hi) > operationDone + CQuadWord(read, 0) || // pre-allocated file is too large (beyond the reserved size and beyond the written portion including the current block) = extra bytes were appended (allocWholeFileOnStart should be 0 /* need-test */) + CFileOffsetResult outputSize = SalGetFileSizeEx(out); + if (!outputSize.Succeeded || // cannot obtain the size + outputSize.Value < operationDone || // file is too small + wholeFileAllocated && outputSize.Value > fileSize && + outputSize.Value > operationDone + CQuadWord(read, 0) || // pre-allocated file is too large (beyond the reserved size and beyond the written portion including the current block) = extra bytes were appended (allocWholeFileOnStart should be 0 /* need-test */) !CheckTailOfOutFile(NULL, in, out, operationDone, operationDone + CQuadWord(read, 0), FALSE)) { // restart the whole operation HANDLES(CloseHandle(in)); @@ -2477,9 +3035,18 @@ void DoCopyFileLoopOrig(HANDLE& in, HANDLE& out, void* buffer, int& limitBufferS return; } - if (err == ERROR_NETNAME_DELETED && ++autoRetryAttemptsSNAP <= 3) - { // on SNAP server reading sometimes randomly fails with ERROR_NETNAME_DELETED; Retry button reportedly helps, so trigger it automatically - Sleep(100); + DWORD retryDelay; + if (PrepareAutomaticRetry(err, &autoRetryAttemptsSNAP, rokReadOnly, + script->GetCancellationEvent(), &retryDelay)) + { // Read retries have no commit side effect, so the central policy can pace them safely. + // Record the retry kind, but never the source path that caused it. + RecordReleaseDiagnosticRetry("copy_network_read"); + script->RecordItemRetry(); // automatic retries need the same correlation history as dialog retries + if (!WaitForAutomaticRetry(script->GetCancellationEvent(), retryDelay)) + { + copyError = TRUE; + return; + } goto RETRY_COPY; } @@ -2495,6 +3062,8 @@ void DoCopyFileLoopOrig(HANDLE& in, HANDLE& out, void* buffer, int& limitBufferS { case IDRETRY: { + // User-approved reports need the retry edge, not the file name or error text. + RecordReleaseDiagnosticRetry("copy_read"); RETRY_COPY: if (in != NULL) @@ -2504,10 +3073,9 @@ void DoCopyFileLoopOrig(HANDLE& in, HANDLE& out, void* buffer, int& limitBufferS OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL)); if (in != INVALID_HANDLE_VALUE) // opened successfully; now adjust the offset { - LONG lo, hi; - lo = GetFileSize(in, (DWORD*)&hi); - if (lo == INVALID_FILE_SIZE && GetLastError() != NO_ERROR || - CQuadWord(lo, hi) < operationDone || + CFileOffsetResult inputSize = SalGetFileSizeEx(in); + if (!inputSize.Succeeded || + inputSize.Value < operationDone || !CheckTailOfOutFile(NULL, in, out, operationDone, operationDone, TRUE)) { // cannot obtain the size or the file is too small; restart the whole operation HANDLES(CloseHandle(in)); @@ -2557,30 +3125,27 @@ void DoCopyFileLoopOrig(HANDLE& in, HANDLE& out, void* buffer, int& limitBufferS if (allocWholeFileOnStart == 0 /* need-test */) { - CQuadWord curFileSize; - curFileSize.LoDWord = GetFileSize(out, &curFileSize.HiDWord); - BOOL getFileSizeSuccess = (curFileSize.LoDWord != INVALID_FILE_SIZE || GetLastError() == NO_ERROR); - if (getFileSizeSuccess && curFileSize == operationDone) + CFileOffsetResult currentSize = SalGetFileSizeEx(out); + if (currentSize.Succeeded && currentSize.Value == operationDone) { // verify that no extra bytes were appended at the end and that truncation works allocWholeFileOnStart = 1 /* yes */; } else { #ifdef _DEBUG - if (getFileSizeSuccess) + if (currentSize.Succeeded) { char num1[50]; char num2[50]; TRACE_E("DoCopyFileLoopOrig(): unable to allocate whole file size before copy operation, please report " "under what conditions this occurs! Error: different file sizes: target=" - << NumberToStr(num1, curFileSize) << " bytes, source=" << NumberToStr(num2, operationDone) << " bytes"); + << NumberToStr(num1, currentSize.Value) << " bytes, source=" << NumberToStr(num2, operationDone) << " bytes"); } else { - DWORD err = GetLastError(); TRACE_E("DoCopyFileLoopOrig(): unable to test result of allocation of whole file size before copy operation, please report " - "under what conditions this occurs! GetFileSize(" - << op->TargetName << ") error: " << GetErrorText(err)); + "under what conditions this occurs! SalGetFileSizeEx(" + << op->TargetName << ") error: " << GetErrorText(currentSize.Error)); } #endif allocWholeFileOnStart = 2 /* no */; // skip further attempts on this target disk @@ -2798,7 +3363,7 @@ BOOL CCopy_Context::StartWriting(int blkIndex, DWORD* err) TRACE_I(sss); #endif // ASYNC_COPY_DEBUG_MSG - if (!WriteFile(*Out, AsyncPar->Buffers[blkIndex], BlockDataLen[blkIndex], NULL, + if (!OperationExecutionFileSystem().WriteFile(*Out, AsyncPar->Buffers[blkIndex], BlockDataLen[blkIndex], NULL, AsyncPar->InitOverlappedWithOffset(blkIndex, WriteOffset)) && GetLastError() != ERROR_IO_PENDING) { // a write error occurred; handle it @@ -2871,15 +3436,15 @@ void CCopy_Context::DiscardBlocksBehindEOF(const CQuadWord& fileSize, int exclud void CCopy_Context::GetNewFileSize(const char* fileName, HANDLE file, CQuadWord* fileSize, const CQuadWord& minFileSize) { - fileSize->LoDWord = GetFileSize(file, &fileSize->HiDWord); - if (fileSize->LoDWord == INVALID_FILE_SIZE && GetLastError() != NO_ERROR) + CFileOffsetResult currentSize = SalGetFileSizeEx(file); + if (!currentSize.Succeeded) { - DWORD err = GetLastError(); - TRACE_E("CCopy_Context::GetNewFileSize(): GetFileSize(" << fileName << "): unexpected error: " << GetErrorText(err)); + TRACE_E("CCopy_Context::GetNewFileSize(): SalGetFileSizeEx(" << fileName << "): unexpected error: " << GetErrorText(currentSize.Error)); *fileSize = minFileSize; } else { + *fileSize = currentSize.Value; if (*fileSize < minFileSize) // if GetFileSize happened to return a shorter length than already read *fileSize = minFileSize; } @@ -2975,10 +3540,10 @@ void CCopy_Context::CancelOpPhase2(int errBlkIndex) // used to prevent fragmentation) if (*Out != NULL) // only if the target file was not closed meanwhile { - if (!SalSetFilePointer(*Out, WriteOffset)) + CFileOffsetResult seekResult = SalSetFilePointerEx(*Out, WriteOffset, FILE_BEGIN); + if (!seekResult.Succeeded) { - DWORD err = GetLastError(); - TRACE_E("CCopy_Context::CancelOpPhase2(): unable to set file pointer in OUT file, error: " << GetErrorText(err)); + TRACE_E("CCopy_Context::CancelOpPhase2(): unable to set file pointer in OUT file, error: " << GetErrorText(seekResult.Error)); } } } @@ -2992,9 +3557,8 @@ BOOL CCopy_Context::RetryCopyReadErr(DWORD* err, BOOL* copyAgain, BOOL* errAgain OPEN_EXISTING, AsyncPar->GetOverlappedFlag() | FILE_FLAG_SEQUENTIAL_SCAN, NULL)); if (*In != INVALID_HANDLE_VALUE) // opened successfully; now adjust the offset { - CQuadWord size; - size.LoDWord = GetFileSize(*In, (DWORD*)&size.HiDWord); - if ((size.LoDWord != INVALID_FILE_SIZE || GetLastError() == NO_ERROR) && size >= ReadOffset) + CFileOffsetResult inputSize = SalGetFileSizeEx(*In); + if (inputSize.Succeeded && inputSize.Value >= ReadOffset) { // size obtained and the file is large enough // if the source is on a network: disable local client-side in-memory caching // http://msdn.microsoft.com/en-us/library/ee210753%28v=vs.85%29.aspx @@ -3058,9 +3622,17 @@ BOOL CCopy_Context::HandleReadingErr(int blkIndex, DWORD err, BOOL* copyError, B } int ret = IDCANCEL; - if (err == ERROR_NETNAME_DELETED && ++AutoRetryAttemptsSNAP <= 3) - { // SNAP servers occasionally return ERROR_NETNAME_DELETED while reading; Retry button reportedly helps, so trigger it automatically - Sleep(100); + DWORD retryDelay; + if (PrepareAutomaticRetry(err, &AutoRetryAttemptsSNAP, rokReadOnly, + Script->GetCancellationEvent(), &retryDelay)) + { // Asynchronous reads use the same cancelable policy as synchronous reads. + Script->RecordItemRetry(); // record a new correlated attempt even without a UI prompt + if (!WaitForAutomaticRetry(Script->GetCancellationEvent(), retryDelay)) + { + CancelOpPhase2(blkIndex); + *copyError = TRUE; + return FALSE; + } ret = IDRETRY; } else @@ -3123,11 +3695,10 @@ BOOL CCopy_Context::RetryCopyWriteErr(DWORD* err, BOOL* copyAgain, BOOL* errAgai if (*Out != INVALID_HANDLE_VALUE) // opened successfully; now adjust the offset { BOOL ok = TRUE; - CQuadWord size; - size.LoDWord = GetFileSize(*Out, (DWORD*)&size.HiDWord); - if (size.LoDWord == INVALID_FILE_SIZE && GetLastError() != NO_ERROR || // cannot obtain the size - size < WriteOffset || // file is too small - WholeFileAllocated && size > allocFileSize && size > maxWriteOffset) // pre-allocated file is too large (greater than the pre-allocated size and the written portion including the current block) = extra bytes appended (allocWholeFileOnStart should be 0 /* need-test */) + CFileOffsetResult outputSize = SalGetFileSizeEx(*Out); + if (!outputSize.Succeeded || // cannot obtain the size + outputSize.Value < WriteOffset || // file is too small + WholeFileAllocated && outputSize.Value > allocFileSize && outputSize.Value > maxWriteOffset) // pre-allocated file is too large (greater than the pre-allocated size and the written portion including the current block) = extra bytes appended (allocWholeFileOnStart should be 0 /* need-test */) { // restart the entire thing ok = FALSE; } @@ -3511,15 +4082,14 @@ void DoCopyFileLoopAsync(CAsyncCopyParams* asyncPar, HANDLE& in, HANDLE& out, vo { while (1) { - CQuadWord off = ctx.WriteOffset; - off.LoDWord = SetFilePointer(out, off.LoDWord, (LONG*)&(off.HiDWord), FILE_BEGIN); - if (off.LoDWord == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR || - off != ctx.WriteOffset || + CFileOffsetResult seekResult = SalSetFilePointerEx(out, ctx.WriteOffset, FILE_BEGIN); + if (!seekResult.Succeeded || + seekResult.Value != ctx.WriteOffset || !SetEndOfFile(out)) { - DWORD err2 = GetLastError(); - if ((off.LoDWord != INVALID_SET_FILE_POINTER || err2 == NO_ERROR) && off != ctx.WriteOffset) - err2 = ERROR_INVALID_FUNCTION; // successful SetFilePointer, but off != ctx.WriteOffset: will probably never happen, included for completeness + DWORD err2 = !seekResult.Succeeded ? seekResult.Error : GetLastError(); + if (seekResult.Succeeded && seekResult.Value != ctx.WriteOffset) + err2 = ERROR_INVALID_FUNCTION; // successful seek, but it reached an unexpected offset if (!ctx.HandleWritingErr(-1, err2, ©Error, &skipCopy, ©Again, allocFileSize, CQuadWord(0, 0))) return; // cancel/skip(skip-all)/retry-complete // retry-resume @@ -3531,30 +4101,27 @@ void DoCopyFileLoopAsync(CAsyncCopyParams* asyncPar, HANDLE& in, HANDLE& out, vo if (allocWholeFileOnStart == 0 /* need-test */) { - CQuadWord curFileSize; - curFileSize.LoDWord = GetFileSize(out, &curFileSize.HiDWord); - BOOL getFileSizeSuccess = (curFileSize.LoDWord != INVALID_FILE_SIZE || GetLastError() == NO_ERROR); - if (getFileSizeSuccess && curFileSize == operationDone) + CFileOffsetResult currentSize = SalGetFileSizeEx(out); + if (currentSize.Succeeded && currentSize.Value == operationDone) { // verify that no extra bytes were appended to the end of the file + that we can truncate the file allocWholeFileOnStart = 1 /* yes */; } else { #ifdef _DEBUG - if (getFileSizeSuccess) + if (currentSize.Succeeded) { char num1[50]; char num2[50]; TRACE_E("DoCopyFileLoopAsync(): unable to allocate whole file size before copy operation, please report " "under what conditions this occurs! Error: different file sizes: target=" - << NumberToStr(num1, curFileSize) << " bytes, source=" << NumberToStr(num2, operationDone) << " bytes"); + << NumberToStr(num1, currentSize.Value) << " bytes, source=" << NumberToStr(num2, operationDone) << " bytes"); } else { - DWORD err2 = GetLastError(); TRACE_E("DoCopyFileLoopAsync(): unable to test result of allocation of whole file size before copy operation, please report " - "under what conditions this occurs! GetFileSize(" - << op->TargetName << ") error: " << GetErrorText(err2)); + "under what conditions this occurs! SalGetFileSizeEx(" + << op->TargetName << ") error: " << GetErrorText(currentSize.Error)); } #endif allocWholeFileOnStart = 2 /* no */; // skip further attempts on this target disk @@ -3587,8 +4154,40 @@ BOOL DoCopyFile(COperation* op, HWND hProgressDlg, void* buffer, DWORD clearReadonlyMask, BOOL* skip, BOOL lantasticCheck, int& mustDeleteFileBeforeOverwrite, int& allocWholeFileOnStart, CProgressDlgData& dlgData, BOOL copyADS, BOOL copyAsEncrypted, - BOOL isMove, CAsyncCopyParams*& asyncPar) + BOOL isMove, CAsyncCopyParams*& asyncPar, BOOL* suspiciousIoRetry) { + if (suspiciousIoRetry != NULL) + *suspiciousIoRetry = FALSE; + char* const requestedTargetName = op->TargetName; + char transactionalTargetName[3 * MAX_PATH]; + BOOL transactionalTarget = FALSE; + BOOL transactionalTargetCommitted = FALSE; + struct CRestoreRequestedTargetName + { + COperation* Operation; + char* RequestedTargetName; + CRestoreRequestedTargetName(COperation* operation, char* requestedTargetName) + : Operation(operation), RequestedTargetName(requestedTargetName) {} + ~CRestoreRequestedTargetName() { Operation->TargetName = RequestedTargetName; } + } RestoreRequestedTargetName(op, requestedTargetName); + struct CCleanupTransactionalTarget + { + char* TargetName; + BOOL* IsTransactionalTarget; + BOOL* IsCommitted; + CCleanupTransactionalTarget(char* targetName, BOOL* isTransactionalTarget, BOOL* isCommitted) + : TargetName(targetName), IsTransactionalTarget(isTransactionalTarget), IsCommitted(isCommitted) {} + ~CCleanupTransactionalTarget() + { + if (*IsTransactionalTarget && !*IsCommitted) + { + ClearReadOnlyAttr(TargetName); + if (!DeleteFileUtf8(TargetName)) + TRACE_I("DoCopyFile(): unable to remove uncommitted transactional target " << TargetName << ": " << GetErrorText(GetLastError())); + } + } + } CleanupTransactionalTarget(transactionalTargetName, &transactionalTarget, &transactionalTargetCommitted); + if (script->CopyAttrs && copyAsEncrypted) TRACE_E("DoCopyFile(): unexpected parameter value: copyAsEncrypted is TRUE when script->CopyAttrs is TRUE!"); @@ -3736,15 +4335,23 @@ BOOL DoCopyFile(COperation* op, HWND hProgressDlg, void* buffer, OPEN_TGT_FILE: BOOL encryptionNotSupported = FALSE; - DWORD fileAttrs = asyncPar->GetOverlappedFlag() | FILE_FLAG_SEQUENTIAL_SCAN | + DWORD fileAttrs = asyncPar->GetOverlappedFlag() | FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_WRITE_THROUGH | (!lossEncryptionAttr && copyAsEncrypted ? FILE_ATTRIBUTE_ENCRYPTED : 0) | (script->CopyAttrs ? (op->Attr & (FILE_ATTRIBUTE_COMPRESSED | (lossEncryptionAttr ? 0 : FILE_ATTRIBUTE_ENCRYPTED))) : 0); if (!invalidTgtName) { // GENERIC_READ for 'out' slows asynchronous copying from disk to network (measured 95 MB/s instead of 111 MB/s on Win7 x64 GLAN) - out = SalCreateFileEx(op->TargetName, GENERIC_WRITE | (script->CopyAttrs ? GENERIC_READ : 0), 0, fileAttrs, &encryptionNotSupported); + if (transactionalTarget) + out = OpenTransactionalTargetFile(op->TargetName, GENERIC_WRITE | (script->CopyAttrs ? GENERIC_READ : 0), fileAttrs, &encryptionNotSupported); + else + out = SalCreateFileEx(op->TargetName, GENERIC_WRITE | (script->CopyAttrs ? GENERIC_READ : 0), 0, fileAttrs, &encryptionNotSupported); if (!encryptionNotSupported && script->CopyAttrs && out == INVALID_HANDLE_VALUE) // in case read access to the directory is not allowed (we added it only for setting the Compressed attribute), try creating a write-only file - out = SalCreateFileEx(op->TargetName, GENERIC_WRITE, 0, fileAttrs, &encryptionNotSupported); + { + if (transactionalTarget) + out = OpenTransactionalTargetFile(op->TargetName, GENERIC_WRITE, fileAttrs, &encryptionNotSupported); + else + out = SalCreateFileEx(op->TargetName, GENERIC_WRITE, 0, fileAttrs, &encryptionNotSupported); + } if (out == INVALID_HANDLE_VALUE && encryptionNotSupported && dlgData.FileOutLossEncrAll && !lossEncryptionAttr) { // the user agreed to lose the Encrypted attribute for all problematic files, so make that happen here @@ -3789,6 +4396,7 @@ BOOL DoCopyFile(COperation* op, HWND hProgressDlg, void* buffer, case IDB_ALL: dlgData.FileOutLossEncrAll = TRUE; // the break; is intentionally missing here case IDYES: + RecordMetadataLoss(dlgData, mmlCompressionAndEncryption, op->SourceName, op->TargetName); lossEncryptionAttr = TRUE; break; @@ -3835,33 +4443,40 @@ BOOL DoCopyFile(COperation* op, HWND hProgressDlg, void* buffer, { BOOL fatal = TRUE; BOOL ignoreErr = FALSE; - if (SalSetFilePointer(out, fileSize)) + DWORD allocationError = NO_ERROR; + CFileOffsetResult allocationSeek = SalSetFilePointerEx(out, fileSize, FILE_BEGIN); + if (allocationSeek.Succeeded) { if (SetEndOfFile(out)) { - if (SetFilePointer(out, 0, NULL, FILE_BEGIN) == 0) + CFileOffsetResult rewind = SalSetFilePointerEx(out, CQuadWord(0, 0), FILE_BEGIN); + if (rewind.Succeeded) { fatal = FALSE; wholeFileAllocated = TRUE; } + else + allocationError = rewind.Error; } else { - if (GetLastError() == ERROR_DISK_FULL) + allocationError = GetLastError(); + if (allocationError == ERROR_DISK_FULL) ignoreErr = TRUE; // not enough space on the disk } } + else + allocationError = allocationSeek.Error; if (fatal) { if (!ignoreErr) { - DWORD err = GetLastError(); - TRACE_E("DoCopyFile(): unable to allocate whole file size before copy operation, please report under what conditions this occurs! GetLastError(): " << GetErrorText(err)); + TRACE_E("DoCopyFile(): unable to allocate whole file size before copy operation, please report under what conditions this occurs! Error: " << GetErrorText(allocationError)); allocWholeFileOnStart = 2 /* no */; // we will forego further attempts on this target disk } // try truncating the file to zero so closing it does not trigger any unnecessary writes - SetFilePointer(out, 0, NULL, FILE_BEGIN); + SalSetFilePointerEx(out, CQuadWord(0, 0), FILE_BEGIN); SetEndOfFile(out); HANDLES(CloseHandle(out)); @@ -3934,14 +4549,17 @@ BOOL DoCopyFile(COperation* op, HWND hProgressDlg, void* buffer, return TRUE; } if (copyAgain) + { + if (suspiciousIoRetry != NULL) + *suspiciousIoRetry = TRUE; goto COPY_AGAIN; + } if (lantasticCheck) { - CQuadWord inSize, outSize; - inSize.LoDWord = GetFileSize(in, &inSize.HiDWord); - outSize.LoDWord = GetFileSize(out, &outSize.HiDWord); - if (inSize != outSize) + CFileOffsetResult inSize = SalGetFileSizeEx(in); + CFileOffsetResult outSize = SalGetFileSizeEx(out); + if (!inSize.Succeeded || !outSize.Succeeded || inSize.Value != outSize.Value) { // Lantastic 7.0: everything seems fine, but the result is wrong WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... if (*dlgData.CancelWorker) @@ -3961,11 +4579,13 @@ BOOL DoCopyFile(COperation* op, HWND hProgressDlg, void* buffer, { case IDRETRY: { + if (suspiciousIoRetry != NULL) + *suspiciousIoRetry = TRUE; operationDone = CQuadWord(0, 0); script->SetTFSandProgressSize(lastTransferredFileSize, totalDone); SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); - SetFilePointer(in, 0, NULL, FILE_BEGIN); // read again - SetFilePointer(out, 0, NULL, FILE_BEGIN); // write again + SalSetFilePointerEx(in, CQuadWord(0, 0), FILE_BEGIN); // read again + SalSetFilePointerEx(out, CQuadWord(0, 0), FILE_BEGIN); // write again SetEndOfFile(out); // truncate the output file goto COPY; } @@ -4072,7 +4692,7 @@ BOOL DoCopyFile(COperation* op, HWND hProgressDlg, void* buffer, { BOOL ignoreSetFileTimeErr = FALSE; while (!ignoreSetFileTimeErr && - !SetFileTime(out, NULL /*&creation*/, NULL /*&lastAccess*/, &lastWrite)) + !OperationExecutionFileSystem().SetFileTime(out, NULL /*&creation*/, NULL /*&lastAccess*/, &lastWrite)) { DWORD err = GetLastError(); @@ -4105,6 +4725,7 @@ BOOL DoCopyFile(COperation* op, HWND hProgressDlg, void* buffer, { IGNORE_SETFILETIME: + RecordMetadataLoss(dlgData, mmlLastWriteTime, op->SourceName, op->TargetName); ignoreSetFileTimeErr = TRUE; break; } @@ -4119,10 +4740,19 @@ BOOL DoCopyFile(COperation* op, HWND hProgressDlg, void* buffer, } } } - if (!HANDLES(CloseHandle(out))) + DWORD closeOrFlushError = NO_ERROR; + // This is the durable copy commit boundary. It applies to new + // destinations too: DoMoveFile may delete the source only after + // DoCopyFile returns success from this boundary. + if (!OperationExecutionFileSystem().FlushFileBuffers(out)) + closeOrFlushError = GetLastError(); + if (!HANDLES(CloseHandle(out)) && closeOrFlushError == NO_ERROR) + closeOrFlushError = GetLastError(); + out = NULL; + if (closeOrFlushError != NO_ERROR) { out = NULL; - DWORD err = GetLastError(); + DWORD err = closeOrFlushError; WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... if (*dlgData.CancelWorker) goto COPY_ERROR; @@ -4141,6 +4771,8 @@ BOOL DoCopyFile(COperation* op, HWND hProgressDlg, void* buffer, { case IDRETRY: { + if (suspiciousIoRetry != NULL) + *suspiciousIoRetry = TRUE; if (DeleteFileUtf8(op->TargetName) == 0) { DWORD err2 = GetLastError(); @@ -4190,6 +4822,7 @@ BOOL DoCopyFile(COperation* op, HWND hProgressDlg, void* buffer, case IDB_IGNOREALL: dlgData.IgnoreAllSetAttrsErr = TRUE; // break is intentional here; nothing is missing case IDB_IGNORE: + RecordMetadataLoss(dlgData, mmlAttributes, op->SourceName, op->TargetName); break; case IDCANCEL: @@ -4231,6 +4864,7 @@ BOOL DoCopyFile(COperation* op, HWND hProgressDlg, void* buffer, case IDB_IGNOREALL: dlgData.IgnoreAllCopyPermErr = TRUE; // the break; is intentionally missing here case IDB_IGNORE: + RecordMetadataLoss(dlgData, mmlSecurity, op->SourceName, op->TargetName); break; case IDCANCEL: @@ -4239,6 +4873,109 @@ BOOL DoCopyFile(COperation* op, HWND hProgressDlg, void* buffer, } } + DWORD verificationError = NO_ERROR; + COperationResult verificationResult = VerifyDurableCopyCommit(op->TargetName, op->FileSize); + while (!verificationResult.ToLegacyBool(&verificationError)) + { + TRACE_I("DoCopyFile(): durable copy commit verification failed for " << op->TargetName << ": " << GetErrorText(verificationError)); + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + goto COPY_ERROR_2; + + if (dlgData.SkipAllFileWrite) + goto SKIP_COPY; + + int ret = IDCANCEL; + char diagnosticSummary[2 * 3 * MAX_PATH + 512]; + char diagnosticText[2 * 3 * MAX_PATH + 768]; + verificationResult.BuildDiagnosticSummary(diagnosticSummary, _countof(diagnosticSummary)); + _snprintf_s(diagnosticText, _countof(diagnosticText), _TRUNCATE, + "%s\r\n\r\nDiagnostic (copy with Ctrl+C):\r\n%s", + GetErrorText(verificationError), diagnosticSummary); + char* data[4]; + data[0] = (char*)&ret; + data[1] = LoadStr(IDS_ERRORWRITINGFILE); + data[2] = op->TargetName; + data[3] = diagnosticText; + SendMessage(hProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); + switch (ret) + { + case IDRETRY: + if (suspiciousIoRetry != NULL) + *suspiciousIoRetry = TRUE; + ClearReadOnlyAttr(op->TargetName); + if (DeleteFileUtf8(op->TargetName) == 0) + { + DWORD err = GetLastError(); + verificationResult.AppendCleanupError(orcpDeleteUnverifiedTarget, err, op->TargetName); + TRACE_E("DoCopyFile(): Unable to remove unverified copy target: " << op->TargetName << ", error: " << GetErrorText(err)); + } + goto COPY_AGAIN; + + case IDB_SKIPALL: + dlgData.SkipAllFileWrite = TRUE; + case IDB_SKIP: + goto SKIP_COPY; + + case IDCANCEL: + goto COPY_ERROR_2; + } + } + + if (transactionalTarget) + { + DWORD err = NO_ERROR; + if (!script->JournalMarkTemporaryReady()) + { + err = ERROR_WRITE_FAULT; + goto COPY_ERROR_2; + } + // The dialog keeps its legacy BOOL/error contract while the + // worker preserves the phase and temporary-target effect for migration. + COperationResult commitResult = CommitTransactionalTargetFile(requestedTargetName, op->TargetName, + op->TargetIdentity); + while (!commitResult.ToLegacyBool(&err)) + { + TRACE_I("DoCopyFile(): unable to commit transactional overwrite of " << requestedTargetName << ": " << GetErrorText(err)); + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... + if (*dlgData.CancelWorker) + goto COPY_ERROR_2; + + if (dlgData.SkipAllFileWrite) + goto SKIP_COPY; + + int ret = IDCANCEL; + char diagnosticSummary[2 * 3 * MAX_PATH + 512]; + char diagnosticText[2 * 3 * MAX_PATH + 768]; + commitResult.BuildDiagnosticSummary(diagnosticSummary, _countof(diagnosticSummary)); + _snprintf_s(diagnosticText, _countof(diagnosticText), _TRUNCATE, + "%s\r\n\r\nDiagnostic (copy with Ctrl+C):\r\n%s", + GetErrorText(err), diagnosticSummary); + char* data[4]; + data[0] = (char*)&ret; + data[1] = LoadStr(IDS_ERRORWRITINGFILE); + data[2] = requestedTargetName; + data[3] = diagnosticText; + SendMessage(hProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); + switch (ret) + { + case IDRETRY: + commitResult = CommitTransactionalTargetFile(requestedTargetName, op->TargetName, + op->TargetIdentity); + break; + + case IDB_SKIPALL: + dlgData.SkipAllFileWrite = TRUE; + case IDB_SKIP: + goto SKIP_COPY; + + case IDCANCEL: + goto COPY_ERROR_2; + } + } + transactionalTargetCommitted = TRUE; + } + totalDone += op->Size; script->SetProgressSize(totalDone); return TRUE; @@ -4267,6 +5004,7 @@ BOOL DoCopyFile(COperation* op, HWND hProgressDlg, void* buffer, case IDB_ALL: dlgData.FileOutLossEncrAll = TRUE; // the break; is intentionally missing here case IDYES: + RecordMetadataLoss(dlgData, mmlCompressionAndEncryption, op->SourceName, op->TargetName); lossEncryptionAttr = TRUE; break; @@ -4452,6 +5190,26 @@ BOOL DoCopyFile(COperation* op, HWND hProgressDlg, void* buffer, BOOL targetCannotOpenForWrite = FALSE; while (1) { + if (!transactionalTarget) + { + // The overwrite decision has been made. From this point on write only + // to a sibling reservation, so retry, low-space, cancellation, and + // metadata failures can remove that file without touching the old target. + if (!CreateTransactionalTargetFileName(requestedTargetName, transactionalTargetName, _countof(transactionalTargetName))) + { + err = GetLastError(); + goto NORMAL_ERROR; + } + if (!script->JournalSetTemporaryPath(transactionalTargetName)) + { + err = ERROR_WRITE_FAULT; + goto NORMAL_ERROR; + } + op->TargetName = transactionalTargetName; + transactionalTarget = TRUE; + goto OPEN_TGT_FILE; + } + if (targetCannotOpenForWrite || mustDeleteFileBeforeOverwrite == 1 /* yes */) { // the file must be deleted first BOOL chAttr = ClearReadOnlyAttr(op->TargetName, attr); @@ -4483,8 +5241,9 @@ BOOL DoCopyFile(COperation* op, HWND hProgressDlg, void* buffer, OPEN_EXISTING, 0, NULL)); if (out != INVALID_HANDLE_VALUE) { - origFileSize.LoDWord = GetFileSize(out, &origFileSize.HiDWord); - if (origFileSize.LoDWord == INVALID_FILE_SIZE && GetLastError() == NO_ERROR) + CFileOffsetResult originalSize = SalGetFileSizeEx(out); + origFileSize = originalSize.Value; + if (!originalSize.Succeeded) origFileSize.Set(0, 0); // error => set the size to zero and test it on another file HANDLES(CloseHandle(out)); } @@ -4550,8 +5309,9 @@ BOOL DoCopyFile(COperation* op, HWND hProgressDlg, void* buffer, continue; } CQuadWord newFileSize(0, 0); // file size after truncation - newFileSize.LoDWord = GetFileSize(out, &newFileSize.HiDWord); - if ((newFileSize.LoDWord != INVALID_FILE_SIZE || GetLastError() == NO_ERROR) && // we have the new size + CFileOffsetResult currentSize = SalGetFileSizeEx(out); + newFileSize = currentSize.Value; + if (currentSize.Succeeded && // we have the new size newFileSize == CQuadWord(0, 0)) // file really has 0 bytes { if (origFileSize != CQuadWord(0, 0)) // truncation can only be tested on a non-zero file @@ -4781,6 +5541,7 @@ BOOL DoMoveFile(COperation* op, HWND hProgressDlg, void* buffer, case IDB_IGNOREALL: dlgData.IgnoreAllCopyPermErr = TRUE; // the break; is intentionally missing here case IDB_IGNORE: + RecordMetadataLoss(dlgData, mmlSecurity, op->SourceName, op->TargetName); break; case IDCANCEL: @@ -4794,7 +5555,11 @@ BOOL DoMoveFile(COperation* op, HWND hProgressDlg, void* buffer, dirTimeModifiedIsValid = GetDirTime(sourceNameMvDir, &dirTimeModified); while (1) { - if (!invalidName && !*novellRenamePatch && SalMoveFile(sourceNameMvDir, targetNameMvDir)) + DWORD identityError = ERROR_SUCCESS; + BOOL identitiesMatch = !invalidName && + VerifyFileIdentity(sourceNameMvDir, op->SourceIdentity, &identityError) && + VerifyFileIdentity(targetNameMvDir, op->TargetIdentity, &identityError); + if (identitiesMatch && !*novellRenamePatch && SalMoveFile(sourceNameMvDir, targetNameMvDir)) { if (script->CopyAttrs && (op->Attr & FILE_ATTRIBUTE_ARCHIVE) == 0) // Archive attribute was not set, MoveFile turned it on, clear it again SetFileAttributesUtf8(targetNameMvDir, op->Attr); // leave without handling or retry, not important (it normally toggles chaotically) @@ -4829,6 +5594,7 @@ BOOL DoMoveFile(COperation* op, HWND hProgressDlg, void* buffer, case IDB_IGNOREALL: dlgData.IgnoreAllSetAttrsErr = TRUE; // the break; is intentionally missing here case IDB_IGNORE: + RecordMetadataLoss(dlgData, mmlAttributes, op->SourceName, op->TargetName); break; case IDCANCEL: @@ -4868,6 +5634,7 @@ BOOL DoMoveFile(COperation* op, HWND hProgressDlg, void* buffer, case IDB_IGNOREALL: dlgData.IgnoreAllCopyPermErr = TRUE; // the break; is intentionally missing here case IDB_IGNORE: + RecordMetadataLoss(dlgData, mmlSecurity, op->SourceName, op->TargetName); break; case IDCANCEL: @@ -4903,7 +5670,7 @@ BOOL DoMoveFile(COperation* op, HWND hProgressDlg, void* buffer, } else { - DWORD err = GetLastError(); + DWORD err = identitiesMatch ? GetLastError() : identityError; if (invalidName) err = ERROR_INVALID_NAME; // Novell patch - before calling MoveFile we need to drop the read-only attribute @@ -4911,7 +5678,9 @@ BOOL DoMoveFile(COperation* op, HWND hProgressDlg, void* buffer, { DWORD attr = SalGetFileAttributes(sourceNameMvDir); BOOL setAttr = ClearReadOnlyAttr(sourceNameMvDir, attr); - if (SalMoveFile(sourceNameMvDir, targetNameMvDir)) + if (VerifyFileIdentity(sourceNameMvDir, op->SourceIdentity, &err) && + VerifyFileIdentity(targetNameMvDir, op->TargetIdentity, &err) && + SalMoveFile(sourceNameMvDir, targetNameMvDir)) { if (!*novellRenamePatch) *novellRenamePatch = TRUE; // the next operations will go straight through here @@ -5163,14 +5932,14 @@ BOOL DoMoveFile(COperation* op, HWND hProgressDlg, void* buffer, } } - ClearReadOnlyAttr(op->TargetName, attr); // make sure it can be deleted ... while (1) { - if (DeleteFileUtf8(op->TargetName)) + DWORD identityError; + if (DeleteFileWithVerifiedIdentity(op->TargetName, op->TargetIdentity, &identityError)) break; else { - DWORD err2 = GetLastError(); + DWORD err2 = identityError; if (err2 == ERROR_FILE_NOT_FOUND) break; // if the user already deleted the file manually, everything is fine @@ -5226,9 +5995,13 @@ BOOL DoMoveFile(COperation* op, HWND hProgressDlg, void* buffer, if (dlgData.SkipAllMoveErrors) goto SKIP_MOVE_ERROR; - if (err == ERROR_SHARING_VIOLATION && ++autoRetryAttempts <= 2) - { // auto-retry added to handle move errors while directory icons are being read (SHGetFileInfo running in parallel with MoveFile) - Sleep(100); // wait a moment before the next attempt + DWORD retryDelay; + if (PrepareAutomaticRetry(err, &autoRetryAttempts, rokDestructiveCommit, + script->GetCancellationEvent(), &retryDelay)) + { // The policy currently rejects this branch: move commit idempotency is not proven. + script->RecordItemRetry(); + if (!WaitForAutomaticRetry(script->GetCancellationEvent(), retryDelay)) + return FALSE; } else { @@ -5274,20 +6047,58 @@ BOOL DoMoveFile(COperation* op, HWND hProgressDlg, void* buffer, } BOOL skip; + BOOL suspiciousIoRetry; + DWORD err = NO_ERROR; BOOL notError = DoCopyFile(op, hProgressDlg, buffer, script, totalDone, clearReadonlyMask, &skip, lantasticCheck, mustDeleteFileBeforeOverwrite, allocWholeFileOnStart, - dlgData, copyADS, copyAsEncrypted, TRUE, asyncPar); + dlgData, copyADS, copyAsEncrypted, TRUE, asyncPar, &suspiciousIoRetry); if (notError && !skip) // still need to clean up the file from the source { - ClearReadOnlyAttr(op->SourceName); // ensure it can be deleted - while (1) + RecordPlannedMetadataLosses(dlgData, script, op->SourceName, op->TargetName); + if (!ConfirmMetadataLossesBeforeSourceDeletion(hProgressDlg, dlgData, op->SourceName, op->TargetName)) + return TRUE; // target is complete; the user retained the move source + + // Retry-resume and retry-copy paths are evidence that the I/O path + // was unstable. Re-read both closed files and compare their full + // SHA-256 digests before this move is allowed to delete its source. + while (suspiciousIoRetry && !VerifyFullFileContentSha256(op->SourceName, op->TargetName, &err)) { - if (DeleteFileUtf8(op->SourceName)) - break; + TRACE_I("DoMoveFile(): full SHA-256 verification failed for " << op->TargetName << ": " << GetErrorText(err)); + WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); + if (*dlgData.CancelWorker) + return FALSE; + + if (dlgData.SkipAllDeleteErr) + return TRUE; // retain the source; the verified delete precondition was not met + + int ret = IDCANCEL; + char* data[4]; + data[0] = (char*)&ret; + data[1] = LoadStr(IDS_ERRORDELETINGFILE); + data[2] = op->SourceName; + data[3] = GetErrorText(err); + SendMessage(hProgressDlg, WM_USER_DIALOG, 0, (LPARAM)data); + switch (ret) { - DWORD err = GetLastError(); + case IDRETRY: + break; + + case IDB_SKIPALL: + dlgData.SkipAllDeleteErr = TRUE; + case IDB_SKIP: + return TRUE; // retain the source; the verified delete precondition was not met + + case IDCANCEL: + return FALSE; + } + } + while (1) + { + if (DeleteFileWithVerifiedIdentity(op->SourceName, op->SourceIdentity, &err)) + break; + { WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... if (*dlgData.CancelWorker) return FALSE; @@ -5320,9 +6131,10 @@ BOOL DoMoveFile(COperation* op, HWND hProgressDlg, void* buffer, } } -BOOL DoDeleteFile(HWND hProgressDlg, char* name, const CQuadWord& size, COperations* script, +BOOL DoDeleteFile(HWND hProgressDlg, COperation* operation, const CQuadWord& size, COperations* script, CQuadWord& totalDone, DWORD attr, CProgressDlgData& dlgData) { + char* name = operation->SourceName; // if the path ends with a space/dot it is invalid and we must not delete it, // DeleteFile would trim the spaces/dots and remove a different file BOOL invalidName = FileNameIsInvalid(name, TRUE); @@ -5367,8 +6179,6 @@ BOOL DoDeleteFile(HWND hProgressDlg, char* name, const CQuadWord& size, COperati } } } - ClearReadOnlyAttr(name, attr); // ensure it can be deleted - err = ERROR_SUCCESS; BOOL useRecycleBin; switch (dlgData.UseRecycleBin) @@ -5412,6 +6222,12 @@ BOOL DoDeleteFile(HWND hProgressDlg, char* name, const CQuadWord& size, COperati } if (useRecycleBin) { + // The shell accepts only a name, so it cannot consume our + // verified handle. Recheck immediately before handing that + // name to the shell and only then adjust its attributes. + if (!VerifyFileIdentity(name, operation->SourceIdentity, &err)) + goto DELETE_READY; + ClearReadOnlyAttr(name, attr); if (!PathContainsValidComponents((char*)name, FALSE)) { err = ERROR_INVALID_NAME; @@ -5454,9 +6270,9 @@ BOOL DoDeleteFile(HWND hProgressDlg, char* name, const CQuadWord& size, COperati } else { - if (DeleteFileUtf8(name) == 0) - err = GetLastError(); + DeleteFileWithVerifiedIdentity(name, operation->SourceIdentity, &err); } + DELETE_READY: } else { @@ -5712,6 +6528,7 @@ BOOL DoCopyDirTime(HWND hProgressDlg, const char* targetName, FILETIME* modified case IDB_IGNOREALL: dlgData.IgnoreAllCopyDirTimeErr = TRUE; // break intentionally omitted here case IDB_IGNORE: + RecordMetadataLoss(dlgData, mmlLastWriteTime, NULL, targetNameCrFile); break; case IDCANCEL: @@ -5827,6 +6644,7 @@ BOOL DoCreateDir(HWND hProgressDlg, char* name, DWORD attr, case IDB_ALL: dlgData.DirCrLossEncrAll = TRUE; // break intentionally omitted here case IDYES: + RecordMetadataLoss(dlgData, mmlCompressionAndEncryption, sourceDir, name); break; case IDB_SKIPALL: @@ -5892,6 +6710,7 @@ BOOL DoCreateDir(HWND hProgressDlg, char* name, DWORD attr, case IDB_IGNOREALL: dlgData.IgnoreAllSetAttrsErr = TRUE; // break intentionally omitted here case IDB_IGNORE: + RecordMetadataLoss(dlgData, mmlAttributes, sourceDir, name); break; case IDCANCEL: @@ -5933,6 +6752,7 @@ BOOL DoCreateDir(HWND hProgressDlg, char* name, DWORD attr, case IDB_IGNOREALL: dlgData.IgnoreAllCopyPermErr = TRUE; // break intentionally omitted here case IDB_IGNORE: + RecordMetadataLoss(dlgData, mmlSecurity, sourceDir, name); break; case IDCANCEL: @@ -6060,12 +6880,12 @@ BOOL DoCreateDir(HWND hProgressDlg, char* name, DWORD attr, } } -BOOL DoDeleteDir(HWND hProgressDlg, char* name, const CQuadWord& size, COperations* script, +BOOL DoDeleteDir(HWND hProgressDlg, COperation* operation, const CQuadWord& size, COperations* script, CQuadWord& totalDone, DWORD attr, BOOL dontUseRecycleBin, CProgressDlgData& dlgData) { + char* name = operation->SourceName; DWORD err; int AutoRetryCounter = 0; - DWORD startTime = GetTickCount(); // if the path ends with a space/dot, we must append '\\'; otherwise SetFileAttributes // and RemoveDirectory trim the spaces/dots and operate on a different path @@ -6075,14 +6895,15 @@ BOOL DoDeleteDir(HWND hProgressDlg, char* name, const CQuadWord& size, COperatio while (1) { - ClearReadOnlyAttr(nameRmDir, attr); // ensure it can be deleted - err = ERROR_SUCCESS; if (script->CanUseRecycleBin && !dontUseRecycleBin && (script->InvertRecycleBin && dlgData.UseRecycleBin == 0 || !script->InvertRecycleBin && dlgData.UseRecycleBin == 1) && IsDirectoryEmpty(name)) // subdirectory must not contain any files!!! { + if (!VerifyFileIdentity(nameRmDir, operation->SourceIdentity, &err)) + goto DELETE_DIR_READY; + ClearReadOnlyAttr(nameRmDir, attr); if (!PathContainsValidComponents((char*)name, FALSE)) { err = ERROR_INVALID_NAME; @@ -6125,10 +6946,10 @@ BOOL DoDeleteDir(HWND hProgressDlg, char* name, const CQuadWord& size, COperatio } else { - if (RemoveDirectoryUtf8(nameRmDir) == 0) - err = GetLastError(); + DeleteFileWithVerifiedIdentity(nameRmDir, operation->SourceIdentity, &err); } + DELETE_DIR_READY: if (err == ERROR_SUCCESS) { script->AddBytesToSpeedMetersAndTFSandPS((DWORD)size.Value, TRUE, 0, NULL, MAX_OP_FILESIZE); @@ -6146,13 +6967,13 @@ BOOL DoDeleteDir(HWND hProgressDlg, char* name, const CQuadWord& size, COperatio if (dlgData.SkipAllDeleteErr) goto SKIP_DELETE; - if (AutoRetryCounter < 4 && GetTickCount() - startTime + (AutoRetryCounter + 1) * 100 <= 2000 && - (err == ERROR_DIR_NOT_EMPTY || err == ERROR_SHARING_VIOLATION)) - { // add auto-retry to handle this case: I have directories 1\2\3, deleting 1 including subdirectories while 3 is shown in a panel (watching for changes) -> removing 2 reports "directory not empty" because 3 stays in a transitional state due to change notifications (it is deleted, so it cannot be listed, but it still exists on disk briefly; quite a mess) - // TRACE_I("DoDeleteDir(): err: " << GetErrorText(err)); - AutoRetryCounter++; - Sleep(AutoRetryCounter * 100); - // TRACE_I("DoDeleteDir(): " << AutoRetryCounter << ". retry, delay is " << AutoRetryCounter * 100 << "ms"); + DWORD retryDelay; + if (PrepareAutomaticRetry(err, &AutoRetryCounter, rokDestructiveCommit, + script->GetCancellationEvent(), &retryDelay)) + { // Directory deletion has no idempotency proof, so the central policy rejects automatic retries. + script->RecordItemRetry(); + if (!WaitForAutomaticRetry(script->GetCancellationEvent(), retryDelay)) + return FALSE; } else { diff --git a/src/bugreprt.cpp b/src/bugreprt.cpp index 07bb67ff..2231bb9a 100644 --- a/src/bugreprt.cpp +++ b/src/bugreprt.cpp @@ -17,6 +17,7 @@ #include "worker.h" #include "snooper.h" #include "gui.h" +#include "release_diagnostics.h" char BugReportReasonBreak[BUG_REPORT_REASON_MAX]; // if not empty, displayed below the "Break" message @@ -2550,6 +2551,8 @@ void CCallStack::PrintBugReport(EXCEPTION_POINTERS* Exception, DWORD ThreadID, D PrintLine(param, "", FALSE); } + // The existing report file is locally viewable and uploaded only after consent. + PrintReleaseDiagnosticRingBuffer(PrintLine, param); PrintLine(param, "End.", FALSE); // for detecting a complete bug report if (Exception != NULL) diff --git a/src/cache.cpp b/src/cache.cpp index b8650bad..a2025c4f 100644 --- a/src/cache.cpp +++ b/src/cache.cpp @@ -905,14 +905,9 @@ void CCacheHandles::Destroy() if (Thread != NULL) { SetEvent(Terminate); // "you should end now" - if (WaitForSingleObject(Thread, 1000) == WAIT_TIMEOUT) - { - // The worker may be in a kernel call that cannot be interrupted immediately. - // Do not destroy its synchronization state or force-kill it: record the deadline - // breach and keep joining until the worker has released its own state. - TRACE_E("Cache-handles thread did not stop within the shutdown deadline; waiting for a safe join."); - WaitForSingleObject(Thread, INFINITE); - } + // Cache cleanup may be waiting on filesystem I/O, so preserve its + // recovery state through the named cancellation/recovery deadline. + CThreadShutdownDeadline("cache-handles worker").WaitForSafeJoin(Thread); HANDLES(CloseHandle(Thread)); Thread = NULL; } @@ -1602,6 +1597,9 @@ CDeleteManager::CDeleteManager() : Data(10, 50) HANDLES(InitializeCriticalSection(&CS)); WaitingForProcessing = FALSE; BlockDataProcessing = FALSE; + // Start invalid so startup workers cannot target a window before it has registered itself. + CallbackWindow = NULL; + CallbackGeneration = 0; } CDeleteManager::~CDeleteManager() @@ -1609,6 +1607,59 @@ CDeleteManager::~CDeleteManager() HANDLES(DeleteCriticalSection(&CS)); } +BOOL CDeleteManager::PostProcessMessageLocked() +{ + if (CallbackWindow == NULL) + return FALSE; + + // Carry the registration generation so a recycled HWND cannot consume a stale worker notification. + return PostMessage(CallbackWindow, WM_USER_PROCESSDELETEMAN, (WPARAM)CallbackGeneration, 0); +} + +void CDeleteManager::RegisterCallbackWindow(HWND hWindow) +{ + HANDLES(EnterCriticalSection(&CS)); + CallbackWindow = hWindow; + CallbackGeneration++; + if (CallbackGeneration == 0) + CallbackGeneration++; + + // A worker may have queued work before the main window finished creating; wake this generation now. + if (Data.Count > 0) + { + WaitingForProcessing = TRUE; + if (!PostProcessMessageLocked()) + { + WaitingForProcessing = FALSE; + TRACE_E("Unable to post delete-manager notification to the registered main window."); + } + } + HANDLES(LeaveCriticalSection(&CS)); +} + +void CDeleteManager::InvalidateCallbackWindow(HWND hWindow) +{ + HANDLES(EnterCriticalSection(&CS)); + if (CallbackWindow == hWindow) + { + // Invalidate before DestroyWindow completes so workers cannot post to a closing or reused HWND. + CallbackWindow = NULL; + CallbackGeneration++; + if (CallbackGeneration == 0) + CallbackGeneration++; + } + HANDLES(LeaveCriticalSection(&CS)); +} + +BOOL CDeleteManager::IsCurrentCallbackWindow(HWND hWindow, DWORD generation) +{ + HANDLES(EnterCriticalSection(&CS)); + // The receiver verifies both values because an HWND can be recycled after an asynchronous post. + BOOL isCurrent = CallbackWindow == hWindow && CallbackGeneration == generation; + HANDLES(LeaveCriticalSection(&CS)); + return isCurrent; +} + void CDeleteManager::AddFile(const char* fileName, CPluginInterfaceAbstract* plugin) { CALL_STACK_MESSAGE2("CDeleteManager::AddFile(%s)", fileName); @@ -1624,10 +1675,12 @@ void CDeleteManager::AddFile(const char* fileName, CPluginInterfaceAbstract* plu if (!WaitingForProcessing) // if needed, we will ensure processing of new data { WaitingForProcessing = TRUE; - if (MainWindow != NULL && MainWindow->HWindow != NULL) - PostMessage(MainWindow->HWindow, WM_USER_PROCESSDELETEMAN, 0, 0); - else - TRACE_E("Unexpected situation in CDeleteManager::AddFile()."); + // Post only through the generation-checked registration, never through MainWindow's raw HWND. + if (!PostProcessMessageLocked()) + { + WaitingForProcessing = FALSE; + TRACE_E("Unable to post delete-manager notification; the main window is not registered."); + } } } else diff --git a/src/cache.h b/src/cache.h index 19b22faa..feb7d9ac 100644 --- a/src/cache.h +++ b/src/cache.h @@ -506,6 +506,11 @@ class CDeleteManager BOOL WaitingForProcessing; // TRUE = message to the main window is on the way or the data // processing is in progress (the added item will be processed immediately) BOOL BlockDataProcessing; // TRUE = do not process data (ProcessData() does nothing) + // This registration is protected by CS so workers never retain MainWindow's raw HWND across teardown. + HWND CallbackWindow; + DWORD CallbackGeneration; + + BOOL PostProcessMessageLocked(); public: CDeleteManager(); @@ -518,6 +523,11 @@ class CDeleteManager // can be called from any thread void AddFile(const char* fileName, CPluginInterfaceAbstract* plugin); + // The main window owns this short-lived registration and invalidates it before destroying its HWND. + void RegisterCallbackWindow(HWND hWindow); + void InvalidateCallbackWindow(HWND hWindow); + BOOL IsCurrentCallbackWindow(HWND hWindow, DWORD generation); + // called from the main thread (calls main window after receiving the message WM_USER_PROCESSDELETEMAN); // processing of new data - deleting files in plugins void ProcessData(); diff --git a/src/callstk.cpp b/src/callstk.cpp index 53cde6ca..1a983097 100644 --- a/src/callstk.cpp +++ b/src/callstk.cpp @@ -3,6 +3,7 @@ // CommentsTranslationProject: TRANSLATED #include "precomp.h" +#include "release_diagnostics.h" #include "mainwnd.h" #include "usermenu.h" @@ -406,7 +407,9 @@ CCallStack::~CCallStack() { // by ending this thread, the code enters here one more time because CCallStack exited for it as well SetEvent(TBRData.TerminateEvent); // terminate the bug report thread - WaitForSingleObject(BugReportThread, 1000); // wait at most one second for the thread to finish + // The report may be preserving crash-recovery evidence, so retain + // its events until cancellation and recovery phases have completed. + CThreadShutdownDeadline("call-stack bug report").WaitForSafeJoin(BugReportThread); NOHANDLES(CloseHandle(BugReportThread)); BugReportThread = NULL; NOHANDLES(CloseHandle(TBRData.TerminateEvent)); @@ -771,6 +774,16 @@ BOOL CCallStack::CreateBugReportFile(EXCEPTION_POINTERS* Exception, DWORD thread } NOHANDLES(CloseHandle(file)); + + char diagnosticPath[MAX_PATH]; + lstrcpyn(diagnosticPath, bugReportFileName, _countof(diagnosticPath)); + char* extension = strrchr(diagnosticPath, '.'); + if (extension != NULL) + { + // The sidecar is a local export and joins an archive only after report consent. + lstrcpyn(extension, ".OPS", (int)(_countof(diagnosticPath) - (extension - diagnosticPath))); + ExportReleaseDiagnosticRingBuffer(diagnosticPath); + } } } } diff --git a/src/cfgdlg.h b/src/cfgdlg.h index e2e8a845..8527b519 100644 --- a/src/cfgdlg.h +++ b/src/cfgdlg.h @@ -314,6 +314,7 @@ struct CConfiguration char MiddleToolBar[400]; char LeftToolBar[200]; char RightToolBar[200]; + int ToolbarIconSize; // logical pixels; only the supported Small/Medium/Large values are persisted int UseRecycleBin; // 0 - do not use, 1 - for all, 2 - for RecycleMasks CMaskGroup RecycleMasks; // mask array determining what is sent to the Recycle Bin diff --git a/src/common/allochan.cpp b/src/common/allochan.cpp index 26c6a804..80963172 100644 --- a/src/common/allochan.cpp +++ b/src/common/allochan.cpp @@ -3,15 +3,11 @@ #include "precomp.h" +#include "allochan.h" + #include -#include -#include #include -#pragma warning(3 : 4706) // warning C4706: assignment within conditional expression - -#include "trace.h" - #ifndef ALLOCHAN_DISABLE #ifndef SAFE_ALLOC @@ -48,6 +44,33 @@ void Initialize__Allochan() #pragma init_seg(".i_alc$m") +const SIZE_T AllocEmergencyReserveSize = 64 * 1024; +volatile PVOID AllocEmergencyReserve = NULL; +volatile LONG AllocEmergencyActive = FALSE; +volatile PVOID AllocEmergencyNotificationWindow = NULL; +volatile LONG AllocEmergencyNotificationMessage = 0; + +void NotifyAllocationEmergency() +{ + HWND window = (HWND)InterlockedCompareExchangePointer((PVOID volatile*)&AllocEmergencyNotificationWindow, NULL, NULL); + UINT message = (UINT)InterlockedCompareExchange(&AllocEmergencyNotificationMessage, 0, 0); + if (window != NULL && message != 0) + PostMessage(window, message, 0, 0); +} + +void ActivateAllocationEmergency() +{ + if (InterlockedCompareExchange(&AllocEmergencyActive, TRUE, FALSE) != FALSE) + return; + + // The only recovery performed on the failing thread is releasing the + // precommitted reserve; all stateful work is deferred to the UI message. + PVOID reserve = InterlockedExchangePointer((PVOID volatile*)&AllocEmergencyReserve, NULL); + if (reserve != NULL) + HeapFree(GetProcessHeap(), 0, reserve); + NotifyAllocationEmergency(); +} + class C__AllocHandlerInit { public: @@ -55,7 +78,8 @@ class C__AllocHandlerInit C__AllocHandlerInit() { - InitializeCriticalSection(&CriticalSection); + // Allocate outside the CRT allocator so its failure cannot recurse into this handler. + AllocEmergencyReserve = HeapAlloc(GetProcessHeap(), 0, AllocEmergencyReserveSize); OldNewHandler = _set_new_handler(TaskscapeLtdNewHandler); // operator new should call our new-handler on insufficient memory OldNewMode = _set_new_mode(1); // malloc should call our new-handler on insufficient memory } @@ -63,92 +87,37 @@ class C__AllocHandlerInit { _set_new_mode(OldNewMode); _set_new_handler(OldNewHandler); - DeleteCriticalSection(&CriticalSection); + PVOID reserve = InterlockedExchangePointer((PVOID volatile*)&AllocEmergencyReserve, NULL); + if (reserve != NULL) + HeapFree(GetProcessHeap(), 0, reserve); } private: - CRITICAL_SECTION CriticalSection; _PNH OldNewHandler; int OldNewMode; } __AllocHandlerInit; -TCHAR __AllocHandlerMessage[500] = _T("Insufficient memory to allocate %Iu bytes. Try to release some memory (e.g. ") - _T("close some running application) and click Retry. If it does not help, you can ") - _T("click Ignore to pass memory allocation error to this application or click Abort ") - _T("to terminate this application."); -TCHAR __AllocHandlerTitle[200] = _T("Error"); -TCHAR __AllocHandlerWarningIgnore[500] = _T("Do you really want to pass memory allocation error to this application?\n\n") - _T("WARNING: Application may crash and then all unsaved data will be lost!\n") - _T("HINT: We recommend to risk this action only if the application is trying to ") - _T("allocate extra large block of memory (i.e. more than 500 MB)."); -TCHAR __AllocHandlerWarningAbort[200] = _T("Do you really want to terminate this application?\n\nWARNING: All unsaved data will be lost!"); - -void SetAllocHandlerMessage(const TCHAR* message, const TCHAR* title, const TCHAR* warningIgnore, const TCHAR* warningAbort) +void SetAllocEmergencyNotificationWindow(HWND window, UINT message) { - if (message != NULL) - lstrcpyn(__AllocHandlerMessage, message, 500); - if (title != NULL) - lstrcpyn(__AllocHandlerTitle, title, 200); - if (warningIgnore != NULL) - lstrcpyn(__AllocHandlerWarningIgnore, warningIgnore, 500); - if (warningAbort != NULL) - lstrcpyn(__AllocHandlerWarningAbort, warningAbort, 200); + // A notification registered after an early OOM is posted once the UI can + // safely persist recovery state and begin the controlled shutdown. + InterlockedExchange(&AllocEmergencyNotificationMessage, (LONG)message); + InterlockedExchangePointer((PVOID volatile*)&AllocEmergencyNotificationWindow, window); + if (window != NULL && message != 0 && IsAllocationEmergencyActive()) + PostMessage(window, message, 0, 0); } -int C__AllocHandlerInit::TaskscapeLtdNewHandler(size_t size) +BOOL IsAllocationEmergencyActive() { - TRACE_ET(_T("TaskscapeLtdNewHandler: not enough memory to allocate ") << size << _T(" bytes!")); - int ret = 1; - int ti = GetTickCount(); - EnterCriticalSection(&__AllocHandlerInit.CriticalSection); - if (GetTickCount() - ti <= 500) // we will show message-box only if we didn't force the user to solve the same problem in another thread a moment ago - { - TCHAR buf[550]; - _sntprintf_s(buf, _countof(buf) - 1, __AllocHandlerMessage, size); - int res; - do - { - res = MessageBox(NULL, buf, __AllocHandlerTitle, MB_ICONERROR | MB_TASKMODAL | MB_ABORTRETRYIGNORE | MB_DEFBUTTON2); - if (res == 0) - { - TRACE_ET(_T("TaskscapeLtdNewHandler: unable to open message-box!")); - Sleep(1000); // let the machine rest and try to show msgbox again - } - } while (res == 0); - if (res == IDABORT) // terminate - { - do - { - res = MessageBox(NULL, __AllocHandlerWarningAbort, __AllocHandlerTitle, MB_ICONQUESTION | MB_TASKMODAL | MB_YESNO | MB_DEFBUTTON2); - if (res == 0) - { - TRACE_ET(_T("TaskscapeLtdNewHandler: unable to open message-box with abort-warning!")); - Sleep(1000); // let the machine rest and try to show msgbox again - } - } while (res == 0); - if (res == IDYES) - TerminateProcess(GetCurrentProcess(), 777); // harder exit (ExitProcess still calls something) - } - else - { - if (res == IDIGNORE) // user wants to pass allocation problem to application (return NULL), places where large blocks are allocated should be protected (otherwise it will crash on NULL access) - { - do - { - res = MessageBox(NULL, __AllocHandlerWarningIgnore, __AllocHandlerTitle, MB_ICONQUESTION | MB_TASKMODAL | MB_YESNO | MB_DEFBUTTON2); - if (res == 0) - { - TRACE_ET(_T("TaskscapeLtdNewHandler: unable to open message-box with ignore-warning!")); - Sleep(1000); // let the machine rest and try to show msgbox again - } - } while (res == 0); - if (res == IDYES) - ret = 0; // returning NULL to application - } - } - } - LeaveCriticalSection(&__AllocHandlerInit.CriticalSection); - return ret; // retry or NULL + return InterlockedCompareExchange(&AllocEmergencyActive, FALSE, FALSE) != FALSE; +} + +int C__AllocHandlerInit::TaskscapeLtdNewHandler(size_t) +{ + ActivateAllocationEmergency(); + // Returning zero propagates the failure immediately instead of retrying + // while the allocator's caller may still own unrelated subsystem locks. + return 0; } #endif // ALLOCHAN_DISABLE diff --git a/src/common/allochan.h b/src/common/allochan.h index 4ce3a2e1..39577358 100644 --- a/src/common/allochan.h +++ b/src/common/allochan.h @@ -3,30 +3,10 @@ #pragma once -// Installs a handler to handle situations when memory runs out during operator new -// or malloc calls (which are used by calloc, realloc and others, see help). Ensures -// that neither operator new nor malloc will ever return NULL without user knowledge. -// Displays a messagebox with "insufficient memory" error and the user can retry the -// memory allocation after closing other applications. The user can also terminate the -// process or let the allocation error pass to the application (operator new or malloc -// will return NULL, large memory block allocations should be prepared for this, otherwise -// a crash will occur - the user is informed about this). +#include -// Setting the localized form of the insufficient memory message and warning messages -// (if the string should not be changed, use NULL); expected content: -// message: -// Insufficient memory to allocate %u bytes. Try to release some memory (e.g. -// close some running application) and click Retry. If it does not help, you can -// click Ignore to pass memory allocation error to this application or click Abort -// to terminate this application. -// title: (used for both: "message" and "warning") -// we recommend using the application name, so the user knows which application is complaining -// warningIgnore: -// Do you really want to pass memory allocation error to this application?\n\n -// WARNING: Application may crash and then all unsaved data will be lost!\n -// HINT: We recommend to risk this action only if the application is trying to -// allocate extra large block of memory (i.e. more than 500 MB). -// warningAbort: -// Do you really want to terminate this application?\n\nWARNING: All unsaved data will be lost! -void SetAllocHandlerMessage(const TCHAR* message, const TCHAR* title, - const TCHAR* warningIgnore, const TCHAR* warningAbort); +// The global allocation handler never blocks the failing thread. Its first +// failure releases the reserve, records fatal pressure atomically, and posts a +// pre-registered notification for the UI thread to perform recovery work. +void SetAllocEmergencyNotificationWindow(HWND window, UINT message); +BOOL IsAllocationEmergencyActive(); diff --git a/src/common/checked_arithmetic.h b/src/common/checked_arithmetic.h new file mode 100644 index 00000000..d5e55d91 --- /dev/null +++ b/src/common/checked_arithmetic.h @@ -0,0 +1,96 @@ +// SPDX-FileCopyrightText: 2026 Taskscape Ltd +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include + +// Size values commonly originate at file, IPC, or network boundaries. Keep +// the overflow test adjacent to the conversion so an invalid value cannot +// wrap into a smaller allocation or Win32 I/O request. +inline BOOL CheckedAddUInt64(uint64_t left, uint64_t right, uint64_t* result) +{ + if (result == NULL || left > UINT64_MAX - right) + return FALSE; + *result = left + right; + return TRUE; +} + +inline BOOL CheckedMultiplyUInt64(uint64_t left, uint64_t right, uint64_t* result) +{ + if (result == NULL || (right != 0 && left > UINT64_MAX / right)) + return FALSE; + *result = left * right; + return TRUE; +} + +inline BOOL CheckedAddSize(size_t left, size_t right, size_t* result) +{ + if (result == NULL || left > (size_t)-1 - right) + return FALSE; + *result = left + right; + return TRUE; +} + +inline BOOL CheckedMultiplySize(size_t left, size_t right, size_t* result) +{ + if (result == NULL || (right != 0 && left > (size_t)-1 / right)) + return FALSE; + *result = left * right; + return TRUE; +} + +inline BOOL CheckedCastUInt64ToSize(uint64_t value, size_t* result) +{ + if (result == NULL || value > (size_t)-1) + return FALSE; + *result = (size_t)value; + return TRUE; +} + +inline BOOL CheckedCastDwordToSize(DWORD value, size_t* result) +{ + if (result == NULL) + return FALSE; + *result = (size_t)value; + return TRUE; +} + +inline BOOL CheckedCastUInt64ToDword(uint64_t value, DWORD* result) +{ + if (result == NULL || value > MAXDWORD) + return FALSE; + *result = (DWORD)value; + return TRUE; +} + +inline BOOL CheckedCastSizeToDword(size_t value, DWORD* result) +{ + if (result == NULL || value > MAXDWORD) + return FALSE; + *result = (DWORD)value; + return TRUE; +} + +inline BOOL CheckedAddDword(DWORD left, DWORD right, DWORD* result) +{ + uint64_t sum; + return CheckedAddUInt64(left, right, &sum) && CheckedCastUInt64ToDword(sum, result); +} + +inline BOOL CheckedMultiplyDword(DWORD left, DWORD right, DWORD* result) +{ + uint64_t product; + return CheckedMultiplyUInt64(left, right, &product) && CheckedCastUInt64ToDword(product, result); +} + +inline BOOL CheckedCastSizeToInt(size_t value, int* result) +{ + if (result == NULL || value > INT_MAX) + return FALSE; + *result = (int)value; + return TRUE; +} diff --git a/src/common/dep/bzip2/LICENSE b/src/common/dep/bzip2/LICENSE index f420cffb..81a37eab 100644 --- a/src/common/dep/bzip2/LICENSE +++ b/src/common/dep/bzip2/LICENSE @@ -2,7 +2,7 @@ -------------------------------------------------------------------------- This program, "bzip2", the associated library "libbzip2", and all -documentation, are copyright (C) 1996-2007 Julian R Seward. All +documentation, are copyright (C) 1996-2019 Julian R Seward. All rights reserved. Redistribution and use in source and binary forms, with or without @@ -36,7 +36,7 @@ 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. -Julian Seward, jseward@bzip.org -bzip2/libbzip2 version 1.0.5 of 10 December 2007 +Julian Seward, jseward@acm.org +bzip2/libbzip2 version 1.0.8 of 13 July 2019 -------------------------------------------------------------------------- diff --git a/src/common/dep/bzip2/VENDOR.md b/src/common/dep/bzip2/VENDOR.md new file mode 100644 index 00000000..745059e7 --- /dev/null +++ b/src/common/dep/bzip2/VENDOR.md @@ -0,0 +1,18 @@ +# bzip2 vendor record + +The main executable builds the upstream bzip2 1.0.8 library sources in this +directory with `BZ_NO_STDIO`. `src/salbzip2.cpp` is the separate host adapter; +it retains the product's `CSalamanderBZIP2Abstract` streaming contract. + +- Upstream release: [bzip2 1.0.8](https://sourceware.org/pub/bzip2/bzip2-1.0.8.tar.gz), 13 July 2019. +- Upstream SHA-512: `083f5e675d73f3233c7930ebe20425a533feedeaaa9d8cc86831312a6581cefbe6ed0d08d2fa89be81082f2a5abdabca8b3c080bf97218a1bd59dc118a30b9f3`. +- Verification: the release archive SHA-512 was checked against + `https://sourceware.org/pub/bzip2/sha512.sum` before the source refresh. +- Local patches: none. `internal_e.cpp` is a FileManager integration shim and + is deliberately outside the imported upstream source set. +- Review cadence: review every calendar quarter and immediately on an upstream + security advisory or release. + +`tools/test-bzip2-compatibility.ps1` builds these checked-in sources, decodes +the retained golden archives through the streaming API, rejects a truncation +fixture, and replays the checked-in malformed-input fuzz corpus. diff --git a/src/common/dep/bzip2/blocksort.c b/src/common/dep/bzip2/blocksort.c index d0d662cd..92d81fe2 100644 --- a/src/common/dep/bzip2/blocksort.c +++ b/src/common/dep/bzip2/blocksort.c @@ -8,8 +8,8 @@ This file is part of bzip2/libbzip2, a program and library for lossless, block-sorting data compression. - bzip2/libbzip2 version 1.0.6 of 6 September 2010 - Copyright (C) 1996-2010 Julian Seward + bzip2/libbzip2 version 1.0.8 of 13 July 2019 + Copyright (C) 1996-2019 Julian Seward Please read the WARNING, DISCLAIMER and PATENTS sections in the README file. @@ -202,9 +202,9 @@ void fallbackQSort3 ( UInt32* fmap, bhtab [ 0 .. 2+(nblock/32) ] destroyed */ -#define SET_BH(zz) bhtab[(zz) >> 5] |= (1 << ((zz) & 31)) -#define CLEAR_BH(zz) bhtab[(zz) >> 5] &= ~(1 << ((zz) & 31)) -#define ISSET_BH(zz) (bhtab[(zz) >> 5] & (1 << ((zz) & 31))) +#define SET_BH(zz) bhtab[(zz) >> 5] |= ((UInt32)1 << ((zz) & 31)) +#define CLEAR_BH(zz) bhtab[(zz) >> 5] &= ~((UInt32)1 << ((zz) & 31)) +#define ISSET_BH(zz) (bhtab[(zz) >> 5] & ((UInt32)1 << ((zz) & 31))) #define WORD_BH(zz) bhtab[(zz) >> 5] #define UNALIGNED_BH(zz) ((zz) & 0x01f) diff --git a/src/common/dep/bzip2/bzlib.c b/src/common/dep/bzip2/bzlib.c index bd358a79..21786551 100644 --- a/src/common/dep/bzip2/bzlib.c +++ b/src/common/dep/bzip2/bzlib.c @@ -8,8 +8,8 @@ This file is part of bzip2/libbzip2, a program and library for lossless, block-sorting data compression. - bzip2/libbzip2 version 1.0.6 of 6 September 2010 - Copyright (C) 1996-2010 Julian Seward + bzip2/libbzip2 version 1.0.8 of 13 July 2019 + Copyright (C) 1996-2019 Julian Seward Please read the WARNING, DISCLAIMER and PATENTS sections in the README file. @@ -43,12 +43,12 @@ void BZ2_bz__AssertH__fail ( int errcode ) fprintf(stderr, "\n\nbzip2/libbzip2: internal error number %d.\n" "This is a bug in bzip2/libbzip2, %s.\n" - "Please report it to me at: jseward@bzip.org. If this happened\n" + "Please report it to: bzip2-devel@sourceware.org. If this happened\n" "when you were using some program which uses libbzip2 as a\n" "component, you should also report this bug to the author(s)\n" "of that program. Please make an effort to report this bug;\n" "timely and accurate bug reports eventually lead to higher\n" - "quality software. Thanks. Julian Seward, 10 December 2007.\n\n", + "quality software. Thanks.\n\n", errcode, BZ2_bzlibVersion() ); diff --git a/src/common/dep/bzip2/bzlib.h b/src/common/dep/bzip2/bzlib.h index b638391f..8966a6c5 100644 --- a/src/common/dep/bzip2/bzlib.h +++ b/src/common/dep/bzip2/bzlib.h @@ -1,4 +1,4 @@ - + /*-------------------------------------------------------------*/ /*--- Public header file for the library. ---*/ /*--- bzlib.h ---*/ @@ -8,8 +8,8 @@ This file is part of bzip2/libbzip2, a program and library for lossless, block-sorting data compression. - bzip2/libbzip2 version 1.0.6 of 6 September 2010 - Copyright (C) 1996-2010 Julian Seward + bzip2/libbzip2 version 1.0.8 of 13 July 2019 + Copyright (C) 1996-2019 Julian Seward Please read the WARNING, DISCLAIMER and PATENTS sections in the README file. diff --git a/src/common/dep/bzip2/bzlib_private.h b/src/common/dep/bzip2/bzlib_private.h index 6b2a45f8..3755a6f7 100644 --- a/src/common/dep/bzip2/bzlib_private.h +++ b/src/common/dep/bzip2/bzlib_private.h @@ -1,4 +1,4 @@ - + /*-------------------------------------------------------------*/ /*--- Private header file for the library. ---*/ /*--- bzlib_private.h ---*/ @@ -8,8 +8,8 @@ This file is part of bzip2/libbzip2, a program and library for lossless, block-sorting data compression. - bzip2/libbzip2 version 1.0.6 of 6 September 2010 - Copyright (C) 1996-2010 Julian Seward + bzip2/libbzip2 version 1.0.8 of 13 July 2019 + Copyright (C) 1996-2019 Julian Seward Please read the WARNING, DISCLAIMER and PATENTS sections in the README file. @@ -36,7 +36,7 @@ /*-- General stuff. --*/ -#define BZ_VERSION "1.0.6, 6-Sept-2010" +#define BZ_VERSION "1.0.8, 13-Jul-2019" typedef char Char; typedef unsigned char Bool; diff --git a/src/common/dep/bzip2/compress.c b/src/common/dep/bzip2/compress.c index 9e944ab6..5dfa0023 100644 --- a/src/common/dep/bzip2/compress.c +++ b/src/common/dep/bzip2/compress.c @@ -8,8 +8,8 @@ This file is part of bzip2/libbzip2, a program and library for lossless, block-sorting data compression. - bzip2/libbzip2 version 1.0.6 of 6 September 2010 - Copyright (C) 1996-2010 Julian Seward + bzip2/libbzip2 version 1.0.8 of 13 July 2019 + Copyright (C) 1996-2019 Julian Seward Please read the WARNING, DISCLAIMER and PATENTS sections in the README file. @@ -202,7 +202,7 @@ void generateMTFValues ( EState* s ) *ryy_j = rtmp2; }; yy[0] = rtmp; - j = (Int32)(ryy_j - &(yy[0])); + j = ryy_j - &(yy[0]); mtfv[wr] = j+1; wr++; s->mtfFreq[j+1]++; } @@ -454,7 +454,7 @@ void sendMTFValues ( EState* s ) AssertH( nGroups < 8, 3002 ); AssertH( nSelectors < 32768 && - nSelectors <= (2 + (900000 / BZ_G_SIZE)), + nSelectors <= BZ_MAX_SELECTORS, 3003 ); diff --git a/src/common/dep/bzip2/crctable.c b/src/common/dep/bzip2/crctable.c index 1fea7e94..2b33c253 100644 --- a/src/common/dep/bzip2/crctable.c +++ b/src/common/dep/bzip2/crctable.c @@ -8,8 +8,8 @@ This file is part of bzip2/libbzip2, a program and library for lossless, block-sorting data compression. - bzip2/libbzip2 version 1.0.6 of 6 September 2010 - Copyright (C) 1996-2010 Julian Seward + bzip2/libbzip2 version 1.0.8 of 13 July 2019 + Copyright (C) 1996-2019 Julian Seward Please read the WARNING, DISCLAIMER and PATENTS sections in the README file. diff --git a/src/common/dep/bzip2/decompress.c b/src/common/dep/bzip2/decompress.c index 311f5668..a1a0bac8 100644 --- a/src/common/dep/bzip2/decompress.c +++ b/src/common/dep/bzip2/decompress.c @@ -8,8 +8,8 @@ This file is part of bzip2/libbzip2, a program and library for lossless, block-sorting data compression. - bzip2/libbzip2 version 1.0.6 of 6 September 2010 - Copyright (C) 1996-2010 Julian Seward + bzip2/libbzip2 version 1.0.8 of 13 July 2019 + Copyright (C) 1996-2019 Julian Seward Please read the WARNING, DISCLAIMER and PATENTS sections in the README file. @@ -285,7 +285,7 @@ Int32 BZ2_decompress ( DState* s ) /*--- Now the selectors ---*/ GET_BITS(BZ_X_SELECTOR_1, nGroups, 3); - if (nGroups < 2 || nGroups > 6) RETURN(BZ_DATA_ERROR); + if (nGroups < 2 || nGroups > BZ_N_GROUPS) RETURN(BZ_DATA_ERROR); GET_BITS(BZ_X_SELECTOR_2, nSelectors, 15); if (nSelectors < 1) RETURN(BZ_DATA_ERROR); for (i = 0; i < nSelectors; i++) { @@ -296,8 +296,14 @@ Int32 BZ2_decompress ( DState* s ) j++; if (j >= nGroups) RETURN(BZ_DATA_ERROR); } - s->selectorMtf[i] = j; + /* Having more than BZ_MAX_SELECTORS doesn't make much sense + since they will never be used, but some implementations might + "round up" the number of selectors, so just ignore those. */ + if (i < BZ_MAX_SELECTORS) + s->selectorMtf[i] = j; } + if (nSelectors > BZ_MAX_SELECTORS) + nSelectors = BZ_MAX_SELECTORS; /*--- Undo the MTF values for the selectors. ---*/ { diff --git a/src/common/dep/bzip2/huffman.c b/src/common/dep/bzip2/huffman.c index 2283fdbc..43a1899e 100644 --- a/src/common/dep/bzip2/huffman.c +++ b/src/common/dep/bzip2/huffman.c @@ -8,8 +8,8 @@ This file is part of bzip2/libbzip2, a program and library for lossless, block-sorting data compression. - bzip2/libbzip2 version 1.0.6 of 6 September 2010 - Copyright (C) 1996-2010 Julian Seward + bzip2/libbzip2 version 1.0.8 of 13 July 2019 + Copyright (C) 1996-2019 Julian Seward Please read the WARNING, DISCLAIMER and PATENTS sections in the README file. diff --git a/src/common/dep/bzip2/randtable.c b/src/common/dep/bzip2/randtable.c index 6d624599..bdc6d4a4 100644 --- a/src/common/dep/bzip2/randtable.c +++ b/src/common/dep/bzip2/randtable.c @@ -8,8 +8,8 @@ This file is part of bzip2/libbzip2, a program and library for lossless, block-sorting data compression. - bzip2/libbzip2 version 1.0.6 of 6 September 2010 - Copyright (C) 1996-2010 Julian Seward + bzip2/libbzip2 version 1.0.8 of 13 July 2019 + Copyright (C) 1996-2019 Julian Seward Please read the WARNING, DISCLAIMER and PATENTS sections in the README file. diff --git a/src/common/dep/sqlite/readme.txt b/src/common/dep/sqlite/readme.txt index 4a9490c1..a6e2804d 100644 --- a/src/common/dep/sqlite/readme.txt +++ b/src/common/dep/sqlite/readme.txt @@ -1,11 +1,41 @@ -Zdrojaky SQLite z webu http://www.sqlite.org/download.html -Pouzivame sqlite-amalgamation-*.zip -Koncepcne bychom meli pro nove verze Salamander pouzivat nejnovejsi verze SQLite, -abychom byli schopni cist nejnovejsi binarni verze SQLite databazi. +# SQLite vendor and recovery record -Updatnout: -salamand\sqlite\sqlite3.c -salamand\plugins\shared\sqlite\sqlite3.h +The vendored amalgamation is SQLite 3.53.4 (2026-07-24), imported from +`https://www.sqlite.org/2026/sqlite-amalgamation-3530400.zip`. -Pridat na to-do list: --otestovat novou verzi SQLite na Google Drive, podivat do TRACE, ze cteme cestu k jeho slozce \ No newline at end of file +| Item | SHA3-256 | +| --- | --- | +| Official amalgamation archive | `628a44cfe82c66aed1ccbbe85a562d2e33ebe64b3288981ed76285612227934e` | +| Imported `sqlite3.c` | `67f423e9ebbbdc473cbc4772c872ee6b89f31fde4ed0279a5c25d5f65c043a16` | + +Both `sqlite3.c` and `plugins/shared/sqlite/sqlite3.h` must come from the same +verified archive. `sqlite3_compileoption_get()` remains available; the +effective build options are deliberately recorded in +`src/vcxproj/sqlite/sqlite_base.props`: + +- `SQLITE_DQS=0` rejects legacy double-quoted string literals. +- `SQLITE_ENABLE_API_ARMOR` turns API misuse into checked errors. +- `SQLITE_DEFAULT_SYNCHRONOUS=2`, `SQLITE_DEFAULT_WAL_SYNCHRONOUS=2`, and + `SQLITE_DEFAULT_WAL_AUTOCHECKPOINT=1000` keep the default and WAL profiles + fully synchronous, with an explicit checkpoint threshold. + +## Database ownership and recovery policy + +The current product only opens Google Drive's `sync_config.db` read-only to +discover its configured path. It is third-party data: FileManager must not run +integrity checks, checkpoints, migrations, or recovery writes against it. + +If FileManager adds an owned SQLite database, its open path must first set +`journal_mode=WAL`, `synchronous=FULL`, and `wal_autocheckpoint=1000`, then +perform every mutation in a `BEGIN IMMEDIATE` transaction. On startup and after +an interrupted write, it must run `PRAGMA integrity_check`. An `ok` result is +required before the database is used. For `SQLITE_CORRUPT`, `SQLITE_NOTADB`, or +a non-`ok` integrity result, close the handle without writing, preserve the +database and its `-wal`/`-shm` companions for diagnosis, and create a fresh +store only through an explicit recovery flow. Never auto-repair or overwrite a +corrupt database in place. + +`tools/test-sqlite-recovery.ps1` executes this contract against the built DLL: +it proves that closing an uncommitted WAL transaction preserves only committed +rows after reopen, and that a deliberately corrupted b-tree page fails the +integrity check. Run it after the Debug x64 SQLite build. diff --git a/src/common/dep/sqlite/sqlite3.c b/src/common/dep/sqlite/sqlite3.c index 44042952..0644a39f 100644 --- a/src/common/dep/sqlite/sqlite3.c +++ b/src/common/dep/sqlite/sqlite3.c @@ -1,6 +1,6 @@ /****************************************************************************** ** This file is an amalgamation of many separate C source files from SQLite -** version 3.28.0. By combining all the individual C code files into this +** version 3.53.4. By combining all the individual C code files into this ** single large file, the entire code can be compiled as a single translation ** unit. This allows many compilers to do optimizations that would not be ** possible if the files were compiled separately. Performance improvements @@ -16,782 +16,18 @@ ** if you want a wrapper to interface SQLite with your choice of programming ** language. The code for the "sqlite3" command-line shell is also in a ** separate file. This file contains only code for the core SQLite library. +** +** The content in this amalgamation comes from Fossil check-in +** bf7c7f30031888f4e796e429ab3978879485 with changes in files: +** +** */ +#ifndef SQLITE_AMALGAMATION #define SQLITE_CORE 1 #define SQLITE_AMALGAMATION 1 #ifndef SQLITE_PRIVATE # define SQLITE_PRIVATE static #endif -/************** Begin file ctime.c *******************************************/ -/* -** 2010 February 23 -** -** The author disclaims copyright to this source code. In place of -** a legal notice, here is a blessing: -** -** May you do good and not evil. -** May you find forgiveness for yourself and forgive others. -** May you share freely, never taking more than you give. -** -************************************************************************* -** -** This file implements routines used to report what compile-time options -** SQLite was built with. -*/ - -#ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS - -/* -** Include the configuration header output by 'configure' if we're using the -** autoconf-based build -*/ -#if defined(_HAVE_SQLITE_CONFIG_H) && !defined(SQLITECONFIG_H) -#include "config.h" -#define SQLITECONFIG_H 1 -#endif - -/* These macros are provided to "stringify" the value of the define -** for those options in which the value is meaningful. */ -#define CTIMEOPT_VAL_(opt) #opt -#define CTIMEOPT_VAL(opt) CTIMEOPT_VAL_(opt) - -/* Like CTIMEOPT_VAL, but especially for SQLITE_DEFAULT_LOOKASIDE. This -** option requires a separate macro because legal values contain a single -** comma. e.g. (-DSQLITE_DEFAULT_LOOKASIDE="100,100") */ -#define CTIMEOPT_VAL2_(opt1,opt2) #opt1 "," #opt2 -#define CTIMEOPT_VAL2(opt) CTIMEOPT_VAL2_(opt) - -/* -** An array of names of all compile-time options. This array should -** be sorted A-Z. -** -** This array looks large, but in a typical installation actually uses -** only a handful of compile-time options, so most times this array is usually -** rather short and uses little memory space. -*/ -static const char * const sqlite3azCompileOpt[] = { - -/* -** BEGIN CODE GENERATED BY tool/mkctime.tcl -*/ -#if SQLITE_32BIT_ROWID - "32BIT_ROWID", -#endif -#if SQLITE_4_BYTE_ALIGNED_MALLOC - "4_BYTE_ALIGNED_MALLOC", -#endif -#if SQLITE_64BIT_STATS - "64BIT_STATS", -#endif -#if SQLITE_ALLOW_COVERING_INDEX_SCAN - "ALLOW_COVERING_INDEX_SCAN", -#endif -#if SQLITE_ALLOW_URI_AUTHORITY - "ALLOW_URI_AUTHORITY", -#endif -#ifdef SQLITE_BITMASK_TYPE - "BITMASK_TYPE=" CTIMEOPT_VAL(SQLITE_BITMASK_TYPE), -#endif -#if SQLITE_BUG_COMPATIBLE_20160819 - "BUG_COMPATIBLE_20160819", -#endif -#if SQLITE_CASE_SENSITIVE_LIKE - "CASE_SENSITIVE_LIKE", -#endif -#if SQLITE_CHECK_PAGES - "CHECK_PAGES", -#endif -#if defined(__clang__) && defined(__clang_major__) - "COMPILER=clang-" CTIMEOPT_VAL(__clang_major__) "." - CTIMEOPT_VAL(__clang_minor__) "." - CTIMEOPT_VAL(__clang_patchlevel__), -#elif defined(_MSC_VER) - "COMPILER=msvc-" CTIMEOPT_VAL(_MSC_VER), -#elif defined(__GNUC__) && defined(__VERSION__) - "COMPILER=gcc-" __VERSION__, -#endif -#if SQLITE_COVERAGE_TEST - "COVERAGE_TEST", -#endif -#if SQLITE_DEBUG - "DEBUG", -#endif -#if SQLITE_DEFAULT_AUTOMATIC_INDEX - "DEFAULT_AUTOMATIC_INDEX", -#endif -#if SQLITE_DEFAULT_AUTOVACUUM - "DEFAULT_AUTOVACUUM", -#endif -#ifdef SQLITE_DEFAULT_CACHE_SIZE - "DEFAULT_CACHE_SIZE=" CTIMEOPT_VAL(SQLITE_DEFAULT_CACHE_SIZE), -#endif -#if SQLITE_DEFAULT_CKPTFULLFSYNC - "DEFAULT_CKPTFULLFSYNC", -#endif -#ifdef SQLITE_DEFAULT_FILE_FORMAT - "DEFAULT_FILE_FORMAT=" CTIMEOPT_VAL(SQLITE_DEFAULT_FILE_FORMAT), -#endif -#ifdef SQLITE_DEFAULT_FILE_PERMISSIONS - "DEFAULT_FILE_PERMISSIONS=" CTIMEOPT_VAL(SQLITE_DEFAULT_FILE_PERMISSIONS), -#endif -#if SQLITE_DEFAULT_FOREIGN_KEYS - "DEFAULT_FOREIGN_KEYS", -#endif -#ifdef SQLITE_DEFAULT_JOURNAL_SIZE_LIMIT - "DEFAULT_JOURNAL_SIZE_LIMIT=" CTIMEOPT_VAL(SQLITE_DEFAULT_JOURNAL_SIZE_LIMIT), -#endif -#ifdef SQLITE_DEFAULT_LOCKING_MODE - "DEFAULT_LOCKING_MODE=" CTIMEOPT_VAL(SQLITE_DEFAULT_LOCKING_MODE), -#endif -#ifdef SQLITE_DEFAULT_LOOKASIDE - "DEFAULT_LOOKASIDE=" CTIMEOPT_VAL2(SQLITE_DEFAULT_LOOKASIDE), -#endif -#if SQLITE_DEFAULT_MEMSTATUS - "DEFAULT_MEMSTATUS", -#endif -#ifdef SQLITE_DEFAULT_MMAP_SIZE - "DEFAULT_MMAP_SIZE=" CTIMEOPT_VAL(SQLITE_DEFAULT_MMAP_SIZE), -#endif -#ifdef SQLITE_DEFAULT_PAGE_SIZE - "DEFAULT_PAGE_SIZE=" CTIMEOPT_VAL(SQLITE_DEFAULT_PAGE_SIZE), -#endif -#ifdef SQLITE_DEFAULT_PCACHE_INITSZ - "DEFAULT_PCACHE_INITSZ=" CTIMEOPT_VAL(SQLITE_DEFAULT_PCACHE_INITSZ), -#endif -#ifdef SQLITE_DEFAULT_PROXYDIR_PERMISSIONS - "DEFAULT_PROXYDIR_PERMISSIONS=" CTIMEOPT_VAL(SQLITE_DEFAULT_PROXYDIR_PERMISSIONS), -#endif -#if SQLITE_DEFAULT_RECURSIVE_TRIGGERS - "DEFAULT_RECURSIVE_TRIGGERS", -#endif -#ifdef SQLITE_DEFAULT_ROWEST - "DEFAULT_ROWEST=" CTIMEOPT_VAL(SQLITE_DEFAULT_ROWEST), -#endif -#ifdef SQLITE_DEFAULT_SECTOR_SIZE - "DEFAULT_SECTOR_SIZE=" CTIMEOPT_VAL(SQLITE_DEFAULT_SECTOR_SIZE), -#endif -#ifdef SQLITE_DEFAULT_SYNCHRONOUS - "DEFAULT_SYNCHRONOUS=" CTIMEOPT_VAL(SQLITE_DEFAULT_SYNCHRONOUS), -#endif -#ifdef SQLITE_DEFAULT_WAL_AUTOCHECKPOINT - "DEFAULT_WAL_AUTOCHECKPOINT=" CTIMEOPT_VAL(SQLITE_DEFAULT_WAL_AUTOCHECKPOINT), -#endif -#ifdef SQLITE_DEFAULT_WAL_SYNCHRONOUS - "DEFAULT_WAL_SYNCHRONOUS=" CTIMEOPT_VAL(SQLITE_DEFAULT_WAL_SYNCHRONOUS), -#endif -#ifdef SQLITE_DEFAULT_WORKER_THREADS - "DEFAULT_WORKER_THREADS=" CTIMEOPT_VAL(SQLITE_DEFAULT_WORKER_THREADS), -#endif -#if SQLITE_DIRECT_OVERFLOW_READ - "DIRECT_OVERFLOW_READ", -#endif -#if SQLITE_DISABLE_DIRSYNC - "DISABLE_DIRSYNC", -#endif -#if SQLITE_DISABLE_FTS3_UNICODE - "DISABLE_FTS3_UNICODE", -#endif -#if SQLITE_DISABLE_FTS4_DEFERRED - "DISABLE_FTS4_DEFERRED", -#endif -#if SQLITE_DISABLE_INTRINSIC - "DISABLE_INTRINSIC", -#endif -#if SQLITE_DISABLE_LFS - "DISABLE_LFS", -#endif -#if SQLITE_DISABLE_PAGECACHE_OVERFLOW_STATS - "DISABLE_PAGECACHE_OVERFLOW_STATS", -#endif -#if SQLITE_DISABLE_SKIPAHEAD_DISTINCT - "DISABLE_SKIPAHEAD_DISTINCT", -#endif -#ifdef SQLITE_ENABLE_8_3_NAMES - "ENABLE_8_3_NAMES=" CTIMEOPT_VAL(SQLITE_ENABLE_8_3_NAMES), -#endif -#if SQLITE_ENABLE_API_ARMOR - "ENABLE_API_ARMOR", -#endif -#if SQLITE_ENABLE_ATOMIC_WRITE - "ENABLE_ATOMIC_WRITE", -#endif -#if SQLITE_ENABLE_BATCH_ATOMIC_WRITE - "ENABLE_BATCH_ATOMIC_WRITE", -#endif -#if SQLITE_ENABLE_CEROD - "ENABLE_CEROD=" CTIMEOPT_VAL(SQLITE_ENABLE_CEROD), -#endif -#if SQLITE_ENABLE_COLUMN_METADATA - "ENABLE_COLUMN_METADATA", -#endif -#if SQLITE_ENABLE_COLUMN_USED_MASK - "ENABLE_COLUMN_USED_MASK", -#endif -#if SQLITE_ENABLE_COSTMULT - "ENABLE_COSTMULT", -#endif -#if SQLITE_ENABLE_CURSOR_HINTS - "ENABLE_CURSOR_HINTS", -#endif -#if SQLITE_ENABLE_DBSTAT_VTAB - "ENABLE_DBSTAT_VTAB", -#endif -#if SQLITE_ENABLE_EXPENSIVE_ASSERT - "ENABLE_EXPENSIVE_ASSERT", -#endif -#if SQLITE_ENABLE_FTS1 - "ENABLE_FTS1", -#endif -#if SQLITE_ENABLE_FTS2 - "ENABLE_FTS2", -#endif -#if SQLITE_ENABLE_FTS3 - "ENABLE_FTS3", -#endif -#if SQLITE_ENABLE_FTS3_PARENTHESIS - "ENABLE_FTS3_PARENTHESIS", -#endif -#if SQLITE_ENABLE_FTS3_TOKENIZER - "ENABLE_FTS3_TOKENIZER", -#endif -#if SQLITE_ENABLE_FTS4 - "ENABLE_FTS4", -#endif -#if SQLITE_ENABLE_FTS5 - "ENABLE_FTS5", -#endif -#if SQLITE_ENABLE_GEOPOLY - "ENABLE_GEOPOLY", -#endif -#if SQLITE_ENABLE_HIDDEN_COLUMNS - "ENABLE_HIDDEN_COLUMNS", -#endif -#if SQLITE_ENABLE_ICU - "ENABLE_ICU", -#endif -#if SQLITE_ENABLE_IOTRACE - "ENABLE_IOTRACE", -#endif -#if SQLITE_ENABLE_JSON1 - "ENABLE_JSON1", -#endif -#if SQLITE_ENABLE_LOAD_EXTENSION - "ENABLE_LOAD_EXTENSION", -#endif -#ifdef SQLITE_ENABLE_LOCKING_STYLE - "ENABLE_LOCKING_STYLE=" CTIMEOPT_VAL(SQLITE_ENABLE_LOCKING_STYLE), -#endif -#if SQLITE_ENABLE_MEMORY_MANAGEMENT - "ENABLE_MEMORY_MANAGEMENT", -#endif -#if SQLITE_ENABLE_MEMSYS3 - "ENABLE_MEMSYS3", -#endif -#if SQLITE_ENABLE_MEMSYS5 - "ENABLE_MEMSYS5", -#endif -#if SQLITE_ENABLE_MULTIPLEX - "ENABLE_MULTIPLEX", -#endif -#if SQLITE_ENABLE_NORMALIZE - "ENABLE_NORMALIZE", -#endif -#if SQLITE_ENABLE_NULL_TRIM - "ENABLE_NULL_TRIM", -#endif -#if SQLITE_ENABLE_OVERSIZE_CELL_CHECK - "ENABLE_OVERSIZE_CELL_CHECK", -#endif -#if SQLITE_ENABLE_PREUPDATE_HOOK - "ENABLE_PREUPDATE_HOOK", -#endif -#if SQLITE_ENABLE_QPSG - "ENABLE_QPSG", -#endif -#if SQLITE_ENABLE_RBU - "ENABLE_RBU", -#endif -#if SQLITE_ENABLE_RTREE - "ENABLE_RTREE", -#endif -#if SQLITE_ENABLE_SELECTTRACE - "ENABLE_SELECTTRACE", -#endif -#if SQLITE_ENABLE_SESSION - "ENABLE_SESSION", -#endif -#if SQLITE_ENABLE_SNAPSHOT - "ENABLE_SNAPSHOT", -#endif -#if SQLITE_ENABLE_SORTER_REFERENCES - "ENABLE_SORTER_REFERENCES", -#endif -#if SQLITE_ENABLE_SQLLOG - "ENABLE_SQLLOG", -#endif -#if defined(SQLITE_ENABLE_STAT4) - "ENABLE_STAT4", -#elif defined(SQLITE_ENABLE_STAT3) - "ENABLE_STAT3", -#endif -#if SQLITE_ENABLE_STMTVTAB - "ENABLE_STMTVTAB", -#endif -#if SQLITE_ENABLE_STMT_SCANSTATUS - "ENABLE_STMT_SCANSTATUS", -#endif -#if SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION - "ENABLE_UNKNOWN_SQL_FUNCTION", -#endif -#if SQLITE_ENABLE_UNLOCK_NOTIFY - "ENABLE_UNLOCK_NOTIFY", -#endif -#if SQLITE_ENABLE_UPDATE_DELETE_LIMIT - "ENABLE_UPDATE_DELETE_LIMIT", -#endif -#if SQLITE_ENABLE_URI_00_ERROR - "ENABLE_URI_00_ERROR", -#endif -#if SQLITE_ENABLE_VFSTRACE - "ENABLE_VFSTRACE", -#endif -#if SQLITE_ENABLE_WHERETRACE - "ENABLE_WHERETRACE", -#endif -#if SQLITE_ENABLE_ZIPVFS - "ENABLE_ZIPVFS", -#endif -#if SQLITE_EXPLAIN_ESTIMATED_ROWS - "EXPLAIN_ESTIMATED_ROWS", -#endif -#if SQLITE_EXTRA_IFNULLROW - "EXTRA_IFNULLROW", -#endif -#ifdef SQLITE_EXTRA_INIT - "EXTRA_INIT=" CTIMEOPT_VAL(SQLITE_EXTRA_INIT), -#endif -#ifdef SQLITE_EXTRA_SHUTDOWN - "EXTRA_SHUTDOWN=" CTIMEOPT_VAL(SQLITE_EXTRA_SHUTDOWN), -#endif -#ifdef SQLITE_FTS3_MAX_EXPR_DEPTH - "FTS3_MAX_EXPR_DEPTH=" CTIMEOPT_VAL(SQLITE_FTS3_MAX_EXPR_DEPTH), -#endif -#if SQLITE_FTS5_ENABLE_TEST_MI - "FTS5_ENABLE_TEST_MI", -#endif -#if SQLITE_FTS5_NO_WITHOUT_ROWID - "FTS5_NO_WITHOUT_ROWID", -#endif -#if SQLITE_HAS_CODEC - "HAS_CODEC", -#endif -#if HAVE_ISNAN || SQLITE_HAVE_ISNAN - "HAVE_ISNAN", -#endif -#if SQLITE_HOMEGROWN_RECURSIVE_MUTEX - "HOMEGROWN_RECURSIVE_MUTEX", -#endif -#if SQLITE_IGNORE_AFP_LOCK_ERRORS - "IGNORE_AFP_LOCK_ERRORS", -#endif -#if SQLITE_IGNORE_FLOCK_LOCK_ERRORS - "IGNORE_FLOCK_LOCK_ERRORS", -#endif -#if SQLITE_INLINE_MEMCPY - "INLINE_MEMCPY", -#endif -#if SQLITE_INT64_TYPE - "INT64_TYPE", -#endif -#ifdef SQLITE_INTEGRITY_CHECK_ERROR_MAX - "INTEGRITY_CHECK_ERROR_MAX=" CTIMEOPT_VAL(SQLITE_INTEGRITY_CHECK_ERROR_MAX), -#endif -#if SQLITE_LIKE_DOESNT_MATCH_BLOBS - "LIKE_DOESNT_MATCH_BLOBS", -#endif -#if SQLITE_LOCK_TRACE - "LOCK_TRACE", -#endif -#if SQLITE_LOG_CACHE_SPILL - "LOG_CACHE_SPILL", -#endif -#ifdef SQLITE_MALLOC_SOFT_LIMIT - "MALLOC_SOFT_LIMIT=" CTIMEOPT_VAL(SQLITE_MALLOC_SOFT_LIMIT), -#endif -#ifdef SQLITE_MAX_ATTACHED - "MAX_ATTACHED=" CTIMEOPT_VAL(SQLITE_MAX_ATTACHED), -#endif -#ifdef SQLITE_MAX_COLUMN - "MAX_COLUMN=" CTIMEOPT_VAL(SQLITE_MAX_COLUMN), -#endif -#ifdef SQLITE_MAX_COMPOUND_SELECT - "MAX_COMPOUND_SELECT=" CTIMEOPT_VAL(SQLITE_MAX_COMPOUND_SELECT), -#endif -#ifdef SQLITE_MAX_DEFAULT_PAGE_SIZE - "MAX_DEFAULT_PAGE_SIZE=" CTIMEOPT_VAL(SQLITE_MAX_DEFAULT_PAGE_SIZE), -#endif -#ifdef SQLITE_MAX_EXPR_DEPTH - "MAX_EXPR_DEPTH=" CTIMEOPT_VAL(SQLITE_MAX_EXPR_DEPTH), -#endif -#ifdef SQLITE_MAX_FUNCTION_ARG - "MAX_FUNCTION_ARG=" CTIMEOPT_VAL(SQLITE_MAX_FUNCTION_ARG), -#endif -#ifdef SQLITE_MAX_LENGTH - "MAX_LENGTH=" CTIMEOPT_VAL(SQLITE_MAX_LENGTH), -#endif -#ifdef SQLITE_MAX_LIKE_PATTERN_LENGTH - "MAX_LIKE_PATTERN_LENGTH=" CTIMEOPT_VAL(SQLITE_MAX_LIKE_PATTERN_LENGTH), -#endif -#ifdef SQLITE_MAX_MEMORY - "MAX_MEMORY=" CTIMEOPT_VAL(SQLITE_MAX_MEMORY), -#endif -#ifdef SQLITE_MAX_MMAP_SIZE - "MAX_MMAP_SIZE=" CTIMEOPT_VAL(SQLITE_MAX_MMAP_SIZE), -#endif -#ifdef SQLITE_MAX_MMAP_SIZE_ - "MAX_MMAP_SIZE_=" CTIMEOPT_VAL(SQLITE_MAX_MMAP_SIZE_), -#endif -#ifdef SQLITE_MAX_PAGE_COUNT - "MAX_PAGE_COUNT=" CTIMEOPT_VAL(SQLITE_MAX_PAGE_COUNT), -#endif -#ifdef SQLITE_MAX_PAGE_SIZE - "MAX_PAGE_SIZE=" CTIMEOPT_VAL(SQLITE_MAX_PAGE_SIZE), -#endif -#ifdef SQLITE_MAX_SCHEMA_RETRY - "MAX_SCHEMA_RETRY=" CTIMEOPT_VAL(SQLITE_MAX_SCHEMA_RETRY), -#endif -#ifdef SQLITE_MAX_SQL_LENGTH - "MAX_SQL_LENGTH=" CTIMEOPT_VAL(SQLITE_MAX_SQL_LENGTH), -#endif -#ifdef SQLITE_MAX_TRIGGER_DEPTH - "MAX_TRIGGER_DEPTH=" CTIMEOPT_VAL(SQLITE_MAX_TRIGGER_DEPTH), -#endif -#ifdef SQLITE_MAX_VARIABLE_NUMBER - "MAX_VARIABLE_NUMBER=" CTIMEOPT_VAL(SQLITE_MAX_VARIABLE_NUMBER), -#endif -#ifdef SQLITE_MAX_VDBE_OP - "MAX_VDBE_OP=" CTIMEOPT_VAL(SQLITE_MAX_VDBE_OP), -#endif -#ifdef SQLITE_MAX_WORKER_THREADS - "MAX_WORKER_THREADS=" CTIMEOPT_VAL(SQLITE_MAX_WORKER_THREADS), -#endif -#if SQLITE_MEMDEBUG - "MEMDEBUG", -#endif -#if SQLITE_MIXED_ENDIAN_64BIT_FLOAT - "MIXED_ENDIAN_64BIT_FLOAT", -#endif -#if SQLITE_MMAP_READWRITE - "MMAP_READWRITE", -#endif -#if SQLITE_MUTEX_NOOP - "MUTEX_NOOP", -#endif -#if SQLITE_MUTEX_NREF - "MUTEX_NREF", -#endif -#if SQLITE_MUTEX_OMIT - "MUTEX_OMIT", -#endif -#if SQLITE_MUTEX_PTHREADS - "MUTEX_PTHREADS", -#endif -#if SQLITE_MUTEX_W32 - "MUTEX_W32", -#endif -#if SQLITE_NEED_ERR_NAME - "NEED_ERR_NAME", -#endif -#if SQLITE_NOINLINE - "NOINLINE", -#endif -#if SQLITE_NO_SYNC - "NO_SYNC", -#endif -#if SQLITE_OMIT_ALTERTABLE - "OMIT_ALTERTABLE", -#endif -#if SQLITE_OMIT_ANALYZE - "OMIT_ANALYZE", -#endif -#if SQLITE_OMIT_ATTACH - "OMIT_ATTACH", -#endif -#if SQLITE_OMIT_AUTHORIZATION - "OMIT_AUTHORIZATION", -#endif -#if SQLITE_OMIT_AUTOINCREMENT - "OMIT_AUTOINCREMENT", -#endif -#if SQLITE_OMIT_AUTOINIT - "OMIT_AUTOINIT", -#endif -#if SQLITE_OMIT_AUTOMATIC_INDEX - "OMIT_AUTOMATIC_INDEX", -#endif -#if SQLITE_OMIT_AUTORESET - "OMIT_AUTORESET", -#endif -#if SQLITE_OMIT_AUTOVACUUM - "OMIT_AUTOVACUUM", -#endif -#if SQLITE_OMIT_BETWEEN_OPTIMIZATION - "OMIT_BETWEEN_OPTIMIZATION", -#endif -#if SQLITE_OMIT_BLOB_LITERAL - "OMIT_BLOB_LITERAL", -#endif -#if SQLITE_OMIT_BTREECOUNT - "OMIT_BTREECOUNT", -#endif -#if SQLITE_OMIT_CAST - "OMIT_CAST", -#endif -#if SQLITE_OMIT_CHECK - "OMIT_CHECK", -#endif -#if SQLITE_OMIT_COMPLETE - "OMIT_COMPLETE", -#endif -#if SQLITE_OMIT_COMPOUND_SELECT - "OMIT_COMPOUND_SELECT", -#endif -#if SQLITE_OMIT_CONFLICT_CLAUSE - "OMIT_CONFLICT_CLAUSE", -#endif -#if SQLITE_OMIT_CTE - "OMIT_CTE", -#endif -#if SQLITE_OMIT_DATETIME_FUNCS - "OMIT_DATETIME_FUNCS", -#endif -#if SQLITE_OMIT_DECLTYPE - "OMIT_DECLTYPE", -#endif -#if SQLITE_OMIT_DEPRECATED - "OMIT_DEPRECATED", -#endif -#if SQLITE_OMIT_DISKIO - "OMIT_DISKIO", -#endif -#if SQLITE_OMIT_EXPLAIN - "OMIT_EXPLAIN", -#endif -#if SQLITE_OMIT_FLAG_PRAGMAS - "OMIT_FLAG_PRAGMAS", -#endif -#if SQLITE_OMIT_FLOATING_POINT - "OMIT_FLOATING_POINT", -#endif -#if SQLITE_OMIT_FOREIGN_KEY - "OMIT_FOREIGN_KEY", -#endif -#if SQLITE_OMIT_GET_TABLE - "OMIT_GET_TABLE", -#endif -#if SQLITE_OMIT_HEX_INTEGER - "OMIT_HEX_INTEGER", -#endif -#if SQLITE_OMIT_INCRBLOB - "OMIT_INCRBLOB", -#endif -#if SQLITE_OMIT_INTEGRITY_CHECK - "OMIT_INTEGRITY_CHECK", -#endif -#if SQLITE_OMIT_LIKE_OPTIMIZATION - "OMIT_LIKE_OPTIMIZATION", -#endif -#if SQLITE_OMIT_LOAD_EXTENSION - "OMIT_LOAD_EXTENSION", -#endif -#if SQLITE_OMIT_LOCALTIME - "OMIT_LOCALTIME", -#endif -#if SQLITE_OMIT_LOOKASIDE - "OMIT_LOOKASIDE", -#endif -#if SQLITE_OMIT_MEMORYDB - "OMIT_MEMORYDB", -#endif -#if SQLITE_OMIT_OR_OPTIMIZATION - "OMIT_OR_OPTIMIZATION", -#endif -#if SQLITE_OMIT_PAGER_PRAGMAS - "OMIT_PAGER_PRAGMAS", -#endif -#if SQLITE_OMIT_PARSER_TRACE - "OMIT_PARSER_TRACE", -#endif -#if SQLITE_OMIT_POPEN - "OMIT_POPEN", -#endif -#if SQLITE_OMIT_PRAGMA - "OMIT_PRAGMA", -#endif -#if SQLITE_OMIT_PROGRESS_CALLBACK - "OMIT_PROGRESS_CALLBACK", -#endif -#if SQLITE_OMIT_QUICKBALANCE - "OMIT_QUICKBALANCE", -#endif -#if SQLITE_OMIT_REINDEX - "OMIT_REINDEX", -#endif -#if SQLITE_OMIT_SCHEMA_PRAGMAS - "OMIT_SCHEMA_PRAGMAS", -#endif -#if SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS - "OMIT_SCHEMA_VERSION_PRAGMAS", -#endif -#if SQLITE_OMIT_SHARED_CACHE - "OMIT_SHARED_CACHE", -#endif -#if SQLITE_OMIT_SHUTDOWN_DIRECTORIES - "OMIT_SHUTDOWN_DIRECTORIES", -#endif -#if SQLITE_OMIT_SUBQUERY - "OMIT_SUBQUERY", -#endif -#if SQLITE_OMIT_TCL_VARIABLE - "OMIT_TCL_VARIABLE", -#endif -#if SQLITE_OMIT_TEMPDB - "OMIT_TEMPDB", -#endif -#if SQLITE_OMIT_TEST_CONTROL - "OMIT_TEST_CONTROL", -#endif -#if SQLITE_OMIT_TRACE - "OMIT_TRACE", -#endif -#if SQLITE_OMIT_TRIGGER - "OMIT_TRIGGER", -#endif -#if SQLITE_OMIT_TRUNCATE_OPTIMIZATION - "OMIT_TRUNCATE_OPTIMIZATION", -#endif -#if SQLITE_OMIT_UTF16 - "OMIT_UTF16", -#endif -#if SQLITE_OMIT_VACUUM - "OMIT_VACUUM", -#endif -#if SQLITE_OMIT_VIEW - "OMIT_VIEW", -#endif -#if SQLITE_OMIT_VIRTUALTABLE - "OMIT_VIRTUALTABLE", -#endif -#if SQLITE_OMIT_WAL - "OMIT_WAL", -#endif -#if SQLITE_OMIT_WSD - "OMIT_WSD", -#endif -#if SQLITE_OMIT_XFER_OPT - "OMIT_XFER_OPT", -#endif -#if SQLITE_PCACHE_SEPARATE_HEADER - "PCACHE_SEPARATE_HEADER", -#endif -#if SQLITE_PERFORMANCE_TRACE - "PERFORMANCE_TRACE", -#endif -#if SQLITE_POWERSAFE_OVERWRITE - "POWERSAFE_OVERWRITE", -#endif -#if SQLITE_PREFER_PROXY_LOCKING - "PREFER_PROXY_LOCKING", -#endif -#if SQLITE_PROXY_DEBUG - "PROXY_DEBUG", -#endif -#if SQLITE_REVERSE_UNORDERED_SELECTS - "REVERSE_UNORDERED_SELECTS", -#endif -#if SQLITE_RTREE_INT_ONLY - "RTREE_INT_ONLY", -#endif -#if SQLITE_SECURE_DELETE - "SECURE_DELETE", -#endif -#if SQLITE_SMALL_STACK - "SMALL_STACK", -#endif -#ifdef SQLITE_SORTER_PMASZ - "SORTER_PMASZ=" CTIMEOPT_VAL(SQLITE_SORTER_PMASZ), -#endif -#if SQLITE_SOUNDEX - "SOUNDEX", -#endif -#ifdef SQLITE_STAT4_SAMPLES - "STAT4_SAMPLES=" CTIMEOPT_VAL(SQLITE_STAT4_SAMPLES), -#endif -#ifdef SQLITE_STMTJRNL_SPILL - "STMTJRNL_SPILL=" CTIMEOPT_VAL(SQLITE_STMTJRNL_SPILL), -#endif -#if SQLITE_SUBSTR_COMPATIBILITY - "SUBSTR_COMPATIBILITY", -#endif -#if SQLITE_SYSTEM_MALLOC - "SYSTEM_MALLOC", -#endif -#if SQLITE_TCL - "TCL", -#endif -#ifdef SQLITE_TEMP_STORE - "TEMP_STORE=" CTIMEOPT_VAL(SQLITE_TEMP_STORE), -#endif -#if SQLITE_TEST - "TEST", -#endif -#if defined(SQLITE_THREADSAFE) - "THREADSAFE=" CTIMEOPT_VAL(SQLITE_THREADSAFE), -#elif defined(THREADSAFE) - "THREADSAFE=" CTIMEOPT_VAL(THREADSAFE), -#else - "THREADSAFE=1", -#endif -#if SQLITE_UNLINK_AFTER_CLOSE - "UNLINK_AFTER_CLOSE", -#endif -#if SQLITE_UNTESTABLE - "UNTESTABLE", -#endif -#if SQLITE_USER_AUTHENTICATION - "USER_AUTHENTICATION", -#endif -#if SQLITE_USE_ALLOCA - "USE_ALLOCA", -#endif -#if SQLITE_USE_FCNTL_TRACE - "USE_FCNTL_TRACE", -#endif -#if SQLITE_USE_URI - "USE_URI", -#endif -#if SQLITE_VDBE_COVERAGE - "VDBE_COVERAGE", -#endif -#if SQLITE_WIN32_MALLOC - "WIN32_MALLOC", -#endif -#if SQLITE_ZERO_MALLOC - "ZERO_MALLOC", -#endif -/* -** END CODE GENERATED BY tool/mkctime.tcl -*/ -}; - -SQLITE_PRIVATE const char **sqlite3CompileOptions(int *pnOpt){ - *pnOpt = sizeof(sqlite3azCompileOpt) / sizeof(sqlite3azCompileOpt[0]); - return (const char**)sqlite3azCompileOpt; -} - -#endif /* SQLITE_OMIT_COMPILEOPTION_DIAGS */ - -/************** End of ctime.c ***********************************************/ /************** Begin file sqliteInt.h ***************************************/ /* ** 2001 September 15 @@ -820,20 +56,20 @@ SQLITE_PRIVATE const char **sqlite3CompileOptions(int *pnOpt){ ** used on lines of code that actually ** implement parts of coverage testing. ** -** OPTIMIZATION-IF-TRUE - This branch is allowed to alway be false +** OPTIMIZATION-IF-TRUE - This branch is allowed to always be false ** and the correct answer is still obtained, ** though perhaps more slowly. ** -** OPTIMIZATION-IF-FALSE - This branch is allowed to alway be true +** OPTIMIZATION-IF-FALSE - This branch is allowed to always be true ** and the correct answer is still obtained, ** though perhaps more slowly. ** ** PREVENTS-HARMLESS-OVERREAD - This branch prevents a buffer overread ** that would be harmless and undetectable -** if it did occur. +** if it did occur. ** ** In all cases, the special comment must be enclosed in the usual -** slash-asterisk...asterisk-slash comment marks, with no spaces between the +** slash-asterisk...asterisk-slash comment marks, with no spaces between the ** asterisks and the comment text. */ @@ -888,6 +124,15 @@ SQLITE_PRIVATE const char **sqlite3CompileOptions(int *pnOpt){ #pragma warning(disable : 4706) #endif /* defined(_MSC_VER) */ +#if defined(_MSC_VER) && !defined(_WIN64) +#undef SQLITE_4_BYTE_ALIGNED_MALLOC +#define SQLITE_4_BYTE_ALIGNED_MALLOC +#endif /* defined(_MSC_VER) && !defined(_WIN64) */ + +#if !defined(HAVE_LOG2) && defined(_MSC_VER) && _MSC_VER<1800 +#define HAVE_LOG2 0 +#endif /* !defined(HAVE_LOG2) && defined(_MSC_VER) && _MSC_VER<1800 */ + #endif /* SQLITE_MSVC_H */ /************** End of msvc.h ************************************************/ @@ -925,7 +170,9 @@ SQLITE_PRIVATE const char **sqlite3CompileOptions(int *pnOpt){ #define HAVE_UTIME 1 #else /* This is not VxWorks. */ -#define OS_VXWORKS 0 +#ifndef OS_VXWORKS +# define OS_VXWORKS 0 +#endif #define HAVE_FCHOWN 1 #define HAVE_READLINK 1 #define HAVE_LSTAT 1 @@ -990,6 +237,18 @@ SQLITE_PRIVATE const char **sqlite3CompileOptions(int *pnOpt){ # define MSVC_VERSION 0 #endif +/* +** Some C99 functions in "math.h" are only present for MSVC when its version +** is associated with Visual Studio 2013 or higher. +*/ +#ifndef SQLITE_HAVE_C99_MATH_FUNCS +# if MSVC_VERSION==0 || MSVC_VERSION>=1800 +# define SQLITE_HAVE_C99_MATH_FUNCS (1) +# else +# define SQLITE_HAVE_C99_MATH_FUNCS (0) +# endif +#endif + /* Needed for various definitions... */ #if defined(__GNUC__) && !defined(_GNU_SOURCE) # define _GNU_SOURCE @@ -999,6 +258,18 @@ SQLITE_PRIVATE const char **sqlite3CompileOptions(int *pnOpt){ # define _BSD_SOURCE #endif +/* +** Macro to disable warnings about missing "break" at the end of a "case". +*/ +#if defined(__has_attribute) +# if __has_attribute(fallthrough) +# define deliberate_fall_through __attribute__((fallthrough)); +# endif +#endif +#if !defined(deliberate_fall_through) +# define deliberate_fall_through +#endif + /* ** For MinGW, check to see if we can include the header file containing its ** version information, among other things. Normally, this internal MinGW @@ -1031,6 +302,17 @@ SQLITE_PRIVATE const char **sqlite3CompileOptions(int *pnOpt){ # define _USE_32BIT_TIME_T #endif +/* Optionally #include a user-defined header, whereby compilation options +** may be set prior to where they take effect, but after platform setup. +** If SQLITE_CUSTOM_INCLUDE=? is defined, its value names the #include +** file. +*/ +#ifdef SQLITE_CUSTOM_INCLUDE +# define INC_STRINGIFY_(f) #f +# define INC_STRINGIFY(f) INC_STRINGIFY_(f) +# include INC_STRINGIFY(SQLITE_CUSTOM_INCLUDE) +#endif + /* The public SQLite interface. The _FILE_OFFSET_BITS macro must appear ** first in QNX. Also, the _USE_32BIT_TIME_T macro must appear first for ** MinGW. @@ -1082,7 +364,30 @@ extern "C" { /* -** Provide the ability to override linkage features of the interface. +** Facilitate override of interface linkage and calling conventions. +** Be aware that these macros may not be used within this particular +** translation of the amalgamation and its associated header file. +** +** The SQLITE_EXTERN and SQLITE_API macros are used to instruct the +** compiler that the target identifier should have external linkage. +** +** The SQLITE_CDECL macro is used to set the calling convention for +** public functions that accept a variable number of arguments. +** +** The SQLITE_APICALL macro is used to set the calling convention for +** public functions that accept a fixed number of arguments. +** +** The SQLITE_STDCALL macro is no longer used and is now deprecated. +** +** The SQLITE_CALLBACK macro is used to set the calling convention for +** function pointers. +** +** The SQLITE_SYSAPI macro is used to set the calling convention for +** functions provided by the operating system. +** +** Currently, the SQLITE_CDECL, SQLITE_APICALL, SQLITE_CALLBACK, and +** SQLITE_SYSAPI macros are used only when building for environments +** that require non-default calling conventions. */ #ifndef SQLITE_EXTERN # define SQLITE_EXTERN extern @@ -1147,9 +452,9 @@ extern "C" { ** be held constant and Z will be incremented or else Y will be incremented ** and Z will be reset to zero. ** -** Since [version 3.6.18] ([dateof:3.6.18]), +** Since [version 3.6.18] ([dateof:3.6.18]), ** SQLite source code has been stored in the -** Fossil configuration management +** Fossil configuration management ** system. ^The SQLITE_SOURCE_ID macro evaluates to ** a string which identifies a particular check-in of SQLite ** within its configuration management system. ^The SQLITE_SOURCE_ID @@ -1162,9 +467,12 @@ extern "C" { ** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite_version()] and [sqlite_source_id()]. */ -#define SQLITE_VERSION "3.28.0" -#define SQLITE_VERSION_NUMBER 3028000 -#define SQLITE_SOURCE_ID "2019-04-16 19:49:53 884b4b7e502b4e991677b53971277adfaf0a04a284f8e483e2553d0f83156b50" +#define SQLITE_VERSION "3.53.4" +#define SQLITE_VERSION_NUMBER 3053004 +#define SQLITE_SOURCE_ID "2026-07-24 19:02:57 bf7c7f30031888f4e796e429ab3978879485813aaca6f641c7b33e4e09459bcc" +#define SQLITE_SCM_BRANCH "branch-3.53" +#define SQLITE_SCM_TAGS "release version-3.53.4" +#define SQLITE_SCM_DATETIME "2026-07-24T19:02:57.525Z" /* ** CAPI3REF: Run-Time Library Version Numbers @@ -1184,14 +492,14 @@ extern "C" { ** assert( strcmp(sqlite3_libversion(),SQLITE_VERSION)==0 ); ** )^ ** -** ^The sqlite3_version[] string constant contains the text of [SQLITE_VERSION] -** macro. ^The sqlite3_libversion() function returns a pointer to the -** to the sqlite3_version[] string constant. The sqlite3_libversion() +** ^The sqlite3_version[] string constant contains the text of the +** [SQLITE_VERSION] macro. ^The sqlite3_libversion() function returns a +** pointer to the sqlite3_version[] string constant. The sqlite3_libversion() ** function is provided for use in DLLs since DLL users usually do not have ** direct access to string constants within the DLL. ^The ** sqlite3_libversion_number() function returns an integer equal to -** [SQLITE_VERSION_NUMBER]. ^(The sqlite3_sourceid() function returns -** a pointer to a string constant whose value is the same as the +** [SQLITE_VERSION_NUMBER]. ^(The sqlite3_sourceid() function returns +** a pointer to a string constant whose value is the same as the ** [SQLITE_SOURCE_ID] C preprocessor macro. Except if SQLite is built ** using an edited copy of [the amalgamation], then the last four characters ** of the hash might be different from [SQLITE_SOURCE_ID].)^ @@ -1206,20 +514,20 @@ SQLITE_API int sqlite3_libversion_number(void); /* ** CAPI3REF: Run-Time Library Compilation Options Diagnostics ** -** ^The sqlite3_compileoption_used() function returns 0 or 1 -** indicating whether the specified option was defined at -** compile time. ^The SQLITE_ prefix may be omitted from the -** option name passed to sqlite3_compileoption_used(). +** ^The sqlite3_compileoption_used() function returns 0 or 1 +** indicating whether the specified option was defined at +** compile time. ^The SQLITE_ prefix may be omitted from the +** option name passed to sqlite3_compileoption_used(). ** ** ^The sqlite3_compileoption_get() function allows iterating ** over the list of options that were defined at compile time by ** returning the N-th compile time option string. ^If N is out of range, -** sqlite3_compileoption_get() returns a NULL pointer. ^The SQLITE_ -** prefix is omitted from any strings returned by +** sqlite3_compileoption_get() returns a NULL pointer. ^The SQLITE_ +** prefix is omitted from any strings returned by ** sqlite3_compileoption_get(). ** ** ^Support for the diagnostic functions sqlite3_compileoption_used() -** and sqlite3_compileoption_get() may be omitted by specifying the +** and sqlite3_compileoption_get() may be omitted by specifying the ** [SQLITE_OMIT_COMPILEOPTION_DIAGS] option at compile time. ** ** See also: SQL functions [sqlite_compileoption_used()] and @@ -1243,7 +551,7 @@ SQLITE_API const char *sqlite3_compileoption_get(int N); ** SQLite can be compiled with or without mutexes. When ** the [SQLITE_THREADSAFE] C preprocessor macro is 1 or 2, mutexes ** are enabled and SQLite is threadsafe. When the -** [SQLITE_THREADSAFE] macro is 0, +** [SQLITE_THREADSAFE] macro is 0, ** the mutexes are omitted. Without the mutexes, it is not safe ** to use SQLite concurrently from more than one thread. ** @@ -1300,14 +608,14 @@ typedef struct sqlite3 sqlite3; ** ** ^The sqlite3_int64 and sqlite_int64 types can store integer values ** between -9223372036854775808 and +9223372036854775807 inclusive. ^The -** sqlite3_uint64 and sqlite_uint64 types can store integer values +** sqlite3_uint64 and sqlite_uint64 types can store integer values ** between 0 and +18446744073709551615 inclusive. */ #ifdef SQLITE_INT64_TYPE typedef SQLITE_INT64_TYPE sqlite_int64; # ifdef SQLITE_UINT64_TYPE typedef SQLITE_UINT64_TYPE sqlite_uint64; -# else +# else typedef unsigned SQLITE_INT64_TYPE sqlite_uint64; # endif #elif defined(_MSC_VER) || defined(__BORLANDC__) @@ -1338,26 +646,22 @@ typedef sqlite_uint64 sqlite3_uint64; ** the [sqlite3] object is successfully destroyed and all associated ** resources are deallocated. ** -** ^If the database connection is associated with unfinalized prepared -** statements or unfinished sqlite3_backup objects then sqlite3_close() -** will leave the database connection open and return [SQLITE_BUSY]. -** ^If sqlite3_close_v2() is called with unfinalized prepared statements -** and/or unfinished sqlite3_backups, then the database connection becomes -** an unusable "zombie" which will automatically be deallocated when the -** last prepared statement is finalized or the last sqlite3_backup is -** finished. The sqlite3_close_v2() interface is intended for use with -** host languages that are garbage collected, and where the order in which -** destructors are called is arbitrary. -** -** Applications should [sqlite3_finalize | finalize] all [prepared statements], -** [sqlite3_blob_close | close] all [BLOB handles], and +** Ideally, applications should [sqlite3_finalize | finalize] all +** [prepared statements], [sqlite3_blob_close | close] all [BLOB handles], and ** [sqlite3_backup_finish | finish] all [sqlite3_backup] objects associated -** with the [sqlite3] object prior to attempting to close the object. ^If -** sqlite3_close_v2() is called on a [database connection] that still has -** outstanding [prepared statements], [BLOB handles], and/or -** [sqlite3_backup] objects then it returns [SQLITE_OK] and the deallocation -** of resources is deferred until all [prepared statements], [BLOB handles], -** and [sqlite3_backup] objects are also destroyed. +** with the [sqlite3] object prior to attempting to close the object. +** ^If the database connection is associated with unfinalized prepared +** statements, BLOB handlers, and/or unfinished sqlite3_backup objects then +** sqlite3_close() will leave the database connection open and return +** [SQLITE_BUSY]. ^If sqlite3_close_v2() is called with unfinalized prepared +** statements, unclosed BLOB handlers, and/or unfinished sqlite3_backups, +** it returns [SQLITE_OK] regardless, but instead of deallocating the database +** connection immediately, it marks the database connection as an unusable +** "zombie" and makes arrangements to automatically deallocate the database +** connection after all prepared statements are finalized, all BLOB handles +** are closed, and all backups have finished. The sqlite3_close_v2() interface +** is intended for use with host languages that are garbage collected, and +** where the order in which destructors are called is arbitrary. ** ** ^If an [sqlite3] object is destroyed while a transaction is open, ** the transaction is automatically rolled back. @@ -1387,10 +691,10 @@ typedef int (*sqlite3_callback)(void*,int,char**, char**); ** The sqlite3_exec() interface is a convenience wrapper around ** [sqlite3_prepare_v2()], [sqlite3_step()], and [sqlite3_finalize()], ** that allows an application to run multiple statements of SQL -** without having to use a lot of C code. +** without having to use a lot of C code. ** ** ^The sqlite3_exec() interface runs zero or more UTF-8 encoded, -** semicolon-separate SQL statements passed into its 2nd argument, +** semicolon-separated SQL statements passed into its 2nd argument, ** in the context of the [database connection] passed in as its 1st ** argument. ^If the callback function of the 3rd argument to ** sqlite3_exec() is not NULL, then it is invoked for each result row @@ -1423,11 +727,11 @@ typedef int (*sqlite3_callback)(void*,int,char**, char**); ** result row is NULL then the corresponding string pointer for the ** sqlite3_exec() callback is a NULL pointer. ^The 4th argument to the ** sqlite3_exec() callback is an array of pointers to strings where each -** entry represents the name of corresponding result column as obtained +** entry represents the name of a corresponding result column as obtained ** from [sqlite3_column_name()]. ** ** ^If the 2nd parameter to sqlite3_exec() is a NULL pointer, a pointer -** to an empty string, or a pointer that contains only whitespace and/or +** to an empty string, or a pointer that contains only whitespace and/or ** SQL comments, then no SQL statements are evaluated and the database ** is not changed. ** @@ -1440,6 +744,8 @@ typedef int (*sqlite3_callback)(void*,int,char**, char**); ** the 1st parameter to sqlite3_exec() while sqlite3_exec() is running. **
  • The application must not modify the SQL statement text passed into ** the 2nd parameter of sqlite3_exec() while sqlite3_exec() is running. +**
  • The application must not dereference the arrays or string pointers +** passed as the 3rd and 4th callback parameters after it returns. ** */ SQLITE_API int sqlite3_exec( @@ -1515,6 +821,9 @@ SQLITE_API int sqlite3_exec( #define SQLITE_ERROR_MISSING_COLLSEQ (SQLITE_ERROR | (1<<8)) #define SQLITE_ERROR_RETRY (SQLITE_ERROR | (2<<8)) #define SQLITE_ERROR_SNAPSHOT (SQLITE_ERROR | (3<<8)) +#define SQLITE_ERROR_RESERVESIZE (SQLITE_ERROR | (4<<8)) +#define SQLITE_ERROR_KEY (SQLITE_ERROR | (5<<8)) +#define SQLITE_ERROR_UNABLE (SQLITE_ERROR | (6<<8)) #define SQLITE_IOERR_READ (SQLITE_IOERR | (1<<8)) #define SQLITE_IOERR_SHORT_READ (SQLITE_IOERR | (2<<8)) #define SQLITE_IOERR_WRITE (SQLITE_IOERR | (3<<8)) @@ -1546,17 +855,25 @@ SQLITE_API int sqlite3_exec( #define SQLITE_IOERR_BEGIN_ATOMIC (SQLITE_IOERR | (29<<8)) #define SQLITE_IOERR_COMMIT_ATOMIC (SQLITE_IOERR | (30<<8)) #define SQLITE_IOERR_ROLLBACK_ATOMIC (SQLITE_IOERR | (31<<8)) +#define SQLITE_IOERR_DATA (SQLITE_IOERR | (32<<8)) +#define SQLITE_IOERR_CORRUPTFS (SQLITE_IOERR | (33<<8)) +#define SQLITE_IOERR_IN_PAGE (SQLITE_IOERR | (34<<8)) +#define SQLITE_IOERR_BADKEY (SQLITE_IOERR | (35<<8)) +#define SQLITE_IOERR_CODEC (SQLITE_IOERR | (36<<8)) #define SQLITE_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1<<8)) #define SQLITE_LOCKED_VTAB (SQLITE_LOCKED | (2<<8)) #define SQLITE_BUSY_RECOVERY (SQLITE_BUSY | (1<<8)) #define SQLITE_BUSY_SNAPSHOT (SQLITE_BUSY | (2<<8)) +#define SQLITE_BUSY_TIMEOUT (SQLITE_BUSY | (3<<8)) #define SQLITE_CANTOPEN_NOTEMPDIR (SQLITE_CANTOPEN | (1<<8)) #define SQLITE_CANTOPEN_ISDIR (SQLITE_CANTOPEN | (2<<8)) #define SQLITE_CANTOPEN_FULLPATH (SQLITE_CANTOPEN | (3<<8)) #define SQLITE_CANTOPEN_CONVPATH (SQLITE_CANTOPEN | (4<<8)) #define SQLITE_CANTOPEN_DIRTYWAL (SQLITE_CANTOPEN | (5<<8)) /* Not Used */ +#define SQLITE_CANTOPEN_SYMLINK (SQLITE_CANTOPEN | (6<<8)) #define SQLITE_CORRUPT_VTAB (SQLITE_CORRUPT | (1<<8)) #define SQLITE_CORRUPT_SEQUENCE (SQLITE_CORRUPT | (2<<8)) +#define SQLITE_CORRUPT_INDEX (SQLITE_CORRUPT | (3<<8)) #define SQLITE_READONLY_RECOVERY (SQLITE_READONLY | (1<<8)) #define SQLITE_READONLY_CANTLOCK (SQLITE_READONLY | (2<<8)) #define SQLITE_READONLY_ROLLBACK (SQLITE_READONLY | (3<<8)) @@ -1574,11 +891,15 @@ SQLITE_API int sqlite3_exec( #define SQLITE_CONSTRAINT_UNIQUE (SQLITE_CONSTRAINT | (8<<8)) #define SQLITE_CONSTRAINT_VTAB (SQLITE_CONSTRAINT | (9<<8)) #define SQLITE_CONSTRAINT_ROWID (SQLITE_CONSTRAINT |(10<<8)) +#define SQLITE_CONSTRAINT_PINNED (SQLITE_CONSTRAINT |(11<<8)) +#define SQLITE_CONSTRAINT_DATATYPE (SQLITE_CONSTRAINT |(12<<8)) #define SQLITE_NOTICE_RECOVER_WAL (SQLITE_NOTICE | (1<<8)) #define SQLITE_NOTICE_RECOVER_ROLLBACK (SQLITE_NOTICE | (2<<8)) +#define SQLITE_NOTICE_RBU (SQLITE_NOTICE | (3<<8)) #define SQLITE_WARNING_AUTOINDEX (SQLITE_WARNING | (1<<8)) #define SQLITE_AUTH_USER (SQLITE_AUTH | (1<<8)) #define SQLITE_OK_LOAD_PERMANENTLY (SQLITE_OK | (1<<8)) +#define SQLITE_OK_SYMLINK (SQLITE_OK | (2<<8)) /* internal only */ /* ** CAPI3REF: Flags For File Open Operations @@ -1586,6 +907,19 @@ SQLITE_API int sqlite3_exec( ** These bit values are intended for use in the ** 3rd parameter to the [sqlite3_open_v2()] interface and ** in the 4th parameter to the [sqlite3_vfs.xOpen] method. +** +** Only those flags marked as "Ok for sqlite3_open_v2()" may be +** used as the third argument to the [sqlite3_open_v2()] interface. +** The other flags have historically been ignored by sqlite3_open_v2(), +** though future versions of SQLite might change so that an error is +** raised if any of the disallowed bits are passed into sqlite3_open_v2(). +** Applications should not depend on the historical behavior. +** +** Note in particular that passing the SQLITE_OPEN_EXCLUSIVE flag into +** [sqlite3_open_v2()] does *not* cause the underlying database file +** to be opened using O_EXCL. Passing SQLITE_OPEN_EXCLUSIVE into +** [sqlite3_open_v2()] has historically been a no-op and might become an +** error in future versions of SQLite. */ #define SQLITE_OPEN_READONLY 0x00000001 /* Ok for sqlite3_open_v2() */ #define SQLITE_OPEN_READWRITE 0x00000002 /* Ok for sqlite3_open_v2() */ @@ -1601,14 +935,19 @@ SQLITE_API int sqlite3_exec( #define SQLITE_OPEN_MAIN_JOURNAL 0x00000800 /* VFS only */ #define SQLITE_OPEN_TEMP_JOURNAL 0x00001000 /* VFS only */ #define SQLITE_OPEN_SUBJOURNAL 0x00002000 /* VFS only */ -#define SQLITE_OPEN_MASTER_JOURNAL 0x00004000 /* VFS only */ +#define SQLITE_OPEN_SUPER_JOURNAL 0x00004000 /* VFS only */ #define SQLITE_OPEN_NOMUTEX 0x00008000 /* Ok for sqlite3_open_v2() */ #define SQLITE_OPEN_FULLMUTEX 0x00010000 /* Ok for sqlite3_open_v2() */ #define SQLITE_OPEN_SHAREDCACHE 0x00020000 /* Ok for sqlite3_open_v2() */ #define SQLITE_OPEN_PRIVATECACHE 0x00040000 /* Ok for sqlite3_open_v2() */ #define SQLITE_OPEN_WAL 0x00080000 /* VFS only */ +#define SQLITE_OPEN_NOFOLLOW 0x01000000 /* Ok for sqlite3_open_v2() */ +#define SQLITE_OPEN_EXRESCODE 0x02000000 /* Extended result codes */ /* Reserved: 0x00F00000 */ +/* Legacy compatibility: */ +#define SQLITE_OPEN_MASTER_JOURNAL 0x00004000 /* VFS only */ + /* ** CAPI3REF: Device Characteristics @@ -1642,6 +981,13 @@ SQLITE_API int sqlite3_exec( ** filesystem supports doing multiple write operations atomically when those ** write operations are bracketed by [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] and ** [SQLITE_FCNTL_COMMIT_ATOMIC_WRITE]. +** +** The SQLITE_IOCAP_SUBPAGE_READ property means that it is ok to read +** from the database file in amounts that are not a multiple of the +** page size and that do not begin at a page boundary. Without this +** property, SQLite is careful to only do full-page reads and write +** on aligned pages, with the one exception that it will do a sub-page +** read of the first page to access the database header. */ #define SQLITE_IOCAP_ATOMIC 0x00000001 #define SQLITE_IOCAP_ATOMIC512 0x00000002 @@ -1658,19 +1004,24 @@ SQLITE_API int sqlite3_exec( #define SQLITE_IOCAP_POWERSAFE_OVERWRITE 0x00001000 #define SQLITE_IOCAP_IMMUTABLE 0x00002000 #define SQLITE_IOCAP_BATCH_ATOMIC 0x00004000 +#define SQLITE_IOCAP_SUBPAGE_READ 0x00008000 /* ** CAPI3REF: File Locking Levels ** ** SQLite uses one of these integer values as the second ** argument to calls it makes to the xLock() and xUnlock() methods -** of an [sqlite3_io_methods] object. +** of an [sqlite3_io_methods] object. These values are ordered from +** least restrictive to most restrictive. +** +** The argument to xLock() is always SHARED or higher. The argument to +** xUnlock is either SHARED or NONE. */ -#define SQLITE_LOCK_NONE 0 -#define SQLITE_LOCK_SHARED 1 -#define SQLITE_LOCK_RESERVED 2 -#define SQLITE_LOCK_PENDING 3 -#define SQLITE_LOCK_EXCLUSIVE 4 +#define SQLITE_LOCK_NONE 0 /* xUnlock() only */ +#define SQLITE_LOCK_SHARED 1 /* xLock() or xUnlock() */ +#define SQLITE_LOCK_RESERVED 2 /* xLock() only */ +#define SQLITE_LOCK_PENDING 3 /* xLock() only */ +#define SQLITE_LOCK_EXCLUSIVE 4 /* xLock() only */ /* ** CAPI3REF: Synchronization Type Flags @@ -1705,7 +1056,7 @@ SQLITE_API int sqlite3_exec( /* ** CAPI3REF: OS Interface Open File Handle ** -** An [sqlite3_file] object represents an open file in the +** An [sqlite3_file] object represents an open file in the ** [sqlite3_vfs | OS interface layer]. Individual OS interface ** implementations will ** want to subclass this object by appending additional fields @@ -1727,7 +1078,7 @@ struct sqlite3_file { ** This object defines the methods used to perform various operations ** against the open file represented by the [sqlite3_file] object. ** -** If the [sqlite3_vfs.xOpen] method sets the sqlite3_file.pMethods element +** If the [sqlite3_vfs.xOpen] method sets the sqlite3_file.pMethods element ** to a non-NULL pointer, then the sqlite3_io_methods.xClose method ** may be invoked even if the [sqlite3_vfs.xOpen] reported that it failed. The ** only way to prevent a call to xClose following a failed [sqlite3_vfs.xOpen] @@ -1748,11 +1099,18 @@ struct sqlite3_file { **
  • [SQLITE_LOCK_PENDING], or **
  • [SQLITE_LOCK_EXCLUSIVE]. ** -** xLock() increases the lock. xUnlock() decreases the lock. +** xLock() upgrades the database file lock. In other words, xLock() moves the +** database file lock in the direction NONE toward EXCLUSIVE. The argument to +** xLock() is always one of SHARED, RESERVED, PENDING, or EXCLUSIVE, never +** SQLITE_LOCK_NONE. If the database file lock is already at or above the +** requested lock, then the call to xLock() is a no-op. +** xUnlock() downgrades the database file lock to either SHARED or NONE. +** If the lock is already at or below the requested lock state, then the call +** to xUnlock() is a no-op. ** The xCheckReservedLock() method checks whether any database connection, ** either in this process or in some other process, is holding a RESERVED, -** PENDING, or EXCLUSIVE lock on the file. It returns true -** if such a lock exists and false otherwise. +** PENDING, or EXCLUSIVE lock on the file. It returns, via its output +** pointer parameter, true if such a lock exists and false otherwise. ** ** The xFileControl() method is a generic interface that allows custom ** VFS implementations to directly control an open file using the @@ -1793,6 +1151,7 @@ struct sqlite3_file { **
  • [SQLITE_IOCAP_POWERSAFE_OVERWRITE] **
  • [SQLITE_IOCAP_IMMUTABLE] **
  • [SQLITE_IOCAP_BATCH_ATOMIC] +**
  • [SQLITE_IOCAP_SUBPAGE_READ] ** ** ** The SQLITE_IOCAP_ATOMIC property means that all writes of @@ -1853,9 +1212,8 @@ struct sqlite3_io_methods { ** opcode causes the xFileControl method to write the current state of ** the lock (one of [SQLITE_LOCK_NONE], [SQLITE_LOCK_SHARED], ** [SQLITE_LOCK_RESERVED], [SQLITE_LOCK_PENDING], or [SQLITE_LOCK_EXCLUSIVE]) -** into an integer that the pArg argument points to. This capability -** is used during testing and is only available when the SQLITE_TEST -** compile-time option is used. +** into an integer that the pArg argument points to. +** This capability is only available if SQLite is compiled with [SQLITE_DEBUG]. ** **
  • [[SQLITE_FCNTL_SIZE_HINT]] ** The [SQLITE_FCNTL_SIZE_HINT] opcode is used by SQLite to give the VFS @@ -1877,7 +1235,7 @@ struct sqlite3_io_methods { **
  • [[SQLITE_FCNTL_CHUNK_SIZE]] ** The [SQLITE_FCNTL_CHUNK_SIZE] opcode is used to request that the VFS ** extends and truncates the database file in chunks of a size specified -** by the user. The fourth argument to [sqlite3_file_control()] should +** by the user. The fourth argument to [sqlite3_file_control()] should ** point to an integer (type int) containing the new chunk-size to use ** for the nominated database. Allocating database file space in large ** chunks (say 1MB at a time), may reduce file-system fragmentation and @@ -1895,29 +1253,29 @@ struct sqlite3_io_methods { ** connection. See also [SQLITE_FCNTL_FILE_POINTER]. ** **
  • [[SQLITE_FCNTL_SYNC_OMITTED]] -** No longer in use. +** The SQLITE_FCNTL_SYNC_OMITTED file-control is no longer used. ** **
  • [[SQLITE_FCNTL_SYNC]] ** The [SQLITE_FCNTL_SYNC] opcode is generated internally by SQLite and ** sent to the VFS immediately before the xSync method is invoked on a -** database file descriptor. Or, if the xSync method is not invoked -** because the user has configured SQLite with -** [PRAGMA synchronous | PRAGMA synchronous=OFF] it is invoked in place +** database file descriptor. Or, if the xSync method is not invoked +** because the user has configured SQLite with +** [PRAGMA synchronous | PRAGMA synchronous=OFF] it is invoked in place ** of the xSync method. In most cases, the pointer argument passed with ** this file-control is NULL. However, if the database file is being synced ** as part of a multi-database commit, the argument points to a nul-terminated -** string containing the transactions master-journal file name. VFSes that -** do not need this signal should silently ignore this opcode. Applications -** should not call [sqlite3_file_control()] with this opcode as doing so may -** disrupt the operation of the specialized VFSes that do require it. +** string containing the transactions super-journal file name. VFSes that +** do not need this signal should silently ignore this opcode. Applications +** should not call [sqlite3_file_control()] with this opcode as doing so may +** disrupt the operation of the specialized VFSes that do require it. ** **
  • [[SQLITE_FCNTL_COMMIT_PHASETWO]] ** The [SQLITE_FCNTL_COMMIT_PHASETWO] opcode is generated internally by SQLite ** and sent to the VFS after a transaction has been committed immediately ** but before the database is unlocked. VFSes that do not need this signal ** should silently ignore this opcode. Applications should not call -** [sqlite3_file_control()] with this opcode as doing so may disrupt the -** operation of the specialized VFSes that do require it. +** [sqlite3_file_control()] with this opcode as doing so may disrupt the +** operation of the specialized VFSes that do require it. ** **
  • [[SQLITE_FCNTL_WIN32_AV_RETRY]] ** ^The [SQLITE_FCNTL_WIN32_AV_RETRY] opcode is used to configure automatic @@ -1965,13 +1323,13 @@ struct sqlite3_io_methods { **
  • [[SQLITE_FCNTL_OVERWRITE]] ** ^The [SQLITE_FCNTL_OVERWRITE] opcode is invoked by SQLite after opening ** a write transaction to indicate that, unless it is rolled back for some -** reason, the entire database file will be overwritten by the current +** reason, the entire database file will be overwritten by the current ** transaction. This is used by VACUUM operations. ** **
  • [[SQLITE_FCNTL_VFSNAME]] ** ^The [SQLITE_FCNTL_VFSNAME] opcode can be used to obtain the names of -** all [VFSes] in the VFS stack. The names are of all VFS shims and the -** final bottom-level VFS are written into memory obtained from +** all [VFSes] in the VFS stack. The names of all VFS shims and the +** final bottom-level VFS are written into memory obtained from ** [sqlite3_malloc()] and the result is stored in the char* variable ** that the fourth parameter of [sqlite3_file_control()] points to. ** The caller is responsible for freeing the memory when done. As with @@ -1984,13 +1342,13 @@ struct sqlite3_io_methods { ** ^The [SQLITE_FCNTL_VFS_POINTER] opcode finds a pointer to the top-level ** [VFSes] currently in use. ^(The argument X in ** sqlite3_file_control(db,SQLITE_FCNTL_VFS_POINTER,X) must be -** of type "[sqlite3_vfs] **". This opcodes will set *X +** of type "[sqlite3_vfs] **". This opcode will set *X ** to a pointer to the top-level VFS.)^ ** ^When there are multiple VFS shims in the stack, this opcode finds the ** upper-most shim only. ** **
  • [[SQLITE_FCNTL_PRAGMA]] -** ^Whenever a [PRAGMA] statement is parsed, an [SQLITE_FCNTL_PRAGMA] +** ^Whenever a [PRAGMA] statement is parsed, an [SQLITE_FCNTL_PRAGMA] ** file control is sent to the open [sqlite3_file] object corresponding ** to the database file to which the pragma statement refers. ^The argument ** to the [SQLITE_FCNTL_PRAGMA] file control is an array of @@ -2001,7 +1359,7 @@ struct sqlite3_io_methods { ** of the char** argument point to a string obtained from [sqlite3_mprintf()] ** or the equivalent and that string will become the result of the pragma or ** the error message if the pragma fails. ^If the -** [SQLITE_FCNTL_PRAGMA] file control returns [SQLITE_NOTFOUND], then normal +** [SQLITE_FCNTL_PRAGMA] file control returns [SQLITE_NOTFOUND], then normal ** [PRAGMA] processing continues. ^If the [SQLITE_FCNTL_PRAGMA] ** file control returns [SQLITE_OK], then the parser assumes that the ** VFS has handled the PRAGMA itself and the parser generates a no-op @@ -2018,16 +1376,16 @@ struct sqlite3_io_methods { ** ^The [SQLITE_FCNTL_BUSYHANDLER] ** file-control may be invoked by SQLite on the database file handle ** shortly after it is opened in order to provide a custom VFS with access -** to the connections busy-handler callback. The argument is of type (void **) +** to the connection's busy-handler callback. The argument is of type (void**) ** - an array of two (void *) values. The first (void *) actually points -** to a function of type (int (*)(void *)). In order to invoke the connections +** to a function of type (int (*)(void *)). In order to invoke the connection's ** busy-handler, this function should be invoked with the second (void *) in ** the array as the only argument. If it returns non-zero, then the operation ** should be retried. If it returns zero, the custom VFS should abandon the ** current operation. ** **
  • [[SQLITE_FCNTL_TEMPFILENAME]] -** ^Application can invoke the [SQLITE_FCNTL_TEMPFILENAME] file-control +** ^Applications can invoke the [SQLITE_FCNTL_TEMPFILENAME] file-control ** to have SQLite generate a ** temporary filename using the same algorithm that is followed to generate ** temporary filenames for TEMP tables and other internal uses. The @@ -2041,7 +1399,7 @@ struct sqlite3_io_methods { ** The argument is a pointer to a value of type sqlite3_int64 that ** is an advisory maximum number of bytes in the file to memory map. The ** pointer is overwritten with the old value. The limit is not changed if -** the value originally pointed to is negative, and so the current limit +** the value originally pointed to is negative, and so the current limit ** can be queried by passing in a pointer to a negative number. This ** file-control is used internally to implement [PRAGMA mmap_size]. ** @@ -2071,6 +1429,11 @@ struct sqlite3_io_methods { ** pointed to by the pArg argument. This capability is used during testing ** and only needs to be supported when SQLITE_TEST is defined. ** +**
  • [[SQLITE_FCNTL_NULL_IO]] +** The [SQLITE_FCNTL_NULL_IO] opcode sets the low-level file descriptor +** or file handle for the [sqlite3_file] object such that it will no longer +** read or write to the database file. +** **
  • [[SQLITE_FCNTL_WAL_BLOCK]] ** The [SQLITE_FCNTL_WAL_BLOCK] is a signal to the VFS layer that it might ** be advantageous to block on the next WAL lock if the lock is not immediately @@ -2085,7 +1448,7 @@ struct sqlite3_io_methods { **
  • [[SQLITE_FCNTL_RBU]] ** The [SQLITE_FCNTL_RBU] opcode is implemented by the special VFS used by ** the RBU extension only. All other VFS should return SQLITE_NOTFOUND for -** this opcode. +** this opcode. ** **
  • [[SQLITE_FCNTL_BEGIN_ATOMIC_WRITE]] ** If the [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] opcode returns SQLITE_OK, then @@ -2102,7 +1465,7 @@ struct sqlite3_io_methods { ** **
  • [[SQLITE_FCNTL_COMMIT_ATOMIC_WRITE]] ** The [SQLITE_FCNTL_COMMIT_ATOMIC_WRITE] opcode causes all write -** operations since the previous successful call to +** operations since the previous successful call to ** [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] to be performed atomically. ** This file control returns [SQLITE_OK] if and only if the writes were ** all performed successfully and have been committed to persistent storage. @@ -2114,7 +1477,7 @@ struct sqlite3_io_methods { ** **
  • [[SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE]] ** The [SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE] opcode causes all write -** operations since the previous successful call to +** operations since the previous successful call to ** [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] to be rolled back. ** ^This file control takes the file descriptor out of batch write mode ** so that all subsequent write operations are independent. @@ -2122,10 +1485,18 @@ struct sqlite3_io_methods { ** a prior successful call to [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE]. ** **
  • [[SQLITE_FCNTL_LOCK_TIMEOUT]] -** The [SQLITE_FCNTL_LOCK_TIMEOUT] opcode causes attempts to obtain -** a file lock using the xLock or xShmLock methods of the VFS to wait -** for up to M milliseconds before failing, where M is the single -** unsigned integer parameter. +** The [SQLITE_FCNTL_LOCK_TIMEOUT] opcode is used to configure a VFS +** to block for up to M milliseconds before failing when attempting to +** obtain a file lock using the xLock or xShmLock methods of the VFS. +** The parameter is a pointer to a 32-bit signed integer that contains +** the value that M is to be set to. Before returning, the 32-bit signed +** integer is overwritten with the previous value of M. +** +**
  • [[SQLITE_FCNTL_BLOCK_ON_CONNECT]] +** The [SQLITE_FCNTL_BLOCK_ON_CONNECT] opcode is used to configure the +** VFS to block when taking a SHARED lock to connect to a wal mode database. +** This is used to implement the functionality associated with +** SQLITE_SETLK_BLOCK_ON_CONNECT. ** **
  • [[SQLITE_FCNTL_DATA_VERSION]] ** The [SQLITE_FCNTL_DATA_VERSION] opcode is used to detect changes to @@ -2140,12 +1511,54 @@ struct sqlite3_io_methods { ** not provide a mechanism to detect changes to MAIN only. Also, the ** [sqlite3_total_changes()] interface responds to internal changes only and ** omits changes made by other database connections. The -** [PRAGMA data_version] command provide a mechanism to detect changes to +** [PRAGMA data_version] command provides a mechanism to detect changes to ** a single attached database that occur due to other database connections, ** but omits changes implemented by the database connection on which it is ** called. This file control is the only mechanism to detect changes that ** happen either internally or externally and that are associated with ** a particular attached database. +** +**
  • [[SQLITE_FCNTL_CKPT_START]] +** The [SQLITE_FCNTL_CKPT_START] opcode is invoked from within a checkpoint +** in wal mode before the client starts to copy pages from the wal +** file to the database file. +** +**
  • [[SQLITE_FCNTL_CKPT_DONE]] +** The [SQLITE_FCNTL_CKPT_DONE] opcode is invoked from within a checkpoint +** in wal mode after the client has finished copying pages from the wal +** file to the database file, but before the *-shm file is updated to +** record the fact that the pages have been checkpointed. +** +**
  • [[SQLITE_FCNTL_EXTERNAL_READER]] +** The EXPERIMENTAL [SQLITE_FCNTL_EXTERNAL_READER] opcode is used to detect +** whether or not there is a database client in another process with a wal-mode +** transaction open on the database or not. It is only available on unix. The +** (void*) argument passed with this file-control should be a pointer to a +** value of type (int). The integer value is set to 1 if the database is a wal +** mode database and there exists at least one client in another process that +** currently has an SQL transaction open on the database. It is set to 0 if +** the database is not a wal-mode db, or if there is no such connection in any +** other process. This opcode cannot be used to detect transactions opened +** by clients within the current process, only within other processes. +** +**
  • [[SQLITE_FCNTL_CKSM_FILE]] +** The [SQLITE_FCNTL_CKSM_FILE] opcode is for use internally by the +** [checksum VFS shim] only. +** +**
  • [[SQLITE_FCNTL_RESET_CACHE]] +** If there is currently no transaction open on the database, and the +** database is not a temp db, then the [SQLITE_FCNTL_RESET_CACHE] file-control +** purges the contents of the in-memory page cache. If there is an open +** transaction, or if the db is a temp-db, this opcode is a no-op, not an error. +** +**
  • [[SQLITE_FCNTL_FILESTAT]] +** The [SQLITE_FCNTL_FILESTAT] opcode returns low-level diagnostic information +** about the [sqlite3_file] objects used access the database and journal files +** for the given schema. The fourth parameter to [sqlite3_file_control()] +** should be an initialized [sqlite3_str] pointer. JSON text describing +** various aspects of the sqlite3_file object is appended to the sqlite3_str. +** The SQLITE_FCNTL_FILESTAT opcode is usually a no-op, unless compile-time +** options are used to enable it. ** */ #define SQLITE_FCNTL_LOCKSTATE 1 @@ -2183,12 +1596,27 @@ struct sqlite3_io_methods { #define SQLITE_FCNTL_LOCK_TIMEOUT 34 #define SQLITE_FCNTL_DATA_VERSION 35 #define SQLITE_FCNTL_SIZE_LIMIT 36 +#define SQLITE_FCNTL_CKPT_DONE 37 +#define SQLITE_FCNTL_RESERVE_BYTES 38 +#define SQLITE_FCNTL_CKPT_START 39 +#define SQLITE_FCNTL_EXTERNAL_READER 40 +#define SQLITE_FCNTL_CKSM_FILE 41 +#define SQLITE_FCNTL_RESET_CACHE 42 +#define SQLITE_FCNTL_NULL_IO 43 +#define SQLITE_FCNTL_BLOCK_ON_CONNECT 44 +#define SQLITE_FCNTL_FILESTAT 45 /* deprecated names */ #define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE #define SQLITE_SET_LOCKPROXYFILE SQLITE_FCNTL_SET_LOCKPROXYFILE #define SQLITE_LAST_ERRNO SQLITE_FCNTL_LAST_ERRNO +/* reserved file-control numbers: +** 101 +** 102 +** 103 +*/ + /* ** CAPI3REF: Mutex Handle @@ -2212,6 +1640,26 @@ typedef struct sqlite3_mutex sqlite3_mutex; */ typedef struct sqlite3_api_routines sqlite3_api_routines; +/* +** CAPI3REF: File Name +** +** Type [sqlite3_filename] is used by SQLite to pass filenames to the +** xOpen method of a [VFS]. It may be cast to (const char*) and treated +** as a normal, nul-terminated, UTF-8 buffer containing the filename, but +** may also be passed to special APIs such as: +** +**
      +**
    • sqlite3_filename_database() +**
    • sqlite3_filename_journal() +**
    • sqlite3_filename_wal() +**
    • sqlite3_uri_parameter() +**
    • sqlite3_uri_boolean() +**
    • sqlite3_uri_int64() +**
    • sqlite3_uri_key() +**
    +*/ +typedef const char *sqlite3_filename; + /* ** CAPI3REF: OS Interface Object ** @@ -2228,10 +1676,10 @@ typedef struct sqlite3_api_routines sqlite3_api_routines; ** to 3 with SQLite [version 3.7.6] on [dateof:3.7.6]. Additional fields ** may be appended to the sqlite3_vfs object and the iVersion value ** may increase again in future versions of SQLite. -** Note that the structure -** of the sqlite3_vfs object changes in the transition from +** Note that due to an oversight, the structure +** of the sqlite3_vfs object changed in the transition from ** SQLite [version 3.5.9] to [version 3.6.0] on [dateof:3.6.0] -** and yet the iVersion field was not modified. +** and yet the iVersion field was not increased. ** ** The szOsFile field is the size of the subclassed [sqlite3_file] ** structure used by this VFS. mxPathname is the maximum length of @@ -2266,14 +1714,14 @@ typedef struct sqlite3_api_routines sqlite3_api_routines; ** the [sqlite3_file] can safely store a pointer to the ** filename if it needs to remember the filename for some reason. ** If the zFilename parameter to xOpen is a NULL pointer then xOpen -** must invent its own temporary name for the file. ^Whenever the +** must invent its own temporary name for the file. ^Whenever the ** xFilename parameter is NULL it will also be the case that the ** flags parameter will include [SQLITE_OPEN_DELETEONCLOSE]. ** ** The flags argument to xOpen() includes all bits set in ** the flags argument to [sqlite3_open_v2()]. Or if [sqlite3_open()] ** or [sqlite3_open16()] is used, then flags includes at least -** [SQLITE_OPEN_READWRITE] | [SQLITE_OPEN_CREATE]. +** [SQLITE_OPEN_READWRITE] | [SQLITE_OPEN_CREATE]. ** If xOpen() opens a file read-only then it sets *pOutFlags to ** include [SQLITE_OPEN_READONLY]. Other bits in *pOutFlags may be set. ** @@ -2287,7 +1735,7 @@ typedef struct sqlite3_api_routines sqlite3_api_routines; **
  • [SQLITE_OPEN_TEMP_JOURNAL] **
  • [SQLITE_OPEN_TRANSIENT_DB] **
  • [SQLITE_OPEN_SUBJOURNAL] -**
  • [SQLITE_OPEN_MASTER_JOURNAL] +**
  • [SQLITE_OPEN_SUPER_JOURNAL] **
  • [SQLITE_OPEN_WAL] ** )^ ** @@ -2315,14 +1763,14 @@ typedef struct sqlite3_api_routines sqlite3_api_routines; ** ^The [SQLITE_OPEN_EXCLUSIVE] flag is always used in conjunction ** with the [SQLITE_OPEN_CREATE] flag, which are both directly ** analogous to the O_EXCL and O_CREAT flags of the POSIX open() -** API. The SQLITE_OPEN_EXCLUSIVE flag, when paired with the +** API. The SQLITE_OPEN_EXCLUSIVE flag, when paired with the ** SQLITE_OPEN_CREATE, is used to indicate that file should always ** be created, and that it is an error if it already exists. -** It is not used to indicate the file should be opened +** It is not used to indicate the file should be opened ** for exclusive access. ** ** ^At least szOsFile bytes of memory are allocated by SQLite -** to hold the [sqlite3_file] structure passed as the third +** to hold the [sqlite3_file] structure passed as the third ** argument to xOpen. The xOpen method does not have to ** allocate the structure; it should just fill it in. Note that ** the xOpen method must set the sqlite3_file.pMethods to either @@ -2335,8 +1783,14 @@ typedef struct sqlite3_api_routines sqlite3_api_routines; ** ^The flags argument to xAccess() may be [SQLITE_ACCESS_EXISTS] ** to test for the existence of a file, or [SQLITE_ACCESS_READWRITE] to ** test whether a file is readable and writable, or [SQLITE_ACCESS_READ] -** to test whether a file is at least readable. The file can be a -** directory. +** to test whether a file is at least readable. The SQLITE_ACCESS_READ +** flag is never actually used and is not implemented in the built-in +** VFSes of SQLite. The file is named by the second argument and can be a +** directory. The xAccess method returns [SQLITE_OK] on success or some +** non-zero error code if there is an I/O error or if the name of +** the file given in the second argument is illegal. If SQLITE_OK +** is returned, then non-zero or zero is written into *pResOut to indicate +** whether or not the file is accessible. ** ** ^SQLite will always allocate at least mxPathname+1 bytes for the ** output buffer xFullPathname. The exact size of the output buffer @@ -2356,16 +1810,16 @@ typedef struct sqlite3_api_routines sqlite3_api_routines; ** method returns a Julian Day Number for the current date and time as ** a floating point value. ** ^The xCurrentTimeInt64() method returns, as an integer, the Julian -** Day Number multiplied by 86400000 (the number of milliseconds in -** a 24-hour day). +** Day Number multiplied by 86400000 (the number of milliseconds in +** a 24-hour day). ** ^SQLite will use the xCurrentTimeInt64() method to get the current -** date and time if that method is available (if iVersion is 2 or +** date and time if that method is available (if iVersion is 2 or ** greater and the function pointer is not NULL) and will fall back ** to xCurrentTime() if xCurrentTimeInt64() is unavailable. ** -** ^The xSetSystemCall(), xGetSystemCall(), and xNestSystemCall() interfaces +** ^The xSetSystemCall(), xGetSystemCall(), and xNextSystemCall() interfaces ** are not used by the SQLite core. These optional interfaces are provided -** by some VFSes to facilitate testing of the VFS code. By overriding +** by some VFSes to facilitate testing of the VFS code. By overriding ** system calls with functions under its control, a test program can ** simulate faults and error conditions that would otherwise be difficult ** or impossible to induce. The set of system calls that can be overridden @@ -2384,7 +1838,7 @@ struct sqlite3_vfs { sqlite3_vfs *pNext; /* Next registered VFS */ const char *zName; /* Name of this virtual file system */ void *pAppData; /* Pointer to application-specific data */ - int (*xOpen)(sqlite3_vfs*, const char *zName, sqlite3_file*, + int (*xOpen)(sqlite3_vfs*, sqlite3_filename zName, sqlite3_file*, int flags, int *pOutFlags); int (*xDelete)(sqlite3_vfs*, const char *zName, int syncDir); int (*xAccess)(sqlite3_vfs*, const char *zName, int flags, int *pResOut); @@ -2412,7 +1866,7 @@ struct sqlite3_vfs { /* ** The methods above are in versions 1 through 3 of the sqlite_vfs object. ** New fields may be appended in future versions. The iVersion - ** value will increment whenever this happens. + ** value will increment whenever this happens. */ }; @@ -2456,7 +1910,7 @@ struct sqlite3_vfs { ** ** ** When unlocking, the same SHARED or EXCLUSIVE flag must be supplied as -** was given on the corresponding lock. +** was given on the corresponding lock. ** ** The xShmLock method can transition between unlocked and SHARED or ** between unlocked and EXCLUSIVE. It cannot transition between SHARED @@ -2519,7 +1973,7 @@ struct sqlite3_vfs { ** SQLite interfaces so that an application usually does not need to ** invoke sqlite3_initialize() directly. For example, [sqlite3_open()] ** calls sqlite3_initialize() so the SQLite library will be automatically -** initialized when [sqlite3_open()] is called if it has not be initialized +** initialized when [sqlite3_open()] is called if it has not been initialized ** already. ^However, if SQLite is compiled with the [SQLITE_OMIT_AUTOINIT] ** compile-time option, then the automatic calls to sqlite3_initialize() ** are omitted and the application must call sqlite3_initialize() directly @@ -2571,20 +2025,24 @@ SQLITE_API int sqlite3_os_end(void); ** must ensure that no other SQLite interfaces are invoked by other ** threads while sqlite3_config() is running. ** -** The sqlite3_config() interface -** may only be invoked prior to library initialization using -** [sqlite3_initialize()] or after shutdown by [sqlite3_shutdown()]. -** ^If sqlite3_config() is called after [sqlite3_initialize()] and before -** [sqlite3_shutdown()] then it will return SQLITE_MISUSE. -** Note, however, that ^sqlite3_config() can be called as part of the -** implementation of an application-defined [sqlite3_os_init()]. -** ** The first argument to sqlite3_config() is an integer ** [configuration option] that determines ** what property of SQLite is to be configured. Subsequent arguments ** vary depending on the [configuration option] ** in the first argument. ** +** For most configuration options, the sqlite3_config() interface +** may only be invoked prior to library initialization using +** [sqlite3_initialize()] or after shutdown by [sqlite3_shutdown()]. +** The exceptional configuration options that may be invoked at any time +** are called "anytime configuration options". +** ^If sqlite3_config() is called after [sqlite3_initialize()] and before +** [sqlite3_shutdown()] with a first argument that is not an anytime +** configuration option, then the sqlite3_config() call will +** return SQLITE_MISUSE. +** Note, however, that ^sqlite3_config() can be called as part of the +** implementation of an application-defined [sqlite3_os_init()]. +** ** ^When a configuration option is set, sqlite3_config() returns [SQLITE_OK]. ** ^If the option is unknown or SQLite is unable to set the option ** then this routine returns a non-zero [error code]. @@ -2601,7 +2059,7 @@ SQLITE_API int sqlite3_config(int, ...); ** [database connection] (specified in the first argument). ** ** The second argument to sqlite3_db_config(D,V,...) is the -** [SQLITE_DBCONFIG_LOOKASIDE | configuration verb] - an integer code +** [SQLITE_DBCONFIG_LOOKASIDE | configuration verb] - an integer code ** that indicates what aspect of the [database connection] is being configured. ** Subsequent arguments vary depending on the configuration verb. ** @@ -2619,7 +2077,7 @@ SQLITE_API int sqlite3_db_config(sqlite3*, int op, ...); ** This object is used in only one place in the SQLite interface. ** A pointer to an instance of this object is the argument to ** [sqlite3_config()] when the configuration option is -** [SQLITE_CONFIG_MALLOC] or [SQLITE_CONFIG_GETMALLOC]. +** [SQLITE_CONFIG_MALLOC] or [SQLITE_CONFIG_GETMALLOC]. ** By creating an instance of this object ** and passing it to [sqlite3_config]([SQLITE_CONFIG_MALLOC]) ** during configuration, an application can specify an alternative @@ -2649,17 +2107,17 @@ SQLITE_API int sqlite3_db_config(sqlite3*, int op, ...); ** allocators round up memory allocations at least to the next multiple ** of 8. Some allocators round up to a larger multiple or to a power of 2. ** Every memory allocation request coming in through [sqlite3_malloc()] -** or [sqlite3_realloc()] first calls xRoundup. If xRoundup returns 0, +** or [sqlite3_realloc()] first calls xRoundup. If xRoundup returns 0, ** that causes the corresponding memory allocation to fail. ** ** The xInit method initializes the memory allocator. For example, -** it might allocate any require mutexes or initialize internal data +** it might allocate any required mutexes or initialize internal data ** structures. The xShutdown method is invoked (indirectly) by ** [sqlite3_shutdown()] and should deallocate any resources acquired ** by xInit. The pAppData pointer is used as the only parameter to ** xInit and xShutdown. ** -** SQLite holds the [SQLITE_MUTEX_STATIC_MASTER] mutex when it invokes +** SQLite holds the [SQLITE_MUTEX_STATIC_MAIN] mutex when it invokes ** the xInit method, so the xInit method need not be threadsafe. The ** xShutdown method is only called from [sqlite3_shutdown()] so it does ** not need to be threadsafe either. For all other methods, SQLite @@ -2692,6 +2150,23 @@ struct sqlite3_mem_methods { ** These constants are the available integer configuration options that ** can be passed as the first argument to the [sqlite3_config()] interface. ** +** Most of the configuration options for sqlite3_config() +** will only work if invoked prior to [sqlite3_initialize()] or after +** [sqlite3_shutdown()]. The few exceptions to this rule are called +** "anytime configuration options". +** ^Calling [sqlite3_config()] with a first argument that is not an +** anytime configuration option in between calls to [sqlite3_initialize()] and +** [sqlite3_shutdown()] is a no-op that returns SQLITE_MISUSE. +** +** The set of anytime configuration options can change (by insertions +** and/or deletions) from one release of SQLite to the next. +** As of SQLite version 3.42.0, the complete set of anytime configuration +** options is: +**
      +**
    • SQLITE_CONFIG_LOG +**
    • SQLITE_CONFIG_PCACHE_HDRSZ +**
    +** ** New configuration options may be added in future releases of SQLite. ** Existing configuration options might be discontinued. Applications ** should check the return code from [sqlite3_config()] to make sure that @@ -2707,7 +2182,7 @@ struct sqlite3_mem_methods { ** by a single thread. ^If SQLite is compiled with ** the [SQLITE_THREADSAFE | SQLITE_THREADSAFE=0] compile-time option then ** it is not possible to change the [threading mode] from its default -** value of Single-thread and so [sqlite3_config()] will return +** value of Single-thread and so [sqlite3_config()] will return ** [SQLITE_ERROR] if called with the SQLITE_CONFIG_SINGLETHREAD ** configuration option. ** @@ -2742,7 +2217,7 @@ struct sqlite3_mem_methods { ** SQLITE_CONFIG_SERIALIZED configuration option. ** ** [[SQLITE_CONFIG_MALLOC]]
    SQLITE_CONFIG_MALLOC
    -**
    ^(The SQLITE_CONFIG_MALLOC option takes a single argument which is +**
    ^(The SQLITE_CONFIG_MALLOC option takes a single argument which is ** a pointer to an instance of the [sqlite3_mem_methods] structure. ** The argument specifies ** alternative low-level memory allocation routines to be used in place of @@ -2756,25 +2231,26 @@ struct sqlite3_mem_methods { ** The [sqlite3_mem_methods] ** structure is filled with the currently defined memory allocation routines.)^ ** This option can be used to overload the default memory allocation -** routines with a wrapper that simulations memory allocation failure or +** routines with a wrapper that simulates memory allocation failure or ** tracks memory usage, for example.
    ** ** [[SQLITE_CONFIG_SMALL_MALLOC]]
    SQLITE_CONFIG_SMALL_MALLOC
    -**
    ^The SQLITE_CONFIG_SMALL_MALLOC option takes single argument of +**
    ^The SQLITE_CONFIG_SMALL_MALLOC option takes a single argument of ** type int, interpreted as a boolean, which if true provides a hint to ** SQLite that it should avoid large memory allocations if possible. ** SQLite will run faster if it is free to make large memory allocations, -** but some application might prefer to run slower in exchange for +** but some applications might prefer to run slower in exchange for ** guarantees about memory fragmentation that are possible if large ** allocations are avoided. This hint is normally off. **
    ** ** [[SQLITE_CONFIG_MEMSTATUS]]
    SQLITE_CONFIG_MEMSTATUS
    -**
    ^The SQLITE_CONFIG_MEMSTATUS option takes single argument of type int, +**
    ^The SQLITE_CONFIG_MEMSTATUS option takes a single argument of type int, ** interpreted as a boolean, which enables or disables the collection of ** memory allocation statistics. ^(When memory allocation statistics are ** disabled, the following SQLite interfaces become non-operational: **
      +**
    • [sqlite3_hard_heap_limit64()] **
    • [sqlite3_memory_used()] **
    • [sqlite3_memory_highwater()] **
    • [sqlite3_soft_heap_limit64()] @@ -2792,8 +2268,8 @@ struct sqlite3_mem_methods { ** [[SQLITE_CONFIG_PAGECACHE]]
      SQLITE_CONFIG_PAGECACHE
      **
      ^The SQLITE_CONFIG_PAGECACHE option specifies a memory pool ** that SQLite can use for the database page cache with the default page -** cache implementation. -** This configuration option is a no-op if an application-define page +** cache implementation. +** This configuration option is a no-op if an application-defined page ** cache implementation is loaded using the [SQLITE_CONFIG_PCACHE2]. ** ^There are three arguments to SQLITE_CONFIG_PAGECACHE: A pointer to ** 8-byte aligned memory (pMem), the size of each page cache line (sz), @@ -2814,13 +2290,13 @@ struct sqlite3_mem_methods { ** ^If pMem is NULL and N is non-zero, then each database connection ** does an initial bulk allocation for page cache memory ** from [sqlite3_malloc()] sufficient for N cache lines if N is positive or -** of -1024*N bytes if N is negative, . ^If additional +** of -1024*N bytes if N is negative. ^If additional ** page cache memory is needed beyond what is provided by the initial ** allocation, then SQLite goes to [sqlite3_malloc()] separately for each ** additional cache line.
      ** ** [[SQLITE_CONFIG_HEAP]]
      SQLITE_CONFIG_HEAP
      -**
      ^The SQLITE_CONFIG_HEAP option specifies a static memory buffer +**
      ^The SQLITE_CONFIG_HEAP option specifies a static memory buffer ** that SQLite will use for all of its dynamic memory allocation needs ** beyond those provided for by [SQLITE_CONFIG_PAGECACHE]. ** ^The SQLITE_CONFIG_HEAP option is only available if SQLite is compiled @@ -2843,7 +2319,7 @@ struct sqlite3_mem_methods { **
      ^(The SQLITE_CONFIG_MUTEX option takes a single argument which is a ** pointer to an instance of the [sqlite3_mutex_methods] structure. ** The argument specifies alternative low-level mutex routines to be used -** in place the mutex routines built into SQLite.)^ ^SQLite makes a copy of +** in place of the mutex routines built into SQLite.)^ ^SQLite makes a copy of ** the content of the [sqlite3_mutex_methods] structure before the call to ** [sqlite3_config()] returns. ^If SQLite is compiled with ** the [SQLITE_THREADSAFE | SQLITE_THREADSAFE=0] compile-time option then @@ -2866,30 +2342,33 @@ struct sqlite3_mem_methods { ** ** [[SQLITE_CONFIG_LOOKASIDE]]
      SQLITE_CONFIG_LOOKASIDE
      **
      ^(The SQLITE_CONFIG_LOOKASIDE option takes two arguments that determine -** the default size of lookaside memory on each [database connection]. +** the default size of [lookaside memory] on each [database connection]. ** The first argument is the -** size of each lookaside buffer slot and the second is the number of -** slots allocated to each database connection.)^ ^(SQLITE_CONFIG_LOOKASIDE -** sets the default lookaside size. The [SQLITE_DBCONFIG_LOOKASIDE] -** option to [sqlite3_db_config()] can be used to change the lookaside -** configuration on individual connections.)^
      +** size of each lookaside buffer slot ("sz") and the second is the number of +** slots allocated to each database connection ("cnt").)^ +** ^(SQLITE_CONFIG_LOOKASIDE sets the default lookaside size. +** The [SQLITE_DBCONFIG_LOOKASIDE] option to [sqlite3_db_config()] can +** be used to change the lookaside configuration on individual connections.)^ +** The [-DSQLITE_DEFAULT_LOOKASIDE] option can be used to change the +** default lookaside configuration at compile-time. +**
    ** ** [[SQLITE_CONFIG_PCACHE2]]
    SQLITE_CONFIG_PCACHE2
    -**
    ^(The SQLITE_CONFIG_PCACHE2 option takes a single argument which is +**
    ^(The SQLITE_CONFIG_PCACHE2 option takes a single argument which is ** a pointer to an [sqlite3_pcache_methods2] object. This object specifies ** the interface to a custom page cache implementation.)^ ** ^SQLite makes a copy of the [sqlite3_pcache_methods2] object.
    ** ** [[SQLITE_CONFIG_GETPCACHE2]]
    SQLITE_CONFIG_GETPCACHE2
    **
    ^(The SQLITE_CONFIG_GETPCACHE2 option takes a single argument which -** is a pointer to an [sqlite3_pcache_methods2] object. SQLite copies of +** is a pointer to an [sqlite3_pcache_methods2] object. SQLite copies off ** the current page cache implementation into that object.)^
    ** ** [[SQLITE_CONFIG_LOG]]
    SQLITE_CONFIG_LOG
    **
    The SQLITE_CONFIG_LOG option is used to configure the SQLite ** global [error log]. ** (^The SQLITE_CONFIG_LOG option takes two arguments: a pointer to a -** function with a call signature of void(*)(void*,int,const char*), +** function with a call signature of void(*)(void*,int,const char*), ** and a pointer to void. ^If the function pointer is not NULL, it is ** invoked by [sqlite3_log()] to process each logging event. ^If the ** function pointer is NULL, the [sqlite3_log()] interface becomes a no-op. @@ -2899,7 +2378,7 @@ struct sqlite3_mem_methods { ** the logger function is a copy of the first parameter to the corresponding ** [sqlite3_log()] call and is intended to be a [result code] or an ** [extended result code]. ^The third parameter passed to the logger is -** log message after formatting via [sqlite3_snprintf()]. +** a log message after formatting via [sqlite3_snprintf()]. ** The SQLite logging interface is not reentrant; the logger function ** supplied by the application must not invoke any SQLite interface. ** In a multi-threaded application, the application-defined logger @@ -2998,7 +2477,7 @@ struct sqlite3_mem_methods { ** [[SQLITE_CONFIG_STMTJRNL_SPILL]] **
    SQLITE_CONFIG_STMTJRNL_SPILL **
    ^The SQLITE_CONFIG_STMTJRNL_SPILL option takes a single parameter which -** becomes the [statement journal] spill-to-disk threshold. +** becomes the [statement journal] spill-to-disk threshold. ** [Statement journals] are held in memory until their size (in bytes) ** exceeds this threshold, at which point they are written to disk. ** Or if the threshold is -1, statement journals are always held @@ -3020,8 +2499,8 @@ struct sqlite3_mem_methods { ** than the configured sorter-reference size threshold - then a reference ** is stored in each sorted record and the required column values loaded ** from the database as records are returned in sorted order. The default -** value for this option is to never use this optimization. Specifying a -** negative value for this option restores the default behaviour. +** value for this option is to never use this optimization. Specifying a +** negative value for this option restores the default behavior. ** This option is only available if SQLite is compiled with the ** [SQLITE_ENABLE_SORTER_REFERENCES] compile-time option. ** @@ -3035,30 +2514,46 @@ struct sqlite3_mem_methods { ** configuration setting is never used, then the default maximum is determined ** by the [SQLITE_MEMDB_DEFAULT_MAXSIZE] compile-time option. If that ** compile-time option is not set, then the default maximum is 1073741824. +** +** [[SQLITE_CONFIG_ROWID_IN_VIEW]] +**
    SQLITE_CONFIG_ROWID_IN_VIEW +**
    The SQLITE_CONFIG_ROWID_IN_VIEW option enables or disables the ability +** for VIEWs to have a ROWID. The capability can only be enabled if SQLite is +** compiled with -DSQLITE_ALLOW_ROWID_IN_VIEW, in which case the capability +** defaults to on. This configuration option queries the current setting or +** changes the setting to off or on. The argument is a pointer to an integer. +** If that integer initially holds a value of 1, then the ability for VIEWs to +** have ROWIDs is activated. If the integer initially holds zero, then the +** ability is deactivated. Any other initial value for the integer leaves the +** setting unchanged. After changes, if any, the integer is written with +** a 1 or 0, if the ability for VIEWs to have ROWIDs is on or off. If SQLite +** is compiled without -DSQLITE_ALLOW_ROWID_IN_VIEW (which is the usual and +** recommended case) then the integer is always filled with zero, regardless +** if its initial value. ** */ -#define SQLITE_CONFIG_SINGLETHREAD 1 /* nil */ -#define SQLITE_CONFIG_MULTITHREAD 2 /* nil */ -#define SQLITE_CONFIG_SERIALIZED 3 /* nil */ -#define SQLITE_CONFIG_MALLOC 4 /* sqlite3_mem_methods* */ -#define SQLITE_CONFIG_GETMALLOC 5 /* sqlite3_mem_methods* */ -#define SQLITE_CONFIG_SCRATCH 6 /* No longer used */ -#define SQLITE_CONFIG_PAGECACHE 7 /* void*, int sz, int N */ -#define SQLITE_CONFIG_HEAP 8 /* void*, int nByte, int min */ -#define SQLITE_CONFIG_MEMSTATUS 9 /* boolean */ -#define SQLITE_CONFIG_MUTEX 10 /* sqlite3_mutex_methods* */ -#define SQLITE_CONFIG_GETMUTEX 11 /* sqlite3_mutex_methods* */ -/* previously SQLITE_CONFIG_CHUNKALLOC 12 which is now unused. */ -#define SQLITE_CONFIG_LOOKASIDE 13 /* int int */ -#define SQLITE_CONFIG_PCACHE 14 /* no-op */ -#define SQLITE_CONFIG_GETPCACHE 15 /* no-op */ -#define SQLITE_CONFIG_LOG 16 /* xFunc, void* */ -#define SQLITE_CONFIG_URI 17 /* int */ -#define SQLITE_CONFIG_PCACHE2 18 /* sqlite3_pcache_methods2* */ -#define SQLITE_CONFIG_GETPCACHE2 19 /* sqlite3_pcache_methods2* */ +#define SQLITE_CONFIG_SINGLETHREAD 1 /* nil */ +#define SQLITE_CONFIG_MULTITHREAD 2 /* nil */ +#define SQLITE_CONFIG_SERIALIZED 3 /* nil */ +#define SQLITE_CONFIG_MALLOC 4 /* sqlite3_mem_methods* */ +#define SQLITE_CONFIG_GETMALLOC 5 /* sqlite3_mem_methods* */ +#define SQLITE_CONFIG_SCRATCH 6 /* No longer used */ +#define SQLITE_CONFIG_PAGECACHE 7 /* void*, int sz, int N */ +#define SQLITE_CONFIG_HEAP 8 /* void*, int nByte, int min */ +#define SQLITE_CONFIG_MEMSTATUS 9 /* boolean */ +#define SQLITE_CONFIG_MUTEX 10 /* sqlite3_mutex_methods* */ +#define SQLITE_CONFIG_GETMUTEX 11 /* sqlite3_mutex_methods* */ +/* previously SQLITE_CONFIG_CHUNKALLOC 12 which is now unused. */ +#define SQLITE_CONFIG_LOOKASIDE 13 /* int int */ +#define SQLITE_CONFIG_PCACHE 14 /* no-op */ +#define SQLITE_CONFIG_GETPCACHE 15 /* no-op */ +#define SQLITE_CONFIG_LOG 16 /* xFunc, void* */ +#define SQLITE_CONFIG_URI 17 /* int */ +#define SQLITE_CONFIG_PCACHE2 18 /* sqlite3_pcache_methods2* */ +#define SQLITE_CONFIG_GETPCACHE2 19 /* sqlite3_pcache_methods2* */ #define SQLITE_CONFIG_COVERING_INDEX_SCAN 20 /* int */ -#define SQLITE_CONFIG_SQLLOG 21 /* xSqllog, void* */ -#define SQLITE_CONFIG_MMAP_SIZE 22 /* sqlite3_int64, sqlite3_int64 */ +#define SQLITE_CONFIG_SQLLOG 21 /* xSqllog, void* */ +#define SQLITE_CONFIG_MMAP_SIZE 22 /* sqlite3_int64, sqlite3_int64 */ #define SQLITE_CONFIG_WIN32_HEAPSIZE 23 /* int nByte */ #define SQLITE_CONFIG_PCACHE_HDRSZ 24 /* int *psz */ #define SQLITE_CONFIG_PMASZ 25 /* unsigned int szPma */ @@ -3066,12 +2561,21 @@ struct sqlite3_mem_methods { #define SQLITE_CONFIG_SMALL_MALLOC 27 /* boolean */ #define SQLITE_CONFIG_SORTERREF_SIZE 28 /* int nByte */ #define SQLITE_CONFIG_MEMDB_MAXSIZE 29 /* sqlite3_int64 */ +#define SQLITE_CONFIG_ROWID_IN_VIEW 30 /* int* */ /* ** CAPI3REF: Database Connection Configuration Options ** ** These constants are the available integer configuration options that -** can be passed as the second argument to the [sqlite3_db_config()] interface. +** can be passed as the second parameter to the [sqlite3_db_config()] interface. +** +** The [sqlite3_db_config()] interface is a var-args function. It takes a +** variable number of parameters, though always at least two. The number of +** parameters passed into sqlite3_db_config() depends on which of these +** constants is given as the second parameter. This documentation page +** refers to parameters beyond the second as "arguments". Thus, when this +** page says "the N-th argument" it means "the N-th parameter past the +** configuration option" or "the (N+2)-th parameter to sqlite3_db_config()". ** ** New configuration options may be added in future releases of SQLite. ** Existing configuration options might be discontinued. Applications @@ -3083,31 +2587,58 @@ struct sqlite3_mem_methods { **
    ** [[SQLITE_DBCONFIG_LOOKASIDE]] **
    SQLITE_DBCONFIG_LOOKASIDE
    -**
    ^This option takes three additional arguments that determine the -** [lookaside memory allocator] configuration for the [database connection]. -** ^The first argument (the third parameter to [sqlite3_db_config()] is a +**
    The SQLITE_DBCONFIG_LOOKASIDE option is used to adjust the +** configuration of the [lookaside memory allocator] within a database +** connection. +** The arguments to the SQLITE_DBCONFIG_LOOKASIDE option are not +** in the [DBCONFIG arguments|usual format]. +** The SQLITE_DBCONFIG_LOOKASIDE option takes three arguments, not two, +** so that a call to [sqlite3_db_config()] that uses SQLITE_DBCONFIG_LOOKASIDE +** should have a total of five parameters. +**
      +**
    1. The first argument ("buf") is a ** pointer to a memory buffer to use for lookaside memory. -** ^The first argument after the SQLITE_DBCONFIG_LOOKASIDE verb -** may be NULL in which case SQLite will allocate the -** lookaside buffer itself using [sqlite3_malloc()]. ^The second argument is the -** size of each lookaside buffer slot. ^The third argument is the number of -** slots. The size of the buffer in the first argument must be greater than -** or equal to the product of the second and third arguments. The buffer -** must be aligned to an 8-byte boundary. ^If the second argument to -** SQLITE_DBCONFIG_LOOKASIDE is not a multiple of 8, it is internally -** rounded down to the next smaller multiple of 8. ^(The lookaside memory +** The first argument may be NULL in which case SQLite will allocate the +** lookaside buffer itself using [sqlite3_malloc()]. +**

    2. The second argument ("sz") is the +** size of each lookaside buffer slot. Lookaside is disabled if "sz" +** is less than 8. The "sz" argument should be a multiple of 8 less than +** 65536. If "sz" does not meet this constraint, it is reduced in size until +** it does. +**

    3. The third argument ("cnt") is the number of slots. +** Lookaside is disabled if "cnt"is less than 1. +* The "cnt" value will be reduced, if necessary, so +** that the product of "sz" and "cnt" does not exceed 2,147,418,112. The "cnt" +** parameter is usually chosen so that the product of "sz" and "cnt" is less +** than 1,000,000. +**

    +**

    If the "buf" argument is not NULL, then it must +** point to a memory buffer with a size that is greater than +** or equal to the product of "sz" and "cnt". +** The buffer must be aligned to an 8-byte boundary. +** The lookaside memory ** configuration for a database connection can only be changed when that ** connection is not currently using lookaside memory, or in other words -** when the "current value" returned by -** [sqlite3_db_status](D,[SQLITE_CONFIG_LOOKASIDE],...) is zero. +** when the value returned by [SQLITE_DBSTATUS_LOOKASIDE_USED] is zero. ** Any attempt to change the lookaside memory configuration when lookaside -** memory is in use leaves the configuration unchanged and returns -** [SQLITE_BUSY].)^

    +** memory is in use leaves the configuration unchanged and returns +** [SQLITE_BUSY]. +** If the "buf" argument is NULL and an attempt +** to allocate memory based on "sz" and "cnt" fails, then +** lookaside is silently disabled. +**

    +** The [SQLITE_CONFIG_LOOKASIDE] configuration option can be used to set the +** default lookaside configuration at initialization. The +** [-DSQLITE_DEFAULT_LOOKASIDE] option can be used to set the default lookaside +** configuration at compile-time. Typical values for lookaside are 1200 for +** "sz" and 40 to 100 for "cnt". +**

    ** ** [[SQLITE_DBCONFIG_ENABLE_FKEY]] **
    SQLITE_DBCONFIG_ENABLE_FKEY
    **
    ^This option is used to enable or disable the enforcement of -** [foreign key constraints]. There should be two additional arguments. +** [foreign key constraints]. This is the same setting that is +** enabled or disabled by the [PRAGMA foreign_keys] statement. ** The first argument is an integer which is 0 to disable FK enforcement, ** positive to enable FK enforcement or negative to leave FK enforcement ** unchanged. The second parameter is a pointer to an integer into which @@ -3124,21 +2655,47 @@ struct sqlite3_mem_methods { ** The second parameter is a pointer to an integer into which ** is written 0 or 1 to indicate whether triggers are disabled or enabled ** following this call. The second parameter may be a NULL pointer, in -** which case the trigger setting is not reported back.
    +** which case the trigger setting is not reported back. +** +**

    Originally this option disabled all triggers. ^(However, since +** SQLite version 3.35.0, TEMP triggers are still allowed even if +** this option is off. So, in other words, this option now only disables +** triggers in the main database schema or in the schemas of [ATTACH]-ed +** databases.)^ +** +** [[SQLITE_DBCONFIG_ENABLE_VIEW]] +**

    SQLITE_DBCONFIG_ENABLE_VIEW
    +**
    ^This option is used to enable or disable [CREATE VIEW | views]. +** There must be two additional arguments. +** The first argument is an integer which is 0 to disable views, +** positive to enable views or negative to leave the setting unchanged. +** The second parameter is a pointer to an integer into which +** is written 0 or 1 to indicate whether views are disabled or enabled +** following this call. The second parameter may be a NULL pointer, in +** which case the view setting is not reported back. +** +**

    Originally this option disabled all views. ^(However, since +** SQLite version 3.35.0, TEMP views are still allowed even if +** this option is off. So, in other words, this option now only disables +** views in the main database schema or in the schemas of ATTACH-ed +** databases.)^

    ** ** [[SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER]] **
    SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER
    -**
    ^This option is used to enable or disable the -** [fts3_tokenizer()] function which is part of the -** [FTS3] full-text search engine extension. -** There should be two additional arguments. -** The first argument is an integer which is 0 to disable fts3_tokenizer() or -** positive to enable fts3_tokenizer() or negative to leave the setting -** unchanged. -** The second parameter is a pointer to an integer into which -** is written 0 or 1 to indicate whether fts3_tokenizer is disabled or enabled -** following this call. The second parameter may be a NULL pointer, in -** which case the new setting is not reported back.
    +**
    ^This option is used to enable or disable using the +** [fts3_tokenizer()] function - part of the [FTS3] full-text search engine +** extension - without using bound parameters as the parameters. Doing so +** is disabled by default. There must be two additional arguments. The first +** argument is an integer. If it is passed 0, then using fts3_tokenizer() +** without bound parameters is disabled. If it is passed a positive value, +** then calling fts3_tokenizer without bound parameters is enabled. If it +** is passed a negative value, this setting is not modified - this can be +** used to query for the current setting. The second parameter is a pointer +** to an integer into which is written 0 or 1 to indicate the current value +** of this setting (after it is modified, if applicable). The second +** parameter may be a NULL pointer, in which case the value of the setting +** is not reported back. Refer to [FTS3] documentation for further details. +**
    ** ** [[SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION]] **
    SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION
    @@ -3146,12 +2703,12 @@ struct sqlite3_mem_methods { ** interface independently of the [load_extension()] SQL function. ** The [sqlite3_enable_load_extension()] API enables or disables both the ** C-API [sqlite3_load_extension()] and the SQL function [load_extension()]. -** There should be two additional arguments. +** There must be two additional arguments. ** When the first argument to this interface is 1, then only the C-API is ** enabled and the SQL function remains disabled. If the first argument to ** this interface is 0, then both the C-API and the SQL function are disabled. -** If the first argument is -1, then no changes are made to state of either the -** C-API or the SQL function. +** If the first argument is -1, then no changes are made to the state of either +** the C-API or the SQL function. ** The second parameter is a pointer to an integer into which ** is written 0 or 1 to indicate whether [sqlite3_load_extension()] interface ** is disabled or enabled following this call. The second parameter may @@ -3160,23 +2717,30 @@ struct sqlite3_mem_methods { ** ** [[SQLITE_DBCONFIG_MAINDBNAME]]
    SQLITE_DBCONFIG_MAINDBNAME
    **
    ^This option is used to change the name of the "main" database -** schema. ^The sole argument is a pointer to a constant UTF8 string -** which will become the new schema name in place of "main". ^SQLite -** does not make a copy of the new main schema name string, so the application -** must ensure that the argument passed into this DBCONFIG option is unchanged -** until after the database connection closes. +** schema. This option does not follow the +** [DBCONFIG arguments|usual SQLITE_DBCONFIG argument format]. +** This option takes exactly one additional argument so that the +** [sqlite3_db_config()] call has a total of three parameters. The +** extra argument must be a pointer to a constant UTF8 string which +** will become the new schema name in place of "main". ^SQLite does +** not make a copy of the new main schema name string, so the application +** must ensure that the argument passed into SQLITE_DBCONFIG MAINDBNAME +** is unchanged until after the database connection closes. **
    ** -** [[SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE]] +** [[SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE]] **
    SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE
    -**
    Usually, when a database in wal mode is closed or detached from a -** database handle, SQLite checks if this will mean that there are now no -** connections at all to the database. If so, it performs a checkpoint -** operation before closing the connection. This option may be used to -** override this behaviour. The first parameter passed to this operation -** is an integer - positive to disable checkpoints-on-close, or zero (the -** default) to enable them, and negative to leave the setting unchanged. -** The second parameter is a pointer to an integer +**
    Usually, when a database in [WAL mode] is closed or detached from a +** database handle, SQLite checks if if there are other connections to the +** same database, and if there are no other database connection (if the +** connection being closed is the last open connection to the database), +** then SQLite performs a [checkpoint] before closing the connection and +** deletes the WAL file. The SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE option can +** be used to override that behavior. The first argument passed to this +** operation (the third parameter to [sqlite3_db_config()]) is an integer +** which is positive to disable checkpoints-on-close, or zero (the default) +** to enable them, and negative to leave the setting unchanged. +** The second argument (the fourth parameter) is a pointer to an integer ** into which is written 0 or 1 to indicate whether checkpoints-on-close ** have been disabled - 0 if they are not disabled, 1 if they are. **
    @@ -3190,7 +2754,7 @@ struct sqlite3_mem_methods { ** slower. But the QPSG has the advantage of more predictable behavior. With ** the QPSG active, SQLite will always use the same query plan in the field as ** was used during testing in the lab. -** The first argument to this setting is an integer which is 0 to disable +** The first argument to this setting is an integer which is 0 to disable ** the QPSG, positive to enable QPSG, or negative to leave the setting ** unchanged. The second parameter is a pointer to an integer into which ** is written 0 or 1 to indicate whether the QPSG is disabled or enabled @@ -3198,15 +2762,15 @@ struct sqlite3_mem_methods { ** ** ** [[SQLITE_DBCONFIG_TRIGGER_EQP]]
    SQLITE_DBCONFIG_TRIGGER_EQP
    -**
    By default, the output of EXPLAIN QUERY PLAN commands does not +**
    By default, the output of EXPLAIN QUERY PLAN commands does not ** include output for any operations performed by trigger programs. This ** option is used to set or clear (the default) a flag that governs this ** behavior. The first parameter passed to this operation is an integer - ** positive to enable output for trigger programs, or zero to disable it, ** or negative to leave the setting unchanged. -** The second parameter is a pointer to an integer into which is written -** 0 or 1 to indicate whether output-for-triggers has been disabled - 0 if -** it is not disabled, 1 if it is. +** The second parameter is a pointer to an integer into which is written +** 0 or 1 to indicate whether output-for-triggers has been disabled - 0 if +** it is not disabled, 1 if it is. **
    ** ** [[SQLITE_DBCONFIG_RESET_DATABASE]]
    SQLITE_DBCONFIG_RESET_DATABASE
    @@ -3220,23 +2784,29 @@ struct sqlite3_mem_methods { ** database, or calling sqlite3_table_column_metadata(), ignoring any ** errors. This step is only necessary if the application desires to keep ** the database in WAL mode after the reset if it was in WAL mode before -** the reset. +** the reset. **
  • sqlite3_db_config(db, SQLITE_DBCONFIG_RESET_DATABASE, 1, 0); **
  • [sqlite3_exec](db, "[VACUUM]", 0, 0, 0); **
  • sqlite3_db_config(db, SQLITE_DBCONFIG_RESET_DATABASE, 0, 0); ** ** Because resetting a database is destructive and irreversible, the -** process requires the use of this obscure API and multiple steps to help -** ensure that it does not happen by accident. +** process requires the use of this obscure API and multiple steps to +** help ensure that it does not happen by accident. Because this +** feature must be capable of resetting corrupt databases, and +** shutting down virtual tables may require access to that corrupt +** storage, the library must abandon any installed virtual tables +** without calling their xDestroy() methods. ** ** [[SQLITE_DBCONFIG_DEFENSIVE]]
    SQLITE_DBCONFIG_DEFENSIVE
    **
    The SQLITE_DBCONFIG_DEFENSIVE option activates or deactivates the ** "defensive" flag for a database connection. When the defensive -** flag is enabled, language features that allow ordinary SQL to +** flag is enabled, language features that allow ordinary SQL to ** deliberately corrupt the database file are disabled. The disabled ** features include but are not limited to the following: **
      **
    • The [PRAGMA writable_schema=ON] statement. +**
    • The [PRAGMA journal_mode=OFF] statement. +**
    • The [PRAGMA schema_version=N] statement. **
    • Writes to the [sqlite_dbpage] virtual table. **
    • Direct writes to [shadow tables]. **
    @@ -3246,13 +2816,206 @@ struct sqlite3_mem_methods { **
    The SQLITE_DBCONFIG_WRITABLE_SCHEMA option activates or deactivates the ** "writable_schema" flag. This has the same effect and is logically equivalent ** to setting [PRAGMA writable_schema=ON] or [PRAGMA writable_schema=OFF]. -** The first argument to this setting is an integer which is 0 to disable +** The first argument to this setting is an integer which is 0 to disable ** the writable_schema, positive to enable writable_schema, or negative to ** leave the setting unchanged. The second parameter is a pointer to an ** integer into which is written 0 or 1 to indicate whether the writable_schema ** is enabled or disabled following this call. **
    +** +** [[SQLITE_DBCONFIG_LEGACY_ALTER_TABLE]] +**
    SQLITE_DBCONFIG_LEGACY_ALTER_TABLE
    +**
    The SQLITE_DBCONFIG_LEGACY_ALTER_TABLE option activates or deactivates +** the legacy behavior of the [ALTER TABLE RENAME] command such that it +** behaves as it did prior to [version 3.24.0] (2018-06-04). See the +** "Compatibility Notice" on the [ALTER TABLE RENAME documentation] for +** additional information. This feature can also be turned on and off +** using the [PRAGMA legacy_alter_table] statement. +**
    +** +** [[SQLITE_DBCONFIG_DQS_DML]] +**
    SQLITE_DBCONFIG_DQS_DML
    +**
    The SQLITE_DBCONFIG_DQS_DML option activates or deactivates +** the legacy [double-quoted string literal] misfeature for DML statements +** only, that is DELETE, INSERT, SELECT, and UPDATE statements. The +** default value of this setting is determined by the [-DSQLITE_DQS] +** compile-time option. +**
    +** +** [[SQLITE_DBCONFIG_DQS_DDL]] +**
    SQLITE_DBCONFIG_DQS_DDL
    +**
    The SQLITE_DBCONFIG_DQS option activates or deactivates +** the legacy [double-quoted string literal] misfeature for DDL statements, +** such as CREATE TABLE and CREATE INDEX. The +** default value of this setting is determined by the [-DSQLITE_DQS] +** compile-time option. +**
    +** +** [[SQLITE_DBCONFIG_TRUSTED_SCHEMA]] +**
    SQLITE_DBCONFIG_TRUSTED_SCHEMA
    +**
    The SQLITE_DBCONFIG_TRUSTED_SCHEMA option tells SQLite to +** assume that database schemas are untainted by malicious content. +** When the SQLITE_DBCONFIG_TRUSTED_SCHEMA option is disabled, SQLite +** takes additional defensive steps to protect the application from harm +** including: +**
      +**
    • Prohibit the use of SQL functions inside triggers, views, +** CHECK constraints, DEFAULT clauses, expression indexes, +** partial indexes, or generated columns +** unless those functions are tagged with [SQLITE_INNOCUOUS]. +**
    • Prohibit the use of virtual tables inside of triggers or views +** unless those virtual tables are tagged with [SQLITE_VTAB_INNOCUOUS]. +**
    +** This setting defaults to "on" for legacy compatibility, however +** all applications are advised to turn it off if possible. This setting +** can also be controlled using the [PRAGMA trusted_schema] statement. +**
    +** +** [[SQLITE_DBCONFIG_LEGACY_FILE_FORMAT]] +**
    SQLITE_DBCONFIG_LEGACY_FILE_FORMAT
    +**
    The SQLITE_DBCONFIG_LEGACY_FILE_FORMAT option activates or deactivates +** the legacy file format flag. When activated, this flag causes all newly +** created database files to have a schema format version number (the 4-byte +** integer found at offset 44 into the database header) of 1. This in turn +** means that the resulting database file will be readable and writable by +** any SQLite version back to 3.0.0 ([dateof:3.0.0]). Without this setting, +** newly created databases are generally not understandable by SQLite versions +** prior to 3.3.0 ([dateof:3.3.0]). As these words are written, there +** is now scarcely any need to generate database files that are compatible +** all the way back to version 3.0.0, and so this setting is of little +** practical use, but is provided so that SQLite can continue to claim the +** ability to generate new database files that are compatible with version +** 3.0.0. +**

    Note that when the SQLITE_DBCONFIG_LEGACY_FILE_FORMAT setting is on, +** the [VACUUM] command will fail with an obscure error when attempting to +** process a table with generated columns and a descending index. This is +** not considered a bug since SQLite versions 3.3.0 and earlier do not support +** either generated columns or descending indexes. +**

    +** +** [[SQLITE_DBCONFIG_STMT_SCANSTATUS]] +**
    SQLITE_DBCONFIG_STMT_SCANSTATUS
    +**
    The SQLITE_DBCONFIG_STMT_SCANSTATUS option is only useful in +** [SQLITE_ENABLE_STMT_SCANSTATUS] builds. In this case, it sets or clears +** a flag that enables collection of run-time performance statistics +** used by [sqlite3_stmt_scanstatus_v2()] and the [nexec and ncycle] +** columns of the [bytecode virtual table]. +** For statistics to be collected, the flag must be set on +** the database handle both when the SQL statement is +** [sqlite3_prepare|prepared] and when it is [sqlite3_step|stepped]. +** The flag is set (collection of statistics is enabled) by default. +**

    This option takes two arguments: an integer and a pointer to +** an integer. The first argument is 1, 0, or -1 to enable, disable, or +** leave unchanged the statement scanstatus option. If the second argument +** is not NULL, then the value of the statement scanstatus setting after +** processing the first argument is written into the integer that the second +** argument points to. +**

    +** +** [[SQLITE_DBCONFIG_REVERSE_SCANORDER]] +**
    SQLITE_DBCONFIG_REVERSE_SCANORDER
    +**
    The SQLITE_DBCONFIG_REVERSE_SCANORDER option changes the default order +** in which tables and indexes are scanned so that the scans start at the end +** and work toward the beginning rather than starting at the beginning and +** working toward the end. Setting SQLITE_DBCONFIG_REVERSE_SCANORDER is the +** same as setting [PRAGMA reverse_unordered_selects].

    This option takes +** two arguments which are an integer and a pointer to an integer. The first +** argument is 1, 0, or -1 to enable, disable, or leave unchanged the +** reverse scan order flag, respectively. If the second argument is not NULL, +** then 0 or 1 is written into the integer that the second argument points to +** depending on if the reverse scan order flag is set after processing the +** first argument. +**

    +** +** [[SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE]] +**
    SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE
    +**
    The SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE option enables or disables +** the ability of the [ATTACH DATABASE] SQL command to create a new database +** file if the database filed named in the ATTACH command does not already +** exist. This ability of ATTACH to create a new database is enabled by +** default. Applications can disable or reenable the ability for ATTACH to +** create new database files using this DBCONFIG option.

    +** This option takes two arguments which are an integer and a pointer +** to an integer. The first argument is 1, 0, or -1 to enable, disable, or +** leave unchanged the attach-create flag, respectively. If the second +** argument is not NULL, then 0 or 1 is written into the integer that the +** second argument points to depending on if the attach-create flag is set +** after processing the first argument. +**

    +** +** [[SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE]] +**
    SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE
    +**
    The SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE option enables or disables the +** ability of the [ATTACH DATABASE] SQL command to open a database for writing. +** This capability is enabled by default. Applications can disable or +** reenable this capability using the current DBCONFIG option. If +** this capability is disabled, the [ATTACH] command will still work, +** but the database will be opened read-only. If this option is disabled, +** then the ability to create a new database using [ATTACH] is also disabled, +** regardless of the value of the [SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE] +** option.

    +** This option takes two arguments which are an integer and a pointer +** to an integer. The first argument is 1, 0, or -1 to enable, disable, or +** leave unchanged the ability to ATTACH another database for writing, +** respectively. If the second argument is not NULL, then 0 or 1 is written +** into the integer to which the second argument points, depending on whether +** the ability to ATTACH a read/write database is enabled or disabled +** after processing the first argument. +**

    +** +** [[SQLITE_DBCONFIG_ENABLE_COMMENTS]] +**
    SQLITE_DBCONFIG_ENABLE_COMMENTS
    +**
    The SQLITE_DBCONFIG_ENABLE_COMMENTS option enables or disables the +** ability to include comments in SQL text. Comments are enabled by default. +** An application can disable or reenable comments in SQL text using this +** DBCONFIG option.

    +** This option takes two arguments which are an integer and a pointer +** to an integer. The first argument is 1, 0, or -1 to enable, disable, or +** leave unchanged the ability to use comments in SQL text, +** respectively. If the second argument is not NULL, then 0 or 1 is written +** into the integer that the second argument points to depending on if +** comments are allowed in SQL text after processing the first argument. +**

    +** +** [[SQLITE_DBCONFIG_FP_DIGITS]] +**
    SQLITE_DBCONFIG_FP_DIGITS
    +**
    The SQLITE_DBCONFIG_FP_DIGITS setting is a small integer that determines +** the number of significant digits that SQLite will attempt to preserve when +** converting floating point numbers (IEEE 754 "doubles") into text. The +** default value 17, as of SQLite version 3.52.0. The value was 15 in all +** prior versions.

    +** This option takes two arguments which are an integer and a pointer +** to an integer. The first argument is a small integer, between 3 and 23, or +** zero. The FP_DIGITS setting is changed to that small integer, or left +** unaltered if the first argument is zero or out of range. The second argument +** is a pointer to an integer. If the pointer is not NULL, then the value of +** the FP_DIGITS setting, after possibly being modified by the first +** arguments, is written into the integer to which the second argument points. +**

    +** ** +** +** [[DBCONFIG arguments]]

    Arguments To SQLITE_DBCONFIG Options

    +** +**

    Most of the SQLITE_DBCONFIG options take two arguments, so that the +** overall call to [sqlite3_db_config()] has a total of four parameters. +** The first argument (the third parameter to sqlite3_db_config()) is +** an integer. +** The second argument is a pointer to an integer. If the first argument is 1, +** then the option becomes enabled. If the first integer argument is 0, +** then the option is disabled. +** If the first argument is -1, then the option setting +** is unchanged. The second argument, the pointer to an integer, may be NULL. +** If the second argument is not NULL, then a value of 0 or 1 is written into +** the integer to which the second argument points, depending on whether the +** setting is disabled or enabled after applying any changes specified by +** the first argument. +** +**

    While most SQLITE_DBCONFIG options use the argument format +** described in the previous paragraph, the [SQLITE_DBCONFIG_MAINDBNAME], +** [SQLITE_DBCONFIG_LOOKASIDE], and [SQLITE_DBCONFIG_FP_DIGITS] options +** are different. See the documentation of those exceptional options for +** details. */ #define SQLITE_DBCONFIG_MAINDBNAME 1000 /* const char* */ #define SQLITE_DBCONFIG_LOOKASIDE 1001 /* void* int int */ @@ -3266,7 +3029,19 @@ struct sqlite3_mem_methods { #define SQLITE_DBCONFIG_RESET_DATABASE 1009 /* int int* */ #define SQLITE_DBCONFIG_DEFENSIVE 1010 /* int int* */ #define SQLITE_DBCONFIG_WRITABLE_SCHEMA 1011 /* int int* */ -#define SQLITE_DBCONFIG_MAX 1011 /* Largest DBCONFIG */ +#define SQLITE_DBCONFIG_LEGACY_ALTER_TABLE 1012 /* int int* */ +#define SQLITE_DBCONFIG_DQS_DML 1013 /* int int* */ +#define SQLITE_DBCONFIG_DQS_DDL 1014 /* int int* */ +#define SQLITE_DBCONFIG_ENABLE_VIEW 1015 /* int int* */ +#define SQLITE_DBCONFIG_LEGACY_FILE_FORMAT 1016 /* int int* */ +#define SQLITE_DBCONFIG_TRUSTED_SCHEMA 1017 /* int int* */ +#define SQLITE_DBCONFIG_STMT_SCANSTATUS 1018 /* int int* */ +#define SQLITE_DBCONFIG_REVERSE_SCANORDER 1019 /* int int* */ +#define SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE 1020 /* int int* */ +#define SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE 1021 /* int int* */ +#define SQLITE_DBCONFIG_ENABLE_COMMENTS 1022 /* int int* */ +#define SQLITE_DBCONFIG_FP_DIGITS 1023 /* int int* */ +#define SQLITE_DBCONFIG_MAX 1023 /* Largest DBCONFIG */ /* ** CAPI3REF: Enable Or Disable Extended Result Codes @@ -3293,8 +3068,8 @@ SQLITE_API int sqlite3_extended_result_codes(sqlite3*, int onoff); ** ^The sqlite3_last_insert_rowid(D) interface usually returns the [rowid] of ** the most recent successful [INSERT] into a rowid table or [virtual table] ** on database connection D. ^Inserts into [WITHOUT ROWID] tables are not -** recorded. ^If no successful [INSERT]s into rowid tables have ever occurred -** on the database connection D, then sqlite3_last_insert_rowid(D) returns +** recorded. ^If no successful [INSERT]s into rowid tables have ever occurred +** on the database connection D, then sqlite3_last_insert_rowid(D) returns ** zero. ** ** As well as being set automatically as rows are inserted into database @@ -3304,15 +3079,15 @@ SQLITE_API int sqlite3_extended_result_codes(sqlite3*, int onoff); ** Some virtual table implementations may INSERT rows into rowid tables as ** part of committing a transaction (e.g. to flush data accumulated in memory ** to disk). In this case subsequent calls to this function return the rowid -** associated with these internal INSERT operations, which leads to +** associated with these internal INSERT operations, which leads to ** unintuitive results. Virtual table implementations that do write to rowid -** tables in this way can avoid this problem by restoring the original -** rowid value using [sqlite3_set_last_insert_rowid()] before returning +** tables in this way can avoid this problem by restoring the original +** rowid value using [sqlite3_set_last_insert_rowid()] before returning ** control to the user. ** -** ^(If an [INSERT] occurs within a trigger then this routine will -** return the [rowid] of the inserted row as long as the trigger is -** running. Once the trigger program ends, the value returned +** ^(If an [INSERT] occurs within a trigger then this routine will +** return the [rowid] of the inserted row as long as the trigger is +** running. Once the trigger program ends, the value returned ** by this routine reverts to what it was before the trigger was fired.)^ ** ** ^An [INSERT] that fails due to a constraint violation is not a @@ -3345,7 +3120,7 @@ SQLITE_API sqlite3_int64 sqlite3_last_insert_rowid(sqlite3*); ** METHOD: sqlite3 ** ** The sqlite3_set_last_insert_rowid(D, R) method allows the application to -** set the value returned by calling sqlite3_last_insert_rowid(D) to R +** set the value returned by calling sqlite3_last_insert_rowid(D) to R ** without inserting a row into the database. */ SQLITE_API void sqlite3_set_last_insert_rowid(sqlite3*,sqlite3_int64); @@ -3354,44 +3129,51 @@ SQLITE_API void sqlite3_set_last_insert_rowid(sqlite3*,sqlite3_int64); ** CAPI3REF: Count The Number Of Rows Modified ** METHOD: sqlite3 ** -** ^This function returns the number of rows modified, inserted or +** ^These functions return the number of rows modified, inserted or ** deleted by the most recently completed INSERT, UPDATE or DELETE ** statement on the database connection specified by the only parameter. -** ^Executing any other type of SQL statement does not modify the value -** returned by this function. +** The two functions are identical except for the type of the return value +** and that if the number of rows modified by the most recent INSERT, UPDATE, +** or DELETE is greater than the maximum value supported by type "int", then +** the return value of sqlite3_changes() is undefined. ^Executing any other +** type of SQL statement does not modify the value returned by these functions. +** For the purposes of this interface, a CREATE TABLE AS SELECT statement +** does not count as an INSERT, UPDATE or DELETE statement and hence the rows +** added to the new table by the CREATE TABLE AS SELECT statement are not +** counted. ** ** ^Only changes made directly by the INSERT, UPDATE or DELETE statement are -** considered - auxiliary changes caused by [CREATE TRIGGER | triggers], +** considered - auxiliary changes caused by [CREATE TRIGGER | triggers], ** [foreign key actions] or [REPLACE] constraint resolution are not counted. -** -** Changes to a view that are intercepted by -** [INSTEAD OF trigger | INSTEAD OF triggers] are not counted. ^The value -** returned by sqlite3_changes() immediately after an INSERT, UPDATE or -** DELETE statement run on a view is always zero. Only changes made to real +** +** Changes to a view that are intercepted by +** [INSTEAD OF trigger | INSTEAD OF triggers] are not counted. ^The value +** returned by sqlite3_changes() immediately after an INSERT, UPDATE or +** DELETE statement run on a view is always zero. Only changes made to real ** tables are counted. ** ** Things are more complicated if the sqlite3_changes() function is ** executed while a trigger program is running. This may happen if the ** program uses the [changes() SQL function], or if some other callback ** function invokes sqlite3_changes() directly. Essentially: -** +** **

      **
    • ^(Before entering a trigger program the value returned by -** sqlite3_changes() function is saved. After the trigger program +** sqlite3_changes() function is saved. After the trigger program ** has finished, the original value is restored.)^ -** -**
    • ^(Within a trigger program each INSERT, UPDATE and DELETE -** statement sets the value returned by sqlite3_changes() -** upon completion as normal. Of course, this value will not include -** any changes performed by sub-triggers, as the sqlite3_changes() +** +**
    • ^(Within a trigger program each INSERT, UPDATE and DELETE +** statement sets the value returned by sqlite3_changes() +** upon completion as normal. Of course, this value will not include +** any changes performed by sub-triggers, as the sqlite3_changes() ** value will be saved and restored after each sub-trigger has run.)^ **
    -** +** ** ^This means that if the changes() SQL function (or similar) is used -** by the first INSERT, UPDATE or DELETE statement within a trigger, it +** by the first INSERT, UPDATE or DELETE statement within a trigger, it ** returns the value as set when the calling statement began executing. -** ^If it is used by the second or subsequent such statement within a trigger -** program, the value returned reflects the number of rows modified by the +** ^If it is used by the second or subsequent such statement within a trigger +** program, the value returned reflects the number of rows modified by the ** previous INSERT, UPDATE or DELETE statement within the same trigger. ** ** If a separate thread makes changes on the same database connection @@ -3407,20 +3189,25 @@ SQLITE_API void sqlite3_set_last_insert_rowid(sqlite3*,sqlite3_int64); ** */ SQLITE_API int sqlite3_changes(sqlite3*); +SQLITE_API sqlite3_int64 sqlite3_changes64(sqlite3*); /* ** CAPI3REF: Total Number Of Rows Modified ** METHOD: sqlite3 ** -** ^This function returns the total number of rows inserted, modified or +** ^These functions return the total number of rows inserted, modified or ** deleted by all [INSERT], [UPDATE] or [DELETE] statements completed ** since the database connection was opened, including those executed as -** part of trigger programs. ^Executing any other type of SQL statement -** does not affect the value returned by sqlite3_total_changes(). -** +** part of trigger programs. The two functions are identical except for the +** type of the return value and that if the number of rows modified by the +** connection exceeds the maximum value supported by type "int", then +** the return value of sqlite3_total_changes() is undefined. ^Executing +** any other type of SQL statement does not affect the value returned by +** sqlite3_total_changes(). +** ** ^Changes made as part of [foreign key actions] are included in the ** count, but those made as part of REPLACE constraint resolution are -** not. ^Changes to a view that are intercepted by INSTEAD OF triggers +** not. ^Changes to a view that are intercepted by INSTEAD OF triggers ** are not counted. ** ** The [sqlite3_total_changes(D)] interface only reports the number @@ -3429,7 +3216,7 @@ SQLITE_API int sqlite3_changes(sqlite3*); ** To detect changes against a database file from other database ** connections use the [PRAGMA data_version] command or the ** [SQLITE_FCNTL_DATA_VERSION] [file control]. -** +** ** If a separate thread makes changes on the same database connection ** while [sqlite3_total_changes()] is running then the value ** returned is unpredictable and not meaningful. @@ -3444,6 +3231,7 @@ SQLITE_API int sqlite3_changes(sqlite3*); ** */ SQLITE_API int sqlite3_total_changes(sqlite3*); +SQLITE_API sqlite3_int64 sqlite3_total_changes64(sqlite3*); /* ** CAPI3REF: Interrupt A Long-Running Query @@ -3471,16 +3259,21 @@ SQLITE_API int sqlite3_total_changes(sqlite3*); ** ** ^The sqlite3_interrupt(D) call is in effect until all currently running ** SQL statements on [database connection] D complete. ^Any new SQL statements -** that are started after the sqlite3_interrupt() call and before the -** running statements reaches zero are interrupted as if they had been +** that are started after the sqlite3_interrupt() call and before the +** running statement count reaches zero are interrupted as if they had been ** running prior to the sqlite3_interrupt() call. ^New SQL statements ** that are started after the running statement count reaches zero are ** not effected by the sqlite3_interrupt(). ** ^A call to sqlite3_interrupt(D) that occurs when there are no running ** SQL statements is a no-op and has no effect on SQL statements ** that are started after the sqlite3_interrupt() call returns. +** +** ^The [sqlite3_is_interrupted(D)] interface can be used to determine whether +** or not an interrupt is currently in effect for [database connection] D. +** It returns 1 if an interrupt is currently in effect, or 0 otherwise. */ SQLITE_API void sqlite3_interrupt(sqlite3*); +SQLITE_API int sqlite3_is_interrupted(sqlite3*); /* ** CAPI3REF: Determine If An SQL Statement Is Complete @@ -3500,10 +3293,10 @@ SQLITE_API void sqlite3_interrupt(sqlite3*); ** ^These routines return 0 if the statement is incomplete. ^If a ** memory allocation fails, then SQLITE_NOMEM is returned. ** -** ^These routines do not parse the SQL statements thus +** ^These routines do not parse the SQL statements and thus ** will not detect syntactically incorrect SQL. ** -** ^(If SQLite has not been initialized using [sqlite3_initialize()] prior +** ^(If SQLite has not been initialized using [sqlite3_initialize()] prior ** to invoking sqlite3_complete16() then sqlite3_initialize() is invoked ** automatically by sqlite3_complete16(). If that initialization fails, ** then the return value from sqlite3_complete16() will be non-zero @@ -3548,7 +3341,7 @@ SQLITE_API int sqlite3_complete16(const void *sql); ** The presence of a busy handler does not guarantee that it will be invoked ** when there is lock contention. ^If SQLite determines that invoking the busy ** handler could result in a deadlock, it will go ahead and return [SQLITE_BUSY] -** to the application instead of invoking the +** to the application instead of invoking the ** busy handler. ** Consider a scenario where one process is holding a read lock that ** it is trying to promote to a reserved lock and @@ -3573,7 +3366,7 @@ SQLITE_API int sqlite3_complete16(const void *sql); ** database connection that invoked the busy handler. In other words, ** the busy handler is not reentrant. Any such actions ** result in undefined behavior. -** +** ** A busy handler must not close the database connection ** or [prepared statement] that invoked the busy handler. */ @@ -3602,6 +3395,44 @@ SQLITE_API int sqlite3_busy_handler(sqlite3*,int(*)(void*,int),void*); */ SQLITE_API int sqlite3_busy_timeout(sqlite3*, int ms); +/* +** CAPI3REF: Set the Setlk Timeout +** METHOD: sqlite3 +** +** This routine is only useful in SQLITE_ENABLE_SETLK_TIMEOUT builds. If +** the VFS supports blocking locks, it sets the timeout in ms used by +** eligible locks taken on wal mode databases by the specified database +** handle. In non-SQLITE_ENABLE_SETLK_TIMEOUT builds, or if the VFS does +** not support blocking locks, this function is a no-op. +** +** Passing 0 to this function disables blocking locks altogether. Passing +** -1 to this function requests that the VFS blocks for a long time - +** indefinitely if possible. The results of passing any other negative value +** are undefined. +** +** Internally, each SQLite database handle stores two timeout values - the +** busy-timeout (used for rollback mode databases, or if the VFS does not +** support blocking locks) and the setlk-timeout (used for blocking locks +** on wal-mode databases). The sqlite3_busy_timeout() method sets both +** values, this function sets only the setlk-timeout value. Therefore, +** to configure separate busy-timeout and setlk-timeout values for a single +** database handle, call sqlite3_busy_timeout() followed by this function. +** +** Whenever the number of connections to a wal mode database falls from +** 1 to 0, the last connection takes an exclusive lock on the database, +** then checkpoints and deletes the wal file. While it is doing this, any +** new connection that tries to read from the database fails with an +** SQLITE_BUSY error. Or, if the SQLITE_SETLK_BLOCK_ON_CONNECT flag is +** passed to this API, the new connection blocks until the exclusive lock +** has been released. +*/ +SQLITE_API int sqlite3_setlk_timeout(sqlite3*, int ms, int flags); + +/* +** CAPI3REF: Flags for sqlite3_setlk_timeout() +*/ +#define SQLITE_SETLK_BLOCK_ON_CONNECT 0x01 + /* ** CAPI3REF: Convenience Routines For Running Queries ** METHOD: sqlite3 @@ -3609,7 +3440,7 @@ SQLITE_API int sqlite3_busy_timeout(sqlite3*, int ms); ** This is a legacy interface that is preserved for backwards compatibility. ** Use of this interface is not recommended. ** -** Definition: A result table is memory data structure created by the +** Definition: A result table is a memory data structure created by the ** [sqlite3_get_table()] interface. A result table records the ** complete query results from one or more queries. ** @@ -3640,9 +3471,9 @@ SQLITE_API int sqlite3_busy_timeout(sqlite3*, int ms); ** Cindy | 21 ** ** -** There are two column (M==2) and three rows (N==3). Thus the +** There are two columns (M==2) and three rows (N==3). Thus the ** result table has 8 entries. Suppose the result table is stored -** in an array names azResult. Then azResult holds this content: +** in an array named azResult. Then azResult holds this content: ** **
     **        azResult[0] = "Name";
    @@ -3691,7 +3522,7 @@ SQLITE_API void sqlite3_free_table(char **result);
     ** These routines are work-alikes of the "printf()" family of functions
     ** from the standard C library.
     ** These routines understand most of the common formatting options from
    -** the standard library printf() 
    +** the standard library printf()
     ** plus some additional non-standard formats ([%q], [%Q], [%w], and [%z]).
     ** See the [built-in printf()] documentation for details.
     **
    @@ -3735,7 +3566,7 @@ SQLITE_API char *sqlite3_vsnprintf(int,char*,const char*, va_list);
     **
     ** The SQLite core uses these three routines for all of its own
     ** internal memory allocation needs. "Core" in the previous sentence
    -** does not include operating-system specific VFS implementation.  The
    +** does not include operating-system specific [VFS] implementation.  The
     ** Windows VFS uses native malloc() and free() for some operations.
     **
     ** ^The sqlite3_malloc() routine returns a pointer to a block
    @@ -3752,7 +3583,7 @@ SQLITE_API char *sqlite3_vsnprintf(int,char*,const char*, va_list);
     ** ^Calling sqlite3_free() with a pointer previously returned
     ** by sqlite3_malloc() or sqlite3_realloc() releases that memory so
     ** that it might be reused.  ^The sqlite3_free() routine is
    -** a no-op if is called with a NULL pointer.  Passing a NULL pointer
    +** a no-op if it is called with a NULL pointer.  Passing a NULL pointer
     ** to sqlite3_free() is harmless.  After being freed, memory
     ** should neither be read nor written.  Even reading previously freed
     ** memory might result in a segmentation fault or other severe error.
    @@ -3770,13 +3601,13 @@ SQLITE_API char *sqlite3_vsnprintf(int,char*,const char*, va_list);
     ** sqlite3_free(X).
     ** ^sqlite3_realloc(X,N) returns a pointer to a memory allocation
     ** of at least N bytes in size or NULL if insufficient memory is available.
    -** ^If M is the size of the prior allocation, then min(N,M) bytes
    -** of the prior allocation are copied into the beginning of buffer returned
    +** ^If M is the size of the prior allocation, then min(N,M) bytes of the
    +** prior allocation are copied into the beginning of the buffer returned
     ** by sqlite3_realloc(X,N) and the prior allocation is freed.
     ** ^If sqlite3_realloc(X,N) returns NULL and N is positive, then the
     ** prior allocation is not freed.
     **
    -** ^The sqlite3_realloc64(X,N) interfaces works the same as
    +** ^The sqlite3_realloc64(X,N) interface works the same as
     ** sqlite3_realloc(X,N) except that N is a 64-bit unsigned integer instead
     ** of a 32-bit signed integer.
     **
    @@ -3796,19 +3627,6 @@ SQLITE_API char *sqlite3_vsnprintf(int,char*,const char*, va_list);
     ** 4 byte boundary if the [SQLITE_4_BYTE_ALIGNED_MALLOC] compile-time
     ** option is used.
     **
    -** In SQLite version 3.5.0 and 3.5.1, it was possible to define
    -** the SQLITE_OMIT_MEMORY_ALLOCATION which would cause the built-in
    -** implementation of these routines to be omitted.  That capability
    -** is no longer provided.  Only built-in memory allocators can be used.
    -**
    -** Prior to SQLite version 3.7.10, the Windows OS interface layer called
    -** the system malloc() and free() directly when converting
    -** filenames between the UTF-8 encoding used by SQLite
    -** and whatever filename encoding is used by the particular Windows
    -** installation.  Memory allocation errors were detected, but
    -** they were reported back as [SQLITE_CANTOPEN] or
    -** [SQLITE_IOERR] rather than [SQLITE_NOMEM].
    -**
     ** The pointer arguments to [sqlite3_free()] and [sqlite3_realloc()]
     ** must be either NULL or else pointers obtained from a prior
     ** invocation of [sqlite3_malloc()] or [sqlite3_realloc()] that have
    @@ -3839,7 +3657,7 @@ SQLITE_API sqlite3_uint64 sqlite3_msize(void*);
     ** was last reset.  ^The values returned by [sqlite3_memory_used()] and
     ** [sqlite3_memory_highwater()] include any overhead
     ** added by SQLite in its implementation of [sqlite3_malloc()],
    -** but not overhead added by the any underlying system library
    +** but not overhead added by any underlying system library
     ** routines that [sqlite3_malloc()] may call.
     **
     ** ^The memory high-water mark is reset to the current value of
    @@ -3857,7 +3675,7 @@ SQLITE_API sqlite3_int64 sqlite3_memory_highwater(int resetFlag);
     ** SQLite contains a high-quality pseudo-random number generator (PRNG) used to
     ** select random [ROWID | ROWIDs] when inserting new records into a table that
     ** already uses the largest possible [ROWID].  The PRNG is also used for
    -** the build-in random() and randomblob() SQL functions.  This interface allows
    +** the built-in random() and randomblob() SQL functions.  This interface allows
     ** applications to access the same PRNG for other purposes.
     **
     ** ^A call to this routine stores N bytes of randomness into buffer P.
    @@ -3900,7 +3718,7 @@ SQLITE_API void sqlite3_randomness(int N, void *P);
     ** requested is ok.  ^When the callback returns [SQLITE_DENY], the
     ** [sqlite3_prepare_v2()] or equivalent call that triggered the
     ** authorizer will fail with an error message explaining that
    -** access is denied. 
    +** access is denied.
     **
     ** ^The first parameter to the authorizer callback is a copy of the third
     ** parameter to the sqlite3_set_authorizer() interface. ^The second parameter
    @@ -3953,7 +3771,7 @@ SQLITE_API void sqlite3_randomness(int N, void *P);
     ** database connections for the meaning of "modify" in this paragraph.
     **
     ** ^When [sqlite3_prepare_v2()] is used to prepare a statement, the
    -** statement might be re-prepared during [sqlite3_step()] due to a 
    +** statement might be re-prepared during [sqlite3_step()] due to a
     ** schema change.  Hence, the application should ensure that the
     ** correct authorizer callback remains in place during the [sqlite3_step()].
     **
    @@ -4040,8 +3858,8 @@ SQLITE_API int sqlite3_set_authorizer(
     #define SQLITE_RECURSIVE            33   /* NULL            NULL            */
     
     /*
    -** CAPI3REF: Tracing And Profiling Functions
    -** METHOD: sqlite3
    +** CAPI3REF: Deprecated Tracing And Profiling Functions
    +** DEPRECATED
     **
     ** These routines are deprecated. Use the [sqlite3_trace_v2()] interface
     ** instead of the routines described here.
    @@ -4101,7 +3919,7 @@ SQLITE_API SQLITE_DEPRECATED void *sqlite3_profile(sqlite3*,
     ** execution of the prepared statement, such as at the start of each
     ** trigger subprogram. ^The P argument is a pointer to the
     ** [prepared statement]. ^The X argument is a pointer to a string which
    -** is the unexpanded SQL text of the prepared statement or an SQL comment 
    +** is the unexpanded SQL text of the prepared statement or an SQL comment
     ** that indicates the invocation of a trigger.  ^The callback can compute
     ** the same text that would have been returned by the legacy [sqlite3_trace()]
     ** interface by using the X argument when X begins with "--" and invoking
    @@ -4111,13 +3929,13 @@ SQLITE_API SQLITE_DEPRECATED void *sqlite3_profile(sqlite3*,
     ** 
    ^An SQLITE_TRACE_PROFILE callback provides approximately the same ** information as is provided by the [sqlite3_profile()] callback. ** ^The P argument is a pointer to the [prepared statement] and the -** X argument points to a 64-bit integer which is the estimated of -** the number of nanosecond that the prepared statement took to run. +** X argument points to a 64-bit integer which is approximately +** the number of nanoseconds that the prepared statement took to run. ** ^The SQLITE_TRACE_PROFILE callback is invoked when the statement finishes. ** ** [[SQLITE_TRACE_ROW]]
    SQLITE_TRACE_ROW
    **
    ^An SQLITE_TRACE_ROW callback is invoked whenever a prepared -** statement generates a single row of result. +** statement generates a single row of result. ** ^The P argument is a pointer to the [prepared statement] and the ** X argument is unused. ** @@ -4144,10 +3962,12 @@ SQLITE_API SQLITE_DEPRECATED void *sqlite3_profile(sqlite3*, ** M argument should be the bitwise OR-ed combination of ** zero or more [SQLITE_TRACE] constants. ** -** ^Each call to either sqlite3_trace() or sqlite3_trace_v2() overrides -** (cancels) any prior calls to sqlite3_trace() or sqlite3_trace_v2(). +** ^Each call to either sqlite3_trace(D,X,P) or sqlite3_trace_v2(D,M,X,P) +** overrides (cancels) all prior calls to sqlite3_trace(D,X,P) or +** sqlite3_trace_v2(D,M,X,P) for the [database connection] D. Each +** database connection may have at most one trace callback. ** -** ^The X callback is invoked whenever any of the events identified by +** ^The X callback is invoked whenever any of the events identified by ** mask M occur. ^The integer return value from the callback is currently ** ignored, though this may change in future releases. Callback ** implementations should return zero to ensure future compatibility. @@ -4175,12 +3995,12 @@ SQLITE_API int sqlite3_trace_v2( ** ** ^The sqlite3_progress_handler(D,N,X,P) interface causes the callback ** function X to be invoked periodically during long running calls to -** [sqlite3_exec()], [sqlite3_step()] and [sqlite3_get_table()] for +** [sqlite3_step()] and [sqlite3_prepare()] and similar for ** database connection D. An example use for this ** interface is to keep a GUI updated during a large query. ** -** ^The parameter P is passed through as the only parameter to the -** callback function X. ^The parameter N is the approximate number of +** ^The parameter P is passed through as the only parameter to the +** callback function X. ^The parameter N is the approximate number of ** [virtual machine instructions] that are evaluated between successive ** invocations of the callback X. ^If N is less than one then the progress ** handler is disabled. @@ -4200,6 +4020,13 @@ SQLITE_API int sqlite3_trace_v2( ** Note that [sqlite3_prepare_v2()] and [sqlite3_step()] both modify their ** database connections for the meaning of "modify" in this paragraph. ** +** The progress handler callback would originally only be invoked from the +** bytecode engine. It still might be invoked during [sqlite3_prepare()] +** and similar because those routines might force a reparse of the schema +** which involves running the bytecode engine. However, beginning with +** SQLite version 3.41.0, the progress handler callback might also be +** invoked directly from [sqlite3_prepare()] while analyzing and generating +** code for complex queries. */ SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*); @@ -4207,7 +4034,7 @@ SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*); ** CAPI3REF: Opening A New Database Connection ** CONSTRUCTOR: sqlite3 ** -** ^These routines open an SQLite database file as specified by the +** ^These routines open an SQLite database file as specified by the ** filename argument. ^The filename argument is interpreted as UTF-8 for ** sqlite3_open() and sqlite3_open_v2() and as UTF-16 in the native byte ** order for sqlite3_open16(). ^(A [database connection] handle is usually @@ -4231,20 +4058,23 @@ SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*); ** The sqlite3_open_v2() interface works like sqlite3_open() ** except that it accepts two additional parameters for additional control ** over the new database connection. ^(The flags parameter to -** sqlite3_open_v2() can take one of -** the following three values, optionally combined with the -** [SQLITE_OPEN_NOMUTEX], [SQLITE_OPEN_FULLMUTEX], [SQLITE_OPEN_SHAREDCACHE], -** [SQLITE_OPEN_PRIVATECACHE], and/or [SQLITE_OPEN_URI] flags:)^ +** sqlite3_open_v2() must include, at a minimum, one of the following +** three flag combinations:)^ ** **
    ** ^(
    [SQLITE_OPEN_READONLY]
    -**
    The database is opened in read-only mode. If the database does not -** already exist, an error is returned.
    )^ +**
    The database is opened in read-only mode. If the database does +** not already exist, an error is returned.
    )^ ** ** ^(
    [SQLITE_OPEN_READWRITE]
    -**
    The database is opened for reading and writing if possible, or reading -** only if the file is write protected by the operating system. In either -** case the database must already exist, otherwise an error is returned.
    )^ +**
    The database is opened for reading and writing if possible, or +** reading only if the file is write protected by the operating +** system. In either case the database must already exist, otherwise +** an error is returned. For historical reasons, if opening in +** read-write mode fails due to OS-level permissions, an attempt is +** made to open it in read-only mode. [sqlite3_db_readonly()] can be +** used to determine whether the database is actually +** read-write.
    )^ ** ** ^(
    [SQLITE_OPEN_READWRITE] | [SQLITE_OPEN_CREATE]
    **
    The database is opened for reading and writing, and is created if @@ -4252,22 +4082,69 @@ SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*); ** sqlite3_open() and sqlite3_open16().
    )^ **
    ** +** In addition to the required flags, the following optional flags are +** also supported: +** +**
    +** ^(
    [SQLITE_OPEN_URI]
    +**
    The filename can be interpreted as a URI if this flag is set.
    )^ +** +** ^(
    [SQLITE_OPEN_MEMORY]
    +**
    The database will be opened as an in-memory database. The database +** is named by the "filename" argument for the purposes of cache-sharing, +** if shared cache mode is enabled, but the "filename" is otherwise ignored. +**
    )^ +** +** ^(
    [SQLITE_OPEN_NOMUTEX]
    +**
    The new database connection will use the "multi-thread" +** [threading mode].)^ This means that separate threads are allowed +** to use SQLite at the same time, as long as each thread is using +** a different [database connection]. +** +** ^(
    [SQLITE_OPEN_FULLMUTEX]
    +**
    The new database connection will use the "serialized" +** [threading mode].)^ This means the multiple threads can safely +** attempt to use the same database connection at the same time. +** (Mutexes will block any actual concurrency, but in this mode +** there is no harm in trying.) +** +** ^(
    [SQLITE_OPEN_SHAREDCACHE]
    +**
    The database is opened with [shared cache] enabled, overriding +** the default shared cache setting provided by +** [sqlite3_enable_shared_cache()].)^ +** The [use of shared cache mode is discouraged] and hence shared cache +** capabilities may be omitted from many builds of SQLite. In such cases, +** this option is a no-op. +** +** ^(
    [SQLITE_OPEN_PRIVATECACHE]
    +**
    The database is opened with [shared cache] disabled, overriding +** the default shared cache setting provided by +** [sqlite3_enable_shared_cache()].)^ +** +** [[OPEN_EXRESCODE]] ^(
    [SQLITE_OPEN_EXRESCODE]
    +**
    The database connection comes up in "extended result code mode". +** In other words, the database behaves as if +** [sqlite3_extended_result_codes(db,1)] were called on the database +** connection as soon as the connection is created. In addition to setting +** the extended result code mode, this flag also causes [sqlite3_open_v2()] +** to return an extended result code.
    +** +** [[OPEN_NOFOLLOW]] ^(
    [SQLITE_OPEN_NOFOLLOW]
    +**
    The database filename is not allowed to contain a symbolic link
    +**
    )^ +** ** If the 3rd parameter to sqlite3_open_v2() is not one of the -** combinations shown above optionally combined with other +** required combinations shown above optionally combined with other ** [SQLITE_OPEN_READONLY | SQLITE_OPEN_* bits] -** then the behavior is undefined. -** -** ^If the [SQLITE_OPEN_NOMUTEX] flag is set, then the database connection -** opens in the multi-thread [threading mode] as long as the single-thread -** mode has not been set at compile-time or start-time. ^If the -** [SQLITE_OPEN_FULLMUTEX] flag is set then the database connection opens -** in the serialized [threading mode] unless single-thread was -** previously selected at compile-time or start-time. -** ^The [SQLITE_OPEN_SHAREDCACHE] flag causes the database connection to be -** eligible to use [shared cache mode], regardless of whether or not shared -** cache is enabled using [sqlite3_enable_shared_cache()]. ^The -** [SQLITE_OPEN_PRIVATECACHE] flag causes the database connection to not -** participate in [shared cache mode] even if it is enabled. +** then the behavior is undefined. Historic versions of SQLite +** have silently ignored surplus bits in the flags parameter to +** sqlite3_open_v2(), however that behavior might not be carried through +** into future versions of SQLite and so applications should not rely +** upon it. Note in particular that the SQLITE_OPEN_EXCLUSIVE flag is a no-op +** for sqlite3_open_v2(). The SQLITE_OPEN_EXCLUSIVE does *not* cause +** the open to fail if the database already exists. The SQLITE_OPEN_EXCLUSIVE +** flag is intended for use by the [sqlite3_vfs|VFS interface] only, and not +** by sqlite3_open_v2(). ** ** ^The fourth parameter to sqlite3_open_v2() is the name of the ** [sqlite3_vfs] object that defines the operating system interface that @@ -4300,17 +4177,17 @@ SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*); ** information. ** ** URI filenames are parsed according to RFC 3986. ^If the URI contains an -** authority, then it must be either an empty string or the string -** "localhost". ^If the authority is not an empty string or "localhost", an -** error is returned to the caller. ^The fragment component of a URI, if +** authority, then it must be either an empty string or the string +** "localhost". ^If the authority is not an empty string or "localhost", an +** error is returned to the caller. ^The fragment component of a URI, if ** present, is ignored. ** ** ^SQLite uses the path component of the URI as the name of the disk file -** which contains the database. ^If the path begins with a '/' character, -** then it is interpreted as an absolute path. ^If the path does not begin +** which contains the database. ^If the path begins with a '/' character, +** then it is interpreted as an absolute path. ^If the path does not begin ** with a '/' (meaning that the authority section is omitted from the URI) -** then the path is interpreted as a relative path. -** ^(On windows, the first component of an absolute path +** then the path is interpreted as a relative path. +** ^(On windows, the first component of an absolute path ** is a drive specification (e.g. "C:").)^ ** ** [[core URI query parameters]] @@ -4330,13 +4207,13 @@ SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*); ** **
  • mode: ^(The mode parameter may be set to either "ro", "rw", ** "rwc", or "memory". Attempting to set it to any other value is -** an error)^. -** ^If "ro" is specified, then the database is opened for read-only -** access, just as if the [SQLITE_OPEN_READONLY] flag had been set in the -** third argument to sqlite3_open_v2(). ^If the mode option is set to -** "rw", then the database is opened for read-write (but not create) -** access, as if SQLITE_OPEN_READWRITE (but not SQLITE_OPEN_CREATE) had -** been set. ^Value "rwc" is equivalent to setting both +** an error)^. +** ^If "ro" is specified, then the database is opened for read-only +** access, just as if the [SQLITE_OPEN_READONLY] flag had been set in the +** third argument to sqlite3_open_v2(). ^If the mode option is set to +** "rw", then the database is opened for read-write (but not create) +** access, as if SQLITE_OPEN_READWRITE (but not SQLITE_OPEN_CREATE) had +** been set. ^Value "rwc" is equivalent to setting both ** SQLITE_OPEN_READWRITE and SQLITE_OPEN_CREATE. ^If the mode option is ** set to "memory" then a pure [in-memory database] that never reads ** or writes from disk is used. ^It is an error to specify a value for @@ -4346,7 +4223,7 @@ SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*); **
  • cache: ^The cache parameter may be set to either "shared" or ** "private". ^Setting it to "shared" is equivalent to setting the ** SQLITE_OPEN_SHAREDCACHE bit in the flags argument passed to -** sqlite3_open_v2(). ^Setting the cache parameter to "private" is +** sqlite3_open_v2(). ^Setting the cache parameter to "private" is ** equivalent to setting the SQLITE_OPEN_PRIVATECACHE bit. ** ^If sqlite3_open_v2() is used and the "cache" parameter is present in ** a URI filename, its value overrides any behavior requested by setting @@ -4372,7 +4249,7 @@ SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*); ** property on a database file that does in fact change can result ** in incorrect query results and/or [SQLITE_CORRUPT] errors. ** See also: [SQLITE_IOCAP_IMMUTABLE]. -** +** ** ** ** ^Specifying an unknown parameter in the query component of a URI is not an @@ -4384,36 +4261,37 @@ SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*); ** ** **
    URI filenames Results -**
    file:data.db +**
    file:data.db ** Open the file "data.db" in the current directory. **
    file:/home/fred/data.db
    -** file:///home/fred/data.db
    -** file://localhost/home/fred/data.db
    +** file:///home/fred/data.db
    +** file://localhost/home/fred/data.db
    ** Open the database file "/home/fred/data.db". -**
    file://darkstar/home/fred/data.db +**
    file://darkstar/home/fred/data.db ** An error. "darkstar" is not a recognized authority. -**
    +**
    ** file:///C:/Documents%20and%20Settings/fred/Desktop/data.db ** Windows only: Open the file "data.db" on fred's desktop on drive -** C:. Note that the %20 escaping in this example is not strictly +** C:. Note that the %20 escaping in this example is not strictly ** necessary - space characters can be used literally ** in URI filenames. -**
    file:data.db?mode=ro&cache=private +**
    file:data.db?mode=ro&cache=private ** Open file "data.db" in the current directory for read-only access. ** Regardless of whether or not shared-cache mode is enabled by ** default, use a private cache. **
    file:/home/fred/data.db?vfs=unix-dotfile ** Open file "/home/fred/data.db". Use the special VFS "unix-dotfile" ** that uses dot-files in place of posix advisory locking. -**
    file:data.db?mode=readonly +**
    file:data.db?mode=readonly ** An error. "readonly" is not a valid option for the "mode" parameter. +** Use "ro" instead: "file:data.db?mode=ro". **
    ** ** ^URI hexadecimal escape sequences (%HH) are supported within the path and ** query components of a URI. A hexadecimal escape sequence consists of a -** percent sign - "%" - followed by exactly two hexadecimal digits +** percent sign - "%" - followed by exactly two hexadecimal digits ** specifying an octet value. ^Before the path or query components of a -** URI filename are interpreted, they are encoded using UTF-8 and all +** URI filename are interpreted, they are encoded using UTF-8 and all ** hexadecimal escape sequences replaced by a single byte containing the ** corresponding octet. If this process generates an invalid UTF-8 encoding, ** the results are undefined. @@ -4448,17 +4326,27 @@ SQLITE_API int sqlite3_open_v2( /* ** CAPI3REF: Obtain Values For URI Parameters ** -** These are utility routines, useful to VFS implementations, that check -** to see if a database file was a URI that contained a specific query +** These are utility routines, useful to [VFS|custom VFS implementations], +** that check if a database file was a URI that contained a specific query ** parameter, and if so obtains the value of that query parameter. ** -** If F is the database filename pointer passed into the xOpen() method of -** a VFS implementation when the flags parameter to xOpen() has one or -** more of the [SQLITE_OPEN_URI] or [SQLITE_OPEN_MAIN_DB] bits set and -** P is the name of the query parameter, then +** The first parameter to these interfaces (hereafter referred to +** as F) must be one of: +**
      +**
    • A database filename pointer created by the SQLite core and +** passed into the xOpen() method of a VFS implementation, or +**
    • A filename obtained from [sqlite3_db_filename()], or +**
    • A new filename constructed using [sqlite3_create_filename()]. +**
    +** If the F parameter is not one of the above, then the behavior is +** undefined and probably undesirable. Older versions of SQLite were +** more tolerant of invalid F parameters than newer versions. +** +** If F is a suitable filename (as described in the previous paragraph) +** and if P is the name of the query parameter, then ** sqlite3_uri_parameter(F,P) returns the value of the P -** parameter if it exists or a NULL pointer if P does not appear as a -** query parameter on F. If P is a query parameter of F +** parameter if it exists or a NULL pointer if P does not appear as a +** query parameter on F. If P is a query parameter of F and it ** has no explicit value, then sqlite3_uri_parameter(F,P) returns ** a pointer to an empty string. ** @@ -4466,41 +4354,160 @@ SQLITE_API int sqlite3_open_v2( ** parameter and returns true (1) or false (0) according to the value ** of P. The sqlite3_uri_boolean(F,P,B) routine returns true (1) if the ** value of query parameter P is one of "yes", "true", or "on" in any -** case or if the value begins with a non-zero number. The +** case or if the value begins with a non-zero number. The ** sqlite3_uri_boolean(F,P,B) routines returns false (0) if the value of ** query parameter P is one of "no", "false", or "off" in any case or ** if the value begins with a numeric zero. If P is not a query -** parameter on F or if the value of P is does not match any of the +** parameter on F or if the value of P does not match any of the ** above, then sqlite3_uri_boolean(F,P,B) returns (B!=0). ** ** The sqlite3_uri_int64(F,P,D) routine converts the value of P into a ** 64-bit signed integer and returns that integer, or D if P does not ** exist. If the value of P is something other than an integer, then ** zero is returned. -** +** +** The sqlite3_uri_key(F,N) returns a pointer to the name (not +** the value) of the N-th query parameter for filename F, or a NULL +** pointer if N is less than zero or greater than the number of query +** parameters minus 1. The N value is zero-based so N should be 0 to obtain +** the name of the first query parameter, 1 for the second parameter, and +** so forth. +** ** If F is a NULL pointer, then sqlite3_uri_parameter(F,P) returns NULL and ** sqlite3_uri_boolean(F,P,B) returns B. If F is not a NULL pointer and -** is not a database file pathname pointer that SQLite passed into the xOpen -** VFS method, then the behavior of this routine is undefined and probably -** undesirable. +** is not a database file pathname pointer that the SQLite core passed +** into the xOpen VFS method, then the behavior of this routine is undefined +** and probably undesirable. +** +** Beginning with SQLite [version 3.31.0] ([dateof:3.31.0]) the input F +** parameter can also be the name of a rollback journal file or WAL file +** in addition to the main database file. Prior to version 3.31.0, these +** routines would only work if F was the name of the main database file. +** When the F parameter is the name of the rollback journal or WAL file, +** it has access to all the same query parameters as were found on the +** main database file. ** ** See the [URI filename] documentation for additional information. */ -SQLITE_API const char *sqlite3_uri_parameter(const char *zFilename, const char *zParam); -SQLITE_API int sqlite3_uri_boolean(const char *zFile, const char *zParam, int bDefault); -SQLITE_API sqlite3_int64 sqlite3_uri_int64(const char*, const char*, sqlite3_int64); +SQLITE_API const char *sqlite3_uri_parameter(sqlite3_filename z, const char *zParam); +SQLITE_API int sqlite3_uri_boolean(sqlite3_filename z, const char *zParam, int bDefault); +SQLITE_API sqlite3_int64 sqlite3_uri_int64(sqlite3_filename, const char*, sqlite3_int64); +SQLITE_API const char *sqlite3_uri_key(sqlite3_filename z, int N); +/* +** CAPI3REF: Translate filenames +** +** These routines are available to [VFS|custom VFS implementations] for +** translating filenames between the main database file, the journal file, +** and the WAL file. +** +** If F is the name of an sqlite database file, journal file, or WAL file +** passed by the SQLite core into the VFS, then sqlite3_filename_database(F) +** returns the name of the corresponding database file. +** +** If F is the name of an sqlite database file, journal file, or WAL file +** passed by the SQLite core into the VFS, or if F is a database filename +** obtained from [sqlite3_db_filename()], then sqlite3_filename_journal(F) +** returns the name of the corresponding rollback journal file. +** +** If F is the name of an sqlite database file, journal file, or WAL file +** that was passed by the SQLite core into the VFS, or if F is a database +** filename obtained from [sqlite3_db_filename()], then +** sqlite3_filename_wal(F) returns the name of the corresponding +** WAL file. +** +** In all of the above, if F is not the name of a database, journal or WAL +** filename passed into the VFS from the SQLite core and F is not the +** return value from [sqlite3_db_filename()], then the result is +** undefined and is likely a memory access violation. +*/ +SQLITE_API const char *sqlite3_filename_database(sqlite3_filename); +SQLITE_API const char *sqlite3_filename_journal(sqlite3_filename); +SQLITE_API const char *sqlite3_filename_wal(sqlite3_filename); + +/* +** CAPI3REF: Database File Corresponding To A Journal +** +** ^If X is the name of a rollback or WAL-mode journal file that is +** passed into the xOpen method of [sqlite3_vfs], then +** sqlite3_database_file_object(X) returns a pointer to the [sqlite3_file] +** object that represents the main database file. +** +** This routine is intended for use in custom [VFS] implementations +** only. It is not a general-purpose interface. +** The argument sqlite3_file_object(X) must be a filename pointer that +** has been passed into [sqlite3_vfs].xOpen method where the +** flags parameter to xOpen contains one of the bits +** [SQLITE_OPEN_MAIN_JOURNAL] or [SQLITE_OPEN_WAL]. Any other use +** of this routine results in undefined and probably undesirable +** behavior. +*/ +SQLITE_API sqlite3_file *sqlite3_database_file_object(const char*); + +/* +** CAPI3REF: Create and Destroy VFS Filenames +** +** These interfaces are provided for use by [VFS shim] implementations and +** are not useful outside of that context. +** +** The sqlite3_create_filename(D,J,W,N,P) allocates memory to hold a version of +** database filename D with corresponding journal file J and WAL file W and +** an array P of N URI Key/Value pairs. The result from +** sqlite3_create_filename(D,J,W,N,P) is a pointer to a database filename that +** is safe to pass to routines like: +**
      +**
    • [sqlite3_uri_parameter()], +**
    • [sqlite3_uri_boolean()], +**
    • [sqlite3_uri_int64()], +**
    • [sqlite3_uri_key()], +**
    • [sqlite3_filename_database()], +**
    • [sqlite3_filename_journal()], or +**
    • [sqlite3_filename_wal()]. +**
    +** If a memory allocation error occurs, sqlite3_create_filename() might +** return a NULL pointer. The memory obtained from sqlite3_create_filename(X) +** must be released by a corresponding call to sqlite3_free_filename(Y). +** +** The P parameter in sqlite3_create_filename(D,J,W,N,P) should be an array +** of 2*N pointers to strings. Each pair of pointers in this array corresponds +** to a key and value for a query parameter. The P parameter may be a NULL +** pointer if N is zero. None of the 2*N pointers in the P array may be +** NULL pointers and key pointers should not be empty strings. +** None of the D, J, or W parameters to sqlite3_create_filename(D,J,W,N,P) may +** be NULL pointers, though they can be empty strings. +** +** The sqlite3_free_filename(Y) routine releases a memory allocation +** previously obtained from sqlite3_create_filename(). Invoking +** sqlite3_free_filename(Y) where Y is a NULL pointer is a harmless no-op. +** +** If the Y parameter to sqlite3_free_filename(Y) is anything other +** than a NULL pointer or a pointer previously acquired from +** sqlite3_create_filename(), then bad things such as heap +** corruption or segfaults may occur. The value Y should not be +** used again after sqlite3_free_filename(Y) has been called. This means +** that if the [sqlite3_vfs.xOpen()] method of a VFS has been called using Y, +** then the corresponding [sqlite3_module.xClose() method should also be +** invoked prior to calling sqlite3_free_filename(Y). +*/ +SQLITE_API sqlite3_filename sqlite3_create_filename( + const char *zDatabase, + const char *zJournal, + const char *zWal, + int nParam, + const char **azParam +); +SQLITE_API void sqlite3_free_filename(sqlite3_filename); /* ** CAPI3REF: Error Codes And Messages ** METHOD: sqlite3 ** -** ^If the most recent sqlite3_* API call associated with +** ^If the most recent sqlite3_* API call associated with ** [database connection] D failed, then the sqlite3_errcode(D) interface ** returns the numeric [result code] or [extended result code] for that ** API call. ** ^The sqlite3_extended_errcode() -** interface is the same except that it always returns the +** interface is the same except that it always returns the ** [extended result code] even when extended result codes are ** disabled. ** @@ -4508,27 +4515,39 @@ SQLITE_API sqlite3_int64 sqlite3_uri_int64(const char*, const char*, sqlite3_int ** sqlite3_extended_errcode() might change with each API call. ** Except, there are some interfaces that are guaranteed to never ** change the value of the error code. The error-code preserving -** interfaces are: +** interfaces include the following: ** **
      **
    • sqlite3_errcode() **
    • sqlite3_extended_errcode() **
    • sqlite3_errmsg() **
    • sqlite3_errmsg16() +**
    • sqlite3_error_offset() +**
    • sqlite3_db_handle() **
    ** ** ^The sqlite3_errmsg() and sqlite3_errmsg16() return English-language -** text that describes the error, as either UTF-8 or UTF-16 respectively. +** text that describes the error, as either UTF-8 or UTF-16 respectively, +** or NULL if no error message is available. +** (See how SQLite handles [invalid UTF] for exceptions to this rule.) ** ^(Memory to hold the error message string is managed internally. ** The application does not need to worry about freeing the result. ** However, the error string might be overwritten or deallocated by ** subsequent calls to other SQLite interface functions.)^ ** -** ^The sqlite3_errstr() interface returns the English-language text -** that describes the [result code], as UTF-8. +** ^The sqlite3_errstr(E) interface returns the English-language text +** that describes the [result code] E, as UTF-8, or NULL if E is not a +** result code for which a text error message is available. ** ^(Memory to hold the error message string is managed internally ** and must not be freed by the application)^. ** +** ^If the most recent error references a specific token in the input +** SQL, the sqlite3_error_offset() interface returns the byte offset +** of the start of that token. ^The byte offset returned by +** sqlite3_error_offset() assumes that the input SQL is UTF-8. +** ^If the most recent error does not reference a specific token in the input +** SQL, then the sqlite3_error_offset() function returns -1. +** ** When the serialized [threading mode] is in use, it might be the ** case that a second error occurs on a separate thread in between ** the time of the first error and the call to these interfaces. @@ -4548,6 +4567,35 @@ SQLITE_API int sqlite3_extended_errcode(sqlite3 *db); SQLITE_API const char *sqlite3_errmsg(sqlite3*); SQLITE_API const void *sqlite3_errmsg16(sqlite3*); SQLITE_API const char *sqlite3_errstr(int); +SQLITE_API int sqlite3_error_offset(sqlite3 *db); + +/* +** CAPI3REF: Set Error Code And Message +** METHOD: sqlite3 +** +** Set the error code of the database handle passed as the first argument +** to errcode, and the error message to a copy of nul-terminated string +** zErrMsg. If zErrMsg is passed NULL, then the error message is set to +** the default message associated with the supplied error code. Subsequent +** calls to [sqlite3_errcode()] and [sqlite3_errmsg()] and similar will +** return the values set by this routine in place of what was previously +** set by SQLite itself. +** +** This function returns SQLITE_OK if the error code and error message are +** successfully set, SQLITE_NOMEM if an OOM occurs, and SQLITE_MISUSE if +** the database handle is NULL or invalid. +** +** The error code and message set by this routine remains in effect until +** they are changed, either by another call to this routine or until they are +** changed to by SQLite itself to reflect the result of some subsquent +** API call. +** +** This function is intended for use by SQLite extensions or wrappers. The +** idea is that an extension or wrapper can use this routine to set error +** messages and error codes and thus behave more like a core SQLite +** feature from the point of view of an application. +*/ +SQLITE_API int sqlite3_set_errmsg(sqlite3 *db, int errcode, const char *zErrMsg); /* ** CAPI3REF: Prepared Statement Object @@ -4557,7 +4605,7 @@ SQLITE_API const char *sqlite3_errstr(int); ** has been compiled into binary form and is ready to be evaluated. ** ** Think of each SQL statement as a separate computer program. The -** original SQL text is source code. A prepared statement object +** original SQL text is source code. A prepared statement object ** is the compiled object code. All SQL must be converted into a ** prepared statement before it can be run. ** @@ -4587,7 +4635,7 @@ typedef struct sqlite3_stmt sqlite3_stmt; ** new limit for that construct.)^ ** ** ^If the new limit is a negative number, the limit is unchanged. -** ^(For each limit category SQLITE_LIMIT_NAME there is a +** ^(For each limit category SQLITE_LIMIT_NAME there is a ** [limits | hard upper bound] ** set at compile-time by a C preprocessor macro called ** [limits | SQLITE_MAX_NAME]. @@ -4595,7 +4643,7 @@ typedef struct sqlite3_stmt sqlite3_stmt; ** ^Attempts to increase a limit above its hard upper bound are ** silently truncated to the hard upper bound. ** -** ^Regardless of whether or not the limit was changed, the +** ^Regardless of whether or not the limit was changed, the ** [sqlite3_limit()] interface returns the prior value of the limit. ** ^Hence, to find the current value of a limit without changing it, ** simply invoke this interface with the third parameter set to -1. @@ -4623,8 +4671,8 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); ** ** These constants define various performance limits ** that can be lowered at run-time using [sqlite3_limit()]. -** The synopsis of the meanings of the various limits is shown below. -** Additional information is available at [limits | Limits in SQLite]. +** A concise description of these limits follows, and additional information +** is available at [limits | Limits in SQLite]. ** **
    ** [[SQLITE_LIMIT_LENGTH]] ^(
    SQLITE_LIMIT_LENGTH
    @@ -4639,7 +4687,12 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); ** or in an ORDER BY or GROUP BY clause.
  • )^ ** ** [[SQLITE_LIMIT_EXPR_DEPTH]] ^(
    SQLITE_LIMIT_EXPR_DEPTH
    -**
    The maximum depth of the parse tree on any expression.
    )^ +**
    The maximum depth of the parse tree on any expression and +** the maximum nesting depth for subqueries and VIEWs
    )^ +** +** [[SQLITE_LIMIT_PARSER_DEPTH]] ^(
    SQLITE_LIMIT_PARSER_DEPTH
    +**
    The maximum depth of the LALR(1) parser stack used to analyze +** input SQL statements.
    )^ ** ** [[SQLITE_LIMIT_COMPOUND_SELECT]] ^(
    SQLITE_LIMIT_COMPOUND_SELECT
    **
    The maximum number of terms in a compound SELECT statement.
    )^ @@ -4666,7 +4719,8 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); **
    The maximum index number of any [parameter] in an SQL statement.)^ ** ** [[SQLITE_LIMIT_TRIGGER_DEPTH]] ^(
    SQLITE_LIMIT_TRIGGER_DEPTH
    -**
    The maximum depth of recursion for triggers.
    )^ +**
    The maximum depth of recursion for triggers, and the maximum +** nesting depth for separate triggers.
    )^ ** ** [[SQLITE_LIMIT_WORKER_THREADS]] ^(
    SQLITE_LIMIT_WORKER_THREADS
    **
    The maximum number of auxiliary worker threads that a single @@ -4685,11 +4739,12 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); #define SQLITE_LIMIT_VARIABLE_NUMBER 9 #define SQLITE_LIMIT_TRIGGER_DEPTH 10 #define SQLITE_LIMIT_WORKER_THREADS 11 +#define SQLITE_LIMIT_PARSER_DEPTH 12 /* ** CAPI3REF: Prepare Flags ** -** These constants define various flags that can be passed into +** These constants define various flags that can be passed into the ** "prepFlags" parameter of the [sqlite3_prepare_v3()] and ** [sqlite3_prepare16_v3()] interfaces. ** @@ -4700,7 +4755,7 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); **
    The SQLITE_PREPARE_PERSISTENT flag is a hint to the query planner ** that the prepared statement will be retained for a long time and ** probably reused many times.)^ ^Without this flag, [sqlite3_prepare_v3()] -** and [sqlite3_prepare16_v3()] assume that the prepared statement will +** and [sqlite3_prepare16_v3()] assume that the prepared statement will ** be used just once or at most a few times and then destroyed using ** [sqlite3_finalize()] relatively soon. The current implementation acts ** on this hint by avoiding the use of [lookaside memory] so as not to @@ -4719,11 +4774,39 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); **
    The SQLITE_PREPARE_NO_VTAB flag causes the SQL compiler ** to return an error (error code SQLITE_ERROR) if the statement uses ** any virtual tables. +** +** [[SQLITE_PREPARE_DONT_LOG]]
    SQLITE_PREPARE_DONT_LOG
    +**
    The SQLITE_PREPARE_DONT_LOG flag prevents SQL compiler +** errors from being sent to the error log defined by +** [SQLITE_CONFIG_LOG]. This can be used, for example, to do test +** compiles to see if some SQL syntax is well-formed, without generating +** messages on the global error log when it is not. If the test compile +** fails, the sqlite3_prepare_v3() call returns the same error indications +** with or without this flag; it just omits the call to [sqlite3_log()] that +** logs the error. +** +** [[SQLITE_PREPARE_FROM_DDL]]
    SQLITE_PREPARE_FROM_DDL
    +**
    The SQLITE_PREPARE_FROM_DDL flag causes the SQL compiler to enforce +** security constraints that would otherwise only be enforced when parsing +** the database schema. In other words, the SQLITE_PREPARE_FROM_DDL flag +** causes the SQL compiler to treat the SQL statement being prepared as if +** it had come from an attacker. When SQLITE_PREPARE_FROM_DDL is used and +** [SQLITE_DBCONFIG_TRUSTED_SCHEMA] is off, SQL functions may only be called +** if they are tagged with [SQLITE_INNOCUOUS] and virtual tables may only +** be used if they are tagged with [SQLITE_VTAB_INNOCUOUS]. Best practice +** is to use the SQLITE_PREPARE_FROM_DDL option when preparing any SQL that +** is derived from parts of the database schema. In particular, virtual +** table implementations that run SQL statements that are derived from +** arguments to their CREATE VIRTUAL TABLE statement should always use +** [sqlite3_prepare_v3()] and set the SQLITE_PREPARE_FROM_DDL flag to +** prevent bypass of the [SQLITE_DBCONFIG_TRUSTED_SCHEMA] security checks. ** */ #define SQLITE_PREPARE_PERSISTENT 0x01 #define SQLITE_PREPARE_NORMALIZE 0x02 #define SQLITE_PREPARE_NO_VTAB 0x04 +#define SQLITE_PREPARE_DONT_LOG 0x10 +#define SQLITE_PREPARE_FROM_DDL 0x20 /* ** CAPI3REF: Compiling An SQL Statement @@ -4737,8 +4820,9 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); ** ** The preferred routine to use is [sqlite3_prepare_v2()]. The ** [sqlite3_prepare()] interface is legacy and should be avoided. -** [sqlite3_prepare_v3()] has an extra "prepFlags" option that is used -** for special purposes. +** [sqlite3_prepare_v3()] has an extra +** [SQLITE_PREPARE_FROM_DDL|"prepFlags" option] that is sometimes +** needed for special purpose or to pass along security restrictions. ** ** The use of the UTF-8 interfaces is preferred, as SQLite currently ** does all parsing using UTF-8. The UTF-16 interfaces are provided @@ -4756,13 +4840,17 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); ** and sqlite3_prepare16_v3() use UTF-16. ** ** ^If the nByte argument is negative, then zSql is read up to the -** first zero terminator. ^If nByte is positive, then it is the -** number of bytes read from zSql. ^If nByte is zero, then no prepared +** first zero terminator. ^If nByte is positive, then it is the maximum +** number of bytes read from zSql. When nByte is positive, zSql is read +** up to the first zero terminator or until the nByte bytes have been read, +** whichever comes first. ^If nByte is zero, then no prepared ** statement is generated. ** If the caller knows that the supplied string is nul-terminated, then ** there is a small performance advantage to passing an nByte parameter that ** is the number of bytes in the input string including ** the nul-terminator. +** Note that nByte measures the length of the input in bytes, not +** characters, even for the UTF-16 interfaces. ** ** ^If pzTail is not NULL then *pzTail is made to point to the first byte ** past the end of the first SQL statement in zSql. These routines only @@ -4807,15 +4895,15 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); **
  • ** **
  • -** ^If the specific value bound to [parameter | host parameter] in the +** ^If the specific value bound to a [parameter | host parameter] in the ** WHERE clause might influence the choice of query plan for a statement, -** then the statement will be automatically recompiled, as if there had been -** a schema change, on the first [sqlite3_step()] call following any change -** to the [sqlite3_bind_text | bindings] of that [parameter]. -** ^The specific value of WHERE-clause [parameter] might influence the +** then the statement will be automatically recompiled, as if there had been +** a schema change, on the first [sqlite3_step()] call following any change +** to the [sqlite3_bind_text | bindings] of that [parameter]. +** ^The specific value of a WHERE-clause [parameter] might influence the ** choice of query plan if the parameter is the left-hand side of a [LIKE] ** or [GLOB] operator or if the parameter is compared to an indexed column -** and the [SQLITE_ENABLE_STAT3] compile-time option is enabled. +** and the [SQLITE_ENABLE_STAT4] compile-time option is enabled. **
  • ** ** @@ -4895,7 +4983,7 @@ SQLITE_API int sqlite3_prepare16_v3( ** ** ^The sqlite3_expanded_sql() interface returns NULL if insufficient memory ** is available to hold the result, or if the result would exceed the -** the maximum string length determined by the [SQLITE_LIMIT_LENGTH]. +** maximum string length determined by the [SQLITE_LIMIT_LENGTH]. ** ** ^The [SQLITE_TRACE_SIZE_LIMIT] compile-time option limits the size of ** bound parameter expansions. ^The [SQLITE_OMIT_TRACE] compile-time @@ -4905,12 +4993,17 @@ SQLITE_API int sqlite3_prepare16_v3( ** are managed by SQLite and are automatically freed when the prepared ** statement is finalized. ** ^The string returned by sqlite3_expanded_sql(P), on the other hand, -** is obtained from [sqlite3_malloc()] and must be free by the application +** is obtained from [sqlite3_malloc()] and must be freed by the application ** by passing it to [sqlite3_free()]. +** +** ^The sqlite3_normalized_sql() interface is only available if +** the [SQLITE_ENABLE_NORMALIZE] compile-time option is defined. */ SQLITE_API const char *sqlite3_sql(sqlite3_stmt *pStmt); SQLITE_API char *sqlite3_expanded_sql(sqlite3_stmt *pStmt); +#ifdef SQLITE_ENABLE_NORMALIZE SQLITE_API const char *sqlite3_normalized_sql(sqlite3_stmt *pStmt); +#endif /* ** CAPI3REF: Determine If An SQL Statement Writes The Database @@ -4921,8 +5014,8 @@ SQLITE_API const char *sqlite3_normalized_sql(sqlite3_stmt *pStmt); ** the content of the database file. ** ** Note that [application-defined SQL functions] or -** [virtual tables] might change the database indirectly as a side effect. -** ^(For example, if an application defines a function "eval()" that +** [virtual tables] might change the database indirectly as a side effect. +** ^(For example, if an application defines a function "eval()" that ** calls [sqlite3_exec()], then the following SQL statement would ** change the database file through side-effects: ** @@ -4936,15 +5029,28 @@ SQLITE_API const char *sqlite3_normalized_sql(sqlite3_stmt *pStmt); ** ^Transaction control statements such as [BEGIN], [COMMIT], [ROLLBACK], ** [SAVEPOINT], and [RELEASE] cause sqlite3_stmt_readonly() to return true, ** since the statements themselves do not actually modify the database but -** rather they control the timing of when other statements modify the +** rather they control the timing of when other statements modify the ** database. ^The [ATTACH] and [DETACH] statements also cause ** sqlite3_stmt_readonly() to return true since, while those statements -** change the configuration of a database connection, they do not make +** change the configuration of a database connection, they do not make ** changes to the content of the database files on disk. ** ^The sqlite3_stmt_readonly() interface returns true for [BEGIN] since ** [BEGIN] merely sets internal flags, but the [BEGIN|BEGIN IMMEDIATE] and ** [BEGIN|BEGIN EXCLUSIVE] commands do touch the database and so ** sqlite3_stmt_readonly() returns false for those commands. +** +** ^This routine returns false if there is any possibility that the +** statement might change the database file. ^A false return does +** not guarantee that the statement will change the database file. +** ^For example, an UPDATE statement might have a WHERE clause that +** makes it a no-op, but the sqlite3_stmt_readonly() result would still +** be false. ^Similarly, a CREATE TABLE IF NOT EXISTS statement is a +** read-only no-op if the table already exists, but +** sqlite3_stmt_readonly() still returns false for such a statement. +** +** ^If prepared statement X is an [EXPLAIN] or [EXPLAIN QUERY PLAN] +** statement, then sqlite3_stmt_readonly(X) returns the same value as +** if the EXPLAIN or EXPLAIN QUERY PLAN prefix were omitted. */ SQLITE_API int sqlite3_stmt_readonly(sqlite3_stmt *pStmt); @@ -4960,23 +5066,58 @@ SQLITE_API int sqlite3_stmt_readonly(sqlite3_stmt *pStmt); */ SQLITE_API int sqlite3_stmt_isexplain(sqlite3_stmt *pStmt); +/* +** CAPI3REF: Change The EXPLAIN Setting For A Prepared Statement +** METHOD: sqlite3_stmt +** +** The sqlite3_stmt_explain(S,E) interface changes the EXPLAIN +** setting for [prepared statement] S. If E is zero, then S becomes +** a normal prepared statement. If E is 1, then S behaves as if +** its SQL text began with "[EXPLAIN]". If E is 2, then S behaves as if +** its SQL text began with "[EXPLAIN QUERY PLAN]". +** +** Calling sqlite3_stmt_explain(S,E) might cause S to be reprepared. +** SQLite tries to avoid a reprepare, but a reprepare might be necessary +** on the first transition into EXPLAIN or EXPLAIN QUERY PLAN mode. +** +** Because of the potential need to reprepare, a call to +** sqlite3_stmt_explain(S,E) will fail with SQLITE_ERROR if S cannot be +** reprepared because it was created using [sqlite3_prepare()] instead of +** the newer [sqlite3_prepare_v2()] or [sqlite3_prepare_v3()] interfaces and +** hence has no saved SQL text with which to reprepare. +** +** Changing the explain setting for a prepared statement does not change +** the original SQL text for the statement. Hence, if the SQL text originally +** began with EXPLAIN or EXPLAIN QUERY PLAN, but sqlite3_stmt_explain(S,0) +** is called to convert the statement into an ordinary statement, the EXPLAIN +** or EXPLAIN QUERY PLAN keywords will still appear in the sqlite3_sql(S) +** output, even though the statement now acts like a normal SQL statement. +** +** This routine returns SQLITE_OK if the explain mode is successfully +** changed, or an error code if the explain mode could not be changed. +** The explain mode cannot be changed while a statement is active. +** Hence, it is good practice to call [sqlite3_reset(S)] +** immediately prior to calling sqlite3_stmt_explain(S,E). +*/ +SQLITE_API int sqlite3_stmt_explain(sqlite3_stmt *pStmt, int eMode); + /* ** CAPI3REF: Determine If A Prepared Statement Has Been Reset ** METHOD: sqlite3_stmt ** ** ^The sqlite3_stmt_busy(S) interface returns true (non-zero) if the -** [prepared statement] S has been stepped at least once using +** [prepared statement] S has been stepped at least once using ** [sqlite3_step(S)] but has neither run to completion (returned ** [SQLITE_DONE] from [sqlite3_step(S)]) nor ** been reset using [sqlite3_reset(S)]. ^The sqlite3_stmt_busy(S) -** interface returns false if S is a NULL pointer. If S is not a +** interface returns false if S is a NULL pointer. If S is not a ** NULL pointer and is not a pointer to a valid [prepared statement] ** object, then the behavior is undefined and probably undesirable. ** ** This interface can be used in combination [sqlite3_next_stmt()] -** to locate all prepared statements associated with a database +** to locate all prepared statements associated with a database ** connection that are in need of being reset. This can be used, -** for example, in diagnostic routines to search for prepared +** for example, in diagnostic routines to search for prepared ** statements that are holding a transaction open. */ SQLITE_API int sqlite3_stmt_busy(sqlite3_stmt*); @@ -4995,7 +5136,7 @@ SQLITE_API int sqlite3_stmt_busy(sqlite3_stmt*); ** will accept either a protected or an unprotected sqlite3_value. ** Every interface that accepts sqlite3_value arguments specifies ** whether or not it requires a protected sqlite3_value. The -** [sqlite3_value_dup()] interface can be used to construct a new +** [sqlite3_value_dup()] interface can be used to construct a new ** protected sqlite3_value from an unprotected sqlite3_value. ** ** The terms "protected" and "unprotected" refer to whether or not @@ -5003,7 +5144,7 @@ SQLITE_API int sqlite3_stmt_busy(sqlite3_stmt*); ** sqlite3_value object but no mutex is held for an unprotected ** sqlite3_value object. If SQLite is compiled to be single-threaded ** (with [SQLITE_THREADSAFE=0] and with [sqlite3_threadsafe()] returning 0) -** or if SQLite is run in one of reduced mutex modes +** or if SQLite is run in one of reduced mutex modes ** [SQLITE_CONFIG_SINGLETHREAD] or [SQLITE_CONFIG_MULTITHREAD] ** then there is no distinction between protected and unprotected ** sqlite3_value objects and they can be used interchangeably. However, @@ -5013,6 +5154,8 @@ SQLITE_API int sqlite3_stmt_busy(sqlite3_stmt*); ** ** ^The sqlite3_value objects that are passed as parameters into the ** implementation of [application-defined SQL functions] are protected. +** ^The sqlite3_value objects returned by [sqlite3_vtab_rhs_value()] +** are protected. ** ^The sqlite3_value object returned by ** [sqlite3_column_value()] is unprotected. ** Unprotected sqlite3_value objects may only be used as arguments @@ -5028,7 +5171,7 @@ typedef struct sqlite3_value sqlite3_value; ** ** The context in which an SQL function executes is stored in an ** sqlite3_context object. ^A pointer to an sqlite3_context object -** is always first parameter to [application-defined SQL functions]. +** is always the first parameter to [application-defined SQL functions]. ** The application-defined SQL function implementation will pass this ** pointer through into calls to [sqlite3_result_int | sqlite3_result()], ** [sqlite3_aggregate_context()], [sqlite3_user_data()], @@ -5044,7 +5187,7 @@ typedef struct sqlite3_context sqlite3_context; ** METHOD: sqlite3_stmt ** ** ^(In the SQL statement text input to [sqlite3_prepare_v2()] and its variants, -** literals may be replaced by a [parameter] that matches one of following +** literals may be replaced by a [parameter] that matches one of the following ** templates: ** **
      @@ -5072,12 +5215,30 @@ typedef struct sqlite3_context sqlite3_context; ** [sqlite3_bind_parameter_index()] API if desired. ^The index ** for "?NNN" parameters is the value of NNN. ** ^The NNN value must be between 1 and the [sqlite3_limit()] -** parameter [SQLITE_LIMIT_VARIABLE_NUMBER] (default value: 999). +** parameter [SQLITE_LIMIT_VARIABLE_NUMBER] (default value: 32766). ** ** ^The third argument is the value to bind to the parameter. ** ^If the third parameter to sqlite3_bind_text() or sqlite3_bind_text16() ** or sqlite3_bind_blob() is a NULL pointer then the fourth parameter ** is ignored and the end result is the same as sqlite3_bind_null(). +** ^If the third parameter to sqlite3_bind_text() is not NULL, then +** it should be a pointer to well-formed UTF8 text. +** ^If the third parameter to sqlite3_bind_text16() is not NULL, then +** it should be a pointer to well-formed UTF16 text. +** ^If the third parameter to sqlite3_bind_text64() is not NULL, then +** it should be a pointer to a well-formed unicode string that is +** either UTF8 if the sixth parameter is SQLITE_UTF8 or SQLITE_UTF8_ZT, +** or UTF16 otherwise. +** +** [[byte-order determination rules]] ^The byte-order of +** UTF16 input text is determined by the byte-order mark (BOM, U+FEFF) +** found in the first character, which is removed, or in the absence of a BOM +** the byte order is the native byte order of the host +** machine for sqlite3_bind_text16() or the byte order specified in +** the 6th parameter for sqlite3_bind_text64().)^ +** ^If UTF16 input text contains invalid unicode +** characters, then SQLite might change those invalid characters +** into the unicode replacement character: U+FFFD. ** ** ^(In those routines that have a fourth argument, its value is the ** number of bytes in the parameter. To be clear: the value is the @@ -5091,28 +5252,37 @@ typedef struct sqlite3_context sqlite3_context; ** or sqlite3_bind_text16() or sqlite3_bind_text64() then ** that parameter must be the byte offset ** where the NUL terminator would occur assuming the string were NUL -** terminated. If any NUL characters occur at byte offsets less than +** terminated. If any NUL characters occur at byte offsets less than ** the value of the fourth parameter then the resulting string value will ** contain embedded NULs. The result of expressions involving strings ** with embedded NULs is undefined. ** -** ^The fifth argument to the BLOB and string binding interfaces -** is a destructor used to dispose of the BLOB or -** string after SQLite has finished with it. ^The destructor is called -** to dispose of the BLOB or string even if the call to the bind API fails, -** except the destructor is not called if the third parameter is a NULL -** pointer or the fourth parameter is negative. -** ^If the fifth argument is -** the special value [SQLITE_STATIC], then SQLite assumes that the -** information is in static, unmanaged space and does not need to be freed. -** ^If the fifth argument has the value [SQLITE_TRANSIENT], then -** SQLite makes its own private copy of the data immediately, before -** the sqlite3_bind_*() routine returns. -** -** ^The sixth argument to sqlite3_bind_text64() must be one of -** [SQLITE_UTF8], [SQLITE_UTF16], [SQLITE_UTF16BE], or [SQLITE_UTF16LE] -** to specify the encoding of the text in the third parameter. If -** the sixth argument to sqlite3_bind_text64() is not one of the +** ^The fifth argument to the BLOB and string binding interfaces controls +** or indicates the lifetime of the object referenced by the third parameter. +** These three options exist: +** ^ (1) A destructor to dispose of the BLOB or string after SQLite has finished +** with it may be passed. ^It is called to dispose of the BLOB or string even +** if the call to the bind API fails, except the destructor is not called if +** the third parameter is a NULL pointer or the fourth parameter is negative. +** ^ (2) The special constant, [SQLITE_STATIC], may be passed to indicate that +** the application remains responsible for disposing of the object. ^In this +** case, the object and the provided pointer to it must remain valid until +** either the prepared statement is finalized or the same SQL parameter is +** bound to something else, whichever occurs sooner. +** ^ (3) The constant, [SQLITE_TRANSIENT], may be passed to indicate that the +** object is to be copied prior to the return from sqlite3_bind_*(). ^The +** object and pointer to it must remain valid until then. ^SQLite will then +** manage the lifetime of its private copy. +** +** ^The sixth argument (the E argument) +** to sqlite3_bind_text64(S,K,Z,N,D,E) must be one of +** [SQLITE_UTF8], [SQLITE_UTF8_ZT], [SQLITE_UTF16], [SQLITE_UTF16BE], +** or [SQLITE_UTF16LE] to specify the encoding of the text in the +** third parameter, Z. The special value [SQLITE_UTF8_ZT] means that the +** string argument is both UTF-8 encoded and is zero-terminated. In other +** words, SQLITE_UTF8_ZT means that the Z array is allocated to hold at +** least N+1 bytes and that the Z[N] byte is zero. If +** the E argument to sqlite3_bind_text64(S,K,Z,N,D,E) is not one of the ** allowed values shown above, or if the text encoding is different ** from the encoding specified by the sixth parameter, then the behavior ** is undefined. @@ -5130,9 +5300,11 @@ typedef struct sqlite3_context sqlite3_context; ** associated with the pointer P of type T. ^D is either a NULL pointer or ** a pointer to a destructor function for P. ^SQLite will invoke the ** destructor D with a single argument of P when it is finished using -** P. The T parameter should be a static string, preferably a string -** literal. The sqlite3_bind_pointer() routine is part of the -** [pointer passing interface] added for SQLite 3.20.0. +** P, even if the call to sqlite3_bind_pointer() fails. Due to a +** historical design quirk, results are undefined if D is +** SQLITE_TRANSIENT. The T parameter should be a static string, +** preferably a string literal. The sqlite3_bind_pointer() routine is +** part of the [pointer passing interface] added for SQLite 3.20.0. ** ** ^If any of the sqlite3_bind_*() routines are called with a NULL pointer ** for the [prepared statement] or with a prepared statement for which @@ -5253,7 +5425,7 @@ SQLITE_API int sqlite3_clear_bindings(sqlite3_stmt*); ** METHOD: sqlite3_stmt ** ** ^Return the number of columns in the result set returned by the -** [prepared statement]. ^If this routine returns 0, that means the +** [prepared statement]. ^If this routine returns 0, that means the ** [prepared statement] returns no data (for example an [UPDATE]). ** ^However, just because this routine returns a positive number does not ** mean that one or more rows of data will be returned. ^A SELECT statement @@ -5299,7 +5471,7 @@ SQLITE_API const void *sqlite3_column_name16(sqlite3_stmt*, int N); ** METHOD: sqlite3_stmt ** ** ^These routines provide a means to determine the database, table, and -** table column that is the origin of a particular result column in +** table column that is the origin of a particular result column in a ** [SELECT] statement. ** ^The name of the database or table or column can be returned as ** either a UTF-8 or UTF-16 string. ^The _database_ routines return @@ -5321,7 +5493,7 @@ SQLITE_API const void *sqlite3_column_name16(sqlite3_stmt*, int N); ** ** ^If the Nth column returned by the statement is an expression or ** subquery and is not a column value, then all of these functions return -** NULL. ^These routine might also return NULL if a memory allocation error +** NULL. ^These routines might also return NULL if a memory allocation error ** occurs. ^Otherwise, they return the name of the attached database, table, ** or column that query result column was extracted from. ** @@ -5331,10 +5503,6 @@ SQLITE_API const void *sqlite3_column_name16(sqlite3_stmt*, int N); ** ^These APIs are only available if the library was compiled with the ** [SQLITE_ENABLE_COLUMN_METADATA] C-preprocessor symbol. ** -** If two or more threads call one or more of these routines against the same -** prepared statement and column at the same time then the results are -** undefined. -** ** If two or more threads call one or more ** [sqlite3_column_database_name | column metadata interfaces] ** for the same [prepared statement] and result column @@ -5439,9 +5607,9 @@ SQLITE_API const void *sqlite3_column_decltype16(sqlite3_stmt*,int); ** For all versions of SQLite up to and including 3.6.23.1, a call to ** [sqlite3_reset()] was required after sqlite3_step() returned anything ** other than [SQLITE_ROW] before any subsequent invocation of -** sqlite3_step(). Failure to reset the prepared statement using +** sqlite3_step(). Failure to reset the prepared statement using ** [sqlite3_reset()] would result in an [SQLITE_MISUSE] return from -** sqlite3_step(). But after [version 3.6.23.1] ([dateof:3.6.23.1], +** sqlite3_step(). But after [version 3.6.23.1] ([dateof:3.6.23.1]), ** sqlite3_step() began ** calling [sqlite3_reset()] automatically in this circumstance rather ** than returning [SQLITE_MISUSE]. This is not considered a compatibility @@ -5471,7 +5639,7 @@ SQLITE_API int sqlite3_step(sqlite3_stmt*); ** ^The sqlite3_data_count(P) interface returns the number of columns in the ** current row of the result set of [prepared statement] P. ** ^If prepared statement P does not have results ready to return -** (via calls to the [sqlite3_column_int | sqlite3_column_*()] of +** (via calls to the [sqlite3_column_int | sqlite3_column()] family of ** interfaces) then sqlite3_data_count(P) returns 0. ** ^The sqlite3_data_count(P) routine also returns 0 if P is a NULL pointer. ** ^The sqlite3_data_count(P) routine returns 0 if the previous call to @@ -5530,7 +5698,7 @@ SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt); ** sqlite3_column_int64→64-bit INTEGER result ** sqlite3_column_text→UTF-8 TEXT result ** sqlite3_column_text16→UTF-16 TEXT result -** sqlite3_column_value→The result as an +** sqlite3_column_value→The result as an ** [sqlite3_value|unprotected sqlite3_value] object. **     ** sqlite3_column_bytes→Size of a BLOB @@ -5578,7 +5746,7 @@ SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt); ** The return value of sqlite3_column_type() can be used to decide which ** of the first six interface should be used to extract the column value. ** The value returned by sqlite3_column_type() is only meaningful if no -** automatic type conversions have occurred for the value in question. +** automatic type conversions have occurred for the value in question. ** After a type conversion, the result of calling sqlite3_column_type() ** is undefined, though harmless. Future ** versions of SQLite may change the behavior of sqlite3_column_type() @@ -5606,7 +5774,7 @@ SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt); ** the number of bytes in that string. ** ^If the result is NULL, then sqlite3_column_bytes16() returns zero. ** -** ^The values returned by [sqlite3_column_bytes()] and +** ^The values returned by [sqlite3_column_bytes()] and ** [sqlite3_column_bytes16()] do not include the zero terminators at the end ** of the string. ^For clarity: the values returned by ** [sqlite3_column_bytes()] and [sqlite3_column_bytes16()] are the number of @@ -5616,6 +5784,10 @@ SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt); ** even empty strings, are always zero-terminated. ^The return ** value from sqlite3_column_blob() for a zero-length BLOB is a NULL pointer. ** +** ^Strings returned by sqlite3_column_text16() always have the endianness +** which is native to the platform, regardless of the text encoding set +** for the database. +** ** Warning: ^The object returned by [sqlite3_column_value()] is an ** [unprotected sqlite3_value] object. In a multithreaded environment, ** an unprotected sqlite3_value object may only be used safely with @@ -5625,11 +5797,11 @@ SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt); ** to routines like [sqlite3_value_int()], [sqlite3_value_text()], ** or [sqlite3_value_bytes()], the behavior is not threadsafe. ** Hence, the sqlite3_column_value() interface -** is normally only useful within the implementation of +** is normally only useful within the implementation of ** [application-defined SQL functions] or [virtual tables], not within ** top-level application code. ** -** The these routines may attempt to convert the datatype of the result. +** These routines may attempt to convert the datatype of the result. ** ^For example, if the internal representation is FLOAT and a text result ** is requested, [sqlite3_snprintf()] is used internally to perform the ** conversion automatically. ^(The following table details the conversions @@ -5654,7 +5826,7 @@ SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt); ** TEXT BLOB No change ** BLOB INTEGER [CAST] to INTEGER ** BLOB FLOAT [CAST] to REAL -** BLOB TEXT Add a zero terminator if needed +** BLOB TEXT [CAST] to TEXT, ensure zero terminator ** ** )^ ** @@ -5743,7 +5915,7 @@ SQLITE_API int sqlite3_column_type(sqlite3_stmt*, int iCol); ** ** ^The sqlite3_finalize() function is called to delete a [prepared statement]. ** ^If the most recent evaluation of the statement encountered no errors -** or if the statement is never been evaluated, then sqlite3_finalize() returns +** or if the statement has never been evaluated, then sqlite3_finalize() returns ** SQLITE_OK. ^If the most recent evaluation of statement S failed, then ** sqlite3_finalize(S) returns the appropriate [error code] or ** [extended error code]. @@ -5778,32 +5950,43 @@ SQLITE_API int sqlite3_finalize(sqlite3_stmt *pStmt); ** ^The [sqlite3_reset(S)] interface resets the [prepared statement] S ** back to the beginning of its program. ** -** ^If the most recent call to [sqlite3_step(S)] for the -** [prepared statement] S returned [SQLITE_ROW] or [SQLITE_DONE], -** or if [sqlite3_step(S)] has never before been called on S, -** then [sqlite3_reset(S)] returns [SQLITE_OK]. +** ^The return code from [sqlite3_reset(S)] indicates whether or not +** the previous evaluation of prepared statement S completed successfully. +** ^If [sqlite3_step(S)] has never before been called on S or if +** [sqlite3_step(S)] has not been called since the previous call +** to [sqlite3_reset(S)], then [sqlite3_reset(S)] will return +** [SQLITE_OK]. ** ** ^If the most recent call to [sqlite3_step(S)] for the ** [prepared statement] S indicated an error, then ** [sqlite3_reset(S)] returns an appropriate [error code]. +** ^The [sqlite3_reset(S)] interface might also return an [error code] +** if there were no prior errors but the process of resetting +** the prepared statement caused a new error. ^For example, if an +** [INSERT] statement with a [RETURNING] clause is only stepped one time, +** that one call to [sqlite3_step(S)] might return SQLITE_ROW but +** the overall statement might still fail and the [sqlite3_reset(S)] call +** might return SQLITE_BUSY if locking constraints prevent the +** database change from committing. Therefore, it is important that +** applications check the return code from [sqlite3_reset(S)] even if +** no prior call to [sqlite3_step(S)] indicated a problem. ** ** ^The [sqlite3_reset(S)] interface does not change the values ** of any [sqlite3_bind_blob|bindings] on the [prepared statement] S. */ SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt); + /* ** CAPI3REF: Create Or Redefine SQL Functions ** KEYWORDS: {function creation routines} -** KEYWORDS: {application-defined SQL function} -** KEYWORDS: {application-defined SQL functions} ** METHOD: sqlite3 ** ** ^These functions (collectively known as "function creation routines") ** are used to add SQL functions or aggregates or to redefine the behavior ** of existing SQL functions or aggregates. The only differences between -** the three "sqlite3_create_function*" routines are the text encoding -** expected for the second parameter (the name of the function being +** the three "sqlite3_create_function*" routines are the text encoding +** expected for the second parameter (the name of the function being ** created) and the presence or absence of a destructor callback for ** the application data pointer. Function sqlite3_create_window_function() ** is similar, but allows the user to supply the extra callback functions @@ -5817,7 +6000,7 @@ SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt); ** ^The second parameter is the name of the SQL function to be created or ** redefined. ^The length of the name is limited to 255 bytes in a UTF-8 ** representation, exclusive of the zero-terminator. ^Note that the name -** length limit is in UTF-8 bytes, not characters nor UTF-16 bytes. +** length limit is in UTF-8 bytes, not characters nor UTF-16 bytes. ** ^Any attempt to create a function with a longer name ** will result in [SQLITE_MISUSE] being returned. ** @@ -5832,7 +6015,7 @@ SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt); ** ^The fourth parameter, eTextRep, specifies what ** [SQLITE_UTF8 | text encoding] this SQL function prefers for ** its parameters. The application should set this parameter to -** [SQLITE_UTF16LE] if the function implementation invokes +** [SQLITE_UTF16LE] if the function implementation invokes ** [sqlite3_value_text16le()] on an input, or [SQLITE_UTF16BE] if the ** implementation invokes [sqlite3_value_text16be()] on an input, or ** [SQLITE_UTF16] if [sqlite3_value_text16()] is used, or [SQLITE_UTF8] @@ -5850,6 +6033,21 @@ SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt); ** perform additional optimizations on deterministic functions, so use ** of the [SQLITE_DETERMINISTIC] flag is recommended where possible. ** +** ^The fourth parameter may also optionally include the [SQLITE_DIRECTONLY] +** flag, which if present prevents the function from being invoked from +** within VIEWs, TRIGGERs, CHECK constraints, generated column expressions, +** index expressions, or the WHERE clause of partial indexes. +** +** For best security, the [SQLITE_DIRECTONLY] flag is recommended for +** all application-defined SQL functions that do not need to be +** used inside of triggers, views, CHECK constraints, or other elements of +** the database schema. This flag is especially recommended for SQL +** functions that have side effects or reveal internal application state. +** Without this flag, an attacker might be able to modify the schema of +** a database file to include invocations of the function with parameters +** chosen by the attacker, which the application will then execute when +** the database file is opened and read. +** ** ^(The fifth parameter is an arbitrary pointer. The implementation of the ** function can gain access to this pointer using [sqlite3_user_data()].)^ ** @@ -5863,21 +6061,21 @@ SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt); ** SQL function or aggregate, pass NULL pointers for all three function ** callbacks. ** -** ^The sixth, seventh, eighth and ninth parameters (xStep, xFinal, xValue +** ^The sixth, seventh, eighth and ninth parameters (xStep, xFinal, xValue ** and xInverse) passed to sqlite3_create_window_function are pointers to ** C-language callbacks that implement the new function. xStep and xFinal ** must both be non-NULL. xValue and xInverse may either both be NULL, in -** which case a regular aggregate function is created, or must both be +** which case a regular aggregate function is created, or must both be ** non-NULL, in which case the new function may be used as either an aggregate ** or aggregate window function. More details regarding the implementation -** of aggregate window functions are +** of aggregate window functions are ** [user-defined window functions|available here]. ** ** ^(If the final parameter to sqlite3_create_function_v2() or -** sqlite3_create_window_function() is not NULL, then it is destructor for -** the application data pointer. The destructor is invoked when the function -** is deleted, either by being overloaded or when the database connection -** closes.)^ ^The destructor is also invoked if the call to +** sqlite3_create_window_function() is not NULL, then it is the destructor for +** the application data pointer. The destructor is invoked when the function +** is deleted, either by being overloaded or when the database connection +** closes.)^ ^The destructor is also invoked if the call to ** sqlite3_create_function_v2() fails. ^When the destructor callback is ** invoked, it is passed a single argument which is a copy of the application ** data pointer which was the fifth parameter to sqlite3_create_function_v2(). @@ -5890,7 +6088,7 @@ SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt); ** nArg parameter is a better match than a function implementation with ** a negative nArg. ^A function where the preferred text encoding ** matches the database encoding is a better -** match than a function where the encoding is different. +** match than a function where the encoding is different. ** ^A function where the encoding difference is between UTF16le and UTF16be ** is a closer match than a function where the encoding difference is ** between UTF8 and UTF16. @@ -5949,8 +6147,54 @@ SQLITE_API int sqlite3_create_window_function( /* ** CAPI3REF: Text Encodings ** -** These constant define integer codes that represent the various +** These constants define integer codes that represent the various ** text encodings supported by SQLite. +** +**
      +** [[SQLITE_UTF8]]
      SQLITE_UTF8
      Text is encoding as UTF-8
      +** +** [[SQLITE_UTF16LE]]
      SQLITE_UTF16LE
      Text is encoding as UTF-16 +** with each code point being expressed "little endian" - the least significant +** byte first. This is the usual encoding, for example on Windows.
      +** +** [[SQLITE_UTF16BE]]
      SQLITE_UTF16BE
      Text is encoding as UTF-16 +** with each code point being expressed "big endian" - the most significant +** byte first. This encoding is less common, but is still sometimes seen, +** specially on older systems. +** +** [[SQLITE_UTF16]]
      SQLITE_UTF16
      Text is encoding as UTF-16 +** with each code point being expressed either little endian or as big +** endian, according to the native endianness of the host computer. +** +** [[SQLITE_ANY]]
      SQLITE_ANY
      This encoding value may only be used +** to declare the preferred text for [application-defined SQL functions] +** created using [sqlite3_create_function()] and similar. If the preferred +** encoding (the 4th parameter to sqlite3_create_function() - the eTextRep +** parameter) is SQLITE_ANY, that indicates that the function does not have +** a preference regarding the text encoding of its parameters and can take +** any text encoding that the SQLite core find convenient to supply. This +** option is deprecated. Please do not use it in new applications. +** +** [[SQLITE_UTF16_ALIGNED]]
      SQLITE_UTF16_ALIGNED
      This encoding +** value may be used as the 3rd parameter (the eTextRep parameter) to +** [sqlite3_create_collation()] and similar. This encoding value means +** that the application-defined collating sequence created expects its +** input strings to be in UTF16 in native byte order, and that the start +** of the strings must be aligned to a 2-byte boundary. +** +** [[SQLITE_UTF8_ZT]]
      SQLITE_UTF8_ZT
      This option can only be +** used to specify the text encoding to strings input to +** [sqlite3_result_text64()] and [sqlite3_bind_text64()]. +** The SQLITE_UTF8_ZT encoding means that the input string (call it "z") +** is UTF-8 encoded and that it is zero-terminated. If the length parameter +** (call it "n") is non-negative, this encoding option means that the caller +** guarantees that z array contains at least n+1 bytes and that the z[n] +** byte has a value of zero. +** This option gives the same output as SQLITE_UTF8, but can be more efficient +** by avoiding the need to make a copy of the input string, in some cases. +** However, if z is allocated to hold fewer than n+1 bytes or if the +** z[n] byte is not zero, undefined behavior may result. +**
      */ #define SQLITE_UTF8 1 /* IMP: R-37514-35566 */ #define SQLITE_UTF16LE 2 /* IMP: R-03371-37637 */ @@ -5958,23 +6202,120 @@ SQLITE_API int sqlite3_create_window_function( #define SQLITE_UTF16 4 /* Use native byte order */ #define SQLITE_ANY 5 /* Deprecated */ #define SQLITE_UTF16_ALIGNED 8 /* sqlite3_create_collation only */ +#define SQLITE_UTF8_ZT 16 /* Zero-terminated UTF8 */ /* ** CAPI3REF: Function Flags ** -** These constants may be ORed together with the +** These constants may be ORed together with the ** [SQLITE_UTF8 | preferred text encoding] as the fourth argument ** to [sqlite3_create_function()], [sqlite3_create_function16()], or ** [sqlite3_create_function_v2()]. +** +**
      +** [[SQLITE_DETERMINISTIC]]
      SQLITE_DETERMINISTIC
      +** The SQLITE_DETERMINISTIC flag means that the new function always gives +** the same output when the input parameters are the same. +** The [abs|abs() function] is deterministic, for example, but +** [randomblob|randomblob()] is not. Functions must +** be deterministic in order to be used in certain contexts such as +** with the WHERE clause of [partial indexes] or in [generated columns]. +** SQLite might also optimize deterministic functions by factoring them +** out of inner loops. +**
      +** +** [[SQLITE_DIRECTONLY]]
      SQLITE_DIRECTONLY
      +** The SQLITE_DIRECTONLY flag means that the function may only be invoked +** from top-level SQL, and cannot be used in VIEWs or TRIGGERs nor in +** schema structures such as [CHECK constraints], [DEFAULT clauses], +** [expression indexes], [partial indexes], or [generated columns]. +**

      +** The SQLITE_DIRECTONLY flag is recommended for any +** [application-defined SQL function] +** that has side-effects or that could potentially leak sensitive information. +** This will prevent attacks in which an application is tricked +** into using a database file that has had its schema surreptitiously +** modified to invoke the application-defined function in ways that are +** harmful. +**

      +** Some people say it is good practice to set SQLITE_DIRECTONLY on all +** [application-defined SQL functions], regardless of whether or not they +** are security sensitive, as doing so prevents those functions from being used +** inside of the database schema, and thus ensures that the database +** can be inspected and modified using generic tools (such as the [CLI]) +** that do not have access to the application-defined functions. +**

      +** +** [[SQLITE_INNOCUOUS]]
      SQLITE_INNOCUOUS
      +** The SQLITE_INNOCUOUS flag means that the function is unlikely +** to cause problems even if misused. An innocuous function should have +** no side effects and should not depend on any values other than its +** input parameters. The [abs|abs() function] is an example of an +** innocuous function. +** The [load_extension() SQL function] is not innocuous because of its +** side effects. +**

      SQLITE_INNOCUOUS is similar to SQLITE_DETERMINISTIC, but is not +** exactly the same. The [random|random() function] is an example of a +** function that is innocuous but not deterministic. +**

      Some heightened security settings +** ([SQLITE_DBCONFIG_TRUSTED_SCHEMA] and [PRAGMA trusted_schema=OFF]) +** disable the use of SQL functions inside views and triggers and in +** schema structures such as [CHECK constraints], [DEFAULT clauses], +** [expression indexes], [partial indexes], and [generated columns] unless +** the function is tagged with SQLITE_INNOCUOUS. Most built-in functions +** are innocuous. Developers are advised to avoid using the +** SQLITE_INNOCUOUS flag for application-defined functions unless the +** function has been carefully audited and found to be free of potentially +** security-adverse side-effects and information-leaks. +**

      +** +** [[SQLITE_SUBTYPE]]
      SQLITE_SUBTYPE
      +** The SQLITE_SUBTYPE flag indicates to SQLite that a function might call +** [sqlite3_value_subtype()] to inspect the sub-types of its arguments. +** This flag instructs SQLite to omit some corner-case optimizations that +** might disrupt the operation of the [sqlite3_value_subtype()] function, +** causing it to return zero rather than the correct subtype(). +** All SQL functions that invoke [sqlite3_value_subtype()] should have this +** property. If the SQLITE_SUBTYPE property is omitted, then the return +** value from [sqlite3_value_subtype()] might sometimes be zero even though +** a non-zero subtype was specified by the function argument expression. +** +** [[SQLITE_RESULT_SUBTYPE]]
      SQLITE_RESULT_SUBTYPE
      +** The SQLITE_RESULT_SUBTYPE flag indicates to SQLite that a function might call +** [sqlite3_result_subtype()] to cause a sub-type to be associated with its +** result. +** Every function that invokes [sqlite3_result_subtype()] should have this +** property. If it does not, then the call to [sqlite3_result_subtype()] +** might become a no-op if the function is used as a term in an +** [expression index]. On the other hand, SQL functions that never invoke +** [sqlite3_result_subtype()] should avoid setting this property, as the +** purpose of this property is to disable certain optimizations that are +** incompatible with subtypes. +** +** [[SQLITE_SELFORDER1]]
      SQLITE_SELFORDER1
      +** The SQLITE_SELFORDER1 flag indicates that the function is an aggregate +** that internally orders the values provided to the first argument. The +** ordered-set aggregate SQL notation with a single ORDER BY term can be +** used to invoke this function. If the ordered-set aggregate notation is +** used on a function that lacks this flag, then an error is raised. Note +** that the ordered-set aggregate syntax is only available if SQLite is +** built using the -DSQLITE_ENABLE_ORDERED_SET_AGGREGATES compile-time option. +**
      +**
      */ -#define SQLITE_DETERMINISTIC 0x800 +#define SQLITE_DETERMINISTIC 0x000000800 +#define SQLITE_DIRECTONLY 0x000080000 +#define SQLITE_SUBTYPE 0x000100000 +#define SQLITE_INNOCUOUS 0x000200000 +#define SQLITE_RESULT_SUBTYPE 0x001000000 +#define SQLITE_SELFORDER1 0x002000000 /* ** CAPI3REF: Deprecated Functions ** DEPRECATED ** ** These functions are [deprecated]. In order to maintain -** backwards compatibility with older code, these functions continue +** backwards compatibility with older code, these functions continue ** to be supported. However, new applications should avoid ** the use of these functions. To encourage programmers to avoid ** these functions, we will not explain what they do. @@ -6026,8 +6367,8 @@ SQLITE_API SQLITE_DEPRECATED int sqlite3_memory_alarm(void(*)(void*,sqlite3_int6 ** ** These routines extract type, size, and content information from ** [protected sqlite3_value] objects. Protected sqlite3_value objects -** are used to pass parameter information into implementation of -** [application-defined SQL functions] and [virtual tables]. +** are used to pass parameter information into the functions that +** implement [application-defined SQL functions] and [virtual tables]. ** ** These routines work only with [protected sqlite3_value] objects. ** Any attempt to use these routines on an [unprotected sqlite3_value] @@ -6042,11 +6383,11 @@ SQLITE_API SQLITE_DEPRECATED int sqlite3_memory_alarm(void(*)(void*,sqlite3_int6 ** sqlite3_value_text16be() and sqlite3_value_text16le() interfaces ** extract UTF-16 strings as big-endian and little-endian respectively. ** -** ^If [sqlite3_value] object V was initialized +** ^If [sqlite3_value] object V was initialized ** using [sqlite3_bind_pointer(S,I,P,X,D)] or [sqlite3_result_pointer(C,P,X,D)] ** and if X and Y are strings that compare equal according to strcmp(X,Y), ** then sqlite3_value_pointer(V,Y) will return the pointer P. ^Otherwise, -** sqlite3_value_pointer(V,Y) returns a NULL. The sqlite3_bind_pointer() +** sqlite3_value_pointer(V,Y) returns a NULL. The sqlite3_bind_pointer() ** routine is part of the [pointer passing interface] added for SQLite 3.20.0. ** ** ^(The sqlite3_value_type(V) interface returns the @@ -6072,7 +6413,7 @@ SQLITE_API SQLITE_DEPRECATED int sqlite3_memory_alarm(void(*)(void*,sqlite3_int6 ** sqlite3_value_nochange(X) interface returns true if and only if ** the column corresponding to X is unchanged by the UPDATE operation ** that the xUpdate method call was invoked to implement and if -** and the prior [xColumn] method call that was invoked to extracted +** the prior [xColumn] method call that was invoked to extract ** the value for that column returned without setting a result (probably ** because it queried [sqlite3_vtab_nochange()] and found that the column ** was unchanging). ^Within an [xUpdate] method, any value for which @@ -6084,7 +6425,7 @@ SQLITE_API SQLITE_DEPRECATED int sqlite3_memory_alarm(void(*)(void*,sqlite3_int6 ** ^The sqlite3_value_frombind(X) interface returns non-zero if the ** value X originated from one of the [sqlite3_bind_int|sqlite3_bind()] ** interfaces. ^If X comes from an SQL literal value, or a table column, -** and expression, then sqlite3_value_frombind(X) returns zero. +** or an expression, then sqlite3_value_frombind(X) returns zero. ** ** Please pay particular attention to the fact that the pointer returned ** from [sqlite3_value_blob()], [sqlite3_value_text()], or @@ -6096,26 +6437,22 @@ SQLITE_API SQLITE_DEPRECATED int sqlite3_memory_alarm(void(*)(void*,sqlite3_int6 ** the SQL function that supplied the [sqlite3_value*] parameters. ** ** As long as the input parameter is correct, these routines can only -** fail if an out-of-memory error occurs during a format conversion. -** Only the following subset of interfaces are subject to out-of-memory -** errors: -** -**
        -**
      • sqlite3_value_blob() -**
      • sqlite3_value_text() -**
      • sqlite3_value_text16() -**
      • sqlite3_value_text16le() -**
      • sqlite3_value_text16be() -**
      • sqlite3_value_bytes() -**
      • sqlite3_value_bytes16() -**
      -** +** fail if an out-of-memory error occurs while trying to do a +** UTF8→UTF16 or UTF16→UTF8 conversion. ** If an out-of-memory error occurs, then the return value from these ** routines is the same as if the column had contained an SQL NULL value. -** Valid SQL NULL returns can be distinguished from out-of-memory errors -** by invoking the [sqlite3_errcode()] immediately after the suspect +** If the input sqlite3_value was not obtained from [sqlite3_value_dup()], +** then valid SQL NULL returns can also be distinguished from +** out-of-memory errors after extracting the value +** by invoking the [sqlite3_errcode()] immediately after the suspicious ** return value is obtained and before any ** other SQLite interface is called on the same [database connection]. +** If the input sqlite3_value was obtained from sqlite3_value_dup() then +** it is disconnected from the database connection and so sqlite3_errcode() +** will not work. +** In that case, the only way to distinguish an out-of-memory +** condition from a true SQL NULL is to invoke sqlite3_value_type() on the +** input to see if it is NULL prior to trying to extract the value. */ SQLITE_API const void *sqlite3_value_blob(sqlite3_value*); SQLITE_API double sqlite3_value_double(sqlite3_value*); @@ -6133,6 +6470,29 @@ SQLITE_API int sqlite3_value_numeric_type(sqlite3_value*); SQLITE_API int sqlite3_value_nochange(sqlite3_value*); SQLITE_API int sqlite3_value_frombind(sqlite3_value*); +/* +** CAPI3REF: Report the internal text encoding state of an sqlite3_value object +** METHOD: sqlite3_value +** +** ^(The sqlite3_value_encoding(X) interface returns one of [SQLITE_UTF8], +** [SQLITE_UTF16BE], or [SQLITE_UTF16LE] according to the current text encoding +** of the value X, assuming that X has type TEXT.)^ If sqlite3_value_type(X) +** returns something other than SQLITE_TEXT, then the return value from +** sqlite3_value_encoding(X) is meaningless. ^Calls to +** [sqlite3_value_text(X)], [sqlite3_value_text16(X)], +** [sqlite3_value_text16be(X)], +** [sqlite3_value_text16le(X)], [sqlite3_value_bytes(X)], or +** [sqlite3_value_bytes16(X)] might change the encoding of the value X and +** thus change the return from subsequent calls to sqlite3_value_encoding(X). +** +** This routine is intended for used by applications that test and validate +** the SQLite implementation. This routine is inquiring about the opaque +** internal state of an [sqlite3_value] object. Ordinary applications should +** not need to know what the internal state of an sqlite3_value object is and +** hence should not need to use this interface. +*/ +SQLITE_API int sqlite3_value_encoding(sqlite3_value*); + /* ** CAPI3REF: Finding The Subtype Of SQL Values ** METHOD: sqlite3_value @@ -6142,6 +6502,12 @@ SQLITE_API int sqlite3_value_frombind(sqlite3_value*); ** information can be used to pass a limited amount of context from ** one SQL function to another. Use the [sqlite3_result_subtype()] ** routine to set the subtype for the return value of an SQL function. +** +** Every [application-defined SQL function] that invokes this interface +** should include the [SQLITE_SUBTYPE] property in the text +** encoding argument when the function is [sqlite3_create_function|registered]. +** If the [SQLITE_SUBTYPE] property is omitted, then sqlite3_value_subtype() +** might return zero instead of the upstream subtype in some corner cases. */ SQLITE_API unsigned int sqlite3_value_subtype(sqlite3_value*); @@ -6150,10 +6516,11 @@ SQLITE_API unsigned int sqlite3_value_subtype(sqlite3_value*); ** METHOD: sqlite3_value ** ** ^The sqlite3_value_dup(V) interface makes a copy of the [sqlite3_value] -** object D and returns a pointer to that copy. ^The [sqlite3_value] returned +** object V and returns a pointer to that copy. ^The [sqlite3_value] returned ** is a [protected sqlite3_value] object even if the input is not. ** ^The sqlite3_value_dup(V) interface returns NULL if V is NULL or if a -** memory allocation fails. +** memory allocation fails. ^If V is a [pointer value], then the result +** of sqlite3_value_dup(V) is a NULL value. ** ** ^The sqlite3_value_free(V) interface frees an [sqlite3_value] object ** previously obtained from [sqlite3_value_dup()]. ^If V is a NULL pointer @@ -6169,9 +6536,9 @@ SQLITE_API void sqlite3_value_free(sqlite3_value*); ** Implementations of aggregate SQL functions use this ** routine to allocate memory for storing their state. ** -** ^The first time the sqlite3_aggregate_context(C,N) routine is called -** for a particular aggregate function, SQLite -** allocates N of memory, zeroes out that memory, and returns a pointer +** ^The first time the sqlite3_aggregate_context(C,N) routine is called +** for a particular aggregate function, SQLite allocates +** N bytes of memory, zeroes out that memory, and returns a pointer ** to the new memory. ^On second and subsequent calls to ** sqlite3_aggregate_context() for the same aggregate function instance, ** the same buffer is returned. Sqlite3_aggregate_context() is normally @@ -6182,19 +6549,19 @@ SQLITE_API void sqlite3_value_free(sqlite3_value*); ** In those cases, sqlite3_aggregate_context() might be called for the ** first time from within xFinal().)^ ** -** ^The sqlite3_aggregate_context(C,N) routine returns a NULL pointer +** ^The sqlite3_aggregate_context(C,N) routine returns a NULL pointer ** when first called if N is less than or equal to zero or if a memory -** allocate error occurs. +** allocation error occurs. ** ** ^(The amount of space allocated by sqlite3_aggregate_context(C,N) is -** determined by the N parameter on first successful call. Changing the -** value of N in subsequent call to sqlite3_aggregate_context() within +** determined by the N parameter on the first successful call. Changing the +** value of N in any subsequent call to sqlite3_aggregate_context() within ** the same aggregate function instance will not resize the memory ** allocation.)^ Within the xFinal callback, it is customary to set -** N=0 in calls to sqlite3_aggregate_context(C,N) so that no +** N=0 in calls to sqlite3_aggregate_context(C,N) so that no ** pointless memory allocations occur. ** -** ^SQLite automatically frees the memory allocated by +** ^SQLite automatically frees the memory allocated by ** sqlite3_aggregate_context() when the aggregate query concludes. ** ** The first parameter must be a copy of the @@ -6239,48 +6606,56 @@ SQLITE_API sqlite3 *sqlite3_context_db_handle(sqlite3_context*); ** METHOD: sqlite3_context ** ** These functions may be used by (non-aggregate) SQL functions to -** associate metadata with argument values. If the same value is passed to -** multiple invocations of the same SQL function during query execution, under -** some circumstances the associated metadata may be preserved. An example -** of where this might be useful is in a regular-expression matching -** function. The compiled version of the regular expression can be stored as -** metadata associated with the pattern string. -** Then as long as the pattern string remains the same, +** associate auxiliary data with argument values. If the same argument +** value is passed to multiple invocations of the same SQL function during +** query execution, under some circumstances the associated auxiliary data +** might be preserved. An example of where this might be useful is in a +** regular-expression matching function. The compiled version of the regular +** expression can be stored as auxiliary data associated with the pattern +** string. Then as long as the pattern string remains the same, ** the compiled regular expression can be reused on multiple ** invocations of the same function. ** -** ^The sqlite3_get_auxdata(C,N) interface returns a pointer to the metadata -** associated by the sqlite3_set_auxdata(C,N,P,X) function with the Nth argument -** value to the application-defined function. ^N is zero for the left-most -** function argument. ^If there is no metadata -** associated with the function argument, the sqlite3_get_auxdata(C,N) interface -** returns a NULL pointer. +** ^The sqlite3_get_auxdata(C,N) interface returns a pointer to the auxiliary +** data associated by the sqlite3_set_auxdata(C,N,P,X) function with the +** Nth argument value to the application-defined function. ^N is zero +** for the left-most function argument. ^If there is no auxiliary data +** associated with the function argument, the sqlite3_get_auxdata(C,N) +** interface returns a NULL pointer. ** -** ^The sqlite3_set_auxdata(C,N,P,X) interface saves P as metadata for the N-th -** argument of the application-defined function. ^Subsequent +** ^The sqlite3_set_auxdata(C,N,P,X) interface saves P as auxiliary data for the +** N-th argument of the application-defined function. ^Subsequent ** calls to sqlite3_get_auxdata(C,N) return P from the most recent -** sqlite3_set_auxdata(C,N,P,X) call if the metadata is still valid or -** NULL if the metadata has been discarded. +** sqlite3_set_auxdata(C,N,P,X) call if the auxiliary data is still valid or +** NULL if the auxiliary data has been discarded. ** ^After each call to sqlite3_set_auxdata(C,N,P,X) where X is not NULL, ** SQLite will invoke the destructor function X with parameter P exactly -** once, when the metadata is discarded. -** SQLite is free to discard the metadata at any time, including:
        +** once, when the auxiliary data is discarded. +** SQLite is free to discard the auxiliary data at any time, including:
          **
        • ^(when the corresponding function parameter changes)^, or **
        • ^(when [sqlite3_reset()] or [sqlite3_finalize()] is called for the ** SQL statement)^, or **
        • ^(when sqlite3_set_auxdata() is invoked again on the same ** parameter)^, or -**
        • ^(during the original sqlite3_set_auxdata() call when a memory -** allocation error occurs.)^
        +**
      • ^(during the original sqlite3_set_auxdata() call when a memory +** allocation error occurs.)^ +**
      • ^(during the original sqlite3_set_auxdata() call if the function +** is evaluated during query planning instead of during query execution, +** as sometimes happens with [SQLITE_ENABLE_STAT4].)^
      ** -** Note the last bullet in particular. The destructor X in +** Note the last two bullets in particular. The destructor X in ** sqlite3_set_auxdata(C,N,P,X) might be called immediately, before the ** sqlite3_set_auxdata() interface even returns. Hence sqlite3_set_auxdata() ** should be called near the end of the function implementation and the ** function implementation should not make any use of P after -** sqlite3_set_auxdata() has been called. -** -** ^(In practice, metadata is preserved between function calls for +** sqlite3_set_auxdata() has been called. Furthermore, a call to +** sqlite3_get_auxdata() that occurs immediately after a corresponding call +** to sqlite3_set_auxdata() might still return NULL if an out-of-memory +** condition occurred during the sqlite3_set_auxdata() call or if the +** function is being evaluated during query planning rather than during +** query execution. +** +** ^(In practice, auxiliary data is preserved between function calls for ** function parameters that are compile-time constants, including literal ** values and [parameters] and expressions composed from the same.)^ ** @@ -6290,10 +6665,72 @@ SQLITE_API sqlite3 *sqlite3_context_db_handle(sqlite3_context*); ** ** These routines must be called from the same thread in which ** the SQL function is running. +** +** See also: [sqlite3_get_clientdata()] and [sqlite3_set_clientdata()]. */ SQLITE_API void *sqlite3_get_auxdata(sqlite3_context*, int N); SQLITE_API void sqlite3_set_auxdata(sqlite3_context*, int N, void*, void (*)(void*)); +/* +** CAPI3REF: Database Connection Client Data +** METHOD: sqlite3 +** +** These functions are used to associate one or more named pointers +** with a [database connection]. +** A call to sqlite3_set_clientdata(D,N,P,X) causes the pointer P +** to be attached to [database connection] D using name N. Subsequent +** calls to sqlite3_get_clientdata(D,N) will return a copy of pointer P +** or a NULL pointer if there were no prior calls to +** sqlite3_set_clientdata() with the same values of D and N. +** Names are compared using strcmp() and are thus case sensitive. +** It returns 0 on success and SQLITE_NOMEM on allocation failure. +** +** If P and X are both non-NULL, then the destructor X is invoked with +** argument P on the first of the following occurrences: +**
        +**
      • An out-of-memory error occurs during the call to +** sqlite3_set_clientdata() which attempts to register pointer P. +**
      • A subsequent call to sqlite3_set_clientdata(D,N,P,X) is made +** with the same D and N parameters. +**
      • The database connection closes. SQLite does not make any guarantees +** about the order in which destructors are called, only that all +** destructors will be called exactly once at some point during the +** database connection closing process. +**
      +** +** SQLite does not do anything with client data other than invoke +** destructors on the client data at the appropriate time. The intended +** use for client data is to provide a mechanism for wrapper libraries +** to store additional information about an SQLite database connection. +** +** There is no limit (other than available memory) on the number of different +** client data pointers (with different names) that can be attached to a +** single database connection. However, the current implementation stores +** the content on a linked list. Insert and retrieval performance will +** be proportional to the number of entries. The design use case, and +** the use case for which the implementation is optimized, is +** that an application will store only small number of client data names, +** typically just one or two. This interface is not intended to be a +** generalized key/value store for thousands or millions of keys. It +** will work for that, but performance might be disappointing. +** +** There is no way to enumerate the client data pointers +** associated with a database connection. The N parameter can be thought +** of as a secret key such that only code that knows the secret key is able +** to access the associated data. +** +** Security Warning: These interfaces should not be exposed in scripting +** languages or in other circumstances where it might be possible for an +** attacker to invoke them. Any agent that can invoke these interfaces +** can probably also take control of the process. +** +** Database connection client data is only available for SQLite +** version 3.44.0 ([dateof:3.44.0]) and later. +** +** See also: [sqlite3_set_auxdata()] and [sqlite3_get_auxdata()]. +*/ +SQLITE_API void *sqlite3_get_clientdata(sqlite3*,const char*); +SQLITE_API int sqlite3_set_clientdata(sqlite3*, const char*, void*, void(*)(void*)); /* ** CAPI3REF: Constants Defining Special Destructor Behavior @@ -6345,8 +6782,9 @@ typedef void (*sqlite3_destructor_type)(void*); ** 2nd parameter of sqlite3_result_error() or sqlite3_result_error16() ** as the text of an error message. ^SQLite interprets the error ** message string from sqlite3_result_error() as UTF-8. ^SQLite -** interprets the string from sqlite3_result_error16() as UTF-16 in native -** byte order. ^If the third parameter to sqlite3_result_error() +** interprets the string from sqlite3_result_error16() as UTF-16 using +** the same [byte-order determination rules] as [sqlite3_bind_text16()]. +** ^If the third parameter to sqlite3_result_error() ** or sqlite3_result_error16() is negative then SQLite takes as the error ** message all text up through the first zero character. ** ^If the third parameter to sqlite3_result_error() or @@ -6382,21 +6820,26 @@ typedef void (*sqlite3_destructor_type)(void*); ** set the return value of the application-defined function to be ** a text string which is represented as UTF-8, UTF-16 native byte order, ** UTF-16 little endian, or UTF-16 big endian, respectively. -** ^The sqlite3_result_text64() interface sets the return value of an +** ^The sqlite3_result_text64(C,Z,N,D,E) interface sets the return value of an ** application-defined function to be a text string in an encoding -** specified by the fifth (and last) parameter, which must be one -** of [SQLITE_UTF8], [SQLITE_UTF16], [SQLITE_UTF16BE], or [SQLITE_UTF16LE]. +** specified the E parameter, which must be one +** of [SQLITE_UTF8], [SQLITE_UTF8_ZT], [SQLITE_UTF16], [SQLITE_UTF16BE], +** or [SQLITE_UTF16LE]. ^The special value [SQLITE_UTF8_ZT] means that +** the result text is both UTF-8 and zero-terminated. In other words, +** SQLITE_UTF8_ZT means that the Z array holds at least N+1 bytes and that +** the Z[N] is zero. ** ^SQLite takes the text result from the application from ** the 2nd parameter of the sqlite3_result_text* interfaces. -** ^If the 3rd parameter to the sqlite3_result_text* interfaces -** is negative, then SQLite takes result text from the 2nd parameter -** through the first zero character. +** ^If the 3rd parameter to any of the sqlite3_result_text* interfaces +** other than sqlite3_result_text64() is negative, then SQLite computes +** the string length itself by searching the 2nd parameter for the first +** zero character. ** ^If the 3rd parameter to the sqlite3_result_text* interfaces ** is non-negative, then as many bytes (not characters) of the text ** pointed to by the 2nd parameter are taken as the application-defined ** function result. If the 3rd parameter is non-negative, then it ** must be the byte offset into the string where the NUL terminator would -** appear if the string where NUL terminated. If any NUL characters occur +** appear if the string were NUL terminated. If any NUL characters occur ** in the string at a byte offset that is less than the value of the 3rd ** parameter, then the resulting string will contain embedded NULs and the ** result of expressions operating on strings with embedded NULs is undefined. @@ -6414,6 +6857,25 @@ typedef void (*sqlite3_destructor_type)(void*); ** then SQLite makes a copy of the result into space obtained ** from [sqlite3_malloc()] before it returns. ** +** ^For the sqlite3_result_text16(), sqlite3_result_text16le(), and +** sqlite3_result_text16be() routines, and for sqlite3_result_text64() +** when the encoding is not UTF8, if the input UTF16 begins with a +** byte-order mark (BOM, U+FEFF) then the BOM is removed from the +** string and the rest of the string is interpreted according to the +** byte-order specified by the BOM. ^The byte-order specified by +** the BOM at the beginning of the text overrides the byte-order +** specified by the interface procedure. ^So, for example, if +** sqlite3_result_text16le() is invoked with text that begins +** with bytes 0xfe, 0xff (a big-endian byte-order mark) then the +** first two bytes of input are skipped and the remaining input +** is interpreted as UTF16BE text. +** +** ^For UTF16 input text to the sqlite3_result_text16(), +** sqlite3_result_text16be(), sqlite3_result_text16le(), and +** sqlite3_result_text64() routines, if the text contains invalid +** UTF16 characters, the invalid characters might be converted +** into the unicode replacement character, U+FFFD. +** ** ^The sqlite3_result_value() interface sets the result of ** the application-defined function to be a copy of the ** [unprotected sqlite3_value] object specified by the 2nd parameter. ^The @@ -6426,7 +6888,7 @@ typedef void (*sqlite3_destructor_type)(void*); ** ** ^The sqlite3_result_pointer(C,P,T,D) interface sets the result to an ** SQL NULL value, just like [sqlite3_result_null(C)], except that it -** also associates the host-language pointer P or type T with that +** also associates the host-language pointer P or type T with that ** NULL value such that the pointer can be retrieved within an ** [application-defined SQL function] using [sqlite3_value_pointer()]. ** ^If the D parameter is not NULL, then it is a pointer to a destructor @@ -6435,7 +6897,7 @@ typedef void (*sqlite3_destructor_type)(void*); ** string and preferably a string literal. The sqlite3_result_pointer() ** routine is part of the [pointer passing interface] added for SQLite 3.20.0. ** -** If these routines are called from within the different thread +** If these routines are called from within a different thread ** than the one containing the application-defined function that received ** the [sqlite3_context] pointer, the results are undefined. */ @@ -6452,7 +6914,7 @@ SQLITE_API void sqlite3_result_int(sqlite3_context*, int); SQLITE_API void sqlite3_result_int64(sqlite3_context*, sqlite3_int64); SQLITE_API void sqlite3_result_null(sqlite3_context*); SQLITE_API void sqlite3_result_text(sqlite3_context*, const char*, int, void(*)(void*)); -SQLITE_API void sqlite3_result_text64(sqlite3_context*, const char*,sqlite3_uint64, +SQLITE_API void sqlite3_result_text64(sqlite3_context*, const char *z, sqlite3_uint64 n, void(*)(void*), unsigned char encoding); SQLITE_API void sqlite3_result_text16(sqlite3_context*, const void*, int, void(*)(void*)); SQLITE_API void sqlite3_result_text16le(sqlite3_context*, const void*, int,void(*)(void*)); @@ -6468,12 +6930,26 @@ SQLITE_API int sqlite3_result_zeroblob64(sqlite3_context*, sqlite3_uint64 n); ** METHOD: sqlite3_context ** ** The sqlite3_result_subtype(C,T) function causes the subtype of -** the result from the [application-defined SQL function] with -** [sqlite3_context] C to be the value T. Only the lower 8 bits +** the result from the [application-defined SQL function] with +** [sqlite3_context] C to be the value T. Only the lower 8 bits ** of the subtype T are preserved in current versions of SQLite; ** higher order bits are discarded. ** The number of subtype bytes preserved by SQLite might increase ** in future releases of SQLite. +** +** Every [application-defined SQL function] that invokes this interface +** should include the [SQLITE_RESULT_SUBTYPE] property in its +** text encoding argument when the SQL function is +** [sqlite3_create_function|registered]. If the [SQLITE_RESULT_SUBTYPE] +** property is omitted from the function that invokes sqlite3_result_subtype(), +** then in some cases the sqlite3_result_subtype() might fail to set +** the result subtype. +** +** If SQLite is compiled with -DSQLITE_STRICT_SUBTYPE=1, then any +** SQL function that invokes the sqlite3_result_subtype() interface +** and that does not have the SQLITE_RESULT_SUBTYPE property will raise +** an error. Future versions of SQLite might enable -DSQLITE_STRICT_SUBTYPE=1 +** by default. */ SQLITE_API void sqlite3_result_subtype(sqlite3_context*,unsigned int); @@ -6499,7 +6975,7 @@ SQLITE_API void sqlite3_result_subtype(sqlite3_context*,unsigned int); **
    • [SQLITE_UTF16_ALIGNED]. **
    )^ ** ^The eTextRep argument determines the encoding of strings passed -** to the collating function callback, xCallback. +** to the collating function callback, xCompare. ** ^The [SQLITE_UTF16] and [SQLITE_UTF16_ALIGNED] values for eTextRep ** force strings to be UTF16 with native byte order. ** ^The [SQLITE_UTF16_ALIGNED] value for eTextRep forces strings to begin @@ -6508,18 +6984,19 @@ SQLITE_API void sqlite3_result_subtype(sqlite3_context*,unsigned int); ** ^The fourth argument, pArg, is an application data pointer that is passed ** through as the first argument to the collating function callback. ** -** ^The fifth argument, xCallback, is a pointer to the collating function. +** ^The fifth argument, xCompare, is a pointer to the collating function. ** ^Multiple collating functions can be registered using the same name but ** with different eTextRep parameters and SQLite will use whichever ** function requires the least amount of data transformation. -** ^If the xCallback argument is NULL then the collating function is +** ^If the xCompare argument is NULL then the collating function is ** deleted. ^When all collating functions having the same name are deleted, ** that collation is no longer usable. ** -** ^The collating function callback is invoked with a copy of the pArg +** ^The collating function callback is invoked with a copy of the pArg ** application data pointer and with two strings in the encoding specified -** by the eTextRep argument. The collating function must return an -** integer that is negative, zero, or positive +** by the eTextRep argument. The two integer parameters to the collating +** function callback are the length of the two strings, in bytes. The collating +** function must return an integer that is negative, zero, or positive ** if the first string is less than, equal to, or greater than the second, ** respectively. A collating function must always return the same answer ** given the same inputs. If two or more collating functions are registered @@ -6536,7 +7013,7 @@ SQLITE_API void sqlite3_result_subtype(sqlite3_context*,unsigned int); ** ** ** If a collating function fails any of the above constraints and that -** collating function is registered and used, then the behavior of SQLite +** collating function is registered and used, then the behavior of SQLite ** is undefined. ** ** ^The sqlite3_create_collation_v2() works like sqlite3_create_collation() @@ -6546,36 +7023,36 @@ SQLITE_API void sqlite3_result_subtype(sqlite3_context*,unsigned int); ** calls to the collation creation functions or when the ** [database connection] is closed using [sqlite3_close()]. ** -** ^The xDestroy callback is not called if the +** ^The xDestroy callback is not called if the ** sqlite3_create_collation_v2() function fails. Applications that invoke -** sqlite3_create_collation_v2() with a non-NULL xDestroy argument should +** sqlite3_create_collation_v2() with a non-NULL xDestroy argument should ** check the return code and dispose of the application data pointer ** themselves rather than expecting SQLite to deal with it for them. -** This is different from every other SQLite interface. The inconsistency -** is unfortunate but cannot be changed without breaking backwards +** This is different from every other SQLite interface. The inconsistency +** is unfortunate but cannot be changed without breaking backwards ** compatibility. ** ** See also: [sqlite3_collation_needed()] and [sqlite3_collation_needed16()]. */ SQLITE_API int sqlite3_create_collation( - sqlite3*, - const char *zName, - int eTextRep, + sqlite3*, + const char *zName, + int eTextRep, void *pArg, int(*xCompare)(void*,int,const void*,int,const void*) ); SQLITE_API int sqlite3_create_collation_v2( - sqlite3*, - const char *zName, - int eTextRep, + sqlite3*, + const char *zName, + int eTextRep, void *pArg, int(*xCompare)(void*,int,const void*,int,const void*), void(*xDestroy)(void*) ); SQLITE_API int sqlite3_create_collation16( - sqlite3*, + sqlite3*, const void *zName, - int eTextRep, + int eTextRep, void *pArg, int(*xCompare)(void*,int,const void*,int,const void*) ); @@ -6608,64 +7085,19 @@ SQLITE_API int sqlite3_create_collation16( ** [sqlite3_create_collation_v2()]. */ SQLITE_API int sqlite3_collation_needed( - sqlite3*, - void*, + sqlite3*, + void*, void(*)(void*,sqlite3*,int eTextRep,const char*) ); SQLITE_API int sqlite3_collation_needed16( - sqlite3*, + sqlite3*, void*, void(*)(void*,sqlite3*,int eTextRep,const void*) ); -#ifdef SQLITE_HAS_CODEC -/* -** Specify the key for an encrypted database. This routine should be -** called right after sqlite3_open(). -** -** The code to implement this API is not available in the public release -** of SQLite. -*/ -SQLITE_API int sqlite3_key( - sqlite3 *db, /* Database to be rekeyed */ - const void *pKey, int nKey /* The key */ -); -SQLITE_API int sqlite3_key_v2( - sqlite3 *db, /* Database to be rekeyed */ - const char *zDbName, /* Name of the database */ - const void *pKey, int nKey /* The key */ -); - -/* -** Change the key on an open database. If the current database is not -** encrypted, this routine will encrypt it. If pNew==0 or nNew==0, the -** database is decrypted. -** -** The code to implement this API is not available in the public release -** of SQLite. -*/ -SQLITE_API int sqlite3_rekey( - sqlite3 *db, /* Database to be rekeyed */ - const void *pKey, int nKey /* The new key */ -); -SQLITE_API int sqlite3_rekey_v2( - sqlite3 *db, /* Database to be rekeyed */ - const char *zDbName, /* Name of the database */ - const void *pKey, int nKey /* The new key */ -); - -/* -** Specify the activation key for a SEE database. Unless -** activated, none of the SEE routines will work. -*/ -SQLITE_API void sqlite3_activate_see( - const char *zPassPhrase /* Activation phrase */ -); -#endif - #ifdef SQLITE_ENABLE_CEROD /* -** Specify the activation key for a CEROD database. Unless +** Specify the activation key for a CEROD database. Unless ** activated, none of the CEROD routines will work. */ SQLITE_API void sqlite3_activate_cerod( @@ -6689,6 +7121,13 @@ SQLITE_API void sqlite3_activate_cerod( ** of the default VFS is not implemented correctly, or not implemented at ** all, then the behavior of sqlite3_sleep() may deviate from the description ** in the previous paragraphs. +** +** If a negative argument is passed to sqlite3_sleep() the results vary by +** VFS and operating system. Some system treat a negative argument as an +** instruction to sleep forever. Others understand it to mean do not sleep +** at all. ^In SQLite version 3.42.0 and later, a negative +** argument passed into sqlite3_sleep() is changed to zero before it is relayed +** down into the xSleep method of the VFS. */ SQLITE_API int sqlite3_sleep(int); @@ -6721,7 +7160,7 @@ SQLITE_API int sqlite3_sleep(int); ** ^The [temp_store_directory pragma] may modify this variable and cause ** it to point to memory obtained from [sqlite3_malloc]. ^Furthermore, ** the [temp_store_directory pragma] always assumes that any string -** that this variable points to is held in memory obtained from +** that this variable points to is held in memory obtained from ** [sqlite3_malloc] and the pragma may attempt to free that memory ** using [sqlite3_free]. ** Hence, if this variable is modified directly, either it should be @@ -6778,7 +7217,7 @@ SQLITE_API char *sqlite3_temp_directory; ** ^The [data_store_directory pragma] may modify this variable and cause ** it to point to memory obtained from [sqlite3_malloc]. ^Furthermore, ** the [data_store_directory pragma] always assumes that any string -** that this variable points to is held in memory obtained from +** that this variable points to is held in memory obtained from ** [sqlite3_malloc] and the pragma may attempt to free that memory ** using [sqlite3_free]. ** Hence, if this variable is modified directly, either it should be @@ -6859,22 +7298,59 @@ SQLITE_API int sqlite3_get_autocommit(sqlite3*); */ SQLITE_API sqlite3 *sqlite3_db_handle(sqlite3_stmt*); +/* +** CAPI3REF: Return The Schema Name For A Database Connection +** METHOD: sqlite3 +** +** ^The sqlite3_db_name(D,N) interface returns a pointer to the schema name +** for the N-th database on database connection D, or a NULL pointer if N is +** out of range. An N value of 0 means the main database file. An N of 1 is +** the "temp" schema. Larger values of N correspond to various ATTACH-ed +** databases. +** +** Space to hold the string that is returned by sqlite3_db_name() is managed +** by SQLite itself. The string might be deallocated by any operation that +** changes the schema, including [ATTACH] or [DETACH] or calls to +** [sqlite3_serialize()] or [sqlite3_deserialize()], even operations that +** occur on a different thread. Applications that need to +** remember the string long-term should make their own copy. Applications that +** are accessing the same database connection simultaneously on multiple +** threads should mutex-protect calls to this API and should make their own +** private copy of the result prior to releasing the mutex. +*/ +SQLITE_API const char *sqlite3_db_name(sqlite3 *db, int N); + /* ** CAPI3REF: Return The Filename For A Database Connection ** METHOD: sqlite3 ** -** ^The sqlite3_db_filename(D,N) interface returns a pointer to a filename -** associated with database N of connection D. ^The main database file -** has the name "main". If there is no attached database N on the database +** ^The sqlite3_db_filename(D,N) interface returns a pointer to the filename +** associated with database N of connection D. +** ^If there is no attached database N on the database ** connection D, or if database N is a temporary or in-memory database, then ** this function will return either a NULL pointer or an empty string. ** +** ^The string value returned by this routine is owned and managed by +** the database connection. ^The value will be valid until the database N +** is [DETACH]-ed or until the database connection closes. +** ** ^The filename returned by this function is the output of the ** xFullPathname method of the [VFS]. ^In other words, the filename ** will be an absolute pathname, even if the filename used ** to open the database originally was a URI or relative pathname. +** +** If the filename pointer returned by this routine is not NULL, then it +** can be used as the filename input parameter to these routines: +**
      +**
    • [sqlite3_uri_parameter()] +**
    • [sqlite3_uri_boolean()] +**
    • [sqlite3_uri_int64()] +**
    • [sqlite3_filename_database()] +**
    • [sqlite3_filename_journal()] +**
    • [sqlite3_filename_wal()] +**
    */ -SQLITE_API const char *sqlite3_db_filename(sqlite3 *db, const char *zDbName); +SQLITE_API sqlite3_filename sqlite3_db_filename(sqlite3 *db, const char *zDbName); /* ** CAPI3REF: Determine if a database is read-only @@ -6886,6 +7362,57 @@ SQLITE_API const char *sqlite3_db_filename(sqlite3 *db, const char *zDbName); */ SQLITE_API int sqlite3_db_readonly(sqlite3 *db, const char *zDbName); +/* +** CAPI3REF: Determine the transaction state of a database +** METHOD: sqlite3 +** +** ^The sqlite3_txn_state(D,S) interface returns the current +** [transaction state] of schema S in database connection D. ^If S is NULL, +** then the highest transaction state of any schema on database connection D +** is returned. Transaction states are (in order of lowest to highest): +**
      +**
    1. SQLITE_TXN_NONE +**
    2. SQLITE_TXN_READ +**
    3. SQLITE_TXN_WRITE +**
    +** ^If the S argument to sqlite3_txn_state(D,S) is not the name of +** a valid schema, then -1 is returned. +*/ +SQLITE_API int sqlite3_txn_state(sqlite3*,const char *zSchema); + +/* +** CAPI3REF: Allowed return values from sqlite3_txn_state() +** KEYWORDS: {transaction state} +** +** These constants define the current transaction state of a database file. +** ^The [sqlite3_txn_state(D,S)] interface returns one of these +** constants in order to describe the transaction state of schema S +** in [database connection] D. +** +**
    +** [[SQLITE_TXN_NONE]]
    SQLITE_TXN_NONE
    +**
    The SQLITE_TXN_NONE state means that no transaction is currently +** pending.
    +** +** [[SQLITE_TXN_READ]]
    SQLITE_TXN_READ
    +**
    The SQLITE_TXN_READ state means that the database is currently +** in a read transaction. Content has been read from the database file +** but nothing in the database file has changed. The transaction state +** will be advanced to SQLITE_TXN_WRITE if any changes occur and there are +** no other conflicting concurrent write transactions. The transaction +** state will revert to SQLITE_TXN_NONE following a [ROLLBACK] or +** [COMMIT].
    +** +** [[SQLITE_TXN_WRITE]]
    SQLITE_TXN_WRITE
    +**
    The SQLITE_TXN_WRITE state means that the database is currently +** in a write transaction. Content has been written to the database file +** but has not yet committed. The transaction state will change to +** SQLITE_TXN_NONE at the next [ROLLBACK] or [COMMIT].
    +*/ +#define SQLITE_TXN_NONE 0 +#define SQLITE_TXN_READ 1 +#define SQLITE_TXN_WRITE 2 + /* ** CAPI3REF: Find the next prepared statement ** METHOD: sqlite3 @@ -6952,6 +7479,72 @@ SQLITE_API sqlite3_stmt *sqlite3_next_stmt(sqlite3 *pDb, sqlite3_stmt *pStmt); SQLITE_API void *sqlite3_commit_hook(sqlite3*, int(*)(void*), void*); SQLITE_API void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*); +/* +** CAPI3REF: Autovacuum Compaction Amount Callback +** METHOD: sqlite3 +** +** ^The sqlite3_autovacuum_pages(D,C,P,X) interface registers a callback +** function C that is invoked prior to each autovacuum of the database +** file. ^The callback is passed a copy of the generic data pointer (P), +** the schema-name of the attached database that is being autovacuumed, +** the size of the database file in pages, the number of free pages, +** and the number of bytes per page, respectively. The callback should +** return the number of free pages that should be removed by the +** autovacuum. ^If the callback returns zero, then no autovacuum happens. +** ^If the value returned is greater than or equal to the number of +** free pages, then a complete autovacuum happens. +** +**

    ^If there are multiple ATTACH-ed database files that are being +** modified as part of a transaction commit, then the autovacuum pages +** callback is invoked separately for each file. +** +**

    The callback is not reentrant. The callback function should +** not attempt to invoke any other SQLite interface. If it does, bad +** things may happen, including segmentation faults and corrupt database +** files. The callback function should be a simple function that +** does some arithmetic on its input parameters and returns a result. +** +** ^The X parameter to sqlite3_autovacuum_pages(D,C,P,X) is an optional +** destructor for the P parameter. ^If X is not NULL, then X(P) is +** invoked whenever the database connection closes or when the callback +** is overwritten by another invocation of sqlite3_autovacuum_pages(). +** +**

    ^There is only one autovacuum pages callback per database connection. +** ^Each call to the sqlite3_autovacuum_pages() interface overrides all +** previous invocations for that database connection. ^If the callback +** argument (C) to sqlite3_autovacuum_pages(D,C,P,X) is a NULL pointer, +** then the autovacuum steps callback is canceled. The return value +** from sqlite3_autovacuum_pages() is normally SQLITE_OK, but might +** be some other error code if something goes wrong. The current +** implementation will only return SQLITE_OK or SQLITE_MISUSE, but other +** return codes might be added in future releases. +** +**

    If no autovacuum pages callback is specified (the usual case) or +** a NULL pointer is provided for the callback, +** then the default behavior is to vacuum all free pages. So, in other +** words, the default behavior is the same as if the callback function +** were something like this: +** +**

    +**     unsigned int demonstration_autovac_pages_callback(
    +**       void *pClientData,
    +**       const char *zSchema,
    +**       unsigned int nDbPage,
    +**       unsigned int nFreePage,
    +**       unsigned int nBytePerPage
    +**     ){
    +**       return nFreePage;
    +**     }
    +** 
    +*/ +SQLITE_API int sqlite3_autovacuum_pages( + sqlite3 *db, + unsigned int(*)(void*,const char*,unsigned int,unsigned int,unsigned int), + void*, + void(*)(void*) +); + + /* ** CAPI3REF: Data Change Notification Callbacks ** METHOD: sqlite3 @@ -6965,6 +7558,8 @@ SQLITE_API void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*); ** ** ^The second argument is a pointer to the function to invoke when a ** row is updated, inserted or deleted in a rowid table. +** ^The update hook is disabled by invoking sqlite3_update_hook() +** with a NULL pointer as the second parameter. ** ^The first argument to the callback is a copy of the third argument ** to sqlite3_update_hook(). ** ^The second callback argument is one of [SQLITE_INSERT], [SQLITE_DELETE], @@ -6976,7 +7571,7 @@ SQLITE_API void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*); ** ^In the case of an update, this is the [rowid] after the update takes place. ** ** ^(The update hook is not invoked when internal system tables are -** modified (i.e. sqlite_master and sqlite_sequence).)^ +** modified (i.e. sqlite_sequence).)^ ** ^The update hook is not invoked when [WITHOUT ROWID] tables are modified. ** ** ^In the current implementation, the update hook @@ -6986,6 +7581,12 @@ SQLITE_API void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*); ** The exceptions defined in this paragraph might change in a future ** release of SQLite. ** +** Whether the update hook is invoked before or after the +** corresponding change is currently unspecified and may differ +** depending on the type of change. Do not rely on the order of the +** hook call with regards to the final result of the operation which +** triggers the hook. +** ** The update hook implementation must not do anything that will modify ** the database connection that invoked the update hook. Any actions ** to modify the database connection must be deferred until after the @@ -7002,7 +7603,7 @@ SQLITE_API void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*); ** and [sqlite3_preupdate_hook()] interfaces. */ SQLITE_API void *sqlite3_update_hook( - sqlite3*, + sqlite3*, void(*)(void *,int ,char const *,char const *,sqlite3_int64), void* ); @@ -7015,26 +7616,35 @@ SQLITE_API void *sqlite3_update_hook( ** to the same database. Sharing is enabled if the argument is true ** and disabled if the argument is false.)^ ** +** This interface is omitted if SQLite is compiled with +** [-DSQLITE_OMIT_SHARED_CACHE]. The [-DSQLITE_OMIT_SHARED_CACHE] +** compile-time option is recommended because the +** [use of shared cache mode is discouraged]. +** ** ^Cache sharing is enabled and disabled for an entire process. -** This is a change as of SQLite [version 3.5.0] ([dateof:3.5.0]). +** This is a change as of SQLite [version 3.5.0] ([dateof:3.5.0]). ** In prior versions of SQLite, ** sharing was enabled or disabled for each thread separately. ** ** ^(The cache sharing mode set by this interface effects all subsequent ** calls to [sqlite3_open()], [sqlite3_open_v2()], and [sqlite3_open16()]. -** Existing database connections continue use the sharing mode +** Existing database connections continue to use the sharing mode ** that was in effect at the time they were opened.)^ ** ** ^(This routine returns [SQLITE_OK] if shared cache was enabled or disabled ** successfully. An [error code] is returned otherwise.)^ ** -** ^Shared cache is disabled by default. But this might change in -** future releases of SQLite. Applications that care about shared -** cache setting should set it explicitly. +** ^Shared cache is disabled by default. It is recommended that it stay +** that way. In other words, do not use this routine. This interface +** continues to be provided for historical compatibility, but its use is +** discouraged. Any use of shared cache is discouraged. If shared cache +** must be used, it is recommended that shared cache only be enabled for +** individual database connections using the [sqlite3_open_v2()] interface +** with the [SQLITE_OPEN_SHAREDCACHE] flag. ** ** Note: This method is disabled on MacOS X 10.7 and iOS version 5.0 -** and will always return SQLITE_MISUSE. On those systems, -** shared cache mode should be enabled per-database connection via +** and will always return SQLITE_MISUSE. On those systems, +** shared cache mode should be enabled per-database connection via ** [sqlite3_open_v2()] with [SQLITE_OPEN_SHAREDCACHE]. ** ** This interface is threadsafe on processors where writing a @@ -7077,6 +7687,9 @@ SQLITE_API int sqlite3_db_release_memory(sqlite3*); /* ** CAPI3REF: Impose A Limit On Heap Size ** +** These interfaces impose limits on the amount of heap memory that will be +** used by all database connections within a single process. +** ** ^The sqlite3_soft_heap_limit64() interface sets and/or queries the ** soft limit on the amount of heap memory that may be allocated by SQLite. ** ^SQLite strives to keep heap memory utilization below the soft heap @@ -7084,23 +7697,44 @@ SQLITE_API int sqlite3_db_release_memory(sqlite3*); ** as heap memory usages approaches the limit. ** ^The soft heap limit is "soft" because even though SQLite strives to stay ** below the limit, it will exceed the limit rather than generate -** an [SQLITE_NOMEM] error. In other words, the soft heap limit +** an [SQLITE_NOMEM] error. In other words, the soft heap limit ** is advisory only. ** -** ^The return value from sqlite3_soft_heap_limit64() is the size of -** the soft heap limit prior to the call, or negative in the case of an -** error. ^If the argument N is negative -** then no change is made to the soft heap limit. Hence, the current -** size of the soft heap limit can be determined by invoking -** sqlite3_soft_heap_limit64() with a negative argument. -** -** ^If the argument N is zero then the soft heap limit is disabled. +** ^The sqlite3_hard_heap_limit64(N) interface sets a hard upper bound of +** N bytes on the amount of memory that will be allocated. ^The +** sqlite3_hard_heap_limit64(N) interface is similar to +** sqlite3_soft_heap_limit64(N) except that memory allocations will fail +** when the hard heap limit is reached. ** -** ^(The soft heap limit is not enforced in the current implementation +** ^The return value from both sqlite3_soft_heap_limit64() and +** sqlite3_hard_heap_limit64() is the size of +** the heap limit prior to the call, or negative in the case of an +** error. ^If the argument N is negative +** then no change is made to the heap limit. Hence, the current +** size of heap limits can be determined by invoking +** sqlite3_soft_heap_limit64(-1) or sqlite3_hard_heap_limit(-1). +** +** ^Setting the heap limits to zero disables the heap limiter mechanism. +** +** ^The soft heap limit may not be greater than the hard heap limit. +** ^If the hard heap limit is enabled and if sqlite3_soft_heap_limit(N) +** is invoked with a value of N that is greater than the hard heap limit, +** the soft heap limit is set to the value of the hard heap limit. +** ^The soft heap limit is automatically enabled whenever the hard heap +** limit is enabled. ^When sqlite3_hard_heap_limit64(N) is invoked and +** the soft heap limit is outside the range of 1..N, then the soft heap +** limit is set to N. ^Invoking sqlite3_soft_heap_limit64(0) when the +** hard heap limit is enabled makes the soft heap limit equal to the +** hard heap limit. +** +** The memory allocation limits can also be adjusted using +** [PRAGMA soft_heap_limit] and [PRAGMA hard_heap_limit]. +** +** ^(The heap limits are not enforced in the current implementation ** if one or more of following conditions are true: ** **
      -**
    • The soft heap limit is set to zero. +**
    • The limit value is set to zero. **
    • Memory accounting is disabled using a combination of the ** [sqlite3_config]([SQLITE_CONFIG_MEMSTATUS],...) start-time option and ** the [SQLITE_DEFAULT_MEMSTATUS] compile-time option. @@ -7111,21 +7745,11 @@ SQLITE_API int sqlite3_db_release_memory(sqlite3*); ** from the heap. **
    )^ ** -** Beginning with SQLite [version 3.7.3] ([dateof:3.7.3]), -** the soft heap limit is enforced -** regardless of whether or not the [SQLITE_ENABLE_MEMORY_MANAGEMENT] -** compile-time option is invoked. With [SQLITE_ENABLE_MEMORY_MANAGEMENT], -** the soft heap limit is enforced on every memory allocation. Without -** [SQLITE_ENABLE_MEMORY_MANAGEMENT], the soft heap limit is only enforced -** when memory is allocated by the page cache. Testing suggests that because -** the page cache is the predominate memory user in SQLite, most -** applications will achieve adequate soft heap limit enforcement without -** the use of [SQLITE_ENABLE_MEMORY_MANAGEMENT]. -** -** The circumstances under which SQLite will enforce the soft heap limit may -** changes in future releases of SQLite. +** The circumstances under which SQLite will enforce the heap limits may +** change in future releases of SQLite. */ SQLITE_API sqlite3_int64 sqlite3_soft_heap_limit64(sqlite3_int64 N); +SQLITE_API sqlite3_int64 sqlite3_hard_heap_limit64(sqlite3_int64 N); /* ** CAPI3REF: Deprecated Soft Heap Limit Interface @@ -7149,7 +7773,7 @@ SQLITE_API SQLITE_DEPRECATED void sqlite3_soft_heap_limit(int N); ** interface returns SQLITE_OK and fills in the non-NULL pointers in ** the final five arguments with appropriate values if the specified ** column exists. ^The sqlite3_table_column_metadata() interface returns -** SQLITE_ERROR and if the specified column does not exist. +** SQLITE_ERROR if the specified column does not exist. ** ^If the column-name parameter to sqlite3_table_column_metadata() is a ** NULL pointer, then this routine simply checks for the existence of the ** table and returns SQLITE_OK if the table exists and SQLITE_ERROR if it @@ -7189,7 +7813,7 @@ SQLITE_API SQLITE_DEPRECATED void sqlite3_soft_heap_limit(int N); ** ** ^If the specified table is actually a view, an [error code] is returned. ** -** ^If the specified column is "rowid", "oid" or "_rowid_" and the table +** ^If the specified column is "rowid", "oid" or "_rowid_" and the table ** is not a [WITHOUT ROWID] table and an ** [INTEGER PRIMARY KEY] column has been explicitly declared, then the output ** parameters are set for the explicitly declared column. ^(If there is no @@ -7229,7 +7853,7 @@ SQLITE_API int sqlite3_table_column_metadata( ** ^The sqlite3_load_extension() interface attempts to load an ** [SQLite extension] library contained in the file zFile. If ** the file cannot be loaded directly, attempts are made to load -** with various operating-system specific extensions added. +** with various operating-system specific filename extensions added. ** So for example, if "samplelib" cannot be loaded, then names like ** "samplelib.so" or "samplelib.dylib" or "samplelib.dll" might ** be tried also. @@ -7237,10 +7861,10 @@ SQLITE_API int sqlite3_table_column_metadata( ** ^The entry point is zProc. ** ^(zProc may be 0, in which case SQLite will try to come up with an ** entry point name on its own. It first tries "sqlite3_extension_init". -** If that does not work, it constructs a name "sqlite3_X_init" where the -** X is consists of the lower-case equivalent of all ASCII alphabetic -** characters in the filename from the last "/" to the first following -** "." and omitting any initial "lib".)^ +** If that does not work, it tries names of the form "sqlite3_X_init" +** where X consists of the lower-case equivalent of all ASCII alphabetic +** characters or all ASCII alphanumeric characters in the filename from +** the last "/" to the first following "." and omitting any initial "lib".)^ ** ^The sqlite3_load_extension() interface returns ** [SQLITE_OK] on success and [SQLITE_ERROR] if something goes wrong. ** ^If an error occurs and pzErrMsg is not 0, then the @@ -7255,7 +7879,7 @@ SQLITE_API int sqlite3_table_column_metadata( ** prior to calling this API, ** otherwise an error will be returned. ** -** Security warning: It is recommended that the +** Security warning: It is recommended that the ** [SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION] method be used to enable only this ** interface. The use of the [sqlite3_enable_load_extension()] interface ** should be avoided. This will keep the SQL function [load_extension()] @@ -7291,7 +7915,7 @@ SQLITE_API int sqlite3_load_extension( ** to enable or disable only the C-API.)^ ** ** Security warning: It is recommended that extension loading -** be disabled using the [SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION] method +** be enabled using the [SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION] method ** rather than this interface, so the [load_extension()] SQL function ** remains disabled. This will prevent SQL injections from giving attackers ** access to extension loading capabilities. @@ -7309,12 +7933,12 @@ SQLITE_API int sqlite3_enable_load_extension(sqlite3 *db, int onoff); ** ^(Even though the function prototype shows that xEntryPoint() takes ** no arguments and returns void, SQLite invokes xEntryPoint() with three ** arguments and expects an integer result as if the signature of the -** entry point where as follows: +** entry point were as follows: ** **
     **    int xEntryPoint(
     **      sqlite3 *db,
    -**      const char **pzErrMsg,
    +**      char **pzErrMsg,
     **      const struct sqlite3_api_routines *pThunk
     **    );
     ** 
    )^ @@ -7342,7 +7966,7 @@ SQLITE_API int sqlite3_auto_extension(void(*xEntryPoint)(void)); ** ^The [sqlite3_cancel_auto_extension(X)] interface unregisters the ** initialization routine X that was registered using a prior call to ** [sqlite3_auto_extension(X)]. ^The [sqlite3_cancel_auto_extension(X)] -** routine returns 1 if initialization routine X was successfully +** routine returns 1 if initialization routine X was successfully ** unregistered and it returns 0 if X was not on the list of initialization ** routines. */ @@ -7356,15 +7980,6 @@ SQLITE_API int sqlite3_cancel_auto_extension(void(*xEntryPoint)(void)); */ SQLITE_API void sqlite3_reset_auto_extension(void); -/* -** The interface to the virtual-table mechanism is currently considered -** to be experimental. The interface might change in incompatible ways. -** If this is a problem for you, do not use the interface at this time. -** -** When the virtual-table mechanism stabilizes, we will declare the -** interface fixed, support it indefinitely, and remove this comment. -*/ - /* ** Structures used by the virtual table interface */ @@ -7377,8 +7992,8 @@ typedef struct sqlite3_module sqlite3_module; ** CAPI3REF: Virtual Table Object ** KEYWORDS: sqlite3_module {virtual table module} ** -** This structure, sometimes called a "virtual table module", -** defines the implementation of a [virtual tables]. +** This structure, sometimes called a "virtual table module", +** defines the implementation of a [virtual table]. ** This structure consists mostly of methods for the module. ** ** ^A virtual table module is created by filling in a persistent @@ -7417,7 +8032,7 @@ struct sqlite3_module { void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), void **ppArg); int (*xRename)(sqlite3_vtab *pVtab, const char *zNew); - /* The methods above are in version 1 of the sqlite_module object. Those + /* The methods above are in version 1 of the sqlite_module object. Those ** below are for version 2 and greater. */ int (*xSavepoint)(sqlite3_vtab *pVTab, int); int (*xRelease)(sqlite3_vtab *pVTab, int); @@ -7425,6 +8040,10 @@ struct sqlite3_module { /* The methods above are in versions 1 and 2 of the sqlite_module object. ** Those below are for version 3 and greater. */ int (*xShadowName)(const char*); + /* The methods above are in versions 1 through 3 of the sqlite_module object. + ** Those below are for version 4 and greater. */ + int (*xIntegrity)(sqlite3_vtab *pVTab, const char *zSchema, + const char *zTabName, int mFlags, char **pzErr); }; /* @@ -7467,7 +8086,7 @@ struct sqlite3_module { ** required by SQLite. If the table has at least 64 columns and any column ** to the right of the first 63 is required, then bit 63 of colUsed is also ** set. In other words, column iCol may be required if the expression -** (colUsed & ((sqlite3_uint64)1 << (iCol>=63 ? 63 : iCol))) evaluates to +** (colUsed & ((sqlite3_uint64)1 << (iCol>=63 ? 63 : iCol))) evaluates to ** non-zero. ** ** The [xBestIndex] method must fill aConstraintUsage[] with information @@ -7475,12 +8094,18 @@ struct sqlite3_module { ** the right-hand side of the corresponding aConstraint[] is evaluated ** and becomes the argvIndex-th entry in argv. ^(If aConstraintUsage[].omit ** is true, then the constraint is assumed to be fully handled by the -** virtual table and is not checked again by SQLite.)^ -** -** ^The idxNum and idxPtr values are recorded and passed into the +** virtual table and might not be checked again by the byte code.)^ ^(The +** aConstraintUsage[].omit flag is an optimization hint. When the omit flag +** is left in its default setting of false, the constraint will always be +** checked separately in byte code. If the omit flag is changed to true, then +** the constraint may or may not be checked in byte code. In other words, +** when the omit flag is true there is no guarantee that the constraint will +** not be checked again using byte code.)^ +** +** ^The idxNum and idxStr values are recorded and passed into the ** [xFilter] method. -** ^[sqlite3_free()] is used to free idxPtr if and only if -** needToFreeIdxPtr is true. +** ^[sqlite3_free()] is used to free idxStr if and only if +** needToFreeIdxStr is true. ** ** ^The orderByConsumed means that output from [xFilter]/[xNext] will occur in ** the correct order to satisfy the ORDER BY clause so that no separate @@ -7488,17 +8113,19 @@ struct sqlite3_module { ** ** ^The estimatedCost value is an estimate of the cost of a particular ** strategy. A cost of N indicates that the cost of the strategy is similar -** to a linear scan of an SQLite table with N rows. A cost of log(N) +** to a linear scan of an SQLite table with N rows. A cost of log(N) ** indicates that the expense of the operation is similar to that of a ** binary search on a unique indexed field of an SQLite table with N rows. ** ** ^The estimatedRows value is an estimate of the number of rows that ** will be returned by the strategy. ** -** The xBestIndex method may optionally populate the idxFlags field with a -** mask of SQLITE_INDEX_SCAN_* flags. Currently there is only one such flag - -** SQLITE_INDEX_SCAN_UNIQUE. If the xBestIndex method sets this flag, SQLite -** assumes that the strategy may visit at most one row. +** The xBestIndex method may optionally populate the idxFlags field with a +** mask of SQLITE_INDEX_SCAN_* flags. One such flag is +** [SQLITE_INDEX_SCAN_HEX], which if set causes the [EXPLAIN QUERY PLAN] +** output to show the idxNum as hex instead of as decimal. Another flag is +** SQLITE_INDEX_SCAN_UNIQUE, which if set indicates that the query plan will +** return at most one row. ** ** Additionally, if xBestIndex sets the SQLITE_INDEX_SCAN_UNIQUE flag, then ** SQLite also assumes that if a call to the xUpdate() method is made as @@ -7511,14 +8138,14 @@ struct sqlite3_module { ** the xUpdate method are automatically rolled back by SQLite. ** ** IMPORTANT: The estimatedRows field was added to the sqlite3_index_info -** structure for SQLite [version 3.8.2] ([dateof:3.8.2]). +** structure for SQLite [version 3.8.2] ([dateof:3.8.2]). ** If a virtual table extension is -** used with an SQLite version earlier than 3.8.2, the results of attempting -** to read or write the estimatedRows field are undefined (but are likely -** to included crashing the application). The estimatedRows field should +** used with an SQLite version earlier than 3.8.2, the results of attempting +** to read or write the estimatedRows field are undefined (but are likely +** to include crashing the application). The estimatedRows field should ** therefore only be used if [sqlite3_libversion_number()] returns a ** value greater than or equal to 3008002. Similarly, the idxFlags field -** was added for [version 3.9.0] ([dateof:3.9.0]). +** was added for [version 3.9.0] ([dateof:3.9.0]). ** It may therefore only be used if ** sqlite3_libversion_number() returns a value greater than or equal to ** 3009000. @@ -7558,35 +8185,69 @@ struct sqlite3_index_info { /* ** CAPI3REF: Virtual Table Scan Flags ** -** Virtual table implementations are allowed to set the +** Virtual table implementations are allowed to set the ** [sqlite3_index_info].idxFlags field to some combination of ** these bits. */ -#define SQLITE_INDEX_SCAN_UNIQUE 1 /* Scan visits at most 1 row */ +#define SQLITE_INDEX_SCAN_UNIQUE 0x00000001 /* Scan visits at most 1 row */ +#define SQLITE_INDEX_SCAN_HEX 0x00000002 /* Display idxNum as hex */ + /* in EXPLAIN QUERY PLAN */ /* ** CAPI3REF: Virtual Table Constraint Operator Codes ** -** These macros defined the allowed values for the +** These macros define the allowed values for the ** [sqlite3_index_info].aConstraint[].op field. Each value represents -** an operator that is part of a constraint term in the wHERE clause of +** an operator that is part of a constraint term in the WHERE clause of ** a query that uses a [virtual table]. -*/ -#define SQLITE_INDEX_CONSTRAINT_EQ 2 -#define SQLITE_INDEX_CONSTRAINT_GT 4 -#define SQLITE_INDEX_CONSTRAINT_LE 8 -#define SQLITE_INDEX_CONSTRAINT_LT 16 -#define SQLITE_INDEX_CONSTRAINT_GE 32 -#define SQLITE_INDEX_CONSTRAINT_MATCH 64 -#define SQLITE_INDEX_CONSTRAINT_LIKE 65 -#define SQLITE_INDEX_CONSTRAINT_GLOB 66 -#define SQLITE_INDEX_CONSTRAINT_REGEXP 67 -#define SQLITE_INDEX_CONSTRAINT_NE 68 -#define SQLITE_INDEX_CONSTRAINT_ISNOT 69 -#define SQLITE_INDEX_CONSTRAINT_ISNOTNULL 70 -#define SQLITE_INDEX_CONSTRAINT_ISNULL 71 -#define SQLITE_INDEX_CONSTRAINT_IS 72 -#define SQLITE_INDEX_CONSTRAINT_FUNCTION 150 +** +** ^The left-hand operand of the operator is given by the corresponding +** aConstraint[].iColumn field. ^An iColumn of -1 indicates the left-hand +** operand is the rowid. +** The SQLITE_INDEX_CONSTRAINT_LIMIT and SQLITE_INDEX_CONSTRAINT_OFFSET +** operators have no left-hand operand, and so for those operators the +** corresponding aConstraint[].iColumn is meaningless and should not be +** used. +** +** All operator values from SQLITE_INDEX_CONSTRAINT_FUNCTION through +** value 255 are reserved to represent functions that are overloaded +** by the [xFindFunction|xFindFunction method] of the virtual table +** implementation. +** +** The right-hand operands for each constraint might be accessible using +** the [sqlite3_vtab_rhs_value()] interface. Usually the right-hand +** operand is only available if it appears as a single constant literal +** in the input SQL. If the right-hand operand is another column or an +** expression (even a constant expression) or a parameter, then the +** sqlite3_vtab_rhs_value() probably will not be able to extract it. +** ^The SQLITE_INDEX_CONSTRAINT_ISNULL and +** SQLITE_INDEX_CONSTRAINT_ISNOTNULL operators have no right-hand operand +** and hence calls to sqlite3_vtab_rhs_value() for those operators will +** always return SQLITE_NOTFOUND. +** +** The collating sequence to be used for comparison can be found using +** the [sqlite3_vtab_collation()] interface. For most real-world virtual +** tables, the collating sequence of constraints does not matter (for example +** because the constraints are numeric) and so the sqlite3_vtab_collation() +** interface is not commonly needed. +*/ +#define SQLITE_INDEX_CONSTRAINT_EQ 2 +#define SQLITE_INDEX_CONSTRAINT_GT 4 +#define SQLITE_INDEX_CONSTRAINT_LE 8 +#define SQLITE_INDEX_CONSTRAINT_LT 16 +#define SQLITE_INDEX_CONSTRAINT_GE 32 +#define SQLITE_INDEX_CONSTRAINT_MATCH 64 +#define SQLITE_INDEX_CONSTRAINT_LIKE 65 +#define SQLITE_INDEX_CONSTRAINT_GLOB 66 +#define SQLITE_INDEX_CONSTRAINT_REGEXP 67 +#define SQLITE_INDEX_CONSTRAINT_NE 68 +#define SQLITE_INDEX_CONSTRAINT_ISNOT 69 +#define SQLITE_INDEX_CONSTRAINT_ISNOTNULL 70 +#define SQLITE_INDEX_CONSTRAINT_ISNULL 71 +#define SQLITE_INDEX_CONSTRAINT_IS 72 +#define SQLITE_INDEX_CONSTRAINT_LIMIT 73 +#define SQLITE_INDEX_CONSTRAINT_OFFSET 74 +#define SQLITE_INDEX_CONSTRAINT_FUNCTION 150 /* ** CAPI3REF: Register A Virtual Table Implementation @@ -7598,12 +8259,12 @@ struct sqlite3_index_info { ** preexisting [virtual table] for the module. ** ** ^The module name is registered on the [database connection] specified -** by the first parameter. ^The name of the module is given by the +** by the first parameter. ^The name of the module is given by the ** second parameter. ^The third parameter is a pointer to ** the implementation of the [virtual table module]. ^The fourth ** parameter is an arbitrary client data pointer that is passed through ** into the [xCreate] and [xConnect] methods of the virtual table module -** when a new virtual table is be being created or reinitialized. +** when a new virtual table is being created or reinitialized. ** ** ^The sqlite3_create_module_v2() interface has a fifth parameter which ** is a pointer to a destructor for the pClientData. ^SQLite will @@ -7613,6 +8274,12 @@ struct sqlite3_index_info { ** ^The sqlite3_create_module() ** interface is equivalent to sqlite3_create_module_v2() with a NULL ** destructor. +** +** ^If the third parameter (the pointer to the sqlite3_module object) is +** NULL then no new module is created and any existing modules with the +** same name are dropped. +** +** See also: [sqlite3_drop_modules()] */ SQLITE_API int sqlite3_create_module( sqlite3 *db, /* SQLite connection to register module with */ @@ -7628,6 +8295,23 @@ SQLITE_API int sqlite3_create_module_v2( void(*xDestroy)(void*) /* Module destructor function */ ); +/* +** CAPI3REF: Remove Unnecessary Virtual Table Implementations +** METHOD: sqlite3 +** +** ^The sqlite3_drop_modules(D,L) interface removes all virtual +** table modules from database connection D except those named on list L. +** The L parameter must be either NULL or a pointer to an array of pointers +** to strings where the array is terminated by a single NULL pointer. +** ^If the L parameter is NULL, then all virtual table modules are removed. +** +** See also: [sqlite3_create_module()] +*/ +SQLITE_API int sqlite3_drop_modules( + sqlite3 *db, /* Remove modules from this connection */ + const char **azKeep /* Except, do not remove the ones named here */ +); + /* ** CAPI3REF: Virtual Table Instance Object ** KEYWORDS: sqlite3_vtab @@ -7690,7 +8374,7 @@ SQLITE_API int sqlite3_declare_vtab(sqlite3*, const char *zSQL); ** METHOD: sqlite3 ** ** ^(Virtual tables can provide alternative implementations of functions -** using the [xFindFunction] method of the [virtual table module]. +** using the [xFindFunction] method of the [virtual table module]. ** But global versions of those functions ** must exist in order to be overloaded.)^ ** @@ -7704,16 +8388,6 @@ SQLITE_API int sqlite3_declare_vtab(sqlite3*, const char *zSQL); */ SQLITE_API int sqlite3_overload_function(sqlite3*, const char *zFuncName, int nArg); -/* -** The interface to the virtual-table mechanism defined above (back up -** to a comment remarkably similar to this one) is currently considered -** to be experimental. The interface might change in incompatible ways. -** If this is a problem for you, do not use the interface at this time. -** -** When the virtual-table mechanism stabilizes, we will declare the -** interface fixed, support it indefinitely, and remove this comment. -*/ - /* ** CAPI3REF: A Handle To An Open BLOB ** KEYWORDS: {BLOB handle} {BLOB handles} @@ -7741,7 +8415,7 @@ typedef struct sqlite3_blob sqlite3_blob; ** SELECT zColumn FROM zDb.zTable WHERE [rowid] = iRow; ** )^ ** -** ^(Parameter zDb is not the filename that contains the database, but +** ^(Parameter zDb is not the filename that contains the database, but ** rather the symbolic name of the database. For attached databases, this is ** the name that appears after the AS keyword in the [ATTACH] statement. ** For the main database file, the database name is "main". For TEMP @@ -7754,28 +8428,28 @@ typedef struct sqlite3_blob sqlite3_blob; ** ^(On success, [SQLITE_OK] is returned and the new [BLOB handle] is stored ** in *ppBlob. Otherwise an [error code] is returned and, unless the error ** code is SQLITE_MISUSE, *ppBlob is set to NULL.)^ ^This means that, provided -** the API is not misused, it is always safe to call [sqlite3_blob_close()] -** on *ppBlob after this function it returns. +** the API is not misused, it is always safe to call [sqlite3_blob_close()] +** on *ppBlob after this function returns. ** ** This function fails with SQLITE_ERROR if any of the following are true: **
      -**
    • ^(Database zDb does not exist)^, -**
    • ^(Table zTable does not exist within database zDb)^, -**
    • ^(Table zTable is a WITHOUT ROWID table)^, +**
    • ^(Database zDb does not exist)^, +**
    • ^(Table zTable does not exist within database zDb)^, +**
    • ^(Table zTable is a WITHOUT ROWID table)^, **
    • ^(Column zColumn does not exist)^, **
    • ^(Row iRow is not present in the table)^, **
    • ^(The specified column of row iRow contains a value that is not ** a TEXT or BLOB value)^, -**
    • ^(Column zColumn is part of an index, PRIMARY KEY or UNIQUE +**
    • ^(Column zColumn is part of an index, PRIMARY KEY or UNIQUE ** constraint and the blob is being opened for read/write access)^, -**
    • ^([foreign key constraints | Foreign key constraints] are enabled, +**
    • ^([foreign key constraints | Foreign key constraints] are enabled, ** column zColumn is part of a [child key] definition and the blob is ** being opened for read/write access)^. **
    ** -** ^Unless it returns SQLITE_MISUSE, this function sets the -** [database connection] error code and message accessible via -** [sqlite3_errcode()] and [sqlite3_errmsg()] and related functions. +** ^Unless it returns SQLITE_MISUSE, this function sets the +** [database connection] error code and message accessible via +** [sqlite3_errcode()] and [sqlite3_errmsg()] and related functions. ** ** A BLOB referenced by sqlite3_blob_open() may be read using the ** [sqlite3_blob_read()] interface and modified by using @@ -7801,7 +8475,7 @@ typedef struct sqlite3_blob sqlite3_blob; ** blob. ** ** ^The [sqlite3_bind_zeroblob()] and [sqlite3_result_zeroblob()] interfaces -** and the built-in [zeroblob] SQL function may be used to create a +** and the built-in [zeroblob] SQL function may be used to create a ** zero-filled blob to read or write using the incremental-blob interface. ** ** To avoid a resource leak, every open [BLOB handle] should eventually @@ -7851,7 +8525,7 @@ SQLITE_API int sqlite3_blob_reopen(sqlite3_blob *, sqlite3_int64); ** DESTRUCTOR: sqlite3_blob ** ** ^This function closes an open [BLOB handle]. ^(The BLOB handle is closed -** unconditionally. Even if this routine returns an error code, the +** unconditionally. Even if this routine returns an error code, the ** handle is still closed.)^ ** ** ^If the blob handle being closed was opened for read-write access, and if @@ -7861,10 +8535,10 @@ SQLITE_API int sqlite3_blob_reopen(sqlite3_blob *, sqlite3_int64); ** code is returned and the transaction rolled back. ** ** Calling this function with an argument that is not a NULL pointer or an -** open blob handle results in undefined behaviour. ^Calling this routine -** with a null pointer (such as would be returned by a failed call to +** open blob handle results in undefined behavior. ^Calling this routine +** with a null pointer (such as would be returned by a failed call to ** [sqlite3_blob_open()]) is a harmless no-op. ^Otherwise, if this function -** is passed a valid open blob handle, the values returned by the +** is passed a valid open blob handle, the values returned by the ** sqlite3_errcode() and sqlite3_errmsg() functions are set before returning. */ SQLITE_API int sqlite3_blob_close(sqlite3_blob *); @@ -7873,9 +8547,9 @@ SQLITE_API int sqlite3_blob_close(sqlite3_blob *); ** CAPI3REF: Return The Size Of An Open BLOB ** METHOD: sqlite3_blob ** -** ^Returns the size in bytes of the BLOB accessible via the +** ^Returns the size in bytes of the BLOB accessible via the ** successfully opened [BLOB handle] in its only argument. ^The -** incremental blob I/O routines can only read or overwriting existing +** incremental blob I/O routines can only read or overwrite existing ** blob content; they cannot change the size of a blob. ** ** This routine only works on a [BLOB handle] which has been created @@ -7924,9 +8598,9 @@ SQLITE_API int sqlite3_blob_read(sqlite3_blob *, void *Z, int N, int iOffset); ** ** ^(On success, sqlite3_blob_write() returns SQLITE_OK. ** Otherwise, an [error code] or an [extended error code] is returned.)^ -** ^Unless SQLITE_MISUSE is returned, this function sets the -** [database connection] error code and message accessible via -** [sqlite3_errcode()] and [sqlite3_errmsg()] and related functions. +** ^Unless SQLITE_MISUSE is returned, this function sets the +** [database connection] error code and message accessible via +** [sqlite3_errcode()] and [sqlite3_errmsg()] and related functions. ** ** ^If the [BLOB handle] passed as the first argument was not opened for ** writing (the flags parameter to [sqlite3_blob_open()] was zero), @@ -7935,9 +8609,9 @@ SQLITE_API int sqlite3_blob_read(sqlite3_blob *, void *Z, int N, int iOffset); ** This function may only modify the contents of the BLOB; it is ** not possible to increase the size of a BLOB using this API. ** ^If offset iOffset is less than N bytes from the end of the BLOB, -** [SQLITE_ERROR] is returned and no data is written. The size of the -** BLOB (and hence the maximum value of N+iOffset) can be determined -** using the [sqlite3_blob_bytes()] interface. ^If N or iOffset are less +** [SQLITE_ERROR] is returned and no data is written. The size of the +** BLOB (and hence the maximum value of N+iOffset) can be determined +** using the [sqlite3_blob_bytes()] interface. ^If N or iOffset are less ** than zero [SQLITE_ERROR] is returned and no data is written. ** ** ^An attempt to write to an expired [BLOB handle] fails with an @@ -8014,24 +8688,17 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*); ** SQLITE_MUTEX_W32 implementations are appropriate for use on Unix ** and Windows. ** -** If SQLite is compiled with the SQLITE_MUTEX_APPDEF preprocessor -** macro defined (with "-DSQLITE_MUTEX_APPDEF=1"), then no mutex -** implementation is included with the library. In this case the -** application must supply a custom mutex implementation using the -** [SQLITE_CONFIG_MUTEX] option of the sqlite3_config() function -** before calling sqlite3_initialize() or any other public sqlite3_ -** function that calls sqlite3_initialize(). ** ** ^The sqlite3_mutex_alloc() routine allocates a new ** mutex and returns a pointer to it. ^The sqlite3_mutex_alloc() ** routine returns NULL if it is unable to allocate the requested -** mutex. The argument to sqlite3_mutex_alloc() must one of these +** mutex. The argument to sqlite3_mutex_alloc() must be one of these ** integer constants: ** **
      **
    • SQLITE_MUTEX_FAST **
    • SQLITE_MUTEX_RECURSIVE -**
    • SQLITE_MUTEX_STATIC_MASTER +**
    • SQLITE_MUTEX_STATIC_MAIN **
    • SQLITE_MUTEX_STATIC_MEM **
    • SQLITE_MUTEX_STATIC_OPEN **
    • SQLITE_MUTEX_STATIC_PRNG @@ -8088,18 +8755,20 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*); ** ** ^(Some systems (for example, Windows 95) do not support the operation ** implemented by sqlite3_mutex_try(). On those systems, sqlite3_mutex_try() -** will always return SQLITE_BUSY. The SQLite core only ever uses -** sqlite3_mutex_try() as an optimization so this is acceptable -** behavior.)^ +** will always return SQLITE_BUSY. In most cases the SQLite core only uses +** sqlite3_mutex_try() as an optimization, so this is acceptable +** behavior. The exceptions are unix builds that set the +** SQLITE_ENABLE_SETLK_TIMEOUT build option. In that case a working +** sqlite3_mutex_try() is required.)^ ** ** ^The sqlite3_mutex_leave() routine exits a mutex that was ** previously entered by the same thread. The behavior ** is undefined if the mutex is not currently entered by the ** calling thread or is not currently allocated. ** -** ^If the argument to sqlite3_mutex_enter(), sqlite3_mutex_try(), or -** sqlite3_mutex_leave() is a NULL pointer, then all three routines -** behave as no-ops. +** ^If the argument to sqlite3_mutex_enter(), sqlite3_mutex_try(), +** sqlite3_mutex_leave(), or sqlite3_mutex_free() is a NULL pointer, +** then any of the four routines behaves as a no-op. ** ** See also: [sqlite3_mutex_held()] and [sqlite3_mutex_notheld()]. */ @@ -8154,7 +8823,7 @@ SQLITE_API void sqlite3_mutex_leave(sqlite3_mutex*); ** The only difference is that the public sqlite3_XXX functions enumerated ** above silently ignore any invocations that pass a NULL pointer instead ** of a valid mutex handle. The implementations of the methods defined -** by this structure are not required to handle this case, the results +** by this structure are not required to handle this case. The results ** of passing a NULL pointer instead of a valid mutex handle are undefined ** (i.e. it is acceptable to provide an implementation that segfaults if ** it is passed a NULL pointer). @@ -8233,7 +8902,7 @@ SQLITE_API int sqlite3_mutex_notheld(sqlite3_mutex*); */ #define SQLITE_MUTEX_FAST 0 #define SQLITE_MUTEX_RECURSIVE 1 -#define SQLITE_MUTEX_STATIC_MASTER 2 +#define SQLITE_MUTEX_STATIC_MAIN 2 #define SQLITE_MUTEX_STATIC_MEM 3 /* sqlite3_malloc() */ #define SQLITE_MUTEX_STATIC_MEM2 4 /* NOT USED */ #define SQLITE_MUTEX_STATIC_OPEN 4 /* sqlite3BtreeOpen() */ @@ -8248,11 +8917,15 @@ SQLITE_API int sqlite3_mutex_notheld(sqlite3_mutex*); #define SQLITE_MUTEX_STATIC_VFS2 12 /* For use by extension VFS */ #define SQLITE_MUTEX_STATIC_VFS3 13 /* For use by application VFS */ +/* Legacy compatibility: */ +#define SQLITE_MUTEX_STATIC_MASTER 2 + + /* ** CAPI3REF: Retrieve the mutex for a database connection ** METHOD: sqlite3 ** -** ^This interface returns a pointer the [sqlite3_mutex] object that +** ^This interface returns a pointer to the [sqlite3_mutex] object that ** serializes access to the [database connection] given in the argument ** when the [threading mode] is Serialized. ** ^If the [threading mode] is Single-thread or Multi-thread then this @@ -8279,7 +8952,7 @@ SQLITE_API sqlite3_mutex *sqlite3_db_mutex(sqlite3*); ** method becomes the return value of this routine. ** ** A few opcodes for [sqlite3_file_control()] are handled directly -** by the SQLite core and never invoke the +** by the SQLite core and never invoke the ** sqlite3_io_methods.xFileControl method. ** ^The [SQLITE_FCNTL_FILE_POINTER] value for the op parameter causes ** a pointer to the underlying [sqlite3_file] object to be written into @@ -8336,16 +9009,19 @@ SQLITE_API int sqlite3_test_control(int op, ...); #define SQLITE_TESTCTRL_FIRST 5 #define SQLITE_TESTCTRL_PRNG_SAVE 5 #define SQLITE_TESTCTRL_PRNG_RESTORE 6 -#define SQLITE_TESTCTRL_PRNG_RESET 7 +#define SQLITE_TESTCTRL_PRNG_RESET 7 /* NOT USED */ +#define SQLITE_TESTCTRL_FK_NO_ACTION 7 #define SQLITE_TESTCTRL_BITVEC_TEST 8 #define SQLITE_TESTCTRL_FAULT_INSTALL 9 #define SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS 10 #define SQLITE_TESTCTRL_PENDING_BYTE 11 #define SQLITE_TESTCTRL_ASSERT 12 #define SQLITE_TESTCTRL_ALWAYS 13 -#define SQLITE_TESTCTRL_RESERVE 14 +#define SQLITE_TESTCTRL_RESERVE 14 /* NOT USED */ +#define SQLITE_TESTCTRL_JSON_SELFCHECK 14 #define SQLITE_TESTCTRL_OPTIMIZATIONS 15 #define SQLITE_TESTCTRL_ISKEYWORD 16 /* NOT USED */ +#define SQLITE_TESTCTRL_GETOPT 16 #define SQLITE_TESTCTRL_SCRATCHMALLOC 17 /* NOT USED */ #define SQLITE_TESTCTRL_INTERNAL_FUNCTIONS 17 #define SQLITE_TESTCTRL_LOCALTIME_FAULT 18 @@ -8358,20 +9034,29 @@ SQLITE_API int sqlite3_test_control(int op, ...); #define SQLITE_TESTCTRL_SORTER_MMAP 24 #define SQLITE_TESTCTRL_IMPOSTER 25 #define SQLITE_TESTCTRL_PARSER_COVERAGE 26 -#define SQLITE_TESTCTRL_LAST 26 /* Largest TESTCTRL */ +#define SQLITE_TESTCTRL_RESULT_INTREAL 27 +#define SQLITE_TESTCTRL_PRNG_SEED 28 +#define SQLITE_TESTCTRL_EXTRA_SCHEMA_CHECKS 29 +#define SQLITE_TESTCTRL_SEEK_COUNT 30 +#define SQLITE_TESTCTRL_TRACEFLAGS 31 +#define SQLITE_TESTCTRL_TUNE 32 +#define SQLITE_TESTCTRL_LOGEST 33 +#define SQLITE_TESTCTRL_USELONGDOUBLE 34 /* NOT USED */ +#define SQLITE_TESTCTRL_ATOF 34 +#define SQLITE_TESTCTRL_LAST 34 /* Largest TESTCTRL */ /* ** CAPI3REF: SQL Keyword Checking ** -** These routines provide access to the set of SQL language keywords -** recognized by SQLite. Applications can uses these routines to determine +** These routines provide access to the set of SQL language keywords +** recognized by SQLite. Applications can use these routines to determine ** whether or not a specific identifier needs to be escaped (for example, ** by enclosing in double-quotes) so as not to confuse the parser. ** ** The sqlite3_keyword_count() interface returns the number of distinct ** keywords understood by SQLite. ** -** The sqlite3_keyword_name(N,Z,L) interface finds the N-th keyword and +** The sqlite3_keyword_name(N,Z,L) interface finds the 0-based N-th keyword and ** makes *Z point to that keyword expressed as UTF8 and writes the number ** of bytes in the keyword into *L. The string that *Z points to is not ** zero-terminated. The sqlite3_keyword_name(N,Z,L) routine returns @@ -8435,14 +9120,14 @@ typedef struct sqlite3_str sqlite3_str; ** ** ^The [sqlite3_str_new(D)] interface allocates and initializes ** a new [sqlite3_str] object. To avoid memory leaks, the object returned by -** [sqlite3_str_new()] must be freed by a subsequent call to +** [sqlite3_str_new()] must be freed by a subsequent call to ** [sqlite3_str_finish(X)]. ** ** ^The [sqlite3_str_new(D)] interface always returns a pointer to a ** valid [sqlite3_str] object, though in the event of an out-of-memory ** error the returned object might be a special singleton that will -** silently reject new text, always return SQLITE_NOMEM from -** [sqlite3_str_errcode()], always return 0 for +** silently reject new text, always return SQLITE_NOMEM from +** [sqlite3_str_errcode()], always return 0 for ** [sqlite3_str_length()], and always return NULL from ** [sqlite3_str_finish(X)]. It is always safe to use the value ** returned by [sqlite3_str_new(D)] as the sqlite3_str parameter @@ -8466,21 +9151,26 @@ SQLITE_API sqlite3_str *sqlite3_str_new(sqlite3*); ** pass the returned value to [sqlite3_free()] to avoid a memory leak. ** ^The [sqlite3_str_finish(X)] interface may return a NULL pointer if any ** errors were encountered during construction of the string. ^The -** [sqlite3_str_finish(X)] interface will also return a NULL pointer if the +** [sqlite3_str_finish(X)] interface might also return a NULL pointer if the ** string in [sqlite3_str] object X is zero bytes long. +** +** ^The [sqlite3_str_free(X)] interface destroys both the sqlite3_str object +** X and the string content it contains. Calling sqlite3_str_free(X) is +** the equivalent of calling [sqlite3_free](sqlite3_str_finish(X)). */ SQLITE_API char *sqlite3_str_finish(sqlite3_str*); +SQLITE_API void sqlite3_str_free(sqlite3_str*); /* ** CAPI3REF: Add Content To A Dynamic String ** METHOD: sqlite3_str ** -** These interfaces add content to an sqlite3_str object previously obtained -** from [sqlite3_str_new()]. +** These interfaces add or remove content to an sqlite3_str object +** previously obtained from [sqlite3_str_new()]. ** -** ^The [sqlite3_str_appendf(X,F,...)] and +** ^The [sqlite3_str_appendf(X,F,...)] and ** [sqlite3_str_vappendf(X,F,V)] interfaces uses the [built-in printf] -** functionality of SQLite to append formatted text onto the end of +** functionality of SQLite to append formatted text onto the end of ** [sqlite3_str] object X. ** ** ^The [sqlite3_str_append(X,S,N)] method appends exactly N bytes from string S @@ -8497,7 +9187,11 @@ SQLITE_API char *sqlite3_str_finish(sqlite3_str*); ** ^This method can be used, for example, to add whitespace indentation. ** ** ^The [sqlite3_str_reset(X)] method resets the string under construction -** inside [sqlite3_str] object X back to zero bytes in length. +** inside [sqlite3_str] object X back to zero bytes in length. +** +** ^The [sqlite3_str_truncate(X,N)] method changes the length of the string +** under construction to be N bytes or less. This routine is a no-op if +** N is negative or if the string is already N bytes or smaller in size. ** ** These methods do not return a result code. ^If an error occurs, that fact ** is recorded in the [sqlite3_str] object and can be recovered by a @@ -8509,6 +9203,7 @@ SQLITE_API void sqlite3_str_append(sqlite3_str*, const char *zIn, int N); SQLITE_API void sqlite3_str_appendall(sqlite3_str*, const char *zIn); SQLITE_API void sqlite3_str_appendchar(sqlite3_str*, int N, char C); SQLITE_API void sqlite3_str_reset(sqlite3_str*); +SQLITE_API void sqlite3_str_truncate(sqlite3_str*,int N); /* ** CAPI3REF: Status Of A Dynamic String @@ -8532,7 +9227,7 @@ SQLITE_API void sqlite3_str_reset(sqlite3_str*); ** content of the dynamic string under construction in X. The value ** returned by [sqlite3_str_value(X)] is managed by the sqlite3_str object X ** and might be freed or altered by any subsequent method on the same -** [sqlite3_str] object. Applications must not used the pointer returned +** [sqlite3_str] object. Applications must not use the pointer returned by ** [sqlite3_str_value(X)] after any subsequent method call on the same ** object. ^Applications may change the content of the string returned ** by [sqlite3_str_value(X)] as long as they do not write into any bytes @@ -8599,7 +9294,7 @@ SQLITE_API int sqlite3_status64( **
      This parameter records the largest memory allocation request ** handed to [sqlite3_malloc()] or [sqlite3_realloc()] (or their ** internal equivalents). Only the value returned in the -** *pHighwater parameter to [sqlite3_status()] is of interest. +** *pHighwater parameter to [sqlite3_status()] is of interest. ** The value written into the *pCurrent parameter is undefined.
      )^ ** ** [[SQLITE_STATUS_MALLOC_COUNT]] ^(
      SQLITE_STATUS_MALLOC_COUNT
      @@ -8608,24 +9303,24 @@ SQLITE_API int sqlite3_status64( ** ** [[SQLITE_STATUS_PAGECACHE_USED]] ^(
      SQLITE_STATUS_PAGECACHE_USED
      **
      This parameter returns the number of pages used out of the -** [pagecache memory allocator] that was configured using +** [pagecache memory allocator] that was configured using ** [SQLITE_CONFIG_PAGECACHE]. The ** value returned is in pages, not in bytes.
      )^ ** -** [[SQLITE_STATUS_PAGECACHE_OVERFLOW]] +** [[SQLITE_STATUS_PAGECACHE_OVERFLOW]] ** ^(
      SQLITE_STATUS_PAGECACHE_OVERFLOW
      **
      This parameter returns the number of bytes of page cache ** allocation which could not be satisfied by the [SQLITE_CONFIG_PAGECACHE] ** buffer and where forced to overflow to [sqlite3_malloc()]. The ** returned value includes allocations that overflowed because they -** where too large (they were larger than the "sz" parameter to +** were too large (they were larger than the "sz" parameter to ** [SQLITE_CONFIG_PAGECACHE]) and allocations that overflowed because ** no space was left in the page cache.
      )^ ** ** [[SQLITE_STATUS_PAGECACHE_SIZE]] ^(
      SQLITE_STATUS_PAGECACHE_SIZE
      **
      This parameter records the largest memory allocation request -** handed to [pagecache memory allocator]. Only the value returned in the -** *pHighwater parameter to [sqlite3_status()] is of interest. +** handed to the [pagecache memory allocator]. Only the value returned in the +** *pHighwater parameter to [sqlite3_status()] is of interest. ** The value written into the *pCurrent parameter is undefined.
      )^ ** ** [[SQLITE_STATUS_SCRATCH_USED]]
      SQLITE_STATUS_SCRATCH_USED
      @@ -8638,7 +9333,7 @@ SQLITE_API int sqlite3_status64( **
      No longer used.
      ** ** [[SQLITE_STATUS_PARSER_STACK]] ^(
      SQLITE_STATUS_PARSER_STACK
      -**
      The *pHighwater parameter records the deepest parser stack. +**
      The *pHighwater parameter records the deepest parser stack. ** The *pCurrent value is undefined. The *pHighwater value is only ** meaningful if SQLite is compiled with [YYTRACKMAXSTACKDEPTH].
      )^ **
    @@ -8660,12 +9355,12 @@ SQLITE_API int sqlite3_status64( ** CAPI3REF: Database Connection Status ** METHOD: sqlite3 ** -** ^This interface is used to retrieve runtime status information +** ^This interface is used to retrieve runtime status information ** about a single [database connection]. ^The first argument is the ** database connection object to be interrogated. ^The second argument ** is an integer constant, taken from the set of ** [SQLITE_DBSTATUS options], that -** determines the parameter to interrogate. The set of +** determines the parameter to interrogate. The set of ** [SQLITE_DBSTATUS options] is likely ** to grow in future releases of SQLite. ** @@ -8677,9 +9372,18 @@ SQLITE_API int sqlite3_status64( ** ^The sqlite3_db_status() routine returns SQLITE_OK on success and a ** non-zero [error code] on failure. ** +** ^The sqlite3_db_status64(D,O,C,H,R) routine works exactly the same +** way as sqlite3_db_status(D,O,C,H,R) routine except that the C and H +** parameters are pointer to 64-bit integers (type: sqlite3_int64) instead +** of pointers to 32-bit integers, which allows larger status values +** to be returned. If a status value exceeds 2,147,483,647 then +** sqlite3_db_status() will truncate the value whereas sqlite3_db_status64() +** will return the full value. +** ** See also: [sqlite3_status()] and [sqlite3_stmt_status()]. */ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int resetFlg); +SQLITE_API int sqlite3_db_status64(sqlite3*,int,sqlite3_int64*,sqlite3_int64*,int); /* ** CAPI3REF: Status Parameters for database connections @@ -8700,51 +9404,53 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r ** checked out.)^ ** ** [[SQLITE_DBSTATUS_LOOKASIDE_HIT]] ^(
    SQLITE_DBSTATUS_LOOKASIDE_HIT
    -**
    This parameter returns the number malloc attempts that were +**
    This parameter returns the number of malloc attempts that were ** satisfied using lookaside memory. Only the high-water value is meaningful; -** the current value is always zero.)^ +** the current value is always zero.
    )^ ** ** [[SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE]] ** ^(
    SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE
    -**
    This parameter returns the number malloc attempts that might have +**
    This parameter returns the number of malloc attempts that might have ** been satisfied using lookaside memory but failed due to the amount of ** memory requested being larger than the lookaside slot size. ** Only the high-water value is meaningful; -** the current value is always zero.)^ +** the current value is always zero.
    )^ ** ** [[SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL]] ** ^(
    SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL
    -**
    This parameter returns the number malloc attempts that might have +**
    This parameter returns the number of malloc attempts that might have ** been satisfied using lookaside memory but failed due to all lookaside ** memory already being in use. ** Only the high-water value is meaningful; -** the current value is always zero.)^ +** the current value is always zero.
    )^ ** ** [[SQLITE_DBSTATUS_CACHE_USED]] ^(
    SQLITE_DBSTATUS_CACHE_USED
    **
    This parameter returns the approximate number of bytes of heap ** memory used by all pager caches associated with the database connection.)^ ** ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_USED is always 0. +**
    ** -** [[SQLITE_DBSTATUS_CACHE_USED_SHARED]] +** [[SQLITE_DBSTATUS_CACHE_USED_SHARED]] ** ^(
    SQLITE_DBSTATUS_CACHE_USED_SHARED
    **
    This parameter is similar to DBSTATUS_CACHE_USED, except that if a ** pager cache is shared between two or more connections the bytes of heap ** memory used by that pager cache is divided evenly between the attached ** connections.)^ In other words, if none of the pager caches associated ** with the database connection are shared, this request returns the same -** value as DBSTATUS_CACHE_USED. Or, if one or more or the pager caches are +** value as DBSTATUS_CACHE_USED. Or, if one or more of the pager caches are ** shared, the value returned by this call will be smaller than that returned ** by DBSTATUS_CACHE_USED. ^The highwater mark associated with -** SQLITE_DBSTATUS_CACHE_USED_SHARED is always 0. +** SQLITE_DBSTATUS_CACHE_USED_SHARED is always 0.
    ** ** [[SQLITE_DBSTATUS_SCHEMA_USED]] ^(
    SQLITE_DBSTATUS_SCHEMA_USED
    **
    This parameter returns the approximate number of bytes of heap ** memory used to store the schema for all databases associated -** with the connection - main, temp, and any [ATTACH]-ed databases.)^ +** with the connection - main, temp, and any [ATTACH]-ed databases.)^ ** ^The full amount of memory used by the schemas is reported, even if the ** schema memory is shared with other database connections due to ** [shared cache mode] being enabled. ** ^The highwater mark associated with SQLITE_DBSTATUS_SCHEMA_USED is always 0. +**
    ** ** [[SQLITE_DBSTATUS_STMT_USED]] ^(
    SQLITE_DBSTATUS_STMT_USED
    **
    This parameter returns the approximate number of bytes of heap @@ -8755,13 +9461,13 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r ** ** [[SQLITE_DBSTATUS_CACHE_HIT]] ^(
    SQLITE_DBSTATUS_CACHE_HIT
    **
    This parameter returns the number of pager cache hits that have -** occurred.)^ ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_HIT +** occurred.)^ ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_HIT ** is always 0. **
    ** ** [[SQLITE_DBSTATUS_CACHE_MISS]] ^(
    SQLITE_DBSTATUS_CACHE_MISS
    **
    This parameter returns the number of pager cache misses that have -** occurred.)^ ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_MISS +** occurred.)^ ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_MISS ** is always 0. **
    ** @@ -8774,6 +9480,10 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r ** If an IO or other error occurs while writing a page to disk, the effect ** on subsequent SQLITE_DBSTATUS_CACHE_WRITE requests is undefined.)^ ^The ** highwater mark associated with SQLITE_DBSTATUS_CACHE_WRITE is always 0. +**

    +** ^(There is overlap between the quantities measured by this parameter +** (SQLITE_DBSTATUS_CACHE_WRITE) and SQLITE_DBSTATUS_TEMPBUF_SPILL. +** Resetting one will reduce the other.)^ ** ** ** [[SQLITE_DBSTATUS_CACHE_SPILL]] ^(

    SQLITE_DBSTATUS_CACHE_SPILL
    @@ -8781,14 +9491,26 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r ** been written to disk in the middle of a transaction due to the page ** cache overflowing. Transactions are more efficient if they are written ** to disk all at once. When pages spill mid-transaction, that introduces -** additional overhead. This parameter can be used help identify -** inefficiencies that can be resolve by increasing the cache size. +** additional overhead. This parameter can be used to help identify +** inefficiencies that can be resolved by increasing the cache size. ** ** ** [[SQLITE_DBSTATUS_DEFERRED_FKS]] ^(
    SQLITE_DBSTATUS_DEFERRED_FKS
    **
    This parameter returns zero for the current value if and only if ** all foreign key constraints (deferred or immediate) have been ** resolved.)^ ^The highwater mark is always 0. +** +** [[SQLITE_DBSTATUS_TEMPBUF_SPILL] ^(
    SQLITE_DBSTATUS_TEMPBUF_SPILL
    +**
    ^(This parameter returns the number of bytes written to temporary +** files on disk that could have been kept in memory had sufficient memory +** been available. This value includes writes to intermediate tables that +** are part of complex queries, external sorts that spill to disk, and +** writes to TEMP tables.)^ +** ^The highwater mark is always 0. +**

    +** ^(There is overlap between the quantities measured by this parameter +** (SQLITE_DBSTATUS_TEMPBUF_SPILL) and SQLITE_DBSTATUS_CACHE_WRITE. +** Resetting one will reduce the other.)^ **

    ** */ @@ -8805,7 +9527,8 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r #define SQLITE_DBSTATUS_DEFERRED_FKS 10 #define SQLITE_DBSTATUS_CACHE_USED_SHARED 11 #define SQLITE_DBSTATUS_CACHE_SPILL 12 -#define SQLITE_DBSTATUS_MAX 12 /* Largest defined DBSTATUS */ +#define SQLITE_DBSTATUS_TEMPBUF_SPILL 13 +#define SQLITE_DBSTATUS_MAX 13 /* Largest defined DBSTATUS */ /* @@ -8819,7 +9542,7 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r ** statements. For example, if the number of table steps greatly exceeds ** the number of table searches or result rows, that would tend to indicate ** that the prepared statement is using a full table scan rather than -** an index. +** an index. ** ** ^(This interface is used to retrieve and reset counter values from ** a [prepared statement]. The first argument is the prepared statement @@ -8846,40 +9569,50 @@ SQLITE_API int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg); ** [[SQLITE_STMTSTATUS_FULLSCAN_STEP]]
    SQLITE_STMTSTATUS_FULLSCAN_STEP
    **
    ^This is the number of times that SQLite has stepped forward in ** a table as part of a full table scan. Large numbers for this counter -** may indicate opportunities for performance improvement through +** may indicate opportunities for performance improvement through ** careful use of indices.
    ** ** [[SQLITE_STMTSTATUS_SORT]]
    SQLITE_STMTSTATUS_SORT
    **
    ^This is the number of sort operations that have occurred. ** A non-zero value in this counter may indicate an opportunity to -** improvement performance through careful use of indices.
    +** improve performance through careful use of indices. ** ** [[SQLITE_STMTSTATUS_AUTOINDEX]]
    SQLITE_STMTSTATUS_AUTOINDEX
    **
    ^This is the number of rows inserted into transient indices that ** were created automatically in order to help joins run faster. ** A non-zero value in this counter may indicate an opportunity to -** improvement performance by adding permanent indices that do not +** improve performance by adding permanent indices that do not ** need to be reinitialized each time the statement is run.
    ** ** [[SQLITE_STMTSTATUS_VM_STEP]]
    SQLITE_STMTSTATUS_VM_STEP
    **
    ^This is the number of virtual machine operations executed ** by the prepared statement if that number is less than or equal -** to 2147483647. The number of virtual machine operations can be +** to 2147483647. The number of virtual machine operations can be ** used as a proxy for the total work done by the prepared statement. ** If the number of virtual machine operations exceeds 2147483647 -** then the value returned by this statement status code is undefined. +** then the value returned by this statement status code is undefined.
    ** ** [[SQLITE_STMTSTATUS_REPREPARE]]
    SQLITE_STMTSTATUS_REPREPARE
    **
    ^This is the number of times that the prepare statement has been -** automatically regenerated due to schema changes or change to -** [bound parameters] that might affect the query plan. +** automatically regenerated due to schema changes or changes to +** [bound parameters] that might affect the query plan.
    ** ** [[SQLITE_STMTSTATUS_RUN]]
    SQLITE_STMTSTATUS_RUN
    **
    ^This is the number of times that the prepared statement has ** been run. A single "run" for the purposes of this counter is one ** or more calls to [sqlite3_step()] followed by a call to [sqlite3_reset()]. ** The counter is incremented on the first [sqlite3_step()] call of each -** cycle. +** cycle.
    +** +** [[SQLITE_STMTSTATUS_FILTER_MISS]] +** [[SQLITE_STMTSTATUS_FILTER HIT]] +**
    SQLITE_STMTSTATUS_FILTER_HIT
    +** SQLITE_STMTSTATUS_FILTER_MISS
    +**
    ^SQLITE_STMTSTATUS_FILTER_HIT is the number of times that a join +** step was bypassed because a Bloom filter returned not-found. The +** corresponding SQLITE_STMTSTATUS_FILTER_MISS value is the number of +** times that the Bloom filter returned a find, and thus the join step +** had to be processed as normal.
    ** ** [[SQLITE_STMTSTATUS_MEMUSED]]
    SQLITE_STMTSTATUS_MEMUSED
    **
    ^This is the approximate number of bytes of heap memory @@ -8895,6 +9628,8 @@ SQLITE_API int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg); #define SQLITE_STMTSTATUS_VM_STEP 4 #define SQLITE_STMTSTATUS_REPREPARE 5 #define SQLITE_STMTSTATUS_RUN 6 +#define SQLITE_STMTSTATUS_FILTER_MISS 7 +#define SQLITE_STMTSTATUS_FILTER_HIT 8 #define SQLITE_STMTSTATUS_MEMUSED 99 /* @@ -8931,15 +9666,15 @@ struct sqlite3_pcache_page { ** KEYWORDS: {page cache} ** ** ^(The [sqlite3_config]([SQLITE_CONFIG_PCACHE2], ...) interface can -** register an alternative page cache implementation by passing in an +** register an alternative page cache implementation by passing in an ** instance of the sqlite3_pcache_methods2 structure.)^ -** In many applications, most of the heap memory allocated by +** In many applications, most of the heap memory allocated by ** SQLite is used for the page cache. -** By implementing a +** By implementing a ** custom page cache using this API, an application can better control -** the amount of memory consumed by SQLite, the way in which -** that memory is allocated and released, and the policies used to -** determine exactly which parts of a database file are cached and for +** the amount of memory consumed by SQLite, the way in which +** that memory is allocated and released, and the policies used to +** determine exactly which parts of a database file are cached and for ** how long. ** ** The alternative page cache mechanism is an @@ -8952,19 +9687,19 @@ struct sqlite3_pcache_page { ** [sqlite3_config()] returns.)^ ** ** [[the xInit() page cache method]] -** ^(The xInit() method is called once for each effective +** ^(The xInit() method is called once for each effective ** call to [sqlite3_initialize()])^ ** (usually only once during the lifetime of the process). ^(The xInit() ** method is passed a copy of the sqlite3_pcache_methods2.pArg value.)^ -** The intent of the xInit() method is to set up global data structures -** required by the custom page cache implementation. -** ^(If the xInit() method is NULL, then the +** The intent of the xInit() method is to set up global data structures +** required by the custom page cache implementation. +** ^(If the xInit() method is NULL, then the ** built-in default page cache is used instead of the application defined ** page cache.)^ ** ** [[the xShutdown() page cache method]] ** ^The xShutdown() method is called by [sqlite3_shutdown()]. -** It can be used to clean up +** It can be used to clean up ** any outstanding resources before process shutdown, if required. ** ^The xShutdown() method may be NULL. ** @@ -8982,9 +9717,9 @@ struct sqlite3_pcache_page { ** SQLite will typically create one cache instance for each open database file, ** though this is not guaranteed. ^The ** first parameter, szPage, is the size in bytes of the pages that must -** be allocated by the cache. ^szPage will always a power of two. ^The -** second parameter szExtra is a number of bytes of extra storage -** associated with each page cache entry. ^The szExtra parameter will +** be allocated by the cache. ^szPage will always be a power of two. ^The +** second parameter szExtra is a number of bytes of extra storage +** associated with each page cache entry. ^The szExtra parameter will be ** a number less than 250. SQLite will use the ** extra szExtra bytes on each page to store metadata about the underlying ** database page on disk. The value passed into szExtra depends @@ -8992,17 +9727,17 @@ struct sqlite3_pcache_page { ** ^The third argument to xCreate(), bPurgeable, is true if the cache being ** created will be used to cache database pages of a file stored on disk, or ** false if it is used for an in-memory database. The cache implementation -** does not have to do anything special based with the value of bPurgeable; +** does not have to do anything special based upon the value of bPurgeable; ** it is purely advisory. ^On a cache where bPurgeable is false, SQLite will ** never invoke xUnpin() except to deliberately delete a page. ** ^In other words, calls to xUnpin() on a cache with bPurgeable set to -** false will always have the "discard" flag set to true. -** ^Hence, a cache created with bPurgeable false will +** false will always have the "discard" flag set to true. +** ^Hence, a cache created with bPurgeable set to false will ** never contain any unpinned pages. ** ** [[the xCachesize() page cache method]] ** ^(The xCachesize() method may be called at any time by SQLite to set the -** suggested maximum cache-size (number of pages stored by) the cache +** suggested maximum cache-size (number of pages stored) for the cache ** instance passed as the first argument. This is the value configured using ** the SQLite "[PRAGMA cache_size]" command.)^ As with the bPurgeable ** parameter, the implementation is not required to do anything with this @@ -9011,12 +9746,12 @@ struct sqlite3_pcache_page { ** [[the xPagecount() page cache methods]] ** The xPagecount() method must return the number of pages currently ** stored in the cache, both pinned and unpinned. -** +** ** [[the xFetch() page cache methods]] -** The xFetch() method locates a page in the cache and returns a pointer to +** The xFetch() method locates a page in the cache and returns a pointer to ** an sqlite3_pcache_page object associated with that page, or a NULL pointer. ** The pBuf element of the returned sqlite3_pcache_page object will be a -** pointer to a buffer of szPage bytes used to store the content of a +** pointer to a buffer of szPage bytes used to store the content of a ** single database page. The pExtra element of sqlite3_pcache_page will be ** a pointer to the szExtra bytes of extra storage that SQLite has requested ** for each entry in the page cache. @@ -9029,12 +9764,12 @@ struct sqlite3_pcache_page { ** implementation must return a pointer to the page buffer with its content ** intact. If the requested page is not already in the cache, then the ** cache implementation should use the value of the createFlag -** parameter to help it determined what action to take: +** parameter to help it determine what action to take: ** ** **
    createFlag Behavior when page is not already in cache **
    0 Do not allocate a new page. Return NULL. -**
    1 Allocate a new page if it easy and convenient to do so. +**
    1 Allocate a new page if it is easy and convenient to do so. ** Otherwise return NULL. **
    2 Make every effort to allocate a new page. Only return ** NULL if allocating a new page is effectively impossible. @@ -9042,7 +9777,7 @@ struct sqlite3_pcache_page { ** ** ^(SQLite will normally invoke xFetch() with a createFlag of 0 or 1. SQLite ** will only use a createFlag of 2 after a prior call with a createFlag of 1 -** failed.)^ In between the to xFetch() calls, SQLite may +** failed.)^ In between the xFetch() calls, SQLite may ** attempt to unpin one or more cache pages by spilling the content of ** pinned pages to disk and synching the operating system disk cache. ** @@ -9051,12 +9786,12 @@ struct sqlite3_pcache_page { ** as its second argument. If the third parameter, discard, is non-zero, ** then the page must be evicted from the cache. ** ^If the discard parameter is -** zero, then the page may be discarded or retained at the discretion of +** zero, then the page may be discarded or retained at the discretion of the ** page cache implementation. ^The page cache implementation ** may choose to evict unpinned pages at any time. ** -** The cache must not perform any reference counting. A single -** call to xUnpin() unpins the page regardless of the number of prior calls +** The cache must not perform any reference counting. A single +** call to xUnpin() unpins the page regardless of the number of prior calls ** to xFetch(). ** ** [[the xRekey() page cache methods]] @@ -9069,7 +9804,7 @@ struct sqlite3_pcache_page { ** When SQLite calls the xTruncate() method, the cache must discard all ** existing cache entries with page numbers (keys) greater than or equal ** to the value of the iLimit parameter passed to xTruncate(). If any -** of these pages are pinned, they are implicitly unpinned, meaning that +** of these pages are pinned, they become implicitly unpinned, meaning that ** they can be safely discarded. ** ** [[the xDestroy() page cache method]] @@ -9096,7 +9831,7 @@ struct sqlite3_pcache_methods2 { int (*xPagecount)(sqlite3_pcache*); sqlite3_pcache_page *(*xFetch)(sqlite3_pcache*, unsigned key, int createFlag); void (*xUnpin)(sqlite3_pcache*, sqlite3_pcache_page*, int discard); - void (*xRekey)(sqlite3_pcache*, sqlite3_pcache_page*, + void (*xRekey)(sqlite3_pcache*, sqlite3_pcache_page*, unsigned oldKey, unsigned newKey); void (*xTruncate)(sqlite3_pcache*, unsigned iLimit); void (*xDestroy)(sqlite3_pcache*); @@ -9141,7 +9876,7 @@ typedef struct sqlite3_backup sqlite3_backup; ** ** The backup API copies the content of one database into another. ** It is useful either for creating backups of databases or -** for copying in-memory databases to or from persistent files. +** for copying in-memory databases to or from persistent files. ** ** See Also: [Using the SQLite Online Backup API] ** @@ -9152,36 +9887,36 @@ typedef struct sqlite3_backup sqlite3_backup; ** ^Thus, the backup may be performed on a live source database without ** preventing other database connections from ** reading or writing to the source database while the backup is underway. -** -** ^(To perform a backup operation: +** +** ^(To perform a backup operation: **
      **
    1. sqlite3_backup_init() is called once to initialize the -** backup, -**
    2. sqlite3_backup_step() is called one or more times to transfer +** backup, +**
    3. sqlite3_backup_step() is called one or more times to transfer ** the data between the two databases, and finally -**
    4. sqlite3_backup_finish() is called to release all resources -** associated with the backup operation. +**
    5. sqlite3_backup_finish() is called to release all resources +** associated with the backup operation. **
    )^ ** There should be exactly one call to sqlite3_backup_finish() for each ** successful call to sqlite3_backup_init(). ** ** [[sqlite3_backup_init()]] sqlite3_backup_init() ** -** ^The D and N arguments to sqlite3_backup_init(D,N,S,M) are the -** [database connection] associated with the destination database +** ^The D and N arguments to sqlite3_backup_init(D,N,S,M) are the +** [database connection] associated with the destination database ** and the database name, respectively. ** ^The database name is "main" for the main database, "temp" for the ** temporary database, or the name specified after the AS keyword in ** an [ATTACH] statement for an attached database. -** ^The S and M arguments passed to +** ^The S and M arguments passed to ** sqlite3_backup_init(D,N,S,M) identify the [database connection] ** and database name of the source database, respectively. ** ^The source and destination [database connections] (parameters S and D) ** must be different or else sqlite3_backup_init(D,N,S,M) will fail with ** an error. ** -** ^A call to sqlite3_backup_init() will fail, returning NULL, if -** there is already a read or read-write transaction open on the +** ^A call to sqlite3_backup_init() will fail, returning NULL, if +** there is already a read or read-write transaction open on the ** destination database. ** ** ^If an error occurs within sqlite3_backup_init(D,N,S,M), then NULL is @@ -9193,14 +9928,14 @@ typedef struct sqlite3_backup sqlite3_backup; ** ^A successful call to sqlite3_backup_init() returns a pointer to an ** [sqlite3_backup] object. ** ^The [sqlite3_backup] object may be used with the sqlite3_backup_step() and -** sqlite3_backup_finish() functions to perform the specified backup +** sqlite3_backup_finish() functions to perform the specified backup ** operation. ** ** [[sqlite3_backup_step()]] sqlite3_backup_step() ** -** ^Function sqlite3_backup_step(B,N) will copy up to N pages between +** ^Function sqlite3_backup_step(B,N) will copy up to N pages between ** the source and destination databases specified by [sqlite3_backup] object B. -** ^If N is negative, all remaining source pages are copied. +** ^If N is negative, all remaining source pages are copied. ** ^If sqlite3_backup_step(B,N) successfully copies N pages and there ** are still more pages to be copied, then the function returns [SQLITE_OK]. ** ^If sqlite3_backup_step(B,N) successfully finishes copying all pages @@ -9222,8 +9957,8 @@ typedef struct sqlite3_backup sqlite3_backup; ** ** ^If sqlite3_backup_step() cannot obtain a required file-system lock, then ** the [sqlite3_busy_handler | busy-handler function] -** is invoked (if one is specified). ^If the -** busy-handler returns non-zero before the lock is available, then +** is invoked (if one is specified). ^If the +** busy-handler returns non-zero before the lock is available, then ** [SQLITE_BUSY] is returned to the caller. ^In this case the call to ** sqlite3_backup_step() can be retried later. ^If the source ** [database connection] @@ -9231,15 +9966,15 @@ typedef struct sqlite3_backup sqlite3_backup; ** is called, then [SQLITE_LOCKED] is returned immediately. ^Again, in this ** case the call to sqlite3_backup_step() can be retried later on. ^(If ** [SQLITE_IOERR_ACCESS | SQLITE_IOERR_XXX], [SQLITE_NOMEM], or -** [SQLITE_READONLY] is returned, then -** there is no point in retrying the call to sqlite3_backup_step(). These -** errors are considered fatal.)^ The application must accept -** that the backup operation has failed and pass the backup operation handle +** [SQLITE_READONLY] is returned, then +** there is no point in retrying the call to sqlite3_backup_step(). These +** errors are considered fatal.)^ The application must accept +** that the backup operation has failed and pass the backup operation handle ** to the sqlite3_backup_finish() to release associated resources. ** ** ^The first call to sqlite3_backup_step() obtains an exclusive lock -** on the destination file. ^The exclusive lock is not released until either -** sqlite3_backup_finish() is called or the backup operation is complete +** on the destination file. ^The exclusive lock is not released until either +** sqlite3_backup_finish() is called or the backup operation is complete ** and sqlite3_backup_step() returns [SQLITE_DONE]. ^Every call to ** sqlite3_backup_step() obtains a [shared lock] on the source database that ** lasts for the duration of the sqlite3_backup_step() call. @@ -9248,25 +9983,25 @@ typedef struct sqlite3_backup sqlite3_backup; ** through the backup process. ^If the source database is modified by an ** external process or via a database connection other than the one being ** used by the backup operation, then the backup will be automatically -** restarted by the next call to sqlite3_backup_step(). ^If the source -** database is modified by the using the same database connection as is used +** restarted by the next call to sqlite3_backup_step(). ^If the source +** database is modified by using the same database connection as is used ** by the backup operation, then the backup database is automatically ** updated at the same time. ** ** [[sqlite3_backup_finish()]] sqlite3_backup_finish() ** -** When sqlite3_backup_step() has returned [SQLITE_DONE], or when the +** When sqlite3_backup_step() has returned [SQLITE_DONE], or when the ** application wishes to abandon the backup operation, the application ** should destroy the [sqlite3_backup] by passing it to sqlite3_backup_finish(). ** ^The sqlite3_backup_finish() interfaces releases all -** resources associated with the [sqlite3_backup] object. +** resources associated with the [sqlite3_backup] object. ** ^If sqlite3_backup_step() has not yet returned [SQLITE_DONE], then any ** active write-transaction on the destination database is rolled back. ** The [sqlite3_backup] object is invalid ** and may not be used following a call to sqlite3_backup_finish(). ** ** ^The value returned by sqlite3_backup_finish is [SQLITE_OK] if no -** sqlite3_backup_step() errors occurred, regardless or whether or not +** sqlite3_backup_step() errors occurred, regardless of whether or not ** sqlite3_backup_step() completed. ** ^If an out-of-memory condition or IO error occurred during any prior ** sqlite3_backup_step() call on the same [sqlite3_backup] object, then @@ -9299,28 +10034,38 @@ typedef struct sqlite3_backup sqlite3_backup; ** connections, then the source database connection may be used concurrently ** from within other threads. ** -** However, the application must guarantee that the destination -** [database connection] is not passed to any other API (by any thread) after +** However, the application must guarantee that the destination +** [database connection] is not passed to any other API (by any thread) after ** sqlite3_backup_init() is called and before the corresponding call to ** sqlite3_backup_finish(). SQLite does not currently check to see ** if the application incorrectly accesses the destination [database connection] ** and so no error code is reported, but the operations may malfunction ** nevertheless. Use of the destination database connection while a -** backup is in progress might also also cause a mutex deadlock. +** backup is in progress might also cause a mutex deadlock. ** ** If running in [shared cache mode], the application must ** guarantee that the shared cache used by the destination database ** is not accessed while the backup is running. In practice this means -** that the application must guarantee that the disk file being +** that the application must guarantee that the disk file being ** backed up to is not accessed by any connection within the process, ** not just the specific connection that was passed to sqlite3_backup_init(). ** -** The [sqlite3_backup] object itself is partially threadsafe. Multiple +** The [sqlite3_backup] object itself is partially threadsafe. Multiple ** threads may safely make multiple concurrent calls to sqlite3_backup_step(). ** However, the sqlite3_backup_remaining() and sqlite3_backup_pagecount() ** APIs are not strictly speaking threadsafe. If they are invoked at the ** same time as another thread is invoking sqlite3_backup_step() it is ** possible that they return invalid values. +** +** Alternatives To Using The Backup API +** +** Other techniques for safely creating a consistent backup of an SQLite +** database include: +** +**
      +**
    • The [VACUUM INTO] command. +**
    • The [sqlite3_rsync] utility program. +**
    */ SQLITE_API sqlite3_backup *sqlite3_backup_init( sqlite3 *pDest, /* Destination database handle */ @@ -9340,8 +10085,8 @@ SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p); ** ^When running in shared-cache mode, a database operation may fail with ** an [SQLITE_LOCKED] error if the required locks on the shared-cache or ** individual tables within the shared-cache cannot be obtained. See -** [SQLite Shared-Cache Mode] for a description of shared-cache locking. -** ^This API may be used to register a callback that SQLite will invoke +** [SQLite Shared-Cache Mode] for a description of shared-cache locking. +** ^This API may be used to register a callback that SQLite will invoke ** when the connection currently holding the required lock relinquishes it. ** ^This API is only available if the library was compiled with the ** [SQLITE_ENABLE_UNLOCK_NOTIFY] C-preprocessor symbol defined. @@ -9349,18 +10094,18 @@ SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p); ** See Also: [Using the SQLite Unlock Notification Feature]. ** ** ^Shared-cache locks are released when a database connection concludes -** its current transaction, either by committing it or rolling it back. +** its current transaction, either by committing it or rolling it back. ** ** ^When a connection (known as the blocked connection) fails to obtain a ** shared-cache lock and SQLITE_LOCKED is returned to the caller, the ** identity of the database connection (the blocking connection) that -** has locked the required resource is stored internally. ^After an +** has locked the required resource is stored internally. ^After an ** application receives an SQLITE_LOCKED error, it may call the -** sqlite3_unlock_notify() method with the blocked connection handle as +** sqlite3_unlock_notify() method with the blocked connection handle as ** the first argument to register for a callback that will be invoked -** when the blocking connections current transaction is concluded. ^The +** when the blocking connection's current transaction is concluded. ^The ** callback is invoked from within the [sqlite3_step] or [sqlite3_close] -** call that concludes the blocking connections transaction. +** call that concludes the blocking connection's transaction. ** ** ^(If sqlite3_unlock_notify() is called in a multi-threaded application, ** there is a chance that the blocking connection will have already @@ -9370,15 +10115,15 @@ SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p); ** ** ^If the blocked connection is attempting to obtain a write-lock on a ** shared-cache table, and more than one other connection currently holds -** a read-lock on the same table, then SQLite arbitrarily selects one of +** a read-lock on the same table, then SQLite arbitrarily selects one of ** the other connections to use as the blocking connection. ** -** ^(There may be at most one unlock-notify callback registered by a +** ^(There may be at most one unlock-notify callback registered by a ** blocked connection. If sqlite3_unlock_notify() is called when the ** blocked connection already has a registered unlock-notify callback, ** then the new callback replaces the old.)^ ^If sqlite3_unlock_notify() is ** called with a NULL pointer as its second argument, then any existing -** unlock-notify callback is canceled. ^The blocked connections +** unlock-notify callback is canceled. ^The blocked connection's ** unlock-notify callback may also be canceled by closing the blocked ** connection using [sqlite3_close()]. ** @@ -9391,25 +10136,25 @@ SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p); ** ** Callback Invocation Details ** -** When an unlock-notify callback is registered, the application provides a +** When an unlock-notify callback is registered, the application provides a ** single void* pointer that is passed to the callback when it is invoked. ** However, the signature of the callback function allows SQLite to pass ** it an array of void* context pointers. The first argument passed to ** an unlock-notify callback is a pointer to an array of void* pointers, ** and the second is the number of entries in the array. ** -** When a blocking connections transaction is concluded, there may be +** When a blocking connection's transaction is concluded, there may be ** more than one blocked connection that has registered for an unlock-notify ** callback. ^If two or more such blocked connections have specified the ** same callback function, then instead of invoking the callback function ** multiple times, it is invoked once with the set of void* context pointers ** specified by the blocked connections bundled together into an array. -** This gives the application an opportunity to prioritize any actions +** This gives the application an opportunity to prioritize any actions ** related to the set of unblocked database connections. ** ** Deadlock Detection ** -** Assuming that after registering for an unlock-notify callback a +** Assuming that after registering for an unlock-notify callback a ** database waits for the callback to be issued before taking any further ** action (a reasonable assumption), then using this API may cause the ** application to deadlock. For example, if connection X is waiting for @@ -9432,7 +10177,7 @@ SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p); ** ** The "DROP TABLE" Exception ** -** When a call to [sqlite3_step()] returns SQLITE_LOCKED, it is almost +** When a call to [sqlite3_step()] returns SQLITE_LOCKED, it is almost ** always appropriate to call sqlite3_unlock_notify(). There is however, ** one exception. When executing a "DROP TABLE" or "DROP INDEX" statement, ** SQLite checks if there are any currently executing SELECT statements @@ -9445,7 +10190,7 @@ SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p); ** One way around this problem is to check the extended error code returned ** by an sqlite3_step() call. ^(If there is a blocking connection, then the ** extended error code is set to SQLITE_LOCKED_SHAREDCACHE. Otherwise, in -** the special "DROP TABLE/INDEX" case, the extended error code is just +** the special "DROP TABLE/INDEX" case, the extended error code is just ** SQLITE_LOCKED.)^ */ SQLITE_API int sqlite3_unlock_notify( @@ -9536,8 +10281,8 @@ SQLITE_API void sqlite3_log(int iErrCode, const char *zFormat, ...); ** ^The [sqlite3_wal_hook()] function is used to register a callback that ** is invoked each time data is committed to a database in wal mode. ** -** ^(The callback is invoked by SQLite after the commit has taken place and -** the associated write-lock on the database released)^, so the implementation +** ^(The callback is invoked by SQLite after the commit has taken place and +** the associated write-lock on the database released)^, so the implementation ** may read, write or [checkpoint] the database as required. ** ** ^The first parameter passed to the callback function when it is invoked @@ -9548,7 +10293,7 @@ SQLITE_API void sqlite3_log(int iErrCode, const char *zFormat, ...); ** is the number of pages currently in the write-ahead log file, ** including those that were just committed. ** -** The callback function should normally return [SQLITE_OK]. ^If an error +** ^The callback function should normally return [SQLITE_OK]. ^If an error ** code is returned, that error will propagate back up through the ** SQLite code base to cause the statement that provoked the callback ** to report an error, though the commit will have still occurred. If the @@ -9556,15 +10301,29 @@ SQLITE_API void sqlite3_log(int iErrCode, const char *zFormat, ...); ** that does not correspond to any valid SQLite error code, the results ** are undefined. ** -** A single database handle may have at most a single write-ahead log callback -** registered at one time. ^Calling [sqlite3_wal_hook()] replaces any -** previously registered write-ahead log callback. ^Note that the -** [sqlite3_wal_autocheckpoint()] interface and the -** [wal_autocheckpoint pragma] both invoke [sqlite3_wal_hook()] and will -** overwrite any prior [sqlite3_wal_hook()] settings. +** ^A single database handle may have at most a single write-ahead log +** callback registered at one time. ^Calling [sqlite3_wal_hook()] +** replaces the default behavior or previously registered write-ahead +** log callback. +** +** ^The return value is a copy of the third parameter from the +** previous call, if any, or 0. +** +** ^The [sqlite3_wal_autocheckpoint()] interface and the +** [wal_autocheckpoint pragma] both invoke [sqlite3_wal_hook()] and +** will overwrite any prior [sqlite3_wal_hook()] settings. +** +** ^If a write-ahead log callback is set using this function then +** [sqlite3_wal_checkpoint_v2()] or [PRAGMA wal_checkpoint] +** should be invoked periodically to keep the write-ahead log file +** from growing without bound. +** +** ^Passing a NULL pointer for the callback disables automatic +** checkpointing entirely. To re-enable the default behavior, call +** sqlite3_wal_autocheckpoint(db,1000) or use [PRAGMA wal_checkpoint]. */ SQLITE_API void *sqlite3_wal_hook( - sqlite3*, + sqlite3*, int(*)(void *,sqlite3*,const char*,int), void* ); @@ -9577,8 +10336,8 @@ SQLITE_API void *sqlite3_wal_hook( ** [sqlite3_wal_hook()] that causes any database on [database connection] D ** to automatically [checkpoint] ** after committing a transaction if there are N or -** more frames in the [write-ahead log] file. ^Passing zero or -** a negative value as the nFrame parameter disables automatic +** more frames in the [write-ahead log] file. ^Passing zero or +** a negative value as the N parameter disables automatic ** checkpoints entirely. ** ** ^The callback registered by this function replaces any existing callback @@ -9594,9 +10353,10 @@ SQLITE_API void *sqlite3_wal_hook( ** ** ^Every new [database connection] defaults to having the auto-checkpoint ** enabled with a threshold of 1000 or [SQLITE_DEFAULT_WAL_AUTOCHECKPOINT] -** pages. The use of this interface -** is only necessary if the default setting is found to be suboptimal -** for a particular application. +** pages. +** +** ^The use of this interface is only necessary if the default setting +** is found to be suboptimal for a particular application. */ SQLITE_API int sqlite3_wal_autocheckpoint(sqlite3 *db, int N); @@ -9607,7 +10367,7 @@ SQLITE_API int sqlite3_wal_autocheckpoint(sqlite3 *db, int N); ** ^(The sqlite3_wal_checkpoint(D,X) is equivalent to ** [sqlite3_wal_checkpoint_v2](D,X,[SQLITE_CHECKPOINT_PASSIVE],0,0).)^ ** -** In brief, sqlite3_wal_checkpoint(D,X) causes the content in the +** In brief, sqlite3_wal_checkpoint(D,X) causes the content in the ** [write-ahead log] for database X on [database connection] D to be ** transferred into the database file and for the write-ahead log to ** be reset. See the [checkpointing] documentation for addition @@ -9633,10 +10393,10 @@ SQLITE_API int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb); ** **
    **
    SQLITE_CHECKPOINT_PASSIVE
    -** ^Checkpoint as many frames as possible without waiting for any database -** readers or writers to finish, then sync the database file if all frames +** ^Checkpoint as many frames as possible without waiting for any database +** readers or writers to finish, then sync the database file if all frames ** in the log were checkpointed. ^The [busy-handler callback] -** is never invoked in the SQLITE_CHECKPOINT_PASSIVE mode. +** is never invoked in the SQLITE_CHECKPOINT_PASSIVE mode. ** ^On the other hand, passive mode might leave the checkpoint unfinished ** if there are concurrent readers or writers. ** @@ -9650,9 +10410,9 @@ SQLITE_API int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb); ** **
    SQLITE_CHECKPOINT_RESTART
    ** ^This mode works the same way as SQLITE_CHECKPOINT_FULL with the addition -** that after checkpointing the log file it blocks (calls the +** that after checkpointing the log file it blocks (calls the ** [busy-handler callback]) -** until all readers are reading from the database file only. ^This ensures +** until all readers are reading from the database file only. ^This ensures ** that the next writer will restart the log file from the beginning. ** ^Like SQLITE_CHECKPOINT_FULL, this mode blocks new ** database writer attempts while it is pending, but does not impede readers. @@ -9661,6 +10421,11 @@ SQLITE_API int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb); ** ^This mode works the same way as SQLITE_CHECKPOINT_RESTART with the ** addition that it also truncates the log file to zero bytes just prior ** to a successful return. +** +**
    SQLITE_CHECKPOINT_NOOP
    +** ^This mode always checkpoints zero frames. The only reason to invoke +** a NOOP checkpoint is to access the values returned by +** sqlite3_wal_checkpoint_v2() via output parameters *pnLog and *pnCkpt. **
    ** ** ^If pnLog is not NULL, then *pnLog is set to the total number of frames in @@ -9674,31 +10439,31 @@ SQLITE_API int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb); ** truncated to zero bytes and so both *pnLog and *pnCkpt will be set to zero. ** ** ^All calls obtain an exclusive "checkpoint" lock on the database file. ^If -** any other process is running a checkpoint operation at the same time, the -** lock cannot be obtained and SQLITE_BUSY is returned. ^Even if there is a +** any other process is running a checkpoint operation at the same time, the +** lock cannot be obtained and SQLITE_BUSY is returned. ^Even if there is a ** busy-handler configured, it will not be invoked in this case. ** -** ^The SQLITE_CHECKPOINT_FULL, RESTART and TRUNCATE modes also obtain the +** ^The SQLITE_CHECKPOINT_FULL, RESTART and TRUNCATE modes also obtain the ** exclusive "writer" lock on the database file. ^If the writer lock cannot be ** obtained immediately, and a busy-handler is configured, it is invoked and ** the writer lock retried until either the busy-handler returns 0 or the lock ** is successfully obtained. ^The busy-handler is also invoked while waiting for ** database readers as described above. ^If the busy-handler returns 0 before ** the writer lock is obtained or while waiting for database readers, the -** checkpoint operation proceeds from that point in the same way as -** SQLITE_CHECKPOINT_PASSIVE - checkpointing as many frames as possible +** checkpoint operation proceeds from that point in the same way as +** SQLITE_CHECKPOINT_PASSIVE - checkpointing as many frames as possible ** without blocking any further. ^SQLITE_BUSY is returned in this case. ** ** ^If parameter zDb is NULL or points to a zero length string, then the -** specified operation is attempted on all WAL databases [attached] to +** specified operation is attempted on all WAL databases [attached] to ** [database connection] db. In this case the -** values written to output parameters *pnLog and *pnCkpt are undefined. ^If -** an SQLITE_BUSY error is encountered when processing one or more of the -** attached WAL databases, the operation is still attempted on any remaining -** attached databases and SQLITE_BUSY is returned at the end. ^If any other -** error occurs while processing an attached database, processing is abandoned -** and the error code is returned to the caller immediately. ^If no error -** (SQLITE_BUSY or otherwise) is encountered while processing the attached +** values written to output parameters *pnLog and *pnCkpt are undefined. ^If +** an SQLITE_BUSY error is encountered when processing one or more of the +** attached WAL databases, the operation is still attempted on any remaining +** attached databases and SQLITE_BUSY is returned at the end. ^If any other +** error occurs while processing an attached database, processing is abandoned +** and the error code is returned to the caller immediately. ^If no error +** (SQLITE_BUSY or otherwise) is encountered while processing the attached ** databases, SQLITE_OK is returned. ** ** ^If database zDb is the name of an attached database that is not in WAL @@ -9731,9 +10496,10 @@ SQLITE_API int sqlite3_wal_checkpoint_v2( ** See the [sqlite3_wal_checkpoint_v2()] documentation for details on the ** meaning of each of these checkpoint modes. */ +#define SQLITE_CHECKPOINT_NOOP -1 /* Do no work at all */ #define SQLITE_CHECKPOINT_PASSIVE 0 /* Do as much as possible w/o blocking */ #define SQLITE_CHECKPOINT_FULL 1 /* Wait for writers, then checkpoint */ -#define SQLITE_CHECKPOINT_RESTART 2 /* Like FULL but wait for for readers */ +#define SQLITE_CHECKPOINT_RESTART 2 /* Like FULL but wait for readers */ #define SQLITE_CHECKPOINT_TRUNCATE 3 /* Like RESTART but also truncate WAL */ /* @@ -9746,14 +10512,20 @@ SQLITE_API int sqlite3_wal_checkpoint_v2( ** If this interface is invoked outside the context of an xConnect or ** xCreate virtual table method then the behavior is undefined. ** -** At present, there is only one option that may be configured using -** this function. (See [SQLITE_VTAB_CONSTRAINT_SUPPORT].) Further options -** may be added in the future. +** In the call sqlite3_vtab_config(D,C,...) the D parameter is the +** [database connection] in which the virtual table is being created and +** which is passed in as the first argument to the [xConnect] or [xCreate] +** method that is invoking sqlite3_vtab_config(). The C parameter is one +** of the [virtual table configuration options]. The presence and meaning +** of parameters after C depend on which [virtual table configuration option] +** is used. */ SQLITE_API int sqlite3_vtab_config(sqlite3*, int op, ...); /* ** CAPI3REF: Virtual Table Configuration Options +** KEYWORDS: {virtual table configuration options} +** KEYWORDS: {virtual table configuration option} ** ** These macros define the various options to the ** [sqlite3_vtab_config()] interface that [virtual table] implementations @@ -9761,7 +10533,7 @@ SQLITE_API int sqlite3_vtab_config(sqlite3*, int op, ...); ** **
    ** [[SQLITE_VTAB_CONSTRAINT_SUPPORT]] -**
    SQLITE_VTAB_CONSTRAINT_SUPPORT +**
    SQLITE_VTAB_CONSTRAINT_SUPPORT
    **
    Calls of the form ** [sqlite3_vtab_config](db,SQLITE_VTAB_CONSTRAINT_SUPPORT,X) are supported, ** where X is an integer. If X is zero, then the [virtual table] whose @@ -9769,30 +10541,62 @@ SQLITE_API int sqlite3_vtab_config(sqlite3*, int op, ...); ** support constraints. In this configuration (which is the default) if ** a call to the [xUpdate] method returns [SQLITE_CONSTRAINT], then the entire ** statement is rolled back as if [ON CONFLICT | OR ABORT] had been -** specified as part of the users SQL statement, regardless of the actual +** specified as part of the user's SQL statement, regardless of the actual ** ON CONFLICT mode specified. ** ** If X is non-zero, then the virtual table implementation guarantees ** that if [xUpdate] returns [SQLITE_CONSTRAINT], it will do so before ** any modifications to internal or persistent data structures have been made. -** If the [ON CONFLICT] mode is ABORT, FAIL, IGNORE or ROLLBACK, SQLite +** If the [ON CONFLICT] mode is ABORT, FAIL, IGNORE or ROLLBACK, SQLite ** is able to roll back a statement or database transaction, and abandon -** or continue processing the current SQL statement as appropriate. +** or continue processing the current SQL statement as appropriate. ** If the ON CONFLICT mode is REPLACE and the [xUpdate] method returns ** [SQLITE_CONSTRAINT], SQLite handles this as if the ON CONFLICT mode ** had been ABORT. ** ** Virtual table implementations that are required to handle OR REPLACE -** must do so within the [xUpdate] method. If a call to the -** [sqlite3_vtab_on_conflict()] function indicates that the current ON -** CONFLICT policy is REPLACE, the virtual table implementation should +** must do so within the [xUpdate] method. If a call to the +** [sqlite3_vtab_on_conflict()] function indicates that the current ON +** CONFLICT policy is REPLACE, the virtual table implementation should ** silently replace the appropriate rows within the xUpdate callback and ** return SQLITE_OK. Or, if this is not possible, it may return -** SQLITE_CONSTRAINT, in which case SQLite falls back to OR ABORT +** SQLITE_CONSTRAINT, in which case SQLite falls back to OR ABORT ** constraint handling. +**
    +** +** [[SQLITE_VTAB_DIRECTONLY]]
    SQLITE_VTAB_DIRECTONLY
    +**
    Calls of the form +** [sqlite3_vtab_config](db,SQLITE_VTAB_DIRECTONLY) from within the +** the [xConnect] or [xCreate] methods of a [virtual table] implementation +** prohibits that virtual table from being used from within triggers and +** views. +**
    +** +** [[SQLITE_VTAB_INNOCUOUS]]
    SQLITE_VTAB_INNOCUOUS
    +**
    Calls of the form +** [sqlite3_vtab_config](db,SQLITE_VTAB_INNOCUOUS) from within the +** [xConnect] or [xCreate] methods of a [virtual table] implementation +** identify that virtual table as being safe to use from within triggers +** and views. Conceptually, the SQLITE_VTAB_INNOCUOUS tag means that the +** virtual table can do no serious harm even if it is controlled by a +** malicious hacker. Developers should avoid setting the SQLITE_VTAB_INNOCUOUS +** flag unless absolutely necessary. +**
    +** +** [[SQLITE_VTAB_USES_ALL_SCHEMAS]]
    SQLITE_VTAB_USES_ALL_SCHEMAS
    +**
    Calls of the form +** [sqlite3_vtab_config](db,SQLITE_VTAB_USES_ALL_SCHEMA) from within the +** the [xConnect] or [xCreate] methods of a [virtual table] implementation +** instruct the query planner to begin at least a read transaction on +** all schemas ("main", "temp", and any ATTACH-ed databases) whenever the +** virtual table is used. +**
    **
    */ #define SQLITE_VTAB_CONSTRAINT_SUPPORT 1 +#define SQLITE_VTAB_INNOCUOUS 2 +#define SQLITE_VTAB_DIRECTONLY 3 +#define SQLITE_VTAB_USES_ALL_SCHEMAS 4 /* ** CAPI3REF: Determine The Virtual Table Conflict Policy @@ -9810,10 +10614,11 @@ SQLITE_API int sqlite3_vtab_on_conflict(sqlite3 *); ** CAPI3REF: Determine If Virtual Table Column Access Is For UPDATE ** ** If the sqlite3_vtab_nochange(X) routine is called within the [xColumn] -** method of a [virtual table], then it returns true if and only if the +** method of a [virtual table], then it might return true if the ** column is being fetched as part of an UPDATE operation during which the -** column value will not change. Applications might use this to substitute -** a return value that is less expensive to compute and that the corresponding +** column value will not change. The virtual table implementation can use +** this hint as permission to substitute a return value that is less +** expensive to compute and that the corresponding ** [xUpdate] method understands as a "no-change" value. ** ** If the [xColumn] method calls sqlite3_vtab_nochange() and finds that @@ -9822,31 +10627,315 @@ SQLITE_API int sqlite3_vtab_on_conflict(sqlite3 *); ** any of the [sqlite3_result_int|sqlite3_result_xxxxx() interfaces]. ** In that case, [sqlite3_value_nochange(X)] will return true for the ** same column in the [xUpdate] method. +** +** The sqlite3_vtab_nochange() routine is an optimization. Virtual table +** implementations should continue to give a correct answer even if the +** sqlite3_vtab_nochange() interface were to always return false. In the +** current implementation, the sqlite3_vtab_nochange() interface does always +** returns false for the enhanced [UPDATE FROM] statement. */ SQLITE_API int sqlite3_vtab_nochange(sqlite3_context*); /* ** CAPI3REF: Determine The Collation For a Virtual Table Constraint +** METHOD: sqlite3_index_info ** ** This function may only be called from within a call to the [xBestIndex] -** method of a [virtual table]. +** method of a [virtual table]. This function returns a pointer to a string +** that is the name of the appropriate collation sequence to use for text +** comparisons on the constraint identified by its arguments. +** +** The first argument must be the pointer to the [sqlite3_index_info] object +** that is the first parameter to the xBestIndex() method. The second argument +** must be an index into the aConstraint[] array belonging to the +** sqlite3_index_info structure passed to xBestIndex. +** +** Important: +** The first parameter must be the same pointer that is passed into the +** xBestMethod() method. The first parameter may not be a pointer to a +** different [sqlite3_index_info] object, even an exact copy. +** +** The return value is computed as follows: +** +**
      +**
    1. If the constraint comes from a WHERE clause expression that contains +** a [COLLATE operator], then the name of the collation specified by +** that COLLATE operator is returned. +**

    2. If there is no COLLATE operator, but the column that is the subject +** of the constraint specifies an alternative collating sequence via +** a [COLLATE clause] on the column definition within the CREATE TABLE +** statement that was passed into [sqlite3_declare_vtab()], then the +** name of that alternative collating sequence is returned. +**

    3. Otherwise, "BINARY" is returned. +**

    +*/ +SQLITE_API const char *sqlite3_vtab_collation(sqlite3_index_info*,int); + +/* +** CAPI3REF: Determine if a virtual table query is DISTINCT +** METHOD: sqlite3_index_info +** +** This API may only be used from within an [xBestIndex|xBestIndex method] +** of a [virtual table] implementation. The result of calling this +** interface from outside of xBestIndex() is undefined and probably harmful. +** +** ^The sqlite3_vtab_distinct() interface returns an integer between 0 and +** 3. The integer returned by sqlite3_vtab_distinct() +** gives the virtual table additional information about how the query +** planner wants the output to be ordered. As long as the virtual table +** can meet the ordering requirements of the query planner, it may set +** the "orderByConsumed" flag. +** +**
    1. +** ^If the sqlite3_vtab_distinct() interface returns 0, that means +** that the query planner needs the virtual table to return all rows in the +** sort order defined by the "nOrderBy" and "aOrderBy" fields of the +** [sqlite3_index_info] object. This is the default expectation. If the +** virtual table outputs all rows in sorted order, then it is always safe for +** the xBestIndex method to set the "orderByConsumed" flag, regardless of +** the return value from sqlite3_vtab_distinct(). +**

    2. +** ^(If the sqlite3_vtab_distinct() interface returns 1, that means +** that the query planner does not need the rows to be returned in sorted order +** as long as all rows with the same values in all columns identified by the +** "aOrderBy" field are adjacent.)^ This mode is used when the query planner +** is doing a GROUP BY. +**

    3. +** ^(If the sqlite3_vtab_distinct() interface returns 2, that means +** that the query planner does not need the rows returned in any particular +** order, as long as rows with the same values in all columns identified +** by "aOrderBy" are adjacent.)^ ^(Furthermore, when two or more rows +** contain the same values for all columns identified by "colUsed", all but +** one such row may optionally be omitted from the result.)^ +** The virtual table is not required to omit rows that are duplicates +** over the "colUsed" columns, but if the virtual table can do that without +** too much extra effort, it could potentially help the query to run faster. +** This mode is used for a DISTINCT query. +**

    4. +** ^(If the sqlite3_vtab_distinct() interface returns 3, that means the +** virtual table must return rows in the order defined by "aOrderBy" as +** if the sqlite3_vtab_distinct() interface had returned 0. However if +** two or more rows in the result have the same values for all columns +** identified by "colUsed", then all but one such row may optionally be +** omitted.)^ Like when the return value is 2, the virtual table +** is not required to omit rows that are duplicates over the "colUsed" +** columns, but if the virtual table can do that without +** too much extra effort, it could potentially help the query to run faster. +** This mode is used for queries +** that have both DISTINCT and ORDER BY clauses. +**

    +** +**

    The following table summarizes the conditions under which the +** virtual table is allowed to set the "orderByConsumed" flag based on +** the value returned by sqlite3_vtab_distinct(). This table is a +** restatement of the previous four paragraphs: +** +** +** +**
    sqlite3_vtab_distinct() return value +** Rows are returned in aOrderBy order +** Rows with the same value in all aOrderBy columns are +** adjacent +** Duplicates over all colUsed columns may be omitted +**
    0yesyesno +**
    1noyesno +**
    2noyesyes +**
    3yesyesyes +**
    +** +** ^For the purposes of comparing virtual table output values to see if the +** values are the same value for sorting purposes, two NULL values are +** considered to be the same. In other words, the comparison operator is "IS" +** (or "IS NOT DISTINCT FROM") and not "==". +** +** If a virtual table implementation is unable to meet the requirements +** specified above, then it must not set the "orderByConsumed" flag in the +** [sqlite3_index_info] object or an incorrect answer may result. +** +** ^A virtual table implementation is always free to return rows in any order +** it wants, as long as the "orderByConsumed" flag is not set. ^When the +** "orderByConsumed" flag is unset, the query planner will add extra +** [bytecode] to ensure that the final results returned by the SQL query are +** ordered correctly. The use of the "orderByConsumed" flag and the +** sqlite3_vtab_distinct() interface is merely an optimization. ^Careful +** use of the sqlite3_vtab_distinct() interface and the "orderByConsumed" +** flag might help queries against a virtual table to run faster. Being +** overly aggressive and setting the "orderByConsumed" flag when it is not +** valid to do so, on the other hand, might cause SQLite to return incorrect +** results. +*/ +SQLITE_API int sqlite3_vtab_distinct(sqlite3_index_info*); + +/* +** CAPI3REF: Identify and handle IN constraints in xBestIndex +** +** This interface may only be used from within an +** [xBestIndex|xBestIndex() method] of a [virtual table] implementation. +** The result of invoking this interface from any other context is +** undefined and probably harmful. +** +** ^(A constraint on a virtual table of the form +** "[IN operator|column IN (...)]" is +** communicated to the xBestIndex method as a +** [SQLITE_INDEX_CONSTRAINT_EQ] constraint.)^ If xBestIndex wants to use +** this constraint, it must set the corresponding +** aConstraintUsage[].argvIndex to a positive integer. ^(Then, under +** the usual mode of handling IN operators, SQLite generates [bytecode] +** that invokes the [xFilter|xFilter() method] once for each value +** on the right-hand side of the IN operator.)^ Thus the virtual table +** only sees a single value from the right-hand side of the IN operator +** at a time. +** +** In some cases, however, it would be advantageous for the virtual +** table to see all values on the right-hand of the IN operator all at +** once. The sqlite3_vtab_in() interfaces facilitates this in two ways: +** +**

      +**
    1. +** ^A call to sqlite3_vtab_in(P,N,-1) will return true (non-zero) +** if and only if the [sqlite3_index_info|P->aConstraint][N] constraint +** is an [IN operator] that can be processed all at once. ^In other words, +** sqlite3_vtab_in() with -1 in the third argument is a mechanism +** by which the virtual table can ask SQLite if all-at-once processing +** of the IN operator is even possible. +** +**

    2. +** ^A call to sqlite3_vtab_in(P,N,F) with F==1 or F==0 indicates +** to SQLite that the virtual table does or does not want to process +** the IN operator all-at-once, respectively. ^Thus when the third +** parameter (F) is non-negative, this interface is the mechanism by +** which the virtual table tells SQLite how it wants to process the +** IN operator. +**

    +** +** ^The sqlite3_vtab_in(P,N,F) interface can be invoked multiple times +** within the same xBestIndex method call. ^For any given P,N pair, +** the return value from sqlite3_vtab_in(P,N,F) will always be the same +** within the same xBestIndex call. ^If the interface returns true +** (non-zero), that means that the constraint is an IN operator +** that can be processed all-at-once. ^If the constraint is not an IN +** operator or cannot be processed all-at-once, then the interface returns +** false. +** +** ^(All-at-once processing of the IN operator is selected if both of the +** following conditions are met: +** +**
      +**
    1. The P->aConstraintUsage[N].argvIndex value is set to a positive +** integer. This is how the virtual table tells SQLite that it wants to +** use the N-th constraint. +** +**

    2. The last call to sqlite3_vtab_in(P,N,F) for which F was +** non-negative had F>=1. +**

    )^ ** -** The first argument must be the sqlite3_index_info object that is the -** first parameter to the xBestIndex() method. The second argument must be -** an index into the aConstraint[] array belonging to the sqlite3_index_info -** structure passed to xBestIndex. This function returns a pointer to a buffer -** containing the name of the collation sequence for the corresponding -** constraint. +** ^If either or both of the conditions above are false, then SQLite uses +** the traditional one-at-a-time processing strategy for the IN constraint. +** ^If both conditions are true, then the argvIndex-th parameter to the +** xFilter method will be an [sqlite3_value] that appears to be NULL, +** but which can be passed to [sqlite3_vtab_in_first()] and +** [sqlite3_vtab_in_next()] to find all values on the right-hand side +** of the IN constraint. */ -SQLITE_API SQLITE_EXPERIMENTAL const char *sqlite3_vtab_collation(sqlite3_index_info*,int); +SQLITE_API int sqlite3_vtab_in(sqlite3_index_info*, int iCons, int bHandle); + +/* +** CAPI3REF: Find all elements on the right-hand side of an IN constraint. +** +** These interfaces are only useful from within the +** [xFilter|xFilter() method] of a [virtual table] implementation. +** The result of invoking these interfaces from any other context +** is undefined and probably harmful. +** +** The X parameter in a call to sqlite3_vtab_in_first(X,P) or +** sqlite3_vtab_in_next(X,P) should be one of the parameters to the +** xFilter method which invokes these routines, and specifically +** a parameter that was previously selected for all-at-once IN constraint +** processing using the [sqlite3_vtab_in()] interface in the +** [xBestIndex|xBestIndex method]. ^(If the X parameter is not +** an xFilter argument that was selected for all-at-once IN constraint +** processing, then these routines return [SQLITE_ERROR].)^ +** +** ^(Use these routines to access all values on the right-hand side +** of the IN constraint using code like the following: +** +**
    +**    for(rc=sqlite3_vtab_in_first(pList, &pVal);
    +**        rc==SQLITE_OK && pVal;
    +**        rc=sqlite3_vtab_in_next(pList, &pVal)
    +**    ){
    +**      // do something with pVal
    +**    }
    +**    if( rc!=SQLITE_DONE ){
    +**      // an error has occurred
    +**    }
    +** 
    )^ +** +** ^On success, the sqlite3_vtab_in_first(X,P) and sqlite3_vtab_in_next(X,P) +** routines return SQLITE_OK and set *P to point to the first or next value +** on the RHS of the IN constraint. ^If there are no more values on the +** right hand side of the IN constraint, then *P is set to NULL and these +** routines return [SQLITE_DONE]. ^The return value might be +** some other value, such as SQLITE_NOMEM, in the event of a malfunction. +** +** The *ppOut values returned by these routines are only valid until the +** next call to either of these routines or until the end of the xFilter +** method from which these routines were called. If the virtual table +** implementation needs to retain the *ppOut values for longer, it must make +** copies. The *ppOut values are [protected sqlite3_value|protected]. +*/ +SQLITE_API int sqlite3_vtab_in_first(sqlite3_value *pVal, sqlite3_value **ppOut); +SQLITE_API int sqlite3_vtab_in_next(sqlite3_value *pVal, sqlite3_value **ppOut); + +/* +** CAPI3REF: Constraint values in xBestIndex() +** METHOD: sqlite3_index_info +** +** This API may only be used from within the [xBestIndex|xBestIndex method] +** of a [virtual table] implementation. The result of calling this interface +** from outside of an xBestIndex method are undefined and probably harmful. +** +** ^When the sqlite3_vtab_rhs_value(P,J,V) interface is invoked from within +** the [xBestIndex] method of a [virtual table] implementation, with P being +** a copy of the [sqlite3_index_info] object pointer passed into xBestIndex and +** J being a 0-based index into P->aConstraint[], then this routine +** attempts to set *V to the value of the right-hand operand of +** that constraint if the right-hand operand is known. ^If the +** right-hand operand is not known, then *V is set to a NULL pointer. +** ^The sqlite3_vtab_rhs_value(P,J,V) interface returns SQLITE_OK if +** and only if *V is set to a value. ^The sqlite3_vtab_rhs_value(P,J,V) +** inteface returns SQLITE_NOTFOUND if the right-hand side of the J-th +** constraint is not available. ^The sqlite3_vtab_rhs_value() interface +** can return a result code other than SQLITE_OK or SQLITE_NOTFOUND if +** something goes wrong. +** +** The sqlite3_vtab_rhs_value() interface is usually only successful if +** the right-hand operand of a constraint is a literal value in the original +** SQL statement. If the right-hand operand is an expression or a reference +** to some other column or a [host parameter], then sqlite3_vtab_rhs_value() +** will probably return [SQLITE_NOTFOUND]. +** +** ^(Some constraints, such as [SQLITE_INDEX_CONSTRAINT_ISNULL] and +** [SQLITE_INDEX_CONSTRAINT_ISNOTNULL], have no right-hand operand. For such +** constraints, sqlite3_vtab_rhs_value() always returns SQLITE_NOTFOUND.)^ +** +** ^The [sqlite3_value] object returned in *V is a protected sqlite3_value +** and remains valid for the duration of the xBestIndex method call. +** ^When xBestIndex returns, the sqlite3_value object returned by +** sqlite3_vtab_rhs_value() is automatically deallocated. +** +** The "_rhs_" in the name of this routine is an abbreviation for +** "Right-Hand Side". +*/ +SQLITE_API int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value **ppVal); /* ** CAPI3REF: Conflict resolution modes ** KEYWORDS: {conflict resolution mode} ** ** These constants are returned by [sqlite3_vtab_on_conflict()] to -** inform a [virtual table] implementation what the [ON CONFLICT] mode -** is for the SQL statement being evaluated. +** inform a [virtual table] implementation of the [ON CONFLICT] mode +** for the SQL statement being evaluated. ** ** Note that the [SQLITE_IGNORE] constant is also used as a potential ** return value from the [sqlite3_set_authorizer()] callback and that @@ -9870,39 +10959,55 @@ SQLITE_API SQLITE_EXPERIMENTAL const char *sqlite3_vtab_collation(sqlite3_index_ ** managed by the prepared statement S and will be automatically freed when ** S is finalized. ** +** Not all values are available for all query elements. When a value is +** not available, the output variable is set to -1 if the value is numeric, +** or to NULL if it is a string (SQLITE_SCANSTAT_NAME). +** **
    ** [[SQLITE_SCANSTAT_NLOOP]]
    SQLITE_SCANSTAT_NLOOP
    -**
    ^The [sqlite3_int64] variable pointed to by the T parameter will be +**
    ^The [sqlite3_int64] variable pointed to by the V parameter will be ** set to the total number of times that the X-th loop has run.
    ** ** [[SQLITE_SCANSTAT_NVISIT]]
    SQLITE_SCANSTAT_NVISIT
    -**
    ^The [sqlite3_int64] variable pointed to by the T parameter will be set +**
    ^The [sqlite3_int64] variable pointed to by the V parameter will be set ** to the total number of rows examined by all iterations of the X-th loop.
    ** ** [[SQLITE_SCANSTAT_EST]]
    SQLITE_SCANSTAT_EST
    -**
    ^The "double" variable pointed to by the T parameter will be set to the +**
    ^The "double" variable pointed to by the V parameter will be set to the ** query planner's estimate for the average number of rows output from each -** iteration of the X-th loop. If the query planner's estimates was accurate, +** iteration of the X-th loop. If the query planner's estimate was accurate, ** then this value will approximate the quotient NVISIT/NLOOP and the ** product of this value for all prior loops with the same SELECTID will -** be the NLOOP value for the current loop. +** be the NLOOP value for the current loop.
    ** ** [[SQLITE_SCANSTAT_NAME]]
    SQLITE_SCANSTAT_NAME
    -**
    ^The "const char *" variable pointed to by the T parameter will be set +**
    ^The "const char *" variable pointed to by the V parameter will be set ** to a zero-terminated UTF-8 string containing the name of the index or table -** used for the X-th loop. +** used for the X-th loop.
    ** ** [[SQLITE_SCANSTAT_EXPLAIN]]
    SQLITE_SCANSTAT_EXPLAIN
    -**
    ^The "const char *" variable pointed to by the T parameter will be set +**
    ^The "const char *" variable pointed to by the V parameter will be set ** to a zero-terminated UTF-8 string containing the [EXPLAIN QUERY PLAN] -** description for the X-th loop. -** -** [[SQLITE_SCANSTAT_SELECTID]]
    SQLITE_SCANSTAT_SELECT
    -**
    ^The "int" variable pointed to by the T parameter will be set to the -** "select-id" for the X-th loop. The select-id identifies which query or -** subquery the loop is part of. The main query has a select-id of zero. -** The select-id is the same value as is output in the first column -** of an [EXPLAIN QUERY PLAN] query. +** description for the X-th loop.
    +** +** [[SQLITE_SCANSTAT_SELECTID]]
    SQLITE_SCANSTAT_SELECTID
    +**
    ^The "int" variable pointed to by the V parameter will be set to the +** id for the X-th query plan element. The id value is unique within the +** statement. The select-id is the same value as is output in the first +** column of an [EXPLAIN QUERY PLAN] query.
    +** +** [[SQLITE_SCANSTAT_PARENTID]]
    SQLITE_SCANSTAT_PARENTID
    +**
    The "int" variable pointed to by the V parameter will be set to the +** id of the parent of the current query element, if applicable, or +** to zero if the query element has no parent. This is the same value as +** returned in the second column of an [EXPLAIN QUERY PLAN] query.
    +** +** [[SQLITE_SCANSTAT_NCYCLE]]
    SQLITE_SCANSTAT_NCYCLE
    +**
    The sqlite3_int64 output value is set to the number of cycles, +** according to the processor time-stamp counter, that elapsed while the +** query element was being processed. This value is not available for +** all query elements - if it is unavailable the output variable is +** set to -1.
    **
    */ #define SQLITE_SCANSTAT_NLOOP 0 @@ -9911,12 +11016,14 @@ SQLITE_API SQLITE_EXPERIMENTAL const char *sqlite3_vtab_collation(sqlite3_index_ #define SQLITE_SCANSTAT_NAME 3 #define SQLITE_SCANSTAT_EXPLAIN 4 #define SQLITE_SCANSTAT_SELECTID 5 +#define SQLITE_SCANSTAT_PARENTID 6 +#define SQLITE_SCANSTAT_NCYCLE 7 /* ** CAPI3REF: Prepared Statement Scan Status ** METHOD: sqlite3_stmt ** -** This interface returns information about the predicted and measured +** These interfaces return information about the predicted and measured ** performance for pStmt. Advanced applications can use this ** interface to compare the predicted and the measured performance and ** issue warnings and/or rerun [ANALYZE] if discrepancies are found. @@ -9927,28 +11034,48 @@ SQLITE_API SQLITE_EXPERIMENTAL const char *sqlite3_vtab_collation(sqlite3_index_ ** ** The "iScanStatusOp" parameter determines which status information to return. ** The "iScanStatusOp" must be one of the [scanstatus options] or the behavior -** of this interface is undefined. -** ^The requested measurement is written into a variable pointed to by -** the "pOut" parameter. -** Parameter "idx" identifies the specific loop to retrieve statistics for. -** Loops are numbered starting from zero. ^If idx is out of range - less than -** zero or greater than or equal to the total number of loops used to implement -** the statement - a non-zero value is returned and the variable that pOut -** points to is unchanged. -** -** ^Statistics might not be available for all loops in all statements. ^In cases -** where there exist loops with no available statistics, this function behaves -** as if the loop did not exist - it returns non-zero and leave the variable -** that pOut points to unchanged. -** -** See also: [sqlite3_stmt_scanstatus_reset()] +** of this interface is undefined. ^The requested measurement is written into +** a variable pointed to by the "pOut" parameter. +** +** The "flags" parameter must be passed a mask of flags. At present only +** one flag is defined - [SQLITE_SCANSTAT_COMPLEX]. If SQLITE_SCANSTAT_COMPLEX +** is specified, then status information is available for all elements +** of a query plan that are reported by "[EXPLAIN QUERY PLAN]" output. If +** SQLITE_SCANSTAT_COMPLEX is not specified, then only query plan elements +** that correspond to query loops (the "SCAN..." and "SEARCH..." elements of +** the EXPLAIN QUERY PLAN output) are available. Invoking API +** sqlite3_stmt_scanstatus() is equivalent to calling +** sqlite3_stmt_scanstatus_v2() with a zeroed flags parameter. +** +** Parameter "idx" identifies the specific query element to retrieve statistics +** for. Query elements are numbered starting from zero. A value of -1 may +** retrieve statistics for the entire query. ^If idx is out of range +** - less than -1 or greater than or equal to the total number of query +** elements used to implement the statement - a non-zero value is returned and +** the variable that pOut points to is unchanged. +** +** See also: [sqlite3_stmt_scanstatus_reset()] and the +** [nexec and ncycle] columns of the [bytecode virtual table]. */ SQLITE_API int sqlite3_stmt_scanstatus( sqlite3_stmt *pStmt, /* Prepared statement for which info desired */ int idx, /* Index of loop to report on */ int iScanStatusOp, /* Information desired. SQLITE_SCANSTAT_* */ void *pOut /* Result written here */ -); +); +SQLITE_API int sqlite3_stmt_scanstatus_v2( + sqlite3_stmt *pStmt, /* Prepared statement for which info desired */ + int idx, /* Index of loop to report on */ + int iScanStatusOp, /* Information desired. SQLITE_SCANSTAT_* */ + int flags, /* Mask of flags defined below */ + void *pOut /* Result written here */ +); + +/* +** CAPI3REF: Prepared Statement Scan Status +** KEYWORDS: {scan status flags} +*/ +#define SQLITE_SCANSTAT_COMPLEX 0x0001 /* ** CAPI3REF: Zero Scan-Status Counters @@ -9963,18 +11090,19 @@ SQLITE_API void sqlite3_stmt_scanstatus_reset(sqlite3_stmt*); /* ** CAPI3REF: Flush caches to disk mid-transaction +** METHOD: sqlite3 ** ** ^If a write-transaction is open on [database connection] D when the -** [sqlite3_db_cacheflush(D)] interface invoked, any dirty -** pages in the pager-cache that are not currently in use are written out +** [sqlite3_db_cacheflush(D)] interface is invoked, any dirty +** pages in the pager-cache that are not currently in use are written out ** to disk. A dirty page may be in use if a database cursor created by an ** active SQL statement is reading from it, or if it is page 1 of a database ** file (page 1 is always "in use"). ^The [sqlite3_db_cacheflush(D)] ** interface flushes caches for all schemas - "main", "temp", and ** any [attached] databases. ** -** ^If this function needs to obtain extra database locks before dirty pages -** can be flushed to disk, it does so. ^If those locks cannot be obtained +** ^If this function needs to obtain extra database locks before dirty pages +** can be flushed to disk, it does so. ^If those locks cannot be obtained ** immediately and there is a busy-handler callback configured, it is invoked ** in the usual manner. ^If the required lock still cannot be obtained, then ** the database is skipped and an attempt made to flush any dirty pages @@ -9995,6 +11123,7 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3*); /* ** CAPI3REF: The pre-update hook. +** METHOD: sqlite3 ** ** ^These interfaces are only available if SQLite is compiled using the ** [SQLITE_ENABLE_PREUPDATE_HOOK] compile-time option. @@ -10012,7 +11141,7 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3*); ** ** ^The preupdate hook only fires for changes to real database tables; the ** preupdate hook is not invoked for changes to [virtual tables] or to -** system tables like sqlite_master or sqlite_stat1. +** system tables like sqlite_sequence or sqlite_stat1. ** ** ^The second parameter to the preupdate callback is a pointer to ** the [database connection] that registered the preupdate hook. @@ -10021,21 +11150,25 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3*); ** kind of update operation that is about to occur. ** ^(The fourth parameter to the preupdate callback is the name of the ** database within the database connection that is being modified. This -** will be "main" for the main database or "temp" for TEMP tables or +** will be "main" for the main database or "temp" for TEMP tables or ** the name given after the AS keyword in the [ATTACH] statement for attached ** databases.)^ ** ^The fifth parameter to the preupdate callback is the name of the ** table that is being modified. ** ** For an UPDATE or DELETE operation on a [rowid table], the sixth -** parameter passed to the preupdate callback is the initial [rowid] of the +** parameter passed to the preupdate callback is the initial [rowid] of the ** row being modified or deleted. For an INSERT operation on a rowid table, -** or any operation on a WITHOUT ROWID table, the value of the sixth +** or any operation on a WITHOUT ROWID table, the value of the sixth ** parameter is undefined. For an INSERT or UPDATE on a rowid table the ** seventh parameter is the final rowid value of the row being inserted ** or updated. The value of the seventh parameter passed to the callback ** function is not defined for operations on WITHOUT ROWID tables, or for -** INSERT operations on rowid tables. +** DELETE operations on rowid tables. +** +** ^The sqlite3_preupdate_hook(D,C,P) function returns the P argument from +** the previous call on the same [database connection] D, or NULL for +** the first call on D. ** ** The [sqlite3_preupdate_old()], [sqlite3_preupdate_new()], ** [sqlite3_preupdate_count()], and [sqlite3_preupdate_depth()] interfaces @@ -10069,10 +11202,19 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3*); ** ** ^The [sqlite3_preupdate_depth(D)] interface returns 0 if the preupdate ** callback was invoked as a result of a direct insert, update, or delete -** operation; or 1 for inserts, updates, or deletes invoked by top-level +** operation; or 1 for inserts, updates, or deletes invoked by top-level ** triggers; or 2 for changes resulting from triggers called by top-level ** triggers; and so forth. ** +** When the [sqlite3_blob_write()] API is used to update a blob column, +** the pre-update hook is invoked with SQLITE_DELETE, because +** the new values are not yet available. In this case, when a +** callback made with op==SQLITE_DELETE is actually a write using the +** sqlite3_blob_write() API, the [sqlite3_preupdate_blobwrite()] returns +** the index of the column being written. In other cases, where the +** pre-update hook is being invoked for some other reason, including a +** regular DELETE, sqlite3_preupdate_blobwrite() returns -1. +** ** See also: [sqlite3_update_hook()] */ #if defined(SQLITE_ENABLE_PREUPDATE_HOOK) @@ -10093,17 +11235,19 @@ SQLITE_API int sqlite3_preupdate_old(sqlite3 *, int, sqlite3_value **); SQLITE_API int sqlite3_preupdate_count(sqlite3 *); SQLITE_API int sqlite3_preupdate_depth(sqlite3 *); SQLITE_API int sqlite3_preupdate_new(sqlite3 *, int, sqlite3_value **); +SQLITE_API int sqlite3_preupdate_blobwrite(sqlite3 *); #endif /* ** CAPI3REF: Low-level system error code +** METHOD: sqlite3 ** ** ^Attempt to return the underlying operating system error code or error ** number that caused the most recent I/O error or failure to open a file. ** The return value is OS-dependent. For example, on unix systems, after ** [sqlite3_open_v2()] returns [SQLITE_CANTOPEN], this interface could be ** called to get back the underlying "errno" that caused the problem, such -** as ENOSPC, EAUTH, EISDIR, and so forth. +** as ENOSPC, EAUTH, EISDIR, and so forth. */ SQLITE_API int sqlite3_system_errno(sqlite3*); @@ -10141,12 +11285,20 @@ typedef struct sqlite3_snapshot { ** [sqlite3_snapshot_get(D,S,P)] interface writes a pointer to the newly ** created [sqlite3_snapshot] object into *P and returns SQLITE_OK. ** If there is not already a read-transaction open on schema S when -** this function is called, one is opened automatically. +** this function is called, one is opened automatically. +** +** If a read-transaction is opened by this function, then it is guaranteed +** that the returned snapshot object may not be invalidated by a database +** writer or checkpointer until after the read-transaction is closed. This +** is not guaranteed if a read-transaction is already open when this +** function is called. In that case, any subsequent write or checkpoint +** operation on the database may invalidate the returned snapshot handle, +** even while the read-transaction remains open. ** ** The following must be true for this function to succeed. If any of ** the following statements are false when sqlite3_snapshot_get() is ** called, SQLITE_ERROR is returned. The final value of *P is undefined -** in this case. +** in this case. ** **
      **
    • The database handle must not be in [autocommit mode]. @@ -10158,13 +11310,13 @@ typedef struct sqlite3_snapshot { ** **
    • One or more transactions must have been written to the current wal ** file since it was created on disk (by any connection). This means -** that a snapshot cannot be taken on a wal mode database with no wal +** that a snapshot cannot be taken on a wal mode database with no wal ** file immediately after it is first opened. At least one transaction ** must be written to it first. **
    ** ** This function may also return SQLITE_NOMEM. If it is called with the -** database handle in autocommit mode but fails for some other reason, +** database handle in autocommit mode but fails for some other reason, ** whether or not a read transaction is opened on schema S is undefined. ** ** The [sqlite3_snapshot] object returned from a successful call to @@ -10174,7 +11326,7 @@ typedef struct sqlite3_snapshot { ** The [sqlite3_snapshot_get()] interface is only available when the ** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used. */ -SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_get( +SQLITE_API int sqlite3_snapshot_get( sqlite3 *db, const char *zSchema, sqlite3_snapshot **ppSnapshot @@ -10184,38 +11336,38 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_get( ** CAPI3REF: Start a read transaction on an historical snapshot ** METHOD: sqlite3_snapshot ** -** ^The [sqlite3_snapshot_open(D,S,P)] interface either starts a new read -** transaction or upgrades an existing one for schema S of -** [database connection] D such that the read transaction refers to -** historical [snapshot] P, rather than the most recent change to the -** database. ^The [sqlite3_snapshot_open()] interface returns SQLITE_OK +** ^The [sqlite3_snapshot_open(D,S,P)] interface either starts a new read +** transaction or upgrades an existing one for schema S of +** [database connection] D such that the read transaction refers to +** historical [snapshot] P, rather than the most recent change to the +** database. ^The [sqlite3_snapshot_open()] interface returns SQLITE_OK ** on success or an appropriate [error code] if it fails. ** -** ^In order to succeed, the database connection must not be in +** ^In order to succeed, the database connection must not be in ** [autocommit mode] when [sqlite3_snapshot_open(D,S,P)] is called. If there ** is already a read transaction open on schema S, then the database handle ** must have no active statements (SELECT statements that have been passed -** to sqlite3_step() but not sqlite3_reset() or sqlite3_finalize()). +** to sqlite3_step() but not sqlite3_reset() or sqlite3_finalize()). ** SQLITE_ERROR is returned if either of these conditions is violated, or ** if schema S does not exist, or if the snapshot object is invalid. ** ** ^A call to sqlite3_snapshot_open() will fail to open if the specified -** snapshot has been overwritten by a [checkpoint]. In this case +** snapshot has been overwritten by a [checkpoint]. In this case ** SQLITE_ERROR_SNAPSHOT is returned. ** -** If there is already a read transaction open when this function is +** If there is already a read transaction open when this function is ** invoked, then the same read transaction remains open (on the same ** database snapshot) if SQLITE_ERROR, SQLITE_BUSY or SQLITE_ERROR_SNAPSHOT ** is returned. If another error code - for example SQLITE_PROTOCOL or an ** SQLITE_IOERR error code - is returned, then the final state of the -** read transaction is undefined. If SQLITE_OK is returned, then the +** read transaction is undefined. If SQLITE_OK is returned, then the ** read transaction is now open on database snapshot P. ** ** ^(A call to [sqlite3_snapshot_open(D,S,P)] will fail if the ** database connection D does not know that the database file for ** schema S is in [WAL mode]. A database connection might not know ** that the database file is in [WAL mode] if there has been no prior -** I/O on that database connection, or if the database entered [WAL mode] +** I/O on that database connection, or if the database entered [WAL mode] ** after the most recent I/O on the database connection.)^ ** (Hint: Run "[PRAGMA application_id]" against a newly opened ** database connection in order to make it ready to use snapshots.) @@ -10223,7 +11375,7 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_get( ** The [sqlite3_snapshot_open()] interface is only available when the ** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used. */ -SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_open( +SQLITE_API int sqlite3_snapshot_open( sqlite3 *db, const char *zSchema, sqlite3_snapshot *pSnapshot @@ -10240,24 +11392,24 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_open( ** The [sqlite3_snapshot_free()] interface is only available when the ** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used. */ -SQLITE_API SQLITE_EXPERIMENTAL void sqlite3_snapshot_free(sqlite3_snapshot*); +SQLITE_API void sqlite3_snapshot_free(sqlite3_snapshot*); /* ** CAPI3REF: Compare the ages of two snapshot handles. ** METHOD: sqlite3_snapshot ** ** The sqlite3_snapshot_cmp(P1, P2) interface is used to compare the ages -** of two valid snapshot handles. +** of two valid snapshot handles. ** -** If the two snapshot handles are not associated with the same database -** file, the result of the comparison is undefined. +** If the two snapshot handles are not associated with the same database +** file, the result of the comparison is undefined. ** ** Additionally, the result of the comparison is only valid if both of the ** snapshot handles were obtained by calling sqlite3_snapshot_get() since the ** last time the wal file was deleted. The wal file is deleted when the ** database is changed back to rollback mode or when the number of database -** clients drops to zero. If either snapshot handle was obtained before the -** wal file was last deleted, the value returned by this function +** clients drops to zero. If either snapshot handle was obtained before the +** wal file was last deleted, the value returned by this function ** is undefined. ** ** Otherwise, this API returns a negative value if P1 refers to an older @@ -10267,7 +11419,7 @@ SQLITE_API SQLITE_EXPERIMENTAL void sqlite3_snapshot_free(sqlite3_snapshot*); ** This interface is only available if SQLite is compiled with the ** [SQLITE_ENABLE_SNAPSHOT] option. */ -SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_cmp( +SQLITE_API int sqlite3_snapshot_cmp( sqlite3_snapshot *p1, sqlite3_snapshot *p2 ); @@ -10295,20 +11447,21 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_cmp( ** This interface is only available if SQLite is compiled with the ** [SQLITE_ENABLE_SNAPSHOT] option. */ -SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb); +SQLITE_API int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb); /* ** CAPI3REF: Serialize a database ** -** The sqlite3_serialize(D,S,P,F) interface returns a pointer to memory -** that is a serialization of the S database on [database connection] D. +** The sqlite3_serialize(D,S,P,F) interface returns a pointer to +** memory that is a serialization of the S database on +** [database connection] D. If S is a NULL pointer, the main database is used. ** If P is not a NULL pointer, then the size of the database in bytes ** is written into *P. ** ** For an ordinary on-disk database file, the serialization is just a ** copy of the disk file. For an in-memory database or a "TEMP" database, ** the serialization is the same sequence of bytes which would be written -** to disk if that database where backed up to disk. +** to disk if that database were backed up to disk. ** ** The usual case is that sqlite3_serialize() copies the serialization of ** the database into memory obtained from [sqlite3_malloc64()] and returns @@ -10317,21 +11470,28 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const c ** contains the SQLITE_SERIALIZE_NOCOPY bit, then no memory allocations ** are made, and the sqlite3_serialize() function will return a pointer ** to the contiguous memory representation of the database that SQLite -** is currently using for that database, or NULL if the no such contiguous +** is currently using for that database, or NULL if no such contiguous ** memory representation of the database exists. A contiguous memory ** representation of the database will usually only exist if there has ** been a prior call to [sqlite3_deserialize(D,S,...)] with the same ** values of D and S. -** The size of the database is written into *P even if the +** The size of the database is written into *P even if the ** SQLITE_SERIALIZE_NOCOPY bit is set but no contiguous copy ** of the database exists. ** +** After the call, if the SQLITE_SERIALIZE_NOCOPY bit had been set, +** the returned buffer content will remain accessible and unchanged +** until either the next write operation on the connection or when +** the connection is closed, and applications must not modify the +** buffer. If the bit had been clear, the returned buffer will not +** be accessed by SQLite after the call. +** ** A call to sqlite3_serialize(D,S,P,F) might return NULL even if the ** SQLITE_SERIALIZE_NOCOPY bit is omitted from argument F if a memory ** allocation error occurs. ** -** This interface is only available if SQLite is compiled with the -** [SQLITE_ENABLE_DESERIALIZE] option. +** This interface is omitted if SQLite is compiled with the +** [SQLITE_OMIT_DESERIALIZE] option. */ SQLITE_API unsigned char *sqlite3_serialize( sqlite3 *db, /* The database connection */ @@ -10359,14 +11519,15 @@ SQLITE_API unsigned char *sqlite3_serialize( /* ** CAPI3REF: Deserialize a database ** -** The sqlite3_deserialize(D,S,P,N,M,F) interface causes the +** The sqlite3_deserialize(D,S,P,N,M,F) interface causes the ** [database connection] D to disconnect from database S and then -** reopen S as an in-memory database based on the serialization contained -** in P. The serialized database P is N bytes in size. M is the size of -** the buffer P, which might be larger than N. If M is larger than N, and -** the SQLITE_DESERIALIZE_READONLY bit is not set in F, then SQLite is -** permitted to add content to the in-memory database as long as the total -** size does not exceed M bytes. +** reopen S as an in-memory database based on the serialization +** contained in P. If S is a NULL pointer, the main database is +** used. The serialized database P is N bytes in size. M is the size +** of the buffer P, which might be larger than N. If M is larger than +** N, and the SQLITE_DESERIALIZE_READONLY bit is not set in F, then +** SQLite is permitted to add content to the in-memory database as +** long as the total size does not exceed M bytes. ** ** If the SQLITE_DESERIALIZE_FREEONCLOSE bit is set in F, then SQLite will ** invoke sqlite3_free() on the serialization buffer when the database @@ -10374,22 +11535,36 @@ SQLITE_API unsigned char *sqlite3_serialize( ** SQLite will try to increase the buffer size using sqlite3_realloc64() ** if writes on the database cause it to grow larger than M bytes. ** +** Applications must not modify the buffer P or invalidate it before +** the database connection D is closed. +** ** The sqlite3_deserialize() interface will fail with SQLITE_BUSY if the ** database is currently in a read transaction or is involved in a backup ** operation. ** -** If sqlite3_deserialize(D,S,P,N,M,F) fails for any reason and if the +** It is not possible to deserialize into the TEMP database. If the +** S argument to sqlite3_deserialize(D,S,P,N,M,F) is "temp" then the +** function returns SQLITE_ERROR. +** +** The deserialized database should not be in [WAL mode]. If the database +** is in WAL mode, then any attempt to use the database file will result +** in an [SQLITE_CANTOPEN] error. The application can set the +** [file format version numbers] (bytes 18 and 19) of the input database P +** to 0x01 prior to invoking sqlite3_deserialize(D,S,P,N,M,F) to force the +** database file into rollback mode and work around this limitation. +** +** If sqlite3_deserialize(D,S,P,N,M,F) fails for any reason and if the ** SQLITE_DESERIALIZE_FREEONCLOSE bit is set in argument F, then ** [sqlite3_free()] is invoked on argument P prior to returning. ** -** This interface is only available if SQLite is compiled with the -** [SQLITE_ENABLE_DESERIALIZE] option. +** This interface is omitted if SQLite is compiled with the +** [SQLITE_OMIT_DESERIALIZE] option. */ SQLITE_API int sqlite3_deserialize( sqlite3 *db, /* The database connection */ const char *zSchema, /* Which DB to reopen with the deserialization */ unsigned char *pData, /* The serialized database content */ - sqlite3_int64 szDb, /* Number bytes in the deserialization */ + sqlite3_int64 szDb, /* Number of bytes in the deserialization */ sqlite3_int64 szBuf, /* Total size of buffer pData[] */ unsigned mFlags /* Zero or more SQLITE_DESERIALIZE_* flags */ ); @@ -10397,7 +11572,7 @@ SQLITE_API int sqlite3_deserialize( /* ** CAPI3REF: Flags for sqlite3_deserialize() ** -** The following are allowed values for 6th argument (the F argument) to +** The following are allowed values for the 6th argument (the F argument) to ** the [sqlite3_deserialize(D,S,P,N,M,F)] interface. ** ** The SQLITE_DESERIALIZE_FREEONCLOSE means that the database serialization @@ -10419,6 +11594,77 @@ SQLITE_API int sqlite3_deserialize( #define SQLITE_DESERIALIZE_RESIZEABLE 2 /* Resize using sqlite3_realloc64() */ #define SQLITE_DESERIALIZE_READONLY 4 /* Database is read-only */ +/* +** CAPI3REF: Bind array values to the CARRAY table-valued function +** +** The sqlite3_carray_bind_v2(S,I,P,N,F,X,D) interface binds an array value to +** parameter that is the first argument of the [carray() table-valued function]. +** The S parameter is a pointer to the [prepared statement] that uses the +** carray() functions. I is the parameter index to be bound. I must be the +** index of the parameter that is the first argument to the carray() +** table-valued function. P is a pointer to the array to be bound, and N +** is the number of elements in the array. The F argument is one of +** constants [SQLITE_CARRAY_INT32], [SQLITE_CARRAY_INT64], +** [SQLITE_CARRAY_DOUBLE], [SQLITE_CARRAY_TEXT], +** or [SQLITE_CARRAY_BLOB] to indicate the datatype of the array P. +** +** If the X argument is not a NULL pointer or one of the special +** values [SQLITE_STATIC] or [SQLITE_TRANSIENT], then SQLite will invoke +** the function X with argument D when it is finished using the data in P. +** The call to X(D) is a destructor for the array P. The destructor X(D) +** is invoked even if the call to sqlite3_carray_bind_v2() fails. If the X +** parameter is the special-case value [SQLITE_STATIC], then SQLite assumes +** that the data static and the destructor is never invoked. If the X +** parameter is the special-case value [SQLITE_TRANSIENT], then +** sqlite3_carray_bind_v2() makes its own private copy of the data prior +** to returning and never invokes the destructor X. +** +** The sqlite3_carray_bind() function works the same as sqlite3_carray_bind_v2() +** with a D parameter set to P. In other words, +** sqlite3_carray_bind(S,I,P,N,F,X) is same as +** sqlite3_carray_bind_v2(S,I,P,N,F,X,P). +*/ +SQLITE_API int sqlite3_carray_bind_v2( + sqlite3_stmt *pStmt, /* Statement to be bound */ + int i, /* Parameter index */ + void *aData, /* Pointer to array data */ + int nData, /* Number of data elements */ + int mFlags, /* CARRAY flags */ + void (*xDel)(void*), /* Destructor for aData */ + void *pDel /* Optional argument to xDel() */ +); +SQLITE_API int sqlite3_carray_bind( + sqlite3_stmt *pStmt, /* Statement to be bound */ + int i, /* Parameter index */ + void *aData, /* Pointer to array data */ + int nData, /* Number of data elements */ + int mFlags, /* CARRAY flags */ + void (*xDel)(void*) /* Destructor for aData */ +); + +/* +** CAPI3REF: Datatypes for the CARRAY table-valued function +** +** The fifth argument to the [sqlite3_carray_bind()] interface musts be +** one of the following constants, to specify the datatype of the array +** that is being bound into the [carray table-valued function]. +*/ +#define SQLITE_CARRAY_INT32 0 /* Data is 32-bit signed integers */ +#define SQLITE_CARRAY_INT64 1 /* Data is 64-bit signed integers */ +#define SQLITE_CARRAY_DOUBLE 2 /* Data is doubles */ +#define SQLITE_CARRAY_TEXT 3 /* Data is char* */ +#define SQLITE_CARRAY_BLOB 4 /* Data is struct iovec */ + +/* +** Versions of the above #defines that omit the initial SQLITE_, for +** legacy compatibility. +*/ +#define CARRAY_INT32 0 /* Data is 32-bit signed integers */ +#define CARRAY_INT64 1 /* Data is 64-bit signed integers */ +#define CARRAY_DOUBLE 2 /* Data is doubles */ +#define CARRAY_TEXT 3 /* Data is char* */ +#define CARRAY_BLOB 4 /* Data is struct iovec */ + /* ** Undo the hack that converts floating point types to integer for ** builds on processors without floating point support. @@ -10427,10 +11673,21 @@ SQLITE_API int sqlite3_deserialize( # undef double #endif +#if defined(__wasi__) +# undef SQLITE_WASI +# define SQLITE_WASI 1 +# ifndef SQLITE_OMIT_LOAD_EXTENSION +# define SQLITE_OMIT_LOAD_EXTENSION +# endif +# ifndef SQLITE_THREADSAFE +# define SQLITE_THREADSAFE 0 +# endif +#endif + #if 0 } /* End of the 'extern "C"' block */ #endif -#endif /* SQLITE3_H */ +/* #endif for SQLITE3_H will be added by mksqlite3.tcl */ /******** Begin file sqlite3rtree.h *********/ /* @@ -10493,7 +11750,7 @@ struct sqlite3_rtree_geometry { }; /* -** Register a 2nd-generation geometry callback named zScore that can be +** Register a 2nd-generation geometry callback named zScore that can be ** used as part of an R-Tree geometry query as follows: ** ** SELECT ... FROM WHERE MATCH $zQueryFunc(... params ...) @@ -10508,7 +11765,7 @@ SQLITE_API int sqlite3_rtree_query_callback( /* -** A pointer to a structure of the following type is passed as the +** A pointer to a structure of the following type is passed as the ** argument to scored geometry callback registered using ** sqlite3_rtree_query_callback(). ** @@ -10603,7 +11860,7 @@ typedef struct sqlite3_changeset_iter sqlite3_changeset_iter; ** is not possible for an application to register a pre-update hook on a ** database handle that has one or more session objects attached. Nor is ** it possible to create a session object attached to a database handle for -** which a pre-update hook is already defined. The results of attempting +** which a pre-update hook is already defined. The results of attempting ** either of these things are undefined. ** ** The session object will be used to create changesets for tables in @@ -10621,17 +11878,62 @@ SQLITE_API int sqlite3session_create( ** CAPI3REF: Delete A Session Object ** DESTRUCTOR: sqlite3_session ** -** Delete a session object previously allocated using +** Delete a session object previously allocated using ** [sqlite3session_create()]. Once a session object has been deleted, the ** results of attempting to use pSession with any other session module ** function are undefined. ** ** Session objects must be deleted before the database handle to which they -** are attached is closed. Refer to the documentation for +** are attached is closed. Refer to the documentation for ** [sqlite3session_create()] for details. */ SQLITE_API void sqlite3session_delete(sqlite3_session *pSession); +/* +** CAPI3REF: Configure a Session Object +** METHOD: sqlite3_session +** +** This method is used to configure a session object after it has been +** created. At present the only valid values for the second parameter are +** [SQLITE_SESSION_OBJCONFIG_SIZE] and [SQLITE_SESSION_OBJCONFIG_ROWID]. +** +*/ +SQLITE_API int sqlite3session_object_config(sqlite3_session*, int op, void *pArg); + +/* +** CAPI3REF: Options for sqlite3session_object_config +** +** The following values may passed as the the 2nd parameter to +** sqlite3session_object_config(). +** +**
    SQLITE_SESSION_OBJCONFIG_SIZE
    +** This option is used to set, clear or query the flag that enables +** the [sqlite3session_changeset_size()] API. Because it imposes some +** computational overhead, this API is disabled by default. Argument +** pArg must point to a value of type (int). If the value is initially +** 0, then the sqlite3session_changeset_size() API is disabled. If it +** is greater than 0, then the same API is enabled. Or, if the initial +** value is less than zero, no change is made. In all cases the (int) +** variable is set to 1 if the sqlite3session_changeset_size() API is +** enabled following the current call, or 0 otherwise. +** +** It is an error (SQLITE_MISUSE) to attempt to modify this setting after +** the first table has been attached to the session object. +** +**
    SQLITE_SESSION_OBJCONFIG_ROWID
    +** This option is used to set, clear or query the flag that enables +** collection of data for tables with no explicit PRIMARY KEY. +** +** Normally, tables with no explicit PRIMARY KEY are simply ignored +** by the sessions module. However, if this flag is set, it behaves +** as if such tables have a column "_rowid_ INTEGER PRIMARY KEY" inserted +** as their leftmost columns. +** +** It is an error (SQLITE_MISUSE) to attempt to modify this setting after +** the first table has been attached to the session object. +*/ +#define SQLITE_SESSION_OBJCONFIG_SIZE 1 +#define SQLITE_SESSION_OBJCONFIG_ROWID 2 /* ** CAPI3REF: Enable Or Disable A Session Object @@ -10645,10 +11947,10 @@ SQLITE_API void sqlite3session_delete(sqlite3_session *pSession); ** the eventual changesets. ** ** Passing zero to this function disables the session. Passing a value -** greater than zero enables it. Passing a value less than zero is a +** greater than zero enables it. Passing a value less than zero is a ** no-op, and may be used to query the current state of the session. ** -** The return value indicates the final state of the session object: 0 if +** The return value indicates the final state of the session object: 0 if ** the session is disabled, or 1 if it is enabled. */ SQLITE_API int sqlite3session_enable(sqlite3_session *pSession, int bEnable); @@ -10663,7 +11965,7 @@ SQLITE_API int sqlite3session_enable(sqlite3_session *pSession, int bEnable); **
      **
    • The session object "indirect" flag is set when the change is ** made, or -**
    • The change is made by an SQL trigger or foreign key action +**
    • The change is made by an SQL trigger or foreign key action ** instead of directly as a result of a users SQL statement. **
    ** @@ -10675,10 +11977,10 @@ SQLITE_API int sqlite3session_enable(sqlite3_session *pSession, int bEnable); ** flag. If the second argument passed to this function is zero, then the ** indirect flag is cleared. If it is greater than zero, the indirect flag ** is set. Passing a value less than zero does not modify the current value -** of the indirect flag, and may be used to query the current state of the +** of the indirect flag, and may be used to query the current state of the ** indirect flag for the specified session object. ** -** The return value indicates the final state of the indirect flag: 0 if +** The return value indicates the final state of the indirect flag: 0 if ** it is clear, or 1 if it is set. */ SQLITE_API int sqlite3session_indirect(sqlite3_session *pSession, int bIndirect); @@ -10688,20 +11990,20 @@ SQLITE_API int sqlite3session_indirect(sqlite3_session *pSession, int bIndirect) ** METHOD: sqlite3_session ** ** If argument zTab is not NULL, then it is the name of a table to attach -** to the session object passed as the first argument. All subsequent changes -** made to the table while the session object is enabled will be recorded. See +** to the session object passed as the first argument. All subsequent changes +** made to the table while the session object is enabled will be recorded. See ** documentation for [sqlite3session_changeset()] for further details. ** ** Or, if argument zTab is NULL, then changes are recorded for all tables -** in the database. If additional tables are added to the database (by -** executing "CREATE TABLE" statements) after this call is made, changes for +** in the database. If additional tables are added to the database (by +** executing "CREATE TABLE" statements) after this call is made, changes for ** the new tables are also recorded. ** ** Changes can only be recorded for tables that have a PRIMARY KEY explicitly -** defined as part of their CREATE TABLE statement. It does not matter if the +** defined as part of their CREATE TABLE statement. It does not matter if the ** PRIMARY KEY is an "INTEGER PRIMARY KEY" (rowid alias) or not. The PRIMARY ** KEY may consist of a single column, or may be a composite key. -** +** ** It is not an error if the named table does not exist in the database. Nor ** is it an error if the named table does not have a PRIMARY KEY. However, ** no changes will be recorded in either of these scenarios. @@ -10709,29 +12011,29 @@ SQLITE_API int sqlite3session_indirect(sqlite3_session *pSession, int bIndirect) ** Changes are not recorded for individual rows that have NULL values stored ** in one or more of their PRIMARY KEY columns. ** -** SQLITE_OK is returned if the call completes without error. Or, if an error +** SQLITE_OK is returned if the call completes without error. Or, if an error ** occurs, an SQLite error code (e.g. SQLITE_NOMEM) is returned. ** **

    Special sqlite_stat1 Handling

    ** -** As of SQLite version 3.22.0, the "sqlite_stat1" table is an exception to +** As of SQLite version 3.22.0, the "sqlite_stat1" table is an exception to ** some of the rules above. In SQLite, the schema of sqlite_stat1 is: **
    -**        CREATE TABLE sqlite_stat1(tbl,idx,stat)  
    +**        CREATE TABLE sqlite_stat1(tbl,idx,stat)
     **  
    ** -** Even though sqlite_stat1 does not have a PRIMARY KEY, changes are -** recorded for it as if the PRIMARY KEY is (tbl,idx). Additionally, changes +** Even though sqlite_stat1 does not have a PRIMARY KEY, changes are +** recorded for it as if the PRIMARY KEY is (tbl,idx). Additionally, changes ** are recorded for rows for which (idx IS NULL) is true. However, for such ** rows a zero-length blob (SQL value X'') is stored in the changeset or ** patchset instead of a NULL value. This allows such changesets to be ** manipulated by legacy implementations of sqlite3changeset_invert(), ** concat() and similar. ** -** The sqlite3changeset_apply() function automatically converts the +** The sqlite3changeset_apply() function automatically converts the ** zero-length blob back to a NULL value when updating the sqlite_stat1 ** table. However, if the application calls sqlite3changeset_new(), -** sqlite3changeset_old() or sqlite3changeset_conflict on a changeset +** sqlite3changeset_old() or sqlite3changeset_conflict on a changeset ** iterator directly (including on a changeset iterator passed to a ** conflict-handler callback) then the X'' value is returned. The application ** must translate X'' to NULL itself if required. @@ -10750,10 +12052,10 @@ SQLITE_API int sqlite3session_attach( ** CAPI3REF: Set a table filter on a Session Object. ** METHOD: sqlite3_session ** -** The second argument (xFilter) is the "filter callback". For changes to rows +** The second argument (xFilter) is the "filter callback". For changes to rows ** in tables that are not attached to the Session object, the filter is called -** to determine whether changes to the table's rows should be tracked or not. -** If xFilter returns 0, changes is not tracked. Note that once a table is +** to determine whether changes to the table's rows should be tracked or not. +** If xFilter returns 0, changes are not tracked. Note that once a table is ** attached, xFilter will not be called again. */ SQLITE_API void sqlite3session_table_filter( @@ -10769,9 +12071,9 @@ SQLITE_API void sqlite3session_table_filter( ** CAPI3REF: Generate A Changeset From A Session Object ** METHOD: sqlite3_session ** -** Obtain a changeset containing changes to the tables attached to the -** session object passed as the first argument. If successful, -** set *ppChangeset to point to a buffer containing the changeset +** Obtain a changeset containing changes to the tables attached to the +** session object passed as the first argument. If successful, +** set *ppChangeset to point to a buffer containing the changeset ** and *pnChangeset to the size of the changeset in bytes before returning ** SQLITE_OK. If an error occurs, set both *ppChangeset and *pnChangeset to ** zero and return an SQLite error code. @@ -10786,7 +12088,7 @@ SQLITE_API void sqlite3session_table_filter( ** modifies the values of primary key columns. If such a change is made, it ** is represented in a changeset as a DELETE followed by an INSERT. ** -** Changes are not recorded for rows that have NULL values stored in one or +** Changes are not recorded for rows that have NULL values stored in one or ** more of their PRIMARY KEY columns. If such a row is inserted or deleted, ** no corresponding change is present in the changesets returned by this ** function. If an existing row with one or more NULL values stored in @@ -10839,14 +12141,14 @@ SQLITE_API void sqlite3session_table_filter( **
      **
    • For each record generated by an insert, the database is queried ** for a row with a matching primary key. If one is found, an INSERT -** change is added to the changeset. If no such row is found, no change +** change is added to the changeset. If no such row is found, no change ** is added to the changeset. ** -**
    • For each record generated by an update or delete, the database is +**
    • For each record generated by an update or delete, the database is ** queried for a row with a matching primary key. If such a row is ** found and one or more of the non-primary key fields have been -** modified from their original values, an UPDATE change is added to -** the changeset. Or, if no such row is found in the table, a DELETE +** modified from their original values, an UPDATE change is added to +** the changeset. Or, if no such row is found in the table, a DELETE ** change is added to the changeset. If there is a row with a matching ** primary key in the database, but all fields contain their original ** values, no change is added to the changeset. @@ -10854,7 +12156,7 @@ SQLITE_API void sqlite3session_table_filter( ** ** This means, amongst other things, that if a row is inserted and then later ** deleted while a session object is active, neither the insert nor the delete -** will be present in the changeset. Or if a row is deleted and then later a +** will be present in the changeset. Or if a row is deleted and then later a ** row with the same primary key values inserted while a session object is ** active, the resulting changeset will contain an UPDATE change instead of ** a DELETE and an INSERT. @@ -10863,12 +12165,13 @@ SQLITE_API void sqlite3session_table_filter( ** it does not accumulate records when rows are inserted, updated or deleted. ** This may appear to have some counter-intuitive effects if a single row ** is written to more than once during a session. For example, if a row -** is inserted while a session object is enabled, then later deleted while +** is inserted while a session object is enabled, then later deleted while ** the same session object is disabled, no INSERT record will appear in the ** changeset, even though the delete took place while the session was disabled. -** Or, if one field of a row is updated while a session is disabled, and -** another field of the same row is updated while the session is enabled, the -** resulting changeset will contain an UPDATE change that updates both fields. +** Or, if one field of a row is updated while a session is enabled, and +** then another field of the same row is updated while the session is disabled, +** the resulting changeset will contain an UPDATE change that updates both +** fields. */ SQLITE_API int sqlite3session_changeset( sqlite3_session *pSession, /* Session object */ @@ -10876,6 +12179,22 @@ SQLITE_API int sqlite3session_changeset( void **ppChangeset /* OUT: Buffer containing changeset */ ); +/* +** CAPI3REF: Return An Upper-limit For The Size Of The Changeset +** METHOD: sqlite3_session +** +** By default, this function always returns 0. For it to return +** a useful result, the sqlite3_session object must have been configured +** to enable this API using sqlite3session_object_config() with the +** SQLITE_SESSION_OBJCONFIG_SIZE verb. +** +** When enabled, this function returns an upper limit, in bytes, for the size +** of the changeset that might be produced if sqlite3session_changeset() were +** called. The final changeset size might be equal to or smaller than the +** size in bytes returned by this function. +*/ +SQLITE_API sqlite3_int64 sqlite3session_changeset_size(sqlite3_session *pSession); + /* ** CAPI3REF: Load The Difference Between Tables Into A Session ** METHOD: sqlite3_session @@ -10887,7 +12206,7 @@ SQLITE_API int sqlite3session_changeset( ** an error). ** ** Argument zFromDb must be the name of a database ("main", "temp" etc.) -** attached to the same database handle as the session object that contains +** attached to the same database handle as the session object that contains ** a table compatible with the table attached to the session by this function. ** A table is considered compatible if it: ** @@ -10903,33 +12222,34 @@ SQLITE_API int sqlite3session_changeset( ** APIs, tables without PRIMARY KEYs are simply ignored. ** ** This function adds a set of changes to the session object that could be -** used to update the table in database zFrom (call this the "from-table") -** so that its content is the same as the table attached to the session +** used to update the table in database zFrom (call this the "from-table") +** so that its content is the same as the table attached to the session ** object (call this the "to-table"). Specifically: ** **
        -**
      • For each row (primary key) that exists in the to-table but not in +**
      • For each row (primary key) that exists in the to-table but not in ** the from-table, an INSERT record is added to the session object. ** -**
      • For each row (primary key) that exists in the to-table but not in +**
      • For each row (primary key) that exists in the to-table but not in ** the from-table, a DELETE record is added to the session object. ** -**
      • For each row (primary key) that exists in both tables, but features +**
      • For each row (primary key) that exists in both tables, but features ** different non-PK values in each, an UPDATE record is added to the -** session. +** session. **
      ** ** To clarify, if this function is called and then a changeset constructed -** using [sqlite3session_changeset()], then after applying that changeset to -** database zFrom the contents of the two compatible tables would be +** using [sqlite3session_changeset()], then after applying that changeset to +** database zFrom the contents of the two compatible tables would be ** identical. ** -** It an error if database zFrom does not exist or does not contain the -** required compatible table. +** Unless the call to this function is a no-op as described above, it is an +** error if database zFrom does not exist or does not contain the required +** compatible table. ** -** If the operation successful, SQLITE_OK is returned. Otherwise, an SQLite +** If the operation is successful, SQLITE_OK is returned. Otherwise, an SQLite ** error code. In this case, if argument pzErrMsg is not NULL, *pzErrMsg -** may be set to point to a buffer containing an English language error +** may be set to point to a buffer containing an English language error ** message. It is the responsibility of the caller to free this buffer using ** sqlite3_free(). */ @@ -10948,19 +12268,19 @@ SQLITE_API int sqlite3session_diff( ** The differences between a patchset and a changeset are that: ** **
        -**
      • DELETE records consist of the primary key fields only. The +**
      • DELETE records consist of the primary key fields only. The ** original values of other fields are omitted. -**
      • The original values of any modified fields are omitted from +**
      • The original values of any modified fields are omitted from ** UPDATE records. **
      ** -** A patchset blob may be used with up to date versions of all -** sqlite3changeset_xxx API functions except for sqlite3changeset_invert(), +** A patchset blob may be used with up to date versions of all +** sqlite3changeset_xxx API functions except for sqlite3changeset_invert(), ** which returns SQLITE_CORRUPT if it is passed a patchset. Similarly, ** attempting to use a patchset blob with old versions of the -** sqlite3changeset_xxx APIs also provokes an SQLITE_CORRUPT error. +** sqlite3changeset_xxx APIs also provokes an SQLITE_CORRUPT error. ** -** Because the non-primary key "old.*" fields are omitted, no +** Because the non-primary key "old.*" fields are omitted, no ** SQLITE_CHANGESET_DATA conflicts can be detected or reported if a patchset ** is passed to the sqlite3changeset_apply() API. Other conflict types work ** in the same way as for changesets. @@ -10979,22 +12299,30 @@ SQLITE_API int sqlite3session_patchset( /* ** CAPI3REF: Test if a changeset has recorded any changes. ** -** Return non-zero if no changes to attached tables have been recorded by -** the session object passed as the first argument. Otherwise, if one or +** Return non-zero if no changes to attached tables have been recorded by +** the session object passed as the first argument. Otherwise, if one or ** more changes have been recorded, return zero. ** ** Even if this function returns zero, it is possible that calling ** [sqlite3session_changeset()] on the session handle may still return a -** changeset that contains no changes. This can happen when a row in -** an attached table is modified and then later on the original values +** changeset that contains no changes. This can happen when a row in +** an attached table is modified and then later on the original values ** are restored. However, if this function returns non-zero, then it is -** guaranteed that a call to sqlite3session_changeset() will return a +** guaranteed that a call to sqlite3session_changeset() will return a ** changeset containing zero changes. */ SQLITE_API int sqlite3session_isempty(sqlite3_session *pSession); /* -** CAPI3REF: Create An Iterator To Traverse A Changeset +** CAPI3REF: Query for the amount of heap memory used by a session object. +** +** This API returns the total amount of heap memory in bytes currently +** used by the session object passed as the only argument. +*/ +SQLITE_API sqlite3_int64 sqlite3session_memory_used(sqlite3_session *pSession); + +/* +** CAPI3REF: Create An Iterator To Traverse A Changeset ** CONSTRUCTOR: sqlite3_changeset_iter ** ** Create an iterator used to iterate through the contents of a changeset. @@ -11002,7 +12330,7 @@ SQLITE_API int sqlite3session_isempty(sqlite3_session *pSession); ** is returned. Otherwise, if an error occurs, *pp is set to zero and an ** SQLite error code is returned. ** -** The following functions can be used to advance and query a changeset +** The following functions can be used to advance and query a changeset ** iterator created by this function: ** **
        @@ -11019,12 +12347,12 @@ SQLITE_API int sqlite3session_isempty(sqlite3_session *pSession); ** ** Assuming the changeset blob was created by one of the ** [sqlite3session_changeset()], [sqlite3changeset_concat()] or -** [sqlite3changeset_invert()] functions, all changes within the changeset -** that apply to a single table are grouped together. This means that when -** an application iterates through a changeset using an iterator created by -** this function, all changes that relate to a single table are visited -** consecutively. There is no chance that the iterator will visit a change -** the applies to table X, then one for table Y, and then later on visit +** [sqlite3changeset_invert()] functions, all changes within the changeset +** that apply to a single table are grouped together. This means that when +** an application iterates through a changeset using an iterator created by +** this function, all changes that relate to a single table are visited +** consecutively. There is no chance that the iterator will visit a change +** the applies to table X, then one for table Y, and then later on visit ** another change for table X. ** ** The behavior of sqlite3changeset_start_v2() and its streaming equivalent @@ -11052,7 +12380,7 @@ SQLITE_API int sqlite3changeset_start_v2( ** The following flags may passed via the 4th parameter to ** [sqlite3changeset_start_v2] and [sqlite3changeset_start_v2_strm]: ** -**
        SQLITE_CHANGESETAPPLY_INVERT
        +**
        SQLITE_CHANGESETSTART_INVERT
        ** Invert the changeset while iterating through it. This is equivalent to ** inverting a changeset using sqlite3changeset_invert() before applying it. ** It is an error to specify this flag with a patchset. @@ -11064,7 +12392,7 @@ SQLITE_API int sqlite3changeset_start_v2( ** CAPI3REF: Advance A Changeset Iterator ** METHOD: sqlite3_changeset_iter ** -** This function may only be used with iterators created by function +** This function may only be used with iterators created by the function ** [sqlite3changeset_start()]. If it is called on an iterator passed to ** a conflict-handler callback by [sqlite3changeset_apply()], SQLITE_MISUSE ** is returned and the call has no effect. @@ -11075,12 +12403,12 @@ SQLITE_API int sqlite3changeset_start_v2( ** point to the first change in the changeset. Each subsequent call advances ** the iterator to point to the next change in the changeset (if any). If ** no error occurs and the iterator points to a valid change after a call -** to sqlite3changeset_next() has advanced it, SQLITE_ROW is returned. +** to sqlite3changeset_next() has advanced it, SQLITE_ROW is returned. ** Otherwise, if all changes in the changeset have already been visited, ** SQLITE_DONE is returned. ** -** If an error occurs, an SQLite error code is returned. Possible error -** codes include SQLITE_CORRUPT (if the changeset buffer is corrupt) or +** If an error occurs, an SQLite error code is returned. Possible error +** codes include SQLITE_CORRUPT (if the changeset buffer is corrupt) or ** SQLITE_NOMEM. */ SQLITE_API int sqlite3changeset_next(sqlite3_changeset_iter *pIter); @@ -11095,18 +12423,23 @@ SQLITE_API int sqlite3changeset_next(sqlite3_changeset_iter *pIter); ** call to [sqlite3changeset_next()] must have returned [SQLITE_ROW]. If this ** is not the case, this function returns [SQLITE_MISUSE]. ** -** If argument pzTab is not NULL, then *pzTab is set to point to a -** nul-terminated utf-8 encoded string containing the name of the table -** affected by the current change. The buffer remains valid until either -** sqlite3changeset_next() is called on the iterator or until the -** conflict-handler function returns. If pnCol is not NULL, then *pnCol is -** set to the number of columns in the table affected by the change. If -** pbIndirect is not NULL, then *pbIndirect is set to true (1) if the change +** Arguments pOp, pnCol and pzTab may not be NULL. Upon return, three +** outputs are set through these pointers: +** +** *pOp is set to one of [SQLITE_INSERT], [SQLITE_DELETE] or [SQLITE_UPDATE], +** depending on the type of change that the iterator currently points to; +** +** *pnCol is set to the number of columns in the table affected by the change; and +** +** *pzTab is set to point to a nul-terminated utf-8 encoded string containing +** the name of the table affected by the current change. The buffer remains +** valid until either sqlite3changeset_next() is called on the iterator +** or until the conflict-handler function returns. +** +** If pbIndirect is not NULL, then *pbIndirect is set to true (1) if the change ** is an indirect change, or false (0) otherwise. See the documentation for ** [sqlite3session_indirect()] for a description of direct and indirect -** changes. Finally, if pOp is not NULL, then *pOp is set to one of -** [SQLITE_INSERT], [SQLITE_DELETE] or [SQLITE_UPDATE], depending on the -** type of change that the iterator currently points to. +** changes. ** ** If no error occurs, SQLITE_OK is returned. If an error does occur, an ** SQLite error code is returned. The values of the output variables may not @@ -11159,7 +12492,7 @@ SQLITE_API int sqlite3changeset_pk( ** The pIter argument passed to this function may either be an iterator ** passed to a conflict-handler by [sqlite3changeset_apply()], or an iterator ** created by [sqlite3changeset_start()]. In the latter case, the most recent -** call to [sqlite3changeset_next()] must have returned SQLITE_ROW. +** call to [sqlite3changeset_next()] must have returned SQLITE_ROW. ** Furthermore, it may only be called if the type of change that the iterator ** currently points to is either [SQLITE_DELETE] or [SQLITE_UPDATE]. Otherwise, ** this function returns [SQLITE_MISUSE] and sets *ppValue to NULL. @@ -11169,9 +12502,9 @@ SQLITE_API int sqlite3changeset_pk( ** [SQLITE_RANGE] is returned and *ppValue is set to NULL. ** ** If successful, this function sets *ppValue to point to a protected -** sqlite3_value object containing the iVal'th value from the vector of +** sqlite3_value object containing the iVal'th value from the vector of ** original row values stored as part of the UPDATE or DELETE change and -** returns SQLITE_OK. The name of the function comes from the fact that this +** returns SQLITE_OK. The name of the function comes from the fact that this ** is similar to the "old.*" columns available to update or delete triggers. ** ** If some other error occurs (e.g. an OOM condition), an SQLite error code @@ -11190,7 +12523,7 @@ SQLITE_API int sqlite3changeset_old( ** The pIter argument passed to this function may either be an iterator ** passed to a conflict-handler by [sqlite3changeset_apply()], or an iterator ** created by [sqlite3changeset_start()]. In the latter case, the most recent -** call to [sqlite3changeset_next()] must have returned SQLITE_ROW. +** call to [sqlite3changeset_next()] must have returned SQLITE_ROW. ** Furthermore, it may only be called if the type of change that the iterator ** currently points to is either [SQLITE_UPDATE] or [SQLITE_INSERT]. Otherwise, ** this function returns [SQLITE_MISUSE] and sets *ppValue to NULL. @@ -11200,12 +12533,12 @@ SQLITE_API int sqlite3changeset_old( ** [SQLITE_RANGE] is returned and *ppValue is set to NULL. ** ** If successful, this function sets *ppValue to point to a protected -** sqlite3_value object containing the iVal'th value from the vector of +** sqlite3_value object containing the iVal'th value from the vector of ** new row values stored as part of the UPDATE or INSERT change and ** returns SQLITE_OK. If the change is an UPDATE and does not include -** a new value for the requested column, *ppValue is set to NULL and -** SQLITE_OK returned. The name of the function comes from the fact that -** this is similar to the "new.*" columns available to update or delete +** a new value for the requested column, *ppValue is set to NULL and +** SQLITE_OK returned. The name of the function comes from the fact that +** this is similar to the "new.*" columns available to update or delete ** triggers. ** ** If some other error occurs (e.g. an OOM condition), an SQLite error code @@ -11232,7 +12565,7 @@ SQLITE_API int sqlite3changeset_new( ** [SQLITE_RANGE] is returned and *ppValue is set to NULL. ** ** If successful, this function sets *ppValue to point to a protected -** sqlite3_value object containing the iVal'th value from the +** sqlite3_value object containing the iVal'th value from the ** "conflicting row" associated with the current conflict-handler callback ** and returns SQLITE_OK. ** @@ -11276,7 +12609,7 @@ SQLITE_API int sqlite3changeset_fk_conflicts( ** call has no effect. ** ** If an error was encountered within a call to an sqlite3changeset_xxx() -** function (for example an [SQLITE_CORRUPT] in [sqlite3changeset_next()] or an +** function (for example an [SQLITE_CORRUPT] in [sqlite3changeset_next()] or an ** [SQLITE_NOMEM] in [sqlite3changeset_new()]) then an error code corresponding ** to that error is returned by this function. Otherwise, SQLITE_OK is ** returned. This is to allow the following pattern (pseudo-code): @@ -11288,7 +12621,7 @@ SQLITE_API int sqlite3changeset_fk_conflicts( ** } ** rc = sqlite3changeset_finalize(); ** if( rc!=SQLITE_OK ){ -** // An error has occurred +** // An error has occurred ** } ** */ @@ -11316,7 +12649,7 @@ SQLITE_API int sqlite3changeset_finalize(sqlite3_changeset_iter *pIter); ** zeroed and an SQLite error code returned. ** ** It is the responsibility of the caller to eventually call sqlite3_free() -** on the *ppOut pointer to free the buffer allocation following a successful +** on the *ppOut pointer to free the buffer allocation following a successful ** call to this function. ** ** WARNING/TODO: This function currently assumes that the input is a valid @@ -11330,11 +12663,11 @@ SQLITE_API int sqlite3changeset_invert( /* ** CAPI3REF: Concatenate Two Changeset Objects ** -** This function is used to concatenate two changesets, A and B, into a +** This function is used to concatenate two changesets, A and B, into a ** single changeset. The result is a changeset equivalent to applying -** changeset A followed by changeset B. +** changeset A followed by changeset B. ** -** This function combines the two input changesets using an +** This function combines the two input changesets using an ** sqlite3_changegroup object. Calling it produces similar results as the ** following code fragment: ** @@ -11362,11 +12695,10 @@ SQLITE_API int sqlite3changeset_concat( void **ppOut /* OUT: Buffer containing output changeset */ ); - /* ** CAPI3REF: Changegroup Handle ** -** A changegroup is an object used to combine two or more +** A changegroup is an object used to combine two or more ** [changesets] or [patchsets] */ typedef struct sqlite3_changegroup sqlite3_changegroup; @@ -11382,7 +12714,7 @@ typedef struct sqlite3_changegroup sqlite3_changegroup; ** ** If successful, this function returns SQLITE_OK and populates (*pp) with ** a pointer to a new sqlite3_changegroup object before returning. The caller -** should eventually free the returned object using a call to +** should eventually free the returned object using a call to ** sqlite3changegroup_delete(). If an error occurs, an SQLite error code ** (i.e. SQLITE_NOMEM) is returned and *pp is set to NULL. ** @@ -11394,7 +12726,7 @@ typedef struct sqlite3_changegroup sqlite3_changegroup; **
      • Zero or more changesets (or patchsets) are added to the object ** by calling sqlite3changegroup_add(). ** -**
      • The result of combining all input changesets together is obtained +**
      • The result of combining all input changesets together is obtained ** by the application via a call to sqlite3changegroup_output(). ** **
      • The object is deleted using a call to sqlite3changegroup_delete(). @@ -11403,18 +12735,50 @@ typedef struct sqlite3_changegroup sqlite3_changegroup; ** Any number of calls to add() and output() may be made between the calls to ** new() and delete(), and in any order. ** -** As well as the regular sqlite3changegroup_add() and +** As well as the regular sqlite3changegroup_add() and ** sqlite3changegroup_output() functions, also available are the streaming ** versions sqlite3changegroup_add_strm() and sqlite3changegroup_output_strm(). */ SQLITE_API int sqlite3changegroup_new(sqlite3_changegroup **pp); +/* +** CAPI3REF: Add a Schema to a Changegroup +** METHOD: sqlite3_changegroup_schema +** +** This method may be used to optionally enforce the rule that the changesets +** added to the changegroup handle must match the schema of database zDb +** ("main", "temp", or the name of an attached database). If +** sqlite3changegroup_add() is called to add a changeset that is not compatible +** with the configured schema, SQLITE_SCHEMA is returned and the changegroup +** object is left in an undefined state. +** +** A changeset schema is considered compatible with the database schema in +** the same way as for sqlite3changeset_apply(). Specifically, for each +** table in the changeset, there exists a database table with: +** +**
          +**
        • The name identified by the changeset, and +**
        • at least as many columns as recorded in the changeset, and +**
        • the primary key columns in the same position as recorded in +** the changeset. +**
        +** +** The output of the changegroup object always has the same schema as the +** database nominated using this function. In cases where changesets passed +** to sqlite3changegroup_add() have fewer columns than the corresponding table +** in the database schema, these are filled in using the default column +** values from the database schema. This makes it possible to combined +** changesets that have different numbers of columns for a single table +** within a changegroup, provided that they are otherwise compatible. +*/ +SQLITE_API int sqlite3changegroup_schema(sqlite3_changegroup*, sqlite3*, const char *zDb); + /* ** CAPI3REF: Add A Changeset To A Changegroup ** METHOD: sqlite3_changegroup ** ** Add all changes within the changeset (or patchset) in buffer pData (size -** nData bytes) to the changegroup. +** nData bytes) to the changegroup. ** ** If the buffer contains a patchset, then all prior calls to this function ** on the same changegroup object must also have specified patchsets. Or, if @@ -11441,7 +12805,7 @@ SQLITE_API int sqlite3changegroup_new(sqlite3_changegroup **pp); ** changeset was recorded immediately after the changesets already ** added to the changegroup. **
    INSERT UPDATE -** The INSERT change remains in the changegroup. The values in the +** The INSERT change remains in the changegroup. The values in the ** INSERT change are modified as if the row was inserted by the ** existing change and then updated according to the new change. **
    INSERT DELETE @@ -11452,17 +12816,17 @@ SQLITE_API int sqlite3changegroup_new(sqlite3_changegroup **pp); ** changeset was recorded immediately after the changesets already ** added to the changegroup. **
    UPDATE UPDATE -** The existing UPDATE remains within the changegroup. It is amended -** so that the accompanying values are as if the row was updated once +** The existing UPDATE remains within the changegroup. It is amended +** so that the accompanying values are as if the row was updated once ** by the existing change and then again by the new change. **
    UPDATE DELETE ** The existing UPDATE is replaced by the new DELETE within the ** changegroup. **
    DELETE INSERT ** If one or more of the column values in the row inserted by the -** new change differ from those in the row deleted by the existing +** new change differ from those in the row deleted by the existing ** change, the existing DELETE is replaced by an UPDATE within the -** changegroup. Otherwise, if the inserted row is exactly the same +** changegroup. Otherwise, if the inserted row is exactly the same ** as the deleted row, the existing DELETE is simply discarded. **
    DELETE UPDATE ** The new change is ignored. This case does not occur if the new @@ -11477,16 +12841,45 @@ SQLITE_API int sqlite3changegroup_new(sqlite3_changegroup **pp); ** If the new changeset contains changes to a table that is already present ** in the changegroup, then the number of columns and the position of the ** primary key columns for the table must be consistent. If this is not the -** case, this function fails with SQLITE_SCHEMA. If the input changeset -** appears to be corrupt and the corruption is detected, SQLITE_CORRUPT is -** returned. Or, if an out-of-memory condition occurs during processing, this -** function returns SQLITE_NOMEM. In all cases, if an error occurs the -** final contents of the changegroup is undefined. +** case, this function fails with SQLITE_SCHEMA. Except, if the changegroup +** object has been configured with a database schema using the +** sqlite3changegroup_schema() API, then it is possible to combine changesets +** with different numbers of columns for a single table, provided that +** they are otherwise compatible. +** +** If the input changeset appears to be corrupt and the corruption is +** detected, SQLITE_CORRUPT is returned. Or, if an out-of-memory condition +** occurs during processing, this function returns SQLITE_NOMEM. ** -** If no error occurs, SQLITE_OK is returned. +** In all cases, if an error occurs the state of the final contents of the +** changegroup is undefined. If no error occurs, SQLITE_OK is returned. */ SQLITE_API int sqlite3changegroup_add(sqlite3_changegroup*, int nData, void *pData); +/* +** CAPI3REF: Add A Single Change To A Changegroup +** METHOD: sqlite3_changegroup +** +** This function adds the single change currently indicated by the iterator +** passed as the second argument to the changegroup object. The rules for +** adding the change are just as described for [sqlite3changegroup_add()]. +** +** If the change is successfully added to the changegroup, SQLITE_OK is +** returned. Otherwise, an SQLite error code is returned. +** +** The iterator must point to a valid entry when this function is called. +** If it does not, SQLITE_ERROR is returned and no change is added to the +** changegroup. Additionally, the iterator must not have been opened with +** the SQLITE_CHANGESETAPPLY_INVERT flag. In this case SQLITE_ERROR is also +** returned. +*/ +SQLITE_API int sqlite3changegroup_add_change( + sqlite3_changegroup*, + sqlite3_changeset_iter* +); + + + /* ** CAPI3REF: Obtain A Composite Changeset From A Changegroup ** METHOD: sqlite3_changegroup @@ -11507,7 +12900,7 @@ SQLITE_API int sqlite3changegroup_add(sqlite3_changegroup*, int nData, void *pDa ** ** If an error occurs, an SQLite error code is returned and the output ** variables (*pnData) and (*ppData) are set to 0. Otherwise, SQLITE_OK -** is returned and the output variables are set to the size of and a +** is returned and the output variables are set to the size of and a ** pointer to the output buffer, respectively. In this case it is the ** responsibility of the caller to eventually free the buffer using a ** call to sqlite3_free(). @@ -11529,27 +12922,45 @@ SQLITE_API void sqlite3changegroup_delete(sqlite3_changegroup*); ** ** Apply a changeset or patchset to a database. These functions attempt to ** update the "main" database attached to handle db with the changes found in -** the changeset passed via the second and third arguments. +** the changeset passed via the second and third arguments. +** +** All changes made by these functions are enclosed in a savepoint transaction. +** If any other error (aside from a constraint failure when attempting to +** write to the target database) occurs, then the savepoint transaction is +** rolled back, restoring the target database to its original state, and an +** SQLite error code returned. Additionally, starting with version 3.51.0, +** an error code and error message that may be accessed using the +** [sqlite3_errcode()] and [sqlite3_errmsg()] APIs are left in the database +** handle. ** ** The fourth argument (xFilter) passed to these functions is the "filter -** callback". If it is not NULL, then for each table affected by at least one -** change in the changeset, the filter callback is invoked with -** the table name as the second argument, and a copy of the context pointer -** passed as the sixth argument as the first. If the "filter callback" -** returns zero, then no attempt is made to apply any changes to the table. -** Otherwise, if the return value is non-zero or the xFilter argument to -** is NULL, all changes related to the table are attempted. -** -** For each table that is not excluded by the filter callback, this function -** tests that the target database contains a compatible table. A table is +** callback". This may be passed NULL, in which case all changes in the +** changeset are applied to the database. For sqlite3changeset_apply() and +** sqlite3_changeset_apply_v2(), if it is not NULL, then it is invoked once +** for each table affected by at least one change in the changeset. In this +** case the table name is passed as the second argument, and a copy of +** the context pointer passed as the sixth argument to apply() or apply_v2() +** as the first. If the "filter callback" returns zero, then no attempt is +** made to apply any changes to the table. Otherwise, if the return value is +** non-zero, all changes related to the table are attempted. +** +** For sqlite3_changeset_apply_v3(), the xFilter callback is invoked once +** per change. The second argument in this case is an sqlite3_changeset_iter +** that may be queried using the usual APIs for the details of the current +** change. If the "filter callback" returns zero in this case, then no attempt +** is made to apply the current change. If it returns non-zero, the change +** is applied. +** +** For each table that is not excluded by the filter callback, this function +** tests that the target database contains a compatible table. A table is ** considered compatible if all of the following are true: ** **
      -**
    • The table has the same name as the name recorded in the +**
    • The table has the same name as the name recorded in the ** changeset, and -**
    • The table has at least as many columns as recorded in the +**
    • The table has at least as many columns as recorded in the ** changeset, and -**
    • The table has primary key columns in the same position as +**
    • The table has primary key columns in the same position as ** recorded in the changeset. **
    ** @@ -11558,35 +12969,35 @@ SQLITE_API void sqlite3changegroup_delete(sqlite3_changegroup*); ** via the sqlite3_log() mechanism with the error code SQLITE_SCHEMA. At most ** one such warning is issued for each table in the changeset. ** -** For each change for which there is a compatible table, an attempt is made -** to modify the table contents according to the UPDATE, INSERT or DELETE -** change. If a change cannot be applied cleanly, the conflict handler -** function passed as the fifth argument to sqlite3changeset_apply() may be -** invoked. A description of exactly when the conflict handler is invoked for -** each type of change is below. +** For each change for which there is a compatible table, an attempt is made +** to modify the table contents according to each UPDATE, INSERT or DELETE +** change that is not excluded by a filter callback. If a change cannot be +** applied cleanly, the conflict handler function passed as the fifth argument +** to sqlite3changeset_apply() may be invoked. A description of exactly when +** the conflict handler is invoked for each type of change is below. ** ** Unlike the xFilter argument, xConflict may not be passed NULL. The results ** of passing anything other than a valid function pointer as the xConflict ** argument are undefined. ** ** Each time the conflict handler function is invoked, it must return one -** of [SQLITE_CHANGESET_OMIT], [SQLITE_CHANGESET_ABORT] or +** of [SQLITE_CHANGESET_OMIT], [SQLITE_CHANGESET_ABORT] or ** [SQLITE_CHANGESET_REPLACE]. SQLITE_CHANGESET_REPLACE may only be returned ** if the second argument passed to the conflict handler is either ** SQLITE_CHANGESET_DATA or SQLITE_CHANGESET_CONFLICT. If the conflict-handler ** returns an illegal value, any changes already made are rolled back and -** the call to sqlite3changeset_apply() returns SQLITE_MISUSE. Different +** the call to sqlite3changeset_apply() returns SQLITE_MISUSE. Different ** actions are taken by sqlite3changeset_apply() depending on the value ** returned by each invocation of the conflict-handler function. Refer to -** the documentation for the three +** the documentation for the three ** [SQLITE_CHANGESET_OMIT|available return values] for details. ** **
    **
    DELETE Changes
    -** For each DELETE change, the function checks if the target database -** contains a row with the same primary key value (or values) as the -** original row values stored in the changeset. If it does, and the values -** stored in all non-primary key columns also match the values stored in +** For each DELETE change, the function checks if the target database +** contains a row with the same primary key value (or values) as the +** original row values stored in the changeset. If it does, and the values +** stored in all non-primary key columns also match the values stored in ** the changeset the row is deleted from the target database. ** ** If a row with matching primary key values is found, but one or more of @@ -11615,22 +13026,22 @@ SQLITE_API void sqlite3changegroup_delete(sqlite3_changegroup*); ** database table, the trailing fields are populated with their default ** values. ** -** If the attempt to insert the row fails because the database already +** If the attempt to insert the row fails because the database already ** contains a row with the same primary key values, the conflict handler -** function is invoked with the second argument set to +** function is invoked with the second argument set to ** [SQLITE_CHANGESET_CONFLICT]. ** ** If the attempt to insert the row fails because of some other constraint -** violation (e.g. NOT NULL or UNIQUE), the conflict handler function is +** violation (e.g. NOT NULL or UNIQUE), the conflict handler function is ** invoked with the second argument set to [SQLITE_CHANGESET_CONSTRAINT]. -** This includes the case where the INSERT operation is re-attempted because -** an earlier call to the conflict handler function returned +** This includes the case where the INSERT operation is re-attempted because +** an earlier call to the conflict handler function returned ** [SQLITE_CHANGESET_REPLACE]. ** **
    UPDATE Changes
    -** For each UPDATE change, the function checks if the target database -** contains a row with the same primary key value (or values) as the -** original row values stored in the changeset. If it does, and the values +** For each UPDATE change, the function checks if the target database +** contains a row with the same primary key value (or values) as the +** original row values stored in the changeset. If it does, and the values ** stored in all modified non-primary key columns also match the values ** stored in the changeset the row is updated within the target database. ** @@ -11646,28 +13057,22 @@ SQLITE_API void sqlite3changegroup_delete(sqlite3_changegroup*); ** the conflict-handler function is invoked with [SQLITE_CHANGESET_NOTFOUND] ** passed as the second argument. ** -** If the UPDATE operation is attempted, but SQLite returns -** SQLITE_CONSTRAINT, the conflict-handler function is invoked with +** If the UPDATE operation is attempted, but SQLite returns +** SQLITE_CONSTRAINT, the conflict-handler function is invoked with ** [SQLITE_CHANGESET_CONSTRAINT] passed as the second argument. -** This includes the case where the UPDATE operation is attempted after +** This includes the case where the UPDATE operation is attempted after ** an earlier call to the conflict handler function returned -** [SQLITE_CHANGESET_REPLACE]. +** [SQLITE_CHANGESET_REPLACE]. **
    ** ** It is safe to execute SQL statements, including those that write to the ** table that the callback related to, from within the xConflict callback. -** This can be used to further customize the applications conflict +** This can be used to further customize the application's conflict ** resolution strategy. ** -** All changes made by these functions are enclosed in a savepoint transaction. -** If any other error (aside from a constraint failure when attempting to -** write to the target database) occurs, then the savepoint transaction is -** rolled back, restoring the target database to its original state, and an -** SQLite error code returned. -** ** If the output parameters (ppRebase) and (pnRebase) are non-NULL and ** the input is a changeset (not a patchset), then sqlite3changeset_apply_v2() -** may set (*ppRebase) to point to a "rebase" that may be used with the +** may set (*ppRebase) to point to a "rebase" that may be used with the ** sqlite3_rebaser APIs buffer before returning. In this case (*pnRebase) ** is set to the size of the buffer in bytes. It is the responsibility of the ** caller to eventually free any such buffer using sqlite3_free(). The buffer @@ -11714,6 +13119,23 @@ SQLITE_API int sqlite3changeset_apply_v2( void **ppRebase, int *pnRebase, /* OUT: Rebase data */ int flags /* SESSION_CHANGESETAPPLY_* flags */ ); +SQLITE_API int sqlite3changeset_apply_v3( + sqlite3 *db, /* Apply change to "main" db of this handle */ + int nChangeset, /* Size of changeset in bytes */ + void *pChangeset, /* Changeset blob */ + int(*xFilter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + sqlite3_changeset_iter *p /* Handle describing change */ + ), + int(*xConflict)( + void *pCtx, /* Copy of sixth arg to _apply() */ + int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ + sqlite3_changeset_iter *p /* Handle describing change and conflict */ + ), + void *pCtx, /* First argument passed to xConflict */ + void **ppRebase, int *pnRebase, /* OUT: Rebase data */ + int flags /* SESSION_CHANGESETAPPLY_* flags */ +); /* ** CAPI3REF: Flags for sqlite3changeset_apply_v2 @@ -11728,18 +13150,51 @@ SQLITE_API int sqlite3changeset_apply_v2( ** SAVEPOINT is committed if the changeset or patchset is successfully ** applied, or rolled back if an error occurs. Specifying this flag ** causes the sessions module to omit this savepoint. In this case, if the -** caller has an open transaction or savepoint when apply_v2() is called, +** caller has an open transaction or savepoint when apply_v2() is called, ** it may revert the partially applied changeset by rolling it back. ** **
    SQLITE_CHANGESETAPPLY_INVERT
    ** Invert the changeset before applying it. This is equivalent to inverting ** a changeset using sqlite3changeset_invert() before applying it. It is ** an error to specify this flag with a patchset. +** +**
    SQLITE_CHANGESETAPPLY_IGNORENOOP
    +** Do not invoke the conflict handler callback for any changes that +** would not actually modify the database even if they were applied. +** Specifically, this means that the conflict handler is not invoked +** for: +**
      +**
    • a delete change if the row being deleted cannot be found, +**
    • an update change if the modified fields are already set to +** their new values in the conflicting row, or +**
    • an insert change if all fields of the conflicting row match +** the row being inserted. +**
    +** +**
    SQLITE_CHANGESETAPPLY_FKNOACTION
    +** If this flag it set, then all foreign key constraints in the target +** database behave as if they were declared with "ON UPDATE NO ACTION ON +** DELETE NO ACTION", even if they are actually CASCADE, RESTRICT, SET NULL +** or SET DEFAULT. +** +**
    SQLITE_CHANGESETAPPLY_NOUPDATELOOP
    +** Sometimes, a changeset contains two or more update statements such that +** although after applying all updates the database will contain no +** constraint violations, no single update can be applied before the others. +** The simplest example of this is a pair of UPDATEs that have "swapped" +** two column values with a UNIQUE constraint. +**

    +** Usually, sqlite3changeset_apply() and similar functions work hard to try +** to find a way to apply such a changeset. However, if this flag is set, +** then all such updates are considered CONSTRAINT conflicts. */ #define SQLITE_CHANGESETAPPLY_NOSAVEPOINT 0x0001 #define SQLITE_CHANGESETAPPLY_INVERT 0x0002 +#define SQLITE_CHANGESETAPPLY_IGNORENOOP 0x0004 +#define SQLITE_CHANGESETAPPLY_FKNOACTION 0x0008 +#define SQLITE_CHANGESETAPPLY_NOUPDATELOOP 0x0010 -/* +/* ** CAPI3REF: Constants Passed To The Conflict Handler ** ** Values that may be passed as the second argument to a conflict-handler. @@ -11748,32 +13203,32 @@ SQLITE_API int sqlite3changeset_apply_v2( **

    SQLITE_CHANGESET_DATA
    ** The conflict handler is invoked with CHANGESET_DATA as the second argument ** when processing a DELETE or UPDATE change if a row with the required -** PRIMARY KEY fields is present in the database, but one or more other -** (non primary-key) fields modified by the update do not contain the +** PRIMARY KEY fields is present in the database, but one or more other +** (non primary-key) fields modified by the update do not contain the ** expected "before" values. -** +** ** The conflicting row, in this case, is the database row with the matching ** primary key. -** +** **
    SQLITE_CHANGESET_NOTFOUND
    ** The conflict handler is invoked with CHANGESET_NOTFOUND as the second ** argument when processing a DELETE or UPDATE change if a row with the ** required PRIMARY KEY fields is not present in the database. -** +** ** There is no conflicting row in this case. The results of invoking the ** sqlite3changeset_conflict() API are undefined. -** +** **
    SQLITE_CHANGESET_CONFLICT
    ** CHANGESET_CONFLICT is passed as the second argument to the conflict -** handler while processing an INSERT change if the operation would result +** handler while processing an INSERT change if the operation would result ** in duplicate primary key values. -** +** ** The conflicting row in this case is the database row with the matching ** primary key. ** **
    SQLITE_CHANGESET_FOREIGN_KEY
    ** If foreign key handling is enabled, and applying a changeset leaves the -** database in a state containing foreign key violations, the conflict +** database in a state containing foreign key violations, the conflict ** handler is invoked with CHANGESET_FOREIGN_KEY as the second argument ** exactly once before the changeset is committed. If the conflict handler ** returns CHANGESET_OMIT, the changes, including those that caused the @@ -11783,12 +13238,12 @@ SQLITE_API int sqlite3changeset_apply_v2( ** No current or conflicting row information is provided. The only function ** it is possible to call on the supplied sqlite3_changeset_iter handle ** is sqlite3changeset_fk_conflicts(). -** +** **
    SQLITE_CHANGESET_CONSTRAINT
    -** If any other constraint violation occurs while applying a change (i.e. -** a UNIQUE, CHECK or NOT NULL constraint), the conflict handler is +** If any other constraint violation occurs while applying a change (i.e. +** a UNIQUE, CHECK or NOT NULL constraint), the conflict handler is ** invoked with CHANGESET_CONSTRAINT as the second argument. -** +** ** There is no conflicting row in this case. The results of invoking the ** sqlite3changeset_conflict() API are undefined. ** @@ -11800,7 +13255,7 @@ SQLITE_API int sqlite3changeset_apply_v2( #define SQLITE_CHANGESET_CONSTRAINT 4 #define SQLITE_CHANGESET_FOREIGN_KEY 5 -/* +/* ** CAPI3REF: Constants Returned By The Conflict Handler ** ** A conflict handler callback must return one of the following three values. @@ -11808,13 +13263,13 @@ SQLITE_API int sqlite3changeset_apply_v2( **
    **
    SQLITE_CHANGESET_OMIT
    ** If a conflict handler returns this value no special action is taken. The -** change that caused the conflict is not applied. The session module +** change that caused the conflict is not applied. The session module ** continues to the next change in the changeset. ** **
    SQLITE_CHANGESET_REPLACE
    ** This value may only be returned if the second argument to the conflict ** handler was SQLITE_CHANGESET_DATA or SQLITE_CHANGESET_CONFLICT. If this -** is not the case, any changes applied so far are rolled back and the +** is not the case, any changes applied so far are rolled back and the ** call to sqlite3changeset_apply() returns SQLITE_MISUSE. ** ** If CHANGESET_REPLACE is returned by an SQLITE_CHANGESET_DATA conflict @@ -11827,7 +13282,7 @@ SQLITE_API int sqlite3changeset_apply_v2( ** the original row is restored to the database before continuing. ** **
    SQLITE_CHANGESET_ABORT
    -** If this value is returned, any changes applied so far are rolled back +** If this value is returned, any changes applied so far are rolled back ** and the call to sqlite3changeset_apply() returns SQLITE_ABORT. **
    */ @@ -11835,20 +13290,20 @@ SQLITE_API int sqlite3changeset_apply_v2( #define SQLITE_CHANGESET_REPLACE 1 #define SQLITE_CHANGESET_ABORT 2 -/* +/* ** CAPI3REF: Rebasing changesets ** EXPERIMENTAL ** ** Suppose there is a site hosting a database in state S0. And that ** modifications are made that move that database to state S1 and a ** changeset recorded (the "local" changeset). Then, a changeset based -** on S0 is received from another site (the "remote" changeset) and -** applied to the database. The database is then in state +** on S0 is received from another site (the "remote" changeset) and +** applied to the database. The database is then in state ** (S1+"remote"), where the exact state depends on any conflict ** resolution decisions (OMIT or REPLACE) made while applying "remote". -** Rebasing a changeset is to update it to take those conflict +** Rebasing a changeset is to update it to take those conflict ** resolution decisions into account, so that the same conflicts -** do not have to be resolved elsewhere in the network. +** do not have to be resolved elsewhere in the network. ** ** For example, if both the local and remote changesets contain an ** INSERT of the same key on "CREATE TABLE t1(a PRIMARY KEY, b)": @@ -11867,7 +13322,7 @@ SQLITE_API int sqlite3changeset_apply_v2( ** **
    **
    Local INSERT
    -** This may only conflict with a remote INSERT. If the conflict +** This may only conflict with a remote INSERT. If the conflict ** resolution was OMIT, then add an UPDATE change to the rebased ** changeset. Or, if the conflict resolution was REPLACE, add ** nothing to the rebased changeset. @@ -11891,12 +13346,12 @@ SQLITE_API int sqlite3changeset_apply_v2( ** the old.* values are rebased using the new.* values in the remote ** change. Or, if the resolution is REPLACE, then the change is copied ** into the rebased changeset with updates to columns also updated by -** the conflicting remote UPDATE removed. If this means no columns would +** the conflicting remote UPDATE removed. If this means no columns would ** be updated, the change is omitted. **
    ** -** A local change may be rebased against multiple remote changes -** simultaneously. If a single key is modified by multiple remote +** A local change may be rebased against multiple remote changes +** simultaneously. If a single key is modified by multiple remote ** changesets, they are combined as follows before the local changeset ** is rebased: ** @@ -11909,10 +13364,10 @@ SQLITE_API int sqlite3changeset_apply_v2( ** of the OMIT resolutions. ** ** -** Note that conflict resolutions from multiple remote changesets are -** combined on a per-field basis, not per-row. This means that in the -** case of multiple remote UPDATE operations, some fields of a single -** local change may be rebased for REPLACE while others are rebased for +** Note that conflict resolutions from multiple remote changesets are +** combined on a per-field basis, not per-row. This means that in the +** case of multiple remote UPDATE operations, some fields of a single +** local change may be rebased for REPLACE while others are rebased for ** OMIT. ** ** In order to rebase a local changeset, the remote changeset must first @@ -11920,7 +13375,7 @@ SQLITE_API int sqlite3changeset_apply_v2( ** the buffer of rebase information captured. Then: ** **
      -**
    1. An sqlite3_rebaser object is created by calling +**
    2. An sqlite3_rebaser object is created by calling ** sqlite3rebaser_create(). **
    3. The new object is configured with the rebase buffer obtained from ** sqlite3changeset_apply_v2() by calling sqlite3rebaser_configure(). @@ -11941,8 +13396,8 @@ typedef struct sqlite3_rebaser sqlite3_rebaser; ** ** Allocate a new changeset rebaser object. If successful, set (*ppNew) to ** point to the new object and return SQLITE_OK. Otherwise, if an error -** occurs, return an SQLite error code (e.g. SQLITE_NOMEM) and set (*ppNew) -** to NULL. +** occurs, return an SQLite error code (e.g. SQLITE_NOMEM) and set (*ppNew) +** to NULL. */ SQLITE_API int sqlite3rebaser_create(sqlite3_rebaser **ppNew); @@ -11956,9 +13411,9 @@ SQLITE_API int sqlite3rebaser_create(sqlite3_rebaser **ppNew); ** sqlite3changeset_apply_v2(). */ SQLITE_API int sqlite3rebaser_configure( - sqlite3_rebaser*, + sqlite3_rebaser*, int nRebase, const void *pRebase -); +); /* ** CAPI3REF: Rebase a changeset @@ -11966,9 +13421,9 @@ SQLITE_API int sqlite3rebaser_configure( ** ** Argument pIn must point to a buffer containing a changeset nIn bytes ** in size. This function allocates and populates a buffer with a copy -** of the changeset rebased rebased according to the configuration of the +** of the changeset rebased according to the configuration of the ** rebaser object passed as the first argument. If successful, (*ppOut) -** is set to point to the new buffer containing the rebased changeset and +** is set to point to the new buffer containing the rebased changeset and ** (*pnOut) to its size in bytes and SQLITE_OK returned. It is the ** responsibility of the caller to eventually free the new buffer using ** sqlite3_free(). Otherwise, if an error occurs, (*ppOut) and (*pnOut) @@ -11976,8 +13431,8 @@ SQLITE_API int sqlite3rebaser_configure( */ SQLITE_API int sqlite3rebaser_rebase( sqlite3_rebaser*, - int nIn, const void *pIn, - int *pnOut, void **ppOut + int nIn, const void *pIn, + int *pnOut, void **ppOut ); /* @@ -11988,30 +13443,30 @@ SQLITE_API int sqlite3rebaser_rebase( ** should be one call to this function for each successful invocation ** of sqlite3rebaser_create(). */ -SQLITE_API void sqlite3rebaser_delete(sqlite3_rebaser *p); +SQLITE_API void sqlite3rebaser_delete(sqlite3_rebaser *p); /* ** CAPI3REF: Streaming Versions of API functions. ** -** The six streaming API xxx_strm() functions serve similar purposes to the +** The six streaming API xxx_strm() functions serve similar purposes to the ** corresponding non-streaming API functions: ** ** ** -**
      Streaming functionNon-streaming equivalent
      sqlite3changeset_apply_strm[sqlite3changeset_apply] -**
      sqlite3changeset_apply_strm_v2[sqlite3changeset_apply_v2] -**
      sqlite3changeset_concat_strm[sqlite3changeset_concat] -**
      sqlite3changeset_invert_strm[sqlite3changeset_invert] -**
      sqlite3changeset_start_strm[sqlite3changeset_start] -**
      sqlite3session_changeset_strm[sqlite3session_changeset] -**
      sqlite3session_patchset_strm[sqlite3session_patchset] +**
      sqlite3changeset_apply_strm[sqlite3changeset_apply] +**
      sqlite3changeset_apply_strm_v2[sqlite3changeset_apply_v2] +**
      sqlite3changeset_concat_strm[sqlite3changeset_concat] +**
      sqlite3changeset_invert_strm[sqlite3changeset_invert] +**
      sqlite3changeset_start_strm[sqlite3changeset_start] +**
      sqlite3session_changeset_strm[sqlite3session_changeset] +**
      sqlite3session_patchset_strm[sqlite3session_patchset] **
      ** ** Non-streaming functions that accept changesets (or patchsets) as input -** require that the entire changeset be stored in a single buffer in memory. -** Similarly, those that return a changeset or patchset do so by returning -** a pointer to a single large buffer allocated using sqlite3_malloc(). -** Normally this is convenient. However, if an application running in a +** require that the entire changeset be stored in a single buffer in memory. +** Similarly, those that return a changeset or patchset do so by returning +** a pointer to a single large buffer allocated using sqlite3_malloc(). +** Normally this is convenient. However, if an application running in a ** low-memory environment is required to handle very large changesets, the ** large contiguous memory allocations required can become onerous. ** @@ -12033,12 +13488,12 @@ SQLITE_API void sqlite3rebaser_delete(sqlite3_rebaser *p); ** ** ** Each time the xInput callback is invoked by the sessions module, the first -** argument passed is a copy of the supplied pIn context pointer. The second -** argument, pData, points to a buffer (*pnData) bytes in size. Assuming no -** error occurs the xInput method should copy up to (*pnData) bytes of data -** into the buffer and set (*pnData) to the actual number of bytes copied -** before returning SQLITE_OK. If the input is completely exhausted, (*pnData) -** should be set to zero to indicate this. Or, if an error occurs, an SQLite +** argument passed is a copy of the supplied pIn context pointer. The second +** argument, pData, points to a buffer (*pnData) bytes in size. Assuming no +** error occurs the xInput method should copy up to (*pnData) bytes of data +** into the buffer and set (*pnData) to the actual number of bytes copied +** before returning SQLITE_OK. If the input is completely exhausted, (*pnData) +** should be set to zero to indicate this. Or, if an error occurs, an SQLite ** error code should be returned. In all cases, if an xInput callback returns ** an error, all processing is abandoned and the streaming API function ** returns a copy of the error code to the caller. @@ -12046,7 +13501,7 @@ SQLITE_API void sqlite3rebaser_delete(sqlite3_rebaser *p); ** In the case of sqlite3changeset_start_strm(), the xInput callback may be ** invoked by the sessions module at any point during the lifetime of the ** iterator. If such an xInput callback returns an error, the iterator enters -** an error state, whereby all subsequent calls to iterator functions +** an error state, whereby all subsequent calls to iterator functions ** immediately fail with the same error code as returned by xInput. ** ** Similarly, streaming API functions that return changesets (or patchsets) @@ -12076,7 +13531,7 @@ SQLITE_API void sqlite3rebaser_delete(sqlite3_rebaser *p); ** is immediately abandoned and the streaming API function returns a copy ** of the xOutput error code to the application. ** -** The sessions module never invokes an xOutput callback with the third +** The sessions module never invokes an xOutput callback with the third ** parameter set to a value less than or equal to zero. Other than this, ** no guarantees are made as to the size of the chunks of data returned. */ @@ -12112,6 +13567,23 @@ SQLITE_API int sqlite3changeset_apply_v2_strm( void **ppRebase, int *pnRebase, int flags ); +SQLITE_API int sqlite3changeset_apply_v3_strm( + sqlite3 *db, /* Apply change to "main" db of this handle */ + int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */ + void *pIn, /* First arg for xInput */ + int(*xFilter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + sqlite3_changeset_iter *p + ), + int(*xConflict)( + void *pCtx, /* Copy of sixth arg to _apply() */ + int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ + sqlite3_changeset_iter *p /* Handle describing change and conflict */ + ), + void *pCtx, /* First argument passed to xConflict */ + void **ppRebase, int *pnRebase, + int flags +); SQLITE_API int sqlite3changeset_concat_strm( int (*xInputA)(void *pIn, void *pData, int *pnData), void *pInA, @@ -12147,12 +13619,12 @@ SQLITE_API int sqlite3session_patchset_strm( int (*xOutput)(void *pOut, const void *pData, int nData), void *pOut ); -SQLITE_API int sqlite3changegroup_add_strm(sqlite3_changegroup*, +SQLITE_API int sqlite3changegroup_add_strm(sqlite3_changegroup*, int (*xInput)(void *pIn, void *pData, int *pnData), void *pIn ); SQLITE_API int sqlite3changegroup_output_strm(sqlite3_changegroup*, - int (*xOutput)(void *pOut, const void *pData, int nData), + int (*xOutput)(void *pOut, const void *pData, int nData), void *pOut ); SQLITE_API int sqlite3rebaser_rebase_strm( @@ -12167,16 +13639,16 @@ SQLITE_API int sqlite3rebaser_rebase_strm( ** CAPI3REF: Configure global parameters ** ** The sqlite3session_config() interface is used to make global configuration -** changes to the sessions module in order to tune it to the specific needs +** changes to the sessions module in order to tune it to the specific needs ** of the application. ** ** The sqlite3session_config() interface is not threadsafe. If it is invoked ** while any other thread is inside any other sessions method then the ** results are undefined. Furthermore, if it is invoked after any sessions -** related objects have been created, the results are also undefined. +** related objects have been created, the results are also undefined. ** ** The first argument to the sqlite3session_config() function must be one -** of the SQLITE_SESSION_CONFIG_XXX constants defined below. The +** of the SQLITE_SESSION_CONFIG_XXX constants defined below. The ** interpretation of the (void*) value passed as the second parameter and ** the effect of calling this function depends on the value of the first ** parameter. @@ -12203,6 +13675,232 @@ SQLITE_API int sqlite3session_config(int op, void *pArg); */ #define SQLITE_SESSION_CONFIG_STRMSIZE 1 +/* +** CAPI3REF: Configure a changegroup object +** +** Configure the changegroup object passed as the first argument. +** At present the only valid value for the second parameter is +** [SQLITE_CHANGEGROUP_CONFIG_PATCHSET]. +*/ +SQLITE_API int sqlite3changegroup_config(sqlite3_changegroup*, int, void *pArg); + +/* +** CAPI3REF: Options for sqlite3changegroup_config(). +** +** The following values may be passed as the 2nd parameter to +** sqlite3changegroup_config(). +** +**
      SQLITE_CHANGEGROUP_CONFIG_PATCHSET
      +** A changegroup object generates either a changeset or patchset. Usually, +** this is determined by whether the first call to sqlite3changegroup_add() +** is passed a changeset or a patchset. Or, if the first changes are added +** to the changegroup object using the sqlite3changegroup_change_xxx() +** APIs, then this option may be used to configure whether the changegroup +** object generates a changeset or patchset. +** +** When this option is invoked, parameter pArg must point to a value of +** type int. If the changegroup currently contains zero changes, and the +** value of the int variable is zero or greater than zero, then the +** changegroup is configured to generate a changeset or patchset, +** respectively. It is a no-op, not an error, if the changegroup is not +** configured because it has already started accumulating changes. +** +** Before returning, the int variable is set to 0 if the changegroup is +** configured to generate a changeset, or 1 if it is configured to generate +** a patchset. +*/ +#define SQLITE_CHANGEGROUP_CONFIG_PATCHSET 1 + + +/* +** CAPI3REF: Begin adding a change to a changegroup +** +** This API is used, in concert with other sqlite3changegroup_change_xxx() +** APIs, to add changes to a changegroup object one at a time. To add a +** single change, the caller must: +** +** 1. Invoke sqlite3changegroup_change_begin() to indicate the type of +** change (INSERT, UPDATE or DELETE), the affected table and whether +** or not the change should be marked as indirect. +** +** 2. Invoke sqlite3changegroup_change_int64() or one of the other four +** value functions - _null(), _double(), _text() or _blob() - one or +** more times to specify old.* and new.* values for the change being +** constructed. +** +** 3. Invoke sqlite3changegroup_change_finish() to either finish adding +** the change to the group, or to discard the change altogether. +** +** The first argument to this function must be a pointer to the existing +** changegroup object that the change will be added to. The second argument +** must be SQLITE_INSERT, SQLITE_UPDATE or SQLITE_DELETE. The third is the +** name of the table that the change affects, and the fourth is a boolean +** flag specifying whether the change should be marked as "indirect" (if +** bIndirect is non-zero) or not indirect (if bIndirect is zero). +** +** Following a successful call to this function, this function may not be +** called again on the same changegroup object until after +** sqlite3changegroup_change_finish() has been called. Doing so is an +** SQLITE_MISUSE error. +** +** The changegroup object passed as the first argument must be already +** configured with schema data for the specified table. It may be configured +** either by calling sqlite3changegroup_schema() with a database that contains +** the table, or sqlite3changegroup_add() with a changeset that contains the +** table. If the changegroup object has not been configured with a schema for +** the specified table when this function is called, SQLITE_ERROR is returned. +** +** If successful, SQLITE_OK is returned. Otherwise, if an error occurs, an +** SQLite error code is returned. In this case, if argument pzErr is non-NULL, +** then (*pzErr) may be set to point to a buffer containing a utf-8 formated, +** nul-terminated, English language error message. It is the responsibility +** of the caller to eventually free this buffer using sqlite3_free(). +*/ +SQLITE_API int sqlite3changegroup_change_begin( + sqlite3_changegroup*, + int eOp, + const char *zTab, + int bIndirect, + char **pzErr +); + +/* +** CAPI3REF: Add a 64-bit integer to a changegroup +** +** This function may only be called between a successful call to +** sqlite3changegroup_change_begin() and its matching +** sqlite3changegroup_change_finish() call. If it is called at any +** other time, it is an SQLITE_MISUSE error. Calling this function +** specifies a 64-bit integer value to be used in the change currently being +** added to the changegroup object passed as the first argument. +** +** The second parameter, bNew, specifies whether the value is to be part of +** the new.* (if bNew is non-zero) or old.* (if bNew is zero) record of +** the change under construction. If this does not match the type of change +** specified by the preceding call to sqlite3changegroup_change_begin() (i.e. +** an old.* value for an SQLITE_INSERT change, or a new.* value for an +** SQLITE_DELETE), then SQLITE_ERROR is returned. +** +** The third parameter specifies the column of the old.* or new.* record that +** the value will be a part of. If the specified table has an explicit primary +** key, then this is the index of the table column, numbered from 0 in the order +** specified within the CREATE TABLE statement. Or, if the table uses an +** implicit rowid key, then the column 0 is the rowid and the explicit columns +** are numbered starting from 1. If the iCol parameter is less than 0 or greater +** than the index of the last column in the table, SQLITE_RANGE is returned. +** +** The fourth parameter is the integer value to use as part of the old.* or +** new.* record. +** +** If this call is successful, SQLITE_OK is returned. Otherwise, if an +** error occurs, an SQLite error code is returned. +*/ +SQLITE_API int sqlite3changegroup_change_int64( + sqlite3_changegroup*, + int bNew, + int iCol, + sqlite3_int64 iVal +); + +/* +** CAPI3REF: Add a NULL to a changegroup +** +** This function is similar to sqlite3changegroup_change_int64(). Except that +** it configures the change currently under construction with a NULL value +** instead of a 64-bit integer. +*/ +SQLITE_API int sqlite3changegroup_change_null(sqlite3_changegroup*, int, int); + +/* +** CAPI3REF: Add an double to a changegroup +** +** This function is similar to sqlite3changegroup_change_int64(). Except that +** it configures the change currently being constructed with a real value +** instead of a 64-bit integer. +*/ +SQLITE_API int sqlite3changegroup_change_double(sqlite3_changegroup*, int, int, double); + +/* +** CAPI3REF: Add a text value to a changegroup +** +** This function is similar to sqlite3changegroup_change_int64(). It configures +** the currently accumulated change with a text value instead of a 64-bit +** integer. Parameter pVal points to a buffer containing the text encoded using +** utf-8. Parameter nVal may either be the size of the text value in bytes, or +** else a negative value, in which case the buffer pVal points to is assumed to +** be nul-terminated. +*/ +SQLITE_API int sqlite3changegroup_change_text( + sqlite3_changegroup*, int, int, const char *pVal, int nVal +); + +/* +** CAPI3REF: Add a blob to a changegroup +** +** This function is similar to sqlite3changegroup_change_int64(). It configures +** the currently accumulated change with a blob value instead of a 64-bit +** integer. Parameter pVal points to a buffer containing the blob. Parameter +** nVal is the size of the blob in bytes. +*/ +SQLITE_API int sqlite3changegroup_change_blob( + sqlite3_changegroup*, int, int, const void *pVal, int nVal +); + +/* +** CAPI3REF: Finish adding one-at-at-time changes to a changegroup +** +** This function may only be called following a successful call to +** sqlite3changegroup_change_begin(). Otherwise, it is an SQLITE_MISUSE error. +** +** If parameter bDiscard is non-zero, then the current change is simply +** discarded. In this case this function is always successful and SQLITE_OK +** returned. +** +** If parameter bDiscard is zero, then an attempt is made to add the current +** change to the changegroup. Assuming the changegroup is configured to +** produce a changeset (not a patchset), this requires that: +** +** * If the change is an INSERT or DELETE, then a value must be specified +** for all columns of the new.* or old.* record, respectively. +** +** * If the change is an UPDATE record, then values must be provided for +** the PRIMARY KEY columns of the old.* record, but must not be provided +** for PRIMARY KEY columns of the new.* record. +** +** * If the change is an UPDATE record, then for each non-PRIMARY KEY +** column in the old.* record for which a value has been provided, a +** value must also be provided for the same column in the new.* record. +** Similarly, for each non-PK column in the old.* record for which +** a value is not provided, a value must not be provided for the same +** column in the new.* record. +** +** * All values specified for PRIMARY KEY columns must be non-NULL. +** +** Otherwise, it is an error. +** +** If the changegroup already contains a change for the same row (identified +** by PRIMARY KEY columns), then the current change is combined with the +** existing change in the same way as for sqlite3changegroup_add(). +** +** For a patchset, all of the above rules apply except that it doesn't matter +** whether or not values are provided for the non-PK old.* record columns +** for an UPDATE or DELETE change. This means that code used to produce +** a changeset using the sqlite3changegroup_change_xxx() APIs may also +** be used to produce patchsets. +** +** If the call is successful, SQLITE_OK is returned. Otherwise, if an error +** occurs, an SQLite error code is returned. If an error is returned and +** parameter pzErr is not NULL, then (*pzErr) may be set to point to a buffer +** containing a nul-terminated, utf-8 encoded, English language error message. +** It is the responsibility of the caller to eventually free any such error +** message buffer using sqlite3_free(). +*/ +SQLITE_API int sqlite3changegroup_change_finish( + sqlite3_changegroup*, + int bDiscard, + char **pzErr +); + /* ** Make sure we can call this stuff from C++. */ @@ -12226,7 +13924,7 @@ SQLITE_API int sqlite3session_config(int op, void *pArg); ** ****************************************************************************** ** -** Interfaces to extend FTS5. Using the interfaces defined in this file, +** Interfaces to extend FTS5. Using the interfaces defined in this file, ** FTS5 may be extended with: ** ** * custom tokenizers, and @@ -12270,19 +13968,19 @@ struct Fts5PhraseIter { ** EXTENSION API FUNCTIONS ** ** xUserData(pFts): -** Return a copy of the context pointer the extension function was -** registered with. +** Return a copy of the pUserData pointer passed to the xCreateFunction() +** API when the extension function was registered. ** ** xColumnTotalSize(pFts, iCol, pnToken): ** If parameter iCol is less than zero, set output variable *pnToken ** to the total number of tokens in the FTS5 table. Or, if iCol is ** non-negative but less than the number of columns in the table, return -** the total number of tokens in column iCol, considering all rows in +** the total number of tokens in column iCol, considering all rows in ** the FTS5 table. ** ** If parameter iCol is greater than or equal to the number of columns ** in the table, SQLITE_RANGE is returned. Or, if an error occurs (e.g. -** an OOM condition or IO error), an appropriate SQLite error code is +** an OOM condition or IO error), an appropriate SQLite error code is ** returned. ** ** xColumnCount(pFts): @@ -12296,15 +13994,18 @@ struct Fts5PhraseIter { ** ** If parameter iCol is greater than or equal to the number of columns ** in the table, SQLITE_RANGE is returned. Or, if an error occurs (e.g. -** an OOM condition or IO error), an appropriate SQLite error code is +** an OOM condition or IO error), an appropriate SQLite error code is ** returned. ** ** This function may be quite inefficient if used with an FTS5 table ** created with the "columnsize=0" option. ** ** xColumnText: -** This function attempts to retrieve the text of column iCol of the -** current document. If successful, (*pz) is set to point to a buffer +** If parameter iCol is less than zero, or greater than or equal to the +** number of columns in the table, SQLITE_RANGE is returned. +** +** Otherwise, this function attempts to retrieve the text of column iCol of +** the current document. If successful, (*pz) is set to point to a buffer ** containing the text in utf-8 encoding, (*pn) is set to the size in bytes ** (not characters) of the buffer and SQLITE_OK is returned. Otherwise, ** if an error occurs, an SQLite error code is returned and the final values @@ -12314,8 +14015,10 @@ struct Fts5PhraseIter { ** Returns the number of phrases in the current query expression. ** ** xPhraseSize: -** Returns the number of tokens in phrase iPhrase of the query. Phrases -** are numbered starting from zero. +** If parameter iCol is less than zero, or greater than or equal to the +** number of phrases in the current query, as returned by xPhraseCount, +** 0 is returned. Otherwise, this function returns the number of tokens in +** phrase iPhrase of the query. Phrases are numbered starting from zero. ** ** xInstCount: ** Set *pnInst to the total number of occurrences of all phrases within @@ -12323,23 +14026,24 @@ struct Fts5PhraseIter { ** an error code (i.e. SQLITE_NOMEM) if an error occurs. ** ** This API can be quite slow if used with an FTS5 table created with the -** "detail=none" or "detail=column" option. If the FTS5 table is created -** with either "detail=none" or "detail=column" and "content=" option +** "detail=none" or "detail=column" option. If the FTS5 table is created +** with either "detail=none" or "detail=column" and "content=" option ** (i.e. if it is a contentless table), then this API always returns 0. ** ** xInst: ** Query for the details of phrase match iIdx within the current row. ** Phrase matches are numbered starting from zero, so the iIdx argument ** should be greater than or equal to zero and smaller than the value -** output by xInstCount(). +** output by xInstCount(). If iIdx is less than zero or greater than +** or equal to the value returned by xInstCount(), SQLITE_RANGE is returned. ** -** Usually, output parameter *piPhrase is set to the phrase number, *piCol +** Otherwise, output parameter *piPhrase is set to the phrase number, *piCol ** to the column in which it occurs and *piOff the token offset of the -** first token of the phrase. Returns SQLITE_OK if successful, or an error -** code (i.e. SQLITE_NOMEM) if an error occurs. +** first token of the phrase. SQLITE_OK is returned if successful, or an +** error code (i.e. SQLITE_NOMEM) if an error occurs. ** ** This API can be quite slow if used with an FTS5 table created with the -** "detail=none" or "detail=column" option. +** "detail=none" or "detail=column" option. ** ** xRowid: ** Returns the rowid of the current row. @@ -12355,13 +14059,17 @@ struct Fts5PhraseIter { ** ** with $p set to a phrase equivalent to the phrase iPhrase of the ** current query is executed. Any column filter that applies to -** phrase iPhrase of the current query is included in $p. For each -** row visited, the callback function passed as the fourth argument -** is invoked. The context and API objects passed to the callback +** phrase iPhrase of the current query is included in $p. For each +** row visited, the callback function passed as the fourth argument +** is invoked. The context and API objects passed to the callback ** function may be used to access the properties of each matched row. -** Invoking Api.xUserData() returns a copy of the pointer passed as +** Invoking Api.xUserData() returns a copy of the pointer passed as ** the third argument to pUserData. ** +** If parameter iPhrase is less than zero, or greater than or equal to +** the number of phrases in the query, as returned by xPhraseCount(), +** this function returns SQLITE_RANGE. +** ** If the callback function returns any value other than SQLITE_OK, the ** query is abandoned and the xQueryPhrase function returns immediately. ** If the returned value is SQLITE_DONE, xQueryPhrase returns SQLITE_OK. @@ -12374,14 +14082,14 @@ struct Fts5PhraseIter { ** ** xSetAuxdata(pFts5, pAux, xDelete) ** -** Save the pointer passed as the second argument as the extension functions +** Save the pointer passed as the second argument as the extension function's ** "auxiliary data". The pointer may then be retrieved by the current or any ** future invocation of the same fts5 extension function made as part of ** the same MATCH query using the xGetAuxdata() API. ** ** Each extension function is allocated a single auxiliary data slot for -** each FTS query (MATCH expression). If the extension function is invoked -** more than once for a single FTS query, then all invocations share a +** each FTS query (MATCH expression). If the extension function is invoked +** more than once for a single FTS query, then all invocations share a ** single auxiliary data context. ** ** If there is already an auxiliary data pointer when this function is @@ -12400,7 +14108,7 @@ struct Fts5PhraseIter { ** ** xGetAuxdata(pFts5, bClear) ** -** Returns the current auxiliary data pointer for the fts5 extension +** Returns the current auxiliary data pointer for the fts5 extension ** function. See the xSetAuxdata() method for details. ** ** If the bClear argument is non-zero, then the auxiliary data is cleared @@ -12420,7 +14128,7 @@ struct Fts5PhraseIter { ** method, to iterate through all instances of a single query phrase within ** the current row. This is the same information as is accessible via the ** xInstCount/xInst APIs. While the xInstCount/xInst APIs are more convenient -** to use, this API may be faster under some circumstances. To iterate +** to use, this API may be faster under some circumstances. To iterate ** through instances of phrase iPhrase, use the following code: ** ** Fts5PhraseIter iter; @@ -12438,11 +14146,15 @@ struct Fts5PhraseIter { ** xPhraseFirstColumn() and xPhraseNextColumn() as illustrated below). ** ** This API can be quite slow if used with an FTS5 table created with the -** "detail=none" or "detail=column" option. If the FTS5 table is created -** with either "detail=none" or "detail=column" and "content=" option +** "detail=none" or "detail=column" option. If the FTS5 table is created +** with either "detail=none" or "detail=column" and "content=" option ** (i.e. if it is a contentless table), then this API always iterates ** through an empty set (all calls to xPhraseFirst() set iCol to -1). ** +** In all cases, matches are visited in (column ASC, offset ASC) order. +** i.e. all those in column 0, sorted by offset, followed by those in +** column 1, etc. +** ** xPhraseNext() ** See xPhraseFirst above. ** @@ -12463,22 +14175,93 @@ struct Fts5PhraseIter { ** } ** ** This API can be quite slow if used with an FTS5 table created with the -** "detail=none" option. If the FTS5 table is created with either -** "detail=none" "content=" option (i.e. if it is a contentless table), -** then this API always iterates through an empty set (all calls to +** "detail=none" option. If the FTS5 table is created with either +** "detail=none" "content=" option (i.e. if it is a contentless table), +** then this API always iterates through an empty set (all calls to ** xPhraseFirstColumn() set iCol to -1). ** ** The information accessed using this API and its companion ** xPhraseFirstColumn() may also be obtained using xPhraseFirst/xPhraseNext ** (or xInst/xInstCount). The chief advantage of this API is that it is ** significantly more efficient than those alternatives when used with -** "detail=column" tables. +** "detail=column" tables. ** ** xPhraseNextColumn() ** See xPhraseFirstColumn above. +** +** xQueryToken(pFts5, iPhrase, iToken, ppToken, pnToken) +** This is used to access token iToken of phrase iPhrase of the current +** query. Before returning, output parameter *ppToken is set to point +** to a buffer containing the requested token, and *pnToken to the +** size of this buffer in bytes. +** +** If iPhrase or iToken are less than zero, or if iPhrase is greater than +** or equal to the number of phrases in the query as reported by +** xPhraseCount(), or if iToken is equal to or greater than the number of +** tokens in the phrase, SQLITE_RANGE is returned and *ppToken and *pnToken + are both zeroed. +** +** The output text is not a copy of the query text that specified the +** token. It is the output of the tokenizer module. For tokendata=1 +** tables, this includes any embedded 0x00 and trailing data. +** +** xInstToken(pFts5, iIdx, iToken, ppToken, pnToken) +** This is used to access token iToken of phrase hit iIdx within the +** current row. If iIdx is less than zero or greater than or equal to the +** value returned by xInstCount(), SQLITE_RANGE is returned. Otherwise, +** output variable (*ppToken) is set to point to a buffer containing the +** matching document token, and (*pnToken) to the size of that buffer in +** bytes. +** +** The output text is not a copy of the document text that was tokenized. +** It is the output of the tokenizer module. For tokendata=1 tables, this +** includes any embedded 0x00 and trailing data. +** +** This API may be slow in some cases if the token identified by parameters +** iIdx and iToken matched a prefix token in the query. In most cases, the +** first call to this API for each prefix token in the query is forced +** to scan the portion of the full-text index that matches the prefix +** token to collect the extra data required by this API. If the prefix +** token matches a large number of token instances in the document set, +** this may be a performance problem. +** +** If the user knows in advance that a query may use this API for a +** prefix token, FTS5 may be configured to collect all required data as part +** of the initial querying of the full-text index, avoiding the second scan +** entirely. This also causes prefix queries that do not use this API to +** run more slowly and use more memory. FTS5 may be configured in this way +** either on a per-table basis using the [FTS5 insttoken | 'insttoken'] +** option, or on a per-query basis using the +** [fts5_insttoken | fts5_insttoken()] user function. +** +** This API can be quite slow if used with an FTS5 table created with the +** "detail=none" or "detail=column" option. +** +** xColumnLocale(pFts5, iIdx, pzLocale, pnLocale) +** If parameter iCol is less than zero, or greater than or equal to the +** number of columns in the table, SQLITE_RANGE is returned. +** +** Otherwise, this function attempts to retrieve the locale associated +** with column iCol of the current row. Usually, there is no associated +** locale, and output parameters (*pzLocale) and (*pnLocale) are set +** to NULL and 0, respectively. However, if the fts5_locale() function +** was used to associate a locale with the value when it was inserted +** into the fts5 table, then (*pzLocale) is set to point to a nul-terminated +** buffer containing the name of the locale in utf-8 encoding. (*pnLocale) +** is set to the size in bytes of the buffer, not including the +** nul-terminator. +** +** If successful, SQLITE_OK is returned. Or, if an error occurs, an +** SQLite error code is returned. The final value of the output parameters +** is undefined in this case. +** +** xTokenize_v2: +** Tokenize text using the tokenizer belonging to the FTS5 table. This +** API is the same as the xTokenize() API, except that it allows a tokenizer +** locale to be specified. */ struct Fts5ExtensionApi { - int iVersion; /* Currently always set to 3 */ + int iVersion; /* Currently always set to 4 */ void *(*xUserData)(Fts5Context*); @@ -12486,7 +14269,7 @@ struct Fts5ExtensionApi { int (*xRowCount)(Fts5Context*, sqlite3_int64 *pnRow); int (*xColumnTotalSize)(Fts5Context*, int iCol, sqlite3_int64 *pnToken); - int (*xTokenize)(Fts5Context*, + int (*xTokenize)(Fts5Context*, const char *pText, int nText, /* Text to tokenize */ void *pCtx, /* Context passed to xToken() */ int (*xToken)(void*, int, const char*, int, int, int) /* Callback */ @@ -12513,17 +14296,33 @@ struct Fts5ExtensionApi { int (*xPhraseFirstColumn)(Fts5Context*, int iPhrase, Fts5PhraseIter*, int*); void (*xPhraseNextColumn)(Fts5Context*, Fts5PhraseIter*, int *piCol); + + /* Below this point are iVersion>=3 only */ + int (*xQueryToken)(Fts5Context*, + int iPhrase, int iToken, + const char **ppToken, int *pnToken + ); + int (*xInstToken)(Fts5Context*, int iIdx, int iToken, const char**, int*); + + /* Below this point are iVersion>=4 only */ + int (*xColumnLocale)(Fts5Context*, int iCol, const char **pz, int *pn); + int (*xTokenize_v2)(Fts5Context*, + const char *pText, int nText, /* Text to tokenize */ + const char *pLocale, int nLocale, /* Locale to pass to tokenizer */ + void *pCtx, /* Context passed to xToken() */ + int (*xToken)(void*, int, const char*, int, int, int) /* Callback */ + ); }; -/* +/* ** CUSTOM AUXILIARY FUNCTIONS *************************************************************************/ /************************************************************************* ** CUSTOM TOKENIZERS ** -** Applications may also register custom tokenizer types. A tokenizer -** is registered by providing fts5 with a populated instance of the +** Applications may also register custom tokenizer types. A tokenizer +** is registered by providing fts5 with a populated instance of the ** following structure. All structure methods must be defined, setting ** any member of the fts5_tokenizer struct to NULL leads to undefined ** behaviour. The structure methods are expected to function as follows: @@ -12533,17 +14332,17 @@ struct Fts5ExtensionApi { ** A tokenizer instance is required to actually tokenize text. ** ** The first argument passed to this function is a copy of the (void*) -** pointer provided by the application when the fts5_tokenizer object -** was registered with FTS5 (the third argument to xCreateTokenizer()). +** pointer provided by the application when the fts5_tokenizer_v2 object +** was registered with FTS5 (the third argument to xCreateTokenizer()). ** The second and third arguments are an array of nul-terminated strings ** containing the tokenizer arguments, if any, specified following the ** tokenizer name as part of the CREATE VIRTUAL TABLE statement used ** to create the FTS5 table. ** -** The final argument is an output variable. If successful, (*ppOut) +** The final argument is an output variable. If successful, (*ppOut) ** should be set to point to the new tokenizer handle and SQLITE_OK ** returned. If an error occurs, some value other than SQLITE_OK should -** be returned. In this case, fts5 assumes that the final value of *ppOut +** be returned. In this case, fts5 assumes that the final value of *ppOut ** is undefined. ** ** xDelete: @@ -12552,12 +14351,12 @@ struct Fts5ExtensionApi { ** be invoked exactly once for each successful call to xCreate(). ** ** xTokenize: -** This function is expected to tokenize the nText byte string indicated +** This function is expected to tokenize the nText byte string indicated ** by argument pText. pText may or may not be nul-terminated. The first ** argument passed to this function is a pointer to an Fts5Tokenizer object ** returned by an earlier call to xCreate(). ** -** The second argument indicates the reason that FTS5 is requesting +** The third argument indicates the reason that FTS5 is requesting ** tokenization of the supplied text. This is always one of the following ** four values: ** @@ -12566,8 +14365,8 @@ struct Fts5ExtensionApi { ** determine the set of tokens to add to (or delete from) the ** FTS index. ** -**
    4. FTS5_TOKENIZE_QUERY - A MATCH query is being executed -** against the FTS index. The tokenizer is being called to tokenize +**
    5. FTS5_TOKENIZE_QUERY - A MATCH query is being executed +** against the FTS index. The tokenizer is being called to tokenize ** a bareword or quoted string specified as part of the query. ** **
    6. (FTS5_TOKENIZE_QUERY | FTS5_TOKENIZE_PREFIX) - Same as @@ -12575,12 +14374,19 @@ struct Fts5ExtensionApi { ** followed by a "*" character, indicating that the last token ** returned by the tokenizer will be treated as a token prefix. ** -**
    7. FTS5_TOKENIZE_AUX - The tokenizer is being invoked to +**
    8. FTS5_TOKENIZE_AUX - The tokenizer is being invoked to ** satisfy an fts5_api.xTokenize() request made by an auxiliary ** function. Or an fts5_api.xColumnSize() request made by the same -** on a columnsize=0 database. +** on a columnsize=0 database. ** ** +** The sixth and seventh arguments passed to xTokenize() - pLocale and +** nLocale - are a pointer to a buffer containing the locale to use for +** tokenization (e.g. "en_US") and its size in bytes, respectively. The +** pLocale buffer is not nul-terminated. pLocale may be passed NULL (in +** which case nLocale is always 0) to indicate that the tokenizer should +** use its default locale. +** ** For each token in the input string, the supplied callback xToken() must ** be invoked. The first argument to it should be a copy of the pointer ** passed as the second argument to xTokenize(). The third and fourth @@ -12590,10 +14396,10 @@ struct Fts5ExtensionApi { ** which the token is derived within the input. ** ** The second argument passed to the xToken() callback ("tflags") should -** normally be set to 0. The exception is if the tokenizer supports +** normally be set to 0. The exception is if the tokenizer supports ** synonyms. In this case see the discussion below for details. ** -** FTS5 assumes the xToken() callback is invoked for each token in the +** FTS5 assumes the xToken() callback is invoked for each token in the ** order that they occur within the input text. ** ** If an xToken() callback returns any value other than SQLITE_OK, then @@ -12604,10 +14410,34 @@ struct Fts5ExtensionApi { ** may abandon the tokenization and return any error code other than ** SQLITE_OK or SQLITE_DONE. ** +** If the tokenizer is registered using an fts5_tokenizer_v2 object, +** then the xTokenize() method has two additional arguments - pLocale +** and nLocale. These specify the locale that the tokenizer should use +** for the current request. If pLocale and nLocale are both 0, then the +** tokenizer should use its default locale. Otherwise, pLocale points to +** an nLocale byte buffer containing the name of the locale to use as utf-8 +** text. pLocale is not nul-terminated. +** +** FTS5_TOKENIZER +** +** There is also an fts5_tokenizer object. This is an older, deprecated, +** version of fts5_tokenizer_v2. It is similar except that: +** +**
        +**
      • There is no "iVersion" field, and +**
      • The xTokenize() method does not take a locale argument. +**
      +** +** Legacy fts5_tokenizer tokenizers must be registered using the +** legacy xCreateTokenizer() function, instead of xCreateTokenizer_v2(). +** +** Tokenizer implementations registered using either API may be retrieved +** using both xFindTokenizer() and xFindTokenizer_v2(). +** ** SYNONYM SUPPORT ** ** Custom tokenizers may also support synonyms. Consider a case in which a -** user wishes to query for a phrase such as "first place". Using the +** user wishes to query for a phrase such as "first place". Using the ** built-in tokenizers, the FTS5 query 'first + place' will match instances ** of "first place" within the document set, but not alternative forms ** such as "1st place". In some applications, it would be better to match @@ -12616,8 +14446,8 @@ struct Fts5ExtensionApi { ** ** There are several ways to approach this in FTS5: ** -**
      1. By mapping all synonyms to a single token. In this case, the -** In the above example, this means that the tokenizer returns the +**
        1. By mapping all synonyms to a single token. In this case, using +** the above example, this means that the tokenizer returns the ** same token for inputs "first" and "1st". Say that token is in ** fact "first", so that when the user inserts the document "I won ** 1st place" entries are added to the index for tokens "i", "won", @@ -12627,34 +14457,34 @@ struct Fts5ExtensionApi { ** **
        2. By querying the index for all synonyms of each query term ** separately. In this case, when tokenizing query text, the -** tokenizer may provide multiple synonyms for a single term -** within the document. FTS5 then queries the index for each +** tokenizer may provide multiple synonyms for a single term +** within the document. FTS5 then queries the index for each ** synonym individually. For example, faced with the query: ** ** ** ... MATCH 'first place' ** ** the tokenizer offers both "1st" and "first" as synonyms for the -** first token in the MATCH query and FTS5 effectively runs a query +** first token in the MATCH query and FTS5 effectively runs a query ** similar to: ** ** ** ... MATCH '(first OR 1st) place' ** ** except that, for the purposes of auxiliary functions, the query -** still appears to contain just two phrases - "(first OR 1st)" +** still appears to contain just two phrases - "(first OR 1st)" ** being treated as a single phrase. ** **
        3. By adding multiple synonyms for a single term to the FTS index. ** Using this method, when tokenizing document text, the tokenizer -** provides multiple synonyms for each token. So that when a +** provides multiple synonyms for each token. So that when a ** document such as "I won first place" is tokenized, entries are ** added to the FTS index for "i", "won", "first", "1st" and ** "place". ** ** This way, even if the tokenizer does not provide synonyms ** when tokenizing query text (it should not - to do so would be -** inefficient), it doesn't matter if the user queries for +** inefficient), it doesn't matter if the user queries for ** 'first + place' or '1st + place', as there are entries in the ** FTS index corresponding to both forms of the first token. **
        @@ -12675,11 +14505,11 @@ struct Fts5ExtensionApi { ** ** It is an error to specify the FTS5_TOKEN_COLOCATED flag the first time ** xToken() is called. Multiple synonyms may be specified for a single token -** by making multiple calls to xToken(FTS5_TOKEN_COLOCATED) in sequence. +** by making multiple calls to xToken(FTS5_TOKEN_COLOCATED) in sequence. ** There is no limit to the number of synonyms that may be provided for a ** single token. ** -** In many cases, method (1) above is the best approach. It does not add +** In many cases, method (1) above is the best approach. It does not add ** extra data to the FTS index or require FTS5 to query for multiple terms, ** so it is efficient in terms of disk space and query speed. However, it ** does not support prefix queries very well. If, as suggested above, the @@ -12691,35 +14521,62 @@ struct Fts5ExtensionApi { ** will not match documents that contain the token "1st" (as the tokenizer ** will probably not map "1s" to any prefix of "first"). ** -** For full prefix support, method (3) may be preferred. In this case, +** For full prefix support, method (3) may be preferred. In this case, ** because the index contains entries for both "first" and "1st", prefix ** queries such as 'fi*' or '1s*' will match correctly. However, because ** extra entries are added to the FTS index, this method uses more space ** within the database. ** ** Method (2) offers a midpoint between (1) and (3). Using this method, -** a query such as '1s*' will match documents that contain the literal +** a query such as '1s*' will match documents that contain the literal ** token "1st", but not "first" (assuming the tokenizer is not able to ** provide synonyms for prefixes). However, a non-prefix query like '1st' ** will match against "1st" and "first". This method does not require -** extra disk space, as no extra entries are added to the FTS index. +** extra disk space, as no extra entries are added to the FTS index. ** On the other hand, it may require more CPU cycles to run MATCH queries, ** as separate queries of the FTS index are required for each synonym. ** ** When using methods (2) or (3), it is important that the tokenizer only -** provide synonyms when tokenizing document text (method (2)) or query -** text (method (3)), not both. Doing so will not cause any errors, but is +** provide synonyms when tokenizing document text (method (3)) or query +** text (method (2)), not both. Doing so will not cause any errors, but is ** inefficient. */ typedef struct Fts5Tokenizer Fts5Tokenizer; +typedef struct fts5_tokenizer_v2 fts5_tokenizer_v2; +struct fts5_tokenizer_v2 { + int iVersion; /* Currently always 2 */ + + int (*xCreate)(void*, const char **azArg, int nArg, Fts5Tokenizer **ppOut); + void (*xDelete)(Fts5Tokenizer*); + int (*xTokenize)(Fts5Tokenizer*, + void *pCtx, + int flags, /* Mask of FTS5_TOKENIZE_* flags */ + const char *pText, int nText, + const char *pLocale, int nLocale, + int (*xToken)( + void *pCtx, /* Copy of 2nd argument to xTokenize() */ + int tflags, /* Mask of FTS5_TOKEN_* flags */ + const char *pToken, /* Pointer to buffer containing token */ + int nToken, /* Size of token in bytes */ + int iStart, /* Byte offset of token within input text */ + int iEnd /* Byte offset of end of token within input text */ + ) + ); +}; + +/* +** New code should use the fts5_tokenizer_v2 type to define tokenizer +** implementations. The following type is included for legacy applications +** that still use it. +*/ typedef struct fts5_tokenizer fts5_tokenizer; struct fts5_tokenizer { int (*xCreate)(void*, const char **azArg, int nArg, Fts5Tokenizer **ppOut); void (*xDelete)(Fts5Tokenizer*); - int (*xTokenize)(Fts5Tokenizer*, + int (*xTokenize)(Fts5Tokenizer*, void *pCtx, int flags, /* Mask of FTS5_TOKENIZE_* flags */ - const char *pText, int nText, + const char *pText, int nText, int (*xToken)( void *pCtx, /* Copy of 2nd argument to xTokenize() */ int tflags, /* Mask of FTS5_TOKEN_* flags */ @@ -12731,6 +14588,7 @@ struct fts5_tokenizer { ); }; + /* Flags that may be passed as the third argument to xTokenize() */ #define FTS5_TOKENIZE_QUERY 0x0001 #define FTS5_TOKENIZE_PREFIX 0x0002 @@ -12750,13 +14608,13 @@ struct fts5_tokenizer { */ typedef struct fts5_api fts5_api; struct fts5_api { - int iVersion; /* Currently always set to 2 */ + int iVersion; /* Currently always set to 3 */ /* Create a new tokenizer */ int (*xCreateTokenizer)( fts5_api *pApi, const char *zName, - void *pContext, + void *pUserData, fts5_tokenizer *pTokenizer, void (*xDestroy)(void*) ); @@ -12765,7 +14623,7 @@ struct fts5_api { int (*xFindTokenizer)( fts5_api *pApi, const char *zName, - void **ppContext, + void **ppUserData, fts5_tokenizer *pTokenizer ); @@ -12773,10 +14631,29 @@ struct fts5_api { int (*xCreateFunction)( fts5_api *pApi, const char *zName, - void *pContext, + void *pUserData, fts5_extension_function xFunction, void (*xDestroy)(void*) ); + + /* APIs below this point are only available if iVersion>=3 */ + + /* Create a new tokenizer */ + int (*xCreateTokenizer_v2)( + fts5_api *pApi, + const char *zName, + void *pUserData, + fts5_tokenizer_v2 *pTokenizer, + void (*xDestroy)(void*) + ); + + /* Find an existing tokenizer */ + int (*xFindTokenizer_v2)( + fts5_api *pApi, + const char *zName, + void **ppUserData, + fts5_tokenizer_v2 **ppTokenizer + ); }; /* @@ -12790,16 +14667,22 @@ struct fts5_api { #endif /* _FTS5_H */ /******** End of fts5.h *********/ +#endif /* SQLITE3_H */ /************** End of sqlite3.h *********************************************/ /************** Continuing where we left off in sqliteInt.h ******************/ +/* +** Reuse the STATIC_LRU for mutex access to sqlite3_temp_directory. +*/ +#define SQLITE_MUTEX_STATIC_TEMPDIR SQLITE_MUTEX_STATIC_VFS1 + /* ** Include the configuration header output by 'configure' if we're using the ** autoconf-based build */ #if defined(_HAVE_SQLITE_CONFIG_H) && !defined(SQLITECONFIG_H) -/* #include "config.h" */ +#include "sqlite_cfg.h" #define SQLITECONFIG_H 1 #endif @@ -12816,7 +14699,7 @@ struct fts5_api { ** May you share freely, never taking more than you give. ** ************************************************************************* -** +** ** This file defines various limits of what SQLite can process. */ @@ -12830,6 +14713,28 @@ struct fts5_api { #ifndef SQLITE_MAX_LENGTH # define SQLITE_MAX_LENGTH 1000000000 #endif +#define SQLITE_MIN_LENGTH 30 /* Minimum value for the length limit */ + +/* +** Maximum size of any single memory allocation. +** +** This is not a limit on the total amount of memory used. This is +** a limit on the size parameter to sqlite3_malloc() and sqlite3_realloc(). +** +** The upper bound is slightly less than 2GiB: 0x7ffffeff == 2,147,483,391 +** This provides a 256-byte safety margin for defense against 32-bit +** signed integer overflow bugs when computing memory allocation sizes. +** Paranoid applications might want to reduce the maximum allocation size +** further for an even larger safety margin. 0x3fffffff or 0x0fffffff +** or even smaller would be reasonable upper bounds on the size of a memory +** allocations for most applications. +*/ +#ifndef SQLITE_MAX_ALLOCATION_SIZE +# define SQLITE_MAX_ALLOCATION_SIZE 2147483391 +#endif +#if SQLITE_MAX_ALLOCATION_SIZE>2147483391 +# error Maximum size for SQLITE_MAX_ALLOCATION_SIZE is 2147483391 +#endif /* ** This is the maximum number of @@ -12842,14 +14747,22 @@ struct fts5_api { ** * Terms in the GROUP BY or ORDER BY clauses of a SELECT statement. ** * Terms in the VALUES clause of an INSERT statement ** -** The hard upper limit here is 32676. Most database people will +** The hard upper limit here is 32767. Most database people will ** tell you that in a well-normalized database, you usually should ** not have more than a dozen or so columns in any table. And if ** that is the case, there is no point in having more than a few ** dozen values in any of the other situations described above. +** +** An index can only have SQLITE_MAX_COLUMN columns from the user +** point of view, but the underlying b-tree that implements the index +** might have up to twice as many columns in a WITHOUT ROWID table, +** since must also store the primary key at the end. Hence the +** column count for Index is u16 instead of i16. */ -#ifndef SQLITE_MAX_COLUMN +#if !defined(SQLITE_MAX_COLUMN) # define SQLITE_MAX_COLUMN 2000 +#elif SQLITE_MAX_COLUMN>32767 +# error SQLITE_MAX_COLUMN may not exceed 32767 #endif /* @@ -12858,32 +14771,49 @@ struct fts5_api { ** It used to be the case that setting this value to zero would ** turn the limit off. That is no longer true. It is not possible ** to turn this limit off. +** +** The hard limit is the largest possible 32-bit signed integer less +** 1024, or 2147482624. */ #ifndef SQLITE_MAX_SQL_LENGTH # define SQLITE_MAX_SQL_LENGTH 1000000000 #endif /* -** The maximum depth of an expression tree. This is limited to -** some extent by SQLITE_MAX_SQL_LENGTH. But sometime you might -** want to place more severe limits on the complexity of an -** expression. +** The maximum depth of an expression tree. The expression tree depth +** is also limited indirectly by SQLITE_MAX_SQL_LENGTH and by +** SQLITE_MAX_PARSER_DEPTH. Reducing the maximum complexity of +** expressions can help prevent excess memory usage by hostile SQL. ** -** A value of 0 used to mean that the limit was not enforced. -** But that is no longer true. The limit is now strictly enforced -** at all times. +** A value of 0 for this compile-time option causes all expression +** depth limiting code to be omitted. */ #ifndef SQLITE_MAX_EXPR_DEPTH # define SQLITE_MAX_EXPR_DEPTH 1000 #endif +/* +** The maximum depth of the LALR(1) stack used in the parser that +** interprets SQL inputs. The parser stack depth can also be limited +** indirectly by SQLITE_MAX_SQL_LENGTH. Limiting the parser stack +** depth can help prevent excess memory usage and excess CPU stack +** usage when processing hostile SQL. +** +** Prior to version 3.45.0 (2024-01-15), the parser stack was +** hard-coded to 100 entries, and that worked fine for almost all +** applications. So the upper bound on this limit need not be large. +*/ +#ifndef SQLITE_MAX_PARSER_DEPTH +# define SQLITE_MAX_PARSER_DEPTH 2500 +#endif + /* ** The maximum number of terms in a compound SELECT statement. ** The code generator for compound SELECT statements does one ** level of recursion for each term. A stack overflow can result ** if the number of terms is too large. In practice, most SQL ** never has more than 3 or 4 terms. Use a value of 0 to disable -** any limit on the number of terms in a compount SELECT. +** any limit on the number of terms in a compound SELECT. */ #ifndef SQLITE_MAX_COMPOUND_SELECT # define SQLITE_MAX_COMPOUND_SELECT 500 @@ -12899,9 +14829,13 @@ struct fts5_api { /* ** The maximum number of arguments to an SQL function. +** +** This value has a hard upper limit of 32767 due to storage +** constraints (it needs to fit inside a i16). We keep it +** lower than that to prevent abuse. */ #ifndef SQLITE_MAX_FUNCTION_ARG -# define SQLITE_MAX_FUNCTION_ARG 127 +# define SQLITE_MAX_FUNCTION_ARG 1000 #endif /* @@ -12938,9 +14872,12 @@ struct fts5_api { /* ** The maximum value of a ?nnn wildcard that the parser will accept. +** If the value exceeds 32767 then extra space is required for the Expr +** structure. But otherwise, we believe that the number can be as large +** as a signed 32-bit integer can hold. */ #ifndef SQLITE_MAX_VARIABLE_NUMBER -# define SQLITE_MAX_VARIABLE_NUMBER 999 +# define SQLITE_MAX_VARIABLE_NUMBER 32766 #endif /* Maximum page size. The upper bound on this value is 65536. This a limit @@ -12948,10 +14885,10 @@ struct fts5_api { ** ** Earlier versions of SQLite allowed the user to change this value at ** compile time. This is no longer permitted, on the grounds that it creates -** a library that is technically incompatible with an SQLite library -** compiled with a different limit. If a process operating on a database -** with a page-size of 65536 bytes crashes, then an instance of SQLite -** compiled with the default page-size limit will not be able to rollback +** a library that is technically incompatible with an SQLite library +** compiled with a different limit. If a process operating on a database +** with a page-size of 65536 bytes crashes, then an instance of SQLite +** compiled with the default page-size limit will not be able to rollback ** the aborted transaction. This could lead to database corruption. */ #ifdef SQLITE_MAX_PAGE_SIZE @@ -12985,17 +14922,21 @@ struct fts5_api { # undef SQLITE_MAX_DEFAULT_PAGE_SIZE # define SQLITE_MAX_DEFAULT_PAGE_SIZE SQLITE_MAX_PAGE_SIZE #endif +#if SQLITE_MAX_DEFAULT_PAGE_SIZE=4007000 || __has_extension(c_atomic) +# define SQLITE_ATOMIC_INTRINSICS 1 +# define AtomicLoad(PTR) __atomic_load_n((PTR),__ATOMIC_RELAXED) +# define AtomicStore(PTR,VAL) __atomic_store_n((PTR),(VAL),__ATOMIC_RELAXED) +#else +# define SQLITE_ATOMIC_INTRINSICS 0 +# define AtomicLoad(PTR) (*(PTR)) +# define AtomicStore(PTR,VAL) (*(PTR) = (VAL)) +#endif + /* ** Include standard header files as necessary */ -#ifdef HAVE_STDINT_H #include -#endif #ifdef HAVE_INTTYPES_H #include #endif @@ -13055,30 +15011,37 @@ struct fts5_api { ** So we have to define the macros in different ways depending on the ** compiler. */ -#if defined(__PTRDIFF_TYPE__) /* This case should work for GCC */ +#if defined(HAVE_STDINT_H) /* Use this case if we have ANSI headers */ +# define SQLITE_INT_TO_PTR(X) ((void*)(intptr_t)(X)) +# define SQLITE_PTR_TO_INT(X) ((int)(intptr_t)(X)) +#elif defined(__PTRDIFF_TYPE__) /* This case should work for GCC */ # define SQLITE_INT_TO_PTR(X) ((void*)(__PTRDIFF_TYPE__)(X)) # define SQLITE_PTR_TO_INT(X) ((int)(__PTRDIFF_TYPE__)(X)) #elif !defined(__GNUC__) /* Works for compilers other than LLVM */ # define SQLITE_INT_TO_PTR(X) ((void*)&((char*)0)[X]) # define SQLITE_PTR_TO_INT(X) ((int)(((char*)X)-(char*)0)) -#elif defined(HAVE_STDINT_H) /* Use this case if we have ANSI headers */ -# define SQLITE_INT_TO_PTR(X) ((void*)(intptr_t)(X)) -# define SQLITE_PTR_TO_INT(X) ((int)(intptr_t)(X)) #else /* Generates a warning - but it always works */ # define SQLITE_INT_TO_PTR(X) ((void*)(X)) # define SQLITE_PTR_TO_INT(X) ((int)(X)) #endif /* -** A macro to hint to the compiler that a function should not be +** Macros to hint to the compiler that a function should or should not be ** inlined. */ #if defined(__GNUC__) # define SQLITE_NOINLINE __attribute__((noinline)) +# define SQLITE_INLINE __attribute__((always_inline)) inline #elif defined(_MSC_VER) && _MSC_VER>=1310 # define SQLITE_NOINLINE __declspec(noinline) +# define SQLITE_INLINE __forceinline #else # define SQLITE_NOINLINE +# define SQLITE_INLINE +#endif +#if defined(SQLITE_COVERAGE_TEST) || defined(__STRICT_ANSI__) +# undef SQLITE_INLINE +# define SQLITE_INLINE #endif /* @@ -13100,6 +15063,29 @@ struct fts5_api { # endif #endif +/* +** Enable SQLITE_USE_SEH by default on MSVC builds. Only omit +** SEH support if the -DSQLITE_OMIT_SEH option is given. +*/ +#if defined(_MSC_VER) && !defined(SQLITE_OMIT_SEH) +# define SQLITE_USE_SEH 1 +#else +# undef SQLITE_USE_SEH +#endif + +/* +** Enable SQLITE_DIRECT_OVERFLOW_READ, unless the build explicitly +** disables it using -DSQLITE_DIRECT_OVERFLOW_READ=0 +*/ +#if defined(SQLITE_DIRECT_OVERFLOW_READ) && SQLITE_DIRECT_OVERFLOW_READ+1==1 + /* Disable if -DSQLITE_DIRECT_OVERFLOW_READ=0 */ +# undef SQLITE_DIRECT_OVERFLOW_READ +#else + /* In all other cases, enable */ +# define SQLITE_DIRECT_OVERFLOW_READ 1 +#endif + + /* ** The SQLITE_THREADSAFE macro must be defined as 0, 1, or 2. ** 0 means mutexes are permanently disable and the library is never @@ -13229,11 +15215,12 @@ struct fts5_api { ** is significant and used at least once. On switch statements ** where multiple cases go to the same block of code, testcase() ** can insure that all cases are evaluated. -** */ -#ifdef SQLITE_COVERAGE_TEST -SQLITE_PRIVATE void sqlite3Coverage(int); -# define testcase(X) if( X ){ sqlite3Coverage(__LINE__); } +#if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_DEBUG) +# ifndef SQLITE_AMALGAMATION + extern unsigned int sqlite3CoverageCounter; +# endif +# define testcase(X) if( X ){ sqlite3CoverageCounter += (unsigned)__LINE__; } #else # define testcase(X) #endif @@ -13263,6 +15250,14 @@ SQLITE_PRIVATE void sqlite3Coverage(int); # define VVA_ONLY(X) #endif +/* +** Disable ALWAYS() and NEVER() (make them pass-throughs) for coverage +** and mutation testing +*/ +#if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_MUTATION_TEST) +# define SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS 1 +#endif + /* ** The ALWAYS and NEVER macros surround boolean expressions which ** are intended to always be true or false, respectively. Such @@ -13278,7 +15273,7 @@ SQLITE_PRIVATE void sqlite3Coverage(int); ** be true and false so that the unreachable code they specify will ** not be counted as untested code. */ -#if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_MUTATION_TEST) +#if defined(SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS) # define ALWAYS(X) (1) # define NEVER(X) (0) #elif !defined(NDEBUG) @@ -13352,6 +15347,15 @@ SQLITE_PRIVATE void sqlite3Coverage(int); # undef SQLITE_ENABLE_EXPLAIN_COMMENTS #endif +/* +** SQLITE_OMIT_VIRTUALTABLE implies SQLITE_OMIT_ALTERTABLE +*/ +#if defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_ALTERTABLE) +# define SQLITE_OMIT_ALTERTABLE +#endif + +#define SQLITE_DIGIT_SEPARATOR '_' + /* ** Return true (non-zero) if the input is an integer that is too large ** to fit in 32-bits. This macro is used inside of various testcase() @@ -13408,7 +15412,7 @@ typedef struct HashElem HashElem; ** element pointed to plus the next _ht.count-1 elements in the list. ** ** Hash.htsize and Hash.ht may be zero. In that case lookup is done -** by a linear search of the global list. For small tables, the +** by a linear search of the global list. For small tables, the ** Hash.ht table is never allocated because if there are few elements ** in the table, it is faster to do a linear search than to manage ** the hash table. @@ -13423,7 +15427,7 @@ struct Hash { } *ht; }; -/* Each element in the hash table is an instance of the following +/* Each element in the hash table is an instance of the following ** structure. All elements are stored on a single doubly-linked list. ** ** Again, this structure is intended to be opaque, but it can't really @@ -13433,6 +15437,7 @@ struct HashElem { HashElem *next, *prev; /* Next and previous elements in the table */ void *data; /* Data associated with this element */ const char *pKey; /* Key associated with this element */ + unsigned int h; /* hash for pKey */ }; /* @@ -13464,7 +15469,7 @@ SQLITE_PRIVATE void sqlite3HashClear(Hash*); /* ** Number of entries in a hash table */ -/* #define sqliteHashCount(H) ((H)->count) // NOT USED */ +#define sqliteHashCount(H) ((H)->count) #endif /* SQLITE_HASH_H */ @@ -13496,8 +15501,8 @@ SQLITE_PRIVATE void sqlite3HashClear(Hash*); #define TK_LP 22 #define TK_RP 23 #define TK_AS 24 -#define TK_WITHOUT 25 -#define TK_COMMA 26 +#define TK_COMMA 25 +#define TK_WITHOUT 26 #define TK_ABORT 27 #define TK_ACTION 28 #define TK_AFTER 29 @@ -13517,136 +15522,147 @@ SQLITE_PRIVATE void sqlite3HashClear(Hash*); #define TK_OR 43 #define TK_AND 44 #define TK_IS 45 -#define TK_MATCH 46 -#define TK_LIKE_KW 47 -#define TK_BETWEEN 48 -#define TK_IN 49 -#define TK_ISNULL 50 -#define TK_NOTNULL 51 -#define TK_NE 52 -#define TK_EQ 53 -#define TK_GT 54 -#define TK_LE 55 -#define TK_LT 56 -#define TK_GE 57 -#define TK_ESCAPE 58 -#define TK_ID 59 -#define TK_COLUMNKW 60 -#define TK_DO 61 -#define TK_FOR 62 -#define TK_IGNORE 63 -#define TK_INITIALLY 64 -#define TK_INSTEAD 65 -#define TK_NO 66 -#define TK_KEY 67 -#define TK_OF 68 -#define TK_OFFSET 69 -#define TK_PRAGMA 70 -#define TK_RAISE 71 -#define TK_RECURSIVE 72 -#define TK_REPLACE 73 -#define TK_RESTRICT 74 -#define TK_ROW 75 -#define TK_ROWS 76 -#define TK_TRIGGER 77 -#define TK_VACUUM 78 -#define TK_VIEW 79 -#define TK_VIRTUAL 80 -#define TK_WITH 81 -#define TK_CURRENT 82 -#define TK_FOLLOWING 83 -#define TK_PARTITION 84 -#define TK_PRECEDING 85 -#define TK_RANGE 86 -#define TK_UNBOUNDED 87 -#define TK_EXCLUDE 88 -#define TK_GROUPS 89 -#define TK_OTHERS 90 -#define TK_TIES 91 -#define TK_REINDEX 92 -#define TK_RENAME 93 -#define TK_CTIME_KW 94 -#define TK_ANY 95 -#define TK_BITAND 96 -#define TK_BITOR 97 -#define TK_LSHIFT 98 -#define TK_RSHIFT 99 -#define TK_PLUS 100 -#define TK_MINUS 101 -#define TK_STAR 102 -#define TK_SLASH 103 -#define TK_REM 104 -#define TK_CONCAT 105 -#define TK_COLLATE 106 -#define TK_BITNOT 107 -#define TK_ON 108 -#define TK_INDEXED 109 -#define TK_STRING 110 -#define TK_JOIN_KW 111 -#define TK_CONSTRAINT 112 -#define TK_DEFAULT 113 -#define TK_NULL 114 -#define TK_PRIMARY 115 -#define TK_UNIQUE 116 -#define TK_CHECK 117 -#define TK_REFERENCES 118 -#define TK_AUTOINCR 119 -#define TK_INSERT 120 -#define TK_DELETE 121 -#define TK_UPDATE 122 -#define TK_SET 123 -#define TK_DEFERRABLE 124 -#define TK_FOREIGN 125 -#define TK_DROP 126 -#define TK_UNION 127 -#define TK_ALL 128 -#define TK_EXCEPT 129 -#define TK_INTERSECT 130 -#define TK_SELECT 131 -#define TK_VALUES 132 -#define TK_DISTINCT 133 -#define TK_DOT 134 -#define TK_FROM 135 -#define TK_JOIN 136 -#define TK_USING 137 -#define TK_ORDER 138 -#define TK_GROUP 139 -#define TK_HAVING 140 -#define TK_LIMIT 141 -#define TK_WHERE 142 -#define TK_INTO 143 -#define TK_NOTHING 144 -#define TK_FLOAT 145 -#define TK_BLOB 146 -#define TK_INTEGER 147 -#define TK_VARIABLE 148 -#define TK_CASE 149 -#define TK_WHEN 150 -#define TK_THEN 151 -#define TK_ELSE 152 -#define TK_INDEX 153 -#define TK_ALTER 154 -#define TK_ADD 155 -#define TK_WINDOW 156 -#define TK_OVER 157 -#define TK_FILTER 158 -#define TK_TRUEFALSE 159 -#define TK_ISNOT 160 -#define TK_FUNCTION 161 -#define TK_COLUMN 162 -#define TK_AGG_FUNCTION 163 -#define TK_AGG_COLUMN 164 -#define TK_UMINUS 165 -#define TK_UPLUS 166 -#define TK_TRUTH 167 -#define TK_REGISTER 168 -#define TK_VECTOR 169 -#define TK_SELECT_COLUMN 170 -#define TK_IF_NULL_ROW 171 -#define TK_ASTERISK 172 -#define TK_SPAN 173 -#define TK_SPACE 174 -#define TK_ILLEGAL 175 +#define TK_ISNOT 46 +#define TK_MATCH 47 +#define TK_LIKE_KW 48 +#define TK_BETWEEN 49 +#define TK_IN 50 +#define TK_ISNULL 51 +#define TK_NOTNULL 52 +#define TK_NE 53 +#define TK_EQ 54 +#define TK_GT 55 +#define TK_LE 56 +#define TK_LT 57 +#define TK_GE 58 +#define TK_ESCAPE 59 +#define TK_ID 60 +#define TK_COLUMNKW 61 +#define TK_DO 62 +#define TK_FOR 63 +#define TK_IGNORE 64 +#define TK_INITIALLY 65 +#define TK_INSTEAD 66 +#define TK_NO 67 +#define TK_KEY 68 +#define TK_OF 69 +#define TK_OFFSET 70 +#define TK_PRAGMA 71 +#define TK_RAISE 72 +#define TK_RECURSIVE 73 +#define TK_REPLACE 74 +#define TK_RESTRICT 75 +#define TK_ROW 76 +#define TK_ROWS 77 +#define TK_TRIGGER 78 +#define TK_VACUUM 79 +#define TK_VIEW 80 +#define TK_VIRTUAL 81 +#define TK_WITH 82 +#define TK_NULLS 83 +#define TK_FIRST 84 +#define TK_LAST 85 +#define TK_CURRENT 86 +#define TK_FOLLOWING 87 +#define TK_PARTITION 88 +#define TK_PRECEDING 89 +#define TK_RANGE 90 +#define TK_UNBOUNDED 91 +#define TK_EXCLUDE 92 +#define TK_GROUPS 93 +#define TK_OTHERS 94 +#define TK_TIES 95 +#define TK_GENERATED 96 +#define TK_ALWAYS 97 +#define TK_MATERIALIZED 98 +#define TK_REINDEX 99 +#define TK_RENAME 100 +#define TK_CTIME_KW 101 +#define TK_ANY 102 +#define TK_BITAND 103 +#define TK_BITOR 104 +#define TK_LSHIFT 105 +#define TK_RSHIFT 106 +#define TK_PLUS 107 +#define TK_MINUS 108 +#define TK_STAR 109 +#define TK_SLASH 110 +#define TK_REM 111 +#define TK_CONCAT 112 +#define TK_PTR 113 +#define TK_COLLATE 114 +#define TK_BITNOT 115 +#define TK_ON 116 +#define TK_INDEXED 117 +#define TK_STRING 118 +#define TK_JOIN_KW 119 +#define TK_CONSTRAINT 120 +#define TK_DEFAULT 121 +#define TK_NULL 122 +#define TK_PRIMARY 123 +#define TK_UNIQUE 124 +#define TK_CHECK 125 +#define TK_REFERENCES 126 +#define TK_AUTOINCR 127 +#define TK_INSERT 128 +#define TK_DELETE 129 +#define TK_UPDATE 130 +#define TK_SET 131 +#define TK_DEFERRABLE 132 +#define TK_FOREIGN 133 +#define TK_DROP 134 +#define TK_UNION 135 +#define TK_ALL 136 +#define TK_EXCEPT 137 +#define TK_INTERSECT 138 +#define TK_SELECT 139 +#define TK_VALUES 140 +#define TK_DISTINCT 141 +#define TK_DOT 142 +#define TK_FROM 143 +#define TK_JOIN 144 +#define TK_USING 145 +#define TK_ORDER 146 +#define TK_GROUP 147 +#define TK_HAVING 148 +#define TK_LIMIT 149 +#define TK_WHERE 150 +#define TK_RETURNING 151 +#define TK_INTO 152 +#define TK_NOTHING 153 +#define TK_FLOAT 154 +#define TK_BLOB 155 +#define TK_INTEGER 156 +#define TK_VARIABLE 157 +#define TK_CASE 158 +#define TK_WHEN 159 +#define TK_THEN 160 +#define TK_ELSE 161 +#define TK_INDEX 162 +#define TK_ALTER 163 +#define TK_ADD 164 +#define TK_WINDOW 165 +#define TK_OVER 166 +#define TK_FILTER 167 +#define TK_COLUMN 168 +#define TK_AGG_FUNCTION 169 +#define TK_AGG_COLUMN 170 +#define TK_TRUEFALSE 171 +#define TK_FUNCTION 172 +#define TK_UPLUS 173 +#define TK_UMINUS 174 +#define TK_TRUTH 175 +#define TK_REGISTER 176 +#define TK_VECTOR 177 +#define TK_SELECT_COLUMN 178 +#define TK_IF_NULL_ROW 179 +#define TK_ASTERISK 180 +#define TK_SPAN 181 +#define TK_ERROR 182 +#define TK_QNUMBER 183 +#define TK_SPACE 184 +#define TK_COMMENT 185 +#define TK_ILLEGAL 186 /************** End of parse.h ***********************************************/ /************** Continuing where we left off in sqliteInt.h ******************/ @@ -13655,6 +15671,7 @@ SQLITE_PRIVATE void sqlite3HashClear(Hash*); #include #include #include +#include /* ** Use a macro to replace memcpy() if compiled with SQLITE_INLINE_MEMCPY. @@ -13675,7 +15692,9 @@ SQLITE_PRIVATE void sqlite3HashClear(Hash*); #ifdef SQLITE_OMIT_FLOATING_POINT # define double sqlite_int64 # define float sqlite_int64 -# define LONGDOUBLE_TYPE sqlite_int64 +# define fabs(X) ((X)<0?-(X):(X)) +# define sqlite3IsOverflow(X) 0 +# define INFINITY (9223372036854775807LL) # ifndef SQLITE_BIG_DBL # define SQLITE_BIG_DBL (((sqlite3_int64)1)<<50) # endif @@ -13752,7 +15771,7 @@ SQLITE_PRIVATE void sqlite3HashClear(Hash*); ** number of pages. A negative number N translations means that a buffer ** of -1024*N bytes is allocated and used for as many pages as it will hold. ** -** The default value of "20" was choosen to minimize the run-time of the +** The default value of "20" was chosen to minimize the run-time of the ** speedtest1 test program with options: --shrink-memory --reprepare */ #ifndef SQLITE_DEFAULT_PCACHE_INITSZ @@ -13767,7 +15786,7 @@ SQLITE_PRIVATE void sqlite3HashClear(Hash*); #endif /* -** The compile-time options SQLITE_MMAP_READWRITE and +** The compile-time options SQLITE_MMAP_READWRITE and ** SQLITE_ENABLE_BATCH_ATOMIC_WRITE are not compatible with one another. ** You must choose one or the other (or neither) but not both. */ @@ -13780,7 +15799,24 @@ SQLITE_PRIVATE void sqlite3HashClear(Hash*); ** ourselves. */ #ifndef offsetof -#define offsetof(STRUCTURE,FIELD) ((int)((char*)&((STRUCTURE*)0)->FIELD)) +# define offsetof(ST,M) ((size_t)((char*)&((ST*)0)->M - (char*)0)) +#endif + +/* +** sizeof64() is like sizeof(), but always returns a 64-bit value, even +** on 32-bit builds. This can help to avoid overflow by ensuring 64-bit +** arithmetic is used consistently in both 32-bit and 64-bit builds. +*/ +#define sizeof64(X) ((sqlite3_int64)sizeof(X)) + +/* +** Work around C99 "flex-array" syntax for pre-C99 compilers, so as +** to avoid complaints from -fsanitize=strict-bounds. +*/ +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) +# define FLEXARRAY +#else +# define FLEXARRAY 1 #endif /* @@ -13850,9 +15886,6 @@ SQLITE_PRIVATE void sqlite3HashClear(Hash*); # define INT8_TYPE signed char # endif #endif -#ifndef LONGDOUBLE_TYPE -# define LONGDOUBLE_TYPE long double -#endif typedef sqlite_int64 i64; /* 8-byte signed integer */ typedef sqlite_uint64 u64; /* 8-byte unsigned integer */ typedef UINT32_TYPE u32; /* 4-byte unsigned integer */ @@ -13861,6 +15894,11 @@ typedef INT16_TYPE i16; /* 2-byte signed integer */ typedef UINT8_TYPE u8; /* 1-byte unsigned integer */ typedef INT8_TYPE i8; /* 1-byte signed integer */ +/* A bitfield type for use inside of structures. Always follow with :N where +** N is the number of bits. +*/ +typedef unsigned bft; /* Bit Field Type */ + /* ** SQLITE_MAX_U32 is a u64 constant that is the maximum u64 value ** that can be stored in a u32 without loss of data. The value @@ -13871,15 +15909,9 @@ typedef INT8_TYPE i8; /* 1-byte signed integer */ /* ** The datatype used to store estimates of the number of rows in a -** table or index. This is an unsigned integer type. For 99.9% of -** the world, a 32-bit integer is sufficient. But a 64-bit integer -** can be used at compile-time if desired. +** table or index. */ -#ifdef SQLITE_64BIT_STATS - typedef u64 tRowcnt; /* 64-bit only if requested at compile-time */ -#else - typedef u32 tRowcnt; /* 32-bit is the default */ -#endif +typedef u64 tRowcnt; /* ** Estimated quantities used for query planning are stored as 16-bit @@ -13905,6 +15937,8 @@ typedef INT8_TYPE i8; /* 1-byte signed integer */ ** 0.5 -> -10 0.1 -> -33 0.0625 -> -40 */ typedef INT16_TYPE LogEst; +#define LOGEST_MIN (-32768) +#define LOGEST_MAX (32767) /* ** Set the SQLITE_PTRSIZE macro to the number of bytes in a pointer @@ -13914,6 +15948,7 @@ typedef INT16_TYPE LogEst; # define SQLITE_PTRSIZE __SIZEOF_POINTER__ # elif defined(i386) || defined(__i386__) || defined(_M_IX86) || \ defined(_M_ARM) || defined(__arm__) || defined(__x86) || \ + (defined(__APPLE__) && defined(__ppc__)) || \ (defined(__TOS_AIX__) && !defined(__64BIT__)) # define SQLITE_PTRSIZE 4 # else @@ -13939,8 +15974,31 @@ typedef INT16_TYPE LogEst; ** the end of buffer S. This macro returns true if P points to something ** contained within the buffer S. */ -#define SQLITE_WITHIN(P,S,E) (((uptr)(P)>=(uptr)(S))&&((uptr)(P)<(uptr)(E))) +#define SQLITE_WITHIN(P,S,E) (((uptr)(P)>=(uptr)(S))&&((uptr)(P)<(uptr)(E))) +/* +** P is one byte past the end of a large buffer. Return true if a span of bytes +** between S..E crosses the end of that buffer. In other words, return true +** if the sub-buffer S..E-1 overflows the buffer whose last byte is P-1. +** +** S is the start of the span. E is one byte past the end of end of span. +** +** P +** |-----------------| FALSE +** |-------| +** S E +** +** P +** |-----------------| +** |-------| TRUE +** S E +** +** P +** |-----------------| +** |-------| FALSE +** S E +*/ +#define SQLITE_OVERFLOW(P,S,E) (((uptr)(S)<(uptr)(P))&&((uptr)(E)>(uptr)(P))) /* ** Macros to determine whether the machine is big or little endian, @@ -13950,15 +16008,33 @@ typedef INT16_TYPE LogEst; ** using C-preprocessor macros. If that is unsuccessful, or if ** -DSQLITE_BYTEORDER=0 is set, then byte-order is determined ** at run-time. +** +** If you are building SQLite on some obscure platform for which the +** following ifdef magic does not work, you can always include either: +** +** -DSQLITE_BYTEORDER=1234 +** +** or +** +** -DSQLITE_BYTEORDER=4321 +** +** to cause the build to work for little-endian or big-endian processors, +** respectively. */ -#ifndef SQLITE_BYTEORDER -# if defined(i386) || defined(__i386__) || defined(_M_IX86) || \ - defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || \ - defined(_M_AMD64) || defined(_M_ARM) || defined(__x86) || \ - defined(__arm__) || defined(_M_ARM64) -# define SQLITE_BYTEORDER 1234 -# elif defined(sparc) || defined(__ppc__) -# define SQLITE_BYTEORDER 4321 +#ifndef SQLITE_BYTEORDER /* Replicate changes at tag-20230904a */ +# if defined(__BYTE_ORDER__) && __BYTE_ORDER__==__ORDER_BIG_ENDIAN__ +# define SQLITE_BYTEORDER 4321 +# elif defined(__BYTE_ORDER__) && __BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__ +# define SQLITE_BYTEORDER 1234 +# elif defined(__BIG_ENDIAN__) && __BIG_ENDIAN__==1 +# define SQLITE_BYTEORDER 4321 +# elif defined(i386) || defined(__i386__) || defined(_M_IX86) || \ + defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || \ + defined(_M_AMD64) || defined(_M_ARM) || defined(__x86) || \ + defined(__ARMEL__) || defined(__AARCH64EL__) || defined(_M_ARM64) +# define SQLITE_BYTEORDER 1234 +# elif defined(sparc) || defined(__ARMEB__) || defined(__AARCH64EB__) +# define SQLITE_BYTEORDER 4321 # else # define SQLITE_BYTEORDER 0 # endif @@ -13988,13 +16064,33 @@ typedef INT16_TYPE LogEst; ** compilers. */ #define LARGEST_INT64 (0xffffffff|(((i64)0x7fffffff)<<32)) +#define LARGEST_UINT64 (0xffffffff|(((u64)0xffffffff)<<32)) #define SMALLEST_INT64 (((i64)-1) - LARGEST_INT64) +/* +** Macro SMXV(n) return the maximum value that can be held in variable n, +** assuming n is a signed integer type. UMXV(n) is similar for unsigned +** integer types. +*/ +#define SMXV(n) ((((i64)1)<<(sizeof(n)*8-1))-1) +#define UMXV(n) ((((i64)1)<<(sizeof(n)*8))-1) + /* ** Round up a number to the next larger multiple of 8. This is used ** to force 8-byte alignment on 64-bit architectures. +** +** ROUND8() always does the rounding, for any argument. +** +** ROUND8P() assumes that the argument is already an integer number of +** pointers in size, and so it is a no-op on systems where the pointer +** size is 8. */ #define ROUND8(x) (((x)+7)&~7) +#if SQLITE_PTRSIZE==8 +# define ROUND8P(x) (x) +#else +# define ROUND8P(x) (((x)+7)&~7) +#endif /* ** Round down to the nearest multiple of 8 @@ -14011,10 +16107,11 @@ typedef INT16_TYPE LogEst; ** pointers. In that case, only verify 4-byte alignment. */ #ifdef SQLITE_4_BYTE_ALIGNED_MALLOC -# define EIGHT_BYTE_ALIGNMENT(X) ((((char*)(X) - (char*)0)&3)==0) +# define EIGHT_BYTE_ALIGNMENT(X) ((((uptr)(X) - (uptr)0)&3)==0) #else -# define EIGHT_BYTE_ALIGNMENT(X) ((((char*)(X) - (char*)0)&7)==0) +# define EIGHT_BYTE_ALIGNMENT(X) ((((uptr)(X) - (uptr)0)&7)==0) #endif +#define TWO_BYTE_ALIGNMENT(X) ((((uptr)(X) - (uptr)0)&1)==0) /* ** Disable MMAP on platforms where it is known to not work @@ -14057,28 +16154,96 @@ typedef INT16_TYPE LogEst; #endif /* -** Only one of SQLITE_ENABLE_STAT3 or SQLITE_ENABLE_STAT4 can be defined. -** Priority is given to SQLITE_ENABLE_STAT4. If either are defined, also -** define SQLITE_ENABLE_STAT3_OR_STAT4 +** TREETRACE_ENABLED will be either 1 or 0 depending on whether or not +** the Abstract Syntax Tree tracing logic is turned on. */ -#ifdef SQLITE_ENABLE_STAT4 -# undef SQLITE_ENABLE_STAT3 -# define SQLITE_ENABLE_STAT3_OR_STAT4 1 -#elif SQLITE_ENABLE_STAT3 -# define SQLITE_ENABLE_STAT3_OR_STAT4 1 -#elif SQLITE_ENABLE_STAT3_OR_STAT4 -# undef SQLITE_ENABLE_STAT3_OR_STAT4 +#if !defined(SQLITE_AMALGAMATION) +SQLITE_PRIVATE u32 sqlite3TreeTrace; +#endif +#if defined(SQLITE_DEBUG) \ + && (defined(SQLITE_TEST) || defined(SQLITE_ENABLE_SELECTTRACE) \ + || defined(SQLITE_ENABLE_TREETRACE)) +# define TREETRACE_ENABLED 1 +# define TREETRACE(K,P,S,X) \ + if(sqlite3TreeTrace&(K)) \ + sqlite3DebugPrintf("%u/%d/%p: ",(S)->selId,(P)->addrExplain,(S)),\ + sqlite3DebugPrintf X +#else +# define TREETRACE(K,P,S,X) +# define TREETRACE_ENABLED 0 +#endif + +/* TREETRACE flag meanings: +** +** 0x00000001 Beginning and end of SELECT processing +** 0x00000002 WHERE clause processing +** 0x00000004 Query flattener +** 0x00000008 Result-set wildcard expansion +** 0x00000010 Query name resolution +** 0x00000020 Aggregate analysis +** 0x00000040 Window functions +** 0x00000080 Generated column names +** 0x00000100 Move HAVING terms into WHERE +** 0x00000200 Count-of-view optimization +** 0x00000400 Compound SELECT processing +** 0x00000800 Drop superfluous ORDER BY +** 0x00001000 LEFT JOIN simplifies to JOIN +** 0x00002000 Constant propagation +** 0x00004000 Push-down optimization +** 0x00008000 After all FROM-clause analysis +** 0x00010000 Beginning of DELETE/INSERT/UPDATE processing +** 0x00020000 Transform DISTINCT into GROUP BY +** 0x00040000 SELECT tree dump after all code has been generated +** 0x00080000 NOT NULL strength reduction +** 0x00100000 Pointers are all shown as zero +** 0x00200000 EXISTS-to-JOIN optimization +*/ + +/* +** Macros for "wheretrace" +*/ +SQLITE_PRIVATE u32 sqlite3WhereTrace; +#if defined(SQLITE_DEBUG) \ + && (defined(SQLITE_TEST) || defined(SQLITE_ENABLE_WHERETRACE)) +# define WHERETRACE(K,X) if(sqlite3WhereTrace&(K)) sqlite3DebugPrintf X +# define WHERETRACE_ENABLED 1 +#else +# define WHERETRACE(K,X) #endif /* -** SELECTTRACE_ENABLED will be either 1 or 0 depending on whether or not -** the Select query generator tracing logic is turned on. +** Bits for the sqlite3WhereTrace mask: +** +** (---any--) Top-level block structure +** 0x-------F High-level debug messages +** 0x----FFF- More detail +** 0xFFFF---- Low-level debug messages +** +** 0x00000001 Code generation +** 0x00000002 Solver (Use 0x40000 for less detail) +** 0x00000004 Solver costs +** 0x00000008 WhereLoop inserts +** +** 0x00000010 Display sqlite3_index_info xBestIndex calls +** 0x00000020 Range an equality scan metrics +** 0x00000040 IN operator decisions +** 0x00000080 WhereLoop cost adjustments +** 0x00000100 +** 0x00000200 Covering index decisions +** 0x00000400 OR optimization +** 0x00000800 Index scanner +** 0x00001000 More details associated with code generation +** 0x00002000 +** 0x00004000 Show all WHERE terms at key points +** 0x00008000 Show the full SELECT statement at key places +** +** 0x00010000 Show more detail when printing WHERE terms +** 0x00020000 Show WHERE terms returned from whereScanNext() +** 0x00040000 Solver overview messages +** 0x00080000 Star-query heuristic +** 0x00100000 Pointers are all shown as zero */ -#if defined(SQLITE_ENABLE_SELECTTRACE) -# define SELECTTRACE_ENABLED 1 -#else -# define SELECTTRACE_ENABLED 0 -#endif + /* ** An instance of the following structure is used to store the busy-handler @@ -14094,26 +16259,41 @@ struct BusyHandler { int (*xBusyHandler)(void *,int); /* The busy callback */ void *pBusyArg; /* First arg to busy callback */ int nBusy; /* Incremented with each busy call */ - u8 bExtraFileArg; /* Include sqlite3_file as callback arg */ }; /* -** Name of the master database table. The master database table -** is a special table that holds the names and attributes of all -** user tables and indices. +** Name of table that holds the database schema. +** +** The PREFERRED names are used wherever possible. But LEGACY is also +** used for backwards compatibility. +** +** 1. Queries can use either the PREFERRED or the LEGACY names +** 2. The sqlite3_set_authorizer() callback uses the LEGACY name +** 3. The PRAGMA table_list statement uses the PREFERRED name +** +** The LEGACY names are stored in the internal symbol hash table +** in support of (2). Names are translated using sqlite3PreferredTableName() +** for (3). The sqlite3FindTable() function takes care of translating +** names for (1). +** +** Note that "sqlite_temp_schema" can also be called "temp.sqlite_schema". */ -#define MASTER_NAME "sqlite_master" -#define TEMP_MASTER_NAME "sqlite_temp_master" +#define LEGACY_SCHEMA_TABLE "sqlite_master" +#define LEGACY_TEMP_SCHEMA_TABLE "sqlite_temp_master" +#define PREFERRED_SCHEMA_TABLE "sqlite_schema" +#define PREFERRED_TEMP_SCHEMA_TABLE "sqlite_temp_schema" + /* -** The root-page of the master database table. +** The root-page of the schema table. */ -#define MASTER_ROOT 1 +#define SCHEMA_ROOT 1 /* -** The name of the schema table. +** The name of the schema table. The name is different for TEMP. */ -#define SCHEMA_TABLE(x) ((!OMIT_TEMPDB)&&(x==1)?TEMP_MASTER_NAME:MASTER_NAME) +#define SCHEMA_TABLE(x) \ + ((!OMIT_TEMPDB)&&(x==1)?LEGACY_TEMP_SCHEMA_TABLE:LEGACY_SCHEMA_TABLE) /* ** A convenience macro that returns the number of elements in @@ -14134,7 +16314,7 @@ struct BusyHandler { ** pointer will work here as long as it is distinct from SQLITE_STATIC ** and SQLITE_TRANSIENT. */ -#define SQLITE_DYNAMIC ((sqlite3_destructor_type)sqlite3MallocSize) +#define SQLITE_DYNAMIC ((sqlite3_destructor_type)sqlite3RowSetClear) /* ** When SQLITE_OMIT_WSD is defined, it means that the target platform does @@ -14190,16 +16370,22 @@ typedef struct AutoincInfo AutoincInfo; typedef struct Bitvec Bitvec; typedef struct CollSeq CollSeq; typedef struct Column Column; +typedef struct Cte Cte; +typedef struct CteUse CteUse; typedef struct Db Db; +typedef struct DbClientData DbClientData; +typedef struct DbFixer DbFixer; typedef struct Schema Schema; typedef struct Expr Expr; typedef struct ExprList ExprList; typedef struct FKey FKey; +typedef struct FpDecode FpDecode; typedef struct FuncDestructor FuncDestructor; typedef struct FuncDef FuncDef; typedef struct FuncDefHash FuncDefHash; typedef struct IdList IdList; typedef struct Index Index; +typedef struct IndexedExpr IndexedExpr; typedef struct IndexSample IndexSample; typedef struct KeyClass KeyClass; typedef struct KeyInfo KeyInfo; @@ -14207,15 +16393,21 @@ typedef struct Lookaside Lookaside; typedef struct LookasideSlot LookasideSlot; typedef struct Module Module; typedef struct NameContext NameContext; +typedef struct OnOrUsing OnOrUsing; typedef struct Parse Parse; +typedef struct ParseCleanup ParseCleanup; typedef struct PreUpdate PreUpdate; typedef struct PrintfArguments PrintfArguments; +typedef struct RCStr RCStr; typedef struct RenameToken RenameToken; +typedef struct Returning Returning; typedef struct RowSet RowSet; typedef struct Savepoint Savepoint; typedef struct Select Select; typedef struct SQLiteThread SQLiteThread; typedef struct SelectDest SelectDest; +typedef struct Subquery Subquery; +typedef struct SrcItem SrcItem; typedef struct SrcList SrcList; typedef struct sqlite3_str StrAccum; /* Internal alias for sqlite3_str */ typedef struct Table Table; @@ -14256,9 +16448,12 @@ typedef struct With With; /* ** A bit in a Bitmask */ -#define MASKBIT(n) (((Bitmask)1)<<(n)) -#define MASKBIT32(n) (((unsigned int)1)<<(n)) -#define ALLBITS ((Bitmask)-1) +#define MASKBIT(n) (((Bitmask)1)<<(n)) +#define MASKBIT64(n) (((u64)1)<<(n)) +#define MASKBIT32(n) (((unsigned int)1)<<(n)) +#define SMASKBIT32(n) ((n)<=31?((unsigned int)1)<<(n):0) +#define ALLBITS ((Bitmask)-1) +#define TOPBIT (((Bitmask)1)<<(BMS-1)) /* A VList object records a mapping between parameters/variables/wildcards ** in the SQL statement (such as $abc, @pqr, or :xyz) and the integer @@ -14273,6 +16468,599 @@ typedef int VList; ** "BusyHandler" typedefs. vdbe.h also requires a few of the opaque ** pointer types (i.e. FuncDef) defined above. */ +/************** Include os.h in the middle of sqliteInt.h ********************/ +/************** Begin file os.h **********************************************/ +/* +** 2001 September 16 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This header file (together with is companion C source-code file +** "os.c") attempt to abstract the underlying operating system so that +** the SQLite library will work on both POSIX and windows systems. +** +** This header file is #include-ed by sqliteInt.h and thus ends up +** being included by every source file. +*/ +#ifndef _SQLITE_OS_H_ +#define _SQLITE_OS_H_ + +/* +** Attempt to automatically detect the operating system and setup the +** necessary pre-processor macros for it. +*/ +/************** Include os_setup.h in the middle of os.h *********************/ +/************** Begin file os_setup.h ****************************************/ +/* +** 2013 November 25 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This file contains pre-processor directives related to operating system +** detection and/or setup. +*/ +#ifndef SQLITE_OS_SETUP_H +#define SQLITE_OS_SETUP_H + +/* +** Figure out if we are dealing with Unix, Windows, or some other operating +** system. +** +** After the following block of preprocess macros, all of +** +** SQLITE_OS_KV +** SQLITE_OS_OTHER +** SQLITE_OS_UNIX +** SQLITE_OS_WIN +** +** will defined to either 1 or 0. One of them will be 1. The others will be 0. +** If none of the macros are initially defined, then select either +** SQLITE_OS_UNIX or SQLITE_OS_WIN depending on the target platform. +** +** If SQLITE_OS_OTHER=1 is specified at compile-time, then the application +** must provide its own VFS implementation together with sqlite3_os_init() +** and sqlite3_os_end() routines. +*/ +#if SQLITE_OS_KV+1<=1 && SQLITE_OS_OTHER+1<=1 && \ + SQLITE_OS_WIN+1<=1 && SQLITE_OS_UNIX+1<=1 +# if defined(_WIN32) || defined(WIN32) || defined(__CYGWIN__) || \ + defined(__MINGW32__) || defined(__BORLANDC__) +# define SQLITE_OS_WIN 1 +# define SQLITE_OS_UNIX 0 +# else +# define SQLITE_OS_WIN 0 +# define SQLITE_OS_UNIX 1 +# endif +#endif +#if SQLITE_OS_OTHER+1>1 +# undef SQLITE_OS_KV +# define SQLITE_OS_KV 0 +# undef SQLITE_OS_UNIX +# define SQLITE_OS_UNIX 0 +# undef SQLITE_OS_WIN +# define SQLITE_OS_WIN 0 +#endif +#if SQLITE_OS_KV+1>1 +# undef SQLITE_OS_OTHER +# define SQLITE_OS_OTHER 0 +# undef SQLITE_OS_UNIX +# define SQLITE_OS_UNIX 0 +# undef SQLITE_OS_WIN +# define SQLITE_OS_WIN 0 +# define SQLITE_OMIT_LOAD_EXTENSION 1 +# define SQLITE_OMIT_WAL 1 +# define SQLITE_OMIT_DEPRECATED 1 +# undef SQLITE_TEMP_STORE +# define SQLITE_TEMP_STORE 3 /* Always use memory for temporary storage */ +# define SQLITE_DQS 0 +# define SQLITE_OMIT_SHARED_CACHE 1 +# define SQLITE_OMIT_AUTOINIT 1 +#endif +#if SQLITE_OS_UNIX+1>1 +# undef SQLITE_OS_KV +# define SQLITE_OS_KV 0 +# undef SQLITE_OS_OTHER +# define SQLITE_OS_OTHER 0 +# undef SQLITE_OS_WIN +# define SQLITE_OS_WIN 0 +#endif +#if SQLITE_OS_WIN+1>1 +# undef SQLITE_OS_KV +# define SQLITE_OS_KV 0 +# undef SQLITE_OS_OTHER +# define SQLITE_OS_OTHER 0 +# undef SQLITE_OS_UNIX +# define SQLITE_OS_UNIX 0 +#endif + + +#endif /* SQLITE_OS_SETUP_H */ + +/************** End of os_setup.h ********************************************/ +/************** Continuing where we left off in os.h *************************/ + +/* If the SET_FULLSYNC macro is not defined above, then make it +** a no-op +*/ +#ifndef SET_FULLSYNC +# define SET_FULLSYNC(x,y) +#endif + +/* Maximum pathname length. Note: FILENAME_MAX defined by stdio.h +*/ +#ifndef SQLITE_MAX_PATHLEN +# define SQLITE_MAX_PATHLEN FILENAME_MAX +#endif + +/* Maximum number of symlinks that will be resolved while trying to +** expand a filename in xFullPathname() in the VFS. +*/ +#ifndef SQLITE_MAX_SYMLINK +# define SQLITE_MAX_SYMLINK 200 +#endif + +/* +** The default size of a disk sector +*/ +#ifndef SQLITE_DEFAULT_SECTOR_SIZE +# define SQLITE_DEFAULT_SECTOR_SIZE 4096 +#endif + +/* +** Temporary files are named starting with this prefix followed by 16 random +** alphanumeric characters, and no file extension. They are stored in the +** OS's standard temporary file directory, and are deleted prior to exit. +** If sqlite is being embedded in another program, you may wish to change the +** prefix to reflect your program's name, so that if your program exits +** prematurely, old temporary files can be easily identified. This can be done +** using -DSQLITE_TEMP_FILE_PREFIX=myprefix_ on the compiler command line. +** +** 2006-10-31: The default prefix used to be "sqlite_". But then +** Mcafee started using SQLite in their anti-virus product and it +** started putting files with the "sqlite" name in the c:/temp folder. +** This annoyed many windows users. Those users would then do a +** Google search for "sqlite", find the telephone numbers of the +** developers and call to wake them up at night and complain. +** For this reason, the default name prefix is changed to be "sqlite" +** spelled backwards. So the temp files are still identified, but +** anybody smart enough to figure out the code is also likely smart +** enough to know that calling the developer will not help get rid +** of the file. +*/ +#ifndef SQLITE_TEMP_FILE_PREFIX +# define SQLITE_TEMP_FILE_PREFIX "etilqs_" +#endif + +/* +** The following values may be passed as the second argument to +** sqlite3OsLock(). The various locks exhibit the following semantics: +** +** SHARED: Any number of processes may hold a SHARED lock simultaneously. +** RESERVED: A single process may hold a RESERVED lock on a file at +** any time. Other processes may hold and obtain new SHARED locks. +** PENDING: A single process may hold a PENDING lock on a file at +** any one time. Existing SHARED locks may persist, but no new +** SHARED locks may be obtained by other processes. +** EXCLUSIVE: An EXCLUSIVE lock precludes all other locks. +** +** PENDING_LOCK may not be passed directly to sqlite3OsLock(). Instead, a +** process that requests an EXCLUSIVE lock may actually obtain a PENDING +** lock. This can be upgraded to an EXCLUSIVE lock by a subsequent call to +** sqlite3OsLock(). +*/ +#define NO_LOCK 0 +#define SHARED_LOCK 1 +#define RESERVED_LOCK 2 +#define PENDING_LOCK 3 +#define EXCLUSIVE_LOCK 4 + +/* +** File Locking Notes: (Mostly about windows but also some info for Unix) +** +** We cannot use LockFileEx() or UnlockFileEx() on Win95/98/ME because +** those functions are not available. So we use only LockFile() and +** UnlockFile(). +** +** LockFile() prevents not just writing but also reading by other processes. +** A SHARED_LOCK is obtained by locking a single randomly-chosen +** byte out of a specific range of bytes. The lock byte is obtained at +** random so two separate readers can probably access the file at the +** same time, unless they are unlucky and choose the same lock byte. +** An EXCLUSIVE_LOCK is obtained by locking all bytes in the range. +** There can only be one writer. A RESERVED_LOCK is obtained by locking +** a single byte of the file that is designated as the reserved lock byte. +** A PENDING_LOCK is obtained by locking a designated byte different from +** the RESERVED_LOCK byte. +** +** On WinNT/2K/XP systems, LockFileEx() and UnlockFileEx() are available, +** which means we can use reader/writer locks. When reader/writer locks +** are used, the lock is placed on the same range of bytes that is used +** for probabilistic locking in Win95/98/ME. Hence, the locking scheme +** will support two or more Win95 readers or two or more WinNT readers. +** But a single Win95 reader will lock out all WinNT readers and a single +** WinNT reader will lock out all other Win95 readers. +** +** The following #defines specify the range of bytes used for locking. +** SHARED_SIZE is the number of bytes available in the pool from which +** a random byte is selected for a shared lock. The pool of bytes for +** shared locks begins at SHARED_FIRST. +** +** The same locking strategy and +** byte ranges are used for Unix. This leaves open the possibility of having +** clients on win95, winNT, and unix all talking to the same shared file +** and all locking correctly. To do so would require that samba (or whatever +** tool is being used for file sharing) implements locks correctly between +** windows and unix. I'm guessing that isn't likely to happen, but by +** using the same locking range we are at least open to the possibility. +** +** Locking in windows is manditory. For this reason, we cannot store +** actual data in the bytes used for locking. The pager never allocates +** the pages involved in locking therefore. SHARED_SIZE is selected so +** that all locks will fit on a single page even at the minimum page size. +** PENDING_BYTE defines the beginning of the locks. By default PENDING_BYTE +** is set high so that we don't have to allocate an unused page except +** for very large databases. But one should test the page skipping logic +** by setting PENDING_BYTE low and running the entire regression suite. +** +** Changing the value of PENDING_BYTE results in a subtly incompatible +** file format. Depending on how it is changed, you might not notice +** the incompatibility right away, even running a full regression test. +** The default location of PENDING_BYTE is the first byte past the +** 1GB boundary. +** +*/ +#ifdef SQLITE_OMIT_WSD +# define PENDING_BYTE (0x40000000) +#else +# define PENDING_BYTE sqlite3PendingByte +#endif +#define RESERVED_BYTE (PENDING_BYTE+1) +#define SHARED_FIRST (PENDING_BYTE+2) +#define SHARED_SIZE 510 + +/* +** Wrapper around OS specific sqlite3_os_init() function. +*/ +SQLITE_PRIVATE int sqlite3OsInit(void); + +/* +** Functions for accessing sqlite3_file methods +*/ +SQLITE_PRIVATE void sqlite3OsClose(sqlite3_file*); +SQLITE_PRIVATE int sqlite3OsRead(sqlite3_file*, void*, int amt, i64 offset); +SQLITE_PRIVATE int sqlite3OsWrite(sqlite3_file*, const void*, int amt, i64 offset); +SQLITE_PRIVATE int sqlite3OsTruncate(sqlite3_file*, i64 size); +SQLITE_PRIVATE int sqlite3OsSync(sqlite3_file*, int); +SQLITE_PRIVATE int sqlite3OsFileSize(sqlite3_file*, i64 *pSize); +SQLITE_PRIVATE int sqlite3OsLock(sqlite3_file*, int); +SQLITE_PRIVATE int sqlite3OsUnlock(sqlite3_file*, int); +SQLITE_PRIVATE int sqlite3OsCheckReservedLock(sqlite3_file *id, int *pResOut); +SQLITE_PRIVATE int sqlite3OsFileControl(sqlite3_file*,int,void*); +SQLITE_PRIVATE void sqlite3OsFileControlHint(sqlite3_file*,int,void*); +#define SQLITE_FCNTL_DB_UNCHANGED 0xca093fa0 +SQLITE_PRIVATE int sqlite3OsSectorSize(sqlite3_file *id); +SQLITE_PRIVATE int sqlite3OsDeviceCharacteristics(sqlite3_file *id); +#ifndef SQLITE_OMIT_WAL +SQLITE_PRIVATE int sqlite3OsShmMap(sqlite3_file *,int,int,int,void volatile **); +SQLITE_PRIVATE int sqlite3OsShmLock(sqlite3_file *id, int, int, int); +SQLITE_PRIVATE void sqlite3OsShmBarrier(sqlite3_file *id); +SQLITE_PRIVATE int sqlite3OsShmUnmap(sqlite3_file *id, int); +#endif /* SQLITE_OMIT_WAL */ +SQLITE_PRIVATE int sqlite3OsFetch(sqlite3_file *id, i64, int, void **); +SQLITE_PRIVATE int sqlite3OsUnfetch(sqlite3_file *, i64, void *); + + +/* +** Functions for accessing sqlite3_vfs methods +*/ +SQLITE_PRIVATE int sqlite3OsOpen(sqlite3_vfs *, const char *, sqlite3_file*, int, int *); +SQLITE_PRIVATE int sqlite3OsDelete(sqlite3_vfs *, const char *, int); +SQLITE_PRIVATE int sqlite3OsAccess(sqlite3_vfs *, const char *, int, int *pResOut); +SQLITE_PRIVATE int sqlite3OsFullPathname(sqlite3_vfs *, const char *, int, char *); +#ifndef SQLITE_OMIT_LOAD_EXTENSION +SQLITE_PRIVATE void *sqlite3OsDlOpen(sqlite3_vfs *, const char *); +SQLITE_PRIVATE void sqlite3OsDlError(sqlite3_vfs *, int, char *); +SQLITE_PRIVATE void (*sqlite3OsDlSym(sqlite3_vfs *, void *, const char *))(void); +SQLITE_PRIVATE void sqlite3OsDlClose(sqlite3_vfs *, void *); +#endif /* SQLITE_OMIT_LOAD_EXTENSION */ +SQLITE_PRIVATE int sqlite3OsRandomness(sqlite3_vfs *, int, char *); +SQLITE_PRIVATE int sqlite3OsSleep(sqlite3_vfs *, int); +SQLITE_PRIVATE int sqlite3OsGetLastError(sqlite3_vfs*); +SQLITE_PRIVATE int sqlite3OsCurrentTimeInt64(sqlite3_vfs *, sqlite3_int64*); + +/* +** Convenience functions for opening and closing files using +** sqlite3_malloc() to obtain space for the file-handle structure. +*/ +SQLITE_PRIVATE int sqlite3OsOpenMalloc(sqlite3_vfs *, const char *, sqlite3_file **, int,int*); +SQLITE_PRIVATE void sqlite3OsCloseFree(sqlite3_file *); + +#endif /* _SQLITE_OS_H_ */ + +/************** End of os.h **************************************************/ +/************** Continuing where we left off in sqliteInt.h ******************/ +/************** Include pager.h in the middle of sqliteInt.h *****************/ +/************** Begin file pager.h *******************************************/ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This header file defines the interface that the sqlite page cache +** subsystem. The page cache subsystem reads and writes a file a page +** at a time and provides a journal for rollback. +*/ + +#ifndef SQLITE_PAGER_H +#define SQLITE_PAGER_H + +/* +** Default maximum size for persistent journal files. A negative +** value means no limit. This value may be overridden using the +** sqlite3PagerJournalSizeLimit() API. See also "PRAGMA journal_size_limit". +*/ +#ifndef SQLITE_DEFAULT_JOURNAL_SIZE_LIMIT + #define SQLITE_DEFAULT_JOURNAL_SIZE_LIMIT -1 +#endif + +/* +** The type used to represent a page number. The first page in a file +** is called page 1. 0 is used to represent "not a page". +*/ +typedef u32 Pgno; + +/* +** Each open file is managed by a separate instance of the "Pager" structure. +*/ +typedef struct Pager Pager; + +/* +** Handle type for pages. +*/ +typedef struct PgHdr DbPage; + +/* +** Page number PAGER_SJ_PGNO is never used in an SQLite database (it is +** reserved for working around a windows/posix incompatibility). It is +** used in the journal to signify that the remainder of the journal file +** is devoted to storing a super-journal name - there are no more pages to +** roll back. See comments for function writeSuperJournal() in pager.c +** for details. +*/ +#define PAGER_SJ_PGNO_COMPUTED(x) ((Pgno)((PENDING_BYTE/((x)->pageSize))+1)) +#define PAGER_SJ_PGNO(x) ((x)->lckPgno) + +/* +** Allowed values for the flags parameter to sqlite3PagerOpen(). +** +** NOTE: These values must match the corresponding BTREE_ values in btree.h. +*/ +#define PAGER_OMIT_JOURNAL 0x0001 /* Do not use a rollback journal */ +#define PAGER_MEMORY 0x0002 /* In-memory database */ + +/* +** Valid values for the second argument to sqlite3PagerLockingMode(). +*/ +#define PAGER_LOCKINGMODE_QUERY -1 +#define PAGER_LOCKINGMODE_NORMAL 0 +#define PAGER_LOCKINGMODE_EXCLUSIVE 1 + +/* +** Numeric constants that encode the journalmode. +** +** The numeric values encoded here (other than PAGER_JOURNALMODE_QUERY) +** are exposed in the API via the "PRAGMA journal_mode" command and +** therefore cannot be changed without a compatibility break. +*/ +#define PAGER_JOURNALMODE_QUERY (-1) /* Query the value of journalmode */ +#define PAGER_JOURNALMODE_DELETE 0 /* Commit by deleting journal file */ +#define PAGER_JOURNALMODE_PERSIST 1 /* Commit by zeroing journal header */ +#define PAGER_JOURNALMODE_OFF 2 /* Journal omitted. */ +#define PAGER_JOURNALMODE_TRUNCATE 3 /* Commit by truncating journal */ +#define PAGER_JOURNALMODE_MEMORY 4 /* In-memory journal file */ +#define PAGER_JOURNALMODE_WAL 5 /* Use write-ahead logging */ + +#define isWalMode(x) ((x)==PAGER_JOURNALMODE_WAL) + +/* +** The argument to this macro is a file descriptor (type sqlite3_file*). +** Return 0 if it is not open, or non-zero (but not 1) if it is. +** +** This is so that expressions can be written as: +** +** if( isOpen(pPager->jfd) ){ ... +** +** instead of +** +** if( pPager->jfd->pMethods ){ ... +*/ +#define isOpen(pFd) ((pFd)->pMethods!=0) + +/* +** Flags that make up the mask passed to sqlite3PagerGet(). +*/ +#define PAGER_GET_NOCONTENT 0x01 /* Do not load data from disk */ +#define PAGER_GET_READONLY 0x02 /* Read-only page is acceptable */ + +/* +** Flags for sqlite3PagerSetFlags() +** +** Value constraints (enforced via assert()): +** PAGER_FULLFSYNC == SQLITE_FullFSync +** PAGER_CKPT_FULLFSYNC == SQLITE_CkptFullFSync +** PAGER_CACHE_SPILL == SQLITE_CacheSpill +*/ +#define PAGER_SYNCHRONOUS_OFF 0x01 /* PRAGMA synchronous=OFF */ +#define PAGER_SYNCHRONOUS_NORMAL 0x02 /* PRAGMA synchronous=NORMAL */ +#define PAGER_SYNCHRONOUS_FULL 0x03 /* PRAGMA synchronous=FULL */ +#define PAGER_SYNCHRONOUS_EXTRA 0x04 /* PRAGMA synchronous=EXTRA */ +#define PAGER_SYNCHRONOUS_MASK 0x07 /* Mask for four values above */ +#define PAGER_FULLFSYNC 0x08 /* PRAGMA fullfsync=ON */ +#define PAGER_CKPT_FULLFSYNC 0x10 /* PRAGMA checkpoint_fullfsync=ON */ +#define PAGER_CACHESPILL 0x20 /* PRAGMA cache_spill=ON */ +#define PAGER_FLAGS_MASK 0x38 /* All above except SYNCHRONOUS */ + +/* +** The remainder of this file contains the declarations of the functions +** that make up the Pager sub-system API. See source code comments for +** a detailed description of each routine. +*/ + +/* Open and close a Pager connection. */ +SQLITE_PRIVATE int sqlite3PagerOpen( + sqlite3_vfs*, + Pager **ppPager, + const char*, + int, + int, + int, + void(*)(DbPage*) +); +SQLITE_PRIVATE int sqlite3PagerClose(Pager *pPager, sqlite3*); +SQLITE_PRIVATE int sqlite3PagerReadFileheader(Pager*, int, unsigned char*); + +/* Functions used to configure a Pager object. */ +SQLITE_PRIVATE void sqlite3PagerSetBusyHandler(Pager*, int(*)(void *), void *); +SQLITE_PRIVATE int sqlite3PagerSetPagesize(Pager*, u32*, int); +SQLITE_PRIVATE Pgno sqlite3PagerMaxPageCount(Pager*, Pgno); +SQLITE_PRIVATE void sqlite3PagerSetCachesize(Pager*, int); +SQLITE_PRIVATE int sqlite3PagerSetSpillsize(Pager*, int); +SQLITE_PRIVATE void sqlite3PagerSetMmapLimit(Pager *, sqlite3_int64); +SQLITE_PRIVATE void sqlite3PagerShrink(Pager*); +SQLITE_PRIVATE void sqlite3PagerSetFlags(Pager*,unsigned); +SQLITE_PRIVATE int sqlite3PagerLockingMode(Pager *, int); +SQLITE_PRIVATE int sqlite3PagerSetJournalMode(Pager *, int); +SQLITE_PRIVATE int sqlite3PagerGetJournalMode(Pager*); +SQLITE_PRIVATE int sqlite3PagerOkToChangeJournalMode(Pager*); +SQLITE_PRIVATE i64 sqlite3PagerJournalSizeLimit(Pager *, i64); +SQLITE_PRIVATE sqlite3_backup **sqlite3PagerBackupPtr(Pager*); +SQLITE_PRIVATE int sqlite3PagerFlush(Pager*); + +/* Functions used to obtain and release page references. */ +SQLITE_PRIVATE int sqlite3PagerGet(Pager *pPager, Pgno pgno, DbPage **ppPage, int clrFlag); +SQLITE_PRIVATE DbPage *sqlite3PagerLookup(Pager *pPager, Pgno pgno); +SQLITE_PRIVATE void sqlite3PagerRef(DbPage*); +SQLITE_PRIVATE void sqlite3PagerUnref(DbPage*); +SQLITE_PRIVATE void sqlite3PagerUnrefNotNull(DbPage*); +SQLITE_PRIVATE void sqlite3PagerUnrefPageOne(DbPage*); + +/* Operations on page references. */ +SQLITE_PRIVATE int sqlite3PagerWrite(DbPage*); +SQLITE_PRIVATE void sqlite3PagerDontWrite(DbPage*); +SQLITE_PRIVATE int sqlite3PagerMovepage(Pager*,DbPage*,Pgno,int); +SQLITE_PRIVATE int sqlite3PagerPageRefcount(DbPage*); +SQLITE_PRIVATE void *sqlite3PagerGetData(DbPage *); +SQLITE_PRIVATE void *sqlite3PagerGetExtra(DbPage *); + +/* Functions used to manage pager transactions and savepoints. */ +SQLITE_PRIVATE void sqlite3PagerPagecount(Pager*, int*); +SQLITE_PRIVATE int sqlite3PagerBegin(Pager*, int exFlag, int); +SQLITE_PRIVATE int sqlite3PagerCommitPhaseOne(Pager*,const char *zSuper, int); +SQLITE_PRIVATE int sqlite3PagerExclusiveLock(Pager*); +SQLITE_PRIVATE int sqlite3PagerSync(Pager *pPager, const char *zSuper); +SQLITE_PRIVATE int sqlite3PagerCommitPhaseTwo(Pager*); +SQLITE_PRIVATE int sqlite3PagerRollback(Pager*); +SQLITE_PRIVATE int sqlite3PagerOpenSavepoint(Pager *pPager, int n); +SQLITE_PRIVATE int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint); +SQLITE_PRIVATE int sqlite3PagerSharedLock(Pager *pPager); + +#ifndef SQLITE_OMIT_WAL +SQLITE_PRIVATE int sqlite3PagerCheckpoint(Pager *pPager, sqlite3*, int, int*, int*); +SQLITE_PRIVATE int sqlite3PagerWalSupported(Pager *pPager); +SQLITE_PRIVATE int sqlite3PagerWalCallback(Pager *pPager); +SQLITE_PRIVATE int sqlite3PagerOpenWal(Pager *pPager, int *pisOpen); +SQLITE_PRIVATE int sqlite3PagerCloseWal(Pager *pPager, sqlite3*); +# ifdef SQLITE_ENABLE_SNAPSHOT +SQLITE_PRIVATE int sqlite3PagerSnapshotGet(Pager*, sqlite3_snapshot **ppSnapshot); +SQLITE_PRIVATE int sqlite3PagerSnapshotOpen(Pager*, sqlite3_snapshot *pSnapshot); +SQLITE_PRIVATE int sqlite3PagerSnapshotRecover(Pager *pPager); +SQLITE_PRIVATE int sqlite3PagerSnapshotCheck(Pager *pPager, sqlite3_snapshot *pSnapshot); +SQLITE_PRIVATE void sqlite3PagerSnapshotUnlock(Pager *pPager); +# endif +#endif + +#if !defined(SQLITE_OMIT_WAL) && defined(SQLITE_ENABLE_SETLK_TIMEOUT) +SQLITE_PRIVATE int sqlite3PagerWalWriteLock(Pager*, int); +SQLITE_PRIVATE void sqlite3PagerWalDb(Pager*, sqlite3*); +#else +# define sqlite3PagerWalWriteLock(y,z) SQLITE_OK +# define sqlite3PagerWalDb(x,y) +#endif + +#ifdef SQLITE_DIRECT_OVERFLOW_READ +SQLITE_PRIVATE int sqlite3PagerDirectReadOk(Pager *pPager, Pgno pgno); +#endif + +#ifdef SQLITE_ENABLE_ZIPVFS +SQLITE_PRIVATE int sqlite3PagerWalFramesize(Pager *pPager); +#endif + +/* Functions used to query pager state and configuration. */ +SQLITE_PRIVATE u8 sqlite3PagerIsreadonly(Pager*); +SQLITE_PRIVATE u32 sqlite3PagerDataVersion(Pager*); +#ifdef SQLITE_DEBUG +SQLITE_PRIVATE int sqlite3PagerRefcount(Pager*); +#endif +SQLITE_PRIVATE int sqlite3PagerMemUsed(Pager*); +SQLITE_PRIVATE const char *sqlite3PagerFilename(const Pager*, int); +SQLITE_PRIVATE sqlite3_vfs *sqlite3PagerVfs(Pager*); +SQLITE_PRIVATE sqlite3_file *sqlite3PagerFile(Pager*); +SQLITE_PRIVATE sqlite3_file *sqlite3PagerJrnlFile(Pager*); +SQLITE_PRIVATE const char *sqlite3PagerJournalname(Pager*); +SQLITE_PRIVATE void *sqlite3PagerTempSpace(Pager*); +SQLITE_PRIVATE int sqlite3PagerIsMemdb(Pager*); +SQLITE_PRIVATE void sqlite3PagerCacheStat(Pager *, int, int, u64*); +SQLITE_PRIVATE void sqlite3PagerClearCache(Pager*); +SQLITE_PRIVATE int sqlite3SectorSize(sqlite3_file *); + +/* Functions used to truncate the database file. */ +SQLITE_PRIVATE void sqlite3PagerTruncateImage(Pager*,Pgno); + +SQLITE_PRIVATE void sqlite3PagerRekey(DbPage*, Pgno, u16); + +/* Functions to support testing and debugging. */ +#if !defined(NDEBUG) || defined(SQLITE_TEST) +SQLITE_PRIVATE Pgno sqlite3PagerPagenumber(DbPage*); +SQLITE_PRIVATE int sqlite3PagerIswriteable(DbPage*); +#endif +#ifdef SQLITE_TEST +SQLITE_PRIVATE int *sqlite3PagerStats(Pager*); +SQLITE_PRIVATE void sqlite3PagerRefdump(Pager*); + void disable_simulated_io_errors(void); + void enable_simulated_io_errors(void); +#else +# define disable_simulated_io_errors() +# define enable_simulated_io_errors() +#endif + +#if defined(SQLITE_USE_SEH) && !defined(SQLITE_OMIT_WAL) +SQLITE_PRIVATE int sqlite3PagerWalSystemErrno(Pager*); +#endif + +#endif /* SQLITE_PAGER_H */ + +/************** End of pager.h ***********************************************/ +/************** Continuing where we left off in sqliteInt.h ******************/ /************** Include btree.h in the middle of sqliteInt.h *****************/ /************** Begin file btree.h *******************************************/ /* @@ -14348,33 +17136,41 @@ SQLITE_PRIVATE int sqlite3BtreeSetMmapLimit(Btree*,sqlite3_int64); SQLITE_PRIVATE int sqlite3BtreeSetPagerFlags(Btree*,unsigned); SQLITE_PRIVATE int sqlite3BtreeSetPageSize(Btree *p, int nPagesize, int nReserve, int eFix); SQLITE_PRIVATE int sqlite3BtreeGetPageSize(Btree*); -SQLITE_PRIVATE int sqlite3BtreeMaxPageCount(Btree*,int); -SQLITE_PRIVATE u32 sqlite3BtreeLastPage(Btree*); +SQLITE_PRIVATE Pgno sqlite3BtreeMaxPageCount(Btree*,Pgno); +SQLITE_PRIVATE Pgno sqlite3BtreeLastPage(Btree*); SQLITE_PRIVATE int sqlite3BtreeSecureDelete(Btree*,int); -SQLITE_PRIVATE int sqlite3BtreeGetOptimalReserve(Btree*); +SQLITE_PRIVATE int sqlite3BtreeGetRequestedReserve(Btree*); SQLITE_PRIVATE int sqlite3BtreeGetReserveNoMutex(Btree *p); SQLITE_PRIVATE int sqlite3BtreeSetAutoVacuum(Btree *, int); SQLITE_PRIVATE int sqlite3BtreeGetAutoVacuum(Btree *); SQLITE_PRIVATE int sqlite3BtreeBeginTrans(Btree*,int,int*); -SQLITE_PRIVATE int sqlite3BtreeCommitPhaseOne(Btree*, const char *zMaster); +SQLITE_PRIVATE int sqlite3BtreeCommitPhaseOne(Btree*, const char*); SQLITE_PRIVATE int sqlite3BtreeCommitPhaseTwo(Btree*, int); SQLITE_PRIVATE int sqlite3BtreeCommit(Btree*); SQLITE_PRIVATE int sqlite3BtreeRollback(Btree*,int,int); SQLITE_PRIVATE int sqlite3BtreeBeginStmt(Btree*,int); -SQLITE_PRIVATE int sqlite3BtreeCreateTable(Btree*, int*, int flags); -SQLITE_PRIVATE int sqlite3BtreeIsInTrans(Btree*); -SQLITE_PRIVATE int sqlite3BtreeIsInReadTrans(Btree*); +SQLITE_PRIVATE int sqlite3BtreeCreateTable(Btree*, Pgno*, int flags); +SQLITE_PRIVATE int sqlite3BtreeTxnState(Btree*); SQLITE_PRIVATE int sqlite3BtreeIsInBackup(Btree*); + SQLITE_PRIVATE void *sqlite3BtreeSchema(Btree *, int, void(*)(void *)); SQLITE_PRIVATE int sqlite3BtreeSchemaLocked(Btree *pBtree); #ifndef SQLITE_OMIT_SHARED_CACHE SQLITE_PRIVATE int sqlite3BtreeLockTable(Btree *pBtree, int iTab, u8 isWriteLock); #endif + +/* Savepoints are named, nestable SQL transactions mostly implemented */ +/* in vdbe.c and pager.c See https://sqlite.org/lang_savepoint.html */ SQLITE_PRIVATE int sqlite3BtreeSavepoint(Btree *, int, int); +/* "Checkpoint" only refers to WAL. See https://sqlite.org/wal.html#ckpt */ +#ifndef SQLITE_OMIT_WAL +SQLITE_PRIVATE int sqlite3BtreeCheckpoint(Btree*, int, int *, int *); +#endif + SQLITE_PRIVATE const char *sqlite3BtreeGetFilename(Btree *); SQLITE_PRIVATE const char *sqlite3BtreeGetJournalname(Btree *); -SQLITE_PRIVATE int sqlite3BtreeCopyFile(Btree *, Btree *); +SQLITE_PRIVATE int sqlite3BtreeCopyFile(Btree*, Btree*); SQLITE_PRIVATE int sqlite3BtreeIncrVacuum(Btree *); @@ -14392,7 +17188,7 @@ SQLITE_PRIVATE int sqlite3BtreeIncrVacuum(Btree *); #define BTREE_BLOBKEY 2 /* Table has keys only - no data */ SQLITE_PRIVATE int sqlite3BtreeDropTable(Btree*, int, int*); -SQLITE_PRIVATE int sqlite3BtreeClearTable(Btree*, int, int*); +SQLITE_PRIVATE int sqlite3BtreeClearTable(Btree*, int, i64*); SQLITE_PRIVATE int sqlite3BtreeClearTableOfCursor(BtCursor*); SQLITE_PRIVATE int sqlite3BtreeTripAllCursors(Btree*, int, int); @@ -14403,7 +17199,7 @@ SQLITE_PRIVATE int sqlite3BtreeNewDb(Btree *p); /* ** The second parameter to sqlite3BtreeGetMeta or sqlite3BtreeUpdateMeta -** should be one of the following values. The integer values are assigned +** should be one of the following values. The integer values are assigned ** to constants so that the offset of the corresponding field in an ** SQLite database header may be found using the following formula: ** @@ -14452,7 +17248,7 @@ SQLITE_PRIVATE int sqlite3BtreeNewDb(Btree *p); ** reduce network bandwidth. ** ** Note that BTREE_HINT_FLAGS with BTREE_BULKLOAD is the only hint used by -** standard SQLite. The other hints are provided for extentions that use +** standard SQLite. The other hints are provided for extensions that use ** the SQLite parser and code generator but substitute their own storage ** engine. */ @@ -14474,7 +17270,7 @@ SQLITE_PRIVATE int sqlite3BtreeNewDb(Btree *p); #define BTREE_BULKLOAD 0x00000001 /* Used to full index in sorted order */ #define BTREE_SEEK_EQ 0x00000002 /* EQ seeks only - no range seeks */ -/* +/* ** Flags passed as the third argument to sqlite3BtreeCursor(). ** ** For read-only cursors the wrFlag argument is always zero. For read-write @@ -14502,13 +17298,16 @@ SQLITE_PRIVATE int sqlite3BtreeNewDb(Btree *p); SQLITE_PRIVATE int sqlite3BtreeCursor( Btree*, /* BTree containing table to open */ - int iTable, /* Index of root page */ + Pgno iTable, /* Index of root page */ int wrFlag, /* 1 for writing. 0 for read-only */ struct KeyInfo*, /* First argument to compare function */ BtCursor *pCursor /* Space to write cursor structure */ ); SQLITE_PRIVATE BtCursor *sqlite3BtreeFakeValidCursor(void); SQLITE_PRIVATE int sqlite3BtreeCursorSize(void); +#ifdef SQLITE_DEBUG +SQLITE_PRIVATE int sqlite3BtreeClosesWithCursor(Btree*,BtCursor*); +#endif SQLITE_PRIVATE void sqlite3BtreeCursorZero(BtCursor*); SQLITE_PRIVATE void sqlite3BtreeCursorHintFlags(BtCursor*, unsigned); #ifdef SQLITE_ENABLE_CURSOR_HINTS @@ -14516,13 +17315,17 @@ SQLITE_PRIVATE void sqlite3BtreeCursorHint(BtCursor*, int, ...); #endif SQLITE_PRIVATE int sqlite3BtreeCloseCursor(BtCursor*); -SQLITE_PRIVATE int sqlite3BtreeMovetoUnpacked( +SQLITE_PRIVATE int sqlite3BtreeTableMoveto( BtCursor*, - UnpackedRecord *pUnKey, i64 intKey, int bias, int *pRes ); +SQLITE_PRIVATE int sqlite3BtreeIndexMoveto( + BtCursor*, + UnpackedRecord *pUnKey, + int *pRes +); SQLITE_PRIVATE int sqlite3BtreeCursorHasMoved(BtCursor*); SQLITE_PRIVATE int sqlite3BtreeCursorRestore(BtCursor*, int*); SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor*, u8 flags); @@ -14531,6 +17334,7 @@ SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor*, u8 flags); #define BTREE_SAVEPOSITION 0x02 /* Leave cursor pointing at NEXT or PREV */ #define BTREE_AUXDELETE 0x04 /* not the primary delete operation */ #define BTREE_APPEND 0x08 /* Insert is likely an append */ +#define BTREE_PREFORMAT 0x80 /* Inserted data is a preformated cell */ /* An instance of the BtreePayload object describes the content of a single ** entry in either an index or table btree. @@ -14542,7 +17346,7 @@ SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor*, u8 flags); ** The nMem field might be zero, indicating that no decomposition is available. ** ** Table btrees (used for rowid tables) contain an integer rowid used as -** the key and passed in the nKey field. The pKey field is zero. +** the key and passed in the nKey field. The pKey field is zero. ** pData,nData hold the content of the new entry. nZero extra zero bytes ** are appended to the end of the content when constructing the entry. ** The aMem,nMem fields are uninitialized for table btrees. @@ -14561,7 +17365,7 @@ SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor*, u8 flags); ** ** This object is used to pass information into sqlite3BtreeInsert(). The ** same information used to be passed as five separate parameters. But placing -** the information into this object helps to keep the interface more +** the information into this object helps to keep the interface more ** organized and understandable, and it also helps the resulting code to ** run a little faster by using fewer registers for parameter passing. */ @@ -14578,20 +17382,30 @@ struct BtreePayload { SQLITE_PRIVATE int sqlite3BtreeInsert(BtCursor*, const BtreePayload *pPayload, int flags, int seekResult); SQLITE_PRIVATE int sqlite3BtreeFirst(BtCursor*, int *pRes); +SQLITE_PRIVATE int sqlite3BtreeIsEmpty(BtCursor *pCur, int *pRes); SQLITE_PRIVATE int sqlite3BtreeLast(BtCursor*, int *pRes); SQLITE_PRIVATE int sqlite3BtreeNext(BtCursor*, int flags); SQLITE_PRIVATE int sqlite3BtreeEof(BtCursor*); SQLITE_PRIVATE int sqlite3BtreePrevious(BtCursor*, int flags); SQLITE_PRIVATE i64 sqlite3BtreeIntegerKey(BtCursor*); -#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC +SQLITE_PRIVATE void sqlite3BtreeCursorPin(BtCursor*); +SQLITE_PRIVATE void sqlite3BtreeCursorUnpin(BtCursor*); SQLITE_PRIVATE i64 sqlite3BtreeOffset(BtCursor*); -#endif SQLITE_PRIVATE int sqlite3BtreePayload(BtCursor*, u32 offset, u32 amt, void*); SQLITE_PRIVATE const void *sqlite3BtreePayloadFetch(BtCursor*, u32 *pAmt); SQLITE_PRIVATE u32 sqlite3BtreePayloadSize(BtCursor*); SQLITE_PRIVATE sqlite3_int64 sqlite3BtreeMaxRecordSize(BtCursor*); -SQLITE_PRIVATE char *sqlite3BtreeIntegrityCheck(Btree*, int *aRoot, int nRoot, int, int*); +SQLITE_PRIVATE int sqlite3BtreeIntegrityCheck( + sqlite3 *db, /* Database connection that is running the check */ + Btree *p, /* The btree to be checked */ + Pgno *aRoot, /* An array of root pages numbers for individual trees */ + sqlite3_value *aCnt, /* OUT: entry counts for each btree in aRoot[] */ + int nRoot, /* Number of entries in aRoot[] */ + int mxErr, /* Stop reporting errors after this many */ + int *pnErr, /* OUT: Write number of errors seen to this variable */ + char **pzOut /* OUT: Write the error message string here */ +); SQLITE_PRIVATE struct Pager *sqlite3BtreePager(Btree*); SQLITE_PRIVATE i64 sqlite3BtreeRowCountEst(BtCursor*); @@ -14606,14 +17420,18 @@ SQLITE_PRIVATE int sqlite3BtreeCursorHasHint(BtCursor*, unsigned int mask); SQLITE_PRIVATE int sqlite3BtreeIsReadonly(Btree *pBt); SQLITE_PRIVATE int sqlite3HeaderSizeBtree(void); +#ifdef SQLITE_DEBUG +SQLITE_PRIVATE sqlite3_uint64 sqlite3BtreeSeekCount(Btree*); +#else +# define sqlite3BtreeSeekCount(X) 0 +#endif + #ifndef NDEBUG SQLITE_PRIVATE int sqlite3BtreeCursorIsValid(BtCursor*); #endif SQLITE_PRIVATE int sqlite3BtreeCursorIsValidNN(BtCursor*); -#ifndef SQLITE_OMIT_BTREECOUNT -SQLITE_PRIVATE int sqlite3BtreeCount(BtCursor *, i64 *); -#endif +SQLITE_PRIVATE int sqlite3BtreeCount(sqlite3*, BtCursor*, i64*); #ifdef SQLITE_TEST SQLITE_PRIVATE int sqlite3BtreeCursorInfo(BtCursor*, int*, int); @@ -14624,6 +17442,10 @@ SQLITE_PRIVATE void sqlite3BtreeCursorList(Btree*); SQLITE_PRIVATE int sqlite3BtreeCheckpoint(Btree*, int, int *, int *); #endif +SQLITE_PRIVATE int sqlite3BtreeTransferRow(BtCursor*, BtCursor*, i64); + +SQLITE_PRIVATE void sqlite3BtreeClearCache(Btree*); + /* ** If we are not using shared cache, then there is no need to ** use mutexes to access the BtShared structures. So make the @@ -14636,7 +17458,7 @@ SQLITE_PRIVATE int sqlite3BtreeSharable(Btree*); SQLITE_PRIVATE void sqlite3BtreeEnterCursor(BtCursor*); SQLITE_PRIVATE int sqlite3BtreeConnectionCount(Btree*); #else -# define sqlite3BtreeEnter(X) +# define sqlite3BtreeEnter(X) # define sqlite3BtreeEnterAll(X) # define sqlite3BtreeSharable(X) 0 # define sqlite3BtreeEnterCursor(X) @@ -14705,6 +17527,20 @@ typedef struct Vdbe Vdbe; */ typedef struct sqlite3_value Mem; typedef struct SubProgram SubProgram; +typedef struct SubrtnSig SubrtnSig; + +/* +** A signature for a reusable subroutine that materializes the RHS of +** an IN operator. +*/ +struct SubrtnSig { + int selId; /* SELECT-id for the SELECT statement on the RHS */ + u8 bComplete; /* True if fully coded and available for reusable */ + char *zAff; /* Affinity of the overall IN expression */ + int iTable; /* Ephemeral table generated by the subroutine */ + int iAddr; /* Subroutine entry address */ + int regReturn; /* Register used to hold return address */ +}; /* ** A single instruction of the virtual machine has an opcode @@ -14730,25 +17566,26 @@ struct VdbeOp { Mem *pMem; /* Used when p4type is P4_MEM */ VTable *pVtab; /* Used when p4type is P4_VTAB */ KeyInfo *pKeyInfo; /* Used when p4type is P4_KEYINFO */ - int *ai; /* Used when p4type is P4_INTARRAY */ + u32 *ai; /* Used when p4type is P4_INTARRAY */ SubProgram *pProgram; /* Used when p4type is P4_SUBPROGRAM */ Table *pTab; /* Used when p4type is P4_TABLE */ + SubrtnSig *pSubrtnSig; /* Used when p4type is P4_SUBRTNSIG */ + Index *pIdx; /* Used when p4type is P4_INDEX */ #ifdef SQLITE_ENABLE_CURSOR_HINTS Expr *pExpr; /* Used when p4type is P4_EXPR */ #endif - int (*xAdvance)(BtCursor *, int); } p4; #ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS char *zComment; /* Comment to improve readability */ #endif -#ifdef VDBE_PROFILE - u32 cnt; /* Number of times this instruction was executed */ - u64 cycles; /* Total time spent executing this instruction */ -#endif #ifdef SQLITE_VDBE_COVERAGE u32 iSrcLine; /* Source-code line that generated this opcode ** with flags in the upper 8 bits */ #endif +#if defined(SQLITE_ENABLE_STMT_SCANSTATUS) || defined(VDBE_PROFILE) + u64 nExec; + u64 nCycle; +#endif }; typedef struct VdbeOp VdbeOp; @@ -14787,8 +17624,8 @@ typedef struct VdbeOpList VdbeOpList; #define P4_COLLSEQ (-2) /* P4 is a pointer to a CollSeq structure */ #define P4_INT32 (-3) /* P4 is a 32-bit signed integer */ #define P4_SUBPROGRAM (-4) /* P4 is a pointer to a SubProgram structure */ -#define P4_ADVANCE (-5) /* P4 is a pointer to BtreeNext() or BtreePrev() */ -#define P4_TABLE (-6) /* P4 is a pointer to a Table structure */ +#define P4_TABLE (-5) /* P4 is a pointer to a Table structure */ +#define P4_INDEX (-6) /* P4 is a pointer to an Index structure */ /* Above do not own any resources. Must free those below */ #define P4_FREE_IF_LE (-7) #define P4_DYNAMIC (-7) /* Pointer to memory from sqliteMalloc() */ @@ -14801,7 +17638,8 @@ typedef struct VdbeOpList VdbeOpList; #define P4_INT64 (-14) /* P4 is a 64-bit signed integer */ #define P4_INTARRAY (-15) /* P4 is a vector of 32-bit integers */ #define P4_FUNCCTX (-16) /* P4 is a pointer to an sqlite3_context object */ -#define P4_DYNBLOB (-17) /* Pointer to memory from sqliteMalloc() */ +#define P4_TABLEREF (-17) /* Like P4_TABLE, but reference counted */ +#define P4_SUBRTNSIG (-18) /* P4 is a SubrtnSig pointer */ /* Error message codes for OP_Halt */ #define P5_ConstraintNotNull 1 @@ -14810,7 +17648,7 @@ typedef struct VdbeOpList VdbeOpList; #define P5_ConstraintFK 4 /* -** The Vdbe.aColName array contains 5n Mem structures, where n is the +** The Vdbe.aColName array contains 5n Mem structures, where n is the ** number of columns of data returned by the statement. */ #define COLNAME_NAME 0 @@ -14846,176 +17684,195 @@ typedef struct VdbeOpList VdbeOpList; #define OP_Savepoint 0 #define OP_AutoCommit 1 #define OP_Transaction 2 -#define OP_SorterNext 3 /* jump */ -#define OP_Prev 4 /* jump */ -#define OP_Next 5 /* jump */ -#define OP_Checkpoint 6 -#define OP_JournalMode 7 -#define OP_Vacuum 8 -#define OP_VFilter 9 /* jump, synopsis: iplan=r[P3] zplan='P4' */ -#define OP_VUpdate 10 /* synopsis: data=r[P3@P2] */ -#define OP_Goto 11 /* jump */ -#define OP_Gosub 12 /* jump */ -#define OP_InitCoroutine 13 /* jump */ -#define OP_Yield 14 /* jump */ -#define OP_MustBeInt 15 /* jump */ -#define OP_Jump 16 /* jump */ -#define OP_Once 17 /* jump */ -#define OP_If 18 /* jump */ +#define OP_Checkpoint 3 +#define OP_JournalMode 4 +#define OP_Vacuum 5 +#define OP_VFilter 6 /* jump, synopsis: iplan=r[P3] zplan='P4' */ +#define OP_VUpdate 7 /* synopsis: data=r[P3@P2] */ +#define OP_Init 8 /* jump0, synopsis: Start at P2 */ +#define OP_Goto 9 /* jump */ +#define OP_Gosub 10 /* jump */ +#define OP_InitCoroutine 11 /* jump0 */ +#define OP_Yield 12 /* jump0 */ +#define OP_MustBeInt 13 /* jump0 */ +#define OP_Jump 14 /* jump */ +#define OP_Once 15 /* jump */ +#define OP_If 16 /* jump */ +#define OP_IfNot 17 /* jump */ +#define OP_IsType 18 /* jump, synopsis: if typeof(P1.P3) in P5 goto P2 */ #define OP_Not 19 /* same as TK_NOT, synopsis: r[P2]= !r[P1] */ -#define OP_IfNot 20 /* jump */ -#define OP_IfNullRow 21 /* jump, synopsis: if P1.nullRow then r[P3]=NULL, goto P2 */ -#define OP_SeekLT 22 /* jump, synopsis: key=r[P3@P4] */ -#define OP_SeekLE 23 /* jump, synopsis: key=r[P3@P4] */ -#define OP_SeekGE 24 /* jump, synopsis: key=r[P3@P4] */ -#define OP_SeekGT 25 /* jump, synopsis: key=r[P3@P4] */ +#define OP_IfNullRow 20 /* jump, synopsis: if P1.nullRow then r[P3]=NULL, goto P2 */ +#define OP_SeekLT 21 /* jump0, synopsis: key=r[P3@P4] */ +#define OP_SeekLE 22 /* jump0, synopsis: key=r[P3@P4] */ +#define OP_SeekGE 23 /* jump0, synopsis: key=r[P3@P4] */ +#define OP_SeekGT 24 /* jump0, synopsis: key=r[P3@P4] */ +#define OP_IfNotOpen 25 /* jump, synopsis: if( !csr[P1] ) goto P2 */ #define OP_IfNoHope 26 /* jump, synopsis: key=r[P3@P4] */ #define OP_NoConflict 27 /* jump, synopsis: key=r[P3@P4] */ #define OP_NotFound 28 /* jump, synopsis: key=r[P3@P4] */ #define OP_Found 29 /* jump, synopsis: key=r[P3@P4] */ -#define OP_SeekRowid 30 /* jump, synopsis: intkey=r[P3] */ +#define OP_SeekRowid 30 /* jump0, synopsis: intkey=r[P3] */ #define OP_NotExists 31 /* jump, synopsis: intkey=r[P3] */ -#define OP_Last 32 /* jump */ -#define OP_IfSmaller 33 /* jump */ +#define OP_Last 32 /* jump0 */ +#define OP_IfSizeBetween 33 /* jump */ #define OP_SorterSort 34 /* jump */ #define OP_Sort 35 /* jump */ -#define OP_Rewind 36 /* jump */ -#define OP_IdxLE 37 /* jump, synopsis: key=r[P3@P4] */ -#define OP_IdxGT 38 /* jump, synopsis: key=r[P3@P4] */ -#define OP_IdxLT 39 /* jump, synopsis: key=r[P3@P4] */ -#define OP_IdxGE 40 /* jump, synopsis: key=r[P3@P4] */ -#define OP_RowSetRead 41 /* jump, synopsis: r[P3]=rowset(P1) */ -#define OP_RowSetTest 42 /* jump, synopsis: if r[P3] in rowset(P1) goto P2 */ +#define OP_Rewind 36 /* jump0 */ +#define OP_IfEmpty 37 /* jump, synopsis: if( empty(P1) ) goto P2 */ +#define OP_SorterNext 38 /* jump */ +#define OP_Prev 39 /* jump */ +#define OP_Next 40 /* jump */ +#define OP_IdxLE 41 /* jump, synopsis: key=r[P3@P4] */ +#define OP_IdxGT 42 /* jump, synopsis: key=r[P3@P4] */ #define OP_Or 43 /* same as TK_OR, synopsis: r[P3]=(r[P1] || r[P2]) */ #define OP_And 44 /* same as TK_AND, synopsis: r[P3]=(r[P1] && r[P2]) */ -#define OP_Program 45 /* jump */ -#define OP_FkIfZero 46 /* jump, synopsis: if fkctr[P1]==0 goto P2 */ -#define OP_IfPos 47 /* jump, synopsis: if r[P1]>0 then r[P1]-=P3, goto P2 */ -#define OP_IfNotZero 48 /* jump, synopsis: if r[P1]!=0 then r[P1]--, goto P2 */ -#define OP_DecrJumpZero 49 /* jump, synopsis: if (--r[P1])==0 goto P2 */ -#define OP_IsNull 50 /* jump, same as TK_ISNULL, synopsis: if r[P1]==NULL goto P2 */ -#define OP_NotNull 51 /* jump, same as TK_NOTNULL, synopsis: if r[P1]!=NULL goto P2 */ -#define OP_Ne 52 /* jump, same as TK_NE, synopsis: IF r[P3]!=r[P1] */ -#define OP_Eq 53 /* jump, same as TK_EQ, synopsis: IF r[P3]==r[P1] */ -#define OP_Gt 54 /* jump, same as TK_GT, synopsis: IF r[P3]>r[P1] */ -#define OP_Le 55 /* jump, same as TK_LE, synopsis: IF r[P3]<=r[P1] */ -#define OP_Lt 56 /* jump, same as TK_LT, synopsis: IF r[P3]=r[P1] */ -#define OP_ElseNotEq 58 /* jump, same as TK_ESCAPE */ -#define OP_IncrVacuum 59 /* jump */ -#define OP_VNext 60 /* jump */ -#define OP_Init 61 /* jump, synopsis: Start at P2 */ -#define OP_PureFunc0 62 -#define OP_Function0 63 /* synopsis: r[P3]=func(r[P2@P5]) */ -#define OP_PureFunc 64 -#define OP_Function 65 /* synopsis: r[P3]=func(r[P2@P5]) */ -#define OP_Return 66 -#define OP_EndCoroutine 67 -#define OP_HaltIfNull 68 /* synopsis: if r[P3]=null halt */ -#define OP_Halt 69 -#define OP_Integer 70 /* synopsis: r[P2]=P1 */ -#define OP_Int64 71 /* synopsis: r[P2]=P4 */ -#define OP_String 72 /* synopsis: r[P2]='P4' (len=P1) */ -#define OP_Null 73 /* synopsis: r[P2..P3]=NULL */ -#define OP_SoftNull 74 /* synopsis: r[P1]=NULL */ -#define OP_Blob 75 /* synopsis: r[P2]=P4 (len=P1) */ -#define OP_Variable 76 /* synopsis: r[P2]=parameter(P1,P4) */ -#define OP_Move 77 /* synopsis: r[P2@P3]=r[P1@P3] */ -#define OP_Copy 78 /* synopsis: r[P2@P3+1]=r[P1@P3+1] */ -#define OP_SCopy 79 /* synopsis: r[P2]=r[P1] */ -#define OP_IntCopy 80 /* synopsis: r[P2]=r[P1] */ -#define OP_ResultRow 81 /* synopsis: output=r[P1@P2] */ -#define OP_CollSeq 82 -#define OP_AddImm 83 /* synopsis: r[P1]=r[P1]+P2 */ -#define OP_RealAffinity 84 -#define OP_Cast 85 /* synopsis: affinity(r[P1]) */ -#define OP_Permutation 86 -#define OP_Compare 87 /* synopsis: r[P1@P3] <-> r[P2@P3] */ -#define OP_IsTrue 88 /* synopsis: r[P2] = coalesce(r[P1]==TRUE,P3) ^ P4 */ -#define OP_Offset 89 /* synopsis: r[P3] = sqlite_offset(P1) */ -#define OP_Column 90 /* synopsis: r[P3]=PX */ -#define OP_Affinity 91 /* synopsis: affinity(r[P1@P2]) */ -#define OP_MakeRecord 92 /* synopsis: r[P3]=mkrec(r[P1@P2]) */ -#define OP_Count 93 /* synopsis: r[P2]=count() */ -#define OP_ReadCookie 94 -#define OP_SetCookie 95 -#define OP_BitAnd 96 /* same as TK_BITAND, synopsis: r[P3]=r[P1]&r[P2] */ -#define OP_BitOr 97 /* same as TK_BITOR, synopsis: r[P3]=r[P1]|r[P2] */ -#define OP_ShiftLeft 98 /* same as TK_LSHIFT, synopsis: r[P3]=r[P2]<>r[P1] */ -#define OP_Add 100 /* same as TK_PLUS, synopsis: r[P3]=r[P1]+r[P2] */ -#define OP_Subtract 101 /* same as TK_MINUS, synopsis: r[P3]=r[P2]-r[P1] */ -#define OP_Multiply 102 /* same as TK_STAR, synopsis: r[P3]=r[P1]*r[P2] */ -#define OP_Divide 103 /* same as TK_SLASH, synopsis: r[P3]=r[P2]/r[P1] */ -#define OP_Remainder 104 /* same as TK_REM, synopsis: r[P3]=r[P2]%r[P1] */ -#define OP_Concat 105 /* same as TK_CONCAT, synopsis: r[P3]=r[P2]+r[P1] */ -#define OP_ReopenIdx 106 /* synopsis: root=P2 iDb=P3 */ -#define OP_BitNot 107 /* same as TK_BITNOT, synopsis: r[P2]= ~r[P1] */ -#define OP_OpenRead 108 /* synopsis: root=P2 iDb=P3 */ -#define OP_OpenWrite 109 /* synopsis: root=P2 iDb=P3 */ -#define OP_String8 110 /* same as TK_STRING, synopsis: r[P2]='P4' */ -#define OP_OpenDup 111 -#define OP_OpenAutoindex 112 /* synopsis: nColumn=P2 */ -#define OP_OpenEphemeral 113 /* synopsis: nColumn=P2 */ -#define OP_SorterOpen 114 -#define OP_SequenceTest 115 /* synopsis: if( cursor[P1].ctr++ ) pc = P2 */ -#define OP_OpenPseudo 116 /* synopsis: P3 columns in r[P2] */ -#define OP_Close 117 -#define OP_ColumnsUsed 118 -#define OP_SeekHit 119 /* synopsis: seekHit=P2 */ -#define OP_Sequence 120 /* synopsis: r[P2]=cursor[P1].ctr++ */ -#define OP_NewRowid 121 /* synopsis: r[P2]=rowid */ -#define OP_Insert 122 /* synopsis: intkey=r[P3] data=r[P2] */ -#define OP_Delete 123 -#define OP_ResetCount 124 -#define OP_SorterCompare 125 /* synopsis: if key(P1)!=trim(r[P3],P4) goto P2 */ -#define OP_SorterData 126 /* synopsis: r[P2]=data */ -#define OP_RowData 127 /* synopsis: r[P2]=data */ -#define OP_Rowid 128 /* synopsis: r[P2]=rowid */ -#define OP_NullRow 129 -#define OP_SeekEnd 130 -#define OP_SorterInsert 131 /* synopsis: key=r[P2] */ -#define OP_IdxInsert 132 /* synopsis: key=r[P2] */ -#define OP_IdxDelete 133 /* synopsis: key=r[P2@P3] */ -#define OP_DeferredSeek 134 /* synopsis: Move P3 to P1.rowid if needed */ -#define OP_IdxRowid 135 /* synopsis: r[P2]=rowid */ -#define OP_Destroy 136 -#define OP_Clear 137 -#define OP_ResetSorter 138 -#define OP_CreateBtree 139 /* synopsis: r[P2]=root iDb=P1 flags=P3 */ -#define OP_SqlExec 140 -#define OP_ParseSchema 141 -#define OP_LoadAnalysis 142 -#define OP_DropTable 143 -#define OP_DropIndex 144 -#define OP_Real 145 /* same as TK_FLOAT, synopsis: r[P2]=P4 */ -#define OP_DropTrigger 146 -#define OP_IntegrityCk 147 -#define OP_RowSetAdd 148 /* synopsis: rowset(P1)=r[P2] */ -#define OP_Param 149 -#define OP_FkCounter 150 /* synopsis: fkctr[P1]+=P2 */ -#define OP_MemMax 151 /* synopsis: r[P1]=max(r[P1],r[P2]) */ -#define OP_OffsetLimit 152 /* synopsis: if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1) */ -#define OP_AggInverse 153 /* synopsis: accum=r[P3] inverse(r[P2@P5]) */ -#define OP_AggStep 154 /* synopsis: accum=r[P3] step(r[P2@P5]) */ -#define OP_AggStep1 155 /* synopsis: accum=r[P3] step(r[P2@P5]) */ -#define OP_AggValue 156 /* synopsis: r[P3]=value N=P2 */ -#define OP_AggFinal 157 /* synopsis: accum=r[P1] N=P2 */ -#define OP_Expire 158 -#define OP_TableLock 159 /* synopsis: iDb=P1 root=P2 write=P3 */ -#define OP_VBegin 160 -#define OP_VCreate 161 -#define OP_VDestroy 162 -#define OP_VOpen 163 -#define OP_VColumn 164 /* synopsis: r[P3]=vcolumn(P2) */ -#define OP_VRename 165 -#define OP_Pagecount 166 -#define OP_MaxPgcnt 167 -#define OP_Trace 168 -#define OP_CursorHint 169 -#define OP_Noop 170 -#define OP_Explain 171 -#define OP_Abortable 172 +#define OP_IdxLT 45 /* jump, synopsis: key=r[P3@P4] */ +#define OP_IdxGE 46 /* jump, synopsis: key=r[P3@P4] */ +#define OP_IFindKey 47 /* jump */ +#define OP_RowSetRead 48 /* jump, synopsis: r[P3]=rowset(P1) */ +#define OP_RowSetTest 49 /* jump, synopsis: if r[P3] in rowset(P1) goto P2 */ +#define OP_Program 50 /* jump0 */ +#define OP_IsNull 51 /* jump, same as TK_ISNULL, synopsis: if r[P1]==NULL goto P2 */ +#define OP_NotNull 52 /* jump, same as TK_NOTNULL, synopsis: if r[P1]!=NULL goto P2 */ +#define OP_Ne 53 /* jump, same as TK_NE, synopsis: IF r[P3]!=r[P1] */ +#define OP_Eq 54 /* jump, same as TK_EQ, synopsis: IF r[P3]==r[P1] */ +#define OP_Gt 55 /* jump, same as TK_GT, synopsis: IF r[P3]>r[P1] */ +#define OP_Le 56 /* jump, same as TK_LE, synopsis: IF r[P3]<=r[P1] */ +#define OP_Lt 57 /* jump, same as TK_LT, synopsis: IF r[P3]=r[P1] */ +#define OP_ElseEq 59 /* jump, same as TK_ESCAPE */ +#define OP_FkIfZero 60 /* jump, synopsis: if fkctr[P1]==0 goto P2 */ +#define OP_IfPos 61 /* jump, synopsis: if r[P1]>0 then r[P1]-=P3, goto P2 */ +#define OP_IfNotZero 62 /* jump, synopsis: if r[P1]!=0 then r[P1]--, goto P2 */ +#define OP_DecrJumpZero 63 /* jump, synopsis: if (--r[P1])==0 goto P2 */ +#define OP_IncrVacuum 64 /* jump */ +#define OP_VNext 65 /* jump */ +#define OP_Filter 66 /* jump, synopsis: if key(P3@P4) not in filter(P1) goto P2 */ +#define OP_PureFunc 67 /* synopsis: r[P3]=func(r[P2@NP]) */ +#define OP_Function 68 /* synopsis: r[P3]=func(r[P2@NP]) */ +#define OP_Return 69 +#define OP_EndCoroutine 70 +#define OP_HaltIfNull 71 /* synopsis: if r[P3]=null halt */ +#define OP_Halt 72 +#define OP_Integer 73 /* synopsis: r[P2]=P1 */ +#define OP_Int64 74 /* synopsis: r[P2]=P4 */ +#define OP_String 75 /* synopsis: r[P2]='P4' (len=P1) */ +#define OP_BeginSubrtn 76 /* synopsis: r[P2]=NULL */ +#define OP_Null 77 /* synopsis: r[P2..P3]=NULL */ +#define OP_SoftNull 78 /* synopsis: r[P1]=NULL */ +#define OP_Blob 79 /* synopsis: r[P2]=P4 (len=P1) */ +#define OP_Variable 80 /* synopsis: r[P2]=parameter(P1) */ +#define OP_Move 81 /* synopsis: r[P2@P3]=r[P1@P3] */ +#define OP_Copy 82 /* synopsis: r[P2@P3+1]=r[P1@P3+1] */ +#define OP_SCopy 83 /* synopsis: r[P2]=r[P1] */ +#define OP_IntCopy 84 /* synopsis: r[P2]=r[P1] */ +#define OP_FkCheck 85 +#define OP_ResultRow 86 /* synopsis: output=r[P1@P2] */ +#define OP_CollSeq 87 +#define OP_AddImm 88 /* synopsis: r[P1]=r[P1]+P2 */ +#define OP_RealAffinity 89 +#define OP_Cast 90 /* synopsis: affinity(r[P1]) */ +#define OP_Permutation 91 +#define OP_Compare 92 /* synopsis: r[P1@P3] <-> r[P2@P3] */ +#define OP_IsTrue 93 /* synopsis: r[P2] = coalesce(r[P1]==TRUE,P3) ^ P4 */ +#define OP_ZeroOrNull 94 /* synopsis: r[P2] = 0 OR NULL */ +#define OP_Offset 95 /* synopsis: r[P3] = sqlite_offset(P1) */ +#define OP_Column 96 /* synopsis: r[P3]=PX cursor P1 column P2 */ +#define OP_TypeCheck 97 /* synopsis: typecheck(r[P1@P2]) */ +#define OP_Affinity 98 /* synopsis: affinity(r[P1@P2]) */ +#define OP_MakeRecord 99 /* synopsis: r[P3]=mkrec(r[P1@P2]) */ +#define OP_Count 100 /* synopsis: r[P2]=count() */ +#define OP_ReadCookie 101 +#define OP_SetCookie 102 +#define OP_BitAnd 103 /* same as TK_BITAND, synopsis: r[P3]=r[P1]&r[P2] */ +#define OP_BitOr 104 /* same as TK_BITOR, synopsis: r[P3]=r[P1]|r[P2] */ +#define OP_ShiftLeft 105 /* same as TK_LSHIFT, synopsis: r[P3]=r[P2]<>r[P1] */ +#define OP_Add 107 /* same as TK_PLUS, synopsis: r[P3]=r[P1]+r[P2] */ +#define OP_Subtract 108 /* same as TK_MINUS, synopsis: r[P3]=r[P2]-r[P1] */ +#define OP_Multiply 109 /* same as TK_STAR, synopsis: r[P3]=r[P1]*r[P2] */ +#define OP_Divide 110 /* same as TK_SLASH, synopsis: r[P3]=r[P2]/r[P1] */ +#define OP_Remainder 111 /* same as TK_REM, synopsis: r[P3]=r[P2]%r[P1] */ +#define OP_Concat 112 /* same as TK_CONCAT, synopsis: r[P3]=r[P2]+r[P1] */ +#define OP_ReopenIdx 113 /* synopsis: root=P2 iDb=P3 */ +#define OP_OpenRead 114 /* synopsis: root=P2 iDb=P3 */ +#define OP_BitNot 115 /* same as TK_BITNOT, synopsis: r[P2]= ~r[P1] */ +#define OP_OpenWrite 116 /* synopsis: root=P2 iDb=P3 */ +#define OP_OpenDup 117 +#define OP_String8 118 /* same as TK_STRING, synopsis: r[P2]='P4' */ +#define OP_OpenAutoindex 119 /* synopsis: nColumn=P2 */ +#define OP_OpenEphemeral 120 /* synopsis: nColumn=P2 */ +#define OP_SorterOpen 121 +#define OP_SequenceTest 122 /* synopsis: if( cursor[P1].ctr++ ) pc = P2 */ +#define OP_OpenPseudo 123 /* synopsis: P3 columns in r[P2] */ +#define OP_Close 124 +#define OP_ColumnsUsed 125 +#define OP_SeekScan 126 /* synopsis: Scan-ahead up to P1 rows */ +#define OP_SeekHit 127 /* synopsis: set P2<=seekHit<=P3 */ +#define OP_Sequence 128 /* synopsis: r[P2]=cursor[P1].ctr++ */ +#define OP_NewRowid 129 /* synopsis: r[P2]=rowid */ +#define OP_Insert 130 /* synopsis: intkey=r[P3] data=r[P2] */ +#define OP_RowCell 131 +#define OP_Delete 132 +#define OP_ResetCount 133 +#define OP_SorterCompare 134 /* synopsis: if key(P1)!=trim(r[P3],P4) goto P2 */ +#define OP_SorterData 135 /* synopsis: r[P2]=data */ +#define OP_RowData 136 /* synopsis: r[P2]=data */ +#define OP_Rowid 137 /* synopsis: r[P2]=PX rowid of P1 */ +#define OP_NullRow 138 +#define OP_SeekEnd 139 +#define OP_IdxInsert 140 /* synopsis: key=r[P2] */ +#define OP_SorterInsert 141 /* synopsis: key=r[P2] */ +#define OP_IdxDelete 142 /* synopsis: key=r[P2@P3] */ +#define OP_DeferredSeek 143 /* synopsis: Move P3 to P1.rowid if needed */ +#define OP_IdxRowid 144 /* synopsis: r[P2]=rowid */ +#define OP_FinishSeek 145 +#define OP_Destroy 146 +#define OP_Clear 147 +#define OP_ResetSorter 148 +#define OP_CreateBtree 149 /* synopsis: r[P2]=root iDb=P1 flags=P3 */ +#define OP_SqlExec 150 +#define OP_ParseSchema 151 +#define OP_LoadAnalysis 152 +#define OP_DropTable 153 +#define OP_Real 154 /* same as TK_FLOAT, synopsis: r[P2]=P4 */ +#define OP_DropIndex 155 +#define OP_DropTrigger 156 +#define OP_IntegrityCk 157 +#define OP_RowSetAdd 158 /* synopsis: rowset(P1)=r[P2] */ +#define OP_Param 159 +#define OP_FkCounter 160 /* synopsis: fkctr[P1]+=P2 */ +#define OP_MemMax 161 /* synopsis: r[P1]=max(r[P1],r[P2]) */ +#define OP_OffsetLimit 162 /* synopsis: if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1) */ +#define OP_AggInverse 163 /* synopsis: accum=r[P3] inverse(r[P2@P5]) */ +#define OP_AggStep 164 /* synopsis: accum=r[P3] step(r[P2@P5]) */ +#define OP_AggStep1 165 /* synopsis: accum=r[P3] step(r[P2@P5]) */ +#define OP_AggValue 166 /* synopsis: r[P3]=value N=P2 */ +#define OP_AggFinal 167 /* synopsis: accum=r[P1] N=P2 */ +#define OP_Expire 168 +#define OP_CursorLock 169 +#define OP_CursorUnlock 170 +#define OP_TableLock 171 /* synopsis: iDb=P1 root=P2 write=P3 */ +#define OP_VBegin 172 +#define OP_VCreate 173 +#define OP_VDestroy 174 +#define OP_VOpen 175 +#define OP_VCheck 176 +#define OP_VInitIn 177 /* synopsis: r[P2]=ValueList(P1,P3) */ +#define OP_VColumn 178 /* synopsis: r[P3]=vcolumn(P2) */ +#define OP_VRename 179 +#define OP_Pagecount 180 +#define OP_MaxPgcnt 181 +#define OP_ClrSubtype 182 /* synopsis: r[P1].subtype = 0 */ +#define OP_GetSubtype 183 /* synopsis: r[P2] = r[P1].subtype */ +#define OP_SetSubtype 184 /* synopsis: r[P2].subtype = r[P1] */ +#define OP_FilterAdd 185 /* synopsis: filter(P1) += key(P3@P4) */ +#define OP_Trace 186 +#define OP_CursorHint 187 +#define OP_ReleaseReg 188 /* synopsis: release r[P1@P2] mask P3 */ +#define OP_Noop 189 +#define OP_Explain 190 +#define OP_Abortable 191 /* Properties such as "out2" or "jump" that are specified in ** comments following the "case" for each opcode in the vdbe.c @@ -15027,37 +17884,42 @@ typedef struct VdbeOpList VdbeOpList; #define OPFLG_IN3 0x08 /* in3: P3 is an input */ #define OPFLG_OUT2 0x10 /* out2: P2 is an output */ #define OPFLG_OUT3 0x20 /* out3: P3 is an output */ +#define OPFLG_NCYCLE 0x40 /* ncycle:Cycles count against P1 */ +#define OPFLG_JUMP0 0x80 /* jump0: P2 might be zero */ #define OPFLG_INITIALIZER {\ -/* 0 */ 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x10,\ -/* 8 */ 0x00, 0x01, 0x00, 0x01, 0x01, 0x01, 0x03, 0x03,\ -/* 16 */ 0x01, 0x01, 0x03, 0x12, 0x03, 0x01, 0x09, 0x09,\ -/* 24 */ 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09,\ -/* 32 */ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,\ -/* 40 */ 0x01, 0x23, 0x0b, 0x26, 0x26, 0x01, 0x01, 0x03,\ -/* 48 */ 0x03, 0x03, 0x03, 0x03, 0x0b, 0x0b, 0x0b, 0x0b,\ -/* 56 */ 0x0b, 0x0b, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00,\ -/* 64 */ 0x00, 0x00, 0x02, 0x02, 0x08, 0x00, 0x10, 0x10,\ -/* 72 */ 0x10, 0x10, 0x00, 0x10, 0x10, 0x00, 0x00, 0x10,\ -/* 80 */ 0x10, 0x00, 0x00, 0x02, 0x02, 0x02, 0x00, 0x00,\ -/* 88 */ 0x12, 0x20, 0x00, 0x00, 0x00, 0x10, 0x10, 0x00,\ -/* 96 */ 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26,\ -/* 104 */ 0x26, 0x26, 0x00, 0x12, 0x00, 0x00, 0x10, 0x00,\ -/* 112 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ -/* 120 */ 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ -/* 128 */ 0x10, 0x00, 0x00, 0x04, 0x04, 0x00, 0x00, 0x10,\ -/* 136 */ 0x10, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,\ -/* 144 */ 0x00, 0x10, 0x00, 0x00, 0x06, 0x10, 0x00, 0x04,\ -/* 152 */ 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ -/* 160 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10,\ -/* 168 */ 0x00, 0x00, 0x00, 0x00, 0x00,} - -/* The sqlite3P2Values() routine is able to run faster if it knows +/* 0 */ 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x41, 0x00,\ +/* 8 */ 0x81, 0x01, 0x01, 0x81, 0x83, 0x83, 0x01, 0x01,\ +/* 16 */ 0x03, 0x03, 0x01, 0x12, 0x01, 0xc9, 0xc9, 0xc9,\ +/* 24 */ 0xc9, 0x01, 0x49, 0x49, 0x49, 0x49, 0xc9, 0x49,\ +/* 32 */ 0xc1, 0x01, 0x41, 0x41, 0xc1, 0x01, 0x01, 0x41,\ +/* 40 */ 0x41, 0x41, 0x41, 0x26, 0x26, 0x41, 0x41, 0x09,\ +/* 48 */ 0x23, 0x0b, 0x81, 0x03, 0x03, 0x0b, 0x0b, 0x0b,\ +/* 56 */ 0x0b, 0x0b, 0x0b, 0x01, 0x01, 0x03, 0x03, 0x03,\ +/* 64 */ 0x01, 0x41, 0x01, 0x00, 0x00, 0x02, 0x02, 0x08,\ +/* 72 */ 0x00, 0x10, 0x10, 0x10, 0x00, 0x10, 0x00, 0x10,\ +/* 80 */ 0x10, 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x00,\ +/* 88 */ 0x02, 0x02, 0x02, 0x00, 0x00, 0x12, 0x1e, 0x20,\ +/* 96 */ 0x40, 0x00, 0x00, 0x00, 0x10, 0x10, 0x00, 0x26,\ +/* 104 */ 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26,\ +/* 112 */ 0x26, 0x40, 0x40, 0x12, 0x00, 0x40, 0x10, 0x40,\ +/* 120 */ 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x40,\ +/* 128 */ 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,\ +/* 136 */ 0x00, 0x50, 0x00, 0x40, 0x04, 0x04, 0x00, 0x40,\ +/* 144 */ 0x50, 0x40, 0x10, 0x00, 0x00, 0x10, 0x00, 0x00,\ +/* 152 */ 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x06, 0x10,\ +/* 160 */ 0x00, 0x04, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00,\ +/* 168 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40,\ +/* 176 */ 0x10, 0x50, 0x40, 0x00, 0x10, 0x10, 0x02, 0x12,\ +/* 184 */ 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ +} + +/* The resolve3P2Values() routine is able to run faster if it knows ** the value of the largest JUMP opcode. The smaller the maximum ** JUMP opcode the better, so the mkopcodeh.tcl script that ** generated this include file strives to group all JUMP opcodes ** together near the beginning of the list. */ -#define SQLITE_MX_JUMP_OPCODE 61 /* Maximum JUMP opcode */ +#define SQLITE_MX_JUMP_OPCODE 66 /* Maximum JUMP opcode */ /************** End of opcodes.h *********************************************/ /************** Continuing where we left off in vdbe.h ***********************/ @@ -15066,13 +17928,14 @@ typedef struct VdbeOpList VdbeOpList; ** Additional non-public SQLITE_PREPARE_* flags */ #define SQLITE_PREPARE_SAVESQL 0x80 /* Preserve SQL text */ -#define SQLITE_PREPARE_MASK 0x0f /* Mask of public flags */ +#define SQLITE_PREPARE_MASK 0x3f /* Mask of public flags */ /* ** Prototypes for the VDBE interface. See comments on the implementation ** for a description of what each of these routines does. */ SQLITE_PRIVATE Vdbe *sqlite3VdbeCreate(Parse*); +SQLITE_PRIVATE Parse *sqlite3VdbeParser(Vdbe*); SQLITE_PRIVATE int sqlite3VdbeAddOp0(Vdbe*,int); SQLITE_PRIVATE int sqlite3VdbeAddOp1(Vdbe*,int,int); SQLITE_PRIVATE int sqlite3VdbeAddOp2(Vdbe*,int,int,int); @@ -15083,6 +17946,7 @@ SQLITE_PRIVATE int sqlite3VdbeAddOp3(Vdbe*,int,int,int,int); SQLITE_PRIVATE int sqlite3VdbeAddOp4(Vdbe*,int,int,int,int,const char *zP4,int); SQLITE_PRIVATE int sqlite3VdbeAddOp4Dup8(Vdbe*,int,int,int,int,const u8*,int); SQLITE_PRIVATE int sqlite3VdbeAddOp4Int(Vdbe*,int,int,int,int,int); +SQLITE_PRIVATE int sqlite3VdbeAddFunctionCall(Parse*,int,int,int,int,const FuncDef*,int); SQLITE_PRIVATE void sqlite3VdbeEndCoroutine(Vdbe*,int); #if defined(SQLITE_DEBUG) && !defined(SQLITE_TEST_REALLOC_STRESS) SQLITE_PRIVATE void sqlite3VdbeVerifyNoMallocRequired(Vdbe *p, int N); @@ -15093,19 +17957,27 @@ SQLITE_PRIVATE void sqlite3VdbeVerifyNoResultRow(Vdbe *p); #endif #if defined(SQLITE_DEBUG) SQLITE_PRIVATE void sqlite3VdbeVerifyAbortable(Vdbe *p, int); +SQLITE_PRIVATE void sqlite3VdbeNoJumpsOutsideSubrtn(Vdbe*,int,int,int); #else # define sqlite3VdbeVerifyAbortable(A,B) +# define sqlite3VdbeNoJumpsOutsideSubrtn(A,B,C,D) #endif SQLITE_PRIVATE VdbeOp *sqlite3VdbeAddOpList(Vdbe*, int nOp, VdbeOpList const *aOp,int iLineno); #ifndef SQLITE_OMIT_EXPLAIN -SQLITE_PRIVATE void sqlite3VdbeExplain(Parse*,u8,const char*,...); +SQLITE_PRIVATE int sqlite3VdbeExplain(Parse*,u8,const char*,...); SQLITE_PRIVATE void sqlite3VdbeExplainPop(Parse*); SQLITE_PRIVATE int sqlite3VdbeExplainParent(Parse*); # define ExplainQueryPlan(P) sqlite3VdbeExplain P +# ifdef SQLITE_ENABLE_STMT_SCANSTATUS +# define ExplainQueryPlan2(V,P) (V = sqlite3VdbeExplain P) +# else +# define ExplainQueryPlan2(V,P) ExplainQueryPlan(P) +# endif # define ExplainQueryPlanPop(P) sqlite3VdbeExplainPop(P) # define ExplainQueryPlanParent(P) sqlite3VdbeExplainParent(P) #else # define ExplainQueryPlan(P) +# define ExplainQueryPlan2(V,P) # define ExplainQueryPlanPop(P) # define ExplainQueryPlanParent(P) 0 # define sqlite3ExplainBreakpoint(A,B) /*no-op*/ @@ -15115,25 +17987,32 @@ SQLITE_PRIVATE void sqlite3ExplainBreakpoint(const char*,const char*); #else # define sqlite3ExplainBreakpoint(A,B) /*no-op*/ #endif -SQLITE_PRIVATE void sqlite3VdbeAddParseSchemaOp(Vdbe*,int,char*); -SQLITE_PRIVATE void sqlite3VdbeChangeOpcode(Vdbe*, u32 addr, u8); -SQLITE_PRIVATE void sqlite3VdbeChangeP1(Vdbe*, u32 addr, int P1); -SQLITE_PRIVATE void sqlite3VdbeChangeP2(Vdbe*, u32 addr, int P2); -SQLITE_PRIVATE void sqlite3VdbeChangeP3(Vdbe*, u32 addr, int P3); +SQLITE_PRIVATE void sqlite3VdbeAddParseSchemaOp(Vdbe*, int, char*, u16); +SQLITE_PRIVATE void sqlite3VdbeChangeOpcode(Vdbe*, int addr, u8); +SQLITE_PRIVATE void sqlite3VdbeChangeP1(Vdbe*, int addr, int P1); +SQLITE_PRIVATE void sqlite3VdbeChangeP2(Vdbe*, int addr, int P2); +SQLITE_PRIVATE void sqlite3VdbeChangeP3(Vdbe*, int addr, int P3); SQLITE_PRIVATE void sqlite3VdbeChangeP5(Vdbe*, u16 P5); +SQLITE_PRIVATE void sqlite3VdbeTypeofColumn(Vdbe*, int); SQLITE_PRIVATE void sqlite3VdbeJumpHere(Vdbe*, int addr); +SQLITE_PRIVATE void sqlite3VdbeJumpHereOrPopInst(Vdbe*, int addr); SQLITE_PRIVATE int sqlite3VdbeChangeToNoop(Vdbe*, int addr); SQLITE_PRIVATE int sqlite3VdbeDeletePriorOpcode(Vdbe*, u8 op); +#ifdef SQLITE_DEBUG +SQLITE_PRIVATE void sqlite3VdbeReleaseRegisters(Parse*,int addr, int n, u32 mask, int); +#else +# define sqlite3VdbeReleaseRegisters(P,A,N,M,F) +#endif SQLITE_PRIVATE void sqlite3VdbeChangeP4(Vdbe*, int addr, const char *zP4, int N); SQLITE_PRIVATE void sqlite3VdbeAppendP4(Vdbe*, void *pP4, int p4type); SQLITE_PRIVATE void sqlite3VdbeSetP4KeyInfo(Parse*, Index*); SQLITE_PRIVATE void sqlite3VdbeUsesBtree(Vdbe*, int); SQLITE_PRIVATE VdbeOp *sqlite3VdbeGetOp(Vdbe*, int); +SQLITE_PRIVATE VdbeOp *sqlite3VdbeGetLastOp(Vdbe*); SQLITE_PRIVATE int sqlite3VdbeMakeLabel(Parse*); SQLITE_PRIVATE void sqlite3VdbeRunOnlyOnce(Vdbe*); SQLITE_PRIVATE void sqlite3VdbeReusable(Vdbe*); SQLITE_PRIVATE void sqlite3VdbeDelete(Vdbe*); -SQLITE_PRIVATE void sqlite3VdbeClearObject(sqlite3*,Vdbe*); SQLITE_PRIVATE void sqlite3VdbeMakeReady(Vdbe*,Parse*); SQLITE_PRIVATE int sqlite3VdbeFinalize(Vdbe*); SQLITE_PRIVATE void sqlite3VdbeResolveLabel(Vdbe*, int); @@ -15163,8 +18042,11 @@ SQLITE_PRIVATE char *sqlite3VdbeExpandSql(Vdbe*, const char*); #endif SQLITE_PRIVATE int sqlite3MemCompare(const Mem*, const Mem*, const CollSeq*); SQLITE_PRIVATE int sqlite3BlobCompare(const Mem*, const Mem*); +#ifdef SQLITE_ENABLE_PERCENTILE +SQLITE_PRIVATE const char *sqlite3VdbeFuncName(const sqlite3_context*); +#endif -SQLITE_PRIVATE void sqlite3VdbeRecordUnpack(KeyInfo*,int,const void*,UnpackedRecord*); +SQLITE_PRIVATE void sqlite3VdbeRecordUnpack(int,const void*,UnpackedRecord*); SQLITE_PRIVATE int sqlite3VdbeRecordCompare(int,const void*,UnpackedRecord*); SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip(int, const void *, UnpackedRecord *, int); SQLITE_PRIVATE UnpackedRecord *sqlite3VdbeAllocUnpackedRecord(KeyInfo*); @@ -15172,14 +18054,20 @@ SQLITE_PRIVATE UnpackedRecord *sqlite3VdbeAllocUnpackedRecord(KeyInfo*); typedef int (*RecordCompare)(int,const void*,UnpackedRecord*); SQLITE_PRIVATE RecordCompare sqlite3VdbeFindCompare(UnpackedRecord*); -#ifndef SQLITE_OMIT_TRIGGER SQLITE_PRIVATE void sqlite3VdbeLinkSubProgram(Vdbe *, SubProgram *); -#endif +SQLITE_PRIVATE int sqlite3VdbeHasSubProgram(Vdbe*); +SQLITE_PRIVATE void sqlite3MemSetArrayInt64(sqlite3_value *aMem, int iIdx, i64 val); + +#ifndef SQLITE_OMIT_DATETIME_FUNCS SQLITE_PRIVATE int sqlite3NotPureFunc(sqlite3_context*); +#endif +#ifdef SQLITE_ENABLE_BYTECODE_VTAB +SQLITE_PRIVATE int sqlite3VdbeBytecodeVtabInit(sqlite3*); +#endif -/* Use SQLITE_ENABLE_COMMENTS to enable generation of extra comments on -** each VDBE opcode. +/* Use SQLITE_ENABLE_EXPLAIN_COMMENTS to enable generation of extra +** comments on each VDBE opcode. ** ** Use the SQLITE_ENABLE_MODULE_COMMENTS macro to see some extra no-op ** comments in VDBE programs that show key decision points in the code @@ -15205,7 +18093,7 @@ SQLITE_PRIVATE void sqlite3VdbeNoopComment(Vdbe*, const char*, ...); ** The VdbeCoverage macros are used to set a coverage testing point ** for VDBE branch instructions. The coverage testing points are line ** numbers in the sqlite3.c source file. VDBE branch coverage testing -** only works with an amalagmation build. That's ok since a VDBE branch +** only works with an amalgamation build. That's ok since a VDBE branch ** coverage build designed for testing the test suite only. No application ** should ever ship with VDBE branch coverage measuring turned on. ** @@ -15223,7 +18111,7 @@ SQLITE_PRIVATE void sqlite3VdbeNoopComment(Vdbe*, const char*, ...); ** // NULL option is not possible ** ** VdbeCoverageEqNe(v) // Previous OP_Jump is only interested -** // in distingishing equal and not-equal. +** // in distinguishing equal and not-equal. ** ** Every VDBE branch operation must be tagged with one of the macros above. ** If not, then when "make test" is run with -DSQLITE_VDBE_COVERAGE and @@ -15233,7 +18121,7 @@ SQLITE_PRIVATE void sqlite3VdbeNoopComment(Vdbe*, const char*, ...); ** During testing, the test application will invoke ** sqlite3_test_control(SQLITE_TESTCTRL_VDBE_COVERAGE,...) to set a callback ** routine that is invoked as each bytecode branch is taken. The callback -** contains the sqlite3.c source line number ov the VdbeCoverage macro and +** contains the sqlite3.c source line number of the VdbeCoverage macro and ** flags to indicate whether or not the branch was taken. The test application ** is responsible for keeping track of this and reporting byte-code branches ** that are never taken. @@ -15269,268 +18157,25 @@ SQLITE_PRIVATE void sqlite3VdbeSetLineNumber(Vdbe*,int); #ifdef SQLITE_ENABLE_STMT_SCANSTATUS SQLITE_PRIVATE void sqlite3VdbeScanStatus(Vdbe*, int, int, int, LogEst, const char*); +SQLITE_PRIVATE void sqlite3VdbeScanStatusRange(Vdbe*, int, int, int); +SQLITE_PRIVATE void sqlite3VdbeScanStatusCounters(Vdbe*, int, int, int); #else -# define sqlite3VdbeScanStatus(a,b,c,d,e) +# define sqlite3VdbeScanStatus(a,b,c,d,e,f) +# define sqlite3VdbeScanStatusRange(a,b,c,d) +# define sqlite3VdbeScanStatusCounters(a,b,c,d) #endif #if defined(SQLITE_DEBUG) || defined(VDBE_PROFILE) SQLITE_PRIVATE void sqlite3VdbePrintOp(FILE*, int, VdbeOp*); #endif -#endif /* SQLITE_VDBE_H */ - -/************** End of vdbe.h ************************************************/ -/************** Continuing where we left off in sqliteInt.h ******************/ -/************** Include pager.h in the middle of sqliteInt.h *****************/ -/************** Begin file pager.h *******************************************/ -/* -** 2001 September 15 -** -** The author disclaims copyright to this source code. In place of -** a legal notice, here is a blessing: -** -** May you do good and not evil. -** May you find forgiveness for yourself and forgive others. -** May you share freely, never taking more than you give. -** -************************************************************************* -** This header file defines the interface that the sqlite page cache -** subsystem. The page cache subsystem reads and writes a file a page -** at a time and provides a journal for rollback. -*/ - -#ifndef SQLITE_PAGER_H -#define SQLITE_PAGER_H - -/* -** Default maximum size for persistent journal files. A negative -** value means no limit. This value may be overridden using the -** sqlite3PagerJournalSizeLimit() API. See also "PRAGMA journal_size_limit". -*/ -#ifndef SQLITE_DEFAULT_JOURNAL_SIZE_LIMIT - #define SQLITE_DEFAULT_JOURNAL_SIZE_LIMIT -1 -#endif - -/* -** The type used to represent a page number. The first page in a file -** is called page 1. 0 is used to represent "not a page". -*/ -typedef u32 Pgno; - -/* -** Each open file is managed by a separate instance of the "Pager" structure. -*/ -typedef struct Pager Pager; - -/* -** Handle type for pages. -*/ -typedef struct PgHdr DbPage; - -/* -** Page number PAGER_MJ_PGNO is never used in an SQLite database (it is -** reserved for working around a windows/posix incompatibility). It is -** used in the journal to signify that the remainder of the journal file -** is devoted to storing a master journal name - there are no more pages to -** roll back. See comments for function writeMasterJournal() in pager.c -** for details. -*/ -#define PAGER_MJ_PGNO(x) ((Pgno)((PENDING_BYTE/((x)->pageSize))+1)) - -/* -** Allowed values for the flags parameter to sqlite3PagerOpen(). -** -** NOTE: These values must match the corresponding BTREE_ values in btree.h. -*/ -#define PAGER_OMIT_JOURNAL 0x0001 /* Do not use a rollback journal */ -#define PAGER_MEMORY 0x0002 /* In-memory database */ - -/* -** Valid values for the second argument to sqlite3PagerLockingMode(). -*/ -#define PAGER_LOCKINGMODE_QUERY -1 -#define PAGER_LOCKINGMODE_NORMAL 0 -#define PAGER_LOCKINGMODE_EXCLUSIVE 1 - -/* -** Numeric constants that encode the journalmode. -** -** The numeric values encoded here (other than PAGER_JOURNALMODE_QUERY) -** are exposed in the API via the "PRAGMA journal_mode" command and -** therefore cannot be changed without a compatibility break. -*/ -#define PAGER_JOURNALMODE_QUERY (-1) /* Query the value of journalmode */ -#define PAGER_JOURNALMODE_DELETE 0 /* Commit by deleting journal file */ -#define PAGER_JOURNALMODE_PERSIST 1 /* Commit by zeroing journal header */ -#define PAGER_JOURNALMODE_OFF 2 /* Journal omitted. */ -#define PAGER_JOURNALMODE_TRUNCATE 3 /* Commit by truncating journal */ -#define PAGER_JOURNALMODE_MEMORY 4 /* In-memory journal file */ -#define PAGER_JOURNALMODE_WAL 5 /* Use write-ahead logging */ - -/* -** Flags that make up the mask passed to sqlite3PagerGet(). -*/ -#define PAGER_GET_NOCONTENT 0x01 /* Do not load data from disk */ -#define PAGER_GET_READONLY 0x02 /* Read-only page is acceptable */ - -/* -** Flags for sqlite3PagerSetFlags() -** -** Value constraints (enforced via assert()): -** PAGER_FULLFSYNC == SQLITE_FullFSync -** PAGER_CKPT_FULLFSYNC == SQLITE_CkptFullFSync -** PAGER_CACHE_SPILL == SQLITE_CacheSpill -*/ -#define PAGER_SYNCHRONOUS_OFF 0x01 /* PRAGMA synchronous=OFF */ -#define PAGER_SYNCHRONOUS_NORMAL 0x02 /* PRAGMA synchronous=NORMAL */ -#define PAGER_SYNCHRONOUS_FULL 0x03 /* PRAGMA synchronous=FULL */ -#define PAGER_SYNCHRONOUS_EXTRA 0x04 /* PRAGMA synchronous=EXTRA */ -#define PAGER_SYNCHRONOUS_MASK 0x07 /* Mask for four values above */ -#define PAGER_FULLFSYNC 0x08 /* PRAGMA fullfsync=ON */ -#define PAGER_CKPT_FULLFSYNC 0x10 /* PRAGMA checkpoint_fullfsync=ON */ -#define PAGER_CACHESPILL 0x20 /* PRAGMA cache_spill=ON */ -#define PAGER_FLAGS_MASK 0x38 /* All above except SYNCHRONOUS */ - -/* -** The remainder of this file contains the declarations of the functions -** that make up the Pager sub-system API. See source code comments for -** a detailed description of each routine. -*/ - -/* Open and close a Pager connection. */ -SQLITE_PRIVATE int sqlite3PagerOpen( - sqlite3_vfs*, - Pager **ppPager, - const char*, - int, - int, - int, - void(*)(DbPage*) -); -SQLITE_PRIVATE int sqlite3PagerClose(Pager *pPager, sqlite3*); -SQLITE_PRIVATE int sqlite3PagerReadFileheader(Pager*, int, unsigned char*); - -/* Functions used to configure a Pager object. */ -SQLITE_PRIVATE void sqlite3PagerSetBusyHandler(Pager*, int(*)(void *), void *); -SQLITE_PRIVATE int sqlite3PagerSetPagesize(Pager*, u32*, int); -#ifdef SQLITE_HAS_CODEC -SQLITE_PRIVATE void sqlite3PagerAlignReserve(Pager*,Pager*); -#endif -SQLITE_PRIVATE int sqlite3PagerMaxPageCount(Pager*, int); -SQLITE_PRIVATE void sqlite3PagerSetCachesize(Pager*, int); -SQLITE_PRIVATE int sqlite3PagerSetSpillsize(Pager*, int); -SQLITE_PRIVATE void sqlite3PagerSetMmapLimit(Pager *, sqlite3_int64); -SQLITE_PRIVATE void sqlite3PagerShrink(Pager*); -SQLITE_PRIVATE void sqlite3PagerSetFlags(Pager*,unsigned); -SQLITE_PRIVATE int sqlite3PagerLockingMode(Pager *, int); -SQLITE_PRIVATE int sqlite3PagerSetJournalMode(Pager *, int); -SQLITE_PRIVATE int sqlite3PagerGetJournalMode(Pager*); -SQLITE_PRIVATE int sqlite3PagerOkToChangeJournalMode(Pager*); -SQLITE_PRIVATE i64 sqlite3PagerJournalSizeLimit(Pager *, i64); -SQLITE_PRIVATE sqlite3_backup **sqlite3PagerBackupPtr(Pager*); -SQLITE_PRIVATE int sqlite3PagerFlush(Pager*); - -/* Functions used to obtain and release page references. */ -SQLITE_PRIVATE int sqlite3PagerGet(Pager *pPager, Pgno pgno, DbPage **ppPage, int clrFlag); -SQLITE_PRIVATE DbPage *sqlite3PagerLookup(Pager *pPager, Pgno pgno); -SQLITE_PRIVATE void sqlite3PagerRef(DbPage*); -SQLITE_PRIVATE void sqlite3PagerUnref(DbPage*); -SQLITE_PRIVATE void sqlite3PagerUnrefNotNull(DbPage*); -SQLITE_PRIVATE void sqlite3PagerUnrefPageOne(DbPage*); - -/* Operations on page references. */ -SQLITE_PRIVATE int sqlite3PagerWrite(DbPage*); -SQLITE_PRIVATE void sqlite3PagerDontWrite(DbPage*); -SQLITE_PRIVATE int sqlite3PagerMovepage(Pager*,DbPage*,Pgno,int); -SQLITE_PRIVATE int sqlite3PagerPageRefcount(DbPage*); -SQLITE_PRIVATE void *sqlite3PagerGetData(DbPage *); -SQLITE_PRIVATE void *sqlite3PagerGetExtra(DbPage *); - -/* Functions used to manage pager transactions and savepoints. */ -SQLITE_PRIVATE void sqlite3PagerPagecount(Pager*, int*); -SQLITE_PRIVATE int sqlite3PagerBegin(Pager*, int exFlag, int); -SQLITE_PRIVATE int sqlite3PagerCommitPhaseOne(Pager*,const char *zMaster, int); -SQLITE_PRIVATE int sqlite3PagerExclusiveLock(Pager*); -SQLITE_PRIVATE int sqlite3PagerSync(Pager *pPager, const char *zMaster); -SQLITE_PRIVATE int sqlite3PagerCommitPhaseTwo(Pager*); -SQLITE_PRIVATE int sqlite3PagerRollback(Pager*); -SQLITE_PRIVATE int sqlite3PagerOpenSavepoint(Pager *pPager, int n); -SQLITE_PRIVATE int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint); -SQLITE_PRIVATE int sqlite3PagerSharedLock(Pager *pPager); - -#ifndef SQLITE_OMIT_WAL -SQLITE_PRIVATE int sqlite3PagerCheckpoint(Pager *pPager, sqlite3*, int, int*, int*); -SQLITE_PRIVATE int sqlite3PagerWalSupported(Pager *pPager); -SQLITE_PRIVATE int sqlite3PagerWalCallback(Pager *pPager); -SQLITE_PRIVATE int sqlite3PagerOpenWal(Pager *pPager, int *pisOpen); -SQLITE_PRIVATE int sqlite3PagerCloseWal(Pager *pPager, sqlite3*); -# ifdef SQLITE_ENABLE_SNAPSHOT -SQLITE_PRIVATE int sqlite3PagerSnapshotGet(Pager *pPager, sqlite3_snapshot **ppSnapshot); -SQLITE_PRIVATE int sqlite3PagerSnapshotOpen(Pager *pPager, sqlite3_snapshot *pSnapshot); -SQLITE_PRIVATE int sqlite3PagerSnapshotRecover(Pager *pPager); -SQLITE_PRIVATE int sqlite3PagerSnapshotCheck(Pager *pPager, sqlite3_snapshot *pSnapshot); -SQLITE_PRIVATE void sqlite3PagerSnapshotUnlock(Pager *pPager); -# endif -#endif - -#ifdef SQLITE_DIRECT_OVERFLOW_READ -SQLITE_PRIVATE int sqlite3PagerDirectReadOk(Pager *pPager, Pgno pgno); -#endif - -#ifdef SQLITE_ENABLE_ZIPVFS -SQLITE_PRIVATE int sqlite3PagerWalFramesize(Pager *pPager); -#endif - -/* Functions used to query pager state and configuration. */ -SQLITE_PRIVATE u8 sqlite3PagerIsreadonly(Pager*); -SQLITE_PRIVATE u32 sqlite3PagerDataVersion(Pager*); -#ifdef SQLITE_DEBUG -SQLITE_PRIVATE int sqlite3PagerRefcount(Pager*); -#endif -SQLITE_PRIVATE int sqlite3PagerMemUsed(Pager*); -SQLITE_PRIVATE const char *sqlite3PagerFilename(Pager*, int); -SQLITE_PRIVATE sqlite3_vfs *sqlite3PagerVfs(Pager*); -SQLITE_PRIVATE sqlite3_file *sqlite3PagerFile(Pager*); -SQLITE_PRIVATE sqlite3_file *sqlite3PagerJrnlFile(Pager*); -SQLITE_PRIVATE const char *sqlite3PagerJournalname(Pager*); -SQLITE_PRIVATE void *sqlite3PagerTempSpace(Pager*); -SQLITE_PRIVATE int sqlite3PagerIsMemdb(Pager*); -SQLITE_PRIVATE void sqlite3PagerCacheStat(Pager *, int, int, int *); -SQLITE_PRIVATE void sqlite3PagerClearCache(Pager*); -SQLITE_PRIVATE int sqlite3SectorSize(sqlite3_file *); -#ifdef SQLITE_ENABLE_SETLK_TIMEOUT -SQLITE_PRIVATE void sqlite3PagerResetLockTimeout(Pager *pPager); -#else -# define sqlite3PagerResetLockTimeout(X) -#endif - -/* Functions used to truncate the database file. */ -SQLITE_PRIVATE void sqlite3PagerTruncateImage(Pager*,Pgno); - -SQLITE_PRIVATE void sqlite3PagerRekey(DbPage*, Pgno, u16); - -#if defined(SQLITE_HAS_CODEC) && !defined(SQLITE_OMIT_WAL) -SQLITE_PRIVATE void *sqlite3PagerCodec(DbPage *); -#endif - -/* Functions to support testing and debugging. */ -#if !defined(NDEBUG) || defined(SQLITE_TEST) -SQLITE_PRIVATE Pgno sqlite3PagerPagenumber(DbPage*); -SQLITE_PRIVATE int sqlite3PagerIswriteable(DbPage*); -#endif -#ifdef SQLITE_TEST -SQLITE_PRIVATE int *sqlite3PagerStats(Pager*); -SQLITE_PRIVATE void sqlite3PagerRefdump(Pager*); - void disable_simulated_io_errors(void); - void enable_simulated_io_errors(void); -#else -# define disable_simulated_io_errors() -# define enable_simulated_io_errors() +#if defined(SQLITE_ENABLE_CURSOR_HINTS) && defined(SQLITE_DEBUG) +SQLITE_PRIVATE int sqlite3CursorRangeHintExprCheck(Walker *pWalker, Expr *pExpr); #endif -#endif /* SQLITE_PAGER_H */ +#endif /* SQLITE_VDBE_H */ -/************** End of pager.h ***********************************************/ +/************** End of vdbe.h ************************************************/ /************** Continuing where we left off in sqliteInt.h ******************/ /************** Include pcache.h in the middle of sqliteInt.h ****************/ /************** Begin file pcache.h ******************************************/ @@ -15546,7 +18191,7 @@ SQLITE_PRIVATE void sqlite3PagerRefdump(Pager*); ** ************************************************************************* ** This header file defines the interface that the sqlite page cache -** subsystem. +** subsystem. */ #ifndef _PCACHE_H_ @@ -15565,18 +18210,18 @@ struct PgHdr { PCache *pCache; /* PRIVATE: Cache that owns this page */ PgHdr *pDirty; /* Transient list of dirty sorted by pgno */ Pager *pPager; /* The pager this page is part of */ - Pgno pgno; /* Page number for this page */ #ifdef SQLITE_CHECK_PAGES - u32 pageHash; /* Hash of page content */ + u64 pageHash; /* Hash of page content */ #endif + Pgno pgno; /* Page number for this page */ u16 flags; /* PGHDR flags defined below */ /********************************************************************** - ** Elements above, except pCache, are public. All that follow are + ** Elements above, except pCache, are public. All that follow are ** private to pcache.c and should not be accessed by other modules. ** pCache is grouped with the public elements for efficiency. */ - i16 nRef; /* Number of users of this page */ + i64 nRef; /* Number of users of this page */ PgHdr *pDirtyNext; /* Next element in list of dirty pages */ PgHdr *pDirtyPrev; /* Previous element in list of dirty pages */ /* NB: pDirtyNext and pDirtyPrev are undefined if the @@ -15625,7 +18270,7 @@ SQLITE_PRIVATE int sqlite3PcacheSetPageSize(PCache *, int); SQLITE_PRIVATE int sqlite3PcacheSize(void); /* One release per successful fetch. Page is pinned until released. -** Reference counted. +** Reference counted. */ SQLITE_PRIVATE sqlite3_pcache_page *sqlite3PcacheFetch(PCache*, Pgno, int createFlag); SQLITE_PRIVATE int sqlite3PcacheFetchStress(PCache*, Pgno, sqlite3_pcache_page**); @@ -15657,19 +18302,19 @@ SQLITE_PRIVATE void sqlite3PcacheClearSyncFlags(PCache *); SQLITE_PRIVATE void sqlite3PcacheClear(PCache*); /* Return the total number of outstanding page references */ -SQLITE_PRIVATE int sqlite3PcacheRefCount(PCache*); +SQLITE_PRIVATE i64 sqlite3PcacheRefCount(PCache*); /* Increment the reference count of an existing page */ SQLITE_PRIVATE void sqlite3PcacheRef(PgHdr*); -SQLITE_PRIVATE int sqlite3PcachePageRefcount(PgHdr*); +SQLITE_PRIVATE i64 sqlite3PcachePageRefcount(PgHdr*); /* Return the total number of pages stored in the cache */ SQLITE_PRIVATE int sqlite3PcachePagecount(PCache*); #if defined(SQLITE_CHECK_PAGES) || defined(SQLITE_DEBUG) /* Iterate through all dirty pages currently stored in the cache. This -** interface is only available if SQLITE_CHECK_PAGES is defined when the +** interface is only available if SQLITE_CHECK_PAGES is defined when the ** library is built. */ SQLITE_PRIVATE void sqlite3PcacheIterateDirty(PCache *pCache, void (*xIter)(PgHdr *)); @@ -15727,284 +18372,6 @@ SQLITE_PRIVATE int sqlite3PCacheIsDirty(PCache *pCache); /************** End of pcache.h **********************************************/ /************** Continuing where we left off in sqliteInt.h ******************/ -/************** Include os.h in the middle of sqliteInt.h ********************/ -/************** Begin file os.h **********************************************/ -/* -** 2001 September 16 -** -** The author disclaims copyright to this source code. In place of -** a legal notice, here is a blessing: -** -** May you do good and not evil. -** May you find forgiveness for yourself and forgive others. -** May you share freely, never taking more than you give. -** -****************************************************************************** -** -** This header file (together with is companion C source-code file -** "os.c") attempt to abstract the underlying operating system so that -** the SQLite library will work on both POSIX and windows systems. -** -** This header file is #include-ed by sqliteInt.h and thus ends up -** being included by every source file. -*/ -#ifndef _SQLITE_OS_H_ -#define _SQLITE_OS_H_ - -/* -** Attempt to automatically detect the operating system and setup the -** necessary pre-processor macros for it. -*/ -/************** Include os_setup.h in the middle of os.h *********************/ -/************** Begin file os_setup.h ****************************************/ -/* -** 2013 November 25 -** -** The author disclaims copyright to this source code. In place of -** a legal notice, here is a blessing: -** -** May you do good and not evil. -** May you find forgiveness for yourself and forgive others. -** May you share freely, never taking more than you give. -** -****************************************************************************** -** -** This file contains pre-processor directives related to operating system -** detection and/or setup. -*/ -#ifndef SQLITE_OS_SETUP_H -#define SQLITE_OS_SETUP_H - -/* -** Figure out if we are dealing with Unix, Windows, or some other operating -** system. -** -** After the following block of preprocess macros, all of SQLITE_OS_UNIX, -** SQLITE_OS_WIN, and SQLITE_OS_OTHER will defined to either 1 or 0. One of -** the three will be 1. The other two will be 0. -*/ -#if defined(SQLITE_OS_OTHER) -# if SQLITE_OS_OTHER==1 -# undef SQLITE_OS_UNIX -# define SQLITE_OS_UNIX 0 -# undef SQLITE_OS_WIN -# define SQLITE_OS_WIN 0 -# else -# undef SQLITE_OS_OTHER -# endif -#endif -#if !defined(SQLITE_OS_UNIX) && !defined(SQLITE_OS_OTHER) -# define SQLITE_OS_OTHER 0 -# ifndef SQLITE_OS_WIN -# if defined(_WIN32) || defined(WIN32) || defined(__CYGWIN__) || \ - defined(__MINGW32__) || defined(__BORLANDC__) -# define SQLITE_OS_WIN 1 -# define SQLITE_OS_UNIX 0 -# else -# define SQLITE_OS_WIN 0 -# define SQLITE_OS_UNIX 1 -# endif -# else -# define SQLITE_OS_UNIX 0 -# endif -#else -# ifndef SQLITE_OS_WIN -# define SQLITE_OS_WIN 0 -# endif -#endif - -#endif /* SQLITE_OS_SETUP_H */ - -/************** End of os_setup.h ********************************************/ -/************** Continuing where we left off in os.h *************************/ - -/* If the SET_FULLSYNC macro is not defined above, then make it -** a no-op -*/ -#ifndef SET_FULLSYNC -# define SET_FULLSYNC(x,y) -#endif - -/* -** The default size of a disk sector -*/ -#ifndef SQLITE_DEFAULT_SECTOR_SIZE -# define SQLITE_DEFAULT_SECTOR_SIZE 4096 -#endif - -/* -** Temporary files are named starting with this prefix followed by 16 random -** alphanumeric characters, and no file extension. They are stored in the -** OS's standard temporary file directory, and are deleted prior to exit. -** If sqlite is being embedded in another program, you may wish to change the -** prefix to reflect your program's name, so that if your program exits -** prematurely, old temporary files can be easily identified. This can be done -** using -DSQLITE_TEMP_FILE_PREFIX=myprefix_ on the compiler command line. -** -** 2006-10-31: The default prefix used to be "sqlite_". But then -** Mcafee started using SQLite in their anti-virus product and it -** started putting files with the "sqlite" name in the c:/temp folder. -** This annoyed many windows users. Those users would then do a -** Google search for "sqlite", find the telephone numbers of the -** developers and call to wake them up at night and complain. -** For this reason, the default name prefix is changed to be "sqlite" -** spelled backwards. So the temp files are still identified, but -** anybody smart enough to figure out the code is also likely smart -** enough to know that calling the developer will not help get rid -** of the file. -*/ -#ifndef SQLITE_TEMP_FILE_PREFIX -# define SQLITE_TEMP_FILE_PREFIX "etilqs_" -#endif - -/* -** The following values may be passed as the second argument to -** sqlite3OsLock(). The various locks exhibit the following semantics: -** -** SHARED: Any number of processes may hold a SHARED lock simultaneously. -** RESERVED: A single process may hold a RESERVED lock on a file at -** any time. Other processes may hold and obtain new SHARED locks. -** PENDING: A single process may hold a PENDING lock on a file at -** any one time. Existing SHARED locks may persist, but no new -** SHARED locks may be obtained by other processes. -** EXCLUSIVE: An EXCLUSIVE lock precludes all other locks. -** -** PENDING_LOCK may not be passed directly to sqlite3OsLock(). Instead, a -** process that requests an EXCLUSIVE lock may actually obtain a PENDING -** lock. This can be upgraded to an EXCLUSIVE lock by a subsequent call to -** sqlite3OsLock(). -*/ -#define NO_LOCK 0 -#define SHARED_LOCK 1 -#define RESERVED_LOCK 2 -#define PENDING_LOCK 3 -#define EXCLUSIVE_LOCK 4 - -/* -** File Locking Notes: (Mostly about windows but also some info for Unix) -** -** We cannot use LockFileEx() or UnlockFileEx() on Win95/98/ME because -** those functions are not available. So we use only LockFile() and -** UnlockFile(). -** -** LockFile() prevents not just writing but also reading by other processes. -** A SHARED_LOCK is obtained by locking a single randomly-chosen -** byte out of a specific range of bytes. The lock byte is obtained at -** random so two separate readers can probably access the file at the -** same time, unless they are unlucky and choose the same lock byte. -** An EXCLUSIVE_LOCK is obtained by locking all bytes in the range. -** There can only be one writer. A RESERVED_LOCK is obtained by locking -** a single byte of the file that is designated as the reserved lock byte. -** A PENDING_LOCK is obtained by locking a designated byte different from -** the RESERVED_LOCK byte. -** -** On WinNT/2K/XP systems, LockFileEx() and UnlockFileEx() are available, -** which means we can use reader/writer locks. When reader/writer locks -** are used, the lock is placed on the same range of bytes that is used -** for probabilistic locking in Win95/98/ME. Hence, the locking scheme -** will support two or more Win95 readers or two or more WinNT readers. -** But a single Win95 reader will lock out all WinNT readers and a single -** WinNT reader will lock out all other Win95 readers. -** -** The following #defines specify the range of bytes used for locking. -** SHARED_SIZE is the number of bytes available in the pool from which -** a random byte is selected for a shared lock. The pool of bytes for -** shared locks begins at SHARED_FIRST. -** -** The same locking strategy and -** byte ranges are used for Unix. This leaves open the possibility of having -** clients on win95, winNT, and unix all talking to the same shared file -** and all locking correctly. To do so would require that samba (or whatever -** tool is being used for file sharing) implements locks correctly between -** windows and unix. I'm guessing that isn't likely to happen, but by -** using the same locking range we are at least open to the possibility. -** -** Locking in windows is manditory. For this reason, we cannot store -** actual data in the bytes used for locking. The pager never allocates -** the pages involved in locking therefore. SHARED_SIZE is selected so -** that all locks will fit on a single page even at the minimum page size. -** PENDING_BYTE defines the beginning of the locks. By default PENDING_BYTE -** is set high so that we don't have to allocate an unused page except -** for very large databases. But one should test the page skipping logic -** by setting PENDING_BYTE low and running the entire regression suite. -** -** Changing the value of PENDING_BYTE results in a subtly incompatible -** file format. Depending on how it is changed, you might not notice -** the incompatibility right away, even running a full regression test. -** The default location of PENDING_BYTE is the first byte past the -** 1GB boundary. -** -*/ -#ifdef SQLITE_OMIT_WSD -# define PENDING_BYTE (0x40000000) -#else -# define PENDING_BYTE sqlite3PendingByte -#endif -#define RESERVED_BYTE (PENDING_BYTE+1) -#define SHARED_FIRST (PENDING_BYTE+2) -#define SHARED_SIZE 510 - -/* -** Wrapper around OS specific sqlite3_os_init() function. -*/ -SQLITE_PRIVATE int sqlite3OsInit(void); - -/* -** Functions for accessing sqlite3_file methods -*/ -SQLITE_PRIVATE void sqlite3OsClose(sqlite3_file*); -SQLITE_PRIVATE int sqlite3OsRead(sqlite3_file*, void*, int amt, i64 offset); -SQLITE_PRIVATE int sqlite3OsWrite(sqlite3_file*, const void*, int amt, i64 offset); -SQLITE_PRIVATE int sqlite3OsTruncate(sqlite3_file*, i64 size); -SQLITE_PRIVATE int sqlite3OsSync(sqlite3_file*, int); -SQLITE_PRIVATE int sqlite3OsFileSize(sqlite3_file*, i64 *pSize); -SQLITE_PRIVATE int sqlite3OsLock(sqlite3_file*, int); -SQLITE_PRIVATE int sqlite3OsUnlock(sqlite3_file*, int); -SQLITE_PRIVATE int sqlite3OsCheckReservedLock(sqlite3_file *id, int *pResOut); -SQLITE_PRIVATE int sqlite3OsFileControl(sqlite3_file*,int,void*); -SQLITE_PRIVATE void sqlite3OsFileControlHint(sqlite3_file*,int,void*); -#define SQLITE_FCNTL_DB_UNCHANGED 0xca093fa0 -SQLITE_PRIVATE int sqlite3OsSectorSize(sqlite3_file *id); -SQLITE_PRIVATE int sqlite3OsDeviceCharacteristics(sqlite3_file *id); -#ifndef SQLITE_OMIT_WAL -SQLITE_PRIVATE int sqlite3OsShmMap(sqlite3_file *,int,int,int,void volatile **); -SQLITE_PRIVATE int sqlite3OsShmLock(sqlite3_file *id, int, int, int); -SQLITE_PRIVATE void sqlite3OsShmBarrier(sqlite3_file *id); -SQLITE_PRIVATE int sqlite3OsShmUnmap(sqlite3_file *id, int); -#endif /* SQLITE_OMIT_WAL */ -SQLITE_PRIVATE int sqlite3OsFetch(sqlite3_file *id, i64, int, void **); -SQLITE_PRIVATE int sqlite3OsUnfetch(sqlite3_file *, i64, void *); - - -/* -** Functions for accessing sqlite3_vfs methods -*/ -SQLITE_PRIVATE int sqlite3OsOpen(sqlite3_vfs *, const char *, sqlite3_file*, int, int *); -SQLITE_PRIVATE int sqlite3OsDelete(sqlite3_vfs *, const char *, int); -SQLITE_PRIVATE int sqlite3OsAccess(sqlite3_vfs *, const char *, int, int *pResOut); -SQLITE_PRIVATE int sqlite3OsFullPathname(sqlite3_vfs *, const char *, int, char *); -#ifndef SQLITE_OMIT_LOAD_EXTENSION -SQLITE_PRIVATE void *sqlite3OsDlOpen(sqlite3_vfs *, const char *); -SQLITE_PRIVATE void sqlite3OsDlError(sqlite3_vfs *, int, char *); -SQLITE_PRIVATE void (*sqlite3OsDlSym(sqlite3_vfs *, void *, const char *))(void); -SQLITE_PRIVATE void sqlite3OsDlClose(sqlite3_vfs *, void *); -#endif /* SQLITE_OMIT_LOAD_EXTENSION */ -SQLITE_PRIVATE int sqlite3OsRandomness(sqlite3_vfs *, int, char *); -SQLITE_PRIVATE int sqlite3OsSleep(sqlite3_vfs *, int); -SQLITE_PRIVATE int sqlite3OsGetLastError(sqlite3_vfs*); -SQLITE_PRIVATE int sqlite3OsCurrentTimeInt64(sqlite3_vfs *, sqlite3_int64*); - -/* -** Convenience functions for opening and closing files using -** sqlite3_malloc() to obtain space for the file-handle structure. -*/ -SQLITE_PRIVATE int sqlite3OsOpenMalloc(sqlite3_vfs *, const char *, sqlite3_file **, int,int*); -SQLITE_PRIVATE void sqlite3OsCloseFree(sqlite3_file *); - -#endif /* _SQLITE_OS_H_ */ - -/************** End of os.h **************************************************/ -/************** Continuing where we left off in sqliteInt.h ******************/ /************** Include mutex.h in the middle of sqliteInt.h *****************/ /************** Begin file mutex.h *******************************************/ /* @@ -16065,9 +18432,9 @@ SQLITE_PRIVATE void sqlite3OsCloseFree(sqlite3_file *); */ #define sqlite3_mutex_alloc(X) ((sqlite3_mutex*)8) #define sqlite3_mutex_free(X) -#define sqlite3_mutex_enter(X) +#define sqlite3_mutex_enter(X) #define sqlite3_mutex_try(X) SQLITE_OK -#define sqlite3_mutex_leave(X) +#define sqlite3_mutex_leave(X) #define sqlite3_mutex_held(X) ((void)(X),1) #define sqlite3_mutex_notheld(X) ((void)(X),1) #define sqlite3MutexAlloc(X) ((sqlite3_mutex*)8) @@ -16076,6 +18443,7 @@ SQLITE_PRIVATE void sqlite3OsCloseFree(sqlite3_file *); #define MUTEX_LOGIC(X) #else #define MUTEX_LOGIC(X) X +SQLITE_API int sqlite3_mutex_held(sqlite3_mutex*); #endif /* defined(SQLITE_MUTEX_OMIT) */ /************** End of mutex.h ***********************************************/ @@ -16092,7 +18460,7 @@ SQLITE_PRIVATE void sqlite3OsCloseFree(sqlite3_file *); /* ** Default synchronous levels. ** -** Note that (for historcal reasons) the PAGER_SYNCHRONOUS_* macros differ +** Note that (for historical reasons) the PAGER_SYNCHRONOUS_* macros differ ** from the SQLITE_DEFAULT_SYNCHRONOUS value by 1. ** ** PAGER_SYNCHRONOUS DEFAULT_SYNCHRONOUS @@ -16131,7 +18499,7 @@ struct Db { ** An instance of the following structure stores a database schema. ** ** Most Schema objects are associated with a Btree. The exception is -** the Schema for the TEMP databaes (sqlite3.aDb[1]) which is free-standing. +** the Schema for the TEMP database (sqlite3.aDb[1]) which is free-standing. ** In shared cache mode, a single Schema object can be shared by multiple ** Btrees that refer to the same underlying BtShared object. ** @@ -16179,14 +18547,13 @@ struct Schema { */ #define DB_SchemaLoaded 0x0001 /* The schema has been loaded */ #define DB_UnresetViews 0x0002 /* Some views have defined column names */ -#define DB_Empty 0x0004 /* The file is empty (length 0 bytes) */ #define DB_ResetWanted 0x0008 /* Reset the schema when nSchemaLock==0 */ /* ** The number of different kinds of things that can be limited ** using the sqlite3_limit() interface. */ -#define SQLITE_N_LIMIT (SQLITE_LIMIT_WORKER_THREADS+1) +#define SQLITE_N_LIMIT (SQLITE_LIMIT_PARSER_DEPTH+1) /* ** Lookaside malloc is a set of fixed-size buffers that can be used @@ -16207,22 +18574,66 @@ struct Schema { ** is shared by multiple database connections. Therefore, while parsing ** schema information, the Lookaside.bEnabled flag is cleared so that ** lookaside allocations are not used to construct the schema objects. +** +** New lookaside allocations are only allowed if bDisable==0. When +** bDisable is greater than zero, sz is set to zero which effectively +** disables lookaside without adding a new test for the bDisable flag +** in a performance-critical path. sz should be set by to szTrue whenever +** bDisable changes back to zero. +** +** Lookaside buffers are initially held on the pInit list. As they are +** used and freed, they are added back to the pFree list. New allocations +** come off of pFree first, then pInit as a fallback. This dual-list +** allows use to compute a high-water mark - the maximum number of allocations +** outstanding at any point in the past - by subtracting the number of +** allocations on the pInit list from the total number of allocations. +** +** Enhancement on 2019-12-12: Two-size-lookaside +** The default lookaside configuration is 100 slots of 1200 bytes each. +** The larger slot sizes are important for performance, but they waste +** a lot of space, as most lookaside allocations are less than 128 bytes. +** The two-size-lookaside enhancement breaks up the lookaside allocation +** into two pools: One of 128-byte slots and the other of the default size +** (1200-byte) slots. Allocations are filled from the small-pool first, +** failing over to the full-size pool if that does not work. Thus more +** lookaside slots are available while also using less memory. +** This enhancement can be omitted by compiling with +** SQLITE_OMIT_TWOSIZE_LOOKASIDE. */ struct Lookaside { u32 bDisable; /* Only operate the lookaside when zero */ u16 sz; /* Size of each buffer in bytes */ + u16 szTrue; /* True value of sz, even if disabled */ u8 bMalloced; /* True if pStart obtained from sqlite3_malloc() */ u32 nSlot; /* Number of lookaside slots allocated */ u32 anStat[3]; /* 0: hits. 1: size misses. 2: full misses */ LookasideSlot *pInit; /* List of buffers not previously used */ LookasideSlot *pFree; /* List of available buffers */ +#ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE + LookasideSlot *pSmallInit; /* List of small buffers not previously used */ + LookasideSlot *pSmallFree; /* List of available small buffers */ + void *pMiddle; /* First byte past end of full-size buffers and + ** the first byte of LOOKASIDE_SMALL buffers */ +#endif /* SQLITE_OMIT_TWOSIZE_LOOKASIDE */ void *pStart; /* First byte of available memory space */ void *pEnd; /* First byte past end of available space */ + void *pTrueEnd; /* True value of pEnd, when db->pnBytesFreed!=0 */ }; struct LookasideSlot { LookasideSlot *pNext; /* Next buffer in the list of free buffers */ }; +#define DisableLookaside db->lookaside.bDisable++;db->lookaside.sz=0 +#define EnableLookaside db->lookaside.bDisable--;\ + db->lookaside.sz=db->lookaside.bDisable?0:db->lookaside.szTrue + +/* Size of the smaller allocations in two-size lookaside */ +#ifdef SQLITE_OMIT_TWOSIZE_LOOKASIDE +# define LOOKASIDE_SMALL 0 +#else +# define LOOKASIDE_SMALL 128 +#endif + /* ** A hash table for built-in function definitions. (Application-defined ** functions use a regular table table from hash.h.) @@ -16237,43 +18648,11 @@ struct FuncDefHash { }; #define SQLITE_FUNC_HASH(C,L) (((C)+(L))%SQLITE_FUNC_HASH_SZ) -#ifdef SQLITE_USER_AUTHENTICATION -/* -** Information held in the "sqlite3" database connection object and used -** to manage user authentication. -*/ -typedef struct sqlite3_userauth sqlite3_userauth; -struct sqlite3_userauth { - u8 authLevel; /* Current authentication level */ - int nAuthPW; /* Size of the zAuthPW in bytes */ - char *zAuthPW; /* Password used to authenticate */ - char *zAuthUser; /* User name used to authenticate */ -}; - -/* Allowed values for sqlite3_userauth.authLevel */ -#define UAUTH_Unknown 0 /* Authentication not yet checked */ -#define UAUTH_Fail 1 /* User authentication failed */ -#define UAUTH_User 2 /* Authenticated as a normal user */ -#define UAUTH_Admin 3 /* Authenticated as an administrator */ - -/* Functions used only by user authorization logic */ -SQLITE_PRIVATE int sqlite3UserAuthTable(const char*); -SQLITE_PRIVATE int sqlite3UserAuthCheckLogin(sqlite3*,const char*,u8*); -SQLITE_PRIVATE void sqlite3UserAuthInit(sqlite3*); -SQLITE_PRIVATE void sqlite3CryptFunc(sqlite3_context*,int,sqlite3_value**); - -#endif /* SQLITE_USER_AUTHENTICATION */ - /* ** typedef for the authorization callback function. */ -#ifdef SQLITE_USER_AUTHENTICATION - typedef int (*sqlite3_xauth)(void*,int,const char*,const char*,const char*, - const char*, const char*); -#else - typedef int (*sqlite3_xauth)(void*,int,const char*,const char*,const char*, - const char*); -#endif +typedef int (*sqlite3_xauth)(void*,int,const char*,const char*,const char*, + const char*); #ifndef SQLITE_OMIT_DEPRECATED /* This is an extra SQLITE_TRACE macro that indicates "legacy" tracing @@ -16287,6 +18666,11 @@ SQLITE_PRIVATE void sqlite3CryptFunc(sqlite3_context*,int,sqlite3_value**); #endif /* SQLITE_OMIT_DEPRECATED */ #define SQLITE_TRACE_NONLEGACY_MASK 0x0f /* Normal flags */ +/* +** Maximum number of sqlite3.aDb[] entries. This is the number of attached +** databases plus 2 for "main" and "temp". +*/ +#define SQLITE_MAX_DB (SQLITE_MAX_ATTACHED+2) /* ** Each database connection is an instance of the following structure. @@ -16294,7 +18678,7 @@ SQLITE_PRIVATE void sqlite3CryptFunc(sqlite3_context*,int,sqlite3_value**); struct sqlite3 { sqlite3_vfs *pVfs; /* OS Interface */ struct Vdbe *pVdbe; /* List of active virtual machines */ - CollSeq *pDfltColl; /* The default collating sequence (BINARY) */ + CollSeq *pDfltColl; /* BINARY collseq for the database encoding */ sqlite3_mutex *mutex; /* Connection mutex */ Db *aDb; /* All backends */ int nDb; /* Number of backends currently in use */ @@ -16305,9 +18689,10 @@ struct sqlite3 { u32 nSchemaLock; /* Do not reset the schema when non-zero */ unsigned int openFlags; /* Flags passed to sqlite3_vfs.xOpen() */ int errCode; /* Most recent error code (SQLITE_*) */ + int errByteOffset; /* Byte offset of error in SQL statement */ int errMask; /* & result codes with this before returning */ int iSysErrno; /* Errno value from last system error */ - u16 dbOptFlags; /* Flags to enable/disable optimizations */ + u32 dbOptFlags; /* Flags to enable/disable optimizations */ u8 enc; /* Text encoding */ u8 autoCommit; /* The auto-commit flag. */ u8 temp_store; /* 1: file 2: memory 0: default */ @@ -16321,19 +18706,21 @@ struct sqlite3 { u8 mTrace; /* zero or more SQLITE_TRACE flags */ u8 noSharedCache; /* True if no shared-cache backends */ u8 nSqlExec; /* Number of pending OP_SqlExec opcodes */ + u8 eOpenState; /* Current condition of the connection */ + u8 nFpDigit; /* Significant digits to keep on double->text */ int nextPagesize; /* Pagesize after VACUUM if >0 */ - u32 magic; /* Magic number for detect library misuse */ - int nChange; /* Value returned by sqlite3_changes() */ - int nTotalChange; /* Value returned by sqlite3_total_changes() */ + i64 nChange; /* Value returned by sqlite3_changes() */ + i64 nTotalChange; /* Value returned by sqlite3_total_changes() */ int aLimit[SQLITE_N_LIMIT]; /* Limits */ int nMaxSorterMmap; /* Maximum size of regions mapped by sorter */ struct sqlite3InitInfo { /* Information used during initialization */ - int newTnum; /* Rootpage of table being initialized */ + Pgno newTnum; /* Rootpage of table being initialized */ u8 iDb; /* Which db file is being initialized */ u8 busy; /* TRUE if currently initializing */ unsigned orphanTrigger : 1; /* Last statement is orphaned TEMP trigger */ - unsigned imposterTable : 1; /* Building an imposter table */ + unsigned imposterTable : 2; /* Building an imposter table */ unsigned reopenMemdb : 1; /* ATTACH is really a reopen using MemDB */ + const char **azInit; /* "type", "name", and "tbl_name" columns */ } init; int nVdbeActive; /* Number of VDBEs currently running */ int nVdbeRead; /* Number of active VDBEs that read or write */ @@ -16342,8 +18729,11 @@ struct sqlite3 { int nVDestroy; /* Number of active OP_VDestroy operations */ int nExtension; /* Number of loaded extensions */ void **aExtension; /* Array of shared library handles */ - int (*xTrace)(u32,void*,void*,void*); /* Trace function */ - void *pTraceArg; /* Argument to the trace function */ + union { + void (*xLegacy)(void*,const char*); /* mTrace==SQLITE_TRACE_LEGACY */ + int (*xV2)(u32,void*,void*,void*); /* All other mTrace values */ + } trace; + void *pTraceArg; /* Argument to the trace function */ #ifndef SQLITE_OMIT_DEPRECATED void (*xProfile)(void*,const char*,u64); /* Profiling function */ void *pProfileArg; /* Argument to profile function */ @@ -16354,6 +18744,9 @@ struct sqlite3 { void (*xRollbackCallback)(void*); /* Invoked at every commit. */ void *pUpdateArg; void (*xUpdateCallback)(void*,int, const char*,const char*,sqlite_int64); + void *pAutovacPagesArg; /* Client argument to autovac_pages */ + void (*xAutovacDestr)(void*); /* Destructor for pAutovacPAgesArg */ + unsigned int (*xAutovacPages)(void*,const char*,u32,u32,u32); Parse *pParse; /* Current parse */ #ifdef SQLITE_ENABLE_PREUPDATE_HOOK void *pPreUpdateArg; /* First argument to xPreUpdateCallback */ @@ -16396,14 +18789,21 @@ struct sqlite3 { BusyHandler busyHandler; /* Busy callback */ Db aDbStatic[2]; /* Static space for the 2 default backends */ Savepoint *pSavepoint; /* List of active savepoints */ + int nAnalysisLimit; /* Number of index rows to ANALYZE */ int busyTimeout; /* Busy handler timeout, in msec */ +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + int setlkTimeout; /* Blocking lock timeout, in msec. -1 -> inf. */ + int setlkFlags; /* Flags passed to setlk_timeout() */ +#endif int nSavepoint; /* Number of non-transaction savepoints */ int nStatement; /* Number of nested statement-transactions */ i64 nDeferredCons; /* Net deferred constraints this transaction. */ i64 nDeferredImmCons; /* Net deferred immediate constraints */ int *pnBytesFreed; /* If not NULL, increment this in DbFree() */ + DbClientData *pDbData; /* sqlite3_set_clientdata() content */ + u64 nSpill; /* TEMP content spilled to disk */ #ifdef SQLITE_ENABLE_UNLOCK_NOTIFY - /* The following variables are all protected by the STATIC_MASTER + /* The following variables are all protected by the STATIC_MAIN ** mutex, not by sqlite3.mutex. They are used by code in notify.c. ** ** When X.pUnlockConnection==Y, that means that X is waiting for Y to @@ -16419,9 +18819,6 @@ struct sqlite3 { void (*xUnlockNotify)(void **, int); /* Unlock notify callback */ sqlite3 *pNextBlocked; /* Next in list of all blocked connections */ #endif -#ifdef SQLITE_USER_AUTHENTICATION - sqlite3_userauth auth; /* User authentication information */ -#endif }; /* @@ -16430,6 +18827,13 @@ struct sqlite3 { #define SCHEMA_ENC(db) ((db)->aDb[0].pSchema->enc) #define ENC(db) ((db)->enc) +/* +** A u64 constant where the lower 32 bits are all zeros. Only the +** upper 32 bits are included in the argument. Necessary because some +** C-compilers still do not accept LL integer literals. +*/ +#define HI(X) ((u64)(X)<<32) + /* ** Possible values for the sqlite3.flags. ** @@ -16438,20 +18842,19 @@ struct sqlite3 { ** SQLITE_CkptFullFSync == PAGER_CKPT_FULLFSYNC ** SQLITE_CacheSpill == PAGER_CACHE_SPILL */ -#define SQLITE_WriteSchema 0x00000001 /* OK to update SQLITE_MASTER */ +#define SQLITE_WriteSchema 0x00000001 /* OK to update SQLITE_SCHEMA */ #define SQLITE_LegacyFileFmt 0x00000002 /* Create new databases in format 1 */ #define SQLITE_FullColNames 0x00000004 /* Show full column names on SELECT */ #define SQLITE_FullFSync 0x00000008 /* Use full fsync on the backend */ #define SQLITE_CkptFullFSync 0x00000010 /* Use full fsync for checkpoint */ #define SQLITE_CacheSpill 0x00000020 /* OK to spill pager cache */ #define SQLITE_ShortColNames 0x00000040 /* Show short columns names */ -#define SQLITE_CountRows 0x00000080 /* Count rows changed by INSERT, */ - /* DELETE, or UPDATE and return */ - /* the count using a callback. */ +#define SQLITE_TrustedSchema 0x00000080 /* Allow unsafe functions and + ** vtabs in the schema definition */ #define SQLITE_NullCallback 0x00000100 /* Invoke the callback once if the */ /* result set is empty */ #define SQLITE_IgnoreChecks 0x00000200 /* Do not enforce check constraints */ -#define SQLITE_ReadUncommit 0x00000400 /* READ UNCOMMITTED in shared-cache */ +#define SQLITE_StmtScanStatus 0x00000400 /* Enable stmt_scanstats() counters */ #define SQLITE_NoCkptOnClose 0x00000800 /* No checkpoint on close()/DETACH */ #define SQLITE_ReverseOrder 0x00001000 /* Reverse unordered SELECTs */ #define SQLITE_RecTriggers 0x00002000 /* Enable recursive triggers */ @@ -16470,16 +18873,27 @@ struct sqlite3 { #define SQLITE_LegacyAlter 0x04000000 /* Legacy ALTER TABLE behaviour */ #define SQLITE_NoSchemaError 0x08000000 /* Do not report schema parse errors*/ #define SQLITE_Defensive 0x10000000 /* Input SQL is likely hostile */ +#define SQLITE_DqsDDL 0x20000000 /* dbl-quoted strings allowed in DDL*/ +#define SQLITE_DqsDML 0x40000000 /* dbl-quoted strings allowed in DML*/ +#define SQLITE_EnableView 0x80000000 /* Enable the use of views */ +#define SQLITE_CountRows HI(0x00001) /* Count rows changed by INSERT, */ + /* DELETE, or UPDATE and return */ + /* the count using a callback. */ +#define SQLITE_CorruptRdOnly HI(0x00002) /* Prohibit writes due to error */ +#define SQLITE_ReadUncommit HI(0x00004) /* READ UNCOMMITTED in shared-cache */ +#define SQLITE_FkNoAction HI(0x00008) /* Treat all FK as NO ACTION */ +#define SQLITE_AttachCreate HI(0x00010) /* ATTACH allowed to create new dbs */ +#define SQLITE_AttachWrite HI(0x00020) /* ATTACH allowed to open for write */ +#define SQLITE_Comments HI(0x00040) /* Enable SQL comments */ /* Flags used only if debugging */ -#define HI(X) ((u64)(X)<<32) #ifdef SQLITE_DEBUG -#define SQLITE_SqlTrace HI(0x0001) /* Debug print SQL as it executes */ -#define SQLITE_VdbeListing HI(0x0002) /* Debug listings of VDBE progs */ -#define SQLITE_VdbeTrace HI(0x0004) /* True to trace VDBE execution */ -#define SQLITE_VdbeAddopTrace HI(0x0008) /* Trace sqlite3VdbeAddOp() calls */ -#define SQLITE_VdbeEQP HI(0x0010) /* Debug EXPLAIN QUERY PLAN */ -#define SQLITE_ParserTrace HI(0x0020) /* PRAGMA parser_trace=ON */ +#define SQLITE_SqlTrace HI(0x0100000) /* Debug print SQL as it executes */ +#define SQLITE_VdbeListing HI(0x0200000) /* Debug listings of VDBE progs */ +#define SQLITE_VdbeTrace HI(0x0400000) /* True to trace VDBE execution */ +#define SQLITE_VdbeAddopTrace HI(0x0800000) /* Trace sqlite3VdbeAddOp() calls */ +#define SQLITE_VdbeEQP HI(0x1000000) /* Debug EXPLAIN QUERY PLAN */ +#define SQLITE_ParserTrace HI(0x2000000) /* PRAGMA parser_trace=ON */ #endif /* @@ -16490,30 +18904,49 @@ struct sqlite3 { #define DBFLAG_Vacuum 0x0004 /* Currently in a VACUUM */ #define DBFLAG_VacuumInto 0x0008 /* Currently running VACUUM INTO */ #define DBFLAG_SchemaKnownOk 0x0010 /* Schema is known to be valid */ +#define DBFLAG_InternalFunc 0x0020 /* Allow use of internal functions */ +#define DBFLAG_EncodingFixed 0x0040 /* No longer possible to change enc. */ /* ** Bits of the sqlite3.dbOptFlags field that are used by the ** sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS,...) interface to ** selectively disable various optimizations. */ -#define SQLITE_QueryFlattener 0x0001 /* Query flattening */ -#define SQLITE_WindowFunc 0x0002 /* Use xInverse for window functions */ -#define SQLITE_GroupByOrder 0x0004 /* GROUPBY cover of ORDERBY */ -#define SQLITE_FactorOutConst 0x0008 /* Constant factoring */ -#define SQLITE_DistinctOpt 0x0010 /* DISTINCT using indexes */ -#define SQLITE_CoverIdxScan 0x0020 /* Covering index scans */ -#define SQLITE_OrderByIdxJoin 0x0040 /* ORDER BY of joins via index */ -#define SQLITE_Transitive 0x0080 /* Transitive constraints */ -#define SQLITE_OmitNoopJoin 0x0100 /* Omit unused tables in joins */ -#define SQLITE_CountOfView 0x0200 /* The count-of-view optimization */ -#define SQLITE_CursorHints 0x0400 /* Add OP_CursorHint opcodes */ -#define SQLITE_Stat34 0x0800 /* Use STAT3 or STAT4 data */ - /* TH3 expects the Stat34 ^^^^^^ value to be 0x0800. Don't change it */ -#define SQLITE_PushDown 0x1000 /* The push-down optimization */ -#define SQLITE_SimplifyJoin 0x2000 /* Convert LEFT JOIN to JOIN */ -#define SQLITE_SkipScan 0x4000 /* Skip-scans */ -#define SQLITE_PropagateConst 0x8000 /* The constant propagation opt */ -#define SQLITE_AllOpts 0xffff /* All optimizations */ +#define SQLITE_QueryFlattener 0x00000001 /* Query flattening */ +#define SQLITE_WindowFunc 0x00000002 /* Use xInverse for window functions */ +#define SQLITE_GroupByOrder 0x00000004 /* GROUPBY cover of ORDERBY */ +#define SQLITE_FactorOutConst 0x00000008 /* Constant factoring */ +#define SQLITE_DistinctOpt 0x00000010 /* DISTINCT using indexes */ +#define SQLITE_CoverIdxScan 0x00000020 /* Covering index scans */ +#define SQLITE_OrderByIdxJoin 0x00000040 /* ORDER BY of joins via index */ +#define SQLITE_Transitive 0x00000080 /* Transitive constraints */ +#define SQLITE_OmitNoopJoin 0x00000100 /* Omit unused tables in joins */ +#define SQLITE_CountOfView 0x00000200 /* The count-of-view optimization */ +#define SQLITE_CursorHints 0x00000400 /* Add OP_CursorHint opcodes */ +#define SQLITE_Stat4 0x00000800 /* Use STAT4 data */ + /* TH3 expects this value ^^^^^^^^^^ to be 0x0000800. Don't change it */ +#define SQLITE_PushDown 0x00001000 /* WHERE-clause push-down opt */ +#define SQLITE_SimplifyJoin 0x00002000 /* Convert LEFT JOIN to JOIN */ +#define SQLITE_SkipScan 0x00004000 /* Skip-scans */ +#define SQLITE_PropagateConst 0x00008000 /* The constant propagation opt */ +#define SQLITE_MinMaxOpt 0x00010000 /* The min/max optimization */ +#define SQLITE_SeekScan 0x00020000 /* The OP_SeekScan optimization */ +#define SQLITE_OmitOrderBy 0x00040000 /* Omit pointless ORDER BY */ + /* TH3 expects this value ^^^^^^^^^^ to be 0x40000. Coordinate any change */ +#define SQLITE_BloomFilter 0x00080000 /* Use a Bloom filter on searches */ +#define SQLITE_BloomPulldown 0x00100000 /* Run Bloom filters early */ +#define SQLITE_BalancedMerge 0x00200000 /* Balance multi-way merges */ +#define SQLITE_ReleaseReg 0x00400000 /* Use OP_ReleaseReg for testing */ +#define SQLITE_FlttnUnionAll 0x00800000 /* Disable the UNION ALL flattener */ + /* TH3 expects this value ^^^^^^^^^^ See flatten04.test */ +#define SQLITE_IndexedExpr 0x01000000 /* Pull exprs from index when able */ +#define SQLITE_Coroutines 0x02000000 /* Co-routines for subqueries */ +#define SQLITE_NullUnusedCols 0x04000000 /* NULL unused columns in subqueries */ +#define SQLITE_OnePass 0x08000000 /* Single-pass DELETE and UPDATE */ +#define SQLITE_OrderBySubq 0x10000000 /* ORDER BY in subquery helps outer */ +#define SQLITE_StarQuery 0x20000000 /* Heurists for star queries */ +#define SQLITE_ExistsToJoin 0x40000000 /* The EXISTS-to-JOIN optimization */ +#define SQLITE_AllOpts 0xffffffff /* All optimizations */ /* ** Macros for testing whether or not optimizations are enabled or disabled. @@ -16527,17 +18960,16 @@ struct sqlite3 { */ #define ConstFactorOk(P) ((P)->okConstFactor) -/* -** Possible values for the sqlite.magic field. -** The numbers are obtained at random and have no special meaning, other -** than being distinct from one another. +/* Possible values for the sqlite3.eOpenState field. +** The numbers are randomly selected such that a minimum of three bits must +** change to convert any number to another or to zero */ -#define SQLITE_MAGIC_OPEN 0xa029a697 /* Database is open */ -#define SQLITE_MAGIC_CLOSED 0x9f3c2d33 /* Database is closed */ -#define SQLITE_MAGIC_SICK 0x4b771290 /* Error and awaiting close */ -#define SQLITE_MAGIC_BUSY 0xf03b7906 /* Database currently in use */ -#define SQLITE_MAGIC_ERROR 0xb5357930 /* An SQLITE_MISUSE error occurred */ -#define SQLITE_MAGIC_ZOMBIE 0x64cffc7f /* Close with last statement close */ +#define SQLITE_STATE_OPEN 0x76 /* Database is open */ +#define SQLITE_STATE_CLOSED 0xce /* Database is closed */ +#define SQLITE_STATE_SICK 0xba /* Error and awaiting close */ +#define SQLITE_STATE_BUSY 0x6d /* Database currently in use */ +#define SQLITE_STATE_ERROR 0xd5 /* An SQLITE_MISUSE error occurred */ +#define SQLITE_STATE_ZOMBIE 0xa7 /* Close with last statement close */ /* ** Each SQL function is defined by an instance of the following @@ -16550,7 +18982,7 @@ struct sqlite3 { ** field is used by per-connection app-def functions. */ struct FuncDef { - i8 nArg; /* Number of arguments. -1 means unlimited */ + i16 nArg; /* Number of arguments. -1 means unlimited */ u32 funcFlags; /* Some combination of SQLITE_FUNC_* */ void *pUserData; /* User data parameter */ FuncDef *pNext; /* Next function with same name */ @@ -16562,7 +18994,7 @@ struct FuncDef { union { FuncDef *pHash; /* Next with a different name but the same hash */ FuncDestructor *pDestructor; /* Reference counted destructor function */ - } u; + } u; /* pHash if SQLITE_FUNC_BUILTIN, pDestructor otherwise */ }; /* @@ -16592,11 +19024,21 @@ struct FuncDestructor { ** are assert() statements in the code to verify this. ** ** Value constraints (enforced via assert()): -** SQLITE_FUNC_MINMAX == NC_MinMaxAgg == SF_MinMaxAgg -** SQLITE_FUNC_LENGTH == OPFLAG_LENGTHARG -** SQLITE_FUNC_TYPEOF == OPFLAG_TYPEOFARG -** SQLITE_FUNC_CONSTANT == SQLITE_DETERMINISTIC from the API +** SQLITE_FUNC_MINMAX == NC_MinMaxAgg == SF_MinMaxAgg +** SQLITE_FUNC_ANYORDER == NC_OrderAgg == SF_OrderByReqd +** SQLITE_FUNC_LENGTH == OPFLAG_LENGTHARG +** SQLITE_FUNC_TYPEOF == OPFLAG_TYPEOFARG +** SQLITE_FUNC_BYTELEN == OPFLAG_BYTELENARG +** SQLITE_FUNC_CONSTANT == SQLITE_DETERMINISTIC from the API +** SQLITE_FUNC_DIRECT == SQLITE_DIRECTONLY from the API +** SQLITE_FUNC_UNSAFE == SQLITE_INNOCUOUS -- opposite meanings!!! ** SQLITE_FUNC_ENCMASK depends on SQLITE_UTF* macros in the API +** +** Note that even though SQLITE_FUNC_UNSAFE and SQLITE_INNOCUOUS have the +** same bit value, their meanings are inverted. SQLITE_FUNC_UNSAFE is +** used internally and if set means that the function has side effects. +** SQLITE_INNOCUOUS is used by application code and means "not unsafe". +** See multiple instances of tag-20230109-1. */ #define SQLITE_FUNC_ENCMASK 0x0003 /* SQLITE_UTF8, SQLITE_UTF16BE or UTF16LE */ #define SQLITE_FUNC_LIKE 0x0004 /* Candidate for the LIKE optimization */ @@ -16605,17 +19047,35 @@ struct FuncDestructor { #define SQLITE_FUNC_NEEDCOLL 0x0020 /* sqlite3GetFuncCollSeq() might be called*/ #define SQLITE_FUNC_LENGTH 0x0040 /* Built-in length() function */ #define SQLITE_FUNC_TYPEOF 0x0080 /* Built-in typeof() function */ +#define SQLITE_FUNC_BYTELEN 0x00c0 /* Built-in octet_length() function */ #define SQLITE_FUNC_COUNT 0x0100 /* Built-in count(*) aggregate */ -#define SQLITE_FUNC_COALESCE 0x0200 /* Built-in coalesce() or ifnull() */ +/* 0x0200 -- available for reuse */ #define SQLITE_FUNC_UNLIKELY 0x0400 /* Built-in unlikely() function */ #define SQLITE_FUNC_CONSTANT 0x0800 /* Constant inputs give a constant output */ #define SQLITE_FUNC_MINMAX 0x1000 /* True for min() and max() aggregates */ #define SQLITE_FUNC_SLOCHNG 0x2000 /* "Slow Change". Value constant during a ** single query - might change over time */ -#define SQLITE_FUNC_AFFINITY 0x4000 /* Built-in affinity() function */ -#define SQLITE_FUNC_OFFSET 0x8000 /* Built-in sqlite_offset() function */ +#define SQLITE_FUNC_TEST 0x4000 /* Built-in testing functions */ +#define SQLITE_FUNC_RUNONLY 0x8000 /* Cannot be used by valueFromFunction */ #define SQLITE_FUNC_WINDOW 0x00010000 /* Built-in window-only function */ #define SQLITE_FUNC_INTERNAL 0x00040000 /* For use by NestedParse() only */ +#define SQLITE_FUNC_DIRECT 0x00080000 /* Not for use in TRIGGERs or VIEWs */ +/* SQLITE_SUBTYPE 0x00100000 // Consumer of subtypes */ +#define SQLITE_FUNC_UNSAFE 0x00200000 /* Function has side effects */ +#define SQLITE_FUNC_INLINE 0x00400000 /* Functions implemented in-line */ +#define SQLITE_FUNC_BUILTIN 0x00800000 /* This is a built-in function */ +/* SQLITE_RESULT_SUBTYPE 0x01000000 // Generator of subtypes */ +#define SQLITE_FUNC_ANYORDER 0x08000000 /* count/min/max aggregate */ + +/* Identifier numbers for each in-line function */ +#define INLINEFUNC_coalesce 0 +#define INLINEFUNC_implies_nonnull_row 1 +#define INLINEFUNC_expr_implies_expr 2 +#define INLINEFUNC_expr_compare 3 +#define INLINEFUNC_affinity 4 +#define INLINEFUNC_iif 5 +#define INLINEFUNC_sqlite_offset 6 +#define INLINEFUNC_unlikely 99 /* Default case */ /* ** The following three macros, FUNCTION(), LIKEFUNC() and AGGREGATE() are @@ -16631,6 +19091,22 @@ struct FuncDestructor { ** VFUNCTION(zName, nArg, iArg, bNC, xFunc) ** Like FUNCTION except it omits the SQLITE_FUNC_CONSTANT flag. ** +** SFUNCTION(zName, nArg, iArg, bNC, xFunc) +** Like FUNCTION except it omits the SQLITE_FUNC_CONSTANT flag and +** adds the SQLITE_DIRECTONLY flag. +** +** INLINE_FUNC(zName, nArg, iFuncId, mFlags) +** zName is the name of a function that is implemented by in-line +** byte code rather than by the usual callbacks. The iFuncId +** parameter determines the function id. The mFlags parameter is +** optional SQLITE_FUNC_ flags for this function. +** +** TEST_FUNC(zName, nArg, iFuncId, mFlags) +** zName is the name of a test-only function implemented by in-line +** byte code rather than by the usual callbacks. The iFuncId +** parameter determines the function id. The mFlags parameter is +** optional SQLITE_FUNC_ flags for this function. +** ** DFUNCTION(zName, nArg, iArg, bNC, xFunc) ** Like FUNCTION except it omits the SQLITE_FUNC_CONSTANT flag and ** adds the SQLITE_FUNC_SLOCHNG flag. Used for date & time functions @@ -16638,10 +19114,13 @@ struct FuncDestructor { ** a single query. The iArg is ignored. The user-data is always set ** to a NULL pointer. The bNC parameter is not used. ** +** MFUNCTION(zName, nArg, xPtr, xFunc) +** For math-library functions. xPtr is an arbitrary pointer. +** ** PURE_DATE(zName, nArg, iArg, bNC, xFunc) ** Used for "pure" date/time functions, this macro is like DFUNCTION ** except that it does set the SQLITE_FUNC_CONSTANT flags. iArg is -** ignored and the user-data for these functions is set to an +** ignored and the user-data for these functions is set to an ** arbitrary non-NULL pointer. The bNC parameter is not used. ** ** AGGREGATE(zName, nArg, iArg, bNC, xStep, xFinal) @@ -16650,7 +19129,7 @@ struct FuncDestructor { ** are interpreted in the same way as the first 4 parameters to ** FUNCTION(). ** -** WFUNCTION(zName, nArg, iArg, xStep, xFinal, xValue, xInverse) +** WAGGREGATE(zName, nArg, iArg, xStep, xFinal, xValue, xInverse) ** Used to create an aggregate function definition implemented by ** the C functions xStep and xFinal. The first four parameters ** are interpreted in the same way as the first 4 parameters to @@ -16665,37 +19144,56 @@ struct FuncDestructor { ** parameter. */ #define FUNCTION(zName, nArg, iArg, bNC, xFunc) \ - {nArg, SQLITE_FUNC_CONSTANT|SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL), \ + {nArg, SQLITE_FUNC_BUILTIN|\ + SQLITE_FUNC_CONSTANT|SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL), \ SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, 0, #zName, {0} } #define VFUNCTION(zName, nArg, iArg, bNC, xFunc) \ - {nArg, SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL), \ + {nArg, SQLITE_FUNC_BUILTIN|SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL), \ SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, 0, #zName, {0} } +#define SFUNCTION(zName, nArg, iArg, bNC, xFunc) \ + {nArg, SQLITE_FUNC_BUILTIN|SQLITE_UTF8|SQLITE_DIRECTONLY|SQLITE_FUNC_UNSAFE, \ + SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, 0, #zName, {0} } +#define MFUNCTION(zName, nArg, xPtr, xFunc) \ + {nArg, SQLITE_FUNC_BUILTIN|SQLITE_FUNC_CONSTANT|SQLITE_UTF8, \ + xPtr, 0, xFunc, 0, 0, 0, #zName, {0} } +#define JFUNCTION(zName, nArg, bUseCache, bWS, bRS, bJsonB, iArg, xFunc) \ + {nArg, SQLITE_FUNC_BUILTIN|SQLITE_DETERMINISTIC|SQLITE_FUNC_CONSTANT|\ + SQLITE_UTF8|((bUseCache)*SQLITE_FUNC_RUNONLY)|\ + ((bRS)*SQLITE_SUBTYPE)|((bWS)*SQLITE_RESULT_SUBTYPE), \ + SQLITE_INT_TO_PTR(iArg|((bJsonB)*JSON_BLOB)),0,xFunc,0, 0, 0, #zName, {0} } +#define INLINE_FUNC(zName, nArg, iArg, mFlags) \ + {nArg, SQLITE_FUNC_BUILTIN|\ + SQLITE_UTF8|SQLITE_FUNC_INLINE|SQLITE_FUNC_CONSTANT|(mFlags), \ + SQLITE_INT_TO_PTR(iArg), 0, noopFunc, 0, 0, 0, #zName, {0} } +#define TEST_FUNC(zName, nArg, iArg, mFlags) \ + {nArg, SQLITE_FUNC_BUILTIN|\ + SQLITE_UTF8|SQLITE_FUNC_INTERNAL|SQLITE_FUNC_TEST| \ + SQLITE_FUNC_INLINE|SQLITE_FUNC_CONSTANT|(mFlags), \ + SQLITE_INT_TO_PTR(iArg), 0, noopFunc, 0, 0, 0, #zName, {0} } #define DFUNCTION(zName, nArg, iArg, bNC, xFunc) \ - {nArg, SQLITE_FUNC_SLOCHNG|SQLITE_UTF8, \ + {nArg, SQLITE_FUNC_BUILTIN|SQLITE_FUNC_SLOCHNG|SQLITE_UTF8, \ 0, 0, xFunc, 0, 0, 0, #zName, {0} } #define PURE_DATE(zName, nArg, iArg, bNC, xFunc) \ - {nArg, SQLITE_FUNC_SLOCHNG|SQLITE_UTF8|SQLITE_FUNC_CONSTANT, \ + {nArg, SQLITE_FUNC_BUILTIN|\ + SQLITE_FUNC_SLOCHNG|SQLITE_UTF8|SQLITE_FUNC_CONSTANT, \ (void*)&sqlite3Config, 0, xFunc, 0, 0, 0, #zName, {0} } #define FUNCTION2(zName, nArg, iArg, bNC, xFunc, extraFlags) \ - {nArg,SQLITE_FUNC_CONSTANT|SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL)|extraFlags,\ + {nArg, SQLITE_FUNC_BUILTIN|\ + SQLITE_FUNC_CONSTANT|SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL)|extraFlags,\ SQLITE_INT_TO_PTR(iArg), 0, xFunc, 0, 0, 0, #zName, {0} } #define STR_FUNCTION(zName, nArg, pArg, bNC, xFunc) \ - {nArg, SQLITE_FUNC_SLOCHNG|SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL), \ - pArg, 0, xFunc, 0, 0, 0, #zName, } + {nArg, SQLITE_FUNC_BUILTIN|\ + SQLITE_FUNC_SLOCHNG|SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL), \ + pArg, 0, xFunc, 0, 0, 0, #zName, {0} } #define LIKEFUNC(zName, nArg, arg, flags) \ - {nArg, SQLITE_FUNC_CONSTANT|SQLITE_UTF8|flags, \ + {nArg, SQLITE_FUNC_BUILTIN|SQLITE_FUNC_CONSTANT|SQLITE_UTF8|flags, \ (void *)arg, 0, likeFunc, 0, 0, 0, #zName, {0} } -#define AGGREGATE(zName, nArg, arg, nc, xStep, xFinal, xValue) \ - {nArg, SQLITE_UTF8|(nc*SQLITE_FUNC_NEEDCOLL), \ - SQLITE_INT_TO_PTR(arg), 0, xStep,xFinal,xValue,0,#zName, {0}} -#define AGGREGATE2(zName, nArg, arg, nc, xStep, xFinal, extraFlags) \ - {nArg, SQLITE_UTF8|(nc*SQLITE_FUNC_NEEDCOLL)|extraFlags, \ - SQLITE_INT_TO_PTR(arg), 0, xStep,xFinal,xFinal,0,#zName, {0}} #define WAGGREGATE(zName, nArg, arg, nc, xStep, xFinal, xValue, xInverse, f) \ - {nArg, SQLITE_UTF8|(nc*SQLITE_FUNC_NEEDCOLL)|f, \ + {nArg, SQLITE_FUNC_BUILTIN|SQLITE_UTF8|(nc*SQLITE_FUNC_NEEDCOLL)|f, \ SQLITE_INT_TO_PTR(arg), 0, xStep,xFinal,xValue,xInverse,#zName, {0}} #define INTERNAL_FUNCTION(zName, nArg, xFunc) \ - {nArg, SQLITE_FUNC_INTERNAL|SQLITE_UTF8|SQLITE_FUNC_CONSTANT, \ + {nArg, SQLITE_FUNC_BUILTIN|\ + SQLITE_FUNC_INTERNAL|SQLITE_UTF8|SQLITE_FUNC_CONSTANT, \ 0, 0, xFunc, 0, 0, 0, #zName, {0} } @@ -16729,32 +19227,84 @@ struct Savepoint { struct Module { const sqlite3_module *pModule; /* Callback pointers */ const char *zName; /* Name passed to create_module() */ + int nRefModule; /* Number of pointers to this object */ void *pAux; /* pAux passed to create_module() */ void (*xDestroy)(void *); /* Module destructor function */ Table *pEpoTab; /* Eponymous table for this module */ }; /* -** information about each column of an SQL table is held in an instance -** of this structure. +** Information about each column of an SQL table is held in an instance +** of the Column structure, in the Table.aCol[] array. +** +** Definitions: +** +** "table column index" This is the index of the column in the +** Table.aCol[] array, and also the index of +** the column in the original CREATE TABLE stmt. +** +** "storage column index" This is the index of the column in the +** record BLOB generated by the OP_MakeRecord +** opcode. The storage column index is less than +** or equal to the table column index. It is +** equal if and only if there are no VIRTUAL +** columns to the left. +** +** Notes on zCnName: +** The zCnName field stores the name of the column, the datatype of the +** column, and the collating sequence for the column, in that order, all in +** a single allocation. Each string is 0x00 terminated. The datatype +** is only included if the COLFLAG_HASTYPE bit of colFlags is set and the +** collating sequence name is only included if the COLFLAG_HASCOLL bit is +** set. */ struct Column { - char *zName; /* Name of this column, \000, then the type */ - Expr *pDflt; /* Default value of this column */ - char *zColl; /* Collating sequence. If NULL, use the default */ - u8 notNull; /* An OE_ code for handling a NOT NULL constraint */ - char affinity; /* One of the SQLITE_AFF_... values */ - u8 szEst; /* Estimated size of value in this column. sizeof(INT)==1 */ - u8 colFlags; /* Boolean properties. See COLFLAG_ defines below */ + char *zCnName; /* Name of this column */ + unsigned notNull :4; /* An OE_ code for handling a NOT NULL constraint */ + unsigned eCType :4; /* One of the standard types */ + char affinity; /* One of the SQLITE_AFF_... values */ + u8 szEst; /* Est size of value in this column. sizeof(INT)==1 */ + u8 hName; /* Column name hash for faster lookup */ + u16 iDflt; /* 1-based index of DEFAULT. 0 means "none" */ + u16 colFlags; /* Boolean properties. See COLFLAG_ defines below */ }; -/* Allowed values for Column.colFlags: -*/ -#define COLFLAG_PRIMKEY 0x0001 /* Column is part of the primary key */ -#define COLFLAG_HIDDEN 0x0002 /* A hidden column in a virtual table */ -#define COLFLAG_HASTYPE 0x0004 /* Type name follows column name */ -#define COLFLAG_UNIQUE 0x0008 /* Column def contains "UNIQUE" or "PK" */ +/* Allowed values for Column.eCType. +** +** Values must match entries in the global constant arrays +** sqlite3StdTypeLen[] and sqlite3StdType[]. Each value is one more +** than the offset into these arrays for the corresponding name. +** Adjust the SQLITE_N_STDTYPE value if adding or removing entries. +*/ +#define COLTYPE_CUSTOM 0 /* Type appended to zName */ +#define COLTYPE_ANY 1 +#define COLTYPE_BLOB 2 +#define COLTYPE_INT 3 +#define COLTYPE_INTEGER 4 +#define COLTYPE_REAL 5 +#define COLTYPE_TEXT 6 +#define SQLITE_N_STDTYPE 6 /* Number of standard types */ + +/* Allowed values for Column.colFlags. +** +** Constraints: +** TF_HasVirtual == COLFLAG_VIRTUAL +** TF_HasStored == COLFLAG_STORED +** TF_HasHidden == COLFLAG_HIDDEN +*/ +#define COLFLAG_PRIMKEY 0x0001 /* Column is part of the primary key */ +#define COLFLAG_HIDDEN 0x0002 /* A hidden column in a virtual table */ +#define COLFLAG_HASTYPE 0x0004 /* Type name follows column name */ +#define COLFLAG_UNIQUE 0x0008 /* Column def contains "UNIQUE" or "PK" */ #define COLFLAG_SORTERREF 0x0010 /* Use sorter-refs with this column */ +#define COLFLAG_VIRTUAL 0x0020 /* GENERATED ALWAYS AS ... VIRTUAL */ +#define COLFLAG_STORED 0x0040 /* GENERATED ALWAYS AS ... STORED */ +#define COLFLAG_NOTAVAIL 0x0080 /* STORED column not yet calculated */ +#define COLFLAG_BUSY 0x0100 /* Blocks recursion on GENERATED columns */ +#define COLFLAG_HASCOLL 0x0200 /* Has collating sequence name in zCnName */ +#define COLFLAG_NOEXPAND 0x0400 /* Omit this column when expanding "*" */ +#define COLFLAG_GENERATED 0x0060 /* Combo: _STORED, _VIRTUAL */ +#define COLFLAG_NOINSERT 0x0062 /* Combo: _HIDDEN, _STORED, _VIRTUAL */ /* ** A "Collating Sequence" is defined by an instance of the following @@ -16794,11 +19344,14 @@ struct CollSeq { ** Note also that the numeric types are grouped together so that testing ** for a numeric type is a single comparison. And the BLOB type is first. */ -#define SQLITE_AFF_BLOB 'A' -#define SQLITE_AFF_TEXT 'B' -#define SQLITE_AFF_NUMERIC 'C' -#define SQLITE_AFF_INTEGER 'D' -#define SQLITE_AFF_REAL 'E' +#define SQLITE_AFF_NONE 0x40 /* '@' */ +#define SQLITE_AFF_BLOB 0x41 /* 'A' */ +#define SQLITE_AFF_TEXT 0x42 /* 'B' */ +#define SQLITE_AFF_NUMERIC 0x43 /* 'C' */ +#define SQLITE_AFF_INTEGER 0x44 /* 'D' */ +#define SQLITE_AFF_REAL 0x45 /* 'E' */ +#define SQLITE_AFF_FLEXNUM 0x46 /* 'F' */ +#define SQLITE_AFF_DEFER 0x58 /* 'X' - defer computation until later */ #define sqlite3IsNumericAffinity(X) ((X)>=SQLITE_AFF_NUMERIC) @@ -16817,9 +19370,7 @@ struct CollSeq { ** operator is NULL. It is added to certain comparison operators to ** prove that the operands are always NOT NULL. */ -#define SQLITE_KEEPNULL 0x08 /* Used by vector == or <> */ #define SQLITE_JUMPIFNULL 0x10 /* jumps if either operand is NULL */ -#define SQLITE_STOREP2 0x20 /* Store result in reg[P2] rather than jump */ #define SQLITE_NULLEQ 0x80 /* NULL=NULL */ #define SQLITE_NOTNULL 0x90 /* Assert that operands are never NULL */ @@ -16871,45 +19422,61 @@ struct VTable { sqlite3_vtab *pVtab; /* Pointer to vtab instance */ int nRef; /* Number of pointers to this structure */ u8 bConstraint; /* True if constraints are supported */ + u8 bAllSchemas; /* True if might use any attached schema */ + u8 eVtabRisk; /* Riskiness of allowing hacker access */ int iSavepoint; /* Depth of the SAVEPOINT stack */ VTable *pNext; /* Next in linked list (see above) */ }; +/* Allowed values for VTable.eVtabRisk +*/ +#define SQLITE_VTABRISK_Low 0 +#define SQLITE_VTABRISK_Normal 1 +#define SQLITE_VTABRISK_High 2 + /* -** The schema for each SQL table and view is represented in memory -** by an instance of the following structure. +** The schema for each SQL table, virtual table, and view is represented +** in memory by an instance of the following structure. */ struct Table { char *zName; /* Name of the table or view */ Column *aCol; /* Information about each column */ Index *pIndex; /* List of SQL indexes on this table. */ - Select *pSelect; /* NULL for tables. Points to definition if a view. */ - FKey *pFKey; /* Linked list of all foreign keys in this table */ char *zColAff; /* String defining the affinity of each column */ ExprList *pCheck; /* All CHECK constraints */ /* ... also used as column name list in a VIEW */ - int tnum; /* Root BTree page for this table */ + Pgno tnum; /* Root BTree page for this table */ u32 nTabRef; /* Number of pointers to this Table */ u32 tabFlags; /* Mask of TF_* values */ i16 iPKey; /* If not negative, use aCol[iPKey] as the rowid */ i16 nCol; /* Number of columns in this table */ + i16 nNVCol; /* Number of columns that are not VIRTUAL */ LogEst nRowLogEst; /* Estimated rows in table - from sqlite_stat1 table */ LogEst szTabRow; /* Estimated size of each table row in bytes */ #ifdef SQLITE_ENABLE_COSTMULT LogEst costMult; /* Cost multiplier for using this table */ #endif u8 keyConf; /* What to do in case of uniqueness conflict on iPKey */ -#ifndef SQLITE_OMIT_ALTERTABLE - int addColOffset; /* Offset in CREATE TABLE stmt to add a new column */ -#endif -#ifndef SQLITE_OMIT_VIRTUALTABLE - int nModuleArg; /* Number of arguments to the module */ - char **azModuleArg; /* 0: module 1: schema 2: vtab name 3...: args */ - VTable *pVTable; /* List of VTable objects. */ -#endif - Trigger *pTrigger; /* List of triggers stored in pSchema */ + u8 eTabType; /* 0: normal, 1: virtual, 2: view */ + union { + struct { /* Used by ordinary tables: */ + int addColOffset; /* Offset in CREATE TABLE stmt to add a new column */ + FKey *pFKey; /* Linked list of all foreign keys in this table */ + ExprList *pDfltList; /* DEFAULT clauses on various columns. + ** Or the AS clause for generated columns. */ + } tab; + struct { /* Used by views: */ + Select *pSelect; /* View definition */ + } view; + struct { /* Used by virtual tables only: */ + int nArg; /* Number of arguments to the module */ + char **azArg; /* 0: module 1: schema 2: vtab name 3...: args */ + VTable *p; /* List of VTable objects. */ + } vtab; + } u; + Trigger *pTrigger; /* List of triggers on this object */ Schema *pSchema; /* Schema that contains this table */ - Table *pNextZombie; /* Next on the Parse.pZombieTab list */ + u8 aHx[16]; /* Column aHt[K%sizeof(aHt)] might have hash K */ }; /* @@ -16919,20 +19486,43 @@ struct Table { ** followed by non-hidden columns. Example: "CREATE VIRTUAL TABLE x USING ** vtab1(a HIDDEN, b);". Since "b" is a non-hidden column but "a" is hidden, ** the TF_OOOHidden attribute would apply in this case. Such tables require -** special handling during INSERT processing. -*/ -#define TF_Readonly 0x0001 /* Read-only system table */ -#define TF_Ephemeral 0x0002 /* An ephemeral table */ -#define TF_HasPrimaryKey 0x0004 /* Table has a primary key */ -#define TF_Autoincrement 0x0008 /* Integer primary key is autoincrement */ -#define TF_HasStat1 0x0010 /* nRowLogEst set from sqlite_stat1 */ -#define TF_WithoutRowid 0x0020 /* No rowid. PRIMARY KEY is the key */ -#define TF_NoVisibleRowid 0x0040 /* No user-visible "rowid" column */ -#define TF_OOOHidden 0x0080 /* Out-of-Order hidden columns */ -#define TF_StatsUsed 0x0100 /* Query planner decisions affected by - ** Index.aiRowLogEst[] values */ -#define TF_HasNotNull 0x0200 /* Contains NOT NULL constraints */ -#define TF_Shadow 0x0400 /* True for a shadow table */ +** special handling during INSERT processing. The "OOO" means "Out Of Order". +** +** Constraints: +** +** TF_HasVirtual == COLFLAG_VIRTUAL +** TF_HasStored == COLFLAG_STORED +** TF_HasHidden == COLFLAG_HIDDEN +*/ +#define TF_Readonly 0x00000001 /* Read-only system table */ +#define TF_HasHidden 0x00000002 /* Has one or more hidden columns */ +#define TF_HasPrimaryKey 0x00000004 /* Table has a primary key */ +#define TF_Autoincrement 0x00000008 /* Integer primary key is autoincrement */ +#define TF_HasStat1 0x00000010 /* nRowLogEst set from sqlite_stat1 */ +#define TF_HasVirtual 0x00000020 /* Has one or more VIRTUAL columns */ +#define TF_HasStored 0x00000040 /* Has one or more STORED columns */ +#define TF_HasGenerated 0x00000060 /* Combo: HasVirtual + HasStored */ +#define TF_WithoutRowid 0x00000080 /* No rowid. PRIMARY KEY is the key */ +#define TF_MaybeReanalyze 0x00000100 /* Maybe run ANALYZE on this table */ +#define TF_NoVisibleRowid 0x00000200 /* No user-visible "rowid" column */ +#define TF_OOOHidden 0x00000400 /* Out-of-Order hidden columns */ +#define TF_HasNotNull 0x00000800 /* Contains NOT NULL constraints */ +#define TF_Shadow 0x00001000 /* True for a shadow table */ +#define TF_HasStat4 0x00002000 /* STAT4 info available for this table */ +#define TF_Ephemeral 0x00004000 /* An ephemeral table */ +#define TF_Eponymous 0x00008000 /* An eponymous virtual table */ +#define TF_Strict 0x00010000 /* STRICT mode */ +#define TF_Imposter 0x00020000 /* An imposter table */ + +/* +** Allowed values for Table.eTabType +*/ +#define TABTYP_NORM 0 /* Ordinary table */ +#define TABTYP_VTAB 1 /* Virtual table */ +#define TABTYP_VIEW 2 /* A view */ + +#define IsView(X) ((X)->eTabType==TABTYP_VIEW) +#define IsOrdinaryTable(X) ((X)->eTabType==TABTYP_NORM) /* ** Test to see whether or not a table is a virtual table. This is @@ -16940,9 +19530,12 @@ struct Table { ** table support is omitted from the build. */ #ifndef SQLITE_OMIT_VIRTUALTABLE -# define IsVirtual(X) ((X)->nModuleArg) +# define IsVirtual(X) ((X)->eTabType==TABTYP_VTAB) +# define ExprIsVtab(X) \ + ((X)->op==TK_COLUMN && (X)->y.pTab->eTabType==TABTYP_VTAB) #else # define IsVirtual(X) 0 +# define ExprIsVtab(X) 0 #endif /* @@ -16967,6 +19560,15 @@ struct Table { #define HasRowid(X) (((X)->tabFlags & TF_WithoutRowid)==0) #define VisibleRowid(X) (((X)->tabFlags & TF_NoVisibleRowid)==0) +/* Macro is true if the SQLITE_ALLOW_ROWID_IN_VIEW (mis-)feature is +** available. By default, this macro is false +*/ +#ifndef SQLITE_ALLOW_ROWID_IN_VIEW +# define ViewCanHaveRowid 0 +#else +# define ViewCanHaveRowid (sqlite3Config.mNoVisibleRowid==0) +#endif + /* ** Each foreign key constraint is an instance of the following structure. ** @@ -17009,9 +19611,13 @@ struct FKey { struct sColMap { /* Mapping of columns in pFrom to columns in zTo */ int iFrom; /* Index of column in pFrom */ char *zCol; /* Name of column in zTo. If NULL use PRIMARY KEY */ - } aCol[1]; /* One entry for each of nCol columns */ + } aCol[FLEXARRAY]; /* One entry for each of nCol columns */ }; +/* The size (in bytes) of an FKey object holding N columns. The answer +** does NOT include space to hold the zTo name. */ +#define SZ_FKEY(N) (offsetof(FKey,aCol)+(N)*sizeof(struct sColMap)) + /* ** SQLite supports many different ways to resolve a constraint ** error. ROLLBACK processing means that a constraint violation @@ -17026,16 +19632,22 @@ struct FKey { ** is returned. REPLACE means that preexisting database rows that caused ** a UNIQUE constraint violation are removed so that the new insert or ** update can proceed. Processing continues and no error is reported. +** UPDATE applies to insert operations only and means that the insert +** is omitted and the DO UPDATE clause of an upsert is run instead. ** -** RESTRICT, SETNULL, and CASCADE actions apply only to foreign keys. +** RESTRICT, SETNULL, SETDFLT, and CASCADE actions apply only to foreign keys. ** RESTRICT is the same as ABORT for IMMEDIATE foreign keys and the ** same as ROLLBACK for DEFERRED keys. SETNULL means that the foreign -** key is set to NULL. CASCADE means that a DELETE or UPDATE of the +** key is set to NULL. SETDFLT means that the foreign key is set +** to its default value. CASCADE means that a DELETE or UPDATE of the ** referenced table row is propagated into the row that holds the ** foreign key. ** +** The OE_Default value is a place holder that means to use whatever +** conflict resolution algorithm is required from context. +** ** The following symbolic values are used to record which type -** of action to take. +** of conflict resolution action to take. */ #define OE_None 0 /* There is no constraint to check */ #define OE_Rollback 1 /* Fail the operation and rollback the transaction */ @@ -17056,9 +19668,15 @@ struct FKey { ** argument to sqlite3VdbeKeyCompare and is used to control the ** comparison of the two index keys. ** -** Note that aSortOrder[] and aColl[] have nField+1 slots. There -** are nField slots for the columns of an index then one extra slot -** for the rowid at the end. +** The aSortOrder[] and aColl[] arrays have nAllField slots each. There +** are nKeyField slots for the columns of an index then extra slots +** for the rowid or key at the end. The aSortOrder array is located after +** the aColl[] array. +** +** If SQLITE_ENABLE_PREUPDATE_HOOK is defined, then aSortFlags might be NULL +** to indicate that this object is for use by a preupdate hook. When aSortFlags +** is NULL, then nAllField is uninitialized and no space is allocated for +** aColl[], so those fields may not be used. */ struct KeyInfo { u32 nRef; /* Number of references to this KeyInfo object */ @@ -17066,10 +19684,28 @@ struct KeyInfo { u16 nKeyField; /* Number of key columns in the index */ u16 nAllField; /* Total columns, including key plus others */ sqlite3 *db; /* The database connection */ - u8 *aSortOrder; /* Sort order for each column. */ - CollSeq *aColl[1]; /* Collating sequence for each term of the key */ + u8 *aSortFlags; /* Sort order for each column. */ + CollSeq *aColl[FLEXARRAY]; /* Collating sequence for each term of the key */ }; +/* The size (in bytes) of a KeyInfo object with up to N fields. This includes +** the main body of the KeyInfo object and the aColl[] array of N elements, +** but does not count the memory used to hold aSortFlags[]. */ +#define SZ_KEYINFO(N) (offsetof(KeyInfo,aColl) + (N)*sizeof(CollSeq*)) + +/* The size of a bare KeyInfo with no aColl[] entries */ +#if FLEXARRAY+1 > 1 +# define SZ_KEYINFO_0 offsetof(KeyInfo,aColl) +#else +# define SZ_KEYINFO_0 sizeof(KeyInfo) +#endif + +/* +** Allowed bit values for entries in the KeyInfo.aSortFlags[] array. +*/ +#define KEYINFO_ORDER_DESC 0x01 /* DESC sort order */ +#define KEYINFO_ORDER_BIGNULL 0x02 /* NULL is larger than any other value */ + /* ** This object holds a record which has been parsed out into individual ** fields, for the purposes of doing a comparison. @@ -17082,9 +19718,8 @@ struct KeyInfo { ** ** An instance of this object serves as a "key" for doing a search on ** an index b+tree. The goal of the search is to find the entry that -** is closed to the key described by this object. This object might hold -** just a prefix of the key. The number of fields is given by -** pKeyInfo->nField. +** is closest to the key described by this object. This object might hold +** just a prefix of the key. The number of fields is given by nField. ** ** The r1 and r2 fields are the values to return if this key is less than ** or greater than a key in the btree, respectively. These are normally @@ -17094,7 +19729,7 @@ struct KeyInfo { ** The key comparison functions actually return default_rc when they find ** an equals comparison. default_rc can be -1, 0, or +1. If there are ** multiple entries in the b-tree with the same key (when only looking -** at the first pKeyInfo->nFields,) then default_rc can be set to -1 to +** at the first nField elements) then default_rc can be set to -1 to ** cause the search to find the last match, or +1 to cause the search to ** find the first match. ** @@ -17106,8 +19741,13 @@ struct KeyInfo { ** b-tree. */ struct UnpackedRecord { - KeyInfo *pKeyInfo; /* Collation and sort-order information */ - Mem *aMem; /* Values */ + KeyInfo *pKeyInfo; /* Comparison info for the index that is unpacked */ + Mem *aMem; /* Values for columns of the index */ + union { + char *z; /* Cache of aMem[0].z for vdbeRecordCompareString() */ + i64 i; /* Cache of aMem[0].u.i for vdbeRecordCompareInt() */ + } u; + int n; /* Cache of aMem[0].n used by vdbeRecordCompareString() */ u16 nField; /* Number of entries in apMem[] */ i8 default_rc; /* Comparison result if keys are equal */ u8 errCode; /* Error detected by xRecordCompare (CORRUPT or NOMEM) */ @@ -17139,12 +19779,24 @@ struct UnpackedRecord { ** The Index.onError field determines whether or not the indexed columns ** must be unique and what to do if they are not. When Index.onError=OE_None, ** it means this is not a unique index. Otherwise it is a unique index -** and the value of Index.onError indicate the which conflict resolution -** algorithm to employ whenever an attempt is made to insert a non-unique +** and the value of Index.onError indicates which conflict resolution +** algorithm to employ when an attempt is made to insert a non-unique ** element. ** +** The colNotIdxed bitmask is used in combination with SrcItem.colUsed +** for a fast test to see if an index can serve as a covering index. +** colNotIdxed has a 1 bit for every column of the original table that +** is *not* available in the index. Thus the expression +** "colUsed & colNotIdxed" will be non-zero if the index is not a +** covering index. The most significant bit of of colNotIdxed will always +** be true (note-20221022-a). If a column beyond the 63rd column of the +** table is used, the "colUsed & colNotIdxed" test will always be non-zero +** and we have to assume either that the index is not covering, or use +** an alternative (slower) algorithm to determine whether or not +** the index is covering. +** ** While parsing a CREATE TABLE or CREATE INDEX statement in order to -** generate VDBE code (as opposed to parsing one read from an sqlite_master +** generate VDBE code (as opposed to parsing one read from an sqlite_schema ** table as part of parsing an existing database schema), transient instances ** of this structure may be created. In this case the Index.tnum variable is ** used to store the address of a VDBE instruction, not a database page @@ -17163,10 +19815,10 @@ struct Index { const char **azColl; /* Array of collation sequence names for index */ Expr *pPartIdxWhere; /* WHERE clause for partial indices */ ExprList *aColExpr; /* Column expressions */ - int tnum; /* DB Page containing root of this index */ + Pgno tnum; /* DB Page containing root of this index */ LogEst szIdxRow; /* Estimated average row size in bytes */ u16 nKeyCol; /* Number of columns forming the key */ - u16 nColumn; /* Number of columns stored in the index */ + u16 nColumn; /* Nr columns in btree. Can be 2*Table.nCol */ u8 onError; /* OE_Abort, OE_Ignore, OE_Replace, or OE_None */ unsigned idxType:2; /* 0:Normal 1:UNIQUE, 2:PRIMARY KEY, 3:IPK */ unsigned bUnordered:1; /* Use this index for == or IN queries only */ @@ -17176,15 +19828,20 @@ struct Index { unsigned noSkipScan:1; /* Do not try to use skip-scan if true */ unsigned hasStat1:1; /* aiRowLogEst values come from sqlite_stat1 */ unsigned bNoQuery:1; /* Do not use this index to optimize queries */ -#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 + unsigned bAscKeyBug:1; /* True if the bba7b69f9849b5bf bug applies */ + unsigned bHasVCol:1; /* Index references one or more VIRTUAL columns */ + unsigned bHasExpr:1; /* Index contains an expression, either a literal + ** expression, or a reference to a VIRTUAL column */ +#ifdef SQLITE_ENABLE_STAT4 int nSample; /* Number of elements in aSample[] */ + int mxSample; /* Number of slots allocated to aSample[] */ int nSampleCol; /* Size of IndexSample.anEq[] and so on */ tRowcnt *aAvgEq; /* Average nEq values for keys not in aSample */ IndexSample *aSample; /* Samples of the left-most key */ tRowcnt *aiRowEst; /* Non-logarithmic stat1 data for this index */ tRowcnt nRowEst0; /* Non-logarithmic number of rows in the index */ #endif - Bitmask colNotIdxed; /* 0 for unindexed columns in pTab */ + Bitmask colNotIdxed; /* Unindexed columns in pTab */ }; /* @@ -17208,7 +19865,7 @@ struct Index { #define XN_EXPR (-2) /* Indexed column is an expression */ /* -** Each sample stored in the sqlite_stat3 table is represented in memory +** Each sample stored in the sqlite_stat4 table is represented in memory ** using a structure of this type. See documentation at the top of the ** analyze.c source file for additional information. */ @@ -17246,7 +19903,7 @@ struct Token { ** code for a SELECT that contains aggregate functions. ** ** If Expr.op==TK_AGG_COLUMN or TK_AGG_FUNCTION then Expr.pAggInfo is a -** pointer to this structure. The Expr.iColumn field is the index in +** pointer to this structure. The Expr.iAgg field is the index in ** AggInfo.aCol[] or AggInfo.aFunc[] of information needed to generate ** code for that node. ** @@ -17259,32 +19916,56 @@ struct AggInfo { ** from source tables rather than from accumulators */ u8 useSortingIdx; /* In direct mode, reference the sorting index rather ** than the source table */ + u32 nSortingColumn; /* Number of columns in the sorting index */ int sortingIdx; /* Cursor number of the sorting index */ int sortingIdxPTab; /* Cursor number of pseudo-table */ - int nSortingColumn; /* Number of columns in the sorting index */ - int mnReg, mxReg; /* Range of registers allocated for aCol and aFunc */ + int iFirstReg; /* First register in range for aCol[] and aFunc[] */ ExprList *pGroupBy; /* The group by clause */ struct AggInfo_col { /* For each column used in source tables */ Table *pTab; /* Source table */ + Expr *pCExpr; /* The original expression */ int iTable; /* Cursor number of the source table */ int iColumn; /* Column number within the source table */ int iSorterColumn; /* Column number in the sorting index */ - int iMem; /* Memory location that acts as accumulator */ - Expr *pExpr; /* The original expression */ } *aCol; int nColumn; /* Number of used entries in aCol[] */ int nAccumulator; /* Number of columns that show through to the output. ** Additional columns are used only as parameters to ** aggregate functions */ struct AggInfo_func { /* For each aggregate function */ - Expr *pExpr; /* Expression encoding the function */ + Expr *pFExpr; /* Expression encoding the function */ FuncDef *pFunc; /* The aggregate function implementation */ - int iMem; /* Memory location that acts as accumulator */ int iDistinct; /* Ephemeral table used to enforce DISTINCT */ + int iDistAddr; /* Address of OP_OpenEphemeral */ + int iOBTab; /* Ephemeral table to implement ORDER BY */ + u8 bOBPayload; /* iOBTab has payload columns separate from key */ + u8 bOBUnique; /* Enforce uniqueness on iOBTab keys */ + u8 bUseSubtype; /* Transfer subtype info through sorter */ } *aFunc; int nFunc; /* Number of entries in aFunc[] */ + u32 selId; /* Select to which this AggInfo belongs */ +#ifdef SQLITE_DEBUG + Select *pSelect; /* SELECT statement that this AggInfo supports */ +#endif }; +/* +** Macros to compute aCol[] and aFunc[] register numbers. +** +** These macros should not be used prior to the call to +** assignAggregateRegisters() that computes the value of pAggInfo->iFirstReg. +** The assert()s that are part of this macro verify that constraint. +*/ +#ifndef NDEBUG +#define AggInfoColumnReg(A,I) (assert((A)->iFirstReg),(A)->iFirstReg+(I)) +#define AggInfoFuncReg(A,I) \ + (assert((A)->iFirstReg),(A)->iFirstReg+(A)->nColumn+(I)) +#else +#define AggInfoColumnReg(A,I) ((A)->iFirstReg+(I)) +#define AggInfoFuncReg(A,I) \ + ((A)->iFirstReg+(A)->nColumn+(I)) +#endif + /* ** The datatype ynVar is a signed integer, either 16-bit or 32-bit. ** Usually it is 16-bits. But if SQLITE_MAX_VARIABLE_NUMBER is greater @@ -17292,10 +19973,10 @@ struct AggInfo { ** it uses less memory in the Expr object, which is a big memory user ** in systems with lots of prepared statements. And few applications ** need more than about 10 or 20 variables. But some extreme users want -** to have prepared statements with over 32767 variables, and for them +** to have prepared statements with over 32766 variables, and for them ** the option is available (at compile-time). */ -#if SQLITE_MAX_VARIABLE_NUMBER<=32767 +#if SQLITE_MAX_VARIABLE_NUMBER<32767 typedef i16 ynVar; #else typedef int ynVar; @@ -17312,10 +19993,10 @@ typedef int ynVar; ** tree. ** ** If the expression is an SQL literal (TK_INTEGER, TK_FLOAT, TK_BLOB, -** or TK_STRING), then Expr.token contains the text of the SQL literal. If -** the expression is a variable (TK_VARIABLE), then Expr.token contains the +** or TK_STRING), then Expr.u.zToken contains the text of the SQL literal. If +** the expression is a variable (TK_VARIABLE), then Expr.u.zToken contains the ** variable name. Finally, if the expression is an SQL function (TK_FUNCTION), -** then Expr.token contains the name of the function. +** then Expr.u.zToken contains the name of the function. ** ** Expr.pRight and Expr.pLeft are the left and right subexpressions of a ** binary operator. Either or both may be NULL. @@ -17355,7 +20036,7 @@ typedef int ynVar; ** help reduce memory requirements, sometimes an Expr object will be ** truncated. And to reduce the number of memory allocations, sometimes ** two or more Expr objects will be stored in a single memory allocation, -** together with Expr.zToken strings. +** together with Expr.u.zToken strings. ** ** If the EP_Reduced and EP_TokenOnly flags are set when ** an Expr object is truncated. When EP_Reduced is set, then all @@ -17366,7 +20047,14 @@ typedef int ynVar; */ struct Expr { u8 op; /* Operation performed by this node */ - char affinity; /* The affinity of the column or 0 if not a column */ + char affExpr; /* affinity, or RAISE type */ + u8 op2; /* TK_REGISTER/TK_TRUTH: original value of Expr.op + ** TK_COLUMN: the value of p5 for OP_Column + ** TK_AGG_FUNCTION: nesting depth + ** TK_FUNCTION: NC_SelfRef flag if needs OP_PureFunc */ +#ifdef SQLITE_DEBUG + u8 vvaFlags; /* Verification flags. */ +#endif u32 flags; /* Various flags. EP_* See below */ union { char *zToken; /* Token value. Zero terminated and dequoted */ @@ -17397,20 +20085,23 @@ struct Expr { ** TK_REGISTER: register number ** TK_TRIGGER: 1 -> new, 0 -> old ** EP_Unlikely: 134217728 times likelihood + ** TK_IN: ephemeral table holding RHS + ** TK_SELECT_COLUMN: Number of columns on the LHS ** TK_SELECT: 1st register of result vector */ ynVar iColumn; /* TK_COLUMN: column index. -1 for rowid. ** TK_VARIABLE: variable number (always >= 1). ** TK_SELECT_COLUMN: column of the result vector */ i16 iAgg; /* Which entry in pAggInfo->aCol[] or ->aFunc[] */ - i16 iRightJoinTable; /* If EP_FromJoin, the right table of the join */ - u8 op2; /* TK_REGISTER: original value of Expr.op - ** TK_COLUMN: the value of p5 for OP_Column - ** TK_AGG_FUNCTION: nesting depth */ + union { + int iJoin; /* If EP_OuterON or EP_InnerON, the right table */ + int iOfst; /* else: start of token from start of statement */ + } w; AggInfo *pAggInfo; /* Used by TK_AGG_COLUMN and TK_AGG_FUNCTION */ union { Table *pTab; /* TK_COLUMN: Table containing column. Can be NULL ** for a column of an index on an expression */ - Window *pWin; /* TK_FUNCTION: Window definition for the func */ + Window *pWin; /* EP_WinFunc: Window/Filter defn for a function */ + int nReg; /* TK_NULLS: Number of registers to NULL out */ struct { /* TK_IN, TK_SELECT, and TK_EXISTS */ int iAddr; /* Subroutine entry address */ int regReturn; /* Register used to hold return address */ @@ -17418,65 +20109,91 @@ struct Expr { } y; }; -/* -** The following are the meanings of bits in the Expr.flags field. +/* The following are the meanings of bits in the Expr.flags field. ** Value restrictions: ** ** EP_Agg == NC_HasAgg == SF_HasAgg ** EP_Win == NC_HasWin */ -#define EP_FromJoin 0x000001 /* Originates in ON/USING clause of outer join */ -#define EP_Distinct 0x000002 /* Aggregate function with DISTINCT keyword */ -#define EP_HasFunc 0x000004 /* Contains one or more functions of any kind */ -#define EP_FixedCol 0x000008 /* TK_Column with a known fixed value */ -#define EP_Agg 0x000010 /* Contains one or more aggregate functions */ -#define EP_VarSelect 0x000020 /* pSelect is correlated, not constant */ -#define EP_DblQuoted 0x000040 /* token.z was originally in "..." */ -#define EP_InfixFunc 0x000080 /* True for an infix function: LIKE, GLOB, etc */ -#define EP_Collate 0x000100 /* Tree contains a TK_COLLATE operator */ -#define EP_Generic 0x000200 /* Ignore COLLATE or affinity on this tree */ -#define EP_IntValue 0x000400 /* Integer value contained in u.iValue */ -#define EP_xIsSelect 0x000800 /* x.pSelect is valid (otherwise x.pList is) */ -#define EP_Skip 0x001000 /* COLLATE, AS, or UNLIKELY */ -#define EP_Reduced 0x002000 /* Expr struct EXPR_REDUCEDSIZE bytes only */ -#define EP_TokenOnly 0x004000 /* Expr struct EXPR_TOKENONLYSIZE bytes only */ -#define EP_Win 0x008000 /* Contains window functions */ -#define EP_MemToken 0x010000 /* Need to sqlite3DbFree() Expr.zToken */ -#define EP_NoReduce 0x020000 /* Cannot EXPRDUP_REDUCE this Expr */ -#define EP_Unlikely 0x040000 /* unlikely() or likelihood() function */ -#define EP_ConstFunc 0x080000 /* A SQLITE_FUNC_CONSTANT or _SLOCHNG function */ -#define EP_CanBeNull 0x100000 /* Can be null despite NOT NULL constraint */ -#define EP_Subquery 0x200000 /* Tree contains a TK_SELECT operator */ -#define EP_Alias 0x400000 /* Is an alias for a result set column */ -#define EP_Leaf 0x800000 /* Expr.pLeft, .pRight, .u.pSelect all NULL */ -#define EP_WinFunc 0x1000000 /* TK_FUNCTION with Expr.y.pWin set */ -#define EP_Subrtn 0x2000000 /* Uses Expr.y.sub. TK_IN, _SELECT, or _EXISTS */ -#define EP_Quoted 0x4000000 /* TK_ID was originally quoted */ -#define EP_Static 0x8000000 /* Held in memory not obtained from malloc() */ - -/* -** The EP_Propagate mask is a set of properties that automatically propagate +#define EP_OuterON 0x000001 /* Originates in ON/USING clause of outer join */ +#define EP_InnerON 0x000002 /* Originates in ON/USING of an inner join */ +#define EP_Distinct 0x000004 /* Aggregate function with DISTINCT keyword */ +#define EP_HasFunc 0x000008 /* Contains one or more functions of any kind */ +#define EP_Agg 0x000010 /* Contains one or more aggregate functions */ +#define EP_FixedCol 0x000020 /* TK_Column with a known fixed value */ +#define EP_VarSelect 0x000040 /* pSelect is correlated, not constant */ +#define EP_DblQuoted 0x000080 /* token.z was originally in "..." */ +#define EP_InfixFunc 0x000100 /* True for an infix function: LIKE, GLOB, etc */ +#define EP_Collate 0x000200 /* Tree contains a TK_COLLATE operator */ +#define EP_Commuted 0x000400 /* Comparison operator has been commuted */ +#define EP_IntValue 0x000800 /* Integer value contained in u.iValue */ +#define EP_xIsSelect 0x001000 /* x.pSelect is valid (otherwise x.pList is) */ +#define EP_Skip 0x002000 /* Operator does not contribute to affinity */ +#define EP_Reduced 0x004000 /* Expr struct EXPR_REDUCEDSIZE bytes only */ +#define EP_Win 0x008000 /* Contains window functions */ +#define EP_TokenOnly 0x010000 /* Expr struct EXPR_TOKENONLYSIZE bytes only */ +#define EP_FullSize 0x020000 /* Expr structure must remain full sized */ +#define EP_IfNullRow 0x040000 /* The TK_IF_NULL_ROW opcode */ +#define EP_Unlikely 0x080000 /* unlikely() or likelihood() function */ +#define EP_ConstFunc 0x100000 /* A SQLITE_FUNC_CONSTANT or _SLOCHNG function */ +#define EP_CanBeNull 0x200000 /* Can be null despite NOT NULL constraint */ +#define EP_Subquery 0x400000 /* Tree contains a TK_SELECT operator */ +#define EP_Leaf 0x800000 /* Expr.pLeft, .pRight, .u.pSelect all NULL */ +#define EP_WinFunc 0x1000000 /* TK_FUNCTION with Expr.y.pWin set */ +#define EP_Subrtn 0x2000000 /* Uses Expr.y.sub. TK_IN, _SELECT, or _EXISTS */ +#define EP_Quoted 0x4000000 /* TK_ID was originally quoted */ +#define EP_Static 0x8000000 /* Held in memory not obtained from malloc() */ +#define EP_IsTrue 0x10000000 /* Always has boolean value of TRUE */ +#define EP_IsFalse 0x20000000 /* Always has boolean value of FALSE */ +#define EP_FromDDL 0x40000000 /* Originates from sqlite_schema */ +#define EP_SubtArg 0x80000000 /* Is argument to SQLITE_SUBTYPE function */ + +/* The EP_Propagate mask is a set of properties that automatically propagate ** upwards into parent nodes. */ #define EP_Propagate (EP_Collate|EP_Subquery|EP_HasFunc) -/* -** These macros can be used to test, set, or clear bits in the +/* Macros can be used to test, set, or clear bits in the ** Expr.flags field. */ -#define ExprHasProperty(E,P) (((E)->flags&(P))!=0) -#define ExprHasAllProperty(E,P) (((E)->flags&(P))==(P)) -#define ExprSetProperty(E,P) (E)->flags|=(P) -#define ExprClearProperty(E,P) (E)->flags&=~(P) +#define ExprHasProperty(E,P) (((E)->flags&(u32)(P))!=0) +#define ExprHasAllProperty(E,P) (((E)->flags&(u32)(P))==(u32)(P)) +#define ExprSetProperty(E,P) (E)->flags|=(u32)(P) +#define ExprClearProperty(E,P) (E)->flags&=~(u32)(P) +#define ExprAlwaysTrue(E) (((E)->flags&(EP_OuterON|EP_IsTrue))==EP_IsTrue) +#define ExprAlwaysFalse(E) (((E)->flags&(EP_OuterON|EP_IsFalse))==EP_IsFalse) +#define ExprIsFullSize(E) (((E)->flags&(EP_Reduced|EP_TokenOnly))==0) + +/* Macros used to ensure that the correct members of unions are accessed +** in Expr. +*/ +#define ExprUseUToken(E) (((E)->flags&EP_IntValue)==0) +#define ExprUseUValue(E) (((E)->flags&EP_IntValue)!=0) +#define ExprUseWOfst(E) (((E)->flags&(EP_InnerON|EP_OuterON))==0) +#define ExprUseWJoin(E) (((E)->flags&(EP_InnerON|EP_OuterON))!=0) +#define ExprUseXList(E) (((E)->flags&EP_xIsSelect)==0) +#define ExprUseXSelect(E) (((E)->flags&EP_xIsSelect)!=0) +#define ExprUseYTab(E) (((E)->flags&(EP_WinFunc|EP_Subrtn))==0) +#define ExprUseYWin(E) (((E)->flags&EP_WinFunc)!=0) +#define ExprUseYSub(E) (((E)->flags&EP_Subrtn)!=0) + +/* Flags for use with Expr.vvaFlags +*/ +#define EP_NoReduce 0x01 /* Cannot EXPRDUP_REDUCE this Expr */ +#define EP_Immutable 0x02 /* Do not change this Expr node */ /* The ExprSetVVAProperty() macro is used for Verification, Validation, ** and Accreditation only. It works like ExprSetProperty() during VVA ** processes but is a no-op for delivery. */ #ifdef SQLITE_DEBUG -# define ExprSetVVAProperty(E,P) (E)->flags|=(P) +# define ExprSetVVAProperty(E,P) (E)->vvaFlags|=(P) +# define ExprHasVVAProperty(E,P) (((E)->vvaFlags&(P))!=0) +# define ExprClearVVAProperties(E) (E)->vvaFlags = 0 #else # define ExprSetVVAProperty(E,P) +# define ExprHasVVAProperty(E,P) 0 +# define ExprClearVVAProperties(E) #endif /* @@ -17494,6 +20211,18 @@ struct Expr { */ #define EXPRDUP_REDUCE 0x0001 /* Used reduced-size Expr nodes */ +/* +** True if the expression passed as an argument was a function with +** an OVER() clause (a window function). +*/ +#ifdef SQLITE_OMIT_WINDOWFUNC +# define IsWindowFunc(p) 0 +#else +# define IsWindowFunc(p) ( \ + ExprHasProperty((p), EP_WinFunc) && p->y.pWin->eFrmType!=TK_FILTER \ + ) +#endif + /* ** A list of expressions. Each expression may optionally have a ** name. An expr/name combination can be used in several ways, such @@ -17502,35 +20231,62 @@ struct Expr { ** also be used as the argument to a function, in which case the a.zName ** field is not used. ** -** By default the Expr.zSpan field holds a human-readable description of -** the expression that is used in the generation of error messages and -** column labels. In this case, Expr.zSpan is typically the text of a -** column expression as it exists in a SELECT statement. However, if -** the bSpanIsTab flag is set, then zSpan is overloaded to mean the name -** of the result column in the form: DATABASE.TABLE.COLUMN. This later -** form is used for name resolution with nested FROM clauses. +** In order to try to keep memory usage down, the Expr.a.zEName field +** is used for multiple purposes: +** +** eEName Usage +** ---------- ------------------------- +** ENAME_NAME (1) the AS of result set column +** (2) COLUMN= of an UPDATE +** +** ENAME_TAB DB.TABLE.NAME used to resolve names +** of subqueries +** +** ENAME_SPAN Text of the original result set +** expression. */ struct ExprList { int nExpr; /* Number of expressions on the list */ + int nAlloc; /* Number of a[] slots allocated */ struct ExprList_item { /* For each expression in the list */ Expr *pExpr; /* The parse tree for this expression */ - char *zName; /* Token associated with this expression */ - char *zSpan; /* Original text of the expression */ - u8 sortOrder; /* 1 for DESC or 0 for ASC */ - unsigned done :1; /* A flag to indicate when processing is finished */ - unsigned bSpanIsTab :1; /* zSpan holds DB.TABLE.COLUMN */ - unsigned reusable :1; /* Constant expression is reusable */ - unsigned bSorterRef :1; /* Defer evaluation until after sorting */ + char *zEName; /* Token associated with this expression */ + struct { + u8 sortFlags; /* Mask of KEYINFO_ORDER_* flags */ + unsigned eEName :2; /* Meaning of zEName */ + unsigned done :1; /* Indicates when processing is finished */ + unsigned reusable :1; /* Constant expression is reusable */ + unsigned bSorterRef :1; /* Defer evaluation until after sorting */ + unsigned bNulls :1; /* True if explicit "NULLS FIRST/LAST" */ + unsigned bUsed :1; /* This column used in a SF_NestedFrom subquery */ + unsigned bUsingTerm:1; /* Term from the USING clause of a NestedFrom */ + unsigned bNoExpand: 1; /* Term is an auxiliary in NestedFrom and should + ** not be expanded by "*" in parent queries */ + } fg; union { - struct { + struct { /* Used by any ExprList other than Parse.pConsExpr */ u16 iOrderByCol; /* For ORDER BY, column number in result set */ u16 iAlias; /* Index into Parse.aAlias[] for zName */ } x; - int iConstExprReg; /* Register in which Expr value is cached */ + int iConstExprReg; /* Register in which Expr value is cached. Used only + ** by Parse.pConstExpr */ } u; - } a[1]; /* One slot for each expression in the list */ + } a[FLEXARRAY]; /* One slot for each expression in the list */ }; +/* The size (in bytes) of an ExprList object that is big enough to hold +** as many as N expressions. */ +#define SZ_EXPRLIST(N) \ + (offsetof(ExprList,a) + (N)*sizeof(struct ExprList_item)) + +/* +** Allowed values for Expr.a.eEName +*/ +#define ENAME_NAME 0 /* The AS clause of a result set */ +#define ENAME_SPAN 1 /* Complete text of the result set expression */ +#define ENAME_TAB 2 /* "DB.TABLE.NAME" for the result set */ +#define ENAME_ROWID 3 /* "DB.TABLE._rowid_" for * expansion of rowid */ + /* ** An instance of this structure can hold a simple list of identifiers, ** such as the list "a,b,c" in the following statements: @@ -17547,23 +20303,36 @@ struct ExprList { ** If "a" is the k-th column of table "t", then IdList.a[0].idx==k. */ struct IdList { + int nId; /* Number of identifiers on the list */ struct IdList_item { char *zName; /* Name of the identifier */ - int idx; /* Index in some Table.aCol[] of a column named zName */ - } *a; - int nId; /* Number of identifiers on the list */ + } a[FLEXARRAY]; }; +/* The size (in bytes) of an IdList object that can hold up to N IDs. */ +#define SZ_IDLIST(N) (offsetof(IdList,a)+(N)*sizeof(struct IdList_item)) + /* -** The following structure describes the FROM clause of a SELECT statement. -** Each table or subquery in the FROM clause is a separate element of -** the SrcList.a[] array. -** -** With the addition of multiple database support, the following structure -** can also be used to describe a particular table such as the table that -** is modified by an INSERT, DELETE, or UPDATE statement. In standard SQL, -** such a table must be a simple name: ID. But in SQLite, the table can -** now be identified by a database name, a dot, then the table name: ID.ID. +** Allowed values for IdList.eType, which determines which value of the a.u4 +** is valid. +*/ +#define EU4_NONE 0 /* Does not use IdList.a.u4 */ +#define EU4_IDX 1 /* Uses IdList.a.u4.idx */ +#define EU4_EXPR 2 /* Uses IdList.a.u4.pExpr -- NOT CURRENTLY USED */ + +/* +** Details of the implementation of a subquery. +*/ +struct Subquery { + Select *pSelect; /* A SELECT statement used in place of a table name */ + int addrFillSub; /* Address of subroutine to initialize a subquery */ + int regReturn; /* Register holding return address of addrFillSub */ + int regResult; /* Registers holding results of a co-routine */ +}; + +/* +** The SrcItem object represents a single term in the FROM clause of a query. +** The SrcList object is mostly an array of SrcItems. ** ** The jointype starts out showing the join type between the current table ** and the next table on the list. The parser builds the list this way. @@ -17572,52 +20341,122 @@ struct IdList { ** ** In the colUsed field, the high-order bit (bit 63) is set if the table ** contains more than 63 columns and the 64-th or later column is used. -*/ -struct SrcList { - int nSrc; /* Number of tables or subqueries in the FROM clause */ - u32 nAlloc; /* Number of entries allocated in a[] below */ - struct SrcList_item { +** +** Aggressive use of "union" helps keep the size of the object small. This +** has been shown to boost performance, in addition to saving memory. +** Access to union elements is gated by the following rules which should +** always be checked, either by an if-statement or by an assert(). +** +** Field Only access if this is true +** --------------- ----------------------------------- +** u1.zIndexedBy fg.isIndexedBy +** u1.pFuncArg fg.isTabFunc +** u1.nRow !fg.isTabFunc && !fg.isIndexedBy +** +** u2.pIBIndex fg.isIndexedBy +** u2.pCteUse fg.isCte +** +** u3.pOn !fg.isUsing +** u3.pUsing fg.isUsing +** +** u4.zDatabase !fg.fixedSchema && !fg.isSubquery +** u4.pSchema fg.fixedSchema +** u4.pSubq fg.isSubquery +** +** See also the sqlite3SrcListDelete() routine for assert() statements that +** check invariants on the fields of this object, especially the flags +** inside the fg struct. +*/ +struct SrcItem { + char *zName; /* Name of the table */ + char *zAlias; /* The "B" part of a "A AS B" phrase. zName is the "A" */ + Table *pSTab; /* Table object for zName. Mnemonic: Srcitem-TABle */ + struct { + u8 jointype; /* Type of join between this table and the previous */ + unsigned notIndexed :1; /* True if there is a NOT INDEXED clause */ + unsigned isIndexedBy :1; /* True if there is an INDEXED BY clause */ + unsigned isSubquery :1; /* True if this term is a subquery */ + unsigned isTabFunc :1; /* True if table-valued-function syntax */ + unsigned isCorrelated :1; /* True if sub-query is correlated */ + unsigned isMaterialized:1; /* This is a materialized view */ + unsigned viaCoroutine :1; /* Implemented as a co-routine */ + unsigned isRecursive :1; /* True for recursive reference in WITH */ + unsigned fromDDL :1; /* Comes from sqlite_schema */ + unsigned isCte :1; /* This is a CTE */ + unsigned notCte :1; /* This item may not match a CTE */ + unsigned isUsing :1; /* u3.pUsing is valid */ + unsigned isOn :1; /* u3.pOn was once valid and non-NULL */ + unsigned isSynthUsing :1; /* u3.pUsing is synthesized from NATURAL */ + unsigned isNestedFrom :1; /* pSelect is a SF_NestedFrom subquery */ + unsigned rowidUsed :1; /* The ROWID of this table is referenced */ + unsigned fixedSchema :1; /* Uses u4.pSchema, not u4.zDatabase */ + unsigned hadSchema :1; /* Had u4.zDatabase before u4.pSchema */ + unsigned fromExists :1; /* Comes from WHERE EXISTS(...) */ + } fg; + int iCursor; /* The VDBE cursor number used to access this table */ + Bitmask colUsed; /* Bit N set if column N used. Details above for N>62 */ + union { + char *zIndexedBy; /* Identifier from "INDEXED BY " clause */ + ExprList *pFuncArg; /* Arguments to table-valued-function */ + u32 nRow; /* Number of rows in a VALUES clause */ + } u1; + union { + Index *pIBIndex; /* Index structure corresponding to u1.zIndexedBy */ + CteUse *pCteUse; /* CTE Usage info when fg.isCte is true */ + } u2; + union { + Expr *pOn; /* fg.isUsing==0 => The ON clause of a join */ + IdList *pUsing; /* fg.isUsing==1 => The USING clause of a join */ + } u3; + union { Schema *pSchema; /* Schema to which this item is fixed */ char *zDatabase; /* Name of database holding this table */ - char *zName; /* Name of the table */ - char *zAlias; /* The "B" part of a "A AS B" phrase. zName is the "A" */ - Table *pTab; /* An SQL table corresponding to zName */ - Select *pSelect; /* A SELECT statement used in place of a table name */ - int addrFillSub; /* Address of subroutine to manifest a subquery */ - int regReturn; /* Register holding return address of addrFillSub */ - int regResult; /* Registers holding results of a co-routine */ - struct { - u8 jointype; /* Type of join between this table and the previous */ - unsigned notIndexed :1; /* True if there is a NOT INDEXED clause */ - unsigned isIndexedBy :1; /* True if there is an INDEXED BY clause */ - unsigned isTabFunc :1; /* True if table-valued-function syntax */ - unsigned isCorrelated :1; /* True if sub-query is correlated */ - unsigned viaCoroutine :1; /* Implemented as a co-routine */ - unsigned isRecursive :1; /* True for recursive reference in WITH */ - } fg; - int iCursor; /* The VDBE cursor number used to access this table */ - Expr *pOn; /* The ON clause of a join */ - IdList *pUsing; /* The USING clause of a join */ - Bitmask colUsed; /* Bit N (1<" clause */ - ExprList *pFuncArg; /* Arguments to table-valued-function */ - } u1; - Index *pIBIndex; /* Index structure corresponding to u1.zIndexedBy */ - } a[1]; /* One entry for each identifier on the list */ + Subquery *pSubq; /* Description of a subquery */ + } u4; }; /* -** Permitted values of the SrcList.a.jointype field +** The OnOrUsing object represents either an ON clause or a USING clause. +** It can never be both at the same time, but it can be neither. +*/ +struct OnOrUsing { + Expr *pOn; /* The ON clause of a join */ + IdList *pUsing; /* The USING clause of a join */ +}; + +/* +** This object represents one or more tables that are the source of +** content for an SQL statement. For example, a single SrcList object +** is used to hold the FROM clause of a SELECT statement. SrcList also +** represents the target tables for DELETE, INSERT, and UPDATE statements. +** */ -#define JT_INNER 0x0001 /* Any kind of inner or cross join */ -#define JT_CROSS 0x0002 /* Explicit use of the CROSS keyword */ -#define JT_NATURAL 0x0004 /* True for a "natural" join */ -#define JT_LEFT 0x0008 /* Left outer join */ -#define JT_RIGHT 0x0010 /* Right outer join */ -#define JT_OUTER 0x0020 /* The "OUTER" keyword is present */ -#define JT_ERROR 0x0040 /* unknown or unsupported join type */ +struct SrcList { + int nSrc; /* Number of tables or subqueries in the FROM clause */ + u32 nAlloc; /* Number of entries allocated in a[] below */ + SrcItem a[FLEXARRAY]; /* One entry for each identifier on the list */ +}; + +/* Size (in bytes) of a SrcList object that can hold as many as N +** SrcItem objects. */ +#define SZ_SRCLIST(N) (offsetof(SrcList,a)+(N)*sizeof(SrcItem)) + +/* Size (in bytes( of a SrcList object that holds 1 SrcItem. This is a +** special case of SZ_SRCITEM(1) that comes up often. */ +#define SZ_SRCLIST_1 (offsetof(SrcList,a)+sizeof(SrcItem)) +/* +** Permitted values of the SrcList.a.jointype field +*/ +#define JT_INNER 0x01 /* Any kind of inner or cross join */ +#define JT_CROSS 0x02 /* Explicit use of the CROSS keyword */ +#define JT_NATURAL 0x04 /* True for a "natural" join */ +#define JT_LEFT 0x08 /* Left outer join */ +#define JT_RIGHT 0x10 /* Right outer join */ +#define JT_OUTER 0x20 /* The "OUTER" keyword is present */ +#define JT_LTORJ 0x40 /* One of the LEFT operands of a RIGHT JOIN + ** Mnemonic: Left Table Of Right Join */ +#define JT_ERROR 0x80 /* unknown or unsupported join type */ /* ** Flags appropriate for the wctrlFlags parameter of sqlite3WhereBegin() @@ -17638,10 +20477,10 @@ struct SrcList { #define WHERE_DISTINCTBY 0x0080 /* pOrderby is really a DISTINCT clause */ #define WHERE_WANT_DISTINCT 0x0100 /* All output needs to be distinct */ #define WHERE_SORTBYGROUP 0x0200 /* Support sqlite3WhereIsSorted() */ -#define WHERE_SEEK_TABLE 0x0400 /* Do not defer seeks on main table */ +#define WHERE_AGG_DISTINCT 0x0400 /* Query is "SELECT agg(DISTINCT ...)" */ #define WHERE_ORDERBY_LIMIT 0x0800 /* ORDERBY+LIMIT on the inner loop */ -#define WHERE_SEEK_UNIQ_TABLE 0x1000 /* Do not defer seeks if unique */ - /* 0x2000 not currently used */ +#define WHERE_RIGHT_JOIN 0x1000 /* Processing a RIGHT JOIN */ +#define WHERE_KEEP_ALL_JOINS 0x2000 /* Do not do the omit-noop-join opt */ #define WHERE_USE_LIMIT 0x4000 /* Use the LIMIT in cost estimates */ /* 0x8000 not currently used */ @@ -17680,11 +20519,13 @@ struct NameContext { ExprList *pEList; /* Optional list of result-set columns */ AggInfo *pAggInfo; /* Information about aggregates at this level */ Upsert *pUpsert; /* ON CONFLICT clause information from an upsert */ + int iBaseReg; /* For TK_REGISTER when parsing RETURNING */ } uNC; NameContext *pNext; /* Next outer name context. NULL for outermost */ int nRef; /* Number of names resolved by this context */ - int nErr; /* Number of errors encountered while resolving names */ - u16 ncFlags; /* Zero or more NC_* flags defined below */ + int nNcErr; /* Number of errors encountered while resolving names */ + int ncFlags; /* Zero or more NC_* flags defined below */ + u32 nNestedSelect; /* Number of nested selects using this NC */ Select *pWinSelect; /* SELECT statement for any window functions */ }; @@ -17692,25 +20533,34 @@ struct NameContext { ** Allowed values for the NameContext, ncFlags field. ** ** Value constraints (all checked via assert()): -** NC_HasAgg == SF_HasAgg == EP_Agg -** NC_MinMaxAgg == SF_MinMaxAgg == SQLITE_FUNC_MINMAX +** NC_HasAgg == SF_HasAgg == EP_Agg +** NC_MinMaxAgg == SF_MinMaxAgg == SQLITE_FUNC_MINMAX +** NC_OrderAgg == SF_OrderByReqd == SQLITE_FUNC_ANYORDER ** NC_HasWin == EP_Win ** */ -#define NC_AllowAgg 0x0001 /* Aggregate functions are allowed here */ -#define NC_PartIdx 0x0002 /* True if resolving a partial index WHERE */ -#define NC_IsCheck 0x0004 /* True if resolving names in a CHECK constraint */ -#define NC_InAggFunc 0x0008 /* True if analyzing arguments to an agg func */ -#define NC_HasAgg 0x0010 /* One or more aggregate functions seen */ -#define NC_IdxExpr 0x0020 /* True if resolving columns of CREATE INDEX */ -#define NC_VarSelect 0x0040 /* A correlated subquery has been seen */ -#define NC_UEList 0x0080 /* True if uNC.pEList is used */ -#define NC_UAggInfo 0x0100 /* True if uNC.pAggInfo is used */ -#define NC_UUpsert 0x0200 /* True if uNC.pUpsert is used */ -#define NC_MinMaxAgg 0x1000 /* min/max aggregates seen. See note above */ -#define NC_Complex 0x2000 /* True if a function or subquery seen */ -#define NC_AllowWin 0x4000 /* Window functions are allowed here */ -#define NC_HasWin 0x8000 /* One or more window functions seen */ +#define NC_AllowAgg 0x000001 /* Aggregate functions are allowed here */ +#define NC_PartIdx 0x000002 /* True if resolving a partial index WHERE */ +#define NC_IsCheck 0x000004 /* True if resolving a CHECK constraint */ +#define NC_GenCol 0x000008 /* True for a GENERATED ALWAYS AS clause */ +#define NC_HasAgg 0x000010 /* One or more aggregate functions seen */ +#define NC_IdxExpr 0x000020 /* True if resolving columns of CREATE INDEX */ +#define NC_SelfRef 0x00002e /* Combo: PartIdx, isCheck, GenCol, and IdxExpr */ +#define NC_Subquery 0x000040 /* A subquery has been seen */ +#define NC_UEList 0x000080 /* True if uNC.pEList is used */ +#define NC_UAggInfo 0x000100 /* True if uNC.pAggInfo is used */ +#define NC_UUpsert 0x000200 /* True if uNC.pUpsert is used */ +#define NC_UBaseReg 0x000400 /* True if uNC.iBaseReg is used */ +#define NC_MinMaxAgg 0x001000 /* min/max aggregates seen. See note above */ +/* 0x002000 // available for reuse */ +#define NC_AllowWin 0x004000 /* Window functions are allowed here */ +#define NC_HasWin 0x008000 /* One or more window functions seen */ +#define NC_IsDDL 0x010000 /* Resolving names in a CREATE statement */ +#define NC_InAggFunc 0x020000 /* True if analyzing arguments to an agg func */ +#define NC_FromDDL 0x040000 /* SQL text comes from sqlite_schema */ +#define NC_NoSelect 0x080000 /* Do not descend into sub-selects */ +#define NC_Where 0x100000 /* Processing WHERE clause of a SELECT */ +#define NC_OrderAgg 0x8000000 /* Has an aggregate other than count/min/max */ /* ** An instance of the following object describes a single ON CONFLICT @@ -17721,21 +20571,28 @@ struct NameContext { ** conflict-target clause.) The pUpsertTargetWhere is the optional ** WHERE clause used to identify partial unique indexes. ** -** pUpsertSet is the list of column=expr terms of the UPDATE statement. +** pUpsertSet is the list of column=expr terms of the UPDATE statement. ** The pUpsertSet field is NULL for a ON CONFLICT DO NOTHING. The ** pUpsertWhere is the WHERE clause for the UPDATE and is NULL if the ** WHERE clause is omitted. */ struct Upsert { - ExprList *pUpsertTarget; /* Optional description of conflicting index */ + ExprList *pUpsertTarget; /* Optional description of conflict target */ Expr *pUpsertTargetWhere; /* WHERE clause for partial index targets */ ExprList *pUpsertSet; /* The SET clause from an ON CONFLICT UPDATE */ Expr *pUpsertWhere; /* WHERE clause for the ON CONFLICT UPDATE */ - /* The fields above comprise the parse tree for the upsert clause. - ** The fields below are used to transfer information from the INSERT - ** processing down into the UPDATE processing while generating code. - ** Upsert owns the memory allocated above, but not the memory below. */ - Index *pUpsertIdx; /* Constraint that pUpsertTarget identifies */ + Upsert *pNextUpsert; /* Next ON CONFLICT clause in the list */ + u8 isDoUpdate; /* True for DO UPDATE. False for DO NOTHING */ + u8 isDup; /* True if 2nd or later with same pUpsertIdx */ + /* Above this point is the parse tree for the ON CONFLICT clauses. + ** The next group of fields stores intermediate data. */ + void *pToFree; /* Free memory when deleting the Upsert object */ + /* All fields above are owned by the Upsert object and must be freed + ** when the Upsert is destroyed. The fields below are used to transfer + ** information from the INSERT processing down into the UPDATE processing + ** while generating code. The fields below are owned by the INSERT + ** statement and will be freed by INSERT processing. */ + Index *pUpsertIdx; /* UNIQUE constraint specified by pUpsertTarget */ SrcList *pUpsertSrc; /* Table to be updated */ int regData; /* First register holding array of VALUES */ int iDataCur; /* Index of the data cursor */ @@ -17745,28 +20602,14 @@ struct Upsert { /* ** An instance of the following structure contains all information ** needed to generate code for a single SELECT statement. -** -** See the header comment on the computeLimitRegisters() routine for a -** detailed description of the meaning of the iLimit and iOffset fields. -** -** addrOpenEphm[] entries contain the address of OP_OpenEphemeral opcodes. -** These addresses must be stored so that we can go back and fill in -** the P4_KEYINFO and P2 parameters later. Neither the KeyInfo nor -** the number of columns in P2 can be computed at the same time -** as the OP_OpenEphm instruction is coded because not -** enough information about the compound query is known at that point. -** The KeyInfo for addrOpenTran[0] and [1] contains collating sequences -** for the result set. The KeyInfo for addrOpenEphm[2] contains collating -** sequences for the ORDER BY clause. */ struct Select { - ExprList *pEList; /* The fields of the result */ u8 op; /* One of: TK_UNION TK_ALL TK_INTERSECT TK_EXCEPT */ LogEst nSelectRow; /* Estimated number of result rows */ u32 selFlags; /* Various SF_* values */ int iLimit, iOffset; /* Memory registers holding LIMIT & OFFSET counters */ u32 selId; /* Unique identifier number for this SELECT */ - int addrOpenEphm[2]; /* OP_OpenEphem opcodes related to this select */ + ExprList *pEList; /* The fields of the result */ SrcList *pSrc; /* The FROM clause */ Expr *pWhere; /* The WHERE clause */ ExprList *pGroupBy; /* The GROUP BY clause */ @@ -17787,40 +20630,53 @@ struct Select { ** "Select Flag". ** ** Value constraints (all checked via assert()) -** SF_HasAgg == NC_HasAgg -** SF_MinMaxAgg == NC_MinMaxAgg == SQLITE_FUNC_MINMAX -** SF_FixedLimit == WHERE_USE_LIMIT -*/ -#define SF_Distinct 0x00001 /* Output should be DISTINCT */ -#define SF_All 0x00002 /* Includes the ALL keyword */ -#define SF_Resolved 0x00004 /* Identifiers have been resolved */ -#define SF_Aggregate 0x00008 /* Contains agg functions or a GROUP BY */ -#define SF_HasAgg 0x00010 /* Contains aggregate functions */ -#define SF_UsesEphemeral 0x00020 /* Uses the OpenEphemeral opcode */ -#define SF_Expanded 0x00040 /* sqlite3SelectExpand() called on this */ -#define SF_HasTypeInfo 0x00080 /* FROM subqueries have Table metadata */ -#define SF_Compound 0x00100 /* Part of a compound query */ -#define SF_Values 0x00200 /* Synthesized from VALUES clause */ -#define SF_MultiValue 0x00400 /* Single VALUES term with multiple rows */ -#define SF_NestedFrom 0x00800 /* Part of a parenthesized FROM clause */ -#define SF_MinMaxAgg 0x01000 /* Aggregate containing min() or max() */ -#define SF_Recursive 0x02000 /* The recursive part of a recursive CTE */ -#define SF_FixedLimit 0x04000 /* nSelectRow set by a constant LIMIT */ -#define SF_MaybeConvert 0x08000 /* Need convertCompoundSelectToSubquery() */ -#define SF_Converted 0x10000 /* By convertCompoundSelectToSubquery() */ -#define SF_IncludeHidden 0x20000 /* Include hidden columns in output */ -#define SF_ComplexResult 0x40000 /* Result contains subquery or function */ +** SF_HasAgg == NC_HasAgg +** SF_MinMaxAgg == NC_MinMaxAgg == SQLITE_FUNC_MINMAX +** SF_OrderByReqd == NC_OrderAgg == SQLITE_FUNC_ANYORDER +** SF_FixedLimit == WHERE_USE_LIMIT +*/ +#define SF_Distinct 0x0000001 /* Output should be DISTINCT */ +#define SF_All 0x0000002 /* Includes the ALL keyword */ +#define SF_Resolved 0x0000004 /* Identifiers have been resolved */ +#define SF_Aggregate 0x0000008 /* Contains agg functions or a GROUP BY */ +#define SF_HasAgg 0x0000010 /* Contains aggregate functions */ +#define SF_ClonedRhsIn 0x0000020 /* Cloned RHS of an IN operator */ +#define SF_Expanded 0x0000040 /* sqlite3SelectExpand() called on this */ +#define SF_HasTypeInfo 0x0000080 /* FROM subqueries have Table metadata */ +#define SF_Compound 0x0000100 /* Part of a compound query */ +#define SF_Values 0x0000200 /* Synthesized from VALUES clause */ +#define SF_MultiValue 0x0000400 /* Single VALUES term with multiple rows */ +#define SF_NestedFrom 0x0000800 /* Part of a parenthesized FROM clause */ +#define SF_MinMaxAgg 0x0001000 /* Aggregate containing min() or max() */ +#define SF_Recursive 0x0002000 /* The recursive part of a recursive CTE */ +#define SF_FixedLimit 0x0004000 /* nSelectRow set by a constant LIMIT */ +/* 0x0008000 // available for reuse */ +#define SF_Converted 0x0010000 /* By convertCompoundSelectToSubquery() */ +#define SF_IncludeHidden 0x0020000 /* Include hidden columns in output */ +#define SF_ComplexResult 0x0040000 /* Result contains subquery or function */ +#define SF_WhereBegin 0x0080000 /* Really a WhereBegin() call. Debug Only */ +#define SF_WinRewrite 0x0100000 /* Window function rewrite accomplished */ +#define SF_View 0x0200000 /* SELECT statement is a view */ +/* 0x0400000 // available for reuse */ +#define SF_UFSrcCheck 0x0800000 /* Check pSrc as required by UPDATE...FROM */ +#define SF_PushDown 0x1000000 /* Modified by WHERE-clause push-down opt */ +#define SF_MultiPart 0x2000000 /* Has multiple incompatible PARTITIONs */ +#define SF_CopyCte 0x4000000 /* SELECT statement is a copy of a CTE */ +#define SF_OrderByReqd 0x8000000 /* The ORDER BY clause may not be omitted */ +#define SF_UpdateFrom 0x10000000 /* Query originates with UPDATE FROM */ +#define SF_Correlated 0x20000000 /* True if references the outer context */ +#define SF_OnToWhere 0x40000000 /* One or more ON clauses moved to WHERE */ + +/* True if SrcItem X is a subquery that has SF_NestedFrom */ +#define IsNestedFrom(X) \ + ((X)->fg.isSubquery && \ + ((X)->u4.pSubq->pSelect->selFlags&SF_NestedFrom)!=0) /* ** The results of a SELECT can be distributed in several ways, as defined ** by one of the following macros. The "SRT" prefix means "SELECT Result ** Type". ** -** SRT_Union Store results as a key in a temporary index -** identified by pDest->iSDParm. -** -** SRT_Except Remove results from the temporary index pDest->iSDParm. -** ** SRT_Exists Store a 1 in memory cell pDest->iSDParm if the result ** set is not empty. ** @@ -17828,9 +20684,6 @@ struct Select { ** statements within triggers whose only purpose is ** the side-effects of functions. ** -** All of the above are free to ignore their ORDER BY clause. Those that -** follow must honor the ORDER BY clause. -** ** SRT_Output Generate a row of output (using the OP_ResultRow ** opcode) for each row in the result set. ** @@ -17842,7 +20695,11 @@ struct Select { ** SRT_Set The result must be a single column. Store each ** row of result as the key in table pDest->iSDParm. ** Apply the affinity pDest->affSdst before storing -** results. Used to implement "IN (SELECT ...)". +** results. if pDest->iSDParm2 is positive, then it is +** a register holding a Bloom filter for the IN operator +** that should be populated in addition to the +** pDest->iSDParm table. This SRT is used to +** implement "IN (SELECT ...)". ** ** SRT_EphemTab Create an temporary table pDest->iSDParm and store ** the result there. The cursor is left open after @@ -17874,36 +20731,49 @@ struct Select { ** SRT_DistQueue Store results in priority queue pDest->iSDParm only if ** the same record has never been stored before. The ** index at pDest->iSDParm+1 hold all prior stores. +** +** SRT_Upfrom Store results in the temporary table already opened by +** pDest->iSDParm. If (pDest->iSDParm<0), then the temp +** table is an intkey table - in this case the first +** column returned by the SELECT is used as the integer +** key. If (pDest->iSDParm>0), then the table is an index +** table. (pDest->iSDParm) is the number of key columns in +** each index record in this case. */ -#define SRT_Union 1 /* Store result as keys in an index */ -#define SRT_Except 2 /* Remove result from a UNION index */ -#define SRT_Exists 3 /* Store 1 if the result is not empty */ -#define SRT_Discard 4 /* Do not save the results anywhere */ -#define SRT_Fifo 5 /* Store result as data with an automatic rowid */ -#define SRT_DistFifo 6 /* Like SRT_Fifo, but unique results only */ -#define SRT_Queue 7 /* Store result in an queue */ -#define SRT_DistQueue 8 /* Like SRT_Queue, but unique results only */ +#define SRT_Exists 1 /* Store 1 if the result is not empty */ +#define SRT_Discard 2 /* Do not save the results anywhere */ +#define SRT_DistFifo 3 /* Like SRT_Fifo, but unique results only */ +#define SRT_DistQueue 4 /* Like SRT_Queue, but unique results only */ + +/* The DISTINCT clause is ignored for all of the above. Not that +** IgnorableDistinct() implies IgnorableOrderby() */ +#define IgnorableDistinct(X) ((X->eDest)<=SRT_DistQueue) + +#define SRT_Queue 5 /* Store result in an queue */ +#define SRT_Fifo 6 /* Store result as data with an automatic rowid */ /* The ORDER BY clause is ignored for all of the above */ -#define IgnorableOrderby(X) ((X->eDest)<=SRT_DistQueue) +#define IgnorableOrderby(X) ((X->eDest)<=SRT_Fifo) -#define SRT_Output 9 /* Output each row of result */ -#define SRT_Mem 10 /* Store result in a memory cell */ -#define SRT_Set 11 /* Store results as keys in an index */ -#define SRT_EphemTab 12 /* Create transient tab and store like SRT_Table */ -#define SRT_Coroutine 13 /* Generate a single row of result */ -#define SRT_Table 14 /* Store result as data with an automatic rowid */ +#define SRT_Output 7 /* Output each row of result */ +#define SRT_Mem 8 /* Store result in a memory cell */ +#define SRT_Set 9 /* Store results as keys in an index */ +#define SRT_EphemTab 10 /* Create transient tab and store like SRT_Table */ +#define SRT_Coroutine 11 /* Generate a single row of result */ +#define SRT_Table 12 /* Store result as data with an automatic rowid */ +#define SRT_Upfrom 13 /* Store result as data with rowid */ /* ** An instance of this object describes where to put of the results of ** a SELECT statement. */ struct SelectDest { - u8 eDest; /* How to dispose of the results. On of SRT_* above. */ + u8 eDest; /* How to dispose of the results. One of SRT_* above. */ int iSDParm; /* A parameter used by the eDest disposal method */ + int iSDParm2; /* A second parameter for the eDest disposal method */ int iSdst; /* Base register where results are written */ int nSdst; /* Number of registers allocated */ - char *zAffSdst; /* Affinity used when eDest==SRT_Set */ + char *zAffSdst; /* Affinity used for SRT_Set */ ExprList *pOrderBy; /* Key columns for SRT_Queue and SRT_DistQueue */ }; @@ -17962,11 +20832,45 @@ struct TriggerPrg { #else typedef unsigned int yDbMask; # define DbMaskTest(M,I) (((M)&(((yDbMask)1)<<(I)))!=0) -# define DbMaskZero(M) (M)=0 -# define DbMaskSet(M,I) (M)|=(((yDbMask)1)<<(I)) -# define DbMaskAllZero(M) (M)==0 -# define DbMaskNonZero(M) (M)!=0 +# define DbMaskZero(M) ((M)=0) +# define DbMaskSet(M,I) ((M)|=(((yDbMask)1)<<(I))) +# define DbMaskAllZero(M) ((M)==0) +# define DbMaskNonZero(M) ((M)!=0) +#endif + +/* +** For each index X that has as one of its arguments either an expression +** or the name of a virtual generated column, and if X is in scope such that +** the value of the expression can simply be read from the index, then +** there is an instance of this object on the Parse.pIdxExpr list. +** +** During code generation, while generating code to evaluate expressions, +** this list is consulted and if a matching expression is found, the value +** is read from the index rather than being recomputed. +*/ +struct IndexedExpr { + Expr *pExpr; /* The expression contained in the index */ + int iDataCur; /* The data cursor associated with the index */ + int iIdxCur; /* The index cursor */ + int iIdxCol; /* The index column that contains value of pExpr */ + u8 bMaybeNullRow; /* True if we need an OP_IfNullRow check */ + u8 aff; /* Affinity of the pExpr expression */ + IndexedExpr *pIENext; /* Next in a list of all indexed expressions */ +#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS + const char *zIdxName; /* Name of index, used only for bytecode comments */ #endif +}; + +/* +** An instance of the ParseCleanup object specifies an operation that +** should be performed after parsing to deallocation resources obtained +** during the parse and which are no longer needed. +*/ +struct ParseCleanup { + ParseCleanup *pNext; /* Next cleanup task */ + void *pPtr; /* Pointer to object to deallocate */ + void (*xCleanup)(sqlite3*,void*); /* Deallocation routine */ +}; /* ** An SQL parser context. A copy of this structure is passed through @@ -17989,16 +20893,33 @@ struct Parse { char *zErrMsg; /* An error message */ Vdbe *pVdbe; /* An engine for executing database bytecode */ int rc; /* Return code from execution */ - u8 colNamesSet; /* TRUE after OP_ColumnName has been issued to pVdbe */ - u8 checkSchema; /* Causes schema cookie check after an error */ + LogEst nQueryLoop; /* Est number of iterations of a query (10*log2(N)) */ u8 nested; /* Number of nested calls to the parser/code generator */ u8 nTempReg; /* Number of temporary registers in aTempReg[] */ u8 isMultiWrite; /* True if statement may modify/insert multiple rows */ - u8 mayAbort; /* True if statement may throw an ABORT exception */ - u8 hasCompound; /* Need to invoke convertCompoundSelectToSubquery() */ - u8 okConstFactor; /* OK to factor out constants */ u8 disableLookaside; /* Number of times lookaside has been disabled */ - u8 disableVtab; /* Disable all virtual tables for this parse */ + u8 prepFlags; /* SQLITE_PREPARE_* flags */ + u8 withinRJSubrtn; /* Nesting level for RIGHT JOIN body subroutines */ + u8 mSubrtnSig; /* mini Bloom filter on available SubrtnSig.selId */ + u8 eTriggerOp; /* TK_UPDATE, TK_INSERT or TK_DELETE */ + u8 eOrconf; /* Default ON CONFLICT policy for trigger steps */ +#if defined(SQLITE_DEBUG) || defined(SQLITE_COVERAGE_TEST) + u8 earlyCleanup; /* OOM inside sqlite3ParserAddCleanup() */ +#endif +#ifdef SQLITE_DEBUG + u8 ifNotExists; /* Might be true if IF NOT EXISTS. Assert()s only */ + u8 isCreate; /* CREATE TABLE, INDEX, or VIEW (but not TRIGGER) + ** and ALTER TABLE ADD COLUMN. */ +#endif + bft disableTriggers:1; /* True to disable triggers */ + bft mayAbort :1; /* True if statement may throw an ABORT exception */ + bft hasCompound :1; /* Need to invoke convertCompoundSelectToSubquery() */ + bft bReturning :1; /* Coding a RETURNING trigger */ + bft bHasExists :1; /* Has a correlated "EXISTS (SELECT ....)" expression */ + bft colNamesSet :1; /* TRUE after OP_ColumnName has been issued to pVdbe */ + bft bHasWith :1; /* True if statement contains WITH */ + bft okConstFactor:1; /* OK to factor out constants */ + bft checkSchema :1; /* Causes schema cookie check after an error */ int nRangeReg; /* Size of the temporary register block */ int iRangeReg; /* First register in temporary register block */ int nErr; /* Number of errors seen */ @@ -18007,17 +20928,20 @@ struct Parse { int szOpAlloc; /* Bytes of memory space allocated for Vdbe.aOp[] */ int iSelfTab; /* Table associated with an index on expr, or negative ** of the base register during check-constraint eval */ + int nNestSel; /* Number of nested SELECT statements and/or VIEWs */ int nLabel; /* The *negative* of the number of labels used */ int nLabelAlloc; /* Number of slots in aLabel */ int *aLabel; /* Space to hold the labels */ ExprList *pConstExpr;/* Constant expressions */ - Token constraintName;/* Name of the constraint currently being parsed */ + IndexedExpr *pIdxEpr;/* List of expressions used by active indexes */ + IndexedExpr *pIdxPartExpr; /* Exprs constrained by index WHERE clauses */ yDbMask writeMask; /* Start a write transaction on these databases */ yDbMask cookieMask; /* Bitmask of schema verified databases */ - int regRowid; /* Register holding rowid of CREATE TABLE entry */ - int regRoot; /* Register holding root page number for new objects */ - int nMaxArg; /* Max args passed to user function by sub-program */ + int nMaxArg; /* Max args to xUpdate and xFilter vtab methods */ int nSelect; /* Number of SELECT stmts. Counter for Select.selId */ +#ifndef SQLITE_OMIT_PROGRESS_CALLBACK + u32 nProgressSteps; /* xProgress steps taken during sqlite3_prepare() */ +#endif #ifndef SQLITE_OMIT_SHARED_CACHE int nTableLock; /* Number of locks in aTableLock */ TableLock *aTableLock; /* Required table locks for shared-cache mode */ @@ -18025,14 +20949,8 @@ struct Parse { AutoincInfo *pAinc; /* Information about AUTOINCREMENT counters */ Parse *pToplevel; /* Parse structure for main program (or NULL) */ Table *pTriggerTab; /* Table triggers are being coded for */ - Parse *pParentParse; /* Parent parser if this parser is nested */ - int addrCrTab; /* Address of OP_CreateBtree opcode on CREATE TABLE */ - u32 nQueryLoop; /* Est number of iterations of a query (10*log2(N)) */ - u32 oldmask; /* Mask of old.* columns referenced */ - u32 newmask; /* Mask of new.* columns referenced */ - u8 eTriggerOp; /* TK_UPDATE, TK_INSERT or TK_DELETE */ - u8 eOrconf; /* Default ON CONFLICT policy for trigger steps */ - u8 disableTriggers; /* True to disable triggers */ + TriggerPrg *pTriggerPrg; /* Linked list of coded triggers */ + ParseCleanup *pCleanup; /* List of cleanup operations to run after parse */ /************************************************************************** ** Fields above must be initialized to zero. The fields that follow, @@ -18042,7 +20960,21 @@ struct Parse { **************************************************************************/ int aTempReg[8]; /* Holding area for temporary registers */ + Parse *pOuterParse; /* Outer Parse object when nested */ Token sNameToken; /* Token with unqualified schema object name */ + u32 oldmask; /* Mask of old.* columns referenced */ + u32 newmask; /* Mask of new.* columns referenced */ + union { + struct { /* These fields available when isCreate is true */ + int addrCrTab; /* Address of OP_CreateBtree on CREATE TABLE */ + int regRowid; /* Register holding rowid of CREATE TABLE entry */ + int regRoot; /* Register holding root page for new objects */ + Token constraintName; /* Name of the constraint currently being parsed */ + } cr; + struct { /* These fields available to all other statements */ + Returning *pReturning; /* The RETURNING clause */ + } d; + } u1; /************************************************************************ ** Above is constant between recursions. Below is reset before and after @@ -18055,16 +20987,12 @@ struct Parse { ynVar nVar; /* Number of '?' variables seen in the SQL so far */ u8 iPkSortOrder; /* ASC or DESC for INTEGER PRIMARY KEY */ u8 explain; /* True if the EXPLAIN flag is found on the query */ -#if !(defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_OMIT_ALTERTABLE)) u8 eParseMode; /* PARSE_MODE_XXX constant */ -#endif #ifndef SQLITE_OMIT_VIRTUALTABLE int nVtabLock; /* Number of virtual tables to lock */ #endif int nHeight; /* Expression tree height of current sub-select */ -#ifndef SQLITE_OMIT_EXPLAIN int addrExplain; /* Address of current OP_Explain opcode */ -#endif VList *pVList; /* Mapping between variable names and numbers */ Vdbe *pReprepare; /* VM being reprepared (sqlite3Reprepare()) */ const char *zTail; /* All SQL text past the last semicolon parsed */ @@ -18078,24 +21006,24 @@ struct Parse { Token sArg; /* Complete text of a module argument */ Table **apVtabLock; /* Pointer to virtual tables needing locking */ #endif - Table *pZombieTab; /* List of Table objects to delete after code gen */ - TriggerPrg *pTriggerPrg; /* Linked list of coded triggers */ With *pWith; /* Current WITH clause, or NULL */ - With *pWithToFree; /* Free this WITH object at the end of the parse */ #ifndef SQLITE_OMIT_ALTERTABLE RenameToken *pRename; /* Tokens subject to renaming by ALTER TABLE */ #endif }; +/* Allowed values for Parse.eParseMode +*/ #define PARSE_MODE_NORMAL 0 #define PARSE_MODE_DECLARE_VTAB 1 -#define PARSE_MODE_RENAME_COLUMN 2 -#define PARSE_MODE_RENAME_TABLE 3 +#define PARSE_MODE_RENAME 2 +#define PARSE_MODE_UNMAP 3 /* ** Sizes and pointers of various parts of the Parse object. */ -#define PARSE_HDR_SZ offsetof(Parse,aTempReg) /* Recursive part w/o aColCache*/ +#define PARSE_HDR(X) (((char*)(X))+offsetof(Parse,zErrMsg)) +#define PARSE_HDR_SZ (offsetof(Parse,aTempReg)-offsetof(Parse,zErrMsg)) /* Recursive part w/o aColCache*/ #define PARSE_RECURSE_SZ offsetof(Parse,sLastToken) /* Recursive part */ #define PARSE_TAIL_SZ (sizeof(Parse)-PARSE_RECURSE_SZ) /* Non-recursive part */ #define PARSE_TAIL(X) (((char*)(X))+PARSE_RECURSE_SZ) /* Pointer to tail */ @@ -18112,7 +21040,7 @@ struct Parse { #if defined(SQLITE_OMIT_ALTERTABLE) #define IN_RENAME_OBJECT 0 #else - #define IN_RENAME_OBJECT (pParse->eParseMode>=PARSE_MODE_RENAME_COLUMN) + #define IN_RENAME_OBJECT (pParse->eParseMode>=PARSE_MODE_RENAME) #endif #if defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_OMIT_ALTERTABLE) @@ -18153,6 +21081,7 @@ struct AuthContext { #define OPFLAG_ISNOOP 0x40 /* OP_Delete does pre-update-hook only */ #define OPFLAG_LENGTHARG 0x40 /* OP_Column only used for length() */ #define OPFLAG_TYPEOFARG 0x80 /* OP_Column only used for typeof() */ +#define OPFLAG_BYTELENARG 0xc0 /* OP_Column only for octet_length() */ #define OPFLAG_BULKCSR 0x01 /* OP_Open** used to open bulk cursor */ #define OPFLAG_SEEKEQ 0x02 /* OP_Open** cursor uses EQ seek only */ #define OPFLAG_FORDELETE 0x08 /* OP_Open should use BTREE_FORDELETE */ @@ -18161,27 +21090,29 @@ struct AuthContext { #define OPFLAG_SAVEPOSITION 0x02 /* OP_Delete/Insert: save cursor pos */ #define OPFLAG_AUXDELETE 0x04 /* OP_Delete: index in a DELETE op */ #define OPFLAG_NOCHNG_MAGIC 0x6d /* OP_MakeRecord: serialtype 10 is ok */ +#define OPFLAG_PREFORMAT 0x80 /* OP_Insert uses preformatted cell */ /* - * Each trigger present in the database schema is stored as an instance of - * struct Trigger. - * - * Pointers to instances of struct Trigger are stored in two ways. - * 1. In the "trigHash" hash table (part of the sqlite3* that represents the - * database). This allows Trigger structures to be retrieved by name. - * 2. All triggers associated with a single table form a linked list, using the - * pNext member of struct Trigger. A pointer to the first element of the - * linked list is stored as the "pTrigger" member of the associated - * struct Table. - * - * The "step_list" member points to the first element of a linked list - * containing the SQL statements specified as the trigger program. - */ +** Each trigger present in the database schema is stored as an instance of +** struct Trigger. +** +** Pointers to instances of struct Trigger are stored in two ways. +** 1. In the "trigHash" hash table (part of the sqlite3* that represents the +** database). This allows Trigger structures to be retrieved by name. +** 2. All triggers associated with a single table form a linked list, using the +** pNext member of struct Trigger. A pointer to the first element of the +** linked list is stored as the "pTrigger" member of the associated +** struct Table. +** +** The "step_list" member points to the first element of a linked list +** containing the SQL statements specified as the trigger program. +*/ struct Trigger { char *zName; /* The name of the trigger */ char *table; /* The table or view to which the trigger applies */ u8 op; /* One of TK_DELETE, TK_UPDATE, TK_INSERT */ u8 tr_tm; /* One of TRIGGER_BEFORE, TRIGGER_AFTER */ + u8 bReturning; /* This trigger implements a RETURNING clause */ Expr *pWhen; /* The WHEN clause of the expression (may be NULL) */ IdList *pColumns; /* If this is an UPDATE OF trigger, the is stored here */ @@ -18202,51 +21133,57 @@ struct Trigger { #define TRIGGER_AFTER 2 /* - * An instance of struct TriggerStep is used to store a single SQL statement - * that is a part of a trigger-program. - * - * Instances of struct TriggerStep are stored in a singly linked list (linked - * using the "pNext" member) referenced by the "step_list" member of the - * associated struct Trigger instance. The first element of the linked list is - * the first step of the trigger-program. - * - * The "op" member indicates whether this is a "DELETE", "INSERT", "UPDATE" or - * "SELECT" statement. The meanings of the other members is determined by the - * value of "op" as follows: - * - * (op == TK_INSERT) - * orconf -> stores the ON CONFLICT algorithm - * pSelect -> If this is an INSERT INTO ... SELECT ... statement, then - * this stores a pointer to the SELECT statement. Otherwise NULL. - * zTarget -> Dequoted name of the table to insert into. - * pExprList -> If this is an INSERT INTO ... VALUES ... statement, then - * this stores values to be inserted. Otherwise NULL. - * pIdList -> If this is an INSERT INTO ... () VALUES ... - * statement, then this stores the column-names to be - * inserted into. - * - * (op == TK_DELETE) - * zTarget -> Dequoted name of the table to delete from. - * pWhere -> The WHERE clause of the DELETE statement if one is specified. - * Otherwise NULL. - * - * (op == TK_UPDATE) - * zTarget -> Dequoted name of the table to update. - * pWhere -> The WHERE clause of the UPDATE statement if one is specified. - * Otherwise NULL. - * pExprList -> A list of the columns to update and the expressions to update - * them to. See sqlite3Update() documentation of "pChanges" - * argument. - * - */ +** An instance of struct TriggerStep is used to store a single SQL statement +** that is a part of a trigger-program. +** +** Instances of struct TriggerStep are stored in a singly linked list (linked +** using the "pNext" member) referenced by the "step_list" member of the +** associated struct Trigger instance. The first element of the linked list is +** the first step of the trigger-program. +** +** The "op" member indicates whether this is a "DELETE", "INSERT", "UPDATE" or +** "SELECT" statement. The meanings of the other members is determined by the +** value of "op" as follows: +** +** (op == TK_INSERT) +** orconf -> stores the ON CONFLICT algorithm +** pSelect -> The content to be inserted - either a SELECT statement or +** a VALUES clause. +** pSrc -> Table to insert into. +** pIdList -> If this is an INSERT INTO ... () VALUES ... +** statement, then this stores the column-names to be +** inserted into. +** pUpsert -> The ON CONFLICT clauses for an Upsert +** +** (op == TK_DELETE) +** pSrc -> Table to delete from +** pWhere -> The WHERE clause of the DELETE statement if one is specified. +** Otherwise NULL. +** +** (op == TK_UPDATE) +** pSrc -> Table to update, followed by any FROM clause tables. +** pWhere -> The WHERE clause of the UPDATE statement if one is specified. +** Otherwise NULL. +** pExprList -> A list of the columns to update and the expressions to update +** them to. See sqlite3Update() documentation of "pChanges" +** argument. +** +** (op == TK_SELECT) +** pSelect -> The SELECT statement +** +** (op == TK_RETURNING) +** pExprList -> The list of expressions that follow the RETURNING keyword. +** +*/ struct TriggerStep { - u8 op; /* One of TK_DELETE, TK_UPDATE, TK_INSERT, TK_SELECT */ + u8 op; /* One of TK_DELETE, TK_UPDATE, TK_INSERT, TK_SELECT, + ** or TK_RETURNING */ u8 orconf; /* OE_Rollback etc. */ Trigger *pTrig; /* The trigger that this step is a part of */ Select *pSelect; /* SELECT statement or RHS of INSERT INTO SELECT ... */ - char *zTarget; /* Target table for DELETE, UPDATE, INSERT */ + SrcList *pSrc; /* Table to insert/update/delete */ Expr *pWhere; /* The WHERE clause for DELETE or UPDATE steps */ - ExprList *pExprList; /* SET clause for UPDATE */ + ExprList *pExprList; /* SET clause for UPDATE, or RETURNING clause */ IdList *pIdList; /* Column names for INSERT */ Upsert *pUpsert; /* Upsert clauses on an INSERT */ char *zSpan; /* Original SQL text of this command */ @@ -18255,22 +21192,21 @@ struct TriggerStep { }; /* -** The following structure contains information used by the sqliteFix... -** routines as they walk the parse tree to make database references -** explicit. +** Information about a RETURNING clause */ -typedef struct DbFixer DbFixer; -struct DbFixer { - Parse *pParse; /* The parsing context. Error messages written here */ - Schema *pSchema; /* Fix items to this schema */ - int bVarOnly; /* Check for variable references only */ - const char *zDb; /* Make sure all objects are contained in this database */ - const char *zType; /* Type of the container - used for error messages */ - const Token *pName; /* Name of the container - used for error messages */ +struct Returning { + Parse *pParse; /* The parse that includes the RETURNING clause */ + ExprList *pReturnEL; /* List of expressions to return */ + Trigger retTrig; /* The transient trigger that implements RETURNING */ + TriggerStep retTStep; /* The trigger step */ + int iRetCur; /* Transient table holding RETURNING results */ + int nRetCol; /* Number of in pReturnEL after expansion */ + int iRetReg; /* Register array for holding a row of RETURNING */ + char zName[40]; /* Name of trigger: "sqlite_returning_%p" */ }; /* -** An objected used to accumulate the text of a string where we +** An object used to accumulate the text of a string where we ** do not necessarily know how big the string will be in the end. */ struct sqlite3_str { @@ -18284,10 +21220,32 @@ struct sqlite3_str { }; #define SQLITE_PRINTF_INTERNAL 0x01 /* Internal-use-only converters allowed */ #define SQLITE_PRINTF_SQLFUNC 0x02 /* SQL function arguments to VXPrintf */ -#define SQLITE_PRINTF_MALLOCED 0x04 /* True if xText is allocated space */ +#define SQLITE_PRINTF_MALLOCED 0x04 /* True if zText is allocated space */ #define isMalloced(X) (((X)->printfFlags & SQLITE_PRINTF_MALLOCED)!=0) +/* +** The following object is the header for an "RCStr" or "reference-counted +** string". An RCStr is passed around and used like any other char* +** that has been dynamically allocated. The important interface +** differences: +** +** 1. RCStr strings are reference counted. They are deallocated +** when the reference count reaches zero. +** +** 2. Use sqlite3RCStrUnref() to free an RCStr string rather than +** sqlite3_free() +** +** 3. Make a (read-only) copy of a read-only RCStr string using +** sqlite3RCStrRef(). +** +** "String" is in the name, but an RCStr object can also be used to hold +** binary data. +*/ +struct RCStr { + u64 nRCRef; /* Number of references */ + /* Total structure size should be a multiple of 8 bytes for alignment */ +}; /* ** A pointer to this structure is used to communicate information @@ -18300,12 +21258,33 @@ typedef struct { int rc; /* Result code stored here */ u32 mInitFlags; /* Flags controlling error messages */ u32 nInitRow; /* Number of rows processed */ + Pgno mxPage; /* Maximum page number. 0 for no limit. */ } InitData; /* ** Allowed values for mInitFlags */ -#define INITFLAG_AlterTable 0x0001 /* This is a reparse after ALTER TABLE */ +#define INITFLAG_AlterMask 0x0007 /* Types of ALTER */ +#define INITFLAG_AlterRename 0x0001 /* Reparse after a RENAME */ +#define INITFLAG_AlterDrop 0x0002 /* Reparse after a DROP COLUMN */ +#define INITFLAG_AlterAdd 0x0003 /* Reparse after an ADD COLUMN */ +#define INITFLAG_AlterDropCons 0x0004 /* Reparse after an ADD COLUMN */ + +/* Tuning parameters are set using SQLITE_TESTCTRL_TUNE and are controlled +** on debug-builds of the CLI using ".testctrl tune ID VALUE". Tuning +** parameters are for temporary use during development, to help find +** optimal values for parameters in the query planner. The should not +** be used on trunk check-ins. They are a temporary mechanism available +** for transient development builds only. +** +** Tuning parameters are numbered starting with 1. +*/ +#define SQLITE_NTUNE 6 /* Should be zero for all trunk check-ins */ +#ifdef SQLITE_DEBUG +# define Tuning(X) (sqlite3Config.aTune[(X)-1]) +#else +# define Tuning(X) 0 +#endif /* ** Structure containing global configuration data for the SQLite library. @@ -18314,11 +21293,15 @@ typedef struct { */ struct Sqlite3Config { int bMemstat; /* True to enable memory status */ - int bCoreMutex; /* True to enable core mutexing */ - int bFullMutex; /* True to enable full mutexing */ - int bOpenUri; /* True to interpret filenames as URIs */ - int bUseCis; /* Use covering indices for full-scans */ - int bSmallMalloc; /* Avoid large memory allocations if true */ + u8 bCoreMutex; /* True to enable core mutexing */ + u8 bFullMutex; /* True to enable full mutexing */ + u8 bOpenUri; /* True to interpret filenames as URIs */ + u8 bUseCis; /* Use covering indices for full-scans */ + u8 bSmallMalloc; /* Avoid large memory allocations if true */ + u8 bExtraSchemaChecks; /* Verify type,name,tbl_name in schema */ +#ifdef SQLITE_DEBUG + u8 bJsonSelfcheck; /* Double-check JSON parsing */ +#endif int mxStrlen; /* Maximum string length */ int neverCorrupt; /* Database is always well-formed */ int szLookaside; /* Default lookaside buffer size */ @@ -18360,16 +21343,26 @@ struct Sqlite3Config { void (*xVdbeBranch)(void*,unsigned iSrcLine,u8 eThis,u8 eMx); /* Callback */ void *pVdbeBranchArg; /* 1st argument */ #endif -#ifdef SQLITE_ENABLE_DESERIALIZE +#ifndef SQLITE_OMIT_DESERIALIZE sqlite3_int64 mxMemdbSize; /* Default max memdb size */ #endif #ifndef SQLITE_UNTESTABLE int (*xTestCallback)(int); /* Invoked by sqlite3FaultSim() */ +#endif +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + u32 mNoVisibleRowid; /* TF_NoVisibleRowid if the ROWID_IN_VIEW + ** feature is disabled. 0 if rowids can + ** occur in views. */ #endif int bLocaltimeFault; /* True to fail localtime() calls */ - int bInternalFunctions; /* Internal SQL functions are visible */ + int (*xAltLocaltime)(const void*,void*); /* Alternative localtime() routine */ int iOnceResetThreshold; /* When to reset OP_Once counters */ u32 szSorterRef; /* Min size in bytes to use sorter-refs */ + unsigned int iPrngSeed; /* Alternative fixed seed for the PRNG */ + /* vvvv--- must be last ---vvv */ +#ifdef SQLITE_DEBUG + sqlite3_int64 aTune[SQLITE_NTUNE]; /* Tuning parameters */ +#endif }; /* @@ -18399,27 +21392,50 @@ struct Walker { int (*xSelectCallback)(Walker*,Select*); /* Callback for SELECTs */ void (*xSelectCallback2)(Walker*,Select*);/* Second callback for SELECTs */ int walkerDepth; /* Number of subqueries */ - u8 eCode; /* A small processing code */ + u16 eCode; /* A small processing code */ + u16 mWFlags; /* Use-dependent flags */ union { /* Extra data for callback */ NameContext *pNC; /* Naming context */ int n; /* A counter */ int iCur; /* A cursor number */ + int sz; /* String literal length */ SrcList *pSrcList; /* FROM clause */ - struct SrcCount *pSrcCount; /* Counting column references */ struct CCurHint *pCCurHint; /* Used by codeCursorHint() */ + struct RefSrcList *pRefSrcList; /* sqlite3ReferencesSrcList() */ int *aiCol; /* array of column indexes */ struct IdxCover *pIdxCover; /* Check for index coverage */ - struct IdxExprTrans *pIdxTrans; /* Convert idxed expr to column */ ExprList *pGroupBy; /* GROUP BY clause */ Select *pSelect; /* HAVING to WHERE clause ctx */ struct WindowRewrite *pRewrite; /* Window rewrite context */ struct WhereConst *pConst; /* WHERE clause constants */ struct RenameCtx *pRename; /* RENAME COLUMN context */ + struct Table *pTab; /* Table of generated column */ + struct CoveringIndexCheck *pCovIdxCk; /* Check for covering index */ + SrcItem *pSrcItem; /* A single FROM clause item */ + DbFixer *pFix; /* See sqlite3FixSelect() */ + Mem *aMem; /* See sqlite3BtreeCursorHint() */ + struct CheckOnCtx *pCheckOnCtx; /* See selectCheckOnClauses() */ } u; }; +/* +** The following structure contains information used by the sqliteFix... +** routines as they walk the parse tree to make database references +** explicit. +*/ +struct DbFixer { + Parse *pParse; /* The parsing context. Error messages written here */ + Walker w; /* Walker object */ + Schema *pSchema; /* Fix items to this schema */ + u8 bTemp; /* True for TEMP schema entries */ + const char *zDb; /* Make sure all objects are contained in this database */ + const char *zType; /* Type of the container - used for error messages */ + const Token *pName; /* Name of the container - used for error messages */ +}; + /* Forward declarations */ SQLITE_PRIVATE int sqlite3WalkExpr(Walker*, Expr*); +SQLITE_PRIVATE int sqlite3WalkExprNN(Walker*, Expr*); SQLITE_PRIVATE int sqlite3WalkExprList(Walker*, ExprList*); SQLITE_PRIVATE int sqlite3WalkSelect(Walker*, Select*); SQLITE_PRIVATE int sqlite3WalkSelectExpr(Walker*, Select*); @@ -18427,10 +21443,20 @@ SQLITE_PRIVATE int sqlite3WalkSelectFrom(Walker*, Select*); SQLITE_PRIVATE int sqlite3ExprWalkNoop(Walker*, Expr*); SQLITE_PRIVATE int sqlite3SelectWalkNoop(Walker*, Select*); SQLITE_PRIVATE int sqlite3SelectWalkFail(Walker*, Select*); +SQLITE_PRIVATE int sqlite3WalkerDepthIncrease(Walker*,Select*); +SQLITE_PRIVATE void sqlite3WalkerDepthDecrease(Walker*,Select*); +SQLITE_PRIVATE void sqlite3WalkWinDefnDummyCallback(Walker*,Select*); + #ifdef SQLITE_DEBUG SQLITE_PRIVATE void sqlite3SelectWalkAssert2(Walker*, Select*); #endif +#ifndef SQLITE_OMIT_CTE +SQLITE_PRIVATE void sqlite3SelectPopWith(Walker*, Select*); +#else +# define sqlite3SelectPopWith 0 +#endif + /* ** Return code from the parse-tree walking primitives and their ** callbacks. @@ -18440,20 +21466,74 @@ SQLITE_PRIVATE void sqlite3SelectWalkAssert2(Walker*, Select*); #define WRC_Abort 2 /* Abandon the tree walk */ /* -** An instance of this structure represents a set of one or more CTEs -** (common table expressions) created by a single WITH clause. +** A single common table expression +*/ +struct Cte { + char *zName; /* Name of this CTE */ + ExprList *pCols; /* List of explicit column names, or NULL */ + Select *pSelect; /* The definition of this CTE */ + const char *zCteErr; /* Error message for circular references */ + CteUse *pUse; /* Usage information for this CTE */ + u8 eM10d; /* The MATERIALIZED flag */ +}; + +/* +** Allowed values for the materialized flag (eM10d): +*/ +#define M10d_Yes 0 /* AS MATERIALIZED */ +#define M10d_Any 1 /* Not specified. Query planner's choice */ +#define M10d_No 2 /* AS NOT MATERIALIZED */ + +/* +** An instance of the With object represents a WITH clause containing +** one or more CTEs (common table expressions). */ struct With { - int nCte; /* Number of CTEs in the WITH clause */ - With *pOuter; /* Containing WITH clause, or NULL */ - struct Cte { /* For each CTE in the WITH clause.... */ - char *zName; /* Name of this CTE */ - ExprList *pCols; /* List of explicit column names, or NULL */ - Select *pSelect; /* The definition of this CTE */ - const char *zCteErr; /* Error message for circular references */ - } a[1]; + int nCte; /* Number of CTEs in the WITH clause */ + int bView; /* Belongs to the outermost Select of a view */ + With *pOuter; /* Containing WITH clause, or NULL */ + Cte a[FLEXARRAY]; /* For each CTE in the WITH clause.... */ +}; + +/* The size (in bytes) of a With object that can hold as many +** as N different CTEs. */ +#define SZ_WITH(N) (offsetof(With,a) + (N)*sizeof(Cte)) + +/* +** The Cte object is not guaranteed to persist for the entire duration +** of code generation. (The query flattener or other parser tree +** edits might delete it.) The following object records information +** about each Common Table Expression that must be preserved for the +** duration of the parse. +** +** The CteUse objects are freed using sqlite3ParserAddCleanup() rather +** than sqlite3SelectDelete(), which is what enables them to persist +** until the end of code generation. +*/ +struct CteUse { + int nUse; /* Number of users of this CTE */ + int addrM9e; /* Start of subroutine to compute materialization */ + int regRtn; /* Return address register for addrM9e subroutine */ + int iCur; /* Ephemeral table holding the materialization */ + LogEst nRowEst; /* Estimated number of rows in the table */ + u8 eM10d; /* The MATERIALIZED flag */ +}; + + +/* Client data associated with sqlite3_set_clientdata() and +** sqlite3_get_clientdata(). +*/ +struct DbClientData { + DbClientData *pNext; /* Next in a linked list */ + void *pData; /* The data */ + void (*xDestructor)(void*); /* Destructor. Might be NULL */ + char zName[FLEXARRAY]; /* Name of this client data. MUST BE LAST */ }; +/* The size (in bytes) of a DbClientData object that can has a name +** that is N bytes long, including the zero-terminator. */ +#define SZ_DBCLIENTDATA(N) (offsetof(DbClientData,zName)+(N)) + #ifdef SQLITE_DEBUG /* ** An instance of the TreeView object is used for printing the content of @@ -18466,10 +21546,11 @@ struct TreeView { #endif /* SQLITE_DEBUG */ /* -** This object is used in various ways, all related to window functions +** This object is used in various ways, most (but not all) related to window +** functions. ** ** (1) A single instance of this structure is attached to the -** the Expr.pWin field for each window function in an expression tree. +** the Expr.y.pWin field for each window function in an expression tree. ** This object holds the information contained in the OVER clause, ** plus additional fields used during code generation. ** @@ -18480,6 +21561,10 @@ struct TreeView { ** (3) The terms of the WINDOW clause of a SELECT are instances of this ** object on a linked list attached to Select.pWinDefn. ** +** (4) For an aggregate function with a FILTER clause, an instance +** of this object is stored in Expr.y.pWin with eFrmType set to +** TK_FILTER. In this case the only field used is Window.pFilter. +** ** The uses (1) and (2) are really the same Window object that just happens ** to be accessible in two different ways. Use case (3) are separate objects. */ @@ -18495,12 +21580,13 @@ struct Window { u8 eExclude; /* TK_NO, TK_CURRENT, TK_TIES, TK_GROUP, or 0 */ Expr *pStart; /* Expression for " PRECEDING" */ Expr *pEnd; /* Expression for " FOLLOWING" */ + Window **ppThis; /* Pointer to this object in Select.pWin list */ Window *pNextWin; /* Next window function belonging to this SELECT */ Expr *pFilter; /* The FILTER expression */ - FuncDef *pFunc; /* The function */ + FuncDef *pWFunc; /* The function */ int iEphCsr; /* Partition buffer or Peer buffer */ - int regAccum; - int regResult; + int regAccum; /* Accumulator */ + int regResult; /* Interim result */ int csrApp; /* Function cursor (used by min/max) */ int regApp; /* Function register (also used by min/max) */ int regPart; /* Array of registers for PARTITION BY values */ @@ -18510,18 +21596,24 @@ struct Window { int regOne; /* Register containing constant value 1 */ int regStartRowid; int regEndRowid; + u8 bExprArgs; /* Defer evaluation of window function arguments + ** due to the SQLITE_SUBTYPE flag */ }; +SQLITE_PRIVATE Select *sqlite3MultiValues(Parse *pParse, Select *pLeft, ExprList *pRow); +SQLITE_PRIVATE void sqlite3MultiValuesEnd(Parse *pParse, Select *pVal); + #ifndef SQLITE_OMIT_WINDOWFUNC SQLITE_PRIVATE void sqlite3WindowDelete(sqlite3*, Window*); +SQLITE_PRIVATE void sqlite3WindowUnlinkFromSelect(Window*); SQLITE_PRIVATE void sqlite3WindowListDelete(sqlite3 *db, Window *p); SQLITE_PRIVATE Window *sqlite3WindowAlloc(Parse*, int, int, Expr*, int , Expr*, u8); SQLITE_PRIVATE void sqlite3WindowAttach(Parse*, Expr*, Window*); -SQLITE_PRIVATE int sqlite3WindowCompare(Parse*, Window*, Window*); -SQLITE_PRIVATE void sqlite3WindowCodeInit(Parse*, Window*); +SQLITE_PRIVATE void sqlite3WindowLink(Select *pSel, Window *pWin); +SQLITE_PRIVATE int sqlite3WindowCompare(const Parse*, const Window*, const Window*, int); +SQLITE_PRIVATE void sqlite3WindowCodeInit(Parse*, Select*); SQLITE_PRIVATE void sqlite3WindowCodeStep(Parse*, Select*, WhereInfo*, int, int); SQLITE_PRIVATE int sqlite3WindowRewrite(Parse*, Select*); -SQLITE_PRIVATE int sqlite3ExpandSubquery(Parse*, struct SrcList_item*); SQLITE_PRIVATE void sqlite3WindowUpdate(Parse*, Window*, Window*, FuncDef*); SQLITE_PRIVATE Window *sqlite3WindowDup(sqlite3 *db, Expr *pOwner, Window *p); SQLITE_PRIVATE Window *sqlite3WindowListDup(sqlite3 *db, Window *p); @@ -18561,13 +21653,16 @@ SQLITE_PRIVATE int sqlite3CantopenError(int); #ifdef SQLITE_DEBUG SQLITE_PRIVATE int sqlite3NomemError(int); SQLITE_PRIVATE int sqlite3IoerrnomemError(int); -SQLITE_PRIVATE int sqlite3CorruptPgnoError(int,Pgno); # define SQLITE_NOMEM_BKPT sqlite3NomemError(__LINE__) # define SQLITE_IOERR_NOMEM_BKPT sqlite3IoerrnomemError(__LINE__) -# define SQLITE_CORRUPT_PGNO(P) sqlite3CorruptPgnoError(__LINE__,(P)) #else # define SQLITE_NOMEM_BKPT SQLITE_NOMEM # define SQLITE_IOERR_NOMEM_BKPT SQLITE_IOERR_NOMEM +#endif +#if defined(SQLITE_DEBUG) || defined(SQLITE_ENABLE_CORRUPT_PGNO) +SQLITE_PRIVATE int sqlite3CorruptPgnoError(int,Pgno); +# define SQLITE_CORRUPT_PGNO(P) sqlite3CorruptPgnoError(__LINE__,(P)) +#else # define SQLITE_CORRUPT_PGNO(P) sqlite3CorruptError(__LINE__) #endif @@ -18588,15 +21683,6 @@ SQLITE_PRIVATE int sqlite3CorruptPgnoError(int,Pgno); # define SQLITE_ENABLE_FTS3 1 #endif -/* -** The ctype.h header is needed for non-ASCII systems. It is also -** needed by FTS3 when FTS3 is included in the amalgamation. -*/ -#if !defined(SQLITE_ASCII) || \ - (defined(SQLITE_ENABLE_FTS3) && defined(SQLITE_AMALGAMATION)) -# include -#endif - /* ** The following macros mimic the standard library functions toupper(), ** isspace(), isalnum(), isdigit() and isxdigit(), respectively. The @@ -18611,6 +21697,8 @@ SQLITE_PRIVATE int sqlite3CorruptPgnoError(int,Pgno); # define sqlite3Isxdigit(x) (sqlite3CtypeMap[(unsigned char)(x)]&0x08) # define sqlite3Tolower(x) (sqlite3UpperToLower[(unsigned char)(x)]) # define sqlite3Isquote(x) (sqlite3CtypeMap[(unsigned char)(x)]&0x80) +# define sqlite3JsonId1(x) (sqlite3CtypeMap[(unsigned char)(x)]&0x42) +# define sqlite3JsonId2(x) (sqlite3CtypeMap[(unsigned char)(x)]&0x46) #else # define sqlite3Toupper(x) toupper((unsigned char)(x)) # define sqlite3Isspace(x) isspace((unsigned char)(x)) @@ -18620,6 +21708,8 @@ SQLITE_PRIVATE int sqlite3CorruptPgnoError(int,Pgno); # define sqlite3Isxdigit(x) isxdigit((unsigned char)(x)) # define sqlite3Tolower(x) tolower((unsigned char)(x)) # define sqlite3Isquote(x) ((x)=='"'||(x)=='\''||(x)=='['||(x)=='`') +# define sqlite3JsonId1(x) (sqlite3IsIdChar(x)&&(x)<'0') +# define sqlite3JsonId2(x) sqlite3IsIdChar(x) #endif SQLITE_PRIVATE int sqlite3IsIdChar(u8); @@ -18647,8 +21737,9 @@ SQLITE_PRIVATE void *sqlite3DbReallocOrFree(sqlite3 *, void *, u64); SQLITE_PRIVATE void *sqlite3DbRealloc(sqlite3 *, void *, u64); SQLITE_PRIVATE void sqlite3DbFree(sqlite3*, void*); SQLITE_PRIVATE void sqlite3DbFreeNN(sqlite3*, void*); -SQLITE_PRIVATE int sqlite3MallocSize(void*); -SQLITE_PRIVATE int sqlite3DbMallocSize(sqlite3*, void*); +SQLITE_PRIVATE void sqlite3DbNNFreeNN(sqlite3*, void*); +SQLITE_PRIVATE int sqlite3MallocSize(const void*); +SQLITE_PRIVATE int sqlite3DbMallocSize(sqlite3*, const void*); SQLITE_PRIVATE void *sqlite3PageMalloc(int); SQLITE_PRIVATE void sqlite3PageFree(void*); SQLITE_PRIVATE void sqlite3MemSetDefault(void); @@ -18667,12 +21758,14 @@ SQLITE_PRIVATE int sqlite3HeapNearlyFull(void); */ #ifdef SQLITE_USE_ALLOCA # define sqlite3StackAllocRaw(D,N) alloca(N) -# define sqlite3StackAllocZero(D,N) memset(alloca(N), 0, N) +# define sqlite3StackAllocRawNN(D,N) alloca(N) # define sqlite3StackFree(D,P) +# define sqlite3StackFreeNN(D,P) #else # define sqlite3StackAllocRaw(D,N) sqlite3DbMallocRaw(D,N) -# define sqlite3StackAllocZero(D,N) sqlite3DbMallocZero(D,N) +# define sqlite3StackAllocRawNN(D,N) sqlite3DbMallocRawNN(D,N) # define sqlite3StackFree(D,P) sqlite3DbFree(D,P) +# define sqlite3StackFreeNN(D,P) sqlite3DbFreeNN(D,P) #endif /* Do not allow both MEMSYS5 and MEMSYS3 to be defined together. If they @@ -18710,16 +21803,36 @@ SQLITE_PRIVATE int sqlite3LookasideUsed(sqlite3*,int*); SQLITE_PRIVATE sqlite3_mutex *sqlite3Pcache1Mutex(void); SQLITE_PRIVATE sqlite3_mutex *sqlite3MallocMutex(void); -#if defined(SQLITE_ENABLE_MULTITHREADED_CHECKS) && !defined(SQLITE_MUTEX_OMIT) + +/* The SQLITE_THREAD_MISUSE_WARNINGS compile-time option used to be called +** SQLITE_ENABLE_MULTITHREADED_CHECKS. Keep that older macro for backwards +** compatibility, at least for a while... */ +#ifdef SQLITE_ENABLE_MULTITHREADED_CHECKS +# define SQLITE_THREAD_MISUSE_WARNINGS 1 +#endif + +/* SQLITE_THREAD_MISUSE_ABORT implies SQLITE_THREAD_MISUSE_WARNINGS */ +#ifdef SQLITE_THREAD_MISUSE_ABORT +# define SQLITE_THREAD_MISUSE_WARNINGS 1 +#endif + +#if defined(SQLITE_THREAD_MISUSE_WARNINGS) && !defined(SQLITE_MUTEX_OMIT) SQLITE_PRIVATE void sqlite3MutexWarnOnContention(sqlite3_mutex*); #else # define sqlite3MutexWarnOnContention(x) #endif #ifndef SQLITE_OMIT_FLOATING_POINT +# define EXP754 (((u64)0x7ff)<<52) +# define MAN754 ((((u64)1)<<52)-1) +# define IsNaN(X) (((X)&EXP754)==EXP754 && ((X)&MAN754)!=0) +# define IsOvfl(X) (((X)&EXP754)==EXP754) SQLITE_PRIVATE int sqlite3IsNaN(double); +SQLITE_PRIVATE int sqlite3IsOverflow(double); #else -# define sqlite3IsNaN(X) 0 +# define IsNaN(X) 0 +# define sqlite3IsNaN(X) 0 +# define sqlite3IsOVerflow(X) 0 #endif /* @@ -18732,6 +21845,25 @@ struct PrintfArguments { sqlite3_value **apArg; /* The argument values */ }; +/* +** Maxium number of base-10 digits in an unsigned 64-bit integer +*/ +#define SQLITE_U64_DIGITS 20 + +/* +** An instance of this object receives the decoding of a floating point +** value into an approximate decimal representation. +*/ +struct FpDecode { + int n; /* Significant digits in the decode */ + int iDP; /* Location of the decimal point */ + char *z; /* Start of significant digits */ + char zBuf[SQLITE_U64_DIGITS+1]; /* Storage for significant digits */ + char sign; /* '+' or '-' */ + char isSpecial; /* 1: Infinity 2: NaN */ +}; + +SQLITE_PRIVATE void sqlite3FpDecode(FpDecode*,double,int,int); SQLITE_PRIVATE char *sqlite3MPrintf(sqlite3*,const char*, ...); SQLITE_PRIVATE char *sqlite3VMPrintf(sqlite3*,const char*, va_list); #if defined(SQLITE_DEBUG) || defined(SQLITE_HAVE_OS_TRACE) @@ -18742,51 +21874,104 @@ SQLITE_PRIVATE void *sqlite3TestTextToPtr(const char*); #endif #if defined(SQLITE_DEBUG) +SQLITE_PRIVATE void sqlite3TreeViewLine(TreeView*, const char *zFormat, ...); SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView*, const Expr*, u8); SQLITE_PRIVATE void sqlite3TreeViewBareExprList(TreeView*, const ExprList*, const char*); SQLITE_PRIVATE void sqlite3TreeViewExprList(TreeView*, const ExprList*, u8, const char*); +SQLITE_PRIVATE void sqlite3TreeViewBareIdList(TreeView*, const IdList*, const char*); +SQLITE_PRIVATE void sqlite3TreeViewIdList(TreeView*, const IdList*, u8, const char*); +SQLITE_PRIVATE void sqlite3TreeViewColumnList(TreeView*, const Column*, int, u8); SQLITE_PRIVATE void sqlite3TreeViewSrcList(TreeView*, const SrcList*); SQLITE_PRIVATE void sqlite3TreeViewSelect(TreeView*, const Select*, u8); SQLITE_PRIVATE void sqlite3TreeViewWith(TreeView*, const With*, u8); +SQLITE_PRIVATE void sqlite3TreeViewUpsert(TreeView*, const Upsert*, u8); +#if TREETRACE_ENABLED +SQLITE_PRIVATE void sqlite3TreeViewDelete(const With*, const SrcList*, const Expr*, + const ExprList*,const Expr*, const Trigger*); +SQLITE_PRIVATE void sqlite3TreeViewInsert(const With*, const SrcList*, + const IdList*, const Select*, const ExprList*, + int, const Upsert*, const Trigger*); +SQLITE_PRIVATE void sqlite3TreeViewUpdate(const With*, const SrcList*, const ExprList*, + const Expr*, int, const ExprList*, const Expr*, + const Upsert*, const Trigger*); +#endif +#ifndef SQLITE_OMIT_TRIGGER +SQLITE_PRIVATE void sqlite3TreeViewTriggerStep(TreeView*, const TriggerStep*, u8, u8); +SQLITE_PRIVATE void sqlite3TreeViewTrigger(TreeView*, const Trigger*, u8, u8); +#endif #ifndef SQLITE_OMIT_WINDOWFUNC SQLITE_PRIVATE void sqlite3TreeViewWindow(TreeView*, const Window*, u8); SQLITE_PRIVATE void sqlite3TreeViewWinFunc(TreeView*, const Window*, u8); #endif +SQLITE_PRIVATE void sqlite3ShowExpr(const Expr*); +SQLITE_PRIVATE void sqlite3ShowExprList(const ExprList*); +SQLITE_PRIVATE void sqlite3ShowIdList(const IdList*); +SQLITE_PRIVATE void sqlite3ShowSrcList(const SrcList*); +SQLITE_PRIVATE void sqlite3ShowSelect(const Select*); +SQLITE_PRIVATE void sqlite3ShowWith(const With*); +SQLITE_PRIVATE void sqlite3ShowUpsert(const Upsert*); +#ifndef SQLITE_OMIT_TRIGGER +SQLITE_PRIVATE void sqlite3ShowTriggerStep(const TriggerStep*); +SQLITE_PRIVATE void sqlite3ShowTriggerStepList(const TriggerStep*); +SQLITE_PRIVATE void sqlite3ShowTrigger(const Trigger*); +SQLITE_PRIVATE void sqlite3ShowTriggerList(const Trigger*); +#endif +#ifndef SQLITE_OMIT_WINDOWFUNC +SQLITE_PRIVATE void sqlite3ShowWindow(const Window*); +SQLITE_PRIVATE void sqlite3ShowWinFunc(const Window*); +#endif +SQLITE_PRIVATE void sqlite3ShowBitvec(Bitvec*); #endif - SQLITE_PRIVATE void sqlite3SetString(char **, sqlite3*, const char*); +SQLITE_PRIVATE void sqlite3ProgressCheck(Parse*); SQLITE_PRIVATE void sqlite3ErrorMsg(Parse*, const char*, ...); SQLITE_PRIVATE int sqlite3ErrorToParser(sqlite3*,int); SQLITE_PRIVATE void sqlite3Dequote(char*); SQLITE_PRIVATE void sqlite3DequoteExpr(Expr*); +SQLITE_PRIVATE void sqlite3DequoteToken(Token*); +SQLITE_PRIVATE void sqlite3DequoteNumber(Parse*, Expr*); SQLITE_PRIVATE void sqlite3TokenInit(Token*,char*); SQLITE_PRIVATE int sqlite3KeywordCode(const unsigned char*, int); -SQLITE_PRIVATE int sqlite3RunParser(Parse*, const char*, char **); +SQLITE_PRIVATE int sqlite3RunParser(Parse*, const char*); SQLITE_PRIVATE void sqlite3FinishCoding(Parse*); SQLITE_PRIVATE int sqlite3GetTempReg(Parse*); SQLITE_PRIVATE void sqlite3ReleaseTempReg(Parse*,int); SQLITE_PRIVATE int sqlite3GetTempRange(Parse*,int); SQLITE_PRIVATE void sqlite3ReleaseTempRange(Parse*,int,int); SQLITE_PRIVATE void sqlite3ClearTempRegCache(Parse*); +SQLITE_PRIVATE void sqlite3TouchRegister(Parse*,int); +#if defined(SQLITE_ENABLE_STAT4) || defined(SQLITE_DEBUG) +SQLITE_PRIVATE int sqlite3FirstAvailableRegister(Parse*,int); +#endif #ifdef SQLITE_DEBUG SQLITE_PRIVATE int sqlite3NoTempsInRange(Parse*,int,int); #endif SQLITE_PRIVATE Expr *sqlite3ExprAlloc(sqlite3*,int,const Token*,int); SQLITE_PRIVATE Expr *sqlite3Expr(sqlite3*,int,const char*); +SQLITE_PRIVATE Expr *sqlite3ExprInt32(sqlite3*,int); SQLITE_PRIVATE void sqlite3ExprAttachSubtrees(sqlite3*,Expr*,Expr*,Expr*); SQLITE_PRIVATE Expr *sqlite3PExpr(Parse*, int, Expr*, Expr*); SQLITE_PRIVATE void sqlite3PExprAddSelect(Parse*, Expr*, Select*); -SQLITE_PRIVATE Expr *sqlite3ExprAnd(sqlite3*,Expr*, Expr*); -SQLITE_PRIVATE Expr *sqlite3ExprFunction(Parse*,ExprList*, Token*, int); +SQLITE_PRIVATE Expr *sqlite3ExprAnd(Parse*,Expr*, Expr*); +SQLITE_PRIVATE Expr *sqlite3ExprSimplifiedAndOr(Expr*); +SQLITE_PRIVATE Expr *sqlite3ExprFunction(Parse*,ExprList*, const Token*, int); +SQLITE_PRIVATE void sqlite3ExprAddFunctionOrderBy(Parse*,Expr*,ExprList*); +SQLITE_PRIVATE void sqlite3ExprOrderByAggregateError(Parse*,Expr*); +SQLITE_PRIVATE void sqlite3ExprFunctionUsable(Parse*,const Expr*,const FuncDef*); SQLITE_PRIVATE void sqlite3ExprAssignVarNumber(Parse*, Expr*, u32); SQLITE_PRIVATE void sqlite3ExprDelete(sqlite3*, Expr*); +SQLITE_PRIVATE void sqlite3ExprDeleteGeneric(sqlite3*,void*); +SQLITE_PRIVATE int sqlite3ExprDeferredDelete(Parse*, Expr*); +SQLITE_PRIVATE void sqlite3ExprUnmapAndDelete(Parse*, Expr*); SQLITE_PRIVATE ExprList *sqlite3ExprListAppend(Parse*,ExprList*,Expr*); SQLITE_PRIVATE ExprList *sqlite3ExprListAppendVector(Parse*,ExprList*,IdList*,Expr*); -SQLITE_PRIVATE void sqlite3ExprListSetSortOrder(ExprList*,int); -SQLITE_PRIVATE void sqlite3ExprListSetName(Parse*,ExprList*,Token*,int); +SQLITE_PRIVATE Select *sqlite3ExprListToValues(Parse*, int, ExprList*); +SQLITE_PRIVATE void sqlite3ExprListSetSortOrder(ExprList*,int,int); +SQLITE_PRIVATE void sqlite3ExprListSetName(Parse*,ExprList*,const Token*,int); SQLITE_PRIVATE void sqlite3ExprListSetSpan(Parse*,ExprList*,const char*,const char*); SQLITE_PRIVATE void sqlite3ExprListDelete(sqlite3*, ExprList*); +SQLITE_PRIVATE void sqlite3ExprListDeleteGeneric(sqlite3*,void*); SQLITE_PRIVATE u32 sqlite3ExprListFlags(const ExprList*); SQLITE_PRIVATE int sqlite3IndexHasDuplicateRootPage(Index*); SQLITE_PRIVATE int sqlite3Init(sqlite3*, char**); @@ -18800,33 +21985,43 @@ SQLITE_PRIVATE void sqlite3ResetAllSchemasOfConnection(sqlite3*); SQLITE_PRIVATE void sqlite3ResetOneSchema(sqlite3*,int); SQLITE_PRIVATE void sqlite3CollapseDatabaseArray(sqlite3*); SQLITE_PRIVATE void sqlite3CommitInternalChanges(sqlite3*); +SQLITE_PRIVATE void sqlite3ColumnSetExpr(Parse*,Table*,Column*,Expr*); +SQLITE_PRIVATE Expr *sqlite3ColumnExpr(Table*,Column*); +SQLITE_PRIVATE void sqlite3ColumnSetColl(sqlite3*,Column*,const char*zColl); +SQLITE_PRIVATE const char *sqlite3ColumnColl(Column*); SQLITE_PRIVATE void sqlite3DeleteColumnNames(sqlite3*,Table*); +SQLITE_PRIVATE void sqlite3GenerateColumnNames(Parse *pParse, Select *pSelect); SQLITE_PRIVATE int sqlite3ColumnsFromExprList(Parse*,ExprList*,i16*,Column**); -SQLITE_PRIVATE void sqlite3SelectAddColumnTypeAndCollation(Parse*,Table*,Select*); -SQLITE_PRIVATE Table *sqlite3ResultSetOfSelect(Parse*,Select*); -SQLITE_PRIVATE void sqlite3OpenMasterTable(Parse *, int); +SQLITE_PRIVATE void sqlite3SubqueryColumnTypes(Parse*,Table*,Select*,char); +SQLITE_PRIVATE Table *sqlite3ResultSetOfSelect(Parse*,Select*,char); +SQLITE_PRIVATE void sqlite3OpenSchemaTable(Parse *, int); SQLITE_PRIVATE Index *sqlite3PrimaryKeyIndex(Table*); -SQLITE_PRIVATE i16 sqlite3ColumnOfIndex(Index*, i16); +SQLITE_PRIVATE int sqlite3TableColumnToIndex(Index*, int); +#ifdef SQLITE_OMIT_GENERATED_COLUMNS +# define sqlite3TableColumnToStorage(T,X) (X) /* No-op pass-through */ +# define sqlite3StorageColumnToTable(T,X) (X) /* No-op pass-through */ +#else +SQLITE_PRIVATE i16 sqlite3TableColumnToStorage(Table*, i16); +SQLITE_PRIVATE i16 sqlite3StorageColumnToTable(Table*, i16); +#endif SQLITE_PRIVATE void sqlite3StartTable(Parse*,Token*,Token*,int,int,int,int); #if SQLITE_ENABLE_HIDDEN_COLUMNS SQLITE_PRIVATE void sqlite3ColumnPropertiesFromName(Table*, Column*); #else # define sqlite3ColumnPropertiesFromName(T,C) /* no-op */ #endif -SQLITE_PRIVATE void sqlite3AddColumn(Parse*,Token*,Token*); +SQLITE_PRIVATE void sqlite3AddColumn(Parse*,Token,Token); SQLITE_PRIVATE void sqlite3AddNotNull(Parse*, int); SQLITE_PRIVATE void sqlite3AddPrimaryKey(Parse*, ExprList*, int, int, int); -SQLITE_PRIVATE void sqlite3AddCheckConstraint(Parse*, Expr*); +SQLITE_PRIVATE void sqlite3AddCheckConstraint(Parse*, Expr*, const char*, const char*); SQLITE_PRIVATE void sqlite3AddDefaultValue(Parse*,Expr*,const char*,const char*); SQLITE_PRIVATE void sqlite3AddCollateType(Parse*, Token*); -SQLITE_PRIVATE void sqlite3EndTable(Parse*,Token*,Token*,u8,Select*); +SQLITE_PRIVATE void sqlite3AddGenerated(Parse*,Expr*,Token*); +SQLITE_PRIVATE void sqlite3EndTable(Parse*,Token*,Token*,u32,Select*); +SQLITE_PRIVATE void sqlite3AddReturning(Parse*,ExprList*); SQLITE_PRIVATE int sqlite3ParseUri(const char*,const char*,unsigned int*, sqlite3_vfs**,char**,char **); -#ifdef SQLITE_HAS_CODEC -SQLITE_PRIVATE int sqlite3CodecQueryParameters(sqlite3*,const char*,const char*); -#else -# define sqlite3CodecQueryParameters(A,B,C) 0 -#endif +#define sqlite3CodecQueryParameters(A,B,C) 0 SQLITE_PRIVATE Btree *sqlite3DbNameToBtree(sqlite3*,const char*); #ifdef SQLITE_UNTESTABLE @@ -18867,6 +22062,7 @@ SQLITE_PRIVATE int sqlite3DbMaskAllZero(yDbMask); SQLITE_PRIVATE void sqlite3DropTable(Parse*, SrcList*, int, int); SQLITE_PRIVATE void sqlite3CodeDropTable(Parse*, Table*, int, int); SQLITE_PRIVATE void sqlite3DeleteTable(sqlite3*, Table*); +SQLITE_PRIVATE void sqlite3DeleteTableGeneric(sqlite3*, void*); SQLITE_PRIVATE void sqlite3FreeIndex(sqlite3*, Index*); #ifndef SQLITE_OMIT_AUTOINCREMENT SQLITE_PRIVATE void sqlite3AutoincrementBegin(Parse *pParse); @@ -18876,21 +22072,29 @@ SQLITE_PRIVATE void sqlite3AutoincrementEnd(Parse *pParse); # define sqlite3AutoincrementEnd(X) #endif SQLITE_PRIVATE void sqlite3Insert(Parse*, SrcList*, Select*, IdList*, int, Upsert*); +#ifndef SQLITE_OMIT_GENERATED_COLUMNS +SQLITE_PRIVATE void sqlite3ComputeGeneratedColumns(Parse*, int, Table*); +#endif SQLITE_PRIVATE void *sqlite3ArrayAllocate(sqlite3*,void*,int,int*,int*); SQLITE_PRIVATE IdList *sqlite3IdListAppend(Parse*, IdList*, Token*); SQLITE_PRIVATE int sqlite3IdListIndex(IdList*,const char*); SQLITE_PRIVATE SrcList *sqlite3SrcListEnlarge(Parse*, SrcList*, int, int); +SQLITE_PRIVATE SrcList *sqlite3SrcListAppendList(Parse *pParse, SrcList *p1, SrcList *p2); SQLITE_PRIVATE SrcList *sqlite3SrcListAppend(Parse*, SrcList*, Token*, Token*); +SQLITE_PRIVATE void sqlite3SubqueryDelete(sqlite3*,Subquery*); +SQLITE_PRIVATE Select *sqlite3SubqueryDetach(sqlite3*,SrcItem*); +SQLITE_PRIVATE int sqlite3SrcItemAttachSubquery(Parse*, SrcItem*, Select*, int); SQLITE_PRIVATE SrcList *sqlite3SrcListAppendFromTerm(Parse*, SrcList*, Token*, Token*, - Token*, Select*, Expr*, IdList*); + Token*, Select*, OnOrUsing*); SQLITE_PRIVATE void sqlite3SrcListIndexedBy(Parse *, SrcList *, Token *); SQLITE_PRIVATE void sqlite3SrcListFuncArgs(Parse*, SrcList*, ExprList*); -SQLITE_PRIVATE int sqlite3IndexedByLookup(Parse *, struct SrcList_item *); -SQLITE_PRIVATE void sqlite3SrcListShiftJoinType(SrcList*); +SQLITE_PRIVATE int sqlite3IndexedByLookup(Parse *, SrcItem *); +SQLITE_PRIVATE void sqlite3SrcListShiftJoinType(Parse*,SrcList*); SQLITE_PRIVATE void sqlite3SrcListAssignCursors(Parse*, SrcList*); SQLITE_PRIVATE void sqlite3IdListDelete(sqlite3*, IdList*); +SQLITE_PRIVATE void sqlite3ClearOnOrUsing(sqlite3*, OnOrUsing*); SQLITE_PRIVATE void sqlite3SrcListDelete(sqlite3*, SrcList*); -SQLITE_PRIVATE Index *sqlite3AllocateIndexObject(sqlite3*,i16,int,char**); +SQLITE_PRIVATE Index *sqlite3AllocateIndexObject(sqlite3*,int,int,char**); SQLITE_PRIVATE void sqlite3CreateIndex(Parse*,Token*,Token*,SrcList*,ExprList*,int,Token*, Expr*, int, int, u8); SQLITE_PRIVATE void sqlite3DropIndex(Parse*, SrcList*, int); @@ -18898,21 +22102,26 @@ SQLITE_PRIVATE int sqlite3Select(Parse*, Select*, SelectDest*); SQLITE_PRIVATE Select *sqlite3SelectNew(Parse*,ExprList*,SrcList*,Expr*,ExprList*, Expr*,ExprList*,u32,Expr*); SQLITE_PRIVATE void sqlite3SelectDelete(sqlite3*, Select*); +SQLITE_PRIVATE void sqlite3SelectDeleteGeneric(sqlite3*,void*); +SQLITE_PRIVATE void sqlite3SelectCheckOnClauses(Parse *pParse, Select *pSelect); SQLITE_PRIVATE Table *sqlite3SrcListLookup(Parse*, SrcList*); -SQLITE_PRIVATE int sqlite3IsReadOnly(Parse*, Table*, int); +SQLITE_PRIVATE int sqlite3IsReadOnly(Parse*, Table*, Trigger*); SQLITE_PRIVATE void sqlite3OpenTable(Parse*, int iCur, int iDb, Table*, int); #if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) && !defined(SQLITE_OMIT_SUBQUERY) SQLITE_PRIVATE Expr *sqlite3LimitWhere(Parse*,SrcList*,Expr*,ExprList*,Expr*,char*); #endif +SQLITE_PRIVATE void sqlite3CodeChangeCount(Vdbe*,int,const char*); SQLITE_PRIVATE void sqlite3DeleteFrom(Parse*, SrcList*, Expr*, ExprList*, Expr*); SQLITE_PRIVATE void sqlite3Update(Parse*, SrcList*, ExprList*,Expr*,int,ExprList*,Expr*, Upsert*); -SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin(Parse*,SrcList*,Expr*,ExprList*,ExprList*,u16,int); +SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin(Parse*,SrcList*,Expr*,ExprList*, + ExprList*,Select*,u16,int); SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo*); SQLITE_PRIVATE LogEst sqlite3WhereOutputRowCount(WhereInfo*); SQLITE_PRIVATE int sqlite3WhereIsDistinct(WhereInfo*); SQLITE_PRIVATE int sqlite3WhereIsOrdered(WhereInfo*); SQLITE_PRIVATE int sqlite3WhereOrderByLimitOptLabel(WhereInfo*); +SQLITE_PRIVATE void sqlite3WhereMinMaxOptEarlyOut(Vdbe*,WhereInfo*); SQLITE_PRIVATE int sqlite3WhereIsSorted(WhereInfo*); SQLITE_PRIVATE int sqlite3WhereContinueLabel(WhereInfo*); SQLITE_PRIVATE int sqlite3WhereBreakLabel(WhereInfo*); @@ -18920,17 +22129,22 @@ SQLITE_PRIVATE int sqlite3WhereOkOnePass(WhereInfo*, int*); #define ONEPASS_OFF 0 /* Use of ONEPASS not allowed */ #define ONEPASS_SINGLE 1 /* ONEPASS valid for a single row update */ #define ONEPASS_MULTI 2 /* ONEPASS is valid for multiple rows */ +SQLITE_PRIVATE int sqlite3WhereUsesDeferredSeek(WhereInfo*); SQLITE_PRIVATE void sqlite3ExprCodeLoadIndexColumn(Parse*, Index*, int, int, int); SQLITE_PRIVATE int sqlite3ExprCodeGetColumn(Parse*, Table*, int, int, int, u8); SQLITE_PRIVATE void sqlite3ExprCodeGetColumnOfTable(Vdbe*, Table*, int, int, int); SQLITE_PRIVATE void sqlite3ExprCodeMove(Parse*, int, int, int); +SQLITE_PRIVATE void sqlite3ExprToRegister(Expr *pExpr, int iReg); SQLITE_PRIVATE void sqlite3ExprCode(Parse*, Expr*, int); +#ifndef SQLITE_OMIT_GENERATED_COLUMNS +SQLITE_PRIVATE void sqlite3ExprCodeGeneratedColumn(Parse*, Table*, Column*, int); +#endif SQLITE_PRIVATE void sqlite3ExprCodeCopy(Parse*, Expr*, int); SQLITE_PRIVATE void sqlite3ExprCodeFactorable(Parse*, Expr*, int); -SQLITE_PRIVATE int sqlite3ExprCodeAtInit(Parse*, Expr*, int); +SQLITE_PRIVATE int sqlite3ExprCodeRunJustOnce(Parse*, Expr*, int); +SQLITE_PRIVATE void sqlite3ExprNullRegisterRange(Parse*, int, int); SQLITE_PRIVATE int sqlite3ExprCodeTemp(Parse*, Expr*, int*); SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse*, Expr*, int); -SQLITE_PRIVATE void sqlite3ExprCodeAndCache(Parse*, Expr*, int); SQLITE_PRIVATE int sqlite3ExprCodeExprList(Parse*, ExprList*, int, int, u8); #define SQLITE_ECEL_DUP 0x01 /* Deep, not shallow copies */ #define SQLITE_ECEL_FACTOR 0x02 /* Factor out constant terms */ @@ -18943,22 +22157,24 @@ SQLITE_PRIVATE Table *sqlite3FindTable(sqlite3*,const char*, const char*); #define LOCATE_VIEW 0x01 #define LOCATE_NOERR 0x02 SQLITE_PRIVATE Table *sqlite3LocateTable(Parse*,u32 flags,const char*, const char*); -SQLITE_PRIVATE Table *sqlite3LocateTableItem(Parse*,u32 flags,struct SrcList_item *); +SQLITE_PRIVATE const char *sqlite3PreferredTableName(const char*); +SQLITE_PRIVATE Table *sqlite3LocateTableItem(Parse*,u32 flags,SrcItem *); SQLITE_PRIVATE Index *sqlite3FindIndex(sqlite3*,const char*, const char*); SQLITE_PRIVATE void sqlite3UnlinkAndDeleteTable(sqlite3*,int,const char*); SQLITE_PRIVATE void sqlite3UnlinkAndDeleteIndex(sqlite3*,int,const char*); SQLITE_PRIVATE void sqlite3Vacuum(Parse*,Token*,Expr*); SQLITE_PRIVATE int sqlite3RunVacuum(char**, sqlite3*, int, sqlite3_value*); -SQLITE_PRIVATE char *sqlite3NameFromToken(sqlite3*, Token*); -SQLITE_PRIVATE int sqlite3ExprCompare(Parse*,Expr*, Expr*, int); -SQLITE_PRIVATE int sqlite3ExprCompareSkip(Expr*, Expr*, int); -SQLITE_PRIVATE int sqlite3ExprListCompare(ExprList*, ExprList*, int); -SQLITE_PRIVATE int sqlite3ExprImpliesExpr(Parse*,Expr*, Expr*, int); -SQLITE_PRIVATE int sqlite3ExprImpliesNonNullRow(Expr*,int); +SQLITE_PRIVATE char *sqlite3NameFromToken(sqlite3*, const Token*); +SQLITE_PRIVATE int sqlite3ExprCompare(const Parse*,const Expr*,const Expr*, int); +SQLITE_PRIVATE int sqlite3ExprCompareSkip(Expr*,Expr*,int); +SQLITE_PRIVATE int sqlite3ExprListCompare(const ExprList*,const ExprList*, int); +SQLITE_PRIVATE int sqlite3ExprImpliesExpr(const Parse*,const Expr*,const Expr*, int); +SQLITE_PRIVATE int sqlite3ExprImpliesNonNullRow(Expr*,int,int); +SQLITE_PRIVATE void sqlite3AggInfoPersistWalkerInit(Walker*,Parse*); SQLITE_PRIVATE void sqlite3ExprAnalyzeAggregates(NameContext*, Expr*); SQLITE_PRIVATE void sqlite3ExprAnalyzeAggList(NameContext*,ExprList*); SQLITE_PRIVATE int sqlite3ExprCoveredByIndex(Expr*, int iCur, Index *pIdx); -SQLITE_PRIVATE int sqlite3FunctionUsesThisSrc(Expr*, SrcList*); +SQLITE_PRIVATE int sqlite3ReferencesSrcList(Parse*, Expr*, SrcList*); SQLITE_PRIVATE Vdbe *sqlite3GetVdbe(Parse*); #ifndef SQLITE_UNTESTABLE SQLITE_PRIVATE void sqlite3PrngSaveState(void); @@ -18972,20 +22188,22 @@ SQLITE_PRIVATE void sqlite3EndTransaction(Parse*,int); SQLITE_PRIVATE void sqlite3Savepoint(Parse*, int, Token*); SQLITE_PRIVATE void sqlite3CloseSavepoints(sqlite3 *); SQLITE_PRIVATE void sqlite3LeaveMutexAndCloseZombie(sqlite3*); +SQLITE_PRIVATE u32 sqlite3IsTrueOrFalse(const char*); SQLITE_PRIVATE int sqlite3ExprIdToTrueFalse(Expr*); SQLITE_PRIVATE int sqlite3ExprTruthValue(const Expr*); -SQLITE_PRIVATE int sqlite3ExprIsConstant(Expr*); -SQLITE_PRIVATE int sqlite3ExprIsConstantNotJoin(Expr*); +SQLITE_PRIVATE int sqlite3ExprIsConstant(Parse*,Expr*); SQLITE_PRIVATE int sqlite3ExprIsConstantOrFunction(Expr*, u8); SQLITE_PRIVATE int sqlite3ExprIsConstantOrGroupBy(Parse*, Expr*, ExprList*); -SQLITE_PRIVATE int sqlite3ExprIsTableConstant(Expr*,int); +SQLITE_PRIVATE int sqlite3ExprIsSingleTableConstraint(Expr*,const SrcList*,int,int); #ifdef SQLITE_ENABLE_CURSOR_HINTS SQLITE_PRIVATE int sqlite3ExprContainsSubquery(Expr*); #endif -SQLITE_PRIVATE int sqlite3ExprIsInteger(Expr*, int*); +SQLITE_PRIVATE int sqlite3ExprIsInteger(const Expr*, int*, Parse*); SQLITE_PRIVATE int sqlite3ExprCanBeNull(const Expr*); SQLITE_PRIVATE int sqlite3ExprNeedsNoAffinityChange(const Expr*, char); +SQLITE_PRIVATE int sqlite3ExprIsLikeOperator(const Expr*); SQLITE_PRIVATE int sqlite3IsRowid(const char*); +SQLITE_PRIVATE const char *sqlite3RowidAlias(Table *pTab); SQLITE_PRIVATE void sqlite3GenerateRowDelete( Parse*,Table*,Trigger*,int,int,int,i16,u8,u8,u8,int); SQLITE_PRIVATE void sqlite3GenerateRowIndexDelete(Parse*, Table*, int, int, int*, int); @@ -19007,20 +22225,31 @@ SQLITE_PRIVATE void sqlite3MayAbort(Parse*); SQLITE_PRIVATE void sqlite3HaltConstraint(Parse*, int, int, char*, i8, u8); SQLITE_PRIVATE void sqlite3UniqueConstraint(Parse*, int, Index*); SQLITE_PRIVATE void sqlite3RowidConstraint(Parse*, int, Table*); -SQLITE_PRIVATE Expr *sqlite3ExprDup(sqlite3*,Expr*,int); -SQLITE_PRIVATE ExprList *sqlite3ExprListDup(sqlite3*,ExprList*,int); -SQLITE_PRIVATE SrcList *sqlite3SrcListDup(sqlite3*,SrcList*,int); -SQLITE_PRIVATE IdList *sqlite3IdListDup(sqlite3*,IdList*); -SQLITE_PRIVATE Select *sqlite3SelectDup(sqlite3*,Select*,int); +SQLITE_PRIVATE Expr *sqlite3ExprDup(sqlite3*,const Expr*,int); +SQLITE_PRIVATE ExprList *sqlite3ExprListDup(sqlite3*,const ExprList*,int); +SQLITE_PRIVATE SrcList *sqlite3SrcListDup(sqlite3*,const SrcList*,int); +SQLITE_PRIVATE IdList *sqlite3IdListDup(sqlite3*,const IdList*); +SQLITE_PRIVATE Select *sqlite3SelectDup(sqlite3*,const Select*,int); SQLITE_PRIVATE FuncDef *sqlite3FunctionSearch(int,const char*); SQLITE_PRIVATE void sqlite3InsertBuiltinFuncs(FuncDef*,int); SQLITE_PRIVATE FuncDef *sqlite3FindFunction(sqlite3*,const char*,int,u8,u8); +SQLITE_PRIVATE void sqlite3QuoteValue(StrAccum*,sqlite3_value*,int); +SQLITE_PRIVATE int sqlite3AppendOneUtf8Character(char*, u32); SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(void); SQLITE_PRIVATE void sqlite3RegisterDateTimeFunctions(void); +SQLITE_PRIVATE void sqlite3RegisterJsonFunctions(void); SQLITE_PRIVATE void sqlite3RegisterPerConnectionBuiltinFunctions(sqlite3*); +#if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_JSON) +SQLITE_PRIVATE Module *sqlite3JsonVtabRegister(sqlite3*,const char*); +#endif SQLITE_PRIVATE int sqlite3SafetyCheckOk(sqlite3*); SQLITE_PRIVATE int sqlite3SafetyCheckSickOrOk(sqlite3*); SQLITE_PRIVATE void sqlite3ChangeCookie(Parse*, int); +SQLITE_PRIVATE With *sqlite3WithDup(sqlite3 *db, With *p); + +#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_CARRAY) +SQLITE_PRIVATE Module *sqlite3CarrayRegister(sqlite3*); +#endif #if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER) SQLITE_PRIVATE void sqlite3MaterializeView(Parse*, Table*, Expr*, ExprList*,Expr*,int); @@ -19041,12 +22270,12 @@ SQLITE_PRIVATE void sqlite3CodeRowTriggerDirect(Parse *, Trigger *, Table *, i SQLITE_PRIVATE void sqlite3DeleteTriggerStep(sqlite3*, TriggerStep*); SQLITE_PRIVATE TriggerStep *sqlite3TriggerSelectStep(sqlite3*,Select*, const char*,const char*); -SQLITE_PRIVATE TriggerStep *sqlite3TriggerInsertStep(Parse*,Token*, IdList*, +SQLITE_PRIVATE TriggerStep *sqlite3TriggerInsertStep(Parse*,SrcList*, IdList*, Select*,u8,Upsert*, const char*,const char*); -SQLITE_PRIVATE TriggerStep *sqlite3TriggerUpdateStep(Parse*,Token*,ExprList*, Expr*, u8, - const char*,const char*); -SQLITE_PRIVATE TriggerStep *sqlite3TriggerDeleteStep(Parse*,Token*, Expr*, +SQLITE_PRIVATE TriggerStep *sqlite3TriggerUpdateStep(Parse*,SrcList*,SrcList*,ExprList*, + Expr*, u8, const char*,const char*); +SQLITE_PRIVATE TriggerStep *sqlite3TriggerDeleteStep(Parse*,SrcList*, Expr*, const char*,const char*); SQLITE_PRIVATE void sqlite3DeleteTrigger(sqlite3*, Trigger*); SQLITE_PRIVATE void sqlite3UnlinkAndDeleteTrigger(sqlite3*,int,const char*); @@ -19067,6 +22296,9 @@ SQLITE_PRIVATE u32 sqlite3TriggerColmask(Parse*,Trigger*,ExprList*,int,int,Tab #endif SQLITE_PRIVATE int sqlite3JoinType(Parse*, Token*, Token*, Token*); +SQLITE_PRIVATE int sqlite3ColumnIndex(Table *pTab, const char *zCol); +SQLITE_PRIVATE void sqlite3SrcItemColumnUsed(SrcItem*,int); +SQLITE_PRIVATE void sqlite3SetJoinExpr(Expr*,int,u32); SQLITE_PRIVATE void sqlite3CreateForeignKey(Parse*, ExprList*, Token*, ExprList*, int); SQLITE_PRIVATE void sqlite3DeferForeignKey(Parse*, int); #ifndef SQLITE_OMIT_AUTHORIZATION @@ -19081,32 +22313,32 @@ SQLITE_PRIVATE int sqlite3AuthReadCol(Parse*, const char *, const char *, int) # define sqlite3AuthContextPush(a,b,c) # define sqlite3AuthContextPop(a) ((void)(a)) #endif +SQLITE_PRIVATE int sqlite3DbIsNamed(sqlite3 *db, int iDb, const char *zName); SQLITE_PRIVATE void sqlite3Attach(Parse*, Expr*, Expr*, Expr*); SQLITE_PRIVATE void sqlite3Detach(Parse*, Expr*); SQLITE_PRIVATE void sqlite3FixInit(DbFixer*, Parse*, int, const char*, const Token*); SQLITE_PRIVATE int sqlite3FixSrcList(DbFixer*, SrcList*); SQLITE_PRIVATE int sqlite3FixSelect(DbFixer*, Select*); SQLITE_PRIVATE int sqlite3FixExpr(DbFixer*, Expr*); -SQLITE_PRIVATE int sqlite3FixExprList(DbFixer*, ExprList*); SQLITE_PRIVATE int sqlite3FixTriggerStep(DbFixer*, TriggerStep*); -SQLITE_PRIVATE int sqlite3AtoF(const char *z, double*, int, u8); + +SQLITE_PRIVATE int sqlite3RealSameAsInt(double,sqlite3_int64); +SQLITE_PRIVATE i64 sqlite3RealToI64(double); +SQLITE_PRIVATE int sqlite3Int64ToText(i64,char*); +SQLITE_PRIVATE int sqlite3AtoF(const char *z, double*); SQLITE_PRIVATE int sqlite3GetInt32(const char *, int*); +SQLITE_PRIVATE int sqlite3GetUInt32(const char*, u32*); SQLITE_PRIVATE int sqlite3Atoi(const char*); #ifndef SQLITE_OMIT_UTF16 -SQLITE_PRIVATE int sqlite3Utf16ByteLen(const void *pData, int nChar); +SQLITE_PRIVATE int sqlite3Utf16ByteLen(const void *pData, int nByte, int nChar); #endif SQLITE_PRIVATE int sqlite3Utf8CharLen(const char *pData, int nByte); SQLITE_PRIVATE u32 sqlite3Utf8Read(const u8**); +SQLITE_PRIVATE int sqlite3Utf8ReadLimited(const u8*, int, u32*); SQLITE_PRIVATE LogEst sqlite3LogEst(u64); SQLITE_PRIVATE LogEst sqlite3LogEstAdd(LogEst,LogEst); -#ifndef SQLITE_OMIT_VIRTUALTABLE SQLITE_PRIVATE LogEst sqlite3LogEstFromDouble(double); -#endif -#if defined(SQLITE_ENABLE_STMT_SCANSTATUS) || \ - defined(SQLITE_ENABLE_STAT3_OR_STAT4) || \ - defined(SQLITE_EXPLAIN_ESTIMATED_ROWS) SQLITE_PRIVATE u64 sqlite3LogEstToInt(LogEst); -#endif SQLITE_PRIVATE VList *sqlite3VListAdd(sqlite3*,VList*,const char*,int,int); SQLITE_PRIVATE const char *sqlite3VListNumToName(VList*,int); SQLITE_PRIVATE int sqlite3VListNameToNum(VList*,const char*,int); @@ -19128,6 +22360,8 @@ SQLITE_PRIVATE int sqlite3VarintLen(u64 v); */ #define getVarint32(A,B) \ (u8)((*(A)<(u8)0x80)?((B)=(u32)*(A)),1:sqlite3GetVarint32((A),(u32 *)&(B))) +#define getVarint32NR(A,B) \ + B=(u32)*(A);if(B>=0x80)sqlite3GetVarint32((A),(u32*)&(B)) #define putVarint32(A,B) \ (u8)(((u32)(B)<(u32)0x80)?(*(A)=(unsigned char)(B)),1:\ sqlite3PutVarint((A),(B))) @@ -19136,17 +22370,22 @@ SQLITE_PRIVATE int sqlite3VarintLen(u64 v); SQLITE_PRIVATE const char *sqlite3IndexAffinityStr(sqlite3*, Index*); +SQLITE_PRIVATE char *sqlite3TableAffinityStr(sqlite3*,const Table*); SQLITE_PRIVATE void sqlite3TableAffinity(Vdbe*, Table*, int); -SQLITE_PRIVATE char sqlite3CompareAffinity(Expr *pExpr, char aff2); -SQLITE_PRIVATE int sqlite3IndexAffinityOk(Expr *pExpr, char idx_affinity); -SQLITE_PRIVATE char sqlite3TableColumnAffinity(Table*,int); -SQLITE_PRIVATE char sqlite3ExprAffinity(Expr *pExpr); +SQLITE_PRIVATE char sqlite3CompareAffinity(const Expr *pExpr, char aff2); +SQLITE_PRIVATE int sqlite3IndexAffinityOk(const Expr *pExpr, char idx_affinity); +SQLITE_PRIVATE char sqlite3TableColumnAffinity(const Table*,int); +SQLITE_PRIVATE char sqlite3ExprAffinity(const Expr *pExpr); +SQLITE_PRIVATE int sqlite3ExprDataType(const Expr *pExpr); SQLITE_PRIVATE int sqlite3Atoi64(const char*, i64*, int, u8); SQLITE_PRIVATE int sqlite3DecOrHexToI64(const char*, i64*); SQLITE_PRIVATE void sqlite3ErrorWithMsg(sqlite3*, int, const char*,...); SQLITE_PRIVATE void sqlite3Error(sqlite3*,int); +SQLITE_PRIVATE void sqlite3ErrorClear(sqlite3*); SQLITE_PRIVATE void sqlite3SystemError(sqlite3*,int); +#if !defined(SQLITE_OMIT_BLOB_LITERAL) SQLITE_PRIVATE void *sqlite3HexToBlob(sqlite3*, const char *z, int n); +#endif SQLITE_PRIVATE u8 sqlite3HexToInt(int h); SQLITE_PRIVATE int sqlite3TwoPartName(Parse *, Token *, Token *, Token **); @@ -19154,8 +22393,11 @@ SQLITE_PRIVATE int sqlite3TwoPartName(Parse *, Token *, Token *, Token **); SQLITE_PRIVATE const char *sqlite3ErrName(int); #endif -#ifdef SQLITE_ENABLE_DESERIALIZE +#ifndef SQLITE_OMIT_DESERIALIZE SQLITE_PRIVATE int sqlite3MemdbInit(void); +SQLITE_PRIVATE int sqlite3IsMemdb(const sqlite3_vfs*); +#else +# define sqlite3IsMemdb(X) 0 #endif SQLITE_PRIVATE const char *sqlite3ErrStr(int); @@ -19163,16 +22405,18 @@ SQLITE_PRIVATE int sqlite3ReadSchema(Parse *pParse); SQLITE_PRIVATE CollSeq *sqlite3FindCollSeq(sqlite3*,u8 enc, const char*,int); SQLITE_PRIVATE int sqlite3IsBinary(const CollSeq*); SQLITE_PRIVATE CollSeq *sqlite3LocateCollSeq(Parse *pParse, const char*zName); -SQLITE_PRIVATE CollSeq *sqlite3ExprCollSeq(Parse *pParse, Expr *pExpr); -SQLITE_PRIVATE CollSeq *sqlite3ExprNNCollSeq(Parse *pParse, Expr *pExpr); -SQLITE_PRIVATE int sqlite3ExprCollSeqMatch(Parse*,Expr*,Expr*); -SQLITE_PRIVATE Expr *sqlite3ExprAddCollateToken(Parse *pParse, Expr*, const Token*, int); -SQLITE_PRIVATE Expr *sqlite3ExprAddCollateString(Parse*,Expr*,const char*); +SQLITE_PRIVATE void sqlite3SetTextEncoding(sqlite3 *db, u8); +SQLITE_PRIVATE CollSeq *sqlite3ExprCollSeq(Parse *pParse, const Expr *pExpr); +SQLITE_PRIVATE CollSeq *sqlite3ExprNNCollSeq(Parse *pParse, const Expr *pExpr); +SQLITE_PRIVATE int sqlite3ExprCollSeqMatch(Parse*,const Expr*,const Expr*); +SQLITE_PRIVATE Expr *sqlite3ExprAddCollateToken(const Parse *pParse, Expr*, const Token*, int); +SQLITE_PRIVATE Expr *sqlite3ExprAddCollateString(const Parse*,Expr*,const char*); SQLITE_PRIVATE Expr *sqlite3ExprSkipCollate(Expr*); +SQLITE_PRIVATE Expr *sqlite3ExprSkipCollateAndLikely(Expr*); SQLITE_PRIVATE int sqlite3CheckCollSeq(Parse *, CollSeq *); SQLITE_PRIVATE int sqlite3WritableSchema(sqlite3*); -SQLITE_PRIVATE int sqlite3CheckObjectName(Parse *, const char *); -SQLITE_PRIVATE void sqlite3VdbeSetChanges(sqlite3 *, int); +SQLITE_PRIVATE int sqlite3CheckObjectName(Parse*, const char*,const char*,const char*); +SQLITE_PRIVATE void sqlite3VdbeSetChanges(sqlite3 *, i64); SQLITE_PRIVATE int sqlite3AddInt64(i64*,i64); SQLITE_PRIVATE int sqlite3SubInt64(i64*,i64); SQLITE_PRIVATE int sqlite3MulInt64(i64*,i64); @@ -19185,45 +22429,74 @@ SQLITE_PRIVATE void sqlite3FileSuffix3(const char*, char*); SQLITE_PRIVATE u8 sqlite3GetBoolean(const char *z,u8); SQLITE_PRIVATE const void *sqlite3ValueText(sqlite3_value*, u8); +SQLITE_PRIVATE int sqlite3ValueIsOfClass(const sqlite3_value*, void(*)(void*)); SQLITE_PRIVATE int sqlite3ValueBytes(sqlite3_value*, u8); SQLITE_PRIVATE void sqlite3ValueSetStr(sqlite3_value*, int, const void *,u8, void(*)(void*)); SQLITE_PRIVATE void sqlite3ValueSetNull(sqlite3_value*); SQLITE_PRIVATE void sqlite3ValueFree(sqlite3_value*); +#ifndef SQLITE_UNTESTABLE +SQLITE_PRIVATE void sqlite3ResultIntReal(sqlite3_context*); +#endif SQLITE_PRIVATE sqlite3_value *sqlite3ValueNew(sqlite3 *); #ifndef SQLITE_OMIT_UTF16 SQLITE_PRIVATE char *sqlite3Utf16to8(sqlite3 *, const void*, int, u8); #endif -SQLITE_PRIVATE int sqlite3ValueFromExpr(sqlite3 *, Expr *, u8, u8, sqlite3_value **); +SQLITE_PRIVATE int sqlite3ValueFromExpr(sqlite3 *, const Expr *, u8, u8, sqlite3_value **); SQLITE_PRIVATE void sqlite3ValueApplyAffinity(sqlite3_value *, u8, u8); #ifndef SQLITE_AMALGAMATION SQLITE_PRIVATE const unsigned char sqlite3OpcodeProperty[]; SQLITE_PRIVATE const char sqlite3StrBINARY[]; +SQLITE_PRIVATE const unsigned char sqlite3StdTypeLen[]; +SQLITE_PRIVATE const char sqlite3StdTypeAffinity[]; +SQLITE_PRIVATE const char *sqlite3StdType[]; SQLITE_PRIVATE const unsigned char sqlite3UpperToLower[]; +SQLITE_PRIVATE const unsigned char *sqlite3aLTb; +SQLITE_PRIVATE const unsigned char *sqlite3aEQb; +SQLITE_PRIVATE const unsigned char *sqlite3aGTb; SQLITE_PRIVATE const unsigned char sqlite3CtypeMap[]; -SQLITE_PRIVATE const Token sqlite3IntTokens[]; SQLITE_PRIVATE SQLITE_WSD struct Sqlite3Config sqlite3Config; SQLITE_PRIVATE FuncDefHash sqlite3BuiltinFunctions; #ifndef SQLITE_OMIT_WSD SQLITE_PRIVATE int sqlite3PendingByte; #endif -#endif +#endif /* SQLITE_AMALGAMATION */ #ifdef VDBE_PROFILE SQLITE_PRIVATE sqlite3_uint64 sqlite3NProfileCnt; #endif -SQLITE_PRIVATE void sqlite3RootPageMoved(sqlite3*, int, int, int); +SQLITE_PRIVATE void sqlite3RootPageMoved(sqlite3*, int, Pgno, Pgno); SQLITE_PRIVATE void sqlite3Reindex(Parse*, Token*, Token*); SQLITE_PRIVATE void sqlite3AlterFunctions(void); SQLITE_PRIVATE void sqlite3AlterRenameTable(Parse*, SrcList*, Token*); SQLITE_PRIVATE void sqlite3AlterRenameColumn(Parse*, SrcList*, Token*, Token*); -SQLITE_PRIVATE int sqlite3GetToken(const unsigned char *, int *); +SQLITE_PRIVATE void sqlite3AlterDropConstraint(Parse*,SrcList*,Token*,Token*); +SQLITE_PRIVATE void sqlite3AlterAddConstraint( + Parse *pParse, /* Parse context */ + SrcList *pSrc, /* Table to add constraint to */ + Token *pFirst, /* First token of new constraint */ + Token *pName, /* Name of new constraint. NULL if name omitted. */ + const char *zExpr, /* Text of CHECK expression */ + int nExpr, /* Size of pExpr in bytes */ + Expr *pExpr /* The parsed CHECK expression */ +); +SQLITE_PRIVATE void sqlite3AlterSetNotNull(Parse*, SrcList*, Token*, Token*); +SQLITE_PRIVATE i64 sqlite3GetToken(const unsigned char *, int *); SQLITE_PRIVATE void sqlite3NestedParse(Parse*, const char*, ...); SQLITE_PRIVATE void sqlite3ExpirePreparedStatements(sqlite3*, int); -SQLITE_PRIVATE void sqlite3CodeRhsOfIN(Parse*, Expr*, int); +SQLITE_PRIVATE void sqlite3CodeRhsOfIN(Parse*, Expr*, int, int); SQLITE_PRIVATE int sqlite3CodeSubselect(Parse*, Expr*); SQLITE_PRIVATE void sqlite3SelectPrep(Parse*, Select*, NameContext*); +SQLITE_PRIVATE int sqlite3ExpandSubquery(Parse*, SrcItem*); SQLITE_PRIVATE void sqlite3SelectWrongNumTermsError(Parse *pParse, Select *p); -SQLITE_PRIVATE int sqlite3MatchSpanName(const char*, const char*, const char*, const char*); +SQLITE_PRIVATE int sqlite3MatchEName( + const struct ExprList_item*, + const char*, + const char*, + const char*, + int* +); +SQLITE_PRIVATE Bitmask sqlite3ExprColUsed(Expr*); +SQLITE_PRIVATE u8 sqlite3StrIHash(const char*); SQLITE_PRIVATE int sqlite3ResolveExprNames(NameContext*, Expr*); SQLITE_PRIVATE int sqlite3ResolveExprListNames(NameContext*, ExprList*); SQLITE_PRIVATE void sqlite3ResolveSelectNames(Parse*, Select*, NameContext*); @@ -19232,14 +22505,15 @@ SQLITE_PRIVATE int sqlite3ResolveOrderGroupBy(Parse*, Select*, ExprList*, const SQLITE_PRIVATE void sqlite3ColumnDefault(Vdbe *, Table *, int, int); SQLITE_PRIVATE void sqlite3AlterFinishAddColumn(Parse *, Token *); SQLITE_PRIVATE void sqlite3AlterBeginAddColumn(Parse *, SrcList *); -SQLITE_PRIVATE void *sqlite3RenameTokenMap(Parse*, void*, Token*); -SQLITE_PRIVATE void sqlite3RenameTokenRemap(Parse*, void *pTo, void *pFrom); +SQLITE_PRIVATE void sqlite3AlterDropColumn(Parse*, SrcList*, const Token*); +SQLITE_PRIVATE const void *sqlite3RenameTokenMap(Parse*, const void*, const Token*); +SQLITE_PRIVATE void sqlite3RenameTokenRemap(Parse*, const void *pTo, const void *pFrom); SQLITE_PRIVATE void sqlite3RenameExprUnmap(Parse*, Expr*); SQLITE_PRIVATE void sqlite3RenameExprlistUnmap(Parse*, ExprList*); SQLITE_PRIVATE CollSeq *sqlite3GetCollSeq(Parse*, u8, CollSeq *, const char*); SQLITE_PRIVATE char sqlite3AffinityType(const char*, Column*); SQLITE_PRIVATE void sqlite3Analyze(Parse*, Token*, Token*); -SQLITE_PRIVATE int sqlite3InvokeBusyHandler(BusyHandler*, sqlite3_file*); +SQLITE_PRIVATE int sqlite3InvokeBusyHandler(BusyHandler*); SQLITE_PRIVATE int sqlite3FindDb(sqlite3*, Token*); SQLITE_PRIVATE int sqlite3FindDbName(sqlite3 *, const char *); SQLITE_PRIVATE int sqlite3AnalysisLoad(sqlite3*,int iDB); @@ -19255,28 +22529,41 @@ SQLITE_PRIVATE void sqlite3KeyInfoUnref(KeyInfo*); SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoRef(KeyInfo*); SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoOfIndex(Parse*, Index*); SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoFromExprList(Parse*, ExprList*, int, int); +SQLITE_PRIVATE const char *sqlite3SelectOpName(int); +SQLITE_PRIVATE int sqlite3HasExplicitNulls(Parse*, ExprList*); #ifdef SQLITE_DEBUG SQLITE_PRIVATE int sqlite3KeyInfoIsWriteable(KeyInfo*); #endif SQLITE_PRIVATE int sqlite3CreateFunc(sqlite3 *, const char *, int, int, void *, void (*)(sqlite3_context*,int,sqlite3_value **), - void (*)(sqlite3_context*,int,sqlite3_value **), + void (*)(sqlite3_context*,int,sqlite3_value **), void (*)(sqlite3_context*), void (*)(sqlite3_context*), - void (*)(sqlite3_context*,int,sqlite3_value **), + void (*)(sqlite3_context*,int,sqlite3_value **), FuncDestructor *pDestructor ); SQLITE_PRIVATE void sqlite3NoopDestructor(void*); -SQLITE_PRIVATE void sqlite3OomFault(sqlite3*); +SQLITE_PRIVATE void *sqlite3OomFault(sqlite3*); SQLITE_PRIVATE void sqlite3OomClear(sqlite3*); SQLITE_PRIVATE int sqlite3ApiExit(sqlite3 *db, int); SQLITE_PRIVATE int sqlite3OpenTempDatabase(Parse *); +SQLITE_PRIVATE char *sqlite3RCStrRef(char*); +SQLITE_PRIVATE void sqlite3RCStrUnref(void*); +SQLITE_PRIVATE char *sqlite3RCStrNew(u64); +SQLITE_PRIVATE char *sqlite3RCStrResize(char*,u64); + SQLITE_PRIVATE void sqlite3StrAccumInit(StrAccum*, sqlite3*, char*, int, int); +SQLITE_PRIVATE int sqlite3StrAccumEnlarge(StrAccum*, i64); +SQLITE_PRIVATE int sqlite3StrAccumEnlargeIfNeeded(StrAccum*, i64); SQLITE_PRIVATE char *sqlite3StrAccumFinish(StrAccum*); +SQLITE_PRIVATE void sqlite3StrAccumSetError(StrAccum*, u8); +SQLITE_PRIVATE void sqlite3ResultStrAccum(sqlite3_context*,StrAccum*); SQLITE_PRIVATE void sqlite3SelectDestInit(SelectDest*,int,int); SQLITE_PRIVATE Expr *sqlite3CreateColumnExpr(sqlite3 *, SrcList *, int, int); +SQLITE_PRIVATE void sqlite3RecordErrorByteOffset(sqlite3*,const char*); +SQLITE_PRIVATE void sqlite3RecordErrorOffsetOfExpr(sqlite3*,const Expr*); SQLITE_PRIVATE void sqlite3BackupRestart(sqlite3_backup *); SQLITE_PRIVATE void sqlite3BackupUpdate(sqlite3_backup *, Pgno, const u8 *); @@ -19287,8 +22574,7 @@ SQLITE_PRIVATE int sqlite3ExprCheckIN(Parse*, Expr*); # define sqlite3ExprCheckIN(x,y) SQLITE_OK #endif -#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 -SQLITE_PRIVATE void sqlite3AnalyzeFunctions(void); +#ifdef SQLITE_ENABLE_STAT4 SQLITE_PRIVATE int sqlite3Stat4ProbeSetValue( Parse*,Index*,UnpackedRecord**,Expr*,int,int,int*); SQLITE_PRIVATE int sqlite3Stat4ValueFromExpr(Parse*, Expr*, u8, sqlite3_value**); @@ -19318,7 +22604,7 @@ SQLITE_PRIVATE void sqlite3CloseExtensions(sqlite3*); #endif #ifndef SQLITE_OMIT_SHARED_CACHE -SQLITE_PRIVATE void sqlite3TableLock(Parse *, int, int, u8, const char *); +SQLITE_PRIVATE void sqlite3TableLock(Parse *, int, Pgno, u8, const char *); #else #define sqlite3TableLock(v,w,x,y,z) #endif @@ -19328,13 +22614,14 @@ SQLITE_PRIVATE int sqlite3Utf8To8(unsigned char*); #endif #ifdef SQLITE_OMIT_VIRTUALTABLE -# define sqlite3VtabClear(Y) +# define sqlite3VtabClear(D,T) # define sqlite3VtabSync(X,Y) SQLITE_OK # define sqlite3VtabRollback(X) # define sqlite3VtabCommit(X) # define sqlite3VtabInSync(db) 0 # define sqlite3VtabLock(X) # define sqlite3VtabUnlock(X) +# define sqlite3VtabModuleUnref(D,X) # define sqlite3VtabUnlockList(X) # define sqlite3VtabSavepoint(X, Y, Z) SQLITE_OK # define sqlite3GetVTable(X,Y) ((VTable*)0) @@ -19346,6 +22633,7 @@ SQLITE_PRIVATE int sqlite3VtabRollback(sqlite3 *db); SQLITE_PRIVATE int sqlite3VtabCommit(sqlite3 *db); SQLITE_PRIVATE void sqlite3VtabLock(VTable *); SQLITE_PRIVATE void sqlite3VtabUnlock(VTable *); +SQLITE_PRIVATE void sqlite3VtabModuleUnref(sqlite3*,Module*); SQLITE_PRIVATE void sqlite3VtabUnlockList(sqlite3*); SQLITE_PRIVATE int sqlite3VtabSavepoint(sqlite3 *, int, int); SQLITE_PRIVATE void sqlite3VtabImportErrmsg(Vdbe*, sqlite3_vtab*); @@ -19359,6 +22647,16 @@ SQLITE_PRIVATE Module *sqlite3VtabCreateModule( ); # define sqlite3VtabInSync(db) ((db)->nVTrans>0 && (db)->aVTrans==0) #endif +SQLITE_PRIVATE int sqlite3ReadOnlyShadowTables(sqlite3 *db); +#ifndef SQLITE_OMIT_VIRTUALTABLE +SQLITE_PRIVATE int sqlite3ShadowTableName(sqlite3 *db, const char *zName); +SQLITE_PRIVATE int sqlite3IsShadowTableOf(sqlite3*,Table*,const char*); +SQLITE_PRIVATE void sqlite3MarkAllShadowTablesOf(sqlite3*, Table*); +#else +# define sqlite3ShadowTableName(A,B) 0 +# define sqlite3IsShadowTableOf(A,B,C) 0 +# define sqlite3MarkAllShadowTablesOf(A,B) +#endif SQLITE_PRIVATE int sqlite3VtabEponymousTableInit(Parse*,Module*); SQLITE_PRIVATE void sqlite3VtabEponymousTableClear(sqlite3*,Module*); SQLITE_PRIVATE void sqlite3VtabMakeWritable(Parse*,Table*); @@ -19370,17 +22668,22 @@ SQLITE_PRIVATE int sqlite3VtabCallCreate(sqlite3*, int, const char *, char **); SQLITE_PRIVATE int sqlite3VtabCallConnect(Parse*, Table*); SQLITE_PRIVATE int sqlite3VtabCallDestroy(sqlite3*, int, const char *); SQLITE_PRIVATE int sqlite3VtabBegin(sqlite3 *, VTable *); + SQLITE_PRIVATE FuncDef *sqlite3VtabOverloadFunction(sqlite3 *,FuncDef*, int nArg, Expr*); +SQLITE_PRIVATE void sqlite3VtabUsesAllSchemas(Parse*); SQLITE_PRIVATE sqlite3_int64 sqlite3StmtCurrentTime(sqlite3_context*); SQLITE_PRIVATE int sqlite3VdbeParameterIndex(Vdbe*, const char*, int); SQLITE_PRIVATE int sqlite3TransferBindings(sqlite3_stmt *, sqlite3_stmt *); -SQLITE_PRIVATE void sqlite3ParserReset(Parse*); +SQLITE_PRIVATE void sqlite3ParseObjectInit(Parse*,sqlite3*); +SQLITE_PRIVATE void sqlite3ParseObjectReset(Parse*); +SQLITE_PRIVATE void *sqlite3ParserAddCleanup(Parse*,void(*)(sqlite3*,void*),void*); #ifdef SQLITE_ENABLE_NORMALIZE SQLITE_PRIVATE char *sqlite3Normalize(Vdbe*, const char*); #endif SQLITE_PRIVATE int sqlite3Reprepare(Vdbe*); SQLITE_PRIVATE void sqlite3ExprListCheckLength(Parse*, ExprList*, const char*); -SQLITE_PRIVATE CollSeq *sqlite3BinaryCompareCollSeq(Parse *, Expr *, Expr *); +SQLITE_PRIVATE CollSeq *sqlite3ExprCompareCollSeq(Parse*,const Expr*); +SQLITE_PRIVATE CollSeq *sqlite3BinaryCompareCollSeq(Parse *, const Expr*, const Expr*); SQLITE_PRIVATE int sqlite3TempInMemory(const sqlite3*); SQLITE_PRIVATE const char *sqlite3JournalModename(int); #ifndef SQLITE_OMIT_WAL @@ -19388,23 +22691,33 @@ SQLITE_PRIVATE int sqlite3Checkpoint(sqlite3*, int, int, int*, int*); SQLITE_PRIVATE int sqlite3WalDefaultHook(void*,sqlite3*,const char*,int); #endif #ifndef SQLITE_OMIT_CTE -SQLITE_PRIVATE With *sqlite3WithAdd(Parse*,With*,Token*,ExprList*,Select*); +SQLITE_PRIVATE Cte *sqlite3CteNew(Parse*,Token*,ExprList*,Select*,u8); +SQLITE_PRIVATE void sqlite3CteDelete(sqlite3*,Cte*); +SQLITE_PRIVATE With *sqlite3WithAdd(Parse*,With*,Cte*); SQLITE_PRIVATE void sqlite3WithDelete(sqlite3*,With*); -SQLITE_PRIVATE void sqlite3WithPush(Parse*, With*, u8); +SQLITE_PRIVATE void sqlite3WithDeleteGeneric(sqlite3*,void*); +SQLITE_PRIVATE With *sqlite3WithPush(Parse*, With*, u8); #else -#define sqlite3WithPush(x,y,z) -#define sqlite3WithDelete(x,y) +# define sqlite3CteNew(P,T,E,S) ((void*)0) +# define sqlite3CteDelete(D,C) +# define sqlite3CteWithAdd(P,W,C) ((void*)0) +# define sqlite3WithDelete(x,y) +# define sqlite3WithPush(x,y,z) ((void*)0) #endif #ifndef SQLITE_OMIT_UPSERT -SQLITE_PRIVATE Upsert *sqlite3UpsertNew(sqlite3*,ExprList*,Expr*,ExprList*,Expr*); +SQLITE_PRIVATE Upsert *sqlite3UpsertNew(sqlite3*,ExprList*,Expr*,ExprList*,Expr*,Upsert*); SQLITE_PRIVATE void sqlite3UpsertDelete(sqlite3*,Upsert*); SQLITE_PRIVATE Upsert *sqlite3UpsertDup(sqlite3*,Upsert*); -SQLITE_PRIVATE int sqlite3UpsertAnalyzeTarget(Parse*,SrcList*,Upsert*); +SQLITE_PRIVATE int sqlite3UpsertAnalyzeTarget(Parse*,SrcList*,Upsert*,Upsert*); SQLITE_PRIVATE void sqlite3UpsertDoUpdate(Parse*,Upsert*,Table*,Index*,int); +SQLITE_PRIVATE Upsert *sqlite3UpsertOfIndex(Upsert*,Index*); +SQLITE_PRIVATE int sqlite3UpsertNextIsIPK(Upsert*); #else -#define sqlite3UpsertNew(v,w,x,y,z) ((Upsert*)0) +#define sqlite3UpsertNew(u,v,w,x,y,z) ((Upsert*)0) #define sqlite3UpsertDelete(x,y) -#define sqlite3UpsertDup(x,y) ((Upsert*)0) +#define sqlite3UpsertDup(x,y) ((Upsert*)0) +#define sqlite3UpsertOfIndex(x,y) ((Upsert*)0) +#define sqlite3UpsertNextIsIPK(x) 0 #endif @@ -19422,6 +22735,7 @@ SQLITE_PRIVATE void sqlite3FkActions(Parse*, Table*, ExprList*, int, int*, int SQLITE_PRIVATE int sqlite3FkRequired(Parse*, Table*, int*, int); SQLITE_PRIVATE u32 sqlite3FkOldmask(Parse*, Table*); SQLITE_PRIVATE FKey *sqlite3FkReferences(Table *); +SQLITE_PRIVATE void sqlite3FkClearTriggerCache(sqlite3*,int); #else #define sqlite3FkActions(a,b,c,d,e,f) #define sqlite3FkCheck(a,b,c,d,e,f) @@ -19429,6 +22743,7 @@ SQLITE_PRIVATE FKey *sqlite3FkReferences(Table *); #define sqlite3FkOldmask(a,b) 0 #define sqlite3FkRequired(a,b,c,d) 0 #define sqlite3FkReferences(a) 0 + #define sqlite3FkClearTriggerCache(a,b) #endif #ifndef SQLITE_OMIT_FOREIGN_KEY SQLITE_PRIVATE void sqlite3FkDelete(sqlite3 *, Table*); @@ -19486,12 +22801,13 @@ SQLITE_PRIVATE void sqlite3MemJournalOpen(sqlite3_file *); SQLITE_PRIVATE void sqlite3ExprSetHeightAndFlags(Parse *pParse, Expr *p); #if SQLITE_MAX_EXPR_DEPTH>0 -SQLITE_PRIVATE int sqlite3SelectExprHeight(Select *); +SQLITE_PRIVATE int sqlite3SelectExprHeight(const Select *); SQLITE_PRIVATE int sqlite3ExprCheckHeight(Parse*, int); #else #define sqlite3SelectExprHeight(x) 0 #define sqlite3ExprCheckHeight(x,y) #endif +SQLITE_PRIVATE void sqlite3ExprSetErrorOffset(Expr*,int); SQLITE_PRIVATE u32 sqlite3Get4byte(const u8*); SQLITE_PRIVATE void sqlite3Put4byte(u8*, u32); @@ -19557,8 +22873,8 @@ SQLITE_API SQLITE_EXTERN void (SQLITE_CDECL *sqlite3IoTrace)(const char*,...); */ #ifdef SQLITE_MEMDEBUG SQLITE_PRIVATE void sqlite3MemdebugSetType(void*,u8); -SQLITE_PRIVATE int sqlite3MemdebugHasType(void*,u8); -SQLITE_PRIVATE int sqlite3MemdebugNoType(void*,u8); +SQLITE_PRIVATE int sqlite3MemdebugHasType(const void*,u8); +SQLITE_PRIVATE int sqlite3MemdebugNoType(const void*,u8); #else # define sqlite3MemdebugSetType(X,Y) /* no-op */ # define sqlite3MemdebugHasType(X,Y) 1 @@ -19583,19 +22899,954 @@ SQLITE_PRIVATE int sqlite3DbpageRegister(sqlite3*); SQLITE_PRIVATE int sqlite3DbstatRegister(sqlite3*); #endif -SQLITE_PRIVATE int sqlite3ExprVectorSize(Expr *pExpr); -SQLITE_PRIVATE int sqlite3ExprIsVector(Expr *pExpr); +SQLITE_PRIVATE int sqlite3ExprVectorSize(const Expr *pExpr); +SQLITE_PRIVATE int sqlite3ExprIsVector(const Expr *pExpr); SQLITE_PRIVATE Expr *sqlite3VectorFieldSubexpr(Expr*, int); -SQLITE_PRIVATE Expr *sqlite3ExprForVectorField(Parse*,Expr*,int); +SQLITE_PRIVATE Expr *sqlite3ExprForVectorField(Parse*,Expr*,int,int); SQLITE_PRIVATE void sqlite3VectorErrorMsg(Parse*, Expr*); #ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS SQLITE_PRIVATE const char **sqlite3CompileOptions(int *pnOpt); #endif +#if SQLITE_OS_UNIX && defined(SQLITE_OS_KV_OPTIONAL) +SQLITE_PRIVATE int sqlite3KvvfsInit(void); +#endif + +#if defined(VDBE_PROFILE) \ + || defined(SQLITE_PERFORMANCE_TRACE) \ + || defined(SQLITE_ENABLE_STMT_SCANSTATUS) +SQLITE_PRIVATE sqlite3_uint64 sqlite3Hwtime(void); +#endif + +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS +# define IS_STMT_SCANSTATUS(db) (db->flags & SQLITE_StmtScanStatus) +#else +# define IS_STMT_SCANSTATUS(db) 0 +#endif + #endif /* SQLITEINT_H */ /************** End of sqliteInt.h *******************************************/ +/************** Begin file os_common.h ***************************************/ +/* +** 2004 May 22 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This file contains macros and a little bit of code that is common to +** all of the platform-specific files (os_*.c) and is #included into those +** files. +** +** This file should be #included by the os_*.c files only. It is not a +** general purpose header file. +*/ +#ifndef _OS_COMMON_H_ +#define _OS_COMMON_H_ + +/* +** At least two bugs have slipped in because we changed the MEMORY_DEBUG +** macro to SQLITE_DEBUG and some older makefiles have not yet made the +** switch. The following code should catch this problem at compile-time. +*/ +#ifdef MEMORY_DEBUG +# error "The MEMORY_DEBUG macro is obsolete. Use SQLITE_DEBUG instead." +#endif + +/* +** Macros for performance tracing. Normally turned off. Only works +** on i486 hardware. +*/ +#ifdef SQLITE_PERFORMANCE_TRACE + +static sqlite_uint64 g_start; +static sqlite_uint64 g_elapsed; +#define TIMER_START g_start=sqlite3Hwtime() +#define TIMER_END g_elapsed=sqlite3Hwtime()-g_start +#define TIMER_ELAPSED g_elapsed +#else +#define TIMER_START +#define TIMER_END +#define TIMER_ELAPSED ((sqlite_uint64)0) +#endif + +/* +** If we compile with the SQLITE_TEST macro set, then the following block +** of code will give us the ability to simulate a disk I/O error. This +** is used for testing the I/O recovery logic. +*/ +#if defined(SQLITE_TEST) +SQLITE_API extern int sqlite3_io_error_hit; +SQLITE_API extern int sqlite3_io_error_hardhit; +SQLITE_API extern int sqlite3_io_error_pending; +SQLITE_API extern int sqlite3_io_error_persist; +SQLITE_API extern int sqlite3_io_error_benign; +SQLITE_API extern int sqlite3_diskfull_pending; +SQLITE_API extern int sqlite3_diskfull; +#define SimulateIOErrorBenign(X) sqlite3_io_error_benign=(X) +#define SimulateIOError(CODE) \ + if( (sqlite3_io_error_persist && sqlite3_io_error_hit) \ + || sqlite3_io_error_pending-- == 1 ) \ + { local_ioerr(); CODE; } +static void local_ioerr(){ + IOTRACE(("IOERR\n")); + sqlite3_io_error_hit++; + if( !sqlite3_io_error_benign ) sqlite3_io_error_hardhit++; +} +#define SimulateDiskfullError(CODE) \ + if( sqlite3_diskfull_pending ){ \ + if( sqlite3_diskfull_pending == 1 ){ \ + local_ioerr(); \ + sqlite3_diskfull = 1; \ + sqlite3_io_error_hit = 1; \ + CODE; \ + }else{ \ + sqlite3_diskfull_pending--; \ + } \ + } +#else +#define SimulateIOErrorBenign(X) +#define SimulateIOError(A) +#define SimulateDiskfullError(A) +#endif /* defined(SQLITE_TEST) */ + +/* +** When testing, keep a count of the number of open files. +*/ +#if defined(SQLITE_TEST) +SQLITE_API extern int sqlite3_open_file_count; +#define OpenCounter(X) sqlite3_open_file_count+=(X) +#else +#define OpenCounter(X) +#endif /* defined(SQLITE_TEST) */ + +#endif /* !defined(_OS_COMMON_H_) */ + +/************** End of os_common.h *******************************************/ +/************** Begin file ctime.c *******************************************/ +/* DO NOT EDIT! +** This file is automatically generated by the script in the canonical +** SQLite source tree at tool/mkctimec.tcl. +** +** To modify this header, edit any of the various lists in that script +** which specify categories of generated conditionals in this file. +*/ + +/* +** 2010 February 23 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file implements routines used to report what compile-time options +** SQLite was built with. +*/ +#ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS /* IMP: R-16824-07538 */ + +/* +** Include the configuration header output by 'configure' if we're using the +** autoconf-based build +*/ +#if defined(_HAVE_SQLITE_CONFIG_H) && !defined(SQLITECONFIG_H) +/* #include "sqlite_cfg.h" */ +#define SQLITECONFIG_H 1 +#endif + +/* These macros are provided to "stringify" the value of the define +** for those options in which the value is meaningful. */ +#define CTIMEOPT_VAL_(opt) #opt +#define CTIMEOPT_VAL(opt) CTIMEOPT_VAL_(opt) + +/* Like CTIMEOPT_VAL, but especially for SQLITE_DEFAULT_LOOKASIDE. This +** option requires a separate macro because legal values contain a single +** comma. e.g. (-DSQLITE_DEFAULT_LOOKASIDE="100,100") */ +#define CTIMEOPT_VAL2_(opt1,opt2) #opt1 "," #opt2 +#define CTIMEOPT_VAL2(opt) CTIMEOPT_VAL2_(opt) +/* #include "sqliteInt.h" */ + +/* +** An array of names of all compile-time options. This array should +** be sorted A-Z. +** +** This array looks large, but in a typical installation actually uses +** only a handful of compile-time options, so most times this array is usually +** rather short and uses little memory space. +*/ +static const char * const sqlite3azCompileOpt[] = { + +#ifdef SQLITE_32BIT_ROWID + "32BIT_ROWID", +#endif +#ifdef SQLITE_4_BYTE_ALIGNED_MALLOC + "4_BYTE_ALIGNED_MALLOC", +#endif +#ifdef SQLITE_ALLOW_COVERING_INDEX_SCAN +# if SQLITE_ALLOW_COVERING_INDEX_SCAN != 1 + "ALLOW_COVERING_INDEX_SCAN=" CTIMEOPT_VAL(SQLITE_ALLOW_COVERING_INDEX_SCAN), +# endif +#endif +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + "ALLOW_ROWID_IN_VIEW", +#endif +#ifdef SQLITE_ALLOW_URI_AUTHORITY + "ALLOW_URI_AUTHORITY", +#endif +#ifdef SQLITE_ATOMIC_INTRINSICS + "ATOMIC_INTRINSICS=" CTIMEOPT_VAL(SQLITE_ATOMIC_INTRINSICS), +#endif +#ifdef SQLITE_BITMASK_TYPE + "BITMASK_TYPE=" CTIMEOPT_VAL(SQLITE_BITMASK_TYPE), +#endif +#ifdef SQLITE_BUG_COMPATIBLE_20160819 + "BUG_COMPATIBLE_20160819", +#endif +#ifdef SQLITE_BUG_COMPATIBLE_20250510 + "BUG_COMPATIBLE_20250510", +#endif +#ifdef SQLITE_CASE_SENSITIVE_LIKE + "CASE_SENSITIVE_LIKE", +#endif +#ifdef SQLITE_CHECK_PAGES + "CHECK_PAGES", +#endif +#if defined(__clang__) && defined(__clang_major__) + "COMPILER=clang-" CTIMEOPT_VAL(__clang_major__) "." + CTIMEOPT_VAL(__clang_minor__) "." + CTIMEOPT_VAL(__clang_patchlevel__), +#elif defined(_MSC_VER) + "COMPILER=msvc-" CTIMEOPT_VAL(_MSC_VER), +#elif defined(__GNUC__) && defined(__VERSION__) + "COMPILER=gcc-" __VERSION__, +#endif +#ifdef SQLITE_COVERAGE_TEST + "COVERAGE_TEST", +#endif +#ifdef SQLITE_DEBUG + "DEBUG", +#endif +#ifdef SQLITE_DEFAULT_AUTOMATIC_INDEX + "DEFAULT_AUTOMATIC_INDEX", +#endif +#ifdef SQLITE_DEFAULT_AUTOVACUUM + "DEFAULT_AUTOVACUUM", +#endif +#ifdef SQLITE_DEFAULT_CACHE_SIZE + "DEFAULT_CACHE_SIZE=" CTIMEOPT_VAL(SQLITE_DEFAULT_CACHE_SIZE), +#endif +#ifdef SQLITE_DEFAULT_CKPTFULLFSYNC + "DEFAULT_CKPTFULLFSYNC", +#endif +#ifdef SQLITE_DEFAULT_FILE_FORMAT + "DEFAULT_FILE_FORMAT=" CTIMEOPT_VAL(SQLITE_DEFAULT_FILE_FORMAT), +#endif +#ifdef SQLITE_DEFAULT_FILE_PERMISSIONS + "DEFAULT_FILE_PERMISSIONS=" CTIMEOPT_VAL(SQLITE_DEFAULT_FILE_PERMISSIONS), +#endif +#ifdef SQLITE_DEFAULT_FOREIGN_KEYS + "DEFAULT_FOREIGN_KEYS", +#endif +#ifdef SQLITE_DEFAULT_JOURNAL_SIZE_LIMIT + "DEFAULT_JOURNAL_SIZE_LIMIT=" CTIMEOPT_VAL(SQLITE_DEFAULT_JOURNAL_SIZE_LIMIT), +#endif +#ifdef SQLITE_DEFAULT_LOCKING_MODE + "DEFAULT_LOCKING_MODE=" CTIMEOPT_VAL(SQLITE_DEFAULT_LOCKING_MODE), +#endif +#ifdef SQLITE_DEFAULT_LOOKASIDE + "DEFAULT_LOOKASIDE=" CTIMEOPT_VAL2(SQLITE_DEFAULT_LOOKASIDE), +#endif +#ifdef SQLITE_DEFAULT_MEMSTATUS +# if SQLITE_DEFAULT_MEMSTATUS != 1 + "DEFAULT_MEMSTATUS=" CTIMEOPT_VAL(SQLITE_DEFAULT_MEMSTATUS), +# endif +#endif +#ifdef SQLITE_DEFAULT_MMAP_SIZE + "DEFAULT_MMAP_SIZE=" CTIMEOPT_VAL(SQLITE_DEFAULT_MMAP_SIZE), +#endif +#ifdef SQLITE_DEFAULT_PAGE_SIZE + "DEFAULT_PAGE_SIZE=" CTIMEOPT_VAL(SQLITE_DEFAULT_PAGE_SIZE), +#endif +#ifdef SQLITE_DEFAULT_PCACHE_INITSZ + "DEFAULT_PCACHE_INITSZ=" CTIMEOPT_VAL(SQLITE_DEFAULT_PCACHE_INITSZ), +#endif +#ifdef SQLITE_DEFAULT_PROXYDIR_PERMISSIONS + "DEFAULT_PROXYDIR_PERMISSIONS=" CTIMEOPT_VAL(SQLITE_DEFAULT_PROXYDIR_PERMISSIONS), +#endif +#ifdef SQLITE_DEFAULT_RECURSIVE_TRIGGERS + "DEFAULT_RECURSIVE_TRIGGERS", +#endif +#ifdef SQLITE_DEFAULT_ROWEST + "DEFAULT_ROWEST=" CTIMEOPT_VAL(SQLITE_DEFAULT_ROWEST), +#endif +#ifdef SQLITE_DEFAULT_SECTOR_SIZE + "DEFAULT_SECTOR_SIZE=" CTIMEOPT_VAL(SQLITE_DEFAULT_SECTOR_SIZE), +#endif +#ifdef SQLITE_DEFAULT_SYNCHRONOUS + "DEFAULT_SYNCHRONOUS=" CTIMEOPT_VAL(SQLITE_DEFAULT_SYNCHRONOUS), +#endif +#ifdef SQLITE_DEFAULT_WAL_AUTOCHECKPOINT + "DEFAULT_WAL_AUTOCHECKPOINT=" CTIMEOPT_VAL(SQLITE_DEFAULT_WAL_AUTOCHECKPOINT), +#endif +#ifdef SQLITE_DEFAULT_WAL_SYNCHRONOUS + "DEFAULT_WAL_SYNCHRONOUS=" CTIMEOPT_VAL(SQLITE_DEFAULT_WAL_SYNCHRONOUS), +#endif +#ifdef SQLITE_DEFAULT_WORKER_THREADS + "DEFAULT_WORKER_THREADS=" CTIMEOPT_VAL(SQLITE_DEFAULT_WORKER_THREADS), +#endif +#ifdef SQLITE_DIRECT_OVERFLOW_READ + "DIRECT_OVERFLOW_READ", +#endif +#ifdef SQLITE_DISABLE_DIRSYNC + "DISABLE_DIRSYNC", +#endif +#ifdef SQLITE_DISABLE_FTS3_UNICODE + "DISABLE_FTS3_UNICODE", +#endif +#ifdef SQLITE_DISABLE_FTS4_DEFERRED + "DISABLE_FTS4_DEFERRED", +#endif +#ifdef SQLITE_DISABLE_INTRINSIC + "DISABLE_INTRINSIC", +#endif +#ifdef SQLITE_DISABLE_LFS + "DISABLE_LFS", +#endif +#ifdef SQLITE_DISABLE_PAGECACHE_OVERFLOW_STATS + "DISABLE_PAGECACHE_OVERFLOW_STATS", +#endif +#ifdef SQLITE_DISABLE_SKIPAHEAD_DISTINCT + "DISABLE_SKIPAHEAD_DISTINCT", +#endif +#ifdef SQLITE_DQS + "DQS=" CTIMEOPT_VAL(SQLITE_DQS), +#endif +#ifdef SQLITE_ENABLE_8_3_NAMES + "ENABLE_8_3_NAMES=" CTIMEOPT_VAL(SQLITE_ENABLE_8_3_NAMES), +#endif +#ifdef SQLITE_ENABLE_API_ARMOR + "ENABLE_API_ARMOR", +#endif +#ifdef SQLITE_ENABLE_ATOMIC_WRITE + "ENABLE_ATOMIC_WRITE", +#endif +#ifdef SQLITE_ENABLE_BATCH_ATOMIC_WRITE + "ENABLE_BATCH_ATOMIC_WRITE", +#endif +#ifdef SQLITE_ENABLE_BYTECODE_VTAB + "ENABLE_BYTECODE_VTAB", +#endif +#ifdef SQLITE_ENABLE_CARRAY + "ENABLE_CARRAY", +#endif +#ifdef SQLITE_ENABLE_CEROD + "ENABLE_CEROD=" CTIMEOPT_VAL(SQLITE_ENABLE_CEROD), +#endif +#ifdef SQLITE_ENABLE_COLUMN_METADATA + "ENABLE_COLUMN_METADATA", +#endif +#ifdef SQLITE_ENABLE_COLUMN_USED_MASK + "ENABLE_COLUMN_USED_MASK", +#endif +#ifdef SQLITE_ENABLE_COSTMULT + "ENABLE_COSTMULT", +#endif +#ifdef SQLITE_ENABLE_CURSOR_HINTS + "ENABLE_CURSOR_HINTS", +#endif +#ifdef SQLITE_ENABLE_DBPAGE_VTAB + "ENABLE_DBPAGE_VTAB", +#endif +#ifdef SQLITE_ENABLE_DBSTAT_VTAB + "ENABLE_DBSTAT_VTAB", +#endif +#ifdef SQLITE_ENABLE_EXPENSIVE_ASSERT + "ENABLE_EXPENSIVE_ASSERT", +#endif +#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS + "ENABLE_EXPLAIN_COMMENTS", +#endif +#ifdef SQLITE_ENABLE_FTS3 + "ENABLE_FTS3", +#endif +#ifdef SQLITE_ENABLE_FTS3_PARENTHESIS + "ENABLE_FTS3_PARENTHESIS", +#endif +#ifdef SQLITE_ENABLE_FTS3_TOKENIZER + "ENABLE_FTS3_TOKENIZER", +#endif +#ifdef SQLITE_ENABLE_FTS4 + "ENABLE_FTS4", +#endif +#ifdef SQLITE_ENABLE_FTS5 + "ENABLE_FTS5", +#endif +#ifdef SQLITE_ENABLE_GEOPOLY + "ENABLE_GEOPOLY", +#endif +#ifdef SQLITE_ENABLE_HIDDEN_COLUMNS + "ENABLE_HIDDEN_COLUMNS", +#endif +#ifdef SQLITE_ENABLE_ICU + "ENABLE_ICU", +#endif +#ifdef SQLITE_ENABLE_IOTRACE + "ENABLE_IOTRACE", +#endif +#ifdef SQLITE_ENABLE_LOAD_EXTENSION + "ENABLE_LOAD_EXTENSION", +#endif +#ifdef SQLITE_ENABLE_LOCKING_STYLE + "ENABLE_LOCKING_STYLE=" CTIMEOPT_VAL(SQLITE_ENABLE_LOCKING_STYLE), +#endif +#ifdef SQLITE_ENABLE_MATH_FUNCTIONS + "ENABLE_MATH_FUNCTIONS", +#endif +#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT + "ENABLE_MEMORY_MANAGEMENT", +#endif +#ifdef SQLITE_ENABLE_MEMSYS3 + "ENABLE_MEMSYS3", +#endif +#ifdef SQLITE_ENABLE_MEMSYS5 + "ENABLE_MEMSYS5", +#endif +#ifdef SQLITE_ENABLE_MULTIPLEX + "ENABLE_MULTIPLEX", +#endif +#ifdef SQLITE_ENABLE_NORMALIZE + "ENABLE_NORMALIZE", +#endif +#ifdef SQLITE_ENABLE_NULL_TRIM + "ENABLE_NULL_TRIM", +#endif +#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC + "ENABLE_OFFSET_SQL_FUNC", +#endif +#ifdef SQLITE_ENABLE_ORDERED_SET_AGGREGATES + "ENABLE_ORDERED_SET_AGGREGATES", +#endif +#ifdef SQLITE_ENABLE_OVERSIZE_CELL_CHECK + "ENABLE_OVERSIZE_CELL_CHECK", +#endif +#ifdef SQLITE_ENABLE_PERCENTILE + "ENABLE_PERCENTILE", +#endif +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + "ENABLE_PREUPDATE_HOOK", +#endif +#ifdef SQLITE_ENABLE_QPSG + "ENABLE_QPSG", +#endif +#ifdef SQLITE_ENABLE_RBU + "ENABLE_RBU", +#endif +#ifdef SQLITE_ENABLE_RTREE + "ENABLE_RTREE", +#endif +#ifdef SQLITE_ENABLE_SESSION + "ENABLE_SESSION", +#endif +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + "ENABLE_SETLK_TIMEOUT", +#endif +#ifdef SQLITE_ENABLE_SNAPSHOT + "ENABLE_SNAPSHOT", +#endif +#ifdef SQLITE_ENABLE_SORTER_REFERENCES + "ENABLE_SORTER_REFERENCES", +#endif +#ifdef SQLITE_ENABLE_SQLLOG + "ENABLE_SQLLOG", +#endif +#ifdef SQLITE_ENABLE_STAT4 + "ENABLE_STAT4", +#endif +#ifdef SQLITE_ENABLE_STMTVTAB + "ENABLE_STMTVTAB", +#endif +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + "ENABLE_STMT_SCANSTATUS", +#endif +#ifdef SQLITE_ENABLE_TREETRACE + "ENABLE_TREETRACE", +#endif +#ifdef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION + "ENABLE_UNKNOWN_SQL_FUNCTION", +#endif +#ifdef SQLITE_ENABLE_UNLOCK_NOTIFY + "ENABLE_UNLOCK_NOTIFY", +#endif +#ifdef SQLITE_ENABLE_UPDATE_DELETE_LIMIT + "ENABLE_UPDATE_DELETE_LIMIT", +#endif +#ifdef SQLITE_ENABLE_URI_00_ERROR + "ENABLE_URI_00_ERROR", +#endif +#ifdef SQLITE_ENABLE_VFSTRACE + "ENABLE_VFSTRACE", +#endif +#ifdef SQLITE_ENABLE_WHERETRACE + "ENABLE_WHERETRACE", +#endif +#ifdef SQLITE_ENABLE_ZIPVFS + "ENABLE_ZIPVFS", +#endif +#ifdef SQLITE_EXPLAIN_ESTIMATED_ROWS + "EXPLAIN_ESTIMATED_ROWS", +#endif +#ifdef SQLITE_EXTRA_AUTOEXT + "EXTRA_AUTOEXT=" CTIMEOPT_VAL(SQLITE_EXTRA_AUTOEXT), +#endif +#ifdef SQLITE_EXTRA_IFNULLROW + "EXTRA_IFNULLROW", +#endif +#ifdef SQLITE_EXTRA_INIT + "EXTRA_INIT=" CTIMEOPT_VAL(SQLITE_EXTRA_INIT), +#endif +#ifdef SQLITE_EXTRA_INIT_MUTEXED + "EXTRA_INIT_MUTEXED=" CTIMEOPT_VAL(SQLITE_EXTRA_INIT_MUTEXED), +#endif +#ifdef SQLITE_EXTRA_SHUTDOWN + "EXTRA_SHUTDOWN=" CTIMEOPT_VAL(SQLITE_EXTRA_SHUTDOWN), +#endif +#ifdef SQLITE_FTS3_MAX_EXPR_DEPTH + "FTS3_MAX_EXPR_DEPTH=" CTIMEOPT_VAL(SQLITE_FTS3_MAX_EXPR_DEPTH), +#endif +#ifdef SQLITE_FTS5_ENABLE_TEST_MI + "FTS5_ENABLE_TEST_MI", +#endif +#ifdef SQLITE_FTS5_NO_WITHOUT_ROWID + "FTS5_NO_WITHOUT_ROWID", +#endif +#if HAVE_ISNAN || SQLITE_HAVE_ISNAN + "HAVE_ISNAN", +#endif +#ifdef SQLITE_HOMEGROWN_RECURSIVE_MUTEX +# if SQLITE_HOMEGROWN_RECURSIVE_MUTEX != 1 + "HOMEGROWN_RECURSIVE_MUTEX=" CTIMEOPT_VAL(SQLITE_HOMEGROWN_RECURSIVE_MUTEX), +# endif +#endif +#ifdef SQLITE_IGNORE_AFP_LOCK_ERRORS + "IGNORE_AFP_LOCK_ERRORS", +#endif +#ifdef SQLITE_IGNORE_FLOCK_LOCK_ERRORS + "IGNORE_FLOCK_LOCK_ERRORS", +#endif +#ifdef SQLITE_INLINE_MEMCPY + "INLINE_MEMCPY", +#endif +#ifdef SQLITE_INT64_TYPE + "INT64_TYPE", +#endif +#ifdef SQLITE_INTEGRITY_CHECK_ERROR_MAX + "INTEGRITY_CHECK_ERROR_MAX=" CTIMEOPT_VAL(SQLITE_INTEGRITY_CHECK_ERROR_MAX), +#endif +#ifdef SQLITE_LEGACY_JSON_VALID + "LEGACY_JSON_VALID", +#endif +#ifdef SQLITE_LIKE_DOESNT_MATCH_BLOBS + "LIKE_DOESNT_MATCH_BLOBS", +#endif +#ifdef SQLITE_LOCK_TRACE + "LOCK_TRACE", +#endif +#ifdef SQLITE_LOG_CACHE_SPILL + "LOG_CACHE_SPILL", +#endif +#ifdef SQLITE_MALLOC_SOFT_LIMIT + "MALLOC_SOFT_LIMIT=" CTIMEOPT_VAL(SQLITE_MALLOC_SOFT_LIMIT), +#endif +#ifdef SQLITE_MAX_ATTACHED + "MAX_ATTACHED=" CTIMEOPT_VAL(SQLITE_MAX_ATTACHED), +#endif +#ifdef SQLITE_MAX_COLUMN + "MAX_COLUMN=" CTIMEOPT_VAL(SQLITE_MAX_COLUMN), +#endif +#ifdef SQLITE_MAX_COMPOUND_SELECT + "MAX_COMPOUND_SELECT=" CTIMEOPT_VAL(SQLITE_MAX_COMPOUND_SELECT), +#endif +#ifdef SQLITE_MAX_DEFAULT_PAGE_SIZE + "MAX_DEFAULT_PAGE_SIZE=" CTIMEOPT_VAL(SQLITE_MAX_DEFAULT_PAGE_SIZE), +#endif +#ifdef SQLITE_MAX_EXPR_DEPTH + "MAX_EXPR_DEPTH=" CTIMEOPT_VAL(SQLITE_MAX_EXPR_DEPTH), +#endif +#ifdef SQLITE_MAX_FUNCTION_ARG + "MAX_FUNCTION_ARG=" CTIMEOPT_VAL(SQLITE_MAX_FUNCTION_ARG), +#endif +#ifdef SQLITE_MAX_LENGTH + "MAX_LENGTH=" CTIMEOPT_VAL(SQLITE_MAX_LENGTH), +#endif +#ifdef SQLITE_MAX_LIKE_PATTERN_LENGTH + "MAX_LIKE_PATTERN_LENGTH=" CTIMEOPT_VAL(SQLITE_MAX_LIKE_PATTERN_LENGTH), +#endif +#ifdef SQLITE_MAX_MEMORY + "MAX_MEMORY=" CTIMEOPT_VAL(SQLITE_MAX_MEMORY), +#endif +#ifdef SQLITE_MAX_MMAP_SIZE + "MAX_MMAP_SIZE=" CTIMEOPT_VAL(SQLITE_MAX_MMAP_SIZE), +#endif +#ifdef SQLITE_MAX_MMAP_SIZE_ + "MAX_MMAP_SIZE_=" CTIMEOPT_VAL(SQLITE_MAX_MMAP_SIZE_), +#endif +#ifdef SQLITE_MAX_PAGE_COUNT + "MAX_PAGE_COUNT=" CTIMEOPT_VAL(SQLITE_MAX_PAGE_COUNT), +#endif +#ifdef SQLITE_MAX_PAGE_SIZE + "MAX_PAGE_SIZE=" CTIMEOPT_VAL(SQLITE_MAX_PAGE_SIZE), +#endif +#ifdef SQLITE_MAX_SCHEMA_RETRY + "MAX_SCHEMA_RETRY=" CTIMEOPT_VAL(SQLITE_MAX_SCHEMA_RETRY), +#endif +#ifdef SQLITE_MAX_SQL_LENGTH + "MAX_SQL_LENGTH=" CTIMEOPT_VAL(SQLITE_MAX_SQL_LENGTH), +#endif +#ifdef SQLITE_MAX_TRIGGER_DEPTH + "MAX_TRIGGER_DEPTH=" CTIMEOPT_VAL(SQLITE_MAX_TRIGGER_DEPTH), +#endif +#ifdef SQLITE_MAX_VARIABLE_NUMBER + "MAX_VARIABLE_NUMBER=" CTIMEOPT_VAL(SQLITE_MAX_VARIABLE_NUMBER), +#endif +#ifdef SQLITE_MAX_VDBE_OP + "MAX_VDBE_OP=" CTIMEOPT_VAL(SQLITE_MAX_VDBE_OP), +#endif +#ifdef SQLITE_MAX_WORKER_THREADS + "MAX_WORKER_THREADS=" CTIMEOPT_VAL(SQLITE_MAX_WORKER_THREADS), +#endif +#ifdef SQLITE_MEMDEBUG + "MEMDEBUG", +#endif +#ifdef SQLITE_MIXED_ENDIAN_64BIT_FLOAT + "MIXED_ENDIAN_64BIT_FLOAT", +#endif +#ifdef SQLITE_MMAP_READWRITE + "MMAP_READWRITE", +#endif +#ifdef SQLITE_MUTEX_NOOP + "MUTEX_NOOP", +#endif +#ifdef SQLITE_MUTEX_OMIT + "MUTEX_OMIT", +#endif +#ifdef SQLITE_MUTEX_PTHREADS + "MUTEX_PTHREADS", +#endif +#ifdef SQLITE_MUTEX_W32 + "MUTEX_W32", +#endif +#ifdef SQLITE_NEED_ERR_NAME + "NEED_ERR_NAME", +#endif +#ifdef SQLITE_NO_SYNC + "NO_SYNC", +#endif +#ifdef SQLITE_OMIT_ALTERTABLE + "OMIT_ALTERTABLE", +#endif +#ifdef SQLITE_OMIT_ANALYZE + "OMIT_ANALYZE", +#endif +#ifdef SQLITE_OMIT_ATTACH + "OMIT_ATTACH", +#endif +#ifdef SQLITE_OMIT_AUTHORIZATION + "OMIT_AUTHORIZATION", +#endif +#ifdef SQLITE_OMIT_AUTOINCREMENT + "OMIT_AUTOINCREMENT", +#endif +#ifdef SQLITE_OMIT_AUTOINIT + "OMIT_AUTOINIT", +#endif +#ifdef SQLITE_OMIT_AUTOMATIC_INDEX + "OMIT_AUTOMATIC_INDEX", +#endif +#ifdef SQLITE_OMIT_AUTORESET + "OMIT_AUTORESET", +#endif +#ifdef SQLITE_OMIT_AUTOVACUUM + "OMIT_AUTOVACUUM", +#endif +#ifdef SQLITE_OMIT_BETWEEN_OPTIMIZATION + "OMIT_BETWEEN_OPTIMIZATION", +#endif +#ifdef SQLITE_OMIT_BLOB_LITERAL + "OMIT_BLOB_LITERAL", +#endif +#ifdef SQLITE_OMIT_CAST + "OMIT_CAST", +#endif +#ifdef SQLITE_OMIT_CHECK + "OMIT_CHECK", +#endif +#ifdef SQLITE_OMIT_COMPLETE + "OMIT_COMPLETE", +#endif +#ifdef SQLITE_OMIT_COMPOUND_SELECT + "OMIT_COMPOUND_SELECT", +#endif +#ifdef SQLITE_OMIT_CONFLICT_CLAUSE + "OMIT_CONFLICT_CLAUSE", +#endif +#ifdef SQLITE_OMIT_CTE + "OMIT_CTE", +#endif +#if defined(SQLITE_OMIT_DATETIME_FUNCS) || defined(SQLITE_OMIT_FLOATING_POINT) + "OMIT_DATETIME_FUNCS", +#endif +#ifdef SQLITE_OMIT_DECLTYPE + "OMIT_DECLTYPE", +#endif +#ifdef SQLITE_OMIT_DEPRECATED + "OMIT_DEPRECATED", +#endif +#ifdef SQLITE_OMIT_DESERIALIZE + "OMIT_DESERIALIZE", +#endif +#ifdef SQLITE_OMIT_DISKIO + "OMIT_DISKIO", +#endif +#ifdef SQLITE_OMIT_EXPLAIN + "OMIT_EXPLAIN", +#endif +#ifdef SQLITE_OMIT_FLAG_PRAGMAS + "OMIT_FLAG_PRAGMAS", +#endif +#ifdef SQLITE_OMIT_FLOATING_POINT + "OMIT_FLOATING_POINT", +#endif +#ifdef SQLITE_OMIT_FOREIGN_KEY + "OMIT_FOREIGN_KEY", +#endif +#ifdef SQLITE_OMIT_GET_TABLE + "OMIT_GET_TABLE", +#endif +#ifdef SQLITE_OMIT_HEX_INTEGER + "OMIT_HEX_INTEGER", +#endif +#ifdef SQLITE_OMIT_INCRBLOB + "OMIT_INCRBLOB", +#endif +#ifdef SQLITE_OMIT_INTEGRITY_CHECK + "OMIT_INTEGRITY_CHECK", +#endif +#ifdef SQLITE_OMIT_INTROSPECTION_PRAGMAS + "OMIT_INTROSPECTION_PRAGMAS", +#endif +#ifdef SQLITE_OMIT_JSON + "OMIT_JSON", +#endif +#ifdef SQLITE_OMIT_LIKE_OPTIMIZATION + "OMIT_LIKE_OPTIMIZATION", +#endif +#ifdef SQLITE_OMIT_LOAD_EXTENSION + "OMIT_LOAD_EXTENSION", +#endif +#ifdef SQLITE_OMIT_LOCALTIME + "OMIT_LOCALTIME", +#endif +#ifdef SQLITE_OMIT_LOOKASIDE + "OMIT_LOOKASIDE", +#endif +#ifdef SQLITE_OMIT_MEMORYDB + "OMIT_MEMORYDB", +#endif +#ifdef SQLITE_OMIT_OR_OPTIMIZATION + "OMIT_OR_OPTIMIZATION", +#endif +#ifdef SQLITE_OMIT_PAGER_PRAGMAS + "OMIT_PAGER_PRAGMAS", +#endif +#ifdef SQLITE_OMIT_PARSER_TRACE + "OMIT_PARSER_TRACE", +#endif +#ifdef SQLITE_OMIT_POPEN + "OMIT_POPEN", +#endif +#ifdef SQLITE_OMIT_PRAGMA + "OMIT_PRAGMA", +#endif +#ifdef SQLITE_OMIT_PROGRESS_CALLBACK + "OMIT_PROGRESS_CALLBACK", +#endif +#ifdef SQLITE_OMIT_QUICKBALANCE + "OMIT_QUICKBALANCE", +#endif +#ifdef SQLITE_OMIT_REINDEX + "OMIT_REINDEX", +#endif +#ifdef SQLITE_OMIT_SCHEMA_PRAGMAS + "OMIT_SCHEMA_PRAGMAS", +#endif +#ifdef SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS + "OMIT_SCHEMA_VERSION_PRAGMAS", +#endif +#ifdef SQLITE_OMIT_SEH + "OMIT_SEH", +#endif +#ifdef SQLITE_OMIT_SHARED_CACHE + "OMIT_SHARED_CACHE", +#endif +#ifdef SQLITE_OMIT_SHUTDOWN_DIRECTORIES + "OMIT_SHUTDOWN_DIRECTORIES", +#endif +#ifdef SQLITE_OMIT_SUBQUERY + "OMIT_SUBQUERY", +#endif +#ifdef SQLITE_OMIT_TCL_VARIABLE + "OMIT_TCL_VARIABLE", +#endif +#ifdef SQLITE_OMIT_TEMPDB + "OMIT_TEMPDB", +#endif +#ifdef SQLITE_OMIT_TEST_CONTROL + "OMIT_TEST_CONTROL", +#endif +#ifdef SQLITE_OMIT_TRACE +# if SQLITE_OMIT_TRACE != 1 + "OMIT_TRACE=" CTIMEOPT_VAL(SQLITE_OMIT_TRACE), +# endif +#endif +#ifdef SQLITE_OMIT_TRIGGER + "OMIT_TRIGGER", +#endif +#ifdef SQLITE_OMIT_TRUNCATE_OPTIMIZATION + "OMIT_TRUNCATE_OPTIMIZATION", +#endif +#ifdef SQLITE_OMIT_UTF16 + "OMIT_UTF16", +#endif +#ifdef SQLITE_OMIT_VACUUM + "OMIT_VACUUM", +#endif +#ifdef SQLITE_OMIT_VIEW + "OMIT_VIEW", +#endif +#ifdef SQLITE_OMIT_VIRTUALTABLE + "OMIT_VIRTUALTABLE", +#endif +#ifdef SQLITE_OMIT_WAL + "OMIT_WAL", +#endif +#ifdef SQLITE_OMIT_WSD + "OMIT_WSD", +#endif +#ifdef SQLITE_OMIT_XFER_OPT + "OMIT_XFER_OPT", +#endif +#ifdef SQLITE_PERFORMANCE_TRACE + "PERFORMANCE_TRACE", +#endif +#ifdef SQLITE_POWERSAFE_OVERWRITE +# if SQLITE_POWERSAFE_OVERWRITE != 1 + "POWERSAFE_OVERWRITE=" CTIMEOPT_VAL(SQLITE_POWERSAFE_OVERWRITE), +# endif +#endif +#ifdef SQLITE_PREFER_PROXY_LOCKING + "PREFER_PROXY_LOCKING", +#endif +#ifdef SQLITE_PROXY_DEBUG + "PROXY_DEBUG", +#endif +#ifdef SQLITE_REVERSE_UNORDERED_SELECTS + "REVERSE_UNORDERED_SELECTS", +#endif +#ifdef SQLITE_RTREE_INT_ONLY + "RTREE_INT_ONLY", +#endif +#ifdef SQLITE_SECURE_DELETE + "SECURE_DELETE", +#endif +#ifdef SQLITE_SMALL_STACK + "SMALL_STACK", +#endif +#ifdef SQLITE_SORTER_PMASZ + "SORTER_PMASZ=" CTIMEOPT_VAL(SQLITE_SORTER_PMASZ), +#endif +#ifdef SQLITE_SOUNDEX + "SOUNDEX", +#endif +#ifdef SQLITE_STAT4_SAMPLES + "STAT4_SAMPLES=" CTIMEOPT_VAL(SQLITE_STAT4_SAMPLES), +#endif +#ifdef SQLITE_STMTJRNL_SPILL + "STMTJRNL_SPILL=" CTIMEOPT_VAL(SQLITE_STMTJRNL_SPILL), +#endif +#ifdef SQLITE_STRICT_SUBTYPE + "STRICT_SUBTYPE", +#endif +#ifdef SQLITE_SUBSTR_COMPATIBILITY + "SUBSTR_COMPATIBILITY", +#endif +#if (!defined(SQLITE_WIN32_MALLOC) \ + && !defined(SQLITE_ZERO_MALLOC) \ + && !defined(SQLITE_MEMDEBUG) \ + ) || defined(SQLITE_SYSTEM_MALLOC) + "SYSTEM_MALLOC", +#endif +#ifdef SQLITE_TCL + "TCL", +#endif +#ifdef SQLITE_TEMP_STORE + "TEMP_STORE=" CTIMEOPT_VAL(SQLITE_TEMP_STORE), +#endif +#ifdef SQLITE_TEST + "TEST", +#endif +#if defined(SQLITE_THREADSAFE) + "THREADSAFE=" CTIMEOPT_VAL(SQLITE_THREADSAFE), +#elif defined(THREADSAFE) + "THREADSAFE=" CTIMEOPT_VAL(THREADSAFE), +#else + "THREADSAFE=1", +#endif +#ifdef SQLITE_UNLINK_AFTER_CLOSE + "UNLINK_AFTER_CLOSE", +#endif +#ifdef SQLITE_UNTESTABLE + "UNTESTABLE", +#endif +#ifdef SQLITE_USE_ALLOCA + "USE_ALLOCA", +#endif +#ifdef SQLITE_USE_FCNTL_TRACE + "USE_FCNTL_TRACE", +#endif +#ifdef SQLITE_USE_URI + "USE_URI", +#endif +#ifdef SQLITE_VDBE_COVERAGE + "VDBE_COVERAGE", +#endif +#ifdef SQLITE_WIN32_MALLOC + "WIN32_MALLOC", +#endif +#ifdef SQLITE_ZERO_MALLOC + "ZERO_MALLOC", +#endif + +} ; + +SQLITE_PRIVATE const char **sqlite3CompileOptions(int *pnOpt){ + *pnOpt = sizeof(sqlite3azCompileOpt) / sizeof(sqlite3azCompileOpt[0]); + return (const char**)sqlite3azCompileOpt; +} + +#endif /* SQLITE_OMIT_COMPILEOPTION_DIAGS */ + +/************** End of ctime.c ***********************************************/ /************** Begin file global.c ******************************************/ /* ** 2008 June 13 @@ -19614,7 +23865,7 @@ SQLITE_PRIVATE const char **sqlite3CompileOptions(int *pnOpt); /* #include "sqliteInt.h" */ /* An array to map all upper-case characters into their corresponding -** lower-case character. +** lower-case character. ** ** SQLite only considers US-ASCII (or EBCDIC) characters. We do not ** handle case conversions for the UTF character set since the tables @@ -19636,7 +23887,7 @@ SQLITE_PRIVATE const unsigned char sqlite3UpperToLower[] = { 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 + 252,253,254,255, #endif #ifdef SQLITE_EBCDIC 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, /* 0x */ @@ -19656,7 +23907,35 @@ SQLITE_PRIVATE const unsigned char sqlite3UpperToLower[] = { 224,225,162,163,164,165,166,167,168,169,234,235,236,237,238,239, /* Ex */ 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255, /* Fx */ #endif +/* All of the upper-to-lower conversion data is above. The following +** 18 integers are completely unrelated. They are appended to the +** sqlite3UpperToLower[] array to avoid UBSAN warnings. Here's what is +** going on: +** +** The SQL comparison operators (<>, =, >, <=, <, and >=) are implemented +** by invoking sqlite3MemCompare(A,B) which compares values A and B and +** returns negative, zero, or positive if A is less then, equal to, or +** greater than B, respectively. Then the true false results is found by +** consulting sqlite3aLTb[opcode], sqlite3aEQb[opcode], or +** sqlite3aGTb[opcode] depending on whether the result of compare(A,B) +** is negative, zero, or positive, where opcode is the specific opcode. +** The only works because the comparison opcodes are consecutive and in +** this order: NE EQ GT LE LT GE. Various assert()s throughout the code +** ensure that is the case. +** +** These elements must be appended to another array. Otherwise the +** index (here shown as [256-OP_Ne]) would be out-of-bounds and thus +** be undefined behavior. That's goofy, but the C-standards people thought +** it was a good idea, so here we are. +*/ +/* NE EQ GT LE LT GE */ + 1, 0, 0, 1, 1, 0, /* aLTb[]: Use when compare(A,B) less than zero */ + 0, 1, 0, 1, 0, 1, /* aEQb[]: Use when compare(A,B) equals zero */ + 1, 0, 1, 0, 0, 1 /* aGTb[]: Use when compare(A,B) greater than zero*/ }; +SQLITE_PRIVATE const unsigned char *sqlite3aLTb = &sqlite3UpperToLower[256-OP_Ne]; +SQLITE_PRIVATE const unsigned char *sqlite3aEQb = &sqlite3UpperToLower[256+6-OP_Ne]; +SQLITE_PRIVATE const unsigned char *sqlite3aGTb = &sqlite3UpperToLower[256+12-OP_Ne]; /* ** The following 256 byte lookup table is used to support SQLites built-in @@ -19668,7 +23947,7 @@ SQLITE_PRIVATE const unsigned char sqlite3UpperToLower[] = { ** isalnum() 0x06 ** isxdigit() 0x08 ** toupper() 0x20 -** SQLite identifier character 0x40 +** SQLite identifier character 0x40 $, _, or non-ascii ** Quote character 0x80 ** ** Bit 0x20 is set if the mapped character requires translation to upper @@ -19681,12 +23960,11 @@ SQLITE_PRIVATE const unsigned char sqlite3UpperToLower[] = { ** The equivalent of tolower() is implemented using the sqlite3UpperToLower[] ** array. tolower() is used more often than toupper() by SQLite. ** -** Bit 0x40 is set if the character is non-alphanumeric and can be used in an +** Bit 0x40 is set if the character is non-alphanumeric and can be used in an ** SQLite identifier. Identifiers are alphanumerics, "_", "$", and any ** non-ASCII UTF character. Hence the test for whether or not a character is ** part of an identifier is 0x46. */ -#ifdef SQLITE_ASCII SQLITE_PRIVATE const unsigned char sqlite3CtypeMap[256] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 00..07 ........ */ 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, /* 08..0f ........ */ @@ -19724,7 +24002,6 @@ SQLITE_PRIVATE const unsigned char sqlite3CtypeMap[256] = { 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, /* f0..f7 ........ */ 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40 /* f8..ff ........ */ }; -#endif /* EVIDENCE-OF: R-02982-34736 In order to maintain full backwards ** compatibility for legacy applications, the URI filename capability is @@ -19736,24 +24013,24 @@ SQLITE_PRIVATE const unsigned char sqlite3CtypeMap[256] = { ** EVIDENCE-OF: R-43642-56306 By default, URI handling is globally ** disabled. The default value may be changed by compiling with the ** SQLITE_USE_URI symbol defined. -** -** URI filenames are enabled by default if SQLITE_HAS_CODEC is -** enabled. */ #ifndef SQLITE_USE_URI -# ifdef SQLITE_HAS_CODEC -# define SQLITE_USE_URI 1 -# else -# define SQLITE_USE_URI 0 -# endif +# define SQLITE_USE_URI 0 #endif /* EVIDENCE-OF: R-38720-18127 The default setting is determined by the ** SQLITE_ALLOW_COVERING_INDEX_SCAN compile-time option, or is "on" if ** that compile-time option is omitted. */ -#ifndef SQLITE_ALLOW_COVERING_INDEX_SCAN +#if !defined(SQLITE_ALLOW_COVERING_INDEX_SCAN) # define SQLITE_ALLOW_COVERING_INDEX_SCAN 1 +#else +# if !SQLITE_ALLOW_COVERING_INDEX_SCAN +# error "Compile-time disabling of covering index scan using the\ + -DSQLITE_ALLOW_COVERING_INDEX_SCAN=0 option is deprecated.\ + Contact SQLite developers if this is a problem for you, and\ + delete this #error macro to continue with your build." +# endif #endif /* The minimum PMA size is set to this value multiplied by the database @@ -19771,7 +24048,7 @@ SQLITE_PRIVATE const unsigned char sqlite3CtypeMap[256] = { ** if journal_mode=MEMORY or if temp_store=MEMORY, regardless of this ** setting.) */ -#ifndef SQLITE_STMTJRNL_SPILL +#ifndef SQLITE_STMTJRNL_SPILL # define SQLITE_STMTJRNL_SPILL (64*1024) #endif @@ -19782,9 +24059,18 @@ SQLITE_PRIVATE const unsigned char sqlite3CtypeMap[256] = { ** changed as start-time using sqlite3_config(SQLITE_CONFIG_LOOKASIDE) ** or at run-time for an individual database connection using ** sqlite3_db_config(db, SQLITE_DBCONFIG_LOOKASIDE); +** +** With the two-size-lookaside enhancement, less lookaside is required. +** The default configuration of 1200,40 actually provides 30 1200-byte slots +** and 93 128-byte slots, which is more lookaside than is available +** using the older 1200,100 configuration without two-size-lookaside. */ #ifndef SQLITE_DEFAULT_LOOKASIDE -# define SQLITE_DEFAULT_LOOKASIDE 1200,100 +# ifdef SQLITE_OMIT_TWOSIZE_LOOKASIDE +# define SQLITE_DEFAULT_LOOKASIDE 1200,100 /* 120KB of memory */ +# else +# define SQLITE_DEFAULT_LOOKASIDE 1200,40 /* 48KB of memory */ +# endif #endif @@ -19806,6 +24092,10 @@ SQLITE_PRIVATE SQLITE_WSD struct Sqlite3Config sqlite3Config = { SQLITE_USE_URI, /* bOpenUri */ SQLITE_ALLOW_COVERING_INDEX_SCAN, /* bUseCis */ 0, /* bSmallMalloc */ + 1, /* bExtraSchemaChecks */ +#ifdef SQLITE_DEBUG + 0, /* bJsonSelfcheck */ +#endif 0x7ffffffe, /* mxStrlen */ 0, /* neverCorrupt */ SQLITE_DEFAULT_LOOKASIDE, /* szLookaside, nLookaside */ @@ -19842,16 +24132,23 @@ SQLITE_PRIVATE SQLITE_WSD struct Sqlite3Config sqlite3Config = { 0, /* xVdbeBranch */ 0, /* pVbeBranchArg */ #endif -#ifdef SQLITE_ENABLE_DESERIALIZE +#ifndef SQLITE_OMIT_DESERIALIZE SQLITE_MEMDB_DEFAULT_MAXSIZE, /* mxMemdbSize */ #endif #ifndef SQLITE_UNTESTABLE 0, /* xTestCallback */ +#endif +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + 0, /* mNoVisibleRowid. 0 == allow rowid-in-view */ #endif 0, /* bLocaltimeFault */ - 0, /* bInternalFunctions */ + 0, /* xAltLocaltime */ 0x7ffffffe, /* iOnceResetThreshold */ SQLITE_DEFAULT_SORTERREF_SIZE, /* szSorterRef */ + 0, /* iPrngSeed */ +#ifdef SQLITE_DEBUG + {0,0,0,0,0,0}, /* aTune */ +#endif }; /* @@ -19861,13 +24158,17 @@ SQLITE_PRIVATE SQLITE_WSD struct Sqlite3Config sqlite3Config = { */ SQLITE_PRIVATE FuncDefHash sqlite3BuiltinFunctions; +#if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_DEBUG) /* -** Constant tokens for values 0 and 1. +** Counter used for coverage testing. Does not come into play for +** release builds. +** +** Access to this global variable is not mutex protected. This might +** result in TSAN warnings. But as the variable does not exist in +** release builds, that should not be a concern. */ -SQLITE_PRIVATE const Token sqlite3IntTokens[] = { - { "0", 1 }, - { "1", 1 } -}; +SQLITE_PRIVATE unsigned int sqlite3CoverageCounter; +#endif /* SQLITE_COVERAGE_TEST || SQLITE_DEBUG */ #ifdef VDBE_PROFILE /* @@ -19899,12 +24200,18 @@ SQLITE_PRIVATE sqlite3_uint64 sqlite3NProfileCnt = 0; SQLITE_PRIVATE int sqlite3PendingByte = 0x40000000; #endif +/* +** Tracing flags set by SQLITE_TESTCTRL_TRACEFLAGS. +*/ +SQLITE_PRIVATE u32 sqlite3TreeTrace = 0; +SQLITE_PRIVATE u32 sqlite3WhereTrace = 0; + /* #include "opcodes.h" */ /* ** Properties of opcodes. The OPFLG_INITIALIZER macro is ** created by mkopcodeh.awk during compilation. Data is obtained ** from the comments following the "case OP_xxxx:" statements in -** the vdbe.c file. +** the vdbe.c file. */ SQLITE_PRIVATE const unsigned char sqlite3OpcodeProperty[] = OPFLG_INITIALIZER; @@ -19913,6 +24220,36 @@ SQLITE_PRIVATE const unsigned char sqlite3OpcodeProperty[] = OPFLG_INITIALIZER; */ SQLITE_PRIVATE const char sqlite3StrBINARY[] = "BINARY"; +/* +** Standard typenames. These names must match the COLTYPE_* definitions. +** Adjust the SQLITE_N_STDTYPE value if adding or removing entries. +** +** sqlite3StdType[] The actual names of the datatypes. +** +** sqlite3StdTypeLen[] The length (in bytes) of each entry +** in sqlite3StdType[]. +** +** sqlite3StdTypeAffinity[] The affinity associated with each entry +** in sqlite3StdType[]. +*/ +SQLITE_PRIVATE const unsigned char sqlite3StdTypeLen[] = { 3, 4, 3, 7, 4, 4 }; +SQLITE_PRIVATE const char sqlite3StdTypeAffinity[] = { + SQLITE_AFF_NUMERIC, + SQLITE_AFF_BLOB, + SQLITE_AFF_INTEGER, + SQLITE_AFF_INTEGER, + SQLITE_AFF_REAL, + SQLITE_AFF_TEXT +}; +SQLITE_PRIVATE const char *sqlite3StdType[] = { + "ANY", + "BLOB", + "INT", + "INTEGER", + "REAL", + "TEXT" +}; + /************** End of global.c **********************************************/ /************** Begin file status.c ******************************************/ /* @@ -19966,7 +24303,8 @@ SQLITE_PRIVATE const char sqlite3StrBINARY[] = "BINARY"; ** "explain" P4 display logic is enabled. */ #if !defined(SQLITE_OMIT_EXPLAIN) || !defined(NDEBUG) \ - || defined(VDBE_PROFILE) || defined(SQLITE_DEBUG) + || defined(VDBE_PROFILE) || defined(SQLITE_DEBUG) \ + || defined(SQLITE_ENABLE_BYTECODE_VTAB) # define VDBE_DISPLAY_P4 1 #else # define VDBE_DISPLAY_P4 0 @@ -19990,6 +24328,9 @@ typedef struct VdbeSorter VdbeSorter; /* Elements of the linked list at Vdbe.pAuxData */ typedef struct AuxData AuxData; +/* A cache of large TEXT or BLOB values in a VdbeCursor */ +typedef struct VdbeTxtBlbCache VdbeTxtBlbCache; + /* Types of VDBE cursors */ #define CURTYPE_BTREE 0 #define CURTYPE_SORTER 1 @@ -20009,7 +24350,7 @@ typedef struct AuxData AuxData; typedef struct VdbeCursor VdbeCursor; struct VdbeCursor { u8 eCurType; /* One of the CURTYPE_* values above */ - i8 iDb; /* Index of cursor database in db->aDb[] (or -1) */ + i8 iDb; /* Index of cursor database in db->aDb[] */ u8 nullRow; /* True if pointing to a row with no data */ u8 deferredMoveto; /* A call to sqlite3BtreeMoveto() is needed */ u8 isTable; /* True for rowid tables. False for indexes */ @@ -20020,10 +24361,14 @@ struct VdbeCursor { Bool isEphemeral:1; /* True for an ephemeral table */ Bool useRandomRowid:1; /* Generate new record numbers semi-randomly */ Bool isOrdered:1; /* True if the table is not BTREE_UNORDERED */ - Bool seekHit:1; /* See the OP_SeekHit and OP_IfNoHope opcodes */ - Btree *pBtx; /* Separate file holding temporary table */ + Bool noReuse:1; /* OpenEphemeral may not reuse this cursor */ + Bool colCache:1; /* pCache pointer is initialized and non-NULL */ + u16 seekHit; /* See the OP_SeekHit and OP_IfNoHope opcodes */ + union { /* pBtx for isEphermeral. pAltMap otherwise */ + Btree *pBtx; /* Separate file holding temporary table */ + u32 *aAltMap; /* Mapping from table to index column numbers */ + } ub; i64 seqCount; /* Sequence counter */ - int *aAltMap; /* Mapping from table to index column numbers */ /* Cached OP_Column parse information is only valid if cacheStatus matches ** Vdbe.cacheCtr. Vdbe.cacheCtr will never take on the value of @@ -20058,24 +24403,50 @@ struct VdbeCursor { #ifdef SQLITE_ENABLE_COLUMN_USED_MASK u64 maskUsed; /* Mask of columns used by this cursor */ #endif + VdbeTxtBlbCache *pCache; /* Cache of large TEXT or BLOB values */ - /* 2*nField extra array elements allocated for aType[], beyond the one - ** static element declared in the structure. nField total array slots for - ** aType[] and nField+1 array slots for aOffset[] */ - u32 aType[1]; /* Type values record decode. MUST BE LAST */ + /* Space is allocated for aType to hold at least 2*nField+1 entries: + ** nField slots for aType[] and nField+1 array slots for aOffset[] */ + u32 aType[FLEXARRAY]; /* Type values record decode. MUST BE LAST */ }; +/* +** The size (in bytes) of a VdbeCursor object that has an nField value of N +** or less. The value of SZ_VDBECURSOR(n) is guaranteed to be a multiple +** of 8. +*/ +#define SZ_VDBECURSOR(N) \ + (ROUND8(offsetof(VdbeCursor,aType)) + ((N)+1)*sizeof(u64)) + +/* Return true if P is a null-only cursor +*/ +#define IsNullCursor(P) \ + ((P)->eCurType==CURTYPE_PSEUDO && (P)->nullRow && (P)->seekResult==0) /* ** A value for VdbeCursor.cacheStatus that means the cache is always invalid. */ #define CACHE_STALE 0 +/* +** Large TEXT or BLOB values can be slow to load, so we want to avoid +** loading them more than once. For that reason, large TEXT and BLOB values +** can be stored in a cache defined by this object, and attached to the +** VdbeCursor using the pCache field. +*/ +struct VdbeTxtBlbCache { + char *pCValue; /* A RCStr buffer to hold the value */ + i64 iOffset; /* File offset of the row being cached */ + int iCol; /* Column for which the cache is valid */ + u32 cacheStatus; /* Vdbe.cacheCtr value */ + u32 colCacheCtr; /* Column cache counter */ +}; + /* ** When a sub-program is executed (OP_Program), a structure of this type ** is allocated to store the current value of the program counter, as ** well as the current memory cell array and various other frame specific -** values stored in the Vdbe struct. When the sub-program is finished, +** values stored in the Vdbe struct. When the sub-program is finished, ** these values are copied back to the Vdbe from the VdbeFrame structure, ** restoring the state of the VM to as it was before the sub-program ** began executing. @@ -20097,7 +24468,6 @@ struct VdbeFrame { Vdbe *v; /* VM this frame belongs to */ VdbeFrame *pParent; /* Parent of this frame, or NULL if parent is main */ Op *aOp; /* Program instructions for parent frame */ - i64 *anExec; /* Event counters from parent frame */ Mem *aMem; /* Array of memory cells for parent frame */ VdbeCursor **apCsr; /* Array of Vdbe cursors for parent frame */ u8 *aOnce; /* Bitmask used by OP_Once */ @@ -20113,8 +24483,8 @@ struct VdbeFrame { int nMem; /* Number of entries in aMem */ int nChildMem; /* Number of memory cells for child frame */ int nChildCsr; /* Number of cursors for child frame */ - int nChange; /* Statement changes (Vdbe.nChange) */ - int nDbChange; /* Value of db->nChange */ + i64 nChange; /* Statement changes (Vdbe.nChange) */ + i64 nDbChange; /* Value of db->nChange */ }; /* Magic number for sanity checking on VdbeFrame objects */ @@ -20139,20 +24509,21 @@ struct sqlite3_value { const char *zPType; /* Pointer type when MEM_Term|MEM_Subtype|MEM_Null */ FuncDef *pDef; /* Used only when flags==MEM_Agg */ } u; + char *z; /* String or BLOB value */ + int n; /* Number of characters in string value, excluding '\0' */ u16 flags; /* Some combination of MEM_Null, MEM_Str, MEM_Dyn, etc. */ u8 enc; /* SQLITE_UTF8, SQLITE_UTF16BE, SQLITE_UTF16LE */ u8 eSubtype; /* Subtype for this value */ - int n; /* Number of characters in string value, excluding '\0' */ - char *z; /* String or BLOB value */ /* ShallowCopy only needs to copy the information above */ - char *zMalloc; /* Space to hold MEM_Str or MEM_Blob if szMalloc>0 */ + sqlite3 *db; /* The associated database connection */ int szMalloc; /* Size of the zMalloc allocation */ u32 uTemp; /* Transient storage for serial_type in OP_MakeRecord */ - sqlite3 *db; /* The associated database connection */ + char *zMalloc; /* Space to hold MEM_Str or MEM_Blob if szMalloc>0 */ void (*xDel)(void*);/* Destructor for Mem.z - only valid if MEM_Dyn */ #ifdef SQLITE_DEBUG Mem *pScopyFrom; /* This Mem is a shallow copy of pScopyFrom */ u16 mScopyFlags; /* flags value immediately after the shallow copy */ + u8 bScopy; /* The pScopyFrom of some other Mem *might* point here */ #endif }; @@ -20160,11 +24531,43 @@ struct sqlite3_value { ** Size of struct Mem not including the Mem.zMalloc member or anything that ** follows. */ -#define MEMCELLSIZE offsetof(Mem,zMalloc) +#define MEMCELLSIZE offsetof(Mem,db) -/* One or more of the following flags are set to indicate the validOK +/* One or more of the following flags are set to indicate the ** representations of the value stored in the Mem struct. ** +** * MEM_Null An SQL NULL value +** +** * MEM_Null|MEM_Zero An SQL NULL with the virtual table +** UPDATE no-change flag set +** +** * MEM_Null|MEM_Term| An SQL NULL, but also contains a +** MEM_Subtype pointer accessible using +** sqlite3_value_pointer(). +** +** * MEM_Null|MEM_Cleared Special SQL NULL that compares non-equal +** to other NULLs even using the IS operator. +** +** * MEM_Str A string, stored in Mem.z with +** length Mem.n. Zero-terminated if +** MEM_Term is set. This flag is +** incompatible with MEM_Blob and +** MEM_Null, but can appear with MEM_Int, +** MEM_Real, and MEM_IntReal. +** +** * MEM_Blob A blob, stored in Mem.z length Mem.n. +** Incompatible with MEM_Str, MEM_Null, +** MEM_Int, MEM_Real, and MEM_IntReal. +** +** * MEM_Blob|MEM_Zero A blob in Mem.z of length Mem.n plus +** Mem.u.nZero extra 0x00 bytes at the end. +** +** * MEM_Int Integer stored in Mem.u.i. +** +** * MEM_Real Real stored in Mem.u.r. +** +** * MEM_IntReal Real stored as an integer in Mem.u.i. +** ** If the MEM_Null flag is set, then the value is an SQL NULL value. ** For a pointer type created using sqlite3_bind_pointer() or ** sqlite3_result_pointer() the MEM_Term and MEM_Subtype flags are also set. @@ -20172,38 +24575,35 @@ struct sqlite3_value { ** If the MEM_Str flag is set then Mem.z points at a string representation. ** Usually this is encoded in the same unicode encoding as the main ** database (see below for exceptions). If the MEM_Term flag is also -** set, then the string is nul terminated. The MEM_Int and MEM_Real +** set, then the string is nul terminated. The MEM_Int and MEM_Real ** flags may coexist with the MEM_Str flag. */ +#define MEM_Undefined 0x0000 /* Value is undefined */ #define MEM_Null 0x0001 /* Value is NULL (or a pointer) */ #define MEM_Str 0x0002 /* Value is a string */ #define MEM_Int 0x0004 /* Value is an integer */ #define MEM_Real 0x0008 /* Value is a real number */ #define MEM_Blob 0x0010 /* Value is a BLOB */ -#define MEM_AffMask 0x001f /* Mask of affinity bits */ -#define MEM_FromBind 0x0020 /* Value originates from sqlite3_bind() */ -/* Available 0x0040 */ -#define MEM_Undefined 0x0080 /* Value is undefined */ -#define MEM_Cleared 0x0100 /* NULL set by OP_Null, not from data */ -#define MEM_TypeMask 0xc1df /* Mask of type bits */ - +#define MEM_IntReal 0x0020 /* MEM_Int that stringifies like MEM_Real */ +#define MEM_AffMask 0x003f /* Mask of affinity bits */ -/* Whenever Mem contains a valid string or blob representation, one of -** the following flags must be set to determine the memory management -** policy for Mem.z. The MEM_Term flag tells us whether or not the -** string is \000 or \u0000 terminated +/* Extra bits that modify the meanings of the core datatypes above */ +#define MEM_FromBind 0x0040 /* Value originates from sqlite3_bind() */ + /* 0x0080 // Available */ +#define MEM_Cleared 0x0100 /* NULL set by OP_Null, not from data */ #define MEM_Term 0x0200 /* String in Mem.z is zero terminated */ -#define MEM_Dyn 0x0400 /* Need to call Mem.xDel() on Mem.z */ -#define MEM_Static 0x0800 /* Mem.z points to a static string */ -#define MEM_Ephem 0x1000 /* Mem.z points to an ephemeral string */ -#define MEM_Agg 0x2000 /* Mem.z points to an agg function context */ -#define MEM_Zero 0x4000 /* Mem.i contains count of 0s appended to blob */ -#define MEM_Subtype 0x8000 /* Mem.eSubtype is valid */ -#ifdef SQLITE_OMIT_INCRBLOB - #undef MEM_Zero - #define MEM_Zero 0x0000 -#endif +#define MEM_Zero 0x0400 /* Mem.i contains count of 0s appended to blob */ +#define MEM_Subtype 0x0800 /* Mem.eSubtype is valid */ +#define MEM_TypeMask 0x0dbf /* Mask of type bits */ + +/* Bits that determine the storage for Mem.z for a string or blob or +** aggregate accumulator. +*/ +#define MEM_Dyn 0x1000 /* Need to call Mem.xDel() on Mem.z */ +#define MEM_Static 0x2000 /* Mem.z points to a static string */ +#define MEM_Ephem 0x4000 /* Mem.z points to an ephemeral string */ +#define MEM_Agg 0x8000 /* Mem.z points to an agg function context */ /* Return TRUE if Mem X contains dynamically allocated content - anything ** that needs to be deallocated to avoid a leak. @@ -20221,18 +24621,23 @@ struct sqlite3_value { ** True if Mem X is a NULL-nochng type. */ #define MemNullNochng(X) \ - ((X)->flags==(MEM_Null|MEM_Zero) && (X)->n==0 && (X)->u.nZero==0) + (((X)->flags&MEM_TypeMask)==(MEM_Null|MEM_Zero) \ + && (X)->n==0 && (X)->u.nZero==0) /* -** Return true if a memory cell is not marked as invalid. This macro +** Return true if a memory cell has been initialized and is valid. ** is for use inside assert() statements only. +** +** A Memory cell is initialized if at least one of the +** MEM_Null, MEM_Str, MEM_Int, MEM_Real, MEM_Blob, or MEM_IntReal bits +** is set. It is "undefined" if all those bits are zero. */ #ifdef SQLITE_DEBUG -#define memIsValid(M) ((M)->flags & MEM_Undefined)==0 +#define memIsValid(M) ((M)->flags & MEM_AffMask)!=0 #endif /* -** Each auxiliary data pointer stored by a user defined function +** Each auxiliary data pointer stored by a user defined function ** implementation calling sqlite3_set_auxdata() is stored in an instance ** of this structure. All such structures associated with a single VM ** are stored in a linked list headed at Vdbe.pAuxData. All are destroyed @@ -20266,22 +24671,35 @@ struct sqlite3_context { Vdbe *pVdbe; /* The VM that owns this context */ int iOp; /* Instruction number of OP_Function */ int isError; /* Error code returned by the function. */ + u8 enc; /* Encoding to use for results */ u8 skipFlag; /* Skip accumulator loading if true */ - u8 argc; /* Number of arguments */ - sqlite3_value *argv[1]; /* Argument set */ + u16 argc; /* Number of arguments */ + sqlite3_value *argv[FLEXARRAY]; /* Argument set */ }; -/* A bitfield type for use inside of structures. Always follow with :N where -** N is the number of bits. +/* +** The size (in bytes) of an sqlite3_context object that holds N +** argv[] arguments. */ -typedef unsigned bft; /* Bit Field Type */ +#define SZ_CONTEXT(N) \ + (offsetof(sqlite3_context,argv)+(N)*sizeof(sqlite3_value*)) + /* The ScanStatus object holds a single value for the ** sqlite3_stmt_scanstatus() interface. +** +** aAddrRange[]: +** This array is used by ScanStatus elements associated with EQP +** notes that make an SQLITE_SCANSTAT_NCYCLE value available. It is +** an array of up to 3 ranges of VM addresses for which the Vdbe.anCycle[] +** values should be summed to calculate the NCYCLE value. Each pair of +** integer addresses is a start and end address (both inclusive) for a range +** instructions. A start value of 0 indicates an empty range. */ typedef struct ScanStatus ScanStatus; struct ScanStatus { int addrExplain; /* OP_Explain for loop */ + int aAddrRange[6]; int addrLoop; /* Address of "loops" counter */ int addrVisit; /* Address of "rows visited" counter */ int iSelectID; /* The "Select-ID" for this loop */ @@ -20311,23 +24729,22 @@ struct DblquoteStr { */ struct Vdbe { sqlite3 *db; /* The database connection that owns this statement */ - Vdbe *pPrev,*pNext; /* Linked list of VDBEs with the same Vdbe.db */ + Vdbe **ppVPrev,*pVNext; /* Linked list of VDBEs with the same Vdbe.db */ Parse *pParse; /* Parsing context used to create this Vdbe */ ynVar nVar; /* Number of entries in aVar[] */ - u32 magic; /* Magic number for sanity checking */ int nMem; /* Number of memory locations currently allocated */ int nCursor; /* Number of slots in apCsr[] */ u32 cacheCtr; /* VdbeCursor row cache generation counter */ int pc; /* The program counter */ int rc; /* Value to return */ - int nChange; /* Number of db changes made since last reset */ + i64 nChange; /* Number of db changes made since last reset */ int iStatement; /* Statement number (or 0 if has no opened stmt) */ i64 iCurrentTime; /* Value of julianday('now') for this statement */ i64 nFkConstraint; /* Number of imm. FK constraints this VM */ i64 nStmtDefCons; /* Number of def. constraints when stmt started */ i64 nStmtDefImmCons; /* Number of def. imm constraints when stmt started */ Mem *aMem; /* The memory locations */ - Mem **apArg; /* Arguments to currently executing user function */ + Mem **apArg; /* Arguments xUpdate and xFilter vtab methods */ VdbeCursor **apCsr; /* One element of this array for each open cursor */ Mem *aVar; /* Values for the OP_Variable opcode. */ @@ -20338,7 +24755,7 @@ struct Vdbe { int nOp; /* Number of instructions in the program */ int nOpAlloc; /* Slots allocated for aOp[] */ Mem *aColName; /* Column names to return */ - Mem *pResultSet; /* Pointer to an array of results */ + Mem *pResultRow; /* Current output row */ char *zErrMsg; /* Error message written here */ VList *pVList; /* Name of variables */ #ifndef SQLITE_OMIT_TRACE @@ -20347,22 +24764,24 @@ struct Vdbe { #ifdef SQLITE_DEBUG int rcApp; /* errcode set by sqlite3_result_error_code() */ u32 nWrite; /* Number of write operations that have occurred */ + int napArg; /* Size of the apArg[] array */ #endif u16 nResColumn; /* Number of columns in one row of the result set */ + u16 nResAlloc; /* Column slots allocated to aColName[] */ u8 errorAction; /* Recovery action to do in case of an error */ u8 minWriteFileFormat; /* Minimum file format for writable database files */ u8 prepFlags; /* SQLITE_PREPARE_* flags */ + u8 eVdbeState; /* On of the VDBE_*_STATE values */ bft expired:2; /* 1: recompile VM immediately 2: when convenient */ - bft explain:2; /* True if EXPLAIN present on SQL command */ - bft doingRerun:1; /* True if rerunning after an auto-reprepare */ + bft explain:2; /* 0: normal, 1: EXPLAIN, 2: EXPLAIN QUERY PLAN */ bft changeCntOn:1; /* True to update the change-counter */ - bft runOnlyOnce:1; /* Automatically expire on reset */ bft usesStmtJournal:1; /* True if uses a statement journal */ bft readOnly:1; /* True for statements that do not write */ bft bIsReader:1; /* True for statements that read */ + bft haveEqpOps:1; /* Bytecode supports EXPLAIN QUERY PLAN */ yDbMask btreeMask; /* Bitmask of db->aDb[] entries referenced */ yDbMask lockMask; /* Subset of btreeMask that requires a lock */ - u32 aCounter[7]; /* Counters used by sqlite3_stmt_status() */ + u32 aCounter[9]; /* Counters used by sqlite3_stmt_status() */ char *zSql; /* Text of the SQL statement that generated this */ #ifdef SQLITE_ENABLE_NORMALIZE char *zNormSql; /* Normalization of the associated SQL statement */ @@ -20376,23 +24795,21 @@ struct Vdbe { SubProgram *pProgram; /* Linked list of all sub-programs used by VM */ AuxData *pAuxData; /* Linked list of auxdata allocations */ #ifdef SQLITE_ENABLE_STMT_SCANSTATUS - i64 *anExec; /* Number of times each op has been executed */ int nScan; /* Entries in aScan[] */ ScanStatus *aScan; /* Scan definitions for sqlite3_stmt_scanstatus() */ #endif }; /* -** The following are allowed values for Vdbe.magic +** The following are allowed values for Vdbe.eVdbeState */ -#define VDBE_MAGIC_INIT 0x16bceaa5 /* Building a VDBE program */ -#define VDBE_MAGIC_RUN 0x2df20da3 /* VDBE is ready to execute */ -#define VDBE_MAGIC_HALT 0x319c2973 /* VDBE has completed execution */ -#define VDBE_MAGIC_RESET 0x48fa9f76 /* Reset and ready to run again */ -#define VDBE_MAGIC_DEAD 0x5606c3c8 /* The VDBE has been deallocated */ +#define VDBE_INIT_STATE 0 /* Prepared statement under construction */ +#define VDBE_READY_STATE 1 /* Ready to run but not yet started */ +#define VDBE_RUN_STATE 2 /* Run in progress */ +#define VDBE_HALT_STATE 3 /* Finished. Need reset() or finalize() */ /* -** Structure used to store the context required by the +** Structure used to store the context required by the ** sqlite3_preupdate_*() API functions. */ struct PreUpdate { @@ -20400,37 +24817,81 @@ struct PreUpdate { VdbeCursor *pCsr; /* Cursor to read old values from */ int op; /* One of SQLITE_INSERT, UPDATE, DELETE */ u8 *aRecord; /* old.* database record */ - KeyInfo keyinfo; + KeyInfo *pKeyinfo; /* Key information */ UnpackedRecord *pUnpacked; /* Unpacked version of aRecord[] */ UnpackedRecord *pNewUnpacked; /* Unpacked version of new.* record */ int iNewReg; /* Register for new.* values */ + int iBlobWrite; /* Value returned by preupdate_blobwrite() */ i64 iKey1; /* First key value passed to hook */ i64 iKey2; /* Second key value passed to hook */ + Mem oldipk; /* Memory cell holding "old" IPK value */ Mem *aNew; /* Array of new.* values */ - Table *pTab; /* Schema object being upated */ + Table *pTab; /* Schema object being updated */ Index *pPk; /* PK index if pTab is WITHOUT ROWID */ + sqlite3_value **apDflt; /* Array of default values, if required */ + struct { + u8 keyinfoSpace[SZ_KEYINFO_0]; /* Space to hold pKeyinfo[0] content */ + } uKey; }; +/* +** An instance of this object is used to pass an vector of values into +** OP_VFilter, the xFilter method of a virtual table. The vector is the +** set of values on the right-hand side of an IN constraint. +** +** The value as passed into xFilter is an sqlite3_value with a "pointer" +** type, such as is generated by sqlite3_result_pointer() and read by +** sqlite3_value_pointer. Such values have MEM_Term|MEM_Subtype|MEM_Null +** and a subtype of 'p'. The sqlite3_vtab_in_first() and _next() interfaces +** know how to use this object to step through all the values in the +** right operand of the IN constraint. +*/ +typedef struct ValueList ValueList; +struct ValueList { + BtCursor *pCsr; /* An ephemeral table holding all values */ + sqlite3_value *pOut; /* Register to hold each decoded output value */ +}; + +/* Size of content associated with serial types that fit into a +** single-byte varint. +*/ +#ifndef SQLITE_AMALGAMATION +SQLITE_PRIVATE const u8 sqlite3SmallTypeSizes[]; +#endif + /* ** Function prototypes */ SQLITE_PRIVATE void sqlite3VdbeError(Vdbe*, const char *, ...); SQLITE_PRIVATE void sqlite3VdbeFreeCursor(Vdbe *, VdbeCursor*); +SQLITE_PRIVATE void sqlite3VdbeFreeCursorNN(Vdbe*,VdbeCursor*); void sqliteVdbePopStack(Vdbe*,int); -SQLITE_PRIVATE int sqlite3VdbeCursorMoveto(VdbeCursor**, int*); +SQLITE_PRIVATE int SQLITE_NOINLINE sqlite3VdbeHandleMovedCursor(VdbeCursor *p); +SQLITE_PRIVATE int SQLITE_NOINLINE sqlite3VdbeFinishMoveto(VdbeCursor*); SQLITE_PRIVATE int sqlite3VdbeCursorRestore(VdbeCursor*); SQLITE_PRIVATE u32 sqlite3VdbeSerialTypeLen(u32); SQLITE_PRIVATE u8 sqlite3VdbeOneByteSerialTypeLen(u8); -SQLITE_PRIVATE u32 sqlite3VdbeSerialType(Mem*, int, u32*); -SQLITE_PRIVATE u32 sqlite3VdbeSerialPut(unsigned char*, Mem*, u32); -SQLITE_PRIVATE u32 sqlite3VdbeSerialGet(const unsigned char*, u32, Mem*); +#ifdef SQLITE_MIXED_ENDIAN_64BIT_FLOAT +SQLITE_PRIVATE u64 sqlite3FloatSwap(u64 in); +# define swapMixedEndianFloat(X) X = sqlite3FloatSwap(X) +#else +# define swapMixedEndianFloat(X) +#endif +SQLITE_PRIVATE void sqlite3VdbeSerialGet(const unsigned char*, u32, Mem*); SQLITE_PRIVATE void sqlite3VdbeDeleteAuxData(sqlite3*, AuxData**, int, int); int sqlite2BtreeKeyCompare(BtCursor *, const void *, int, int, int *); SQLITE_PRIVATE int sqlite3VdbeIdxKeyCompare(sqlite3*,VdbeCursor*,UnpackedRecord*,int*); SQLITE_PRIVATE int sqlite3VdbeIdxRowid(sqlite3*, BtCursor*, i64*); SQLITE_PRIVATE int sqlite3VdbeExec(Vdbe*); -#ifndef SQLITE_OMIT_EXPLAIN +#if !defined(SQLITE_OMIT_EXPLAIN) || defined(SQLITE_ENABLE_BYTECODE_VTAB) +SQLITE_PRIVATE int sqlite3VdbeNextOpcode(Vdbe*,Mem*,int,int*,int*,Op**); +SQLITE_PRIVATE char *sqlite3VdbeDisplayP4(sqlite3*,Op*); +#endif +#if defined(SQLITE_ENABLE_EXPLAIN_COMMENTS) +SQLITE_PRIVATE char *sqlite3VdbeDisplayComment(sqlite3*,const Op*,const char*); +#endif +#if !defined(SQLITE_OMIT_EXPLAIN) SQLITE_PRIVATE int sqlite3VdbeList(Vdbe*); #endif SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe*); @@ -20440,7 +24901,8 @@ SQLITE_PRIVATE int sqlite3VdbeMemCopy(Mem*, const Mem*); SQLITE_PRIVATE void sqlite3VdbeMemShallowCopy(Mem*, const Mem*, int); SQLITE_PRIVATE void sqlite3VdbeMemMove(Mem*, Mem*); SQLITE_PRIVATE int sqlite3VdbeMemNulTerminate(Mem*); -SQLITE_PRIVATE int sqlite3VdbeMemSetStr(Mem*, const char*, int, u8, void(*)(void*)); +SQLITE_PRIVATE int sqlite3VdbeMemSetStr(Mem*, const char*, i64, u8, void(*)(void*)); +SQLITE_PRIVATE int sqlite3VdbeMemSetText(Mem*, const char*, i64, void(*)(void*)); SQLITE_PRIVATE void sqlite3VdbeMemSetInt64(Mem*, i64); #ifdef SQLITE_OMIT_FLOATING_POINT # define sqlite3VdbeMemSetDouble sqlite3VdbeMemSetInt64 @@ -20450,28 +24912,37 @@ SQLITE_PRIVATE void sqlite3VdbeMemSetDouble(Mem*, double); SQLITE_PRIVATE void sqlite3VdbeMemSetPointer(Mem*, void*, const char*, void(*)(void*)); SQLITE_PRIVATE void sqlite3VdbeMemInit(Mem*,sqlite3*,u16); SQLITE_PRIVATE void sqlite3VdbeMemSetNull(Mem*); +#ifndef SQLITE_OMIT_INCRBLOB SQLITE_PRIVATE void sqlite3VdbeMemSetZeroBlob(Mem*,int); +#else +SQLITE_PRIVATE int sqlite3VdbeMemSetZeroBlob(Mem*,int); +#endif #ifdef SQLITE_DEBUG SQLITE_PRIVATE int sqlite3VdbeMemIsRowSet(const Mem*); #endif SQLITE_PRIVATE int sqlite3VdbeMemSetRowSet(Mem*); +SQLITE_PRIVATE int sqlite3VdbeMemZeroTerminateIfAble(Mem*); SQLITE_PRIVATE int sqlite3VdbeMemMakeWriteable(Mem*); SQLITE_PRIVATE int sqlite3VdbeMemStringify(Mem*, u8, u8); -SQLITE_PRIVATE i64 sqlite3VdbeIntValue(Mem*); +SQLITE_PRIVATE int sqlite3IntFloatCompare(i64,double); +SQLITE_PRIVATE i64 sqlite3VdbeIntValue(const Mem*); SQLITE_PRIVATE int sqlite3VdbeMemIntegerify(Mem*); SQLITE_PRIVATE double sqlite3VdbeRealValue(Mem*); +SQLITE_PRIVATE int sqlite3MemRealValueRC(Mem*, double*); SQLITE_PRIVATE int sqlite3VdbeBooleanValue(Mem*, int ifNull); SQLITE_PRIVATE void sqlite3VdbeIntegerAffinity(Mem*); SQLITE_PRIVATE int sqlite3VdbeMemRealify(Mem*); SQLITE_PRIVATE int sqlite3VdbeMemNumerify(Mem*); -SQLITE_PRIVATE void sqlite3VdbeMemCast(Mem*,u8,u8); +SQLITE_PRIVATE int sqlite3VdbeMemCast(Mem*,u8,u8); SQLITE_PRIVATE int sqlite3VdbeMemFromBtree(BtCursor*,u32,u32,Mem*); +SQLITE_PRIVATE int sqlite3VdbeMemFromBtreeZeroOffset(BtCursor*,u32,Mem*); SQLITE_PRIVATE void sqlite3VdbeMemRelease(Mem *p); +SQLITE_PRIVATE void sqlite3VdbeMemReleaseMalloc(Mem*p); SQLITE_PRIVATE int sqlite3VdbeMemFinalize(Mem*, FuncDef*); #ifndef SQLITE_OMIT_WINDOWFUNC SQLITE_PRIVATE int sqlite3VdbeMemAggValue(Mem*, Mem*, FuncDef*); #endif -#ifndef SQLITE_OMIT_EXPLAIN +#if !defined(SQLITE_OMIT_EXPLAIN) || defined(SQLITE_ENABLE_BYTECODE_VTAB) SQLITE_PRIVATE const char *sqlite3OpcodeName(int); #endif SQLITE_PRIVATE int sqlite3VdbeMemGrow(Mem *pMem, int n, int preserve); @@ -20484,9 +24955,11 @@ SQLITE_PRIVATE void sqlite3VdbeFrameMemDel(void*); /* Destructor on Mem */ SQLITE_PRIVATE void sqlite3VdbeFrameDelete(VdbeFrame*); /* Actually deletes the Frame */ SQLITE_PRIVATE int sqlite3VdbeFrameRestore(VdbeFrame *); #ifdef SQLITE_ENABLE_PREUPDATE_HOOK -SQLITE_PRIVATE void sqlite3VdbePreUpdateHook(Vdbe*,VdbeCursor*,int,const char*,Table*,i64,int); +SQLITE_PRIVATE void sqlite3VdbePreUpdateHook( + Vdbe*,VdbeCursor*,int,const char*,Table*,i64,int,int); #endif SQLITE_PRIVATE int sqlite3VdbeTransferError(Vdbe *p); +SQLITE_PRIVATE int sqlite3VdbeFindIndexKey(BtCursor*, Index*, UnpackedRecord*, int*, int); SQLITE_PRIVATE int sqlite3VdbeSorterInit(sqlite3 *, int, VdbeCursor *); SQLITE_PRIVATE void sqlite3VdbeSorterReset(sqlite3 *, VdbeSorter *); @@ -20497,6 +24970,8 @@ SQLITE_PRIVATE int sqlite3VdbeSorterRewind(const VdbeCursor *, int *); SQLITE_PRIVATE int sqlite3VdbeSorterWrite(const VdbeCursor *, Mem *); SQLITE_PRIVATE int sqlite3VdbeSorterCompare(const VdbeCursor *, Mem *, int, int *); +SQLITE_PRIVATE void sqlite3VdbeValueListFree(void*); + #ifdef SQLITE_DEBUG SQLITE_PRIVATE void sqlite3VdbeIncrWriteCounter(Vdbe*, VdbeCursor*); SQLITE_PRIVATE void sqlite3VdbeAssertAbortable(Vdbe*); @@ -20505,7 +24980,7 @@ SQLITE_PRIVATE void sqlite3VdbeAssertAbortable(Vdbe*); # define sqlite3VdbeAssertAbortable(V) #endif -#if !defined(SQLITE_OMIT_SHARED_CACHE) +#if !defined(SQLITE_OMIT_SHARED_CACHE) SQLITE_PRIVATE void sqlite3VdbeEnter(Vdbe*); #else # define sqlite3VdbeEnter(X) @@ -20523,14 +24998,16 @@ SQLITE_PRIVATE int sqlite3VdbeCheckMemInvariants(Mem*); #endif #ifndef SQLITE_OMIT_FOREIGN_KEY -SQLITE_PRIVATE int sqlite3VdbeCheckFk(Vdbe *, int); +SQLITE_PRIVATE int sqlite3VdbeCheckFkImmediate(Vdbe*); +SQLITE_PRIVATE int sqlite3VdbeCheckFkDeferred(Vdbe*); #else -# define sqlite3VdbeCheckFk(p,i) 0 +# define sqlite3VdbeCheckFkImmediate(p) 0 +# define sqlite3VdbeCheckFkDeferred(p) 0 #endif #ifdef SQLITE_DEBUG SQLITE_PRIVATE void sqlite3VdbePrintSql(Vdbe*); -SQLITE_PRIVATE void sqlite3VdbeMemPrettyPrint(Mem *pMem, char *zBuf); +SQLITE_PRIVATE void sqlite3VdbeMemPrettyPrint(Mem *pMem, StrAccum *pStr); #endif #ifndef SQLITE_OMIT_UTF16 SQLITE_PRIVATE int sqlite3VdbeMemTranslate(Mem*, u8); @@ -20722,30 +25199,37 @@ static u32 countLookasideSlots(LookasideSlot *p){ SQLITE_PRIVATE int sqlite3LookasideUsed(sqlite3 *db, int *pHighwater){ u32 nInit = countLookasideSlots(db->lookaside.pInit); u32 nFree = countLookasideSlots(db->lookaside.pFree); - if( pHighwater ) *pHighwater = db->lookaside.nSlot - nInit; - return db->lookaside.nSlot - (nInit+nFree); +#ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE + nInit += countLookasideSlots(db->lookaside.pSmallInit); + nFree += countLookasideSlots(db->lookaside.pSmallFree); +#endif /* SQLITE_OMIT_TWOSIZE_LOOKASIDE */ + assert( db->lookaside.nSlot >= nInit+nFree ); + if( pHighwater ) *pHighwater = (int)(db->lookaside.nSlot - nInit); + return (int)(db->lookaside.nSlot - (nInit+nFree)); } /* ** Query status information for a single database connection */ -SQLITE_API int sqlite3_db_status( - sqlite3 *db, /* The database connection whose status is desired */ - int op, /* Status verb */ - int *pCurrent, /* Write current value here */ - int *pHighwater, /* Write high-water mark here */ - int resetFlag /* Reset high-water mark if true */ +SQLITE_API int sqlite3_db_status64( + sqlite3 *db, /* The database connection whose status is desired */ + int op, /* Status verb */ + sqlite3_int64 *pCurrent, /* Write current value here */ + sqlite3_int64 *pHighwtr, /* Write high-water mark here */ + int resetFlag /* Reset high-water mark if true */ ){ int rc = SQLITE_OK; /* Return code */ #ifdef SQLITE_ENABLE_API_ARMOR - if( !sqlite3SafetyCheckOk(db) || pCurrent==0|| pHighwater==0 ){ + if( !sqlite3SafetyCheckOk(db) || pCurrent==0|| pHighwtr==0 ){ return SQLITE_MISUSE_BKPT; } #endif sqlite3_mutex_enter(db->mutex); switch( op ){ case SQLITE_DBSTATUS_LOOKASIDE_USED: { - *pCurrent = sqlite3LookasideUsed(db, pHighwater); + int H = 0; + *pCurrent = sqlite3LookasideUsed(db, &H); + *pHighwtr = H; if( resetFlag ){ LookasideSlot *p = db->lookaside.pFree; if( p ){ @@ -20754,6 +25238,15 @@ SQLITE_API int sqlite3_db_status( db->lookaside.pInit = db->lookaside.pFree; db->lookaside.pFree = 0; } +#ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE + p = db->lookaside.pSmallFree; + if( p ){ + while( p->pNext ) p = p->pNext; + p->pNext = db->lookaside.pSmallInit; + db->lookaside.pSmallInit = db->lookaside.pSmallFree; + db->lookaside.pSmallFree = 0; + } +#endif } break; } @@ -20767,21 +25260,21 @@ SQLITE_API int sqlite3_db_status( assert( (op-SQLITE_DBSTATUS_LOOKASIDE_HIT)>=0 ); assert( (op-SQLITE_DBSTATUS_LOOKASIDE_HIT)<3 ); *pCurrent = 0; - *pHighwater = db->lookaside.anStat[op - SQLITE_DBSTATUS_LOOKASIDE_HIT]; + *pHighwtr = db->lookaside.anStat[op-SQLITE_DBSTATUS_LOOKASIDE_HIT]; if( resetFlag ){ db->lookaside.anStat[op - SQLITE_DBSTATUS_LOOKASIDE_HIT] = 0; } break; } - /* + /* ** Return an approximation for the amount of memory currently used ** by all pagers associated with the given database connection. The ** highwater mark is meaningless and is returned as zero. */ case SQLITE_DBSTATUS_CACHE_USED_SHARED: case SQLITE_DBSTATUS_CACHE_USED: { - int totalUsed = 0; + sqlite3_int64 totalUsed = 0; int i; sqlite3BtreeEnterAll(db); for(i=0; inDb; i++){ @@ -20797,28 +25290,30 @@ SQLITE_API int sqlite3_db_status( } sqlite3BtreeLeaveAll(db); *pCurrent = totalUsed; - *pHighwater = 0; + *pHighwtr = 0; break; } /* ** *pCurrent gets an accurate estimate of the amount of memory used ** to store the schema for all databases (main, temp, and any ATTACHed - ** databases. *pHighwater is set to zero. + ** databases. *pHighwtr is set to zero. */ case SQLITE_DBSTATUS_SCHEMA_USED: { - int i; /* Used to iterate through schemas */ - int nByte = 0; /* Used to accumulate return value */ + int i; /* Used to iterate through schemas */ + int nByte = 0; /* Used to accumulate return value */ sqlite3BtreeEnterAll(db); db->pnBytesFreed = &nByte; + assert( db->lookaside.pEnd==db->lookaside.pTrueEnd ); + db->lookaside.pEnd = db->lookaside.pStart; for(i=0; inDb; i++){ Schema *pSchema = db->aDb[i].pSchema; if( ALWAYS(pSchema!=0) ){ HashElem *p; nByte += sqlite3GlobalConfig.m.xRoundup(sizeof(HashElem)) * ( - pSchema->tblHash.count + pSchema->tblHash.count + pSchema->trigHash.count + pSchema->idxHash.count + pSchema->fkeyHash.count @@ -20837,9 +25332,10 @@ SQLITE_API int sqlite3_db_status( } } db->pnBytesFreed = 0; + db->lookaside.pEnd = db->lookaside.pTrueEnd; sqlite3BtreeLeaveAll(db); - *pHighwater = 0; + *pHighwtr = 0; *pCurrent = nByte; break; } @@ -20847,20 +25343,22 @@ SQLITE_API int sqlite3_db_status( /* ** *pCurrent gets an accurate estimate of the amount of memory used ** to store all prepared statements. - ** *pHighwater is set to zero. + ** *pHighwtr is set to zero. */ case SQLITE_DBSTATUS_STMT_USED: { struct Vdbe *pVdbe; /* Used to iterate through VMs */ int nByte = 0; /* Used to accumulate return value */ db->pnBytesFreed = &nByte; - for(pVdbe=db->pVdbe; pVdbe; pVdbe=pVdbe->pNext){ - sqlite3VdbeClearObject(db, pVdbe); - sqlite3DbFree(db, pVdbe); + assert( db->lookaside.pEnd==db->lookaside.pTrueEnd ); + db->lookaside.pEnd = db->lookaside.pStart; + for(pVdbe=db->pVdbe; pVdbe; pVdbe=pVdbe->pVNext){ + sqlite3VdbeDelete(pVdbe); } + db->lookaside.pEnd = db->lookaside.pTrueEnd; db->pnBytesFreed = 0; - *pHighwater = 0; /* IMP: R-64479-57858 */ + *pHighwtr = 0; /* IMP: R-64479-57858 */ *pCurrent = nByte; break; @@ -20868,17 +25366,17 @@ SQLITE_API int sqlite3_db_status( /* ** Set *pCurrent to the total cache hits or misses encountered by all - ** pagers the database handle is connected to. *pHighwater is always set + ** pagers the database handle is connected to. *pHighwtr is always set ** to zero. */ case SQLITE_DBSTATUS_CACHE_SPILL: op = SQLITE_DBSTATUS_CACHE_WRITE+1; - /* Fall through into the next case */ + /* no break */ deliberate_fall_through case SQLITE_DBSTATUS_CACHE_HIT: case SQLITE_DBSTATUS_CACHE_MISS: case SQLITE_DBSTATUS_CACHE_WRITE:{ int i; - int nRet = 0; + u64 nRet = 0; assert( SQLITE_DBSTATUS_CACHE_MISS==SQLITE_DBSTATUS_CACHE_HIT+1 ); assert( SQLITE_DBSTATUS_CACHE_WRITE==SQLITE_DBSTATUS_CACHE_HIT+2 ); @@ -20888,19 +25386,39 @@ SQLITE_API int sqlite3_db_status( sqlite3PagerCacheStat(pPager, op, resetFlag, &nRet); } } - *pHighwater = 0; /* IMP: R-42420-56072 */ + *pHighwtr = 0; /* IMP: R-42420-56072 */ /* IMP: R-54100-20147 */ /* IMP: R-29431-39229 */ *pCurrent = nRet; break; } + /* Set *pCurrent to the number of bytes that the db database connection + ** has spilled to the filesystem in temporary files that could have been + ** stored in memory, had sufficient memory been available. + ** The *pHighwater is always set to zero. + */ + case SQLITE_DBSTATUS_TEMPBUF_SPILL: { + u64 nRet = 0; + if( db->aDb[1].pBt ){ + Pager *pPager = sqlite3BtreePager(db->aDb[1].pBt); + sqlite3PagerCacheStat(pPager, SQLITE_DBSTATUS_CACHE_WRITE, + resetFlag, &nRet); + nRet *= sqlite3BtreeGetPageSize(db->aDb[1].pBt); + } + nRet += db->nSpill; + if( resetFlag ) db->nSpill = 0; + *pHighwtr = 0; + *pCurrent = nRet; + break; + } + /* Set *pCurrent to non-zero if there are unresolved deferred foreign ** key constraints. Set *pCurrent to zero if all foreign key constraints - ** have been satisfied. The *pHighwater is always set to zero. + ** have been satisfied. The *pHighwtr is always set to zero. */ case SQLITE_DBSTATUS_DEFERRED_FKS: { - *pHighwater = 0; /* IMP: R-11967-56545 */ + *pHighwtr = 0; /* IMP: R-11967-56545 */ *pCurrent = db->nDeferredImmCons>0 || db->nDeferredCons>0; break; } @@ -20913,6 +25431,31 @@ SQLITE_API int sqlite3_db_status( return rc; } +/* +** 32-bit variant of sqlite3_db_status64() +*/ +SQLITE_API int sqlite3_db_status( + sqlite3 *db, /* The database connection whose status is desired */ + int op, /* Status verb */ + int *pCurrent, /* Write current value here */ + int *pHighwtr, /* Write high-water mark here */ + int resetFlag /* Reset high-water mark if true */ +){ + sqlite3_int64 C = 0, H = 0; + int rc; +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) || pCurrent==0|| pHighwtr==0 ){ + return SQLITE_MISUSE_BKPT; + } +#endif + rc = sqlite3_db_status64(db, op, &C, &H, resetFlag); + if( rc==0 ){ + *pCurrent = C & 0x7fffffff; + *pHighwtr = H & 0x7fffffff; + } + return rc; +} + /************** End of status.c **********************************************/ /************** Begin file date.c ********************************************/ /* @@ -20927,7 +25470,7 @@ SQLITE_API int sqlite3_db_status( ** ************************************************************************* ** This file contains the C functions that implement date and time -** functions for SQLite. +** functions for SQLite. ** ** There is only one exported symbol in this file - the function ** sqlite3RegisterDateTimeFunctions() found at the bottom of the file. @@ -20936,7 +25479,7 @@ SQLITE_API int sqlite3_db_status( ** SQLite processes all times and dates as julian day numbers. The ** dates and times are stored as the number of days since noon ** in Greenwich on November 24, 4714 B.C. according to the Gregorian -** calendar system. +** calendar system. ** ** 1970-01-01 00:00:00 is JD 2440587.5 ** 2000-01-01 00:00:00 is JD 2451544.5 @@ -20988,12 +25531,14 @@ struct DateTime { int tz; /* Timezone offset in minutes */ double s; /* Seconds */ char validJD; /* True (1) if iJD is valid */ - char rawS; /* Raw numeric value stored in s */ char validYMD; /* True (1) if Y,M,D are valid */ char validHMS; /* True (1) if h,m,s are valid */ - char validTZ; /* True (1) if tz is valid */ - char tzSet; /* Timezone was set explicitly */ - char isError; /* An overflow has occurred */ + char nFloor; /* Days to implement "floor" */ + unsigned rawS : 1; /* Raw numeric value stored in s */ + unsigned isError : 1; /* An overflow has occurred */ + unsigned useSubsec : 1; /* Display subsecond precision */ + unsigned isUtc : 1; /* Time is known to be UTC */ + unsigned isLocal : 1; /* Time is known to be localtime */ }; @@ -21026,8 +25571,8 @@ struct DateTime { */ static int getDigits(const char *zDate, const char *zFormat, ...){ /* The aMx[] array translates the 3rd character of each format - ** spec into a max size: a b c d e f */ - static const u16 aMx[] = { 12, 14, 24, 31, 59, 9999 }; + ** spec into a max size: a b c d e f */ + static const u16 aMx[] = { 12, 14, 24, 31, 59, 14712 }; va_list ap; int cnt = 0; char nextC; @@ -21091,6 +25636,8 @@ static int parseTimezone(const char *zDate, DateTime *p){ sgn = +1; }else if( c=='Z' || c=='z' ){ zDate++; + p->isLocal = 0; + p->isUtc = 1; goto zulu_time; }else{ return c!=0; @@ -21101,9 +25648,12 @@ static int parseTimezone(const char *zDate, DateTime *p){ } zDate += 5; p->tz = sgn*(nMn + nHr*60); + if( p->tz==0 ){ /* Forum post 2025-09-17T10:12:14z */ + p->isLocal = 0; + p->isUtc = 1; + } zulu_time: while( sqlite3Isspace(*zDate) ){ zDate++; } - p->tzSet = 1; return *zDate!=0; } @@ -21136,6 +25686,9 @@ static int parseHhMmSs(const char *zDate, DateTime *p){ zDate++; } ms /= rScale; + /* Truncate to avoid problems with sub-milliseconds + ** rounding. https://sqlite.org/forum/forumpost/766a2c9231 */ + if( ms>0.999 ) ms = 0.999; } }else{ s = 0; @@ -21147,7 +25700,6 @@ static int parseHhMmSs(const char *zDate, DateTime *p){ p->m = m; p->s = s + ms; if( parseTimezone(zDate, p) ) return 1; - p->validTZ = (p->tz!=0)?1:0; return 0; } @@ -21186,23 +25738,48 @@ static void computeJD(DateTime *p){ Y--; M += 12; } - A = Y/100; - B = 2 - A + (A/4); + A = (Y+4800)/100; + B = 38 - A + (A/4); X1 = 36525*(Y+4716)/100; X2 = 306001*(M+1)/10000; p->iJD = (sqlite3_int64)((X1 + X2 + D + B - 1524.5 ) * 86400000); p->validJD = 1; if( p->validHMS ){ - p->iJD += p->h*3600000 + p->m*60000 + (sqlite3_int64)(p->s*1000); - if( p->validTZ ){ + p->iJD += p->h*3600000 + p->m*60000 + (sqlite3_int64)(p->s*1000 + 0.5); + if( p->tz ){ p->iJD -= p->tz*60000; p->validYMD = 0; p->validHMS = 0; - p->validTZ = 0; + p->tz = 0; + p->isUtc = 1; + p->isLocal = 0; } } } +/* +** Given the YYYY-MM-DD information current in p, determine if there +** is day-of-month overflow and set nFloor to the number of days that +** would need to be subtracted from the date in order to bring the +** date back to the end of the month. +*/ +static void computeFloor(DateTime *p){ + assert( p->validYMD || p->isError ); + assert( p->D>=0 && p->D<=31 ); + assert( p->M>=0 && p->M<=12 ); + if( p->D<=28 ){ + p->nFloor = 0; + }else if( (1<M) & 0x15aa ){ + p->nFloor = 0; + }else if( p->M!=2 ){ + p->nFloor = (p->D==31); + }else if( p->Y%4!=0 || (p->Y%100==0 && p->Y%400!=0) ){ + p->nFloor = p->D - 28; + }else{ + p->nFloor = p->D - 29; + } +} + /* ** Parse dates of the form ** @@ -21241,12 +25818,16 @@ static int parseYyyyMmDd(const char *zDate, DateTime *p){ p->Y = neg ? -Y : Y; p->M = M; p->D = D; - if( p->validTZ ){ + computeFloor(p); + if( p->tz ){ computeJD(p); } return 0; } + +static void clearYMD_HMS_TZ(DateTime *p); /* Forward declaration */ + /* ** Set the time to the current time reported by the VFS. ** @@ -21256,6 +25837,9 @@ static int setDateTimeToCurrent(sqlite3_context *context, DateTime *p){ p->iJD = sqlite3StmtCurrentTime(context); if( p->iJD>0 ){ p->validJD = 1; + p->isUtc = 1; + p->isLocal = 0; + clearYMD_HMS_TZ(p); return 0; }else{ return 1; @@ -21284,7 +25868,7 @@ static void setRawDateNumber(DateTime *p, double r){ ** The following are acceptable forms for the input string: ** ** YYYY-MM-DD HH:MM:SS.FFF +/-HH:MM -** DDDD.DD +** DDDD.DD ** now ** ** In the first form, the +/-HH:MM is always optional. The fractional @@ -21294,8 +25878,8 @@ static void setRawDateNumber(DateTime *p, double r){ ** as there is a year and date. */ static int parseDateOrTime( - sqlite3_context *context, - const char *zDate, + sqlite3_context *context, + const char *zDate, DateTime *p ){ double r; @@ -21305,9 +25889,14 @@ static int parseDateOrTime( return 0; }else if( sqlite3StrICmp(zDate,"now")==0 && sqlite3NotPureFunc(context) ){ return setDateTimeToCurrent(context, p); - }else if( sqlite3AtoF(zDate, &r, sqlite3Strlen30(zDate), SQLITE_UTF8) ){ + }else if( sqlite3AtoF(zDate, &r)>0 ){ setRawDateNumber(p, r); return 0; + }else if( (sqlite3StrICmp(zDate,"subsec")==0 + || sqlite3StrICmp(zDate,"subsecond")==0) + && sqlite3NotPureFunc(context) ){ + p->useSubsec = 1; + return setDateTimeToCurrent(context, p); } return 1; } @@ -21316,7 +25905,7 @@ static int parseDateOrTime( ** Multiplying this by 86400000 gives 464269060799999 as the maximum value ** for DateTime.iJD. ** -** But some older compilers (ex: gcc 4.2.1 on older Macs) cannot deal with +** But some older compilers (ex: gcc 4.2.1 on older Macs) cannot deal with ** such a large integer literal, so we have to encode it. */ #define INT_464269060799999 ((((i64)0x1a640)<<32)|0x1072fdff) @@ -21334,7 +25923,7 @@ static int validJulianDay(sqlite3_int64 iJD){ ** Compute the Year, Month, and Day from the julian day number. */ static void computeYMD(DateTime *p){ - int Z, A, B, C, D, E, X1; + int Z, alpha, A, B, C, D, E, X1; if( p->validYMD ) return; if( !p->validJD ){ p->Y = 2000; @@ -21345,8 +25934,8 @@ static void computeYMD(DateTime *p){ return; }else{ Z = (int)((p->iJD + 43200000)/86400000); - A = (int)((Z - 1867216.25)/36524.25); - A = Z + 1 + A - (A/4); + alpha = (int)((Z + 32044.75)/36524.25) - 52; + A = Z + 1 + alpha - ((alpha+100)/4) + 25; B = A + 1524; C = (int)((B - 122.1)/365.25); D = (36525*(C&32767))/100; @@ -21363,17 +25952,14 @@ static void computeYMD(DateTime *p){ ** Compute the Hour, Minute, and Seconds from the julian day number. */ static void computeHMS(DateTime *p){ - int s; + int day_ms, day_min; /* milliseconds, minutes into the day */ if( p->validHMS ) return; computeJD(p); - s = (int)((p->iJD + 43200000) % 86400000); - p->s = s/1000.0; - s = (int)p->s; - p->s -= s; - p->h = s/3600; - s -= p->h*3600; - p->m = s/60; - p->s += s - p->m*60; + day_ms = (int)((p->iJD + 43200000) % 86400000); + p->s = (day_ms % 60000)/1000.0; + day_min = day_ms/60000; + p->m = day_min % 60; + p->h = day_min / 60; p->rawS = 0; p->validHMS = 1; } @@ -21392,20 +25978,20 @@ static void computeYMD_HMS(DateTime *p){ static void clearYMD_HMS_TZ(DateTime *p){ p->validYMD = 0; p->validHMS = 0; - p->validTZ = 0; + p->tz = 0; } #ifndef SQLITE_OMIT_LOCALTIME /* ** On recent Windows platforms, the localtime_s() function is available -** as part of the "Secure CRT". It is essentially equivalent to -** localtime_r() available under most POSIX platforms, except that the +** as part of the "Secure CRT". It is essentially equivalent to +** localtime_r() available under most POSIX platforms, except that the ** order of the parameters is reversed. ** ** See http://msdn.microsoft.com/en-us/library/a442x3ye(VS.80).aspx. ** ** If the user has not indicated to use localtime_r() or localtime_s() -** already, check for an MSVC build environment that provides +** already, check for an MSVC build environment that provides ** localtime_s(). */ #if !HAVE_LOCALTIME_R && !HAVE_LOCALTIME_S \ @@ -21420,8 +26006,10 @@ static void clearYMD_HMS_TZ(DateTime *p){ ** is available. This routine returns 0 on success and ** non-zero on any kind of error. ** -** If the sqlite3GlobalConfig.bLocaltimeFault variable is true then this -** routine will always fail. +** If the sqlite3GlobalConfig.bLocaltimeFault variable is non-zero then this +** routine will always fail. If bLocaltimeFault is nonzero and +** sqlite3GlobalConfig.xAltLocaltime is not NULL, then xAltLocaltime() is +** invoked in place of the OS-defined localtime() function. ** ** EVIDENCE-OF: R-62172-00036 In this implementation, the standard C ** library function localtime_r() is used to assist in the calculation of @@ -21432,19 +26020,35 @@ static int osLocaltime(time_t *t, struct tm *pTm){ #if !HAVE_LOCALTIME_R && !HAVE_LOCALTIME_S struct tm *pX; #if SQLITE_THREADSAFE>0 - sqlite3_mutex *mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); + sqlite3_mutex *mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MAIN); #endif sqlite3_mutex_enter(mutex); pX = localtime(t); #ifndef SQLITE_UNTESTABLE - if( sqlite3GlobalConfig.bLocaltimeFault ) pX = 0; + if( sqlite3GlobalConfig.bLocaltimeFault ){ + if( sqlite3GlobalConfig.xAltLocaltime!=0 + && 0==sqlite3GlobalConfig.xAltLocaltime((const void*)t,(void*)pTm) + ){ + pX = pTm; + }else{ + pX = 0; + } + } #endif if( pX ) *pTm = *pX; +#if SQLITE_THREADSAFE>0 sqlite3_mutex_leave(mutex); +#endif rc = pX==0; #else #ifndef SQLITE_UNTESTABLE - if( sqlite3GlobalConfig.bLocaltimeFault ) return 1; + if( sqlite3GlobalConfig.bLocaltimeFault ){ + if( sqlite3GlobalConfig.xAltLocaltime!=0 ){ + return sqlite3GlobalConfig.xAltLocaltime((const void*)t,(void*)pTm); + }else{ + return 1; + } + } #endif #if HAVE_LOCALTIME_R rc = localtime_r(t, pTm)==0; @@ -21459,67 +26063,56 @@ static int osLocaltime(time_t *t, struct tm *pTm){ #ifndef SQLITE_OMIT_LOCALTIME /* -** Compute the difference (in milliseconds) between localtime and UTC -** (a.k.a. GMT) for the time value p where p is in UTC. If no error occurs, -** return this value and set *pRc to SQLITE_OK. -** -** Or, if an error does occur, set *pRc to SQLITE_ERROR. The returned value -** is undefined in this case. +** Assuming the input DateTime is UTC, move it to its localtime equivalent. */ -static sqlite3_int64 localtimeOffset( - DateTime *p, /* Date at which to calculate offset */ - sqlite3_context *pCtx, /* Write error here if one occurs */ - int *pRc /* OUT: Error code. SQLITE_OK or ERROR */ +static int toLocaltime( + DateTime *p, /* Date at which to calculate offset */ + sqlite3_context *pCtx /* Write error here if one occurs */ ){ - DateTime x, y; time_t t; struct tm sLocal; + int iYearDiff; /* Initialize the contents of sLocal to avoid a compiler warning. */ memset(&sLocal, 0, sizeof(sLocal)); - x = *p; - computeYMD_HMS(&x); - if( x.Y<1971 || x.Y>=2038 ){ + computeJD(p); + if( p->iJD<2108667600*(i64)100000 /* 1970-01-01 */ + || p->iJD>2130141456*(i64)100000 /* 2038-01-18 */ + ){ /* EVIDENCE-OF: R-55269-29598 The localtime_r() C function normally only ** works for years between 1970 and 2037. For dates outside this range, ** SQLite attempts to map the year into an equivalent year within this ** range, do the calculation, then map the year back. */ - x.Y = 2000; - x.M = 1; - x.D = 1; - x.h = 0; - x.m = 0; - x.s = 0.0; - } else { - int s = (int)(x.s + 0.5); - x.s = s; + DateTime x = *p; + computeYMD_HMS(&x); + iYearDiff = (2000 + x.Y%4) - x.Y; + x.Y += iYearDiff; + x.validJD = 0; + computeJD(&x); + t = (time_t)(x.iJD/1000 - 21086676*(i64)10000); + }else{ + iYearDiff = 0; + t = (time_t)(p->iJD/1000 - 21086676*(i64)10000); } - x.tz = 0; - x.validJD = 0; - computeJD(&x); - t = (time_t)(x.iJD/1000 - 21086676*(i64)10000); if( osLocaltime(&t, &sLocal) ){ sqlite3_result_error(pCtx, "local time unavailable", -1); - *pRc = SQLITE_ERROR; - return 0; + return SQLITE_ERROR; } - y.Y = sLocal.tm_year + 1900; - y.M = sLocal.tm_mon + 1; - y.D = sLocal.tm_mday; - y.h = sLocal.tm_hour; - y.m = sLocal.tm_min; - y.s = sLocal.tm_sec; - y.validYMD = 1; - y.validHMS = 1; - y.validJD = 0; - y.rawS = 0; - y.validTZ = 0; - y.isError = 0; - computeJD(&y); - *pRc = SQLITE_OK; - return y.iJD - x.iJD; + p->Y = sLocal.tm_year + 1900 - iYearDiff; + p->M = sLocal.tm_mon + 1; + p->D = sLocal.tm_mday; + p->h = sLocal.tm_hour; + p->m = sLocal.tm_min; + p->s = sLocal.tm_sec + (p->iJD%1000)*0.001; + p->validYMD = 1; + p->validHMS = 1; + p->validJD = 0; + p->rawS = 0; + p->tz = 0; + p->isError = 0; + return SQLITE_OK; } #endif /* SQLITE_OMIT_LOCALTIME */ @@ -21532,20 +26125,38 @@ static sqlite3_int64 localtimeOffset( ** of several units of time. */ static const struct { - u8 eType; /* Transformation type code */ - u8 nName; /* Length of th name */ - char *zName; /* Name of the transformation */ - double rLimit; /* Maximum NNN value for this transform */ - double rXform; /* Constant used for this transform */ + u8 nName; /* Length of the name */ + char zName[7]; /* Name of the transformation */ + float rLimit; /* Maximum NNN value for this transform */ + float rXform; /* Constant used for this transform */ } aXformType[] = { - { 0, 6, "second", 464269060800.0, 86400000.0/(24.0*60.0*60.0) }, - { 0, 6, "minute", 7737817680.0, 86400000.0/(24.0*60.0) }, - { 0, 4, "hour", 128963628.0, 86400000.0/24.0 }, - { 0, 3, "day", 5373485.0, 86400000.0 }, - { 1, 5, "month", 176546.0, 30.0*86400000.0 }, - { 2, 4, "year", 14713.0, 365.0*86400000.0 }, + /* 0 */ { 6, "second", 4.6427e+14, 1.0 }, + /* 1 */ { 6, "minute", 7.7379e+12, 60.0 }, + /* 2 */ { 4, "hour", 1.2897e+11, 3600.0 }, + /* 3 */ { 3, "day", 5373485.0, 86400.0 }, + /* 4 */ { 5, "month", 176546.0, 2592000.0 }, + /* 5 */ { 4, "year", 14713.0, 31536000.0 }, }; +/* +** If the DateTime p is raw number, try to figure out if it is +** a julian day number of a unix timestamp. Set the p value +** appropriately. +*/ +static void autoAdjustDate(DateTime *p){ + if( !p->rawS || p->validJD ){ + p->rawS = 0; + }else if( p->s>=-21086676*(i64)10000 /* -4713-11-24 12:00:00 */ + && p->s<=(25340230*(i64)10000)+799 /* 9999-12-31 23:59:59 */ + ){ + double r = p->s*1000.0 + 210866760000000.0; + clearYMD_HMS_TZ(p); + p->iJD = (sqlite3_int64)(r + 0.5); + p->validJD = 1; + p->rawS = 0; + } +} + /* ** Process a modifier to a date-time stamp. The modifiers are ** as follows: @@ -21556,14 +26167,20 @@ static const struct { ** NNN.NNNN seconds ** NNN months ** NNN years +** +/-YYYY-MM-DD HH:MM:SS.SSS +** ceiling +** floor ** start of month ** start of year ** start of week ** start of day ** weekday N ** unixepoch +** auto ** localtime ** utc +** subsec +** subsecond ** ** Return 0 on success and 1 if there is any kind of error. If the error ** is in a system call (i.e. localtime()), then an error message is written @@ -21574,11 +26191,75 @@ static int parseModifier( sqlite3_context *pCtx, /* Function context */ const char *z, /* The text of the modifier */ int n, /* Length of zMod in bytes */ - DateTime *p /* The date/time value to be modified */ + DateTime *p, /* The date/time value to be modified */ + int idx /* Parameter index of the modifier */ ){ int rc = 1; double r; switch(sqlite3UpperToLower[(u8)z[0]] ){ + case 'a': { + /* + ** auto + ** + ** If rawS is available, then interpret as a julian day number, or + ** a unix timestamp, depending on its magnitude. + */ + if( sqlite3_stricmp(z, "auto")==0 ){ + if( idx>1 ) return 1; /* IMP: R-33611-57934 */ + autoAdjustDate(p); + rc = 0; + } + break; + } + case 'c': { + /* + ** ceiling + ** + ** Resolve day-of-month overflow by rolling forward into the next + ** month. As this is the default action, this modifier is really + ** a no-op that is only included for symmetry. See "floor". + */ + if( sqlite3_stricmp(z, "ceiling")==0 ){ + computeJD(p); + clearYMD_HMS_TZ(p); + rc = 0; + p->nFloor = 0; + } + break; + } + case 'f': { + /* + ** floor + ** + ** Resolve day-of-month overflow by rolling back to the end of the + ** previous month. + */ + if( sqlite3_stricmp(z, "floor")==0 ){ + computeJD(p); + p->iJD -= p->nFloor*86400000; + clearYMD_HMS_TZ(p); + rc = 0; + } + break; + } + case 'j': { + /* + ** julianday + ** + ** Always interpret the prior number as a julian-day value. If this + ** is not the first modifier, or if the prior argument is not a numeric + ** value in the allowed range of julian day numbers understood by + ** SQLite (0..5373484.5) then the result will be NULL. + */ + if( sqlite3_stricmp(z, "julianday")==0 ){ + if( idx>1 ) return 1; /* IMP: R-31176-64601 */ + if( p->validJD && p->rawS ){ + rc = 0; + p->rawS = 0; + } + } + break; + } #ifndef SQLITE_OMIT_LOCALTIME case 'l': { /* localtime @@ -21587,9 +26268,9 @@ static int parseModifier( ** show local time. */ if( sqlite3_stricmp(z, "localtime")==0 && sqlite3NotPureFunc(pCtx) ){ - computeJD(p); - p->iJD += localtimeOffset(p, pCtx, &rc); - clearYMD_HMS_TZ(p); + rc = p->isLocal ? SQLITE_OK : toLocaltime(p, pCtx); + p->isUtc = 0; + p->isLocal = 1; } break; } @@ -21602,10 +26283,11 @@ static int parseModifier( ** seconds since 1970. Convert to a real julian day number. */ if( sqlite3_stricmp(z, "unixepoch")==0 && p->rawS ){ + if( idx>1 ) return 1; /* IMP: R-49255-55373 */ r = p->s*1000.0 + 210866760000000.0; if( r>=0.0 && r<464269060800000.0 ){ clearYMD_HMS_TZ(p); - p->iJD = (sqlite3_int64)r; + p->iJD = (sqlite3_int64)(r + 0.5); p->validJD = 1; p->rawS = 0; rc = 0; @@ -21613,19 +26295,33 @@ static int parseModifier( } #ifndef SQLITE_OMIT_LOCALTIME else if( sqlite3_stricmp(z, "utc")==0 && sqlite3NotPureFunc(pCtx) ){ - if( p->tzSet==0 ){ - sqlite3_int64 c1; + if( p->isUtc==0 ){ + i64 iOrigJD; /* Original localtime */ + i64 iGuess; /* Guess at the corresponding utc time */ + int cnt = 0; /* Safety to prevent infinite loop */ + i64 iErr; /* Guess is off by this much */ + computeJD(p); - c1 = localtimeOffset(p, pCtx, &rc); - if( rc==SQLITE_OK ){ - p->iJD -= c1; - clearYMD_HMS_TZ(p); - p->iJD += c1 - localtimeOffset(p, pCtx, &rc); - } - p->tzSet = 1; - }else{ - rc = SQLITE_OK; + iGuess = iOrigJD = p->iJD; + iErr = 0; + do{ + DateTime new; + memset(&new, 0, sizeof(new)); + iGuess -= iErr; + new.iJD = iGuess; + new.validJD = 1; + rc = toLocaltime(&new, pCtx); + if( rc ) return rc; + computeJD(&new); + iErr = new.iJD - iOrigJD; + }while( iErr && cnt++<3 ); + memset(p, 0, sizeof(*p)); + p->iJD = iGuess; + p->validJD = 1; + p->isUtc = 1; + p->isLocal = 0; } + rc = SQLITE_OK; } #endif break; @@ -21639,11 +26335,11 @@ static int parseModifier( ** date is already on the appropriate weekday, this is a no-op. */ if( sqlite3_strnicmp(z, "weekday ", 8)==0 - && sqlite3AtoF(&z[8], &r, sqlite3Strlen30(&z[8]), SQLITE_UTF8) - && (n=(int)r)==r && n>=0 && r<7 ){ + && sqlite3AtoF(&z[8], &r)>0 + && r>=0.0 && r<7.0 && (n=(int)r)==r ){ sqlite3_int64 Z; computeYMD_HMS(p); - p->validTZ = 0; + p->tz = 0; p->validJD = 0; computeJD(p); Z = ((p->iJD + 129600000)/86400000) % 7; @@ -21660,8 +26356,22 @@ static int parseModifier( ** ** Move the date backwards to the beginning of the current day, ** or month or year. + ** + ** subsecond + ** subsec + ** + ** Show subsecond precision in the output of datetime() and + ** unixepoch() and strftime('%s'). */ - if( sqlite3_strnicmp(z, "start of ", 9)!=0 ) break; + if( sqlite3_strnicmp(z, "start of ", 9)!=0 ){ + if( sqlite3_stricmp(z, "subsec")==0 + || sqlite3_stricmp(z, "subsecond")==0 + ){ + p->useSubsec = 1; + rc = 0; + } + break; + } if( !p->validJD && !p->validYMD && !p->validHMS ) break; z += 9; computeYMD(p); @@ -21669,7 +26379,7 @@ static int parseModifier( p->h = p->m = 0; p->s = 0.0; p->rawS = 0; - p->validTZ = 0; + p->tz = 0; p->validJD = 0; if( sqlite3_stricmp(z,"month")==0 ){ p->D = 1; @@ -21696,19 +26406,81 @@ static int parseModifier( case '8': case '9': { double rRounder; - int i; - for(n=1; z[n] && z[n]!=':' && !sqlite3Isspace(z[n]); n++){} - if( !sqlite3AtoF(z, &r, n, SQLITE_UTF8) ){ - rc = 1; + int i, rx; + int Y,M,D,h,m,x; + const char *z2 = z; + char *zCopy; + sqlite3 *db = sqlite3_context_db_handle(pCtx); + char z0 = z[0]; + for(n=1; z[n]; n++){ + if( z[n]==':' ) break; + if( sqlite3Isspace(z[n]) ) break; + if( z[n]=='-' ){ + if( n==5 && getDigits(&z[1], "40f", &Y)==1 ) break; + if( n==6 && getDigits(&z[1], "50f", &Y)==1 ) break; + } + } + zCopy = sqlite3DbStrNDup(db, z, n); + if( zCopy==0 ) break; + rx = sqlite3AtoF(zCopy, &r)<=0; + sqlite3DbFree(db, zCopy); + if( rx ){ + assert( rc==1 ); break; } - if( z[n]==':' ){ + if( z[n]=='-' ){ + /* A modifier of the form (+|-)YYYY-MM-DD adds or subtracts the + ** specified number of years, months, and days. MM is limited to + ** the range 0-11 and DD is limited to 0-30. + */ + if( z0!='+' && z0!='-' ) break; /* Must start with +/- */ + if( n==5 ){ + if( getDigits(&z[1], "40f-20a-20d", &Y, &M, &D)!=3 ) break; + }else{ + assert( n==6 ); + if( getDigits(&z[1], "50f-20a-20d", &Y, &M, &D)!=3 ) break; + z++; + } + if( M>=12 ) break; /* M range 0..11 */ + if( D>=31 ) break; /* D range 0..30 */ + computeYMD_HMS(p); + p->validJD = 0; + if( z0=='-' ){ + p->Y -= Y; + p->M -= M; + D = -D; + }else{ + p->Y += Y; + p->M += M; + } + x = p->M>0 ? (p->M-1)/12 : (p->M-12)/12; + p->Y += x; + p->M -= x*12; + computeFloor(p); + computeJD(p); + p->validHMS = 0; + p->validYMD = 0; + p->iJD += (i64)D*86400000; + if( z[11]==0 ){ + rc = 0; + break; + } + if( sqlite3Isspace(z[11]) + && getDigits(&z[12], "20c:20e", &h, &m)==2 + ){ + z2 = &z[12]; + n = 2; + }else{ + break; + } + } + if( z2[n]==':' ){ /* A modifier of the form (+|-)HH:MM:SS.FFF adds (or subtracts) the ** specified number of hours, minutes, seconds, and fractional seconds ** to the time. The ".FFF" may be omitted. The ":SS.FFF" may be ** omitted. */ - const char *z2 = z; + DateTime tx; sqlite3_int64 day; if( !sqlite3Isdigit(*z2) ) z2++; @@ -21718,7 +26490,7 @@ static int parseModifier( tx.iJD -= 43200000; day = tx.iJD/86400000; tx.iJD -= day*86400000; - if( z[0]=='-' ) tx.iJD = -tx.iJD; + if( z0=='-' ) tx.iJD = -tx.iJD; computeJD(p); clearYMD_HMS_TZ(p); p->iJD += tx.iJD; @@ -21731,39 +26503,44 @@ static int parseModifier( z += n; while( sqlite3Isspace(*z) ) z++; n = sqlite3Strlen30(z); - if( n>10 || n<3 ) break; + if( n<3 || n>10 ) break; if( sqlite3UpperToLower[(u8)z[n-1]]=='s' ) n--; computeJD(p); - rc = 1; + assert( rc==1 ); rRounder = r<0 ? -0.5 : +0.5; + p->nFloor = 0; for(i=0; i-aXformType[i].rLimit && rM += (int)r; x = p->M>0 ? (p->M-1)/12 : (p->M-12)/12; p->Y += x; p->M -= x*12; + computeFloor(p); p->validJD = 0; r -= (int)r; break; } - case 2: { /* Special processing to add years */ + case 5: { /* Special processing to add years */ int y = (int)r; + assert( strcmp(aXformType[5].zName,"year")==0 ); computeYMD_HMS(p); + assert( p->M>=0 && p->M<=12 ); p->Y += y; + computeFloor(p); p->validJD = 0; r -= (int)r; break; } } computeJD(p); - p->iJD += (sqlite3_int64)(r*aXformType[i].rXform + rRounder); + p->iJD += (sqlite3_int64)(r*1000.0*aXformType[i].rXform + rRounder); rc = 0; break; } @@ -21788,9 +26565,9 @@ static int parseModifier( ** then assume a default value of "now" for argv[0]. */ static int isDate( - sqlite3_context *context, - int argc, - sqlite3_value **argv, + sqlite3_context *context, + int argc, + sqlite3_value **argv, DateTime *p ){ int i, n; @@ -21798,6 +26575,7 @@ static int isDate( int eType; memset(p, 0, sizeof(*p)); if( argc==0 ){ + if( !sqlite3NotPureFunc(context) ) return 1; return setDateTimeToCurrent(context, p); } if( (eType = sqlite3_value_type(argv[0]))==SQLITE_FLOAT @@ -21812,10 +26590,16 @@ static int isDate( for(i=1; iisError || !validJulianDay(p->iJD) ) return 1; + if( argc==1 && p->validYMD && p->D>28 ){ + /* Make sure a YYYY-MM-DD is normalized. + ** Example: 2023-02-31 -> 2023-03-03 */ + assert( p->validJD ); + p->validYMD = 0; + } return 0; } @@ -21842,6 +26626,28 @@ static void juliandayFunc( } } +/* +** unixepoch( TIMESTRING, MOD, MOD, ...) +** +** Return the number of seconds (including fractional seconds) since +** the unix epoch of 1970-01-01 00:00:00 GMT. +*/ +static void unixepochFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + DateTime x; + if( isDate(context, argc, argv, &x)==0 ){ + computeJD(&x); + if( x.useSubsec ){ + sqlite3_result_double(context, (x.iJD - 21086676*(i64)10000000)/1000.0); + }else{ + sqlite3_result_int64(context, x.iJD/1000 - 21086676*(i64)10000); + } + } +} + /* ** datetime( TIMESTRING, MOD, MOD, ...) ** @@ -21854,11 +26660,51 @@ static void datetimeFunc( ){ DateTime x; if( isDate(context, argc, argv, &x)==0 ){ - char zBuf[100]; + int Y, s, n; + char zBuf[32]; computeYMD_HMS(&x); - sqlite3_snprintf(sizeof(zBuf), zBuf, "%04d-%02d-%02d %02d:%02d:%02d", - x.Y, x.M, x.D, x.h, x.m, (int)(x.s)); - sqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT); + Y = x.Y; + if( Y<0 ) Y = -Y; + zBuf[1] = '0' + (Y/1000)%10; + zBuf[2] = '0' + (Y/100)%10; + zBuf[3] = '0' + (Y/10)%10; + zBuf[4] = '0' + (Y)%10; + zBuf[5] = '-'; + zBuf[6] = '0' + (x.M/10)%10; + zBuf[7] = '0' + (x.M)%10; + zBuf[8] = '-'; + zBuf[9] = '0' + (x.D/10)%10; + zBuf[10] = '0' + (x.D)%10; + zBuf[11] = ' '; + zBuf[12] = '0' + (x.h/10)%10; + zBuf[13] = '0' + (x.h)%10; + zBuf[14] = ':'; + zBuf[15] = '0' + (x.m/10)%10; + zBuf[16] = '0' + (x.m)%10; + zBuf[17] = ':'; + if( x.useSubsec ){ + s = (int)(1000.0*x.s + 0.5); + zBuf[18] = '0' + (s/10000)%10; + zBuf[19] = '0' + (s/1000)%10; + zBuf[20] = '.'; + zBuf[21] = '0' + (s/100)%10; + zBuf[22] = '0' + (s/10)%10; + zBuf[23] = '0' + (s)%10; + zBuf[24] = 0; + n = 24; + }else{ + s = (int)x.s; + zBuf[18] = '0' + (s/10)%10; + zBuf[19] = '0' + (s)%10; + zBuf[20] = 0; + n = 20; + } + if( x.Y<0 ){ + zBuf[0] = '-'; + sqlite3_result_text(context, zBuf, n, SQLITE_TRANSIENT); + }else{ + sqlite3_result_text(context, &zBuf[1], n-1, SQLITE_TRANSIENT); + } } } @@ -21874,10 +26720,33 @@ static void timeFunc( ){ DateTime x; if( isDate(context, argc, argv, &x)==0 ){ - char zBuf[100]; + int s, n; + char zBuf[16]; computeHMS(&x); - sqlite3_snprintf(sizeof(zBuf), zBuf, "%02d:%02d:%02d", x.h, x.m, (int)x.s); - sqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT); + zBuf[0] = '0' + (x.h/10)%10; + zBuf[1] = '0' + (x.h)%10; + zBuf[2] = ':'; + zBuf[3] = '0' + (x.m/10)%10; + zBuf[4] = '0' + (x.m)%10; + zBuf[5] = ':'; + if( x.useSubsec ){ + s = (int)(1000.0*x.s + 0.5); + zBuf[6] = '0' + (s/10000)%10; + zBuf[7] = '0' + (s/1000)%10; + zBuf[8] = '.'; + zBuf[9] = '0' + (s/100)%10; + zBuf[10] = '0' + (s/10)%10; + zBuf[11] = '0' + (s)%10; + zBuf[12] = 0; + n = 12; + }else{ + s = (int)x.s; + zBuf[6] = '0' + (s/10)%10; + zBuf[7] = '0' + (s)%10; + zBuf[8] = 0; + n = 8; + } + sqlite3_result_text(context, zBuf, n, SQLITE_TRANSIENT); } } @@ -21893,29 +26762,108 @@ static void dateFunc( ){ DateTime x; if( isDate(context, argc, argv, &x)==0 ){ - char zBuf[100]; + int Y; + char zBuf[16]; computeYMD(&x); - sqlite3_snprintf(sizeof(zBuf), zBuf, "%04d-%02d-%02d", x.Y, x.M, x.D); - sqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT); + Y = x.Y; + if( Y<0 ) Y = -Y; + zBuf[1] = '0' + (Y/1000)%10; + zBuf[2] = '0' + (Y/100)%10; + zBuf[3] = '0' + (Y/10)%10; + zBuf[4] = '0' + (Y)%10; + zBuf[5] = '-'; + zBuf[6] = '0' + (x.M/10)%10; + zBuf[7] = '0' + (x.M)%10; + zBuf[8] = '-'; + zBuf[9] = '0' + (x.D/10)%10; + zBuf[10] = '0' + (x.D)%10; + zBuf[11] = 0; + if( x.Y<0 ){ + zBuf[0] = '-'; + sqlite3_result_text(context, zBuf, 11, SQLITE_TRANSIENT); + }else{ + sqlite3_result_text(context, &zBuf[1], 10, SQLITE_TRANSIENT); + } } } +/* +** Compute the number of days after the most recent January 1. +** +** In other words, compute the zero-based day number for the +** current year: +** +** Jan01 = 0, Jan02 = 1, ..., Jan31 = 30, Feb01 = 31, ... +** Dec31 = 364 or 365. +*/ +static int daysAfterJan01(DateTime *pDate){ + DateTime jan01 = *pDate; + assert( jan01.validYMD ); + assert( jan01.validHMS ); + assert( pDate->validJD ); + jan01.validJD = 0; + jan01.M = 1; + jan01.D = 1; + computeJD(&jan01); + return (int)((pDate->iJD-jan01.iJD+43200000)/86400000); +} + +/* +** Return the number of days after the most recent Monday. +** +** In other words, return the day of the week according +** to this code: +** +** 0=Monday, 1=Tuesday, 2=Wednesday, ..., 6=Sunday. +*/ +static int daysAfterMonday(DateTime *pDate){ + assert( pDate->validJD ); + return (int)((pDate->iJD+43200000)/86400000) % 7; +} + +/* +** Return the number of days after the most recent Sunday. +** +** In other words, return the day of the week according +** to this code: +** +** 0=Sunday, 1=Monday, 2=Tuesday, ..., 6=Saturday +*/ +static int daysAfterSunday(DateTime *pDate){ + assert( pDate->validJD ); + return (int)((pDate->iJD+129600000)/86400000) % 7; +} + /* ** strftime( FORMAT, TIMESTRING, MOD, MOD, ...) ** ** Return a string described by FORMAT. Conversions as follows: ** -** %d day of month +** %d day of month 01-31 +** %e day of month 1-31 ** %f ** fractional seconds SS.SSS +** %F ISO date. YYYY-MM-DD +** %G ISO year corresponding to %V 0000-9999. +** %g 2-digit ISO year corresponding to %V 00-99 ** %H hour 00-24 -** %j day of year 000-366 +** %k hour 0-24 (leading zero converted to space) +** %I hour 01-12 +** %j day of year 001-366 ** %J ** julian day number +** %l hour 1-12 (leading zero converted to space) ** %m month 01-12 ** %M minute 00-59 +** %p "AM" or "PM" +** %P "am" or "pm" +** %R time as HH:MM ** %s seconds since 1970-01-01 ** %S seconds 00-59 -** %w day of week 0-6 sunday==0 -** %W week of year 00-53 +** %T time as HH:MM:SS +** %u day of week 1-7 Monday==1, Sunday==7 +** %w day of week 0-6 Sunday==0, Monday==1 +** %U week of year 00-53 (First Sunday is start of week 01) +** %V week of year 01-53 (First week containing Thursday is week 01) +** %W week of year 00-53 (First Monday is start of week 01) ** %Y year 0000-9999 ** %% % */ @@ -21925,131 +26873,161 @@ static void strftimeFunc( sqlite3_value **argv ){ DateTime x; - u64 n; size_t i,j; - char *z; sqlite3 *db; const char *zFmt; - char zBuf[100]; + sqlite3_str sRes; + + if( argc==0 ) return; zFmt = (const char*)sqlite3_value_text(argv[0]); if( zFmt==0 || isDate(context, argc-1, argv+1, &x) ) return; db = sqlite3_context_db_handle(context); - for(i=0, n=1; zFmt[i]; i++, n++){ - if( zFmt[i]=='%' ){ - switch( zFmt[i+1] ){ - case 'd': - case 'H': - case 'm': - case 'M': - case 'S': - case 'W': - n++; - /* fall thru */ - case 'w': - case '%': - break; - case 'f': - n += 8; - break; - case 'j': - n += 3; - break; - case 'Y': - n += 8; - break; - case 's': - case 'J': - n += 50; - break; - default: - return; /* ERROR. return a NULL */ - } - i++; - } - } - testcase( n==sizeof(zBuf)-1 ); - testcase( n==sizeof(zBuf) ); - testcase( n==(u64)db->aLimit[SQLITE_LIMIT_LENGTH]+1 ); - testcase( n==(u64)db->aLimit[SQLITE_LIMIT_LENGTH] ); - if( n(u64)db->aLimit[SQLITE_LIMIT_LENGTH] ){ - sqlite3_result_error_toobig(context); - return; - }else{ - z = sqlite3DbMallocRawNN(db, (int)n); - if( z==0 ){ - sqlite3_result_error_nomem(context); - return; - } - } + sqlite3StrAccumInit(&sRes, 0, 0, 0, db->aLimit[SQLITE_LIMIT_LENGTH]); + computeJD(&x); computeYMD_HMS(&x); for(i=j=0; zFmt[i]; i++){ - if( zFmt[i]!='%' ){ - z[j++] = zFmt[i]; - }else{ - i++; - switch( zFmt[i] ){ - case 'd': sqlite3_snprintf(3, &z[j],"%02d",x.D); j+=2; break; - case 'f': { - double s = x.s; - if( s>59.999 ) s = 59.999; - sqlite3_snprintf(7, &z[j],"%06.3f", s); - j += sqlite3Strlen30(&z[j]); - break; - } - case 'H': sqlite3_snprintf(3, &z[j],"%02d",x.h); j+=2; break; - case 'W': /* Fall thru */ - case 'j': { - int nDay; /* Number of days since 1st day of year */ - DateTime y = x; - y.validJD = 0; - y.M = 1; - y.D = 1; - computeJD(&y); - nDay = (int)((x.iJD-y.iJD+43200000)/86400000); - if( zFmt[i]=='W' ){ - int wd; /* 0=Monday, 1=Tuesday, ... 6=Sunday */ - wd = (int)(((x.iJD+43200000)/86400000)%7); - sqlite3_snprintf(3, &z[j],"%02d",(nDay+7-wd)/7); - j += 2; - }else{ - sqlite3_snprintf(4, &z[j],"%03d",nDay+1); - j += 3; - } - break; - } - case 'J': { - sqlite3_snprintf(20, &z[j],"%.16g",x.iJD/86400000.0); - j+=sqlite3Strlen30(&z[j]); - break; - } - case 'm': sqlite3_snprintf(3, &z[j],"%02d",x.M); j+=2; break; - case 'M': sqlite3_snprintf(3, &z[j],"%02d",x.m); j+=2; break; - case 's': { - sqlite3_snprintf(30,&z[j],"%lld", - (i64)(x.iJD/1000 - 21086676*(i64)10000)); - j += sqlite3Strlen30(&z[j]); - break; + char cf; + if( zFmt[i]!='%' ) continue; + if( j59.999) ) s = 59.999; + sqlite3_str_appendf(&sRes, "%06.3f", s); + break; + } + case 'F': { + sqlite3_str_appendf(&sRes, "%04d-%02d-%02d", x.Y, x.M, x.D); + break; + } + case 'G': /* Fall thru */ + case 'g': { + DateTime y = x; + assert( y.validJD ); + /* Move y so that it is the Thursday in the same week as x */ + y.iJD += (3 - daysAfterMonday(&x))*86400000; + y.validYMD = 0; + computeYMD(&y); + if( cf=='g' ){ + sqlite3_str_appendf(&sRes, "%02d", y.Y%100); + }else{ + sqlite3_str_appendf(&sRes, "%04d", y.Y); } - case 'S': sqlite3_snprintf(3,&z[j],"%02d",(int)x.s); j+=2; break; - case 'w': { - z[j++] = (char)(((x.iJD+129600000)/86400000) % 7) + '0'; - break; + break; + } + case 'H': + case 'k': { + sqlite3_str_appendf(&sRes, cf=='H' ? "%02d" : "%2d", x.h); + break; + } + case 'I': /* Fall thru */ + case 'l': { + int h = x.h; + if( h>12 ) h -= 12; + if( h==0 ) h = 12; + sqlite3_str_appendf(&sRes, cf=='I' ? "%02d" : "%2d", h); + break; + } + case 'j': { /* Day of year. Jan01==1, Jan02==2, and so forth */ + sqlite3_str_appendf(&sRes,"%03d",daysAfterJan01(&x)+1); + break; + } + case 'J': { /* Julian day number. (Non-standard) */ + sqlite3_str_appendf(&sRes,"%.16g",x.iJD/86400000.0); + break; + } + case 'm': { + sqlite3_str_appendf(&sRes,"%02d",x.M); + break; + } + case 'M': { + sqlite3_str_appendf(&sRes,"%02d",x.m); + break; + } + case 'p': /* Fall thru */ + case 'P': { + if( x.h>=12 ){ + sqlite3_str_append(&sRes, cf=='p' ? "PM" : "pm", 2); + }else{ + sqlite3_str_append(&sRes, cf=='p' ? "AM" : "am", 2); } - case 'Y': { - sqlite3_snprintf(5,&z[j],"%04d",x.Y); j+=sqlite3Strlen30(&z[j]); - break; + break; + } + case 'R': { + sqlite3_str_appendf(&sRes, "%02d:%02d", x.h, x.m); + break; + } + case 's': { + if( x.useSubsec ){ + sqlite3_str_appendf(&sRes,"%.3f", + (x.iJD - 21086676*(i64)10000000)/1000.0); + }else{ + i64 iS = (i64)(x.iJD/1000 - 21086676*(i64)10000); + sqlite3_str_appendf(&sRes,"%lld",iS); } - default: z[j++] = '%'; break; + break; + } + case 'S': { + sqlite3_str_appendf(&sRes,"%02d",(int)x.s); + break; + } + case 'T': { + sqlite3_str_appendf(&sRes,"%02d:%02d:%02d", x.h, x.m, (int)x.s); + break; + } + case 'u': /* Day of week. 1 to 7. Monday==1, Sunday==7 */ + case 'w': { /* Day of week. 0 to 6. Sunday==0, Monday==1 */ + char c = (char)daysAfterSunday(&x) + '0'; + if( c=='0' && cf=='u' ) c = '7'; + sqlite3_str_appendchar(&sRes, 1, c); + break; + } + case 'U': { /* Week num. 00-53. First Sun of the year is week 01 */ + sqlite3_str_appendf(&sRes,"%02d", + (daysAfterJan01(&x)-daysAfterSunday(&x)+7)/7); + break; + } + case 'V': { /* Week num. 01-53. First week with a Thur is week 01 */ + DateTime y = x; + /* Adjust y so that is the Thursday in the same week as x */ + assert( y.validJD ); + y.iJD += (3 - daysAfterMonday(&x))*86400000; + y.validYMD = 0; + computeYMD(&y); + sqlite3_str_appendf(&sRes,"%02d", daysAfterJan01(&y)/7+1); + break; + } + case 'W': { /* Week num. 00-53. First Mon of the year is week 01 */ + sqlite3_str_appendf(&sRes,"%02d", + (daysAfterJan01(&x)-daysAfterMonday(&x)+7)/7); + break; + } + case 'Y': { + sqlite3_str_appendf(&sRes,"%04d",x.Y); + break; + } + case '%': { + sqlite3_str_appendchar(&sRes, 1, '%'); + break; + } + default: { + sqlite3_str_reset(&sRes); + return; } } } - z[j] = 0; - sqlite3_result_text(context, z, -1, - z==zBuf ? SQLITE_TRANSIENT : SQLITE_DYNAMIC); + if( j=d2.iJD ){ + sign = '+'; + Y = d1.Y - d2.Y; + if( Y ){ + d2.Y = d1.Y; + d2.validJD = 0; + computeJD(&d2); + } + M = d1.M - d2.M; + if( M<0 ){ + Y--; + M += 12; + } + if( M!=0 ){ + d2.M = d1.M; + d2.validJD = 0; + computeJD(&d2); + } + while( d1.iJDd2.iJD ){ + M--; + if( M<0 ){ + M = 11; + Y--; + } + d2.M++; + if( d2.M>12 ){ + d2.M = 1; + d2.Y++; + } + d2.validJD = 0; + computeJD(&d2); + } + d1.iJD = d2.iJD - d1.iJD; + d1.iJD += (u64)1486995408 * (u64)100000; + } + clearYMD_HMS_TZ(&d1); + computeYMD_HMS(&d1); + sqlite3StrAccumInit(&sRes, 0, 0, 0, 100); + sqlite3_str_appendf(&sRes, "%c%04d-%02d-%02d %02d:%02d:%06.3f", + sign, Y, M, d1.D-1, d1.h, d1.m, d1.s); + sqlite3ResultStrAccum(context, &sRes); +} + + /* ** current_timestamp() ** @@ -22128,10 +27215,10 @@ static void currentTimeFunc( #if HAVE_GMTIME_R pTm = gmtime_r(&t, &sNow); #else - sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER)); + sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MAIN)); pTm = gmtime(&t); if( pTm ) memcpy(&sNow, pTm, sizeof(sNow)); - sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER)); + sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MAIN)); #endif if( pTm ){ strftime(zBuf, 20, zFormat, &sNow); @@ -22140,6 +27227,36 @@ static void currentTimeFunc( } #endif +#if !defined(SQLITE_OMIT_DATETIME_FUNCS) && defined(SQLITE_DEBUG) +/* +** datedebug(...) +** +** This routine returns JSON that describes the internal DateTime object. +** Used for debugging and testing only. Subject to change. +*/ +static void datedebugFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + DateTime x; + if( isDate(context, argc, argv, &x)==0 ){ + char *zJson; + zJson = sqlite3_mprintf( + "{iJD:%lld,Y:%d,M:%d,D:%d,h:%d,m:%d,tz:%d," + "s:%.3f,validJD:%d,validYMD:%d,validHMS:%d," + "nFloor:%d,rawS:%d,isError:%d,useSubsec:%d," + "isUtc:%d,isLocal:%d}", + x.iJD, x.Y, x.M, x.D, x.h, x.m, x.tz, + x.s, x.validJD, x.validYMD, x.validHMS, + x.nFloor, x.rawS, x.isError, x.useSubsec, + x.isUtc, x.isLocal); + sqlite3_result_text(context, zJson, -1, sqlite3_free); + } +} +#endif /* !SQLITE_OMIT_DATETIME_FUNCS && SQLITE_DEBUG */ + + /* ** This function registered all of the above C functions as SQL ** functions. This should be the only routine in this file with @@ -22149,10 +27266,15 @@ SQLITE_PRIVATE void sqlite3RegisterDateTimeFunctions(void){ static FuncDef aDateTimeFuncs[] = { #ifndef SQLITE_OMIT_DATETIME_FUNCS PURE_DATE(julianday, -1, 0, 0, juliandayFunc ), + PURE_DATE(unixepoch, -1, 0, 0, unixepochFunc ), PURE_DATE(date, -1, 0, 0, dateFunc ), PURE_DATE(time, -1, 0, 0, timeFunc ), PURE_DATE(datetime, -1, 0, 0, datetimeFunc ), PURE_DATE(strftime, -1, 0, 0, strftimeFunc ), + PURE_DATE(timediff, 2, 0, 0, timediffFunc ), +#ifdef SQLITE_DEBUG + PURE_DATE(datedebug, -1, 0, 0, datedebugFunc ), +#endif DFUNCTION(current_time, 0, 0, 0, ctimeFunc ), DFUNCTION(current_timestamp, 0, 0, 0, ctimestampFunc), DFUNCTION(current_date, 0, 0, 0, cdateFunc ), @@ -22275,9 +27397,11 @@ SQLITE_PRIVATE int sqlite3OsFileSize(sqlite3_file *id, i64 *pSize){ } SQLITE_PRIVATE int sqlite3OsLock(sqlite3_file *id, int lockType){ DO_OS_MALLOC_TEST(id); + assert( lockType>=SQLITE_LOCK_SHARED && lockType<=SQLITE_LOCK_EXCLUSIVE ); return id->pMethods->xLock(id, lockType); } SQLITE_PRIVATE int sqlite3OsUnlock(sqlite3_file *id, int lockType){ + assert( lockType==SQLITE_LOCK_NONE || lockType==SQLITE_LOCK_SHARED ); return id->pMethods->xUnlock(id, lockType); } SQLITE_PRIVATE int sqlite3OsCheckReservedLock(sqlite3_file *id, int *pResOut){ @@ -22298,17 +27422,24 @@ SQLITE_PRIVATE int sqlite3OsFileControl(sqlite3_file *id, int op, void *pArg){ #ifdef SQLITE_TEST if( op!=SQLITE_FCNTL_COMMIT_PHASETWO && op!=SQLITE_FCNTL_LOCK_TIMEOUT + && op!=SQLITE_FCNTL_CKPT_DONE + && op!=SQLITE_FCNTL_CKPT_START ){ /* Faults are not injected into COMMIT_PHASETWO because, assuming SQLite ** is using a regular VFS, it is called after the corresponding ** transaction has been committed. Injecting a fault at this point - ** confuses the test scripts - the COMMIT comand returns SQLITE_NOMEM + ** confuses the test scripts - the COMMIT command returns SQLITE_NOMEM ** but the transaction is committed anyway. ** ** The core must call OsFileControl() though, not OsFileControlHint(), ** as if a custom VFS (e.g. zipvfs) returns an error here, it probably ** means the commit really has failed and an error should be returned - ** to the user. */ + ** to the user. + ** + ** The CKPT_DONE and CKPT_START file-controls are write-only signals + ** to the cksumvfs. Their return code is meaningless and is ignored + ** by the SQLite core, so there is no point in simulating OOMs for them. + */ DO_OS_MALLOC_TEST(id); } #endif @@ -22323,6 +27454,7 @@ SQLITE_PRIVATE int sqlite3OsSectorSize(sqlite3_file *id){ return (xSectorSize ? xSectorSize(id) : SQLITE_DEFAULT_SECTOR_SIZE); } SQLITE_PRIVATE int sqlite3OsDeviceCharacteristics(sqlite3_file *id){ + if( NEVER(id->pMethods==0) ) return 0; return id->pMethods->xDeviceCharacteristics(id); } #ifndef SQLITE_OMIT_WAL @@ -22384,14 +27516,15 @@ SQLITE_PRIVATE int sqlite3OsOpen( ** down into the VFS layer. Some SQLITE_OPEN_ flags (for example, ** SQLITE_OPEN_FULLMUTEX or SQLITE_OPEN_SHAREDCACHE) are blocked before ** reaching the VFS. */ - rc = pVfs->xOpen(pVfs, zPath, pFile, flags & 0x87f7f, pFlagsOut); + assert( zPath || (flags & SQLITE_OPEN_EXCLUSIVE) ); + rc = pVfs->xOpen(pVfs, zPath, pFile, flags & 0x1087f7f, pFlagsOut); assert( rc==SQLITE_OK || pFile->pMethods==0 ); return rc; } SQLITE_PRIVATE int sqlite3OsDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){ DO_OS_MALLOC_TEST(0); assert( dirSync==0 || dirSync==1 ); - return pVfs->xDelete(pVfs, zPath, dirSync); + return pVfs->xDelete!=0 ? pVfs->xDelete(pVfs, zPath, dirSync) : SQLITE_OK; } SQLITE_PRIVATE int sqlite3OsAccess( sqlite3_vfs *pVfs, @@ -22414,6 +27547,8 @@ SQLITE_PRIVATE int sqlite3OsFullPathname( } #ifndef SQLITE_OMIT_LOAD_EXTENSION SQLITE_PRIVATE void *sqlite3OsDlOpen(sqlite3_vfs *pVfs, const char *zPath){ + assert( zPath!=0 ); + assert( strlen(zPath)<=SQLITE_MAX_PATHLEN ); /* tag-20210611-1 */ return pVfs->xDlOpen(pVfs, zPath); } SQLITE_PRIVATE void sqlite3OsDlError(sqlite3_vfs *pVfs, int nByte, char *zBufOut){ @@ -22427,7 +27562,15 @@ SQLITE_PRIVATE void sqlite3OsDlClose(sqlite3_vfs *pVfs, void *pHandle){ } #endif /* SQLITE_OMIT_LOAD_EXTENSION */ SQLITE_PRIVATE int sqlite3OsRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){ - return pVfs->xRandomness(pVfs, nByte, zBufOut); + if( sqlite3Config.iPrngSeed ){ + memset(zBufOut, 0, nByte); + if( ALWAYS(nByte>(signed)sizeof(unsigned)) ) nByte = sizeof(unsigned int); + memcpy(zBufOut, &sqlite3Config.iPrngSeed, nByte); + return SQLITE_OK; + }else{ + return pVfs->xRandomness(pVfs, nByte, zBufOut); + } + } SQLITE_PRIVATE int sqlite3OsSleep(sqlite3_vfs *pVfs, int nMicro){ return pVfs->xSleep(pVfs, nMicro); @@ -22448,7 +27591,7 @@ SQLITE_PRIVATE int sqlite3OsCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *p }else{ double r; rc = pVfs->xCurrentTime(pVfs, &r); - *pTimeOut = (sqlite3_int64)(r*86400000.0); + *pTimeOut = sqlite3RealToI64(r*86400000.0); } return rc; } @@ -22467,12 +27610,15 @@ SQLITE_PRIVATE int sqlite3OsOpenMalloc( rc = sqlite3OsOpen(pVfs, zFile, pFile, flags, pOutFlags); if( rc!=SQLITE_OK ){ sqlite3_free(pFile); + *ppFile = 0; }else{ *ppFile = pFile; } }else{ + *ppFile = 0; rc = SQLITE_NOMEM_BKPT; } + assert( *ppFile!=0 || rc!=SQLITE_OK ); return rc; } SQLITE_PRIVATE void sqlite3OsCloseFree(sqlite3_file *pFile){ @@ -22514,7 +27660,7 @@ SQLITE_API sqlite3_vfs *sqlite3_vfs_find(const char *zVfs){ if( rc ) return 0; #endif #if SQLITE_THREADSAFE - mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); + mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MAIN); #endif sqlite3_mutex_enter(mutex); for(pVfs = vfsList; pVfs; pVfs=pVfs->pNext){ @@ -22529,7 +27675,7 @@ SQLITE_API sqlite3_vfs *sqlite3_vfs_find(const char *zVfs){ ** Unlink a VFS from the linked list */ static void vfsUnlink(sqlite3_vfs *pVfs){ - assert( sqlite3_mutex_held(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER)) ); + assert( sqlite3_mutex_held(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MAIN)) ); if( pVfs==0 ){ /* No-op */ }else if( vfsList==pVfs ){ @@ -22560,7 +27706,7 @@ SQLITE_API int sqlite3_vfs_register(sqlite3_vfs *pVfs, int makeDflt){ if( pVfs==0 ) return SQLITE_MISUSE_BKPT; #endif - MUTEX_LOGIC( mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); ) + MUTEX_LOGIC( mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MAIN); ) sqlite3_mutex_enter(mutex); vfsUnlink(pVfs); if( makeDflt || vfsList==0 ){ @@ -22584,7 +27730,7 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs *pVfs){ int rc = sqlite3_initialize(); if( rc ) return rc; #endif - MUTEX_LOGIC( mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); ) + MUTEX_LOGIC( mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MAIN); ) sqlite3_mutex_enter(mutex); vfsUnlink(pVfs); sqlite3_mutex_leave(mutex); @@ -22605,17 +27751,17 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs *pVfs){ ** ************************************************************************* ** -** This file contains code to support the concept of "benign" +** This file contains code to support the concept of "benign" ** malloc failures (when the xMalloc() or xRealloc() method of the ** sqlite3_mem_methods structure fails to allocate a block of memory -** and returns 0). +** and returns 0). ** ** Most malloc failures are non-benign. After they occur, SQLite ** abandons the current operation and returns an error code (usually ** SQLITE_NOMEM) to the user. However, sometimes a fault is not necessarily -** fatal. For example, if a malloc fails while resizing a hash table, this -** is completely recoverable simply by not carrying out the resize. The -** hash table will continue to function normally. So a malloc failure +** fatal. For example, if a malloc fails while resizing a hash table, this +** is completely recoverable simply by not carrying out the resize. The +** hash table will continue to function normally. So a malloc failure ** during a hash table resize is a benign fault. */ @@ -22817,7 +27963,7 @@ static malloc_zone_t* _sqliteZone_; #else /* if not __APPLE__ */ /* -** Use standard C library malloc and free on non-Apple systems. +** Use standard C library malloc and free on non-Apple systems. ** Also used by Apple systems if SQLITE_WITHOUT_ZONEMALLOC is defined. */ #define SQLITE_MALLOC(x) malloc(x) @@ -22903,7 +28049,7 @@ static void *sqlite3MemMalloc(int nByte){ ** or sqlite3MemRealloc(). ** ** For this low-level routine, we already know that pPrior!=0 since -** cases where pPrior==0 will have been intecepted and dealt with +** cases where pPrior==0 will have been intercepted and dealt with ** by higher-level routines. */ static void sqlite3MemFree(void *pPrior){ @@ -22991,13 +28137,13 @@ static int sqlite3MemInit(void *NotUsed){ return SQLITE_OK; } len = sizeof(cpuCount); - /* One usually wants to use hw.acctivecpu for MT decisions, but not here */ + /* One usually wants to use hw.activecpu for MT decisions, but not here */ sysctlbyname("hw.ncpu", &cpuCount, &len, NULL, 0); if( cpuCount>1 ){ /* defer MT decisions to system malloc */ _sqliteZone_ = malloc_default_zone(); }else{ - /* only 1 core, use our own zone to contention over global locks, + /* only 1 core, use our own zone to contention over global locks, ** e.g. we have our own dedicated locks */ _sqliteZone_ = malloc_create_zone(4096, 0); malloc_set_zone_name(_sqliteZone_, "Sqlite_Heap"); @@ -23121,7 +28267,7 @@ struct MemBlockHdr { ** when this module is combined with other in the amalgamation. */ static struct { - + /* ** Mutex to control access to the memory allocation subsystem. */ @@ -23132,7 +28278,7 @@ static struct { */ struct MemBlockHdr *pFirst; struct MemBlockHdr *pLast; - + /* ** The number of levels of backtrace to save in new allocations. */ @@ -23145,7 +28291,7 @@ static struct { int nTitle; /* Bytes of zTitle to save. Includes '\0' and padding */ char zTitle[100]; /* The title text */ - /* + /* ** sqlite3MallocDisallow() increments the following counter. ** sqlite3MallocAllow() decrements it. */ @@ -23190,7 +28336,7 @@ static void adjustStats(int iSize, int increment){ ** This routine checks the guards at either end of the allocation and ** if they are incorrect it asserts. */ -static struct MemBlockHdr *sqlite3MemsysGetHeader(void *pAllocation){ +static struct MemBlockHdr *sqlite3MemsysGetHeader(const void *pAllocation){ struct MemBlockHdr *p; int *pInt; u8 *pU8; @@ -23204,7 +28350,7 @@ static struct MemBlockHdr *sqlite3MemsysGetHeader(void *pAllocation){ pU8 = (u8*)pAllocation; assert( pInt[nReserve/sizeof(int)]==(int)REARGUARD ); /* This checks any of the "extra" bytes allocated due - ** to rounding up to an 8 byte boundary to ensure + ** to rounding up to an 8 byte boundary to ensure ** they haven't been overwritten. */ while( nReserve-- > p->iSize ) assert( pU8[nReserve]==0x65 ); @@ -23333,7 +28479,7 @@ static void *sqlite3MemMalloc(int nByte){ p = (void*)pInt; } sqlite3_mutex_leave(mem.mutex); - return p; + return p; } /* @@ -23343,7 +28489,7 @@ static void sqlite3MemFree(void *pPrior){ struct MemBlockHdr *pHdr; void **pBt; char *z; - assert( sqlite3GlobalConfig.bMemstat || sqlite3GlobalConfig.bCoreMutex==0 + assert( sqlite3GlobalConfig.bMemstat || sqlite3GlobalConfig.bCoreMutex==0 || mem.mutex!=0 ); pHdr = sqlite3MemsysGetHeader(pPrior); pBt = (void**)pHdr; @@ -23369,15 +28515,15 @@ static void sqlite3MemFree(void *pPrior){ randomFill(z, sizeof(void*)*pHdr->nBacktraceSlots + sizeof(*pHdr) + (int)pHdr->iSize + sizeof(int) + pHdr->nTitle); free(z); - sqlite3_mutex_leave(mem.mutex); + sqlite3_mutex_leave(mem.mutex); } /* ** Change the size of an existing memory allocation. ** ** For this debugging implementation, we *always* make a copy of the -** allocation into a new place in memory. In this way, if the -** higher level code is using pointer to the old allocation, it is +** allocation into a new place in memory. In this way, if the +** higher level code is using pointer to the old allocation, it is ** much more likely to break and we are much more liking to find ** the error. */ @@ -23420,7 +28566,7 @@ SQLITE_PRIVATE void sqlite3MemSetDefault(void){ ** Set the "type" of an allocation. */ SQLITE_PRIVATE void sqlite3MemdebugSetType(void *p, u8 eType){ - if( p && sqlite3GlobalConfig.m.xMalloc==sqlite3MemMalloc ){ + if( p && sqlite3GlobalConfig.m.xFree==sqlite3MemFree ){ struct MemBlockHdr *pHdr; pHdr = sqlite3MemsysGetHeader(p); assert( pHdr->iForeGuard==FOREGUARD ); @@ -23437,9 +28583,9 @@ SQLITE_PRIVATE void sqlite3MemdebugSetType(void *p, u8 eType){ ** ** assert( sqlite3MemdebugHasType(p, MEMTYPE_HEAP) ); */ -SQLITE_PRIVATE int sqlite3MemdebugHasType(void *p, u8 eType){ +SQLITE_PRIVATE int sqlite3MemdebugHasType(const void *p, u8 eType){ int rc = 1; - if( p && sqlite3GlobalConfig.m.xMalloc==sqlite3MemMalloc ){ + if( p && sqlite3GlobalConfig.m.xFree==sqlite3MemFree ){ struct MemBlockHdr *pHdr; pHdr = sqlite3MemsysGetHeader(p); assert( pHdr->iForeGuard==FOREGUARD ); /* Allocation is valid */ @@ -23459,9 +28605,9 @@ SQLITE_PRIVATE int sqlite3MemdebugHasType(void *p, u8 eType){ ** ** assert( sqlite3MemdebugNoType(p, MEMTYPE_LOOKASIDE) ); */ -SQLITE_PRIVATE int sqlite3MemdebugNoType(void *p, u8 eType){ +SQLITE_PRIVATE int sqlite3MemdebugNoType(const void *p, u8 eType){ int rc = 1; - if( p && sqlite3GlobalConfig.m.xMalloc==sqlite3MemMalloc ){ + if( p && sqlite3GlobalConfig.m.xFree==sqlite3MemFree ){ struct MemBlockHdr *pHdr; pHdr = sqlite3MemsysGetHeader(p); assert( pHdr->iForeGuard==FOREGUARD ); /* Allocation is valid */ @@ -23511,7 +28657,7 @@ SQLITE_PRIVATE void sqlite3MemdebugSync(){ } /* -** Open the file indicated and write a log of all unfreed memory +** Open the file indicated and write a log of all unfreed memory ** allocations into that log. */ SQLITE_PRIVATE void sqlite3MemdebugDump(const char *zFilename){ @@ -23528,7 +28674,7 @@ SQLITE_PRIVATE void sqlite3MemdebugDump(const char *zFilename){ for(pHdr=mem.pFirst; pHdr; pHdr=pHdr->pNext){ char *z = (char*)pHdr; z -= pHdr->nBacktraceSlots*sizeof(void*) + pHdr->nTitle; - fprintf(out, "**** %lld bytes at %p from %s ****\n", + fprintf(out, "**** %lld bytes at %p from %s ****\n", pHdr->iSize, &pHdr[1], pHdr->nTitle ? z : "???"); if( pHdr->nBacktrace ){ fflush(out); @@ -23541,7 +28687,7 @@ SQLITE_PRIVATE void sqlite3MemdebugDump(const char *zFilename){ fprintf(out, "COUNTS:\n"); for(i=0; i=nBlock ); - if( nBlock>=mem3.szMaster-1 ){ - /* Use the entire master */ - void *p = memsys3Checkout(mem3.iMaster, mem3.szMaster); - mem3.iMaster = 0; - mem3.szMaster = 0; - mem3.mnMaster = 0; + assert( mem3.szKeyBlk>=nBlock ); + if( nBlock>=mem3.szKeyBlk-1 ){ + /* Use the entire key chunk */ + void *p = memsys3Checkout(mem3.iKeyBlk, mem3.szKeyBlk); + mem3.iKeyBlk = 0; + mem3.szKeyBlk = 0; + mem3.mnKeyBlk = 0; return p; }else{ - /* Split the master block. Return the tail. */ + /* Split the key block. Return the tail. */ u32 newi, x; - newi = mem3.iMaster + mem3.szMaster - nBlock; - assert( newi > mem3.iMaster+1 ); - mem3.aPool[mem3.iMaster+mem3.szMaster-1].u.hdr.prevSize = nBlock; - mem3.aPool[mem3.iMaster+mem3.szMaster-1].u.hdr.size4x |= 2; + newi = mem3.iKeyBlk + mem3.szKeyBlk - nBlock; + assert( newi > mem3.iKeyBlk+1 ); + mem3.aPool[mem3.iKeyBlk+mem3.szKeyBlk-1].u.hdr.prevSize = nBlock; + mem3.aPool[mem3.iKeyBlk+mem3.szKeyBlk-1].u.hdr.size4x |= 2; mem3.aPool[newi-1].u.hdr.size4x = nBlock*4 + 1; - mem3.szMaster -= nBlock; - mem3.aPool[newi-1].u.hdr.prevSize = mem3.szMaster; - x = mem3.aPool[mem3.iMaster-1].u.hdr.size4x & 2; - mem3.aPool[mem3.iMaster-1].u.hdr.size4x = mem3.szMaster*4 | x; - if( mem3.szMaster < mem3.mnMaster ){ - mem3.mnMaster = mem3.szMaster; + mem3.szKeyBlk -= nBlock; + mem3.aPool[newi-1].u.hdr.prevSize = mem3.szKeyBlk; + x = mem3.aPool[mem3.iKeyBlk-1].u.hdr.size4x & 2; + mem3.aPool[mem3.iKeyBlk-1].u.hdr.size4x = mem3.szKeyBlk*4 | x; + if( mem3.szKeyBlk < mem3.mnKeyBlk ){ + mem3.mnKeyBlk = mem3.szKeyBlk; } return (void*)&mem3.aPool[newi]; } @@ -23871,18 +29017,18 @@ static void *memsys3FromMaster(u32 nBlock){ /* ** *pRoot is the head of a list of free chunks of the same size ** or same size hash. In other words, *pRoot is an entry in either -** mem3.aiSmall[] or mem3.aiHash[]. +** mem3.aiSmall[] or mem3.aiHash[]. ** ** This routine examines all entries on the given list and tries -** to coalesce each entries with adjacent free chunks. +** to coalesce each entries with adjacent free chunks. ** -** If it sees a chunk that is larger than mem3.iMaster, it replaces -** the current mem3.iMaster with the new larger chunk. In order for -** this mem3.iMaster replacement to work, the master chunk must be +** If it sees a chunk that is larger than mem3.iKeyBlk, it replaces +** the current mem3.iKeyBlk with the new larger chunk. In order for +** this mem3.iKeyBlk replacement to work, the key chunk must be ** linked into the hash tables. That is not the normal state of -** affairs, of course. The calling routine must link the master +** affairs, of course. The calling routine must link the key ** chunk before invoking this routine, then must unlink the (possibly -** changed) master chunk once this routine has finished. +** changed) key chunk once this routine has finished. */ static void memsys3Merge(u32 *pRoot){ u32 iNext, prev, size, i, x; @@ -23909,9 +29055,9 @@ static void memsys3Merge(u32 *pRoot){ }else{ size /= 4; } - if( size>mem3.szMaster ){ - mem3.iMaster = i; - mem3.szMaster = size; + if( size>mem3.szKeyBlk ){ + mem3.iKeyBlk = i; + mem3.szKeyBlk = size; } } } @@ -23960,26 +29106,26 @@ static void *memsys3MallocUnsafe(int nByte){ /* STEP 2: ** Try to satisfy the allocation by carving a piece off of the end - ** of the master chunk. This step usually works if step 1 fails. + ** of the key chunk. This step usually works if step 1 fails. */ - if( mem3.szMaster>=nBlock ){ - return memsys3FromMaster(nBlock); + if( mem3.szKeyBlk>=nBlock ){ + return memsys3FromKeyBlk(nBlock); } - /* STEP 3: + /* STEP 3: ** Loop through the entire memory pool. Coalesce adjacent free - ** chunks. Recompute the master chunk as the largest free chunk. + ** chunks. Recompute the key chunk as the largest free chunk. ** Then try again to satisfy the allocation by carving a piece off - ** of the end of the master chunk. This step happens very + ** of the end of the key chunk. This step happens very ** rarely (we hope!) */ for(toFree=nBlock*16; toFree<(mem3.nPool*16); toFree *= 2){ memsys3OutOfMemory(toFree); - if( mem3.iMaster ){ - memsys3Link(mem3.iMaster); - mem3.iMaster = 0; - mem3.szMaster = 0; + if( mem3.iKeyBlk ){ + memsys3Link(mem3.iKeyBlk); + mem3.iKeyBlk = 0; + mem3.szKeyBlk = 0; } for(i=0; i=nBlock ){ - return memsys3FromMaster(nBlock); + if( mem3.szKeyBlk ){ + memsys3Unlink(mem3.iKeyBlk); + if( mem3.szKeyBlk>=nBlock ){ + return memsys3FromKeyBlk(nBlock); } } } @@ -24020,23 +29166,23 @@ static void memsys3FreeUnsafe(void *pOld){ mem3.aPool[i+size-1].u.hdr.size4x &= ~2; memsys3Link(i); - /* Try to expand the master using the newly freed chunk */ - if( mem3.iMaster ){ - while( (mem3.aPool[mem3.iMaster-1].u.hdr.size4x&2)==0 ){ - size = mem3.aPool[mem3.iMaster-1].u.hdr.prevSize; - mem3.iMaster -= size; - mem3.szMaster += size; - memsys3Unlink(mem3.iMaster); - x = mem3.aPool[mem3.iMaster-1].u.hdr.size4x & 2; - mem3.aPool[mem3.iMaster-1].u.hdr.size4x = mem3.szMaster*4 | x; - mem3.aPool[mem3.iMaster+mem3.szMaster-1].u.hdr.prevSize = mem3.szMaster; + /* Try to expand the key using the newly freed chunk */ + if( mem3.iKeyBlk ){ + while( (mem3.aPool[mem3.iKeyBlk-1].u.hdr.size4x&2)==0 ){ + size = mem3.aPool[mem3.iKeyBlk-1].u.hdr.prevSize; + mem3.iKeyBlk -= size; + mem3.szKeyBlk += size; + memsys3Unlink(mem3.iKeyBlk); + x = mem3.aPool[mem3.iKeyBlk-1].u.hdr.size4x & 2; + mem3.aPool[mem3.iKeyBlk-1].u.hdr.size4x = mem3.szKeyBlk*4 | x; + mem3.aPool[mem3.iKeyBlk+mem3.szKeyBlk-1].u.hdr.prevSize = mem3.szKeyBlk; } - x = mem3.aPool[mem3.iMaster-1].u.hdr.size4x & 2; - while( (mem3.aPool[mem3.iMaster+mem3.szMaster-1].u.hdr.size4x&1)==0 ){ - memsys3Unlink(mem3.iMaster+mem3.szMaster); - mem3.szMaster += mem3.aPool[mem3.iMaster+mem3.szMaster-1].u.hdr.size4x/4; - mem3.aPool[mem3.iMaster-1].u.hdr.size4x = mem3.szMaster*4 | x; - mem3.aPool[mem3.iMaster+mem3.szMaster-1].u.hdr.prevSize = mem3.szMaster; + x = mem3.aPool[mem3.iKeyBlk-1].u.hdr.size4x & 2; + while( (mem3.aPool[mem3.iKeyBlk+mem3.szKeyBlk-1].u.hdr.size4x&1)==0 ){ + memsys3Unlink(mem3.iKeyBlk+mem3.szKeyBlk); + mem3.szKeyBlk += mem3.aPool[mem3.iKeyBlk+mem3.szKeyBlk-1].u.hdr.size4x/4; + mem3.aPool[mem3.iKeyBlk-1].u.hdr.size4x = mem3.szKeyBlk*4 | x; + mem3.aPool[mem3.iKeyBlk+mem3.szKeyBlk-1].u.hdr.prevSize = mem3.szKeyBlk; } } } @@ -24074,7 +29220,7 @@ static void *memsys3Malloc(int nBytes){ memsys3Enter(); p = memsys3MallocUnsafe(nBytes); memsys3Leave(); - return (void*)p; + return (void*)p; } /* @@ -24132,11 +29278,11 @@ static int memsys3Init(void *NotUsed){ mem3.aPool = (Mem3Block *)sqlite3GlobalConfig.pHeap; mem3.nPool = (sqlite3GlobalConfig.nHeap / sizeof(Mem3Block)) - 2; - /* Initialize the master block. */ - mem3.szMaster = mem3.nPool; - mem3.mnMaster = mem3.szMaster; - mem3.iMaster = 1; - mem3.aPool[0].u.hdr.size4x = (mem3.szMaster<<2) + 2; + /* Initialize the key block. */ + mem3.szKeyBlk = mem3.nPool; + mem3.mnKeyBlk = mem3.szKeyBlk; + mem3.iKeyBlk = 1; + mem3.aPool[0].u.hdr.size4x = (mem3.szKeyBlk<<2) + 2; mem3.aPool[mem3.nPool].u.hdr.prevSize = mem3.nPool; mem3.aPool[mem3.nPool].u.hdr.size4x = 1; @@ -24155,7 +29301,7 @@ static void memsys3Shutdown(void *NotUsed){ /* -** Open the file indicated and write a log of all unfreed memory +** Open the file indicated and write a log of all unfreed memory ** allocations into that log. */ SQLITE_PRIVATE void sqlite3Memsys3Dump(const char *zFilename){ @@ -24196,7 +29342,7 @@ SQLITE_PRIVATE void sqlite3Memsys3Dump(const char *zFilename){ fprintf(out, "%p %6d bytes checked out\n", &mem3.aPool[i], (size/4)*8-8); }else{ fprintf(out, "%p %6d bytes free%s\n", &mem3.aPool[i], (size/4)*8-8, - i==mem3.iMaster ? " **master**" : ""); + i==mem3.iKeyBlk ? " **key**" : ""); } } for(i=0; i= M*(1 + log2(n)/2) - n + 1 @@ -24313,7 +29459,7 @@ SQLITE_PRIVATE const sqlite3_mem_methods *sqlite3MemGetMemsys3(void){ /* #include "sqliteInt.h" */ /* -** This version of the memory allocator is used only when +** This version of the memory allocator is used only when ** SQLITE_ENABLE_MEMSYS5 is defined. */ #ifdef SQLITE_ENABLE_MEMSYS5 @@ -24358,7 +29504,7 @@ static SQLITE_WSD struct Mem5Global { int szAtom; /* Smallest possible allocation in bytes */ int nBlock; /* Number of szAtom sized blocks in zPool */ u8 *zPool; /* Memory available to be allocated */ - + /* ** Mutex to control access to the memory allocation subsystem. */ @@ -24377,7 +29523,7 @@ static SQLITE_WSD struct Mem5Global { u32 maxCount; /* Maximum instantaneous currentCount */ u32 maxRequest; /* Largest allocation (exclusive of internal frag) */ #endif - + /* ** Lists of free blocks. aiFreelist[0] is a list of free blocks of ** size mem5.szAtom. aiFreelist[1] holds blocks of size szAtom*2. @@ -24553,7 +29699,7 @@ static void memsys5FreeUnsafe(void *pOld){ u32 size, iLogsize; int iBlock; - /* Set iBlock to the index of the block pointed to by pOld in + /* Set iBlock to the index of the block pointed to by pOld in ** the array of mem5.szAtom byte blocks pointed to by mem5.zPool. */ iBlock = (int)(((u8 *)pOld-mem5.zPool)/mem5.szAtom); @@ -24622,7 +29768,7 @@ static void *memsys5Malloc(int nBytes){ p = memsys5MallocUnsafe(nBytes); memsys5Leave(); } - return (void*)p; + return (void*)p; } /* @@ -24635,14 +29781,14 @@ static void memsys5Free(void *pPrior){ assert( pPrior!=0 ); memsys5Enter(); memsys5FreeUnsafe(pPrior); - memsys5Leave(); + memsys5Leave(); } /* ** Change the size of an existing memory allocation. ** ** The outer layer memory allocator prevents this routine from -** being called with pPrior==0. +** being called with pPrior==0. ** ** nBytes is always a value obtained from a prior call to ** memsys5Round(). Hence nBytes is always a non-negative power @@ -24682,8 +29828,17 @@ static void *memsys5Realloc(void *pPrior, int nBytes){ */ static int memsys5Roundup(int n){ int iFullSz; - if( n > 0x40000000 ) return 0; - for(iFullSz=mem5.szAtom; iFullSz0x10000000 ){ + if( n>0x40000000 ) return 0; + if( n>0x20000000 ) return 0x40000000; + return 0x20000000; + } + for(iFullSz=mem5.szAtom*8; iFullSz=(i64)n ) return iFullSz/2; return iFullSz; } @@ -24775,7 +29930,7 @@ static void memsys5Shutdown(void *NotUsed){ #ifdef SQLITE_TEST /* -** Open the file indicated and write a log of all unfreed memory +** Open the file indicated and write a log of all unfreed memory ** allocations into that log. */ SQLITE_PRIVATE void sqlite3Memsys5Dump(const char *zFilename){ @@ -24817,7 +29972,7 @@ SQLITE_PRIVATE void sqlite3Memsys5Dump(const char *zFilename){ #endif /* -** This routine is the only routine in this file with external +** This routine is the only routine in this file with external ** linkage. It returns a pointer to a static sqlite3_mem_methods ** struct populated with the memsys5 methods. */ @@ -24868,23 +30023,28 @@ static SQLITE_WSD int mutexIsInit = 0; #ifndef SQLITE_MUTEX_OMIT -#ifdef SQLITE_ENABLE_MULTITHREADED_CHECKS +#ifdef SQLITE_THREAD_MISUSE_WARNINGS /* -** This block (enclosed by SQLITE_ENABLE_MULTITHREADED_CHECKS) contains +** This block (enclosed by SQLITE_THREAD_MISUSE_WARNINGS) contains ** the implementation of a wrapper around the system default mutex -** implementation (sqlite3DefaultMutex()). +** implementation (sqlite3DefaultMutex()). ** ** Most calls are passed directly through to the underlying default ** mutex implementation. Except, if a mutex is configured by calling ** sqlite3MutexWarnOnContention() on it, then if contention is ever -** encountered within xMutexEnter() a warning is emitted via sqlite3_log(). +** encountered within xMutexEnter() then a warning is emitted via +** sqlite3_log(). Furthermore, if SQLITE_THREAD_MISUSE_ABORT is +** defined then abort() is called after the sqlite3_log() warning. ** -** This type of mutex is used as the database handle mutex when testing -** apps that usually use SQLITE_CONFIG_MULTITHREAD mode. +** This type of mutex is used on the database handle mutex when testing +** apps that usually use SQLITE_CONFIG_MULTITHREAD mode. A failure +** indicates that the app ought to be using SQLITE_OPEN_FULLMUTEX or +** similar because it is trying to use the same database handle from +** two different connections at the same time. */ -/* -** Type for all mutexes used when SQLITE_ENABLE_MULTITHREADED_CHECKS +/* +** Type for all mutexes used when SQLITE_THREAD_MISUSE_WARNINGS ** is defined. Variable CheckMutex.mutex is a pointer to the real mutex ** allocated by the system mutex implementation. Variable iType is usually set ** to the type of mutex requested - SQLITE_MUTEX_RECURSIVE, SQLITE_MUTEX_FAST @@ -24900,9 +30060,9 @@ struct CheckMutex { #define SQLITE_MUTEX_WARNONCONTENTION (-1) -/* +/* ** Pointer to real mutex methods object used by the CheckMutex -** implementation. Set by checkMutexInit(). +** implementation. Set by checkMutexInit(). */ static SQLITE_WSD const sqlite3_mutex_methods *pGlobalMutexMethods; @@ -24918,13 +30078,14 @@ static int checkMutexNotheld(sqlite3_mutex *p){ /* ** Initialize and deinitialize the mutex subsystem. */ -static int checkMutexInit(void){ +static int checkMutexInit(void){ pGlobalMutexMethods = sqlite3DefaultMutex(); - return SQLITE_OK; + return pGlobalMutexMethods->xMutexInit(); } -static int checkMutexEnd(void){ +static int checkMutexEnd(void){ + int rc = pGlobalMutexMethods->xMutexEnd(); pGlobalMutexMethods = 0; - return SQLITE_OK; + return rc; } /* @@ -24974,7 +30135,7 @@ static void checkMutexFree(sqlite3_mutex *p){ assert( SQLITE_MUTEX_FAST<2 ); assert( SQLITE_MUTEX_WARNONCONTENTION<2 ); -#if SQLITE_ENABLE_API_ARMOR +#ifdef SQLITE_ENABLE_API_ARMOR if( ((CheckMutex*)p)->iType<2 ) #endif { @@ -24998,9 +30159,12 @@ static void checkMutexEnter(sqlite3_mutex *p){ if( SQLITE_OK==pGlobalMutexMethods->xMutexTry(pCheck->mutex) ){ return; } - sqlite3_log(SQLITE_MISUSE, + sqlite3_log(SQLITE_MISUSE, "illegal multi-threaded access to database connection" ); +#if SQLITE_THREAD_MISUSE_ABORT + abort(); +#endif } pGlobalMutexMethods->xMutexEnter(pCheck->mutex); } @@ -25052,16 +30216,16 @@ SQLITE_PRIVATE void sqlite3MutexWarnOnContention(sqlite3_mutex *p){ pCheck->iType = SQLITE_MUTEX_WARNONCONTENTION; } } -#endif /* ifdef SQLITE_ENABLE_MULTITHREADED_CHECKS */ +#endif /* ifdef SQLITE_THREAD_MISUSE_WARNINGS */ /* ** Initialize the mutex system. */ -SQLITE_PRIVATE int sqlite3MutexInit(void){ +SQLITE_PRIVATE int sqlite3MutexInit(void){ int rc = SQLITE_OK; if( !sqlite3GlobalConfig.mutex.xMutexAlloc ){ /* If the xMutexAlloc method has not been set, then the user did not - ** install a mutex implementation via sqlite3_config() prior to + ** install a mutex implementation via sqlite3_config() prior to ** sqlite3_initialize() being called. This block copies pointers to ** the default implementation into the sqlite3GlobalConfig structure. */ @@ -25069,7 +30233,7 @@ SQLITE_PRIVATE int sqlite3MutexInit(void){ sqlite3_mutex_methods *pTo = &sqlite3GlobalConfig.mutex; if( sqlite3GlobalConfig.bCoreMutex ){ -#ifdef SQLITE_ENABLE_MULTITHREADED_CHECKS +#ifdef SQLITE_THREAD_MISUSE_WARNINGS pFrom = multiThreadedCheckMutex(); #else pFrom = sqlite3DefaultMutex(); @@ -25095,6 +30259,7 @@ SQLITE_PRIVATE int sqlite3MutexInit(void){ GLOBAL(int, mutexIsInit) = 1; #endif + sqlite3MemoryBarrier(); return rc; } @@ -25172,7 +30337,7 @@ SQLITE_API int sqlite3_mutex_try(sqlite3_mutex *p){ /* ** The sqlite3_mutex_leave() routine exits a mutex that was previously -** entered by the same thread. The behavior is undefined if the mutex +** entered by the same thread. The behavior is undefined if the mutex ** is not currently entered. If a NULL pointer is passed as an argument ** this function is a no-op. */ @@ -25187,16 +30352,29 @@ SQLITE_API void sqlite3_mutex_leave(sqlite3_mutex *p){ /* ** The sqlite3_mutex_held() and sqlite3_mutex_notheld() routine are ** intended for use inside assert() statements. +** +** Because these routines raise false-positive alerts in TSAN, disable +** them (make them always return 1) when compiling with TSAN. */ SQLITE_API int sqlite3_mutex_held(sqlite3_mutex *p){ +# if defined(__has_feature) +# if __has_feature(thread_sanitizer) + p = 0; +# endif +# endif assert( p==0 || sqlite3GlobalConfig.mutex.xMutexHeld ); return p==0 || sqlite3GlobalConfig.mutex.xMutexHeld(p); } SQLITE_API int sqlite3_mutex_notheld(sqlite3_mutex *p){ +# if defined(__has_feature) +# if __has_feature(thread_sanitizer) + p = 0; +# endif +# endif assert( p==0 || sqlite3GlobalConfig.mutex.xMutexNotheld ); return p==0 || sqlite3GlobalConfig.mutex.xMutexNotheld(p); } -#endif +#endif /* NDEBUG */ #endif /* !defined(SQLITE_MUTEX_OMIT) */ @@ -25241,9 +30419,9 @@ SQLITE_API int sqlite3_mutex_notheld(sqlite3_mutex *p){ */ static int noopMutexInit(void){ return SQLITE_OK; } static int noopMutexEnd(void){ return SQLITE_OK; } -static sqlite3_mutex *noopMutexAlloc(int id){ +static sqlite3_mutex *noopMutexAlloc(int id){ UNUSED_PARAMETER(id); - return (sqlite3_mutex*)8; + return (sqlite3_mutex*)8; } static void noopMutexFree(sqlite3_mutex *p){ UNUSED_PARAMETER(p); return; } static void noopMutexEnter(sqlite3_mutex *p){ UNUSED_PARAMETER(p); return; } @@ -25308,7 +30486,7 @@ static int debugMutexEnd(void){ return SQLITE_OK; } /* ** The sqlite3_mutex_alloc() routine allocates a new ** mutex and returns a pointer to it. If it returns NULL -** that means that a mutex could not be allocated. +** that means that a mutex could not be allocated. */ static sqlite3_mutex *debugMutexAlloc(int id){ static sqlite3_debug_mutex aStatic[SQLITE_MUTEX_STATIC_VFS3 - 1]; @@ -25448,7 +30626,7 @@ SQLITE_PRIVATE sqlite3_mutex_methods const *sqlite3DefaultMutex(void){ /* ** The sqlite3_mutex.id, sqlite3_mutex.nRef, and sqlite3_mutex.owner fields -** are necessary under two condidtions: (1) Debug builds and (2) using +** are necessary under two conditions: (1) Debug builds and (2) using ** home-grown mutexes. Encapsulate these conditions into a single #define. */ #if defined(SQLITE_DEBUG) || defined(SQLITE_HOMEGROWN_RECURSIVE_MUTEX) @@ -25486,7 +30664,7 @@ struct sqlite3_mutex { ** there might be race conditions that can cause these routines to ** deliver incorrect results. In particular, if pthread_equal() is ** not an atomic operation, then these routines might delivery -** incorrect results. On most platforms, pthread_equal() is a +** incorrect results. On most platforms, pthread_equal() is a ** comparison of two integers and is therefore atomic. But we are ** told that HPUX is not such a platform. If so, then these routines ** will not always work correctly on HPUX. @@ -25534,7 +30712,7 @@ static int pthreadMutexEnd(void){ return SQLITE_OK; } **
          **
        • SQLITE_MUTEX_FAST **
        • SQLITE_MUTEX_RECURSIVE -**
        • SQLITE_MUTEX_STATIC_MASTER +**
        • SQLITE_MUTEX_STATIC_MAIN **
        • SQLITE_MUTEX_STATIC_MEM **
        • SQLITE_MUTEX_STATIC_OPEN **
        • SQLITE_MUTEX_STATIC_PRNG @@ -25568,7 +30746,7 @@ static int pthreadMutexEnd(void){ return SQLITE_OK; } ** ** Note that if one of the dynamic mutex parameters (SQLITE_MUTEX_FAST ** or SQLITE_MUTEX_RECURSIVE) is used then sqlite3_mutex_alloc() -** returns a different mutex on every call. But for the static +** returns a different mutex on every call. But for the static ** mutex types, the same mutex is returned on every call that has ** the same type number. */ @@ -25645,7 +30823,7 @@ static sqlite3_mutex *pthreadMutexAlloc(int iType){ */ static void pthreadMutexFree(sqlite3_mutex *p){ assert( p->nRef==0 ); -#if SQLITE_ENABLE_API_ARMOR +#ifdef SQLITE_ENABLE_API_ARMOR if( p->id==SQLITE_MUTEX_FAST || p->id==SQLITE_MUTEX_RECURSIVE ) #endif { @@ -25679,7 +30857,7 @@ static void pthreadMutexEnter(sqlite3_mutex *p){ ** is atomic - that it cannot be deceived into thinking self ** and p->owner are equal if p->owner changes between two values ** that are not equal to self while the comparison is taking place. - ** This implementation also assumes a coherent cache - that + ** This implementation also assumes a coherent cache - that ** separate processes cannot read different values from the same ** address at the same time. If either of these two conditions ** are not met, then the mutexes will fail and problems will result. @@ -25722,7 +30900,7 @@ static int pthreadMutexTry(sqlite3_mutex *p){ ** is atomic - that it cannot be deceived into thinking self ** and p->owner are equal if p->owner changes between two values ** that are not equal to self while the comparison is taking place. - ** This implementation also assumes a coherent cache - that + ** This implementation also assumes a coherent cache - that ** separate processes cannot read different values from the same ** address at the same time. If either of these two conditions ** are not met, then the mutexes will fail and problems will result. @@ -25836,205 +31014,7 @@ SQLITE_PRIVATE sqlite3_mutex_methods const *sqlite3DefaultMutex(void){ /* ** Include code that is common to all os_*.c files */ -/************** Include os_common.h in the middle of mutex_w32.c *************/ -/************** Begin file os_common.h ***************************************/ -/* -** 2004 May 22 -** -** The author disclaims copyright to this source code. In place of -** a legal notice, here is a blessing: -** -** May you do good and not evil. -** May you find forgiveness for yourself and forgive others. -** May you share freely, never taking more than you give. -** -****************************************************************************** -** -** This file contains macros and a little bit of code that is common to -** all of the platform-specific files (os_*.c) and is #included into those -** files. -** -** This file should be #included by the os_*.c files only. It is not a -** general purpose header file. -*/ -#ifndef _OS_COMMON_H_ -#define _OS_COMMON_H_ - -/* -** At least two bugs have slipped in because we changed the MEMORY_DEBUG -** macro to SQLITE_DEBUG and some older makefiles have not yet made the -** switch. The following code should catch this problem at compile-time. -*/ -#ifdef MEMORY_DEBUG -# error "The MEMORY_DEBUG macro is obsolete. Use SQLITE_DEBUG instead." -#endif - -/* -** Macros for performance tracing. Normally turned off. Only works -** on i486 hardware. -*/ -#ifdef SQLITE_PERFORMANCE_TRACE - -/* -** hwtime.h contains inline assembler code for implementing -** high-performance timing routines. -*/ -/************** Include hwtime.h in the middle of os_common.h ****************/ -/************** Begin file hwtime.h ******************************************/ -/* -** 2008 May 27 -** -** The author disclaims copyright to this source code. In place of -** a legal notice, here is a blessing: -** -** May you do good and not evil. -** May you find forgiveness for yourself and forgive others. -** May you share freely, never taking more than you give. -** -****************************************************************************** -** -** This file contains inline asm code for retrieving "high-performance" -** counters for x86 class CPUs. -*/ -#ifndef SQLITE_HWTIME_H -#define SQLITE_HWTIME_H - -/* -** The following routine only works on pentium-class (or newer) processors. -** It uses the RDTSC opcode to read the cycle count value out of the -** processor and returns that value. This can be used for high-res -** profiling. -*/ -#if (defined(__GNUC__) || defined(_MSC_VER)) && \ - (defined(i386) || defined(__i386__) || defined(_M_IX86)) - - #if defined(__GNUC__) - - __inline__ sqlite_uint64 sqlite3Hwtime(void){ - unsigned int lo, hi; - __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi)); - return (sqlite_uint64)hi << 32 | lo; - } - - #elif defined(_MSC_VER) - - __declspec(naked) __inline sqlite_uint64 __cdecl sqlite3Hwtime(void){ - __asm { - rdtsc - ret ; return value at EDX:EAX - } - } - - #endif - -#elif (defined(__GNUC__) && defined(__x86_64__)) - - __inline__ sqlite_uint64 sqlite3Hwtime(void){ - unsigned long val; - __asm__ __volatile__ ("rdtsc" : "=A" (val)); - return val; - } - -#elif (defined(__GNUC__) && defined(__ppc__)) - - __inline__ sqlite_uint64 sqlite3Hwtime(void){ - unsigned long long retval; - unsigned long junk; - __asm__ __volatile__ ("\n\ - 1: mftbu %1\n\ - mftb %L0\n\ - mftbu %0\n\ - cmpw %0,%1\n\ - bne 1b" - : "=r" (retval), "=r" (junk)); - return retval; - } - -#else - - #error Need implementation of sqlite3Hwtime() for your platform. - - /* - ** To compile without implementing sqlite3Hwtime() for your platform, - ** you can remove the above #error and use the following - ** stub function. You will lose timing support for many - ** of the debugging and testing utilities, but it should at - ** least compile and run. - */ -SQLITE_PRIVATE sqlite_uint64 sqlite3Hwtime(void){ return ((sqlite_uint64)0); } - -#endif - -#endif /* !defined(SQLITE_HWTIME_H) */ - -/************** End of hwtime.h **********************************************/ -/************** Continuing where we left off in os_common.h ******************/ - -static sqlite_uint64 g_start; -static sqlite_uint64 g_elapsed; -#define TIMER_START g_start=sqlite3Hwtime() -#define TIMER_END g_elapsed=sqlite3Hwtime()-g_start -#define TIMER_ELAPSED g_elapsed -#else -#define TIMER_START -#define TIMER_END -#define TIMER_ELAPSED ((sqlite_uint64)0) -#endif - -/* -** If we compile with the SQLITE_TEST macro set, then the following block -** of code will give us the ability to simulate a disk I/O error. This -** is used for testing the I/O recovery logic. -*/ -#if defined(SQLITE_TEST) -SQLITE_API extern int sqlite3_io_error_hit; -SQLITE_API extern int sqlite3_io_error_hardhit; -SQLITE_API extern int sqlite3_io_error_pending; -SQLITE_API extern int sqlite3_io_error_persist; -SQLITE_API extern int sqlite3_io_error_benign; -SQLITE_API extern int sqlite3_diskfull_pending; -SQLITE_API extern int sqlite3_diskfull; -#define SimulateIOErrorBenign(X) sqlite3_io_error_benign=(X) -#define SimulateIOError(CODE) \ - if( (sqlite3_io_error_persist && sqlite3_io_error_hit) \ - || sqlite3_io_error_pending-- == 1 ) \ - { local_ioerr(); CODE; } -static void local_ioerr(){ - IOTRACE(("IOERR\n")); - sqlite3_io_error_hit++; - if( !sqlite3_io_error_benign ) sqlite3_io_error_hardhit++; -} -#define SimulateDiskfullError(CODE) \ - if( sqlite3_diskfull_pending ){ \ - if( sqlite3_diskfull_pending == 1 ){ \ - local_ioerr(); \ - sqlite3_diskfull = 1; \ - sqlite3_io_error_hit = 1; \ - CODE; \ - }else{ \ - sqlite3_diskfull_pending--; \ - } \ - } -#else -#define SimulateIOErrorBenign(X) -#define SimulateIOError(A) -#define SimulateDiskfullError(A) -#endif /* defined(SQLITE_TEST) */ - -/* -** When testing, keep a count of the number of open files. -*/ -#if defined(SQLITE_TEST) -SQLITE_API extern int sqlite3_open_file_count; -#define OpenCounter(X) sqlite3_open_file_count+=(X) -#else -#define OpenCounter(X) -#endif /* defined(SQLITE_TEST) */ - -#endif /* !defined(_OS_COMMON_H_) */ - -/************** End of os_common.h *******************************************/ -/************** Continuing where we left off in mutex_w32.c ******************/ +/* #include "os_common.h" */ /* ** Include the header file for the Windows VFS. @@ -26065,6 +31045,8 @@ SQLITE_API extern int sqlite3_open_file_count; #ifdef __CYGWIN__ # include +# include /* amalgamator: dontcache */ +# include /* amalgamator: dontcache */ # include /* amalgamator: dontcache */ #endif @@ -26099,14 +31081,6 @@ SQLITE_API extern int sqlite3_open_file_count; # define SQLITE_OS_WINCE 0 #endif -/* -** Determine if we are dealing with WinRT, which provides only a subset of -** the full Win32 API. -*/ -#if !defined(SQLITE_OS_WINRT) -# define SQLITE_OS_WINRT 0 -#endif - /* ** For WinCE, some API function parameters do not appear to be declared as ** volatile. @@ -26121,7 +31095,7 @@ SQLITE_API extern int sqlite3_open_file_count; ** For some Windows sub-platforms, the _beginthreadex() / _endthreadex() ** functions are not available (e.g. those not using MSVC, Cygwin, etc). */ -#if SQLITE_OS_WIN && !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && \ +#if SQLITE_OS_WIN && !SQLITE_OS_WINCE && \ SQLITE_THREADSAFE>0 && !defined(__CYGWIN__) # define SQLITE_OS_WIN_THREADS 1 #else @@ -26147,7 +31121,7 @@ struct sqlite3_mutex { CRITICAL_SECTION mutex; /* Mutex controlling the lock */ int id; /* Mutex type */ #ifdef SQLITE_DEBUG - volatile int nRef; /* Number of enterances */ + volatile int nRef; /* Number of entrances */ volatile DWORD owner; /* Thread holding this mutex */ volatile LONG trace; /* True to trace changes */ #endif @@ -26196,7 +31170,7 @@ SQLITE_PRIVATE void sqlite3MemoryBarrier(void){ SQLITE_MEMORY_BARRIER; #elif defined(__GNUC__) __sync_synchronize(); -#elif MSVC_VERSION>=1300 +#elif MSVC_VERSION>=1400 _ReadWriteBarrier(); #elif defined(MemoryBarrier) MemoryBarrier(); @@ -26238,11 +31212,7 @@ static int winMutexInit(void){ if( InterlockedCompareExchange(&winMutex_lock, 1, 0)==0 ){ int i; for(i=0; i **
        • SQLITE_MUTEX_FAST **
        • SQLITE_MUTEX_RECURSIVE -**
        • SQLITE_MUTEX_STATIC_MASTER +**
        • SQLITE_MUTEX_STATIC_MAIN **
        • SQLITE_MUTEX_STATIC_MEM **
        • SQLITE_MUTEX_STATIC_OPEN **
        • SQLITE_MUTEX_STATIC_PRNG @@ -26332,11 +31302,7 @@ static sqlite3_mutex *winMutexAlloc(int iType){ p->trace = 1; #endif #endif -#if SQLITE_OS_WINRT - InitializeCriticalSectionEx(&p->mutex, 0, 0); -#else InitializeCriticalSection(&p->mutex); -#endif } break; } @@ -26543,19 +31509,27 @@ SQLITE_API int sqlite3_release_memory(int n){ #endif } +/* +** Default value of the hard heap limit. 0 means "no limit". +*/ +#ifndef SQLITE_MAX_MEMORY +# define SQLITE_MAX_MEMORY 0 +#endif + /* ** State information local to the memory allocation subsystem. */ static SQLITE_WSD struct Mem0Global { sqlite3_mutex *mutex; /* Mutex to serialize access */ sqlite3_int64 alarmThreshold; /* The soft heap limit */ + sqlite3_int64 hardLimit; /* The hard upper bound on memory */ /* ** True if heap is nearly "full" where "full" is defined by the ** sqlite3_soft_heap_limit() setting. */ int nearlyFull; -} mem0 = { 0, 0, 0 }; +} mem0 = { 0, SQLITE_MAX_MEMORY, SQLITE_MAX_MEMORY, 0 }; #define mem0 GLOBAL(struct Mem0Global, mem0) @@ -26585,8 +31559,15 @@ SQLITE_API int sqlite3_memory_alarm( #endif /* -** Set the soft heap-size limit for the library. Passing a zero or -** negative value indicates no limit. +** Set the soft heap-size limit for the library. An argument of +** zero disables the limit. A negative argument is a no-op used to +** obtain the return value. +** +** The return value is the value of the heap limit just before this +** interface was called. +** +** If the hard heap limit is enabled, then the soft heap limit cannot +** be disabled nor raised above the hard heap limit. */ SQLITE_API sqlite3_int64 sqlite3_soft_heap_limit64(sqlite3_int64 n){ sqlite3_int64 priorLimit; @@ -26602,9 +31583,12 @@ SQLITE_API sqlite3_int64 sqlite3_soft_heap_limit64(sqlite3_int64 n){ sqlite3_mutex_leave(mem0.mutex); return priorLimit; } + if( mem0.hardLimit>0 && (n>mem0.hardLimit || n==0) ){ + n = mem0.hardLimit; + } mem0.alarmThreshold = n; nUsed = sqlite3StatusValue(SQLITE_STATUS_MEMORY_USED); - mem0.nearlyFull = (n>0 && n<=nUsed); + AtomicStore(&mem0.nearlyFull, n>0 && n<=nUsed); sqlite3_mutex_leave(mem0.mutex); excess = sqlite3_memory_used() - n; if( excess>0 ) sqlite3_release_memory((int)(excess & 0x7fffffff)); @@ -26615,6 +31599,37 @@ SQLITE_API void sqlite3_soft_heap_limit(int n){ sqlite3_soft_heap_limit64(n); } +/* +** Set the hard heap-size limit for the library. An argument of zero +** disables the hard heap limit. A negative argument is a no-op used +** to obtain the return value without affecting the hard heap limit. +** +** The return value is the value of the hard heap limit just prior to +** calling this interface. +** +** Setting the hard heap limit will also activate the soft heap limit +** and constrain the soft heap limit to be no more than the hard heap +** limit. +*/ +SQLITE_API sqlite3_int64 sqlite3_hard_heap_limit64(sqlite3_int64 n){ + sqlite3_int64 priorLimit; +#ifndef SQLITE_OMIT_AUTOINIT + int rc = sqlite3_initialize(); + if( rc ) return -1; +#endif + sqlite3_mutex_enter(mem0.mutex); + priorLimit = mem0.hardLimit; + if( n>=0 ){ + mem0.hardLimit = n; + if( n>32) < 0xffffffff ); +} +#else +# define test_oom_breakpoint(X) /* No-op for production builds */ +#endif + /* ** Do a memory allocation with statistics and alarms. Assume the ** lock is already held. @@ -26701,21 +31733,22 @@ static void mallocWithAlarm(int n, void **pp){ ** following xRoundup() call. */ nFull = sqlite3GlobalConfig.m.xRoundup(n); -#ifdef SQLITE_MAX_MEMORY - if( sqlite3StatusValue(SQLITE_STATUS_MEMORY_USED)+nFull>SQLITE_MAX_MEMORY ){ - *pp = 0; - return; - } -#endif - sqlite3StatusHighwater(SQLITE_STATUS_MALLOC_SIZE, n); if( mem0.alarmThreshold>0 ){ sqlite3_int64 nUsed = sqlite3StatusValue(SQLITE_STATUS_MEMORY_USED); if( nUsed >= mem0.alarmThreshold - nFull ){ - mem0.nearlyFull = 1; + AtomicStore(&mem0.nearlyFull, 1); sqlite3MallocAlarm(nFull); + if( mem0.hardLimit ){ + nUsed = sqlite3StatusValue(SQLITE_STATUS_MEMORY_USED); + if( nUsed >= mem0.hardLimit - nFull ){ + test_oom_breakpoint(1); + *pp = 0; + return; + } + } }else{ - mem0.nearlyFull = 0; + AtomicStore(&mem0.nearlyFull, 0); } } p = sqlite3GlobalConfig.m.xMalloc(nFull); @@ -26739,12 +31772,7 @@ static void mallocWithAlarm(int n, void **pp){ */ SQLITE_PRIVATE void *sqlite3Malloc(u64 n){ void *p; - if( n==0 || n>=0x7fffff00 ){ - /* A memory allocation of a number of bytes which is near the maximum - ** signed integer value might cause an integer overflow inside of the - ** xMalloc(). Hence we limit the maximum size to 0x7fffff00, giving - ** 255 bytes of overhead. SQLite itself will never use anything near - ** this amount. The only way to reach the limit is with sqlite3_malloc() */ + if( n==0 || n>SQLITE_MAX_ALLOCATION_SIZE ){ p = 0; }else if( sqlite3GlobalConfig.bMemstat ){ sqlite3_mutex_enter(mem0.mutex); @@ -26779,8 +31807,8 @@ SQLITE_API void *sqlite3_malloc64(sqlite3_uint64 n){ ** TRUE if p is a lookaside memory allocation from db */ #ifndef SQLITE_OMIT_LOOKASIDE -static int isLookaside(sqlite3 *db, void *p){ - return SQLITE_WITHIN(p, db->lookaside.pStart, db->lookaside.pEnd); +static int isLookaside(sqlite3 *db, const void *p){ + return SQLITE_WITHIN(p, db->lookaside.pStart, db->lookaside.pTrueEnd); } #else #define isLookaside(A,B) 0 @@ -26790,27 +31818,43 @@ static int isLookaside(sqlite3 *db, void *p){ ** Return the size of a memory allocation previously obtained from ** sqlite3Malloc() or sqlite3_malloc(). */ -SQLITE_PRIVATE int sqlite3MallocSize(void *p){ +SQLITE_PRIVATE int sqlite3MallocSize(const void *p){ assert( sqlite3MemdebugHasType(p, MEMTYPE_HEAP) ); - return sqlite3GlobalConfig.m.xSize(p); + return sqlite3GlobalConfig.m.xSize((void*)p); } -SQLITE_PRIVATE int sqlite3DbMallocSize(sqlite3 *db, void *p){ +static int lookasideMallocSize(sqlite3 *db, const void *p){ +#ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE + return plookaside.pMiddle ? db->lookaside.szTrue : LOOKASIDE_SMALL; +#else + return db->lookaside.szTrue; +#endif +} +SQLITE_PRIVATE int sqlite3DbMallocSize(sqlite3 *db, const void *p){ assert( p!=0 ); - if( db==0 || !isLookaside(db,p) ){ #ifdef SQLITE_DEBUG - if( db==0 ){ - assert( sqlite3MemdebugNoType(p, (u8)~MEMTYPE_HEAP) ); - assert( sqlite3MemdebugHasType(p, MEMTYPE_HEAP) ); - }else{ - assert( sqlite3MemdebugHasType(p, (MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) ); - assert( sqlite3MemdebugNoType(p, (u8)~(MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) ); - } + if( db==0 ){ + assert( sqlite3MemdebugNoType(p, (u8)~MEMTYPE_HEAP) ); + assert( sqlite3MemdebugHasType(p, MEMTYPE_HEAP) ); + }else if( !isLookaside(db,p) ){ + assert( sqlite3MemdebugHasType(p, (MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) ); + assert( sqlite3MemdebugNoType(p, (u8)~(MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) ); + } #endif - return sqlite3GlobalConfig.m.xSize(p); - }else{ - assert( sqlite3_mutex_held(db->mutex) ); - return db->lookaside.sz; + if( db ){ + if( ((uptr)p)<(uptr)(db->lookaside.pTrueEnd) ){ +#ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE + if( ((uptr)p)>=(uptr)(db->lookaside.pMiddle) ){ + assert( sqlite3_mutex_held(db->mutex) ); + return LOOKASIDE_SMALL; + } +#endif + if( ((uptr)p)>=(uptr)(db->lookaside.pStart) ){ + assert( sqlite3_mutex_held(db->mutex) ); + return db->lookaside.szTrue; + } + } } + return sqlite3GlobalConfig.m.xSize((void*)p); } SQLITE_API sqlite3_uint64 sqlite3_msize(void *p){ assert( sqlite3MemdebugNoType(p, (u8)~MEMTYPE_HEAP) ); @@ -26853,24 +31897,75 @@ SQLITE_PRIVATE void sqlite3DbFreeNN(sqlite3 *db, void *p){ assert( db==0 || sqlite3_mutex_held(db->mutex) ); assert( p!=0 ); if( db ){ + if( ((uptr)p)<(uptr)(db->lookaside.pEnd) ){ +#ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE + if( ((uptr)p)>=(uptr)(db->lookaside.pMiddle) ){ + LookasideSlot *pBuf = (LookasideSlot*)p; + assert( db->pnBytesFreed==0 ); +#ifdef SQLITE_DEBUG + memset(p, 0xaa, LOOKASIDE_SMALL); /* Trash freed content */ +#endif + pBuf->pNext = db->lookaside.pSmallFree; + db->lookaside.pSmallFree = pBuf; + return; + } +#endif /* SQLITE_OMIT_TWOSIZE_LOOKASIDE */ + if( ((uptr)p)>=(uptr)(db->lookaside.pStart) ){ + LookasideSlot *pBuf = (LookasideSlot*)p; + assert( db->pnBytesFreed==0 ); +#ifdef SQLITE_DEBUG + memset(p, 0xaa, db->lookaside.szTrue); /* Trash freed content */ +#endif + pBuf->pNext = db->lookaside.pFree; + db->lookaside.pFree = pBuf; + return; + } + } if( db->pnBytesFreed ){ measureAllocationSize(db, p); return; } - if( isLookaside(db, p) ){ + } + assert( sqlite3MemdebugHasType(p, (MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) ); + assert( sqlite3MemdebugNoType(p, (u8)~(MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) ); + assert( db!=0 || sqlite3MemdebugNoType(p, MEMTYPE_LOOKASIDE) ); + sqlite3MemdebugSetType(p, MEMTYPE_HEAP); + sqlite3_free(p); +} +SQLITE_PRIVATE void sqlite3DbNNFreeNN(sqlite3 *db, void *p){ + assert( db!=0 ); + assert( sqlite3_mutex_held(db->mutex) ); + assert( p!=0 ); + if( ((uptr)p)<(uptr)(db->lookaside.pEnd) ){ +#ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE + if( ((uptr)p)>=(uptr)(db->lookaside.pMiddle) ){ + LookasideSlot *pBuf = (LookasideSlot*)p; + assert( db->pnBytesFreed==0 ); +#ifdef SQLITE_DEBUG + memset(p, 0xaa, LOOKASIDE_SMALL); /* Trash freed content */ +#endif + pBuf->pNext = db->lookaside.pSmallFree; + db->lookaside.pSmallFree = pBuf; + return; + } +#endif /* SQLITE_OMIT_TWOSIZE_LOOKASIDE */ + if( ((uptr)p)>=(uptr)(db->lookaside.pStart) ){ LookasideSlot *pBuf = (LookasideSlot*)p; + assert( db->pnBytesFreed==0 ); #ifdef SQLITE_DEBUG - /* Trash all content in the buffer being freed */ - memset(p, 0xaa, db->lookaside.sz); + memset(p, 0xaa, db->lookaside.szTrue); /* Trash freed content */ #endif pBuf->pNext = db->lookaside.pFree; db->lookaside.pFree = pBuf; return; } } + if( db->pnBytesFreed ){ + measureAllocationSize(db, p); + return; + } assert( sqlite3MemdebugHasType(p, (MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) ); assert( sqlite3MemdebugNoType(p, (u8)~(MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) ); - assert( db!=0 || sqlite3MemdebugNoType(p, MEMTYPE_LOOKASIDE) ); sqlite3MemdebugSetType(p, MEMTYPE_HEAP); sqlite3_free(p); } @@ -26894,8 +31989,7 @@ SQLITE_PRIVATE void *sqlite3Realloc(void *pOld, u64 nBytes){ sqlite3_free(pOld); /* IMP: R-26507-47431 */ return 0; } - if( nBytes>=0x7fffff00 ){ - /* The 0x7ffff00 limit term is explained in comments on sqlite3Malloc() */ + if( nBytes>SQLITE_MAX_ALLOCATION_SIZE ){ return 0; } nOld = sqlite3MallocSize(pOld); @@ -26906,18 +32000,26 @@ SQLITE_PRIVATE void *sqlite3Realloc(void *pOld, u64 nBytes){ if( nOld==nNew ){ pNew = pOld; }else if( sqlite3GlobalConfig.bMemstat ){ + sqlite3_int64 nUsed; sqlite3_mutex_enter(mem0.mutex); sqlite3StatusHighwater(SQLITE_STATUS_MALLOC_SIZE, (int)nBytes); nDiff = nNew - nOld; - if( nDiff>0 && sqlite3StatusValue(SQLITE_STATUS_MEMORY_USED) >= + if( nDiff>0 && (nUsed = sqlite3StatusValue(SQLITE_STATUS_MEMORY_USED)) >= mem0.alarmThreshold-nDiff ){ sqlite3MallocAlarm(nDiff); + if( mem0.hardLimit>0 && nUsed >= mem0.hardLimit - nDiff ){ + sqlite3_mutex_leave(mem0.mutex); + test_oom_breakpoint(1); + return 0; + } } pNew = sqlite3GlobalConfig.m.xRealloc(pOld, nNew); +#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT if( pNew==0 && mem0.alarmThreshold>0 ){ sqlite3MallocAlarm((int)nBytes); pNew = sqlite3GlobalConfig.m.xRealloc(pOld, nNew); } +#endif if( pNew ){ nNew = sqlite3MallocSize(pNew); sqlite3StatusUp(SQLITE_STATUS_MEMORY_USED, nNew-nOld); @@ -26951,7 +32053,7 @@ SQLITE_API void *sqlite3_realloc64(void *pOld, sqlite3_uint64 n){ /* ** Allocate and zero memory. -*/ +*/ SQLITE_PRIVATE void *sqlite3MallocZero(u64 n){ void *p = sqlite3Malloc(n); if( p ){ @@ -26981,13 +32083,13 @@ static SQLITE_NOINLINE void *dbMallocRawFinish(sqlite3 *db, u64 n){ assert( db!=0 ); p = sqlite3Malloc(n); if( !p ) sqlite3OomFault(db); - sqlite3MemdebugSetType(p, + sqlite3MemdebugSetType(p, (db->lookaside.bDisable==0) ? MEMTYPE_LOOKASIDE : MEMTYPE_HEAP); return p; } /* -** Allocate memory, either lookaside (if possible) or heap. +** Allocate memory, either lookaside (if possible) or heap. ** If the allocation fails, set the mallocFailed flag in ** the connection pointer. ** @@ -27021,23 +32123,37 @@ SQLITE_PRIVATE void *sqlite3DbMallocRawNN(sqlite3 *db, u64 n){ assert( db!=0 ); assert( sqlite3_mutex_held(db->mutex) ); assert( db->pnBytesFreed==0 ); - if( db->lookaside.bDisable==0 ){ - assert( db->mallocFailed==0 ); - if( n>db->lookaside.sz ){ + if( n>db->lookaside.sz ){ + if( !db->lookaside.bDisable ){ db->lookaside.anStat[1]++; - }else if( (pBuf = db->lookaside.pFree)!=0 ){ - db->lookaside.pFree = pBuf->pNext; + }else if( db->mallocFailed ){ + return 0; + } + return dbMallocRawFinish(db, n); + } +#ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE + if( n<=LOOKASIDE_SMALL ){ + if( (pBuf = db->lookaside.pSmallFree)!=0 ){ + db->lookaside.pSmallFree = pBuf->pNext; db->lookaside.anStat[0]++; return (void*)pBuf; - }else if( (pBuf = db->lookaside.pInit)!=0 ){ - db->lookaside.pInit = pBuf->pNext; + }else if( (pBuf = db->lookaside.pSmallInit)!=0 ){ + db->lookaside.pSmallInit = pBuf->pNext; db->lookaside.anStat[0]++; return (void*)pBuf; - }else{ - db->lookaside.anStat[2]++; } - }else if( db->mallocFailed ){ - return 0; + } +#endif + if( (pBuf = db->lookaside.pFree)!=0 ){ + db->lookaside.pFree = pBuf->pNext; + db->lookaside.anStat[0]++; + return (void*)pBuf; + }else if( (pBuf = db->lookaside.pInit)!=0 ){ + db->lookaside.pInit = pBuf->pNext; + db->lookaside.anStat[0]++; + return (void*)pBuf; + }else{ + db->lookaside.anStat[2]++; } #else assert( db!=0 ); @@ -27061,7 +32177,16 @@ SQLITE_PRIVATE void *sqlite3DbRealloc(sqlite3 *db, void *p, u64 n){ assert( db!=0 ); if( p==0 ) return sqlite3DbMallocRawNN(db, n); assert( sqlite3_mutex_held(db->mutex) ); - if( isLookaside(db,p) && n<=db->lookaside.sz ) return p; + if( ((uptr)p)<(uptr)db->lookaside.pEnd ){ +#ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE + if( ((uptr)p)>=(uptr)db->lookaside.pMiddle ){ + if( n<=LOOKASIDE_SMALL ) return p; + }else +#endif + if( ((uptr)p)>=(uptr)db->lookaside.pStart ){ + if( n<=db->lookaside.szTrue ) return p; + } + } return dbReallocFinish(db, p, n); } static SQLITE_NOINLINE void *dbReallocFinish(sqlite3 *db, void *p, u64 n){ @@ -27072,14 +32197,14 @@ static SQLITE_NOINLINE void *dbReallocFinish(sqlite3 *db, void *p, u64 n){ if( isLookaside(db, p) ){ pNew = sqlite3DbMallocRawNN(db, n); if( pNew ){ - memcpy(pNew, p, db->lookaside.sz); + memcpy(pNew, p, lookasideMallocSize(db, p)); sqlite3DbFree(db, p); } }else{ assert( sqlite3MemdebugHasType(p, (MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) ); assert( sqlite3MemdebugNoType(p, (u8)~(MEMTYPE_LOOKASIDE|MEMTYPE_HEAP)) ); sqlite3MemdebugSetType(p, MEMTYPE_HEAP); - pNew = sqlite3_realloc64(p, n); + pNew = sqlite3Realloc(p, n); if( !pNew ){ sqlite3OomFault(db); } @@ -27104,9 +32229,9 @@ SQLITE_PRIVATE void *sqlite3DbReallocOrFree(sqlite3 *db, void *p, u64 n){ } /* -** Make a copy of a string in memory obtained from sqliteMalloc(). These +** Make a copy of a string in memory obtained from sqliteMalloc(). These ** functions call sqlite3MallocRaw() directly instead of sqliteMalloc(). This -** is because when memory debugging is turned on, these two functions are +** is because when memory debugging is turned on, these two functions are ** called via macros that record the current file and line number in the ** ThreadData structure. */ @@ -27126,11 +32251,9 @@ SQLITE_PRIVATE char *sqlite3DbStrDup(sqlite3 *db, const char *z){ SQLITE_PRIVATE char *sqlite3DbStrNDup(sqlite3 *db, const char *z, u64 n){ char *zNew; assert( db!=0 ); - if( z==0 ){ - return 0; - } + assert( z!=0 || n==0 ); assert( (n&0x7fffffff)==n ); - zNew = sqlite3DbMallocRawNN(db, n+1); + zNew = z ? sqlite3DbMallocRawNN(db, n+1) : 0; if( zNew ){ memcpy(zNew, z, (size_t)n); zNew[n] = 0; @@ -27145,9 +32268,14 @@ SQLITE_PRIVATE char *sqlite3DbStrNDup(sqlite3 *db, const char *z, u64 n){ */ SQLITE_PRIVATE char *sqlite3DbSpanDup(sqlite3 *db, const char *zStart, const char *zEnd){ int n; +#ifdef SQLITE_DEBUG + /* Because of the way the parser works, the span is guaranteed to contain + ** at least one non-space character */ + for(n=0; sqlite3Isspace(zStart[n]); n++){ assert( &zStart[n]0) && sqlite3Isspace(zStart[n-1]) ) n--; + while( sqlite3Isspace(zStart[n-1]) ) n--; return sqlite3DbStrNDup(db, zStart, n); } @@ -27155,8 +32283,9 @@ SQLITE_PRIVATE char *sqlite3DbSpanDup(sqlite3 *db, const char *zStart, const cha ** Free any prior content in *pz and replace it with a copy of zNew. */ SQLITE_PRIVATE void sqlite3SetString(char **pz, sqlite3 *db, const char *zNew){ + char *z = sqlite3DbStrDup(db, zNew); sqlite3DbFree(db, *pz); - *pz = sqlite3DbStrDup(db, zNew); + *pz = z; } /* @@ -27164,18 +32293,32 @@ SQLITE_PRIVATE void sqlite3SetString(char **pz, sqlite3 *db, const char *zNew){ ** has happened. This routine will set db->mallocFailed, and also ** temporarily disable the lookaside memory allocator and interrupt ** any running VDBEs. +** +** Always return a NULL pointer so that this routine can be invoked using +** +** return sqlite3OomFault(db); +** +** and thereby avoid unnecessary stack frame allocations for the overwhelmingly +** common case where no OOM occurs. */ -SQLITE_PRIVATE void sqlite3OomFault(sqlite3 *db){ +SQLITE_PRIVATE void *sqlite3OomFault(sqlite3 *db){ if( db->mallocFailed==0 && db->bBenignMalloc==0 ){ db->mallocFailed = 1; if( db->nVdbeExec>0 ){ - db->u1.isInterrupted = 1; + AtomicStore(&db->u1.isInterrupted, 1); } - db->lookaside.bDisable++; + DisableLookaside; if( db->pParse ){ + Parse *pParse; + sqlite3ErrorMsg(db->pParse, "out of memory"); db->pParse->rc = SQLITE_NOMEM_BKPT; + for(pParse=db->pParse->pOuterParse; pParse; pParse = pParse->pOuterParse){ + pParse->nErr++; + pParse->rc = SQLITE_NOMEM; + } } } + return 0; } /* @@ -27188,51 +32331,54 @@ SQLITE_PRIVATE void sqlite3OomFault(sqlite3 *db){ SQLITE_PRIVATE void sqlite3OomClear(sqlite3 *db){ if( db->mallocFailed && db->nVdbeExec==0 ){ db->mallocFailed = 0; - db->u1.isInterrupted = 0; + AtomicStore(&db->u1.isInterrupted, 0); assert( db->lookaside.bDisable>0 ); - db->lookaside.bDisable--; + EnableLookaside; } } /* -** Take actions at the end of an API call to indicate an OOM error +** Take actions at the end of an API call to deal with error codes. */ -static SQLITE_NOINLINE int apiOomError(sqlite3 *db){ - sqlite3OomClear(db); - sqlite3Error(db, SQLITE_NOMEM); - return SQLITE_NOMEM_BKPT; +static SQLITE_NOINLINE int apiHandleError(sqlite3 *db, int rc){ + if( db->mallocFailed || rc==SQLITE_IOERR_NOMEM ){ + sqlite3OomClear(db); + sqlite3Error(db, SQLITE_NOMEM); + return SQLITE_NOMEM_BKPT; + } + return rc & db->errMask; } /* -** This function must be called before exiting any API function (i.e. +** This function must be called before exiting any API function (i.e. ** returning control to the user) that has called sqlite3_malloc or ** sqlite3_realloc. ** ** The returned value is normally a copy of the second argument to this ** function. However, if a malloc() failure has occurred since the previous -** invocation SQLITE_NOMEM is returned instead. +** invocation SQLITE_NOMEM is returned instead. ** ** If an OOM as occurred, then the connection error-code (the value ** returned by sqlite3_errcode()) is set to SQLITE_NOMEM. */ SQLITE_PRIVATE int sqlite3ApiExit(sqlite3* db, int rc){ /* If the db handle must hold the connection handle mutex here. - ** Otherwise the read (and possible write) of db->mallocFailed + ** Otherwise the read (and possible write) of db->mallocFailed ** is unsafe, as is the call to sqlite3Error(). */ assert( db!=0 ); assert( sqlite3_mutex_held(db->mutex) ); - if( db->mallocFailed || rc==SQLITE_IOERR_NOMEM ){ - return apiOomError(db); + if( db->mallocFailed || rc ){ + return apiHandleError(db, rc); } - return rc & db->errMask; + return 0; } /************** End of malloc.c **********************************************/ /************** Begin file printf.c ******************************************/ /* ** The "printf" code that follows dates from the 1980's. It is in -** the public domain. +** the public domain. ** ************************************************************************** ** @@ -27257,17 +32403,17 @@ SQLITE_PRIVATE int sqlite3ApiExit(sqlite3* db, int rc){ #define etPERCENT 7 /* Percent symbol. %% */ #define etCHARX 8 /* Characters. %c */ /* The rest are extensions, not normally found in printf() */ -#define etSQLESCAPE 9 /* Strings with '\'' doubled. %q */ -#define etSQLESCAPE2 10 /* Strings with '\'' doubled and enclosed in '', - NULL pointers replaced by SQL NULL. %Q */ -#define etTOKEN 11 /* a pointer to a Token structure */ -#define etSRCLIST 12 /* a pointer to a SrcList */ -#define etPOINTER 13 /* The %p conversion */ -#define etSQLESCAPE3 14 /* %w -> Strings with '\"' doubled */ -#define etORDINAL 15 /* %r -> 1st, 2nd, 3rd, 4th, etc. English only */ -#define etDECIMAL 16 /* %d or %u, but not %x, %o */ +#define etESCAPE_q 9 /* Strings with '\'' doubled. %q */ +#define etESCAPE_Q 10 /* Strings with '\'' doubled and enclosed in '', + NULL pointers replaced by SQL NULL. %Q */ +#define etTOKEN 11 /* a pointer to a Token structure */ +#define etSRCITEM 12 /* a pointer to a SrcItem */ +#define etPOINTER 13 /* The %p conversion */ +#define etESCAPE_w 14 /* %w -> Strings with '\"' doubled */ +#define etORDINAL 15 /* %r -> 1st, 2nd, 3rd, 4th, etc. English only */ +#define etDECIMAL 16 /* %d or %u, but not %x, %o */ -#define etINVALID 17 /* Any unrecognized conversion type */ +#define etINVALID 17 /* Any unrecognized conversion type */ /* @@ -27286,6 +32432,7 @@ typedef struct et_info { /* Information about each format field */ etByte type; /* Conversion paradigm */ etByte charset; /* Offset into aDigits[] of the digits string */ etByte prefix; /* Offset into aPrefix[] of the prefix string */ + char iNxt; /* Next with same hash, or 0 for end of chain */ } et_info; /* @@ -27294,78 +32441,70 @@ typedef struct et_info { /* Information about each format field */ #define FLAG_SIGNED 1 /* True if the value to convert is signed */ #define FLAG_STRING 4 /* Allow infinite precision */ - /* -** The following table is searched linearly, so it is good to put the -** most frequently used conversion types first. +** The table is searched by hash. In the case of %C where C is the character +** and that character has ASCII value j, then the hash is j%23. +** +** The order of the entries in fmtinfo[] and the hash chain was entered +** manually, but based on the output of the following TCL script: */ +#if 0 /***** Beginning of script ******/ +foreach c {d s g z q Q w c o u x X f e E G i n % p T S r} { + scan $c %c x + set n($c) $x +} +set mx [llength [array names n]] +puts "count: $mx" + +set mx 27 +puts "*********** mx=$mx ************" +for {set r 0} {$r<$mx} {incr r} { + puts -nonewline [format %2d: $r] + foreach c [array names n] { + if {($n($c))%$mx==$r} {puts -nonewline " $c"} + } + puts "" +} +#endif /***** End of script ********/ + static const char aDigits[] = "0123456789ABCDEF0123456789abcdef"; static const char aPrefix[] = "-x0\000X0"; -static const et_info fmtinfo[] = { - { 'd', 10, 1, etDECIMAL, 0, 0 }, - { 's', 0, 4, etSTRING, 0, 0 }, - { 'g', 0, 1, etGENERIC, 30, 0 }, - { 'z', 0, 4, etDYNSTRING, 0, 0 }, - { 'q', 0, 4, etSQLESCAPE, 0, 0 }, - { 'Q', 0, 4, etSQLESCAPE2, 0, 0 }, - { 'w', 0, 4, etSQLESCAPE3, 0, 0 }, - { 'c', 0, 0, etCHARX, 0, 0 }, - { 'o', 8, 0, etRADIX, 0, 2 }, - { 'u', 10, 0, etDECIMAL, 0, 0 }, - { 'x', 16, 0, etRADIX, 16, 1 }, - { 'X', 16, 0, etRADIX, 0, 4 }, -#ifndef SQLITE_OMIT_FLOATING_POINT - { 'f', 0, 1, etFLOAT, 0, 0 }, - { 'e', 0, 1, etEXP, 30, 0 }, - { 'E', 0, 1, etEXP, 14, 0 }, - { 'G', 0, 1, etGENERIC, 14, 0 }, -#endif - { 'i', 10, 1, etDECIMAL, 0, 0 }, - { 'n', 0, 0, etSIZE, 0, 0 }, - { '%', 0, 0, etPERCENT, 0, 0 }, - { 'p', 16, 0, etPOINTER, 0, 1 }, - - /* All the rest are undocumented and are for internal use only */ - { 'T', 0, 0, etTOKEN, 0, 0 }, - { 'S', 0, 0, etSRCLIST, 0, 0 }, - { 'r', 10, 1, etORDINAL, 0, 0 }, +static const et_info fmtinfo[23] = { + /* 0 */ { 's', 0, 4, etSTRING, 0, 0, 1 }, + /* 1 */ { 'E', 0, 1, etEXP, 14, 0, 0 }, /* Hash: 0 */ + /* 2 */ { 'u', 10, 0, etDECIMAL, 0, 0, 3 }, + /* 3 */ { 'G', 0, 1, etGENERIC, 14, 0, 0 }, /* Hash: 2 */ + /* 4 */ { 'w', 0, 4, etESCAPE_w, 0, 0, 0 }, + /* 5 */ { 'x', 16, 0, etRADIX, 16, 1, 0 }, + /* 6 */ { 'c', 0, 0, etCHARX, 0, 0, 0 }, /* Hash: 7 */ + /* 7 */ { 'z', 0, 4, etDYNSTRING, 0, 0, 6 }, + /* 8 */ { 'd', 10, 1, etDECIMAL, 0, 0, 0 }, + /* 9 */ { 'e', 0, 1, etEXP, 30, 0, 0 }, + /* 10 */ { 'f', 0, 1, etFLOAT, 0, 0, 0 }, + /* 11 */ { 'g', 0, 1, etGENERIC, 30, 0, 0 }, + /* 12 */ { 'Q', 0, 4, etESCAPE_Q, 0, 0, 0 }, + /* 13 */ { 'i', 10, 1, etDECIMAL, 0, 0, 0 }, + /* 14 */ { '%', 0, 0, etPERCENT, 0, 0, 16 }, + /* 15 */ { 'T', 0, 0, etTOKEN, 0, 0, 0 }, + /* 16 */ { 'S', 0, 0, etSRCITEM, 0, 0, 0 }, /* Hash: 14 */ + /* 17 */ { 'X', 16, 0, etRADIX, 0, 4, 0 }, /* Hash: 19 */ + /* 18 */ { 'n', 0, 0, etSIZE, 0, 0, 0 }, + /* 19 */ { 'o', 8, 0, etRADIX, 0, 2, 17 }, + /* 20 */ { 'p', 16, 0, etPOINTER, 0, 1, 0 }, + /* 21 */ { 'q', 0, 4, etESCAPE_q, 0, 0, 0 }, + /* 22 */ { 'r', 10, 1, etORDINAL, 0, 0, 0 } }; -/* -** If SQLITE_OMIT_FLOATING_POINT is defined, then none of the floating point -** conversions will work. -*/ -#ifndef SQLITE_OMIT_FLOATING_POINT -/* -** "*val" is a double such that 0.1 <= *val < 10.0 -** Return the ascii code for the leading digit of *val, then -** multiply "*val" by 10.0 to renormalize. +/* Additional Notes: ** -** Example: -** input: *val = 3.14159 -** output: *val = 1.4159 function return = '3' -** -** The counter *cnt is incremented each time. After counter exceeds -** 16 (the number of significant digits in a 64-bit float) '0' is -** always returned. -*/ -static char et_getdigit(LONGDOUBLE_TYPE *val, int *cnt){ - int digit; - LONGDOUBLE_TYPE d; - if( (*cnt)<=0 ) return '0'; - (*cnt)--; - digit = (int)*val; - d = digit; - digit += '0'; - *val = (*val - d)*10.0; - return (char)digit; -} -#endif /* SQLITE_OMIT_FLOATING_POINT */ +** %S Takes a pointer to SrcItem. Shows name or database.name +** %!S Like %S but prefer the zName over the zAlias +*/ /* ** Set the StrAccum object to an error mode. */ -static void setStrAccumError(StrAccum *p, u8 eError){ +SQLITE_PRIVATE void sqlite3StrAccumSetError(StrAccum *p, u8 eError){ assert( eError==SQLITE_NOMEM || eError==SQLITE_TOOBIG ); p->accError = eError; if( p->mxAlloc ) sqlite3_str_reset(p); @@ -27401,12 +32540,12 @@ static char *printfTempBuf(sqlite3_str *pAccum, sqlite3_int64 n){ char *z; if( pAccum->accError ) return 0; if( n>pAccum->nAlloc && n>pAccum->mxAlloc ){ - setStrAccumError(pAccum, SQLITE_TOOBIG); + sqlite3StrAccumSetError(pAccum, SQLITE_TOOBIG); return 0; } - z = sqlite3DbMallocRaw(pAccum->db, n); + z = sqlite3_malloc(n); if( z==0 ){ - setStrAccumError(pAccum, SQLITE_NOMEM); + sqlite3StrAccumSetError(pAccum, SQLITE_NOMEM); } return z; } @@ -27420,6 +32559,13 @@ static char *printfTempBuf(sqlite3_str *pAccum, sqlite3_int64 n){ #endif #define etBUFSIZE SQLITE_PRINT_BUF_SIZE /* Size of the output buffer */ +/* +** Hard limit on the precision of floating-point conversions. +*/ +#ifndef SQLITE_PRINTF_PRECISION_LIMIT +# define SQLITE_FP_PRECISION_LIMIT 100000000 +#endif + /* ** Render a string given by "fmt" into the StrAccum object. */ @@ -27446,22 +32592,19 @@ SQLITE_API void sqlite3_str_vappendf( u8 bArgList; /* True for SQLITE_PRINTF_SQLFUNC */ char prefix; /* Prefix character. "+" or "-" or " " or '\0'. */ sqlite_uint64 longvalue; /* Value for integer types */ - LONGDOUBLE_TYPE realvalue; /* Value for real types */ + double realvalue; /* Value for real types */ const et_info *infop; /* Pointer to the appropriate info structure */ char *zOut; /* Rendering buffer */ int nOut; /* Size of the rendering buffer */ char *zExtra = 0; /* Malloced memory used by some conversion */ -#ifndef SQLITE_OMIT_FLOATING_POINT - int exp, e2; /* exponent of real numbers */ - int nsd; /* Number of significant digits returned */ - double rounder; /* Used for rounding floating point values */ + int exp, e2; /* exponent of real numbers */ etByte flag_dp; /* True if decimal point should be shown */ etByte flag_rtz; /* True if trailing zeros should be removed */ -#endif + PrintfArguments *pArgList = 0; /* Arguments for SQLITE_PRINTF_SQLFUNC */ char buf[etBUFSIZE]; /* Conversion buffer */ - /* pAccum never starts out with an empty buffer that was obtained from + /* pAccum never starts out with an empty buffer that was obtained from ** malloc(). This precondition is required by the mprintf("%z...") ** optimization. */ assert( pAccum->nChar>0 || (pAccum->printfFlags&SQLITE_PRINTF_MALLOCED)==0 ); @@ -27479,7 +32622,10 @@ SQLITE_API void sqlite3_str_vappendf( #if HAVE_STRCHRNUL fmt = strchrnul(fmt, '%'); #else - do{ fmt++; }while( *fmt && *fmt != '%' ); + fmt = strchr(fmt, '%'); + if( fmt==0 ){ + fmt = bufpt + strlen(bufpt); + } #endif sqlite3_str_append(pAccum, bufpt, (int)(fmt - bufpt)); if( *fmt==0 ) break; @@ -27593,6 +32739,9 @@ SQLITE_API void sqlite3_str_vappendf( }while( !done && (c=(*++fmt))!=0 ); /* Fetch the info entry for the field */ +#ifdef SQLITE_EBCDIC + /* The hash table only works for ASCII. For EBCDIC, we need to do + ** a linear search of the table */ infop = &fmtinfo[0]; xtype = etINVALID; for(idx=0; idxtype; + }else{ + infop = &fmtinfo[0]; + xtype = etINVALID; + } +#endif /* ** At this point, variables are initialized as follows: @@ -27620,15 +32783,17 @@ SQLITE_API void sqlite3_str_vappendf( ** xtype The class of the conversion. ** infop Pointer to the appropriate info struct. */ + assert( width>=0 ); + assert( precision>=(-1) ); switch( xtype ){ case etPOINTER: flag_long = sizeof(char*)==sizeof(i64) ? 2 : sizeof(char*)==sizeof(long int) ? 1 : 0; - /* Fall through into the next case */ + /* no break */ deliberate_fall_through case etORDINAL: - case etRADIX: + case etRADIX: cThousand = 0; - /* Fall through into the next case */ + /* no break */ deliberate_fall_through case etDECIMAL: if( infop->flags & FLAG_SIGNED ){ i64 v; @@ -27644,11 +32809,10 @@ SQLITE_API void sqlite3_str_vappendf( v = va_arg(ap,int); } if( v<0 ){ - if( v==SMALLEST_INT64 ){ - longvalue = ((u64)1)<<63; - }else{ - longvalue = -v; - } + testcase( v==SMALLEST_INT64 ); + testcase( v==(-1) ); + longvalue = ~v; + longvalue++; prefix = '-'; }else{ longvalue = v; @@ -27668,6 +32832,14 @@ SQLITE_API void sqlite3_str_vappendf( } prefix = 0; } + +#if WHERETRACE_ENABLED + if( xtype==etPOINTER && sqlite3WhereTrace & 0x100000 ) longvalue = 0; +#endif +#if TREETRACE_ENABLED + if( xtype==etPOINTER && sqlite3TreeTrace & 0x100000 ) longvalue = 0; +#endif + if( longvalue==0 ) flag_alternateform = 0; if( flag_zeropad && precision0 ); } length = (int)(&zOut[nOut-1]-bufpt); - while( precision>length ){ - *(--bufpt) = '0'; /* Zero pad */ - length++; + if( precision>length ){ /* zero pad */ + int nn = precision-length; + bufpt -= nn; + memset(bufpt,'0',nn); + length = precision; } if( cThousand ){ int nn = (length - 1)/3; /* Number of "," to insert */ @@ -27731,59 +32905,84 @@ SQLITE_API void sqlite3_str_vappendf( break; case etFLOAT: case etEXP: - case etGENERIC: + case etGENERIC: { + FpDecode s; + int iRound; + int j; + i64 szBufNeeded; /* Size needed to hold the output */ + if( bArgList ){ realvalue = getDoubleArg(pArgList); }else{ realvalue = va_arg(ap,double); } -#ifdef SQLITE_OMIT_FLOATING_POINT - length = 0; -#else if( precision<0 ) precision = 6; /* Set default precision */ - if( realvalue<0.0 ){ - realvalue = -realvalue; - prefix = '-'; - }else{ - prefix = flag_prefix; +#ifdef SQLITE_FP_PRECISION_LIMIT + if( precision>SQLITE_FP_PRECISION_LIMIT ){ + precision = SQLITE_FP_PRECISION_LIMIT; } - if( xtype==etGENERIC && precision>0 ) precision--; - testcase( precision>0xfff ); - for(idx=precision&0xfff, rounder=0.5; idx>0; idx--, rounder*=0.1){} - if( xtype==etFLOAT ) realvalue += rounder; - /* Normalize realvalue to within 10.0 > realvalue >= 1.0 */ - exp = 0; - if( sqlite3IsNaN((double)realvalue) ){ - bufpt = "NaN"; - length = 3; - break; +#endif + if( xtype==etFLOAT ){ + iRound = -precision; + }else if( xtype==etGENERIC ){ + if( precision==0 ) precision = 1; + iRound = precision; + }else{ + iRound = precision+1; } - if( realvalue>0.0 ){ - LONGDOUBLE_TYPE scale = 1.0; - while( realvalue>=1e100*scale && exp<=350 ){ scale *= 1e100;exp+=100;} - while( realvalue>=1e10*scale && exp<=350 ){ scale *= 1e10; exp+=10; } - while( realvalue>=10.0*scale && exp<=350 ){ scale *= 10.0; exp++; } - realvalue /= scale; - while( realvalue<1e-8 ){ realvalue *= 1e8; exp-=8; } - while( realvalue<1.0 ){ realvalue *= 10.0; exp--; } - if( exp>350 ){ + sqlite3FpDecode(&s, realvalue, iRound, flag_altform2 ? 20 : 16); + if( s.isSpecial ){ + if( s.isSpecial==2 ){ + bufpt = flag_zeropad ? "null" : "NaN"; + length = sqlite3Strlen30(bufpt); + break; + }else if( flag_zeropad ){ + s.z[0] = '9'; + s.iDP = 1000; + s.n = 1; + }else{ + memcpy(buf, "-Inf", 5); bufpt = buf; - buf[0] = prefix; - memcpy(buf+(prefix!=0),"Inf",4); - length = 3+(prefix!=0); + if( s.sign=='-' ){ + /* no-op */ + }else if( flag_prefix ){ + buf[0] = flag_prefix; + }else{ + bufpt++; + } + length = sqlite3Strlen30(bufpt); break; } } - bufpt = buf; + if( s.sign=='-' ){ + if( flag_alternateform + && !flag_prefix + && xtype==etFLOAT + && s.iDP<=iRound + ){ + /* Suppress the minus sign if all of the following are true: + ** * The value displayed is zero + ** * The '#' flag is used + ** * The '+' flag is not used, and + ** * The format is %f + */ + prefix = 0; + }else{ + prefix = '-'; + } + }else{ + prefix = flag_prefix; + } + + exp = s.iDP-1; + /* ** If the field type is etGENERIC, then convert to either etEXP ** or etFLOAT, as appropriate. */ - if( xtype!=etFLOAT ){ - realvalue += rounder; - if( realvalue>=10.0 ){ realvalue *= 0.1; exp++; } - } if( xtype==etGENERIC ){ + assert( precision>0 ); + precision--; flag_rtz = !flag_alternateform; if( exp<-4 || exp>precision ){ xtype = etEXP; @@ -27797,29 +32996,58 @@ SQLITE_API void sqlite3_str_vappendf( if( xtype==etEXP ){ e2 = 0; }else{ - e2 = exp; + e2 = s.iDP - 1; } - { - i64 szBufNeeded; /* Size of a temporary buffer needed */ - szBufNeeded = MAX(e2,0)+(i64)precision+(i64)width+15; - if( szBufNeeded > etBUFSIZE ){ - bufpt = zExtra = printfTempBuf(pAccum, szBufNeeded); - if( bufpt==0 ) return; + + szBufNeeded = MAX(e2,0)+(i64)precision+(i64)width+10; + if( cThousand && e2>0 ) szBufNeeded += (e2+2)/3; + if( szBufNeeded + pAccum->nChar >= pAccum->nAlloc ){ + if( pAccum->mxAlloc==0 && pAccum->accError==0 ){ + /* Unable to allocate space in pAccum, perhaps because it + ** is coming from sqlite3_snprintf() or similar. We'll have + ** to render into temporary space and the memcpy() it over. */ + bufpt = sqlite3_malloc(szBufNeeded); + if( bufpt==0 ){ + sqlite3StrAccumSetError(pAccum, SQLITE_NOMEM); + return; + } + zExtra = bufpt; + }else if( sqlite3StrAccumEnlarge(pAccum, szBufNeeded)zText + pAccum->nChar; } + }else{ + bufpt = pAccum->zText + pAccum->nChar; } zOut = bufpt; - nsd = 16 + flag_altform2*10; + flag_dp = (precision>0 ?1:0) | flag_alternateform | flag_altform2; /* The sign in front of the number */ if( prefix ){ *(bufpt++) = prefix; } /* Digits prior to the decimal point */ + j = 0; + assert( s.n>0 ); if( e2<0 ){ *(bufpt++) = '0'; - }else{ + }else if( cThousand ){ for(; e2>=0; e2--){ - *(bufpt++) = et_getdigit(&realvalue,&nsd); + *(bufpt++) = j1 ) *(bufpt++) = ','; + } + }else{ + j = e2+1; + if( j>s.n ) j = s.n; + memcpy(bufpt, s.z, j); + bufpt += j; + e2 -= j; + if( e2>=0 ){ + memset(bufpt, '0', e2+1); + bufpt += e2+1; + e2 = -1; } } /* The decimal point */ @@ -27828,13 +33056,26 @@ SQLITE_API void sqlite3_str_vappendf( } /* "0" digits after the decimal point but before the first ** significant digit of the number */ - for(e2++; e2<0; precision--, e2++){ - assert( precision>0 ); - *(bufpt++) = '0'; + if( e2<(-1) && precision>0 ){ + int nn = -1-e2; + if( nn>precision ) nn = precision; + memset(bufpt, '0', nn); + bufpt += nn; + precision -= nn; } /* Significant digits after the decimal point */ - while( (precision--)>0 ){ - *(bufpt++) = et_getdigit(&realvalue,&nsd); + if( precision>0 ){ + int nn = s.n - j; + if( NEVER(nn>precision) ) nn = precision; + if( nn>0 ){ + memcpy(bufpt, s.z+j, nn); + bufpt += nn; + precision -= nn; + } + if( precision>0 && !flag_rtz ){ + memset(bufpt, '0', precision); + bufpt += precision; + } } /* Remove trailing zeros and the "." if no digits follow the "." */ if( flag_rtz && flag_dp ){ @@ -27850,6 +33091,7 @@ SQLITE_API void sqlite3_str_vappendf( } /* Add the "eNNN" suffix */ if( xtype==etEXP ){ + exp = s.iDP - 1; *(bufpt++) = aDigits[infop->charset]; if( exp<0 ){ *(bufpt++) = '-'; exp = -exp; @@ -27863,28 +33105,40 @@ SQLITE_API void sqlite3_str_vappendf( *(bufpt++) = (char)(exp/10+'0'); /* 10's digit */ *(bufpt++) = (char)(exp%10+'0'); /* 1's digit */ } - *bufpt = 0; - /* The converted number is in buf[] and zero terminated. Output it. - ** Note that the number is in the usual order, not reversed as with - ** integer conversions. */ length = (int)(bufpt-zOut); - bufpt = zOut; - - /* Special case: Add leading zeros if the flag_zeropad flag is - ** set and we are not left justified */ - if( flag_zeropad && !flag_leftjustify && length < width){ - int i; - int nPad = width - length; - for(i=width; i>=nPad; i--){ - bufpt[i] = bufpt[i-nPad]; + assert( length <= szBufNeeded ); + if( lengthnChar += length; + zOut[length] = 0; + continue; + }else{ + /* We were unable to render directly into pAccum because we + ** couldn't allocate sufficient memory. We need to memcpy() + ** the rendering (or some prefix thereof) into the output + ** buffer. */ + bufpt[0] = 0; + bufpt = zExtra; + break; + } + } case etSIZE: if( !bArgList ){ *(va_arg(ap,int*)) = pAccum->nChar; @@ -27912,34 +33166,28 @@ SQLITE_API void sqlite3_str_vappendf( } }else{ unsigned int ch = va_arg(ap,unsigned int); - if( ch<0x00080 ){ - buf[0] = ch & 0xff; - length = 1; - }else if( ch<0x00800 ){ - buf[0] = 0xc0 + (u8)((ch>>6)&0x1f); - buf[1] = 0x80 + (u8)(ch & 0x3f); - length = 2; - }else if( ch<0x10000 ){ - buf[0] = 0xe0 + (u8)((ch>>12)&0x0f); - buf[1] = 0x80 + (u8)((ch>>6) & 0x3f); - buf[2] = 0x80 + (u8)(ch & 0x3f); - length = 3; - }else{ - buf[0] = 0xf0 + (u8)((ch>>18) & 0x07); - buf[1] = 0x80 + (u8)((ch>>12) & 0x3f); - buf[2] = 0x80 + (u8)((ch>>6) & 0x3f); - buf[3] = 0x80 + (u8)(ch & 0x3f); - length = 4; - } + length = sqlite3AppendOneUtf8Character(buf, ch); } if( precision>1 ){ + i64 nPrior = 1; width -= precision-1; if( width>1 && !flag_leftjustify ){ sqlite3_str_appendchar(pAccum, width-1, ' '); width = 0; } - while( precision-- > 1 ){ - sqlite3_str_append(pAccum, buf, length); + sqlite3_str_append(pAccum, buf, length); + precision--; + while( precision > 1 ){ + i64 nCopyBytes; + if( nPrior > precision-1 ) nPrior = precision - 1; + nCopyBytes = length*nPrior; + if( sqlite3StrAccumEnlargeIfNeeded(pAccum, nCopyBytes) ){ + break; + } + sqlite3_str_append(pAccum, + &pAccum->zText[pAccum->nChar-nCopyBytes], nCopyBytes); + precision -= nPrior; + nPrior *= 2; } } bufpt = buf; @@ -27997,23 +33245,32 @@ SQLITE_API void sqlite3_str_vappendf( while( ii>=0 ) if( (bufpt[ii--] & 0xc0)==0x80 ) width++; } break; - case etSQLESCAPE: /* %q: Escape ' characters */ - case etSQLESCAPE2: /* %Q: Escape ' and enclose in '...' */ - case etSQLESCAPE3: { /* %w: Escape " characters */ - int i, j, k, n, isnull; - int needQuote; + case etESCAPE_q: /* %q: Escape ' characters */ + case etESCAPE_Q: /* %Q: Escape ' and enclose in '...' */ + case etESCAPE_w: { /* %w: Escape " characters */ + i64 i, j, k, n; + int needQuote = 0; char ch; - char q = ((xtype==etSQLESCAPE3)?'"':'\''); /* Quote character */ char *escarg; + char q; if( bArgList ){ escarg = getTextArg(pArgList); }else{ escarg = va_arg(ap,char*); } - isnull = escarg==0; - if( isnull ) escarg = (xtype==etSQLESCAPE2 ? "NULL" : "(NULL)"); - /* For %q, %Q, and %w, the precision is the number of byte (or + if( escarg==0 ){ + escarg = (xtype==etESCAPE_Q ? "NULL" : "(NULL)"); + }else if( xtype==etESCAPE_Q ){ + needQuote = 1; + } + if( xtype==etESCAPE_w ){ + q = '"'; + flag_alternateform = 0; + }else{ + q = '\''; + } + /* For %q, %Q, and %w, the precision is the number of bytes (or ** characters if the ! flags is present) to use from the input. ** Because of the extra quoting characters inserted, the number ** of output characters may be larger than the precision. @@ -28025,7 +33282,30 @@ SQLITE_API void sqlite3_str_vappendf( while( (escarg[i+1]&0xc0)==0x80 ){ i++; } } } - needQuote = !isnull && xtype==etSQLESCAPE2; + if( flag_alternateform ){ + /* For %#q, do unistr()-style backslash escapes for + ** all control characters, and for backslash itself. + ** For %#Q, do the same but only if there is at least + ** one control character. */ + i64 nBack = 0; + i64 nCtrl = 0; + for(k=0; ketBUFSIZE ){ bufpt = zExtra = printfTempBuf(pAccum, n); @@ -28034,43 +33314,97 @@ SQLITE_API void sqlite3_str_vappendf( bufpt = buf; } j = 0; - if( needQuote ) bufpt[j++] = q; + if( needQuote ){ + if( needQuote==2 ){ + memcpy(&bufpt[j], "unistr('", 8); + j += 8; + }else{ + bufpt[j++] = '\''; + } + } k = i; - for(i=0; i=0x10 ? '1' : '0'; + bufpt[j++] = "0123456789abcdef"[ch&0xf]; + } + } + }else{ + for(i=0; iprintfFlags & SQLITE_PRINTF_INTERNAL)==0 ) return; - pToken = va_arg(ap, Token*); - assert( bArgList==0 ); - if( pToken && pToken->n ){ - sqlite3_str_append(pAccum, (const char*)pToken->z, pToken->n); + if( flag_alternateform ){ + /* %#T means an Expr pointer that uses Expr.u.zToken */ + Expr *pExpr = va_arg(ap,Expr*); + if( ALWAYS(pExpr) && ALWAYS(!ExprHasProperty(pExpr,EP_IntValue)) ){ + sqlite3_str_appendall(pAccum, (const char*)pExpr->u.zToken); + sqlite3RecordErrorOffsetOfExpr(pAccum->db, pExpr); + } + }else{ + /* %T means a Token pointer */ + Token *pToken = va_arg(ap, Token*); + assert( bArgList==0 ); + if( pToken && pToken->n ){ + sqlite3_str_append(pAccum, (const char*)pToken->z, pToken->n); + sqlite3RecordErrorByteOffset(pAccum->db, pToken->z); + } } length = width = 0; break; } - case etSRCLIST: { - SrcList *pSrc; - int k; - struct SrcList_item *pItem; + case etSRCITEM: { + SrcItem *pItem; if( (pAccum->printfFlags & SQLITE_PRINTF_INTERNAL)==0 ) return; - pSrc = va_arg(ap, SrcList*); - k = va_arg(ap, int); - pItem = &pSrc->a[k]; + pItem = va_arg(ap, SrcItem*); assert( bArgList==0 ); - assert( k>=0 && knSrc ); - if( pItem->zDatabase ){ - sqlite3_str_appendall(pAccum, pItem->zDatabase); - sqlite3_str_append(pAccum, ".", 1); + if( pItem->zAlias && !flag_altform2 ){ + sqlite3_str_appendall(pAccum, pItem->zAlias); + }else if( pItem->zName ){ + if( pItem->fg.fixedSchema==0 + && pItem->fg.isSubquery==0 + && pItem->u4.zDatabase!=0 + ){ + sqlite3_str_appendall(pAccum, pItem->u4.zDatabase); + sqlite3_str_append(pAccum, ".", 1); + } + sqlite3_str_appendall(pAccum, pItem->zName); + }else if( pItem->zAlias ){ + sqlite3_str_appendall(pAccum, pItem->zAlias); + }else if( ALWAYS(pItem->fg.isSubquery) ){/* Because of tag-20240424-1 */ + Select *pSel = pItem->u4.pSubq->pSelect; + assert( pSel!=0 ); + if( pSel->selFlags & SF_NestedFrom ){ + sqlite3_str_appendf(pAccum, "(join-%u)", pSel->selId); + }else if( pSel->selFlags & SF_MultiValue ){ + assert( !pItem->fg.isTabFunc && !pItem->fg.isIndexedBy ); + sqlite3_str_appendf(pAccum, "%u-ROW VALUES CLAUSE", + pItem->u1.nRow); + }else{ + sqlite3_str_appendf(pAccum, "(subquery-%u)", pSel->selId); + } } - sqlite3_str_appendall(pAccum, pItem->zName); length = width = 0; break; } @@ -28103,6 +33437,45 @@ SQLITE_API void sqlite3_str_vappendf( }/* End for loop over the format string */ } /* End of function */ + +/* +** The z string points to the first character of a token that is +** associated with an error. If db does not already have an error +** byte offset recorded, try to compute the error byte offset for +** z and set the error byte offset in db. +*/ +SQLITE_PRIVATE void sqlite3RecordErrorByteOffset(sqlite3 *db, const char *z){ + const Parse *pParse; + const char *zText; + const char *zEnd; + assert( z!=0 ); + if( NEVER(db==0) ) return; + if( db->errByteOffset!=(-2) ) return; + pParse = db->pParse; + if( NEVER(pParse==0) ) return; + zText =pParse->zTail; + if( NEVER(zText==0) ) return; + zEnd = &zText[strlen(zText)]; + if( SQLITE_WITHIN(z,zText,zEnd) ){ + db->errByteOffset = (int)(z-zText); + } +} + +/* +** If pExpr has a byte offset for the start of a token, record that as +** as the error offset. +*/ +SQLITE_PRIVATE void sqlite3RecordErrorOffsetOfExpr(sqlite3 *db, const Expr *pExpr){ + while( pExpr + && (ExprHasProperty(pExpr,EP_OuterON|EP_InnerON) || pExpr->w.iOfst<=0) + ){ + pExpr = pExpr->pLeft; + } + if( pExpr==0 ) return; + if( ExprHasProperty(pExpr, EP_FromDDL) ) return; + db->errByteOffset = pExpr->w.iOfst; +} + /* ** Enlarge the memory allocation on a StrAccum object so that it is ** able to accept at least N more bytes of text. @@ -28110,21 +33483,20 @@ SQLITE_API void sqlite3_str_vappendf( ** Return the number of bytes of text that StrAccum is able to accept ** after the attempted enlargement. The value returned might be zero. */ -static int sqlite3StrAccumEnlarge(StrAccum *p, int N){ +SQLITE_PRIVATE int sqlite3StrAccumEnlarge(StrAccum *p, i64 N){ char *zNew; - assert( p->nChar+(i64)N >= p->nAlloc ); /* Only called if really needed */ + assert( p->nChar+N >= p->nAlloc ); /* Only called if really needed */ if( p->accError ){ testcase(p->accError==SQLITE_TOOBIG); testcase(p->accError==SQLITE_NOMEM); return 0; } if( p->mxAlloc==0 ){ - setStrAccumError(p, SQLITE_TOOBIG); + sqlite3StrAccumSetError(p, SQLITE_TOOBIG); return p->nAlloc - p->nChar - 1; }else{ char *zOld = isMalloced(p) ? p->zText : 0; - i64 szNew = p->nChar; - szNew += N + 1; + i64 szNew = p->nChar + N + 1; if( szNew+p->nChar<=p->mxAlloc ){ /* Force exponential buffer size growth as long as it does not overflow, ** to avoid having to call this routine too often */ @@ -28132,7 +33504,7 @@ static int sqlite3StrAccumEnlarge(StrAccum *p, int N){ } if( szNew > p->mxAlloc ){ sqlite3_str_reset(p); - setStrAccumError(p, SQLITE_TOOBIG); + sqlite3StrAccumSetError(p, SQLITE_TOOBIG); return 0; }else{ p->nAlloc = (int)szNew; @@ -28140,7 +33512,7 @@ static int sqlite3StrAccumEnlarge(StrAccum *p, int N){ if( p->db ){ zNew = sqlite3DbRealloc(p->db, zOld, p->nAlloc); }else{ - zNew = sqlite3_realloc64(zOld, p->nAlloc); + zNew = sqlite3Realloc(zOld, p->nAlloc); } if( zNew ){ assert( p->zText!=0 || p->nChar==0 ); @@ -28150,11 +33522,19 @@ static int sqlite3StrAccumEnlarge(StrAccum *p, int N){ p->printfFlags |= SQLITE_PRINTF_MALLOCED; }else{ sqlite3_str_reset(p); - setStrAccumError(p, SQLITE_NOMEM); + sqlite3StrAccumSetError(p, SQLITE_NOMEM); return 0; } } - return N; + assert( N>=0 && N<=0x7fffffff ); + return (int)N; +} + +SQLITE_PRIVATE int sqlite3StrAccumEnlargeIfNeeded(StrAccum *p, i64 N){ + if( N + p->nChar >= p->nAlloc ){ + sqlite3StrAccumEnlarge(p, N); + } + return p->accError; } /* @@ -28218,12 +33598,12 @@ SQLITE_API void sqlite3_str_appendall(sqlite3_str *p, const char *z){ static SQLITE_NOINLINE char *strAccumFinishRealloc(StrAccum *p){ char *zText; assert( p->mxAlloc>0 && !isMalloced(p) ); - zText = sqlite3DbMallocRaw(p->db, p->nChar+1 ); + zText = sqlite3DbMallocRaw(p->db, 1+(u64)p->nChar ); if( zText ){ memcpy(zText, p->zText, p->nChar+1); p->printfFlags |= SQLITE_PRINTF_MALLOCED; }else{ - setStrAccumError(p, SQLITE_NOMEM); + sqlite3StrAccumSetError(p, SQLITE_NOMEM); } p->zText = zText; return zText; @@ -28238,6 +33618,22 @@ SQLITE_PRIVATE char *sqlite3StrAccumFinish(StrAccum *p){ return p->zText; } +/* +** Use the content of the StrAccum passed as the second argument +** as the result of an SQL function. +*/ +SQLITE_PRIVATE void sqlite3ResultStrAccum(sqlite3_context *pCtx, StrAccum *p){ + if( p->accError ){ + sqlite3_result_error_code(pCtx, p->accError); + sqlite3_str_reset(p); + }else if( isMalloced(p) ){ + sqlite3_result_text(pCtx, p->zText, p->nChar, SQLITE_DYNAMIC); + }else{ + sqlite3_result_text(pCtx, "", 0, SQLITE_STATIC); + sqlite3_str_reset(p); + } +} + /* ** This singleton is an sqlite3_str object that is returned if ** sqlite3_malloc() fails to provide space for a real one. This @@ -28271,6 +33667,14 @@ SQLITE_API int sqlite3_str_length(sqlite3_str *p){ return p ? p->nChar : 0; } +/* Truncate the text of the string to be no more than N bytes. */ +SQLITE_API void sqlite3_str_truncate(sqlite3_str *p, int N){ + if( p!=0 && N>=0 && (u32)NnChar ){ + p->nChar = N; + p->zText[p->nChar] = 0; + } +} + /* Return the current value for p */ SQLITE_API char *sqlite3_str_value(sqlite3_str *p){ if( p==0 || p->nChar==0 ) return 0; @@ -28291,6 +33695,17 @@ SQLITE_API void sqlite3_str_reset(StrAccum *p){ p->zText = 0; } +/* +** Destroy a dynamically allocate sqlite3_str object and all +** of its content, all in one call. +*/ +SQLITE_API void sqlite3_str_free(sqlite3_str *p){ + if( p!=0 && p!=&sqlite3OomStr ){ + sqlite3_str_reset(p); + sqlite3_free(p); + } +} + /* ** Initialize a string accumulator. ** @@ -28369,7 +33784,7 @@ SQLITE_API char *sqlite3_vmprintf(const char *zFormat, va_list ap){ char zBase[SQLITE_PRINT_BUF_SIZE]; StrAccum acc; -#ifdef SQLITE_ENABLE_API_ARMOR +#ifdef SQLITE_ENABLE_API_ARMOR if( zFormat==0 ){ (void)SQLITE_MISUSE_BKPT; return 0; @@ -28429,14 +33844,33 @@ SQLITE_API char *sqlite3_vsnprintf(int n, char *zBuf, const char *zFormat, va_li return zBuf; } SQLITE_API char *sqlite3_snprintf(int n, char *zBuf, const char *zFormat, ...){ - char *z; + StrAccum acc; va_list ap; + if( n<=0 ) return zBuf; +#ifdef SQLITE_ENABLE_API_ARMOR + if( zBuf==0 || zFormat==0 ) { + (void)SQLITE_MISUSE_BKPT; + if( zBuf ) zBuf[0] = 0; + return zBuf; + } +#endif + sqlite3StrAccumInit(&acc, 0, zBuf, n, 0); va_start(ap,zFormat); - z = sqlite3_vsnprintf(n, zBuf, zFormat, ap); + sqlite3_str_vappendf(&acc, zFormat, ap); va_end(ap); - return z; + zBuf[acc.nChar] = 0; + return zBuf; } +/* Maximum size of an sqlite3_log() message. */ +#if defined(SQLITE_MAX_LOG_MESSAGE) + /* Leave the definition as supplied */ +#elif SQLITE_PRINT_BUF_SIZE*10>10000 +# define SQLITE_MAX_LOG_MESSAGE 10000 +#else +# define SQLITE_MAX_LOG_MESSAGE (SQLITE_PRINT_BUF_SIZE*10) +#endif + /* ** This is the routine that actually formats the sqlite3_log() message. ** We house it in a separate routine from sqlite3_log() to avoid using @@ -28453,7 +33887,7 @@ SQLITE_API char *sqlite3_snprintf(int n, char *zBuf, const char *zFormat, ...){ */ static void renderLogMsg(int iErrCode, const char *zFormat, va_list ap){ StrAccum acc; /* String accumulator */ - char zMsg[SQLITE_PRINT_BUF_SIZE*3]; /* Complete log message */ + char zMsg[SQLITE_MAX_LOG_MESSAGE]; /* Complete log message */ sqlite3StrAccumInit(&acc, 0, zMsg, sizeof(zMsg), 0); sqlite3_str_vappendf(&acc, zFormat, ap); @@ -28482,7 +33916,7 @@ SQLITE_API void sqlite3_log(int iErrCode, const char *zFormat, ...){ SQLITE_PRIVATE void sqlite3DebugPrintf(const char *zFormat, ...){ va_list ap; StrAccum acc; - char zBuf[500]; + char zBuf[SQLITE_PRINT_BUF_SIZE*10]; sqlite3StrAccumInit(&acc, 0, zBuf, sizeof(zBuf), 0); va_start(ap,zFormat); sqlite3_str_vappendf(&acc, zFormat, ap); @@ -28512,6 +33946,75 @@ SQLITE_API void sqlite3_str_appendf(StrAccum *p, const char *zFormat, ...){ va_end(ap); } + +/***************************************************************************** +** Reference counted string/blob storage +*****************************************************************************/ + +/* +** Increase the reference count of the string by one. +** +** The input parameter is returned. +*/ +SQLITE_PRIVATE char *sqlite3RCStrRef(char *z){ + RCStr *p = (RCStr*)z; + assert( p!=0 ); + p--; + p->nRCRef++; + return z; +} + +/* +** Decrease the reference count by one. Free the string when the +** reference count reaches zero. +*/ +SQLITE_PRIVATE void sqlite3RCStrUnref(void *z){ + RCStr *p = (RCStr*)z; + assert( p!=0 ); + p--; + assert( p->nRCRef>0 ); + if( p->nRCRef>=2 ){ + p->nRCRef--; + }else{ + sqlite3_free(p); + } +} + +/* +** Create a new string that is capable of holding N bytes of text, not counting +** the zero byte at the end. The string is uninitialized. +** +** The reference count is initially 1. Call sqlite3RCStrUnref() to free the +** newly allocated string. +** +** This routine returns 0 on an OOM. +*/ +SQLITE_PRIVATE char *sqlite3RCStrNew(u64 N){ + RCStr *p = sqlite3_malloc64( N + sizeof(*p) + 1 ); + if( p==0 ) return 0; + p->nRCRef = 1; + return (char*)&p[1]; +} + +/* +** Change the size of the string so that it is able to hold N bytes. +** The string might be reallocated, so return the new allocation. +*/ +SQLITE_PRIVATE char *sqlite3RCStrResize(char *z, u64 N){ + RCStr *p = (RCStr*)z; + RCStr *pNew; + assert( p!=0 ); + p--; + assert( p->nRCRef==1 ); + pNew = sqlite3_realloc64(p, N+sizeof(RCStr)+1); + if( pNew==0 ){ + sqlite3_free(p); + return 0; + }else{ + return (char*)&pNew[1]; + } +} + /************** End of printf.c **********************************************/ /************** Begin file treeview.c ****************************************/ /* @@ -28528,7 +34031,7 @@ SQLITE_API void sqlite3_str_appendf(StrAccum *p, const char *zFormat, ...){ ** ** This file contains C code to implement the TreeView debugging routines. ** These routines print a parse tree to standard output for debugging and -** analysis. +** analysis. ** ** The interfaces in this file is only available when compiling ** with SQLITE_DEBUG. @@ -28540,40 +34043,44 @@ SQLITE_API void sqlite3_str_appendf(StrAccum *p, const char *zFormat, ...){ ** Add a new subitem to the tree. The moreToFollow flag indicates that this ** is not the last item in the tree. */ -static TreeView *sqlite3TreeViewPush(TreeView *p, u8 moreToFollow){ +static void sqlite3TreeViewPush(TreeView **pp, u8 moreToFollow){ + TreeView *p = *pp; if( p==0 ){ - p = sqlite3_malloc64( sizeof(*p) ); - if( p==0 ) return 0; + *pp = p = sqlite3_malloc64( sizeof(*p) ); + if( p==0 ) return; memset(p, 0, sizeof(*p)); }else{ p->iLevel++; } assert( moreToFollow==0 || moreToFollow==1 ); - if( p->iLevelbLine) ) p->bLine[p->iLevel] = moreToFollow; - return p; + if( p->iLevel<(int)sizeof(p->bLine) ) p->bLine[p->iLevel] = moreToFollow; } /* ** Finished with one layer of the tree */ -static void sqlite3TreeViewPop(TreeView *p){ +static void sqlite3TreeViewPop(TreeView **pp){ + TreeView *p = *pp; if( p==0 ) return; p->iLevel--; - if( p->iLevel<0 ) sqlite3_free(p); + if( p->iLevel<0 ){ + sqlite3_free(p); + *pp = 0; + } } /* ** Generate a single line of output for the tree, with a prefix that contains ** all the appropriate tree lines */ -static void sqlite3TreeViewLine(TreeView *p, const char *zFormat, ...){ +SQLITE_PRIVATE void sqlite3TreeViewLine(TreeView *p, const char *zFormat, ...){ va_list ap; int i; StrAccum acc; - char zBuf[500]; + char zBuf[1000]; sqlite3StrAccumInit(&acc, 0, zBuf, sizeof(zBuf), 0); if( p ){ - for(i=0; iiLevel && ibLine)-1; i++){ + for(i=0; iiLevel && i<(int)sizeof(p->bLine)-1; i++){ sqlite3_str_append(&acc, p->bLine[i] ? "| " : " ", 4); } sqlite3_str_append(&acc, p->bLine[i] ? "|-- " : "'-- ", 4); @@ -28582,7 +34089,7 @@ static void sqlite3TreeViewLine(TreeView *p, const char *zFormat, ...){ va_start(ap, zFormat); sqlite3_str_vappendf(&acc, zFormat, ap); va_end(ap); - assert( acc.nChar>0 ); + assert( acc.nChar>0 || acc.accError ); sqlite3_str_append(&acc, "\n", 1); } sqlite3StrAccumFinish(&acc); @@ -28594,10 +34101,57 @@ static void sqlite3TreeViewLine(TreeView *p, const char *zFormat, ...){ ** Shorthand for starting a new tree item that consists of a single label */ static void sqlite3TreeViewItem(TreeView *p, const char *zLabel,u8 moreFollows){ - p = sqlite3TreeViewPush(p, moreFollows); + sqlite3TreeViewPush(&p, moreFollows); sqlite3TreeViewLine(p, "%s", zLabel); } +/* +** Show a list of Column objects in tree format. +*/ +SQLITE_PRIVATE void sqlite3TreeViewColumnList( + TreeView *pView, + const Column *aCol, + int nCol, + u8 moreToFollow +){ + int i; + sqlite3TreeViewPush(&pView, moreToFollow); + sqlite3TreeViewLine(pView, "COLUMNS"); + for(i=0; inCte>0 ){ - pView = sqlite3TreeViewPush(pView, 1); + sqlite3TreeViewPush(&pView, moreToFollow); for(i=0; inCte; i++){ StrAccum x; char zLine[1000]; @@ -28622,18 +34176,25 @@ SQLITE_PRIVATE void sqlite3TreeViewWith(TreeView *pView, const With *pWith, u8 m char cSep = '('; int j; for(j=0; jpCols->nExpr; j++){ - sqlite3_str_appendf(&x, "%c%s", cSep, pCte->pCols->a[j].zName); + sqlite3_str_appendf(&x, "%c%s", cSep, pCte->pCols->a[j].zEName); cSep = ','; } sqlite3_str_appendf(&x, ")"); } - sqlite3_str_appendf(&x, " AS"); + if( pCte->eM10d!=M10d_Any ){ + sqlite3_str_appendf(&x, " %sMATERIALIZED", + pCte->eM10d==M10d_No ? "NOT " : ""); + } + if( pCte->pUse ){ + sqlite3_str_appendf(&x, " (pUse=0x%p, nUse=%d)", pCte->pUse, + pCte->pUse->nUse); + } sqlite3StrAccumFinish(&x); sqlite3TreeViewItem(pView, zLine, inCte-1); sqlite3TreeViewSelect(pView, pCte->pSelect, 0); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } } @@ -28642,36 +34203,81 @@ SQLITE_PRIVATE void sqlite3TreeViewWith(TreeView *pView, const With *pWith, u8 m */ SQLITE_PRIVATE void sqlite3TreeViewSrcList(TreeView *pView, const SrcList *pSrc){ int i; + if( pSrc==0 ) return; for(i=0; inSrc; i++){ - const struct SrcList_item *pItem = &pSrc->a[i]; + const SrcItem *pItem = &pSrc->a[i]; StrAccum x; - char zLine[100]; + int n = 0; + char zLine[1000]; sqlite3StrAccumInit(&x, 0, zLine, sizeof(zLine), 0); - sqlite3_str_appendf(&x, "{%d,*}", pItem->iCursor); - if( pItem->zDatabase ){ - sqlite3_str_appendf(&x, " %s.%s", pItem->zDatabase, pItem->zName); - }else if( pItem->zName ){ - sqlite3_str_appendf(&x, " %s", pItem->zName); - } - if( pItem->pTab ){ - sqlite3_str_appendf(&x, " tab=%Q nCol=%d ptr=%p", - pItem->pTab->zName, pItem->pTab->nCol, pItem->pTab); - } - if( pItem->zAlias ){ - sqlite3_str_appendf(&x, " (AS %s)", pItem->zAlias); - } - if( pItem->fg.jointype & JT_LEFT ){ + x.printfFlags |= SQLITE_PRINTF_INTERNAL; + sqlite3_str_appendf(&x, "{%d:*} %!S", pItem->iCursor, pItem); + if( pItem->pSTab ){ + sqlite3_str_appendf(&x, " tab=%Q nCol=%d ptr=%p used=%llx%s", + pItem->pSTab->zName, pItem->pSTab->nCol, pItem->pSTab, + pItem->colUsed, + pItem->fg.rowidUsed ? "+rowid" : ""); + } + if( (pItem->fg.jointype & (JT_LEFT|JT_RIGHT))==(JT_LEFT|JT_RIGHT) ){ + sqlite3_str_appendf(&x, " FULL-OUTER-JOIN"); + }else if( pItem->fg.jointype & JT_LEFT ){ sqlite3_str_appendf(&x, " LEFT-JOIN"); - } + }else if( pItem->fg.jointype & JT_RIGHT ){ + sqlite3_str_appendf(&x, " RIGHT-JOIN"); + }else if( pItem->fg.jointype & JT_CROSS ){ + sqlite3_str_appendf(&x, " CROSS-JOIN"); + } + if( pItem->fg.jointype & JT_LTORJ ){ + sqlite3_str_appendf(&x, " LTORJ"); + } + if( pItem->fg.fromDDL ){ + sqlite3_str_appendf(&x, " DDL"); + } + if( pItem->fg.isCte ){ + static const char *aMat[] = {",MAT", "", ",NO-MAT"}; + sqlite3_str_appendf(&x, " CteUse=%d%s", + pItem->u2.pCteUse->nUse, + aMat[pItem->u2.pCteUse->eM10d]); + } + if( pItem->fg.isOn || (pItem->fg.isUsing==0 && pItem->u3.pOn!=0) ){ + sqlite3_str_appendf(&x, " isOn"); + } + if( pItem->fg.isTabFunc ) sqlite3_str_appendf(&x, " isTabFunc"); + if( pItem->fg.isCorrelated ) sqlite3_str_appendf(&x, " isCorrelated"); + if( pItem->fg.isMaterialized ) sqlite3_str_appendf(&x, " isMaterialized"); + if( pItem->fg.viaCoroutine ) sqlite3_str_appendf(&x, " viaCoroutine"); + if( pItem->fg.notCte ) sqlite3_str_appendf(&x, " notCte"); + if( pItem->fg.isNestedFrom ) sqlite3_str_appendf(&x, " isNestedFrom"); + if( pItem->fg.fixedSchema ) sqlite3_str_appendf(&x, " fixedSchema"); + if( pItem->fg.hadSchema ) sqlite3_str_appendf(&x, " hadSchema"); + if( pItem->fg.isSubquery ) sqlite3_str_appendf(&x, " isSubquery"); + sqlite3StrAccumFinish(&x); - sqlite3TreeViewItem(pView, zLine, inSrc-1); - if( pItem->pSelect ){ - sqlite3TreeViewSelect(pView, pItem->pSelect, 0); + sqlite3TreeViewItem(pView, zLine, inSrc-1); + n = 0; + if( pItem->fg.isSubquery ) n++; + if( pItem->fg.isTabFunc ) n++; + if( pItem->fg.isUsing || pItem->u3.pOn!=0 ) n++; + if( pItem->fg.isUsing ){ + sqlite3TreeViewIdList(pView, pItem->u3.pUsing, (--n)>0, "USING"); + }else if( pItem->u3.pOn!=0 ){ + sqlite3TreeViewItem(pView, "ON", (--n)>0); + sqlite3TreeViewExpr(pView, pItem->u3.pOn, 0); + sqlite3TreeViewPop(&pView); + } + if( pItem->fg.isSubquery ){ + assert( n==1 ); + if( pItem->pSTab ){ + Table *pTab = pItem->pSTab; + sqlite3TreeViewColumnList(pView, pTab->aCol, pTab->nCol, 1); + } + assert( (int)pItem->fg.isNestedFrom == IsNestedFrom(pItem) ); + sqlite3TreeViewSelect(pView, pItem->u4.pSubq->pSelect, 0); } if( pItem->fg.isTabFunc ){ sqlite3TreeViewExprList(pView, pItem->u1.pFuncArg, 0, "func-args:"); } - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } } @@ -28684,27 +34290,31 @@ SQLITE_PRIVATE void sqlite3TreeViewSelect(TreeView *pView, const Select *p, u8 m if( p==0 ){ sqlite3TreeViewLine(pView, "nil-SELECT"); return; - } - pView = sqlite3TreeViewPush(pView, moreToFollow); + } + sqlite3TreeViewPush(&pView, moreToFollow); if( p->pWith ){ sqlite3TreeViewWith(pView, p->pWith, 1); cnt = 1; - sqlite3TreeViewPush(pView, 1); + sqlite3TreeViewPush(&pView, 1); } do{ - sqlite3TreeViewLine(pView, - "SELECT%s%s (%u/%p) selFlags=0x%x nSelectRow=%d", - ((p->selFlags & SF_Distinct) ? " DISTINCT" : ""), - ((p->selFlags & SF_Aggregate) ? " agg_flag" : ""), - p->selId, p, p->selFlags, - (int)p->nSelectRow - ); - if( cnt++ ) sqlite3TreeViewPop(pView); + if( p->selFlags & SF_WhereBegin ){ + sqlite3TreeViewLine(pView, "sqlite3WhereBegin()"); + }else{ + sqlite3TreeViewLine(pView, + "SELECT%s%s (%u/%p) selFlags=0x%x nSelectRow=%d", + ((p->selFlags & SF_Distinct) ? " DISTINCT" : ""), + ((p->selFlags & SF_Aggregate) ? " agg_flag" : ""), + p->selId, p, p->selFlags, + (int)p->nSelectRow + ); + } + if( cnt++ ) sqlite3TreeViewPop(&pView); if( p->pPrior ){ n = 1000; }else{ n = 0; - if( p->pSrc && p->pSrc->nSrc ) n++; + if( p->pSrc && p->pSrc->nSrc && p->pSrc->nAlloc ) n++; if( p->pWhere ) n++; if( p->pGroupBy ) n++; if( p->pHaving ) n++; @@ -28715,28 +34325,31 @@ SQLITE_PRIVATE void sqlite3TreeViewSelect(TreeView *pView, const Select *p, u8 m if( p->pWinDefn ) n++; #endif } - sqlite3TreeViewExprList(pView, p->pEList, (n--)>0, "result-set"); + if( p->pEList ){ + sqlite3TreeViewExprList(pView, p->pEList, n>0, "result-set"); + } + n--; #ifndef SQLITE_OMIT_WINDOWFUNC if( p->pWin ){ Window *pX; - pView = sqlite3TreeViewPush(pView, (n--)>0); + sqlite3TreeViewPush(&pView, (n--)>0); sqlite3TreeViewLine(pView, "window-functions"); for(pX=p->pWin; pX; pX=pX->pNextWin){ sqlite3TreeViewWinFunc(pView, pX, pX->pNextWin!=0); } - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } #endif - if( p->pSrc && p->pSrc->nSrc ){ - pView = sqlite3TreeViewPush(pView, (n--)>0); + if( p->pSrc && p->pSrc->nSrc && p->pSrc->nAlloc ){ + sqlite3TreeViewPush(&pView, (n--)>0); sqlite3TreeViewLine(pView, "FROM"); sqlite3TreeViewSrcList(pView, p->pSrc); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } if( p->pWhere ){ sqlite3TreeViewItem(pView, "WHERE", (n--)>0); sqlite3TreeViewExpr(pView, p->pWhere, 0); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } if( p->pGroupBy ){ sqlite3TreeViewExprList(pView, p->pGroupBy, (n--)>0, "GROUPBY"); @@ -28744,7 +34357,7 @@ SQLITE_PRIVATE void sqlite3TreeViewSelect(TreeView *pView, const Select *p, u8 m if( p->pHaving ){ sqlite3TreeViewItem(pView, "HAVING", (n--)>0); sqlite3TreeViewExpr(pView, p->pHaving, 0); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } #ifndef SQLITE_OMIT_WINDOWFUNC if( p->pWinDefn ){ @@ -28753,7 +34366,7 @@ SQLITE_PRIVATE void sqlite3TreeViewSelect(TreeView *pView, const Select *p, u8 m for(pX=p->pWinDefn; pX; pX=pX->pNextWin){ sqlite3TreeViewWindow(pView, pX, pX->pNextWin!=0); } - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } #endif if( p->pOrderBy ){ @@ -28763,11 +34376,11 @@ SQLITE_PRIVATE void sqlite3TreeViewSelect(TreeView *pView, const Select *p, u8 m sqlite3TreeViewItem(pView, "LIMIT", (n--)>0); sqlite3TreeViewExpr(pView, p->pLimit->pLeft, p->pLimit->pRight!=0); if( p->pLimit->pRight ){ - sqlite3TreeViewItem(pView, "OFFSET", (n--)>0); + sqlite3TreeViewItem(pView, "OFFSET", 0); sqlite3TreeViewExpr(pView, p->pLimit->pRight, 0); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } if( p->pPrior ){ const char *zOp = "UNION"; @@ -28780,7 +34393,7 @@ SQLITE_PRIVATE void sqlite3TreeViewSelect(TreeView *pView, const Select *p, u8 m } p = p->pPrior; }while( p!=0 ); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } #ifndef SQLITE_OMIT_WINDOWFUNC @@ -28796,24 +34409,24 @@ SQLITE_PRIVATE void sqlite3TreeViewBound( switch( eBound ){ case TK_UNBOUNDED: { sqlite3TreeViewItem(pView, "UNBOUNDED", moreToFollow); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); break; } case TK_CURRENT: { sqlite3TreeViewItem(pView, "CURRENT", moreToFollow); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); break; } case TK_PRECEDING: { sqlite3TreeViewItem(pView, "PRECEDING", moreToFollow); sqlite3TreeViewExpr(pView, pExpr, 0); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); break; } case TK_FOLLOWING: { sqlite3TreeViewItem(pView, "FOLLOWING", moreToFollow); sqlite3TreeViewExpr(pView, pExpr, 0); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); break; } } @@ -28826,12 +34439,14 @@ SQLITE_PRIVATE void sqlite3TreeViewBound( */ SQLITE_PRIVATE void sqlite3TreeViewWindow(TreeView *pView, const Window *pWin, u8 more){ int nElement = 0; + if( pWin==0 ) return; if( pWin->pFilter ){ sqlite3TreeViewItem(pView, "FILTER", 1); sqlite3TreeViewExpr(pView, pWin->pFilter, 0); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); + if( pWin->eFrmType==TK_FILTER ) return; } - pView = sqlite3TreeViewPush(pView, more); + sqlite3TreeViewPush(&pView, more); if( pWin->zName ){ sqlite3TreeViewLine(pView, "OVER %s (%p)", pWin->zName, pWin); }else{ @@ -28839,12 +34454,12 @@ SQLITE_PRIVATE void sqlite3TreeViewWindow(TreeView *pView, const Window *pWin, u } if( pWin->zBase ) nElement++; if( pWin->pOrderBy ) nElement++; - if( pWin->eFrmType ) nElement++; + if( pWin->eFrmType!=0 && pWin->eFrmType!=TK_FILTER ) nElement++; if( pWin->eExclude ) nElement++; if( pWin->zBase ){ - sqlite3TreeViewPush(pView, (--nElement)>0); + sqlite3TreeViewPush(&pView, (--nElement)>0); sqlite3TreeViewLine(pView, "window: %s", pWin->zBase); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } if( pWin->pPartition ){ sqlite3TreeViewExprList(pView, pWin->pPartition, nElement>0,"PARTITION-BY"); @@ -28852,7 +34467,7 @@ SQLITE_PRIVATE void sqlite3TreeViewWindow(TreeView *pView, const Window *pWin, u if( pWin->pOrderBy ){ sqlite3TreeViewExprList(pView, pWin->pOrderBy, (--nElement)>0, "ORDER-BY"); } - if( pWin->eFrmType ){ + if( pWin->eFrmType!=0 && pWin->eFrmType!=TK_FILTER ){ char zBuf[30]; const char *zFrmType = "ROWS"; if( pWin->eFrmType==TK_RANGE ) zFrmType = "RANGE"; @@ -28862,7 +34477,7 @@ SQLITE_PRIVATE void sqlite3TreeViewWindow(TreeView *pView, const Window *pWin, u sqlite3TreeViewItem(pView, zBuf, (--nElement)>0); sqlite3TreeViewBound(pView, pWin->eStart, pWin->pStart, 1); sqlite3TreeViewBound(pView, pWin->eEnd, pWin->pEnd, 0); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } if( pWin->eExclude ){ char zBuf[30]; @@ -28877,11 +34492,11 @@ SQLITE_PRIVATE void sqlite3TreeViewWindow(TreeView *pView, const Window *pWin, u zExclude = zBuf; break; } - sqlite3TreeViewPush(pView, 0); + sqlite3TreeViewPush(&pView, 0); sqlite3TreeViewLine(pView, "EXCLUDE %s", zExclude); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } #endif /* SQLITE_OMIT_WINDOWFUNC */ @@ -28890,11 +34505,12 @@ SQLITE_PRIVATE void sqlite3TreeViewWindow(TreeView *pView, const Window *pWin, u ** Generate a human-readable explanation for a Window Function object */ SQLITE_PRIVATE void sqlite3TreeViewWinFunc(TreeView *pView, const Window *pWin, u8 more){ - pView = sqlite3TreeViewPush(pView, more); + if( pWin==0 ) return; + sqlite3TreeViewPush(&pView, more); sqlite3TreeViewLine(pView, "WINFUNC %s(%d)", - pWin->pFunc->zName, pWin->pFunc->nArg); + pWin->pWFunc->zName, pWin->pWFunc->nArg); sqlite3TreeViewWindow(pView, pWin, 0); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } #endif /* SQLITE_OMIT_WINDOWFUNC */ @@ -28904,20 +34520,34 @@ SQLITE_PRIVATE void sqlite3TreeViewWinFunc(TreeView *pView, const Window *pWin, SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 moreToFollow){ const char *zBinOp = 0; /* Binary operator */ const char *zUniOp = 0; /* Unary operator */ - char zFlgs[60]; - pView = sqlite3TreeViewPush(pView, moreToFollow); + char zFlgs[200]; + sqlite3TreeViewPush(&pView, moreToFollow); if( pExpr==0 ){ sqlite3TreeViewLine(pView, "nil"); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); return; } - if( pExpr->flags ){ - if( ExprHasProperty(pExpr, EP_FromJoin) ){ - sqlite3_snprintf(sizeof(zFlgs),zFlgs," flags=0x%x iRJT=%d", - pExpr->flags, pExpr->iRightJoinTable); - }else{ - sqlite3_snprintf(sizeof(zFlgs),zFlgs," flags=0x%x",pExpr->flags); + if( pExpr->flags || pExpr->affExpr || pExpr->vvaFlags || pExpr->pAggInfo ){ + StrAccum x; + sqlite3StrAccumInit(&x, 0, zFlgs, sizeof(zFlgs), 0); + sqlite3_str_appendf(&x, " fg.af=%x.%c", + pExpr->flags, pExpr->affExpr ? pExpr->affExpr : 'n'); + if( ExprHasProperty(pExpr, EP_OuterON) ){ + sqlite3_str_appendf(&x, " outer.iJoin=%d", pExpr->w.iJoin); + } + if( ExprHasProperty(pExpr, EP_InnerON) ){ + sqlite3_str_appendf(&x, " inner.iJoin=%d", pExpr->w.iJoin); + } + if( ExprHasProperty(pExpr, EP_FromDDL) ){ + sqlite3_str_appendf(&x, " DDL"); + } + if( ExprHasVVAProperty(pExpr, EP_Immutable) ){ + sqlite3_str_appendf(&x, " IMMUTABLE"); } + if( pExpr->pAggInfo!=0 ){ + sqlite3_str_appendf(&x, " agg-column[%d]", pExpr->iAgg); + } + sqlite3StrAccumFinish(&x); }else{ zFlgs[0] = 0; } @@ -28930,10 +34560,19 @@ SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 m case TK_COLUMN: { if( pExpr->iTable<0 ){ /* This only happens when coding check constraints */ - sqlite3TreeViewLine(pView, "COLUMN(%d)%s", pExpr->iColumn, zFlgs); + char zOp2[16]; + if( pExpr->op2 ){ + sqlite3_snprintf(sizeof(zOp2),zOp2," op2=0x%02x",pExpr->op2); + }else{ + zOp2[0] = 0; + } + sqlite3TreeViewLine(pView, "COLUMN(%d)%s%s", + pExpr->iColumn, zFlgs, zOp2); }else{ - sqlite3TreeViewLine(pView, "{%d:%d}%s", - pExpr->iTable, pExpr->iColumn, zFlgs); + assert( ExprUseYTab(pExpr) ); + sqlite3TreeViewLine(pView, "{%d:%d} pTab=%p%s", + pExpr->iTable, pExpr->iColumn, + pExpr->y.pTab, zFlgs); } if( ExprHasProperty(pExpr, EP_FixedCol) ){ sqlite3TreeViewExpr(pView, pExpr->pLeft, 0); @@ -28950,11 +34589,13 @@ SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 m } #ifndef SQLITE_OMIT_FLOATING_POINT case TK_FLOAT: { + assert( !ExprHasProperty(pExpr, EP_IntValue) ); sqlite3TreeViewLine(pView,"%s", pExpr->u.zToken); break; } #endif case TK_STRING: { + assert( !ExprHasProperty(pExpr, EP_IntValue) ); sqlite3TreeViewLine(pView,"%Q", pExpr->u.zToken); break; } @@ -28963,17 +34604,19 @@ SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 m break; } case TK_TRUEFALSE: { - sqlite3TreeViewLine(pView, - sqlite3ExprTruthValue(pExpr) ? "TRUE" : "FALSE"); + sqlite3TreeViewLine(pView,"%s%s", + sqlite3ExprTruthValue(pExpr) ? "TRUE" : "FALSE", zFlgs); break; } #ifndef SQLITE_OMIT_BLOB_LITERAL case TK_BLOB: { + assert( !ExprHasProperty(pExpr, EP_IntValue) ); sqlite3TreeViewLine(pView,"%s", pExpr->u.zToken); break; } #endif case TK_VARIABLE: { + assert( !ExprHasProperty(pExpr, EP_IntValue) ); sqlite3TreeViewLine(pView,"VARIABLE(%s,%d)", pExpr->u.zToken, pExpr->iColumn); break; @@ -28983,12 +34626,14 @@ SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 m break; } case TK_ID: { + assert( !ExprHasProperty(pExpr, EP_IntValue) ); sqlite3TreeViewLine(pView,"ID \"%w\"", pExpr->u.zToken); break; } #ifndef SQLITE_OMIT_CAST case TK_CAST: { /* Expressions of the form: CAST(pLeft AS token) */ + assert( !ExprHasProperty(pExpr, EP_IntValue) ); sqlite3TreeViewLine(pView,"CAST %Q", pExpr->u.zToken); sqlite3TreeViewExpr(pView, pExpr->pLeft, 0); break; @@ -29015,6 +34660,7 @@ SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 m case TK_RSHIFT: zBinOp = "RSHIFT"; break; case TK_CONCAT: zBinOp = "CONCAT"; break; case TK_DOT: zBinOp = "DOT"; break; + case TK_LIMIT: zBinOp = "LIMIT"; break; case TK_UMINUS: zUniOp = "UMINUS"; break; case TK_UPLUS: zUniOp = "UPLUS"; break; @@ -29030,20 +34676,30 @@ SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 m }; assert( pExpr->op2==TK_IS || pExpr->op2==TK_ISNOT ); assert( pExpr->pRight ); - assert( pExpr->pRight->op==TK_TRUEFALSE ); + assert( sqlite3ExprSkipCollateAndLikely(pExpr->pRight)->op + == TK_TRUEFALSE ); x = (pExpr->op2==TK_ISNOT)*2 + sqlite3ExprTruthValue(pExpr->pRight); zUniOp = azOp[x]; break; } case TK_SPAN: { + assert( !ExprHasProperty(pExpr, EP_IntValue) ); sqlite3TreeViewLine(pView, "SPAN %Q", pExpr->u.zToken); sqlite3TreeViewExpr(pView, pExpr->pLeft, 0); break; } case TK_COLLATE: { - sqlite3TreeViewLine(pView, "COLLATE %Q", pExpr->u.zToken); + /* COLLATE operators without the EP_Collate flag are intended to + ** emulate collation associated with a table column. These show + ** up in the treeview output as "SOFT-COLLATE". Explicit COLLATE + ** operators that appear in the original SQL always have the + ** EP_Collate bit set and appear in treeview output as just "COLLATE" */ + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + sqlite3TreeViewLine(pView, "%sCOLLATE %Q%s", + !ExprHasProperty(pExpr, EP_Collate) ? "SOFT-" : "", + pExpr->u.zToken, zFlgs); sqlite3TreeViewExpr(pView, pExpr->pLeft, 0); break; } @@ -29056,21 +34712,42 @@ SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 m pFarg = 0; pWin = 0; }else{ + assert( ExprUseXList(pExpr) ); pFarg = pExpr->x.pList; #ifndef SQLITE_OMIT_WINDOWFUNC - pWin = pExpr->y.pWin; + pWin = IsWindowFunc(pExpr) ? pExpr->y.pWin : 0; #else pWin = 0; -#endif +#endif } + assert( !ExprHasProperty(pExpr, EP_IntValue) ); if( pExpr->op==TK_AGG_FUNCTION ){ - sqlite3TreeViewLine(pView, "AGG_FUNCTION%d %Q", - pExpr->op2, pExpr->u.zToken); + sqlite3TreeViewLine(pView, "AGG_FUNCTION%d %Q%s agg=%d[%d]/%p", + pExpr->op2, pExpr->u.zToken, zFlgs, + pExpr->pAggInfo ? pExpr->pAggInfo->selId : 0, + pExpr->iAgg, pExpr->pAggInfo); + }else if( pExpr->op2!=0 ){ + const char *zOp2; + char zBuf[8]; + sqlite3_snprintf(sizeof(zBuf),zBuf,"0x%02x",pExpr->op2); + zOp2 = zBuf; + if( pExpr->op2==NC_IsCheck ) zOp2 = "NC_IsCheck"; + if( pExpr->op2==NC_IdxExpr ) zOp2 = "NC_IdxExpr"; + if( pExpr->op2==NC_PartIdx ) zOp2 = "NC_PartIdx"; + if( pExpr->op2==NC_GenCol ) zOp2 = "NC_GenCol"; + sqlite3TreeViewLine(pView, "FUNCTION %Q%s op2=%s", + pExpr->u.zToken, zFlgs, zOp2); }else{ - sqlite3TreeViewLine(pView, "FUNCTION %Q", pExpr->u.zToken); + sqlite3TreeViewLine(pView, "FUNCTION %Q%s", pExpr->u.zToken, zFlgs); } if( pFarg ){ - sqlite3TreeViewExprList(pView, pFarg, pWin!=0, 0); + sqlite3TreeViewExprList(pView, pFarg, pWin!=0 || pExpr->pLeft, 0); + if( pExpr->pLeft ){ + Expr *pOB = pExpr->pLeft; + assert( pOB->op==TK_ORDER ); + assert( ExprUseXList(pOB) ); + sqlite3TreeViewExprList(pView, pOB->x.pList, pWin!=0, "ORDERBY"); + } } #ifndef SQLITE_OMIT_WINDOWFUNC if( pWin ){ @@ -29079,21 +34756,37 @@ SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 m #endif break; } + case TK_ORDER: { + sqlite3TreeViewExprList(pView, pExpr->x.pList, 0, "ORDERBY"); + break; + } #ifndef SQLITE_OMIT_SUBQUERY case TK_EXISTS: { + assert( ExprUseXSelect(pExpr) ); sqlite3TreeViewLine(pView, "EXISTS-expr flags=0x%x", pExpr->flags); sqlite3TreeViewSelect(pView, pExpr->x.pSelect, 0); break; } case TK_SELECT: { - sqlite3TreeViewLine(pView, "SELECT-expr flags=0x%x", pExpr->flags); + assert( ExprUseXSelect(pExpr) ); + sqlite3TreeViewLine(pView, "subquery-expr flags=0x%x", pExpr->flags); sqlite3TreeViewSelect(pView, pExpr->x.pSelect, 0); break; } case TK_IN: { - sqlite3TreeViewLine(pView, "IN flags=0x%x", pExpr->flags); + sqlite3_str *pStr = sqlite3_str_new(0); + char *z; + sqlite3_str_appendf(pStr, "IN flags=0x%x", pExpr->flags); + if( pExpr->iTable ) sqlite3_str_appendf(pStr, " iTable=%d",pExpr->iTable); + if( ExprHasProperty(pExpr, EP_Subrtn) ){ + sqlite3_str_appendf(pStr, " subrtn(%d,%d)", + pExpr->y.sub.regReturn, pExpr->y.sub.iAddr); + } + z = sqlite3_str_finish(pStr); + sqlite3TreeViewLine(pView, z); + sqlite3_free(z); sqlite3TreeViewExpr(pView, pExpr->pLeft, 1); - if( ExprHasProperty(pExpr, EP_xIsSelect) ){ + if( ExprUseXSelect(pExpr) ){ sqlite3TreeViewSelect(pView, pExpr->x.pSelect, 0); }else{ sqlite3TreeViewExprList(pView, pExpr->x.pList, 0, 0); @@ -29114,10 +34807,13 @@ SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 m ** Z is stored in pExpr->pList->a[1].pExpr. */ case TK_BETWEEN: { - Expr *pX = pExpr->pLeft; - Expr *pY = pExpr->x.pList->a[0].pExpr; - Expr *pZ = pExpr->x.pList->a[1].pExpr; - sqlite3TreeViewLine(pView, "BETWEEN"); + const Expr *pX, *pY, *pZ; + pX = pExpr->pLeft; + assert( ExprUseXList(pExpr) ); + assert( pExpr->x.pList->nExpr==2 ); + pY = pExpr->x.pList->a[0].pExpr; + pZ = pExpr->x.pList->a[1].pExpr; + sqlite3TreeViewLine(pView, "BETWEEN%s", zFlgs); sqlite3TreeViewExpr(pView, pX, 1); sqlite3TreeViewExpr(pView, pY, 1); sqlite3TreeViewExpr(pView, pZ, 0); @@ -29131,26 +34827,29 @@ SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 m ** is set to the column of the pseudo-table to read, or to -1 to ** read the rowid field. */ - sqlite3TreeViewLine(pView, "%s(%d)", + sqlite3TreeViewLine(pView, "%s(%d)", pExpr->iTable ? "NEW" : "OLD", pExpr->iColumn); break; } case TK_CASE: { sqlite3TreeViewLine(pView, "CASE"); sqlite3TreeViewExpr(pView, pExpr->pLeft, 1); + assert( ExprUseXList(pExpr) ); sqlite3TreeViewExprList(pView, pExpr->x.pList, 0, 0); break; } #ifndef SQLITE_OMIT_TRIGGER case TK_RAISE: { const char *zType = "unk"; - switch( pExpr->affinity ){ + switch( pExpr->affExpr ){ case OE_Rollback: zType = "rollback"; break; case OE_Abort: zType = "abort"; break; case OE_Fail: zType = "fail"; break; case OE_Ignore: zType = "ignore"; break; } - sqlite3TreeViewLine(pView, "RAISE %s(%Q)", zType, pExpr->u.zToken); + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + sqlite3TreeViewLine(pView, "RAISE %s", zType); + sqlite3TreeViewExpr(pView, pExpr->pLeft, 0); break; } #endif @@ -29161,11 +34860,17 @@ SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 m break; } case TK_VECTOR: { - sqlite3TreeViewBareExprList(pView, pExpr->x.pList, "VECTOR"); + char *z = sqlite3_mprintf("VECTOR%s",zFlgs); + assert( ExprUseXList(pExpr) ); + sqlite3TreeViewBareExprList(pView, pExpr->x.pList, z); + sqlite3_free(z); break; } case TK_SELECT_COLUMN: { - sqlite3TreeViewLine(pView, "SELECT-COLUMN %d", pExpr->iColumn); + sqlite3TreeViewLine(pView, "SELECT-COLUMN %d of [0..%d]%s", + pExpr->iColumn, pExpr->iTable-1, + pExpr->pRight==pExpr->pLeft ? " (SELECT-owner)" : ""); + assert( ExprUseXSelect(pExpr->pLeft) ); sqlite3TreeViewSelect(pView, pExpr->pLeft->x.pSelect, 0); break; } @@ -29174,6 +34879,23 @@ SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 m sqlite3TreeViewExpr(pView, pExpr->pLeft, 0); break; } + case TK_ERROR: { + Expr tmp; + sqlite3TreeViewLine(pView, "ERROR"); + tmp = *pExpr; + tmp.op = pExpr->op2; + sqlite3TreeViewExpr(pView, &tmp, 0); + break; + } + case TK_ROW: { + if( pExpr->iColumn<=0 ){ + sqlite3TreeViewLine(pView, "First FROM table rowid"); + }else{ + sqlite3TreeViewLine(pView, "First FROM table column %d", + pExpr->iColumn-1); + } + break; + } default: { sqlite3TreeViewLine(pView, "op=%d", pExpr->op); break; @@ -29185,9 +34907,9 @@ SQLITE_PRIVATE void sqlite3TreeViewExpr(TreeView *pView, const Expr *pExpr, u8 m sqlite3TreeViewExpr(pView, pExpr->pRight, 0); }else if( zUniOp ){ sqlite3TreeViewLine(pView, "%s%s", zUniOp, zFlgs); - sqlite3TreeViewExpr(pView, pExpr->pLeft, 0); + sqlite3TreeViewExpr(pView, pExpr->pLeft, 0); } - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); } @@ -29207,24 +34929,43 @@ SQLITE_PRIVATE void sqlite3TreeViewBareExprList( sqlite3TreeViewLine(pView, "%s", zLabel); for(i=0; inExpr; i++){ int j = pList->a[i].u.x.iOrderByCol; - char *zName = pList->a[i].zName; + u8 sortFlags = pList->a[i].fg.sortFlags; + char *zName = pList->a[i].zEName; int moreToFollow = inExpr - 1; - if( j || zName ){ - sqlite3TreeViewPush(pView, moreToFollow); + if( j || zName || sortFlags ){ + sqlite3TreeViewPush(&pView, moreToFollow); moreToFollow = 0; sqlite3TreeViewLine(pView, 0); if( zName ){ - fprintf(stdout, "AS %s ", zName); + switch( pList->a[i].fg.eEName ){ + default: + fprintf(stdout, "AS %s ", zName); + break; + case ENAME_TAB: + fprintf(stdout, "TABLE-ALIAS-NAME(\"%s\") ", zName); + if( pList->a[i].fg.bUsed ) fprintf(stdout, "(used) "); + if( pList->a[i].fg.bUsingTerm ) fprintf(stdout, "(USING-term) "); + if( pList->a[i].fg.bNoExpand ) fprintf(stdout, "(NoExpand) "); + break; + case ENAME_SPAN: + fprintf(stdout, "SPAN(\"%s\") ", zName); + break; + } } if( j ){ - fprintf(stdout, "iOrderByCol=%d", j); + fprintf(stdout, "iOrderByCol=%d ", j); + } + if( sortFlags & KEYINFO_ORDER_DESC ){ + fprintf(stdout, "DESC "); + }else if( sortFlags & KEYINFO_ORDER_BIGNULL ){ + fprintf(stdout, "NULLS-LAST"); } fprintf(stdout, "\n"); fflush(stdout); } sqlite3TreeViewExpr(pView, pList->a[i].pExpr, moreToFollow); - if( j || zName ){ - sqlite3TreeViewPop(pView); + if( j || zName || sortFlags ){ + sqlite3TreeViewPop(&pView); } } } @@ -29235,11 +34976,374 @@ SQLITE_PRIVATE void sqlite3TreeViewExprList( u8 moreToFollow, const char *zLabel ){ - pView = sqlite3TreeViewPush(pView, moreToFollow); + sqlite3TreeViewPush(&pView, moreToFollow); sqlite3TreeViewBareExprList(pView, pList, zLabel); - sqlite3TreeViewPop(pView); + sqlite3TreeViewPop(&pView); +} + +/* +** Generate a human-readable explanation of an id-list. +*/ +SQLITE_PRIVATE void sqlite3TreeViewBareIdList( + TreeView *pView, + const IdList *pList, + const char *zLabel +){ + if( zLabel==0 || zLabel[0]==0 ) zLabel = "LIST"; + if( pList==0 ){ + sqlite3TreeViewLine(pView, "%s (empty)", zLabel); + }else{ + int i; + sqlite3TreeViewLine(pView, "%s", zLabel); + for(i=0; inId; i++){ + char *zName = pList->a[i].zName; + int moreToFollow = inId - 1; + if( zName==0 ) zName = "(null)"; + sqlite3TreeViewPush(&pView, moreToFollow); + sqlite3TreeViewLine(pView, 0); + fprintf(stdout, "%s\n", zName); + sqlite3TreeViewPop(&pView); + } + } +} +SQLITE_PRIVATE void sqlite3TreeViewIdList( + TreeView *pView, + const IdList *pList, + u8 moreToFollow, + const char *zLabel +){ + sqlite3TreeViewPush(&pView, moreToFollow); + sqlite3TreeViewBareIdList(pView, pList, zLabel); + sqlite3TreeViewPop(&pView); } +/* +** Generate a human-readable explanation of a list of Upsert objects +*/ +SQLITE_PRIVATE void sqlite3TreeViewUpsert( + TreeView *pView, + const Upsert *pUpsert, + u8 moreToFollow +){ + if( pUpsert==0 ) return; + sqlite3TreeViewPush(&pView, moreToFollow); + while( pUpsert ){ + int n; + sqlite3TreeViewPush(&pView, pUpsert->pNextUpsert!=0 || moreToFollow); + sqlite3TreeViewLine(pView, "ON CONFLICT DO %s", + pUpsert->isDoUpdate ? "UPDATE" : "NOTHING"); + n = (pUpsert->pUpsertSet!=0) + (pUpsert->pUpsertWhere!=0); + sqlite3TreeViewExprList(pView, pUpsert->pUpsertTarget, (n--)>0, "TARGET"); + sqlite3TreeViewExprList(pView, pUpsert->pUpsertSet, (n--)>0, "SET"); + if( pUpsert->pUpsertWhere ){ + sqlite3TreeViewItem(pView, "WHERE", (n--)>0); + sqlite3TreeViewExpr(pView, pUpsert->pUpsertWhere, 0); + sqlite3TreeViewPop(&pView); + } + sqlite3TreeViewPop(&pView); + pUpsert = pUpsert->pNextUpsert; + } + sqlite3TreeViewPop(&pView); +} + +#if TREETRACE_ENABLED +/* +** Generate a human-readable diagram of the data structure that go +** into generating an DELETE statement. +*/ +SQLITE_PRIVATE void sqlite3TreeViewDelete( + const With *pWith, + const SrcList *pTabList, + const Expr *pWhere, + const ExprList *pOrderBy, + const Expr *pLimit, + const Trigger *pTrigger +){ + int n = 0; + TreeView *pView = 0; + sqlite3TreeViewPush(&pView, 0); + sqlite3TreeViewLine(pView, "DELETE"); + if( pWith ) n++; + if( pTabList ) n++; + if( pWhere ) n++; + if( pOrderBy ) n++; + if( pLimit ) n++; + if( pTrigger ) n++; + if( pWith ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewWith(pView, pWith, 0); + sqlite3TreeViewPop(&pView); + } + if( pTabList ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewLine(pView, "FROM"); + sqlite3TreeViewSrcList(pView, pTabList); + sqlite3TreeViewPop(&pView); + } + if( pWhere ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewLine(pView, "WHERE"); + sqlite3TreeViewExpr(pView, pWhere, 0); + sqlite3TreeViewPop(&pView); + } + if( pOrderBy ){ + sqlite3TreeViewExprList(pView, pOrderBy, (--n)>0, "ORDER-BY"); + } + if( pLimit ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewLine(pView, "LIMIT"); + sqlite3TreeViewExpr(pView, pLimit, 0); + sqlite3TreeViewPop(&pView); + } + if( pTrigger ){ + sqlite3TreeViewTrigger(pView, pTrigger, (--n)>0, 1); + } + sqlite3TreeViewPop(&pView); +} +#endif /* TREETRACE_ENABLED */ + +#if TREETRACE_ENABLED +/* +** Generate a human-readable diagram of the data structure that go +** into generating an INSERT statement. +*/ +SQLITE_PRIVATE void sqlite3TreeViewInsert( + const With *pWith, + const SrcList *pTabList, + const IdList *pColumnList, + const Select *pSelect, + const ExprList *pExprList, + int onError, + const Upsert *pUpsert, + const Trigger *pTrigger +){ + TreeView *pView = 0; + int n = 0; + const char *zLabel = "INSERT"; + switch( onError ){ + case OE_Replace: zLabel = "REPLACE"; break; + case OE_Ignore: zLabel = "INSERT OR IGNORE"; break; + case OE_Rollback: zLabel = "INSERT OR ROLLBACK"; break; + case OE_Abort: zLabel = "INSERT OR ABORT"; break; + case OE_Fail: zLabel = "INSERT OR FAIL"; break; + } + sqlite3TreeViewPush(&pView, 0); + sqlite3TreeViewLine(pView, zLabel); + if( pWith ) n++; + if( pTabList ) n++; + if( pColumnList ) n++; + if( pSelect ) n++; + if( pExprList ) n++; + if( pUpsert ) n++; + if( pTrigger ) n++; + if( pWith ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewWith(pView, pWith, 0); + sqlite3TreeViewPop(&pView); + } + if( pTabList ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewLine(pView, "INTO"); + sqlite3TreeViewSrcList(pView, pTabList); + sqlite3TreeViewPop(&pView); + } + if( pColumnList ){ + sqlite3TreeViewIdList(pView, pColumnList, (--n)>0, "COLUMNS"); + } + if( pSelect ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewLine(pView, "DATA-SOURCE"); + sqlite3TreeViewSelect(pView, pSelect, 0); + sqlite3TreeViewPop(&pView); + } + if( pExprList ){ + sqlite3TreeViewExprList(pView, pExprList, (--n)>0, "VALUES"); + } + if( pUpsert ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewLine(pView, "UPSERT"); + sqlite3TreeViewUpsert(pView, pUpsert, 0); + sqlite3TreeViewPop(&pView); + } + if( pTrigger ){ + sqlite3TreeViewTrigger(pView, pTrigger, (--n)>0, 1); + } + sqlite3TreeViewPop(&pView); +} +#endif /* TREETRACE_ENABLED */ + +#if TREETRACE_ENABLED +/* +** Generate a human-readable diagram of the data structure that go +** into generating an UPDATE statement. +*/ +SQLITE_PRIVATE void sqlite3TreeViewUpdate( + const With *pWith, + const SrcList *pTabList, + const ExprList *pChanges, + const Expr *pWhere, + int onError, + const ExprList *pOrderBy, + const Expr *pLimit, + const Upsert *pUpsert, + const Trigger *pTrigger +){ + int n = 0; + TreeView *pView = 0; + const char *zLabel = "UPDATE"; + switch( onError ){ + case OE_Replace: zLabel = "UPDATE OR REPLACE"; break; + case OE_Ignore: zLabel = "UPDATE OR IGNORE"; break; + case OE_Rollback: zLabel = "UPDATE OR ROLLBACK"; break; + case OE_Abort: zLabel = "UPDATE OR ABORT"; break; + case OE_Fail: zLabel = "UPDATE OR FAIL"; break; + } + sqlite3TreeViewPush(&pView, 0); + sqlite3TreeViewLine(pView, zLabel); + if( pWith ) n++; + if( pTabList ) n++; + if( pChanges ) n++; + if( pWhere ) n++; + if( pOrderBy ) n++; + if( pLimit ) n++; + if( pUpsert ) n++; + if( pTrigger ) n++; + if( pWith ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewWith(pView, pWith, 0); + sqlite3TreeViewPop(&pView); + } + if( pTabList ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewLine(pView, "FROM"); + sqlite3TreeViewSrcList(pView, pTabList); + sqlite3TreeViewPop(&pView); + } + if( pChanges ){ + sqlite3TreeViewExprList(pView, pChanges, (--n)>0, "SET"); + } + if( pWhere ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewLine(pView, "WHERE"); + sqlite3TreeViewExpr(pView, pWhere, 0); + sqlite3TreeViewPop(&pView); + } + if( pOrderBy ){ + sqlite3TreeViewExprList(pView, pOrderBy, (--n)>0, "ORDER-BY"); + } + if( pLimit ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewLine(pView, "LIMIT"); + sqlite3TreeViewExpr(pView, pLimit, 0); + sqlite3TreeViewPop(&pView); + } + if( pUpsert ){ + sqlite3TreeViewPush(&pView, (--n)>0); + sqlite3TreeViewLine(pView, "UPSERT"); + sqlite3TreeViewUpsert(pView, pUpsert, 0); + sqlite3TreeViewPop(&pView); + } + if( pTrigger ){ + sqlite3TreeViewTrigger(pView, pTrigger, (--n)>0, 1); + } + sqlite3TreeViewPop(&pView); +} +#endif /* TREETRACE_ENABLED */ + +#ifndef SQLITE_OMIT_TRIGGER +/* +** Show a human-readable graph of a TriggerStep +*/ +SQLITE_PRIVATE void sqlite3TreeViewTriggerStep( + TreeView *pView, + const TriggerStep *pStep, + u8 moreToFollow, + u8 showFullList +){ + int cnt = 0; + if( pStep==0 ) return; + sqlite3TreeViewPush(&pView, + moreToFollow || (showFullList && pStep->pNext!=0)); + do{ + if( cnt++ && pStep->pNext==0 ){ + sqlite3TreeViewPop(&pView); + sqlite3TreeViewPush(&pView, 0); + } + sqlite3TreeViewLine(pView, "%s", pStep->zSpan ? pStep->zSpan : "RETURNING"); + }while( showFullList && (pStep = pStep->pNext)!=0 ); + sqlite3TreeViewPop(&pView); +} + +/* +** Show a human-readable graph of a Trigger +*/ +SQLITE_PRIVATE void sqlite3TreeViewTrigger( + TreeView *pView, + const Trigger *pTrigger, + u8 moreToFollow, + u8 showFullList +){ + int cnt = 0; + if( pTrigger==0 ) return; + sqlite3TreeViewPush(&pView, + moreToFollow || (showFullList && pTrigger->pNext!=0)); + do{ + if( cnt++ && pTrigger->pNext==0 ){ + sqlite3TreeViewPop(&pView); + sqlite3TreeViewPush(&pView, 0); + } + sqlite3TreeViewLine(pView, "TRIGGER %s", pTrigger->zName); + sqlite3TreeViewPush(&pView, 0); + sqlite3TreeViewTriggerStep(pView, pTrigger->step_list, 0, 1); + sqlite3TreeViewPop(&pView); + }while( showFullList && (pTrigger = pTrigger->pNext)!=0 ); + sqlite3TreeViewPop(&pView); +} +#endif /* SQLITE_OMIT_TRIGGER */ + + +/* +** These simplified versions of the tree-view routines omit unnecessary +** parameters. These variants are intended to be used from a symbolic +** debugger, such as "gdb", during interactive debugging sessions. +** +** This routines are given external linkage so that they will always be +** accessible to the debugging, and to avoid warnings about unused +** functions. But these routines only exist in debugging builds, so they +** do not contaminate the interface. +** +** See Also: +** +** sqlite3ShowWhereTerm() in where.c +*/ +SQLITE_PRIVATE void sqlite3ShowExpr(const Expr *p){ sqlite3TreeViewExpr(0,p,0); } +SQLITE_PRIVATE void sqlite3ShowExprList(const ExprList *p){ sqlite3TreeViewExprList(0,p,0,0);} +SQLITE_PRIVATE void sqlite3ShowIdList(const IdList *p){ sqlite3TreeViewIdList(0,p,0,0); } +SQLITE_PRIVATE void sqlite3ShowSrcList(const SrcList *p){ + TreeView *pView = 0; + sqlite3TreeViewPush(&pView, 0); + sqlite3TreeViewLine(pView, "SRCLIST"); + sqlite3TreeViewSrcList(pView,p); + sqlite3TreeViewPop(&pView); +} +SQLITE_PRIVATE void sqlite3ShowSelect(const Select *p){ sqlite3TreeViewSelect(0,p,0); } +SQLITE_PRIVATE void sqlite3ShowWith(const With *p){ sqlite3TreeViewWith(0,p,0); } +SQLITE_PRIVATE void sqlite3ShowUpsert(const Upsert *p){ sqlite3TreeViewUpsert(0,p,0); } +#ifndef SQLITE_OMIT_TRIGGER +SQLITE_PRIVATE void sqlite3ShowTriggerStep(const TriggerStep *p){ + sqlite3TreeViewTriggerStep(0,p,0,0); +} +SQLITE_PRIVATE void sqlite3ShowTriggerStepList(const TriggerStep *p){ + sqlite3TreeViewTriggerStep(0,p,0,1); +} +SQLITE_PRIVATE void sqlite3ShowTrigger(const Trigger *p){ sqlite3TreeViewTrigger(0,p,0,0); } +SQLITE_PRIVATE void sqlite3ShowTriggerList(const Trigger *p){ sqlite3TreeViewTrigger(0,p,0,1);} +#endif +#ifndef SQLITE_OMIT_WINDOWFUNC +SQLITE_PRIVATE void sqlite3ShowWindow(const Window *p){ sqlite3TreeViewWindow(0,p,0); } +SQLITE_PRIVATE void sqlite3ShowWinFunc(const Window *p){ sqlite3TreeViewWinFunc(0,p,0); } +#endif + #endif /* SQLITE_DEBUG */ /************** End of treeview.c ********************************************/ @@ -29268,16 +35372,41 @@ SQLITE_PRIVATE void sqlite3TreeViewExprList( ** This structure is the current state of the generator. */ static SQLITE_WSD struct sqlite3PrngType { - unsigned char isInit; /* True if initialized */ - unsigned char i, j; /* State variables */ - unsigned char s[256]; /* State variables */ + u32 s[16]; /* 64 bytes of chacha20 state */ + u8 out[64]; /* Output bytes */ + u8 n; /* Output bytes remaining */ } sqlite3Prng; + +/* The RFC-7539 ChaCha20 block function +*/ +#define ROTL(a,b) (((a) << (b)) | ((a) >> (32 - (b)))) +#define QR(a, b, c, d) ( \ + a += b, d ^= a, d = ROTL(d,16), \ + c += d, b ^= c, b = ROTL(b,12), \ + a += b, d ^= a, d = ROTL(d, 8), \ + c += d, b ^= c, b = ROTL(b, 7)) +static void chacha_block(u32 *out, const u32 *in){ + int i; + u32 x[16]; + memcpy(x, in, 64); + for(i=0; i<10; i++){ + QR(x[0], x[4], x[ 8], x[12]); + QR(x[1], x[5], x[ 9], x[13]); + QR(x[2], x[6], x[10], x[14]); + QR(x[3], x[7], x[11], x[15]); + QR(x[0], x[5], x[10], x[15]); + QR(x[1], x[6], x[11], x[12]); + QR(x[2], x[7], x[ 8], x[13]); + QR(x[3], x[4], x[ 9], x[14]); + } + for(i=0; i<16; i++) out[i] = x[i]+in[i]; +} + /* ** Return N random bytes. */ SQLITE_API void sqlite3_randomness(int N, void *pBuf){ - unsigned char t; unsigned char *zBuf = pBuf; /* The "wsdPrng" macro will resolve to the pseudo-random number generator @@ -29307,48 +35436,46 @@ SQLITE_API void sqlite3_randomness(int N, void *pBuf){ sqlite3_mutex_enter(mutex); if( N<=0 || pBuf==0 ){ - wsdPrng.isInit = 0; + wsdPrng.s[0] = 0; sqlite3_mutex_leave(mutex); return; } /* Initialize the state of the random number generator once, - ** the first time this routine is called. The seed value does - ** not need to contain a lot of randomness since we are not - ** trying to do secure encryption or anything like that... - ** - ** Nothing in this file or anywhere else in SQLite does any kind of - ** encryption. The RC4 algorithm is being used as a PRNG (pseudo-random - ** number generator) not as an encryption device. + ** the first time this routine is called. */ - if( !wsdPrng.isInit ){ - int i; - char k[256]; - wsdPrng.j = 0; - wsdPrng.i = 0; - sqlite3OsRandomness(sqlite3_vfs_find(0), 256, k); - for(i=0; i<256; i++){ - wsdPrng.s[i] = (u8)i; - } - for(i=0; i<256; i++){ - wsdPrng.j += wsdPrng.s[i] + k[i]; - t = wsdPrng.s[wsdPrng.j]; - wsdPrng.s[wsdPrng.j] = wsdPrng.s[i]; - wsdPrng.s[i] = t; + if( wsdPrng.s[0]==0 ){ + sqlite3_vfs *pVfs = sqlite3_vfs_find(0); + static const u32 chacha20_init[] = { + 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574 + }; + memcpy(&wsdPrng.s[0], chacha20_init, 16); + if( NEVER(pVfs==0) ){ + memset(&wsdPrng.s[4], 0, 44); + }else{ + sqlite3OsRandomness(pVfs, 44, (char*)&wsdPrng.s[4]); } - wsdPrng.isInit = 1; + wsdPrng.s[15] = wsdPrng.s[12]; + wsdPrng.s[12] = 0; + wsdPrng.n = 0; } assert( N>0 ); - do{ - wsdPrng.i++; - t = wsdPrng.s[wsdPrng.i]; - wsdPrng.j += t; - wsdPrng.s[wsdPrng.i] = wsdPrng.s[wsdPrng.j]; - wsdPrng.s[wsdPrng.j] = t; - t += wsdPrng.s[wsdPrng.i]; - *(zBuf++) = wsdPrng.s[t]; - }while( --N ); + while( 1 /* exit by break */ ){ + if( N<=wsdPrng.n ){ + memcpy(zBuf, &wsdPrng.out[wsdPrng.n-N], N); + wsdPrng.n -= N; + break; + } + if( wsdPrng.n>0 ){ + memcpy(zBuf, wsdPrng.out, wsdPrng.n); + N -= wsdPrng.n; + zBuf += wsdPrng.n; + } + wsdPrng.s[12]++; + chacha_block((u32*)wsdPrng.out, wsdPrng.s); + wsdPrng.n = 64; + } sqlite3_mutex_leave(mutex); } @@ -29450,13 +35577,13 @@ SQLITE_PRIVATE int sqlite3ThreadCreate( memset(p, 0, sizeof(*p)); p->xTask = xTask; p->pIn = pIn; - /* If the SQLITE_TESTCTRL_FAULT_INSTALL callback is registered to a + /* If the SQLITE_TESTCTRL_FAULT_INSTALL callback is registered to a ** function that returns SQLITE_ERROR when passed the argument 200, that - ** forces worker threads to run sequentially and deterministically + ** forces worker threads to run sequentially and deterministically ** for testing purposes. */ if( sqlite3FaultSim(200) ){ rc = 1; - }else{ + }else{ rc = pthread_create(&p->tid, 0, xTask, pIn); } if( rc ){ @@ -29538,9 +35665,9 @@ SQLITE_PRIVATE int sqlite3ThreadCreate( *ppThread = 0; p = sqlite3Malloc(sizeof(*p)); if( p==0 ) return SQLITE_NOMEM_BKPT; - /* If the SQLITE_TESTCTRL_FAULT_INSTALL callback is registered to a + /* If the SQLITE_TESTCTRL_FAULT_INSTALL callback is registered to a ** function that returns SQLITE_ERROR when passed the argument 200, that - ** forces worker threads to run sequentially and deterministically + ** forces worker threads to run sequentially and deterministically ** (via the sqlite3FaultSim() term of the conditional) for testing ** purposes. */ if( sqlite3GlobalConfig.bCoreMutex==0 || sqlite3FaultSim(200) ){ @@ -29579,6 +35706,7 @@ SQLITE_PRIVATE int sqlite3ThreadJoin(SQLiteThread *p, void **ppOut){ rc = sqlite3Win32Wait((HANDLE)p->tid); assert( rc!=WAIT_IO_COMPLETION ); bRc = CloseHandle((HANDLE)p->tid); + (void)bRc; /* Prevent warning when assert() is a no-op */ assert( bRc ); } if( rc==WAIT_OBJECT_0 ) *ppOut = p->pResult; @@ -29669,7 +35797,7 @@ SQLITE_PRIVATE int sqlite3ThreadJoin(SQLiteThread *p, void **ppOut){ ** May you share freely, never taking more than you give. ** ************************************************************************* -** This file contains routines used to translate between UTF-8, +** This file contains routines used to translate between UTF-8, ** UTF-16, UTF-16BE, and UTF-16LE. ** ** Notes on UTF-8: @@ -29765,24 +35893,33 @@ static const unsigned char sqlite3Utf8Trans1[] = { } \ } -#define READ_UTF16LE(zIn, TERM, c){ \ - c = (*zIn++); \ - c += ((*zIn++)<<8); \ - if( c>=0xD800 && c<0xE000 && TERM ){ \ - int c2 = (*zIn++); \ - c2 += ((*zIn++)<<8); \ - c = (c2&0x03FF) + ((c&0x003F)<<10) + (((c&0x03C0)+0x0040)<<10); \ - } \ -} - -#define READ_UTF16BE(zIn, TERM, c){ \ - c = ((*zIn++)<<8); \ - c += (*zIn++); \ - if( c>=0xD800 && c<0xE000 && TERM ){ \ - int c2 = ((*zIn++)<<8); \ - c2 += (*zIn++); \ - c = (c2&0x03FF) + ((c&0x003F)<<10) + (((c&0x03C0)+0x0040)<<10); \ - } \ +/* +** Write a single UTF8 character whose value is v into the +** buffer starting at zOut. zOut must be sized to hold at +** least four bytes. Return the number of bytes needed +** to encode the new character. +*/ +SQLITE_PRIVATE int sqlite3AppendOneUtf8Character(char *zOut, u32 v){ + if( v<0x00080 ){ + zOut[0] = (u8)(v & 0xff); + return 1; + } + if( v<0x00800 ){ + zOut[0] = 0xc0 + (u8)((v>>6) & 0x1f); + zOut[1] = 0x80 + (u8)(v & 0x3f); + return 2; + } + if( v<0x10000 ){ + zOut[0] = 0xe0 + (u8)((v>>12) & 0x0f); + zOut[1] = 0x80 + (u8)((v>>6) & 0x3f); + zOut[2] = 0x80 + (u8)(v & 0x3f); + return 3; + } + zOut[0] = 0xf0 + (u8)((v>>18) & 0x07); + zOut[1] = 0x80 + (u8)((v>>12) & 0x3f); + zOut[2] = 0x80 + (u8)((v>>6) & 0x3f); + zOut[3] = 0x80 + (u8)(v & 0x3f); + return 4; } /* @@ -29816,7 +35953,7 @@ static const unsigned char sqlite3Utf8Trans1[] = { c = *(zIn++); \ if( c>=0xc0 ){ \ c = sqlite3Utf8Trans1[c-0xc0]; \ - while( zIn!=zTerm && (*zIn & 0xc0)==0x80 ){ \ + while( zIn0 ); + c = z[0]; + if( c>=0xc0 ){ + c = sqlite3Utf8Trans1[c-0xc0]; + if( n>4 ) n = 4; + while( ienc==SQLITE_UTF16LE ){ /* UTF-16 Little-endian -> UTF-8 */ while( zIn=0xd800 && c<0xe000 ){ +#ifdef SQLITE_REPLACE_INVALID_UTF + if( c>=0xdc00 || zIn>=zTerm ){ + c = 0xfffd; + }else{ + int c2 = *(zIn++); + c2 += (*(zIn++))<<8; + if( c2<0xdc00 || c2>=0xe000 ){ + zIn -= 2; + c = 0xfffd; + }else{ + c = ((c&0x3ff)<<10) + (c2&0x3ff) + 0x10000; + } + } +#else + if( zIn UTF-8 */ while( zIn=0xd800 && c<0xe000 ){ +#ifdef SQLITE_REPLACE_INVALID_UTF + if( c>=0xdc00 || zIn>=zTerm ){ + c = 0xfffd; + }else{ + int c2 = (*(zIn++))<<8; + c2 += *(zIn++); + if( c2<0xdc00 || c2>=0xe000 ){ + zIn -= 2; + c = 0xfffd; + }else{ + c = ((c&0x3ff)<<10) + (c2&0x3ff) + 0x10000; + } + } +#else + if( zInn+(desiredEnc==SQLITE_UTF8?1:2))<=len ); - c = pMem->flags; + c = MEM_Str|MEM_Term|(pMem->flags&(MEM_AffMask|MEM_Subtype)); sqlite3VdbeMemRelease(pMem); - pMem->flags = MEM_Str|MEM_Term|(c&(MEM_AffMask|MEM_Subtype)); + pMem->flags = c; pMem->enc = desiredEnc; pMem->z = (char*)zOut; pMem->zMalloc = pMem->z; @@ -29985,9 +36201,11 @@ SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3VdbeMemTranslate(Mem *pMem, u8 desired translate_out: #if defined(TRANSLATE_TRACE) && defined(SQLITE_DEBUG) { - char zBuf[100]; - sqlite3VdbeMemPrettyPrint(pMem, zBuf); - fprintf(stderr, "OUTPUT: %s\n", zBuf); + StrAccum acc; + char zBuf[1000]; + sqlite3StrAccumInit(&acc, 0, zBuf, sizeof(zBuf), 0); + sqlite3VdbeMemPrettyPrint(pMem, &acc); + fprintf(stderr, "OUTPUT: %s\n", sqlite3StrAccumFinish(&acc)); } #endif return SQLITE_OK; @@ -29996,7 +36214,7 @@ SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3VdbeMemTranslate(Mem *pMem, u8 desired #ifndef SQLITE_OMIT_UTF16 /* -** This routine checks for a byte-order mark at the beginning of the +** This routine checks for a byte-order mark at the beginning of the ** UTF-16 string stored in *pMem. If one is present, it is removed and ** the encoding of the Mem adjusted. This routine does not do any ** byte-swapping, it just sets Mem.enc appropriately. @@ -30019,7 +36237,7 @@ SQLITE_PRIVATE int sqlite3VdbeMemHandleBom(Mem *pMem){ bom = SQLITE_UTF16LE; } } - + if( bom ){ rc = sqlite3VdbeMemMakeWriteable(pMem); if( rc==SQLITE_OK ){ @@ -30039,7 +36257,7 @@ SQLITE_PRIVATE int sqlite3VdbeMemHandleBom(Mem *pMem){ ** pZ is a UTF-8 encoded unicode string. If nByte is less than zero, ** return the number of unicode characters in pZ up to (but not including) ** the first 0x00 byte. If nByte is not less than zero, return the -** number of unicode characters in the first nByte of pZ (or up to +** number of unicode characters in the first nByte of pZ (or up to ** the first 0x00, whichever comes first). */ SQLITE_PRIVATE int sqlite3Utf8CharLen(const char *zIn, int nByte){ @@ -30059,7 +36277,7 @@ SQLITE_PRIVATE int sqlite3Utf8CharLen(const char *zIn, int nByte){ return r; } -/* This test function is not currently used by the automated test-suite. +/* This test function is not currently used by the automated test-suite. ** Hence it is only available in debug builds. */ #if defined(SQLITE_TEST) && defined(SQLITE_DEBUG) @@ -30113,27 +36331,26 @@ SQLITE_PRIVATE char *sqlite3Utf16to8(sqlite3 *db, const void *z, int nByte, u8 e } /* -** zIn is a UTF-16 encoded unicode string at least nChar characters long. +** zIn is a UTF-16 encoded unicode string at least nByte bytes long. ** Return the number of bytes in the first nChar unicode characters -** in pZ. nChar must be non-negative. +** in pZ. nChar must be non-negative. Surrogate pairs count as a single +** character. */ -SQLITE_PRIVATE int sqlite3Utf16ByteLen(const void *zIn, int nChar){ +SQLITE_PRIVATE int sqlite3Utf16ByteLen(const void *zIn, int nByte, int nChar){ int c; unsigned char const *z = zIn; + unsigned char const *zEnd = &z[nByte-1]; int n = 0; - - if( SQLITE_UTF16NATIVE==SQLITE_UTF16BE ){ - while( n=0xd8 && c<0xdc && z<=zEnd && z[0]>=0xdc && z[0]<0xe0 ) z += 2; + n++; } - return (int)(z-(unsigned char const *)zIn); + return (int)(z-(unsigned char const *)zIn) + - (SQLITE_UTF16NATIVE==SQLITE_UTF16LE); } #if defined(SQLITE_TEST) @@ -30163,30 +36380,6 @@ SQLITE_PRIVATE void sqlite3UtfSelfTest(void){ assert( c==t ); assert( (z-zBuf)==n ); } - for(i=0; i<0x00110000; i++){ - if( i>=0xD800 && i<0xE000 ) continue; - z = zBuf; - WRITE_UTF16LE(z, i); - n = (int)(z-zBuf); - assert( n>0 && n<=4 ); - z[0] = 0; - z = zBuf; - READ_UTF16LE(z, 1, c); - assert( c==i ); - assert( (z-zBuf)==n ); - } - for(i=0; i<0x00110000; i++){ - if( i>=0xD800 && i<0xE000 ) continue; - z = zBuf; - WRITE_UTF16BE(z, i); - n = (int)(z-zBuf); - assert( n>0 && n<=4 ); - z[0] = 0; - z = zBuf; - READ_UTF16BE(z, 1, c); - assert( c==i ); - assert( (z-zBuf)==n ); - } } #endif /* SQLITE_TEST */ #endif /* SQLITE_OMIT_UTF16 */ @@ -30212,24 +36405,14 @@ SQLITE_PRIVATE void sqlite3UtfSelfTest(void){ */ /* #include "sqliteInt.h" */ /* #include */ -#if HAVE_ISNAN || SQLITE_HAVE_ISNAN -# include -#endif - -/* -** Routine needed to support the testcase() macro. -*/ -#ifdef SQLITE_COVERAGE_TEST -SQLITE_PRIVATE void sqlite3Coverage(int x){ - static unsigned dummy = 0; - dummy += (unsigned)x; -} +#ifndef SQLITE_OMIT_FLOATING_POINT +#include #endif /* ** Calls to sqlite3FaultSim() are used to simulate a failure during testing, -** or to bypass normal error detection during testing in order to let -** execute proceed futher downstream. +** or to bypass normal error detection during testing in order to let +** execute proceed further downstream. ** ** In deployment, sqlite3FaultSim() *always* return SQLITE_OK (0). The ** sqlite3FaultSim() function only returns non-zero during testing. @@ -30262,36 +36445,10 @@ SQLITE_PRIVATE int sqlite3FaultSim(int iTest){ SQLITE_PRIVATE int sqlite3IsNaN(double x){ int rc; /* The value return */ #if !SQLITE_HAVE_ISNAN && !HAVE_ISNAN - /* - ** Systems that support the isnan() library function should probably - ** make use of it by compiling with -DSQLITE_HAVE_ISNAN. But we have - ** found that many systems do not have a working isnan() function so - ** this implementation is provided as an alternative. - ** - ** This NaN test sometimes fails if compiled on GCC with -ffast-math. - ** On the other hand, the use of -ffast-math comes with the following - ** warning: - ** - ** This option [-ffast-math] should never be turned on by any - ** -O option since it can result in incorrect output for programs - ** which depend on an exact implementation of IEEE or ISO - ** rules/specifications for math functions. - ** - ** Under MSVC, this NaN test may fail if compiled with a floating- - ** point precision mode other than /fp:precise. From the MSDN - ** documentation: - ** - ** The compiler [with /fp:precise] will properly handle comparisons - ** involving NaN. For example, x != x evaluates to true if x is NaN - ** ... - */ -#ifdef __FAST_MATH__ -# error SQLite will not work correctly with the -ffast-math option of GCC. -#endif - volatile double y = x; - volatile double z = y; - rc = (y!=z); -#else /* if HAVE_ISNAN */ + u64 y; + memcpy(&y,&x,sizeof(y)); + rc = IsNaN(y); +#else rc = isnan(x); #endif /* HAVE_ISNAN */ testcase( rc ); @@ -30299,6 +36456,19 @@ SQLITE_PRIVATE int sqlite3IsNaN(double x){ } #endif /* SQLITE_OMIT_FLOATING_POINT */ +#ifndef SQLITE_OMIT_FLOATING_POINT +/* +** Return true if the floating point value is NaN or +Inf or -Inf. +*/ +SQLITE_PRIVATE int sqlite3IsOverflow(double x){ + int rc; /* The value return */ + u64 y; + memcpy(&y,&x,sizeof(y)); + rc = IsOvfl(y); + return rc; +} +#endif /* SQLITE_OMIT_FLOATING_POINT */ + /* ** Compute a string length that is limited to what can be stored in ** lower 30 bits of a 32-bit signed integer. @@ -30313,15 +36483,21 @@ SQLITE_PRIVATE int sqlite3Strlen30(const char *z){ } /* -** Return the declared type of a column. Or return zDflt if the column +** Return the declared type of a column. Or return zDflt if the column ** has no declared type. ** ** The column type is an extra string stored after the zero-terminator on ** the column name if and only if the COLFLAG_HASTYPE flag is set. */ SQLITE_PRIVATE char *sqlite3ColumnType(Column *pCol, char *zDflt){ - if( (pCol->colFlags & COLFLAG_HASTYPE)==0 ) return zDflt; - return pCol->zName + strlen(pCol->zName) + 1; + if( pCol->colFlags & COLFLAG_HASTYPE ){ + return pCol->zCnName + strlen(pCol->zCnName) + 1; + }else if( pCol->eCType ){ + assert( pCol->eCType<=SQLITE_N_STDTYPE ); + return (char*)sqlite3StdType[pCol->eCType-1]; + }else{ + return zDflt; + } } /* @@ -30342,7 +36518,22 @@ static SQLITE_NOINLINE void sqlite3ErrorFinish(sqlite3 *db, int err_code){ SQLITE_PRIVATE void sqlite3Error(sqlite3 *db, int err_code){ assert( db!=0 ); db->errCode = err_code; - if( err_code || db->pErr ) sqlite3ErrorFinish(db, err_code); + if( err_code || db->pErr ){ + sqlite3ErrorFinish(db, err_code); + }else{ + db->errByteOffset = -1; + } +} + +/* +** The equivalent of sqlite3Error(db, SQLITE_OK). Clear the error state +** and error message. +*/ +SQLITE_PRIVATE void sqlite3ErrorClear(sqlite3 *db){ + assert( db!=0 ); + db->errCode = SQLITE_OK; + db->errByteOffset = -1; + if( db->pErr ) sqlite3ValueSetNull(db->pErr); } /* @@ -30351,6 +36542,23 @@ SQLITE_PRIVATE void sqlite3Error(sqlite3 *db, int err_code){ */ SQLITE_PRIVATE void sqlite3SystemError(sqlite3 *db, int rc){ if( rc==SQLITE_IOERR_NOMEM ) return; +#if defined(SQLITE_USE_SEH) && !defined(SQLITE_OMIT_WAL) + if( rc==SQLITE_IOERR_IN_PAGE ){ + int ii; + int iErr; + sqlite3BtreeEnterAll(db); + for(ii=0; iinDb; ii++){ + if( db->aDb[ii].pBt ){ + iErr = sqlite3PagerWalSystemErrno(sqlite3BtreePager(db->aDb[ii].pBt)); + if( iErr ){ + db->iSysErrno = iErr; + } + } + } + sqlite3BtreeLeaveAll(db); + return; + } +#endif rc &= 0xff; if( rc==SQLITE_CANTOPEN || rc==SQLITE_IOERR ){ db->iSysErrno = sqlite3OsGetLastError(db->pVfs); @@ -30362,17 +36570,8 @@ SQLITE_PRIVATE void sqlite3SystemError(sqlite3 *db, int rc){ ** handle "db". The error code is set to "err_code". ** ** If it is not NULL, string zFormat specifies the format of the -** error string in the style of the printf functions: The following -** format characters are allowed: -** -** %s Insert a string -** %z A string that should be freed after use -** %d Insert an integer -** %T Insert a token -** %S Insert the first element of a SrcList -** -** zFormat and any string tokens that follow it are assumed to be -** encoded in UTF-8. +** error string. zFormat and any string tokens that follow it are +** assumed to be encoded in UTF-8. ** ** To clear the most recent error for sqlite handle "db", sqlite3Error ** should be called with err_code set to SQLITE_OK and zFormat set @@ -30394,15 +36593,32 @@ SQLITE_PRIVATE void sqlite3ErrorWithMsg(sqlite3 *db, int err_code, const char *z } } +/* +** Check for interrupts and invoke progress callback. +*/ +SQLITE_PRIVATE void sqlite3ProgressCheck(Parse *p){ + sqlite3 *db = p->db; + if( AtomicLoad(&db->u1.isInterrupted) ){ + p->nErr++; + p->rc = SQLITE_INTERRUPT; + } +#ifndef SQLITE_OMIT_PROGRESS_CALLBACK + if( db->xProgress ){ + if( p->rc==SQLITE_INTERRUPT ){ + p->nProgressSteps = 0; + }else if( (++p->nProgressSteps)>=db->nProgressOps ){ + if( db->xProgress(db->pProgressArg) ){ + p->nErr++; + p->rc = SQLITE_INTERRUPT; + } + p->nProgressSteps = 0; + } + } +#endif +} + /* ** Add an error message to pParse->zErrMsg and increment pParse->nErr. -** The following formatting characters are allowed: -** -** %s Insert a string -** %z A string that should be freed after use -** %d Insert an integer -** %T Insert a token -** %S Insert the first element of a SrcList ** ** This function should be used to report any error that occurs while ** compiling an SQL statement (i.e. within sqlite3_prepare()). The @@ -30415,16 +36631,25 @@ SQLITE_PRIVATE void sqlite3ErrorMsg(Parse *pParse, const char *zFormat, ...){ char *zMsg; va_list ap; sqlite3 *db = pParse->db; + assert( db!=0 ); + assert( db->pParse==pParse || db->pParse->pToplevel==pParse ); + db->errByteOffset = -2; va_start(ap, zFormat); zMsg = sqlite3VMPrintf(db, zFormat, ap); va_end(ap); + if( db->errByteOffset<-1 ) db->errByteOffset = -1; if( db->suppressErr ){ sqlite3DbFree(db, zMsg); + if( db->mallocFailed ){ + pParse->nErr++; + pParse->rc = SQLITE_NOMEM; + } }else{ pParse->nErr++; sqlite3DbFree(db, pParse->zErrMsg); pParse->zErrMsg = zMsg; pParse->rc = SQLITE_ERROR; + pParse->pWith = 0; } } @@ -30481,11 +36706,72 @@ SQLITE_PRIVATE void sqlite3Dequote(char *z){ z[j] = 0; } SQLITE_PRIVATE void sqlite3DequoteExpr(Expr *p){ + assert( !ExprHasProperty(p, EP_IntValue) ); assert( sqlite3Isquote(p->u.zToken[0]) ); p->flags |= p->u.zToken[0]=='"' ? EP_Quoted|EP_DblQuoted : EP_Quoted; sqlite3Dequote(p->u.zToken); } +/* +** Expression p is a QNUMBER (quoted number). Dequote the value in p->u.zToken +** and set the type to INTEGER or FLOAT. "Quoted" integers or floats are those +** that contain '_' characters that must be removed before further processing. +*/ +SQLITE_PRIVATE void sqlite3DequoteNumber(Parse *pParse, Expr *p){ + assert( p!=0 || pParse->db->mallocFailed ); + if( p ){ + const char *pIn = p->u.zToken; + char *pOut = p->u.zToken; + int bHex = (pIn[0]=='0' && (pIn[1]=='x' || pIn[1]=='X')); + int iValue; + assert( p->op==TK_QNUMBER ); + p->op = TK_INTEGER; + do { + if( *pIn!=SQLITE_DIGIT_SEPARATOR ){ + *pOut++ = *pIn; + if( *pIn=='e' || *pIn=='E' || *pIn=='.' ) p->op = TK_FLOAT; + }else{ + if( (bHex==0 && (!sqlite3Isdigit(pIn[-1]) || !sqlite3Isdigit(pIn[1]))) + || (bHex==1 && (!sqlite3Isxdigit(pIn[-1]) || !sqlite3Isxdigit(pIn[1]))) + ){ + sqlite3ErrorMsg(pParse, "unrecognized token: \"%s\"", p->u.zToken); + } + } + }while( *pIn++ ); + if( bHex ) p->op = TK_INTEGER; + + /* tag-20240227-a: If after dequoting, the number is an integer that + ** fits in 32 bits, then it must be converted into EP_IntValue. Other + ** parts of the code expect this. See also tag-20240227-b. */ + if( p->op==TK_INTEGER && sqlite3GetInt32(p->u.zToken, &iValue) ){ + p->u.iValue = iValue; + p->flags |= EP_IntValue; + } + } +} + +/* +** If the input token p is quoted, try to adjust the token to remove +** the quotes. This is not always possible: +** +** "abc" -> abc +** "ab""cd" -> (not possible because of the interior "") +** +** Remove the quotes if possible. This is a optimization. The overall +** system should still return the correct answer even if this routine +** is always a no-op. +*/ +SQLITE_PRIVATE void sqlite3DequoteToken(Token *p){ + unsigned int i; + if( p->n<2 ) return; + if( !sqlite3Isquote(p->z[0]) ) return; + for(i=1; in-1; i++){ + if( sqlite3Isquote(p->z[i]) ) return; + } + p->n -= 2; + p->z++; +} + /* ** Generate a Token object from a string */ @@ -30517,12 +36803,18 @@ SQLITE_API int sqlite3_stricmp(const char *zLeft, const char *zRight){ } SQLITE_PRIVATE int sqlite3StrICmp(const char *zLeft, const char *zRight){ unsigned char *a, *b; - int c; + int c, x; a = (unsigned char *)zLeft; b = (unsigned char *)zRight; for(;;){ - c = (int)UpperToLower[*a] - (int)UpperToLower[*b]; - if( c || *a==0 ) break; + c = *a; + x = *b; + if( c==x ){ + if( c==0 ) break; + }else{ + c = (int)UpperToLower[c] - (int)UpperToLower[x]; + if( c ) break; + } a++; b++; } @@ -30542,240 +36834,674 @@ SQLITE_API int sqlite3_strnicmp(const char *zLeft, const char *zRight, int N){ } /* -** Compute 10 to the E-th power. Examples: E==1 results in 10. -** E==2 results in 100. E==50 results in 1.0e50. -** -** This routine only works for values of E between 1 and 341. +** Compute an 8-bit hash on a string that is insensitive to case differences */ -static LONGDOUBLE_TYPE sqlite3Pow10(int E){ -#if defined(_MSC_VER) - static const LONGDOUBLE_TYPE x[] = { - 1.0e+001, - 1.0e+002, - 1.0e+004, - 1.0e+008, - 1.0e+016, - 1.0e+032, - 1.0e+064, - 1.0e+128, - 1.0e+256 - }; - LONGDOUBLE_TYPE r = 1.0; - int i; - assert( E>=0 && E<=307 ); - for(i=0; E!=0; i++, E >>=1){ - if( E & 1 ) r *= x[i]; +SQLITE_PRIVATE u8 sqlite3StrIHash(const char *z){ + u8 h = 0; + if( z==0 ) return 0; + while( z[0] ){ + h += UpperToLower[(unsigned char)z[0]]; + z++; } - return r; + return h; +} + +#if !defined(SQLITE_DISABLE_INTRINSIC) \ + && (defined(__GNUC__) || defined(__clang__)) \ + && (defined(__x86_64__) || defined(__aarch64__) || \ + (defined(__riscv) && defined(__riscv_xlen) && (__riscv_xlen>32))) +#define SQLITE_USE_UINT128 +#endif + +/* +** Two inputs are multiplied to get a 128-bit result. Write the +** lower 64-bits of the result into *pLo, and return the high-order +** 64 bits. +*/ +static u64 sqlite3Multiply128(u64 a, u64 b, u64 *pLo){ +#if defined(SQLITE_USE_UINT128) + __uint128_t r = (__uint128_t)a * b; + *pLo = (u64)r; + return (u64)(r>>64); +#elif defined(_WIN64) && !defined(SQLITE_DISABLE_INTRINSIC) + *pLo = a*b; + return __umulh(a, b); #else - LONGDOUBLE_TYPE x = 10.0; - LONGDOUBLE_TYPE r = 1.0; - while(1){ - if( E & 1 ) r *= x; - E >>= 1; - if( E==0 ) break; - x *= x; - } - return r; + u64 a0 = (u32)a; + u64 a1 = a >> 32; + u64 b0 = (u32)b; + u64 b1 = b >> 32; + u64 a0b0 = a0 * b0; + u64 a1b1 = a1 * b1; + u64 a0b1 = a0 * b1; + u64 a1b0 = a1 * b0; + u64 t = (a0b0 >> 32) + (u32)a0b1 + (u32)a1b0; + *pLo = (a0b0 & UINT64_C(0xffffffff)) | (t << 32); + return a1b1 + (a0b1>>32) + (a1b0>>32) + (t>>32); +#endif +} + +/* +** A is an unsigned 96-bit integer formed by (a<<32)+aLo. +** B is an unsigned 64-bit integer. +** +** Compute the upper 96 bits of 160-bit result of A*B. +** +** Write ((A*B)>>64 & 0xffffffff) (the middle 32 bits of A*B) +** into *pLo. Return the upper 64 bits of A*B. +** +** The lower 64 bits of A*B are discarded. +*/ +static u64 sqlite3Multiply160(u64 a, u32 aLo, u64 b, u32 *pLo){ +#if defined(SQLITE_USE_UINT128) + __uint128_t r = (__uint128_t)a * b; + r += ((__uint128_t)aLo * b) >> 32; + *pLo = (r>>32)&0xffffffff; + return r>>64; +#elif defined(_WIN64) && !defined(SQLITE_DISABLE_INTRINSIC) + u64 r1_hi = __umulh(a,b); + u64 r1_lo = a*b; + u64 r2 = (__umulh((u64)aLo,b)<<32) + ((aLo*b)>>32); + u64 t = r1_lo + r2; + if( t>32; + return r1_hi; +#else + u64 x2 = a>>32; + u64 x1 = a&0xffffffff; + u64 x0 = aLo; + u64 y1 = b>>32; + u64 y0 = b&0xffffffff; + u64 x2y1 = x2*y1; + u64 r4 = x2y1>>32; + u64 x2y0 = x2*y0; + u64 x1y1 = x1*y1; + u64 r3 = (x2y1 & 0xffffffff) + (x2y0 >>32) + (x1y1 >>32); + u64 x1y0 = x1*y0; + u64 x0y1 = x0*y1; + u64 r2 = (x2y0 & 0xffffffff) + (x1y1 & 0xffffffff) + + (x1y0 >>32) + (x0y1>>32); + u64 x0y0 = x0*y0; + u64 r1 = (x1y0 & 0xffffffff) + (x0y1 & 0xffffffff) + + (x0y0 >>32); + r2 += r1>>32; + r3 += r2>>32; + *pLo = r2&0xffffffff; + return (r4<<32) + r3; #endif } +#undef SQLITE_USE_UINT128 + /* -** The string z[] is an text representation of a real number. -** Convert this string to a double and write it into *pResult. +** Return a u64 with the N-th bit set. +*/ +#define U64_BIT(N) (((u64)1)<<(N)) + +/* +** Range of powers of 10 that we need to deal with when converting +** IEEE754 doubles to and from decimal. +*/ +#define POWERSOF10_FIRST (-348) +#define POWERSOF10_LAST (+347) + +/* +** For any p between -348 and +347, return the integer part of ** -** The string z[] is length bytes in length (bytes, not characters) and -** uses the encoding enc. The string is not necessarily zero-terminated. +** pow(10,p) * pow(2,63-pow10to2(p)) ** -** Return TRUE if the result is a valid real number (or integer) and FALSE -** if the string is empty or contains extraneous text. Valid numbers -** are in one of these formats: +** Or, in other words, for any p in range, return the most significant +** 64 bits of pow(10,p). The pow(10,p) value is shifted left or right, +** as appropriate so the most significant 64 bits fit exactly into a +** 64-bit unsigned integer. ** -** [+-]digits[E[+-]digits] -** [+-]digits.[digits][E[+-]digits] -** [+-].digits[E[+-]digits] +** Write into *pLo the next 32 significant bits of the answer after +** the first 64. ** -** Leading and trailing whitespace is ignored for the purpose of determining -** validity. +** Algorithm: ** -** If some prefix of the input string is a valid number, this routine -** returns FALSE but it still converts the prefix and writes the result -** into *pResult. +** (1) For p between 0 and 26, return the value directly from the aBase[] +** lookup table. +** +** (2) For p outside the range 0 to 26, use aScale[] for the initial value +** then refine that result (if necessary) by a single multiplication +** against aBase[]. +** +** The constant tables aBase[], aScale[], and aScaleLo[] are generated +** by the C program at ../tool/mkfptab.c run with the --round option. +*/ +static u64 powerOfTen(int p, u32 *pLo){ + static const u64 aBase[] = { + UINT64_C(0x8000000000000000), /* 0: 1.0e+0 << 63 */ + UINT64_C(0xa000000000000000), /* 1: 1.0e+1 << 60 */ + UINT64_C(0xc800000000000000), /* 2: 1.0e+2 << 57 */ + UINT64_C(0xfa00000000000000), /* 3: 1.0e+3 << 54 */ + UINT64_C(0x9c40000000000000), /* 4: 1.0e+4 << 50 */ + UINT64_C(0xc350000000000000), /* 5: 1.0e+5 << 47 */ + UINT64_C(0xf424000000000000), /* 6: 1.0e+6 << 44 */ + UINT64_C(0x9896800000000000), /* 7: 1.0e+7 << 40 */ + UINT64_C(0xbebc200000000000), /* 8: 1.0e+8 << 37 */ + UINT64_C(0xee6b280000000000), /* 9: 1.0e+9 << 34 */ + UINT64_C(0x9502f90000000000), /* 10: 1.0e+10 << 30 */ + UINT64_C(0xba43b74000000000), /* 11: 1.0e+11 << 27 */ + UINT64_C(0xe8d4a51000000000), /* 12: 1.0e+12 << 24 */ + UINT64_C(0x9184e72a00000000), /* 13: 1.0e+13 << 20 */ + UINT64_C(0xb5e620f480000000), /* 14: 1.0e+14 << 17 */ + UINT64_C(0xe35fa931a0000000), /* 15: 1.0e+15 << 14 */ + UINT64_C(0x8e1bc9bf04000000), /* 16: 1.0e+16 << 10 */ + UINT64_C(0xb1a2bc2ec5000000), /* 17: 1.0e+17 << 7 */ + UINT64_C(0xde0b6b3a76400000), /* 18: 1.0e+18 << 4 */ + UINT64_C(0x8ac7230489e80000), /* 19: 1.0e+19 >> 0 */ + UINT64_C(0xad78ebc5ac620000), /* 20: 1.0e+20 >> 3 */ + UINT64_C(0xd8d726b7177a8000), /* 21: 1.0e+21 >> 6 */ + UINT64_C(0x878678326eac9000), /* 22: 1.0e+22 >> 10 */ + UINT64_C(0xa968163f0a57b400), /* 23: 1.0e+23 >> 13 */ + UINT64_C(0xd3c21bcecceda100), /* 24: 1.0e+24 >> 16 */ + UINT64_C(0x84595161401484a0), /* 25: 1.0e+25 >> 20 */ + UINT64_C(0xa56fa5b99019a5c8), /* 26: 1.0e+26 >> 23 */ + }; + static const u64 aScale[] = { + UINT64_C(0x8049a4ac0c5811ae), /* 0: 1.0e-351 << 1229 */ + UINT64_C(0xcf42894a5dce35ea), /* 1: 1.0e-324 << 1140 */ + UINT64_C(0xa76c582338ed2621), /* 2: 1.0e-297 << 1050 */ + UINT64_C(0x873e4f75e2224e68), /* 3: 1.0e-270 << 960 */ + UINT64_C(0xda7f5bf590966848), /* 4: 1.0e-243 << 871 */ + UINT64_C(0xb080392cc4349dec), /* 5: 1.0e-216 << 781 */ + UINT64_C(0x8e938662882af53e), /* 6: 1.0e-189 << 691 */ + UINT64_C(0xe65829b3046b0afa), /* 7: 1.0e-162 << 602 */ + UINT64_C(0xba121a4650e4ddeb), /* 8: 1.0e-135 << 512 */ + UINT64_C(0x964e858c91ba2655), /* 9: 1.0e-108 << 422 */ + UINT64_C(0xf2d56790ab41c2a2), /* 10: 1.0e-81 << 333 */ + UINT64_C(0xc428d05aa4751e4c), /* 11: 1.0e-54 << 243 */ + UINT64_C(0x9e74d1b791e07e48), /* 12: 1.0e-27 << 153 */ + UINT64_C(0xcccccccccccccccc), /* 13: 1.0e-1 << 67 (special case) */ + UINT64_C(0xcecb8f27f4200f3a), /* 14: 1.0e+27 >> 26 */ + UINT64_C(0xa70c3c40a64e6c51), /* 15: 1.0e+54 >> 116 */ + UINT64_C(0x86f0ac99b4e8dafd), /* 16: 1.0e+81 >> 206 */ + UINT64_C(0xda01ee641a708de9), /* 17: 1.0e+108 >> 295 */ + UINT64_C(0xb01ae745b101e9e4), /* 18: 1.0e+135 >> 385 */ + UINT64_C(0x8e41ade9fbebc27d), /* 19: 1.0e+162 >> 475 */ + UINT64_C(0xe5d3ef282a242e81), /* 20: 1.0e+189 >> 564 */ + UINT64_C(0xb9a74a0637ce2ee1), /* 21: 1.0e+216 >> 654 */ + UINT64_C(0x95f83d0a1fb69cd9), /* 22: 1.0e+243 >> 744 */ + UINT64_C(0xf24a01a73cf2dccf), /* 23: 1.0e+270 >> 833 */ + UINT64_C(0xc3b8358109e84f07), /* 24: 1.0e+297 >> 923 */ + UINT64_C(0x9e19db92b4e31ba9), /* 25: 1.0e+324 >> 1013 */ + }; + static const unsigned int aScaleLo[] = { + 0x205b896d, /* 0: 1.0e-351 << 1229 */ + 0x52064cad, /* 1: 1.0e-324 << 1140 */ + 0xaf2af2b8, /* 2: 1.0e-297 << 1050 */ + 0x5a7744a7, /* 3: 1.0e-270 << 960 */ + 0xaf39a475, /* 4: 1.0e-243 << 871 */ + 0xbd8d794e, /* 5: 1.0e-216 << 781 */ + 0x547eb47b, /* 6: 1.0e-189 << 691 */ + 0x0cb4a5a3, /* 7: 1.0e-162 << 602 */ + 0x92f34d62, /* 8: 1.0e-135 << 512 */ + 0x3a6a07f9, /* 9: 1.0e-108 << 422 */ + 0xfae27299, /* 10: 1.0e-81 << 333 */ + 0xaa97e14c, /* 11: 1.0e-54 << 243 */ + 0x775ea265, /* 12: 1.0e-27 << 153 */ + 0xcccccccc, /* 13: 1.0e-1 << 67 (special case) */ + 0x00000000, /* 14: 1.0e+27 >> 26 */ + 0x999090b6, /* 15: 1.0e+54 >> 116 */ + 0x69a028bb, /* 16: 1.0e+81 >> 206 */ + 0xe80e6f48, /* 17: 1.0e+108 >> 295 */ + 0x5ec05dd0, /* 18: 1.0e+135 >> 385 */ + 0x14588f14, /* 19: 1.0e+162 >> 475 */ + 0x8f1668c9, /* 20: 1.0e+189 >> 564 */ + 0x6d953e2c, /* 21: 1.0e+216 >> 654 */ + 0x4abdaf10, /* 22: 1.0e+243 >> 744 */ + 0xbc633b39, /* 23: 1.0e+270 >> 833 */ + 0x0a862f81, /* 24: 1.0e+297 >> 923 */ + 0x6c07a2c2, /* 25: 1.0e+324 >> 1013 */ + }; + int g, n; + u64 s, x; + u32 lo; + + assert( p>=POWERSOF10_FIRST && p<=POWERSOF10_LAST ); + if( p<0 ){ + if( p==(-1) ){ + *pLo = aScaleLo[13]; + return aScale[13]; + } + g = p/27; + n = p%27; + if( n ){ + g--; + n += 27; + } + }else if( p<27 ){ + *pLo = 0; + return aBase[p]; + }else{ + g = p/27; + n = p%27; + } + s = aScale[g+13]; + if( n==0 ){ + *pLo = aScaleLo[g+13]; + return s; + } + x = sqlite3Multiply160(s,aScaleLo[g+13],aBase[n],&lo); + if( (U64_BIT(63) & x)==0 ){ + x = x<<1 | ((lo>>31)&1); + lo = (lo<<1) | 1; + } + *pLo = lo; + return x; +} + +/* +** pow10to2(x) computes floor(log2(pow(10,x))). +** pow2to10(y) computes floor(log10(pow(2,y))). +** +** Conceptually, pow10to2(p) converts a base-10 exponent p into +** a corresponding base-2 exponent, and pow2to10(e) converts a base-2 +** exponent into a base-10 exponent. +** +** The conversions are based on the observation that: +** +** ln(10.0)/ln(2.0) == 108853/32768 (approximately) +** ln(2.0)/ln(10.0) == 78913/262144 (approximately) +** +** These ratios are approximate, but they are accurate to 5 digits, +** which is close enough for the usage here. Right-shift is used +** for division so that rounding of negative numbers happens in the +** right direction. */ -SQLITE_PRIVATE int sqlite3AtoF(const char *z, double *pResult, int length, u8 enc){ -#ifndef SQLITE_OMIT_FLOATING_POINT - int incr; - const char *zEnd = z + length; - /* sign * significand * (10 ^ (esign * exponent)) */ - int sign = 1; /* sign of significand */ - i64 s = 0; /* significand */ - int d = 0; /* adjust exponent for shifting decimal point */ - int esign = 1; /* sign of exponent */ - int e = 0; /* exponent */ - int eValid = 1; /* True exponent is either not used or is well-formed */ - double result; - int nDigits = 0; - int nonNum = 0; /* True if input contains UTF16 with high byte non-zero */ +static int pwr10to2(int p){ return (p*108853) >> 15; } +static int pwr2to10(int p){ return (p*78913) >> 18; } - assert( enc==SQLITE_UTF8 || enc==SQLITE_UTF16LE || enc==SQLITE_UTF16BE ); - *pResult = 0.0; /* Default return value, in case of an error */ +/* +** Count leading zeros for a 64-bit unsigned integer. +*/ +static int countLeadingZeros(u64 m){ +#if (defined(__GNUC__) || defined(__clang__)) \ + && !defined(SQLITE_DISABLE_INTRINSIC) + return __builtin_clzll(m); +#else + int n = 0; + if( m <= 0x00000000ffffffffULL) { n += 32; m <<= 32; } + if( m <= 0x0000ffffffffffffULL) { n += 16; m <<= 16; } + if( m <= 0x00ffffffffffffffULL) { n += 8; m <<= 8; } + if( m <= 0x0fffffffffffffffULL) { n += 4; m <<= 4; } + if( m <= 0x3fffffffffffffffULL) { n += 2; m <<= 2; } + if( m <= 0x7fffffffffffffffULL) { n += 1; } + return n; +#endif +} - if( enc==SQLITE_UTF8 ){ - incr = 1; +/* +** Given m and e, which represent a quantity r == m*pow(2,e), +** return values *pD and *pP such that r == (*pD)*pow(10,*pP), +** approximately. *pD should contain at least n significant digits. +** +** The input m is required to have its highest bit set. In other words, +** m should be left-shifted, and e decremented, to maximize the value of m. +*/ +static void sqlite3Fp2Convert10(u64 m, int e, int n, u64 *pD, int *pP){ + int p; + u64 h, d1; + u32 d2; + assert( n>=1 && n<=18 ); + p = n - 1 - pwr2to10(e+63); + h = sqlite3Multiply128(m, powerOfTen(p,&d2), &d1); + assert( -(e + pwr10to2(p) + 2) >= 0 ); + assert( -(e + pwr10to2(p) + 1) <= 63 ); + if( n==18 ){ + h >>= -(e + pwr10to2(p) + 2); + *pD = (h + ((h<<1)&2))>>1; }else{ - int i; - incr = 2; - assert( SQLITE_UTF16LE==2 && SQLITE_UTF16BE==3 ); - for(i=3-enc; i> -(e + pwr10to2(p) + 1); } + *pP = -p; +} - /* skip leading spaces */ - while( z=zEnd ) return 0; - - /* get sign of significand */ - if( *z=='-' ){ - sign = -1; - z+=incr; - }else if( *z=='+' ){ - z+=incr; - } +/* +** Return an IEEE754 floating point value that approximates d*pow(10,p). +** +** The (current) algorithm is adapted from the work of Ross Cox at +** https://github.com/rsc/fpfmt +*/ +static double sqlite3Fp10Convert2(u64 d, int p){ + int b, lp, e, adj, s; + u32 pwr10l, mid1; + u64 pwr10h, x, hi, lo, sticky, u, m; + double r; + if( pPOWERSOF10_LAST ) return INFINITY; + b = 64 - countLeadingZeros(d); + lp = pwr10to2(p); + e = 53 - b - lp; + if( e > 1074 ){ + if( e>=1130 ) return 0.0; + e = 1074; + } + s = -(e-(64-b) + lp + 3); + pwr10h = powerOfTen(p, &pwr10l); + if( pwr10l!=0 ){ + pwr10h++; + pwr10l = ~pwr10l; + } + x = d<<(64-b); + hi = sqlite3Multiply128(x,pwr10h,&lo); + mid1 = lo>>32; + sticky = 1; + if( (hi & (U64_BIT(s)-1))==0 ) { + u32 mid2 = sqlite3Multiply128(x,((u64)pwr10l)<<32,&lo)>>32; + sticky = (mid1-mid2 > 1); + hi -= mid1 < mid2; + } + u = (hi>>s) | sticky; + adj = (u >= U64_BIT(55)-2); + if( adj ){ + u = (u>>adj) | (u&1); + e -= adj; + } + m = (u + 1 + ((u>>2)&1)) >> 2; + if( e<=(-972) ) return INFINITY; + if((m & U64_BIT(52)) != 0){ + m = (m & ~U64_BIT(52)) | ((u64)(1075-e)<<52); + } + memcpy(&r,&m,8); + return r; +} - /* copy max significant digits to significand */ - while( z Set if any prefix of the input is valid. Clear if +** there is no prefix of the input that can be seen as +** a valid floating point number. +** bit 1 => Set if the input contains a decimal point or eNNN +** clause. Zero if the input is an integer. +** bit 2 => The input is exactly 0.0, not an underflow from +** some value near zero. +** bit 3 => Set if there are more than about 19 significant +** digits in the input. +** +** If the input contains a syntax error but begins with text that might +** be a valid number of some kind, then the result is negative. The +** result is only zero if no prefix of the input could be interpreted as +** a number. +** +** Leading and trailing whitespace is ignored. Valid numbers are in +** one of the formats below: +** +** [+-]digits[E[+-]digits] +** [+-]digits.[digits][E[+-]digits] +** [+-].digits[E[+-]digits] +** +** Algorithm sketch: Compute an unsigned 64-bit integer s and a base-10 +** exponent d such that the value encoding by the input is s*pow(10,d). +** Then invoke sqlite3Fp10Convert2() to calculated the closest possible +** IEEE754 double. The sign is added back afterwards, if the input string +** starts with a "-". The use of an unsigned 64-bit s mantissa means that +** only about the first 19 significant digits of the input can contribute +** to the result. This can result in suboptimal rounding decisions when +** correct rounding requires more than 19 input digits. For example, +** this routine renders "3500000000000000.2500001" as +** 3500000000000000.0 instead of 3500000000000000.5 because the decision +** to round up instead of using banker's rounding to round down is determined +** by the 23rd significant digit, which this routine ignores. It is not +** possible to do better without some kind of BigNum. +*/ +SQLITE_PRIVATE int sqlite3AtoF(const char *zIn, double *pResult){ +#ifndef SQLITE_OMIT_FLOATING_POINT + const unsigned char *z = (const unsigned char*)zIn; + int neg = 0; /* True for a negative value */ + u64 s = 0; /* mantissa */ + int d = 0; /* Value is s * pow(10,d) */ + int mState = 0; /* 1: digit seen 2: fp 4: hard-zero */ + unsigned v; /* Value of a single digit */ + + start_of_text: + if( (v = (unsigned)z[0] - '0')<10 ){ + parse_integer_part: + mState = 1; + s = v; + z++; + while( (v = (unsigned)z[0] - '0')<10 ){ + s = s*10 + v; + z++; + if( s>=(LARGEST_UINT64-9)/10 ){ + mState = 9; + while( sqlite3Isdigit(z[0]) ){ z++; d++; } + break; + } + } + }else if( z[0]=='-' ){ + neg = 1; + z++; + if( (v = (unsigned)z[0] - '0')<10 ) goto parse_integer_part; + }else if( z[0]=='+' ){ + z++; + if( (v = (unsigned)z[0] - '0')<10 ) goto parse_integer_part; + }else if( sqlite3Isspace(z[0]) ){ + do{ z++; }while( sqlite3Isspace(z[0]) ); + goto start_of_text; + }else{ + s = 0; } - /* skip non-significant significand digits - ** (increase exponent by d to shift decimal left) */ - while( z=zEnd ) goto do_atof_calc; - /* if decimal point is present */ if( *z=='.' ){ - z+=incr; - /* copy digits from after decimal to significand - ** (decrease exponent by d to shift decimal right) */ - while( z=zEnd ) goto do_atof_calc; /* if exponent is present */ if( *z=='e' || *z=='E' ){ - z+=incr; - eValid = 0; - - /* This branch is needed to avoid a (harmless) buffer overread. The - ** special comment alerts the mutation tester that the correct answer - ** is obtained even if the branch is omitted */ - if( z>=zEnd ) goto do_atof_calc; /*PREVENTS-HARMLESS-OVERREAD*/ + int esign; + z++; /* get sign of exponent */ if( *z=='-' ){ esign = -1; - z+=incr; - }else if( *z=='+' ){ - z+=incr; + z++; + }else{ + esign = +1; + if( *z=='+' ){ + z++; + } } /* copy digits to exponent */ - while( z0 ){ /*OPTIMIZATION-IF-TRUE*/ - if( esign>0 ){ - if( s>=(LARGEST_INT64/10) ) break; /*OPTIMIZATION-IF-FALSE*/ - s *= 10; - }else{ - if( s%10!=0 ) break; /*OPTIMIZATION-IF-FALSE*/ - s /= 10; - } - e--; + /* return true if number and no extra non-whitespace characters after */ + if( z[0]==0 ){ + return mState; + } + if( sqlite3Isspace(z[0]) ){ + do{ z++; }while( sqlite3Isspace(*z) ); + if( z[0]==0 ){ + return mState; } + } + return 0xfffffff0 | mState; +#else + return sqlite3Atoi64(z, pResult, strlen(z), SQLITE_UTF8)==0; +#endif /* SQLITE_OMIT_FLOATING_POINT */ +} - /* adjust the sign of significand */ - s = sign<0 ? -s : s; +/* +** Digit pairs used to convert a U64 or I64 into text, two digits +** at a time. +*/ +static const union { + char a[201]; + short int forceAlignment; +} sqlite3DigitPairs = { + "00010203040506070809" + "10111213141516171819" + "20212223242526272829" + "30313233343536373839" + "40414243444546474849" + "50515253545556575859" + "60616263646566676869" + "70717273747576777879" + "80818283848586878889" + "90919293949596979899" +}; - if( e==0 ){ /*OPTIMIZATION-IF-TRUE*/ - result = (double)s; - }else{ - /* attempt to handle extremely small/large numbers better */ - if( e>307 ){ /*OPTIMIZATION-IF-TRUE*/ - if( e<342 ){ /*OPTIMIZATION-IF-TRUE*/ - LONGDOUBLE_TYPE scale = sqlite3Pow10(e-308); - if( esign<0 ){ - result = s / scale; - result /= 1.0e+308; - }else{ - result = s * scale; - result *= 1.0e+308; - } - }else{ assert( e>=342 ); - if( esign<0 ){ - result = 0.0*s; - }else{ -#ifdef INFINITY - result = INFINITY*s; -#else - result = 1e308*1e308*s; /* Infinity */ +/* +** ARMv6, ARMv7, PPC32 are known to not support hardware u64 division. +*/ +#if (defined(__arm__) && !defined(__aarch64__)) || \ + (defined(__ppc__) && !defined(__ppc64__)) +# define SQLITE_AVOID_U64_DIVIDE 1 #endif - } - } - }else{ - LONGDOUBLE_TYPE scale = sqlite3Pow10(e); - if( esign<0 ){ - result = s / scale; - }else{ - result = s * scale; - } - } - } - } - /* store the result */ - *pResult = result; +#ifdef SQLITE_AVOID_U64_DIVIDE +/* +** Render an unsigned 64-bit integer as text onto the end of a 2-byte +** aligned buffer that is SQLITE_U64_DIGIT+1 bytes long. The last byte +** of the buffer will be filled with a \000 byte. +** +** Return the index into the buffer of the first byte. +** +** This routine is used on platforms where u64-division is slow because +** it is not available in hardware and has to be emulated in software. +** It seeks to minimize the number of u64 divisions and use u32 divisions +** instead. It is slower on platforms that have hardware u64 division, +** but much faster on platforms that do not. +*/ +static int sqlite3UInt64ToText(u64 v, char *zOut){ + u32 x32, kk; + int i; + zOut[SQLITE_U64_DIGITS] = 0; + i = SQLITE_U64_DIGITS; + assert( TWO_BYTE_ALIGNMENT(&sqlite3DigitPairs.a[0]) ); + assert( TWO_BYTE_ALIGNMENT(zOut) ); + while( (v>>32)!=0 ){ + u32 y, x0, x1, y0, y1; + x32 = v % 100000000; + v = v / 100000000; + y = x32 % 10000; + x32 /= 10000; + x1 = x32 / 100; + x0 = x32 % 100; + y1 = y / 100; + y0 = y % 100; + assert( i>=8 ); + i -= 8; + *(u16*)(&zOut[i]) = *(u16*)&sqlite3DigitPairs.a[x1*2]; + *(u16*)(&zOut[i+2]) = *(u16*)&sqlite3DigitPairs.a[x0*2]; + *(u16*)(&zOut[i+4]) = *(u16*)&sqlite3DigitPairs.a[y1*2]; + *(u16*)(&zOut[i+6]) = *(u16*)&sqlite3DigitPairs.a[y0*2]; + } + x32 = v; + while( x32>=10 ){ + kk = x32 % 100; + x32 = x32 / 100; + assert( TWO_BYTE_ALIGNMENT(&sqlite3DigitPairs.a[kk*2]) ); + assert( i>=2 ); + i -= 2; + assert( TWO_BYTE_ALIGNMENT(&zOut[i]) ); + *(u16*)(&zOut[i]) = *(u16*)&sqlite3DigitPairs.a[kk*2]; + } + if( x32 ){ + assert( i>0 ); + zOut[--i] = x32 + '0'; + } + return i; +} +#endif /* defined(SQLITE_AVOID_U64_DIVIDE) */ - /* return true if number and no extra non-whitespace chracters after */ - return z==zEnd && nDigits>0 && eValid && nonNum==0; +/* +** Render an signed 64-bit integer as text. Store the result in zOut[] and +** return the length of the string that was stored, in bytes. The value +** returned does not include the zero terminator at the end of the output +** string. +** +** The caller must ensure that zOut[] is at least 21 bytes in size. +*/ +SQLITE_PRIVATE int sqlite3Int64ToText(i64 v, char *zOut){ + int i; + u64 x; + union { + char a[SQLITE_U64_DIGITS+1]; + u16 forceAlignment; + } u; + if( v>0 ){ + x = v; + }else if( v==0 ){ + zOut[0] = '0'; + zOut[1] = 0; + return 1; + }else{ + x = (v==SMALLEST_INT64) ? ((u64)1)<<63 : (u64)-v; + } +#ifdef SQLITE_AVOID_U64_DIVIDE + i = sqlite3UInt64ToText(x, u.a); #else - return !sqlite3Atoi64(z, pResult, length, enc); -#endif /* SQLITE_OMIT_FLOATING_POINT */ + i = sizeof(u.a)-1; + u.a[i] = 0; + while( x>=10 ){ + int kk = (x%100)*2; + assert( TWO_BYTE_ALIGNMENT(&sqlite3DigitPairs.a[kk]) ); + assert( TWO_BYTE_ALIGNMENT(&u.a[i-2]) ); + *(u16*)(&u.a[i-2]) = *(u16*)&sqlite3DigitPairs.a[kk]; + i -= 2; + x /= 100; + } + if( x ){ + u.a[--i] = x + '0'; + } +#endif /* SQLITE_AVOID_U64_DIVIDE */ + if( v<0 ) u.a[--i] = '-'; + memcpy(zOut, &u.a[i], sizeof(u.a)-i); + return sizeof(u.a)-1-i; } /* @@ -30815,6 +37541,7 @@ static int compare2pow63(const char *zNum, int incr){ ** ** Returns: ** +** -1 Not even a prefix of the input text looks like an integer ** 0 Successful transformation. Fits in a 64-bit signed integer. ** 1 Excess non-space text after the integer value ** 2 Integer too large for a 64-bit signed integer or is malformed @@ -30828,8 +37555,8 @@ SQLITE_PRIVATE int sqlite3Atoi64(const char *zNum, i64 *pNum, int length, u8 enc int incr; u64 u = 0; int neg = 0; /* assume positive */ - int i; - int c = 0; + int i, j; + unsigned int c = 0; int nonNum = 0; /* True if input contains UTF16 with high byte non-zero */ int rc; /* Baseline return code */ const char *zStart; @@ -30839,6 +37566,7 @@ SQLITE_PRIVATE int sqlite3Atoi64(const char *zNum, i64 *pNum, int length, u8 enc incr = 1; }else{ incr = 2; + length &= ~1; assert( SQLITE_UTF16LE==2 && SQLITE_UTF16BE==3 ); for(i=3-enc; i='0' && c<='9'; i+=incr){ - u = u*10 + c - '0'; + for(i=0; &zNum[i]19*incr ? 1 : compare2pow63(zNum, incr); - if( c<0 ){ + j = i>19*incr ? 1 : compare2pow63(zNum, incr); + if( j<0 ){ /* zNum is less than 9223372036854775808 so it fits */ assert( u<=LARGEST_INT64 ); return rc; }else{ *pNum = neg ? SMALLEST_INT64 : LARGEST_INT64; - if( c>0 ){ + if( j>0 ){ /* zNum is greater than 9223372036854775808 so it overflows */ return 2; }else{ @@ -30938,11 +37666,15 @@ SQLITE_PRIVATE int sqlite3DecOrHexToI64(const char *z, i64 *pOut){ u = u*16 + sqlite3HexToInt(z[k]); } memcpy(pOut, &u, 8); - return (z[k]==0 && k-i<=16) ? 0 : 2; + if( k-i>16 ) return 2; + if( z[k]!=0 ) return 1; + return 0; }else #endif /* SQLITE_OMIT_HEX_INTEGER */ { - return sqlite3Atoi64(z, pOut, sqlite3Strlen30(z), SQLITE_UTF8); + int n = (int)(0x3fffffff&strspn(z,"+- \n\t0123456789")); + if( z[n] ) n++; + return sqlite3Atoi64(z, pOut, n, SQLITE_UTF8); } } @@ -30974,7 +37706,7 @@ SQLITE_PRIVATE int sqlite3GetInt32(const char *zNum, int *pValue){ u32 u = 0; zNum += 2; while( zNum[0]=='0' ) zNum++; - for(i=0; sqlite3Isxdigit(zNum[i]) && i<8; i++){ + for(i=0; i<8 && sqlite3Isxdigit(zNum[i]); i++){ u = u*16 + sqlite3HexToInt(zNum[i]); } if( (u&0x80000000)==0 && sqlite3Isxdigit(zNum[i])==0 ){ @@ -31017,10 +37749,192 @@ SQLITE_PRIVATE int sqlite3GetInt32(const char *zNum, int *pValue){ */ SQLITE_PRIVATE int sqlite3Atoi(const char *z){ int x = 0; - if( z ) sqlite3GetInt32(z, &x); + sqlite3GetInt32(z, &x); return x; } +/* +** Decode a floating-point value into an approximate decimal +** representation. +** +** If iRound<=0 then round to -iRound significant digits to the +** the right of the decimal point, or to a maximum of mxRound total +** significant digits. +** +** If iRound>0 round to min(iRound,mxRound) significant digits total. +** +** mxRound must be positive. +** +** The significant digits of the decimal representation are +** stored in p->z[] which is a often (but not always) a pointer +** into the middle of p->zBuf[]. There are p->n significant digits. +** The p->z[] array is *not* zero-terminated. +*/ +SQLITE_PRIVATE void sqlite3FpDecode(FpDecode *p, double r, int iRound, int mxRound){ + int i; /* Index into zBuf[] where to put next character */ + int n; /* Number of digits */ + u64 v; /* mantissa */ + int e, exp = 0; /* Base-2 and base-10 exponent */ + char *zBuf; /* Local alias for p->zBuf */ + char *z; /* Local alias for p->z */ + + p->isSpecial = 0; + assert( mxRound>0 ); + + /* Convert negative numbers to positive. Deal with Infinity, 0.0, and + ** NaN. */ + if( r<0.0 ){ + p->sign = '-'; + r = -r; + }else if( r==0.0 ){ + p->sign = '+'; + p->n = 1; + p->iDP = 1; + p->z = "0"; + return; + }else{ + p->sign = '+'; + } + memcpy(&v,&r,8); + e = (v>>52)&0x7ff; + if( e==0x7ff ){ + p->isSpecial = 1 + (v!=0x7ff0000000000000LL); + p->n = 0; + p->iDP = 0; + p->z = p->zBuf; + return; + } + v &= 0x000fffffffffffffULL; + if( e==0 ){ + int nn = countLeadingZeros(v); + v <<= nn; + e = -1074 - nn; + }else{ + v = (v<<11) | U64_BIT(63); + e -= 1086; + } + sqlite3Fp2Convert10(v, e, (iRound<=0||iRound>=18)?18:iRound+1, &v, &exp); + + /* Extract significant digits, start at the right-most slot in p->zBuf + ** and working back to the right. "i" keeps track of the next slot in + ** which to store a digit. */ + assert( sizeof(p->zBuf)==SQLITE_U64_DIGITS+1 ); + assert( v>0 ); + zBuf = p->zBuf; +#ifdef SQLITE_AVOID_U64_DIVIDE + i = sqlite3UInt64ToText(v, zBuf); +#else + i = SQLITE_U64_DIGITS; + while( v>=10 ){ + int kk = (v%100)*2; + assert( TWO_BYTE_ALIGNMENT(&sqlite3DigitPairs.a[kk]) ); + assert( TWO_BYTE_ALIGNMENT(&zBuf[i]) ); + assert( i-2>=0 ); + *(u16*)(&zBuf[i-2]) = *(u16*)&sqlite3DigitPairs.a[kk]; + i -= 2; + v /= 100; + } + if( v ){ + assert( v<10 ); + assert( i>0 ); + zBuf[--i] = v + '0'; + } +#endif /* SQLITE_AVOID_U64_DIVIDE */ + assert( i>=0 && i0 ); + assert( n<=SQLITE_U64_DIGITS ); + p->iDP = n + exp; + if( iRound<=0 ){ + iRound = p->iDP - iRound; + if( iRound==0 && zBuf[i]>='5' ){ + iRound = 1; + zBuf[--i] = '0'; + n++; + p->iDP++; + } + } + z = &zBuf[i]; /* z points to the first digit */ + if( iRound>0 && (iRoundmxRound) ){ + if( iRound>mxRound ) iRound = mxRound; + if( iRound==17 ){ + /* If the precision is exactly 17, which only happens with the "!" + ** flag (ex: "%!.17g") then try to reduce the precision if that + ** yields text that will round-trip to the original floating-point. + ** value. Thus, for exaple, 49.47 will render as 49.47, rather than + ** as 49.469999999999999. */ + if( z[15]=='9' && z[14]=='9' ){ + int jj, kk; + u64 v2; + for(jj=14; jj>0 && z[jj-1]=='9'; jj--){} + if( jj==0 ){ + v2 = 1; + }else{ + v2 = z[0] - '0'; + for(kk=1; kkiDP>=n || (z[15]=='0' && z[14]=='0' && z[13]=='0') ){ + int jj, kk; + u64 v2; + assert( z[0]!='0' ); + for(jj=13; z[jj-1]=='0'; jj--){} + v2 = z[0] - '0'; + for(kk=1; kk='5' ){ + int j = iRound-1; + while( 1 /*exit-by-break*/ ){ + z[j]++; + if( z[j]<='9' ) break; + z[j] = '0'; + if( j==0 ){ + z--; + z[0] = '1'; + n++; + p->iDP++; + break; + }else{ + j--; + } + } + } + } + assert( n>0 ); + while( z[n-1]=='0' ){ + n--; + assert( n>0 ); + } + p->n = n; + p->z = z; +} + +/* +** Try to convert z into an unsigned 32-bit integer. Return true on +** success and false if there is an error. +** +** Only decimal notation is accepted. +*/ +SQLITE_PRIVATE int sqlite3GetUInt32(const char *z, u32 *pI){ + u64 v = 0; + int i; + for(i=0; sqlite3Isdigit(z[i]); i++){ + v = v*10 + z[i] - '0'; + if( v>4294967296LL ){ *pI = 0; return 0; } + } + if( i==0 || z[i]!=0 ){ *pI = 0; return 0; } + *pI = (u32)v; + return 1; +} + /* ** The variable-length integer encoding is as follows: ** @@ -31061,7 +37975,7 @@ static int SQLITE_NOINLINE putVarint64(unsigned char *p, u64 v){ v >>= 7; } return 9; - } + } n = 0; do{ buf[n++] = (u8)((v & 0x7f) | 0x80); @@ -31107,23 +38021,12 @@ SQLITE_PRIVATE int sqlite3PutVarint(unsigned char *p, u64 v){ SQLITE_PRIVATE u8 sqlite3GetVarint(const unsigned char *p, u64 *v){ u32 a,b,s; - a = *p; - /* a: p0 (unmasked) */ - if (!(a&0x80)) - { - *v = a; + if( ((signed char*)p)[0]>=0 ){ + *v = *p; return 1; } - - p++; - b = *p; - /* b: p1 (unmasked) */ - if (!(b&0x80)) - { - a &= 0x7f; - a = a<<7; - a |= b; - *v = a; + if( ((signed char*)p)[1]>=0 ){ + *v = ((u32)(p[0]&0x7f)<<7) | p[1]; return 2; } @@ -31131,8 +38034,9 @@ SQLITE_PRIVATE u8 sqlite3GetVarint(const unsigned char *p, u64 *v){ assert( SLOT_2_0 == ((0x7f<<14) | (0x7f)) ); assert( SLOT_4_2_0 == ((0xfU<<28) | (0x7f<<14) | (0x7f)) ); - p++; - a = a<<14; + a = ((u32)p[0])<<14; + b = p[1]; + p += 2; a |= *p; /* a: p0<<14 | p2 (unmasked) */ if (!(a&0x80)) @@ -31271,127 +38175,37 @@ SQLITE_PRIVATE u8 sqlite3GetVarint(const unsigned char *p, u64 *v){ ** If the varint stored in p[0] is larger than can fit in a 32-bit unsigned ** integer, then set *v to 0xffffffff. ** -** A MACRO version, getVarint32, is provided which inlines the -** single-byte case. All code should use the MACRO version as +** A MACRO version, getVarint32, is provided which inlines the +** single-byte case. All code should use the MACRO version as ** this function assumes the single-byte case has already been handled. */ SQLITE_PRIVATE u8 sqlite3GetVarint32(const unsigned char *p, u32 *v){ - u32 a,b; + u64 v64; + u8 n; - /* The 1-byte case. Overwhelmingly the most common. Handled inline - ** by the getVarin32() macro */ - a = *p; - /* a: p0 (unmasked) */ -#ifndef getVarint32 - if (!(a&0x80)) - { - /* Values between 0 and 127 */ - *v = a; - return 1; - } -#endif + /* Assume that the single-byte case has already been handled by + ** the getVarint32() macro */ + assert( (p[0] & 0x80)!=0 ); - /* The 2-byte case */ - p++; - b = *p; - /* b: p1 (unmasked) */ - if (!(b&0x80)) - { - /* Values between 128 and 16383 */ - a &= 0x7f; - a = a<<7; - *v = a | b; + if( (p[1] & 0x80)==0 ){ + /* This is the two-byte case */ + *v = ((p[0]&0x7f)<<7) | p[1]; return 2; } - - /* The 3-byte case */ - p++; - a = a<<14; - a |= *p; - /* a: p0<<14 | p2 (unmasked) */ - if (!(a&0x80)) - { - /* Values between 16384 and 2097151 */ - a &= (0x7f<<14)|(0x7f); - b &= 0x7f; - b = b<<7; - *v = a | b; + if( (p[2] & 0x80)==0 ){ + /* This is the three-byte case */ + *v = ((p[0]&0x7f)<<14) | ((p[1]&0x7f)<<7) | p[2]; return 3; } - - /* A 32-bit varint is used to store size information in btrees. - ** Objects are rarely larger than 2MiB limit of a 3-byte varint. - ** A 3-byte varint is sufficient, for example, to record the size - ** of a 1048569-byte BLOB or string. - ** - ** We only unroll the first 1-, 2-, and 3- byte cases. The very - ** rare larger cases can be handled by the slower 64-bit varint - ** routine. - */ -#if 1 - { - u64 v64; - u8 n; - - p -= 2; - n = sqlite3GetVarint(p, &v64); - assert( n>3 && n<=9 ); - if( (v64 & SQLITE_MAX_U32)!=v64 ){ - *v = 0xffffffff; - }else{ - *v = (u32)v64; - } - return n; - } - -#else - /* For following code (kept for historical record only) shows an - ** unrolling for the 3- and 4-byte varint cases. This code is - ** slightly faster, but it is also larger and much harder to test. - */ - p++; - b = b<<14; - b |= *p; - /* b: p1<<14 | p3 (unmasked) */ - if (!(b&0x80)) - { - /* Values between 2097152 and 268435455 */ - b &= (0x7f<<14)|(0x7f); - a &= (0x7f<<14)|(0x7f); - a = a<<7; - *v = a | b; - return 4; - } - - p++; - a = a<<14; - a |= *p; - /* a: p0<<28 | p2<<14 | p4 (unmasked) */ - if (!(a&0x80)) - { - /* Values between 268435456 and 34359738367 */ - a &= SLOT_4_2_0; - b &= SLOT_4_2_0; - b = b<<7; - *v = a | b; - return 5; - } - - /* We can only reach this point when reading a corrupt database - ** file. In that case we are not in any hurry. Use the (relatively - ** slow) general-purpose sqlite3GetVarint() routine to extract the - ** value. */ - { - u64 v64; - u8 n; - - p -= 4; - n = sqlite3GetVarint(p, &v64); - assert( n>5 && n<=9 ); + /* four or more bytes */ + n = sqlite3GetVarint(p, &v64); + assert( n>3 && n<=9 ); + if( (v64 & SQLITE_MAX_U32)!=v64 ){ + *v = 0xffffffff; + }else{ *v = (u32)v64; - return n; } -#endif + return n; } /* @@ -31461,7 +38275,7 @@ SQLITE_PRIVATE u8 sqlite3HexToInt(int h){ return (u8)(h & 0xf); } -#if !defined(SQLITE_OMIT_BLOB_LITERAL) || defined(SQLITE_HAS_CODEC) +#if !defined(SQLITE_OMIT_BLOB_LITERAL) /* ** Convert a BLOB literal of the form "x'hhhhhh'" into its binary ** value. Return a pointer to its binary value. Space to hold the @@ -31482,7 +38296,7 @@ SQLITE_PRIVATE void *sqlite3HexToBlob(sqlite3 *db, const char *z, int n){ } return zBlob; } -#endif /* !SQLITE_OMIT_BLOB_LITERAL || SQLITE_HAS_CODEC */ +#endif /* !SQLITE_OMIT_BLOB_LITERAL */ /* ** Log an error that is an API call on a connection pointer that should @@ -31490,7 +38304,7 @@ SQLITE_PRIVATE void *sqlite3HexToBlob(sqlite3 *db, const char *z, int n){ ** argument. The zType is a word like "NULL" or "closed" or "invalid". */ static void logBadConnection(const char *zType){ - sqlite3_log(SQLITE_MISUSE, + sqlite3_log(SQLITE_MISUSE, "API call with %s database connection pointer", zType ); @@ -31511,13 +38325,13 @@ static void logBadConnection(const char *zType){ ** used as an argument to sqlite3_errmsg() or sqlite3_close(). */ SQLITE_PRIVATE int sqlite3SafetyCheckOk(sqlite3 *db){ - u32 magic; + u8 eOpenState; if( db==0 ){ logBadConnection("NULL"); return 0; } - magic = db->magic; - if( magic!=SQLITE_MAGIC_OPEN ){ + eOpenState = db->eOpenState; + if( eOpenState!=SQLITE_STATE_OPEN ){ if( sqlite3SafetyCheckSickOrOk(db) ){ testcase( sqlite3GlobalConfig.xLog!=0 ); logBadConnection("unopened"); @@ -31528,11 +38342,11 @@ SQLITE_PRIVATE int sqlite3SafetyCheckOk(sqlite3 *db){ } } SQLITE_PRIVATE int sqlite3SafetyCheckSickOrOk(sqlite3 *db){ - u32 magic; - magic = db->magic; - if( magic!=SQLITE_MAGIC_SICK && - magic!=SQLITE_MAGIC_OPEN && - magic!=SQLITE_MAGIC_BUSY ){ + u8 eOpenState; + eOpenState = db->eOpenState; + if( eOpenState!=SQLITE_STATE_SICK && + eOpenState!=SQLITE_STATE_OPEN && + eOpenState!=SQLITE_STATE_BUSY ){ testcase( sqlite3GlobalConfig.xLog!=0 ); logBadConnection("invalid"); return 0; @@ -31542,7 +38356,7 @@ SQLITE_PRIVATE int sqlite3SafetyCheckSickOrOk(sqlite3 *db){ } /* -** Attempt to add, substract, or multiply the 64-bit signed value iB against +** Attempt to add, subtract, or multiply the 64-bit signed value iB against ** the other 64-bit signed integer at *pA and store the result in *pA. ** Return 0 on success. Or if the operation would have resulted in an ** overflow, leave *pA unchanged and return 1. @@ -31564,7 +38378,7 @@ SQLITE_PRIVATE int sqlite3AddInt64(i64 *pA, i64 iB){ if( iA<0 && -(iA + LARGEST_INT64) > iB + 1 ) return 1; } *pA += iB; - return 0; + return 0; #endif } SQLITE_PRIVATE int sqlite3SubInt64(i64 *pA, i64 iB){ @@ -31605,7 +38419,7 @@ SQLITE_PRIVATE int sqlite3MulInt64(i64 *pA, i64 iB){ } /* -** Compute the absolute value of a 32-bit signed integer, of possible. Or +** Compute the absolute value of a 32-bit signed integer, if possible. Or ** if the integer has a value of -2147483648, return +2147483647 */ SQLITE_PRIVATE int sqlite3AbsInt32(int x){ @@ -31645,11 +38459,11 @@ SQLITE_PRIVATE void sqlite3FileSuffix3(const char *zBaseFilename, char *z){ } #endif -/* +/* ** Find (an approximate) sum of two LogEst values. This computation is ** not a simple "+" operator because LogEst is stored as a logarithmic ** value. -** +** */ SQLITE_PRIVATE LogEst sqlite3LogEstAdd(LogEst a, LogEst b){ static const unsigned char x[] = { @@ -31697,7 +38511,6 @@ SQLITE_PRIVATE LogEst sqlite3LogEst(u64 x){ return a[x&7] + y - 10; } -#ifndef SQLITE_OMIT_VIRTUALTABLE /* ** Convert a double into a LogEst ** In other words, compute an approximation for 10*log2(x). @@ -31712,16 +38525,9 @@ SQLITE_PRIVATE LogEst sqlite3LogEstFromDouble(double x){ e = (a>>52) - 1022; return e*10; } -#endif /* SQLITE_OMIT_VIRTUALTABLE */ -#if defined(SQLITE_ENABLE_STMT_SCANSTATUS) || \ - defined(SQLITE_ENABLE_STAT3_OR_STAT4) || \ - defined(SQLITE_EXPLAIN_ESTIMATED_ROWS) /* ** Convert a LogEst into an integer. -** -** Note that this routine is only used when one or more of various -** non-standard compile-time options is enabled. */ SQLITE_PRIVATE u64 sqlite3LogEstToInt(LogEst x){ u64 n; @@ -31729,17 +38535,9 @@ SQLITE_PRIVATE u64 sqlite3LogEstToInt(LogEst x){ x /= 10; if( n>=5 ) n -= 2; else if( n>=1 ) n -= 1; -#if defined(SQLITE_ENABLE_STMT_SCANSTATUS) || \ - defined(SQLITE_EXPLAIN_ESTIMATED_ROWS) if( x>60 ) return (u64)LARGEST_INT64; -#else - /* If only SQLITE_ENABLE_STAT3_OR_STAT4 is on, then the largest input - ** possible to this routine is 310, resulting in a maximum x of 31 */ - assert( x<=60 ); -#endif return x>=3 ? (n+8)<<(x-3) : (n+8)>>(3-x); } -#endif /* defined SCANSTAT or STAT4 or ESTIMATED_ROWS */ /* ** Add a new name/number pair to a VList. This might require that the @@ -31763,8 +38561,8 @@ SQLITE_PRIVATE u64 sqlite3LogEstToInt(LogEst x){ ** Conceptually: ** ** struct VList { -** int nAlloc; // Number of allocated slots -** int nUsed; // Number of used slots +** int nAlloc; // Number of allocated slots +** int nUsed; // Number of used slots ** struct VListEntry { ** int iValue; // Value for this entry ** int nSlot; // Slots used by this entry @@ -31773,7 +38571,7 @@ SQLITE_PRIVATE u64 sqlite3LogEstToInt(LogEst x){ ** } ** ** During code generation, pointers to the variable names within the -** VList are taken. When that happens, nAlloc is set to zero as an +** VList are taken. When that happens, nAlloc is set to zero as an ** indication that the VList may never again be enlarged, since the ** accompanying realloc() would invalidate the pointers. */ @@ -31902,12 +38700,19 @@ SQLITE_PRIVATE void sqlite3HashClear(Hash *pH){ */ static unsigned int strHash(const char *z){ unsigned int h = 0; - unsigned char c; - while( (c = (unsigned char)*z++)!=0 ){ /*OPTIMIZATION-IF-TRUE*/ + while( z[0] ){ /*OPTIMIZATION-IF-TRUE*/ /* Knuth multiplicative hashing. (Sorting & Searching, p. 510). ** 0x9e3779b1 is 2654435761 which is the closest prime number to - ** (2**32)*golden_ratio, where golden_ratio = (sqrt(5) - 1)/2. */ - h += sqlite3UpperToLower[c]; + ** (2**32)*golden_ratio, where golden_ratio = (sqrt(5) - 1)/2. + ** + ** Only bits 0xdf for ASCII and bits 0xbf for EBCDIC each octet are + ** hashed since the omitted bits determine the upper/lower case difference. + */ +#ifdef SQLITE_EBCDIC + h += 0xbf & (unsigned char)*(z++); +#else + h += 0xdf & (unsigned char)*(z++); +#endif h *= 0x9e3779b1; } return h; @@ -31945,7 +38750,7 @@ static void insertElement( } -/* Resize the hash table so that it cantains "new_size" buckets. +/* Resize the hash table so that it contains "new_size" buckets. ** ** The hash table might fail to resize if sqlite3_malloc() fails or ** if the new size is the same as the prior size. @@ -31964,7 +38769,7 @@ static int rehash(Hash *pH, unsigned int new_size){ /* The inability to allocates space for a larger hash table is ** a performance hit but it is not a fatal error. So mark the - ** allocation as a benign. Use sqlite3Malloc()/memset(0) instead of + ** allocation as a benign. Use sqlite3Malloc()/memset(0) instead of ** sqlite3MallocZero() to make the allocation, as sqlite3MallocZero() ** only zeroes the requested number of bytes whereas this module will ** use the actual amount of space allocated for the hash table (which @@ -31980,9 +38785,8 @@ static int rehash(Hash *pH, unsigned int new_size){ pH->htsize = new_size = sqlite3MallocSize(new_ht)/sizeof(struct _ht); memset(new_ht, 0, new_size*sizeof(struct _ht)); for(elem=pH->first, pH->first=0; elem; elem = next_elem){ - unsigned int h = strHash(elem->pKey) % new_size; next_elem = elem->next; - insertElement(pH, &new_ht[h], elem); + insertElement(pH, &new_ht[elem->h % new_size], elem); } return 1; } @@ -32000,26 +38804,26 @@ static HashElem *findElementWithHash( HashElem *elem; /* Used to loop thru the element list */ unsigned int count; /* Number of elements left to test */ unsigned int h; /* The computed hash */ - static HashElem nullElement = { 0, 0, 0, 0 }; + static HashElem nullElement = { 0, 0, 0, 0, 0 }; + h = strHash(pKey); if( pH->ht ){ /*OPTIMIZATION-IF-TRUE*/ struct _ht *pEntry; - h = strHash(pKey) % pH->htsize; - pEntry = &pH->ht[h]; + pEntry = &pH->ht[h % pH->htsize]; elem = pEntry->chain; count = pEntry->count; }else{ - h = 0; elem = pH->first; count = pH->count; } if( pHash ) *pHash = h; - while( count-- ){ + while( count ){ assert( elem!=0 ); - if( sqlite3StrICmp(elem->pKey,pKey)==0 ){ + if( h==elem->h && sqlite3StrICmp(elem->pKey,pKey)==0 ){ return elem; } elem = elem->next; + count--; } return &nullElement; } @@ -32027,14 +38831,13 @@ static HashElem *findElementWithHash( /* Remove a single entry from the hash table given a pointer to that ** element and a hash on the element's key. */ -static void removeElementGivenHash( +static void removeElement( Hash *pH, /* The pH containing "elem" */ - HashElem* elem, /* The element to be removed from the pH */ - unsigned int h /* Hash value for the element */ + HashElem *elem /* The element to be removed from the pH */ ){ struct _ht *pEntry; if( elem->prev ){ - elem->prev->next = elem->next; + elem->prev->next = elem->next; }else{ pH->first = elem->next; } @@ -32042,7 +38845,7 @@ static void removeElementGivenHash( elem->next->prev = elem->prev; } if( pH->ht ){ - pEntry = &pH->ht[h]; + pEntry = &pH->ht[elem->h % pH->htsize]; if( pEntry->chain==elem ){ pEntry->chain = elem->next; } @@ -32093,7 +38896,7 @@ SQLITE_PRIVATE void *sqlite3HashInsert(Hash *pH, const char *pKey, void *data){ if( elem->data ){ void *old_data = elem->data; if( data==0 ){ - removeElementGivenHash(pH,elem,h); + removeElement(pH,elem); }else{ elem->data = data; elem->pKey = pKey; @@ -32104,18 +38907,17 @@ SQLITE_PRIVATE void *sqlite3HashInsert(Hash *pH, const char *pKey, void *data){ new_elem = (HashElem*)sqlite3Malloc( sizeof(HashElem) ); if( new_elem==0 ) return data; new_elem->pKey = pKey; + new_elem->h = h; new_elem->data = data; pH->count++; - if( pH->count>=10 && pH->count > 2*pH->htsize ){ - if( rehash(pH, pH->count*2) ){ - assert( pH->htsize>0 ); - h = strHash(pKey) % pH->htsize; - } + if( pH->count>=5 && pH->count > 2*pH->htsize ){ + rehash(pH, pH->count*3); } - insertElement(pH, pH->ht ? &pH->ht[h] : 0, new_elem); + insertElement(pH, pH->ht ? &pH->ht[new_elem->h % pH->htsize] : 0, new_elem); return 0; } + /************** End of hash.c ************************************************/ /************** Begin file opcodes.c *****************************************/ /* Automatically generated. Do not edit */ @@ -32133,29 +38935,29 @@ SQLITE_PRIVATE const char *sqlite3OpcodeName(int i){ /* 0 */ "Savepoint" OpHelp(""), /* 1 */ "AutoCommit" OpHelp(""), /* 2 */ "Transaction" OpHelp(""), - /* 3 */ "SorterNext" OpHelp(""), - /* 4 */ "Prev" OpHelp(""), - /* 5 */ "Next" OpHelp(""), - /* 6 */ "Checkpoint" OpHelp(""), - /* 7 */ "JournalMode" OpHelp(""), - /* 8 */ "Vacuum" OpHelp(""), - /* 9 */ "VFilter" OpHelp("iplan=r[P3] zplan='P4'"), - /* 10 */ "VUpdate" OpHelp("data=r[P3@P2]"), - /* 11 */ "Goto" OpHelp(""), - /* 12 */ "Gosub" OpHelp(""), - /* 13 */ "InitCoroutine" OpHelp(""), - /* 14 */ "Yield" OpHelp(""), - /* 15 */ "MustBeInt" OpHelp(""), - /* 16 */ "Jump" OpHelp(""), - /* 17 */ "Once" OpHelp(""), - /* 18 */ "If" OpHelp(""), + /* 3 */ "Checkpoint" OpHelp(""), + /* 4 */ "JournalMode" OpHelp(""), + /* 5 */ "Vacuum" OpHelp(""), + /* 6 */ "VFilter" OpHelp("iplan=r[P3] zplan='P4'"), + /* 7 */ "VUpdate" OpHelp("data=r[P3@P2]"), + /* 8 */ "Init" OpHelp("Start at P2"), + /* 9 */ "Goto" OpHelp(""), + /* 10 */ "Gosub" OpHelp(""), + /* 11 */ "InitCoroutine" OpHelp(""), + /* 12 */ "Yield" OpHelp(""), + /* 13 */ "MustBeInt" OpHelp(""), + /* 14 */ "Jump" OpHelp(""), + /* 15 */ "Once" OpHelp(""), + /* 16 */ "If" OpHelp(""), + /* 17 */ "IfNot" OpHelp(""), + /* 18 */ "IsType" OpHelp("if typeof(P1.P3) in P5 goto P2"), /* 19 */ "Not" OpHelp("r[P2]= !r[P1]"), - /* 20 */ "IfNot" OpHelp(""), - /* 21 */ "IfNullRow" OpHelp("if P1.nullRow then r[P3]=NULL, goto P2"), - /* 22 */ "SeekLT" OpHelp("key=r[P3@P4]"), - /* 23 */ "SeekLE" OpHelp("key=r[P3@P4]"), - /* 24 */ "SeekGE" OpHelp("key=r[P3@P4]"), - /* 25 */ "SeekGT" OpHelp("key=r[P3@P4]"), + /* 20 */ "IfNullRow" OpHelp("if P1.nullRow then r[P3]=NULL, goto P2"), + /* 21 */ "SeekLT" OpHelp("key=r[P3@P4]"), + /* 22 */ "SeekLE" OpHelp("key=r[P3@P4]"), + /* 23 */ "SeekGE" OpHelp("key=r[P3@P4]"), + /* 24 */ "SeekGT" OpHelp("key=r[P3@P4]"), + /* 25 */ "IfNotOpen" OpHelp("if( !csr[P1] ) goto P2"), /* 26 */ "IfNoHope" OpHelp("key=r[P3@P4]"), /* 27 */ "NoConflict" OpHelp("key=r[P3@P4]"), /* 28 */ "NotFound" OpHelp("key=r[P3@P4]"), @@ -32163,152 +38965,1235 @@ SQLITE_PRIVATE const char *sqlite3OpcodeName(int i){ /* 30 */ "SeekRowid" OpHelp("intkey=r[P3]"), /* 31 */ "NotExists" OpHelp("intkey=r[P3]"), /* 32 */ "Last" OpHelp(""), - /* 33 */ "IfSmaller" OpHelp(""), + /* 33 */ "IfSizeBetween" OpHelp(""), /* 34 */ "SorterSort" OpHelp(""), /* 35 */ "Sort" OpHelp(""), /* 36 */ "Rewind" OpHelp(""), - /* 37 */ "IdxLE" OpHelp("key=r[P3@P4]"), - /* 38 */ "IdxGT" OpHelp("key=r[P3@P4]"), - /* 39 */ "IdxLT" OpHelp("key=r[P3@P4]"), - /* 40 */ "IdxGE" OpHelp("key=r[P3@P4]"), - /* 41 */ "RowSetRead" OpHelp("r[P3]=rowset(P1)"), - /* 42 */ "RowSetTest" OpHelp("if r[P3] in rowset(P1) goto P2"), + /* 37 */ "IfEmpty" OpHelp("if( empty(P1) ) goto P2"), + /* 38 */ "SorterNext" OpHelp(""), + /* 39 */ "Prev" OpHelp(""), + /* 40 */ "Next" OpHelp(""), + /* 41 */ "IdxLE" OpHelp("key=r[P3@P4]"), + /* 42 */ "IdxGT" OpHelp("key=r[P3@P4]"), /* 43 */ "Or" OpHelp("r[P3]=(r[P1] || r[P2])"), /* 44 */ "And" OpHelp("r[P3]=(r[P1] && r[P2])"), - /* 45 */ "Program" OpHelp(""), - /* 46 */ "FkIfZero" OpHelp("if fkctr[P1]==0 goto P2"), - /* 47 */ "IfPos" OpHelp("if r[P1]>0 then r[P1]-=P3, goto P2"), - /* 48 */ "IfNotZero" OpHelp("if r[P1]!=0 then r[P1]--, goto P2"), - /* 49 */ "DecrJumpZero" OpHelp("if (--r[P1])==0 goto P2"), - /* 50 */ "IsNull" OpHelp("if r[P1]==NULL goto P2"), - /* 51 */ "NotNull" OpHelp("if r[P1]!=NULL goto P2"), - /* 52 */ "Ne" OpHelp("IF r[P3]!=r[P1]"), - /* 53 */ "Eq" OpHelp("IF r[P3]==r[P1]"), - /* 54 */ "Gt" OpHelp("IF r[P3]>r[P1]"), - /* 55 */ "Le" OpHelp("IF r[P3]<=r[P1]"), - /* 56 */ "Lt" OpHelp("IF r[P3]=r[P1]"), - /* 58 */ "ElseNotEq" OpHelp(""), - /* 59 */ "IncrVacuum" OpHelp(""), - /* 60 */ "VNext" OpHelp(""), - /* 61 */ "Init" OpHelp("Start at P2"), - /* 62 */ "PureFunc0" OpHelp(""), - /* 63 */ "Function0" OpHelp("r[P3]=func(r[P2@P5])"), - /* 64 */ "PureFunc" OpHelp(""), - /* 65 */ "Function" OpHelp("r[P3]=func(r[P2@P5])"), - /* 66 */ "Return" OpHelp(""), - /* 67 */ "EndCoroutine" OpHelp(""), - /* 68 */ "HaltIfNull" OpHelp("if r[P3]=null halt"), - /* 69 */ "Halt" OpHelp(""), - /* 70 */ "Integer" OpHelp("r[P2]=P1"), - /* 71 */ "Int64" OpHelp("r[P2]=P4"), - /* 72 */ "String" OpHelp("r[P2]='P4' (len=P1)"), - /* 73 */ "Null" OpHelp("r[P2..P3]=NULL"), - /* 74 */ "SoftNull" OpHelp("r[P1]=NULL"), - /* 75 */ "Blob" OpHelp("r[P2]=P4 (len=P1)"), - /* 76 */ "Variable" OpHelp("r[P2]=parameter(P1,P4)"), - /* 77 */ "Move" OpHelp("r[P2@P3]=r[P1@P3]"), - /* 78 */ "Copy" OpHelp("r[P2@P3+1]=r[P1@P3+1]"), - /* 79 */ "SCopy" OpHelp("r[P2]=r[P1]"), - /* 80 */ "IntCopy" OpHelp("r[P2]=r[P1]"), - /* 81 */ "ResultRow" OpHelp("output=r[P1@P2]"), - /* 82 */ "CollSeq" OpHelp(""), - /* 83 */ "AddImm" OpHelp("r[P1]=r[P1]+P2"), - /* 84 */ "RealAffinity" OpHelp(""), - /* 85 */ "Cast" OpHelp("affinity(r[P1])"), - /* 86 */ "Permutation" OpHelp(""), - /* 87 */ "Compare" OpHelp("r[P1@P3] <-> r[P2@P3]"), - /* 88 */ "IsTrue" OpHelp("r[P2] = coalesce(r[P1]==TRUE,P3) ^ P4"), - /* 89 */ "Offset" OpHelp("r[P3] = sqlite_offset(P1)"), - /* 90 */ "Column" OpHelp("r[P3]=PX"), - /* 91 */ "Affinity" OpHelp("affinity(r[P1@P2])"), - /* 92 */ "MakeRecord" OpHelp("r[P3]=mkrec(r[P1@P2])"), - /* 93 */ "Count" OpHelp("r[P2]=count()"), - /* 94 */ "ReadCookie" OpHelp(""), - /* 95 */ "SetCookie" OpHelp(""), - /* 96 */ "BitAnd" OpHelp("r[P3]=r[P1]&r[P2]"), - /* 97 */ "BitOr" OpHelp("r[P3]=r[P1]|r[P2]"), - /* 98 */ "ShiftLeft" OpHelp("r[P3]=r[P2]<>r[P1]"), - /* 100 */ "Add" OpHelp("r[P3]=r[P1]+r[P2]"), - /* 101 */ "Subtract" OpHelp("r[P3]=r[P2]-r[P1]"), - /* 102 */ "Multiply" OpHelp("r[P3]=r[P1]*r[P2]"), - /* 103 */ "Divide" OpHelp("r[P3]=r[P2]/r[P1]"), - /* 104 */ "Remainder" OpHelp("r[P3]=r[P2]%r[P1]"), - /* 105 */ "Concat" OpHelp("r[P3]=r[P2]+r[P1]"), - /* 106 */ "ReopenIdx" OpHelp("root=P2 iDb=P3"), - /* 107 */ "BitNot" OpHelp("r[P2]= ~r[P1]"), - /* 108 */ "OpenRead" OpHelp("root=P2 iDb=P3"), - /* 109 */ "OpenWrite" OpHelp("root=P2 iDb=P3"), - /* 110 */ "String8" OpHelp("r[P2]='P4'"), - /* 111 */ "OpenDup" OpHelp(""), - /* 112 */ "OpenAutoindex" OpHelp("nColumn=P2"), - /* 113 */ "OpenEphemeral" OpHelp("nColumn=P2"), - /* 114 */ "SorterOpen" OpHelp(""), - /* 115 */ "SequenceTest" OpHelp("if( cursor[P1].ctr++ ) pc = P2"), - /* 116 */ "OpenPseudo" OpHelp("P3 columns in r[P2]"), - /* 117 */ "Close" OpHelp(""), - /* 118 */ "ColumnsUsed" OpHelp(""), - /* 119 */ "SeekHit" OpHelp("seekHit=P2"), - /* 120 */ "Sequence" OpHelp("r[P2]=cursor[P1].ctr++"), - /* 121 */ "NewRowid" OpHelp("r[P2]=rowid"), - /* 122 */ "Insert" OpHelp("intkey=r[P3] data=r[P2]"), - /* 123 */ "Delete" OpHelp(""), - /* 124 */ "ResetCount" OpHelp(""), - /* 125 */ "SorterCompare" OpHelp("if key(P1)!=trim(r[P3],P4) goto P2"), - /* 126 */ "SorterData" OpHelp("r[P2]=data"), - /* 127 */ "RowData" OpHelp("r[P2]=data"), - /* 128 */ "Rowid" OpHelp("r[P2]=rowid"), - /* 129 */ "NullRow" OpHelp(""), - /* 130 */ "SeekEnd" OpHelp(""), - /* 131 */ "SorterInsert" OpHelp("key=r[P2]"), - /* 132 */ "IdxInsert" OpHelp("key=r[P2]"), - /* 133 */ "IdxDelete" OpHelp("key=r[P2@P3]"), - /* 134 */ "DeferredSeek" OpHelp("Move P3 to P1.rowid if needed"), - /* 135 */ "IdxRowid" OpHelp("r[P2]=rowid"), - /* 136 */ "Destroy" OpHelp(""), - /* 137 */ "Clear" OpHelp(""), - /* 138 */ "ResetSorter" OpHelp(""), - /* 139 */ "CreateBtree" OpHelp("r[P2]=root iDb=P1 flags=P3"), - /* 140 */ "SqlExec" OpHelp(""), - /* 141 */ "ParseSchema" OpHelp(""), - /* 142 */ "LoadAnalysis" OpHelp(""), - /* 143 */ "DropTable" OpHelp(""), - /* 144 */ "DropIndex" OpHelp(""), - /* 145 */ "Real" OpHelp("r[P2]=P4"), - /* 146 */ "DropTrigger" OpHelp(""), - /* 147 */ "IntegrityCk" OpHelp(""), - /* 148 */ "RowSetAdd" OpHelp("rowset(P1)=r[P2]"), - /* 149 */ "Param" OpHelp(""), - /* 150 */ "FkCounter" OpHelp("fkctr[P1]+=P2"), - /* 151 */ "MemMax" OpHelp("r[P1]=max(r[P1],r[P2])"), - /* 152 */ "OffsetLimit" OpHelp("if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1)"), - /* 153 */ "AggInverse" OpHelp("accum=r[P3] inverse(r[P2@P5])"), - /* 154 */ "AggStep" OpHelp("accum=r[P3] step(r[P2@P5])"), - /* 155 */ "AggStep1" OpHelp("accum=r[P3] step(r[P2@P5])"), - /* 156 */ "AggValue" OpHelp("r[P3]=value N=P2"), - /* 157 */ "AggFinal" OpHelp("accum=r[P1] N=P2"), - /* 158 */ "Expire" OpHelp(""), - /* 159 */ "TableLock" OpHelp("iDb=P1 root=P2 write=P3"), - /* 160 */ "VBegin" OpHelp(""), - /* 161 */ "VCreate" OpHelp(""), - /* 162 */ "VDestroy" OpHelp(""), - /* 163 */ "VOpen" OpHelp(""), - /* 164 */ "VColumn" OpHelp("r[P3]=vcolumn(P2)"), - /* 165 */ "VRename" OpHelp(""), - /* 166 */ "Pagecount" OpHelp(""), - /* 167 */ "MaxPgcnt" OpHelp(""), - /* 168 */ "Trace" OpHelp(""), - /* 169 */ "CursorHint" OpHelp(""), - /* 170 */ "Noop" OpHelp(""), - /* 171 */ "Explain" OpHelp(""), - /* 172 */ "Abortable" OpHelp(""), + /* 45 */ "IdxLT" OpHelp("key=r[P3@P4]"), + /* 46 */ "IdxGE" OpHelp("key=r[P3@P4]"), + /* 47 */ "IFindKey" OpHelp(""), + /* 48 */ "RowSetRead" OpHelp("r[P3]=rowset(P1)"), + /* 49 */ "RowSetTest" OpHelp("if r[P3] in rowset(P1) goto P2"), + /* 50 */ "Program" OpHelp(""), + /* 51 */ "IsNull" OpHelp("if r[P1]==NULL goto P2"), + /* 52 */ "NotNull" OpHelp("if r[P1]!=NULL goto P2"), + /* 53 */ "Ne" OpHelp("IF r[P3]!=r[P1]"), + /* 54 */ "Eq" OpHelp("IF r[P3]==r[P1]"), + /* 55 */ "Gt" OpHelp("IF r[P3]>r[P1]"), + /* 56 */ "Le" OpHelp("IF r[P3]<=r[P1]"), + /* 57 */ "Lt" OpHelp("IF r[P3]=r[P1]"), + /* 59 */ "ElseEq" OpHelp(""), + /* 60 */ "FkIfZero" OpHelp("if fkctr[P1]==0 goto P2"), + /* 61 */ "IfPos" OpHelp("if r[P1]>0 then r[P1]-=P3, goto P2"), + /* 62 */ "IfNotZero" OpHelp("if r[P1]!=0 then r[P1]--, goto P2"), + /* 63 */ "DecrJumpZero" OpHelp("if (--r[P1])==0 goto P2"), + /* 64 */ "IncrVacuum" OpHelp(""), + /* 65 */ "VNext" OpHelp(""), + /* 66 */ "Filter" OpHelp("if key(P3@P4) not in filter(P1) goto P2"), + /* 67 */ "PureFunc" OpHelp("r[P3]=func(r[P2@NP])"), + /* 68 */ "Function" OpHelp("r[P3]=func(r[P2@NP])"), + /* 69 */ "Return" OpHelp(""), + /* 70 */ "EndCoroutine" OpHelp(""), + /* 71 */ "HaltIfNull" OpHelp("if r[P3]=null halt"), + /* 72 */ "Halt" OpHelp(""), + /* 73 */ "Integer" OpHelp("r[P2]=P1"), + /* 74 */ "Int64" OpHelp("r[P2]=P4"), + /* 75 */ "String" OpHelp("r[P2]='P4' (len=P1)"), + /* 76 */ "BeginSubrtn" OpHelp("r[P2]=NULL"), + /* 77 */ "Null" OpHelp("r[P2..P3]=NULL"), + /* 78 */ "SoftNull" OpHelp("r[P1]=NULL"), + /* 79 */ "Blob" OpHelp("r[P2]=P4 (len=P1)"), + /* 80 */ "Variable" OpHelp("r[P2]=parameter(P1)"), + /* 81 */ "Move" OpHelp("r[P2@P3]=r[P1@P3]"), + /* 82 */ "Copy" OpHelp("r[P2@P3+1]=r[P1@P3+1]"), + /* 83 */ "SCopy" OpHelp("r[P2]=r[P1]"), + /* 84 */ "IntCopy" OpHelp("r[P2]=r[P1]"), + /* 85 */ "FkCheck" OpHelp(""), + /* 86 */ "ResultRow" OpHelp("output=r[P1@P2]"), + /* 87 */ "CollSeq" OpHelp(""), + /* 88 */ "AddImm" OpHelp("r[P1]=r[P1]+P2"), + /* 89 */ "RealAffinity" OpHelp(""), + /* 90 */ "Cast" OpHelp("affinity(r[P1])"), + /* 91 */ "Permutation" OpHelp(""), + /* 92 */ "Compare" OpHelp("r[P1@P3] <-> r[P2@P3]"), + /* 93 */ "IsTrue" OpHelp("r[P2] = coalesce(r[P1]==TRUE,P3) ^ P4"), + /* 94 */ "ZeroOrNull" OpHelp("r[P2] = 0 OR NULL"), + /* 95 */ "Offset" OpHelp("r[P3] = sqlite_offset(P1)"), + /* 96 */ "Column" OpHelp("r[P3]=PX cursor P1 column P2"), + /* 97 */ "TypeCheck" OpHelp("typecheck(r[P1@P2])"), + /* 98 */ "Affinity" OpHelp("affinity(r[P1@P2])"), + /* 99 */ "MakeRecord" OpHelp("r[P3]=mkrec(r[P1@P2])"), + /* 100 */ "Count" OpHelp("r[P2]=count()"), + /* 101 */ "ReadCookie" OpHelp(""), + /* 102 */ "SetCookie" OpHelp(""), + /* 103 */ "BitAnd" OpHelp("r[P3]=r[P1]&r[P2]"), + /* 104 */ "BitOr" OpHelp("r[P3]=r[P1]|r[P2]"), + /* 105 */ "ShiftLeft" OpHelp("r[P3]=r[P2]<>r[P1]"), + /* 107 */ "Add" OpHelp("r[P3]=r[P1]+r[P2]"), + /* 108 */ "Subtract" OpHelp("r[P3]=r[P2]-r[P1]"), + /* 109 */ "Multiply" OpHelp("r[P3]=r[P1]*r[P2]"), + /* 110 */ "Divide" OpHelp("r[P3]=r[P2]/r[P1]"), + /* 111 */ "Remainder" OpHelp("r[P3]=r[P2]%r[P1]"), + /* 112 */ "Concat" OpHelp("r[P3]=r[P2]+r[P1]"), + /* 113 */ "ReopenIdx" OpHelp("root=P2 iDb=P3"), + /* 114 */ "OpenRead" OpHelp("root=P2 iDb=P3"), + /* 115 */ "BitNot" OpHelp("r[P2]= ~r[P1]"), + /* 116 */ "OpenWrite" OpHelp("root=P2 iDb=P3"), + /* 117 */ "OpenDup" OpHelp(""), + /* 118 */ "String8" OpHelp("r[P2]='P4'"), + /* 119 */ "OpenAutoindex" OpHelp("nColumn=P2"), + /* 120 */ "OpenEphemeral" OpHelp("nColumn=P2"), + /* 121 */ "SorterOpen" OpHelp(""), + /* 122 */ "SequenceTest" OpHelp("if( cursor[P1].ctr++ ) pc = P2"), + /* 123 */ "OpenPseudo" OpHelp("P3 columns in r[P2]"), + /* 124 */ "Close" OpHelp(""), + /* 125 */ "ColumnsUsed" OpHelp(""), + /* 126 */ "SeekScan" OpHelp("Scan-ahead up to P1 rows"), + /* 127 */ "SeekHit" OpHelp("set P2<=seekHit<=P3"), + /* 128 */ "Sequence" OpHelp("r[P2]=cursor[P1].ctr++"), + /* 129 */ "NewRowid" OpHelp("r[P2]=rowid"), + /* 130 */ "Insert" OpHelp("intkey=r[P3] data=r[P2]"), + /* 131 */ "RowCell" OpHelp(""), + /* 132 */ "Delete" OpHelp(""), + /* 133 */ "ResetCount" OpHelp(""), + /* 134 */ "SorterCompare" OpHelp("if key(P1)!=trim(r[P3],P4) goto P2"), + /* 135 */ "SorterData" OpHelp("r[P2]=data"), + /* 136 */ "RowData" OpHelp("r[P2]=data"), + /* 137 */ "Rowid" OpHelp("r[P2]=PX rowid of P1"), + /* 138 */ "NullRow" OpHelp(""), + /* 139 */ "SeekEnd" OpHelp(""), + /* 140 */ "IdxInsert" OpHelp("key=r[P2]"), + /* 141 */ "SorterInsert" OpHelp("key=r[P2]"), + /* 142 */ "IdxDelete" OpHelp("key=r[P2@P3]"), + /* 143 */ "DeferredSeek" OpHelp("Move P3 to P1.rowid if needed"), + /* 144 */ "IdxRowid" OpHelp("r[P2]=rowid"), + /* 145 */ "FinishSeek" OpHelp(""), + /* 146 */ "Destroy" OpHelp(""), + /* 147 */ "Clear" OpHelp(""), + /* 148 */ "ResetSorter" OpHelp(""), + /* 149 */ "CreateBtree" OpHelp("r[P2]=root iDb=P1 flags=P3"), + /* 150 */ "SqlExec" OpHelp(""), + /* 151 */ "ParseSchema" OpHelp(""), + /* 152 */ "LoadAnalysis" OpHelp(""), + /* 153 */ "DropTable" OpHelp(""), + /* 154 */ "Real" OpHelp("r[P2]=P4"), + /* 155 */ "DropIndex" OpHelp(""), + /* 156 */ "DropTrigger" OpHelp(""), + /* 157 */ "IntegrityCk" OpHelp(""), + /* 158 */ "RowSetAdd" OpHelp("rowset(P1)=r[P2]"), + /* 159 */ "Param" OpHelp(""), + /* 160 */ "FkCounter" OpHelp("fkctr[P1]+=P2"), + /* 161 */ "MemMax" OpHelp("r[P1]=max(r[P1],r[P2])"), + /* 162 */ "OffsetLimit" OpHelp("if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1)"), + /* 163 */ "AggInverse" OpHelp("accum=r[P3] inverse(r[P2@P5])"), + /* 164 */ "AggStep" OpHelp("accum=r[P3] step(r[P2@P5])"), + /* 165 */ "AggStep1" OpHelp("accum=r[P3] step(r[P2@P5])"), + /* 166 */ "AggValue" OpHelp("r[P3]=value N=P2"), + /* 167 */ "AggFinal" OpHelp("accum=r[P1] N=P2"), + /* 168 */ "Expire" OpHelp(""), + /* 169 */ "CursorLock" OpHelp(""), + /* 170 */ "CursorUnlock" OpHelp(""), + /* 171 */ "TableLock" OpHelp("iDb=P1 root=P2 write=P3"), + /* 172 */ "VBegin" OpHelp(""), + /* 173 */ "VCreate" OpHelp(""), + /* 174 */ "VDestroy" OpHelp(""), + /* 175 */ "VOpen" OpHelp(""), + /* 176 */ "VCheck" OpHelp(""), + /* 177 */ "VInitIn" OpHelp("r[P2]=ValueList(P1,P3)"), + /* 178 */ "VColumn" OpHelp("r[P3]=vcolumn(P2)"), + /* 179 */ "VRename" OpHelp(""), + /* 180 */ "Pagecount" OpHelp(""), + /* 181 */ "MaxPgcnt" OpHelp(""), + /* 182 */ "ClrSubtype" OpHelp("r[P1].subtype = 0"), + /* 183 */ "GetSubtype" OpHelp("r[P2] = r[P1].subtype"), + /* 184 */ "SetSubtype" OpHelp("r[P2].subtype = r[P1]"), + /* 185 */ "FilterAdd" OpHelp("filter(P1) += key(P3@P4)"), + /* 186 */ "Trace" OpHelp(""), + /* 187 */ "CursorHint" OpHelp(""), + /* 188 */ "ReleaseReg" OpHelp("release r[P1@P2] mask P3"), + /* 189 */ "Noop" OpHelp(""), + /* 190 */ "Explain" OpHelp(""), + /* 191 */ "Abortable" OpHelp(""), }; return azName[i]; } #endif /************** End of opcodes.c *********************************************/ +/************** Begin file os_kv.c *******************************************/ +/* +** 2022-09-06 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This file contains an experimental VFS layer that operates on a +** Key/Value storage engine where both keys and values must be pure +** text. +*/ +/* #include */ +#if SQLITE_OS_KV || (SQLITE_OS_UNIX && defined(SQLITE_OS_KV_OPTIONAL)) + +/***************************************************************************** +** Debugging logic +*/ + +/* SQLITE_KV_TRACE() is used for tracing calls to kvrecord routines. */ +#if 0 +#define SQLITE_KV_TRACE(X) printf X +#else +#define SQLITE_KV_TRACE(X) +#endif + +/* SQLITE_KV_LOG() is used for tracing calls to the VFS interface */ +#if 0 +#define SQLITE_KV_LOG(X) printf X +#else +#define SQLITE_KV_LOG(X) +#endif + +/* +** Forward declaration of objects used by this VFS implementation +*/ +typedef struct KVVfsFile KVVfsFile; + +/* A single open file. There are only two files represented by this +** VFS - the database and the rollback journal. +** +** Maintenance reminder: if this struct changes in any way, the JSON +** rendering of its structure must be updated in +** sqlite3-wasm.c:sqlite3__wasm_enum_json(). There are no binary +** compatibility concerns, so it does not need an iVersion member. +*/ +struct KVVfsFile { + sqlite3_file base; /* IO methods */ + const char *zClass; /* Storage class */ + int isJournal; /* True if this is a journal file */ + unsigned int nJrnl; /* Space allocated for aJrnl[] */ + char *aJrnl; /* Journal content */ + int szPage; /* Last known page size */ + sqlite3_int64 szDb; /* Database file size. -1 means unknown */ + char *aData; /* Buffer to hold page data */ +}; +#define SQLITE_KVOS_SZ 133073 + +/* +** Methods for KVVfsFile +*/ +static int kvvfsClose(sqlite3_file*); +static int kvvfsReadDb(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst); +static int kvvfsReadJrnl(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst); +static int kvvfsWriteDb(sqlite3_file*,const void*,int iAmt, sqlite3_int64); +static int kvvfsWriteJrnl(sqlite3_file*,const void*,int iAmt, sqlite3_int64); +static int kvvfsTruncateDb(sqlite3_file*, sqlite3_int64 size); +static int kvvfsTruncateJrnl(sqlite3_file*, sqlite3_int64 size); +static int kvvfsSyncDb(sqlite3_file*, int flags); +static int kvvfsSyncJrnl(sqlite3_file*, int flags); +static int kvvfsFileSizeDb(sqlite3_file*, sqlite3_int64 *pSize); +static int kvvfsFileSizeJrnl(sqlite3_file*, sqlite3_int64 *pSize); +static int kvvfsLock(sqlite3_file*, int); +static int kvvfsUnlock(sqlite3_file*, int); +static int kvvfsCheckReservedLock(sqlite3_file*, int *pResOut); +static int kvvfsFileControlDb(sqlite3_file*, int op, void *pArg); +static int kvvfsFileControlJrnl(sqlite3_file*, int op, void *pArg); +static int kvvfsSectorSize(sqlite3_file*); +static int kvvfsDeviceCharacteristics(sqlite3_file*); + +/* +** Methods for sqlite3_vfs +*/ +static int kvvfsOpen(sqlite3_vfs*, const char *, sqlite3_file*, int , int *); +static int kvvfsDelete(sqlite3_vfs*, const char *zName, int syncDir); +static int kvvfsAccess(sqlite3_vfs*, const char *zName, int flags, int *); +static int kvvfsFullPathname(sqlite3_vfs*, const char *zName, int, char *zOut); +static void *kvvfsDlOpen(sqlite3_vfs*, const char *zFilename); +static int kvvfsRandomness(sqlite3_vfs*, int nByte, char *zOut); +static int kvvfsSleep(sqlite3_vfs*, int microseconds); +static int kvvfsCurrentTime(sqlite3_vfs*, double*); +static int kvvfsCurrentTimeInt64(sqlite3_vfs*, sqlite3_int64*); + +static sqlite3_vfs sqlite3OsKvvfsObject = { + 2, /* iVersion */ + sizeof(KVVfsFile), /* szOsFile */ + 1024, /* mxPathname */ + 0, /* pNext */ + "kvvfs", /* zName */ + 0, /* pAppData */ + kvvfsOpen, /* xOpen */ + kvvfsDelete, /* xDelete */ + kvvfsAccess, /* xAccess */ + kvvfsFullPathname, /* xFullPathname */ + kvvfsDlOpen, /* xDlOpen */ + 0, /* xDlError */ + 0, /* xDlSym */ + 0, /* xDlClose */ + kvvfsRandomness, /* xRandomness */ + kvvfsSleep, /* xSleep */ + kvvfsCurrentTime, /* xCurrentTime */ + 0, /* xGetLastError */ + kvvfsCurrentTimeInt64 /* xCurrentTimeInt64 */ +}; + +/* Methods for sqlite3_file objects referencing a database file +*/ +static sqlite3_io_methods kvvfs_db_io_methods = { + 1, /* iVersion */ + kvvfsClose, /* xClose */ + kvvfsReadDb, /* xRead */ + kvvfsWriteDb, /* xWrite */ + kvvfsTruncateDb, /* xTruncate */ + kvvfsSyncDb, /* xSync */ + kvvfsFileSizeDb, /* xFileSize */ + kvvfsLock, /* xLock */ + kvvfsUnlock, /* xUnlock */ + kvvfsCheckReservedLock, /* xCheckReservedLock */ + kvvfsFileControlDb, /* xFileControl */ + kvvfsSectorSize, /* xSectorSize */ + kvvfsDeviceCharacteristics, /* xDeviceCharacteristics */ + 0, /* xShmMap */ + 0, /* xShmLock */ + 0, /* xShmBarrier */ + 0, /* xShmUnmap */ + 0, /* xFetch */ + 0 /* xUnfetch */ +}; + +/* Methods for sqlite3_file objects referencing a rollback journal +*/ +static sqlite3_io_methods kvvfs_jrnl_io_methods = { + 1, /* iVersion */ + kvvfsClose, /* xClose */ + kvvfsReadJrnl, /* xRead */ + kvvfsWriteJrnl, /* xWrite */ + kvvfsTruncateJrnl, /* xTruncate */ + kvvfsSyncJrnl, /* xSync */ + kvvfsFileSizeJrnl, /* xFileSize */ + kvvfsLock, /* xLock */ + kvvfsUnlock, /* xUnlock */ + kvvfsCheckReservedLock, /* xCheckReservedLock */ + kvvfsFileControlJrnl, /* xFileControl */ + kvvfsSectorSize, /* xSectorSize */ + kvvfsDeviceCharacteristics, /* xDeviceCharacteristics */ + 0, /* xShmMap */ + 0, /* xShmLock */ + 0, /* xShmBarrier */ + 0, /* xShmUnmap */ + 0, /* xFetch */ + 0 /* xUnfetch */ +}; + +/****** Storage subsystem **************************************************/ +#include +#include +#include + +/* Forward declarations for the low-level storage engine +*/ +#ifndef SQLITE_WASM +/* In WASM builds these are implemented in JS. */ +static int kvrecordWrite(const char*, const char *zKey, const char *zData); +static int kvrecordDelete(const char*, const char *zKey); +static int kvrecordRead(const char*, const char *zKey, char *zBuf, int nBuf); +#endif +#ifndef KVRECORD_KEY_SZ +#define KVRECORD_KEY_SZ 32 +#endif + +/* Expand the key name with an appropriate prefix and put the result +** in zKeyOut[]. The zKeyOut[] buffer is assumed to hold at least +** KVRECORD_KEY_SZ bytes. +*/ +static void kvrecordMakeKey( + const char *zClass, + const char *zKeyIn, + char *zKeyOut +){ + assert( zKeyIn ); + assert( zKeyOut ); + assert( zClass ); + sqlite3_snprintf(KVRECORD_KEY_SZ, zKeyOut, "kvvfs-%s-%s", + zClass, zKeyIn); +} + +#ifndef SQLITE_WASM +/* In WASM builds do not define APIs which use fopen(), fwrite(), +** and the like because those APIs are a portability issue for +** WASM. +*/ +/* Write content into a key. zClass is the particular namespace of the +** underlying key/value store to use - either "local" or "session". +** +** Both zKey and zData are zero-terminated pure text strings. +** +** Return the number of errors. +*/ +static int kvrecordWrite( + const char *zClass, + const char *zKey, + const char *zData +){ + FILE *fd; + char zXKey[KVRECORD_KEY_SZ]; + kvrecordMakeKey(zClass, zKey, zXKey); + fd = fopen(zXKey, "wb"); + if( fd ){ + SQLITE_KV_TRACE(("KVVFS-WRITE %-15s (%d) %.50s%s\n", zXKey, + (int)strlen(zData), zData, + strlen(zData)>50 ? "..." : "")); + fputs(zData, fd); + fclose(fd); + return 0; + }else{ + return 1; + } +} + +/* Delete a key (with its corresponding data) from the key/value +** namespace given by zClass. If the key does not previously exist, +** this routine is a no-op. +*/ +static int kvrecordDelete(const char *zClass, const char *zKey){ + char zXKey[KVRECORD_KEY_SZ]; + kvrecordMakeKey(zClass, zKey, zXKey); + unlink(zXKey); + SQLITE_KV_TRACE(("KVVFS-DELETE %-15s\n", zXKey)); + return 0; +} + +/* Read the value associated with a zKey from the key/value namespace given +** by zClass and put the text data associated with that key in the first +** nBuf bytes of zBuf[]. The value might be truncated if zBuf is not large +** enough to hold it all. The value put into zBuf must always be zero +** terminated, even if it gets truncated because nBuf is not large enough. +** +** Return the total number of bytes in the data, without truncation, and +** not counting the final zero terminator. Return -1 if the key does +** not exist or its key cannot be read. +** +** If nBuf<=0 then this routine simply returns the size of the data +** without actually reading it. Similarly, if nBuf==1 then it +** zero-terminates zBuf at zBuf[0] and returns the size of the data +** without reading it. +*/ +static int kvrecordRead( + const char *zClass, + const char *zKey, + char *zBuf, + int nBuf +){ + FILE *fd; + struct stat buf; + char zXKey[KVRECORD_KEY_SZ]; + kvrecordMakeKey(zClass, zKey, zXKey); + if( access(zXKey, R_OK)!=0 + || stat(zXKey, &buf)!=0 + || !S_ISREG(buf.st_mode) + ){ + SQLITE_KV_TRACE(("KVVFS-READ %-15s (-1)\n", zXKey)); + return -1; + } + if( nBuf<=0 ){ + return (int)buf.st_size; + }else if( nBuf==1 ){ + zBuf[0] = 0; + SQLITE_KV_TRACE(("KVVFS-READ %-15s (%d)\n", zXKey, + (int)buf.st_size)); + return (int)buf.st_size; + } + if( nBuf > buf.st_size + 1 ){ + nBuf = buf.st_size + 1; + } + fd = fopen(zXKey, "rb"); + if( fd==0 ){ + SQLITE_KV_TRACE(("KVVFS-READ %-15s (-1)\n", zXKey)); + return -1; + }else{ + sqlite3_int64 n = fread(zBuf, 1, nBuf-1, fd); + fclose(fd); + zBuf[n] = 0; + SQLITE_KV_TRACE(("KVVFS-READ %-15s (%lld) %.50s%s\n", zXKey, + n, zBuf, n>50 ? "..." : "")); + return (int)n; + } +} +#endif /* #ifndef SQLITE_WASM */ + + +/* +** An internal level of indirection which enables us to replace the +** kvvfs i/o methods with JavaScript implementations in WASM builds. +** Maintenance reminder: if this struct changes in any way, the JSON +** rendering of its structure must be updated in +** sqlite3-wasm.c:sqlite3__wasm_enum_json(). There are no binary +** compatibility concerns, so it does not need an iVersion member. +*/ +typedef struct sqlite3_kvvfs_methods sqlite3_kvvfs_methods; +struct sqlite3_kvvfs_methods { + int (*xRcrdRead)(const char*, const char *zKey, char *zBuf, int nBuf); + int (*xRcrdWrite)(const char*, const char *zKey, const char *zData); + int (*xRcrdDelete)(const char*, const char *zKey); + const int nKeySize; + const int nBufferSize; +#ifndef SQLITE_WASM +# define MAYBE_CONST const +#else +# define MAYBE_CONST +#endif + MAYBE_CONST sqlite3_vfs * pVfs; + MAYBE_CONST sqlite3_io_methods *pIoDb; + MAYBE_CONST sqlite3_io_methods *pIoJrnl; +#undef MAYBE_CONST +}; + + +/* +** This object holds the kvvfs I/O methods which may be swapped out +** for JavaScript-side implementations in WASM builds. In such builds +** it cannot be const, but in native builds it should be so that +** the compiler can hopefully optimize this level of indirection out. +** That said, kvvfs is intended primarily for use in WASM builds. +** +** This is not explicitly flagged as static because the amalgamation +** build will tag it with SQLITE_PRIVATE. +*/ +#ifndef SQLITE_WASM +const +#endif +SQLITE_PRIVATE sqlite3_kvvfs_methods sqlite3KvvfsMethods = { +#ifndef SQLITE_WASM + .xRcrdRead = kvrecordRead, + .xRcrdWrite = kvrecordWrite, + .xRcrdDelete = kvrecordDelete, +#else + .xRcrdRead = 0, + .xRcrdWrite = 0, + .xRcrdDelete = 0, +#endif + .nKeySize = KVRECORD_KEY_SZ, + .nBufferSize = SQLITE_KVOS_SZ, + .pVfs = &sqlite3OsKvvfsObject, + .pIoDb = &kvvfs_db_io_methods, + .pIoJrnl = &kvvfs_jrnl_io_methods +}; + +/****** Utility subroutines ************************************************/ + +/* +** Encode binary into the text encoded used to persist on disk. +** The output text is stored in aOut[], which must be at least +** nData+1 bytes in length. +** +** Return the actual length of the encoded text, not counting the +** zero terminator at the end. +** +** Encoding format +** --------------- +** +** * Non-zero bytes are encoded as upper-case hexadecimal +** +** * A sequence of one or more zero-bytes that are not at the +** beginning of the buffer are encoded as a little-endian +** base-26 number using a..z. "a" means 0. "b" means 1, +** "z" means 25. "ab" means 26. "ac" means 52. And so forth. +** +** * Because there is no overlap between the encoding characters +** of hexadecimal and base-26 numbers, it is always clear where +** one stops and the next begins. +*/ +#ifndef SQLITE_WASM +static +#endif +int kvvfsEncode(const char *aData, int nData, char *aOut){ + int i, j; + const unsigned char *a = (const unsigned char*)aData; + for(i=j=0; i>4]; + aOut[j++] = "0123456789ABCDEF"[c&0xf]; + }else{ + /* A sequence of 1 or more zeros is stored as a little-endian + ** base-26 number using a..z as the digits. So one zero is "b". + ** Two zeros is "c". 25 zeros is "z", 26 zeros is "ab", 27 is "bb", + ** and so forth. + */ + int k; + for(k=1; i+k0 ){ + aOut[j++] = 'a'+(k%26); + k /= 26; + } + } + } + aOut[j] = 0; + return j; +} + +static const signed char kvvfsHexValue[256] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 +}; + +/* +** Decode the text encoding back to binary. The binary content is +** written into pOut, which must be at least nOut bytes in length. +** +** The return value is the number of bytes actually written into aOut[], or +** -1 for malformed inputs. +*/ +#ifndef SQLITE_WASM +static +#endif +int kvvfsDecode(const char *a, char *aOut, int nOut){ + int i, j; + int c; + const unsigned char *aIn = (const unsigned char*)a; + i = 0; + j = 0; + while( 1 ){ + c = kvvfsHexValue[aIn[i]]; + if( c<0 ){ + sqlite3_int64 n = 0; + sqlite3_int64 mult = 1; + c = aIn[i]; + if( c==0 ) break; + while( c>='a' && c<='z' ){ + n += (c - 'a')*mult; + if( n>nOut ) return -1 /* oversized/malformed input */; + mult *= 26; + c = aIn[++i]; + } + if( j+n>nOut ) return -1 /* oversized/malformed input */; + memset(&aOut[j], 0, n); + j += n; + if( c==0 || mult==1 ) break; /* progress stalled if mult==1 */ + }else{ + aOut[j] = c<<4; + c = kvvfsHexValue[aIn[++i]]; + if( c<0 ) return -1 /* hex bytes are always in pairs */; + aOut[j++] += c; + i++; + } + } + return j; +} + +/* +** Decode a complete journal file. Allocate space in pFile->aJrnl +** and store the decoding there. Or leave pFile->aJrnl set to NULL +** if an error is encountered. +** +** The first few characters of the text encoding will be a little-endian +** base-26 number (digits a..z) that is the total number of bytes +** in the decoded journal file image. This base-26 number is followed +** by a single space, then the encoding of the journal. The space +** separator is required to act as a terminator for the base-26 number. +*/ +static void kvvfsDecodeJournal( + KVVfsFile *pFile, /* Store decoding in pFile->aJrnl */ + const char *zTxt, /* Text encoding. Zero-terminated */ + int nTxt /* Bytes in zTxt, excluding zero terminator */ +){ + unsigned int n = 0; + int c, i, mult; + i = 0; + mult = 1; + while( (c = zTxt[i++])>='a' && c<='z' ){ + n += (c - 'a')*mult; + mult *= 26; + } + sqlite3_free(pFile->aJrnl); + pFile->aJrnl = sqlite3_malloc64( n ); + if( pFile->aJrnl==0 ){ + pFile->nJrnl = 0; + return; + } + pFile->nJrnl = n; + n = kvvfsDecode(zTxt+i, pFile->aJrnl, pFile->nJrnl); + if( nnJrnl ){ + sqlite3_free(pFile->aJrnl); + pFile->aJrnl = 0; + pFile->nJrnl = 0; + } +} + +/* +** Read or write the "sz" element, containing the database file size. +*/ +static sqlite3_int64 kvvfsReadFileSize(KVVfsFile *pFile){ + char zData[50]; + zData[0] = 0; + sqlite3KvvfsMethods.xRcrdRead(pFile->zClass, "sz", zData, + sizeof(zData)-1); + return strtoll(zData, 0, 0); +} +static int kvvfsWriteFileSize(KVVfsFile *pFile, sqlite3_int64 sz){ + char zData[50]; + sqlite3_snprintf(sizeof(zData), zData, "%lld", sz); + return sqlite3KvvfsMethods.xRcrdWrite(pFile->zClass, "sz", zData); +} + +/****** sqlite3_io_methods methods ******************************************/ + +/* +** Close an kvvfs-file. +*/ +static int kvvfsClose(sqlite3_file *pProtoFile){ + KVVfsFile *pFile = (KVVfsFile *)pProtoFile; + + SQLITE_KV_LOG(("xClose %s %s\n", pFile->zClass, + pFile->isJournal ? "journal" : "db")); + sqlite3_free(pFile->aJrnl); + sqlite3_free(pFile->aData); + memset(pFile, 0, sizeof(*pFile)); + return SQLITE_OK; +} + +/* +** Read from the -journal file. +*/ +static int kvvfsReadJrnl( + sqlite3_file *pProtoFile, + void *zBuf, + int iAmt, + sqlite_int64 iOfst +){ + KVVfsFile *pFile = (KVVfsFile*)pProtoFile; + assert( pFile->isJournal ); + SQLITE_KV_LOG(("xRead('%s-journal',%d,%lld)\n", pFile->zClass, iAmt, iOfst)); + if( pFile->aJrnl==0 ){ + int rc; + int szTxt = sqlite3KvvfsMethods.xRcrdRead(pFile->zClass, "jrnl", + 0, 0); + char *aTxt; + if( szTxt<=4 ){ + return SQLITE_IOERR; + } + aTxt = sqlite3_malloc64( szTxt+1 ); + if( aTxt==0 ) return SQLITE_NOMEM; + rc = sqlite3KvvfsMethods.xRcrdRead(pFile->zClass, "jrnl", + aTxt, szTxt+1); + if( rc>=0 ){ + kvvfsDecodeJournal(pFile, aTxt, szTxt); + rc = 0; + } + sqlite3_free(aTxt); + if( rc ) return rc; + if( pFile->aJrnl==0 ) return SQLITE_IOERR; + } + if( iOfst+iAmt>pFile->nJrnl ){ + return SQLITE_IOERR_SHORT_READ; + } + memcpy(zBuf, pFile->aJrnl+iOfst, iAmt); + return SQLITE_OK; +} + +/* +** Read from the database file. +*/ +static int kvvfsReadDb( + sqlite3_file *pProtoFile, + void *zBuf, + int iAmt, + sqlite_int64 iOfst +){ + KVVfsFile *pFile = (KVVfsFile*)pProtoFile; + unsigned int pgno; + int got, n; + char zKey[30]; + char *aData = pFile->aData; + assert( iOfst>=0 ); + assert( iAmt>=0 ); + SQLITE_KV_LOG(("xRead('%s-db',%d,%lld)\n", pFile->zClass, iAmt, iOfst)); + if( iOfst+iAmt>=512 ){ + if( (iOfst % iAmt)!=0 ){ + return SQLITE_IOERR_READ; + } + if( (iAmt & (iAmt-1))!=0 || iAmt<512 || iAmt>65536 ){ + return SQLITE_IOERR_READ; + } + pFile->szPage = iAmt; + pgno = 1 + iOfst/iAmt; + }else{ + pgno = 1; + } + sqlite3_snprintf(sizeof(zKey), zKey, "%u", pgno); + got = sqlite3KvvfsMethods.xRcrdRead(pFile->zClass, zKey, + aData, SQLITE_KVOS_SZ-1); + if( got<0 ){ + n = 0; + }else{ + aData[got] = 0; + if( iOfst+iAmt<512 ){ + int k = iOfst+iAmt; + aData[k*2] = 0; + n = kvvfsDecode(aData, &aData[2000], SQLITE_KVOS_SZ-2000); + if( n>=iOfst+iAmt ){ + memcpy(zBuf, &aData[2000+iOfst], iAmt); + n = iAmt; + }else{ + n = 0; + } + }else{ + n = kvvfsDecode(aData, zBuf, iAmt); + } + } + if( nzClass, iAmt, iOfst)); + if( iEnd>=0x10000000 ) return SQLITE_FULL; + if( pFile->aJrnl==0 || pFile->nJrnlaJrnl, iEnd); + if( aNew==0 ){ + return SQLITE_IOERR_NOMEM; + } + pFile->aJrnl = aNew; + if( pFile->nJrnlaJrnl+pFile->nJrnl, 0, iOfst-pFile->nJrnl); + } + pFile->nJrnl = iEnd; + } + memcpy(pFile->aJrnl+iOfst, zBuf, iAmt); + return SQLITE_OK; +} + +/* +** Write into the database file. +*/ +static int kvvfsWriteDb( + sqlite3_file *pProtoFile, + const void *zBuf, + int iAmt, + sqlite_int64 iOfst +){ + KVVfsFile *pFile = (KVVfsFile*)pProtoFile; + unsigned int pgno; + char zKey[30]; + char *aData = pFile->aData; + int rc; + SQLITE_KV_LOG(("xWrite('%s-db',%d,%lld)\n", pFile->zClass, iAmt, iOfst)); + assert( iAmt>=512 && iAmt<=65536 ); + assert( (iAmt & (iAmt-1))==0 ); + assert( pFile->szPage<0 || pFile->szPage==iAmt ); + pFile->szPage = iAmt; + pgno = 1 + iOfst/iAmt; + sqlite3_snprintf(sizeof(zKey), zKey, "%u", pgno); + kvvfsEncode(zBuf, iAmt, aData); + rc = sqlite3KvvfsMethods.xRcrdWrite(pFile->zClass, zKey, aData); + if( 0==rc ){ + if( iOfst+iAmt > pFile->szDb ){ + pFile->szDb = iOfst + iAmt; + } + } + return rc; +} + +/* +** Truncate an kvvfs-file. +*/ +static int kvvfsTruncateJrnl(sqlite3_file *pProtoFile, sqlite_int64 size){ + KVVfsFile *pFile = (KVVfsFile *)pProtoFile; + SQLITE_KV_LOG(("xTruncate('%s-journal',%lld)\n", pFile->zClass, size)); + assert( size==0 ); + sqlite3KvvfsMethods.xRcrdDelete(pFile->zClass, "jrnl"); + sqlite3_free(pFile->aJrnl); + pFile->aJrnl = 0; + pFile->nJrnl = 0; + return SQLITE_OK; +} +static int kvvfsTruncateDb(sqlite3_file *pProtoFile, sqlite_int64 size){ + KVVfsFile *pFile = (KVVfsFile *)pProtoFile; + if( pFile->szDb>size + && pFile->szPage>0 + && (size % pFile->szPage)==0 + ){ + char zKey[50]; + unsigned int pgno, pgnoMax; + SQLITE_KV_LOG(("xTruncate('%s-db',%lld)\n", pFile->zClass, size)); + pgno = 1 + size/pFile->szPage; + pgnoMax = 2 + pFile->szDb/pFile->szPage; + while( pgno<=pgnoMax ){ + sqlite3_snprintf(sizeof(zKey), zKey, "%u", pgno); + sqlite3KvvfsMethods.xRcrdDelete(pFile->zClass, zKey); + pgno++; + } + pFile->szDb = size; + return kvvfsWriteFileSize(pFile, size) ? SQLITE_IOERR : SQLITE_OK; + } + return SQLITE_IOERR; +} + +/* +** Sync an kvvfs-file. +*/ +static int kvvfsSyncJrnl(sqlite3_file *pProtoFile, int flags){ + int i, n; + KVVfsFile *pFile = (KVVfsFile *)pProtoFile; + char *zOut; + SQLITE_KV_LOG(("xSync('%s-journal')\n", pFile->zClass)); + if( pFile->nJrnl<=0 ){ + return kvvfsTruncateJrnl(pProtoFile, 0); + } + zOut = sqlite3_malloc64( pFile->nJrnl*2 + 50 ); + if( zOut==0 ){ + return SQLITE_IOERR_NOMEM; + } + n = pFile->nJrnl; + i = 0; + do{ + zOut[i++] = 'a' + (n%26); + n /= 26; + }while( n>0 ); + zOut[i++] = ' '; + kvvfsEncode(pFile->aJrnl, pFile->nJrnl, &zOut[i]); + i = sqlite3KvvfsMethods.xRcrdWrite(pFile->zClass, "jrnl", zOut); + sqlite3_free(zOut); + return i ? SQLITE_IOERR : SQLITE_OK; +} +static int kvvfsSyncDb(sqlite3_file *pProtoFile, int flags){ + return SQLITE_OK; +} + +/* +** Return the current file-size of an kvvfs-file. +*/ +static int kvvfsFileSizeJrnl(sqlite3_file *pProtoFile, sqlite_int64 *pSize){ + KVVfsFile *pFile = (KVVfsFile *)pProtoFile; + SQLITE_KV_LOG(("xFileSize('%s-journal')\n", pFile->zClass)); + *pSize = pFile->nJrnl; + return SQLITE_OK; +} +static int kvvfsFileSizeDb(sqlite3_file *pProtoFile, sqlite_int64 *pSize){ + KVVfsFile *pFile = (KVVfsFile *)pProtoFile; + SQLITE_KV_LOG(("xFileSize('%s-db')\n", pFile->zClass)); + if( pFile->szDb>=0 ){ + *pSize = pFile->szDb; + }else{ + *pSize = kvvfsReadFileSize(pFile); + } + return SQLITE_OK; +} + +/* +** Lock an kvvfs-file. +*/ +static int kvvfsLock(sqlite3_file *pProtoFile, int eLock){ + KVVfsFile *pFile = (KVVfsFile *)pProtoFile; + assert( !pFile->isJournal ); + SQLITE_KV_LOG(("xLock(%s,%d)\n", pFile->zClass, eLock)); + + if( eLock!=SQLITE_LOCK_NONE ){ + pFile->szDb = kvvfsReadFileSize(pFile); + } + return SQLITE_OK; +} + +/* +** Unlock an kvvfs-file. +*/ +static int kvvfsUnlock(sqlite3_file *pProtoFile, int eLock){ + KVVfsFile *pFile = (KVVfsFile *)pProtoFile; + assert( !pFile->isJournal ); + SQLITE_KV_LOG(("xUnlock(%s,%d)\n", pFile->zClass, eLock)); + if( eLock==SQLITE_LOCK_NONE ){ + pFile->szDb = -1; + } + return SQLITE_OK; +} + +/* +** Check if another file-handle holds a RESERVED lock on an kvvfs-file. +*/ +static int kvvfsCheckReservedLock(sqlite3_file *pProtoFile, int *pResOut){ + SQLITE_KV_LOG(("xCheckReservedLock\n")); + *pResOut = 0; + return SQLITE_OK; +} + +/* +** File control method. For custom operations on an kvvfs-file. +*/ +static int kvvfsFileControlJrnl(sqlite3_file *pProtoFile, int op, void *pArg){ + SQLITE_KV_LOG(("xFileControl(%d) on journal\n", op)); + return SQLITE_NOTFOUND; +} +static int kvvfsFileControlDb(sqlite3_file *pProtoFile, int op, void *pArg){ + SQLITE_KV_LOG(("xFileControl(%d) on database\n", op)); + if( op==SQLITE_FCNTL_SYNC ){ + KVVfsFile *pFile = (KVVfsFile *)pProtoFile; + int rc = SQLITE_OK; + SQLITE_KV_LOG(("xSync('%s-db')\n", pFile->zClass)); + if( pFile->szDb>0 && 0!=kvvfsWriteFileSize(pFile, pFile->szDb) ){ + rc = SQLITE_IOERR; + } + return rc; + } + return SQLITE_NOTFOUND; +} + +/* +** Return the sector-size in bytes for an kvvfs-file. +*/ +static int kvvfsSectorSize(sqlite3_file *pFile){ + return 512; +} + +/* +** Return the device characteristic flags supported by an kvvfs-file. +*/ +static int kvvfsDeviceCharacteristics(sqlite3_file *pProtoFile){ + return 0; +} + +/****** sqlite3_vfs methods *************************************************/ + +/* +** Open an kvvfs file handle. +*/ +static int kvvfsOpen( + sqlite3_vfs *pProtoVfs, + const char *zName, + sqlite3_file *pProtoFile, + int flags, + int *pOutFlags +){ + KVVfsFile *pFile = (KVVfsFile*)pProtoFile; + if( zName==0 ) zName = ""; + SQLITE_KV_LOG(("xOpen(\"%s\")\n", zName)); + assert(!pFile->zClass); + assert(!pFile->aData); + assert(!pFile->aJrnl); + assert(!pFile->nJrnl); + assert(!pFile->base.pMethods); + pFile->szPage = -1; + pFile->szDb = -1; + if( 0==sqlite3_strglob("*-journal", zName) ){ + pFile->isJournal = 1; + pFile->base.pMethods = &kvvfs_jrnl_io_methods; + if( 0==strcmp("session-journal",zName) ){ + pFile->zClass = "session"; + }else if( 0==strcmp("local-journal",zName) ){ + pFile->zClass = "local"; + } + }else{ + pFile->isJournal = 0; + pFile->base.pMethods = &kvvfs_db_io_methods; + } + if( !pFile->zClass ){ + pFile->zClass = zName; + } + pFile->aData = sqlite3_malloc64(SQLITE_KVOS_SZ); + if( pFile->aData==0 ){ + return SQLITE_NOMEM; + } + return SQLITE_OK; +} + +/* +** Delete the file located at zPath. If the dirSync argument is true, +** ensure the file-system modifications are synced to disk before +** returning. +*/ +static int kvvfsDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){ + int rc /* The JS impl can fail with OOM in argument conversion */; + if( strcmp(zPath, "local-journal")==0 ){ + rc = sqlite3KvvfsMethods.xRcrdDelete("local", "jrnl"); + }else + if( strcmp(zPath, "session-journal")==0 ){ + rc = sqlite3KvvfsMethods.xRcrdDelete("session", "jrnl"); + } + else{ + rc = 0; + } + return rc; +} + +/* +** Test for access permissions. Return true if the requested permission +** is available, or false otherwise. +*/ +static int kvvfsAccess( + sqlite3_vfs *pProtoVfs, + const char *zPath, + int flags, + int *pResOut +){ + SQLITE_KV_LOG(("xAccess(\"%s\")\n", zPath)); +#if 0 && defined(SQLITE_WASM) + /* + ** This is not having the desired effect in the JS bindings. + ** It's ostensibly the same logic as the #else block, but + ** it's not behaving that way. + ** + ** In JS we map all zPaths to Storage objects, and -journal files + ** are mapped to the storage for the main db (which is is exactly + ** what the mapping of "local-journal" -> "local" is doing). + */ + const char *zKey = (0==sqlite3_strglob("*-journal", zPath)) + ? "jrnl" : "sz"; + *pResOut = + sqlite3KvvfsMethods.xRcrdRead(zPath, zKey, 0, 0)>0; +#else + if( strcmp(zPath, "local-journal")==0 ){ + *pResOut = + sqlite3KvvfsMethods.xRcrdRead("local", "jrnl", 0, 0)>0; + }else + if( strcmp(zPath, "session-journal")==0 ){ + *pResOut = + sqlite3KvvfsMethods.xRcrdRead("session", "jrnl", 0, 0)>0; + }else + if( strcmp(zPath, "local")==0 ){ + *pResOut = + sqlite3KvvfsMethods.xRcrdRead("local", "sz", 0, 0)>0; + }else + if( strcmp(zPath, "session")==0 ){ + *pResOut = + sqlite3KvvfsMethods.xRcrdRead("session", "sz", 0, 0)>0; + }else + { + *pResOut = 0; + } + /*all current JS tests avoid triggering: assert( *pResOut == 0 ); */ +#endif + SQLITE_KV_LOG(("xAccess returns %d\n",*pResOut)); + return SQLITE_OK; +} + +/* +** Populate buffer zOut with the full canonical pathname corresponding +** to the pathname in zPath. zOut is guaranteed to point to a buffer +** of at least (INST_MAX_PATHNAME+1) bytes. +*/ +static int kvvfsFullPathname( + sqlite3_vfs *pVfs, + const char *zPath, + int nOut, + char *zOut +){ + size_t nPath; +#ifdef SQLITE_OS_KV_ALWAYS_LOCAL + zPath = "local"; +#endif + nPath = strlen(zPath); + SQLITE_KV_LOG(("xFullPathname(\"%s\")\n", zPath)); + if( nOut +static int kvvfsCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *pTimeOut){ + static const sqlite3_int64 unixEpoch = 24405875*(sqlite3_int64)8640000; + struct timeval sNow; + (void)gettimeofday(&sNow, 0); /* Cannot fail given valid arguments */ + *pTimeOut = unixEpoch + 1000*(sqlite3_int64)sNow.tv_sec + sNow.tv_usec/1000; + return SQLITE_OK; +} +#endif /* SQLITE_OS_KV || SQLITE_OS_UNIX */ + +#if SQLITE_OS_KV +/* +** This routine is called initialize the KV-vfs as the default VFS. +*/ +SQLITE_API int sqlite3_os_init(void){ + return sqlite3_vfs_register(&sqlite3OsKvvfsObject, 1); +} +SQLITE_API int sqlite3_os_end(void){ + return SQLITE_OK; +} +#endif /* SQLITE_OS_KV */ + +#if SQLITE_OS_UNIX && defined(SQLITE_OS_KV_OPTIONAL) +SQLITE_PRIVATE int sqlite3KvvfsInit(void){ + return sqlite3_vfs_register(&sqlite3OsKvvfsObject, 0); +} +#endif + +/************** End of os_kv.c ***********************************************/ /************** Begin file os_unix.c *****************************************/ /* ** 2004 May 22 @@ -32334,7 +40219,7 @@ SQLITE_PRIVATE const char *sqlite3OpcodeName(int i){ ** This source file is organized into divisions where the logic for various ** subfunctions is contained within the appropriate division. PLEASE ** KEEP THE STRUCTURE OF THIS FILE INTACT. New code should be placed -** in the correct division and should be clearly labeled. +** in the correct division and should be clearly labelled. ** ** The layout of divisions is as follows: ** @@ -32373,7 +40258,7 @@ SQLITE_PRIVATE const char *sqlite3OpcodeName(int i){ ** Styles 4, 5, and 7 are only available of SQLITE_ENABLE_LOCKING_STYLE ** is defined to 1. The SQLITE_ENABLE_LOCKING_STYLE also enables automatic ** selection of the appropriate locking style based on the filesystem -** where the database is located. +** where the database is located. */ #if !defined(SQLITE_ENABLE_LOCKING_STYLE) # if defined(__APPLE__) @@ -32384,7 +40269,7 @@ SQLITE_PRIVATE const char *sqlite3OpcodeName(int i){ #endif /* Use pread() and pwrite() if they are available */ -#if defined(__APPLE__) +#if defined(__APPLE__) || defined(__linux__) # define HAVE_PREAD 1 # define HAVE_PWRITE 1 #endif @@ -32399,15 +40284,16 @@ SQLITE_PRIVATE const char *sqlite3OpcodeName(int i){ /* ** standard include files. */ -#include -#include +#include /* amalgamator: keep */ +#include /* amalgamator: keep */ #include #include -#include +#include /* amalgamator: keep */ /* #include */ -#include +#include /* amalgamator: keep */ #include -#if !defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0 +#if (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0) \ + && !defined(SQLITE_WASI) # include #endif @@ -32417,13 +40303,30 @@ SQLITE_PRIVATE const char *sqlite3OpcodeName(int i){ # include #endif /* SQLITE_ENABLE_LOCKING_STYLE */ -#if defined(__APPLE__) && ((__MAC_OS_X_VERSION_MIN_REQUIRED > 1050) || \ - (__IPHONE_OS_VERSION_MIN_REQUIRED > 2000)) -# if (!defined(TARGET_OS_EMBEDDED) || (TARGET_OS_EMBEDDED==0)) \ - && (!defined(TARGET_IPHONE_SIMULATOR) || (TARGET_IPHONE_SIMULATOR==0)) -# define HAVE_GETHOSTUUID 1 -# else -# warning "gethostuuid() is disabled." +/* +** Try to determine if gethostuuid() is available based on standard +** macros. This might sometimes compute the wrong value for some +** obscure platforms. For those cases, simply compile with one of +** the following: +** +** -DHAVE_GETHOSTUUID=0 +** -DHAVE_GETHOSTUUID=1 +** +** None if this matters except when building on Apple products with +** -DSQLITE_ENABLE_LOCKING_STYLE. +*/ +#ifndef HAVE_GETHOSTUUID +# define HAVE_GETHOSTUUID 0 +# if defined(__APPLE__) && ((__MAC_OS_X_VERSION_MIN_REQUIRED > 1050) || \ + (__IPHONE_OS_VERSION_MIN_REQUIRED > 2000)) +# if (!defined(TARGET_OS_EMBEDDED) || (TARGET_OS_EMBEDDED==0)) \ + && (!defined(TARGET_IPHONE_SIMULATOR) || (TARGET_IPHONE_SIMULATOR==0))\ + && (!defined(TARGET_OS_MACCATALYST) || (TARGET_OS_MACCATALYST==0)) +# undef HAVE_GETHOSTUUID +# define HAVE_GETHOSTUUID 1 +# else +# warning "gethostuuid() is disabled." +# endif # endif #endif @@ -32478,12 +40381,49 @@ SQLITE_PRIVATE const char *sqlite3OpcodeName(int i){ */ #define SQLITE_MAX_SYMLINKS 100 +/* +** Remove and stub certain info for WASI (WebAssembly System +** Interface) builds. +*/ +#ifdef SQLITE_WASI +# undef HAVE_FCHMOD +# undef HAVE_FCHOWN +# undef HAVE_MREMAP +# define HAVE_MREMAP 0 +# ifndef SQLITE_DEFAULT_UNIX_VFS +# define SQLITE_DEFAULT_UNIX_VFS "unix-dotfile" + /* ^^^ should SQLITE_DEFAULT_UNIX_VFS be "unix-none"? */ +# endif +# ifndef F_RDLCK +# define F_RDLCK 0 +# define F_WRLCK 1 +# define F_UNLCK 2 +# if __LONG_MAX == 0x7fffffffL +# define F_GETLK 12 +# define F_SETLK 13 +# define F_SETLKW 14 +# else +# define F_GETLK 5 +# define F_SETLK 6 +# define F_SETLKW 7 +# endif +# endif +#else /* !SQLITE_WASI */ +# ifndef HAVE_FCHMOD +# define HAVE_FCHMOD 1 +# endif +#endif /* SQLITE_WASI */ + +#ifdef SQLITE_WASI +# define osGetpid(X) (pid_t)1 +#else /* Always cast the getpid() return type for compatibility with ** kernel modules in VxWorks. */ -#define osGetpid(X) (pid_t)getpid() +# define osGetpid(X) (pid_t)getpid() +#endif /* -** Only set the lastErrno if the error code is a real error and not +** Only set the lastErrno if the error code is a real error and not ** a normal expected return code of SQLITE_BUSY or SQLITE_OK */ #define IS_LOCK_ERROR(x) ((x != SQLITE_OK) && (x != SQLITE_BUSY)) @@ -32541,6 +40481,7 @@ struct unixFile { #endif #ifdef SQLITE_ENABLE_SETLK_TIMEOUT unsigned iBusyTimeout; /* Wait this many millisec on locks */ + int bBlockOnConnect; /* True to block for SHARED locks */ #endif #if OS_VXWORKS struct vxworksFileId *pId; /* Unique file ID */ @@ -32551,7 +40492,7 @@ struct unixFile { ** whenever any part of the database changes. An assertion fault will ** occur if a file is updated without also updating the transaction ** counter. This test is made to avoid new problems similar to the - ** one described by ticket #3584. + ** one described by ticket #3584. */ unsigned char transCntrChng; /* True if the transaction counter changed */ unsigned char dbUpdate; /* True if any part of database file changed */ @@ -32560,7 +40501,7 @@ struct unixFile { #endif #ifdef SQLITE_TEST - /* In test mode, increase the size of this structure a bit so that + /* In test mode, increase the size of this structure a bit so that ** it is larger than the struct CrashFile defined in test6.c. */ char aPadding[32]; @@ -32579,7 +40520,7 @@ static pid_t randomnessPid = 0; #define UNIXFILE_EXCL 0x01 /* Connections from one process only */ #define UNIXFILE_RDONLY 0x02 /* Connection is read only */ #define UNIXFILE_PERSIST_WAL 0x04 /* Persistent WAL mode */ -#ifndef SQLITE_DISABLE_DIRSYNC +#if !defined(SQLITE_DISABLE_DIRSYNC) && !defined(_AIX) # define UNIXFILE_DIRSYNC 0x08 /* Directory sync needed */ #else # define UNIXFILE_DIRSYNC 0x00 @@ -32592,205 +40533,7 @@ static pid_t randomnessPid = 0; /* ** Include code that is common to all os_*.c files */ -/************** Include os_common.h in the middle of os_unix.c ***************/ -/************** Begin file os_common.h ***************************************/ -/* -** 2004 May 22 -** -** The author disclaims copyright to this source code. In place of -** a legal notice, here is a blessing: -** -** May you do good and not evil. -** May you find forgiveness for yourself and forgive others. -** May you share freely, never taking more than you give. -** -****************************************************************************** -** -** This file contains macros and a little bit of code that is common to -** all of the platform-specific files (os_*.c) and is #included into those -** files. -** -** This file should be #included by the os_*.c files only. It is not a -** general purpose header file. -*/ -#ifndef _OS_COMMON_H_ -#define _OS_COMMON_H_ - -/* -** At least two bugs have slipped in because we changed the MEMORY_DEBUG -** macro to SQLITE_DEBUG and some older makefiles have not yet made the -** switch. The following code should catch this problem at compile-time. -*/ -#ifdef MEMORY_DEBUG -# error "The MEMORY_DEBUG macro is obsolete. Use SQLITE_DEBUG instead." -#endif - -/* -** Macros for performance tracing. Normally turned off. Only works -** on i486 hardware. -*/ -#ifdef SQLITE_PERFORMANCE_TRACE - -/* -** hwtime.h contains inline assembler code for implementing -** high-performance timing routines. -*/ -/************** Include hwtime.h in the middle of os_common.h ****************/ -/************** Begin file hwtime.h ******************************************/ -/* -** 2008 May 27 -** -** The author disclaims copyright to this source code. In place of -** a legal notice, here is a blessing: -** -** May you do good and not evil. -** May you find forgiveness for yourself and forgive others. -** May you share freely, never taking more than you give. -** -****************************************************************************** -** -** This file contains inline asm code for retrieving "high-performance" -** counters for x86 class CPUs. -*/ -#ifndef SQLITE_HWTIME_H -#define SQLITE_HWTIME_H - -/* -** The following routine only works on pentium-class (or newer) processors. -** It uses the RDTSC opcode to read the cycle count value out of the -** processor and returns that value. This can be used for high-res -** profiling. -*/ -#if (defined(__GNUC__) || defined(_MSC_VER)) && \ - (defined(i386) || defined(__i386__) || defined(_M_IX86)) - - #if defined(__GNUC__) - - __inline__ sqlite_uint64 sqlite3Hwtime(void){ - unsigned int lo, hi; - __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi)); - return (sqlite_uint64)hi << 32 | lo; - } - - #elif defined(_MSC_VER) - - __declspec(naked) __inline sqlite_uint64 __cdecl sqlite3Hwtime(void){ - __asm { - rdtsc - ret ; return value at EDX:EAX - } - } - - #endif - -#elif (defined(__GNUC__) && defined(__x86_64__)) - - __inline__ sqlite_uint64 sqlite3Hwtime(void){ - unsigned long val; - __asm__ __volatile__ ("rdtsc" : "=A" (val)); - return val; - } - -#elif (defined(__GNUC__) && defined(__ppc__)) - - __inline__ sqlite_uint64 sqlite3Hwtime(void){ - unsigned long long retval; - unsigned long junk; - __asm__ __volatile__ ("\n\ - 1: mftbu %1\n\ - mftb %L0\n\ - mftbu %0\n\ - cmpw %0,%1\n\ - bne 1b" - : "=r" (retval), "=r" (junk)); - return retval; - } - -#else - - #error Need implementation of sqlite3Hwtime() for your platform. - - /* - ** To compile without implementing sqlite3Hwtime() for your platform, - ** you can remove the above #error and use the following - ** stub function. You will lose timing support for many - ** of the debugging and testing utilities, but it should at - ** least compile and run. - */ -SQLITE_PRIVATE sqlite_uint64 sqlite3Hwtime(void){ return ((sqlite_uint64)0); } - -#endif - -#endif /* !defined(SQLITE_HWTIME_H) */ - -/************** End of hwtime.h **********************************************/ -/************** Continuing where we left off in os_common.h ******************/ - -static sqlite_uint64 g_start; -static sqlite_uint64 g_elapsed; -#define TIMER_START g_start=sqlite3Hwtime() -#define TIMER_END g_elapsed=sqlite3Hwtime()-g_start -#define TIMER_ELAPSED g_elapsed -#else -#define TIMER_START -#define TIMER_END -#define TIMER_ELAPSED ((sqlite_uint64)0) -#endif - -/* -** If we compile with the SQLITE_TEST macro set, then the following block -** of code will give us the ability to simulate a disk I/O error. This -** is used for testing the I/O recovery logic. -*/ -#if defined(SQLITE_TEST) -SQLITE_API extern int sqlite3_io_error_hit; -SQLITE_API extern int sqlite3_io_error_hardhit; -SQLITE_API extern int sqlite3_io_error_pending; -SQLITE_API extern int sqlite3_io_error_persist; -SQLITE_API extern int sqlite3_io_error_benign; -SQLITE_API extern int sqlite3_diskfull_pending; -SQLITE_API extern int sqlite3_diskfull; -#define SimulateIOErrorBenign(X) sqlite3_io_error_benign=(X) -#define SimulateIOError(CODE) \ - if( (sqlite3_io_error_persist && sqlite3_io_error_hit) \ - || sqlite3_io_error_pending-- == 1 ) \ - { local_ioerr(); CODE; } -static void local_ioerr(){ - IOTRACE(("IOERR\n")); - sqlite3_io_error_hit++; - if( !sqlite3_io_error_benign ) sqlite3_io_error_hardhit++; -} -#define SimulateDiskfullError(CODE) \ - if( sqlite3_diskfull_pending ){ \ - if( sqlite3_diskfull_pending == 1 ){ \ - local_ioerr(); \ - sqlite3_diskfull = 1; \ - sqlite3_io_error_hit = 1; \ - CODE; \ - }else{ \ - sqlite3_diskfull_pending--; \ - } \ - } -#else -#define SimulateIOErrorBenign(X) -#define SimulateIOError(A) -#define SimulateDiskfullError(A) -#endif /* defined(SQLITE_TEST) */ - -/* -** When testing, keep a count of the number of open files. -*/ -#if defined(SQLITE_TEST) -SQLITE_API extern int sqlite3_open_file_count; -#define OpenCounter(X) sqlite3_open_file_count+=(X) -#else -#define OpenCounter(X) -#endif /* defined(SQLITE_TEST) */ - -#endif /* !defined(_OS_COMMON_H_) */ - -/************** End of os_common.h *******************************************/ -/************** Continuing where we left off in os_unix.c ********************/ +/* #include "os_common.h" */ /* ** Define various macros that are missing from some systems. @@ -32903,7 +40646,7 @@ static struct unix_syscall { #ifdef __DJGPP__ { "fstat", 0, 0 }, #define osFstat(a,b,c) 0 -#else +#else { "fstat", (sqlite3_syscall_ptr)fstat, 0 }, #define osFstat ((int(*)(int,struct stat*))aSyscall[5].pCurrent) #endif @@ -32950,8 +40693,13 @@ static struct unix_syscall { #define osPwrite64 ((ssize_t(*)(int,const void*,size_t,off64_t))\ aSyscall[13].pCurrent) +#if defined(HAVE_FCHMOD) { "fchmod", (sqlite3_syscall_ptr)fchmod, 0 }, #define osFchmod ((int(*)(int,mode_t))aSyscall[14].pCurrent) +#else + { "fchmod", (sqlite3_syscall_ptr)0, 0 }, +#define osFchmod(FID,MODE) 0 +#endif #if defined(HAVE_POSIX_FALLOCATE) && HAVE_POSIX_FALLOCATE { "fallocate", (sqlite3_syscall_ptr)posix_fallocate, 0 }, @@ -32986,14 +40734,16 @@ static struct unix_syscall { #endif #define osGeteuid ((uid_t(*)(void))aSyscall[21].pCurrent) -#if !defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0 +#if (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0) \ + && !defined(SQLITE_WASI) { "mmap", (sqlite3_syscall_ptr)mmap, 0 }, #else { "mmap", (sqlite3_syscall_ptr)0, 0 }, #endif #define osMmap ((void*(*)(void*,size_t,int,int,int,off_t))aSyscall[22].pCurrent) -#if !defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0 +#if (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0) \ + && !defined(SQLITE_WASI) { "munmap", (sqlite3_syscall_ptr)munmap, 0 }, #else { "munmap", (sqlite3_syscall_ptr)0, 0 }, @@ -33031,17 +40781,131 @@ static struct unix_syscall { #if defined(__linux__) && defined(SQLITE_ENABLE_BATCH_ATOMIC_WRITE) # ifdef __ANDROID__ { "ioctl", (sqlite3_syscall_ptr)(int(*)(int, int, ...))ioctl, 0 }, +#define osIoctl ((int(*)(int,int,...))aSyscall[28].pCurrent) # else { "ioctl", (sqlite3_syscall_ptr)ioctl, 0 }, +#define osIoctl ((int(*)(int,unsigned long,...))aSyscall[28].pCurrent) # endif #else { "ioctl", (sqlite3_syscall_ptr)0, 0 }, #endif -#define osIoctl ((int(*)(int,int,...))aSyscall[28].pCurrent) }; /* End of the overrideable system calls */ +#if defined(SQLITE_DEBUG) || defined(SQLITE_ENABLE_FILESTAT) +/* +** Extract Posix Advisory Locking information about file description fd +** from the /proc/PID/fdinfo/FD pseudo-file. Fill the string buffer a[16] +** with characters to indicate which SQLite-relevant locks are held. +** a[16] will be a 15-character zero-terminated string with the following +** schema: +** +** AAA/B.DDD.DDDDD +** +** Each of character A-D will be "w" or "r" or "-" to indicate either a +** write-lock, a read-lock, or no-lock, respectively. The "." and "/" +** characters are delimiters intended to make the string more easily +** readable by humans. Here are the meaning of the specific letters: +** +** AAA -> The main database locks. PENDING_BYTE, RESERVED_BYTE, +** and SHARED_FIRST, respectively. +** +** B -> The deadman switch lock. Offset 128 of the -shm file. +** +** CCC -> WAL locks: WRITE, CKPT, RECOVER +** +** DDDDD -> WAL read-locks 0 through 5 +** +** Note that elements before the "/" apply to the main database file and +** elements after the "/" apply to the -shm file in WAL mode. +** +** Here is another way of thinking about the meaning of the result string: +** +** AAA/B.CCC.DDDDD +** ||| | ||| \___/ +** PENDING--'|| | ||| `----- READ 0-5 +** RESERVED--'| | ||`---- RECOVER +** SHARED ----' | |`----- CKPT +** DMS ------' `------ WRITE +** +** Return SQLITE_OK on success and SQLITE_ERROR_UNABLE if the /proc +** pseudo-filesystem is unavailable. +*/ +static int unixPosixAdvisoryLocks( + int fd, /* The file descriptor to analyze */ + char a[16] /* Write a text description of PALs here */ +){ + int in; + ssize_t n; + char *p, *pNext, *x; + char z[2000]; + + /* 1 */ + /* 012 4 678 01234 */ + memcpy(a, "---/-.---.-----", 16); + sqlite3_snprintf(sizeof(z), z, "/proc/%d/fdinfo/%d", getpid(), fd); + in = osOpen(z, O_RDONLY, 0); + if( in<0 ){ + return SQLITE_ERROR_UNABLE; + } + n = osRead(in, z, sizeof(z)-1); + osClose(in); + if( n<=0 ) return SQLITE_ERROR_UNABLE; + z[n] = 0; + + /* We are looking for lines that begin with "lock:\t". Examples: + ** + ** lock: 1: POSIX ADVISORY READ 494716 08:02:5277597 1073741826 1073742335 + ** lock: 1: POSIX ADVISORY WRITE 494716 08:02:5282282 120 120 + ** lock: 2: POSIX ADVISORY READ 494716 08:02:5282282 123 123 + ** lock: 3: POSIX ADVISORY READ 494716 08:02:5282282 128 128 + */ + pNext = strstr(z, "lock:\t"); + while( pNext ){ + char cType = 0; + sqlite3_int64 iFirst, iLast; + p = pNext+6; + pNext = strstr(p, "lock:\t"); + if( pNext ) pNext[-1] = 0; + if( (x = strstr(p, " READ "))!=0 ){ + cType = 'r'; + x += 6; + }else if( (x = strstr(p, " WRITE "))!=0 ){ + cType = 'w'; + x += 7; + }else{ + continue; + } + x = strrchr(x, ' '); + if( x==0 ) continue; + iLast = strtoll(x+1, 0, 10); + *x = 0; + x = strrchr(p, ' '); + if( x==0 ) continue; + iFirst = strtoll(x+1, 0, 10); + if( iLast>=PENDING_BYTE ){ + if( iFirst<=PENDING_BYTE && iLast>=PENDING_BYTE ) a[0] = cType; + if( iFirst<=PENDING_BYTE+1 && iLast>=PENDING_BYTE+1 ) a[1] = cType; + if( iFirst<=PENDING_BYTE+2 && iLast>=PENDING_BYTE+510 ) a[2] = cType; + }else if( iLast<=128 ){ + if( iFirst<=128 && iLast>=128 ) a[4] = cType; + if( iFirst<=120 && iLast>=120 ) a[6] = cType; + if( iFirst<=121 && iLast>=121 ) a[7] = cType; + if( iFirst<=122 && iLast>=122 ) a[8] = cType; + if( iFirst<=123 && iLast>=123 ) a[10] = cType; + if( iFirst<=124 && iLast>=124 ) a[11] = cType; + if( iFirst<=125 && iLast>=125 ) a[12] = cType; + if( iFirst<=126 && iLast>=126 ) a[13] = cType; + if( iFirst<=127 && iLast>=127 ) a[14] = cType; + } + } + return SQLITE_OK; +} +#else +# define unixPosixAdvisoryLocks(A,B) SQLITE_ERROR_UNABLE +#endif /* SQLITE_DEBUG || SQLITE_ENABLE_FILESTAT */ + /* ** On some systems, calls to fchown() will trigger a message in a security ** log if they come from non-root processes. So avoid calling fchown() if @@ -33057,7 +40921,7 @@ static int robustFchown(int fd, uid_t uid, gid_t gid){ /* ** This is the xSetSystemCall() method of sqlite3_vfs for all of the -** "unix" VFSes. Return SQLITE_OK opon successfully updating the +** "unix" VFSes. Return SQLITE_OK upon successfully updating the ** system call pointer, or SQLITE_NOTFOUND if there is no configurable ** system call named zName. */ @@ -33140,7 +41004,7 @@ static const char *unixNextSystemCall(sqlite3_vfs *p, const char *zName){ /* ** Do not accept any file descriptor less than this value, in order to avoid -** opening database file using file descriptors that are commonly used for +** opening database file using file descriptors that are commonly used for ** standard input, output, and error. */ #ifndef SQLITE_MINIMUM_FILE_DESCRIPTOR @@ -33178,18 +41042,21 @@ static int robust_open(const char *z, int f, mode_t m){ break; } if( fd>=SQLITE_MINIMUM_FILE_DESCRIPTOR ) break; + if( (f & (O_EXCL|O_CREAT))==(O_EXCL|O_CREAT) ){ + (void)osUnlink(z); + } osClose(fd); - sqlite3_log(SQLITE_WARNING, + sqlite3_log(SQLITE_WARNING, "attempt to open \"%s\" as file descriptor %d", z, fd); fd = -1; - if( osOpen("/dev/null", f, m)<0 ) break; + if( osOpen("/dev/null", O_RDONLY, m)<0 ) break; } if( fd>=0 ){ if( m!=0 ){ struct stat statbuf; - if( osFstat(fd, &statbuf)==0 + if( osFstat(fd, &statbuf)==0 && statbuf.st_size==0 - && (statbuf.st_mode&0777)!=m + && (statbuf.st_mode&0777)!=m ){ osFchmod(fd, m); } @@ -33203,12 +41070,11 @@ static int robust_open(const char *z, int f, mode_t m){ /* ** Helper functions to obtain and relinquish the global mutex. The -** global mutex is used to protect the unixInodeInfo and -** vxworksFileId objects used by this file, all of which may be -** shared by multiple threads. +** global mutex is used to protect the unixInodeInfo objects used by +** this file, all of which may be shared by multiple threads. ** -** Function unixMutexHeld() is used to assert() that the global mutex -** is held when required. This function is only used as part of assert() +** Function unixMutexHeld() is used to assert() that the global mutex +** is held when required. This function is only used as part of assert() ** statements. e.g. ** ** unixEnterMutex() @@ -33330,7 +41196,7 @@ static int lockTrace(int fd, int op, struct flock *p){ static int robust_ftruncate(int h, sqlite3_int64 sz){ int rc; #ifdef __ANDROID__ - /* On Android, ftruncate() always uses 32-bit offsets, even if + /* On Android, ftruncate() always uses 32-bit offsets, even if ** _FILE_OFFSET_BITS=64 is defined. This means it is unsafe to attempt to ** truncate a file to any size larger than 2GiB. Silently ignore any ** such attempts. */ @@ -33346,32 +41212,32 @@ static int robust_ftruncate(int h, sqlite3_int64 sz){ ** This routine translates a standard POSIX errno code into something ** useful to the clients of the sqlite3 functions. Specifically, it is ** intended to translate a variety of "try again" errors into SQLITE_BUSY -** and a variety of "please close the file descriptor NOW" errors into +** and a variety of "please close the file descriptor NOW" errors into ** SQLITE_IOERR -** +** ** Errors during initialization of locks, or file system support for locks, ** should handle ENOLCK, ENOTSUP, EOPNOTSUPP separately. */ static int sqliteErrorFromPosixError(int posixError, int sqliteIOErr) { - assert( (sqliteIOErr == SQLITE_IOERR_LOCK) || - (sqliteIOErr == SQLITE_IOERR_UNLOCK) || + assert( (sqliteIOErr == SQLITE_IOERR_LOCK) || + (sqliteIOErr == SQLITE_IOERR_UNLOCK) || (sqliteIOErr == SQLITE_IOERR_RDLOCK) || (sqliteIOErr == SQLITE_IOERR_CHECKRESERVEDLOCK) ); switch (posixError) { - case EACCES: + case EACCES: case EAGAIN: case ETIMEDOUT: case EBUSY: case EINTR: - case ENOLCK: - /* random NFS retry error, unless during file system support + case ENOLCK: + /* random NFS retry error, unless during file system support * introspection, in which it actually means what it says */ return SQLITE_BUSY; - - case EPERM: + + case EPERM: return SQLITE_PERM; - - default: + + default: return sqliteIOErr; } } @@ -33386,7 +41252,7 @@ static int sqliteErrorFromPosixError(int posixError, int sqliteIOErr) { ** ** A pointer to an instance of the following structure can be used as a ** unique file ID in VxWorks. Each instance of this structure contains -** a copy of the canonical filename. There is also a reference count. +** a copy of the canonical filename. There is also a reference count. ** The structure is reclaimed when the number of pointers to it drops to ** zero. ** @@ -33402,11 +41268,12 @@ struct vxworksFileId { }; #if OS_VXWORKS -/* +/* ** All unique filenames are held on a linked list headed by this ** variable: */ static struct vxworksFileId *vxworksFileList = 0; +static sqlite3_mutex *vxworksMutex = 0; /* ** Simplify a filename into its canonical form @@ -33472,14 +41339,14 @@ static struct vxworksFileId *vxworksFindFileId(const char *zAbsoluteName){ ** If found, increment the reference count and return a pointer to ** the existing file ID. */ - unixEnterMutex(); + sqlite3_mutex_enter(vxworksMutex); for(pCandidate=vxworksFileList; pCandidate; pCandidate=pCandidate->pNext){ - if( pCandidate->nName==n + if( pCandidate->nName==n && memcmp(pCandidate->zCanonicalName, pNew->zCanonicalName, n)==0 ){ sqlite3_free(pNew); pCandidate->nRef++; - unixLeaveMutex(); + sqlite3_mutex_leave(vxworksMutex); return pCandidate; } } @@ -33489,7 +41356,7 @@ static struct vxworksFileId *vxworksFindFileId(const char *zAbsoluteName){ pNew->nName = n; pNew->pNext = vxworksFileList; vxworksFileList = pNew; - unixLeaveMutex(); + sqlite3_mutex_leave(vxworksMutex); return pNew; } @@ -33498,7 +41365,7 @@ static struct vxworksFileId *vxworksFindFileId(const char *zAbsoluteName){ ** the object when the reference count reaches zero. */ static void vxworksReleaseFileId(struct vxworksFileId *pId){ - unixEnterMutex(); + sqlite3_mutex_enter(vxworksMutex); assert( pId->nRef>0 ); pId->nRef--; if( pId->nRef==0 ){ @@ -33508,7 +41375,7 @@ static void vxworksReleaseFileId(struct vxworksFileId *pId){ *pp = pId->pNext; sqlite3_free(pId); } - unixLeaveMutex(); + sqlite3_mutex_leave(vxworksMutex); } #endif /* OS_VXWORKS */ /*************** End of Unique File ID Utility Used By VxWorks **************** @@ -33567,7 +41434,7 @@ static void vxworksReleaseFileId(struct vxworksFileId *pId){ ** cnt>0 means there are cnt shared locks on the file. ** ** Any attempt to lock or unlock a file first checks the locking -** structure. The fcntl() system call is only invoked to set a +** structure. The fcntl() system call is only invoked to set a ** POSIX lock if the internal lock structure transitions between ** a locked and an unlocked state. ** @@ -33576,7 +41443,7 @@ static void vxworksReleaseFileId(struct vxworksFileId *pId){ ** If you close a file descriptor that points to a file that has locks, ** all locks on that file that are owned by the current process are ** released. To work around this problem, each unixInodeInfo object -** maintains a count of the number of pending locks on tha inode. +** maintains a count of the number of pending locks on the inode. ** When an attempt is made to close an unixFile, if there are ** other unixFile open on the same inode that are holding locks, the call ** to close() the file descriptor is deferred until all of the locks clear. @@ -33590,7 +41457,7 @@ static void vxworksReleaseFileId(struct vxworksFileId *pId){ ** not posix compliant. Under LinuxThreads, a lock created by thread ** A cannot be modified or overridden by a different thread B. ** Only thread A can modify the lock. Locking behavior is correct -** if the appliation uses the newer Native Posix Thread Library (NPTL) +** if the application uses the newer Native Posix Thread Library (NPTL) ** on linux - with NPTL a lock created by thread A can override locks ** in thread B. But there is no way to know at compile-time which ** threading library is being used. So there is no way to know at @@ -33600,7 +41467,7 @@ static void vxworksReleaseFileId(struct vxworksFileId *pId){ ** ** SQLite used to support LinuxThreads. But support for LinuxThreads ** was dropped beginning with version 3.7.0. SQLite will still work with -** LinuxThreads provided that (1) there is no more than one connection +** LinuxThreads provided that (1) there is no more than one connection ** per database file in the same process and (2) database connections ** do not move across threads. */ @@ -33617,7 +41484,7 @@ struct unixFileId { /* We are told that some versions of Android contain a bug that ** sizes ino_t at only 32-bits instead of 64-bits. (See ** https://android-review.googlesource.com/#/c/115351/3/dist/sqlite3.c) - ** To work around this, always allocate 64-bits for the inode number. + ** To work around this, always allocate 64-bits for the inode number. ** On small machines that only have 32-bit inodes, this wastes 4 bytes, ** but that should not be a big deal. */ /* WAS: ino_t ino; */ @@ -33705,7 +41572,7 @@ int unixFileMutexNotheld(unixFile *pFile){ ** strerror_r(). ** ** The first argument passed to the macro should be the error code that -** will be returned to SQLite (e.g. SQLITE_IOERR_DELETE, SQLITE_CANTOPEN). +** will be returned to SQLite (e.g. SQLITE_IOERR_DELETE, SQLITE_CANTOPEN). ** The two subsequent arguments should be the name of the OS function that ** failed (e.g. "unlink", "open") and the associated file-system path, ** if any. @@ -33723,7 +41590,7 @@ static int unixLogErrorAtLine( /* If this is not a threadsafe build (SQLITE_THREADSAFE==0), then use ** the strerror() function to obtain the human-readable error message ** equivalent to errno. Otherwise, use strerror_r(). - */ + */ #if SQLITE_THREADSAFE && defined(HAVE_STRERROR_R) char aErr[80]; memset(aErr, 0, sizeof(aErr)); @@ -33731,18 +41598,22 @@ static int unixLogErrorAtLine( /* If STRERROR_R_CHAR_P (set by autoconf scripts) or __USE_GNU is defined, ** assume that the system provides the GNU version of strerror_r() that - ** returns a pointer to a buffer containing the error message. That pointer - ** may point to aErr[], or it may point to some static storage somewhere. - ** Otherwise, assume that the system provides the POSIX version of + ** returns a pointer to a buffer containing the error message. That pointer + ** may point to aErr[], or it may point to some static storage somewhere. + ** Otherwise, assume that the system provides the POSIX version of ** strerror_r(), which always writes an error message into aErr[]. ** ** If the code incorrectly assumes that it is the POSIX version that is ** available, the error message will often be an empty string. Not a - ** huge problem. Incorrectly concluding that the GNU version is available + ** huge problem. Incorrectly concluding that the GNU version is available ** could lead to a segfault though. + ** + ** Forum post 3f13857fa4062301 reports that the Android SDK may use + ** int-type return, depending on its version. */ -#if defined(STRERROR_R_CHAR_P) || defined(__USE_GNU) - zErr = +#if (defined(STRERROR_R_CHAR_P) || defined(__USE_GNU)) \ + && !defined(ANDROID) && !defined(__ANDROID__) + zErr = # endif strerror_r(iErrno, aErr, sizeof(aErr)-1); @@ -33792,8 +41663,8 @@ static void storeLastErrno(unixFile *pFile, int error){ } /* -** Close all file descriptors accumuated in the unixInodeInfo->pUnused list. -*/ +** Close all file descriptors accumulated in the unixInodeInfo->pUnused list. +*/ static void closePendingFds(unixFile *pFile){ unixInodeInfo *pInode = pFile->pInode; UnixUnusedFd *p; @@ -33892,6 +41763,10 @@ static int findInodeInfo( storeLastErrno(pFile, errno); return SQLITE_IOERR; } + if( fsync(fd) ){ + storeLastErrno(pFile, errno); + return SQLITE_IOERR_FSYNC; + } rc = osFstat(fd, &statbuf); if( rc!=0 ){ storeLastErrno(pFile, errno); @@ -33948,7 +41823,7 @@ static int fileHasMoved(unixFile *pFile){ #else struct stat buf; return pFile->pInode!=0 && - (osStat(pFile->zPath, &buf)!=0 + (osStat(pFile->zPath, &buf)!=0 || (u64)buf.st_ino!=pFile->pInode->fileId.ino); #endif } @@ -34029,7 +41904,7 @@ static int unixCheckReservedLock(sqlite3_file *id, int *pResOut){ } } #endif - + sqlite3_mutex_leave(pFile->pInode->pLockMutex); OSTRACE(("TEST WR-LOCK %d %d %d (unix)\n", pFile->h, rc, reserved)); @@ -34037,6 +41912,9 @@ static int unixCheckReservedLock(sqlite3_file *id, int *pResOut){ return rc; } +/* Forward declaration*/ +static int unixSleep(sqlite3_vfs*,int); + /* ** Set a posix-advisory-lock. ** @@ -34058,24 +41936,49 @@ static int osSetPosixAdvisoryLock( struct flock *pLock, /* The description of the lock */ unixFile *pFile /* Structure holding timeout value */ ){ - int rc = osFcntl(h,F_SETLK,pLock); - while( rc<0 && pFile->iBusyTimeout>0 ){ - /* On systems that support some kind of blocking file lock with a timeout, - ** make appropriate changes here to invoke that blocking file lock. On - ** generic posix, however, there is no such API. So we simply try the - ** lock once every millisecond until either the timeout expires, or until - ** the lock is obtained. */ - usleep(1000); + int rc = 0; + + if( pFile->iBusyTimeout==0 ){ + /* unixFile->iBusyTimeout is set to 0. In this case, attempt a + ** non-blocking lock. */ rc = osFcntl(h,F_SETLK,pLock); - pFile->iBusyTimeout--; + }else{ + /* unixFile->iBusyTimeout is set to greater than zero. In this case, + ** attempt a blocking-lock with a unixFile->iBusyTimeout ms timeout. + ** + ** On systems that support some kind of blocking file lock operation, + ** this block should be replaced by code to attempt a blocking lock + ** with a timeout of unixFile->iBusyTimeout ms. The code below is + ** placeholder code. If SQLITE_TEST is defined, the placeholder code + ** retries the lock once every 1ms until it succeeds or the timeout + ** is reached. Or, if SQLITE_TEST is not defined, the placeholder + ** code attempts a non-blocking lock and sets unixFile->iBusyTimeout + ** to 0. This causes the caller to return SQLITE_BUSY, instead of + ** SQLITE_BUSY_TIMEOUT to SQLite - as required by a VFS that does not + ** support blocking locks. + */ +#ifdef SQLITE_TEST + int tm = pFile->iBusyTimeout; + while( tm>0 ){ + rc = osFcntl(h,F_SETLK,pLock); + if( rc==0 ) break; + unixSleep(0,1000); + tm--; + } +#else + rc = osFcntl(h,F_SETLK,pLock); + pFile->iBusyTimeout = 0; +#endif + /* End of code to replace with real blocking-locks code. */ } + return rc; } #endif /* SQLITE_ENABLE_SETLK_TIMEOUT */ /* -** Attempt to set a system-lock on the file pFile. The lock is +** Attempt to set a system-lock on the file pFile. The lock is ** described by pLock. ** ** If the pFile was opened read/write from unix-excl, then the only lock @@ -34101,7 +42004,7 @@ static int unixFileLock(unixFile *pFile, struct flock *pLock){ if( (pFile->ctrlFlags & (UNIXFILE_EXCL|UNIXFILE_RDONLY))==UNIXFILE_EXCL ){ if( pInode->bProcessLock==0 ){ struct flock lock; - assert( pInode->nLock==0 ); + /* assert( pInode->nLock==0 ); <-- Not true if unix-excl READONLY used */ lock.l_whence = SEEK_SET; lock.l_start = SHARED_FIRST; lock.l_len = SHARED_SIZE; @@ -34114,11 +42017,25 @@ static int unixFileLock(unixFile *pFile, struct flock *pLock){ rc = 0; } }else{ +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + if( pFile->bBlockOnConnect && pLock->l_type==F_RDLCK + && pLock->l_start==SHARED_FIRST && pLock->l_len==SHARED_SIZE + ){ + rc = osFcntl(pFile->h, F_SETLKW, pLock); + }else +#endif rc = osSetPosixAdvisoryLock(pFile->h, pLock, pFile); } return rc; } +#if !defined(SQLITE_WASI) && !defined(SQLITE_OMIT_WAL) +/* Forward reference */ +static int unixIsSharingShmNode(unixFile*); +#else +#define unixIsSharingShmNode(pFile) (0) +#endif + /* ** Lock the file with the lock specified by parameter eFileLock - one ** of the following: @@ -34136,7 +42053,7 @@ static int unixFileLock(unixFile *pFile, struct flock *pLock){ ** ** UNLOCKED -> SHARED ** SHARED -> RESERVED -** SHARED -> (PENDING) -> EXCLUSIVE +** SHARED -> EXCLUSIVE ** RESERVED -> (PENDING) -> EXCLUSIVE ** PENDING -> EXCLUSIVE ** @@ -34151,7 +42068,7 @@ static int unixLock(sqlite3_file *id, int eFileLock){ ** slightly in order to be compatible with Windows95 systems simultaneously ** accessing the same database file, in case that is ever required. ** - ** Symbols defined in os.h indentify the 'pending byte' and the 'reserved + ** Symbols defined in os.h identify the 'pending byte' and the 'reserved ** byte', each single bytes at well known offsets, and the 'shared byte ** range', a range of 510 bytes at a well known offset. ** @@ -34159,7 +42076,7 @@ static int unixLock(sqlite3_file *id, int eFileLock){ ** byte'. If this is successful, 'shared byte range' is read-locked ** and the lock on the 'pending byte' released. (Legacy note: When ** SQLite was first developed, Windows95 systems were still very common, - ** and Widnows95 lacks a shared-lock capability. So on Windows95, a + ** and Windows95 lacks a shared-lock capability. So on Windows95, a ** single randomly selected by from the 'shared byte range' is locked. ** Windows95 is now pretty much extinct, but this work-around for the ** lack of shared-locks on Windows95 lives on, for backwards @@ -34167,21 +42084,22 @@ static int unixLock(sqlite3_file *id, int eFileLock){ ** ** A process may only obtain a RESERVED lock after it has a SHARED lock. ** A RESERVED lock is implemented by grabbing a write-lock on the - ** 'reserved byte'. + ** 'reserved byte'. ** - ** A process may only obtain a PENDING lock after it has obtained a - ** SHARED lock. A PENDING lock is implemented by obtaining a write-lock - ** on the 'pending byte'. This ensures that no new SHARED locks can be - ** obtained, but existing SHARED locks are allowed to persist. A process - ** does not have to obtain a RESERVED lock on the way to a PENDING lock. - ** This property is used by the algorithm for rolling back a journal file - ** after a crash. + ** An EXCLUSIVE lock may only be requested after either a SHARED or + ** RESERVED lock is held. An EXCLUSIVE lock is implemented by obtaining + ** a write-lock on the entire 'shared byte range'. Since all other locks + ** require a read-lock on one of the bytes within this range, this ensures + ** that no other locks are held on the database. ** - ** An EXCLUSIVE lock, obtained after a PENDING lock is held, is - ** implemented by obtaining a write-lock on the entire 'shared byte - ** range'. Since all other locks require a read-lock on one of the bytes - ** within this range, this ensures that no other locks are held on the - ** database. + ** If a process that holds a RESERVED lock requests an EXCLUSIVE, then + ** a PENDING lock is obtained first. A PENDING lock is implemented by + ** obtaining a write-lock on the 'pending byte'. This ensures that no new + ** SHARED locks can be obtained, but existing SHARED locks are allowed to + ** persist. If the call to this function fails to obtain the EXCLUSIVE + ** lock in this case, it holds the PENDING lock instead. The client may + ** then re-attempt the EXCLUSIVE lock later on, after existing SHARED + ** locks have cleared. */ int rc = SQLITE_OK; unixFile *pFile = (unixFile*)id; @@ -34207,7 +42125,7 @@ static int unixLock(sqlite3_file *id, int eFileLock){ /* Make sure the locking sequence is correct. ** (1) We never move from unlocked to anything higher than shared lock. - ** (2) SQLite never explicitly requests a pendig lock. + ** (2) SQLite never explicitly requests a pending lock. ** (3) A shared lock is always held when a reserve lock is requested. */ assert( pFile->eFileLock!=NO_LOCK || eFileLock==SHARED_LOCK ); @@ -34222,7 +42140,7 @@ static int unixLock(sqlite3_file *id, int eFileLock){ /* If some thread using this PID has a lock via a different unixFile* ** handle that precludes the requested lock, return BUSY. */ - if( (pFile->eFileLock!=pInode->eFileLock && + if( (pFile->eFileLock!=pInode->eFileLock && (pInode->eFileLock>=PENDING_LOCK || eFileLock>SHARED_LOCK)) ){ rc = SQLITE_BUSY; @@ -34233,7 +42151,7 @@ static int unixLock(sqlite3_file *id, int eFileLock){ ** has a SHARED or RESERVED lock, then increment reference counts and ** return SQLITE_OK. */ - if( eFileLock==SHARED_LOCK && + if( eFileLock==SHARED_LOCK && (pInode->eFileLock==SHARED_LOCK || pInode->eFileLock==RESERVED_LOCK) ){ assert( eFileLock==SHARED_LOCK ); assert( pFile->eFileLock==0 ); @@ -34251,8 +42169,8 @@ static int unixLock(sqlite3_file *id, int eFileLock){ */ lock.l_len = 1L; lock.l_whence = SEEK_SET; - if( eFileLock==SHARED_LOCK - || (eFileLock==EXCLUSIVE_LOCK && pFile->eFileLockeFileLock==RESERVED_LOCK) ){ lock.l_type = (eFileLock==SHARED_LOCK?F_RDLCK:F_WRLCK); lock.l_start = PENDING_BYTE; @@ -34263,6 +42181,9 @@ static int unixLock(sqlite3_file *id, int eFileLock){ storeLastErrno(pFile, tErrno); } goto end_lock; + }else if( eFileLock==EXCLUSIVE_LOCK ){ + pFile->eFileLock = PENDING_LOCK; + pInode->eFileLock = PENDING_LOCK; } } @@ -34290,7 +42211,7 @@ static int unixLock(sqlite3_file *id, int eFileLock){ if( unixFileLock(pFile, &lock) && rc==SQLITE_OK ){ /* This could happen with a network mount */ tErrno = errno; - rc = SQLITE_IOERR_UNLOCK; + rc = SQLITE_IOERR_UNLOCK; } if( rc ){ @@ -34307,6 +42228,14 @@ static int unixLock(sqlite3_file *id, int eFileLock){ /* We are trying for an exclusive lock but another thread in this ** same process is still holding a shared lock. */ rc = SQLITE_BUSY; + }else if( unixIsSharingShmNode(pFile) ){ + /* We are in WAL mode and attempting to delete the SHM and WAL + ** files due to closing the connection or changing out of WAL mode, + ** but another process still holds locks on the SHM file, thus + ** indicating that database locks have been broken, perhaps due + ** to a rogue close(open(dbFile)) or similar. + */ + rc = SQLITE_BUSY; }else{ /* The request was for a RESERVED or EXCLUSIVE lock. It is ** assumed that there is a SHARED or greater lock on the file @@ -34332,7 +42261,7 @@ static int unixLock(sqlite3_file *id, int eFileLock){ } } } - + #ifdef SQLITE_DEBUG /* Set up the transaction-counter change checking flags when @@ -34350,18 +42279,14 @@ static int unixLock(sqlite3_file *id, int eFileLock){ } #endif - if( rc==SQLITE_OK ){ pFile->eFileLock = eFileLock; pInode->eFileLock = eFileLock; - }else if( eFileLock==EXCLUSIVE_LOCK ){ - pFile->eFileLock = PENDING_LOCK; - pInode->eFileLock = PENDING_LOCK; } end_lock: sqlite3_mutex_leave(pInode->pLockMutex); - OSTRACE(("LOCK %d %s %s (unix)\n", pFile->h, azFileLock(eFileLock), + OSTRACE(("LOCK %d %s %s (unix)\n", pFile->h, azFileLock(eFileLock), rc==SQLITE_OK ? "ok" : "failed")); return rc; } @@ -34386,11 +42311,11 @@ static void setPendingFd(unixFile *pFile){ ** ** If the locking level of the file descriptor is already at or below ** the requested locking level, this routine is a no-op. -** +** ** If handleNFSUnlock is true, then on downgrading an EXCLUSIVE_LOCK to SHARED ** the byte range is divided into 2 parts and the first part is unlocked then -** set to a read lock, then the other part is simply unlocked. This works -** around a bug in BSD NFS lockd (also seen on MacOSX 10.3+) that fails to +** set to a read lock, then the other part is simply unlocked. This works +** around a bug in BSD NFS lockd (also seen on MacOSX 10.3+) that fails to ** remove the write lock on a region when a read lock is set. */ static int posixUnlock(sqlite3_file *id, int eFileLock, int handleNFSUnlock){ @@ -34428,7 +42353,7 @@ static int posixUnlock(sqlite3_file *id, int eFileLock, int handleNFSUnlock){ /* downgrading to a shared lock on NFS involves clearing the write lock ** before establishing the readlock - to avoid a race condition we downgrade - ** the lock in 2 blocks, so that part of the range will be covered by a + ** the lock in 2 blocks, so that part of the range will be covered by a ** write lock until the rest is covered by a read lock: ** 1: [WWWWW] ** 2: [....W] @@ -34444,7 +42369,7 @@ static int posixUnlock(sqlite3_file *id, int eFileLock, int handleNFSUnlock){ if( handleNFSUnlock ){ int tErrno; /* Error code from system call errors */ off_t divSize = SHARED_SIZE - 1; - + lock.l_type = F_UNLCK; lock.l_whence = SEEK_SET; lock.l_start = SHARED_FIRST; @@ -34486,11 +42411,11 @@ static int posixUnlock(sqlite3_file *id, int eFileLock, int handleNFSUnlock){ lock.l_len = SHARED_SIZE; if( unixFileLock(pFile, &lock) ){ /* In theory, the call to unixFileLock() cannot fail because another - ** process is holding an incompatible lock. If it does, this + ** process is holding an incompatible lock. If it does, this ** indicates that the other process is not following the locking ** protocol. If this happens, return SQLITE_IOERR_RDLOCK. Returning - ** SQLITE_BUSY would confuse the upper layer (in practice it causes - ** an assert to fail). */ + ** SQLITE_BUSY would confuse the upper layer (in practice it causes + ** an assert to fail). */ rc = SQLITE_IOERR_RDLOCK; storeLastErrno(pFile, errno); goto end_unlock; @@ -34566,7 +42491,7 @@ static void unixUnmapfile(unixFile *pFd); #endif /* -** This function performs the parts of the "close file" operation +** This function performs the parts of the "close file" operation ** common to all locking schemes. It closes the directory and file ** handles, if they are valid, and sets all fields of the unixFile ** structure to 0. @@ -34629,13 +42554,14 @@ static int unixClose(sqlite3_file *id){ if( pInode->nLock ){ /* If there are outstanding locks, do not actually close the file just ** yet because that would clear those locks. Instead, add the file - ** descriptor to pInode->pUnused list. It will be automatically closed + ** descriptor to pInode->pUnused list. It will be automatically closed ** when the last lock is cleared. */ setPendingFd(pFile); } sqlite3_mutex_leave(pInode->pLockMutex); releaseInodeInfo(pFile); + assert( pFile->pShm==0 ); rc = closeUnixFile(id); unixLeaveMutex(); return rc; @@ -34715,26 +42641,22 @@ static int nolockClose(sqlite3_file *id) { /* ** This routine checks if there is a RESERVED lock held on the specified -** file by this or any other process. If such a lock is held, set *pResOut -** to a non-zero value otherwise *pResOut is set to zero. The return value -** is set to SQLITE_OK unless an I/O error occurs during lock checking. -** -** In dotfile locking, either a lock exists or it does not. So in this -** variation of CheckReservedLock(), *pResOut is set to true if any lock -** is held on the file and false if the file is unlocked. +** file by this or any other process. If the caller holds a SHARED +** or greater lock when it is called, then it is assumed that no other +** client may hold RESERVED. Or, if the caller holds no lock, then it +** is assumed another client holds RESERVED if the lock-file exists. */ static int dotlockCheckReservedLock(sqlite3_file *id, int *pResOut) { - int rc = SQLITE_OK; - int reserved = 0; unixFile *pFile = (unixFile*)id; - SimulateIOError( return SQLITE_IOERR_CHECKRESERVEDLOCK; ); - - assert( pFile ); - reserved = osAccess((const char*)pFile->lockingContext, 0)==0; - OSTRACE(("TEST WR-LOCK %d %d %d (dotlock)\n", pFile->h, rc, reserved)); - *pResOut = reserved; - return rc; + + if( pFile->eFileLock>=SHARED_LOCK ){ + *pResOut = 0; + }else{ + *pResOut = osAccess((const char*)pFile->lockingContext, 0)==0; + } + OSTRACE(("TEST WR-LOCK %d %d %d (dotlock)\n", pFile->h, 0, *pResOut)); + return SQLITE_OK; } /* @@ -34783,7 +42705,7 @@ static int dotlockLock(sqlite3_file *id, int eFileLock) { #endif return SQLITE_OK; } - + /* grab an exclusive lock */ rc = osMkdir(zLockFile, 0777); if( rc<0 ){ @@ -34798,8 +42720,8 @@ static int dotlockLock(sqlite3_file *id, int eFileLock) { } } return rc; - } - + } + /* got it, set the type and return ok */ pFile->eFileLock = eFileLock; return rc; @@ -34823,7 +42745,7 @@ static int dotlockUnlock(sqlite3_file *id, int eFileLock) { OSTRACE(("UNLOCK %d %d was %d pid=%d (dotlock)\n", pFile->h, eFileLock, pFile->eFileLock, osGetpid(0))); assert( eFileLock<=SHARED_LOCK ); - + /* no-op if possible */ if( pFile->eFileLock==eFileLock ){ return SQLITE_OK; @@ -34836,7 +42758,7 @@ static int dotlockUnlock(sqlite3_file *id, int eFileLock) { pFile->eFileLock = SHARED_LOCK; return SQLITE_OK; } - + /* To fully unlock the database, delete the lock file */ assert( eFileLock==NO_LOCK ); rc = osRmdir(zLockFile); @@ -34848,7 +42770,7 @@ static int dotlockUnlock(sqlite3_file *id, int eFileLock) { rc = SQLITE_IOERR_UNLOCK; storeLastErrno(pFile, tErrno); } - return rc; + return rc; } pFile->eFileLock = NO_LOCK; return SQLITE_OK; @@ -34895,7 +42817,7 @@ static int robust_flock(int fd, int op){ #else # define robust_flock(a,b) flock(a,b) #endif - + /* ** This routine checks if there is a RESERVED lock held on the specified @@ -34904,54 +42826,33 @@ static int robust_flock(int fd, int op){ ** is set to SQLITE_OK unless an I/O error occurs during lock checking. */ static int flockCheckReservedLock(sqlite3_file *id, int *pResOut){ - int rc = SQLITE_OK; - int reserved = 0; +#ifdef SQLITE_DEBUG unixFile *pFile = (unixFile*)id; - +#else + UNUSED_PARAMETER(id); +#endif + SimulateIOError( return SQLITE_IOERR_CHECKRESERVEDLOCK; ); - + assert( pFile ); - - /* Check if a thread in this process holds such a lock */ - if( pFile->eFileLock>SHARED_LOCK ){ - reserved = 1; - } - - /* Otherwise see if some other process holds it. */ - if( !reserved ){ - /* attempt to get the lock */ - int lrc = robust_flock(pFile->h, LOCK_EX | LOCK_NB); - if( !lrc ){ - /* got the lock, unlock it */ - lrc = robust_flock(pFile->h, LOCK_UN); - if ( lrc ) { - int tErrno = errno; - /* unlock failed with an error */ - lrc = SQLITE_IOERR_UNLOCK; - storeLastErrno(pFile, tErrno); - rc = lrc; - } - } else { - int tErrno = errno; - reserved = 1; - /* someone else might have it reserved */ - lrc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_LOCK); - if( IS_LOCK_ERROR(lrc) ){ - storeLastErrno(pFile, tErrno); - rc = lrc; - } - } - } - OSTRACE(("TEST WR-LOCK %d %d %d (flock)\n", pFile->h, rc, reserved)); + assert( pFile->eFileLock<=SHARED_LOCK ); -#ifdef SQLITE_IGNORE_FLOCK_LOCK_ERRORS - if( (rc & 0xff) == SQLITE_IOERR ){ - rc = SQLITE_OK; - reserved=1; - } -#endif /* SQLITE_IGNORE_FLOCK_LOCK_ERRORS */ - *pResOut = reserved; - return rc; + /* The flock VFS only ever takes exclusive locks (see function flockLock). + ** Therefore, if this connection is holding any lock at all, no other + ** connection may be holding a RESERVED lock. So set *pResOut to 0 + ** in this case. + ** + ** Or, this connection may be holding no lock. In that case, set *pResOut to + ** 0 as well. The caller will then attempt to take an EXCLUSIVE lock on the + ** db in order to roll the hot journal back. If there is another connection + ** holding a lock, that attempt will fail and an SQLITE_BUSY returned to + ** the user. With other VFS, we try to avoid this, in order to allow a reader + ** to proceed while a writer is preparing its transaction. But that won't + ** work with the flock VFS - as it always takes EXCLUSIVE locks - so it is + ** not a problem in this case. */ + *pResOut = 0; + + return SQLITE_OK; } /* @@ -34989,15 +42890,15 @@ static int flockLock(sqlite3_file *id, int eFileLock) { assert( pFile ); - /* if we already have a lock, it is exclusive. + /* if we already have a lock, it is exclusive. ** Just adjust level and punt on outta here. */ if (pFile->eFileLock > NO_LOCK) { pFile->eFileLock = eFileLock; return SQLITE_OK; } - + /* grab an exclusive lock */ - + if (robust_flock(pFile->h, LOCK_EX | LOCK_NB)) { int tErrno = errno; /* didn't get, must be busy */ @@ -35009,7 +42910,7 @@ static int flockLock(sqlite3_file *id, int eFileLock) { /* got it, set the type and return ok */ pFile->eFileLock = eFileLock; } - OSTRACE(("LOCK %d %s %s (flock)\n", pFile->h, azFileLock(eFileLock), + OSTRACE(("LOCK %d %s %s (flock)\n", pFile->h, azFileLock(eFileLock), rc==SQLITE_OK ? "ok" : "failed")); #ifdef SQLITE_IGNORE_FLOCK_LOCK_ERRORS if( (rc & 0xff) == SQLITE_IOERR ){ @@ -35029,23 +42930,23 @@ static int flockLock(sqlite3_file *id, int eFileLock) { */ static int flockUnlock(sqlite3_file *id, int eFileLock) { unixFile *pFile = (unixFile*)id; - + assert( pFile ); OSTRACE(("UNLOCK %d %d was %d pid=%d (flock)\n", pFile->h, eFileLock, pFile->eFileLock, osGetpid(0))); assert( eFileLock<=SHARED_LOCK ); - + /* no-op if possible */ if( pFile->eFileLock==eFileLock ){ return SQLITE_OK; } - + /* shared can just be set because we always have an exclusive */ if (eFileLock==SHARED_LOCK) { pFile->eFileLock = eFileLock; return SQLITE_OK; } - + /* no, really, unlock. */ if( robust_flock(pFile->h, LOCK_UN) ){ #ifdef SQLITE_IGNORE_FLOCK_LOCK_ERRORS @@ -35096,14 +42997,14 @@ static int semXCheckReservedLock(sqlite3_file *id, int *pResOut) { unixFile *pFile = (unixFile*)id; SimulateIOError( return SQLITE_IOERR_CHECKRESERVEDLOCK; ); - + assert( pFile ); /* Check if a thread in this process holds such a lock */ if( pFile->eFileLock>SHARED_LOCK ){ reserved = 1; } - + /* Otherwise see if some other process holds it. */ if( !reserved ){ sem_t *pSem = pFile->pInode->pSem; @@ -35162,14 +43063,14 @@ static int semXLock(sqlite3_file *id, int eFileLock) { sem_t *pSem = pFile->pInode->pSem; int rc = SQLITE_OK; - /* if we already have a lock, it is exclusive. + /* if we already have a lock, it is exclusive. ** Just adjust level and punt on outta here. */ if (pFile->eFileLock > NO_LOCK) { pFile->eFileLock = eFileLock; rc = SQLITE_OK; goto sem_end_lock; } - + /* lock semaphore now but bail out when already locked. */ if( sem_trywait(pSem)==-1 ){ rc = SQLITE_BUSY; @@ -35199,18 +43100,18 @@ static int semXUnlock(sqlite3_file *id, int eFileLock) { OSTRACE(("UNLOCK %d %d was %d pid=%d (sem)\n", pFile->h, eFileLock, pFile->eFileLock, osGetpid(0))); assert( eFileLock<=SHARED_LOCK ); - + /* no-op if possible */ if( pFile->eFileLock==eFileLock ){ return SQLITE_OK; } - + /* shared can just be set because we always have an exclusive */ if (eFileLock==SHARED_LOCK) { pFile->eFileLock = eFileLock; return SQLITE_OK; } - + /* no, really unlock. */ if ( sem_post(pSem)==-1 ) { int rc, tErrno = errno; @@ -35218,7 +43119,7 @@ static int semXUnlock(sqlite3_file *id, int eFileLock) { if( IS_LOCK_ERROR(rc) ){ storeLastErrno(pFile, tErrno); } - return rc; + return rc; } pFile->eFileLock = NO_LOCK; return SQLITE_OK; @@ -35284,7 +43185,7 @@ struct ByteRangeLockPB2 /* ** This is a utility for setting or clearing a bit-range lock on an ** AFP filesystem. -** +** ** Return SQLITE_OK on success, SQLITE_BUSY on failure. */ static int afpSetLock( @@ -35296,14 +43197,14 @@ static int afpSetLock( ){ struct ByteRangeLockPB2 pb; int err; - + pb.unLockFlag = setLockFlag ? 0 : 1; pb.startEndFlag = 0; pb.offset = offset; - pb.length = length; + pb.length = length; pb.fd = pFile->h; - - OSTRACE(("AFPSETLOCK [%s] for %d%s in range %llx:%llx\n", + + OSTRACE(("AFPSETLOCK [%s] for %d%s in range %llx:%llx\n", (setLockFlag?"ON":"OFF"), pFile->h, (pb.fd==-1?"[testval-1]":""), offset, length)); err = fsctl(path, afpfsByteRangeLock2FSCTL, &pb, 0); @@ -35338,9 +43239,9 @@ static int afpCheckReservedLock(sqlite3_file *id, int *pResOut){ int reserved = 0; unixFile *pFile = (unixFile*)id; afpLockingContext *context; - + SimulateIOError( return SQLITE_IOERR_CHECKRESERVEDLOCK; ); - + assert( pFile ); context = (afpLockingContext *) pFile->lockingContext; if( context->reserved ){ @@ -35352,12 +43253,12 @@ static int afpCheckReservedLock(sqlite3_file *id, int *pResOut){ if( pFile->pInode->eFileLock>SHARED_LOCK ){ reserved = 1; } - + /* Otherwise see if some other process holds it. */ if( !reserved ){ /* lock the RESERVED byte */ - int lrc = afpSetLock(context->dbPath, pFile, RESERVED_BYTE, 1,1); + int lrc = afpSetLock(context->dbPath, pFile, RESERVED_BYTE, 1,1); if( SQLITE_OK==lrc ){ /* if we succeeded in taking the reserved lock, unlock it to restore ** the original state */ @@ -35370,10 +43271,10 @@ static int afpCheckReservedLock(sqlite3_file *id, int *pResOut){ rc=lrc; } } - + sqlite3_mutex_leave(pFile->pInode->pLockMutex); OSTRACE(("TEST WR-LOCK %d %d %d (afp)\n", pFile->h, rc, reserved)); - + *pResOut = reserved; return rc; } @@ -35407,7 +43308,7 @@ static int afpLock(sqlite3_file *id, int eFileLock){ unixFile *pFile = (unixFile*)id; unixInodeInfo *pInode = pFile->pInode; afpLockingContext *context = (afpLockingContext *) pFile->lockingContext; - + assert( pFile ); OSTRACE(("LOCK %d %s was %s(%s,%d) pid=%d (afp)\n", pFile->h, azFileLock(eFileLock), azFileLock(pFile->eFileLock), @@ -35425,13 +43326,13 @@ static int afpLock(sqlite3_file *id, int eFileLock){ /* Make sure the locking sequence is correct ** (1) We never move from unlocked to anything higher than shared lock. - ** (2) SQLite never explicitly requests a pendig lock. + ** (2) SQLite never explicitly requests a pending lock. ** (3) A shared lock is always held when a reserve lock is requested. */ assert( pFile->eFileLock!=NO_LOCK || eFileLock==SHARED_LOCK ); assert( eFileLock!=PENDING_LOCK ); assert( eFileLock!=RESERVED_LOCK || pFile->eFileLock==SHARED_LOCK ); - + /* This mutex is needed because pFile->pInode is shared across threads */ pInode = pFile->pInode; @@ -35440,18 +43341,18 @@ static int afpLock(sqlite3_file *id, int eFileLock){ /* If some thread using this PID has a lock via a different unixFile* ** handle that precludes the requested lock, return BUSY. */ - if( (pFile->eFileLock!=pInode->eFileLock && + if( (pFile->eFileLock!=pInode->eFileLock && (pInode->eFileLock>=PENDING_LOCK || eFileLock>SHARED_LOCK)) ){ rc = SQLITE_BUSY; goto afp_end_lock; } - + /* If a SHARED lock is requested, and some thread using this PID already ** has a SHARED or RESERVED lock, then increment reference counts and ** return SQLITE_OK. */ - if( eFileLock==SHARED_LOCK && + if( eFileLock==SHARED_LOCK && (pInode->eFileLock==SHARED_LOCK || pInode->eFileLock==RESERVED_LOCK) ){ assert( eFileLock==SHARED_LOCK ); assert( pFile->eFileLock==0 ); @@ -35461,12 +43362,12 @@ static int afpLock(sqlite3_file *id, int eFileLock){ pInode->nLock++; goto afp_end_lock; } - + /* A PENDING lock is needed before acquiring a SHARED lock and before ** acquiring an EXCLUSIVE lock. For the SHARED lock, the PENDING will ** be released. */ - if( eFileLock==SHARED_LOCK + if( eFileLock==SHARED_LOCK || (eFileLock==EXCLUSIVE_LOCK && pFile->eFileLocknShared==0 ); assert( pInode->eFileLock==0 ); - + mask = (sizeof(long)==8) ? LARGEST_INT64 : 0x7fffffff; /* Now get the read-lock SHARED_LOCK */ /* note that the quality of the randomness doesn't matter that much */ - lk = random(); + lk = random(); pInode->sharedByte = (lk & mask)%(SHARED_SIZE - 1); - lrc1 = afpSetLock(context->dbPath, pFile, + lrc1 = afpSetLock(context->dbPath, pFile, SHARED_FIRST+pInode->sharedByte, 1, 1); if( IS_LOCK_ERROR(lrc1) ){ lrc1Errno = pFile->lastErrno; } /* Drop the temporary PENDING lock */ lrc2 = afpSetLock(context->dbPath, pFile, PENDING_BYTE, 1, 0); - + if( IS_LOCK_ERROR(lrc1) ) { storeLastErrno(pFile, lrc1Errno); rc = lrc1; @@ -35534,34 +43435,34 @@ static int afpLock(sqlite3_file *id, int eFileLock){ } if (!failed && eFileLock == EXCLUSIVE_LOCK) { /* Acquire an EXCLUSIVE lock */ - - /* Remove the shared lock before trying the range. we'll need to + + /* Remove the shared lock before trying the range. we'll need to ** reestablish the shared lock if we can't get the afpUnlock */ if( !(failed = afpSetLock(context->dbPath, pFile, SHARED_FIRST + pInode->sharedByte, 1, 0)) ){ int failed2 = SQLITE_OK; - /* now attemmpt to get the exclusive lock range */ - failed = afpSetLock(context->dbPath, pFile, SHARED_FIRST, + /* now attempt to get the exclusive lock range */ + failed = afpSetLock(context->dbPath, pFile, SHARED_FIRST, SHARED_SIZE, 1); - if( failed && (failed2 = afpSetLock(context->dbPath, pFile, + if( failed && (failed2 = afpSetLock(context->dbPath, pFile, SHARED_FIRST + pInode->sharedByte, 1, 1)) ){ /* Can't reestablish the shared lock. Sqlite can't deal, this is ** a critical I/O error */ - rc = ((failed & 0xff) == SQLITE_IOERR) ? failed2 : + rc = ((failed & 0xff) == SQLITE_IOERR) ? failed2 : SQLITE_IOERR_LOCK; goto afp_end_lock; - } + } }else{ - rc = failed; + rc = failed; } } if( failed ){ rc = failed; } } - + if( rc==SQLITE_OK ){ pFile->eFileLock = eFileLock; pInode->eFileLock = eFileLock; @@ -35569,10 +43470,10 @@ static int afpLock(sqlite3_file *id, int eFileLock){ pFile->eFileLock = PENDING_LOCK; pInode->eFileLock = PENDING_LOCK; } - + afp_end_lock: sqlite3_mutex_leave(pInode->pLockMutex); - OSTRACE(("LOCK %d %s %s (afp)\n", pFile->h, azFileLock(eFileLock), + OSTRACE(("LOCK %d %s %s (afp)\n", pFile->h, azFileLock(eFileLock), rc==SQLITE_OK ? "ok" : "failed")); return rc; } @@ -35590,9 +43491,6 @@ static int afpUnlock(sqlite3_file *id, int eFileLock) { unixInodeInfo *pInode; afpLockingContext *context = (afpLockingContext *) pFile->lockingContext; int skipShared = 0; -#ifdef SQLITE_TEST - int h = pFile->h; -#endif assert( pFile ); OSTRACE(("UNLOCK %d %d was %d(%d,%d) pid=%d (afp)\n", pFile->h, eFileLock, @@ -35608,10 +43506,7 @@ static int afpUnlock(sqlite3_file *id, int eFileLock) { assert( pInode->nShared!=0 ); if( pFile->eFileLock>SHARED_LOCK ){ assert( pInode->eFileLock==pFile->eFileLock ); - SimulateIOErrorBenign(1); - SimulateIOError( h=(-1) ) - SimulateIOErrorBenign(0); - + #ifdef SQLITE_DEBUG /* When reducing a lock such that other processes can start ** reading the database file again, make sure that the @@ -35626,7 +43521,7 @@ static int afpUnlock(sqlite3_file *id, int eFileLock) { || pFile->transCntrChng==1 ); pFile->inNormalWrite = 0; #endif - + if( pFile->eFileLock==EXCLUSIVE_LOCK ){ rc = afpSetLock(context->dbPath, pFile, SHARED_FIRST, SHARED_SIZE, 0); if( rc==SQLITE_OK && (eFileLock==SHARED_LOCK || pInode->nShared>1) ){ @@ -35639,11 +43534,11 @@ static int afpUnlock(sqlite3_file *id, int eFileLock) { } if( rc==SQLITE_OK && pFile->eFileLock>=PENDING_LOCK ){ rc = afpSetLock(context->dbPath, pFile, PENDING_BYTE, 1, 0); - } + } if( rc==SQLITE_OK && pFile->eFileLock>=RESERVED_LOCK && context->reserved ){ rc = afpSetLock(context->dbPath, pFile, RESERVED_BYTE, 1, 0); - if( !rc ){ - context->reserved = 0; + if( !rc ){ + context->reserved = 0; } } if( rc==SQLITE_OK && (eFileLock==SHARED_LOCK || pInode->nShared>1)){ @@ -35659,9 +43554,6 @@ static int afpUnlock(sqlite3_file *id, int eFileLock) { unsigned long long sharedLockByte = SHARED_FIRST+pInode->sharedByte; pInode->nShared--; if( pInode->nShared==0 ){ - SimulateIOErrorBenign(1); - SimulateIOError( h=(-1) ) - SimulateIOErrorBenign(0); if( !skipShared ){ rc = afpSetLock(context->dbPath, pFile, sharedLockByte, 1, 0); } @@ -35676,7 +43568,7 @@ static int afpUnlock(sqlite3_file *id, int eFileLock) { if( pInode->nLock==0 ) closePendingFds(pFile); } } - + sqlite3_mutex_leave(pInode->pLockMutex); if( rc==SQLITE_OK ){ pFile->eFileLock = eFileLock; @@ -35685,7 +43577,7 @@ static int afpUnlock(sqlite3_file *id, int eFileLock) { } /* -** Close a file & cleanup AFP specific locking context +** Close a file & cleanup AFP specific locking context */ static int afpClose(sqlite3_file *id) { int rc = SQLITE_OK; @@ -35743,7 +43635,7 @@ static int nfsUnlock(sqlite3_file *id, int eFileLock){ /* ** The code above is the NFS lock implementation. The code is specific ** to MacOSX and does not work on other unix platforms. No alternative -** is available. +** is available. ** ********************* End of the NFS lock implementation ********************** ******************************************************************************/ @@ -35751,7 +43643,7 @@ static int nfsUnlock(sqlite3_file *id, int eFileLock){ /****************************************************************************** **************** Non-locking sqlite3_file methods ***************************** ** -** The next division contains implementations for all methods of the +** The next division contains implementations for all methods of the ** sqlite3_file object other than the locking methods. The locking ** methods were defined in divisions above (one locking method per ** division). Those methods that are common to all locking modes @@ -35759,15 +43651,9 @@ static int nfsUnlock(sqlite3_file *id, int eFileLock){ */ /* -** Seek to the offset passed as the second argument, then read cnt +** Seek to the offset passed as the second argument, then read cnt ** bytes into pBuf. Return the number of bytes actually read. ** -** NB: If you define USE_PREAD or USE_PREAD64, then it might also -** be necessary to define _XOPEN_SOURCE to be 500. This varies from -** one system to another. Since SQLite does not define USE_PREAD -** in any form by default, we will not attempt to define _XOPEN_SOURCE. -** See tickets #2741 and #2681. -** ** To avoid stomping the errno value on a failed read the lastErrno value ** is set before returning. */ @@ -35821,8 +43707,8 @@ static int seekAndRead(unixFile *id, sqlite3_int64 offset, void *pBuf, int cnt){ ** wrong. */ static int unixRead( - sqlite3_file *id, - void *pBuf, + sqlite3_file *id, + void *pBuf, int amt, sqlite3_int64 offset ){ @@ -35832,17 +43718,17 @@ static int unixRead( assert( offset>=0 ); assert( amt>0 ); - /* If this is a database file (not a journal, master-journal or temp + /* If this is a database file (not a journal, super-journal or temp ** file), the bytes in the locking range should never be read or written. */ #if 0 assert( pFile->pPreallocatedUnused==0 || offset>=PENDING_BYTE+512 - || offset+amt<=PENDING_BYTE + || offset+amt<=PENDING_BYTE ); #endif #if SQLITE_MAX_MMAP_SIZE>0 - /* Deal with as much of this read request as possible by transfering + /* Deal with as much of this read request as possible by transferring ** data from the memory mapping using memcpy(). */ if( offsetmmapSize ){ if( offset+amt <= pFile->mmapSize ){ @@ -35862,7 +43748,24 @@ static int unixRead( if( got==amt ){ return SQLITE_OK; }else if( got<0 ){ - /* lastErrno set by seekAndRead */ + /* pFile->lastErrno has been set by seekAndRead(). + ** Usually we return SQLITE_IOERR_READ here, though for some + ** kinds of errors we return SQLITE_IOERR_CORRUPTFS. The + ** SQLITE_IOERR_CORRUPTFS will be converted into SQLITE_CORRUPT + ** prior to returning to the application by the sqlite3ApiExit() + ** routine. + */ + switch( pFile->lastErrno ){ + case ERANGE: + case EIO: +#ifdef ENXIO + case ENXIO: +#endif +#ifdef EDEVERR + case EDEVERR: +#endif + return SQLITE_IOERR_CORRUPTFS; + } return SQLITE_IOERR_READ; }else{ storeLastErrno(pFile, 0); /* not a system error */ @@ -35875,7 +43778,7 @@ static int unixRead( /* ** Attempt to seek the file-descriptor passed as the first argument to ** absolute offset iOff, then attempt to write nBuf bytes of data from -** pBuf to it. If an error occurs, return -1 and set *piErrno. Otherwise, +** pBuf to it. If an error occurs, return -1 and set *piErrno. Otherwise, ** return the actual number of bytes written (which may be less than ** nBuf). */ @@ -35935,22 +43838,22 @@ static int seekAndWrite(unixFile *id, i64 offset, const void *pBuf, int cnt){ ** or some other error code on failure. */ static int unixWrite( - sqlite3_file *id, - const void *pBuf, + sqlite3_file *id, + const void *pBuf, int amt, - sqlite3_int64 offset + sqlite3_int64 offset ){ unixFile *pFile = (unixFile*)id; int wrote = 0; assert( id ); assert( amt>0 ); - /* If this is a database file (not a journal, master-journal or temp + /* If this is a database file (not a journal, super-journal or temp ** file), the bytes in the locking range should never be read or written. */ #if 0 assert( pFile->pPreallocatedUnused==0 || offset>=PENDING_BYTE+512 - || offset+amt<=PENDING_BYTE + || offset+amt<=PENDING_BYTE ); #endif @@ -35977,7 +43880,7 @@ static int unixWrite( #endif #if defined(SQLITE_MMAP_READWRITE) && SQLITE_MAX_MMAP_SIZE>0 - /* Deal with as much of this write request as possible by transfering + /* Deal with as much of this write request as possible by transferring ** data from the memory mapping using memcpy(). */ if( offsetmmapSize ){ if( offset+amt <= pFile->mmapSize ){ @@ -35992,7 +43895,7 @@ static int unixWrite( } } #endif - + while( (wrote = seekAndWrite(pFile, offset, pBuf, amt))0 ){ amt -= wrote; offset += wrote; @@ -36058,8 +43961,8 @@ SQLITE_API int sqlite3_fullsync_count = 0; ** ** SQLite sets the dataOnly flag if the size of the file is unchanged. ** The idea behind dataOnly is that it should only write the file content -** to disk, not the inode. We only set dataOnly if the file size is -** unchanged since the file size is part of the inode. However, +** to disk, not the inode. We only set dataOnly if the file size is +** unchanged since the file size is part of the inode. However, ** Ted Ts'o tells us that fdatasync() will also write the inode if the ** file size has changed. The only real difference between fdatasync() ** and fsync(), Ted tells us, is that fdatasync() will not flush the @@ -36073,7 +43976,7 @@ static int full_fsync(int fd, int fullSync, int dataOnly){ int rc; /* The following "ifdef/elif/else/" block has the same structure as - ** the one below. It is replicated here solely to avoid cluttering + ** the one below. It is replicated here solely to avoid cluttering ** up the real code with the UNUSED_PARAMETER() macros. */ #ifdef SQLITE_NO_SYNC @@ -36087,7 +43990,7 @@ static int full_fsync(int fd, int fullSync, int dataOnly){ UNUSED_PARAMETER(dataOnly); #endif - /* Record the number of times that we do a normal fsync() and + /* Record the number of times that we do a normal fsync() and ** FULLSYNC. This is used during testing to verify that this procedure ** gets called with the correct arguments. */ @@ -36099,7 +44002,7 @@ static int full_fsync(int fd, int fullSync, int dataOnly){ /* If we compiled with the SQLITE_NO_SYNC flag, then syncing is a ** no-op. But go ahead and call fstat() to validate the file ** descriptor as we need a method to provoke a failure during - ** coverate testing. + ** coverage testing. */ #ifdef SQLITE_NO_SYNC { @@ -36113,11 +44016,11 @@ static int full_fsync(int fd, int fullSync, int dataOnly){ rc = 1; } /* If the FULLFSYNC failed, fall back to attempting an fsync(). - ** It shouldn't be possible for fullfsync to fail on the local + ** It shouldn't be possible for fullfsync to fail on the local ** file system (on OSX), so failure indicates that FULLFSYNC - ** isn't supported for this file system. So, attempt an fsync - ** and (for now) ignore the overhead of a superfluous fcntl call. - ** It'd be better to detect fullfsync support once and avoid + ** isn't supported for this file system. So, attempt an fsync + ** and (for now) ignore the overhead of a superfluous fcntl call. + ** It'd be better to detect fullfsync support once and avoid ** the fcntl call every time sync is called. */ if( rc ) rc = fsync(fd); @@ -36127,7 +44030,7 @@ static int full_fsync(int fd, int fullSync, int dataOnly){ ** so currently we default to the macro that redefines fdatasync to fsync */ rc = fsync(fd); -#else +#else rc = fdatasync(fd); #if OS_VXWORKS if( rc==-1 && errno==ENOTSUP ){ @@ -36288,7 +44191,7 @@ static int unixTruncate(sqlite3_file *id, i64 nByte){ #if SQLITE_MAX_MMAP_SIZE>0 /* If the file was just truncated to a size smaller than the currently ** mapped region, reduce the effective mapping size as well. SQLite will - ** use read() and write() to access data beyond this point from now on. + ** use read() and write() to access data beyond this point from now on. */ if( nBytemmapSize ){ pFile->mmapSize = nByte; @@ -36334,8 +44237,8 @@ static int unixFileSize(sqlite3_file *id, i64 *pSize){ static int proxyFileControl(sqlite3_file*,int,void*); #endif -/* -** This function is called to handle the SQLITE_FCNTL_SIZE_HINT +/* +** This function is called to handle the SQLITE_FCNTL_SIZE_HINT ** file-control operation. Enlarge the database to nBytes in size ** (rounded up to the next chunk-size). If the database is already ** nBytes or larger, this routine is a no-op. @@ -36344,7 +44247,7 @@ static int fcntlSizeHint(unixFile *pFile, i64 nByte){ if( pFile->szChunk>0 ){ i64 nSize; /* Required file size */ struct stat buf; /* Used to hold return values of fstat() */ - + if( osFstat(pFile->h, &buf) ){ return SQLITE_IOERR_FSTAT; } @@ -36353,8 +44256,8 @@ static int fcntlSizeHint(unixFile *pFile, i64 nByte){ if( nSize>(i64)buf.st_size ){ #if defined(HAVE_POSIX_FALLOCATE) && HAVE_POSIX_FALLOCATE - /* The code below is handling the return value of osFallocate() - ** correctly. posix_fallocate() is defined to "returns zero on success, + /* The code below is handling the return value of osFallocate() + ** correctly. posix_fallocate() is defined to "returns zero on success, ** or an error number on failure". See the manpage for details. */ int err; do{ @@ -36362,7 +44265,7 @@ static int fcntlSizeHint(unixFile *pFile, i64 nByte){ }while( err==EINTR ); if( err && err!=EINVAL ) return SQLITE_IOERR_WRITE; #else - /* If the OS does not have posix_fallocate(), fake it. Write a + /* If the OS does not have posix_fallocate(), fake it. Write a ** single byte to the last byte in each block that falls entirely ** within the extended region. Then, if required, a single byte ** at offset (nSize-1), to set the size of the file correctly. @@ -36421,6 +44324,13 @@ static void unixModeBit(unixFile *pFile, unsigned char mask, int *pArg){ /* Forward declaration */ static int unixGetTempname(int nBuf, char *zBuf); +#if !defined(SQLITE_WASI) && !defined(SQLITE_OMIT_WAL) + static int unixFcntlExternalReader(unixFile*, int*); +#endif +#if defined(SQLITE_DEBUG) || defined(SQLITE_ENABLE_FILESTAT) + static void unixDescribeShm(sqlite3_str*,unixShm*); +#endif + /* ** Information and control of an open file handle. @@ -36443,6 +44353,11 @@ static int unixFileControl(sqlite3_file *id, int op, void *pArg){ } #endif /* __linux__ && SQLITE_ENABLE_BATCH_ATOMIC_WRITE */ + case SQLITE_FCNTL_NULL_IO: { + osClose(pFile->h); + pFile->h = -1; + return SQLITE_OK; + } case SQLITE_FCNTL_LOCKSTATE: { *(int*)pArg = pFile->eFileLock; return SQLITE_OK; @@ -36488,10 +44403,24 @@ static int unixFileControl(sqlite3_file *id, int op, void *pArg){ } #ifdef SQLITE_ENABLE_SETLK_TIMEOUT case SQLITE_FCNTL_LOCK_TIMEOUT: { - pFile->iBusyTimeout = *(int*)pArg; + int iOld = pFile->iBusyTimeout; + int iNew = *(int*)pArg; +#if SQLITE_ENABLE_SETLK_TIMEOUT==1 + pFile->iBusyTimeout = iNew<0 ? 0x7FFFFFFF : (unsigned)iNew; +#elif SQLITE_ENABLE_SETLK_TIMEOUT==2 + pFile->iBusyTimeout = !!(*(int*)pArg); +#else +# error "SQLITE_ENABLE_SETLK_TIMEOUT must be set to 1 or 2" +#endif + *(int*)pArg = iOld; return SQLITE_OK; } -#endif + case SQLITE_FCNTL_BLOCK_ON_CONNECT: { + int iNew = *(int*)pArg; + pFile->bBlockOnConnect = iNew; + return SQLITE_OK; + } +#endif /* SQLITE_ENABLE_SETLK_TIMEOUT */ #if SQLITE_MAX_MMAP_SIZE>0 case SQLITE_FCNTL_MMAP_SIZE: { i64 newLimit = *(i64*)pArg; @@ -36535,15 +44464,84 @@ static int unixFileControl(sqlite3_file *id, int op, void *pArg){ return proxyFileControl(id,op,pArg); } #endif /* SQLITE_ENABLE_LOCKING_STYLE && defined(__APPLE__) */ + + case SQLITE_FCNTL_EXTERNAL_READER: { +#if !defined(SQLITE_WASI) && !defined(SQLITE_OMIT_WAL) + return unixFcntlExternalReader((unixFile*)id, (int*)pArg); +#else + *(int*)pArg = 0; + return SQLITE_OK; +#endif + } + +#if defined(SQLITE_DEBUG) || defined(SQLITE_ENABLE_FILESTAT) + case SQLITE_FCNTL_FILESTAT: { + sqlite3_str *pStr = (sqlite3_str*)pArg; + char aLck[16]; + unixInodeInfo *pInode; + static const char *azLock[] = { "SHARED", "RESERVED", + "PENDING", "EXCLUSIVE" }; + sqlite3_str_appendf(pStr, "{\"h\":%d", pFile->h); + sqlite3_str_appendf(pStr, ",\"vfs\":\"%s\"", pFile->pVfs->zName); + if( pFile->eFileLock ){ + sqlite3_str_appendf(pStr, ",\"eFileLock\":\"%s\"", + azLock[pFile->eFileLock-1]); + if( unixPosixAdvisoryLocks(pFile->h, aLck)==SQLITE_OK ){ + sqlite3_str_appendf(pStr, ",\"pal\":\"%s\"", aLck); + } + } + unixEnterMutex(); + if( pFile->pShm ){ + sqlite3_str_appendall(pStr, ",\"shm\":"); + unixDescribeShm(pStr, pFile->pShm); + } +#if SQLITE_MAX_MMAP_SIZE>0 + if( pFile->mmapSize ){ + sqlite3_str_appendf(pStr, ",\"mmapSize\":%lld", pFile->mmapSize); + sqlite3_str_appendf(pStr, ",\"nFetchOut\":%d", pFile->nFetchOut); + } +#endif + if( (pInode = pFile->pInode)!=0 ){ + sqlite3_str_appendf(pStr, ",\"inode\":{\"nRef\":%d",pInode->nRef); + sqlite3_mutex_enter(pInode->pLockMutex); + sqlite3_str_appendf(pStr, ",\"nShared\":%d", pInode->nShared); + if( pInode->eFileLock ){ + sqlite3_str_appendf(pStr, ",\"eFileLock\":\"%s\"", + azLock[pInode->eFileLock-1]); + } + if( pInode->pUnused ){ + char cSep = '['; + UnixUnusedFd *pUFd = pFile->pInode->pUnused; + sqlite3_str_appendall(pStr, ",\"unusedFd\":"); + while( pUFd ){ + sqlite3_str_appendf(pStr, "%c{\"fd\":%d,\"flags\":%d", + cSep, pUFd->fd, pUFd->flags); + cSep = ','; + if( unixPosixAdvisoryLocks(pUFd->fd, aLck)==SQLITE_OK ){ + sqlite3_str_appendf(pStr, ",\"pal\":\"%s\"", aLck); + } + sqlite3_str_append(pStr, "}", 1); + pUFd = pUFd->pNext; + } + sqlite3_str_append(pStr, "]", 1); + } + sqlite3_mutex_leave(pInode->pLockMutex); + sqlite3_str_append(pStr, "}", 1); + } + unixLeaveMutex(); + sqlite3_str_append(pStr, "}", 1); + return SQLITE_OK; + } +#endif /* SQLITE_DEBUG || SQLITE_ENABLE_FILESTAT */ } return SQLITE_NOTFOUND; } /* ** If pFd->sectorSize is non-zero when this function is called, it is a -** no-op. Otherwise, the values of pFd->sectorSize and -** pFd->deviceCharacteristics are set according to the file-system -** characteristics. +** no-op. Otherwise, the values of pFd->sectorSize and +** pFd->deviceCharacteristics are set according to the file-system +** characteristics. ** ** There are two versions of this function. One for QNX and one for all ** other systems. @@ -36567,6 +44565,7 @@ static void setDeviceCharacteristics(unixFile *pFd){ if( pFd->ctrlFlags & UNIXFILE_PSOW ){ pFd->deviceCharacteristics |= SQLITE_IOCAP_POWERSAFE_OVERWRITE; } + pFd->deviceCharacteristics |= SQLITE_IOCAP_SUBPAGE_READ; pFd->sectorSize = SQLITE_DEFAULT_SECTOR_SIZE; } @@ -36577,7 +44576,7 @@ static void setDeviceCharacteristics(unixFile *pFd){ static void setDeviceCharacteristics(unixFile *pFile){ if( pFile->sectorSize == 0 ){ struct statvfs fsInfo; - + /* Set defaults for non-supported filesystems */ pFile->sectorSize = SQLITE_DEFAULT_SECTOR_SIZE; pFile->deviceCharacteristics = 0; @@ -36617,7 +44616,7 @@ static void setDeviceCharacteristics(unixFile *pFile){ pFile->sectorSize = fsInfo.f_bsize; pFile->deviceCharacteristics = /* full bitset of atomics from max sector size and smaller */ - ((pFile->sectorSize / 512 * SQLITE_IOCAP_ATOMIC512) << 1) - 2 | + (((pFile->sectorSize / 512 * SQLITE_IOCAP_ATOMIC512) << 1) - 2) | SQLITE_IOCAP_SEQUENTIAL | /* The ram filesystem has no write behind ** so it is ordered */ 0; @@ -36625,7 +44624,7 @@ static void setDeviceCharacteristics(unixFile *pFile){ pFile->sectorSize = fsInfo.f_bsize; pFile->deviceCharacteristics = /* full bitset of atomics from max sector size and smaller */ - ((pFile->sectorSize / 512 * SQLITE_IOCAP_ATOMIC512) << 1) - 2 | + (((pFile->sectorSize / 512 * SQLITE_IOCAP_ATOMIC512) << 1) - 2) | SQLITE_IOCAP_SEQUENTIAL | /* The ram filesystem has no write behind ** so it is ordered */ 0; @@ -36686,7 +44685,7 @@ static int unixDeviceCharacteristics(sqlite3_file *id){ /* ** Return the system page size. ** -** This function should not be called directly by other code in this file. +** This function should not be called directly by other code in this file. ** Instead, it should be called via macro osGetpagesize(). */ static int unixGetpagesize(void){ @@ -36701,10 +44700,10 @@ static int unixGetpagesize(void){ #endif /* !defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0 */ -#ifndef SQLITE_OMIT_WAL +#if !defined(SQLITE_WASI) && !defined(SQLITE_OMIT_WAL) /* -** Object used to represent an shared memory buffer. +** Object used to represent an shared memory buffer. ** ** When multiple threads all reference the same wal-index, each thread ** has its own unixShm object, but they all point to a single instance @@ -36724,13 +44723,32 @@ static int unixGetpagesize(void){ ** nRef ** ** The following fields are read-only after the object is created: -** +** ** hShm ** zFilename ** ** Either unixShmNode.pShmMutex must be held or unixShmNode.nRef==0 and ** unixMutexHeld() is true when reading or writing any other field ** in this structure. +** +** aLock[SQLITE_SHM_NLOCK]: +** This array records the various locks held by clients on each of the +** SQLITE_SHM_NLOCK slots. If the aLock[] entry is set to 0, then no +** locks are held by the process on this slot. If it is set to -1, then +** some client holds an EXCLUSIVE lock on the locking slot. If the aLock[] +** value is set to a positive value, then it is the number of shared +** locks currently held on the slot. +** +** aMutex[SQLITE_SHM_NLOCK]: +** Normally, when SQLITE_ENABLE_SETLK_TIMEOUT is not defined, mutex +** pShmMutex is used to protect the aLock[] array and the right to +** call fcntl() on unixShmNode.hShm to obtain or release locks. +** +** If SQLITE_ENABLE_SETLK_TIMEOUT is defined though, we use an array +** of mutexes - one for each locking slot. To read or write locking +** slot aLock[iSlot], the caller must hold the corresponding mutex +** aMutex[iSlot]. Similarly, to call fcntl() to obtain or release a +** lock corresponding to slot iSlot, mutex aMutex[iSlot] must be held. */ struct unixShmNode { unixInodeInfo *pInode; /* unixInodeInfo that owns this SHM node */ @@ -36744,9 +44762,11 @@ struct unixShmNode { char **apRegion; /* Array of mapped shared-memory regions */ int nRef; /* Number of unixShm objects pointing to this */ unixShm *pFirst; /* All unixShm objects pointing to this */ +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + sqlite3_mutex *aMutex[SQLITE_SHM_NLOCK]; +#endif + int aLock[SQLITE_SHM_NLOCK]; /* # shared locks on slot, -1==excl lock */ #ifdef SQLITE_DEBUG - u8 exclMask; /* Mask of exclusive locks held */ - u8 sharedMask; /* Mask of shared locks held */ u8 nextShmId; /* Next available unixShm.id value */ #endif }; @@ -36779,6 +44799,98 @@ struct unixShm { #define UNIX_SHM_BASE ((22+SQLITE_SHM_NLOCK)*4) /* first lock byte */ #define UNIX_SHM_DMS (UNIX_SHM_BASE+SQLITE_SHM_NLOCK) /* deadman switch */ +#if defined(SQLITE_DEBUG) || defined(SQLITE_ENABLE_FILESTAT) +/* +** Describe the pShm object using JSON. Used for diagnostics only. +*/ +static void unixDescribeShm(sqlite3_str *pStr, unixShm *pShm){ + unixShmNode *pNode = pShm->pShmNode; + char aLck[16]; + sqlite3_str_appendf(pStr, "{\"h\":%d", pNode->hShm); + assert( unixMutexHeld() ); + sqlite3_str_appendf(pStr, ",\"nRef\":%d", pNode->nRef); + sqlite3_str_appendf(pStr, ",\"id\":%d", pShm->id); + sqlite3_str_appendf(pStr, ",\"sharedMask\":%d", pShm->sharedMask); + sqlite3_str_appendf(pStr, ",\"exclMask\":%d", pShm->exclMask); + if( unixPosixAdvisoryLocks(pNode->hShm, aLck)==SQLITE_OK ){ + sqlite3_str_appendf(pStr, ",\"pal\":\"%s\"", aLck); + } + sqlite3_str_append(pStr, "}", 1); +} +#endif /* SQLITE_DEBUG || SQLITE_ENABLE_FILESTAT */ + +/* +** Use F_GETLK to check whether or not there are any readers with open +** wal-mode transactions in other processes on database file pFile. If +** no error occurs, return SQLITE_OK and set (*piOut) to 1 if there are +** such transactions, or 0 otherwise. If an error occurs, return an +** SQLite error code. The final value of *piOut is undefined in this +** case. +*/ +static int unixFcntlExternalReader(unixFile *pFile, int *piOut){ + int rc = SQLITE_OK; + *piOut = 0; + if( pFile->pShm){ + unixShmNode *pShmNode = pFile->pShm->pShmNode; + struct flock f; + + memset(&f, 0, sizeof(f)); + f.l_type = F_WRLCK; + f.l_whence = SEEK_SET; + f.l_start = UNIX_SHM_BASE + 3; + f.l_len = SQLITE_SHM_NLOCK - 3; + + sqlite3_mutex_enter(pShmNode->pShmMutex); + if( osFcntl(pShmNode->hShm, F_GETLK, &f)<0 ){ + rc = SQLITE_IOERR_LOCK; + }else{ + *piOut = (f.l_type!=F_UNLCK); + } + sqlite3_mutex_leave(pShmNode->pShmMutex); + } + + return rc; +} + +/* +** If pFile has a -shm file open and it is sharing that file with some +** other connection, either in the same process or in a separate process, +** then return true. Return false if either pFile does not have a -shm +** file open or if it is the only connection to that -shm file across the +** entire system. +** +** This routine is not required for correct operation. It can always return +** false and SQLite will continue to operate according to spec. However, +** when this routine does its job, it adds extra robustness in cases +** where database file locks have been erroneously deleted in a WAL-mode +** database by doing close(open(DATABASE_PATHNAME)) or similar. +** +** With false negatives, SQLite still operates to spec, though with less +** robustness. With false positives, the last database connection on a +** WAL-mode database will fail to unlink the -wal and -shm files, which +** is annoying but harmless. False positives will also prevent a database +** connection from running "PRAGMA journal_mode=DELETE" in order to take +** the database out of WAL mode, which is perhaps more serious, but is +** still not a disaster. +*/ +static int unixIsSharingShmNode(unixFile *pFile){ + unixShmNode *pShmNode; + struct flock lock; + if( pFile->pShm==0 ) return 0; + if( pFile->ctrlFlags & UNIXFILE_EXCL ) return 0; + pShmNode = pFile->pShm->pShmNode; +#if SQLITE_ATOMIC_INTRINSICS + assert( AtomicLoad(&pShmNode->nRef)==1 ); +#endif + memset(&lock, 0, sizeof(lock)); + lock.l_whence = SEEK_SET; + lock.l_start = UNIX_SHM_DMS; + lock.l_len = 1; + lock.l_type = F_WRLCK; + osFcntl(pShmNode->hShm, F_GETLK, &lock); + return (lock.l_type!=F_UNLCK); +} + /* ** Apply posix advisory locks for all bytes from ofst through ofst+n-1. ** @@ -36795,63 +44907,79 @@ static int unixShmSystemLock( struct flock f; /* The posix advisory locking structure */ int rc = SQLITE_OK; /* Result code form fcntl() */ - /* Access to the unixShmNode object is serialized by the caller */ pShmNode = pFile->pInode->pShmNode; - assert( pShmNode->nRef==0 || sqlite3_mutex_held(pShmNode->pShmMutex) ); - assert( pShmNode->nRef>0 || unixMutexHeld() ); + + /* Assert that the parameters are within expected range and that the + ** correct mutex or mutexes are held. */ + assert( pShmNode->nRef>=0 ); + assert( (ofst==UNIX_SHM_DMS && n==1) + || (ofst>=UNIX_SHM_BASE && ofst+n<=(UNIX_SHM_BASE+SQLITE_SHM_NLOCK)) + ); + if( ofst==UNIX_SHM_DMS ){ + assert( pShmNode->nRef>0 || unixMutexHeld() ); + assert( pShmNode->nRef==0 || sqlite3_mutex_held(pShmNode->pShmMutex) ); + }else{ +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + int ii; + for(ii=ofst-UNIX_SHM_BASE; iiaMutex[ii]) ); + } +#else + assert( sqlite3_mutex_held(pShmNode->pShmMutex) ); + assert( pShmNode->nRef>0 ); +#endif + } /* Shared locks never span more than one byte */ assert( n==1 || lockType!=F_RDLCK ); /* Locks are within range */ assert( n>=1 && n<=SQLITE_SHM_NLOCK ); + assert( ofst>=UNIX_SHM_BASE && ofst<=UNIX_SHM_DMS ); + assert( ofst+n-1<=UNIX_SHM_DMS ); if( pShmNode->hShm>=0 ){ + int res; /* Initialize the locking parameters */ f.l_type = lockType; f.l_whence = SEEK_SET; f.l_start = ofst; f.l_len = n; - rc = osSetPosixAdvisoryLock(pShmNode->hShm, &f, pFile); - rc = (rc!=(-1)) ? SQLITE_OK : SQLITE_BUSY; + res = osSetPosixAdvisoryLock(pShmNode->hShm, &f, pFile); + if( res==-1 ){ +#if defined(SQLITE_ENABLE_SETLK_TIMEOUT) && SQLITE_ENABLE_SETLK_TIMEOUT==1 + rc = (pFile->iBusyTimeout ? SQLITE_BUSY_TIMEOUT : SQLITE_BUSY); +#else + rc = SQLITE_BUSY; +#endif + } } - /* Update the global lock state and do debug tracing */ + /* Do debug tracing */ #ifdef SQLITE_DEBUG - { u16 mask; OSTRACE(("SHM-LOCK ")); - mask = ofst>31 ? 0xffff : (1<<(ofst+n)) - (1<exclMask &= ~mask; - pShmNode->sharedMask &= ~mask; + OSTRACE(("unlock %d..%d ok\n", ofst, ofst+n-1)); }else if( lockType==F_RDLCK ){ - OSTRACE(("read-lock %d ok", ofst)); - pShmNode->exclMask &= ~mask; - pShmNode->sharedMask |= mask; + OSTRACE(("read-lock %d..%d ok\n", ofst, ofst+n-1)); }else{ assert( lockType==F_WRLCK ); - OSTRACE(("write-lock %d ok", ofst)); - pShmNode->exclMask |= mask; - pShmNode->sharedMask &= ~mask; + OSTRACE(("write-lock %d..%d ok\n", ofst, ofst+n-1)); } }else{ if( lockType==F_UNLCK ){ - OSTRACE(("unlock %d failed", ofst)); + OSTRACE(("unlock %d..%d failed\n", ofst, ofst+n-1)); }else if( lockType==F_RDLCK ){ - OSTRACE(("read-lock failed")); + OSTRACE(("read-lock %d..%d failed\n", ofst, ofst+n-1)); }else{ assert( lockType==F_WRLCK ); - OSTRACE(("write-lock %d failed", ofst)); + OSTRACE(("write-lock %d..%d failed\n", ofst, ofst+n-1)); } } - OSTRACE((" - afterwards %03x,%03x\n", - pShmNode->sharedMask, pShmNode->exclMask)); - } #endif - return rc; + return rc; } /* @@ -36885,6 +45013,11 @@ static void unixShmPurge(unixFile *pFd){ int i; assert( p->pInode==pFd->pInode ); sqlite3_mutex_free(p->pShmMutex); +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + for(i=0; iaMutex[i]); + } +#endif for(i=0; inRegion; i+=nShmPerMap){ if( p->hShm>=0 ){ osMunmap(p->apRegion[i], p->szRegion); @@ -36907,7 +45040,7 @@ static void unixShmPurge(unixFile *pFd){ ** take it now. Return SQLITE_OK if successful, or an SQLite error ** code otherwise. ** -** If the DMS cannot be locked because this is a readonly_shm=1 +** If the DMS cannot be locked because this is a readonly_shm=1 ** connection and no other process already holds a lock, return ** SQLITE_READONLY_CANTINIT and set pShmNode->isUnlocked=1. */ @@ -36918,7 +45051,7 @@ static int unixLockSharedMemory(unixFile *pDbFd, unixShmNode *pShmNode){ /* Use F_GETLK to determine the locks other processes are holding ** on the DMS byte. If it indicates that another process is holding ** a SHARED lock, then this process may also take a SHARED lock - ** and proceed with opening the *-shm file. + ** and proceed with opening the *-shm file. ** ** Or, if no other process is holding any lock, then this process ** is the first to open it. In this case take an EXCLUSIVE lock on the @@ -36944,7 +45077,20 @@ static int unixLockSharedMemory(unixFile *pDbFd, unixShmNode *pShmNode){ pShmNode->isUnlocked = 1; rc = SQLITE_READONLY_CANTINIT; }else{ +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + /* Do not use a blocking lock here. If the lock cannot be obtained + ** immediately, it means some other connection is truncating the + ** *-shm file. And after it has done so, it will not release its + ** lock, but only downgrade it to a shared lock. So no point in + ** blocking here. The call below to obtain the shared DMS lock may + ** use a blocking lock. */ + int iSaveTimeout = pDbFd->iBusyTimeout; + pDbFd->iBusyTimeout = 0; +#endif rc = unixShmSystemLock(pDbFd, F_WRLCK, UNIX_SHM_DMS, 1); +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + pDbFd->iBusyTimeout = iSaveTimeout; +#endif /* The first connection to attach must truncate the -shm file. We ** truncate to 3 bytes (an arbitrary small number, less than the ** -shm header size) rather than 0 as a system debugging aid, to @@ -36966,20 +45112,20 @@ static int unixLockSharedMemory(unixFile *pDbFd, unixShmNode *pShmNode){ } /* -** Open a shared-memory area associated with open database file pDbFd. +** Open a shared-memory area associated with open database file pDbFd. ** This particular implementation uses mmapped files. ** ** The file used to implement shared-memory is in the same directory ** as the open database file and has the same name as the open database ** file with the "-shm" suffix added. For example, if the database file ** is "/home/user1/config.db" then the file that is created and mmapped -** for shared memory will be called "/home/user1/config.db-shm". +** for shared memory will be called "/home/user1/config.db-shm". ** ** Another approach to is to use files in /dev/shm or /dev/tmp or an ** some other tmpfs mount. But if a file in a different directory ** from the database file is used, then differing access permissions ** or a chroot() might cause two different processes on the same -** database to end up using different files for shared memory - +** database to end up using different files for shared memory - ** meaning that their memory would not really be shared - resulting ** in database corruption. Nevertheless, this tmpfs file usage ** can be enabled at compile-time using -DSQLITE_SHM_DIRECTORY="/dev/shm" @@ -37049,7 +45195,7 @@ static int unixOpenSharedMemory(unixFile *pDbFd){ memset(pShmNode, 0, sizeof(*pShmNode)+nShmFilename); zShm = pShmNode->zFilename = (char*)&pShmNode[1]; #ifdef SQLITE_SHM_DIRECTORY - sqlite3_snprintf(nShmFilename, zShm, + sqlite3_snprintf(nShmFilename, zShm, SQLITE_SHM_DIRECTORY "/sqlite-shm-%x-%x", (u32)sStat.st_ino, (u32)sStat.st_dev); #else @@ -37065,14 +45211,28 @@ static int unixOpenSharedMemory(unixFile *pDbFd){ rc = SQLITE_NOMEM_BKPT; goto shm_open_err; } +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + { + int ii; + for(ii=0; iiaMutex[ii] = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); + if( pShmNode->aMutex[ii]==0 ){ + rc = SQLITE_NOMEM_BKPT; + goto shm_open_err; + } + } + } +#endif } if( pInode->bProcessLock==0 ){ if( 0==sqlite3_uri_boolean(pDbFd->zPath, "readonly_shm", 0) ){ - pShmNode->hShm = robust_open(zShm, O_RDWR|O_CREAT,(sStat.st_mode&0777)); + pShmNode->hShm = robust_open(zShm, O_RDWR|O_CREAT|O_NOFOLLOW, + (sStat.st_mode&0777)); } if( pShmNode->hShm<0 ){ - pShmNode->hShm = robust_open(zShm, O_RDONLY, (sStat.st_mode&0777)); + pShmNode->hShm = robust_open(zShm, O_RDONLY|O_NOFOLLOW, + (sStat.st_mode&0777)); if( pShmNode->hShm<0 ){ rc = unixLogError(SQLITE_CANTOPEN_BKPT, "open", zShm); goto shm_open_err; @@ -37122,22 +45282,22 @@ static int unixOpenSharedMemory(unixFile *pDbFd){ } /* -** This function is called to obtain a pointer to region iRegion of the -** shared-memory associated with the database file fd. Shared-memory regions -** are numbered starting from zero. Each shared-memory region is szRegion +** This function is called to obtain a pointer to region iRegion of the +** shared-memory associated with the database file fd. Shared-memory regions +** are numbered starting from zero. Each shared-memory region is szRegion ** bytes in size. ** ** If an error occurs, an error code is returned and *pp is set to NULL. ** ** Otherwise, if the bExtend parameter is 0 and the requested shared-memory ** region has not been allocated (by any client, including one running in a -** separate process), then *pp is set to NULL and SQLITE_OK returned. If -** bExtend is non-zero and the requested shared-memory region has not yet +** separate process), then *pp is set to NULL and SQLITE_OK returned. If +** bExtend is non-zero and the requested shared-memory region has not yet ** been allocated, it is allocated by this function. ** ** If the shared-memory region has already been allocated or is allocated by -** this call as described above, then it is mapped into this processes -** address space (if it is not already), *pp is set to point to the mapped +** this call as described above, then it is mapped into this processes +** address space (if it is not already), *pp is set to point to the mapped ** memory and SQLITE_OK returned. */ static int unixShmMap( @@ -37177,9 +45337,9 @@ static int unixShmMap( nReqRegion = ((iRegion+nShmPerMap) / nShmPerMap) * nShmPerMap; if( pShmNode->nRegionszRegion = szRegion; @@ -37192,7 +45352,7 @@ static int unixShmMap( rc = SQLITE_IOERR_SHMSIZE; goto shmpage_out; } - + if( sStat.st_sizeapRegion, nReqRegion*sizeof(char *) ); if( !apNew ){ @@ -37236,12 +45396,12 @@ static int unixShmMap( } pShmNode->apRegion = apNew; while( pShmNode->nRegionhShm>=0 ){ pMem = osMmap(0, nMap, - pShmNode->isReadonly ? PROT_READ : PROT_READ|PROT_WRITE, + pShmNode->isReadonly ? PROT_READ : PROT_READ|PROT_WRITE, MAP_SHARED, pShmNode->hShm, szRegion*(i64)pShmNode->nRegion ); if( pMem==MAP_FAILED ){ @@ -37275,10 +45435,45 @@ static int unixShmMap( return rc; } +/* +** Check that the pShmNode->aLock[] array comports with the locking bitmasks +** held by each client. Return true if it does, or false otherwise. This +** is to be used in an assert(). e.g. +** +** assert( assertLockingArrayOk(pShmNode) ); +*/ +#ifdef SQLITE_DEBUG +static int assertLockingArrayOk(unixShmNode *pShmNode){ +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + return 1; +#else + unixShm *pX; + int aLock[SQLITE_SHM_NLOCK]; + + memset(aLock, 0, sizeof(aLock)); + for(pX=pShmNode->pFirst; pX; pX=pX->pNext){ + int i; + for(i=0; iexclMask & (1<sharedMask & (1<=0 ); + aLock[i]++; + } + } + } + + assert( 0==memcmp(pShmNode->aLock, aLock, sizeof(aLock)) ); + return (memcmp(pShmNode->aLock, aLock, sizeof(aLock))==0); +#endif +} +#endif /* !defined(SQLITE_WASI) && !defined(SQLITE_OMIT_WAL) */ + /* ** Change the lock state for a shared-memory segment. ** -** Note that the relationship between SHAREd and EXCLUSIVE locks is a little +** Note that the relationship between SHARED and EXCLUSIVE locks is a little ** different here than in posix. In xShmLock(), one can go from unlocked ** to shared and back or from unlocked to exclusive and back. But one may ** not go from shared to exclusive or from exclusive to shared. @@ -37290,11 +45485,17 @@ static int unixShmLock( int flags /* What to do with the lock */ ){ unixFile *pDbFd = (unixFile*)fd; /* Connection holding shared memory */ - unixShm *p = pDbFd->pShm; /* The shared memory being locked */ - unixShm *pX; /* For looping over all siblings */ - unixShmNode *pShmNode = p->pShmNode; /* The underlying file iNode */ + unixShm *p; /* The shared memory being locked */ + unixShmNode *pShmNode; /* The underlying file iNode */ int rc = SQLITE_OK; /* Result code */ - u16 mask; /* Mask of locks to take or release */ + u16 mask = (1<<(ofst+n)) - (1<pShm; + if( p==0 ) return SQLITE_IOERR_SHMLOCK; + pShmNode = p->pShmNode; + if( NEVER(pShmNode==0) ) return SQLITE_IOERR_SHMLOCK; + aLock = pShmNode->aLock; assert( pShmNode==pDbFd->pInode->pShmNode ); assert( pShmNode->pInode==pDbFd->pInode ); @@ -37308,89 +45509,171 @@ static int unixShmLock( assert( pShmNode->hShm>=0 || pDbFd->pInode->bProcessLock==1 ); assert( pShmNode->hShm<0 || pDbFd->pInode->bProcessLock==0 ); - mask = (1<<(ofst+n)) - (1<1 || mask==(1<pShmMutex); - if( flags & SQLITE_SHM_UNLOCK ){ - u16 allMask = 0; /* Mask of locks held by siblings */ - - /* See if any siblings hold this same lock */ - for(pX=pShmNode->pFirst; pX; pX=pX->pNext){ - if( pX==p ) continue; - assert( (pX->exclMask & (p->exclMask|p->sharedMask))==0 ); - allMask |= pX->sharedMask; - } + /* Check that, if this to be a blocking lock, no locks that occur later + ** in the following list than the lock being obtained are already held: + ** + ** 1. Recovery lock (ofst==2). + ** 2. Checkpointer lock (ofst==1). + ** 3. Write lock (ofst==0). + ** 4. Read locks (ofst>=3 && ofstexclMask|p->sharedMask); + assert( (flags & SQLITE_SHM_UNLOCK) || pDbFd->iBusyTimeout==0 || ( + (ofst!=2 || lockMask==0) + && (ofst!=1 || lockMask==0 || lockMask==2) + && (ofst!=0 || lockMask<3) + && (ofst<3 || lockMask<(1<exclMask & mask) + ); + if( ((flags & SQLITE_SHM_UNLOCK) && ((p->exclMask|p->sharedMask) & mask)) + || (flags==(SQLITE_SHM_SHARED|SQLITE_SHM_LOCK) && 0==(p->sharedMask & mask)) + || (flags==(SQLITE_SHM_EXCLUSIVE|SQLITE_SHM_LOCK)) + ){ - /* Undo the local locks */ - if( rc==SQLITE_OK ){ - p->exclMask &= ~mask; - p->sharedMask &= ~mask; - } - }else if( flags & SQLITE_SHM_SHARED ){ - u16 allShared = 0; /* Union of locks held by connections other than "p" */ - - /* Find out which shared locks are already held by sibling connections. - ** If any sibling already holds an exclusive lock, go ahead and return - ** SQLITE_BUSY. - */ - for(pX=pShmNode->pFirst; pX; pX=pX->pNext){ - if( (pX->exclMask & mask)!=0 ){ - rc = SQLITE_BUSY; - break; + /* Take the required mutexes. In SETLK_TIMEOUT mode (blocking locks), if + ** this is an attempt on an exclusive lock use sqlite3_mutex_try(). If any + ** other thread is holding this mutex, then it is either holding or about + ** to hold a lock exclusive to the one being requested, and we may + ** therefore return SQLITE_BUSY to the caller. + ** + ** Doing this prevents some deadlock scenarios. For example, thread 1 may + ** be a checkpointer blocked waiting on the WRITER lock. And thread 2 + ** may be a normal SQL client upgrading to a write transaction. In this + ** case thread 2 does a non-blocking request for the WRITER lock. But - + ** if it were to use sqlite3_mutex_enter() then it would effectively + ** become a (doomed) blocking request, as thread 2 would block until thread + ** 1 obtained WRITER and released the mutex. Since thread 2 already holds + ** a lock on a read-locking slot at this point, this breaks the + ** anti-deadlock rules (see above). */ +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + int iMutex; + for(iMutex=ofst; iMutexaMutex[iMutex]); + if( rc!=SQLITE_OK ) goto leave_shmnode_mutexes; + }else{ + sqlite3_mutex_enter(pShmNode->aMutex[iMutex]); } - allShared |= pX->sharedMask; } +#else + sqlite3_mutex_enter(pShmNode->pShmMutex); +#endif + + if( ALWAYS(rc==SQLITE_OK) ){ + if( flags & SQLITE_SHM_UNLOCK ){ + /* Case (a) - unlock. */ + int bUnlock = 1; + assert( (p->exclMask & p->sharedMask)==0 ); + assert( !(flags & SQLITE_SHM_EXCLUSIVE) || (p->exclMask & mask)==mask ); + assert( !(flags & SQLITE_SHM_SHARED) || (p->sharedMask & mask)==mask ); + + /* If this is a SHARED lock being unlocked, it is possible that other + ** clients within this process are holding the same SHARED lock. In + ** this case, set bUnlock to 0 so that the posix lock is not removed + ** from the file-descriptor below. */ + if( flags & SQLITE_SHM_SHARED ){ + assert( n==1 ); + assert( aLock[ofst]>=1 ); + if( aLock[ofst]>1 ){ + bUnlock = 0; + aLock[ofst]--; + p->sharedMask &= ~mask; + } + } - /* Get shared locks at the system level, if necessary */ - if( rc==SQLITE_OK ){ - if( (allShared & mask)==0 ){ - rc = unixShmSystemLock(pDbFd, F_RDLCK, ofst+UNIX_SHM_BASE, n); + if( bUnlock ){ + rc = unixShmSystemLock(pDbFd, F_UNLCK, ofst+UNIX_SHM_BASE, n); + if( rc==SQLITE_OK ){ + memset(&aLock[ofst], 0, sizeof(int)*n); + p->sharedMask &= ~mask; + p->exclMask &= ~mask; + } + } + }else if( flags & SQLITE_SHM_SHARED ){ + /* Case (b) - a shared lock. */ + + if( aLock[ofst]<0 ){ + /* An exclusive lock is held by some other connection. BUSY. */ + rc = SQLITE_BUSY; + }else if( aLock[ofst]==0 ){ + rc = unixShmSystemLock(pDbFd, F_RDLCK, ofst+UNIX_SHM_BASE, n); + } + + /* Get the local shared locks */ + if( rc==SQLITE_OK ){ + p->sharedMask |= mask; + aLock[ofst]++; + } }else{ - rc = SQLITE_OK; - } - } + /* Case (c) - an exclusive lock. */ + int ii; - /* Get the local shared locks */ - if( rc==SQLITE_OK ){ - p->sharedMask |= mask; - } - }else{ - /* Make sure no sibling connections hold locks that will block this - ** lock. If any do, return SQLITE_BUSY right away. - */ - for(pX=pShmNode->pFirst; pX; pX=pX->pNext){ - if( (pX->exclMask & mask)!=0 || (pX->sharedMask & mask)!=0 ){ - rc = SQLITE_BUSY; - break; - } - } - - /* Get the exclusive locks at the system level. Then if successful - ** also mark the local connection as being locked. - */ - if( rc==SQLITE_OK ){ - rc = unixShmSystemLock(pDbFd, F_WRLCK, ofst+UNIX_SHM_BASE, n); - if( rc==SQLITE_OK ){ + assert( flags==(SQLITE_SHM_LOCK|SQLITE_SHM_EXCLUSIVE) ); assert( (p->sharedMask & mask)==0 ); - p->exclMask |= mask; + assert( (p->exclMask & mask)==0 ); + + /* Make sure no sibling connections hold locks that will block this + ** lock. If any do, return SQLITE_BUSY right away. */ + for(ii=ofst; iiexclMask |= mask; + for(ii=ofst; ii=ofst; iMutex--){ + sqlite3_mutex_leave(pShmNode->aMutex[iMutex]); + } +#else + sqlite3_mutex_leave(pShmNode->pShmMutex); +#endif } - sqlite3_mutex_leave(pShmNode->pShmMutex); + OSTRACE(("SHM-LOCK shmid-%d, pid-%d got %03x,%03x\n", p->id, osGetpid(0), p->sharedMask, p->exclMask)); return rc; } /* -** Implement a memory barrier or memory fence on shared memory. +** Implement a memory barrier or memory fence on shared memory. ** ** All loads and stores begun before the barrier must complete before ** any load or store begun after the barrier. @@ -37400,15 +45683,15 @@ static void unixShmBarrier( ){ UNUSED_PARAMETER(fd); sqlite3MemoryBarrier(); /* compiler-defined memory barrier */ - assert( fd->pMethods->xLock==nolockLock - || unixFileMutexNotheld((unixFile*)fd) + assert( fd->pMethods->xLock==nolockLock + || unixFileMutexNotheld((unixFile*)fd) ); unixEnterMutex(); /* Also mutex, for redundancy */ unixLeaveMutex(); } /* -** Close a connection to shared-memory. Delete the underlying +** Close a connection to shared-memory. Delete the underlying ** storage if deleteFlag is true. ** ** If there is no shared memory associated with the connection then this @@ -37482,7 +45765,7 @@ static void unixUnmapfile(unixFile *pFd){ } /* -** Attempt to set the size of the memory mapping maintained by file +** Attempt to set the size of the memory mapping maintained by file ** descriptor pFd to nNew bytes. Any existing mapping is discarded. ** ** If successful, this function sets the following variables: @@ -37574,14 +45857,14 @@ static void unixRemapfile( /* ** Memory map or remap the file opened by file-descriptor pFd (if the file -** is already mapped, the existing mapping is replaced by the new). Or, if -** there already exists a mapping for this file, and there are still +** is already mapped, the existing mapping is replaced by the new). Or, if +** there already exists a mapping for this file, and there are still ** outstanding xFetch() references to it, this function is a no-op. ** -** If parameter nByte is non-negative, then it is the requested size of -** the mapping to create. Otherwise, if nByte is less than zero, then the +** If parameter nByte is non-negative, then it is the requested size of +** the mapping to create. Otherwise, if nByte is less than zero, then the ** requested size is the size of the file on disk. The actual size of the -** created mapping is either the requested size or the value configured +** created mapping is either the requested size or the value configured ** using SQLITE_FCNTL_MMAP_LIMIT, whichever is smaller. ** ** SQLITE_OK is returned if no error occurs (even if the mapping is not @@ -37622,7 +45905,7 @@ static int unixMapfile(unixFile *pFd, i64 nMap){ ** Finally, if an error does occur, return an SQLite error code. The final ** value of *pp is undefined in this case. ** -** If this function does return a pointer, the caller must eventually +** If this function does return a pointer, the caller must eventually ** release the reference by calling unixUnfetch(). */ static int unixFetch(sqlite3_file *fd, i64 iOff, int nAmt, void **pp){ @@ -37633,11 +45916,16 @@ static int unixFetch(sqlite3_file *fd, i64 iOff, int nAmt, void **pp){ #if SQLITE_MAX_MMAP_SIZE>0 if( pFd->mmapSizeMax>0 ){ + /* Ensure that there is always at least a 256 byte buffer of addressable + ** memory following the returned page. If the database is corrupt, + ** SQLite may overread the page slightly (in practice only a few bytes, + ** but 256 is safe, round, number). */ + const int nEofBuffer = 256; if( pFd->pMapRegion==0 ){ int rc = unixMapfile(pFd, -1); if( rc!=SQLITE_OK ) return rc; } - if( pFd->mmapSize >= iOff+nAmt ){ + if( pFd->mmapSize >= (iOff+nAmt+nEofBuffer) ){ *pp = &((u8 *)pFd->pMapRegion)[iOff]; pFd->nFetchOut++; } @@ -37647,13 +45935,13 @@ static int unixFetch(sqlite3_file *fd, i64 iOff, int nAmt, void **pp){ } /* -** If the third argument is non-NULL, then this function releases a +** If the third argument is non-NULL, then this function releases a ** reference obtained by an earlier call to unixFetch(). The second ** argument passed to this function must be the same as the corresponding -** argument that was passed to the unixFetch() invocation. +** argument that was passed to the unixFetch() invocation. ** -** Or, if the third argument is NULL, then this function is being called -** to inform the VFS layer that, according to POSIX, any existing mapping +** Or, if the third argument is NULL, then this function is being called +** to inform the VFS layer that, according to POSIX, any existing mapping ** may now be invalid and should be unmapped. */ static int unixUnfetch(sqlite3_file *fd, i64 iOff, void *p){ @@ -37661,7 +45949,7 @@ static int unixUnfetch(sqlite3_file *fd, i64 iOff, void *p){ unixFile *pFd = (unixFile *)fd; /* The underlying database file */ UNUSED_PARAMETER(iOff); - /* If p==0 (unmap the entire file) then there must be no outstanding + /* If p==0 (unmap the entire file) then there must be no outstanding ** xFetch references. Or, if p!=0 (meaning it is an xFetch reference), ** then there must be at least one outstanding. */ assert( (p==0)==(pFd->nFetchOut==0) ); @@ -37869,8 +46157,8 @@ IOMETHODS( #endif #if defined(__APPLE__) && SQLITE_ENABLE_LOCKING_STYLE -/* -** This "finder" function attempts to determine the best locking strategy +/* +** This "finder" function attempts to determine the best locking strategy ** for the database file "filePath". It then returns the sqlite3_io_methods ** object that implements that strategy. ** @@ -37912,8 +46200,8 @@ static const sqlite3_io_methods *autolockIoFinderImpl( } /* Default case. Handles, amongst others, "nfs". - ** Test byte-range lock using fcntl(). If the call succeeds, - ** assume that the file-system supports POSIX style locks. + ** Test byte-range lock using fcntl(). If the call succeeds, + ** assume that the file-system supports POSIX style locks. */ lockInfo.l_len = 1; lockInfo.l_start = 0; @@ -37929,7 +46217,7 @@ static const sqlite3_io_methods *autolockIoFinderImpl( return &dotlockIoMethods; } } -static const sqlite3_io_methods +static const sqlite3_io_methods *(*const autolockIoFinder)(const char*,unixFile*) = autolockIoFinderImpl; #endif /* defined(__APPLE__) && SQLITE_ENABLE_LOCKING_STYLE */ @@ -37965,7 +46253,7 @@ static const sqlite3_io_methods *vxworksIoFinderImpl( return &semIoMethods; } } -static const sqlite3_io_methods +static const sqlite3_io_methods *(*const vxworksIoFinder)(const char*,unixFile*) = vxworksIoFinderImpl; #endif /* OS_VXWORKS */ @@ -38093,14 +46381,14 @@ static int fillInUnixFile( robust_close(pNew, h, __LINE__); h = -1; } - unixLeaveMutex(); + unixLeaveMutex(); } } #endif else if( pLockingStyle == &dotlockIoMethods ){ /* Dotfile locking uses the file path so it needs to be included in - ** the dotlockLockingContext + ** the dotlockLockingContext */ char *zLockFile; int nFilename; @@ -38138,55 +46426,75 @@ static int fillInUnixFile( unixLeaveMutex(); } #endif - + storeLastErrno(pNew, 0); #if OS_VXWORKS if( rc!=SQLITE_OK ){ - if( h>=0 ) robust_close(pNew, h, __LINE__); - h = -1; - osUnlink(zFilename); - pNew->ctrlFlags |= UNIXFILE_DELETE; + if( h>=0 ){ + robust_close(pNew, h, __LINE__); + h = -1; + } + if( pNew->ctrlFlags & UNIXFILE_DELETE ){ + osUnlink(zFilename); + } + if( pNew->pId ){ + vxworksReleaseFileId(pNew->pId); + pNew->pId = 0; + } } #endif if( rc!=SQLITE_OK ){ if( h>=0 ) robust_close(pNew, h, __LINE__); }else{ - pNew->pMethod = pLockingStyle; + pId->pMethods = pLockingStyle; OpenCounter(+1); verifyDbFile(pNew); } return rc; } +/* +** Directories to consider for temp files. +*/ +static const char *azTempDirs[] = { + 0, + 0, + "/var/tmp", + "/usr/tmp", + "/tmp", + "." +}; + +/* +** Initialize first two members of azTempDirs[] array. +*/ +static void unixTempFileInit(void){ + azTempDirs[0] = getenv("SQLITE_TMPDIR"); + azTempDirs[1] = getenv("TMPDIR"); +} + /* ** Return the name of a directory in which to put temporary files. ** If no suitable temporary file directory can be found, return NULL. */ static const char *unixTempFileDir(void){ - static const char *azDirs[] = { - 0, - 0, - "/var/tmp", - "/usr/tmp", - "/tmp", - "." - }; unsigned int i = 0; struct stat buf; const char *zDir = sqlite3_temp_directory; - if( !azDirs[0] ) azDirs[0] = getenv("SQLITE_TMPDIR"); - if( !azDirs[1] ) azDirs[1] = getenv("TMPDIR"); while(1){ if( zDir!=0 +#if OS_VXWORKS + && zDir[0]=='/' +#endif && osStat(zDir, &buf)==0 && S_ISDIR(buf.st_mode) && osAccess(zDir, 03)==0 ){ return zDir; } - if( i>=sizeof(azDirs)/sizeof(azDirs[0]) ) break; - zDir = azDirs[i++]; + if( i>=sizeof(azTempDirs)/sizeof(azTempDirs[0]) ) break; + zDir = azTempDirs[i++]; } return 0; } @@ -38199,26 +46507,35 @@ static const char *unixTempFileDir(void){ static int unixGetTempname(int nBuf, char *zBuf){ const char *zDir; int iLimit = 0; + int rc = SQLITE_OK; /* It's odd to simulate an io-error here, but really this is just ** using the io-error infrastructure to test that SQLite handles this - ** function failing. + ** function failing. */ zBuf[0] = 0; SimulateIOError( return SQLITE_IOERR ); + sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); zDir = unixTempFileDir(); - if( zDir==0 ) return SQLITE_IOERR_GETTEMPPATH; - do{ - u64 r; - sqlite3_randomness(sizeof(r), &r); - assert( nBuf>2 ); - zBuf[nBuf-2] = 0; - sqlite3_snprintf(nBuf, zBuf, "%s/"SQLITE_TEMP_FILE_PREFIX"%llx%c", - zDir, r, 0); - if( zBuf[nBuf-2]!=0 || (iLimit++)>10 ) return SQLITE_ERROR; - }while( osAccess(zBuf,0)==0 ); - return SQLITE_OK; + if( zDir==0 ){ + rc = SQLITE_IOERR_GETTEMPPATH; + }else{ + do{ + u64 r; + sqlite3_randomness(sizeof(r), &r); + assert( nBuf>2 ); + zBuf[nBuf-2] = 0; + sqlite3_snprintf(nBuf, zBuf, "%s/"SQLITE_TEMP_FILE_PREFIX"%llx%c", + zDir, r, 0); + if( zBuf[nBuf-2]!=0 || (iLimit++)>10 ){ + rc = SQLITE_ERROR; + break; + } + }while( osAccess(zBuf,0)==0 ); + } + sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); + return rc; } #if SQLITE_ENABLE_LOCKING_STYLE && defined(__APPLE__) @@ -38231,8 +46548,8 @@ static int proxyTransformUnixFile(unixFile*, const char*); #endif /* -** Search for an unused file descriptor that was opened on the database -** file (not a journal or master-journal file) identified by pathname +** Search for an unused file descriptor that was opened on the database +** file (not a journal or super-journal file) identified by pathname ** zPath with SQLITE_OPEN_XXX flags matching those passed as the second ** argument to this function. ** @@ -38240,7 +46557,7 @@ static int proxyTransformUnixFile(unixFile*, const char*); ** but the associated file descriptor could not be closed because some ** other file descriptor open on the same file is holding a file-lock. ** Refer to comments in the unixClose() function and the lengthy comment -** describing "Posix Advisory Locking" at the start of this file for +** describing "Posix Advisory Locking" at the start of this file for ** further details. Also, ticket #4018. ** ** If a suitable file descriptor is found, then it is returned. If no @@ -38251,8 +46568,8 @@ static UnixUnusedFd *findReusableFd(const char *zPath, int flags){ /* Do not search for an unused file descriptor on vxworks. Not because ** vxworks would not benefit from the change (it might, we're not sure), - ** but because no way to test it is currently available. It is better - ** not to risk breaking vxworks support for the sake of such an obscure + ** but because no way to test it is currently available. It is better + ** not to risk breaking vxworks support for the sake of such an obscure ** feature. */ #if !OS_VXWORKS struct stat sStat; /* Results of stat() call */ @@ -38279,6 +46596,7 @@ static UnixUnusedFd *findReusableFd(const char *zPath, int flags){ UnixUnusedFd **pp; assert( sqlite3_mutex_notheld(pInode->pLockMutex) ); sqlite3_mutex_enter(pInode->pLockMutex); + flags &= (SQLITE_OPEN_READONLY|SQLITE_OPEN_READWRITE); for(pp=&pInode->pUnused; *pp && (*pp)->flags!=flags; pp=&((*pp)->pNext)); pUnused = *pp; if( pUnused ){ @@ -38293,7 +46611,7 @@ static UnixUnusedFd *findReusableFd(const char *zPath, int flags){ } /* -** Find the mode, uid and gid of file zFile. +** Find the mode, uid and gid of file zFile. */ static int getFileMode( const char *zFile, /* File name */ @@ -38317,22 +46635,22 @@ static int getFileMode( ** This function is called by unixOpen() to determine the unix permissions ** to create new files with. If no error occurs, then SQLITE_OK is returned ** and a value suitable for passing as the third argument to open(2) is -** written to *pMode. If an IO error occurs, an SQLite error code is +** written to *pMode. If an IO error occurs, an SQLite error code is ** returned and the value of *pMode is not modified. ** ** In most cases, this routine sets *pMode to 0, which will become ** an indication to robust_open() to create the file using ** SQLITE_DEFAULT_FILE_PERMISSIONS adjusted by the umask. -** But if the file being opened is a WAL or regular journal file, then -** this function queries the file-system for the permissions on the -** corresponding database file and sets *pMode to this value. Whenever -** possible, WAL and journal files are created using the same permissions +** But if the file being opened is a WAL or regular journal file, then +** this function queries the file-system for the permissions on the +** corresponding database file and sets *pMode to this value. Whenever +** possible, WAL and journal files are created using the same permissions ** as the associated database file. ** ** If the SQLITE_ENABLE_8_3_NAMES option is enabled, then the ** original filename is unavailable. But 8_3_NAMES is only used for ** FAT filesystems and permissions do not matter there, so just use -** the default permissions. +** the default permissions. In 8_3_NAMES mode, leave *pMode set to zero. */ static int findCreateFileMode( const char *zPath, /* Path of file (possibly) being created */ @@ -38358,22 +46676,25 @@ static int findCreateFileMode( ** "-journalNN" ** "-walNN" ** - ** where NN is a decimal number. The NN naming schemes are + ** where NN is a decimal number. The NN naming schemes are ** used by the test_multiplex.c module. + ** + ** In normal operation, the journal file name will always contain + ** a '-' character. However in 8+3 filename mode, or if a corrupt + ** rollback journal specifies a super-journal with a goofy name, then + ** the '-' might be missing or the '-' might be the first character in + ** the filename. In that case, just return SQLITE_OK with *pMode==0. */ - nDb = sqlite3Strlen30(zPath) - 1; - while( zPath[nDb]!='-' ){ - /* In normal operation, the journal file name will always contain - ** a '-' character. However in 8+3 filename mode, or if a corrupt - ** rollback journal specifies a master journal with a goofy name, then - ** the '-' might be missing. */ - if( nDb==0 || zPath[nDb]=='.' ) return SQLITE_OK; + nDb = sqlite3Strlen30(zPath) - 1; + while( nDb>0 && zPath[nDb]!='.' ){ + if( zPath[nDb]=='-' ){ + memcpy(zDb, zPath, nDb); + zDb[nDb] = '\0'; + rc = getFileMode(zDb, pMode, pUid, pGid); + break; + } nDb--; } - memcpy(zDb, zPath, nDb); - zDb[nDb] = '\0'; - - rc = getFileMode(zDb, pMode, pUid, pGid); }else if( flags & SQLITE_OPEN_DELETEONCLOSE ){ *pMode = 0600; }else if( flags & SQLITE_OPEN_URI ){ @@ -38391,7 +46712,7 @@ static int findCreateFileMode( /* ** Open the file zPath. -** +** ** Previously, the SQLite OS layer used three functions in place of this ** one: ** @@ -38402,13 +46723,13 @@ static int findCreateFileMode( ** These calls correspond to the following combinations of flags: ** ** ReadWrite() -> (READWRITE | CREATE) -** ReadOnly() -> (READONLY) +** ReadOnly() -> (READONLY) ** OpenExclusive() -> (READWRITE | CREATE | EXCLUSIVE) ** ** The old OpenExclusive() accepted a boolean argument - "delFlag". If ** true, the file was configured to be automatically deleted when the -** file handle closed. To achieve the same effect using this new -** interface, add the DELETEONCLOSE flag to those specified above for +** file handle closed. To achieve the same effect using this new +** interface, add the DELETEONCLOSE flag to those specified above for ** OpenExclusive(). */ static int unixOpen( @@ -38421,7 +46742,7 @@ static int unixOpen( unixFile *p = (unixFile *)pFile; int fd = -1; /* File descriptor returned by open() */ int openFlags = 0; /* Flags to pass to open() */ - int eType = flags&0xFFFFFF00; /* Type of file to open */ + int eType = flags&0x0FFF00; /* Type of file to open */ int noLock; /* True to omit locking primitives */ int rc = SQLITE_OK; /* Function Return Code */ int ctrlFlags = 0; /* UNIXFILE_* flags */ @@ -38438,13 +46759,13 @@ static int unixOpen( struct statfs fsInfo; #endif - /* If creating a master or main-file journal, this function will open + /* If creating a super- or main-file journal, this function will open ** a file-descriptor on the directory too. The first time unixSync() ** is called the directory file descriptor will be fsync()ed and close()d. */ int isNewJrnl = (isCreate && ( - eType==SQLITE_OPEN_MASTER_JOURNAL - || eType==SQLITE_OPEN_MAIN_JOURNAL + eType==SQLITE_OPEN_SUPER_JOURNAL + || eType==SQLITE_OPEN_MAIN_JOURNAL || eType==SQLITE_OPEN_WAL )); @@ -38454,9 +46775,9 @@ static int unixOpen( char zTmpname[MAX_PATHNAME+2]; const char *zName = zPath; - /* Check the following statements are true: + /* Check the following statements are true: ** - ** (a) Exactly one of the READWRITE and READONLY flags must be set, and + ** (a) Exactly one of the READWRITE and READONLY flags must be set, and ** (b) if CREATE is set, then READWRITE must also be set, and ** (c) if EXCLUSIVE is set, then CREATE must also be set. ** (d) if DELETEONCLOSE is set, then CREATE must also be set. @@ -38466,20 +46787,26 @@ static int unixOpen( assert(isExclusive==0 || isCreate); assert(isDelete==0 || isCreate); - /* The main DB, main journal, WAL file and master journal are never + /* The main DB, main journal, WAL file and super-journal are never ** automatically deleted. Nor are they ever temporary files. */ assert( (!isDelete && zName) || eType!=SQLITE_OPEN_MAIN_DB ); assert( (!isDelete && zName) || eType!=SQLITE_OPEN_MAIN_JOURNAL ); - assert( (!isDelete && zName) || eType!=SQLITE_OPEN_MASTER_JOURNAL ); + assert( (!isDelete && zName) || eType!=SQLITE_OPEN_SUPER_JOURNAL ); assert( (!isDelete && zName) || eType!=SQLITE_OPEN_WAL ); /* Assert that the upper layer has set one of the "file-type" flags. */ - assert( eType==SQLITE_OPEN_MAIN_DB || eType==SQLITE_OPEN_TEMP_DB - || eType==SQLITE_OPEN_MAIN_JOURNAL || eType==SQLITE_OPEN_TEMP_JOURNAL - || eType==SQLITE_OPEN_SUBJOURNAL || eType==SQLITE_OPEN_MASTER_JOURNAL + assert( eType==SQLITE_OPEN_MAIN_DB || eType==SQLITE_OPEN_TEMP_DB + || eType==SQLITE_OPEN_MAIN_JOURNAL || eType==SQLITE_OPEN_TEMP_JOURNAL + || eType==SQLITE_OPEN_SUBJOURNAL || eType==SQLITE_OPEN_SUPER_JOURNAL || eType==SQLITE_OPEN_TRANSIENT_DB || eType==SQLITE_OPEN_WAL ); +#if OS_VXWORKS + /* The file-ID mechanism used in Vxworks requires that all pathnames + ** provided to unixOpen must be absolute pathnames. */ + if( zPath!=0 && zPath[0]!='/' ){ return SQLITE_CANTOPEN; } +#endif + /* Detect a pid change and reset the PRNG. There is a race condition ** here such that two or more threads all trying to open databases at ** the same instant might all reset the PRNG. But multiple resets @@ -38491,6 +46818,11 @@ static int unixOpen( } memset(p, 0, sizeof(unixFile)); +#ifdef SQLITE_ASSERT_NO_FILES + /* Applications that never read or write a persistent disk files */ + assert( zName==0 ); +#endif + if( eType==SQLITE_OPEN_MAIN_DB ){ UnixUnusedFd *pUnused; pUnused = findReusableFd(zName, flags); @@ -38525,13 +46857,13 @@ static int unixOpen( /* Determine the value of the flags parameter passed to POSIX function ** open(). These must be calculated even if open() is not called, as - ** they may be stored as part of the file handle and used by the + ** they may be stored as part of the file handle and used by the ** 'conch file' locking functions later on. */ if( isReadonly ) openFlags |= O_RDONLY; if( isReadWrite ) openFlags |= O_RDWR; if( isCreate ) openFlags |= O_CREAT; if( isExclusive ) openFlags |= (O_EXCL|O_NOFOLLOW); - openFlags |= (O_LARGEFILE|O_BINARY); + openFlags |= (O_LARGEFILE|O_BINARY|O_NOFOLLOW); if( fd<0 ){ mode_t openMode; /* Permissions to create file with */ @@ -38553,12 +46885,19 @@ static int unixOpen( rc = SQLITE_READONLY_DIRECTORY; }else if( errno!=EISDIR && isReadWrite ){ /* Failed to open the file for read/write access. Try read-only. */ + UnixUnusedFd *pReadonly = 0; flags &= ~(SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE); openFlags &= ~(O_RDWR|O_CREAT); flags |= SQLITE_OPEN_READONLY; openFlags |= O_RDONLY; isReadonly = 1; - fd = robust_open(zName, openFlags, openMode); + pReadonly = findReusableFd(zName, flags); + if( pReadonly ){ + fd = pReadonly->fd; + sqlite3_free(pReadonly); + }else{ + fd = robust_open(zName, openFlags, openMode); + } } } if( fd<0 ){ @@ -38567,11 +46906,19 @@ static int unixOpen( goto open_finished; } - /* If this process is running as root and if creating a new rollback - ** journal or WAL file, set the ownership of the journal or WAL to be - ** the same as the original database. + /* The owner of the rollback journal or WAL file should always be the + ** same as the owner of the database file. Try to ensure that this is + ** the case. The chown() system call will be a no-op if the current + ** process lacks root privileges, be we should at least try. Without + ** this step, if a root process opens a database file, it can leave + ** behinds a journal/WAL that is owned by root and hence make the + ** database inaccessible to unprivileged processes. + ** + ** If openMode==0, then that means uid and gid are not set correctly + ** (probably because SQLite is configured to use 8+3 filename mode) and + ** in that case we do not want to attempt the chown(). */ - if( flags & (SQLITE_OPEN_WAL|SQLITE_OPEN_MAIN_JOURNAL) ){ + if( openMode && (flags & (SQLITE_OPEN_WAL|SQLITE_OPEN_MAIN_JOURNAL))!=0 ){ robustFchown(fd, uid, gid); } } @@ -38582,7 +46929,8 @@ static int unixOpen( if( p->pPreallocatedUnused ){ p->pPreallocatedUnused->fd = fd; - p->pPreallocatedUnused->flags = flags; + p->pPreallocatedUnused->flags = + flags & (SQLITE_OPEN_READONLY|SQLITE_OPEN_READWRITE); } if( isDelete ){ @@ -38603,7 +46951,7 @@ static int unixOpen( p->openFlags = openFlags; } #endif - + #if defined(__APPLE__) || SQLITE_ENABLE_LOCKING_STYLE if( fstatfs(fd, &fsInfo) == -1 ){ storeLastErrno(p, errno); @@ -38634,7 +46982,7 @@ static int unixOpen( char *envforce = getenv("SQLITE_FORCE_PROXY_LOCKING"); int useProxy = 0; - /* SQLITE_FORCE_PROXY_LOCKING==1 means force always use proxy, 0 means + /* SQLITE_FORCE_PROXY_LOCKING==1 means force always use proxy, 0 means ** never use proxy, NULL means use proxy for non-local files only. */ if( envforce!=NULL ){ useProxy = atoi(envforce)>0; @@ -38646,9 +46994,9 @@ static int unixOpen( if( rc==SQLITE_OK ){ rc = proxyTransformUnixFile((unixFile*)pFile, ":auto:"); if( rc!=SQLITE_OK ){ - /* Use unixClose to clean up the resources added in fillInUnixFile - ** and clear all the structure's references. Specifically, - ** pFile->pMethods will be NULL so sqlite3OsClose will be a no-op + /* Use unixClose to clean up the resources added in fillInUnixFile + ** and clear all the structure's references. Specifically, + ** pFile->pMethods will be NULL so sqlite3OsClose will be a no-op */ unixClose(pFile); return rc; @@ -38658,9 +47006,12 @@ static int unixOpen( } } #endif - - assert( zPath==0 || zPath[0]=='/' - || eType==SQLITE_OPEN_MASTER_JOURNAL || eType==SQLITE_OPEN_MAIN_JOURNAL + + assert( zPath==0 + || zPath[0]=='/' + || eType==SQLITE_OPEN_SUPER_JOURNAL + || eType==SQLITE_OPEN_MAIN_JOURNAL + || eType==SQLITE_OPEN_TEMP_JOURNAL ); rc = fillInUnixFile(pVfs, fd, pFile, zPath, ctrlFlags); @@ -38740,7 +47091,8 @@ static int unixAccess( if( flags==SQLITE_ACCESS_EXISTS ){ struct stat buf; - *pResOut = (0==osStat(zPath, &buf) && buf.st_size>0); + *pResOut = 0==osStat(zPath, &buf) && + (!S_ISREG(buf.st_mode) || buf.st_size>0); }else{ *pResOut = osAccess(zPath, W_OK|R_OK)==0; } @@ -38748,38 +47100,105 @@ static int unixAccess( } /* -** +** A pathname under construction */ -static int mkFullPathname( - const char *zPath, /* Input path */ - char *zOut, /* Output buffer */ - int nOut /* Allocated size of buffer zOut */ +typedef struct DbPath DbPath; +struct DbPath { + int rc; /* Non-zero following any error */ + int nSymlink; /* Number of symlinks resolved */ + char *zOut; /* Write the pathname here */ + int nOut; /* Bytes of space available to zOut[] */ + int nUsed; /* Bytes of zOut[] currently being used */ +}; + +/* Forward reference */ +static void appendAllPathElements(DbPath*,const char*); + +/* +** Append a single path element to the DbPath under construction +*/ +static void appendOnePathElement( + DbPath *pPath, /* Path under construction, to which to append zName */ + const char *zName, /* Name to append to pPath. Not zero-terminated */ + int nName /* Number of significant bytes in zName */ ){ - int nPath = sqlite3Strlen30(zPath); - int iOff = 0; - if( zPath[0]!='/' ){ - if( osGetcwd(zOut, nOut-2)==0 ){ - return unixLogError(SQLITE_CANTOPEN_BKPT, "getcwd", zPath); + assert( nName>0 ); + assert( zName!=0 ); + if( zName[0]=='.' ){ + if( nName==1 ) return; + if( zName[1]=='.' && nName==2 ){ + if( pPath->nUsed>1 ){ + assert( pPath->zOut[0]=='/' ); + while( pPath->zOut[--pPath->nUsed]!='/' ){} + } + return; } - iOff = sqlite3Strlen30(zOut); - zOut[iOff++] = '/'; } - if( (iOff+nPath+1)>nOut ){ - /* SQLite assumes that xFullPathname() nul-terminates the output buffer - ** even if it returns an error. */ - zOut[iOff] = '\0'; - return SQLITE_CANTOPEN_BKPT; + if( pPath->nUsed + nName + 2 >= pPath->nOut ){ + pPath->rc = SQLITE_ERROR; + return; } - sqlite3_snprintf(nOut-iOff, &zOut[iOff], "%s", zPath); - return SQLITE_OK; + pPath->zOut[pPath->nUsed++] = '/'; + memcpy(&pPath->zOut[pPath->nUsed], zName, nName); + pPath->nUsed += nName; +#if defined(HAVE_READLINK) && defined(HAVE_LSTAT) + if( pPath->rc==SQLITE_OK ){ + const char *zIn; + struct stat buf; + pPath->zOut[pPath->nUsed] = 0; + zIn = pPath->zOut; + if( osLstat(zIn, &buf)!=0 ){ + if( errno!=ENOENT ){ + pPath->rc = unixLogError(SQLITE_CANTOPEN_BKPT, "lstat", zIn); + } + }else if( S_ISLNK(buf.st_mode) ){ + ssize_t got; + char zLnk[SQLITE_MAX_PATHLEN+2]; + if( pPath->nSymlink++ > SQLITE_MAX_SYMLINK ){ + pPath->rc = SQLITE_CANTOPEN_BKPT; + return; + } + got = osReadlink(zIn, zLnk, sizeof(zLnk)-2); + if( got<=0 || got>=(ssize_t)sizeof(zLnk)-2 ){ + pPath->rc = unixLogError(SQLITE_CANTOPEN_BKPT, "readlink", zIn); + return; + } + zLnk[got] = 0; + if( zLnk[0]=='/' ){ + pPath->nUsed = 0; + }else{ + pPath->nUsed -= nName + 1; + } + appendAllPathElements(pPath, zLnk); + } + } +#endif +} + +/* +** Append all path elements in zPath to the DbPath under construction. +*/ +static void appendAllPathElements( + DbPath *pPath, /* Path under construction, to which to append zName */ + const char *zPath /* Path to append to pPath. Is zero-terminated */ +){ + int i = 0; + int j = 0; + do{ + while( zPath[i] && zPath[i]!='/' ){ i++; } + if( i>j ){ + appendOnePathElement(pPath, &zPath[j], i-j); + } + j = i+1; + }while( zPath[i++] ); } /* ** Turn a relative pathname into a full pathname. The relative path ** is stored as a nul-terminated string in the buffer pointed to by -** zPath. +** zPath. ** -** zOut points to a buffer of at least sqlite3_vfs.mxPathname bytes +** zOut points to a buffer of at least sqlite3_vfs.mxPathname bytes ** (in this case, MAX_PATHNAME bytes). The full-path is written to ** this buffer before returning. */ @@ -38789,84 +47208,27 @@ static int unixFullPathname( int nOut, /* Size of output buffer in bytes */ char *zOut /* Output buffer */ ){ -#if !defined(HAVE_READLINK) || !defined(HAVE_LSTAT) - return mkFullPathname(zPath, zOut, nOut); -#else - int rc = SQLITE_OK; - int nByte; - int nLink = 1; /* Number of symbolic links followed so far */ - const char *zIn = zPath; /* Input path for each iteration of loop */ - char *zDel = 0; - - assert( pVfs->mxPathname==MAX_PATHNAME ); + DbPath path; UNUSED_PARAMETER(pVfs); - - /* It's odd to simulate an io-error here, but really this is just - ** using the io-error infrastructure to test that SQLite handles this - ** function failing. This function could fail if, for example, the - ** current working directory has been unlinked. - */ - SimulateIOError( return SQLITE_ERROR ); - - do { - - /* Call stat() on path zIn. Set bLink to true if the path is a symbolic - ** link, or false otherwise. */ - int bLink = 0; - struct stat buf; - if( osLstat(zIn, &buf)!=0 ){ - if( errno!=ENOENT ){ - rc = unixLogError(SQLITE_CANTOPEN_BKPT, "lstat", zIn); - } - }else{ - bLink = S_ISLNK(buf.st_mode); - } - - if( bLink ){ - if( zDel==0 ){ - zDel = sqlite3_malloc(nOut); - if( zDel==0 ) rc = SQLITE_NOMEM_BKPT; - }else if( ++nLink>SQLITE_MAX_SYMLINKS ){ - rc = SQLITE_CANTOPEN_BKPT; - } - - if( rc==SQLITE_OK ){ - nByte = osReadlink(zIn, zDel, nOut-1); - if( nByte<0 ){ - rc = unixLogError(SQLITE_CANTOPEN_BKPT, "readlink", zIn); - }else{ - if( zDel[0]!='/' ){ - int n; - for(n = sqlite3Strlen30(zIn); n>0 && zIn[n-1]!='/'; n--); - if( nByte+n+1>nOut ){ - rc = SQLITE_CANTOPEN_BKPT; - }else{ - memmove(&zDel[n], zDel, nByte+1); - memcpy(zDel, zIn, n); - nByte += n; - } - } - zDel[nByte] = '\0'; - } - } - - zIn = zDel; - } - - assert( rc!=SQLITE_OK || zIn!=zOut || zIn[0]=='/' ); - if( rc==SQLITE_OK && zIn!=zOut ){ - rc = mkFullPathname(zIn, zOut, nOut); + path.rc = 0; + path.nUsed = 0; + path.nSymlink = 0; + path.nOut = nOut; + path.zOut = zOut; + if( zPath[0]!='/' ){ + char zPwd[SQLITE_MAX_PATHLEN+2]; + if( osGetcwd(zPwd, sizeof(zPwd)-2)==0 ){ + return unixLogError(SQLITE_CANTOPEN_BKPT, "getcwd", zPath); } - if( bLink==0 ) break; - zIn = zOut; - }while( rc==SQLITE_OK ); - - sqlite3_free(zDel); - return rc; -#endif /* HAVE_READLINK && HAVE_LSTAT */ + appendAllPathElements(&path, zPwd); + } + appendAllPathElements(&path, zPath); + zOut[path.nUsed] = 0; + if( path.rc || path.nUsed<2 ) return SQLITE_CANTOPEN_BKPT; + if( path.nSymlink ) return SQLITE_OK_SYMLINK; + return SQLITE_OK; } - #ifndef SQLITE_OMIT_LOAD_EXTENSION /* ** Interfaces for opening a shared library, finding entry points @@ -38896,7 +47258,7 @@ static void unixDlError(sqlite3_vfs *NotUsed, int nBuf, char *zBufOut){ unixLeaveMutex(); } static void (*unixDlSym(sqlite3_vfs *NotUsed, void *p, const char*zSym))(void){ - /* + /* ** GCC with -pedantic-errors says that C90 does not allow a void* to be ** cast into a pointer to a function. And yet the library dlsym() routine ** returns a void* which is really a pointer to a function. So how do we @@ -38906,7 +47268,7 @@ static void (*unixDlSym(sqlite3_vfs *NotUsed, void *p, const char*zSym))(void){ ** parameters void* and const char* and returning a pointer to a function. ** We initialize x by assigning it a pointer to the dlsym() function. ** (That assignment requires a cast.) Then we call the function that - ** x points to. + ** x points to. ** ** This work-around is unlikely to work correctly on any system where ** you really cannot cast a function pointer into void*. But then, on the @@ -38949,7 +47311,7 @@ static int unixRandomness(sqlite3_vfs *NotUsed, int nBuf, char *zBuf){ ** tests repeatable. */ memset(zBuf, 0, nBuf); - randomnessPid = osGetpid(0); + randomnessPid = osGetpid(0); #if !defined(SQLITE_TEST) && !defined(SQLITE_OMIT_RANDOMNESS) { int fd, got; @@ -38980,16 +47342,22 @@ static int unixRandomness(sqlite3_vfs *NotUsed, int nBuf, char *zBuf){ ** than the argument. */ static int unixSleep(sqlite3_vfs *NotUsed, int microseconds){ -#if OS_VXWORKS +#if !defined(HAVE_NANOSLEEP) || HAVE_NANOSLEEP+0 struct timespec sp; - sp.tv_sec = microseconds / 1000000; sp.tv_nsec = (microseconds % 1000000) * 1000; + + /* Almost all modern unix systems support nanosleep(). But if you are + ** compiling for one of the rare exceptions, you can use + ** -DHAVE_NANOSLEEP=0 (perhaps in conjunction with -DHAVE_USLEEP if + ** usleep() is available) in order to bypass the use of nanosleep() */ nanosleep(&sp, NULL); + UNUSED_PARAMETER(NotUsed); return microseconds; #elif defined(HAVE_USLEEP) && HAVE_USLEEP - usleep(microseconds); + if( microseconds>=1000000 ) sleep(microseconds/1000000); + if( microseconds%1000000 ) usleep(microseconds%1000000); UNUSED_PARAMETER(NotUsed); return microseconds; #else @@ -39016,7 +47384,7 @@ SQLITE_API int sqlite3_current_time = 0; /* Fake system time in seconds since 1 ** epoch of noon in Greenwich on November 24, 4714 B.C according to the ** proleptic Gregorian calendar. ** -** On success, return SQLITE_OK. Return SQLITE_ERROR if the time and date +** On success, return SQLITE_OK. Return SQLITE_ERROR if the time and date ** cannot be found. */ static int unixCurrentTimeInt64(sqlite3_vfs *NotUsed, sqlite3_int64 *piNow){ @@ -39123,7 +47491,7 @@ static int unixGetLastError(sqlite3_vfs *NotUsed, int NotUsed2, char *NotUsed3){ ** To address the performance and cache coherency issues, proxy file locking ** changes the way database access is controlled by limiting access to a ** single host at a time and moving file locks off of the database file -** and onto a proxy file on the local file system. +** and onto a proxy file on the local file system. ** ** ** Using proxy locks @@ -39149,19 +47517,19 @@ static int unixGetLastError(sqlite3_vfs *NotUsed, int NotUsed2, char *NotUsed3){ ** actual proxy file name is generated from the name and path of the ** database file. For example: ** -** For database path "/Users/me/foo.db" +** For database path "/Users/me/foo.db" ** The lock path will be "/sqliteplocks/_Users_me_foo.db:auto:") ** ** Once a lock proxy is configured for a database connection, it can not ** be removed, however it may be switched to a different proxy path via ** the above APIs (assuming the conch file is not being held by another -** connection or process). +** connection or process). ** ** ** How proxy locking works ** ----------------------- ** -** Proxy file locking relies primarily on two new supporting files: +** Proxy file locking relies primarily on two new supporting files: ** ** * conch file to limit access to the database file to a single host ** at a time @@ -39188,11 +47556,11 @@ static int unixGetLastError(sqlite3_vfs *NotUsed, int NotUsed2, char *NotUsed3){ ** host (the conch ensures that they all use the same local lock file). ** ** Requesting the lock proxy does not immediately take the conch, it is -** only taken when the first request to lock database file is made. +** only taken when the first request to lock database file is made. ** This matches the semantics of the traditional locking behavior, where ** opening a connection to a database file does not take a lock on it. -** The shared lock and an open file descriptor are maintained until -** the connection to the database is closed. +** The shared lock and an open file descriptor are maintained until +** the connection to the database is closed. ** ** The proxy file and the lock file are never deleted so they only need ** to be created the first time they are used. @@ -39206,7 +47574,7 @@ static int unixGetLastError(sqlite3_vfs *NotUsed, int NotUsed2, char *NotUsed3){ ** automatically configured for proxy locking, lock files are ** named automatically using the same logic as ** PRAGMA lock_proxy_file=":auto:" -** +** ** SQLITE_PROXY_DEBUG ** ** Enables the logging of error messages during host id file @@ -39221,8 +47589,8 @@ static int unixGetLastError(sqlite3_vfs *NotUsed, int NotUsed2, char *NotUsed3){ ** ** Permissions to use when creating a directory for storing the ** lock proxy files, only used when LOCKPROXYDIR is not set. -** -** +** +** ** As mentioned above, when compiled with SQLITE_PREFER_PROXY_LOCKING, ** setting the environment variable SQLITE_FORCE_PROXY_LOCKING to 1 will ** force proxy locking to be used for every database file opened, and 0 @@ -39232,12 +47600,12 @@ static int unixGetLastError(sqlite3_vfs *NotUsed, int NotUsed2, char *NotUsed3){ */ /* -** Proxy locking is only available on MacOSX +** Proxy locking is only available on MacOSX */ #if defined(__APPLE__) && SQLITE_ENABLE_LOCKING_STYLE /* -** The proxyLockingContext has the path and file structures for the remote +** The proxyLockingContext has the path and file structures for the remote ** and local proxy files in it */ typedef struct proxyLockingContext proxyLockingContext; @@ -39253,10 +47621,10 @@ struct proxyLockingContext { sqlite3_io_methods const *pOldMethod; /* Original I/O methods for close */ }; -/* -** The proxy lock file path for the database at dbPath is written into lPath, +/* +** The proxy lock file path for the database at dbPath is written into lPath, ** which must point to valid, writable memory large enough for a maxLen length -** file path. +** file path. */ static int proxyGetLockPath(const char *dbPath, char *lPath, size_t maxLen){ int len; @@ -39273,7 +47641,7 @@ static int proxyGetLockPath(const char *dbPath, char *lPath, size_t maxLen){ lPath, errno, osGetpid(0))); return SQLITE_IOERR_LOCK; } - len = strlcat(lPath, "sqliteplocks", maxLen); + len = strlcat(lPath, "sqliteplocks", maxLen); } # else len = strlcpy(lPath, "/tmp/", maxLen); @@ -39283,7 +47651,7 @@ static int proxyGetLockPath(const char *dbPath, char *lPath, size_t maxLen){ if( lPath[len-1]!='/' ){ len = strlcat(lPath, "/", maxLen); } - + /* transform the db path to a unique cache name */ dbLen = (int)strlen(dbPath); for( i=0; i 0) ){ /* only mkdir if leaf dir != "." or "/" or ".." */ - if( i-start>2 || (i-start==1 && buf[start] != '.' && buf[start] != '/') + if( i-start>2 || (i-start==1 && buf[start] != '.' && buf[start] != '/') || (i-start==2 && buf[start] != '.' && buf[start+1] != '.') ){ buf[i]='\0'; if( osMkdir(buf, SQLITE_DEFAULT_PROXYDIR_PERMISSIONS) ){ @@ -39347,7 +47715,7 @@ static int proxyCreateUnixFile( int fd = -1; unixFile *pNew; int rc = SQLITE_OK; - int openFlags = O_RDWR | O_CREAT; + int openFlags = O_RDWR | O_CREAT | O_NOFOLLOW; sqlite3_vfs dummyVfs; int terrno = 0; UnixUnusedFd *pUnused = NULL; @@ -39377,7 +47745,7 @@ static int proxyCreateUnixFile( } } if( fd<0 ){ - openFlags = O_RDONLY; + openFlags = O_RDONLY | O_NOFOLLOW; fd = robust_open(path, openFlags, 0); terrno = errno; } @@ -39388,13 +47756,13 @@ static int proxyCreateUnixFile( switch (terrno) { case EACCES: return SQLITE_PERM; - case EIO: + case EIO: return SQLITE_IOERR_LOCK; /* even though it is the conch */ default: return SQLITE_CANTOPEN_BKPT; } } - + pNew = (unixFile *)sqlite3_malloc64(sizeof(*pNew)); if( pNew==NULL ){ rc = SQLITE_NOMEM_BKPT; @@ -39408,13 +47776,13 @@ static int proxyCreateUnixFile( pUnused->fd = fd; pUnused->flags = openFlags; pNew->pPreallocatedUnused = pUnused; - + rc = fillInUnixFile(&dummyVfs, fd, (sqlite3_file*)pNew, path, 0); if( rc==SQLITE_OK ){ *ppFile = pNew; return SQLITE_OK; } -end_create_proxy: +end_create_proxy: robust_close(pNew, fd, __LINE__); sqlite3_free(pNew); sqlite3_free(pUnused); @@ -39428,18 +47796,18 @@ SQLITE_API int sqlite3_hostid_num = 0; #define PROXY_HOSTIDLEN 16 /* conch file host id length */ -#ifdef HAVE_GETHOSTUUID +#if HAVE_GETHOSTUUID /* Not always defined in the headers as it ought to be */ extern int gethostuuid(uuid_t id, const struct timespec *wait); #endif -/* get the host ID via gethostuuid(), pHostID must point to PROXY_HOSTIDLEN +/* get the host ID via gethostuuid(), pHostID must point to PROXY_HOSTIDLEN ** bytes of writable memory. */ static int proxyGetHostID(unsigned char *pHostID, int *pError){ assert(PROXY_HOSTIDLEN == sizeof(uuid_t)); memset(pHostID, 0, PROXY_HOSTIDLEN); -#ifdef HAVE_GETHOSTUUID +#if HAVE_GETHOSTUUID { struct timespec timeout = {1, 0}; /* 1 sec timeout */ if( gethostuuid(pHostID, &timeout) ){ @@ -39459,7 +47827,7 @@ static int proxyGetHostID(unsigned char *pHostID, int *pError){ pHostID[0] = (char)(pHostID[0] + (char)(sqlite3_hostid_num & 0xFF)); } #endif - + return SQLITE_OK; } @@ -39470,14 +47838,14 @@ static int proxyGetHostID(unsigned char *pHostID, int *pError){ #define PROXY_PATHINDEX (PROXY_HEADERLEN+PROXY_HOSTIDLEN) #define PROXY_MAXCONCHLEN (PROXY_HEADERLEN+PROXY_HOSTIDLEN+MAXPATHLEN) -/* -** Takes an open conch file, copies the contents to a new path and then moves +/* +** Takes an open conch file, copies the contents to a new path and then moves ** it back. The newly created file's file descriptor is assigned to the -** conch file structure and finally the original conch file descriptor is +** conch file structure and finally the original conch file descriptor is ** closed. Returns zero if successful. */ static int proxyBreakConchLock(unixFile *pFile, uuid_t myHostID){ - proxyLockingContext *pCtx = (proxyLockingContext *)pFile->lockingContext; + proxyLockingContext *pCtx = (proxyLockingContext *)pFile->lockingContext; unixFile *conchFile = pCtx->conchFile; char tPath[MAXPATHLEN]; char buf[PROXY_MAXCONCHLEN]; @@ -39491,7 +47859,7 @@ static int proxyBreakConchLock(unixFile *pFile, uuid_t myHostID){ /* create a new path by replace the trailing '-conch' with '-break' */ pathLen = strlcpy(tPath, cPath, MAXPATHLEN); - if( pathLen>MAXPATHLEN || pathLen<6 || + if( pathLen>MAXPATHLEN || pathLen<6 || (strlcpy(&tPath[pathLen-5], "break", 6) != 5) ){ sqlite3_snprintf(sizeof(errmsg),errmsg,"path error (len %d)",(int)pathLen); goto end_breaklock; @@ -39503,7 +47871,7 @@ static int proxyBreakConchLock(unixFile *pFile, uuid_t myHostID){ goto end_breaklock; } /* write it out to the temporary break file */ - fd = robust_open(tPath, (O_RDWR|O_CREAT|O_EXCL), 0); + fd = robust_open(tPath, (O_RDWR|O_CREAT|O_EXCL|O_NOFOLLOW), 0); if( fd<0 ){ sqlite3_snprintf(sizeof(errmsg), errmsg, "create failed (%d)", errno); goto end_breaklock; @@ -39533,24 +47901,24 @@ static int proxyBreakConchLock(unixFile *pFile, uuid_t myHostID){ return rc; } -/* Take the requested lock on the conch file and break a stale lock if the +/* Take the requested lock on the conch file and break a stale lock if the ** host id matches. */ static int proxyConchLock(unixFile *pFile, uuid_t myHostID, int lockType){ - proxyLockingContext *pCtx = (proxyLockingContext *)pFile->lockingContext; + proxyLockingContext *pCtx = (proxyLockingContext *)pFile->lockingContext; unixFile *conchFile = pCtx->conchFile; int rc = SQLITE_OK; int nTries = 0; struct timespec conchModTime; - + memset(&conchModTime, 0, sizeof(conchModTime)); do { rc = conchFile->pMethod->xLock((sqlite3_file*)conchFile, lockType); nTries ++; if( rc==SQLITE_BUSY ){ /* If the lock failed (busy): - * 1st try: get the mod time of the conch, wait 0.5s and try again. - * 2nd try: fail if the mod time changed or host id is different, wait + * 1st try: get the mod time of the conch, wait 0.5s and try again. + * 2nd try: fail if the mod time changed or host id is different, wait * 10 sec and try again * 3rd try: break the lock unless the mod time has changed. */ @@ -39559,20 +47927,20 @@ static int proxyConchLock(unixFile *pFile, uuid_t myHostID, int lockType){ storeLastErrno(pFile, errno); return SQLITE_IOERR_LOCK; } - + if( nTries==1 ){ conchModTime = buf.st_mtimespec; - usleep(500000); /* wait 0.5 sec and try the lock again*/ - continue; + unixSleep(0,500000); /* wait 0.5 sec and try the lock again*/ + continue; } assert( nTries>1 ); - if( conchModTime.tv_sec != buf.st_mtimespec.tv_sec || + if( conchModTime.tv_sec != buf.st_mtimespec.tv_sec || conchModTime.tv_nsec != buf.st_mtimespec.tv_nsec ){ return SQLITE_BUSY; } - - if( nTries==2 ){ + + if( nTries==2 ){ char tBuf[PROXY_MAXCONCHLEN]; int len = osPread(conchFile->h, tBuf, PROXY_MAXCONCHLEN, 0); if( len<0 ){ @@ -39588,10 +47956,10 @@ static int proxyConchLock(unixFile *pFile, uuid_t myHostID, int lockType){ /* don't break the lock on short read or a version mismatch */ return SQLITE_BUSY; } - usleep(10000000); /* wait 10 sec and try the lock again */ - continue; + unixSleep(0,10000000); /* wait 10 sec and try the lock again */ + continue; } - + assert( nTries==3 ); if( 0==proxyBreakConchLock(pFile, myHostID) ){ rc = SQLITE_OK; @@ -39604,19 +47972,19 @@ static int proxyConchLock(unixFile *pFile, uuid_t myHostID, int lockType){ } } } while( rc==SQLITE_BUSY && nTries<3 ); - + return rc; } -/* Takes the conch by taking a shared lock and read the contents conch, if -** lockPath is non-NULL, the host ID and lock file path must match. A NULL -** lockPath means that the lockPath in the conch file will be used if the -** host IDs match, or a new lock path will be generated automatically +/* Takes the conch by taking a shared lock and read the contents conch, if +** lockPath is non-NULL, the host ID and lock file path must match. A NULL +** lockPath means that the lockPath in the conch file will be used if the +** host IDs match, or a new lock path will be generated automatically ** and written to the conch file. */ static int proxyTakeConch(unixFile *pFile){ - proxyLockingContext *pCtx = (proxyLockingContext *)pFile->lockingContext; - + proxyLockingContext *pCtx = (proxyLockingContext *)pFile->lockingContext; + if( pCtx->conchHeld!=0 ){ return SQLITE_OK; }else{ @@ -39632,7 +48000,7 @@ static int proxyTakeConch(unixFile *pFile){ int readLen = 0; int tryOldLockPath = 0; int forceNewLockPath = 0; - + OSTRACE(("TAKECONCH %d for %s pid=%d\n", conchFile->h, (pCtx->lockProxyPath ? pCtx->lockProxyPath : ":auto:"), osGetpid(0))); @@ -39653,21 +48021,21 @@ static int proxyTakeConch(unixFile *pFile){ storeLastErrno(pFile, conchFile->lastErrno); rc = SQLITE_IOERR_READ; goto end_takeconch; - }else if( readLen<=(PROXY_HEADERLEN+PROXY_HOSTIDLEN) || + }else if( readLen<=(PROXY_HEADERLEN+PROXY_HOSTIDLEN) || readBuf[0]!=(char)PROXY_CONCHVERSION ){ - /* a short read or version format mismatch means we need to create a new - ** conch file. + /* a short read or version format mismatch means we need to create a new + ** conch file. */ createConch = 1; } /* if the host id matches and the lock path already exists in the conch - ** we'll try to use the path there, if we can't open that path, we'll - ** retry with a new auto-generated path + ** we'll try to use the path there, if we can't open that path, we'll + ** retry with a new auto-generated path */ do { /* in case we need to try again for an :auto: named lock file */ if( !createConch && !forceNewLockPath ){ - hostIdMatch = !memcmp(&readBuf[PROXY_HEADERLEN], myHostID, + hostIdMatch = !memcmp(&readBuf[PROXY_HEADERLEN], myHostID, PROXY_HOSTIDLEN); /* if the conch has data compare the contents */ if( !pCtx->lockProxyPath ){ @@ -39676,7 +48044,7 @@ static int proxyTakeConch(unixFile *pFile){ */ if( hostIdMatch ){ size_t pathLen = (readLen - PROXY_PATHINDEX); - + if( pathLen>=MAXPATHLEN ){ pathLen=MAXPATHLEN-1; } @@ -39692,23 +48060,23 @@ static int proxyTakeConch(unixFile *pFile){ readLen-PROXY_PATHINDEX) ){ /* conch host and lock path match */ - goto end_takeconch; + goto end_takeconch; } } - + /* if the conch isn't writable and doesn't match, we can't take it */ if( (conchFile->openFlags&O_RDWR) == 0 ){ rc = SQLITE_BUSY; goto end_takeconch; } - + /* either the conch didn't match or we need to create a new one */ if( !pCtx->lockProxyPath ){ proxyGetLockPath(pCtx->dbPath, lockPath, MAXPATHLEN); tempLockPath = lockPath; /* create a copy of the lock path _only_ if the conch is taken */ } - + /* update conch with host and path (this will fail if other process ** has a shared lock already), if the host id matches, use the big ** stick. @@ -39719,7 +48087,7 @@ static int proxyTakeConch(unixFile *pFile){ /* We are trying for an exclusive lock but another thread in this ** same process is still holding a shared lock. */ rc = SQLITE_BUSY; - } else { + } else { rc = proxyConchLock(pFile, myHostID, EXCLUSIVE_LOCK); } }else{ @@ -39728,7 +48096,7 @@ static int proxyTakeConch(unixFile *pFile){ if( rc==SQLITE_OK ){ char writeBuffer[PROXY_MAXCONCHLEN]; int writeSize = 0; - + writeBuffer[0] = (char)PROXY_CONCHVERSION; memcpy(&writeBuffer[PROXY_HEADERLEN], myHostID, PROXY_HOSTIDLEN); if( pCtx->lockProxyPath!=NULL ){ @@ -39741,8 +48109,8 @@ static int proxyTakeConch(unixFile *pFile){ robust_ftruncate(conchFile->h, writeSize); rc = unixWrite((sqlite3_file *)conchFile, writeBuffer, writeSize, 0); full_fsync(conchFile->h,0,0); - /* If we created a new conch file (not just updated the contents of a - ** valid conch file), try to match the permissions of the database + /* If we created a new conch file (not just updated the contents of a + ** valid conch file), try to match the permissions of the database */ if( rc==SQLITE_OK && createConch ){ struct stat buf; @@ -39766,14 +48134,14 @@ static int proxyTakeConch(unixFile *pFile){ } }else{ int code = errno; - fprintf(stderr, "STAT FAILED[%d] with %d %s\n", + fprintf(stderr, "STAT FAILED[%d] with %d %s\n", err, code, strerror(code)); #endif } } } conchFile->pMethod->xUnlock((sqlite3_file*)conchFile, SHARED_LOCK); - + end_takeconch: OSTRACE(("TRANSPROXY: CLOSE %d\n", pFile->h)); if( rc==SQLITE_OK && pFile->openFlags ){ @@ -39796,7 +48164,7 @@ static int proxyTakeConch(unixFile *pFile){ rc = proxyCreateUnixFile(path, &pCtx->lockProxy, 1); if( rc!=SQLITE_OK && rc!=SQLITE_NOMEM && tryOldLockPath ){ /* we couldn't create the proxy lock file with the old lock file path - ** so try again via auto-naming + ** so try again via auto-naming */ forceNewLockPath = 1; tryOldLockPath = 0; @@ -39816,7 +48184,7 @@ static int proxyTakeConch(unixFile *pFile){ } if( rc==SQLITE_OK ){ pCtx->conchHeld = 1; - + if( pCtx->lockProxy->pMethod == &afpIoMethods ){ afpLockingContext *afpCtx; afpCtx = (afpLockingContext *)pCtx->lockProxy->lockingContext; @@ -39828,7 +48196,7 @@ static int proxyTakeConch(unixFile *pFile){ OSTRACE(("TAKECONCH %d %s\n", conchFile->h, rc==SQLITE_OK?"ok":"failed")); return rc; - } while (1); /* in case we need to retry the :auto: lock file - + } while (1); /* in case we need to retry the :auto: lock file - ** we should never get here except via the 'continue' call. */ } } @@ -39844,7 +48212,7 @@ static int proxyReleaseConch(unixFile *pFile){ pCtx = (proxyLockingContext *)pFile->lockingContext; conchFile = pCtx->conchFile; OSTRACE(("RELEASECONCH %d for %s pid=%d\n", conchFile->h, - (pCtx->lockProxyPath ? pCtx->lockProxyPath : ":auto:"), + (pCtx->lockProxyPath ? pCtx->lockProxyPath : ":auto:"), osGetpid(0))); if( pCtx->conchHeld>0 ){ rc = conchFile->pMethod->xUnlock((sqlite3_file*)conchFile, NO_LOCK); @@ -39872,13 +48240,13 @@ static int proxyCreateConchPathname(char *dbPath, char **pConchPath){ char *conchPath; /* buffer in which to construct conch name */ /* Allocate space for the conch filename and initialize the name to - ** the name of the original database file. */ + ** the name of the original database file. */ *pConchPath = conchPath = (char *)sqlite3_malloc64(len + 8); if( conchPath==0 ){ return SQLITE_NOMEM_BKPT; } memcpy(conchPath, dbPath, len+1); - + /* now insert a "." before the last / character */ for( i=(len-1); i>=0; i-- ){ if( conchPath[i]=='/' ){ @@ -39901,7 +48269,7 @@ static int proxyCreateConchPathname(char *dbPath, char **pConchPath){ /* Takes a fully configured proxy locking-style unix file and switches -** the local lock file path +** the local lock file path */ static int switchLockProxyPath(unixFile *pFile, const char *path) { proxyLockingContext *pCtx = (proxyLockingContext*)pFile->lockingContext; @@ -39910,7 +48278,7 @@ static int switchLockProxyPath(unixFile *pFile, const char *path) { if( pFile->eFileLock!=NO_LOCK ){ return SQLITE_BUSY; - } + } /* nothing to do if the path is NULL, :auto: or matches the existing path */ if( !path || path[0]=='\0' || !strcmp(path, ":auto:") || @@ -39928,7 +48296,7 @@ static int switchLockProxyPath(unixFile *pFile, const char *path) { sqlite3_free(oldPath); pCtx->lockProxyPath = sqlite3DbStrDup(0, path); } - + return rc; } @@ -39942,7 +48310,7 @@ static int switchLockProxyPath(unixFile *pFile, const char *path) { static int proxyGetDbPathForUnixFile(unixFile *pFile, char *dbPath){ #if defined(__APPLE__) if( pFile->pMethod == &afpIoMethods ){ - /* afp style keeps a reference to the db path in the filePath field + /* afp style keeps a reference to the db path in the filePath field ** of the struct */ assert( (int)strlen((char*)pFile->lockingContext)<=MAXPATHLEN ); strlcpy(dbPath, ((afpLockingContext *)pFile->lockingContext)->dbPath, @@ -39963,9 +48331,9 @@ static int proxyGetDbPathForUnixFile(unixFile *pFile, char *dbPath){ } /* -** Takes an already filled in unix file and alters it so all file locking +** Takes an already filled in unix file and alters it so all file locking ** will be performed on the local proxy lock file. The following fields -** are preserved in the locking context so that they can be restored and +** are preserved in the locking context so that they can be restored and ** the unix structure properly cleaned up at close time: ** ->lockingContext ** ->pMethod @@ -39975,7 +48343,7 @@ static int proxyTransformUnixFile(unixFile *pFile, const char *path) { char dbPath[MAXPATHLEN+1]; /* Name of the database file */ char *lockPath=NULL; int rc = SQLITE_OK; - + if( pFile->eFileLock!=NO_LOCK ){ return SQLITE_BUSY; } @@ -39985,7 +48353,7 @@ static int proxyTransformUnixFile(unixFile *pFile, const char *path) { }else{ lockPath=(char *)path; } - + OSTRACE(("TRANSPROXY %d for %s pid=%d\n", pFile->h, (lockPath ? lockPath : ":auto:"), osGetpid(0))); @@ -40019,7 +48387,7 @@ static int proxyTransformUnixFile(unixFile *pFile, const char *path) { rc = SQLITE_OK; } } - } + } if( rc==SQLITE_OK && lockPath ){ pCtx->lockProxyPath = sqlite3DbStrDup(0, lockPath); } @@ -40031,7 +48399,7 @@ static int proxyTransformUnixFile(unixFile *pFile, const char *path) { } } if( rc==SQLITE_OK ){ - /* all memory is allocated, proxys are created and assigned, + /* all memory is allocated, proxys are created and assigned, ** switch the locking context and pMethod then return. */ pCtx->oldLockingContext = pFile->lockingContext; @@ -40039,12 +48407,12 @@ static int proxyTransformUnixFile(unixFile *pFile, const char *path) { pCtx->pOldMethod = pFile->pMethod; pFile->pMethod = &proxyIoMethods; }else{ - if( pCtx->conchFile ){ + if( pCtx->conchFile ){ pCtx->conchFile->pMethod->xClose((sqlite3_file *)pCtx->conchFile); sqlite3_free(pCtx->conchFile); } sqlite3DbFree(0, pCtx->lockProxyPath); - sqlite3_free(pCtx->conchFilePath); + sqlite3_free(pCtx->conchFilePath); sqlite3_free(pCtx); } OSTRACE(("TRANSPROXY %d %s\n", pFile->h, @@ -40082,7 +48450,7 @@ static int proxyFileControl(sqlite3_file *id, int op, void *pArg){ if( isProxyStyle ){ /* turn off proxy locking - not supported. If support is added for ** switching proxy locking mode off then it will need to fail if - ** the journal mode is WAL mode. + ** the journal mode is WAL mode. */ rc = SQLITE_ERROR /*SQLITE_PROTOCOL? SQLITE_MISUSE?*/; }else{ @@ -40092,9 +48460,9 @@ static int proxyFileControl(sqlite3_file *id, int op, void *pArg){ }else{ const char *proxyPath = (const char *)pArg; if( isProxyStyle ){ - proxyLockingContext *pCtx = + proxyLockingContext *pCtx = (proxyLockingContext*)pFile->lockingContext; - if( !strcmp(pArg, ":auto:") + if( !strcmp(pArg, ":auto:") || (pCtx->lockProxyPath && !strncmp(pCtx->lockProxyPath, proxyPath, MAXPATHLEN)) ){ @@ -40113,7 +48481,7 @@ static int proxyFileControl(sqlite3_file *id, int op, void *pArg){ assert( 0 ); /* The call assures that only valid opcodes are sent */ } } - /*NOTREACHED*/ + /*NOTREACHED*/ assert(0); return SQLITE_ERROR; } @@ -40219,7 +48587,7 @@ static int proxyClose(sqlite3_file *id) { unixFile *lockProxy = pCtx->lockProxy; unixFile *conchFile = pCtx->conchFile; int rc = SQLITE_OK; - + if( lockProxy ){ rc = lockProxy->pMethod->xUnlock((sqlite3_file*)lockProxy, NO_LOCK); if( rc ) return rc; @@ -40256,7 +48624,7 @@ static int proxyClose(sqlite3_file *id) { ** The proxy locking style is intended for use with AFP filesystems. ** And since AFP is only supported on MacOSX, the proxy locking is also ** restricted to MacOSX. -** +** ** ******************* End of the proxy lock implementation ********************** ******************************************************************************/ @@ -40274,8 +48642,8 @@ static int proxyClose(sqlite3_file *id) { ** necessarily been initialized when this routine is called, and so they ** should not be used. */ -SQLITE_API int sqlite3_os_init(void){ - /* +SQLITE_API int sqlite3_os_init(void){ + /* ** The following macro defines an initializer for an sqlite3_vfs object. ** The name of the VFS is NAME. The pAppData is a pointer to a pointer ** to the "finder" function. (pAppData is a pointer to a pointer because @@ -40291,7 +48659,7 @@ SQLITE_API int sqlite3_os_init(void){ ** ** Most finders simply return a pointer to a fixed sqlite3_io_methods ** object. But the "autolockIoFinder" available on MacOSX does a little - ** more than that; it looks at the filesystem type that hosts the + ** more than that; it looks at the filesystem type that hosts the ** database file and tries to choose an locking method appropriate for ** that filesystem time. */ @@ -40361,10 +48729,43 @@ SQLITE_API int sqlite3_os_init(void){ /* Register all VFSes defined in the aVfs[] array */ for(i=0; i<(sizeof(aVfs)/sizeof(sqlite3_vfs)); i++){ +#ifdef SQLITE_DEFAULT_UNIX_VFS + sqlite3_vfs_register(&aVfs[i], + 0==strcmp(aVfs[i].zName,SQLITE_DEFAULT_UNIX_VFS)); +#else sqlite3_vfs_register(&aVfs[i], i==0); +#endif } +#ifdef SQLITE_OS_KV_OPTIONAL + sqlite3KvvfsInit(); +#endif unixBigLock = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1); - return SQLITE_OK; +#if OS_VXWORKS + vxworksMutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS2); +#endif + +#ifndef SQLITE_OMIT_WAL + /* Validate lock assumptions */ + assert( SQLITE_SHM_NLOCK==8 ); /* Number of available locks */ + assert( UNIX_SHM_BASE==120 ); /* Start of locking area */ + /* Locks: + ** WRITE UNIX_SHM_BASE 120 + ** CKPT UNIX_SHM_BASE+1 121 + ** RECOVER UNIX_SHM_BASE+2 122 + ** READ-0 UNIX_SHM_BASE+3 123 + ** READ-1 UNIX_SHM_BASE+4 124 + ** READ-2 UNIX_SHM_BASE+5 125 + ** READ-3 UNIX_SHM_BASE+6 126 + ** READ-4 UNIX_SHM_BASE+7 127 + ** DMS UNIX_SHM_BASE+8 128 + */ + assert( UNIX_SHM_DMS==128 ); /* Byte offset of the deadman-switch */ +#endif + + /* Initialize temp file dir array. */ + unixTempFileInit(); + + return SQLITE_OK; } /* @@ -40374,11 +48775,14 @@ SQLITE_API int sqlite3_os_init(void){ ** to release dynamically allocated objects. But not on unix. ** This routine is a no-op for unix. */ -SQLITE_API int sqlite3_os_end(void){ +SQLITE_API int sqlite3_os_end(void){ unixBigLock = 0; - return SQLITE_OK; +#if OS_VXWORKS + vxworksMutex = 0; +#endif + return SQLITE_OK; } - + #endif /* SQLITE_OS_UNIX */ /************** End of os_unix.c *********************************************/ @@ -40403,205 +48807,7 @@ SQLITE_API int sqlite3_os_end(void){ /* ** Include code that is common to all os_*.c files */ -/************** Include os_common.h in the middle of os_win.c ****************/ -/************** Begin file os_common.h ***************************************/ -/* -** 2004 May 22 -** -** The author disclaims copyright to this source code. In place of -** a legal notice, here is a blessing: -** -** May you do good and not evil. -** May you find forgiveness for yourself and forgive others. -** May you share freely, never taking more than you give. -** -****************************************************************************** -** -** This file contains macros and a little bit of code that is common to -** all of the platform-specific files (os_*.c) and is #included into those -** files. -** -** This file should be #included by the os_*.c files only. It is not a -** general purpose header file. -*/ -#ifndef _OS_COMMON_H_ -#define _OS_COMMON_H_ - -/* -** At least two bugs have slipped in because we changed the MEMORY_DEBUG -** macro to SQLITE_DEBUG and some older makefiles have not yet made the -** switch. The following code should catch this problem at compile-time. -*/ -#ifdef MEMORY_DEBUG -# error "The MEMORY_DEBUG macro is obsolete. Use SQLITE_DEBUG instead." -#endif - -/* -** Macros for performance tracing. Normally turned off. Only works -** on i486 hardware. -*/ -#ifdef SQLITE_PERFORMANCE_TRACE - -/* -** hwtime.h contains inline assembler code for implementing -** high-performance timing routines. -*/ -/************** Include hwtime.h in the middle of os_common.h ****************/ -/************** Begin file hwtime.h ******************************************/ -/* -** 2008 May 27 -** -** The author disclaims copyright to this source code. In place of -** a legal notice, here is a blessing: -** -** May you do good and not evil. -** May you find forgiveness for yourself and forgive others. -** May you share freely, never taking more than you give. -** -****************************************************************************** -** -** This file contains inline asm code for retrieving "high-performance" -** counters for x86 class CPUs. -*/ -#ifndef SQLITE_HWTIME_H -#define SQLITE_HWTIME_H - -/* -** The following routine only works on pentium-class (or newer) processors. -** It uses the RDTSC opcode to read the cycle count value out of the -** processor and returns that value. This can be used for high-res -** profiling. -*/ -#if (defined(__GNUC__) || defined(_MSC_VER)) && \ - (defined(i386) || defined(__i386__) || defined(_M_IX86)) - - #if defined(__GNUC__) - - __inline__ sqlite_uint64 sqlite3Hwtime(void){ - unsigned int lo, hi; - __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi)); - return (sqlite_uint64)hi << 32 | lo; - } - - #elif defined(_MSC_VER) - - __declspec(naked) __inline sqlite_uint64 __cdecl sqlite3Hwtime(void){ - __asm { - rdtsc - ret ; return value at EDX:EAX - } - } - - #endif - -#elif (defined(__GNUC__) && defined(__x86_64__)) - - __inline__ sqlite_uint64 sqlite3Hwtime(void){ - unsigned long val; - __asm__ __volatile__ ("rdtsc" : "=A" (val)); - return val; - } - -#elif (defined(__GNUC__) && defined(__ppc__)) - - __inline__ sqlite_uint64 sqlite3Hwtime(void){ - unsigned long long retval; - unsigned long junk; - __asm__ __volatile__ ("\n\ - 1: mftbu %1\n\ - mftb %L0\n\ - mftbu %0\n\ - cmpw %0,%1\n\ - bne 1b" - : "=r" (retval), "=r" (junk)); - return retval; - } - -#else - - #error Need implementation of sqlite3Hwtime() for your platform. - - /* - ** To compile without implementing sqlite3Hwtime() for your platform, - ** you can remove the above #error and use the following - ** stub function. You will lose timing support for many - ** of the debugging and testing utilities, but it should at - ** least compile and run. - */ -SQLITE_PRIVATE sqlite_uint64 sqlite3Hwtime(void){ return ((sqlite_uint64)0); } - -#endif - -#endif /* !defined(SQLITE_HWTIME_H) */ - -/************** End of hwtime.h **********************************************/ -/************** Continuing where we left off in os_common.h ******************/ - -static sqlite_uint64 g_start; -static sqlite_uint64 g_elapsed; -#define TIMER_START g_start=sqlite3Hwtime() -#define TIMER_END g_elapsed=sqlite3Hwtime()-g_start -#define TIMER_ELAPSED g_elapsed -#else -#define TIMER_START -#define TIMER_END -#define TIMER_ELAPSED ((sqlite_uint64)0) -#endif - -/* -** If we compile with the SQLITE_TEST macro set, then the following block -** of code will give us the ability to simulate a disk I/O error. This -** is used for testing the I/O recovery logic. -*/ -#if defined(SQLITE_TEST) -SQLITE_API extern int sqlite3_io_error_hit; -SQLITE_API extern int sqlite3_io_error_hardhit; -SQLITE_API extern int sqlite3_io_error_pending; -SQLITE_API extern int sqlite3_io_error_persist; -SQLITE_API extern int sqlite3_io_error_benign; -SQLITE_API extern int sqlite3_diskfull_pending; -SQLITE_API extern int sqlite3_diskfull; -#define SimulateIOErrorBenign(X) sqlite3_io_error_benign=(X) -#define SimulateIOError(CODE) \ - if( (sqlite3_io_error_persist && sqlite3_io_error_hit) \ - || sqlite3_io_error_pending-- == 1 ) \ - { local_ioerr(); CODE; } -static void local_ioerr(){ - IOTRACE(("IOERR\n")); - sqlite3_io_error_hit++; - if( !sqlite3_io_error_benign ) sqlite3_io_error_hardhit++; -} -#define SimulateDiskfullError(CODE) \ - if( sqlite3_diskfull_pending ){ \ - if( sqlite3_diskfull_pending == 1 ){ \ - local_ioerr(); \ - sqlite3_diskfull = 1; \ - sqlite3_io_error_hit = 1; \ - CODE; \ - }else{ \ - sqlite3_diskfull_pending--; \ - } \ - } -#else -#define SimulateIOErrorBenign(X) -#define SimulateIOError(A) -#define SimulateDiskfullError(A) -#endif /* defined(SQLITE_TEST) */ - -/* -** When testing, keep a count of the number of open files. -*/ -#if defined(SQLITE_TEST) -SQLITE_API extern int sqlite3_open_file_count; -#define OpenCounter(X) sqlite3_open_file_count+=(X) -#else -#define OpenCounter(X) -#endif /* defined(SQLITE_TEST) */ - -#endif /* !defined(_OS_COMMON_H_) */ - -/************** End of os_common.h *******************************************/ -/************** Continuing where we left off in os_win.c *********************/ +/* #include "os_common.h" */ /* ** Include the header file for the Windows VFS. @@ -40626,7 +48832,7 @@ SQLITE_API extern int sqlite3_open_file_count; ** Are most of the Win32 ANSI APIs available (i.e. with certain exceptions ** based on the sub-platform)? */ -#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && !defined(SQLITE_WIN32_NO_ANSI) +#if !SQLITE_OS_WINCE && !defined(SQLITE_WIN32_NO_ANSI) # define SQLITE_WIN32_HAS_ANSI #endif @@ -40634,7 +48840,7 @@ SQLITE_API extern int sqlite3_open_file_count; ** Are most of the Win32 Unicode APIs available (i.e. with certain exceptions ** based on the sub-platform)? */ -#if (SQLITE_OS_WINCE || SQLITE_OS_WINNT || SQLITE_OS_WINRT) && \ +#if (SQLITE_OS_WINCE || SQLITE_OS_WINNT) && \ !defined(SQLITE_WIN32_NO_WIDE) # define SQLITE_WIN32_HAS_WIDE #endif @@ -40773,16 +48979,7 @@ SQLITE_API extern int sqlite3_open_file_count; */ #if SQLITE_WIN32_FILEMAPPING_API && \ (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0) -/* -** Two of the file mapping APIs are different under WinRT. Figure out which -** set we need. -*/ -#if SQLITE_OS_WINRT -WINBASEAPI HANDLE WINAPI CreateFileMappingFromApp(HANDLE, \ - LPSECURITY_ATTRIBUTES, ULONG, ULONG64, LPCWSTR); -WINBASEAPI LPVOID WINAPI MapViewOfFileFromApp(HANDLE, ULONG, ULONG64, SIZE_T); -#else #if defined(SQLITE_WIN32_HAS_ANSI) WINBASEAPI HANDLE WINAPI CreateFileMappingA(HANDLE, LPSECURITY_ATTRIBUTES, \ DWORD, DWORD, DWORD, LPCSTR); @@ -40794,7 +48991,6 @@ WINBASEAPI HANDLE WINAPI CreateFileMappingW(HANDLE, LPSECURITY_ATTRIBUTES, \ #endif /* defined(SQLITE_WIN32_HAS_WIDE) */ WINBASEAPI LPVOID WINAPI MapViewOfFile(HANDLE, DWORD, DWORD, DWORD, SIZE_T); -#endif /* SQLITE_OS_WINRT */ /* ** These file mapping APIs are common to both Win32 and WinRT. @@ -40870,8 +49066,18 @@ struct winFile { sqlite3_int64 mmapSize; /* Size of mapped region */ sqlite3_int64 mmapSizeMax; /* Configured FCNTL_MMAP_SIZE value */ #endif +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + DWORD iBusyTimeout; /* Wait this many millisec on locks */ + int bBlockOnConnect; +#endif }; +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT +# define winFileBusyTimeout(pDbFd) pDbFd->iBusyTimeout +#else +# define winFileBusyTimeout(pDbFd) 0 +#endif + /* ** The winVfsAppData structure is used for the pAppData member for all of the ** Win32 VFS variants. @@ -41075,7 +49281,7 @@ static LONG SQLITE_WIN32_VOLATILE sqlite3_os_type = 0; ** This function is not available on Windows CE or WinRT. */ -#if SQLITE_OS_WINCE || SQLITE_OS_WINRT +#if SQLITE_OS_WINCE # define osAreFileApisANSI() 1 #endif @@ -41090,7 +49296,7 @@ static struct win_syscall { sqlite3_syscall_ptr pCurrent; /* Current value of the system call */ sqlite3_syscall_ptr pDefault; /* Default value */ } aSyscall[] = { -#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT +#if !SQLITE_OS_WINCE { "AreFileApisANSI", (SYSCALL)AreFileApisANSI, 0 }, #else { "AreFileApisANSI", (SYSCALL)0, 0 }, @@ -41129,7 +49335,7 @@ static struct win_syscall { #define osCreateFileA ((HANDLE(WINAPI*)(LPCSTR,DWORD,DWORD, \ LPSECURITY_ATTRIBUTES,DWORD,DWORD,HANDLE))aSyscall[4].pCurrent) -#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) +#if defined(SQLITE_WIN32_HAS_WIDE) { "CreateFileW", (SYSCALL)CreateFileW, 0 }, #else { "CreateFileW", (SYSCALL)0, 0 }, @@ -41138,7 +49344,7 @@ static struct win_syscall { #define osCreateFileW ((HANDLE(WINAPI*)(LPCWSTR,DWORD,DWORD, \ LPSECURITY_ATTRIBUTES,DWORD,DWORD,HANDLE))aSyscall[5].pCurrent) -#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_ANSI) && \ +#if defined(SQLITE_WIN32_HAS_ANSI) && \ (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0) && \ SQLITE_WIN32_CREATEFILEMAPPINGA { "CreateFileMappingA", (SYSCALL)CreateFileMappingA, 0 }, @@ -41149,8 +49355,8 @@ static struct win_syscall { #define osCreateFileMappingA ((HANDLE(WINAPI*)(HANDLE,LPSECURITY_ATTRIBUTES, \ DWORD,DWORD,DWORD,LPCSTR))aSyscall[6].pCurrent) -#if SQLITE_OS_WINCE || (!SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) && \ - (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0)) +#if (SQLITE_OS_WINCE || defined(SQLITE_WIN32_HAS_WIDE)) && \ + (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0) { "CreateFileMappingW", (SYSCALL)CreateFileMappingW, 0 }, #else { "CreateFileMappingW", (SYSCALL)0, 0 }, @@ -41159,7 +49365,7 @@ static struct win_syscall { #define osCreateFileMappingW ((HANDLE(WINAPI*)(HANDLE,LPSECURITY_ATTRIBUTES, \ DWORD,DWORD,DWORD,LPCWSTR))aSyscall[7].pCurrent) -#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) +#if defined(SQLITE_WIN32_HAS_WIDE) { "CreateMutexW", (SYSCALL)CreateMutexW, 0 }, #else { "CreateMutexW", (SYSCALL)0, 0 }, @@ -41190,7 +49396,7 @@ static struct win_syscall { { "FileTimeToLocalFileTime", (SYSCALL)0, 0 }, #endif -#define osFileTimeToLocalFileTime ((BOOL(WINAPI*)(CONST FILETIME*, \ +#define osFileTimeToLocalFileTime ((BOOL(WINAPI*)(const FILETIME*, \ LPFILETIME))aSyscall[11].pCurrent) #if SQLITE_OS_WINCE @@ -41199,7 +49405,7 @@ static struct win_syscall { { "FileTimeToSystemTime", (SYSCALL)0, 0 }, #endif -#define osFileTimeToSystemTime ((BOOL(WINAPI*)(CONST FILETIME*, \ +#define osFileTimeToSystemTime ((BOOL(WINAPI*)(const FILETIME*, \ LPSYSTEMTIME))aSyscall[12].pCurrent) { "FlushFileBuffers", (SYSCALL)FlushFileBuffers, 0 }, @@ -41245,7 +49451,7 @@ static struct win_syscall { #define osGetDiskFreeSpaceA ((BOOL(WINAPI*)(LPCSTR,LPDWORD,LPDWORD,LPDWORD, \ LPDWORD))aSyscall[18].pCurrent) -#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) +#if !SQLITE_OS_WINCE && defined(SQLITE_WIN32_HAS_WIDE) { "GetDiskFreeSpaceW", (SYSCALL)GetDiskFreeSpaceW, 0 }, #else { "GetDiskFreeSpaceW", (SYSCALL)0, 0 }, @@ -41262,7 +49468,7 @@ static struct win_syscall { #define osGetFileAttributesA ((DWORD(WINAPI*)(LPCSTR))aSyscall[20].pCurrent) -#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) +#if defined(SQLITE_WIN32_HAS_WIDE) { "GetFileAttributesW", (SYSCALL)GetFileAttributesW, 0 }, #else { "GetFileAttributesW", (SYSCALL)0, 0 }, @@ -41279,11 +49485,7 @@ static struct win_syscall { #define osGetFileAttributesExW ((BOOL(WINAPI*)(LPCWSTR,GET_FILEEX_INFO_LEVELS, \ LPVOID))aSyscall[22].pCurrent) -#if !SQLITE_OS_WINRT { "GetFileSize", (SYSCALL)GetFileSize, 0 }, -#else - { "GetFileSize", (SYSCALL)0, 0 }, -#endif #define osGetFileSize ((DWORD(WINAPI*)(HANDLE,LPDWORD))aSyscall[23].pCurrent) @@ -41296,7 +49498,7 @@ static struct win_syscall { #define osGetFullPathNameA ((DWORD(WINAPI*)(LPCSTR,DWORD,LPSTR, \ LPSTR*))aSyscall[24].pCurrent) -#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) +#if !SQLITE_OS_WINCE && defined(SQLITE_WIN32_HAS_WIDE) { "GetFullPathNameW", (SYSCALL)GetFullPathNameW, 0 }, #else { "GetFullPathNameW", (SYSCALL)0, 0 }, @@ -41305,6 +49507,12 @@ static struct win_syscall { #define osGetFullPathNameW ((DWORD(WINAPI*)(LPCWSTR,DWORD,LPWSTR, \ LPWSTR*))aSyscall[25].pCurrent) +/* +** For GetLastError(), MSDN says: +** +** Minimum supported client: Windows XP [desktop apps | UWP apps] +** Minimum supported server: Windows Server 2003 [desktop apps | UWP apps] +*/ { "GetLastError", (SYSCALL)GetLastError, 0 }, #define osGetLastError ((DWORD(WINAPI*)(VOID))aSyscall[26].pCurrent) @@ -41325,16 +49533,10 @@ static struct win_syscall { #define osGetProcAddressA ((FARPROC(WINAPI*)(HMODULE, \ LPCSTR))aSyscall[27].pCurrent) -#if !SQLITE_OS_WINRT { "GetSystemInfo", (SYSCALL)GetSystemInfo, 0 }, -#else - { "GetSystemInfo", (SYSCALL)0, 0 }, -#endif - #define osGetSystemInfo ((VOID(WINAPI*)(LPSYSTEM_INFO))aSyscall[28].pCurrent) { "GetSystemTime", (SYSCALL)GetSystemTime, 0 }, - #define osGetSystemTime ((VOID(WINAPI*)(LPSYSTEMTIME))aSyscall[29].pCurrent) #if !SQLITE_OS_WINCE @@ -41354,7 +49556,7 @@ static struct win_syscall { #define osGetTempPathA ((DWORD(WINAPI*)(DWORD,LPSTR))aSyscall[31].pCurrent) -#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) +#if defined(SQLITE_WIN32_HAS_WIDE) { "GetTempPathW", (SYSCALL)GetTempPathW, 0 }, #else { "GetTempPathW", (SYSCALL)0, 0 }, @@ -41362,11 +49564,7 @@ static struct win_syscall { #define osGetTempPathW ((DWORD(WINAPI*)(DWORD,LPWSTR))aSyscall[32].pCurrent) -#if !SQLITE_OS_WINRT { "GetTickCount", (SYSCALL)GetTickCount, 0 }, -#else - { "GetTickCount", (SYSCALL)0, 0 }, -#endif #define osGetTickCount ((DWORD(WINAPI*)(VOID))aSyscall[33].pCurrent) @@ -41379,7 +49577,7 @@ static struct win_syscall { #define osGetVersionExA ((BOOL(WINAPI*)( \ LPOSVERSIONINFOA))aSyscall[34].pCurrent) -#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) && \ +#if defined(SQLITE_WIN32_HAS_WIDE) && \ SQLITE_WIN32_GETVERSIONEX { "GetVersionExW", (SYSCALL)GetVersionExW, 0 }, #else @@ -41394,20 +49592,12 @@ static struct win_syscall { #define osHeapAlloc ((LPVOID(WINAPI*)(HANDLE,DWORD, \ SIZE_T))aSyscall[36].pCurrent) -#if !SQLITE_OS_WINRT { "HeapCreate", (SYSCALL)HeapCreate, 0 }, -#else - { "HeapCreate", (SYSCALL)0, 0 }, -#endif #define osHeapCreate ((HANDLE(WINAPI*)(DWORD,SIZE_T, \ SIZE_T))aSyscall[37].pCurrent) -#if !SQLITE_OS_WINRT { "HeapDestroy", (SYSCALL)HeapDestroy, 0 }, -#else - { "HeapDestroy", (SYSCALL)0, 0 }, -#endif #define osHeapDestroy ((BOOL(WINAPI*)(HANDLE))aSyscall[38].pCurrent) @@ -41425,16 +49615,12 @@ static struct win_syscall { #define osHeapSize ((SIZE_T(WINAPI*)(HANDLE,DWORD, \ LPCVOID))aSyscall[41].pCurrent) -#if !SQLITE_OS_WINRT { "HeapValidate", (SYSCALL)HeapValidate, 0 }, -#else - { "HeapValidate", (SYSCALL)0, 0 }, -#endif #define osHeapValidate ((BOOL(WINAPI*)(HANDLE,DWORD, \ LPCVOID))aSyscall[42].pCurrent) -#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT +#if !SQLITE_OS_WINCE { "HeapCompact", (SYSCALL)HeapCompact, 0 }, #else { "HeapCompact", (SYSCALL)0, 0 }, @@ -41450,7 +49636,7 @@ static struct win_syscall { #define osLoadLibraryA ((HMODULE(WINAPI*)(LPCSTR))aSyscall[44].pCurrent) -#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_HAS_WIDE) && \ +#if defined(SQLITE_WIN32_HAS_WIDE) && \ !defined(SQLITE_OMIT_LOAD_EXTENSION) { "LoadLibraryW", (SYSCALL)LoadLibraryW, 0 }, #else @@ -41459,21 +49645,17 @@ static struct win_syscall { #define osLoadLibraryW ((HMODULE(WINAPI*)(LPCWSTR))aSyscall[45].pCurrent) -#if !SQLITE_OS_WINRT { "LocalFree", (SYSCALL)LocalFree, 0 }, -#else - { "LocalFree", (SYSCALL)0, 0 }, -#endif #define osLocalFree ((HLOCAL(WINAPI*)(HLOCAL))aSyscall[46].pCurrent) -#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT +#if !SQLITE_OS_WINCE { "LockFile", (SYSCALL)LockFile, 0 }, #else { "LockFile", (SYSCALL)0, 0 }, #endif -#ifndef osLockFile +#if !defined(osLockFile) && defined(SQLITE_WIN32_HAS_ANSI) #define osLockFile ((BOOL(WINAPI*)(HANDLE,DWORD,DWORD,DWORD, \ DWORD))aSyscall[47].pCurrent) #endif @@ -41489,8 +49671,7 @@ static struct win_syscall { LPOVERLAPPED))aSyscall[48].pCurrent) #endif -#if SQLITE_OS_WINCE || (!SQLITE_OS_WINRT && \ - (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0)) +#if SQLITE_OS_WINCE || !defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0 { "MapViewOfFile", (SYSCALL)MapViewOfFile, 0 }, #else { "MapViewOfFile", (SYSCALL)0, 0 }, @@ -41518,35 +49699,27 @@ static struct win_syscall { #define osSetEndOfFile ((BOOL(WINAPI*)(HANDLE))aSyscall[53].pCurrent) -#if !SQLITE_OS_WINRT { "SetFilePointer", (SYSCALL)SetFilePointer, 0 }, -#else - { "SetFilePointer", (SYSCALL)0, 0 }, -#endif #define osSetFilePointer ((DWORD(WINAPI*)(HANDLE,LONG,PLONG, \ DWORD))aSyscall[54].pCurrent) -#if !SQLITE_OS_WINRT { "Sleep", (SYSCALL)Sleep, 0 }, -#else - { "Sleep", (SYSCALL)0, 0 }, -#endif #define osSleep ((VOID(WINAPI*)(DWORD))aSyscall[55].pCurrent) { "SystemTimeToFileTime", (SYSCALL)SystemTimeToFileTime, 0 }, -#define osSystemTimeToFileTime ((BOOL(WINAPI*)(CONST SYSTEMTIME*, \ +#define osSystemTimeToFileTime ((BOOL(WINAPI*)(const SYSTEMTIME*, \ LPFILETIME))aSyscall[56].pCurrent) -#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT +#if !SQLITE_OS_WINCE { "UnlockFile", (SYSCALL)UnlockFile, 0 }, #else { "UnlockFile", (SYSCALL)0, 0 }, #endif -#ifndef osUnlockFile +#if !defined(osUnlockFile) && defined(SQLITE_WIN32_HAS_ANSI) #define osUnlockFile ((BOOL(WINAPI*)(HANDLE,DWORD,DWORD,DWORD, \ DWORD))aSyscall[57].pCurrent) #endif @@ -41578,23 +49751,16 @@ static struct win_syscall { #define osWriteFile ((BOOL(WINAPI*)(HANDLE,LPCVOID,DWORD,LPDWORD, \ LPOVERLAPPED))aSyscall[61].pCurrent) -#if SQLITE_OS_WINRT - { "CreateEventExW", (SYSCALL)CreateEventExW, 0 }, -#else - { "CreateEventExW", (SYSCALL)0, 0 }, -#endif - -#define osCreateEventExW ((HANDLE(WINAPI*)(LPSECURITY_ATTRIBUTES,LPCWSTR, \ - DWORD,DWORD))aSyscall[62].pCurrent) - -#if !SQLITE_OS_WINRT +/* +** For WaitForSingleObject(), MSDN says: +** +** Minimum supported client: Windows XP [desktop apps | UWP apps] +** Minimum supported server: Windows Server 2003 [desktop apps | UWP apps] +*/ { "WaitForSingleObject", (SYSCALL)WaitForSingleObject, 0 }, -#else - { "WaitForSingleObject", (SYSCALL)0, 0 }, -#endif #define osWaitForSingleObject ((DWORD(WINAPI*)(HANDLE, \ - DWORD))aSyscall[63].pCurrent) + DWORD))aSyscall[62].pCurrent) #if !SQLITE_OS_WINCE { "WaitForSingleObjectEx", (SYSCALL)WaitForSingleObjectEx, 0 }, @@ -41603,146 +49769,169 @@ static struct win_syscall { #endif #define osWaitForSingleObjectEx ((DWORD(WINAPI*)(HANDLE,DWORD, \ - BOOL))aSyscall[64].pCurrent) + BOOL))aSyscall[63].pCurrent) + + { "GetNativeSystemInfo", (SYSCALL)0, 0 }, + /* ^^^^^^^^^^^^^^^^^^^----------------^------- placeholder only */ -#if SQLITE_OS_WINRT - { "SetFilePointerEx", (SYSCALL)SetFilePointerEx, 0 }, +#if defined(SQLITE_WIN32_HAS_ANSI) + { "OutputDebugStringA", (SYSCALL)OutputDebugStringA, 0 }, #else - { "SetFilePointerEx", (SYSCALL)0, 0 }, + { "OutputDebugStringA", (SYSCALL)0, 0 }, #endif -#define osSetFilePointerEx ((BOOL(WINAPI*)(HANDLE,LARGE_INTEGER, \ - PLARGE_INTEGER,DWORD))aSyscall[65].pCurrent) +#define osOutputDebugStringA ((VOID(WINAPI*)(LPCSTR))aSyscall[65].pCurrent) -#if SQLITE_OS_WINRT - { "GetFileInformationByHandleEx", (SYSCALL)GetFileInformationByHandleEx, 0 }, +#if defined(SQLITE_WIN32_HAS_WIDE) + { "OutputDebugStringW", (SYSCALL)OutputDebugStringW, 0 }, #else - { "GetFileInformationByHandleEx", (SYSCALL)0, 0 }, + { "OutputDebugStringW", (SYSCALL)0, 0 }, #endif -#define osGetFileInformationByHandleEx ((BOOL(WINAPI*)(HANDLE, \ - FILE_INFO_BY_HANDLE_CLASS,LPVOID,DWORD))aSyscall[66].pCurrent) +#define osOutputDebugStringW ((VOID(WINAPI*)(LPCWSTR))aSyscall[66].pCurrent) + + { "GetProcessHeap", (SYSCALL)GetProcessHeap, 0 }, + +#define osGetProcessHeap ((HANDLE(WINAPI*)(VOID))aSyscall[67].pCurrent) + +/* +** NOTE: On some sub-platforms, the InterlockedCompareExchange "function" +** is really just a macro that uses a compiler intrinsic (e.g. x64). +** So do not try to make this is into a redefinable interface. +*/ +#if defined(InterlockedCompareExchange) + { "InterlockedCompareExchange", (SYSCALL)0, 0 }, -#if SQLITE_OS_WINRT && (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0) - { "MapViewOfFileFromApp", (SYSCALL)MapViewOfFileFromApp, 0 }, +#define osInterlockedCompareExchange InterlockedCompareExchange #else - { "MapViewOfFileFromApp", (SYSCALL)0, 0 }, -#endif + { "InterlockedCompareExchange", (SYSCALL)InterlockedCompareExchange, 0 }, -#define osMapViewOfFileFromApp ((LPVOID(WINAPI*)(HANDLE,ULONG,ULONG64, \ - SIZE_T))aSyscall[67].pCurrent) +#define osInterlockedCompareExchange ((LONG(WINAPI*)(LONG \ + SQLITE_WIN32_VOLATILE*, LONG,LONG))aSyscall[68].pCurrent) +#endif /* defined(InterlockedCompareExchange) */ -#if SQLITE_OS_WINRT - { "CreateFile2", (SYSCALL)CreateFile2, 0 }, +#if !SQLITE_OS_WINCE && SQLITE_WIN32_USE_UUID + { "UuidCreate", (SYSCALL)UuidCreate, 0 }, #else - { "CreateFile2", (SYSCALL)0, 0 }, + { "UuidCreate", (SYSCALL)0, 0 }, #endif -#define osCreateFile2 ((HANDLE(WINAPI*)(LPCWSTR,DWORD,DWORD,DWORD, \ - LPCREATEFILE2_EXTENDED_PARAMETERS))aSyscall[68].pCurrent) +#define osUuidCreate ((RPC_STATUS(RPC_ENTRY*)(UUID*))aSyscall[69].pCurrent) -#if SQLITE_OS_WINRT && !defined(SQLITE_OMIT_LOAD_EXTENSION) - { "LoadPackagedLibrary", (SYSCALL)LoadPackagedLibrary, 0 }, +#if !SQLITE_OS_WINCE && SQLITE_WIN32_USE_UUID + { "UuidCreateSequential", (SYSCALL)UuidCreateSequential, 0 }, #else - { "LoadPackagedLibrary", (SYSCALL)0, 0 }, + { "UuidCreateSequential", (SYSCALL)0, 0 }, #endif -#define osLoadPackagedLibrary ((HMODULE(WINAPI*)(LPCWSTR, \ - DWORD))aSyscall[69].pCurrent) +#define osUuidCreateSequential \ + ((RPC_STATUS(RPC_ENTRY*)(UUID*))aSyscall[70].pCurrent) -#if SQLITE_OS_WINRT - { "GetTickCount64", (SYSCALL)GetTickCount64, 0 }, +#if !defined(SQLITE_NO_SYNC) && SQLITE_MAX_MMAP_SIZE>0 + { "FlushViewOfFile", (SYSCALL)FlushViewOfFile, 0 }, #else - { "GetTickCount64", (SYSCALL)0, 0 }, + { "FlushViewOfFile", (SYSCALL)0, 0 }, #endif -#define osGetTickCount64 ((ULONGLONG(WINAPI*)(VOID))aSyscall[70].pCurrent) +#define osFlushViewOfFile \ + ((BOOL(WINAPI*)(LPCVOID,SIZE_T))aSyscall[71].pCurrent) -#if SQLITE_OS_WINRT - { "GetNativeSystemInfo", (SYSCALL)GetNativeSystemInfo, 0 }, +/* +** If SQLITE_ENABLE_SETLK_TIMEOUT is defined, we require CreateEvent() +** to implement blocking locks with timeouts. MSDN says: +** +** Minimum supported client: Windows XP [desktop apps | UWP apps] +** Minimum supported server: Windows Server 2003 [desktop apps | UWP apps] +*/ +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + { "CreateEvent", (SYSCALL)CreateEvent, 0 }, #else - { "GetNativeSystemInfo", (SYSCALL)0, 0 }, + { "CreateEvent", (SYSCALL)0, 0 }, #endif -#define osGetNativeSystemInfo ((VOID(WINAPI*)( \ - LPSYSTEM_INFO))aSyscall[71].pCurrent) +#define osCreateEvent ( \ + (HANDLE(WINAPI*) (LPSECURITY_ATTRIBUTES,BOOL,BOOL,LPCSTR)) \ + aSyscall[72].pCurrent \ +) -#if defined(SQLITE_WIN32_HAS_ANSI) - { "OutputDebugStringA", (SYSCALL)OutputDebugStringA, 0 }, +/* +** If SQLITE_ENABLE_SETLK_TIMEOUT is defined, we require CancelIo() +** for the case where a timeout expires and a lock request must be +** cancelled. +** +** Minimum supported client: Windows XP [desktop apps | UWP apps] +** Minimum supported server: Windows Server 2003 [desktop apps | UWP apps] +*/ +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + { "CancelIo", (SYSCALL)CancelIo, 0 }, #else - { "OutputDebugStringA", (SYSCALL)0, 0 }, + { "CancelIo", (SYSCALL)0, 0 }, #endif -#define osOutputDebugStringA ((VOID(WINAPI*)(LPCSTR))aSyscall[72].pCurrent) +#define osCancelIo ((BOOL(WINAPI*)(HANDLE))aSyscall[73].pCurrent) -#if defined(SQLITE_WIN32_HAS_WIDE) - { "OutputDebugStringW", (SYSCALL)OutputDebugStringW, 0 }, +#if defined(SQLITE_WIN32_HAS_WIDE) && defined(_WIN32) + { "GetModuleHandleW", (SYSCALL)GetModuleHandleW, 0 }, #else - { "OutputDebugStringW", (SYSCALL)0, 0 }, + { "GetModuleHandleW", (SYSCALL)0, 0 }, #endif -#define osOutputDebugStringW ((VOID(WINAPI*)(LPCWSTR))aSyscall[73].pCurrent) +#define osGetModuleHandleW ((HMODULE(WINAPI*)(LPCWSTR))aSyscall[74].pCurrent) - { "GetProcessHeap", (SYSCALL)GetProcessHeap, 0 }, +#ifndef _WIN32 + { "getenv", (SYSCALL)getenv, 0 }, +#else + { "getenv", (SYSCALL)0, 0 }, +#endif -#define osGetProcessHeap ((HANDLE(WINAPI*)(VOID))aSyscall[74].pCurrent) +#define osGetenv ((const char *(*)(const char *))aSyscall[75].pCurrent) -#if SQLITE_OS_WINRT && (!defined(SQLITE_OMIT_WAL) || SQLITE_MAX_MMAP_SIZE>0) - { "CreateFileMappingFromApp", (SYSCALL)CreateFileMappingFromApp, 0 }, +#ifndef _WIN32 + { "getcwd", (SYSCALL)getcwd, 0 }, #else - { "CreateFileMappingFromApp", (SYSCALL)0, 0 }, + { "getcwd", (SYSCALL)0, 0 }, #endif -#define osCreateFileMappingFromApp ((HANDLE(WINAPI*)(HANDLE, \ - LPSECURITY_ATTRIBUTES,ULONG,ULONG64,LPCWSTR))aSyscall[75].pCurrent) - -/* -** NOTE: On some sub-platforms, the InterlockedCompareExchange "function" -** is really just a macro that uses a compiler intrinsic (e.g. x64). -** So do not try to make this is into a redefinable interface. -*/ -#if defined(InterlockedCompareExchange) - { "InterlockedCompareExchange", (SYSCALL)0, 0 }, +#define osGetcwd ((char*(*)(char*,size_t))aSyscall[76].pCurrent) -#define osInterlockedCompareExchange InterlockedCompareExchange +#ifndef _WIN32 + { "readlink", (SYSCALL)readlink, 0 }, #else - { "InterlockedCompareExchange", (SYSCALL)InterlockedCompareExchange, 0 }, + { "readlink", (SYSCALL)0, 0 }, +#endif -#define osInterlockedCompareExchange ((LONG(WINAPI*)(LONG \ - SQLITE_WIN32_VOLATILE*, LONG,LONG))aSyscall[76].pCurrent) -#endif /* defined(InterlockedCompareExchange) */ +#define osReadlink ((ssize_t(*)(const char*,char*,size_t))aSyscall[77].pCurrent) -#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && SQLITE_WIN32_USE_UUID - { "UuidCreate", (SYSCALL)UuidCreate, 0 }, +#ifndef _WIN32 + { "lstat", (SYSCALL)lstat, 0 }, #else - { "UuidCreate", (SYSCALL)0, 0 }, + { "lstat", (SYSCALL)0, 0 }, #endif -#define osUuidCreate ((RPC_STATUS(RPC_ENTRY*)(UUID*))aSyscall[77].pCurrent) +#define osLstat ((int(*)(const char*,struct stat*))aSyscall[78].pCurrent) -#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && SQLITE_WIN32_USE_UUID - { "UuidCreateSequential", (SYSCALL)UuidCreateSequential, 0 }, +#ifndef _WIN32 + { "__errno", (SYSCALL)__errno, 0 }, #else - { "UuidCreateSequential", (SYSCALL)0, 0 }, + { "__errno", (SYSCALL)0, 0 }, #endif -#define osUuidCreateSequential \ - ((RPC_STATUS(RPC_ENTRY*)(UUID*))aSyscall[78].pCurrent) +#define osErrno (*((int*(*)(void))aSyscall[79].pCurrent)()) -#if !defined(SQLITE_NO_SYNC) && SQLITE_MAX_MMAP_SIZE>0 - { "FlushViewOfFile", (SYSCALL)FlushViewOfFile, 0 }, +#ifndef _WIN32 + { "cygwin_conv_path", (SYSCALL)cygwin_conv_path, 0 }, #else - { "FlushViewOfFile", (SYSCALL)0, 0 }, + { "cygwin_conv_path", (SYSCALL)0, 0 }, #endif -#define osFlushViewOfFile \ - ((BOOL(WINAPI*)(LPCVOID,SIZE_T))aSyscall[79].pCurrent) +#define osCygwin_conv_path ((size_t(*)(unsigned int, \ + const void *, void *, size_t))aSyscall[80].pCurrent) }; /* End of the overrideable system calls */ /* ** This is the xSetSystemCall() method of sqlite3_vfs for all of the -** "win32" VFSes. Return SQLITE_OK opon successfully updating the +** "win32" VFSes. Return SQLITE_OK upon successfully updating the ** system call pointer, or SQLITE_NOTFOUND if there is no configurable ** system call named zName. */ @@ -41840,10 +50029,10 @@ SQLITE_API int sqlite3_win32_compact_heap(LPUINT pnLargest){ hHeap = winMemGetHeap(); assert( hHeap!=0 ); assert( hHeap!=INVALID_HANDLE_VALUE ); -#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_MALLOC_VALIDATE) +#if defined(SQLITE_WIN32_MALLOC_VALIDATE) assert( osHeapValidate(hHeap, SQLITE_WIN32_HEAP_FLAGS, NULL) ); #endif -#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT +#if !SQLITE_OS_WINCE if( (nLargest=osHeapCompact(hHeap, SQLITE_WIN32_HEAP_FLAGS))==0 ){ DWORD lastErrno = osGetLastError(); if( lastErrno==NO_ERROR ){ @@ -41873,17 +50062,17 @@ SQLITE_API int sqlite3_win32_compact_heap(LPUINT pnLargest){ */ SQLITE_API int sqlite3_win32_reset_heap(){ int rc; - MUTEX_LOGIC( sqlite3_mutex *pMaster; ) /* The main static mutex */ + MUTEX_LOGIC( sqlite3_mutex *pMainMtx; ) /* The main static mutex */ MUTEX_LOGIC( sqlite3_mutex *pMem; ) /* The memsys static mutex */ - MUTEX_LOGIC( pMaster = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); ) + MUTEX_LOGIC( pMainMtx = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MAIN); ) MUTEX_LOGIC( pMem = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MEM); ) - sqlite3_mutex_enter(pMaster); + sqlite3_mutex_enter(pMainMtx); sqlite3_mutex_enter(pMem); winMemAssertMagic(); if( winMemGetHeap()!=NULL && winMemGetOwned() && sqlite3_memory_used()==0 ){ /* ** At this point, there should be no outstanding memory allocations on - ** the heap. Also, since both the master and memsys locks are currently + ** the heap. Also, since both the main and memsys locks are currently ** being held by us, no other function (i.e. from another thread) should ** be able to even access the heap. Attempt to destroy and recreate our ** isolated Win32 native heap now. @@ -41906,11 +50095,12 @@ SQLITE_API int sqlite3_win32_reset_heap(){ rc = SQLITE_BUSY; } sqlite3_mutex_leave(pMem); - sqlite3_mutex_leave(pMaster); + sqlite3_mutex_leave(pMainMtx); return rc; } #endif /* SQLITE_WIN32_MALLOC */ +#ifdef _WIN32 /* ** This function outputs the specified (ANSI) string to the Win32 debugger ** (if available). @@ -41953,29 +50143,13 @@ SQLITE_API void sqlite3_win32_write_debug(const char *zBuf, int nBuf){ } #endif } - -/* -** The following routine suspends the current thread for at least ms -** milliseconds. This is equivalent to the Win32 Sleep() interface. -*/ -#if SQLITE_OS_WINRT -static HANDLE sleepObj = NULL; -#endif +#endif /* _WIN32 */ SQLITE_API void sqlite3_win32_sleep(DWORD milliseconds){ -#if SQLITE_OS_WINRT - if ( sleepObj==NULL ){ - sleepObj = osCreateEventExW(NULL, NULL, CREATE_EVENT_MANUAL_RESET, - SYNCHRONIZE); - } - assert( sleepObj!=NULL ); - osWaitForSingleObjectEx(sleepObj, milliseconds, FALSE); -#else osSleep(milliseconds); -#endif } -#if SQLITE_MAX_WORKER_THREADS>0 && !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && \ +#if SQLITE_MAX_WORKER_THREADS>0 && !SQLITE_OS_WINCE && \ SQLITE_THREADSAFE>0 SQLITE_PRIVATE DWORD sqlite3Win32Wait(HANDLE hObject){ DWORD rc; @@ -41999,7 +50173,7 @@ SQLITE_PRIVATE DWORD sqlite3Win32Wait(HANDLE hObject){ #if !SQLITE_WIN32_GETVERSIONEX # define osIsNT() (1) -#elif SQLITE_OS_WINCE || SQLITE_OS_WINRT || !defined(SQLITE_WIN32_HAS_ANSI) +#elif SQLITE_OS_WINCE || !defined(SQLITE_WIN32_HAS_ANSI) # define osIsNT() (1) #elif !defined(SQLITE_WIN32_HAS_WIDE) # define osIsNT() (0) @@ -42012,13 +50186,7 @@ SQLITE_PRIVATE DWORD sqlite3Win32Wait(HANDLE hObject){ ** based on the NT kernel. */ SQLITE_API int sqlite3_win32_is_nt(void){ -#if SQLITE_OS_WINRT - /* - ** NOTE: The WinRT sub-platform is always assumed to be based on the NT - ** kernel. - */ - return 1; -#elif SQLITE_WIN32_GETVERSIONEX +#if SQLITE_WIN32_GETVERSIONEX if( osInterlockedCompareExchange(&sqlite3_os_type, 0, 0)==0 ){ #if defined(SQLITE_WIN32_HAS_ANSI) OSVERSIONINFOA sInfo; @@ -42036,7 +50204,9 @@ SQLITE_API int sqlite3_win32_is_nt(void){ } return osInterlockedCompareExchange(&sqlite3_os_type, 2, 2)==2; #elif SQLITE_TEST - return osInterlockedCompareExchange(&sqlite3_os_type, 2, 2)==2; + return osInterlockedCompareExchange(&sqlite3_os_type, 2, 2)==2 + || osInterlockedCompareExchange(&sqlite3_os_type, 0, 0)==0 + ; #else /* ** NOTE: All sub-platforms where the GetVersionEx[AW] functions are @@ -42058,7 +50228,7 @@ static void *winMemMalloc(int nBytes){ hHeap = winMemGetHeap(); assert( hHeap!=0 ); assert( hHeap!=INVALID_HANDLE_VALUE ); -#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_MALLOC_VALIDATE) +#if defined(SQLITE_WIN32_MALLOC_VALIDATE) assert( osHeapValidate(hHeap, SQLITE_WIN32_HEAP_FLAGS, NULL) ); #endif assert( nBytes>=0 ); @@ -42080,7 +50250,7 @@ static void winMemFree(void *pPrior){ hHeap = winMemGetHeap(); assert( hHeap!=0 ); assert( hHeap!=INVALID_HANDLE_VALUE ); -#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_MALLOC_VALIDATE) +#if defined(SQLITE_WIN32_MALLOC_VALIDATE) assert( osHeapValidate(hHeap, SQLITE_WIN32_HEAP_FLAGS, pPrior) ); #endif if( !pPrior ) return; /* Passing NULL to HeapFree is undefined. */ @@ -42101,7 +50271,7 @@ static void *winMemRealloc(void *pPrior, int nBytes){ hHeap = winMemGetHeap(); assert( hHeap!=0 ); assert( hHeap!=INVALID_HANDLE_VALUE ); -#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_MALLOC_VALIDATE) +#if defined(SQLITE_WIN32_MALLOC_VALIDATE) assert( osHeapValidate(hHeap, SQLITE_WIN32_HEAP_FLAGS, pPrior) ); #endif assert( nBytes>=0 ); @@ -42129,7 +50299,7 @@ static int winMemSize(void *p){ hHeap = winMemGetHeap(); assert( hHeap!=0 ); assert( hHeap!=INVALID_HANDLE_VALUE ); -#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_MALLOC_VALIDATE) +#if defined(SQLITE_WIN32_MALLOC_VALIDATE) assert( osHeapValidate(hHeap, SQLITE_WIN32_HEAP_FLAGS, p) ); #endif if( !p ) return 0; @@ -42159,7 +50329,7 @@ static int winMemInit(void *pAppData){ assert( pWinMemData->magic1==WINMEM_MAGIC1 ); assert( pWinMemData->magic2==WINMEM_MAGIC2 ); -#if !SQLITE_OS_WINRT && SQLITE_WIN32_HEAP_CREATE +#if SQLITE_WIN32_HEAP_CREATE if( !pWinMemData->hHeap ){ DWORD dwInitialSize = SQLITE_WIN32_HEAP_INIT_SIZE; DWORD dwMaximumSize = (DWORD)sqlite3GlobalConfig.nHeap; @@ -42192,7 +50362,7 @@ static int winMemInit(void *pAppData){ #endif assert( pWinMemData->hHeap!=0 ); assert( pWinMemData->hHeap!=INVALID_HANDLE_VALUE ); -#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_MALLOC_VALIDATE) +#if defined(SQLITE_WIN32_MALLOC_VALIDATE) assert( osHeapValidate(pWinMemData->hHeap, SQLITE_WIN32_HEAP_FLAGS, NULL) ); #endif return SQLITE_OK; @@ -42210,7 +50380,7 @@ static void winMemShutdown(void *pAppData){ if( pWinMemData->hHeap ){ assert( pWinMemData->hHeap!=INVALID_HANDLE_VALUE ); -#if !SQLITE_OS_WINRT && defined(SQLITE_WIN32_MALLOC_VALIDATE) +#if defined(SQLITE_WIN32_MALLOC_VALIDATE) assert( osHeapValidate(pWinMemData->hHeap, SQLITE_WIN32_HEAP_FLAGS, NULL) ); #endif if( pWinMemData->bOwned ){ @@ -42251,6 +50421,7 @@ SQLITE_PRIVATE void sqlite3MemSetDefault(void){ } #endif /* SQLITE_WIN32_MALLOC */ +#ifdef _WIN32 /* ** Convert a UTF-8 string to Microsoft Unicode. ** @@ -42276,6 +50447,7 @@ static LPWSTR winUtf8ToUnicode(const char *zText){ } return zWideText; } +#endif /* _WIN32 */ /* ** Convert a Microsoft Unicode string to UTF-8. @@ -42310,28 +50482,29 @@ static char *winUnicodeToUtf8(LPCWSTR zWideText){ ** Space to hold the returned string is obtained from sqlite3_malloc(). */ static LPWSTR winMbcsToUnicode(const char *zText, int useAnsi){ - int nByte; + int nWideChar; LPWSTR zMbcsText; int codepage = useAnsi ? CP_ACP : CP_OEMCP; - nByte = osMultiByteToWideChar(codepage, 0, zText, -1, NULL, - 0)*sizeof(WCHAR); - if( nByte==0 ){ + nWideChar = osMultiByteToWideChar(codepage, 0, zText, -1, NULL, + 0); + if( nWideChar==0 ){ return 0; } - zMbcsText = sqlite3MallocZero( nByte*sizeof(WCHAR) ); + zMbcsText = sqlite3MallocZero( nWideChar*sizeof(WCHAR) ); if( zMbcsText==0 ){ return 0; } - nByte = osMultiByteToWideChar(codepage, 0, zText, -1, zMbcsText, - nByte); - if( nByte==0 ){ + nWideChar = osMultiByteToWideChar(codepage, 0, zText, -1, zMbcsText, + nWideChar); + if( nWideChar==0 ){ sqlite3_free(zMbcsText); zMbcsText = 0; } return zMbcsText; } +#ifdef _WIN32 /* ** Convert a Microsoft Unicode string to a multi-byte character string, ** using the ANSI or OEM code page. @@ -42359,6 +50532,7 @@ static char *winUnicodeToMbcs(LPCWSTR zWideText, int useAnsi){ } return zText; } +#endif /* _WIN32 */ /* ** Convert a multi-byte character string to UTF-8. @@ -42378,6 +50552,7 @@ static char *winMbcsToUtf8(const char *zText, int useAnsi){ return zTextUtf8; } +#ifdef _WIN32 /* ** Convert a UTF-8 string to a multi-byte character string. ** @@ -42427,6 +50602,7 @@ SQLITE_API char *sqlite3_win32_unicode_to_utf8(LPCWSTR zWideText){ #endif return winUnicodeToUtf8(zWideText); } +#endif /* _WIN32 */ /* ** This is a public wrapper for the winMbcsToUtf8() function. @@ -42444,6 +50620,7 @@ SQLITE_API char *sqlite3_win32_mbcs_to_utf8(const char *zText){ return winMbcsToUtf8(zText, osAreFileApisANSI()); } +#ifdef _WIN32 /* ** This is a public wrapper for the winMbcsToUtf8() function. */ @@ -42501,10 +50678,12 @@ SQLITE_API int sqlite3_win32_set_directory8( const char *zValue /* New value for directory being set or reset */ ){ char **ppDirectory = 0; + int rc; #ifndef SQLITE_OMIT_AUTOINIT - int rc = sqlite3_initialize(); + rc = sqlite3_initialize(); if( rc ) return rc; #endif + sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); if( type==SQLITE_WIN32_DATA_DIRECTORY_TYPE ){ ppDirectory = &sqlite3_data_directory; }else if( type==SQLITE_WIN32_TEMP_DIRECTORY_TYPE ){ @@ -42519,14 +50698,19 @@ SQLITE_API int sqlite3_win32_set_directory8( if( zValue && zValue[0] ){ zCopy = sqlite3_mprintf("%s", zValue); if ( zCopy==0 ){ - return SQLITE_NOMEM_BKPT; + rc = SQLITE_NOMEM_BKPT; + goto set_directory8_done; } } sqlite3_free(*ppDirectory); *ppDirectory = zCopy; - return SQLITE_OK; + rc = SQLITE_OK; + }else{ + rc = SQLITE_ERROR; } - return SQLITE_ERROR; +set_directory8_done: + sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); + return rc; } /* @@ -42561,6 +50745,7 @@ SQLITE_API int sqlite3_win32_set_directory( ){ return sqlite3_win32_set_directory16(type, zValue); } +#endif /* _WIN32 */ /* ** The return value of winGetLastErrorMsg @@ -42576,17 +50761,6 @@ static int winGetLastErrorMsg(DWORD lastErrno, int nBuf, char *zBuf){ char *zOut = 0; if( osIsNT() ){ -#if SQLITE_OS_WINRT - WCHAR zTempWide[SQLITE_WIN32_MAX_ERRMSG_CHARS+1]; - dwLen = osFormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, - lastErrno, - 0, - zTempWide, - SQLITE_WIN32_MAX_ERRMSG_CHARS, - 0); -#else LPWSTR zTempWide = NULL; dwLen = osFormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | @@ -42597,16 +50771,13 @@ static int winGetLastErrorMsg(DWORD lastErrno, int nBuf, char *zBuf){ (LPWSTR) &zTempWide, 0, 0); -#endif if( dwLen > 0 ){ /* allocate a buffer and convert to UTF8 */ sqlite3BeginBenignMalloc(); zOut = winUnicodeToUtf8(zTempWide); sqlite3EndBenignMalloc(); -#if !SQLITE_OS_WINRT /* free the system buffer allocated by FormatMessage */ osLocalFree(zTempWide); -#endif } } #ifdef SQLITE_WIN32_HAS_ANSI @@ -43109,13 +51280,100 @@ static BOOL winLockFile( ovlp.Offset = offsetLow; ovlp.OffsetHigh = offsetHigh; return osLockFileEx(*phFile, flags, 0, numBytesLow, numBytesHigh, &ovlp); +#ifdef SQLITE_WIN32_HAS_ANSI }else{ return osLockFile(*phFile, offsetLow, offsetHigh, numBytesLow, numBytesHigh); +#endif } #endif } +#ifndef SQLITE_OMIT_WAL +/* +** Lock a region of nByte bytes starting at offset offset of file hFile. +** Take an EXCLUSIVE lock if parameter bExclusive is true, or a SHARED lock +** otherwise. If nMs is greater than zero and the lock cannot be obtained +** immediately, block for that many ms before giving up. +** +** This function returns SQLITE_OK if the lock is obtained successfully. If +** some other process holds the lock, SQLITE_BUSY is returned if nMs==0, or +** SQLITE_BUSY_TIMEOUT otherwise. Or, if an error occurs, SQLITE_IOERR. +*/ +static int winHandleLockTimeout( + HANDLE hFile, + DWORD offset, + DWORD nByte, + int bExcl, + DWORD nMs +){ + DWORD flags = LOCKFILE_FAIL_IMMEDIATELY | (bExcl?LOCKFILE_EXCLUSIVE_LOCK:0); + int rc = SQLITE_OK; + BOOL ret; + + if( !osIsNT() ){ + ret = winLockFile(&hFile, flags, offset, 0, nByte, 0); + }else{ + OVERLAPPED ovlp; + memset(&ovlp, 0, sizeof(OVERLAPPED)); + ovlp.Offset = offset; + +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + if( nMs!=0 ){ + flags &= ~LOCKFILE_FAIL_IMMEDIATELY; + } + ovlp.hEvent = osCreateEvent(NULL, TRUE, FALSE, NULL); + if( ovlp.hEvent==NULL ){ + return SQLITE_IOERR_LOCK; + } +#endif + + ret = osLockFileEx(hFile, flags, 0, nByte, 0, &ovlp); + +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + /* If SQLITE_ENABLE_SETLK_TIMEOUT is defined, then the file-handle was + ** opened with FILE_FLAG_OVERHEAD specified. In this case, the call to + ** LockFileEx() may fail because the request is still pending. This can + ** happen even if LOCKFILE_FAIL_IMMEDIATELY was specified. + ** + ** If nMs is 0, then LOCKFILE_FAIL_IMMEDIATELY was set in the flags + ** passed to LockFileEx(). In this case, if the operation is pending, + ** block indefinitely until it is finished. + ** + ** Otherwise, wait for up to nMs ms for the operation to finish. nMs + ** may be set to INFINITE. + */ + if( !ret && GetLastError()==ERROR_IO_PENDING ){ + DWORD nDelay = (nMs==0 ? INFINITE : nMs); + DWORD res = osWaitForSingleObject(ovlp.hEvent, nDelay); + if( res==WAIT_OBJECT_0 ){ + ret = TRUE; + }else if( res==WAIT_TIMEOUT ){ +#if SQLITE_ENABLE_SETLK_TIMEOUT==1 + rc = SQLITE_BUSY_TIMEOUT; +#else + rc = SQLITE_BUSY; +#endif + }else{ + /* Some other error has occurred */ + rc = SQLITE_IOERR_LOCK; + } + + /* If it is still pending, cancel the LockFileEx() call. */ + osCancelIo(hFile); + } + + osCloseHandle(ovlp.hEvent); +#endif + } + + if( rc==SQLITE_OK && !ret ){ + rc = SQLITE_BUSY; + } + return rc; +} +#endif /* #ifndef SQLITE_OMIT_WAL */ + /* ** Unlock a file region. */ @@ -43140,13 +51398,25 @@ static BOOL winUnlockFile( ovlp.Offset = offsetLow; ovlp.OffsetHigh = offsetHigh; return osUnlockFileEx(*phFile, 0, numBytesLow, numBytesHigh, &ovlp); +#ifdef SQLITE_WIN32_HAS_ANSI }else{ return osUnlockFile(*phFile, offsetLow, offsetHigh, numBytesLow, numBytesHigh); +#endif } #endif } +#ifndef SQLITE_OMIT_WAL +/* +** Remove an nByte lock starting at offset iOff from HANDLE h. +*/ +static int winHandleUnlock(HANDLE h, int iOff, int nByte){ + BOOL ret = winUnlockFile(&h, iOff, 0, nByte, 0); + return (ret ? SQLITE_OK : SQLITE_IOERR_UNLOCK); +} +#endif + /***************************************************************************** ** The next group of routines implement the I/O methods specified ** by the sqlite3_io_methods object. @@ -43160,66 +51430,56 @@ static BOOL winUnlockFile( #endif /* -** Move the current position of the file handle passed as the first -** argument to offset iOffset within the file. If successful, return 0. -** Otherwise, set pFile->lastErrno and return non-zero. +** Seek the file handle h to offset nByte of the file. +** +** If successful, return SQLITE_OK. Or, if an error occurs, return an SQLite +** error code. */ -static int winSeekFile(winFile *pFile, sqlite3_int64 iOffset){ -#if !SQLITE_OS_WINRT +static int winHandleSeek(HANDLE h, sqlite3_int64 iOffset){ + int rc = SQLITE_OK; /* Return value */ + LONG upperBits; /* Most sig. 32 bits of new offset */ LONG lowerBits; /* Least sig. 32 bits of new offset */ DWORD dwRet; /* Value returned by SetFilePointer() */ - DWORD lastErrno; /* Value returned by GetLastError() */ - - OSTRACE(("SEEK file=%p, offset=%lld\n", pFile->h, iOffset)); upperBits = (LONG)((iOffset>>32) & 0x7fffffff); lowerBits = (LONG)(iOffset & 0xffffffff); + dwRet = osSetFilePointer(h, lowerBits, &upperBits, FILE_BEGIN); + /* API oddity: If successful, SetFilePointer() returns a dword ** containing the lower 32-bits of the new file-offset. Or, if it fails, ** it returns INVALID_SET_FILE_POINTER. However according to MSDN, ** INVALID_SET_FILE_POINTER may also be a valid new offset. So to determine ** whether an error has actually occurred, it is also necessary to call - ** GetLastError(). - */ - dwRet = osSetFilePointer(pFile->h, lowerBits, &upperBits, FILE_BEGIN); - - if( (dwRet==INVALID_SET_FILE_POINTER - && ((lastErrno = osGetLastError())!=NO_ERROR)) ){ - pFile->lastErrno = lastErrno; - winLogError(SQLITE_IOERR_SEEK, pFile->lastErrno, - "winSeekFile", pFile->zPath); - OSTRACE(("SEEK file=%p, rc=SQLITE_IOERR_SEEK\n", pFile->h)); - return 1; + ** GetLastError(). */ + if( dwRet==INVALID_SET_FILE_POINTER ){ + DWORD lastErrno = osGetLastError(); + if( lastErrno!=NO_ERROR ){ + rc = SQLITE_IOERR_SEEK; + } } + OSTRACE(("SEEK file=%p, offset=%lld rc=%s\n", h, iOffset,sqlite3ErrName(rc))); + return rc; +} - OSTRACE(("SEEK file=%p, rc=SQLITE_OK\n", pFile->h)); - return 0; -#else - /* - ** Same as above, except that this implementation works for WinRT. - */ - - LARGE_INTEGER x; /* The new offset */ - BOOL bRet; /* Value returned by SetFilePointerEx() */ - - x.QuadPart = iOffset; - bRet = osSetFilePointerEx(pFile->h, x, 0, FILE_BEGIN); +/* +** Move the current position of the file handle passed as the first +** argument to offset iOffset within the file. If successful, return 0. +** Otherwise, set pFile->lastErrno and return non-zero. +*/ +static int winSeekFile(winFile *pFile, sqlite3_int64 iOffset){ + int rc; - if(!bRet){ + rc = winHandleSeek(pFile->h, iOffset); + if( rc!=SQLITE_OK ){ pFile->lastErrno = osGetLastError(); - winLogError(SQLITE_IOERR_SEEK, pFile->lastErrno, - "winSeekFile", pFile->zPath); - OSTRACE(("SEEK file=%p, rc=SQLITE_IOERR_SEEK\n", pFile->h)); - return 1; + winLogError(rc, pFile->lastErrno, "winSeekFile", pFile->zPath); } - - OSTRACE(("SEEK file=%p, rc=SQLITE_OK\n", pFile->h)); - return 0; -#endif + return rc; } + #if SQLITE_MAX_MMAP_SIZE>0 /* Forward references to VFS helper methods used for memory mapped files */ static int winMapfile(winFile*, sqlite3_int64); @@ -43315,7 +51575,7 @@ static int winRead( pFile->h, pBuf, amt, offset, pFile->locktype)); #if SQLITE_MAX_MMAP_SIZE>0 - /* Deal with as much of this read request as possible by transfering + /* Deal with as much of this read request as possible by transferring ** data from the memory mapping using memcpy(). */ if( offsetmmapSize ){ if( offset+amt <= pFile->mmapSize ){ @@ -43393,7 +51653,7 @@ static int winWrite( pFile->h, pBuf, amt, offset, pFile->locktype)); #if defined(SQLITE_MMAP_READWRITE) && SQLITE_MAX_MMAP_SIZE>0 - /* Deal with as much of this write request as possible by transfering + /* Deal with as much of this write request as possible by transferring ** data from the memory mapping using memcpy(). */ if( offsetmmapSize ){ if( offset+amt <= pFile->mmapSize ){ @@ -43479,6 +51739,49 @@ static int winWrite( return SQLITE_OK; } +#ifndef SQLITE_OMIT_WAL +/* +** Truncate the file opened by handle h to nByte bytes in size. +*/ +static int winHandleTruncate(HANDLE h, sqlite3_int64 nByte){ + int rc = SQLITE_OK; /* Return code */ + rc = winHandleSeek(h, nByte); + if( rc==SQLITE_OK ){ + if( 0==osSetEndOfFile(h) ){ + rc = SQLITE_IOERR_TRUNCATE; + } + } + return rc; +} + +/* +** Determine the size in bytes of the file opened by the handle passed as +** the first argument. +*/ +static int winHandleSize(HANDLE h, sqlite3_int64 *pnByte){ + int rc = SQLITE_OK; + DWORD upperBits = 0; + DWORD lowerBits = 0; + + assert( pnByte ); + lowerBits = osGetFileSize(h, &upperBits); + *pnByte = (((sqlite3_int64)upperBits)<<32) + lowerBits; + if( lowerBits==INVALID_FILE_SIZE && osGetLastError()!=NO_ERROR ){ + rc = SQLITE_IOERR_FSTAT; + } + return rc; +} + +/* +** Close the handle passed as the only argument. +*/ +static void winHandleClose(HANDLE h){ + if( h!=INVALID_HANDLE_VALUE ){ + osCloseHandle(h); + } +} +#endif /* #ifndef SQLITE_OMIT_WAL */ + /* ** Truncate an open file to a specified size */ @@ -43503,7 +51806,7 @@ static int winTruncate(sqlite3_file *id, sqlite3_int64 nByte){ ** all references to memory-mapped content are closed. That is doable, ** but involves adding a few branches in the common write code path which ** could slow down normal operations slightly. Hence, we have decided for - ** now to simply make trancations a no-op if there are pending reads. We + ** now to simply make transactions a no-op if there are pending reads. We ** can maybe revisit this decision in the future. */ return SQLITE_OK; @@ -43562,7 +51865,7 @@ static int winTruncate(sqlite3_file *id, sqlite3_int64 nByte){ #ifdef SQLITE_TEST /* ** Count the number of fullsyncs and normal syncs. This is used to test -** that syncs and fullsyncs are occuring at the right times. +** that syncs and fullsyncs are occurring at the right times. */ SQLITE_API int sqlite3_sync_count = 0; SQLITE_API int sqlite3_fullsync_count = 0; @@ -43664,20 +51967,6 @@ static int winFileSize(sqlite3_file *id, sqlite3_int64 *pSize){ assert( pSize!=0 ); SimulateIOError(return SQLITE_IOERR_FSTAT); OSTRACE(("SIZE file=%p, pSize=%p\n", pFile->h, pSize)); - -#if SQLITE_OS_WINRT - { - FILE_STANDARD_INFO info; - if( osGetFileInformationByHandleEx(pFile->h, FileStandardInfo, - &info, sizeof(info)) ){ - *pSize = info.EndOfFile.QuadPart; - }else{ - pFile->lastErrno = osGetLastError(); - rc = winLogError(SQLITE_IOERR_FSTAT, pFile->lastErrno, - "winFileSize", pFile->zPath); - } - } -#else { DWORD upperBits; DWORD lowerBits; @@ -43692,7 +51981,6 @@ static int winFileSize(sqlite3_file *id, sqlite3_int64 *pSize){ "winFileSize", pFile->zPath); } } -#endif OSTRACE(("SIZE file=%p, pSize=%p, *pSize=%lld, rc=%s\n", pFile->h, pSize, *pSize, sqlite3ErrName(rc))); return rc; @@ -43734,8 +52022,9 @@ static int winFileSize(sqlite3_file *id, sqlite3_int64 *pSize){ ** Different API routines are called depending on whether or not this ** is Win9x or WinNT. */ -static int winGetReadLock(winFile *pFile){ +static int winGetReadLock(winFile *pFile, int bBlock){ int res; + DWORD mask = ~(bBlock ? LOCKFILE_FAIL_IMMEDIATELY : 0); OSTRACE(("READ-LOCK file=%p, lock=%d\n", pFile->h, pFile->locktype)); if( osIsNT() ){ #if SQLITE_OS_WINCE @@ -43745,7 +52034,7 @@ static int winGetReadLock(winFile *pFile){ */ res = winceLockFile(&pFile->h, SHARED_FIRST, 0, 1, 0); #else - res = winLockFile(&pFile->h, SQLITE_LOCKFILEEX_FLAGS, SHARED_FIRST, 0, + res = winLockFile(&pFile->h, SQLITE_LOCKFILEEX_FLAGS&mask, SHARED_FIRST, 0, SHARED_SIZE, 0); #endif } @@ -43754,7 +52043,7 @@ static int winGetReadLock(winFile *pFile){ int lk; sqlite3_randomness(sizeof(lk), &lk); pFile->sharedLockByte = (short)((lk & 0x7fffffff)%(SHARED_SIZE - 1)); - res = winLockFile(&pFile->h, SQLITE_LOCKFILE_FLAGS, + res = winLockFile(&pFile->h, SQLITE_LOCKFILE_FLAGS&mask, SHARED_FIRST+pFile->sharedLockByte, 0, 1, 0); } #endif @@ -43849,46 +52138,62 @@ static int winLock(sqlite3_file *id, int locktype){ assert( locktype!=PENDING_LOCK ); assert( locktype!=RESERVED_LOCK || pFile->locktype==SHARED_LOCK ); - /* Lock the PENDING_LOCK byte if we need to acquire a PENDING lock or + /* Lock the PENDING_LOCK byte if we need to acquire an EXCLUSIVE lock or ** a SHARED lock. If we are acquiring a SHARED lock, the acquisition of ** the PENDING_LOCK byte is temporary. */ newLocktype = pFile->locktype; - if( pFile->locktype==NO_LOCK - || (locktype==EXCLUSIVE_LOCK && pFile->locktype<=RESERVED_LOCK) + if( locktype==SHARED_LOCK + || (locktype==EXCLUSIVE_LOCK && pFile->locktype==RESERVED_LOCK) ){ int cnt = 3; - while( cnt-->0 && (res = winLockFile(&pFile->h, SQLITE_LOCKFILE_FLAGS, - PENDING_BYTE, 0, 1, 0))==0 ){ + + /* Flags for the LockFileEx() call. This should be an exclusive lock if + ** this call is to obtain EXCLUSIVE, or a shared lock if this call is to + ** obtain SHARED. */ + int flags = LOCKFILE_FAIL_IMMEDIATELY; + if( locktype==EXCLUSIVE_LOCK ){ + flags |= LOCKFILE_EXCLUSIVE_LOCK; + } + while( cnt>0 ){ /* Try 3 times to get the pending lock. This is needed to work ** around problems caused by indexing and/or anti-virus software on ** Windows systems. + ** ** If you are using this code as a model for alternative VFSes, do not - ** copy this retry logic. It is a hack intended for Windows only. - */ + ** copy this retry logic. It is a hack intended for Windows only. */ + res = winLockFile(&pFile->h, flags, PENDING_BYTE, 0, 1, 0); + if( res ) break; + lastErrno = osGetLastError(); OSTRACE(("LOCK-PENDING-FAIL file=%p, count=%d, result=%d\n", - pFile->h, cnt, res)); + pFile->h, cnt, res + )); + if( lastErrno==ERROR_INVALID_HANDLE ){ pFile->lastErrno = lastErrno; rc = SQLITE_IOERR_LOCK; OSTRACE(("LOCK-FAIL file=%p, count=%d, rc=%s\n", - pFile->h, cnt, sqlite3ErrName(rc))); + pFile->h, cnt, sqlite3ErrName(rc) + )); return rc; } - if( cnt ) sqlite3_win32_sleep(1); + + cnt--; + if( cnt>0 ) sqlite3_win32_sleep(1); } gotPendingLock = res; - if( !res ){ - lastErrno = osGetLastError(); - } } /* Acquire a shared lock */ if( locktype==SHARED_LOCK && res ){ assert( pFile->locktype==NO_LOCK ); - res = winGetReadLock(pFile); +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + res = winGetReadLock(pFile, pFile->bBlockOnConnect); +#else + res = winGetReadLock(pFile, 0); +#endif if( res ){ newLocktype = SHARED_LOCK; }else{ @@ -43919,14 +52224,14 @@ static int winLock(sqlite3_file *id, int locktype){ */ if( locktype==EXCLUSIVE_LOCK && res ){ assert( pFile->locktype>=SHARED_LOCK ); - res = winUnlockReadLock(pFile); + (void)winUnlockReadLock(pFile); res = winLockFile(&pFile->h, SQLITE_LOCKFILE_FLAGS, SHARED_FIRST, 0, SHARED_SIZE, 0); if( res ){ newLocktype = EXCLUSIVE_LOCK; }else{ lastErrno = osGetLastError(); - winGetReadLock(pFile); + winGetReadLock(pFile, 0); } } @@ -44006,7 +52311,7 @@ static int winUnlock(sqlite3_file *id, int locktype){ type = pFile->locktype; if( type>=EXCLUSIVE_LOCK ){ winUnlockFile(&pFile->h, SHARED_FIRST, 0, SHARED_SIZE, 0); - if( locktype==SHARED_LOCK && !winGetReadLock(pFile) ){ + if( locktype==SHARED_LOCK && !winGetReadLock(pFile, 0) ){ /* This should never happen. We should always be able to ** reacquire the read lock */ rc = winLogError(SQLITE_IOERR_UNLOCK, osGetLastError(), @@ -44085,6 +52390,7 @@ static void winModeBit(winFile *pFile, unsigned char mask, int *pArg){ /* Forward references to VFS helper methods used for temporary files */ static int winGetTempname(sqlite3_vfs *, char **); static int winIsDir(const void *); +static BOOL winIsLongPathPrefix(const char *); static BOOL winIsDriveLetterAndColon(const char *); /* @@ -44174,6 +52480,11 @@ static int winFileControl(sqlite3_file *id, int op, void *pArg){ return SQLITE_OK; } #endif + case SQLITE_FCNTL_NULL_IO: { + (void)osCloseHandle(pFile->h); + pFile->h = NULL; + return SQLITE_OK; + } case SQLITE_FCNTL_TEMPFILENAME: { char *zTFile = 0; int rc = winGetTempname(pFile->pVfs, &zTFile); @@ -44210,6 +52521,50 @@ static int winFileControl(sqlite3_file *id, int op, void *pArg){ return rc; } #endif + +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + case SQLITE_FCNTL_LOCK_TIMEOUT: { + int iOld = pFile->iBusyTimeout; + int iNew = *(int*)pArg; +#if SQLITE_ENABLE_SETLK_TIMEOUT==1 + pFile->iBusyTimeout = (iNew < 0) ? INFINITE : (DWORD)iNew; +#elif SQLITE_ENABLE_SETLK_TIMEOUT==2 + pFile->iBusyTimeout = (DWORD)(!!iNew); +#else +# error "SQLITE_ENABLE_SETLK_TIMEOUT must be set to 1 or 2" +#endif + *(int*)pArg = iOld; + return SQLITE_OK; + } + case SQLITE_FCNTL_BLOCK_ON_CONNECT: { + int iNew = *(int*)pArg; + pFile->bBlockOnConnect = iNew; + return SQLITE_OK; + } +#endif /* SQLITE_ENABLE_SETLK_TIMEOUT */ + +#if defined(SQLITE_DEBUG) || defined(SQLITE_ENABLE_FILESTAT) + case SQLITE_FCNTL_FILESTAT: { + sqlite3_str *pStr = (sqlite3_str*)pArg; + sqlite3_str_appendf(pStr, "{\"h\":%llu", (sqlite3_uint64)pFile->h); + sqlite3_str_appendf(pStr, ",\"vfs\":\"%s\"", pFile->pVfs->zName); + if( pFile->locktype ){ + static const char *azLock[] = { "SHARED", "RESERVED", + "PENDING", "EXCLUSIVE" }; + sqlite3_str_appendf(pStr, ",\"locktype\":\"%s\"", + azLock[pFile->locktype-1]); + } +#if SQLITE_MAX_MMAP_SIZE>0 + if( pFile->mmapSize ){ + sqlite3_str_appendf(pStr, ",\"mmapSize\":%lld", pFile->mmapSize); + sqlite3_str_appendf(pStr, ",\"nFetchOut\":%d", pFile->nFetchOut); + } +#endif + sqlite3_str_append(pStr, "}", 1); + return SQLITE_OK; + } +#endif /* SQLITE_DEBUG || SQLITE_ENABLE_FILESTAT */ + } OSTRACE(("FCNTL file=%p, rc=SQLITE_NOTFOUND\n", pFile->h)); return SQLITE_NOTFOUND; @@ -44235,7 +52590,7 @@ static int winSectorSize(sqlite3_file *id){ */ static int winDeviceCharacteristics(sqlite3_file *id){ winFile *p = (winFile*)id; - return SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN | + return SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN | SQLITE_IOCAP_SUBPAGE_READ | ((p->ctrlFlags & WINFILE_PSOW)?SQLITE_IOCAP_POWERSAFE_OVERWRITE:0); } @@ -44247,6 +52602,103 @@ static int winDeviceCharacteristics(sqlite3_file *id){ */ static SYSTEM_INFO winSysInfo; +/* +** Convert a UTF-8 filename into whatever form the underlying +** operating system wants filenames in. Space to hold the result +** is obtained from malloc and must be freed by the calling +** function +** +** On Cygwin, 3 possible input forms are accepted: +** - If the filename starts with ":/" or ":\", +** it is converted to UTF-16 as-is. +** - If the filename contains '/', it is assumed to be a +** Cygwin absolute path, it is converted to a win32 +** absolute path in UTF-16. +** - Otherwise it must be a filename only, the win32 filename +** is returned in UTF-16. +** Note: If the function cygwin_conv_path() fails, only +** UTF-8 -> UTF-16 conversion will be done. This can only +** happen when the file path >32k, in which case winUtf8ToUnicode() +** will fail too. +*/ +static void *winConvertFromUtf8Filename(const char *zFilename){ + void *zConverted = 0; + if( osIsNT() ){ +#ifdef __CYGWIN__ + int nChar; + LPWSTR zWideFilename; + + if( osCygwin_conv_path && !(winIsDriveLetterAndColon(zFilename) + && winIsDirSep(zFilename[2])) ){ + i64 nByte; + int convertflag = CCP_POSIX_TO_WIN_W; + if( !strchr(zFilename, '/') ) convertflag |= CCP_RELATIVE; + nByte = (i64)osCygwin_conv_path(convertflag, + zFilename, 0, 0); + if( nByte>0 ){ + zConverted = sqlite3MallocZero(12+(u64)nByte); + if ( zConverted==0 ){ + return zConverted; + } + zWideFilename = zConverted; + /* Filenames should be prefixed, except when converted + * full path already starts with "\\?\". */ + if( osCygwin_conv_path(convertflag, zFilename, + zWideFilename+4, nByte)==0 ){ + if( (convertflag&CCP_RELATIVE) ){ + memmove(zWideFilename, zWideFilename+4, nByte); + }else if( memcmp(zWideFilename+4, L"\\\\", 4) ){ + memcpy(zWideFilename, L"\\\\?\\", 8); + }else if( zWideFilename[6]!='?' ){ + memmove(zWideFilename+6, zWideFilename+4, nByte); + memcpy(zWideFilename, L"\\\\?\\UNC", 14); + }else{ + memmove(zWideFilename, zWideFilename+4, nByte); + } + return zConverted; + } + sqlite3_free(zConverted); + } + } + nChar = osMultiByteToWideChar(CP_UTF8, 0, zFilename, -1, NULL, 0); + if( nChar==0 ){ + return 0; + } + zWideFilename = sqlite3MallocZero( nChar*sizeof(WCHAR)+12 ); + if( zWideFilename==0 ){ + return 0; + } + nChar = osMultiByteToWideChar(CP_UTF8, 0, zFilename, -1, + zWideFilename, nChar); + if( nChar==0 ){ + sqlite3_free(zWideFilename); + zWideFilename = 0; + }else if( nChar>MAX_PATH + && winIsDriveLetterAndColon(zFilename) + && winIsDirSep(zFilename[2]) ){ + memmove(zWideFilename+4, zWideFilename, nChar*sizeof(WCHAR)); + zWideFilename[2] = '\\'; + memcpy(zWideFilename, L"\\\\?\\", 8); + }else if( nChar>MAX_PATH + && winIsDirSep(zFilename[0]) && winIsDirSep(zFilename[1]) + && zFilename[2] != '?' ){ + memmove(zWideFilename+6, zWideFilename, nChar*sizeof(WCHAR)); + memcpy(zWideFilename, L"\\\\?\\UNC", 14); + } + zConverted = zWideFilename; +#else + zConverted = winUtf8ToUnicode(zFilename); +#endif /* __CYGWIN__ */ + } +#if defined(SQLITE_WIN32_HAS_ANSI) && defined(_WIN32) + else{ + zConverted = winUtf8ToMbcs(zFilename, osAreFileApisANSI()); + } +#endif + /* caller will handle out of memory */ + return zConverted; +} + #ifndef SQLITE_OMIT_WAL /* @@ -44283,30 +52735,40 @@ static int winShmMutexHeld(void) { ** log-summary is opened only once per process. ** ** winShmMutexHeld() must be true when creating or destroying -** this object or while reading or writing the following fields: +** this object, or while editing the global linked list that starts +** at winShmNodeList. ** -** nRef -** pNext +** When reading or writing the linked list starting at winShmNode.pWinShmList, +** pShmNode->mutex must be held. ** -** The following fields are read-only after the object is created: +** The following fields are constant after the object is created: ** -** fid ** zFilename +** hSharedShm +** mutex +** bUseSharedLockHandle ** -** Either winShmNode.mutex must be held or winShmNode.nRef==0 and +** Either winShmNode.mutex must be held or winShmNode.pWinShmList==0 and ** winShmMutexHeld() is true when reading or writing any other field ** in this structure. ** +** File-handle hSharedShm is always used to (a) take the DMS lock, (b) +** truncate the *-shm file if the DMS-locking protocol demands it, and +** (c) map regions of the *-shm file into memory using MapViewOfFile() +** or similar. If bUseSharedLockHandle is true, then other locks are also +** taken on hSharedShm. Or, if bUseSharedLockHandle is false, then other +** locks are taken using each connection's winShm.hShm handles. */ struct winShmNode { sqlite3_mutex *mutex; /* Mutex to access this object */ char *zFilename; /* Name of the file */ - winFile hFile; /* File handle from winOpen */ + HANDLE hSharedShm; /* File handle open on zFilename */ + int bUseSharedLockHandle; /* True to use hSharedShm for everything */ + int isUnlocked; /* DMS lock has not yet been obtained */ + int isReadonly; /* True if read-only */ int szRegion; /* Size of shared-memory regions */ int nRegion; /* Size of array apRegion */ - u8 isReadonly; /* True if read-only */ - u8 isUnlocked; /* True if no DMS lock held */ struct ShmRegion { HANDLE hMap; /* File handle from CreateFileMapping */ @@ -44314,8 +52776,8 @@ struct winShmNode { } *aRegion; DWORD lastErrno; /* The Windows errno from the last I/O error */ - int nRef; /* Number of winShm objects pointing to this */ - winShm *pFirst; /* All winShm objects pointing to this */ + winShm *pWinShmList; /* List of winShm objects with ptrs to this */ + winShmNode *pNext; /* Next in list of all winShmNode objects */ #if defined(SQLITE_DEBUG) || defined(SQLITE_HAVE_OS_TRACE) u8 nextShmId; /* Next available winShm.id value */ @@ -44331,26 +52793,19 @@ static winShmNode *winShmNodeList = 0; /* ** Structure used internally by this VFS to record the state of an -** open shared memory connection. -** -** The following fields are initialized when this object is created and -** are read-only thereafter: -** -** winShm.pShmNode -** winShm.id -** -** All other fields are read/write. The winShm.pShmNode->mutex must be held -** while accessing any read/write fields. +** open shared memory connection. There is one such structure for each +** winFile open on a wal mode database. */ struct winShm { winShmNode *pShmNode; /* The underlying winShmNode object */ - winShm *pNext; /* Next winShm with the same winShmNode */ - u8 hasMutex; /* True if holding the winShmNode mutex */ u16 sharedMask; /* Mask of shared locks held */ u16 exclMask; /* Mask of exclusive locks held */ + HANDLE hShm; /* File-handle on *-shm file. For locking. */ + int bReadonly; /* True if hShm is opened read-only */ #if defined(SQLITE_DEBUG) || defined(SQLITE_HAVE_OS_TRACE) u8 id; /* Id of this connection with its winShmNode */ #endif + winShm *pWinShmNext; /* Next winShm object on same winShmNode */ }; /* @@ -44359,56 +52814,12 @@ struct winShm { #define WIN_SHM_BASE ((22+SQLITE_SHM_NLOCK)*4) /* first lock byte */ #define WIN_SHM_DMS (WIN_SHM_BASE+SQLITE_SHM_NLOCK) /* deadman switch */ -/* -** Apply advisory locks for all n bytes beginning at ofst. -*/ -#define WINSHM_UNLCK 1 -#define WINSHM_RDLCK 2 -#define WINSHM_WRLCK 3 -static int winShmSystemLock( - winShmNode *pFile, /* Apply locks to this open shared-memory segment */ - int lockType, /* WINSHM_UNLCK, WINSHM_RDLCK, or WINSHM_WRLCK */ - int ofst, /* Offset to first byte to be locked/unlocked */ - int nByte /* Number of bytes to lock or unlock */ -){ - int rc = 0; /* Result code form Lock/UnlockFileEx() */ - - /* Access to the winShmNode object is serialized by the caller */ - assert( pFile->nRef==0 || sqlite3_mutex_held(pFile->mutex) ); - - OSTRACE(("SHM-LOCK file=%p, lock=%d, offset=%d, size=%d\n", - pFile->hFile.h, lockType, ofst, nByte)); - - /* Release/Acquire the system-level lock */ - if( lockType==WINSHM_UNLCK ){ - rc = winUnlockFile(&pFile->hFile.h, ofst, 0, nByte, 0); - }else{ - /* Initialize the locking parameters */ - DWORD dwFlags = LOCKFILE_FAIL_IMMEDIATELY; - if( lockType == WINSHM_WRLCK ) dwFlags |= LOCKFILE_EXCLUSIVE_LOCK; - rc = winLockFile(&pFile->hFile.h, dwFlags, ofst, 0, nByte, 0); - } - - if( rc!= 0 ){ - rc = SQLITE_OK; - }else{ - pFile->lastErrno = osGetLastError(); - rc = SQLITE_BUSY; - } - - OSTRACE(("SHM-LOCK file=%p, func=%s, errno=%lu, rc=%s\n", - pFile->hFile.h, (lockType == WINSHM_UNLCK) ? "winUnlockFile" : - "winLockFile", pFile->lastErrno, sqlite3ErrName(rc))); - - return rc; -} - /* Forward references to VFS methods */ static int winOpen(sqlite3_vfs*,const char*,sqlite3_file*,int,int*); static int winDelete(sqlite3_vfs *,const char*,int); /* -** Purge the winShmNodeList list of all entries with winShmNode.nRef==0. +** Purge the winShmNodeList list of all entries with winShmNode.pWinShmList==0. ** ** This is not a VFS shared-memory method; it is a utility function called ** by VFS shared-memory methods. @@ -44421,7 +52832,7 @@ static void winShmPurge(sqlite3_vfs *pVfs, int deleteFlag){ osGetCurrentProcessId(), deleteFlag)); pp = &winShmNodeList; while( (p = *pp)!=0 ){ - if( p->nRef==0 ){ + if( p->pWinShmList==0 ){ int i; if( p->mutex ){ sqlite3_mutex_free(p->mutex); } for(i=0; inRegion; i++){ @@ -44434,11 +52845,7 @@ static void winShmPurge(sqlite3_vfs *pVfs, int deleteFlag){ osGetCurrentProcessId(), i, bRc ? "ok" : "failed")); UNUSED_VARIABLE_VALUE(bRc); } - if( p->hFile.h!=NULL && p->hFile.h!=INVALID_HANDLE_VALUE ){ - SimulateIOErrorBenign(1); - winClose((sqlite3_file *)&p->hFile); - SimulateIOErrorBenign(0); - } + winHandleClose(p->hSharedShm); if( deleteFlag ){ SimulateIOErrorBenign(1); sqlite3BeginBenignMalloc(); @@ -44456,42 +52863,199 @@ static void winShmPurge(sqlite3_vfs *pVfs, int deleteFlag){ } /* -** The DMS lock has not yet been taken on shm file pShmNode. Attempt to -** take it now. Return SQLITE_OK if successful, or an SQLite error -** code otherwise. -** -** If the DMS cannot be locked because this is a readonly_shm=1 -** connection and no other process already holds a lock, return -** SQLITE_READONLY_CANTINIT and set pShmNode->isUnlocked=1. +** The DMS lock has not yet been taken on the shm file associated with +** pShmNode. Take the lock. Truncate the *-shm file if required. +** Return SQLITE_OK if successful, or an SQLite error code otherwise. */ -static int winLockSharedMemory(winShmNode *pShmNode){ - int rc = winShmSystemLock(pShmNode, WINSHM_WRLCK, WIN_SHM_DMS, 1); +static int winLockSharedMemory(winShmNode *pShmNode, DWORD nMs){ + HANDLE h = pShmNode->hSharedShm; + int rc = SQLITE_OK; + assert( sqlite3_mutex_held(pShmNode->mutex) ); + rc = winHandleLockTimeout(h, WIN_SHM_DMS, 1, 1, 0); if( rc==SQLITE_OK ){ + /* We have an EXCLUSIVE lock on the DMS byte. This means that this + ** is the first process to open the file. Truncate it to zero bytes + ** in this case. */ if( pShmNode->isReadonly ){ - pShmNode->isUnlocked = 1; - winShmSystemLock(pShmNode, WINSHM_UNLCK, WIN_SHM_DMS, 1); - return SQLITE_READONLY_CANTINIT; - }else if( winTruncate((sqlite3_file*)&pShmNode->hFile, 0) ){ - winShmSystemLock(pShmNode, WINSHM_UNLCK, WIN_SHM_DMS, 1); - return winLogError(SQLITE_IOERR_SHMOPEN, osGetLastError(), - "winLockSharedMemory", pShmNode->zFilename); + rc = SQLITE_READONLY_CANTINIT; + }else{ + rc = winHandleTruncate(h, 0); } + + /* Release the EXCLUSIVE lock acquired above. */ + winUnlockFile(&h, WIN_SHM_DMS, 0, 1, 0); + }else if( (rc & 0xFF)==SQLITE_BUSY ){ + rc = SQLITE_OK; } if( rc==SQLITE_OK ){ - winShmSystemLock(pShmNode, WINSHM_UNLCK, WIN_SHM_DMS, 1); + /* Take a SHARED lock on the DMS byte. */ + rc = winHandleLockTimeout(h, WIN_SHM_DMS, 1, 0, nMs); + if( rc==SQLITE_OK ){ + pShmNode->isUnlocked = 0; + } } - return winShmSystemLock(pShmNode, WINSHM_RDLCK, WIN_SHM_DMS, 1); + return rc; } + /* -** Open the shared-memory area associated with database file pDbFd. +** This function is used to open a handle on a *-shm file. ** -** When opening a new shared-memory file, if no other instances of that -** file are currently open, in this process or in other processes, then -** the file must be truncated to zero length or have its header cleared. +** If SQLITE_ENABLE_SETLK_TIMEOUT is defined at build time, then the file +** is opened with FILE_FLAG_OVERLAPPED specified. If not, it is not. +*/ +static int winHandleOpen( + const char *zUtf8, /* File to open */ + int *pbReadonly, /* IN/OUT: True for readonly handle */ + HANDLE *ph /* OUT: New HANDLE for file */ +){ + int rc = SQLITE_OK; + void *zConverted = 0; + int bReadonly = *pbReadonly; + HANDLE h = INVALID_HANDLE_VALUE; + +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + const DWORD flag_overlapped = FILE_FLAG_OVERLAPPED; +#else + const DWORD flag_overlapped = 0; +#endif + + /* Convert the filename to the system encoding. */ + zConverted = winConvertFromUtf8Filename(zUtf8); + if( zConverted==0 ){ + OSTRACE(("OPEN name=%s, rc=SQLITE_IOERR_NOMEM", zUtf8)); + rc = SQLITE_IOERR_NOMEM_BKPT; + goto winopenfile_out; + } + + /* Ensure the file we are trying to open is not actually a directory. */ + if( winIsDir(zConverted) ){ + OSTRACE(("OPEN name=%s, rc=SQLITE_CANTOPEN_ISDIR", zUtf8)); + rc = SQLITE_CANTOPEN_ISDIR; + goto winopenfile_out; + } + + /* TODO: platforms. + ** TODO: retry-on-ioerr. + */ + if( osIsNT() ){ + h = osCreateFileW((LPCWSTR)zConverted, /* lpFileName */ + (GENERIC_READ | (bReadonly ? 0 : GENERIC_WRITE)), /* dwDesiredAccess */ + FILE_SHARE_READ | FILE_SHARE_WRITE, /* dwShareMode */ + NULL, /* lpSecurityAttributes */ + OPEN_ALWAYS, /* dwCreationDisposition */ + FILE_ATTRIBUTE_NORMAL|flag_overlapped, + NULL + ); + }else{ + /* Due to pre-processor directives earlier in this file, + ** SQLITE_WIN32_HAS_ANSI is always defined if osIsNT() is false. */ +#ifdef SQLITE_WIN32_HAS_ANSI + h = osCreateFileA((LPCSTR)zConverted, + (GENERIC_READ | (bReadonly ? 0 : GENERIC_WRITE)), /* dwDesiredAccess */ + FILE_SHARE_READ | FILE_SHARE_WRITE, /* dwShareMode */ + NULL, /* lpSecurityAttributes */ + OPEN_ALWAYS, /* dwCreationDisposition */ + FILE_ATTRIBUTE_NORMAL|flag_overlapped, + NULL + ); +#endif + } + + if( h==INVALID_HANDLE_VALUE ){ + if( bReadonly==0 ){ + bReadonly = 1; + rc = winHandleOpen(zUtf8, &bReadonly, &h); + }else{ + rc = SQLITE_CANTOPEN_BKPT; + } + } + + winopenfile_out: + sqlite3_free(zConverted); + *pbReadonly = bReadonly; + *ph = h; + return rc; +} + +/* +** Close pDbFd's connection to shared-memory. Delete the underlying +** *-shm file if deleteFlag is true. +*/ +static int winCloseSharedMemory(winFile *pDbFd, int deleteFlag){ + winShm *p; /* The connection to be closed */ + winShm **pp; /* Iterator for pShmNode->pWinShmList */ + winShmNode *pShmNode; /* The underlying shared-memory file */ + + p = pDbFd->pShm; + if( p==0 ) return SQLITE_OK; + if( p->hShm!=INVALID_HANDLE_VALUE ){ + osCloseHandle(p->hShm); + } + + winShmEnterMutex(); + pShmNode = p->pShmNode; + + /* Remove this connection from the winShmNode.pWinShmList list */ + sqlite3_mutex_enter(pShmNode->mutex); + for(pp=&pShmNode->pWinShmList; *pp!=p; pp=&(*pp)->pWinShmNext){} + *pp = p->pWinShmNext; + sqlite3_mutex_leave(pShmNode->mutex); + + winShmPurge(pDbFd->pVfs, deleteFlag); + winShmLeaveMutex(); + + /* Free the connection p */ + sqlite3_free(p); + pDbFd->pShm = 0; + return SQLITE_OK; +} + +/* +** testfixture builds may set this global variable to true via a +** Tcl interface. This forces the VFS to use the locking normally +** only used for UNC paths for all files. +*/ +#ifdef SQLITE_TEST +SQLITE_API int sqlite3_win_test_unc_locking = 0; +#else +# define sqlite3_win_test_unc_locking 0 +#endif + +/* +** Return true if the string passed as the only argument is likely +** to be a UNC path. Return false if note. +** +** Return true if: +** +** (1) The name begins with "\\" +** (2) But does not begin with "\\?\C:\" where C can be any alphabetic +** character. +** +** For testing, also return true in all cases if the global variable +** sqlite3_win_test_unc_locking is true. +*/ +static int winIsUNCPath(const char *zFile){ + if( zFile[0]=='\\' && zFile[1]=='\\' ){ + if( zFile[2]=='?' + && zFile[3]=='\\' + && sqlite3Isalpha(zFile[4]) + && zFile[5]==':' + && winIsDirSep(zFile[6]) + ){ + return sqlite3_win_test_unc_locking; + }else{ + return 1; + } + } + return sqlite3_win_test_unc_locking; +} + +/* +** Open the shared-memory area associated with database file pDbFd. */ static int winOpenSharedMemory(winFile *pDbFd){ struct winShm *p; /* The connection to be opened */ @@ -44503,98 +53067,93 @@ static int winOpenSharedMemory(winFile *pDbFd){ assert( pDbFd->pShm==0 ); /* Not previously opened */ /* Allocate space for the new sqlite3_shm object. Also speculatively - ** allocate space for a new winShmNode and filename. - */ + ** allocate space for a new winShmNode and filename. */ p = sqlite3MallocZero( sizeof(*p) ); if( p==0 ) return SQLITE_IOERR_NOMEM_BKPT; nName = sqlite3Strlen30(pDbFd->zPath); - pNew = sqlite3MallocZero( sizeof(*pShmNode) + nName + 17 ); + pNew = sqlite3MallocZero( sizeof(*pShmNode) + (i64)nName + 17 ); if( pNew==0 ){ sqlite3_free(p); return SQLITE_IOERR_NOMEM_BKPT; } pNew->zFilename = (char*)&pNew[1]; + pNew->hSharedShm = INVALID_HANDLE_VALUE; + pNew->isUnlocked = 1; + pNew->bUseSharedLockHandle = winIsUNCPath(pDbFd->zPath); sqlite3_snprintf(nName+15, pNew->zFilename, "%s-shm", pDbFd->zPath); sqlite3FileSuffix3(pDbFd->zPath, pNew->zFilename); /* Look to see if there is an existing winShmNode that can be used. - ** If no matching winShmNode currently exists, create a new one. - */ + ** If no matching winShmNode currently exists, then create a new one. */ winShmEnterMutex(); for(pShmNode = winShmNodeList; pShmNode; pShmNode=pShmNode->pNext){ /* TBD need to come up with better match here. Perhaps - ** use FILE_ID_BOTH_DIR_INFO Structure. - */ + ** use FILE_ID_BOTH_DIR_INFO Structure. */ if( sqlite3StrICmp(pShmNode->zFilename, pNew->zFilename)==0 ) break; } - if( pShmNode ){ - sqlite3_free(pNew); - }else{ - int inFlags = SQLITE_OPEN_WAL; - int outFlags = 0; - + if( pShmNode==0 ){ pShmNode = pNew; - pNew = 0; - ((winFile*)(&pShmNode->hFile))->h = INVALID_HANDLE_VALUE; - pShmNode->pNext = winShmNodeList; - winShmNodeList = pShmNode; + /* Allocate a mutex for this winShmNode object, if one is required. */ if( sqlite3GlobalConfig.bCoreMutex ){ pShmNode->mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); - if( pShmNode->mutex==0 ){ - rc = SQLITE_IOERR_NOMEM_BKPT; - goto shm_open_err; - } + if( pShmNode->mutex==0 ) rc = SQLITE_IOERR_NOMEM_BKPT; } - if( 0==sqlite3_uri_boolean(pDbFd->zPath, "readonly_shm", 0) ){ - inFlags |= SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; - }else{ - inFlags |= SQLITE_OPEN_READONLY; - } - rc = winOpen(pDbFd->pVfs, pShmNode->zFilename, - (sqlite3_file*)&pShmNode->hFile, - inFlags, &outFlags); - if( rc!=SQLITE_OK ){ - rc = winLogError(rc, osGetLastError(), "winOpenShm", - pShmNode->zFilename); - goto shm_open_err; + /* Open a file-handle to use for mappings, and for the DMS lock. */ + if( rc==SQLITE_OK ){ + HANDLE h = INVALID_HANDLE_VALUE; + pShmNode->isReadonly = sqlite3_uri_boolean(pDbFd->zPath,"readonly_shm",0); + rc = winHandleOpen(pNew->zFilename, &pShmNode->isReadonly, &h); + pShmNode->hSharedShm = h; } - if( outFlags==SQLITE_OPEN_READONLY ) pShmNode->isReadonly = 1; - rc = winLockSharedMemory(pShmNode); - if( rc!=SQLITE_OK && rc!=SQLITE_READONLY_CANTINIT ) goto shm_open_err; + /* If successful, link the new winShmNode into the global list. If an + ** error occurred, free the object. */ + if( rc==SQLITE_OK ){ + pShmNode->pNext = winShmNodeList; + winShmNodeList = pShmNode; + pNew = 0; + }else{ + sqlite3_mutex_free(pShmNode->mutex); + if( pShmNode->hSharedShm!=INVALID_HANDLE_VALUE ){ + osCloseHandle(pShmNode->hSharedShm); + } + } } - /* Make the new connection a child of the winShmNode */ - p->pShmNode = pShmNode; + /* If no error has occurred, link the winShm object to the winShmNode and + ** the winShm to pDbFd. */ + if( rc==SQLITE_OK ){ + sqlite3_mutex_enter(pShmNode->mutex); + p->pShmNode = pShmNode; + p->pWinShmNext = pShmNode->pWinShmList; + pShmNode->pWinShmList = p; #if defined(SQLITE_DEBUG) || defined(SQLITE_HAVE_OS_TRACE) - p->id = pShmNode->nextShmId++; + p->id = pShmNode->nextShmId++; #endif - pShmNode->nRef++; - pDbFd->pShm = p; + pDbFd->pShm = p; + sqlite3_mutex_leave(pShmNode->mutex); + }else if( p ){ + sqlite3_free(p); + } + + assert( rc!=SQLITE_OK || pShmNode->isUnlocked==0 || pShmNode->nRegion==0 ); winShmLeaveMutex(); + sqlite3_free(pNew); - /* The reference count on pShmNode has already been incremented under - ** the cover of the winShmEnterMutex() mutex and the pointer from the - ** new (struct winShm) object to the pShmNode has been set. All that is - ** left to do is to link the new object into the linked list starting - ** at pShmNode->pFirst. This must be done while holding the pShmNode->mutex - ** mutex. - */ - sqlite3_mutex_enter(pShmNode->mutex); - p->pNext = pShmNode->pFirst; - pShmNode->pFirst = p; - sqlite3_mutex_leave(pShmNode->mutex); - return rc; + /* Open a file-handle on the *-shm file for this connection. This file-handle + ** is only used for locking. The mapping of the *-shm file is created using + ** the shared file handle in winShmNode.hSharedShm. */ + if( rc==SQLITE_OK && pShmNode->bUseSharedLockHandle==0 ){ + p->bReadonly = sqlite3_uri_boolean(pDbFd->zPath, "readonly_shm", 0); + rc = winHandleOpen(pShmNode->zFilename, &p->bReadonly, &p->hShm); + if( rc!=SQLITE_OK ){ + assert( p->hShm==INVALID_HANDLE_VALUE ); + winCloseSharedMemory(pDbFd, 0); + } + } - /* Jump here on any error */ -shm_open_err: - winShmSystemLock(pShmNode, WINSHM_UNLCK, WIN_SHM_DMS, 1); - winShmPurge(pDbFd->pVfs, 0); /* This call frees pShmNode if required */ - sqlite3_free(p); - sqlite3_free(pNew); - winShmLeaveMutex(); return rc; } @@ -44606,38 +53165,7 @@ static int winShmUnmap( sqlite3_file *fd, /* Database holding shared memory */ int deleteFlag /* Delete after closing if true */ ){ - winFile *pDbFd; /* Database holding shared-memory */ - winShm *p; /* The connection to be closed */ - winShmNode *pShmNode; /* The underlying shared-memory file */ - winShm **pp; /* For looping over sibling connections */ - - pDbFd = (winFile*)fd; - p = pDbFd->pShm; - if( p==0 ) return SQLITE_OK; - pShmNode = p->pShmNode; - - /* Remove connection p from the set of connections associated - ** with pShmNode */ - sqlite3_mutex_enter(pShmNode->mutex); - for(pp=&pShmNode->pFirst; (*pp)!=p; pp = &(*pp)->pNext){} - *pp = p->pNext; - - /* Free the connection p */ - sqlite3_free(p); - pDbFd->pShm = 0; - sqlite3_mutex_leave(pShmNode->mutex); - - /* If pShmNode->nRef has reached 0, then close the underlying - ** shared-memory file, too */ - winShmEnterMutex(); - assert( pShmNode->nRef>0 ); - pShmNode->nRef--; - if( pShmNode->nRef==0 ){ - winShmPurge(pDbFd->pVfs, deleteFlag); - } - winShmLeaveMutex(); - - return SQLITE_OK; + return winCloseSharedMemory((winFile*)fd, deleteFlag); } /* @@ -44651,10 +53179,13 @@ static int winShmLock( ){ winFile *pDbFd = (winFile*)fd; /* Connection holding shared memory */ winShm *p = pDbFd->pShm; /* The shared memory being locked */ - winShm *pX; /* For looping over all siblings */ - winShmNode *pShmNode = p->pShmNode; + winShmNode *pShmNode; int rc = SQLITE_OK; /* Result code */ - u16 mask; /* Mask of locks to take or release */ + u16 mask = (u16)((1U<<(ofst+n)) - (1U<pShmNode; + if( NEVER(pShmNode==0) ) return SQLITE_IOERR_SHMLOCK; assert( ofst>=0 && ofst+n<=SQLITE_SHM_NLOCK ); assert( n>=1 ); @@ -44664,85 +53195,127 @@ static int winShmLock( || flags==(SQLITE_SHM_UNLOCK | SQLITE_SHM_EXCLUSIVE) ); assert( n==1 || (flags & SQLITE_SHM_EXCLUSIVE)!=0 ); - mask = (u16)((1U<<(ofst+n)) - (1U<1 || mask==(1<mutex); - if( flags & SQLITE_SHM_UNLOCK ){ - u16 allMask = 0; /* Mask of locks held by siblings */ - - /* See if any siblings hold this same lock */ - for(pX=pShmNode->pFirst; pX; pX=pX->pNext){ - if( pX==p ) continue; - assert( (pX->exclMask & (p->exclMask|p->sharedMask))==0 ); - allMask |= pX->sharedMask; - } - - /* Unlock the system-level locks */ - if( (mask & allMask)==0 ){ - rc = winShmSystemLock(pShmNode, WINSHM_UNLCK, ofst+WIN_SHM_BASE, n); - }else{ - rc = SQLITE_OK; - } - - /* Undo the local locks */ - if( rc==SQLITE_OK ){ - p->exclMask &= ~mask; - p->sharedMask &= ~mask; - } - }else if( flags & SQLITE_SHM_SHARED ){ - u16 allShared = 0; /* Union of locks held by connections other than "p" */ + /* Check that, if this to be a blocking lock, no locks that occur later + ** in the following list than the lock being obtained are already held: + ** + ** 1. Recovery lock (ofst==2). + ** 2. Checkpointer lock (ofst==1). + ** 3. Write lock (ofst==0). + ** 4. Read locks (ofst>=3 && ofstexclMask|p->sharedMask); + assert( (flags & SQLITE_SHM_UNLOCK) || pDbFd->iBusyTimeout==0 || ( + (ofst!=2 || lockMask==0) + && (ofst!=1 || lockMask==0 || lockMask==2) + && (ofst!=0 || lockMask<3) + && (ofst<3 || lockMask<(1<pFirst; pX; pX=pX->pNext){ - if( (pX->exclMask & mask)!=0 ){ - rc = SQLITE_BUSY; - break; + /* Check if there is any work to do. There are three cases: + ** + ** a) An unlock operation where there are locks to unlock, + ** b) An shared lock where the requested lock is not already held + ** c) An exclusive lock where the requested lock is not already held + ** + ** The SQLite core never requests an exclusive lock that it already holds. + ** This is assert()ed immediately below. */ + assert( flags!=(SQLITE_SHM_EXCLUSIVE|SQLITE_SHM_LOCK) + || 0==(p->exclMask & mask) + ); + if( ((flags & SQLITE_SHM_UNLOCK) && ((p->exclMask|p->sharedMask) & mask)) + || (flags==(SQLITE_SHM_SHARED|SQLITE_SHM_LOCK) && 0==(p->sharedMask & mask)) + || (flags==(SQLITE_SHM_EXCLUSIVE|SQLITE_SHM_LOCK)) + ){ + HANDLE h = p->hShm; + + if( flags & SQLITE_SHM_UNLOCK ){ + /* Case (a) - unlock. */ + + assert( (p->exclMask & p->sharedMask)==0 ); + assert( !(flags & SQLITE_SHM_EXCLUSIVE) || (p->exclMask & mask)==mask ); + assert( !(flags & SQLITE_SHM_SHARED) || (p->sharedMask & mask)==mask ); + + assert( !(flags & SQLITE_SHM_SHARED) || n==1 ); + if( pShmNode->bUseSharedLockHandle ){ + h = pShmNode->hSharedShm; + if( flags & SQLITE_SHM_SHARED ){ + winShm *pShm; + sqlite3_mutex_enter(pShmNode->mutex); + for(pShm=pShmNode->pWinShmList; pShm; pShm=pShm->pWinShmNext){ + if( pShm!=p && (pShm->sharedMask & mask) ){ + /* Another connection within this process is also holding this + ** SHARED lock. So do not actually release the OS lock. */ + h = INVALID_HANDLE_VALUE; + break; + } + } + sqlite3_mutex_leave(pShmNode->mutex); + } } - allShared |= pX->sharedMask; - } - /* Get shared locks at the system level, if necessary */ - if( rc==SQLITE_OK ){ - if( (allShared & mask)==0 ){ - rc = winShmSystemLock(pShmNode, WINSHM_RDLCK, ofst+WIN_SHM_BASE, n); - }else{ - rc = SQLITE_OK; + if( h!=INVALID_HANDLE_VALUE ){ + rc = winHandleUnlock(h, ofst+WIN_SHM_BASE, n); } - } - /* Get the local shared locks */ - if( rc==SQLITE_OK ){ - p->sharedMask |= mask; - } - }else{ - /* Make sure no sibling connections hold locks that will block this - ** lock. If any do, return SQLITE_BUSY right away. - */ - for(pX=pShmNode->pFirst; pX; pX=pX->pNext){ - if( (pX->exclMask & mask)!=0 || (pX->sharedMask & mask)!=0 ){ - rc = SQLITE_BUSY; - break; + /* If successful, also clear the bits in sharedMask/exclMask */ + if( rc==SQLITE_OK ){ + p->exclMask = (p->exclMask & ~mask); + p->sharedMask = (p->sharedMask & ~mask); + } + }else{ + int bExcl = ((flags & SQLITE_SHM_EXCLUSIVE) ? 1 : 0); + DWORD nMs = winFileBusyTimeout(pDbFd); + + if( pShmNode->bUseSharedLockHandle ){ + winShm *pShm; + h = pShmNode->hSharedShm; + sqlite3_mutex_enter(pShmNode->mutex); + for(pShm=pShmNode->pWinShmList; pShm; pShm=pShm->pWinShmNext){ + if( bExcl ){ + if( (pShm->sharedMask|pShm->exclMask) & mask ){ + rc = SQLITE_BUSY; + h = INVALID_HANDLE_VALUE; + } + }else{ + if( pShm->sharedMask & mask ){ + h = INVALID_HANDLE_VALUE; + }else if( pShm->exclMask & mask ){ + rc = SQLITE_BUSY; + h = INVALID_HANDLE_VALUE; + } + } + } + sqlite3_mutex_leave(pShmNode->mutex); } - } - /* Get the exclusive locks at the system level. Then if successful - ** also mark the local connection as being locked. - */ - if( rc==SQLITE_OK ){ - rc = winShmSystemLock(pShmNode, WINSHM_WRLCK, ofst+WIN_SHM_BASE, n); + if( h!=INVALID_HANDLE_VALUE ){ + rc = winHandleLockTimeout(h, ofst+WIN_SHM_BASE, n, bExcl, nMs); + } if( rc==SQLITE_OK ){ - assert( (p->sharedMask & mask)==0 ); - p->exclMask |= mask; + if( bExcl ){ + p->exclMask = (p->exclMask | mask); + }else{ + p->sharedMask = (p->sharedMask | mask); + } } } } - sqlite3_mutex_leave(pShmNode->mutex); - OSTRACE(("SHM-LOCK pid=%lu, id=%d, sharedMask=%03x, exclMask=%03x, rc=%s\n", - osGetCurrentProcessId(), p->id, p->sharedMask, p->exclMask, - sqlite3ErrName(rc))); + + OSTRACE(( + "SHM-LOCK(%d,%d,%d) pid=%lu, id=%d, sharedMask=%03x, exclMask=%03x," + " rc=%s\n", + ofst, n, flags, + osGetCurrentProcessId(), p->id, p->sharedMask, p->exclMask, + sqlite3ErrName(rc)) + ); return rc; } @@ -44798,20 +53371,23 @@ static int winShmMap( rc = winOpenSharedMemory(pDbFd); if( rc!=SQLITE_OK ) return rc; pShm = pDbFd->pShm; + assert( pShm!=0 ); } pShmNode = pShm->pShmNode; sqlite3_mutex_enter(pShmNode->mutex); if( pShmNode->isUnlocked ){ - rc = winLockSharedMemory(pShmNode); + /* Take the DMS lock. */ + assert( pShmNode->nRegion==0 ); + rc = winLockSharedMemory(pShmNode, winFileBusyTimeout(pDbFd)); if( rc!=SQLITE_OK ) goto shmpage_out; - pShmNode->isUnlocked = 0; } - assert( szRegion==pShmNode->szRegion || pShmNode->nRegion==0 ); + assert( szRegion==pShmNode->szRegion || pShmNode->nRegion==0 ); if( pShmNode->nRegion<=iRegion ){ + HANDLE hShared = pShmNode->hSharedShm; struct ShmRegion *apNew; /* New aRegion[] array */ - int nByte = (iRegion+1)*szRegion; /* Minimum required file size */ + i64 nByte = ((i64)iRegion+1)*(i64)szRegion; /* Minimum file size */ sqlite3_int64 sz; /* Current size of wal-index file */ pShmNode->szRegion = szRegion; @@ -44820,10 +53396,9 @@ static int winShmMap( ** Check to see if it has been allocated (i.e. if the wal-index file is ** large enough to contain the requested region). */ - rc = winFileSize((sqlite3_file *)&pShmNode->hFile, &sz); + rc = winHandleSize(hShared, &sz); if( rc!=SQLITE_OK ){ - rc = winLogError(SQLITE_IOERR_SHMSIZE, osGetLastError(), - "winShmMap1", pDbFd->zPath); + rc = winLogError(rc, osGetLastError(), "winShmMap1", pDbFd->zPath); goto shmpage_out; } @@ -44832,20 +53407,18 @@ static int winShmMap( ** zero, exit early. *pp will be set to NULL and SQLITE_OK returned. ** ** Alternatively, if isWrite is non-zero, use ftruncate() to allocate - ** the requested memory region. - */ + ** the requested memory region. */ if( !isWrite ) goto shmpage_out; - rc = winTruncate((sqlite3_file *)&pShmNode->hFile, nByte); + rc = winHandleTruncate(hShared, nByte); if( rc!=SQLITE_OK ){ - rc = winLogError(SQLITE_IOERR_SHMSIZE, osGetLastError(), - "winShmMap2", pDbFd->zPath); + rc = winLogError(rc, osGetLastError(), "winShmMap2", pDbFd->zPath); goto shmpage_out; } } /* Map the requested memory region into this processes address space. */ - apNew = (struct ShmRegion *)sqlite3_realloc64( - pShmNode->aRegion, (iRegion+1)*sizeof(apNew[0]) + apNew = (struct ShmRegion*)sqlite3_realloc64( + pShmNode->aRegion, ((i64)iRegion+1)*sizeof(apNew[0]) ); if( !apNew ){ rc = SQLITE_IOERR_NOMEM_BKPT; @@ -44862,34 +53435,20 @@ static int winShmMap( HANDLE hMap = NULL; /* file-mapping handle */ void *pMap = 0; /* Mapped memory region */ -#if SQLITE_OS_WINRT - hMap = osCreateFileMappingFromApp(pShmNode->hFile.h, - NULL, protect, nByte, NULL - ); -#elif defined(SQLITE_WIN32_HAS_WIDE) - hMap = osCreateFileMappingW(pShmNode->hFile.h, - NULL, protect, 0, nByte, NULL - ); +#if defined(SQLITE_WIN32_HAS_WIDE) + hMap = osCreateFileMappingW(hShared, NULL, protect, 0, nByte, NULL); #elif defined(SQLITE_WIN32_HAS_ANSI) && SQLITE_WIN32_CREATEFILEMAPPINGA - hMap = osCreateFileMappingA(pShmNode->hFile.h, - NULL, protect, 0, nByte, NULL - ); + hMap = osCreateFileMappingA(hShared, NULL, protect, 0, nByte, NULL); #endif - OSTRACE(("SHM-MAP-CREATE pid=%lu, region=%d, size=%d, rc=%s\n", + OSTRACE(("SHM-MAP-CREATE pid=%lu, region=%d, size=%lld, rc=%s\n", osGetCurrentProcessId(), pShmNode->nRegion, nByte, hMap ? "ok" : "failed")); if( hMap ){ - int iOffset = pShmNode->nRegion*szRegion; + i64 iOffset = pShmNode->nRegion*szRegion; int iOffsetShift = iOffset % winSysInfo.dwAllocationGranularity; -#if SQLITE_OS_WINRT - pMap = osMapViewOfFileFromApp(hMap, flags, - iOffset - iOffsetShift, szRegion + iOffsetShift - ); -#else pMap = osMapViewOfFile(hMap, flags, - 0, iOffset - iOffsetShift, szRegion + iOffsetShift + 0, iOffset - iOffsetShift, (i64)szRegion + iOffsetShift ); -#endif OSTRACE(("SHM-MAP-MAP pid=%lu, region=%d, offset=%d, size=%d, rc=%s\n", osGetCurrentProcessId(), pShmNode->nRegion, iOffset, szRegion, pMap ? "ok" : "failed")); @@ -44910,14 +53469,16 @@ static int winShmMap( shmpage_out: if( pShmNode->nRegion>iRegion ){ - int iOffset = iRegion*szRegion; + i64 iOffset = (i64)iRegion*(i64)szRegion; int iOffsetShift = iOffset % winSysInfo.dwAllocationGranularity; char *p = (char *)pShmNode->aRegion[iRegion].pMap; *pp = (void *)&p[iOffsetShift]; }else{ *pp = 0; } - if( pShmNode->isReadonly && rc==SQLITE_OK ) rc = SQLITE_READONLY; + if( pShmNode->isReadonly && rc==SQLITE_OK ){ + rc = SQLITE_READONLY; + } sqlite3_mutex_leave(pShmNode->mutex); return rc; } @@ -45020,9 +53581,7 @@ static int winMapfile(winFile *pFd, sqlite3_int64 nByte){ flags |= FILE_MAP_WRITE; } #endif -#if SQLITE_OS_WINRT - pFd->hMap = osCreateFileMappingFromApp(pFd->h, NULL, protect, nMap, NULL); -#elif defined(SQLITE_WIN32_HAS_WIDE) +#if defined(SQLITE_WIN32_HAS_WIDE) pFd->hMap = osCreateFileMappingW(pFd->h, NULL, protect, (DWORD)((nMap>>32) & 0xffffffff), (DWORD)(nMap & 0xffffffff), NULL); @@ -45042,11 +53601,7 @@ static int winMapfile(winFile *pFd, sqlite3_int64 nByte){ } assert( (nMap % winSysInfo.dwPageSize)==0 ); assert( sizeof(SIZE_T)==sizeof(sqlite3_int64) || nMap<=0xffffffff ); -#if SQLITE_OS_WINRT - pNew = osMapViewOfFileFromApp(pFd->hMap, flags, 0, (SIZE_T)nMap); -#else pNew = osMapViewOfFile(pFd->hMap, flags, 0, 0, (SIZE_T)nMap); -#endif if( pNew==NULL ){ osCloseHandle(pFd->hMap); pFd->hMap = NULL; @@ -45091,6 +53646,11 @@ static int winFetch(sqlite3_file *fd, i64 iOff, int nAmt, void **pp){ #if SQLITE_MAX_MMAP_SIZE>0 if( pFd->mmapSizeMax>0 ){ + /* Ensure that there is always at least a 256 byte buffer of addressable + ** memory following the returned page. If the database is corrupt, + ** SQLite may overread the page slightly (in practice only a few bytes, + ** but 256 is safe, round, number). */ + const int nEofBuffer = 256; if( pFd->pMapRegion==0 ){ int rc = winMapfile(pFd, -1); if( rc!=SQLITE_OK ){ @@ -45099,7 +53659,8 @@ static int winFetch(sqlite3_file *fd, i64 iOff, int nAmt, void **pp){ return rc; } } - if( pFd->mmapSize >= iOff+nAmt ){ + if( pFd->mmapSize >= (iOff+nAmt+nEofBuffer) ){ + assert( pFd->pMapRegion!=0 ); *pp = &((u8 *)pFd->pMapRegion)[iOff]; pFd->nFetchOut++; } @@ -45231,47 +53792,6 @@ static winVfsAppData winNolockAppData = { ** sqlite3_vfs object. */ -#if defined(__CYGWIN__) -/* -** Convert a filename from whatever the underlying operating system -** supports for filenames into UTF-8. Space to hold the result is -** obtained from malloc and must be freed by the calling function. -*/ -static char *winConvertToUtf8Filename(const void *zFilename){ - char *zConverted = 0; - if( osIsNT() ){ - zConverted = winUnicodeToUtf8(zFilename); - } -#ifdef SQLITE_WIN32_HAS_ANSI - else{ - zConverted = winMbcsToUtf8(zFilename, osAreFileApisANSI()); - } -#endif - /* caller will handle out of memory */ - return zConverted; -} -#endif - -/* -** Convert a UTF-8 filename into whatever form the underlying -** operating system wants filenames in. Space to hold the result -** is obtained from malloc and must be freed by the calling -** function. -*/ -static void *winConvertFromUtf8Filename(const char *zFilename){ - void *zConverted = 0; - if( osIsNT() ){ - zConverted = winUtf8ToUnicode(zFilename); - } -#ifdef SQLITE_WIN32_HAS_ANSI - else{ - zConverted = winUtf8ToMbcs(zFilename, osAreFileApisANSI()); - } -#endif - /* caller will handle out of memory */ - return zConverted; -} - /* ** This function returns non-zero if the specified UTF-8 string buffer ** ends with a directory separator character or one was successfully @@ -45284,7 +53804,14 @@ static int winMakeEndInDirSep(int nBuf, char *zBuf){ if( winIsDirSep(zBuf[nLen-1]) ){ return 1; }else if( nLen+1mxPathname; nBuf = nMax + 2; + nMax = pVfs->mxPathname; + nBuf = 2 + (i64)nMax; zBuf = sqlite3MallocZero( nBuf ); if( !zBuf ){ OSTRACE(("TEMP-FILENAME rc=SQLITE_IOERR_NOMEM\n")); @@ -45329,22 +53871,25 @@ static int winGetTempname(sqlite3_vfs *pVfs, char **pzBuf){ */ nDir = nMax - (nPre + 15); assert( nDir>0 ); - if( sqlite3_temp_directory ){ + if( winTempDirDefined() ){ int nDirLen = sqlite3Strlen30(sqlite3_temp_directory); if( nDirLen>0 ){ if( !winIsDirSep(sqlite3_temp_directory[nDirLen-1]) ){ nDirLen++; } if( nDirLen>nDir ){ + sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); sqlite3_free(zBuf); OSTRACE(("TEMP-FILENAME rc=SQLITE_ERROR\n")); return winLogError(SQLITE_ERROR, 0, "winGetTempname1", 0); } sqlite3_snprintf(nMax, zBuf, "%s", sqlite3_temp_directory); } + sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); } + #if defined(__CYGWIN__) - else{ + else if( osGetenv!=NULL ){ static const char *azDirs[] = { 0, /* getenv("SQLITE_TMPDIR") */ 0, /* getenv("TMPDIR") */ @@ -45360,11 +53905,11 @@ static int winGetTempname(sqlite3_vfs *pVfs, char **pzBuf){ unsigned int i; const char *zDir = 0; - if( !azDirs[0] ) azDirs[0] = getenv("SQLITE_TMPDIR"); - if( !azDirs[1] ) azDirs[1] = getenv("TMPDIR"); - if( !azDirs[2] ) azDirs[2] = getenv("TMP"); - if( !azDirs[3] ) azDirs[3] = getenv("TEMP"); - if( !azDirs[4] ) azDirs[4] = getenv("USERPROFILE"); + if( !azDirs[0] ) azDirs[0] = osGetenv("SQLITE_TMPDIR"); + if( !azDirs[1] ) azDirs[1] = osGetenv("TMPDIR"); + if( !azDirs[2] ) azDirs[2] = osGetenv("TMP"); + if( !azDirs[3] ) azDirs[3] = osGetenv("TEMP"); + if( !azDirs[4] ) azDirs[4] = osGetenv("USERPROFILE"); for(i=0; i>= 8; zBuf[j] = (char)zChars[ ((unsigned char)zBuf[j])%(sizeof(zChars)-1) ]; } zBuf[j] = 0; @@ -45544,7 +54058,7 @@ static int winIsDir(const void *zConverted){ return 0; /* Invalid name? */ } attr = sAttrData.dwFileAttributes; -#if SQLITE_OS_WINCE==0 +#if SQLITE_OS_WINCE==0 && defined(SQLITE_WIN32_HAS_ANSI) }else{ attr = osGetFileAttributesA((char*)zConverted); #endif @@ -45560,6 +54074,12 @@ static int winAccess( int *pResOut /* OUT: Result */ ); +/* +** The Windows version of xAccess() accepts an extra bit in the flags +** parameter that prevents an anti-virus retry loop. +*/ +#define NORETRY 0x4000 + /* ** Open a file. */ @@ -45584,6 +54104,7 @@ static int winOpen( void *zConverted; /* Filename in OS encoding */ const char *zUtf8Name = zName; /* Filename in UTF-8 encoding */ int cnt = 0; + int isRO = 0; /* file is known to be accessible readonly */ /* If argument zPath is a NULL pointer, this function is required to open ** a temporary file. Use this buffer to store the file name in. @@ -45592,7 +54113,7 @@ static int winOpen( int rc = SQLITE_OK; /* Function Return Code */ #if !defined(NDEBUG) || SQLITE_OS_WINCE - int eType = flags&0xFFFFFF00; /* Type of file to open */ + int eType = flags&0x0FFF00; /* Type of file to open */ #endif int isExclusive = (flags & SQLITE_OPEN_EXCLUSIVE); @@ -45603,7 +54124,7 @@ static int winOpen( #ifndef NDEBUG int isOpenJournal = (isCreate && ( - eType==SQLITE_OPEN_MASTER_JOURNAL + eType==SQLITE_OPEN_SUPER_JOURNAL || eType==SQLITE_OPEN_MAIN_JOURNAL || eType==SQLITE_OPEN_WAL )); @@ -45624,17 +54145,17 @@ static int winOpen( assert(isExclusive==0 || isCreate); assert(isDelete==0 || isCreate); - /* The main DB, main journal, WAL file and master journal are never + /* The main DB, main journal, WAL file and super-journal are never ** automatically deleted. Nor are they ever temporary files. */ assert( (!isDelete && zName) || eType!=SQLITE_OPEN_MAIN_DB ); assert( (!isDelete && zName) || eType!=SQLITE_OPEN_MAIN_JOURNAL ); - assert( (!isDelete && zName) || eType!=SQLITE_OPEN_MASTER_JOURNAL ); + assert( (!isDelete && zName) || eType!=SQLITE_OPEN_SUPER_JOURNAL ); assert( (!isDelete && zName) || eType!=SQLITE_OPEN_WAL ); /* Assert that the upper layer has set one of the "file-type" flags. */ assert( eType==SQLITE_OPEN_MAIN_DB || eType==SQLITE_OPEN_TEMP_DB || eType==SQLITE_OPEN_MAIN_JOURNAL || eType==SQLITE_OPEN_TEMP_JOURNAL - || eType==SQLITE_OPEN_SUBJOURNAL || eType==SQLITE_OPEN_MASTER_JOURNAL + || eType==SQLITE_OPEN_SUBJOURNAL || eType==SQLITE_OPEN_SUPER_JOURNAL || eType==SQLITE_OPEN_TRANSIENT_DB || eType==SQLITE_OPEN_WAL ); @@ -45642,13 +54163,6 @@ static int winOpen( memset(pFile, 0, sizeof(winFile)); pFile->h = INVALID_HANDLE_VALUE; -#if SQLITE_OS_WINRT - if( !zUtf8Name && !sqlite3_temp_directory ){ - sqlite3_log(SQLITE_ERROR, - "sqlite3_temp_directory variable should be set for WinRT"); - } -#endif - /* If the second argument to this function is NULL, generate a ** temporary file name to use */ @@ -45706,7 +54220,11 @@ static int winOpen( dwCreationDisposition = OPEN_EXISTING; } - dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE; + if( 0==sqlite3_uri_boolean(zName, "exclusive", 0) ){ + dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE; + }else{ + dwShareMode = 0; + } if( isDelete ){ #if SQLITE_OS_WINCE @@ -45727,31 +54245,6 @@ static int winOpen( #endif if( osIsNT() ){ -#if SQLITE_OS_WINRT - CREATEFILE2_EXTENDED_PARAMETERS extendedParameters; - extendedParameters.dwSize = sizeof(CREATEFILE2_EXTENDED_PARAMETERS); - extendedParameters.dwFileAttributes = - dwFlagsAndAttributes & FILE_ATTRIBUTE_MASK; - extendedParameters.dwFileFlags = dwFlagsAndAttributes & FILE_FLAG_MASK; - extendedParameters.dwSecurityQosFlags = SECURITY_ANONYMOUS; - extendedParameters.lpSecurityAttributes = NULL; - extendedParameters.hTemplateFile = NULL; - do{ - h = osCreateFile2((LPCWSTR)zConverted, - dwDesiredAccess, - dwShareMode, - dwCreationDisposition, - &extendedParameters); - if( h!=INVALID_HANDLE_VALUE ) break; - if( isReadWrite ){ - int rc2, isRO = 0; - sqlite3BeginBenignMalloc(); - rc2 = winAccess(pVfs, zName, SQLITE_ACCESS_READ, &isRO); - sqlite3EndBenignMalloc(); - if( rc2==SQLITE_OK && isRO ) break; - } - }while( winRetryIoerr(&cnt, &lastErrno) ); -#else do{ h = osCreateFileW((LPCWSTR)zConverted, dwDesiredAccess, @@ -45761,14 +54254,13 @@ static int winOpen( NULL); if( h!=INVALID_HANDLE_VALUE ) break; if( isReadWrite ){ - int rc2, isRO = 0; + int rc2; sqlite3BeginBenignMalloc(); - rc2 = winAccess(pVfs, zName, SQLITE_ACCESS_READ, &isRO); + rc2 = winAccess(pVfs, zUtf8Name, SQLITE_ACCESS_READ|NORETRY, &isRO); sqlite3EndBenignMalloc(); if( rc2==SQLITE_OK && isRO ) break; } }while( winRetryIoerr(&cnt, &lastErrno) ); -#endif } #ifdef SQLITE_WIN32_HAS_ANSI else{ @@ -45781,9 +54273,9 @@ static int winOpen( NULL); if( h!=INVALID_HANDLE_VALUE ) break; if( isReadWrite ){ - int rc2, isRO = 0; + int rc2; sqlite3BeginBenignMalloc(); - rc2 = winAccess(pVfs, zName, SQLITE_ACCESS_READ, &isRO); + rc2 = winAccess(pVfs, zUtf8Name, SQLITE_ACCESS_READ|NORETRY, &isRO); sqlite3EndBenignMalloc(); if( rc2==SQLITE_OK && isRO ) break; } @@ -45798,7 +54290,7 @@ static int winOpen( if( h==INVALID_HANDLE_VALUE ){ sqlite3_free(zConverted); sqlite3_free(zTmpname); - if( isReadWrite && !isExclusive ){ + if( isReadWrite && isRO && !isExclusive ){ return winOpen(pVfs, zName, id, ((flags|SQLITE_OPEN_READONLY) & ~(SQLITE_OPEN_CREATE|SQLITE_OPEN_READWRITE)), @@ -45846,13 +54338,15 @@ static int winOpen( } sqlite3_free(zTmpname); - pFile->pMethod = pAppData ? pAppData->pMethod : &winIoMethod; + id->pMethods = pAppData ? pAppData->pMethod : &winIoMethod; pFile->pVfs = pVfs; pFile->h = h; if( isReadonly ){ pFile->ctrlFlags |= WINFILE_RDONLY; } - if( sqlite3_uri_boolean(zName, "psow", SQLITE_POWERSAFE_OVERWRITE) ){ + if( (flags & SQLITE_OPEN_MAIN_DB) + && sqlite3_uri_boolean(zName, "psow", SQLITE_POWERSAFE_OVERWRITE) + ){ pFile->ctrlFlags |= WINFILE_PSOW; } pFile->lastErrno = NO_ERROR; @@ -45903,25 +54397,7 @@ static int winDelete( } if( osIsNT() ){ do { -#if SQLITE_OS_WINRT - WIN32_FILE_ATTRIBUTE_DATA sAttrData; - memset(&sAttrData, 0, sizeof(sAttrData)); - if ( osGetFileAttributesExW(zConverted, GetFileExInfoStandard, - &sAttrData) ){ - attr = sAttrData.dwFileAttributes; - }else{ - lastErrno = osGetLastError(); - if( lastErrno==ERROR_FILE_NOT_FOUND - || lastErrno==ERROR_PATH_NOT_FOUND ){ - rc = SQLITE_IOERR_DELETE_NOENT; /* Already gone? */ - }else{ - rc = SQLITE_ERROR; - } - break; - } -#else attr = osGetFileAttributesW(zConverted); -#endif if ( attr==INVALID_FILE_ATTRIBUTES ){ lastErrno = osGetLastError(); if( lastErrno==ERROR_FILE_NOT_FOUND @@ -45998,12 +54474,25 @@ static int winAccess( int rc = 0; DWORD lastErrno = 0; void *zConverted; + int noRetry = 0; /* Do not use winRetryIoerr() */ UNUSED_PARAMETER(pVfs); + if( (flags & NORETRY)!=0 ){ + noRetry = 1; + flags &= ~NORETRY; + } + SimulateIOError( return SQLITE_IOERR_ACCESS; ); OSTRACE(("ACCESS name=%s, flags=%x, pResOut=%p\n", zFilename, flags, pResOut)); + if( zFilename==0 ){ + *pResOut = 0; + OSTRACE(("ACCESS name=%s, pResOut=%p, *pResOut=%d, rc=SQLITE_OK\n", + zFilename, pResOut, *pResOut)); + return SQLITE_OK; + } + zConverted = winConvertFromUtf8Filename(zFilename); if( zConverted==0 ){ OSTRACE(("ACCESS name=%s, rc=SQLITE_IOERR_NOMEM\n", zFilename)); @@ -46015,7 +54504,10 @@ static int winAccess( memset(&sAttrData, 0, sizeof(sAttrData)); while( !(rc = osGetFileAttributesExW((LPCWSTR)zConverted, GetFileExInfoStandard, - &sAttrData)) && winRetryIoerr(&cnt, &lastErrno) ){} + &sAttrData)) + && !noRetry + && winRetryIoerr(&cnt, &lastErrno) + ){ /* Loop until true */} if( rc ){ /* For an SQLITE_ACCESS_EXISTS query, treat a zero-length file ** as if it does not exist. @@ -46028,6 +54520,7 @@ static int winAccess( attr = sAttrData.dwFileAttributes; } }else{ + if( noRetry ) lastErrno = osGetLastError(); winLogIoerr(cnt, __LINE__); if( lastErrno!=ERROR_FILE_NOT_FOUND && lastErrno!=ERROR_PATH_NOT_FOUND ){ sqlite3_free(zConverted); @@ -46062,6 +54555,17 @@ static int winAccess( return SQLITE_OK; } +/* +** Returns non-zero if the specified path name starts with the "long path" +** prefix. +*/ +static BOOL winIsLongPathPrefix( + const char *zPathname +){ + return ( zPathname[0]=='\\' && zPathname[1]=='\\' + && zPathname[2]=='?' && zPathname[3]=='\\' ); +} + /* ** Returns non-zero if the specified path name starts with a drive letter ** followed by a colon character. @@ -46072,6 +54576,7 @@ static BOOL winIsDriveLetterAndColon( return ( sqlite3Isalpha(zPathname[0]) && zPathname[1]==':' ); } +#ifdef _WIN32 /* ** Returns non-zero if the specified path name should be used verbatim. If ** non-zero is returned from this function, the calling function must simply @@ -46108,89 +54613,172 @@ static BOOL winIsVerbatimPathname( */ return FALSE; } +#endif /* _WIN32 */ + +#ifdef __CYGWIN__ +/* +** Simplify a filename into its canonical form +** by making the following changes: +** +** * convert any '/' to '\' (win32) or reverse (Cygwin) +** * removing any trailing and duplicate / (except for UNC paths) +** * convert /./ into just / +** +** Changes are made in-place. Return the new name length. +** +** The original filename is in z[0..]. If the path is shortened, +** no-longer used bytes will be written by '\0'. +*/ +static void winSimplifyName(char *z){ + int i, j; + for(i=j=0; z[i]; ++i){ + if( winIsDirSep(z[i]) ){ +#if !defined(SQLITE_TEST) + /* Some test-cases assume that "./foo" and "foo" are different */ + if( z[i+1]=='.' && winIsDirSep(z[i+2]) ){ + ++i; + continue; + } +#endif + if( !z[i+1] || (winIsDirSep(z[i+1]) && (i!=0)) ){ + continue; + } + z[j++] = osGetenv?'/':'\\'; + }else{ + z[j++] = z[i]; + } + } + while(jnOut ){ + /* SQLite assumes that xFullPathname() nul-terminates the output buffer + ** even if it returns an error. */ + zOut[iOff] = '\0'; + return SQLITE_CANTOPEN_BKPT; + } + sqlite3_snprintf(nOut-iOff, &zOut[iOff], "%s", zPath); + return SQLITE_OK; +} +#endif /* __CYGWIN__ */ /* ** Turn a relative pathname into a full pathname. Write the full ** pathname into zOut[]. zOut[] will be at least pVfs->mxPathname ** bytes in size. */ -static int winFullPathname( +static int winFullPathnameNoMutex( sqlite3_vfs *pVfs, /* Pointer to vfs object */ const char *zRelative, /* Possibly relative input path */ int nFull, /* Size of output buffer in bytes */ char *zFull /* Output buffer */ ){ -#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && !defined(__CYGWIN__) - DWORD nByte; +#if !SQLITE_OS_WINCE + int nByte; void *zConverted; char *zOut; #endif - /* If this path name begins with "/X:", where "X" is any alphabetic - ** character, discard the initial "/" from the pathname. + /* If this path name begins with "/X:" or "\\?\", where "X" is any + ** alphabetic character, discard the initial "/" from the pathname. */ - if( zRelative[0]=='/' && winIsDriveLetterAndColon(zRelative+1) ){ + if( zRelative[0]=='/' && (winIsDriveLetterAndColon(zRelative+1) + || winIsLongPathPrefix(zRelative+1)) ){ zRelative++; } -#if defined(__CYGWIN__) SimulateIOError( return SQLITE_ERROR ); - UNUSED_PARAMETER(nFull); - assert( nFull>=pVfs->mxPathname ); - if ( sqlite3_data_directory && !winIsVerbatimPathname(zRelative) ){ - /* - ** NOTE: We are dealing with a relative path name and the data - ** directory has been set. Therefore, use it as the basis - ** for converting the relative path name to an absolute - ** one by prepending the data directory and a slash. - */ - char *zOut = sqlite3MallocZero( pVfs->mxPathname+1 ); - if( !zOut ){ - return SQLITE_IOERR_NOMEM_BKPT; - } - if( cygwin_conv_path( - (osIsNT() ? CCP_POSIX_TO_WIN_W : CCP_POSIX_TO_WIN_A) | - CCP_RELATIVE, zRelative, zOut, pVfs->mxPathname+1)<0 ){ - sqlite3_free(zOut); - return winLogError(SQLITE_CANTOPEN_CONVPATH, (DWORD)errno, - "winFullPathname1", zRelative); - }else{ - char *zUtf8 = winConvertToUtf8Filename(zOut); - if( !zUtf8 ){ - sqlite3_free(zOut); - return SQLITE_IOERR_NOMEM_BKPT; - } - sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s%c%s", - sqlite3_data_directory, winGetDirSep(), zUtf8); - sqlite3_free(zUtf8); - sqlite3_free(zOut); - } - }else{ - char *zOut = sqlite3MallocZero( pVfs->mxPathname+1 ); - if( !zOut ){ - return SQLITE_IOERR_NOMEM_BKPT; - } - if( cygwin_conv_path( - (osIsNT() ? CCP_POSIX_TO_WIN_W : CCP_POSIX_TO_WIN_A), - zRelative, zOut, pVfs->mxPathname+1)<0 ){ - sqlite3_free(zOut); - return winLogError(SQLITE_CANTOPEN_CONVPATH, (DWORD)errno, - "winFullPathname2", zRelative); - }else{ - char *zUtf8 = winConvertToUtf8Filename(zOut); - if( !zUtf8 ){ - sqlite3_free(zOut); - return SQLITE_IOERR_NOMEM_BKPT; - } - sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zUtf8); - sqlite3_free(zUtf8); - sqlite3_free(zOut); + +#ifdef __CYGWIN__ + if( osGetcwd ){ + zFull[nFull-1] = '\0'; + if( !winIsDriveLetterAndColon(zRelative) || !winIsDirSep(zRelative[2]) ){ + int rc = SQLITE_OK; + int nLink = 1; /* Number of symbolic links followed so far */ + const char *zIn = zRelative; /* Input path for each iteration of loop */ + char *zDel = 0; + struct stat buf; + + UNUSED_PARAMETER(pVfs); + + do { + /* Call lstat() on path zIn. Set bLink to true if the path is a symbolic + ** link, or false otherwise. */ + int bLink = 0; + if( osLstat && osReadlink ) { + if( osLstat(zIn, &buf)!=0 ){ + int myErrno = osErrno; + if( myErrno!=ENOENT ){ + rc = winLogError(SQLITE_CANTOPEN_BKPT, (DWORD)myErrno, "lstat", zIn); + } + }else{ + bLink = ((buf.st_mode & 0170000) == 0120000); + } + + if( bLink ){ + if( zDel==0 ){ + zDel = sqlite3MallocZero(nFull); + if( zDel==0 ) rc = SQLITE_NOMEM; + }else if( ++nLink>SQLITE_MAX_SYMLINKS ){ + rc = SQLITE_CANTOPEN_BKPT; + } + + if( rc==SQLITE_OK ){ + nByte = osReadlink(zIn, zDel, nFull-1); + if( nByte ==(DWORD)-1 ){ + rc = winLogError(SQLITE_CANTOPEN_BKPT, (DWORD)osErrno, "readlink", zIn); + }else{ + if( zDel[0]!='/' ){ + int n; + for(n = sqlite3Strlen30(zIn); n>0 && zIn[n-1]!='/'; n--); + if( nByte+n+1>nFull ){ + rc = SQLITE_CANTOPEN_BKPT; + }else{ + memmove(&zDel[n], zDel, nByte+1); + memcpy(zDel, zIn, n); + nByte += n; + } + } + zDel[nByte] = '\0'; + } + } + + zIn = zDel; + } + } + + assert( rc!=SQLITE_OK || zIn!=zFull || zIn[0]=='/' ); + if( rc==SQLITE_OK && zIn!=zFull ){ + rc = mkFullPathname(zIn, zFull, nFull); + } + if( bLink==0 ) break; + zIn = zFull; + }while( rc==SQLITE_OK ); + + sqlite3_free(zDel); + winSimplifyName(zFull); + return rc; } } - return SQLITE_OK; -#endif +#endif /* __CYGWIN__ */ -#if (SQLITE_OS_WINCE || SQLITE_OS_WINRT) && !defined(__CYGWIN__) +#if SQLITE_OS_WINCE && defined(_WIN32) SimulateIOError( return SQLITE_ERROR ); /* WinCE has no concept of a relative pathname, or so I am told. */ /* WinRT has no way to convert a relative path to an absolute one. */ @@ -46209,7 +54797,8 @@ static int winFullPathname( return SQLITE_OK; #endif -#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && !defined(__CYGWIN__) +#if !SQLITE_OS_WINCE +#if defined(_WIN32) /* It's odd to simulate an io-error here, but really this is just ** using the io-error infrastructure to test that SQLite handles this ** function failing. This function could fail if, for example, the @@ -46227,6 +54816,7 @@ static int winFullPathname( sqlite3_data_directory, winGetDirSep(), zRelative); return SQLITE_OK; } +#endif zConverted = winConvertFromUtf8Filename(zRelative); if( zConverted==0 ){ return SQLITE_IOERR_NOMEM_BKPT; @@ -46265,13 +54855,12 @@ static int winFullPathname( return winLogError(SQLITE_CANTOPEN_FULLPATH, osGetLastError(), "winFullPathname3", zRelative); } - nByte += 3; - zTemp = sqlite3MallocZero( nByte*sizeof(zTemp[0]) ); + zTemp = sqlite3MallocZero( nByte*sizeof(zTemp[0]) + 3*sizeof(zTemp[0]) ); if( zTemp==0 ){ sqlite3_free(zConverted); return SQLITE_IOERR_NOMEM_BKPT; } - nByte = osGetFullPathNameA((char*)zConverted, nByte, zTemp, 0); + nByte = osGetFullPathNameA((char*)zConverted, nByte+3, zTemp, 0); if( nByte==0 ){ sqlite3_free(zConverted); sqlite3_free(zTemp); @@ -46284,7 +54873,26 @@ static int winFullPathname( } #endif if( zOut ){ +#ifdef __CYGWIN__ + if( memcmp(zOut, "\\\\?\\", 4) ){ + sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zOut); + }else if( memcmp(zOut+4, "UNC\\", 4) ){ + sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zOut+4); + }else{ + char *p = zOut+6; + *p = '\\'; + if( osGetcwd ){ + /* On Cygwin, UNC paths use forward slashes */ + while( *p ){ + if( *p=='\\' ) *p = '/'; + ++p; + } + } + sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zOut+6); + } +#else sqlite3_snprintf(MIN(nFull, pVfs->mxPathname), zFull, "%s", zOut); +#endif /* __CYGWIN__ */ sqlite3_free(zOut); return SQLITE_OK; }else{ @@ -46292,6 +54900,20 @@ static int winFullPathname( } #endif } +static int winFullPathname( + sqlite3_vfs *pVfs, /* Pointer to vfs object */ + const char *zRelative, /* Possibly relative input path */ + int nFull, /* Size of output buffer in bytes */ + char *zFull /* Output buffer */ +){ + int rc; + MUTEX_LOGIC( sqlite3_mutex *pMutex; ) + MUTEX_LOGIC( pMutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR); ) + sqlite3_mutex_enter(pMutex); + rc = winFullPathnameNoMutex(pVfs, zRelative, nFull, zFull); + sqlite3_mutex_leave(pMutex); + return rc; +} #ifndef SQLITE_OMIT_LOAD_EXTENSION /* @@ -46300,35 +54922,14 @@ static int winFullPathname( */ static void *winDlOpen(sqlite3_vfs *pVfs, const char *zFilename){ HANDLE h; -#if defined(__CYGWIN__) - int nFull = pVfs->mxPathname+1; - char *zFull = sqlite3MallocZero( nFull ); - void *zConverted = 0; - if( zFull==0 ){ - OSTRACE(("DLOPEN name=%s, handle=%p\n", zFilename, (void*)0)); - return 0; - } - if( winFullPathname(pVfs, zFilename, nFull, zFull)!=SQLITE_OK ){ - sqlite3_free(zFull); - OSTRACE(("DLOPEN name=%s, handle=%p\n", zFilename, (void*)0)); - return 0; - } - zConverted = winConvertFromUtf8Filename(zFull); - sqlite3_free(zFull); -#else void *zConverted = winConvertFromUtf8Filename(zFilename); UNUSED_PARAMETER(pVfs); -#endif if( zConverted==0 ){ OSTRACE(("DLOPEN name=%s, handle=%p\n", zFilename, (void*)0)); return 0; } if( osIsNT() ){ -#if SQLITE_OS_WINRT - h = osLoadPackagedLibrary((LPCWSTR)zConverted, 0); -#else h = osLoadLibraryW((LPCWSTR)zConverted); -#endif } #ifdef SQLITE_WIN32_HAS_ANSI else{ @@ -46410,23 +55011,16 @@ static int winRandomness(sqlite3_vfs *pVfs, int nBuf, char *zBuf){ DWORD pid = osGetCurrentProcessId(); xorMemory(&e, (unsigned char*)&pid, sizeof(DWORD)); } -#if SQLITE_OS_WINRT - { - ULONGLONG cnt = osGetTickCount64(); - xorMemory(&e, (unsigned char*)&cnt, sizeof(ULONGLONG)); - } -#else { DWORD cnt = osGetTickCount(); xorMemory(&e, (unsigned char*)&cnt, sizeof(DWORD)); } -#endif /* SQLITE_OS_WINRT */ { LARGE_INTEGER i; osQueryPerformanceCounter(&i); xorMemory(&e, (unsigned char*)&i, sizeof(LARGE_INTEGER)); } -#if !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && SQLITE_WIN32_USE_UUID +#if !SQLITE_OS_WINCE && SQLITE_WIN32_USE_UUID { UUID id; memset(&id, 0, sizeof(UUID)); @@ -46436,7 +55030,7 @@ static int winRandomness(sqlite3_vfs *pVfs, int nBuf, char *zBuf){ osUuidCreateSequential(&id); xorMemory(&e, (unsigned char*)&id, sizeof(UUID)); } -#endif /* !SQLITE_OS_WINCE && !SQLITE_OS_WINRT && SQLITE_WIN32_USE_UUID */ +#endif /* !SQLITE_OS_WINCE && SQLITE_WIN32_USE_UUID */ return e.nXor>nBuf ? nBuf : e.nXor; #endif /* defined(SQLITE_TEST) || defined(SQLITE_OMIT_RANDOMNESS) */ } @@ -46667,15 +55261,16 @@ SQLITE_API int sqlite3_os_init(void){ /* Double-check that the aSyscall[] array has been constructed ** correctly. See ticket [bb3a86e890c8e96ab] */ - assert( ArraySize(aSyscall)==80 ); + assert( ArraySize(aSyscall)==81 ); + assert( strcmp(aSyscall[0].zName,"AreFileApisANSI")==0 ); + assert( strcmp(aSyscall[20].zName,"GetFileAttributesA")==0 ); + assert( strcmp(aSyscall[40].zName,"HeapReAlloc")==0 ); + assert( strcmp(aSyscall[60].zName,"WideCharToMultiByte")==0 ); + assert( strcmp(aSyscall[80].zName,"cygwin_conv_path")==0 ); /* get memory map allocation granularity */ memset(&winSysInfo, 0, sizeof(SYSTEM_INFO)); -#if SQLITE_OS_WINRT - osGetNativeSystemInfo(&winSysInfo); -#else osGetSystemInfo(&winSysInfo); -#endif assert( winSysInfo.dwAllocationGranularity>0 ); assert( winSysInfo.dwPageSize>0 ); @@ -46699,17 +55294,9 @@ SQLITE_API int sqlite3_os_init(void){ } SQLITE_API int sqlite3_os_end(void){ -#if SQLITE_OS_WINRT - if( sleepObj!=NULL ){ - osCloseHandle(sleepObj); - sleepObj = NULL; - } -#endif - #ifndef SQLITE_OMIT_WAL winBigLock = 0; #endif - return SQLITE_OK; } @@ -46736,31 +55323,88 @@ SQLITE_API int sqlite3_os_end(void){ ** sqlite3_deserialize(). */ /* #include "sqliteInt.h" */ -#ifdef SQLITE_ENABLE_DESERIALIZE +#ifndef SQLITE_OMIT_DESERIALIZE /* ** Forward declaration of objects used by this utility */ typedef struct sqlite3_vfs MemVfs; typedef struct MemFile MemFile; +typedef struct MemStore MemStore; /* Access to a lower-level VFS that (might) implement dynamic loading, ** access to randomness, etc. */ #define ORIGVFS(p) ((sqlite3_vfs*)((p)->pAppData)) -/* An open file */ -struct MemFile { - sqlite3_file base; /* IO methods */ +/* Storage for a memdb file. +** +** An memdb object can be shared or separate. Shared memdb objects can be +** used by more than one database connection. Mutexes are used by shared +** memdb objects to coordinate access. Separate memdb objects are only +** connected to a single database connection and do not require additional +** mutexes. +** +** Shared memdb objects have .zFName!=0 and .pMutex!=0. They are created +** using "file:/name?vfs=memdb". The first character of the name must be +** "/" or else the object will be a separate memdb object. All shared +** memdb objects are stored in memdb_g.apMemStore[] in an arbitrary order. +** +** Separate memdb objects are created using a name that does not begin +** with "/" or using sqlite3_deserialize(). +** +** Access rules for shared MemStore objects: +** +** * .zFName is initialized when the object is created and afterwards +** is unchanged until the object is destroyed. So it can be accessed +** at any time as long as we know the object is not being destroyed, +** which means while either the SQLITE_MUTEX_STATIC_VFS1 or +** .pMutex is held or the object is not part of memdb_g.apMemStore[]. +** +** * Can .pMutex can only be changed while holding the +** SQLITE_MUTEX_STATIC_VFS1 mutex or while the object is not part +** of memdb_g.apMemStore[]. +** +** * Other fields can only be changed while holding the .pMutex mutex +** or when the .nRef is less than zero and the object is not part of +** memdb_g.apMemStore[]. +** +** * The .aData pointer has the added requirement that it can can only +** be changed (for resizing) when nMmap is zero. +** +*/ +struct MemStore { sqlite3_int64 sz; /* Size of the file */ sqlite3_int64 szAlloc; /* Space allocated to aData */ sqlite3_int64 szMax; /* Maximum allowed size of the file */ unsigned char *aData; /* content of the file */ + sqlite3_mutex *pMutex; /* Used by shared stores only */ int nMmap; /* Number of memory mapped pages */ unsigned mFlags; /* Flags */ + int nRdLock; /* Number of readers */ + int nWrLock; /* Number of writers. (Always 0 or 1) */ + int nRef; /* Number of users of this MemStore */ + char *zFName; /* The filename for shared stores */ +}; + +/* An open file */ +struct MemFile { + sqlite3_file base; /* IO methods */ + MemStore *pStore; /* The storage */ int eLock; /* Most recent lock against this file */ }; +/* +** File-scope variables for holding the memdb files that are accessible +** to multiple database connections in separate threads. +** +** Must hold SQLITE_MUTEX_STATIC_VFS1 to access any part of this object. +*/ +static struct MemFS { + int nMemStore; /* Number of shared MemStore objects */ + MemStore **apMemStore; /* Array of all shared MemStore objects */ +} memdb_g; + /* ** Methods for MemFile */ @@ -46771,6 +55415,7 @@ static int memdbTruncate(sqlite3_file*, sqlite3_int64 size); static int memdbSync(sqlite3_file*, int flags); static int memdbFileSize(sqlite3_file*, sqlite3_int64 *pSize); static int memdbLock(sqlite3_file*, int); +static int memdbUnlock(sqlite3_file*, int); /* static int memdbCheckReservedLock(sqlite3_file*, int *pResOut);// not used */ static int memdbFileControl(sqlite3_file*, int op, void *pArg); /* static int memdbSectorSize(sqlite3_file*); // not used */ @@ -46801,7 +55446,7 @@ static sqlite3_vfs memdb_vfs = { 1024, /* mxPathname */ 0, /* pNext */ "memdb", /* zName */ - 0, /* pAppData (set when registered) */ + 0, /* pAppData (set when registered) */ memdbOpen, /* xOpen */ 0, /* memdbDelete, */ /* xDelete */ memdbAccess, /* xAccess */ @@ -46814,7 +55459,10 @@ static sqlite3_vfs memdb_vfs = { memdbSleep, /* xSleep */ 0, /* memdbCurrentTime, */ /* xCurrentTime */ memdbGetLastError, /* xGetLastError */ - memdbCurrentTimeInt64 /* xCurrentTimeInt64 */ + memdbCurrentTimeInt64, /* xCurrentTimeInt64 */ + 0, /* xSetSystemCall */ + 0, /* xGetSystemCall */ + 0, /* xNextSystemCall */ }; static const sqlite3_io_methods memdb_io_methods = { @@ -46826,7 +55474,7 @@ static const sqlite3_io_methods memdb_io_methods = { memdbSync, /* xSync */ memdbFileSize, /* xFileSize */ memdbLock, /* xLock */ - memdbLock, /* xUnlock - same as xLock in this case */ + memdbUnlock, /* xUnlock */ 0, /* memdbCheckReservedLock, */ /* xCheckReservedLock */ memdbFileControl, /* xFileControl */ 0, /* memdbSectorSize,*/ /* xSectorSize */ @@ -46839,17 +55487,68 @@ static const sqlite3_io_methods memdb_io_methods = { memdbUnfetch /* xUnfetch */ }; +/* +** Enter/leave the mutex on a MemStore +*/ +#if defined(SQLITE_THREADSAFE) && SQLITE_THREADSAFE==0 +static void memdbEnter(MemStore *p){ + UNUSED_PARAMETER(p); +} +static void memdbLeave(MemStore *p){ + UNUSED_PARAMETER(p); +} +#else +static void memdbEnter(MemStore *p){ + sqlite3_mutex_enter(p->pMutex); +} +static void memdbLeave(MemStore *p){ + sqlite3_mutex_leave(p->pMutex); +} +#endif + /* ** Close an memdb-file. -** -** The pData pointer is owned by the application, so there is nothing -** to free. +** Free the underlying MemStore object when its refcount drops to zero +** or less. */ static int memdbClose(sqlite3_file *pFile){ - MemFile *p = (MemFile *)pFile; - if( p->mFlags & SQLITE_DESERIALIZE_FREEONCLOSE ) sqlite3_free(p->aData); + MemStore *p = ((MemFile*)pFile)->pStore; + if( p->zFName ){ + int i; +#ifndef SQLITE_MUTEX_OMIT + sqlite3_mutex *pVfsMutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1); +#endif + sqlite3_mutex_enter(pVfsMutex); + for(i=0; ALWAYS(inRef==1 ){ + memdb_g.apMemStore[i] = memdb_g.apMemStore[--memdb_g.nMemStore]; + if( memdb_g.nMemStore==0 ){ + sqlite3_free(memdb_g.apMemStore); + memdb_g.apMemStore = 0; + } + } + break; + } + } + sqlite3_mutex_leave(pVfsMutex); + }else{ + memdbEnter(p); + } + p->nRef--; + if( p->nRef<=0 ){ + if( p->mFlags & SQLITE_DESERIALIZE_FREEONCLOSE ){ + sqlite3_free(p->aData); + } + memdbLeave(p); + sqlite3_mutex_free(p->pMutex); + sqlite3_free(p); + }else{ + memdbLeave(p); + } return SQLITE_OK; } @@ -46857,27 +55556,30 @@ static int memdbClose(sqlite3_file *pFile){ ** Read data from an memdb-file. */ static int memdbRead( - sqlite3_file *pFile, - void *zBuf, - int iAmt, + sqlite3_file *pFile, + void *zBuf, + int iAmt, sqlite_int64 iOfst ){ - MemFile *p = (MemFile *)pFile; + MemStore *p = ((MemFile*)pFile)->pStore; + memdbEnter(p); if( iOfst+iAmt>p->sz ){ memset(zBuf, 0, iAmt); if( iOfstsz ) memcpy(zBuf, p->aData+iOfst, p->sz - iOfst); + memdbLeave(p); return SQLITE_IOERR_SHORT_READ; } memcpy(zBuf, p->aData+iOfst, iAmt); + memdbLeave(p); return SQLITE_OK; } /* ** Try to enlarge the memory allocation to hold at least sz bytes */ -static int memdbEnlarge(MemFile *p, sqlite3_int64 newSz){ +static int memdbEnlarge(MemStore *p, sqlite3_int64 newSz){ unsigned char *pNew; - if( (p->mFlags & SQLITE_DESERIALIZE_RESIZEABLE)==0 || p->nMmap>0 ){ + if( (p->mFlags & SQLITE_DESERIALIZE_RESIZEABLE)==0 || NEVER(p->nMmap>0) ){ return SQLITE_FULL; } if( newSz>p->szMax ){ @@ -46885,8 +55587,8 @@ static int memdbEnlarge(MemFile *p, sqlite3_int64 newSz){ } newSz *= 2; if( newSz>p->szMax ) newSz = p->szMax; - pNew = sqlite3_realloc64(p->aData, newSz); - if( pNew==0 ) return SQLITE_NOMEM; + pNew = sqlite3Realloc(p->aData, newSz); + if( pNew==0 ) return SQLITE_IOERR_NOMEM; p->aData = pNew; p->szAlloc = newSz; return SQLITE_OK; @@ -46901,19 +55603,27 @@ static int memdbWrite( int iAmt, sqlite_int64 iOfst ){ - MemFile *p = (MemFile *)pFile; - if( NEVER(p->mFlags & SQLITE_DESERIALIZE_READONLY) ) return SQLITE_READONLY; + MemStore *p = ((MemFile*)pFile)->pStore; + memdbEnter(p); + if( NEVER(p->mFlags & SQLITE_DESERIALIZE_READONLY) ){ + /* Can't happen: memdbLock() will return SQLITE_READONLY before + ** reaching this point */ + memdbLeave(p); + return SQLITE_IOERR_WRITE; + } if( iOfst+iAmt>p->sz ){ int rc; if( iOfst+iAmt>p->szAlloc && (rc = memdbEnlarge(p, iOfst+iAmt))!=SQLITE_OK ){ + memdbLeave(p); return rc; } if( iOfst>p->sz ) memset(p->aData+p->sz, 0, iOfst-p->sz); p->sz = iOfst+iAmt; } memcpy(p->aData+iOfst, z, iAmt); + memdbLeave(p); return SQLITE_OK; } @@ -46925,16 +55635,25 @@ static int memdbWrite( ** the size of a file, never to increase the size. */ static int memdbTruncate(sqlite3_file *pFile, sqlite_int64 size){ - MemFile *p = (MemFile *)pFile; - if( NEVER(size>p->sz) ) return SQLITE_FULL; - p->sz = size; - return SQLITE_OK; + MemStore *p = ((MemFile*)pFile)->pStore; + int rc = SQLITE_OK; + memdbEnter(p); + if( size>p->sz ){ + /* This can only happen with a corrupt wal mode db */ + rc = SQLITE_CORRUPT; + }else{ + p->sz = size; + } + memdbLeave(p); + return rc; } /* ** Sync an memdb-file. */ static int memdbSync(sqlite3_file *pFile, int flags){ + UNUSED_PARAMETER(pFile); + UNUSED_PARAMETER(flags); return SQLITE_OK; } @@ -46942,8 +55661,10 @@ static int memdbSync(sqlite3_file *pFile, int flags){ ** Return the current file-size of an memdb-file. */ static int memdbFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){ - MemFile *p = (MemFile *)pFile; + MemStore *p = ((MemFile*)pFile)->pStore; + memdbEnter(p); *pSize = p->sz; + memdbLeave(p); return SQLITE_OK; } @@ -46951,19 +55672,90 @@ static int memdbFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){ ** Lock an memdb-file. */ static int memdbLock(sqlite3_file *pFile, int eLock){ - MemFile *p = (MemFile *)pFile; - if( eLock>SQLITE_LOCK_SHARED - && (p->mFlags & SQLITE_DESERIALIZE_READONLY)!=0 - ){ - return SQLITE_READONLY; + MemFile *pThis = (MemFile*)pFile; + MemStore *p = pThis->pStore; + int rc = SQLITE_OK; + if( eLock<=pThis->eLock ) return SQLITE_OK; + memdbEnter(p); + + assert( p->nWrLock==0 || p->nWrLock==1 ); + assert( pThis->eLock<=SQLITE_LOCK_SHARED || p->nWrLock==1 ); + assert( pThis->eLock==SQLITE_LOCK_NONE || p->nRdLock>=1 ); + + if( eLock>SQLITE_LOCK_SHARED && (p->mFlags & SQLITE_DESERIALIZE_READONLY) ){ + rc = SQLITE_READONLY; + }else{ + switch( eLock ){ + case SQLITE_LOCK_SHARED: { + assert( pThis->eLock==SQLITE_LOCK_NONE ); + if( p->nWrLock>0 ){ + rc = SQLITE_BUSY; + }else{ + p->nRdLock++; + } + break; + }; + + case SQLITE_LOCK_RESERVED: + case SQLITE_LOCK_PENDING: { + assert( pThis->eLock>=SQLITE_LOCK_SHARED ); + if( ALWAYS(pThis->eLock==SQLITE_LOCK_SHARED) ){ + if( p->nWrLock>0 ){ + rc = SQLITE_BUSY; + }else{ + p->nWrLock = 1; + } + } + break; + } + + default: { + assert( eLock==SQLITE_LOCK_EXCLUSIVE ); + assert( pThis->eLock>=SQLITE_LOCK_SHARED ); + if( p->nRdLock>1 ){ + rc = SQLITE_BUSY; + }else if( pThis->eLock==SQLITE_LOCK_SHARED ){ + p->nWrLock = 1; + } + break; + } + } + } + if( rc==SQLITE_OK ) pThis->eLock = eLock; + memdbLeave(p); + return rc; +} + +/* +** Unlock an memdb-file. +*/ +static int memdbUnlock(sqlite3_file *pFile, int eLock){ + MemFile *pThis = (MemFile*)pFile; + MemStore *p = pThis->pStore; + if( eLock>=pThis->eLock ) return SQLITE_OK; + memdbEnter(p); + + assert( eLock==SQLITE_LOCK_SHARED || eLock==SQLITE_LOCK_NONE ); + if( eLock==SQLITE_LOCK_SHARED ){ + if( ALWAYS(pThis->eLock>SQLITE_LOCK_SHARED) ){ + p->nWrLock--; + } + }else{ + if( pThis->eLock>SQLITE_LOCK_SHARED ){ + p->nWrLock--; + } + p->nRdLock--; } - p->eLock = eLock; + + pThis->eLock = eLock; + memdbLeave(p); return SQLITE_OK; } -#if 0 /* Never used because memdbAccess() always returns false */ +#if 0 /* -** Check if another file-handle holds a RESERVED lock on an memdb-file. +** This interface is only used for crash recovery, which does not +** occur on an in-memory database. */ static int memdbCheckReservedLock(sqlite3_file *pFile, int *pResOut){ *pResOut = 0; @@ -46971,12 +55763,14 @@ static int memdbCheckReservedLock(sqlite3_file *pFile, int *pResOut){ } #endif + /* ** File control method. For custom operations on an memdb-file. */ static int memdbFileControl(sqlite3_file *pFile, int op, void *pArg){ - MemFile *p = (MemFile *)pFile; + MemStore *p = ((MemFile*)pFile)->pStore; int rc = SQLITE_NOTFOUND; + memdbEnter(p); if( op==SQLITE_FCNTL_VFSNAME ){ *(char**)pArg = sqlite3_mprintf("memdb(%p,%lld)", p->aData, p->sz); rc = SQLITE_OK; @@ -46994,6 +55788,7 @@ static int memdbFileControl(sqlite3_file *pFile, int op, void *pArg){ *(sqlite3_int64*)pArg = iLimit; rc = SQLITE_OK; } + memdbLeave(p); return rc; } @@ -47010,7 +55805,8 @@ static int memdbSectorSize(sqlite3_file *pFile){ ** Return the device characteristic flags supported by an memdb-file. */ static int memdbDeviceCharacteristics(sqlite3_file *pFile){ - return SQLITE_IOCAP_ATOMIC | + UNUSED_PARAMETER(pFile); + return SQLITE_IOCAP_ATOMIC | SQLITE_IOCAP_POWERSAFE_OVERWRITE | SQLITE_IOCAP_SAFE_APPEND | SQLITE_IOCAP_SEQUENTIAL; @@ -47023,20 +55819,26 @@ static int memdbFetch( int iAmt, void **pp ){ - MemFile *p = (MemFile *)pFile; - if( iOfst+iAmt>p->sz ){ + MemStore *p = ((MemFile*)pFile)->pStore; + memdbEnter(p); + if( iOfst+iAmt>p->sz || (p->mFlags & SQLITE_DESERIALIZE_RESIZEABLE)!=0 ){ *pp = 0; }else{ p->nMmap++; *pp = (void*)(p->aData + iOfst); } + memdbLeave(p); return SQLITE_OK; } /* Release a memory-mapped page */ static int memdbUnfetch(sqlite3_file *pFile, sqlite3_int64 iOfst, void *pPage){ - MemFile *p = (MemFile *)pFile; + MemStore *p = ((MemFile*)pFile)->pStore; + UNUSED_PARAMETER(iOfst); + UNUSED_PARAMETER(pPage); + memdbEnter(p); p->nMmap--; + memdbLeave(p); return SQLITE_OK; } @@ -47046,24 +55848,83 @@ static int memdbUnfetch(sqlite3_file *pFile, sqlite3_int64 iOfst, void *pPage){ static int memdbOpen( sqlite3_vfs *pVfs, const char *zName, - sqlite3_file *pFile, + sqlite3_file *pFd, int flags, int *pOutFlags ){ - MemFile *p = (MemFile*)pFile; - if( (flags & SQLITE_OPEN_MAIN_DB)==0 ){ - return ORIGVFS(pVfs)->xOpen(ORIGVFS(pVfs), zName, pFile, flags, pOutFlags); + MemFile *pFile = (MemFile*)pFd; + MemStore *p = 0; + int szName; + UNUSED_PARAMETER(pVfs); + + memset(pFile, 0, sizeof(*pFile)); + szName = sqlite3Strlen30(zName); + if( szName>1 && (zName[0]=='/' || zName[0]=='\\') ){ + int i; +#ifndef SQLITE_MUTEX_OMIT + sqlite3_mutex *pVfsMutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1); +#endif + sqlite3_mutex_enter(pVfsMutex); + for(i=0; izFName,zName)==0 ){ + p = memdb_g.apMemStore[i]; + break; + } + } + if( p==0 ){ + MemStore **apNew; + p = sqlite3Malloc( sizeof(*p) + (i64)szName + 3 ); + if( p==0 ){ + sqlite3_mutex_leave(pVfsMutex); + return SQLITE_NOMEM; + } + apNew = sqlite3Realloc(memdb_g.apMemStore, + sizeof(apNew[0])*(1+(i64)memdb_g.nMemStore) ); + if( apNew==0 ){ + sqlite3_free(p); + sqlite3_mutex_leave(pVfsMutex); + return SQLITE_NOMEM; + } + apNew[memdb_g.nMemStore++] = p; + memdb_g.apMemStore = apNew; + memset(p, 0, sizeof(*p)); + p->mFlags = SQLITE_DESERIALIZE_RESIZEABLE|SQLITE_DESERIALIZE_FREEONCLOSE; + p->szMax = sqlite3GlobalConfig.mxMemdbSize; + p->zFName = (char*)&p[1]; + memcpy(p->zFName, zName, szName+1); + p->pMutex = sqlite3_mutex_alloc(SQLITE_MUTEX_FAST); + if( p->pMutex==0 ){ + memdb_g.nMemStore--; + sqlite3_free(p); + sqlite3_mutex_leave(pVfsMutex); + return SQLITE_NOMEM; + } + p->nRef = 1; + memdbEnter(p); + }else{ + memdbEnter(p); + p->nRef++; + } + sqlite3_mutex_leave(pVfsMutex); + }else{ + p = sqlite3Malloc( sizeof(*p) ); + if( p==0 ){ + return SQLITE_NOMEM; + } + memset(p, 0, sizeof(*p)); + p->mFlags = SQLITE_DESERIALIZE_RESIZEABLE | SQLITE_DESERIALIZE_FREEONCLOSE; + p->szMax = sqlite3GlobalConfig.mxMemdbSize; } - memset(p, 0, sizeof(*p)); - p->mFlags = SQLITE_DESERIALIZE_RESIZEABLE | SQLITE_DESERIALIZE_FREEONCLOSE; - assert( pOutFlags!=0 ); /* True because flags==SQLITE_OPEN_MAIN_DB */ - *pOutFlags = flags | SQLITE_OPEN_MEMORY; - p->base.pMethods = &memdb_io_methods; - p->szMax = sqlite3GlobalConfig.mxMemdbSize; + pFile->pStore = p; + if( pOutFlags!=0 ){ + *pOutFlags = flags | SQLITE_OPEN_MEMORY; + } + pFd->pMethods = &memdb_io_methods; + memdbLeave(p); return SQLITE_OK; } -#if 0 /* Only used to delete rollback journals, master journals, and WAL +#if 0 /* Only used to delete rollback journals, super-journals, and WAL ** files, none of which exist in memdb. So this routine is never used */ /* ** Delete the file located at zPath. If the dirSync argument is true, @@ -47082,11 +55943,14 @@ static int memdbDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){ ** With memdb, no files ever exist on disk. So always return false. */ static int memdbAccess( - sqlite3_vfs *pVfs, - const char *zPath, - int flags, + sqlite3_vfs *pVfs, + const char *zPath, + int flags, int *pResOut ){ + UNUSED_PARAMETER(pVfs); + UNUSED_PARAMETER(zPath); + UNUSED_PARAMETER(flags); *pResOut = 0; return SQLITE_OK; } @@ -47097,11 +55961,12 @@ static int memdbAccess( ** of at least (INST_MAX_PATHNAME+1) bytes. */ static int memdbFullPathname( - sqlite3_vfs *pVfs, - const char *zPath, - int nOut, + sqlite3_vfs *pVfs, + const char *zPath, + int nOut, char *zOut ){ + UNUSED_PARAMETER(pVfs); sqlite3_snprintf(nOut, zOut, "%s", zPath); return SQLITE_OK; } @@ -47115,7 +55980,7 @@ static void *memdbDlOpen(sqlite3_vfs *pVfs, const char *zPath){ /* ** Populate the buffer zErrMsg (size nByte bytes) with a human readable -** utf-8 string describing the most recent error encountered associated +** utf-8 string describing the most recent error encountered associated ** with dynamic libraries. */ static void memdbDlError(sqlite3_vfs *pVfs, int nByte, char *zErrMsg){ @@ -47137,7 +56002,7 @@ static void memdbDlClose(sqlite3_vfs *pVfs, void *pHandle){ } /* -** Populate the buffer pointed to by zBufOut with nByte bytes of +** Populate the buffer pointed to by zBufOut with nByte bytes of ** random data. */ static int memdbRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){ @@ -47145,7 +56010,7 @@ static int memdbRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){ } /* -** Sleep for nMicro microseconds. Return the number of microseconds +** Sleep for nMicro microseconds. Return the number of microseconds ** actually slept. */ static int memdbSleep(sqlite3_vfs *pVfs, int nMicro){ @@ -47174,9 +56039,14 @@ static int memdbCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *p){ */ static MemFile *memdbFromDbSchema(sqlite3 *db, const char *zSchema){ MemFile *p = 0; + MemStore *pStore; int rc = sqlite3_file_control(db, zSchema, SQLITE_FCNTL_FILE_POINTER, &p); if( rc ) return 0; if( p->base.pMethods!=&memdb_io_methods ) return 0; + pStore = p->pStore; + memdbEnter(pStore); + if( pStore->zFName!=0 ) p = 0; + memdbLeave(pStore); return p; } @@ -47195,7 +56065,7 @@ SQLITE_API unsigned char *sqlite3_serialize( sqlite3_int64 sz; int szPage = 0; sqlite3_stmt *pStmt = 0; - unsigned char *pOut; + unsigned char *pOut = 0; char *zSql; int rc; @@ -47205,34 +56075,43 @@ SQLITE_API unsigned char *sqlite3_serialize( return 0; } #endif + sqlite3_mutex_enter(db->mutex); if( zSchema==0 ) zSchema = db->aDb[0].zDbSName; p = memdbFromDbSchema(db, zSchema); iDb = sqlite3FindDbName(db, zSchema); if( piSize ) *piSize = -1; - if( iDb<0 ) return 0; + if( iDb<0 ) goto serialize_out; if( p ){ - if( piSize ) *piSize = p->sz; + MemStore *pStore = p->pStore; + assert( pStore->pMutex==0 ); + if( piSize ) *piSize = pStore->sz; if( mFlags & SQLITE_SERIALIZE_NOCOPY ){ - pOut = p->aData; + pOut = pStore->aData; }else{ - pOut = sqlite3_malloc64( p->sz ); - if( pOut ) memcpy(pOut, p->aData, p->sz); + pOut = sqlite3_malloc64( pStore->sz ); + if( pOut ) memcpy(pOut, pStore->aData, pStore->sz); } - return pOut; + goto serialize_out; } pBt = db->aDb[iDb].pBt; - if( pBt==0 ) return 0; + if( pBt==0 ) goto serialize_out; szPage = sqlite3BtreeGetPageSize(pBt); zSql = sqlite3_mprintf("PRAGMA \"%w\".page_count", zSchema); rc = zSql ? sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0) : SQLITE_NOMEM; sqlite3_free(zSql); - if( rc ) return 0; + if( rc ) goto serialize_out; rc = sqlite3_step(pStmt); - if( rc!=SQLITE_ROW ){ - pOut = 0; - }else{ + if( rc==SQLITE_ROW ){ sz = sqlite3_column_int64(pStmt, 0)*szPage; + if( sz==0 ){ + sqlite3_reset(pStmt); + sqlite3_exec(db, "BEGIN IMMEDIATE; COMMIT;", 0, 0, 0); + rc = sqlite3_step(pStmt); + if( rc==SQLITE_ROW ){ + sz = sqlite3_column_int64(pStmt, 0)*szPage; + } + } if( piSize ) *piSize = sz; if( mFlags & SQLITE_SERIALIZE_NOCOPY ){ pOut = 0; @@ -47251,12 +56130,15 @@ SQLITE_API unsigned char *sqlite3_serialize( }else{ memset(pTo, 0, szPage); } - sqlite3PagerUnref(pPage); + sqlite3PagerUnref(pPage); } } } } sqlite3_finalize(pStmt); + + serialize_out: + sqlite3_mutex_leave(db->mutex); return pOut; } @@ -47287,59 +56169,78 @@ SQLITE_API int sqlite3_deserialize( sqlite3_mutex_enter(db->mutex); if( zSchema==0 ) zSchema = db->aDb[0].zDbSName; iDb = sqlite3FindDbName(db, zSchema); - if( iDb<0 ){ + testcase( iDb==1 ); + if( iDb<2 && iDb!=0 ){ rc = SQLITE_ERROR; goto end_deserialize; - } + } zSql = sqlite3_mprintf("ATTACH x AS %Q", zSchema); - rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); - sqlite3_free(zSql); + if( zSql==0 ){ + rc = SQLITE_NOMEM; + }else{ + rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); + sqlite3_free(zSql); + } if( rc ) goto end_deserialize; db->init.iDb = (u8)iDb; db->init.reopenMemdb = 1; - rc = sqlite3_step(pStmt); + sqlite3_step(pStmt); db->init.reopenMemdb = 0; - if( rc!=SQLITE_DONE ){ - rc = SQLITE_ERROR; + rc = sqlite3_finalize(pStmt); + if( rc!=SQLITE_OK ){ goto end_deserialize; } p = memdbFromDbSchema(db, zSchema); if( p==0 ){ rc = SQLITE_ERROR; }else{ - p->aData = pData; - p->sz = szDb; - p->szAlloc = szBuf; - p->szMax = szBuf; - if( p->szMaxszMax = sqlite3GlobalConfig.mxMemdbSize; - } - p->mFlags = mFlags; + MemStore *pStore = p->pStore; + pStore->aData = pData; + pData = 0; + pStore->sz = szDb; + pStore->szAlloc = szBuf; + pStore->szMax = szBuf; + if( pStore->szMaxszMax = sqlite3GlobalConfig.mxMemdbSize; + } + pStore->mFlags = mFlags; rc = SQLITE_OK; } end_deserialize: - sqlite3_finalize(pStmt); + if( pData && (mFlags & SQLITE_DESERIALIZE_FREEONCLOSE)!=0 ){ + sqlite3_free(pData); + } sqlite3_mutex_leave(db->mutex); return rc; } -/* +/* +** Return true if the VFS is the memvfs. +*/ +SQLITE_PRIVATE int sqlite3IsMemdb(const sqlite3_vfs *pVfs){ + return pVfs==&memdb_vfs; +} + +/* ** This routine is called when the extension is loaded. ** Register the new VFS. */ SQLITE_PRIVATE int sqlite3MemdbInit(void){ sqlite3_vfs *pLower = sqlite3_vfs_find(0); - int sz = pLower->szOsFile; + unsigned int sz; + if( NEVER(pLower==0) ) return SQLITE_ERROR; + sz = pLower->szOsFile; memdb_vfs.pAppData = pLower; - /* In all known configurations of SQLite, the size of a default - ** sqlite3_file is greater than the size of a memdb sqlite3_file. - ** Should that ever change, remove the following NEVER() */ - if( NEVER(szu.aHash[h] ){ if (p->nSet<(BITVEC_NINT-1)) { goto bitvec_set_end; @@ -47561,7 +56463,9 @@ SQLITE_PRIVATE int sqlite3BitvecSet(Bitvec *p, u32 i){ }else{ memcpy(aiValues, p->u.aHash, sizeof(p->u.aHash)); memset(p->u.apSub, 0, sizeof(p->u.apSub)); - p->iDivisor = (p->iSize + BITVEC_NPTR - 1)/BITVEC_NPTR; + p->iDivisor = p->iSize/BITVEC_NPTR; + if( (p->iSize%BITVEC_NPTR)!=0 ) p->iDivisor++; + if( p->iDivisoriDivisor = BITVEC_NBIT; rc = sqlite3BitvecSet(p, i); for(j=0; jiSize<=BITVEC_NBIT ){ - p->u.aBitmap[i/BITVEC_SZELEM] &= ~(1 << (i&(BITVEC_SZELEM-1))); + p->u.aBitmap[i/BITVEC_SZELEM] &= ~(BITVEC_TELEM)(1<<(i&(BITVEC_SZELEM-1))); }else{ unsigned int j; u32 *aiValues = pBuf; @@ -47638,6 +56542,52 @@ SQLITE_PRIVATE u32 sqlite3BitvecSize(Bitvec *p){ return p->iSize; } +#ifdef SQLITE_DEBUG +/* +** Show the content of a Bitvec option and its children. Indent +** everything by n spaces. Add x to each bitvec value. +** +** From a debugger such as gdb, one can type: +** +** call sqlite3ShowBitvec(p) +** +** For some Bitvec p and see a recursive view of the Bitvec's content. +*/ +static void showBitvec(Bitvec *p, int n, unsigned x){ + int i; + if( p==0 ){ + printf("NULL\n"); + return; + } + printf("Bitvec 0x%p iSize=%u", p, p->iSize); + if( p->iSize<=BITVEC_NBIT ){ + printf(" bitmap\n"); + printf("%*s bits:", n, ""); + for(i=1; i<=BITVEC_NBIT; i++){ + if( sqlite3BitvecTest(p,i) ) printf(" %u", x+(unsigned)i); + } + printf("\n"); + }else if( p->iDivisor==0 ){ + printf(" hash with %u entries\n", p->nSet); + printf("%*s bits:", n, ""); + for(i=0; iu.aHash[i] ) printf(" %u", x+(unsigned)p->u.aHash[i]); + } + printf("\n"); + }else{ + printf(" sub-bitvec with iDivisor=%u\n", p->iDivisor); + for(i=0; iu.apSub[i]==0 ) continue; + printf("%*s apSub[%d]=", n, "", i); + showBitvec(p->u.apSub[i], n+4, i*p->iDivisor); + } + } +} +SQLITE_PRIVATE void sqlite3ShowBitvec(Bitvec *p){ + showBitvec(p, 0, 0); +} +#endif + #ifndef SQLITE_UNTESTABLE /* ** Let V[] be an array of unsigned characters sufficient to hold @@ -47646,9 +56596,10 @@ SQLITE_PRIVATE u32 sqlite3BitvecSize(Bitvec *p){ ** individual bits within V. */ #define SETBIT(V,I) V[I>>3] |= (1<<(I&7)) -#define CLEARBIT(V,I) V[I>>3] &= ~(1<<(I&7)) +#define CLEARBIT(V,I) V[I>>3] &= ~(BITVEC_TELEM)(1<<(I&7)) #define TESTBIT(V,I) (V[I>>3]&(1<<(I&7)))!=0 + /* ** This routine runs an extensive test of the Bitvec code. ** @@ -47657,7 +56608,7 @@ SQLITE_PRIVATE u32 sqlite3BitvecSize(Bitvec *p){ ** by 0, 1, or 3 operands, depending on the opcode. Another ** opcode follows immediately after the last operand. ** -** There are 6 opcodes numbered from 0 through 5. 0 is the +** There are opcodes numbered starting with 0. 0 is the ** "halt" opcode and causes the test to end. ** ** 0 Halt and return the number of errors @@ -47666,18 +56617,25 @@ SQLITE_PRIVATE u32 sqlite3BitvecSize(Bitvec *p){ ** 3 N Set N randomly chosen bits ** 4 N Clear N randomly chosen bits ** 5 N S X Set N bits from S increment X in array only, not in bitvec +** 6 Invoice sqlite3ShowBitvec() on the Bitvec object so far +** 7 X Show compile-time parameters and the hash of X ** ** The opcodes 1 through 4 perform set and clear operations are performed ** on both a Bitvec object and on a linear array of bits obtained from malloc. ** Opcode 5 works on the linear array only, not on the Bitvec. ** Opcode 5 is used to deliberately induce a fault in order to -** confirm that error detection works. +** confirm that error detection works. Opcodes 6 and greater are +** state output opcodes. Opcodes 6 and greater are no-ops unless +** SQLite has been compiled with SQLITE_DEBUG. ** ** At the conclusion of the test the linear array is compared ** against the Bitvec object. If there are any differences, ** an error is returned. If they are the same, zero is returned. ** ** If a memory allocation error occurs, return -1. +** +** sz is the size of the Bitvec. Or if sz is negative, make the size +** 2*(unsigned)(-sz) and disabled the linear vector check. */ SQLITE_PRIVATE int sqlite3BitvecBuiltinTest(int sz, int *aOp){ Bitvec *pBitvec = 0; @@ -47688,18 +56646,41 @@ SQLITE_PRIVATE int sqlite3BitvecBuiltinTest(int sz, int *aOp){ /* Allocate the Bitvec to be tested and a linear array of ** bits to act as the reference */ - pBitvec = sqlite3BitvecCreate( sz ); - pV = sqlite3MallocZero( (sz+7)/8 + 1 ); + if( sz<=0 ){ + pBitvec = sqlite3BitvecCreate( 2*(unsigned)(-sz) ); + pV = 0; + }else{ + pBitvec = sqlite3BitvecCreate( sz ); + pV = sqlite3MallocZero( (7+(i64)sz)/8 + 1 ); + } pTmpSpace = sqlite3_malloc64(BITVEC_SZ); - if( pBitvec==0 || pV==0 || pTmpSpace==0 ) goto bitvec_end; + if( pBitvec==0 || pTmpSpace==0 || (pV==0 && sz>0) ) goto bitvec_end; /* NULL pBitvec tests */ sqlite3BitvecSet(0, 1); sqlite3BitvecClear(0, 1, pTmpSpace); /* Run the program */ - pc = 0; + pc = i = 0; while( (op = aOp[pc])!=0 ){ + if( op>=6 ){ +#ifdef SQLITE_DEBUG + if( op==6 ){ + sqlite3ShowBitvec(pBitvec); + }else if( op==7 ){ + printf("BITVEC_SZ = %d (%d by sizeof)\n", + BITVEC_SZ, (int)sizeof(Bitvec)); + printf("BITVEC_USIZE = %d\n", (int)BITVEC_USIZE); + printf("BITVEC_NELEM = %d\n", (int)BITVEC_NELEM); + printf("BITVEC_NBIT = %d\n", (int)BITVEC_NBIT); + printf("BITVEC_NINT = %d\n", (int)BITVEC_NINT); + printf("BITVEC_MXHASH = %d\n", (int)BITVEC_MXHASH); + printf("BITVEC_NPTR = %d\n", (int)BITVEC_NPTR); + } +#endif + pc++; + continue; + } switch( op ){ case 1: case 2: @@ -47710,7 +56691,7 @@ SQLITE_PRIVATE int sqlite3BitvecBuiltinTest(int sz, int *aOp){ break; } case 3: - case 4: + case 4: default: { nx = 2; sqlite3_randomness(sizeof(i), &i); @@ -47721,12 +56702,12 @@ SQLITE_PRIVATE int sqlite3BitvecBuiltinTest(int sz, int *aOp){ pc += nx; i = (i & 0x7fffffff)%sz; if( (op & 1)!=0 ){ - SETBIT(pV, (i+1)); + if( pV ) SETBIT(pV, (i+1)); if( op!=5 ){ if( sqlite3BitvecSet(pBitvec, i+1) ) goto bitvec_end; } }else{ - CLEARBIT(pV, (i+1)); + if( pV ) CLEARBIT(pV, (i+1)); sqlite3BitvecClear(pBitvec, i+1, pTmpSpace); } } @@ -47736,14 +56717,18 @@ SQLITE_PRIVATE int sqlite3BitvecBuiltinTest(int sz, int *aOp){ ** match (rc==0). Change rc to non-zero if a discrepancy ** is found. */ - rc = sqlite3BitvecTest(0,0) + sqlite3BitvecTest(pBitvec, sz+1) - + sqlite3BitvecTest(pBitvec, 0) - + (sqlite3BitvecSize(pBitvec) - sz); - for(i=1; i<=sz; i++){ - if( (TESTBIT(pV,i))!=sqlite3BitvecTest(pBitvec,i) ){ - rc = i; - break; + if( pV ){ + rc = sqlite3BitvecTest(0,0) + sqlite3BitvecTest(pBitvec, sz+1) + + sqlite3BitvecTest(pBitvec, 0) + + (sqlite3BitvecSize(pBitvec) - sz); + for(i=1; i<=sz; i++){ + if( (TESTBIT(pV,i))!=sqlite3BitvecTest(pBitvec,i) ){ + rc = i; + break; + } } + }else{ + rc = 0; } /* Free allocated structure */ @@ -47790,7 +56775,7 @@ SQLITE_PRIVATE int sqlite3BitvecBuiltinTest(int sz, int *aOp){ ** ** The PCache.pSynced variable is used to optimize searching for a dirty ** page to eject from the cache mid-transaction. It is better to eject -** a page that does not require a journal sync than one that does. +** a page that does not require a journal sync than one that does. ** Therefore, pSynced is maintained so that it *almost* always points ** to either the oldest page in the pDirty/pDirtyTail list that has a ** clear PGHDR_NEED_SYNC flag or to a page that is older than this one @@ -47800,7 +56785,7 @@ SQLITE_PRIVATE int sqlite3BitvecBuiltinTest(int sz, int *aOp){ struct PCache { PgHdr *pDirty, *pDirtyTail; /* List of dirty pages in LRU order */ PgHdr *pSynced; /* Last synced page in dirty page list */ - int nRefSum; /* Sum of ref counts over all pages */ + i64 nRefSum; /* Sum of ref counts over all pages */ int szCache; /* Configured cache size */ int szSpill; /* Size before spilling occurs */ int szPage; /* Size of every page in this cache */ @@ -47825,35 +56810,67 @@ struct PCache { int sqlite3PcacheTrace = 2; /* 0: off 1: simple 2: cache dumps */ int sqlite3PcacheMxDump = 9999; /* Max cache entries for pcacheDump() */ # define pcacheTrace(X) if(sqlite3PcacheTrace){sqlite3DebugPrintf X;} - void pcacheDump(PCache *pCache){ - int N; - int i, j; - sqlite3_pcache_page *pLower; + static void pcachePageTrace(int i, sqlite3_pcache_page *pLower){ PgHdr *pPg; unsigned char *a; - + int j; + if( pLower==0 ){ + printf("%3d: NULL\n", i); + }else{ + pPg = (PgHdr*)pLower->pExtra; + printf("%3d: nRef %2lld flgs %02x data ", i, pPg->nRef, pPg->flags); + a = (unsigned char *)pLower->pBuf; + for(j=0; j<12; j++) printf("%02x", a[j]); + printf(" ptr %p\n", pPg); + } + } + static void pcacheDump(PCache *pCache){ + int N; + int i; + sqlite3_pcache_page *pLower; + if( sqlite3PcacheTrace<2 ) return; if( pCache->pCache==0 ) return; N = sqlite3PcachePagecount(pCache); if( N>sqlite3PcacheMxDump ) N = sqlite3PcacheMxDump; for(i=1; i<=N; i++){ pLower = sqlite3GlobalConfig.pcache2.xFetch(pCache->pCache, i, 0); - if( pLower==0 ) continue; - pPg = (PgHdr*)pLower->pExtra; - printf("%3d: nRef %2d flgs %02x data ", i, pPg->nRef, pPg->flags); - a = (unsigned char *)pLower->pBuf; - for(j=0; j<12; j++) printf("%02x", a[j]); - printf("\n"); - if( pPg->pPage==0 ){ + pcachePageTrace(i, pLower); + if( pLower && ((PgHdr*)pLower)->pPage==0 ){ sqlite3GlobalConfig.pcache2.xUnpin(pCache->pCache, pLower, 0); } } } - #else +#else # define pcacheTrace(X) +# define pcachePageTrace(PGNO, X) # define pcacheDump(X) #endif +/* +** Return 1 if pPg is on the dirty list for pCache. Return 0 if not. +** This routine runs inside of assert() statements only. +*/ +#if defined(SQLITE_ENABLE_EXPENSIVE_ASSERT) +static int pageOnDirtyList(PCache *pCache, PgHdr *pPg){ + PgHdr *p; + for(p=pCache->pDirty; p; p=p->pDirtyNext){ + if( p==pPg ) return 1; + } + return 0; +} +static int pageNotOnDirtyList(PCache *pCache, PgHdr *pPg){ + PgHdr *p; + for(p=pCache->pDirty; p; p=p->pDirtyNext){ + if( p==pPg ) return 0; + } + return 1; +} +#else +# define pageOnDirtyList(A,B) 1 +# define pageNotOnDirtyList(A,B) 1 +#endif + /* ** Check invariants on a PgHdr entry. Return true if everything is OK. ** Return false if any invariant is violated. @@ -47872,8 +56889,13 @@ SQLITE_PRIVATE int sqlite3PcachePageSanity(PgHdr *pPg){ assert( pCache!=0 ); /* Every page has an associated PCache */ if( pPg->flags & PGHDR_CLEAN ){ assert( (pPg->flags & PGHDR_DIRTY)==0 );/* Cannot be both CLEAN and DIRTY */ - assert( pCache->pDirty!=pPg ); /* CLEAN pages not on dirty list */ - assert( pCache->pDirtyTail!=pPg ); + assert( pageNotOnDirtyList(pCache, pPg) );/* CLEAN pages not on dirtylist */ + }else{ + assert( (pPg->flags & PGHDR_DIRTY)!=0 );/* If not CLEAN must be DIRTY */ + assert( pPg->pDirtyNext==0 || pPg->pDirtyNext->pDirtyPrev==pPg ); + assert( pPg->pDirtyPrev==0 || pPg->pDirtyPrev->pDirtyNext==pPg ); + assert( pPg->pDirtyPrev!=0 || pCache->pDirty==pPg ); + assert( pageOnDirtyList(pCache, pPg) ); } /* WRITEABLE pages must also be DIRTY */ if( pPg->flags & PGHDR_WRITEABLE ){ @@ -47923,12 +56945,12 @@ static void pcacheManageDirtyList(PgHdr *pPage, u8 addRemove){ if( addRemove & PCACHE_DIRTYLIST_REMOVE ){ assert( pPage->pDirtyNext || pPage==p->pDirtyTail ); assert( pPage->pDirtyPrev || pPage==p->pDirty ); - + /* Update the PCache1.pSynced variable if necessary. */ if( p->pSynced==pPage ){ p->pSynced = pPage->pDirtyPrev; } - + if( pPage->pDirtyNext ){ pPage->pDirtyNext->pDirtyPrev = pPage->pDirtyPrev; }else{ @@ -47938,7 +56960,7 @@ static void pcacheManageDirtyList(PgHdr *pPage, u8 addRemove){ if( pPage->pDirtyPrev ){ pPage->pDirtyPrev->pDirtyNext = pPage->pDirtyNext; }else{ - /* If there are now no dirty pages in the cache, set eCreate to 2. + /* If there are now no dirty pages in the cache, set eCreate to 2. ** This is an optimization that allows sqlite3PcacheFetch() to skip ** searching for a dirty page to eject from the cache when it might ** otherwise have to. */ @@ -47967,11 +56989,11 @@ static void pcacheManageDirtyList(PgHdr *pPage, u8 addRemove){ p->pDirty = pPage; /* If pSynced is NULL and this page has a clear NEED_SYNC flag, set - ** pSynced to point to it. Checking the NEED_SYNC flag is an + ** pSynced to point to it. Checking the NEED_SYNC flag is an ** optimization, as if pSynced points to a page with the NEED_SYNC - ** flag set sqlite3PcacheFetchStress() searches through all newer + ** flag set sqlite3PcacheFetchStress() searches through all newer ** entries of the dirty-list for a page with NEED_SYNC clear anyway. */ - if( !p->pSynced + if( !p->pSynced && 0==(pPage->flags&PGHDR_NEED_SYNC) /*OPTIMIZATION-IF-FALSE*/ ){ p->pSynced = pPage; @@ -48002,16 +57024,20 @@ static int numberOfCachePages(PCache *p){ ** suggested cache size is set to N. */ return p->szCache; }else{ - /* IMPLEMENTATION-OF: R-61436-13639 If the argument N is negative, then - ** the number of cache pages is adjusted to use approximately abs(N*1024) - ** bytes of memory. */ - return (int)((-1024*(i64)p->szCache)/(p->szPage+p->szExtra)); + i64 n; + /* IMPLEMENTATION-OF: R-59858-46238 If the argument N is negative, then the + ** number of cache pages is adjusted to be a number of pages that would + ** use approximately abs(N*1024) bytes of memory based on the current + ** page size. */ + n = ((-1024*(i64)p->szCache)/(p->szPage+p->szExtra)); + if( n>1000000000 ) n = 1000000000; + return (int)n; } } /*************************************************** General Interfaces ****** ** -** Initialize and shutdown the page cache subsystem. Neither of these +** Initialize and shutdown the page cache subsystem. Neither of these ** functions are threadsafe. */ SQLITE_PRIVATE int sqlite3PcacheInitialize(void){ @@ -48020,6 +57046,7 @@ SQLITE_PRIVATE int sqlite3PcacheInitialize(void){ ** built-in default page cache is used instead of the application defined ** page cache. */ sqlite3PCacheSetDefault(); + assert( sqlite3GlobalConfig.pcache2.xInit!=0 ); } return sqlite3GlobalConfig.pcache2.xInit(sqlite3GlobalConfig.pcache2.pArg); } @@ -48037,8 +57064,8 @@ SQLITE_PRIVATE int sqlite3PcacheSize(void){ return sizeof(PCache); } /* ** Create a new PCache object. Storage space to hold the object -** has already been allocated and is passed in as the p pointer. -** The caller discovers how much space needs to be allocated by +** has already been allocated and is passed in as the p pointer. +** The caller discovers how much space needs to be allocated by ** calling sqlite3PcacheSize(). ** ** szExtra is some extra space allocated for each page. The first @@ -48142,15 +57169,16 @@ SQLITE_PRIVATE sqlite3_pcache_page *sqlite3PcacheFetch( assert( createFlag==0 || pCache->eCreate==eCreate ); assert( createFlag==0 || eCreate==1+(!pCache->bPurgeable||!pCache->pDirty) ); pRes = sqlite3GlobalConfig.pcache2.xFetch(pCache->pCache, pgno, eCreate); - pcacheTrace(("%p.FETCH %d%s (result: %p)\n",pCache,pgno, + pcacheTrace(("%p.FETCH %d%s (result: %p) ",pCache,pgno, createFlag?" create":"",pRes)); + pcachePageTrace(pgno, pRes); return pRes; } /* ** If the sqlite3PcacheFetch() routine is unable to allocate a new ** page because no clean pages are available for reuse and the cache -** size limit has been reached, then this routine can be invoked to +** size limit has been reached, then this routine can be invoked to ** try harder to allocate a page. This routine might invoke the stress ** callback to spill dirty pages to the journal. It will then try to ** allocate the new page and will only fail to allocate a new page on @@ -48167,17 +57195,17 @@ SQLITE_PRIVATE int sqlite3PcacheFetchStress( if( pCache->eCreate==2 ) return 0; if( sqlite3PcachePagecount(pCache)>pCache->szSpill ){ - /* Find a dirty page to write-out and recycle. First try to find a + /* Find a dirty page to write-out and recycle. First try to find a ** page that does not require a journal-sync (one with PGHDR_NEED_SYNC - ** cleared), but if that is not possible settle for any other + ** cleared), but if that is not possible settle for any other ** unreferenced dirty page. ** ** If the LRU page in the dirty list that has a clear PGHDR_NEED_SYNC ** flag is currently referenced, then the following may leave pSynced ** set incorrectly (pointing to other than the LRU page with NEED_SYNC ** cleared). This is Ok, as pSynced is just an optimization. */ - for(pPg=pCache->pSynced; - pPg && (pPg->nRef || (pPg->flags&PGHDR_NEED_SYNC)); + for(pPg=pCache->pSynced; + pPg && (pPg->nRef || (pPg->flags&PGHDR_NEED_SYNC)); pPg=pPg->pDirtyPrev ); pCache->pSynced = pPg; @@ -48187,7 +57215,7 @@ SQLITE_PRIVATE int sqlite3PcacheFetchStress( if( pPg ){ int rc; #ifdef SQLITE_LOG_CACHE_SPILL - sqlite3_log(SQLITE_FULL, + sqlite3_log(SQLITE_FULL, "spill page %d making room for %d - cache used: %d/%d", pPg->pgno, pgno, sqlite3GlobalConfig.pcache2.xPagecount(pCache->pCache), @@ -48228,6 +57256,7 @@ static SQLITE_NOINLINE PgHdr *pcacheFetchFinishWithInit( pPgHdr->pData = pPage->pBuf; pPgHdr->pExtra = (void *)&pPgHdr[1]; memset(pPgHdr->pExtra, 0, 8); + assert( EIGHT_BYTE_ALIGNMENT( pPgHdr->pExtra ) ); pPgHdr->pCache = pCache; pPgHdr->pgno = pgno; pPgHdr->flags = PGHDR_CLEAN; @@ -48271,6 +57300,7 @@ SQLITE_PRIVATE void SQLITE_NOINLINE sqlite3PcacheRelease(PgHdr *p){ pcacheUnpin(p); }else{ pcacheManageDirtyList(p, PCACHE_DIRTYLIST_FRONT); + assert( sqlite3PcachePageSanity(p) ); } } } @@ -48314,6 +57344,7 @@ SQLITE_PRIVATE void sqlite3PcacheMakeDirty(PgHdr *p){ pcacheTrace(("%p.DIRTY %d\n",p->pCache,p->pgno)); assert( (p->flags & (PGHDR_DIRTY|PGHDR_CLEAN))==PGHDR_DIRTY ); pcacheManageDirtyList(p, PCACHE_DIRTYLIST_ADD); + assert( sqlite3PcachePageSanity(p) ); } assert( sqlite3PcachePageSanity(p) ); } @@ -48372,18 +57403,28 @@ SQLITE_PRIVATE void sqlite3PcacheClearSyncFlags(PCache *pCache){ } /* -** Change the page number of page p to newPgno. +** Change the page number of page p to newPgno. */ SQLITE_PRIVATE void sqlite3PcacheMove(PgHdr *p, Pgno newPgno){ PCache *pCache = p->pCache; + sqlite3_pcache_page *pOther; assert( p->nRef>0 ); assert( newPgno>0 ); assert( sqlite3PcachePageSanity(p) ); pcacheTrace(("%p.MOVE %d -> %d\n",pCache,p->pgno,newPgno)); + pOther = sqlite3GlobalConfig.pcache2.xFetch(pCache->pCache, newPgno, 0); + if( pOther ){ + PgHdr *pXPage = (PgHdr*)pOther->pExtra; + assert( pXPage->nRef==0 ); + pXPage->nRef++; + pCache->nRefSum++; + sqlite3PcacheDrop(pXPage); + } sqlite3GlobalConfig.pcache2.xRekey(pCache->pCache, p->pPage, p->pgno,newPgno); p->pgno = newPgno; if( (p->flags&PGHDR_DIRTY) && (p->flags&PGHDR_NEED_SYNC) ){ pcacheManageDirtyList(p, PCACHE_DIRTYLIST_FRONT); + assert( sqlite3PcachePageSanity(p) ); } } @@ -48435,7 +57476,7 @@ SQLITE_PRIVATE void sqlite3PcacheClose(PCache *pCache){ sqlite3GlobalConfig.pcache2.xDestroy(pCache->pCache); } -/* +/* ** Discard the contents of the cache. */ SQLITE_PRIVATE void sqlite3PcacheClear(PCache *pCache){ @@ -48473,7 +57514,7 @@ static PgHdr *pcacheMergeDirtyList(PgHdr *pA, PgHdr *pB){ } /* -** Sort the list of pages in accending order by pgno. Pages are +** Sort the list of pages in ascending order by pgno. Pages are ** connected by pDirty pointers. The pDirtyPrev pointers are ** corrupted by this sort. ** @@ -48526,24 +57567,24 @@ SQLITE_PRIVATE PgHdr *sqlite3PcacheDirtyList(PCache *pCache){ return pcacheSortDirtyList(pCache->pDirty); } -/* +/* ** Return the total number of references to all pages held by the cache. ** ** This is not the total number of pages referenced, but the sum of the ** reference count for all pages. */ -SQLITE_PRIVATE int sqlite3PcacheRefCount(PCache *pCache){ +SQLITE_PRIVATE i64 sqlite3PcacheRefCount(PCache *pCache){ return pCache->nRefSum; } /* ** Return the number of references to the page supplied as an argument. */ -SQLITE_PRIVATE int sqlite3PcachePageRefcount(PgHdr *p){ +SQLITE_PRIVATE i64 sqlite3PcachePageRefcount(PgHdr *p){ return p->nRef; } -/* +/* ** Return the total number of pages in the cache. */ SQLITE_PRIVATE int sqlite3PcachePagecount(PCache *pCache){ @@ -48585,7 +57626,7 @@ SQLITE_PRIVATE int sqlite3PcacheSetSpillsize(PCache *p, int mxPage){ p->szSpill = mxPage; } res = numberOfCachePages(p); - if( resszSpill ) res = p->szSpill; + if( resszSpill ) res = p->szSpill; return res; } @@ -48616,7 +57657,7 @@ SQLITE_PRIVATE int sqlite3PCachePercentDirty(PCache *pCache){ } #ifdef SQLITE_DIRECT_OVERFLOW_READ -/* +/* ** Return true if there are one or more dirty pages in the cache. Else false. */ SQLITE_PRIVATE int sqlite3PCacheIsDirty(PCache *pCache){ @@ -48681,12 +57722,13 @@ SQLITE_PRIVATE void sqlite3PcacheIterateDirty(PCache *pCache, void (*xIter)(PgHd ** size can vary according to architecture, compile-time options, and ** SQLite library version number. ** -** If SQLITE_PCACHE_SEPARATE_HEADER is defined, then the extension is obtained -** using a separate memory allocation from the database page content. This -** seeks to overcome the "clownshoe" problem (also called "internal -** fragmentation" in academic literature) of allocating a few bytes more -** than a power of two with the memory allocator rounding up to the next -** power of two, and leaving the rounded-up space unused. +** Historical note: It used to be that if the SQLITE_PCACHE_SEPARATE_HEADER +** was defined, then the page content would be held in a separate memory +** allocation from the PgHdr1. This was intended to avoid clownshoe memory +** allocations. However, the btree layer needs a small (16-byte) overrun +** area after the page content buffer. The header serves as that overrun +** area. Therefore SQLITE_PCACHE_SEPARATE_HEADER was discontinued to avoid +** any possibility of a memory error. ** ** This module tracks pointers to PgHdr1 objects. Only pcache.c communicates ** with this module. Information is passed back and forth as PgHdr1 pointers. @@ -48705,14 +57747,14 @@ SQLITE_PRIVATE void sqlite3PcacheIterateDirty(PCache *pCache, void (*xIter)(PgHd ** ** The third case is a chunk of heap memory (defaulting to 100 pages worth) ** that is allocated when the page cache is created. The size of the local -** bulk allocation can be adjusted using +** bulk allocation can be adjusted using ** ** sqlite3_config(SQLITE_CONFIG_PAGECACHE, (void*)0, 0, N). ** ** If N is positive, then N pages worth of memory are allocated using a single ** sqlite3Malloc() call and that memory is used for the first N pages allocated. ** Or if N is negative, then -1024*N bytes of memory are allocated and used -** for as many pages as can be accomodated. +** for as many pages as can be accommodated. ** ** Only one of (2) or (3) can be used. Once the memory available to (2) or ** (3) is exhausted, subsequent allocations fail over to the general-purpose @@ -48730,31 +57772,41 @@ typedef struct PgFreeslot PgFreeslot; typedef struct PGroup PGroup; /* -** Each cache entry is represented by an instance of the following -** structure. Unless SQLITE_PCACHE_SEPARATE_HEADER is defined, a buffer of -** PgHdr1.pCache->szPage bytes is allocated directly before this structure -** in memory. +** Each cache entry is represented by an instance of the following +** structure. A buffer of PgHdr1.pCache->szPage bytes is allocated +** directly before this structure and is used to cache the page content. +** +** When reading a corrupt database file, it is possible that SQLite might +** read a few bytes (no more than 16 bytes) past the end of the page buffer. +** It will only read past the end of the page buffer, never write. This +** object is positioned immediately after the page buffer to serve as an +** overrun area, so that overreads are harmless. ** -** Note: Variables isBulkLocal and isAnchor were once type "u8". That works, -** but causes a 2-byte gap in the structure for most architectures (since +** Variables isBulkLocal and isAnchor were once type "u8". That works, +** but causes a 2-byte gap in the structure for most architectures (since ** pointers must be either 4 or 8-byte aligned). As this structure is located ** in memory directly after the associated page data, if the database is -** corrupt, code at the b-tree layer may overread the page buffer and +** corrupt, code at the b-tree layer may overread the page buffer and ** read part of this structure before the corruption is detected. This -** can cause a valgrind error if the unitialized gap is accessed. Using u16 -** ensures there is no such gap, and therefore no bytes of unitialized memory -** in the structure. +** can cause a valgrind error if the uninitialized gap is accessed. Using u16 +** ensures there is no such gap, and therefore no bytes of uninitialized +** memory in the structure. +** +** The pLruNext and pLruPrev pointers form a double-linked circular list +** of all pages that are unpinned. The PGroup.lru element (which should be +** the only element on the list with PgHdr1.isAnchor set to 1) forms the +** beginning and the end of the list. */ struct PgHdr1 { - sqlite3_pcache_page page; /* Base class. Must be first. pBuf & pExtra */ - unsigned int iKey; /* Key value (page number) */ - u16 isBulkLocal; /* This page from bulk local storage */ - u16 isAnchor; /* This is the PGroup.lru element */ - PgHdr1 *pNext; /* Next in hash table chain */ - PCache1 *pCache; /* Cache that currently owns this page */ - PgHdr1 *pLruNext; /* Next in LRU list of unpinned pages */ - PgHdr1 *pLruPrev; /* Previous in LRU list of unpinned pages */ - /* NB: pLruPrev is only valid if pLruNext!=0 */ + sqlite3_pcache_page page; /* Base class. Must be first. pBuf & pExtra */ + unsigned int iKey; /* Key value (page number) */ + u16 isBulkLocal; /* This page from bulk local storage */ + u16 isAnchor; /* This is the PGroup.lru element */ + PgHdr1 *pNext; /* Next in hash table chain */ + PCache1 *pCache; /* Cache that currently owns this page */ + PgHdr1 *pLruNext; /* Next in circular LRU list of unpinned pages */ + PgHdr1 *pLruPrev; /* Previous in LRU list of unpinned pages */ + /* NB: pLruPrev is only valid if pLruNext!=0 */ }; /* @@ -48764,7 +57816,7 @@ struct PgHdr1 { #define PAGE_IS_PINNED(p) ((p)->pLruNext==0) #define PAGE_IS_UNPINNED(p) ((p)->pLruNext!=0) -/* Each page cache (or PCache) belongs to a PGroup. A PGroup is a set +/* Each page cache (or PCache) belongs to a PGroup. A PGroup is a set ** of one or more PCaches that are able to recycle each other's unpinned ** pages when they are under memory pressure. A PGroup is an instance of ** the following object. @@ -48800,13 +57852,13 @@ struct PGroup { ** temporary or transient database) has a single page cache which ** is an instance of this object. ** -** Pointers to structures of this type are cast and returned as +** Pointers to structures of this type are cast and returned as ** opaque sqlite3_pcache* handles. */ struct PCache1 { /* Cache configuration parameters. Page size (szPage) and the purgeable ** flag (bPurgeable) and the pnPurgeable pointer are all set when the - ** cache is created and are never changed thereafter. nMax may be + ** cache is created and are never changed thereafter. nMax may be ** modified at any time by a call to the pcache1Cachesize() method. ** The PGroup mutex must be held when accessing nMax. */ @@ -48854,7 +57906,7 @@ static SQLITE_WSD struct PCacheGlobal { */ int isInit; /* True if initialized */ int separateCache; /* Use a new PGroup for each PCache */ - int nInitPage; /* Initial bulk allocation size */ + int nInitPage; /* Initial bulk allocation size */ int szSlot; /* Size of each free slot */ int nSlot; /* The number of pcache slots */ int nReserve; /* Try to keep nFreeSlot above this */ @@ -48863,10 +57915,6 @@ static SQLITE_WSD struct PCacheGlobal { sqlite3_mutex *mutex; /* Mutex for accessing the following: */ PgFreeslot *pFree; /* Free page blocks */ int nFreeSlot; /* Number of unused pcache slots */ - /* The following value requires a mutex to change. We skip the mutex on - ** reading because (1) most platforms read a 32-bit integer atomically and - ** (2) even if an incorrect value is read, no great harm is done since this - ** is really just an optimization. */ int bUnderPressure; /* True if low on PAGECACHE memory */ } pcache1_g; @@ -48895,7 +57943,7 @@ static SQLITE_WSD struct PCacheGlobal { /* -** This function is called during initialization if a static buffer is +** This function is called during initialization if a static buffer is ** supplied to use for the page-cache by passing the SQLITE_CONFIG_PAGECACHE ** verb to sqlite3_config(). Parameter pBuf points to an allocation large ** enough to contain 'n' buffers of 'sz' bytes each. @@ -48914,7 +57962,7 @@ SQLITE_PRIVATE void sqlite3PCacheBufferSetup(void *pBuf, int sz, int n){ pcache1.nReserve = n>90 ? 10 : (n/10 + 1); pcache1.pStart = pBuf; pcache1.pFree = 0; - pcache1.bUnderPressure = 0; + AtomicStore(&pcache1.bUnderPressure,0); while( n-- ){ p = (PgFreeslot*)pBuf; p->pNext = pcache1.pFree; @@ -48944,29 +57992,32 @@ static int pcache1InitBulk(PCache1 *pCache){ if( szBulk > pCache->szAlloc*(i64)pCache->nMax ){ szBulk = pCache->szAlloc*(i64)pCache->nMax; } - zBulk = pCache->pBulk = sqlite3Malloc( szBulk ); - sqlite3EndBenignMalloc(); - if( zBulk ){ - int nBulk = sqlite3MallocSize(zBulk)/pCache->szAlloc; - do{ - PgHdr1 *pX = (PgHdr1*)&zBulk[pCache->szPage]; - pX->page.pBuf = zBulk; - pX->page.pExtra = &pX[1]; - pX->isBulkLocal = 1; - pX->isAnchor = 0; - pX->pNext = pCache->pFree; - pX->pLruPrev = 0; /* Initializing this saves a valgrind error */ - pCache->pFree = pX; - zBulk += pCache->szAlloc; - }while( --nBulk ); + if( szBulk>=pCache->szAlloc ){ + zBulk = pCache->pBulk = sqlite3Malloc( szBulk ); + sqlite3EndBenignMalloc(); + if( zBulk ){ + int nBulk = sqlite3MallocSize(zBulk)/pCache->szAlloc; + do{ + PgHdr1 *pX = (PgHdr1*)&zBulk[pCache->szPage]; + pX->page.pBuf = zBulk; + pX->page.pExtra = (u8*)pX + ROUND8(sizeof(*pX)); + assert( EIGHT_BYTE_ALIGNMENT( pX->page.pExtra ) ); + pX->isBulkLocal = 1; + pX->isAnchor = 0; + pX->pNext = pCache->pFree; + pX->pLruPrev = 0; /* Initializing this saves a valgrind error */ + pCache->pFree = pX; + zBulk += pCache->szAlloc; + }while( --nBulk ); + } } return pCache->pFree!=0; } /* ** Malloc function used within this file to allocate space from the buffer -** configured using sqlite3_config(SQLITE_CONFIG_PAGECACHE) option. If no -** such buffer exists or there is no space left in it, this function falls +** configured using sqlite3_config(SQLITE_CONFIG_PAGECACHE) option. If no +** such buffer exists or there is no space left in it, this function falls ** back to sqlite3Malloc(). ** ** Multiple threads can run this routine at the same time. Global variables @@ -48981,7 +58032,7 @@ static void *pcache1Alloc(int nByte){ if( p ){ pcache1.pFree = pcache1.pFree->pNext; pcache1.nFreeSlot--; - pcache1.bUnderPressure = pcache1.nFreeSlot=0 ); sqlite3StatusHighwater(SQLITE_STATUS_PAGECACHE_SIZE, nByte); sqlite3StatusUp(SQLITE_STATUS_PAGECACHE_USED, 1); @@ -49020,7 +58071,7 @@ static void pcache1Free(void *p){ pSlot->pNext = pcache1.pFree; pcache1.pFree = pSlot; pcache1.nFreeSlot++; - pcache1.bUnderPressure = pcache1.nFreeSlotpGroup->mutex) ); if( pCache->pFree || (pCache->nPage==0 && pcache1InitBulk(pCache)) ){ + assert( pCache->pFree!=0 ); p = pCache->pFree; pCache->pFree = p->pNext; p->pNext = 0; }else{ #ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT /* The group mutex must be released before pcache1Alloc() is called. This - ** is because it might call sqlite3_release_memory(), which assumes that + ** is because it might call sqlite3_release_memory(), which assumes that ** this mutex is not held. */ assert( pcache1.separateCache==0 ); assert( pCache->pGroup==&pcache1.grp ); pcache1LeaveMutex(pCache->pGroup); #endif if( benignMalloc ){ sqlite3BeginBenignMalloc(); } -#ifdef SQLITE_PCACHE_SEPARATE_HEADER - pPg = pcache1Alloc(pCache->szPage); - p = sqlite3Malloc(sizeof(PgHdr1) + pCache->szExtra); - if( !pPg || !p ){ - pcache1Free(pPg); - sqlite3_free(p); - pPg = 0; - } -#else pPg = pcache1Alloc(pCache->szAlloc); - p = (PgHdr1 *)&((u8 *)pPg)[pCache->szPage]; -#endif if( benignMalloc ){ sqlite3EndBenignMalloc(); } #ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT pcache1EnterMutex(pCache->pGroup); #endif if( pPg==0 ) return 0; + p = (PgHdr1 *)&((u8 *)pPg)[pCache->szPage]; p->page.pBuf = pPg; - p->page.pExtra = &p[1]; + p->page.pExtra = (u8*)p + ROUND8(sizeof(*p)); + assert( EIGHT_BYTE_ALIGNMENT( p->page.pExtra ) ); p->isBulkLocal = 0; p->isAnchor = 0; + p->pLruPrev = 0; /* Initializing this saves a valgrind error */ } (*pCache->pnPurgeable)++; return p; @@ -49118,9 +58162,6 @@ static void pcache1FreePage(PgHdr1 *p){ pCache->pFree = p; }else{ pcache1Free(p->page.pBuf); -#ifdef SQLITE_PCACHE_SEPARATE_HEADER - sqlite3_free(p); -#endif } (*pCache->pnPurgeable)--; } @@ -49161,7 +58202,7 @@ SQLITE_PRIVATE void sqlite3PageFree(void *p){ */ static int pcache1UnderMemoryPressure(PCache1 *pCache){ if( pcache1.nSlot && (pCache->szPage+pCache->szExtra)<=pcache1.szSlot ){ - return pcache1.bUnderPressure; + return AtomicLoad(&pcache1.bUnderPressure); }else{ return sqlite3HeapNearlyFull(); } @@ -49178,12 +58219,12 @@ static int pcache1UnderMemoryPressure(PCache1 *pCache){ */ static void pcache1ResizeHash(PCache1 *p){ PgHdr1 **apNew; - unsigned int nNew; - unsigned int i; + u64 nNew; + u32 i; assert( sqlite3_mutex_held(p->pGroup->mutex) ); - nNew = p->nHash*2; + nNew = 2*(u64)p->nHash; if( nNew<256 ){ nNew = 256; } @@ -49211,7 +58252,7 @@ static void pcache1ResizeHash(PCache1 *p){ } /* -** This function is used internally to remove the page pPage from the +** This function is used internally to remove the page pPage from the ** PGroup LRU list, if is part of it. If pPage is not part of the PGroup ** LRU list, then this function is a no-op. ** @@ -49236,7 +58277,7 @@ static PgHdr1 *pcache1PinPage(PgHdr1 *pPage){ /* -** Remove the page supplied as an argument from the hash table +** Remove the page supplied as an argument from the hash table ** (PCache1.apHash structure) that it is currently stored in. ** Also free the page if freePage is true. ** @@ -49279,8 +58320,8 @@ static void pcache1EnforceMaxPage(PCache1 *pCache){ } /* -** Discard all pages from cache pCache with a page number (key value) -** greater than or equal to iLimit. Any pinned pages that meet this +** Discard all pages from cache pCache with a page number (key value) +** greater than or equal to iLimit. Any pinned pages that meet this ** criteria are unpinned before they are discarded. ** ** The PCache mutex must be held when this function is called. @@ -49312,7 +58353,7 @@ static void pcache1TruncateUnsafe( PgHdr1 **pp; PgHdr1 *pPage; assert( hnHash ); - pp = &pCache->apHash[h]; + pp = &pCache->apHash[h]; while( (pPage = *pp)!=0 ){ if( pPage->iKey>=iLimit ){ pCache->nPage--; @@ -49351,7 +58392,7 @@ static int pcache1Init(void *NotUsed){ ** ** * Use a unified cache in single-threaded applications that have ** configured a start-time buffer for use as page-cache memory using - ** sqlite3_config(SQLITE_CONFIG_PAGECACHE, pBuf, sz, N) with non-NULL + ** sqlite3_config(SQLITE_CONFIG_PAGECACHE, pBuf, sz, N) with non-NULL ** pBuf argument. ** ** * Otherwise use separate caches (mode-1) @@ -49386,7 +58427,7 @@ static int pcache1Init(void *NotUsed){ /* ** Implementation of the sqlite3_pcache.xShutdown method. -** Note that the static mutex allocated in xInit does +** Note that the static mutex allocated in xInit does ** not need to be freed. */ static void pcache1Shutdown(void *NotUsed){ @@ -49406,7 +58447,7 @@ static void pcache1Destroy(sqlite3_pcache *p); static sqlite3_pcache *pcache1Create(int szPage, int szExtra, int bPurgeable){ PCache1 *pCache; /* The newly created page cache */ PGroup *pGroup; /* The group the new page cache will belong to */ - int sz; /* Bytes of memory required to allocate the new cache */ + i64 sz; /* Bytes of memory required to allocate the new cache */ assert( (szPage & (szPage-1))==0 && szPage>=512 && szPage<=65536 ); assert( szExtra < 300 ); @@ -49420,6 +58461,7 @@ static sqlite3_pcache *pcache1Create(int szPage, int szExtra, int bPurgeable){ }else{ pGroup = &pcache1.grp; } + pcache1EnterMutex(pGroup); if( pGroup->lru.isAnchor==0 ){ pGroup->lru.isAnchor = 1; pGroup->lru.pLruPrev = pGroup->lru.pLruNext = &pGroup->lru; @@ -49429,7 +58471,6 @@ static sqlite3_pcache *pcache1Create(int szPage, int szExtra, int bPurgeable){ pCache->szExtra = szExtra; pCache->szAlloc = szPage + szExtra + ROUND8(sizeof(PgHdr1)); pCache->bPurgeable = (bPurgeable ? 1 : 0); - pcache1EnterMutex(pGroup); pcache1ResizeHash(pCache); if( bPurgeable ){ pCache->nMin = 10; @@ -49449,18 +58490,24 @@ static sqlite3_pcache *pcache1Create(int szPage, int szExtra, int bPurgeable){ } /* -** Implementation of the sqlite3_pcache.xCachesize method. +** Implementation of the sqlite3_pcache.xCachesize method. ** ** Configure the cache_size limit for a cache. */ static void pcache1Cachesize(sqlite3_pcache *p, int nMax){ PCache1 *pCache = (PCache1 *)p; + u32 n; + assert( nMax>=0 ); if( pCache->bPurgeable ){ PGroup *pGroup = pCache->pGroup; pcache1EnterMutex(pGroup); - pGroup->nMaxPage += (nMax - pCache->nMax); + n = (u32)nMax; + if( n > 0x7fff0000 - pGroup->nMaxPage + pCache->nMax ){ + n = 0x7fff0000 - pGroup->nMaxPage + pCache->nMax; + } + pGroup->nMaxPage += (n - pCache->nMax); pGroup->mxPinned = pGroup->nMaxPage + 10 - pGroup->nMinPage; - pCache->nMax = nMax; + pCache->nMax = n; pCache->n90pct = pCache->nMax*9/10; pcache1EnforceMaxPage(pCache); pcache1LeaveMutex(pGroup); @@ -49468,7 +58515,7 @@ static void pcache1Cachesize(sqlite3_pcache *p, int nMax){ } /* -** Implementation of the sqlite3_pcache.xShrink method. +** Implementation of the sqlite3_pcache.xShrink method. ** ** Free up as much memory as possible. */ @@ -49476,7 +58523,7 @@ static void pcache1Shrink(sqlite3_pcache *p){ PCache1 *pCache = (PCache1*)p; if( pCache->bPurgeable ){ PGroup *pGroup = pCache->pGroup; - int savedMaxPage; + unsigned int savedMaxPage; pcache1EnterMutex(pGroup); savedMaxPage = pGroup->nMaxPage; pGroup->nMaxPage = 0; @@ -49487,7 +58534,7 @@ static void pcache1Shrink(sqlite3_pcache *p){ } /* -** Implementation of the sqlite3_pcache.xPagecount method. +** Implementation of the sqlite3_pcache.xPagecount method. */ static int pcache1Pagecount(sqlite3_pcache *p){ int n; @@ -49508,8 +58555,8 @@ static int pcache1Pagecount(sqlite3_pcache *p){ ** for these steps, the main pcache1Fetch() procedure can run faster. */ static SQLITE_NOINLINE PgHdr1 *pcache1FetchStage2( - PCache1 *pCache, - unsigned int iKey, + PCache1 *pCache, + unsigned int iKey, int createFlag ){ unsigned int nPinned; @@ -49551,8 +58598,8 @@ static SQLITE_NOINLINE PgHdr1 *pcache1FetchStage2( } } - /* Step 5. If a usable page buffer has still not been found, - ** attempt to allocate a new one. + /* Step 5. If a usable page buffer has still not been found, + ** attempt to allocate a new one. */ if( !pPage ){ pPage = pcache1AllocPage(pCache, createFlag==1); @@ -49577,13 +58624,13 @@ static SQLITE_NOINLINE PgHdr1 *pcache1FetchStage2( } /* -** Implementation of the sqlite3_pcache.xFetch method. +** Implementation of the sqlite3_pcache.xFetch method. ** ** Fetch a page by key value. ** ** Whether or not a new page may be allocated by this function depends on ** the value of the createFlag argument. 0 means do not allocate a new -** page. 1 means allocate a new page if space is easily available. 2 +** page. 1 means allocate a new page if space is easily available. 2 ** means to try really hard to allocate a new page. ** ** For a non-purgeable cache (a cache used as the storage for an in-memory @@ -49594,7 +58641,7 @@ static SQLITE_NOINLINE PgHdr1 *pcache1FetchStage2( ** There are three different approaches to obtaining space for a page, ** depending on the value of parameter createFlag (which may be 0, 1 or 2). ** -** 1. Regardless of the value of createFlag, the cache is searched for a +** 1. Regardless of the value of createFlag, the cache is searched for a ** copy of the requested page. If one is found, it is returned. ** ** 2. If createFlag==0 and the page is not already in the cache, NULL is @@ -49608,13 +58655,13 @@ static SQLITE_NOINLINE PgHdr1 *pcache1FetchStage2( ** PCache1.nMax, or ** ** (b) the number of pages pinned by the cache is greater than -** the sum of nMax for all purgeable caches, less the sum of +** the sum of nMax for all purgeable caches, less the sum of ** nMin for all other purgeable caches, or ** ** 4. If none of the first three conditions apply and the cache is marked ** as purgeable, and if one of the following is true: ** -** (a) The number of pages allocated for the cache is already +** (a) The number of pages allocated for the cache is already ** PCache1.nMax, or ** ** (b) The number of pages allocated for all purgeable caches is @@ -49626,7 +58673,7 @@ static SQLITE_NOINLINE PgHdr1 *pcache1FetchStage2( ** ** then attempt to recycle a page from the LRU list. If it is the right ** size, return the recycled buffer. Otherwise, free the buffer and -** proceed to step 5. +** proceed to step 5. ** ** 5. Otherwise, allocate and return a new page buffer. ** @@ -49636,8 +58683,8 @@ static SQLITE_NOINLINE PgHdr1 *pcache1FetchStage2( ** invokes the appropriate routine. */ static PgHdr1 *pcache1FetchNoMutex( - sqlite3_pcache *p, - unsigned int iKey, + sqlite3_pcache *p, + unsigned int iKey, int createFlag ){ PCache1 *pCache = (PCache1 *)p; @@ -49666,8 +58713,8 @@ static PgHdr1 *pcache1FetchNoMutex( } #if PCACHE1_MIGHT_USE_GROUP_MUTEX static PgHdr1 *pcache1FetchWithMutex( - sqlite3_pcache *p, - unsigned int iKey, + sqlite3_pcache *p, + unsigned int iKey, int createFlag ){ PCache1 *pCache = (PCache1 *)p; @@ -49681,8 +58728,8 @@ static PgHdr1 *pcache1FetchWithMutex( } #endif static sqlite3_pcache_page *pcache1Fetch( - sqlite3_pcache *p, - unsigned int iKey, + sqlite3_pcache *p, + unsigned int iKey, int createFlag ){ #if PCACHE1_MIGHT_USE_GROUP_MUTEX || defined(SQLITE_DEBUG) @@ -49712,18 +58759,18 @@ static sqlite3_pcache_page *pcache1Fetch( ** Mark a page as unpinned (eligible for asynchronous recycling). */ static void pcache1Unpin( - sqlite3_pcache *p, - sqlite3_pcache_page *pPg, + sqlite3_pcache *p, + sqlite3_pcache_page *pPg, int reuseUnlikely ){ PCache1 *pCache = (PCache1 *)p; PgHdr1 *pPage = (PgHdr1 *)pPg; PGroup *pGroup = pCache->pGroup; - + assert( pPage->pCache==pCache ); pcache1EnterMutex(pGroup); - /* It is an error to call this function if the page is already + /* It is an error to call this function if the page is already ** part of the PGroup LRU list. */ assert( pPage->pLruNext==0 ); @@ -49744,7 +58791,7 @@ static void pcache1Unpin( } /* -** Implementation of the sqlite3_pcache.xRekey method. +** Implementation of the sqlite3_pcache.xRekey method. */ static void pcache1Rekey( sqlite3_pcache *p, @@ -49755,23 +58802,26 @@ static void pcache1Rekey( PCache1 *pCache = (PCache1 *)p; PgHdr1 *pPage = (PgHdr1 *)pPg; PgHdr1 **pp; - unsigned int h; + unsigned int hOld, hNew; assert( pPage->iKey==iOld ); assert( pPage->pCache==pCache ); + assert( iOld!=iNew ); /* The page number really is changing */ pcache1EnterMutex(pCache->pGroup); - h = iOld%pCache->nHash; - pp = &pCache->apHash[h]; + assert( pcache1FetchNoMutex(p, iOld, 0)==pPage ); /* pPg really is iOld */ + hOld = iOld%pCache->nHash; + pp = &pCache->apHash[hOld]; while( (*pp)!=pPage ){ pp = &(*pp)->pNext; } *pp = pPage->pNext; - h = iNew%pCache->nHash; + assert( pcache1FetchNoMutex(p, iNew, 0)==0 ); /* iNew not in cache */ + hNew = iNew%pCache->nHash; pPage->iKey = iNew; - pPage->pNext = pCache->apHash[h]; - pCache->apHash[h] = pPage; + pPage->pNext = pCache->apHash[hNew]; + pCache->apHash[hNew] = pPage; if( iNew>pCache->iMaxKey ){ pCache->iMaxKey = iNew; } @@ -49780,7 +58830,7 @@ static void pcache1Rekey( } /* -** Implementation of the sqlite3_pcache.xTruncate method. +** Implementation of the sqlite3_pcache.xTruncate method. ** ** Discard all unpinned pages in the cache with a page number equal to ** or greater than parameter iLimit. Any pinned pages with a page number @@ -49797,7 +58847,7 @@ static void pcache1Truncate(sqlite3_pcache *p, unsigned int iLimit){ } /* -** Implementation of the sqlite3_pcache.xDestroy method. +** Implementation of the sqlite3_pcache.xDestroy method. ** ** Destroy a cache allocated using pcache1Create(). */ @@ -49863,7 +58913,7 @@ SQLITE_PRIVATE sqlite3_mutex *sqlite3Pcache1Mutex(void){ ** by the current thread may be sqlite3_free()ed. ** ** nReq is the number of bytes of memory required. Once this much has -** been released, the function returns. The return value is the total number +** been released, the function returns. The return value is the total number ** of bytes of memory released. */ SQLITE_PRIVATE int sqlite3PcacheReleaseMemory(int nReq){ @@ -49878,9 +58928,6 @@ SQLITE_PRIVATE int sqlite3PcacheReleaseMemory(int nReq){ && p->isAnchor==0 ){ nFree += pcache1MemSize(p->page.pBuf); -#ifdef SQLITE_PCACHE_SEPARATE_HEADER - nFree += sqlite3MemSize(p); -#endif assert( PAGE_IS_UNPINNED(p) ); pcache1PinPage(p); pcache1RemoveFromHash(p, 1); @@ -49954,14 +59001,14 @@ SQLITE_PRIVATE void sqlite3PcacheStats( ** extracts the least value from the RowSet. ** ** The INSERT primitive might allocate additional memory. Memory is -** allocated in chunks so most INSERTs do no allocation. There is an +** allocated in chunks so most INSERTs do no allocation. There is an ** upper bound on the size of allocated memory. No memory is freed ** until DESTROY. ** ** The TEST primitive includes a "batch" number. The TEST primitive ** will only see elements that were inserted before the last change ** in the batch number. In other words, if an INSERT occurs between -** two TESTs where the TESTs have the same batch nubmer, then the +** two TESTs where the TESTs have the same batch number, then the ** value added by the INSERT will not be visible to the second TEST. ** The initial batch number is zero, so if the very first TEST contains ** a non-zero batch number, it will see all prior INSERTs. @@ -50002,7 +59049,7 @@ SQLITE_PRIVATE void sqlite3PcacheStats( ** in the list, pLeft points to the tree, and v is unused. The ** RowSet.pForest value points to the head of this forest list. */ -struct RowSetEntry { +struct RowSetEntry { i64 v; /* ROWID value for this entry */ struct RowSetEntry *pRight; /* Right subtree (larger entries) or list */ struct RowSetEntry *pLeft; /* Left subtree (smaller entries) */ @@ -50096,7 +59143,7 @@ SQLITE_PRIVATE void sqlite3RowSetDelete(void *pArg){ /* ** Allocate a new RowSetEntry object that is associated with the ** given RowSet. Return a pointer to the new and completely uninitialized -** objected. +** object. ** ** In an OOM situation, the RowSet.db->mallocFailed flag is set and this ** routine returns NULL. @@ -50154,7 +59201,7 @@ SQLITE_PRIVATE void sqlite3RowSetInsert(RowSet *p, i64 rowid){ /* ** Merge two lists of RowSetEntry objects. Remove duplicates. ** -** The input lists are connected via pRight pointers and are +** The input lists are connected via pRight pointers and are ** assumed to each already be in sorted order. */ static struct RowSetEntry *rowSetEntryMerge( @@ -50191,7 +59238,7 @@ static struct RowSetEntry *rowSetEntryMerge( /* ** Sort all elements on the list of RowSetEntry objects into order of ** increasing v. -*/ +*/ static struct RowSetEntry *rowSetEntrySort(struct RowSetEntry *pIn){ unsigned int i; struct RowSetEntry *pNext, *aBucket[40]; @@ -50264,7 +59311,7 @@ static struct RowSetEntry *rowSetNDeepTree( struct RowSetEntry *pLeft; /* Left subtree */ if( *ppList==0 ){ /*OPTIMIZATION-IF-TRUE*/ /* Prevent unnecessary deep recursion when we run out of entries */ - return 0; + return 0; } if( iDepth>1 ){ /*OPTIMIZATION-IF-TRUE*/ /* This branch causes a *balanced* tree to be generated. A valid tree @@ -50372,7 +59419,7 @@ SQLITE_PRIVATE int sqlite3RowSetTest(RowSet *pRowSet, int iBatch, sqlite3_int64 if( p ){ struct RowSetEntry **ppPrevTree = &pRowSet->pForest; if( (pRowSet->rsFlags & ROWSET_SORTED)==0 ){ /*OPTIMIZATION-IF-FALSE*/ - /* Only sort the current set of entiries if they need it */ + /* Only sort the current set of entries if they need it */ p = rowSetEntrySort(p); } for(pTree = pRowSet->pForest; pTree; pTree=pTree->pRight){ @@ -50434,7 +59481,7 @@ SQLITE_PRIVATE int sqlite3RowSetTest(RowSet *pRowSet, int iBatch, sqlite3_int64 ** ************************************************************************* ** This is the implementation of the page cache subsystem or "pager". -** +** ** The pager is used to access a database disk file. It implements ** atomic commit and rollback through the use of a journal file that ** is separate from the database file. The pager also implements file @@ -50457,8 +59504,8 @@ SQLITE_PRIVATE int sqlite3RowSetTest(RowSet *pRowSet, int iBatch, sqlite3_int64 ** May you share freely, never taking more than you give. ** ************************************************************************* -** This header file defines the interface to the write-ahead logging -** system. Refer to the comments below and the header comment attached to +** This header file defines the interface to the write-ahead logging +** system. Refer to the comments below and the header comment attached to ** the implementation of each function in log.c for further details. */ @@ -50493,12 +59540,13 @@ SQLITE_PRIVATE int sqlite3RowSetTest(RowSet *pRowSet, int iBatch, sqlite3_int64 # define sqlite3WalFramesize(z) 0 # define sqlite3WalFindFrame(x,y,z) 0 # define sqlite3WalFile(x) 0 +# undef SQLITE_USE_SEH #else #define WAL_SAVEPOINT_NDATA 4 -/* Connection to a write-ahead log (WAL) file. -** There is one object of this type for each pager. +/* Connection to a write-ahead log (WAL) file. +** There is one object of this type for each pager. */ typedef struct Wal Wal; @@ -50509,7 +59557,7 @@ SQLITE_PRIVATE int sqlite3WalClose(Wal *pWal, sqlite3*, int sync_flags, int, u8 /* Set the limiting size of a WAL file. */ SQLITE_PRIVATE void sqlite3WalLimit(Wal*, i64); -/* Used by readers to open (lock) and close (unlock) a snapshot. A +/* Used by readers to open (lock) and close (unlock) a snapshot. A ** snapshot is like a read-transaction. It is the state of the database ** at an instant in time. sqlite3WalOpenSnapshot gets a read lock and ** preserves the current state even if the other threads or processes @@ -50544,7 +59592,7 @@ SQLITE_PRIVATE int sqlite3WalSavepointUndo(Wal *pWal, u32 *aWalData); /* Write a frame or frames to the log. */ SQLITE_PRIVATE int sqlite3WalFrames(Wal *pWal, int, PgHdr *, Pgno, int, int); -/* Copy pages from the log to the database file */ +/* Copy pages from the log to the database file */ SQLITE_PRIVATE int sqlite3WalCheckpoint( Wal *pWal, /* Write-ahead log connection */ sqlite3 *db, /* Check this handle's interrupt flag */ @@ -50572,7 +59620,7 @@ SQLITE_PRIVATE int sqlite3WalExclusiveMode(Wal *pWal, int op); /* Return true if the argument is non-NULL and the WAL module is using ** heap-memory for the wal-index. Otherwise, if the argument is NULL or the -** WAL module is using shared-memory, return false. +** WAL module is using shared-memory, return false. */ SQLITE_PRIVATE int sqlite3WalHeapMemory(Wal *pWal); @@ -50594,6 +59642,15 @@ SQLITE_PRIVATE int sqlite3WalFramesize(Wal *pWal); /* Return the sqlite3_file object for the WAL file */ SQLITE_PRIVATE sqlite3_file *sqlite3WalFile(Wal *pWal); +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT +SQLITE_PRIVATE int sqlite3WalWriteLock(Wal *pWal, int bLock); +SQLITE_PRIVATE void sqlite3WalDb(Wal *pWal, sqlite3 *db); +#endif + +#ifdef SQLITE_USE_SEH +SQLITE_PRIVATE int sqlite3WalSystemErrno(Wal*); +#endif + #endif /* ifndef SQLITE_OMIT_WAL */ #endif /* SQLITE_WAL_H */ @@ -50614,60 +59671,60 @@ SQLITE_PRIVATE sqlite3_file *sqlite3WalFile(Wal *pWal); ** ** Definition: A page of the database file is said to be "overwriteable" if ** one or more of the following are true about the page: -** +** ** (a) The original content of the page as it was at the beginning of ** the transaction has been written into the rollback journal and ** synced. -** +** ** (b) The page was a freelist leaf page at the start of the transaction. -** +** ** (c) The page number is greater than the largest page that existed in ** the database file at the start of the transaction. -** +** ** (1) A page of the database file is never overwritten unless one of the ** following are true: -** +** ** (a) The page and all other pages on the same sector are overwriteable. -** +** ** (b) The atomic page write optimization is enabled, and the entire ** transaction other than the update of the transaction sequence ** number consists of a single page change. -** +** ** (2) The content of a page written into the rollback journal exactly matches ** both the content in the database when the rollback journal was written ** and the content in the database at the beginning of the current ** transaction. -** +** ** (3) Writes to the database file are an integer multiple of the page size ** in length and are aligned on a page boundary. -** +** ** (4) Reads from the database file are either aligned on a page boundary and ** an integer multiple of the page size in length or are taken from the ** first 100 bytes of the database file. -** +** ** (5) All writes to the database file are synced prior to the rollback journal ** being deleted, truncated, or zeroed. -** -** (6) If a master journal file is used, then all writes to the database file -** are synced prior to the master journal being deleted. -** +** +** (6) If a super-journal file is used, then all writes to the database file +** are synced prior to the super-journal being deleted. +** ** Definition: Two databases (or the same database at two points it time) ** are said to be "logically equivalent" if they give the same answer to ** all queries. Note in particular the content of freelist leaf ** pages can be changed arbitrarily without affecting the logical equivalence ** of the database. -** +** ** (7) At any time, if any subset, including the empty set and the total set, -** of the unsynced changes to a rollback journal are removed and the +** of the unsynced changes to a rollback journal are removed and the ** journal is rolled back, the resulting database file will be logically ** equivalent to the database file at the beginning of the transaction. -** +** ** (8) When a transaction is rolled back, the xTruncate method of the VFS ** is called to restore the database file to the same size it was at ** the beginning of the transaction. (In some VFSes, the xTruncate ** method is a no-op, but that does not change the fact the SQLite will ** invoke it.) -** +** ** (9) Whenever the database file is modified, at least one bit in the range ** of bytes from 24 through 39 inclusive will be changed prior to releasing ** the EXCLUSIVE lock, thus signaling other connections on the same @@ -50700,7 +59757,7 @@ int sqlite3PagerTrace=1; /* True to enable tracing */ /* ** The following two macros are used within the PAGERTRACE() macros above -** to print out file-descriptors. +** to print out file-descriptors. ** ** PAGERID() takes a pointer to a Pager struct as its argument. The ** associated file-descriptor is returned. FILEHANDLEID() takes an sqlite3_file @@ -50721,7 +59778,7 @@ int sqlite3PagerTrace=1; /* True to enable tracing */ ** | | | ** | V | ** |<-------WRITER_LOCKED------> ERROR -** | | ^ +** | | ^ ** | V | ** |<------WRITER_CACHEMOD-------->| ** | | | @@ -50733,7 +59790,7 @@ int sqlite3PagerTrace=1; /* True to enable tracing */ ** ** ** List of state transitions and the C [function] that performs each: -** +** ** OPEN -> READER [sqlite3PagerSharedLock] ** READER -> OPEN [pager_unlock] ** @@ -50745,7 +59802,7 @@ int sqlite3PagerTrace=1; /* True to enable tracing */ ** ** WRITER_*** -> ERROR [pager_error] ** ERROR -> OPEN [pager_unlock] -** +** ** ** OPEN: ** @@ -50759,9 +59816,9 @@ int sqlite3PagerTrace=1; /* True to enable tracing */ ** ** READER: ** -** In this state all the requirements for reading the database in +** In this state all the requirements for reading the database in ** rollback (non-WAL) mode are met. Unless the pager is (or recently -** was) in exclusive-locking mode, a user-level read transaction is +** was) in exclusive-locking mode, a user-level read transaction is ** open. The database size is known in this state. ** ** A connection running with locking_mode=normal enters this state when @@ -50771,28 +59828,28 @@ int sqlite3PagerTrace=1; /* True to enable tracing */ ** this state even after the read-transaction is closed. The only way ** a locking_mode=exclusive connection can transition from READER to OPEN ** is via the ERROR state (see below). -** +** ** * A read transaction may be active (but a write-transaction cannot). ** * A SHARED or greater lock is held on the database file. -** * The dbSize variable may be trusted (even if a user-level read +** * The dbSize variable may be trusted (even if a user-level read ** transaction is not active). The dbOrigSize and dbFileSize variables ** may not be trusted at this point. ** * If the database is a WAL database, then the WAL connection is open. -** * Even if a read-transaction is not open, it is guaranteed that +** * Even if a read-transaction is not open, it is guaranteed that ** there is no hot-journal in the file-system. ** ** WRITER_LOCKED: ** ** The pager moves to this state from READER when a write-transaction -** is first opened on the database. In WRITER_LOCKED state, all locks -** required to start a write-transaction are held, but no actual +** is first opened on the database. In WRITER_LOCKED state, all locks +** required to start a write-transaction are held, but no actual ** modifications to the cache or database have taken place. ** -** In rollback mode, a RESERVED or (if the transaction was opened with +** In rollback mode, a RESERVED or (if the transaction was opened with ** BEGIN EXCLUSIVE) EXCLUSIVE lock is obtained on the database file when -** moving to this state, but the journal file is not written to or opened -** to in this state. If the transaction is committed or rolled back while -** in WRITER_LOCKED state, all that is required is to unlock the database +** moving to this state, but the journal file is not written to or opened +** to in this state. If the transaction is committed or rolled back while +** in WRITER_LOCKED state, all that is required is to unlock the database ** file. ** ** IN WAL mode, WalBeginWriteTransaction() is called to lock the log file. @@ -50800,7 +59857,7 @@ int sqlite3PagerTrace=1; /* True to enable tracing */ ** is made to obtain an EXCLUSIVE lock on the database file. ** ** * A write transaction is active. -** * If the connection is open in rollback-mode, a RESERVED or greater +** * If the connection is open in rollback-mode, a RESERVED or greater ** lock is held on the database file. ** * If the connection is open in WAL-mode, a WAL write transaction ** is open (i.e. sqlite3WalBeginWriteTransaction() has been successfully @@ -50819,7 +59876,7 @@ int sqlite3PagerTrace=1; /* True to enable tracing */ ** ** * A write transaction is active. ** * A RESERVED or greater lock is held on the database file. -** * The journal file is open and the first header has been written +** * The journal file is open and the first header has been written ** to it, but the header has not been synced to disk. ** * The contents of the page cache have been modified. ** @@ -50832,7 +59889,7 @@ int sqlite3PagerTrace=1; /* True to enable tracing */ ** ** * A write transaction is active. ** * An EXCLUSIVE or greater lock is held on the database file. -** * The journal file is open and the first header has been written +** * The journal file is open and the first header has been written ** and synced to disk. ** * The contents of the page cache have been modified (and possibly ** written to disk). @@ -50844,8 +59901,8 @@ int sqlite3PagerTrace=1; /* True to enable tracing */ ** A rollback-mode pager changes to WRITER_FINISHED state from WRITER_DBMOD ** state after the entire transaction has been successfully written into the ** database file. In this state the transaction may be committed simply -** by finalizing the journal file. Once in WRITER_FINISHED state, it is -** not possible to modify the database further. At this point, the upper +** by finalizing the journal file. Once in WRITER_FINISHED state, it is +** not possible to modify the database further. At this point, the upper ** layer must either commit or rollback the transaction. ** ** * A write transaction is active. @@ -50853,19 +59910,19 @@ int sqlite3PagerTrace=1; /* True to enable tracing */ ** * All writing and syncing of journal and database data has finished. ** If no error occurred, all that remains is to finalize the journal to ** commit the transaction. If an error did occur, the caller will need -** to rollback the transaction. +** to rollback the transaction. ** ** ERROR: ** ** The ERROR state is entered when an IO or disk-full error (including -** SQLITE_IOERR_NOMEM) occurs at a point in the code that makes it -** difficult to be sure that the in-memory pager state (cache contents, +** SQLITE_IOERR_NOMEM) occurs at a point in the code that makes it +** difficult to be sure that the in-memory pager state (cache contents, ** db size etc.) are consistent with the contents of the file-system. ** ** Temporary pager files may enter the ERROR state, but in-memory pagers ** cannot. ** -** For example, if an IO error occurs while performing a rollback, +** For example, if an IO error occurs while performing a rollback, ** the contents of the page-cache may be left in an inconsistent state. ** At this point it would be dangerous to change back to READER state ** (as usually happens after a rollback). Any subsequent readers might @@ -50875,13 +59932,13 @@ int sqlite3PagerTrace=1; /* True to enable tracing */ ** instead of READER following such an error. ** ** Once it has entered the ERROR state, any attempt to use the pager -** to read or write data returns an error. Eventually, once all +** to read or write data returns an error. Eventually, once all ** outstanding transactions have been abandoned, the pager is able to -** transition back to OPEN state, discarding the contents of the +** transition back to OPEN state, discarding the contents of the ** page-cache and any other in-memory state at the same time. Everything -** is reloaded from disk (and, if necessary, hot-journal rollback peformed) +** is reloaded from disk (and, if necessary, hot-journal rollback performed) ** when a read-transaction is next opened on the pager (transitioning -** the pager into READER state). At that point the system has recovered +** the pager into READER state). At that point the system has recovered ** from the error. ** ** Specifically, the pager jumps into the ERROR state if: @@ -50897,21 +59954,21 @@ int sqlite3PagerTrace=1; /* True to enable tracing */ ** memory. ** ** In other cases, the error is returned to the b-tree layer. The b-tree -** layer then attempts a rollback operation. If the error condition +** layer then attempts a rollback operation. If the error condition ** persists, the pager enters the ERROR state via condition (1) above. ** ** Condition (3) is necessary because it can be triggered by a read-only ** statement executed within a transaction. In this case, if the error ** code were simply returned to the user, the b-tree layer would not ** automatically attempt a rollback, as it assumes that an error in a -** read-only statement cannot leave the pager in an internally inconsistent +** read-only statement cannot leave the pager in an internally inconsistent ** state. ** ** * The Pager.errCode variable is set to something other than SQLITE_OK. ** * There are one or more outstanding references to pages (after the ** last reference is dropped the pager should move back to OPEN state). ** * The pager is not an in-memory pager. -** +** ** ** Notes: ** @@ -50921,7 +59978,7 @@ int sqlite3PagerTrace=1; /* True to enable tracing */ ** ** * Normally, a connection open in exclusive mode is never in PAGER_OPEN ** state. There are two exceptions: immediately after exclusive-mode has -** been turned on (and before any read or write transactions are +** been turned on (and before any read or write transactions are ** executed), and when the pager is leaving the "error state". ** ** * See also: assert_pager_state(). @@ -50935,7 +59992,7 @@ int sqlite3PagerTrace=1; /* True to enable tracing */ #define PAGER_ERROR 6 /* -** The Pager.eLock variable is almost always set to one of the +** The Pager.eLock variable is almost always set to one of the ** following locking-states, according to the lock currently held on ** the database file: NO_LOCK, SHARED_LOCK, RESERVED_LOCK or EXCLUSIVE_LOCK. ** This variable is kept up to date as locks are taken and released by @@ -50950,20 +60007,20 @@ int sqlite3PagerTrace=1; /* True to enable tracing */ ** to a less exclusive (lower) value than the lock that is actually held ** at the system level, but it is never set to a more exclusive value. ** -** This is usually safe. If an xUnlock fails or appears to fail, there may +** This is usually safe. If an xUnlock fails or appears to fail, there may ** be a few redundant xLock() calls or a lock may be held for longer than ** required, but nothing really goes wrong. ** ** The exception is when the database file is unlocked as the pager moves -** from ERROR to OPEN state. At this point there may be a hot-journal file +** from ERROR to OPEN state. At this point there may be a hot-journal file ** in the file-system that needs to be rolled back (as part of an OPEN->SHARED ** transition, by the same pager or any other). If the call to xUnlock() ** fails at this point and the pager is left holding an EXCLUSIVE lock, this ** can confuse the call to xCheckReservedLock() call made later as part ** of hot-journal detection. ** -** xCheckReservedLock() is defined as returning true "if there is a RESERVED -** lock held by this process or any others". So xCheckReservedLock may +** xCheckReservedLock() is defined as returning true "if there is a RESERVED +** lock held by this process or any others". So xCheckReservedLock may ** return true because the caller itself is holding an EXCLUSIVE lock (but ** doesn't know it because of a previous error in xUnlock). If this happens ** a hot-journal may be mistaken for a journal being created by an active @@ -50974,32 +60031,18 @@ int sqlite3PagerTrace=1; /* True to enable tracing */ ** database in the ERROR state, Pager.eLock is set to UNKNOWN_LOCK. It ** is only changed back to a real locking state after a successful call ** to xLock(EXCLUSIVE). Also, the code to do the OPEN->SHARED state transition -** omits the check for a hot-journal if Pager.eLock is set to UNKNOWN_LOCK +** omits the check for a hot-journal if Pager.eLock is set to UNKNOWN_LOCK ** lock. Instead, it assumes a hot-journal exists and obtains an EXCLUSIVE ** lock on the database file before attempting to roll it back. See function ** PagerSharedLock() for more detail. ** -** Pager.eLock may only be set to UNKNOWN_LOCK when the pager is in +** Pager.eLock may only be set to UNKNOWN_LOCK when the pager is in ** PAGER_OPEN state. */ #define UNKNOWN_LOCK (EXCLUSIVE_LOCK+1) /* -** A macro used for invoking the codec if there is one -*/ -#ifdef SQLITE_HAS_CODEC -# define CODEC1(P,D,N,X,E) \ - if( P->xCodec && P->xCodec(P->pCodec,D,N,X)==0 ){ E; } -# define CODEC2(P,D,N,X,E,O) \ - if( P->xCodec==0 ){ O=(char*)D; }else \ - if( (O=(char*)(P->xCodec(P->pCodec,D,N,X)))==0 ){ E; } -#else -# define CODEC1(P,D,N,X,E) /* NO-OP */ -# define CODEC2(P,D,N,X,E,O) O=(char*)D -#endif - -/* -** The maximum allowed sector size. 64KiB. If the xSectorsize() method +** The maximum allowed sector size. 64KiB. If the xSectorsize() method ** returns a value larger than this, then MAX_SECTOR_SIZE is used instead. ** This could conceivably cause corruption following a power failure on ** such a system. This is currently an undocumented limit. @@ -51015,7 +60058,7 @@ int sqlite3PagerTrace=1; /* True to enable tracing */ ** ** When a savepoint is created, the PagerSavepoint.iHdrOffset field is ** set to 0. If a journal-header is written into the main journal while -** the savepoint is active, then iHdrOffset is set to the byte offset +** the savepoint is active, then iHdrOffset is set to the byte offset ** immediately following the last journal record written into the main ** journal before the journal-header. This is required during savepoint ** rollback (see pagerPlaybackSavepoint()). @@ -51027,6 +60070,7 @@ struct PagerSavepoint { Bitvec *pInSavepoint; /* Set of pages in this savepoint */ Pgno nOrig; /* Original number of pages in file */ Pgno iSubRec; /* Index of first record in sub-journal */ + int bTruncateOnRelease; /* If stmt journal may be truncated on RELEASE */ #ifndef SQLITE_OMIT_WAL u32 aWalData[WAL_SAVEPOINT_NDATA]; /* WAL savepoint context */ #endif @@ -51065,44 +60109,44 @@ struct PagerSavepoint { ** ** changeCountDone ** -** This boolean variable is used to make sure that the change-counter -** (the 4-byte header field at byte offset 24 of the database file) is -** not updated more often than necessary. +** This boolean variable is used to make sure that the change-counter +** (the 4-byte header field at byte offset 24 of the database file) is +** not updated more often than necessary. ** -** It is set to true when the change-counter field is updated, which +** It is set to true when the change-counter field is updated, which ** can only happen if an exclusive lock is held on the database file. -** It is cleared (set to false) whenever an exclusive lock is +** It is cleared (set to false) whenever an exclusive lock is ** relinquished on the database file. Each time a transaction is committed, ** The changeCountDone flag is inspected. If it is true, the work of ** updating the change-counter is omitted for the current transaction. ** -** This mechanism means that when running in exclusive mode, a connection +** This mechanism means that when running in exclusive mode, a connection ** need only update the change-counter once, for the first transaction ** committed. ** -** setMaster +** setSuper ** ** When PagerCommitPhaseOne() is called to commit a transaction, it may -** (or may not) specify a master-journal name to be written into the +** (or may not) specify a super-journal name to be written into the ** journal file before it is synced to disk. ** -** Whether or not a journal file contains a master-journal pointer affects -** the way in which the journal file is finalized after the transaction is +** Whether or not a journal file contains a super-journal pointer affects +** the way in which the journal file is finalized after the transaction is ** committed or rolled back when running in "journal_mode=PERSIST" mode. -** If a journal file does not contain a master-journal pointer, it is +** If a journal file does not contain a super-journal pointer, it is ** finalized by overwriting the first journal header with zeroes. If -** it does contain a master-journal pointer the journal file is finalized -** by truncating it to zero bytes, just as if the connection were +** it does contain a super-journal pointer the journal file is finalized +** by truncating it to zero bytes, just as if the connection were ** running in "journal_mode=truncate" mode. ** -** Journal files that contain master journal pointers cannot be finalized +** Journal files that contain super-journal pointers cannot be finalized ** simply by overwriting the first journal-header with zeroes, as the -** master journal pointer could interfere with hot-journal rollback of any +** super-journal pointer could interfere with hot-journal rollback of any ** subsequently interrupted transaction that reuses the journal file. ** ** The flag is cleared as soon as the journal file is finalized (either ** by PagerCommitPhaseTwo or PagerRollback). If an IO error prevents the -** journal file from being successfully finalized, the setMaster flag +** journal file from being successfully finalized, the setSuper flag ** is cleared anyway (and the pager will move to ERROR state). ** ** doNotSpill @@ -51118,12 +60162,12 @@ struct PagerSavepoint { ** to allocate a new page to prevent the journal file from being written ** while it is being traversed by code in pager_playback(). The SPILLFLAG_OFF ** case is a user preference. -** +** ** If the SPILLFLAG_NOSYNC bit is set, writing to the database from ** pagerStress() is permitted, but syncing the journal file is not. ** This flag is set by sqlite3PagerWrite() when the file-system sector-size ** is larger than the database page-size in order to prevent a journal sync -** from happening in between the journalling of two pages on the same sector. +** from happening in between the journalling of two pages on the same sector. ** ** subjInMemory ** @@ -51131,16 +60175,16 @@ struct PagerSavepoint { ** is opened as an in-memory journal file. If false, then in-memory ** sub-journals are only used for in-memory pager files. ** -** This variable is updated by the upper layer each time a new +** This variable is updated by the upper layer each time a new ** write-transaction is opened. ** ** dbSize, dbOrigSize, dbFileSize ** ** Variable dbSize is set to the number of pages in the database file. ** It is valid in PAGER_READER and higher states (all states except for -** OPEN and ERROR). +** OPEN and ERROR). ** -** dbSize is set based on the size of the database file, which may be +** dbSize is set based on the size of the database file, which may be ** larger than the size of the database (the value stored at offset ** 28 of the database header by the btree). If the size of the file ** is not an integer multiple of the page-size, the value stored in @@ -51151,10 +60195,10 @@ struct PagerSavepoint { ** ** During a write-transaction, if pages with page-numbers greater than ** dbSize are modified in the cache, dbSize is updated accordingly. -** Similarly, if the database is truncated using PagerTruncateImage(), +** Similarly, if the database is truncated using PagerTruncateImage(), ** dbSize is updated. ** -** Variables dbOrigSize and dbFileSize are valid in states +** Variables dbOrigSize and dbFileSize are valid in states ** PAGER_WRITER_LOCKED and higher. dbOrigSize is a copy of the dbSize ** variable at the start of the transaction. It is used during rollback, ** and to determine whether or not pages need to be journalled before @@ -51163,12 +60207,12 @@ struct PagerSavepoint { ** Throughout a write-transaction, dbFileSize contains the size of ** the file on disk in pages. It is set to a copy of dbSize when the ** write-transaction is first opened, and updated when VFS calls are made -** to write or truncate the database file on disk. +** to write or truncate the database file on disk. ** -** The only reason the dbFileSize variable is required is to suppress -** unnecessary calls to xTruncate() after committing a transaction. If, -** when a transaction is committed, the dbFileSize variable indicates -** that the database file is larger than the database image (Pager.dbSize), +** The only reason the dbFileSize variable is required is to suppress +** unnecessary calls to xTruncate() after committing a transaction. If, +** when a transaction is committed, the dbFileSize variable indicates +** that the database file is larger than the database image (Pager.dbSize), ** pager_truncate() is called. The pager_truncate() call uses xFilesize() ** to measure the database file on disk, and then truncates it if required. ** dbFileSize is not used when rolling back a transaction. In this case @@ -51179,20 +60223,20 @@ struct PagerSavepoint { ** dbHintSize ** ** The dbHintSize variable is used to limit the number of calls made to -** the VFS xFileControl(FCNTL_SIZE_HINT) method. +** the VFS xFileControl(FCNTL_SIZE_HINT) method. ** ** dbHintSize is set to a copy of the dbSize variable when a ** write-transaction is opened (at the same time as dbFileSize and ** dbOrigSize). If the xFileControl(FCNTL_SIZE_HINT) method is called, ** dbHintSize is increased to the number of pages that correspond to the -** size-hint passed to the method call. See pager_write_pagelist() for +** size-hint passed to the method call. See pager_write_pagelist() for ** details. ** ** errCode ** ** The Pager.errCode variable is only ever used in PAGER_ERROR state. It -** is set to zero in all other states. In PAGER_ERROR state, Pager.errCode -** is always set to SQLITE_FULL, SQLITE_IOERR or one of the SQLITE_IOERR_XXX +** is set to zero in all other states. In PAGER_ERROR state, Pager.errCode +** is always set to SQLITE_FULL, SQLITE_IOERR or one of the SQLITE_IOERR_XXX ** sub-codes. ** ** syncFlags, walSyncFlags @@ -51221,6 +60265,7 @@ struct Pager { u8 noLock; /* Do not lock (except in WAL mode) */ u8 readOnly; /* True for a read-only database */ u8 memDb; /* True to inhibit all file I/O */ + u8 memVfs; /* VFS-implemented memory database */ /************************************************************************** ** The following block contains those class members that change during @@ -51234,7 +60279,7 @@ struct Pager { u8 eState; /* Pager state (OPEN, READER, WRITER_LOCKED..) */ u8 eLock; /* Current lock held on database file */ u8 changeCountDone; /* Set after incrementing the change-counter */ - u8 setMaster; /* True if a m-j name has been written to jrnl */ + u8 setSuper; /* Super-jrnl name is written into jrnl */ u8 doNotSpill; /* Do not spill the cache when non-zero */ u8 subjInMemory; /* True to use in-memory sub-journals */ u8 bUseFetch; /* True to use xFetch() */ @@ -51270,36 +60315,34 @@ struct Pager { i16 nReserve; /* Number of unused bytes at end of each page */ u32 vfsFlags; /* Flags for sqlite3_vfs.xOpen() */ u32 sectorSize; /* Assumed sector size during rollback */ - int pageSize; /* Number of bytes in a page */ Pgno mxPgno; /* Maximum allowed size of the database */ + Pgno lckPgno; /* Page number for the locking page */ + i64 pageSize; /* Number of bytes in a page */ i64 journalSizeLimit; /* Size limit for persistent journal files */ char *zFilename; /* Name of the database file */ char *zJournal; /* Name of the journal file */ int (*xBusyHandler)(void*); /* Function to call when busy */ void *pBusyHandlerArg; /* Context argument for xBusyHandler */ - int aStat[4]; /* Total cache hits, misses, writes, spills */ + u32 aStat[4]; /* Total cache hits, misses, writes, spills */ #ifdef SQLITE_TEST int nRead; /* Database pages read */ #endif void (*xReiniter)(DbPage*); /* Call this routine when reloading pages */ int (*xGet)(Pager*,Pgno,DbPage**,int); /* Routine to fetch a patch */ -#ifdef SQLITE_HAS_CODEC - void *(*xCodec)(void*,void*,Pgno,int); /* Routine for en/decoding data */ - void (*xCodecSizeChng)(void*,int,int); /* Notify of page size changes */ - void (*xCodecFree)(void*); /* Destructor for the codec */ - void *pCodec; /* First argument to xCodec... methods */ -#endif char *pTmpSpace; /* Pager.pageSize bytes of space for tmp use */ PCache *pPCache; /* Pointer to page cache object */ #ifndef SQLITE_OMIT_WAL Wal *pWal; /* Write-ahead log used by "journal_mode=wal" */ char *zWal; /* File name for write-ahead log */ #endif +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + sqlite3 *dbWal; +#endif }; /* ** Indexes for use with Pager.aStat[]. The Pager.aStat[] array contains -** the values accessed by passing SQLITE_DBSTATUS_CACHE_HIT, CACHE_MISS +** the values accessed by passing SQLITE_DBSTATUS_CACHE_HIT, CACHE_MISS ** or CACHE_WRITE to sqlite3_db_status(). */ #define PAGER_STAT_HIT 0 @@ -51357,7 +60400,7 @@ static const unsigned char aJournalMagic[] = { #define JOURNAL_PG_SZ(pPager) ((pPager->pageSize) + 8) /* -** The journal header size for this pager. This is usually the same +** The journal header size for this pager. This is usually the same ** size as a single disk sector. See also setSectorSize(). */ #define JOURNAL_HDR_SZ(pPager) (pPager->sectorSize) @@ -51384,48 +60427,35 @@ static const unsigned char aJournalMagic[] = { # define USEFETCH(x) 0 #endif -/* -** The maximum legal page number is (2^31 - 1). -*/ -#define PAGER_MAX_PGNO 2147483647 - -/* -** The argument to this macro is a file descriptor (type sqlite3_file*). -** Return 0 if it is not open, or non-zero (but not 1) if it is. -** -** This is so that expressions can be written as: -** -** if( isOpen(pPager->jfd) ){ ... -** -** instead of -** -** if( pPager->jfd->pMethods ){ ... -*/ -#define isOpen(pFd) ((pFd)->pMethods!=0) - #ifdef SQLITE_DIRECT_OVERFLOW_READ /* ** Return true if page pgno can be read directly from the database file ** by the b-tree layer. This is the case if: ** -** * the database file is open, -** * there are no dirty pages in the cache, and -** * the desired page is not currently in the wal file. +** (1) the database file is open +** (2) the VFS for the database is able to do unaligned sub-page reads +** (3) there are no dirty pages in the cache, and +** (4) the desired page is not currently in the wal file. */ SQLITE_PRIVATE int sqlite3PagerDirectReadOk(Pager *pPager, Pgno pgno){ - if( pPager->fd->pMethods==0 ) return 0; - if( sqlite3PCacheIsDirty(pPager->pPCache) ) return 0; -#ifdef SQLITE_HAS_CODEC - if( pPager->xCodec!=0 ) return 0; -#endif + assert( pPager!=0 ); + assert( pPager->fd!=0 ); + if( pPager->fd->pMethods==0 ) return 0; /* Case (1) */ + if( sqlite3PCacheIsDirty(pPager->pPCache) ) return 0; /* Failed (3) */ #ifndef SQLITE_OMIT_WAL if( pPager->pWal ){ u32 iRead = 0; - int rc; - rc = sqlite3WalFindFrame(pPager->pWal, pgno, &iRead); - return (rc==SQLITE_OK && iRead==0); + (void)sqlite3WalFindFrame(pPager->pWal, pgno, &iRead); + if( iRead ) return 0; /* Case (4) */ } +#else + UNUSED_PARAMETER(pgno); #endif + assert( pPager->fd->pMethods->xDeviceCharacteristics!=0 ); + if( (pPager->fd->pMethods->xDeviceCharacteristics(pPager->fd) + & SQLITE_IOCAP_SUBPAGE_READ)==0 ){ + return 0; /* Case (2) */ + } return 1; } #endif @@ -51440,7 +60470,7 @@ SQLITE_PRIVATE int sqlite3PagerDirectReadOk(Pager *pPager, Pgno pgno){ # define pagerBeginReadTransaction(z) SQLITE_OK #endif -#ifndef NDEBUG +#ifndef NDEBUG /* ** Usage: ** @@ -51469,25 +60499,25 @@ static int assert_pager_state(Pager *p){ assert( p->tempFile==0 || p->eLock==EXCLUSIVE_LOCK ); assert( p->tempFile==0 || pPager->changeCountDone ); - /* If the useJournal flag is clear, the journal-mode must be "OFF". + /* If the useJournal flag is clear, the journal-mode must be "OFF". ** And if the journal-mode is "OFF", the journal file must not be open. */ assert( p->journalMode==PAGER_JOURNALMODE_OFF || p->useJournal ); assert( p->journalMode!=PAGER_JOURNALMODE_OFF || !isOpen(p->jfd) ); - /* Check that MEMDB implies noSync. And an in-memory journal. Since - ** this means an in-memory pager performs no IO at all, it cannot encounter - ** either SQLITE_IOERR or SQLITE_FULL during rollback or while finalizing - ** a journal file. (although the in-memory journal implementation may - ** return SQLITE_IOERR_NOMEM while the journal file is being written). It - ** is therefore not possible for an in-memory pager to enter the ERROR + /* Check that MEMDB implies noSync. And an in-memory journal. Since + ** this means an in-memory pager performs no IO at all, it cannot encounter + ** either SQLITE_IOERR or SQLITE_FULL during rollback or while finalizing + ** a journal file. (although the in-memory journal implementation may + ** return SQLITE_IOERR_NOMEM while the journal file is being written). It + ** is therefore not possible for an in-memory pager to enter the ERROR ** state. */ if( MEMDB ){ assert( !isOpen(p->fd) ); assert( p->noSync ); - assert( p->journalMode==PAGER_JOURNALMODE_OFF - || p->journalMode==PAGER_JOURNALMODE_MEMORY + assert( p->journalMode==PAGER_JOURNALMODE_OFF + || p->journalMode==PAGER_JOURNALMODE_MEMORY ); assert( p->eState!=PAGER_ERROR && p->eState!=PAGER_OPEN ); assert( pagerUseWal(p)==0 ); @@ -51521,7 +60551,7 @@ static int assert_pager_state(Pager *p){ assert( pPager->dbSize==pPager->dbOrigSize ); assert( pPager->dbOrigSize==pPager->dbFileSize ); assert( pPager->dbOrigSize==pPager->dbHintSize ); - assert( pPager->setMaster==0 ); + assert( pPager->setSuper==0 ); break; case PAGER_WRITER_CACHEMOD: @@ -51534,9 +60564,9 @@ static int assert_pager_state(Pager *p){ ** to journal_mode=wal. */ assert( p->eLock>=RESERVED_LOCK ); - assert( isOpen(p->jfd) - || p->journalMode==PAGER_JOURNALMODE_OFF - || p->journalMode==PAGER_JOURNALMODE_WAL + assert( isOpen(p->jfd) + || p->journalMode==PAGER_JOURNALMODE_OFF + || p->journalMode==PAGER_JOURNALMODE_WAL ); } assert( pPager->dbOrigSize==pPager->dbFileSize ); @@ -51548,9 +60578,9 @@ static int assert_pager_state(Pager *p){ assert( pPager->errCode==SQLITE_OK ); assert( !pagerUseWal(pPager) ); assert( p->eLock>=EXCLUSIVE_LOCK ); - assert( isOpen(p->jfd) - || p->journalMode==PAGER_JOURNALMODE_OFF - || p->journalMode==PAGER_JOURNALMODE_WAL + assert( isOpen(p->jfd) + || p->journalMode==PAGER_JOURNALMODE_OFF + || p->journalMode==PAGER_JOURNALMODE_WAL || (sqlite3OsDeviceCharacteristics(p->fd)&SQLITE_IOCAP_BATCH_ATOMIC) ); assert( pPager->dbOrigSize<=pPager->dbHintSize ); @@ -51560,9 +60590,9 @@ static int assert_pager_state(Pager *p){ assert( p->eLock==EXCLUSIVE_LOCK ); assert( pPager->errCode==SQLITE_OK ); assert( !pagerUseWal(pPager) ); - assert( isOpen(p->jfd) - || p->journalMode==PAGER_JOURNALMODE_OFF - || p->journalMode==PAGER_JOURNALMODE_WAL + assert( isOpen(p->jfd) + || p->journalMode==PAGER_JOURNALMODE_OFF + || p->journalMode==PAGER_JOURNALMODE_WAL || (sqlite3OsDeviceCharacteristics(p->fd)&SQLITE_IOCAP_BATCH_ATOMIC) ); break; @@ -51581,7 +60611,7 @@ static int assert_pager_state(Pager *p){ } #endif /* ifndef NDEBUG */ -#ifdef SQLITE_DEBUG +#ifdef SQLITE_DEBUG /* ** Return a pointer to a human readable string in a static buffer ** containing the state of the Pager object passed as an argument. This @@ -51651,11 +60681,7 @@ static void setGetterMethod(Pager *pPager){ if( pPager->errCode ){ pPager->xGet = getPageError; #if SQLITE_MAX_MMAP_SIZE>0 - }else if( USEFETCH(pPager) -#ifdef SQLITE_HAS_CODEC - && pPager->xCodec==0 -#endif - ){ + }else if( USEFETCH(pPager) ){ pPager->xGet = getPageMMap; #endif /* SQLITE_MAX_MMAP_SIZE>0 */ }else{ @@ -51680,6 +60706,9 @@ static int subjRequiresPage(PgHdr *pPg){ for(i=0; inSavepoint; i++){ p = &pPager->aSavepoint[i]; if( p->nOrig>=pgno && 0==sqlite3BitvecTestNotNull(p->pInSavepoint, pgno) ){ + for(i=i+1; inSavepoint; i++){ + pPager->aSavepoint[i].bTruncateOnRelease = 0; + } return 1; } } @@ -51733,7 +60762,7 @@ static int write32bits(sqlite3_file *fd, i64 offset, u32 val){ ** succeeds, set the Pager.eLock variable to match the (attempted) new lock. ** ** Except, if Pager.eLock is set to UNKNOWN_LOCK when this function is -** called, do not modify it. See the comment above the #define of +** called, do not modify it. See the comment above the #define of ** UNKNOWN_LOCK for an explanation of this. */ static int pagerUnlockDb(Pager *pPager, int eLock){ @@ -51750,17 +60779,18 @@ static int pagerUnlockDb(Pager *pPager, int eLock){ } IOTRACE(("UNLOCK %p %d\n", pPager, eLock)) } + pPager->changeCountDone = pPager->tempFile; /* ticket fb3b3024ea238d5c */ return rc; } /* ** Lock the database file to level eLock, which must be either SHARED_LOCK, ** RESERVED_LOCK or EXCLUSIVE_LOCK. If the caller is successful, set the -** Pager.eLock variable to the new locking state. +** Pager.eLock variable to the new locking state. ** -** Except, if Pager.eLock is set to UNKNOWN_LOCK when this function is -** called, do not modify it unless the new locking state is EXCLUSIVE_LOCK. -** See the comment above the #define of UNKNOWN_LOCK for an explanation +** Except, if Pager.eLock is set to UNKNOWN_LOCK when this function is +** called, do not modify it unless the new locking state is EXCLUSIVE_LOCK. +** See the comment above the #define of UNKNOWN_LOCK for an explanation ** of this. */ static int pagerLockDb(Pager *pPager, int eLock){ @@ -51787,7 +60817,7 @@ static int pagerLockDb(Pager *pPager, int eLock){ ** (b) the value returned by OsSectorSize() is less than or equal ** to the page size. ** -** If it can be used, then the value returned is the size of the journal +** If it can be used, then the value returned is the size of the journal ** file when it contains rollback data for exactly one page. ** ** The atomic-batch-write optimization can be used if OsDeviceCharacteristics() @@ -51840,17 +60870,17 @@ static int jrnlBufferSize(Pager *pPager){ */ #ifdef SQLITE_CHECK_PAGES /* -** Return a 32-bit hash of the page data for pPage. +** Return a 64-bit hash of the page data for pPage. */ -static u32 pager_datahash(int nByte, unsigned char *pData){ - u32 hash = 0; +static u64 pager_datahash(int nByte, unsigned char *pData){ + u64 hash = 0; int i; for(i=0; ipPager->pageSize, (unsigned char *)pPage->pData); } static void pager_set_pagehash(PgHdr *pPage){ @@ -51877,73 +60907,85 @@ static void checkPage(PgHdr *pPg){ #endif /* SQLITE_CHECK_PAGES */ /* -** When this is called the journal file for pager pPager must be open. -** This function attempts to read a master journal file name from the -** end of the file and, if successful, copies it into memory supplied -** by the caller. See comments above writeMasterJournal() for the format -** used to store a master journal file name at the end of a journal file. -** -** zMaster must point to a buffer of at least nMaster bytes allocated by -** the caller. This should be sqlite3_vfs.mxPathname+1 (to ensure there is -** enough space to write the master journal name). If the master journal -** name in the journal is longer than nMaster bytes (including a -** nul-terminator), then this is handled as if no master journal name -** were present in the journal. +** Free a buffer allocated by the readSuperJournal() function. +*/ +static void freeSuperJournal(char *zSuper){ + if( zSuper ){ + sqlite3_free(&zSuper[-4]); + } +} + +/* +** Parameter pJrnl is a file-handle open on a journal file. This function +** attempts to read a super-journal file name from the end of the journal +** file. If successful, it sets output parameter (*pzSuper) to point to a +** buffer containing the super-journal name as a nul-terminated string. +** The caller is responsible for freeing the buffer using freeSuperJournal(). ** -** If a master journal file name is present at the end of the journal -** file, then it is copied into the buffer pointed to by zMaster. A -** nul-terminator byte is appended to the buffer following the master -** journal file name. +** Refer to comments above writeSuperJournal() for the format used to store +** a super-journal file name at the end of a journal file. ** -** If it is determined that no master journal file name is present -** zMaster[0] is set to 0 and SQLITE_OK returned. +** Parameter nSuper is passed the maximum allowable size of the super journal +** name in bytes. If the super-journal name in the journal is longer than +** nSuper bytes (including a nul-terminator), then this is handled as if no +** super-journal name were present in the journal. ** -** If an error occurs while reading from the journal file, an SQLite -** error code is returned. +** If there is no super-journal name at the end of pJrnl, (*pzSuper) is +** set to 0 and SQLITE_OK is returned. Or, if an error occurs while reading +** the super-journal name, an SQLite error code is returned and (*pzSuper) +** is set to 0. */ -static int readMasterJournal(sqlite3_file *pJrnl, char *zMaster, u32 nMaster){ +static int readSuperJournal(sqlite3_file *pJrnl, u64 nSuper, char **pzSuper){ int rc; /* Return code */ - u32 len; /* Length in bytes of master journal name */ + u32 len; /* Length in bytes of super-journal name */ i64 szJ; /* Total size in bytes of journal file pJrnl */ u32 cksum; /* MJ checksum value read from journal */ - u32 u; /* Unsigned loop counter */ unsigned char aMagic[8]; /* A buffer to hold the magic header */ - zMaster[0] = '\0'; + char *zOut = 0; + *pzSuper = 0; if( SQLITE_OK!=(rc = sqlite3OsFileSize(pJrnl, &szJ)) || szJ<16 || SQLITE_OK!=(rc = read32bits(pJrnl, szJ-16, &len)) - || len>=nMaster + || len>=nSuper || len>szJ-16 - || len==0 + || len==0 || SQLITE_OK!=(rc = read32bits(pJrnl, szJ-12, &cksum)) || SQLITE_OK!=(rc = sqlite3OsRead(pJrnl, aMagic, 8, szJ-8)) || memcmp(aMagic, aJournalMagic, 8) - || SQLITE_OK!=(rc = sqlite3OsRead(pJrnl, zMaster, len, szJ-16-len)) ){ return rc; } - /* See if the checksum matches the master journal name */ - for(u=0; ujournalOff, assuming a sector +** Return the offset of the sector boundary at or immediately +** following the value in pPager->journalOff, assuming a sector ** size of pPager->sectorSize bytes. ** ** i.e for a sector size of 512: @@ -51954,7 +60996,7 @@ static int readMasterJournal(sqlite3_file *pJrnl, char *zMaster, u32 nMaster){ ** 512 512 ** 100 512 ** 2000 2048 -** +** */ static i64 journalHdrOffset(Pager *pPager){ i64 offset = 0; @@ -51976,12 +61018,12 @@ static i64 journalHdrOffset(Pager *pPager){ ** ** If doTruncate is non-zero or the Pager.journalSizeLimit variable is ** set to 0, then truncate the journal file to zero bytes in size. Otherwise, -** zero the 28-byte header at the start of the journal file. In either case, -** if the pager is not in no-sync mode, sync the journal file immediately +** zero the 28-byte header at the start of the journal file. In either case, +** if the pager is not in no-sync mode, sync the journal file immediately ** after writing or truncating it. ** ** If Pager.journalSizeLimit is set to a positive, non-zero value, and -** following the truncation or zeroing described above the size of the +** following the truncation or zeroing described above the size of the ** journal file in bytes is larger than this value, then truncate the ** journal file to Pager.journalSizeLimit bytes. The journal file does ** not need to be synced following this operation. @@ -52007,8 +61049,8 @@ static int zeroJournalHdr(Pager *pPager, int doTruncate){ rc = sqlite3OsSync(pPager->jfd, SQLITE_SYNC_DATAONLY|pPager->syncFlags); } - /* At this point the transaction is committed but the write lock - ** is still held on the file. If there is a size limit configured for + /* At this point the transaction is committed but the write lock + ** is still held on the file. If there is a size limit configured for ** the persistent journal and the journal file currently consumes more ** space than that limit allows for, truncate it now. There is no need ** to sync the file following this operation. @@ -52036,7 +61078,7 @@ static int zeroJournalHdr(Pager *pPager, int doTruncate){ ** - 4 bytes: Initial database page count. ** - 4 bytes: Sector size used by the process that wrote this journal. ** - 4 bytes: Database page size. -** +** ** Followed by (JOURNAL_HDR_SZ - 28) bytes of unused space. */ static int writeJournalHdr(Pager *pPager){ @@ -52052,8 +61094,8 @@ static int writeJournalHdr(Pager *pPager){ nHeader = JOURNAL_HDR_SZ(pPager); } - /* If there are active savepoints and any of them were created - ** since the most recent journal header was written, update the + /* If there are active savepoints and any of them were created + ** since the most recent journal header was written, update the ** PagerSavepoint.iHdrOffset fields now. */ for(ii=0; iinSavepoint; ii++){ @@ -52064,10 +61106,10 @@ static int writeJournalHdr(Pager *pPager){ pPager->journalHdr = pPager->journalOff = journalHdrOffset(pPager); - /* + /* ** Write the nRec Field - the number of page records that follow this ** journal header. Normally, zero is written to this value at this time. - ** After the records are added to the journal (and the journal synced, + ** After the records are added to the journal (and the journal synced, ** if in full-sync mode), the zero is overwritten with the true number ** of records (see syncJournal()). ** @@ -52086,7 +61128,7 @@ static int writeJournalHdr(Pager *pPager){ */ assert( isOpen(pPager->fd) || pPager->noSync ); if( pPager->noSync || (pPager->journalMode==PAGER_JOURNALMODE_MEMORY) - || (sqlite3OsDeviceCharacteristics(pPager->fd)&SQLITE_IOCAP_SAFE_APPEND) + || (sqlite3OsDeviceCharacteristics(pPager->fd)&SQLITE_IOCAP_SAFE_APPEND) ){ memcpy(zHeader, aJournalMagic, sizeof(aJournalMagic)); put32bits(&zHeader[sizeof(aJournalMagic)], 0xffffffff); @@ -52094,9 +61136,32 @@ static int writeJournalHdr(Pager *pPager){ memset(zHeader, 0, sizeof(aJournalMagic)+4); } - /* The random check-hash initializer */ - sqlite3_randomness(sizeof(pPager->cksumInit), &pPager->cksumInit); + + + /* The random check-hash initializer */ + if( pPager->journalMode!=PAGER_JOURNALMODE_MEMORY ){ + sqlite3_randomness(sizeof(pPager->cksumInit), &pPager->cksumInit); + } +#ifdef SQLITE_DEBUG + else{ + /* The Pager.cksumInit variable is usually randomized above to protect + ** against there being existing records in the journal file. This is + ** dangerous, as following a crash they may be mistaken for records + ** written by the current transaction and rolled back into the database + ** file, causing corruption. The following assert statements verify + ** that this is not required in "journal_mode=memory" mode, as in that + ** case the journal file is always 0 bytes in size at this point. + ** It is advantageous to avoid the sqlite3_randomness() call if possible + ** as it takes the global PRNG mutex. */ + i64 sz = 0; + sqlite3OsFileSize(pPager->jfd, &sz); + assert( sz==0 ); + assert( pPager->journalOff==journalHdrOffset(pPager) ); + assert( sqlite3JournalIsInMemory(pPager->jfd) ); + } +#endif put32bits(&zHeader[sizeof(aJournalMagic)+4], pPager->cksumInit); + /* The initial database size */ put32bits(&zHeader[sizeof(aJournalMagic)+8], pPager->dbOrigSize); /* The assumed sector size for this process */ @@ -52113,23 +61178,23 @@ static int writeJournalHdr(Pager *pPager){ memset(&zHeader[sizeof(aJournalMagic)+20], 0, nHeader-(sizeof(aJournalMagic)+20)); - /* In theory, it is only necessary to write the 28 bytes that the - ** journal header consumes to the journal file here. Then increment the - ** Pager.journalOff variable by JOURNAL_HDR_SZ so that the next + /* In theory, it is only necessary to write the 28 bytes that the + ** journal header consumes to the journal file here. Then increment the + ** Pager.journalOff variable by JOURNAL_HDR_SZ so that the next ** record is written to the following sector (leaving a gap in the file ** that will be implicitly filled in by the OS). ** - ** However it has been discovered that on some systems this pattern can + ** However it has been discovered that on some systems this pattern can ** be significantly slower than contiguously writing data to the file, - ** even if that means explicitly writing data to the block of + ** even if that means explicitly writing data to the block of ** (JOURNAL_HDR_SZ - 28) bytes that will not be used. So that is what - ** is done. + ** is done. ** - ** The loop is required here in case the sector-size is larger than the + ** The loop is required here in case the sector-size is larger than the ** database page size. Since the zHeader buffer is only Pager.pageSize ** bytes in size, more than one call to sqlite3OsWrite() may be required ** to populate the entire journal header sector. - */ + */ for(nWrite=0; rc==SQLITE_OK&&nWritejournalHdr, nHeader)) rc = sqlite3OsWrite(pPager->jfd, zHeader, nHeader, pPager->journalOff); @@ -52227,29 +61292,29 @@ static int readJournalHdr( /* Check that the values read from the page-size and sector-size fields ** are within range. To be 'in range', both values need to be a power - ** of two greater than or equal to 512 or 32, and not greater than their + ** of two greater than or equal to 512 or 32, and not greater than their ** respective compile time maximum limits. */ if( iPageSize<512 || iSectorSize<32 || iPageSize>SQLITE_MAX_PAGE_SIZE || iSectorSize>MAX_SECTOR_SIZE - || ((iPageSize-1)&iPageSize)!=0 || ((iSectorSize-1)&iSectorSize)!=0 + || ((iPageSize-1)&iPageSize)!=0 || ((iSectorSize-1)&iSectorSize)!=0 ){ - /* If the either the page-size or sector-size in the journal-header is - ** invalid, then the process that wrote the journal-header must have - ** crashed before the header was synced. In this case stop reading + /* If the either the page-size or sector-size in the journal-header is + ** invalid, then the process that wrote the journal-header must have + ** crashed before the header was synced. In this case stop reading ** the journal file here. */ return SQLITE_DONE; } - /* Update the page-size to match the value read from the journal. - ** Use a testcase() macro to make sure that malloc failure within + /* Update the page-size to match the value read from the journal. + ** Use a testcase() macro to make sure that malloc failure within ** PagerSetPagesize() is tested. */ rc = sqlite3PagerSetPagesize(pPager, &iPageSize, -1); testcase( rc!=SQLITE_OK ); - /* Update the assumed sector-size to match the value used by + /* Update the assumed sector-size to match the value used by ** the process that created this journal. If this journal was ** created by a process other than this one, then this routine ** is being called from within pager_playback(). The local value @@ -52264,50 +61329,50 @@ static int readJournalHdr( /* -** Write the supplied master journal name into the journal file for pager -** pPager at the current location. The master journal name must be the last +** Write the supplied super-journal name into the journal file for pager +** pPager at the current location. The super-journal name must be the last ** thing written to a journal file. If the pager is in full-sync mode, the ** journal file descriptor is advanced to the next sector boundary before ** anything is written. The format is: ** -** + 4 bytes: PAGER_MJ_PGNO. -** + N bytes: Master journal filename in utf-8. -** + 4 bytes: N (length of master journal name in bytes, no nul-terminator). -** + 4 bytes: Master journal name checksum. +** + 4 bytes: PAGER_SJ_PGNO. +** + N bytes: super-journal filename in utf-8. +** + 4 bytes: N (length of super-journal name in bytes, no nul-terminator). +** + 4 bytes: super-journal name checksum. ** + 8 bytes: aJournalMagic[]. ** -** The master journal page checksum is the sum of the bytes in the master -** journal name, where each byte is interpreted as a signed 8-bit integer. +** The super-journal page checksum is the sum of the bytes in the super-journal +** name, where each byte is interpreted as a signed 8-bit integer. ** -** If zMaster is a NULL pointer (occurs for a single database transaction), +** If zSuper is a NULL pointer (occurs for a single database transaction), ** this call is a no-op. */ -static int writeMasterJournal(Pager *pPager, const char *zMaster){ +static int writeSuperJournal(Pager *pPager, const char *zSuper){ int rc; /* Return code */ - int nMaster; /* Length of string zMaster */ + int nSuper; /* Length of string zSuper */ i64 iHdrOff; /* Offset of header in journal file */ i64 jrnlSize; /* Size of journal file on disk */ - u32 cksum = 0; /* Checksum of string zMaster */ + u32 cksum = 0; /* Checksum of string zSuper */ - assert( pPager->setMaster==0 ); + assert( pPager->setSuper==0 ); assert( !pagerUseWal(pPager) ); - if( !zMaster - || pPager->journalMode==PAGER_JOURNALMODE_MEMORY + if( !zSuper + || pPager->journalMode==PAGER_JOURNALMODE_MEMORY || !isOpen(pPager->jfd) ){ return SQLITE_OK; } - pPager->setMaster = 1; + pPager->setSuper = 1; assert( pPager->journalHdr <= pPager->journalOff ); - /* Calculate the length in bytes and the checksum of zMaster */ - for(nMaster=0; zMaster[nMaster]; nMaster++){ - cksum += zMaster[nMaster]; + /* Calculate the length in bytes and the checksum of zSuper */ + for(nSuper=0; zSuper[nSuper]; nSuper++){ + cksum += zSuper[nSuper]; } /* If in full-sync mode, advance to the next disk sector before writing - ** the master journal name. This is in case the previous page written to + ** the super-journal name. This is in case the previous page written to ** the journal has already been synced. */ if( pPager->fullSync ){ @@ -52315,30 +61380,30 @@ static int writeMasterJournal(Pager *pPager, const char *zMaster){ } iHdrOff = pPager->journalOff; - /* Write the master journal data to the end of the journal file. If + /* Write the super-journal data to the end of the journal file. If ** an error occurs, return the error code to the caller. */ - if( (0 != (rc = write32bits(pPager->jfd, iHdrOff, PAGER_MJ_PGNO(pPager)))) - || (0 != (rc = sqlite3OsWrite(pPager->jfd, zMaster, nMaster, iHdrOff+4))) - || (0 != (rc = write32bits(pPager->jfd, iHdrOff+4+nMaster, nMaster))) - || (0 != (rc = write32bits(pPager->jfd, iHdrOff+4+nMaster+4, cksum))) + if( (0 != (rc = write32bits(pPager->jfd, iHdrOff, PAGER_SJ_PGNO(pPager)))) + || (0 != (rc = sqlite3OsWrite(pPager->jfd, zSuper, nSuper, iHdrOff+4))) + || (0 != (rc = write32bits(pPager->jfd, iHdrOff+4+nSuper, nSuper))) + || (0 != (rc = write32bits(pPager->jfd, iHdrOff+4+nSuper+4, cksum))) || (0 != (rc = sqlite3OsWrite(pPager->jfd, aJournalMagic, 8, - iHdrOff+4+nMaster+8))) + iHdrOff+4+nSuper+8))) ){ return rc; } - pPager->journalOff += (nMaster+20); + pPager->journalOff += (nSuper+20); - /* If the pager is in peristent-journal mode, then the physical - ** journal-file may extend past the end of the master-journal name - ** and 8 bytes of magic data just written to the file. This is + /* If the pager is in persistent-journal mode, then the physical + ** journal-file may extend past the end of the super-journal name + ** and 8 bytes of magic data just written to the file. This is ** dangerous because the code to rollback a hot-journal file - ** will not be able to find the master-journal name to determine - ** whether or not the journal is hot. + ** will not be able to find the super-journal name to determine + ** whether or not the journal is hot. ** - ** Easiest thing to do in this scenario is to truncate the journal + ** Easiest thing to do in this scenario is to truncate the journal ** file to the required size. - */ + */ if( SQLITE_OK==(rc = sqlite3OsFileSize(pPager->jfd, &jrnlSize)) && jrnlSize>pPager->journalOff ){ @@ -52383,7 +61448,7 @@ static void releaseAllSavepoints(Pager *pPager){ } /* -** Set the bit number pgno in the PagerSavepoint.pInSavepoint +** Set the bit number pgno in the PagerSavepoint.pInSavepoint ** bitvecs of all open savepoints. Return SQLITE_OK if successful ** or SQLITE_NOMEM if a malloc failure occurs. */ @@ -52412,8 +61477,8 @@ static int addToSavepointBitvecs(Pager *pPager, Pgno pgno){ ** not exhibit the UNDELETABLE_WHEN_OPEN property, the journal file is ** closed (if it is open). ** -** If the pager is in ERROR state when this function is called, the -** contents of the pager cache are discarded before switching back to +** If the pager is in ERROR state when this function is called, the +** contents of the pager cache are discarded before switching back to ** the OPEN state. Regardless of whether the pager is in exclusive-mode ** or not, any journal file left in the file-system will be treated ** as a hot-journal and rolled back the next time a read-transaction @@ -52421,9 +61486,9 @@ static int addToSavepointBitvecs(Pager *pPager, Pgno pgno){ */ static void pager_unlock(Pager *pPager){ - assert( pPager->eState==PAGER_READER - || pPager->eState==PAGER_OPEN - || pPager->eState==PAGER_ERROR + assert( pPager->eState==PAGER_READER + || pPager->eState==PAGER_OPEN + || pPager->eState==PAGER_ERROR ); sqlite3BitvecDestroy(pPager->pInJournal); @@ -52432,6 +61497,15 @@ static void pager_unlock(Pager *pPager){ if( pagerUseWal(pPager) ){ assert( !isOpen(pPager->jfd) ); + if( pPager->eState==PAGER_ERROR ){ + /* If an IO error occurs in wal.c while attempting to wrap the wal file, + ** then the Wal object may be holding a write-lock but no read-lock. + ** This call ensures that the write-lock is dropped as well. We cannot + ** have sqlite3WalEndReadTransaction() drop the write-lock, as it once + ** did, because this would break "BEGIN EXCLUSIVE" handling for + ** SQLITE_ENABLE_SETLK_TIMEOUT builds. */ + (void)sqlite3WalEndWriteTransaction(pPager->pWal); + } sqlite3WalEndReadTransaction(pPager->pWal); pPager->eState = PAGER_OPEN; }else if( !pPager->exclusiveMode ){ @@ -52470,7 +61544,6 @@ static void pager_unlock(Pager *pPager){ ** code is cleared and the cache reset in the block below. */ assert( pPager->errCode || pPager->eState!=PAGER_ERROR ); - pPager->changeCountDone = 0; pPager->eState = PAGER_OPEN; } @@ -52495,23 +61568,23 @@ static void pager_unlock(Pager *pPager){ pPager->journalOff = 0; pPager->journalHdr = 0; - pPager->setMaster = 0; + pPager->setSuper = 0; } /* ** This function is called whenever an IOERR or FULL error that requires -** the pager to transition into the ERROR state may ahve occurred. -** The first argument is a pointer to the pager structure, the second -** the error-code about to be returned by a pager API function. The -** value returned is a copy of the second argument to this function. +** the pager to transition into the ERROR state may have occurred. +** The first argument is a pointer to the pager structure, the second +** the error-code about to be returned by a pager API function. The +** value returned is a copy of the second argument to this function. ** ** If the second argument is SQLITE_FULL, SQLITE_IOERR or one of the ** IOERR sub-codes, the pager enters the ERROR state and the error code ** is stored in Pager.errCode. While the pager remains in the ERROR state, ** all major API calls on the Pager will immediately return Pager.errCode. ** -** The ERROR state indicates that the contents of the pager-cache -** cannot be trusted. This state can be cleared by completely discarding +** The ERROR state indicates that the contents of the pager-cache +** cannot be trusted. This state can be cleared by completely discarding ** the contents of the pager-cache. If a transaction was active when ** the persistent error occurred, then the rollback journal may need ** to be replayed to restore the contents of the database file (as if @@ -52559,27 +61632,27 @@ static int pagerFlushOnCommit(Pager *pPager, int bCommit){ } /* -** This routine ends a transaction. A transaction is usually ended by -** either a COMMIT or a ROLLBACK operation. This routine may be called +** This routine ends a transaction. A transaction is usually ended by +** either a COMMIT or a ROLLBACK operation. This routine may be called ** after rollback of a hot-journal, or if an error occurs while opening ** the journal file or writing the very first journal-header of a ** database transaction. -** +** ** This routine is never called in PAGER_ERROR state. If it is called ** in PAGER_NONE or PAGER_SHARED state and the lock held is less ** exclusive than a RESERVED lock, it is a no-op. ** ** Otherwise, any active savepoints are released. ** -** If the journal file is open, then it is "finalized". Once a journal -** file has been finalized it is not possible to use it to roll back a +** If the journal file is open, then it is "finalized". Once a journal +** file has been finalized it is not possible to use it to roll back a ** transaction. Nor will it be considered to be a hot-journal by this ** or any other database connection. Exactly how a journal is finalized ** depends on whether or not the pager is running in exclusive mode and ** the current journal-mode (Pager.journalMode value), as follows: ** ** journalMode==MEMORY -** Journal file descriptor is simply closed. This destroys an +** Journal file descriptor is simply closed. This destroys an ** in-memory journal. ** ** journalMode==TRUNCATE @@ -52599,19 +61672,19 @@ static int pagerFlushOnCommit(Pager *pPager, int bCommit){ ** journalMode==PERSIST is used instead. ** ** After the journal is finalized, the pager moves to PAGER_READER state. -** If running in non-exclusive rollback mode, the lock on the file is +** If running in non-exclusive rollback mode, the lock on the file is ** downgraded to a SHARED_LOCK. ** ** SQLITE_OK is returned if no error occurs. If an error occurs during ** any of the IO operations to finalize the journal file or unlock the -** database then the IO error code is returned to the user. If the +** database then the IO error code is returned to the user. If the ** operation to finalize the journal file fails, then the code still ** tries to unlock the database file if not in exclusive mode. If the ** unlock operation fails as well, then the first error code related ** to the first error encountered (the journal finalization one) is ** returned. */ -static int pager_end_transaction(Pager *pPager, int hasMaster, int bCommit){ +static int pager_end_transaction(Pager *pPager, int hasSuper, int bCommit){ int rc = SQLITE_OK; /* Error code from journal finalization operation */ int rc2 = SQLITE_OK; /* Error code from db file unlock operation */ @@ -52623,9 +61696,9 @@ static int pager_end_transaction(Pager *pPager, int hasMaster, int bCommit){ ** 1. After a successful hot-journal rollback, it is called with ** eState==PAGER_NONE and eLock==EXCLUSIVE_LOCK. ** - ** 2. If a connection with locking_mode=exclusive holding an EXCLUSIVE + ** 2. If a connection with locking_mode=exclusive holding an EXCLUSIVE ** lock switches back to locking_mode=normal and then executes a - ** read-transaction, this function is called with eState==PAGER_READER + ** read-transaction, this function is called with eState==PAGER_READER ** and eLock==EXCLUSIVE_LOCK when the read-transaction is closed. */ assert( assert_pager_state(pPager) ); @@ -52635,7 +61708,7 @@ static int pager_end_transaction(Pager *pPager, int hasMaster, int bCommit){ } releaseAllSavepoints(pPager); - assert( isOpen(pPager->jfd) || pPager->pInJournal==0 + assert( isOpen(pPager->jfd) || pPager->pInJournal==0 || (sqlite3OsDeviceCharacteristics(pPager->fd)&SQLITE_IOCAP_BATCH_ATOMIC) ); if( isOpen(pPager->jfd) ){ @@ -52661,9 +61734,9 @@ static int pager_end_transaction(Pager *pPager, int hasMaster, int bCommit){ } pPager->journalOff = 0; }else if( pPager->journalMode==PAGER_JOURNALMODE_PERSIST - || (pPager->exclusiveMode && pPager->journalMode!=PAGER_JOURNALMODE_WAL) + || (pPager->exclusiveMode && pPager->journalModetempFile); + rc = zeroJournalHdr(pPager, hasSuper||pPager->tempFile); pPager->journalOff = 0; }else{ /* This branch may be executed with Pager.journalMode==MEMORY if @@ -52673,9 +61746,9 @@ static int pager_end_transaction(Pager *pPager, int hasMaster, int bCommit){ */ int bDelete = !pPager->tempFile; assert( sqlite3JournalIsInMemory(pPager->jfd)==0 ); - assert( pPager->journalMode==PAGER_JOURNALMODE_DELETE - || pPager->journalMode==PAGER_JOURNALMODE_MEMORY - || pPager->journalMode==PAGER_JOURNALMODE_WAL + assert( pPager->journalMode==PAGER_JOURNALMODE_DELETE + || pPager->journalMode==PAGER_JOURNALMODE_MEMORY + || pPager->journalMode==PAGER_JOURNALMODE_WAL ); sqlite3OsClose(pPager->jfd); if( bDelete ){ @@ -52708,8 +61781,8 @@ static int pager_end_transaction(Pager *pPager, int hasMaster, int bCommit){ } if( pagerUseWal(pPager) ){ - /* Drop the WAL write-lock, if any. Also, if the connection was in - ** locking_mode=exclusive mode but is no longer, drop the EXCLUSIVE + /* Drop the WAL write-lock, if any. Also, if the connection was in + ** locking_mode=exclusive mode but is no longer, drop the EXCLUSIVE ** lock held on the database file. */ rc2 = sqlite3WalEndWriteTransaction(pPager->pWal); @@ -52717,7 +61790,7 @@ static int pager_end_transaction(Pager *pPager, int hasMaster, int bCommit){ }else if( rc==SQLITE_OK && bCommit && pPager->dbFileSize>pPager->dbSize ){ /* This branch is taken when committing a transaction in rollback-journal ** mode if the database file on disk is larger than the database image. - ** At this point the journal has been finalized and the transaction + ** At this point the journal has been finalized and the transaction ** successfully committed, but the EXCLUSIVE lock is still held on the ** file. So it is safe to truncate the database file to its minimum ** required size. */ @@ -52730,32 +61803,34 @@ static int pager_end_transaction(Pager *pPager, int hasMaster, int bCommit){ if( rc==SQLITE_NOTFOUND ) rc = SQLITE_OK; } - if( !pPager->exclusiveMode + if( !pPager->exclusiveMode && (!pagerUseWal(pPager) || sqlite3WalExclusiveMode(pPager->pWal, 0)) ){ rc2 = pagerUnlockDb(pPager, SHARED_LOCK); - pPager->changeCountDone = 0; } pPager->eState = PAGER_READER; - pPager->setMaster = 0; + pPager->setSuper = 0; return (rc==SQLITE_OK?rc2:rc); } +/* Forward reference */ +static int pager_playback(Pager *pPager, int isHot); + /* -** Execute a rollback if a transaction is active and unlock the -** database file. +** Execute a rollback if a transaction is active and unlock the +** database file. ** -** If the pager has already entered the ERROR state, do not attempt +** If the pager has already entered the ERROR state, do not attempt ** the rollback at this time. Instead, pager_unlock() is called. The ** call to pager_unlock() will discard all in-memory pages, unlock -** the database file and move the pager back to OPEN state. If this -** means that there is a hot-journal left in the file-system, the next -** connection to obtain a shared lock on the pager (which may be this one) +** the database file and move the pager back to OPEN state. If this +** means that there is a hot-journal left in the file-system, the next +** connection to obtain a shared lock on the pager (which may be this one) ** will roll it back. ** ** If the pager has not already entered the ERROR state, but an IO or -** malloc error occurs during a rollback, then this will itself cause +** malloc error occurs during a rollback, then this will itself cause ** the pager to enter the ERROR state. Which will be cleared by the ** call to pager_unlock(), as described above. */ @@ -52770,16 +61845,31 @@ static void pagerUnlockAndRollback(Pager *pPager){ assert( pPager->eState==PAGER_READER ); pager_end_transaction(pPager, 0, 0); } + }else if( pPager->eState==PAGER_ERROR + && pPager->journalMode==PAGER_JOURNALMODE_MEMORY + && isOpen(pPager->jfd) + ){ + /* Special case for a ROLLBACK due to I/O error with an in-memory + ** journal: We have to rollback immediately, before the journal is + ** closed, because once it is closed, all content is forgotten. */ + int errCode = pPager->errCode; + u8 eLock = pPager->eLock; + pPager->eState = PAGER_OPEN; + pPager->errCode = SQLITE_OK; + pPager->eLock = EXCLUSIVE_LOCK; + pager_playback(pPager, 1); + pPager->errCode = errCode; + pPager->eLock = eLock; } pager_unlock(pPager); } /* ** Parameter aData must point to a buffer of pPager->pageSize bytes -** of data. Compute and return a checksum based ont the contents of the +** of data. Compute and return a checksum based on the contents of the ** page of data and the current value of pPager->cksumInit. ** -** This is not a real checksum. It is really just the sum of the +** This is not a real checksum. It is really just the sum of the ** random initial value (pPager->cksumInit) and every 200th byte ** of the page data, starting with byte offset (pPager->pageSize%200). ** Each byte is interpreted as an 8-bit unsigned integer. @@ -52787,8 +61877,8 @@ static void pagerUnlockAndRollback(Pager *pPager){ ** Changing the formula used to compute this checksum results in an ** incompatible journal file format. ** -** If journal corruption occurs due to a power failure, the most likely -** scenario is that one end or the other of the record will be changed. +** If journal corruption occurs due to a power failure, the most likely +** scenario is that one end or the other of the record will be changed. ** It is much less likely that the two ends of the journal record will be ** correct and the middle be corrupt. Thus, this "checksum" scheme, ** though fast and simple, catches the mostly likely kind of corruption. @@ -52803,42 +61893,13 @@ static u32 pager_cksum(Pager *pPager, const u8 *aData){ return cksum; } -/* -** Report the current page size and number of reserved bytes back -** to the codec. -*/ -#ifdef SQLITE_HAS_CODEC -static void pagerReportSize(Pager *pPager){ - if( pPager->xCodecSizeChng ){ - pPager->xCodecSizeChng(pPager->pCodec, pPager->pageSize, - (int)pPager->nReserve); - } -} -#else -# define pagerReportSize(X) /* No-op if we do not support a codec */ -#endif - -#ifdef SQLITE_HAS_CODEC -/* -** Make sure the number of reserved bits is the same in the destination -** pager as it is in the source. This comes up when a VACUUM changes the -** number of reserved bits to the "optimal" amount. -*/ -SQLITE_PRIVATE void sqlite3PagerAlignReserve(Pager *pDest, Pager *pSrc){ - if( pDest->nReserve!=pSrc->nReserve ){ - pDest->nReserve = pSrc->nReserve; - pagerReportSize(pDest); - } -} -#endif - /* ** Read a single page from either the journal file (if isMainJrnl==1) or ** from the sub-journal (if isMainJrnl==0) and playback that page. ** The page begins at offset *pOffset into the file. The *pOffset ** value is increased to the start of the next page in the journal. ** -** The main rollback journal uses checksums - the statement journal does +** The main rollback journal uses checksums - the statement journal does ** not. ** ** If the page number of the page record read from the (sub-)journal file @@ -52858,8 +61919,8 @@ SQLITE_PRIVATE void sqlite3PagerAlignReserve(Pager *pDest, Pager *pSrc){ ** is successfully read from the (sub-)journal file but appears to be ** corrupted, SQLITE_DONE is returned. Data is considered corrupted in ** two circumstances: -** -** * If the record page-number is illegal (0 or PAGER_MJ_PGNO), or +** +** * If the record page-number is illegal (0 or PAGER_SJ_PGNO), or ** * If the record is being rolled back from the main journal file ** and the checksum field does not match the record content. ** @@ -52883,11 +61944,6 @@ static int pager_playback_one_page( char *aData; /* Temporary storage for the page */ sqlite3_file *jfd; /* The file descriptor for the journal file */ int isSynced; /* True if journal page is synced */ -#ifdef SQLITE_HAS_CODEC - /* The jrnlEnc flag is true if Journal pages should be passed through - ** the codec. It is false for pure in-memory journals. */ - const int jrnlEnc = (isMainJrnl || pPager->subjInMemory==0); -#endif assert( (isMainJrnl&~1)==0 ); /* isMainJrnl is 0 or 1 */ assert( (isSavepnt&~1)==0 ); /* isSavepnt is 0 or 1 */ @@ -52898,7 +61954,7 @@ static int pager_playback_one_page( assert( aData ); /* Temp storage must have already been allocated */ assert( pagerUseWal(pPager)==0 || (!isMainJrnl && isSavepnt) ); - /* Either the state is greater than PAGER_WRITER_CACHEMOD (a transaction + /* Either the state is greater than PAGER_WRITER_CACHEMOD (a transaction ** or savepoint rollback done at the request of the caller) or this is ** a hot-journal rollback. If it is a hot-journal rollback, the pager ** is in state OPEN and holds an EXCLUSIVE lock. Hot-journal rollback @@ -52924,7 +61980,7 @@ static int pager_playback_one_page( ** it could cause invalid data to be written into the journal. We need to ** detect this invalid data (with high probability) and ignore it. */ - if( pgno==0 || pgno==PAGER_MJ_PGNO(pPager) ){ + if( pgno==0 || pgno==PAGER_SJ_PGNO(pPager) ){ assert( !isSavepnt ); return SQLITE_DONE; } @@ -52950,7 +62006,6 @@ static int pager_playback_one_page( */ if( pgno==1 && pPager->nReserve!=((u8*)aData)[20] ){ pPager->nReserve = ((u8*)aData)[20]; - pagerReportSize(pPager); } /* If the pager is in CACHEMOD state, then there must be a copy of this @@ -52965,7 +62020,7 @@ static int pager_playback_one_page( ** assert()able. ** ** If in WRITER_DBMOD, WRITER_FINISHED or OPEN state, then we update the - ** pager cache if it exists and the main file. The page is then marked + ** pager cache if it exists and the main file. The page is then marked ** not dirty. Since this code is only executed in PAGER_OPEN state for ** a hot-journal rollback, it is guaranteed that the page-cache is empty ** if the pager is in OPEN state. @@ -53018,43 +62073,29 @@ static int pager_playback_one_page( ** is if the data was just read from an in-memory sub-journal. In that ** case it must be encrypted here before it is copied into the database ** file. */ -#ifdef SQLITE_HAS_CODEC - if( !jrnlEnc ){ - CODEC2(pPager, aData, pgno, 7, rc=SQLITE_NOMEM_BKPT, aData); - rc = sqlite3OsWrite(pPager->fd, (u8 *)aData, pPager->pageSize, ofst); - CODEC1(pPager, aData, pgno, 3, rc=SQLITE_NOMEM_BKPT); - }else -#endif rc = sqlite3OsWrite(pPager->fd, (u8 *)aData, pPager->pageSize, ofst); if( pgno>pPager->dbFileSize ){ pPager->dbFileSize = pgno; } if( pPager->pBackup ){ -#ifdef SQLITE_HAS_CODEC - if( jrnlEnc ){ - CODEC1(pPager, aData, pgno, 3, rc=SQLITE_NOMEM_BKPT); - sqlite3BackupUpdate(pPager->pBackup, pgno, (u8*)aData); - CODEC2(pPager, aData, pgno, 7, rc=SQLITE_NOMEM_BKPT,aData); - }else -#endif sqlite3BackupUpdate(pPager->pBackup, pgno, (u8*)aData); } }else if( !isMainJrnl && pPg==0 ){ /* If this is a rollback of a savepoint and data was not written to ** the database and the page is not in-memory, there is a potential - ** problem. When the page is next fetched by the b-tree layer, it - ** will be read from the database file, which may or may not be - ** current. + ** problem. When the page is next fetched by the b-tree layer, it + ** will be read from the database file, which may or may not be + ** current. ** ** There are a couple of different ways this can happen. All are quite - ** obscure. When running in synchronous mode, this can only happen + ** obscure. When running in synchronous mode, this can only happen ** if the page is on the free-list at the start of the transaction, then ** populated, then moved using sqlite3PagerMovepage(). ** ** The solution is to add an in-memory page to the cache containing - ** the data just read from the sub-journal. Mark the page as dirty - ** and if the pager requires a journal-sync, then mark the page as + ** the data just read from the sub-journal. Mark the page as dirty + ** and if the pager requires a journal-sync, then mark the page as ** requiring a journal-sync before it is written. */ assert( isSavepnt ); @@ -53088,164 +62129,222 @@ static int pager_playback_one_page( if( pgno==1 ){ memcpy(&pPager->dbFileVers, &((u8*)pData)[24],sizeof(pPager->dbFileVers)); } - - /* Decode the page just read from disk */ -#if SQLITE_HAS_CODEC - if( jrnlEnc ){ CODEC1(pPager, pData, pPg->pgno, 3, rc=SQLITE_NOMEM_BKPT); } -#endif sqlite3PcacheRelease(pPg); } return rc; } /* -** Parameter zMaster is the name of a master journal file. A single journal -** file that referred to the master journal file has just been rolled back. -** This routine checks if it is possible to delete the master journal file, +** Check if zSuper is a valid super-journal name. There are two valid +** formats: +** +** + The 3rd and 4th last bytes of the filename are ".9", and the +** following 2 bytes are hex digits. This is a file created in 8.3 +** filenames mode. +** +** + The 3rd last byte of the filename is "9" and the filename +** contains the string "-mj" starting at the 12th last byte. +** All bytes following the "-mj" are hex digits. +** +** If the filename matches either of these patterns, return non-zero. +** Otherwise, return zero. +*/ +static int pagerIsSuperJrnlName(const char *zSuper){ + const int nSuper = sqlite3Strlen30(zSuper); + int ii; + + if( nSuper<4 ) return 0; + if( zSuper[nSuper-3]!='9' ) return 0; +#ifdef SQLITE_ENABLE_8_3_NAMES + if( sqlite3Isxdigit(zSuper[nSuper-2])==0 ) return 0; + if( sqlite3Isxdigit(zSuper[nSuper-1])==0 ) return 0; + if( zSuper[nSuper-4]=='.' ) return 1; +#endif + if( nSuper<12 ) return 0; + if( memcmp(&zSuper[nSuper-12], "-mj", 3) ) return 0; + for(ii=nSuper-9; iipVfs; int rc; /* Return code */ - sqlite3_file *pMaster; /* Malloc'd master-journal file descriptor */ + sqlite3_file *pSuper; /* Malloc'd super-journal file descriptor */ sqlite3_file *pJournal; /* Malloc'd child-journal file descriptor */ - char *zMasterJournal = 0; /* Contents of master journal file */ - i64 nMasterJournal; /* Size of master journal file */ + char *zSuperJournal = 0; /* Contents of super-journal file */ + i64 nSuperJournal; /* Size of super-journal file */ char *zJournal; /* Pointer to one journal within MJ file */ - char *zMasterPtr; /* Space to hold MJ filename from a journal file */ - int nMasterPtr; /* Amount of space allocated to zMasterPtr[] */ + char *zFree = 0; /* Free this buffer */ + int bSeen = 0; /* If super-journal contains pPager->zJournal */ + + /* Check if this looks like a real super-journal name. If it does not, + ** return SQLITE_OK without attempting to delete it. This is to limit + ** the degree to which a crafted journal file can be used to cause + ** SQLite to delete arbitrary files. */ + if( pagerIsSuperJrnlName(zSuper)==0 ){ + return SQLITE_OK; + } - /* Allocate space for both the pJournal and pMaster file descriptors. - ** If successful, open the master journal file for reading. + /* Allocate space for both the pJournal and pSuper file descriptors. + ** If successful, open the super-journal file for reading. */ - pMaster = (sqlite3_file *)sqlite3MallocZero(pVfs->szOsFile * 2); - pJournal = (sqlite3_file *)(((u8 *)pMaster) + pVfs->szOsFile); - if( !pMaster ){ + pSuper = (sqlite3_file *)sqlite3MallocZero(2 * (i64)pVfs->szOsFile); + if( !pSuper ){ rc = SQLITE_NOMEM_BKPT; + pJournal = 0; }else{ - const int flags = (SQLITE_OPEN_READONLY|SQLITE_OPEN_MASTER_JOURNAL); - rc = sqlite3OsOpen(pVfs, zMaster, pMaster, flags, 0); + const int flags = (SQLITE_OPEN_READONLY|SQLITE_OPEN_SUPER_JOURNAL); + rc = sqlite3OsOpen(pVfs, zSuper, pSuper, flags, 0); + pJournal = (sqlite3_file *)(((u8 *)pSuper) + pVfs->szOsFile); } - if( rc!=SQLITE_OK ) goto delmaster_out; + if( rc!=SQLITE_OK ) goto delsuper_out; - /* Load the entire master journal file into space obtained from - ** sqlite3_malloc() and pointed to by zMasterJournal. Also obtain - ** sufficient space (in zMasterPtr) to hold the names of master - ** journal files extracted from regular rollback-journals. + /* Load the entire super-journal file into space obtained from + ** sqlite3_malloc() and pointed to by zSuperJournal. Also obtain + ** sufficient space (in zSuperPtr) to hold the names of super-journal + ** files extracted from regular rollback-journals. */ - rc = sqlite3OsFileSize(pMaster, &nMasterJournal); - if( rc!=SQLITE_OK ) goto delmaster_out; - nMasterPtr = pVfs->mxPathname+1; - zMasterJournal = sqlite3Malloc(nMasterJournal + nMasterPtr + 1); - if( !zMasterJournal ){ + rc = sqlite3OsFileSize(pSuper, &nSuperJournal); + if( rc!=SQLITE_OK ) goto delsuper_out; + assert( nSuperJournal>=0 ); + zFree = sqlite3Malloc(4 + nSuperJournal + 2); + if( !zFree ){ rc = SQLITE_NOMEM_BKPT; - goto delmaster_out; - } - zMasterPtr = &zMasterJournal[nMasterJournal+1]; - rc = sqlite3OsRead(pMaster, zMasterJournal, (int)nMasterJournal, 0); - if( rc!=SQLITE_OK ) goto delmaster_out; - zMasterJournal[nMasterJournal] = 0; - - zJournal = zMasterJournal; - while( (zJournal-zMasterJournal)zJournal)==0 ){ + bSeen = 1; + }else{ + int exists; + rc = sqlite3OsAccess(pVfs, zJournal, SQLITE_ACCESS_EXISTS, &exists); if( rc!=SQLITE_OK ){ - goto delmaster_out; + goto delsuper_out; } + if( exists ){ + char *zSuperPtr = 0; - rc = readMasterJournal(pJournal, zMasterPtr, nMasterPtr); - sqlite3OsClose(pJournal); - if( rc!=SQLITE_OK ){ - goto delmaster_out; - } + /* One of the journals pointed to by the super-journal exists. + ** Open it and check if it points at the super-journal. If + ** so, return without deleting the super-journal file. + ** NB: zJournal is really a MAIN_JOURNAL. But call it a + ** SUPER_JOURNAL here so that the VFS will not send the zJournal + ** name into sqlite3_database_file_object(). + */ + int c; + int flags = (SQLITE_OPEN_READONLY|SQLITE_OPEN_SUPER_JOURNAL); + rc = sqlite3OsOpen(pVfs, zJournal, pJournal, flags, 0); + if( rc!=SQLITE_OK ){ + goto delsuper_out; + } - c = zMasterPtr[0]!=0 && strcmp(zMasterPtr, zMaster)==0; - if( c ){ - /* We have a match. Do not delete the master journal file. */ - goto delmaster_out; + rc = readSuperJournal(pJournal, 1+(u64)pVfs->mxPathname, &zSuperPtr); + sqlite3OsClose(pJournal); + if( rc!=SQLITE_OK ){ + assert( zSuperPtr==0 ); + goto delsuper_out; + } + + c = zSuperPtr!=0 && strcmp(zSuperPtr, zSuper)==0; + freeSuperJournal(zSuperPtr); + if( c ){ + /* We have a match. Do not delete the super-journal file. */ + goto delsuper_out; + } } } zJournal += (sqlite3Strlen30(zJournal)+1); } - - sqlite3OsClose(pMaster); - rc = sqlite3OsDelete(pVfs, zMaster, 0); -delmaster_out: - sqlite3_free(zMasterJournal); - if( pMaster ){ - sqlite3OsClose(pMaster); + sqlite3OsClose(pSuper); + if( bSeen ){ + /* Only delete the super-journal if bSeen is true - indicating that + ** the super-journal contained a pointer to this database's journal + ** file. */ + rc = sqlite3OsDelete(pVfs, zSuper, 0); + } + +delsuper_out: + sqlite3_free(zFree); + if( pSuper ){ + sqlite3OsClose(pSuper); assert( !isOpen(pJournal) ); - sqlite3_free(pMaster); + sqlite3_free(pSuper); } return rc; } /* -** This function is used to change the actual size of the database +** This function is used to change the actual size of the database ** file in the file-system. This only happens when committing a transaction, ** or rolling back a transaction (including rolling back a hot-journal). ** ** If the main database file is not open, or the pager is not in either -** DBMOD or OPEN state, this function is a no-op. Otherwise, the size -** of the file is changed to nPage pages (nPage*pPager->pageSize bytes). +** DBMOD or OPEN state, this function is a no-op. Otherwise, the size +** of the file is changed to nPage pages (nPage*pPager->pageSize bytes). ** If the file on disk is currently larger than nPage pages, then use the VFS ** xTruncate() method to truncate it. ** -** Or, it might be the case that the file on disk is smaller than -** nPage pages. Some operating system implementations can get confused if -** you try to truncate a file to some size that is larger than it -** currently is, so detect this case and write a single zero byte to +** Or, it might be the case that the file on disk is smaller than +** nPage pages. Some operating system implementations can get confused if +** you try to truncate a file to some size that is larger than it +** currently is, so detect this case and write a single zero byte to ** the end of the new file instead. ** ** If successful, return SQLITE_OK. If an IO error occurs while modifying @@ -53255,9 +62354,11 @@ static int pager_truncate(Pager *pPager, Pgno nPage){ int rc = SQLITE_OK; assert( pPager->eState!=PAGER_ERROR ); assert( pPager->eState!=PAGER_READER ); - - if( isOpen(pPager->fd) - && (pPager->eState>=PAGER_WRITER_DBMOD || pPager->eState==PAGER_OPEN) + PAGERTRACE(("Truncate %d npage %u\n", PAGERID(pPager), nPage)); + + + if( isOpen(pPager->fd) + && (pPager->eState>=PAGER_WRITER_DBMOD || pPager->eState==PAGER_OPEN) ){ i64 currentSize, newSize; int szPage = pPager->pageSize; @@ -53273,6 +62374,7 @@ static int pager_truncate(Pager *pPager, Pgno nPage){ memset(pTmp, 0, szPage); testcase( (newSize-szPage) == currentSize ); testcase( (newSize-szPage) > currentSize ); + sqlite3OsFileControlHint(pPager->fd, SQLITE_FCNTL_SIZE_HINT, &newSize); rc = sqlite3OsWrite(pPager->fd, pTmp, szPage, newSize-szPage); } if( rc==SQLITE_OK ){ @@ -53301,9 +62403,9 @@ SQLITE_PRIVATE int sqlite3SectorSize(sqlite3_file *pFile){ /* ** Set the value of the Pager.sectorSize variable for the given ** pager based on the value returned by the xSectorSize method -** of the open database file. The sector size will be used -** to determine the size and alignment of journal header and -** master journal pointers within created journal files. +** of the open database file. The sector size will be used +** to determine the size and alignment of journal header and +** super-journal pointers within created journal files. ** ** For temporary files the effective sector size is always 512 bytes. ** @@ -53325,7 +62427,7 @@ static void setSectorSize(Pager *pPager){ assert( isOpen(pPager->fd) || pPager->tempFile ); if( pPager->tempFile - || (sqlite3OsDeviceCharacteristics(pPager->fd) & + || (sqlite3OsDeviceCharacteristics(pPager->fd) & SQLITE_IOCAP_POWERSAFE_OVERWRITE)!=0 ){ /* Sector size doesn't matter for temporary files. Also, the file @@ -53339,15 +62441,15 @@ static void setSectorSize(Pager *pPager){ /* ** Playback the journal and thus restore the database file to -** the state it was in before we started making changes. +** the state it was in before we started making changes. ** -** The journal file format is as follows: +** The journal file format is as follows: ** ** (1) 8 byte prefix. A copy of aJournalMagic[]. ** (2) 4 byte big-endian integer which is the number of valid page records ** in the journal. If this value is 0xffffffff, then compute the ** number of page records from the journal size. -** (3) 4 byte big-endian integer which is the initial value for the +** (3) 4 byte big-endian integer which is the initial value for the ** sanity checksum. ** (4) 4 byte integer which is the number of pages to truncate the ** database to during a rollback. @@ -53376,7 +62478,7 @@ static void setSectorSize(Pager *pPager){ ** from the file size. This value is used when the user selects the ** no-sync option for the journal. A power failure could lead to corruption ** in this case. But for things like temporary table (which will be -** deleted when the power is restored) we don't care. +** deleted when the power is restored) we don't care. ** ** If the file opened as the journal file is not a well-formed ** journal file then all pages up to the first corrupted page are rolled @@ -53388,7 +62490,7 @@ static void setSectorSize(Pager *pPager){ ** and an error code is returned. ** ** The isHot parameter indicates that we are trying to rollback a journal -** that might be a hot journal. Or, it could be that the journal is +** that might be a hot journal. Or, it could be that the journal is ** preserved because of JOURNALMODE_PERSIST or JOURNALMODE_TRUNCATE. ** If the journal really is hot, reset the pager cache prior rolling ** back any content. If the journal is merely persistent, no reset is @@ -53402,7 +62504,7 @@ static int pager_playback(Pager *pPager, int isHot){ Pgno mxPg = 0; /* Size of the original file in pages */ int rc; /* Result code of a subroutine */ int res = 1; /* Value returned by sqlite3OsAccess() */ - char *zMaster = 0; /* Name of master journal file if any */ + char *zSuper = 0; /* Name of super-journal file if any */ int needPagerReset; /* True to reset page prior to first page rollback */ int nPlayback = 0; /* Total number of pages restored from journal */ u32 savedPageSize = pPager->pageSize; @@ -53416,32 +62518,24 @@ static int pager_playback(Pager *pPager, int isHot){ goto end_playback; } - /* Read the master journal name from the journal, if it is present. - ** If a master journal file name is specified, but the file is not + /* Read the super-journal name from the journal, if it is present. + ** If a super-journal file name is specified, but the file is not ** present on disk, then the journal is not hot and does not need to be ** played back. - ** - ** TODO: Technically the following is an error because it assumes that - ** buffer Pager.pTmpSpace is (mxPathname+1) bytes or larger. i.e. that - ** (pPager->pageSize >= pPager->pVfs->mxPathname+1). Using os_unix.c, - ** mxPathname is 512, which is the same as the minimum allowable value - ** for pageSize. - */ - zMaster = pPager->pTmpSpace; - rc = readMasterJournal(pPager->jfd, zMaster, pPager->pVfs->mxPathname+1); - if( rc==SQLITE_OK && zMaster[0] ){ - rc = sqlite3OsAccess(pVfs, zMaster, SQLITE_ACCESS_EXISTS, &res); - } - zMaster = 0; + */ + rc = readSuperJournal(pPager->jfd, 1+(i64)pPager->pVfs->mxPathname, &zSuper); + if( rc==SQLITE_OK && zSuper ){ + rc = sqlite3OsAccess(pVfs, zSuper, SQLITE_ACCESS_EXISTS, &res); + } if( rc!=SQLITE_OK || !res ){ goto end_playback; } pPager->journalOff = 0; needPagerReset = isHot; - /* This loop terminates either when a readJournalHdr() or - ** pager_playback_one_page() call returns SQLITE_DONE or an IO error - ** occurs. + /* This loop terminates either when a readJournalHdr() or + ** pager_playback_one_page() call returns SQLITE_DONE or an IO error + ** occurs. */ while( 1 ){ /* Read the next journal header from the journal file. If there are @@ -53450,7 +62544,7 @@ static int pager_playback(Pager *pPager, int isHot){ ** This indicates nothing more needs to be rolled back. */ rc = readJournalHdr(pPager, isHot, szJ, &nRec, &mxPg); - if( rc!=SQLITE_OK ){ + if( rc!=SQLITE_OK ){ if( rc==SQLITE_DONE ){ rc = SQLITE_OK; } @@ -53478,7 +62572,7 @@ static int pager_playback(Pager *pPager, int isHot){ ** chunk of the journal contains zero pages to be rolled back. But ** when doing a ROLLBACK and the nRec==0 chunk is the last chunk in ** the journal, it means that the journal might contain additional - ** pages that need to be rolled back and that the number of pages + ** pages that need to be rolled back and that the number of pages ** should be computed based on the journal file size. */ if( nRec==0 && !isHot && @@ -53495,9 +62589,12 @@ static int pager_playback(Pager *pPager, int isHot){ goto end_playback; } pPager->dbSize = mxPg; + if( pPager->mxPgnomxPgno = mxPg; + } } - /* Copy original pages out of the journal and back into the + /* Copy original pages out of the journal and back into the ** database file and/or page cache. */ for(u=0; ufd,SQLITE_FCNTL_DB_UNCHANGED,0); #endif - /* If this playback is happening automatically as a result of an IO or - ** malloc error that occurred after the change-counter was updated but - ** before the transaction was committed, then the change-counter - ** modification may just have been reverted. If this happens in exclusive + /* If this playback is happening automatically as a result of an IO or + ** malloc error that occurred after the change-counter was updated but + ** before the transaction was committed, then the change-counter + ** modification may just have been reverted. If this happens in exclusive ** mode, then subsequent transactions performed by the connection will not ** update the change-counter at all. This may lead to cache inconsistency ** problems for other processes at some point in the future. So, just @@ -53558,25 +62655,21 @@ static int pager_playback(Pager *pPager, int isHot){ */ pPager->changeCountDone = pPager->tempFile; - if( rc==SQLITE_OK ){ - zMaster = pPager->pTmpSpace; - rc = readMasterJournal(pPager->jfd, zMaster, pPager->pVfs->mxPathname+1); - testcase( rc!=SQLITE_OK ); - } if( rc==SQLITE_OK && (pPager->eState>=PAGER_WRITER_DBMOD || pPager->eState==PAGER_OPEN) ){ rc = sqlite3PagerSync(pPager, 0); } if( rc==SQLITE_OK ){ - rc = pager_end_transaction(pPager, zMaster[0]!='\0', 0); + rc = pager_end_transaction(pPager, zSuper!=0, 0); testcase( rc!=SQLITE_OK ); } - if( rc==SQLITE_OK && zMaster[0] && res ){ - /* If there was a master journal and this routine will return success, - ** see if it is possible to delete the master journal. + if( rc==SQLITE_OK && zSuper && res ){ + /* If there was a super-journal and this routine will return success, + ** see if it is possible to delete the super-journal. */ - rc = pager_delmaster(pPager, zMaster); + assert( memcmp(&zSuper[-4], "\0\0\0\0", 4)==0 ); + rc = pager_delsuper(pPager, zSuper); testcase( rc!=SQLITE_OK ); } if( isHot && nPlayback ){ @@ -53588,6 +62681,7 @@ static int pager_playback(Pager *pPager, int isHot){ ** back a journal created by a process with a different sector size ** value. Reset it to the correct value for this process. */ + freeSuperJournal(zSuper); setSectorSize(pPager); return rc; } @@ -53595,7 +62689,7 @@ static int pager_playback(Pager *pPager, int isHot){ /* ** Read the content for page pPg out of the database file (or out of -** the WAL if that is where the most recent copy if found) into +** the WAL if that is where the most recent copy if found) into ** pPg->pData. A shared lock or greater must be held on the database ** file before this function is called. ** @@ -53651,8 +62745,6 @@ static int readDbPage(PgHdr *pPg){ memcpy(&pPager->dbFileVers, dbFileVers, sizeof(pPager->dbFileVers)); } } - CODEC1(pPager, pPg->pData, pPg->pgno, 3, rc = SQLITE_NOMEM_BKPT); - PAGER_INCR(sqlite3_pager_readdb_count); PAGER_INCR(pPager->nRead); IOTRACE(("PGIN %p %d\n", pPager, pPg->pgno)); @@ -53672,6 +62764,7 @@ static int readDbPage(PgHdr *pPg){ */ static void pager_write_changecounter(PgHdr *pPg){ u32 change_counter; + if( NEVER(pPg==0) ) return; /* Increment the value just read and write it back to byte 24. */ change_counter = sqlite3Get4byte((u8*)pPg->pPager->dbFileVers)+1; @@ -53686,15 +62779,15 @@ static void pager_write_changecounter(PgHdr *pPg){ #ifndef SQLITE_OMIT_WAL /* -** This function is invoked once for each page that has already been +** This function is invoked once for each page that has already been ** written into the log file when a WAL transaction is rolled back. -** Parameter iPg is the page number of said page. The pCtx argument +** Parameter iPg is the page number of said page. The pCtx argument ** is actually a pointer to the Pager structure. ** ** If page iPg is present in the cache, and has no outstanding references, ** it is discarded. Otherwise, if there are one or more outstanding ** references, the page content is reloaded from the database. If the -** attempt to reload content from the database is required and fails, +** attempt to reload content from the database is required and fails, ** return an SQLite error code. Otherwise, SQLITE_OK. */ static int pagerUndoCallback(void *pCtx, Pgno iPg){ @@ -53720,7 +62813,7 @@ static int pagerUndoCallback(void *pCtx, Pgno iPg){ ** updated as data is copied out of the rollback journal and into the ** database. This is not generally possible with a WAL database, as ** rollback involves simply truncating the log file. Therefore, if one - ** or more frames have already been written to the log (and therefore + ** or more frames have already been written to the log (and therefore ** also copied into the backup databases) as part of this transaction, ** the backups must be restarted. */ @@ -53737,7 +62830,7 @@ static int pagerRollbackWal(Pager *pPager){ PgHdr *pList; /* List of dirty pages to revert */ /* For all pages in the cache that are currently dirty or have already - ** been written (but not committed) to the log file, do one of the + ** been written (but not committed) to the log file, do one of the ** following: ** ** + Discard the cached page (if refcount==0), or @@ -53759,11 +62852,11 @@ static int pagerRollbackWal(Pager *pPager){ ** This function is a wrapper around sqlite3WalFrames(). As well as logging ** the contents of the list of pages headed by pList (connected by pDirty), ** this function notifies any active backup processes that the pages have -** changed. +** changed. ** ** The list of pages passed into this routine is always sorted by page number. ** Hence, if page 1 appears anywhere on the list, it will be the first page. -*/ +*/ static int pagerWalFrames( Pager *pPager, /* Pager object */ PgHdr *pList, /* List of frames to log */ @@ -53777,7 +62870,7 @@ static int pagerWalFrames( assert( pPager->pWal ); assert( pList ); #ifdef SQLITE_DEBUG - /* Verify that the page list is in accending order */ + /* Verify that the page list is in ascending order */ for(p=pList; p && p->pDirty; p=p->pDirty){ assert( p->pgno < p->pDirty->pgno ); } @@ -53804,7 +62897,7 @@ static int pagerWalFrames( pPager->aStat[PAGER_STAT_WRITE] += nList; if( pList->pgno==1 ) pager_write_changecounter(pList); - rc = sqlite3WalFrames(pPager->pWal, + rc = sqlite3WalFrames(pPager->pWal, pPager->pageSize, pList, nTruncate, isCommit, pPager->walSyncFlags ); if( rc==SQLITE_OK && pPager->pBackup ){ @@ -53908,7 +63001,7 @@ static int pagerPagecount(Pager *pPager, Pgno *pnPage){ #ifndef SQLITE_OMIT_WAL /* ** Check if the *-wal file that corresponds to the database opened by pPager -** exists if the database is not empy, or verify that the *-wal file does +** exists if the database is not empty, or verify that the *-wal file does ** not exist (by deleting it) if the database file is empty. ** ** If the database is not empty and the *-wal file exists, open the pager @@ -53919,9 +63012,9 @@ static int pagerPagecount(Pager *pPager, Pgno *pnPage){ ** Return SQLITE_OK or an error code. ** ** The caller must hold a SHARED lock on the database file to call this -** function. Because an EXCLUSIVE lock on the db file is required to delete -** a WAL on a none-empty database, this ensures there is no race condition -** between the xAccess() below and an xDelete() being executed by some +** function. Because an EXCLUSIVE lock on the db file is required to delete +** a WAL on a none-empty database, this ensures there is no race condition +** between the xAccess() below and an xDelete() being executed by some ** other connection. */ static int pagerOpenWalIfPresent(Pager *pPager){ @@ -53957,21 +63050,21 @@ static int pagerOpenWalIfPresent(Pager *pPager){ /* ** Playback savepoint pSavepoint. Or, if pSavepoint==NULL, then playback -** the entire master journal file. The case pSavepoint==NULL occurs when -** a ROLLBACK TO command is invoked on a SAVEPOINT that is a transaction +** the entire super-journal file. The case pSavepoint==NULL occurs when +** a ROLLBACK TO command is invoked on a SAVEPOINT that is a transaction ** savepoint. ** -** When pSavepoint is not NULL (meaning a non-transaction savepoint is +** When pSavepoint is not NULL (meaning a non-transaction savepoint is ** being rolled back), then the rollback consists of up to three stages, ** performed in the order specified: ** ** * Pages are played back from the main journal starting at byte -** offset PagerSavepoint.iOffset and continuing to +** offset PagerSavepoint.iOffset and continuing to ** PagerSavepoint.iHdrOffset, or to the end of the main journal ** file if PagerSavepoint.iHdrOffset is zero. ** ** * If PagerSavepoint.iHdrOffset is not zero, then pages are played -** back starting from the journal header immediately following +** back starting from the journal header immediately following ** PagerSavepoint.iHdrOffset to the end of the main journal file. ** ** * Pages are then played back from the sub-journal file, starting @@ -53987,7 +63080,7 @@ static int pagerOpenWalIfPresent(Pager *pPager){ ** journal file. There is no need for a bitvec in this case. ** ** In either case, before playback commences the Pager.dbSize variable -** is reset to the value that it held at the start of the savepoint +** is reset to the value that it held at the start of the savepoint ** (or transaction). No page with a page-number greater than this value ** is played back. If one is encountered it is simply skipped. */ @@ -54008,7 +63101,7 @@ static int pagerPlaybackSavepoint(Pager *pPager, PagerSavepoint *pSavepoint){ } } - /* Set the database size back to the value it was before the savepoint + /* Set the database size back to the value it was before the savepoint ** being reverted was opened. */ pPager->dbSize = pSavepoint ? pSavepoint->nOrig : pPager->dbOrigSize; @@ -54061,7 +63154,7 @@ static int pagerPlaybackSavepoint(Pager *pPager, PagerSavepoint *pSavepoint){ ** test is related to ticket #2565. See the discussion in the ** pager_playback() function for additional information. */ - if( nJRec==0 + if( nJRec==0 && pPager->journalHdr+JOURNAL_HDR_SZ(pPager)==pPager->journalOff ){ nJRec = (u32)((szJ - pPager->journalOff)/JOURNAL_PG_SZ(pPager)); @@ -54197,20 +63290,32 @@ SQLITE_PRIVATE void sqlite3PagerShrink(Pager *pPager){ ** Numeric values associated with these states are OFF==1, NORMAL=2, ** and FULL=3. */ -#ifndef SQLITE_OMIT_PAGER_PRAGMAS SQLITE_PRIVATE void sqlite3PagerSetFlags( Pager *pPager, /* The pager to set safety level for */ unsigned pgFlags /* Various flags */ ){ unsigned level = pgFlags & PAGER_SYNCHRONOUS_MASK; - if( pPager->tempFile ){ + if( pPager->tempFile || level==PAGER_SYNCHRONOUS_OFF ){ pPager->noSync = 1; pPager->fullSync = 0; pPager->extraSync = 0; }else{ - pPager->noSync = level==PAGER_SYNCHRONOUS_OFF ?1:0; + pPager->noSync = 0; pPager->fullSync = level>=PAGER_SYNCHRONOUS_FULL ?1:0; - pPager->extraSync = level==PAGER_SYNCHRONOUS_EXTRA ?1:0; + + /* Set Pager.extraSync if "PRAGMA synchronous=EXTRA" is requested, or + ** if the file-system supports F2FS style atomic writes. If this flag + ** is set, SQLite syncs the directory to disk immediately after deleting + ** a journal file in "PRAGMA journal_mode=DELETE" mode. */ + if( level==PAGER_SYNCHRONOUS_EXTRA +#ifdef SQLITE_ENABLE_BATCH_ATOMIC_WRITE + || (sqlite3OsDeviceCharacteristics(pPager->fd) & SQLITE_IOCAP_BATCH_ATOMIC) +#endif + ){ + pPager->extraSync = 1; + }else{ + pPager->extraSync = 0; + } } if( pPager->noSync ){ pPager->syncFlags = 0; @@ -54232,12 +63337,11 @@ SQLITE_PRIVATE void sqlite3PagerSetFlags( pPager->doNotSpill |= SPILLFLAG_OFF; } } -#endif /* ** The following global variable is incremented whenever the library ** attempts to open a temporary file. This information is used for -** testing and analysis only. +** testing and analysis only. */ #ifdef SQLITE_TEST SQLITE_API int sqlite3_opentemp_count = 0; @@ -54246,8 +63350,8 @@ SQLITE_API int sqlite3_opentemp_count = 0; /* ** Open a temporary file. ** -** Write the file descriptor into *pFile. Return SQLITE_OK on success -** or some other error code if we fail. The OS will automatically +** Write the file descriptor into *pFile. Return SQLITE_OK on success +** or some other error code if we fail. The OS will automatically ** delete the temporary file when it is closed. ** ** The flags passed to the VFS layer xOpen() call are those specified @@ -54279,9 +63383,9 @@ static int pagerOpentemp( /* ** Set the busy handler function. ** -** The pager invokes the busy-handler if sqlite3OsLock() returns +** The pager invokes the busy-handler if sqlite3OsLock() returns ** SQLITE_BUSY when trying to upgrade from no-lock to a SHARED lock, -** or when trying to upgrade from a RESERVED lock to an EXCLUSIVE +** or when trying to upgrade from a RESERVED lock to an EXCLUSIVE ** lock. It does *not* invoke the busy handler when upgrading from ** SHARED to RESERVED, or when upgrading from SHARED to EXCLUSIVE ** (which occurs during hot-journal rollback). Summary: @@ -54293,7 +63397,7 @@ static int pagerOpentemp( ** SHARED_LOCK -> EXCLUSIVE_LOCK | No ** RESERVED_LOCK -> EXCLUSIVE_LOCK | Yes ** -** If the busy-handler callback returns non-zero, the lock is +** If the busy-handler callback returns non-zero, the lock is ** retried. If it returns zero, then the SQLITE_BUSY error is ** returned to the caller of the pager API function. */ @@ -54312,16 +63416,16 @@ SQLITE_PRIVATE void sqlite3PagerSetBusyHandler( } /* -** Change the page size used by the Pager object. The new page size +** Change the page size used by the Pager object. The new page size ** is passed in *pPageSize. ** ** If the pager is in the error state when this function is called, it -** is a no-op. The value returned is the error state error code (i.e. +** is a no-op. The value returned is the error state error code (i.e. ** one of SQLITE_IOERR, an SQLITE_IOERR_xxx sub-code or SQLITE_FULL). ** ** Otherwise, if all of the following are true: ** -** * the new page size (value of *pPageSize) is valid (a power +** * the new page size (value of *pPageSize) is valid (a power ** of two between 512 and SQLITE_MAX_PAGE_SIZE, inclusive), and ** ** * there are no outstanding page references, and @@ -54331,14 +63435,14 @@ SQLITE_PRIVATE void sqlite3PagerSetBusyHandler( ** ** then the pager object page size is set to *pPageSize. ** -** If the page size is changed, then this function uses sqlite3PagerMalloc() -** to obtain a new Pager.pTmpSpace buffer. If this allocation attempt -** fails, SQLITE_NOMEM is returned and the page size remains unchanged. +** If the page size is changed, then this function uses sqlite3PagerMalloc() +** to obtain a new Pager.pTmpSpace buffer. If this allocation attempt +** fails, SQLITE_NOMEM is returned and the page size remains unchanged. ** In all other cases, SQLITE_OK is returned. ** ** If the page size is not changed, either because one of the enumerated ** conditions above is not true, the pager was in error state when this -** function was called, or because the memory allocation attempt failed, +** function was called, or because the memory allocation attempt failed, ** then *pPageSize is set to the old, retained page size before returning. */ SQLITE_PRIVATE int sqlite3PagerSetPagesize(Pager *pPager, u32 *pPageSize, int nReserve){ @@ -54348,7 +63452,7 @@ SQLITE_PRIVATE int sqlite3PagerSetPagesize(Pager *pPager, u32 *pPageSize, int nR ** function may be called from within PagerOpen(), before the state ** of the Pager object is internally consistent. ** - ** At one point this function returned an error if the pager was in + ** At one point this function returned an error if the pager was in ** PAGER_ERROR state. But since PAGER_ERROR state guarantees that ** there is at least one outstanding page reference, this function ** is a no-op for that case anyhow. @@ -54357,8 +63461,8 @@ SQLITE_PRIVATE int sqlite3PagerSetPagesize(Pager *pPager, u32 *pPageSize, int nR u32 pageSize = *pPageSize; assert( pageSize==0 || (pageSize>=512 && pageSize<=SQLITE_MAX_PAGE_SIZE) ); if( (pPager->memDb==0 || pPager->dbSize==0) - && sqlite3PcacheRefCount(pPager->pPCache)==0 - && pageSize && pageSize!=(u32)pPager->pageSize + && sqlite3PcacheRefCount(pPager->pPCache)==0 + && pageSize && pageSize!=(u32)pPager->pageSize ){ char *pNew = NULL; /* New temp space */ i64 nByte = 0; @@ -54386,6 +63490,7 @@ SQLITE_PRIVATE int sqlite3PagerSetPagesize(Pager *pPager, u32 *pPageSize, int nR pPager->pTmpSpace = pNew; pPager->dbSize = (Pgno)((nByte+pageSize-1)/pageSize); pPager->pageSize = pageSize; + pPager->lckPgno = (Pgno)(PENDING_BYTE/pageSize) + 1; }else{ sqlite3PageFree(pNew); } @@ -54396,7 +63501,6 @@ SQLITE_PRIVATE int sqlite3PagerSetPagesize(Pager *pPager, u32 *pPageSize, int nR if( nReserve<0 ) nReserve = pPager->nReserve; assert( nReserve>=0 && nReserve<1000 ); pPager->nReserve = (i16)nReserve; - pagerReportSize(pPager); pagerFixMaplimit(pPager); } return rc; @@ -54415,13 +63519,13 @@ SQLITE_PRIVATE void *sqlite3PagerTempSpace(Pager *pPager){ } /* -** Attempt to set the maximum database page count if mxPage is positive. +** Attempt to set the maximum database page count if mxPage is positive. ** Make no changes if mxPage is zero or negative. And never reduce the ** maximum page count below the current size of the database. ** ** Regardless of mxPage, return the current maximum page count. */ -SQLITE_PRIVATE int sqlite3PagerMaxPageCount(Pager *pPager, int mxPage){ +SQLITE_PRIVATE Pgno sqlite3PagerMaxPageCount(Pager *pPager, Pgno mxPage){ if( mxPage>0 ){ pPager->mxPgno = mxPage; } @@ -54459,11 +63563,11 @@ void enable_simulated_io_errors(void){ /* ** Read the first N bytes from the beginning of the file into memory -** that pDest points to. +** that pDest points to. ** ** If the pager was opened on a transient file (zFilename==""), or ** opened on a file less than N bytes in size, the output buffer is -** zeroed and SQLITE_OK returned. The rationale for this is that this +** zeroed and SQLITE_OK returned. The rationale for this is that this ** function is used to read database headers, and a new transient or ** zero sized database has a header than consists entirely of zeroes. ** @@ -54496,7 +63600,7 @@ SQLITE_PRIVATE int sqlite3PagerReadFileheader(Pager *pPager, int N, unsigned cha ** This function may only be called when a read-transaction is open on ** the pager. It returns the total number of pages in the database. ** -** However, if the file is between 1 and bytes in size, then +** However, if the file is between 1 and bytes in size, then ** this is considered a 1 page file. */ SQLITE_PRIVATE void sqlite3PagerPagecount(Pager *pPager, int *pnPage){ @@ -54511,19 +63615,19 @@ SQLITE_PRIVATE void sqlite3PagerPagecount(Pager *pPager, int *pnPage){ ** a similar or greater lock is already held, this function is a no-op ** (returning SQLITE_OK immediately). ** -** Otherwise, attempt to obtain the lock using sqlite3OsLock(). Invoke -** the busy callback if the lock is currently not available. Repeat -** until the busy callback returns false or until the attempt to +** Otherwise, attempt to obtain the lock using sqlite3OsLock(). Invoke +** the busy callback if the lock is currently not available. Repeat +** until the busy callback returns false or until the attempt to ** obtain the lock succeeds. ** ** Return SQLITE_OK on success and an error code if we cannot obtain -** the lock. If the lock is obtained successfully, set the Pager.state +** the lock. If the lock is obtained successfully, set the Pager.state ** variable to locktype before returning. */ static int pager_wait_on_lock(Pager *pPager, int locktype){ int rc; /* Return code */ - /* Check that this is either a no-op (because the requested lock is + /* Check that this is either a no-op (because the requested lock is ** already held), or one of the transitions that the busy-handler ** may be invoked during, according to the comment above ** sqlite3PagerSetBusyhandler(). @@ -54540,15 +63644,14 @@ static int pager_wait_on_lock(Pager *pPager, int locktype){ } /* -** Function assertTruncateConstraint(pPager) checks that one of the +** Function assertTruncateConstraint(pPager) checks that one of the ** following is true for all dirty pages currently in the page-cache: ** -** a) The page number is less than or equal to the size of the +** a) The page number is less than or equal to the size of the ** current database image, in pages, OR ** ** b) if the page content were written at this time, it would not -** be necessary to write the current content out to the sub-journal -** (as determined by function subjRequiresPage()). +** be necessary to write the current content out to the sub-journal. ** ** If the condition asserted by this function were not true, and the ** dirty page were to be discarded from the cache via the pagerStress() @@ -54556,15 +63659,23 @@ static int pager_wait_on_lock(Pager *pPager, int locktype){ ** the database file. If a savepoint transaction were rolled back after ** this happened, the correct behavior would be to restore the current ** content of the page. However, since this content is not present in either -** the database file or the portion of the rollback journal and +** the database file or the portion of the rollback journal and ** sub-journal rolled back the content could not be restored and the -** database image would become corrupt. It is therefore fortunate that +** database image would become corrupt. It is therefore fortunate that ** this circumstance cannot arise. */ #if defined(SQLITE_DEBUG) static void assertTruncateConstraintCb(PgHdr *pPg){ + Pager *pPager = pPg->pPager; assert( pPg->flags&PGHDR_DIRTY ); - assert( !subjRequiresPage(pPg) || pPg->pgno<=pPg->pPager->dbSize ); + if( pPg->pgno>pPager->dbSize ){ /* if (a) is false */ + Pgno pgno = pPg->pgno; + int i; + for(i=0; ipPager->nSavepoint; i++){ + PagerSavepoint *p = &pPager->aSavepoint[i]; + assert( p->nOrigpInSavepoint,pgno) ); + } + } } static void assertTruncateConstraint(Pager *pPager){ sqlite3PcacheIterateDirty(pPager->pPCache, assertTruncateConstraintCb); @@ -54574,9 +63685,9 @@ static void assertTruncateConstraint(Pager *pPager){ #endif /* -** Truncate the in-memory database file image to nPage pages. This -** function does not actually modify the database file on disk. It -** just sets the internal state of the pager object so that the +** Truncate the in-memory database file image to nPage pages. This +** function does not actually modify the database file on disk. It +** just sets the internal state of the pager object so that the ** truncation will be done when the current transaction is committed. ** ** This function is only called right before committing a transaction. @@ -54585,17 +63696,17 @@ static void assertTruncateConstraint(Pager *pPager){ ** then continue writing to the database. */ SQLITE_PRIVATE void sqlite3PagerTruncateImage(Pager *pPager, Pgno nPage){ - assert( pPager->dbSize>=nPage ); + assert( pPager->dbSize>=nPage || CORRUPT_DB ); assert( pPager->eState>=PAGER_WRITER_CACHEMOD ); pPager->dbSize = nPage; /* At one point the code here called assertTruncateConstraint() to ** ensure that all pages being truncated away by this operation are, - ** if one or more savepoints are open, present in the savepoint + ** if one or more savepoints are open, present in the savepoint ** journal so that they can be restored if the savepoint is rolled ** back. This is no longer necessary as this function is now only - ** called right before committing a transaction. So although the - ** Pager object may still have open savepoints (Pager.nSavepoint!=0), + ** called right before committing a transaction. So although the + ** Pager object may still have open savepoints (Pager.nSavepoint!=0), ** they cannot be rolled back. So the assertTruncateConstraint() call ** is no longer correct. */ } @@ -54607,12 +63718,12 @@ SQLITE_PRIVATE void sqlite3PagerTruncateImage(Pager *pPager, Pgno nPage){ ** size of the journal file so that the pager_playback() routine knows ** that the entire journal file has been synced. ** -** Syncing a hot-journal to disk before attempting to roll it back ensures +** Syncing a hot-journal to disk before attempting to roll it back ensures ** that if a power-failure occurs during the rollback, the process that ** attempts rollback following system recovery sees the same journal ** content as this process. ** -** If everything goes as planned, SQLITE_OK is returned. Otherwise, +** If everything goes as planned, SQLITE_OK is returned. Otherwise, ** an SQLite error code. */ static int pagerSyncHotJournal(Pager *pPager){ @@ -54628,7 +63739,7 @@ static int pagerSyncHotJournal(Pager *pPager){ #if SQLITE_MAX_MMAP_SIZE>0 /* -** Obtain a reference to a memory mapped page object for page number pgno. +** Obtain a reference to a memory mapped page object for page number pgno. ** The new object will use the pointer pData, obtained from xFetch(). ** If successful, set *ppPage to point to the new page reference ** and return SQLITE_OK. Otherwise, return an SQLite error code and set @@ -54644,7 +63755,7 @@ static int pagerAcquireMapPage( PgHdr **ppPage /* OUT: Acquired page object */ ){ PgHdr *p; /* Memory mapped page to return */ - + if( pPager->pMmapFreelist ){ *ppPage = p = pPager->pMmapFreelist; pPager->pMmapFreelist = p->pDirty; @@ -54658,6 +63769,7 @@ static int pagerAcquireMapPage( return SQLITE_NOMEM_BKPT; } p->pExtra = (void *)&p[1]; + assert( EIGHT_BYTE_ALIGNMENT( p->pExtra ) ); p->flags = PGHDR_MMAP; p->nRef = 1; p->pPager = pPager; @@ -54678,7 +63790,7 @@ static int pagerAcquireMapPage( #endif /* -** Release a reference to page pPg. pPg must have been returned by an +** Release a reference to page pPg. pPg must have been returned by an ** earlier call to pagerAcquireMapPage(). */ static void pagerReleaseMapPage(PgHdr *pPg){ @@ -54738,7 +63850,7 @@ static int databaseIsUnmoved(Pager *pPager){ ** result in a coredump. ** ** This function always succeeds. If a transaction is active an attempt -** is made to roll it back. If an error occurs during the rollback +** is made to roll it back. If an error occurs during the rollback ** a hot journal may be left in the filesystem but no error is returned ** to the caller. */ @@ -54755,7 +63867,7 @@ SQLITE_PRIVATE int sqlite3PagerClose(Pager *pPager, sqlite3 *db){ { u8 *a = 0; assert( db || pPager->pWal==0 ); - if( db && 0==(db->flags & SQLITE_NoCkptOnClose) + if( db && 0==(db->flags & SQLITE_NoCkptOnClose) && SQLITE_OK==databaseIsUnmoved(pPager) ){ a = pTmp; @@ -54763,14 +63875,16 @@ SQLITE_PRIVATE int sqlite3PagerClose(Pager *pPager, sqlite3 *db){ sqlite3WalClose(pPager->pWal, db, pPager->walSyncFlags, pPager->pageSize,a); pPager->pWal = 0; } +#else + UNUSED_PARAMETER(db); #endif pager_reset(pPager); if( MEMDB ){ pager_unlock(pPager); }else{ /* If it is open, sync the journal file before calling UnlockAndRollback. - ** If this is not done, then an unsynced portion of the open journal - ** file may be played back into the database. If a power failure occurs + ** If this is not done, then an unsynced portion of the open journal + ** file may be played back into the database. If a power failure occurs ** while this is happening, the database could become corrupt. ** ** If an error occurs while trying to sync the journal, shift the pager @@ -54792,11 +63906,6 @@ SQLITE_PRIVATE int sqlite3PagerClose(Pager *pPager, sqlite3 *db){ sqlite3OsClose(pPager->fd); sqlite3PageFree(pTmp); sqlite3PcacheClose(pPager->pPCache); - -#ifdef SQLITE_HAS_CODEC - if( pPager->xCodecFree ) pPager->xCodecFree(pPager->pCodec); -#endif - assert( !pPager->aSavepoint && !pPager->pInJournal ); assert( !isOpen(pPager->jfd) && !isOpen(pPager->sjfd) ); @@ -54826,7 +63935,7 @@ SQLITE_PRIVATE void sqlite3PagerRef(DbPage *pPg){ ** disk and can be restored in the event of a hot-journal rollback. ** ** If the Pager.noSync flag is set, then this function is a no-op. -** Otherwise, the actions required depend on the journal-mode and the +** Otherwise, the actions required depend on the journal-mode and the ** device characteristics of the file-system, as follows: ** ** * If the journal file is an in-memory journal file, no action need @@ -54838,7 +63947,7 @@ SQLITE_PRIVATE void sqlite3PagerRef(DbPage *pPg){ ** been written following it. If the pager is operating in full-sync ** mode, then the journal file is synced before this field is updated. ** -** * If the device does not support the SEQUENTIAL property, then +** * If the device does not support the SEQUENTIAL property, then ** journal file is synced. ** ** Or, in pseudo-code: @@ -54847,11 +63956,11 @@ SQLITE_PRIVATE void sqlite3PagerRef(DbPage *pPg){ ** if( NOT SAFE_APPEND ){ ** if( ) xSync(); ** -** } +** } ** if( NOT SEQUENTIAL ) xSync(); ** } ** -** If successful, this routine clears the PGHDR_NEED_SYNC flag of every +** If successful, this routine clears the PGHDR_NEED_SYNC flag of every ** page currently held in memory before returning SQLITE_OK. If an IO ** error is encountered, then the IO error code is returned to the caller. */ @@ -54879,10 +63988,10 @@ static int syncJournal(Pager *pPager, int newHdr){ ** mode, then the journal file may at this point actually be larger ** than Pager.journalOff bytes. If the next thing in the journal ** file happens to be a journal-header (written as part of the - ** previous connection's transaction), and a crash or power-failure - ** occurs after nRec is updated but before this connection writes - ** anything else to the journal file (or commits/rolls back its - ** transaction), then SQLite may become confused when doing the + ** previous connection's transaction), and a crash or power-failure + ** occurs after nRec is updated but before this connection writes + ** anything else to the journal file (or commits/rolls back its + ** transaction), then SQLite may become confused when doing the ** hot-journal rollback following recovery. It may roll back all ** of this connections data, then proceed to rolling back the old, ** out-of-date data that follows it. Database corruption. @@ -54892,7 +64001,7 @@ static int syncJournal(Pager *pPager, int newHdr){ ** byte to the start of it to prevent it from being recognized. ** ** Variable iNextHdrOffset is set to the offset at which this - ** problematic header will occur, if it exists. aMagic is used + ** problematic header will occur, if it exists. aMagic is used ** as a temporary buffer to inspect the first couple of bytes of ** the potential journal header. */ @@ -54919,7 +64028,7 @@ static int syncJournal(Pager *pPager, int newHdr){ ** it as a candidate for rollback. ** ** This is not required if the persistent media supports the - ** SAFE_APPEND property. Because in this case it is not possible + ** SAFE_APPEND property. Because in this case it is not possible ** for garbage data to be appended to the file, the nRec field ** is populated with 0xFFFFFFFF when the journal header is written ** and never needs to be updated. @@ -54939,7 +64048,7 @@ static int syncJournal(Pager *pPager, int newHdr){ if( 0==(iDc&SQLITE_IOCAP_SEQUENTIAL) ){ PAGERTRACE(("SYNC journal of %d\n", PAGERID(pPager))); IOTRACE(("JSYNC %p\n", pPager)) - rc = sqlite3OsSync(pPager->jfd, pPager->syncFlags| + rc = sqlite3OsSync(pPager->jfd, pPager->syncFlags| (pPager->syncFlags==SQLITE_SYNC_FULL?SQLITE_SYNC_DATAONLY:0) ); if( rc!=SQLITE_OK ) return rc; @@ -54956,8 +64065,8 @@ static int syncJournal(Pager *pPager, int newHdr){ } } - /* Unless the pager is in noSync mode, the journal file was just - ** successfully synced. Either way, clear the PGHDR_NEED_SYNC flag on + /* Unless the pager is in noSync mode, the journal file was just + ** successfully synced. Either way, clear the PGHDR_NEED_SYNC flag on ** all pages. */ sqlite3PcacheClearSyncFlags(pPager->pPCache); @@ -54977,9 +64086,9 @@ static int syncJournal(Pager *pPager, int newHdr){ ** is called. Before writing anything to the database file, this lock ** is upgraded to an EXCLUSIVE lock. If the lock cannot be obtained, ** SQLITE_BUSY is returned and no data is written to the database file. -** +** ** If the pager is a temp-file pager and the actual file-system file -** is not yet open, it is created and opened before any data is +** is not yet open, it is created and opened before any data is ** written out. ** ** Once the lock has been upgraded and, if necessary, the file opened, @@ -54994,7 +64103,7 @@ static int syncJournal(Pager *pPager, int newHdr){ ** in Pager.dbFileVers[] is updated to match the new value stored in ** the database file. ** -** If everything is successful, SQLITE_OK is returned. If an IO error +** If everything is successful, SQLITE_OK is returned. If an IO error ** occurs, an IO error code is returned. Or, if the EXCLUSIVE lock cannot ** be obtained, SQLITE_BUSY is returned. */ @@ -55020,7 +64129,7 @@ static int pager_write_pagelist(Pager *pPager, PgHdr *pList){ ** file size will be. */ assert( rc!=SQLITE_OK || isOpen(pPager->fd) ); - if( rc==SQLITE_OK + if( rc==SQLITE_OK && pPager->dbHintSizedbSize && (pList->pDirty || pList->pgno>pPager->dbHintSize) ){ @@ -55042,20 +64151,19 @@ static int pager_write_pagelist(Pager *pPager, PgHdr *pList){ */ if( pgno<=pPager->dbSize && 0==(pList->flags&PGHDR_DONT_WRITE) ){ i64 offset = (pgno-1)*(i64)pPager->pageSize; /* Offset to write */ - char *pData; /* Data to write */ + char *pData; /* Data to write */ assert( (pList->flags&PGHDR_NEED_SYNC)==0 ); if( pList->pgno==1 ) pager_write_changecounter(pList); - /* Encode the database */ - CODEC2(pPager, pList->pData, pgno, 6, return SQLITE_NOMEM_BKPT, pData); + pData = pList->pData; /* Write out the page data. */ rc = sqlite3OsWrite(pPager->fd, pData, pPager->pageSize, offset); /* If page 1 was just written, update Pager.dbFileVers to match - ** the value now stored in the database file. If writing this - ** page caused the database file to grow, update dbFileSize. + ** the value now stored in the database file. If writing this + ** page caused the database file to grow, update dbFileSize. */ if( pgno==1 ){ memcpy(&pPager->dbFileVers, &pData[24], sizeof(pPager->dbFileVers)); @@ -55083,18 +64191,18 @@ static int pager_write_pagelist(Pager *pPager, PgHdr *pList){ } /* -** Ensure that the sub-journal file is open. If it is already open, this +** Ensure that the sub-journal file is open. If it is already open, this ** function is a no-op. ** -** SQLITE_OK is returned if everything goes according to plan. An -** SQLITE_IOERR_XXX error code is returned if a call to sqlite3OsOpen() +** SQLITE_OK is returned if everything goes according to plan. An +** SQLITE_IOERR_XXX error code is returned if a call to sqlite3OsOpen() ** fails. */ static int openSubJournal(Pager *pPager){ int rc = SQLITE_OK; if( !isOpen(pPager->sjfd) ){ - const int flags = SQLITE_OPEN_SUBJOURNAL | SQLITE_OPEN_READWRITE - | SQLITE_OPEN_CREATE | SQLITE_OPEN_EXCLUSIVE + const int flags = SQLITE_OPEN_SUBJOURNAL | SQLITE_OPEN_READWRITE + | SQLITE_OPEN_CREATE | SQLITE_OPEN_EXCLUSIVE | SQLITE_OPEN_DELETEONCLOSE; int nStmtSpill = sqlite3Config.nStmtSpill; if( pPager->journalMode==PAGER_JOURNALMODE_MEMORY || pPager->subjInMemory ){ @@ -55106,13 +64214,13 @@ static int openSubJournal(Pager *pPager){ } /* -** Append a record of the current state of page pPg to the sub-journal. +** Append a record of the current state of page pPg to the sub-journal. ** ** If successful, set the bit corresponding to pPg->pgno in the bitvecs ** for all open savepoints before returning. ** ** This function returns SQLITE_OK if everything is successful, an IO -** error code if the attempt to write to the sub-journal fails, or +** error code if the attempt to write to the sub-journal fails, or ** SQLITE_NOMEM if a malloc fails while setting a bit in a savepoint ** bitvec. */ @@ -55125,9 +64233,9 @@ static int subjournalPage(PgHdr *pPg){ assert( pPager->useJournal ); assert( isOpen(pPager->jfd) || pagerUseWal(pPager) ); assert( isOpen(pPager->sjfd) || pPager->nSubRec==0 ); - assert( pagerUseWal(pPager) - || pageInJournal(pPager, pPg) - || pPg->pgno>pPager->dbOrigSize + assert( pagerUseWal(pPager) + || pageInJournal(pPager, pPg) + || pPg->pgno>pPager->dbOrigSize ); rc = openSubJournal(pPager); @@ -55137,12 +64245,6 @@ static int subjournalPage(PgHdr *pPg){ void *pData = pPg->pData; i64 offset = (i64)pPager->nSubRec*(4+pPager->pageSize); char *pData2; - -#if SQLITE_HAS_CODEC - if( !pPager->subjInMemory ){ - CODEC2(pPager, pData, pPg->pgno, 7, return SQLITE_NOMEM_BKPT, pData2); - }else -#endif pData2 = pData; PAGERTRACE(("STMT-JOURNAL %d page %d\n", PAGERID(pPager), pPg->pgno)); rc = write32bits(pPager->sjfd, offset, pPg->pgno); @@ -55170,14 +64272,14 @@ static int subjournalPageIfRequired(PgHdr *pPg){ ** This function is called by the pcache layer when it has reached some ** soft memory limit. The first argument is a pointer to a Pager object ** (cast as a void*). The pager is always 'purgeable' (not an in-memory -** database). The second argument is a reference to a page that is +** database). The second argument is a reference to a page that is ** currently dirty but has no outstanding references. The page -** is always associated with the Pager object passed as the first +** is always associated with the Pager object passed as the first ** argument. ** ** The job of this function is to make pPg clean by writing its contents ** out to the database file, if possible. This may involve syncing the -** journal file. +** journal file. ** ** If successful, sqlite3PcacheMakeClean() is called on the page and ** SQLITE_OK returned. If an IO error occurs while trying to make the @@ -55202,7 +64304,7 @@ static int pagerStress(void *p, PgHdr *pPg){ ** a rollback or by user request, respectively. ** ** Spilling is also prohibited when in an error state since that could - ** lead to database corruption. In the current implementation it + ** lead to database corruption. In the current implementation it ** is impossible for sqlite3PcacheFetch() to be called with createFlag==3 ** while in the error state, hence it is impossible for this routine to ** be called in the error state. Nevertheless, we include a NEVER() @@ -55223,26 +64325,26 @@ static int pagerStress(void *p, PgHdr *pPg){ pPg->pDirty = 0; if( pagerUseWal(pPager) ){ /* Write a single frame for this page to the log. */ - rc = subjournalPageIfRequired(pPg); + rc = subjournalPageIfRequired(pPg); if( rc==SQLITE_OK ){ rc = pagerWalFrames(pPager, pPg, 0, 0); } }else{ - + #ifdef SQLITE_ENABLE_BATCH_ATOMIC_WRITE if( pPager->tempFile==0 ){ rc = sqlite3JournalCreate(pPager->jfd); if( rc!=SQLITE_OK ) return pager_error(pPager, rc); } #endif - + /* Sync the journal file if required. */ - if( pPg->flags&PGHDR_NEED_SYNC + if( pPg->flags&PGHDR_NEED_SYNC || pPager->eState==PAGER_WRITER_CACHEMOD ){ rc = syncJournal(pPager, 1); } - + /* Write the contents of the page out to the database file. */ if( rc==SQLITE_OK ){ assert( (pPg->flags&PGHDR_NEED_SYNC)==0 ); @@ -55256,7 +64358,7 @@ static int pagerStress(void *p, PgHdr *pPg){ sqlite3PcacheMakeClean(pPg); } - return pager_error(pPager, rc); + return pager_error(pPager, rc); } /* @@ -55287,8 +64389,8 @@ SQLITE_PRIVATE int sqlite3PagerFlush(Pager *pPager){ ** The zFilename argument is the path to the database file to open. ** If zFilename is NULL then a randomly-named temporary file is created ** and used as the file to be cached. Temporary files are be deleted -** automatically when they are closed. If zFilename is ":memory:" then -** all information is held in cache. It is never written to disk. +** automatically when they are closed. If zFilename is ":memory:" then +** all information is held in cache. It is never written to disk. ** This can be used to implement an in-memory database. ** ** The nExtra parameter specifies the number of bytes of space allocated @@ -55302,13 +64404,13 @@ SQLITE_PRIVATE int sqlite3PagerFlush(Pager *pPager){ ** of the PAGER_* flags. ** ** The vfsFlags parameter is a bitmask to pass to the flags parameter -** of the xOpen() method of the supplied VFS when opening files. +** of the xOpen() method of the supplied VFS when opening files. ** -** If the pager object is allocated and the specified file opened +** If the pager object is allocated and the specified file opened ** successfully, SQLITE_OK is returned and *ppPager set to point to ** the new pager object. If an error occurs, *ppPager is set to NULL ** and error code returned. This function may return SQLITE_NOMEM -** (sqlite3Malloc() is used to allocate memory), SQLITE_CANTOPEN or +** (sqlite3Malloc() is used to allocate memory), SQLITE_CANTOPEN or ** various SQLITE_IO_XXX errors. */ SQLITE_PRIVATE int sqlite3PagerOpen( @@ -55325,11 +64427,7 @@ SQLITE_PRIVATE int sqlite3PagerOpen( int rc = SQLITE_OK; /* Return code */ int tempFile = 0; /* True for temp files (incl. in-memory files) */ int memDb = 0; /* True if this is an in-memory file */ -#ifdef SQLITE_ENABLE_DESERIALIZE int memJM = 0; /* Memory journal mode */ -#else -# define memJM 0 -#endif int readOnly = 0; /* True if this is a read-only file */ int journalFileSize; /* Bytes to allocate for each journal fd */ char *zPathname = 0; /* Full path to database file */ @@ -55338,7 +64436,8 @@ SQLITE_PRIVATE int sqlite3PagerOpen( int pcacheSize = sqlite3PcacheSize(); /* Bytes to allocate for PCache */ u32 szPageDflt = SQLITE_DEFAULT_PAGE_SIZE; /* Default page size */ const char *zUri = 0; /* URI args to copy */ - int nUri = 0; /* Number of bytes of URI args at *zUri */ + int nUriByte = 1; /* Number of bytes of URI args at *zUri */ + /* Figure out how much space is required for each journal file-handle ** (there are two of them, the main journal and the sub-journal). */ @@ -55365,21 +64464,30 @@ SQLITE_PRIVATE int sqlite3PagerOpen( */ if( zFilename && zFilename[0] ){ const char *z; - nPathname = pVfs->mxPathname+1; - zPathname = sqlite3DbMallocRaw(0, nPathname*2); + nPathname = pVfs->mxPathname + 1; + zPathname = sqlite3DbMallocRaw(0, 2*(i64)nPathname); if( zPathname==0 ){ return SQLITE_NOMEM_BKPT; } zPathname[0] = 0; /* Make sure initialized even if FullPathname() fails */ rc = sqlite3OsFullPathname(pVfs, zFilename, nPathname, zPathname); + if( rc!=SQLITE_OK ){ + if( rc==SQLITE_OK_SYMLINK ){ + if( vfsFlags & SQLITE_OPEN_NOFOLLOW ){ + rc = SQLITE_CANTOPEN_SYMLINK; + }else{ + rc = SQLITE_OK; + } + } + } nPathname = sqlite3Strlen30(zPathname); z = zUri = &zFilename[sqlite3Strlen30(zFilename)+1]; while( *z ){ - z += sqlite3Strlen30(z)+1; - z += sqlite3Strlen30(z)+1; + z += strlen(z)+1; + z += strlen(z)+1; } - nUri = (int)(&z[1] - zUri); - assert( nUri>=0 ); + nUriByte = (int)(&z[1] - zUri); + assert( nUriByte>=1 ); if( rc==SQLITE_OK && nPathname+8>pVfs->mxPathname ){ /* This branch is taken when the journal path required by ** the database being opened will be more than pVfs->mxPathname @@ -55396,7 +64504,7 @@ SQLITE_PRIVATE int sqlite3PagerOpen( } /* Allocate memory for the Pager structure, PCache object, the - ** three file descriptors, the database file name and the journal + ** three file descriptors, the database file name and the journal ** file name. The layout in memory is as follows: ** ** Pager object (sizeof(Pager) bytes) @@ -55404,50 +64512,113 @@ SQLITE_PRIVATE int sqlite3PagerOpen( ** Database file handle (pVfs->szOsFile bytes) ** Sub-journal file handle (journalFileSize bytes) ** Main journal file handle (journalFileSize bytes) + ** Ptr back to the Pager (sizeof(Pager*) bytes) + ** \0\0\0\0 database prefix (4 bytes) ** Database file name (nPathname+1 bytes) - ** Journal file name (nPathname+8+1 bytes) + ** URI query parameters (nUriByte bytes) + ** Journal filename (nPathname+8+1 bytes) + ** WAL filename (nPathname+4+1 bytes) + ** \0\0\0 terminator (3 bytes) + ** + ** Some 3rd-party software, over which we have no control, depends on + ** the specific order of the filenames and the \0 separators between them + ** so that it can (for example) find the database filename given the WAL + ** filename without using the sqlite3_filename_database() API. This is a + ** misuse of SQLite and a bug in the 3rd-party software, but the 3rd-party + ** software is in widespread use, so we try to avoid changing the filename + ** order and formatting if possible. In particular, the details of the + ** filename format expected by 3rd-party software should be as follows: + ** + ** - Main Database Path + ** - \0 + ** - Multiple URI components consisting of: + ** - Key + ** - \0 + ** - Value + ** - \0 + ** - \0 + ** - Journal Path + ** - \0 + ** - WAL Path (zWALName) + ** - \0 + ** + ** The sqlite3_create_filename() interface and the databaseFilename() utility + ** that is used by sqlite3_filename_database() and kin also depend on the + ** specific formatting and order of the various filenames, so if the format + ** changes here, be sure to change it there as well. */ + assert( SQLITE_PTRSIZE==sizeof(Pager*) ); pPtr = (u8 *)sqlite3MallocZero( - ROUND8(sizeof(*pPager)) + /* Pager structure */ - ROUND8(pcacheSize) + /* PCache object */ - ROUND8(pVfs->szOsFile) + /* The main db file */ - journalFileSize * 2 + /* The two journal files */ - nPathname + 1 + nUri + /* zFilename */ - nPathname + 8 + 2 /* zJournal */ + ROUND8(sizeof(*pPager)) + /* Pager structure */ + ROUND8(pcacheSize) + /* PCache object */ + ROUND8(pVfs->szOsFile) + /* The main db file */ + (u64)journalFileSize * 2 + /* The two journal files */ + SQLITE_PTRSIZE + /* Space to hold a pointer */ + 4 + /* Database prefix */ + (u64)nPathname + 1 + /* database filename */ + (u64)nUriByte + /* query parameters */ + (u64)nPathname + 8 + 1 + /* Journal filename */ #ifndef SQLITE_OMIT_WAL - + nPathname + 4 + 2 /* zWal */ + (u64)nPathname + 4 + 1 + /* WAL filename */ #endif + 3 /* Terminator */ ); assert( EIGHT_BYTE_ALIGNMENT(SQLITE_INT_TO_PTR(journalFileSize)) ); if( !pPtr ){ sqlite3DbFree(0, zPathname); return SQLITE_NOMEM_BKPT; } - pPager = (Pager*)(pPtr); - pPager->pPCache = (PCache*)(pPtr += ROUND8(sizeof(*pPager))); - pPager->fd = (sqlite3_file*)(pPtr += ROUND8(pcacheSize)); - pPager->sjfd = (sqlite3_file*)(pPtr += ROUND8(pVfs->szOsFile)); - pPager->jfd = (sqlite3_file*)(pPtr += journalFileSize); - pPager->zFilename = (char*)(pPtr += journalFileSize); + pPager = (Pager*)pPtr; pPtr += ROUND8(sizeof(*pPager)); + pPager->pPCache = (PCache*)pPtr; pPtr += ROUND8(pcacheSize); + pPager->fd = (sqlite3_file*)pPtr; pPtr += ROUND8(pVfs->szOsFile); + pPager->sjfd = (sqlite3_file*)pPtr; pPtr += journalFileSize; + pPager->jfd = (sqlite3_file*)pPtr; pPtr += journalFileSize; assert( EIGHT_BYTE_ALIGNMENT(pPager->jfd) ); + memcpy(pPtr, &pPager, SQLITE_PTRSIZE); pPtr += SQLITE_PTRSIZE; + + /* Fill in the Pager.zFilename and pPager.zQueryParam fields */ + pPtr += 4; /* Skip zero prefix */ + pPager->zFilename = (char*)pPtr; + if( nPathname>0 ){ + memcpy(pPtr, zPathname, nPathname); pPtr += nPathname + 1; + if( zUri ){ + memcpy(pPtr, zUri, nUriByte); pPtr += nUriByte; + }else{ + pPtr++; + } + } + + + /* Fill in Pager.zJournal */ + if( nPathname>0 ){ + pPager->zJournal = (char*)pPtr; + memcpy(pPtr, zPathname, nPathname); pPtr += nPathname; + memcpy(pPtr, "-journal",8); pPtr += 8 + 1; +#ifdef SQLITE_ENABLE_8_3_NAMES + sqlite3FileSuffix3(zFilename,pPager->zJournal); + pPtr = (u8*)(pPager->zJournal + sqlite3Strlen30(pPager->zJournal)+1); +#endif + }else{ + pPager->zJournal = 0; + } - /* Fill in the Pager.zFilename and Pager.zJournal buffers, if required. */ - if( zPathname ){ - assert( nPathname>0 ); - pPager->zJournal = (char*)(pPtr += nPathname + 1 + nUri); - memcpy(pPager->zFilename, zPathname, nPathname); - if( nUri ) memcpy(&pPager->zFilename[nPathname+1], zUri, nUri); - memcpy(pPager->zJournal, zPathname, nPathname); - memcpy(&pPager->zJournal[nPathname], "-journal\000", 8+2); - sqlite3FileSuffix3(pPager->zFilename, pPager->zJournal); #ifndef SQLITE_OMIT_WAL - pPager->zWal = &pPager->zJournal[nPathname+8+1]; - memcpy(pPager->zWal, zPathname, nPathname); - memcpy(&pPager->zWal[nPathname], "-wal\000", 4+1); - sqlite3FileSuffix3(pPager->zFilename, pPager->zWal); + /* Fill in Pager.zWal */ + if( nPathname>0 ){ + pPager->zWal = (char*)pPtr; + memcpy(pPtr, zPathname, nPathname); pPtr += nPathname; + memcpy(pPtr, "-wal", 4); pPtr += 4 + 1; +#ifdef SQLITE_ENABLE_8_3_NAMES + sqlite3FileSuffix3(zFilename, pPager->zWal); + pPtr = (u8*)(pPager->zWal + sqlite3Strlen30(pPager->zWal)+1); #endif - sqlite3DbFree(0, zPathname); + }else{ + pPager->zWal = 0; } +#endif + (void)pPtr; /* Suppress warning about unused pPtr value */ + + if( nPathname ) sqlite3DbFree(0, zPathname); pPager->pVfs = pVfs; pPager->vfsFlags = vfsFlags; @@ -55457,9 +64628,7 @@ SQLITE_PRIVATE int sqlite3PagerOpen( int fout = 0; /* VFS flags returned by xOpen() */ rc = sqlite3OsOpen(pVfs, pPager->zFilename, pPager->fd, vfsFlags, &fout); assert( !memDb ); -#ifdef SQLITE_ENABLE_DESERIALIZE - memJM = (fout&SQLITE_OPEN_MEMORY)!=0; -#endif + pPager->memVfs = memJM = (fout&SQLITE_OPEN_MEMORY)!=0; readOnly = (fout&SQLITE_OPEN_READONLY)!=0; /* If the file was successfully opened for read/write access, @@ -55496,9 +64665,9 @@ SQLITE_PRIVATE int sqlite3PagerOpen( } #endif } - pPager->noLock = sqlite3_uri_boolean(zFilename, "nolock", 0); + pPager->noLock = sqlite3_uri_boolean(pPager->zFilename, "nolock", 0); if( (iDc & SQLITE_IOCAP_IMMUTABLE)!=0 - || sqlite3_uri_boolean(zFilename, "immutable", 0) ){ + || sqlite3_uri_boolean(pPager->zFilename, "immutable", 0) ){ vfsFlags |= SQLITE_OPEN_READONLY; goto act_like_temp_file; } @@ -55513,7 +64682,7 @@ SQLITE_PRIVATE int sqlite3PagerOpen( ** disk and uses an in-memory rollback journal. ** ** This branch also runs for files marked as immutable. - */ + */ act_like_temp_file: tempFile = 1; pPager->eState = PAGER_READER; /* Pretend we already have a lock */ @@ -55522,7 +64691,7 @@ SQLITE_PRIVATE int sqlite3PagerOpen( readOnly = (vfsFlags&SQLITE_OPEN_READONLY); } - /* The following call to PagerSetPagesize() serves to set the value of + /* The following call to PagerSetPagesize() serves to set the value of ** Pager.pageSize and to allocate the Pager.pTmpSpace buffer. */ if( rc==SQLITE_OK ){ @@ -55562,26 +64731,15 @@ SQLITE_PRIVATE int sqlite3PagerOpen( /* pPager->state = PAGER_UNLOCK; */ /* pPager->errMask = 0; */ pPager->tempFile = (u8)tempFile; - assert( tempFile==PAGER_LOCKINGMODE_NORMAL + assert( tempFile==PAGER_LOCKINGMODE_NORMAL || tempFile==PAGER_LOCKINGMODE_EXCLUSIVE ); assert( PAGER_LOCKINGMODE_EXCLUSIVE==1 ); - pPager->exclusiveMode = (u8)tempFile; + pPager->exclusiveMode = (u8)tempFile; pPager->changeCountDone = pPager->tempFile; pPager->memDb = (u8)memDb; pPager->readOnly = (u8)readOnly; assert( useJournal || pPager->tempFile ); - pPager->noSync = pPager->tempFile; - if( pPager->noSync ){ - assert( pPager->fullSync==0 ); - assert( pPager->extraSync==0 ); - assert( pPager->syncFlags==0 ); - assert( pPager->walSyncFlags==0 ); - }else{ - pPager->fullSync = 1; - pPager->extraSync = 0; - pPager->syncFlags = SQLITE_SYNC_NORMAL; - pPager->walSyncFlags = SQLITE_SYNC_NORMAL | (SQLITE_SYNC_NORMAL<<2); - } + sqlite3PagerSetFlags(pPager, (SQLITE_DEFAULT_SYNCHRONOUS+1)|PAGER_CACHESPILL); /* pPager->pFirst = 0; */ /* pPager->pFirstSynced = 0; */ /* pPager->pLast = 0; */ @@ -55605,12 +64763,28 @@ SQLITE_PRIVATE int sqlite3PagerOpen( return SQLITE_OK; } +/* +** Return the sqlite3_file for the main database given the name +** of the corresponding WAL or Journal name as passed into +** xOpen. +*/ +SQLITE_API sqlite3_file *sqlite3_database_file_object(const char *zName){ + Pager *pPager; + const char *p; + while( zName[-1]!=0 || zName[-2]!=0 || zName[-3]!=0 || zName[-4]!=0 ){ + zName--; + } + p = zName - 4 - sizeof(Pager*); + assert( EIGHT_BYTE_ALIGNMENT(p) ); + pPager = *(Pager**)p; + return pPager->fd; +} /* ** This function is called after transitioning from PAGER_UNLOCK to ** PAGER_SHARED state. It tests if there is a hot journal present in -** the file-system for the given pager. A hot journal is one that +** the file-system for the given pager. A hot journal is one that ** needs to be played back. According to this function, a hot-journal ** file exists if the following criteria are met: ** @@ -55625,14 +64799,14 @@ SQLITE_PRIVATE int sqlite3PagerOpen( ** just deleted using OsDelete, *pExists is set to 0 and SQLITE_OK ** is returned. ** -** This routine does not check if there is a master journal filename -** at the end of the file. If there is, and that master journal file +** This routine does not check if there is a super-journal filename +** at the end of the file. If there is, and that super-journal file ** does not exist, then the journal file is not really hot. In this ** case this routine will return a false-positive. The pager_playback() -** routine will discover that the journal file is not really hot and -** will not roll it back. +** routine will discover that the journal file is not really hot and +** will not roll it back. ** -** If a hot-journal file is found to exist, *pExists is set to 1 and +** If a hot-journal file is found to exist, *pExists is set to 1 and ** SQLITE_OK returned. If no hot-journal file is present, *pExists is ** set to 0 and SQLITE_OK returned. If an IO error occurs while trying ** to determine whether or not a hot-journal file exists, the IO error @@ -55660,7 +64834,7 @@ static int hasHotJournal(Pager *pPager, int *pExists){ int locked = 0; /* True if some process holds a RESERVED lock */ /* Race condition here: Another process might have been holding the - ** the RESERVED lock and have a journal open at the sqlite3OsAccess() + ** the RESERVED lock and have a journal open at the sqlite3OsAccess() ** call above, but then delete the journal and drop the lock before ** we get to the following sqlite3OsCheckReservedLock() call. If that ** is the case, this routine might think there is a hot journal when @@ -55693,7 +64867,7 @@ static int hasHotJournal(Pager *pPager, int *pExists){ /* The journal file exists and no other connection has a reserved ** or greater lock on the database file. Now check that there is ** at least one non-zero bytes at the start of the journal file. - ** If there is, then we consider this journal to be hot. If not, + ** If there is, then we consider this journal to be hot. If not, ** it can be ignored. */ if( !jrnlOpen ){ @@ -55743,7 +64917,7 @@ static int hasHotJournal(Pager *pPager, int *pExists){ ** on the database file), then an attempt is made to obtain a ** SHARED lock on the database file. Immediately after obtaining ** the SHARED lock, the file-system is checked for a hot-journal, -** which is played back if present. Following any hot-journal +** which is played back if present. Following any hot-journal ** rollback, the contents of the cache are validated by checking ** the 'change-counter' field of the database file header and ** discarded if they are found to be invalid. @@ -55754,8 +64928,8 @@ static int hasHotJournal(Pager *pPager, int *pExists){ ** the contents of the page cache and rolling back any open journal ** file. ** -** If everything is successful, SQLITE_OK is returned. If an IO error -** occurs while locking the database, checking for a hot-journal file or +** If everything is successful, SQLITE_OK is returned. If an IO error +** occurs while locking the database, checking for a hot-journal file or ** rolling back a journal file, the IO error code is returned. */ SQLITE_PRIVATE int sqlite3PagerSharedLock(Pager *pPager){ @@ -55763,7 +64937,7 @@ SQLITE_PRIVATE int sqlite3PagerSharedLock(Pager *pPager){ /* This routine is only called from b-tree and only when there are no ** outstanding pages. This implies that the pager state should either - ** be OPEN or READER. READER is only possible if the pager is or was in + ** be OPEN or READER. READER is only possible if the pager is or was in ** exclusive access mode. */ assert( sqlite3PcacheRefCount(pPager->pPCache)==0 ); assert( assert_pager_state(pPager) ); @@ -55801,12 +64975,12 @@ SQLITE_PRIVATE int sqlite3PagerSharedLock(Pager *pPager){ ** important that a RESERVED lock is not obtained on the way to the ** EXCLUSIVE lock. If it were, another process might open the ** database file, detect the RESERVED lock, and conclude that the - ** database is safe to read while this process is still rolling the + ** database is safe to read while this process is still rolling the ** hot-journal back. - ** + ** ** Because the intermediate RESERVED lock is not requested, any - ** other process attempting to access the database file will get to - ** this point in the code and fail to obtain its own EXCLUSIVE lock + ** other process attempting to access the database file will get to + ** this point in the code and fail to obtain its own EXCLUSIVE lock ** on the database file. ** ** Unless the pager is in locking_mode=exclusive mode, the lock is @@ -55816,21 +64990,21 @@ SQLITE_PRIVATE int sqlite3PagerSharedLock(Pager *pPager){ if( rc!=SQLITE_OK ){ goto failed; } - - /* If it is not already open and the file exists on disk, open the - ** journal for read/write access. Write access is required because - ** in exclusive-access mode the file descriptor will be kept open - ** and possibly used for a transaction later on. Also, write-access - ** is usually required to finalize the journal in journal_mode=persist + + /* If it is not already open and the file exists on disk, open the + ** journal for read/write access. Write access is required because + ** in exclusive-access mode the file descriptor will be kept open + ** and possibly used for a transaction later on. Also, write-access + ** is usually required to finalize the journal in journal_mode=persist ** mode (and also for journal_mode=truncate on some systems). ** - ** If the journal does not exist, it usually means that some - ** other connection managed to get in and roll it back before - ** this connection obtained the exclusive lock above. Or, it + ** If the journal does not exist, it usually means that some + ** other connection managed to get in and roll it back before + ** this connection obtained the exclusive lock above. Or, it ** may mean that the pager was in the error-state when this ** function was called and the journal file does not exist. */ - if( !isOpen(pPager->jfd) ){ + if( !isOpen(pPager->jfd) && pPager->journalMode!=PAGER_JOURNALMODE_OFF ){ sqlite3_vfs * const pVfs = pPager->pVfs; int bExists; /* True if journal file exists */ rc = sqlite3OsAccess( @@ -55847,7 +65021,7 @@ SQLITE_PRIVATE int sqlite3PagerSharedLock(Pager *pPager){ } } } - + /* Playback and delete the journal. Drop the database write ** lock and reacquire the read lock. Purge the cache before ** playing back the hot-journal so that we don't end up with @@ -55872,8 +65046,8 @@ SQLITE_PRIVATE int sqlite3PagerSharedLock(Pager *pPager){ ** or roll back a hot-journal while holding an EXCLUSIVE lock. The ** pager_unlock() routine will be called before returning to unlock ** the file. If the unlock attempt fails, then Pager.eLock must be - ** set to UNKNOWN_LOCK (see the comment above the #define for - ** UNKNOWN_LOCK above for an explanation). + ** set to UNKNOWN_LOCK (see the comment above the #define for + ** UNKNOWN_LOCK above for an explanation). ** ** In order to get pager_unlock() to do this, set Pager.eState to ** PAGER_ERROR now. This is not actually counted as a transition @@ -55881,7 +65055,7 @@ SQLITE_PRIVATE int sqlite3PagerSharedLock(Pager *pPager){ ** since we know that the same call to pager_unlock() will very ** shortly transition the pager object to the OPEN state. Calling ** assert_pager_state() would fail now, as it should not be possible - ** to be in ERROR state when there are zero outstanding page + ** to be in ERROR state when there are zero outstanding page ** references. */ pager_error(pPager, rc); @@ -55906,8 +65080,8 @@ SQLITE_PRIVATE int sqlite3PagerSharedLock(Pager *pPager){ ** a 32-bit counter that is incremented with each change. The ** other bytes change randomly with each file change when ** a codec is in use. - ** - ** There is a vanishingly small chance that a change will not be + ** + ** There is a vanishingly small chance that a change will not be ** detected. The chance of an undetected change is so small that ** it can be neglected. */ @@ -55974,7 +65148,7 @@ SQLITE_PRIVATE int sqlite3PagerSharedLock(Pager *pPager){ ** Except, in locking_mode=EXCLUSIVE when there is nothing to in ** the rollback journal, the unlock is not performed and there is ** nothing to rollback, so this routine is a no-op. -*/ +*/ static void pagerUnlockIfUnused(Pager *pPager){ if( sqlite3PcacheRefCount(pPager->pPCache)==0 ){ assert( pPager->nMmapOut==0 ); /* because page1 is never memory mapped */ @@ -55984,7 +65158,7 @@ static void pagerUnlockIfUnused(Pager *pPager){ /* ** The page getter methods each try to acquire a reference to a -** page with page number pgno. If the requested reference is +** page with page number pgno. If the requested reference is ** successfully obtained, it is copied to *ppPage and SQLITE_OK returned. ** ** There are different implementations of the getter method depending @@ -55994,22 +65168,22 @@ static void pagerUnlockIfUnused(Pager *pPager){ ** getPageError() -- Used if the pager is in an error state ** getPageMmap() -- Used if memory-mapped I/O is enabled ** -** If the requested page is already in the cache, it is returned. +** If the requested page is already in the cache, it is returned. ** Otherwise, a new page object is allocated and populated with data ** read from the database file. In some cases, the pcache module may ** choose not to allocate a new page object and may reuse an existing ** object with no outstanding references. ** -** The extra data appended to a page is always initialized to zeros the -** first time a page is loaded into memory. If the page requested is +** The extra data appended to a page is always initialized to zeros the +** first time a page is loaded into memory. If the page requested is ** already in the cache when this function is called, then the extra ** data is left as it was when the page object was last used. ** -** If the database image is smaller than the requested page or if -** the flags parameter contains the PAGER_GET_NOCONTENT bit and the -** requested page is not already stored in the cache, then no -** actual disk read occurs. In this case the memory image of the -** page is initialized to all zeros. +** If the database image is smaller than the requested page or if +** the flags parameter contains the PAGER_GET_NOCONTENT bit and the +** requested page is not already stored in the cache, then no +** actual disk read occurs. In this case the memory image of the +** page is initialized to all zeros. ** ** If PAGER_GET_NOCONTENT is true, it means that we do not care about ** the contents of the page. This occurs in two scenarios: @@ -56075,18 +65249,18 @@ static int getPageNormal( if( pPg->pPager && !noContent ){ /* In this case the pcache already contains an initialized copy of ** the page. Return without further ado. */ - assert( pgno<=PAGER_MAX_PGNO && pgno!=PAGER_MJ_PGNO(pPager) ); + assert( pgno!=PAGER_SJ_PGNO(pPager) ); pPager->aStat[PAGER_STAT_HIT]++; return SQLITE_OK; }else{ - /* The pager cache has created a new page. Its content needs to + /* The pager cache has created a new page. Its content needs to ** be initialized. But first some error checks: ** - ** (1) The maximum page number is 2^31 + ** (*) obsolete. Was: maximum page number is 2^31 ** (2) Never try to fetch the locking page */ - if( pgno>PAGER_MAX_PGNO || pgno==PAGER_MJ_PGNO(pPager) ){ + if( pgno==PAGER_SJ_PGNO(pPager) ){ rc = SQLITE_CORRUPT_BKPT; goto pager_acquire_err; } @@ -56097,13 +65271,17 @@ static int getPageNormal( if( !isOpen(pPager->fd) || pPager->dbSizepPager->mxPgno ){ rc = SQLITE_FULL; + if( pgno<=pPager->dbSize ){ + sqlite3PcacheRelease(pPg); + pPg = 0; + } goto pager_acquire_err; } if( noContent ){ /* Failure to set the bits in the InJournal bit-vectors is benign. - ** It merely means that we might do some extra work to journal a - ** page that does not need to be journaled. Nevertheless, be sure - ** to test the case where a malloc error occurs while trying to set + ** It merely means that we might do some extra work to journal a + ** page that does not need to be journaled. Nevertheless, be sure + ** to test the case where a malloc error occurs while trying to set ** a bit in a bit vector. */ sqlite3BeginBenignMalloc(); @@ -56153,16 +65331,13 @@ static int getPageMMap( /* It is acceptable to use a read-only (mmap) page for any page except ** page 1 if there is no write-transaction open or the ACQUIRE_READONLY - ** flag was specified by the caller. And so long as the db is not a + ** flag was specified by the caller. And so long as the db is not a ** temporary or in-memory database. */ const int bMmapOk = (pgno>1 && (pPager->eState==PAGER_READER || (flags & PAGER_GET_READONLY)) ); assert( USEFETCH(pPager) ); -#ifdef SQLITE_HAS_CODEC - assert( pPager->xCodec==0 ); -#endif /* Optimization note: Adding the "pgno<=1" term before "pgno==0" here ** allows the compiler optimizer to reuse the results of the "pgno>1" @@ -56185,7 +65360,7 @@ static int getPageMMap( } if( bMmapOk && iFrame==0 ){ void *pData = 0; - rc = sqlite3OsFetch(pPager->fd, + rc = sqlite3OsFetch(pPager->fd, (i64)(pgno-1) * pPager->pageSize, pPager->pageSize, &pData ); if( rc==SQLITE_OK && pData ){ @@ -56235,18 +65410,31 @@ SQLITE_PRIVATE int sqlite3PagerGet( DbPage **ppPage, /* Write a pointer to the page here */ int flags /* PAGER_GET_XXX flags */ ){ +#if 0 /* Trace page fetch by setting to 1 */ + int rc; + printf("PAGE %u\n", pgno); + fflush(stdout); + rc = pPager->xGet(pPager, pgno, ppPage, flags); + if( rc ){ + printf("PAGE %u failed with 0x%02x\n", pgno, rc); + fflush(stdout); + } + return rc; +#else + /* Normal, high-speed version of sqlite3PagerGet() */ return pPager->xGet(pPager, pgno, ppPage, flags); +#endif } /* ** Acquire a page if it is already in the in-memory cache. Do ** not read the page from disk. Return a pointer to the page, -** or 0 if the page is not in cache. +** or 0 if the page is not in cache. ** ** See also sqlite3PagerGet(). The difference between this routine ** and sqlite3PagerGet() is that _get() will go to the disk and read ** in the page if the page is not already in cache. This routine -** returns NULL if the page is not in cache or if a disk I/O error +** returns NULL if the page is not in cache or if a disk I/O error ** has ever happened. */ SQLITE_PRIVATE DbPage *sqlite3PagerLookup(Pager *pPager, Pgno pgno){ @@ -56263,10 +65451,12 @@ SQLITE_PRIVATE DbPage *sqlite3PagerLookup(Pager *pPager, Pgno pgno){ /* ** Release a page reference. ** -** The sqlite3PagerUnref() and sqlite3PagerUnrefNotNull() may only be -** used if we know that the page being released is not the last page. +** The sqlite3PagerUnref() and sqlite3PagerUnrefNotNull() may only be used +** if we know that the page being released is not the last reference to page1. ** The btree layer always holds page1 open until the end, so these first -** to routines can be used to release any page other than BtShared.pPage1. +** two routines can be used to release any page other than BtShared.pPage1. +** The assert() at tag-20230419-2 proves that this constraint is always +** honored. ** ** Use sqlite3PagerUnrefPageOne() to release page1. This latter routine ** checks the total number of outstanding pages and if the number of @@ -56282,7 +65472,7 @@ SQLITE_PRIVATE void sqlite3PagerUnrefNotNull(DbPage *pPg){ sqlite3PcacheRelease(pPg); } /* Do not use this routine to release the last reference to page1 */ - assert( sqlite3PcacheRefCount(pPager->pPCache)>0 ); + assert( sqlite3PcacheRefCount(pPager->pPCache)>0 ); /* tag-20230419-2 */ } SQLITE_PRIVATE void sqlite3PagerUnref(DbPage *pPg){ if( pPg ) sqlite3PagerUnrefNotNull(pPg); @@ -56293,31 +65483,30 @@ SQLITE_PRIVATE void sqlite3PagerUnrefPageOne(DbPage *pPg){ assert( pPg->pgno==1 ); assert( (pPg->flags & PGHDR_MMAP)==0 ); /* Page1 is never memory mapped */ pPager = pPg->pPager; - sqlite3PagerResetLockTimeout(pPager); sqlite3PcacheRelease(pPg); pagerUnlockIfUnused(pPager); } /* ** This function is called at the start of every write transaction. -** There must already be a RESERVED or EXCLUSIVE lock on the database +** There must already be a RESERVED or EXCLUSIVE lock on the database ** file when this routine is called. ** ** Open the journal file for pager pPager and write a journal header ** to the start of it. If there are active savepoints, open the sub-journal -** as well. This function is only used when the journal file is being -** opened to write a rollback log for a transaction. It is not used +** as well. This function is only used when the journal file is being +** opened to write a rollback log for a transaction. It is not used ** when opening a hot journal file to roll it back. ** ** If the journal file is already open (as it may be in exclusive mode), ** then this function just writes a journal header to the start of the -** already open file. +** already open file. ** ** Whether or not the journal file is opened by this function, the ** Pager.pInJournal bitvec structure is allocated. ** -** Return SQLITE_OK if everything is successful. Otherwise, return -** SQLITE_NOMEM if the attempt to allocate Pager.pInJournal fails, or +** Return SQLITE_OK if everything is successful. Otherwise, return +** SQLITE_NOMEM if the attempt to allocate Pager.pInJournal fails, or ** an IO error code if opening or writing the journal file fails. */ static int pager_open_journal(Pager *pPager){ @@ -56327,7 +65516,7 @@ static int pager_open_journal(Pager *pPager){ assert( pPager->eState==PAGER_WRITER_LOCKED ); assert( assert_pager_state(pPager) ); assert( pPager->pInJournal==0 ); - + /* If already in the error state, this function is a no-op. But on ** the other hand, this routine is never called if we are already in ** an error state. */ @@ -56338,7 +65527,7 @@ static int pager_open_journal(Pager *pPager){ if( pPager->pInJournal==0 ){ return SQLITE_NOMEM_BKPT; } - + /* Open the journal file if it is not already open. */ if( !isOpen(pPager->jfd) ){ if( pPager->journalMode==PAGER_JOURNALMODE_MEMORY ){ @@ -56349,12 +65538,13 @@ static int pager_open_journal(Pager *pPager){ if( pPager->tempFile ){ flags |= (SQLITE_OPEN_DELETEONCLOSE|SQLITE_OPEN_TEMP_JOURNAL); + flags |= SQLITE_OPEN_EXCLUSIVE; nSpill = sqlite3Config.nStmtSpill; }else{ flags |= SQLITE_OPEN_MAIN_JOURNAL; nSpill = jrnlBufferSize(pPager); } - + /* Verify that the database still has the same name as it did when ** it was originally opened. */ rc = databaseIsUnmoved(pPager); @@ -56366,16 +65556,16 @@ static int pager_open_journal(Pager *pPager){ } assert( rc!=SQLITE_OK || isOpen(pPager->jfd) ); } - - - /* Write the first journal header to the journal file and open + + + /* Write the first journal header to the journal file and open ** the sub-journal if necessary. */ if( rc==SQLITE_OK ){ /* TODO: Check if all of these are really required. */ pPager->nRec = 0; pPager->journalOff = 0; - pPager->setMaster = 0; + pPager->setSuper = 0; pPager->journalHdr = 0; rc = writeJournalHdr(pPager); } @@ -56384,6 +65574,7 @@ static int pager_open_journal(Pager *pPager){ if( rc!=SQLITE_OK ){ sqlite3BitvecDestroy(pPager->pInJournal); pPager->pInJournal = 0; + pPager->journalOff = 0; }else{ assert( pPager->eState==PAGER_WRITER_LOCKED ); pPager->eState = PAGER_WRITER_CACHEMOD; @@ -56393,12 +65584,12 @@ static int pager_open_journal(Pager *pPager){ } /* -** Begin a write-transaction on the specified pager object. If a +** Begin a write-transaction on the specified pager object. If a ** write-transaction has already been opened, this function is a no-op. ** ** If the exFlag argument is false, then acquire at least a RESERVED ** lock on the database file. If exFlag is true, then acquire at least -** an EXCLUSIVE lock. If such a lock is already held, no locking +** an EXCLUSIVE lock. If such a lock is already held, no locking ** functions need be called. ** ** If the subjInMemory argument is non-zero, then any sub-journal opened @@ -56406,7 +65597,7 @@ static int pager_open_journal(Pager *pPager){ ** has no effect if the sub-journal is already opened (as it may be when ** running in exclusive mode) or if the transaction does not require a ** sub-journal. If the subjInMemory argument is zero, then any required -** sub-journal is implemented in-memory if pPager is an in-memory database, +** sub-journal is implemented in-memory if pPager is an in-memory database, ** or using a temporary file otherwise. */ SQLITE_PRIVATE int sqlite3PagerBegin(Pager *pPager, int exFlag, int subjInMemory){ @@ -56416,7 +65607,7 @@ SQLITE_PRIVATE int sqlite3PagerBegin(Pager *pPager, int exFlag, int subjInMemory assert( pPager->eState>=PAGER_READER && pPager->eStatesubjInMemory = (u8)subjInMemory; - if( ALWAYS(pPager->eState==PAGER_READER) ){ + if( pPager->eState==PAGER_READER ){ assert( pPager->pInJournal==0 ); if( pagerUseWal(pPager) ){ @@ -56454,9 +65645,9 @@ SQLITE_PRIVATE int sqlite3PagerBegin(Pager *pPager, int exFlag, int subjInMemory ** ** WAL mode sets Pager.eState to PAGER_WRITER_LOCKED or CACHEMOD ** when it has an open transaction, but never to DBMOD or FINISHED. - ** This is because in those states the code to roll back savepoint - ** transactions may copy data from the sub-journal into the database - ** file as well as into the page cache. Which would be incorrect in + ** This is because in those states the code to roll back savepoint + ** transactions may copy data from the sub-journal into the database + ** file as well as into the page cache. Which would be incorrect in ** WAL mode. */ pPager->eState = PAGER_WRITER_LOCKED; @@ -56488,10 +65679,10 @@ static SQLITE_NOINLINE int pagerAddPageToRollbackJournal(PgHdr *pPg){ /* We should never write to the journal file the page that ** contains the database locks. The following assert verifies ** that we do not. */ - assert( pPg->pgno!=PAGER_MJ_PGNO(pPager) ); + assert( pPg->pgno!=PAGER_SJ_PGNO(pPager) ); assert( pPager->journalHdr<=pPager->journalOff ); - CODEC2(pPager, pPg->pData, pPg->pgno, 7, return SQLITE_NOMEM_BKPT, pData2); + pData2 = pPg->pData; cksum = pager_cksum(pPager, (u8*)pData2); /* Even if an IO or diskfull error occurs while journalling the @@ -56510,11 +65701,11 @@ static SQLITE_NOINLINE int pagerAddPageToRollbackJournal(PgHdr *pPg){ rc = write32bits(pPager->jfd, iOff+pPager->pageSize+4, cksum); if( rc!=SQLITE_OK ) return rc; - IOTRACE(("JOUT %p %d %lld %d\n", pPager, pPg->pgno, + IOTRACE(("JOUT %p %d %lld %d\n", pPager, pPg->pgno, pPager->journalOff, pPager->pageSize)); PAGER_INCR(sqlite3_pager_writej_count); PAGERTRACE(("JOURNAL %d page %d needSync=%d hash(%08x)\n", - PAGERID(pPager), pPg->pgno, + PAGERID(pPager), pPg->pgno, ((pPg->flags&PGHDR_NEED_SYNC)?1:0), pager_pagehash(pPg))); pPager->journalOff += 8 + pPager->pageSize; @@ -56529,9 +65720,9 @@ static SQLITE_NOINLINE int pagerAddPageToRollbackJournal(PgHdr *pPg){ } /* -** Mark a single data page as writeable. The page is written into the +** Mark a single data page as writeable. The page is written into the ** main journal or sub-journal as required. If the page is written into -** one of the journals, the corresponding bit is set in the +** one of the journals, the corresponding bit is set in the ** Pager.pInJournal bitvec and the PagerSavepoint.pInSavepoint bitvecs ** of any open savepoints as appropriate. */ @@ -56539,7 +65730,7 @@ static int pager_write(PgHdr *pPg){ Pager *pPager = pPg->pPager; int rc = SQLITE_OK; - /* This routine is not called unless a write-transaction has already + /* This routine is not called unless a write-transaction has already ** been started. The journal file may or may not be open at this point. ** It is never called in the ERROR state. */ @@ -56556,7 +65747,7 @@ static int pager_write(PgHdr *pPg){ ** obtained the necessary locks to begin the write-transaction, but the ** rollback journal might not yet be open. Open it now if this is the case. ** - ** This is done before calling sqlite3PcacheMakeDirty() on the page. + ** This is done before calling sqlite3PcacheMakeDirty() on the page. ** Otherwise, if it were done after calling sqlite3PcacheMakeDirty(), then ** an error might occur and the pager would end up in WRITER_LOCKED state ** with pages marked as dirty in the cache. @@ -56601,7 +65792,7 @@ static int pager_write(PgHdr *pPg){ ** PGHDR_WRITEABLE bit that indicates that the page can be safely modified. */ pPg->flags |= PGHDR_WRITEABLE; - + /* If the statement journal is open and the page is not in it, ** then write the page into the statement journal. */ @@ -56667,7 +65858,7 @@ static SQLITE_NOINLINE int pagerWriteLargeSector(PgHdr *pPg){ Pgno pg = pg1+ii; PgHdr *pPage; if( pg==pPg->pgno || !sqlite3BitvecTest(pPager->pInJournal, pg) ){ - if( pg!=PAGER_MJ_PGNO(pPager) ){ + if( pg!=PAGER_SJ_PGNO(pPager) ){ rc = sqlite3PagerGet(pPager, pg, &pPage, 0); if( rc==SQLITE_OK ){ rc = pager_write(pPage); @@ -56685,7 +65876,7 @@ static SQLITE_NOINLINE int pagerWriteLargeSector(PgHdr *pPg){ } } - /* If the PGHDR_NEED_SYNC flag is set for any of the nPage pages + /* If the PGHDR_NEED_SYNC flag is set for any of the nPage pages ** starting at pg1, then it needs to be set for all of them. Because ** writing to any of these nPage pages may damage the others, the ** journal file must contain sync()ed copies of all of them @@ -56708,9 +65899,9 @@ static SQLITE_NOINLINE int pagerWriteLargeSector(PgHdr *pPg){ } /* -** Mark a data page as writeable. This routine must be called before -** making changes to a page. The caller must check the return value -** of this function and be careful not to change any page data unless +** Mark a data page as writeable. This routine must be called before +** making changes to a page. The caller must check the return value +** of this function and be careful not to change any page data unless ** this routine returns SQLITE_OK. ** ** The difference between this function and pager_write() is that this @@ -56761,13 +65952,13 @@ SQLITE_PRIVATE int sqlite3PagerIswriteable(DbPage *pPg){ ** on the given page is unused. The pager marks the page as clean so ** that it does not get written to disk. ** -** Tests show that this optimization can quadruple the speed of large +** Tests show that this optimization can quadruple the speed of large ** DELETE operations. ** ** This optimization cannot be used with a temp-file, as the page may ** have been dirty at the start of the transaction. In that case, if -** memory pressure forces page pPg out of the cache, the data does need -** to be written out to disk so that it may be read back in if the +** memory pressure forces page pPg out of the cache, the data does need +** to be written out to disk so that it may be read back in if the ** current transaction is rolled back. */ SQLITE_PRIVATE void sqlite3PagerDontWrite(PgHdr *pPg){ @@ -56783,17 +65974,17 @@ SQLITE_PRIVATE void sqlite3PagerDontWrite(PgHdr *pPg){ } /* -** This routine is called to increment the value of the database file -** change-counter, stored as a 4-byte big-endian integer starting at +** This routine is called to increment the value of the database file +** change-counter, stored as a 4-byte big-endian integer starting at ** byte offset 24 of the pager file. The secondary change counter at ** 92 is also updated, as is the SQLite version number at offset 96. ** ** But this only happens if the pPager->changeCountDone flag is false. ** To avoid excess churning of page 1, the update only happens once. -** See also the pager_write_changecounter() routine that does an +** See also the pager_write_changecounter() routine that does an ** unconditional update of the change counters. ** -** If the isDirectMode flag is zero, then this is done by calling +** If the isDirectMode flag is zero, then this is done by calling ** sqlite3PagerWrite() on page 1, then modifying the contents of the ** page data. In this case the file will be updated when the current ** transaction is committed. @@ -56801,7 +65992,7 @@ SQLITE_PRIVATE void sqlite3PagerDontWrite(PgHdr *pPg){ ** The isDirectMode flag may only be non-zero if the library was compiled ** with the SQLITE_ENABLE_ATOMIC_WRITE macro defined. In this case, ** if isDirect is non-zero, then the database file is updated directly -** by writing an updated version of page 1 using a call to the +** by writing an updated version of page 1 using a call to the ** sqlite3OsWrite() function. */ static int pager_incr_changecounter(Pager *pPager, int isDirectMode){ @@ -56830,7 +66021,7 @@ static int pager_incr_changecounter(Pager *pPager, int isDirectMode){ # define DIRECT_MODE isDirectMode #endif - if( !pPager->changeCountDone && ALWAYS(pPager->dbSize>0) ){ + if( !pPager->changeCountDone && pPager->dbSize>0 ){ PgHdr *pPgHdr; /* Reference to page 1 */ assert( !pPager->tempFile && isOpen(pPager->fd) ); @@ -56840,7 +66031,7 @@ static int pager_incr_changecounter(Pager *pPager, int isDirectMode){ assert( pPgHdr==0 || rc==SQLITE_OK ); /* If page one was fetched successfully, and this function is not - ** operating in direct-mode, make page 1 writable. When not in + ** operating in direct-mode, make page 1 writable. When not in ** direct mode, page 1 is always held in cache and hence the PagerGet() ** above is always successful - hence the ALWAYS on rc==SQLITE_OK. */ @@ -56856,7 +66047,7 @@ static int pager_incr_changecounter(Pager *pPager, int isDirectMode){ if( DIRECT_MODE ){ const void *zBuf; assert( pPager->dbFileSize>0 ); - CODEC2(pPager, pPgHdr->pData, 1, 6, rc=SQLITE_NOMEM_BKPT, zBuf); + zBuf = pPgHdr->pData; if( rc==SQLITE_OK ){ rc = sqlite3OsWrite(pPager->fd, zBuf, pPager->pageSize, 0); pPager->aStat[PAGER_STAT_WRITE]++; @@ -56887,9 +66078,9 @@ static int pager_incr_changecounter(Pager *pPager, int isDirectMode){ ** If successful, or if called on a pager for which it is a no-op, this ** function returns SQLITE_OK. Otherwise, an IO error code is returned. */ -SQLITE_PRIVATE int sqlite3PagerSync(Pager *pPager, const char *zMaster){ +SQLITE_PRIVATE int sqlite3PagerSync(Pager *pPager, const char *zSuper){ int rc = SQLITE_OK; - void *pArg = (void*)zMaster; + void *pArg = (void*)zSuper; rc = sqlite3OsFileControl(pPager->fd, SQLITE_FCNTL_SYNC, pArg); if( rc==SQLITE_NOTFOUND ) rc = SQLITE_OK; if( rc==SQLITE_OK && !pPager->noSync ){ @@ -56901,22 +66092,22 @@ SQLITE_PRIVATE int sqlite3PagerSync(Pager *pPager, const char *zMaster){ /* ** This function may only be called while a write-transaction is active in -** rollback. If the connection is in WAL mode, this call is a no-op. -** Otherwise, if the connection does not already have an EXCLUSIVE lock on +** rollback. If the connection is in WAL mode, this call is a no-op. +** Otherwise, if the connection does not already have an EXCLUSIVE lock on ** the database file, an attempt is made to obtain one. ** ** If the EXCLUSIVE lock is already held or the attempt to obtain it is ** successful, or the connection is in WAL mode, SQLITE_OK is returned. -** Otherwise, either SQLITE_BUSY or an SQLITE_IOERR_XXX error code is +** Otherwise, either SQLITE_BUSY or an SQLITE_IOERR_XXX error code is ** returned. */ SQLITE_PRIVATE int sqlite3PagerExclusiveLock(Pager *pPager){ int rc = pPager->errCode; assert( assert_pager_state(pPager) ); if( rc==SQLITE_OK ){ - assert( pPager->eState==PAGER_WRITER_CACHEMOD - || pPager->eState==PAGER_WRITER_DBMOD - || pPager->eState==PAGER_WRITER_LOCKED + assert( pPager->eState==PAGER_WRITER_CACHEMOD + || pPager->eState==PAGER_WRITER_DBMOD + || pPager->eState==PAGER_WRITER_LOCKED ); assert( assert_pager_state(pPager) ); if( 0==pagerUseWal(pPager) ){ @@ -56927,24 +66118,24 @@ SQLITE_PRIVATE int sqlite3PagerExclusiveLock(Pager *pPager){ } /* -** Sync the database file for the pager pPager. zMaster points to the name -** of a master journal file that should be written into the individual -** journal file. zMaster may be NULL, which is interpreted as no master -** journal (a single database transaction). +** Sync the database file for the pager pPager. zSuper points to the name +** of a super-journal file that should be written into the individual +** journal file. zSuper may be NULL, which is interpreted as no +** super-journal (a single database transaction). ** ** This routine ensures that: ** ** * The database file change-counter is updated, ** * the journal is synced (unless the atomic-write optimization is used), -** * all dirty pages are written to the database file, +** * all dirty pages are written to the database file, ** * the database file is truncated (if required), and -** * the database file synced. +** * the database file synced. ** -** The only thing that remains to commit the transaction is to finalize -** (delete, truncate or zero the first part of) the journal file (or -** delete the master journal file if specified). +** The only thing that remains to commit the transaction is to finalize +** (delete, truncate or zero the first part of) the journal file (or +** delete the super-journal file if specified). ** -** Note that if zMaster==NULL, this does not overwrite a previous value +** Note that if zSuper==NULL, this does not overwrite a previous value ** passed to an sqlite3PagerCommitPhaseOne() call. ** ** If the final parameter - noSync - is true, then the database file itself @@ -56954,7 +66145,7 @@ SQLITE_PRIVATE int sqlite3PagerExclusiveLock(Pager *pPager){ */ SQLITE_PRIVATE int sqlite3PagerCommitPhaseOne( Pager *pPager, /* Pager object */ - const char *zMaster, /* If not NULL, the master journal name */ + const char *zSuper, /* If not NULL, the super-journal name */ int noSync /* True to omit the xSync on the db file */ ){ int rc = SQLITE_OK; /* Return code */ @@ -56972,8 +66163,8 @@ SQLITE_PRIVATE int sqlite3PagerCommitPhaseOne( /* Provide the ability to easily simulate an I/O error during testing */ if( sqlite3FaultSim(400) ) return SQLITE_IOERR; - PAGERTRACE(("DATABASE SYNC: File=%s zMaster=%s nSize=%d\n", - pPager->zFilename, zMaster, pPager->dbSize)); + PAGERTRACE(("DATABASE SYNC: File=%s zSuper=%s nSize=%d\n", + pPager->zFilename, zSuper, pPager->dbSize)); /* If no database changes have been made, return early. */ if( pPager->eStatefd; - int bBatch = zMaster==0 /* An SQLITE_IOCAP_BATCH_ATOMIC commit */ + int bBatch = zSuper==0 /* An SQLITE_IOCAP_BATCH_ATOMIC commit */ && (sqlite3OsDeviceCharacteristics(fd) & SQLITE_IOCAP_BATCH_ATOMIC) && !pPager->noSync && sqlite3JournalIsInMemory(pPager->jfd); @@ -57023,11 +66214,11 @@ SQLITE_PRIVATE int sqlite3PagerCommitPhaseOne( #ifdef SQLITE_ENABLE_ATOMIC_WRITE /* The following block updates the change-counter. Exactly how it ** does this depends on whether or not the atomic-update optimization - ** was enabled at compile time, and if this transaction meets the - ** runtime criteria to use the operation: + ** was enabled at compile time, and if this transaction meets the + ** runtime criteria to use the operation: ** ** * The file-system supports the atomic-write property for - ** blocks of size page-size, and + ** blocks of size page-size, and ** * This commit is not part of a multi-file transaction, and ** * Exactly one page has been modified and store in the journal file. ** @@ -57037,7 +66228,7 @@ SQLITE_PRIVATE int sqlite3PagerCommitPhaseOne( ** is not applicable to this transaction, call sqlite3JournalCreate() ** to make sure the journal file has actually been created, then call ** pager_incr_changecounter() to update the change-counter in indirect - ** mode. + ** mode. ** ** Otherwise, if the optimization is both enabled and applicable, ** then call pager_incr_changecounter() to update the change-counter @@ -57046,19 +66237,19 @@ SQLITE_PRIVATE int sqlite3PagerCommitPhaseOne( */ if( bBatch==0 ){ PgHdr *pPg; - assert( isOpen(pPager->jfd) - || pPager->journalMode==PAGER_JOURNALMODE_OFF - || pPager->journalMode==PAGER_JOURNALMODE_WAL + assert( isOpen(pPager->jfd) + || pPager->journalMode==PAGER_JOURNALMODE_OFF + || pPager->journalMode==PAGER_JOURNALMODE_WAL ); - if( !zMaster && isOpen(pPager->jfd) - && pPager->journalOff==jrnlBufferSize(pPager) + if( !zSuper && isOpen(pPager->jfd) + && pPager->journalOff==jrnlBufferSize(pPager) && pPager->dbSize>=pPager->dbOrigSize && (!(pPg = sqlite3PcacheDirtyList(pPager->pPCache)) || 0==pPg->pDirty) ){ - /* Update the db file change counter via the direct-write method. The - ** following call will modify the in-memory representation of page 1 - ** to include the updated change counter and then write page 1 - ** directly to the database file. Because of the atomic-write + /* Update the db file change counter via the direct-write method. The + ** following call will modify the in-memory representation of page 1 + ** to include the updated change counter and then write page 1 + ** directly to the database file. Because of the atomic-write ** property of the host file-system, this is safe. */ rc = pager_incr_changecounter(pPager, 1); @@ -57071,7 +66262,7 @@ SQLITE_PRIVATE int sqlite3PagerCommitPhaseOne( } #else /* SQLITE_ENABLE_ATOMIC_WRITE */ #ifdef SQLITE_ENABLE_BATCH_ATOMIC_WRITE - if( zMaster ){ + if( zSuper ){ rc = sqlite3JournalCreate(pPager->jfd); if( rc!=SQLITE_OK ) goto commit_phase_one_exit; assert( bBatch==0 ); @@ -57080,24 +66271,24 @@ SQLITE_PRIVATE int sqlite3PagerCommitPhaseOne( rc = pager_incr_changecounter(pPager, 0); #endif /* !SQLITE_ENABLE_ATOMIC_WRITE */ if( rc!=SQLITE_OK ) goto commit_phase_one_exit; - - /* Write the master journal name into the journal file. If a master - ** journal file name has already been written to the journal file, - ** or if zMaster is NULL (no master journal), then this call is a no-op. + + /* Write the super-journal name into the journal file. If a + ** super-journal file name has already been written to the journal file, + ** or if zSuper is NULL (no super-journal), then this call is a no-op. */ - rc = writeMasterJournal(pPager, zMaster); + rc = writeSuperJournal(pPager, zSuper); if( rc!=SQLITE_OK ) goto commit_phase_one_exit; - + /* Sync the journal file and write all dirty pages to the database. - ** If the atomic-update optimization is being used, this sync will not + ** If the atomic-update optimization is being used, this sync will not ** create the journal file or perform any real IO. ** ** Because the change-counter page was just modified, unless the ** atomic-update optimization is used it is almost certain that the ** journal requires a sync here. However, in locking_mode=exclusive - ** on a system under memory pressure it is just possible that this is + ** on a system under memory pressure it is just possible that this is ** not the case. In this case it is likely enough that the redundant - ** xSync() call will be changed to a no-op by the OS anyhow. + ** xSync() call will be changed to a no-op by the OS anyhow. */ rc = syncJournal(pPager, 0); if( rc!=SQLITE_OK ) goto commit_phase_one_exit; @@ -57108,6 +66299,13 @@ SQLITE_PRIVATE int sqlite3PagerCommitPhaseOne( rc = sqlite3OsFileControl(fd, SQLITE_FCNTL_BEGIN_ATOMIC_WRITE, 0); if( rc==SQLITE_OK ){ rc = pager_write_pagelist(pPager, pList); + if( rc==SQLITE_OK && pPager->dbSize>pPager->dbFileSize ){ + char *pTmp = pPager->pTmpSpace; + int szPage = (int)pPager->pageSize; + memset(pTmp, 0, szPage); + rc = sqlite3OsWrite(pPager->fd, pTmp, szPage, + ((i64)pPager->dbSize*pPager->pageSize)-szPage); + } if( rc==SQLITE_OK ){ rc = sqlite3OsFileControl(fd, SQLITE_FCNTL_COMMIT_ATOMIC_WRITE, 0); } @@ -57138,22 +66336,22 @@ SQLITE_PRIVATE int sqlite3PagerCommitPhaseOne( } sqlite3PcacheCleanAll(pPager->pPCache); - /* If the file on disk is smaller than the database image, use + /* If the file on disk is smaller than the database image, use ** pager_truncate to grow the file here. This can happen if the database ** image was extended as part of the current transaction and then the ** last page in the db image moved to the free-list. In this case the ** last page is never written out to disk, leaving the database file ** undersized. Fix this now if it is the case. */ if( pPager->dbSize>pPager->dbFileSize ){ - Pgno nNew = pPager->dbSize - (pPager->dbSize==PAGER_MJ_PGNO(pPager)); + Pgno nNew = pPager->dbSize - (pPager->dbSize==PAGER_SJ_PGNO(pPager)); assert( pPager->eState==PAGER_WRITER_DBMOD ); rc = pager_truncate(pPager, nNew); if( rc!=SQLITE_OK ) goto commit_phase_one_exit; } - + /* Finally, sync the database file. */ if( !noSync ){ - rc = sqlite3PagerSync(pPager, zMaster); + rc = sqlite3PagerSync(pPager, zSuper); } IOTRACE(("DBSYNC %p\n", pPager)) } @@ -57170,12 +66368,12 @@ SQLITE_PRIVATE int sqlite3PagerCommitPhaseOne( /* ** When this function is called, the database file has been completely ** updated to reflect the changes made by the current transaction and -** synced to disk. The journal file still exists in the file-system +** synced to disk. The journal file still exists in the file-system ** though, and if a failure occurs at this point it will eventually ** be used as a hot-journal and the current transaction rolled back. ** -** This function finalizes the journal file, either by deleting, -** truncating or partially zeroing it, so that it cannot be used +** This function finalizes the journal file, either by deleting, +** truncating or partially zeroing it, so that it cannot be used ** for hot-journal rollback. Once this is done the transaction is ** irrevocably committed. ** @@ -57189,6 +66387,7 @@ SQLITE_PRIVATE int sqlite3PagerCommitPhaseTwo(Pager *pPager){ ** But if (due to a coding error elsewhere in the system) it does get ** called, just return the same error code without doing anything. */ if( NEVER(pPager->errCode) ) return pPager->errCode; + pPager->iDataVersion++; assert( pPager->eState==PAGER_WRITER_LOCKED || pPager->eState==PAGER_WRITER_FINISHED @@ -57200,15 +66399,15 @@ SQLITE_PRIVATE int sqlite3PagerCommitPhaseTwo(Pager *pPager){ ** this transaction, the pager is running in exclusive-mode and is ** using persistent journals, then this function is a no-op. ** - ** The start of the journal file currently contains a single journal + ** The start of the journal file currently contains a single journal ** header with the nRec field set to 0. If such a journal is used as ** a hot-journal during hot-journal rollback, 0 changes will be made - ** to the database file. So there is no need to zero the journal + ** to the database file. So there is no need to zero the journal ** header. Since the pager is in exclusive mode, there is no need ** to drop any locks either. */ - if( pPager->eState==PAGER_WRITER_LOCKED - && pPager->exclusiveMode + if( pPager->eState==PAGER_WRITER_LOCKED + && pPager->exclusiveMode && pPager->journalMode==PAGER_JOURNALMODE_PERSIST ){ assert( pPager->journalOff==JOURNAL_HDR_SZ(pPager) || !pPager->journalOff ); @@ -57217,13 +66416,12 @@ SQLITE_PRIVATE int sqlite3PagerCommitPhaseTwo(Pager *pPager){ } PAGERTRACE(("COMMIT %d\n", PAGERID(pPager))); - pPager->iDataVersion++; - rc = pager_end_transaction(pPager, pPager->setMaster, 1); + rc = pager_end_transaction(pPager, pPager->setSuper, 1); return pager_error(pPager, rc); } /* -** If a write transaction is open, then all changes made within the +** If a write transaction is open, then all changes made within the ** transaction are reverted and the current write-transaction is closed. ** The pager falls back to PAGER_READER state if successful, or PAGER_ERROR ** state if an error occurs. @@ -57233,14 +66431,14 @@ SQLITE_PRIVATE int sqlite3PagerCommitPhaseTwo(Pager *pPager){ ** ** Otherwise, in rollback mode, this function performs two functions: ** -** 1) It rolls back the journal file, restoring all database file and +** 1) It rolls back the journal file, restoring all database file and ** in-memory cache pages to the state they were in when the transaction ** was opened, and ** ** 2) It finalizes the journal file, so that it is not used for hot ** rollback at any point in the future. ** -** Finalization of the journal file (task 2) is only performed if the +** Finalization of the journal file (task 2) is only performed if the ** rollback is successful. ** ** In WAL mode, all cache-entries containing data modified within the @@ -57253,7 +66451,7 @@ SQLITE_PRIVATE int sqlite3PagerRollback(Pager *pPager){ PAGERTRACE(("ROLLBACK %d\n", PAGERID(pPager))); /* PagerRollback() is a no-op if called in READER or OPEN state. If - ** the pager is already in the ERROR state, the rollback is not + ** the pager is already in the ERROR state, the rollback is not ** attempted here. Instead, the error code is returned to the caller. */ assert( assert_pager_state(pPager) ); @@ -57263,13 +66461,13 @@ SQLITE_PRIVATE int sqlite3PagerRollback(Pager *pPager){ if( pagerUseWal(pPager) ){ int rc2; rc = sqlite3PagerSavepoint(pPager, SAVEPOINT_ROLLBACK, -1); - rc2 = pager_end_transaction(pPager, pPager->setMaster, 0); + rc2 = pager_end_transaction(pPager, pPager->setSuper, 0); if( rc==SQLITE_OK ) rc = rc2; }else if( !isOpen(pPager->jfd) || pPager->eState==PAGER_WRITER_LOCKED ){ int eState = pPager->eState; rc = pager_end_transaction(pPager, 0, 0); if( !MEMDB && eState>PAGER_WRITER_LOCKED ){ - /* This can happen using journal_mode=off. Move the pager to the error + /* This can happen using journal_mode=off. Move the pager to the error ** state to indicate that the contents of the cache may not be trusted. ** Any active readers will get SQLITE_ABORT. */ @@ -57284,7 +66482,7 @@ SQLITE_PRIVATE int sqlite3PagerRollback(Pager *pPager){ assert( pPager->eState==PAGER_READER || rc!=SQLITE_OK ); assert( rc==SQLITE_OK || rc==SQLITE_FULL || rc==SQLITE_CORRUPT - || rc==SQLITE_NOMEM || (rc&0xFF)==SQLITE_IOERR + || rc==SQLITE_NOMEM || (rc&0xFF)==SQLITE_IOERR || rc==SQLITE_CANTOPEN ); @@ -57316,8 +66514,8 @@ SQLITE_PRIVATE int sqlite3PagerRefcount(Pager *pPager){ ** used by the pager and its associated cache. */ SQLITE_PRIVATE int sqlite3PagerMemUsed(Pager *pPager){ - int perPageSize = pPager->pageSize + pPager->nExtra + sizeof(PgHdr) - + 5*sizeof(void*); + int perPageSize = pPager->pageSize + pPager->nExtra + + (int)(sizeof(PgHdr) + 5*sizeof(void*)); return perPageSize*sqlite3PcachePagecount(pPager->pPCache) + sqlite3MallocSize(pPager) + pPager->pageSize; @@ -57342,11 +66540,11 @@ SQLITE_PRIVATE int *sqlite3PagerStats(Pager *pPager){ a[3] = pPager->eState==PAGER_OPEN ? -1 : (int) pPager->dbSize; a[4] = pPager->eState; a[5] = pPager->errCode; - a[6] = pPager->aStat[PAGER_STAT_HIT]; - a[7] = pPager->aStat[PAGER_STAT_MISS]; + a[6] = (int)pPager->aStat[PAGER_STAT_HIT] & 0x7fffffff; + a[7] = (int)pPager->aStat[PAGER_STAT_MISS] & 0x7fffffff; a[8] = 0; /* Used to be pPager->nOvfl */ a[9] = pPager->nRead; - a[10] = pPager->aStat[PAGER_STAT_WRITE]; + a[10] = (int)pPager->aStat[PAGER_STAT_WRITE] & 0x7fffffff; return a; } #endif @@ -57358,11 +66556,11 @@ SQLITE_PRIVATE int *sqlite3PagerStats(Pager *pPager){ ** it was added later. ** ** Before returning, *pnVal is incremented by the -** current cache hit or miss count, according to the value of eStat. If the -** reset parameter is non-zero, the cache hit or miss count is zeroed before +** current cache hit or miss count, according to the value of eStat. If the +** reset parameter is non-zero, the cache hit or miss count is zeroed before ** returning. */ -SQLITE_PRIVATE void sqlite3PagerCacheStat(Pager *pPager, int eStat, int reset, int *pnVal){ +SQLITE_PRIVATE void sqlite3PagerCacheStat(Pager *pPager, int eStat, int reset, u64 *pnVal){ assert( eStat==SQLITE_DBSTATUS_CACHE_HIT || eStat==SQLITE_DBSTATUS_CACHE_MISS @@ -57386,7 +66584,7 @@ SQLITE_PRIVATE void sqlite3PagerCacheStat(Pager *pPager, int eStat, int reset, i ** Return true if this is an in-memory or temp-file backed pager. */ SQLITE_PRIVATE int sqlite3PagerIsMemdb(Pager *pPager){ - return pPager->tempFile; + return pPager->tempFile || pPager->memVfs; } /* @@ -57395,7 +66593,7 @@ SQLITE_PRIVATE int sqlite3PagerIsMemdb(Pager *pPager){ ** to make up the difference. If the number of savepoints is already ** equal to nSavepoint, then this function is a no-op. ** -** If a memory allocation fails, SQLITE_NOMEM is returned. If an error +** If a memory allocation fails, SQLITE_NOMEM is returned. If an error ** occurs while opening the sub-journal file, then an IO error code is ** returned. Otherwise, SQLITE_OK. */ @@ -57410,7 +66608,7 @@ static SQLITE_NOINLINE int pagerOpenSavepoint(Pager *pPager, int nSavepoint){ assert( nSavepoint>nCurrent && pPager->useJournal ); /* Grow the Pager.aSavepoint array using realloc(). Return SQLITE_NOMEM - ** if the allocation fails. Otherwise, zero the new portion in case a + ** if the allocation fails. Otherwise, zero the new portion in case a ** malloc failure occurs while populating it in the for(...) loop below. */ aNew = (PagerSavepoint *)sqlite3Realloc( @@ -57432,6 +66630,7 @@ static SQLITE_NOINLINE int pagerOpenSavepoint(Pager *pPager, int nSavepoint){ } aNew[ii].iSubRec = pPager->nSubRec; aNew[ii].pInSavepoint = sqlite3BitvecCreate(pPager->dbSize); + aNew[ii].bTruncateOnRelease = 1; if( !aNew[ii].pInSavepoint ){ return SQLITE_NOMEM_BKPT; } @@ -57458,7 +66657,7 @@ SQLITE_PRIVATE int sqlite3PagerOpenSavepoint(Pager *pPager, int nSavepoint){ /* ** This function is called to rollback or release (commit) a savepoint. -** The savepoint to release or rollback need not be the most recently +** The savepoint to release or rollback need not be the most recently ** created savepoint. ** ** Parameter op is always either SAVEPOINT_ROLLBACK or SAVEPOINT_RELEASE. @@ -57466,29 +66665,29 @@ SQLITE_PRIVATE int sqlite3PagerOpenSavepoint(Pager *pPager, int nSavepoint){ ** index iSavepoint. If it is SAVEPOINT_ROLLBACK, then rollback all changes ** that have occurred since the specified savepoint was created. ** -** The savepoint to rollback or release is identified by parameter +** The savepoint to rollback or release is identified by parameter ** iSavepoint. A value of 0 means to operate on the outermost savepoint ** (the first created). A value of (Pager.nSavepoint-1) means operate ** on the most recently created savepoint. If iSavepoint is greater than ** (Pager.nSavepoint-1), then this function is a no-op. ** ** If a negative value is passed to this function, then the current -** transaction is rolled back. This is different to calling +** transaction is rolled back. This is different to calling ** sqlite3PagerRollback() because this function does not terminate -** the transaction or unlock the database, it just restores the -** contents of the database to its original state. +** the transaction or unlock the database, it just restores the +** contents of the database to its original state. ** -** In any case, all savepoints with an index greater than iSavepoint +** In any case, all savepoints with an index greater than iSavepoint ** are destroyed. If this is a release operation (op==SAVEPOINT_RELEASE), ** then savepoint iSavepoint is also destroyed. ** ** This function may return SQLITE_NOMEM if a memory allocation fails, -** or an IO error code if an IO error occurs while rolling back a +** or an IO error code if an IO error occurs while rolling back a ** savepoint. If no errors occur, SQLITE_OK is returned. -*/ +*/ SQLITE_PRIVATE int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint){ int rc = pPager->errCode; - + #ifdef SQLITE_ENABLE_ZIPVFS if( op==SAVEPOINT_RELEASE ) rc = SQLITE_OK; #endif @@ -57501,7 +66700,7 @@ SQLITE_PRIVATE int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint){ int nNew; /* Number of remaining savepoints after this op. */ /* Figure out how many savepoints will still be active after this - ** operation. Store this value in nNew. Then free resources associated + ** operation. Store this value in nNew. Then free resources associated ** with any savepoints that are destroyed by this operation. */ nNew = iSavepoint + (( op==SAVEPOINT_RELEASE ) ? 0 : 1); @@ -57510,16 +66709,18 @@ SQLITE_PRIVATE int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint){ } pPager->nSavepoint = nNew; - /* If this is a release of the outermost savepoint, truncate - ** the sub-journal to zero bytes in size. */ + /* Truncate the sub-journal so that it only includes the parts + ** that are still in use. */ if( op==SAVEPOINT_RELEASE ){ - if( nNew==0 && isOpen(pPager->sjfd) ){ + PagerSavepoint *pRel = &pPager->aSavepoint[nNew]; + if( pRel->bTruncateOnRelease && isOpen(pPager->sjfd) ){ /* Only truncate if it is an in-memory sub-journal. */ if( sqlite3JournalIsInMemory(pPager->sjfd) ){ - rc = sqlite3OsTruncate(pPager->sjfd, 0); + i64 sz = (pPager->pageSize+4)*(i64)pRel->iSubRec; + rc = sqlite3OsTruncate(pPager->sjfd, sz); assert( rc==SQLITE_OK ); } - pPager->nSubRec = 0; + pPager->nSubRec = pRel->iSubRec; } } /* Else this is a rollback operation, playback the specified savepoint. @@ -57532,14 +66733,14 @@ SQLITE_PRIVATE int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint){ rc = pagerPlaybackSavepoint(pPager, pSavepoint); assert(rc!=SQLITE_DONE); } - + #ifdef SQLITE_ENABLE_ZIPVFS - /* If the cache has been modified but the savepoint cannot be rolled + /* If the cache has been modified but the savepoint cannot be rolled ** back journal_mode=off, put the pager in the error state. This way, ** if the VFS used by this pager includes ZipVFS, the entire transaction ** can be rolled back at the ZipVFS level. */ - else if( - pPager->journalMode==PAGER_JOURNALMODE_OFF + else if( + pPager->journalMode==PAGER_JOURNALMODE_OFF && pPager->eState>=PAGER_WRITER_CACHEMOD ){ pPager->errCode = SQLITE_ABORT; @@ -57561,9 +66762,17 @@ SQLITE_PRIVATE int sqlite3PagerSavepoint(Pager *pPager, int op, int iSavepoint){ ** behavior. But when the Btree needs to know the filename for matching to ** shared cache, it uses nullIfMemDb==0 so that in-memory databases can ** participate in shared-cache. +** +** The return value to this routine is always safe to use with +** sqlite3_uri_parameter() and sqlite3_filename_database() and friends. */ -SQLITE_PRIVATE const char *sqlite3PagerFilename(Pager *pPager, int nullIfMemDb){ - return (nullIfMemDb && pPager->memDb) ? "" : pPager->zFilename; +SQLITE_PRIVATE const char *sqlite3PagerFilename(const Pager *pPager, int nullIfMemDb){ + static const char zFake[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + if( nullIfMemDb && (pPager->memDb || sqlite3IsMemdb(pPager->pVfs)) ){ + return &zFake[4]; + }else{ + return pPager->zFilename; + } } /* @@ -57582,22 +66791,12 @@ SQLITE_PRIVATE sqlite3_file *sqlite3PagerFile(Pager *pPager){ return pPager->fd; } -#ifdef SQLITE_ENABLE_SETLK_TIMEOUT -/* -** Reset the lock timeout for pager. -*/ -SQLITE_PRIVATE void sqlite3PagerResetLockTimeout(Pager *pPager){ - int x = 0; - sqlite3OsFileControl(pPager->fd, SQLITE_FCNTL_LOCK_TIMEOUT, &x); -} -#endif - /* ** Return the file handle for the journal file (if it exists). ** This will be either the rollback journal or the WAL file. */ SQLITE_PRIVATE sqlite3_file *sqlite3PagerJrnlFile(Pager *pPager){ -#if SQLITE_OMIT_WAL +#ifdef SQLITE_OMIT_WAL return pPager->jfd; #else return pPager->pWal ? sqlite3WalFile(pPager->pWal) : pPager->jfd; @@ -57611,54 +66810,6 @@ SQLITE_PRIVATE const char *sqlite3PagerJournalname(Pager *pPager){ return pPager->zJournal; } -#ifdef SQLITE_HAS_CODEC -/* -** Set or retrieve the codec for this pager -*/ -SQLITE_PRIVATE void sqlite3PagerSetCodec( - Pager *pPager, - void *(*xCodec)(void*,void*,Pgno,int), - void (*xCodecSizeChng)(void*,int,int), - void (*xCodecFree)(void*), - void *pCodec -){ - if( pPager->xCodecFree ){ - pPager->xCodecFree(pPager->pCodec); - }else{ - pager_reset(pPager); - } - pPager->xCodec = pPager->memDb ? 0 : xCodec; - pPager->xCodecSizeChng = xCodecSizeChng; - pPager->xCodecFree = xCodecFree; - pPager->pCodec = pCodec; - setGetterMethod(pPager); - pagerReportSize(pPager); -} -SQLITE_PRIVATE void *sqlite3PagerGetCodec(Pager *pPager){ - return pPager->pCodec; -} - -/* -** This function is called by the wal module when writing page content -** into the log file. -** -** This function returns a pointer to a buffer containing the encrypted -** page content. If a malloc fails, this function may return NULL. -*/ -SQLITE_PRIVATE void *sqlite3PagerCodec(PgHdr *pPg){ - void *aData = 0; - CODEC2(pPg->pPager, pPg->pData, pPg->pgno, 6, return 0, aData); - return aData; -} - -/* -** Return the current pager state -*/ -SQLITE_PRIVATE int sqlite3PagerState(Pager *pPager){ - return pPager->eState; -} -#endif /* SQLITE_HAS_CODEC */ - #ifndef SQLITE_OMIT_AUTOVACUUM /* ** Move the page pPg to location pgno in the file. @@ -57678,8 +66829,8 @@ SQLITE_PRIVATE int sqlite3PagerState(Pager *pPager){ ** transaction is active). ** ** If the fourth argument, isCommit, is non-zero, then this page is being -** moved as part of a database reorganization just before the transaction -** is being committed. In this case, it is guaranteed that the database page +** moved as part of a database reorganization just before the transaction +** is being committed. In this case, it is guaranteed that the database page ** pPg refers to will not be written to again within this transaction. ** ** This function may return SQLITE_NOMEM or an IO error code if an error @@ -57707,7 +66858,7 @@ SQLITE_PRIVATE int sqlite3PagerMovepage(Pager *pPager, DbPage *pPg, Pgno pgno, i } /* If the page being moved is dirty and has not been saved by the latest - ** savepoint, then save the current contents of the page into the + ** savepoint, then save the current contents of the page into the ** sub-journal now. This is required to handle the following scenario: ** ** BEGIN; @@ -57730,7 +66881,7 @@ SQLITE_PRIVATE int sqlite3PagerMovepage(Pager *pPager, DbPage *pPg, Pgno pgno, i return rc; } - PAGERTRACE(("MOVE %d page %d (needSync=%d) moves to %d\n", + PAGERTRACE(("MOVE %d page %d (needSync=%d) moves to %d\n", PAGERID(pPager), pPg->pgno, (pPg->flags&PGHDR_NEED_SYNC)?1:0, pgno)); IOTRACE(("MOVE %p %d %d\n", pPager, pPg->pgno, pgno)) @@ -57738,7 +66889,7 @@ SQLITE_PRIVATE int sqlite3PagerMovepage(Pager *pPager, DbPage *pPg, Pgno pgno, i ** be written to, store pPg->pgno in local variable needSyncPgno. ** ** If the isCommit flag is set, there is no need to remember that - ** the journal needs to be sync()ed before database page pPg->pgno + ** the journal needs to be sync()ed before database page pPg->pgno ** can be written to. The caller has already promised not to write to it. */ if( (pPg->flags&PGHDR_NEED_SYNC) && !isCommit ){ @@ -57749,15 +66900,15 @@ SQLITE_PRIVATE int sqlite3PagerMovepage(Pager *pPager, DbPage *pPg, Pgno pgno, i } /* If the cache contains a page with page-number pgno, remove it - ** from its hash chain. Also, if the PGHDR_NEED_SYNC flag was set for - ** page pgno before the 'move' operation, it needs to be retained + ** from its hash chain. Also, if the PGHDR_NEED_SYNC flag was set for + ** page pgno before the 'move' operation, it needs to be retained ** for the page moved there. */ pPg->flags &= ~PGHDR_NEED_SYNC; pPgOld = sqlite3PagerLookup(pPager, pgno); assert( !pPgOld || pPgOld->nRef==1 || CORRUPT_DB ); if( pPgOld ){ - if( pPgOld->nRef>1 ){ + if( NEVER(pPgOld->nRef>1) ){ sqlite3PagerUnrefNotNull(pPgOld); return SQLITE_CORRUPT_BKPT; } @@ -57785,9 +66936,9 @@ SQLITE_PRIVATE int sqlite3PagerMovepage(Pager *pPager, DbPage *pPg, Pgno pgno, i } if( needSyncPgno ){ - /* If needSyncPgno is non-zero, then the journal file needs to be + /* If needSyncPgno is non-zero, then the journal file needs to be ** sync()ed before any data is written to database file page needSyncPgno. - ** Currently, no such page exists in the page-cache and the + ** Currently, no such page exists in the page-cache and the ** "is journaled" bitvec flag has been set. This needs to be remedied by ** loading the page into the pager-cache and setting the PGHDR_NEED_SYNC ** flag. @@ -57818,9 +66969,9 @@ SQLITE_PRIVATE int sqlite3PagerMovepage(Pager *pPager, DbPage *pPg, Pgno pgno, i #endif /* -** The page handle passed as the first argument refers to a dirty page -** with a page number other than iNew. This function changes the page's -** page number to iNew and sets the value of the PgHdr.flags field to +** The page handle passed as the first argument refers to a dirty page +** with a page number other than iNew. This function changes the page's +** page number to iNew and sets the value of the PgHdr.flags field to ** the value passed as the third parameter. */ SQLITE_PRIVATE void sqlite3PagerRekey(DbPage *pPg, Pgno iNew, u16 flags){ @@ -57838,7 +66989,7 @@ SQLITE_PRIVATE void *sqlite3PagerGetData(DbPage *pPg){ } /* -** Return a pointer to the Pager.nExtra bytes of "extra" space +** Return a pointer to the Pager.nExtra bytes of "extra" space ** allocated along with the specified page. */ SQLITE_PRIVATE void *sqlite3PagerGetExtra(DbPage *pPg){ @@ -57847,7 +66998,7 @@ SQLITE_PRIVATE void *sqlite3PagerGetExtra(DbPage *pPg){ /* ** Get/set the locking-mode for this pager. Parameter eMode must be one -** of PAGER_LOCKINGMODE_QUERY, PAGER_LOCKINGMODE_NORMAL or +** of PAGER_LOCKINGMODE_QUERY, PAGER_LOCKINGMODE_NORMAL or ** PAGER_LOCKINGMODE_EXCLUSIVE. If the parameter is not _QUERY, then ** the locking-mode is set to the value specified. ** @@ -57892,12 +67043,12 @@ SQLITE_PRIVATE int sqlite3PagerSetJournalMode(Pager *pPager, int eMode){ u8 eOld = pPager->journalMode; /* Prior journalmode */ /* The eMode parameter is always valid */ - assert( eMode==PAGER_JOURNALMODE_DELETE - || eMode==PAGER_JOURNALMODE_TRUNCATE - || eMode==PAGER_JOURNALMODE_PERSIST - || eMode==PAGER_JOURNALMODE_OFF - || eMode==PAGER_JOURNALMODE_WAL - || eMode==PAGER_JOURNALMODE_MEMORY ); + assert( eMode==PAGER_JOURNALMODE_DELETE /* 0 */ + || eMode==PAGER_JOURNALMODE_PERSIST /* 1 */ + || eMode==PAGER_JOURNALMODE_OFF /* 2 */ + || eMode==PAGER_JOURNALMODE_TRUNCATE /* 3 */ + || eMode==PAGER_JOURNALMODE_MEMORY /* 4 */ + || eMode==PAGER_JOURNALMODE_WAL /* 5 */ ); /* This routine is only called from the OP_JournalMode opcode, and ** the logic there will never allow a temporary file to be changed @@ -57921,7 +67072,7 @@ SQLITE_PRIVATE int sqlite3PagerSetJournalMode(Pager *pPager, int eMode){ assert( pPager->eState!=PAGER_ERROR ); pPager->journalMode = (u8)eMode; - /* When transistioning from TRUNCATE or PERSIST to any other journal + /* When transitioning from TRUNCATE or PERSIST to any other journal ** mode except WAL, unless the pager is in locking_mode=exclusive mode, ** delete the journal file. */ @@ -57934,7 +67085,6 @@ SQLITE_PRIVATE int sqlite3PagerSetJournalMode(Pager *pPager, int eMode){ assert( isOpen(pPager->fd) || pPager->exclusiveMode ); if( !pPager->exclusiveMode && (eOld & 5)==1 && (eMode & 1)==0 ){ - /* In this case we would like to delete the journal file. If it is ** not possible, then that is not a problem. Deleting the journal file ** here is an optimization only. @@ -57967,7 +67117,7 @@ SQLITE_PRIVATE int sqlite3PagerSetJournalMode(Pager *pPager, int eMode){ } assert( state==pPager->eState ); } - }else if( eMode==PAGER_JOURNALMODE_OFF ){ + }else if( eMode==PAGER_JOURNALMODE_OFF || eMode==PAGER_JOURNALMODE_MEMORY ){ sqlite3OsClose(pPager->jfd); } } @@ -58046,14 +67196,25 @@ SQLITE_PRIVATE int sqlite3PagerCheckpoint( int *pnCkpt /* OUT: Final number of checkpointed frames */ ){ int rc = SQLITE_OK; + if( pPager->pWal==0 && pPager->journalMode==PAGER_JOURNALMODE_WAL ){ + /* This only happens when a database file is zero bytes in size opened and + ** then "PRAGMA journal_mode=WAL" is run and then sqlite3_wal_checkpoint() + ** is invoked without any intervening transactions. We need to start + ** a transaction to initialize pWal. The PRAGMA table_list statement is + ** used for this since it starts transactions on every database file, + ** including all ATTACHed databases. This seems expensive for a single + ** sqlite3_wal_checkpoint() call, but it happens very rarely. + ** https://sqlite.org/forum/forumpost/fd0f19d229156939 + */ + sqlite3_exec(db, "PRAGMA table_list",0,0,0); + } if( pPager->pWal ){ rc = sqlite3WalCheckpoint(pPager->pWal, db, eMode, - (eMode==SQLITE_CHECKPOINT_PASSIVE ? 0 : pPager->xBusyHandler), + (eMode<=SQLITE_CHECKPOINT_PASSIVE ? 0 : pPager->xBusyHandler), pPager->pBusyHandlerArg, pPager->walSyncFlags, pPager->pageSize, (u8 *)pPager->pTmpSpace, pnLog, pnCkpt ); - sqlite3PagerResetLockTimeout(pPager); } return rc; } @@ -58078,20 +67239,22 @@ SQLITE_PRIVATE int sqlite3PagerWalSupported(Pager *pPager){ */ static int pagerExclusiveLock(Pager *pPager){ int rc; /* Return code */ + u8 eOrigLock; /* Original lock */ - assert( pPager->eLock==SHARED_LOCK || pPager->eLock==EXCLUSIVE_LOCK ); + assert( pPager->eLock>=SHARED_LOCK ); + eOrigLock = pPager->eLock; rc = pagerLockDb(pPager, EXCLUSIVE_LOCK); if( rc!=SQLITE_OK ){ - /* If the attempt to grab the exclusive lock failed, release the + /* If the attempt to grab the exclusive lock failed, release the ** pending lock that may have been obtained instead. */ - pagerUnlockDb(pPager, SHARED_LOCK); + pagerUnlockDb(pPager, eOrigLock); } return rc; } /* -** Call sqlite3WalOpen() to open the WAL handle. If the pager is in +** Call sqlite3WalOpen() to open the WAL handle. If the pager is in ** exclusive-locking mode when this function is called, take an EXCLUSIVE ** lock on the database file and use heap-memory to store the wal-index ** in. Otherwise, use the normal shared-memory. @@ -58102,8 +67265,8 @@ static int pagerOpenWal(Pager *pPager){ assert( pPager->pWal==0 && pPager->tempFile==0 ); assert( pPager->eLock==SHARED_LOCK || pPager->eLock==EXCLUSIVE_LOCK ); - /* If the pager is already in exclusive-mode, the WAL module will use - ** heap-memory for the wal-index instead of the VFS shared-memory + /* If the pager is already in exclusive-mode, the WAL module will use + ** heap-memory for the wal-index instead of the VFS shared-memory ** implementation. Take the exclusive lock now, before opening the WAL ** file, to make sure this is safe. */ @@ -58111,7 +67274,7 @@ static int pagerOpenWal(Pager *pPager){ rc = pagerExclusiveLock(pPager); } - /* Open the connection to the log file. If this operation fails, + /* Open the connection to the log file. If this operation fails, ** (e.g. due to malloc() failure), return an error code. */ if( rc==SQLITE_OK ){ @@ -58119,6 +67282,11 @@ static int pagerOpenWal(Pager *pPager){ pPager->fd, pPager->zWal, pPager->exclusiveMode, pPager->journalSizeLimit, &pPager->pWal ); +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + if( rc==SQLITE_OK ){ + sqlite3WalDb(pPager->pWal, pPager->dbWal); + } +#endif } pagerFixMaplimit(pPager); @@ -58133,7 +67301,7 @@ static int pagerOpenWal(Pager *pPager){ ** If the pager passed as the first argument is open on a real database ** file (not a temp file or an in-memory database), and the WAL file ** is not already open, make an attempt to open it now. If successful, -** return SQLITE_OK. If an error occurs or the VFS used by the pager does +** return SQLITE_OK. If an error occurs or the VFS used by the pager does ** not support the xShmXXX() methods, return an error code. *pbOpen is ** not modified in either case. ** @@ -58175,7 +67343,7 @@ SQLITE_PRIVATE int sqlite3PagerOpenWal( ** This function is called to close the connection to the log file prior ** to switching from WAL to rollback mode. ** -** Before closing the log file, this function attempts to take an +** Before closing the log file, this function attempts to take an ** EXCLUSIVE lock on the database file. If this cannot be obtained, an ** error (SQLITE_BUSY) is returned and the log connection is not closed. ** If successful, the EXCLUSIVE lock is not released before returning. @@ -58201,7 +67369,7 @@ SQLITE_PRIVATE int sqlite3PagerCloseWal(Pager *pPager, sqlite3 *db){ rc = pagerOpenWal(pPager); } } - + /* Checkpoint and close the log. Because an EXCLUSIVE lock is held on ** the database file, the log and log-summary files will be deleted. */ @@ -58218,6 +67386,33 @@ SQLITE_PRIVATE int sqlite3PagerCloseWal(Pager *pPager, sqlite3 *db){ return rc; } +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT +/* +** If pager pPager is a wal-mode database not in exclusive locking mode, +** invoke the sqlite3WalWriteLock() function on the associated Wal object +** with the same db and bLock parameters as were passed to this function. +** Return an SQLite error code if an error occurs, or SQLITE_OK otherwise. +*/ +SQLITE_PRIVATE int sqlite3PagerWalWriteLock(Pager *pPager, int bLock){ + int rc = SQLITE_OK; + if( pagerUseWal(pPager) && pPager->exclusiveMode==0 ){ + rc = sqlite3WalWriteLock(pPager->pWal, bLock); + } + return rc; +} + +/* +** Set the database handle used by the wal layer to determine if +** blocking locks are required. +*/ +SQLITE_PRIVATE void sqlite3PagerWalDb(Pager *pPager, sqlite3 *db){ + pPager->dbWal = db; + if( pagerUseWal(pPager) ){ + sqlite3WalDb(pPager->pWal, db); + } +} +#endif + #ifdef SQLITE_ENABLE_SNAPSHOT /* ** If this is a WAL database, obtain a snapshot handle for the snapshot @@ -58233,10 +67428,13 @@ SQLITE_PRIVATE int sqlite3PagerSnapshotGet(Pager *pPager, sqlite3_snapshot **ppS /* ** If this is a WAL database, store a pointer to pSnapshot. Next time a -** read transaction is opened, attempt to read from the snapshot it +** read transaction is opened, attempt to read from the snapshot it ** identifies. If this is not a WAL database, return an error. */ -SQLITE_PRIVATE int sqlite3PagerSnapshotOpen(Pager *pPager, sqlite3_snapshot *pSnapshot){ +SQLITE_PRIVATE int sqlite3PagerSnapshotOpen( + Pager *pPager, + sqlite3_snapshot *pSnapshot +){ int rc = SQLITE_OK; if( pPager->pWal ){ sqlite3WalSnapshotOpen(pPager->pWal, pSnapshot); @@ -58247,7 +67445,7 @@ SQLITE_PRIVATE int sqlite3PagerSnapshotOpen(Pager *pPager, sqlite3_snapshot *pSn } /* -** If this is a WAL database, call sqlite3WalSnapshotRecover(). If this +** If this is a WAL database, call sqlite3WalSnapshotRecover(). If this ** is not a WAL database, return an error. */ SQLITE_PRIVATE int sqlite3PagerSnapshotRecover(Pager *pPager){ @@ -58264,7 +67462,7 @@ SQLITE_PRIVATE int sqlite3PagerSnapshotRecover(Pager *pPager){ ** The caller currently has a read transaction open on the database. ** If this is not a WAL database, SQLITE_ERROR is returned. Otherwise, ** this function takes a SHARED lock on the CHECKPOINTER slot and then -** checks if the snapshot passed as the second argument is still +** checks if the snapshot passed as the second argument is still ** available. If so, SQLITE_OK is returned. ** ** If the snapshot is not available, SQLITE_ERROR is returned. Or, if @@ -58308,6 +67506,12 @@ SQLITE_PRIVATE int sqlite3PagerWalFramesize(Pager *pPager){ } #endif +#if defined(SQLITE_USE_SEH) && !defined(SQLITE_OMIT_WAL) +SQLITE_PRIVATE int sqlite3PagerWalSystemErrno(Pager *pPager){ + return sqlite3WalSystemErrno(pPager->pWal); +} +#endif + #endif /* SQLITE_OMIT_DISKIO */ /************** End of pager.c ***********************************************/ @@ -58324,7 +67528,7 @@ SQLITE_PRIVATE int sqlite3PagerWalFramesize(Pager *pPager){ ** ************************************************************************* ** -** This file contains the implementation of a write-ahead log (WAL) used in +** This file contains the implementation of a write-ahead log (WAL) used in ** "journal_mode=WAL" mode. ** ** WRITE-AHEAD LOG (WAL) FILE FORMAT @@ -58333,7 +67537,7 @@ SQLITE_PRIVATE int sqlite3PagerWalFramesize(Pager *pPager){ ** Each frame records the revised content of a single page from the ** database file. All changes to the database are recorded by writing ** frames into the WAL. Transactions commit when a frame is written that -** contains a commit marker. A single WAL can and usually does record +** contains a commit marker. A single WAL can and usually does record ** multiple transactions. Periodically, the content of the WAL is ** transferred back into the database file in an operation called a ** "checkpoint". @@ -58358,12 +67562,12 @@ SQLITE_PRIVATE int sqlite3PagerWalFramesize(Pager *pPager){ ** 28: Checksum-2 (second part of checksum for first 24 bytes of header). ** ** Immediately following the wal-header are zero or more frames. Each -** frame consists of a 24-byte frame-header followed by a bytes -** of page data. The frame-header is six big-endian 32-bit unsigned +** frame consists of a 24-byte frame-header followed by bytes +** of page data. The frame-header is six big-endian 32-bit unsigned ** integer values, as follows: ** ** 0: Page number. -** 4: For commit records, the size of the database image in pages +** 4: For commit records, the size of the database image in pages ** after the commit. For all other records, zero. ** 8: Salt-1 (copied from the header) ** 12: Salt-2 (copied from the header) @@ -58389,7 +67593,7 @@ SQLITE_PRIVATE int sqlite3PagerWalFramesize(Pager *pPager){ ** the checksum. The checksum is computed by interpreting the input as ** an even number of unsigned 32-bit integers: x[0] through x[N]. The ** algorithm used for the checksum is as follows: -** +** ** for i from 0 to n-1 step 2: ** s0 += x[i] + s1; ** s1 += x[i+1] + s0; @@ -58397,7 +67601,7 @@ SQLITE_PRIVATE int sqlite3PagerWalFramesize(Pager *pPager){ ** ** Note that s0 and s1 are both weighted checksums using fibonacci weights ** in reverse order (the largest fibonacci weight occurs on the first element -** of the sequence being summed.) The s1 value spans all 32-bit +** of the sequence being summed.) The s1 value spans all 32-bit ** terms of the sequence whereas s0 omits the final term. ** ** On a checkpoint, the WAL is first VFS.xSync-ed, then valid content of the @@ -58430,19 +67634,19 @@ SQLITE_PRIVATE int sqlite3PagerWalFramesize(Pager *pPager){ ** multiple concurrent readers to view different versions of the database ** content simultaneously. ** -** The reader algorithm in the previous paragraphs works correctly, but +** The reader algorithm in the previous paragraphs works correctly, but ** because frames for page P can appear anywhere within the WAL, the ** reader has to scan the entire WAL looking for page P frames. If the ** WAL is large (multiple megabytes is typical) that scan can be slow, ** and read performance suffers. To overcome this problem, a separate ** data structure called the wal-index is maintained to expedite the ** search for frames of a particular page. -** +** ** WAL-INDEX FORMAT ** ** Conceptually, the wal-index is shared memory, though VFS implementations ** might choose to implement the wal-index using a mmapped file. Because -** the wal-index is shared memory, SQLite does not support journal_mode=WAL +** the wal-index is shared memory, SQLite does not support journal_mode=WAL ** on a network filesystem. All users of the database must be able to ** share memory. ** @@ -58460,28 +67664,31 @@ SQLITE_PRIVATE int sqlite3PagerWalFramesize(Pager *pPager){ ** byte order of the host computer. ** ** The purpose of the wal-index is to answer this question quickly: Given -** a page number P and a maximum frame index M, return the index of the +** a page number P and a maximum frame index M, return the index of the ** last frame in the wal before frame M for page P in the WAL, or return ** NULL if there are no frames for page P in the WAL prior to M. ** ** The wal-index consists of a header region, followed by an one or -** more index blocks. +** more index blocks. ** ** The wal-index header contains the total number of frames within the WAL ** in the mxFrame field. ** -** Each index block except for the first contains information on +** Each index block except for the first contains information on ** HASHTABLE_NPAGE frames. The first index block contains information on -** HASHTABLE_NPAGE_ONE frames. The values of HASHTABLE_NPAGE_ONE and +** HASHTABLE_NPAGE_ONE frames. The values of HASHTABLE_NPAGE_ONE and ** HASHTABLE_NPAGE are selected so that together the wal-index header and ** first index block are the same size as all other index blocks in the -** wal-index. +** wal-index. The values are: +** +** HASHTABLE_NPAGE 4096 +** HASHTABLE_NPAGE_ONE 4062 ** ** Each index block contains two sections, a page-mapping that contains the -** database page number associated with each wal frame, and a hash-table +** database page number associated with each wal frame, and a hash-table ** that allows readers to query an index block for a specific page number. ** The page-mapping is an array of HASHTABLE_NPAGE (or HASHTABLE_NPAGE_ONE -** for the first index block) 32-bit page numbers. The first entry in the +** for the first index block) 32-bit page numbers. The first entry in the ** first index-block contains the database page number corresponding to the ** first frame in the WAL file. The first entry in the second index block ** in the WAL file corresponds to the (HASHTABLE_NPAGE_ONE+1)th frame in @@ -58502,8 +67709,8 @@ SQLITE_PRIVATE int sqlite3PagerWalFramesize(Pager *pPager){ ** ** The hash table consists of HASHTABLE_NSLOT 16-bit unsigned integers. ** HASHTABLE_NSLOT = 2*HASHTABLE_NPAGE, and there is one entry in the -** hash table for each page number in the mapping section, so the hash -** table is never more than half full. The expected number of collisions +** hash table for each page number in the mapping section, so the hash +** table is never more than half full. The expected number of collisions ** prior to finding a match is 1. Each entry of the hash table is an ** 1-based index of an entry in the mapping section of the same ** index block. Let K be the 1-based index of the largest entry in @@ -58522,12 +67729,12 @@ SQLITE_PRIVATE int sqlite3PagerWalFramesize(Pager *pPager){ ** reached) until an unused hash slot is found. Let the first unused slot ** be at index iUnused. (iUnused might be less than iKey if there was ** wrap-around.) Because the hash table is never more than half full, -** the search is guaranteed to eventually hit an unused entry. Let +** the search is guaranteed to eventually hit an unused entry. Let ** iMax be the value between iKey and iUnused, closest to iUnused, ** where aHash[iMax]==P. If there is no iMax entry (if there exists ** no hash slot such that aHash[i]==p) then page P is not in the ** current index block. Otherwise the iMax-th mapping entry of the -** current index block corresponds to the last entry that references +** current index block corresponds to the last entry that references ** page P. ** ** A hash search begins with the last index block and moves toward the @@ -58552,7 +67759,7 @@ SQLITE_PRIVATE int sqlite3PagerWalFramesize(Pager *pPager){ ** if no values greater than K0 had ever been inserted into the hash table ** in the first place - which is what reader one wants. Meanwhile, the ** second reader using K1 will see additional values that were inserted -** later, which is exactly what reader two wants. +** later, which is exactly what reader two wants. ** ** When a rollback occurs, the value of K is decreased. Hash table entries ** that correspond to frames greater than the new K value are removed @@ -58572,18 +67779,6 @@ SQLITE_PRIVATE int sqlite3WalTrace = 0; # define WALTRACE(X) #endif -/* -** WAL mode depends on atomic aligned 32-bit loads and stores in a few -** places. The following macros try to make this explicit. -*/ -#if GCC_VESRION>=5004000 -# define AtomicLoad(PTR) __atomic_load_n((PTR),__ATOMIC_RELAXED) -# define AtomicStore(PTR,VAL) __atomic_store_n((PTR),(VAL),__ATOMIC_RELAXED) -#else -# define AtomicLoad(PTR) (*(PTR)) -# define AtomicStore(PTR,VAL) (*(PTR) = (VAL)) -#endif - /* ** The maximum (and only) versions of the wal and wal-index formats ** that may be interpreted by this version of SQLite. @@ -58592,7 +67787,7 @@ SQLITE_PRIVATE int sqlite3WalTrace = 0; ** values in the wal-header are correct and (b) the version field is not ** WAL_MAX_VERSION, recovery fails and SQLite returns SQLITE_CANTOPEN. ** -** Similarly, if a client successfully reads a wal-index header (i.e. the +** Similarly, if a client successfully reads a wal-index header (i.e. the ** checksum test is successful) and finds that the version field is not ** WALINDEX_MAX_VERSION, then no read-transaction is opened and SQLite ** returns SQLITE_CANTOPEN. @@ -58607,7 +67802,7 @@ SQLITE_PRIVATE int sqlite3WalTrace = 0; ** ** Technically, the various VFSes are free to implement these locks however ** they see fit. However, compatibility is encouraged so that VFSes can -** interoperate. The standard implemention used on both unix and windows +** interoperate. The standard implementation used on both unix and windows ** is for the index number to indicate a byte offset into the ** WalCkptInfo.aLock[] array in the wal-index header. In other words, all ** locks are on the shm file. The WALINDEX_LOCK_OFFSET constant (which @@ -58639,7 +67834,7 @@ typedef struct WalCkptInfo WalCkptInfo; ** ** The szPage value can be any power of 2 between 512 and 32768, inclusive. ** Or it can be 1 to represent a 65536-byte page. The latter case was -** added in 3.7.1 when support for 64K pages was added. +** added in 3.7.1 when support for 64K pages was added. */ struct WalIndexHdr { u32 iVersion; /* Wal-index version */ @@ -58681,9 +67876,9 @@ struct WalIndexHdr { ** There is one entry in aReadMark[] for each reader lock. If a reader ** holds read-lock K, then the value in aReadMark[K] is no greater than ** the mxFrame for that reader. The value READMARK_NOT_USED (0xffffffff) -** for any aReadMark[] means that entry is unused. aReadMark[0] is +** for any aReadMark[] means that entry is unused. aReadMark[0] is ** a special case; its value is never used and it exists as a place-holder -** to avoid having to offset aReadMark[] indexs by one. Readers holding +** to avoid having to offset aReadMark[] indexes by one. Readers holding ** WAL_READ_LOCK(0) always ignore the entire WAL and read all content ** directly from the database. ** @@ -58701,7 +67896,7 @@ struct WalIndexHdr { ** previous sentence is when nBackfill equals mxFrame (meaning that everything ** in the WAL has been backfilled into the database) then new readers ** will choose aReadMark[0] which has value 0 and hence such reader will -** get all their all content directly from the database file and ignore +** get all their all content directly from the database file and ignore ** the WAL. ** ** Writers normally append new frames to the end of the WAL. However, @@ -58723,6 +67918,70 @@ struct WalCkptInfo { }; #define READMARK_NOT_USED 0xffffffff +/* +** This is a schematic view of the complete 136-byte header of the +** wal-index file (also known as the -shm file): +** +** +-----------------------------+ +** 0: | iVersion | \ +** +-----------------------------+ | +** 4: | (unused padding) | | +** +-----------------------------+ | +** 8: | iChange | | +** +-------+-------+-------------+ | +** 12: | bInit | bBig | szPage | | +** +-------+-------+-------------+ | +** 16: | mxFrame | | First copy of the +** +-----------------------------+ | WalIndexHdr object +** 20: | nPage | | +** +-----------------------------+ | +** 24: | aFrameCksum | | +** | | | +** +-----------------------------+ | +** 32: | aSalt | | +** | | | +** +-----------------------------+ | +** 40: | aCksum | | +** | | / +** +-----------------------------+ +** 48: | iVersion | \ +** +-----------------------------+ | +** 52: | (unused padding) | | +** +-----------------------------+ | +** 56: | iChange | | +** +-------+-------+-------------+ | +** 60: | bInit | bBig | szPage | | +** +-------+-------+-------------+ | Second copy of the +** 64: | mxFrame | | WalIndexHdr +** +-----------------------------+ | +** 68: | nPage | | +** +-----------------------------+ | +** 72: | aFrameCksum | | +** | | | +** +-----------------------------+ | +** 80: | aSalt | | +** | | | +** +-----------------------------+ | +** 88: | aCksum | | +** | | / +** +-----------------------------+ +** 96: | nBackfill | +** +-----------------------------+ +** 100: | 5 read marks | +** | | +** | | +** | | +** | | +** +-------+-------+------+------+ +** 120: | Write | Ckpt | Rcvr | Rd0 | \ +** +-------+-------+------+------+ ) 8 lock bytes +** | Read1 | Read2 | Rd3 | Rd4 | / +** +-------+-------+------+------+ +** 128: | nBackfillAttempted | +** +-----------------------------+ +** 132: | (unused padding) | +** +-----------------------------+ +*/ /* A block of WALINDEX_LOCK_RESERVED bytes beginning at ** WALINDEX_LOCK_OFFSET is reserved for locks. Since some systems @@ -58743,14 +68002,14 @@ struct WalCkptInfo { ** big-endian format in the first 4 bytes of a WAL file. ** ** If the LSB is set, then the checksums for each frame within the WAL -** file are calculated by treating all data as an array of 32-bit -** big-endian words. Otherwise, they are calculated by interpreting +** file are calculated by treating all data as an array of 32-bit +** big-endian words. Otherwise, they are calculated by interpreting ** all data as 32-bit little-endian words. */ #define WAL_MAGIC 0x377f0682 /* -** Return the offset of frame iFrame in the write-ahead log file, +** Return the offset of frame iFrame in the write-ahead log file, ** assuming a database page size of szPage bytes. The offset returned ** is to the start of the write-ahead log frame-header. */ @@ -58761,6 +68020,11 @@ struct WalCkptInfo { /* ** An open write-ahead log file is represented by an instance of the ** following object. +** +** writeLock: +** This is usually set to 1 whenever the WRITER lock is held. However, +** if it is set to 2, then the WRITER lock is held but must be released +** by walHandleException() if a SEH exception is thrown. */ struct Wal { sqlite3_vfs *pVfs; /* The VFS used to create pDbFd */ @@ -58787,11 +68051,23 @@ struct Wal { u32 iReCksum; /* On commit, recalculate checksums from here */ const char *zWalName; /* Name of WAL file */ u32 nCkpt; /* Checkpoint sequence counter in the wal-header */ +#ifdef SQLITE_USE_SEH + u32 lockMask; /* Mask of locks held */ + void *pFree; /* Pointer to sqlite3_free() if exception thrown */ + u32 *pWiValue; /* Value to write into apWiData[iWiPg] */ + int iWiPg; /* Write pWiValue into apWiData[iWiPg] */ + int iSysErrno; /* System error code following exception */ +#endif #ifdef SQLITE_DEBUG + int nSehTry; /* Number of nested SEH_TRY{} blocks */ u8 lockError; /* True if a locking error has occurred */ #endif #ifdef SQLITE_ENABLE_SNAPSHOT WalIndexHdr *pSnapshot; /* Start transaction here if not NULL */ + int bGetSnapshot; /* Transaction opened for sqlite3_get_snapshot() */ +#endif +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + sqlite3 *db; #endif }; @@ -58799,7 +68075,7 @@ struct Wal { ** Candidate values for Wal.exclusiveMode. */ #define WAL_NORMAL_MODE 0 -#define WAL_EXCLUSIVE_MODE 1 +#define WAL_EXCLUSIVE_MODE 1 #define WAL_HEAPMEMORY_MODE 2 /* @@ -58818,7 +68094,7 @@ typedef u16 ht_slot; /* ** This structure is used to implement an iterator that loops through ** all frames in the WAL in database page order. Where two or more frames -** correspond to the same database page, the iterator visits only the +** correspond to the same database page, the iterator visits only the ** frame most recently written to the WAL (in other words, the frame with ** the largest index). ** @@ -58831,7 +68107,7 @@ typedef u16 ht_slot; ** This functionality is used by the checkpoint code (see walCheckpoint()). */ struct WalIterator { - int iPrior; /* Last result returned from the iterator */ + u32 iPrior; /* Last result returned from the iterator */ int nSegment; /* Number of entries in aSegment[] */ struct WalSegment { int iNext; /* Next slot in aIndex[] not yet returned */ @@ -58839,9 +68115,13 @@ struct WalIterator { u32 *aPgno; /* Array of page numbers. */ int nEntry; /* Nr. of entries in aPgno[] and aIndex[] */ int iZero; /* Frame number associated with aPgno[0] */ - } aSegment[1]; /* One for every 32KB page in the wal-index */ + } aSegment[FLEXARRAY]; /* One for every 32KB page in the wal-index */ }; +/* Size (in bytes) of a WalIterator object suitable for N or fewer segments */ +#define SZ_WALITERATOR(N) \ + (offsetof(WalIterator,aSegment)+(N)*sizeof(struct WalSegment)) + /* ** Define the parameters of the hash tables in the wal-index file. There ** is a hash-table following every HASHTABLE_NPAGE page numbers in the @@ -58854,7 +68134,7 @@ struct WalIterator { #define HASHTABLE_HASH_1 383 /* Should be prime */ #define HASHTABLE_NSLOT (HASHTABLE_NPAGE*2) /* Must be a power of 2 */ -/* +/* ** The block of page numbers associated with the first hash-table in a ** wal-index is smaller than usual. This is so that there is a complete ** hash-table on each aligned 32KB page of the wal-index. @@ -58866,6 +68146,113 @@ struct WalIterator { sizeof(ht_slot)*HASHTABLE_NSLOT + HASHTABLE_NPAGE*sizeof(u32) \ ) +/* +** Structured Exception Handling (SEH) is a Windows-specific technique +** for catching exceptions raised while accessing memory-mapped files. +** +** The -DSQLITE_USE_SEH compile-time option means to use SEH to catch and +** deal with system-level errors that arise during WAL -shm file processing. +** Without this compile-time option, any system-level faults that appear +** while accessing the memory-mapped -shm file will cause a process-wide +** signal to be deliver, which will more than likely cause the entire +** process to exit. +*/ +#ifdef SQLITE_USE_SEH +#include + +/* Beginning of a block of code in which an exception might occur */ +# define SEH_TRY __try { \ + assert( walAssertLockmask(pWal) && pWal->nSehTry==0 ); \ + VVA_ONLY(pWal->nSehTry++); + +/* The end of a block of code in which an exception might occur */ +# define SEH_EXCEPT(X) \ + VVA_ONLY(pWal->nSehTry--); \ + assert( pWal->nSehTry==0 ); \ + } __except( sehExceptionFilter(pWal, GetExceptionCode(), GetExceptionInformation() ) ){ X } + +/* Simulate a memory-mapping fault in the -shm file for testing purposes */ +# define SEH_INJECT_FAULT sehInjectFault(pWal) + +/* +** The second argument is the return value of GetExceptionCode() for the +** current exception. Return EXCEPTION_EXECUTE_HANDLER if the exception code +** indicates that the exception may have been caused by accessing the *-shm +** file mapping. Or EXCEPTION_CONTINUE_SEARCH otherwise. +*/ +static int sehExceptionFilter(Wal *pWal, int eCode, EXCEPTION_POINTERS *p){ + VVA_ONLY(pWal->nSehTry--); + if( eCode==EXCEPTION_IN_PAGE_ERROR ){ + if( p && p->ExceptionRecord && p->ExceptionRecord->NumberParameters>=3 ){ + /* From MSDN: For this type of exception, the first element of the + ** ExceptionInformation[] array is a read-write flag - 0 if the exception + ** was thrown while reading, 1 if while writing. The second element is + ** the virtual address being accessed. The "third array element specifies + ** the underlying NTSTATUS code that resulted in the exception". */ + pWal->iSysErrno = (int)p->ExceptionRecord->ExceptionInformation[2]; + } + return EXCEPTION_EXECUTE_HANDLER; + } + return EXCEPTION_CONTINUE_SEARCH; +} + +/* +** If one is configured, invoke the xTestCallback callback with 650 as +** the argument. If it returns true, throw the same exception that is +** thrown by the system if the *-shm file mapping is accessed after it +** has been invalidated. +*/ +static void sehInjectFault(Wal *pWal){ + int res; + assert( pWal->nSehTry>0 ); + + res = sqlite3FaultSim(650); + if( res!=0 ){ + ULONG_PTR aArg[3]; + aArg[0] = 0; + aArg[1] = 0; + aArg[2] = (ULONG_PTR)res; + RaiseException(EXCEPTION_IN_PAGE_ERROR, 0, 3, (const ULONG_PTR*)aArg); + } +} + +/* +** There are two ways to use this macro. To set a pointer to be freed +** if an exception is thrown: +** +** SEH_FREE_ON_ERROR(0, pPtr); +** +** and to cancel the same: +** +** SEH_FREE_ON_ERROR(pPtr, 0); +** +** In the first case, there must not already be a pointer registered to +** be freed. In the second case, pPtr must be the registered pointer. +*/ +#define SEH_FREE_ON_ERROR(X,Y) \ + assert( (X==0 || Y==0) && pWal->pFree==X ); pWal->pFree = Y + +/* +** There are two ways to use this macro. To arrange for pWal->apWiData[iPg] +** to be set to pValue if an exception is thrown: +** +** SEH_SET_ON_ERROR(iPg, pValue); +** +** and to cancel the same: +** +** SEH_SET_ON_ERROR(0, 0); +*/ +#define SEH_SET_ON_ERROR(X,Y) pWal->iWiPg = X; pWal->pWiValue = Y + +#else +# define SEH_TRY VVA_ONLY(pWal->nSehTry++); +# define SEH_EXCEPT(X) VVA_ONLY(pWal->nSehTry--); assert( pWal->nSehTry==0 ); +# define SEH_INJECT_FAULT assert( pWal->nSehTry>0 ); +# define SEH_FREE_ON_ERROR(X,Y) +# define SEH_SET_ON_ERROR(X,Y) +#endif /* ifdef SQLITE_USE_SEH */ + + /* ** Obtain a pointer to the iPage'th page of the wal-index. The wal-index ** is broken into pages of WALINDEX_PGSZ bytes. Wal-index pages are @@ -58876,9 +68263,13 @@ struct WalIterator { ** so. It is safe to enlarge the wal-index if pWal->writeLock is true ** or pWal->exclusiveMode==WAL_HEAPMEMORY_MODE. ** -** If this call is successful, *ppPage is set to point to the wal-index -** page and SQLITE_OK is returned. If an error (an OOM or VFS error) occurs, -** then an SQLite error code is returned and *ppPage is set to 0. +** Three possible result scenarios: +** +** (1) rc==SQLITE_OK and *ppPage==Requested-Wal-Index-Page +** (2) rc>=SQLITE_ERROR and *ppPage==NULL +** (3) rc==SQLITE_OK and *ppPage==NULL // only if iPage==0 +** +** Scenario (3) can only occur when pWal->writeLock is false and iPage==0 */ static SQLITE_NOINLINE int walIndexPageRealloc( Wal *pWal, /* The WAL context */ @@ -58889,9 +68280,9 @@ static SQLITE_NOINLINE int walIndexPageRealloc( /* Enlarge the pWal->apWiData[] array if required */ if( pWal->nWiData<=iPage ){ - sqlite3_int64 nByte = sizeof(u32*)*(iPage+1); + sqlite3_int64 nByte = sizeof(u32*)*(1+(i64)iPage); volatile u32 **apNew; - apNew = (volatile u32 **)sqlite3_realloc64((void *)pWal->apWiData, nByte); + apNew = (volatile u32 **)sqlite3Realloc((void *)pWal->apWiData, nByte); if( !apNew ){ *ppPage = 0; return SQLITE_NOMEM_BKPT; @@ -58908,12 +68299,16 @@ static SQLITE_NOINLINE int walIndexPageRealloc( pWal->apWiData[iPage] = (u32 volatile *)sqlite3MallocZero(WALINDEX_PGSZ); if( !pWal->apWiData[iPage] ) rc = SQLITE_NOMEM_BKPT; }else{ - rc = sqlite3OsShmMap(pWal->pDbFd, iPage, WALINDEX_PGSZ, + rc = sqlite3OsShmMap(pWal->pDbFd, iPage, WALINDEX_PGSZ, pWal->writeLock, (void volatile **)&pWal->apWiData[iPage] ); - assert( pWal->apWiData[iPage]!=0 || rc!=SQLITE_OK || pWal->writeLock==0 ); + assert( pWal->apWiData[iPage]!=0 + || rc!=SQLITE_OK + || (pWal->writeLock==0 && iPage==0) ); testcase( pWal->apWiData[iPage]==0 && rc==SQLITE_OK ); - if( (rc&0xff)==SQLITE_READONLY ){ + if( rc==SQLITE_OK ){ + if( iPage>0 && sqlite3FaultSim(600) ) rc = SQLITE_NOMEM; + }else if( (rc&0xff)==SQLITE_READONLY ){ pWal->readOnly |= WAL_SHM_RDONLY; if( rc==SQLITE_READONLY ){ rc = SQLITE_OK; @@ -58930,6 +68325,7 @@ static int walIndexPage( int iPage, /* The page we seek */ volatile u32 **ppPage /* Write the page pointer here */ ){ + SEH_INJECT_FAULT; if( pWal->nWiData<=iPage || (*ppPage = pWal->apWiData[iPage])==0 ){ return walIndexPageRealloc(pWal, iPage, ppPage); } @@ -58941,6 +68337,7 @@ static int walIndexPage( */ static volatile WalCkptInfo *walCkptInfo(Wal *pWal){ assert( pWal->nWiData>0 && pWal->apWiData[0] ); + SEH_INJECT_FAULT; return (volatile WalCkptInfo*)&(pWal->apWiData[0][sizeof(WalIndexHdr)/2]); } @@ -58949,6 +68346,7 @@ static volatile WalCkptInfo *walCkptInfo(Wal *pWal){ */ static volatile WalIndexHdr *walIndexHdr(Wal *pWal){ assert( pWal->nWiData>0 && pWal->apWiData[0] ); + SEH_INJECT_FAULT; return (volatile WalIndexHdr*)pWal->apWiData[0]; } @@ -58965,7 +68363,7 @@ static volatile WalIndexHdr *walIndexHdr(Wal *pWal){ ) /* -** Generate or extend an 8 byte checksum based on the data in +** Generate or extend an 8 byte checksum based on the data in ** array aByte[] and the initial values of aIn[0] and aIn[1] (or ** initial values of 0 and 0 if aIn==NULL). ** @@ -58991,39 +68389,75 @@ static void walChecksumBytes( s1 = s2 = 0; } - assert( nByte>=8 ); - assert( (nByte&0x00000007)==0 ); - assert( nByte<=65536 ); + /* nByte is a multiple of 8 between 8 and 65536 */ + assert( nByte>=8 && (nByte&7)==0 && nByte<=65536 ); - if( nativeCksum ){ + if( !nativeCksum ){ + do { + s1 += BYTESWAP32(aData[0]) + s2; + s2 += BYTESWAP32(aData[1]) + s1; + aData += 2; + }while( aDataexclusiveMode!=WAL_HEAPMEMORY_MODE ){ sqlite3OsShmBarrier(pWal->pDbFd); } } +/* +** Add the SQLITE_NO_TSAN as part of the return-type of a function +** definition as a hint that the function contains constructs that +** might give false-positive TSAN warnings. +** +** See tag-20200519-1. +*/ +#if defined(__clang__) && !defined(SQLITE_NO_TSAN) +# define SQLITE_NO_TSAN __attribute__((no_sanitize_thread)) +#else +# define SQLITE_NO_TSAN +#endif + /* ** Write the header information in pWal->hdr into the wal-index. ** ** The checksum on pWal->hdr is updated before it is written. */ -static void walIndexWriteHdr(Wal *pWal){ +static SQLITE_NO_TSAN void walIndexWriteHdr(Wal *pWal){ volatile WalIndexHdr *aHdr = walIndexHdr(pWal); const int nCksum = offsetof(WalIndexHdr, aCksum); @@ -59031,6 +68465,7 @@ static void walIndexWriteHdr(Wal *pWal){ pWal->hdr.isInit = 1; pWal->hdr.iVersion = WALINDEX_MAX_VERSION; walChecksumBytes(1, (u8*)&pWal->hdr, nCksum, 0, pWal->hdr.aCksum); + /* Possible TSAN false-positive. See tag-20200519-1 */ memcpy((void*)&aHdr[1], (const void*)&pWal->hdr, sizeof(WalIndexHdr)); walShmBarrier(pWal); memcpy((void*)&aHdr[0], (const void*)&pWal->hdr, sizeof(WalIndexHdr)); @@ -59038,11 +68473,11 @@ static void walIndexWriteHdr(Wal *pWal){ /* ** This function encodes a single frame header and writes it to a buffer -** supplied by the caller. A frame-header is made up of a series of +** supplied by the caller. A frame-header is made up of a series of ** 4-byte big-endian integers, as follows: ** ** 0: Page number. -** 4: For commit records, the size of the database image in pages +** 4: For commit records, the size of the database image in pages ** after the commit. For all other records, zero. ** 8: Salt-1 (copied from the wal-header) ** 12: Salt-2 (copied from the wal-header) @@ -59093,29 +68528,35 @@ static int walDecodeFrame( assert( WAL_FRAME_HDRSIZE==24 ); /* A frame is only valid if the salt values in the frame-header - ** match the salt values in the wal-header. + ** match the salt values in the wal-header. */ if( memcmp(&pWal->hdr.aSalt, &aFrame[8], 8)!=0 ){ return 0; } - /* A frame is only valid if the page number is creater than zero. + /* A frame is only valid if the page number is greater than zero. */ pgno = sqlite3Get4byte(&aFrame[0]); if( pgno==0 ){ return 0; } + /* Need a valid page size + */ + if( !pWal->szPage ){ + return 0; + } + /* A frame is only valid if a checksum of the WAL header, - ** all prior frams, the first 16 bytes of this frame-header, - ** and the frame-data matches the checksum in the last 8 + ** all prior frames, the first 16 bytes of this frame-header, + ** and the frame-data matches the checksum in the last 8 ** bytes of this frame-header. */ nativeCksum = (pWal->hdr.bigEndCksum==SQLITE_BIGENDIAN); walChecksumBytes(nativeCksum, aFrame, 8, aCksum, aCksum); walChecksumBytes(nativeCksum, aData, pWal->szPage, aCksum, aCksum); - if( aCksum[0]!=sqlite3Get4byte(&aFrame[16]) - || aCksum[1]!=sqlite3Get4byte(&aFrame[20]) + if( aCksum[0]!=sqlite3Get4byte(&aFrame[16]) + || aCksum[1]!=sqlite3Get4byte(&aFrame[20]) ){ /* Checksum failed. */ return 0; @@ -59150,7 +68591,7 @@ static const char *walLockName(int lockIdx){ } } #endif /*defined(SQLITE_TEST) || defined(SQLITE_DEBUG) */ - + /* ** Set or release locks on the WAL. Locks are either shared or exclusive. @@ -59166,13 +68607,19 @@ static int walLockShared(Wal *pWal, int lockIdx){ SQLITE_SHM_LOCK | SQLITE_SHM_SHARED); WALTRACE(("WAL%p: acquire SHARED-%s %s\n", pWal, walLockName(lockIdx), rc ? "failed" : "ok")); - VVA_ONLY( pWal->lockError = (u8)(rc!=SQLITE_OK && rc!=SQLITE_BUSY); ) + VVA_ONLY( pWal->lockError = (u8)(rc!=SQLITE_OK && (rc&0xFF)!=SQLITE_BUSY); ) +#ifdef SQLITE_USE_SEH + if( rc==SQLITE_OK ) pWal->lockMask |= (1 << lockIdx); +#endif return rc; } static void walUnlockShared(Wal *pWal, int lockIdx){ if( pWal->exclusiveMode ) return; (void)sqlite3OsShmLock(pWal->pDbFd, lockIdx, 1, SQLITE_SHM_UNLOCK | SQLITE_SHM_SHARED); +#ifdef SQLITE_USE_SEH + pWal->lockMask &= ~(1 << lockIdx); +#endif WALTRACE(("WAL%p: release SHARED-%s\n", pWal, walLockName(lockIdx))); } static int walLockExclusive(Wal *pWal, int lockIdx, int n){ @@ -59182,20 +68629,28 @@ static int walLockExclusive(Wal *pWal, int lockIdx, int n){ SQLITE_SHM_LOCK | SQLITE_SHM_EXCLUSIVE); WALTRACE(("WAL%p: acquire EXCLUSIVE-%s cnt=%d %s\n", pWal, walLockName(lockIdx), n, rc ? "failed" : "ok")); - VVA_ONLY( pWal->lockError = (u8)(rc!=SQLITE_OK && rc!=SQLITE_BUSY); ) + VVA_ONLY( pWal->lockError = (u8)(rc!=SQLITE_OK && (rc&0xFF)!=SQLITE_BUSY); ) +#ifdef SQLITE_USE_SEH + if( rc==SQLITE_OK ){ + pWal->lockMask |= (((1<exclusiveMode ) return; (void)sqlite3OsShmLock(pWal->pDbFd, lockIdx, n, SQLITE_SHM_UNLOCK | SQLITE_SHM_EXCLUSIVE); +#ifdef SQLITE_USE_SEH + pWal->lockMask &= ~(((1<aHash to point to the start of the hash table -** in the wal-index file. Set pLoc->iZero to one less than the frame +** in the wal-index file. Set pLoc->iZero to one less than the frame ** number of the first frame indexed by this hash table. If a -** slot in the hash table is set to N, it refers to frame number +** slot in the hash table is set to N, it refers to frame number ** (pLoc->iZero+N) in the log. ** -** Finally, set pLoc->aPgno so that pLoc->aPgno[1] is the page number of the -** first frame indexed by the hash table, frame (pLoc->iZero+1). +** Finally, set pLoc->aPgno so that pLoc->aPgno[0] is the page number of the +** first frame indexed by the hash table, frame (pLoc->iZero). */ static int walHashGet( Wal *pWal, /* WAL handle */ @@ -59243,7 +68698,7 @@ static int walHashGet( rc = walIndexPage(pWal, iHash, &pLoc->aPgno); assert( rc==SQLITE_OK || iHash>0 ); - if( rc==SQLITE_OK ){ + if( pLoc->aPgno ){ pLoc->aHash = (volatile ht_slot *)&pLoc->aPgno[HASHTABLE_NPAGE]; if( iHash==0 ){ pLoc->aPgno = &pLoc->aPgno[WALINDEX_HDR_SIZE/sizeof(u32)]; @@ -59251,7 +68706,8 @@ static int walHashGet( }else{ pLoc->iZero = HASHTABLE_NPAGE_ONE + (iHash-1)*HASHTABLE_NPAGE; } - pLoc->aPgno = &pLoc->aPgno[-1]; + }else if( NEVER(rc==SQLITE_OK) ){ + rc = SQLITE_ERROR; } return rc; } @@ -59259,7 +68715,7 @@ static int walHashGet( /* ** Return the number of the wal-index page that contains the hash-table ** and page-number array that contain entries corresponding to WAL frame -** iFrame. The wal-index is broken up into 32KB pages. Wal-index pages +** iFrame. The wal-index is broken up into 32KB pages. Wal-index pages ** are numbered starting from 0. */ static int walFramePage(u32 iFrame){ @@ -59270,6 +68726,7 @@ static int walFramePage(u32 iFrame){ && (iHash>=2 || iFrame<=HASHTABLE_NPAGE_ONE+HASHTABLE_NPAGE) && (iHash<=2 || iFrame>(HASHTABLE_NPAGE_ONE+2*HASHTABLE_NPAGE)) ); + assert( iHash>=0 ); return iHash; } @@ -59278,6 +68735,7 @@ static int walFramePage(u32 iFrame){ */ static u32 walFramePgno(Wal *pWal, u32 iFrame){ int iHash = walFramePage(iFrame); + SEH_INJECT_FAULT; if( iHash==0 ){ return pWal->apWiData[0][WALINDEX_HDR_SIZE/sizeof(u32) + iFrame - 1]; } @@ -59301,7 +68759,6 @@ static void walCleanupHash(Wal *pWal){ int iLimit = 0; /* Zero values greater than this */ int nByte; /* Number of bytes to zero in aPgno[] */ int i; /* Used to iterate through aHash[] */ - int rc; /* Return code form walHashGet() */ assert( pWal->writeLock ); testcase( pWal->hdr.mxFrame==HASHTABLE_NPAGE_ONE-1 ); @@ -59310,14 +68767,14 @@ static void walCleanupHash(Wal *pWal){ if( pWal->hdr.mxFrame==0 ) return; - /* Obtain pointers to the hash-table and page-number array containing + /* Obtain pointers to the hash-table and page-number array containing ** the entry that corresponds to frame pWal->hdr.mxFrame. It is guaranteed ** that the page said hash-table and array reside on is already mapped.(1) */ assert( pWal->nWiData>walFramePage(pWal->hdr.mxFrame) ); assert( pWal->apWiData[walFramePage(pWal->hdr.mxFrame)] ); - rc = walHashGet(pWal, walFramePage(pWal->hdr.mxFrame), &sLoc); - if( NEVER(rc) ) return; /* Defense-in-depth, in case (1) above is wrong */ + i = walHashGet(pWal, walFramePage(pWal->hdr.mxFrame), &sLoc); + if( NEVER(i) ) return; /* Defense-in-depth, in case (1) above is wrong */ /* Zero all hash-table entries that correspond to frame numbers greater ** than pWal->hdr.mxFrame. @@ -59329,12 +68786,13 @@ static void walCleanupHash(Wal *pWal){ sLoc.aHash[i] = 0; } } - + /* Zero the entries in the aPgno array that correspond to frames with - ** frame numbers greater than pWal->hdr.mxFrame. + ** frame numbers greater than pWal->hdr.mxFrame. */ - nByte = (int)((char *)sLoc.aHash - (char *)&sLoc.aPgno[iLimit+1]); - memset((void *)&sLoc.aPgno[iLimit+1], 0, nByte); + nByte = (int)((char *)sLoc.aHash - (char *)&sLoc.aPgno[iLimit]); + assert( nByte>=0 ); + memset((void *)&sLoc.aPgno[iLimit], 0, nByte); #ifdef SQLITE_ENABLE_EXPENSIVE_ASSERT /* Verify that the every entry in the mapping region is still reachable @@ -59343,11 +68801,11 @@ static void walCleanupHash(Wal *pWal){ if( iLimit ){ int j; /* Loop counter */ int iKey; /* Hash key */ - for(j=1; j<=iLimit; j++){ + for(j=0; j=0 ); + memset((void*)sLoc.aPgno, 0, nByte); } /* If the entry in aPgno[] is already set, then the previous writer ** must have exited unexpectedly in the middle of a transaction (after - ** writing one or more dirty pages to the WAL to free up memory). - ** Remove the remnants of that writers uncommitted transaction from + ** writing one or more dirty pages to the WAL to free up memory). + ** Remove the remnants of that writers uncommitted transaction from ** the hash-table before writing any new entries. */ - if( sLoc.aPgno[idx] ){ + if( sLoc.aPgno[idx-1] ){ walCleanupHash(pWal); - assert( !sLoc.aPgno[idx] ); + assert( !sLoc.aPgno[idx-1] ); } /* Write the aPgno[] array entry and the hash-table slot. */ @@ -59400,8 +68858,8 @@ static int walIndexAppend(Wal *pWal, u32 iFrame, u32 iPage){ for(iKey=walHash(iPage); sLoc.aHash[iKey]; iKey=walNextHash(iKey)){ if( (nCollide--)==0 ) return SQLITE_CORRUPT_BKPT; } - sLoc.aPgno[idx] = iPage; - sLoc.aHash[iKey] = (ht_slot)idx; + sLoc.aPgno[(idx-1)&(HASHTABLE_NPAGE-1)] = iPage; + AtomicStore(&sLoc.aHash[iKey], (ht_slot)idx); #ifdef SQLITE_ENABLE_EXPENSIVE_ASSERT /* Verify that the number of entries in the hash table exactly equals @@ -59421,25 +68879,24 @@ static int walIndexAppend(Wal *pWal, u32 iFrame, u32 iPage){ */ if( (idx&0x3ff)==0 ){ int i; /* Loop counter */ - for(i=1; i<=idx; i++){ + for(i=0; iwriteLock ); iLock = WAL_ALL_BUT_WRITE + pWal->ckptLock; rc = walLockExclusive(pWal, iLock, WAL_READ_LOCK(0)-iLock); - if( rc==SQLITE_OK ){ - rc = walLockExclusive(pWal, WAL_READ_LOCK(1), WAL_NREADER-1); - if( rc!=SQLITE_OK ){ - walUnlockExclusive(pWal, iLock, WAL_READ_LOCK(0)-iLock); - } - } if( rc ){ return rc; } @@ -59487,15 +68938,16 @@ static int walIndexRecover(Wal *pWal){ if( nSize>WAL_HDRSIZE ){ u8 aBuf[WAL_HDRSIZE]; /* Buffer to load WAL header into */ + u32 *aPrivate = 0; /* Heap copy of *-shm hash being populated */ u8 *aFrame = 0; /* Malloc'd buffer to load entire frame */ int szFrame; /* Number of bytes in buffer aFrame[] */ u8 *aData; /* Pointer to data part of aFrame buffer */ - int iFrame; /* Index of last frame read */ - i64 iOffset; /* Next offset to read from log file */ int szPage; /* Page size according to the log */ u32 magic; /* Magic value read from WAL header */ u32 version; /* Magic value read from WAL header */ int isValid; /* True if this frame is valid */ + u32 iPg; /* Current 32KB wal-index page */ + u32 iLastFrame; /* Last frame in wal, based on nSize alone */ /* Read in the WAL header. */ rc = sqlite3OsRead(pWal->pWalFd, aBuf, WAL_HDRSIZE, 0); @@ -59504,16 +68956,16 @@ static int walIndexRecover(Wal *pWal){ } /* If the database page size is not a power of two, or is greater than - ** SQLITE_MAX_PAGE_SIZE, conclude that the WAL file contains no valid + ** SQLITE_MAX_PAGE_SIZE, conclude that the WAL file contains no valid ** data. Similarly, if the 'magic' value is invalid, ignore the whole ** WAL file. */ magic = sqlite3Get4byte(&aBuf[0]); szPage = sqlite3Get4byte(&aBuf[8]); - if( (magic&0xFFFFFFFE)!=WAL_MAGIC - || szPage&(szPage-1) - || szPage>SQLITE_MAX_PAGE_SIZE - || szPage<512 + if( (magic&0xFFFFFFFE)!=WAL_MAGIC + || szPage&(szPage-1) + || szPage>SQLITE_MAX_PAGE_SIZE + || szPage<512 ){ goto finished; } @@ -59523,7 +68975,7 @@ static int walIndexRecover(Wal *pWal){ memcpy(&pWal->hdr.aSalt, &aBuf[16], 8); /* Verify that the WAL header checksum is correct */ - walChecksumBytes(pWal->hdr.bigEndCksum==SQLITE_BIGENDIAN, + walChecksumBytes(pWal->hdr.bigEndCksum==SQLITE_BIGENDIAN, aBuf, WAL_HDRSIZE-2*4, 0, pWal->hdr.aFrameCksum ); if( pWal->hdr.aFrameCksum[0]!=sqlite3Get4byte(&aBuf[24]) @@ -59542,40 +68994,90 @@ static int walIndexRecover(Wal *pWal){ /* Malloc a buffer to read frames into. */ szFrame = szPage + WAL_FRAME_HDRSIZE; - aFrame = (u8 *)sqlite3_malloc64(szFrame); + aFrame = (u8 *)sqlite3_malloc64(szFrame + WALINDEX_PGSZ); + SEH_FREE_ON_ERROR(0, aFrame); if( !aFrame ){ rc = SQLITE_NOMEM_BKPT; goto recovery_error; } aData = &aFrame[WAL_FRAME_HDRSIZE]; + aPrivate = (u32*)&aData[szPage]; /* Read all frames from the log file. */ - iFrame = 0; - for(iOffset=WAL_HDRSIZE; (iOffset+szFrame)<=nSize; iOffset+=szFrame){ - u32 pgno; /* Database page number for frame */ - u32 nTruncate; /* dbsize field from frame header */ - - /* Read and decode the next log frame. */ - iFrame++; - rc = sqlite3OsRead(pWal->pWalFd, aFrame, szFrame, iOffset); - if( rc!=SQLITE_OK ) break; - isValid = walDecodeFrame(pWal, &pgno, &nTruncate, aData, aFrame); - if( !isValid ) break; - rc = walIndexAppend(pWal, iFrame, pgno); - if( rc!=SQLITE_OK ) break; - - /* If nTruncate is non-zero, this is a commit record. */ - if( nTruncate ){ - pWal->hdr.mxFrame = iFrame; - pWal->hdr.nPage = nTruncate; - pWal->hdr.szPage = (u16)((szPage&0xff00) | (szPage>>16)); - testcase( szPage<=32768 ); - testcase( szPage>=65536 ); - aFrameCksum[0] = pWal->hdr.aFrameCksum[0]; - aFrameCksum[1] = pWal->hdr.aFrameCksum[1]; + iLastFrame = (nSize - WAL_HDRSIZE) / szFrame; + for(iPg=0; iPg<=(u32)walFramePage(iLastFrame); iPg++){ + u32 *aShare; + u32 iFrame; /* Index of last frame read */ + u32 iLast = MIN(iLastFrame, HASHTABLE_NPAGE_ONE+iPg*HASHTABLE_NPAGE); + u32 iFirst = 1 + (iPg==0?0:HASHTABLE_NPAGE_ONE+(iPg-1)*HASHTABLE_NPAGE); + u32 nHdr, nHdr32; + rc = walIndexPage(pWal, iPg, (volatile u32**)&aShare); + assert( aShare!=0 || rc!=SQLITE_OK ); + if( aShare==0 ) break; + SEH_SET_ON_ERROR(iPg, aShare); + pWal->apWiData[iPg] = aPrivate; + + for(iFrame=iFirst; iFrame<=iLast; iFrame++){ + i64 iOffset = walFrameOffset(iFrame, szPage); + u32 pgno; /* Database page number for frame */ + u32 nTruncate; /* dbsize field from frame header */ + + /* Read and decode the next log frame. */ + rc = sqlite3OsRead(pWal->pWalFd, aFrame, szFrame, iOffset); + if( rc!=SQLITE_OK ) break; + isValid = walDecodeFrame(pWal, &pgno, &nTruncate, aData, aFrame); + if( !isValid ) break; + rc = walIndexAppend(pWal, iFrame, pgno); + if( NEVER(rc!=SQLITE_OK) ) break; + + /* If nTruncate is non-zero, this is a commit record. */ + if( nTruncate ){ + pWal->hdr.mxFrame = iFrame; + pWal->hdr.nPage = nTruncate; + pWal->hdr.szPage = (u16)((szPage&0xff00) | (szPage>>16)); + testcase( szPage<=32768 ); + testcase( szPage>=65536 ); + aFrameCksum[0] = pWal->hdr.aFrameCksum[0]; + aFrameCksum[1] = pWal->hdr.aFrameCksum[1]; + } } + pWal->apWiData[iPg] = aShare; + SEH_SET_ON_ERROR(0,0); + nHdr = (iPg==0 ? WALINDEX_HDR_SIZE : 0); + nHdr32 = nHdr / sizeof(u32); +#ifndef SQLITE_SAFER_WALINDEX_RECOVERY + /* Memcpy() should work fine here, on all reasonable implementations. + ** Technically, memcpy() might change the destination to some + ** intermediate value before setting to the final value, and that might + ** cause a concurrent reader to malfunction. Memcpy() is allowed to + ** do that, according to the spec, but no memcpy() implementation that + ** we know of actually does that, which is why we say that memcpy() + ** is safe for this. Memcpy() is certainly a lot faster. + */ + memcpy(&aShare[nHdr32], &aPrivate[nHdr32], WALINDEX_PGSZ-nHdr); +#else + /* In the event that some platform is found for which memcpy() + ** changes the destination to some intermediate value before + ** setting the final value, this alternative copy routine is + ** provided. + */ + { + int i; + for(i=nHdr32; ihdr.aFrameCksum[1] = aFrameCksum[1]; walIndexWriteHdr(pWal); - /* Reset the checkpoint-header. This is safe because this thread is - ** currently holding locks that exclude all other readers, writers and - ** checkpointers. + /* Reset the checkpoint-header. This is safe because this thread is + ** currently holding locks that exclude all other writers and + ** checkpointers. Then set the values of read-mark slots 1 through N. */ pInfo = walCkptInfo(pWal); pInfo->nBackfill = 0; pInfo->nBackfillAttempted = pWal->hdr.mxFrame; pInfo->aReadMark[0] = 0; - for(i=1; iaReadMark[i] = READMARK_NOT_USED; - if( pWal->hdr.mxFrame ) pInfo->aReadMark[1] = pWal->hdr.mxFrame; + for(i=1; ihdr.mxFrame ){ + pInfo->aReadMark[i] = pWal->hdr.mxFrame; + }else{ + pInfo->aReadMark[i] = READMARK_NOT_USED; + } + SEH_INJECT_FAULT; + walUnlockExclusive(pWal, WAL_READ_LOCK(i), 1); + }else if( rc!=SQLITE_BUSY ){ + goto recovery_error; + } + } /* If more than one frame was recovered from the log file, report an ** event via sqlite3_log(). This is to help with identifying performance @@ -59614,7 +69128,6 @@ static int walIndexRecover(Wal *pWal){ recovery_error: WALTRACE(("WAL%p: recovery %s\n", pWal, rc ? "failed" : "ok")); walUnlockExclusive(pWal, iLock, WAL_READ_LOCK(0)-iLock); - walUnlockExclusive(pWal, WAL_READ_LOCK(1), WAL_NREADER-1); return rc; } @@ -59634,8 +69147,8 @@ static void walIndexClose(Wal *pWal, int isDelete){ } } -/* -** Open a connection to the WAL file zWalName. The database file must +/* +** Open a connection to the WAL file zWalName. The database file must ** already be opened on connection pDbFd. The buffer that zWalName points ** to must remain valid for the lifetime of the returned Wal* handle. ** @@ -59645,7 +69158,7 @@ static void walIndexClose(Wal *pWal, int isDelete){ ** were to do this just after this client opened one of these files, the ** system would be badly broken. ** -** If the log file is successfully opened, SQLITE_OK is returned and +** If the log file is successfully opened, SQLITE_OK is returned and ** *ppWal is set to point to a new WAL handle. If an error occurs, ** an SQLite error code is returned and *ppWal is left unmodified. */ @@ -59664,14 +69177,43 @@ SQLITE_PRIVATE int sqlite3WalOpen( assert( zWalName && zWalName[0] ); assert( pDbFd ); + /* Verify the values of various constants. Any changes to the values + ** of these constants would result in an incompatible on-disk format + ** for the -shm file. Any change that causes one of these asserts to + ** fail is a backward compatibility problem, even if the change otherwise + ** works. + ** + ** This table also serves as a helpful cross-reference when trying to + ** interpret hex dumps of the -shm file. + */ + assert( 48 == sizeof(WalIndexHdr) ); + assert( 40 == sizeof(WalCkptInfo) ); + assert( 120 == WALINDEX_LOCK_OFFSET ); + assert( 136 == WALINDEX_HDR_SIZE ); + assert( 4096 == HASHTABLE_NPAGE ); + assert( 4062 == HASHTABLE_NPAGE_ONE ); + assert( 8192 == HASHTABLE_NSLOT ); + assert( 383 == HASHTABLE_HASH_1 ); + assert( 32768 == WALINDEX_PGSZ ); + assert( 8 == SQLITE_SHM_NLOCK ); + assert( 5 == WAL_NREADER ); + assert( 24 == WAL_FRAME_HDRSIZE ); + assert( 32 == WAL_HDRSIZE ); + assert( 120 == WALINDEX_LOCK_OFFSET + WAL_WRITE_LOCK ); + assert( 121 == WALINDEX_LOCK_OFFSET + WAL_CKPT_LOCK ); + assert( 122 == WALINDEX_LOCK_OFFSET + WAL_RECOVER_LOCK ); + assert( 123 == WALINDEX_LOCK_OFFSET + WAL_READ_LOCK(0) ); + assert( 124 == WALINDEX_LOCK_OFFSET + WAL_READ_LOCK(1) ); + assert( 125 == WALINDEX_LOCK_OFFSET + WAL_READ_LOCK(2) ); + assert( 126 == WALINDEX_LOCK_OFFSET + WAL_READ_LOCK(3) ); + assert( 127 == WALINDEX_LOCK_OFFSET + WAL_READ_LOCK(4) ); + /* In the amalgamation, the os_unix.c and os_win.c source files come before ** this source file. Verify that the #defines of the locking byte offsets ** in os_unix.c and os_win.c agree with the WALINDEX_LOCK_OFFSET value. ** For that matter, if the lock offset ever changes from its initial design ** value of 120, we need to know that so there is an assert() to check it. */ - assert( 120==WALINDEX_LOCK_OFFSET ); - assert( 136==WALINDEX_HDR_SIZE ); #ifdef WIN_SHM_BASE assert( WIN_SHM_BASE==WALINDEX_LOCK_OFFSET ); #endif @@ -59721,7 +69263,7 @@ SQLITE_PRIVATE int sqlite3WalOpen( } /* -** Change the size to which the WAL file is trucated on each reset. +** Change the size to which the WAL file is truncated on each reset. */ SQLITE_PRIVATE void sqlite3WalLimit(Wal *pWal, i64 iLimit){ if( pWal ) pWal->mxWalSize = iLimit; @@ -59809,7 +69351,7 @@ static void walMerge( ht_slot logpage; Pgno dbpage; - if( (iLeft=nRight || aContent[aLeft[iLeft]]HASHTABLE_NPAGE?HASHTABLE_NPAGE:iLast) + ); if( !p ){ return SQLITE_NOMEM_BKPT; } memset(p, 0, nByte); p->nSegment = nSegment; - - /* Allocate temporary space used by the merge-sort routine. This block - ** of memory will be freed before this function returns. - */ - aTmp = (ht_slot *)sqlite3_malloc64( - sizeof(ht_slot) * (iLast>HASHTABLE_NPAGE?HASHTABLE_NPAGE:iLast) - ); - if( !aTmp ){ - rc = SQLITE_NOMEM_BKPT; - } - + aTmp = (ht_slot*)&(((u8*)p)[nByte]); + SEH_FREE_ON_ERROR(0, p); for(i=walFramePage(nBackfill+1); rc==SQLITE_OK && iaSegment[p->nSegment])[sLoc.iZero]; sLoc.iZero++; - + for(j=0; jaSegment[i].aPgno = (u32 *)sLoc.aPgno; } } - sqlite3_free(aTmp); - if( rc!=SQLITE_OK ){ + SEH_FREE_ON_ERROR(p, 0); walIteratorFree(p); p = 0; } @@ -60002,6 +69534,88 @@ static int walIteratorInit(Wal *pWal, u32 nBackfill, WalIterator **pp){ return rc; } +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + + +/* +** Attempt to enable blocking locks that block for nMs ms. Return 1 if +** blocking locks are successfully enabled, or 0 otherwise. +*/ +static int walEnableBlockingMs(Wal *pWal, int nMs){ + int rc = sqlite3OsFileControl( + pWal->pDbFd, SQLITE_FCNTL_LOCK_TIMEOUT, (void*)&nMs + ); + return (rc==SQLITE_OK); +} + +/* +** Attempt to enable blocking locks. Blocking locks are enabled only if (a) +** they are supported by the VFS, and (b) the database handle is configured +** with a busy-timeout. Return 1 if blocking locks are successfully enabled, +** or 0 otherwise. +*/ +static int walEnableBlocking(Wal *pWal){ + int res = 0; + if( pWal->db ){ + int tmout = pWal->db->setlkTimeout; + if( tmout ){ + res = walEnableBlockingMs(pWal, tmout); + } + } + return res; +} + +/* +** Disable blocking locks. +*/ +static void walDisableBlocking(Wal *pWal){ + int tmout = 0; + sqlite3OsFileControl(pWal->pDbFd, SQLITE_FCNTL_LOCK_TIMEOUT, (void*)&tmout); +} + +/* +** If parameter bLock is true, attempt to enable blocking locks, take +** the WRITER lock, and then disable blocking locks. If blocking locks +** cannot be enabled, no attempt to obtain the WRITER lock is made. Return +** an SQLite error code if an error occurs, or SQLITE_OK otherwise. It is not +** an error if blocking locks can not be enabled. +** +** If the bLock parameter is false and the WRITER lock is held, release it. +*/ +SQLITE_PRIVATE int sqlite3WalWriteLock(Wal *pWal, int bLock){ + int rc = SQLITE_OK; + assert( pWal->readLock<0 || bLock==0 ); + if( bLock ){ + assert( pWal->db ); + if( walEnableBlocking(pWal) ){ + rc = walLockExclusive(pWal, WAL_WRITE_LOCK, 1); + if( rc==SQLITE_OK ){ + pWal->writeLock = 1; + } + walDisableBlocking(pWal); + } + }else if( pWal->writeLock ){ + walUnlockExclusive(pWal, WAL_WRITE_LOCK, 1); + pWal->writeLock = 0; + } + return rc; +} + +/* +** Set the database handle used to determine if blocking locks are required. +*/ +SQLITE_PRIVATE void sqlite3WalDb(Wal *pWal, sqlite3 *db){ + pWal->db = db; +} + +#else +# define walEnableBlocking(x) 0 +# define walDisableBlocking(x) +# define walEnableBlockingMs(pWal, ms) 0 +# define sqlite3WalDb(pWal, db) +#endif /* ifdef SQLITE_ENABLE_SETLK_TIMEOUT */ + + /* ** Attempt to obtain the exclusive WAL lock defined by parameters lockIdx and ** n. If the attempt fails and parameter xBusy is not NULL, then it is a @@ -60019,6 +69633,12 @@ static int walBusyLock( do { rc = walLockExclusive(pWal, lockIdx, n); }while( xBusy && rc==SQLITE_BUSY && xBusy(pBusyArg) ); +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + if( rc==SQLITE_BUSY_TIMEOUT ){ + walDisableBlocking(pWal); + rc = SQLITE_BUSY; + } +#endif return rc; } @@ -60043,8 +69663,8 @@ static int walPagesize(Wal *pWal){ ** client to write to the database (which may be this one) does so by ** writing frames into the start of the log file. ** -** The value of parameter salt1 is used as the aSalt[1] value in the -** new wal-index header. It should be passed a pseudo-random value (i.e. +** The value of parameter salt1 is used as the aSalt[1] value in the +** new wal-index header. It should be passed a pseudo-random value (i.e. ** one obtained from sqlite3_randomness()). */ static void walRestartHdr(Wal *pWal, u32 salt1){ @@ -60056,7 +69676,7 @@ static void walRestartHdr(Wal *pWal, u32 salt1){ sqlite3Put4byte((u8*)&aSalt[0], 1 + sqlite3Get4byte((u8*)&aSalt[0])); memcpy(&pWal->hdr.aSalt[1], &salt1, 4); walIndexWriteHdr(pWal); - pInfo->nBackfill = 0; + AtomicStore(&pInfo->nBackfill, 0); pInfo->nBackfillAttempted = 0; pInfo->aReadMark[1] = 0; for(i=2; iaReadMark[i] = READMARK_NOT_USED; @@ -60072,8 +69692,8 @@ static void walRestartHdr(Wal *pWal, u32 salt1){ ** that a concurrent reader might be using. ** ** All I/O barrier operations (a.k.a fsyncs) occur in this routine when -** SQLite is in WAL-mode in synchronous=NORMAL. That means that if -** checkpoints are always run by a background thread or background +** SQLite is in WAL-mode in synchronous=NORMAL. That means that if +** checkpoints are always run by a background thread or background ** process, foreground threads will never block on a lengthy fsync call. ** ** Fsync is called on the WAL before writing content out of the WAL and @@ -60086,7 +69706,7 @@ static void walRestartHdr(Wal *pWal, u32 salt1){ ** database file. ** ** This routine uses and updates the nBackfill field of the wal-index header. -** This is the only routine that will increase the value of nBackfill. +** This is the only routine that will increase the value of nBackfill. ** (A WAL reset or recovery will revert nBackfill to zero, but not increase ** its value.) ** @@ -60131,20 +69751,13 @@ static int walCheckpoint( mxSafeFrame = pWal->hdr.mxFrame; mxPage = pWal->hdr.nPage; for(i=1; iaReadMark[i]; + u32 y = AtomicLoad(pInfo->aReadMark+i); SEH_INJECT_FAULT; if( mxSafeFrame>y ){ assert( y<=pWal->hdr.mxFrame ); rc = walBusyLock(pWal, xBusy, pBusyArg, WAL_READ_LOCK(i), 1); if( rc==SQLITE_OK ){ - pInfo->aReadMark[i] = (i==1 ? mxSafeFrame : READMARK_NOT_USED); + u32 iMark = (i==1 ? mxSafeFrame : READMARK_NOT_USED); + AtomicStore(pInfo->aReadMark+i, iMark); SEH_INJECT_FAULT; walUnlockExclusive(pWal, WAL_READ_LOCK(i), 1); }else if( rc==SQLITE_BUSY ){ mxSafeFrame = y; @@ -60162,62 +69775,86 @@ static int walCheckpoint( } if( pIter - && (rc = walBusyLock(pWal, xBusy, pBusyArg, WAL_READ_LOCK(0),1))==SQLITE_OK + && (rc = walBusyLock(pWal,xBusy,pBusyArg,WAL_READ_LOCK(0),1))==SQLITE_OK ){ u32 nBackfill = pInfo->nBackfill; + WalIndexHdr *pLive = (WalIndexHdr*)walIndexHdr(pWal); + + /* Now that read-lock slot 0 is locked, check that the wal has not been + ** wrapped since the header was read for this checkpoint. If it was, then + ** there was no work to do anyway. In this case the + ** (pInfo->nBackfillhdr.mxFrame) test above only passed because + ** pInfo->nBackfill had already been set to 0 by the writer that wrapped + ** the wal file. It would also be dangerous to proceed, as there may be + ** fewer than pWal->hdr.mxFrame valid frames in the wal file. */ + int bChg = memcmp(pLive->aSalt, pWal->hdr.aSalt, sizeof(pWal->hdr.aSalt)); + if( 0==bChg ){ + pInfo->nBackfillAttempted = mxSafeFrame; SEH_INJECT_FAULT; + + /* Sync the WAL to disk */ + rc = sqlite3OsSync(pWal->pWalFd, CKPT_SYNC_FLAGS(sync_flags)); + + /* If the database may grow as a result of this checkpoint, hint + ** about the eventual size of the db file to the VFS layer. + */ + if( rc==SQLITE_OK ){ + i64 nReq = ((i64)mxPage * szPage); + i64 nSize; /* Current size of database file */ + sqlite3OsFileControl(pWal->pDbFd, SQLITE_FCNTL_CKPT_START, 0); + rc = sqlite3OsFileSize(pWal->pDbFd, &nSize); + if( rc==SQLITE_OK && nSizehdr.mxFrame*szPage)pDbFd, SQLITE_FCNTL_SIZE_HINT, &nReq); + } + } - pInfo->nBackfillAttempted = mxSafeFrame; - - /* Sync the WAL to disk */ - rc = sqlite3OsSync(pWal->pWalFd, CKPT_SYNC_FLAGS(sync_flags)); - - /* If the database may grow as a result of this checkpoint, hint - ** about the eventual size of the db file to the VFS layer. - */ - if( rc==SQLITE_OK ){ - i64 nReq = ((i64)mxPage * szPage); - i64 nSize; /* Current size of database file */ - rc = sqlite3OsFileSize(pWal->pDbFd, &nSize); - if( rc==SQLITE_OK && nSizepDbFd, SQLITE_FCNTL_SIZE_HINT, &nReq); } - } - - /* Iterate through the contents of the WAL, copying data to the db file */ - while( rc==SQLITE_OK && 0==walIteratorNext(pIter, &iDbpage, &iFrame) ){ - i64 iOffset; - assert( walFramePgno(pWal, iFrame)==iDbpage ); - if( db->u1.isInterrupted ){ - rc = db->mallocFailed ? SQLITE_NOMEM_BKPT : SQLITE_INTERRUPT; - break; - } - if( iFrame<=nBackfill || iFrame>mxSafeFrame || iDbpage>mxPage ){ - continue; + /* Iterate through the contents of the WAL, copying data to the + ** db file */ + while( rc==SQLITE_OK && 0==walIteratorNext(pIter, &iDbpage, &iFrame) ){ + i64 iOffset; + assert( walFramePgno(pWal, iFrame)==iDbpage ); + SEH_INJECT_FAULT; + if( AtomicLoad(&db->u1.isInterrupted) ){ + rc = db->mallocFailed ? SQLITE_NOMEM_BKPT : SQLITE_INTERRUPT; + break; + } + if( iFrame<=nBackfill || iFrame>mxSafeFrame || iDbpage>mxPage ){ + continue; + } + iOffset = walFrameOffset(iFrame, szPage) + WAL_FRAME_HDRSIZE; + /* testcase( IS_BIG_INT(iOffset) ); // requires a 4GiB WAL file */ + rc = sqlite3OsRead(pWal->pWalFd, zBuf, szPage, iOffset); + if( rc!=SQLITE_OK ) break; + iOffset = (iDbpage-1)*(i64)szPage; + testcase( IS_BIG_INT(iOffset) ); + rc = sqlite3OsWrite(pWal->pDbFd, zBuf, szPage, iOffset); + if( rc!=SQLITE_OK ) break; } - iOffset = walFrameOffset(iFrame, szPage) + WAL_FRAME_HDRSIZE; - /* testcase( IS_BIG_INT(iOffset) ); // requires a 4GiB WAL file */ - rc = sqlite3OsRead(pWal->pWalFd, zBuf, szPage, iOffset); - if( rc!=SQLITE_OK ) break; - iOffset = (iDbpage-1)*(i64)szPage; - testcase( IS_BIG_INT(iOffset) ); - rc = sqlite3OsWrite(pWal->pDbFd, zBuf, szPage, iOffset); - if( rc!=SQLITE_OK ) break; - } + sqlite3OsFileControl(pWal->pDbFd, SQLITE_FCNTL_CKPT_DONE, 0); - /* If work was actually accomplished... */ - if( rc==SQLITE_OK ){ - if( mxSafeFrame==walIndexHdr(pWal)->mxFrame ){ - i64 szDb = pWal->hdr.nPage*(i64)szPage; - testcase( IS_BIG_INT(szDb) ); - rc = sqlite3OsTruncate(pWal->pDbFd, szDb); + /* If work was actually accomplished... */ + if( rc==SQLITE_OK ){ + if( mxSafeFrame==walIndexHdr(pWal)->mxFrame ){ + i64 szDb = pWal->hdr.nPage*(i64)szPage; + testcase( IS_BIG_INT(szDb) ); + rc = sqlite3OsTruncate(pWal->pDbFd, szDb); + if( rc==SQLITE_OK ){ + rc = sqlite3OsSync(pWal->pDbFd, CKPT_SYNC_FLAGS(sync_flags)); + } + } if( rc==SQLITE_OK ){ - rc = sqlite3OsSync(pWal->pDbFd, CKPT_SYNC_FLAGS(sync_flags)); + AtomicStore(&pInfo->nBackfill, mxSafeFrame); SEH_INJECT_FAULT; } } - if( rc==SQLITE_OK ){ - pInfo->nBackfill = mxSafeFrame; - } } /* Release the reader lock held while backfilling */ @@ -60232,12 +69869,13 @@ static int walCheckpoint( } /* If this is an SQLITE_CHECKPOINT_RESTART or TRUNCATE operation, and the - ** entire wal file has been copied into the database file, then block - ** until all readers have finished using the wal file. This ensures that + ** entire wal file has been copied into the database file, then block + ** until all readers have finished using the wal file. This ensures that ** the next process to write to the database restarts the wal file. */ if( rc==SQLITE_OK && eMode!=SQLITE_CHECKPOINT_PASSIVE ){ assert( pWal->writeLock ); + SEH_INJECT_FAULT; if( pInfo->nBackfillhdr.mxFrame ){ rc = SQLITE_BUSY; }else if( eMode>=SQLITE_CHECKPOINT_RESTART ){ @@ -60257,7 +69895,7 @@ static int walCheckpoint( ** writer clients should see that the entire log file has been ** checkpointed and behave accordingly. This seems unsafe though, ** as it would leave the system in a state where the contents of - ** the wal-index header do not match the contents of the + ** the wal-index header do not match the contents of the ** file-system. To avoid this, update the wal-index header to ** indicate that the log file contains zero valid frames. */ walRestartHdr(pWal, salt1); @@ -60269,6 +69907,7 @@ static int walCheckpoint( } walcheckpoint_out: + SEH_FREE_ON_ERROR(pIter, 0); walIteratorFree(pIter); return rc; } @@ -60291,6 +69930,95 @@ static void walLimitSize(Wal *pWal, i64 nMax){ } } +#ifdef SQLITE_USE_SEH +/* +** This is the "standard" exception handler used in a few places to handle +** an exception thrown by reading from the *-shm mapping after it has become +** invalid in SQLITE_USE_SEH builds. It is used as follows: +** +** SEH_TRY { ... } +** SEH_EXCEPT( rc = walHandleException(pWal); ) +** +** This function does three things: +** +** 1) Determines the locks that should be held, based on the contents of +** the Wal.readLock, Wal.writeLock and Wal.ckptLock variables. All other +** held locks are assumed to be transient locks that would have been +** released had the exception not been thrown and are dropped. +** +** 2) Frees the pointer at Wal.pFree, if any, using sqlite3_free(). +** +** 3) Set pWal->apWiData[pWal->iWiPg] to pWal->pWiValue if not NULL +** +** 4) Returns SQLITE_IOERR. +*/ +static int walHandleException(Wal *pWal){ + if( pWal->exclusiveMode==0 ){ + static const int S = 1; + static const int E = (1<writeLock==2 ) pWal->writeLock = 0; + mUnlock = pWal->lockMask & ~( + (pWal->readLock<0 ? 0 : (S << WAL_READ_LOCK(pWal->readLock))) + | (pWal->writeLock ? (E << WAL_WRITE_LOCK) : 0) + | (pWal->ckptLock ? (E << WAL_CKPT_LOCK) : 0) + ); + for(ii=0; iipFree); + pWal->pFree = 0; + if( pWal->pWiValue ){ + pWal->apWiData[pWal->iWiPg] = pWal->pWiValue; + pWal->pWiValue = 0; + } + return SQLITE_IOERR_IN_PAGE; +} + +/* +** Assert that the Wal.lockMask mask, which indicates the locks held +** by the connection, is consistent with the Wal.readLock, Wal.writeLock +** and Wal.ckptLock variables. To be used as: +** +** assert( walAssertLockmask(pWal) ); +*/ +static int walAssertLockmask(Wal *pWal){ + if( pWal->exclusiveMode==0 ){ + static const int S = 1; + static const int E = (1<readLock<0 ? 0 : (S << WAL_READ_LOCK(pWal->readLock))) + | (pWal->writeLock ? (E << WAL_WRITE_LOCK) : 0) + | (pWal->ckptLock ? (E << WAL_CKPT_LOCK) : 0) +#ifdef SQLITE_ENABLE_SNAPSHOT + | (pWal->pSnapshot ? (pWal->lockMask & (1 << WAL_CKPT_LOCK)) : 0) +#endif + ); + assert( mExpect==pWal->lockMask ); + } + return 1; +} + +/* +** Return and zero the "system error" field set when an +** EXCEPTION_IN_PAGE_ERROR exception is caught. +*/ +SQLITE_PRIVATE int sqlite3WalSystemErrno(Wal *pWal){ + int iRet = 0; + if( pWal ){ + iRet = pWal->iSysErrno; + pWal->iSysErrno = 0; + } + return iRet; +} + +#else +# define walAssertLockmask(x) 1 +#endif /* ifdef SQLITE_USE_SEH */ + /* ** Close a connection to a log file. */ @@ -60305,6 +70033,8 @@ SQLITE_PRIVATE int sqlite3WalClose( if( pWal ){ int isDelete = 0; /* True to unlink wal and wal-index files */ + assert( walAssertLockmask(pWal) ); + /* If an EXCLUSIVE lock can be obtained on the database file (using the ** ordinary, rollback-mode locking methods, this guarantees that the ** connection associated with this log file is the only connection to @@ -60319,7 +70049,7 @@ SQLITE_PRIVATE int sqlite3WalClose( if( pWal->exclusiveMode==WAL_NORMAL_MODE ){ pWal->exclusiveMode = WAL_EXCLUSIVE_MODE; } - rc = sqlite3WalCheckpoint(pWal, db, + rc = sqlite3WalCheckpoint(pWal, db, SQLITE_CHECKPOINT_PASSIVE, 0, 0, sync_flags, nBuf, zBuf, 0, 0 ); if( rc==SQLITE_OK ){ @@ -60329,7 +70059,7 @@ SQLITE_PRIVATE int sqlite3WalClose( ); if( bPersist!=1 ){ /* Try to delete the WAL file if the checkpoint completed and - ** fsyned (rc==SQLITE_OK) and if we are not in persistent-wal + ** fsynced (rc==SQLITE_OK) and if we are not in persistent-wal ** mode (!bPersist) */ isDelete = 1; }else if( pWal->mxWalSize>=0 ){ @@ -60375,7 +70105,7 @@ SQLITE_PRIVATE int sqlite3WalClose( ** If the checksum cannot be verified return non-zero. If the header ** is read successfully and the checksum verified, return zero. */ -static int walIndexTryHdr(Wal *pWal, int *pChanged){ +static SQLITE_NO_TSAN int walIndexTryHdr(Wal *pWal, int *pChanged){ u32 aCksum[2]; /* Checksum on the header content */ WalIndexHdr h1, h2; /* Two copies of the header content */ WalIndexHdr volatile *aHdr; /* Header in shared memory */ @@ -60388,19 +70118,25 @@ static int walIndexTryHdr(Wal *pWal, int *pChanged){ ** meaning it is possible that an inconsistent snapshot is read ** from the file. If this happens, return non-zero. ** + ** tag-20200519-1: ** There are two copies of the header at the beginning of the wal-index. ** When reading, read [0] first then [1]. Writes are in the reverse order. ** Memory barriers are used to prevent the compiler or the hardware from - ** reordering the reads and writes. + ** reordering the reads and writes. TSAN and similar tools can sometimes + ** give false-positive warnings about these accesses because the tools do not + ** account for the double-read and the memory barrier. The use of mutexes + ** here would be problematic as the memory being accessed is potentially + ** shared among multiple processes and not all mutex implementations work + ** reliably in that environment. */ aHdr = walIndexHdr(pWal); - memcpy(&h1, (void *)&aHdr[0], sizeof(h1)); + memcpy(&h1, (void *)&aHdr[0], sizeof(h1)); /* Possible TSAN false-positive */ walShmBarrier(pWal); memcpy(&h2, (void *)&aHdr[1], sizeof(h2)); if( memcmp(&h1, &h2, sizeof(h1))!=0 ){ return 1; /* Dirty read */ - } + } if( h1.isInit==0 ){ return 1; /* Malformed header - probably all zeros */ } @@ -60436,7 +70172,7 @@ static int walIndexTryHdr(Wal *pWal, int *pChanged){ ** changed by this operation. If pWal->hdr is unchanged, set *pChanged ** to 0. ** -** If the wal-index header is successfully read, return SQLITE_OK. +** If the wal-index header is successfully read, return SQLITE_OK. ** Otherwise an SQLite error code. */ static int walIndexReadHdr(Wal *pWal, int *pChanged){ @@ -60444,7 +70180,7 @@ static int walIndexReadHdr(Wal *pWal, int *pChanged){ int badHdr; /* True if a header read failed */ volatile u32 *page0; /* Chunk of wal-index containing header */ - /* Ensure that page 0 of the wal-index (the page that contains the + /* Ensure that page 0 of the wal-index (the page that contains the ** wal-index header) is mapped. Return early if an error occurs here. */ assert( pChanged ); @@ -60476,7 +70212,7 @@ static int walIndexReadHdr(Wal *pWal, int *pChanged){ /* If the first page of the wal-index has been mapped, try to read the ** wal-index header immediately, without holding any lock. This usually - ** works, but may fail if the wal-index header is corrupt or currently + ** works, but may fail if the wal-index header is corrupt or currently ** being modified by another thread or process. */ badHdr = (page0 ? walIndexTryHdr(pWal, pChanged) : 1); @@ -60484,28 +70220,40 @@ static int walIndexReadHdr(Wal *pWal, int *pChanged){ /* If the first attempt failed, it might have been due to a race ** with a writer. So get a WRITE lock and try again. */ - assert( badHdr==0 || pWal->writeLock==0 ); if( badHdr ){ if( pWal->bShmUnreliable==0 && (pWal->readOnly & WAL_SHM_RDONLY) ){ if( SQLITE_OK==(rc = walLockShared(pWal, WAL_WRITE_LOCK)) ){ walUnlockShared(pWal, WAL_WRITE_LOCK); rc = SQLITE_READONLY_RECOVERY; } - }else if( SQLITE_OK==(rc = walLockExclusive(pWal, WAL_WRITE_LOCK, 1)) ){ - pWal->writeLock = 1; - if( SQLITE_OK==(rc = walIndexPage(pWal, 0, &page0)) ){ - badHdr = walIndexTryHdr(pWal, pChanged); - if( badHdr ){ - /* If the wal-index header is still malformed even while holding - ** a WRITE lock, it can only mean that the header is corrupted and - ** needs to be reconstructed. So run recovery to do exactly that. - */ - rc = walIndexRecover(pWal); - *pChanged = 1; + }else{ + int bWriteLock = pWal->writeLock; + if( bWriteLock + || SQLITE_OK==(rc = walLockExclusive(pWal, WAL_WRITE_LOCK, 1)) + ){ + /* If the write-lock was just obtained, set writeLock to 2 instead of + ** the usual 1. This causes walIndexPage() to behave as if the + ** write-lock were held (so that it allocates new pages as required), + ** and walHandleException() to unlock the write-lock if a SEH exception + ** is thrown. */ + if( !bWriteLock ) pWal->writeLock = 2; + if( SQLITE_OK==(rc = walIndexPage(pWal, 0, &page0)) ){ + badHdr = walIndexTryHdr(pWal, pChanged); + if( badHdr ){ + /* If the wal-index header is still malformed even while holding + ** a WRITE lock, it can only mean that the header is corrupted and + ** needs to be reconstructed. So run recovery to do exactly that. + ** Disable blocking locks first. */ + walDisableBlocking(pWal); + rc = walIndexRecover(pWal); + *pChanged = 1; + } + } + if( bWriteLock==0 ){ + pWal->writeLock = 0; + walUnlockExclusive(pWal, WAL_WRITE_LOCK, 1); } } - pWal->writeLock = 0; - walUnlockExclusive(pWal, WAL_WRITE_LOCK, 1); } } @@ -60547,15 +70295,15 @@ static int walIndexReadHdr(Wal *pWal, int *pChanged){ ** ** The *-wal file has been read and an appropriate wal-index has been ** constructed in pWal->apWiData[] using heap memory instead of shared -** memory. +** memory. ** ** If this function returns SQLITE_OK, then the read transaction has -** been successfully opened. In this case output variable (*pChanged) +** been successfully opened. In this case output variable (*pChanged) ** is set to true before returning if the caller should discard the -** contents of the page cache before proceeding. Or, if it returns -** WAL_RETRY, then the heap memory wal-index has been discarded and -** the caller should retry opening the read transaction from the -** beginning (including attempting to map the *-shm file). +** contents of the page cache before proceeding. Or, if it returns +** WAL_RETRY, then the heap memory wal-index has been discarded and +** the caller should retry opening the read transaction from the +** beginning (including attempting to map the *-shm file). ** ** If an error occurs, an SQLite error code is returned. */ @@ -60652,7 +70400,9 @@ static int walBeginShmUnreliable(Wal *pWal, int *pChanged){ } /* Allocate a buffer to read frames into */ - szFrame = pWal->hdr.szPage + WAL_FRAME_HDRSIZE; + assert( (pWal->szPage & (pWal->szPage-1))==0 ); + assert( (pWal->szPage>=512 && pWal->szPage<=65536) || pWal->szPage==0 ); + szFrame = pWal->szPage + WAL_FRAME_HDRSIZE; aFrame = (u8 *)sqlite3_malloc64(szFrame); if( aFrame==0 ){ rc = SQLITE_NOMEM_BKPT; @@ -60666,8 +70416,8 @@ static int walBeginShmUnreliable(Wal *pWal, int *pChanged){ ** the caller. */ aSaveCksum[0] = pWal->hdr.aFrameCksum[0]; aSaveCksum[1] = pWal->hdr.aFrameCksum[1]; - for(iOffset=walFrameOffset(pWal->hdr.mxFrame+1, pWal->hdr.szPage); - iOffset+szFrame<=szWal; + for(iOffset=walFrameOffset(pWal->hdr.mxFrame+1, pWal->szPage); + iOffset+szFrame<=szWal; iOffset+=szFrame ){ u32 pgno; /* Database page number for frame */ @@ -60704,6 +70454,37 @@ static int walBeginShmUnreliable(Wal *pWal, int *pChanged){ return rc; } +/* +** The final argument passed to walTryBeginRead() is of type (int*). The +** caller should invoke walTryBeginRead as follows: +** +** int cnt = 0; +** do { +** rc = walTryBeginRead(..., &cnt); +** }while( rc==WAL_RETRY ); +** +** The final value of "cnt" is of no use to the caller. It is used by +** the implementation of walTryBeginRead() as follows: +** +** + Each time walTryBeginRead() is called, it is incremented. Once +** it reaches WAL_RETRY_PROTOCOL_LIMIT - indicating that walTryBeginRead() +** has many times been invoked and failed with WAL_RETRY - walTryBeginRead() +** returns SQLITE_PROTOCOL. +** +** + If SQLITE_ENABLE_SETLK_TIMEOUT is defined and walTryBeginRead() failed +** because a blocking lock timed out (SQLITE_BUSY_TIMEOUT from the OS +** layer), the WAL_RETRY_BLOCKED_MASK bit is set in "cnt". In this case +** the next invocation of walTryBeginRead() may omit an expected call to +** sqlite3OsSleep(). There has already been a delay when the previous call +** waited on a lock. +*/ +#define WAL_RETRY_PROTOCOL_LIMIT 100 +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT +# define WAL_RETRY_BLOCKED_MASK 0x10000000 +#else +# define WAL_RETRY_BLOCKED_MASK 0 +#endif + /* ** Attempt to start a read transaction. This might fail due to a race or ** other transient condition. When that happens, it returns WAL_RETRY to @@ -60715,10 +70496,10 @@ static int walBeginShmUnreliable(Wal *pWal, int *pChanged){ ** ** The useWal parameter is true to force the use of the WAL and disable ** the case where the WAL is bypassed because it has been completely -** checkpointed. If useWal==0 then this routine calls walIndexReadHdr() -** to make a copy of the wal-index header into pWal->hdr. If the -** wal-index header has changed, *pChanged is set to 1 (as an indication -** to the caller that the local page cache is obsolete and needs to be +** checkpointed. If useWal==0 then this routine calls walIndexReadHdr() +** to make a copy of the wal-index header into pWal->hdr. If the +** wal-index header has changed, *pChanged is set to 1 (as an indication +** to the caller that the local page cache is obsolete and needs to be ** flushed.) When useWal==1, the wal-index header is assumed to already ** be loaded and the pChanged parameter is unused. ** @@ -60733,7 +70514,7 @@ static int walBeginShmUnreliable(Wal *pWal, int *pChanged){ ** bad luck when there is lots of contention for the wal-index, but that ** possibility is so small that it can be safely neglected, we believe. ** -** On success, this routine obtains a read lock on +** On success, this routine obtains a read lock on ** WAL_READ_LOCK(pWal->readLock). The pWal->readLock integer is ** in the range 0 <= pWal->readLock < WAL_NREADER. If pWal->readLock==(-1) ** that means the Wal does not hold any read lock. The reader must not @@ -60754,13 +70535,12 @@ static int walBeginShmUnreliable(Wal *pWal, int *pChanged){ ** so it takes care to hold an exclusive lock on the corresponding ** WAL_READ_LOCK() while changing values. */ -static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){ +static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int *pCnt){ volatile WalCkptInfo *pInfo; /* Checkpoint information in wal-index */ - u32 mxReadMark; /* Largest aReadMark[] value */ - int mxI; /* Index of largest aReadMark[] value */ - int i; /* Loop counter */ int rc = SQLITE_OK; /* Return code */ - u32 mxFrame; /* Wal frame to lock to */ +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + int nBlockTmout = 0; +#endif assert( pWal->readLock<0 ); /* Not currently locked */ @@ -60771,27 +70551,47 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){ ** ** Circumstances that cause a RETRY should only last for the briefest ** instances of time. No I/O or other system calls are done while the - ** locks are held, so the locks should not be held for very long. But + ** locks are held, so the locks should not be held for very long. But ** if we are unlucky, another process that is holding a lock might get - ** paged out or take a page-fault that is time-consuming to resolve, + ** paged out or take a page-fault that is time-consuming to resolve, ** during the few nanoseconds that it is holding the lock. In that case, ** it might take longer than normal for the lock to free. ** ** After 5 RETRYs, we begin calling sqlite3OsSleep(). The first few ** calls to sqlite3OsSleep() have a delay of 1 microsecond. Really this ** is more of a scheduler yield than an actual delay. But on the 10th - ** an subsequent retries, the delays start becoming longer and longer, + ** an subsequent retries, the delays start becoming longer and longer, ** so that on the 100th (and last) RETRY we delay for 323 milliseconds. ** The total delay time before giving up is less than 10 seconds. */ - if( cnt>5 ){ + (*pCnt)++; + if( *pCnt>5 ){ int nDelay = 1; /* Pause time in microseconds */ - if( cnt>100 ){ + int cnt = (*pCnt & ~WAL_RETRY_BLOCKED_MASK); + if( cnt>WAL_RETRY_PROTOCOL_LIMIT ){ VVA_ONLY( pWal->lockError = 1; ) return SQLITE_PROTOCOL; } - if( cnt>=10 ) nDelay = (cnt-9)*(cnt-9)*39; + if( *pCnt>=10 ) nDelay = (cnt-9)*(cnt-9)*39; +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + /* In SQLITE_ENABLE_SETLK_TIMEOUT builds, configure the file-descriptor + ** to block for locks for approximately nDelay us. This affects three + ** locks: (a) the shared lock taken on the DMS slot in os_unix.c (if + ** using os_unix.c), (b) the WRITER lock taken in walIndexReadHdr() if the + ** first attempted read fails, and (c) the shared lock taken on the + ** read-mark. + ** + ** If the previous call failed due to an SQLITE_BUSY_TIMEOUT error, + ** then sleep for the minimum of 1us. The previous call already provided + ** an extra delay while it was blocking on the lock. + */ + nBlockTmout = (nDelay+998) / 1000; + if( !useWal && walEnableBlockingMs(pWal, nBlockTmout) ){ + if( *pCnt & WAL_RETRY_BLOCKED_MASK ) nDelay = 1; + } +#endif sqlite3OsSleep(pWal->pVfs, nDelay); + *pCnt &= ~WAL_RETRY_BLOCKED_MASK; } if( !useWal ){ @@ -60799,6 +70599,12 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){ if( pWal->bShmUnreliable==0 ){ rc = walIndexReadHdr(pWal, pChanged); } +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + if( rc==SQLITE_BUSY_TIMEOUT ){ + rc = SQLITE_BUSY; + *pCnt |= WAL_RETRY_BLOCKED_MASK; + } +#endif if( rc==SQLITE_BUSY ){ /* If there is not a recovery running in another thread or process ** then convert BUSY errors to WAL_RETRY. If recovery is known to @@ -60808,12 +70614,13 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){ ** WAL_RETRY this routine will be called again and will probably be ** right on the second iteration. */ + (void)walEnableBlocking(pWal); if( pWal->apWiData[0]==0 ){ /* This branch is taken when the xShmMap() method returns SQLITE_BUSY. ** We assume this is a transient condition, so return WAL_RETRY. The - ** xShmMap() implementation used by the default unix and win32 VFS - ** modules may return SQLITE_BUSY due to a race condition in the - ** code that determines whether or not the shared-memory region + ** xShmMap() implementation used by the default unix and win32 VFS + ** modules may return SQLITE_BUSY due to a race condition in the + ** code that determines whether or not the shared-memory region ** must be zeroed before the requested page is returned. */ rc = WAL_RETRY; @@ -60824,6 +70631,7 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){ rc = SQLITE_BUSY_RECOVERY; } } + walDisableBlocking(pWal); if( rc!=SQLITE_OK ){ return rc; } @@ -60835,145 +70643,211 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){ assert( pWal->nWiData>0 ); assert( pWal->apWiData[0]!=0 ); pInfo = walCkptInfo(pWal); - if( !useWal && pInfo->nBackfill==pWal->hdr.mxFrame + SEH_INJECT_FAULT; + { + u32 mxReadMark; /* Largest aReadMark[] value */ + int mxI; /* Index of largest aReadMark[] value */ + int i; /* Loop counter */ + u32 mxFrame; /* Wal frame to lock to */ + if( !useWal && AtomicLoad(&pInfo->nBackfill)==pWal->hdr.mxFrame #ifdef SQLITE_ENABLE_SNAPSHOT - && (pWal->pSnapshot==0 || pWal->hdr.mxFrame==0) + && ((pWal->bGetSnapshot==0 && pWal->pSnapshot==0) || pWal->hdr.mxFrame==0) #endif - ){ - /* The WAL has been completely backfilled (or it is empty). - ** and can be safely ignored. - */ - rc = walLockShared(pWal, WAL_READ_LOCK(0)); - walShmBarrier(pWal); - if( rc==SQLITE_OK ){ - if( memcmp((void *)walIndexHdr(pWal), &pWal->hdr, sizeof(WalIndexHdr)) ){ - /* It is not safe to allow the reader to continue here if frames - ** may have been appended to the log before READ_LOCK(0) was obtained. - ** When holding READ_LOCK(0), the reader ignores the entire log file, - ** which implies that the database file contains a trustworthy - ** snapshot. Since holding READ_LOCK(0) prevents a checkpoint from - ** happening, this is usually correct. - ** - ** However, if frames have been appended to the log (or if the log - ** is wrapped and written for that matter) before the READ_LOCK(0) - ** is obtained, that is not necessarily true. A checkpointer may - ** have started to backfill the appended frames but crashed before - ** it finished. Leaving a corrupt image in the database file. - */ - walUnlockShared(pWal, WAL_READ_LOCK(0)); - return WAL_RETRY; + ){ + /* The WAL has been completely backfilled (or it is empty). + ** and can be safely ignored. + */ + rc = walLockShared(pWal, WAL_READ_LOCK(0)); + walShmBarrier(pWal); + if( rc==SQLITE_OK ){ + if( memcmp((void *)walIndexHdr(pWal), &pWal->hdr,sizeof(WalIndexHdr)) ){ + /* It is not safe to allow the reader to continue here if frames + ** may have been appended to the log before READ_LOCK(0) was obtained. + ** When holding READ_LOCK(0), the reader ignores the entire log file, + ** which implies that the database file contains a trustworthy + ** snapshot. Since holding READ_LOCK(0) prevents a checkpoint from + ** happening, this is usually correct. + ** + ** However, if frames have been appended to the log (or if the log + ** is wrapped and written for that matter) before the READ_LOCK(0) + ** is obtained, that is not necessarily true. A checkpointer may + ** have started to backfill the appended frames but crashed before + ** it finished. Leaving a corrupt image in the database file. + */ + walUnlockShared(pWal, WAL_READ_LOCK(0)); + return WAL_RETRY; + } + pWal->readLock = 0; + return SQLITE_OK; + }else if( rc!=SQLITE_BUSY ){ + return rc; } - pWal->readLock = 0; - return SQLITE_OK; - }else if( rc!=SQLITE_BUSY ){ - return rc; } - } - /* If we get this far, it means that the reader will want to use - ** the WAL to get at content from recent commits. The job now is - ** to select one of the aReadMark[] entries that is closest to - ** but not exceeding pWal->hdr.mxFrame and lock that entry. - */ - mxReadMark = 0; - mxI = 0; - mxFrame = pWal->hdr.mxFrame; + /* If we get this far, it means that the reader will want to use + ** the WAL to get at content from recent commits. The job now is + ** to select one of the aReadMark[] entries that is closest to + ** but not exceeding pWal->hdr.mxFrame and lock that entry. + */ + mxReadMark = 0; + mxI = 0; + mxFrame = pWal->hdr.mxFrame; #ifdef SQLITE_ENABLE_SNAPSHOT - if( pWal->pSnapshot && pWal->pSnapshot->mxFramepSnapshot->mxFrame; - } -#endif - for(i=1; iaReadMark+i); - if( mxReadMark<=thisMark && thisMark<=mxFrame ){ - assert( thisMark!=READMARK_NOT_USED ); - mxReadMark = thisMark; - mxI = i; + if( pWal->pSnapshot && pWal->pSnapshot->mxFramepSnapshot->mxFrame; } - } - if( (pWal->readOnly & WAL_SHM_RDONLY)==0 - && (mxReadMarkaReadMark+i,mxFrame); + u32 thisMark = AtomicLoad(pInfo->aReadMark+i); SEH_INJECT_FAULT; + if( mxReadMark<=thisMark && thisMark<=mxFrame ){ + assert( thisMark!=READMARK_NOT_USED ); + mxReadMark = thisMark; mxI = i; - walUnlockExclusive(pWal, WAL_READ_LOCK(i), 1); - break; - }else if( rc!=SQLITE_BUSY ){ - return rc; } } - } - if( mxI==0 ){ - assert( rc==SQLITE_BUSY || (pWal->readOnly & WAL_SHM_RDONLY)!=0 ); - return rc==SQLITE_BUSY ? WAL_RETRY : SQLITE_READONLY_CANTINIT; - } + if( (pWal->readOnly & WAL_SHM_RDONLY)==0 + && (mxReadMarkaReadMark+i,mxFrame); + mxReadMark = mxFrame; + mxI = i; + walUnlockExclusive(pWal, WAL_READ_LOCK(i), 1); + break; + }else if( rc!=SQLITE_BUSY ){ + return rc; + } + } + } + if( mxI==0 ){ + assert( rc==SQLITE_BUSY || (pWal->readOnly & WAL_SHM_RDONLY)!=0 ); + return rc==SQLITE_BUSY ? WAL_RETRY : SQLITE_READONLY_CANTINIT; + } - rc = walLockShared(pWal, WAL_READ_LOCK(mxI)); - if( rc ){ - return rc==SQLITE_BUSY ? WAL_RETRY : rc; - } - /* Now that the read-lock has been obtained, check that neither the - ** value in the aReadMark[] array or the contents of the wal-index - ** header have changed. - ** - ** It is necessary to check that the wal-index header did not change - ** between the time it was read and when the shared-lock was obtained - ** on WAL_READ_LOCK(mxI) was obtained to account for the possibility - ** that the log file may have been wrapped by a writer, or that frames - ** that occur later in the log than pWal->hdr.mxFrame may have been - ** copied into the database by a checkpointer. If either of these things - ** happened, then reading the database with the current value of - ** pWal->hdr.mxFrame risks reading a corrupted snapshot. So, retry - ** instead. - ** - ** Before checking that the live wal-index header has not changed - ** since it was read, set Wal.minFrame to the first frame in the wal - ** file that has not yet been checkpointed. This client will not need - ** to read any frames earlier than minFrame from the wal file - they - ** can be safely read directly from the database file. - ** - ** Because a ShmBarrier() call is made between taking the copy of - ** nBackfill and checking that the wal-header in shared-memory still - ** matches the one cached in pWal->hdr, it is guaranteed that the - ** checkpointer that set nBackfill was not working with a wal-index - ** header newer than that cached in pWal->hdr. If it were, that could - ** cause a problem. The checkpointer could omit to checkpoint - ** a version of page X that lies before pWal->minFrame (call that version - ** A) on the basis that there is a newer version (version B) of the same - ** page later in the wal file. But if version B happens to like past - ** frame pWal->hdr.mxFrame - then the client would incorrectly assume - ** that it can read version A from the database file. However, since - ** we can guarantee that the checkpointer that set nBackfill could not - ** see any pages past pWal->hdr.mxFrame, this problem does not come up. - */ - pWal->minFrame = AtomicLoad(&pInfo->nBackfill)+1; - walShmBarrier(pWal); - if( AtomicLoad(pInfo->aReadMark+mxI)!=mxReadMark - || memcmp((void *)walIndexHdr(pWal), &pWal->hdr, sizeof(WalIndexHdr)) - ){ - walUnlockShared(pWal, WAL_READ_LOCK(mxI)); - return WAL_RETRY; - }else{ - assert( mxReadMark<=pWal->hdr.mxFrame ); - pWal->readLock = (i16)mxI; + (void)walEnableBlockingMs(pWal, nBlockTmout); + rc = walLockShared(pWal, WAL_READ_LOCK(mxI)); + walDisableBlocking(pWal); + if( rc ){ +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + if( rc==SQLITE_BUSY_TIMEOUT ){ + *pCnt |= WAL_RETRY_BLOCKED_MASK; + } +#else + assert( rc!=SQLITE_BUSY_TIMEOUT ); +#endif + assert((rc&0xFF)!=SQLITE_BUSY||rc==SQLITE_BUSY||rc==SQLITE_BUSY_TIMEOUT); + return (rc&0xFF)==SQLITE_BUSY ? WAL_RETRY : rc; + } + /* Now that the read-lock has been obtained, check that neither the + ** value in the aReadMark[] array or the contents of the wal-index + ** header have changed. + ** + ** It is necessary to check that the wal-index header did not change + ** between the time it was read and when the shared-lock was obtained + ** on WAL_READ_LOCK(mxI) was obtained to account for the possibility + ** that the log file may have been wrapped by a writer, or that frames + ** that occur later in the log than pWal->hdr.mxFrame may have been + ** copied into the database by a checkpointer. If either of these things + ** happened, then reading the database with the current value of + ** pWal->hdr.mxFrame risks reading a corrupted snapshot. So, retry + ** instead. + ** + ** Before checking that the live wal-index header has not changed + ** since it was read, set Wal.minFrame to the first frame in the wal + ** file that has not yet been checkpointed. This client will not need + ** to read any frames earlier than minFrame from the wal file - they + ** can be safely read directly from the database file. + ** + ** Because a ShmBarrier() call is made between taking the copy of + ** nBackfill and checking that the wal-header in shared-memory still + ** matches the one cached in pWal->hdr, it is guaranteed that the + ** checkpointer that set nBackfill was not working with a wal-index + ** header newer than that cached in pWal->hdr. If it were, that could + ** cause a problem. The checkpointer could omit to checkpoint + ** a version of page X that lies before pWal->minFrame (call that version + ** A) on the basis that there is a newer version (version B) of the same + ** page later in the wal file. But if version B happens to like past + ** frame pWal->hdr.mxFrame - then the client would incorrectly assume + ** that it can read version A from the database file. However, since + ** we can guarantee that the checkpointer that set nBackfill could not + ** see any pages past pWal->hdr.mxFrame, this problem does not come up. + */ + pWal->minFrame = AtomicLoad(&pInfo->nBackfill)+1; SEH_INJECT_FAULT; + walShmBarrier(pWal); + if( AtomicLoad(pInfo->aReadMark+mxI)!=mxReadMark + || memcmp((void *)walIndexHdr(pWal), &pWal->hdr, sizeof(WalIndexHdr)) + ){ + walUnlockShared(pWal, WAL_READ_LOCK(mxI)); + return WAL_RETRY; + }else{ + assert( mxReadMark<=pWal->hdr.mxFrame ); + pWal->readLock = (i16)mxI; + } } return rc; } #ifdef SQLITE_ENABLE_SNAPSHOT /* -** Attempt to reduce the value of the WalCkptInfo.nBackfillAttempted +** This function does the work of sqlite3WalSnapshotRecover(). +*/ +static int walSnapshotRecover( + Wal *pWal, /* WAL handle */ + void *pBuf1, /* Temp buffer pWal->szPage bytes in size */ + void *pBuf2 /* Temp buffer pWal->szPage bytes in size */ +){ + int szPage = (int)pWal->szPage; + int rc; + i64 szDb; /* Size of db file in bytes */ + + rc = sqlite3OsFileSize(pWal->pDbFd, &szDb); + if( rc==SQLITE_OK ){ + volatile WalCkptInfo *pInfo = walCkptInfo(pWal); + u32 i = pInfo->nBackfillAttempted; + for(i=pInfo->nBackfillAttempted; i>AtomicLoad(&pInfo->nBackfill); i--){ + WalHashLoc sLoc; /* Hash table location */ + u32 pgno; /* Page number in db file */ + i64 iDbOff; /* Offset of db file entry */ + i64 iWalOff; /* Offset of wal file entry */ + + rc = walHashGet(pWal, walFramePage(i), &sLoc); + if( rc!=SQLITE_OK ) break; + assert( i - sLoc.iZero - 1 >=0 ); + pgno = sLoc.aPgno[i-sLoc.iZero-1]; + iDbOff = (i64)(pgno-1) * szPage; + + if( iDbOff+szPage<=szDb ){ + iWalOff = walFrameOffset(i, szPage) + WAL_FRAME_HDRSIZE; + rc = sqlite3OsRead(pWal->pWalFd, pBuf1, szPage, iWalOff); + + if( rc==SQLITE_OK ){ + rc = sqlite3OsRead(pWal->pDbFd, pBuf2, szPage, iDbOff); + } + + if( rc!=SQLITE_OK || 0==memcmp(pBuf1, pBuf2, szPage) ){ + break; + } + } + + pInfo->nBackfillAttempted = i-1; + } + } + + return rc; +} + +/* +** Attempt to reduce the value of the WalCkptInfo.nBackfillAttempted ** variable so that older snapshots can be accessed. To do this, loop -** through all wal frames from nBackfillAttempted to (nBackfill+1), +** through all wal frames from nBackfillAttempted to (nBackfill+1), ** comparing their content to the corresponding page with the database ** file, if any. Set nBackfillAttempted to the frame number of the ** first frame for which the wal file content matches the db file. ** -** This is only really safe if the file-system is such that any page -** writes made by earlier checkpointers were atomic operations, which +** This is only really safe if the file-system is such that any page +** writes made by earlier checkpointers were atomic operations, which ** is not always true. It is also possible that nBackfillAttempted ** may be left set to a value larger than expected, if a wal frame ** contains content that duplicate of an earlier version of the same @@ -60989,49 +70863,21 @@ SQLITE_PRIVATE int sqlite3WalSnapshotRecover(Wal *pWal){ assert( pWal->readLock>=0 ); rc = walLockExclusive(pWal, WAL_CKPT_LOCK, 1); if( rc==SQLITE_OK ){ - volatile WalCkptInfo *pInfo = walCkptInfo(pWal); - int szPage = (int)pWal->szPage; - i64 szDb; /* Size of db file in bytes */ - - rc = sqlite3OsFileSize(pWal->pDbFd, &szDb); - if( rc==SQLITE_OK ){ - void *pBuf1 = sqlite3_malloc(szPage); - void *pBuf2 = sqlite3_malloc(szPage); - if( pBuf1==0 || pBuf2==0 ){ - rc = SQLITE_NOMEM; - }else{ - u32 i = pInfo->nBackfillAttempted; - for(i=pInfo->nBackfillAttempted; i>pInfo->nBackfill; i--){ - WalHashLoc sLoc; /* Hash table location */ - u32 pgno; /* Page number in db file */ - i64 iDbOff; /* Offset of db file entry */ - i64 iWalOff; /* Offset of wal file entry */ - - rc = walHashGet(pWal, walFramePage(i), &sLoc); - if( rc!=SQLITE_OK ) break; - pgno = sLoc.aPgno[i-sLoc.iZero]; - iDbOff = (i64)(pgno-1) * szPage; - - if( iDbOff+szPage<=szDb ){ - iWalOff = walFrameOffset(i, szPage) + WAL_FRAME_HDRSIZE; - rc = sqlite3OsRead(pWal->pWalFd, pBuf1, szPage, iWalOff); - - if( rc==SQLITE_OK ){ - rc = sqlite3OsRead(pWal->pDbFd, pBuf2, szPage, iDbOff); - } - - if( rc!=SQLITE_OK || 0==memcmp(pBuf1, pBuf2, szPage) ){ - break; - } - } - - pInfo->nBackfillAttempted = i-1; - } + void *pBuf1 = sqlite3_malloc(pWal->szPage); + void *pBuf2 = sqlite3_malloc(pWal->szPage); + if( pBuf1==0 || pBuf2==0 ){ + rc = SQLITE_NOMEM; + }else{ + pWal->ckptLock = 1; + SEH_TRY { + rc = walSnapshotRecover(pWal, pBuf1, pBuf2); } - - sqlite3_free(pBuf1); - sqlite3_free(pBuf2); + SEH_EXCEPT( rc = SQLITE_IOERR_IN_PAGE; ) + pWal->ckptLock = 0; } + + sqlite3_free(pBuf1); + sqlite3_free(pBuf2); walUnlockExclusive(pWal, WAL_CKPT_LOCK, 1); } @@ -61040,33 +70886,48 @@ SQLITE_PRIVATE int sqlite3WalSnapshotRecover(Wal *pWal){ #endif /* SQLITE_ENABLE_SNAPSHOT */ /* -** Begin a read transaction on the database. -** -** This routine used to be called sqlite3OpenSnapshot() and with good reason: -** it takes a snapshot of the state of the WAL and wal-index for the current -** instant in time. The current thread will continue to use this snapshot. -** Other threads might append new content to the WAL and wal-index but -** that extra content is ignored by the current thread. -** -** If the database contents have changes since the previous read -** transaction, then *pChanged is set to 1 before returning. The -** Pager layer will use this to know that its cache is stale and -** needs to be flushed. +** This function does the work of sqlite3WalBeginReadTransaction() (see +** below). That function simply calls this one inside an SEH_TRY{...} block. */ -SQLITE_PRIVATE int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){ +static int walBeginReadTransaction(Wal *pWal, int *pChanged){ int rc; /* Return code */ int cnt = 0; /* Number of TryBeginRead attempts */ - #ifdef SQLITE_ENABLE_SNAPSHOT + int ckptLock = 0; int bChanged = 0; WalIndexHdr *pSnapshot = pWal->pSnapshot; - if( pSnapshot && memcmp(pSnapshot, &pWal->hdr, sizeof(WalIndexHdr))!=0 ){ - bChanged = 1; +#endif + + assert( pWal->ckptLock==0 ); + assert( pWal->nSehTry>0 ); + +#ifdef SQLITE_ENABLE_SNAPSHOT + if( pSnapshot ){ + if( memcmp(pSnapshot, &pWal->hdr, sizeof(WalIndexHdr))!=0 ){ + bChanged = 1; + } + + /* It is possible that there is a checkpointer thread running + ** concurrent with this code. If this is the case, it may be that the + ** checkpointer has already determined that it will checkpoint + ** snapshot X, where X is later in the wal file than pSnapshot, but + ** has not yet set the pInfo->nBackfillAttempted variable to indicate + ** its intent. To avoid the race condition this leads to, ensure that + ** there is no checkpointer process by taking a shared CKPT lock + ** before checking pInfo->nBackfillAttempted. */ + (void)walEnableBlocking(pWal); + rc = walLockShared(pWal, WAL_CKPT_LOCK); + walDisableBlocking(pWal); + + if( rc!=SQLITE_OK ){ + return rc; + } + ckptLock = 1; } #endif do{ - rc = walTryBeginRead(pWal, pChanged, 0, ++cnt); + rc = walTryBeginRead(pWal, pChanged, 0, &cnt); }while( rc==WAL_RETRY ); testcase( (rc&0xff)==SQLITE_BUSY ); testcase( (rc&0xff)==SQLITE_IOERR ); @@ -61094,59 +70955,78 @@ SQLITE_PRIVATE int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){ assert( pWal->readLock>0 || pWal->hdr.mxFrame==0 ); assert( pInfo->aReadMark[pWal->readLock]<=pSnapshot->mxFrame ); - /* It is possible that there is a checkpointer thread running - ** concurrent with this code. If this is the case, it may be that the - ** checkpointer has already determined that it will checkpoint - ** snapshot X, where X is later in the wal file than pSnapshot, but - ** has not yet set the pInfo->nBackfillAttempted variable to indicate - ** its intent. To avoid the race condition this leads to, ensure that - ** there is no checkpointer process by taking a shared CKPT lock - ** before checking pInfo->nBackfillAttempted. - ** - ** TODO: Does the aReadMark[] lock prevent a checkpointer from doing - ** this already? - */ - rc = walLockShared(pWal, WAL_CKPT_LOCK); - - if( rc==SQLITE_OK ){ - /* Check that the wal file has not been wrapped. Assuming that it has - ** not, also check that no checkpointer has attempted to checkpoint any - ** frames beyond pSnapshot->mxFrame. If either of these conditions are - ** true, return SQLITE_ERROR_SNAPSHOT. Otherwise, overwrite pWal->hdr - ** with *pSnapshot and set *pChanged as appropriate for opening the - ** snapshot. */ - if( !memcmp(pSnapshot->aSalt, pWal->hdr.aSalt, sizeof(pWal->hdr.aSalt)) - && pSnapshot->mxFrame>=pInfo->nBackfillAttempted - ){ - assert( pWal->readLock>0 ); - memcpy(&pWal->hdr, pSnapshot, sizeof(WalIndexHdr)); - *pChanged = bChanged; - }else{ - rc = SQLITE_ERROR_SNAPSHOT; - } - - /* Release the shared CKPT lock obtained above. */ - walUnlockShared(pWal, WAL_CKPT_LOCK); - pWal->minFrame = 1; + /* Check that the wal file has not been wrapped. Assuming that it has + ** not, also check that no checkpointer has attempted to checkpoint any + ** frames beyond pSnapshot->mxFrame. If either of these conditions are + ** true, return SQLITE_ERROR_SNAPSHOT. Otherwise, overwrite pWal->hdr + ** with *pSnapshot and set *pChanged as appropriate for opening the + ** snapshot. */ + if( !memcmp(pSnapshot->aSalt, pWal->hdr.aSalt, sizeof(pWal->hdr.aSalt)) + && pSnapshot->mxFrame>=pInfo->nBackfillAttempted + ){ + assert( pWal->readLock>0 ); + memcpy(&pWal->hdr, pSnapshot, sizeof(WalIndexHdr)); + *pChanged = bChanged; + }else{ + rc = SQLITE_ERROR_SNAPSHOT; } + /* A client using a non-current snapshot may not ignore any frames + ** from the start of the wal file. This is because, for a system + ** where (minFrame < iSnapshot < maxFrame), a checkpointer may + ** have omitted to checkpoint a frame earlier than minFrame in + ** the file because there exists a frame after iSnapshot that + ** is the same database page. */ + pWal->minFrame = 1; if( rc!=SQLITE_OK ){ sqlite3WalEndReadTransaction(pWal); } } } + + /* Release the shared CKPT lock obtained above. */ + if( ckptLock ){ + assert( pSnapshot ); + walUnlockShared(pWal, WAL_CKPT_LOCK); + } #endif return rc; } +/* +** Begin a read transaction on the database. +** +** This routine used to be called sqlite3OpenSnapshot() and with good reason: +** it takes a snapshot of the state of the WAL and wal-index for the current +** instant in time. The current thread will continue to use this snapshot. +** Other threads might append new content to the WAL and wal-index but +** that extra content is ignored by the current thread. +** +** If the database contents have changes since the previous read +** transaction, then *pChanged is set to 1 before returning. The +** Pager layer will use this to know that its cache is stale and +** needs to be flushed. +*/ +SQLITE_PRIVATE int sqlite3WalBeginReadTransaction(Wal *pWal, int *pChanged){ + int rc; + SEH_TRY { + rc = walBeginReadTransaction(pWal, pChanged); + } + SEH_EXCEPT( rc = walHandleException(pWal); ) + return rc; +} + /* ** Finish with a read transaction. All this does is release the ** read-lock. */ SQLITE_PRIVATE void sqlite3WalEndReadTransaction(Wal *pWal){ - sqlite3WalEndWriteTransaction(pWal); +#ifndef SQLITE_ENABLE_SETLK_TIMEOUT + assert( pWal->writeLock==0 || pWal->readLock<0 ); +#endif if( pWal->readLock>=0 ){ + (void)sqlite3WalEndWriteTransaction(pWal); walUnlockShared(pWal, WAL_READ_LOCK(pWal->readLock)); pWal->readLock = -1; } @@ -61160,7 +71040,7 @@ SQLITE_PRIVATE void sqlite3WalEndReadTransaction(Wal *pWal){ ** Return SQLITE_OK if successful, or an error code if an error occurs. If an ** error does occur, the final value of *piRead is undefined. */ -SQLITE_PRIVATE int sqlite3WalFindFrame( +static int walFindFrame( Wal *pWal, /* WAL handle */ Pgno pgno, /* Database page number to read data for */ u32 *piRead /* OUT: Frame number (or zero) */ @@ -61175,8 +71055,8 @@ SQLITE_PRIVATE int sqlite3WalFindFrame( /* If the "last page" field of the wal-index header snapshot is 0, then ** no data will be read from the wal under any circumstances. Return early - ** in this case as an optimization. Likewise, if pWal->readLock==0, - ** then the WAL is ignored by the reader so return early, as if the + ** in this case as an optimization. Likewise, if pWal->readLock==0, + ** then the WAL is ignored by the reader so return early, as if the ** WAL were empty. */ if( iLast==0 || (pWal->readLock==0 && pWal->bShmUnreliable==0) ){ @@ -61189,9 +71069,9 @@ SQLITE_PRIVATE int sqlite3WalFindFrame( ** hash table (each hash table indexes up to HASHTABLE_NPAGE frames). ** ** This code might run concurrently to the code in walIndexAppend() - ** that adds entries to the wal-index (and possibly to this hash - ** table). This means the value just read from the hash - ** slot (aHash[iKey]) may have been added before or after the + ** that adds entries to the wal-index (and possibly to this hash + ** table). This means the value just read from the hash + ** slot (aHash[iKey]) may have been added before or after the ** current read transaction was opened. Values added after the ** read transaction was opened may have been written incorrectly - ** i.e. these slots may contain garbage data. However, we assume @@ -61199,13 +71079,13 @@ SQLITE_PRIVATE int sqlite3WalFindFrame( ** opened remain unmodified. ** ** For the reasons above, the if(...) condition featured in the inner - ** loop of the following block is more stringent that would be required + ** loop of the following block is more stringent that would be required ** if we had exclusive access to the hash-table: ** - ** (aPgno[iFrame]==pgno): + ** (aPgno[iFrame]==pgno): ** This condition filters out normal hash-table collisions. ** - ** (iFrame<=iLast): + ** (iFrame<=iLast): ** This condition filters out entries that were added to the hash ** table after the current read-transaction had started. */ @@ -61215,22 +71095,29 @@ SQLITE_PRIVATE int sqlite3WalFindFrame( int iKey; /* Hash slot index */ int nCollide; /* Number of hash collisions remaining */ int rc; /* Error code */ + u32 iH; rc = walHashGet(pWal, iHash, &sLoc); if( rc!=SQLITE_OK ){ return rc; } nCollide = HASHTABLE_NSLOT; - for(iKey=walHash(pgno); sLoc.aHash[iKey]; iKey=walNextHash(iKey)){ - u32 iFrame = sLoc.aHash[iKey] + sLoc.iZero; - if( iFrame<=iLast && iFrame>=pWal->minFrame - && sLoc.aPgno[sLoc.aHash[iKey]]==pgno ){ + iKey = walHash(pgno); + SEH_INJECT_FAULT; + while( (iH = AtomicLoad(&sLoc.aHash[iKey]))!=0 ){ + u32 iFrame = iH + sLoc.iZero; + if( iFrame<=iLast + && iFrame>=pWal->minFrame + && sLoc.aPgno[(iH-1)&(HASHTABLE_NPAGE-1)]==pgno + ){ assert( iFrame>iRead || CORRUPT_DB ); iRead = iFrame; } if( (nCollide--)==0 ){ + *piRead = 0; return SQLITE_CORRUPT_BKPT; } + iKey = walNextHash(iKey); } if( iRead ) break; } @@ -61257,6 +71144,30 @@ SQLITE_PRIVATE int sqlite3WalFindFrame( return SQLITE_OK; } +/* +** Search the wal file for page pgno. If found, set *piRead to the frame that +** contains the page. Otherwise, if pgno is not in the wal file, set *piRead +** to zero. +** +** Return SQLITE_OK if successful, or an error code if an error occurs. If an +** error does occur, the final value of *piRead is undefined. +** +** The difference between this function and walFindFrame() is that this +** function wraps walFindFrame() in an SEH_TRY{...} block. +*/ +SQLITE_PRIVATE int sqlite3WalFindFrame( + Wal *pWal, /* WAL handle */ + Pgno pgno, /* Database page number to read data for */ + u32 *piRead /* OUT: Frame number (or zero) */ +){ + int rc; + SEH_TRY { + rc = walFindFrame(pWal, pgno, piRead); + } + SEH_EXCEPT( rc = SQLITE_IOERR_IN_PAGE; ) + return rc; +} + /* ** Read the contents of frame iRead from the wal file into buffer pOut ** (which is nOut bytes in size). Return SQLITE_OK if successful, or an @@ -61279,7 +71190,7 @@ SQLITE_PRIVATE int sqlite3WalReadFrame( return sqlite3OsRead(pWal->pWalFd, pOut, (nOut>sz ? sz : nOut), iOffset); } -/* +/* ** Return the size of the database in pages (or zero, if unknown). */ SQLITE_PRIVATE Pgno sqlite3WalDbsize(Wal *pWal){ @@ -61290,7 +71201,7 @@ SQLITE_PRIVATE Pgno sqlite3WalDbsize(Wal *pWal){ } -/* +/* ** This function starts a write transaction on the WAL. ** ** A read transaction must have already been started by a prior call @@ -61306,6 +71217,16 @@ SQLITE_PRIVATE Pgno sqlite3WalDbsize(Wal *pWal){ SQLITE_PRIVATE int sqlite3WalBeginWriteTransaction(Wal *pWal){ int rc; +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + /* If the write-lock is already held, then it was obtained before the + ** read-transaction was even opened, making this call a no-op. + ** Return early. */ + if( pWal->writeLock ){ + assert( !memcmp(&pWal->hdr,(void*)pWal->apWiData[0],sizeof(WalIndexHdr)) ); + return SQLITE_OK; + } +#endif + /* Cannot start a write transaction without first holding a read ** transaction. */ assert( pWal->readLock>=0 ); @@ -61328,12 +71249,17 @@ SQLITE_PRIVATE int sqlite3WalBeginWriteTransaction(Wal *pWal){ ** time the read transaction on this connection was started, then ** the write is disallowed. */ - if( memcmp(&pWal->hdr, (void *)walIndexHdr(pWal), sizeof(WalIndexHdr))!=0 ){ + SEH_TRY { + if( memcmp(&pWal->hdr, (void *)walIndexHdr(pWal), sizeof(WalIndexHdr))!=0 ){ + rc = SQLITE_BUSY_SNAPSHOT; + } + } + SEH_EXCEPT( rc = SQLITE_IOERR_IN_PAGE; ) + + if( rc!=SQLITE_OK ){ walUnlockExclusive(pWal, WAL_WRITE_LOCK, 1); pWal->writeLock = 0; - rc = SQLITE_BUSY_SNAPSHOT; } - return rc; } @@ -61368,39 +71294,43 @@ SQLITE_PRIVATE int sqlite3WalUndo(Wal *pWal, int (*xUndo)(void *, Pgno), void *p if( ALWAYS(pWal->writeLock) ){ Pgno iMax = pWal->hdr.mxFrame; Pgno iFrame; - - /* Restore the clients cache of the wal-index header to the state it - ** was in before the client began writing to the database. - */ - memcpy(&pWal->hdr, (void *)walIndexHdr(pWal), sizeof(WalIndexHdr)); - for(iFrame=pWal->hdr.mxFrame+1; - ALWAYS(rc==SQLITE_OK) && iFrame<=iMax; - iFrame++ - ){ - /* This call cannot fail. Unless the page for which the page number - ** is passed as the second argument is (a) in the cache and - ** (b) has an outstanding reference, then xUndo is either a no-op - ** (if (a) is false) or simply expels the page from the cache (if (b) - ** is false). - ** - ** If the upper layer is doing a rollback, it is guaranteed that there - ** are no outstanding references to any page other than page 1. And - ** page 1 is never written to the log until the transaction is - ** committed. As a result, the call to xUndo may not fail. + SEH_TRY { + /* Restore the clients cache of the wal-index header to the state it + ** was in before the client began writing to the database. */ - assert( walFramePgno(pWal, iFrame)!=1 ); - rc = xUndo(pUndoCtx, walFramePgno(pWal, iFrame)); + memcpy(&pWal->hdr, (void *)walIndexHdr(pWal), sizeof(WalIndexHdr)); + + for(iFrame=pWal->hdr.mxFrame+1; + ALWAYS(rc==SQLITE_OK) && iFrame<=iMax; + iFrame++ + ){ + /* This call cannot fail. Unless the page for which the page number + ** is passed as the second argument is (a) in the cache and + ** (b) has an outstanding reference, then xUndo is either a no-op + ** (if (a) is false) or simply expels the page from the cache (if (b) + ** is false). + ** + ** If the upper layer is doing a rollback, it is guaranteed that there + ** are no outstanding references to any page other than page 1. And + ** page 1 is never written to the log until the transaction is + ** committed. As a result, the call to xUndo may not fail. + */ + assert( walFramePgno(pWal, iFrame)!=1 ); + rc = xUndo(pUndoCtx, walFramePgno(pWal, iFrame)); + } + if( iMax!=pWal->hdr.mxFrame ) walCleanupHash(pWal); } - if( iMax!=pWal->hdr.mxFrame ) walCleanupHash(pWal); + SEH_EXCEPT( rc = SQLITE_IOERR_IN_PAGE; ) + pWal->iReCksum = 0; } return rc; } -/* -** Argument aWalData must point to an array of WAL_SAVEPOINT_NDATA u32 -** values. This function populates the array with values required to -** "rollback" the write position of the WAL handle back to the current +/* +** Argument aWalData must point to an array of WAL_SAVEPOINT_NDATA u32 +** values. This function populates the array with values required to +** "rollback" the write position of the WAL handle back to the current ** point in the event of a savepoint rollback (via WalSavepointUndo()). */ SQLITE_PRIVATE void sqlite3WalSavepoint(Wal *pWal, u32 *aWalData){ @@ -61411,7 +71341,7 @@ SQLITE_PRIVATE void sqlite3WalSavepoint(Wal *pWal, u32 *aWalData){ aWalData[3] = pWal->nCkpt; } -/* +/* ** Move the write position of the WAL back to the point identified by ** the values in the aWalData[] array. aWalData must point to an array ** of WAL_SAVEPOINT_NDATA u32 values that has been previously populated @@ -61436,7 +71366,13 @@ SQLITE_PRIVATE int sqlite3WalSavepointUndo(Wal *pWal, u32 *aWalData){ pWal->hdr.mxFrame = aWalData[0]; pWal->hdr.aFrameCksum[0] = aWalData[1]; pWal->hdr.aFrameCksum[1] = aWalData[2]; - walCleanupHash(pWal); + SEH_TRY { + walCleanupHash(pWal); + } + SEH_EXCEPT( rc = SQLITE_IOERR_IN_PAGE; ) + if( pWal->iReCksum>pWal->hdr.mxFrame ){ + pWal->iReCksum = 0; + } } return rc; @@ -61486,7 +71422,7 @@ static int walRestartLog(Wal *pWal){ cnt = 0; do{ int notUsed; - rc = walTryBeginRead(pWal, ¬Used, 1, ++cnt); + rc = walTryBeginRead(pWal, ¬Used, 1, &cnt); }while( rc==WAL_RETRY ); assert( (rc&0xff)!=SQLITE_BUSY ); /* BUSY not possible when useWal==1 */ testcase( (rc&0xff)==SQLITE_IOERR ); @@ -61551,11 +71487,7 @@ static int walWriteOneFrame( int rc; /* Result code from subfunctions */ void *pData; /* Data actually written */ u8 aFrame[WAL_FRAME_HDRSIZE]; /* Buffer to assemble frame-header in */ -#if defined(SQLITE_HAS_CODEC) - if( (pData = sqlite3PagerCodec(pPage))==0 ) return SQLITE_NOMEM_BKPT; -#else pData = pPage->pData; -#endif walEncodeFrame(p->pWal, pPage->pgno, nTruncate, pData, aFrame); rc = walWriteToLog(p, aFrame, sizeof(aFrame), iOffset); if( rc ) return rc; @@ -61617,11 +71549,11 @@ static int walRewriteChecksums(Wal *pWal, u32 iLast){ return rc; } -/* +/* ** Write a set of frames to the log. The caller must hold the write-lock ** on the log file (obtained using sqlite3WalBeginWriteTransaction()). */ -SQLITE_PRIVATE int sqlite3WalFrames( +static int walFrames( Wal *pWal, /* Wal handle to write to */ int szPage, /* Database page-size in bytes */ PgHdr *pList, /* List of dirty pages to write */ @@ -61684,7 +71616,7 @@ SQLITE_PRIVATE int sqlite3WalFrames( walChecksumBytes(1, aWalHdr, WAL_HDRSIZE-2*4, 0, aCksum); sqlite3Put4byte(&aWalHdr[24], aCksum[0]); sqlite3Put4byte(&aWalHdr[28], aCksum[1]); - + pWal->szPage = szPage; pWal->hdr.bigEndCksum = SQLITE_BIGENDIAN; pWal->hdr.aFrameCksum[0] = aCksum[0]; @@ -61709,7 +71641,9 @@ SQLITE_PRIVATE int sqlite3WalFrames( if( rc ) return rc; } } - assert( (int)pWal->szPage==szPage ); + if( (int)pWal->szPage!=szPage ){ + return SQLITE_CORRUPT_BKPT; /* TH3 test case: cov1/corrupt155.test */ + } /* Setup information needed to write frames into the WAL */ w.pWal = pWal; @@ -61726,11 +71660,11 @@ SQLITE_PRIVATE int sqlite3WalFrames( /* Check if this page has already been written into the wal file by ** the current transaction. If so, overwrite the existing frame and - ** set Wal.writeLock to WAL_WRITELOCK_RECKSUM - indicating that + ** set Wal.writeLock to WAL_WRITELOCK_RECKSUM - indicating that ** checksums must be recomputed when the transaction is committed. */ if( iFirst && (p->pDirty || isCommit==0) ){ u32 iWrite = 0; - VVA_ONLY(rc =) sqlite3WalFindFrame(pWal, p->pgno, &iWrite); + VVA_ONLY(rc =) walFindFrame(pWal, p->pgno, &iWrite); assert( rc==SQLITE_OK || iWrite==0 ); if( iWrite>=iFirst ){ i64 iOff = walFrameOffset(iWrite, szPage) + WAL_FRAME_HDRSIZE; @@ -61738,11 +71672,7 @@ SQLITE_PRIVATE int sqlite3WalFrames( if( pWal->iReCksum==0 || iWriteiReCksum ){ pWal->iReCksum = iWrite; } -#if defined(SQLITE_HAS_CODEC) - if( (pData = sqlite3PagerCodec(p))==0 ) return SQLITE_NOMEM; -#else pData = p->pData; -#endif rc = sqlite3OsWrite(pWal->pWalFd, pData, szPage, iOff); if( rc ) return rc; p->flags &= ~PGHDR_WAL_APPEND; @@ -61792,6 +71722,7 @@ SQLITE_PRIVATE int sqlite3WalFrames( if( rc ) return rc; iOffset += szFrame; nExtra++; + assert( pLast!=0 ); } } if( bSync ){ @@ -61813,7 +71744,7 @@ SQLITE_PRIVATE int sqlite3WalFrames( pWal->truncateOnCommit = 0; } - /* Append data to the wal-index. It is not necessary to lock the + /* Append data to the wal-index. It is not necessary to lock the ** wal-index to do this as the SQLITE_SHM_WRITE lock held on the wal-index ** guarantees that there are no other writers, and no data that may ** be in use by existing readers is being overwritten. @@ -61824,6 +71755,7 @@ SQLITE_PRIVATE int sqlite3WalFrames( iFrame++; rc = walIndexAppend(pWal, iFrame, p->pgno); } + assert( pLast!=0 || nExtra==0 ); while( rc==SQLITE_OK && nExtra>0 ){ iFrame++; nExtra--; @@ -61851,7 +71783,30 @@ SQLITE_PRIVATE int sqlite3WalFrames( return rc; } -/* +/* +** Write a set of frames to the log. The caller must hold the write-lock +** on the log file (obtained using sqlite3WalBeginWriteTransaction()). +** +** The difference between this function and walFrames() is that this +** function wraps walFrames() in an SEH_TRY{...} block. +*/ +SQLITE_PRIVATE int sqlite3WalFrames( + Wal *pWal, /* Wal handle to write to */ + int szPage, /* Database page-size in bytes */ + PgHdr *pList, /* List of dirty pages to write */ + Pgno nTruncate, /* Database size after this commit */ + int isCommit, /* True if this is a commit */ + int sync_flags /* Flags to pass to OsSync() (or 0) */ +){ + int rc; + SEH_TRY { + rc = walFrames(pWal, szPage, pList, nTruncate, isCommit, sync_flags); + } + SEH_EXCEPT( rc = walHandleException(pWal); ) + return rc; +} + +/* ** This routine is called to implement sqlite3_wal_checkpoint() and ** related interfaces. ** @@ -61883,73 +71838,93 @@ SQLITE_PRIVATE int sqlite3WalCheckpoint( /* EVIDENCE-OF: R-62920-47450 The busy-handler callback is never invoked ** in the SQLITE_CHECKPOINT_PASSIVE mode. */ - assert( eMode!=SQLITE_CHECKPOINT_PASSIVE || xBusy==0 ); + assert( SQLITE_CHECKPOINT_NOOPSQLITE_CHECKPOINT_PASSIVE || xBusy==0 ); if( pWal->readOnly ) return SQLITE_READONLY; WALTRACE(("WAL%p: checkpoint begins\n", pWal)); - /* IMPLEMENTATION-OF: R-62028-47212 All calls obtain an exclusive - ** "checkpoint" lock on the database file. */ - rc = walLockExclusive(pWal, WAL_CKPT_LOCK, 1); - if( rc ){ - /* EVIDENCE-OF: R-10421-19736 If any other process is running a - ** checkpoint operation at the same time, the lock cannot be obtained and - ** SQLITE_BUSY is returned. - ** EVIDENCE-OF: R-53820-33897 Even if there is a busy-handler configured, - ** it will not be invoked in this case. - */ - testcase( rc==SQLITE_BUSY ); - testcase( xBusy!=0 ); - return rc; - } - pWal->ckptLock = 1; - - /* IMPLEMENTATION-OF: R-59782-36818 The SQLITE_CHECKPOINT_FULL, RESTART and - ** TRUNCATE modes also obtain the exclusive "writer" lock on the database - ** file. - ** - ** EVIDENCE-OF: R-60642-04082 If the writer lock cannot be obtained - ** immediately, and a busy-handler is configured, it is invoked and the - ** writer lock retried until either the busy-handler returns 0 or the - ** lock is successfully obtained. + /* Enable blocking locks, if possible. */ + sqlite3WalDb(pWal, db); + if( xBusy2 ) (void)walEnableBlocking(pWal); + + /* IMPLEMENTATION-OF: R-62028-47212 All calls obtain an exclusive + ** "checkpoint" lock on the database file. + ** EVIDENCE-OF: R-10421-19736 If any other process is running a + ** checkpoint operation at the same time, the lock cannot be obtained and + ** SQLITE_BUSY is returned. + ** EVIDENCE-OF: R-53820-33897 Even if there is a busy-handler configured, + ** it will not be invoked in this case. */ - if( eMode!=SQLITE_CHECKPOINT_PASSIVE ){ - rc = walBusyLock(pWal, xBusy, pBusyArg, WAL_WRITE_LOCK, 1); + if( eMode!=SQLITE_CHECKPOINT_NOOP ){ + rc = walLockExclusive(pWal, WAL_CKPT_LOCK, 1); + testcase( rc==SQLITE_BUSY ); + testcase( rc!=SQLITE_OK && xBusy2!=0 ); if( rc==SQLITE_OK ){ - pWal->writeLock = 1; - }else if( rc==SQLITE_BUSY ){ - eMode2 = SQLITE_CHECKPOINT_PASSIVE; - xBusy2 = 0; - rc = SQLITE_OK; - } - } + pWal->ckptLock = 1; - /* Read the wal-index header. */ - if( rc==SQLITE_OK ){ - rc = walIndexReadHdr(pWal, &isChanged); - if( isChanged && pWal->pDbFd->pMethods->iVersion>=3 ){ - sqlite3OsUnfetch(pWal->pDbFd, 0, 0); + /* IMPLEMENTATION-OF: R-59782-36818 The SQLITE_CHECKPOINT_FULL, RESTART + ** and TRUNCATE modes also obtain the exclusive "writer" lock on the + ** database file. + ** + ** EVIDENCE-OF: R-60642-04082 If the writer lock cannot be obtained + ** immediately, and a busy-handler is configured, it is invoked and the + ** writer lock retried until either the busy-handler returns 0 or the + ** lock is successfully obtained. + */ + if( eMode!=SQLITE_CHECKPOINT_PASSIVE ){ + rc = walBusyLock(pWal, xBusy2, pBusyArg, WAL_WRITE_LOCK, 1); + if( rc==SQLITE_OK ){ + pWal->writeLock = 1; + }else if( rc==SQLITE_BUSY ){ + eMode2 = SQLITE_CHECKPOINT_PASSIVE; + xBusy2 = 0; + rc = SQLITE_OK; + } + } } + }else{ + rc = SQLITE_OK; } - /* Copy data from the log to the database file. */ - if( rc==SQLITE_OK ){ - if( pWal->hdr.mxFrame && walPagesize(pWal)!=nBuf ){ - rc = SQLITE_CORRUPT_BKPT; - }else{ - rc = walCheckpoint(pWal, db, eMode2, xBusy2, pBusyArg, sync_flags, zBuf); + /* Read the wal-index header. */ + SEH_TRY { + if( rc==SQLITE_OK ){ + /* For a passive checkpoint, do not re-enable blocking locks after + ** reading the wal-index header. A passive checkpoint should not block + ** or invoke the busy handler. The only lock such a checkpoint may + ** attempt to obtain is a lock on a read-slot, and it should give up + ** immediately and do a partial checkpoint if it cannot obtain it. */ + walDisableBlocking(pWal); + rc = walIndexReadHdr(pWal, &isChanged); + if( eMode2>SQLITE_CHECKPOINT_PASSIVE ) (void)walEnableBlocking(pWal); + if( isChanged && pWal->pDbFd->pMethods->iVersion>=3 ){ + sqlite3OsUnfetch(pWal->pDbFd, 0, 0); + } } - /* If no error occurred, set the output variables. */ - if( rc==SQLITE_OK || rc==SQLITE_BUSY ){ - if( pnLog ) *pnLog = (int)pWal->hdr.mxFrame; - if( pnCkpt ) *pnCkpt = (int)(walCkptInfo(pWal)->nBackfill); + /* Copy data from the log to the database file. */ + if( rc==SQLITE_OK ){ + sqlite3FaultSim(660); + if( pWal->hdr.mxFrame && walPagesize(pWal)!=nBuf ){ + rc = SQLITE_CORRUPT_BKPT; + }else if( eMode2!=SQLITE_CHECKPOINT_NOOP ){ + rc = walCheckpoint(pWal, db, eMode2, xBusy2, pBusyArg, sync_flags,zBuf); + } + + /* If no error occurred, set the output variables. */ + if( rc==SQLITE_OK || rc==SQLITE_BUSY ){ + if( pnLog ) *pnLog = (int)pWal->hdr.mxFrame; + SEH_INJECT_FAULT; + if( pnCkpt ) *pnCkpt = (int)(walCkptInfo(pWal)->nBackfill); + } } } + SEH_EXCEPT( rc = walHandleException(pWal); ) if( isChanged ){ - /* If a new wal-index header was loaded before the checkpoint was + /* If a new wal-index header was loaded before the checkpoint was ** performed, then the pager-cache associated with pWal is now ** out of date. So zero the cached wal-index header to ensure that ** next time the pager opens a snapshot on this database it knows that @@ -61958,11 +71933,19 @@ SQLITE_PRIVATE int sqlite3WalCheckpoint( memset(&pWal->hdr, 0, sizeof(WalIndexHdr)); } + walDisableBlocking(pWal); + sqlite3WalDb(pWal, 0); + /* Release the locks. */ - sqlite3WalEndWriteTransaction(pWal); - walUnlockExclusive(pWal, WAL_CKPT_LOCK, 1); - pWal->ckptLock = 0; + (void)sqlite3WalEndWriteTransaction(pWal); + if( pWal->ckptLock ){ + walUnlockExclusive(pWal, WAL_CKPT_LOCK, 1); + pWal->ckptLock = 0; + } WALTRACE(("WAL%p: checkpoint %s\n", pWal, rc ? "failed" : "ok")); +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + if( rc==SQLITE_BUSY_TIMEOUT ) rc = SQLITE_BUSY; +#endif return (rc==SQLITE_OK && eMode!=eMode2 ? SQLITE_BUSY : rc); } @@ -61992,7 +71975,7 @@ SQLITE_PRIVATE int sqlite3WalCallback(Wal *pWal){ ** operation must occur while the pager is still holding the exclusive ** lock on the main database file. ** -** If op is one, then change from locking_mode=NORMAL into +** If op is one, then change from locking_mode=NORMAL into ** locking_mode=EXCLUSIVE. This means that the pWal->readLock must ** be released. Return 1 if the transition is made and 0 if the ** WAL is already in exclusive-locking mode - meaning that this @@ -62009,13 +71992,15 @@ SQLITE_PRIVATE int sqlite3WalExclusiveMode(Wal *pWal, int op){ assert( pWal->writeLock==0 ); assert( pWal->exclusiveMode!=WAL_HEAPMEMORY_MODE || op==-1 ); - /* pWal->readLock is usually set, but might be -1 if there was a - ** prior error while attempting to acquire are read-lock. This cannot + /* pWal->readLock is usually set, but might be -1 if there was a + ** prior error while attempting to acquire are read-lock. This cannot ** happen if the connection is actually in exclusive mode (as no xShmLock ** locks are taken in this case). Nor should the pager attempt to ** upgrade to exclusive-mode following such an error. */ +#ifndef SQLITE_USE_SEH assert( pWal->readLock>=0 || pWal->lockError ); +#endif assert( pWal->readLock>=0 || (op<=0 && pWal->exclusiveMode==0) ); if( op==0 ){ @@ -62041,10 +72026,10 @@ SQLITE_PRIVATE int sqlite3WalExclusiveMode(Wal *pWal, int op){ return rc; } -/* +/* ** Return true if the argument is non-NULL and the WAL module is using ** heap-memory for the wal-index. Otherwise, if the argument is NULL or the -** WAL module is using shared-memory, return false. +** WAL module is using shared-memory, return false. */ SQLITE_PRIVATE int sqlite3WalHeapMemory(Wal *pWal){ return (pWal && pWal->exclusiveMode==WAL_HEAPMEMORY_MODE ); @@ -62079,11 +72064,27 @@ SQLITE_PRIVATE int sqlite3WalSnapshotGet(Wal *pWal, sqlite3_snapshot **ppSnapsho /* Try to open on pSnapshot when the next read-transaction starts */ -SQLITE_PRIVATE void sqlite3WalSnapshotOpen(Wal *pWal, sqlite3_snapshot *pSnapshot){ - pWal->pSnapshot = (WalIndexHdr*)pSnapshot; +SQLITE_PRIVATE void sqlite3WalSnapshotOpen( + Wal *pWal, + sqlite3_snapshot *pSnapshot +){ + if( pSnapshot && ((WalIndexHdr*)pSnapshot)->iVersion==0 ){ + /* iVersion==0 means that this is a call to sqlite3_snapshot_get(). In + ** this case set the bGetSnapshot flag so that if the call to + ** sqlite3_snapshot_get() is about to read transaction on this wal + ** file, it does not take read-lock 0 if the wal file has been completely + ** checkpointed. Taking read-lock 0 would work, but then it would be + ** possible for a subsequent writer to destroy the snapshot even while + ** this connection is holding its read-transaction open. This is contrary + ** to user expectations, so we avoid it by not taking read-lock 0. */ + pWal->bGetSnapshot = 1; + }else{ + pWal->pSnapshot = (WalIndexHdr*)pSnapshot; + pWal->bGetSnapshot = 0; + } } -/* +/* ** Return a +ve value if snapshot p1 is newer than p2. A -ve value if ** p1 is older than p2 and zero if p1 and p2 are the same snapshot. */ @@ -62103,7 +72104,7 @@ SQLITE_API int sqlite3_snapshot_cmp(sqlite3_snapshot *p1, sqlite3_snapshot *p2){ /* ** The caller currently has a read transaction open on the database. ** This function takes a SHARED lock on the CHECKPOINTER slot and then -** checks if the snapshot passed as the second argument is still +** checks if the snapshot passed as the second argument is still ** available. If so, SQLITE_OK is returned. ** ** If the snapshot is not available, SQLITE_ERROR is returned. Or, if @@ -62113,16 +72114,19 @@ SQLITE_API int sqlite3_snapshot_cmp(sqlite3_snapshot *p1, sqlite3_snapshot *p2){ */ SQLITE_PRIVATE int sqlite3WalSnapshotCheck(Wal *pWal, sqlite3_snapshot *pSnapshot){ int rc; - rc = walLockShared(pWal, WAL_CKPT_LOCK); - if( rc==SQLITE_OK ){ - WalIndexHdr *pNew = (WalIndexHdr*)pSnapshot; - if( memcmp(pNew->aSalt, pWal->hdr.aSalt, sizeof(pWal->hdr.aSalt)) - || pNew->mxFramenBackfillAttempted - ){ - rc = SQLITE_ERROR_SNAPSHOT; - walUnlockShared(pWal, WAL_CKPT_LOCK); + SEH_TRY { + rc = walLockShared(pWal, WAL_CKPT_LOCK); + if( rc==SQLITE_OK ){ + WalIndexHdr *pNew = (WalIndexHdr*)pSnapshot; + if( memcmp(pNew->aSalt, pWal->hdr.aSalt, sizeof(pWal->hdr.aSalt)) + || pNew->mxFramenBackfillAttempted + ){ + rc = SQLITE_ERROR_SNAPSHOT; + walUnlockShared(pWal, WAL_CKPT_LOCK); + } } } + SEH_EXCEPT( rc = walHandleException(pWal); ) return rc; } @@ -62210,16 +72214,16 @@ SQLITE_PRIVATE sqlite3_file *sqlite3WalFile(Wal *pWal){ ** on Ptr(N) and its subpages have values greater than Key(N-1). And ** so forth. ** -** Finding a particular key requires reading O(log(M)) pages from the +** Finding a particular key requires reading O(log(M)) pages from the ** disk where M is the number of entries in the tree. ** -** In this implementation, a single file can hold one or more separate +** In this implementation, a single file can hold one or more separate ** BTrees. Each BTree is identified by the index of its root page. The ** key and data for any entry are combined to form the "payload". A ** fixed amount of payload can be carried directly on the database ** page. If the payload is larger than the preset amount then surplus ** bytes are stored on overflow pages. The payload for an entry -** and the preceding pointer are combined to form a "Cell". Each +** and the preceding pointer are combined to form a "Cell". Each ** page has a small header which contains the Ptr(N) pointer and other ** information such as the size of key and data. ** @@ -62245,7 +72249,7 @@ SQLITE_PRIVATE sqlite3_file *sqlite3WalFile(Wal *pWal){ ** 22 1 Min embedded payload fraction (must be 32) ** 23 1 Min leaf payload fraction (must be 32) ** 24 4 File change counter -** 28 4 Reserved for future use +** 28 4 The size of the database in pages ** 32 4 First freelist page ** 36 4 Number of freelist pages in the file ** 40 60 15 4-byte meta values passed to higher layers @@ -62349,11 +72353,11 @@ SQLITE_PRIVATE sqlite3_file *sqlite3WalFile(Wal *pWal){ ** contiguous or in order, but cell pointers are contiguous and in order. ** ** Cell content makes use of variable length integers. A variable -** length integer is 1 to 9 bytes where the lower 7 bits of each +** length integer is 1 to 9 bytes where the lower 7 bits of each ** byte are used. The integer consists of all bytes that have bit 8 set and ** the first byte with bit 8 clear. The most significant byte of the integer ** appears first. A variable-length integer may not be more than 9 bytes long. -** As a special case, all 8 bytes of the 9th byte are used as data. This +** As a special case, all 8 bits of the 9th byte are used as data. This ** allows a 64-bit integer to be encoded in 9 bytes. ** ** 0x00 becomes 0x00000000 @@ -62361,7 +72365,7 @@ SQLITE_PRIVATE sqlite3_file *sqlite3WalFile(Wal *pWal){ ** 0x81 0x00 becomes 0x00000080 ** 0x82 0x00 becomes 0x00000100 ** 0x80 0x7f becomes 0x0000007f -** 0x8a 0x91 0xd1 0xac 0x78 becomes 0x12345678 +** 0x81 0x91 0xd1 0xac 0x78 becomes 0x12345678 ** 0x81 0x81 0x81 0x81 0x01 becomes 0x10204081 ** ** Variable length integers are used for rowids and to hold the number of @@ -62422,7 +72426,7 @@ typedef struct CellInfo CellInfo; ** -DSQLITE_FILE_HEADER="..." on the compiler command-line. The ** header must be exactly 16 bytes including the zero-terminator so ** the string itself should be 15 characters long. If you change -** the header, then your custom library will not be able to read +** the header, then your custom library will not be able to read ** databases generated by the standard tools and the standard tools ** will not be able to read databases created by your custom library. */ @@ -62444,7 +72448,7 @@ typedef struct CellInfo CellInfo; ** page that has been loaded into memory. The information in this object ** is derived from the raw on-disk page content. ** -** As each database page is loaded into memory, the pager allocats an +** As each database page is loaded into memory, the pager allocates an ** instance of this object and zeros the first 8 bytes. (This is the ** "extra" information associated with each page of the pager.) ** @@ -62453,7 +72457,6 @@ typedef struct CellInfo CellInfo; */ struct MemPage { u8 isInit; /* True if previously initialized. MUST BE FIRST! */ - u8 bBusy; /* Prevent endless loops on corrupt database files */ u8 intKey; /* True if table b-trees. False for index b-trees */ u8 intKeyLeaf; /* True if the leaf of an intKey table */ Pgno pgno; /* Page number for this page */ @@ -62475,7 +72478,9 @@ struct MemPage { u8 *apOvfl[4]; /* Pointers to the body of overflow cells */ BtShared *pBt; /* Pointer to BtShared that this page is part of */ u8 *aData; /* Pointer to disk image of the page data */ - u8 *aDataEnd; /* One byte past the end of usable data */ + u8 *aDataEnd; /* One byte past the end of the entire page - not just + ** the usable space, the entire page. Used to prevent + ** corruption-induced buffer overflow. */ u8 *aCellIdx; /* The cell index area */ u8 *aDataOfst; /* Same as aData for leaves. aData+4 for interior */ DbPage *pDbPage; /* Pager page handle */ @@ -62485,7 +72490,7 @@ struct MemPage { /* ** A linked list of the following structures is stored at BtShared.pLock. -** Locks are added (or upgraded from READ_LOCK to WRITE_LOCK) when a cursor +** Locks are added (or upgraded from READ_LOCK to WRITE_LOCK) when a cursor ** is opened on the table with root page BtShared.iTable. Locks are removed ** from this list when a transaction is committed or rolled back, or when ** a btree handle is closed. @@ -62509,7 +72514,7 @@ struct BtLock { ** see the internals of this structure and only deals with pointers to ** this structure. ** -** For some database files, the same underlying database cache might be +** For some database files, the same underlying database cache might be ** shared between multiple connections. In that case, each connection ** has it own instance of this object. But each instance of this object ** points to the same BtShared object. The database cache and the @@ -62517,7 +72522,7 @@ struct BtLock { ** the BtShared object. ** ** All fields in this structure are accessed under sqlite3.mutex. -** The pBt pointer itself may not be changed while there exists cursors +** The pBt pointer itself may not be changed while there exists cursors ** in the referenced BtShared that point back to this Btree since those ** cursors have to go through this Btree to find their BtShared and ** they often do so without holding sqlite3.mutex. @@ -62531,9 +72536,12 @@ struct Btree { u8 hasIncrblobCur; /* True if there are one or more Incrblob cursors */ int wantToLock; /* Number of nested calls to sqlite3BtreeEnter() */ int nBackup; /* Number of backup operations reading this btree */ - u32 iDataVersion; /* Combines with pBt->pPager->iDataVersion */ + u32 iBDataVersion; /* Combines with pBt->pPager->iDataVersion */ Btree *pNext; /* List of other sharable Btrees from the same db */ Btree *pPrev; /* Back pointer of the same list */ +#ifdef SQLITE_DEBUG + u64 nSeek; /* Calls to sqlite3BtreeMovetoUnpacked() */ +#endif #ifndef SQLITE_OMIT_SHARED_CACHE BtLock lock; /* Object used to lock page 1 */ #endif @@ -62545,14 +72553,28 @@ struct Btree { ** If the shared-data extension is enabled, there may be multiple users ** of the Btree structure. At most one of these may open a write transaction, ** but any number may have active read transactions. +** +** These values must match SQLITE_TXN_NONE, SQLITE_TXN_READ, and +** SQLITE_TXN_WRITE */ #define TRANS_NONE 0 #define TRANS_READ 1 #define TRANS_WRITE 2 +#if TRANS_NONE!=SQLITE_TXN_NONE +# error wrong numeric code for no-transaction +#endif +#if TRANS_READ!=SQLITE_TXN_READ +# error wrong numeric code for read-transaction +#endif +#if TRANS_WRITE!=SQLITE_TXN_WRITE +# error wrong numeric code for write-transaction +#endif + + /* ** An instance of this object represents a single database file. -** +** ** A single database file can be in use at the same time by two ** or more database connections. When two or more connections are ** sharing the same database file, each connection has it own @@ -62562,7 +72584,7 @@ struct Btree { ** ** Fields in this structure are accessed under the BtShared.mutex ** mutex, except for nRef and pNext which are accessed under the -** global SQLITE_MUTEX_STATIC_MASTER mutex. The pPager field +** global SQLITE_MUTEX_STATIC_MAIN mutex. The pPager field ** may not be modified once it is initially set as long as nRef>0. ** The pSchema field may be set once under BtShared.mutex and ** thereafter is unchanged as long as nRef>0. @@ -62598,9 +72620,7 @@ struct BtShared { #endif u8 inTransaction; /* Transaction state */ u8 max1bytePayload; /* Maximum first byte of cell for a 1-byte payload */ -#ifdef SQLITE_HAS_CODEC - u8 optimalReserve; /* Desired amount of reserved space per page */ -#endif + u8 nReserveWanted; /* Desired number of extra bytes per page */ u16 btsFlags; /* Boolean parameters. See BTS_* macros below */ u16 maxLocal; /* Maximum local payload in non-LEAFDATA tables */ u16 minLocal; /* Minimum local payload in non-LEAFDATA tables */ @@ -62621,6 +72641,7 @@ struct BtShared { Btree *pWriter; /* Btree with currently open write transaction */ #endif u8 *pTmpSpace; /* Temp space sufficient to hold a single cell */ + int nPreformatSize; /* Size of last cell written by TransferRow() */ }; /* @@ -62660,6 +72681,12 @@ struct CellInfo { */ #define BTCURSOR_MAX_DEPTH 20 +/* +** Maximum amount of storage local to a database page, regardless of +** page size. +*/ +#define BT_MAX_LOCAL 65501 /* 65536 - 35 */ + /* ** A cursor is a pointer to a particular entry within a particular ** b-tree within a database file. @@ -62672,7 +72699,7 @@ struct CellInfo { ** particular database connection identified BtCursor.pBtree.db. ** ** Fields in this structure are accessed under the BtShared.mutex -** found at self->pBt->mutex. +** found at self->pBt->mutex. ** ** skipNext meaning: ** The meaning of skipNext depends on the value of eState: @@ -62720,15 +72747,16 @@ struct BtCursor { #define BTCF_WriteFlag 0x01 /* True if a write cursor */ #define BTCF_ValidNKey 0x02 /* True if info.nKey is valid */ #define BTCF_ValidOvfl 0x04 /* True if aOverflow is valid */ -#define BTCF_AtLast 0x08 /* Cursor is pointing ot the last entry */ +#define BTCF_AtLast 0x08 /* Cursor is pointing to the last entry */ #define BTCF_Incrblob 0x10 /* True if an incremental I/O handle */ #define BTCF_Multiple 0x20 /* Maybe another cursor on the same btree */ +#define BTCF_Pinned 0x40 /* Cursor is busy and cannot be moved */ /* ** Potential values for BtCursor.eState. ** ** CURSOR_INVALID: -** Cursor does not point to a valid entry. This can happen (for example) +** Cursor does not point to a valid entry. This can happen (for example) ** because the table is empty or because BtreeCursorFirst() has not been ** called. ** @@ -62741,9 +72769,9 @@ struct BtCursor { ** operation should be a no-op. ** ** CURSOR_REQUIRESEEK: -** The table that this cursor was opened on still exists, but has been +** The table that this cursor was opened on still exists, but has been ** modified since the cursor was last used. The cursor position is saved -** in variables BtCursor.pKey and BtCursor.nKey. When a cursor is in +** in variables BtCursor.pKey and BtCursor.nKey. When a cursor is in ** this state, restoreCursorPosition() can be called to attempt to ** seek the cursor to the saved position. ** @@ -62760,13 +72788,13 @@ struct BtCursor { #define CURSOR_REQUIRESEEK 3 #define CURSOR_FAULT 4 -/* +/* ** The database page the PENDING_BYTE occupies. This page is never used. */ -# define PENDING_BYTE_PAGE(pBt) PAGER_MJ_PGNO(pBt) +#define PENDING_BYTE_PAGE(pBt) ((Pgno)((PENDING_BYTE/((pBt)->pageSize))+1)) /* -** These macros define the location of the pointer-map entry for a +** These macros define the location of the pointer-map entry for a ** database page. The first argument to each is the number of usable ** bytes on each page of the database (often 1024). The second is the ** page number to look up in the pointer map. @@ -62801,10 +72829,10 @@ struct BtCursor { ** PTRMAP_ROOTPAGE: The database page is a root-page. The page-number is not ** used in this case. ** -** PTRMAP_FREEPAGE: The database page is an unused (free) page. The page-number +** PTRMAP_FREEPAGE: The database page is an unused (free) page. The page-number ** is not used in this case. ** -** PTRMAP_OVERFLOW1: The database page is the first page in a list of +** PTRMAP_OVERFLOW1: The database page is the first page in a list of ** overflow pages. The page number identifies the page that ** contains the cell with a pointer to this overflow page. ** @@ -62826,31 +72854,31 @@ struct BtCursor { */ #define btreeIntegrity(p) \ assert( p->pBt->inTransaction!=TRANS_NONE || p->pBt->nTransaction==0 ); \ - assert( p->pBt->inTransaction>=p->inTrans ); + assert( p->pBt->inTransaction>=p->inTrans ); /* ** The ISAUTOVACUUM macro is used within balance_nonroot() to determine ** if the database supports auto-vacuum or not. Because it is used -** within an expression that is an argument to another macro +** within an expression that is an argument to another macro ** (sqliteMallocRaw), it is not possible to use conditional compilation. ** So, this macro is defined instead. */ #ifndef SQLITE_OMIT_AUTOVACUUM -#define ISAUTOVACUUM (pBt->autoVacuum) +#define ISAUTOVACUUM(pBt) (pBt->autoVacuum) #else -#define ISAUTOVACUUM 0 +#define ISAUTOVACUUM(pBt) 0 #endif /* -** This structure is passed around through all the sanity checking routines -** in order to keep track of some global state information. +** This structure is passed around through all the PRAGMA integrity_check +** checking routines in order to keep track of some global state information. ** ** The aRef[] array is allocated so that there is 1 bit for each page in ** the database. As the integrity-check proceeds, for each page used in -** the database the corresponding bit is set. This allows integrity-check to -** detect pages that are used twice and orphaned pages (both of which +** the database the corresponding bit is set. This allows integrity-check to +** detect pages that are used twice and orphaned pages (both of which ** indicate corruption). */ typedef struct IntegrityCk IntegrityCk; @@ -62858,14 +72886,22 @@ struct IntegrityCk { BtShared *pBt; /* The tree being checked out */ Pager *pPager; /* The associated pager. Also accessible by pBt->pPager */ u8 *aPgRef; /* 1 bit per page in the db (see above) */ - Pgno nPage; /* Number of pages in the database */ + Pgno nCkPage; /* Pages in the database. 0 for partial check */ int mxErr; /* Stop accumulating errors when this reaches zero */ int nErr; /* Number of messages written to zErrMsg so far */ - int mallocFailed; /* A memory allocation error has occurred */ + int rc; /* SQLITE_OK, SQLITE_NOMEM, or SQLITE_INTERRUPT */ + u32 nStep; /* Number of steps into the integrity_check process */ const char *zPfx; /* Error message prefix */ - int v1, v2; /* Values for up to two %d fields in zPfx */ + Pgno v0; /* Value for first %u substitution in zPfx (root page) */ + Pgno v1; /* Value for second %u substitution in zPfx (current pg) */ + int v2; /* Value for third %d substitution in zPfx */ StrAccum errMsg; /* Accumulate the error message text here */ u32 *heap; /* Min-heap used for analyzing cell coverage */ + sqlite3 *db; /* Database connection running the check */ + i64 nRow; /* Number of rows visited in current tree */ +#ifdef SQLITE_DEBUG + u32 mxHeap; /* Maximum number of entries in the Min-heap */ +#endif }; /* @@ -62878,7 +72914,7 @@ struct IntegrityCk { /* ** get2byteAligned(), unlike get2byte(), requires that its argument point to a -** two-byte aligned address. get2bytea() is only used for accessing the +** two-byte aligned address. get2byteAligned() is only used for accessing the ** cell addresses in a btree header. */ #if SQLITE_BYTEORDER==4321 @@ -63055,14 +73091,14 @@ SQLITE_PRIVATE int sqlite3BtreeHoldsMutex(Btree *p){ ** ** There is a corresponding leave-all procedures. ** -** Enter the mutexes in accending order by BtShared pointer address +** Enter the mutexes in ascending order by BtShared pointer address ** to avoid the possibility of deadlock when two threads with ** two or more btrees in common both try to lock all their btrees ** at the same instant. */ static void SQLITE_NOINLINE btreeEnterAll(sqlite3 *db){ int i; - int skipOk = 1; + u8 skipOk = 1; Btree *p; assert( sqlite3_mutex_held(db->mutex) ); for(i=0; inDb; i++){ @@ -63129,6 +73165,7 @@ SQLITE_PRIVATE int sqlite3BtreeHoldsAllMutexes(sqlite3 *db){ SQLITE_PRIVATE int sqlite3SchemaMutexHeld(sqlite3 *db, int iDb, Schema *pSchema){ Btree *p; assert( db!=0 ); + if( db->pVfs==0 && db->nDb==0 ) return 1; if( pSchema ) iDb = sqlite3SchemaToIndex(db, pSchema); assert( iDb>=0 && iDbnDb ); if( !sqlite3_mutex_held(db->mutex) ) return 0; @@ -63166,10 +73203,10 @@ SQLITE_PRIVATE void sqlite3BtreeEnterAll(sqlite3 *db){ #ifndef SQLITE_OMIT_INCRBLOB /* -** Enter a mutex on a Btree given a cursor owned by that Btree. +** Enter a mutex on a Btree given a cursor owned by that Btree. ** -** These entry points are used by incremental I/O only. Enter() is required -** any time OMIT_SHARED_CACHE is not defined, regardless of whether or not +** These entry points are used by incremental I/O only. Enter() is required +** any time OMIT_SHARED_CACHE is not defined, regardless of whether or not ** the build is threadsafe. Leave() is only required by threadsafe builds. */ SQLITE_PRIVATE void sqlite3BtreeEnterCursor(BtCursor *pCur){ @@ -63239,7 +73276,7 @@ int sqlite3BtreeTrace=1; /* True to enable tracing */ #define BTALLOC_LE 2 /* Allocate any page <= the parameter */ /* -** Macro IfNotOmitAV(x) returns (x) if SQLITE_OMIT_AUTOVACUUM is not +** Macro IfNotOmitAV(x) returns (x) if SQLITE_OMIT_AUTOVACUUM is not ** defined, or 0 if it is. For example: ** ** bIncrVacuum = IfNotOmitAV(pBtShared->incrVacuum); @@ -63254,10 +73291,10 @@ int sqlite3BtreeTrace=1; /* True to enable tracing */ /* ** A list of BtShared objects that are eligible for participation ** in shared cache. This variable has file scope during normal builds, -** but the test harness needs to access it so we make it global for +** but the test harness needs to access it so we make it global for ** test builds. ** -** Access to this variable is protected by SQLITE_MUTEX_STATIC_MASTER. +** Access to this variable is protected by SQLITE_MUTEX_STATIC_MAIN. */ #ifdef SQLITE_TEST SQLITE_PRIVATE BtShared *SQLITE_WSD sqlite3SharedCacheList = 0; @@ -63289,7 +73326,7 @@ SQLITE_API int sqlite3_enable_shared_cache(int enable){ ** manipulate entries in the BtShared.pLock linked list used to store ** shared-cache table level locks. If the library is compiled with the ** shared-cache feature disabled, then there is only ever one user - ** of each BtShared structure and so this locking is not necessary. + ** of each BtShared structure and so this locking is not necessary. ** So define the lock related functions as no-ops. */ #define querySharedCacheTableLock(a,b,c) SQLITE_OK @@ -63300,6 +73337,17 @@ SQLITE_API int sqlite3_enable_shared_cache(int enable){ #define hasReadConflicts(a, b) 0 #endif +#ifdef SQLITE_DEBUG +/* +** Return and reset the seek counter for a Btree object. +*/ +SQLITE_PRIVATE sqlite3_uint64 sqlite3BtreeSeekCount(Btree *pBt){ + u64 n = pBt->nSeek; + pBt->nSeek = 0; + return n; +} +#endif + /* ** Implementation of the SQLITE_CORRUPT_PAGE() macro. Takes a single ** (MemPage*) as an argument. The (MemPage*) must not be NULL. @@ -63313,8 +73361,8 @@ SQLITE_API int sqlite3_enable_shared_cache(int enable){ int corruptPageError(int lineno, MemPage *p){ char *zMsg; sqlite3BeginBenignMalloc(); - zMsg = sqlite3_mprintf("database corruption page %d of %s", - (int)p->pgno, sqlite3PagerFilename(p->pBt->pPager, 0) + zMsg = sqlite3_mprintf("database corruption page %u of %s", + p->pgno, sqlite3PagerFilename(p->pBt->pPager, 0) ); sqlite3EndBenignMalloc(); if( zMsg ){ @@ -63328,21 +73376,60 @@ int corruptPageError(int lineno, MemPage *p){ # define SQLITE_CORRUPT_PAGE(pMemPage) SQLITE_CORRUPT_PGNO(pMemPage->pgno) #endif +/* Default value for SHARED_LOCK_TRACE macro if shared-cache is disabled +** or if the lock tracking is disabled. This is always the value for +** release builds. +*/ +#define SHARED_LOCK_TRACE(X,MSG,TAB,TYPE) /*no-op*/ + #ifndef SQLITE_OMIT_SHARED_CACHE +#if 0 +/* ^---- Change to 1 and recompile to enable shared-lock tracing +** for debugging purposes. +** +** Print all shared-cache locks on a BtShared. Debugging use only. +*/ +static void sharedLockTrace( + BtShared *pBt, + const char *zMsg, + int iRoot, + int eLockType +){ + BtLock *pLock; + if( iRoot>0 ){ + printf("%s-%p %u%s:", zMsg, pBt, iRoot, eLockType==READ_LOCK?"R":"W"); + }else{ + printf("%s-%p:", zMsg, pBt); + } + for(pLock=pBt->pLock; pLock; pLock=pLock->pNext){ + printf(" %p/%u%s", pLock->pBtree, pLock->iTable, + pLock->eLock==READ_LOCK ? "R" : "W"); + while( pLock->pNext && pLock->pBtree==pLock->pNext->pBtree ){ + pLock = pLock->pNext; + printf(",%u%s", pLock->iTable, pLock->eLock==READ_LOCK ? "R" : "W"); + } + } + printf("\n"); + fflush(stdout); +} +#undef SHARED_LOCK_TRACE +#define SHARED_LOCK_TRACE(X,MSG,TAB,TYPE) sharedLockTrace(X,MSG,TAB,TYPE) +#endif /* Shared-lock tracing */ + #ifdef SQLITE_DEBUG /* **** This function is only used as part of an assert() statement. *** ** -** Check to see if pBtree holds the required locks to read or write to the +** Check to see if pBtree holds the required locks to read or write to the ** table with root page iRoot. Return 1 if it does and 0 if not. ** -** For example, when writing to a table with root-page iRoot via +** For example, when writing to a table with root-page iRoot via ** Btree connection pBtree: ** ** assert( hasSharedCacheTableLock(pBtree, iRoot, 0, WRITE_LOCK) ); ** -** When writing to an index that resides in a sharable database, the +** When writing to an index that resides in a sharable database, the ** caller should have first obtained a lock specifying the root page of ** the corresponding table. This makes things a bit more complicated, ** as this module treats each table as a separate structure. To determine @@ -63364,7 +73451,7 @@ static int hasSharedCacheTableLock( BtLock *pLock; /* If this database is not shareable, or if the client is reading - ** and has the read-uncommitted flag set, then no lock is required. + ** and has the read-uncommitted flag set, then no lock is required. ** Return true immediately. */ if( (pBtree->sharable==0) @@ -63388,29 +73475,33 @@ static int hasSharedCacheTableLock( ** table. */ if( isIndex ){ HashElem *p; + int bSeen = 0; for(p=sqliteHashFirst(&pSchema->idxHash); p; p=sqliteHashNext(p)){ Index *pIdx = (Index *)sqliteHashData(p); - if( pIdx->tnum==(int)iRoot ){ - if( iTab ){ + if( pIdx->tnum==iRoot ){ + if( bSeen ){ /* Two or more indexes share the same root page. There must ** be imposter tables. So just return true. The assert is not ** useful in that case. */ return 1; } iTab = pIdx->pTable->tnum; + bSeen = 1; } } }else{ iTab = iRoot; } - /* Search for the required lock. Either a write-lock on root-page iTab, a + SHARED_LOCK_TRACE(pBtree->pBt,"hasLock",iRoot,eLockType); + + /* Search for the required lock. Either a write-lock on root-page iTab, a ** write-lock on the schema table, or (if the client is reading) a ** read-lock on iTab will suffice. Return 1 if any of these are found. */ for(pLock=pBtree->pBt->pLock; pLock; pLock=pLock->pNext){ - if( pLock->pBtree==pBtree + if( pLock->pBtree==pBtree && (pLock->iTable==iTab || (pLock->eLock==WRITE_LOCK && pLock->iTable==1)) - && pLock->eLock>=eLockType + && pLock->eLock>=eLockType ){ return 1; } @@ -63443,7 +73534,7 @@ static int hasSharedCacheTableLock( static int hasReadConflicts(Btree *pBtree, Pgno iRoot){ BtCursor *p; for(p=pBtree->pBt->pCursor; p; p=p->pNext){ - if( p->pgnoRoot==iRoot + if( p->pgnoRoot==iRoot && p->pBtree!=pBtree && 0==(p->pBtree->db->flags & SQLITE_ReadUncommit) ){ @@ -63455,7 +73546,7 @@ static int hasReadConflicts(Btree *pBtree, Pgno iRoot){ #endif /* #ifdef SQLITE_DEBUG */ /* -** Query to see if Btree handle p may obtain a lock of type eLock +** Query to see if Btree handle p may obtain a lock of type eLock ** (READ_LOCK or WRITE_LOCK) on the table with root-page iTab. Return ** SQLITE_OK if the lock may be obtained (by calling ** setSharedCacheTableLock()), or SQLITE_LOCKED if not. @@ -63468,14 +73559,14 @@ static int querySharedCacheTableLock(Btree *p, Pgno iTab, u8 eLock){ assert( eLock==READ_LOCK || eLock==WRITE_LOCK ); assert( p->db!=0 ); assert( !(p->db->flags&SQLITE_ReadUncommit)||eLock==WRITE_LOCK||iTab==1 ); - + /* If requesting a write-lock, then the Btree must have an open write - ** transaction on this file. And, obviously, for this to be so there + ** transaction on this file. And, obviously, for this to be so there ** must be an open write transaction on the file itself. */ assert( eLock==READ_LOCK || (p==pBt->pWriter && p->inTrans==TRANS_WRITE) ); assert( eLock==READ_LOCK || pBt->inTransaction==TRANS_WRITE ); - + /* This routine is a no-op if the shared-cache is not enabled */ if( !p->sharable ){ return SQLITE_OK; @@ -63490,7 +73581,7 @@ static int querySharedCacheTableLock(Btree *p, Pgno iTab, u8 eLock){ } for(pIter=pBt->pLock; pIter; pIter=pIter->pNext){ - /* The condition (pIter->eLock!=eLock) in the following if(...) + /* The condition (pIter->eLock!=eLock) in the following if(...) ** statement is a simplification of: ** ** (eLock==WRITE_LOCK || pIter->eLock==WRITE_LOCK) @@ -63517,7 +73608,7 @@ static int querySharedCacheTableLock(Btree *p, Pgno iTab, u8 eLock){ #ifndef SQLITE_OMIT_SHARED_CACHE /* ** Add a lock on the table with root-page iTable to the shared-btree used -** by Btree handle p. Parameter eLock must be either READ_LOCK or +** by Btree handle p. Parameter eLock must be either READ_LOCK or ** WRITE_LOCK. ** ** This function assumes the following: @@ -63529,7 +73620,7 @@ static int querySharedCacheTableLock(Btree *p, Pgno iTab, u8 eLock){ ** with the requested lock (i.e. querySharedCacheTableLock() has ** already been called and returned SQLITE_OK). ** -** SQLITE_OK is returned if the lock is added successfully. SQLITE_NOMEM +** SQLITE_OK is returned if the lock is added successfully. SQLITE_NOMEM ** is returned if a malloc attempt fails. */ static int setSharedCacheTableLock(Btree *p, Pgno iTable, u8 eLock){ @@ -63537,17 +73628,19 @@ static int setSharedCacheTableLock(Btree *p, Pgno iTable, u8 eLock){ BtLock *pLock = 0; BtLock *pIter; + SHARED_LOCK_TRACE(pBt,"setLock", iTable, eLock); + assert( sqlite3BtreeHoldsMutex(p) ); assert( eLock==READ_LOCK || eLock==WRITE_LOCK ); assert( p->db!=0 ); /* A connection with the read-uncommitted flag set will never try to ** obtain a read-lock using this function. The only read-lock obtained - ** by a connection in read-uncommitted mode is on the sqlite_master + ** by a connection in read-uncommitted mode is on the sqlite_schema ** table, and that lock is obtained in BtreeBeginTrans(). */ assert( 0==(p->db->flags&SQLITE_ReadUncommit) || eLock==WRITE_LOCK ); - /* This function should only be called on a sharable b-tree after it + /* This function should only be called on a sharable b-tree after it ** has been determined that no other b-tree holds a conflicting lock. */ assert( p->sharable ); assert( SQLITE_OK==querySharedCacheTableLock(p, iTable, eLock) ); @@ -63592,7 +73685,7 @@ static int setSharedCacheTableLock(Btree *p, Pgno iTable, u8 eLock){ ** Release all the table locks (locks obtained via calls to ** the setSharedCacheTableLock() procedure) held by Btree object p. ** -** This function assumes that Btree p has an open read or write +** This function assumes that Btree p has an open read or write ** transaction. If it does not, then the BTS_PENDING flag ** may be incorrectly cleared. */ @@ -63604,6 +73697,8 @@ static void clearAllSharedCacheTableLocks(Btree *p){ assert( p->sharable || 0==*ppIter ); assert( p->inTrans>0 ); + SHARED_LOCK_TRACE(pBt, "clearAllLocks", 0, 0); + while( *ppIter ){ BtLock *pLock = *ppIter; assert( (pBt->btsFlags & BTS_EXCLUSIVE)==0 || pBt->pWriter==pLock->pBtree ); @@ -63624,7 +73719,7 @@ static void clearAllSharedCacheTableLocks(Btree *p){ pBt->pWriter = 0; pBt->btsFlags &= ~(BTS_EXCLUSIVE|BTS_PENDING); }else if( pBt->nTransaction==2 ){ - /* This function is called when Btree p is concluding its + /* This function is called when Btree p is concluding its ** transaction. If there currently exists a writer, and p is not ** that writer, then the number of locks held by connections other ** than the writer must be about to drop to zero. In this case @@ -63642,6 +73737,9 @@ static void clearAllSharedCacheTableLocks(Btree *p){ */ static void downgradeAllSharedCacheTableLocks(Btree *p){ BtShared *pBt = p->pBt; + + SHARED_LOCK_TRACE(pBt, "downgradeLocks", 0, 0); + if( pBt->pWriter==p ){ BtLock *pLock; pBt->pWriter = 0; @@ -63670,7 +73768,7 @@ static int cursorHoldsMutex(BtCursor *p){ } /* Verify that the cursor and the BtShared agree about what is the current -** database connetion. This is important in shared-cache mode. If the database +** database connetion. This is important in shared-cache mode. If the database ** connection pointers get out-of-sync, it is possible for routines like ** btreeInitPage() to reference an stale connection pointer that references a ** a connection that has already closed. This routine is used inside assert() @@ -63722,7 +73820,7 @@ static void invalidateIncrblobCursors( int isClearTable /* True if all rows are being deleted */ ){ BtCursor *p; - if( pBtree->hasIncrblobCur==0 ) return; + assert( pBtree->hasIncrblobCur ); assert( sqlite3BtreeHoldsMutex(pBtree) ); pBtree->hasIncrblobCur = 0; for(p=pBtree->pBt->pCursor; p; p=p->pNext){ @@ -63741,8 +73839,8 @@ static void invalidateIncrblobCursors( #endif /* SQLITE_OMIT_INCRBLOB */ /* -** Set bit pgno of the BtShared.pHasContent bitvec. This is called -** when a page that previously contained data becomes a free-list leaf +** Set bit pgno of the BtShared.pHasContent bitvec. This is called +** when a page that previously contained data becomes a free-list leaf ** page. ** ** The BtShared.pHasContent bitvec exists to work around an obscure @@ -63768,7 +73866,7 @@ static void invalidateIncrblobCursors( ** may be lost. In the event of a rollback, it may not be possible ** to restore the database to its original configuration. ** -** The solution is the BtShared.pHasContent bitvec. Whenever a page is +** The solution is the BtShared.pHasContent bitvec. Whenever a page is ** moved to become a free-list leaf page, the corresponding bit is ** set in the bitvec. Whenever a leaf page is extracted from the free-list, ** optimization 2 above is omitted if the corresponding bit is already @@ -63799,7 +73897,7 @@ static int btreeSetHasContent(BtShared *pBt, Pgno pgno){ */ static int btreeGetHasContent(BtShared *pBt, Pgno pgno){ Bitvec *p = pBt->pHasContent; - return (p && (pgno>sqlite3BitvecSize(p) || sqlite3BitvecTest(p, pgno))); + return p && (pgno>sqlite3BitvecSize(p) || sqlite3BitvecTestNotNull(p, pgno)); } /* @@ -63829,13 +73927,13 @@ static void btreeReleaseAllCursorPages(BtCursor *pCur){ ** The cursor passed as the only argument must point to a valid entry ** when this function is called (i.e. have eState==CURSOR_VALID). This ** function saves the current cursor key in variables pCur->nKey and -** pCur->pKey. SQLITE_OK is returned if successful or an SQLite error +** pCur->pKey. SQLITE_OK is returned if successful or an SQLite error ** code otherwise. ** ** If the cursor is open on an intkey table, then the integer key ** (the rowid) is stored in pCur->nKey and pCur->pKey is left set to -** NULL. If the cursor is open on a non-intkey table, then pCur->pKey is -** set to point to a malloced buffer pCur->nKey bytes in size containing +** NULL. If the cursor is open on a non-intkey table, then pCur->pKey is +** set to point to a malloced buffer pCur->nKey bytes in size containing ** the key. */ static int saveCursorKey(BtCursor *pCur){ @@ -63851,12 +73949,12 @@ static int saveCursorKey(BtCursor *pCur){ /* For an index btree, save the complete key content. It is possible ** that the current key is corrupt. In that case, it is possible that ** the sqlite3VdbeRecordUnpack() function may overread the buffer by - ** up to the size of 1 varint plus 1 8-byte value when the cursor - ** position is restored. Hence the 17 bytes of padding allocated + ** up to the size of 1 varint plus 1 8-byte value when the cursor + ** position is restored. Hence the 17 bytes of padding allocated ** below. */ void *pKey; pCur->nKey = sqlite3BtreePayloadSize(pCur); - pKey = sqlite3Malloc( pCur->nKey + 9 + 8 ); + pKey = sqlite3Malloc( ((i64)pCur->nKey) + 9 + 8 ); if( pKey ){ rc = sqlite3BtreePayload(pCur, 0, (int)pCur->nKey, pKey); if( rc==SQLITE_OK ){ @@ -63874,11 +73972,11 @@ static int saveCursorKey(BtCursor *pCur){ } /* -** Save the current cursor position in the variables BtCursor.nKey +** Save the current cursor position in the variables BtCursor.nKey ** and BtCursor.pKey. The cursor's state is set to CURSOR_REQUIRESEEK. ** ** The caller must ensure that the cursor is valid (has eState==CURSOR_VALID) -** prior to calling this routine. +** prior to calling this routine. */ static int saveCursorPosition(BtCursor *pCur){ int rc; @@ -63887,6 +73985,9 @@ static int saveCursorPosition(BtCursor *pCur){ assert( 0==pCur->pKey ); assert( cursorHoldsMutex(pCur) ); + if( pCur->curFlags & BTCF_Pinned ){ + return SQLITE_CONSTRAINT_PINNED; + } if( pCur->eState==CURSOR_SKIPNEXT ){ pCur->eState = CURSOR_VALID; }else{ @@ -63914,7 +74015,7 @@ static int SQLITE_NOINLINE saveCursorsOnList(BtCursor*,Pgno,BtCursor*); ** routine is called just before cursor pExcept is used to modify the ** table, for example in BtreeDelete() or BtreeInsert(). ** -** If there are two or more cursors on the same btree, then all such +** If there are two or more cursors on the same btree, then all such ** cursors should have their BTCF_Multiple flag set. The btreeCursor() ** routine enforces that rule. This routine only needs to be called in ** the uncommon case when pExpect has the BTCF_Multiple flag set. @@ -63979,7 +74080,7 @@ SQLITE_PRIVATE void sqlite3BtreeClearCursor(BtCursor *pCur){ /* ** In this version of BtreeMoveto, pKey is a packed index record ** such as is generated by the OP_MakeRecord opcode. Unpack the -** record and then call BtreeMovetoUnpacked() to do the work. +** record and then call sqlite3BtreeIndexMoveto() to do the work. */ static int btreeMoveto( BtCursor *pCur, /* Cursor open on the btree to be searched */ @@ -63996,27 +74097,25 @@ static int btreeMoveto( assert( nKey==(i64)(int)nKey ); pIdxKey = sqlite3VdbeAllocUnpackedRecord(pKeyInfo); if( pIdxKey==0 ) return SQLITE_NOMEM_BKPT; - sqlite3VdbeRecordUnpack(pKeyInfo, (int)nKey, pKey, pIdxKey); + sqlite3VdbeRecordUnpack((int)nKey, pKey, pIdxKey); if( pIdxKey->nField==0 || pIdxKey->nField>pKeyInfo->nAllField ){ rc = SQLITE_CORRUPT_BKPT; - goto moveto_done; + }else{ + rc = sqlite3BtreeIndexMoveto(pCur, pIdxKey, pRes); } + sqlite3DbFree(pCur->pKeyInfo->db, pIdxKey); }else{ pIdxKey = 0; - } - rc = sqlite3BtreeMovetoUnpacked(pCur, pIdxKey, nKey, bias, pRes); -moveto_done: - if( pIdxKey ){ - sqlite3DbFree(pCur->pKeyInfo->db, pIdxKey); + rc = sqlite3BtreeTableMoveto(pCur, nKey, bias, pRes); } return rc; } /* ** Restore the cursor to the position it was in (or as close to as possible) -** when saveCursorPosition() was called. Note that this call deletes the +** when saveCursorPosition() was called. Note that this call deletes the ** saved position info stored by saveCursorPosition(), so there can be -** at most one effective restoreCursorPosition() call after each +** at most one effective restoreCursorPosition() call after each ** saveCursorPosition(). */ static int btreeRestoreCursorPosition(BtCursor *pCur){ @@ -64084,7 +74183,7 @@ SQLITE_PRIVATE BtCursor *sqlite3BtreeFakeValidCursor(void){ /* ** This routine restores a cursor back to its original position after it ** has been moved by some outside activity (such as a btree rebalance or -** a row having been deleted out from under the cursor). +** a row having been deleted out from under the cursor). ** ** On success, the *pDifferentRow parameter is false if the cursor is left ** pointing at exactly the same row. *pDifferntRow is the row the cursor @@ -64120,15 +74219,32 @@ SQLITE_PRIVATE int sqlite3BtreeCursorRestore(BtCursor *pCur, int *pDifferentRow) */ SQLITE_PRIVATE void sqlite3BtreeCursorHint(BtCursor *pCur, int eHintType, ...){ /* Used only by system that substitute their own storage engine */ +#ifdef SQLITE_DEBUG + if( ALWAYS(eHintType==BTREE_HINT_RANGE) ){ + va_list ap; + Expr *pExpr; + Walker w; + memset(&w, 0, sizeof(w)); + w.xExprCallback = sqlite3CursorRangeHintExprCheck; + va_start(ap, eHintType); + pExpr = va_arg(ap, Expr*); + w.u.aMem = va_arg(ap, Mem*); + va_end(ap); + assert( pExpr!=0 ); + assert( w.u.aMem!=0 ); + sqlite3WalkExpr(&w, pExpr); + } +#endif /* SQLITE_DEBUG */ } -#endif +#endif /* SQLITE_ENABLE_CURSOR_HINTS */ + /* ** Provide flag hints to the cursor. */ SQLITE_PRIVATE void sqlite3BtreeCursorHintFlags(BtCursor *pCur, unsigned x){ assert( x==BTREE_SEEK_EQ || x==BTREE_BULKLOAD || x==0 ); - pCur->hints = x; + pCur->hints = (u8)x; } @@ -64149,7 +74265,7 @@ static Pgno ptrmapPageno(BtShared *pBt, Pgno pgno){ if( pgno<2 ) return 0; nPagesPerMapPage = (pBt->usableSize/5)+1; iPtrMap = (pgno-2)/nPagesPerMapPage; - ret = (iPtrMap*nPagesPerMapPage) + 2; + ret = (iPtrMap*nPagesPerMapPage) + 2; if( ret==PENDING_BYTE_PAGE(pBt) ){ ret++; } @@ -64176,7 +74292,7 @@ static void ptrmapPut(BtShared *pBt, Pgno key, u8 eType, Pgno parent, int *pRC){ if( *pRC ) return; assert( sqlite3_mutex_held(pBt->mutex) ); - /* The master-journal page number must never be used as a pointer map page */ + /* The super-journal page number must never be used as a pointer map page */ assert( 0==PTRMAP_ISPAGE(pBt, PENDING_BYTE_PAGE(pBt)) ); assert( pBt->autoVacuum ); @@ -64206,7 +74322,7 @@ static void ptrmapPut(BtShared *pBt, Pgno key, u8 eType, Pgno parent, int *pRC){ pPtrmap = (u8 *)sqlite3PagerGetData(pDbPage); if( eType!=pPtrmap[offset] || get4byte(&pPtrmap[offset+1])!=parent ){ - TRACE(("PTRMAP_UPDATE: %d->(%d,%d)\n", key, eType, parent)); + TRACE(("PTRMAP_UPDATE: %u->(%u,%u)\n", key, eType, parent)); *pRC= rc = sqlite3PagerWrite(pDbPage); if( rc==SQLITE_OK ){ pPtrmap[offset] = eType; @@ -64315,6 +74431,25 @@ static SQLITE_NOINLINE void btreeParseCellAdjustSizeForOverflow( pInfo->nSize = (u16)(&pInfo->pPayload[pInfo->nLocal] - pCell) + 4; } +/* +** Given a record with nPayload bytes of payload stored within btree +** page pPage, return the number of bytes of payload stored locally. +*/ +static int btreePayloadToLocal(MemPage *pPage, i64 nPayload){ + int maxLocal; /* Maximum amount of payload held locally */ + maxLocal = pPage->maxLocal; + assert( nPayload>=0 ); + if( nPayload<=maxLocal ){ + return (int)nPayload; + }else{ + int minLocal; /* Minimum amount of payload held locally */ + int surplus; /* Overflow payload available for local storage */ + minLocal = pPage->minLocal; + surplus = (int)(minLocal +(nPayload - minLocal)%(pPage->pBt->usableSize-4)); + return (surplus <= maxLocal) ? surplus : minLocal; + } +} + /* ** The following routines are implementations of the MemPage.xParseCell() ** method. @@ -64352,7 +74487,7 @@ static void btreeParseCellPtr( CellInfo *pInfo /* Fill in this structure */ ){ u8 *pIter; /* For scanning through pCell */ - u32 nPayload; /* Number of bytes of cell payload */ + u64 nPayload; /* Number of bytes of cell payload */ u64 iKey; /* Extracted Key value */ assert( sqlite3_mutex_held(pPage->pBt->mutex) ); @@ -64374,6 +74509,7 @@ static void btreeParseCellPtr( do{ nPayload = (nPayload<<7) | (*++pIter & 0x7f); }while( (*pIter)>=0x80 && pIternKey); ** - ** The code is inlined to avoid a function call. + ** The code is inlined and the loop is unrolled for performance. + ** This routine is a high-runner. */ iKey = *pIter; if( iKey>=0x80 ){ - u8 *pEnd = &pIter[7]; - iKey &= 0x7f; - while(1){ - iKey = (iKey<<7) | (*++pIter & 0x7f); - if( (*pIter)<0x80 ) break; - if( pIter>=pEnd ){ - iKey = (iKey<<8) | *++pIter; - break; + u8 x; + iKey = (iKey<<7) ^ (x = *++pIter); + if( x>=0x80 ){ + iKey = (iKey<<7) ^ (x = *++pIter); + if( x>=0x80 ){ + iKey = (iKey<<7) ^ 0x10204000 ^ (x = *++pIter); + if( x>=0x80 ){ + iKey = (iKey<<7) ^ 0x4000 ^ (x = *++pIter); + if( x>=0x80 ){ + iKey = (iKey<<7) ^ 0x4000 ^ (x = *++pIter); + if( x>=0x80 ){ + iKey = (iKey<<7) ^ 0x4000 ^ (x = *++pIter); + if( x>=0x80 ){ + iKey = (iKey<<7) ^ 0x4000 ^ (x = *++pIter); + if( x>=0x80 ){ + iKey = (iKey<<8) ^ 0x8000 ^ (*++pIter); + } + } + } + } + } + }else{ + iKey ^= 0x204000; } + }else{ + iKey ^= 0x4000; } } pIter++; pInfo->nKey = *(i64*)&iKey; - pInfo->nPayload = nPayload; + pInfo->nPayload = (u32)nPayload; pInfo->pPayload = pIter; testcase( nPayload==pPage->maxLocal ); - testcase( nPayload==pPage->maxLocal+1 ); + testcase( nPayload==(u32)pPage->maxLocal+1 ); + assert( pPage->maxLocal <= BT_MAX_LOCAL ); if( nPayload<=pPage->maxLocal ){ /* This is the (easy) common case where the entire payload fits ** on the local page. No overflow is required. */ - pInfo->nSize = nPayload + (u16)(pIter - pCell); + pInfo->nSize = (u16)nPayload + (u16)(pIter - pCell); if( pInfo->nSize<4 ) pInfo->nSize = 4; pInfo->nLocal = (u16)nPayload; }else{ @@ -64439,12 +74594,14 @@ static void btreeParseCellPtrIndex( pInfo->nPayload = nPayload; pInfo->pPayload = pIter; testcase( nPayload==pPage->maxLocal ); - testcase( nPayload==pPage->maxLocal+1 ); + testcase( nPayload==(u32)pPage->maxLocal+1 ); + assert( nPayload>=0 ); + assert( pPage->maxLocal <= BT_MAX_LOCAL ); if( nPayload<=pPage->maxLocal ){ /* This is the (easy) common case where the entire payload fits ** on the local page. No overflow is required. */ - pInfo->nSize = nPayload + (u16)(pIter - pCell); + pInfo->nSize = (u16)nPayload + (u16)(pIter - pCell); if( pInfo->nSize<4 ) pInfo->nSize = 4; pInfo->nLocal = (u16)nPayload; }else{ @@ -64469,10 +74626,12 @@ static void btreeParseCell( ** the space used by the cell pointer. ** ** cellSizePtrNoPayload() => table internal nodes -** cellSizePtr() => all index nodes & table leaf nodes +** cellSizePtrTableLeaf() => table leaf nodes +** cellSizePtr() => index internal nodes +** cellSizeIdxLeaf() => index leaf nodes */ static u16 cellSizePtr(MemPage *pPage, u8 *pCell){ - u8 *pIter = pCell + pPage->childPtrSize; /* For looping over bytes of pCell */ + u8 *pIter = pCell + 4; /* For looping over bytes of pCell */ u8 *pEnd; /* End mark for a varint */ u32 nSize; /* Size value to return */ @@ -64485,6 +74644,7 @@ static u16 cellSizePtr(MemPage *pPage, u8 *pCell){ pPage->xParseCell(pPage, pCell, &debuginfo); #endif + assert( pPage->childPtrSize==4 ); nSize = *pIter; if( nSize>=0x80 ){ pEnd = &pIter[8]; @@ -64494,15 +74654,50 @@ static u16 cellSizePtr(MemPage *pPage, u8 *pCell){ }while( *(pIter)>=0x80 && pIterintKey ){ - /* pIter now points at the 64-bit integer key value, a variable length - ** integer. The following block moves pIter to point at the first byte - ** past the end of the key value. */ - pEnd = &pIter[9]; - while( (*pIter++)&0x80 && pItermaxLocal ); + testcase( nSize==(u32)pPage->maxLocal+1 ); + if( nSize<=pPage->maxLocal ){ + nSize += (u32)(pIter - pCell); + assert( nSize>4 ); + }else{ + int minLocal = pPage->minLocal; + nSize = minLocal + (nSize - minLocal) % (pPage->pBt->usableSize - 4); + testcase( nSize==pPage->maxLocal ); + testcase( nSize==(u32)pPage->maxLocal+1 ); + if( nSize>pPage->maxLocal ){ + nSize = minLocal; + } + nSize += 4 + (u16)(pIter - pCell); + } + assert( nSize==debuginfo.nSize || CORRUPT_DB ); + return (u16)nSize; +} +static u16 cellSizePtrIdxLeaf(MemPage *pPage, u8 *pCell){ + u8 *pIter = pCell; /* For looping over bytes of pCell */ + u8 *pEnd; /* End mark for a varint */ + u32 nSize; /* Size value to return */ + +#ifdef SQLITE_DEBUG + /* The value returned by this function should always be the same as + ** the (CellInfo.nSize) value found by doing a full parse of the + ** cell. If SQLITE_DEBUG is defined, an assert() at the bottom of + ** this function verifies that this invariant is not violated. */ + CellInfo debuginfo; + pPage->xParseCell(pPage, pCell, &debuginfo); +#endif + + assert( pPage->childPtrSize==0 ); + nSize = *pIter; + if( nSize>=0x80 ){ + pEnd = &pIter[8]; + nSize &= 0x7f; + do{ + nSize = (nSize<<7) | (*++pIter & 0x7f); + }while( *(pIter)>=0x80 && pItermaxLocal ); - testcase( nSize==pPage->maxLocal+1 ); + testcase( nSize==(u32)pPage->maxLocal+1 ); if( nSize<=pPage->maxLocal ){ nSize += (u32)(pIter - pCell); if( nSize<4 ) nSize = 4; @@ -64510,7 +74705,7 @@ static u16 cellSizePtr(MemPage *pPage, u8 *pCell){ int minLocal = pPage->minLocal; nSize = minLocal + (nSize - minLocal) % (pPage->pBt->usableSize - 4); testcase( nSize==pPage->maxLocal ); - testcase( nSize==pPage->maxLocal+1 ); + testcase( nSize==(u32)pPage->maxLocal+1 ); if( nSize>pPage->maxLocal ){ nSize = minLocal; } @@ -64540,6 +74735,58 @@ static u16 cellSizePtrNoPayload(MemPage *pPage, u8 *pCell){ assert( debuginfo.nSize==(u16)(pIter - pCell) || CORRUPT_DB ); return (u16)(pIter - pCell); } +static u16 cellSizePtrTableLeaf(MemPage *pPage, u8 *pCell){ + u8 *pIter = pCell; /* For looping over bytes of pCell */ + u8 *pEnd; /* End mark for a varint */ + u32 nSize; /* Size value to return */ + +#ifdef SQLITE_DEBUG + /* The value returned by this function should always be the same as + ** the (CellInfo.nSize) value found by doing a full parse of the + ** cell. If SQLITE_DEBUG is defined, an assert() at the bottom of + ** this function verifies that this invariant is not violated. */ + CellInfo debuginfo; + pPage->xParseCell(pPage, pCell, &debuginfo); +#endif + + nSize = *pIter; + if( nSize>=0x80 ){ + pEnd = &pIter[8]; + nSize &= 0x7f; + do{ + nSize = (nSize<<7) | (*++pIter & 0x7f); + }while( *(pIter)>=0x80 && pItermaxLocal ); + testcase( nSize==(u32)pPage->maxLocal+1 ); + if( nSize<=pPage->maxLocal ){ + nSize += (u32)(pIter - pCell); + if( nSize<4 ) nSize = 4; + }else{ + int minLocal = pPage->minLocal; + nSize = minLocal + (nSize - minLocal) % (pPage->pBt->usableSize - 4); + testcase( nSize==pPage->maxLocal ); + testcase( nSize==(u32)pPage->maxLocal+1 ); + if( nSize>pPage->maxLocal ){ + nSize = minLocal; + } + nSize += 4 + (u16)(pIter - pCell); + } + assert( nSize==debuginfo.nSize || CORRUPT_DB ); + return (u16)nSize; +} #ifdef SQLITE_DEBUG @@ -64553,7 +74800,7 @@ static u16 cellSize(MemPage *pPage, int iCell){ #ifndef SQLITE_OMIT_AUTOVACUUM /* ** The cell pCell is currently part of page pSrc but will ultimately be part -** of pPage. (pSrc and pPager are often the same.) If pCell contains a +** of pPage. (pSrc and pPage are often the same.) If pCell contains a ** pointer to an overflow page, insert an entry into the pointer-map for ** the overflow page that will be valid after pCell has been moved to pPage. */ @@ -64564,7 +74811,7 @@ static void ptrmapPutOvflPtr(MemPage *pPage, MemPage *pSrc, u8 *pCell,int *pRC){ pPage->xParseCell(pPage, pCell, &info); if( info.nLocalaDataEnd, pCell, pCell+info.nLocal) ){ + if( SQLITE_OVERFLOW(pSrc->aDataEnd, pCell, pCell+info.nLocal) ){ testcase( pSrc!=pPage ); *pRC = SQLITE_CORRUPT_BKPT; return; @@ -64602,14 +74849,14 @@ static int defragmentPage(MemPage *pPage, int nMaxFrag){ unsigned char *src; /* Source of content */ int iCellFirst; /* First allowable cell index */ int iCellLast; /* Last possible cell index */ + int iCellStart; /* First cell offset in input */ assert( sqlite3PagerIswriteable(pPage->pDbPage) ); assert( pPage->pBt!=0 ); assert( pPage->pBt->usableSize <= SQLITE_MAX_PAGE_SIZE ); assert( pPage->nOverflow==0 ); assert( sqlite3_mutex_held(pPage->pBt->mutex) ); - temp = 0; - src = data = pPage->aData; + data = pPage->aData; hdr = pPage->hdrOffset; cellOffset = pPage->cellOffset; nCell = pPage->nCell; @@ -64620,7 +74867,7 @@ static int defragmentPage(MemPage *pPage, int nMaxFrag){ /* This block handles pages with two or fewer free blocks and nMaxFrag ** or fewer fragmented bytes. In this case it is faster to move the ** two (or one) blocks of cells using memmove() and add the required - ** offsets to each pointer in the cell-pointer array than it is to + ** offsets to each pointer in the cell-pointer array than it is to ** reconstruct the entire page. */ if( (int)data[hdr+7]<=nMaxFrag ){ int iFree = get2byte(&data[hdr+1]); @@ -64662,41 +74909,39 @@ static int defragmentPage(MemPage *pPage, int nMaxFrag){ cbrk = usableSize; iCellLast = usableSize - 4; - for(i=0; iiCellLast ){ - return SQLITE_CORRUPT_PAGE(pPage); - } - assert( pc>=iCellFirst && pc<=iCellLast ); - size = pPage->xCellSize(pPage, &src[pc]); - cbrk -= size; - if( cbrkusableSize ){ - return SQLITE_CORRUPT_PAGE(pPage); - } - assert( cbrk+size<=usableSize && cbrk>=iCellFirst ); - testcase( cbrk+size==usableSize ); - testcase( pc+size==usableSize ); - put2byte(pAddr, cbrk); - if( temp==0 ){ - int x; - if( cbrk==pc ) continue; - temp = sqlite3PagerTempSpace(pPage->pBt->pPager); - x = get2byte(&data[hdr+5]); - memcpy(&temp[x], &data[x], (cbrk+size) - x); - src = temp; + iCellStart = get2byte(&data[hdr+5]); + if( nCell>0 ){ + temp = sqlite3PagerTempSpace(pPage->pBt->pPager); + memcpy(temp, data, usableSize); + src = temp; + for(i=0; iiCellLast ){ + return SQLITE_CORRUPT_PAGE(pPage); + } + assert( pc>=0 && pc<=iCellLast ); + size = pPage->xCellSize(pPage, &src[pc]); + cbrk -= size; + if( cbrkusableSize ){ + return SQLITE_CORRUPT_PAGE(pPage); + } + assert( cbrk+size<=usableSize && cbrk>=iCellStart ); + testcase( cbrk+size==usableSize ); + testcase( pc+size==usableSize ); + put2byte(pAddr, cbrk); + memcpy(&data[cbrk], &src[pc], size); } - memcpy(&data[cbrk], &src[pc], size); } data[hdr+7] = 0; - defragment_out: +defragment_out: assert( pPage->nFree>=0 ); if( data[hdr+7]+cbrk-iCellFirst!=pPage->nFree ){ return SQLITE_CORRUPT_PAGE(pPage); @@ -64728,7 +74973,8 @@ static u8 *pageFindSlot(MemPage *pPg, int nByte, int *pRc){ const int hdr = pPg->hdrOffset; /* Offset to page header */ u8 * const aData = pPg->aData; /* Page data */ int iAddr = hdr + 1; /* Address of ptr to pc */ - int pc = get2byte(&aData[iAddr]); /* Address of a free slot */ + u8 *pTmp = &aData[iAddr]; /* Temporary ptr into aData[] */ + int pc = get2byte(pTmp); /* Address of a free slot */ int x; /* Excess size of the slot */ int maxPC = pPg->pBt->usableSize - nByte; /* Max address for a usable slot */ int size; /* Size of the free slot */ @@ -64738,7 +74984,8 @@ static u8 *pageFindSlot(MemPage *pPg, int nByte, int *pRc){ /* EVIDENCE-OF: R-22710-53328 The third and fourth bytes of each ** freeblock form a big-endian integer which is the size of the freeblock ** in bytes, including the 4-byte header. */ - size = get2byte(&aData[pc+2]); + pTmp = &aData[pc+2]; + size = get2byte(pTmp); if( (x = size - nByte)>=0 ){ testcase( x==4 ); testcase( x==3 ); @@ -64751,6 +74998,7 @@ static u8 *pageFindSlot(MemPage *pPg, int nByte, int *pRc){ ** fragmented bytes within the page. */ memcpy(&aData[iAddr], &aData[pc], 2); aData[hdr+7] += (u8)x; + return &aData[pc]; }else if( x+pc > maxPC ){ /* This slot extends off the end of the usable part of the page */ *pRc = SQLITE_CORRUPT_PAGE(pPg); @@ -64763,10 +75011,11 @@ static u8 *pageFindSlot(MemPage *pPg, int nByte, int *pRc){ return &aData[pc + x]; } iAddr = pc; - pc = get2byte(&aData[pc]); - if( pc<=iAddr+size ){ + pTmp = &aData[pc]; + pc = get2byte(pTmp); + if( pc<=iAddr ){ if( pc ){ - /* The next slot in the chain is not past the end of the current slot */ + /* The next slot in the chain comes before the current slot */ *pRc = SQLITE_CORRUPT_PAGE(pPg); } return 0; @@ -64792,13 +75041,14 @@ static u8 *pageFindSlot(MemPage *pPg, int nByte, int *pRc){ ** allocation is being made in order to insert a new cell, so we will ** also end up needing a new cell pointer. */ -static int allocateSpace(MemPage *pPage, int nByte, int *pIdx){ +static SQLITE_INLINE int allocateSpace(MemPage *pPage, int nByte, int *pIdx){ const int hdr = pPage->hdrOffset; /* Local cache of pPage->hdrOffset */ u8 * const data = pPage->aData; /* Local cache of pPage->aData */ int top; /* First byte of cell content area */ int rc = SQLITE_OK; /* Integer return code */ + u8 *pTmp; /* Temp ptr into data[] */ int gap; /* First byte of gap between cell pointers and cell content */ - + assert( sqlite3PagerIswriteable(pPage->pDbPage) ); assert( pPage->pBt ); assert( sqlite3_mutex_held(pPage->pBt->mutex) ); @@ -64815,14 +75065,16 @@ static int allocateSpace(MemPage *pPage, int nByte, int *pIdx){ ** then the cell content offset of an empty page wants to be 65536. ** However, that integer is too large to be stored in a 2-byte unsigned ** integer, so a value of 0 is used in its place. */ - top = get2byte(&data[hdr+5]); - assert( top<=(int)pPage->pBt->usableSize ); /* Prevent by getAndInitPage() */ + pTmp = &data[hdr+5]; + top = get2byte(pTmp); if( gap>top ){ if( top==0 && pPage->pBt->usableSize==65536 ){ top = 65536; }else{ return SQLITE_CORRUPT_PAGE(pPage); } + }else if( top>(int)pPage->pBt->usableSize ){ + return SQLITE_CORRUPT_PAGE(pPage); } /* If there is enough space between gap and top for one more cell pointer, @@ -64835,9 +75087,14 @@ static int allocateSpace(MemPage *pPage, int nByte, int *pIdx){ if( (data[hdr+2] || data[hdr+1]) && gap+2<=top ){ u8 *pSpace = pageFindSlot(pPage, nByte, &rc); if( pSpace ){ - assert( pSpace>=data && (pSpace - data)<65536 ); - *pIdx = (int)(pSpace - data); - return SQLITE_OK; + int g2; + assert( pSpace+nByte<=data+pPage->pBt->usableSize ); + *pIdx = g2 = (int)(pSpace-data); + if( g2<=gap ){ + return SQLITE_CORRUPT_PAGE(pPage); + }else{ + return SQLITE_OK; + } }else if( rc ){ return rc; } @@ -64879,29 +75136,30 @@ static int allocateSpace(MemPage *pPage, int nByte, int *pIdx){ ** ** Even though the freeblock list was checked by btreeComputeFreeSpace(), ** that routine will not detect overlap between cells or freeblocks. Nor -** does it detect cells or freeblocks that encrouch into the reserved bytes +** does it detect cells or freeblocks that encroach into the reserved bytes ** at the end of the page. So do additional corruption checks inside this ** routine and return SQLITE_CORRUPT if any problems are found. */ -static int freeSpace(MemPage *pPage, u16 iStart, u16 iSize){ - u16 iPtr; /* Address of ptr to next freeblock */ - u16 iFreeBlk; /* Address of the next freeblock */ +static int freeSpace(MemPage *pPage, int iStart, int iSize){ + int iPtr; /* Address of ptr to next freeblock */ + int iFreeBlk; /* Address of the next freeblock */ u8 hdr; /* Page header size. 0 or 100 */ - u8 nFrag = 0; /* Reduction in fragmentation */ - u16 iOrigSize = iSize; /* Original value of iSize */ - u16 x; /* Offset to cell content area */ - u32 iEnd = iStart + iSize; /* First byte past the iStart buffer */ + int nFrag = 0; /* Reduction in fragmentation */ + int iOrigSize = iSize; /* Original value of iSize */ + int x; /* Offset to cell content area */ + int iEnd = iStart + iSize; /* First byte past the iStart buffer */ unsigned char *data = pPage->aData; /* Page content */ + u8 *pTmp; /* Temporary ptr into data[] */ assert( pPage->pBt!=0 ); assert( sqlite3PagerIswriteable(pPage->pDbPage) ); assert( CORRUPT_DB || iStart>=pPage->hdrOffset+6+pPage->childPtrSize ); - assert( CORRUPT_DB || iEnd <= pPage->pBt->usableSize ); + assert( CORRUPT_DB || iEnd <= (int)pPage->pBt->usableSize ); assert( sqlite3_mutex_held(pPage->pBt->mutex) ); assert( iSize>=4 ); /* Minimum cell size is 4 */ - assert( iStart<=pPage->pBt->usableSize-4 ); + assert( CORRUPT_DB || iStart<=(int)pPage->pBt->usableSize-4 ); - /* The list of freeblocks must be in ascending order. Find the + /* The list of freeblocks must be in ascending order. Find the ** spot on the list where iStart should be inserted. */ hdr = pPage->hdrOffset; @@ -64910,17 +75168,17 @@ static int freeSpace(MemPage *pPage, u16 iStart, u16 iSize){ iFreeBlk = 0; /* Shortcut for the case when the freelist is empty */ }else{ while( (iFreeBlk = get2byte(&data[iPtr]))pPage->pBt->usableSize-4 ){ + if( iFreeBlk>(int)pPage->pBt->usableSize-4 ){ /* TH3: corrupt081.100 */ return SQLITE_CORRUPT_PAGE(pPage); } - assert( iFreeBlk>iPtr || iFreeBlk==0 ); - + assert( iFreeBlk>iPtr || iFreeBlk==0 || CORRUPT_DB ); + /* At this point: ** iFreeBlk: First freeblock after iStart, or zero if none ** iPtr: The address of a pointer to iFreeBlk @@ -64931,13 +75189,13 @@ static int freeSpace(MemPage *pPage, u16 iStart, u16 iSize){ nFrag = iFreeBlk - iEnd; if( iEnd>iFreeBlk ) return SQLITE_CORRUPT_PAGE(pPage); iEnd = iFreeBlk + get2byte(&data[iFreeBlk+2]); - if( iEnd > pPage->pBt->usableSize ){ + if( iEnd > (int)pPage->pBt->usableSize ){ return SQLITE_CORRUPT_PAGE(pPage); } iSize = iEnd - iStart; iFreeBlk = get2byte(&data[iFreeBlk]); } - + /* If iPtr is another freeblock (that is, if iPtr is not the freelist ** pointer in the page header) then check to see if iStart should be ** coalesced onto the end of iPtr. @@ -64952,27 +75210,30 @@ static int freeSpace(MemPage *pPage, u16 iStart, u16 iSize){ } } if( nFrag>data[hdr+7] ) return SQLITE_CORRUPT_PAGE(pPage); - data[hdr+7] -= nFrag; + data[hdr+7] -= (u8)nFrag; + } + pTmp = &data[hdr+5]; + x = get2byte(pTmp); + if( pPage->pBt->btsFlags & BTS_FAST_SECURE ){ + /* Overwrite deleted information with zeros when the secure_delete + ** option is enabled */ + memset(&data[iStart], 0, iSize); } - x = get2byte(&data[hdr+5]); if( iStart<=x ){ /* The new freeblock is at the beginning of the cell content area, ** so just extend the cell content area rather than create another ** freelist entry */ - if( iStart=0 && iSize<=0xffff ); + put2byte(&data[iStart+2], (u16)iSize); } - if( pPage->pBt->btsFlags & BTS_FAST_SECURE ){ - /* Overwrite deleted information with zeros when the secure_delete - ** option is enabled */ - memset(&data[iStart], 0, iSize); - } - put2byte(&data[iStart], iFreeBlk); - put2byte(&data[iStart+2], iSize); pPage->nFree += iOrigSize; return SQLITE_OK; } @@ -64984,57 +75245,67 @@ static int freeSpace(MemPage *pPage, u16 iStart, u16 iSize){ ** Only the following combinations are supported. Anything different ** indicates a corrupt database files: ** -** PTF_ZERODATA -** PTF_ZERODATA | PTF_LEAF -** PTF_LEAFDATA | PTF_INTKEY -** PTF_LEAFDATA | PTF_INTKEY | PTF_LEAF +** PTF_ZERODATA (0x02, 2) +** PTF_LEAFDATA | PTF_INTKEY (0x05, 5) +** PTF_ZERODATA | PTF_LEAF (0x0a, 10) +** PTF_LEAFDATA | PTF_INTKEY | PTF_LEAF (0x0d, 13) */ static int decodeFlags(MemPage *pPage, int flagByte){ BtShared *pBt; /* A copy of pPage->pBt */ assert( pPage->hdrOffset==(pPage->pgno==1 ? 100 : 0) ); assert( sqlite3_mutex_held(pPage->pBt->mutex) ); - pPage->leaf = (u8)(flagByte>>3); assert( PTF_LEAF == 1<<3 ); - flagByte &= ~PTF_LEAF; - pPage->childPtrSize = 4-4*pPage->leaf; - pPage->xCellSize = cellSizePtr; pBt = pPage->pBt; - if( flagByte==(PTF_LEAFDATA | PTF_INTKEY) ){ - /* EVIDENCE-OF: R-07291-35328 A value of 5 (0x05) means the page is an - ** interior table b-tree page. */ - assert( (PTF_LEAFDATA|PTF_INTKEY)==5 ); - /* EVIDENCE-OF: R-26900-09176 A value of 13 (0x0d) means the page is a - ** leaf table b-tree page. */ - assert( (PTF_LEAFDATA|PTF_INTKEY|PTF_LEAF)==13 ); - pPage->intKey = 1; - if( pPage->leaf ){ + pPage->max1bytePayload = pBt->max1bytePayload; + if( flagByte>=(PTF_ZERODATA | PTF_LEAF) ){ + pPage->childPtrSize = 0; + pPage->leaf = 1; + if( flagByte==(PTF_LEAFDATA | PTF_INTKEY | PTF_LEAF) ){ pPage->intKeyLeaf = 1; + pPage->xCellSize = cellSizePtrTableLeaf; pPage->xParseCell = btreeParseCellPtr; + pPage->intKey = 1; + pPage->maxLocal = pBt->maxLeaf; + pPage->minLocal = pBt->minLeaf; + }else if( flagByte==(PTF_ZERODATA | PTF_LEAF) ){ + pPage->intKey = 0; + pPage->intKeyLeaf = 0; + pPage->xCellSize = cellSizePtrIdxLeaf; + pPage->xParseCell = btreeParseCellPtrIndex; + pPage->maxLocal = pBt->maxLocal; + pPage->minLocal = pBt->minLocal; }else{ + pPage->intKey = 0; + pPage->intKeyLeaf = 0; + pPage->xCellSize = cellSizePtrIdxLeaf; + pPage->xParseCell = btreeParseCellPtrIndex; + return SQLITE_CORRUPT_PAGE(pPage); + } + }else{ + pPage->childPtrSize = 4; + pPage->leaf = 0; + if( flagByte==(PTF_ZERODATA) ){ + pPage->intKey = 0; + pPage->intKeyLeaf = 0; + pPage->xCellSize = cellSizePtr; + pPage->xParseCell = btreeParseCellPtrIndex; + pPage->maxLocal = pBt->maxLocal; + pPage->minLocal = pBt->minLocal; + }else if( flagByte==(PTF_LEAFDATA | PTF_INTKEY) ){ pPage->intKeyLeaf = 0; pPage->xCellSize = cellSizePtrNoPayload; pPage->xParseCell = btreeParseCellPtrNoPayload; + pPage->intKey = 1; + pPage->maxLocal = pBt->maxLeaf; + pPage->minLocal = pBt->minLeaf; + }else{ + pPage->intKey = 0; + pPage->intKeyLeaf = 0; + pPage->xCellSize = cellSizePtr; + pPage->xParseCell = btreeParseCellPtrIndex; + return SQLITE_CORRUPT_PAGE(pPage); } - pPage->maxLocal = pBt->maxLeaf; - pPage->minLocal = pBt->minLeaf; - }else if( flagByte==PTF_ZERODATA ){ - /* EVIDENCE-OF: R-43316-37308 A value of 2 (0x02) means the page is an - ** interior index b-tree page. */ - assert( (PTF_ZERODATA)==2 ); - /* EVIDENCE-OF: R-59615-42828 A value of 10 (0x0a) means the page is a - ** leaf index b-tree page. */ - assert( (PTF_ZERODATA|PTF_LEAF)==10 ); - pPage->intKey = 0; - pPage->intKeyLeaf = 0; - pPage->xParseCell = btreeParseCellPtrIndex; - pPage->maxLocal = pBt->maxLocal; - pPage->minLocal = pBt->minLocal; - }else{ - /* EVIDENCE-OF: R-47608-56469 Any other value for the b-tree page type is - ** an error. */ - return SQLITE_CORRUPT_PAGE(pPage); } - pPage->max1bytePayload = pBt->max1bytePayload; return SQLITE_OK; } @@ -65079,11 +75350,11 @@ static int btreeComputeFreeSpace(MemPage *pPage){ nFree = data[hdr+7] + top; /* Init nFree to non-freeblock free space */ if( pc>0 ){ u32 next, size; - if( pciCellLast ){ @@ -65092,8 +75363,12 @@ static int btreeComputeFreeSpace(MemPage *pPage){ } next = get2byte(&data[pc]); size = get2byte(&data[pc+2]); + if( size<4 ){ + /* Minimum freeblock size is 4 */ + return SQLITE_CORRUPT_PAGE(pPage); + } nFree = nFree + size; - if( next<=pc+size+3 ) break; + if( next0 ){ @@ -65113,7 +75388,7 @@ static int btreeComputeFreeSpace(MemPage *pPage){ ** serves to verify that the offset to the start of the cell-content ** area, according to the page header, lies within the page. */ - if( nFree>usableSize ){ + if( nFree>usableSize || nFreenFree = (u16)(nFree - iCellFirst); @@ -65122,7 +75397,7 @@ static int btreeComputeFreeSpace(MemPage *pPage){ /* ** Do additional sanity check after btreeInitPage() if -** PRAGMA cell_size_check=ON +** PRAGMA cell_size_check=ON */ static SQLITE_NOINLINE int btreeCellSizeCheck(MemPage *pPage){ int iCellFirst; /* First allowable cell or freeblock offset */ @@ -65160,7 +75435,7 @@ static SQLITE_NOINLINE int btreeCellSizeCheck(MemPage *pPage){ ** Initialize the auxiliary information for a disk block. ** ** Return SQLITE_OK on success. If we see that the page does -** not contain a well-formed database page, then return +** not contain a well-formed database page, then return ** SQLITE_CORRUPT. Note that a return of SQLITE_OK does not ** guarantee that the page is well-formed. It only shows that ** we failed to detect any corruption. @@ -65187,9 +75462,9 @@ static int btreeInitPage(MemPage *pPage){ assert( pBt->pageSize>=512 && pBt->pageSize<=65536 ); pPage->maskPage = (u16)(pBt->pageSize - 1); pPage->nOverflow = 0; - pPage->cellOffset = pPage->hdrOffset + 8 + pPage->childPtrSize; + pPage->cellOffset = (u16)(pPage->hdrOffset + 8 + pPage->childPtrSize); pPage->aCellIdx = data + pPage->childPtrSize + 8; - pPage->aDataEnd = pPage->aData + pBt->usableSize; + pPage->aDataEnd = pPage->aData + pBt->pageSize; pPage->aDataOfst = pPage->aData + pPage->childPtrSize; /* EVIDENCE-OF: R-37002-32774 The two-byte integer at offset 3 gives the ** number of cells on the page. */ @@ -65221,10 +75496,10 @@ static int btreeInitPage(MemPage *pPage){ static void zeroPage(MemPage *pPage, int flags){ unsigned char *data = pPage->aData; BtShared *pBt = pPage->pBt; - u8 hdr = pPage->hdrOffset; - u16 first; + int hdr = pPage->hdrOffset; + int first; - assert( sqlite3PagerPagenumber(pPage->pDbPage)==pPage->pgno ); + assert( sqlite3PagerPagenumber(pPage->pDbPage)==pPage->pgno || CORRUPT_DB ); assert( sqlite3PagerGetExtra(pPage->pDbPage) == (void*)pPage ); assert( sqlite3PagerGetData(pPage->pDbPage) == data ); assert( sqlite3PagerIswriteable(pPage->pDbPage) ); @@ -65239,8 +75514,8 @@ static void zeroPage(MemPage *pPage, int flags){ put2byte(&data[hdr+5], pBt->usableSize); pPage->nFree = (u16)(pBt->usableSize - first); decodeFlags(pPage, flags); - pPage->cellOffset = first; - pPage->aDataEnd = &data[pBt->usableSize]; + pPage->cellOffset = (u16)first; + pPage->aDataEnd = &data[pBt->pageSize]; pPage->aCellIdx = &data[first]; pPage->aDataOfst = &data[pPage->childPtrSize]; pPage->nOverflow = 0; @@ -65265,7 +75540,7 @@ static MemPage *btreePageFromDbPage(DbPage *pDbPage, Pgno pgno, BtShared *pBt){ pPage->hdrOffset = pgno==1 ? 100 : 0; } assert( pPage->aData==sqlite3PagerGetData(pDbPage) ); - return pPage; + return pPage; } /* @@ -65318,76 +75593,48 @@ static MemPage *btreePageLookup(BtShared *pBt, Pgno pgno){ static Pgno btreePagecount(BtShared *pBt){ return pBt->nPage; } -SQLITE_PRIVATE u32 sqlite3BtreeLastPage(Btree *p){ +SQLITE_PRIVATE Pgno sqlite3BtreeLastPage(Btree *p){ assert( sqlite3BtreeHoldsMutex(p) ); - assert( ((p->pBt->nPage)&0x80000000)==0 ); return btreePagecount(p->pBt); } /* ** Get a page from the pager and initialize it. -** -** If pCur!=0 then the page is being fetched as part of a moveToChild() -** call. Do additional sanity checking on the page in this case. -** And if the fetch fails, this routine must decrement pCur->iPage. -** -** The page is fetched as read-write unless pCur is not NULL and is -** a read-only cursor. -** -** If an error occurs, then *ppPage is undefined. It -** may remain unchanged, or it may be set to an invalid value. */ static int getAndInitPage( BtShared *pBt, /* The database file */ Pgno pgno, /* Number of the page to get */ MemPage **ppPage, /* Write the page pointer here */ - BtCursor *pCur, /* Cursor to receive the page, or NULL */ int bReadOnly /* True for a read-only page */ ){ int rc; DbPage *pDbPage; + MemPage *pPage; assert( sqlite3_mutex_held(pBt->mutex) ); - assert( pCur==0 || ppPage==&pCur->pPage ); - assert( pCur==0 || bReadOnly==pCur->curPagerFlags ); - assert( pCur==0 || pCur->iPage>0 ); if( pgno>btreePagecount(pBt) ){ - rc = SQLITE_CORRUPT_BKPT; - goto getAndInitPage_error1; + *ppPage = 0; + return SQLITE_CORRUPT_BKPT; } rc = sqlite3PagerGet(pBt->pPager, pgno, (DbPage**)&pDbPage, bReadOnly); if( rc ){ - goto getAndInitPage_error1; + *ppPage = 0; + return rc; } - *ppPage = (MemPage*)sqlite3PagerGetExtra(pDbPage); - if( (*ppPage)->isInit==0 ){ + pPage = (MemPage*)sqlite3PagerGetExtra(pDbPage); + if( pPage->isInit==0 ){ btreePageFromDbPage(pDbPage, pgno, pBt); - rc = btreeInitPage(*ppPage); + rc = btreeInitPage(pPage); if( rc!=SQLITE_OK ){ - goto getAndInitPage_error2; + releasePage(pPage); + *ppPage = 0; + return rc; } } - assert( (*ppPage)->pgno==pgno ); - assert( (*ppPage)->aData==sqlite3PagerGetData(pDbPage) ); - - /* If obtaining a child page for a cursor, we must verify that the page is - ** compatible with the root page. */ - if( pCur && ((*ppPage)->nCell<1 || (*ppPage)->intKey!=pCur->curIntKey) ){ - rc = SQLITE_CORRUPT_PGNO(pgno); - goto getAndInitPage_error2; - } + assert( pPage->pgno==pgno || CORRUPT_DB ); + assert( pPage->aData==sqlite3PagerGetData(pDbPage) ); + *ppPage = pPage; return SQLITE_OK; - -getAndInitPage_error2: - releasePage(*ppPage); -getAndInitPage_error1: - if( pCur ){ - pCur->iPage--; - pCur->pPage = pCur->apPage[pCur->iPage]; - } - testcase( pgno==0 ); - assert( pgno!=0 || rc==SQLITE_CORRUPT ); - return rc; } /* @@ -65470,7 +75717,7 @@ static void pageReinit(DbPage *pData){ ** call to btreeInitPage() will likely return SQLITE_CORRUPT. ** But no harm is done by this. And it is very important that ** btreeInitPage() be called on every btree page so we make - ** the call for every page that comes in for re-initing. */ + ** the call for every page that comes in for re-initializing. */ btreeInitPage(pPage); } } @@ -65483,17 +75730,16 @@ static int btreeInvokeBusyHandler(void *pArg){ BtShared *pBt = (BtShared*)pArg; assert( pBt->db ); assert( sqlite3_mutex_held(pBt->db->mutex) ); - return sqlite3InvokeBusyHandler(&pBt->db->busyHandler, - sqlite3PagerFile(pBt->pPager)); + return sqlite3InvokeBusyHandler(&pBt->db->busyHandler); } /* ** Open a database file. -** +** ** zFilename is the name of the database file. If zFilename is NULL ** then an ephemeral database is created. The ephemeral database might ** be exclusively in memory, or it might use a disk-based memory cache. -** Either way, the ephemeral database will be automatically deleted +** Either way, the ephemeral database will be automatically deleted ** when sqlite3BtreeClose() is called. ** ** If zFilename is ":memory:" then an in-memory database is created @@ -65526,7 +75772,7 @@ SQLITE_PRIVATE int sqlite3BtreeOpen( /* True if opening an ephemeral, temporary database */ const int isTempDb = zFilename==0 || zFilename[0]==0; - /* Set the variable isMemdb to true for an in-memory database, or + /* Set the variable isMemdb to true for an in-memory database, or ** false for a file-based database. */ #ifdef SQLITE_OMIT_MEMORYDB @@ -65588,15 +75834,19 @@ SQLITE_PRIVATE int sqlite3BtreeOpen( rc = sqlite3OsFullPathname(pVfs, zFilename, nFullPathname, zFullPathname); if( rc ){ - sqlite3_free(zFullPathname); - sqlite3_free(p); - return rc; + if( rc==SQLITE_OK_SYMLINK ){ + rc = SQLITE_OK; + }else{ + sqlite3_free(zFullPathname); + sqlite3_free(p); + return rc; + } } } #if SQLITE_THREADSAFE mutexOpen = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_OPEN); sqlite3_mutex_enter(mutexOpen); - mutexShared = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); + mutexShared = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MAIN); sqlite3_mutex_enter(mutexShared); #endif for(pBt=GLOBAL(BtShared*,sqlite3SharedCacheList); pBt; pBt=pBt->pNext){ @@ -65645,7 +75895,10 @@ SQLITE_PRIVATE int sqlite3BtreeOpen( assert( sizeof(u32)==4 ); assert( sizeof(u16)==2 ); assert( sizeof(Pgno)==4 ); - + + /* Suppress false-positive compiler warning from PVS-Studio */ + memset(&zDbHeader[16], 0, 8); + pBt = sqlite3MallocZero( sizeof(*pBt) ); if( pBt==0 ){ rc = SQLITE_NOMEM_BKPT; @@ -65664,7 +75917,7 @@ SQLITE_PRIVATE int sqlite3BtreeOpen( pBt->db = db; sqlite3PagerSetBusyHandler(pBt->pPager, btreeInvokeBusyHandler, pBt); p->pBt = pBt; - + pBt->pCursor = 0; pBt->pPage1 = 0; if( sqlite3PagerIsreadonly(pBt->pPager) ) pBt->btsFlags |= BTS_READ_ONLY; @@ -65708,14 +75961,14 @@ SQLITE_PRIVATE int sqlite3BtreeOpen( if( rc ) goto btree_open_out; pBt->usableSize = pBt->pageSize - nReserve; assert( (pBt->pageSize & 7)==0 ); /* 8-byte alignment of pageSize */ - + #if !defined(SQLITE_OMIT_SHARED_CACHE) && !defined(SQLITE_OMIT_DISKIO) /* Add the new BtShared object to the linked list sharable BtShareds. */ pBt->nRef = 1; if( p->sharable ){ MUTEX_LOGIC( sqlite3_mutex *mutexShared; ) - MUTEX_LOGIC( mutexShared = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER);) + MUTEX_LOGIC( mutexShared = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MAIN);) if( SQLITE_THREADSAFE && sqlite3GlobalConfig.bCoreMutex ){ pBt->mutex = sqlite3MutexAlloc(SQLITE_MUTEX_FAST); if( pBt->mutex==0 ){ @@ -65780,7 +76033,7 @@ SQLITE_PRIVATE int sqlite3BtreeOpen( ** do not change the pager-cache size. */ if( sqlite3BtreeSchema(p, 0, 0)==0 ){ - sqlite3PagerSetCachesize(p->pBt->pPager, SQLITE_DEFAULT_CACHE_SIZE); + sqlite3BtreeSetCacheSize(p, SQLITE_DEFAULT_CACHE_SIZE); } pFile = sqlite3PagerFile(pBt->pPager); @@ -65804,13 +76057,13 @@ SQLITE_PRIVATE int sqlite3BtreeOpen( */ static int removeFromSharingList(BtShared *pBt){ #ifndef SQLITE_OMIT_SHARED_CACHE - MUTEX_LOGIC( sqlite3_mutex *pMaster; ) + MUTEX_LOGIC( sqlite3_mutex *pMainMtx; ) BtShared *pList; int removed = 0; assert( sqlite3_mutex_notheld(pBt->mutex) ); - MUTEX_LOGIC( pMaster = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); ) - sqlite3_mutex_enter(pMaster); + MUTEX_LOGIC( pMainMtx = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MAIN); ) + sqlite3_mutex_enter(pMainMtx); pBt->nRef--; if( pBt->nRef<=0 ){ if( GLOBAL(BtShared*,sqlite3SharedCacheList)==pBt ){ @@ -65829,42 +76082,51 @@ static int removeFromSharingList(BtShared *pBt){ } removed = 1; } - sqlite3_mutex_leave(pMaster); + sqlite3_mutex_leave(pMainMtx); return removed; #else + UNUSED_PARAMETER( pBt ); return 1; #endif } /* -** Make sure pBt->pTmpSpace points to an allocation of +** Make sure pBt->pTmpSpace points to an allocation of ** MX_CELL_SIZE(pBt) bytes with a 4-byte prefix for a left-child ** pointer. */ -static void allocateTempSpace(BtShared *pBt){ - if( !pBt->pTmpSpace ){ - pBt->pTmpSpace = sqlite3PageMalloc( pBt->pageSize ); - - /* One of the uses of pBt->pTmpSpace is to format cells before - ** inserting them into a leaf page (function fillInCell()). If - ** a cell is less than 4 bytes in size, it is rounded up to 4 bytes - ** by the various routines that manipulate binary cells. Which - ** can mean that fillInCell() only initializes the first 2 or 3 - ** bytes of pTmpSpace, but that the first 4 bytes are copied from - ** it into a database page. This is not actually a problem, but it - ** does cause a valgrind error when the 1 or 2 bytes of unitialized - ** data is passed to system call write(). So to avoid this error, - ** zero the first 4 bytes of temp space here. - ** - ** Also: Provide four bytes of initialized space before the - ** beginning of pTmpSpace as an area available to prepend the - ** left-child pointer to the beginning of a cell. - */ - if( pBt->pTmpSpace ){ - memset(pBt->pTmpSpace, 0, 8); - pBt->pTmpSpace += 4; - } +static SQLITE_NOINLINE int allocateTempSpace(BtShared *pBt){ + assert( pBt!=0 ); + assert( pBt->pTmpSpace==0 ); + /* This routine is called only by btreeCursor() when allocating the + ** first write cursor for the BtShared object */ + assert( pBt->pCursor!=0 && (pBt->pCursor->curFlags & BTCF_WriteFlag)!=0 ); + pBt->pTmpSpace = sqlite3PageMalloc( pBt->pageSize ); + if( pBt->pTmpSpace==0 ){ + BtCursor *pCur = pBt->pCursor; + pBt->pCursor = pCur->pNext; /* Unlink the cursor */ + memset(pCur, 0, sizeof(*pCur)); + return SQLITE_NOMEM_BKPT; } + + /* One of the uses of pBt->pTmpSpace is to format cells before + ** inserting them into a leaf page (function fillInCell()). If + ** a cell is less than 4 bytes in size, it is rounded up to 4 bytes + ** by the various routines that manipulate binary cells. Which + ** can mean that fillInCell() only initializes the first 2 or 3 + ** bytes of pTmpSpace, but that the first 4 bytes are copied from + ** it into a database page. This is not actually a problem, but it + ** does cause a valgrind error when the 1 or 2 bytes of uninitialized + ** data is passed to system call write(). So to avoid this error, + ** zero the first 4 bytes of temp space here. + ** + ** Also: Provide four bytes of initialized space before the + ** beginning of pTmpSpace as an area available to prepend the + ** left-child pointer to the beginning of a cell. + */ + memset(pBt->pTmpSpace, 0, 8); + pBt->pTmpSpace += 4; + return SQLITE_OK; } /* @@ -65883,19 +76145,23 @@ static void freeTempSpace(BtShared *pBt){ */ SQLITE_PRIVATE int sqlite3BtreeClose(Btree *p){ BtShared *pBt = p->pBt; - BtCursor *pCur; /* Close all cursors opened via this handle. */ assert( sqlite3_mutex_held(p->db->mutex) ); sqlite3BtreeEnter(p); - pCur = pBt->pCursor; - while( pCur ){ - BtCursor *pTmp = pCur; - pCur = pCur->pNext; - if( pTmp->pBtree==p ){ - sqlite3BtreeCloseCursor(pTmp); + + /* Verify that no other cursors have this Btree open */ +#ifdef SQLITE_DEBUG + { + BtCursor *pCur = pBt->pCursor; + while( pCur ){ + BtCursor *pTmp = pCur; + pCur = pCur->pNext; + assert( pTmp->pBtree!=p ); + } } +#endif /* Rollback any active transaction and free the handle structure. ** The call to sqlite3BtreeRollback() drops any table-locks held by @@ -65905,7 +76171,7 @@ SQLITE_PRIVATE int sqlite3BtreeClose(Btree *p){ sqlite3BtreeLeave(p); /* If there are still other outstanding references to the shared-btree - ** structure, return now. The remainder of this procedure cleans + ** structure, return now. The remainder of this procedure cleans ** up the shared-btree. */ assert( p->wantToLock==0 && p->locked==0 ); @@ -66011,7 +76277,7 @@ SQLITE_PRIVATE int sqlite3BtreeSetPagerFlags( /* ** Change the default pages size and the number of reserved bytes per page. -** Or, if the page size has already been fixed, return SQLITE_READONLY +** Or, if the page size has already been fixed, return SQLITE_READONLY ** without changing anything. ** ** The page size must be a power of 2 between 512 and 65536. If the page @@ -66031,24 +76297,27 @@ SQLITE_PRIVATE int sqlite3BtreeSetPagerFlags( */ SQLITE_PRIVATE int sqlite3BtreeSetPageSize(Btree *p, int pageSize, int nReserve, int iFix){ int rc = SQLITE_OK; + int x; BtShared *pBt = p->pBt; - assert( nReserve>=-1 && nReserve<=255 ); + assert( nReserve>=0 && nReserve<=255 ); sqlite3BtreeEnter(p); -#if SQLITE_HAS_CODEC - if( nReserve>pBt->optimalReserve ) pBt->optimalReserve = (u8)nReserve; -#endif + pBt->nReserveWanted = (u8)nReserve; + x = pBt->pageSize - pBt->usableSize; + if( x==nReserve && (pageSize==0 || (u32)pageSize==pBt->pageSize) ){ + sqlite3BtreeLeave(p); + return SQLITE_OK; + } + if( nReservebtsFlags & BTS_PAGESIZE_FIXED ){ sqlite3BtreeLeave(p); return SQLITE_READONLY; } - if( nReserve<0 ){ - nReserve = pBt->pageSize - pBt->usableSize; - } assert( nReserve>=0 && nReserve<=255 ); if( pageSize>=512 && pageSize<=SQLITE_MAX_PAGE_SIZE && ((pageSize-1)&pageSize)==0 ){ assert( (pageSize & 7)==0 ); assert( !pBt->pCursor ); + if( nReserve>32 && pageSize==512 ) pageSize = 1024; pBt->pageSize = (u32)pageSize; freeTempSpace(pBt); } @@ -66072,7 +76341,7 @@ SQLITE_PRIVATE int sqlite3BtreeGetPageSize(Btree *p){ ** held. ** ** This is useful in one special case in the backup API code where it is -** known that the shared b-tree mutex is held, but the mutex on the +** known that the shared b-tree mutex is held, but the mutex on the ** database handle that owns *p is not. In this case if sqlite3BtreeEnter() ** were to be called, it might collide with some other operation on the ** database handle that owns *p, causing undefined behavior. @@ -66086,22 +76355,20 @@ SQLITE_PRIVATE int sqlite3BtreeGetReserveNoMutex(Btree *p){ /* ** Return the number of bytes of space at the end of every page that -** are intentually left unused. This is the "reserved" space that is +** are intentionally left unused. This is the "reserved" space that is ** sometimes used by extensions. ** -** If SQLITE_HAS_MUTEX is defined then the number returned is the -** greater of the current reserved space and the maximum requested -** reserve space. +** The value returned is the larger of the current reserve size and +** the latest reserve size requested by SQLITE_FILECTRL_RESERVE_BYTES. +** The amount of reserve can only grow - never shrink. */ -SQLITE_PRIVATE int sqlite3BtreeGetOptimalReserve(Btree *p){ - int n; +SQLITE_PRIVATE int sqlite3BtreeGetRequestedReserve(Btree *p){ + int n1, n2; sqlite3BtreeEnter(p); - n = sqlite3BtreeGetReserveNoMutex(p); -#ifdef SQLITE_HAS_CODEC - if( npBt->optimalReserve ) n = p->pBt->optimalReserve; -#endif + n1 = (int)p->pBt->nReserveWanted; + n2 = sqlite3BtreeGetReserveNoMutex(p); sqlite3BtreeLeave(p); - return n; + return n1>n2 ? n1 : n2; } @@ -66110,8 +76377,8 @@ SQLITE_PRIVATE int sqlite3BtreeGetOptimalReserve(Btree *p){ ** No changes are made if mxPage is 0 or negative. ** Regardless of the value of mxPage, return the maximum page count. */ -SQLITE_PRIVATE int sqlite3BtreeMaxPageCount(Btree *p, int mxPage){ - int n; +SQLITE_PRIVATE Pgno sqlite3BtreeMaxPageCount(Btree *p, Pgno mxPage){ + Pgno n; sqlite3BtreeEnter(p); n = sqlite3PagerMaxPageCount(p->pBt->pPager, mxPage); sqlite3BtreeLeave(p); @@ -66144,7 +76411,7 @@ SQLITE_PRIVATE int sqlite3BtreeSecureDelete(Btree *p, int newFlag){ assert( BTS_FAST_SECURE==(BTS_OVERWRITE|BTS_SECURE_DELETE) ); if( newFlag>=0 ){ p->pBt->btsFlags &= ~BTS_FAST_SECURE; - p->pBt->btsFlags |= BTS_SECURE_DELETE*newFlag; + p->pBt->btsFlags |= (u16)(BTS_SECURE_DELETE*newFlag); } b = (p->pBt->btsFlags & BTS_FAST_SECURE)/BTS_SECURE_DELETE; sqlite3BtreeLeave(p); @@ -66154,7 +76421,7 @@ SQLITE_PRIVATE int sqlite3BtreeSecureDelete(Btree *p, int newFlag){ /* ** Change the 'auto-vacuum' property of the database. If the 'autoVacuum' ** parameter is non-zero, then auto-vacuum mode is enabled. If zero, it -** is disabled. The default value for the auto-vacuum property is +** is disabled. The default value for the auto-vacuum property is ** determined by the SQLITE_DEFAULT_AUTOVACUUM macro. */ SQLITE_PRIVATE int sqlite3BtreeSetAutoVacuum(Btree *p, int autoVacuum){ @@ -66178,7 +76445,7 @@ SQLITE_PRIVATE int sqlite3BtreeSetAutoVacuum(Btree *p, int autoVacuum){ } /* -** Return the value of the 'auto-vacuum' property. If auto-vacuum is +** Return the value of the 'auto-vacuum' property. If auto-vacuum is ** enabled 1 is returned. Otherwise 0. */ SQLITE_PRIVATE int sqlite3BtreeGetAutoVacuum(Btree *p){ @@ -66210,9 +76477,9 @@ static void setDefaultSyncFlag(BtShared *pBt, u8 safety_level){ Db *pDb; if( (db=pBt->db)!=0 && (pDb=db->aDb)!=0 ){ while( pDb->pBt==0 || pDb->pBt->pBt!=pBt ){ pDb++; } - if( pDb->bSyncSet==0 - && pDb->safety_level!=safety_level - && pDb!=&db->aDb[1] + if( pDb->bSyncSet==0 + && pDb->safety_level!=safety_level + && pDb!=&db->aDb[1] ){ pDb->safety_level = safety_level; sqlite3PagerSetFlags(pBt->pPager, @@ -66235,14 +76502,13 @@ static int newDatabase(BtShared*); ** SQLITE_OK is returned on success. If the file is not a ** well-formed database file, then SQLITE_CORRUPT is returned. ** SQLITE_BUSY is returned if the database is locked. SQLITE_NOMEM -** is returned if we run out of memory. +** is returned if we run out of memory. */ static int lockBtree(BtShared *pBt){ int rc; /* Result code from subfunctions */ MemPage *pPage1; /* Page 1 of the database file */ u32 nPage; /* Number of pages in the database */ u32 nPageFile = 0; /* Number of pages in the database file */ - u32 nPageHeader; /* Number of pages in the database according to hdr */ assert( sqlite3_mutex_held(pBt->mutex) ); assert( pBt->pPage1==0 ); @@ -66252,9 +76518,9 @@ static int lockBtree(BtShared *pBt){ if( rc!=SQLITE_OK ) return rc; /* Do some checking to help insure the file we opened really is - ** a valid database file. + ** a valid database file. */ - nPage = nPageHeader = get4byte(28+(u8*)pPage1->aData); + nPage = get4byte(28+(u8*)pPage1->aData); sqlite3PagerPagecount(pBt->pPager, (int*)&nPageFile); if( nPage==0 || memcmp(24+(u8*)pPage1->aData, 92+(u8*)pPage1->aData,4)!=0 ){ nPage = nPageFile; @@ -66289,8 +76555,8 @@ static int lockBtree(BtShared *pBt){ goto page1_init_failed; } - /* If the write version is set to 2, this database should be accessed - ** in WAL mode. If the log is not already open, open it now. Then + /* If the read version is set to 2, this database should be accessed + ** in WAL mode. If the log is not already open, open it now. Then ** return SQLITE_OK and return without populating BtShared.pPage1. ** The caller detects this and calls this function again. This is ** required as the version of page 1 currently in the page1 buffer @@ -66331,16 +76597,15 @@ static int lockBtree(BtShared *pBt){ /* EVIDENCE-OF: R-25008-21688 The size of a page is a power of two ** between 512 and 65536 inclusive. */ if( ((pageSize-1)&pageSize)!=0 - || pageSize>SQLITE_MAX_PAGE_SIZE - || pageSize<=256 + || pageSize>SQLITE_MAX_PAGE_SIZE + || pageSize<=256 ){ goto page1_init_failed; } - pBt->btsFlags |= BTS_PAGESIZE_FIXED; assert( (pageSize & 7)==0 ); /* EVIDENCE-OF: R-59310-51205 The "reserved space" size in the 1-byte ** integer at offset 20 is the number of bytes of space at the end of - ** each page to reserve for extensions. + ** each page to reserve for extensions. ** ** EVIDENCE-OF: R-37497-42412 The size of the reserved region is ** determined by the one-byte unsigned integer found at an offset of 20 @@ -66356,14 +76621,19 @@ static int lockBtree(BtShared *pBt){ releasePageOne(pPage1); pBt->usableSize = usableSize; pBt->pageSize = pageSize; + pBt->btsFlags |= BTS_PAGESIZE_FIXED; freeTempSpace(pBt); rc = sqlite3PagerSetPagesize(pBt->pPager, &pBt->pageSize, pageSize-usableSize); return rc; } - if( sqlite3WritableSchema(pBt->db)==0 && nPage>nPageFile ){ - rc = SQLITE_CORRUPT_BKPT; - goto page1_init_failed; + if( nPage>nPageFile ){ + if( sqlite3WritableSchema(pBt->db)==0 ){ + rc = SQLITE_CORRUPT_BKPT; + goto page1_init_failed; + }else{ + nPage = nPageFile; + } } /* EVIDENCE-OF: R-28312-64704 However, the usable size is not allowed to ** be less than 480. In other words, if the page size is 512, then the @@ -66371,6 +76641,7 @@ static int lockBtree(BtShared *pBt){ if( usableSize<480 ){ goto page1_init_failed; } + pBt->btsFlags |= BTS_PAGESIZE_FIXED; pBt->pageSize = pageSize; pBt->usableSize = usableSize; #ifndef SQLITE_OMIT_AUTOVACUUM @@ -66430,7 +76701,7 @@ static int countValidCursors(BtShared *pBt, int wrOnly){ int r = 0; for(pCur=pBt->pCursor; pCur; pCur=pCur->pNext){ if( (wrOnly==0 || (pCur->curFlags & BTCF_WriteFlag)!=0) - && pCur->eState!=CURSOR_FAULT ) r++; + && pCur->eState!=CURSOR_FAULT ) r++; } return r; } @@ -66439,7 +76710,7 @@ static int countValidCursors(BtShared *pBt, int wrOnly){ /* ** If there are no outstanding cursors and we are not in the middle ** of a transaction but there is a read lock on the database, then -** this routine unrefs the first page of the database file which +** this routine unrefs the first page of the database file which ** has the effect of releasing the read lock. ** ** If there is a transaction in progress, this routine is a no-op. @@ -66523,8 +76794,8 @@ SQLITE_PRIVATE int sqlite3BtreeNewDb(Btree *p){ ** upgraded to exclusive by calling this routine a second time - the ** exclusivity flag only works for a new transaction. ** -** A write-transaction must be started before attempting any -** changes to the database. None of the following routines +** A write-transaction must be started before attempting any +** changes to the database. None of the following routines ** will work unless a transaction is started first: ** ** sqlite3BtreeCreateTable() @@ -66538,7 +76809,7 @@ SQLITE_PRIVATE int sqlite3BtreeNewDb(Btree *p){ ** If an initial attempt to acquire the lock fails because of lock contention ** and the database was previously unlocked, then invoke the busy handler ** if there is one. But if there was previously a read-lock, do not -** invoke the busy handler - just return SQLITE_BUSY. SQLITE_BUSY is +** invoke the busy handler - just return SQLITE_BUSY. SQLITE_BUSY is ** returned when there is already a read-lock in order to avoid a deadlock. ** ** Suppose there are two processes A and B. A has a read lock and B has @@ -66549,8 +76820,13 @@ SQLITE_PRIVATE int sqlite3BtreeNewDb(Btree *p){ ** when A already has a read lock, we encourage A to give up and let B ** proceed. */ -SQLITE_PRIVATE int sqlite3BtreeBeginTrans(Btree *p, int wrflag, int *pSchemaVersion){ +static SQLITE_NOINLINE int btreeBeginTrans( + Btree *p, /* The btree in which to start the transaction */ + int wrflag, /* True to start a write transaction */ + int *pSchemaVersion /* Put schema version number here, if not NULL */ +){ BtShared *pBt = p->pBt; + Pager *pPager = pBt->pPager; int rc = SQLITE_OK; sqlite3BtreeEnter(p); @@ -66565,8 +76841,8 @@ SQLITE_PRIVATE int sqlite3BtreeBeginTrans(Btree *p, int wrflag, int *pSchemaVers } assert( pBt->inTransaction==TRANS_WRITE || IfNotOmitAV(pBt->bDoTruncate)==0 ); - if( (p->db->flags & SQLITE_ResetDatabase) - && sqlite3PagerIsreadonly(pBt->pPager)==0 + if( (p->db->flags & SQLITE_ResetDatabase) + && sqlite3PagerIsreadonly(pPager)==0 ){ pBt->btsFlags &= ~BTS_READ_ONLY; } @@ -66580,7 +76856,7 @@ SQLITE_PRIVATE int sqlite3BtreeBeginTrans(Btree *p, int wrflag, int *pSchemaVers #ifndef SQLITE_OMIT_SHARED_CACHE { sqlite3 *pBlock = 0; - /* If another database handle has already opened a write transaction + /* If another database handle has already opened a write transaction ** on this shared-btree structure and a second write transaction is ** requested, return SQLITE_LOCKED. */ @@ -66605,19 +76881,55 @@ SQLITE_PRIVATE int sqlite3BtreeBeginTrans(Btree *p, int wrflag, int *pSchemaVers } #endif - /* Any read-only or read-write transaction implies a read-lock on - ** page 1. So if some other shared-cache client already has a write-lock + /* Any read-only or read-write transaction implies a read-lock on + ** page 1. So if some other shared-cache client already has a write-lock ** on page 1, the transaction cannot be opened. */ - rc = querySharedCacheTableLock(p, MASTER_ROOT, READ_LOCK); + rc = querySharedCacheTableLock(p, SCHEMA_ROOT, READ_LOCK); if( SQLITE_OK!=rc ) goto trans_begun; pBt->btsFlags &= ~BTS_INITIALLY_EMPTY; if( pBt->nPage==0 ) pBt->btsFlags |= BTS_INITIALLY_EMPTY; do { + sqlite3PagerWalDb(pPager, p->db); + +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + /* If transitioning from no transaction directly to a write transaction, + ** block for the WRITER lock first if possible. */ + if( pBt->pPage1==0 && wrflag ){ + assert( pBt->inTransaction==TRANS_NONE ); + rc = sqlite3PagerWalWriteLock(pPager, 1); + if( rc!=SQLITE_BUSY && rc!=SQLITE_OK ) break; + } +#endif + +#ifdef SQLITE_EXPERIMENTAL_PRAGMA_20251114 + /* If both a read and write transaction will be opened by this call, + ** then issue a file-control as if the following pragma command had + ** been evaluated: + ** + ** PRAGMA experimental_pragma_20251114 = 1|2 + ** + ** where the RHS is "1" if wrflag is 1 (RESERVED lock), or "2" if wrflag + ** is 2 (EXCLUSIVE lock). Ignore any result or error returned by the VFS. + ** + ** WARNING: This code will likely remain part of SQLite only temporarily - + ** it exists to allow users to experiment with certain types of blocking + ** locks in custom VFS implementations. It MAY BE REMOVED AT ANY TIME. */ + if( pBt->pPage1==0 && wrflag ){ + sqlite3_file *fd = sqlite3PagerFile(pPager); + char *aFcntl[3] = {0,0,0}; + aFcntl[1] = "experimental_pragma_20251114"; + assert( wrflag==1 || wrflag==2 ); + aFcntl[2] = (wrflag==1 ? "1" : "2"); + sqlite3OsFileControlHint(fd, SQLITE_FCNTL_PRAGMA, (void*)aFcntl); + sqlite3_free(aFcntl[0]); + } +#endif + /* Call lockBtree() until either pBt->pPage1 is populated or ** lockBtree() returns something other than SQLITE_OK. lockBtree() ** may return SQLITE_OK but leave pBt->pPage1 set to 0 if after - ** reading page 1 it discovers that the page-size of the database + ** reading page 1 it discovers that the page-size of the database ** file is not pBt->pageSize. In this case lockBtree() will update ** pBt->pageSize to the page-size of the file on disk. */ @@ -66627,7 +76939,7 @@ SQLITE_PRIVATE int sqlite3BtreeBeginTrans(Btree *p, int wrflag, int *pSchemaVers if( (pBt->btsFlags & BTS_READ_ONLY)!=0 ){ rc = SQLITE_READONLY; }else{ - rc = sqlite3PagerBegin(pBt->pPager,wrflag>1,sqlite3TempInMemory(p->db)); + rc = sqlite3PagerBegin(pPager, wrflag>1, sqlite3TempInMemory(p->db)); if( rc==SQLITE_OK ){ rc = newDatabase(pBt); }else if( rc==SQLITE_BUSY_SNAPSHOT && pBt->inTransaction==TRANS_NONE ){ @@ -66638,13 +76950,24 @@ SQLITE_PRIVATE int sqlite3BtreeBeginTrans(Btree *p, int wrflag, int *pSchemaVers } } } - + if( rc!=SQLITE_OK ){ + (void)sqlite3PagerWalWriteLock(pPager, 0); unlockBtreeIfUnused(pBt); } +#if defined(SQLITE_ENABLE_SETLK_TIMEOUT) + if( rc==SQLITE_BUSY_TIMEOUT ){ + /* If a blocking lock timed out, break out of the loop here so that + ** the busy-handler is not invoked. */ + break; + } +#endif }while( (rc&0xFF)==SQLITE_BUSY && pBt->inTransaction==TRANS_NONE && btreeInvokeBusyHandler(pBt) ); - sqlite3PagerResetLockTimeout(pBt->pPager); + sqlite3PagerWalDb(pPager, 0); +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + if( rc==SQLITE_BUSY_TIMEOUT ) rc = SQLITE_BUSY; +#endif if( rc==SQLITE_OK ){ if( p->inTrans==TRANS_NONE ){ @@ -66673,7 +76996,7 @@ SQLITE_PRIVATE int sqlite3BtreeBeginTrans(Btree *p, int wrflag, int *pSchemaVers /* If the db-size header field is incorrect (as it may be if an old ** client has been writing the database file), update it now. Doing - ** this sooner rather than later means the database size can safely + ** this sooner rather than later means the database size can safely ** re-read the database size from page 1 if a savepoint or transaction ** rollback occurs within the transaction. */ @@ -66696,7 +77019,7 @@ SQLITE_PRIVATE int sqlite3BtreeBeginTrans(Btree *p, int wrflag, int *pSchemaVers ** open savepoints. If the second parameter is greater than 0 and ** the sub-journal is not already open, then it will be opened here. */ - rc = sqlite3PagerOpenSavepoint(pBt->pPager, p->db->nSavepoint); + rc = sqlite3PagerOpenSavepoint(pPager, p->db->nSavepoint); } } @@ -66704,6 +77027,28 @@ SQLITE_PRIVATE int sqlite3BtreeBeginTrans(Btree *p, int wrflag, int *pSchemaVers sqlite3BtreeLeave(p); return rc; } +SQLITE_PRIVATE int sqlite3BtreeBeginTrans(Btree *p, int wrflag, int *pSchemaVersion){ + BtShared *pBt; + if( p->sharable + || p->inTrans==TRANS_NONE + || (p->inTrans==TRANS_READ && wrflag!=0) + ){ + return btreeBeginTrans(p,wrflag,pSchemaVersion); + } + pBt = p->pBt; + if( pSchemaVersion ){ + *pSchemaVersion = get4byte(&pBt->pPage1->aData[40]); + } + if( wrflag ){ + /* This call makes sure that the pager has the correct number of + ** open savepoints. If the second parameter is greater than 0 and + ** the sub-journal is not already open, then it will be opened here. + */ + return sqlite3PagerOpenSavepoint(pBt->pPager, p->db->nSavepoint); + }else{ + return SQLITE_OK; + } +} #ifndef SQLITE_OMIT_AUTOVACUUM @@ -66748,7 +77093,7 @@ static int setChildPtrmaps(MemPage *pPage){ ** that it points to iTo. Parameter eType describes the type of pointer to ** be modified, as follows: ** -** PTRMAP_BTREE: pPage is a btree-page. The pointer points at a child +** PTRMAP_BTREE: pPage is a btree-page. The pointer points at a child ** page of pPage. ** ** PTRMAP_OVERFLOW1: pPage is a btree-page. The pointer points at an overflow @@ -66790,15 +77135,18 @@ static int modifyPagePointer(MemPage *pPage, Pgno iFrom, Pgno iTo, u8 eType){ } } }else{ + if( pCell+4 > pPage->aData+pPage->pBt->usableSize ){ + return SQLITE_CORRUPT_PAGE(pPage); + } if( get4byte(pCell)==iFrom ){ put4byte(pCell, iTo); break; } } } - + if( i==nCell ){ - if( eType!=PTRMAP_BTREE || + if( eType!=PTRMAP_BTREE || get4byte(&pPage->aData[pPage->hdrOffset+8])!=iFrom ){ return SQLITE_CORRUPT_PAGE(pPage); } @@ -66810,11 +77158,11 @@ static int modifyPagePointer(MemPage *pPage, Pgno iFrom, Pgno iTo, u8 eType){ /* -** Move the open database page pDbPage to location iFreePage in the +** Move the open database page pDbPage to location iFreePage in the ** database. The pDbPage reference remains valid. ** ** The isCommit flag indicates that there is no need to remember that -** the journal needs to be sync()ed before database page pDbPage->pgno +** the journal needs to be sync()ed before database page pDbPage->pgno ** can be written to. The caller has already promised not to write to that ** page. */ @@ -66831,14 +77179,14 @@ static int relocatePage( Pager *pPager = pBt->pPager; int rc; - assert( eType==PTRMAP_OVERFLOW2 || eType==PTRMAP_OVERFLOW1 || + assert( eType==PTRMAP_OVERFLOW2 || eType==PTRMAP_OVERFLOW1 || eType==PTRMAP_BTREE || eType==PTRMAP_ROOTPAGE ); assert( sqlite3_mutex_held(pBt->mutex) ); assert( pDbPage->pBt==pBt ); if( iDbPage<3 ) return SQLITE_CORRUPT_BKPT; /* Move page iDbPage from its current location to page number iFreePage */ - TRACE(("AUTOVACUUM: Moving %d to free page %d (ptr page %d type %d)\n", + TRACE(("AUTOVACUUM: Moving %u to free page %u (ptr page %u type %u)\n", iDbPage, iFreePage, iPtrPage, eType)); rc = sqlite3PagerMovepage(pPager, pDbPage->pDbPage, iFreePage, isCommit); if( rc!=SQLITE_OK ){ @@ -66897,19 +77245,19 @@ static int allocateBtreePage(BtShared *, MemPage **, Pgno *, Pgno, u8); /* ** Perform a single step of an incremental-vacuum. If successful, return -** SQLITE_OK. If there is no work to do (and therefore no point in -** calling this function again), return SQLITE_DONE. Or, if an error +** SQLITE_OK. If there is no work to do (and therefore no point in +** calling this function again), return SQLITE_DONE. Or, if an error ** occurs, return some other error code. ** -** More specifically, this function attempts to re-organize the database so +** More specifically, this function attempts to re-organize the database so ** that the last page of the file currently in use is no longer in use. ** ** Parameter nFin is the number of pages that this database would contain ** were this function called until it returns SQLITE_DONE. ** -** If the bCommit parameter is non-zero, this function assumes that the -** caller will keep calling incrVacuumStep() until it returns SQLITE_DONE -** or an error. bCommit is passed true for an auto-vacuum-on-commit +** If the bCommit parameter is non-zero, this function assumes that the +** caller will keep calling incrVacuumStep() until it returns SQLITE_DONE +** or an error. bCommit is passed true for an auto-vacuum-on-commit ** operation, or false for an incremental vacuum. */ static int incrVacuumStep(BtShared *pBt, Pgno nFin, Pgno iLastPg, int bCommit){ @@ -66940,7 +77288,7 @@ static int incrVacuumStep(BtShared *pBt, Pgno nFin, Pgno iLastPg, int bCommit){ if( bCommit==0 ){ /* Remove the page from the files free-list. This is not required ** if bCommit is non-zero. In that case, the free-list will be - ** truncated to zero after this function returns, so it doesn't + ** truncated to zero after this function returns, so it doesn't ** matter if it still contains some garbage entries. */ Pgno iFreePg; @@ -66976,15 +77324,20 @@ static int incrVacuumStep(BtShared *pBt, Pgno nFin, Pgno iLastPg, int bCommit){ } do { MemPage *pFreePg; + Pgno dbSize = btreePagecount(pBt); rc = allocateBtreePage(pBt, &pFreePg, &iFreePg, iNear, eMode); if( rc!=SQLITE_OK ){ releasePage(pLastPg); return rc; } releasePage(pFreePg); + if( iFreePg>dbSize ){ + releasePage(pLastPg); + return SQLITE_CORRUPT_BKPT; + } }while( bCommit && iFreePg>nFin ); assert( iFreePgpPage1->aData[36]); Pgno nFin = finalDbSize(pBt, nOrig, nFree); - if( nOrig=nOrig ){ rc = SQLITE_CORRUPT_BKPT; }else if( nFree>0 ){ rc = saveAllCursors(pBt, 0, 0); @@ -67070,16 +77423,18 @@ SQLITE_PRIVATE int sqlite3BtreeIncrVacuum(Btree *p){ /* ** This routine is called prior to sqlite3PagerCommit when a transaction ** is committed for an auto-vacuum database. -** -** If SQLITE_OK is returned, then *pnTrunc is set to the number of pages -** the database file should be truncated to during the commit process. -** i.e. the database has been reorganized so that only the first *pnTrunc -** pages are in use. */ -static int autoVacuumCommit(BtShared *pBt){ +static int autoVacuumCommit(Btree *p){ int rc = SQLITE_OK; - Pager *pPager = pBt->pPager; - VVA_ONLY( int nRef = sqlite3PagerRefcount(pPager); ) + Pager *pPager; + BtShared *pBt; + sqlite3 *db; + VVA_ONLY( int nRef ); + + assert( p!=0 ); + pBt = p->pBt; + pPager = pBt->pPager; + VVA_ONLY( nRef = sqlite3PagerRefcount(pPager); ) assert( sqlite3_mutex_held(pBt->mutex) ); invalidateAllOverflowCache(pBt); @@ -67087,6 +77442,7 @@ static int autoVacuumCommit(BtShared *pBt){ if( !pBt->incrVacuum ){ Pgno nFin; /* Number of pages in database after autovacuuming */ Pgno nFree; /* Number of pages on the freelist initially */ + Pgno nVac; /* Number of pages to vacuum */ Pgno iFree; /* The next page to be freed */ Pgno nOrig; /* Database size before freeing */ @@ -67100,18 +77456,42 @@ static int autoVacuumCommit(BtShared *pBt){ } nFree = get4byte(&pBt->pPage1->aData[36]); - nFin = finalDbSize(pBt, nOrig, nFree); + db = p->db; + if( db->xAutovacPages ){ + int iDb; + for(iDb=0; ALWAYS(iDbnDb); iDb++){ + if( db->aDb[iDb].pBt==p ) break; + } + nVac = db->xAutovacPages( + db->pAutovacPagesArg, + db->aDb[iDb].zDbSName, + nOrig, + nFree, + pBt->pageSize + ); + if( nVac>nFree ){ + nVac = nFree; + } + if( nVac==0 ){ + return SQLITE_OK; + } + }else{ + nVac = nFree; + } + nFin = finalDbSize(pBt, nOrig, nVac); if( nFin>nOrig ) return SQLITE_CORRUPT_BKPT; if( nFinnFin && rc==SQLITE_OK; iFree--){ - rc = incrVacuumStep(pBt, nFin, iFree, 1); + rc = incrVacuumStep(pBt, nFin, iFree, nVac==nFree); } if( (rc==SQLITE_DONE || rc==SQLITE_OK) && nFree>0 ){ rc = sqlite3PagerWrite(pBt->pPage1->pDbPage); - put4byte(&pBt->pPage1->aData[32], 0); - put4byte(&pBt->pPage1->aData[36], 0); + if( nVac==nFree ){ + put4byte(&pBt->pPage1->aData[32], 0); + put4byte(&pBt->pPage1->aData[36], 0); + } put4byte(&pBt->pPage1->aData[28], nFin); pBt->bDoTruncate = 1; pBt->nPage = nFin; @@ -67144,25 +77524,25 @@ static int autoVacuumCommit(BtShared *pBt){ ** ** This call is a no-op if no write-transaction is currently active on pBt. ** -** Otherwise, sync the database file for the btree pBt. zMaster points to -** the name of a master journal file that should be written into the -** individual journal file, or is NULL, indicating no master journal file +** Otherwise, sync the database file for the btree pBt. zSuperJrnl points to +** the name of a super-journal file that should be written into the +** individual journal file, or is NULL, indicating no super-journal file ** (single database transaction). ** -** When this is called, the master journal should already have been +** When this is called, the super-journal should already have been ** created, populated with this journal pointer and synced to disk. ** ** Once this is routine has returned, the only thing required to commit ** the write-transaction for this database file is to delete the journal. */ -SQLITE_PRIVATE int sqlite3BtreeCommitPhaseOne(Btree *p, const char *zMaster){ +SQLITE_PRIVATE int sqlite3BtreeCommitPhaseOne(Btree *p, const char *zSuperJrnl){ int rc = SQLITE_OK; if( p->inTrans==TRANS_WRITE ){ BtShared *pBt = p->pBt; sqlite3BtreeEnter(p); #ifndef SQLITE_OMIT_AUTOVACUUM if( pBt->autoVacuum ){ - rc = autoVacuumCommit(pBt); + rc = autoVacuumCommit(p); if( rc!=SQLITE_OK ){ sqlite3BtreeLeave(p); return rc; @@ -67172,7 +77552,7 @@ SQLITE_PRIVATE int sqlite3BtreeCommitPhaseOne(Btree *p, const char *zMaster){ sqlite3PagerTruncateImage(pBt->pPager, pBt->nPage); } #endif - rc = sqlite3PagerCommitPhaseOne(pBt->pPager, zMaster, 0); + rc = sqlite3PagerCommitPhaseOne(pBt->pPager, zSuperJrnl, 0); sqlite3BtreeLeave(p); } return rc; @@ -67197,8 +77577,8 @@ static void btreeEndTransaction(Btree *p){ downgradeAllSharedCacheTableLocks(p); p->inTrans = TRANS_READ; }else{ - /* If the handle had any kind of transaction open, decrement the - ** transaction count of the shared btree. If the transaction count + /* If the handle had any kind of transaction open, decrement the + ** transaction count of the shared btree. If the transaction count ** reaches 0, set the shared state to TRANS_NONE. The unlockBtreeIfUnused() ** call below will unlock the pager. */ if( p->inTrans!=TRANS_NONE ){ @@ -67209,7 +77589,7 @@ static void btreeEndTransaction(Btree *p){ } } - /* Set the current transaction state to TRANS_NONE and unlock the + /* Set the current transaction state to TRANS_NONE and unlock the ** pager if this call closed the only read or write transaction. */ p->inTrans = TRANS_NONE; unlockBtreeIfUnused(pBt); @@ -67230,12 +77610,12 @@ static void btreeEndTransaction(Btree *p){ ** the rollback journal (which causes the transaction to commit) and ** drop locks. ** -** Normally, if an error occurs while the pager layer is attempting to +** Normally, if an error occurs while the pager layer is attempting to ** finalize the underlying journal file, this function returns an error and ** the upper layer will attempt a rollback. However, if the second argument -** is non-zero then this b-tree transaction is part of a multi-file -** transaction. In this case, the transaction has already been committed -** (by deleting a master journal file) and the caller will ignore this +** is non-zero then this b-tree transaction is part of a multi-file +** transaction. In this case, the transaction has already been committed +** (by deleting a super-journal file) and the caller will ignore this ** functions return code. So, even if an error occurs in the pager layer, ** reset the b-tree objects internal state to indicate that the write ** transaction has been closed. This is quite safe, as the pager will have @@ -67250,7 +77630,7 @@ SQLITE_PRIVATE int sqlite3BtreeCommitPhaseTwo(Btree *p, int bCleanup){ sqlite3BtreeEnter(p); btreeIntegrity(p); - /* If the handle has a write-transaction open, commit the shared-btrees + /* If the handle has a write-transaction open, commit the shared-btrees ** transaction and set the shared state to TRANS_READ. */ if( p->inTrans==TRANS_WRITE ){ @@ -67263,7 +77643,7 @@ SQLITE_PRIVATE int sqlite3BtreeCommitPhaseTwo(Btree *p, int bCleanup){ sqlite3BtreeLeave(p); return rc; } - p->iDataVersion--; /* Compensate for pPager->iDataVersion++; */ + p->iBDataVersion--; /* Compensate for pPager->iDataVersion++; */ pBt->inTransaction = TRANS_READ; btreeClearHasContent(pBt); } @@ -67299,15 +77679,15 @@ SQLITE_PRIVATE int sqlite3BtreeCommit(Btree *p){ ** ** This routine gets called when a rollback occurs. If the writeOnly ** flag is true, then only write-cursors need be tripped - read-only -** cursors save their current positions so that they may continue -** following the rollback. Or, if writeOnly is false, all cursors are +** cursors save their current positions so that they may continue +** following the rollback. Or, if writeOnly is false, all cursors are ** tripped. In general, writeOnly is false if the transaction being ** rolled back modified the database schema. In this case b-tree root ** pages may be moved or deleted from the database altogether, making ** it unsafe for read cursors to continue. ** -** If the writeOnly flag is true and an error is encountered while -** saving the current position of a read-only cursor, all cursors, +** If the writeOnly flag is true and an error is encountered while +** saving the current position of a read-only cursor, all cursors, ** including all read-cursors are tripped. ** ** SQLITE_OK is returned if successful, or if an error occurs while @@ -67341,6 +77721,18 @@ SQLITE_PRIVATE int sqlite3BtreeTripAllCursors(Btree *pBtree, int errCode, int wr return rc; } +/* +** Set the pBt->nPage field correctly, according to the current +** state of the database. Assume pBt->pPage1 is valid. +*/ +static void btreeSetNPage(BtShared *pBt, MemPage *pPage1){ + int nPage = get4byte(&pPage1->aData[28]); + testcase( nPage==0 ); + if( nPage==0 ) sqlite3PagerPagecount(pBt->pPager, &nPage); + testcase( pBt->nPage!=(u32)nPage ); + pBt->nPage = nPage; +} + /* ** Rollback the transaction in progress. ** @@ -67386,11 +77778,7 @@ SQLITE_PRIVATE int sqlite3BtreeRollback(Btree *p, int tripCode, int writeOnly){ ** call btreeGetPage() on page 1 again to make ** sure pPage1->aData is set correctly. */ if( btreeGetPage(pBt, 1, &pPage1, 0)==SQLITE_OK ){ - int nPage = get4byte(28+(u8*)pPage1->aData); - testcase( nPage==0 ); - if( nPage==0 ) sqlite3PagerPagecount(pBt->pPager, &nPage); - testcase( pBt->nPage!=nPage ); - pBt->nPage = nPage; + btreeSetNPage(pBt, pPage1); releasePageOne(pPage1); } assert( countValidCursors(pBt, 1)==0 ); @@ -67405,8 +77793,8 @@ SQLITE_PRIVATE int sqlite3BtreeRollback(Btree *p, int tripCode, int writeOnly){ /* ** Start a statement subtransaction. The subtransaction can be rolled -** back independently of the main transaction. You must start a transaction -** before starting a subtransaction. The subtransaction is ended automatically +** back independently of the main transaction. You must start a transaction +** before starting a subtransaction. The subtransaction is ended automatically ** if the main transaction commits or rolls back. ** ** Statement subtransactions are used around individual SQL statements @@ -67443,11 +77831,11 @@ SQLITE_PRIVATE int sqlite3BtreeBeginStmt(Btree *p, int iStatement){ /* ** The second argument to this function, op, is always SAVEPOINT_ROLLBACK ** or SAVEPOINT_RELEASE. This function either releases or rolls back the -** savepoint identified by parameter iSavepoint, depending on the value +** savepoint identified by parameter iSavepoint, depending on the value ** of op. ** ** Normally, iSavepoint is greater than or equal to zero. However, if op is -** SAVEPOINT_ROLLBACK, then iSavepoint may also be -1. In this case the +** SAVEPOINT_ROLLBACK, then iSavepoint may also be -1. In this case the ** contents of the entire transaction are rolled back. This is different ** from a normal transaction rollback, as no locks are released and the ** transaction remains open. @@ -67470,12 +77858,11 @@ SQLITE_PRIVATE int sqlite3BtreeSavepoint(Btree *p, int op, int iSavepoint){ pBt->nPage = 0; } rc = newDatabase(pBt); - pBt->nPage = get4byte(28 + pBt->pPage1->aData); + btreeSetNPage(pBt, pBt->pPage1); - /* The database size was written into the offset 28 of the header - ** when the transaction started, so we know that the value at offset - ** 28 is nonzero. */ - assert( pBt->nPage>0 ); + /* pBt->nPage might be zero if the database was corrupt when + ** the transaction was started. Otherwise, it must be at least 1. */ + assert( CORRUPT_DB || pBt->nPage>0 ); } sqlite3BtreeLeave(p); } @@ -67511,10 +77898,10 @@ SQLITE_PRIVATE int sqlite3BtreeSavepoint(Btree *p, int op, int iSavepoint){ ** is set. If FORDELETE is set, that is a hint to the implementation that ** this cursor will only be used to seek to and delete entries of an index ** as part of a larger DELETE statement. The FORDELETE hint is not used by -** this implementation. But in a hypothetical alternative storage engine +** this implementation. But in a hypothetical alternative storage engine ** in which index entries are automatically deleted when corresponding table ** rows are deleted, the FORDELETE flag is a hint that all SEEK and DELETE -** operations on this cursor can be no-ops and all READ operations can +** operations on this cursor can be no-ops and all READ operations can ** return a null row (2-bytes: 0x01 0x00). ** ** No checking is done to make sure that page iTable really is the @@ -67526,7 +77913,7 @@ SQLITE_PRIVATE int sqlite3BtreeSavepoint(Btree *p, int op, int iSavepoint){ */ static int btreeCursor( Btree *p, /* The btree */ - int iTable, /* Root page of table to open */ + Pgno iTable, /* Root page of table to open */ int wrFlag, /* 1 to write. 0 read-only */ struct KeyInfo *pKeyInfo, /* First arg to comparison function */ BtCursor *pCur /* Space for new cursor */ @@ -67535,16 +77922,17 @@ static int btreeCursor( BtCursor *pX; /* Looping over other all cursors */ assert( sqlite3BtreeHoldsMutex(p) ); - assert( wrFlag==0 - || wrFlag==BTREE_WRCSR - || wrFlag==(BTREE_WRCSR|BTREE_FORDELETE) + assert( wrFlag==0 + || wrFlag==BTREE_WRCSR + || wrFlag==(BTREE_WRCSR|BTREE_FORDELETE) ); - /* The following assert statements verify that if this is a sharable - ** b-tree database, the connection is holding the required table locks, - ** and that no other connection has any open cursor that conflicts with - ** this lock. */ - assert( hasSharedCacheTableLock(p, iTable, pKeyInfo!=0, (wrFlag?2:1)) ); + /* The following assert statements verify that if this is a sharable + ** b-tree database, the connection is holding the required table locks, + ** and that no other connection has any open cursor that conflicts with + ** this lock. The iTable<1 term disables the check for corrupt schemas. */ + assert( hasSharedCacheTableLock(p, iTable, pKeyInfo!=0, (wrFlag?2:1)) + || iTable<1 ); assert( wrFlag==0 || !hasReadConflicts(p, iTable) ); /* Assert that the caller has opened the required transaction. */ @@ -67553,53 +77941,68 @@ static int btreeCursor( assert( pBt->pPage1 && pBt->pPage1->aData ); assert( wrFlag==0 || (pBt->btsFlags & BTS_READ_ONLY)==0 ); - if( wrFlag ){ - allocateTempSpace(pBt); - if( pBt->pTmpSpace==0 ) return SQLITE_NOMEM_BKPT; - } - if( iTable==1 && btreePagecount(pBt)==0 ){ - assert( wrFlag==0 ); - iTable = 0; + if( iTable<=1 ){ + if( iTable<1 ){ + return SQLITE_CORRUPT_BKPT; + }else if( btreePagecount(pBt)==0 ){ + assert( wrFlag==0 ); + iTable = 0; + } } /* Now that no other errors can occur, finish filling in the BtCursor ** variables and link the cursor into the BtShared list. */ - pCur->pgnoRoot = (Pgno)iTable; + pCur->pgnoRoot = iTable; pCur->iPage = -1; pCur->pKeyInfo = pKeyInfo; pCur->pBtree = p; pCur->pBt = pBt; - pCur->curFlags = wrFlag ? BTCF_WriteFlag : 0; - pCur->curPagerFlags = wrFlag ? 0 : PAGER_GET_READONLY; + pCur->curFlags = 0; /* If there are two or more cursors on the same btree, then all such ** cursors *must* have the BTCF_Multiple flag set. */ for(pX=pBt->pCursor; pX; pX=pX->pNext){ - if( pX->pgnoRoot==(Pgno)iTable ){ + if( pX->pgnoRoot==iTable ){ pX->curFlags |= BTCF_Multiple; - pCur->curFlags |= BTCF_Multiple; + pCur->curFlags = BTCF_Multiple; } } + pCur->eState = CURSOR_INVALID; pCur->pNext = pBt->pCursor; pBt->pCursor = pCur; - pCur->eState = CURSOR_INVALID; + if( wrFlag ){ + pCur->curFlags |= BTCF_WriteFlag; + pCur->curPagerFlags = 0; + if( pBt->pTmpSpace==0 ) return allocateTempSpace(pBt); + }else{ + pCur->curPagerFlags = PAGER_GET_READONLY; + } return SQLITE_OK; } +static int btreeCursorWithLock( + Btree *p, /* The btree */ + Pgno iTable, /* Root page of table to open */ + int wrFlag, /* 1 to write. 0 read-only */ + struct KeyInfo *pKeyInfo, /* First arg to comparison function */ + BtCursor *pCur /* Space for new cursor */ +){ + int rc; + sqlite3BtreeEnter(p); + rc = btreeCursor(p, iTable, wrFlag, pKeyInfo, pCur); + sqlite3BtreeLeave(p); + return rc; +} SQLITE_PRIVATE int sqlite3BtreeCursor( Btree *p, /* The btree */ - int iTable, /* Root page of table to open */ + Pgno iTable, /* Root page of table to open */ int wrFlag, /* 1 to write. 0 read-only */ struct KeyInfo *pKeyInfo, /* First arg to xCompare() */ BtCursor *pCur /* Write new cursor here */ ){ - int rc; - if( iTable<1 ){ - rc = SQLITE_CORRUPT_BKPT; + if( p->sharable ){ + return btreeCursorWithLock(p, iTable, wrFlag, pKeyInfo, pCur); }else{ - sqlite3BtreeEnter(p); - rc = btreeCursor(p, iTable, wrFlag, pKeyInfo, pCur); - sqlite3BtreeLeave(p); + return btreeCursor(p, iTable, wrFlag, pKeyInfo, pCur); } - return rc; } /* @@ -67614,6 +78017,25 @@ SQLITE_PRIVATE int sqlite3BtreeCursorSize(void){ return ROUND8(sizeof(BtCursor)); } +#ifdef SQLITE_DEBUG +/* +** Return true if and only if the Btree object will be automatically +** closed with the BtCursor closes. This is used within assert() statements +** only. +*/ +SQLITE_PRIVATE int sqlite3BtreeClosesWithCursor( + Btree *pBtree, /* the btree object */ + BtCursor *pCur /* Corresponding cursor */ +){ + BtShared *pBt = pBtree->pBt; + if( (pBt->openFlags & BTREE_SINGLE)==0 ) return 0; + if( pBt->pCursor!=pCur ) return 0; + if( pCur->pNext!=0 ) return 0; + if( pCur->pBtree!=pBtree ) return 0; + return 1; +} +#endif + /* ** Initialize memory that will be converted into a BtCursor object. ** @@ -67652,7 +78074,14 @@ SQLITE_PRIVATE int sqlite3BtreeCloseCursor(BtCursor *pCur){ unlockBtreeIfUnused(pBt); sqlite3_free(pCur->aOverflow); sqlite3_free(pCur->pKey); - sqlite3BtreeLeave(pBtree); + if( (pBt->openFlags & BTREE_SINGLE) && pBt->pCursor==0 ){ + /* Since the BtShared is not sharable, there is no need to + ** worry about the missing sqlite3BtreeLeave() call here. */ + assert( pBtree->sharable==0 ); + sqlite3BtreeClose(pBtree); + }else{ + sqlite3BtreeLeave(pBtree); + } pCur->pBtree = 0; } return SQLITE_OK; @@ -67722,7 +78151,18 @@ SQLITE_PRIVATE i64 sqlite3BtreeIntegerKey(BtCursor *pCur){ return pCur->info.nKey; } -#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC +/* +** Pin or unpin a cursor. +*/ +SQLITE_PRIVATE void sqlite3BtreeCursorPin(BtCursor *pCur){ + assert( (pCur->curFlags & BTCF_Pinned)==0 ); + pCur->curFlags |= BTCF_Pinned; +} +SQLITE_PRIVATE void sqlite3BtreeCursorUnpin(BtCursor *pCur){ + assert( (pCur->curFlags & BTCF_Pinned)!=0 ); + pCur->curFlags &= ~BTCF_Pinned; +} + /* ** Return the offset into the database file for the start of the ** payload to which the cursor is pointing. @@ -67734,7 +78174,6 @@ SQLITE_PRIVATE i64 sqlite3BtreeOffset(BtCursor *pCur){ return (i64)pCur->pBt->pageSize*((i64)pCur->pPage->pgno - 1) + (i64)(pCur->info.pPayload - pCur->pPage->aData); } -#endif /* SQLITE_ENABLE_OFFSET_SQL_FUNC */ /* ** Return the number of bytes of payload for the entry that pCur is @@ -67760,7 +78199,7 @@ SQLITE_PRIVATE u32 sqlite3BtreePayloadSize(BtCursor *pCur){ ** routine always returns 2147483647 (which is the largest record ** that SQLite can handle) or more. But returning a smaller value might ** prevent large memory allocations when trying to interpret a -** corrupt datrabase. +** corrupt database. ** ** The current implementation merely returns the size of the underlying ** database file. @@ -67773,15 +78212,15 @@ SQLITE_PRIVATE sqlite3_int64 sqlite3BtreeMaxRecordSize(BtCursor *pCur){ /* ** Given the page number of an overflow page in the database (parameter -** ovfl), this function finds the page number of the next page in the +** ovfl), this function finds the page number of the next page in the ** linked list of overflow pages. If possible, it uses the auto-vacuum -** pointer-map data instead of reading the content of page ovfl to do so. +** pointer-map data instead of reading the content of page ovfl to do so. ** ** If an error occurs an SQLite error code is returned. Otherwise: ** -** The page number of the next overflow page in the linked list is -** written to *pPgnoNext. If page ovfl is the last page in its linked -** list, *pPgnoNext is set to zero. +** The page number of the next overflow page in the linked list is +** written to *pPgnoNext. If page ovfl is the last page in its linked +** list, *pPgnoNext is set to zero. ** ** If ppPage is not NULL, and a reference to the MemPage object corresponding ** to page number pOvfl was obtained, then *ppPage is set to point to that @@ -67805,9 +78244,9 @@ static int getOverflowPage( #ifndef SQLITE_OMIT_AUTOVACUUM /* Try to find the next page in the overflow list using the - ** autovacuum pointer-map pages. Guess that the next page in - ** the overflow list is page number (ovfl+1). If that guess turns - ** out to be wrong, fall back to loading the data of page + ** autovacuum pointer-map pages. Guess that the next page in + ** the overflow list is page number (ovfl+1). If that guess turns + ** out to be wrong, fall back to loading the data of page ** number ovfl to determine the next page number. */ if( pBt->autoVacuum ){ @@ -67895,8 +78334,8 @@ static int copyPayload( ** ** If the current cursor entry uses one or more overflow pages ** this function may allocate space for and lazily populate -** the overflow page-list cache array (BtCursor.aOverflow). -** Subsequent calls use this cache to make seeking to the supplied offset +** the overflow page-list cache array (BtCursor.aOverflow). +** Subsequent calls use this cache to make seeking to the supplied offset ** more efficient. ** ** Once an overflow page-list cache has been allocated, it must be @@ -67912,7 +78351,7 @@ static int accessPayload( BtCursor *pCur, /* Cursor pointing to entry to read from */ u32 offset, /* Begin reading this far into payload */ u32 amt, /* Read this many bytes */ - unsigned char *pBuf, /* Write the bytes into this buffer */ + unsigned char *pBuf, /* Write the bytes into this buffer */ int eOp /* zero to read. non-zero to write. */ ){ unsigned char *aPayload; @@ -67927,12 +78366,14 @@ static int accessPayload( assert( pPage ); assert( eOp==0 || eOp==1 ); assert( pCur->eState==CURSOR_VALID ); - assert( pCur->ixnCell ); + if( pCur->ix>=pPage->nCell ){ + return SQLITE_CORRUPT_PAGE(pPage); + } assert( cursorHoldsMutex(pCur) ); getCellInfo(pCur); aPayload = pCur->info.pPayload; - assert( offset+amt <= pCur->info.nPayload ); + assert( (u64)offset+(u64)amt <= (u64)pCur->info.nPayload ); assert( aPayload > pPage->aData ); if( (uptr)(aPayload - pPage->aData) > (pBt->usableSize - pCur->info.nLocal) ){ @@ -67973,13 +78414,18 @@ static int accessPayload( ** means "not yet known" (the cache is lazily populated). */ if( (pCur->curFlags & BTCF_ValidOvfl)==0 ){ - int nOvfl = (pCur->info.nPayload-pCur->info.nLocal+ovflSize-1)/ovflSize; + i64 nOvfl = pCur->info.nPayload; + testcase( nOvfl - pCur->info.nLocal + ovflSize - 1 > 0xffffffffU ); + nOvfl = (nOvfl - pCur->info.nLocal + ovflSize-1)/ovflSize; if( pCur->aOverflow==0 || nOvfl*(int)sizeof(Pgno) > sqlite3MallocSize(pCur->aOverflow) ){ - Pgno *aNew = (Pgno*)sqlite3Realloc( - pCur->aOverflow, nOvfl*2*sizeof(Pgno) - ); + Pgno *aNew; + if( sqlite3FaultSim(413) ){ + aNew = 0; + }else{ + aNew = (Pgno*)sqlite3Realloc(pCur->aOverflow, nOvfl*2*sizeof(Pgno)); + } if( aNew==0 ){ return SQLITE_NOMEM_BKPT; }else{ @@ -67989,6 +78435,12 @@ static int accessPayload( memset(pCur->aOverflow, 0, nOvfl*sizeof(Pgno)); pCur->curFlags |= BTCF_ValidOvfl; }else{ + /* Sanity check the validity of the overflow page cache */ + assert( pCur->aOverflow[0]==nextPage + || pCur->aOverflow[0]==0 + || CORRUPT_DB ); + assert( pCur->aOverflow[0]!=0 || pCur->aOverflow[offset/ovflSize]==0 ); + /* If the overflow page-list cache has been allocated and the ** entry for the first required overflow page is valid, skip ** directly to it. @@ -68003,6 +78455,7 @@ static int accessPayload( assert( rc==SQLITE_OK && amt>0 ); while( nextPage ){ /* If required, populate the overflow page-list cache. */ + if( nextPage > pBt->nPage ) return SQLITE_CORRUPT_BKPT; assert( pCur->aOverflow[iIdx]==0 || pCur->aOverflow[iIdx]==nextPage || CORRUPT_DB ); @@ -68035,12 +78488,12 @@ static int accessPayload( #ifdef SQLITE_DIRECT_OVERFLOW_READ /* If all the following are true: ** - ** 1) this is a read operation, and + ** 1) this is a read operation, and ** 2) data is required from the start of this overflow page, and ** 3) there are no dirty pages in the page-cache ** 4) the database is file-backed, and ** 5) the page is not in the WAL file - ** 6) at least 4 bytes have already been read into the output buffer + ** 6) at least 4 bytes have already been read into the output buffer ** ** then data can be read directly from the database file into the ** output buffer, bypassing the page-cache altogether. This speeds @@ -68068,6 +78521,12 @@ static int accessPayload( (eOp==0 ? PAGER_GET_READONLY : 0) ); if( rc==SQLITE_OK ){ + if( eOp!=0 + && (sqlite3PagerPageRefcount(pDbPage)!=1 + || NEVER(((MemPage*)sqlite3PagerGetExtra(pDbPage))->isInit)) ){ + sqlite3PagerUnref(pDbPage); + return SQLITE_CORRUPT_PAGE(pPage); + } aPayload = sqlite3PagerGetData(pDbPage); nextPage = get4byte(aPayload); rc = copyPayload(&aPayload[offset+4], pBuf, a, eOp, pDbPage); @@ -68112,7 +78571,6 @@ SQLITE_PRIVATE int sqlite3BtreePayload(BtCursor *pCur, u32 offset, u32 amt, void assert( cursorHoldsMutex(pCur) ); assert( pCur->eState==CURSOR_VALID ); assert( pCur->iPage>=0 && pCur->pPage ); - assert( pCur->ixpPage->nCell ); return accessPayload(pCur, offset, amt, (unsigned char*)pBuf, 0); } @@ -68147,7 +78605,7 @@ SQLITE_PRIVATE int sqlite3BtreePayloadChecked(BtCursor *pCur, u32 offset, u32 am #endif /* SQLITE_OMIT_INCRBLOB */ /* -** Return a pointer to payload information from the entry that the +** Return a pointer to payload information from the entry that the ** pCur cursor is pointing to. The pointer is to the beginning of ** the key if index btrees (pPage->intKey==0) and is the data for ** table btrees (pPage->intKey==1). The number of bytes of available @@ -68174,7 +78632,7 @@ static const void *fetchPayload( assert( pCur->eState==CURSOR_VALID ); assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) ); assert( cursorOwnsBtShared(pCur) ); - assert( pCur->ixpPage->nCell ); + assert( pCur->ixpPage->nCell || CORRUPT_DB ); assert( pCur->info.nSize>0 ); assert( pCur->info.pPayload>pCur->pPage->aData || CORRUPT_DB ); assert( pCur->info.pPayloadpPage->aDataEnd ||CORRUPT_DB); @@ -68219,8 +78677,7 @@ SQLITE_PRIVATE const void *sqlite3BtreePayloadFetch(BtCursor *pCur, u32 *pAmt){ ** vice-versa). */ static int moveToChild(BtCursor *pCur, u32 newPgno){ - BtShared *pBt = pCur->pBt; - + int rc; assert( cursorOwnsBtShared(pCur) ); assert( pCur->eState==CURSOR_VALID ); assert( pCur->iPageapPage[pCur->iPage] = pCur->pPage; pCur->ix = 0; pCur->iPage++; - return getAndInitPage(pBt, newPgno, &pCur->pPage, pCur, pCur->curPagerFlags); + rc = getAndInitPage(pCur->pBt, newPgno, &pCur->pPage, pCur->curPagerFlags); + assert( pCur->pPage!=0 || rc!=SQLITE_OK ); + if( rc==SQLITE_OK + && (pCur->pPage->nCell<1 || pCur->pPage->intKey!=pCur->curIntKey) + ){ + releasePage(pCur->pPage); + rc = SQLITE_CORRUPT_PGNO(newPgno); + } + if( rc ){ + pCur->pPage = pCur->apPage[--pCur->iPage]; + } + return rc; } #ifdef SQLITE_DEBUG /* -** Page pParent is an internal (non-leaf) tree page. This function +** Page pParent is an internal (non-leaf) tree page. This function ** asserts that page number iChild is the left-child if the iIdx'th ** cell in page pParent. Or, if iIdx is equal to the total number of ** cells in pParent, that page number iChild is the right-child of @@ -68256,7 +78724,7 @@ static void assertParentIndex(MemPage *pParent, int iIdx, Pgno iChild){ } } #else -# define assertParentIndex(x,y,z) +# define assertParentIndex(x,y,z) #endif /* @@ -68274,8 +78742,8 @@ static void moveToParent(BtCursor *pCur){ assert( pCur->iPage>0 ); assert( pCur->pPage ); assertParentIndex( - pCur->apPage[pCur->iPage-1], - pCur->aiIdx[pCur->iPage-1], + pCur->apPage[pCur->iPage-1], + pCur->aiIdx[pCur->iPage-1], pCur->pPage->pgno ); testcase( pCur->aiIdx[pCur->iPage-1] > pCur->apPage[pCur->iPage-1]->nCell ); @@ -68292,19 +78760,19 @@ static void moveToParent(BtCursor *pCur){ ** ** If the table has a virtual root page, then the cursor is moved to point ** to the virtual root page instead of the actual root page. A table has a -** virtual root page when the actual root page contains no cells and a +** virtual root page when the actual root page contains no cells and a ** single child page. This can only happen with the table rooted at page 1. ** -** If the b-tree structure is empty, the cursor state is set to +** If the b-tree structure is empty, the cursor state is set to ** CURSOR_INVALID and this routine returns SQLITE_EMPTY. Otherwise, ** the cursor is set to point to the first cell located on the root ** (or virtual root) page and the cursor state is set to CURSOR_VALID. ** ** If this function returns successfully, it may be assumed that the -** page-header flags indicate that the [virtual] root-page is the expected +** page-header flags indicate that the [virtual] root-page is the expected ** kind of b-tree page (i.e. if when opening the cursor the caller did not ** specify a KeyInfo structure the flags byte is set to 0x05 or 0x0D, -** indicating a table b-tree, or if the caller did specify a KeyInfo +** indicating a table b-tree, or if the caller did specify a KeyInfo ** structure the flags byte is set to 0x02 or 0x0A, indicating an index ** b-tree). */ @@ -68325,7 +78793,7 @@ static int moveToRoot(BtCursor *pCur){ while( --pCur->iPage ){ releasePageNotNull(pCur->apPage[pCur->iPage]); } - pCur->pPage = pCur->apPage[0]; + pRoot = pCur->pPage = pCur->apPage[0]; goto skip_init; } }else if( pCur->pgnoRoot==0 ){ @@ -68340,8 +78808,8 @@ static int moveToRoot(BtCursor *pCur){ } sqlite3BtreeClearCursor(pCur); } - rc = getAndInitPage(pCur->pBtree->pBt, pCur->pgnoRoot, &pCur->pPage, - 0, pCur->curPagerFlags); + rc = getAndInitPage(pCur->pBt, pCur->pgnoRoot, &pCur->pPage, + pCur->curPagerFlags); if( rc!=SQLITE_OK ){ pCur->eState = CURSOR_INVALID; return rc; @@ -68350,29 +78818,28 @@ static int moveToRoot(BtCursor *pCur){ pCur->curIntKey = pCur->pPage->intKey; } pRoot = pCur->pPage; - assert( pRoot->pgno==pCur->pgnoRoot ); + assert( pRoot->pgno==pCur->pgnoRoot || CORRUPT_DB ); /* If pCur->pKeyInfo is not NULL, then the caller that opened this cursor ** expected to open it on an index b-tree. Otherwise, if pKeyInfo is ** NULL, the caller expects a table b-tree. If this is not the case, - ** return an SQLITE_CORRUPT error. + ** return an SQLITE_CORRUPT error. ** ** Earlier versions of SQLite assumed that this test could not fail ** if the root page was already loaded when this function was called (i.e. - ** if pCur->iPage>=0). But this is not so if the database is corrupted - ** in such a way that page pRoot is linked into a second b-tree table + ** if pCur->iPage>=0). But this is not so if the database is corrupted + ** in such a way that page pRoot is linked into a second b-tree table ** (or the freelist). */ assert( pRoot->intKey==1 || pRoot->intKey==0 ); if( pRoot->isInit==0 || (pCur->pKeyInfo==0)!=pRoot->intKey ){ return SQLITE_CORRUPT_PAGE(pCur->pPage); } -skip_init: +skip_init: pCur->ix = 0; pCur->info.nSize = 0; pCur->curFlags &= ~(BTCF_AtLast|BTCF_ValidNKey|BTCF_ValidOvfl); - pRoot = pCur->pPage; if( pRoot->nCell>0 ){ pCur->eState = CURSOR_VALID; }else if( !pRoot->leaf ){ @@ -68454,39 +78921,60 @@ SQLITE_PRIVATE int sqlite3BtreeFirst(BtCursor *pCur, int *pRes){ *pRes = 0; rc = moveToLeftmost(pCur); }else if( rc==SQLITE_EMPTY ){ - assert( pCur->pgnoRoot==0 || pCur->pPage->nCell==0 ); + assert( pCur->pgnoRoot==0 || (pCur->pPage!=0 && pCur->pPage->nCell==0) ); *pRes = 1; rc = SQLITE_OK; } return rc; } -/* Move the cursor to the last entry in the table. Return SQLITE_OK -** on success. Set *pRes to 0 if the cursor actually points to something -** or set *pRes to 1 if the table is empty. +/* Set *pRes to 1 (true) if the BTree pointed to by cursor pCur contains zero +** rows of content. Set *pRes to 0 (false) if the table contains content. +** Return SQLITE_OK on success or some error code (ex: SQLITE_NOMEM) if +** something goes wrong. */ -SQLITE_PRIVATE int sqlite3BtreeLast(BtCursor *pCur, int *pRes){ +SQLITE_PRIVATE int sqlite3BtreeIsEmpty(BtCursor *pCur, int *pRes){ int rc; - + assert( cursorOwnsBtShared(pCur) ); assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) ); + if( NEVER(pCur->eState==CURSOR_VALID) ){ + *pRes = 0; + return SQLITE_OK; + } + rc = moveToRoot(pCur); + if( rc==SQLITE_EMPTY ){ + *pRes = 1; + rc = SQLITE_OK; + }else{ + *pRes = 0; + } + return rc; +} - /* If the cursor already points to the last entry, this is a no-op. */ - if( CURSOR_VALID==pCur->eState && (pCur->curFlags & BTCF_AtLast)!=0 ){ #ifdef SQLITE_DEBUG - /* This block serves to assert() that the cursor really does point - ** to the last entry in the b-tree. */ - int ii; - for(ii=0; iiiPage; ii++){ - assert( pCur->aiIdx[ii]==pCur->apPage[ii]->nCell ); - } - assert( pCur->ix==pCur->pPage->nCell-1 ); - assert( pCur->pPage->leaf ); -#endif - return SQLITE_OK; +/* The cursors is CURSOR_VALID and has BTCF_AtLast set. Verify that +** this flags are true for a consistent database. +** +** This routine is is called from within assert() statements only. +** It is an internal verification routine and does not appear in production +** builds. +*/ +static int cursorIsAtLastEntry(BtCursor *pCur){ + int ii; + for(ii=0; iiiPage; ii++){ + if( pCur->aiIdx[ii]!=pCur->apPage[ii]->nCell ) return 0; } + return pCur->ix==pCur->pPage->nCell-1 && pCur->pPage->leaf!=0; +} +#endif - rc = moveToRoot(pCur); +/* Move the cursor to the last entry in the table. Return SQLITE_OK +** on success. Set *pRes to 0 if the cursor actually points to something +** or set *pRes to 1 if the table is empty. +*/ +static SQLITE_NOINLINE int btreeLast(BtCursor *pCur, int *pRes){ + int rc = moveToRoot(pCur); if( rc==SQLITE_OK ){ assert( pCur->eState==CURSOR_VALID ); *pRes = 0; @@ -68503,13 +78991,21 @@ SQLITE_PRIVATE int sqlite3BtreeLast(BtCursor *pCur, int *pRes){ } return rc; } +SQLITE_PRIVATE int sqlite3BtreeLast(BtCursor *pCur, int *pRes){ + assert( cursorOwnsBtShared(pCur) ); + assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) ); -/* Move the cursor so that it points to an entry near the key -** specified by pIdxKey or intKey. Return a success code. -** -** For INTKEY tables, the intKey parameter is used. pIdxKey -** must be NULL. For index tables, pIdxKey is used and intKey -** is ignored. + /* If the cursor already points to the last entry, this is a no-op. */ + if( CURSOR_VALID==pCur->eState && (pCur->curFlags & BTCF_AtLast)!=0 ){ + assert( cursorIsAtLastEntry(pCur) || CORRUPT_DB ); + *pRes = 0; + return SQLITE_OK; + } + return btreeLast(pCur, pRes); +} + +/* Move the cursor so that it points to an entry in a table (a.k.a INTKEY) +** table near the key intKey. Return a success code. ** ** If an exact match is not found, then the cursor is always ** left pointing at a leaf page which would hold the entry if it @@ -68517,57 +79013,51 @@ SQLITE_PRIVATE int sqlite3BtreeLast(BtCursor *pCur, int *pRes){ ** before or after the key. ** ** An integer is written into *pRes which is the result of -** comparing the key with the entry to which the cursor is +** comparing the key with the entry to which the cursor is ** pointing. The meaning of the integer written into ** *pRes is as follows: ** ** *pRes<0 The cursor is left pointing at an entry that -** is smaller than intKey/pIdxKey or if the table is empty +** is smaller than intKey or if the table is empty ** and the cursor is therefore left point to nothing. ** ** *pRes==0 The cursor is left pointing at an entry that -** exactly matches intKey/pIdxKey. +** exactly matches intKey. ** ** *pRes>0 The cursor is left pointing at an entry that -** is larger than intKey/pIdxKey. -** -** For index tables, the pIdxKey->eqSeen field is set to 1 if there -** exists an entry in the table that exactly matches pIdxKey. +** is larger than intKey. */ -SQLITE_PRIVATE int sqlite3BtreeMovetoUnpacked( +SQLITE_PRIVATE int sqlite3BtreeTableMoveto( BtCursor *pCur, /* The cursor to be moved */ - UnpackedRecord *pIdxKey, /* Unpacked index key */ i64 intKey, /* The table key */ int biasRight, /* If true, bias the search to the high end */ int *pRes /* Write search results here */ ){ int rc; - RecordCompare xRecordCompare; assert( cursorOwnsBtShared(pCur) ); assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) ); assert( pRes ); - assert( (pIdxKey==0)==(pCur->pKeyInfo==0) ); - assert( pCur->eState!=CURSOR_VALID || (pIdxKey==0)==(pCur->curIntKey!=0) ); + assert( pCur->pKeyInfo==0 ); + assert( pCur->eState!=CURSOR_VALID || pCur->curIntKey!=0 ); /* If the cursor is already positioned at the point we are trying ** to move to, then just return without doing any work */ - if( pIdxKey==0 - && pCur->eState==CURSOR_VALID && (pCur->curFlags & BTCF_ValidNKey)!=0 - ){ + if( pCur->eState==CURSOR_VALID && (pCur->curFlags & BTCF_ValidNKey)!=0 ){ if( pCur->info.nKey==intKey ){ *pRes = 0; return SQLITE_OK; } if( pCur->info.nKeycurFlags & BTCF_AtLast)!=0 ){ + assert( cursorIsAtLastEntry(pCur) || CORRUPT_DB ); *pRes = -1; return SQLITE_OK; } /* If the requested key is one more than the previous key, then ** try to get there using sqlite3BtreeNext() rather than a full ** binary search. This is an optimization only. The correct answer - ** is still obtained without this case, only a little more slowely */ + ** is still obtained without this case, only a little more slowly. */ if( pCur->info.nKey+1==intKey ){ *pRes = 0; rc = sqlite3BtreeNext(pCur, 0); @@ -68576,25 +79066,16 @@ SQLITE_PRIVATE int sqlite3BtreeMovetoUnpacked( if( pCur->info.nKey==intKey ){ return SQLITE_OK; } - }else if( rc==SQLITE_DONE ){ - rc = SQLITE_OK; - }else{ + }else if( rc!=SQLITE_DONE ){ return rc; } } } } - if( pIdxKey ){ - xRecordCompare = sqlite3VdbeFindCompare(pIdxKey); - pIdxKey->errCode = 0; - assert( pIdxKey->default_rc==1 - || pIdxKey->default_rc==0 - || pIdxKey->default_rc==-1 - ); - }else{ - xRecordCompare = 0; /* All keys are integers */ - } +#ifdef SQLITE_DEBUG + pCur->pBtree->nSeek++; /* Performance measurement during testing */ +#endif rc = moveToRoot(pCur); if( rc ){ @@ -68610,7 +79091,8 @@ SQLITE_PRIVATE int sqlite3BtreeMovetoUnpacked( assert( pCur->eState==CURSOR_VALID ); assert( pCur->pPage->nCell > 0 ); assert( pCur->iPage==0 || pCur->apPage[0]->intKey==pCur->curIntKey ); - assert( pCur->curIntKey || pIdxKey ); + assert( pCur->curIntKey ); + for(;;){ int lwr, upr, idx, c; Pgno chldPg; @@ -68624,142 +79106,55 @@ SQLITE_PRIVATE int sqlite3BtreeMovetoUnpacked( ** be the right kind (index or table) of b-tree page. Otherwise ** a moveToChild() or moveToRoot() call would have detected corruption. */ assert( pPage->nCell>0 ); - assert( pPage->intKey==(pIdxKey==0) ); + assert( pPage->intKey ); lwr = 0; upr = pPage->nCell-1; assert( biasRight==0 || biasRight==1 ); idx = upr>>(1-biasRight); /* idx = biasRight ? upr : (lwr+upr)/2; */ - pCur->ix = (u16)idx; - if( xRecordCompare==0 ){ - for(;;){ - i64 nCellKey; - pCell = findCellPastPtr(pPage, idx); - if( pPage->intKeyLeaf ){ - while( 0x80 <= *(pCell++) ){ - if( pCell>=pPage->aDataEnd ){ - return SQLITE_CORRUPT_PAGE(pPage); - } - } - } - getVarint(pCell, (u64*)&nCellKey); - if( nCellKeyupr ){ c = -1; break; } - }else if( nCellKey>intKey ){ - upr = idx-1; - if( lwr>upr ){ c = +1; break; } - }else{ - assert( nCellKey==intKey ); - pCur->ix = (u16)idx; - if( !pPage->leaf ){ - lwr = idx; - goto moveto_next_layer; - }else{ - pCur->curFlags |= BTCF_ValidNKey; - pCur->info.nKey = nCellKey; - pCur->info.nSize = 0; - *pRes = 0; - return SQLITE_OK; + for(;;){ + i64 nCellKey; + pCell = findCellPastPtr(pPage, idx); + if( pPage->intKeyLeaf ){ + while( 0x80 <= *(pCell++) ){ + if( pCell>=pPage->aDataEnd ){ + return SQLITE_CORRUPT_PAGE(pPage); } } - assert( lwr+upr>=0 ); - idx = (lwr+upr)>>1; /* idx = (lwr+upr)/2; */ } - }else{ - for(;;){ - int nCell; /* Size of the pCell cell in bytes */ - pCell = findCellPastPtr(pPage, idx); - - /* The maximum supported page-size is 65536 bytes. This means that - ** the maximum number of record bytes stored on an index B-Tree - ** page is less than 16384 bytes and may be stored as a 2-byte - ** varint. This information is used to attempt to avoid parsing - ** the entire cell by checking for the cases where the record is - ** stored entirely within the b-tree page by inspecting the first - ** 2 bytes of the cell. - */ - nCell = pCell[0]; - if( nCell<=pPage->max1bytePayload ){ - /* This branch runs if the record-size field of the cell is a - ** single byte varint and the record fits entirely on the main - ** b-tree page. */ - testcase( pCell+nCell+1==pPage->aDataEnd ); - c = xRecordCompare(nCell, (void*)&pCell[1], pIdxKey); - }else if( !(pCell[1] & 0x80) - && (nCell = ((nCell&0x7f)<<7) + pCell[1])<=pPage->maxLocal - ){ - /* The record-size field is a 2 byte varint and the record - ** fits entirely on the main b-tree page. */ - testcase( pCell+nCell+2==pPage->aDataEnd ); - c = xRecordCompare(nCell, (void*)&pCell[2], pIdxKey); - }else{ - /* The record flows over onto one or more overflow pages. In - ** this case the whole cell needs to be parsed, a buffer allocated - ** and accessPayload() used to retrieve the record into the - ** buffer before VdbeRecordCompare() can be called. - ** - ** If the record is corrupt, the xRecordCompare routine may read - ** up to two varints past the end of the buffer. An extra 18 - ** bytes of padding is allocated at the end of the buffer in - ** case this happens. */ - void *pCellKey; - u8 * const pCellBody = pCell - pPage->childPtrSize; - pPage->xParseCell(pPage, pCellBody, &pCur->info); - nCell = (int)pCur->info.nKey; - testcase( nCell<0 ); /* True if key size is 2^32 or more */ - testcase( nCell==0 ); /* Invalid key size: 0x80 0x80 0x00 */ - testcase( nCell==1 ); /* Invalid key size: 0x80 0x80 0x01 */ - testcase( nCell==2 ); /* Minimum legal index key size */ - if( nCell<2 || nCell/pCur->pBt->usableSize>pCur->pBt->nPage ){ - rc = SQLITE_CORRUPT_PAGE(pPage); - goto moveto_finish; - } - pCellKey = sqlite3Malloc( nCell+18 ); - if( pCellKey==0 ){ - rc = SQLITE_NOMEM_BKPT; - goto moveto_finish; - } - pCur->ix = (u16)idx; - rc = accessPayload(pCur, 0, nCell, (unsigned char*)pCellKey, 0); - pCur->curFlags &= ~BTCF_ValidOvfl; - if( rc ){ - sqlite3_free(pCellKey); - goto moveto_finish; - } - c = sqlite3VdbeRecordCompare(nCell, pCellKey, pIdxKey); - sqlite3_free(pCellKey); - } - assert( - (pIdxKey->errCode!=SQLITE_CORRUPT || c==0) - && (pIdxKey->errCode!=SQLITE_NOMEM || pCur->pBtree->db->mallocFailed) - ); - if( c<0 ){ - lwr = idx+1; - }else if( c>0 ){ - upr = idx-1; + getVarint(pCell, (u64*)&nCellKey); + if( nCellKeyupr ){ c = -1; break; } + }else if( nCellKey>intKey ){ + upr = idx-1; + if( lwr>upr ){ c = +1; break; } + }else{ + assert( nCellKey==intKey ); + pCur->ix = (u16)idx; + if( !pPage->leaf ){ + lwr = idx; + goto moveto_table_next_layer; }else{ - assert( c==0 ); + pCur->curFlags |= BTCF_ValidNKey; + pCur->info.nKey = nCellKey; + pCur->info.nSize = 0; *pRes = 0; - rc = SQLITE_OK; - pCur->ix = (u16)idx; - if( pIdxKey->errCode ) rc = SQLITE_CORRUPT_BKPT; - goto moveto_finish; + return SQLITE_OK; } - if( lwr>upr ) break; - assert( lwr+upr>=0 ); - idx = (lwr+upr)>>1; /* idx = (lwr+upr)/2 */ } + assert( lwr+upr>=0 ); + idx = (lwr+upr)>>1; /* idx = (lwr+upr)/2; */ } - assert( lwr==upr+1 || (pPage->intKey && !pPage->leaf) ); + assert( lwr==upr+1 || !pPage->leaf ); assert( pPage->isInit ); if( pPage->leaf ){ assert( pCur->ixpPage->nCell ); pCur->ix = (u16)idx; *pRes = c; rc = SQLITE_OK; - goto moveto_finish; + goto moveto_table_finish; } -moveto_next_layer: +moveto_table_next_layer: if( lwr>=pPage->nCell ){ chldPg = get4byte(&pPage->aData[pPage->hdrOffset+8]); }else{ @@ -68769,7 +79164,328 @@ SQLITE_PRIVATE int sqlite3BtreeMovetoUnpacked( rc = moveToChild(pCur, chldPg); if( rc ) break; } -moveto_finish: +moveto_table_finish: + pCur->info.nSize = 0; + assert( (pCur->curFlags & BTCF_ValidOvfl)==0 ); + return rc; +} + +/* +** Compare the "idx"-th cell on the page pPage against the key +** pointing to by pIdxKey using xRecordCompare. Return negative or +** zero if the cell is less than or equal pIdxKey. Return positive +** if unknown. +** +** Return value negative: Cell at pCur[idx] less than pIdxKey +** +** Return value is zero: Cell at pCur[idx] equals pIdxKey +** +** Return value positive: Nothing is known about the relationship +** of the cell at pCur[idx] and pIdxKey. +** +** This routine is part of an optimization. It is always safe to return +** a positive value as that will cause the optimization to be skipped. +*/ +static int indexCellCompare( + MemPage *pPage, + int idx, + UnpackedRecord *pIdxKey, + RecordCompare xRecordCompare +){ + int c; + int nCell; /* Size of the pCell cell in bytes */ + u8 *pCell = findCellPastPtr(pPage, idx); + + nCell = pCell[0]; + if( nCell<=pPage->max1bytePayload ){ + /* This branch runs if the record-size field of the cell is a + ** single byte varint and the record fits entirely on the main + ** b-tree page. */ + if( pCell + nCell >= pPage->aDataEnd ) return 99; + c = xRecordCompare(nCell, (void*)&pCell[1], pIdxKey); + }else if( !(pCell[1] & 0x80) + && (nCell = ((nCell&0x7f)<<7) + pCell[1])<=pPage->maxLocal + ){ + /* The record-size field is a 2 byte varint and the record + ** fits entirely on the main b-tree page. */ + if( pCell + nCell >= pPage->aDataEnd ) return 99; + c = xRecordCompare(nCell, (void*)&pCell[2], pIdxKey); + }else{ + /* If the record extends into overflow pages, do not attempt + ** the optimization. */ + c = 99; + } + return c; +} + +/* +** Return true (non-zero) if pCur is current pointing to the last +** page of a table. +*/ +static int cursorOnLastPage(BtCursor *pCur){ + int i; + assert( pCur->eState==CURSOR_VALID ); + for(i=0; iiPage; i++){ + MemPage *pPage = pCur->apPage[i]; + if( pCur->aiIdx[i]nCell ) return 0; + } + return 1; +} + +/* Move the cursor so that it points to an entry in an index table +** near the key pIdxKey. Return a success code. +** +** If an exact match is not found, then the cursor is always +** left pointing at a leaf page which would hold the entry if it +** were present. The cursor might point to an entry that comes +** before or after the key. +** +** An integer is written into *pRes which is the result of +** comparing the key with the entry to which the cursor is +** pointing. The meaning of the integer written into +** *pRes is as follows: +** +** *pRes<0 The cursor is left pointing at an entry that +** is smaller than pIdxKey or if the table is empty +** and the cursor is therefore left point to nothing. +** +** *pRes==0 The cursor is left pointing at an entry that +** exactly matches pIdxKey. +** +** *pRes>0 The cursor is left pointing at an entry that +** is larger than pIdxKey. +** +** The pIdxKey->eqSeen field is set to 1 if there +** exists an entry in the table that exactly matches pIdxKey. +*/ +SQLITE_PRIVATE int sqlite3BtreeIndexMoveto( + BtCursor *pCur, /* The cursor to be moved */ + UnpackedRecord *pIdxKey, /* Unpacked index key */ + int *pRes /* Write search results here */ +){ + int rc; + RecordCompare xRecordCompare; + + assert( cursorOwnsBtShared(pCur) ); + assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) ); + assert( pRes ); + assert( pCur->pKeyInfo!=0 ); + +#ifdef SQLITE_DEBUG + pCur->pBtree->nSeek++; /* Performance measurement during testing */ +#endif + + xRecordCompare = sqlite3VdbeFindCompare(pIdxKey); + pIdxKey->errCode = 0; + assert( pIdxKey->default_rc==1 + || pIdxKey->default_rc==0 + || pIdxKey->default_rc==-1 + ); + + + /* Check to see if we can skip a lot of work. Two cases: + ** + ** (1) If the cursor is already pointing to the very last cell + ** in the table and the pIdxKey search key is greater than or + ** equal to that last cell, then no movement is required. + ** + ** (2) If the cursor is on the last page of the table and the first + ** cell on that last page is less than or equal to the pIdxKey + ** search key, then we can start the search on the current page + ** without needing to go back to root. + */ + if( pCur->eState==CURSOR_VALID + && pCur->pPage->leaf + && cursorOnLastPage(pCur) + ){ + int c; + if( pCur->ix==pCur->pPage->nCell-1 + && (c = indexCellCompare(pCur->pPage,pCur->ix,pIdxKey,xRecordCompare))<=0 + && pIdxKey->errCode==SQLITE_OK + ){ + *pRes = c; + return SQLITE_OK; /* Cursor already pointing at the correct spot */ + } + if( pCur->iPage>0 + && indexCellCompare(pCur->pPage, 0, pIdxKey, xRecordCompare)<=0 + && pIdxKey->errCode==SQLITE_OK + ){ + pCur->curFlags &= ~(BTCF_ValidOvfl|BTCF_AtLast); + if( !pCur->pPage->isInit ){ + return SQLITE_CORRUPT_BKPT; + } + goto bypass_moveto_root; /* Start search on the current page */ + } + pIdxKey->errCode = SQLITE_OK; + } + + rc = moveToRoot(pCur); + if( rc ){ + if( rc==SQLITE_EMPTY ){ + assert( pCur->pgnoRoot==0 || pCur->pPage->nCell==0 ); + *pRes = -1; + return SQLITE_OK; + } + return rc; + } + +bypass_moveto_root: + assert( pCur->pPage ); + assert( pCur->pPage->isInit ); + assert( pCur->eState==CURSOR_VALID ); + assert( pCur->pPage->nCell > 0 ); + assert( pCur->curIntKey==0 ); + assert( pIdxKey!=0 ); + for(;;){ + int lwr, upr, idx, c; + Pgno chldPg; + MemPage *pPage = pCur->pPage; + u8 *pCell; /* Pointer to current cell in pPage */ + + /* pPage->nCell must be greater than zero. If this is the root-page + ** the cursor would have been INVALID above and this for(;;) loop + ** not run. If this is not the root-page, then the moveToChild() routine + ** would have already detected db corruption. Similarly, pPage must + ** be the right kind (index or table) of b-tree page. Otherwise + ** a moveToChild() or moveToRoot() call would have detected corruption. */ + assert( pPage->nCell>0 ); + assert( pPage->intKey==0 ); + lwr = 0; + upr = pPage->nCell-1; + idx = upr>>1; /* idx = (lwr+upr)/2; */ + for(;;){ + int nCell; /* Size of the pCell cell in bytes */ + pCell = findCellPastPtr(pPage, idx); + + /* The maximum supported page-size is 65536 bytes. This means that + ** the maximum number of record bytes stored on an index B-Tree + ** page is less than 16384 bytes and may be stored as a 2-byte + ** varint. This information is used to attempt to avoid parsing + ** the entire cell by checking for the cases where the record is + ** stored entirely within the b-tree page by inspecting the first + ** 2 bytes of the cell. + */ + nCell = pCell[0]; + if( nCell<=pPage->max1bytePayload ){ + /* This branch runs if the record-size field of the cell is a + ** single byte varint and the record fits entirely on the main + ** b-tree page. */ + if( pCell + nCell >= pPage->aDataEnd ){ + rc = SQLITE_CORRUPT_PAGE(pPage); + goto moveto_index_finish; + } + c = xRecordCompare(nCell, (void*)&pCell[1], pIdxKey); + }else if( !(pCell[1] & 0x80) + && (nCell = ((nCell&0x7f)<<7) + pCell[1])<=pPage->maxLocal + && pCell + nCell < pPage->aDataEnd + ){ + /* The record-size field is a 2 byte varint and the record + ** fits entirely on the main b-tree page. */ + c = xRecordCompare(nCell, (void*)&pCell[2], pIdxKey); + }else{ + /* The record flows over onto one or more overflow pages. In + ** this case the whole cell needs to be parsed, a buffer allocated + ** and accessPayload() used to retrieve the record into the + ** buffer before VdbeRecordCompare() can be called. + ** + ** If the record is corrupt, the xRecordCompare routine may read + ** up to two varints past the end of the buffer. An extra 18 + ** bytes of padding is allocated at the end of the buffer in + ** case this happens. */ + void *pCellKey; + u8 * const pCellBody = pCell - pPage->childPtrSize; + const int nOverrun = 18; /* Size of the overrun padding */ + pPage->xParseCell(pPage, pCellBody, &pCur->info); + nCell = (int)pCur->info.nKey; + testcase( nCell<0 ); /* True if key size is 2^32 or more */ + testcase( nCell==0 ); /* Invalid key size: 0x80 0x80 0x00 */ + testcase( nCell==1 ); /* Invalid key size: 0x80 0x80 0x01 */ + testcase( nCell==2 ); /* Minimum legal index key size */ + if( nCell<2 || nCell/pCur->pBt->usableSize>pCur->pBt->nPage ){ + rc = SQLITE_CORRUPT_PAGE(pPage); + goto moveto_index_finish; + } + pCellKey = sqlite3Malloc( (u64)nCell+(u64)nOverrun ); + if( pCellKey==0 ){ + rc = SQLITE_NOMEM_BKPT; + goto moveto_index_finish; + } + pCur->ix = (u16)idx; + rc = accessPayload(pCur, 0, nCell, (unsigned char*)pCellKey, 0); + memset(((u8*)pCellKey)+nCell,0,nOverrun); /* Fix uninit warnings */ + pCur->curFlags &= ~BTCF_ValidOvfl; + if( rc ){ + sqlite3_free(pCellKey); + goto moveto_index_finish; + } + c = sqlite3VdbeRecordCompare(nCell, pCellKey, pIdxKey); + sqlite3_free(pCellKey); + } + assert( + (pIdxKey->errCode!=SQLITE_CORRUPT || c==0) + && (pIdxKey->errCode!=SQLITE_NOMEM || pCur->pBtree->db->mallocFailed) + ); + if( c<0 ){ + lwr = idx+1; + }else if( c>0 ){ + upr = idx-1; + }else{ + assert( c==0 ); + *pRes = 0; + rc = SQLITE_OK; + pCur->ix = (u16)idx; + if( pIdxKey->errCode ) rc = SQLITE_CORRUPT_BKPT; + goto moveto_index_finish; + } + if( lwr>upr ) break; + assert( lwr+upr>=0 ); + idx = (lwr+upr)>>1; /* idx = (lwr+upr)/2 */ + } + assert( lwr==upr+1 || (pPage->intKey && !pPage->leaf) ); + assert( pPage->isInit ); + if( pPage->leaf ){ + assert( pCur->ixpPage->nCell || CORRUPT_DB ); + pCur->ix = (u16)idx; + *pRes = c; + rc = SQLITE_OK; + goto moveto_index_finish; + } + if( lwr>=pPage->nCell ){ + chldPg = get4byte(&pPage->aData[pPage->hdrOffset+8]); + }else{ + chldPg = get4byte(findCell(pPage, lwr)); + } + + /* This block is similar to an in-lined version of: + ** + ** pCur->ix = (u16)lwr; + ** rc = moveToChild(pCur, chldPg); + ** if( rc ) break; + */ + pCur->info.nSize = 0; + pCur->curFlags &= ~(BTCF_ValidNKey|BTCF_ValidOvfl); + if( pCur->iPage>=(BTCURSOR_MAX_DEPTH-1) ){ + return SQLITE_CORRUPT_BKPT; + } + pCur->aiIdx[pCur->iPage] = (u16)lwr; + pCur->apPage[pCur->iPage] = pCur->pPage; + pCur->ix = 0; + pCur->iPage++; + rc = getAndInitPage(pCur->pBt, chldPg, &pCur->pPage, pCur->curPagerFlags); + if( rc==SQLITE_OK + && (pCur->pPage->nCell<1 || pCur->pPage->intKey!=pCur->curIntKey) + ){ + releasePage(pCur->pPage); + rc = SQLITE_CORRUPT_PGNO(chldPg); + } + if( rc ){ + pCur->pPage = pCur->apPage[--pCur->iPage]; + break; + } + /* + ***** End of in-lined moveToChild() call */ + } +moveto_index_finish: pCur->info.nSize = 0; assert( (pCur->curFlags & BTCF_ValidOvfl)==0 ); return rc; @@ -68793,7 +79509,7 @@ SQLITE_PRIVATE int sqlite3BtreeEof(BtCursor *pCur){ /* ** Return an estimate for the number of rows in the table that pCur is -** pointing to. Return a negative number if no estimate is currently +** pointing to. Return a negative number if no estimate is currently ** available. */ SQLITE_PRIVATE i64 sqlite3BtreeRowCountEst(BtCursor *pCur){ @@ -68803,21 +79519,21 @@ SQLITE_PRIVATE i64 sqlite3BtreeRowCountEst(BtCursor *pCur){ assert( cursorOwnsBtShared(pCur) ); assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) ); - /* Currently this interface is only called by the OP_IfSmaller - ** opcode, and it that case the cursor will always be valid and - ** will always point to a leaf node. */ - if( NEVER(pCur->eState!=CURSOR_VALID) ) return -1; + /* Currently this interface is only called by the OP_IfSizeBetween + ** opcode and the OP_Count opcode with P3=1. In either case, + ** the cursor will always be valid unless the btree is empty. */ + if( pCur->eState!=CURSOR_VALID ) return 0; if( NEVER(pCur->pPage->leaf==0) ) return -1; n = pCur->pPage->nCell; for(i=0; iiPage; i++){ - n *= pCur->apPage[i]->nCell; + n *= pCur->apPage[i]->nCell+1; } return n; } /* -** Advance the cursor to the next entry in the database. +** Advance the cursor to the next entry in the database. ** Return value: ** ** SQLITE_OK success @@ -68859,24 +79575,11 @@ static SQLITE_NOINLINE int btreeNext(BtCursor *pCur){ pPage = pCur->pPage; idx = ++pCur->ix; + if( sqlite3FaultSim(412) ) pPage->isInit = 0; if( !pPage->isInit ){ - /* The only known way for this to happen is for there to be a - ** recursive SQL function that does a DELETE operation as part of a - ** SELECT which deletes content out from under an active cursor - ** in a corrupt database file where the table being DELETE-ed from - ** has pages in common with the table being queried. See TH3 - ** module cov1/btree78.test testcase 220 (2018-06-08) for an - ** example. */ return SQLITE_CORRUPT_BKPT; } - /* If the database file is corrupt, it is possible for the value of idx - ** to be invalid here. This can only occur if a second cursor modifies - ** the page while cursor pCur is holding a reference to it. Which can - ** only happen if the database is corrupt in such a way as to link the - ** page into more than one b-tree structure. */ - testcase( idx>pPage->nCell ); - if( idx>=pPage->nCell ){ if( !pPage->leaf ){ rc = moveToChild(pCur, get4byte(&pPage->aData[pPage->hdrOffset+8])); @@ -68965,7 +79668,10 @@ static SQLITE_NOINLINE int btreePrevious(BtCursor *pCur){ } pPage = pCur->pPage; - assert( pPage->isInit ); + if( sqlite3FaultSim(412) ) pPage->isInit = 0; + if( !pPage->isInit ){ + return SQLITE_CORRUPT_BKPT; + } if( !pPage->leaf ){ int idx = pCur->ix; rc = moveToChild(pCur, get4byte(findCell(pPage, idx))); @@ -69019,7 +79725,7 @@ SQLITE_PRIVATE int sqlite3BtreePrevious(BtCursor *pCur, int flags){ ** SQLITE_OK is returned on success. Any other return value indicates ** an error. *ppPage is set to NULL in the event of an error. ** -** If the "nearby" parameter is not 0, then an effort is made to +** If the "nearby" parameter is not 0, then an effort is made to ** locate a page close to the page number "nearby". This can be used in an ** attempt to keep related pages close to each other in the database file, ** which in turn can make database access faster. @@ -69049,8 +79755,8 @@ static int allocateBtreePage( assert( eMode==BTALLOC_ANY || (nearby>0 && IfNotOmitAV(pBt->autoVacuum)) ); pPage1 = pBt->pPage1; mxPage = btreePagecount(pBt); - /* EVIDENCE-OF: R-05119-02637 The 4-byte big-endian integer at offset 36 - ** stores stores the total number of pages on the freelist. */ + /* EVIDENCE-OF: R-21003-45125 The 4-byte big-endian integer at offset 36 + ** stores the total number of pages on the freelist. */ n = get4byte(&pPage1->aData[36]); testcase( n==mxPage-1 ); if( n>=mxPage ){ @@ -69061,7 +79767,7 @@ static int allocateBtreePage( Pgno iTrunk; u8 searchList = 0; /* If the free-list must be searched for 'nearby' */ u32 nSearch = 0; /* Count of the number of search attempts */ - + /* If eMode==BTALLOC_EXACT and a query of the pointer-map ** shows that the page 'nearby' is somewhere on the free-list, then ** the entire-list will be searched for that page. @@ -69124,8 +79830,8 @@ static int allocateBtreePage( ** is the number of leaf page pointers to follow. */ k = get4byte(&pTrunk->aData[4]); if( k==0 && !searchList ){ - /* The trunk has no leaves and the list is not being searched. - ** So extract the trunk page itself and use it as the newly + /* The trunk has no leaves and the list is not being searched. + ** So extract the trunk page itself and use it as the newly ** allocated page */ assert( pPrevTrunk==0 ); rc = sqlite3PagerWrite(pTrunk->pDbPage); @@ -69136,14 +79842,14 @@ static int allocateBtreePage( memcpy(&pPage1->aData[32], &pTrunk->aData[0], 4); *ppPage = pTrunk; pTrunk = 0; - TRACE(("ALLOCATE: %d trunk - %d free pages left\n", *pPgno, n-1)); + TRACE(("ALLOCATE: %u trunk - %u free pages left\n", *pPgno, n-1)); }else if( k>(u32)(pBt->usableSize/4 - 2) ){ /* Value of k is out of range. Database corruption */ rc = SQLITE_CORRUPT_PGNO(iTrunk); goto end_allocate_page; #ifndef SQLITE_OMIT_AUTOVACUUM - }else if( searchList - && (nearby==iTrunk || (iTrunkaData[0], &pTrunk->aData[0], 4); } }else{ - /* The trunk page is required by the caller but it contains + /* The trunk page is required by the caller but it contains ** pointers to free-list leaves. The first leaf becomes a trunk ** page in this case. */ MemPage *pNewTrunk; Pgno iNewTrunk = get4byte(&pTrunk->aData[8]); - if( iNewTrunk>mxPage ){ + if( iNewTrunk>mxPage ){ rc = SQLITE_CORRUPT_PGNO(iTrunk); goto end_allocate_page; } @@ -69202,7 +79908,7 @@ static int allocateBtreePage( } } pTrunk = 0; - TRACE(("ALLOCATE: %d trunk - %d free pages left\n", *pPgno, n-1)); + TRACE(("ALLOCATE: %u trunk - %u free pages left\n", *pPgno, n-1)); #endif }else if( k>0 ){ /* Extract a leaf from the trunk */ @@ -69237,18 +79943,18 @@ static int allocateBtreePage( iPage = get4byte(&aData[8+closest*4]); testcase( iPage==mxPage ); - if( iPage>mxPage ){ + if( iPage>mxPage || iPage<2 ){ rc = SQLITE_CORRUPT_PGNO(iTrunk); goto end_allocate_page; } testcase( iPage==mxPage ); - if( !searchList - || (iPage==nearby || (iPagepgno, n-1)); rc = sqlite3PagerWrite(pTrunk->pDbPage); if( rc ) goto end_allocate_page; @@ -69284,7 +79990,7 @@ static int allocateBtreePage( ** not set the no-content flag. This causes the pager to load and journal ** the current page content before overwriting it. ** - ** Note that the pager will not actually attempt to load or journal + ** Note that the pager will not actually attempt to load or journal ** content for any page that really does lie past the end of the database ** file on disk. So the effects of disabling the no-content optimization ** here are confined to those pages that lie between the end of the @@ -69304,7 +80010,7 @@ static int allocateBtreePage( ** becomes a new pointer-map page, the second is used by the caller. */ MemPage *pPg = 0; - TRACE(("ALLOCATE: %d from end of file (pointer-map page)\n", pBt->nPage)); + TRACE(("ALLOCATE: %u from end of file (pointer-map page)\n", pBt->nPage)); assert( pBt->nPage!=PENDING_BYTE_PAGE(pBt) ); rc = btreeGetUnusedPage(pBt, pBt->nPage, &pPg, bNoContent); if( rc==SQLITE_OK ){ @@ -69327,7 +80033,7 @@ static int allocateBtreePage( releasePage(*ppPage); *ppPage = 0; } - TRACE(("ALLOCATE: %d from end of file\n", *pPgno)); + TRACE(("ALLOCATE: %u from end of file\n", *pPgno)); } assert( CORRUPT_DB || *pPgno!=PENDING_BYTE_PAGE(pBt) ); @@ -69341,12 +80047,12 @@ static int allocateBtreePage( } /* -** This function is used to add page iPage to the database file free-list. +** This function is used to add page iPage to the database file free-list. ** It is assumed that the page is not already a part of the free-list. ** ** The value passed as the second argument to this function is optional. -** If the caller happens to have a pointer to the MemPage object -** corresponding to page iPage handy, it may pass it as the second value. +** If the caller happens to have a pointer to the MemPage object +** corresponding to page iPage handy, it may pass it as the second value. ** Otherwise, it may pass NULL. ** ** If a pointer to a MemPage object is passed as the second argument, @@ -69354,7 +80060,7 @@ static int allocateBtreePage( */ static int freePage2(BtShared *pBt, MemPage *pMemPage, Pgno iPage){ MemPage *pTrunk = 0; /* Free-list trunk page */ - Pgno iTrunk = 0; /* Page number of free-list trunk page */ + Pgno iTrunk = 0; /* Page number of free-list trunk page */ MemPage *pPage1 = pBt->pPage1; /* Local reference to page 1 */ MemPage *pPage; /* Page being freed. May be NULL. */ int rc; /* Return Code */ @@ -69395,7 +80101,7 @@ static int freePage2(BtShared *pBt, MemPage *pMemPage, Pgno iPage){ /* If the database supports auto-vacuum, write an entry in the pointer-map ** to indicate that the page is free. */ - if( ISAUTOVACUUM ){ + if( ISAUTOVACUUM(pBt) ){ ptrmapPut(pBt, iPage, PTRMAP_FREEPAGE, 0, &rc); if( rc ) goto freepage_out; } @@ -69411,6 +80117,10 @@ static int freePage2(BtShared *pBt, MemPage *pMemPage, Pgno iPage){ u32 nLeaf; /* Initial number of leaf cells on trunk page */ iTrunk = get4byte(&pPage1->aData[32]); + if( iTrunk>btreePagecount(pBt) ){ + rc = SQLITE_CORRUPT_BKPT; + goto freepage_out; + } rc = btreeGetPage(pBt, iTrunk, &pTrunk, 0); if( rc!=SQLITE_OK ){ goto freepage_out; @@ -69451,14 +80161,14 @@ static int freePage2(BtShared *pBt, MemPage *pMemPage, Pgno iPage){ } rc = btreeSetHasContent(pBt, iPage); } - TRACE(("FREE-PAGE: %d leaf on trunk page %d\n",pPage->pgno,pTrunk->pgno)); + TRACE(("FREE-PAGE: %u leaf on trunk page %u\n",pPage->pgno,pTrunk->pgno)); goto freepage_out; } } /* If control flows to this point, then it was not possible to add the ** the page being freed as a leaf page of the first trunk in the free-list. - ** Possibly because the free-list is empty, or possibly because the + ** Possibly because the free-list is empty, or possibly because the ** first trunk in the free-list is full. Either way, the page being freed ** will become the new first trunk page in the free-list. */ @@ -69472,7 +80182,7 @@ static int freePage2(BtShared *pBt, MemPage *pMemPage, Pgno iPage){ put4byte(pPage->aData, iTrunk); put4byte(&pPage->aData[4], 0); put4byte(&pPage1->aData[32], iPage); - TRACE(("FREE-PAGE: %d new trunk page replacing %d\n", pPage->pgno, iTrunk)); + TRACE(("FREE-PAGE: %u new trunk page replacing %u\n", pPage->pgno, iTrunk)); freepage_out: if( pPage ){ @@ -69489,10 +80199,9 @@ static void freePage(MemPage *pPage, int *pRC){ } /* -** Free any overflow pages associated with the given Cell. Store -** size information about the cell in pInfo. +** Free the overflow pages associated with the given Cell. */ -static int clearCell( +static SQLITE_NOINLINE int clearCellOverflow( MemPage *pPage, /* The page that contains the Cell */ unsigned char *pCell, /* First byte of the Cell */ CellInfo *pInfo /* Size information about the cell */ @@ -69504,10 +80213,7 @@ static int clearCell( u32 ovflPageSize; assert( sqlite3_mutex_held(pPage->pBt->mutex) ); - pPage->xParseCell(pPage, pCell, pInfo); - if( pInfo->nLocal==pInfo->nPayload ){ - return SQLITE_OK; /* No overflow pages. Return without doing anything */ - } + assert( pInfo->nLocal!=pInfo->nPayload ); testcase( pCell + pInfo->nSize == pPage->aDataEnd ); testcase( pCell + (pInfo->nSize-1) == pPage->aDataEnd ); if( pCell + pInfo->nSize > pPage->aDataEnd ){ @@ -69519,15 +80225,15 @@ static int clearCell( assert( pBt->usableSize > 4 ); ovflPageSize = pBt->usableSize - 4; nOvfl = (pInfo->nPayload - pInfo->nLocal + ovflPageSize - 1)/ovflPageSize; - assert( nOvfl>0 || + assert( nOvfl>0 || (CORRUPT_DB && (pInfo->nPayload + ovflPageSize)btreePagecount(pBt) ){ - /* 0 is not a legal page number and page 1 cannot be an - ** overflow page. Therefore if ovflPgno<2 or past the end of the + /* 0 is not a legal page number and page 1 cannot be an + ** overflow page. Therefore if ovflPgno<2 or past the end of the ** file the database must be corrupt. */ return SQLITE_CORRUPT_BKPT; } @@ -69539,11 +80245,11 @@ static int clearCell( if( ( pOvfl || ((pOvfl = btreePageLookup(pBt, ovflPgno))!=0) ) && sqlite3PagerPageRefcount(pOvfl->pDbPage)!=1 ){ - /* There is no reason any cursor should have an outstanding reference + /* There is no reason any cursor should have an outstanding reference ** to an overflow page belonging to a cell that is being deleted/updated. - ** So if there exists more than one reference to this page, then it - ** must not really be an overflow page and the database must be corrupt. - ** It is helpful to detect this before calling freePage2(), as + ** So if there exists more than one reference to this page, then it + ** must not really be an overflow page and the database must be corrupt. + ** It is helpful to detect this before calling freePage2(), as ** freePage2() may zero the page contents if secure-delete mode is ** enabled. If this 'overflow' page happens to be a page that the ** caller is iterating through or using in some other way, this @@ -69563,6 +80269,21 @@ static int clearCell( return SQLITE_OK; } +/* Call xParseCell to compute the size of a cell. If the cell contains +** overflow, then invoke cellClearOverflow to clear out that overflow. +** Store the result code (SQLITE_OK or some error code) in rc. +** +** Implemented as macro to force inlining for performance. +*/ +#define BTREE_CLEAR_CELL(rc, pPage, pCell, sInfo) \ + pPage->xParseCell(pPage, pCell, &sInfo); \ + if( sInfo.nLocal!=sInfo.nPayload ){ \ + rc = clearCellOverflow(pPage, pCell, &sInfo); \ + }else{ \ + rc = SQLITE_OK; \ + } + + /* ** Create the byte sequence used to represent a cell on page pPage ** and write that byte sequence into pCell[]. Overflow pages are @@ -69614,7 +80335,7 @@ static int fillInCell( pSrc = pX->pKey; nHeader += putVarint32(&pCell[nHeader], nPayload); } - + /* Fill in the payload */ pPayload = &pCell[nHeader]; if( nPayload<=pPage->maxLocal ){ @@ -69623,7 +80344,10 @@ static int fillInCell( n = nHeader + nPayload; testcase( n==3 ); testcase( n==4 ); - if( n<4 ) n = 4; + if( n<4 ){ + n = 4; + pPayload[nPayload] = 0; + } *pnSize = n; assert( nSrc<=nPayload ); testcase( nSrcautoVacuum ){ do{ pgnoOvfl++; - } while( - PTRMAP_ISPAGE(pBt, pgnoOvfl) || pgnoOvfl==PENDING_BYTE_PAGE(pBt) + } while( + PTRMAP_ISPAGE(pBt, pgnoOvfl) || pgnoOvfl==PENDING_BYTE_PAGE(pBt) ); } #endif @@ -69714,9 +80438,9 @@ static int fillInCell( #ifndef SQLITE_OMIT_AUTOVACUUM /* If the database supports auto-vacuum, and the second or subsequent ** overflow page is being allocated, add an entry to the pointer-map - ** for that page now. + ** for that page now. ** - ** If this is the first overflow page, then write a partial entry + ** If this is the first overflow page, then write a partial entry ** to the pointer-map. If we write nothing to this pointer-map slot, ** then the optimistic overflow chain processing in clearCell() ** may misinterpret the uninitialized values and delete the @@ -69773,16 +80497,18 @@ static void dropCell(MemPage *pPage, int idx, int sz, int *pRC){ int hdr; /* Beginning of the header. 0 most pages. 100 page 1 */ if( *pRC ) return; - assert( idx>=0 && idxnCell ); + assert( idx>=0 ); + assert( idxnCell ); assert( CORRUPT_DB || sz==cellSize(pPage, idx) ); assert( sqlite3PagerIswriteable(pPage->pDbPage) ); assert( sqlite3_mutex_held(pPage->pBt->mutex) ); assert( pPage->nFree>=0 ); data = pPage->aData; ptr = &pPage->aCellIdx[2*idx]; + assert( pPage->pBt->usableSize > (u32)(ptr-data) ); pc = get2byte(ptr); hdr = pPage->hdrOffset; - testcase( pc==get2byte(&data[hdr+5]) ); + testcase( pc==(u32)get2byte(&data[hdr+5]) ); testcase( pc+sz==pPage->pBt->usableSize ); if( pc+sz > pPage->pBt->usableSize ){ *pRC = SQLITE_CORRUPT_BKPT; @@ -69815,48 +80541,136 @@ static void dropCell(MemPage *pPage, int idx, int sz, int *pRC){ ** will not fit, then make a copy of the cell content into pTemp if ** pTemp is not null. Regardless of pTemp, allocate a new entry ** in pPage->apOvfl[] and make it point to the cell content (either -** in pTemp or the original pCell) and also record its index. -** Allocating a new entry in pPage->aCell[] implies that +** in pTemp or the original pCell) and also record its index. +** Allocating a new entry in pPage->aCell[] implies that ** pPage->nOverflow is incremented. ** -** *pRC must be SQLITE_OK when this routine is called. +** The insertCellFast() routine below works exactly the same as +** insertCell() except that it lacks the pTemp and iChild parameters +** which are assumed zero. Other than that, the two routines are the +** same. +** +** Fixes or enhancements to this routine should be reflected in +** insertCellFast()! */ -static void insertCell( +static int insertCell( MemPage *pPage, /* Page into which we are copying */ int i, /* New cell becomes the i-th cell of the page */ u8 *pCell, /* Content of the new cell */ int sz, /* Bytes of content in pCell */ u8 *pTemp, /* Temp storage space for pCell, if needed */ - Pgno iChild, /* If non-zero, replace first 4 bytes with this value */ - int *pRC /* Read and write return code from here */ + Pgno iChild /* If non-zero, replace first 4 bytes with this value */ ){ int idx = 0; /* Where to write new cell content in data[] */ int j; /* Loop counter */ u8 *data; /* The content of the whole page */ u8 *pIns; /* The point in pPage->aCellIdx[] where no cell inserted */ - assert( *pRC==SQLITE_OK ); assert( i>=0 && i<=pPage->nCell+pPage->nOverflow ); assert( MX_CELL(pPage->pBt)<=10921 ); assert( pPage->nCell<=MX_CELL(pPage->pBt) || CORRUPT_DB ); assert( pPage->nOverflow<=ArraySize(pPage->apOvfl) ); assert( ArraySize(pPage->apOvfl)==ArraySize(pPage->aiOvfl) ); assert( sqlite3_mutex_held(pPage->pBt->mutex) ); - /* The cell should normally be sized correctly. However, when moving a - ** malformed cell from a leaf page to an interior page, if the cell size - ** wanted to be less than 4 but got rounded up to 4 on the leaf, then size - ** might be less than 8 (leaf-size + pointer) on the interior node. Hence - ** the term after the || in the following assert(). */ - assert( sz==pPage->xCellSize(pPage, pCell) || (sz==8 && iChild>0) ); + assert( sz==pPage->xCellSize(pPage, pCell) || CORRUPT_DB ); assert( pPage->nFree>=0 ); + assert( iChild>0 ); if( pPage->nOverflow || sz+2>pPage->nFree ){ if( pTemp ){ memcpy(pTemp, pCell, sz); pCell = pTemp; } - if( iChild ){ - put4byte(pCell, iChild); + put4byte(pCell, iChild); + j = pPage->nOverflow++; + /* Comparison against ArraySize-1 since we hold back one extra slot + ** as a contingency. In other words, never need more than 3 overflow + ** slots but 4 are allocated, just to be safe. */ + assert( j < ArraySize(pPage->apOvfl)-1 ); + pPage->apOvfl[j] = pCell; + pPage->aiOvfl[j] = (u16)i; + + /* When multiple overflows occur, they are always sequential and in + ** sorted order. This invariants arise because multiple overflows can + ** only occur when inserting divider cells into the parent page during + ** balancing, and the dividers are adjacent and sorted. + */ + assert( j==0 || pPage->aiOvfl[j-1]<(u16)i ); /* Overflows in sorted order */ + assert( j==0 || i==pPage->aiOvfl[j-1]+1 ); /* Overflows are sequential */ + }else{ + int rc = sqlite3PagerWrite(pPage->pDbPage); + if( NEVER(rc!=SQLITE_OK) ){ + return rc; + } + assert( sqlite3PagerIswriteable(pPage->pDbPage) ); + data = pPage->aData; + assert( &data[pPage->cellOffset]==pPage->aCellIdx ); + rc = allocateSpace(pPage, sz, &idx); + if( rc ){ return rc; } + /* The allocateSpace() routine guarantees the following properties + ** if it returns successfully */ + assert( idx >= 0 ); + assert( idx >= pPage->cellOffset+2*pPage->nCell+2 || CORRUPT_DB ); + assert( idx+sz <= (int)pPage->pBt->usableSize ); + pPage->nFree -= (u16)(2 + sz); + /* In a corrupt database where an entry in the cell index section of + ** a btree page has a value of 3 or less, the pCell value might point + ** as many as 4 bytes in front of the start of the aData buffer for + ** the source page. Make sure this does not cause problems by not + ** reading the first 4 bytes */ + memcpy(&data[idx+4], pCell+4, sz-4); + put4byte(&data[idx], iChild); + pIns = pPage->aCellIdx + i*2; + memmove(pIns+2, pIns, 2*(pPage->nCell - i)); + put2byte(pIns, idx); + pPage->nCell++; + /* increment the cell count */ + if( (++data[pPage->hdrOffset+4])==0 ) data[pPage->hdrOffset+3]++; + assert( get2byte(&data[pPage->hdrOffset+3])==pPage->nCell || CORRUPT_DB ); +#ifndef SQLITE_OMIT_AUTOVACUUM + if( pPage->pBt->autoVacuum ){ + int rc2 = SQLITE_OK; + /* The cell may contain a pointer to an overflow page. If so, write + ** the entry for the overflow page into the pointer map. + */ + ptrmapPutOvflPtr(pPage, pPage, pCell, &rc2); + if( rc2 ) return rc2; } +#endif + } + return SQLITE_OK; +} + +/* +** This variant of insertCell() assumes that the pTemp and iChild +** parameters are both zero. Use this variant in sqlite3BtreeInsert() +** for performance improvement, and also so that this variant is only +** called from that one place, and is thus inlined, and thus runs must +** faster. +** +** Fixes or enhancements to this routine should be reflected into +** the insertCell() routine. +*/ +static int insertCellFast( + MemPage *pPage, /* Page into which we are copying */ + int i, /* New cell becomes the i-th cell of the page */ + u8 *pCell, /* Content of the new cell */ + int sz /* Bytes of content in pCell */ +){ + int idx = 0; /* Where to write new cell content in data[] */ + int j; /* Loop counter */ + u8 *data; /* The content of the whole page */ + u8 *pIns; /* The point in pPage->aCellIdx[] where no cell inserted */ + + assert( i>=0 && i<=pPage->nCell+pPage->nOverflow ); + assert( MX_CELL(pPage->pBt)<=10921 ); + assert( pPage->nCell<=MX_CELL(pPage->pBt) || CORRUPT_DB ); + assert( pPage->nOverflow<=ArraySize(pPage->apOvfl) ); + assert( ArraySize(pPage->apOvfl)==ArraySize(pPage->aiOvfl) ); + assert( sqlite3_mutex_held(pPage->pBt->mutex) ); + assert( sz==pPage->xCellSize(pPage, pCell) || CORRUPT_DB ); + assert( pPage->nFree>=0 ); + assert( pPage->nOverflow==0 ); + if( sz+2>pPage->nFree ){ j = pPage->nOverflow++; /* Comparison against ArraySize-1 since we hold back one extra slot ** as a contingency. In other words, never need more than 3 overflow @@ -69875,31 +80689,20 @@ static void insertCell( }else{ int rc = sqlite3PagerWrite(pPage->pDbPage); if( rc!=SQLITE_OK ){ - *pRC = rc; - return; + return rc; } assert( sqlite3PagerIswriteable(pPage->pDbPage) ); data = pPage->aData; assert( &data[pPage->cellOffset]==pPage->aCellIdx ); rc = allocateSpace(pPage, sz, &idx); - if( rc ){ *pRC = rc; return; } + if( rc ){ return rc; } /* The allocateSpace() routine guarantees the following properties ** if it returns successfully */ assert( idx >= 0 ); assert( idx >= pPage->cellOffset+2*pPage->nCell+2 || CORRUPT_DB ); assert( idx+sz <= (int)pPage->pBt->usableSize ); pPage->nFree -= (u16)(2 + sz); - if( iChild ){ - /* In a corrupt database where an entry in the cell index section of - ** a btree page has a value of 3 or less, the pCell value might point - ** as many as 4 bytes in front of the start of the aData buffer for - ** the source page. Make sure this does not cause problems by not - ** reading the first 4 bytes */ - memcpy(&data[idx+4], pCell+4, sz-4); - put4byte(&data[idx], iChild); - }else{ - memcpy(&data[idx], pCell, sz); - } + memcpy(&data[idx], pCell, sz); pIns = pPage->aCellIdx + i*2; memmove(pIns+2, pIns, 2*(pPage->nCell - i)); put2byte(pIns, idx); @@ -69909,13 +80712,16 @@ static void insertCell( assert( get2byte(&data[pPage->hdrOffset+3])==pPage->nCell || CORRUPT_DB ); #ifndef SQLITE_OMIT_AUTOVACUUM if( pPage->pBt->autoVacuum ){ + int rc2 = SQLITE_OK; /* The cell may contain a pointer to an overflow page. If so, write ** the entry for the overflow page into the pointer map. */ - ptrmapPutOvflPtr(pPage, pPage, pCell, pRC); + ptrmapPutOvflPtr(pPage, pPage, pCell, &rc2); + if( rc2 ) return rc2; } #endif } + return SQLITE_OK; } /* @@ -70016,14 +80822,16 @@ struct CellArray { ** computed. */ static void populateCellCache(CellArray *p, int idx, int N){ + MemPage *pRef = p->pRef; + u16 *szCell = p->szCell; assert( idx>=0 && idx+N<=p->nCell ); while( N>0 ){ assert( p->apCell[idx]!=0 ); - if( p->szCell[idx]==0 ){ - p->szCell[idx] = p->pRef->xCellSize(p->pRef, p->apCell[idx]); + if( szCell[idx]==0 ){ + szCell[idx] = pRef->xCellSize(pRef, p->apCell[idx]); }else{ assert( CORRUPT_DB || - p->szCell[idx]==p->pRef->xCellSize(p->pRef, p->apCell[idx]) ); + szCell[idx]==pRef->xCellSize(pRef, p->apCell[idx]) ); } idx++; N--; @@ -70046,16 +80854,16 @@ static u16 cachedCellSize(CellArray *p, int N){ } /* -** Array apCell[] contains pointers to nCell b-tree page cells. The +** Array apCell[] contains pointers to nCell b-tree page cells. The ** szCell[] array contains the size in bytes of each cell. This function ** replaces the current contents of page pPg with the contents of the cell ** array. ** ** Some of the cells in apCell[] may currently be stored in pPg. This -** function works around problems caused by this by making a copy of any +** function works around problems caused by this by making a copy of any ** such cells before overwriting the page data. ** -** The MemPage.nFree field is invalidated by this function. It is the +** The MemPage.nFree field is invalidated by this function. It is the ** responsibility of the caller to set it correctly. */ static int rebuildPage( @@ -70077,12 +80885,14 @@ static int rebuildPage( int k; /* Current slot in pCArray->apEnd[] */ u8 *pSrcEnd; /* Current pCArray->apEnd[k] value */ + assert( nCell>0 ); assert( i(u32)usableSize) ){ j = 0; } + if( j>(u32)usableSize ){ j = 0; } memcpy(&pTmp[j], &aData[j], usableSize - j); - for(k=0; pCArray->ixNx[k]<=i && ALWAYS(kixNx[NB*2-1]>i ); + for(k=0; pCArray->ixNx[k]<=i; k++){} pSrcEnd = pCArray->apEnd[k]; pData = pEnd; @@ -70090,7 +80900,7 @@ static int rebuildPage( u8 *pCell = pCArray->apCell[i]; u16 sz = pCArray->szCell[i]; assert( sz>0 ); - if( SQLITE_WITHIN(pCell,aData,pEnd) ){ + if( SQLITE_WITHIN(pCell,aData+j,pEnd) ){ if( ((uptr)(pCell+sz))>(uptr)pEnd ) return SQLITE_CORRUPT_BKPT; pCell = &pTmp[pCell - aData]; }else if( (uptr)(pCell+sz)>(uptr)pSrcEnd @@ -70103,9 +80913,8 @@ static int rebuildPage( put2byte(pCellptr, (pData - aData)); pCellptr += 2; if( pData < pCellptr ) return SQLITE_CORRUPT_BKPT; - memcpy(pData, pCell, sz); + memmove(pData, pCell, sz); assert( sz==pPg->xCellSize(pPg, pCell) || CORRUPT_DB ); - testcase( sz!=pPg->xCellSize(pPg,pCell) ); i++; if( i>=iEnd ) break; if( pCArray->ixNx[k]<=i ){ @@ -70115,7 +80924,8 @@ static int rebuildPage( } /* The pPg->nFree field is now set incorrectly. The caller will fix it. */ - pPg->nCell = nCell; + assert( nCell < 10922 ); + pPg->nCell = (u16)nCell; pPg->nOverflow = 0; put2byte(&aData[hdr+1], 0); @@ -70138,7 +80948,7 @@ static int rebuildPage( ** cell in the array. It is the responsibility of the caller to ensure ** that it is safe to overwrite this part of the cell-pointer array. ** -** When this function is called, *ppData points to the start of the +** When this function is called, *ppData points to the start of the ** content area on page pPg. If the size of the content area is extended, ** *ppData is updated to point to the new start of the content area ** before returning. @@ -70146,7 +80956,7 @@ static int rebuildPage( ** Finally, argument pBegin points to the byte immediately following the ** end of the space required by this page for the cell-pointer area (for ** all cells - not just those inserted by the current call). If the content -** area must be extended to before this point in order to accomodate all +** area must be extended to before this point in order to accommodate all ** cells in apCell[], then the cells do not fit and non-zero is returned. */ static int pageInsertArray( @@ -70166,12 +80976,14 @@ static int pageInsertArray( u8 *pEnd; /* Maximum extent of cell data */ assert( CORRUPT_DB || pPg->hdrOffset==0 ); /* Never called on page 1 */ if( iEnd<=iFirst ) return 0; - for(k=0; pCArray->ixNx[k]<=i && ALWAYS(kixNx[NB*2-1]>i ); + for(k=0; pCArray->ixNx[k]<=i ; k++){} pEnd = pCArray->apEnd[k]; while( 1 /*Exit by break*/ ){ int sz, rc; u8 *pSlot; - sz = cachedCellSize(pCArray, i); + assert( pCArray->szCell[i]!=0 ); + sz = pCArray->szCell[i]; if( (aData[1]==0 && aData[2]==0) || (pSlot = pageFindSlot(pPg,sz,&rc))==0 ){ if( (pData - pBegin)pBt->usableSize]; u8 * const pStart = &aData[pPg->hdrOffset + 8 + pPg->childPtrSize]; int nRet = 0; - int i; + int i, j; int iEnd = iFirst + nCell; - u8 *pFree = 0; - int szFree = 0; + int nFree = 0; + int aOfst[10]; + int aAfter[10]; for(i=iFirst; iapCell[i]; if( SQLITE_WITHIN(pCell, pStart, pEnd) ){ int sz; + int iAfter; + int iOfst; /* No need to use cachedCellSize() here. The sizes of all cells that ** are to be freed have already been computing while deciding which ** cells need freeing */ sz = pCArray->szCell[i]; assert( sz>0 ); - if( pFree!=(pCell + sz) ){ - if( pFree ){ - assert( pFree>aData && (pFree - aData)<65536 ); - freeSpace(pPg, (u16)(pFree - aData), szFree); + iOfst = (u16)(pCell - aData); + iAfter = iOfst+sz; + for(j=0; jpEnd ) return 0; - }else{ - pFree = pCell; - szFree += sz; + } + if( j>=nFree ){ + if( nFree>=(int)(sizeof(aOfst)/sizeof(aOfst[0])) ){ + for(j=0; jpEnd ) return 0; + nFree++; } nRet++; } } - if( pFree ){ - assert( pFree>aData && (pFree - aData)<65536 ); - freeSpace(pPg, (u16)(pFree - aData), szFree); + for(j=0; j=0 ); if( iOldnCell ) return SQLITE_CORRUPT_BKPT; + if( NEVER(nShift>nCell) ) return SQLITE_CORRUPT_BKPT; memmove(pPg->aCellIdx, &pPg->aCellIdx[nShift*2], nCell*2); nCell -= nShift; } @@ -70306,8 +81131,9 @@ static int editPage( nCell -= nTail; } - pData = &aData[get2byteNotZero(&aData[hdr+5])]; + pData = &aData[get2byte(&aData[hdr+5])]; if( pDatapPg->aDataEnd) ) goto editpage_fail; /* Add cells to the start of the page */ if( iNewnCell = nNew; + assert( nNew < 10922 ); + pPg->nCell = (u16)nNew; pPg->nOverflow = 0; put2byte(&aData[hdr+3], pPg->nCell); @@ -70368,6 +81199,7 @@ static int editPage( return SQLITE_OK; editpage_fail: /* Unable to edit this page. Rebuild it from scratch instead. */ + if( nNew<1 ) return SQLITE_CORRUPT_BKPT; populateCellCache(pCArray, iNew, nNew); return rebuildPage(pCArray, iNew, nNew, pPg); } @@ -70406,12 +81238,12 @@ static int balance_quick(MemPage *pParent, MemPage *pPage, u8 *pSpace){ assert( sqlite3_mutex_held(pPage->pBt->mutex) ); assert( sqlite3PagerIswriteable(pParent->pDbPage) ); assert( pPage->nOverflow==1 ); - + if( pPage->nCell==0 ) return SQLITE_CORRUPT_BKPT; /* dbfuzz001.test */ assert( pPage->nFree>=0 ); assert( pParent->nFree>=0 ); - /* Allocate a new page. This page will become the right-sibling of + /* Allocate a new page. This page will become the right-sibling of ** pPage. Make the parent page writable, so that the new divider cell ** may be inserted. If both these operations are successful, proceed. */ @@ -70434,6 +81266,7 @@ static int balance_quick(MemPage *pParent, MemPage *pPage, u8 *pSpace){ b.szCell = &szCell; b.apEnd[0] = pPage->aDataEnd; b.ixNx[0] = 2; + b.ixNx[NB*2-1] = 0x7fffffff; rc = rebuildPage(&b, 0, 1, pNew); if( NEVER(rc) ){ releasePage(pNew); @@ -70442,28 +81275,28 @@ static int balance_quick(MemPage *pParent, MemPage *pPage, u8 *pSpace){ pNew->nFree = pBt->usableSize - pNew->cellOffset - 2 - szCell; /* If this is an auto-vacuum database, update the pointer map - ** with entries for the new page, and any pointer from the + ** with entries for the new page, and any pointer from the ** cell on the page to an overflow page. If either of these ** operations fails, the return code is set, but the contents - ** of the parent page are still manipulated by thh code below. + ** of the parent page are still manipulated by the code below. ** That is Ok, at this point the parent page is guaranteed to ** be marked as dirty. Returning an error code will cause a ** rollback, undoing any changes made to the parent page. */ - if( ISAUTOVACUUM ){ + if( ISAUTOVACUUM(pBt) ){ ptrmapPut(pBt, pgnoNew, PTRMAP_BTREE, pParent->pgno, &rc); if( szCell>pNew->minLocal ){ ptrmapPutOvflPtr(pNew, pNew, pCell, &rc); } } - + /* Create a divider cell to insert into pParent. The divider cell ** consists of a 4-byte page number (the page number of pPage) and ** a variable length key value (which must be the same value as the ** largest key on pPage). ** - ** To find the largest key value on pPage, first find the right-most - ** cell on pPage. The first two fields of this cell are the + ** To find the largest key value on pPage, first find the right-most + ** cell on pPage. The first two fields of this cell are the ** record-length (a variable length integer at most 32-bits in size) ** and the key value (a variable length integer, may have any value). ** The first of the while(...) loops below skips over the record-length @@ -70478,13 +81311,13 @@ static int balance_quick(MemPage *pParent, MemPage *pPage, u8 *pSpace){ /* Insert the new divider cell into pParent. */ if( rc==SQLITE_OK ){ - insertCell(pParent, pParent->nCell, pSpace, (int)(pOut-pSpace), - 0, pPage->pgno, &rc); + rc = insertCell(pParent, pParent->nCell, pSpace, (int)(pOut-pSpace), + 0, pPage->pgno); } /* Set the right-child pointer of pParent to point to the new page. */ put4byte(&pParent->aData[pParent->hdrOffset+8], pgnoNew); - + /* Release the reference to the new page. */ releasePage(pNew); } @@ -70496,7 +81329,7 @@ static int balance_quick(MemPage *pParent, MemPage *pPage, u8 *pSpace){ #if 0 /* ** This function does not contribute anything to the operation of SQLite. -** it is sometimes activated temporarily while debugging code responsible +** it is sometimes activated temporarily while debugging code responsible ** for setting pointer-map entries. */ static int ptrmapCheckPages(MemPage **apPage, int nPage){ @@ -70511,7 +81344,7 @@ static int ptrmapCheckPages(MemPage **apPage, int nPage){ for(j=0; jnCell; j++){ CellInfo info; u8 *z; - + z = findCell(pPage, j); pPage->xParseCell(pPage, z, &info); if( info.nLocalpgno==1) ? 100 : 0); int rc; int iData; - - + + assert( pFrom->isInit ); assert( pFrom->nFree>=iToHdr ); assert( get2byte(&aFrom[iFromHdr+5]) <= (int)pBt->usableSize ); - + /* Copy the b-tree node content from page pFrom to page pTo. */ iData = get2byte(&aFrom[iFromHdr+5]); memcpy(&aTo[iData], &aFrom[iData], pBt->usableSize-iData); memcpy(&aTo[iToHdr], &aFrom[iFromHdr], pFrom->cellOffset + 2*pFrom->nCell); - + /* Reinitialize page pTo so that the contents of the MemPage structure ** match the new data. The initialization of pTo can actually fail under - ** fairly obscure circumstances, even though it is a copy of initialized + ** fairly obscure circumstances, even though it is a copy of initialized ** page pFrom. */ pTo->isInit = 0; @@ -70584,11 +81417,11 @@ static void copyNodeContent(MemPage *pFrom, MemPage *pTo, int *pRC){ *pRC = rc; return; } - + /* If this is an auto-vacuum database, update the pointer-map entries ** for any b-tree or overflow pages that pTo now contains the pointers to. */ - if( ISAUTOVACUUM ){ + if( ISAUTOVACUUM(pBt) ){ *pRC = setChildPtrmaps(pTo); } } @@ -70599,13 +81432,13 @@ static void copyNodeContent(MemPage *pFrom, MemPage *pTo, int *pRC){ ** (hereafter "the page") and up to 2 siblings so that all pages have about the ** same amount of free space. Usually a single sibling on either side of the ** page are used in the balancing, though both siblings might come from one -** side if the page is the first or last child of its parent. If the page +** side if the page is the first or last child of its parent. If the page ** has fewer than 2 siblings (something which can only happen if the page ** is a root page or a child of a root page) then all available siblings ** participate in the balancing. ** -** The number of siblings of the page might be increased or decreased by -** one or two in an effort to keep pages nearly full but not over full. +** The number of siblings of the page might be increased or decreased by +** one or two in an effort to keep pages nearly full but not over full. ** ** Note that when this routine is called, some of the cells on the page ** might not actually be stored in MemPage.aData[]. This can happen @@ -70616,7 +81449,7 @@ static void copyNodeContent(MemPage *pFrom, MemPage *pTo, int *pRC){ ** inserted into or removed from the parent page (pParent). Doing so ** may cause the parent page to become overfull or underfull. If this ** happens, it is the responsibility of the caller to invoke the correct -** balancing routine to fix this problem (see the balance() routine). +** balancing routine to fix this problem (see the balance() routine). ** ** If this routine fails for any reason, it might leave the database ** in a corrupted state. So if this routine fails, the database should @@ -70631,7 +81464,7 @@ static void copyNodeContent(MemPage *pFrom, MemPage *pTo, int *pRC){ ** of the page-size, the aOvflSpace[] buffer is guaranteed to be large ** enough for all overflow cells. ** -** If aOvflSpace is set to a null pointer, this function returns +** If aOvflSpace is set to a null pointer, this function returns ** SQLITE_NOMEM. */ static int balance_nonroot( @@ -70654,7 +81487,7 @@ static int balance_nonroot( int pageFlags; /* Value of pPage->aData[0] */ int iSpace1 = 0; /* First unused byte of aSpace1[] */ int iOvflSpace = 0; /* First unused byte of aOvflSpace[] */ - int szScratch; /* Size of scratch memory requested */ + u64 szScratch; /* Size of scratch memory requested */ MemPage *apOld[NB]; /* pPage and up to two siblings */ MemPage *apNew[NB+2]; /* pPage and up to NB siblings after balancing */ u8 *pRight; /* Location in parent of right-sibling pointer */ @@ -70666,19 +81499,18 @@ static int balance_nonroot( Pgno pgno; /* Temp var to store a page number in */ u8 abDone[NB+2]; /* True after i'th new page is populated */ Pgno aPgno[NB+2]; /* Page numbers of new pages before shuffling */ - Pgno aPgOrder[NB+2]; /* Copy of aPgno[] used for sorting pages */ - u16 aPgFlags[NB+2]; /* flags field of new pages before shuffling */ - CellArray b; /* Parsed information on cells being balanced */ + CellArray b; /* Parsed information on cells being balanced */ memset(abDone, 0, sizeof(abDone)); - b.nCell = 0; - b.apCell = 0; + assert( sizeof(b) - sizeof(b.ixNx) == offsetof(CellArray,ixNx) ); + memset(&b, 0, sizeof(b)-sizeof(b.ixNx[0])); + b.ixNx[NB*2-1] = 0x7fffffff; pBt = pParent->pBt; assert( sqlite3_mutex_held(pBt->mutex) ); assert( sqlite3PagerIswriteable(pParent->pDbPage) ); /* At this point pParent may have at most one overflow cell. And if - ** this overflow cell is present, it must be the cell with + ** this overflow cell is present, it must be the cell with ** index iParentIdx. This scenario comes about when this function ** is called (indirectly) from sqlite3BtreeDelete(). */ @@ -70690,11 +81522,11 @@ static int balance_nonroot( } assert( pParent->nFree>=0 ); - /* Find the sibling pages to balance. Also locate the cells in pParent - ** that divide the siblings. An attempt is made to find NN siblings on - ** either side of pPage. More siblings are taken from one side, however, + /* Find the sibling pages to balance. Also locate the cells in pParent + ** that divide the siblings. An attempt is made to find NN siblings on + ** either side of pPage. More siblings are taken from one side, however, ** if there are fewer than NN siblings on the other side. If pParent - ** has NB or fewer children then all children of pParent are taken. + ** has NB or fewer children then all children of pParent are taken. ** ** This loop also drops the divider cells from the parent page. This ** way, the remainder of the function does not have to deal with any @@ -70706,7 +81538,7 @@ static int balance_nonroot( nxDiv = 0; }else{ assert( bBulk==0 || bBulk==1 ); - if( iParentIdx==0 ){ + if( iParentIdx==0 ){ nxDiv = 0; }else if( iParentIdx==i ){ nxDiv = i-2+bBulk; @@ -70723,7 +81555,9 @@ static int balance_nonroot( } pgno = get4byte(pRight); while( 1 ){ - rc = getAndInitPage(pBt, pgno, &apOld[i], 0, 0); + if( rc==SQLITE_OK ){ + rc = getAndInitPage(pBt, pgno, &apOld[i], 0); + } if( rc ){ memset(apOld, 0, (i+1)*sizeof(MemPage*)); goto balance_cleanup; @@ -70735,6 +81569,7 @@ static int balance_nonroot( goto balance_cleanup; } } + nMaxCells += apOld[i]->nCell + ArraySize(pParent->apOvfl); if( (i--)==0 ) break; if( pParent->nOverflow && i+nxDiv==pParent->aiOvfl[0] ){ @@ -70752,7 +81587,7 @@ static int balance_nonroot( ** This is safe because dropping a cell only overwrites the first ** four bytes of it, and this function does not need the first ** four bytes of the divider cell. So the pointer is safe to use - ** later on. + ** later on. ** ** But not if we are in secure-delete mode. In secure-delete mode, ** the dropCell() routine will overwrite the entire cell with zeroes. @@ -70762,12 +81597,10 @@ static int balance_nonroot( if( pBt->btsFlags & BTS_FAST_SECURE ){ int iOff; + /* If the following if() condition is not true, the db is corrupted. + ** The call to dropCell() below will detect this. */ iOff = SQLITE_PTR_TO_INT(apDiv[i]) - SQLITE_PTR_TO_INT(pParent->aData); - if( (iOff+szNew[i])>(int)pBt->usableSize ){ - rc = SQLITE_CORRUPT_BKPT; - memset(apOld, 0, (i+1)*sizeof(MemPage*)); - goto balance_cleanup; - }else{ + if( (iOff+szNew[i])<=(int)pBt->usableSize ){ memcpy(&aOvflSpace[iOff], apDiv[i], szNew[i]); apDiv[i] = &aOvflSpace[apDiv[i]-pParent->aData]; } @@ -70778,7 +81611,6 @@ static int balance_nonroot( /* Make nMaxCells a multiple of 4 in order to preserve 8-byte ** alignment */ - nMaxCells = nOld*(MX_CELL(pBt) + ArraySize(pParent->apOvfl)); nMaxCells = (nMaxCells + 3)&~3; /* @@ -70825,12 +81657,13 @@ static int balance_nonroot( u16 maskPage = pOld->maskPage; u8 *piCell = aData + pOld->cellOffset; u8 *piEnd; + VVA_ONLY( int nCellAtStart = b.nCell; ) /* Verify that all sibling pages are of the same "type" (table-leaf, ** table-interior, index-leaf, or index-interior). */ if( pOld->aData[0]!=apOld[0]->aData[0] ){ - rc = SQLITE_CORRUPT_BKPT; + rc = SQLITE_CORRUPT_PAGE(pOld); goto balance_cleanup; } @@ -70853,6 +81686,10 @@ static int balance_nonroot( */ memset(&b.szCell[b.nCell], 0, sizeof(b.szCell[0])*(limit+pOld->nOverflow)); if( pOld->nOverflow>0 ){ + if( NEVER(limitaiOvfl[0]) ){ + rc = SQLITE_CORRUPT_PAGE(pOld); + goto balance_cleanup; + } limit = pOld->aiOvfl[0]; for(j=0; jnCell+pOld->nOverflow) ); cntOld[i] = b.nCell; if( ileaf ){ assert( leafCorrection==0 ); - assert( pOld->hdrOffset==0 ); + assert( pOld->hdrOffset==0 || CORRUPT_DB ); /* The right pointer of the child page pOld becomes the left ** pointer of the divider cell */ memcpy(b.apCell[b.nCell], &pOld->aData[8], 4); @@ -70912,7 +81750,7 @@ static int balance_nonroot( ** Figure out the number of pages needed to hold all b.nCell cells. ** Store this number in "k". Also compute szNew[] which is the total ** size of all cells on the i-th page and cntNew[] which is the index - ** in b.apCell[] of the cell that divides page i from page i+1. + ** in b.apCell[] of the cell that divides page i from page i+1. ** cntNew[k] should equal b.nCell. ** ** Values computed by this block: @@ -70922,7 +81760,7 @@ static int balance_nonroot( ** cntNew[i]: Index in b.apCell[] and b.szCell[] for the first cell to ** the right of the i-th sibling page. ** usableSpace: Number of bytes of space available on each sibling. - ** + ** */ usableSpace = pBt->usableSize - 12 + leafCorrection; for(i=k=0; i szLeft-(b.szCell[r]+(i==k-1?0:2)))){ + && (bBulk || szRight+szD+2 > szLeft-(szR+(i==k-1?0:2)))){ break; } - szRight += b.szCell[d] + 2; - szLeft -= b.szCell[r] + 2; + szRight += szD + 2; + szLeft -= szR + 2; cntNew[i-1] = r; r--; d--; @@ -71030,7 +81870,7 @@ static int balance_nonroot( } } - /* Sanity check: For a non-corrupt database file one of the follwing + /* Sanity check: For a non-corrupt database file one of the following ** must be true: ** (1) We found one or more cells (cntNew[0])>0), or ** (2) pPage is a virtual root page. A virtual root page is when @@ -71038,7 +81878,7 @@ static int balance_nonroot( ** that page. */ assert( cntNew[0]>0 || (pParent->pgno==1 && pParent->nCell==0) || CORRUPT_DB); - TRACE(("BALANCE: old: %d(nc=%d) %d(nc=%d) %d(nc=%d)\n", + TRACE(("BALANCE: old: %u(nc=%u) %u(nc=%u) %u(nc=%u)\n", apOld[0]->pgno, apOld[0]->nCell, nOld>=2 ? apOld[1]->pgno : 0, nOld>=2 ? apOld[1]->nCell : 0, nOld>=3 ? apOld[2]->pgno : 0, nOld>=3 ? apOld[2]->nCell : 0 @@ -71055,6 +81895,11 @@ static int balance_nonroot( apOld[i] = 0; rc = sqlite3PagerWrite(pNew->pDbPage); nNew++; + if( sqlite3PagerPageRefcount(pNew->pDbPage)!=1+(i==(iParentIdx-nxDiv)) + && rc==SQLITE_OK + ){ + rc = SQLITE_CORRUPT_BKPT; + } if( rc ) goto balance_cleanup; }else{ assert( i>0 ); @@ -71066,7 +81911,7 @@ static int balance_nonroot( cntOld[i] = b.nCell; /* Set the pointer-map entry for the new sibling page. */ - if( ISAUTOVACUUM ){ + if( ISAUTOVACUUM(pBt) ){ ptrmapPut(pBt, pNew->pgno, PTRMAP_BTREE, pParent->pgno, &rc); if( rc!=SQLITE_OK ){ goto balance_cleanup; @@ -71076,52 +81921,49 @@ static int balance_nonroot( } /* - ** Reassign page numbers so that the new pages are in ascending order. + ** Reassign page numbers so that the new pages are in ascending order. ** This helps to keep entries in the disk file in order so that a scan - ** of the table is closer to a linear scan through the file. That in turn + ** of the table is closer to a linear scan through the file. That in turn ** helps the operating system to deliver pages from the disk more rapidly. ** - ** An O(n^2) insertion sort algorithm is used, but since n is never more - ** than (NB+2) (a small constant), that should not be a problem. + ** An O(N*N) sort algorithm is used, but since N is never more than NB+2 + ** (5), that is not a performance concern. ** - ** When NB==3, this one optimization makes the database about 25% faster + ** When NB==3, this one optimization makes the database about 25% faster ** for large insertions and deletions. */ for(i=0; ipgno; - aPgFlags[i] = apNew[i]->pDbPage->flags; - for(j=0; jpgno; + assert( apNew[i]->pDbPage->flags & PGHDR_WRITEABLE ); + assert( apNew[i]->pDbPage->flags & PGHDR_DIRTY ); } - for(i=0; ii ){ - sqlite3PagerRekey(apNew[iBest]->pDbPage, pBt->nPage+iBest+1, 0); - } - sqlite3PagerRekey(apNew[i]->pDbPage, pgno, aPgFlags[iBest]); - apNew[i]->pgno = pgno; + for(i=0; ipgno < apNew[iB]->pgno ) iB = j; } - } - TRACE(("BALANCE: new: %d(%d nc=%d) %d(%d nc=%d) %d(%d nc=%d) " - "%d(%d nc=%d) %d(%d nc=%d)\n", + /* If apNew[i] has a page number that is bigger than any of the + ** subsequence apNew[i] entries, then swap apNew[i] with the subsequent + ** entry that has the smallest page number (which we know to be + ** entry apNew[iB]). + */ + if( iB!=i ){ + Pgno pgnoA = apNew[i]->pgno; + Pgno pgnoB = apNew[iB]->pgno; + Pgno pgnoTemp = (PENDING_BYTE/pBt->pageSize)+1; + u16 fgA = apNew[i]->pDbPage->flags; + u16 fgB = apNew[iB]->pDbPage->flags; + sqlite3PagerRekey(apNew[i]->pDbPage, pgnoTemp, fgB); + sqlite3PagerRekey(apNew[iB]->pDbPage, pgnoA, fgA); + sqlite3PagerRekey(apNew[i]->pDbPage, pgnoB, fgB); + apNew[i]->pgno = pgnoB; + apNew[iB]->pgno = pgnoA; + } + } + + TRACE(("BALANCE: new: %u(%u nc=%u) %u(%u nc=%u) %u(%u nc=%u) " + "%u(%u nc=%u) %u(%u nc=%u)\n", apNew[0]->pgno, szNew[0], cntNew[0], nNew>=2 ? apNew[1]->pgno : 0, nNew>=2 ? szNew[1] : 0, nNew>=2 ? cntNew[1] - cntNew[0] - !leafData : 0, @@ -71134,17 +81976,24 @@ static int balance_nonroot( )); assert( sqlite3PagerIswriteable(pParent->pDbPage) ); + assert( nNew>=1 && nNew<=ArraySize(apNew) ); + assert( apNew[nNew-1]!=0 ); put4byte(pRight, apNew[nNew-1]->pgno); /* If the sibling pages are not leaves, ensure that the right-child pointer - ** of the right-most new sibling page is set to the value that was + ** of the right-most new sibling page is set to the value that was ** originally in the same field of the right-most old sibling page. */ if( (pageFlags & PTF_LEAF)==0 && nOld!=nNew ){ - MemPage *pOld = (nNew>nOld ? apNew : apOld)[nOld-1]; + MemPage *pOld; + if( nNew>nOld ){ + pOld = apNew[nOld-1]; + }else{ + pOld = apOld[nOld-1]; + } memcpy(&apNew[nNew-1]->aData[8], &pOld->aData[8], 4); } - /* Make any required updates to pointer map entries associated with + /* Make any required updates to pointer map entries associated with ** cells stored on sibling pages following the balance operation. Pointer ** map entries associated with divider cells are set by the insertCell() ** routine. The associated pointer map entries are: @@ -71155,12 +82004,12 @@ static int balance_nonroot( ** b) if the sibling pages are not leaves, the child page associated ** with the cell. ** - ** If the sibling pages are not leaves, then the pointer map entry - ** associated with the right-child of each sibling may also need to be - ** updated. This happens below, after the sibling pages have been + ** If the sibling pages are not leaves, then the pointer map entry + ** associated with the right-child of each sibling may also need to be + ** updated. This happens below, after the sibling pages have been ** populated, not here. */ - if( ISAUTOVACUUM ){ + if( ISAUTOVACUUM(pBt) ){ MemPage *pOld; MemPage *pNew = pOld = apNew[0]; int cntOldNext = pNew->nCell + pNew->nOverflow; @@ -71172,6 +82021,7 @@ static int balance_nonroot( while( i==cntOldNext ){ iOld++; assert( iOld=0 && iOldnCell + pOld->nOverflow + !leafData; } @@ -71181,7 +82031,7 @@ static int balance_nonroot( } /* Cell pCell is destined for new sibling page pNew. Originally, it - ** was either part of sibling page iOld (possibly an overflow cell), + ** was either part of sibling page iOld (possibly an overflow cell), ** or else the divider cell to the left of sibling page iOld. So, ** if sibling page iOld had the same page number as pNew, and if ** pCell really was a part of sibling page iOld (not a divider or @@ -71206,6 +82056,7 @@ static int balance_nonroot( u8 *pCell; u8 *pTemp; int sz; + u8 *pSrcEnd; MemPage *pNew = apNew[i]; j = cntNew[i]; @@ -71217,9 +82068,9 @@ static int balance_nonroot( if( !pNew->leaf ){ memcpy(&pNew->aData[8], pCell, 4); }else if( leafData ){ - /* If the tree is a leaf-data tree, and the siblings are leaves, - ** then there is no divider cell in b.apCell[]. Instead, the divider - ** cell consists of the integer key for the right-most cell of + /* If the tree is a leaf-data tree, and the siblings are leaves, + ** then there is no divider cell in b.apCell[]. Instead, the divider + ** cell consists of the integer key for the right-most cell of ** the sibling-page assembled above only. */ CellInfo info; @@ -71232,9 +82083,9 @@ static int balance_nonroot( pCell -= 4; /* Obscure case for non-leaf-data trees: If the cell at pCell was ** previously stored on a leaf node, and its reported size was 4 - ** bytes, then it may actually be smaller than this + ** bytes, then it may actually be smaller than this ** (see btreeParseCellPtr(), 4 bytes is the minimum size of - ** any cell). But it is important to pass the correct size to + ** any cell). But it is important to pass the correct size to ** insertCell(), so reparse the cell now. ** ** This can only happen for b-trees used to evaluate "IN (SELECT ...)" @@ -71249,7 +82100,14 @@ static int balance_nonroot( iOvflSpace += sz; assert( sz<=pBt->maxLocal+23 ); assert( iOvflSpace <= (int)pBt->pageSize ); - insertCell(pParent, nxDiv+i, pCell, sz, pTemp, pNew->pgno, &rc); + assert( b.ixNx[NB*2-1]>j ); + for(k=0; b.ixNx[k]<=j; k++){} + pSrcEnd = b.apEnd[k]; + if( SQLITE_OVERFLOW(pSrcEnd, pCell, pCell+sz) ){ + rc = SQLITE_CORRUPT_BKPT; + goto balance_cleanup; + } + rc = insertCell(pParent, nxDiv+i, pCell, sz, pTemp, pNew->pgno); if( rc!=SQLITE_OK ) goto balance_cleanup; assert( sqlite3PagerIswriteable(pParent->pDbPage) ); } @@ -71279,6 +82137,8 @@ static int balance_nonroot( for(i=1-nNew; i=0 && iPg=1 || i>=0 ); + assert( iPg=0 /* On the upwards pass, or... */ || cntOld[iPg-1]>=cntNew[iPg-1] /* Condition (1) is true */ @@ -71326,8 +82186,8 @@ static int balance_nonroot( ** b-tree structure by one. This is described as the "balance-shallower" ** sub-algorithm in some documentation. ** - ** If this is an auto-vacuum database, the call to copyNodeContent() - ** sets all pointer-map entries corresponding to database image pages + ** If this is an auto-vacuum database, the call to copyNodeContent() + ** sets all pointer-map entries corresponding to database image pages ** for which the pointer is stored within the content being copied. ** ** It is critical that the child page be defragmented before being @@ -71338,14 +82198,14 @@ static int balance_nonroot( assert( nNew==1 || CORRUPT_DB ); rc = defragmentPage(apNew[0], -1); testcase( rc!=SQLITE_OK ); - assert( apNew[0]->nFree == + assert( apNew[0]->nFree == (get2byteNotZero(&apNew[0]->aData[5]) - apNew[0]->cellOffset - apNew[0]->nCell*2) || rc!=SQLITE_OK ); copyNodeContent(apNew[0], pParent, &rc); freePage(apNew[0], &rc); - }else if( ISAUTOVACUUM && !leafCorrection ){ + }else if( ISAUTOVACUUM(pBt) && !leafCorrection ){ /* Fix the pointer map entries associated with the right-child of each ** sibling page. All other pointer map entries have already been taken ** care of. */ @@ -71356,7 +82216,7 @@ static int balance_nonroot( } assert( pParent->isInit ); - TRACE(("BALANCE: finished: old=%d new=%d cells=%d\n", + TRACE(("BALANCE: finished: old=%u new=%u cells=%u\n", nOld, nNew, b.nCell)); /* Free any old pages that were not reused as new pages. @@ -71366,9 +82226,9 @@ static int balance_nonroot( } #if 0 - if( ISAUTOVACUUM && rc==SQLITE_OK && apNew[0]->isInit ){ + if( ISAUTOVACUUM(pBt) && rc==SQLITE_OK && apNew[0]->isInit ){ /* The ptrmapCheckPages() contains assert() statements that verify that - ** all pointer map pages are set correctly. This is helpful while + ** all pointer map pages are set correctly. This is helpful while ** debugging. This is usually disabled because a corrupt database may ** cause an assert() statement to fail. */ ptrmapCheckPages(apNew, nNew); @@ -71398,15 +82258,15 @@ static int balance_nonroot( ** ** A new child page is allocated and the contents of the current root ** page, including overflow cells, are copied into the child. The root -** page is then overwritten to make it an empty page with the right-child +** page is then overwritten to make it an empty page with the right-child ** pointer pointing to the new page. ** -** Before returning, all pointer-map entries corresponding to pages +** Before returning, all pointer-map entries corresponding to pages ** that the new child-page now contains pointers to are updated. The ** entry corresponding to the new right-child pointer of the root ** page is also updated. ** -** If successful, *ppChild is set to contain a reference to the child +** If successful, *ppChild is set to contain a reference to the child ** page and SQLITE_OK is returned. In this case the caller is required ** to call releasePage() on *ppChild exactly once. If an error occurs, ** an error code is returned and *ppChild is set to 0. @@ -71420,7 +82280,7 @@ static int balance_deeper(MemPage *pRoot, MemPage **ppChild){ assert( pRoot->nOverflow>0 ); assert( sqlite3_mutex_held(pBt->mutex) ); - /* Make pRoot, the root page of the b-tree, writable. Allocate a new + /* Make pRoot, the root page of the b-tree, writable. Allocate a new ** page that will become the new right-child of pPage. Copy the contents ** of the node stored on pRoot into the new child page. */ @@ -71428,7 +82288,7 @@ static int balance_deeper(MemPage *pRoot, MemPage **ppChild){ if( rc==SQLITE_OK ){ rc = allocateBtreePage(pBt,&pChild,&pgnoChild,pRoot->pgno,0); copyNodeContent(pRoot, pChild, &rc); - if( ISAUTOVACUUM ){ + if( ISAUTOVACUUM(pBt) ){ ptrmapPut(pBt, pgnoChild, PTRMAP_BTREE, pRoot->pgno, &rc); } } @@ -71441,7 +82301,7 @@ static int balance_deeper(MemPage *pRoot, MemPage **ppChild){ assert( sqlite3PagerIswriteable(pRoot->pDbPage) ); assert( pChild->nCell==pRoot->nCell || CORRUPT_DB ); - TRACE(("BALANCE: copy root %d into %d\n", pRoot->pgno, pChild->pgno)); + TRACE(("BALANCE: copy root %u into %u\n", pRoot->pgno, pChild->pgno)); /* Copy the overflow cells from pRoot to pChild */ memcpy(pChild->aiOvfl, pRoot->aiOvfl, @@ -71458,10 +82318,34 @@ static int balance_deeper(MemPage *pRoot, MemPage **ppChild){ return SQLITE_OK; } +/* +** Return SQLITE_CORRUPT if any cursor other than pCur is currently valid +** on the same B-tree as pCur. +** +** This can occur if a database is corrupt with two or more SQL tables +** pointing to the same b-tree. If an insert occurs on one SQL table +** and causes a BEFORE TRIGGER to do a secondary insert on the other SQL +** table linked to the same b-tree. If the secondary insert causes a +** rebalance, that can change content out from under the cursor on the +** first SQL table, violating invariants on the first insert. +*/ +static int anotherValidCursor(BtCursor *pCur){ + BtCursor *pOther; + for(pOther=pCur->pBt->pCursor; pOther; pOther=pOther->pNext){ + if( pOther!=pCur + && pOther->eState==CURSOR_VALID + && pOther->pPage==pCur->pPage + ){ + return SQLITE_CORRUPT_PAGE(pCur->pPage); + } + } + return SQLITE_OK; +} + /* ** The page that pCur currently points to has just been modified in ** some way. This function figures out if this modification means the -** tree needs to be balanced, and if so calls the appropriate balancing +** tree needs to be balanced, and if so calls the appropriate balancing ** routine. Balancing routines are: ** ** balance_quick() @@ -71470,7 +82354,6 @@ static int balance_deeper(MemPage *pRoot, MemPage **ppChild){ */ static int balance(BtCursor *pCur){ int rc = SQLITE_OK; - const int nMin = pCur->pBt->usableSize * 2 / 3; u8 aBalanceQuickSpace[13]; u8 *pFree = 0; @@ -71478,17 +82361,23 @@ static int balance(BtCursor *pCur){ VVA_ONLY( int balance_deeper_called = 0 ); do { - int iPage = pCur->iPage; + int iPage; MemPage *pPage = pCur->pPage; if( NEVER(pPage->nFree<0) && btreeComputeFreeSpace(pPage) ) break; - if( iPage==0 ){ - if( pPage->nOverflow ){ + if( pPage->nOverflow==0 && pPage->nFree*3<=(int)pCur->pBt->usableSize*2 ){ + /* No rebalance required as long as: + ** (1) There are no overflow cells + ** (2) The amount of free space on the page is less than 2/3rds of + ** the total usable space on the page. */ + break; + }else if( (iPage = pCur->iPage)==0 ){ + if( pPage->nOverflow && (rc = anotherValidCursor(pCur))==SQLITE_OK ){ /* The root page of the b-tree is overfull. In this case call the ** balance_deeper() function to create a new child for the root-page ** and copy the current contents of the root-page to it. The ** next iteration of the do-loop will balance the child page. - */ + */ assert( balance_deeper_called==0 ); VVA_ONLY( balance_deeper_called++ ); rc = balance_deeper(pPage, &pCur->apPage[1]); @@ -71503,8 +82392,11 @@ static int balance(BtCursor *pCur){ }else{ break; } - }else if( pPage->nOverflow==0 && pPage->nFree<=nMin ){ - break; + }else if( sqlite3PagerPageRefcount(pPage->pDbPage)>1 ){ + /* The page being written is not a root page, and there is currently + ** more than one reference to it. This only happens if the page is one + ** of its own ancestor pages. Corruption. */ + rc = SQLITE_CORRUPT_PAGE(pPage); }else{ MemPage * const pParent = pCur->apPage[iPage-1]; int const iIdx = pCur->aiIdx[iPage-1]; @@ -71524,17 +82416,17 @@ static int balance(BtCursor *pCur){ /* Call balance_quick() to create a new sibling of pPage on which ** to store the overflow cell. balance_quick() inserts a new cell ** into pParent, which may cause pParent overflow. If this - ** happens, the next iteration of the do-loop will balance pParent + ** happens, the next iteration of the do-loop will balance pParent ** use either balance_nonroot() or balance_deeper(). Until this ** happens, the overflow cell is stored in the aBalanceQuickSpace[] - ** buffer. + ** buffer. ** ** The purpose of the following assert() is to check that only a ** single call to balance_quick() is made for each call to this ** function. If this were not verified, a subtle bug involving reuse ** of the aBalanceQuickSpace[] might sneak in. */ - assert( balance_quick_called==0 ); + assert( balance_quick_called==0 ); VVA_ONLY( balance_quick_called++ ); rc = balance_quick(pParent, pPage, aBalanceQuickSpace); }else @@ -71545,15 +82437,15 @@ static int balance(BtCursor *pCur){ ** modifying the contents of pParent, which may cause pParent to ** become overfull or underfull. The next iteration of the do-loop ** will balance the parent page to correct this. - ** + ** ** If the parent page becomes overfull, the overflow cell or cells - ** are stored in the pSpace buffer allocated immediately below. + ** are stored in the pSpace buffer allocated immediately below. ** A subsequent iteration of the do-loop will deal with this by ** calling balance_nonroot() (balance_deeper() may be called first, ** but it doesn't deal with overflow cells - just moves them to a - ** different page). Once this subsequent call to balance_nonroot() + ** different page). Once this subsequent call to balance_nonroot() ** has completed, it is safe to release the pSpace buffer used by - ** the previous call, as the overflow cell data will have been + ** the previous call, as the overflow cell data will have been ** copied either into the body of a database page or into the new ** pSpace buffer passed to the latter call to balance_nonroot(). */ @@ -71561,9 +82453,9 @@ static int balance(BtCursor *pCur){ rc = balance_nonroot(pParent, iIdx, pSpace, iPage==1, pCur->hints&BTREE_BULKLOAD); if( pFree ){ - /* If pFree is not NULL, it points to the pSpace buffer used + /* If pFree is not NULL, it points to the pSpace buffer used ** by a previous call to balance_nonroot(). Its contents are - ** now stored either on real database pages or within the + ** now stored either on real database pages or within the ** new pSpace buffer, so it may be safely freed here. */ sqlite3PageFree(pFree); } @@ -71603,7 +82495,7 @@ static int btreeOverwriteContent( ){ int nData = pX->nData - iOffset; if( nData<=0 ){ - /* Overwritting with zeros */ + /* Overwriting with zeros */ int i; for(i=0; ipData to write */ int nTotal = pX->nData + pX->nZero; /* Total bytes of to write */ int rc; /* Return code */ @@ -71646,14 +82542,12 @@ static int btreeOverwriteCell(BtCursor *pCur, const BtreePayload *pX){ Pgno ovflPgno; /* Next overflow page to write */ u32 ovflPageSize; /* Size to write on overflow page */ - if( pCur->info.pPayload + pCur->info.nLocal > pPage->aDataEnd ){ - return SQLITE_CORRUPT_BKPT; - } + assert( pCur->info.nLocalinfo.pPayload, pX, 0, pCur->info.nLocal); if( rc ) return rc; - if( pCur->info.nLocal==nTotal ) return SQLITE_OK; /* Now overwrite the overflow pages */ iOffset = pCur->info.nLocal; @@ -71665,8 +82559,8 @@ static int btreeOverwriteCell(BtCursor *pCur, const BtreePayload *pX){ do{ rc = btreeGetPage(pBt, ovflPgno, &pPage, 0); if( rc ) return rc; - if( sqlite3PagerPageRefcount(pPage->pDbPage)!=1 ){ - rc = SQLITE_CORRUPT_BKPT; + if( sqlite3PagerPageRefcount(pPage->pDbPage)!=1 || pPage->isInit ){ + rc = SQLITE_CORRUPT_PAGE(pPage); }else{ if( iOffset+ovflPageSize<(u32)nTotal ){ ovflPgno = get4byte(pPage->aData); @@ -71680,7 +82574,30 @@ static int btreeOverwriteCell(BtCursor *pCur, const BtreePayload *pX){ if( rc ) return rc; iOffset += ovflPageSize; }while( iOffsetnData + pX->nZero; /* Total bytes of to write */ + MemPage *pPage = pCur->pPage; /* Page being written */ + + if( pCur->info.pPayload + pCur->info.nLocal > pPage->aDataEnd + || pCur->info.pPayload < pPage->aData + pPage->cellOffset + ){ + return SQLITE_CORRUPT_PAGE(pPage); + } + if( pCur->info.nLocal==nTotal ){ + /* The entire cell is local */ + return btreeOverwriteContent(pPage, pCur->info.pPayload, pX, + 0, pCur->info.nLocal); + }else{ + /* The cell contains overflow content */ + return btreeOverwriteOverflowCell(pCur, pX); + } } @@ -71696,11 +82613,11 @@ static int btreeOverwriteCell(BtCursor *pCur, const BtreePayload *pX){ ** hold the content of the row. ** ** For an index btree (used for indexes and WITHOUT ROWID tables), the -** key is an arbitrary byte sequence stored in pX.pKey,nKey. The +** key is an arbitrary byte sequence stored in pX.pKey,nKey. The ** pX.pData,nData,nZero fields must be zero. ** ** If the seekResult parameter is non-zero, then a successful call to -** MovetoUnpacked() to seek cursor pCur to (pKey,nKey) has already +** sqlite3BtreeIndexMoveto() to seek cursor pCur to (pKey,nKey) has already ** been performed. In other words, if seekResult!=0 then the cursor ** is currently pointing to a cell that will be adjacent to the cell ** to be inserted. If seekResult<0 then pCur points to a cell that is @@ -71718,7 +82635,7 @@ SQLITE_PRIVATE int sqlite3BtreeInsert( BtCursor *pCur, /* Insert data into the table of this cursor */ const BtreePayload *pX, /* Content of the row to be inserted */ int flags, /* True if this is likely an append */ - int seekResult /* Result of prior MovetoUnpacked() call */ + int seekResult /* Result of prior IndexMoveto() call */ ){ int rc; int loc = seekResult; /* -1: before desired location +1: after */ @@ -71726,60 +82643,74 @@ SQLITE_PRIVATE int sqlite3BtreeInsert( int idx; MemPage *pPage; Btree *p = pCur->pBtree; - BtShared *pBt = p->pBt; unsigned char *oldCell; unsigned char *newCell = 0; - assert( (flags & (BTREE_SAVEPOSITION|BTREE_APPEND))==flags ); - - if( pCur->eState==CURSOR_FAULT ){ - assert( pCur->skipNext!=SQLITE_OK ); - return pCur->skipNext; - } - - assert( cursorOwnsBtShared(pCur) ); - assert( (pCur->curFlags & BTCF_WriteFlag)!=0 - && pBt->inTransaction==TRANS_WRITE - && (pBt->btsFlags & BTS_READ_ONLY)==0 ); - assert( hasSharedCacheTableLock(p, pCur->pgnoRoot, pCur->pKeyInfo!=0, 2) ); - - /* Assert that the caller has been consistent. If this cursor was opened - ** expecting an index b-tree, then the caller should be inserting blob - ** keys with no associated data. If the cursor was opened expecting an - ** intkey table, the caller should be inserting integer keys with a - ** blob of associated data. */ - assert( (pX->pKey==0)==(pCur->pKeyInfo==0) ); + assert( (flags & (BTREE_SAVEPOSITION|BTREE_APPEND|BTREE_PREFORMAT))==flags ); + assert( (flags & BTREE_PREFORMAT)==0 || seekResult || pCur->pKeyInfo==0 ); /* Save the positions of any other cursors open on this table. ** ** In some cases, the call to btreeMoveto() below is a no-op. For ** example, when inserting data into a table with auto-generated integer - ** keys, the VDBE layer invokes sqlite3BtreeLast() to figure out the - ** integer key to use. It then calls this function to actually insert the + ** keys, the VDBE layer invokes sqlite3BtreeLast() to figure out the + ** integer key to use. It then calls this function to actually insert the ** data into the intkey B-Tree. In this case btreeMoveto() recognizes ** that the cursor is already where it needs to be and returns without ** doing any work. To avoid thwarting these optimizations, it is important ** not to clear the cursor here. */ if( pCur->curFlags & BTCF_Multiple ){ - rc = saveAllCursors(pBt, pCur->pgnoRoot, pCur); + rc = saveAllCursors(p->pBt, pCur->pgnoRoot, pCur); if( rc ) return rc; + if( loc && pCur->iPage<0 ){ + /* This can only happen if the schema is corrupt such that there is more + ** than one table or index with the same root page as used by the cursor. + ** Which can only happen if the SQLITE_NoSchemaError flag was set when + ** the schema was loaded. This cannot be asserted though, as a user might + ** set the flag, load the schema, and then unset the flag. */ + return SQLITE_CORRUPT_PGNO(pCur->pgnoRoot); + } } + /* Ensure that the cursor is not in the CURSOR_FAULT state and that it + ** points to a valid cell. + */ + if( pCur->eState>=CURSOR_REQUIRESEEK ){ + testcase( pCur->eState==CURSOR_REQUIRESEEK ); + testcase( pCur->eState==CURSOR_FAULT ); + rc = moveToRoot(pCur); + if( rc && rc!=SQLITE_EMPTY ) return rc; + } + + assert( cursorOwnsBtShared(pCur) ); + assert( (pCur->curFlags & BTCF_WriteFlag)!=0 + && p->pBt->inTransaction==TRANS_WRITE + && (p->pBt->btsFlags & BTS_READ_ONLY)==0 ); + assert( hasSharedCacheTableLock(p, pCur->pgnoRoot, pCur->pKeyInfo!=0, 2) ); + + /* Assert that the caller has been consistent. If this cursor was opened + ** expecting an index b-tree, then the caller should be inserting blob + ** keys with no associated data. If the cursor was opened expecting an + ** intkey table, the caller should be inserting integer keys with a + ** blob of associated data. */ + assert( (flags & BTREE_PREFORMAT) || (pX->pKey==0)==(pCur->pKeyInfo==0) ); + if( pCur->pKeyInfo==0 ){ assert( pX->pKey==0 ); - /* If this is an insert into a table b-tree, invalidate any incrblob + /* If this is an insert into a table b-tree, invalidate any incrblob ** cursors open on the row being replaced */ - invalidateIncrblobCursors(p, pCur->pgnoRoot, pX->nKey, 0); + if( p->hasIncrblobCur ){ + invalidateIncrblobCursors(p, pCur->pgnoRoot, pX->nKey, 0); + } - /* If BTREE_SAVEPOSITION is set, the cursor must already be pointing + /* If BTREE_SAVEPOSITION is set, the cursor must already be pointing ** to a row with the same key as the new entry being inserted. */ #ifdef SQLITE_DEBUG if( flags & BTREE_SAVEPOSITION ){ assert( pCur->curFlags & BTCF_ValidNKey ); assert( pX->nKey==pCur->info.nKey ); - assert( pCur->info.nSize!=0 ); assert( loc==0 ); } #endif @@ -71804,13 +82735,14 @@ SQLITE_PRIVATE int sqlite3BtreeInsert( ** to an adjacent cell. Move the cursor so that it is pointing either ** to the cell to be overwritten or an adjacent cell. */ - rc = sqlite3BtreeMovetoUnpacked(pCur, 0, pX->nKey, flags!=0, &loc); + rc = sqlite3BtreeTableMoveto(pCur, pX->nKey, + (flags & BTREE_APPEND)!=0, &loc); if( rc ) return rc; } }else{ /* This is an index or a WITHOUT ROWID table */ - /* If BTREE_SAVEPOSITION is set, the cursor must already be pointing + /* If BTREE_SAVEPOSITION is set, the cursor must already be pointing ** to a row with the same key as the new entry being inserted. */ assert( (flags & BTREE_SAVEPOSITION)==0 || loc==0 ); @@ -71827,13 +82759,11 @@ SQLITE_PRIVATE int sqlite3BtreeInsert( r.aMem = pX->aMem; r.nField = pX->nMem; r.default_rc = 0; - r.errCode = 0; - r.r1 = 0; - r.r2 = 0; r.eqSeen = 0; - rc = sqlite3BtreeMovetoUnpacked(pCur, &r, 0, flags!=0, &loc); + rc = sqlite3BtreeIndexMoveto(pCur, &r, &loc); }else{ - rc = btreeMoveto(pCur, pX->pKey, pX->nKey, flags!=0, &loc); + rc = btreeMoveto(pCur, pX->pKey, pX->nKey, + (flags & BTREE_APPEND)!=0, &loc); } if( rc ) return rc; } @@ -71847,37 +82777,65 @@ SQLITE_PRIVATE int sqlite3BtreeInsert( if( pCur->info.nKey==pX->nKey ){ BtreePayload x2; x2.pData = pX->pKey; - x2.nData = pX->nKey; + x2.nData = (int)pX->nKey; assert( pX->nKey<=0x7fffffff ); x2.nZero = 0; return btreeOverwriteCell(pCur, &x2); } } - } - assert( pCur->eState==CURSOR_VALID || (pCur->eState==CURSOR_INVALID && loc) ); + assert( pCur->eState==CURSOR_VALID + || (pCur->eState==CURSOR_INVALID && loc) || CORRUPT_DB ); pPage = pCur->pPage; - assert( pPage->intKey || pX->nKey>=0 ); + assert( pPage->intKey || pX->nKey>=0 || (flags & BTREE_PREFORMAT) ); assert( pPage->leaf || !pPage->intKey ); if( pPage->nFree<0 ){ - rc = btreeComputeFreeSpace(pPage); + if( NEVER(pCur->eState>CURSOR_INVALID) ){ + /* ^^^^^--- due to the moveToRoot() call above */ + rc = SQLITE_CORRUPT_PAGE(pPage); + }else{ + rc = btreeComputeFreeSpace(pPage); + } if( rc ) return rc; } - TRACE(("INSERT: table=%d nkey=%lld ndata=%d page=%d %s\n", + TRACE(("INSERT: table=%u nkey=%lld ndata=%u page=%u %s\n", pCur->pgnoRoot, pX->nKey, pX->nData, pPage->pgno, loc==0 ? "overwrite" : "new entry")); - assert( pPage->isInit ); - newCell = pBt->pTmpSpace; + assert( pPage->isInit || CORRUPT_DB ); + newCell = p->pBt->pTmpSpace; assert( newCell!=0 ); - rc = fillInCell(pPage, newCell, pX, &szNew); - if( rc ) goto end_insert; + assert( BTREE_PREFORMAT==OPFLAG_PREFORMAT ); + if( flags & BTREE_PREFORMAT ){ + rc = SQLITE_OK; + szNew = p->pBt->nPreformatSize; + if( szNew<4 ){ + szNew = 4; + newCell[3] = 0; + } + if( ISAUTOVACUUM(p->pBt) && szNew>pPage->maxLocal ){ + CellInfo info; + pPage->xParseCell(pPage, newCell, &info); + if( info.nPayload!=info.nLocal ){ + Pgno ovfl = get4byte(&newCell[szNew-4]); + ptrmapPut(p->pBt, ovfl, PTRMAP_OVERFLOW1, pPage->pgno, &rc); + if( NEVER(rc) ) goto end_insert; + } + } + }else{ + rc = fillInCell(pPage, newCell, pX, &szNew); + if( rc ) goto end_insert; + } assert( szNew==pPage->xCellSize(pPage, newCell) ); - assert( szNew <= MX_CELL_SIZE(pBt) ); + assert( szNew <= MX_CELL_SIZE(p->pBt) ); idx = pCur->ix; + pCur->info.nSize = 0; if( loc==0 ){ CellInfo info; - assert( idxnCell ); + assert( idx>=0 ); + if( idx>=pPage->nCell ){ + return SQLITE_CORRUPT_PAGE(pPage); + } rc = sqlite3PagerWrite(pPage->pDbPage); if( rc ){ goto end_insert; @@ -71886,21 +82844,28 @@ SQLITE_PRIVATE int sqlite3BtreeInsert( if( !pPage->leaf ){ memcpy(newCell, oldCell, 4); } - rc = clearCell(pPage, oldCell, &info); - if( info.nSize==szNew && info.nLocal==info.nPayload - && (!ISAUTOVACUUM || szNewminLocal) + BTREE_CLEAR_CELL(rc, pPage, oldCell, info); + testcase( pCur->curFlags & BTCF_ValidOvfl ); + invalidateOverflowCache(pCur); + if( info.nSize==szNew && info.nLocal==info.nPayload + && (!ISAUTOVACUUM(p->pBt) || szNewminLocal) ){ /* Overwrite the old cell with the new if they are the same size. ** We could also try to do this if the old cell is smaller, then add ** the leftover space to the free list. But experiments show that ** doing that is no faster then skipping this optimization and just - ** calling dropCell() and insertCell(). + ** calling dropCell() and insertCell(). ** ** This optimization cannot be used on an autovacuum database if the ** new entry uses overflow pages, as the insertCell() call below is ** necessary to add the PTRMAP_OVERFLOW1 pointer-map entry. */ assert( rc==SQLITE_OK ); /* clearCell never fails when nLocal==nPayload */ - if( oldCell+szNew > pPage->aDataEnd ) return SQLITE_CORRUPT_BKPT; + if( oldCell < pPage->aData+pPage->hdrOffset+10 ){ + return SQLITE_CORRUPT_PAGE(pPage); + } + if( oldCell+szNew > pPage->aDataEnd ){ + return SQLITE_CORRUPT_PAGE(pPage); + } memcpy(oldCell, newCell, szNew); return SQLITE_OK; } @@ -71909,15 +82874,15 @@ SQLITE_PRIVATE int sqlite3BtreeInsert( }else if( loc<0 && pPage->nCell>0 ){ assert( pPage->leaf ); idx = ++pCur->ix; - pCur->curFlags &= ~BTCF_ValidNKey; + pCur->curFlags &= ~(BTCF_ValidNKey|BTCF_ValidOvfl); }else{ assert( pPage->leaf ); } - insertCell(pPage, idx, newCell, szNew, 0, 0, &rc); + rc = insertCellFast(pPage, idx, newCell, szNew); assert( pPage->nOverflow==0 || rc==SQLITE_OK ); assert( rc!=SQLITE_OK || pPage->nCell>0 || pPage->nOverflow>0 ); - /* If no error has occurred and pPage has an overflow cell, call balance() + /* If no error has occurred and pPage has an overflow cell, call balance() ** to redistribute the cells within the tree. Since balance() may move ** the cursor, zero the BtCursor.info.nSize and BTCF_ValidNKey ** variables. @@ -71937,14 +82902,13 @@ SQLITE_PRIVATE int sqlite3BtreeInsert( ** larger than the largest existing key, it is possible to insert the ** row without seeking the cursor. This can be a big performance boost. */ - pCur->info.nSize = 0; if( pPage->nOverflow ){ assert( rc==SQLITE_OK ); - pCur->curFlags &= ~(BTCF_ValidNKey); + pCur->curFlags &= ~(BTCF_ValidNKey|BTCF_ValidOvfl); rc = balance(pCur); /* Must make sure nOverflow is reset to zero even if the balance() - ** fails. Internal data structure corruption will result otherwise. + ** fails. Internal data structure corruption will result otherwise. ** Also, set the cursor state to invalid. This stops saveCursorPosition() ** from trying to save the current position of the cursor. */ pCur->pPage->nOverflow = 0; @@ -71971,7 +82935,119 @@ SQLITE_PRIVATE int sqlite3BtreeInsert( } /* -** Delete the entry that the cursor is pointing to. +** This function is used as part of copying the current row from cursor +** pSrc into cursor pDest. If the cursors are open on intkey tables, then +** parameter iKey is used as the rowid value when the record is copied +** into pDest. Otherwise, the record is copied verbatim. +** +** This function does not actually write the new value to cursor pDest. +** Instead, it creates and populates any required overflow pages and +** writes the data for the new cell into the BtShared.pTmpSpace buffer +** for the destination database. The size of the cell, in bytes, is left +** in BtShared.nPreformatSize. The caller completes the insertion by +** calling sqlite3BtreeInsert() with the BTREE_PREFORMAT flag specified. +** +** SQLITE_OK is returned if successful, or an SQLite error code otherwise. +*/ +SQLITE_PRIVATE int sqlite3BtreeTransferRow(BtCursor *pDest, BtCursor *pSrc, i64 iKey){ + BtShared *pBt = pDest->pBt; + u8 *aOut = pBt->pTmpSpace; /* Pointer to next output buffer */ + const u8 *aIn; /* Pointer to next input buffer */ + u32 nIn; /* Size of input buffer aIn[] */ + u32 nRem; /* Bytes of data still to copy */ + + getCellInfo(pSrc); + if( pSrc->info.nPayload<0x80 ){ + *(aOut++) = (u8)pSrc->info.nPayload; + }else{ + aOut += sqlite3PutVarint(aOut, pSrc->info.nPayload); + } + if( pDest->pKeyInfo==0 ) aOut += putVarint(aOut, iKey); + nIn = pSrc->info.nLocal; + aIn = pSrc->info.pPayload; + if( aIn+nIn>pSrc->pPage->aDataEnd ){ + return SQLITE_CORRUPT_PAGE(pSrc->pPage); + } + nRem = pSrc->info.nPayload; + if( nIn==nRem && nInpPage->maxLocal ){ + memcpy(aOut, aIn, nIn); + pBt->nPreformatSize = nIn + (int)(aOut - pBt->pTmpSpace); + return SQLITE_OK; + }else{ + int rc = SQLITE_OK; + Pager *pSrcPager = pSrc->pBt->pPager; + u8 *pPgnoOut = 0; + Pgno ovflIn = 0; + DbPage *pPageIn = 0; + MemPage *pPageOut = 0; + u32 nOut; /* Size of output buffer aOut[] */ + + nOut = btreePayloadToLocal(pDest->pPage, pSrc->info.nPayload); + pBt->nPreformatSize = (int)nOut + (int)(aOut - pBt->pTmpSpace); + if( nOutinfo.nPayload ){ + pPgnoOut = &aOut[nOut]; + pBt->nPreformatSize += 4; + } + + if( nRem>nIn ){ + if( aIn+nIn+4>pSrc->pPage->aDataEnd ){ + return SQLITE_CORRUPT_PAGE(pSrc->pPage); + } + ovflIn = get4byte(&pSrc->info.pPayload[nIn]); + } + + do { + nRem -= nOut; + do{ + assert( nOut>0 ); + if( nIn>0 ){ + int nCopy = MIN(nOut, nIn); + memcpy(aOut, aIn, nCopy); + nOut -= nCopy; + nIn -= nCopy; + aOut += nCopy; + aIn += nCopy; + } + if( nOut>0 ){ + sqlite3PagerUnref(pPageIn); + pPageIn = 0; + rc = sqlite3PagerGet(pSrcPager, ovflIn, &pPageIn, PAGER_GET_READONLY); + if( rc==SQLITE_OK ){ + aIn = (const u8*)sqlite3PagerGetData(pPageIn); + ovflIn = get4byte(aIn); + aIn += 4; + nIn = pSrc->pBt->usableSize - 4; + } + } + }while( rc==SQLITE_OK && nOut>0 ); + + if( rc==SQLITE_OK && nRem>0 && ALWAYS(pPgnoOut) ){ + Pgno pgnoNew = 0; /* Prevent harmless static-analyzer warning */ + MemPage *pNew = 0; + rc = allocateBtreePage(pBt, &pNew, &pgnoNew, 0, 0); + put4byte(pPgnoOut, pgnoNew); + if( ISAUTOVACUUM(pBt) && pPageOut ){ + ptrmapPut(pBt, pgnoNew, PTRMAP_OVERFLOW2, pPageOut->pgno, &rc); + } + releasePage(pPageOut); + pPageOut = pNew; + if( pPageOut ){ + pPgnoOut = pPageOut->aData; + put4byte(pPgnoOut, 0); + aOut = &pPgnoOut[4]; + nOut = MIN(pBt->usableSize - 4, nRem); + } + } + }while( nRem>0 && rc==SQLITE_OK ); + + releasePage(pPageOut); + sqlite3PagerUnref(pPageIn); + return rc; + } +} + +/* +** Delete the entry that the cursor is pointing to. ** ** If the BTREE_SAVEPOSITION bit of the flags parameter is zero, then ** the cursor is left pointing at an arbitrary location after the delete. @@ -71989,15 +83065,14 @@ SQLITE_PRIVATE int sqlite3BtreeInsert( */ SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){ Btree *p = pCur->pBtree; - BtShared *pBt = p->pBt; - int rc; /* Return code */ - MemPage *pPage; /* Page to delete cell from */ - unsigned char *pCell; /* Pointer to cell to delete */ - int iCellIdx; /* Index of cell to delete */ - int iCellDepth; /* Depth of node containing pCell */ - CellInfo info; /* Size of the cell being deleted */ - int bSkipnext = 0; /* Leaf cursor in SKIPNEXT state */ - u8 bPreserve = flags & BTREE_SAVEPOSITION; /* Keep cursor valid */ + BtShared *pBt = p->pBt; + int rc; /* Return code */ + MemPage *pPage; /* Page to delete cell from */ + unsigned char *pCell; /* Pointer to cell to delete */ + int iCellIdx; /* Index of cell to delete */ + int iCellDepth; /* Depth of node containing pCell */ + CellInfo info; /* Size of the cell being deleted */ + u8 bPreserve; /* Keep cursor valid. 2 for CURSOR_SKIPNEXT */ assert( cursorOwnsBtShared(pCur) ); assert( pBt->inTransaction==TRANS_WRITE ); @@ -72006,30 +83081,52 @@ SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){ assert( hasSharedCacheTableLock(p, pCur->pgnoRoot, pCur->pKeyInfo!=0, 2) ); assert( !hasReadConflicts(p, pCur->pgnoRoot) ); assert( (flags & ~(BTREE_SAVEPOSITION | BTREE_AUXDELETE))==0 ); - if( pCur->eState==CURSOR_REQUIRESEEK ){ - rc = btreeRestoreCursorPosition(pCur); - if( rc ) return rc; + if( pCur->eState!=CURSOR_VALID ){ + if( pCur->eState>=CURSOR_REQUIRESEEK ){ + rc = btreeRestoreCursorPosition(pCur); + assert( rc!=SQLITE_OK || CORRUPT_DB || pCur->eState==CURSOR_VALID ); + if( rc || pCur->eState!=CURSOR_VALID ) return rc; + }else{ + return SQLITE_CORRUPT_PGNO(pCur->pgnoRoot); + } } assert( pCur->eState==CURSOR_VALID ); iCellDepth = pCur->iPage; iCellIdx = pCur->ix; pPage = pCur->pPage; + if( pPage->nCell<=iCellIdx ){ + return SQLITE_CORRUPT_PAGE(pPage); + } pCell = findCell(pPage, iCellIdx); - if( pPage->nFree<0 && btreeComputeFreeSpace(pPage) ) return SQLITE_CORRUPT; + if( pPage->nFree<0 && btreeComputeFreeSpace(pPage) ){ + return SQLITE_CORRUPT_PAGE(pPage); + } + if( pCell<&pPage->aCellIdx[pPage->nCell] ){ + return SQLITE_CORRUPT_PAGE(pPage); + } - /* If the bPreserve flag is set to true, then the cursor position must + /* If the BTREE_SAVEPOSITION bit is on, then the cursor position must ** be preserved following this delete operation. If the current delete ** will cause a b-tree rebalance, then this is done by saving the cursor - ** key and leaving the cursor in CURSOR_REQUIRESEEK state before - ** returning. + ** key and leaving the cursor in CURSOR_REQUIRESEEK state before + ** returning. ** - ** Or, if the current delete will not cause a rebalance, then the cursor + ** If the current delete will not cause a rebalance, then the cursor ** will be left in CURSOR_SKIPNEXT state pointing to the entry immediately - ** before or after the deleted entry. In this case set bSkipnext to true. */ + ** before or after the deleted entry. + ** + ** The bPreserve value records which path is required: + ** + ** bPreserve==0 Not necessary to save the cursor position + ** bPreserve==1 Use CURSOR_REQUIRESEEK to save the cursor position + ** bPreserve==2 Cursor won't move. Set CURSOR_SKIPNEXT. + */ + bPreserve = (flags & BTREE_SAVEPOSITION)!=0; if( bPreserve ){ - if( !pPage->leaf - || (pPage->nFree+cellSizePtr(pPage,pCell)+2)>(int)(pBt->usableSize*2/3) + if( !pPage->leaf + || (pPage->nFree+pPage->xCellSize(pPage,pCell)+2) > + (int)(pBt->usableSize*2/3) || pPage->nCell==1 /* See dbfuzz001.test for a test case */ ){ /* A b-tree rebalance will be required after deleting this entry. @@ -72037,7 +83134,7 @@ SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){ rc = saveCursorKey(pCur); if( rc ) return rc; }else{ - bSkipnext = 1; + bPreserve = 2; } } @@ -72063,7 +83160,7 @@ SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){ /* If this is a delete operation to remove a row from a table b-tree, ** invalidate any incrblob cursors open on the row being deleted. */ - if( pCur->pKeyInfo==0 ){ + if( pCur->pKeyInfo==0 && p->hasIncrblobCur ){ invalidateIncrblobCursors(p, pCur->pgnoRoot, pCur->info.nKey, 0); } @@ -72072,7 +83169,7 @@ SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){ ** itself from within the page. */ rc = sqlite3PagerWrite(pPage->pDbPage); if( rc ) return rc; - rc = clearCell(pPage, pCell, &info); + BTREE_CLEAR_CELL(rc, pPage, pCell, info); dropCell(pPage, iCellIdx, info.nSize, &rc); if( rc ) return rc; @@ -72097,14 +83194,14 @@ SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){ n = pCur->pPage->pgno; } pCell = findCell(pLeaf, pLeaf->nCell-1); - if( pCell<&pLeaf->aData[4] ) return SQLITE_CORRUPT_BKPT; + if( pCell<&pLeaf->aData[4] ) return SQLITE_CORRUPT_PAGE(pLeaf); nCell = pLeaf->xCellSize(pLeaf, pCell); assert( MX_CELL_SIZE(pBt) >= nCell ); pTmp = pBt->pTmpSpace; assert( pTmp!=0 ); rc = sqlite3PagerWrite(pLeaf->pDbPage); if( rc==SQLITE_OK ){ - insertCell(pPage, iCellIdx, pCell-4, nCell+4, pTmp, n, &rc); + rc = insertCell(pPage, iCellIdx, pCell-4, nCell+4, pTmp, n); } dropCell(pLeaf, pLeaf->nCell-1, nCell, &rc); if( rc ) return rc; @@ -72123,9 +83220,17 @@ SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){ ** on the leaf node first. If the balance proceeds far enough up the ** tree that we can be sure that any problem in the internal node has ** been corrected, so be it. Otherwise, after balancing the leaf node, - ** walk the cursor up the tree to the internal node and balance it as + ** walk the cursor up the tree to the internal node and balance it as ** well. */ - rc = balance(pCur); + assert( pCur->pPage->nOverflow==0 ); + assert( pCur->pPage->nFree>=0 ); + if( pCur->pPage->nFree*3<=(int)pCur->pBt->usableSize*2 ){ + /* Optimization: If the free space is less than 2/3rds of the page, + ** then balance() will always be a no-op. No need to invoke it. */ + rc = SQLITE_OK; + }else{ + rc = balance(pCur); + } if( rc==SQLITE_OK && pCur->iPage>iCellDepth ){ releasePageNotNull(pCur->pPage); pCur->iPage--; @@ -72137,8 +83242,8 @@ SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){ } if( rc==SQLITE_OK ){ - if( bSkipnext ){ - assert( bPreserve && (pCur->iPage==iCellDepth || CORRUPT_DB) ); + if( bPreserve>1 ){ + assert( (pCur->iPage==iCellDepth || CORRUPT_DB) ); assert( pPage==pCur->pPage || CORRUPT_DB ); assert( (pPage->nCell>0 || CORRUPT_DB) && iCellIdx<=pPage->nCell ); pCur->eState = CURSOR_SKIPNEXT; @@ -72171,12 +83276,12 @@ SQLITE_PRIVATE int sqlite3BtreeDelete(BtCursor *pCur, u8 flags){ ** BTREE_INTKEY|BTREE_LEAFDATA Used for SQL tables with rowid keys ** BTREE_ZERODATA Used for SQL indices */ -static int btreeCreateTable(Btree *p, int *piTable, int createTabFlags){ +static int btreeCreateTable(Btree *p, Pgno *piTable, int createTabFlags){ BtShared *pBt = p->pBt; MemPage *pRoot; Pgno pgnoRoot; int rc; - int ptfFlags; /* Page-type flage for the root page of new table */ + int ptfFlags; /* Page-type flags for the root page of new table */ assert( sqlite3BtreeHoldsMutex(p) ); assert( pBt->inTransaction==TRANS_WRITE ); @@ -72204,6 +83309,9 @@ static int btreeCreateTable(Btree *p, int *piTable, int createTabFlags){ ** created so far, so the new root-page is (meta[3]+1). */ sqlite3BtreeGetMeta(p, BTREE_LARGEST_ROOT_PAGE, &pgnoRoot); + if( pgnoRoot>btreePagecount(pBt) ){ + return SQLITE_CORRUPT_PGNO(pgnoRoot); + } pgnoRoot++; /* The new root-page may not be allocated on a pointer-map page, or the @@ -72213,8 +83321,7 @@ static int btreeCreateTable(Btree *p, int *piTable, int createTabFlags){ pgnoRoot==PENDING_BYTE_PAGE(pBt) ){ pgnoRoot++; } - assert( pgnoRoot>=3 || CORRUPT_DB ); - testcase( pgnoRoot<3 ); + assert( pgnoRoot>=3 ); /* Allocate a page. The page that currently resides at pgnoRoot will ** be moved to the allocated page (unless the allocated page happens @@ -72251,7 +83358,7 @@ static int btreeCreateTable(Btree *p, int *piTable, int createTabFlags){ } rc = ptrmapGet(pBt, pgnoRoot, &eType, &iPtrPage); if( eType==PTRMAP_ROOTPAGE || eType==PTRMAP_FREEPAGE ){ - rc = SQLITE_CORRUPT_BKPT; + rc = SQLITE_CORRUPT_PGNO(pgnoRoot); } if( rc!=SQLITE_OK ){ releasePage(pRoot); @@ -72277,7 +83384,7 @@ static int btreeCreateTable(Btree *p, int *piTable, int createTabFlags){ } }else{ pRoot = pPageMove; - } + } /* Update the pointer-map and meta-data with the new root-page number. */ ptrmapPut(pBt, pgnoRoot, PTRMAP_ROOTPAGE, 0, &rc); @@ -72311,10 +83418,10 @@ static int btreeCreateTable(Btree *p, int *piTable, int createTabFlags){ zeroPage(pRoot, ptfFlags); sqlite3PagerUnref(pRoot->pDbPage); assert( (pBt->openFlags & BTREE_SINGLE)==0 || pgnoRoot==2 ); - *piTable = (int)pgnoRoot; + *piTable = pgnoRoot; return SQLITE_OK; } -SQLITE_PRIVATE int sqlite3BtreeCreateTable(Btree *p, int *piTable, int flags){ +SQLITE_PRIVATE int sqlite3BtreeCreateTable(Btree *p, Pgno *piTable, int flags){ int rc; sqlite3BtreeEnter(p); rc = btreeCreateTable(p, piTable, flags); @@ -72330,7 +83437,7 @@ static int clearDatabasePage( BtShared *pBt, /* The BTree that contains the table */ Pgno pgno, /* Page number to clear */ int freePageFlag, /* Deallocate page if true */ - int *pnChange /* Add number of Cells freed to this counter */ + i64 *pnChange /* Add number of Cells freed to this counter */ ){ MemPage *pPage; int rc; @@ -72341,15 +83448,16 @@ static int clearDatabasePage( assert( sqlite3_mutex_held(pBt->mutex) ); if( pgno>btreePagecount(pBt) ){ - return SQLITE_CORRUPT_BKPT; + return SQLITE_CORRUPT_PGNO(pgno); } - rc = getAndInitPage(pBt, pgno, &pPage, 0, 0); + rc = getAndInitPage(pBt, pgno, &pPage, 0); if( rc ) return rc; - if( pPage->bBusy ){ - rc = SQLITE_CORRUPT_BKPT; + if( (pBt->openFlags & BTREE_SINGLE)==0 + && sqlite3PagerPageRefcount(pPage->pDbPage) != (1 + (pgno==1)) + ){ + rc = SQLITE_CORRUPT_PAGE(pPage); goto cleardatabasepage_out; } - pPage->bBusy = 1; hdr = pPage->hdrOffset; for(i=0; inCell; i++){ pCell = findCell(pPage, i); @@ -72357,14 +83465,15 @@ static int clearDatabasePage( rc = clearDatabasePage(pBt, get4byte(pCell), 1, pnChange); if( rc ) goto cleardatabasepage_out; } - rc = clearCell(pPage, pCell, &info); + BTREE_CLEAR_CELL(rc, pPage, pCell, info); if( rc ) goto cleardatabasepage_out; } if( !pPage->leaf ){ rc = clearDatabasePage(pBt, get4byte(&pPage->aData[hdr+8]), 1, pnChange); if( rc ) goto cleardatabasepage_out; - }else if( pnChange ){ - assert( pPage->intKey || CORRUPT_DB ); + if( pPage->intKey ) pnChange = 0; + } + if( pnChange ){ testcase( !pPage->intKey ); *pnChange += pPage->nCell; } @@ -72375,7 +83484,6 @@ static int clearDatabasePage( } cleardatabasepage_out: - pPage->bBusy = 0; releasePage(pPage); return rc; } @@ -72389,11 +83497,10 @@ static int clearDatabasePage( ** read cursors on the table. Open write cursors are moved to the ** root of the table. ** -** If pnChange is not NULL, then table iTable must be an intkey table. The -** integer value pointed to by pnChange is incremented by the number of -** entries in the table. +** If pnChange is not NULL, then the integer value pointed to by pnChange +** is incremented by the number of entries in the table. */ -SQLITE_PRIVATE int sqlite3BtreeClearTable(Btree *p, int iTable, int *pnChange){ +SQLITE_PRIVATE int sqlite3BtreeClearTable(Btree *p, int iTable, i64 *pnChange){ int rc; BtShared *pBt = p->pBt; sqlite3BtreeEnter(p); @@ -72405,7 +83512,9 @@ SQLITE_PRIVATE int sqlite3BtreeClearTable(Btree *p, int iTable, int *pnChange){ /* Invalidate all incrblob cursors open on table iTable (assuming iTable ** is the root of a table b-tree - if it is not, the following call is ** a no-op). */ - invalidateIncrblobCursors(p, (Pgno)iTable, 0, 1); + if( p->hasIncrblobCur ){ + invalidateIncrblobCursors(p, (Pgno)iTable, 0, 1); + } rc = clearDatabasePage(pBt, (Pgno)iTable, 0, pnChange); } sqlite3BtreeLeave(p); @@ -72430,12 +83539,12 @@ SQLITE_PRIVATE int sqlite3BtreeClearTableOfCursor(BtCursor *pCur){ ** cursors on the table. ** ** If AUTOVACUUM is enabled and the page at iTable is not the last -** root page in the database file, then the last root page +** root page in the database file, then the last root page ** in the database file is moved into the slot formerly occupied by ** iTable and that last slot formerly occupied by the last root page ** is added to the freelist instead of iTable. In this say, all ** root pages are kept at the beginning of the database file, which -** is necessary for AUTOVACUUM to work right. *piMoved is set to the +** is necessary for AUTOVACUUM to work right. *piMoved is set to the ** page number that used to be the last root page in the file before ** the move. If no page gets moved, *piMoved is set to 0. ** The last root page is recorded in meta[3] and the value of @@ -72450,13 +83559,13 @@ static int btreeDropTable(Btree *p, Pgno iTable, int *piMoved){ assert( p->inTrans==TRANS_WRITE ); assert( iTable>=2 ); if( iTable>btreePagecount(pBt) ){ - return SQLITE_CORRUPT_BKPT; + return SQLITE_CORRUPT_PGNO(iTable); } - rc = btreeGetPage(pBt, (Pgno)iTable, &pPage, 0); - if( rc ) return rc; rc = sqlite3BtreeClearTable(p, iTable, 0); - if( rc ){ + if( rc ) return rc; + rc = btreeGetPage(pBt, (Pgno)iTable, &pPage, 0); + if( NEVER(rc) ){ releasePage(pPage); return rc; } @@ -72473,7 +83582,7 @@ static int btreeDropTable(Btree *p, Pgno iTable, int *piMoved){ if( iTable==maxRootPgno ){ /* If the table being dropped is the table with the largest root-page - ** number in the database, put the root page on the free list. + ** number in the database, put the root page on the free list. */ freePage(pPage, &rc); releasePage(pPage); @@ -72482,7 +83591,7 @@ static int btreeDropTable(Btree *p, Pgno iTable, int *piMoved){ } }else{ /* The table being dropped does not have the largest root-page - ** number in the database. So move the page that does into the + ** number in the database. So move the page that does into the ** gap left by the deleted root-page. */ MemPage *pMove; @@ -72524,7 +83633,7 @@ static int btreeDropTable(Btree *p, Pgno iTable, int *piMoved){ releasePage(pPage); } #endif - return rc; + return rc; } SQLITE_PRIVATE int sqlite3BtreeDropTable(Btree *p, int iTable, int *piMoved){ int rc; @@ -72543,7 +83652,7 @@ SQLITE_PRIVATE int sqlite3BtreeDropTable(Btree *p, int iTable, int *piMoved){ ** is the number of free pages currently in the database. Meta[1] ** through meta[15] are available for use by higher layers. Meta[0] ** is read-only, the others are read/write. -** +** ** The schema layer numbers meta values differently. At the schema ** layer (and the SetCookie and ReadCookie opcodes) the number of ** free pages is not visible. So Cookie[0] is the same as Meta[1]. @@ -72560,12 +83669,12 @@ SQLITE_PRIVATE void sqlite3BtreeGetMeta(Btree *p, int idx, u32 *pMeta){ sqlite3BtreeEnter(p); assert( p->inTrans>TRANS_NONE ); - assert( SQLITE_OK==querySharedCacheTableLock(p, MASTER_ROOT, READ_LOCK) ); + assert( SQLITE_OK==querySharedCacheTableLock(p, SCHEMA_ROOT, READ_LOCK) ); assert( pBt->pPage1 ); assert( idx>=0 && idx<=15 ); if( idx==BTREE_DATA_VERSION ){ - *pMeta = sqlite3PagerDataVersion(pBt->pPager) + p->iDataVersion; + *pMeta = sqlite3PagerDataVersion(pBt->pPager) + p->iBDataVersion; }else{ *pMeta = get4byte(&pBt->pPage1->aData[36 + idx*4]); } @@ -72609,16 +83718,15 @@ SQLITE_PRIVATE int sqlite3BtreeUpdateMeta(Btree *p, int idx, u32 iMeta){ return rc; } -#ifndef SQLITE_OMIT_BTREECOUNT /* ** The first argument, pCur, is a cursor opened on some b-tree. Count the ** number of entries in the b-tree and write the result to *pnEntry. ** -** SQLITE_OK is returned if the operation is successfully executed. +** SQLITE_OK is returned if the operation is successfully executed. ** Otherwise, if an error is encountered (i.e. an IO error or database ** corruption) an SQLite error code is returned. */ -SQLITE_PRIVATE int sqlite3BtreeCount(BtCursor *pCur, i64 *pnEntry){ +SQLITE_PRIVATE int sqlite3BtreeCount(sqlite3 *db, BtCursor *pCur, i64 *pnEntry){ i64 nEntry = 0; /* Value to return in *pnEntry */ int rc; /* Return code */ @@ -72629,13 +83737,13 @@ SQLITE_PRIVATE int sqlite3BtreeCount(BtCursor *pCur, i64 *pnEntry){ } /* Unless an error occurs, the following loop runs one iteration for each - ** page in the B-Tree structure (not including overflow pages). + ** page in the B-Tree structure (not including overflow pages). */ - while( rc==SQLITE_OK ){ + while( rc==SQLITE_OK && !AtomicLoad(&db->u1.isInterrupted) ){ int iIdx; /* Index of child node in parent */ MemPage *pPage; /* Current page of the b-tree */ - /* If this is a leaf page or the tree is not an int-key tree, then + /* If this is a leaf page or the tree is not an int-key tree, then ** this page contains countable entries. Increment the entry counter ** accordingly. */ @@ -72644,7 +83752,7 @@ SQLITE_PRIVATE int sqlite3BtreeCount(BtCursor *pCur, i64 *pnEntry){ nEntry += pPage->nCell; } - /* pPage is a leaf node. This loop navigates the cursor so that it + /* pPage is a leaf node. This loop navigates the cursor so that it ** points to the first interior cell that it points to the parent of ** the next page in the tree that has not yet been visited. The ** pCur->aiIdx[pCur->iPage] value is set to the index of the parent cell @@ -72668,7 +83776,7 @@ SQLITE_PRIVATE int sqlite3BtreeCount(BtCursor *pCur, i64 *pnEntry){ pPage = pCur->pPage; } - /* Descend to the child node of the cell that the cursor currently + /* Descend to the child node of the cell that the cursor currently ** points at. This is the right-child if (iIdx==pPage->nCell). */ iIdx = pCur->ix; @@ -72682,7 +83790,6 @@ SQLITE_PRIVATE int sqlite3BtreeCount(BtCursor *pCur, i64 *pnEntry){ /* An error has occurred. Return an error code. */ return rc; } -#endif /* ** Return the pager associated with a BTree. This routine is used for @@ -72693,6 +83800,41 @@ SQLITE_PRIVATE Pager *sqlite3BtreePager(Btree *p){ } #ifndef SQLITE_OMIT_INTEGRITY_CHECK +/* +** Record an OOM error during integrity_check +*/ +static void checkOom(IntegrityCk *pCheck){ + pCheck->rc = SQLITE_NOMEM; + pCheck->mxErr = 0; /* Causes integrity_check processing to stop */ + if( pCheck->nErr==0 ) pCheck->nErr++; +} + +/* +** Invoke the progress handler, if appropriate. Also check for an +** interrupt. +*/ +static void checkProgress(IntegrityCk *pCheck){ + sqlite3 *db = pCheck->db; + if( AtomicLoad(&db->u1.isInterrupted) ){ + pCheck->rc = SQLITE_INTERRUPT; + pCheck->nErr++; + pCheck->mxErr = 0; + } +#ifndef SQLITE_OMIT_PROGRESS_CALLBACK + if( db->xProgress ){ + assert( db->nProgressOps>0 ); + pCheck->nStep++; + if( (pCheck->nStep % db->nProgressOps)==0 + && db->xProgress(db->pProgressArg) + ){ + pCheck->rc = SQLITE_INTERRUPT; + pCheck->nErr++; + pCheck->mxErr = 0; + } + } +#endif +} + /* ** Append a message to the error message string. */ @@ -72702,6 +83844,7 @@ static void checkAppendMsg( ... ){ va_list ap; + checkProgress(pCheck); if( !pCheck->mxErr ) return; pCheck->mxErr--; pCheck->nErr++; @@ -72710,12 +83853,13 @@ static void checkAppendMsg( sqlite3_str_append(&pCheck->errMsg, "\n", 1); } if( pCheck->zPfx ){ - sqlite3_str_appendf(&pCheck->errMsg, pCheck->zPfx, pCheck->v1, pCheck->v2); + sqlite3_str_appendf(&pCheck->errMsg, pCheck->zPfx, + pCheck->v0, pCheck->v1, pCheck->v2); } sqlite3_str_vappendf(&pCheck->errMsg, zFormat, ap); va_end(ap); if( pCheck->errMsg.accError==SQLITE_NOMEM ){ - pCheck->mallocFailed = 1; + checkOom(pCheck); } } #endif /* SQLITE_OMIT_INTEGRITY_CHECK */ @@ -72727,7 +83871,8 @@ static void checkAppendMsg( ** corresponds to page iPg is already set. */ static int getPageReferenced(IntegrityCk *pCheck, Pgno iPg){ - assert( iPg<=pCheck->nPage && sizeof(pCheck->aPgRef[0])==1 ); + assert( pCheck->aPgRef!=0 ); + assert( iPg<=pCheck->nCkPage && sizeof(pCheck->aPgRef[0])==1 ); return (pCheck->aPgRef[iPg/8] & (1 << (iPg & 0x07))); } @@ -72735,7 +83880,8 @@ static int getPageReferenced(IntegrityCk *pCheck, Pgno iPg){ ** Set the bit in the IntegrityCk.aPgRef[] array that corresponds to page iPg. */ static void setPageReferenced(IntegrityCk *pCheck, Pgno iPg){ - assert( iPg<=pCheck->nPage && sizeof(pCheck->aPgRef[0])==1 ); + assert( pCheck->aPgRef!=0 ); + assert( iPg<=pCheck->nCkPage && sizeof(pCheck->aPgRef[0])==1 ); pCheck->aPgRef[iPg/8] |= (1 << (iPg & 0x07)); } @@ -72749,12 +83895,12 @@ static void setPageReferenced(IntegrityCk *pCheck, Pgno iPg){ ** Also check that the page number is in bounds. */ static int checkRef(IntegrityCk *pCheck, Pgno iPage){ - if( iPage>pCheck->nPage || iPage==0 ){ - checkAppendMsg(pCheck, "invalid page number %d", iPage); + if( iPage>pCheck->nCkPage || iPage==0 ){ + checkAppendMsg(pCheck, "invalid page number %u", iPage); return 1; } if( getPageReferenced(pCheck, iPage) ){ - checkAppendMsg(pCheck, "2nd reference to page %d", iPage); + checkAppendMsg(pCheck, "2nd reference to page %u", iPage); return 1; } setPageReferenced(pCheck, iPage); @@ -72763,7 +83909,7 @@ static int checkRef(IntegrityCk *pCheck, Pgno iPage){ #ifndef SQLITE_OMIT_AUTOVACUUM /* -** Check that the entry in the pointer-map for page iChild maps to +** Check that the entry in the pointer-map for page iChild maps to ** page iParent, pointer type ptrType. If not, append an error message ** to pCheck. */ @@ -72779,14 +83925,14 @@ static void checkPtrmap( rc = ptrmapGet(pCheck->pBt, iChild, &ePtrmapType, &iPtrmapParent); if( rc!=SQLITE_OK ){ - if( rc==SQLITE_NOMEM || rc==SQLITE_IOERR_NOMEM ) pCheck->mallocFailed = 1; - checkAppendMsg(pCheck, "Failed to read ptrmap key=%d", iChild); + if( rc==SQLITE_NOMEM || rc==SQLITE_IOERR_NOMEM ) checkOom(pCheck); + checkAppendMsg(pCheck, "Failed to read ptrmap key=%u", iChild); return; } if( ePtrmapType!=eType || iPtrmapParent!=iParent ){ checkAppendMsg(pCheck, - "Bad ptr map entry key=%d expected=(%d,%d) got=(%d,%d)", + "Bad ptr map entry key=%u expected=(%u,%u) got=(%u,%u)", iChild, eType, iParent, ePtrmapType, iPtrmapParent); } } @@ -72799,7 +83945,7 @@ static void checkPtrmap( static void checkList( IntegrityCk *pCheck, /* Integrity checking context */ int isFreeList, /* True for a freelist. False for overflow page list */ - int iPage, /* Page number for first page in the list */ + Pgno iPage, /* Page number for first page in the list */ u32 N /* Expected number of pages in the list */ ){ int i; @@ -72811,7 +83957,7 @@ static void checkList( if( checkRef(pCheck, iPage) ) break; N--; if( sqlite3PagerGet(pCheck->pPager, (Pgno)iPage, &pOvflPage, 0) ){ - checkAppendMsg(pCheck, "failed to get page %d", iPage); + checkAppendMsg(pCheck, "failed to get page %u", iPage); break; } pOvflData = (unsigned char *)sqlite3PagerGetData(pOvflPage); @@ -72824,7 +83970,7 @@ static void checkList( #endif if( n>pCheck->pBt->usableSize/4-2 ){ checkAppendMsg(pCheck, - "freelist leaf count too big on page %d", iPage); + "freelist leaf count too big on page %u", iPage); N--; }else{ for(i=0; i<(int)n; i++){ @@ -72856,7 +84002,7 @@ static void checkList( } if( N && nErrAtStart==pCheck->nErr ){ checkAppendMsg(pCheck, - "%s is %d but should be %d", + "%s is %u but should be %u", isFreeList ? "size" : "overflow list length", expected-N, expected); } @@ -72881,12 +84027,14 @@ static void checkList( ** property. ** ** This heap is used for cell overlap and coverage testing. Each u32 -** entry represents the span of a cell or freeblock on a btree page. +** entry represents the span of a cell or freeblock on a btree page. ** The upper 16 bits are the index of the first byte of a range and the ** lower 16 bits are the index of the last byte of that range. */ static void btreeHeapInsert(u32 *aHeap, u32 x){ - u32 j, i = ++aHeap[0]; + u32 j, i; + assert( aHeap!=0 ); + i = ++aHeap[0]; aHeap[i] = x; while( (j = i/2)>0 && aHeap[j]>aHeap[i] ){ x = aHeap[j]; @@ -72911,7 +84059,7 @@ static int btreeHeapPull(u32 *aHeap, u32 *pOut){ aHeap[j] = x; i = j; } - return 1; + return 1; } #ifndef SQLITE_OMIT_INTEGRITY_CHECK @@ -72919,7 +84067,7 @@ static int btreeHeapPull(u32 *aHeap, u32 *pOut){ ** Do various sanity checks on a single page of a tree. Return ** the tree depth. Root pages return 0. Parents of root pages ** return 1, and so forth. -** +** ** These checks are done: ** ** 1. Make sure that cells and freeblocks do not overlap @@ -72931,7 +84079,7 @@ static int btreeHeapPull(u32 *aHeap, u32 *pOut){ */ static int checkTreePage( IntegrityCk *pCheck, /* Context for the sanity check */ - int iPage, /* Page number of the page to check */ + Pgno iPage, /* Page number of the page to check */ i64 *piMinKey, /* Write minimum integer primary key here */ i64 maxKey /* Error if integer primary key greater than this */ ){ @@ -72963,15 +84111,18 @@ static int checkTreePage( /* Check that the page exists */ + checkProgress(pCheck); + if( pCheck->mxErr==0 ) goto end_of_check; pBt = pCheck->pBt; usableSize = pBt->usableSize; if( iPage==0 ) return 0; if( checkRef(pCheck, iPage) ) return 0; - pCheck->zPfx = "Page %d: "; + pCheck->zPfx = "Tree %u page %u: "; pCheck->v1 = iPage; - if( (rc = btreeGetPage(pBt, (Pgno)iPage, &pPage, 0))!=0 ){ + if( (rc = btreeGetPage(pBt, iPage, &pPage, 0))!=0 ){ checkAppendMsg(pCheck, "unable to get the page. error code=%d", rc); + if( rc==SQLITE_IOERR_NOMEM ) pCheck->rc = SQLITE_NOMEM; goto end_of_check; } @@ -72994,7 +84145,7 @@ static int checkTreePage( hdr = pPage->hdrOffset; /* Set up for cell analysis */ - pCheck->zPfx = "On tree page %d cell %d: "; + pCheck->zPfx = "Tree %u page %u cell %u: "; contentOffset = get2byteNotZero(&data[hdr+5]); assert( contentOffset<=usableSize ); /* Enforced by btreeInitPage() */ @@ -73002,6 +84153,9 @@ static int checkTreePage( ** number of cells on the page. */ nCell = get2byte(&data[hdr+3]); assert( pPage->nCell==nCell ); + if( pPage->leaf || pPage->intKey==0 ){ + pCheck->nRow += nCell; + } /* EVIDENCE-OF: R-23882-45353 The cell pointer array of a b-tree page ** immediately follows the b-tree page header. */ @@ -73014,7 +84168,7 @@ static int checkTreePage( pgno = get4byte(&data[hdr+8]); #ifndef SQLITE_OMIT_AUTOVACUUM if( pBt->autoVacuum ){ - pCheck->zPfx = "On page %d at right child: "; + pCheck->zPfx = "Tree %u page %u right child: "; checkPtrmap(pCheck, pgno, PTRMAP_BTREE, iPage); } #endif @@ -73038,7 +84192,7 @@ static int checkTreePage( pc = get2byteAligned(pCellIdx); pCellIdx -= 2; if( pcusableSize-4 ){ - checkAppendMsg(pCheck, "Offset %d out of range %d..%d", + checkAppendMsg(pCheck, "Offset %u out of range %u..%u", pc, contentOffset, usableSize-4); doCoverageCheck = 0; continue; @@ -73091,6 +84245,7 @@ static int checkTreePage( } }else{ /* Populate the coverage-checking heap for leaf pages */ + assert( heap[0] < pCheck->mxHeap ); btreeHeapInsert(heap, (pc<<16)|(pc+info.nSize-1)); } } @@ -73110,14 +84265,16 @@ static int checkTreePage( u32 size; pc = get2byteAligned(&data[cellStart+i*2]); size = pPage->xCellSize(pPage, &data[pc]); + assert( heap[0] < pCheck->mxHeap ); btreeHeapInsert(heap, (pc<<16)|(pc+size-1)); } } + assert( heap!=0 ); /* Add the freeblocks to the min-heap ** ** EVIDENCE-OF: R-20690-50594 The second field of the b-tree page header ** is the offset of the first freeblock, or zero if there are no - ** freeblocks on the page. + ** freeblocks on the page. */ i = get2byte(&data[hdr+1]); while( i>0 ){ @@ -73125,6 +84282,7 @@ static int checkTreePage( assert( (u32)i<=usableSize-4 ); /* Enforced by btreeComputeFreeSpace() */ size = get2byte(&data[i+2]); assert( (u32)(i+size)<=usableSize ); /* due to btreeComputeFreeSpace() */ + assert( heap[0] < pCheck->mxHeap ); btreeHeapInsert(heap, (((u32)i)<<16)|(i+size-1)); /* EVIDENCE-OF: R-58208-19414 The first 2 bytes of a freeblock are a ** big-endian integer which is the offset in the b-tree page of the next @@ -73137,13 +84295,13 @@ static int checkTreePage( assert( (u32)j<=usableSize-4 ); /* Enforced by btreeComputeFreeSpace() */ i = j; } - /* Analyze the min-heap looking for overlap between cells and/or + /* Analyze the min-heap looking for overlap between cells and/or ** freeblocks, and counting the number of untracked bytes in nFrag. - ** + ** ** Each min-heap entry is of the form: (start_address<<16)|end_address. ** There is an implied first entry the covers the page header, the cell ** pointer index, and the gap between the cell pointer index and the start - ** of cell content. + ** of cell content. ** ** The loop below pulls entries from the min-heap in order and compares ** the start_address against the previous end_address. If there is an @@ -73155,7 +84313,7 @@ static int checkTreePage( while( btreeHeapPull(heap,&x) ){ if( (prev&0xffff)>=(x>>16) ){ checkAppendMsg(pCheck, - "Multiple uses for byte %u of page %d", x>>16, iPage); + "Multiple uses for byte %u of page %u", x>>16, iPage); break; }else{ nFrag += (x>>16) - (prev&0xffff) - 1; @@ -73170,7 +84328,7 @@ static int checkTreePage( */ if( heap[0]==0 && nFrag!=data[hdr+7] ){ checkAppendMsg(pCheck, - "Fragmentation of %d bytes reported as %d on page %d", + "Fragmentation of %u bytes reported as %u on page %u", nFrag, data[hdr+7], iPage); } } @@ -73198,117 +84356,149 @@ static int checkTreePage( ** allocation errors, an error message held in memory obtained from ** malloc is returned if *pnErr is non-zero. If *pnErr==0 then NULL is ** returned. If a memory allocation error occurs, NULL is returned. -*/ -SQLITE_PRIVATE char *sqlite3BtreeIntegrityCheck( +** +** If the first entry in aRoot[] is 0, that indicates that the list of +** root pages is incomplete. This is a "partial integrity-check". This +** happens when performing an integrity check on a single table. The +** zero is skipped, of course. But in addition, the freelist checks +** and the checks to make sure every page is referenced are also skipped, +** since obviously it is not possible to know which pages are covered by +** the unverified btrees. Except, if aRoot[1] is 1, then the freelist +** checks are still performed. +*/ +SQLITE_PRIVATE int sqlite3BtreeIntegrityCheck( + sqlite3 *db, /* Database connection that is running the check */ Btree *p, /* The btree to be checked */ - int *aRoot, /* An array of root pages numbers for individual trees */ + Pgno *aRoot, /* An array of root pages numbers for individual trees */ + Mem *aCnt, /* Memory cells to write counts for each tree to */ int nRoot, /* Number of entries in aRoot[] */ int mxErr, /* Stop reporting errors after this many */ - int *pnErr /* Write number of errors seen to this variable */ + int *pnErr, /* OUT: Write number of errors seen to this variable */ + char **pzOut /* OUT: Write the error message string here */ ){ Pgno i; IntegrityCk sCheck; BtShared *pBt = p->pBt; u64 savedDbFlags = pBt->db->flags; char zErr[100]; + int bPartial = 0; /* True if not checking all btrees */ + int bCkFreelist = 1; /* True to scan the freelist */ VVA_ONLY( int nRef ); + assert( nRoot>0 ); + assert( aCnt!=0 ); + + /* aRoot[0]==0 means this is a partial check */ + if( aRoot[0]==0 ){ + assert( nRoot>1 ); + bPartial = 1; + if( aRoot[1]!=1 ) bCkFreelist = 0; + } + sqlite3BtreeEnter(p); assert( p->inTrans>TRANS_NONE && pBt->inTransaction>TRANS_NONE ); VVA_ONLY( nRef = sqlite3PagerRefcount(pBt->pPager) ); assert( nRef>=0 ); + memset(&sCheck, 0, sizeof(sCheck)); + sCheck.db = db; sCheck.pBt = pBt; sCheck.pPager = pBt->pPager; - sCheck.nPage = btreePagecount(sCheck.pBt); + sCheck.nCkPage = btreePagecount(sCheck.pBt); sCheck.mxErr = mxErr; - sCheck.nErr = 0; - sCheck.mallocFailed = 0; - sCheck.zPfx = 0; - sCheck.v1 = 0; - sCheck.v2 = 0; - sCheck.aPgRef = 0; - sCheck.heap = 0; sqlite3StrAccumInit(&sCheck.errMsg, 0, zErr, sizeof(zErr), SQLITE_MAX_LENGTH); sCheck.errMsg.printfFlags = SQLITE_PRINTF_INTERNAL; - if( sCheck.nPage==0 ){ + if( sCheck.nCkPage==0 ){ goto integrity_ck_cleanup; } - sCheck.aPgRef = sqlite3MallocZero((sCheck.nPage / 8)+ 1); + sCheck.aPgRef = sqlite3MallocZero((sCheck.nCkPage / 8)+ 1); if( !sCheck.aPgRef ){ - sCheck.mallocFailed = 1; + checkOom(&sCheck); goto integrity_ck_cleanup; } sCheck.heap = (u32*)sqlite3PageMalloc( pBt->pageSize ); +#ifdef SQLITE_DEBUG + sCheck.mxHeap = pBt->pageSize/4 - 1; +#endif if( sCheck.heap==0 ){ - sCheck.mallocFailed = 1; + checkOom(&sCheck); goto integrity_ck_cleanup; } i = PENDING_BYTE_PAGE(pBt); - if( i<=sCheck.nPage ) setPageReferenced(&sCheck, i); + if( i<=sCheck.nCkPage ) setPageReferenced(&sCheck, i); /* Check the integrity of the freelist */ - sCheck.zPfx = "Main freelist: "; - checkList(&sCheck, 1, get4byte(&pBt->pPage1->aData[32]), - get4byte(&pBt->pPage1->aData[36])); - sCheck.zPfx = 0; + if( bCkFreelist ){ + sCheck.zPfx = "Freelist: "; + checkList(&sCheck, 1, get4byte(&pBt->pPage1->aData[32]), + get4byte(&pBt->pPage1->aData[36])); + sCheck.zPfx = 0; + } /* Check all the tables. */ #ifndef SQLITE_OMIT_AUTOVACUUM - if( pBt->autoVacuum ){ - int mx = 0; - int mxInHdr; - for(i=0; (int)ipPage1->aData[52]); - if( mx!=mxInHdr ){ + if( !bPartial ){ + if( pBt->autoVacuum ){ + Pgno mx = 0; + Pgno mxInHdr; + for(i=0; (int)ipPage1->aData[52]); + if( mx!=mxInHdr ){ + checkAppendMsg(&sCheck, + "max rootpage (%u) disagrees with header (%u)", + mx, mxInHdr + ); + } + }else if( get4byte(&pBt->pPage1->aData[64])!=0 ){ checkAppendMsg(&sCheck, - "max rootpage (%d) disagrees with header (%d)", - mx, mxInHdr + "incremental_vacuum enabled with a max rootpage of zero" ); } - }else if( get4byte(&pBt->pPage1->aData[64])!=0 ){ - checkAppendMsg(&sCheck, - "incremental_vacuum enabled with a max rootpage of zero" - ); } #endif testcase( pBt->db->flags & SQLITE_CellSizeCk ); pBt->db->flags &= ~(u64)SQLITE_CellSizeCk; for(i=0; (int)iautoVacuum && aRoot[i]>1 ){ - checkPtrmap(&sCheck, aRoot[i], PTRMAP_ROOTPAGE, 0); - } + if( pBt->autoVacuum && aRoot[i]>1 && !bPartial ){ + checkPtrmap(&sCheck, aRoot[i], PTRMAP_ROOTPAGE, 0); + } #endif - checkTreePage(&sCheck, aRoot[i], ¬Used, LARGEST_INT64); + sCheck.v0 = aRoot[i]; + checkTreePage(&sCheck, aRoot[i], ¬Used, LARGEST_INT64); + } + sqlite3MemSetArrayInt64(aCnt, i, sCheck.nRow); } pBt->db->flags = savedDbFlags; /* Make sure every page in the file is referenced */ - for(i=1; i<=sCheck.nPage && sCheck.mxErr; i++){ + if( !bPartial ){ + for(i=1; i<=sCheck.nCkPage && sCheck.mxErr; i++){ #ifdef SQLITE_OMIT_AUTOVACUUM - if( getPageReferenced(&sCheck, i)==0 ){ - checkAppendMsg(&sCheck, "Page %d is never used", i); - } + if( getPageReferenced(&sCheck, i)==0 ){ + checkAppendMsg(&sCheck, "Page %u: never used", i); + } #else - /* If the database supports auto-vacuum, make sure no tables contain - ** references to pointer-map pages. - */ - if( getPageReferenced(&sCheck, i)==0 && - (PTRMAP_PAGENO(pBt, i)!=i || !pBt->autoVacuum) ){ - checkAppendMsg(&sCheck, "Page %d is never used", i); - } - if( getPageReferenced(&sCheck, i)!=0 && - (PTRMAP_PAGENO(pBt, i)==i && pBt->autoVacuum) ){ - checkAppendMsg(&sCheck, "Pointer map page %d is referenced", i); - } + /* If the database supports auto-vacuum, make sure no tables contain + ** references to pointer-map pages. + */ + if( getPageReferenced(&sCheck, i)==0 && + (PTRMAP_PAGENO(pBt, i)!=i || !pBt->autoVacuum) ){ + checkAppendMsg(&sCheck, "Page %u: never used", i); + } + if( getPageReferenced(&sCheck, i)!=0 && + (PTRMAP_PAGENO(pBt, i)==i && pBt->autoVacuum) ){ + checkAppendMsg(&sCheck, "Page %u: pointer map referenced", i); + } #endif + } } /* Clean up and report errors. @@ -73316,16 +84506,17 @@ SQLITE_PRIVATE char *sqlite3BtreeIntegrityCheck( integrity_ck_cleanup: sqlite3PageFree(sCheck.heap); sqlite3_free(sCheck.aPgRef); - if( sCheck.mallocFailed ){ + *pnErr = sCheck.nErr; + if( sCheck.nErr==0 ){ sqlite3_str_reset(&sCheck.errMsg); - sCheck.nErr++; + *pzOut = 0; + }else{ + *pzOut = sqlite3StrAccumFinish(&sCheck.errMsg); } - *pnErr = sCheck.nErr; - if( sCheck.nErr==0 ) sqlite3_str_reset(&sCheck.errMsg); /* Make sure this analysis did not leave any unref() pages. */ assert( nRef==sqlite3PagerRefcount(pBt->pPager) ); sqlite3BtreeLeave(p); - return sqlite3StrAccumFinish(&sCheck.errMsg); + return sCheck.rc; } #endif /* SQLITE_OMIT_INTEGRITY_CHECK */ @@ -73355,18 +84546,19 @@ SQLITE_PRIVATE const char *sqlite3BtreeGetJournalname(Btree *p){ } /* -** Return non-zero if a transaction is active. +** Return one of SQLITE_TXN_NONE, SQLITE_TXN_READ, or SQLITE_TXN_WRITE +** to describe the current transaction state of Btree p. */ -SQLITE_PRIVATE int sqlite3BtreeIsInTrans(Btree *p){ +SQLITE_PRIVATE int sqlite3BtreeTxnState(Btree *p){ assert( p==0 || sqlite3_mutex_held(p->db->mutex) ); - return (p && (p->inTrans==TRANS_WRITE)); + return p ? p->inTrans : 0; } #ifndef SQLITE_OMIT_WAL /* ** Run a checkpoint on the Btree passed as the first argument. ** -** Return SQLITE_LOCKED if this or any other connection has an open +** Return SQLITE_LOCKED if this or any other connection has an open ** transaction on the shared-cache the argument Btree is connected to. ** ** Parameter eMode is one of SQLITE_CHECKPOINT_PASSIVE, FULL or RESTART. @@ -73388,14 +84580,8 @@ SQLITE_PRIVATE int sqlite3BtreeCheckpoint(Btree *p, int eMode, int *pnLog, int * #endif /* -** Return non-zero if a read (or write) transaction is active. +** Return true if there is currently a backup running on Btree p. */ -SQLITE_PRIVATE int sqlite3BtreeIsInReadTrans(Btree *p){ - assert( p ); - assert( sqlite3_mutex_held(p->db->mutex) ); - return p->inTrans!=TRANS_NONE; -} - SQLITE_PRIVATE int sqlite3BtreeIsInBackup(Btree *p){ assert( p ); assert( sqlite3_mutex_held(p->db->mutex) ); @@ -73405,25 +84591,26 @@ SQLITE_PRIVATE int sqlite3BtreeIsInBackup(Btree *p){ /* ** This function returns a pointer to a blob of memory associated with ** a single shared-btree. The memory is used by client code for its own -** purposes (for example, to store a high-level schema associated with +** purposes (for example, to store a high-level schema associated with ** the shared-btree). The btree layer manages reference counting issues. ** ** The first time this is called on a shared-btree, nBytes bytes of memory -** are allocated, zeroed, and returned to the caller. For each subsequent +** are allocated, zeroed, and returned to the caller. For each subsequent ** call the nBytes parameter is ignored and a pointer to the same blob -** of memory returned. +** of memory returned. ** ** If the nBytes parameter is 0 and the blob of memory has not yet been ** allocated, a null pointer is returned. If the blob has already been ** allocated, it is returned as normal. ** -** Just before the shared-btree is closed, the function passed as the -** xFree argument when the memory allocation was made is invoked on the +** Just before the shared-btree is closed, the function passed as the +** xFree argument when the memory allocation was made is invoked on the ** blob of allocated memory. The xFree function should not call sqlite3_free() ** on the memory, the btree layer does that. */ SQLITE_PRIVATE void *sqlite3BtreeSchema(Btree *p, int nBytes, void(*xFree)(void *)){ BtShared *pBt = p->pBt; + assert( nBytes==0 || nBytes==sizeof(Schema) ); sqlite3BtreeEnter(p); if( !pBt->pSchema && nBytes ){ pBt->pSchema = sqlite3DbMallocZero(0, nBytes); @@ -73434,15 +84621,16 @@ SQLITE_PRIVATE void *sqlite3BtreeSchema(Btree *p, int nBytes, void(*xFree)(void } /* -** Return SQLITE_LOCKED_SHAREDCACHE if another user of the same shared -** btree as the argument handle holds an exclusive lock on the -** sqlite_master table. Otherwise SQLITE_OK. +** Return SQLITE_LOCKED_SHAREDCACHE if another user of the same shared +** btree as the argument handle holds an exclusive lock on the +** sqlite_schema table. Otherwise SQLITE_OK. */ SQLITE_PRIVATE int sqlite3BtreeSchemaLocked(Btree *p){ int rc; + UNUSED_PARAMETER(p); /* only used in DEBUG builds */ assert( sqlite3_mutex_held(p->db->mutex) ); sqlite3BtreeEnter(p); - rc = querySharedCacheTableLock(p, MASTER_ROOT, READ_LOCK); + rc = querySharedCacheTableLock(p, SCHEMA_ROOT, READ_LOCK); assert( rc==SQLITE_OK || rc==SQLITE_LOCKED_SHAREDCACHE ); sqlite3BtreeLeave(p); return rc; @@ -73476,11 +84664,11 @@ SQLITE_PRIVATE int sqlite3BtreeLockTable(Btree *p, int iTab, u8 isWriteLock){ #ifndef SQLITE_OMIT_INCRBLOB /* -** Argument pCsr must be a cursor opened for writing on an -** INTKEY table currently pointing at a valid table entry. +** Argument pCsr must be a cursor opened for writing on an +** INTKEY table currently pointing at a valid table entry. ** This function modifies the data stored as part of that entry. ** -** Only the data content may only be modified, it is not possible to +** Only the data content may only be modified, it is not possible to ** change the length of the data stored. If this function is called with ** parameters that attempt to write past the end of the existing data, ** no modifications are made and SQLITE_CORRUPT is returned. @@ -73511,7 +84699,7 @@ SQLITE_PRIVATE int sqlite3BtreePutData(BtCursor *pCsr, u32 offset, u32 amt, void VVA_ONLY(rc =) saveAllCursors(pCsr->pBt, pCsr->pgnoRoot, pCsr); assert( rc==SQLITE_OK ); - /* Check some assumptions: + /* Check some assumptions: ** (a) the cursor is open for writing, ** (b) there is a read/write transaction open, ** (c) the connection holds a write-lock on the table (if required), @@ -73530,7 +84718,7 @@ SQLITE_PRIVATE int sqlite3BtreePutData(BtCursor *pCsr, u32 offset, u32 amt, void return accessPayload(pCsr, offset, amt, (unsigned char *)z, 1); } -/* +/* ** Mark this cursor as an incremental blob cursor. */ SQLITE_PRIVATE void sqlite3BtreeIncrblobCursor(BtCursor *pCur){ @@ -73540,14 +84728,14 @@ SQLITE_PRIVATE void sqlite3BtreeIncrblobCursor(BtCursor *pCur){ #endif /* -** Set both the "read version" (single byte at byte offset 18) and +** Set both the "read version" (single byte at byte offset 18) and ** "write version" (single byte at byte offset 19) fields in the database ** header to iVersion. */ SQLITE_PRIVATE int sqlite3BtreeSetVersion(Btree *pBtree, int iVersion){ BtShared *pBt = pBtree->pBt; int rc; /* Return code */ - + assert( iVersion==1 || iVersion==2 ); /* If setting the version fields to 1, do not automatically open the @@ -73595,6 +84783,17 @@ SQLITE_PRIVATE int sqlite3BtreeIsReadonly(Btree *p){ */ SQLITE_PRIVATE int sqlite3HeaderSizeBtree(void){ return ROUND8(sizeof(MemPage)); } +/* +** If no transaction is active and the database is not a temp-db, clear +** the in-memory pager cache. +*/ +SQLITE_PRIVATE void sqlite3BtreeClearCache(Btree *p){ + BtShared *pBt = p->pBt; + if( pBt->inTransaction==TRANS_NONE ){ + sqlite3PagerClearCache(pBt->pPager); + } +} + #if !defined(SQLITE_OMIT_SHARED_CACHE) /* ** Return true if the Btree passed as the only argument is sharable. @@ -73605,7 +84804,7 @@ SQLITE_PRIVATE int sqlite3BtreeSharable(Btree *p){ /* ** Return the number of connections to the BtShared object accessed by -** the Btree handle passed as the only argument. For private caches +** the Btree handle passed as the only argument. For private caches ** this is always 1. For shared caches it may be 1 or greater. */ SQLITE_PRIVATE int sqlite3BtreeConnectionCount(Btree *p){ @@ -73627,7 +84826,7 @@ SQLITE_PRIVATE int sqlite3BtreeConnectionCount(Btree *p){ ** May you share freely, never taking more than you give. ** ************************************************************************* -** This file contains the implementation of the sqlite3_backup_XXX() +** This file contains the implementation of the sqlite3_backup_XXX() ** API functions and the related features. */ /* #include "sqliteInt.h" */ @@ -73638,6 +84837,7 @@ SQLITE_PRIVATE int sqlite3BtreeConnectionCount(Btree *p){ */ struct sqlite3_backup { sqlite3* pDestDb; /* Destination database handle */ + char *zDestDb; Btree *pDest; /* Destination b-tree file */ u32 iDestSchema; /* Original schema cookie in destination */ int bDestLocked; /* True once a write-transaction is open on pDest */ @@ -73664,15 +84864,15 @@ struct sqlite3_backup { ** Once it has been created using backup_init(), a single sqlite3_backup ** structure may be accessed via two groups of thread-safe entry points: ** -** * Via the sqlite3_backup_XXX() API function backup_step() and +** * Via the sqlite3_backup_XXX() API function backup_step() and ** backup_finish(). Both these functions obtain the source database -** handle mutex and the mutex associated with the source BtShared +** handle mutex and the mutex associated with the source BtShared ** structure, in that order. ** ** * Via the BackupUpdate() and BackupRestart() functions, which are ** invoked by the pager layer to report various state changes in ** the page cache associated with the source database. The mutex -** associated with the source database BtShared structure will always +** associated with the source database BtShared structure will always ** be held when either of these functions are invoked. ** ** The other sqlite3_backup_XXX() API functions, backup_remaining() and @@ -73693,8 +84893,8 @@ struct sqlite3_backup { ** in connection handle pDb. If such a database cannot be found, return ** a NULL pointer and write an error message to pErrorDb. ** -** If the "temp" database is requested, it may need to be opened by this -** function. If an error occurs while doing so, return 0 and write an +** If the "temp" database is requested, it may need to be opened by this +** function. If an error occurs while doing so, return 0 and write an ** error message to pErrorDb. */ static Btree *findBtree(sqlite3 *pErrorDb, sqlite3 *pDb, const char *zDb){ @@ -73703,14 +84903,13 @@ static Btree *findBtree(sqlite3 *pErrorDb, sqlite3 *pDb, const char *zDb){ if( i==1 ){ Parse sParse; int rc = 0; - memset(&sParse, 0, sizeof(sParse)); - sParse.db = pDb; + sqlite3ParseObjectInit(&sParse,pDb); if( sqlite3OpenTempDatabase(&sParse) ){ sqlite3ErrorWithMsg(pErrorDb, sParse.rc, "%s", sParse.zErrMsg); rc = SQLITE_ERROR; } sqlite3DbFree(pErrorDb, sParse.zErrMsg); - sqlite3ParserReset(&sParse); + sqlite3ParseObjectReset(&sParse); if( rc ){ return 0; } @@ -73728,20 +84927,18 @@ static Btree *findBtree(sqlite3 *pErrorDb, sqlite3 *pDb, const char *zDb){ ** Attempt to set the page size of the destination to match the page size ** of the source. */ -static int setDestPgsz(sqlite3_backup *p){ - int rc; - rc = sqlite3BtreeSetPageSize(p->pDest,sqlite3BtreeGetPageSize(p->pSrc),-1,0); - return rc; +static int setDestPgsz(Btree *pDest, Btree *pSrc){ + return sqlite3BtreeSetPageSize(pDest, sqlite3BtreeGetPageSize(pSrc), 0, 0); } /* ** Check that there is no open read-transaction on the b-tree passed as the ** second argument. If there is not, return SQLITE_OK. Otherwise, if there -** is an open read-transaction, return SQLITE_ERROR and leave an error +** is an open read-transaction, return SQLITE_ERROR and leave an error ** message in database handle db. */ static int checkReadTransaction(sqlite3 *db, Btree *p){ - if( sqlite3BtreeIsInReadTrans(p) ){ + if( sqlite3BtreeTxnState(p)!=SQLITE_TXN_NONE ){ sqlite3ErrorWithMsg(db, SQLITE_ERROR, "destination database is in use"); return SQLITE_ERROR; } @@ -73788,32 +84985,42 @@ SQLITE_API sqlite3_backup *sqlite3_backup_init( ); p = 0; }else { + int nDest = sqlite3Strlen30(zDestDb); + /* Allocate space for a new sqlite3_backup object... ** EVIDENCE-OF: R-64852-21591 The sqlite3_backup object is created by a ** call to sqlite3_backup_init() and is destroyed by a call to ** sqlite3_backup_finish(). */ - p = (sqlite3_backup *)sqlite3MallocZero(sizeof(sqlite3_backup)); + p = (sqlite3_backup*)sqlite3MallocZero(sizeof(sqlite3_backup)+nDest+1); if( !p ){ sqlite3Error(pDestDb, SQLITE_NOMEM_BKPT); + }else{ + p->zDestDb = (char*)&p[1]; + memcpy(p->zDestDb, zDestDb, nDest); } } /* If the allocation succeeded, populate the new object. */ if( p ){ + /* Do not store the pointer to the destination b-tree at this point. + ** This is because there is nothing preventing it from being detached + ** or otherwise freed before the first call to sqlite3_backup_step() + ** on this object. The source b-tree does not have this problem, as + ** incrementing Btree.nBackup (see below) effectively locks the object. */ + Btree *pDest = findBtree(pDestDb, pDestDb, zDestDb); p->pSrc = findBtree(pDestDb, pSrcDb, zSrcDb); - p->pDest = findBtree(pDestDb, pDestDb, zDestDb); p->pDestDb = pDestDb; p->pSrcDb = pSrcDb; p->iNext = 1; p->isAttached = 0; - if( 0==p->pSrc || 0==p->pDest - || checkReadTransaction(pDestDb, p->pDest)!=SQLITE_OK + if( 0==p->pSrc || 0==pDest + || checkReadTransaction(pDestDb, pDest)!=SQLITE_OK ){ /* One (or both) of the named databases did not exist or an OOM ** error was hit. Or there is a transaction open on the destination - ** database. The error has already been written into the pDestDb - ** handle. All that is left to do here is free the sqlite3_backup + ** database. The error has already been written into the pDestDb + ** handle. All that is left to do here is free the sqlite3_backup ** structure. */ sqlite3_free(p); p = 0; @@ -73829,7 +85036,7 @@ SQLITE_API sqlite3_backup *sqlite3_backup_init( } /* -** Argument rc is an SQLite error code. Return true if this error is +** Argument rc is an SQLite error code. Return true if this error is ** considered fatal if encountered during a backup operation. All errors ** are considered fatal except for SQLITE_BUSY and SQLITE_LOCKED. */ @@ -73838,8 +85045,8 @@ static int isFatalError(int rc){ } /* -** Parameter zSrcData points to a buffer containing the data for -** page iSrcPg from the source database. Copy this data into the +** Parameter zSrcData points to a buffer containing the data for +** page iSrcPg from the source database. Copy this data into the ** destination database. */ static int backupOnePage( @@ -73853,13 +85060,6 @@ static int backupOnePage( int nDestPgsz = sqlite3BtreeGetPageSize(p->pDest); const int nCopy = MIN(nSrcPgsz, nDestPgsz); const i64 iEnd = (i64)iSrcPg*(i64)nSrcPgsz; -#ifdef SQLITE_HAS_CODEC - /* Use BtreeGetReserveNoMutex() for the source b-tree, as although it is - ** guaranteed that the shared-mutex is held by this thread, handle - ** p->pSrc may not actually be the owner. */ - int nSrcReserve = sqlite3BtreeGetReserveNoMutex(p->pSrc); - int nDestReserve = sqlite3BtreeGetOptimalReserve(p->pDest); -#endif int rc = SQLITE_OK; i64 iOff; @@ -73868,35 +85068,9 @@ static int backupOnePage( assert( !isFatalError(p->rc) ); assert( iSrcPg!=PENDING_BYTE_PAGE(p->pSrc->pBt) ); assert( zSrcData ); + assert( nSrcPgsz==nDestPgsz || sqlite3PagerIsMemdb(pDestPager)==0 ); - /* Catch the case where the destination is an in-memory database and the - ** page sizes of the source and destination differ. - */ - if( nSrcPgsz!=nDestPgsz && sqlite3PagerIsMemdb(pDestPager) ){ - rc = SQLITE_READONLY; - } - -#ifdef SQLITE_HAS_CODEC - /* Backup is not possible if the page size of the destination is changing - ** and a codec is in use. - */ - if( nSrcPgsz!=nDestPgsz && sqlite3PagerGetCodec(pDestPager)!=0 ){ - rc = SQLITE_READONLY; - } - - /* Backup is not possible if the number of bytes of reserve space differ - ** between source and destination. If there is a difference, try to - ** fix the destination to agree with the source. If that is not possible, - ** then the backup cannot proceed. - */ - if( nSrcReserve!=nDestReserve ){ - u32 newPgsz = nSrcPgsz; - rc = sqlite3PagerSetPagesize(pDestPager, &newPgsz, nSrcReserve); - if( rc==SQLITE_OK && newPgsz!=nSrcPgsz ) rc = SQLITE_READONLY; - } -#endif - - /* This loop runs once for each destination page spanned by the source + /* This loop runs once for each destination page spanned by the source ** page. For each iteration, variable iOff is set to the byte offset ** of the destination page. */ @@ -73915,7 +85089,7 @@ static int backupOnePage( ** Then clear the Btree layer MemPage.isInit flag. Both this module ** and the pager code use this trick (clearing the first byte ** of the page 'extra' space to invalidate the Btree layers - ** cached parse of the page). MemPage.isInit is marked + ** cached parse of the page). MemPage.isInit is marked ** "MUST BE FIRST" for this purpose. */ memcpy(zOut, zIn, nCopy); @@ -73935,7 +85109,7 @@ static int backupOnePage( ** exactly iSize bytes. If pFile is not larger than iSize bytes, then ** this function is a no-op. ** -** Return SQLITE_OK if everything is successful, or an SQLite error +** Return SQLITE_OK if everything is successful, or an SQLite error ** code if an error occurs. */ static int backupTruncateFile(sqlite3_file *pFile, i64 iSize){ @@ -73965,7 +85139,7 @@ static void attachBackupObject(sqlite3_backup *p){ */ SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage){ int rc; - int destMode; /* Destination journal mode */ + int destMode = 0; /* Destination journal mode */ int pgszSrc = 0; /* Source page size */ int pgszDest = 0; /* Destination page size */ @@ -73981,7 +85155,8 @@ SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage){ rc = p->rc; if( !isFatalError(rc) ){ Pager * const pSrcPager = sqlite3BtreePager(p->pSrc); /* Source pager */ - Pager * const pDestPager = sqlite3BtreePager(p->pDest); /* Dest pager */ + Btree * pDest = 0; /* Dest btree */ + Pager * pDestPager = 0; /* Dest pager */ int ii; /* Iterator variable */ int nSrcPage = -1; /* Size of source db in pages */ int bCloseTrans = 0; /* True if src db requires unlocking */ @@ -73995,42 +85170,60 @@ SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage){ rc = SQLITE_OK; } + /* If there is no open read-transaction on the source database, open ** one now. If a transaction is opened here, then it will be closed ** before this function exits. */ - if( rc==SQLITE_OK && 0==sqlite3BtreeIsInReadTrans(p->pSrc) ){ + if( rc==SQLITE_OK && SQLITE_TXN_NONE==sqlite3BtreeTxnState(p->pSrc) ){ rc = sqlite3BtreeBeginTrans(p->pSrc, 0, 0); bCloseTrans = 1; } + /* Locate the destination btree and pager. */ + if( (pDest = p->pDest)==0 ){ + pDest = findBtree(p->pDestDb, p->pDestDb, p->zDestDb); + } + if( pDest==0 ){ + rc = SQLITE_ERROR; + }else{ + pDestPager = sqlite3BtreePager(pDest); + } + /* If the destination database has not yet been locked (i.e. if this ** is the first call to backup_step() for the current backup operation), ** try to set its page size to the same as the source database. This ** is especially important on ZipVFS systems, as in that case it is ** not possible to create a database file that uses one page size by ** writing to it with another. */ - if( p->bDestLocked==0 && rc==SQLITE_OK && setDestPgsz(p)==SQLITE_NOMEM ){ + if( p->bDestLocked==0 && rc==SQLITE_OK + && setDestPgsz(pDest, p->pSrc)==SQLITE_NOMEM + ){ rc = SQLITE_NOMEM; } /* Lock the destination database, if it is not locked already. */ if( SQLITE_OK==rc && p->bDestLocked==0 - && SQLITE_OK==(rc = sqlite3BtreeBeginTrans(p->pDest, 2, - (int*)&p->iDestSchema)) + && SQLITE_OK==(rc = sqlite3BtreeBeginTrans(pDest, 2, + (int*)&p->iDestSchema)) ){ p->bDestLocked = 1; + p->pDest = pDest; } /* Do not allow backup if the destination database is in WAL mode ** and the page sizes are different between source and destination */ - pgszSrc = sqlite3BtreeGetPageSize(p->pSrc); - pgszDest = sqlite3BtreeGetPageSize(p->pDest); - destMode = sqlite3PagerGetJournalMode(sqlite3BtreePager(p->pDest)); - if( SQLITE_OK==rc && destMode==PAGER_JOURNALMODE_WAL && pgszSrc!=pgszDest ){ - rc = SQLITE_READONLY; + if( rc==SQLITE_OK ){ + pgszSrc = sqlite3BtreeGetPageSize(p->pSrc); + pgszDest = sqlite3BtreeGetPageSize(p->pDest); + destMode = sqlite3PagerGetJournalMode(sqlite3BtreePager(p->pDest)); + if( (destMode==PAGER_JOURNALMODE_WAL || sqlite3PagerIsMemdb(pDestPager)) + && pgszSrc!=pgszDest + ){ + rc = SQLITE_READONLY; + } } - + /* Now that there is a read-lock on the source database, query the ** source pager for the number of pages in the database. */ @@ -74057,7 +85250,7 @@ SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage){ attachBackupObject(p); } } - + /* Update the schema version field in the destination database. This ** is to make sure that the schema-version really does change in ** the case where the source and destination databases have the @@ -74083,12 +85276,12 @@ SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage){ int nDestTruncate; /* Set nDestTruncate to the final number of pages in the destination ** database. The complication here is that the destination page - ** size may be different to the source page size. + ** size may be different to the source page size. ** - ** If the source page size is smaller than the destination page size, + ** If the source page size is smaller than the destination page size, ** round up. In this case the call to sqlite3OsTruncate() below will ** fix the size of the file. However it is important to call - ** sqlite3PagerTruncateImage() here so that any pages in the + ** sqlite3PagerTruncateImage() here so that any pages in the ** destination file that lie beyond the nDestTruncate page mark are ** journalled by PagerCommitPhaseOne() before they are destroyed ** by the file truncation. @@ -74112,7 +85305,7 @@ SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage){ ** ** * The destination may need to be truncated, and ** - ** * Data stored on the pages immediately following the + ** * Data stored on the pages immediately following the ** pending-byte page in the source database may need to be ** copied into the destination database. */ @@ -74124,7 +85317,7 @@ SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage){ i64 iEnd; assert( pFile ); - assert( nDestTruncate==0 + assert( nDestTruncate==0 || (i64)nDestTruncate*(i64)pgszDest >= iSize || ( nDestTruncate==(int)(PENDING_BYTE_PAGE(p->pDest->pBt)-1) && iSize>=PENDING_BYTE && iSize<=PENDING_BYTE+pgszDest @@ -74134,7 +85327,7 @@ SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage){ ** database has been stored in the journal for pDestPager and the ** journal synced to disk. So at this point we may safely modify ** the database file in any way, knowing that if a power failure - ** occurs, the original database will be reconstructed from the + ** occurs, the original database will be reconstructed from the ** journal file. */ sqlite3PagerPagecount(pDestPager, &nDstPage); for(iPg=nDestTruncate; rc==SQLITE_OK && iPg<=(Pgno)nDstPage; iPg++){ @@ -74154,8 +85347,8 @@ SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage){ /* Write the extra pages and truncate the database file as required */ iEnd = MIN(PENDING_BYTE + pgszDest, iSize); for( - iOff=PENDING_BYTE+pgszSrc; - rc==SQLITE_OK && iOffpDest, 0)) @@ -74188,7 +85381,7 @@ SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage){ } } } - + /* If bCloseTrans is true, then this function opened a read transaction ** on the source database. Close the read transaction here. There is ** no need to check the return values of the btree methods here, as @@ -74200,7 +85393,7 @@ SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage){ TESTONLY( rc2 |= ) sqlite3BtreeCommitPhaseTwo(p->pSrc, 0); assert( rc2==SQLITE_OK ); } - + if( rc==SQLITE_IOERR_NOMEM ){ rc = SQLITE_NOMEM_BKPT; } @@ -74237,14 +85430,18 @@ SQLITE_API int sqlite3_backup_finish(sqlite3_backup *p){ } if( p->isAttached ){ pp = sqlite3PagerBackupPtr(sqlite3BtreePager(p->pSrc)); + assert( pp!=0 ); while( *pp!=p ){ pp = &(*pp)->pNext; + assert( pp!=0 ); } *pp = p->pNext; } /* If a transaction is still open on the Btree, roll it back. */ - sqlite3BtreeRollback(p->pDest, SQLITE_OK, 0); + if( p->pDest ){ + sqlite3BtreeRollback(p->pDest, SQLITE_OK, 0); + } /* Set the error code of the destination database handle. */ rc = (p->rc==SQLITE_DONE) ? SQLITE_OK : p->rc; @@ -74280,7 +85477,7 @@ SQLITE_API int sqlite3_backup_remaining(sqlite3_backup *p){ } /* -** Return the total number of pages in the source database as of the most +** Return the total number of pages in the source database as of the most ** recent call to sqlite3_backup_step(). */ SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p){ @@ -74295,7 +85492,7 @@ SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p){ /* ** This function is called after the contents of page iPage of the -** source database have been modified. If page iPage has already been +** source database have been modified. If page iPage has already been ** copied into the destination database, then the data written to the ** destination is now invalidated. The destination copy of iPage needs ** to be updated with the new data before the backup operation is @@ -74338,7 +85535,7 @@ SQLITE_PRIVATE void sqlite3BackupUpdate(sqlite3_backup *pBackup, Pgno iPage, con ** Restart the backup process. This is called when the pager layer ** detects that the database has been modified by an external database ** connection. In this case there is no way of knowing which of the -** pages that have been copied into the destination database are still +** pages that have been copied into the destination database are still ** valid and which are not, so the entire process needs to be restarted. ** ** It is assumed that the mutex associated with the BtShared object @@ -74358,8 +85555,8 @@ SQLITE_PRIVATE void sqlite3BackupRestart(sqlite3_backup *pBackup){ ** Copy the complete content of pBtFrom into pBtTo. A transaction ** must be active for both files. ** -** The size of file pTo may be reduced by this operation. If anything -** goes wrong, the transaction on pTo is rolled back. If successful, the +** The size of file pTo may be reduced by this operation. If anything +** goes wrong, the transaction on pTo is rolled back. If successful, the ** transaction is committed before returning. */ SQLITE_PRIVATE int sqlite3BtreeCopyFile(Btree *pTo, Btree *pFrom){ @@ -74369,7 +85566,7 @@ SQLITE_PRIVATE int sqlite3BtreeCopyFile(Btree *pTo, Btree *pFrom){ sqlite3BtreeEnter(pTo); sqlite3BtreeEnter(pFrom); - assert( sqlite3BtreeIsInTrans(pTo) ); + assert( sqlite3BtreeTxnState(pTo)==SQLITE_TXN_WRITE ); pFd = sqlite3PagerFile(sqlite3BtreePager(pTo)); if( pFd->pMethods ){ i64 nByte = sqlite3BtreeGetPageSize(pFrom)*(i64)sqlite3BtreeLastPage(pFrom); @@ -74389,15 +85586,11 @@ SQLITE_PRIVATE int sqlite3BtreeCopyFile(Btree *pTo, Btree *pFrom){ b.pDest = pTo; b.iNext = 1; -#ifdef SQLITE_HAS_CODEC - sqlite3PagerAlignReserve(sqlite3BtreePager(pTo), sqlite3BtreePager(pFrom)); -#endif - /* 0x7FFFFFFF is the hard limit for the number of pages in a database ** file. By passing this as the number of pages to copy to - ** sqlite3_backup_step(), we can guarantee that the copy finishes + ** sqlite3_backup_step(), we can guarantee that the copy finishes ** within a single call (unless an error occurs). The assert() statement - ** checks this assumption - (p->rc) should be set to either SQLITE_DONE + ** checks this assumption - (p->rc) should be set to either SQLITE_DONE ** or an error code. */ sqlite3_backup_step(&b, 0x7FFFFFFF); assert( b.rc!=SQLITE_OK ); @@ -74409,7 +85602,7 @@ SQLITE_PRIVATE int sqlite3BtreeCopyFile(Btree *pTo, Btree *pFrom){ sqlite3PagerClearCache(sqlite3BtreePager(b.pDest)); } - assert( sqlite3BtreeIsInTrans(pTo)==0 ); + assert( sqlite3BtreeTxnState(pTo)!=SQLITE_TXN_WRITE ); copy_finished: sqlite3BtreeLeave(pFrom); sqlite3BtreeLeave(pTo); @@ -74439,6 +85632,11 @@ SQLITE_PRIVATE int sqlite3BtreeCopyFile(Btree *pTo, Btree *pFrom){ /* #include "sqliteInt.h" */ /* #include "vdbeInt.h" */ +/* True if X is a power of two. 0 is considered a power of two here. +** In other words, return true if X has at most one bit set. +*/ +#define ISPOWEROF2(X) (((X)&((X)-1))==0) + #ifdef SQLITE_DEBUG /* ** Check invariants on a Mem object. @@ -74447,7 +85645,7 @@ SQLITE_PRIVATE int sqlite3BtreeCopyFile(Btree *pTo, Btree *pFrom){ ** this: assert( sqlite3VdbeCheckMemInvariants(pMem) ); */ SQLITE_PRIVATE int sqlite3VdbeCheckMemInvariants(Mem *p){ - /* If MEM_Dyn is set then Mem.xDel!=0. + /* If MEM_Dyn is set then Mem.xDel!=0. ** Mem.xDel might not be initialized if MEM_Dyn is clear. */ assert( (p->flags & MEM_Dyn)==0 || p->xDel!=0 ); @@ -74458,8 +85656,8 @@ SQLITE_PRIVATE int sqlite3VdbeCheckMemInvariants(Mem *p){ ** That saves a few cycles in inner loops. */ assert( (p->flags & MEM_Dyn)==0 || p->szMalloc==0 ); - /* Cannot be both MEM_Int and MEM_Real at the same time */ - assert( (p->flags & (MEM_Int|MEM_Real))!=(MEM_Int|MEM_Real) ); + /* Cannot have more than one of MEM_Int, MEM_Real, or MEM_IntReal */ + assert( ISPOWEROF2(p->flags & (MEM_Int|MEM_Real|MEM_IntReal)) ); if( p->flags & MEM_Null ){ /* Cannot be both MEM_Null and some other type */ @@ -74491,7 +85689,9 @@ SQLITE_PRIVATE int sqlite3VdbeCheckMemInvariants(Mem *p){ /* The szMalloc field holds the correct memory allocation size */ assert( p->szMalloc==0 - || p->szMalloc==sqlite3DbMallocSize(p->db,p->zMalloc) ); + || (p->flags==MEM_Undefined + && p->szMalloc<=sqlite3DbMallocSize(p->db,p->zMalloc)) + || p->szMalloc==sqlite3DbMallocSize(p->db,p->zMalloc)); /* If p holds a string or blob, the Mem.z must point to exactly ** one of the following: @@ -74502,7 +85702,7 @@ SQLITE_PRIVATE int sqlite3VdbeCheckMemInvariants(Mem *p){ ** (4) A static string or blob */ if( (p->flags & (MEM_Str|MEM_Blob)) && p->n>0 ){ - assert( + assert( ((p->szMalloc>0 && p->z==p->zMalloc)? 1 : 0) + ((p->flags&MEM_Dyn)!=0 ? 1 : 0) + ((p->flags&MEM_Ephem)!=0 ? 1 : 0) + @@ -74513,9 +85713,47 @@ SQLITE_PRIVATE int sqlite3VdbeCheckMemInvariants(Mem *p){ } #endif +/* +** Render a Mem object which is one of MEM_Int, MEM_Real, or MEM_IntReal +** into a buffer. +*/ +static void vdbeMemRenderNum(int sz, char *zBuf, Mem *p){ + StrAccum acc; + assert( p->flags & (MEM_Int|MEM_Real|MEM_IntReal) ); + assert( sz>22 ); + if( p->flags & (MEM_Int|MEM_IntReal) ){ +#if GCC_VERSION>=7000000 && GCC_VERSION<15000000 && defined(__i386__) + /* Work-around for GCC bug or bugs: + ** https://gcc.gnu.org/bugzilla/show_bug.cgi?id=96270 + ** https://gcc.gnu.org/bugzilla/show_bug.cgi?id=114659 + ** The problem appears to be fixed in GCC 15 */ + i64 x; + assert( (MEM_Str&~p->flags)*4==sizeof(x) ); + memcpy(&x, (char*)&p->u, (MEM_Str&~p->flags)*4); + p->n = sqlite3Int64ToText(x, zBuf); +#else + p->n = sqlite3Int64ToText(p->u.i, zBuf); +#endif + if( p->flags & MEM_IntReal ){ + memcpy(zBuf+p->n,".0", 3); + p->n += 2; + } + }else{ + sqlite3StrAccumInit(&acc, 0, zBuf, sz, 0); + sqlite3_str_appendf(&acc, "%!.*g", + (p->db ? p->db->nFpDigit : 17), p->u.r); + assert( acc.zText==zBuf && acc.mxAlloc<=0 ); + zBuf[acc.nChar] = 0; /* Fast version of sqlite3StrAccumFinish(&acc) */ + p->n = acc.nChar; + } +} + #ifdef SQLITE_DEBUG /* -** Check that string value of pMem agrees with its integer or real value. +** Validity checks on pMem. pMem holds a string. +** +** (1) Check that string value of pMem agrees with its integer or real value. +** (2) Check that the string is correctly zero terminated ** ** A single int or real value always converts to the same strings. But ** many different strings can be converted into the same int or real. @@ -74523,7 +85761,7 @@ SQLITE_PRIVATE int sqlite3VdbeCheckMemInvariants(Mem *p){ ** corresponding string value, then it is important that the string be ** derived from the numeric value, not the other way around, to ensure ** that the index and table are consistent. See ticket -** https://www.sqlite.org/src/info/343634942dd54ab (2018-01-31) for +** https://sqlite.org/src/info/343634942dd54ab (2018-01-31) for ** an example. ** ** This routine looks at pMem to verify that if it has both a numeric @@ -74533,17 +85771,30 @@ SQLITE_PRIVATE int sqlite3VdbeCheckMemInvariants(Mem *p){ ** ** This routine is for use inside of assert() statements only. */ -SQLITE_PRIVATE int sqlite3VdbeMemConsistentDualRep(Mem *p){ +SQLITE_PRIVATE int sqlite3VdbeMemValidStrRep(Mem *p){ + Mem tmp; char zBuf[100]; char *z; int i, j, incr; if( (p->flags & MEM_Str)==0 ) return 1; - if( (p->flags & (MEM_Int|MEM_Real))==0 ) return 1; - if( p->flags & MEM_Int ){ - sqlite3_snprintf(sizeof(zBuf),zBuf,"%lld",p->u.i); - }else{ - sqlite3_snprintf(sizeof(zBuf),zBuf,"%!.15g",p->u.r); + if( p->db && p->db->mallocFailed ) return 1; + if( p->flags & MEM_Term ){ + /* Insure that the string is properly zero-terminated. Pay particular + ** attention to the case where p->n is odd */ + if( p->szMalloc>0 && p->z==p->zMalloc ){ + assert( p->enc==SQLITE_UTF8 || p->szMalloc >= ((p->n+1)&~1)+2 ); + assert( p->enc!=SQLITE_UTF8 || p->szMalloc >= p->n+1 ); + } + assert( p->z[p->n]==0 ); + assert( p->enc==SQLITE_UTF8 || p->z[(p->n+1)&~1]==0 ); + assert( p->enc==SQLITE_UTF8 || p->z[((p->n+1)&~1)+1]==0 ); + } + if( (p->flags & (MEM_Int|MEM_Real|MEM_IntReal))==0 ) return 1; + if( p->db==0 ){ + return 1; /* db->nFpDigit required to validate p->z[] */ } + memcpy(&tmp, p, sizeof(tmp)); + vdbeMemRenderNum(sizeof(zBuf), zBuf, &tmp); z = p->z; i = j = 0; incr = 1; @@ -74576,10 +85827,15 @@ SQLITE_PRIVATE int sqlite3VdbeChangeEncoding(Mem *pMem, int desiredEnc){ #ifndef SQLITE_OMIT_UTF16 int rc; #endif + assert( pMem!=0 ); assert( !sqlite3VdbeMemIsRowSet(pMem) ); assert( desiredEnc==SQLITE_UTF8 || desiredEnc==SQLITE_UTF16LE || desiredEnc==SQLITE_UTF16BE ); - if( !(pMem->flags&MEM_Str) || pMem->enc==desiredEnc ){ + if( !(pMem->flags&MEM_Str) ){ + pMem->enc = desiredEnc; + return SQLITE_OK; + } + if( pMem->enc==desiredEnc ){ return SQLITE_OK; } assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); @@ -74617,9 +85873,17 @@ SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3VdbeMemGrow(Mem *pMem, int n, int bPre testcase( bPreserve && pMem->z==0 ); assert( pMem->szMalloc==0 - || pMem->szMalloc==sqlite3DbMallocSize(pMem->db, pMem->zMalloc) ); + || (pMem->flags==MEM_Undefined + && pMem->szMalloc<=sqlite3DbMallocSize(pMem->db,pMem->zMalloc)) + || pMem->szMalloc==sqlite3DbMallocSize(pMem->db,pMem->zMalloc)); if( pMem->szMalloc>0 && bPreserve && pMem->z==pMem->zMalloc ){ - pMem->z = pMem->zMalloc = sqlite3DbReallocOrFree(pMem->db, pMem->z, n); + if( pMem->db ){ + pMem->z = pMem->zMalloc = sqlite3DbReallocOrFree(pMem->db, pMem->z, n); + }else{ + pMem->zMalloc = sqlite3Realloc(pMem->z, n); + if( pMem->zMalloc==0 ) sqlite3_free(pMem->z); + pMem->z = pMem->zMalloc; + } bPreserve = 0; }else{ if( pMem->szMalloc>0 ) sqlite3DbFreeNN(pMem->db, pMem->zMalloc); @@ -74655,8 +85919,8 @@ SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3VdbeMemGrow(Mem *pMem, int n, int bPre ** ** Any prior string or blob content in the pMem object may be discarded. ** The pMem->xDel destructor is called, if it exists. Though MEM_Str -** and MEM_Blob values may be discarded, MEM_Int, MEM_Real, and MEM_Null -** values are preserved. +** and MEM_Blob values may be discarded, MEM_Int, MEM_Real, MEM_IntReal, +** and MEM_Null values are preserved. ** ** Return SQLITE_OK on success or an error code (probably SQLITE_NOMEM) ** if unable to complete the resizing. @@ -74669,20 +85933,64 @@ SQLITE_PRIVATE int sqlite3VdbeMemClearAndResize(Mem *pMem, int szNew){ } assert( (pMem->flags & MEM_Dyn)==0 ); pMem->z = pMem->zMalloc; - pMem->flags &= (MEM_Null|MEM_Int|MEM_Real); + pMem->flags &= (MEM_Null|MEM_Int|MEM_Real|MEM_IntReal); return SQLITE_OK; } +/* +** If pMem is already a string, detect if it is a zero-terminated +** string, or make it into one if possible, and mark it as such. +** +** This is an optimization. Correct operation continues even if +** this routine is a no-op. +** +** Return true if the strig is zero-terminated after this routine is +** called and false if it is not. +*/ +SQLITE_PRIVATE int sqlite3VdbeMemZeroTerminateIfAble(Mem *pMem){ + if( (pMem->flags & (MEM_Str|MEM_Term|MEM_Ephem|MEM_Static))!=MEM_Str ){ + /* pMem must be a string, and it cannot be an ephemeral or static string */ + return 0; + } + if( pMem->enc!=SQLITE_UTF8 ) return 0; + assert( pMem->z!=0 ); + if( pMem->flags & MEM_Dyn ){ + if( pMem->xDel==sqlite3_free + && sqlite3_msize(pMem->z) >= (u64)(pMem->n+1) + ){ + pMem->z[pMem->n] = 0; + pMem->flags |= MEM_Term; + return 1; + } + if( pMem->xDel==sqlite3RCStrUnref ){ + /* Blindly assume that all RCStr objects are zero-terminated */ + pMem->flags |= MEM_Term; + return 1; + } + }else if( pMem->szMalloc >= pMem->n+1 ){ + pMem->z[pMem->n] = 0; + pMem->flags |= MEM_Term; + return 1; + } + return 0; +} + /* ** It is already known that pMem contains an unterminated string. ** Add the zero terminator. +** +** Three bytes of zero are added. In this way, there is guaranteed +** to be a double-zero byte at an even byte boundary in order to +** terminate a UTF16 string, even if the initial size of the buffer +** is an odd number of bytes. */ static SQLITE_NOINLINE int vdbeMemAddTerminator(Mem *pMem){ - if( sqlite3VdbeMemGrow(pMem, pMem->n+2, 1) ){ + if( sqlite3VdbeMemGrow(pMem, pMem->n+3, 1) ){ return SQLITE_NOMEM_BKPT; } pMem->z[pMem->n] = 0; pMem->z[pMem->n+1] = 0; + pMem->z[pMem->n+2] = 0; pMem->flags |= MEM_Term; return SQLITE_OK; } @@ -74694,6 +86002,7 @@ static SQLITE_NOINLINE int vdbeMemAddTerminator(Mem *pMem){ ** Return SQLITE_OK on success or SQLITE_NOMEM if malloc fails. */ SQLITE_PRIVATE int sqlite3VdbeMemMakeWriteable(Mem *pMem){ + assert( pMem!=0 ); assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); assert( !sqlite3VdbeMemIsRowSet(pMem) ); if( (pMem->flags & (MEM_Str|MEM_Blob))!=0 ){ @@ -74718,6 +86027,7 @@ SQLITE_PRIVATE int sqlite3VdbeMemMakeWriteable(Mem *pMem){ #ifndef SQLITE_OMIT_INCRBLOB SQLITE_PRIVATE int sqlite3VdbeMemExpandBlob(Mem *pMem){ int nByte; + assert( pMem!=0 ); assert( pMem->flags & MEM_Zero ); assert( (pMem->flags&MEM_Blob)!=0 || MemNullNochng(pMem) ); testcase( sqlite3_value_nochange(pMem) ); @@ -74733,6 +86043,8 @@ SQLITE_PRIVATE int sqlite3VdbeMemExpandBlob(Mem *pMem){ if( sqlite3VdbeMemGrow(pMem, nByte, 1) ){ return SQLITE_NOMEM_BKPT; } + assert( pMem->z!=0 ); + assert( sqlite3DbMallocSize(pMem->db,pMem->z) >= nByte ); memset(&pMem->z[pMem->n], 0, pMem->u.nZero); pMem->n += pMem->u.nZero; @@ -74745,6 +86057,7 @@ SQLITE_PRIVATE int sqlite3VdbeMemExpandBlob(Mem *pMem){ ** Make sure the given Mem is \u0000 terminated. */ SQLITE_PRIVATE int sqlite3VdbeMemNulTerminate(Mem *pMem){ + assert( pMem!=0 ); assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); testcase( (pMem->flags & (MEM_Term|MEM_Str))==(MEM_Term|MEM_Str) ); testcase( (pMem->flags & (MEM_Term|MEM_Str))==0 ); @@ -74756,12 +86069,12 @@ SQLITE_PRIVATE int sqlite3VdbeMemNulTerminate(Mem *pMem){ } /* -** Add MEM_Str to the set of representations for the given Mem. Numbers -** are converted using sqlite3_snprintf(). Converting a BLOB to a string -** is a no-op. +** Add MEM_Str to the set of representations for the given Mem. This +** routine is only called if pMem is a number of some kind, not a NULL +** or a BLOB. ** -** Existing representations MEM_Int and MEM_Real are invalidated if -** bForce is true but are retained if bForce is false. +** Existing representations MEM_Int, MEM_Real, or MEM_IntReal are invalidated +** if bForce is true but are retained if bForce is false. ** ** A MEM_Null value will never be passed to this function. This function is ** used for converting values to text for returning to the user (i.e. via @@ -74770,13 +86083,13 @@ SQLITE_PRIVATE int sqlite3VdbeMemNulTerminate(Mem *pMem){ ** user and the latter is an internal programming error. */ SQLITE_PRIVATE int sqlite3VdbeMemStringify(Mem *pMem, u8 enc, u8 bForce){ - int fg = pMem->flags; const int nByte = 32; + assert( pMem!=0 ); assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); - assert( !(fg&MEM_Zero) ); - assert( !(fg&(MEM_Str|MEM_Blob)) ); - assert( fg&(MEM_Int|MEM_Real) ); + assert( !(pMem->flags&MEM_Zero) ); + assert( !(pMem->flags&(MEM_Str|MEM_Blob)) ); + assert( pMem->flags&(MEM_Int|MEM_Real|MEM_IntReal) ); assert( !sqlite3VdbeMemIsRowSet(pMem) ); assert( EIGHT_BYTE_ALIGNMENT(pMem) ); @@ -74786,23 +86099,12 @@ SQLITE_PRIVATE int sqlite3VdbeMemStringify(Mem *pMem, u8 enc, u8 bForce){ return SQLITE_NOMEM_BKPT; } - /* For a Real or Integer, use sqlite3_snprintf() to produce the UTF-8 - ** string representation of the value. Then, if the required encoding - ** is UTF-16le or UTF-16be do a translation. - ** - ** FIX ME: It would be better if sqlite3_snprintf() could do UTF-16. - */ - if( fg & MEM_Int ){ - sqlite3_snprintf(nByte, pMem->z, "%lld", pMem->u.i); - }else{ - assert( fg & MEM_Real ); - sqlite3_snprintf(nByte, pMem->z, "%!.15g", pMem->u.r); - } + vdbeMemRenderNum(nByte, pMem->z, pMem); assert( pMem->z!=0 ); - pMem->n = sqlite3Strlen30NN(pMem->z); + assert( pMem->n==(int)sqlite3Strlen30NN(pMem->z) ); pMem->enc = SQLITE_UTF8; pMem->flags |= MEM_Str|MEM_Term; - if( bForce ) pMem->flags &= ~(MEM_Int|MEM_Real); + if( bForce ) pMem->flags &= ~(MEM_Int|MEM_Real|MEM_IntReal); sqlite3VdbeChangeEncoding(pMem, enc); return SQLITE_OK; } @@ -74819,9 +86121,11 @@ SQLITE_PRIVATE int sqlite3VdbeMemFinalize(Mem *pMem, FuncDef *pFunc){ sqlite3_context ctx; Mem t; assert( pFunc!=0 ); + assert( pMem!=0 ); + assert( pMem->db!=0 ); assert( pFunc->xFinalize!=0 ); assert( (pMem->flags & MEM_Null)!=0 || pFunc==pMem->u.pDef ); - assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); + assert( sqlite3_mutex_held(pMem->db->mutex) ); memset(&ctx, 0, sizeof(ctx)); memset(&t, 0, sizeof(t)); t.flags = MEM_Null; @@ -74829,6 +86133,7 @@ SQLITE_PRIVATE int sqlite3VdbeMemFinalize(Mem *pMem, FuncDef *pFunc){ ctx.pOut = &t; ctx.pMem = pMem; ctx.pFunc = pFunc; + ctx.enc = ENC(t.db); pFunc->xFinalize(&ctx); /* IMP: R-24505-23230 */ assert( (pMem->flags & MEM_Dyn)==0 ); if( pMem->szMalloc>0 ) sqlite3DbFreeNN(pMem->db, pMem->zMalloc); @@ -74841,25 +86146,23 @@ SQLITE_PRIVATE int sqlite3VdbeMemFinalize(Mem *pMem, FuncDef *pFunc){ ** This routine calls the xValue method for that function and stores ** the results in memory cell pMem. ** -** SQLITE_ERROR is returned if xValue() reports an error. SQLITE_OK +** SQLITE_ERROR is returned if xValue() reports an error. SQLITE_OK ** otherwise. */ #ifndef SQLITE_OMIT_WINDOWFUNC SQLITE_PRIVATE int sqlite3VdbeMemAggValue(Mem *pAccum, Mem *pOut, FuncDef *pFunc){ sqlite3_context ctx; - Mem t; assert( pFunc!=0 ); assert( pFunc->xValue!=0 ); assert( (pAccum->flags & MEM_Null)!=0 || pFunc==pAccum->u.pDef ); - assert( pAccum->db==0 || sqlite3_mutex_held(pAccum->db->mutex) ); + assert( pAccum->db!=0 ); + assert( sqlite3_mutex_held(pAccum->db->mutex) ); memset(&ctx, 0, sizeof(ctx)); - memset(&t, 0, sizeof(t)); - t.flags = MEM_Null; - t.db = pAccum->db; sqlite3VdbeMemSetNull(pOut); ctx.pOut = pOut; ctx.pMem = pAccum; ctx.pFunc = pFunc; + ctx.enc = ENC(pAccum->db); pFunc->xValue(&ctx); return ctx.isError; } @@ -74925,34 +86228,12 @@ SQLITE_PRIVATE void sqlite3VdbeMemRelease(Mem *p){ } } -/* -** Convert a 64-bit IEEE double into a 64-bit signed integer. -** If the double is out of range of a 64-bit signed integer then -** return the closest available 64-bit signed integer. +/* Like sqlite3VdbeMemRelease() but faster for cases where we +** know in advance that the Mem is not MEM_Dyn or MEM_Agg. */ -static SQLITE_NOINLINE i64 doubleToInt64(double r){ -#ifdef SQLITE_OMIT_FLOATING_POINT - /* When floating-point is omitted, double and int64 are the same thing */ - return r; -#else - /* - ** Many compilers we encounter do not define constants for the - ** minimum and maximum 64-bit integers, or they define them - ** inconsistently. And many do not understand the "LL" notation. - ** So we define our own static constants here using nothing - ** larger than a 32-bit integer constant. - */ - static const i64 maxInt = LARGEST_INT64; - static const i64 minInt = SMALLEST_INT64; - - if( r<=(double)minInt ){ - return minInt; - }else if( r>=(double)maxInt ){ - return maxInt; - }else{ - return (i64)r; - } -#endif +SQLITE_PRIVATE void sqlite3VdbeMemReleaseMalloc(Mem *p){ + assert( !VdbeMemDynamic(p) ); + if( p->szMalloc ) vdbeMemClear(p); } /* @@ -74966,49 +86247,151 @@ static SQLITE_NOINLINE i64 doubleToInt64(double r){ ** ** If pMem represents a string value, its encoding might be changed. */ -static SQLITE_NOINLINE i64 memIntValue(Mem *pMem){ +static SQLITE_NOINLINE i64 memIntValue(const Mem *pMem){ i64 value = 0; sqlite3Atoi64(pMem->z, &value, pMem->n, pMem->enc); return value; } -SQLITE_PRIVATE i64 sqlite3VdbeIntValue(Mem *pMem){ +SQLITE_PRIVATE i64 sqlite3VdbeIntValue(const Mem *pMem){ int flags; + assert( pMem!=0 ); assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); assert( EIGHT_BYTE_ALIGNMENT(pMem) ); flags = pMem->flags; - if( flags & MEM_Int ){ + if( flags & (MEM_Int|MEM_IntReal) ){ + testcase( flags & MEM_IntReal ); return pMem->u.i; }else if( flags & MEM_Real ){ - return doubleToInt64(pMem->u.r); - }else if( flags & (MEM_Str|MEM_Blob) ){ - assert( pMem->z || pMem->n==0 ); + return sqlite3RealToI64(pMem->u.r); + }else if( (flags & (MEM_Str|MEM_Blob))!=0 && pMem->z!=0 ){ return memIntValue(pMem); }else{ return 0; } } +/* +** This routine implements the uncommon and slower path for +** sqlite3MemRealValueRC() that has to deal with input strings +** that are not UTF8 or that are not zero-terminated. It is +** broken out into a separate no-inline routine so that the +** main sqlite3MemRealValueRC() routine can avoid unnecessary +** stack pushes. +** +** A text->float translation of pMem->z is written into *pValue. +** +** Result code invariants: +** +** rc==0 => ERROR: Input string not well-formed, or OOM +** rc<0 => Some prefix of the input is well-formed +** rc>0 => All of the input is well-formed +** (rc&2)==0 => The number is expressed as an integer, with no +** decimal point or eNNN suffix. +*/ +static SQLITE_NOINLINE int sqlite3MemRealValueRCSlowPath( + Mem *pMem, + double *pValue +){ + int rc = SQLITE_OK; + *pValue = 0.0; + if( pMem->enc==SQLITE_UTF8 ){ + char *zCopy = sqlite3DbStrNDup(pMem->db, pMem->z, pMem->n); + if( zCopy ){ + rc = sqlite3AtoF(zCopy, pValue); + sqlite3DbFree(pMem->db, zCopy); + } + return rc; + }else{ + int n, i, j; + char *zCopy; + const char *z; + + n = pMem->n & ~1; + zCopy = sqlite3DbMallocRaw(pMem->db, n/2 + 2); + if( zCopy ){ + z = pMem->z; + if( pMem->enc==SQLITE_UTF16LE ){ + for(i=j=0; idb, zCopy); + } + return rc; + } +} + +/* +** Invoke sqlite3AtoF() on the text value of pMem. Write the +** translation of the text input into *pValue. +** +** The caller must ensure that pMem->db!=0 and that pMem is in +** mode MEM_Str or MEM_Blob. +** +** Result code invariants: +** +** rc==0 => ERROR: Input string not well-formed, or OOM +** rc<0 => Some prefix of the input is well-formed +** rc>0 => All of the input is well-formed +** (rc&2)==0 => The number is expressed as an integer, with no +** decimal point or eNNN suffix. +*/ +SQLITE_PRIVATE int sqlite3MemRealValueRC(Mem *pMem, double *pValue){ + testcase( pMem->db==0 ); + assert( pMem->flags & (MEM_Str|MEM_Blob) ); + if( pMem->z==0 ){ + *pValue = 0.0; + return 0; + }else if( pMem->enc==SQLITE_UTF8 + && ((pMem->flags & MEM_Term)!=0 || sqlite3VdbeMemZeroTerminateIfAble(pMem)) + ){ + return sqlite3AtoF(pMem->z, pValue); + }else if( pMem->n==0 ){ + *pValue = 0.0; + return 0; + }else{ + return sqlite3MemRealValueRCSlowPath(pMem, pValue); + } +} + +/* +** This routine acts as a bridge from sqlite3VdbeRealValue() to +** sqlite3VdbeRealValueRC, allowing sqlite3VdbeRealValue() to avoid +** stuffing values onto the stack. +*/ +static SQLITE_NOINLINE double sqlite3MemRealValueNoRC(Mem *pMem){ + double r; + (void)sqlite3MemRealValueRC(pMem, &r); + return r; +} + /* ** Return the best representation of pMem that we can get into a ** double. If pMem is already a double or an integer, return its ** value. If it is a string or blob, try to convert it to a double. ** If it is a NULL, return 0.0. */ -static SQLITE_NOINLINE double memRealValue(Mem *pMem){ - /* (double)0 In case of SQLITE_OMIT_FLOATING_POINT... */ - double val = (double)0; - sqlite3AtoF(pMem->z, &val, pMem->n, pMem->enc); - return val; -} SQLITE_PRIVATE double sqlite3VdbeRealValue(Mem *pMem){ + assert( pMem!=0 ); assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); assert( EIGHT_BYTE_ALIGNMENT(pMem) ); if( pMem->flags & MEM_Real ){ return pMem->u.r; - }else if( pMem->flags & MEM_Int ){ + }else if( pMem->flags & (MEM_Int|MEM_IntReal) ){ + testcase( pMem->flags & MEM_IntReal ); return (double)pMem->u.i; }else if( pMem->flags & (MEM_Str|MEM_Blob) ){ - return memRealValue(pMem); + return sqlite3MemRealValueNoRC(pMem); }else{ /* (double)0 In case of SQLITE_OMIT_FLOATING_POINT... */ return (double)0; @@ -75017,40 +86400,45 @@ SQLITE_PRIVATE double sqlite3VdbeRealValue(Mem *pMem){ /* ** Return 1 if pMem represents true, and return 0 if pMem represents false. -** Return the value ifNull if pMem is NULL. +** Return the value ifNull if pMem is NULL. */ SQLITE_PRIVATE int sqlite3VdbeBooleanValue(Mem *pMem, int ifNull){ - if( pMem->flags & MEM_Int ) return pMem->u.i!=0; + testcase( pMem->flags & MEM_IntReal ); + if( pMem->flags & (MEM_Int|MEM_IntReal) ) return pMem->u.i!=0; if( pMem->flags & MEM_Null ) return ifNull; return sqlite3VdbeRealValue(pMem)!=0.0; } /* -** The MEM structure is already a MEM_Real. Try to also make it a -** MEM_Int if we can. +** The MEM structure is already a MEM_Real or MEM_IntReal. Try to +** make it a MEM_Int if we can. */ SQLITE_PRIVATE void sqlite3VdbeIntegerAffinity(Mem *pMem){ - i64 ix; - assert( pMem->flags & MEM_Real ); + assert( pMem!=0 ); + assert( pMem->flags & (MEM_Real|MEM_IntReal) ); assert( !sqlite3VdbeMemIsRowSet(pMem) ); assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); assert( EIGHT_BYTE_ALIGNMENT(pMem) ); - ix = doubleToInt64(pMem->u.r); - - /* Only mark the value as an integer if - ** - ** (1) the round-trip conversion real->int->real is a no-op, and - ** (2) The integer is neither the largest nor the smallest - ** possible integer (ticket #3922) - ** - ** The second and third terms in the following conditional enforces - ** the second condition under the assumption that addition overflow causes - ** values to wrap around. - */ - if( pMem->u.r==ix && ix>SMALLEST_INT64 && ixu.i = ix; + if( pMem->flags & MEM_IntReal ){ MemSetTypeFlag(pMem, MEM_Int); + }else{ + i64 ix = sqlite3RealToI64(pMem->u.r); + + /* Only mark the value as an integer if + ** + ** (1) the round-trip conversion real->int->real is a no-op, and + ** (2) The integer is neither the largest nor the smallest + ** possible integer (ticket #3922) + ** + ** The second and third terms in the following conditional enforces + ** the second condition under the assumption that addition overflow causes + ** values to wrap around. + */ + if( pMem->u.r==ix && ix>SMALLEST_INT64 && ixu.i = ix; + MemSetTypeFlag(pMem, MEM_Int); + } } } @@ -75058,6 +86446,7 @@ SQLITE_PRIVATE void sqlite3VdbeIntegerAffinity(Mem *pMem){ ** Convert pMem to type integer. Invalidate any prior representations. */ SQLITE_PRIVATE int sqlite3VdbeMemIntegerify(Mem *pMem){ + assert( pMem!=0 ); assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); assert( !sqlite3VdbeMemIsRowSet(pMem) ); assert( EIGHT_BYTE_ALIGNMENT(pMem) ); @@ -75072,6 +86461,7 @@ SQLITE_PRIVATE int sqlite3VdbeMemIntegerify(Mem *pMem){ ** Invalidate any prior representations. */ SQLITE_PRIVATE int sqlite3VdbeMemRealify(Mem *pMem){ + assert( pMem!=0 ); assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); assert( EIGHT_BYTE_ALIGNMENT(pMem) ); @@ -75083,17 +86473,31 @@ SQLITE_PRIVATE int sqlite3VdbeMemRealify(Mem *pMem){ /* Compare a floating point value to an integer. Return true if the two ** values are the same within the precision of the floating point value. ** +** This function assumes that i was obtained by assignment from r1. +** ** For some versions of GCC on 32-bit machines, if you do the more obvious ** comparison of "r1==(double)i" you sometimes get an answer of false even ** though the r1 and (double)i values are bit-for-bit the same. */ -static int sqlite3RealSameAsInt(double r1, sqlite3_int64 i){ +SQLITE_PRIVATE int sqlite3RealSameAsInt(double r1, sqlite3_int64 i){ double r2 = (double)i; - return memcmp(&r1, &r2, sizeof(r1))==0; + return r1==0.0 + || (memcmp(&r1, &r2, sizeof(r1))==0 + && i >= -2251799813685248LL && i < 2251799813685248LL); +} + +/* Convert a floating point value to its closest integer. Do so in +** a way that avoids 'outside the range of representable values' warnings +** from UBSAN. +*/ +SQLITE_PRIVATE i64 sqlite3RealToI64(double r){ + if( r<-9223372036854774784.0 ) return SMALLEST_INT64; + if( r>+9223372036854774784.0 ) return LARGEST_INT64; + return (i64)r; } /* -** Convert pMem so that it has types MEM_Real or MEM_Int or both. +** Convert pMem so that it has type MEM_Real or MEM_Int. ** Invalidate any prior representations. ** ** Every effort is made to force the conversion, even if the input @@ -75101,25 +86505,27 @@ static int sqlite3RealSameAsInt(double r1, sqlite3_int64 i){ ** as much of the string as we can and ignore the rest. */ SQLITE_PRIVATE int sqlite3VdbeMemNumerify(Mem *pMem){ - if( (pMem->flags & (MEM_Int|MEM_Real|MEM_Null))==0 ){ + assert( pMem!=0 ); + testcase( pMem->flags & MEM_Int ); + testcase( pMem->flags & MEM_Real ); + testcase( pMem->flags & MEM_IntReal ); + testcase( pMem->flags & MEM_Null ); + if( (pMem->flags & (MEM_Int|MEM_Real|MEM_IntReal|MEM_Null))==0 ){ int rc; + sqlite3_int64 ix; assert( (pMem->flags & (MEM_Blob|MEM_Str))!=0 ); assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); - rc = sqlite3Atoi64(pMem->z, &pMem->u.i, pMem->n, pMem->enc); - if( rc==0 ){ + rc = sqlite3MemRealValueRC(pMem, &pMem->u.r); + if( ((rc&2)==0 && sqlite3Atoi64(pMem->z, &ix, pMem->n, pMem->enc)<2) + || sqlite3RealSameAsInt(pMem->u.r, (ix = sqlite3RealToI64(pMem->u.r))) + ){ + pMem->u.i = ix; MemSetTypeFlag(pMem, MEM_Int); }else{ - i64 i = pMem->u.i; - sqlite3AtoF(pMem->z, &pMem->u.r, pMem->n, pMem->enc); - if( rc==1 && sqlite3RealSameAsInt(pMem->u.r, i) ){ - pMem->u.i = i; - MemSetTypeFlag(pMem, MEM_Int); - }else{ - MemSetTypeFlag(pMem, MEM_Real); - } + MemSetTypeFlag(pMem, MEM_Real); } } - assert( (pMem->flags & (MEM_Int|MEM_Real|MEM_Null))!=0 ); + assert( (pMem->flags & (MEM_Int|MEM_Real|MEM_IntReal|MEM_Null))!=0 ); pMem->flags &= ~(MEM_Str|MEM_Blob|MEM_Zero); return SQLITE_OK; } @@ -75131,8 +86537,8 @@ SQLITE_PRIVATE int sqlite3VdbeMemNumerify(Mem *pMem){ ** affinity even if that results in loss of data. This routine is ** used (for example) to implement the SQL "cast()" operator. */ -SQLITE_PRIVATE void sqlite3VdbeMemCast(Mem *pMem, u8 aff, u8 encoding){ - if( pMem->flags & MEM_Null ) return; +SQLITE_PRIVATE int sqlite3VdbeMemCast(Mem *pMem, u8 aff, u8 encoding){ + if( pMem->flags & MEM_Null ) return SQLITE_OK; switch( aff ){ case SQLITE_AFF_BLOB: { /* Really a cast to BLOB */ if( (pMem->flags & MEM_Blob)==0 ){ @@ -75157,15 +86563,20 @@ SQLITE_PRIVATE void sqlite3VdbeMemCast(Mem *pMem, u8 aff, u8 encoding){ break; } default: { + int rc; assert( aff==SQLITE_AFF_TEXT ); assert( MEM_Str==(MEM_Blob>>3) ); pMem->flags |= (pMem->flags&MEM_Blob)>>3; sqlite3ValueApplyAffinity(pMem, SQLITE_AFF_TEXT, encoding); assert( pMem->flags & MEM_Str || pMem->db->mallocFailed ); - pMem->flags &= ~(MEM_Int|MEM_Real|MEM_Blob|MEM_Zero); - break; + pMem->flags &= ~(MEM_Int|MEM_Real|MEM_IntReal|MEM_Blob|MEM_Zero); + if( encoding!=SQLITE_UTF8 ) pMem->n &= ~1; + rc = sqlite3VdbeChangeEncoding(pMem, encoding); + if( rc ) return rc; + sqlite3VdbeMemZeroTerminateIfAble(pMem); } } + return SQLITE_OK; } /* @@ -75201,13 +86612,14 @@ SQLITE_PRIVATE void sqlite3VdbeMemSetNull(Mem *pMem){ } } SQLITE_PRIVATE void sqlite3ValueSetNull(sqlite3_value *p){ - sqlite3VdbeMemSetNull((Mem*)p); + sqlite3VdbeMemSetNull((Mem*)p); } /* ** Delete any previous value and set the value to be a BLOB of length ** n containing all zeros. */ +#ifndef SQLITE_OMIT_INCRBLOB SQLITE_PRIVATE void sqlite3VdbeMemSetZeroBlob(Mem *pMem, int n){ sqlite3VdbeMemRelease(pMem); pMem->flags = MEM_Blob|MEM_Zero; @@ -75217,6 +86629,21 @@ SQLITE_PRIVATE void sqlite3VdbeMemSetZeroBlob(Mem *pMem, int n){ pMem->enc = SQLITE_UTF8; pMem->z = 0; } +#else +SQLITE_PRIVATE int sqlite3VdbeMemSetZeroBlob(Mem *pMem, int n){ + int nByte = n>0?n:1; + if( sqlite3VdbeMemGrow(pMem, nByte, 0) ){ + return SQLITE_NOMEM_BKPT; + } + assert( pMem->z!=0 ); + assert( sqlite3DbMallocSize(pMem->db, pMem->z)>=nByte ); + memset(pMem->z, 0, nByte); + pMem->n = n>0?n:0; + pMem->flags = MEM_Blob; + pMem->enc = SQLITE_UTF8; + return SQLITE_OK; +} +#endif /* ** The pMem is known to contain content that needs to be destroyed prior @@ -75242,6 +86669,13 @@ SQLITE_PRIVATE void sqlite3VdbeMemSetInt64(Mem *pMem, i64 val){ } } +/* +** Set the iIdx'th entry of array aMem[] to contain integer value val. +*/ +SQLITE_PRIVATE void sqlite3MemSetArrayInt64(sqlite3_value *aMem, int iIdx, i64 val){ + sqlite3VdbeMemSetInt64(&aMem[iIdx], val); +} + /* A no-op destructor */ SQLITE_PRIVATE void sqlite3NoopDestructor(void *p){ UNUSED_PARAMETER(p); } @@ -75256,6 +86690,7 @@ SQLITE_PRIVATE void sqlite3VdbeMemSetPointer( void (*xDestructor)(void*) ){ assert( pMem->flags==MEM_Null ); + vdbeMemClear(pMem); pMem->u.zPType = zPType ? zPType : ""; pMem->z = pPtr; pMem->flags = MEM_Null|MEM_Dyn|MEM_Subtype|MEM_Term; @@ -75322,7 +86757,7 @@ SQLITE_PRIVATE int sqlite3VdbeMemTooBig(Mem *p){ } return n>p->db->aLimit[SQLITE_LIMIT_LENGTH]; } - return 0; + return 0; } #ifdef SQLITE_DEBUG @@ -75331,37 +86766,41 @@ SQLITE_PRIVATE int sqlite3VdbeMemTooBig(Mem *p){ ** its link to a shallow copy and by marking any current shallow ** copies of this cell as invalid. ** -** This is used for testing and debugging only - to make sure shallow -** copies are not misused. +** This is used for testing and debugging only - to help ensure that shallow +** copies (created by OP_SCopy) are not misused. */ SQLITE_PRIVATE void sqlite3VdbeMemAboutToChange(Vdbe *pVdbe, Mem *pMem){ int i; Mem *pX; - for(i=0, pX=pVdbe->aMem; inMem; i++, pX++){ - if( pX->pScopyFrom==pMem ){ - /* If pX is marked as a shallow copy of pMem, then verify that - ** no significant changes have been made to pX since the OP_SCopy. - ** A significant change would indicated a missed call to this - ** function for pX. Minor changes, such as adding or removing a - ** dual type, are allowed, as long as the underlying value is the - ** same. */ - u16 mFlags = pMem->flags & pX->flags & pX->mScopyFlags; - assert( (mFlags&MEM_Int)==0 || pMem->u.i==pX->u.i ); - assert( (mFlags&MEM_Real)==0 || pMem->u.r==pX->u.r ); - assert( (mFlags&MEM_Str)==0 || (pMem->n==pX->n && pMem->z==pX->z) ); - assert( (mFlags&MEM_Blob)==0 || sqlite3BlobCompare(pMem,pX)==0 ); - - /* pMem is the register that is changing. But also mark pX as - ** undefined so that we can quickly detect the shallow-copy error */ - pX->flags = MEM_Undefined; - pX->pScopyFrom = 0; + if( pMem->bScopy ){ + for(i=1, pX=pVdbe->aMem+1; inMem; i++, pX++){ + if( pX->pScopyFrom==pMem ){ + u16 mFlags; + if( pVdbe->db->flags & SQLITE_VdbeTrace ){ + sqlite3DebugPrintf("Invalidate R[%d] due to change in R[%d]\n", + (int)(pX - pVdbe->aMem), (int)(pMem - pVdbe->aMem)); + } + /* If pX is marked as a shallow copy of pMem, then try to verify that + ** no significant changes have been made to pX since the OP_SCopy. + ** A significant change would indicated a missed call to this + ** function for pX. Minor changes, such as adding or removing a + ** dual type, are allowed, as long as the underlying value is the + ** same. */ + mFlags = pMem->flags & pX->flags & pX->mScopyFlags; + assert( (mFlags&(MEM_Int|MEM_IntReal))==0 || pMem->u.i==pX->u.i ); + + /* pMem is the register that is changing. But also mark pX as + ** undefined so that we can quickly detect the shallow-copy error */ + pX->flags = MEM_Undefined; + pX->pScopyFrom = 0; + } } + pMem->bScopy = 0; } pMem->pScopyFrom = 0; } #endif /* SQLITE_DEBUG */ - /* ** Make an shallow copy of pFrom into pTo. Prior contents of ** pTo are freed. The pFrom->z field is not duplicated. If @@ -75427,8 +86866,8 @@ SQLITE_PRIVATE void sqlite3VdbeMemMove(Mem *pTo, Mem *pFrom){ ** Change the value of a Mem to be a string or a BLOB. ** ** The memory management strategy depends on the value of the xDel -** parameter. If the value passed is SQLITE_TRANSIENT, then the -** string is copied into a (possibly existing) buffer managed by the +** parameter. If the value passed is SQLITE_TRANSIENT, then the +** string is copied into a (possibly existing) buffer managed by the ** Mem structure. Otherwise, any existing buffer is freed and the ** pointer copied. ** @@ -75437,20 +86876,29 @@ SQLITE_PRIVATE void sqlite3VdbeMemMove(Mem *pTo, Mem *pFrom){ ** stored without allocating memory, then it is. If a memory allocation ** is required to store the string, then value of pMem is unchanged. In ** either case, SQLITE_TOOBIG is returned. +** +** The "enc" parameter is the text encoding for the string, or zero +** to store a blob. +** +** If n is negative, then the string consists of all bytes up to but +** excluding the first zero character. The n parameter must be +** non-negative for blobs. */ SQLITE_PRIVATE int sqlite3VdbeMemSetStr( Mem *pMem, /* Memory cell to set to string value */ const char *z, /* String pointer */ - int n, /* Bytes in string, or negative */ + i64 n, /* Bytes in string, or negative */ u8 enc, /* Encoding of z. 0 for BLOBs */ void (*xDel)(void*) /* Destructor function */ ){ - int nByte = n; /* New value for pMem->n */ + i64 nByte = n; /* New value for pMem->n */ int iLimit; /* Maximum allowed string or blob size */ - u16 flags = 0; /* New value for pMem->flags */ + u16 flags; /* New value for pMem->flags */ + assert( pMem!=0 ); assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); assert( !sqlite3VdbeMemIsRowSet(pMem) ); + assert( enc!=0 || n>=0 ); /* If z is a NULL pointer, set pMem to contain an SQL NULL. */ if( !z ){ @@ -75463,15 +86911,30 @@ SQLITE_PRIVATE int sqlite3VdbeMemSetStr( }else{ iLimit = SQLITE_MAX_LENGTH; } - flags = (enc==0?MEM_Blob:MEM_Str); if( nByte<0 ){ assert( enc!=0 ); if( enc==SQLITE_UTF8 ){ - nByte = 0x7fffffff & (int)strlen(z); + nByte = strlen(z); }else{ for(nByte=0; nByte<=iLimit && (z[nByte] | z[nByte+1]); nByte+=2){} } - flags |= MEM_Term; + flags= MEM_Str|MEM_Term; + }else if( enc==0 ){ + flags = MEM_Blob; + enc = SQLITE_UTF8; + }else{ + flags = MEM_Str; + } + if( nByte>iLimit ){ + if( xDel && xDel!=SQLITE_TRANSIENT ){ + if( xDel==SQLITE_DYNAMIC ){ + sqlite3DbFree(pMem->db, (void*)z); + }else{ + xDel((void*)z); + } + } + sqlite3VdbeMemSetNull(pMem); + return sqlite3ErrorToParser(pMem->db, SQLITE_TOOBIG); } /* The following block sets the new values of Mem.z and Mem.xDel. It @@ -75479,19 +86942,17 @@ SQLITE_PRIVATE int sqlite3VdbeMemSetStr( ** management (one of MEM_Dyn or MEM_Static). */ if( xDel==SQLITE_TRANSIENT ){ - u32 nAlloc = nByte; + i64 nAlloc = nByte; if( flags&MEM_Term ){ nAlloc += (enc==SQLITE_UTF8?1:2); } - if( nByte>iLimit ){ - return sqlite3ErrorToParser(pMem->db, SQLITE_TOOBIG); - } testcase( nAlloc==0 ); testcase( nAlloc==31 ); testcase( nAlloc==32 ); if( sqlite3VdbeMemClearAndResize(pMem, (int)MAX(nAlloc,32)) ){ return SQLITE_NOMEM_BKPT; } + assert( pMem->z!=0 ); memcpy(pMem->z, z, nAlloc); }else{ sqlite3VdbeMemRelease(pMem); @@ -75505,20 +86966,95 @@ SQLITE_PRIVATE int sqlite3VdbeMemSetStr( } } - pMem->n = nByte; + pMem->n = (int)(nByte & 0x7fffffff); pMem->flags = flags; - pMem->enc = (enc==0 ? SQLITE_UTF8 : enc); + pMem->enc = enc; #ifndef SQLITE_OMIT_UTF16 - if( pMem->enc!=SQLITE_UTF8 && sqlite3VdbeMemHandleBom(pMem) ){ + if( enc>SQLITE_UTF8 && sqlite3VdbeMemHandleBom(pMem) ){ return SQLITE_NOMEM_BKPT; } #endif - if( nByte>iLimit ){ - return SQLITE_TOOBIG; + + return SQLITE_OK; +} + +/* Like sqlite3VdbeMemSetStr() except: +** +** enc is always SQLITE_UTF8 +** pMem->db is always non-NULL +*/ +SQLITE_PRIVATE int sqlite3VdbeMemSetText( + Mem *pMem, /* Memory cell to set to string value */ + const char *z, /* String pointer */ + i64 n, /* Bytes in string, or negative */ + void (*xDel)(void*) /* Destructor function */ +){ + i64 nByte = n; /* New value for pMem->n */ + u16 flags; + + assert( pMem!=0 ); + assert( pMem->db!=0 ); + assert( sqlite3_mutex_held(pMem->db->mutex) ); + assert( !sqlite3VdbeMemIsRowSet(pMem) ); + + /* If z is a NULL pointer, set pMem to contain an SQL NULL. */ + if( !z ){ + sqlite3VdbeMemSetNull(pMem); + return SQLITE_OK; } + if( nByte<0 ){ + nByte = strlen(z); + flags = MEM_Str|MEM_Term; + }else{ + flags = MEM_Str; + } + if( nByte>(i64)pMem->db->aLimit[SQLITE_LIMIT_LENGTH] ){ + if( xDel && xDel!=SQLITE_TRANSIENT ){ + if( xDel==SQLITE_DYNAMIC ){ + sqlite3DbFree(pMem->db, (void*)z); + }else{ + xDel((void*)z); + } + } + sqlite3VdbeMemSetNull(pMem); + return sqlite3ErrorToParser(pMem->db, SQLITE_TOOBIG); + } + + /* The following block sets the new values of Mem.z and Mem.xDel. It + ** also sets a flag in local variable "flags" to indicate the memory + ** management (one of MEM_Dyn or MEM_Static). + */ + if( xDel==SQLITE_TRANSIENT ){ + i64 nAlloc = nByte + 1; + testcase( nAlloc==31 ); + testcase( nAlloc==32 ); + if( sqlite3VdbeMemClearAndResize(pMem, (int)MAX(nAlloc,32)) ){ + return SQLITE_NOMEM_BKPT; + } + assert( pMem->z!=0 ); + memcpy(pMem->z, z, nByte); + pMem->z[nByte] = 0; + }else{ + sqlite3VdbeMemRelease(pMem); + pMem->z = (char *)z; + if( xDel==SQLITE_DYNAMIC ){ + pMem->zMalloc = pMem->z; + pMem->szMalloc = sqlite3DbMallocSize(pMem->db, pMem->zMalloc); + pMem->xDel = 0; + }else if( xDel==SQLITE_STATIC ){ + pMem->xDel = xDel; + flags |= MEM_Static; + }else{ + pMem->xDel = xDel; + flags |= MEM_Dyn; + } + } + pMem->flags = flags; + pMem->n = (int)(nByte & 0x7fffffff); + pMem->enc = SQLITE_UTF8; return SQLITE_OK; } @@ -75537,7 +87073,7 @@ SQLITE_PRIVATE int sqlite3VdbeMemSetStr( ** If this routine fails for any reason (malloc returns NULL or unable ** to read from the disk) then the pMem is left in an inconsistent state. */ -static SQLITE_NOINLINE int vdbeMemFromBtreeResize( +SQLITE_PRIVATE int sqlite3VdbeMemFromBtree( BtCursor *pCur, /* Cursor pointing at record to retrieve. */ u32 offset, /* Offset from the start of data to return bytes from. */ u32 amt, /* Number of bytes to return. */ @@ -75545,7 +87081,12 @@ static SQLITE_NOINLINE int vdbeMemFromBtreeResize( ){ int rc; pMem->flags = MEM_Null; - if( sqlite3BtreeMaxRecordSize(pCur)=SQLITE_MAX_ALLOCATION_SIZE ){ + return SQLITE_NOMEM_BKPT; + } + if( (u64)amt + (u64)offset > (u64)sqlite3BtreeMaxRecordSize(pCur) ){ return SQLITE_CORRUPT_BKPT; } if( SQLITE_OK==(rc = sqlite3VdbeMemClearAndResize(pMem, amt+1)) ){ @@ -75560,31 +87101,28 @@ static SQLITE_NOINLINE int vdbeMemFromBtreeResize( } return rc; } -SQLITE_PRIVATE int sqlite3VdbeMemFromBtree( +SQLITE_PRIVATE int sqlite3VdbeMemFromBtreeZeroOffset( BtCursor *pCur, /* Cursor pointing at record to retrieve. */ - u32 offset, /* Offset from the start of data to return bytes from. */ u32 amt, /* Number of bytes to return. */ Mem *pMem /* OUT: Return data in this Mem structure. */ ){ - char *zData; /* Data from the btree layer */ u32 available = 0; /* Number of bytes available on the local btree page */ int rc = SQLITE_OK; /* Return code */ assert( sqlite3BtreeCursorIsValid(pCur) ); assert( !VdbeMemDynamic(pMem) ); - /* Note: the calls to BtreeKeyFetch() and DataFetch() below assert() + /* Note: the calls to BtreeKeyFetch() and DataFetch() below assert() ** that both the BtShared and database handle mutexes are held. */ assert( !sqlite3VdbeMemIsRowSet(pMem) ); - zData = (char *)sqlite3BtreePayloadFetch(pCur, &available); - assert( zData!=0 ); + pMem->z = (char *)sqlite3BtreePayloadFetch(pCur, &available); + assert( pMem->z!=0 ); - if( offset+amt<=available ){ - pMem->z = &zData[offset]; + if( amt<=available ){ pMem->flags = MEM_Blob|MEM_Ephem; pMem->n = (int)amt; }else{ - rc = vdbeMemFromBtreeResize(pCur, offset, amt, pMem); + rc = sqlite3VdbeMemFromBtree(pCur, 0, amt, pMem); } return rc; @@ -75621,7 +87159,7 @@ static SQLITE_NOINLINE const void *valueToText(sqlite3_value* pVal, u8 enc){ assert(pVal->enc==(enc & ~SQLITE_UTF16_ALIGNED) || pVal->db==0 || pVal->db->mallocFailed ); if( pVal->enc==(enc & ~SQLITE_UTF16_ALIGNED) ){ - assert( sqlite3VdbeMemConsistentDualRep(pVal) ); + assert( sqlite3VdbeMemValidStrRep(pVal) ); return pVal->z; }else{ return 0; @@ -75644,7 +87182,7 @@ SQLITE_PRIVATE const void *sqlite3ValueText(sqlite3_value* pVal, u8 enc){ assert( (enc&3)==(enc&~SQLITE_UTF16_ALIGNED) ); assert( !sqlite3VdbeMemIsRowSet(pVal) ); if( (pVal->flags&(MEM_Str|MEM_Term))==(MEM_Str|MEM_Term) && pVal->enc==enc ){ - assert( sqlite3VdbeMemConsistentDualRep(pVal) ); + assert( sqlite3VdbeMemValidStrRep(pVal) ); return pVal->z; } if( pVal->flags&MEM_Null ){ @@ -75653,6 +87191,24 @@ SQLITE_PRIVATE const void *sqlite3ValueText(sqlite3_value* pVal, u8 enc){ return valueToText(pVal, enc); } +/* Return true if sqlit3_value object pVal is a string or blob value +** that uses the destructor specified in the second argument. +** +** TODO: Maybe someday promote this interface into a published API so +** that third-party extensions can get access to it? +*/ +SQLITE_PRIVATE int sqlite3ValueIsOfClass(const sqlite3_value *pVal, void(*xFree)(void*)){ + if( ALWAYS(pVal!=0) + && ALWAYS((pVal->flags & (MEM_Str|MEM_Blob))!=0) + && (pVal->flags & MEM_Dyn)!=0 + && pVal->xDel==xFree + ){ + return 1; + }else{ + return 0; + } +} + /* ** Create a new sqlite3_value object. */ @@ -75666,7 +87222,7 @@ SQLITE_PRIVATE sqlite3_value *sqlite3ValueNew(sqlite3 *db){ } /* -** Context object passed by sqlite3Stat4ProbeSetValue() through to +** Context object passed by sqlite3Stat4ProbeSetValue() through to ** valueNew(). See comments above valueNew() for details. */ struct ValueNewStat4Ctx { @@ -75681,23 +87237,23 @@ struct ValueNewStat4Ctx { ** the second argument to this function is NULL, the object is allocated ** by calling sqlite3ValueNew(). ** -** Otherwise, if the second argument is non-zero, then this function is +** Otherwise, if the second argument is non-zero, then this function is ** being called indirectly by sqlite3Stat4ProbeSetValue(). If it has not -** already been allocated, allocate the UnpackedRecord structure that +** already been allocated, allocate the UnpackedRecord structure that ** that function will return to its caller here. Then return a pointer to ** an sqlite3_value within the UnpackedRecord.a[] array. */ static sqlite3_value *valueNew(sqlite3 *db, struct ValueNewStat4Ctx *p){ -#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 +#ifdef SQLITE_ENABLE_STAT4 if( p ){ UnpackedRecord *pRec = p->ppRec[0]; if( pRec==0 ){ Index *pIdx = p->pIdx; /* Index being probed */ - int nByte; /* Bytes of space to allocate */ + i64 nByte; /* Bytes of space to allocate */ int i; /* Counter variable */ int nCol = pIdx->nColumn; /* Number of index columns including rowid */ - + nByte = sizeof(Mem) * nCol + ROUND8(sizeof(UnpackedRecord)); pRec = (UnpackedRecord*)sqlite3DbMallocZero(db, nByte); if( pRec ){ @@ -75718,13 +87274,14 @@ static sqlite3_value *valueNew(sqlite3 *db, struct ValueNewStat4Ctx *p){ if( pRec==0 ) return 0; p->ppRec[0] = pRec; } - + pRec->nField = p->iVal+1; + sqlite3VdbeMemSetNull(&pRec->aMem[p->iVal]); return &pRec->aMem[p->iVal]; } #else UNUSED_PARAMETER(p); -#endif /* defined(SQLITE_ENABLE_STAT3_OR_STAT4) */ +#endif /* defined(SQLITE_ENABLE_STAT4) */ return sqlite3ValueNew(db); } @@ -75737,21 +87294,21 @@ static sqlite3_value *valueNew(sqlite3 *db, struct ValueNewStat4Ctx *p){ ** * the SQLITE_FUNC_NEEDCOLL function flag is not set, ** ** then this routine attempts to invoke the SQL function. Assuming no -** error occurs, output parameter (*ppVal) is set to point to a value +** error occurs, output parameter (*ppVal) is set to point to a value ** object containing the result before returning SQLITE_OK. ** ** Affinity aff is applied to the result of the function before returning. -** If the result is a text value, the sqlite3_value object uses encoding +** If the result is a text value, the sqlite3_value object uses encoding ** enc. ** ** If the conditions above are not met, this function returns SQLITE_OK ** and sets (*ppVal) to NULL. Or, if an error occurs, (*ppVal) is set to ** NULL and an SQLite error code returned. */ -#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 +#ifdef SQLITE_ENABLE_STAT4 static int valueFromFunction( sqlite3 *db, /* The database connection */ - Expr *p, /* The expression to evaluate */ + const Expr *p, /* The expression to evaluate */ u8 enc, /* Encoding to use */ u8 aff, /* Affinity to use */ sqlite3_value **ppVal, /* Write the new value here */ @@ -75759,7 +87316,7 @@ static int valueFromFunction( ){ sqlite3_context ctx; /* Context object for function invocation */ sqlite3_value **apVal = 0; /* Function arguments */ - int nVal = 0; /* Size of apVal[] array */ + int nVal = 0; /* Number of function arguments */ FuncDef *pFunc = 0; /* Function definition */ sqlite3_value *pVal = 0; /* New value */ int rc = SQLITE_OK; /* Return code */ @@ -75768,12 +87325,17 @@ static int valueFromFunction( assert( pCtx!=0 ); assert( (p->flags & EP_TokenOnly)==0 ); + assert( ExprUseXList(p) ); pList = p->x.pList; if( pList ) nVal = pList->nExpr; + assert( !ExprHasProperty(p, EP_IntValue) ); pFunc = sqlite3FindFunction(db, p->u.zToken, nVal, enc, 0); +#ifdef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION + if( pFunc==0 ) return SQLITE_OK; +#endif assert( pFunc ); - if( (pFunc->funcFlags & (SQLITE_FUNC_CONSTANT|SQLITE_FUNC_SLOCHNG))==0 - || (pFunc->funcFlags & SQLITE_FUNC_NEEDCOLL) + if( (pFunc->funcFlags & (SQLITE_FUNC_CONSTANT|SQLITE_FUNC_SLOCHNG))==0 + || (pFunc->funcFlags & (SQLITE_FUNC_NEEDCOLL|SQLITE_FUNC_RUNONLY))!=0 ){ return SQLITE_OK; } @@ -75785,7 +87347,8 @@ static int valueFromFunction( goto value_from_function_out; } for(i=0; ia[i].pExpr, enc, aff, &apVal[i]); + rc = sqlite3Stat4ValueFromExpr(pCtx->pParse, pList->a[i].pExpr, aff, + &apVal[i]); if( apVal[i]==0 || rc!=SQLITE_OK ) goto value_from_function_out; } } @@ -75796,10 +87359,10 @@ static int valueFromFunction( goto value_from_function_out; } - assert( pCtx->pParse->rc==SQLITE_OK ); memset(&ctx, 0, sizeof(ctx)); ctx.pOut = pVal; ctx.pFunc = pFunc; + ctx.enc = ENC(db); pFunc->xSFunc(&ctx, nVal, apVal); if( ctx.isError ){ rc = ctx.isError; @@ -75808,16 +87371,16 @@ static int valueFromFunction( sqlite3ValueApplyAffinity(pVal, aff, SQLITE_UTF8); assert( rc==SQLITE_OK ); rc = sqlite3VdbeChangeEncoding(pVal, enc); - if( rc==SQLITE_OK && sqlite3VdbeMemTooBig(pVal) ){ + if( NEVER(rc==SQLITE_OK && sqlite3VdbeMemTooBig(pVal)) ){ rc = SQLITE_TOOBIG; pCtx->pParse->nErr++; } } - pCtx->pParse->rc = rc; value_from_function_out: if( rc!=SQLITE_OK ){ pVal = 0; + pCtx->pParse->rc = rc; } if( apVal ){ for(i=0; iop)==TK_UPLUS || op==TK_SPAN ) pExpr = pExpr->pLeft; -#if defined(SQLITE_ENABLE_STAT3_OR_STAT4) if( op==TK_REGISTER ) op = pExpr->op2; -#else - if( NEVER(op==TK_REGISTER) ) op = pExpr->op2; -#endif /* Compressed expressions only appear when parsing the DEFAULT clause ** on a table column definition, and hence only when pCtx==0. This @@ -75873,25 +87432,40 @@ static int valueFromExpr( assert( (pExpr->flags & EP_TokenOnly)==0 || pCtx==0 ); if( op==TK_CAST ){ - u8 aff = sqlite3AffinityType(pExpr->u.zToken,0); + u8 aff; + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + aff = sqlite3AffinityType(pExpr->u.zToken,0); rc = valueFromExpr(db, pExpr->pLeft, enc, aff, ppVal, pCtx); testcase( rc!=SQLITE_OK ); if( *ppVal ){ - sqlite3VdbeMemCast(*ppVal, aff, SQLITE_UTF8); - sqlite3ValueApplyAffinity(*ppVal, affinity, SQLITE_UTF8); +#ifdef SQLITE_ENABLE_STAT4 + rc = ExpandBlob(*ppVal); +#else + /* zero-blobs only come from functions, not literal values. And + ** functions are only processed under STAT4 */ + assert( (ppVal[0][0].flags & MEM_Zero)==0 ); +#endif + sqlite3VdbeMemCast(*ppVal, aff, enc); + sqlite3ValueApplyAffinity(*ppVal, affinity, enc); } return rc; } /* Handle negative integers in a single step. This is needed in the - ** case when the value is -9223372036854775808. - */ - if( op==TK_UMINUS - && (pExpr->pLeft->op==TK_INTEGER || pExpr->pLeft->op==TK_FLOAT) ){ - pExpr = pExpr->pLeft; - op = pExpr->op; - negInt = -1; - zNeg = "-"; + ** case when the value is -9223372036854775808. Except - do not do this + ** for hexadecimal literals. */ + if( op==TK_UMINUS ){ + Expr *pLeft = pExpr->pLeft; + if( (pLeft->op==TK_INTEGER || pLeft->op==TK_FLOAT) ){ + if( ExprHasProperty(pLeft, EP_IntValue) + || pLeft->u.zToken[0]!='0' || (pLeft->u.zToken[1] & ~0x20)!='X' + ){ + pExpr = pLeft; + op = pExpr->op; + negInt = -1; + zNeg = "-"; + } + } } if( op==TK_STRING || op==TK_FLOAT || op==TK_INTEGER ){ @@ -75900,29 +87474,52 @@ static int valueFromExpr( if( ExprHasProperty(pExpr, EP_IntValue) ){ sqlite3VdbeMemSetInt64(pVal, (i64)pExpr->u.iValue*negInt); }else{ - zVal = sqlite3MPrintf(db, "%s%s", zNeg, pExpr->u.zToken); - if( zVal==0 ) goto no_mem; - sqlite3ValueSetStr(pVal, -1, zVal, SQLITE_UTF8, SQLITE_DYNAMIC); - } - if( (op==TK_INTEGER || op==TK_FLOAT ) && affinity==SQLITE_AFF_BLOB ){ - sqlite3ValueApplyAffinity(pVal, SQLITE_AFF_NUMERIC, SQLITE_UTF8); + i64 iVal; + if( op==TK_INTEGER && 0==sqlite3DecOrHexToI64(pExpr->u.zToken, &iVal) ){ + sqlite3VdbeMemSetInt64(pVal, iVal*negInt); + }else{ + zVal = sqlite3MPrintf(db, "%s%s", zNeg, pExpr->u.zToken); + if( zVal==0 ) goto no_mem; + sqlite3ValueSetStr(pVal, -1, zVal, SQLITE_UTF8, SQLITE_DYNAMIC); + } + } + if( affinity==SQLITE_AFF_BLOB ){ + if( op==TK_FLOAT ){ + assert( pVal && pVal->z && pVal->flags==(MEM_Str|MEM_Term) ); + sqlite3AtoF(pVal->z, &pVal->u.r); + pVal->flags = MEM_Real; + }else if( op==TK_INTEGER ){ + /* This case is required by -9223372036854775808 and other strings + ** that look like integers but cannot be handled by the + ** sqlite3DecOrHexToI64() call above. */ + sqlite3ValueApplyAffinity(pVal, SQLITE_AFF_NUMERIC, SQLITE_UTF8); + } }else{ sqlite3ValueApplyAffinity(pVal, affinity, SQLITE_UTF8); } - if( pVal->flags & (MEM_Int|MEM_Real) ) pVal->flags &= ~MEM_Str; + assert( (pVal->flags & MEM_IntReal)==0 ); + if( pVal->flags & (MEM_Int|MEM_IntReal|MEM_Real) ){ + testcase( pVal->flags & MEM_Int ); + testcase( pVal->flags & MEM_Real ); + pVal->flags &= ~MEM_Str; + } if( enc!=SQLITE_UTF8 ){ rc = sqlite3VdbeChangeEncoding(pVal, enc); } }else if( op==TK_UMINUS ) { /* This branch happens for multiple negative signs. Ex: -(-5) */ - if( SQLITE_OK==valueFromExpr(db,pExpr->pLeft,enc,affinity,&pVal,pCtx) + if( SQLITE_OK==valueFromExpr(db,pExpr->pLeft,enc,affinity,&pVal,pCtx) && pVal!=0 ){ sqlite3VdbeMemNumerify(pVal); if( pVal->flags & MEM_Real ){ pVal->u.r = -pVal->u.r; }else if( pVal->u.i==SMALLEST_INT64 ){ +#ifndef SQLITE_OMIT_FLOATING_POINT pVal->u.r = -(double)SMALLEST_INT64; +#else + pVal->u.r = LARGEST_INT64; +#endif MemSetTypeFlag(pVal, MEM_Real); }else{ pVal->u.i = -pVal->u.i; @@ -75932,11 +87529,12 @@ static int valueFromExpr( }else if( op==TK_NULL ){ pVal = valueNew(db, pCtx); if( pVal==0 ) goto no_mem; - sqlite3VdbeMemNumerify(pVal); + sqlite3VdbeMemSetNull(pVal); } #ifndef SQLITE_OMIT_BLOB_LITERAL else if( op==TK_BLOB ){ int nVal; + assert( !ExprHasProperty(pExpr, EP_IntValue) ); assert( pExpr->u.zToken[0]=='x' || pExpr->u.zToken[0]=='X' ); assert( pExpr->u.zToken[1]=='\'' ); pVal = valueNew(db, pCtx); @@ -75948,16 +87546,18 @@ static int valueFromExpr( 0, SQLITE_DYNAMIC); } #endif -#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 +#ifdef SQLITE_ENABLE_STAT4 else if( op==TK_FUNCTION && pCtx!=0 ){ rc = valueFromFunction(db, pExpr, enc, affinity, &pVal, pCtx); } #endif else if( op==TK_TRUEFALSE ){ + assert( !ExprHasProperty(pExpr, EP_IntValue) ); pVal = valueNew(db, pCtx); if( pVal ){ pVal->flags = MEM_Int; pVal->u.i = pExpr->u.zToken[4]==0; + sqlite3ValueApplyAffinity(pVal, affinity, enc); } } @@ -75965,13 +87565,13 @@ static int valueFromExpr( return rc; no_mem: -#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 - if( pCtx==0 || pCtx->pParse->nErr==0 ) +#ifdef SQLITE_ENABLE_STAT4 + if( pCtx==0 || NEVER(pCtx->pParse->nErr==0) ) #endif sqlite3OomFault(db); sqlite3DbFree(db, zVal); assert( *ppVal==0 ); -#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 +#ifdef SQLITE_ENABLE_STAT4 if( pCtx==0 ) sqlite3ValueFree(pVal); #else assert( pCtx==0 ); sqlite3ValueFree(pVal); @@ -75991,7 +87591,7 @@ static int valueFromExpr( */ SQLITE_PRIVATE int sqlite3ValueFromExpr( sqlite3 *db, /* The database connection */ - Expr *pExpr, /* The expression to evaluate */ + const Expr *pExpr, /* The expression to evaluate */ u8 enc, /* Encoding to use */ u8 affinity, /* Affinity to use */ sqlite3_value **ppVal /* Write the new value here */ @@ -75999,56 +87599,7 @@ SQLITE_PRIVATE int sqlite3ValueFromExpr( return pExpr ? valueFromExpr(db, pExpr, enc, affinity, ppVal, 0) : 0; } -#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 -/* -** The implementation of the sqlite_record() function. This function accepts -** a single argument of any type. The return value is a formatted database -** record (a blob) containing the argument value. -** -** This is used to convert the value stored in the 'sample' column of the -** sqlite_stat3 table to the record format SQLite uses internally. -*/ -static void recordFunc( - sqlite3_context *context, - int argc, - sqlite3_value **argv -){ - const int file_format = 1; - u32 iSerial; /* Serial type */ - int nSerial; /* Bytes of space for iSerial as varint */ - u32 nVal; /* Bytes of space required for argv[0] */ - int nRet; - sqlite3 *db; - u8 *aRet; - - UNUSED_PARAMETER( argc ); - iSerial = sqlite3VdbeSerialType(argv[0], file_format, &nVal); - nSerial = sqlite3VarintLen(iSerial); - db = sqlite3_context_db_handle(context); - - nRet = 1 + nSerial + nVal; - aRet = sqlite3DbMallocRawNN(db, nRet); - if( aRet==0 ){ - sqlite3_result_error_nomem(context); - }else{ - aRet[0] = nSerial+1; - putVarint32(&aRet[1], iSerial); - sqlite3VdbeSerialPut(&aRet[1+nSerial], argv[0], iSerial); - sqlite3_result_blob(context, aRet, nRet, SQLITE_TRANSIENT); - sqlite3DbFreeNN(db, aRet); - } -} - -/* -** Register built-in functions used to help read ANALYZE data. -*/ -SQLITE_PRIVATE void sqlite3AnalyzeFunctions(void){ - static FuncDef aAnalyzeTableFuncs[] = { - FUNCTION(sqlite_record, 1, 0, 0, recordFunc), - }; - sqlite3InsertBuiltinFuncs(aAnalyzeTableFuncs, ArraySize(aAnalyzeTableFuncs)); -} - +#ifdef SQLITE_ENABLE_STAT4 /* ** Attempt to extract a value from pExpr and use it to construct *ppVal. ** @@ -76109,8 +87660,8 @@ static int stat4ValueFromExpr( } /* -** This function is used to allocate and populate UnpackedRecord -** structures intended to be compared against sample index keys stored +** This function is used to allocate and populate UnpackedRecord +** structures intended to be compared against sample index keys stored ** in the sqlite_stat4 table. ** ** A single call to this function populates zero or more fields of the @@ -76121,14 +87672,14 @@ static int stat4ValueFromExpr( ** ** * The expression is a bound variable, and this is a reprepare, or ** -** * The sqlite3ValueFromExpr() function is able to extract a value +** * The sqlite3ValueFromExpr() function is able to extract a value ** from the expression (i.e. the expression is a literal value). ** ** Or, if pExpr is a TK_VECTOR, one field is populated for each of the ** vector components that match either of the two latter criteria listed ** above. ** -** Before any value is appended to the record, the affinity of the +** Before any value is appended to the record, the affinity of the ** corresponding column within index pIdx is applied to it. Before ** this function returns, output parameter *pnExtract is set to the ** number of values appended to the record. @@ -76179,9 +87730,9 @@ SQLITE_PRIVATE int sqlite3Stat4ProbeSetValue( /* ** Attempt to extract a value from expression pExpr using the methods -** as described for sqlite3Stat4ProbeSetValue() above. +** as described for sqlite3Stat4ProbeSetValue() above. ** -** If successful, set *ppVal to point to a new value object and return +** If successful, set *ppVal to point to a new value object and return ** SQLITE_OK. If no value can be extracted, but no other error occurs ** (e.g. OOM), return SQLITE_OK and set *ppVal to NULL. Or, if an error ** does occur, return an SQLite error code. The final value of *ppVal @@ -76201,8 +87752,13 @@ SQLITE_PRIVATE int sqlite3Stat4ValueFromExpr( ** the column value into *ppVal. If *ppVal is initially NULL then a new ** sqlite3_value object is allocated. ** -** If *ppVal is initially NULL then the caller is responsible for +** If *ppVal is initially NULL then the caller is responsible for ** ensuring that the value written into *ppVal is eventually freed. +** +** If the buffer does not contain a well-formed record, this routine may +** read several bytes past the end of the buffer. Callers must therefore +** ensure that any buffer which may contain a corrupt record is padded +** with at least 8 bytes of addressable memory. */ SQLITE_PRIVATE int sqlite3Stat4Column( sqlite3 *db, /* Database handle */ @@ -76212,17 +87768,17 @@ SQLITE_PRIVATE int sqlite3Stat4Column( sqlite3_value **ppVal /* OUT: Extracted value */ ){ u32 t = 0; /* a column type code */ - int nHdr; /* Size of the header in the record */ - int iHdr; /* Next unread header byte */ - int iField; /* Next unread data byte */ - int szField = 0; /* Size of the current data field */ + u32 nHdr; /* Size of the header in the record */ + u32 iHdr; /* Next unread header byte */ + i64 iField; /* Next unread data byte */ + u32 szField = 0; /* Size of the current data field */ int i; /* Column index */ u8 *a = (u8*)pRec; /* Typecast byte array */ Mem *pMem = *ppVal; /* Write result into this Mem object */ assert( iCol>0 ); iHdr = getVarint32(a, nHdr); - if( nHdr>nRec || iHdr>=nHdr ) return SQLITE_CORRUPT_BKPT; + if( nHdr>(u32)nRec || iHdr>=nHdr ) return SQLITE_CORRUPT_BKPT; iField = nHdr; for(i=0; i<=iCol; i++){ iHdr += getVarint32(&a[iHdr], t); @@ -76300,6 +87856,9 @@ SQLITE_PRIVATE int sqlite3ValueBytes(sqlite3_value *pVal, u8 enc){ if( (p->flags & MEM_Str)!=0 && pVal->enc==enc ){ return p->n; } + if( (p->flags & MEM_Str)!=0 && enc!=SQLITE_UTF8 && pVal->enc!=SQLITE_UTF8 ){ + return p->n; + } if( (p->flags & MEM_Blob)!=0 ){ if( p->flags & MEM_Zero ){ return p->n + p->u.nZero; @@ -76325,11 +87884,15 @@ SQLITE_PRIVATE int sqlite3ValueBytes(sqlite3_value *pVal, u8 enc){ ** ************************************************************************* ** This file contains code used for creating, destroying, and populating -** a VDBE (or an "sqlite3_stmt" as it is known to the outside world.) +** a VDBE (or an "sqlite3_stmt" as it is known to the outside world.) */ /* #include "sqliteInt.h" */ /* #include "vdbeInt.h" */ +/* Forward references */ +static void freeEphemeralFunction(sqlite3 *db, FuncDef *pDef); +static void vdbeFreeOpArray(sqlite3 *, Op *, int); + /* ** Create a new virtual database engine. */ @@ -76341,12 +87904,12 @@ SQLITE_PRIVATE Vdbe *sqlite3VdbeCreate(Parse *pParse){ memset(&p->aOp, 0, sizeof(Vdbe)-offsetof(Vdbe,aOp)); p->db = db; if( db->pVdbe ){ - db->pVdbe->pPrev = p; + db->pVdbe->ppVPrev = &p->pVNext; } - p->pNext = db->pVdbe; - p->pPrev = 0; + p->pVNext = db->pVdbe; + p->ppVPrev = &db->pVdbe; db->pVdbe = p; - p->magic = VDBE_MAGIC_INIT; + assert( p->eVdbeState==VDBE_INIT_STATE ); p->pParse = pParse; pParse->pVdbe = p; assert( pParse->aLabel==0 ); @@ -76357,6 +87920,13 @@ SQLITE_PRIVATE Vdbe *sqlite3VdbeCreate(Parse *pParse){ return p; } +/* +** Return the Parse object that owns a Vdbe object. +*/ +SQLITE_PRIVATE Parse *sqlite3VdbeParser(Vdbe *p){ + return p->pParse; +} + /* ** Change the error string stored in Vdbe.zErrMsg */ @@ -76419,25 +87989,32 @@ SQLITE_PRIVATE int sqlite3VdbeUsesDoubleQuotedString( #endif /* -** Swap all content between two VDBE structures. +** Swap byte-code between two VDBE structures. +** +** This happens after pB was previously run and returned +** SQLITE_SCHEMA. The statement was then reprepared in pA. +** This routine transfers the new bytecode in pA over to pB +** so that pB can be run again. The old pB byte code is +** moved back to pA so that it will be cleaned up when pA is +** finalized. */ SQLITE_PRIVATE void sqlite3VdbeSwap(Vdbe *pA, Vdbe *pB){ - Vdbe tmp, *pTmp; + Vdbe tmp, *pTmp, **ppTmp; char *zTmp; assert( pA->db==pB->db ); tmp = *pA; *pA = *pB; *pB = tmp; - pTmp = pA->pNext; - pA->pNext = pB->pNext; - pB->pNext = pTmp; - pTmp = pA->pPrev; - pA->pPrev = pB->pPrev; - pB->pPrev = pTmp; + pTmp = pA->pVNext; + pA->pVNext = pB->pVNext; + pB->pVNext = pTmp; + ppTmp = pA->ppVPrev; + pA->ppVPrev = pB->ppVPrev; + pB->ppVPrev = ppTmp; zTmp = pA->zSql; pA->zSql = pB->zSql; pB->zSql = zTmp; -#if 0 +#ifdef SQLITE_ENABLE_NORMALIZE zTmp = pA->zNormSql; pA->zNormSql = pB->zNormSql; pB->zNormSql = zTmp; @@ -76449,13 +88026,13 @@ SQLITE_PRIVATE void sqlite3VdbeSwap(Vdbe *pA, Vdbe *pB){ } /* -** Resize the Vdbe.aOp array so that it is at least nOp elements larger +** Resize the Vdbe.aOp array so that it is at least nOp elements larger ** than its current size. nOp is guaranteed to be less than or equal ** to 1024/sizeof(Op). ** ** If an out-of-memory error occurs while resizing the array, return -** SQLITE_NOMEM. In this case Vdbe.aOp and Vdbe.nOpAlloc remain -** unchanged (this is so that any opcodes already allocated can be +** SQLITE_NOMEM. In this case Vdbe.aOp and Vdbe.nOpAlloc remain +** unchanged (this is so that any opcodes already allocated can be ** correctly deallocated along with the rest of the Vdbe). */ static int growOpArray(Vdbe *v, int nOp){ @@ -76463,7 +88040,7 @@ static int growOpArray(Vdbe *v, int nOp){ Parse *p = v->pParse; /* The SQLITE_TEST_REALLOC_STRESS compile-time option is designed to force - ** more frequent reallocs and hence provide more opportunities for + ** more frequent reallocs and hence provide more opportunities for ** simulated OOM faults. SQLITE_TEST_REALLOC_STRESS is generally used ** during testing only. With SQLITE_TEST_REALLOC_STRESS grow the op array ** by the minimum* amount required until the size reaches 512. Normal @@ -76484,7 +88061,7 @@ static int growOpArray(Vdbe *v, int nOp){ return SQLITE_NOMEM; } - assert( nOp<=(1024/sizeof(Op)) ); + assert( nOp<=(int)(1024/sizeof(Op)) ); assert( nNew>=(v->nOpAlloc+nOp) ); pNew = sqlite3DbRealloc(p->db, v->aOp, nNew*sizeof(Op)); if( pNew ){ @@ -76498,14 +88075,53 @@ static int growOpArray(Vdbe *v, int nOp){ #ifdef SQLITE_DEBUG /* This routine is just a convenient place to set a breakpoint that will ** fire after each opcode is inserted and displayed using -** "PRAGMA vdbe_addoptrace=on". -*/ -static void test_addop_breakpoint(void){ - static int n = 0; +** "PRAGMA vdbe_addoptrace=on". Parameters "pc" (program counter) and +** pOp are available to make the breakpoint conditional. +** +** Other useful labels for breakpoints include: +** test_trace_breakpoint(pc,pOp) +** sqlite3CorruptError(lineno) +** sqlite3MisuseError(lineno) +** sqlite3CantopenError(lineno) +*/ +static void test_addop_breakpoint(int pc, Op *pOp){ + static u64 n = 0; + (void)pc; + (void)pOp; n++; + if( n==LARGEST_UINT64 ) abort(); /* so that n is used, preventing a warning */ } #endif +/* +** Slow paths for sqlite3VdbeAddOp3() and sqlite3VdbeAddOp4Int() for the +** unusual case when we need to increase the size of the Vdbe.aOp[] array +** before adding the new opcode. +*/ +static SQLITE_NOINLINE int growOp3(Vdbe *p, int op, int p1, int p2, int p3){ + assert( p->nOpAlloc<=p->nOp ); + if( growOpArray(p, 1) ) return 1; + assert( p->nOpAlloc>p->nOp ); + return sqlite3VdbeAddOp3(p, op, p1, p2, p3); +} +static SQLITE_NOINLINE int addOp4IntSlow( + Vdbe *p, /* Add the opcode to this VM */ + int op, /* The new opcode */ + int p1, /* The P1 operand */ + int p2, /* The P2 operand */ + int p3, /* The P3 operand */ + int p4 /* The P4 operand as an integer */ +){ + int addr = sqlite3VdbeAddOp3(p, op, p1, p2, p3); + if( p->db->mallocFailed==0 ){ + VdbeOp *pOp = &p->aOp[addr]; + pOp->p4type = P4_INT32; + pOp->p4.i = p4; + } + return addr; +} + + /* ** Add a new instruction to the list of instructions current in the ** VDBE. Return the address of the new instruction. @@ -76516,30 +88132,31 @@ static void test_addop_breakpoint(void){ ** ** op The opcode for this instruction ** -** p1, p2, p3 Operands -** -** Use the sqlite3VdbeResolveLabel() function to fix an address and -** the sqlite3VdbeChangeP4() function to change the value of the P4 -** operand. +** p1, p2, p3, p4 Operands */ -static SQLITE_NOINLINE int growOp3(Vdbe *p, int op, int p1, int p2, int p3){ - assert( p->nOpAlloc<=p->nOp ); - if( growOpArray(p, 1) ) return 1; - assert( p->nOpAlloc>p->nOp ); - return sqlite3VdbeAddOp3(p, op, p1, p2, p3); +SQLITE_PRIVATE int sqlite3VdbeAddOp0(Vdbe *p, int op){ + return sqlite3VdbeAddOp3(p, op, 0, 0, 0); +} +SQLITE_PRIVATE int sqlite3VdbeAddOp1(Vdbe *p, int op, int p1){ + return sqlite3VdbeAddOp3(p, op, p1, 0, 0); +} +SQLITE_PRIVATE int sqlite3VdbeAddOp2(Vdbe *p, int op, int p1, int p2){ + return sqlite3VdbeAddOp3(p, op, p1, p2, 0); } SQLITE_PRIVATE int sqlite3VdbeAddOp3(Vdbe *p, int op, int p1, int p2, int p3){ int i; VdbeOp *pOp; i = p->nOp; - assert( p->magic==VDBE_MAGIC_INIT ); + assert( p->eVdbeState==VDBE_INIT_STATE ); assert( op>=0 && op<0xff ); if( p->nOpAlloc<=i ){ return growOp3(p, op, p1, p2, p3); } + assert( p->aOp!=0 ); p->nOp++; pOp = &p->aOp[i]; + assert( pOp!=0 ); pOp->opcode = (u8)op; pOp->p5 = 0; pOp->p1 = p1; @@ -76547,32 +88164,78 @@ SQLITE_PRIVATE int sqlite3VdbeAddOp3(Vdbe *p, int op, int p1, int p2, int p3){ pOp->p3 = p3; pOp->p4.p = 0; pOp->p4type = P4_NOTUSED; + + /* Replicate this logic in sqlite3VdbeAddOp4Int() + ** vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv */ #ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS pOp->zComment = 0; #endif +#if defined(SQLITE_ENABLE_STMT_SCANSTATUS) || defined(VDBE_PROFILE) + pOp->nExec = 0; + pOp->nCycle = 0; +#endif #ifdef SQLITE_DEBUG if( p->db->flags & SQLITE_VdbeAddopTrace ){ sqlite3VdbePrintOp(0, i, &p->aOp[i]); - test_addop_breakpoint(); + test_addop_breakpoint(i, &p->aOp[i]); } #endif -#ifdef VDBE_PROFILE - pOp->cycles = 0; - pOp->cnt = 0; -#endif #ifdef SQLITE_VDBE_COVERAGE pOp->iSrcLine = 0; #endif + /* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ** Replicate in sqlite3VdbeAddOp4Int() */ + return i; } -SQLITE_PRIVATE int sqlite3VdbeAddOp0(Vdbe *p, int op){ - return sqlite3VdbeAddOp3(p, op, 0, 0, 0); -} -SQLITE_PRIVATE int sqlite3VdbeAddOp1(Vdbe *p, int op, int p1){ - return sqlite3VdbeAddOp3(p, op, p1, 0, 0); -} -SQLITE_PRIVATE int sqlite3VdbeAddOp2(Vdbe *p, int op, int p1, int p2){ - return sqlite3VdbeAddOp3(p, op, p1, p2, 0); +SQLITE_PRIVATE int sqlite3VdbeAddOp4Int( + Vdbe *p, /* Add the opcode to this VM */ + int op, /* The new opcode */ + int p1, /* The P1 operand */ + int p2, /* The P2 operand */ + int p3, /* The P3 operand */ + int p4 /* The P4 operand as an integer */ +){ + int i; + VdbeOp *pOp; + + i = p->nOp; + if( p->nOpAlloc<=i ){ + return addOp4IntSlow(p, op, p1, p2, p3, p4); + } + p->nOp++; + pOp = &p->aOp[i]; + assert( pOp!=0 ); + pOp->opcode = (u8)op; + pOp->p5 = 0; + pOp->p1 = p1; + pOp->p2 = p2; + pOp->p3 = p3; + pOp->p4.i = p4; + pOp->p4type = P4_INT32; + + /* Replicate this logic in sqlite3VdbeAddOp3() + ** vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv */ +#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS + pOp->zComment = 0; +#endif +#if defined(SQLITE_ENABLE_STMT_SCANSTATUS) || defined(VDBE_PROFILE) + pOp->nExec = 0; + pOp->nCycle = 0; +#endif +#ifdef SQLITE_DEBUG + if( p->db->flags & SQLITE_VdbeAddopTrace ){ + sqlite3VdbePrintOp(0, i, &p->aOp[i]); + test_addop_breakpoint(i, &p->aOp[i]); + } +#endif +#ifdef SQLITE_VDBE_COVERAGE + pOp->iSrcLine = 0; +#endif + /* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ** Replicate in sqlite3VdbeAddOp3() */ + + return i; } /* Generate code for an unconditional jump to instruction iDest @@ -76636,6 +88299,48 @@ SQLITE_PRIVATE int sqlite3VdbeAddOp4( return addr; } +/* +** Add an OP_Function or OP_PureFunc opcode. +** +** The eCallCtx argument is information (typically taken from Expr.op2) +** that describes the calling context of the function. 0 means a general +** function call. NC_IsCheck means called by a check constraint, +** NC_IdxExpr means called as part of an index expression. NC_PartIdx +** means in the WHERE clause of a partial index. NC_GenCol means called +** while computing a generated column value. 0 is the usual case. +*/ +SQLITE_PRIVATE int sqlite3VdbeAddFunctionCall( + Parse *pParse, /* Parsing context */ + int p1, /* Constant argument mask */ + int p2, /* First argument register */ + int p3, /* Register into which results are written */ + int nArg, /* Number of argument */ + const FuncDef *pFunc, /* The function to be invoked */ + int eCallCtx /* Calling context */ +){ + Vdbe *v = pParse->pVdbe; + int addr; + sqlite3_context *pCtx; + assert( v ); + pCtx = sqlite3DbMallocRawNN(pParse->db, SZ_CONTEXT(nArg)); + if( pCtx==0 ){ + assert( pParse->db->mallocFailed ); + freeEphemeralFunction(pParse->db, (FuncDef*)pFunc); + return 0; + } + pCtx->pOut = 0; + pCtx->pFunc = (FuncDef*)pFunc; + pCtx->pVdbe = 0; + pCtx->isError = 0; + pCtx->argc = nArg; + pCtx->iOp = sqlite3VdbeCurrentAddr(v); + addr = sqlite3VdbeAddOp4(v, eCallCtx ? OP_PureFunc : OP_Function, + p1, p2, p3, (char*)pCtx, P4_FUNCCTX); + sqlite3VdbeChangeP5(v, eCallCtx & NC_SelfRef); + sqlite3MayAbort(pParse); + return addr; +} + /* ** Add an opcode that includes the p4 value with a P4_INT64 or ** P4_REAL type. @@ -76678,16 +88383,17 @@ SQLITE_PRIVATE void sqlite3ExplainBreakpoint(const char *z1, const char *z2){ #endif /* -** Add a new OP_ opcode. +** Add a new OP_Explain opcode. ** ** If the bPush flag is true, then make this opcode the parent for ** subsequent Explains until sqlite3VdbeExplainPop() is called. */ -SQLITE_PRIVATE void sqlite3VdbeExplain(Parse *pParse, u8 bPush, const char *zFmt, ...){ -#ifndef SQLITE_DEBUG +SQLITE_PRIVATE int sqlite3VdbeExplain(Parse *pParse, u8 bPush, const char *zFmt, ...){ + int addr = 0; +#if !defined(SQLITE_DEBUG) /* Always include the OP_Explain opcodes if SQLITE_DEBUG is defined. ** But omit them (for performance) during production builds */ - if( pParse->explain==2 ) + if( pParse->explain==2 || IS_STMT_SCANSTATUS(pParse->db) ) #endif { char *zMsg; @@ -76699,13 +88405,15 @@ SQLITE_PRIVATE void sqlite3VdbeExplain(Parse *pParse, u8 bPush, const char *zFmt va_end(ap); v = pParse->pVdbe; iThis = v->nOp; - sqlite3VdbeAddOp4(v, OP_Explain, iThis, pParse->addrExplain, 0, + addr = sqlite3VdbeAddOp4(v, OP_Explain, iThis, pParse->addrExplain, 0, zMsg, P4_DYNAMIC); - sqlite3ExplainBreakpoint(bPush?"PUSH":"", sqlite3VdbeGetOp(v,-1)->p4.z); + sqlite3ExplainBreakpoint(bPush?"PUSH":"", sqlite3VdbeGetLastOp(v)->p4.z); if( bPush){ pParse->addrExplain = iThis; } + sqlite3VdbeScanStatus(v, iThis, -1, -1, 0, 0); } + return addr; } /* @@ -76725,30 +88433,12 @@ SQLITE_PRIVATE void sqlite3VdbeExplainPop(Parse *pParse){ ** The zWhere string must have been obtained from sqlite3_malloc(). ** This routine will take ownership of the allocated memory. */ -SQLITE_PRIVATE void sqlite3VdbeAddParseSchemaOp(Vdbe *p, int iDb, char *zWhere){ +SQLITE_PRIVATE void sqlite3VdbeAddParseSchemaOp(Vdbe *p, int iDb, char *zWhere, u16 p5){ int j; sqlite3VdbeAddOp4(p, OP_ParseSchema, iDb, 0, 0, zWhere, P4_DYNAMIC); + sqlite3VdbeChangeP5(p, p5); for(j=0; jdb->nDb; j++) sqlite3VdbeUsesBtree(p, j); -} - -/* -** Add an opcode that includes the p4 value as an integer. -*/ -SQLITE_PRIVATE int sqlite3VdbeAddOp4Int( - Vdbe *p, /* Add the opcode to this VM */ - int op, /* The new opcode */ - int p1, /* The P1 operand */ - int p2, /* The P2 operand */ - int p3, /* The P3 operand */ - int p4 /* The P4 operand as an integer */ -){ - int addr = sqlite3VdbeAddOp3(p, op, p1, p2, p3); - if( p->db->mallocFailed==0 ){ - VdbeOp *pOp = &p->aOp[addr]; - pOp->p4type = P4_INT32; - pOp->p4.i = p4; - } - return addr; + sqlite3MayAbort(p->pParse); } /* Insert the end of a co-routine @@ -76811,6 +88501,9 @@ static SQLITE_NOINLINE void resizeResolveLabel(Parse *p, Vdbe *v, int j){ int i; for(i=p->nLabelAlloc; iaLabel[i] = -1; #endif + if( nNewSize>=100 && (nNewSize/100)>(p->nLabelAlloc/100) ){ + sqlite3ProgressCheck(p); + } p->nLabelAlloc = nNewSize; p->aLabel[j] = v->nOp; } @@ -76818,7 +88511,7 @@ static SQLITE_NOINLINE void resizeResolveLabel(Parse *p, Vdbe *v, int j){ SQLITE_PRIVATE void sqlite3VdbeResolveLabel(Vdbe *v, int x){ Parse *p = v->pParse; int j = ADDR(x); - assert( v->magic==VDBE_MAGIC_INIT ); + assert( v->eVdbeState==VDBE_INIT_STATE ); assert( j<-p->nLabel ); assert( j>=0 ); #ifdef SQLITE_DEBUG @@ -76838,33 +88531,39 @@ SQLITE_PRIVATE void sqlite3VdbeResolveLabel(Vdbe *v, int x){ ** Mark the VDBE as one that can only be run one time. */ SQLITE_PRIVATE void sqlite3VdbeRunOnlyOnce(Vdbe *p){ - p->runOnlyOnce = 1; + sqlite3VdbeAddOp2(p, OP_Expire, 1, 1); } /* -** Mark the VDBE as one that can only be run multiple times. +** Mark the VDBE as one that can be run multiple times. */ SQLITE_PRIVATE void sqlite3VdbeReusable(Vdbe *p){ - p->runOnlyOnce = 0; + int i; + for(i=1; ALWAYS(inOp); i++){ + if( ALWAYS(p->aOp[i].opcode==OP_Expire) ){ + p->aOp[1].opcode = OP_Noop; + break; + } + } } #ifdef SQLITE_DEBUG /* sqlite3AssertMayAbort() logic */ /* ** The following type and function are used to iterate through all opcodes -** in a Vdbe main program and each of the sub-programs (triggers) it may +** in a Vdbe main program and each of the sub-programs (triggers) it may ** invoke directly or indirectly. It should be used as follows: ** ** Op *pOp; ** VdbeOpIter sIter; ** ** memset(&sIter, 0, sizeof(sIter)); -** sIter.v = v; // v is of type Vdbe* +** sIter.v = v; // v is of type Vdbe* ** while( (pOp = opIterNext(&sIter)) ){ ** // Do something with pOp ** } ** sqlite3DbFree(v->db, sIter.apSub); -** +** */ typedef struct VdbeOpIter VdbeOpIter; struct VdbeOpIter { @@ -76897,9 +88596,9 @@ static Op *opIterNext(VdbeOpIter *p){ p->iSub++; p->iAddr = 0; } - + if( pRet->p4type==P4_SUBPROGRAM ){ - int nByte = (p->nSub+1)*sizeof(SubProgram*); + i64 nByte = (1+(u64)p->nSub)*sizeof(SubProgram*); int j; for(j=0; jnSub; j++){ if( p->apSub[j]==pRet->p4.pProgram ) break; @@ -76928,9 +88627,10 @@ static Op *opIterNext(VdbeOpIter *p){ ** * OP_HaltIfNull with P1=SQLITE_CONSTRAINT and P2=OE_Abort. ** * OP_Destroy ** * OP_VUpdate +** * OP_VCreate ** * OP_VRename ** * OP_FkCounter with P2==0 (immediate foreign key constraint) -** * OP_CreateBtree/BTREE_INTKEY and OP_InitCoroutine +** * OP_CreateBtree/BTREE_INTKEY and OP_InitCoroutine ** (for CREATE TABLE AS SELECT ...) ** ** Then check that the value of Parse.mayAbort is true if an @@ -76944,24 +88644,37 @@ SQLITE_PRIVATE int sqlite3VdbeAssertMayAbort(Vdbe *v, int mayAbort){ int hasAbort = 0; int hasFkCounter = 0; int hasCreateTable = 0; + int hasCreateIndex = 0; int hasInitCoroutine = 0; Op *pOp; VdbeOpIter sIter; + + if( v==0 ) return 0; memset(&sIter, 0, sizeof(sIter)); sIter.v = v; while( (pOp = opIterNext(&sIter))!=0 ){ int opcode = pOp->opcode; - if( opcode==OP_Destroy || opcode==OP_VUpdate || opcode==OP_VRename + if( opcode==OP_Destroy || opcode==OP_VUpdate || opcode==OP_VRename || opcode==OP_VDestroy - || (opcode==OP_Function0 && pOp->p4.pFunc->funcFlags&SQLITE_FUNC_INTERNAL) - || ((opcode==OP_Halt || opcode==OP_HaltIfNull) + || opcode==OP_VCreate + || opcode==OP_ParseSchema + || opcode==OP_Function || opcode==OP_PureFunc + || ((opcode==OP_Halt || opcode==OP_HaltIfNull) && ((pOp->p1)!=SQLITE_OK && pOp->p2==OE_Abort)) ){ hasAbort = 1; break; } if( opcode==OP_CreateBtree && pOp->p3==BTREE_INTKEY ) hasCreateTable = 1; + if( mayAbort ){ + /* hasCreateIndex may also be set for some DELETE statements that use + ** OP_Clear. So this routine may end up returning true in the case + ** where a "DELETE FROM tbl" has a statement-journal but does not + ** require one. This is not so bad - it is an inefficiency, not a bug. */ + if( opcode==OP_CreateBtree && pOp->p3==BTREE_BLOBKEY ) hasCreateIndex = 1; + if( opcode==OP_Clear ) hasCreateIndex = 1; + } if( opcode==OP_InitCoroutine ) hasInitCoroutine = 1; #ifndef SQLITE_OMIT_FOREIGN_KEY if( opcode==OP_FkCounter && pOp->p1==0 && pOp->p2==1 ){ @@ -76977,7 +88690,8 @@ SQLITE_PRIVATE int sqlite3VdbeAssertMayAbort(Vdbe *v, int mayAbort){ ** true for this case to prevent the assert() in the callers frame ** from failing. */ return ( v->db->mallocFailed || hasAbort==mayAbort || hasFkCounter - || (hasCreateTable && hasInitCoroutine) ); + || (hasCreateTable && hasInitCoroutine) || hasCreateIndex + ); } #endif /* SQLITE_DEBUG - the sqlite3AssertMayAbort() function */ @@ -77014,13 +88728,13 @@ SQLITE_PRIVATE void sqlite3VdbeAssertAbortable(Vdbe *p){ ** (1) For each jump instruction with a negative P2 value (a label) ** resolve the P2 value to an actual address. ** -** (2) Compute the maximum number of arguments used by any SQL function -** and store that value in *pMaxFuncArgs. +** (2) Compute the maximum number of arguments used by the xUpdate/xFilter +** methods of any virtual table and store that value in *pMaxVtabArgs. ** ** (3) Update the Vdbe.readOnly and Vdbe.bIsReader flags to accurately ** indicate what the prepared statement actually does. ** -** (4) Initialize the p4.xAdvance pointer on opcodes that use it. +** (4) (discontinued) ** ** (5) Reclaim the memory allocated for storing labels. ** @@ -77028,16 +88742,18 @@ SQLITE_PRIVATE void sqlite3VdbeAssertAbortable(Vdbe *p){ ** script numbers the opcodes correctly. Changes to this routine must be ** coordinated with changes to mkopcodeh.tcl. */ -static void resolveP2Values(Vdbe *p, int *pMaxFuncArgs){ - int nMaxArgs = *pMaxFuncArgs; +static void resolveP2Values(Vdbe *p, int *pMaxVtabArgs){ + int nMaxVtabArgs = *pMaxVtabArgs; Op *pOp; Parse *pParse = p->pParse; int *aLabel = pParse->aLabel; + + assert( pParse->db->mallocFailed==0 ); /* tag-20230419-1 */ p->readOnly = 1; p->bIsReader = 0; pOp = &p->aOp[p->nOp-1]; - while(1){ - + assert( p->aOp[0].opcode==OP_Init ); + while( 1 /* Loop terminates when it reaches the OP_Init opcode */ ){ /* Only JUMP opcodes and the short list of special opcodes in the switch ** below need to be considered. The mkopcodeh.tcl generator script groups ** all these opcodes together near the front of the opcode list. Skip @@ -77050,7 +88766,7 @@ static void resolveP2Values(Vdbe *p, int *pMaxFuncArgs){ switch( pOp->opcode ){ case OP_Transaction: { if( pOp->p2!=0 ) p->readOnly = 0; - /* fall thru */ + /* no break */ deliberate_fall_through } case OP_AutoCommit: case OP_Savepoint: { @@ -77066,37 +88782,27 @@ static void resolveP2Values(Vdbe *p, int *pMaxFuncArgs){ p->bIsReader = 1; break; } - case OP_Next: - case OP_SorterNext: { - pOp->p4.xAdvance = sqlite3BtreeNext; - pOp->p4type = P4_ADVANCE; - /* The code generator never codes any of these opcodes as a jump - ** to a label. They are always coded as a jump backwards to a - ** known address */ - assert( pOp->p2>=0 ); - break; - } - case OP_Prev: { - pOp->p4.xAdvance = sqlite3BtreePrevious; - pOp->p4type = P4_ADVANCE; - /* The code generator never codes any of these opcodes as a jump - ** to a label. They are always coded as a jump backwards to a - ** known address */ + case OP_Init: { assert( pOp->p2>=0 ); - break; + goto resolve_p2_values_loop_exit; } #ifndef SQLITE_OMIT_VIRTUALTABLE case OP_VUpdate: { - if( pOp->p2>nMaxArgs ) nMaxArgs = pOp->p2; + if( pOp->p2>nMaxVtabArgs ) nMaxVtabArgs = pOp->p2; break; } case OP_VFilter: { int n; + /* The instruction immediately prior to VFilter will be an + ** OP_Integer that sets the "argc" value for the VFilter. See + ** the code where OP_VFilter is generated at tag-20250207a. */ assert( (pOp - p->aOp) >= 3 ); assert( pOp[-1].opcode==OP_Integer ); + assert( pOp[-1].p2==pOp->p3+1 ); n = pOp[-1].p1; - if( n>nMaxArgs ) nMaxArgs = n; + if( n>nMaxVtabArgs ) nMaxVtabArgs = n; /* Fall through into the default case */ + /* no break */ deliberate_fall_through } #endif default: { @@ -77106,8 +88812,18 @@ static void resolveP2Values(Vdbe *p, int *pMaxFuncArgs){ ** have non-negative values for P2. */ assert( (sqlite3OpcodeProperty[pOp->opcode] & OPFLG_JUMP)!=0 ); assert( ADDR(pOp->p2)<-pParse->nLabel ); + assert( aLabel!=0 ); /* True because of tag-20230419-1 */ pOp->p2 = aLabel[ADDR(pOp->p2)]; } + + /* OPFLG_JUMP opcodes never have P2==0, though OPFLG_JUMP0 opcodes + ** might */ + assert( pOp->p2>0 + || (sqlite3OpcodeProperty[pOp->opcode] & OPFLG_JUMP0)!=0 ); + + /* Jumps never go off the end of the bytecode array */ + assert( pOp->p2nOp + || (sqlite3OpcodeProperty[pOp->opcode] & OPFLG_JUMP)==0 ); break; } } @@ -77116,21 +88832,112 @@ static void resolveP2Values(Vdbe *p, int *pMaxFuncArgs){ ** have non-negative values for P2. */ assert( (sqlite3OpcodeProperty[pOp->opcode]&OPFLG_JUMP)==0 || pOp->p2>=0); } - if( pOp==p->aOp ) break; + assert( pOp>p->aOp ); pOp--; } - sqlite3DbFree(p->db, pParse->aLabel); - pParse->aLabel = 0; +resolve_p2_values_loop_exit: + if( aLabel ){ + sqlite3DbNNFreeNN(p->db, pParse->aLabel); + pParse->aLabel = 0; + } pParse->nLabel = 0; - *pMaxFuncArgs = nMaxArgs; + *pMaxVtabArgs = nMaxVtabArgs; assert( p->bIsReader!=0 || DbMaskAllZero(p->btreeMask) ); } +#ifdef SQLITE_DEBUG +/* +** Check to see if a subroutine contains a jump to a location outside of +** the subroutine. If a jump outside the subroutine is detected, add code +** that will cause the program to halt with an error message. +** +** The subroutine consists of opcodes between iFirst and iLast. Jumps to +** locations within the subroutine are acceptable. iRetReg is a register +** that contains the return address. Jumps to outside the range of iFirst +** through iLast are also acceptable as long as the jump destination is +** an OP_Return to iReturnAddr. +** +** A jump to an unresolved label means that the jump destination will be +** beyond the current address. That is normally a jump to an early +** termination and is consider acceptable. +** +** This routine only runs during debug builds. The purpose is (of course) +** to detect invalid escapes out of a subroutine. The OP_Halt opcode +** is generated rather than an assert() or other error, so that ".eqp full" +** will still work to show the original bytecode, to aid in debugging. +*/ +SQLITE_PRIVATE void sqlite3VdbeNoJumpsOutsideSubrtn( + Vdbe *v, /* The byte-code program under construction */ + int iFirst, /* First opcode of the subroutine */ + int iLast, /* Last opcode of the subroutine */ + int iRetReg /* Subroutine return address register */ +){ + VdbeOp *pOp; + Parse *pParse; + int i; + sqlite3_str *pErr = 0; + assert( v!=0 ); + pParse = v->pParse; + assert( pParse!=0 ); + if( pParse->nErr ) return; + assert( iLast>=iFirst ); + assert( iLastnOp ); + pOp = &v->aOp[iFirst]; + for(i=iFirst; i<=iLast; i++, pOp++){ + if( (sqlite3OpcodeProperty[pOp->opcode] & OPFLG_JUMP)!=0 ){ + int iDest = pOp->p2; /* Jump destination */ + if( iDest==0 ) continue; + if( pOp->opcode==OP_Gosub ) continue; + if( pOp->p3==20230325 && pOp->opcode==OP_NotNull ){ + /* This is a deliberately taken illegal branch. tag-20230325-2 */ + continue; + } + if( iDest<0 ){ + int j = ADDR(iDest); + assert( j>=0 ); + if( j>=-pParse->nLabel || pParse->aLabel[j]<0 ){ + continue; + } + iDest = pParse->aLabel[j]; + } + if( iDestiLast ){ + int j = iDest; + for(; jnOp; j++){ + VdbeOp *pX = &v->aOp[j]; + if( pX->opcode==OP_Return ){ + if( pX->p1==iRetReg ) break; + continue; + } + if( pX->opcode==OP_Noop ) continue; + if( pX->opcode==OP_Explain ) continue; + if( pErr==0 ){ + pErr = sqlite3_str_new(0); + }else{ + sqlite3_str_appendchar(pErr, 1, '\n'); + } + sqlite3_str_appendf(pErr, + "Opcode at %d jumps to %d which is outside the " + "subroutine at %d..%d", + i, iDest, iFirst, iLast); + break; + } + } + } + } + if( pErr ){ + char *zErr = sqlite3_str_finish(pErr); + sqlite3VdbeAddOp4(v, OP_Halt, SQLITE_INTERNAL, OE_Abort, 0, zErr, 0); + sqlite3_free(zErr); + sqlite3MayAbort(pParse); + } +} +#endif /* SQLITE_DEBUG */ + /* ** Return the address of the next instruction to be inserted. */ SQLITE_PRIVATE int sqlite3VdbeCurrentAddr(Vdbe *p){ - assert( p->magic==VDBE_MAGIC_INIT ); + assert( p->eVdbeState==VDBE_INIT_STATE ); return p->nOp; } @@ -77178,12 +88985,12 @@ SQLITE_PRIVATE void sqlite3VdbeVerifyAbortable(Vdbe *p, int onError){ /* ** This function returns a pointer to the array of opcodes associated with ** the Vdbe passed as the first argument. It is the callers responsibility -** to arrange for the returned array to be eventually freed using the +** to arrange for the returned array to be eventually freed using the ** vdbeFreeOpArray() function. ** ** Before returning, *pnOp is set to the number of entries in the returned -** array. Also, *pnMaxArg is set to the larger of its current value and -** the number of entries in the Vdbe.apArg[] array required to execute the +** array. Also, *pnMaxArg is set to the larger of its current value and +** the number of entries in the Vdbe.apArg[] array required to execute the ** returned program. */ SQLITE_PRIVATE VdbeOp *sqlite3VdbeTakeOpArray(Vdbe *p, int *pnOp, int *pnMaxArg){ @@ -77215,7 +89022,7 @@ SQLITE_PRIVATE VdbeOp *sqlite3VdbeAddOpList( int i; VdbeOp *pOut, *pFirst; assert( nOp>0 ); - assert( p->magic==VDBE_MAGIC_INIT ); + assert( p->eVdbeState==VDBE_INIT_STATE ); if( p->nOp + nOp > p->nOpAlloc && growOpArray(p, nOp) ){ return 0; } @@ -77257,41 +89064,108 @@ SQLITE_PRIVATE VdbeOp *sqlite3VdbeAddOpList( SQLITE_PRIVATE void sqlite3VdbeScanStatus( Vdbe *p, /* VM to add scanstatus() to */ int addrExplain, /* Address of OP_Explain (or 0) */ - int addrLoop, /* Address of loop counter */ + int addrLoop, /* Address of loop counter */ int addrVisit, /* Address of rows visited counter */ LogEst nEst, /* Estimated number of output rows */ const char *zName /* Name of table or index being scanned */ ){ - sqlite3_int64 nByte = (p->nScan+1) * sizeof(ScanStatus); - ScanStatus *aNew; - aNew = (ScanStatus*)sqlite3DbRealloc(p->db, p->aScan, nByte); - if( aNew ){ - ScanStatus *pNew = &aNew[p->nScan++]; - pNew->addrExplain = addrExplain; - pNew->addrLoop = addrLoop; - pNew->addrVisit = addrVisit; - pNew->nEst = nEst; - pNew->zName = sqlite3DbStrDup(p->db, zName); - p->aScan = aNew; + if( IS_STMT_SCANSTATUS(p->db) ){ + i64 nByte = (1+(i64)p->nScan) * sizeof(ScanStatus); + ScanStatus *aNew; + aNew = (ScanStatus*)sqlite3DbRealloc(p->db, p->aScan, nByte); + if( aNew ){ + ScanStatus *pNew = &aNew[p->nScan++]; + memset(pNew, 0, sizeof(ScanStatus)); + pNew->addrExplain = addrExplain; + pNew->addrLoop = addrLoop; + pNew->addrVisit = addrVisit; + pNew->nEst = nEst; + pNew->zName = sqlite3DbStrDup(p->db, zName); + p->aScan = aNew; + } + } +} + +/* +** Add the range of instructions from addrStart to addrEnd (inclusive) to +** the set of those corresponding to the sqlite3_stmt_scanstatus() counters +** associated with the OP_Explain instruction at addrExplain. The +** sum of the sqlite3Hwtime() values for each of these instructions +** will be returned for SQLITE_SCANSTAT_NCYCLE requests. +*/ +SQLITE_PRIVATE void sqlite3VdbeScanStatusRange( + Vdbe *p, + int addrExplain, + int addrStart, + int addrEnd +){ + if( IS_STMT_SCANSTATUS(p->db) ){ + ScanStatus *pScan = 0; + int ii; + for(ii=p->nScan-1; ii>=0; ii--){ + pScan = &p->aScan[ii]; + if( pScan->addrExplain==addrExplain ) break; + pScan = 0; + } + if( pScan ){ + if( addrEnd<0 ) addrEnd = sqlite3VdbeCurrentAddr(p)-1; + for(ii=0; iiaAddrRange); ii+=2){ + if( pScan->aAddrRange[ii]==0 ){ + pScan->aAddrRange[ii] = addrStart; + pScan->aAddrRange[ii+1] = addrEnd; + break; + } + } + } } } -#endif + +/* +** Set the addresses for the SQLITE_SCANSTAT_NLOOP and SQLITE_SCANSTAT_NROW +** counters for the query element associated with the OP_Explain at +** addrExplain. +*/ +SQLITE_PRIVATE void sqlite3VdbeScanStatusCounters( + Vdbe *p, + int addrExplain, + int addrLoop, + int addrVisit +){ + if( IS_STMT_SCANSTATUS(p->db) ){ + ScanStatus *pScan = 0; + int ii; + for(ii=p->nScan-1; ii>=0; ii--){ + pScan = &p->aScan[ii]; + if( pScan->addrExplain==addrExplain ) break; + pScan = 0; + } + if( pScan ){ + if( addrLoop>0 ) pScan->addrLoop = addrLoop; + if( addrVisit>0 ) pScan->addrVisit = addrVisit; + } + } +} +#endif /* defined(SQLITE_ENABLE_STMT_SCANSTATUS) */ /* ** Change the value of the opcode, or P1, P2, P3, or P5 operands ** for a specific instruction. */ -SQLITE_PRIVATE void sqlite3VdbeChangeOpcode(Vdbe *p, u32 addr, u8 iNewOpcode){ +SQLITE_PRIVATE void sqlite3VdbeChangeOpcode(Vdbe *p, int addr, u8 iNewOpcode){ + assert( addr>=0 ); sqlite3VdbeGetOp(p,addr)->opcode = iNewOpcode; } -SQLITE_PRIVATE void sqlite3VdbeChangeP1(Vdbe *p, u32 addr, int val){ +SQLITE_PRIVATE void sqlite3VdbeChangeP1(Vdbe *p, int addr, int val){ + assert( addr>=0 ); sqlite3VdbeGetOp(p,addr)->p1 = val; } -SQLITE_PRIVATE void sqlite3VdbeChangeP2(Vdbe *p, u32 addr, int val){ +SQLITE_PRIVATE void sqlite3VdbeChangeP2(Vdbe *p, int addr, int val){ + assert( addr>=0 || p->db->mallocFailed ); sqlite3VdbeGetOp(p,addr)->p2 = val; } -SQLITE_PRIVATE void sqlite3VdbeChangeP3(Vdbe *p, u32 addr, int val){ +SQLITE_PRIVATE void sqlite3VdbeChangeP3(Vdbe *p, int addr, int val){ + assert( addr>=0 ); sqlite3VdbeGetOp(p,addr)->p3 = val; } SQLITE_PRIVATE void sqlite3VdbeChangeP5(Vdbe *p, u16 p5){ @@ -77299,6 +89173,21 @@ SQLITE_PRIVATE void sqlite3VdbeChangeP5(Vdbe *p, u16 p5){ if( p->nOp>0 ) p->aOp[p->nOp-1].p5 = p5; } +/* +** If the previous opcode is an OP_Column that delivers results +** into register iDest, then add the OPFLAG_TYPEOFARG flag to that +** opcode. +*/ +SQLITE_PRIVATE void sqlite3VdbeTypeofColumn(Vdbe *p, int iDest){ + VdbeOp *pOp = sqlite3VdbeGetLastOp(p); +#ifdef SQLITE_DEBUG + while( pOp->opcode==OP_ReleaseReg ) pOp--; +#endif + if( pOp->p3==iDest && pOp->opcode==OP_Column ){ + pOp->p5 |= OPFLAG_TYPEOFARG; + } +} + /* ** Change the P2 operand of instruction addr so that it points to ** the address of the next instruction to be coded. @@ -77307,29 +89196,57 @@ SQLITE_PRIVATE void sqlite3VdbeJumpHere(Vdbe *p, int addr){ sqlite3VdbeChangeP2(p, addr, p->nOp); } +/* +** Change the P2 operand of the jump instruction at addr so that +** the jump lands on the next opcode. Or if the jump instruction was +** the previous opcode (and is thus a no-op) then simply back up +** the next instruction counter by one slot so that the jump is +** overwritten by the next inserted opcode. +** +** This routine is an optimization of sqlite3VdbeJumpHere() that +** strives to omit useless byte-code like this: +** +** 7 Once 0 8 0 +** 8 ... +*/ +SQLITE_PRIVATE void sqlite3VdbeJumpHereOrPopInst(Vdbe *p, int addr){ + if( addr==p->nOp-1 ){ + assert( p->aOp[addr].opcode==OP_Once + || p->aOp[addr].opcode==OP_If + || p->aOp[addr].opcode==OP_FkIfZero ); + assert( p->aOp[addr].p4type==0 ); +#ifdef SQLITE_VDBE_COVERAGE + sqlite3VdbeGetLastOp(p)->iSrcLine = 0; /* Erase VdbeCoverage() macros */ +#endif + p->nOp--; + }else{ + sqlite3VdbeChangeP2(p, addr, p->nOp); + } +} + /* ** If the input FuncDef structure is ephemeral, then free it. If -** the FuncDef is not ephermal, then do nothing. +** the FuncDef is not ephemeral, then do nothing. */ static void freeEphemeralFunction(sqlite3 *db, FuncDef *pDef){ + assert( db!=0 ); if( (pDef->funcFlags & SQLITE_FUNC_EPHEM)!=0 ){ - sqlite3DbFreeNN(db, pDef); + sqlite3DbNNFreeNN(db, pDef); } } -static void vdbeFreeOpArray(sqlite3 *, Op *, int); - /* ** Delete a P4 value if necessary. */ static SQLITE_NOINLINE void freeP4Mem(sqlite3 *db, Mem *p){ if( p->szMalloc ) sqlite3DbFree(db, p->zMalloc); - sqlite3DbFreeNN(db, p); + sqlite3DbNNFreeNN(db, p); } static SQLITE_NOINLINE void freeP4FuncCtx(sqlite3 *db, sqlite3_context *p){ + assert( db!=0 ); freeEphemeralFunction(db, p->pFunc); - sqlite3DbFreeNN(db, p); + sqlite3DbNNFreeNN(db, p); } static void freeP4(sqlite3 *db, int p4type, void *p4){ assert( db ); @@ -77341,9 +89258,8 @@ static void freeP4(sqlite3 *db, int p4type, void *p4){ case P4_REAL: case P4_INT64: case P4_DYNAMIC: - case P4_DYNBLOB: case P4_INTARRAY: { - sqlite3DbFree(db, p4); + if( p4 ) sqlite3DbNNFreeNN(db, p4); break; } case P4_KEYINFO: { @@ -77372,24 +89288,38 @@ static void freeP4(sqlite3 *db, int p4type, void *p4){ if( db->pnBytesFreed==0 ) sqlite3VtabUnlock((VTable *)p4); break; } + case P4_TABLEREF: { + if( db->pnBytesFreed==0 ) sqlite3DeleteTable(db, (Table*)p4); + break; + } + case P4_SUBRTNSIG: { + SubrtnSig *pSig = (SubrtnSig*)p4; + sqlite3DbFree(db, pSig->zAff); + sqlite3DbFree(db, pSig); + break; + } } } /* ** Free the space allocated for aOp and any p4 values allocated for the -** opcodes contained within. If aOp is not NULL it is assumed to contain -** nOp entries. +** opcodes contained within. If aOp is not NULL it is assumed to contain +** nOp entries. */ static void vdbeFreeOpArray(sqlite3 *db, Op *aOp, int nOp){ + assert( nOp>=0 ); + assert( db!=0 ); if( aOp ){ - Op *pOp; - for(pOp=&aOp[nOp-1]; pOp>=aOp; pOp--){ + Op *pOp = &aOp[nOp-1]; + while(1){ /* Exit via break */ if( pOp->p4type <= P4_FREE_IF_LE ) freeP4(db, pOp->p4type, pOp->p4.p); #ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS sqlite3DbFree(db, pOp->zComment); -#endif +#endif + if( pOp==aOp ) break; + pOp--; } - sqlite3DbFreeNN(db, aOp); + sqlite3DbNNFreeNN(db, aOp); } } @@ -77403,6 +89333,13 @@ SQLITE_PRIVATE void sqlite3VdbeLinkSubProgram(Vdbe *pVdbe, SubProgram *p){ pVdbe->pProgram = p; } +/* +** Return true if the given Vdbe has any SubPrograms. +*/ +SQLITE_PRIVATE int sqlite3VdbeHasSubProgram(Vdbe *pVdbe){ + return pVdbe->pProgram!=0; +} + /* ** Change the opcode at addr into OP_Noop */ @@ -77430,6 +89367,40 @@ SQLITE_PRIVATE int sqlite3VdbeDeletePriorOpcode(Vdbe *p, u8 op){ } } +#ifdef SQLITE_DEBUG +/* +** Generate an OP_ReleaseReg opcode to indicate that a range of +** registers, except any identified by mask, are no longer in use. +*/ +SQLITE_PRIVATE void sqlite3VdbeReleaseRegisters( + Parse *pParse, /* Parsing context */ + int iFirst, /* Index of first register to be released */ + int N, /* Number of registers to release */ + u32 mask, /* Mask of registers to NOT release */ + int bUndefine /* If true, mark registers as undefined */ +){ + if( N==0 || OptimizationDisabled(pParse->db, SQLITE_ReleaseReg) ) return; + assert( pParse->pVdbe ); + assert( iFirst>=1 ); + assert( iFirst+N-1<=pParse->nMem ); + if( N<=31 && mask!=0 ){ + while( N>0 && (mask&1)!=0 ){ + mask >>= 1; + iFirst++; + N--; + } + while( N>0 && N<=32 && (mask & MASKBIT32(N-1))!=0 ){ + mask &= ~MASKBIT32(N-1); + N--; + } + } + if( N>0 ){ + sqlite3VdbeAddOp3(pParse->pVdbe, OP_ReleaseReg, iFirst, N, *(int*)&mask); + if( bUndefine ) sqlite3VdbeChangeP5(pParse->pVdbe, 1); + } +} +#endif /* SQLITE_DEBUG */ + /* ** Change the value of the P4 operand for a specific instruction. ** This routine is useful when a large program is loaded from a @@ -77440,7 +89411,7 @@ SQLITE_PRIVATE int sqlite3VdbeDeletePriorOpcode(Vdbe *p, u8 op){ ** the string is made into memory obtained from sqlite3_malloc(). ** A value of n==0 means copy bytes of zP4 up to and including the ** first null byte. If n>0 then copy n+1 bytes of zP4. -** +** ** Other values of n (P4_STATIC, P4_COLLSEQ etc.) indicate that zP4 points ** to a string or structure that is guaranteed to exist for the lifetime of ** the Vdbe. In these cases we can just copy the pointer. @@ -77454,7 +89425,7 @@ static void SQLITE_NOINLINE vdbeChangeP4Full( int n ){ if( pOp->p4type ){ - freeP4(p->db, pOp->p4type, pOp->p4.p); + assert( pOp->p4type > P4_FREE_IF_LE ); pOp->p4type = 0; pOp->p4.p = 0; } @@ -77471,7 +89442,7 @@ SQLITE_PRIVATE void sqlite3VdbeChangeP4(Vdbe *p, int addr, const char *zP4, int sqlite3 *db; assert( p!=0 ); db = p->db; - assert( p->magic==VDBE_MAGIC_INIT ); + assert( p->eVdbeState==VDBE_INIT_STATE ); assert( p->aOp!=0 || db->mallocFailed ); if( db->mallocFailed ){ if( n!=P4_VTAB ) freeP4(db, n, (void*)*(char**)&zP4); @@ -77501,7 +89472,7 @@ SQLITE_PRIVATE void sqlite3VdbeChangeP4(Vdbe *p, int addr, const char *zP4, int } /* -** Change the P4 operand of the most recently coded instruction +** Change the P4 operand of the most recently coded instruction ** to the value defined by the arguments. This is a high-speed ** version of sqlite3VdbeChangeP4(). ** @@ -77516,7 +89487,7 @@ SQLITE_PRIVATE void sqlite3VdbeAppendP4(Vdbe *p, void *pP4, int n){ if( p->db->mallocFailed ){ freeP4(p->db, n, pP4); }else{ - assert( pP4!=0 ); + assert( pP4!=0 || n==P4_DYNAMIC ); assert( p->nOp>0 ); pOp = &p->aOp[p->nOp-1]; assert( pOp->p4type==P4_NOTUSED ); @@ -77547,7 +89518,7 @@ SQLITE_PRIVATE void sqlite3VdbeSetP4KeyInfo(Parse *pParse, Index *pIdx){ */ static void vdbeVComment(Vdbe *p, const char *zFormat, va_list ap){ assert( p->nOp>0 || p->aOp==0 ); - assert( p->aOp==0 || p->aOp[p->nOp-1].zComment==0 || p->db->mallocFailed ); + assert( p->aOp==0 || p->aOp[p->nOp-1].zComment==0 || p->pParse->nErr>0 ); if( p->nOp ){ assert( p->aOp ); sqlite3DbFree(p->db, p->aOp[p->nOp-1].zComment); @@ -77578,19 +89549,19 @@ SQLITE_PRIVATE void sqlite3VdbeNoopComment(Vdbe *p, const char *zFormat, ...){ ** Set the value if the iSrcLine field for the previously coded instruction. */ SQLITE_PRIVATE void sqlite3VdbeSetLineNumber(Vdbe *v, int iLine){ - sqlite3VdbeGetOp(v,-1)->iSrcLine = iLine; + sqlite3VdbeGetLastOp(v)->iSrcLine = iLine; } #endif /* SQLITE_VDBE_COVERAGE */ /* -** Return the opcode for a given address. If the address is -1, then -** return the most recently inserted opcode. +** Return the opcode for a given address. The address must be non-negative. +** See sqlite3VdbeGetLastOp() to get the most recently added opcode. ** ** If a memory allocation error has occurred prior to the calling of this ** routine, then a pointer to a dummy VdbeOp will be returned. That opcode ** is readable but not writable, though it is cast to a writable value. ** The return of a dummy opcode allows the call to continue functioning -** after an OOM fault without having to check to see if the return from +** after an OOM fault without having to check to see if the return from ** this routine is a valid pointer. But because the dummy.opcode is 0, ** dummy will never be written to. This is verified by code inspection and ** by running with Valgrind. @@ -77599,10 +89570,7 @@ SQLITE_PRIVATE VdbeOp *sqlite3VdbeGetOp(Vdbe *p, int addr){ /* C89 specifies that the constant "dummy" will be initialized to all ** zeros, which is correct. MSVC generates a warning, nevertheless. */ static VdbeOp dummy; /* Ignore the MSVC warning about no initializer */ - assert( p->magic==VDBE_MAGIC_INIT ); - if( addr<0 ){ - addr = p->nOp - 1; - } + assert( p->eVdbeState==VDBE_INIT_STATE ); assert( (addr>=0 && addrnOp) || p->db->mallocFailed ); if( p->db->mallocFailed ){ return (VdbeOp*)&dummy; @@ -77611,6 +89579,12 @@ SQLITE_PRIVATE VdbeOp *sqlite3VdbeGetOp(Vdbe *p, int addr){ } } +/* Return the most recently added opcode +*/ +SQLITE_PRIVATE VdbeOp *sqlite3VdbeGetLastOp(Vdbe *p){ + return sqlite3VdbeGetOp(p, p->nOp - 1); +} + #if defined(SQLITE_ENABLE_EXPLAIN_COMMENTS) /* ** Return an integer value for one of the parameters to the opcode pOp @@ -77637,78 +89611,90 @@ static int translateP(char c, const Op *pOp){ ** "PX@PY+1" -> "r[X..X+Y]" or "r[x]" if y is 0 ** "PY..PY" -> "r[X..Y]" or "r[x]" if y<=x */ -static int displayComment( +SQLITE_PRIVATE char *sqlite3VdbeDisplayComment( + sqlite3 *db, /* Optional - Oom error reporting only */ const Op *pOp, /* The opcode to be commented */ - const char *zP4, /* Previously obtained value for P4 */ - char *zTemp, /* Write result here */ - int nTemp /* Space available in zTemp[] */ + const char *zP4 /* Previously obtained value for P4 */ ){ const char *zOpName; const char *zSynopsis; int nOpName; - int ii, jj; + int ii; char zAlt[50]; + StrAccum x; + + sqlite3StrAccumInit(&x, 0, 0, 0, SQLITE_MAX_LENGTH); zOpName = sqlite3OpcodeName(pOp->opcode); nOpName = sqlite3Strlen30(zOpName); if( zOpName[nOpName+1] ){ int seenCom = 0; char c; - zSynopsis = zOpName += nOpName + 1; + zSynopsis = zOpName + nOpName + 1; if( strncmp(zSynopsis,"IF ",3)==0 ){ - if( pOp->p5 & SQLITE_STOREP2 ){ - sqlite3_snprintf(sizeof(zAlt), zAlt, "r[P2] = (%s)", zSynopsis+3); - }else{ - sqlite3_snprintf(sizeof(zAlt), zAlt, "if %s goto P2", zSynopsis+3); - } + sqlite3_snprintf(sizeof(zAlt), zAlt, "if %s goto P2", zSynopsis+3); zSynopsis = zAlt; } - for(ii=jj=0; jjzComment); - seenCom = 1; + if( pOp->zComment && pOp->zComment[0] ){ + sqlite3_str_appendall(&x, pOp->zComment); + seenCom = 1; + break; + } }else{ int v1 = translateP(c, pOp); int v2; - sqlite3_snprintf(nTemp-jj, zTemp+jj, "%d", v1); if( strncmp(zSynopsis+ii+1, "@P", 2)==0 ){ ii += 3; - jj += sqlite3Strlen30(zTemp+jj); v2 = translateP(zSynopsis[ii], pOp); if( strncmp(zSynopsis+ii+1,"+1",2)==0 ){ ii += 2; v2++; } - if( v2>1 ){ - sqlite3_snprintf(nTemp-jj, zTemp+jj, "..%d", v1+v2-1); + if( v2<2 ){ + sqlite3_str_appendf(&x, "%d", v1); + }else{ + sqlite3_str_appendf(&x, "%d..%d", v1, v1+v2-1); + } + }else if( strncmp(zSynopsis+ii+1, "@NP", 3)==0 ){ + sqlite3_context *pCtx = pOp->p4.pCtx; + if( pOp->p4type!=P4_FUNCCTX || pCtx->argc==1 ){ + sqlite3_str_appendf(&x, "%d", v1); + }else if( pCtx->argc>1 ){ + sqlite3_str_appendf(&x, "%d..%d", v1, v1+pCtx->argc-1); + }else if( x.accError==0 ){ + assert( x.nChar>2 ); + x.nChar -= 2; + ii++; + } + ii += 3; + }else{ + sqlite3_str_appendf(&x, "%d", v1); + if( strncmp(zSynopsis+ii+1, "..P3", 4)==0 && pOp->p3==0 ){ + ii += 4; } - }else if( strncmp(zSynopsis+ii+1, "..P3", 4)==0 && pOp->p3==0 ){ - ii += 4; } } - jj += sqlite3Strlen30(zTemp+jj); }else{ - zTemp[jj++] = c; + sqlite3_str_appendchar(&x, 1, c); } } - if( !seenCom && jjzComment ){ - sqlite3_snprintf(nTemp-jj, zTemp+jj, "; %s", pOp->zComment); - jj += sqlite3Strlen30(zTemp+jj); + if( !seenCom && pOp->zComment ){ + sqlite3_str_appendf(&x, "; %s", pOp->zComment); } - if( jjzComment ){ - sqlite3_snprintf(nTemp, zTemp, "%s", pOp->zComment); - jj = sqlite3Strlen30(zTemp); - }else{ - zTemp[0] = 0; - jj = 0; + sqlite3_str_appendall(&x, pOp->zComment); + } + if( (x.accError & SQLITE_NOMEM)!=0 && db!=0 ){ + sqlite3OomFault(db); } - return jj; + return sqlite3StrAccumFinish(&x); } -#endif /* SQLITE_DEBUG */ +#endif /* SQLITE_ENABLE_EXPLAIN_COMMENTS */ #if VDBE_DISPLAY_P4 && defined(SQLITE_ENABLE_CURSOR_HINTS) /* @@ -77719,6 +89705,7 @@ static void displayP4Expr(StrAccum *p, Expr *pExpr){ const char *zOp = 0; switch( pExpr->op ){ case TK_STRING: + assert( !ExprHasProperty(pExpr, EP_IntValue) ); sqlite3_str_appendf(p, "%Q", pExpr->u.zToken); break; case TK_INTEGER: @@ -77789,23 +89776,25 @@ static void displayP4Expr(StrAccum *p, Expr *pExpr){ ** Compute a string that describes the P4 parameter for an opcode. ** Use zTemp for any required temporary buffer space. */ -static char *displayP4(Op *pOp, char *zTemp, int nTemp){ - char *zP4 = zTemp; +SQLITE_PRIVATE char *sqlite3VdbeDisplayP4(sqlite3 *db, Op *pOp){ + char *zP4 = 0; StrAccum x; - assert( nTemp>=20 ); - sqlite3StrAccumInit(&x, 0, zTemp, nTemp, 0); + + sqlite3StrAccumInit(&x, 0, 0, 0, SQLITE_MAX_LENGTH); switch( pOp->p4type ){ case P4_KEYINFO: { int j; KeyInfo *pKeyInfo = pOp->p4.pKeyInfo; - assert( pKeyInfo->aSortOrder!=0 ); + assert( pKeyInfo->aSortFlags!=0 ); sqlite3_str_appendf(&x, "k(%d", pKeyInfo->nKeyField); for(j=0; jnKeyField; j++){ CollSeq *pColl = pKeyInfo->aColl[j]; const char *zColl = pColl ? pColl->zName : ""; if( strcmp(zColl, "BINARY")==0 ) zColl = "B"; - sqlite3_str_appendf(&x, ",%s%s", - pKeyInfo->aSortOrder[j] ? "-" : "", zColl); + sqlite3_str_appendf(&x, ",%s%s%s", + (pKeyInfo->aSortFlags[j] & KEYINFO_ORDER_DESC) ? "-" : "", + (pKeyInfo->aSortFlags[j] & KEYINFO_ORDER_BIGNULL)? "N." : "", + zColl); } sqlite3_str_append(&x, ")", 1); break; @@ -77817,8 +89806,11 @@ static char *displayP4(Op *pOp, char *zTemp, int nTemp){ } #endif case P4_COLLSEQ: { + static const char *const encnames[] = {"?", "8", "16LE", "16BE"}; CollSeq *pColl = pOp->p4.pColl; - sqlite3_str_appendf(&x, "(%.20s)", pColl->zName); + assert( pColl->enc<4 ); + sqlite3_str_appendf(&x, "%.18s-%s", pColl->zName, + encnames[pColl->enc]); break; } case P4_FUNCDEF: { @@ -77826,13 +89818,11 @@ static char *displayP4(Op *pOp, char *zTemp, int nTemp){ sqlite3_str_appendf(&x, "%s(%d)", pDef->zName, pDef->nArg); break; } -#if defined(SQLITE_DEBUG) || defined(VDBE_PROFILE) case P4_FUNCCTX: { FuncDef *pDef = pOp->p4.pCtx->pFunc; sqlite3_str_appendf(&x, "%s(%d)", pDef->zName, pDef->nArg); break; } -#endif case P4_INT64: { sqlite3_str_appendf(&x, "%lld", *pOp->p4.pI64); break; @@ -77849,7 +89839,7 @@ static char *displayP4(Op *pOp, char *zTemp, int nTemp){ Mem *pMem = pOp->p4.pMem; if( pMem->flags & MEM_Str ){ zP4 = pMem->z; - }else if( pMem->flags & MEM_Int ){ + }else if( pMem->flags & (MEM_Int|MEM_IntReal) ){ sqlite3_str_appendf(&x, "%lld", pMem->u.i); }else if( pMem->flags & MEM_Real ){ sqlite3_str_appendf(&x, "%.16g", pMem->u.r); @@ -77869,41 +89859,42 @@ static char *displayP4(Op *pOp, char *zTemp, int nTemp){ } #endif case P4_INTARRAY: { - int i; - int *ai = pOp->p4.ai; - int n = ai[0]; /* The first element of an INTARRAY is always the + u32 i; + u32 *ai = pOp->p4.ai; + u32 n = ai[0]; /* The first element of an INTARRAY is always the ** count of the number of elements to follow */ for(i=1; i<=n; i++){ - sqlite3_str_appendf(&x, ",%d", ai[i]); + sqlite3_str_appendf(&x, "%c%u", (i==1 ? '[' : ','), ai[i]); } - zTemp[0] = '['; sqlite3_str_append(&x, "]", 1); break; } case P4_SUBPROGRAM: { - sqlite3_str_appendf(&x, "program"); + zP4 = "program"; break; } - case P4_DYNBLOB: - case P4_ADVANCE: { - zTemp[0] = 0; + case P4_TABLE: { + zP4 = pOp->p4.pTab->zName; break; } - case P4_TABLE: { - sqlite3_str_appendf(&x, "%s", pOp->p4.pTab->zName); + case P4_INDEX: { + zP4 = pOp->p4.pIdx->zName; + break; + } + case P4_SUBRTNSIG: { + SubrtnSig *pSig = pOp->p4.pSubrtnSig; + sqlite3_str_appendf(&x, "subrtnsig:%d,%s", pSig->selId, pSig->zAff); break; } default: { zP4 = pOp->p4.z; - if( zP4==0 ){ - zP4 = zTemp; - zTemp[0] = 0; - } } } - sqlite3StrAccumFinish(&x); - assert( zP4!=0 ); - return zP4; + if( zP4 ) sqlite3_str_appendall(&x, zP4); + if( (x.accError & SQLITE_NOMEM)!=0 ){ + sqlite3OomFault(db); + } + return sqlite3StrAccumFinish(&x); } #endif /* VDBE_DISPLAY_P4 */ @@ -77934,13 +89925,13 @@ SQLITE_PRIVATE void sqlite3VdbeUsesBtree(Vdbe *p, int i){ ** ** If SQLite is not threadsafe but does support shared-cache mode, then ** sqlite3BtreeEnter() is invoked to set the BtShared.db variables -** of all of BtShared structures accessible via the database handle +** of all of BtShared structures accessible via the database handle ** associated with the VM. ** ** If SQLite is not threadsafe and does not support shared-cache mode, this ** function is a no-op. ** -** The p->btreeMask field is a bitmask of all btrees that the prepared +** The p->btreeMask field is a bitmask of all btrees that the prepared ** statement p will ever use. Let N be the number of bits in p->btreeMask ** corresponding to btrees that use shared cache. Then the runtime of ** this routine is N*N. But as N is rarely more than 1, this should not @@ -77993,49 +89984,77 @@ SQLITE_PRIVATE void sqlite3VdbeLeave(Vdbe *p){ */ SQLITE_PRIVATE void sqlite3VdbePrintOp(FILE *pOut, int pc, VdbeOp *pOp){ char *zP4; - char zPtr[50]; - char zCom[100]; + char *zCom; + sqlite3 dummyDb; static const char *zFormat1 = "%4d %-13s %4d %4d %4d %-13s %.2X %s\n"; if( pOut==0 ) pOut = stdout; - zP4 = displayP4(pOp, zPtr, sizeof(zPtr)); + sqlite3BeginBenignMalloc(); + dummyDb.mallocFailed = 1; + zP4 = sqlite3VdbeDisplayP4(&dummyDb, pOp); #ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS - displayComment(pOp, zP4, zCom, sizeof(zCom)); + zCom = sqlite3VdbeDisplayComment(0, pOp, zP4); #else - zCom[0] = 0; + zCom = 0; #endif /* NB: The sqlite3OpcodeName() function is implemented by code created ** by the mkopcodeh.awk and mkopcodec.awk scripts which extract the ** information from the vdbe.c source text */ - fprintf(pOut, zFormat1, pc, - sqlite3OpcodeName(pOp->opcode), pOp->p1, pOp->p2, pOp->p3, zP4, pOp->p5, - zCom + fprintf(pOut, zFormat1, pc, + sqlite3OpcodeName(pOp->opcode), pOp->p1, pOp->p2, pOp->p3, + zP4 ? zP4 : "", pOp->p5, + zCom ? zCom : "" ); fflush(pOut); + sqlite3_free(zP4); + sqlite3_free(zCom); + sqlite3EndBenignMalloc(); } #endif /* ** Initialize an array of N Mem element. +** +** This is a high-runner, so only those fields that really do need to +** be initialized are set. The Mem structure is organized so that +** the fields that get initialized are nearby and hopefully on the same +** cache line. +** +** Mem.flags = flags +** Mem.db = db +** Mem.szMalloc = 0 +** +** All other fields of Mem can safely remain uninitialized for now. They +** will be initialized before use. */ static void initMemArray(Mem *p, int N, sqlite3 *db, u16 flags){ - while( (N--)>0 ){ - p->db = db; - p->flags = flags; - p->szMalloc = 0; + assert( db!=0 ); + if( N>0 ){ + do{ + p->flags = flags; + p->db = db; + p->szMalloc = 0; #ifdef SQLITE_DEBUG - p->pScopyFrom = 0; + p->pScopyFrom = 0; + p->bScopy = 0; #endif - p++; + p++; + }while( (--N)>0 ); } } /* -** Release an array of N Mem elements +** Release auxiliary memory held in an array of N Mem elements. +** +** After this routine returns, all Mem elements in the array will still +** be valid. Those Mem elements that were not holding auxiliary resources +** will be unchanged. Mem elements which had something freed will be +** set to MEM_Undefined. */ static void releaseMemArray(Mem *p, int N){ if( p && N ){ Mem *pEnd = &p[N]; sqlite3 *db = p->db; + assert( db!=0 ); if( db->pnBytesFreed ){ do{ if( p->szMalloc ) sqlite3DbFree(db, p->zMalloc); @@ -78047,28 +90066,33 @@ static void releaseMemArray(Mem *p, int N){ assert( sqlite3VdbeCheckMemInvariants(p) ); /* This block is really an inlined version of sqlite3VdbeMemRelease() - ** that takes advantage of the fact that the memory cell value is + ** that takes advantage of the fact that the memory cell value is ** being set to NULL after releasing any dynamic resources. ** - ** The justification for duplicating code is that according to - ** callgrind, this causes a certain test case to hit the CPU 4.7 - ** percent less (x86 linux, gcc version 4.1.2, -O6) than if + ** The justification for duplicating code is that according to + ** callgrind, this causes a certain test case to hit the CPU 4.7 + ** percent less (x86 linux, gcc version 4.1.2, -O6) than if ** sqlite3MemRelease() were called from here. With -O2, this jumps - ** to 6.6 percent. The test case is inserting 1000 rows into a table - ** with no indexes using a single prepared INSERT statement, bind() + ** to 6.6 percent. The test case is inserting 1000 rows into a table + ** with no indexes using a single prepared INSERT statement, bind() ** and reset(). Inserts are grouped into a transaction. */ testcase( p->flags & MEM_Agg ); testcase( p->flags & MEM_Dyn ); - testcase( p->xDel==sqlite3VdbeFrameMemDel ); if( p->flags&(MEM_Agg|MEM_Dyn) ){ + testcase( (p->flags & MEM_Dyn)!=0 && p->xDel==sqlite3VdbeFrameMemDel ); sqlite3VdbeMemRelease(p); + p->flags = MEM_Undefined; }else if( p->szMalloc ){ - sqlite3DbFreeNN(db, p->zMalloc); + sqlite3DbNNFreeNN(db, p->zMalloc); p->szMalloc = 0; + p->flags = MEM_Undefined; } - - p->flags = MEM_Undefined; +#ifdef SQLITE_DEBUG + else{ + p->flags = MEM_Undefined; + } +#endif }while( (++p)v->pDelFrame = pFrame; } +#if defined(SQLITE_ENABLE_BYTECODE_VTAB) || !defined(SQLITE_OMIT_EXPLAIN) +/* +** Locate the next opcode to be displayed in EXPLAIN or EXPLAIN +** QUERY PLAN output. +** +** Return SQLITE_ROW on success. Return SQLITE_DONE if there are no +** more opcodes to be displayed. +*/ +SQLITE_PRIVATE int sqlite3VdbeNextOpcode( + Vdbe *p, /* The statement being explained */ + Mem *pSub, /* Storage for keeping track of subprogram nesting */ + int eMode, /* 0: normal. 1: EQP. 2: TablesUsed */ + int *piPc, /* IN/OUT: Current rowid. Overwritten with next rowid */ + int *piAddr, /* OUT: Write index into (*paOp)[] here */ + Op **paOp /* OUT: Write the opcode array here */ +){ + int nRow; /* Stop when row count reaches this */ + int nSub = 0; /* Number of sub-vdbes seen so far */ + SubProgram **apSub = 0; /* Array of sub-vdbes */ + int i; /* Next instruction address */ + int rc = SQLITE_OK; /* Result code */ + Op *aOp = 0; /* Opcode array */ + int iPc; /* Rowid. Copy of value in *piPc */ + + /* When the number of output rows reaches nRow, that means the + ** listing has finished and sqlite3_step() should return SQLITE_DONE. + ** nRow is the sum of the number of rows in the main program, plus + ** the sum of the number of rows in all trigger subprograms encountered + ** so far. The nRow value will increase as new trigger subprograms are + ** encountered, but p->pc will eventually catch up to nRow. + */ + nRow = p->nOp; + if( pSub!=0 ){ + if( pSub->flags&MEM_Blob ){ + /* pSub is initiallly NULL. It is initialized to a BLOB by + ** the P4_SUBPROGRAM processing logic below */ + nSub = pSub->n/sizeof(Vdbe*); + apSub = (SubProgram **)pSub->z; + } + for(i=0; inOp; + } + } + iPc = *piPc; + while(1){ /* Loop exits via break */ + i = iPc++; + if( i>=nRow ){ + p->rc = SQLITE_OK; + rc = SQLITE_DONE; + break; + } + if( inOp ){ + /* The rowid is small enough that we are still in the + ** main program. */ + aOp = p->aOp; + }else{ + /* We are currently listing subprograms. Figure out which one and + ** pick up the appropriate opcode. */ + int j; + i -= p->nOp; + assert( apSub!=0 ); + assert( nSub>0 ); + for(j=0; i>=apSub[j]->nOp; j++){ + i -= apSub[j]->nOp; + assert( inOp || j+1aOp; + } + + /* When an OP_Program opcode is encounter (the only opcode that has + ** a P4_SUBPROGRAM argument), expand the size of the array of subprograms + ** kept in p->aMem[9].z to hold the new program - assuming this subprogram + ** has not already been seen. + */ + if( pSub!=0 && aOp[i].p4type==P4_SUBPROGRAM ){ + int nByte = (nSub+1)*sizeof(SubProgram*); + int j; + for(j=0; jrc = sqlite3VdbeMemGrow(pSub, nByte, nSub!=0); + if( p->rc!=SQLITE_OK ){ + rc = SQLITE_ERROR; + break; + } + apSub = (SubProgram **)pSub->z; + apSub[nSub++] = aOp[i].p4.pProgram; + MemSetTypeFlag(pSub, MEM_Blob); + pSub->n = nSub*sizeof(SubProgram*); + nRow += aOp[i].p4.pProgram->nOp; + } + } + if( eMode==0 ) break; +#ifdef SQLITE_ENABLE_BYTECODE_VTAB + if( eMode==2 ){ + Op *pOp = aOp + i; + if( pOp->opcode==OP_OpenRead ) break; + if( pOp->opcode==OP_OpenWrite && (pOp->p5 & OPFLAG_P2ISREG)==0 ) break; + if( pOp->opcode==OP_ReopenIdx ) break; + }else +#endif + { + assert( eMode==1 ); + if( aOp[i].opcode==OP_Explain ) break; + if( aOp[i].opcode==OP_Init && iPc>1 ) break; + } + } + *piPc = iPc; + *piAddr = i; + *paOp = aOp; + return rc; +} +#endif /* SQLITE_ENABLE_BYTECODE_VTAB || !SQLITE_OMIT_EXPLAIN */ + /* ** Delete a VdbeFrame object and its contents. VdbeFrame objects are @@ -78112,7 +90251,7 @@ SQLITE_PRIVATE void sqlite3VdbeFrameDelete(VdbeFrame *p){ VdbeCursor **apCsr = (VdbeCursor **)&aMem[p->nChildMem]; assert( sqlite3VdbeFrameIsValid(p) ); for(i=0; inChildCsr; i++){ - sqlite3VdbeFreeCursor(p->v, apCsr[i]); + if( apCsr[i] ) sqlite3VdbeFreeCursorNN(p->v, apCsr[i]); } releaseMemArray(aMem, p->nChildMem); sqlite3VdbeDeleteAuxData(p->v->db, &p->pAuxData, -1, 0); @@ -78141,19 +90280,17 @@ SQLITE_PRIVATE void sqlite3VdbeFrameDelete(VdbeFrame *p){ SQLITE_PRIVATE int sqlite3VdbeList( Vdbe *p /* The VDBE */ ){ - int nRow; /* Stop when row count reaches this */ - int nSub = 0; /* Number of sub-vdbes seen so far */ - SubProgram **apSub = 0; /* Array of sub-vdbes */ Mem *pSub = 0; /* Memory cell hold array of subprogs */ sqlite3 *db = p->db; /* The database connection */ int i; /* Loop counter */ int rc = SQLITE_OK; /* Return code */ Mem *pMem = &p->aMem[1]; /* First Mem of result set */ int bListSubprogs = (p->explain==1 || (db->flags & SQLITE_TriggerEQP)!=0); - Op *pOp = 0; + Op *aOp; /* Array of opcodes */ + Op *pOp; /* Current opcode */ assert( p->explain ); - assert( p->magic==VDBE_MAGIC_RUN ); + assert( p->eVdbeState==VDBE_RUN_STATE ); assert( p->rc==SQLITE_OK || p->rc==SQLITE_BUSY || p->rc==SQLITE_NOMEM ); /* Even though this opcode does not use dynamic strings for @@ -78161,7 +90298,6 @@ SQLITE_PRIVATE int sqlite3VdbeList( ** sqlite3_column_text16(), causing a translation to UTF-16 encoding. */ releaseMemArray(pMem, 8); - p->pResultSet = 0; if( p->rc==SQLITE_NOMEM ){ /* This happens if a malloc() inside a call to sqlite3_column_text() or @@ -78170,14 +90306,6 @@ SQLITE_PRIVATE int sqlite3VdbeList( return SQLITE_ERROR; } - /* When the number of output rows reaches nRow, that means the - ** listing has finished and sqlite3_step() should return SQLITE_DONE. - ** nRow is the sum of the number of rows in the main program, plus - ** the sum of the number of rows in all trigger subprograms encountered - ** so far. The nRow value will increase as new trigger subprograms are - ** encountered, but p->pc will eventually catch up to nRow. - */ - nRow = p->nOp; if( bListSubprogs ){ /* The first 8 memory cells are used for the result set. So we will ** commandeer the 9th cell to use as storage for an array of pointers @@ -78185,144 +90313,55 @@ SQLITE_PRIVATE int sqlite3VdbeList( ** cells. */ assert( p->nMem>9 ); pSub = &p->aMem[9]; - if( pSub->flags&MEM_Blob ){ - /* On the first call to sqlite3_step(), pSub will hold a NULL. It is - ** initialized to a BLOB by the P4_SUBPROGRAM processing logic below */ - nSub = pSub->n/sizeof(Vdbe*); - apSub = (SubProgram **)pSub->z; - } - for(i=0; inOp; - } + }else{ + pSub = 0; } - while(1){ /* Loop exits via break */ - i = p->pc++; - if( i>=nRow ){ - p->rc = SQLITE_OK; - rc = SQLITE_DONE; - break; - } - if( inOp ){ - /* The output line number is small enough that we are still in the - ** main program. */ - pOp = &p->aOp[i]; - }else{ - /* We are currently listing subprograms. Figure out which one and - ** pick up the appropriate opcode. */ - int j; - i -= p->nOp; - for(j=0; i>=apSub[j]->nOp; j++){ - i -= apSub[j]->nOp; - } - pOp = &apSub[j]->aOp[i]; - } - - /* When an OP_Program opcode is encounter (the only opcode that has - ** a P4_SUBPROGRAM argument), expand the size of the array of subprograms - ** kept in p->aMem[9].z to hold the new program - assuming this subprogram - ** has not already been seen. - */ - if( bListSubprogs && pOp->p4type==P4_SUBPROGRAM ){ - int nByte = (nSub+1)*sizeof(SubProgram*); - int j; - for(j=0; jp4.pProgram ) break; - } - if( j==nSub ){ - p->rc = sqlite3VdbeMemGrow(pSub, nByte, nSub!=0); - if( p->rc!=SQLITE_OK ){ - rc = SQLITE_ERROR; - break; - } - apSub = (SubProgram **)pSub->z; - apSub[nSub++] = pOp->p4.pProgram; - pSub->flags |= MEM_Blob; - pSub->n = nSub*sizeof(SubProgram*); - nRow += pOp->p4.pProgram->nOp; - } - } - if( p->explain<2 ) break; - if( pOp->opcode==OP_Explain ) break; - if( pOp->opcode==OP_Init && p->pc>1 ) break; - } + /* Figure out which opcode is next to display */ + rc = sqlite3VdbeNextOpcode(p, pSub, p->explain==2, &p->pc, &i, &aOp); if( rc==SQLITE_OK ){ - if( db->u1.isInterrupted ){ + pOp = aOp + i; + if( AtomicLoad(&db->u1.isInterrupted) ){ p->rc = SQLITE_INTERRUPT; rc = SQLITE_ERROR; sqlite3VdbeError(p, sqlite3ErrStr(p->rc)); }else{ - char *zP4; - if( p->explain==1 ){ - pMem->flags = MEM_Int; - pMem->u.i = i; /* Program counter */ - pMem++; - - pMem->flags = MEM_Static|MEM_Str|MEM_Term; - pMem->z = (char*)sqlite3OpcodeName(pOp->opcode); /* Opcode */ - assert( pMem->z!=0 ); - pMem->n = sqlite3Strlen30(pMem->z); - pMem->enc = SQLITE_UTF8; - pMem++; - } - - pMem->flags = MEM_Int; - pMem->u.i = pOp->p1; /* P1 */ - pMem++; - - pMem->flags = MEM_Int; - pMem->u.i = pOp->p2; /* P2 */ - pMem++; - - pMem->flags = MEM_Int; - pMem->u.i = pOp->p3; /* P3 */ - pMem++; - - if( sqlite3VdbeMemClearAndResize(pMem, 100) ){ /* P4 */ - assert( p->db->mallocFailed ); - return SQLITE_ERROR; - } - pMem->flags = MEM_Str|MEM_Term; - zP4 = displayP4(pOp, pMem->z, pMem->szMalloc); - if( zP4!=pMem->z ){ - pMem->n = 0; - sqlite3VdbeMemSetStr(pMem, zP4, -1, SQLITE_UTF8, 0); + char *zP4 = sqlite3VdbeDisplayP4(db, pOp); + if( p->explain==2 ){ + sqlite3VdbeMemSetInt64(pMem, pOp->p1); + sqlite3VdbeMemSetInt64(pMem+1, pOp->p2); + sqlite3VdbeMemSetInt64(pMem+2, pOp->p3); + sqlite3VdbeMemSetStr(pMem+3, zP4, -1, SQLITE_UTF8, sqlite3_free); + assert( p->nResColumn==4 ); }else{ - assert( pMem->z!=0 ); - pMem->n = sqlite3Strlen30(pMem->z); - pMem->enc = SQLITE_UTF8; - } - pMem++; - - if( p->explain==1 ){ - if( sqlite3VdbeMemClearAndResize(pMem, 4) ){ - assert( p->db->mallocFailed ); - return SQLITE_ERROR; - } - pMem->flags = MEM_Str|MEM_Term; - pMem->n = 2; - sqlite3_snprintf(3, pMem->z, "%.2x", pOp->p5); /* P5 */ - pMem->enc = SQLITE_UTF8; - pMem++; - + sqlite3VdbeMemSetInt64(pMem+0, i); + sqlite3VdbeMemSetStr(pMem+1, (char*)sqlite3OpcodeName(pOp->opcode), + -1, SQLITE_UTF8, SQLITE_STATIC); + sqlite3VdbeMemSetInt64(pMem+2, pOp->p1); + sqlite3VdbeMemSetInt64(pMem+3, pOp->p2); + sqlite3VdbeMemSetInt64(pMem+4, pOp->p3); + /* pMem+5 for p4 is done last */ + sqlite3VdbeMemSetInt64(pMem+6, pOp->p5); #ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS - if( sqlite3VdbeMemClearAndResize(pMem, 500) ){ - assert( p->db->mallocFailed ); - return SQLITE_ERROR; + { + char *zCom = sqlite3VdbeDisplayComment(db, pOp, zP4); + sqlite3VdbeMemSetStr(pMem+7, zCom, -1, SQLITE_UTF8, sqlite3_free); } - pMem->flags = MEM_Str|MEM_Term; - pMem->n = displayComment(pOp, zP4, pMem->z, 500); - pMem->enc = SQLITE_UTF8; #else - pMem->flags = MEM_Null; /* Comment */ + sqlite3VdbeMemSetNull(pMem+7); #endif + sqlite3VdbeMemSetStr(pMem+5, zP4, -1, SQLITE_UTF8, sqlite3_free); + assert( p->nResColumn==8 ); + } + p->pResultRow = pMem; + if( db->mallocFailed ){ + p->rc = SQLITE_NOMEM; + rc = SQLITE_ERROR; + }else{ + p->rc = SQLITE_OK; + rc = SQLITE_ROW; } - - p->nResColumn = 8 - 4*(p->explain-1); - p->pResultSet = &p->aMem[1]; - p->rc = SQLITE_OK; - rc = SQLITE_ROW; } } return rc; @@ -78405,11 +90444,11 @@ struct ReusableSpace { static void *allocSpace( struct ReusableSpace *p, /* Bulk memory available for allocation */ void *pBuf, /* Pointer to a prior allocation */ - sqlite3_int64 nByte /* Bytes of memory needed */ + sqlite3_int64 nByte /* Bytes of memory needed. */ ){ assert( EIGHT_BYTE_ALIGNMENT(p->pSpace) ); if( pBuf==0 ){ - nByte = ROUND8(nByte); + nByte = ROUND8P(nByte); if( nByte <= p->nFree ){ p->nFree -= nByte; pBuf = &p->pSpace[p->nFree]; @@ -78426,18 +90465,19 @@ static void *allocSpace( ** running it. */ SQLITE_PRIVATE void sqlite3VdbeRewind(Vdbe *p){ -#if defined(SQLITE_DEBUG) || defined(VDBE_PROFILE) +#if defined(SQLITE_DEBUG) int i; #endif assert( p!=0 ); - assert( p->magic==VDBE_MAGIC_INIT || p->magic==VDBE_MAGIC_RESET ); + assert( p->eVdbeState==VDBE_INIT_STATE + || p->eVdbeState==VDBE_READY_STATE + || p->eVdbeState==VDBE_HALT_STATE ); /* There should be at least one opcode. */ assert( p->nOp>0 ); - /* Set the magic to VDBE_MAGIC_RUN sooner rather than later. */ - p->magic = VDBE_MAGIC_RUN; + p->eVdbeState = VDBE_READY_STATE; #ifdef SQLITE_DEBUG for(i=0; inMem; i++){ @@ -78454,8 +90494,8 @@ SQLITE_PRIVATE void sqlite3VdbeRewind(Vdbe *p){ p->nFkConstraint = 0; #ifdef VDBE_PROFILE for(i=0; inOp; i++){ - p->aOp[i].cnt = 0; - p->aOp[i].cycles = 0; + p->aOp[i].nExec = 0; + p->aOp[i].nCycle = 0; } #endif } @@ -78465,11 +90505,11 @@ SQLITE_PRIVATE void sqlite3VdbeRewind(Vdbe *p){ ** creating the virtual machine. This involves things such ** as allocating registers and initializing the program counter. ** After the VDBE has be prepped, it can be executed by one or more -** calls to sqlite3VdbeExec(). +** calls to sqlite3VdbeExec(). ** ** This function may be called exactly once on each virtual machine. ** After this routine is called the VM has been "packaged" and is ready -** to run. After this routine is called, further calls to +** to run. After this routine is called, further calls to ** sqlite3VdbeAddOp() functions are prohibited. This routine disconnects ** the Vdbe from the Parse object that helped generate it so that the ** the Vdbe becomes an independent entity and the Parse object can be @@ -78486,22 +90526,25 @@ SQLITE_PRIVATE void sqlite3VdbeMakeReady( int nVar; /* Number of parameters */ int nMem; /* Number of VM memory registers */ int nCursor; /* Number of cursors required */ - int nArg; /* Number of arguments in subprograms */ + int nArg; /* Max number args to xFilter or xUpdate */ int n; /* Loop counter */ struct ReusableSpace x; /* Reusable bulk memory */ assert( p!=0 ); assert( p->nOp>0 ); assert( pParse!=0 ); - assert( p->magic==VDBE_MAGIC_INIT ); + assert( p->eVdbeState==VDBE_INIT_STATE ); assert( pParse==p->pParse ); + assert( pParse->db==p->db ); + p->pVList = pParse->pVList; + pParse->pVList = 0; db = p->db; assert( db->mallocFailed==0 ); nVar = pParse->nVar; nMem = pParse->nMem; nCursor = pParse->nTab; nArg = pParse->nMaxArg; - + /* Each cursor uses a memory cell. The first cursor (cursor 0) can ** use aMem[0] which is not otherwise used by the VDBE program. Allocate ** space at the end of aMem[] for cursors 1 and greater. @@ -78514,7 +90557,7 @@ SQLITE_PRIVATE void sqlite3VdbeMakeReady( ** opcode array. This extra memory will be reallocated for other elements ** of the prepared statement. */ - n = ROUND8(sizeof(Op)*p->nOp); /* Bytes of opcode memory used */ + n = ROUND8P(sizeof(Op)*p->nOp); /* Bytes of opcode memory used */ x.pSpace = &((u8*)p->aOp)[n]; /* Unused opcode memory */ assert( EIGHT_BYTE_ALIGNMENT(x.pSpace) ); x.nFree = ROUNDDOWN8(pParse->szOpAlloc - n); /* Bytes of unused memory */ @@ -78523,16 +90566,18 @@ SQLITE_PRIVATE void sqlite3VdbeMakeReady( resolveP2Values(p, &nArg); p->usesStmtJournal = (u8)(pParse->isMultiWrite && pParse->mayAbort); - if( pParse->explain && nMem<10 ){ - nMem = 10; + if( pParse->explain ){ + if( nMem<10 ) nMem = 10; + p->explain = pParse->explain; + p->nResColumn = 12 - 4*p->explain; } p->expired = 0; /* Memory for registers, parameters, cursor, etc, is allocated in one or two - ** passes. On the first pass, we try to reuse unused memory at the + ** passes. On the first pass, we try to reuse unused memory at the ** end of the opcode array. If we are unable to satisfy all memory ** requirements by reusing the opcode array tail, then the second - ** pass will fill in the remainder using a fresh memory allocation. + ** pass will fill in the remainder using a fresh memory allocation. ** ** This two-pass approach that reuses as much memory as possible from ** the leftover memory at the end of the opcode array. This can significantly @@ -78543,9 +90588,6 @@ SQLITE_PRIVATE void sqlite3VdbeMakeReady( p->aVar = allocSpace(&x, 0, nVar*sizeof(Mem)); p->apArg = allocSpace(&x, 0, nArg*sizeof(Mem*)); p->apCsr = allocSpace(&x, 0, nCursor*sizeof(VdbeCursor*)); -#ifdef SQLITE_ENABLE_STMT_SCANSTATUS - p->anExec = allocSpace(&x, 0, p->nOp*sizeof(i64)); -#endif if( x.nNeeded ){ x.pSpace = p->pFree = sqlite3DbMallocRawNN(db, x.nNeeded); x.nFree = x.nNeeded; @@ -78554,15 +90596,12 @@ SQLITE_PRIVATE void sqlite3VdbeMakeReady( p->aVar = allocSpace(&x, p->aVar, nVar*sizeof(Mem)); p->apArg = allocSpace(&x, p->apArg, nArg*sizeof(Mem*)); p->apCsr = allocSpace(&x, p->apCsr, nCursor*sizeof(VdbeCursor*)); -#ifdef SQLITE_ENABLE_STMT_SCANSTATUS - p->anExec = allocSpace(&x, p->anExec, p->nOp*sizeof(i64)); -#endif } } +#ifdef SQLITE_DEBUG + p->napArg = nArg; +#endif - p->pVList = pParse->pVList; - pParse->pVList = 0; - p->explain = pParse->explain; if( db->mallocFailed ){ p->nVar = 0; p->nCursor = 0; @@ -78574,36 +90613,42 @@ SQLITE_PRIVATE void sqlite3VdbeMakeReady( p->nMem = nMem; initMemArray(p->aMem, nMem, db, MEM_Undefined); memset(p->apCsr, 0, nCursor*sizeof(VdbeCursor*)); -#ifdef SQLITE_ENABLE_STMT_SCANSTATUS - memset(p->anExec, 0, p->nOp*sizeof(i64)); -#endif } sqlite3VdbeRewind(p); } /* -** Close a VDBE cursor and release all the resources that cursor +** Close a VDBE cursor and release all the resources that cursor ** happens to hold. */ SQLITE_PRIVATE void sqlite3VdbeFreeCursor(Vdbe *p, VdbeCursor *pCx){ - if( pCx==0 ){ + if( pCx ) sqlite3VdbeFreeCursorNN(p,pCx); +} +static SQLITE_NOINLINE void freeCursorWithCache(Vdbe *p, VdbeCursor *pCx){ + VdbeTxtBlbCache *pCache = pCx->pCache; + assert( pCx->colCache ); + pCx->colCache = 0; + pCx->pCache = 0; + if( pCache->pCValue ){ + sqlite3RCStrUnref(pCache->pCValue); + pCache->pCValue = 0; + } + sqlite3DbFree(p->db, pCache); + sqlite3VdbeFreeCursorNN(p, pCx); +} +SQLITE_PRIVATE void sqlite3VdbeFreeCursorNN(Vdbe *p, VdbeCursor *pCx){ + if( pCx->colCache ){ + freeCursorWithCache(p, pCx); return; } - assert( pCx->pBtx==0 || pCx->eCurType==CURTYPE_BTREE ); switch( pCx->eCurType ){ case CURTYPE_SORTER: { sqlite3VdbeSorterClose(p->db, pCx); break; } case CURTYPE_BTREE: { - if( pCx->isEphemeral ){ - if( pCx->pBtx ) sqlite3BtreeClose(pCx->pBtx); - /* The pCx->pCursor will be close automatically, if it exists, by - ** the call above. */ - }else{ - assert( pCx->uc.pCursor!=0 ); - sqlite3BtreeCloseCursor(pCx->uc.pCursor); - } + assert( pCx->uc.pCursor!=0 ); + sqlite3BtreeCloseCursor(pCx->uc.pCursor); break; } #ifndef SQLITE_OMIT_VIRTUALTABLE @@ -78623,14 +90668,12 @@ SQLITE_PRIVATE void sqlite3VdbeFreeCursor(Vdbe *p, VdbeCursor *pCx){ ** Close all cursors in the current frame. */ static void closeCursorsInFrame(Vdbe *p){ - if( p->apCsr ){ - int i; - for(i=0; inCursor; i++){ - VdbeCursor *pC = p->apCsr[i]; - if( pC ){ - sqlite3VdbeFreeCursor(p, pC); - p->apCsr[i] = 0; - } + int i; + for(i=0; inCursor; i++){ + VdbeCursor *pC = p->apCsr[i]; + if( pC ){ + sqlite3VdbeFreeCursorNN(p, pC); + p->apCsr[i] = 0; } } } @@ -78643,9 +90686,6 @@ static void closeCursorsInFrame(Vdbe *p){ SQLITE_PRIVATE int sqlite3VdbeFrameRestore(VdbeFrame *pFrame){ Vdbe *v = pFrame->v; closeCursorsInFrame(v); -#ifdef SQLITE_ENABLE_STMT_SCANSTATUS - v->anExec = pFrame->anExec; -#endif v->aOp = pFrame->aOp; v->nOp = pFrame->nOp; v->aMem = pFrame->aMem; @@ -78664,7 +90704,7 @@ SQLITE_PRIVATE int sqlite3VdbeFrameRestore(VdbeFrame *pFrame){ /* ** Close all cursors. ** -** Also release any dynamic memory held by the VM in the Vdbe.aMem memory +** Also release any dynamic memory held by the VM in the Vdbe.aMem memory ** cell array. This is necessary as the memory cell array may contain ** pointers to VdbeFrame objects, which may in turn contain pointers to ** open cursors. @@ -78679,9 +90719,7 @@ static void closeAllCursors(Vdbe *p){ } assert( p->nFrame==0 ); closeCursorsInFrame(p); - if( p->aMem ){ - releaseMemArray(p->aMem, p->nMem); - } + releaseMemArray(p->aMem, p->nMem); while( p->pDelFrame ){ VdbeFrame *pDel = p->pDelFrame; p->pDelFrame = pDel->pParent; @@ -78703,12 +90741,12 @@ SQLITE_PRIVATE void sqlite3VdbeSetNumCols(Vdbe *p, int nResColumn){ int n; sqlite3 *db = p->db; - if( p->nResColumn ){ - releaseMemArray(p->aColName, p->nResColumn*COLNAME_N); + if( p->nResAlloc ){ + releaseMemArray(p->aColName, p->nResAlloc*COLNAME_N); sqlite3DbFree(db, p->aColName); } n = nResColumn*COLNAME_N; - p->nResColumn = (u16)nResColumn; + p->nResColumn = p->nResAlloc = (u16)nResColumn; p->aColName = (Mem*)sqlite3DbMallocRawNN(db, sizeof(Mem)*n ); if( p->aColName==0 ) return; initMemArray(p->aColName, n, db, MEM_Null); @@ -78733,15 +90771,15 @@ SQLITE_PRIVATE int sqlite3VdbeSetColName( ){ int rc; Mem *pColName; - assert( idxnResColumn ); + assert( idxnResAlloc ); assert( vardb->mallocFailed ){ assert( !zName || xDel!=SQLITE_DYNAMIC ); return SQLITE_NOMEM_BKPT; } assert( p->aColName!=0 ); - pColName = &(p->aColName[idx+var*p->nResColumn]); - rc = sqlite3VdbeMemSetStr(pColName, zName, -1, SQLITE_UTF8, xDel); + pColName = &(p->aColName[idx+var*p->nResAlloc]); + rc = sqlite3VdbeMemSetText(pColName, zName, -1, xDel); assert( rc!=0 || !zName || (pColName->flags&MEM_Term)!=0 ); return rc; } @@ -78750,43 +90788,43 @@ SQLITE_PRIVATE int sqlite3VdbeSetColName( ** A read or write transaction may or may not be active on database handle ** db. If a transaction is active, commit it. If there is a ** write-transaction spanning more than one database file, this routine -** takes care of the master journal trickery. +** takes care of the super-journal trickery. */ static int vdbeCommit(sqlite3 *db, Vdbe *p){ int i; int nTrans = 0; /* Number of databases with an active write-transaction ** that are candidates for a two-phase commit using a - ** master-journal */ + ** super-journal */ int rc = SQLITE_OK; int needXcommit = 0; #ifdef SQLITE_OMIT_VIRTUALTABLE - /* With this option, sqlite3VtabSync() is defined to be simply - ** SQLITE_OK so p is not used. + /* With this option, sqlite3VtabSync() is defined to be simply + ** SQLITE_OK so p is not used. */ UNUSED_PARAMETER(p); #endif /* Before doing anything else, call the xSync() callback for any ** virtual module tables written in this transaction. This has to - ** be done before determining whether a master journal file is + ** be done before determining whether a super-journal file is ** required, as an xSync() callback may add an attached database ** to the transaction. */ rc = sqlite3VtabSync(db, p); /* This loop determines (a) if the commit hook should be invoked and - ** (b) how many database files have open write transactions, not - ** including the temp database. (b) is important because if more than - ** one database file has an open write transaction, a master journal + ** (b) how many database files have open write transactions, not + ** including the temp database. (b) is important because if more than + ** one database file has an open write transaction, a super-journal ** file is required for an atomic commit. - */ - for(i=0; rc==SQLITE_OK && inDb; i++){ + */ + for(i=0; rc==SQLITE_OK && inDb; i++){ Btree *pBt = db->aDb[i].pBt; - if( sqlite3BtreeIsInTrans(pBt) ){ - /* Whether or not a database might need a master journal depends upon + if( sqlite3BtreeTxnState(pBt)==SQLITE_TXN_WRITE ){ + /* Whether or not a database might need a super-journal depends upon ** its journal mode (among other things). This matrix determines which - ** journal modes use a master journal and which do not */ + ** journal modes use a super-journal and which do not */ static const u8 aMJNeeded[] = { /* DELETE */ 1, /* PERSIST */ 1, @@ -78802,7 +90840,7 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){ if( db->aDb[i].safety_level!=PAGER_SYNCHRONOUS_OFF && aMJNeeded[sqlite3PagerGetJournalMode(pPager)] && sqlite3PagerIsMemdb(pPager)==0 - ){ + ){ assert( i!=1 ); nTrans++; } @@ -78824,31 +90862,35 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){ /* The simple case - no more than one database file (not counting the ** TEMP database) has a transaction active. There is no need for the - ** master-journal. + ** super-journal. ** ** If the return value of sqlite3BtreeGetFilename() is a zero length - ** string, it means the main database is :memory: or a temp file. In - ** that case we do not support atomic multi-file commits, so use the + ** string, it means the main database is :memory: or a temp file. In + ** that case we do not support atomic multi-file commits, so use the ** simple case then too. */ if( 0==sqlite3Strlen30(sqlite3BtreeGetFilename(db->aDb[0].pBt)) || nTrans<=1 ){ - for(i=0; rc==SQLITE_OK && inDb; i++){ - Btree *pBt = db->aDb[i].pBt; - if( pBt ){ - rc = sqlite3BtreeCommitPhaseOne(pBt, 0); + if( needXcommit ){ + for(i=0; rc==SQLITE_OK && inDb; i++){ + Btree *pBt = db->aDb[i].pBt; + if( sqlite3BtreeTxnState(pBt)>=SQLITE_TXN_WRITE ){ + rc = sqlite3BtreeCommitPhaseOne(pBt, 0); + } } } - /* Do the commit only if all databases successfully complete phase 1. + /* Do the commit only if all databases successfully complete phase 1. ** If one of the BtreeCommitPhaseOne() calls fails, this indicates an ** IO error while deleting or truncating a journal file. It is unlikely, ** but could happen. In this case abandon processing and return the error. */ for(i=0; rc==SQLITE_OK && inDb; i++){ Btree *pBt = db->aDb[i].pBt; - if( pBt ){ + int txn = sqlite3BtreeTxnState(pBt); + if( txn!=SQLITE_TXN_NONE ){ + assert( needXcommit || txn==SQLITE_TXN_READ ); rc = sqlite3BtreeCommitPhaseTwo(pBt, 0); } } @@ -78858,124 +90900,125 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){ } /* The complex case - There is a multi-file write-transaction active. - ** This requires a master journal file to ensure the transaction is + ** This requires a super-journal file to ensure the transaction is ** committed atomically. */ #ifndef SQLITE_OMIT_DISKIO else{ sqlite3_vfs *pVfs = db->pVfs; - char *zMaster = 0; /* File-name for the master journal */ + char *zSuper = 0; /* File-name for the super-journal */ char const *zMainFile = sqlite3BtreeGetFilename(db->aDb[0].pBt); - sqlite3_file *pMaster = 0; + sqlite3_file *pSuperJrnl = 0; i64 offset = 0; int res; int retryCount = 0; int nMainFile; - /* Select a master journal file name */ + /* Select a super-journal file name */ nMainFile = sqlite3Strlen30(zMainFile); - zMaster = sqlite3MPrintf(db, "%s-mjXXXXXX9XXz", zMainFile); - if( zMaster==0 ) return SQLITE_NOMEM_BKPT; + zSuper = sqlite3MPrintf(db, "%.4c%s%.16c", 0,zMainFile,0); + if( zSuper==0 ) return SQLITE_NOMEM_BKPT; + zSuper += 4; do { u32 iRandom; if( retryCount ){ if( retryCount>100 ){ - sqlite3_log(SQLITE_FULL, "MJ delete: %s", zMaster); - sqlite3OsDelete(pVfs, zMaster, 0); + sqlite3_log(SQLITE_FULL, "MJ delete: %s", zSuper); + sqlite3OsDelete(pVfs, zSuper, 0); break; }else if( retryCount==1 ){ - sqlite3_log(SQLITE_FULL, "MJ collide: %s", zMaster); + sqlite3_log(SQLITE_FULL, "MJ collide: %s", zSuper); } } retryCount++; sqlite3_randomness(sizeof(iRandom), &iRandom); - sqlite3_snprintf(13, &zMaster[nMainFile], "-mj%06X9%02X", + sqlite3_snprintf(13, &zSuper[nMainFile], "-mj%06X9%02X", (iRandom>>8)&0xffffff, iRandom&0xff); - /* The antipenultimate character of the master journal name must + /* The antipenultimate character of the super-journal name must ** be "9" to avoid name collisions when using 8+3 filenames. */ - assert( zMaster[sqlite3Strlen30(zMaster)-3]=='9' ); - sqlite3FileSuffix3(zMainFile, zMaster); - rc = sqlite3OsAccess(pVfs, zMaster, SQLITE_ACCESS_EXISTS, &res); + assert( zSuper[sqlite3Strlen30(zSuper)-3]=='9' ); + sqlite3FileSuffix3(zMainFile, zSuper); + rc = sqlite3OsAccess(pVfs, zSuper, SQLITE_ACCESS_EXISTS, &res); }while( rc==SQLITE_OK && res ); if( rc==SQLITE_OK ){ - /* Open the master journal. */ - rc = sqlite3OsOpenMalloc(pVfs, zMaster, &pMaster, + /* Open the super-journal. */ + rc = sqlite3OsOpenMalloc(pVfs, zSuper, &pSuperJrnl, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE| - SQLITE_OPEN_EXCLUSIVE|SQLITE_OPEN_MASTER_JOURNAL, 0 + SQLITE_OPEN_EXCLUSIVE|SQLITE_OPEN_SUPER_JOURNAL, 0 ); } if( rc!=SQLITE_OK ){ - sqlite3DbFree(db, zMaster); + sqlite3DbFree(db, zSuper-4); return rc; } - + /* Write the name of each database file in the transaction into the new - ** master journal file. If an error occurs at this point close - ** and delete the master journal file. All the individual journal files - ** still have 'null' as the master journal pointer, so they will roll + ** super-journal file. If an error occurs at this point close + ** and delete the super-journal file. All the individual journal files + ** still have 'null' as the super-journal pointer, so they will roll ** back independently if a failure occurs. */ for(i=0; inDb; i++){ Btree *pBt = db->aDb[i].pBt; - if( sqlite3BtreeIsInTrans(pBt) ){ + if( sqlite3BtreeTxnState(pBt)==SQLITE_TXN_WRITE ){ char const *zFile = sqlite3BtreeGetJournalname(pBt); if( zFile==0 ){ continue; /* Ignore TEMP and :memory: databases */ } assert( zFile[0]!=0 ); - rc = sqlite3OsWrite(pMaster, zFile, sqlite3Strlen30(zFile)+1, offset); + rc = sqlite3OsWrite(pSuperJrnl, zFile, sqlite3Strlen30(zFile)+1,offset); offset += sqlite3Strlen30(zFile)+1; if( rc!=SQLITE_OK ){ - sqlite3OsCloseFree(pMaster); - sqlite3OsDelete(pVfs, zMaster, 0); - sqlite3DbFree(db, zMaster); + sqlite3OsCloseFree(pSuperJrnl); + sqlite3OsDelete(pVfs, zSuper, 0); + sqlite3DbFree(db, zSuper-4); return rc; } } } - /* Sync the master journal file. If the IOCAP_SEQUENTIAL device + /* Sync the super-journal file. If the IOCAP_SEQUENTIAL device ** flag is set this is not required. */ - if( 0==(sqlite3OsDeviceCharacteristics(pMaster)&SQLITE_IOCAP_SEQUENTIAL) - && SQLITE_OK!=(rc = sqlite3OsSync(pMaster, SQLITE_SYNC_NORMAL)) + if( 0==(sqlite3OsDeviceCharacteristics(pSuperJrnl)&SQLITE_IOCAP_SEQUENTIAL) + && SQLITE_OK!=(rc = sqlite3OsSync(pSuperJrnl, SQLITE_SYNC_NORMAL)) ){ - sqlite3OsCloseFree(pMaster); - sqlite3OsDelete(pVfs, zMaster, 0); - sqlite3DbFree(db, zMaster); + sqlite3OsCloseFree(pSuperJrnl); + sqlite3OsDelete(pVfs, zSuper, 0); + sqlite3DbFree(db, zSuper-4); return rc; } /* Sync all the db files involved in the transaction. The same call - ** sets the master journal pointer in each individual journal. If - ** an error occurs here, do not delete the master journal file. + ** sets the super-journal pointer in each individual journal. If + ** an error occurs here, do not delete the super-journal file. ** ** If the error occurs during the first call to ** sqlite3BtreeCommitPhaseOne(), then there is a chance that the - ** master journal file will be orphaned. But we cannot delete it, - ** in case the master journal file name was written into the journal + ** super-journal file will be orphaned. But we cannot delete it, + ** in case the super-journal file name was written into the journal ** file before the failure occurred. */ - for(i=0; rc==SQLITE_OK && inDb; i++){ + for(i=0; rc==SQLITE_OK && inDb; i++){ Btree *pBt = db->aDb[i].pBt; if( pBt ){ - rc = sqlite3BtreeCommitPhaseOne(pBt, zMaster); + rc = sqlite3BtreeCommitPhaseOne(pBt, zSuper); } } - sqlite3OsCloseFree(pMaster); + sqlite3OsCloseFree(pSuperJrnl); assert( rc!=SQLITE_BUSY ); if( rc!=SQLITE_OK ){ - sqlite3DbFree(db, zMaster); + sqlite3DbFree(db, zSuper-4); return rc; } - /* Delete the master journal file. This commits the transaction. After + /* Delete the super-journal file. This commits the transaction. After ** doing this the directory is synced again before any individual ** transaction files are deleted. */ - rc = sqlite3OsDelete(pVfs, zMaster, 1); - sqlite3DbFree(db, zMaster); - zMaster = 0; + rc = sqlite3OsDelete(pVfs, zSuper, 1); + sqlite3DbFree(db, zSuper-4); + zSuper = 0; if( rc ){ return rc; } @@ -78989,7 +91032,7 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){ */ disable_simulated_io_errors(); sqlite3BeginBenignMalloc(); - for(i=0; inDb; i++){ + for(i=0; inDb; i++){ Btree *pBt = db->aDb[i].pBt; if( pBt ){ sqlite3BtreeCommitPhaseTwo(pBt, 1); @@ -79005,7 +91048,7 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){ return rc; } -/* +/* ** This routine checks that the sqlite3.nVdbeActive count variable ** matches the number of vdbe's in the list sqlite3.pVdbe that are ** currently active. An assertion fails if the two counts do not match. @@ -79027,7 +91070,7 @@ static void checkActiveVdbeCnt(sqlite3 *db){ if( p->readOnly==0 ) nWrite++; if( p->bIsReader ) nRead++; } - p = p->pNext; + p = p->pVNext; } assert( cnt==db->nVdbeActive ); assert( nWrite==db->nVdbeWrite ); @@ -79041,10 +91084,10 @@ static void checkActiveVdbeCnt(sqlite3 *db){ ** If the Vdbe passed as the first argument opened a statement-transaction, ** close it now. Argument eOp must be either SAVEPOINT_ROLLBACK or ** SAVEPOINT_RELEASE. If it is SAVEPOINT_ROLLBACK, then the statement -** transaction is rolled back. If eOp is SAVEPOINT_RELEASE, then the +** transaction is rolled back. If eOp is SAVEPOINT_RELEASE, then the ** statement transaction is committed. ** -** If an IO error occurs, an SQLITE_IOERR_XXX error code is returned. +** If an IO error occurs, an SQLITE_IOERR_XXX error code is returned. ** Otherwise SQLITE_OK. */ static SQLITE_NOINLINE int vdbeCloseStatement(Vdbe *p, int eOp){ @@ -79057,7 +91100,7 @@ static SQLITE_NOINLINE int vdbeCloseStatement(Vdbe *p, int eOp){ assert( db->nStatement>0 ); assert( p->iStatement==(db->nStatement+db->nSavepoint) ); - for(i=0; inDb; i++){ + for(i=0; inDb; i++){ int rc2 = SQLITE_OK; Btree *pBt = db->aDb[i].pBt; if( pBt ){ @@ -79084,8 +91127,8 @@ static SQLITE_NOINLINE int vdbeCloseStatement(Vdbe *p, int eOp){ } } - /* If the statement transaction is being rolled back, also restore the - ** database handles deferred constraint counter to the value it had when + /* If the statement transaction is being rolled back, also restore the + ** database handles deferred constraint counter to the value it had when ** the statement transaction was opened. */ if( eOp==SAVEPOINT_ROLLBACK ){ db->nDeferredCons = p->nStmtDefCons; @@ -79102,27 +91145,31 @@ SQLITE_PRIVATE int sqlite3VdbeCloseStatement(Vdbe *p, int eOp){ /* -** This function is called when a transaction opened by the database -** handle associated with the VM passed as an argument is about to be -** committed. If there are outstanding deferred foreign key constraint -** violations, return SQLITE_ERROR. Otherwise, SQLITE_OK. +** These functions are called when a transaction opened by the database +** handle associated with the VM passed as an argument is about to be +** committed. If there are outstanding foreign key constraint violations +** return an error code. Otherwise, SQLITE_OK. ** -** If there are outstanding FK violations and this function returns -** SQLITE_ERROR, set the result of the VM to SQLITE_CONSTRAINT_FOREIGNKEY -** and write an error message to it. Then return SQLITE_ERROR. +** If there are outstanding FK violations and this function returns +** non-zero, set the result of the VM to SQLITE_CONSTRAINT_FOREIGNKEY +** and write an error message to it. */ #ifndef SQLITE_OMIT_FOREIGN_KEY -SQLITE_PRIVATE int sqlite3VdbeCheckFk(Vdbe *p, int deferred){ +static SQLITE_NOINLINE int vdbeFkError(Vdbe *p){ + p->rc = SQLITE_CONSTRAINT_FOREIGNKEY; + p->errorAction = OE_Abort; + sqlite3VdbeError(p, "FOREIGN KEY constraint failed"); + if( (p->prepFlags & SQLITE_PREPARE_SAVESQL)==0 ) return SQLITE_ERROR; + return SQLITE_CONSTRAINT_FOREIGNKEY; +} +SQLITE_PRIVATE int sqlite3VdbeCheckFkImmediate(Vdbe *p){ + if( p->nFkConstraint==0 ) return SQLITE_OK; + return vdbeFkError(p); +} +SQLITE_PRIVATE int sqlite3VdbeCheckFkDeferred(Vdbe *p){ sqlite3 *db = p->db; - if( (deferred && (db->nDeferredCons+db->nDeferredImmCons)>0) - || (!deferred && p->nFkConstraint>0) - ){ - p->rc = SQLITE_CONSTRAINT_FOREIGNKEY; - p->errorAction = OE_Abort; - sqlite3VdbeError(p, "FOREIGN KEY constraint failed"); - return SQLITE_ERROR; - } - return SQLITE_OK; + if( (db->nDeferredCons+db->nDeferredImmCons)==0 ) return SQLITE_OK; + return vdbeFkError(p); } #endif @@ -79131,9 +91178,9 @@ SQLITE_PRIVATE int sqlite3VdbeCheckFk(Vdbe *p, int deferred){ ** has made changes and is in autocommit mode, then commit those ** changes. If a rollback is needed, then do the rollback. ** -** This routine is the only way to move the state of a VM from -** SQLITE_MAGIC_RUN to SQLITE_MAGIC_HALT. It is harmless to -** call this on a VM that is in the SQLITE_MAGIC_HALT state. +** This routine is the only way to move the sqlite3eOpenState of a VM from +** SQLITE_STATE_RUN to SQLITE_STATE_HALT. It is harmless to +** call this on a VM that is in the SQLITE_STATE_HALT state. ** ** Return an error code. If the commit could not complete because of ** lock contention, return SQLITE_BUSY. If SQLITE_BUSY is returned, it @@ -79145,7 +91192,7 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){ /* This function contains the logic that determines if a statement or ** transaction will be committed or rolled back as a result of the - ** execution of this virtual machine. + ** execution of this virtual machine. ** ** If any of the following errors occur: ** @@ -79159,9 +91206,7 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){ ** one, or the complete transaction if there is no statement transaction. */ - if( p->magic!=VDBE_MAGIC_RUN ){ - return SQLITE_OK; - } + assert( p->eVdbeState==VDBE_RUN_STATE ); if( db->mallocFailed ){ p->rc = SQLITE_NOMEM_BKPT; } @@ -79170,7 +91215,7 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){ /* No commit or rollback needed if the program never started or if the ** SQL statement does not read or write a database file. */ - if( p->pc>=0 && p->bIsReader ){ + if( p->bIsReader ){ int mrc; /* Primary error code from p->rc */ int eStatementOp = 0; int isSpecialError; /* Set to true if a 'special' error */ @@ -79179,20 +91224,26 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){ sqlite3VdbeEnter(p); /* Check for one of the special errors */ - mrc = p->rc & 0xff; - isSpecialError = mrc==SQLITE_NOMEM || mrc==SQLITE_IOERR - || mrc==SQLITE_INTERRUPT || mrc==SQLITE_FULL; + if( p->rc ){ + mrc = p->rc & 0xff; + isSpecialError = mrc==SQLITE_NOMEM + || mrc==SQLITE_IOERR + || mrc==SQLITE_INTERRUPT + || mrc==SQLITE_FULL; + }else{ + mrc = isSpecialError = 0; + } if( isSpecialError ){ - /* If the query was read-only and the error code is SQLITE_INTERRUPT, - ** no rollback is necessary. Otherwise, at least a savepoint - ** transaction must be rolled back to restore the database to a + /* If the query was read-only and the error code is SQLITE_INTERRUPT, + ** no rollback is necessary. Otherwise, at least a savepoint + ** transaction must be rolled back to restore the database to a ** consistent state. ** ** Even if the statement is read-only, it is important to perform - ** a statement or transaction rollback operation. If the error + ** a statement or transaction rollback operation. If the error ** occurred while writing to the journal, sub-journal or database ** file as part of an effort to free up cache space (see function - ** pagerStress() in pager.c), the rollback is required to restore + ** pagerStress() in pager.c), the rollback is required to restore ** the pager to a consistent state. */ if( !p->readOnly || mrc!=SQLITE_INTERRUPT ){ @@ -79211,32 +91262,35 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){ } /* Check for immediate foreign key violations. */ - if( p->rc==SQLITE_OK ){ - sqlite3VdbeCheckFk(p, 0); + if( p->rc==SQLITE_OK || (p->errorAction==OE_Fail && !isSpecialError) ){ + (void)sqlite3VdbeCheckFkImmediate(p); } - - /* If the auto-commit flag is set and this is the only active writer - ** VM, then we do either a commit or rollback of the current transaction. + + /* If the auto-commit flag is set and this is the only active writer + ** VM, then we do either a commit or rollback of the current transaction. ** - ** Note: This block also runs if one of the special errors handled - ** above has occurred. + ** Note: This block also runs if one of the special errors handled + ** above has occurred. */ - if( !sqlite3VtabInSync(db) - && db->autoCommit - && db->nVdbeWrite==(p->readOnly==0) + if( !sqlite3VtabInSync(db) + && db->autoCommit + && db->nVdbeWrite==(p->readOnly==0) ){ if( p->rc==SQLITE_OK || (p->errorAction==OE_Fail && !isSpecialError) ){ - rc = sqlite3VdbeCheckFk(p, 1); + rc = sqlite3VdbeCheckFkDeferred(p); if( rc!=SQLITE_OK ){ if( NEVER(p->readOnly) ){ sqlite3VdbeLeave(p); return SQLITE_ERROR; } rc = SQLITE_CONSTRAINT_FOREIGNKEY; - }else{ - /* The auto-commit flag is true, the vdbe program was successful + }else if( db->flags & SQLITE_CorruptRdOnly ){ + rc = SQLITE_CORRUPT; + db->flags &= ~SQLITE_CorruptRdOnly; + }else{ + /* The auto-commit flag is true, the vdbe program was successful ** or hit an 'OR FAIL' constraint and there are no deferred foreign - ** key constraints to hold up the transaction. This means a commit + ** key constraints to hold up the transaction. This means a commit ** is required. */ rc = vdbeCommit(db, p); } @@ -79244,6 +91298,7 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){ sqlite3VdbeLeave(p); return SQLITE_BUSY; }else if( rc!=SQLITE_OK ){ + sqlite3SystemError(db, rc); p->rc = rc; sqlite3RollbackAll(db, SQLITE_OK); p->nChange = 0; @@ -79253,6 +91308,8 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){ db->flags &= ~(u64)SQLITE_DeferFKs; sqlite3CommitInternalChanges(db); } + }else if( p->rc==SQLITE_SCHEMA && db->nVdbeActive>1 ){ + p->nChange = 0; }else{ sqlite3RollbackAll(db, SQLITE_OK); p->nChange = 0; @@ -79270,7 +91327,7 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){ p->nChange = 0; } } - + /* If eStatementOp is non-zero, then a statement transaction needs to ** be committed or rolled back. Call sqlite3VdbeCloseStatement() to ** do so. If this operation returns an error, and the current statement @@ -79291,9 +91348,9 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){ p->nChange = 0; } } - + /* If this was an INSERT, UPDATE or DELETE and no statement transaction - ** has been rolled back, update the database connection change-counter. + ** has been rolled back, update the database connection change-counter. */ if( p->changeCntOn ){ if( eStatementOp!=SAVEPOINT_ROLLBACK ){ @@ -79309,22 +91366,20 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){ } /* We have successfully halted and closed the VM. Record this fact. */ - if( p->pc>=0 ){ - db->nVdbeActive--; - if( !p->readOnly ) db->nVdbeWrite--; - if( p->bIsReader ) db->nVdbeRead--; - assert( db->nVdbeActive>=db->nVdbeRead ); - assert( db->nVdbeRead>=db->nVdbeWrite ); - assert( db->nVdbeWrite>=0 ); - } - p->magic = VDBE_MAGIC_HALT; + db->nVdbeActive--; + if( !p->readOnly ) db->nVdbeWrite--; + if( p->bIsReader ) db->nVdbeRead--; + assert( db->nVdbeActive>=db->nVdbeRead ); + assert( db->nVdbeRead>=db->nVdbeWrite ); + assert( db->nVdbeWrite>=0 ); + p->eVdbeState = VDBE_HALT_STATE; checkActiveVdbeCnt(db); if( db->mallocFailed ){ p->rc = SQLITE_NOMEM_BKPT; } /* If the auto-commit flag is set to true, then any locks that were held - ** by connection db have now been released. Call sqlite3ConnectionUnlocked() + ** by connection db have now been released. Call sqlite3ConnectionUnlocked() ** to invoke any required unlock-notify callbacks. */ if( db->autoCommit ){ @@ -79346,7 +91401,7 @@ SQLITE_PRIVATE void sqlite3VdbeResetStepResult(Vdbe *p){ /* ** Copy the error code and error message belonging to the VDBE passed -** as the first argument to its database handle (so that they will be +** as the first argument to its database handle (so that they will be ** returned by calls to sqlite3_errcode() and sqlite3_errmsg()). ** ** This function does not clear the VDBE error code or message, just @@ -79366,12 +91421,13 @@ SQLITE_PRIVATE int sqlite3VdbeTransferError(Vdbe *p){ sqlite3ValueSetNull(db->pErr); } db->errCode = rc; + db->errByteOffset = -1; return rc; } #ifdef SQLITE_ENABLE_SQLLOG /* -** If an SQLITE_CONFIG_SQLLOG hook is registered and the VM has been run, +** If an SQLITE_CONFIG_SQLLOG hook is registered and the VM has been run, ** invoke it. */ static void vdbeInvokeSqllog(Vdbe *v){ @@ -79398,8 +91454,8 @@ static void vdbeInvokeSqllog(Vdbe *v){ ** again. ** ** To look at it another way, this routine resets the state of the -** virtual machine from VDBE_MAGIC_RUN or VDBE_MAGIC_HALT back to -** VDBE_MAGIC_INIT. +** virtual machine from VDBE_RUN_STATE or VDBE_HALT_STATE back to +** VDBE_READY_STATE. */ SQLITE_PRIVATE int sqlite3VdbeReset(Vdbe *p){ #if defined(SQLITE_DEBUG) || defined(VDBE_PROFILE) @@ -79413,7 +91469,7 @@ SQLITE_PRIVATE int sqlite3VdbeReset(Vdbe *p){ ** error, then it might not have been halted properly. So halt ** it now. */ - sqlite3VdbeHalt(p); + if( p->eVdbeState==VDBE_RUN_STATE ) sqlite3VdbeHalt(p); /* If the VDBE has been run even partially, then transfer the error code ** and error message from the VDBE into the main database structure. But @@ -79422,29 +91478,28 @@ SQLITE_PRIVATE int sqlite3VdbeReset(Vdbe *p){ */ if( p->pc>=0 ){ vdbeInvokeSqllog(p); - sqlite3VdbeTransferError(p); - if( p->runOnlyOnce ) p->expired = 1; - }else if( p->rc && p->expired ){ - /* The expired flag was set on the VDBE before the first call - ** to sqlite3_step(). For consistency (since sqlite3_step() was - ** called), set the database error in this case as well. - */ - sqlite3ErrorWithMsg(db, p->rc, p->zErrMsg ? "%s" : 0, p->zErrMsg); + if( db->pErr || p->zErrMsg ){ + sqlite3VdbeTransferError(p); + }else{ + db->errCode = p->rc; + } } /* Reset register contents and reclaim error message memory. */ #ifdef SQLITE_DEBUG - /* Execute assert() statements to ensure that the Vdbe.apCsr[] and + /* Execute assert() statements to ensure that the Vdbe.apCsr[] and ** Vdbe.aMem[] arrays have already been cleaned up. */ if( p->apCsr ) for(i=0; inCursor; i++) assert( p->apCsr[i]==0 ); if( p->aMem ){ for(i=0; inMem; i++) assert( p->aMem[i].flags==MEM_Undefined ); } #endif - sqlite3DbFree(db, p->zErrMsg); - p->zErrMsg = 0; - p->pResultSet = 0; + if( p->zErrMsg ){ + sqlite3DbFree(db, p->zErrMsg); + p->zErrMsg = 0; + } + p->pResultRow = 0; #ifdef SQLITE_DEBUG p->nWrite = 0; #endif @@ -79472,10 +91527,12 @@ SQLITE_PRIVATE int sqlite3VdbeReset(Vdbe *p){ } for(i=0; inOp; i++){ char zHdr[100]; + i64 cnt = p->aOp[i].nExec; + i64 cycles = p->aOp[i].nCycle; sqlite3_snprintf(sizeof(zHdr), zHdr, "%6u %12llu %8llu ", - p->aOp[i].cnt, - p->aOp[i].cycles, - p->aOp[i].cnt>0 ? p->aOp[i].cycles/p->aOp[i].cnt : 0 + cnt, + cycles, + cnt>0 ? cycles/cnt : 0 ); fprintf(out, "%s", zHdr); sqlite3VdbePrintOp(out, i, &p->aOp[i]); @@ -79484,17 +91541,19 @@ SQLITE_PRIVATE int sqlite3VdbeReset(Vdbe *p){ } } #endif - p->magic = VDBE_MAGIC_RESET; return p->rc & db->errMask; } - + /* ** Clean up and delete a VDBE after execution. Return an integer which is ** the result code. Write any error message text into *pzErrMsg. */ SQLITE_PRIVATE int sqlite3VdbeFinalize(Vdbe *p){ int rc = SQLITE_OK; - if( p->magic==VDBE_MAGIC_RUN || p->magic==VDBE_MAGIC_HALT ){ + assert( VDBE_RUN_STATE>VDBE_READY_STATE ); + assert( VDBE_HALT_STATE>VDBE_READY_STATE ); + assert( VDBE_INIT_STATEeVdbeState>=VDBE_READY_STATE ){ rc = sqlite3VdbeReset(p); assert( (rc & p->db->errMask)==rc ); } @@ -79508,8 +91567,8 @@ SQLITE_PRIVATE int sqlite3VdbeFinalize(Vdbe *p){ ** the first argument. ** ** Or, if iOp is greater than or equal to zero, then the destructor is -** only invoked for those auxiliary data pointers created by the user -** function invoked by the OP_Function opcode at instruction iOp of +** only invoked for those auxiliary data pointers created by the user +** function invoked by the OP_Function opcode at instruction iOp of ** VM pVdbe, and only then if: ** ** * the associated function parameter is the 32nd or later (counting @@ -79546,29 +91605,32 @@ SQLITE_PRIVATE void sqlite3VdbeDeleteAuxData(sqlite3 *db, AuxData **pp, int iOp, ** VdbeDelete() also unlinks the Vdbe from the list of VMs associated with ** the database connection and frees the object itself. */ -SQLITE_PRIVATE void sqlite3VdbeClearObject(sqlite3 *db, Vdbe *p){ +static void sqlite3VdbeClearObject(sqlite3 *db, Vdbe *p){ SubProgram *pSub, *pNext; + assert( db!=0 ); assert( p->db==0 || p->db==db ); - releaseMemArray(p->aColName, p->nResColumn*COLNAME_N); + if( p->aColName ){ + releaseMemArray(p->aColName, p->nResAlloc*COLNAME_N); + sqlite3DbNNFreeNN(db, p->aColName); + } for(pSub=p->pProgram; pSub; pSub=pNext){ pNext = pSub->pNext; vdbeFreeOpArray(db, pSub->aOp, pSub->nOp); sqlite3DbFree(db, pSub); } - if( p->magic!=VDBE_MAGIC_INIT ){ + if( p->eVdbeState!=VDBE_INIT_STATE ){ releaseMemArray(p->aVar, p->nVar); - sqlite3DbFree(db, p->pVList); - sqlite3DbFree(db, p->pFree); + if( p->pVList ) sqlite3DbNNFreeNN(db, p->pVList); + if( p->pFree ) sqlite3DbNNFreeNN(db, p->pFree); } vdbeFreeOpArray(db, p->aOp, p->nOp); - sqlite3DbFree(db, p->aColName); - sqlite3DbFree(db, p->zSql); + if( p->zSql ) sqlite3DbNNFreeNN(db, p->zSql); #ifdef SQLITE_ENABLE_NORMALIZE sqlite3DbFree(db, p->zNormSql); { - DblquoteStr *pThis, *pNext; - for(pThis=p->pDblStr; pThis; pThis=pNext){ - pNext = pThis->pNextStr; + DblquoteStr *pThis, *pNxt; + for(pThis=p->pDblStr; pThis; pThis=pNxt){ + pNxt = pThis->pNextStr; sqlite3DbFree(db, pThis); } } @@ -79592,20 +91654,17 @@ SQLITE_PRIVATE void sqlite3VdbeDelete(Vdbe *p){ assert( p!=0 ); db = p->db; + assert( db!=0 ); assert( sqlite3_mutex_held(db->mutex) ); sqlite3VdbeClearObject(db, p); - if( p->pPrev ){ - p->pPrev->pNext = p->pNext; - }else{ - assert( db->pVdbe==p ); - db->pVdbe = p->pNext; - } - if( p->pNext ){ - p->pNext->pPrev = p->pPrev; + if( db->pnBytesFreed==0 ){ + assert( p->ppVPrev!=0 ); + *p->ppVPrev = p->pVNext; + if( p->pVNext ){ + p->pVNext->ppVPrev = p->ppVPrev; + } } - p->magic = VDBE_MAGIC_DEAD; - p->db = 0; - sqlite3DbFreeNN(db, p); + sqlite3DbNNFreeNN(db, p); } /* @@ -79613,7 +91672,7 @@ SQLITE_PRIVATE void sqlite3VdbeDelete(Vdbe *p){ ** carried out. Seek the cursor now. If an error occurs, return ** the appropriate error code. */ -static int SQLITE_NOINLINE handleDeferredMoveto(VdbeCursor *p){ +SQLITE_PRIVATE int SQLITE_NOINLINE sqlite3VdbeFinishMoveto(VdbeCursor *p){ int res, rc; #ifdef SQLITE_TEST extern int sqlite3_search_count; @@ -79621,7 +91680,7 @@ static int SQLITE_NOINLINE handleDeferredMoveto(VdbeCursor *p){ assert( p->deferredMoveto ); assert( p->isTable ); assert( p->eCurType==CURTYPE_BTREE ); - rc = sqlite3BtreeMovetoUnpacked(p->uc.pCursor, 0, p->movetoTarget, 0, &res); + rc = sqlite3BtreeTableMoveto(p->uc.pCursor, p->movetoTarget, 0, &res); if( rc ) return rc; if( res!=0 ) return SQLITE_CORRUPT_BKPT; #ifdef SQLITE_TEST @@ -79639,7 +91698,7 @@ static int SQLITE_NOINLINE handleDeferredMoveto(VdbeCursor *p){ ** is supposed to be pointing. If the row was deleted out from under the ** cursor, set the cursor to point to a NULL row. */ -static int SQLITE_NOINLINE handleMovedCursor(VdbeCursor *p){ +SQLITE_PRIVATE int SQLITE_NOINLINE sqlite3VdbeHandleMovedCursor(VdbeCursor *p){ int isDifferentRow, rc; assert( p->eCurType==CURTYPE_BTREE ); assert( p->uc.pCursor!=0 ); @@ -79655,40 +91714,9 @@ static int SQLITE_NOINLINE handleMovedCursor(VdbeCursor *p){ ** if need be. Return any I/O error from the restore operation. */ SQLITE_PRIVATE int sqlite3VdbeCursorRestore(VdbeCursor *p){ - assert( p->eCurType==CURTYPE_BTREE ); + assert( p->eCurType==CURTYPE_BTREE || IsNullCursor(p) ); if( sqlite3BtreeCursorHasMoved(p->uc.pCursor) ){ - return handleMovedCursor(p); - } - return SQLITE_OK; -} - -/* -** Make sure the cursor p is ready to read or write the row to which it -** was last positioned. Return an error code if an OOM fault or I/O error -** prevents us from positioning the cursor to its correct position. -** -** If a MoveTo operation is pending on the given cursor, then do that -** MoveTo now. If no move is pending, check to see if the row has been -** deleted out from under the cursor and if it has, mark the row as -** a NULL row. -** -** If the cursor is already pointing to the correct row and that row has -** not been deleted out from under the cursor, then this routine is a no-op. -*/ -SQLITE_PRIVATE int sqlite3VdbeCursorMoveto(VdbeCursor **pp, int *piCol){ - VdbeCursor *p = *pp; - assert( p->eCurType==CURTYPE_BTREE || p->eCurType==CURTYPE_PSEUDO ); - if( p->deferredMoveto ){ - int iMap; - if( p->aAltMap && (iMap = p->aAltMap[1+*piCol])>0 ){ - *pp = p->pAltCursor; - *piCol = iMap - 1; - return SQLITE_OK; - } - return handleDeferredMoveto(p); - } - if( sqlite3BtreeCursorHasMoved(p->uc.pCursor) ){ - return handleMovedCursor(p); + return sqlite3VdbeHandleMovedCursor(p); } return SQLITE_OK; } @@ -79699,7 +91727,7 @@ SQLITE_PRIVATE int sqlite3VdbeCursorMoveto(VdbeCursor **pp, int *piCol){ ** sqlite3VdbeSerialType() ** sqlite3VdbeSerialTypeLen() ** sqlite3VdbeSerialLen() -** sqlite3VdbeSerialPut() +** sqlite3VdbeSerialPut() <--- in-lined into OP_MakeRecord as of 2022-04-02 ** sqlite3VdbeSerialGet() ** ** encapsulate the code that serializes values for storage in SQLite @@ -79735,8 +91763,17 @@ SQLITE_PRIVATE int sqlite3VdbeCursorMoveto(VdbeCursor **pp, int *piCol){ ** of SQLite will not understand those serial types. */ +#if 0 /* Inlined into the OP_MakeRecord opcode */ /* ** Return the serial-type for the value stored in pMem. +** +** This routine might convert a large MEM_IntReal value into MEM_Real. +** +** 2019-07-11: The primary user of this subroutine was the OP_MakeRecord +** opcode in the byte-code engine. But by moving this routine in-line, we +** can omit some redundant tests and make that opcode a lot faster. So +** this routine is now only used by the STAT3 logic and STAT3 support has +** ended. The code is kept here for historical reference only. */ SQLITE_PRIVATE u32 sqlite3VdbeSerialType(Mem *pMem, int file_format, u32 *pLen){ int flags = pMem->flags; @@ -79747,11 +91784,13 @@ SQLITE_PRIVATE u32 sqlite3VdbeSerialType(Mem *pMem, int file_format, u32 *pLen){ *pLen = 0; return 0; } - if( flags&MEM_Int ){ + if( flags&(MEM_Int|MEM_IntReal) ){ /* Figure out whether to use 1, 2, 4, 6 or 8 bytes. */ # define MAX_6BYTE ((((i64)0x00008000)<<32)-1) i64 i = pMem->u.i; u64 u; + testcase( flags & MEM_Int ); + testcase( flags & MEM_IntReal ); if( i<0 ){ u = ~i; }else{ @@ -79771,6 +91810,15 @@ SQLITE_PRIVATE u32 sqlite3VdbeSerialType(Mem *pMem, int file_format, u32 *pLen){ if( u<=2147483647 ){ *pLen = 4; return 4; } if( u<=MAX_6BYTE ){ *pLen = 6; return 5; } *pLen = 8; + if( flags&MEM_IntReal ){ + /* If the value is IntReal and is going to take up 8 bytes to store + ** as an integer, then we might as well make it an 8-byte floating + ** point value */ + pMem->u.r = (double)pMem->u.i; + pMem->flags &= ~MEM_IntReal; + pMem->flags |= MEM_Real; + return 7; + } return 6; } if( flags&MEM_Real ){ @@ -79786,12 +91834,13 @@ SQLITE_PRIVATE u32 sqlite3VdbeSerialType(Mem *pMem, int file_format, u32 *pLen){ *pLen = n; return ((n*2) + 12 + ((flags&MEM_Str)!=0)); } +#endif /* inlined into OP_MakeRecord */ /* ** The sizes for serial types less than 128 */ -static const u8 sqlite3SmallTypeSizes[] = { - /* 0 1 2 3 4 5 6 7 8 9 */ +SQLITE_PRIVATE const u8 sqlite3SmallTypeSizes[128] = { + /* 0 1 2 3 4 5 6 7 8 9 */ /* 0 */ 0, 1, 2, 3, 4, 6, 8, 8, 0, 0, /* 10 */ 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, /* 20 */ 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, @@ -79814,19 +91863,19 @@ SQLITE_PRIVATE u32 sqlite3VdbeSerialTypeLen(u32 serial_type){ if( serial_type>=128 ){ return (serial_type-12)/2; }else{ - assert( serial_type<12 + assert( serial_type<12 || sqlite3SmallTypeSizes[serial_type]==(serial_type - 12)/2 ); return sqlite3SmallTypeSizes[serial_type]; } } SQLITE_PRIVATE u8 sqlite3VdbeOneByteSerialTypeLen(u8 serial_type){ assert( serial_type<128 ); - return sqlite3SmallTypeSizes[serial_type]; + return sqlite3SmallTypeSizes[serial_type]; } /* -** If we are on an architecture with mixed-endian floating -** points (ex: ARM7) then swap the lower 4 bytes with the +** If we are on an architecture with mixed-endian floating +** points (ex: ARM7) then swap the lower 4 bytes with the ** upper 4 bytes. Return the result. ** ** For most architectures, this is a no-op. @@ -79848,7 +91897,7 @@ SQLITE_PRIVATE u8 sqlite3VdbeOneByteSerialTypeLen(u8 serial_type){ ** (2007-08-30) Frank van Vugt has studied this problem closely ** and has send his findings to the SQLite developers. Frank ** writes that some Linux kernels offer floating point hardware -** emulation that uses only 32-bit mantissas instead of a full +** emulation that uses only 32-bit mantissas instead of a full ** 48-bits as required by the IEEE standard. (This is the ** CONFIG_FPE_FASTFPE option.) On such systems, floating point ** byte swapping becomes very complicated. To avoid problems, @@ -79859,7 +91908,7 @@ SQLITE_PRIVATE u8 sqlite3VdbeOneByteSerialTypeLen(u8 serial_type){ ** so we trust him. */ #ifdef SQLITE_MIXED_ENDIAN_64BIT_FLOAT -static u64 floatSwap(u64 in){ +SQLITE_PRIVATE u64 sqlite3FloatSwap(u64 in){ union { u64 r; u32 i[2]; @@ -79872,59 +91921,8 @@ static u64 floatSwap(u64 in){ u.i[1] = t; return u.r; } -# define swapMixedEndianFloat(X) X = floatSwap(X) -#else -# define swapMixedEndianFloat(X) -#endif +#endif /* SQLITE_MIXED_ENDIAN_64BIT_FLOAT */ -/* -** Write the serialized data blob for the value stored in pMem into -** buf. It is assumed that the caller has allocated sufficient space. -** Return the number of bytes written. -** -** nBuf is the amount of space left in buf[]. The caller is responsible -** for allocating enough space to buf[] to hold the entire field, exclusive -** of the pMem->u.nZero bytes for a MEM_Zero value. -** -** Return the number of bytes actually written into buf[]. The number -** of bytes in the zero-filled tail is included in the return value only -** if those bytes were zeroed in buf[]. -*/ -SQLITE_PRIVATE u32 sqlite3VdbeSerialPut(u8 *buf, Mem *pMem, u32 serial_type){ - u32 len; - - /* Integer and Real */ - if( serial_type<=7 && serial_type>0 ){ - u64 v; - u32 i; - if( serial_type==7 ){ - assert( sizeof(v)==sizeof(pMem->u.r) ); - memcpy(&v, &pMem->u.r, sizeof(v)); - swapMixedEndianFloat(v); - }else{ - v = pMem->u.i; - } - len = i = sqlite3SmallTypeSizes[serial_type]; - assert( i>0 ); - do{ - buf[--i] = (u8)(v&0xFF); - v >>= 8; - }while( i ); - return len; - } - - /* String or blob */ - if( serial_type>=12 ){ - assert( pMem->n + ((pMem->flags & MEM_Zero)?pMem->u.nZero:0) - == (int)sqlite3VdbeSerialTypeLen(serial_type) ); - len = pMem->n; - if( len>0 ) memcpy(buf, pMem->z, len); - return len; - } - - /* NULL or constants 0 or 1 */ - return 0; -} /* Input "x" is a sequence of unsigned characters that represent a ** big-endian integer. Return the equivalent native integer @@ -79937,14 +91935,14 @@ SQLITE_PRIVATE u32 sqlite3VdbeSerialPut(u8 *buf, Mem *pMem, u32 serial_type){ /* ** Deserialize the data blob pointed to by buf as serial type serial_type -** and store the result in pMem. Return the number of bytes read. +** and store the result in pMem. ** ** This function is implemented as two separate routines for performance. ** The few cases that require local variables are broken out into a separate ** routine so that in most cases the overhead of moving the stack pointer ** is avoided. -*/ -static u32 SQLITE_NOINLINE serialGet( +*/ +static void serialGet( const unsigned char *buf, /* Buffer to deserialize from */ u32 serial_type, /* Serial type to deserialize */ Mem *pMem /* Memory cell to write value into */ @@ -79976,11 +91974,27 @@ static u32 SQLITE_NOINLINE serialGet( assert( sizeof(x)==8 && sizeof(pMem->u.r)==8 ); swapMixedEndianFloat(x); memcpy(&pMem->u.r, &x, sizeof(x)); - pMem->flags = sqlite3IsNaN(pMem->u.r) ? MEM_Null : MEM_Real; + pMem->flags = IsNaN(x) ? MEM_Null : MEM_Real; } - return 8; } -SQLITE_PRIVATE u32 sqlite3VdbeSerialGet( +static int serialGet7( + const unsigned char *buf, /* Buffer to deserialize from */ + Mem *pMem /* Memory cell to write value into */ +){ + u64 x = FOUR_BYTE_UINT(buf); + u32 y = FOUR_BYTE_UINT(buf+4); + x = (x<<32) + y; + assert( sizeof(x)==8 && sizeof(pMem->u.r)==8 ); + swapMixedEndianFloat(x); + memcpy(&pMem->u.r, &x, sizeof(x)); + if( IsNaN(x) ){ + pMem->flags = MEM_Null; + return 1; + } + pMem->flags = MEM_Real; + return 0; +} +SQLITE_PRIVATE void sqlite3VdbeSerialGet( const unsigned char *buf, /* Buffer to deserialize from */ u32 serial_type, /* Serial type to deserialize */ Mem *pMem /* Memory cell to write value into */ @@ -79991,13 +92005,13 @@ SQLITE_PRIVATE u32 sqlite3VdbeSerialGet( pMem->flags = MEM_Null|MEM_Zero; pMem->n = 0; pMem->u.nZero = 0; - break; + return; } case 11: /* Reserved for future use */ case 0: { /* Null */ /* EVIDENCE-OF: R-24078-09375 Value is a NULL. */ pMem->flags = MEM_Null; - break; + return; } case 1: { /* EVIDENCE-OF: R-44885-25196 Value is an 8-bit twos-complement @@ -80005,7 +92019,7 @@ SQLITE_PRIVATE u32 sqlite3VdbeSerialGet( pMem->u.i = ONE_BYTE_INT(buf); pMem->flags = MEM_Int; testcase( pMem->u.i<0 ); - return 1; + return; } case 2: { /* 2-byte signed integer */ /* EVIDENCE-OF: R-49794-35026 Value is a big-endian 16-bit @@ -80013,7 +92027,7 @@ SQLITE_PRIVATE u32 sqlite3VdbeSerialGet( pMem->u.i = TWO_BYTE_INT(buf); pMem->flags = MEM_Int; testcase( pMem->u.i<0 ); - return 2; + return; } case 3: { /* 3-byte signed integer */ /* EVIDENCE-OF: R-37839-54301 Value is a big-endian 24-bit @@ -80021,19 +92035,19 @@ SQLITE_PRIVATE u32 sqlite3VdbeSerialGet( pMem->u.i = THREE_BYTE_INT(buf); pMem->flags = MEM_Int; testcase( pMem->u.i<0 ); - return 3; + return; } case 4: { /* 4-byte signed integer */ /* EVIDENCE-OF: R-01849-26079 Value is a big-endian 32-bit ** twos-complement integer. */ pMem->u.i = FOUR_BYTE_INT(buf); -#ifdef __HP_cc +#ifdef __HP_cc /* Work around a sign-extension bug in the HP compiler for HP/UX */ if( buf[0]&0x80 ) pMem->u.i |= 0xffffffff80000000LL; #endif pMem->flags = MEM_Int; testcase( pMem->u.i<0 ); - return 4; + return; } case 5: { /* 6-byte signed integer */ /* EVIDENCE-OF: R-50385-09674 Value is a big-endian 48-bit @@ -80041,13 +92055,14 @@ SQLITE_PRIVATE u32 sqlite3VdbeSerialGet( pMem->u.i = FOUR_BYTE_UINT(buf+2) + (((i64)1)<<32)*TWO_BYTE_INT(buf); pMem->flags = MEM_Int; testcase( pMem->u.i<0 ); - return 6; + return; } case 6: /* 8-byte signed integer */ case 7: { /* IEEE floating point */ /* These use local variables, so do them in a separate routine ** to avoid having to move the frame pointer in the common case */ - return serialGet(buf,serial_type,pMem); + serialGet(buf,serial_type,pMem); + return; } case 8: /* Integer 0 */ case 9: { /* Integer 1 */ @@ -80055,7 +92070,7 @@ SQLITE_PRIVATE u32 sqlite3VdbeSerialGet( /* EVIDENCE-OF: R-18143-12121 Value is the integer 1. */ pMem->u.i = serial_type-8; pMem->flags = MEM_Int; - return 0; + return; } default: { /* EVIDENCE-OF: R-14606-31564 Value is a BLOB that is (N-12)/2 bytes in @@ -80066,57 +92081,50 @@ SQLITE_PRIVATE u32 sqlite3VdbeSerialGet( pMem->z = (char *)buf; pMem->n = (serial_type-12)/2; pMem->flags = aFlag[serial_type&1]; - return pMem->n; + return; } } - return 0; + return; } /* -** This routine is used to allocate sufficient space for an UnpackedRecord -** structure large enough to be used with sqlite3VdbeRecordUnpack() if -** the first argument is a pointer to KeyInfo structure pKeyInfo. +** Allocate sufficient space for an UnpackedRecord structure large enough +** to hold a decoded index record for pKeyInfo. ** -** The space is either allocated using sqlite3DbMallocRaw() or from within -** the unaligned buffer passed via the second and third arguments (presumably -** stack space). If the former, then *ppFree is set to a pointer that should -** be eventually freed by the caller using sqlite3DbFree(). Or, if the -** allocation comes from the pSpace/szSpace buffer, *ppFree is set to NULL -** before returning. -** -** If an OOM error occurs, NULL is returned. +** The space is allocated using sqlite3DbMallocRaw(). If an OOM error +** occurs, NULL is returned. */ SQLITE_PRIVATE UnpackedRecord *sqlite3VdbeAllocUnpackedRecord( KeyInfo *pKeyInfo /* Description of the record */ ){ UnpackedRecord *p; /* Unpacked record to return */ - int nByte; /* Number of bytes required for *p */ - nByte = ROUND8(sizeof(UnpackedRecord)) + sizeof(Mem)*(pKeyInfo->nKeyField+1); + u64 nByte; /* Number of bytes required for *p */ + assert( sizeof(UnpackedRecord) + sizeof(Mem)*65536 < 0x7fffffff ); + nByte = ROUND8P(sizeof(UnpackedRecord)) + sizeof(Mem)*(pKeyInfo->nKeyField+1); p = (UnpackedRecord *)sqlite3DbMallocRaw(pKeyInfo->db, nByte); if( !p ) return 0; - p->aMem = (Mem*)&((char*)p)[ROUND8(sizeof(UnpackedRecord))]; - assert( pKeyInfo->aSortOrder!=0 ); + p->aMem = (Mem*)&((char*)p)[ROUND8P(sizeof(UnpackedRecord))]; p->pKeyInfo = pKeyInfo; p->nField = pKeyInfo->nKeyField + 1; return p; } /* -** Given the nKey-byte encoding of a record in pKey[], populate the +** Given the nKey-byte encoding of a record in pKey[], populate the ** UnpackedRecord structure indicated by the fourth argument with the ** contents of the decoded record. -*/ +*/ SQLITE_PRIVATE void sqlite3VdbeRecordUnpack( - KeyInfo *pKeyInfo, /* Information about the record format */ int nKey, /* Size of the binary record */ const void *pKey, /* The binary record */ UnpackedRecord *p /* Populate this structure before returning. */ ){ const unsigned char *aKey = (const unsigned char *)pKey; - u32 d; + u32 d; u32 idx; /* Offset in aKey[] to read from */ u16 u; /* Unsigned loop counter */ u32 szHdr; Mem *pMem = p->aMem; + KeyInfo *pKeyInfo = p->pKeyInfo; p->default_rc = 0; assert( EIGHT_BYTE_ALIGNMENT(pMem) ); @@ -80132,17 +92140,20 @@ SQLITE_PRIVATE void sqlite3VdbeRecordUnpack( /* pMem->flags = 0; // sqlite3VdbeSerialGet() will set this for us */ pMem->szMalloc = 0; pMem->z = 0; - d += sqlite3VdbeSerialGet(&aKey[d], serial_type, pMem); - pMem++; + sqlite3VdbeSerialGet(&aKey[d], serial_type, pMem); + d += sqlite3VdbeSerialTypeLen(serial_type); if( (++u)>=p->nField ) break; + pMem++; } if( d>(u32)nKey && u ){ assert( CORRUPT_DB ); - /* In a corrupt record entry, the last pMem might have been set up using + /* In a corrupt record entry, the last pMem might have been set up using ** uninitialized memory. Overwrite its value with NULL, to prevent ** warnings from MSAN. */ - sqlite3VdbeMemSetNull(pMem-1); + sqlite3VdbeMemSetNull(pMem-(unField)); } + testcase( u == pKeyInfo->nKeyField + 1 ); + testcase( u < pKeyInfo->nKeyField + 1 ); assert( u<=pKeyInfo->nKeyField + 1 ); p->nField = u; } @@ -80182,18 +92193,18 @@ static int vdbeRecordCompareDebug( /* Compilers may complain that mem1.u.i is potentially uninitialized. ** We could initialize it, as shown here, to silence those complaints. - ** But in fact, mem1.u.i will never actually be used uninitialized, and doing + ** But in fact, mem1.u.i will never actually be used uninitialized, and doing ** the unnecessary initialization has a measurable negative performance ** impact, since this routine is a very high runner. And so, we choose ** to ignore the compiler warnings and leave this variable uninitialized. */ /* mem1.u.i = 0; // not needed, here to silence compiler warning */ - + idx1 = getVarint32(aKey1, szHdr1); if( szHdr1>98307 ) return SQLITE_CORRUPT; d1 = szHdr1; assert( pKeyInfo->nAllField>=pPKey2->nField || CORRUPT_DB ); - assert( pKeyInfo->aSortOrder!=0 ); + assert( pKeyInfo->aSortFlags!=0 ); assert( pKeyInfo->nKeyField>0 ); assert( idx1<=szHdr1 || CORRUPT_DB ); do{ @@ -80209,14 +92220,24 @@ static int vdbeRecordCompareDebug( ** sqlite3VdbeSerialTypeLen() in the common case. */ if( d1+(u64)serial_type1+2>(u64)nKey1 - && d1+(u64)sqlite3VdbeSerialTypeLen(serial_type1)>(u64)nKey1 + && d1+(u64)sqlite3VdbeSerialTypeLen(serial_type1)>(u64)nKey1 ){ + if( serial_type1>=1 + && serial_type1<=7 + && d1+(u64)sqlite3VdbeSerialTypeLen(serial_type1)<=(u64)nKey1+8 + && CORRUPT_DB + ){ + return 1; /* corrupt record not detected by + ** sqlite3VdbeRecordCompareWithSkip(). Return true + ** to avoid firing the assert() */ + } break; } /* Extract the values to be compared. */ - d1 += sqlite3VdbeSerialGet(&aKey1[d1], serial_type1, &mem1); + sqlite3VdbeSerialGet(&aKey1[d1], serial_type1, &mem1); + d1 += sqlite3VdbeSerialTypeLen(serial_type1); /* Do the comparison */ @@ -80224,7 +92245,12 @@ static int vdbeRecordCompareDebug( pKeyInfo->nAllField>i ? pKeyInfo->aColl[i] : 0); if( rc!=0 ){ assert( mem1.szMalloc==0 ); /* See comment below */ - if( pKeyInfo->aSortOrder[i] ){ + if( (pKeyInfo->aSortFlags[i] & KEYINFO_ORDER_BIGNULL) + && ((mem1.flags & MEM_Null) || (pPKey2->aMem[i].flags & MEM_Null)) + ){ + rc = -rc; + } + if( pKeyInfo->aSortFlags[i] & KEYINFO_ORDER_DESC ){ rc = -rc; /* Invert the result for DESC sort order. */ } goto debugCompareEnd; @@ -80266,7 +92292,7 @@ static int vdbeRecordCompareDebug( ** incorrectly. */ static void vdbeAssertFieldCountWithinLimits( - int nKey, const void *pKey, /* The record to verify */ + int nKey, const void *pKey, /* The record to verify */ const KeyInfo *pKeyInfo /* Compare size with this KeyInfo */ ){ int nField = 0; @@ -80292,9 +92318,35 @@ static void vdbeAssertFieldCountWithinLimits( /* ** Both *pMem1 and *pMem2 contain string values. Compare the two values ** using the collation sequence pColl. As usual, return a negative , zero -** or positive value if *pMem1 is less than, equal to or greater than +** or positive value if *pMem1 is less than, equal to or greater than ** *pMem2, respectively. Similar in spirit to "rc = (*pMem1) - (*pMem2);". */ +static SQLITE_NOINLINE int vdbeCompareMemStringWithEncodingChange( + const Mem *pMem1, + const Mem *pMem2, + const CollSeq *pColl, + u8 *prcErr /* If an OOM occurs, set to SQLITE_NOMEM */ +){ + int rc; + const void *v1, *v2; + Mem c1; + Mem c2; + sqlite3VdbeMemInit(&c1, pMem1->db, MEM_Null); + sqlite3VdbeMemInit(&c2, pMem1->db, MEM_Null); + sqlite3VdbeMemShallowCopy(&c1, pMem1, MEM_Ephem); + sqlite3VdbeMemShallowCopy(&c2, pMem2, MEM_Ephem); + v1 = sqlite3ValueText((sqlite3_value*)&c1, pColl->enc); + v2 = sqlite3ValueText((sqlite3_value*)&c2, pColl->enc); + if( (v1==0 || v2==0) ){ + if( prcErr ) *prcErr = SQLITE_NOMEM_BKPT; + rc = 0; + }else{ + rc = pColl->xCmp(pColl->pUser, c1.n, v1, c2.n, v2); + } + sqlite3VdbeMemReleaseMalloc(&c1); + sqlite3VdbeMemReleaseMalloc(&c2); + return rc; +} static int vdbeCompareMemString( const Mem *pMem1, const Mem *pMem2, @@ -80306,25 +92358,7 @@ static int vdbeCompareMemString( ** comparison function directly */ return pColl->xCmp(pColl->pUser,pMem1->n,pMem1->z,pMem2->n,pMem2->z); }else{ - int rc; - const void *v1, *v2; - Mem c1; - Mem c2; - sqlite3VdbeMemInit(&c1, pMem1->db, MEM_Null); - sqlite3VdbeMemInit(&c2, pMem1->db, MEM_Null); - sqlite3VdbeMemShallowCopy(&c1, pMem1, MEM_Ephem); - sqlite3VdbeMemShallowCopy(&c2, pMem2, MEM_Ephem); - v1 = sqlite3ValueText((sqlite3_value*)&c1, pColl->enc); - v2 = sqlite3ValueText((sqlite3_value*)&c2, pColl->enc); - if( (v1==0 || v2==0) ){ - if( prcErr ) *prcErr = SQLITE_NOMEM_BKPT; - rc = 0; - }else{ - rc = pColl->xCmp(pColl->pUser, c1.n, v1, c2.n, v2); - } - sqlite3VdbeMemRelease(&c1); - sqlite3VdbeMemRelease(&c2); - return rc; + return vdbeCompareMemStringWithEncodingChange(pMem1,pMem2,pColl,prcErr); } } @@ -80373,29 +92407,37 @@ SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3BlobCompare(const Mem *pB1, const Mem return n1 - n2; } +/* The following two functions are used only within testcase() to prove +** test coverage. These functions do no exist for production builds. +** We must use separate SQLITE_NOINLINE functions here, since otherwise +** optimizer code movement causes gcov to become very confused. +*/ +#if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_DEBUG) +static int SQLITE_NOINLINE doubleLt(double a, double b){ return a8 ){ - LONGDOUBLE_TYPE x = (LONGDOUBLE_TYPE)i; - if( xr ) return +1; - return 0; +SQLITE_PRIVATE int sqlite3IntFloatCompare(i64 i, double r){ + if( sqlite3IsNaN(r) ){ + /* SQLite considers NaN to be a NULL. And all integer values are greater + ** than NULL */ + return 1; }else{ i64 y; - double s; if( r<-9223372036854775808.0 ) return +1; if( r>=9223372036854775808.0 ) return -1; y = (i64)r; if( iy ) return +1; - s = (double)i; - if( sr ) return +1; - return 0; + testcase( doubleLt(((double)i),r) ); + testcase( doubleLt(r,((double)i)) ); + testcase( doubleEq(r,((double)i)) ); + return (((double)i)r); } } @@ -80416,7 +92458,7 @@ SQLITE_PRIVATE int sqlite3MemCompare(const Mem *pMem1, const Mem *pMem2, const C f2 = pMem2->flags; combined_flags = f1|f2; assert( !sqlite3VdbeMemIsRowSet(pMem1) && !sqlite3VdbeMemIsRowSet(pMem2) ); - + /* If one value is NULL, it is less than the other. If both values ** are NULL, return 0. */ @@ -80426,8 +92468,13 @@ SQLITE_PRIVATE int sqlite3MemCompare(const Mem *pMem1, const Mem *pMem2, const C /* At least one of the two values is a number */ - if( combined_flags&(MEM_Int|MEM_Real) ){ - if( (f1 & f2 & MEM_Int)!=0 ){ + if( combined_flags&(MEM_Int|MEM_Real|MEM_IntReal) ){ + testcase( combined_flags & MEM_Int ); + testcase( combined_flags & MEM_Real ); + testcase( combined_flags & MEM_IntReal ); + if( (f1 & f2 & (MEM_Int|MEM_IntReal))!=0 ){ + testcase( f1 & f2 & MEM_Int ); + testcase( f1 & f2 & MEM_IntReal ); if( pMem1->u.i < pMem2->u.i ) return -1; if( pMem1->u.i > pMem2->u.i ) return +1; return 0; @@ -80437,15 +92484,23 @@ SQLITE_PRIVATE int sqlite3MemCompare(const Mem *pMem1, const Mem *pMem2, const C if( pMem1->u.r > pMem2->u.r ) return +1; return 0; } - if( (f1&MEM_Int)!=0 ){ + if( (f1&(MEM_Int|MEM_IntReal))!=0 ){ + testcase( f1 & MEM_Int ); + testcase( f1 & MEM_IntReal ); if( (f2&MEM_Real)!=0 ){ return sqlite3IntFloatCompare(pMem1->u.i, pMem2->u.r); + }else if( (f2&(MEM_Int|MEM_IntReal))!=0 ){ + if( pMem1->u.i < pMem2->u.i ) return -1; + if( pMem1->u.i > pMem2->u.i ) return +1; + return 0; }else{ return -1; } } if( (f1&MEM_Real)!=0 ){ - if( (f2&MEM_Int)!=0 ){ + if( (f2&(MEM_Int|MEM_IntReal))!=0 ){ + testcase( f2 & MEM_Int ); + testcase( f2 & MEM_IntReal ); return -sqlite3IntFloatCompare(pMem2->u.i, pMem1->u.r); }else{ return -1; @@ -80466,7 +92521,7 @@ SQLITE_PRIVATE int sqlite3MemCompare(const Mem *pMem1, const Mem *pMem2, const C } assert( pMem1->enc==pMem2->enc || pMem1->db->mallocFailed ); - assert( pMem1->enc==SQLITE_UTF8 || + assert( pMem1->enc==SQLITE_UTF8 || pMem1->enc==SQLITE_UTF16LE || pMem1->enc==SQLITE_UTF16BE ); /* The collation sequence must be defined at this point, even if @@ -80481,7 +92536,7 @@ SQLITE_PRIVATE int sqlite3MemCompare(const Mem *pMem1, const Mem *pMem2, const C /* If a NULL pointer was passed as the collate function, fall through ** to the blob case and use memcmp(). */ } - + /* Both values must be blobs. Compare using memcmp(). */ return sqlite3BlobCompare(pMem1, pMem2); } @@ -80489,7 +92544,7 @@ SQLITE_PRIVATE int sqlite3MemCompare(const Mem *pMem1, const Mem *pMem2, const C /* ** The first argument passed to this function is a serial-type that -** corresponds to an integer - all values between 1 and 9 inclusive +** corresponds to an integer - all values between 1 and 9 inclusive ** except 7. The second points to a buffer containing an integer value ** serialized according to serial_type. This function deserializes ** and returns the value. @@ -80531,7 +92586,7 @@ static i64 vdbeRecordDecodeInt(u32 serial_type, const u8 *aKey){ /* ** This function compares the two table rows or index records ** specified by {nKey1, pKey1} and pPKey2. It returns a negative, zero -** or positive integer if key1 is less than, equal to or +** or positive integer if key1 is less than, equal to or ** greater than key2. The {nKey1, pKey1} key must be a blob ** created by the OP_MakeRecord opcode of the VDBE. The pPKey2 ** key must be a parsed key such as obtained from @@ -80540,12 +92595,12 @@ static i64 vdbeRecordDecodeInt(u32 serial_type, const u8 *aKey){ ** If argument bSkip is non-zero, it is assumed that the caller has already ** determined that the first fields of the keys are equal. ** -** Key1 and Key2 do not have to contain the same number of fields. If all -** fields that appear in both keys are equal, then pPKey2->default_rc is +** Key1 and Key2 do not have to contain the same number of fields. If all +** fields that appear in both keys are equal, then pPKey2->default_rc is ** returned. ** -** If database corruption is discovered, set pPKey2->errCode to -** SQLITE_CORRUPT and return 0. If an OOM error is encountered, +** If database corruption is discovered, set pPKey2->errCode to +** SQLITE_CORRUPT and return 0. If an OOM error is encountered, ** pPKey2->errCode is set to SQLITE_NOMEM and, if it is not NULL, the ** malloc-failed flag set on database handle (pPKey2->pKeyInfo->db). */ @@ -80568,41 +92623,51 @@ SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip( ** two elements in the keys are equal. Fix the various stack variables so ** that this routine begins comparing at the second field. */ if( bSkip ){ - u32 s1; - idx1 = 1 + getVarint32(&aKey1[1], s1); + u32 s1 = aKey1[1]; + if( s1<0x80 ){ + idx1 = 2; + }else{ + idx1 = 1 + sqlite3GetVarint32(&aKey1[1], &s1); + } szHdr1 = aKey1[0]; d1 = szHdr1 + sqlite3VdbeSerialTypeLen(s1); i = 1; pRhs++; }else{ - idx1 = getVarint32(aKey1, szHdr1); + if( (szHdr1 = aKey1[0])<0x80 ){ + idx1 = 1; + }else{ + idx1 = sqlite3GetVarint32(aKey1, &szHdr1); + } d1 = szHdr1; i = 0; } - if( d1>(unsigned)nKey1 ){ + if( d1>(unsigned)nKey1 ){ pPKey2->errCode = (u8)SQLITE_CORRUPT_BKPT; return 0; /* Corruption */ } VVA_ONLY( mem1.szMalloc = 0; ) /* Only needed by assert() statements */ - assert( pPKey2->pKeyInfo->nAllField>=pPKey2->nField + assert( pPKey2->pKeyInfo->nAllField>=pPKey2->nField || CORRUPT_DB ); - assert( pPKey2->pKeyInfo->aSortOrder!=0 ); + assert( pPKey2->pKeyInfo->aSortFlags!=0 ); assert( pPKey2->pKeyInfo->nKeyField>0 ); assert( idx1<=szHdr1 || CORRUPT_DB ); - do{ + while( 1 /*exit-by-break*/ ){ u32 serial_type; /* RHS is an integer */ - if( pRhs->flags & MEM_Int ){ + if( pRhs->flags & (MEM_Int|MEM_IntReal) ){ + testcase( pRhs->flags & MEM_Int ); + testcase( pRhs->flags & MEM_IntReal ); serial_type = aKey1[idx1]; testcase( serial_type==12 ); if( serial_type>=10 ){ - rc = +1; + rc = serial_type==10 ? -1 : +1; }else if( serial_type==0 ){ rc = -1; }else if( serial_type==7 ){ - sqlite3VdbeSerialGet(&aKey1[d1], serial_type, &mem1); + serialGet7(&aKey1[d1], &mem1); rc = -sqlite3IntFloatCompare(pRhs->u.i, mem1.u.r); }else{ i64 lhs = vdbeRecordDecodeInt(serial_type, &aKey1[d1]); @@ -80620,21 +92685,25 @@ SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip( serial_type = aKey1[idx1]; if( serial_type>=10 ){ /* Serial types 12 or greater are strings and blobs (greater than - ** numbers). Types 10 and 11 are currently "reserved for future + ** numbers). Types 10 and 11 are currently "reserved for future ** use", so it doesn't really matter what the results of comparing - ** them to numberic values are. */ - rc = +1; + ** them to numeric values are. */ + rc = serial_type==10 ? -1 : +1; }else if( serial_type==0 ){ rc = -1; }else{ - sqlite3VdbeSerialGet(&aKey1[d1], serial_type, &mem1); if( serial_type==7 ){ - if( mem1.u.ru.r ){ + if( serialGet7(&aKey1[d1], &mem1) ){ + rc = -1; /* mem1 is a NaN */ + }else if( mem1.u.ru.r ){ rc = -1; }else if( mem1.u.r>pRhs->u.r ){ rc = +1; + }else{ + assert( rc==0 ); } }else{ + sqlite3VdbeSerialGet(&aKey1[d1], serial_type, &mem1); rc = sqlite3IntFloatCompare(mem1.u.i, pRhs->u.r); } } @@ -80642,7 +92711,7 @@ SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip( /* RHS is a string */ else if( pRhs->flags & MEM_Str ){ - getVarint32(&aKey1[idx1], serial_type); + getVarint32NR(&aKey1[idx1], serial_type); testcase( serial_type==12 ); if( serial_type<12 ){ rc = -1; @@ -80668,7 +92737,7 @@ SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip( }else{ int nCmp = MIN(mem1.n, pRhs->n); rc = memcmp(&aKey1[d1], pRhs->z, nCmp); - if( rc==0 ) rc = mem1.n - pRhs->n; + if( rc==0 ) rc = mem1.n - pRhs->n; } } } @@ -80676,7 +92745,7 @@ SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip( /* RHS is a blob */ else if( pRhs->flags & MEM_Blob ){ assert( (pRhs->flags & MEM_Zero)==0 || pRhs->n==0 ); - getVarint32(&aKey1[idx1], serial_type); + getVarint32NR(&aKey1[idx1], serial_type); testcase( serial_type==12 ); if( serial_type<12 || (serial_type & 0x01) ){ rc = -1; @@ -80704,12 +92773,25 @@ SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip( /* RHS is null */ else{ serial_type = aKey1[idx1]; - rc = (serial_type!=0); + if( serial_type==0 + || serial_type==10 + || (serial_type==7 && serialGet7(&aKey1[d1], &mem1)!=0) + ){ + assert( rc==0 ); + }else{ + rc = 1; + } } if( rc!=0 ){ - if( pPKey2->pKeyInfo->aSortOrder[i] ){ - rc = -rc; + int sortFlags = pPKey2->pKeyInfo->aSortFlags[i]; + if( sortFlags ){ + if( (sortFlags & KEYINFO_ORDER_BIGNULL)==0 + || ((sortFlags & KEYINFO_ORDER_DESC) + !=(serial_type==0 || (pRhs->flags&MEM_Null))) + ){ + rc = -rc; + } } assert( vdbeRecordCompareDebug(nKey1, pKey1, pPKey2, rc) ); assert( mem1.szMalloc==0 ); /* See comment below */ @@ -80720,8 +92802,13 @@ SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip( if( i==pPKey2->nField ) break; pRhs++; d1 += sqlite3VdbeSerialTypeLen(serial_type); + if( d1>(unsigned)nKey1 ) break; idx1 += sqlite3VarintLen(serial_type); - }while( idx1<(unsigned)szHdr1 && d1<=(unsigned)nKey1 ); + if( idx1>=(unsigned)szHdr1 ){ + pPKey2->errCode = (u8)SQLITE_CORRUPT_BKPT; + return 0; /* Corrupt index */ + } + } /* No memory allocation is ever used on mem1. Prove this using ** the following assert(). If the assert() fails, it indicates a @@ -80731,8 +92818,8 @@ SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip( /* rc==0 here means that one or both of the keys ran out of fields and ** all the fields up to that point were equal. Return the default_rc ** value. */ - assert( CORRUPT_DB - || vdbeRecordCompareDebug(nKey1, pKey1, pPKey2, pPKey2->default_rc) + assert( CORRUPT_DB + || vdbeRecordCompareDebug(nKey1, pKey1, pPKey2, pPKey2->default_rc) || pPKey2->pKeyInfo->db->mallocFailed ); pPKey2->eqSeen = 1; @@ -80747,8 +92834,8 @@ SQLITE_PRIVATE int sqlite3VdbeRecordCompare( /* -** This function is an optimized version of sqlite3VdbeRecordCompare() -** that (a) the first field of pPKey2 is an integer, and (b) the +** This function is an optimized version of sqlite3VdbeRecordCompare() +** that (a) the first field of pPKey2 is an integer, and (b) the ** size-of-header varint at the start of (pKey1/nKey1) fits in a single ** byte (i.e. is less than 128). ** @@ -80803,7 +92890,7 @@ static int vdbeRecordCompareInt( testcase( lhs<0 ); break; } - case 8: + case 8: lhs = 0; break; case 9: @@ -80811,11 +92898,11 @@ static int vdbeRecordCompareInt( break; /* This case could be removed without changing the results of running - ** this code. Including it causes gcc to generate a faster switch + ** this code. Including it causes gcc to generate a faster switch ** statement (since the range of switch targets now starts at zero and ** is contiguous) but does not cause any duplicate code to be generated - ** (as gcc is clever enough to combine the two like cases). Other - ** compilers might be similar. */ + ** (as gcc is clever enough to combine the two like cases). Other + ** compilers might be similar. */ case 0: case 7: return sqlite3VdbeRecordCompare(nKey1, pKey1, pPKey2); @@ -80823,13 +92910,14 @@ static int vdbeRecordCompareInt( return sqlite3VdbeRecordCompare(nKey1, pKey1, pPKey2); } - v = pPKey2->aMem[0].u.i; + assert( pPKey2->u.i == pPKey2->aMem[0].u.i ); + v = pPKey2->u.i; if( v>lhs ){ res = pPKey2->r1; }else if( vr2; }else if( pPKey2->nField>1 ){ - /* The first fields of the two keys are equal. Compare the trailing + /* The first fields of the two keys are equal. Compare the trailing ** fields. */ res = sqlite3VdbeRecordCompareWithSkip(nKey1, pKey1, pPKey2, 1); }else{ @@ -80844,9 +92932,9 @@ static int vdbeRecordCompareInt( } /* -** This function is an optimized version of sqlite3VdbeRecordCompare() +** This function is an optimized version of sqlite3VdbeRecordCompare() ** that (a) the first field of pPKey2 is a string, that (b) the first field -** uses the collation sequence BINARY and (c) that the size-of-header varint +** uses the collation sequence BINARY and (c) that the size-of-header varint ** at the start of (pKey1/nKey1) fits in a single byte. */ static int vdbeRecordCompareString( @@ -80858,11 +92946,20 @@ static int vdbeRecordCompareString( int res; assert( pPKey2->aMem[0].flags & MEM_Str ); + assert( pPKey2->aMem[0].n == pPKey2->n ); + assert( pPKey2->aMem[0].z == pPKey2->u.z ); vdbeAssertFieldCountWithinLimits(nKey1, pKey1, pPKey2->pKeyInfo); - getVarint32(&aKey1[1], serial_type); + serial_type = (signed char)(aKey1[1]); + +vrcs_restart: if( serial_type<12 ){ + if( serial_type<0 ){ + sqlite3GetVarint32(&aKey1[1], (u32*)&serial_type); + if( serial_type>=12 ) goto vrcs_restart; + assert( CORRUPT_DB ); + } res = pPKey2->r1; /* (pKey1/nKey1) is a number or a null */ - }else if( !(serial_type & 0x01) ){ + }else if( !(serial_type & 0x01) ){ res = pPKey2->r2; /* (pKey1/nKey1) is a blob */ }else{ int nCmp; @@ -80874,11 +92971,15 @@ static int vdbeRecordCompareString( pPKey2->errCode = (u8)SQLITE_CORRUPT_BKPT; return 0; /* Corruption */ } - nCmp = MIN( pPKey2->aMem[0].n, nStr ); - res = memcmp(&aKey1[szHdr], pPKey2->aMem[0].z, nCmp); + nCmp = MIN( pPKey2->n, nStr ); + res = memcmp(&aKey1[szHdr], pPKey2->u.z, nCmp); - if( res==0 ){ - res = nStr - pPKey2->aMem[0].n; + if( res>0 ){ + res = pPKey2->r2; + }else if( res<0 ){ + res = pPKey2->r1; + }else{ + res = nStr - pPKey2->n; if( res==0 ){ if( pPKey2->nField>1 ){ res = sqlite3VdbeRecordCompareWithSkip(nKey1, pKey1, pPKey2, 1); @@ -80891,10 +92992,6 @@ static int vdbeRecordCompareString( }else{ res = pPKey2->r1; } - }else if( res>0 ){ - res = pPKey2->r2; - }else{ - res = pPKey2->r1; } } @@ -80914,7 +93011,7 @@ SQLITE_PRIVATE RecordCompare sqlite3VdbeFindCompare(UnpackedRecord *p){ /* varintRecordCompareInt() and varintRecordCompareString() both assume ** that the size-of-header varint that occurs at the start of each record ** fits in a single byte (i.e. is 127 or less). varintRecordCompareInt() - ** also assumes that it is safe to overread a buffer by at least the + ** also assumes that it is safe to overread a buffer by at least the ** maximum possible legal header size plus 8 bytes. Because there is ** guaranteed to be at least 74 (but not 136) bytes of padding following each ** buffer passed to varintRecordCompareInt() this makes it convenient to @@ -80924,9 +93021,13 @@ SQLITE_PRIVATE RecordCompare sqlite3VdbeFindCompare(UnpackedRecord *p){ ** The easiest way to enforce this limit is to consider only records with ** 13 fields or less. If the first field is an integer, the maximum legal ** header size is (12*5 + 1 + 1) bytes. */ + assert( p->pKeyInfo->aSortFlags!=0 ); if( p->pKeyInfo->nAllField<=13 ){ int flags = p->aMem[0].flags; - if( p->pKeyInfo->aSortOrder[0] ){ + if( p->pKeyInfo->aSortFlags[0] ){ + if( p->pKeyInfo->aSortFlags[0] & KEYINFO_ORDER_BIGNULL ){ + return sqlite3VdbeRecordCompare; + } p->r1 = 1; p->r2 = -1; }else{ @@ -80934,13 +93035,18 @@ SQLITE_PRIVATE RecordCompare sqlite3VdbeFindCompare(UnpackedRecord *p){ p->r2 = 1; } if( (flags & MEM_Int) ){ + p->u.i = p->aMem[0].u.i; return vdbeRecordCompareInt; } testcase( flags & MEM_Real ); testcase( flags & MEM_Null ); testcase( flags & MEM_Blob ); - if( (flags & (MEM_Real|MEM_Null|MEM_Blob))==0 && p->pKeyInfo->aColl[0]==0 ){ + if( (flags & (MEM_Real|MEM_IntReal|MEM_Null|MEM_Blob))==0 + && p->pKeyInfo->aColl[0]==0 + ){ assert( flags & MEM_Str ); + p->u.z = p->aMem[0].z; + p->n = p->aMem[0].n; return vdbeRecordCompareString; } } @@ -80967,7 +93073,7 @@ SQLITE_PRIVATE int sqlite3VdbeIdxRowid(sqlite3 *db, BtCursor *pCur, i64 *rowid){ /* Get the size of the index entry. Only indices entries of less ** than 2GiB are support - anything large must be database corruption. ** Any corruption is detected in sqlite3BtreeParseCellPtr(), though, so - ** this code can safely assume that nCellKey is 32-bits + ** this code can safely assume that nCellKey is 32-bits */ assert( sqlite3BtreeCursorIsValid(pCur) ); nCellKey = sqlite3BtreePayloadSize(pCur); @@ -80975,15 +93081,15 @@ SQLITE_PRIVATE int sqlite3VdbeIdxRowid(sqlite3 *db, BtCursor *pCur, i64 *rowid){ /* Read in the complete content of the index entry */ sqlite3VdbeMemInit(&m, db, 0); - rc = sqlite3VdbeMemFromBtree(pCur, 0, (u32)nCellKey, &m); + rc = sqlite3VdbeMemFromBtreeZeroOffset(pCur, (u32)nCellKey, &m); if( rc ){ return rc; } /* The index entry must begin with a header size */ - (void)getVarint32((u8*)m.z, szHdr); + getVarint32NR((u8*)m.z, szHdr); testcase( szHdr==3 ); - testcase( szHdr==m.n ); + testcase( szHdr==(u32)m.n ); testcase( szHdr>0x7fffffff ); assert( m.n>=0 ); if( unlikely(szHdr<3 || szHdr>(unsigned)m.n) ){ @@ -80992,7 +93098,7 @@ SQLITE_PRIVATE int sqlite3VdbeIdxRowid(sqlite3 *db, BtCursor *pCur, i64 *rowid){ /* The last field of the index should be an integer - the ROWID. ** Verify that the last entry really is an integer. */ - (void)getVarint32((u8*)&m.z[szHdr-1], typeRowid); + getVarint32NR((u8*)&m.z[szHdr-1], typeRowid); testcase( typeRowid==1 ); testcase( typeRowid==2 ); testcase( typeRowid==3 ); @@ -81013,14 +93119,14 @@ SQLITE_PRIVATE int sqlite3VdbeIdxRowid(sqlite3 *db, BtCursor *pCur, i64 *rowid){ /* Fetch the integer off the end of the index record */ sqlite3VdbeSerialGet((u8*)&m.z[m.n-lenRowid], typeRowid, &v); *rowid = v.u.i; - sqlite3VdbeMemRelease(&m); + sqlite3VdbeMemReleaseMalloc(&m); return SQLITE_OK; /* Jump here if database corruption is detected after m has been ** allocated. Free the m object and return SQLITE_CORRUPT. */ idx_rowid_corruption: testcase( m.szMalloc!=0 ); - sqlite3VdbeMemRelease(&m); + sqlite3VdbeMemReleaseMalloc(&m); return SQLITE_CORRUPT_BKPT; } @@ -81032,7 +93138,7 @@ SQLITE_PRIVATE int sqlite3VdbeIdxRowid(sqlite3 *db, BtCursor *pCur, i64 *rowid){ ** ** pUnpacked is either created without a rowid or is truncated so that it ** omits the rowid at the end. The rowid at the end of the index entry -** is ignored as well. Hence, this routine only compares the prefixes +** is ignored as well. Hence, this routine only compares the prefixes ** of the keys prior to the final rowid, not the entire key. */ SQLITE_PRIVATE int sqlite3VdbeIdxKeyCompare( @@ -81057,20 +93163,20 @@ SQLITE_PRIVATE int sqlite3VdbeIdxKeyCompare( return SQLITE_CORRUPT_BKPT; } sqlite3VdbeMemInit(&m, db, 0); - rc = sqlite3VdbeMemFromBtree(pCur, 0, (u32)nCellKey, &m); + rc = sqlite3VdbeMemFromBtreeZeroOffset(pCur, (u32)nCellKey, &m); if( rc ){ return rc; } *res = sqlite3VdbeRecordCompareWithSkip(m.n, m.z, pUnpacked, 0); - sqlite3VdbeMemRelease(&m); + sqlite3VdbeMemReleaseMalloc(&m); return SQLITE_OK; } /* ** This routine sets the value to be returned by subsequent calls to -** sqlite3_changes() on the database handle 'db'. +** sqlite3_changes() on the database handle 'db'. */ -SQLITE_PRIVATE void sqlite3VdbeSetChanges(sqlite3 *db, int nChange){ +SQLITE_PRIVATE void sqlite3VdbeSetChanges(sqlite3 *db, i64 nChange){ assert( sqlite3_mutex_held(db->mutex) ); db->nChange = nChange; db->nTotalChange += nChange; @@ -81104,7 +93210,7 @@ SQLITE_PRIVATE void sqlite3VdbeCountChanges(Vdbe *v){ */ SQLITE_PRIVATE void sqlite3ExpirePreparedStatements(sqlite3 *db, int iCode){ Vdbe *p; - for(p = db->pVdbe; p; p=p->pNext){ + for(p = db->pVdbe; p; p=p->pVNext){ p->expired = iCode+1; } } @@ -81125,7 +93231,7 @@ SQLITE_PRIVATE u8 sqlite3VdbePrepareFlags(Vdbe *v){ /* ** Return a pointer to an sqlite3_value structure containing the value bound -** parameter iVar of VM v. Except, if the value is an SQL NULL, return +** parameter iVar of VM v. Except, if the value is an SQL NULL, return ** 0 instead. Unless it is NULL, apply affinity aff (one of the SQLITE_AFF_* ** constants) to the value before returning it. ** @@ -81135,7 +93241,8 @@ SQLITE_PRIVATE sqlite3_value *sqlite3VdbeGetBoundValue(Vdbe *v, int iVar, u8 aff assert( iVar>0 ); if( v ){ Mem *pMem = &v->aVar[iVar-1]; - assert( (v->db->flags & SQLITE_EnableQPSG)==0 ); + assert( (v->db->flags & SQLITE_EnableQPSG)==0 + || (v->db->mDbFlags & DBFLAG_InternalFunc)!=0 ); if( 0==(pMem->flags & MEM_Null) ){ sqlite3_value *pRet = sqlite3ValueNew(v->db); if( pRet ){ @@ -81155,7 +93262,8 @@ SQLITE_PRIVATE sqlite3_value *sqlite3VdbeGetBoundValue(Vdbe *v, int iVar, u8 aff */ SQLITE_PRIVATE void sqlite3VdbeSetVarmask(Vdbe *v, int iVar){ assert( iVar>0 ); - assert( (v->db->flags & SQLITE_EnableQPSG)==0 ); + assert( (v->db->flags & SQLITE_EnableQPSG)==0 + || (v->db->mDbFlags & DBFLAG_InternalFunc)!=0 ); if( iVar>=32 ){ v->expmask |= 0x80000000; }else{ @@ -81163,6 +93271,224 @@ SQLITE_PRIVATE void sqlite3VdbeSetVarmask(Vdbe *v, int iVar){ } } +/* +** Helper function for vdbeIsMatchingIndexKey(). Return true if column +** iCol should be ignored when comparing a record with a record from +** an index on disk. The field should be ignored if: +** +** * the corresponding bit in mask is set, and +** * either: +** - bIntegrity is false, or +** - the two Mem values are both real values that differ by +** BTREE_ULPDISTORTION or fewer ULPs. +*/ +static int vdbeSkipField( + Bitmask mask, /* Mask of indexed expression fields */ + int iCol, /* Column of index being considered */ + Mem *pMem1, /* Expected index value */ + Mem *pMem2, /* Actual indexed value */ + int bIntegrity /* True if running PRAGMA integrity_check */ +){ +#define BTREE_ULPDISTORTION 2 + if( iCol>=BMS || (mask & MASKBIT(iCol))==0 ) return 0; + if( bIntegrity==0 ) return 1; + if( (pMem1->flags & MEM_Real) && (pMem2->flags & MEM_Real) ){ + u64 m1, m2; + memcpy(&m1,&pMem1->u.r,8); + memcpy(&m2,&pMem2->u.r,8); + if( (m1pKeyInfo->enc; + mem.db = p->pKeyInfo->db; + nRec = sqlite3BtreePayloadSize(pCur); + if( nRec>0x7fffffff ){ + return SQLITE_CORRUPT_BKPT; + } + + /* Allocate 5 extra bytes at the end of the buffer. This allows the + ** getVarint32() call below to read slightly past the end of the buffer + ** if the record is corrupt. */ + aRec = sqlite3MallocZero(nRec+5); + if( aRec==0 ){ + rc = SQLITE_NOMEM_BKPT; + }else{ + rc = sqlite3BtreePayload(pCur, 0, nRec, aRec); + } + + if( rc==SQLITE_OK ){ + u32 szHdr = 0; /* Size of record header in bytes */ + u32 idxHdr = 0; /* Current index in header */ + + idxHdr = getVarint32(aRec, szHdr); + if( szHdr>98307 ){ + rc = SQLITE_CORRUPT; + }else{ + int res = 0; /* Result of this function call */ + u32 idxRec = szHdr; /* Index of next field in record body */ + int ii = 0; /* Iterator variable */ + + int nCol = p->pKeyInfo->nAllField; + for(ii=0; ii=szHdr ){ + rc = SQLITE_CORRUPT_BKPT; + break; + } + idxHdr += getVarint32(&aRec[idxHdr], iSerial); + nSerial = sqlite3VdbeSerialTypeLen(iSerial); + if( (idxRec+nSerial)>nRec ){ + rc = SQLITE_CORRUPT_BKPT; + }else{ + sqlite3VdbeSerialGet(&aRec[idxRec], iSerial, &mem); + if( vdbeSkipField(mask, ii, &p->aMem[ii], &mem, bInt)==0 ){ + res = sqlite3MemCompare(&mem, &p->aMem[ii], p->pKeyInfo->aColl[ii]); + if( res!=0 ) break; + } + } + idxRec += sqlite3VdbeSerialTypeLen(iSerial); + } + + *piRes = res; + } + } + + sqlite3_free(aRec); + return rc; +} + +/* +** This is called when the record in (*p) should be found in the index +** opened by cursor pCur, but was not. This may happen as part of a DELETE +** operation or an integrity check. +** +** One reason that an exact match was not found may be the EIIB bug - that +** a text-to-float conversion may have caused a real value in record (*p) +** to be slightly different from its counterpart on disk. This function +** attempts to find the right index record. If it does find the right +** record, it leaves *pCur pointing to it and sets (*pRes) to 0 before +** returning. Otherwise, (*pRes) is set to non-zero and an SQLite error +** code returned. +** +** The algorithm used to find the correct record is: +** +** * Scan up to BTREE_FDK_RANGE entries either side of the current entry. +** If parameter bIntegrity is false, then all fields that are indexed +** expressions or virtual table columns are omitted from the comparison. +** If bIntegrity is true, then small differences in real values in +** such fields are overlooked, but they are not omitted from the comparison +** altogether. +** +** * If the above fails to find an entry and bIntegrity is false, search +** the entire index. +*/ +SQLITE_PRIVATE int sqlite3VdbeFindIndexKey( + BtCursor *pCur, + Index *pIdx, + UnpackedRecord *p, + int *pRes, + int bIntegrity +){ +#define BTREE_FDK_RANGE 10 + int nStep = 0; + int res = 1; + int rc = SQLITE_OK; + int ii = 0; + + /* Calculate a mask based on the first 64 columns of the index. The mask + ** bit is set if the corresponding index field is either an expression + ** or a virtual column of the table. */ + Bitmask mask = 0; + for(ii=0; iinColumn, BMS); ii++){ + int iCol = pIdx->aiColumn[ii]; + if( (iCol==XN_EXPR) + || (iCol>=0 && (pIdx->pTable->aCol[iCol].colFlags & COLFLAG_VIRTUAL)) + ){ + mask |= MASKBIT(ii); + } + } + + /* If the mask is 0 at this point, then the index contains no expressions + ** or virtual columns. So do not search for a match - return so that the + ** caller may declare the db corrupt immediately. Or, if mask is non-zero, + ** proceed. */ + if( mask!=0 ){ + + /* Move the cursor back BTREE_FDK_RANGE entries. If this hits an EOF, + ** position the cursor at the first entry in the index and set nStep + ** to -1 so that the first loop below scans the entire index. Otherwise, + ** set nStep to BTREE_FDK_RANGE*2 so that the first loop below scans + ** just that many entries. */ + for(ii=0; sqlite3BtreeEof(pCur)==0 && ii=0), or the entire index if (nStep<0). */ + while( sqlite3BtreeCursorIsValidNN(pCur) ){ + for(ii=0; rc==SQLITE_OK && (iipVdbe==0 ) return 1; #endif - if( pCtx->pVdbe->aOp[pCtx->iOp].opcode==OP_PureFunc ){ - sqlite3_result_error(pCtx, - "non-deterministic function in index expression or CHECK constraint", - -1); + pOp = pCtx->pVdbe->aOp + pCtx->iOp; + if( pOp->opcode==OP_PureFunc ){ + const char *zContext; + char *zMsg; + if( pOp->p5 & NC_IsCheck ){ + zContext = "a CHECK constraint"; + }else if( pOp->p5 & NC_GenCol ){ + zContext = "a generated column"; + }else{ + zContext = "an index"; + } + zMsg = sqlite3_mprintf("non-deterministic use of %s() in %s", + pCtx->pFunc->zName, zContext); + sqlite3_result_error(pCtx, zMsg, -1); + sqlite3_free(zMsg); return 0; } return 1; } +#endif /* SQLITE_OMIT_DATETIME_FUNCS */ + +#if defined(SQLITE_ENABLE_CURSOR_HINTS) && defined(SQLITE_DEBUG) +/* +** This Walker callback is used to help verify that calls to +** sqlite3BtreeCursorHint() with opcode BTREE_HINT_RANGE have +** byte-code register values correctly initialized. +*/ +SQLITE_PRIVATE int sqlite3CursorRangeHintExprCheck(Walker *pWalker, Expr *pExpr){ + if( pExpr->op==TK_REGISTER ){ + assert( (pWalker->u.aMem[pExpr->iTable].flags & MEM_Undefined)==0 ); + } + return WRC_Continue; +} +#endif /* SQLITE_ENABLE_CURSOR_HINTS && SQLITE_DEBUG */ #ifndef SQLITE_OMIT_VIRTUALTABLE /* @@ -81205,7 +93558,7 @@ SQLITE_PRIVATE void sqlite3VtabImportErrmsg(Vdbe *p, sqlite3_vtab *pVtab){ #ifdef SQLITE_ENABLE_PREUPDATE_HOOK /* -** If the second argument is not NULL, release any allocations associated +** If the second argument is not NULL, release any allocations associated ** with the memory cells in the p->aMem[] array. Also free the UnpackedRecord ** structure itself, using sqlite3DbFree(). ** @@ -81213,13 +93566,14 @@ SQLITE_PRIVATE void sqlite3VtabImportErrmsg(Vdbe *p, sqlite3_vtab *pVtab){ ** the vdbeUnpackRecord() function found in vdbeapi.c. */ static void vdbeFreeUnpacked(sqlite3 *db, int nField, UnpackedRecord *p){ + assert( db!=0 ); if( p ){ int i; for(i=0; iaMem[i]; - if( pMem->zMalloc ) sqlite3VdbeMemRelease(pMem); + if( pMem->zMalloc ) sqlite3VdbeMemReleaseMalloc(pMem); } - sqlite3DbFreeNN(db, p); + sqlite3DbNNFreeNN(db, p); } } #endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ @@ -81238,13 +93592,23 @@ SQLITE_PRIVATE void sqlite3VdbePreUpdateHook( const char *zDb, /* Database name */ Table *pTab, /* Modified table */ i64 iKey1, /* Initial key value */ - int iReg /* Register for new.* record */ + int iReg, /* Register for new.* record */ + int iBlobWrite ){ sqlite3 *db = v->db; i64 iKey2; PreUpdate preupdate; const char *zTbl = pTab->zName; - static const u8 fakeSortOrder = 0; +#ifdef SQLITE_DEBUG + int nRealCol; + if( pTab->tabFlags & TF_WithoutRowid ){ + nRealCol = sqlite3PrimaryKeyIndex(pTab)->nColumn; + }else if( pTab->tabFlags & TF_HasVirtual ){ + nRealCol = pTab->nNVCol; + }else{ + nRealCol = pTab->nCol; + } +#endif assert( db->pPreUpdate==0 ); memset(&preupdate, 0, sizeof(PreUpdate)); @@ -81259,38 +93623,61 @@ SQLITE_PRIVATE void sqlite3VdbePreUpdateHook( } } - assert( pCsr->nField==pTab->nCol - || (pCsr->nField==pTab->nCol+1 && op==SQLITE_DELETE && iReg==-1) + assert( pCsr!=0 ); + assert( pCsr->eCurType==CURTYPE_BTREE ); + assert( pCsr->nField==nRealCol + || (pCsr->nField==nRealCol+1 && op==SQLITE_DELETE && iReg==-1) ); preupdate.v = v; preupdate.pCsr = pCsr; preupdate.op = op; preupdate.iNewReg = iReg; - preupdate.keyinfo.db = db; - preupdate.keyinfo.enc = ENC(db); - preupdate.keyinfo.nKeyField = pTab->nCol; - preupdate.keyinfo.aSortOrder = (u8*)&fakeSortOrder; + preupdate.pKeyinfo = (KeyInfo*)&preupdate.uKey; + preupdate.pKeyinfo->db = db; + preupdate.pKeyinfo->enc = ENC(db); + preupdate.pKeyinfo->nKeyField = pTab->nCol; + preupdate.pKeyinfo->aSortFlags = 0; /* Indicate .aColl, .nAllField uninit */ preupdate.iKey1 = iKey1; preupdate.iKey2 = iKey2; preupdate.pTab = pTab; + preupdate.iBlobWrite = iBlobWrite; db->pPreUpdate = &preupdate; db->xPreUpdateCallback(db->pPreUpdateArg, db, op, zDb, zTbl, iKey1, iKey2); db->pPreUpdate = 0; sqlite3DbFree(db, preupdate.aRecord); - vdbeFreeUnpacked(db, preupdate.keyinfo.nKeyField+1, preupdate.pUnpacked); - vdbeFreeUnpacked(db, preupdate.keyinfo.nKeyField+1, preupdate.pNewUnpacked); + vdbeFreeUnpacked(db, preupdate.pKeyinfo->nKeyField+1,preupdate.pUnpacked); + vdbeFreeUnpacked(db, preupdate.pKeyinfo->nKeyField+1,preupdate.pNewUnpacked); + sqlite3VdbeMemRelease(&preupdate.oldipk); if( preupdate.aNew ){ int i; for(i=0; inField; i++){ sqlite3VdbeMemRelease(&preupdate.aNew[i]); } - sqlite3DbFreeNN(db, preupdate.aNew); + sqlite3DbNNFreeNN(db, preupdate.aNew); + } + if( preupdate.apDflt ){ + int i; + for(i=0; inCol; i++){ + sqlite3ValueFree(preupdate.apDflt[i]); + } + sqlite3DbFree(db, preupdate.apDflt); } } #endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ +#ifdef SQLITE_ENABLE_PERCENTILE +/* +** Return the name of an SQL function associated with the sqlite3_context. +*/ +SQLITE_PRIVATE const char *sqlite3VdbeFuncName(const sqlite3_context *pCtx){ + assert( pCtx!=0 ); + assert( pCtx->pFunc!=0 ); + return pCtx->pFunc->zName; +} +#endif /* SQLITE_ENABLE_PERCENTILE */ + /************** End of vdbeaux.c *********************************************/ /************** Begin file vdbeapi.c *****************************************/ /* @@ -81310,6 +93697,7 @@ SQLITE_PRIVATE void sqlite3VdbePreUpdateHook( */ /* #include "sqliteInt.h" */ /* #include "vdbeInt.h" */ +/* #include "opcodes.h" */ #ifndef SQLITE_OMIT_DEPRECATED /* @@ -81321,8 +93709,14 @@ SQLITE_PRIVATE void sqlite3VdbePreUpdateHook( ** added or changed. */ SQLITE_API int sqlite3_expired(sqlite3_stmt *pStmt){ - Vdbe *p = (Vdbe*)pStmt; - return p==0 || p->expired; + int iRet = 1; + if( pStmt ){ + Vdbe *p = (Vdbe*)pStmt; + sqlite3_mutex_enter(p->db->mutex); + iRet = p->expired; + sqlite3_mutex_leave(p->db->mutex); + } + return iRet; } #endif @@ -81357,7 +93751,6 @@ static SQLITE_NOINLINE void invokeProfileCallback(sqlite3 *db, Vdbe *p){ sqlite3_int64 iNow; sqlite3_int64 iElapse; assert( p->startTime>0 ); - assert( (db->mTrace & (SQLITE_TRACE_PROFILE|SQLITE_TRACE_XPROFILE))!=0 ); assert( db->init.busy==0 ); assert( p->zSql!=0 ); sqlite3OsCurrentTimeInt64(db->pVfs, &iNow); @@ -81368,7 +93761,7 @@ static SQLITE_NOINLINE void invokeProfileCallback(sqlite3 *db, Vdbe *p){ } #endif if( db->mTrace & SQLITE_TRACE_PROFILE ){ - db->xTrace(SQLITE_TRACE_PROFILE, db->pTraceArg, p, (void*)&iElapse); + db->trace.xV2(SQLITE_TRACE_PROFILE, db->pTraceArg, p, (void*)&iElapse); } p->startTime = 0; } @@ -81403,7 +93796,9 @@ SQLITE_API int sqlite3_finalize(sqlite3_stmt *pStmt){ if( vdbeSafety(v) ) return SQLITE_MISUSE_BKPT; sqlite3_mutex_enter(db->mutex); checkProfileCallback(db, v); - rc = sqlite3VdbeFinalize(v); + assert( v->eVdbeState>=VDBE_READY_STATE ); + rc = sqlite3VdbeReset(v); + sqlite3VdbeDelete(v); rc = sqlite3ApiExit(db, rc); sqlite3LeaveMutexAndCloseZombie(db); } @@ -81444,7 +93839,15 @@ SQLITE_API int sqlite3_clear_bindings(sqlite3_stmt *pStmt){ int rc = SQLITE_OK; Vdbe *p = (Vdbe*)pStmt; #if SQLITE_THREADSAFE - sqlite3_mutex *mutex = ((Vdbe*)pStmt)->db->mutex; + sqlite3_mutex *mutex; +#endif +#ifdef SQLITE_ENABLE_API_ARMOR + if( pStmt==0 ){ + return SQLITE_MISUSE_BKPT; + } +#endif +#if SQLITE_THREADSAFE + mutex = p->db->mutex; #endif sqlite3_mutex_enter(mutex); for(i=0; inVar; i++){ @@ -81529,41 +93932,91 @@ SQLITE_API const void *sqlite3_value_text16le(sqlite3_value *pVal){ */ SQLITE_API int sqlite3_value_type(sqlite3_value* pVal){ static const u8 aType[] = { - SQLITE_BLOB, /* 0x00 */ - SQLITE_NULL, /* 0x01 */ - SQLITE_TEXT, /* 0x02 */ - SQLITE_NULL, /* 0x03 */ - SQLITE_INTEGER, /* 0x04 */ - SQLITE_NULL, /* 0x05 */ - SQLITE_INTEGER, /* 0x06 */ - SQLITE_NULL, /* 0x07 */ - SQLITE_FLOAT, /* 0x08 */ - SQLITE_NULL, /* 0x09 */ - SQLITE_FLOAT, /* 0x0a */ - SQLITE_NULL, /* 0x0b */ - SQLITE_INTEGER, /* 0x0c */ - SQLITE_NULL, /* 0x0d */ - SQLITE_INTEGER, /* 0x0e */ - SQLITE_NULL, /* 0x0f */ - SQLITE_BLOB, /* 0x10 */ - SQLITE_NULL, /* 0x11 */ - SQLITE_TEXT, /* 0x12 */ - SQLITE_NULL, /* 0x13 */ - SQLITE_INTEGER, /* 0x14 */ - SQLITE_NULL, /* 0x15 */ - SQLITE_INTEGER, /* 0x16 */ - SQLITE_NULL, /* 0x17 */ - SQLITE_FLOAT, /* 0x18 */ - SQLITE_NULL, /* 0x19 */ - SQLITE_FLOAT, /* 0x1a */ - SQLITE_NULL, /* 0x1b */ - SQLITE_INTEGER, /* 0x1c */ - SQLITE_NULL, /* 0x1d */ - SQLITE_INTEGER, /* 0x1e */ - SQLITE_NULL, /* 0x1f */ + SQLITE_BLOB, /* 0x00 (not possible) */ + SQLITE_NULL, /* 0x01 NULL */ + SQLITE_TEXT, /* 0x02 TEXT */ + SQLITE_NULL, /* 0x03 (not possible) */ + SQLITE_INTEGER, /* 0x04 INTEGER */ + SQLITE_NULL, /* 0x05 (not possible) */ + SQLITE_INTEGER, /* 0x06 INTEGER + TEXT */ + SQLITE_NULL, /* 0x07 (not possible) */ + SQLITE_FLOAT, /* 0x08 FLOAT */ + SQLITE_NULL, /* 0x09 (not possible) */ + SQLITE_FLOAT, /* 0x0a FLOAT + TEXT */ + SQLITE_NULL, /* 0x0b (not possible) */ + SQLITE_INTEGER, /* 0x0c (not possible) */ + SQLITE_NULL, /* 0x0d (not possible) */ + SQLITE_INTEGER, /* 0x0e (not possible) */ + SQLITE_NULL, /* 0x0f (not possible) */ + SQLITE_BLOB, /* 0x10 BLOB */ + SQLITE_NULL, /* 0x11 (not possible) */ + SQLITE_TEXT, /* 0x12 (not possible) */ + SQLITE_NULL, /* 0x13 (not possible) */ + SQLITE_INTEGER, /* 0x14 INTEGER + BLOB */ + SQLITE_NULL, /* 0x15 (not possible) */ + SQLITE_INTEGER, /* 0x16 (not possible) */ + SQLITE_NULL, /* 0x17 (not possible) */ + SQLITE_FLOAT, /* 0x18 FLOAT + BLOB */ + SQLITE_NULL, /* 0x19 (not possible) */ + SQLITE_FLOAT, /* 0x1a (not possible) */ + SQLITE_NULL, /* 0x1b (not possible) */ + SQLITE_INTEGER, /* 0x1c (not possible) */ + SQLITE_NULL, /* 0x1d (not possible) */ + SQLITE_INTEGER, /* 0x1e (not possible) */ + SQLITE_NULL, /* 0x1f (not possible) */ + SQLITE_FLOAT, /* 0x20 INTREAL */ + SQLITE_NULL, /* 0x21 (not possible) */ + SQLITE_FLOAT, /* 0x22 INTREAL + TEXT */ + SQLITE_NULL, /* 0x23 (not possible) */ + SQLITE_FLOAT, /* 0x24 (not possible) */ + SQLITE_NULL, /* 0x25 (not possible) */ + SQLITE_FLOAT, /* 0x26 (not possible) */ + SQLITE_NULL, /* 0x27 (not possible) */ + SQLITE_FLOAT, /* 0x28 (not possible) */ + SQLITE_NULL, /* 0x29 (not possible) */ + SQLITE_FLOAT, /* 0x2a (not possible) */ + SQLITE_NULL, /* 0x2b (not possible) */ + SQLITE_FLOAT, /* 0x2c (not possible) */ + SQLITE_NULL, /* 0x2d (not possible) */ + SQLITE_FLOAT, /* 0x2e (not possible) */ + SQLITE_NULL, /* 0x2f (not possible) */ + SQLITE_BLOB, /* 0x30 (not possible) */ + SQLITE_NULL, /* 0x31 (not possible) */ + SQLITE_TEXT, /* 0x32 (not possible) */ + SQLITE_NULL, /* 0x33 (not possible) */ + SQLITE_FLOAT, /* 0x34 (not possible) */ + SQLITE_NULL, /* 0x35 (not possible) */ + SQLITE_FLOAT, /* 0x36 (not possible) */ + SQLITE_NULL, /* 0x37 (not possible) */ + SQLITE_FLOAT, /* 0x38 (not possible) */ + SQLITE_NULL, /* 0x39 (not possible) */ + SQLITE_FLOAT, /* 0x3a (not possible) */ + SQLITE_NULL, /* 0x3b (not possible) */ + SQLITE_FLOAT, /* 0x3c (not possible) */ + SQLITE_NULL, /* 0x3d (not possible) */ + SQLITE_FLOAT, /* 0x3e (not possible) */ + SQLITE_NULL, /* 0x3f (not possible) */ }; +#ifdef SQLITE_DEBUG + { + int eType = SQLITE_BLOB; + if( pVal->flags & MEM_Null ){ + eType = SQLITE_NULL; + }else if( pVal->flags & (MEM_Real|MEM_IntReal) ){ + eType = SQLITE_FLOAT; + }else if( pVal->flags & MEM_Int ){ + eType = SQLITE_INTEGER; + }else if( pVal->flags & MEM_Str ){ + eType = SQLITE_TEXT; + } + assert( eType == aType[pVal->flags&MEM_AffMask] ); + } +#endif return aType[pVal->flags&MEM_AffMask]; } +SQLITE_API int sqlite3_value_encoding(sqlite3_value *pVal){ + return pVal->enc; +} /* Return true if a parameter to xUpdate represents an unchanged column */ SQLITE_API int sqlite3_value_nochange(sqlite3_value *pVal){ @@ -81593,6 +94046,9 @@ SQLITE_API sqlite3_value *sqlite3_value_dup(const sqlite3_value *pOrig){ sqlite3ValueFree(pNew); pNew = 0; } + }else if( pNew->flags & MEM_Null ){ + /* Do not duplicate pointer values */ + pNew->flags &= ~(MEM_Term|MEM_Subtype); } return pNew; } @@ -81603,18 +94059,18 @@ SQLITE_API sqlite3_value *sqlite3_value_dup(const sqlite3_value *pOrig){ SQLITE_API void sqlite3_value_free(sqlite3_value *pOld){ sqlite3ValueFree(pOld); } - + /**************************** sqlite3_result_ ******************************* ** The following routines are used by user-defined functions to specify ** the function result. ** ** The setStrOrError() function calls sqlite3VdbeMemSetStr() to store the -** result as a string or blob but if the string or blob is too large, it -** then sets the error code to SQLITE_TOOBIG +** result as a string or blob. Appropriate errors are set if the string/blob +** is too big or if an OOM occurs. ** ** The invokeValueDestructor(P,X) routine invokes destructor function X() -** on value P is not going to be used and need to be destroyed. +** on value P if P is not going to be used and need to be destroyed. */ static void setResultStrOrError( sqlite3_context *pCtx, /* Function context */ @@ -81623,14 +94079,44 @@ static void setResultStrOrError( u8 enc, /* Encoding of z. 0 for BLOBs */ void (*xDel)(void*) /* Destructor function */ ){ - if( sqlite3VdbeMemSetStr(pCtx->pOut, z, n, enc, xDel)==SQLITE_TOOBIG ){ + Mem *pOut = pCtx->pOut; + int rc; + if( enc==SQLITE_UTF8 ){ + rc = sqlite3VdbeMemSetText(pOut, z, n, xDel); + }else if( enc==SQLITE_UTF8_ZT ){ + /* It is usually considered improper to assert() on an input. However, + ** the following assert() is checking for inputs that are documented + ** to result in undefined behavior. */ + assert( z==0 + || n<0 + || n>pOut->db->aLimit[SQLITE_LIMIT_LENGTH] + || z[n]==0 + ); + rc = sqlite3VdbeMemSetText(pOut, z, n, xDel); + pOut->flags |= MEM_Term; + }else{ + rc = sqlite3VdbeMemSetStr(pOut, z, n, enc, xDel); + } + if( rc ){ + if( rc==SQLITE_TOOBIG ){ + sqlite3_result_error_toobig(pCtx); + }else{ + /* The only errors possible from sqlite3VdbeMemSetStr are + ** SQLITE_TOOBIG and SQLITE_NOMEM */ + assert( rc==SQLITE_NOMEM ); + sqlite3_result_error_nomem(pCtx); + } + return; + } + sqlite3VdbeChangeEncoding(pOut, pCtx->enc); + if( sqlite3VdbeMemTooBig(pOut) ){ sqlite3_result_error_toobig(pCtx); } } static int invokeValueDestructor( const void *p, /* Value to destroy */ void (*xDel)(void*), /* The destructor */ - sqlite3_context *pCtx /* Set a SQLITE_TOOBIG error if no NULL */ + sqlite3_context *pCtx /* Set a SQLITE_TOOBIG error if not NULL */ ){ assert( xDel!=SQLITE_DYNAMIC ); if( xDel==0 ){ @@ -81640,27 +94126,46 @@ static int invokeValueDestructor( }else{ xDel((void*)p); } - if( pCtx ) sqlite3_result_error_toobig(pCtx); +#ifdef SQLITE_ENABLE_API_ARMOR + if( pCtx!=0 ){ + sqlite3_result_error_toobig(pCtx); + } +#else + assert( pCtx!=0 ); + sqlite3_result_error_toobig(pCtx); +#endif return SQLITE_TOOBIG; } SQLITE_API void sqlite3_result_blob( - sqlite3_context *pCtx, - const void *z, - int n, + sqlite3_context *pCtx, + const void *z, + int n, void (*xDel)(void *) ){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( pCtx==0 || n<0 ){ + invokeValueDestructor(z, xDel, pCtx); + return; + } +#endif assert( n>=0 ); assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); setResultStrOrError(pCtx, z, n, 0, xDel); } SQLITE_API void sqlite3_result_blob64( - sqlite3_context *pCtx, - const void *z, + sqlite3_context *pCtx, + const void *z, sqlite3_uint64 n, void (*xDel)(void *) ){ - assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); assert( xDel!=SQLITE_DYNAMIC ); +#ifdef SQLITE_ENABLE_API_ARMOR + if( pCtx==0 ){ + invokeValueDestructor(z, xDel, 0); + return; + } +#endif + assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); if( n>0x7fffffff ){ (void)invokeValueDestructor(z, xDel, pCtx); }else{ @@ -81668,30 +94173,48 @@ SQLITE_API void sqlite3_result_blob64( } } SQLITE_API void sqlite3_result_double(sqlite3_context *pCtx, double rVal){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( pCtx==0 ) return; +#endif assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); sqlite3VdbeMemSetDouble(pCtx->pOut, rVal); } SQLITE_API void sqlite3_result_error(sqlite3_context *pCtx, const char *z, int n){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( pCtx==0 ) return; +#endif assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); pCtx->isError = SQLITE_ERROR; sqlite3VdbeMemSetStr(pCtx->pOut, z, n, SQLITE_UTF8, SQLITE_TRANSIENT); } #ifndef SQLITE_OMIT_UTF16 SQLITE_API void sqlite3_result_error16(sqlite3_context *pCtx, const void *z, int n){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( pCtx==0 ) return; +#endif assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); pCtx->isError = SQLITE_ERROR; sqlite3VdbeMemSetStr(pCtx->pOut, z, n, SQLITE_UTF16NATIVE, SQLITE_TRANSIENT); } #endif SQLITE_API void sqlite3_result_int(sqlite3_context *pCtx, int iVal){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( pCtx==0 ) return; +#endif assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); sqlite3VdbeMemSetInt64(pCtx->pOut, (i64)iVal); } SQLITE_API void sqlite3_result_int64(sqlite3_context *pCtx, i64 iVal){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( pCtx==0 ) return; +#endif assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); sqlite3VdbeMemSetInt64(pCtx->pOut, iVal); } SQLITE_API void sqlite3_result_null(sqlite3_context *pCtx){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( pCtx==0 ) return; +#endif assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); sqlite3VdbeMemSetNull(pCtx->pOut); } @@ -81701,118 +94224,204 @@ SQLITE_API void sqlite3_result_pointer( const char *zPType, void (*xDestructor)(void*) ){ - Mem *pOut = pCtx->pOut; + Mem *pOut; +#ifdef SQLITE_ENABLE_API_ARMOR + if( pCtx==0 ){ + invokeValueDestructor(pPtr, xDestructor, 0); + return; + } +#endif + pOut = pCtx->pOut; assert( sqlite3_mutex_held(pOut->db->mutex) ); sqlite3VdbeMemRelease(pOut); pOut->flags = MEM_Null; sqlite3VdbeMemSetPointer(pOut, pPtr, zPType, xDestructor); } SQLITE_API void sqlite3_result_subtype(sqlite3_context *pCtx, unsigned int eSubtype){ - Mem *pOut = pCtx->pOut; + Mem *pOut; +#ifdef SQLITE_ENABLE_API_ARMOR + if( pCtx==0 ) return; +#endif +#if defined(SQLITE_STRICT_SUBTYPE) && SQLITE_STRICT_SUBTYPE+0!=0 + if( pCtx->pFunc!=0 + && (pCtx->pFunc->funcFlags & SQLITE_RESULT_SUBTYPE)==0 + ){ + char zErr[200]; + sqlite3_snprintf(sizeof(zErr), zErr, + "misuse of sqlite3_result_subtype() by %s()", + pCtx->pFunc->zName); + sqlite3_result_error(pCtx, zErr, -1); + return; + } +#endif /* SQLITE_STRICT_SUBTYPE */ + pOut = pCtx->pOut; assert( sqlite3_mutex_held(pOut->db->mutex) ); pOut->eSubtype = eSubtype & 0xff; pOut->flags |= MEM_Subtype; } SQLITE_API void sqlite3_result_text( - sqlite3_context *pCtx, - const char *z, + sqlite3_context *pCtx, + const char *z, int n, void (*xDel)(void *) ){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( pCtx==0 ){ + invokeValueDestructor(z, xDel, 0); + return; + } +#endif assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); setResultStrOrError(pCtx, z, n, SQLITE_UTF8, xDel); } SQLITE_API void sqlite3_result_text64( - sqlite3_context *pCtx, - const char *z, + sqlite3_context *pCtx, + const char *z, sqlite3_uint64 n, void (*xDel)(void *), unsigned char enc ){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( pCtx==0 ){ + invokeValueDestructor(z, xDel, 0); + return; + } +#endif assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); assert( xDel!=SQLITE_DYNAMIC ); - if( enc==SQLITE_UTF16 ) enc = SQLITE_UTF16NATIVE; + if( enc!=SQLITE_UTF8 && enc!=SQLITE_UTF8_ZT ){ + if( enc==SQLITE_UTF16 ) enc = SQLITE_UTF16NATIVE; + n &= ~(u64)1; + } if( n>0x7fffffff ){ (void)invokeValueDestructor(z, xDel, pCtx); }else{ setResultStrOrError(pCtx, z, (int)n, enc, xDel); + sqlite3VdbeMemZeroTerminateIfAble(pCtx->pOut); } } #ifndef SQLITE_OMIT_UTF16 SQLITE_API void sqlite3_result_text16( - sqlite3_context *pCtx, - const void *z, - int n, + sqlite3_context *pCtx, + const void *z, + int n, void (*xDel)(void *) ){ assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); - setResultStrOrError(pCtx, z, n, SQLITE_UTF16NATIVE, xDel); + setResultStrOrError(pCtx, z, n & ~(u64)1, SQLITE_UTF16NATIVE, xDel); } SQLITE_API void sqlite3_result_text16be( - sqlite3_context *pCtx, - const void *z, - int n, + sqlite3_context *pCtx, + const void *z, + int n, void (*xDel)(void *) ){ assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); - setResultStrOrError(pCtx, z, n, SQLITE_UTF16BE, xDel); + setResultStrOrError(pCtx, z, n & ~(u64)1, SQLITE_UTF16BE, xDel); } SQLITE_API void sqlite3_result_text16le( - sqlite3_context *pCtx, - const void *z, - int n, + sqlite3_context *pCtx, + const void *z, + int n, void (*xDel)(void *) ){ assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); - setResultStrOrError(pCtx, z, n, SQLITE_UTF16LE, xDel); + setResultStrOrError(pCtx, z, n & ~(u64)1, SQLITE_UTF16LE, xDel); } #endif /* SQLITE_OMIT_UTF16 */ SQLITE_API void sqlite3_result_value(sqlite3_context *pCtx, sqlite3_value *pValue){ + Mem *pOut; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( pCtx==0 ) return; + if( pValue==0 ){ + sqlite3_result_null(pCtx); + return; + } +#endif + pOut = pCtx->pOut; assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); - sqlite3VdbeMemCopy(pCtx->pOut, pValue); + sqlite3VdbeMemCopy(pOut, pValue); + sqlite3VdbeChangeEncoding(pOut, pCtx->enc); + if( sqlite3VdbeMemTooBig(pOut) ){ + sqlite3_result_error_toobig(pCtx); + } } SQLITE_API void sqlite3_result_zeroblob(sqlite3_context *pCtx, int n){ - assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); - sqlite3VdbeMemSetZeroBlob(pCtx->pOut, n); + sqlite3_result_zeroblob64(pCtx, n>0 ? n : 0); } SQLITE_API int sqlite3_result_zeroblob64(sqlite3_context *pCtx, u64 n){ - Mem *pOut = pCtx->pOut; + Mem *pOut; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( pCtx==0 ) return SQLITE_MISUSE_BKPT; +#endif + pOut = pCtx->pOut; assert( sqlite3_mutex_held(pOut->db->mutex) ); if( n>(u64)pOut->db->aLimit[SQLITE_LIMIT_LENGTH] ){ + sqlite3_result_error_toobig(pCtx); return SQLITE_TOOBIG; } +#ifndef SQLITE_OMIT_INCRBLOB sqlite3VdbeMemSetZeroBlob(pCtx->pOut, (int)n); return SQLITE_OK; +#else + return sqlite3VdbeMemSetZeroBlob(pCtx->pOut, (int)n); +#endif } SQLITE_API void sqlite3_result_error_code(sqlite3_context *pCtx, int errCode){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( pCtx==0 ) return; +#endif pCtx->isError = errCode ? errCode : -1; #ifdef SQLITE_DEBUG if( pCtx->pVdbe ) pCtx->pVdbe->rcApp = errCode; #endif if( pCtx->pOut->flags & MEM_Null ){ - sqlite3VdbeMemSetStr(pCtx->pOut, sqlite3ErrStr(errCode), -1, - SQLITE_UTF8, SQLITE_STATIC); + setResultStrOrError(pCtx, sqlite3ErrStr(errCode), -1, SQLITE_UTF8, + SQLITE_STATIC); } } /* Force an SQLITE_TOOBIG error. */ SQLITE_API void sqlite3_result_error_toobig(sqlite3_context *pCtx){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( pCtx==0 ) return; +#endif assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); pCtx->isError = SQLITE_TOOBIG; - sqlite3VdbeMemSetStr(pCtx->pOut, "string or blob too big", -1, + sqlite3VdbeMemSetStr(pCtx->pOut, "string or blob too big", -1, SQLITE_UTF8, SQLITE_STATIC); } /* An SQLITE_NOMEM error. */ SQLITE_API void sqlite3_result_error_nomem(sqlite3_context *pCtx){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( pCtx==0 ) return; +#endif assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); sqlite3VdbeMemSetNull(pCtx->pOut); pCtx->isError = SQLITE_NOMEM_BKPT; sqlite3OomFault(pCtx->pOut->db); } +#ifndef SQLITE_UNTESTABLE +/* Force the INT64 value currently stored as the result to be +** a MEM_IntReal value. See the SQLITE_TESTCTRL_RESULT_INTREAL +** test-control. +*/ +SQLITE_PRIVATE void sqlite3ResultIntReal(sqlite3_context *pCtx){ + assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); + if( pCtx->pOut->flags & MEM_Int ){ + pCtx->pOut->flags &= ~MEM_Int; + pCtx->pOut->flags |= MEM_IntReal; + } +} +#endif + + /* -** This function is called after a transaction has been committed. It +** This function is called after a transaction has been committed. It ** invokes callbacks registered with sqlite3_wal_hook() as required. */ static int doWalCallbacks(sqlite3 *db){ @@ -81831,6 +94440,8 @@ static int doWalCallbacks(sqlite3 *db){ } } } +#else + UNUSED_PARAMETER(db); #endif return rc; } @@ -81841,7 +94452,7 @@ static int doWalCallbacks(sqlite3 *db){ ** statement is completely executed or an error occurs. ** ** This routine implements the bulk of the logic behind the sqlite_step() -** API. The only thing omitted is the automatic recompile if a +** API. The only thing omitted is the automatic recompile if a ** schema change has occurred. That detail is handled by the ** outer sqlite3_step() wrapper procedure. */ @@ -81850,73 +94461,83 @@ static int sqlite3Step(Vdbe *p){ int rc; assert(p); - if( p->magic!=VDBE_MAGIC_RUN ){ - /* We used to require that sqlite3_reset() be called before retrying - ** sqlite3_step() after any error or after SQLITE_DONE. But beginning - ** with version 3.7.0, we changed this so that sqlite3_reset() would - ** be called automatically instead of throwing the SQLITE_MISUSE error. - ** This "automatic-reset" change is not technically an incompatibility, - ** since any application that receives an SQLITE_MISUSE is broken by - ** definition. - ** - ** Nevertheless, some published applications that were originally written - ** for version 3.6.23 or earlier do in fact depend on SQLITE_MISUSE - ** returns, and those were broken by the automatic-reset change. As a - ** a work-around, the SQLITE_OMIT_AUTORESET compile-time restores the - ** legacy behavior of returning SQLITE_MISUSE for cases where the - ** previous sqlite3_step() returned something other than a SQLITE_LOCKED - ** or SQLITE_BUSY error. - */ -#ifdef SQLITE_OMIT_AUTORESET - if( (rc = p->rc&0xff)==SQLITE_BUSY || rc==SQLITE_LOCKED ){ - sqlite3_reset((sqlite3_stmt*)p); - }else{ - return SQLITE_MISUSE_BKPT; - } -#else - sqlite3_reset((sqlite3_stmt*)p); -#endif - } - - /* Check that malloc() has not failed. If it has, return early. */ db = p->db; - if( db->mallocFailed ){ - p->rc = SQLITE_NOMEM; - return SQLITE_NOMEM_BKPT; - } + if( p->eVdbeState!=VDBE_RUN_STATE ){ + restart_step: + if( p->eVdbeState==VDBE_READY_STATE ){ + if( p->expired ){ + p->rc = SQLITE_SCHEMA; + rc = SQLITE_ERROR; + if( (p->prepFlags & SQLITE_PREPARE_SAVESQL)!=0 ){ + /* If this statement was prepared using saved SQL and an + ** error has occurred, then return the error code in p->rc to the + ** caller. Set the error code in the database handle to the same + ** value. + */ + rc = sqlite3VdbeTransferError(p); + } + goto end_of_step; + } - if( p->pc<0 && p->expired ){ - p->rc = SQLITE_SCHEMA; - rc = SQLITE_ERROR; - goto end_of_step; - } - if( p->pc<0 ){ - /* If there are no other statements currently running, then - ** reset the interrupt flag. This prevents a call to sqlite3_interrupt - ** from interrupting a statement that has not yet started. - */ - if( db->nVdbeActive==0 ){ - db->u1.isInterrupted = 0; - } + /* If there are no other statements currently running, then + ** reset the interrupt flag. This prevents a call to sqlite3_interrupt + ** from interrupting a statement that has not yet started. + */ + if( db->nVdbeActive==0 ){ + AtomicStore(&db->u1.isInterrupted, 0); + } - assert( db->nVdbeWrite>0 || db->autoCommit==0 - || (db->nDeferredCons==0 && db->nDeferredImmCons==0) - ); + assert( db->nVdbeWrite>0 || db->autoCommit==0 + || ((db->nDeferredCons + db->nDeferredImmCons)==0) + ); #ifndef SQLITE_OMIT_TRACE - if( (db->mTrace & (SQLITE_TRACE_PROFILE|SQLITE_TRACE_XPROFILE))!=0 - && !db->init.busy && p->zSql ){ - sqlite3OsCurrentTimeInt64(db->pVfs, &p->startTime); - }else{ - assert( p->startTime==0 ); - } + if( (db->mTrace & (SQLITE_TRACE_PROFILE|SQLITE_TRACE_XPROFILE))!=0 + && !db->init.busy && p->zSql ){ + sqlite3OsCurrentTimeInt64(db->pVfs, &p->startTime); + }else{ + assert( p->startTime==0 ); + } #endif - db->nVdbeActive++; - if( p->readOnly==0 ) db->nVdbeWrite++; - if( p->bIsReader ) db->nVdbeRead++; - p->pc = 0; + db->nVdbeActive++; + if( p->readOnly==0 ) db->nVdbeWrite++; + if( p->bIsReader ) db->nVdbeRead++; + p->pc = 0; + p->eVdbeState = VDBE_RUN_STATE; + }else + + if( ALWAYS(p->eVdbeState==VDBE_HALT_STATE) ){ + /* We used to require that sqlite3_reset() be called before retrying + ** sqlite3_step() after any error or after SQLITE_DONE. But beginning + ** with version 3.7.0, we changed this so that sqlite3_reset() would + ** be called automatically instead of throwing the SQLITE_MISUSE error. + ** This "automatic-reset" change is not technically an incompatibility, + ** since any application that receives an SQLITE_MISUSE is broken by + ** definition. + ** + ** Nevertheless, some published applications that were originally written + ** for version 3.6.23 or earlier do in fact depend on SQLITE_MISUSE + ** returns, and those were broken by the automatic-reset change. As a + ** a work-around, the SQLITE_OMIT_AUTORESET compile-time restores the + ** legacy behavior of returning SQLITE_MISUSE for cases where the + ** previous sqlite3_step() returned something other than a SQLITE_LOCKED + ** or SQLITE_BUSY error. + */ +#ifdef SQLITE_OMIT_AUTORESET + if( (rc = p->rc&0xff)==SQLITE_BUSY || rc==SQLITE_LOCKED ){ + sqlite3_reset((sqlite3_stmt*)p); + }else{ + return SQLITE_MISUSE_BKPT; + } +#else + sqlite3_reset((sqlite3_stmt*)p); +#endif + assert( p->eVdbeState==VDBE_READY_STATE ); + goto restart_step; + } } + #ifdef SQLITE_DEBUG p->rcApp = SQLITE_OK; #endif @@ -81931,47 +94552,44 @@ static int sqlite3Step(Vdbe *p){ db->nVdbeExec--; } - if( rc!=SQLITE_ROW ){ + if( rc==SQLITE_ROW ){ + assert( p->rc==SQLITE_OK ); + assert( db->mallocFailed==0 ); + db->errCode = SQLITE_ROW; + return SQLITE_ROW; + }else{ #ifndef SQLITE_OMIT_TRACE /* If the statement completed successfully, invoke the profile callback */ checkProfileCallback(db, p); #endif - + p->pResultRow = 0; if( rc==SQLITE_DONE && db->autoCommit ){ assert( p->rc==SQLITE_OK ); p->rc = doWalCallbacks(db); if( p->rc!=SQLITE_OK ){ rc = SQLITE_ERROR; } + }else if( rc!=SQLITE_DONE && (p->prepFlags & SQLITE_PREPARE_SAVESQL)!=0 ){ + /* If this statement was prepared using saved SQL and an + ** error has occurred, then return the error code in p->rc to the + ** caller. Set the error code in the database handle to the same value. + */ + rc = sqlite3VdbeTransferError(p); } } db->errCode = rc; if( SQLITE_NOMEM==sqlite3ApiExit(p->db, p->rc) ){ p->rc = SQLITE_NOMEM_BKPT; + if( (p->prepFlags & SQLITE_PREPARE_SAVESQL)!=0 ) rc = p->rc; } end_of_step: - /* At this point local variable rc holds the value that should be - ** returned if this statement was compiled using the legacy - ** sqlite3_prepare() interface. According to the docs, this can only - ** be one of the values in the first assert() below. Variable p->rc - ** contains the value that would be returned if sqlite3_finalize() - ** were called on statement p. - */ - assert( rc==SQLITE_ROW || rc==SQLITE_DONE || rc==SQLITE_ERROR + /* There are only a limited number of result codes allowed from the + ** statements prepared using the legacy sqlite3_prepare() interface */ + assert( (p->prepFlags & SQLITE_PREPARE_SAVESQL)!=0 + || rc==SQLITE_ROW || rc==SQLITE_DONE || rc==SQLITE_ERROR || (rc&0xff)==SQLITE_BUSY || rc==SQLITE_MISUSE ); - assert( (p->rc!=SQLITE_ROW && p->rc!=SQLITE_DONE) || p->rc==p->rcApp ); - if( rc!=SQLITE_ROW - && rc!=SQLITE_DONE - && (p->prepFlags & SQLITE_PREPARE_SAVESQL)!=0 - ){ - /* If this statement was prepared using saved SQL and an - ** error has occurred, then return the error code in p->rc to the - ** caller. Set the error code in the database handle to the same value. - */ - rc = sqlite3VdbeTransferError(p); - } return (rc&db->errMask); } @@ -81991,21 +94609,20 @@ SQLITE_API int sqlite3_step(sqlite3_stmt *pStmt){ } db = v->db; sqlite3_mutex_enter(db->mutex); - v->doingRerun = 0; while( (rc = sqlite3Step(v))==SQLITE_SCHEMA && cnt++ < SQLITE_MAX_SCHEMA_RETRY ){ int savedPc = v->pc; rc = sqlite3Reprepare(v); if( rc!=SQLITE_OK ){ - /* This case occurs after failing to recompile an sql statement. - ** The error message from the SQL compiler has already been loaded - ** into the database handle. This block copies the error message + /* This case occurs after failing to recompile an sql statement. + ** The error message from the SQL compiler has already been loaded + ** into the database handle. This block copies the error message ** from the database handle into the statement and sets the statement - ** program counter to 0 to ensure that when the statement is + ** program counter to 0 to ensure that when the statement is ** finalized or reset the parser error message is available via ** sqlite3_errmsg() and sqlite3_errcode(). */ - const char *zErr = (const char *)sqlite3_value_text(db->pErr); + const char *zErr = (const char *)sqlite3_value_text(db->pErr); sqlite3DbFree(db, v->zErrMsg); if( !db->mallocFailed ){ v->zErrMsg = sqlite3DbStrDup(db, zErr); @@ -82017,7 +94634,13 @@ SQLITE_API int sqlite3_step(sqlite3_stmt *pStmt){ break; } sqlite3_reset(pStmt); - if( savedPc>=0 ) v->doingRerun = 1; + if( savedPc>=0 ){ + /* Setting minWriteFileFormat to 254 is a signal to the OP_Init and + ** OP_Trace opcodes to *not* perform SQLITE_TRACE_STMT because it has + ** already been done once on a prior invocation that failed due to + ** SQLITE_SCHEMA. tag-20220401a */ + v->minWriteFileFormat = 254; + } assert( v->expired==0 ); } sqlite3_mutex_leave(db->mutex); @@ -82030,6 +94653,9 @@ SQLITE_API int sqlite3_step(sqlite3_stmt *pStmt){ ** pointer to it. */ SQLITE_API void *sqlite3_user_data(sqlite3_context *p){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( p==0 ) return 0; +#endif assert( p && p->pFunc ); return p->pFunc->pUserData; } @@ -82045,7 +94671,11 @@ SQLITE_API void *sqlite3_user_data(sqlite3_context *p){ ** application defined function. */ SQLITE_API sqlite3 *sqlite3_context_db_handle(sqlite3_context *p){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( p==0 ) return 0; +#else assert( p && p->pOut ); +#endif return p->pOut->db; } @@ -82064,10 +94694,96 @@ SQLITE_API sqlite3 *sqlite3_context_db_handle(sqlite3_context *p){ ** value, as a signal to the xUpdate routine that the column is unchanged. */ SQLITE_API int sqlite3_vtab_nochange(sqlite3_context *p){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( p==0 ) return 0; +#else assert( p ); +#endif return sqlite3_value_nochange(p->pOut); } +/* +** The destructor function for a ValueList object. This needs to be +** a separate function, unknowable to the application, to ensure that +** calls to sqlite3_vtab_in_first()/sqlite3_vtab_in_next() that are not +** preceded by activation of IN processing via sqlite3_vtab_int() do not +** try to access a fake ValueList object inserted by a hostile extension. +*/ +SQLITE_PRIVATE void sqlite3VdbeValueListFree(void *pToDelete){ + sqlite3_free(pToDelete); +} + +/* +** Implementation of sqlite3_vtab_in_first() (if bNext==0) and +** sqlite3_vtab_in_next() (if bNext!=0). +*/ +static int valueFromValueList( + sqlite3_value *pVal, /* Pointer to the ValueList object */ + sqlite3_value **ppOut, /* Store the next value from the list here */ + int bNext /* 1 for _next(). 0 for _first() */ +){ + int rc; + ValueList *pRhs; + + *ppOut = 0; + if( pVal==0 ) return SQLITE_MISUSE_BKPT; + if( (pVal->flags & MEM_Dyn)==0 || pVal->xDel!=sqlite3VdbeValueListFree ){ + return SQLITE_ERROR; + }else{ + assert( (pVal->flags&(MEM_TypeMask|MEM_Term|MEM_Subtype)) == + (MEM_Null|MEM_Term|MEM_Subtype) ); + assert( pVal->eSubtype=='p' ); + assert( pVal->u.zPType!=0 && strcmp(pVal->u.zPType,"ValueList")==0 ); + pRhs = (ValueList*)pVal->z; + } + if( bNext ){ + rc = sqlite3BtreeNext(pRhs->pCsr, 0); + }else{ + int dummy = 0; + rc = sqlite3BtreeFirst(pRhs->pCsr, &dummy); + assert( rc==SQLITE_OK || sqlite3BtreeEof(pRhs->pCsr) ); + if( sqlite3BtreeEof(pRhs->pCsr) ) rc = SQLITE_DONE; + } + if( rc==SQLITE_OK ){ + u32 sz; /* Size of current row in bytes */ + Mem sMem; /* Raw content of current row */ + memset(&sMem, 0, sizeof(sMem)); + sz = sqlite3BtreePayloadSize(pRhs->pCsr); + rc = sqlite3VdbeMemFromBtreeZeroOffset(pRhs->pCsr,sz,&sMem); + if( rc==SQLITE_OK ){ + u8 *zBuf = (u8*)sMem.z; + u32 iSerial; + sqlite3_value *pOut = pRhs->pOut; + int iOff = 1 + getVarint32(&zBuf[1], iSerial); + sqlite3VdbeSerialGet(&zBuf[iOff], iSerial, pOut); + pOut->enc = ENC(pOut->db); + if( (pOut->flags & MEM_Ephem)!=0 && sqlite3VdbeMemMakeWriteable(pOut) ){ + rc = SQLITE_NOMEM; + }else{ + *ppOut = pOut; + } + } + sqlite3VdbeMemRelease(&sMem); + } + return rc; +} + +/* +** Set the iterator value pVal to point to the first value in the set. +** Set (*ppOut) to point to this value before returning. +*/ +SQLITE_API int sqlite3_vtab_in_first(sqlite3_value *pVal, sqlite3_value **ppOut){ + return valueFromValueList(pVal, ppOut, 0); +} + +/* +** Set the iterator value pVal to point to the next value in the set. +** Set (*ppOut) to point to this value before returning. +*/ +SQLITE_API int sqlite3_vtab_in_next(sqlite3_value *pVal, sqlite3_value **ppOut){ + return valueFromValueList(pVal, ppOut, 1); +} + /* ** Return the current time for a statement. If the current time ** is requested more than once within the same run of a single prepared @@ -82077,7 +94793,7 @@ SQLITE_API int sqlite3_vtab_nochange(sqlite3_context *p){ */ SQLITE_PRIVATE sqlite3_int64 sqlite3StmtCurrentTime(sqlite3_context *p){ int rc; -#ifndef SQLITE_ENABLE_STAT3_OR_STAT4 +#ifndef SQLITE_ENABLE_STAT4 sqlite3_int64 *piTime = &p->pVdbe->iCurrentTime; assert( p->pVdbe!=0 ); #else @@ -82141,8 +94857,11 @@ SQLITE_API void *sqlite3_aggregate_context(sqlite3_context *p, int nByte){ SQLITE_API void *sqlite3_get_auxdata(sqlite3_context *pCtx, int iArg){ AuxData *pAuxData; +#ifdef SQLITE_ENABLE_API_ARMOR + if( pCtx==0 ) return 0; +#endif assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); -#if SQLITE_ENABLE_STAT3_OR_STAT4 +#if SQLITE_ENABLE_STAT4 if( pCtx->pVdbe==0 ) return 0; #else assert( pCtx->pVdbe!=0 ); @@ -82167,16 +94886,20 @@ SQLITE_API void *sqlite3_get_auxdata(sqlite3_context *pCtx, int iArg){ ** access code. */ SQLITE_API void sqlite3_set_auxdata( - sqlite3_context *pCtx, - int iArg, - void *pAux, + sqlite3_context *pCtx, + int iArg, + void *pAux, void (*xDelete)(void*) ){ AuxData *pAuxData; - Vdbe *pVdbe = pCtx->pVdbe; + Vdbe *pVdbe; +#ifdef SQLITE_ENABLE_API_ARMOR + if( pCtx==0 ) return; +#endif + pVdbe= pCtx->pVdbe; assert( sqlite3_mutex_held(pCtx->pOut->db->mutex) ); -#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 +#ifdef SQLITE_ENABLE_STAT4 if( pVdbe==0 ) goto failed; #else assert( pVdbe!=0 ); @@ -82211,7 +94934,7 @@ SQLITE_API void sqlite3_set_auxdata( #ifndef SQLITE_OMIT_DEPRECATED /* -** Return the number of times the Step function of an aggregate has been +** Return the number of times the Step function of an aggregate has been ** called. ** ** This function is deprecated. Do not use it for new code. It is @@ -82230,7 +94953,8 @@ SQLITE_API int sqlite3_aggregate_count(sqlite3_context *p){ */ SQLITE_API int sqlite3_column_count(sqlite3_stmt *pStmt){ Vdbe *pVm = (Vdbe *)pStmt; - return pVm ? pVm->nResColumn : 0; + if( pVm==0 ) return 0; + return pVm->nResColumn; } /* @@ -82239,7 +94963,7 @@ SQLITE_API int sqlite3_column_count(sqlite3_stmt *pStmt){ */ SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt){ Vdbe *pVm = (Vdbe *)pStmt; - if( pVm==0 || pVm->pResultSet==0 ) return 0; + if( pVm==0 || pVm->pResultRow==0 ) return 0; return pVm->nResColumn; } @@ -82256,25 +94980,26 @@ static const Mem *columnNullValue(void){ ** these assert()s from failing, when building with SQLITE_DEBUG defined ** using gcc, we force nullMem to be 8-byte aligned using the magical ** __attribute__((aligned(8))) macro. */ - static const Mem nullMem + static const Mem nullMem #if defined(SQLITE_DEBUG) && defined(__GNUC__) - __attribute__((aligned(8))) + __attribute__((aligned(8))) #endif = { /* .u = */ {0}, + /* .z = */ (char*)0, + /* .n = */ (int)0, /* .flags = */ (u16)MEM_Null, /* .enc = */ (u8)0, /* .eSubtype = */ (u8)0, - /* .n = */ (int)0, - /* .z = */ (char*)0, - /* .zMalloc = */ (char*)0, + /* .db = */ (sqlite3*)0, /* .szMalloc = */ (int)0, /* .uTemp = */ (u32)0, - /* .db = */ (sqlite3*)0, + /* .zMalloc = */ (char*)0, /* .xDel = */ (void(*)(void*))0, #ifdef SQLITE_DEBUG /* .pScopyFrom = */ (Mem*)0, /* .mScopyFlags= */ 0, + /* .bScopy = */ 0, #endif }; return &nullMem; @@ -82294,8 +95019,8 @@ static Mem *columnMem(sqlite3_stmt *pStmt, int i){ if( pVm==0 ) return (Mem*)columnNullValue(); assert( pVm->db ); sqlite3_mutex_enter(pVm->db->mutex); - if( pVm->pResultSet!=0 && inResColumn && i>=0 ){ - pOut = &pVm->pResultSet[i]; + if( pVm->pResultRow!=0 && inResColumn && i>=0 ){ + pOut = &pVm->pResultRow[i]; }else{ sqlite3Error(pVm->db, SQLITE_RANGE); pOut = (Mem*)columnNullValue(); @@ -82304,9 +95029,9 @@ static Mem *columnMem(sqlite3_stmt *pStmt, int i){ } /* -** This function is called after invoking an sqlite3_value_XXX function on a +** This function is called after invoking an sqlite3_value_XXX function on a ** column value (i.e. a value returned by evaluating an SQL expression in the -** select list of a SELECT statement) that may cause a malloc() failure. If +** select list of a SELECT statement) that may cause a malloc() failure. If ** malloc() has failed, the threads mallocFailed flag is cleared and the result ** code of statement pStmt set to SQLITE_NOMEM. ** @@ -82316,10 +95041,10 @@ static Mem *columnMem(sqlite3_stmt *pStmt, int i){ ** sqlite3_column_int64() ** sqlite3_column_text() ** sqlite3_column_text16() -** sqlite3_column_real() +** sqlite3_column_double() ** sqlite3_column_bytes() ** sqlite3_column_bytes16() -** sqiite3_column_blob() +** sqlite3_column_blob() */ static void columnMallocFailure(sqlite3_stmt *pStmt) { @@ -82345,8 +95070,8 @@ SQLITE_API const void *sqlite3_column_blob(sqlite3_stmt *pStmt, int i){ const void *val; val = sqlite3_value_blob( columnMem(pStmt,i) ); /* Even though there is no encoding conversion, value_blob() might - ** need to call malloc() to expand the result of a zeroblob() - ** expression. + ** need to call malloc() to expand the result of a zeroblob() + ** expression. */ columnMallocFailure(pStmt); return val; @@ -82403,6 +95128,32 @@ SQLITE_API int sqlite3_column_type(sqlite3_stmt *pStmt, int i){ return iType; } +/* +** Column names appropriate for EXPLAIN or EXPLAIN QUERY PLAN. +*/ +static const char * const azExplainColNames8[] = { + "addr", "opcode", "p1", "p2", "p3", "p4", "p5", "comment", /* EXPLAIN */ + "id", "parent", "notused", "detail" /* EQP */ +}; +static const u16 azExplainColNames16data[] = { + /* 0 */ 'a', 'd', 'd', 'r', 0, + /* 5 */ 'o', 'p', 'c', 'o', 'd', 'e', 0, + /* 12 */ 'p', '1', 0, + /* 15 */ 'p', '2', 0, + /* 18 */ 'p', '3', 0, + /* 21 */ 'p', '4', 0, + /* 24 */ 'p', '5', 0, + /* 27 */ 'c', 'o', 'm', 'm', 'e', 'n', 't', 0, + /* 35 */ 'i', 'd', 0, + /* 38 */ 'p', 'a', 'r', 'e', 'n', 't', 0, + /* 45 */ 'n', 'o', 't', 'u', 's', 'e', 'd', 0, + /* 53 */ 'd', 'e', 't', 'a', 'i', 'l', 0 +}; +static const u8 iExplainColNames16[] = { + 0, 5, 12, 15, 18, 21, 24, 27, + 35, 38, 45, 53 +}; + /* ** Convert the N-th element of pStmt->pColName[] into a string using ** xFunc() then return that string. If N is out of range, return 0. @@ -82435,15 +95186,29 @@ static const void *columnName( return 0; } #endif + if( N<0 ) return 0; ret = 0; p = (Vdbe *)pStmt; db = p->db; assert( db!=0 ); - n = sqlite3_column_count(pStmt); - if( N=0 ){ + sqlite3_mutex_enter(db->mutex); + + if( p->explain ){ + if( useType>0 ) goto columnName_end; + n = p->explain==1 ? 8 : 4; + if( N>=n ) goto columnName_end; + if( useUtf16 ){ + int i = iExplainColNames16[N + 8*p->explain - 8]; + ret = (void*)&azExplainColNames16data[i]; + }else{ + ret = (void*)azExplainColNames8[N + 8*p->explain - 8]; + } + goto columnName_end; + } + n = p->nResColumn; + if( NmallocFailed; N += useType*n; - sqlite3_mutex_enter(db->mutex); - assert( db->mallocFailed==0 ); #ifndef SQLITE_OMIT_UTF16 if( useUtf16 ){ ret = sqlite3_value_text16((sqlite3_value*)&p->aColName[N]); @@ -82455,12 +95220,14 @@ static const void *columnName( /* A malloc may have failed inside of the _text() call. If this ** is the case, clear the mallocFailed flag and return NULL. */ - if( db->mallocFailed ){ + assert( db->mallocFailed==0 || db->mallocFailed==1 ); + if( db->mallocFailed > prior_mallocFailed ){ sqlite3OomClear(db); ret = 0; } - sqlite3_mutex_leave(db->mutex); } +columnName_end: + sqlite3_mutex_leave(db->mutex); return ret; } @@ -82547,48 +95314,58 @@ SQLITE_API const void *sqlite3_column_origin_name16(sqlite3_stmt *pStmt, int N){ /******************************* sqlite3_bind_ *************************** -** +** ** Routines used to attach values to wildcards in a compiled SQL statement. */ /* -** Unbind the value bound to variable i in virtual machine p. This is the +** Unbind the value bound to variable i in virtual machine p. This is the ** the same as binding a NULL value to the column. If the "i" parameter is -** out of range, then SQLITE_RANGE is returned. Othewise SQLITE_OK. +** out of range, then SQLITE_RANGE is returned. Otherwise SQLITE_OK. ** ** A successful evaluation of this routine acquires the mutex on p. ** the mutex is released if any kind of error occurs. ** ** The error code stored in database p->db is overwritten with the return ** value in any case. +** +** (tag-20240917-01) If vdbeUnbind(p,(u32)(i-1)) returns SQLITE_OK, +** that means all of the the following will be true: +** +** p!=0 +** p->pVar!=0 +** i>0 +** i<=p->nVar +** +** An assert() is normally added after vdbeUnbind() to help static analyzers +** realize this. */ -static int vdbeUnbind(Vdbe *p, int i){ +static int vdbeUnbind(Vdbe *p, unsigned int i){ Mem *pVar; if( vdbeSafetyNotNull(p) ){ return SQLITE_MISUSE_BKPT; } sqlite3_mutex_enter(p->db->mutex); - if( p->magic!=VDBE_MAGIC_RUN || p->pc>=0 ){ - sqlite3Error(p->db, SQLITE_MISUSE); + if( p->eVdbeState!=VDBE_READY_STATE ){ + sqlite3Error(p->db, SQLITE_MISUSE_BKPT); sqlite3_mutex_leave(p->db->mutex); - sqlite3_log(SQLITE_MISUSE, + sqlite3_log(SQLITE_MISUSE, "bind on a busy prepared statement: [%s]", p->zSql); return SQLITE_MISUSE_BKPT; } - if( i<1 || i>p->nVar ){ + if( i>=(unsigned int)p->nVar ){ sqlite3Error(p->db, SQLITE_RANGE); sqlite3_mutex_leave(p->db->mutex); return SQLITE_RANGE; } - i--; pVar = &p->aVar[i]; sqlite3VdbeMemRelease(pVar); pVar->flags = MEM_Null; p->db->errCode = SQLITE_OK; - /* If the bit corresponding to this variable in Vdbe.expmask is set, then + /* If the bit corresponding to this variable in Vdbe.expmask is set, then ** binding a new value to this variable invalidates the current query plan. ** - ** IMPLEMENTATION-OF: R-48440-37595 If the specific value bound to host + ** IMPLEMENTATION-OF: R-57496-20354 If the specific value bound to a host ** parameter in the WHERE clause might influence the choice of query plan ** for a statement, then the statement will be automatically recompiled, ** as if there had been a schema change, on the first sqlite3_step() call @@ -82608,7 +95385,7 @@ static int bindText( sqlite3_stmt *pStmt, /* The statement to bind against */ int i, /* Index of the parameter to bind */ const void *zData, /* Pointer to the data to be bound */ - int nData, /* Number of bytes of data to be bound */ + i64 nData, /* Number of bytes of data to be bound */ void (*xDel)(void*), /* Destructor for the data */ u8 encoding /* Encoding for the data */ ){ @@ -82616,11 +95393,28 @@ static int bindText( Mem *pVar; int rc; - rc = vdbeUnbind(p, i); + rc = vdbeUnbind(p, (u32)(i-1)); if( rc==SQLITE_OK ){ + assert( p!=0 && p->aVar!=0 && i>0 && i<=p->nVar ); /* tag-20240917-01 */ if( zData!=0 ){ pVar = &p->aVar[i-1]; - rc = sqlite3VdbeMemSetStr(pVar, zData, nData, encoding, xDel); + if( encoding==SQLITE_UTF8 ){ + rc = sqlite3VdbeMemSetText(pVar, zData, nData, xDel); + }else if( encoding==SQLITE_UTF8_ZT ){ + /* It is usually consider improper to assert() on an input. + ** However, the following assert() is checking for inputs + ** that are documented to result in undefined behavior. */ + assert( zData==0 + || nData<0 + || nData>pVar->db->aLimit[SQLITE_LIMIT_LENGTH] + || ((u8*)zData)[nData]==0 + ); + rc = sqlite3VdbeMemSetText(pVar, zData, nData, xDel); + pVar->flags |= MEM_Term; + }else{ + rc = sqlite3VdbeMemSetStr(pVar, zData, nData, encoding, xDel); + if( encoding==0 ) pVar->enc = ENC(p->db); + } if( rc==SQLITE_OK && encoding!=0 ){ rc = sqlite3VdbeChangeEncoding(pVar, ENC(p->db)); } @@ -82641,10 +95435,10 @@ static int bindText( ** Bind a blob value to an SQL statement variable. */ SQLITE_API int sqlite3_bind_blob( - sqlite3_stmt *pStmt, - int i, - const void *zData, - int nData, + sqlite3_stmt *pStmt, + int i, + const void *zData, + int nData, void (*xDel)(void*) ){ #ifdef SQLITE_ENABLE_API_ARMOR @@ -82653,24 +95447,21 @@ SQLITE_API int sqlite3_bind_blob( return bindText(pStmt, i, zData, nData, xDel, 0); } SQLITE_API int sqlite3_bind_blob64( - sqlite3_stmt *pStmt, - int i, - const void *zData, - sqlite3_uint64 nData, + sqlite3_stmt *pStmt, + int i, + const void *zData, + sqlite3_uint64 nData, void (*xDel)(void*) ){ assert( xDel!=SQLITE_DYNAMIC ); - if( nData>0x7fffffff ){ - return invokeValueDestructor(zData, xDel, 0); - }else{ - return bindText(pStmt, i, zData, (int)nData, xDel, 0); - } + return bindText(pStmt, i, zData, nData, xDel, 0); } SQLITE_API int sqlite3_bind_double(sqlite3_stmt *pStmt, int i, double rValue){ int rc; Vdbe *p = (Vdbe *)pStmt; - rc = vdbeUnbind(p, i); + rc = vdbeUnbind(p, (u32)(i-1)); if( rc==SQLITE_OK ){ + assert( p!=0 && p->aVar!=0 && i>0 && i<=p->nVar ); /* tag-20240917-01 */ sqlite3VdbeMemSetDouble(&p->aVar[i-1], rValue); sqlite3_mutex_leave(p->db->mutex); } @@ -82682,8 +95473,9 @@ SQLITE_API int sqlite3_bind_int(sqlite3_stmt *p, int i, int iValue){ SQLITE_API int sqlite3_bind_int64(sqlite3_stmt *pStmt, int i, sqlite_int64 iValue){ int rc; Vdbe *p = (Vdbe *)pStmt; - rc = vdbeUnbind(p, i); + rc = vdbeUnbind(p, (u32)(i-1)); if( rc==SQLITE_OK ){ + assert( p!=0 && p->aVar!=0 && i>0 && i<=p->nVar ); /* tag-20240917-01 */ sqlite3VdbeMemSetInt64(&p->aVar[i-1], iValue); sqlite3_mutex_leave(p->db->mutex); } @@ -82692,8 +95484,9 @@ SQLITE_API int sqlite3_bind_int64(sqlite3_stmt *pStmt, int i, sqlite_int64 iValu SQLITE_API int sqlite3_bind_null(sqlite3_stmt *pStmt, int i){ int rc; Vdbe *p = (Vdbe*)pStmt; - rc = vdbeUnbind(p, i); + rc = vdbeUnbind(p, (u32)(i-1)); if( rc==SQLITE_OK ){ + assert( p!=0 && p->aVar!=0 && i>0 && i<=p->nVar ); /* tag-20240917-01 */ sqlite3_mutex_leave(p->db->mutex); } return rc; @@ -82707,8 +95500,9 @@ SQLITE_API int sqlite3_bind_pointer( ){ int rc; Vdbe *p = (Vdbe*)pStmt; - rc = vdbeUnbind(p, i); + rc = vdbeUnbind(p, (u32)(i-1)); if( rc==SQLITE_OK ){ + assert( p!=0 && p->aVar!=0 && i>0 && i<=p->nVar ); /* tag-20240917-01 */ sqlite3VdbeMemSetPointer(&p->aVar[i-1], pPtr, zPTtype, xDestructor); sqlite3_mutex_leave(p->db->mutex); }else if( xDestructor ){ @@ -82716,40 +95510,39 @@ SQLITE_API int sqlite3_bind_pointer( } return rc; } -SQLITE_API int sqlite3_bind_text( - sqlite3_stmt *pStmt, - int i, - const char *zData, - int nData, +SQLITE_API int sqlite3_bind_text( + sqlite3_stmt *pStmt, + int i, + const char *zData, + int nData, void (*xDel)(void*) ){ return bindText(pStmt, i, zData, nData, xDel, SQLITE_UTF8); } -SQLITE_API int sqlite3_bind_text64( - sqlite3_stmt *pStmt, - int i, - const char *zData, - sqlite3_uint64 nData, +SQLITE_API int sqlite3_bind_text64( + sqlite3_stmt *pStmt, + int i, + const char *zData, + sqlite3_uint64 nData, void (*xDel)(void*), unsigned char enc ){ assert( xDel!=SQLITE_DYNAMIC ); - if( nData>0x7fffffff ){ - return invokeValueDestructor(zData, xDel, 0); - }else{ + if( enc!=SQLITE_UTF8 && enc!=SQLITE_UTF8_ZT ){ if( enc==SQLITE_UTF16 ) enc = SQLITE_UTF16NATIVE; - return bindText(pStmt, i, zData, (int)nData, xDel, enc); + nData &= ~(u64)1; } + return bindText(pStmt, i, zData, nData, xDel, enc); } #ifndef SQLITE_OMIT_UTF16 SQLITE_API int sqlite3_bind_text16( - sqlite3_stmt *pStmt, - int i, - const void *zData, - int nData, + sqlite3_stmt *pStmt, + int i, + const void *zData, + int n, void (*xDel)(void*) ){ - return bindText(pStmt, i, zData, nData, xDel, SQLITE_UTF16NATIVE); + return bindText(pStmt, i, zData, n & ~(u64)1, xDel, SQLITE_UTF16NATIVE); } #endif /* SQLITE_OMIT_UTF16 */ SQLITE_API int sqlite3_bind_value(sqlite3_stmt *pStmt, int i, const sqlite3_value *pValue){ @@ -82760,7 +95553,10 @@ SQLITE_API int sqlite3_bind_value(sqlite3_stmt *pStmt, int i, const sqlite3_valu break; } case SQLITE_FLOAT: { - rc = sqlite3_bind_double(pStmt, i, pValue->u.r); + assert( pValue->flags & (MEM_Real|MEM_IntReal) ); + rc = sqlite3_bind_double(pStmt, i, + (pValue->flags & MEM_Real) ? pValue->u.r : (double)pValue->u.i + ); break; } case SQLITE_BLOB: { @@ -82786,9 +95582,14 @@ SQLITE_API int sqlite3_bind_value(sqlite3_stmt *pStmt, int i, const sqlite3_valu SQLITE_API int sqlite3_bind_zeroblob(sqlite3_stmt *pStmt, int i, int n){ int rc; Vdbe *p = (Vdbe *)pStmt; - rc = vdbeUnbind(p, i); + rc = vdbeUnbind(p, (u32)(i-1)); if( rc==SQLITE_OK ){ + assert( p!=0 && p->aVar!=0 && i>0 && i<=p->nVar ); /* tag-20240917-01 */ +#ifndef SQLITE_OMIT_INCRBLOB sqlite3VdbeMemSetZeroBlob(&p->aVar[i-1], n); +#else + rc = sqlite3VdbeMemSetZeroBlob(&p->aVar[i-1], n); +#endif sqlite3_mutex_leave(p->db->mutex); } return rc; @@ -82796,6 +95597,9 @@ SQLITE_API int sqlite3_bind_zeroblob(sqlite3_stmt *pStmt, int i, int n){ SQLITE_API int sqlite3_bind_zeroblob64(sqlite3_stmt *pStmt, int i, sqlite3_uint64 n){ int rc; Vdbe *p = (Vdbe *)pStmt; +#ifdef SQLITE_ENABLE_API_ARMOR + if( p==0 ) return SQLITE_MISUSE_BKPT; +#endif sqlite3_mutex_enter(p->db->mutex); if( n>(u64)p->db->aLimit[SQLITE_LIMIT_LENGTH] ){ rc = SQLITE_TOOBIG; @@ -82810,7 +95614,7 @@ SQLITE_API int sqlite3_bind_zeroblob64(sqlite3_stmt *pStmt, int i, sqlite3_uint6 /* ** Return the number of wildcards that can be potentially bound to. -** This routine is added to support DBD::SQLite. +** This routine is added to support DBD::SQLite. */ SQLITE_API int sqlite3_bind_parameter_count(sqlite3_stmt *pStmt){ Vdbe *p = (Vdbe*)pStmt; @@ -82916,12 +95720,48 @@ SQLITE_API int sqlite3_stmt_isexplain(sqlite3_stmt *pStmt){ return pStmt ? ((Vdbe*)pStmt)->explain : 0; } +/* +** Set the explain mode for a statement. +*/ +SQLITE_API int sqlite3_stmt_explain(sqlite3_stmt *pStmt, int eMode){ + Vdbe *v = (Vdbe*)pStmt; + int rc; +#ifdef SQLITE_ENABLE_API_ARMOR + if( pStmt==0 ) return SQLITE_MISUSE_BKPT; +#endif + sqlite3_mutex_enter(v->db->mutex); + if( ((int)v->explain)==eMode ){ + rc = SQLITE_OK; + }else if( eMode<0 || eMode>2 ){ + rc = SQLITE_ERROR; + }else if( (v->prepFlags & SQLITE_PREPARE_SAVESQL)==0 ){ + rc = SQLITE_ERROR; + }else if( v->eVdbeState!=VDBE_READY_STATE ){ + rc = SQLITE_BUSY; + }else if( v->nMem>=10 && (eMode!=2 || v->haveEqpOps) ){ + /* No reprepare necessary */ + v->explain = eMode; + rc = SQLITE_OK; + }else{ + v->explain = eMode; + rc = sqlite3Reprepare(v); + v->haveEqpOps = eMode==2; + } + if( v->explain ){ + v->nResColumn = 12 - 4*v->explain; + }else{ + v->nResColumn = v->nResAlloc; + } + sqlite3_mutex_leave(v->db->mutex); + return rc; +} + /* ** Return true if the prepared statement is in need of being reset. */ SQLITE_API int sqlite3_stmt_busy(sqlite3_stmt *pStmt){ Vdbe *v = (Vdbe*)pStmt; - return v!=0 && v->magic==VDBE_MAGIC_RUN && v->pc>=0; + return v!=0 && v->eVdbeState==VDBE_RUN_STATE; } /* @@ -82942,7 +95782,7 @@ SQLITE_API sqlite3_stmt *sqlite3_next_stmt(sqlite3 *pDb, sqlite3_stmt *pStmt){ if( pStmt==0 ){ pNext = (sqlite3_stmt*)pDb->pVdbe; }else{ - pNext = (sqlite3_stmt*)((Vdbe*)pStmt)->pNext; + pNext = (sqlite3_stmt*)((Vdbe*)pStmt)->pVNext; } sqlite3_mutex_leave(pDb->mutex); return pNext; @@ -82955,7 +95795,7 @@ SQLITE_API int sqlite3_stmt_status(sqlite3_stmt *pStmt, int op, int resetFlag){ Vdbe *pVdbe = (Vdbe*)pStmt; u32 v; #ifdef SQLITE_ENABLE_API_ARMOR - if( !pStmt + if( !pStmt || (op!=SQLITE_STMTSTATUS_MEMUSED && (op<0||op>=ArraySize(pVdbe->aCounter))) ){ (void)SQLITE_MISUSE_BKPT; @@ -82967,9 +95807,11 @@ SQLITE_API int sqlite3_stmt_status(sqlite3_stmt *pStmt, int op, int resetFlag){ sqlite3_mutex_enter(db->mutex); v = 0; db->pnBytesFreed = (int*)&v; - sqlite3VdbeClearObject(db, pVdbe); - sqlite3DbFree(db, pVdbe); + assert( db->lookaside.pEnd==db->lookaside.pTrueEnd ); + db->lookaside.pEnd = db->lookaside.pStart; + sqlite3VdbeDelete(pVdbe); db->pnBytesFreed = 0; + db->lookaside.pEnd = db->lookaside.pTrueEnd; sqlite3_mutex_leave(db->mutex); }else{ v = pVdbe->aCounter[op]; @@ -83034,8 +95876,8 @@ SQLITE_API const char *sqlite3_normalized_sql(sqlite3_stmt *pStmt){ ** if successful, or a NULL pointer if an OOM error is encountered. */ static UnpackedRecord *vdbeUnpackRecord( - KeyInfo *pKeyInfo, - int nKey, + KeyInfo *pKeyInfo, + int nKey, const void *pKey ){ UnpackedRecord *pRet; /* Return value */ @@ -83043,7 +95885,7 @@ static UnpackedRecord *vdbeUnpackRecord( pRet = sqlite3VdbeAllocUnpackedRecord(pKeyInfo); if( pRet ){ memset(pRet->aMem, 0, sizeof(Mem)*(pKeyInfo->nKeyField+1)); - sqlite3VdbeRecordUnpack(pKeyInfo, nKey, pKey, pRet); + sqlite3VdbeRecordUnpack(nKey, pKey, pRet); } return pRet; } @@ -83053,10 +95895,17 @@ static UnpackedRecord *vdbeUnpackRecord( ** a field of the row currently being updated or deleted. */ SQLITE_API int sqlite3_preupdate_old(sqlite3 *db, int iIdx, sqlite3_value **ppValue){ - PreUpdate *p = db->pPreUpdate; + PreUpdate *p; Mem *pMem; int rc = SQLITE_OK; + int iStore = 0; +#ifdef SQLITE_ENABLE_API_ARMOR + if( db==0 || ppValue==0 ){ + return SQLITE_MISUSE_BKPT; + } +#endif + p = db->pPreUpdate; /* Test that this call is being made from within an SQLITE_DELETE or ** SQLITE_UPDATE pre-update callback, and that iIdx is within range. */ if( !p || p->op==SQLITE_INSERT ){ @@ -83064,41 +95913,78 @@ SQLITE_API int sqlite3_preupdate_old(sqlite3 *db, int iIdx, sqlite3_value **ppVa goto preupdate_old_out; } if( p->pPk ){ - iIdx = sqlite3ColumnOfIndex(p->pPk, iIdx); + iStore = sqlite3TableColumnToIndex(p->pPk, iIdx); + }else if( iIdx >= p->pTab->nCol ){ + rc = SQLITE_MISUSE_BKPT; + goto preupdate_old_out; + }else{ + iStore = sqlite3TableColumnToStorage(p->pTab, iIdx); } - if( iIdx>=p->pCsr->nField || iIdx<0 ){ + if( iStore>=p->pCsr->nField || iStore<0 ){ rc = SQLITE_RANGE; goto preupdate_old_out; } - /* If the old.* record has not yet been loaded into memory, do so now. */ - if( p->pUnpacked==0 ){ - u32 nRec; - u8 *aRec; - - nRec = sqlite3BtreePayloadSize(p->pCsr->uc.pCursor); - aRec = sqlite3DbMallocRaw(db, nRec); - if( !aRec ) goto preupdate_old_out; - rc = sqlite3BtreePayload(p->pCsr->uc.pCursor, 0, nRec, aRec); - if( rc==SQLITE_OK ){ - p->pUnpacked = vdbeUnpackRecord(&p->keyinfo, nRec, aRec); - if( !p->pUnpacked ) rc = SQLITE_NOMEM; - } - if( rc!=SQLITE_OK ){ - sqlite3DbFree(db, aRec); - goto preupdate_old_out; - } - p->aRecord = aRec; - } - - pMem = *ppValue = &p->pUnpacked->aMem[iIdx]; if( iIdx==p->pTab->iPKey ){ + *ppValue = pMem = &p->oldipk; sqlite3VdbeMemSetInt64(pMem, p->iKey1); - }else if( iIdx>=p->pUnpacked->nField ){ - *ppValue = (sqlite3_value *)columnNullValue(); - }else if( p->pTab->aCol[iIdx].affinity==SQLITE_AFF_REAL ){ - if( pMem->flags & MEM_Int ){ - sqlite3VdbeMemRealify(pMem); + }else{ + + /* If the old.* record has not yet been loaded into memory, do so now. */ + if( p->pUnpacked==0 ){ + u32 nRec; + u8 *aRec; + + assert( p->pCsr->eCurType==CURTYPE_BTREE ); + nRec = sqlite3BtreePayloadSize(p->pCsr->uc.pCursor); + aRec = sqlite3DbMallocRaw(db, nRec); + if( !aRec ) goto preupdate_old_out; + rc = sqlite3BtreePayload(p->pCsr->uc.pCursor, 0, nRec, aRec); + if( rc==SQLITE_OK ){ + p->pUnpacked = vdbeUnpackRecord(p->pKeyinfo, nRec, aRec); + if( !p->pUnpacked ) rc = SQLITE_NOMEM; + } + if( rc!=SQLITE_OK ){ + sqlite3DbFree(db, aRec); + goto preupdate_old_out; + } + p->aRecord = aRec; + } + + pMem = *ppValue = &p->pUnpacked->aMem[iStore]; + if( iStore>=p->pUnpacked->nField ){ + /* This occurs when the table has been extended using ALTER TABLE + ** ADD COLUMN. The value to return is the default value of the column. */ + Column *pCol = &p->pTab->aCol[iIdx]; + if( pCol->iDflt>0 ){ + if( p->apDflt==0 ){ + int nByte; + assert( sizeof(sqlite3_value*)*UMXV(p->pTab->nCol) < 0x7fffffff ); + nByte = sizeof(sqlite3_value*)*p->pTab->nCol; + p->apDflt = (sqlite3_value**)sqlite3DbMallocZero(db, nByte); + if( p->apDflt==0 ) goto preupdate_old_out; + } + if( p->apDflt[iIdx]==0 ){ + sqlite3_value *pVal = 0; + Expr *pDflt; + assert( p->pTab!=0 && IsOrdinaryTable(p->pTab) ); + pDflt = p->pTab->u.tab.pDfltList->a[pCol->iDflt-1].pExpr; + rc = sqlite3ValueFromExpr(db, pDflt, ENC(db), pCol->affinity, &pVal); + if( rc==SQLITE_OK && pVal==0 ){ + rc = SQLITE_CORRUPT_BKPT; + } + p->apDflt[iIdx] = pVal; + } + *ppValue = p->apDflt[iIdx]; + }else{ + *ppValue = (sqlite3_value *)columnNullValue(); + } + }else if( p->pTab->aCol[iIdx].affinity==SQLITE_AFF_REAL ){ + if( pMem->flags & (MEM_Int|MEM_IntReal) ){ + testcase( pMem->flags & MEM_Int ); + testcase( pMem->flags & MEM_IntReal ); + sqlite3VdbeMemRealify(pMem); + } } } @@ -83114,8 +96000,13 @@ SQLITE_API int sqlite3_preupdate_old(sqlite3 *db, int iIdx, sqlite3_value **ppVa ** the number of columns in the row being updated, deleted or inserted. */ SQLITE_API int sqlite3_preupdate_count(sqlite3 *db){ - PreUpdate *p = db->pPreUpdate; - return (p ? p->keyinfo.nKeyField : 0); + PreUpdate *p; +#ifdef SQLITE_ENABLE_API_ARMOR + p = db!=0 ? db->pPreUpdate : 0; +#else + p = db->pPreUpdate; +#endif + return (p ? p->pKeyinfo->nKeyField : 0); } #endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ @@ -83125,36 +96016,69 @@ SQLITE_API int sqlite3_preupdate_count(sqlite3 *db){ ** only. It returns zero if the change that caused the callback was made ** immediately by a user SQL statement. Or, if the change was made by a ** trigger program, it returns the number of trigger programs currently -** on the stack (1 for a top-level trigger, 2 for a trigger fired by a +** on the stack (1 for a top-level trigger, 2 for a trigger fired by a ** top-level trigger etc.). ** ** For the purposes of the previous paragraph, a foreign key CASCADE, SET NULL ** or SET DEFAULT action is considered a trigger. */ SQLITE_API int sqlite3_preupdate_depth(sqlite3 *db){ - PreUpdate *p = db->pPreUpdate; + PreUpdate *p; +#ifdef SQLITE_ENABLE_API_ARMOR + p = db!=0 ? db->pPreUpdate : 0; +#else + p = db->pPreUpdate; +#endif return (p ? p->v->nFrame : 0); } #endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK +/* +** This function is designed to be called from within a pre-update callback +** only. +*/ +SQLITE_API int sqlite3_preupdate_blobwrite(sqlite3 *db){ + PreUpdate *p; +#ifdef SQLITE_ENABLE_API_ARMOR + p = db!=0 ? db->pPreUpdate : 0; +#else + p = db->pPreUpdate; +#endif + return (p ? p->iBlobWrite : -1); +} +#endif + #ifdef SQLITE_ENABLE_PREUPDATE_HOOK /* ** This function is called from within a pre-update callback to retrieve ** a field of the row currently being updated or inserted. */ SQLITE_API int sqlite3_preupdate_new(sqlite3 *db, int iIdx, sqlite3_value **ppValue){ - PreUpdate *p = db->pPreUpdate; + PreUpdate *p; int rc = SQLITE_OK; Mem *pMem; + int iStore = 0; +#ifdef SQLITE_ENABLE_API_ARMOR + if( db==0 || ppValue==0 ){ + return SQLITE_MISUSE_BKPT; + } +#endif + p = db->pPreUpdate; if( !p || p->op==SQLITE_DELETE ){ rc = SQLITE_MISUSE_BKPT; goto preupdate_new_out; } if( p->pPk && p->op!=SQLITE_UPDATE ){ - iIdx = sqlite3ColumnOfIndex(p->pPk, iIdx); + iStore = sqlite3TableColumnToIndex(p->pPk, iIdx); + }else if( iIdx >= p->pTab->nCol ){ + return SQLITE_MISUSE_BKPT; + }else{ + iStore = sqlite3TableColumnToStorage(p->pTab, iIdx); } - if( iIdx>=p->pCsr->nField || iIdx<0 ){ + + if( iStore>=p->pCsr->nField || iStore<0 ){ rc = SQLITE_RANGE; goto preupdate_new_out; } @@ -83167,40 +96091,41 @@ SQLITE_API int sqlite3_preupdate_new(sqlite3 *db, int iIdx, sqlite3_value **ppVa Mem *pData = &p->v->aMem[p->iNewReg]; rc = ExpandBlob(pData); if( rc!=SQLITE_OK ) goto preupdate_new_out; - pUnpack = vdbeUnpackRecord(&p->keyinfo, pData->n, pData->z); + pUnpack = vdbeUnpackRecord(p->pKeyinfo, pData->n, pData->z); if( !pUnpack ){ rc = SQLITE_NOMEM; goto preupdate_new_out; } p->pNewUnpacked = pUnpack; } - pMem = &pUnpack->aMem[iIdx]; + pMem = &pUnpack->aMem[iStore]; if( iIdx==p->pTab->iPKey ){ sqlite3VdbeMemSetInt64(pMem, p->iKey2); - }else if( iIdx>=pUnpack->nField ){ + }else if( iStore>=pUnpack->nField ){ pMem = (sqlite3_value *)columnNullValue(); } }else{ - /* For an UPDATE, memory cell (p->iNewReg+1+iIdx) contains the required + /* For an UPDATE, memory cell (p->iNewReg+1+iStore) contains the required ** value. Make a copy of the cell contents and return a pointer to it. ** It is not safe to return a pointer to the memory cell itself as the ** caller may modify the value text encoding. */ assert( p->op==SQLITE_UPDATE ); if( !p->aNew ){ - p->aNew = (Mem *)sqlite3DbMallocZero(db, sizeof(Mem) * p->pCsr->nField); + assert( sizeof(Mem)*UMXV(p->pCsr->nField) < 0x7fffffff ); + p->aNew = (Mem *)sqlite3DbMallocZero(db, sizeof(Mem)*p->pCsr->nField); if( !p->aNew ){ rc = SQLITE_NOMEM; goto preupdate_new_out; } } - assert( iIdx>=0 && iIdxpCsr->nField ); - pMem = &p->aNew[iIdx]; + assert( iStore>=0 && iStorepCsr->nField ); + pMem = &p->aNew[iStore]; if( pMem->flags==0 ){ if( iIdx==p->pTab->iPKey ){ sqlite3VdbeMemSetInt64(pMem, p->iKey2); }else{ - rc = sqlite3VdbeMemCopy(pMem, &p->v->aMem[p->iNewReg+1+iIdx]); + rc = sqlite3VdbeMemCopy(pMem, &p->v->aMem[p->iNewReg+1+iStore]); if( rc!=SQLITE_OK ) goto preupdate_new_out; } } @@ -83217,23 +96142,79 @@ SQLITE_API int sqlite3_preupdate_new(sqlite3 *db, int iIdx, sqlite3_value **ppVa /* ** Return status data for a single loop within query pStmt. */ -SQLITE_API int sqlite3_stmt_scanstatus( +SQLITE_API int sqlite3_stmt_scanstatus_v2( sqlite3_stmt *pStmt, /* Prepared statement being queried */ - int idx, /* Index of loop to report on */ + int iScan, /* Index of loop to report on */ int iScanStatusOp, /* Which metric to return */ + int flags, void *pOut /* OUT: Write the answer here */ ){ Vdbe *p = (Vdbe*)pStmt; - ScanStatus *pScan; - if( idx<0 || idx>=p->nScan ) return 1; + VdbeOp *aOp; + int nOp; + ScanStatus *pScan = 0; + int idx; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( p==0 || pOut==0 + || iScanStatusOpSQLITE_SCANSTAT_NCYCLE ){ + return 1; + } +#endif + aOp = p->aOp; + nOp = p->nOp; + if( p->pFrame ){ + VdbeFrame *pFrame; + for(pFrame=p->pFrame; pFrame->pParent; pFrame=pFrame->pParent); + aOp = pFrame->aOp; + nOp = pFrame->nOp; + } + + if( iScan<0 ){ + int ii; + if( iScanStatusOp==SQLITE_SCANSTAT_NCYCLE ){ + i64 res = 0; + for(ii=0; iinScan; idx++){ + pScan = &p->aScan[idx]; + if( pScan->zName ){ + iScan--; + if( iScan<0 ) break; + } + } + } + if( idx>=p->nScan ) return 1; + assert( pScan==0 || pScan==&p->aScan[idx] ); pScan = &p->aScan[idx]; + switch( iScanStatusOp ){ case SQLITE_SCANSTAT_NLOOP: { - *(sqlite3_int64*)pOut = p->anExec[pScan->addrLoop]; + if( pScan->addrLoop>0 ){ + *(sqlite3_int64*)pOut = aOp[pScan->addrLoop].nExec; + }else{ + *(sqlite3_int64*)pOut = -1; + } break; } case SQLITE_SCANSTAT_NVISIT: { - *(sqlite3_int64*)pOut = p->anExec[pScan->addrVisit]; + if( pScan->addrVisit>0 ){ + *(sqlite3_int64*)pOut = aOp[pScan->addrVisit].nExec; + }else{ + *(sqlite3_int64*)pOut = -1; + } break; } case SQLITE_SCANSTAT_EST: { @@ -83252,7 +96233,7 @@ SQLITE_API int sqlite3_stmt_scanstatus( } case SQLITE_SCANSTAT_EXPLAIN: { if( pScan->addrExplain ){ - *(const char**)pOut = p->aOp[ pScan->addrExplain ].p4.z; + *(const char**)pOut = aOp[ pScan->addrExplain ].p4.z; }else{ *(const char**)pOut = 0; } @@ -83260,12 +96241,51 @@ SQLITE_API int sqlite3_stmt_scanstatus( } case SQLITE_SCANSTAT_SELECTID: { if( pScan->addrExplain ){ - *(int*)pOut = p->aOp[ pScan->addrExplain ].p1; + *(int*)pOut = aOp[ pScan->addrExplain ].p1; + }else{ + *(int*)pOut = -1; + } + break; + } + case SQLITE_SCANSTAT_PARENTID: { + if( pScan->addrExplain ){ + *(int*)pOut = aOp[ pScan->addrExplain ].p2; }else{ *(int*)pOut = -1; } break; } + case SQLITE_SCANSTAT_NCYCLE: { + i64 res = 0; + if( pScan->aAddrRange[0]==0 ){ + res = -1; + }else{ + int ii; + for(ii=0; iiaAddrRange); ii+=2){ + int iIns = pScan->aAddrRange[ii]; + int iEnd = pScan->aAddrRange[ii+1]; + if( iIns==0 ) break; + if( iIns>0 ){ + while( iIns<=iEnd ){ + res += aOp[iIns].nCycle; + iIns++; + } + }else{ + int iOp; + for(iOp=0; iOpp1!=iEnd ) continue; + if( (sqlite3OpcodeProperty[pOp->opcode] & OPFLG_NCYCLE)==0 ){ + continue; + } + res += aOp[iOp].nCycle; + } + } + } + } + *(i64*)pOut = res; + break; + } default: { return 1; } @@ -83273,12 +96293,29 @@ SQLITE_API int sqlite3_stmt_scanstatus( return 0; } +/* +** Return status data for a single loop within query pStmt. +*/ +SQLITE_API int sqlite3_stmt_scanstatus( + sqlite3_stmt *pStmt, /* Prepared statement being queried */ + int iScan, /* Index of loop to report on */ + int iScanStatusOp, /* Which metric to return */ + void *pOut /* OUT: Write the answer here */ +){ + return sqlite3_stmt_scanstatus_v2(pStmt, iScan, iScanStatusOp, 0, pOut); +} + /* ** Zero all counters associated with the sqlite3_stmt_scanstatus() data. */ SQLITE_API void sqlite3_stmt_scanstatus_reset(sqlite3_stmt *pStmt){ Vdbe *p = (Vdbe*)pStmt; - memset(p->anExec, 0, p->nOp * sizeof(i64)); + int ii; + for(ii=0; p!=0 && iinOp; ii++){ + Op *pOp = &p->aOp[ii]; + pOp->nExec = 0; + pOp->nCycle = 0; + } } #endif /* SQLITE_ENABLE_STMT_SCANSTATUS */ @@ -83312,10 +96349,10 @@ SQLITE_API void sqlite3_stmt_scanstatus_reset(sqlite3_stmt *pStmt){ ** a host parameter. If the text contains no host parameters, return ** the total number of bytes in the text. */ -static int findNextHostParameter(const char *zSql, int *pnToken){ +static i64 findNextHostParameter(const char *zSql, i64 *pnToken){ int tokenType; - int nTotal = 0; - int n; + i64 nTotal = 0; + i64 n; *pnToken = 0; while( zSql[0] ){ @@ -83334,8 +96371,8 @@ static int findNextHostParameter(const char *zSql, int *pnToken){ /* ** This function returns a pointer to a nul-terminated string in memory ** obtained from sqlite3DbMalloc(). If sqlite3.nVdbeExec is 1, then the -** string contains a copy of zRawSql but with host parameters expanded to -** their current bindings. Or, if sqlite3.nVdbeExec is greater than 1, +** string contains a copy of zRawSql but with host parameters expanded to +** their current bindings. Or, if sqlite3.nVdbeExec is greater than 1, ** then the returned string holds a copy of zRawSql with "-- " prepended ** to each line of text. ** @@ -83362,19 +96399,17 @@ SQLITE_PRIVATE char *sqlite3VdbeExpandSql( sqlite3 *db; /* The database connection */ int idx = 0; /* Index of a host parameter */ int nextIndex = 1; /* Index of next ? host parameter */ - int n; /* Length of a token prefix */ - int nToken; /* Length of the parameter token */ + i64 n; /* Length of a token prefix */ + i64 nToken; /* Length of the parameter token */ int i; /* Loop counter */ Mem *pVar; /* Value of a host parameter */ StrAccum out; /* Accumulate the output here */ #ifndef SQLITE_OMIT_UTF16 Mem utf8; /* Used to convert UTF16 into UTF8 for display */ #endif - char zBase[100]; /* Initial working space */ db = p->db; - sqlite3StrAccumInit(&out, 0, zBase, sizeof(zBase), - db->aLimit[SQLITE_LIMIT_LENGTH]); + sqlite3StrAccumInit(&out, 0, 0, 0, db->aLimit[SQLITE_LIMIT_LENGTH]); if( db->nVdbeExec>1 ){ while( *zRawSql ){ const char *zStart = zRawSql; @@ -83411,12 +96446,12 @@ SQLITE_PRIVATE char *sqlite3VdbeExpandSql( assert( idx>0 ); } zRawSql += nToken; - nextIndex = idx + 1; + nextIndex = MAX(idx + 1, nextIndex); assert( idx>0 && idx<=p->nVar ); pVar = &p->aVar[idx-1]; if( pVar->flags & MEM_Null ){ sqlite3_str_append(&out, "NULL", 4); - }else if( pVar->flags & MEM_Int ){ + }else if( pVar->flags & (MEM_Int|MEM_IntReal) ){ sqlite3_str_appendf(&out, "%lld", pVar->u.i); }else if( pVar->flags & MEM_Real ){ sqlite3_str_appendf(&out, "%!.15g", pVar->u.r); @@ -83441,7 +96476,7 @@ SQLITE_PRIVATE char *sqlite3VdbeExpandSql( nOut = SQLITE_TRACE_SIZE_LIMIT; while( nOutn && (pVar->z[nOut]&0xc0)==0x80 ){ nOut++; } } -#endif +#endif sqlite3_str_appendf(&out, "'%.*q'", nOut, pVar->z); #ifdef SQLITE_TRACE_SIZE_LIMIT if( nOutn ){ @@ -83504,6 +96539,103 @@ SQLITE_PRIVATE char *sqlite3VdbeExpandSql( /* #include "sqliteInt.h" */ /* #include "vdbeInt.h" */ +/* +** High-resolution hardware timer used for debugging and testing only. +*/ +#if defined(VDBE_PROFILE) \ + || defined(SQLITE_PERFORMANCE_TRACE) \ + || defined(SQLITE_ENABLE_STMT_SCANSTATUS) +/************** Include hwtime.h in the middle of vdbe.c *********************/ +/************** Begin file hwtime.h ******************************************/ +/* +** 2008 May 27 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This file contains inline asm code for retrieving "high-performance" +** counters for x86 and x86_64 class CPUs. +*/ +#ifndef SQLITE_HWTIME_H +#define SQLITE_HWTIME_H + +#if defined(_MSC_VER) && defined(_WIN32) + +/* #include "windows.h" */ + #include + + __inline sqlite3_uint64 sqlite3Hwtime(void){ + LARGE_INTEGER tm; + QueryPerformanceCounter(&tm); + return (sqlite3_uint64)tm.QuadPart; + } + +#elif !defined(__STRICT_ANSI__) && defined(__GNUC__) && \ + (defined(i386) || defined(__i386__) || defined(_M_IX86)) + + __inline__ sqlite_uint64 sqlite3Hwtime(void){ + unsigned int lo, hi; + __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi)); + return (sqlite_uint64)hi << 32 | lo; + } + +#elif !defined(__STRICT_ANSI__) && (defined(__GNUC__) && defined(__x86_64__)) + + __inline__ sqlite_uint64 sqlite3Hwtime(void){ + unsigned int lo, hi; + __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi)); + return (sqlite_uint64)hi << 32 | lo; + } + +#elif !defined(__STRICT_ANSI__) && defined(__GNUC__) && defined(__aarch64__) + + __inline__ sqlite_uint64 sqlite3Hwtime(void){ + sqlite3_uint64 cnt; + __asm__ __volatile__ ("mrs %0, cntvct_el0" : "=r" (cnt)); + return cnt; + } + +#elif !defined(__STRICT_ANSI__) && (defined(__GNUC__) && defined(__ppc__)) + + __inline__ sqlite_uint64 sqlite3Hwtime(void){ + unsigned long long retval; + unsigned long junk; + __asm__ __volatile__ ("\n\ + 1: mftbu %1\n\ + mftb %L0\n\ + mftbu %0\n\ + cmpw %0,%1\n\ + bne 1b" + : "=r" (retval), "=r" (junk)); + return retval; + } + +#else + + /* + ** asm() is needed for hardware timing support. Without asm(), + ** disable the sqlite3Hwtime() routine. + ** + ** sqlite3Hwtime() is only used for some obscure debugging + ** and analysis configurations, not in any deliverable, so this + ** should not be a great loss. + */ +SQLITE_PRIVATE sqlite_uint64 sqlite3Hwtime(void){ return ((sqlite_uint64)0); } + +#endif + +#endif /* !defined(SQLITE_HWTIME_H) */ + +/************** End of hwtime.h **********************************************/ +/************** Continuing where we left off in vdbe.c ***********************/ +#endif + /* ** Invoke this macro on memory cells just prior to changing the ** value of the cell. This macro verifies that shallow copies are @@ -83600,6 +96732,30 @@ SQLITE_API int sqlite3_found_count = 0; # define UPDATE_MAX_BLOBSIZE(P) #endif +#ifdef SQLITE_DEBUG +/* This routine provides a convenient place to set a breakpoint during +** tracing with PRAGMA vdbe_trace=on. The breakpoint fires right after +** each opcode is printed. Variables "pc" (program counter) and pOp are +** available to add conditionals to the breakpoint. GDB example: +** +** break test_trace_breakpoint if pc=22 +** +** Other useful labels for breakpoints include: +** test_addop_breakpoint(pc,pOp) +** sqlite3CorruptError(lineno) +** sqlite3MisuseError(lineno) +** sqlite3CantopenError(lineno) +*/ +static void test_trace_breakpoint(int pc, Op *pOp, Vdbe *v){ + static u64 n = 0; + (void)pc; + (void)pOp; + (void)v; + n++; + if( n==LARGEST_UINT64 ) abort(); /* So that n is used, preventing a warning */ +} +#endif + /* ** Invoke the VDBE coverage callback, if that callback is defined. This ** feature is used for test suite validation only and does not appear an @@ -83614,7 +96770,7 @@ SQLITE_API int sqlite3_found_count = 0; ** ** In other words, if M is 2, then I is either 0 (for fall-through) or ** 1 (for when the branch is taken). If M is 3, the I is 0 for an -** ordinary fall-through, I is 1 if the branch was taken, and I is 2 +** ordinary fall-through, I is 1 if the branch was taken, and I is 2 ** if the result of comparison is NULL. For M=3, I=2 the jump may or ** may not be taken, depending on the SQLITE_JUMPIFNULL flags in p5. ** When M is 4, that means that an OP_Jump is being run. I is 0, 1, or 2 @@ -83678,14 +96834,6 @@ SQLITE_API int sqlite3_found_count = 0; } #endif -/* -** Convert the given register into a string if it isn't one -** already. Return non-zero if a malloc() fails. -*/ -#define Stringify(P, enc) \ - if(((P)->flags&(MEM_Str|MEM_Blob))==0 && sqlite3VdbeMemStringify(P,enc,0)) \ - { goto no_mem; } - /* ** An ephemeral string value (signified by the MEM_Ephem flag) contains ** a pointer to a dynamically allocated string where some other entity @@ -83712,11 +96860,10 @@ static VdbeCursor *allocateCursor( Vdbe *p, /* The virtual machine */ int iCur, /* Index of the new VdbeCursor */ int nField, /* Number of fields in the table or index */ - int iDb, /* Database the cursor belongs to, or -1 */ u8 eCurType /* Type of the new cursor */ ){ /* Find the memory cell that will be used to store the blob of memory - ** required for this VdbeCursor structure. It is convenient to use a + ** required for this VdbeCursor structure. It is convenient to use a ** vdbe memory cell to manage the memory allocation required for a ** VdbeCursor structure for the following reasons: ** @@ -83735,38 +96882,66 @@ static VdbeCursor *allocateCursor( */ Mem *pMem = iCur>0 ? &p->aMem[p->nMem-iCur] : p->aMem; - int nByte; + i64 nByte; VdbeCursor *pCx = 0; - nByte = - ROUND8(sizeof(VdbeCursor)) + 2*sizeof(u32)*nField + - (eCurType==CURTYPE_BTREE?sqlite3BtreeCursorSize():0); + nByte = SZ_VDBECURSOR(nField); + assert( ROUND8(nByte)==nByte ); + if( eCurType==CURTYPE_BTREE ) nByte += sqlite3BtreeCursorSize(); assert( iCur>=0 && iCurnCursor ); if( p->apCsr[iCur] ){ /*OPTIMIZATION-IF-FALSE*/ - /* Before calling sqlite3VdbeFreeCursor(), ensure the isEphemeral flag - ** is clear. Otherwise, if this is an ephemeral cursor created by - ** OP_OpenDup, the cursor will not be closed and will still be part - ** of a BtShared.pCursor list. */ - p->apCsr[iCur]->isEphemeral = 0; - sqlite3VdbeFreeCursor(p, p->apCsr[iCur]); + sqlite3VdbeFreeCursorNN(p, p->apCsr[iCur]); p->apCsr[iCur] = 0; } - if( SQLITE_OK==sqlite3VdbeMemClearAndResize(pMem, nByte) ){ - p->apCsr[iCur] = pCx = (VdbeCursor*)pMem->z; - memset(pCx, 0, offsetof(VdbeCursor,pAltCursor)); - pCx->eCurType = eCurType; - pCx->iDb = iDb; - pCx->nField = nField; - pCx->aOffset = &pCx->aType[nField]; - if( eCurType==CURTYPE_BTREE ){ - pCx->uc.pCursor = (BtCursor*) - &pMem->z[ROUND8(sizeof(VdbeCursor))+2*sizeof(u32)*nField]; - sqlite3BtreeCursorZero(pCx->uc.pCursor); + + /* There used to be a call to sqlite3VdbeMemClearAndResize() to make sure + ** the pMem used to hold space for the cursor has enough storage available + ** in pMem->zMalloc. But for the special case of the aMem[] entries used + ** to hold cursors, it is faster to in-line the logic. */ + assert( pMem->flags==MEM_Undefined ); + assert( (pMem->flags & MEM_Dyn)==0 ); + assert( pMem->szMalloc==0 || pMem->z==pMem->zMalloc ); + if( pMem->szMallocszMalloc>0 ){ + sqlite3DbFreeNN(pMem->db, pMem->zMalloc); + } + pMem->z = pMem->zMalloc = sqlite3DbMallocRaw(pMem->db, nByte); + if( pMem->zMalloc==0 ){ + pMem->szMalloc = 0; + return 0; } + pMem->szMalloc = (int)nByte; + } + + p->apCsr[iCur] = pCx = (VdbeCursor*)pMem->zMalloc; + memset(pCx, 0, offsetof(VdbeCursor,pAltCursor)); + pCx->eCurType = eCurType; + pCx->nField = nField; + pCx->aOffset = &pCx->aType[nField]; + if( eCurType==CURTYPE_BTREE ){ + assert( ROUND8(SZ_VDBECURSOR(nField))==SZ_VDBECURSOR(nField) ); + pCx->uc.pCursor = (BtCursor*)&pMem->z[SZ_VDBECURSOR(nField)]; + sqlite3BtreeCursorZero(pCx->uc.pCursor); } return pCx; } +/* +** The string in pRec is known to look like an integer and to have a +** floating point value of rValue. Return true and set *piValue to the +** integer value if the string is in range to be an integer. Otherwise, +** return false. +*/ +static int alsoAnInt(Mem *pRec, double rValue, i64 *piValue){ + i64 iValue; + iValue = sqlite3RealToI64(rValue); + if( sqlite3RealSameAsInt(rValue,iValue) ){ + *piValue = iValue; + return 1; + } + return 0==sqlite3Atoi64(pRec->z, piValue, pRec->n, pRec->enc); +} + /* ** Try to convert a value into a numeric representation if we can ** do so without loss of information. In other words, if the string @@ -83784,12 +96959,11 @@ static VdbeCursor *allocateCursor( */ static void applyNumericAffinity(Mem *pRec, int bTryForInt){ double rValue; - i64 iValue; - u8 enc = pRec->enc; - assert( (pRec->flags & (MEM_Str|MEM_Int|MEM_Real))==MEM_Str ); - if( sqlite3AtoF(pRec->z, &rValue, pRec->n, enc)==0 ) return; - if( 0==sqlite3Atoi64(pRec->z, &iValue, pRec->n, enc) ){ - pRec->u.i = iValue; + int rc; + assert( (pRec->flags & (MEM_Str|MEM_Int|MEM_Real|MEM_IntReal))==MEM_Str ); + rc = sqlite3MemRealValueRC(pRec, &rValue); + if( rc<=0 ) return; + if( (rc&2)==0 && alsoAnInt(pRec, rValue, &pRec->u.i) ){ pRec->flags |= MEM_Int; }else{ pRec->u.r = rValue; @@ -83809,16 +96983,21 @@ static void applyNumericAffinity(Mem *pRec, int bTryForInt){ ** SQLITE_AFF_INTEGER: ** SQLITE_AFF_REAL: ** SQLITE_AFF_NUMERIC: -** Try to convert pRec to an integer representation or a +** Try to convert pRec to an integer representation or a ** floating-point representation if an integer representation ** is not possible. Note that the integer representation is ** always preferred, even if the affinity is REAL, because ** an integer representation is more space efficient on disk. ** +** SQLITE_AFF_FLEXNUM: +** If the value is text, then try to convert it into a number of +** some kind (integer or real) but do not make any other changes. +** ** SQLITE_AFF_TEXT: ** Convert pRec to a text representation. ** ** SQLITE_AFF_BLOB: +** SQLITE_AFF_NONE: ** No-op. pRec is unchanged. */ static void applyAffinity( @@ -83828,26 +97007,29 @@ static void applyAffinity( ){ if( affinity>=SQLITE_AFF_NUMERIC ){ assert( affinity==SQLITE_AFF_INTEGER || affinity==SQLITE_AFF_REAL - || affinity==SQLITE_AFF_NUMERIC ); + || affinity==SQLITE_AFF_NUMERIC || affinity==SQLITE_AFF_FLEXNUM ); if( (pRec->flags & MEM_Int)==0 ){ /*OPTIMIZATION-IF-FALSE*/ - if( (pRec->flags & MEM_Real)==0 ){ + if( (pRec->flags & (MEM_Real|MEM_IntReal))==0 ){ if( pRec->flags & MEM_Str ) applyNumericAffinity(pRec,1); - }else{ + }else if( affinity<=SQLITE_AFF_REAL ){ sqlite3VdbeIntegerAffinity(pRec); } } }else if( affinity==SQLITE_AFF_TEXT ){ /* Only attempt the conversion to TEXT if there is an integer or real ** representation (blob and NULL do not get converted) but no string - ** representation. It would be harmless to repeat the conversion if + ** representation. It would be harmless to repeat the conversion if ** there is already a string rep, but it is pointless to waste those ** CPU cycles. */ if( 0==(pRec->flags&MEM_Str) ){ /*OPTIMIZATION-IF-FALSE*/ - if( (pRec->flags&(MEM_Real|MEM_Int)) ){ + if( (pRec->flags&(MEM_Real|MEM_Int|MEM_IntReal)) ){ + testcase( pRec->flags & MEM_Int ); + testcase( pRec->flags & MEM_Real ); + testcase( pRec->flags & MEM_IntReal ); sqlite3VdbeMemStringify(pRec, enc, 1); } } - pRec->flags &= ~(MEM_Real|MEM_Int); + pRec->flags &= ~(MEM_Real|MEM_Int|MEM_IntReal); } } @@ -83861,19 +97043,22 @@ SQLITE_API int sqlite3_value_numeric_type(sqlite3_value *pVal){ int eType = sqlite3_value_type(pVal); if( eType==SQLITE_TEXT ){ Mem *pMem = (Mem*)pVal; + assert( pMem->db!=0 ); + sqlite3_mutex_enter(pMem->db->mutex); applyNumericAffinity(pMem, 0); + sqlite3_mutex_leave(pMem->db->mutex); eType = sqlite3_value_type(pVal); } return eType; } /* -** Exported version of applyAffinity(). This one works on sqlite3_value*, +** Exported version of applyAffinity(). This one works on sqlite3_value*, ** not the internal Mem* type. */ SQLITE_PRIVATE void sqlite3ValueApplyAffinity( - sqlite3_value *pVal, - u8 affinity, + sqlite3_value *pVal, + u8 affinity, u8 enc ){ applyAffinity((Mem *)pVal, affinity, enc); @@ -83886,13 +97071,24 @@ SQLITE_PRIVATE void sqlite3ValueApplyAffinity( ** accordingly. */ static u16 SQLITE_NOINLINE computeNumericType(Mem *pMem){ - assert( (pMem->flags & (MEM_Int|MEM_Real))==0 ); + int rc; + sqlite3_int64 ix; + assert( (pMem->flags & (MEM_Int|MEM_Real|MEM_IntReal))==0 ); assert( (pMem->flags & (MEM_Str|MEM_Blob))!=0 ); - ExpandBlob(pMem); - if( sqlite3AtoF(pMem->z, &pMem->u.r, pMem->n, pMem->enc)==0 ){ - return 0; + if( ExpandBlob(pMem) ){ + pMem->u.i = 0; + return MEM_Int; } - if( sqlite3Atoi64(pMem->z, &pMem->u.i, pMem->n, pMem->enc)==0 ){ + rc = sqlite3MemRealValueRC(pMem, &pMem->u.r); + if( rc<=0 ){ + if( (rc&2)==0 && sqlite3Atoi64(pMem->z, &ix, pMem->n, pMem->enc)<=1 ){ + pMem->u.i = ix; + return MEM_Int; + }else{ + return MEM_Real; + } + }else if( (rc&2)==0 && sqlite3Atoi64(pMem->z, &ix, pMem->n, pMem->enc)==0 ){ + pMem->u.i = ix; return MEM_Int; } return MEM_Real; @@ -83900,18 +97096,24 @@ static u16 SQLITE_NOINLINE computeNumericType(Mem *pMem){ /* ** Return the numeric type for pMem, either MEM_Int or MEM_Real or both or -** none. +** none. ** ** Unlike applyNumericAffinity(), this routine does not modify pMem->flags. ** But it does set pMem->u.r and pMem->u.i appropriately. */ static u16 numericType(Mem *pMem){ - if( pMem->flags & (MEM_Int|MEM_Real) ){ - return pMem->flags & (MEM_Int|MEM_Real); - } - if( pMem->flags & (MEM_Str|MEM_Blob) ){ - return computeNumericType(pMem); - } + assert( (pMem->flags & MEM_Null)==0 + || pMem->db==0 || pMem->db->mallocFailed ); + if( pMem->flags & (MEM_Int|MEM_Real|MEM_IntReal|MEM_Null) ){ + testcase( pMem->flags & MEM_Int ); + testcase( pMem->flags & MEM_Real ); + testcase( pMem->flags & MEM_IntReal ); + return pMem->flags & (MEM_Int|MEM_Real|MEM_IntReal|MEM_Null); + } + assert( pMem->flags & (MEM_Str|MEM_Blob) ); + testcase( pMem->flags & MEM_Str ); + testcase( pMem->flags & MEM_Blob ); + return computeNumericType(pMem); return 0; } @@ -83920,12 +97122,9 @@ static u16 numericType(Mem *pMem){ ** Write a nice string representation of the contents of cell pMem ** into buffer zBuf, length nBuf. */ -SQLITE_PRIVATE void sqlite3VdbeMemPrettyPrint(Mem *pMem, char *zBuf){ - char *zCsr = zBuf; +SQLITE_PRIVATE void sqlite3VdbeMemPrettyPrint(Mem *pMem, StrAccum *pStr){ int f = pMem->flags; - static const char *const encnames[] = {"(X)", "(8)", "(16LE)", "(16BE)"}; - if( f&MEM_Blob ){ int i; char c; @@ -83941,55 +97140,43 @@ SQLITE_PRIVATE void sqlite3VdbeMemPrettyPrint(Mem *pMem, char *zBuf){ }else{ c = 's'; } - *(zCsr++) = c; - sqlite3_snprintf(100, zCsr, "%d[", pMem->n); - zCsr += sqlite3Strlen30(zCsr); - for(i=0; i<16 && in; i++){ - sqlite3_snprintf(100, zCsr, "%02X", ((int)pMem->z[i] & 0xFF)); - zCsr += sqlite3Strlen30(zCsr); + sqlite3_str_appendf(pStr, "%cx[", c); + for(i=0; i<25 && in; i++){ + sqlite3_str_appendf(pStr, "%02X", ((int)pMem->z[i] & 0xFF)); } - for(i=0; i<16 && in; i++){ + sqlite3_str_appendf(pStr, "|"); + for(i=0; i<25 && in; i++){ char z = pMem->z[i]; - if( z<32 || z>126 ) *zCsr++ = '.'; - else *zCsr++ = z; + sqlite3_str_appendchar(pStr, 1, (z<32||z>126)?'.':z); } - *(zCsr++) = ']'; + sqlite3_str_appendf(pStr,"]"); if( f & MEM_Zero ){ - sqlite3_snprintf(100, zCsr,"+%dz",pMem->u.nZero); - zCsr += sqlite3Strlen30(zCsr); + sqlite3_str_appendf(pStr, "+%dz",pMem->u.nZero); } - *zCsr = '\0'; }else if( f & MEM_Str ){ - int j, k; - zBuf[0] = ' '; + int j; + u8 c; if( f & MEM_Dyn ){ - zBuf[1] = 'z'; + c = 'z'; assert( (f & (MEM_Static|MEM_Ephem))==0 ); }else if( f & MEM_Static ){ - zBuf[1] = 't'; + c = 't'; assert( (f & (MEM_Dyn|MEM_Ephem))==0 ); }else if( f & MEM_Ephem ){ - zBuf[1] = 'e'; + c = 'e'; assert( (f & (MEM_Static|MEM_Dyn))==0 ); }else{ - zBuf[1] = 's'; + c = 's'; } - k = 2; - sqlite3_snprintf(100, &zBuf[k], "%d", pMem->n); - k += sqlite3Strlen30(&zBuf[k]); - zBuf[k++] = '['; - for(j=0; j<15 && jn; j++){ - u8 c = pMem->z[j]; - if( c>=0x20 && c<0x7f ){ - zBuf[k++] = c; - }else{ - zBuf[k++] = '.'; - } + sqlite3_str_appendf(pStr, " %c%d[", c, pMem->n); + for(j=0; j<25 && jn; j++){ + c = pMem->z[j]; + sqlite3_str_appendchar(pStr, 1, (c>=0x20&&c<=0x7f) ? c : '.'); + } + sqlite3_str_appendf(pStr, "]%s", encnames[pMem->enc]); + if( f & MEM_Term ){ + sqlite3_str_appendf(pStr, "(0-term)"); } - zBuf[k++] = ']'; - sqlite3_snprintf(100,&zBuf[k], encnames[pMem->enc]); - k += sqlite3Strlen30(&zBuf[k]); - zBuf[k++] = 0; } } #endif @@ -84005,142 +97192,67 @@ static void memTracePrint(Mem *p){ printf(p->flags & MEM_Zero ? " NULL-nochng" : " NULL"); }else if( (p->flags & (MEM_Int|MEM_Str))==(MEM_Int|MEM_Str) ){ printf(" si:%lld", p->u.i); + }else if( (p->flags & (MEM_IntReal))!=0 ){ + printf(" ir:%lld", p->u.i); }else if( p->flags & MEM_Int ){ printf(" i:%lld", p->u.i); #ifndef SQLITE_OMIT_FLOATING_POINT }else if( p->flags & MEM_Real ){ - printf(" r:%g", p->u.r); + printf(" r:%.17g", p->u.r); #endif }else if( sqlite3VdbeMemIsRowSet(p) ){ printf(" (rowset)"); }else{ - char zBuf[200]; - sqlite3VdbeMemPrettyPrint(p, zBuf); - printf(" %s", zBuf); + StrAccum acc; + char zBuf[1000]; + sqlite3StrAccumInit(&acc, 0, zBuf, sizeof(zBuf), 0); + sqlite3VdbeMemPrettyPrint(p, &acc); + printf(" %s", sqlite3StrAccumFinish(&acc)); } if( p->flags & MEM_Subtype ) printf(" subtype=0x%02x", p->eSubtype); } static void registerTrace(int iReg, Mem *p){ - printf("REG[%d] = ", iReg); + printf("R[%d] = ", iReg); memTracePrint(p); + if( p->pScopyFrom ){ + assert( p->pScopyFrom->bScopy ); + printf(" <== R[%d]", (int)(p->pScopyFrom - &p[-iReg])); + } printf("\n"); sqlite3VdbeCheckMemInvariants(p); } +/**/ void sqlite3PrintMem(Mem *pMem){ + memTracePrint(pMem); + printf("\n"); + fflush(stdout); +} #endif #ifdef SQLITE_DEBUG -# define REGISTER_TRACE(R,M) if(db->flags&SQLITE_VdbeTrace)registerTrace(R,M) -#else -# define REGISTER_TRACE(R,M) -#endif - - -#ifdef VDBE_PROFILE - -/* -** hwtime.h contains inline assembler code for implementing -** high-performance timing routines. -*/ -/************** Include hwtime.h in the middle of vdbe.c *********************/ -/************** Begin file hwtime.h ******************************************/ -/* -** 2008 May 27 -** -** The author disclaims copyright to this source code. In place of -** a legal notice, here is a blessing: -** -** May you do good and not evil. -** May you find forgiveness for yourself and forgive others. -** May you share freely, never taking more than you give. -** -****************************************************************************** -** -** This file contains inline asm code for retrieving "high-performance" -** counters for x86 class CPUs. -*/ -#ifndef SQLITE_HWTIME_H -#define SQLITE_HWTIME_H - /* -** The following routine only works on pentium-class (or newer) processors. -** It uses the RDTSC opcode to read the cycle count value out of the -** processor and returns that value. This can be used for high-res -** profiling. +** Show the values of all registers in the virtual machine. Used for +** interactive debugging. */ -#if (defined(__GNUC__) || defined(_MSC_VER)) && \ - (defined(i386) || defined(__i386__) || defined(_M_IX86)) - - #if defined(__GNUC__) - - __inline__ sqlite_uint64 sqlite3Hwtime(void){ - unsigned int lo, hi; - __asm__ __volatile__ ("rdtsc" : "=a" (lo), "=d" (hi)); - return (sqlite_uint64)hi << 32 | lo; - } - - #elif defined(_MSC_VER) - - __declspec(naked) __inline sqlite_uint64 __cdecl sqlite3Hwtime(void){ - __asm { - rdtsc - ret ; return value at EDX:EAX - } - } - - #endif - -#elif (defined(__GNUC__) && defined(__x86_64__)) +SQLITE_PRIVATE void sqlite3VdbeRegisterDump(Vdbe *v){ + int i; + for(i=1; inMem; i++) registerTrace(i, v->aMem+i); +} +#endif /* SQLITE_DEBUG */ - __inline__ sqlite_uint64 sqlite3Hwtime(void){ - unsigned long val; - __asm__ __volatile__ ("rdtsc" : "=A" (val)); - return val; - } - -#elif (defined(__GNUC__) && defined(__ppc__)) - - __inline__ sqlite_uint64 sqlite3Hwtime(void){ - unsigned long long retval; - unsigned long junk; - __asm__ __volatile__ ("\n\ - 1: mftbu %1\n\ - mftb %L0\n\ - mftbu %0\n\ - cmpw %0,%1\n\ - bne 1b" - : "=r" (retval), "=r" (junk)); - return retval; - } +#ifdef SQLITE_DEBUG +# define REGISTER_TRACE(R,M) if(db->flags&SQLITE_VdbeTrace)registerTrace(R,M) #else - - #error Need implementation of sqlite3Hwtime() for your platform. - - /* - ** To compile without implementing sqlite3Hwtime() for your platform, - ** you can remove the above #error and use the following - ** stub function. You will lose timing support for many - ** of the debugging and testing utilities, but it should at - ** least compile and run. - */ -SQLITE_PRIVATE sqlite_uint64 sqlite3Hwtime(void){ return ((sqlite_uint64)0); } - -#endif - -#endif /* !defined(SQLITE_HWTIME_H) */ - -/************** End of hwtime.h **********************************************/ -/************** Continuing where we left off in vdbe.c ***********************/ - +# define REGISTER_TRACE(R,M) #endif #ifndef NDEBUG /* ** This function is only called from within an assert() expression. It ** checks that the sqlite3.nTransaction variable is correctly set to -** the number of non-transaction savepoints currently in the +** the number of non-transaction savepoints currently in the ** linked list starting at sqlite3.pSavepoint. -** +** ** Usage: ** ** assert( checkSavepointCount(db) ); @@ -84177,50 +97289,208 @@ static Mem *out2Prerelease(Vdbe *p, VdbeOp *pOp){ } } +/* +** Compute a bloom filter hash using pOp->p4.i registers from aMem[] beginning +** with pOp->p3. Return the hash. +*/ +static u64 filterHash(const Mem *aMem, const Op *pOp){ + int i, mx; + u64 h = 0; + + assert( pOp->p4type==P4_INT32 ); + for(i=pOp->p3, mx=i+pOp->p4.i; iflags & (MEM_Int|MEM_IntReal) ){ + h += p->u.i; + }else if( p->flags & MEM_Real ){ + h += sqlite3VdbeIntValue(p); + }else if( p->flags & (MEM_Str|MEM_Blob) ){ + /* All strings have the same hash and all blobs have the same hash, + ** though, at least, those hashes are different from each other and + ** from NULL. */ + h += 4093 + (p->flags & (MEM_Str|MEM_Blob)); + } + } + return h; +} + + +/* +** For OP_Column, factor out the case where content is loaded from +** overflow pages, so that the code to implement this case is separate +** the common case where all content fits on the page. Factoring out +** the code reduces register pressure and helps the common case +** to run faster. +*/ +static SQLITE_NOINLINE int vdbeColumnFromOverflow( + VdbeCursor *pC, /* The BTree cursor from which we are reading */ + int iCol, /* The column to read */ + u32 t, /* The serial-type code for the column value */ + i64 iOffset, /* Offset to the start of the content value */ + u32 cacheStatus, /* Current Vdbe.cacheCtr value */ + u32 colCacheCtr, /* Current value of the column cache counter */ + Mem *pDest /* Store the value into this register. */ +){ + int rc; + sqlite3 *db = pDest->db; + int encoding = pDest->enc; + int len = sqlite3VdbeSerialTypeLen(t); + assert( pC->eCurType==CURTYPE_BTREE ); + if( len>db->aLimit[SQLITE_LIMIT_LENGTH] ) return SQLITE_TOOBIG; + if( len > 4000 && pC->pKeyInfo==0 ){ + /* Cache large column values that are on overflow pages using + ** an RCStr (reference counted string) so that if they are reloaded, + ** that do not have to be copied a second time. The overhead of + ** creating and managing the cache is such that this is only + ** profitable for larger TEXT and BLOB values. + ** + ** Only do this on table-btrees so that writes to index-btrees do not + ** need to clear the cache. This buys performance in the common case + ** in exchange for generality. + */ + VdbeTxtBlbCache *pCache; + char *pBuf; + if( pC->colCache==0 ){ + pC->pCache = sqlite3DbMallocZero(db, sizeof(VdbeTxtBlbCache) ); + if( pC->pCache==0 ) return SQLITE_NOMEM; + pC->colCache = 1; + } + pCache = pC->pCache; + if( pCache->pCValue==0 + || pCache->iCol!=iCol + || pCache->cacheStatus!=cacheStatus + || pCache->colCacheCtr!=colCacheCtr + || pCache->iOffset!=sqlite3BtreeOffset(pC->uc.pCursor) + ){ + if( pCache->pCValue ) sqlite3RCStrUnref(pCache->pCValue); + pBuf = pCache->pCValue = sqlite3RCStrNew( len+3 ); + if( pBuf==0 ) return SQLITE_NOMEM; + rc = sqlite3BtreePayload(pC->uc.pCursor, iOffset, len, pBuf); + if( rc ) return rc; + pBuf[len] = 0; + pBuf[len+1] = 0; + pBuf[len+2] = 0; + pCache->iCol = iCol; + pCache->cacheStatus = cacheStatus; + pCache->colCacheCtr = colCacheCtr; + pCache->iOffset = sqlite3BtreeOffset(pC->uc.pCursor); + }else{ + pBuf = pCache->pCValue; + } + assert( t>=12 ); + sqlite3RCStrRef(pBuf); + if( t&1 ){ + rc = sqlite3VdbeMemSetStr(pDest, pBuf, len, encoding, + sqlite3RCStrUnref); + pDest->flags |= MEM_Term; + }else{ + rc = sqlite3VdbeMemSetStr(pDest, pBuf, len, 0, + sqlite3RCStrUnref); + } + }else{ + rc = sqlite3VdbeMemFromBtree(pC->uc.pCursor, iOffset, len, pDest); + if( rc ) return rc; + sqlite3VdbeSerialGet((const u8*)pDest->z, t, pDest); + if( (t&1)!=0 && encoding==SQLITE_UTF8 ){ + pDest->z[len] = 0; + pDest->flags |= MEM_Term; + } + } + pDest->flags &= ~MEM_Ephem; + return rc; +} + +/* +** Send a "statement aborts" message to the error log. +*/ +static SQLITE_NOINLINE void sqlite3VdbeLogAbort( + Vdbe *p, /* The statement that is running at the time of failure */ + int rc, /* Error code */ + Op *pOp, /* Opcode that filed */ + Op *aOp /* All opcodes */ +){ + const char *zSql = p->zSql; /* Original SQL text */ + const char *zPrefix = ""; /* Prefix added to SQL text */ + int pc; /* Opcode address */ + char zXtra[100]; /* Buffer space to store zPrefix */ + + if( p->pFrame ){ + assert( aOp[0].opcode==OP_Init ); + if( aOp[0].p4.z!=0 ){ + assert( aOp[0].p4.z[0]=='-' + && aOp[0].p4.z[1]=='-' + && aOp[0].p4.z[2]==' ' ); + sqlite3_snprintf(sizeof(zXtra), zXtra,"/* %s */ ",aOp[0].p4.z+3); + zPrefix = zXtra; + }else{ + zPrefix = "/* unknown trigger */ "; + } + } + pc = (int)(pOp - aOp); + sqlite3_log(rc, "statement aborts at %d: %s; [%s%s]", + pc, p->zErrMsg, zPrefix, zSql); +} + +/* +** Return the symbolic name for the data type of a pMem +*/ +static const char *vdbeMemTypeName(Mem *pMem){ + static const char *azTypes[] = { + /* SQLITE_INTEGER */ "INT", + /* SQLITE_FLOAT */ "REAL", + /* SQLITE_TEXT */ "TEXT", + /* SQLITE_BLOB */ "BLOB", + /* SQLITE_NULL */ "NULL" + }; + return azTypes[sqlite3_value_type(pMem)-1]; +} /* ** Execute as much of a VDBE program as we can. -** This is the core of sqlite3_step(). +** This is the core of sqlite3_step(). */ SQLITE_PRIVATE int sqlite3VdbeExec( Vdbe *p /* The VDBE */ ){ Op *aOp = p->aOp; /* Copy of p->aOp */ Op *pOp = aOp; /* Current operation */ -#if defined(SQLITE_DEBUG) || defined(VDBE_PROFILE) - Op *pOrigOp; /* Value of pOp at the top of the loop */ -#endif #ifdef SQLITE_DEBUG + Op *pOrigOp; /* Value of pOp at the top of the loop */ int nExtraDelete = 0; /* Verifies FORDELETE and AUXDELETE flags */ + u8 iCompareIsInit = 0; /* iCompare is initialized */ #endif int rc = SQLITE_OK; /* Value to return */ sqlite3 *db = p->db; /* The database */ u8 resetSchemaOnFault = 0; /* Reset schema after an error if positive */ u8 encoding = ENC(db); /* The database encoding */ int iCompare = 0; /* Result of last comparison */ - unsigned nVmStep = 0; /* Number of virtual machine steps */ + u64 nVmStep = 0; /* Number of virtual machine steps */ #ifndef SQLITE_OMIT_PROGRESS_CALLBACK - unsigned nProgressLimit; /* Invoke xProgress() when nVmStep reaches this */ + u64 nProgressLimit; /* Invoke xProgress() when nVmStep reaches this */ #endif Mem *aMem = p->aMem; /* Copy of p->aMem */ Mem *pIn1 = 0; /* 1st input operand */ Mem *pIn2 = 0; /* 2nd input operand */ Mem *pIn3 = 0; /* 3rd input operand */ Mem *pOut = 0; /* Output operand */ -#ifdef VDBE_PROFILE - u64 start; /* CPU clock count at start of opcode */ + u32 colCacheCtr = 0; /* Column cache counter */ +#if defined(SQLITE_ENABLE_STMT_SCANSTATUS) || defined(VDBE_PROFILE) + u64 *pnCycle = 0; + int bStmtScanStatus = IS_STMT_SCANSTATUS(db)!=0; #endif /*** INSERT STACK UNION HERE ***/ - assert( p->magic==VDBE_MAGIC_RUN ); /* sqlite3_step() verifies this */ - sqlite3VdbeEnter(p); + assert( p->eVdbeState==VDBE_RUN_STATE ); /* sqlite3_step() verifies this */ + if( DbMaskNonZero(p->lockMask) ){ + sqlite3VdbeEnter(p); + } #ifndef SQLITE_OMIT_PROGRESS_CALLBACK if( db->xProgress ){ u32 iPrior = p->aCounter[SQLITE_STMTSTATUS_VM_STEP]; assert( 0 < db->nProgressOps ); nProgressLimit = db->nProgressOps - (iPrior % db->nProgressOps); }else{ - nProgressLimit = 0xffffffff; + nProgressLimit = LARGEST_UINT64; } #endif if( p->rc==SQLITE_NOMEM ){ @@ -84229,12 +97499,13 @@ SQLITE_PRIVATE int sqlite3VdbeExec( goto no_mem; } assert( p->rc==SQLITE_OK || (p->rc&0xff)==SQLITE_BUSY ); + testcase( p->rc!=SQLITE_OK ); + p->rc = SQLITE_OK; assert( p->bIsReader || p->readOnly!=0 ); p->iCurrentTime = 0; assert( p->explain==0 ); - p->pResultSet = 0; db->busyHandler.nBusy = 0; - if( db->u1.isInterrupted ) goto abort_due_to_interrupt; + if( AtomicLoad(&db->u1.isInterrupted) ) goto abort_due_to_interrupt; sqlite3VdbeIOTraceSql(p); #ifdef SQLITE_DEBUG sqlite3BeginBenignMalloc(); @@ -84269,12 +97540,18 @@ SQLITE_PRIVATE int sqlite3VdbeExec( assert( rc==SQLITE_OK ); assert( pOp>=aOp && pOp<&aOp[p->nOp]); -#ifdef VDBE_PROFILE - start = sqlite3NProfileCnt ? sqlite3NProfileCnt : sqlite3Hwtime(); -#endif nVmStep++; -#ifdef SQLITE_ENABLE_STMT_SCANSTATUS - if( p->anExec ) p->anExec[(int)(pOp-aOp)]++; + +#if defined(VDBE_PROFILE) + pOp->nExec++; + pnCycle = &pOp->nCycle; + if( sqlite3NProfileCnt==0 ) *pnCycle -= sqlite3Hwtime(); +#elif defined(SQLITE_ENABLE_STMT_SCANSTATUS) + if( bStmtScanStatus ){ + pOp->nExec++; + pnCycle = &pOp->nCycle; + *pnCycle -= sqlite3Hwtime(); + } #endif /* Only allow tracing if SQLITE_DEBUG is defined. @@ -84282,9 +97559,10 @@ SQLITE_PRIVATE int sqlite3VdbeExec( #ifdef SQLITE_DEBUG if( db->flags & SQLITE_VdbeTrace ){ sqlite3VdbePrintOp(stdout, (int)(pOp - aOp), pOp); + test_trace_breakpoint((int)(pOp - aOp),pOp,p); } #endif - + /* Check to see if we need to simulate an interrupt. This only happens ** if we have a special test build. @@ -84335,10 +97613,10 @@ SQLITE_PRIVATE int sqlite3VdbeExec( } } #endif -#if defined(SQLITE_DEBUG) || defined(VDBE_PROFILE) +#ifdef SQLITE_DEBUG pOrigOp = pOp; #endif - + switch( pOp->opcode ){ /***************************************************************************** @@ -84379,7 +97657,7 @@ SQLITE_PRIVATE int sqlite3VdbeExec( /* Opcode: Goto * P2 * * * ** ** An unconditional jump to address P2. -** The next instruction executed will be +** The next instruction executed will be ** the one at index P2 from the beginning of ** the program. ** @@ -84389,13 +97667,27 @@ SQLITE_PRIVATE int sqlite3VdbeExec( ** to the current line should be indented for EXPLAIN output. */ case OP_Goto: { /* jump */ + +#ifdef SQLITE_DEBUG + /* In debugging mode, when the p5 flags is set on an OP_Goto, that + ** means we should really jump back to the preceding OP_ReleaseReg + ** instruction. */ + if( pOp->p5 ){ + assert( pOp->p2 < (int)(pOp - aOp) ); + assert( pOp->p2 > 1 ); + pOp = &aOp[pOp->p2 - 2]; + assert( pOp[1].opcode==OP_ReleaseReg ); + goto check_for_interrupt; + } +#endif + jump_to_p2_and_check_for_interrupt: pOp = &aOp[pOp->p2 - 1]; /* Opcodes that are used as the bottom of a loop (OP_Next, OP_Prev, ** OP_VNext, or OP_SorterNext) all jump here upon ** completion. Check to see if sqlite3_interrupt() has been called - ** or if the progress callback needs to be invoked. + ** or if the progress callback needs to be invoked. ** ** This code uses unstructured "goto" statements and does not look clean. ** But that is not due to sloppy coding habits. The code is written this @@ -84403,7 +97695,7 @@ case OP_Goto: { /* jump */ ** checks on every opcode. This helps sqlite3_step() to run about 1.5% ** faster according to "valgrind --tool=cachegrind" */ check_for_interrupt: - if( db->u1.isInterrupted ) goto abort_due_to_interrupt; + if( AtomicLoad(&db->u1.isInterrupted) ) goto abort_due_to_interrupt; #ifndef SQLITE_OMIT_PROGRESS_CALLBACK /* Call the progress callback if it is configured and the required number ** of VDBE ops have been executed (either since this invocation of @@ -84415,13 +97707,13 @@ case OP_Goto: { /* jump */ assert( db->nProgressOps!=0 ); nProgressLimit += db->nProgressOps; if( db->xProgress(db->pProgressArg) ){ - nProgressLimit = 0xffffffff; + nProgressLimit = LARGEST_UINT64; rc = SQLITE_INTERRUPT; goto abort_due_to_error; } } #endif - + break; } @@ -84438,24 +97730,39 @@ case OP_Gosub: { /* jump */ pIn1->flags = MEM_Int; pIn1->u.i = (int)(pOp-aOp); REGISTER_TRACE(pOp->p1, pIn1); - - /* Most jump operations do a goto to this spot in order to update - ** the pOp pointer. */ -jump_to_p2: - pOp = &aOp[pOp->p2 - 1]; - break; + goto jump_to_p2_and_check_for_interrupt; } -/* Opcode: Return P1 * * * * +/* Opcode: Return P1 P2 P3 * * +** +** Jump to the address stored in register P1. If P1 is a return address +** register, then this accomplishes a return from a subroutine. +** +** If P3 is 1, then the jump is only taken if register P1 holds an integer +** values, otherwise execution falls through to the next opcode, and the +** OP_Return becomes a no-op. If P3 is 0, then register P1 must hold an +** integer or else an assert() is raised. P3 should be set to 1 when +** this opcode is used in combination with OP_BeginSubrtn, and set to 0 +** otherwise. +** +** The value in register P1 is unchanged by this opcode. ** -** Jump to the next instruction after the address in register P1. After -** the jump, register P1 becomes undefined. +** P2 is not used by the byte-code engine. However, if P2 is positive +** and also less than the current address, then the "EXPLAIN" output +** formatter in the CLI will indent all opcodes from the P2 opcode up +** to be not including the current Return. P2 should be the first opcode +** in the subroutine from which this opcode is returning. Thus the P2 +** value is a byte-code indentation hint. See tag-20220407a in +** wherecode.c and shell.c. */ case OP_Return: { /* in1 */ pIn1 = &aMem[pOp->p1]; - assert( pIn1->flags==MEM_Int ); - pOp = &aOp[pIn1->u.i]; - pIn1->flags = MEM_Undefined; + if( pIn1->flags & MEM_Int ){ + if( pOp->p3 ){ VdbeBranchTaken(1, 2); } + pOp = &aOp[pIn1->u.i]; + }else if( ALWAYS(pOp->p3) ){ + VdbeBranchTaken(0, 2); + } break; } @@ -84470,7 +97777,7 @@ case OP_Return: { /* in1 */ ** ** See also: EndCoroutine */ -case OP_InitCoroutine: { /* jump */ +case OP_InitCoroutine: { /* jump0 */ assert( pOp->p1>0 && pOp->p1<=(p->nMem+1 - p->nCursor) ); assert( pOp->p2>=0 && pOp->p2nOp ); assert( pOp->p3>=0 && pOp->p3nOp ); @@ -84478,7 +97785,14 @@ case OP_InitCoroutine: { /* jump */ assert( !VdbeMemDynamic(pOut) ); pOut->u.i = pOp->p3 - 1; pOut->flags = MEM_Int; - if( pOp->p2 ) goto jump_to_p2; + if( pOp->p2==0 ) break; + + /* Most jump operations do a goto to this spot in order to update + ** the pOp pointer. */ +jump_to_p2: + assert( pOp->p2>0 ); /* There are never any jumps to instruction 0 */ + assert( pOp->p2nOp ); /* Jumps must be in range */ + pOp = &aOp[pOp->p2 - 1]; break; } @@ -84486,7 +97800,9 @@ case OP_InitCoroutine: { /* jump */ ** ** The instruction at the address in register P1 is a Yield. ** Jump to the P2 parameter of that Yield. -** After the jump, register P1 becomes undefined. +** After the jump, the value register P1 is left with a value +** such that subsequent OP_Yields go back to the this same +** OP_EndCoroutine instruction. ** ** See also: InitCoroutine */ @@ -84498,8 +97814,8 @@ case OP_EndCoroutine: { /* in1 */ pCaller = &aOp[pIn1->u.i]; assert( pCaller->opcode==OP_Yield ); assert( pCaller->p2>=0 && pCaller->p2nOp ); + pIn1->u.i = (int)(pOp - p->aOp) - 1; pOp = &aOp[pCaller->p2 - 1]; - pIn1->flags = MEM_Undefined; break; } @@ -84516,7 +97832,7 @@ case OP_EndCoroutine: { /* in1 */ ** ** See also: InitCoroutine */ -case OP_Yield: { /* in1, jump */ +case OP_Yield: { /* in1, jump0 */ int pcDest; pIn1 = &aMem[pOp->p1]; assert( VdbeMemDynamic(pIn1)==0 ); @@ -84543,9 +97859,10 @@ case OP_HaltIfNull: { /* in3 */ #endif if( (pIn3->flags & MEM_Null)==0 ) break; /* Fall through into OP_Halt */ + /* no break */ deliberate_fall_through } -/* Opcode: Halt P1 P2 * P4 P5 +/* Opcode: Halt P1 P2 P3 P4 P5 ** ** Exit immediately. All open cursors, etc are closed ** automatically. @@ -84556,20 +97873,24 @@ case OP_HaltIfNull: { /* in3 */ ** whether or not to rollback the current transaction. Do not rollback ** if P2==OE_Fail. Do the rollback if P2==OE_Rollback. If P2==OE_Abort, ** then back out all changes that have occurred during this execution of the -** VDBE, but do not rollback the transaction. +** VDBE, but do not rollback the transaction. ** -** If P4 is not null then it is an error message string. +** If P3 is not zero and P4 is NULL, then P3 is a register that holds the +** text of an error message. ** -** P5 is a value between 0 and 4, inclusive, that modifies the P4 string. +** If P3 is zero and P4 is not null then the error message string is held +** in P4. ** -** 0: (no change) -** 1: NOT NULL contraint failed: P4 +** P5 is a value between 1 and 4, inclusive, then the P4 error message +** string is modified as follows: +** +** 1: NOT NULL constraint failed: P4 ** 2: UNIQUE constraint failed: P4 ** 3: CHECK constraint failed: P4 ** 4: FOREIGN KEY constraint failed: P4 ** -** If P5 is not zero and P4 is NULL, then everything after the ":" is -** omitted. +** If P3 is zero and P5 is not zero and P4 is NULL, then everything after +** the ":" is omitted. ** ** There is an implied "Halt 0 0 0" instruction inserted at the very end of ** every program. So a jump past the last instruction of the program @@ -84579,11 +97900,19 @@ case OP_Halt: { VdbeFrame *pFrame; int pcx; - pcx = (int)(pOp - aOp); #ifdef SQLITE_DEBUG if( pOp->p2==OE_Abort ){ sqlite3VdbeAssertAbortable(p); } #endif - if( pOp->p1==SQLITE_OK && p->pFrame ){ + assert( pOp->p4type==P4_NOTUSED + || pOp->p4type==P4_STATIC + || pOp->p4type==P4_DYNAMIC ); + + /* A deliberately coded "OP_Halt SQLITE_INTERNAL * * * *" opcode indicates + ** something is wrong with the code generator. Raise an assertion in order + ** to bring this to the attention of fuzzers and other testing tools. */ + assert( pOp->p1!=SQLITE_INTERNAL ); + + if( p->pFrame && pOp->p1==SQLITE_OK ){ /* Halt the sub-program. Return control to the parent frame. */ pFrame = p->pFrame; p->pFrame = pFrame->pParent; @@ -84591,7 +97920,7 @@ case OP_Halt: { sqlite3VdbeSetChanges(db, p->nChange); pcx = sqlite3VdbeFrameRestore(pFrame); if( pOp->p2==OE_Ignore ){ - /* Instruction pcx is the OP_Program that invoked the sub-program + /* Instruction pcx is the OP_Program that invoked the sub-program ** currently being halted. If the p2 instruction of this OP_Halt ** instruction is set to OE_Ignore, then the sub-program is throwing ** an IGNORE exception. In this case jump to the address specified @@ -84605,10 +97934,14 @@ case OP_Halt: { } p->rc = pOp->p1; p->errorAction = (u8)pOp->p2; - p->pc = pcx; assert( pOp->p5<=4 ); if( p->rc ){ - if( pOp->p5 ){ + if( pOp->p3>0 && pOp->p4type==P4_NOTUSED ){ + const char *zErr; + assert( pOp->p3<=(p->nMem + 1 - p->nCursor) ); + zErr = sqlite3ValueText(&aMem[pOp->p3], SQLITE_UTF8); + sqlite3VdbeError(p, "%s", zErr); + }else if( pOp->p5 ){ static const char * const azType[] = { "NOT NULL", "UNIQUE", "CHECK", "FOREIGN KEY" }; testcase( pOp->p5==1 ); @@ -84622,7 +97955,7 @@ case OP_Halt: { }else{ sqlite3VdbeError(p, "%s", pOp->p4.z); } - sqlite3_log(pOp->p1, "abort at %d in [%s]: %s", pcx, p->zSql, p->zErrMsg); + sqlite3VdbeLogAbort(p, pOp->p1, pOp, aOp); } rc = sqlite3VdbeHalt(p); assert( rc==SQLITE_BUSY || rc==SQLITE_OK || rc==SQLITE_ERROR ); @@ -84679,7 +98012,7 @@ case OP_Real: { /* same as TK_FLOAT, out2 */ /* Opcode: String8 * P2 * P4 * ** Synopsis: r[P2]='P4' ** -** P4 points to a nul terminated UTF-8 string. This opcode is transformed +** P4 points to a nul terminated UTF-8 string. This opcode is transformed ** into a String opcode before it is executed for the first time. During ** this transformation, the length of string P4 is computed and stored ** as the P1 parameter. @@ -84687,7 +98020,6 @@ case OP_Real: { /* same as TK_FLOAT, out2 */ case OP_String8: { /* same as TK_STRING, out2 */ assert( pOp->p4.z!=0 ); pOut = out2Prerelease(p, pOp); - pOp->opcode = OP_String; pOp->p1 = sqlite3Strlen30(pOp->p4.z); #ifndef SQLITE_OMIT_UTF16 @@ -84711,10 +98043,12 @@ case OP_String8: { /* same as TK_STRING, out2 */ if( pOp->p1>db->aLimit[SQLITE_LIMIT_LENGTH] ){ goto too_big; } + pOp->opcode = OP_String; assert( rc==SQLITE_OK ); /* Fall through to the next case, OP_String */ + /* no break */ deliberate_fall_through } - + /* Opcode: String P1 P2 P3 P4 P5 ** Synopsis: r[P2]='P4' (len=P1) ** @@ -84746,6 +98080,28 @@ case OP_String: { /* out2 */ break; } +/* Opcode: BeginSubrtn * P2 * * * +** Synopsis: r[P2]=NULL +** +** Mark the beginning of a subroutine that can be entered in-line +** or that can be called using OP_Gosub. The subroutine should +** be terminated by an OP_Return instruction that has a P1 operand that +** is the same as the P2 operand to this opcode and that has P3 set to 1. +** If the subroutine is entered in-line, then the OP_Return will simply +** fall through. But if the subroutine is entered using OP_Gosub, then +** the OP_Return will jump back to the first instruction after the OP_Gosub. +** +** This routine works by loading a NULL into the P2 register. When the +** return address register contains a NULL, the OP_Return instruction is +** a no-op that simply falls through to the next instruction (assuming that +** the OP_Return opcode has a P3 value of 1). Thus if the subroutine is +** entered in-line, then the OP_Return will cause in-line execution to +** continue. But if the subroutine is entered via OP_Gosub, then the +** OP_Return will cause a return to the address following the OP_Gosub. +** +** This opcode is identical to OP_Null. It has a different name +** only to make the byte code easier to read and verify. +*/ /* Opcode: Null P1 P2 P3 * * ** Synopsis: r[P2..P3]=NULL ** @@ -84758,6 +98114,7 @@ case OP_String: { /* out2 */ ** NULL values will not compare equal even if SQLITE_NULLEQ is set on ** OP_Ne or OP_Eq. */ +case OP_BeginSubrtn: case OP_Null: { /* out2 */ int cnt; u16 nullFlag; @@ -84799,30 +98156,32 @@ case OP_SoftNull: { ** Synopsis: r[P2]=P4 (len=P1) ** ** P4 points to a blob of data P1 bytes long. Store this -** blob in register P2. +** blob in register P2. If P4 is a NULL pointer, then construct +** a zero-filled blob that is P1 bytes long in P2. */ case OP_Blob: { /* out2 */ assert( pOp->p1 <= SQLITE_MAX_LENGTH ); pOut = out2Prerelease(p, pOp); - sqlite3VdbeMemSetStr(pOut, pOp->p4.z, pOp->p1, 0, 0); + if( pOp->p4.z==0 ){ + sqlite3VdbeMemSetZeroBlob(pOut, pOp->p1); + if( sqlite3VdbeMemExpandBlob(pOut) ) goto no_mem; + }else{ + sqlite3VdbeMemSetStr(pOut, pOp->p4.z, pOp->p1, 0, 0); + } pOut->enc = encoding; UPDATE_MAX_BLOBSIZE(pOut); break; } -/* Opcode: Variable P1 P2 * P4 * -** Synopsis: r[P2]=parameter(P1,P4) +/* Opcode: Variable P1 P2 * * * +** Synopsis: r[P2]=parameter(P1) ** ** Transfer the values of bound parameter P1 into register P2 -** -** If the parameter is named, then its name appears in P4. -** The P4 value is used by sqlite3_bind_parameter_name(). */ case OP_Variable: { /* out2 */ Mem *pVar; /* Value being transferred */ assert( pOp->p1>0 && pOp->p1<=p->nVar ); - assert( pOp->p4.z==0 || pOp->p4.z==sqlite3VListNumToName(p->pVList,pOp->p1) ); pVar = &p->aVar[pOp->p1 - 1]; if( sqlite3VdbeMemTooBig(pVar) ){ goto too_big; @@ -84865,8 +98224,14 @@ case OP_Move: { memAboutToChange(p, pOut); sqlite3VdbeMemMove(pOut, pIn1); #ifdef SQLITE_DEBUG - if( pOut->pScopyFrom>=&aMem[p1] && pOut->pScopyFrompScopyFrom += pOp->p2 - p1; + pIn1->pScopyFrom = 0; + { int i; + for(i=1; inMem; i++){ + if( aMem[i].pScopyFrom==pIn1 ){ + assert( aMem[i].bScopy ); + aMem[i].pScopyFrom = pOut; + } + } } #endif Deephemeralize(pOut); @@ -84877,11 +98242,16 @@ case OP_Move: { break; } -/* Opcode: Copy P1 P2 P3 * * +/* Opcode: Copy P1 P2 P3 * P5 ** Synopsis: r[P2@P3+1]=r[P1@P3+1] ** ** Make a copy of registers P1..P1+P3 into registers P2..P2+P3. ** +** If the 0x0002 bit of P5 is set then also clear the MEM_Subtype flag in the +** destination. The 0x0001 bit of P5 indicates that this Copy opcode cannot +** be merged. The 0x0001 bit is used by the query planner and does not +** come into play during query execution. +** ** This instruction makes a deep copy of the value. A duplicate ** is made of any string or blob constant. See also OP_SCopy. */ @@ -84896,6 +98266,9 @@ case OP_Copy: { memAboutToChange(p, pOut); sqlite3VdbeMemShallowCopy(pOut, pIn1, MEM_Ephem); Deephemeralize(pOut); + if( (pOut->flags & MEM_Subtype)!=0 && (pOp->p5 & 0x0002)!=0 ){ + pOut->flags &= ~MEM_Subtype; + } #ifdef SQLITE_DEBUG pOut->pScopyFrom = 0; #endif @@ -84928,6 +98301,7 @@ case OP_SCopy: { /* out2 */ #ifdef SQLITE_DEBUG pOut->pScopyFrom = pIn1; pOut->mScopyFlags = pIn1->flags; + pIn1->bScopy = 1; #endif break; } @@ -84948,6 +98322,24 @@ case OP_IntCopy: { /* out2 */ break; } +/* Opcode: FkCheck * * * * * +** +** Halt with an SQLITE_CONSTRAINT error if there are any unresolved +** foreign key constraint violations. If there are no foreign key +** constraint violations, this is a no-op. +** +** FK constraint violations are also checked when the prepared statement +** exits. This opcode is used to raise foreign key constraint errors prior +** to returning results such as a row change count or the result of a +** RETURNING clause. +*/ +case OP_FkCheck: { + if( (rc = sqlite3VdbeCheckFkImmediate(p))!=SQLITE_OK ){ + goto abort_due_to_error; + } + break; +} + /* Opcode: ResultRow P1 P2 * * * ** Synopsis: output=r[P1@P2] ** @@ -84958,64 +98350,32 @@ case OP_IntCopy: { /* out2 */ ** the result row. */ case OP_ResultRow: { - Mem *pMem; - int i; assert( p->nResColumn==pOp->p2 ); - assert( pOp->p1>0 ); + assert( pOp->p1>0 || CORRUPT_DB ); assert( pOp->p1+pOp->p2<=(p->nMem+1 - p->nCursor)+1 ); - /* If this statement has violated immediate foreign key constraints, do - ** not return the number of rows modified. And do not RELEASE the statement - ** transaction. It needs to be rolled back. */ - if( SQLITE_OK!=(rc = sqlite3VdbeCheckFk(p, 0)) ){ - assert( db->flags&SQLITE_CountRows ); - assert( p->usesStmtJournal ); - goto abort_due_to_error; - } - - /* If the SQLITE_CountRows flag is set in sqlite3.flags mask, then - ** DML statements invoke this opcode to return the number of rows - ** modified to the user. This is the only way that a VM that - ** opens a statement transaction may invoke this opcode. - ** - ** In case this is such a statement, close any statement transaction - ** opened by this VM before returning control to the user. This is to - ** ensure that statement-transactions are always nested, not overlapping. - ** If the open statement-transaction is not closed here, then the user - ** may step another VM that opens its own statement transaction. This - ** may lead to overlapping statement transactions. - ** - ** The statement transaction is never a top-level transaction. Hence - ** the RELEASE call below can never fail. - */ - assert( p->iStatement==0 || db->flags&SQLITE_CountRows ); - rc = sqlite3VdbeCloseStatement(p, SAVEPOINT_RELEASE); - assert( rc==SQLITE_OK ); - - /* Invalidate all ephemeral cursor row caches */ p->cacheCtr = (p->cacheCtr + 2)|1; - - /* Make sure the results of the current row are \000 terminated - ** and have an assigned type. The results are de-ephemeralized as - ** a side effect. - */ - pMem = p->pResultSet = &aMem[pOp->p1]; - for(i=0; ip2; i++){ - assert( memIsValid(&pMem[i]) ); - Deephemeralize(&pMem[i]); - assert( (pMem[i].flags & MEM_Ephem)==0 - || (pMem[i].flags & (MEM_Str|MEM_Blob))==0 ); - sqlite3VdbeMemNulTerminate(&pMem[i]); - REGISTER_TRACE(pOp->p1+i, &pMem[i]); + p->pResultRow = &aMem[pOp->p1]; +#ifdef SQLITE_DEBUG + { + Mem *pMem = p->pResultRow; + int i; + for(i=0; ip2; i++){ + assert( memIsValid(&pMem[i]) ); + REGISTER_TRACE(pOp->p1+i, &pMem[i]); + /* The registers in the result will not be used again when the + ** prepared statement restarts. This is because sqlite3_column() + ** APIs might have caused type conversions of made other changes to + ** the register values. Therefore, we can go ahead and break any + ** OP_SCopy dependencies. */ + pMem[i].pScopyFrom = 0; + } } +#endif if( db->mallocFailed ) goto no_mem; - if( db->mTrace & SQLITE_TRACE_ROW ){ - db->xTrace(SQLITE_TRACE_ROW, db->pTraceArg, p, 0); + db->trace.xV2(SQLITE_TRACE_ROW, db->pTraceArg, p, 0); } - - /* Return SQLITE_ROW - */ p->pc = (int)(pOp - aOp) + 1; rc = SQLITE_ROW; goto vdbe_return; @@ -85035,31 +98395,58 @@ case OP_ResultRow: { ** to avoid a memcpy(). */ case OP_Concat: { /* same as TK_CONCAT, in1, in2, out3 */ - i64 nByte; + i64 nByte; /* Total size of the output string or blob */ + u16 flags1; /* Initial flags for P1 */ + u16 flags2; /* Initial flags for P2 */ pIn1 = &aMem[pOp->p1]; pIn2 = &aMem[pOp->p2]; pOut = &aMem[pOp->p3]; + testcase( pOut==pIn2 ); assert( pIn1!=pOut ); - if( (pIn1->flags | pIn2->flags) & MEM_Null ){ + flags1 = pIn1->flags; + testcase( flags1 & MEM_Null ); + testcase( pIn2->flags & MEM_Null ); + if( (flags1 | pIn2->flags) & MEM_Null ){ sqlite3VdbeMemSetNull(pOut); break; } - if( ExpandBlob(pIn1) || ExpandBlob(pIn2) ) goto no_mem; - Stringify(pIn1, encoding); - Stringify(pIn2, encoding); - nByte = pIn1->n + pIn2->n; + if( (flags1 & (MEM_Str|MEM_Blob))==0 ){ + if( sqlite3VdbeMemStringify(pIn1,encoding,0) ) goto no_mem; + flags1 = pIn1->flags & ~MEM_Str; + }else if( (flags1 & MEM_Zero)!=0 ){ + if( sqlite3VdbeMemExpandBlob(pIn1) ) goto no_mem; + flags1 = pIn1->flags & ~MEM_Str; + } + flags2 = pIn2->flags; + if( (flags2 & (MEM_Str|MEM_Blob))==0 ){ + if( sqlite3VdbeMemStringify(pIn2,encoding,0) ) goto no_mem; + flags2 = pIn2->flags & ~MEM_Str; + }else if( (flags2 & MEM_Zero)!=0 ){ + if( sqlite3VdbeMemExpandBlob(pIn2) ) goto no_mem; + flags2 = pIn2->flags & ~MEM_Str; + } + nByte = pIn1->n; + nByte += pIn2->n; if( nByte>db->aLimit[SQLITE_LIMIT_LENGTH] ){ goto too_big; } +#if SQLITE_MAX_LENGTH>2147483645 + if( nByte>2147483645 ){ goto too_big; } +#endif if( sqlite3VdbeMemGrow(pOut, (int)nByte+2, pOut==pIn2) ){ goto no_mem; } MemSetTypeFlag(pOut, MEM_Str); if( pOut!=pIn2 ){ memcpy(pOut->z, pIn2->z, pIn2->n); + assert( (pIn2->flags & MEM_Dyn) == (flags2 & MEM_Dyn) ); + pIn2->flags = flags2; } memcpy(&pOut->z[pIn2->n], pIn1->z, pIn1->n); + assert( (pIn1->flags & MEM_Dyn) == (flags1 & MEM_Dyn) ); + pIn1->flags = flags1; + if( encoding>SQLITE_UTF8 ) nByte &= ~1; pOut->z[nByte]=0; pOut->z[nByte+1] = 0; pOut->flags |= MEM_Term; @@ -85095,15 +98482,15 @@ case OP_Concat: { /* same as TK_CONCAT, in1, in2, out3 */ ** Synopsis: r[P3]=r[P2]/r[P1] ** ** Divide the value in register P1 by the value in register P2 -** and store the result in register P3 (P3=P2/P1). If the value in -** register P1 is zero, then the result is NULL. If either input is +** and store the result in register P3 (P3=P2/P1). If the value in +** register P1 is zero, then the result is NULL. If either input is ** NULL, the result is NULL. */ /* Opcode: Remainder P1 P2 P3 * * ** Synopsis: r[P3]=r[P2]%r[P1] ** -** Compute the remainder after integer register P2 is divided by -** register P1 and store the result in register P3. +** Compute the remainder after integer register P2 is divided by +** register P1 and store the result in register P3. ** If the value in register P1 is zero the result is NULL. ** If either operand is NULL, the result is NULL. */ @@ -85112,8 +98499,6 @@ case OP_Subtract: /* same as TK_MINUS, in1, in2, out3 */ case OP_Multiply: /* same as TK_STAR, in1, in2, out3 */ case OP_Divide: /* same as TK_SLASH, in1, in2, out3 */ case OP_Remainder: { /* same as TK_REM, in1, in2, out3 */ - char bIntint; /* Started out as two integer operands */ - u16 flags; /* Combined MEM_* flags from both inputs */ u16 type1; /* Numeric type of left operand */ u16 type2; /* Numeric type of right operand */ i64 iA; /* Integer value of left operand */ @@ -85122,15 +98507,14 @@ case OP_Remainder: { /* same as TK_REM, in1, in2, out3 */ double rB; /* Real value of right operand */ pIn1 = &aMem[pOp->p1]; - type1 = numericType(pIn1); + type1 = pIn1->flags; pIn2 = &aMem[pOp->p2]; - type2 = numericType(pIn2); + type2 = pIn2->flags; pOut = &aMem[pOp->p3]; - flags = pIn1->flags | pIn2->flags; if( (type1 & type2 & MEM_Int)!=0 ){ +int_math: iA = pIn1->u.i; iB = pIn2->u.i; - bIntint = 1; switch( pOp->opcode ){ case OP_Add: if( sqlite3AddInt64(&iB,iA) ) goto fp_math; break; case OP_Subtract: if( sqlite3SubInt64(&iB,iA) ) goto fp_math; break; @@ -85150,10 +98534,12 @@ case OP_Remainder: { /* same as TK_REM, in1, in2, out3 */ } pOut->u.i = iB; MemSetTypeFlag(pOut, MEM_Int); - }else if( (flags & MEM_Null)!=0 ){ + }else if( ((type1 | type2) & MEM_Null)!=0 ){ goto arithmetic_result_is_null; }else{ - bIntint = 0; + type1 = numericType(pIn1); + type2 = numericType(pIn2); + if( (type1 & type2 & MEM_Int)!=0 ) goto int_math; fp_math: rA = sqlite3VdbeRealValue(pIn1); rB = sqlite3VdbeRealValue(pIn2); @@ -85185,9 +98571,6 @@ case OP_Remainder: { /* same as TK_REM, in1, in2, out3 */ } pOut->u.r = rB; MemSetTypeFlag(pOut, MEM_Real); - if( ((type1|type2)&MEM_Real)==0 && !bIntint ){ - sqlite3VdbeIntegerAffinity(pOut); - } #endif } break; @@ -85304,7 +98687,7 @@ case OP_ShiftRight: { /* same as TK_RSHIFT, in1, in2, out3 */ /* Opcode: AddImm P1 P2 * * * ** Synopsis: r[P1]=r[P1]+P2 -** +** ** Add the constant P2 to the value in register P1. ** The result is always an integer. ** @@ -85314,18 +98697,18 @@ case OP_AddImm: { /* in1 */ pIn1 = &aMem[pOp->p1]; memAboutToChange(p, pIn1); sqlite3VdbeMemIntegerify(pIn1); - pIn1->u.i += pOp->p2; + *(u64*)&pIn1->u.i += (u64)pOp->p2; break; } /* Opcode: MustBeInt P1 P2 * * * -** +** ** Force the value in register P1 to be an integer. If the value ** in P1 is not an integer and cannot be converted into an integer ** without data loss, then jump immediately to P2, or if P2==0 ** raise an SQLITE_MISMATCH exception. */ -case OP_MustBeInt: { /* jump, in1 */ +case OP_MustBeInt: { /* jump0, in1 */ pIn1 = &aMem[pOp->p1]; if( (pIn1->flags & MEM_Int)==0 ){ applyAffinity(pIn1, SQLITE_AFF_NUMERIC, encoding); @@ -85356,19 +98739,22 @@ case OP_MustBeInt: { /* jump, in1 */ */ case OP_RealAffinity: { /* in1 */ pIn1 = &aMem[pOp->p1]; - if( pIn1->flags & MEM_Int ){ + if( pIn1->flags & (MEM_Int|MEM_IntReal) ){ + testcase( pIn1->flags & MEM_Int ); + testcase( pIn1->flags & MEM_IntReal ); sqlite3VdbeMemRealify(pIn1); + REGISTER_TRACE(pOp->p1, pIn1); } break; } #endif -#ifndef SQLITE_OMIT_CAST +#if !defined(SQLITE_OMIT_CAST) || !defined(SQLITE_OMIT_ANALYZE) /* Opcode: Cast P1 P2 * * * ** Synopsis: affinity(r[P1]) ** ** Force the value in register P1 to be the type defined by P2. -** +** **
            **
          • P2=='A' → BLOB **
          • P2=='B' → TEXT @@ -85389,9 +98775,11 @@ case OP_Cast: { /* in1 */ pIn1 = &aMem[pOp->p1]; memAboutToChange(p, pIn1); rc = ExpandBlob(pIn1); - sqlite3VdbeMemCast(pIn1, pOp->p2, encoding); - UPDATE_MAX_BLOBSIZE(pIn1); if( rc ) goto abort_due_to_error; + rc = sqlite3VdbeMemCast(pIn1, pOp->p2, encoding); + if( rc ) goto abort_due_to_error; + UPDATE_MAX_BLOBSIZE(pIn1); + REGISTER_TRACE(pOp->p1, pIn1); break; } #endif /* SQLITE_OMIT_CAST */ @@ -85400,18 +98788,17 @@ case OP_Cast: { /* in1 */ ** Synopsis: IF r[P3]==r[P1] ** ** Compare the values in register P1 and P3. If reg(P3)==reg(P1) then -** jump to address P2. Or if the SQLITE_STOREP2 flag is set in P5, then -** store the result of comparison in register P2. +** jump to address P2. ** ** The SQLITE_AFF_MASK portion of P5 must be an affinity character - -** SQLITE_AFF_TEXT, SQLITE_AFF_INTEGER, and so forth. An attempt is made +** SQLITE_AFF_TEXT, SQLITE_AFF_INTEGER, and so forth. An attempt is made ** to coerce both inputs according to this affinity before the ** comparison is made. If the SQLITE_AFF_MASK is 0x00, then numeric ** affinity is used. Note that the affinity conversions are stored ** back into the input registers P1 and P3. So this opcode can cause ** persistent changes to registers P1 and P3. ** -** Once any conversions have taken place, and neither value is NULL, +** Once any conversions have taken place, and neither value is NULL, ** the values are compared. If both values are blobs then memcmp() is ** used to determine the results of the comparison. If both values ** are text, then the appropriate collating function specified in @@ -85427,9 +98814,8 @@ case OP_Cast: { /* in1 */ ** If neither operand is NULL the result is the same as it would be if ** the SQLITE_NULLEQ flag were omitted from P5. ** -** If both SQLITE_STOREP2 and SQLITE_KEEPNULL flags are set then the -** content of r[P2] is only changed if the new value is NULL or 0 (false). -** In other words, a prior r[P2] value will not be overwritten by 1 (true). +** This opcode saves the result of comparison for use by the new +** OP_Jump opcode. */ /* Opcode: Ne P1 P2 P3 P4 P5 ** Synopsis: IF r[P3]!=r[P1] @@ -85437,31 +98823,26 @@ case OP_Cast: { /* in1 */ ** This works just like the Eq opcode except that the jump is taken if ** the operands in registers P1 and P3 are not equal. See the Eq opcode for ** additional information. -** -** If both SQLITE_STOREP2 and SQLITE_KEEPNULL flags are set then the -** content of r[P2] is only changed if the new value is NULL or 1 (true). -** In other words, a prior r[P2] value will not be overwritten by 0 (false). */ /* Opcode: Lt P1 P2 P3 P4 P5 ** Synopsis: IF r[P3]p3]; flags1 = pIn1->flags; flags3 = pIn3->flags; + if( (flags1 & flags3 & MEM_Int)!=0 ){ + /* Common case of comparison of two integers */ + if( pIn3->u.i > pIn1->u.i ){ + if( sqlite3aGTb[pOp->opcode] ){ + VdbeBranchTaken(1, (pOp->p5 & SQLITE_NULLEQ)?2:3); + goto jump_to_p2; + } + iCompare = +1; + VVA_ONLY( iCompareIsInit = 1; ) + }else if( pIn3->u.i < pIn1->u.i ){ + if( sqlite3aLTb[pOp->opcode] ){ + VdbeBranchTaken(1, (pOp->p5 & SQLITE_NULLEQ)?2:3); + goto jump_to_p2; + } + iCompare = -1; + VVA_ONLY( iCompareIsInit = 1; ) + }else{ + if( sqlite3aEQb[pOp->opcode] ){ + VdbeBranchTaken(1, (pOp->p5 & SQLITE_NULLEQ)?2:3); + goto jump_to_p2; + } + iCompare = 0; + VVA_ONLY( iCompareIsInit = 1; ) + } + VdbeBranchTaken(0, (pOp->p5 & SQLITE_NULLEQ)?2:3); + break; + } if( (flags1 | flags3)&MEM_Null ){ /* One or both operands are NULL */ if( pOp->p5 & SQLITE_NULLEQ ){ @@ -85529,59 +98940,47 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ ** then the result is always NULL. ** The jump is taken if the SQLITE_JUMPIFNULL bit is set. */ - if( pOp->p5 & SQLITE_STOREP2 ){ - pOut = &aMem[pOp->p2]; - iCompare = 1; /* Operands are not equal */ - memAboutToChange(p, pOut); - MemSetTypeFlag(pOut, MEM_Null); - REGISTER_TRACE(pOp->p2, pOut); - }else{ - VdbeBranchTaken(2,3); - if( pOp->p5 & SQLITE_JUMPIFNULL ){ - goto jump_to_p2; - } + VdbeBranchTaken(2,3); + if( pOp->p5 & SQLITE_JUMPIFNULL ){ + goto jump_to_p2; } + iCompare = 1; /* Operands are not equal */ + VVA_ONLY( iCompareIsInit = 1; ) break; } }else{ - /* Neither operand is NULL. Do a comparison. */ + /* Neither operand is NULL and we couldn't do the special high-speed + ** integer comparison case. So do a general-case comparison. */ affinity = pOp->p5 & SQLITE_AFF_MASK; if( affinity>=SQLITE_AFF_NUMERIC ){ if( (flags1 | flags3)&MEM_Str ){ - if( (flags1 & (MEM_Int|MEM_Real|MEM_Str))==MEM_Str ){ + if( (flags1 & (MEM_Int|MEM_IntReal|MEM_Real|MEM_Str))==MEM_Str ){ applyNumericAffinity(pIn1,0); - assert( flags3==pIn3->flags ); - /* testcase( flags3!=pIn3->flags ); - ** this used to be possible with pIn1==pIn3, but not since - ** the column cache was removed. The following assignment - ** is essentially a no-op. But, it provides defense-in-depth - ** in case our analysis is incorrect, so it is left in. */ + assert( flags3==pIn3->flags || CORRUPT_DB ); flags3 = pIn3->flags; } - if( (flags3 & (MEM_Int|MEM_Real|MEM_Str))==MEM_Str ){ + if( (flags3 & (MEM_Int|MEM_IntReal|MEM_Real|MEM_Str))==MEM_Str ){ applyNumericAffinity(pIn3,0); } } - /* Handle the common case of integer comparison here, as an - ** optimization, to avoid a call to sqlite3MemCompare() */ - if( (pIn1->flags & pIn3->flags & MEM_Int)!=0 ){ - if( pIn3->u.i > pIn1->u.i ){ res = +1; goto compare_op; } - if( pIn3->u.i < pIn1->u.i ){ res = -1; goto compare_op; } - res = 0; - goto compare_op; - } - }else if( affinity==SQLITE_AFF_TEXT ){ - if( (flags1 & MEM_Str)==0 && (flags1 & (MEM_Int|MEM_Real))!=0 ){ + }else if( affinity==SQLITE_AFF_TEXT && ((flags1 | flags3) & MEM_Str)!=0 ){ + if( (flags1 & MEM_Str)!=0 ){ + pIn1->flags &= ~(MEM_Int|MEM_Real|MEM_IntReal); + }else if( (flags1&(MEM_Int|MEM_Real|MEM_IntReal))!=0 ){ testcase( pIn1->flags & MEM_Int ); testcase( pIn1->flags & MEM_Real ); + testcase( pIn1->flags & MEM_IntReal ); sqlite3VdbeMemStringify(pIn1, encoding, 1); testcase( (flags1&MEM_Dyn) != (pIn1->flags&MEM_Dyn) ); flags1 = (pIn1->flags & ~MEM_TypeMask) | (flags1 & MEM_TypeMask); - assert( pIn1!=pIn3 ); + if( NEVER(pIn1==pIn3) ) flags3 = flags1 | MEM_Str; } - if( (flags3 & MEM_Str)==0 && (flags3 & (MEM_Int|MEM_Real))!=0 ){ + if( (flags3 & MEM_Str)!=0 ){ + pIn3->flags &= ~(MEM_Int|MEM_Real|MEM_IntReal); + }else if( (flags3&(MEM_Int|MEM_Real|MEM_IntReal))!=0 ){ testcase( pIn3->flags & MEM_Int ); testcase( pIn3->flags & MEM_Real ); + testcase( pIn3->flags & MEM_IntReal ); sqlite3VdbeMemStringify(pIn3, encoding, 1); testcase( (flags3&MEM_Dyn) != (pIn3->flags&MEM_Dyn) ); flags3 = (pIn3->flags & ~MEM_TypeMask) | (flags3 & MEM_TypeMask); @@ -85590,7 +98989,7 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ assert( pOp->p4type==P4_COLLSEQ || pOp->p4.pColl==0 ); res = sqlite3MemCompare(pIn3, pIn1, pOp->p4.pColl); } -compare_op: + /* At this point, res is negative, zero, or positive if reg[P1] is ** less than, equal to, or greater than reg[P3], respectively. Compute ** the answer to this operator in res2, depending on what the comparison @@ -85599,69 +98998,56 @@ case OP_Ge: { /* same as TK_GE, jump, in1, in3 */ ** order: NE, EQ, GT, LE, LT, GE */ assert( OP_Eq==OP_Ne+1 ); assert( OP_Gt==OP_Ne+2 ); assert( OP_Le==OP_Ne+3 ); assert( OP_Lt==OP_Ne+4 ); assert( OP_Ge==OP_Ne+5 ); - if( res<0 ){ /* ne, eq, gt, le, lt, ge */ - static const unsigned char aLTb[] = { 1, 0, 0, 1, 1, 0 }; - res2 = aLTb[pOp->opcode - OP_Ne]; + if( res<0 ){ + res2 = sqlite3aLTb[pOp->opcode]; }else if( res==0 ){ - static const unsigned char aEQb[] = { 0, 1, 0, 1, 0, 1 }; - res2 = aEQb[pOp->opcode - OP_Ne]; + res2 = sqlite3aEQb[pOp->opcode]; }else{ - static const unsigned char aGTb[] = { 1, 0, 1, 0, 0, 1 }; - res2 = aGTb[pOp->opcode - OP_Ne]; + res2 = sqlite3aGTb[pOp->opcode]; } + iCompare = res; + VVA_ONLY( iCompareIsInit = 1; ) /* Undo any changes made by applyAffinity() to the input registers. */ - assert( (pIn1->flags & MEM_Dyn) == (flags1 & MEM_Dyn) ); - pIn1->flags = flags1; assert( (pIn3->flags & MEM_Dyn) == (flags3 & MEM_Dyn) ); pIn3->flags = flags3; + assert( (pIn1->flags & MEM_Dyn) == (flags1 & MEM_Dyn) ); + pIn1->flags = flags1; - if( pOp->p5 & SQLITE_STOREP2 ){ - pOut = &aMem[pOp->p2]; - iCompare = res; - if( (pOp->p5 & SQLITE_KEEPNULL)!=0 ){ - /* The KEEPNULL flag prevents OP_Eq from overwriting a NULL with 1 - ** and prevents OP_Ne from overwriting NULL with 0. This flag - ** is only used in contexts where either: - ** (1) op==OP_Eq && (r[P2]==NULL || r[P2]==0) - ** (2) op==OP_Ne && (r[P2]==NULL || r[P2]==1) - ** Therefore it is not necessary to check the content of r[P2] for - ** NULL. */ - assert( pOp->opcode==OP_Ne || pOp->opcode==OP_Eq ); - assert( res2==0 || res2==1 ); - testcase( res2==0 && pOp->opcode==OP_Eq ); - testcase( res2==1 && pOp->opcode==OP_Eq ); - testcase( res2==0 && pOp->opcode==OP_Ne ); - testcase( res2==1 && pOp->opcode==OP_Ne ); - if( (pOp->opcode==OP_Eq)==res2 ) break; - } - memAboutToChange(p, pOut); - MemSetTypeFlag(pOut, MEM_Int); - pOut->u.i = res2; - REGISTER_TRACE(pOp->p2, pOut); - }else{ - VdbeBranchTaken(res2!=0, (pOp->p5 & SQLITE_NULLEQ)?2:3); - if( res2 ){ - goto jump_to_p2; - } + VdbeBranchTaken(res2!=0, (pOp->p5 & SQLITE_NULLEQ)?2:3); + if( res2 ){ + goto jump_to_p2; } break; } -/* Opcode: ElseNotEq * P2 * * * +/* Opcode: ElseEq * P2 * * * ** -** This opcode must immediately follow an OP_Lt or OP_Gt comparison operator. -** If result of an OP_Eq comparison on the same two operands -** would have be NULL or false (0), then then jump to P2. -** If the result of an OP_Eq comparison on the two previous operands -** would have been true (1), then fall through. +** This opcode must follow an OP_Lt or OP_Gt comparison operator. There +** can be zero or more OP_ReleaseReg opcodes intervening, but no other +** opcodes are allowed to occur between this instruction and the previous +** OP_Lt or OP_Gt. +** +** If the result of an OP_Eq comparison on the same two operands as +** the prior OP_Lt or OP_Gt would have been true, then jump to P2. If +** the result of an OP_Eq comparison on the two previous operands +** would have been false or NULL, then fall through. */ -case OP_ElseNotEq: { /* same as TK_ESCAPE, jump */ - assert( pOp>aOp ); - assert( pOp[-1].opcode==OP_Lt || pOp[-1].opcode==OP_Gt ); - assert( pOp[-1].p5 & SQLITE_STOREP2 ); - VdbeBranchTaken(iCompare!=0, 2); - if( iCompare!=0 ) goto jump_to_p2; +case OP_ElseEq: { /* same as TK_ESCAPE, jump */ + +#ifdef SQLITE_DEBUG + /* Verify the preconditions of this opcode - that it follows an OP_Lt or + ** OP_Gt with zero or more intervening OP_ReleaseReg opcodes */ + int iAddr; + for(iAddr = (int)(pOp - aOp) - 1; ALWAYS(iAddr>=0); iAddr--){ + if( aOp[iAddr].opcode==OP_ReleaseReg ) continue; + assert( aOp[iAddr].opcode==OP_Lt || aOp[iAddr].opcode==OP_Gt ); + break; + } +#endif /* SQLITE_DEBUG */ + assert( iCompareIsInit ); + VdbeBranchTaken(iCompare==0, 2); + if( iCompare==0 ) goto jump_to_p2; break; } @@ -85671,9 +99057,8 @@ case OP_ElseNotEq: { /* same as TK_ESCAPE, jump */ ** Set the permutation used by the OP_Compare operator in the next ** instruction. The permutation is stored in the P4 operand. ** -** The permutation is only valid until the next OP_Compare that has -** the OPFLAG_PERMUTE bit set in P5. Typically the OP_Permutation should -** occur immediately prior to the OP_Compare. +** The permutation is only valid for the next opcode which must be +** an OP_Compare that has the OPFLAG_PERMUTE bit set in P5. ** ** The first integer in the P4 integer array is the length of the array ** and does not become part of the permutation. @@ -85705,6 +99090,8 @@ case OP_Permutation: { ** The comparison is a sort comparison, so NULLs compare equal, ** NULLs are less than numbers, numbers are less than strings, ** and strings are less than blobs. +** +** This opcode must be immediately followed by an OP_Jump opcode. */ case OP_Compare: { int n; @@ -85712,10 +99099,10 @@ case OP_Compare: { int p1; int p2; const KeyInfo *pKeyInfo; - int idx; + u32 idx; CollSeq *pColl; /* Collating sequence to use on this term */ int bRev; /* True for DESCENDING sort order */ - int *aPermute; /* The permutation */ + u32 *aPermute; /* The permutation */ if( (pOp->p5 & OPFLAG_PERMUTE)==0 ){ aPermute = 0; @@ -85730,12 +99117,13 @@ case OP_Compare: { pKeyInfo = pOp->p4.pKeyInfo; assert( n>0 ); assert( pKeyInfo!=0 ); + assert( pKeyInfo->aSortFlags!=0 ); p1 = pOp->p1; p2 = pOp->p2; #ifdef SQLITE_DEBUG if( aPermute ){ int k, mx = 0; - for(k=0; kmx ) mx = aPermute[k]; + for(k=0; k(u32)mx ) mx = aPermute[k]; assert( p1>0 && p1+mx<=(p->nMem+1 - p->nCursor)+1 ); assert( p2>0 && p2+mx<=(p->nMem+1 - p->nCursor)+1 ); }else{ @@ -85744,30 +99132,41 @@ case OP_Compare: { } #endif /* SQLITE_DEBUG */ for(i=0; inKeyField ); pColl = pKeyInfo->aColl[i]; - bRev = pKeyInfo->aSortOrder[i]; + bRev = (pKeyInfo->aSortFlags[i] & KEYINFO_ORDER_DESC); iCompare = sqlite3MemCompare(&aMem[p1+idx], &aMem[p2+idx], pColl); + VVA_ONLY( iCompareIsInit = 1; ) if( iCompare ){ + if( (pKeyInfo->aSortFlags[i] & KEYINFO_ORDER_BIGNULL) + && ((aMem[p1+idx].flags & MEM_Null) || (aMem[p2+idx].flags & MEM_Null)) + ){ + iCompare = -iCompare; + } if( bRev ) iCompare = -iCompare; break; } } + assert( pOp[1].opcode==OP_Jump ); break; } /* Opcode: Jump P1 P2 P3 * * ** ** Jump to the instruction at address P1, P2, or P3 depending on whether -** in the most recent OP_Compare instruction the P1 vector was less than +** in the most recent OP_Compare instruction the P1 vector was less than, ** equal to, or greater than the P2 vector, respectively. +** +** This opcode must immediately follow an OP_Compare opcode. */ case OP_Jump: { /* jump */ + assert( pOp>aOp && pOp[-1].opcode==OP_Compare ); + assert( iCompareIsInit ); if( iCompare<0 ){ VdbeBranchTaken(0,4); pOp = &aOp[pOp->p1 - 1]; }else if( iCompare==0 ){ @@ -85829,13 +99228,13 @@ case OP_Or: { /* same as TK_OR, in1, in2, out3 */ ** IS NOT FALSE operators. ** ** Interpret the value in register P1 as a boolean value. Store that -** boolean (a 0 or 1) in register P2. Or if the value in register P1 is +** boolean (a 0 or 1) in register P2. Or if the value in register P1 is ** NULL, then the P3 is stored in register P2. Invert the answer if P4 ** is 1. ** ** The logic is summarized like this: ** -**
              +**
                **
              • If P3==0 and P4==0 then r[P2] := r[P1] IS TRUE **
              • If P3==1 and P4==1 then r[P2] := r[P1] IS FALSE **
              • If P3==0 and P4==1 then r[P2] := r[P1] IS NOT TRUE @@ -85855,7 +99254,7 @@ case OP_IsTrue: { /* in1, out2 */ ** Synopsis: r[P2]= !r[P1] ** ** Interpret the value in register P1 as a boolean value. Store the -** boolean complement in register P2. If the value in register P1 is +** boolean complement in register P2. If the value in register P1 is ** NULL, then a NULL is stored in P2. */ case OP_Not: { /* same as TK_NOT, in1, out2 */ @@ -85887,7 +99286,7 @@ case OP_BitNot: { /* same as TK_BITNOT, in1, out2 */ break; } -/* Opcode: Once P1 P2 * * * +/* Opcode: Once P1 P2 P3 * * ** ** Fall through to the next instruction the first time this opcode is ** encountered on each invocation of the byte-code program. Jump to P2 @@ -85903,6 +99302,12 @@ case OP_BitNot: { /* same as TK_BITNOT, in1, out2 */ ** whether or not the jump should be taken. The bitmask is necessary ** because the self-altering code trick does not work for recursive ** triggers. +** +** The P3 operand is not used directly by this opcode. However P3 is +** used by the code generator as follows: If this opcode is the start +** of a subroutine and that subroutine uses a Bloom filter, then P3 will +** be the register that holds that Bloom filter. See tag-202407032019 +** in the source code for implementation details. */ case OP_Once: { /* jump */ u32 iAddr; /* Address of this instruction */ @@ -85967,10 +99372,121 @@ case OP_IsNull: { /* same as TK_ISNULL, jump, in1 */ break; } +/* Opcode: IsType P1 P2 P3 P4 P5 +** Synopsis: if typeof(P1.P3) in P5 goto P2 +** +** Jump to P2 if the type of a column in a btree is one of the types specified +** by the P5 bitmask. +** +** P1 is normally a cursor on a btree for which the row decode cache is +** valid through at least column P3. In other words, there should have been +** a prior OP_Column for column P3 or greater. If the cursor is not valid, +** then this opcode might give spurious results. +** The the btree row has fewer than P3 columns, then use P4 as the +** datatype. +** +** If P1 is -1, then P3 is a register number and the datatype is taken +** from the value in that register. +** +** P5 is a bitmask of data types. SQLITE_INTEGER is the least significant +** (0x01) bit. SQLITE_FLOAT is the 0x02 bit. SQLITE_TEXT is 0x04. +** SQLITE_BLOB is 0x08. SQLITE_NULL is 0x10. +** +** WARNING: This opcode does not reliably distinguish between NULL and REAL +** when P1>=0. If the database contains a NaN value, this opcode will think +** that the datatype is REAL when it should be NULL. When P1<0 and the value +** is already stored in register P3, then this opcode does reliably +** distinguish between NULL and REAL. The problem only arises then P1>=0. +** +** Take the jump to address P2 if and only if the datatype of the +** value determined by P1 and P3 corresponds to one of the bits in the +** P5 bitmask. +** +*/ +case OP_IsType: { /* jump */ + VdbeCursor *pC; + u16 typeMask; + u32 serialType; + + assert( pOp->p1>=(-1) && pOp->p1nCursor ); + assert( pOp->p1>=0 || (pOp->p3>=0 && pOp->p3<=(p->nMem+1 - p->nCursor)) ); + if( pOp->p1>=0 ){ + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); + assert( pOp->p3>=0 ); + if( pOp->p3nHdrParsed ){ + serialType = pC->aType[pOp->p3]; + if( serialType>=12 ){ + if( serialType&1 ){ + typeMask = 0x04; /* SQLITE_TEXT */ + }else{ + typeMask = 0x08; /* SQLITE_BLOB */ + } + }else{ + static const unsigned char aMask[] = { + 0x10, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x2, + 0x01, 0x01, 0x10, 0x10 + }; + testcase( serialType==0 ); + testcase( serialType==1 ); + testcase( serialType==2 ); + testcase( serialType==3 ); + testcase( serialType==4 ); + testcase( serialType==5 ); + testcase( serialType==6 ); + testcase( serialType==7 ); + testcase( serialType==8 ); + testcase( serialType==9 ); + testcase( serialType==10 ); + testcase( serialType==11 ); + typeMask = aMask[serialType]; + } + }else{ + typeMask = 1 << (pOp->p4.i - 1); + testcase( typeMask==0x01 ); + testcase( typeMask==0x02 ); + testcase( typeMask==0x04 ); + testcase( typeMask==0x08 ); + testcase( typeMask==0x10 ); + } + }else{ + assert( memIsValid(&aMem[pOp->p3]) ); + typeMask = 1 << (sqlite3_value_type((sqlite3_value*)&aMem[pOp->p3])-1); + testcase( typeMask==0x01 ); + testcase( typeMask==0x02 ); + testcase( typeMask==0x04 ); + testcase( typeMask==0x08 ); + testcase( typeMask==0x10 ); + } + VdbeBranchTaken( (typeMask & pOp->p5)!=0, 2); + if( typeMask & pOp->p5 ){ + goto jump_to_p2; + } + break; +} + +/* Opcode: ZeroOrNull P1 P2 P3 * * +** Synopsis: r[P2] = 0 OR NULL +** +** If both registers P1 and P3 are NOT NULL, then store a zero in +** register P2. If either registers P1 or P3 are NULL then put +** a NULL in register P2. +*/ +case OP_ZeroOrNull: { /* in1, in2, out2, in3 */ + if( (aMem[pOp->p1].flags & MEM_Null)!=0 + || (aMem[pOp->p3].flags & MEM_Null)!=0 + ){ + sqlite3VdbeMemSetNull(aMem + pOp->p2); + }else{ + sqlite3VdbeMemSetInt64(aMem + pOp->p2, 0); + } + break; +} + /* Opcode: NotNull P1 P2 * * * ** Synopsis: if r[P1]!=NULL goto P2 ** -** Jump to P2 if the value in register P1 is not NULL. +** Jump to P2 if the value in register P1 is not NULL. */ case OP_NotNull: { /* same as TK_NOTNULL, jump, in1 */ pIn1 = &aMem[pOp->p1]; @@ -85988,11 +99504,14 @@ case OP_NotNull: { /* same as TK_NOTNULL, jump, in1 */ ** If it is, then set register P3 to NULL and jump immediately to P2. ** If P1 is not on a NULL row, then fall through without making any ** changes. +** +** If P1 is not an open cursor, then this opcode is a no-op. */ case OP_IfNullRow: { /* jump */ + VdbeCursor *pC; assert( pOp->p1>=0 && pOp->p1nCursor ); - assert( p->apCsr[pOp->p1]!=0 ); - if( p->apCsr[pOp->p1]->nullRow ){ + pC = p->apCsr[pOp->p1]; + if( pC && pC->nullRow ){ sqlite3VdbeMemSetNull(aMem + pOp->p3); goto jump_to_p2; } @@ -86020,22 +99539,30 @@ case OP_Offset: { /* out3 */ assert( pOp->p1>=0 && pOp->p1nCursor ); pC = p->apCsr[pOp->p1]; pOut = &p->aMem[pOp->p3]; - if( NEVER(pC==0) || pC->eCurType!=CURTYPE_BTREE ){ + if( pC==0 || pC->eCurType!=CURTYPE_BTREE ){ sqlite3VdbeMemSetNull(pOut); }else{ - sqlite3VdbeMemSetInt64(pOut, sqlite3BtreeOffset(pC->uc.pCursor)); + if( pC->deferredMoveto ){ + rc = sqlite3VdbeFinishMoveto(pC); + if( rc ) goto abort_due_to_error; + } + if( sqlite3BtreeEof(pC->uc.pCursor) ){ + sqlite3VdbeMemSetNull(pOut); + }else{ + sqlite3VdbeMemSetInt64(pOut, sqlite3BtreeOffset(pC->uc.pCursor)); + } } break; } #endif /* SQLITE_ENABLE_OFFSET_SQL_FUNC */ /* Opcode: Column P1 P2 P3 P4 P5 -** Synopsis: r[P3]=PX +** Synopsis: r[P3]=PX cursor P1 column P2 ** ** Interpret the data that cursor P1 points to as a structure built using ** the MakeRecord instruction. (See the MakeRecord opcode for additional ** information about the format of the data.) Extract the P2-th column -** from this record. If there are less that (P2+1) +** from this record. If there are less than (P2+1) ** values in the record, extract a NULL. ** ** The value extracted is stored in register P3. @@ -86044,20 +99571,17 @@ case OP_Offset: { /* out3 */ ** if the P4 argument is a P4_MEM use the value of the P4 argument as ** the result. ** -** If the OPFLAG_CLEARCACHE bit is set on P5 and P1 is a pseudo-table cursor, -** then the cache of the cursor is reset prior to extracting the column. -** The first OP_Column against a pseudo-table after the value of the content -** register has changed should have this bit set. -** -** If the OPFLAG_LENGTHARG and OPFLAG_TYPEOFARG bits are set on P5 then -** the result is guaranteed to only be used as the argument of a length() -** or typeof() function, respectively. The loading of large blobs can be -** skipped for length() and all content loading can be skipped for typeof(). +** If the OPFLAG_LENGTHARG bit is set in P5 then the result is guaranteed +** to only be used by the length() function or the equivalent. The content +** of large blobs is not loaded, thus saving CPU cycles. If the +** OPFLAG_TYPEOFARG bit is set then the result will only be used by the +** typeof() function or the IS NULL or IS NOT NULL operators or the +** equivalent. In this case, all content loading can be omitted. */ -case OP_Column: { - int p2; /* column number to retrieve */ +case OP_Column: { /* ncycle */ + u32 p2; /* column number to retrieve */ VdbeCursor *pC; /* The VDBE cursor */ - BtCursor *pCrsr; /* The BTree cursor */ + BtCursor *pCrsr; /* The B-Tree cursor corresponding to pC */ u32 *aOffset; /* aOffset[i] is offset to start of data for i-th column */ int len; /* The length of the serialized data for the column */ int i; /* Loop counter */ @@ -86070,43 +99594,54 @@ case OP_Column: { u32 t; /* A type code from the record header */ Mem *pReg; /* PseudoTable input register */ + assert( pOp->p1>=0 && pOp->p1nCursor ); + assert( pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor) ); pC = p->apCsr[pOp->p1]; - p2 = pOp->p2; - - /* If the cursor cache is stale (meaning it is not currently point at - ** the correct row) then bring it up-to-date by doing the necessary - ** B-Tree seek. */ - rc = sqlite3VdbeCursorMoveto(&pC, &p2); - if( rc ) goto abort_due_to_error; + p2 = (u32)pOp->p2; - assert( pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor) ); - pDest = &aMem[pOp->p3]; - memAboutToChange(p, pDest); - assert( pOp->p1>=0 && pOp->p1nCursor ); +op_column_restart: assert( pC!=0 ); - assert( p2nField ); + assert( p2<(u32)pC->nField + || (pC->eCurType==CURTYPE_PSEUDO && pC->seekResult==0) ); aOffset = pC->aOffset; + assert( aOffset==pC->aType+pC->nField ); assert( pC->eCurType!=CURTYPE_VTAB ); assert( pC->eCurType!=CURTYPE_PSEUDO || pC->nullRow ); assert( pC->eCurType!=CURTYPE_SORTER ); if( pC->cacheStatus!=p->cacheCtr ){ /*OPTIMIZATION-IF-FALSE*/ if( pC->nullRow ){ - if( pC->eCurType==CURTYPE_PSEUDO ){ + if( pC->eCurType==CURTYPE_PSEUDO && pC->seekResult>0 ){ /* For the special case of as pseudo-cursor, the seekResult field ** identifies the register that holds the record */ - assert( pC->seekResult>0 ); pReg = &aMem[pC->seekResult]; assert( pReg->flags & MEM_Blob ); assert( memIsValid(pReg) ); pC->payloadSize = pC->szRow = pReg->n; pC->aRow = (u8*)pReg->z; }else{ + pDest = &aMem[pOp->p3]; + memAboutToChange(p, pDest); sqlite3VdbeMemSetNull(pDest); goto op_column_out; } }else{ pCrsr = pC->uc.pCursor; + if( pC->deferredMoveto ){ + u32 iMap; + assert( !pC->isEphemeral ); + if( pC->ub.aAltMap && (iMap = pC->ub.aAltMap[1+p2])>0 ){ + pC = pC->pAltCursor; + p2 = iMap - 1; + goto op_column_restart; + } + rc = sqlite3VdbeFinishMoveto(pC); + if( rc ) goto abort_due_to_error; + }else if( sqlite3BtreeCursorHasMoved(pCrsr) ){ + rc = sqlite3VdbeHandleMovedCursor(pC); + if( rc ) goto abort_due_to_error; + goto op_column_restart; + } assert( pC->eCurType==CURTYPE_BTREE ); assert( pCrsr ); assert( sqlite3BtreeCursorIsValid(pCrsr) ); @@ -86114,15 +99649,15 @@ case OP_Column: { pC->aRow = sqlite3BtreePayloadFetch(pCrsr, &pC->szRow); assert( pC->szRow<=pC->payloadSize ); assert( pC->szRow<=65536 ); /* Maximum page size is 64KiB */ - if( pC->payloadSize > (u32)db->aLimit[SQLITE_LIMIT_LENGTH] ){ - goto too_big; - } } pC->cacheStatus = p->cacheCtr; - pC->iHdrOffset = getVarint32(pC->aRow, aOffset[0]); + if( (aOffset[0] = pC->aRow[0])<0x80 ){ + pC->iHdrOffset = 1; + }else{ + pC->iHdrOffset = sqlite3GetVarint32(pC->aRow, aOffset); + } pC->nHdrParsed = 0; - if( pC->szRowaRow does not have to hold the entire row, but it does at least ** need to cover the header of the record. If pC->aRow does not contain @@ -86162,6 +99697,10 @@ case OP_Column: { testcase( aOffset[0]==0 ); goto op_column_read_header; } + }else if( sqlite3BtreeCursorHasMoved(pC->uc.pCursor) ){ + rc = sqlite3VdbeHandleMovedCursor(pC); + if( rc ) goto abort_due_to_error; + goto op_column_restart; } /* Make sure at least the first p2+1 entries of the header have been @@ -86169,19 +99708,19 @@ case OP_Column: { */ if( pC->nHdrParsed<=p2 ){ /* If there is more header available for parsing in the record, try - ** to extract additional fields up through the p2+1-th field + ** to extract additional fields up through the p2+1-th field */ if( pC->iHdrOffsetaRow==0 ){ memset(&sMem, 0, sizeof(sMem)); - rc = sqlite3VdbeMemFromBtree(pC->uc.pCursor, 0, aOffset[0], &sMem); + rc = sqlite3VdbeMemFromBtreeZeroOffset(pC->uc.pCursor,aOffset[0],&sMem); if( rc!=SQLITE_OK ) goto abort_due_to_error; zData = (u8*)sMem.z; }else{ zData = pC->aRow; } - + /* Fill in pC->aType[i] and aOffset[i] values through the p2-th field. */ op_column_read_header: i = pC->nHdrParsed; @@ -86199,7 +99738,7 @@ case OP_Column: { offset64 += sqlite3VdbeSerialTypeLen(t); } aOffset[++i] = (u32)(offset64 & 0xffffffff); - }while( i<=p2 && zHdrnHdrParsed<=p2 ){ + pDest = &aMem[pOp->p3]; + memAboutToChange(p, pDest); if( pOp->p4type==P4_MEM ){ sqlite3VdbeMemShallowCopy(pDest, pOp->p4.pMem, MEM_Static); }else{ @@ -86247,6 +99788,8 @@ case OP_Column: { */ assert( p2nHdrParsed ); assert( rc==SQLITE_OK ); + pDest = &aMem[pOp->p3]; + memAboutToChange(p, pDest); assert( sqlite3VdbeCheckMemInvariants(pDest) ); if( VdbeMemDynamic(pDest) ){ sqlite3VdbeMemSetNull(pDest); @@ -86267,6 +99810,7 @@ case OP_Column: { pDest->n = len = (t-12)/2; pDest->enc = encoding; if( pDest->szMalloc < len+2 ){ + if( len>db->aLimit[SQLITE_LIMIT_LENGTH] ) goto too_big; pDest->flags = MEM_Null; if( sqlite3VdbeMemGrow(pDest, len+2, 0) ) goto no_mem; }else{ @@ -86278,30 +99822,39 @@ case OP_Column: { pDest->flags = aFlag[t&1]; } }else{ + u8 p5; pDest->enc = encoding; + assert( pDest->db==db ); /* This branch happens only when content is on overflow pages */ - if( ((pOp->p5 & (OPFLAG_LENGTHARG|OPFLAG_TYPEOFARG))!=0 - && ((t>=12 && (t&1)==0) || (pOp->p5 & OPFLAG_TYPEOFARG)!=0)) - || (len = sqlite3VdbeSerialTypeLen(t))==0 + if( ((p5 = (pOp->p5 & OPFLAG_BYTELENARG))!=0 + && (p5==OPFLAG_TYPEOFARG + || (t>=12 && ((t&1)==0 || p5==OPFLAG_BYTELENARG)) + ) + ) + || sqlite3VdbeSerialTypeLen(t)==0 ){ /* Content is irrelevant for ** 1. the typeof() function, ** 2. the length(X) function if X is a blob, and ** 3. if the content length is zero. ** So we might as well use bogus content rather than reading - ** content from disk. + ** content from disk. ** ** Although sqlite3VdbeSerialGet() may read at most 8 bytes from the ** buffer passed to it, debugging function VdbeMemPrettyPrint() may - ** read up to 16. So 16 bytes of bogus content is supplied. + ** read more. Use the global constant sqlite3CtypeMap[] as the array, + ** as that array is 256 bytes long (plenty for VdbeMemPrettyPrint()) + ** and it begins with a bunch of zeros. */ - static u8 aZero[16]; /* This is the bogus content */ - sqlite3VdbeSerialGet(aZero, t, pDest); + sqlite3VdbeSerialGet((u8*)sqlite3CtypeMap, t, pDest); }else{ - rc = sqlite3VdbeMemFromBtree(pC->uc.pCursor, aOffset[p2], len, pDest); - if( rc!=SQLITE_OK ) goto abort_due_to_error; - sqlite3VdbeSerialGet((const u8*)pDest->z, t, pDest); - pDest->flags &= ~MEM_Ephem; + rc = vdbeColumnFromOverflow(pC, p2, t, aOffset[p2], + p->cacheCtr, colCacheCtr, pDest); + if( rc ){ + if( rc==SQLITE_NOMEM ) goto no_mem; + if( rc==SQLITE_TOOBIG ) goto too_big; + goto abort_due_to_error; + } } } @@ -86320,6 +99873,131 @@ case OP_Column: { } } +/* Opcode: TypeCheck P1 P2 P3 P4 * +** Synopsis: typecheck(r[P1@P2]) +** +** Apply affinities to the range of P2 registers beginning with P1. +** Take the affinities from the Table object in P4. If any value +** cannot be coerced into the correct type, then raise an error. +** +** If P3==0, then omit checking of VIRTUAL columns. +** +** If P3==1, then omit checking of all generated column, both VIRTUAL +** and STORED. +** +** If P3>=2, then only check column number P3-2 in the table (which will +** be a VIRTUAL column) against the value in reg[P1]. In this case, +** P2 will be 1. +** +** This opcode is similar to OP_Affinity except that this opcode +** forces the register type to the Table column type. This is used +** to implement "strict affinity". +** +** GENERATED ALWAYS AS ... STATIC columns are only checked if P3 +** is zero. When P3 is non-zero, no type checking occurs for +** static generated columns. Virtual columns are computed at query time +** and so they are never checked. +** +** Preconditions: +** +**
                  +**
                • P2 should be the number of non-virtual columns in the +** table of P4 unless P3>1, in which case P2 will be 1. +**
                • Table P4 is a STRICT table. +**
                +** +** If any precondition is false, an assertion fault occurs. +*/ +case OP_TypeCheck: { + Table *pTab; + Column *aCol; + int i; + int nCol; + + assert( pOp->p4type==P4_TABLE ); + pTab = pOp->p4.pTab; + assert( pTab->tabFlags & TF_Strict ); + assert( pOp->p3>=0 && pOp->p3nCol+2 ); + aCol = pTab->aCol; + pIn1 = &aMem[pOp->p1]; + if( pOp->p3<2 ){ + assert( pTab->nNVCol==pOp->p2 ); + i = 0; + nCol = pTab->nCol; + }else{ + i = pOp->p3-2; + nCol = i+1; + assert( inCol ); + assert( aCol[i].colFlags & COLFLAG_VIRTUAL ); + assert( pOp->p2==1 ); + } + for(; ip3<2 ){ + if( (aCol[i].colFlags & COLFLAG_VIRTUAL)!=0 ) continue; + if( pOp->p3 ){ pIn1++; continue; } + } + assert( pIn1 < &aMem[pOp->p1+pOp->p2] ); + applyAffinity(pIn1, aCol[i].affinity, encoding); + if( (pIn1->flags & MEM_Null)==0 ){ + switch( aCol[i].eCType ){ + case COLTYPE_BLOB: { + if( (pIn1->flags & MEM_Blob)==0 ) goto vdbe_type_error; + break; + } + case COLTYPE_INTEGER: + case COLTYPE_INT: { + if( (pIn1->flags & MEM_Int)==0 ) goto vdbe_type_error; + break; + } + case COLTYPE_TEXT: { + if( (pIn1->flags & MEM_Str)==0 ) goto vdbe_type_error; + break; + } + case COLTYPE_REAL: { + testcase( (pIn1->flags & (MEM_Real|MEM_IntReal))==MEM_Real ); + assert( (pIn1->flags & MEM_IntReal)==0 ); + if( pIn1->flags & MEM_Int ){ + /* When applying REAL affinity, if the result is still an MEM_Int + ** that will fit in 6 bytes, then change the type to MEM_IntReal + ** so that we keep the high-resolution integer value but know that + ** the type really wants to be REAL. */ + testcase( pIn1->u.i==140737488355328LL ); + testcase( pIn1->u.i==140737488355327LL ); + testcase( pIn1->u.i==-140737488355328LL ); + testcase( pIn1->u.i==-140737488355329LL ); + if( pIn1->u.i<=140737488355327LL && pIn1->u.i>=-140737488355328LL){ + pIn1->flags |= MEM_IntReal; + pIn1->flags &= ~MEM_Int; + }else{ + pIn1->u.r = (double)pIn1->u.i; + pIn1->flags |= MEM_Real; + pIn1->flags &= ~MEM_Int; + } + }else if( (pIn1->flags & (MEM_Real|MEM_IntReal))==0 ){ + goto vdbe_type_error; + } + break; + } + default: { + /* COLTYPE_ANY. Accept anything. */ + break; + } + } + } + REGISTER_TRACE((int)(pIn1-aMem), pIn1); + pIn1++; + } + assert( pIn1 == &aMem[pOp->p1+pOp->p2] ); + break; + +vdbe_type_error: + sqlite3VdbeError(p, "cannot store %s value in %s column %s.%s", + vdbeMemTypeName(pIn1), sqlite3StdType[aCol[i].eCType-1], + pTab->zName, aCol[i].zCnName); + rc = SQLITE_CONSTRAINT_DATATYPE; + goto abort_due_to_error; +} + /* Opcode: Affinity P1 P2 * P4 * ** Synopsis: affinity(r[P1@P2]) ** @@ -86337,12 +100015,33 @@ case OP_Affinity: { assert( pOp->p2>0 ); assert( zAffinity[pOp->p2]==0 ); pIn1 = &aMem[pOp->p1]; - do{ + while( 1 /*exit-by-break*/ ){ assert( pIn1 <= &p->aMem[(p->nMem+1 - p->nCursor)] ); - assert( memIsValid(pIn1) ); - applyAffinity(pIn1, *(zAffinity++), encoding); + assert( zAffinity[0]==SQLITE_AFF_NONE || memIsValid(pIn1) ); + applyAffinity(pIn1, zAffinity[0], encoding); + if( zAffinity[0]==SQLITE_AFF_REAL && (pIn1->flags & MEM_Int)!=0 ){ + /* When applying REAL affinity, if the result is still an MEM_Int + ** that will fit in 6 bytes, then change the type to MEM_IntReal + ** so that we keep the high-resolution integer value but know that + ** the type really wants to be REAL. */ + testcase( pIn1->u.i==140737488355328LL ); + testcase( pIn1->u.i==140737488355327LL ); + testcase( pIn1->u.i==-140737488355328LL ); + testcase( pIn1->u.i==-140737488355329LL ); + if( pIn1->u.i<=140737488355327LL && pIn1->u.i>=-140737488355328LL ){ + pIn1->flags |= MEM_IntReal; + pIn1->flags &= ~MEM_Int; + }else{ + pIn1->u.r = (double)pIn1->u.i; + pIn1->flags |= MEM_Real; + pIn1->flags &= ~(MEM_Int|MEM_Str); + } + } + REGISTER_TRACE((int)(pIn1-aMem), pIn1); + zAffinity++; + if( zAffinity[0]==0 ) break; pIn1++; - }while( zAffinity[0] ); + } break; } @@ -86361,9 +100060,19 @@ case OP_Affinity: { ** macros defined in sqliteInt.h. ** ** If P4 is NULL then all index fields have the affinity BLOB. +** +** The meaning of P5 depends on whether or not the SQLITE_ENABLE_NULL_TRIM +** compile-time option is enabled: +** +** * If SQLITE_ENABLE_NULL_TRIM is enabled, then the P5 is the index +** of the right-most table that can be null-trimmed. +** +** * If SQLITE_ENABLE_NULL_TRIM is omitted, then P5 has the value +** OPFLAG_NOCHNG_MAGIC if the OP_MakeRecord opcode is allowed to +** accept no-change records with serial_type 10. This value is +** only used inside an assert() and does not affect the end result. */ case OP_MakeRecord: { - u8 *zNewRecord; /* A buffer to hold the data for the new record */ Mem *pRec; /* The new record */ u64 nData; /* Number of bytes of data space */ int nHdr; /* Number of bytes of header space */ @@ -86375,22 +100084,21 @@ case OP_MakeRecord: { Mem *pLast; /* Last field of the record */ int nField; /* Number of fields in the record */ char *zAffinity; /* The affinity string for the record */ - int file_format; /* File format to use for encoding */ - int i; /* Space used in zNewRecord[] header */ - int j; /* Space used in zNewRecord[] content */ u32 len; /* Length of a field */ + u8 *zHdr; /* Where to write next byte of the header */ + u8 *zPayload; /* Where to write next byte of the payload */ /* Assuming the record contains N fields, the record format looks ** like this: ** ** ------------------------------------------------------------------------ - ** | hdr-size | type 0 | type 1 | ... | type N-1 | data0 | ... | data N-1 | + ** | hdr-size | type 0 | type 1 | ... | type N-1 | data0 | ... | data N-1 | ** ------------------------------------------------------------------------ ** ** Data(0) is taken from register P1. Data(1) comes from register P1+1 ** and so forth. ** - ** Each type field is a varint representing the serial type of the + ** Each type field is a varint representing the serial type of the ** corresponding data element (see sqlite3VdbeSerialType()). The ** hdr-size field is also a varint which is the offset from the beginning ** of the record to data0. @@ -86404,7 +100112,6 @@ case OP_MakeRecord: { pData0 = &aMem[nField]; nField = pOp->p2; pLast = &pData0[nField-1]; - file_format = p->minWriteFileFormat; /* Identify the output register */ assert( pOp->p3p1 || pOp->p3>=pOp->p1+pOp->p2 ); @@ -86417,7 +100124,14 @@ case OP_MakeRecord: { if( zAffinity ){ pRec = pData0; do{ - applyAffinity(pRec++, *(zAffinity++), encoding); + applyAffinity(pRec, zAffinity[0], encoding); + if( zAffinity[0]==SQLITE_AFF_REAL && (pRec->flags & MEM_Int) ){ + pRec->flags |= MEM_IntReal; + pRec->flags &= ~(MEM_Int); + } + REGISTER_TRACE((int)(pRec-aMem), pRec); + zAffinity++; + pRec++; assert( zAffinity[0]==0 || pRec<=pLast ); }while( zAffinity[0] ); } @@ -86437,34 +100151,122 @@ case OP_MakeRecord: { #endif /* Loop through the elements that will make up the record to figure - ** out how much space is required for the new record. + ** out how much space is required for the new record. After this loop, + ** the Mem.uTemp field of each term should hold the serial-type that will + ** be used for that term in the generated record: + ** + ** Mem.uTemp value type + ** --------------- --------------- + ** 0 NULL + ** 1 1-byte signed integer + ** 2 2-byte signed integer + ** 3 3-byte signed integer + ** 4 4-byte signed integer + ** 5 6-byte signed integer + ** 6 8-byte signed integer + ** 7 IEEE float + ** 8 Integer constant 0 + ** 9 Integer constant 1 + ** 10,11 reserved for expansion + ** N>=12 and even BLOB + ** N>=13 and odd text + ** + ** The following additional values are computed: + ** nHdr Number of bytes needed for the record header + ** nData Number of bytes of data space needed for the record + ** nZero Zero bytes at the end of the record */ pRec = pLast; do{ assert( memIsValid(pRec) ); - serial_type = sqlite3VdbeSerialType(pRec, file_format, &len); - if( pRec->flags & MEM_Zero ){ - if( serial_type==0 ){ + if( pRec->flags & MEM_Null ){ + if( pRec->flags & MEM_Zero ){ /* Values with MEM_Null and MEM_Zero are created by xColumn virtual ** table methods that never invoke sqlite3_result_xxxxx() while ** computing an unchanging column value in an UPDATE statement. ** Give such values a special internal-use-only serial-type of 10 ** so that they can be passed through to xUpdate and have ** a true sqlite3_value_nochange(). */ +#ifndef SQLITE_ENABLE_NULL_TRIM assert( pOp->p5==OPFLAG_NOCHNG_MAGIC || CORRUPT_DB ); - serial_type = 10; - }else if( nData ){ - if( sqlite3VdbeMemExpandBlob(pRec) ) goto no_mem; +#endif + pRec->uTemp = 10; }else{ - nZero += pRec->u.nZero; - len -= pRec->u.nZero; + pRec->uTemp = 0; + } + nHdr++; + }else if( pRec->flags & (MEM_Int|MEM_IntReal) ){ + /* Figure out whether to use 1, 2, 4, 6 or 8 bytes. */ + i64 i = pRec->u.i; + u64 uu; + testcase( pRec->flags & MEM_Int ); + testcase( pRec->flags & MEM_IntReal ); + if( i<0 ){ + uu = ~i; + }else{ + uu = i; + } + nHdr++; + testcase( uu==127 ); testcase( uu==128 ); + testcase( uu==32767 ); testcase( uu==32768 ); + testcase( uu==8388607 ); testcase( uu==8388608 ); + testcase( uu==2147483647 ); testcase( uu==2147483648LL ); + testcase( uu==140737488355327LL ); testcase( uu==140737488355328LL ); + if( uu<=127 ){ + if( (i&1)==i && p->minWriteFileFormat>=4 ){ + pRec->uTemp = 8+(u32)uu; + }else{ + nData++; + pRec->uTemp = 1; + } + }else if( uu<=32767 ){ + nData += 2; + pRec->uTemp = 2; + }else if( uu<=8388607 ){ + nData += 3; + pRec->uTemp = 3; + }else if( uu<=2147483647 ){ + nData += 4; + pRec->uTemp = 4; + }else if( uu<=140737488355327LL ){ + nData += 6; + pRec->uTemp = 5; + }else{ + nData += 8; + if( pRec->flags & MEM_IntReal ){ + /* If the value is IntReal and is going to take up 8 bytes to store + ** as an integer, then we might as well make it an 8-byte floating + ** point value */ + pRec->u.r = (double)pRec->u.i; + pRec->flags &= ~MEM_IntReal; + pRec->flags |= MEM_Real; + pRec->uTemp = 7; + }else{ + pRec->uTemp = 6; + } + } + }else if( pRec->flags & MEM_Real ){ + nHdr++; + nData += 8; + pRec->uTemp = 7; + }else{ + assert( db->mallocFailed || pRec->flags&(MEM_Str|MEM_Blob) ); + assert( pRec->n>=0 ); + len = (u32)pRec->n; + serial_type = (len*2) + 12 + ((pRec->flags & MEM_Str)!=0); + if( pRec->flags & MEM_Zero ){ + serial_type += (u32)pRec->u.nZero*2; + if( nData ){ + if( sqlite3VdbeMemExpandBlob(pRec) ) goto no_mem; + len += pRec->u.nZero; + }else{ + nZero += pRec->u.nZero; + } } + nData += len; + nHdr += sqlite3VarintLen(serial_type); + pRec->uTemp = serial_type; } - nData += len; - testcase( serial_type==127 ); - testcase( serial_type==128 ); - nHdr += serial_type<=127 ? 1 : sqlite3VarintLen(serial_type); - pRec->uTemp = serial_type; if( pRec==pData0 ) break; pRec--; }while(1); @@ -86486,7 +100288,7 @@ case OP_MakeRecord: { } nByte = nHdr+nData; - /* Make sure the output register has a buffer large enough to store + /* Make sure the output register has a buffer large enough to store ** the new record. The output register (pOp->p3) is not allowed to ** be one of the input registers (because the following call to ** sqlite3VdbeMemClearAndResize() could clobber the value before it is used). @@ -86505,44 +100307,99 @@ case OP_MakeRecord: { goto no_mem; } } - zNewRecord = (u8 *)pOut->z; + pOut->n = (int)nByte; + pOut->flags = MEM_Blob; + if( nZero ){ + pOut->u.nZero = nZero; + pOut->flags |= MEM_Zero; + } + UPDATE_MAX_BLOBSIZE(pOut); + zHdr = (u8 *)pOut->z; + zPayload = zHdr + nHdr; /* Write the record */ - i = putVarint32(zNewRecord, nHdr); - j = nHdr; + if( nHdr<0x80 ){ + *(zHdr++) = nHdr; + }else{ + zHdr += sqlite3PutVarint(zHdr,nHdr); + } assert( pData0<=pLast ); pRec = pData0; - do{ + while( 1 /*exit-by-break*/ ){ serial_type = pRec->uTemp; /* EVIDENCE-OF: R-06529-47362 Following the size varint are one or more - ** additional varints, one per column. */ - i += putVarint32(&zNewRecord[i], serial_type); /* serial type */ - /* EVIDENCE-OF: R-64536-51728 The values for each column in the record + ** additional varints, one per column. + ** EVIDENCE-OF: R-64536-51728 The values for each column in the record ** immediately follow the header. */ - j += sqlite3VdbeSerialPut(&zNewRecord[j], pRec, serial_type); /* content */ - }while( (++pRec)<=pLast ); - assert( i==nHdr ); - assert( j==nByte ); + if( serial_type<=7 ){ + *(zHdr++) = serial_type; + if( serial_type==0 ){ + /* NULL value. No change in zPayload */ + }else{ + u64 v; + if( serial_type==7 ){ + assert( sizeof(v)==sizeof(pRec->u.r) ); + memcpy(&v, &pRec->u.r, sizeof(v)); + swapMixedEndianFloat(v); + }else{ + v = pRec->u.i; + } + len = sqlite3SmallTypeSizes[serial_type]; + assert( len>=1 && len<=8 && len!=5 && len!=7 ); + switch( len ){ + default: zPayload[7] = (u8)(v&0xff); v >>= 8; + zPayload[6] = (u8)(v&0xff); v >>= 8; + /* no break */ deliberate_fall_through + case 6: zPayload[5] = (u8)(v&0xff); v >>= 8; + zPayload[4] = (u8)(v&0xff); v >>= 8; + /* no break */ deliberate_fall_through + case 4: zPayload[3] = (u8)(v&0xff); v >>= 8; + /* no break */ deliberate_fall_through + case 3: zPayload[2] = (u8)(v&0xff); v >>= 8; + /* no break */ deliberate_fall_through + case 2: zPayload[1] = (u8)(v&0xff); v >>= 8; + /* no break */ deliberate_fall_through + case 1: zPayload[0] = (u8)(v&0xff); + } + zPayload += len; + } + }else if( serial_type<0x80 ){ + *(zHdr++) = serial_type; + if( serial_type>=14 && pRec->n>0 ){ + assert( pRec->z!=0 ); + memcpy(zPayload, pRec->z, pRec->n); + zPayload += pRec->n; + } + }else{ + zHdr += sqlite3PutVarint(zHdr, serial_type); + if( pRec->n ){ + assert( pRec->z!=0 ); + assert( pRec->z!=(const char*)sqlite3CtypeMap ); + memcpy(zPayload, pRec->z, pRec->n); + zPayload += pRec->n; + } + } + if( pRec==pLast ) break; + pRec++; + } + assert( nHdr==(int)(zHdr - (u8*)pOut->z) ); + assert( nByte==(int)(zPayload - (u8*)pOut->z) ); assert( pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor) ); - pOut->n = (int)nByte; - pOut->flags = MEM_Blob; - if( nZero ){ - pOut->u.nZero = nZero; - pOut->flags |= MEM_Zero; - } REGISTER_TRACE(pOp->p3, pOut); - UPDATE_MAX_BLOBSIZE(pOut); break; } -/* Opcode: Count P1 P2 * * * +/* Opcode: Count P1 P2 P3 * * ** Synopsis: r[P2]=count() ** -** Store the number of entries (an integer value) in the table or index -** opened by cursor P1 in register P2 +** Store the number of entries (an integer value) in the table or index +** opened by cursor P1 in register P2. +** +** If P3==0, then an exact count is obtained, which involves visiting +** every btree page of the table. But if P3 is non-zero, an estimate +** is returned based on the current cursor position. */ -#ifndef SQLITE_OMIT_BTREECOUNT case OP_Count: { /* out2 */ i64 nEntry; BtCursor *pCrsr; @@ -86550,20 +100407,24 @@ case OP_Count: { /* out2 */ assert( p->apCsr[pOp->p1]->eCurType==CURTYPE_BTREE ); pCrsr = p->apCsr[pOp->p1]->uc.pCursor; assert( pCrsr ); - nEntry = 0; /* Not needed. Only used to silence a warning. */ - rc = sqlite3BtreeCount(pCrsr, &nEntry); - if( rc ) goto abort_due_to_error; + if( pOp->p3 ){ + nEntry = sqlite3BtreeRowCountEst(pCrsr); + }else{ + nEntry = 0; /* Not needed. Only used to silence a warning. */ + rc = sqlite3BtreeCount(db, pCrsr, &nEntry); + if( rc ) goto abort_due_to_error; + } pOut = out2Prerelease(p, pOp); pOut->u.i = nEntry; - break; + goto check_for_interrupt; } -#endif /* Opcode: Savepoint P1 * * P4 * ** ** Open, release or rollback the savepoint named by parameter P4, depending -** on the value of P1. To open a new savepoint, P1==0. To release (commit) an -** existing savepoint, P1==1, or to rollback an existing savepoint P1==2. +** on the value of P1. To open a new savepoint set P1==0 (SAVEPOINT_BEGIN). +** To release (commit) an existing savepoint set P1==1 (SAVEPOINT_RELEASE). +** To rollback an existing savepoint set P1==2 (SAVEPOINT_ROLLBACK). */ case OP_Savepoint: { int p1; /* Value of P1 operand */ @@ -86579,7 +100440,7 @@ case OP_Savepoint: { zName = pOp->p4.z; /* Assert that the p1 parameter is valid. Also that if there is no open - ** transaction, then there cannot be any savepoints. + ** transaction, then there cannot be any savepoints. */ assert( db->pSavepoint==0 || db->autoCommit==0 ); assert( p1==SAVEPOINT_BEGIN||p1==SAVEPOINT_RELEASE||p1==SAVEPOINT_ROLLBACK ); @@ -86589,7 +100450,7 @@ case OP_Savepoint: { if( p1==SAVEPOINT_BEGIN ){ if( db->nVdbeWrite>0 ){ - /* A new savepoint cannot be created if there are active write + /* A new savepoint cannot be created if there are active write ** statements (i.e. open read/write incremental blob handles). */ sqlite3VdbeError(p, "cannot open savepoint - SQL statements in progress"); @@ -86613,7 +100474,7 @@ case OP_Savepoint: { if( pNew ){ pNew->zName = (char *)&pNew[1]; memcpy(pNew->zName, zName, nName+1); - + /* If there is no open transaction, then mark this as a special ** "transaction savepoint". */ if( db->autoCommit ){ @@ -86631,12 +100492,13 @@ case OP_Savepoint: { } } }else{ + assert( p1==SAVEPOINT_RELEASE || p1==SAVEPOINT_ROLLBACK ); iSavepoint = 0; /* Find the named savepoint. If there is no such savepoint, then an ** an error is returned to the user. */ for( - pSavepoint = db->pSavepoint; + pSavepoint = db->pSavepoint; pSavepoint && sqlite3StrICmp(pSavepoint->zName, zName); pSavepoint = pSavepoint->pNext ){ @@ -86646,7 +100508,7 @@ case OP_Savepoint: { sqlite3VdbeError(p, "no such savepoint: %s", zName); rc = SQLITE_ERROR; }else if( db->nVdbeWrite>0 && p1==SAVEPOINT_RELEASE ){ - /* It is not possible to release (commit) a savepoint if there are + /* It is not possible to release (commit) a savepoint if there are ** active write statements. */ sqlite3VdbeError(p, "cannot release savepoint - " @@ -86655,12 +100517,12 @@ case OP_Savepoint: { }else{ /* Determine whether or not this is a transaction savepoint. If so, - ** and this is a RELEASE command, then the current transaction - ** is committed. + ** and this is a RELEASE command, then the current transaction + ** is committed. */ int isTransaction = pSavepoint->pNext==0 && db->isTransactionSavepoint; if( isTransaction && p1==SAVEPOINT_RELEASE ){ - if( (rc = sqlite3VdbeCheckFk(p, 1))!=SQLITE_OK ){ + if( (rc = sqlite3VdbeCheckFkDeferred(p))!=SQLITE_OK ){ goto vdbe_return; } db->autoCommit = 1; @@ -86670,8 +100532,12 @@ case OP_Savepoint: { p->rc = rc = SQLITE_BUSY; goto vdbe_return; } - db->isTransactionSavepoint = 0; rc = p->rc; + if( rc ){ + db->autoCommit = 0; + }else{ + db->isTransactionSavepoint = 0; + } }else{ int isSchemaChange; iSavepoint = db->nSavepoint - iSavepoint - 1; @@ -86684,6 +100550,7 @@ case OP_Savepoint: { if( rc!=SQLITE_OK ) goto abort_due_to_error; } }else{ + assert( p1==SAVEPOINT_RELEASE ); isSchemaChange = 0; } for(ii=0; iinDb; ii++){ @@ -86698,8 +100565,9 @@ case OP_Savepoint: { db->mDbFlags |= DBFLAG_SchemaChange; } } - - /* Regardless of whether this is a RELEASE or ROLLBACK, destroy all + if( rc ) goto abort_due_to_error; + + /* Regardless of whether this is a RELEASE or ROLLBACK, destroy all ** savepoints nested inside of the savepoint being operated on. */ while( db->pSavepoint!=pSavepoint ){ pTmp = db->pSavepoint; @@ -86708,8 +100576,8 @@ case OP_Savepoint: { db->nSavepoint--; } - /* If it is a RELEASE, then destroy the savepoint being operated on - ** too. If it is a ROLLBACK TO, then set the number of deferred + /* If it is a RELEASE, then destroy the savepoint being operated on + ** too. If it is a ROLLBACK TO, then set the number of deferred ** constraint violations present in the database to the value stored ** when the savepoint was created. */ if( p1==SAVEPOINT_RELEASE ){ @@ -86720,6 +100588,7 @@ case OP_Savepoint: { db->nSavepoint--; } }else{ + assert( p1==SAVEPOINT_ROLLBACK ); db->nDeferredCons = pSavepoint->nDeferredCons; db->nDeferredImmCons = pSavepoint->nDeferredImmCons; } @@ -86731,7 +100600,10 @@ case OP_Savepoint: { } } if( rc ) goto abort_due_to_error; - + if( p->eVdbeState==VDBE_HALT_STATE ){ + rc = SQLITE_DONE; + goto vdbe_return; + } break; } @@ -86762,13 +100634,13 @@ case OP_AutoCommit: { db->autoCommit = 1; }else if( desiredAutoCommit && db->nVdbeWrite>0 ){ /* If this instruction implements a COMMIT and other VMs are writing - ** return an error indicating that the other VMs must complete first. + ** return an error indicating that the other VMs must complete first. */ sqlite3VdbeError(p, "cannot commit transaction - " "SQL statements in progress"); rc = SQLITE_BUSY; goto abort_due_to_error; - }else if( (rc = sqlite3VdbeCheckFk(p, 1))!=SQLITE_OK ){ + }else if( (rc = sqlite3VdbeCheckFkDeferred(p))!=SQLITE_OK ){ goto vdbe_return; }else{ db->autoCommit = (u8)desiredAutoCommit; @@ -86779,7 +100651,6 @@ case OP_AutoCommit: { p->rc = rc = SQLITE_BUSY; goto vdbe_return; } - assert( db->nStatement==0 ); sqlite3CloseSavepoints(db); if( p->rc==SQLITE_OK ){ rc = SQLITE_DONE; @@ -86792,20 +100663,21 @@ case OP_AutoCommit: { (!desiredAutoCommit)?"cannot start a transaction within a transaction":( (iRollback)?"cannot rollback - no transaction is active": "cannot commit - no transaction is active")); - + rc = SQLITE_ERROR; goto abort_due_to_error; } - break; + /*NOTREACHED*/ assert(0); } /* Opcode: Transaction P1 P2 P3 P4 P5 ** ** Begin a transaction on database P1 if a transaction is not already ** active. -** If P2 is non-zero, then a write-transaction is started, or if a +** If P2 is non-zero, then a write-transaction is started, or if a ** read-transaction is already active, it is upgraded to a write-transaction. -** If P2 is zero, then a read-transaction is started. +** If P2 is zero, then a read-transaction is started. If P2 is 2 or more +** then an exclusive transaction is started. ** ** P1 is the index of the database file on which the transaction is ** started. Index 0 is the main database file and index 1 is the @@ -86835,17 +100707,28 @@ case OP_AutoCommit: { */ case OP_Transaction: { Btree *pBt; + Db *pDb; int iMeta = 0; assert( p->bIsReader ); assert( p->readOnly==0 || pOp->p2==0 ); + assert( pOp->p2>=0 && pOp->p2<=2 ); assert( pOp->p1>=0 && pOp->p1nDb ); assert( DbMaskTest(p->btreeMask, pOp->p1) ); - if( pOp->p2 && (db->flags & SQLITE_QueryOnly)!=0 ){ - rc = SQLITE_READONLY; + assert( rc==SQLITE_OK ); + if( pOp->p2 && (db->flags & (SQLITE_QueryOnly|SQLITE_CorruptRdOnly))!=0 ){ + if( db->flags & SQLITE_QueryOnly ){ + /* Writes prohibited by the "PRAGMA query_only=TRUE" statement */ + rc = SQLITE_READONLY; + }else{ + /* Writes prohibited due to a prior SQLITE_CORRUPT in the current + ** transaction */ + rc = SQLITE_CORRUPT; + } goto abort_due_to_error; } - pBt = db->aDb[pOp->p1].pBt; + pDb = &db->aDb[pOp->p1]; + pBt = pDb->pBt; if( pBt ){ rc = sqlite3BtreeBeginTrans(pBt, pOp->p2, &iMeta); @@ -86860,13 +100743,14 @@ case OP_Transaction: { goto abort_due_to_error; } - if( pOp->p2 && p->usesStmtJournal - && (db->autoCommit==0 || db->nVdbeRead>1) + if( p->usesStmtJournal + && pOp->p2 + && (db->autoCommit==0 || db->nVdbeRead>1) ){ - assert( sqlite3BtreeIsInTrans(pBt) ); + assert( sqlite3BtreeTxnState(pBt)==SQLITE_TXN_WRITE ); if( p->iStatement==0 ){ assert( db->nStatement>=0 && db->nSavepoint>=0 ); - db->nStatement++; + db->nStatement++; p->iStatement = db->nSavepoint + db->nStatement; } @@ -86883,9 +100767,9 @@ case OP_Transaction: { } } assert( pOp->p5==0 || pOp->p4type==P4_INT32 ); - if( pOp->p5 - && (iMeta!=pOp->p3 - || db->aDb[pOp->p1].pSchema->iGeneration!=pOp->p4.i) + if( rc==SQLITE_OK + && pOp->p5 + && (iMeta!=pOp->p3 || pDb->pSchema->iGeneration!=pOp->p4.i) ){ /* ** IMPLEMENTATION-OF: R-03189-51135 As each SQL statement runs, the schema @@ -86894,7 +100778,7 @@ case OP_Transaction: { */ sqlite3DbFree(db, p->zErrMsg); p->zErrMsg = sqlite3DbStrDup(db, "database schema has changed"); - /* If the schema-cookie from the database file matches the cookie + /* If the schema-cookie from the database file matches the cookie ** stored with the in-memory representation of the schema, do ** not reload the schema from the database file. ** @@ -86904,7 +100788,7 @@ case OP_Transaction: { ** prepared queries. If such a query is out-of-date, we do not want to ** discard the database schema, as the user code implementing the ** v-table would have to be ready for the sqlite3_vtab structure itself - ** to be invalidated whenever sqlite3_step() is called from within + ** to be invalidated whenever sqlite3_step() is called from within ** a v-table method. */ if( db->aDb[pOp->p1].pSchema->schema_cookie!=iMeta ){ @@ -86912,6 +100796,11 @@ case OP_Transaction: { } p->expired = 1; rc = SQLITE_SCHEMA; + + /* Set changeCntOn to 0 to prevent the value returned by sqlite3_changes() + ** from being modified in sqlite3VdbeHalt(). If this statement is + ** reprepared, changeCntOn will be set again. */ + p->changeCntOn = 0; } if( rc ) goto abort_due_to_error; break; @@ -86948,15 +100837,20 @@ case OP_ReadCookie: { /* out2 */ break; } -/* Opcode: SetCookie P1 P2 P3 * * +/* Opcode: SetCookie P1 P2 P3 * P5 ** ** Write the integer value P3 into cookie number P2 of database P1. ** P2==1 is the schema version. P2==2 is the database format. -** P2==3 is the recommended pager cache -** size, and so forth. P1==0 is the main database file and P1==1 is the +** P2==3 is the recommended pager cache +** size, and so forth. P1==0 is the main database file and P1==1 is the ** database file used to store temporary tables. ** ** A transaction must be started before executing this opcode. +** +** If P2 is the SCHEMA_VERSION cookie (cookie number 1) then the internal +** schema version is set to P3-P5. The "PRAGMA schema_version=N" statement +** has P5 set to 1, so that the internal schema version will be different +** from the database schema version, resulting in a schema reset. */ case OP_SetCookie: { Db *pDb; @@ -86973,8 +100867,9 @@ case OP_SetCookie: { rc = sqlite3BtreeUpdateMeta(pDb->pBt, pOp->p2, pOp->p3); if( pOp->p2==BTREE_SCHEMA_VERSION ){ /* When the schema cookie changes, record the new cookie internally */ - pDb->pSchema->schema_cookie = pOp->p3; + *(u32*)&pDb->pSchema->schema_cookie = *(u32*)&pOp->p3 - pOp->p5; db->mDbFlags |= DBFLAG_SchemaChange; + sqlite3FkClearTriggerCache(db, pOp->p1); }else if( pOp->p2==BTREE_FILE_FORMAT ){ /* Record changes in the file format */ pDb->pSchema->file_format = pOp->p3; @@ -86993,8 +100888,8 @@ case OP_SetCookie: { ** Synopsis: root=P2 iDb=P3 ** ** Open a read-only cursor for the database table whose root page is -** P2 in a database file. The database file is determined by P3. -** P3==0 means the main database, P3==1 means the database used for +** P2 in a database file. The database file is determined by P3. +** P3==0 means the main database, P3==1 means the database used for ** temporary tables, and P3>1 means used the corresponding attached ** database. Give the new cursor an identifier of P1. The P1 ** values need not be contiguous but all P1 values should be small integers. @@ -87004,14 +100899,14 @@ case OP_SetCookie: { **
                  **
                • 0x02 OPFLAG_SEEKEQ: This cursor will only be used for ** equality lookups (implemented as a pair of opcodes OP_SeekGE/OP_IdxGT -** of OP_SeekLE/OP_IdxGT) +** of OP_SeekLE/OP_IdxLT) **
                ** ** The P4 value may be either an integer (P4_INT32) or a pointer to -** a KeyInfo structure (P4_KEYINFO). If it is a pointer to a KeyInfo +** a KeyInfo structure (P4_KEYINFO). If it is a pointer to a KeyInfo ** object, then table being opened must be an [index b-tree] where the -** KeyInfo object defines the content and collating -** sequence of that index b-tree. Otherwise, if P4 is an integer +** KeyInfo object defines the content and collating +** sequence of that index b-tree. Otherwise, if P4 is an integer ** value, then the table being opened must be a [table b-tree] with a ** number of columns no less than the value of P4. ** @@ -87034,7 +100929,7 @@ case OP_SetCookie: { **
                  **
                • 0x02 OPFLAG_SEEKEQ: This cursor will only be used for ** equality lookups (implemented as a pair of opcodes OP_SeekGE/OP_IdxGT -** of OP_SeekLE/OP_IdxGT) +** of OP_SeekLE/OP_IdxLT) **
                ** ** See also: OP_OpenRead, OP_OpenWrite @@ -87047,10 +100942,10 @@ case OP_SetCookie: { ** OPFLAG_P2ISREG bit is set in P5 - see below). ** ** The P4 value may be either an integer (P4_INT32) or a pointer to -** a KeyInfo structure (P4_KEYINFO). If it is a pointer to a KeyInfo +** a KeyInfo structure (P4_KEYINFO). If it is a pointer to a KeyInfo ** object, then table being opened must be an [index b-tree] where the -** KeyInfo object defines the content and collating -** sequence of that index b-tree. Otherwise, if P4 is an integer +** KeyInfo object defines the content and collating +** sequence of that index b-tree. Otherwise, if P4 is an integer ** value, then the table being opened must be a [table b-tree] with a ** number of columns no less than the value of P4. ** @@ -87058,7 +100953,7 @@ case OP_SetCookie: { **
                  **
                • 0x02 OPFLAG_SEEKEQ: This cursor will only be used for ** equality lookups (implemented as a pair of opcodes OP_SeekGE/OP_IdxGT -** of OP_SeekLE/OP_IdxGT) +** of OP_SeekLE/OP_IdxLT) **
                • 0x08 OPFLAG_FORDELETE: This cursor is used only to seek ** and subsequently delete entries in an index btree. This is a ** hint to the storage engine that the storage engine is allowed to @@ -87073,10 +100968,10 @@ case OP_SetCookie: { ** ** See also: OP_OpenRead, OP_ReopenIdx */ -case OP_ReopenIdx: { +case OP_ReopenIdx: { /* ncycle */ int nField; KeyInfo *pKeyInfo; - int p2; + u32 p2; int iDb; int wrFlag; Btree *pX; @@ -87088,11 +100983,13 @@ case OP_ReopenIdx: { pCur = p->apCsr[pOp->p1]; if( pCur && pCur->pgnoRoot==(u32)pOp->p2 ){ assert( pCur->iDb==pOp->p3 ); /* Guaranteed by the code generator */ + assert( pCur->eCurType==CURTYPE_BTREE ); + sqlite3BtreeClearCursor(pCur->uc.pCursor); goto open_cursor_set_hints; } /* If the cursor is not currently open or is open on a different ** index, then fall through into OP_OpenRead to force a reopen */ -case OP_OpenRead: +case OP_OpenRead: /* ncycle */ case OP_OpenWrite: assert( pOp->opcode==OP_OpenWrite || pOp->p5==0 || pOp->p5==OPFLAG_SEEKEQ ); @@ -87107,7 +101004,7 @@ case OP_OpenWrite: nField = 0; pKeyInfo = 0; - p2 = pOp->p2; + p2 = (u32)pOp->p2; iDb = pOp->p3; assert( iDb>=0 && iDbnDb ); assert( DbMaskTest(p->btreeMask, iDb) ); @@ -87121,23 +101018,23 @@ case OP_OpenWrite: if( pDb->pSchema->file_format < p->minWriteFileFormat ){ p->minWriteFileFormat = pDb->pSchema->file_format; } + if( pOp->p5 & OPFLAG_P2ISREG ){ + assert( p2>0 ); + assert( p2<=(u32)(p->nMem+1 - p->nCursor) ); + pIn2 = &aMem[p2]; + assert( memIsValid(pIn2) ); + assert( (pIn2->flags & MEM_Int)!=0 ); + sqlite3VdbeMemIntegerify(pIn2); + p2 = (int)pIn2->u.i; + /* The p2 value always comes from a prior OP_CreateBtree opcode and + ** that opcode will always set the p2 value to 2 or more or else fail. + ** If there were a failure, the prepared statement would have halted + ** before reaching this instruction. */ + assert( p2>=2 ); + } }else{ wrFlag = 0; - } - if( pOp->p5 & OPFLAG_P2ISREG ){ - assert( p2>0 ); - assert( p2<=(p->nMem+1 - p->nCursor) ); - assert( pOp->opcode==OP_OpenWrite ); - pIn2 = &aMem[p2]; - assert( memIsValid(pIn2) ); - assert( (pIn2->flags & MEM_Int)!=0 ); - sqlite3VdbeMemIntegerify(pIn2); - p2 = (int)pIn2->u.i; - /* The p2 value always comes from a prior OP_CreateBtree opcode and - ** that opcode will always set the p2 value to 2 or more or else fail. - ** If there were a failure, the prepared statement would have halted - ** before reaching this instruction. */ - assert( p2>=2 ); + assert( (pOp->p5 & OPFLAG_P2ISREG)==0 ); } if( pOp->p4type==P4_KEYINFO ){ pKeyInfo = pOp->p4.pKeyInfo; @@ -87150,8 +101047,9 @@ case OP_OpenWrite: assert( pOp->p1>=0 ); assert( nField>=0 ); testcase( nField==0 ); /* Table with INTEGER PRIMARY KEY and nothing else */ - pCur = allocateCursor(p, pOp->p1, nField, iDb, CURTYPE_BTREE); + pCur = allocateCursor(p, pOp->p1, nField, CURTYPE_BTREE); if( pCur==0 ) goto no_mem; + pCur->iDb = iDb; pCur->nullRow = 1; pCur->isOrdered = 1; pCur->pgnoRoot = p2; @@ -87163,16 +101061,14 @@ case OP_OpenWrite: /* Set the VdbeCursor.isTable variable. Previous versions of ** SQLite used to check if the root-page flags were sane at this point ** and report database corruption if they were not, but this check has - ** since moved into the btree layer. */ + ** since moved into the btree layer. */ pCur->isTable = pOp->p4type!=P4_KEYINFO; open_cursor_set_hints: assert( OPFLAG_BULKCSR==BTREE_BULKLOAD ); assert( OPFLAG_SEEKEQ==BTREE_SEEK_EQ ); testcase( pOp->p5 & OPFLAG_BULKCSR ); -#ifdef SQLITE_ENABLE_CURSOR_HINTS testcase( pOp->p2 & OPFLAG_SEEKEQ ); -#endif sqlite3BtreeCursorHintFlags(pCur->uc.pCursor, (pOp->p5 & (OPFLAG_BULKCSR|OPFLAG_SEEKEQ))); if( rc ) goto abort_due_to_error; @@ -87187,14 +101083,15 @@ case OP_OpenWrite: ** ** Duplicate ephemeral cursors are used for self-joins of materialized views. */ -case OP_OpenDup: { +case OP_OpenDup: { /* ncycle */ VdbeCursor *pOrig; /* The original cursor to be duplicated */ VdbeCursor *pCx; /* The new cursor */ pOrig = p->apCsr[pOp->p2]; - assert( pOrig->pBtx!=0 ); /* Only ephemeral cursors can be duplicated */ + assert( pOrig ); + assert( pOrig->isEphemeral ); /* Only ephemeral cursors can be duplicated */ - pCx = allocateCursor(p, pOp->p1, pOrig->nField, -1, CURTYPE_BTREE); + pCx = allocateCursor(p, pOp->p1, pOrig->nField, CURTYPE_BTREE); if( pCx==0 ) goto no_mem; pCx->nullRow = 1; pCx->isEphemeral = 1; @@ -87202,7 +101099,10 @@ case OP_OpenDup: { pCx->isTable = pOrig->isTable; pCx->pgnoRoot = pOrig->pgnoRoot; pCx->isOrdered = pOrig->isOrdered; - rc = sqlite3BtreeCursor(pOrig->pBtx, pCx->pgnoRoot, BTREE_WRCSR, + pCx->ub.pBtx = pOrig->ub.pBtx; + pCx->noReuse = 1; + pOrig->noReuse = 1; + rc = sqlite3BtreeCursor(pCx->ub.pBtx, pCx->pgnoRoot, BTREE_WRCSR, pCx->pKeyInfo, pCx->uc.pCursor); /* The sqlite3BtreeCursor() routine can only fail for the first cursor ** opened for a database. Since there is already an open cursor when this @@ -87212,11 +101112,11 @@ case OP_OpenDup: { } -/* Opcode: OpenEphemeral P1 P2 * P4 P5 +/* Opcode: OpenEphemeral P1 P2 P3 P4 P5 ** Synopsis: nColumn=P2 ** ** Open a new cursor P1 to a transient table. -** The cursor is always opened read/write even if +** The cursor is always opened read/write even if ** the main database is read-only. The ephemeral ** table is deleted automatically when the cursor is closed. ** @@ -87232,6 +101132,10 @@ case OP_OpenDup: { ** in btree.h. These flags control aspects of the operation of ** the btree. The BTREE_OMIT_JOURNAL and BTREE_SINGLE flags are ** added automatically. +** +** If P3 is positive, then reg[P3] is modified slightly so that it +** can be used as zero-length data for OP_Insert. This is an optimization +** that avoids an extra OP_Blob opcode to initialize that register. */ /* Opcode: OpenAutoindex P1 P2 * P4 * ** Synopsis: nColumn=P2 @@ -87241,12 +101145,12 @@ case OP_OpenDup: { ** by this opcode will be used for automatically created transient ** indices in joins. */ -case OP_OpenAutoindex: -case OP_OpenEphemeral: { +case OP_OpenAutoindex: /* ncycle */ +case OP_OpenEphemeral: { /* ncycle */ VdbeCursor *pCx; KeyInfo *pKeyInfo; - static const int vfsFlags = + static const int vfsFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_EXCLUSIVE | @@ -87254,50 +101158,71 @@ case OP_OpenEphemeral: { SQLITE_OPEN_TRANSIENT_DB; assert( pOp->p1>=0 ); assert( pOp->p2>=0 ); + if( pOp->p3>0 ){ + /* Make register reg[P3] into a value that can be used as the data + ** form sqlite3BtreeInsert() where the length of the data is zero. */ + assert( pOp->p2==0 ); /* Only used when number of columns is zero */ + assert( pOp->opcode==OP_OpenEphemeral ); + assert( aMem[pOp->p3].flags & MEM_Null ); + aMem[pOp->p3].n = 0; + aMem[pOp->p3].z = ""; + } pCx = p->apCsr[pOp->p1]; - if( pCx ){ - /* If the ephermeral table is already open, erase all existing content - ** so that the table is empty again, rather than creating a new table. */ - rc = sqlite3BtreeClearTable(pCx->pBtx, pCx->pgnoRoot, 0); + if( pCx && !pCx->noReuse && ALWAYS(pOp->p2<=pCx->nField) ){ + /* If the ephemeral table is already open and has no duplicates from + ** OP_OpenDup, then erase all existing content so that the table is + ** empty again, rather than creating a new table. */ + assert( pCx->isEphemeral ); + pCx->seqCount = 0; + pCx->cacheStatus = CACHE_STALE; + rc = sqlite3BtreeClearTable(pCx->ub.pBtx, pCx->pgnoRoot, 0); }else{ - pCx = allocateCursor(p, pOp->p1, pOp->p2, -1, CURTYPE_BTREE); + pCx = allocateCursor(p, pOp->p1, pOp->p2, CURTYPE_BTREE); if( pCx==0 ) goto no_mem; - pCx->nullRow = 1; pCx->isEphemeral = 1; - rc = sqlite3BtreeOpen(db->pVfs, 0, db, &pCx->pBtx, + rc = sqlite3BtreeOpen(db->pVfs, 0, db, &pCx->ub.pBtx, BTREE_OMIT_JOURNAL | BTREE_SINGLE | pOp->p5, vfsFlags); if( rc==SQLITE_OK ){ - rc = sqlite3BtreeBeginTrans(pCx->pBtx, 1, 0); - } - if( rc==SQLITE_OK ){ - /* If a transient index is required, create it by calling - ** sqlite3BtreeCreateTable() with the BTREE_BLOBKEY flag before - ** opening it. If a transient table is required, just use the - ** automatically created table with root-page 1 (an BLOB_INTKEY table). - */ - if( (pCx->pKeyInfo = pKeyInfo = pOp->p4.pKeyInfo)!=0 ){ - assert( pOp->p4type==P4_KEYINFO ); - rc = sqlite3BtreeCreateTable(pCx->pBtx, (int*)&pCx->pgnoRoot, - BTREE_BLOBKEY | pOp->p5); - if( rc==SQLITE_OK ){ - assert( pCx->pgnoRoot==MASTER_ROOT+1 ); - assert( pKeyInfo->db==db ); - assert( pKeyInfo->enc==ENC(db) ); - rc = sqlite3BtreeCursor(pCx->pBtx, pCx->pgnoRoot, BTREE_WRCSR, - pKeyInfo, pCx->uc.pCursor); + rc = sqlite3BtreeBeginTrans(pCx->ub.pBtx, 1, 0); + if( rc==SQLITE_OK ){ + /* If a transient index is required, create it by calling + ** sqlite3BtreeCreateTable() with the BTREE_BLOBKEY flag before + ** opening it. If a transient table is required, just use the + ** automatically created table with root-page 1 (an BLOB_INTKEY table). + */ + if( (pCx->pKeyInfo = pKeyInfo = pOp->p4.pKeyInfo)!=0 ){ + assert( pOp->p4type==P4_KEYINFO ); + rc = sqlite3BtreeCreateTable(pCx->ub.pBtx, &pCx->pgnoRoot, + BTREE_BLOBKEY | pOp->p5); + if( rc==SQLITE_OK ){ + assert( pCx->pgnoRoot==SCHEMA_ROOT+1 ); + assert( pKeyInfo->db==db ); + assert( pKeyInfo->enc==ENC(db) ); + rc = sqlite3BtreeCursor(pCx->ub.pBtx, pCx->pgnoRoot, BTREE_WRCSR, + pKeyInfo, pCx->uc.pCursor); + } + pCx->isTable = 0; + }else{ + pCx->pgnoRoot = SCHEMA_ROOT; + rc = sqlite3BtreeCursor(pCx->ub.pBtx, SCHEMA_ROOT, BTREE_WRCSR, + 0, pCx->uc.pCursor); + pCx->isTable = 1; } - pCx->isTable = 0; + } + pCx->isOrdered = (pOp->p5!=BTREE_UNORDERED); + assert( p->apCsr[pOp->p1]==pCx ); + if( rc ){ + assert( !sqlite3BtreeClosesWithCursor(pCx->ub.pBtx, pCx->uc.pCursor) ); + sqlite3BtreeClose(pCx->ub.pBtx); + p->apCsr[pOp->p1] = 0; /* Not required; helps with static analysis */ }else{ - pCx->pgnoRoot = MASTER_ROOT; - rc = sqlite3BtreeCursor(pCx->pBtx, MASTER_ROOT, BTREE_WRCSR, - 0, pCx->uc.pCursor); - pCx->isTable = 1; + assert( sqlite3BtreeClosesWithCursor(pCx->ub.pBtx, pCx->uc.pCursor) ); } } - pCx->isOrdered = (pOp->p5!=BTREE_UNORDERED); } if( rc ) goto abort_due_to_error; + pCx->nullRow = 1; break; } @@ -87316,7 +101241,7 @@ case OP_SorterOpen: { assert( pOp->p1>=0 ); assert( pOp->p2>=0 ); - pCx = allocateCursor(p, pOp->p1, pOp->p2, -1, CURTYPE_SORTER); + pCx = allocateCursor(p, pOp->p1, pOp->p2, CURTYPE_SORTER); if( pCx==0 ) goto no_mem; pCx->pKeyInfo = pOp->p4.pKeyInfo; assert( pCx->pKeyInfo->db==db ); @@ -87349,7 +101274,7 @@ case OP_SequenceTest: { ** ** Open a new cursor that points to a fake table that contains a single ** row of data. The content of that one row is the content of memory -** register P2. In other words, cursor P1 becomes an alias for the +** register P2. In other words, cursor P1 becomes an alias for the ** MEM_Blob content contained in register P2. ** ** A pseudo-table created by this opcode is used to hold a single @@ -87358,14 +101283,15 @@ case OP_SequenceTest: { ** is the only cursor opcode that works with a pseudo-table. ** ** P3 is the number of fields in the records that will be stored by -** the pseudo-table. +** the pseudo-table. If P2 is 0 or negative then the pseudo-cursor +** will return NULL for every column. */ case OP_OpenPseudo: { VdbeCursor *pCx; assert( pOp->p1>=0 ); assert( pOp->p3>=0 ); - pCx = allocateCursor(p, pOp->p1, pOp->p3, -1, CURTYPE_PSEUDO); + pCx = allocateCursor(p, pOp->p1, pOp->p3, CURTYPE_PSEUDO); if( pCx==0 ) goto no_mem; pCx->nullRow = 1; pCx->seekResult = pOp->p2; @@ -87384,7 +101310,7 @@ case OP_OpenPseudo: { ** Close a cursor previously opened as P1. If P1 is not ** currently open, this instruction is a no-op. */ -case OP_Close: { +case OP_Close: { /* ncycle */ assert( pOp->p1>=0 && pOp->p1nCursor ); sqlite3VdbeFreeCursor(p, p->apCsr[pOp->p1]); p->apCsr[pOp->p1] = 0; @@ -87414,21 +101340,23 @@ case OP_ColumnsUsed: { /* Opcode: SeekGE P1 P2 P3 P4 * ** Synopsis: key=r[P3@P4] ** -** If cursor P1 refers to an SQL table (B-Tree that uses integer keys), -** use the value in register P3 as the key. If cursor P1 refers -** to an SQL index, then P3 is the first in an array of P4 registers -** that are used as an unpacked index key. +** If cursor P1 refers to an SQL table (B-Tree that uses integer keys), +** use the value in register P3 as the key. If cursor P1 refers +** to an SQL index, then P3 is the first in an array of P4 registers +** that are used as an unpacked index key. ** -** Reposition cursor P1 so that it points to the smallest entry that -** is greater than or equal to the key value. If there are no records +** Reposition cursor P1 so that it points to the smallest entry that +** is greater than or equal to the key value. If there are no records ** greater than or equal to the key and P2 is not zero, then jump to P2. ** ** If the cursor P1 was opened using the OPFLAG_SEEKEQ flag, then this -** opcode will always land on a record that equally equals the key, or -** else jump immediately to P2. When the cursor is OPFLAG_SEEKEQ, this -** opcode must be followed by an IdxLE opcode with the same arguments. -** The IdxLE opcode will be skipped if this opcode succeeds, but the -** IdxLE opcode will be used on subsequent loop iterations. +** opcode will either land on a record that exactly matches the key, or +** else it will cause a jump to P2. When the cursor is OPFLAG_SEEKEQ, +** this opcode must be followed by an IdxLE opcode with the same arguments. +** The IdxGT opcode will be skipped if this opcode succeeds, but the +** IdxGT opcode will be used on subsequent loop iterations. The +** OPFLAG_SEEKEQ flags is a hint to the btree layer to say that this +** is an equality search. ** ** This opcode leaves the cursor configured to move in forward order, ** from the beginning toward the end. In other words, the cursor is @@ -87439,13 +101367,13 @@ case OP_ColumnsUsed: { /* Opcode: SeekGT P1 P2 P3 P4 * ** Synopsis: key=r[P3@P4] ** -** If cursor P1 refers to an SQL table (B-Tree that uses integer keys), -** use the value in register P3 as a key. If cursor P1 refers -** to an SQL index, then P3 is the first in an array of P4 registers -** that are used as an unpacked index key. +** If cursor P1 refers to an SQL table (B-Tree that uses integer keys), +** use the value in register P3 as a key. If cursor P1 refers +** to an SQL index, then P3 is the first in an array of P4 registers +** that are used as an unpacked index key. ** -** Reposition cursor P1 so that it points to the smallest entry that -** is greater than the key value. If there are no records greater than +** Reposition cursor P1 so that it points to the smallest entry that +** is greater than the key value. If there are no records greater than ** the key and P2 is not zero, then jump to P2. ** ** This opcode leaves the cursor configured to move in forward order, @@ -87454,16 +101382,16 @@ case OP_ColumnsUsed: { ** ** See also: Found, NotFound, SeekLt, SeekGe, SeekLe */ -/* Opcode: SeekLT P1 P2 P3 P4 * +/* Opcode: SeekLT P1 P2 P3 P4 * ** Synopsis: key=r[P3@P4] ** -** If cursor P1 refers to an SQL table (B-Tree that uses integer keys), -** use the value in register P3 as a key. If cursor P1 refers -** to an SQL index, then P3 is the first in an array of P4 registers -** that are used as an unpacked index key. +** If cursor P1 refers to an SQL table (B-Tree that uses integer keys), +** use the value in register P3 as a key. If cursor P1 refers +** to an SQL index, then P3 is the first in an array of P4 registers +** that are used as an unpacked index key. ** -** Reposition cursor P1 so that it points to the largest entry that -** is less than the key value. If there are no records less than +** Reposition cursor P1 so that it points to the largest entry that +** is less than the key value. If there are no records less than ** the key and P2 is not zero, then jump to P2. ** ** This opcode leaves the cursor configured to move in reverse order, @@ -87475,13 +101403,13 @@ case OP_ColumnsUsed: { /* Opcode: SeekLE P1 P2 P3 P4 * ** Synopsis: key=r[P3@P4] ** -** If cursor P1 refers to an SQL table (B-Tree that uses integer keys), -** use the value in register P3 as a key. If cursor P1 refers -** to an SQL index, then P3 is the first in an array of P4 registers -** that are used as an unpacked index key. +** If cursor P1 refers to an SQL table (B-Tree that uses integer keys), +** use the value in register P3 as a key. If cursor P1 refers +** to an SQL index, then P3 is the first in an array of P4 registers +** that are used as an unpacked index key. ** -** Reposition cursor P1 so that it points to the largest entry that -** is less than or equal to the key value. If there are no records +** Reposition cursor P1 so that it points to the largest entry that +** is less than or equal to the key value. If there are no records ** less than or equal to the key and P2 is not zero, then jump to P2. ** ** This opcode leaves the cursor configured to move in reverse order, @@ -87489,18 +101417,20 @@ case OP_ColumnsUsed: { ** configured to use Prev, not Next. ** ** If the cursor P1 was opened using the OPFLAG_SEEKEQ flag, then this -** opcode will always land on a record that equally equals the key, or -** else jump immediately to P2. When the cursor is OPFLAG_SEEKEQ, this -** opcode must be followed by an IdxGE opcode with the same arguments. +** opcode will either land on a record that exactly matches the key, or +** else it will cause a jump to P2. When the cursor is OPFLAG_SEEKEQ, +** this opcode must be followed by an IdxLE opcode with the same arguments. ** The IdxGE opcode will be skipped if this opcode succeeds, but the -** IdxGE opcode will be used on subsequent loop iterations. +** IdxGE opcode will be used on subsequent loop iterations. The +** OPFLAG_SEEKEQ flags is a hint to the btree layer to say that this +** is an equality search. ** ** See also: Found, NotFound, SeekGt, SeekGe, SeekLt */ -case OP_SeekLT: /* jump, in3, group */ -case OP_SeekLE: /* jump, in3, group */ -case OP_SeekGE: /* jump, in3, group */ -case OP_SeekGT: { /* jump, in3, group */ +case OP_SeekLT: /* jump0, in3, group, ncycle */ +case OP_SeekLE: /* jump0, in3, group, ncycle */ +case OP_SeekGE: /* jump0, in3, group, ncycle */ +case OP_SeekGT: { /* jump0, in3, group, ncycle */ int res; /* Comparison result */ int oc; /* Opcode */ VdbeCursor *pC; /* The cursor to seek */ @@ -87526,8 +101456,11 @@ case OP_SeekGT: { /* jump, in3, group */ pC->seekOp = pOp->opcode; #endif + pC->deferredMoveto = 0; + pC->cacheStatus = CACHE_STALE; if( pC->isTable ){ - /* The BTREE_SEEK_EQ flag is only set on index cursors */ + u16 flags3, newType; + /* The OPFLAG_SEEKEQ/BTREE_SEEK_EQ flag is only set on index cursors */ assert( sqlite3BtreeCursorHasHint(pC->uc.pCursor, BTREE_SEEK_EQ)==0 || CORRUPT_DB ); @@ -87535,20 +101468,29 @@ case OP_SeekGT: { /* jump, in3, group */ ** blob, or NULL. But it needs to be an integer before we can do ** the seek, so convert it. */ pIn3 = &aMem[pOp->p3]; - if( (pIn3->flags & (MEM_Int|MEM_Real|MEM_Str))==MEM_Str ){ + flags3 = pIn3->flags; + if( (flags3 & (MEM_Int|MEM_Real|MEM_IntReal|MEM_Str))==MEM_Str ){ applyNumericAffinity(pIn3, 0); } - iKey = sqlite3VdbeIntValue(pIn3); + iKey = sqlite3VdbeIntValue(pIn3); /* Get the integer key value */ + newType = pIn3->flags; /* Record the type after applying numeric affinity */ + pIn3->flags = flags3; /* But convert the type back to its original */ /* If the P3 value could not be converted into an integer without ** loss of information, then special processing is required... */ - if( (pIn3->flags & MEM_Int)==0 ){ - if( (pIn3->flags & MEM_Real)==0 ){ - /* If the P3 value cannot be converted into any kind of a number, - ** then the seek is not possible, so jump to P2 */ - VdbeBranchTaken(1,2); goto jump_to_p2; - break; + if( (newType & (MEM_Int|MEM_IntReal))==0 ){ + int c; + if( (newType & MEM_Real)==0 ){ + if( (newType & MEM_Null) || oc>=OP_SeekGE ){ + VdbeBranchTaken(1,2); + goto jump_to_p2; + }else{ + rc = sqlite3BtreeLast(pC->uc.pCursor, &res); + if( rc!=SQLITE_OK ) goto abort_due_to_error; + goto seek_not_found; + } } + c = sqlite3IntFloatCompare(iKey, pIn3->u.r); /* If the approximation iKey is larger than the actual real search ** term, substitute >= for > and < for <=. e.g. if the search term @@ -87557,7 +101499,7 @@ case OP_SeekGT: { /* jump, in3, group */ ** (x > 4.9) -> (x >= 5) ** (x <= 4.9) -> (x < 5) */ - if( pIn3->u.r<(double)iKey ){ + if( c>0 ){ assert( OP_SeekGE==(OP_SeekGT-1) ); assert( OP_SeekLT==(OP_SeekLE-1) ); assert( (OP_SeekLE & 0x0001)==(OP_SeekGT & 0x0001) ); @@ -87566,27 +101508,30 @@ case OP_SeekGT: { /* jump, in3, group */ /* If the approximation iKey is smaller than the actual real search ** term, substitute <= for < and > for >=. */ - else if( pIn3->u.r>(double)iKey ){ + else if( c<0 ){ assert( OP_SeekLE==(OP_SeekLT+1) ); assert( OP_SeekGT==(OP_SeekGE+1) ); assert( (OP_SeekLT & 0x0001)==(OP_SeekGE & 0x0001) ); if( (oc & 0x0001)==(OP_SeekLT & 0x0001) ) oc++; } - } - rc = sqlite3BtreeMovetoUnpacked(pC->uc.pCursor, 0, (u64)iKey, 0, &res); + } + rc = sqlite3BtreeTableMoveto(pC->uc.pCursor, (u64)iKey, 0, &res); pC->movetoTarget = iKey; /* Used by OP_Delete */ if( rc!=SQLITE_OK ){ goto abort_due_to_error; } }else{ - /* For a cursor with the BTREE_SEEK_EQ hint, only the OP_SeekGE and - ** OP_SeekLE opcodes are allowed, and these must be immediately followed - ** by an OP_IdxGT or OP_IdxLT opcode, respectively, with the same key. + /* For a cursor with the OPFLAG_SEEKEQ/BTREE_SEEK_EQ hint, only the + ** OP_SeekGE and OP_SeekLE opcodes are allowed, and these must be + ** immediately followed by an OP_IdxGT or OP_IdxLT opcode, respectively, + ** with the same key. */ if( sqlite3BtreeCursorHasHint(pC->uc.pCursor, BTREE_SEEK_EQ) ){ eqOnly = 1; assert( pOp->opcode==OP_SeekGE || pOp->opcode==OP_SeekLE ); assert( pOp[1].opcode==OP_IdxLT || pOp[1].opcode==OP_IdxGT ); + assert( pOp->opcode==OP_SeekGE || pOp[1].opcode==OP_IdxLT ); + assert( pOp->opcode==OP_SeekLE || pOp[1].opcode==OP_IdxGT ); assert( pOp[1].p1==pOp[0].p1 ); assert( pOp[1].p2==pOp[0].p2 ); assert( pOp[1].p3==pOp[0].p3 ); @@ -87614,10 +101559,16 @@ case OP_SeekGT: { /* jump, in3, group */ r.aMem = &aMem[pOp->p3]; #ifdef SQLITE_DEBUG - { int i; for(i=0; i0 ) REGISTER_TRACE(pOp->p3+i, &r.aMem[i]); + } + } #endif r.eqSeen = 0; - rc = sqlite3BtreeMovetoUnpacked(pC->uc.pCursor, &r, 0, 0, &res); + rc = sqlite3BtreeIndexMoveto(pC->uc.pCursor, &r, &res); if( rc!=SQLITE_OK ){ goto abort_due_to_error; } @@ -87626,8 +101577,6 @@ case OP_SeekGT: { /* jump, in3, group */ goto seek_not_found; } } - pC->deferredMoveto = 0; - pC->cacheStatus = CACHE_STALE; #ifdef SQLITE_TEST sqlite3_search_count++; #endif @@ -87678,22 +101627,237 @@ case OP_SeekGT: { /* jump, in3, group */ break; } -/* Opcode: SeekHit P1 P2 * * * -** Synopsis: seekHit=P2 + +/* Opcode: SeekScan P1 P2 * * P5 +** Synopsis: Scan-ahead up to P1 rows +** +** This opcode is a prefix opcode to OP_SeekGE. In other words, this +** opcode must be immediately followed by OP_SeekGE. This constraint is +** checked by assert() statements. +** +** This opcode uses the P1 through P4 operands of the subsequent +** OP_SeekGE. In the text that follows, the operands of the subsequent +** OP_SeekGE opcode are denoted as SeekOP.P1 through SeekOP.P4. Only +** the P1, P2 and P5 operands of this opcode are also used, and are called +** This.P1, This.P2 and This.P5. +** +** This opcode helps to optimize IN operators on a multi-column index +** where the IN operator is on the later terms of the index by avoiding +** unnecessary seeks on the btree, substituting steps to the next row +** of the b-tree instead. A correct answer is obtained if this opcode +** is omitted or is a no-op. +** +** The SeekGE.P3 and SeekGE.P4 operands identify an unpacked key which +** is the desired entry that we want the cursor SeekGE.P1 to be pointing +** to. Call this SeekGE.P3/P4 row the "target". +** +** If the SeekGE.P1 cursor is not currently pointing to a valid row, +** then this opcode is a no-op and control passes through into the OP_SeekGE. +** +** If the SeekGE.P1 cursor is pointing to a valid row, then that row +** might be the target row, or it might be near and slightly before the +** target row, or it might be after the target row. If the cursor is +** currently before the target row, then this opcode attempts to position +** the cursor on or after the target row by invoking sqlite3BtreeStep() +** on the cursor between 1 and This.P1 times. +** +** The This.P5 parameter is a flag that indicates what to do if the +** cursor ends up pointing at a valid row that is past the target +** row. If This.P5 is false (0) then a jump is made to SeekGE.P2. If +** This.P5 is true (non-zero) then a jump is made to This.P2. The P5==0 +** case occurs when there are no inequality constraints to the right of +** the IN constraint. The jump to SeekGE.P2 ends the loop. The P5!=0 case +** occurs when there are inequality constraints to the right of the IN +** operator. In that case, the This.P2 will point either directly to or +** to setup code prior to the OP_IdxGT or OP_IdxGE opcode that checks for +** loop terminate. +** +** Possible outcomes from this opcode:
                    +** +**
                  1. If the cursor is initially not pointed to any valid row, then +** fall through into the subsequent OP_SeekGE opcode. +** +**
                  2. If the cursor is left pointing to a row that is before the target +** row, even after making as many as This.P1 calls to +** sqlite3BtreeNext(), then also fall through into OP_SeekGE. +** +**
                  3. If the cursor is left pointing at the target row, either because it +** was at the target row to begin with or because one or more +** sqlite3BtreeNext() calls moved the cursor to the target row, +** then jump to This.P2.., +** +**
                  4. If the cursor started out before the target row and a call to +** to sqlite3BtreeNext() moved the cursor off the end of the index +** (indicating that the target row definitely does not exist in the +** btree) then jump to SeekGE.P2, ending the loop. +** +**
                  5. If the cursor ends up on a valid row that is past the target row +** (indicating that the target row does not exist in the btree) then +** jump to SeekOP.P2 if This.P5==0 or to This.P2 if This.P5>0. +**
                  +*/ +case OP_SeekScan: { /* ncycle */ + VdbeCursor *pC; + int res; + int nStep; + UnpackedRecord r; + + assert( pOp[1].opcode==OP_SeekGE ); + + /* If pOp->p5 is clear, then pOp->p2 points to the first instruction past the + ** OP_IdxGT that follows the OP_SeekGE. Otherwise, it points to the first + ** opcode past the OP_SeekGE itself. */ + assert( pOp->p2>=(int)(pOp-aOp)+2 ); +#ifdef SQLITE_DEBUG + if( pOp->p5==0 ){ + /* There are no inequality constraints following the IN constraint. */ + assert( pOp[1].p1==aOp[pOp->p2-1].p1 ); + assert( pOp[1].p2==aOp[pOp->p2-1].p2 ); + assert( pOp[1].p3==aOp[pOp->p2-1].p3 ); + assert( aOp[pOp->p2-1].opcode==OP_IdxGT + || aOp[pOp->p2-1].opcode==OP_IdxGE ); + testcase( aOp[pOp->p2-1].opcode==OP_IdxGE ); + }else{ + /* There are inequality constraints. */ + assert( pOp->p2==(int)(pOp-aOp)+2 ); + assert( aOp[pOp->p2-1].opcode==OP_SeekGE ); + } +#endif + + assert( pOp->p1>0 ); + pC = p->apCsr[pOp[1].p1]; + assert( pC!=0 ); + assert( pC->eCurType==CURTYPE_BTREE ); + assert( !pC->isTable ); + if( !sqlite3BtreeCursorIsValidNN(pC->uc.pCursor) ){ +#ifdef SQLITE_DEBUG + if( db->flags&SQLITE_VdbeTrace ){ + printf("... cursor not valid - fall through\n"); + } +#endif + break; + } + nStep = pOp->p1; + assert( nStep>=1 ); + r.pKeyInfo = pC->pKeyInfo; + r.nField = (u16)pOp[1].p4.i; + r.default_rc = 0; + r.aMem = &aMem[pOp[1].p3]; +#ifdef SQLITE_DEBUG + { + int i; + for(i=0; i0 && pOp->p5==0 ){ + seekscan_search_fail: + /* Jump to SeekGE.P2, ending the loop */ +#ifdef SQLITE_DEBUG + if( db->flags&SQLITE_VdbeTrace ){ + printf("... %d steps and then skip\n", pOp->p1 - nStep); + } +#endif + VdbeBranchTaken(1,3); + pOp++; + goto jump_to_p2; + } + if( res>=0 ){ + /* Jump to This.P2, bypassing the OP_SeekGE opcode */ +#ifdef SQLITE_DEBUG + if( db->flags&SQLITE_VdbeTrace ){ + printf("... %d steps and then success\n", pOp->p1 - nStep); + } +#endif + VdbeBranchTaken(2,3); + goto jump_to_p2; + break; + } + if( nStep<=0 ){ +#ifdef SQLITE_DEBUG + if( db->flags&SQLITE_VdbeTrace ){ + printf("... fall through after %d steps\n", pOp->p1); + } +#endif + VdbeBranchTaken(0,3); + break; + } + nStep--; + pC->cacheStatus = CACHE_STALE; + rc = sqlite3BtreeNext(pC->uc.pCursor, 0); + if( rc ){ + if( rc==SQLITE_DONE ){ + rc = SQLITE_OK; + goto seekscan_search_fail; + }else{ + goto abort_due_to_error; + } + } + } + + break; +} + + +/* Opcode: SeekHit P1 P2 P3 * * +** Synopsis: set P2<=seekHit<=P3 +** +** Increase or decrease the seekHit value for cursor P1, if necessary, +** so that it is no less than P2 and no greater than P3. ** -** Set the seekHit flag on cursor P1 to the value in P2. -** The seekHit flag is used by the IfNoHope opcode. +** The seekHit integer represents the maximum of terms in an index for which +** there is known to be at least one match. If the seekHit value is smaller +** than the total number of equality terms in an index lookup, then the +** OP_IfNoHope opcode might run to see if the IN loop can be abandoned +** early, thus saving work. This is part of the IN-early-out optimization. ** -** P1 must be a valid b-tree cursor. P2 must be a boolean value, -** either 0 or 1. +** P1 must be a valid b-tree cursor. */ -case OP_SeekHit: { +case OP_SeekHit: { /* ncycle */ VdbeCursor *pC; assert( pOp->p1>=0 && pOp->p1nCursor ); pC = p->apCsr[pOp->p1]; assert( pC!=0 ); - assert( pOp->p2==0 || pOp->p2==1 ); - pC->seekHit = pOp->p2 & 1; + assert( pOp->p3>=pOp->p2 ); + if( pC->seekHitp2 ){ +#ifdef SQLITE_DEBUG + if( db->flags&SQLITE_VdbeTrace ){ + printf("seekHit changes from %d to %d\n", pC->seekHit, pOp->p2); + } +#endif + pC->seekHit = pOp->p2; + }else if( pC->seekHit>pOp->p3 ){ +#ifdef SQLITE_DEBUG + if( db->flags&SQLITE_VdbeTrace ){ + printf("seekHit changes from %d to %d\n", pC->seekHit, pOp->p3); + } +#endif + pC->seekHit = pOp->p3; + } + break; +} + +/* Opcode: IfNotOpen P1 P2 * * * +** Synopsis: if( !csr[P1] ) goto P2 +** +** If cursor P1 is not open or if P1 is set to a NULL row using the +** OP_NullRow opcode, then jump to instruction P2. Otherwise, fall through. +*/ +case OP_IfNotOpen: { /* jump */ + VdbeCursor *pCur; + + assert( pOp->p1>=0 && pOp->p1nCursor ); + pCur = p->apCsr[pOp->p1]; + VdbeBranchTaken(pCur==0 || pCur->nullRow, 2); + if( pCur==0 || pCur->nullRow ){ + goto jump_to_p2_and_check_for_interrupt; + } break; } @@ -87720,9 +101884,9 @@ case OP_SeekHit: { ** If P4==0 then register P3 holds a blob constructed by MakeRecord. If ** P4>0 then register P3 is the first of P4 registers that form an unpacked ** record. -** +** ** Cursor P1 is on an index btree. If the record identified by P3 and P4 -** is not the prefix of any entry in P1 then a jump is made to P2. If P1 +** is not the prefix of any entry in P1 then a jump is made to P2. If P1 ** does contain an entry whose prefix matches the P3/P4 record then control ** falls through to the next instruction and P1 is left pointing at the ** matching entry. @@ -87737,16 +101901,20 @@ case OP_SeekHit: { ** Synopsis: key=r[P3@P4] ** ** Register P3 is the first of P4 registers that form an unpacked -** record. +** record. Cursor P1 is an index btree. P2 is a jump destination. +** In other words, the operands to this opcode are the same as the +** operands to OP_NotFound and OP_IdxGT. ** -** Cursor P1 is on an index btree. If the seekHit flag is set on P1, then -** this opcode is a no-op. But if the seekHit flag of P1 is clear, then -** check to see if there is any entry in P1 that matches the -** prefix identified by P3 and P4. If no entry matches the prefix, -** jump to P2. Otherwise fall through. +** This opcode is an optimization attempt only. If this opcode always +** falls through, the correct answer is still obtained, but extra work +** is performed. ** -** This opcode behaves like OP_NotFound if the seekHit -** flag is clear and it behaves like OP_Noop if the seekHit flag is set. +** A value of N in the seekHit flag of cursor P1 means that there exists +** a key P3:N that will match some record in the index. We want to know +** if it is possible for a record P3:P4 to match some record in the +** index. If it is not possible, we can skip some work. So if seekHit +** is less than P4, attempt to find out if a match is possible by running +** OP_NotFound. ** ** This opcode is used in IN clause processing for a multi-column key. ** If an IN clause is attached to an element of the key other than the @@ -87766,7 +101934,7 @@ case OP_SeekHit: { ** If P4==0 then register P3 holds a blob constructed by MakeRecord. If ** P4>0 then register P3 is the first of P4 registers that form an unpacked ** record. -** +** ** Cursor P1 is on an index btree. If the record identified by P3 and P4 ** contains any NULL value, jump immediately to P2. If all terms of the ** record are not-NULL then a check is done to determine if any row in the @@ -87783,23 +101951,26 @@ case OP_SeekHit: { ** ** See also: NotFound, Found, NotExists */ -case OP_IfNoHope: { /* jump, in3 */ +case OP_IfNoHope: { /* jump, in3, ncycle */ VdbeCursor *pC; assert( pOp->p1>=0 && pOp->p1nCursor ); pC = p->apCsr[pOp->p1]; assert( pC!=0 ); - if( pC->seekHit ) break; +#ifdef SQLITE_DEBUG + if( db->flags&SQLITE_VdbeTrace ){ + printf("seekHit is %d\n", pC->seekHit); + } +#endif + if( pC->seekHit>=pOp->p4.i ) break; /* Fall through into OP_NotFound */ + /* no break */ deliberate_fall_through } -case OP_NoConflict: /* jump, in3 */ -case OP_NotFound: /* jump, in3 */ -case OP_Found: { /* jump, in3 */ +case OP_NoConflict: /* jump, in3, ncycle */ +case OP_NotFound: /* jump, in3, ncycle */ +case OP_Found: { /* jump, in3, ncycle */ int alreadyExists; - int takeJump; int ii; VdbeCursor *pC; - int res; - UnpackedRecord *pFree; UnpackedRecord *pIdxKey; UnpackedRecord r; @@ -87814,52 +101985,42 @@ case OP_Found: { /* jump, in3 */ #ifdef SQLITE_DEBUG pC->seekOp = pOp->opcode; #endif - pIn3 = &aMem[pOp->p3]; + r.aMem = &aMem[pOp->p3]; assert( pC->eCurType==CURTYPE_BTREE ); assert( pC->uc.pCursor!=0 ); assert( pC->isTable==0 ); - if( pOp->p4.i>0 ){ + r.nField = (u16)pOp->p4.i; + if( r.nField>0 ){ + /* Key values in an array of registers */ r.pKeyInfo = pC->pKeyInfo; - r.nField = (u16)pOp->p4.i; - r.aMem = pIn3; + r.default_rc = 0; #ifdef SQLITE_DEBUG + (void)sqlite3FaultSim(50); /* For use by --counter in TH3 */ for(ii=0; iip3+ii, &r.aMem[ii]); } #endif - pIdxKey = &r; - pFree = 0; + rc = sqlite3BtreeIndexMoveto(pC->uc.pCursor, &r, &pC->seekResult); }else{ - assert( pIn3->flags & MEM_Blob ); - rc = ExpandBlob(pIn3); + /* Composite key generated by OP_MakeRecord */ + assert( r.aMem->flags & MEM_Blob ); + assert( pOp->opcode!=OP_NoConflict ); + rc = ExpandBlob(r.aMem); assert( rc==SQLITE_OK || rc==SQLITE_NOMEM ); if( rc ) goto no_mem; - pFree = pIdxKey = sqlite3VdbeAllocUnpackedRecord(pC->pKeyInfo); + pIdxKey = sqlite3VdbeAllocUnpackedRecord(pC->pKeyInfo); if( pIdxKey==0 ) goto no_mem; - sqlite3VdbeRecordUnpack(pC->pKeyInfo, pIn3->n, pIn3->z, pIdxKey); + sqlite3VdbeRecordUnpack(r.aMem->n, r.aMem->z, pIdxKey); + pIdxKey->default_rc = 0; + rc = sqlite3BtreeIndexMoveto(pC->uc.pCursor, pIdxKey, &pC->seekResult); + sqlite3DbFreeNN(db, pIdxKey); } - pIdxKey->default_rc = 0; - takeJump = 0; - if( pOp->opcode==OP_NoConflict ){ - /* For the OP_NoConflict opcode, take the jump if any of the - ** input fields are NULL, since any key with a NULL will not - ** conflict */ - for(ii=0; iinField; ii++){ - if( pIdxKey->aMem[ii].flags & MEM_Null ){ - takeJump = 1; - break; - } - } - } - rc = sqlite3BtreeMovetoUnpacked(pC->uc.pCursor, pIdxKey, 0, 0, &res); - if( pFree ) sqlite3DbFreeNN(db, pFree); if( rc!=SQLITE_OK ){ goto abort_due_to_error; } - pC->seekResult = res; - alreadyExists = (res==0); + alreadyExists = (pC->seekResult==0); pC->nullRow = 1-alreadyExists; pC->deferredMoveto = 0; pC->cacheStatus = CACHE_STALE; @@ -87867,8 +102028,25 @@ case OP_Found: { /* jump, in3 */ VdbeBranchTaken(alreadyExists!=0,2); if( alreadyExists ) goto jump_to_p2; }else{ - VdbeBranchTaken(takeJump||alreadyExists==0,2); - if( takeJump || !alreadyExists ) goto jump_to_p2; + if( !alreadyExists ){ + VdbeBranchTaken(1,2); + goto jump_to_p2; + } + if( pOp->opcode==OP_NoConflict ){ + /* For the OP_NoConflict opcode, take the jump if any of the + ** input fields are NULL, since any key with a NULL will not + ** conflict */ + for(ii=0; iiopcode==OP_IfNoHope ){ + pC->seekHit = pOp->p4.i; + } } break; } @@ -87878,9 +102056,9 @@ case OP_Found: { /* jump, in3 */ ** ** P1 is the index of a cursor open on an SQL table btree (with integer ** keys). If register P3 does not contain an integer or if P1 does not -** contain a record with rowid P3 then jump immediately to P2. +** contain a record with rowid P3 then jump immediately to P2. ** Or, if P2 is 0, raise an SQLITE_CORRUPT error. If P1 does contain -** a record with rowid P3 then +** a record with rowid P3 then ** leave the cursor pointing at that record and fall through to the next ** instruction. ** @@ -87903,7 +102081,7 @@ case OP_Found: { /* jump, in3 */ ** P1 is the index of a cursor open on an SQL table btree (with integer ** keys). P3 is an integer rowid. If P1 does not contain a record with ** rowid P3 then jump immediately to P2. Or, if P2 is 0, raise an -** SQLITE_CORRUPT error. If P1 does contain a record with rowid P3 then +** SQLITE_CORRUPT error. If P1 does contain a record with rowid P3 then ** leave the cursor pointing at that record and fall through to the next ** instruction. ** @@ -87920,30 +102098,37 @@ case OP_Found: { /* jump, in3 */ ** ** See also: Found, NotFound, NoConflict, SeekRowid */ -case OP_SeekRowid: { /* jump, in3 */ +case OP_SeekRowid: { /* jump0, in3, ncycle */ VdbeCursor *pC; BtCursor *pCrsr; int res; u64 iKey; pIn3 = &aMem[pOp->p3]; - if( (pIn3->flags & MEM_Int)==0 ){ - /* Make sure pIn3->u.i contains a valid integer representation of - ** the key value, but do not change the datatype of the register, as - ** other parts of the perpared statement might be depending on the - ** current datatype. */ - u16 origFlags = pIn3->flags; - int isNotInt; - applyAffinity(pIn3, SQLITE_AFF_NUMERIC, encoding); - isNotInt = (pIn3->flags & MEM_Int)==0; - pIn3->flags = origFlags; - if( isNotInt ) goto jump_to_p2; + testcase( pIn3->flags & MEM_Int ); + testcase( pIn3->flags & MEM_IntReal ); + testcase( pIn3->flags & MEM_Real ); + testcase( (pIn3->flags & (MEM_Str|MEM_Int))==MEM_Str ); + if( (pIn3->flags & (MEM_Int|MEM_IntReal))==0 ){ + /* If pIn3->u.i does not contain an integer, compute iKey as the + ** integer value of pIn3. Jump to P2 if pIn3 cannot be converted + ** into an integer without loss of information. Take care to avoid + ** changing the datatype of pIn3, however, as it is used by other + ** parts of the prepared statement. */ + Mem x = pIn3[0]; + applyAffinity(&x, SQLITE_AFF_NUMERIC, encoding); + if( (x.flags & MEM_Int)==0 ) goto jump_to_p2; + iKey = x.u.i; + goto notExistsWithKey; } /* Fall through into OP_NotExists */ -case OP_NotExists: /* jump, in3 */ + /* no break */ deliberate_fall_through +case OP_NotExists: /* jump, in3, ncycle */ pIn3 = &aMem[pOp->p3]; assert( (pIn3->flags & MEM_Int)!=0 || pOp->opcode==OP_SeekRowid ); assert( pOp->p1>=0 && pOp->p1nCursor ); + iKey = pIn3->u.i; +notExistsWithKey: pC = p->apCsr[pOp->p1]; assert( pC!=0 ); #ifdef SQLITE_DEBUG @@ -87954,8 +102139,7 @@ case OP_NotExists: /* jump, in3 */ pCrsr = pC->uc.pCursor; assert( pCrsr!=0 ); res = 0; - iKey = pIn3->u.i; - rc = sqlite3BtreeMovetoUnpacked(pCrsr, 0, iKey, 0, &res); + rc = sqlite3BtreeTableMoveto(pCrsr, iKey, 0, &res); assert( rc==SQLITE_OK || res==0 ); pC->movetoTarget = iKey; /* Used by OP_Delete */ pC->nullRow = 0; @@ -87981,7 +102165,7 @@ case OP_NotExists: /* jump, in3 */ ** Find the next available sequence number for cursor P1. ** Write the sequence number into register P2. ** The sequence number on the cursor is incremented after this -** instruction. +** instruction. */ case OP_Sequence: { /* out2 */ assert( pOp->p1>=0 && pOp->p1nCursor ); @@ -88001,9 +102185,9 @@ case OP_Sequence: { /* out2 */ ** table that cursor P1 points to. The new record number is written ** written to register P2. ** -** If P3>0 then P3 is a register in the root frame of this VDBE that holds +** If P3>0 then P3 is a register in the root frame of this VDBE that holds ** the largest previously generated record number. No new record numbers are -** allowed to be less than this value. When this value reaches its maximum, +** allowed to be less than this value. When this value reaches its maximum, ** an SQLITE_FULL error is generated. The P3 register is updated with the ' ** generated record number. This P3 mechanism is used to help implement the ** AUTOINCREMENT feature. @@ -88013,8 +102197,10 @@ case OP_NewRowid: { /* out2 */ VdbeCursor *pC; /* Cursor of table to get the new rowid */ int res; /* Result of an sqlite3BtreeLast() */ int cnt; /* Counter to limit the number of searches */ +#ifndef SQLITE_OMIT_AUTOINCREMENT Mem *pMem; /* Register holding largest rowid for AUTOINCREMENT */ VdbeFrame *pFrame; /* Root frame of VDBE */ +#endif v = 0; res = 0; @@ -88110,7 +102296,7 @@ case OP_NewRowid: { /* out2 */ do{ sqlite3_randomness(sizeof(v), &v); v &= (MAX_ROWID>>1); v++; /* Ensure that v is greater than zero */ - }while( ((rc = sqlite3BtreeMovetoUnpacked(pC->uc.pCursor, 0, (u64)v, + }while( ((rc = sqlite3BtreeTableMoveto(pC->uc.pCursor, (u64)v, 0, &res))==SQLITE_OK) && (res==0) && (++cnt<100)); @@ -88152,8 +102338,8 @@ case OP_NewRowid: { /* out2 */ ** is part of an INSERT operation. The difference is only important to ** the update hook. ** -** Parameter P4 may point to a Table structure, or may be NULL. If it is -** not NULL, then the update-hook (sqlite3.xUpdateCallback) is invoked +** Parameter P4 may point to a Table structure, or may be NULL. If it is +** not NULL, then the update-hook (sqlite3.xUpdateCallback) is invoked ** following a successful insert. ** ** (WARNING/TODO: If P1 is a pseudo-cursor and P2 is dynamically @@ -88180,6 +102366,7 @@ case OP_Insert: { pC = p->apCsr[pOp->p1]; assert( pC!=0 ); assert( pC->eCurType==CURTYPE_BTREE ); + assert( pC->deferredMoveto==0 ); assert( pC->uc.pCursor!=0 ); assert( (pOp->p5 & OPFLAG_ISNOOP) || pC->isTable ); assert( pOp->p4type==P4_TABLE || pOp->p4type>=P4_STATIC ); @@ -88199,14 +102386,14 @@ case OP_Insert: { assert( (pOp->p5 & OPFLAG_ISNOOP) || HasRowid(pTab) ); }else{ pTab = 0; - zDb = 0; /* Not needed. Silence a compiler warning. */ + zDb = 0; } #ifdef SQLITE_ENABLE_PREUPDATE_HOOK /* Invoke the pre-update hook, if any */ if( pTab ){ if( db->xPreUpdateCallback && !(pOp->p5 & OPFLAG_ISUPDATE) ){ - sqlite3VdbePreUpdateHook(p, pC, SQLITE_INSERT, zDb, pTab, x.nKey,pOp->p2); + sqlite3VdbePreUpdateHook(p,pC,SQLITE_INSERT,zDb,pTab,x.nKey,pOp->p2,-1); } if( db->xUpdateCallback==0 || pTab->aCol==0 ){ /* Prevent post-update hook from running in cases when it should not */ @@ -88216,9 +102403,12 @@ case OP_Insert: { if( pOp->p5 & OPFLAG_ISNOOP ) break; #endif - if( pOp->p5 & OPFLAG_NCHANGE ) p->nChange++; - if( pOp->p5 & OPFLAG_LASTROWID ) db->lastRowid = x.nKey; - assert( pData->flags & (MEM_Blob|MEM_Str) ); + assert( (pOp->p5 & OPFLAG_LASTROWID)==0 || (pOp->p5 & OPFLAG_NCHANGE)!=0 ); + if( pOp->p5 & OPFLAG_NCHANGE ){ + p->nChange++; + if( pOp->p5 & OPFLAG_LASTROWID ) db->lastRowid = x.nKey; + } + assert( (pData->flags & (MEM_Blob|MEM_Str))!=0 || pData->n==0 ); x.pData = pData->z; x.nData = pData->n; seekResult = ((pOp->p5 & OPFLAG_USESEEKRESULT) ? pC->seekResult : 0); @@ -88228,11 +102418,14 @@ case OP_Insert: { x.nZero = 0; } x.pKey = 0; + assert( BTREE_PREFORMAT==OPFLAG_PREFORMAT ); rc = sqlite3BtreeInsert(pC->uc.pCursor, &x, - (pOp->p5 & (OPFLAG_APPEND|OPFLAG_SAVEPOSITION)), seekResult + (pOp->p5 & (OPFLAG_APPEND|OPFLAG_SAVEPOSITION|OPFLAG_PREFORMAT)), + seekResult ); pC->deferredMoveto = 0; pC->cacheStatus = CACHE_STALE; + colCacheCtr++; /* Invoke the update-hook if required. */ if( rc ) goto abort_due_to_error; @@ -88246,6 +102439,33 @@ case OP_Insert: { break; } +/* Opcode: RowCell P1 P2 P3 * * +** +** P1 and P2 are both open cursors. Both must be opened on the same type +** of table - intkey or index. This opcode is used as part of copying +** the current row from P2 into P1. If the cursors are opened on intkey +** tables, register P3 contains the rowid to use with the new record in +** P1. If they are opened on index tables, P3 is not used. +** +** This opcode must be followed by either an Insert or InsertIdx opcode +** with the OPFLAG_PREFORMAT flag set to complete the insert operation. +*/ +case OP_RowCell: { + VdbeCursor *pDest; /* Cursor to write to */ + VdbeCursor *pSrc; /* Cursor to read from */ + i64 iKey; /* Rowid value to insert with */ + assert( pOp[1].opcode==OP_Insert || pOp[1].opcode==OP_IdxInsert ); + assert( pOp[1].opcode==OP_Insert || pOp->p3==0 ); + assert( pOp[1].opcode==OP_IdxInsert || pOp->p3>0 ); + assert( pOp[1].p5 & OPFLAG_PREFORMAT ); + pDest = p->apCsr[pOp->p1]; + pSrc = p->apCsr[pOp->p2]; + iKey = pOp->p3 ? aMem[pOp->p3].u.i : 0; + rc = sqlite3BtreeTransferRow(pDest->uc.pCursor, pSrc->uc.pCursor, iKey); + if( rc!=SQLITE_OK ) goto abort_due_to_error; + break; +}; + /* Opcode: Delete P1 P2 P3 P4 P5 ** ** Delete the record at which the P1 cursor is currently pointing. @@ -88254,27 +102474,32 @@ case OP_Insert: { ** the cursor will be left pointing at either the next or the previous ** record in the table. If it is left pointing at the next record, then ** the next Next instruction will be a no-op. As a result, in this case -** it is ok to delete a record from within a Next loop. If +** it is ok to delete a record from within a Next loop. If ** OPFLAG_SAVEPOSITION bit of P5 is clear, then the cursor will be ** left in an undefined state. ** ** If the OPFLAG_AUXDELETE bit is set on P5, that indicates that this -** delete one of several associated with deleting a table row and all its -** associated index entries. Exactly one of those deletes is the "primary" -** delete. The others are all on OPFLAG_FORDELETE cursors or else are -** marked with the AUXDELETE flag. +** delete is one of several associated with deleting a table row and +** all its associated index entries. Exactly one of those deletes is +** the "primary" delete. The others are all on OPFLAG_FORDELETE +** cursors or else are marked with the AUXDELETE flag. +** +** If the OPFLAG_NCHANGE (0x01) flag of P2 (NB: P2 not P5) is set, then +** the row change count is incremented (otherwise not). ** -** If the OPFLAG_NCHANGE flag of P2 (NB: P2 not P5) is set, then the row -** change count is incremented (otherwise not). +** If the OPFLAG_ISNOOP (0x40) flag of P2 (not P5!) is set, then the +** pre-update-hook for deletes is run, but the btree is otherwise unchanged. +** This happens when the OP_Delete is to be shortly followed by an OP_Insert +** with the same key, causing the btree entry to be overwritten. ** ** P1 must not be pseudo-table. It has to be a real table with ** multiple rows. ** -** If P4 is not NULL then it points to a Table object. In this case either +** If P4 is not NULL then it points to a Table object. In this case either ** the update or pre-update hook, or both, may be invoked. The P1 cursor must -** have been positioned using OP_NotFound prior to invoking this opcode in -** this case. Specifically, if one is configured, the pre-update hook is -** invoked if P4 is not NULL. The update-hook is invoked if one is configured, +** have been positioned using OP_NotFound prior to invoking this opcode in +** this case. Specifically, if one is configured, the pre-update hook is +** invoked if P4 is not NULL. The update-hook is invoked if one is configured, ** P4 is not NULL, and the OPFLAG_NCHANGE flag is set in P2. ** ** If the OPFLAG_ISUPDATE flag is set in P2, then P3 contains the address @@ -88297,19 +102522,23 @@ case OP_Delete: { sqlite3VdbeIncrWriteCounter(p, pC); #ifdef SQLITE_DEBUG - if( pOp->p4type==P4_TABLE && HasRowid(pOp->p4.pTab) && pOp->p5==0 ){ + if( pOp->p4type==P4_TABLE + && HasRowid(pOp->p4.pTab) + && pOp->p5==0 + && sqlite3BtreeCursorIsValidNN(pC->uc.pCursor) + ){ /* If p5 is zero, the seek operation that positioned the cursor prior to ** OP_Delete will have also set the pC->movetoTarget field to the rowid of ** the row that is being deleted */ i64 iKey = sqlite3BtreeIntegerKey(pC->uc.pCursor); - assert( pC->movetoTarget==iKey ); + assert( CORRUPT_DB || pC->movetoTarget==iKey ); } #endif /* If the update-hook or pre-update-hook will be invoked, set zDb to ** the name of the db to pass as to it. Also set local pTab to a copy ** of p4.pTab. Finally, if p5 is true, indicating that this cursor was - ** last moved with OP_Next or OP_Prev, not Seek or NotFound, set + ** last moved with OP_Next or OP_Prev, not Seek or NotFound, set ** VdbeCursor.movetoTarget to the current rowid. */ if( pOp->p4type==P4_TABLE && HAS_UPDATE_HOOK(db) ){ assert( pC->iDb>=0 ); @@ -88320,27 +102549,28 @@ case OP_Delete: { pC->movetoTarget = sqlite3BtreeIntegerKey(pC->uc.pCursor); } }else{ - zDb = 0; /* Not needed. Silence a compiler warning. */ - pTab = 0; /* Not needed. Silence a compiler warning. */ + zDb = 0; + pTab = 0; } #ifdef SQLITE_ENABLE_PREUPDATE_HOOK /* Invoke the pre-update-hook if required. */ - if( db->xPreUpdateCallback && pOp->p4.pTab ){ - assert( !(opflags & OPFLAG_ISUPDATE) - || HasRowid(pTab)==0 - || (aMem[pOp->p3].flags & MEM_Int) + assert( db->xPreUpdateCallback==0 || pTab==pOp->p4.pTab ); + if( db->xPreUpdateCallback && pTab ){ + assert( !(opflags & OPFLAG_ISUPDATE) + || HasRowid(pTab)==0 + || (aMem[pOp->p3].flags & MEM_Int) ); sqlite3VdbePreUpdateHook(p, pC, - (opflags & OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_DELETE, + (opflags & OPFLAG_ISUPDATE) ? SQLITE_UPDATE : SQLITE_DELETE, zDb, pTab, pC->movetoTarget, - pOp->p3 + pOp->p3, -1 ); } if( opflags & OPFLAG_ISNOOP ) break; #endif - - /* Only flags that can be set are SAVEPOISTION and AUXDELETE */ + + /* Only flags that can be set are SAVEPOISTION and AUXDELETE */ assert( (pOp->p5 & ~(OPFLAG_SAVEPOSITION|OPFLAG_AUXDELETE))==0 ); assert( OPFLAG_SAVEPOSITION==BTREE_SAVEPOSITION ); assert( OPFLAG_AUXDELETE==BTREE_AUXDELETE ); @@ -88361,13 +102591,14 @@ case OP_Delete: { rc = sqlite3BtreeDelete(pC->uc.pCursor, pOp->p5); pC->cacheStatus = CACHE_STALE; + colCacheCtr++; pC->seekResult = 0; if( rc ) goto abort_due_to_error; /* Invoke the update-hook if required. */ if( opflags & OPFLAG_NCHANGE ){ p->nChange++; - if( db->xUpdateCallback && HasRowid(pTab) ){ + if( db->xUpdateCallback && ALWAYS(pTab!=0) && HasRowid(pTab) ){ db->xUpdateCallback(db->pUpdateArg, SQLITE_DELETE, zDb, pTab->zName, pC->movetoTarget); assert( pC->iDb>=0 ); @@ -88393,7 +102624,7 @@ case OP_ResetCount: { ** Synopsis: if key(P1)!=trim(r[P3],P4) goto P2 ** ** P1 is a sorter cursor. This instruction compares a prefix of the -** record blob in register P3 against a prefix of the entry that +** record blob in register P3 against a prefix of the entry that ** the sorter cursor currently points to. Only the first P4 fields ** of r[P3] and the sorter record are compared. ** @@ -88428,13 +102659,13 @@ case OP_SorterCompare: { ** Write into register P2 the current sorter data for sorter cursor P1. ** Then clear the column header cache on cursor P3. ** -** This opcode is normally use to move a record out of the sorter and into +** This opcode is normally used to move a record out of the sorter and into ** a register that is the source for a pseudo-table cursor created using ** OpenPseudo. That pseudo-table cursor is the one that is identified by ** parameter P3. Clearing the P3 column cache as part of this opcode saves ** us from having to issue a separate NullRow instruction to clear that cache. */ -case OP_SorterData: { +case OP_SorterData: { /* ncycle */ VdbeCursor *pC; pOut = &aMem[pOp->p2]; @@ -88451,10 +102682,10 @@ case OP_SorterData: { /* Opcode: RowData P1 P2 P3 * * ** Synopsis: r[P2]=data ** -** Write into register P2 the complete row content for the row at +** Write into register P2 the complete row content for the row at ** which cursor P1 is currently pointing. -** There is no interpretation of the data. -** It is just copied onto the P2 register exactly as +** There is no interpretation of the data. +** It is just copied onto the P2 register exactly as ** it is found in the database file. ** ** If cursor P1 is an index, then the content is the key of the row. @@ -88495,24 +102726,20 @@ case OP_RowData: { /* The OP_RowData opcodes always follow OP_NotExists or ** OP_SeekRowid or OP_Rewind/Op_Next with no intervening instructions ** that might invalidate the cursor. - ** If this where not the case, on of the following assert()s + ** If this were not the case, one of the following assert()s ** would fail. Should this ever change (because of changes in the code ** generator) then the fix would be to insert a call to ** sqlite3VdbeCursorMoveto(). */ assert( pC->deferredMoveto==0 ); assert( sqlite3BtreeCursorIsValid(pCrsr) ); -#if 0 /* Not required due to the previous to assert() statements */ - rc = sqlite3VdbeCursorMoveto(pC); - if( rc!=SQLITE_OK ) goto abort_due_to_error; -#endif n = sqlite3BtreePayloadSize(pCrsr); if( n>(u32)db->aLimit[SQLITE_LIMIT_LENGTH] ){ goto too_big; } testcase( n==0 ); - rc = sqlite3VdbeMemFromBtree(pCrsr, 0, n, pOut); + rc = sqlite3VdbeMemFromBtreeZeroOffset(pCrsr, n, pOut); if( rc ) goto abort_due_to_error; if( !pOp->p3 ) Deephemeralize(pOut); UPDATE_MAX_BLOBSIZE(pOut); @@ -88521,7 +102748,7 @@ case OP_RowData: { } /* Opcode: Rowid P1 P2 * * * -** Synopsis: r[P2]=rowid +** Synopsis: r[P2]=PX rowid of P1 ** ** Store in register P2 an integer which is the key of the table entry that ** P1 is currently point to. @@ -88530,7 +102757,7 @@ case OP_RowData: { ** be a separate OP_VRowid opcode for use with virtual tables, but this ** one opcode now works for both table types. */ -case OP_Rowid: { /* out2 */ +case OP_Rowid: { /* out2, ncycle */ VdbeCursor *pC; i64 v; sqlite3_vtab *pVtab; @@ -88576,13 +102803,25 @@ case OP_Rowid: { /* out2 */ ** Move the cursor P1 to a null row. Any OP_Column operations ** that occur while the cursor is on the null row will always ** write a NULL. +** +** If cursor P1 is not previously opened, open it now to a special +** pseudo-cursor that always returns NULL for every column. */ case OP_NullRow: { VdbeCursor *pC; assert( pOp->p1>=0 && pOp->p1nCursor ); pC = p->apCsr[pOp->p1]; - assert( pC!=0 ); + if( pC==0 ){ + /* If the cursor is not already open, create a special kind of + ** pseudo-cursor that always gives null rows. */ + pC = allocateCursor(p, pOp->p1, 1, CURTYPE_PSEUDO); + if( pC==0 ) goto no_mem; + pC->seekResult = 0; + pC->isTable = 1; + pC->noReuse = 1; + pC->uc.pCursor = sqlite3BtreeFakeValidCursor(); + } pC->nullRow = 1; pC->cacheStatus = CACHE_STALE; if( pC->eCurType==CURTYPE_BTREE ){ @@ -88607,7 +102846,7 @@ case OP_NullRow: { */ /* Opcode: Last P1 P2 * * * ** -** The next use of the Rowid or Column or Prev instruction for P1 +** The next use of the Rowid or Column or Prev instruction for P1 ** will refer to the last entry in the database table or index. ** If the table or index is empty and P2>0, then jump immediately to P2. ** If P2 is 0 or if the table or index is not empty, fall through @@ -88617,8 +102856,8 @@ case OP_NullRow: { ** from the end toward the beginning. In other words, the cursor is ** configured to use Prev, not Next. */ -case OP_SeekEnd: -case OP_Last: { /* jump */ +case OP_SeekEnd: /* ncycle */ +case OP_Last: { /* jump0, ncycle */ VdbeCursor *pC; BtCursor *pCrsr; int res; @@ -88652,28 +102891,38 @@ case OP_Last: { /* jump */ break; } -/* Opcode: IfSmaller P1 P2 P3 * * +/* Opcode: IfSizeBetween P1 P2 P3 P4 * ** -** Estimate the number of rows in the table P1. Jump to P2 if that -** estimate is less than approximately 2**(0.1*P3). +** Let N be the approximate number of rows in the table or index +** with cursor P1 and let X be 10*log2(N) if N is positive or -1 +** if N is zero. +** +** Jump to P2 if X is in between P3 and P4, inclusive. */ -case OP_IfSmaller: { /* jump */ +case OP_IfSizeBetween: { /* jump */ VdbeCursor *pC; BtCursor *pCrsr; int res; i64 sz; assert( pOp->p1>=0 && pOp->p1nCursor ); + assert( pOp->p4type==P4_INT32 ); + assert( pOp->p3>=-1 && pOp->p3<=640*2 ); + assert( pOp->p4.i>=-1 && pOp->p4.i<=640*2 ); pC = p->apCsr[pOp->p1]; assert( pC!=0 ); pCrsr = pC->uc.pCursor; assert( pCrsr ); rc = sqlite3BtreeFirst(pCrsr, &res); if( rc ) goto abort_due_to_error; - if( res==0 ){ + if( res!=0 ){ + sz = -1; /* -Infinity encoding */ + }else{ sz = sqlite3BtreeRowCountEst(pCrsr); - if( ALWAYS(sz>=0) && sqlite3LogEst((u64)sz)p3 ) res = 1; + assert( sz>0 ); + sz = sqlite3LogEst((u64)sz); } + res = sz>=pOp->p3 && sz<=pOp->p4.i; VdbeBranchTaken(res!=0,2); if( res ) goto jump_to_p2; break; @@ -88701,34 +102950,40 @@ case OP_IfSmaller: { /* jump */ ** regression tests can determine whether or not the optimizer is ** correctly optimizing out sorts. */ -case OP_SorterSort: /* jump */ -case OP_Sort: { /* jump */ +case OP_SorterSort: /* jump ncycle */ +case OP_Sort: { /* jump ncycle */ #ifdef SQLITE_TEST sqlite3_sort_count++; sqlite3_search_count--; #endif p->aCounter[SQLITE_STMTSTATUS_SORT]++; /* Fall through into OP_Rewind */ + /* no break */ deliberate_fall_through } /* Opcode: Rewind P1 P2 * * * ** -** The next use of the Rowid or Column or Next instruction for P1 +** The next use of the Rowid or Column or Next instruction for P1 ** will refer to the first entry in the database table or index. ** If the table or index is empty, jump immediately to P2. -** If the table or index is not empty, fall through to the following +** If the table or index is not empty, fall through to the following ** instruction. ** +** If P2 is zero, that is an assertion that the P1 table is never +** empty and hence the jump will never be taken. +** ** This opcode leaves the cursor configured to move in forward order, ** from the beginning toward the end. In other words, the cursor is ** configured to use Next, not Prev. */ -case OP_Rewind: { /* jump */ +case OP_Rewind: { /* jump0, ncycle */ VdbeCursor *pC; BtCursor *pCrsr; int res; assert( pOp->p1>=0 && pOp->p1nCursor ); assert( pOp->p5==0 ); + assert( pOp->p2>=0 && pOp->p2nOp ); + pC = p->apCsr[pOp->p1]; assert( pC!=0 ); assert( isSorter(pC)==(pOp->opcode==OP_SorterSort) ); @@ -88748,13 +103003,40 @@ case OP_Rewind: { /* jump */ } if( rc ) goto abort_due_to_error; pC->nullRow = (u8)res; - assert( pOp->p2>0 && pOp->p2nOp ); + if( pOp->p2>0 ){ + VdbeBranchTaken(res!=0,2); + if( res ) goto jump_to_p2; + } + break; +} + +/* Opcode: IfEmpty P1 P2 * * * +** Synopsis: if( empty(P1) ) goto P2 +** +** Check to see if the b-tree table that cursor P1 references is empty +** and jump to P2 if it is. +*/ +case OP_IfEmpty: { /* jump */ + VdbeCursor *pC; + BtCursor *pCrsr; + int res; + + assert( pOp->p1>=0 && pOp->p1nCursor ); + assert( pOp->p2>=0 && pOp->p2nOp ); + + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); + assert( pC->eCurType==CURTYPE_BTREE ); + pCrsr = pC->uc.pCursor; + assert( pCrsr ); + rc = sqlite3BtreeIsEmpty(pCrsr, &res); + if( rc ) goto abort_due_to_error; VdbeBranchTaken(res!=0,2); if( res ) goto jump_to_p2; break; } -/* Opcode: Next P1 P2 P3 P4 P5 +/* Opcode: Next P1 P2 P3 * P5 ** ** Advance cursor P1 so that it points to the next key/data pair in its ** table or index. If there are no more key/value pairs then fall through @@ -88773,15 +103055,12 @@ case OP_Rewind: { /* jump */ ** omitted if that index had been unique. P3 is usually 0. P3 is ** always either 0 or 1. ** -** P4 is always of type P4_ADVANCE. The function pointer points to -** sqlite3BtreeNext(). -** ** If P5 is positive and the jump is taken, then event counter ** number P5-1 in the prepared statement is incremented. ** ** See also: Prev */ -/* Opcode: Prev P1 P2 P3 P4 P5 +/* Opcode: Prev P1 P2 P3 * P5 ** ** Back up cursor P1 so that it points to the previous key/data pair in its ** table or index. If there is no previous key/value pairs then fall through @@ -88801,9 +103080,6 @@ case OP_Rewind: { /* jump */ ** omitted if that index had been unique. P3 is usually 0. P3 is ** always either 0 or 1. ** -** P4 is always of type P4_ADVANCE. The function pointer points to -** sqlite3BtreePrevious(). -** ** If P5 is positive and the jump is taken, then event counter ** number P5-1 in the prepared statement is incremented. */ @@ -88821,29 +103097,37 @@ case OP_SorterNext: { /* jump */ assert( isSorter(pC) ); rc = sqlite3VdbeSorterNext(db, pC); goto next_tail; -case OP_Prev: /* jump */ -case OP_Next: /* jump */ + +case OP_Prev: /* jump, ncycle */ assert( pOp->p1>=0 && pOp->p1nCursor ); - assert( pOp->p5aCounter) ); + assert( pOp->p5==0 + || pOp->p5==SQLITE_STMTSTATUS_FULLSCAN_STEP + || pOp->p5==SQLITE_STMTSTATUS_AUTOINDEX); pC = p->apCsr[pOp->p1]; assert( pC!=0 ); assert( pC->deferredMoveto==0 ); assert( pC->eCurType==CURTYPE_BTREE ); - assert( pOp->opcode!=OP_Next || pOp->p4.xAdvance==sqlite3BtreeNext ); - assert( pOp->opcode!=OP_Prev || pOp->p4.xAdvance==sqlite3BtreePrevious ); - - /* The Next opcode is only used after SeekGT, SeekGE, Rewind, and Found. - ** The Prev opcode is only used after SeekLT, SeekLE, and Last. */ - assert( pOp->opcode!=OP_Next - || pC->seekOp==OP_SeekGT || pC->seekOp==OP_SeekGE - || pC->seekOp==OP_Rewind || pC->seekOp==OP_Found - || pC->seekOp==OP_NullRow|| pC->seekOp==OP_SeekRowid); - assert( pOp->opcode!=OP_Prev - || pC->seekOp==OP_SeekLT || pC->seekOp==OP_SeekLE - || pC->seekOp==OP_Last + assert( pC->seekOp==OP_SeekLT || pC->seekOp==OP_SeekLE + || pC->seekOp==OP_Last || pC->seekOp==OP_IfNoHope || pC->seekOp==OP_NullRow); + rc = sqlite3BtreePrevious(pC->uc.pCursor, pOp->p3); + goto next_tail; + +case OP_Next: /* jump, ncycle */ + assert( pOp->p1>=0 && pOp->p1nCursor ); + assert( pOp->p5==0 + || pOp->p5==SQLITE_STMTSTATUS_FULLSCAN_STEP + || pOp->p5==SQLITE_STMTSTATUS_AUTOINDEX); + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); + assert( pC->deferredMoveto==0 ); + assert( pC->eCurType==CURTYPE_BTREE ); + assert( pC->seekOp==OP_SeekGT || pC->seekOp==OP_SeekGE + || pC->seekOp==OP_Rewind || pC->seekOp==OP_Found + || pC->seekOp==OP_NullRow|| pC->seekOp==OP_SeekRowid + || pC->seekOp==OP_IfNoHope); + rc = sqlite3BtreeNext(pC->uc.pCursor, pOp->p3); - rc = pOp->p4.xAdvance(pC->uc.pCursor, pOp->p3); next_tail: pC->cacheStatus = CACHE_STALE; VdbeBranchTaken(rc==SQLITE_OK,2); @@ -88884,11 +103168,41 @@ case OP_Next: /* jump */ ** run faster by avoiding an unnecessary seek on cursor P1. However, ** the OPFLAG_USESEEKRESULT flag must only be set if there have been no prior ** seeks on the cursor or if the most recent seek used a key equivalent -** to P2. +** to P2. ** ** This instruction only works for indices. The equivalent instruction ** for tables is OP_Insert. */ +case OP_IdxInsert: { /* in2 */ + VdbeCursor *pC; + BtreePayload x; + + assert( pOp->p1>=0 && pOp->p1nCursor ); + pC = p->apCsr[pOp->p1]; + sqlite3VdbeIncrWriteCounter(p, pC); + assert( pC!=0 ); + assert( !isSorter(pC) ); + pIn2 = &aMem[pOp->p2]; + assert( (pIn2->flags & MEM_Blob) || (pOp->p5 & OPFLAG_PREFORMAT) ); + if( pOp->p5 & OPFLAG_NCHANGE ) p->nChange++; + assert( pC->eCurType==CURTYPE_BTREE ); + assert( pC->isTable==0 ); + rc = ExpandBlob(pIn2); + if( rc ) goto abort_due_to_error; + x.nKey = pIn2->n; + x.pKey = pIn2->z; + x.aMem = aMem + pOp->p3; + x.nMem = (u16)pOp->p4.i; + rc = sqlite3BtreeInsert(pC->uc.pCursor, &x, + (pOp->p5 & (OPFLAG_APPEND|OPFLAG_SAVEPOSITION|OPFLAG_PREFORMAT)), + ((pOp->p5 & OPFLAG_USESEEKRESULT) ? pC->seekResult : 0) + ); + assert( pC->deferredMoveto==0 ); + pC->cacheStatus = CACHE_STALE; + if( rc) goto abort_due_to_error; + break; +} + /* Opcode: SorterInsert P1 P2 * * * ** Synopsis: key=r[P2] ** @@ -88896,47 +103210,35 @@ case OP_Next: /* jump */ ** MakeRecord instructions. This opcode writes that key ** into the sorter P1. Data for the entry is nil. */ -case OP_SorterInsert: /* in2 */ -case OP_IdxInsert: { /* in2 */ +case OP_SorterInsert: { /* in2 */ VdbeCursor *pC; - BtreePayload x; assert( pOp->p1>=0 && pOp->p1nCursor ); pC = p->apCsr[pOp->p1]; sqlite3VdbeIncrWriteCounter(p, pC); assert( pC!=0 ); - assert( isSorter(pC)==(pOp->opcode==OP_SorterInsert) ); + assert( isSorter(pC) ); pIn2 = &aMem[pOp->p2]; assert( pIn2->flags & MEM_Blob ); - if( pOp->p5 & OPFLAG_NCHANGE ) p->nChange++; - assert( pC->eCurType==CURTYPE_BTREE || pOp->opcode==OP_SorterInsert ); assert( pC->isTable==0 ); rc = ExpandBlob(pIn2); if( rc ) goto abort_due_to_error; - if( pOp->opcode==OP_SorterInsert ){ - rc = sqlite3VdbeSorterWrite(pC, pIn2); - }else{ - x.nKey = pIn2->n; - x.pKey = pIn2->z; - x.aMem = aMem + pOp->p3; - x.nMem = (u16)pOp->p4.i; - rc = sqlite3BtreeInsert(pC->uc.pCursor, &x, - (pOp->p5 & (OPFLAG_APPEND|OPFLAG_SAVEPOSITION)), - ((pOp->p5 & OPFLAG_USESEEKRESULT) ? pC->seekResult : 0) - ); - assert( pC->deferredMoveto==0 ); - pC->cacheStatus = CACHE_STALE; - } + rc = sqlite3VdbeSorterWrite(pC, pIn2); if( rc) goto abort_due_to_error; break; } -/* Opcode: IdxDelete P1 P2 P3 * * +/* Opcode: IdxDelete P1 P2 P3 P4 * ** Synopsis: key=r[P2@P3] ** ** The content of P3 registers starting at register P2 form -** an unpacked index key. This opcode removes that entry from the +** an unpacked index key. This opcode removes that entry from the ** index opened by cursor P1. +** +** P4 is a pointer to an Index structure. +** +** Raise an SQLITE_CORRUPT_INDEX error if no matching index entry is found +** and not in writable_schema mode. */ case OP_IdxDelete: { VdbeCursor *pC; @@ -88953,17 +103255,28 @@ case OP_IdxDelete: { sqlite3VdbeIncrWriteCounter(p, pC); pCrsr = pC->uc.pCursor; assert( pCrsr!=0 ); - assert( pOp->p5==0 ); r.pKeyInfo = pC->pKeyInfo; r.nField = (u16)pOp->p3; r.default_rc = 0; r.aMem = &aMem[pOp->p2]; - rc = sqlite3BtreeMovetoUnpacked(pCrsr, &r, 0, 0, &res); + rc = sqlite3BtreeIndexMoveto(pCrsr, &r, &res); if( rc ) goto abort_due_to_error; - if( res==0 ){ - rc = sqlite3BtreeDelete(pCrsr, BTREE_AUXDELETE); - if( rc ) goto abort_due_to_error; + if( res!=0 ){ + rc = sqlite3VdbeFindIndexKey(pCrsr, pOp->p4.pIdx, &r, &res, 0); + if( rc!=SQLITE_OK ) goto abort_due_to_error; + if( res!=0 ){ + if( !sqlite3WritableSchema(db) ){ + rc = sqlite3ReportError( + SQLITE_CORRUPT_INDEX, __LINE__, "index corruption"); + goto abort_due_to_error; + } + pC->cacheStatus = CACHE_STALE; + pC->seekResult = 0; + break; + } } + rc = sqlite3BtreeDelete(pCrsr, BTREE_AUXDELETE); + if( rc ) goto abort_due_to_error; assert( pC->deferredMoveto==0 ); pC->cacheStatus = CACHE_STALE; pC->seekResult = 0; @@ -88983,8 +103296,8 @@ case OP_IdxDelete: { ** ** P4 may be an array of integers (type P4_INTARRAY) containing ** one entry for each column in the P3 table. If array entry a(i) -** is non-zero, then reading column a(i)-1 from cursor P3 is -** equivalent to performing the deferred seek and then reading column i +** is non-zero, then reading column a(i)-1 from cursor P3 is +** equivalent to performing the deferred seek and then reading column i ** from P1. This information is stored in P3 and used to redirect ** reads against P3 over to P1, thus possibly avoiding the need to ** seek and read cursor P3. @@ -88998,8 +103311,8 @@ case OP_IdxDelete: { ** ** See also: Rowid, MakeRecord. */ -case OP_DeferredSeek: -case OP_IdxRowid: { /* out2 */ +case OP_DeferredSeek: /* ncycle */ +case OP_IdxRowid: { /* out2, ncycle */ VdbeCursor *pC; /* The P1 index cursor */ VdbeCursor *pTabCur; /* The P2 table cursor (OP_DeferredSeek only) */ i64 rowid; /* Rowid that P1 current points to */ @@ -89007,9 +103320,9 @@ case OP_IdxRowid: { /* out2 */ assert( pOp->p1>=0 && pOp->p1nCursor ); pC = p->apCsr[pOp->p1]; assert( pC!=0 ); - assert( pC->eCurType==CURTYPE_BTREE ); + assert( pC->eCurType==CURTYPE_BTREE || IsNullCursor(pC) ); assert( pC->uc.pCursor!=0 ); - assert( pC->isTable==0 ); + assert( pC->isTable==0 || IsNullCursor(pC) ); assert( pC->deferredMoveto==0 ); assert( !pC->nullRow || pOp->opcode==OP_IdxRowid ); @@ -89017,10 +103330,10 @@ case OP_IdxRowid: { /* out2 */ ** of sqlite3VdbeCursorRestore() and sqlite3VdbeIdxRowid(). */ rc = sqlite3VdbeCursorRestore(pC); - /* sqlite3VbeCursorRestore() can only fail if the record has been deleted - ** out from under the cursor. That will never happens for an IdxRowid - ** or Seek opcode */ - if( NEVER(rc!=SQLITE_OK) ) goto abort_due_to_error; + /* sqlite3VdbeCursorRestore() may fail if the cursor has been disturbed + ** since it was last positioned and an error (e.g. OOM or an IO error) + ** occurs while trying to reposition it. */ + if( rc!=SQLITE_OK ) goto abort_due_to_error; if( !pC->nullRow ){ rowid = 0; /* Not needed. Only used to silence a warning. */ @@ -89038,8 +103351,11 @@ case OP_IdxRowid: { /* out2 */ pTabCur->nullRow = 0; pTabCur->movetoTarget = rowid; pTabCur->deferredMoveto = 1; + pTabCur->cacheStatus = CACHE_STALE; assert( pOp->p4type==P4_INTARRAY || pOp->p4.ai==0 ); - pTabCur->aAltMap = pOp->p4.ai; + assert( !pTabCur->isEphemeral ); + pTabCur->ub.aAltMap = pOp->p4.ai; + assert( !pC->isEphemeral ); pTabCur->pAltCursor = pC; }else{ pOut = out2Prerelease(p, pOp); @@ -89052,32 +103368,50 @@ case OP_IdxRowid: { /* out2 */ break; } -/* Opcode: IdxGE P1 P2 P3 P4 P5 +/* Opcode: FinishSeek P1 * * * * +** +** If cursor P1 was previously moved via OP_DeferredSeek, complete that +** seek operation now, without further delay. If the cursor seek has +** already occurred, this instruction is a no-op. +*/ +case OP_FinishSeek: { /* ncycle */ + VdbeCursor *pC; /* The P1 index cursor */ + + assert( pOp->p1>=0 && pOp->p1nCursor ); + pC = p->apCsr[pOp->p1]; + if( pC->deferredMoveto ){ + rc = sqlite3VdbeFinishMoveto(pC); + if( rc ) goto abort_due_to_error; + } + break; +} + +/* Opcode: IdxGE P1 P2 P3 P4 * ** Synopsis: key=r[P3@P4] ** -** The P4 register values beginning with P3 form an unpacked index -** key that omits the PRIMARY KEY. Compare this key value against the index -** that P1 is currently pointing to, ignoring the PRIMARY KEY or ROWID +** The P4 register values beginning with P3 form an unpacked index +** key that omits the PRIMARY KEY. Compare this key value against the index +** that P1 is currently pointing to, ignoring the PRIMARY KEY or ROWID ** fields at the end. ** ** If the P1 index entry is greater than or equal to the key value ** then jump to P2. Otherwise fall through to the next instruction. */ -/* Opcode: IdxGT P1 P2 P3 P4 P5 +/* Opcode: IdxGT P1 P2 P3 P4 * ** Synopsis: key=r[P3@P4] ** -** The P4 register values beginning with P3 form an unpacked index -** key that omits the PRIMARY KEY. Compare this key value against the index -** that P1 is currently pointing to, ignoring the PRIMARY KEY or ROWID +** The P4 register values beginning with P3 form an unpacked index +** key that omits the PRIMARY KEY. Compare this key value against the index +** that P1 is currently pointing to, ignoring the PRIMARY KEY or ROWID ** fields at the end. ** ** If the P1 index entry is greater than the key value ** then jump to P2. Otherwise fall through to the next instruction. */ -/* Opcode: IdxLT P1 P2 P3 P4 P5 +/* Opcode: IdxLT P1 P2 P3 P4 * ** Synopsis: key=r[P3@P4] ** -** The P4 register values beginning with P3 form an unpacked index +** The P4 register values beginning with P3 form an unpacked index ** key that omits the PRIMARY KEY or ROWID. Compare this key value against ** the index that P1 is currently pointing to, ignoring the PRIMARY KEY or ** ROWID on the P1 index. @@ -89085,10 +103419,10 @@ case OP_IdxRowid: { /* out2 */ ** If the P1 index entry is less than the key value then jump to P2. ** Otherwise fall through to the next instruction. */ -/* Opcode: IdxLE P1 P2 P3 P4 P5 +/* Opcode: IdxLE P1 P2 P3 P4 * ** Synopsis: key=r[P3@P4] ** -** The P4 register values beginning with P3 form an unpacked index +** The P4 register values beginning with P3 form an unpacked index ** key that omits the PRIMARY KEY or ROWID. Compare this key value against ** the index that P1 is currently pointing to, ignoring the PRIMARY KEY or ** ROWID on the P1 index. @@ -89096,10 +103430,10 @@ case OP_IdxRowid: { /* out2 */ ** If the P1 index entry is less than or equal to the key value then jump ** to P2. Otherwise fall through to the next instruction. */ -case OP_IdxLE: /* jump */ -case OP_IdxGT: /* jump */ -case OP_IdxLT: /* jump */ -case OP_IdxGE: { /* jump */ +case OP_IdxLE: /* jump, ncycle */ +case OP_IdxGT: /* jump, ncycle */ +case OP_IdxLT: /* jump, ncycle */ +case OP_IdxGE: { /* jump, ncycle */ VdbeCursor *pC; int res; UnpackedRecord r; @@ -89111,7 +103445,6 @@ case OP_IdxGE: { /* jump */ assert( pC->eCurType==CURTYPE_BTREE ); assert( pC->uc.pCursor!=0); assert( pC->deferredMoveto==0 ); - assert( pOp->p5==0 || pOp->p5==1 ); assert( pOp->p4type==P4_INT32 ); r.pKeyInfo = pC->pKeyInfo; r.nField = (u16)pOp->p4.i; @@ -89132,8 +103465,31 @@ case OP_IdxGE: { /* jump */ } } #endif - res = 0; /* Not needed. Only used to silence a warning. */ - rc = sqlite3VdbeIdxKeyCompare(db, pC, &r, &res); + + /* Inlined version of sqlite3VdbeIdxKeyCompare() */ + { + i64 nCellKey = 0; + BtCursor *pCur; + Mem m; + + assert( pC->eCurType==CURTYPE_BTREE ); + pCur = pC->uc.pCursor; + assert( sqlite3BtreeCursorIsValid(pCur) ); + nCellKey = sqlite3BtreePayloadSize(pCur); + /* nCellKey will always be between 0 and 0xffffffff because of the way + ** that btreeParseCellPtr() and sqlite3GetVarint32() are implemented */ + if( nCellKey<=0 || nCellKey>0x7fffffff ){ + rc = SQLITE_CORRUPT_BKPT; + goto abort_due_to_error; + } + sqlite3VdbeMemInit(&m, db, 0); + rc = sqlite3VdbeMemFromBtreeZeroOffset(pCur, (u32)nCellKey, &m); + if( rc ) goto abort_due_to_error; + res = sqlite3VdbeRecordCompareWithSkip(m.n, m.z, &r, 0); + sqlite3VdbeMemReleaseMalloc(&m); + } + /* End of inlined sqlite3VdbeIdxKeyCompare() */ + assert( (OP_IdxLE&1)==(OP_IdxLT&1) && (OP_IdxGE&1)==(OP_IdxGT&1) ); if( (pOp->opcode&1)==(OP_IdxLT&1) ){ assert( pOp->opcode==OP_IdxLE || pOp->opcode==OP_IdxLT ); @@ -89143,7 +103499,7 @@ case OP_IdxGE: { /* jump */ res++; } VdbeBranchTaken(res>0,2); - if( rc ) goto abort_due_to_error; + assert( rc==SQLITE_OK ); if( res>0 ) goto jump_to_p2; break; } @@ -89154,7 +103510,7 @@ case OP_IdxGE: { /* jump */ ** file is given by P1. ** ** The table being destroyed is in the main database file if P3==0. If -** P3==1 then the table to be clear is in the auxiliary database file +** P3==1 then the table to be destroyed is in the auxiliary database file ** that is used to store tables create using CREATE TEMPORARY TABLE. ** ** If AUTOVACUUM is enabled then it is possible that another root page @@ -89162,15 +103518,15 @@ case OP_IdxGE: { /* jump */ ** root pages contiguous at the beginning of the database. The former ** value of the root page that moved - its value before the move occurred - ** is stored in register P2. If no page movement was required (because the -** table being dropped was already the last one in the database) then a -** zero is stored in register P2. If AUTOVACUUM is disabled then a zero +** table being dropped was already the last one in the database) then a +** zero is stored in register P2. If AUTOVACUUM is disabled then a zero ** is stored in register P2. ** ** This opcode throws an error if there are any active reader VMs when -** it is invoked. This is done to avoid the difficulty associated with -** updating existing cursors when a root page is moved in an AUTOVACUUM -** database. This error is thrown even if the database is not an AUTOVACUUM -** db in order to avoid introducing an incompatibility between autovacuum +** it is invoked. This is done to avoid the difficulty associated with +** updating existing cursors when a root page is moved in an AUTOVACUUM +** database. This error is thrown even if the database is not an AUTOVACUUM +** db in order to avoid introducing an incompatibility between autovacuum ** and non-autovacuum modes. ** ** See also: Clear @@ -89214,28 +103570,25 @@ case OP_Destroy: { /* out2 */ ** in the database file is given by P1. But, unlike Destroy, do not ** remove the table or index from the database file. ** -** The table being clear is in the main database file if P2==0. If -** P2==1 then the table to be clear is in the auxiliary database file +** The table being cleared is in the main database file if P2==0. If +** P2==1 then the table to be cleared is in the auxiliary database file ** that is used to store tables create using CREATE TEMPORARY TABLE. ** -** If the P3 value is non-zero, then the table referred to must be an -** intkey table (an SQL table, not an index). In this case the row change -** count is incremented by the number of rows in the table being cleared. -** If P3 is greater than zero, then the value stored in register P3 is -** also incremented by the number of rows in the table being cleared. +** If the P3 value is non-zero, then the row change count is incremented +** by the number of rows in the table being cleared. If P3 is greater +** than zero, then the value stored in register P3 is also incremented +** by the number of rows in the table being cleared. ** ** See also: Destroy */ case OP_Clear: { - int nChange; - + i64 nChange; + sqlite3VdbeIncrWriteCounter(p, 0); nChange = 0; assert( p->readOnly==0 ); assert( DbMaskTest(p->btreeMask, pOp->p2) ); - rc = sqlite3BtreeClearTable( - db->aDb[pOp->p2].pBt, pOp->p1, (pOp->p3 ? &nChange : 0) - ); + rc = sqlite3BtreeClearTable(db->aDb[pOp->p2].pBt, (u32)pOp->p1, &nChange); if( pOp->p3 ){ p->nChange += nChange; if( pOp->p3>0 ){ @@ -89258,7 +103611,7 @@ case OP_Clear: { */ case OP_ResetSorter: { VdbeCursor *pC; - + assert( pOp->p1>=0 && pOp->p1nCursor ); pC = p->apCsr[pOp->p1]; assert( pC!=0 ); @@ -89283,7 +103636,7 @@ case OP_ResetSorter: { ** The root page number of the new b-tree is stored in register P2. */ case OP_CreateBtree: { /* out2 */ - int pgno; + Pgno pgno; Db *pDb; sqlite3VdbeIncrWriteCounter(p, 0); @@ -89301,22 +103654,63 @@ case OP_CreateBtree: { /* out2 */ break; } -/* Opcode: SqlExec * * * P4 * +/* Opcode: SqlExec P1 P2 * P4 * ** ** Run the SQL statement or statements specified in the P4 string. +** +** The P1 parameter is a bitmask of options: +** +** 0x0001 Disable Auth and Trace callbacks while the statements +** in P4 are running. +** +** 0x0002 Set db->nAnalysisLimit to P2 while the statements in +** P4 are running. +** */ case OP_SqlExec: { + char *zErr; +#ifndef SQLITE_OMIT_AUTHORIZATION + sqlite3_xauth xAuth; +#endif + u8 mTrace; + int savedAnalysisLimit; + sqlite3VdbeIncrWriteCounter(p, 0); db->nSqlExec++; - rc = sqlite3_exec(db, pOp->p4.z, 0, 0, 0); + zErr = 0; +#ifndef SQLITE_OMIT_AUTHORIZATION + xAuth = db->xAuth; +#endif + mTrace = db->mTrace; + savedAnalysisLimit = db->nAnalysisLimit; + if( pOp->p1 & 0x0001 ){ +#ifndef SQLITE_OMIT_AUTHORIZATION + db->xAuth = 0; +#endif + db->mTrace = 0; + } + if( pOp->p1 & 0x0002 ){ + db->nAnalysisLimit = pOp->p2; + } + rc = sqlite3_exec(db, pOp->p4.z, 0, 0, &zErr); db->nSqlExec--; - if( rc ) goto abort_due_to_error; +#ifndef SQLITE_OMIT_AUTHORIZATION + db->xAuth = xAuth; +#endif + db->mTrace = mTrace; + db->nAnalysisLimit = savedAnalysisLimit; + if( zErr || rc ){ + sqlite3VdbeError(p, "%s", zErr); + sqlite3_free(zErr); + if( rc==SQLITE_NOMEM ) goto no_mem; + goto abort_due_to_error; + } break; } /* Opcode: ParseSchema P1 * * P4 * ** -** Read and parse all entries from the SQLITE_MASTER table of database P1 +** Read and parse all entries from the schema table of database P1 ** that match the WHERE clause P4. If P4 is a NULL pointer, then the ** entire schema for P1 is reparsed. ** @@ -89325,12 +103719,12 @@ case OP_SqlExec: { */ case OP_ParseSchema: { int iDb; - const char *zMaster; + const char *zSchema; char *zSql; InitData initData; /* Any prepared statement that invokes this opcode will hold mutexes - ** on every btree. This is a prerequisite for invoking + ** on every btree. This is a prerequisite for invoking ** sqlite3InitCallback(). */ #ifdef SQLITE_DEBUG @@ -89341,26 +103735,29 @@ case OP_ParseSchema: { iDb = pOp->p1; assert( iDb>=0 && iDbnDb ); - assert( DbHasProperty(db, iDb, DB_SchemaLoaded) ); + assert( DbHasProperty(db, iDb, DB_SchemaLoaded) + || db->mallocFailed + || (CORRUPT_DB && (db->flags & SQLITE_NoSchemaError)!=0) ); #ifndef SQLITE_OMIT_ALTERTABLE if( pOp->p4.z==0 ){ sqlite3SchemaClear(db->aDb[iDb].pSchema); db->mDbFlags &= ~DBFLAG_SchemaKnownOk; - rc = sqlite3InitOne(db, iDb, &p->zErrMsg, INITFLAG_AlterTable); + rc = sqlite3InitOne(db, iDb, &p->zErrMsg, pOp->p5); db->mDbFlags |= DBFLAG_SchemaChange; p->expired = 0; }else #endif { - zMaster = MASTER_NAME; + zSchema = LEGACY_SCHEMA_TABLE; initData.db = db; initData.iDb = iDb; initData.pzErrMsg = &p->zErrMsg; initData.mInitFlags = 0; + initData.mxPage = sqlite3BtreeLastPage(db->aDb[iDb].pBt); zSql = sqlite3MPrintf(db, - "SELECT name, rootpage, sql FROM '%q'.%s WHERE %s ORDER BY rowid", - db->aDb[iDb].zDbSName, zMaster, pOp->p4.z); + "SELECT*FROM\"%w\".%s WHERE %s ORDER BY rowid", + db->aDb[iDb].zDbSName, zSchema, pOp->p4.z); if( zSql==0 ){ rc = SQLITE_NOMEM_BKPT; }else{ @@ -89374,7 +103771,7 @@ case OP_ParseSchema: { if( rc==SQLITE_OK && initData.nInitRow==0 ){ /* The OP_ParseSchema opcode with a non-NULL P4 argument should parse ** at least one SQL statement. Any less than that indicates that - ** the sqlite_master table is corrupt. */ + ** the sqlite_schema table is corrupt. */ rc = SQLITE_CORRUPT_BKPT; } sqlite3DbFreeNN(db, zSql); @@ -89388,7 +103785,7 @@ case OP_ParseSchema: { } goto abort_due_to_error; } - break; + break; } #if !defined(SQLITE_OMIT_ANALYZE) @@ -89402,7 +103799,7 @@ case OP_LoadAnalysis: { assert( pOp->p1>=0 && pOp->p1nDb ); rc = sqlite3AnalysisLoad(db, pOp->p1); if( rc ) goto abort_due_to_error; - break; + break; } #endif /* !defined(SQLITE_OMIT_ANALYZE) */ @@ -89410,7 +103807,7 @@ case OP_LoadAnalysis: { ** ** Remove the internal (in-memory) data structures that describe ** the table named P4 in database P1. This is called after a table -** is dropped from disk (using the Destroy opcode) in order to keep +** is dropped from disk (using the Destroy opcode) in order to keep ** the internal representation of the ** schema consistent with what is on disk. */ @@ -89438,7 +103835,7 @@ case OP_DropIndex: { ** ** Remove the internal (in-memory) data structures that describe ** the trigger named P4 in database P1. This is called after a trigger -** is dropped from disk (using the Destroy opcode) in order to keep +** is dropped from disk (using the Destroy opcode) in order to keep ** the internal representation of the ** schema consistent with what is on disk. */ @@ -89453,12 +103850,12 @@ case OP_DropTrigger: { /* Opcode: IntegrityCk P1 P2 P3 P4 P5 ** ** Do an analysis of the currently open database. Store in -** register P1 the text of an error message describing any problems. -** If no problems are found, store a NULL in register P1. +** register (P1+1) the text of an error message describing any problems. +** If no problems are found, store a NULL in register (P1+1). ** -** The register P3 contains one less than the maximum number of allowed errors. -** At most reg(P3) errors will be reported. -** In other words, the analysis stops as soon as reg(P1) errors are +** The register (P1) contains one less than the maximum number of allowed +** errors. At most reg(P1) errors will be reported. +** In other words, the analysis stops as soon as reg(P1) errors are ** seen. Reg(P1) is updated with the number of errors remaining. ** ** The root page numbers of all tables in the database are integers @@ -89471,38 +103868,93 @@ case OP_DropTrigger: { */ case OP_IntegrityCk: { int nRoot; /* Number of tables to check. (Number of root pages.) */ - int *aRoot; /* Array of rootpage numbers for tables to be checked */ + Pgno *aRoot; /* Array of rootpage numbers for tables to be checked */ int nErr; /* Number of errors reported */ char *z; /* Text of the error report */ Mem *pnErr; /* Register keeping track of errors remaining */ assert( p->bIsReader ); + assert( pOp->p4type==P4_INTARRAY ); nRoot = pOp->p2; aRoot = pOp->p4.ai; assert( nRoot>0 ); - assert( aRoot[0]==nRoot ); - assert( pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor) ); - pnErr = &aMem[pOp->p3]; + assert( aRoot!=0 ); + assert( aRoot[0]==(Pgno)nRoot ); + assert( pOp->p1>0 && (pOp->p1+1)<=(p->nMem+1 - p->nCursor) ); + pnErr = &aMem[pOp->p1]; assert( (pnErr->flags & MEM_Int)!=0 ); assert( (pnErr->flags & (MEM_Str|MEM_Blob))==0 ); - pIn1 = &aMem[pOp->p1]; + pIn1 = &aMem[pOp->p1+1]; assert( pOp->p5nDb ); assert( DbMaskTest(p->btreeMask, pOp->p5) ); - z = sqlite3BtreeIntegrityCheck(db->aDb[pOp->p5].pBt, &aRoot[1], nRoot, - (int)pnErr->u.i+1, &nErr); + rc = sqlite3BtreeIntegrityCheck(db, db->aDb[pOp->p5].pBt, &aRoot[1], + &aMem[pOp->p3], nRoot, (int)pnErr->u.i+1, &nErr, &z); sqlite3VdbeMemSetNull(pIn1); if( nErr==0 ){ assert( z==0 ); - }else if( z==0 ){ - goto no_mem; + }else if( rc ){ + sqlite3_free(z); + goto abort_due_to_error; }else{ pnErr->u.i -= nErr-1; sqlite3VdbeMemSetStr(pIn1, z, -1, SQLITE_UTF8, sqlite3_free); } UPDATE_MAX_BLOBSIZE(pIn1); sqlite3VdbeChangeEncoding(pIn1, encoding); - break; + goto check_for_interrupt; } + +/* Opcode: IFindKey P1 P2 P3 P4 * +** +** This instruction always follows an OP_Found with the same P1, P2 and P3 +** values as this instruction and a non-zero P4 value. The P4 value to +** this opcode is of type P4_INDEX and contains a pointer to the Index +** object of for the index being searched. +** +** This opcode uses sqlite3VdbeFindIndexKey() to search around the current +** cursor location for an index key that exactly matches all fields that +** are not indexed expressions or references to VIRTUAL generated columns, +** and either exactly match or are real numbers that are within 2 ULPs of +** each other if the don't match. +** +** To put it another way, this opcode looks for nearby index entries that +** are very close to the search key, but which might have small differences +** in floating-point values that come via an expression. +** +** If no nearby alternative entry is found in cursor P1, then jump to P2. +** But if a close match is found, fall through. +** +** This opcode is used by PRAGMA integrity_check to help distinguish +** between truely corrupt indexes and expression indexes that are holding +** floating-point values that are off by one or two ULPs. +*/ +case OP_IFindKey: { /* jump, in3 */ + VdbeCursor *pC; + int res; + UnpackedRecord r; + + assert( pOp[-1].opcode==OP_Found ); + assert( pOp[-1].p1==pOp->p1 ); + assert( pOp[-1].p3==pOp->p3 ); + pC = p->apCsr[pOp->p1]; + assert( pOp->p4type==P4_INDEX ); + assert( pC->eCurType==CURTYPE_BTREE ); + assert( pC->uc.pCursor!=0 ); + assert( pC->isTable==0 ); + + memset(&r, 0, sizeof(r)); + r.aMem = &aMem[pOp->p3]; + r.nField = pOp->p4.pIdx->nColumn; + r.pKeyInfo = pC->pKeyInfo; + + rc = sqlite3VdbeFindIndexKey(pC->uc.pCursor, pOp->p4.pIdx, &r, &res, 1); + if( rc || res!=0 ){ + rc = SQLITE_OK; + goto jump_to_p2; + } + pC->nullRow = 0; + break; +}; #endif /* SQLITE_OMIT_INTEGRITY_CHECK */ /* Opcode: RowSetAdd P1 P2 * * * @@ -89538,7 +103990,7 @@ case OP_RowSetRead: { /* jump, in1, out3 */ pIn1 = &aMem[pOp->p1]; assert( (pIn1->flags & MEM_Blob)==0 || sqlite3VdbeMemIsRowSet(pIn1) ); - if( (pIn1->flags & MEM_Blob)==0 + if( (pIn1->flags & MEM_Blob)==0 || sqlite3RowSetNext((RowSet*)pIn1->z, &val)==0 ){ /* The boolean index is empty */ @@ -89610,22 +104062,24 @@ case OP_RowSetTest: { /* jump, in1, in3 */ /* Opcode: Program P1 P2 P3 P4 P5 ** -** Execute the trigger program passed as P4 (type P4_SUBPROGRAM). +** Execute the trigger program passed as P4 (type P4_SUBPROGRAM). ** -** P1 contains the address of the memory cell that contains the first memory -** cell in an array of values used as arguments to the sub-program. P2 -** contains the address to jump to if the sub-program throws an IGNORE -** exception using the RAISE() function. Register P3 contains the address -** of a memory cell in this (the parent) VM that is used to allocate the +** P1 contains the address of the memory cell that contains the first memory +** cell in an array of values used as arguments to the sub-program. P2 +** contains the address to jump to if the sub-program throws an IGNORE +** exception using the RAISE() function. P2 might be zero, if there is +** no possibility that an IGNORE exception will be raised. +** Register P3 contains the address +** of a memory cell in this (the parent) VM that is used to allocate the ** memory required by the sub-vdbe at runtime. ** ** P4 is a pointer to the VM containing the trigger program. ** ** If P5 is non-zero, then recursive program invocation is enabled. */ -case OP_Program: { /* jump */ +case OP_Program: { /* jump0 */ int nMem; /* Number of memory registers for sub-program */ - int nByte; /* Bytes of runtime space required for sub-program */ + i64 nByte; /* Bytes of runtime space required for sub-program */ Mem *pRt; /* Register to allocate runtime space */ Mem *pMem; /* Used to iterate through memory cells */ Mem *pEnd; /* Last memory cell in new array */ @@ -89636,17 +104090,17 @@ case OP_Program: { /* jump */ pProgram = pOp->p4.pProgram; pRt = &aMem[pOp->p3]; assert( pProgram->nOp>0 ); - - /* If the p5 flag is clear, then recursive invocation of triggers is + + /* If the p5 flag is clear, then recursive invocation of triggers is ** disabled for backwards compatibility (p5 is set if this sub-program ** is really a trigger, not a foreign key action, and the flag set ** and cleared by the "PRAGMA recursive_triggers" command is clear). - ** - ** It is recursive invocation of triggers, at the SQL level, that is - ** disabled. In some cases a single trigger may generate more than one - ** SubProgram (if the trigger may be executed with more than one different + ** + ** It is recursive invocation of triggers, at the SQL level, that is + ** disabled. In some cases a single trigger may generate more than one + ** SubProgram (if the trigger may be executed with more than one different ** ON CONFLICT algorithm). SubProgram structures associated with a - ** single trigger all have the same value for the SubProgram.token + ** single trigger all have the same value for the SubProgram.token ** variable. */ if( pOp->p5 ){ t = pProgram->token; @@ -89662,10 +104116,10 @@ case OP_Program: { /* jump */ /* Register pRt is used to store the memory required to save the state ** of the current program, and the memory required at runtime to execute - ** the trigger program. If this trigger has been fired before, then pRt + ** the trigger program. If this trigger has been fired before, then pRt ** is already allocated. Otherwise, it must be initialized. */ if( (pRt->flags&MEM_Blob)==0 ){ - /* SubProgram.nMem is set to the number of memory cells used by the + /* SubProgram.nMem is set to the number of memory cells used by the ** program stored in SubProgram.aOp. As well as these, one memory ** cell is required for each cursor used by the program. Set local ** variable nMem (and later, VdbeFrame.nChildMem) to this value. @@ -89676,7 +104130,7 @@ case OP_Program: { /* jump */ nByte = ROUND8(sizeof(VdbeFrame)) + nMem * sizeof(Mem) + pProgram->nCsr * sizeof(VdbeCursor*) - + (pProgram->nOp + 7)/8; + + (7 + (i64)pProgram->nOp)/8; pFrame = sqlite3DbMallocZero(db, nByte); if( !pFrame ){ goto no_mem; @@ -89684,7 +104138,7 @@ case OP_Program: { /* jump */ sqlite3VdbeMemRelease(pRt); pRt->flags = MEM_Blob|MEM_Dyn; pRt->z = (char*)pFrame; - pRt->n = nByte; + pRt->n = (int)nByte; pRt->xDel = sqlite3VdbeFrameMemDel; pFrame->v = p; @@ -89698,9 +104152,6 @@ case OP_Program: { /* jump */ pFrame->aOp = p->aOp; pFrame->nOp = p->nOp; pFrame->token = pProgram->token; -#ifdef SQLITE_ENABLE_STMT_SCANSTATUS - pFrame->anExec = p->anExec; -#endif #ifdef SQLITE_DEBUG pFrame->iFrameMagic = SQLITE_FRAME_MAGIC; #endif @@ -89713,7 +104164,7 @@ case OP_Program: { /* jump */ }else{ pFrame = (VdbeFrame*)pRt->z; assert( pRt->xDel==sqlite3VdbeFrameMemDel ); - assert( pProgram->nMem+pProgram->nCsr==pFrame->nChildMem + assert( pProgram->nMem+pProgram->nCsr==pFrame->nChildMem || (pProgram->nCsr==0 && pProgram->nMem+1==pFrame->nChildMem) ); assert( pProgram->nCsr==pFrame->nChildCsr ); assert( (int)(pOp - aOp)==pFrame->pc ); @@ -89737,9 +104188,6 @@ case OP_Program: { /* jump */ memset(pFrame->aOnce, 0, (pProgram->nOp + 7)/8); p->aOp = aOp = pProgram->aOp; p->nOp = pProgram->nOp; -#ifdef SQLITE_ENABLE_STMT_SCANSTATUS - p->anExec = 0; -#endif #ifdef SQLITE_DEBUG /* Verify that second and subsequent executions of the same trigger do not ** try to reuse register values from the first use. */ @@ -89747,7 +104195,7 @@ case OP_Program: { /* jump */ int i; for(i=0; inMem; i++){ aMem[i].pScopyFrom = 0; /* Prevent false-positive AboutToChange() errs */ - aMem[i].flags |= MEM_Undefined; /* Cause a fault if this reg is reused */ + MemSetTypeFlag(&aMem[i], MEM_Undefined); /* Fault if this reg is reused */ } } #endif @@ -89757,10 +104205,10 @@ case OP_Program: { /* jump */ /* Opcode: Param P1 P2 * * * ** -** This opcode is only ever present in sub-programs called via the -** OP_Program instruction. Copy a value currently stored in a memory -** cell of the calling (parent) frame to cell P2 in the current frames -** address space. This is used by trigger programs to access the new.* +** This opcode is only ever present in sub-programs called via the +** OP_Program instruction. Copy a value currently stored in a memory +** cell of the calling (parent) frame to cell P2 in the current frames +** address space. This is used by trigger programs to access the new.* ** and old.* values. ** ** The address of the cell in the parent frame is determined by adding @@ -89772,7 +104220,7 @@ case OP_Param: { /* out2 */ Mem *pIn; pOut = out2Prerelease(p, pOp); pFrame = p->pFrame; - pIn = &pFrame->aMem[pOp->p1 + pFrame->aOp[pFrame->pc].p1]; + pIn = &pFrame->aMem[pOp->p1 + pFrame->aOp[pFrame->pc].p1]; sqlite3VdbeMemShallowCopy(pOut, pIn, MEM_Ephem); break; } @@ -89784,17 +104232,19 @@ case OP_Param: { /* out2 */ ** Synopsis: fkctr[P1]+=P2 ** ** Increment a "constraint counter" by P2 (P2 may be negative or positive). -** If P1 is non-zero, the database constraint counter is incremented -** (deferred foreign key constraints). Otherwise, if P1 is zero, the +** If P1 is non-zero, the database constraint counter is incremented +** (deferred foreign key constraints). Otherwise, if P1 is zero, the ** statement counter is incremented (immediate foreign key constraints). */ case OP_FkCounter: { - if( db->flags & SQLITE_DeferFKs ){ - db->nDeferredImmCons += pOp->p2; - }else if( pOp->p1 ){ + if( pOp->p1 ){ db->nDeferredCons += pOp->p2; }else{ - p->nFkConstraint += pOp->p2; + if( db->flags & SQLITE_DeferFKs ){ + db->nDeferredImmCons += pOp->p2; + }else{ + p->nFkConstraint += pOp->p2; + } } break; } @@ -89803,7 +104253,7 @@ case OP_FkCounter: { ** Synopsis: if fkctr[P1]==0 goto P2 ** ** This opcode tests if a foreign key constraint-counter is currently zero. -** If so, jump to instruction P2. Otherwise, fall through to the next +** If so, jump to instruction P2. Otherwise, fall through to the next ** instruction. ** ** If P1 is non-zero, then the jump is taken if the database constraint-counter @@ -89829,7 +104279,7 @@ case OP_FkIfZero: { /* jump */ ** ** P1 is a register in the root frame of this VM (the root frame is ** different from the current frame if this instruction is being executed -** within a sub-program). Set the value of register P1 to the maximum of +** within a sub-program). Set the value of register P1 to the maximum of ** its current value and the value in register P2. ** ** This instruction throws an error if the memory cell is not initially @@ -89879,7 +104329,7 @@ case OP_IfPos: { /* jump, in1 */ ** Synopsis: if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1) ** ** This opcode performs a commonly used computation associated with -** LIMIT and OFFSET process. r[P1] holds the limit counter. r[P3] +** LIMIT and OFFSET processing. r[P1] holds the limit counter. r[P3] ** holds the offset counter. The opcode computes the combined value ** of the LIMIT and OFFSET and stores that value in r[P2]. The r[P2] ** value computed is the total number of rows that will need to be @@ -89889,7 +104339,7 @@ case OP_IfPos: { /* jump, in1 */ ** and r[P2] is set to be the value of the LIMIT, r[P1]. ** ** if r[P1] is zero or negative, that means there is no LIMIT -** and r[P2] is set to -1. +** and r[P2] is set to -1. ** ** Otherwise, r[P2] is set to the sum of r[P1] and r[P3]. */ @@ -89921,7 +104371,7 @@ case OP_OffsetLimit: { /* in1, out2, in3 */ ** ** Register P1 must contain an integer. If the content of register P1 is ** initially greater than zero, then decrement the value in register P1. -** If it is non-zero (negative or positive) and then also jump to P2. +** If it is non-zero (negative or positive) and then also jump to P2. ** If register P1 is initially zero, leave it unchanged and fall through. */ case OP_IfNotZero: { /* jump, in1 */ @@ -89955,7 +104405,7 @@ case OP_DecrJumpZero: { /* jump, in1 */ ** Synopsis: accum=r[P3] step(r[P2@P5]) ** ** Execute the xStep function for an aggregate. -** The function has P5 arguments. P4 is a pointer to the +** The function has P5 arguments. P4 is a pointer to the ** FuncDef structure that specifies the function. Register P3 is the ** accumulator. ** @@ -89966,7 +104416,7 @@ case OP_DecrJumpZero: { /* jump, in1 */ ** Synopsis: accum=r[P3] inverse(r[P2@P5]) ** ** Execute the xInverse function for an aggregate. -** The function has P5 arguments. P4 is a pointer to the +** The function has P5 arguments. P4 is a pointer to the ** FuncDef structure that specifies the function. Register P3 is the ** accumulator. ** @@ -89977,7 +104427,7 @@ case OP_DecrJumpZero: { /* jump, in1 */ ** Synopsis: accum=r[P3] step(r[P2@P5]) ** ** Execute the xStep (if P1==0) or xInverse (if P1!=0) function for an -** aggregate. The function has P5 arguments. P4 is a pointer to the +** aggregate. The function has P5 arguments. P4 is a pointer to the ** FuncDef structure that specifies the function. Register P3 is the ** accumulator. ** @@ -89994,23 +104444,35 @@ case OP_AggInverse: case OP_AggStep: { int n; sqlite3_context *pCtx; + u64 nAlloc; assert( pOp->p4type==P4_FUNCDEF ); n = pOp->p5; assert( pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor) ); assert( n==0 || (pOp->p2>0 && pOp->p2+n<=(p->nMem+1 - p->nCursor)+1) ); assert( pOp->p3p2 || pOp->p3>=pOp->p2+n ); - pCtx = sqlite3DbMallocRawNN(db, n*sizeof(sqlite3_value*) + - (sizeof(pCtx[0]) + sizeof(Mem) - sizeof(sqlite3_value*))); + + /* Allocate space for (a) the context object and (n-1) extra pointers + ** to append to the sqlite3_context.argv[1] array, and (b) a memory + ** cell in which to store the accumulation. Be careful that the memory + ** cell is 8-byte aligned, even on platforms where a pointer is 32-bits. + ** + ** Note: We could avoid this by using a regular memory cell from aMem[] for + ** the accumulator, instead of allocating one here. */ + nAlloc = ROUND8P( SZ_CONTEXT(n) ); + pCtx = sqlite3DbMallocRawNN(db, nAlloc + sizeof(Mem)); if( pCtx==0 ) goto no_mem; - pCtx->pMem = 0; - pCtx->pOut = (Mem*)&(pCtx->argv[n]); + pCtx->pOut = (Mem*)((u8*)pCtx + nAlloc); + assert( EIGHT_BYTE_ALIGNMENT(pCtx->pOut) ); + sqlite3VdbeMemInit(pCtx->pOut, db, MEM_Null); + pCtx->pMem = 0; pCtx->pFunc = pOp->p4.pFunc; pCtx->iOp = (int)(pOp - aOp); pCtx->pVdbe = p; pCtx->skipFlag = 0; pCtx->isError = 0; + pCtx->enc = encoding; pCtx->argc = n; pOp->p4type = P4_FUNCCTX; pOp->p4.pCtx = pCtx; @@ -90020,6 +104482,7 @@ case OP_AggStep: { pOp->opcode = OP_AggStep1; /* Fall through into OP_AggStep */ + /* no break */ deliberate_fall_through } case OP_AggStep1: { int i; @@ -90044,7 +104507,7 @@ case OP_AggStep1: { /* If this function is inside of a trigger, the register array in aMem[] ** might change from one evaluation to the next. The next block of code ** checks to see if the register array has changed, and if so it - ** reinitializes the relavant parts of the sqlite3_context object */ + ** reinitializes the relevant parts of the sqlite3_context object */ if( pCtx->pMem != pMem ){ pCtx->pMem = pMem; for(i=pCtx->argc-1; i>=0; i--) pCtx->argv[i] = &aMem[pOp->p2+i]; @@ -90093,7 +104556,7 @@ case OP_AggStep1: { ** Synopsis: accum=r[P1] N=P2 ** ** P1 is the memory location that is the accumulator for an aggregate -** or window function. Execute the finalizer function +** or window function. Execute the finalizer function ** for an aggregate and store the result in P1. ** ** P2 is the number of arguments that the step function takes and @@ -90132,16 +104595,14 @@ case OP_AggFinal: { { rc = sqlite3VdbeMemFinalize(pMem, pOp->p4.pFunc); } - + if( rc ){ sqlite3VdbeError(p, "%s", sqlite3_value_text(pMem)); goto abort_due_to_error; } sqlite3VdbeChangeEncoding(pMem, encoding); UPDATE_MAX_BLOBSIZE(pMem); - if( sqlite3VdbeMemTooBig(pMem) ){ - goto too_big; - } + REGISTER_TRACE((int)(pMem-aMem), pMem); break; } @@ -90169,6 +104630,7 @@ case OP_Checkpoint: { || pOp->p2==SQLITE_CHECKPOINT_FULL || pOp->p2==SQLITE_CHECKPOINT_RESTART || pOp->p2==SQLITE_CHECKPOINT_TRUNCATE + || pOp->p2==SQLITE_CHECKPOINT_NOOP ); rc = sqlite3Checkpoint(db, pOp->p1, pOp->p2, &aRes[1], &aRes[2]); if( rc ){ @@ -90178,9 +104640,9 @@ case OP_Checkpoint: { } for(i=0, pMem = &aMem[pOp->p3]; i<3; i++, pMem++){ sqlite3VdbeMemSetInt64(pMem, (i64)aRes[i]); - } + } break; -}; +}; #endif #ifndef SQLITE_OMIT_PRAGMA @@ -90206,9 +104668,9 @@ case OP_JournalMode: { /* out2 */ pOut = out2Prerelease(p, pOp); eNew = pOp->p3; - assert( eNew==PAGER_JOURNALMODE_DELETE - || eNew==PAGER_JOURNALMODE_TRUNCATE - || eNew==PAGER_JOURNALMODE_PERSIST + assert( eNew==PAGER_JOURNALMODE_DELETE + || eNew==PAGER_JOURNALMODE_TRUNCATE + || eNew==PAGER_JOURNALMODE_PERSIST || eNew==PAGER_JOURNALMODE_OFF || eNew==PAGER_JOURNALMODE_MEMORY || eNew==PAGER_JOURNALMODE_WAL @@ -90221,13 +104683,14 @@ case OP_JournalMode: { /* out2 */ pPager = sqlite3BtreePager(pBt); eOld = sqlite3PagerGetJournalMode(pPager); if( eNew==PAGER_JOURNALMODE_QUERY ) eNew = eOld; + assert( sqlite3BtreeHoldsMutex(pBt) ); if( !sqlite3PagerOkToChangeJournalMode(pPager) ) eNew = eOld; #ifndef SQLITE_OMIT_WAL zFilename = sqlite3PagerFilename(pPager, 1); /* Do not allow a transition to journal_mode=WAL for a database - ** in temporary storage or if the VFS does not support shared memory + ** in temporary storage or if the VFS does not support shared memory */ if( eNew==PAGER_JOURNALMODE_WAL && (sqlite3Strlen30(zFilename)==0 /* Temp file */ @@ -90247,12 +104710,12 @@ case OP_JournalMode: { /* out2 */ ); goto abort_due_to_error; }else{ - + if( eOld==PAGER_JOURNALMODE_WAL ){ /* If leaving WAL mode, close the log file. If successful, the call - ** to PagerCloseWal() checkpoints and deletes the write-ahead-log - ** file. An EXCLUSIVE lock may still be held on the database file - ** after a successful return. + ** to PagerCloseWal() checkpoints and deletes the write-ahead-log + ** file. An EXCLUSIVE lock may still be held on the database file + ** after a successful return. */ rc = sqlite3PagerCloseWal(pPager, db); if( rc==SQLITE_OK ){ @@ -90263,11 +104726,11 @@ case OP_JournalMode: { /* out2 */ ** as an intermediate */ sqlite3PagerSetJournalMode(pPager, PAGER_JOURNALMODE_OFF); } - + /* Open a transaction on the database file. Regardless of the journal ** mode, this transaction always uses a rollback journal. */ - assert( sqlite3BtreeIsInTrans(pBt)==0 ); + assert( sqlite3BtreeTxnState(pBt)!=SQLITE_TXN_WRITE ); if( rc==SQLITE_OK ){ rc = sqlite3BtreeSetVersion(pBt, (eNew==PAGER_JOURNALMODE_WAL ? 2 : 1)); } @@ -90338,7 +104801,7 @@ case OP_IncrVacuum: { /* jump */ ** is executed using sqlite3_step() it will either automatically ** reprepare itself (if it was originally created using sqlite3_prepare_v2()) ** or it will fail with SQLITE_SCHEMA. -** +** ** If P1 is 0, then all SQL statements become expired. If P1 is non-zero, ** then only the currently executing statement is expired. ** @@ -90358,12 +104821,42 @@ case OP_Expire: { break; } +/* Opcode: CursorLock P1 * * * * +** +** Lock the btree to which cursor P1 is pointing so that the btree cannot be +** written by an other cursor. +*/ +case OP_CursorLock: { + VdbeCursor *pC; + assert( pOp->p1>=0 && pOp->p1nCursor ); + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); + assert( pC->eCurType==CURTYPE_BTREE ); + sqlite3BtreeCursorPin(pC->uc.pCursor); + break; +} + +/* Opcode: CursorUnlock P1 * * * * +** +** Unlock the btree to which cursor P1 is pointing so that it can be +** written by other cursors. +*/ +case OP_CursorUnlock: { + VdbeCursor *pC; + assert( pOp->p1>=0 && pOp->p1nCursor ); + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); + assert( pC->eCurType==CURTYPE_BTREE ); + sqlite3BtreeCursorUnpin(pC->uc.pCursor); + break; +} + #ifndef SQLITE_OMIT_SHARED_CACHE /* Opcode: TableLock P1 P2 P3 P4 * ** Synopsis: iDb=P1 root=P2 write=P3 ** ** Obtain a lock on a particular table. This instruction is only used when -** the shared-cache feature is enabled. +** the shared-cache feature is enabled. ** ** P1 is the index of the database in sqlite3.aDb[] of the database ** on which the lock is acquired. A readlock is obtained if P3==0 or @@ -90377,7 +104870,7 @@ case OP_Expire: { case OP_TableLock: { u8 isWriteLock = (u8)pOp->p3; if( isWriteLock || 0==(db->flags&SQLITE_ReadUncommit) ){ - int p1 = pOp->p1; + int p1 = pOp->p1; assert( p1>=0 && p1nDb ); assert( DbMaskTest(p->btreeMask, p1) ); assert( isWriteLock==0 || isWriteLock==1 ); @@ -90397,7 +104890,7 @@ case OP_TableLock: { #ifndef SQLITE_OMIT_VIRTUALTABLE /* Opcode: VBegin * * * P4 * ** -** P4 may be a pointer to an sqlite3_vtab structure. If so, call the +** P4 may be a pointer to an sqlite3_vtab structure. If so, call the ** xBegin method for that table. ** ** Also, whether or not P4 is set, check that this is not being called from @@ -90417,7 +104910,7 @@ case OP_VBegin: { #ifndef SQLITE_OMIT_VIRTUALTABLE /* Opcode: VCreate P1 P2 * * * ** -** P2 is a register that holds the name of a virtual table in database +** P2 is a register that holds the name of a virtual table in database ** P1. Call the xCreate method for that table. */ case OP_VCreate: { @@ -90466,14 +104959,21 @@ case OP_VDestroy: { ** P1 is a cursor number. This opcode opens a cursor to the virtual ** table and stores that cursor in P1. */ -case OP_VOpen: { +case OP_VOpen: { /* ncycle */ VdbeCursor *pCur; sqlite3_vtab_cursor *pVCur; sqlite3_vtab *pVtab; const sqlite3_module *pModule; assert( p->bIsReader ); - pCur = 0; + pCur = p->apCsr[pOp->p1]; + if( pCur!=0 + && ALWAYS( pCur->eCurType==CURTYPE_VTAB ) + && ALWAYS( pCur->uc.pVCur->pVtab==pOp->p4.pVtab->pVtab ) + ){ + /* This opcode is a no-op if the cursor is already open */ + break; + } pVCur = 0; pVtab = pOp->p4.pVtab->pVtab; if( pVtab==0 || NEVER(pVtab->pModule==0) ){ @@ -90489,7 +104989,7 @@ case OP_VOpen: { pVCur->pVtab = pVtab; /* Initialize vdbe cursor object */ - pCur = allocateCursor(p, pOp->p1, 0, -1, CURTYPE_VTAB); + pCur = allocateCursor(p, pOp->p1, 0, CURTYPE_VTAB); if( pCur ){ pCur->uc.pVCur = pVCur; pVtab->nRef++; @@ -90502,6 +105002,80 @@ case OP_VOpen: { } #endif /* SQLITE_OMIT_VIRTUALTABLE */ +#ifndef SQLITE_OMIT_VIRTUALTABLE +/* Opcode: VCheck P1 P2 P3 P4 * +** +** P4 is a pointer to a Table object that is a virtual table in schema P1 +** that supports the xIntegrity() method. This opcode runs the xIntegrity() +** method for that virtual table, using P3 as the integer argument. If +** an error is reported back, the table name is prepended to the error +** message and that message is stored in P2. If no errors are seen, +** register P2 is set to NULL. +*/ +case OP_VCheck: { /* out2 */ + Table *pTab; + sqlite3_vtab *pVtab; + const sqlite3_module *pModule; + char *zErr = 0; + + pOut = &aMem[pOp->p2]; + sqlite3VdbeMemSetNull(pOut); /* Innocent until proven guilty */ + assert( pOp->p4type==P4_TABLEREF ); + pTab = pOp->p4.pTab; + assert( pTab!=0 ); + assert( pTab->nTabRef>0 ); + assert( IsVirtual(pTab) ); + if( pTab->u.vtab.p==0 ) break; + pVtab = pTab->u.vtab.p->pVtab; + assert( pVtab!=0 ); + pModule = pVtab->pModule; + assert( pModule!=0 ); + assert( pModule->iVersion>=4 ); + assert( pModule->xIntegrity!=0 ); + sqlite3VtabLock(pTab->u.vtab.p); + assert( pOp->p1>=0 && pOp->p1nDb ); + rc = pModule->xIntegrity(pVtab, db->aDb[pOp->p1].zDbSName, pTab->zName, + pOp->p3, &zErr); + sqlite3VtabUnlock(pTab->u.vtab.p); + if( rc ){ + sqlite3_free(zErr); + goto abort_due_to_error; + } + if( zErr ){ + sqlite3VdbeMemSetStr(pOut, zErr, -1, SQLITE_UTF8, sqlite3_free); + } + break; +} +#endif /* SQLITE_OMIT_VIRTUALTABLE */ + +#ifndef SQLITE_OMIT_VIRTUALTABLE +/* Opcode: VInitIn P1 P2 P3 * * +** Synopsis: r[P2]=ValueList(P1,P3) +** +** Set register P2 to be a pointer to a ValueList object for cursor P1 +** with cache register P3 and output register P3+1. This ValueList object +** can be used as the first argument to sqlite3_vtab_in_first() and +** sqlite3_vtab_in_next() to extract all of the values stored in the P1 +** cursor. Register P3 is used to hold the values returned by +** sqlite3_vtab_in_first() and sqlite3_vtab_in_next(). +*/ +case OP_VInitIn: { /* out2, ncycle */ + VdbeCursor *pC; /* The cursor containing the RHS values */ + ValueList *pRhs; /* New ValueList object to put in reg[P2] */ + + pC = p->apCsr[pOp->p1]; + pRhs = sqlite3_malloc64( sizeof(*pRhs) ); + if( pRhs==0 ) goto no_mem; + pRhs->pCsr = pC->uc.pCursor; + pRhs->pOut = &aMem[pOp->p3]; + pOut = out2Prerelease(p, pOp); + pOut->flags = MEM_Null; + sqlite3VdbeMemSetPointer(pOut, pRhs, "ValueList", sqlite3VdbeValueListFree); + break; +} +#endif /* SQLITE_OMIT_VIRTUALTABLE */ + + #ifndef SQLITE_OMIT_VIRTUALTABLE /* Opcode: VFilter P1 P2 P3 P4 * ** Synopsis: iplan=r[P3] zplan='P4' @@ -90522,7 +105096,7 @@ case OP_VOpen: { ** ** A jump is made to P2 if the result set after filtering would be empty. */ -case OP_VFilter: { /* jump */ +case OP_VFilter: { /* jump, ncycle */ int nArg; int iQuery; const sqlite3_module *pModule; @@ -90540,6 +105114,7 @@ case OP_VFilter: { /* jump */ pCur = p->apCsr[pOp->p1]; assert( memIsValid(pQuery) ); REGISTER_TRACE(pOp->p3, pQuery); + assert( pCur!=0 ); assert( pCur->eCurType==CURTYPE_VTAB ); pVCur = pCur->uc.pVCur; pVtab = pVCur->pVtab; @@ -90551,8 +105126,8 @@ case OP_VFilter: { /* jump */ iQuery = (int)pQuery->u.i; /* Invoke the xFilter method */ - res = 0; apArg = p->apArg; + assert( nArg<=p->napArg ); for(i = 0; iapCsr[pOp->p1]; - assert( pCur->eCurType==CURTYPE_VTAB ); + assert( pCur!=0 ); assert( pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor) ); pDest = &aMem[pOp->p3]; memAboutToChange(p, pDest); @@ -90597,12 +105173,17 @@ case OP_VColumn: { sqlite3VdbeMemSetNull(pDest); break; } + assert( pCur->eCurType==CURTYPE_VTAB ); pVtab = pCur->uc.pVCur->pVtab; pModule = pVtab->pModule; assert( pModule->xColumn ); memset(&sContext, 0, sizeof(sContext)); sContext.pOut = pDest; - testcase( (pOp->p5 & OPFLAG_NOCHNG)==0 && pOp->p5!=0 ); + sContext.enc = encoding; + nullFunc.pUserData = 0; + nullFunc.funcFlags = SQLITE_RESULT_SUBTYPE; + sContext.pFunc = &nullFunc; + assert( pOp->p5==OPFLAG_NOCHNG || pOp->p5==0 ); if( pOp->p5 & OPFLAG_NOCHNG ){ sqlite3VdbeMemSetNull(pDest); pDest->flags = MEM_Null|MEM_Zero; @@ -90620,9 +105201,6 @@ case OP_VColumn: { REGISTER_TRACE(pOp->p3, pDest); UPDATE_MAX_BLOBSIZE(pDest); - if( sqlite3VdbeMemTooBig(pDest) ){ - goto too_big; - } if( rc ) goto abort_due_to_error; break; } @@ -90635,14 +105213,14 @@ case OP_VColumn: { ** jump to instruction P2. Or, if the virtual table has reached ** the end of its result set, then fall through to the next instruction. */ -case OP_VNext: { /* jump */ +case OP_VNext: { /* jump, ncycle */ sqlite3_vtab *pVtab; const sqlite3_module *pModule; int res; VdbeCursor *pCur; - res = 0; pCur = p->apCsr[pOp->p1]; + assert( pCur!=0 ); assert( pCur->eCurType==CURTYPE_VTAB ); if( pCur->nullRow ){ break; @@ -90653,7 +105231,7 @@ case OP_VNext: { /* jump */ /* Invoke the xNext() method of the module. There is no way for the ** underlying implementation to return an error if one occurs during - ** xNext(). Instead, if an error occurs, true is returned (indicating that + ** xNext(). Instead, if an error occurs, true is returned (indicating that ** data is available) and the error code returned when xColumn or ** some other method is next invoked on the save virtual table cursor. */ @@ -90681,7 +105259,7 @@ case OP_VRename: { sqlite3_vtab *pVtab; Mem *pName; int isLegacy; - + isLegacy = (db->flags & SQLITE_LegacyAlter); db->flags |= SQLITE_LegacyAlter; pVtab = pOp->p4.pVtab->pVtab; @@ -90711,23 +105289,23 @@ case OP_VRename: { ** ** P4 is a pointer to a virtual table object, an sqlite3_vtab structure. ** This opcode invokes the corresponding xUpdate method. P2 values -** are contiguous memory cells starting at P3 to pass to the xUpdate -** invocation. The value in register (P3+P2-1) corresponds to the +** are contiguous memory cells starting at P3 to pass to the xUpdate +** invocation. The value in register (P3+P2-1) corresponds to the ** p2th element of the argv array passed to xUpdate. ** ** The xUpdate method will do a DELETE or an INSERT or both. ** The argv[0] element (which corresponds to memory cell P3) -** is the rowid of a row to delete. If argv[0] is NULL then no -** deletion occurs. The argv[1] element is the rowid of the new -** row. This can be NULL to have the virtual table select the new -** rowid for itself. The subsequent elements in the array are +** is the rowid of a row to delete. If argv[0] is NULL then no +** deletion occurs. The argv[1] element is the rowid of the new +** row. This can be NULL to have the virtual table select the new +** rowid for itself. The subsequent elements in the array are ** the values of columns in the new row. ** ** If P2==1 then no insert is performed. argv[0] is the rowid of ** a row to delete. ** ** P1 is a boolean flag. If it is set to true and the xUpdate call -** is successful, then the value returned by sqlite3_last_insert_rowid() +** is successful, then the value returned by sqlite3_last_insert_rowid() ** is set to the value of the rowid for the row just inserted. ** ** P5 is the error actions (OE_Replace, OE_Fail, OE_Ignore, etc) to @@ -90738,11 +105316,11 @@ case OP_VUpdate: { const sqlite3_module *pModule; int nArg; int i; - sqlite_int64 rowid; + sqlite_int64 rowid = 0; Mem **apArg; Mem *pX; - assert( pOp->p2==1 || pOp->p5==OE_Fail || pOp->p5==OE_Rollback + assert( pOp->p2==1 || pOp->p5==OE_Fail || pOp->p5==OE_Rollback || pOp->p5==OE_Abort || pOp->p5==OE_Ignore || pOp->p5==OE_Replace ); assert( p->readOnly==0 ); @@ -90760,6 +105338,7 @@ case OP_VUpdate: { u8 vtabOnConflict = db->vtabOnConflict; apArg = p->apArg; pX = &aMem[pOp->p3]; + assert( nArg<=p->napArg ); for(i=0; ip4type==P4_FUNCDEF ); - n = pOp->p5; - assert( pOp->p3>0 && pOp->p3<=(p->nMem+1 - p->nCursor) ); - assert( n==0 || (pOp->p2>0 && pOp->p2+n<=(p->nMem+1 - p->nCursor)+1) ); - assert( pOp->p3p2 || pOp->p3>=pOp->p2+n ); - pCtx = sqlite3DbMallocRawNN(db, sizeof(*pCtx) + (n-1)*sizeof(sqlite3_value*)); - if( pCtx==0 ) goto no_mem; - pCtx->pOut = 0; - pCtx->pFunc = pOp->p4.pFunc; - pCtx->iOp = (int)(pOp - aOp); - pCtx->pVdbe = p; - pCtx->isError = 0; - pCtx->argc = n; - pOp->p4type = P4_FUNCCTX; - pOp->p4.pCtx = pCtx; - assert( OP_PureFunc == OP_PureFunc0+2 ); - assert( OP_Function == OP_Function0+2 ); - pOp->opcode += 2; - /* Fall through into OP_Function */ -} case OP_PureFunc: /* group */ case OP_Function: { /* group */ int i; @@ -90904,12 +105463,15 @@ case OP_Function: { /* group */ /* If this function is inside of a trigger, the register array in aMem[] ** might change from one evaluation to the next. The next block of code ** checks to see if the register array has changed, and if so it - ** reinitializes the relavant parts of the sqlite3_context object */ + ** reinitializes the relevant parts of the sqlite3_context object */ pOut = &aMem[pOp->p3]; if( pCtx->pOut != pOut ){ + pCtx->pVdbe = p; pCtx->pOut = pOut; + pCtx->enc = encoding; for(i=pCtx->argc-1; i>=0; i--) pCtx->argv[i] = &aMem[pOp->p2+i]; } + assert( pCtx->pVdbe==p ); memAboutToChange(p, pOut); #ifdef SQLITE_DEBUG @@ -90933,17 +105495,134 @@ case OP_Function: { /* group */ if( rc ) goto abort_due_to_error; } - /* Copy the result of the function into register P3 */ - if( pOut->flags & (MEM_Str|MEM_Blob) ){ - sqlite3VdbeChangeEncoding(pOut, encoding); - if( sqlite3VdbeMemTooBig(pOut) ) goto too_big; - } + assert( (pOut->flags&MEM_Str)==0 + || pOut->enc==encoding + || db->mallocFailed ); + assert( !sqlite3VdbeMemTooBig(pOut) ); REGISTER_TRACE(pOp->p3, pOut); UPDATE_MAX_BLOBSIZE(pOut); break; } +/* Opcode: ClrSubtype P1 * * * * +** Synopsis: r[P1].subtype = 0 +** +** Clear the subtype from register P1. +*/ +case OP_ClrSubtype: { /* in1 */ + pIn1 = &aMem[pOp->p1]; + pIn1->flags &= ~MEM_Subtype; + break; +} + +/* Opcode: GetSubtype P1 P2 * * * +** Synopsis: r[P2] = r[P1].subtype +** +** Extract the subtype value from register P1 and write that subtype +** into register P2. If P1 has no subtype, then P1 gets a NULL. +*/ +case OP_GetSubtype: { /* in1 out2 */ + pIn1 = &aMem[pOp->p1]; + pOut = &aMem[pOp->p2]; + if( pIn1->flags & MEM_Subtype ){ + sqlite3VdbeMemSetInt64(pOut, pIn1->eSubtype); + }else{ + sqlite3VdbeMemSetNull(pOut); + } + break; +} + +/* Opcode: SetSubtype P1 P2 * * * +** Synopsis: r[P2].subtype = r[P1] +** +** Set the subtype value of register P2 to the integer from register P1. +** If P1 is NULL, clear the subtype from p2. +*/ +case OP_SetSubtype: { /* in1 out2 */ + pIn1 = &aMem[pOp->p1]; + pOut = &aMem[pOp->p2]; + if( pIn1->flags & MEM_Null ){ + pOut->flags &= ~MEM_Subtype; + }else{ + assert( pIn1->flags & MEM_Int ); + pOut->flags |= MEM_Subtype; + pOut->eSubtype = (u8)(pIn1->u.i & 0xff); + } + break; +} + +/* Opcode: FilterAdd P1 * P3 P4 * +** Synopsis: filter(P1) += key(P3@P4) +** +** Compute a hash on the P4 registers starting with r[P3] and +** add that hash to the bloom filter contained in r[P1]. +*/ +case OP_FilterAdd: { + u64 h; + + assert( pOp->p1>0 && pOp->p1<=(p->nMem+1 - p->nCursor) ); + pIn1 = &aMem[pOp->p1]; + assert( pIn1->flags & MEM_Blob ); + assert( pIn1->n>0 ); + h = filterHash(aMem, pOp); +#ifdef SQLITE_DEBUG + if( db->flags&SQLITE_VdbeTrace ){ + int ii; + for(ii=pOp->p3; iip3+pOp->p4.i; ii++){ + registerTrace(ii, &aMem[ii]); + } + printf("hash: %llu modulo %d -> %u\n", h, pIn1->n, (int)(h%pIn1->n)); + } +#endif + h %= (pIn1->n*8); + pIn1->z[h/8] |= 1<<(h&7); + break; +} + +/* Opcode: Filter P1 P2 P3 P4 * +** Synopsis: if key(P3@P4) not in filter(P1) goto P2 +** +** Compute a hash on the key contained in the P4 registers starting +** with r[P3]. Check to see if that hash is found in the +** bloom filter hosted by register P1. If it is not present then +** maybe jump to P2. Otherwise fall through. +** +** False negatives are harmless. It is always safe to fall through, +** even if the value is in the bloom filter. A false negative causes +** more CPU cycles to be used, but it should still yield the correct +** answer. However, an incorrect answer may well arise from a +** false positive - if the jump is taken when it should fall through. +*/ +case OP_Filter: { /* jump */ + u64 h; + + assert( pOp->p1>0 && pOp->p1<=(p->nMem+1 - p->nCursor) ); + pIn1 = &aMem[pOp->p1]; + assert( (pIn1->flags & MEM_Blob)!=0 ); + assert( pIn1->n >= 1 ); + h = filterHash(aMem, pOp); +#ifdef SQLITE_DEBUG + if( db->flags&SQLITE_VdbeTrace ){ + int ii; + for(ii=pOp->p3; iip3+pOp->p4.i; ii++){ + registerTrace(ii, &aMem[ii]); + } + printf("hash: %llu modulo %d -> %u\n", h, pIn1->n, (int)(h%pIn1->n)); + } +#endif + h %= (pIn1->n*8); + if( (pIn1->z[h/8] & (1<<(h&7)))==0 ){ + VdbeBranchTaken(1, 2); + p->aCounter[SQLITE_STMTSTATUS_FILTER_HIT]++; + goto jump_to_p2; + }else{ + p->aCounter[SQLITE_STMTSTATUS_FILTER_MISS]++; + VdbeBranchTaken(0, 2); + } + break; +} + /* Opcode: Trace P1 P2 * P4 * ** ** Write P4 on the statement trace output if statement tracing is @@ -90970,7 +105649,7 @@ case OP_Function: { /* group */ ** error is encountered. */ case OP_Trace: -case OP_Init: { /* jump */ +case OP_Init: { /* jump0 */ int i; #ifndef SQLITE_OMIT_TRACE char *zTrace; @@ -90992,23 +105671,22 @@ case OP_Init: { /* jump */ #ifndef SQLITE_OMIT_TRACE if( (db->mTrace & (SQLITE_TRACE_STMT|SQLITE_TRACE_LEGACY))!=0 - && !p->doingRerun + && p->minWriteFileFormat!=254 /* tag-20220401a */ && (zTrace = (pOp->p4.z ? pOp->p4.z : p->zSql))!=0 ){ #ifndef SQLITE_OMIT_DEPRECATED if( db->mTrace & SQLITE_TRACE_LEGACY ){ - void (*x)(void*,const char*) = (void(*)(void*,const char*))db->xTrace; char *z = sqlite3VdbeExpandSql(p, zTrace); - x(db->pTraceArg, z); + db->trace.xLegacy(db->pTraceArg, z); sqlite3_free(z); }else #endif if( db->nVdbeExec>1 ){ char *z = sqlite3MPrintf(db, "-- %s", zTrace); - (void)db->xTrace(SQLITE_TRACE_STMT, db->pTraceArg, p, z); + (void)db->trace.xV2(SQLITE_TRACE_STMT, db->pTraceArg, p, z); sqlite3DbFree(db, z); }else{ - (void)db->xTrace(SQLITE_TRACE_STMT, db->pTraceArg, p, zTrace); + (void)db->trace.xV2(SQLITE_TRACE_STMT, db->pTraceArg, p, zTrace); } } #ifdef SQLITE_USE_FCNTL_TRACE @@ -91081,16 +105759,80 @@ case OP_Abortable: { } #endif +#ifdef SQLITE_DEBUG +/* Opcode: ReleaseReg P1 P2 P3 * P5 +** Synopsis: release r[P1@P2] mask P3 +** +** Release registers from service. Any content that was in the +** the registers is unreliable after this opcode completes. +** +** The registers released will be the P2 registers starting at P1, +** except if bit ii of P3 set, then do not release register P1+ii. +** In other words, P3 is a mask of registers to preserve. +** +** Releasing a register clears the Mem.pScopyFrom pointer. That means +** that if the content of the released register was set using OP_SCopy, +** a change to the value of the source register for the OP_SCopy will no longer +** generate an assertion fault in sqlite3VdbeMemAboutToChange(). +** +** If P5 is set, then all released registers have their type set +** to MEM_Undefined so that any subsequent attempt to read the released +** register (before it is reinitialized) will generate an assertion fault. +** +** P5 ought to be set on every call to this opcode. +** However, there are places in the code generator will release registers +** before their are used, under the (valid) assumption that the registers +** will not be reallocated for some other purpose before they are used and +** hence are safe to release. +** +** This opcode is only available in testing and debugging builds. It is +** not generated for release builds. The purpose of this opcode is to help +** validate the generated bytecode. This opcode does not actually contribute +** to computing an answer. +*/ +case OP_ReleaseReg: { + Mem *pMem; + int i; + u32 constMask; + assert( pOp->p1>0 ); + assert( pOp->p1+pOp->p2<=(p->nMem+1 - p->nCursor)+1 ); + pMem = &aMem[pOp->p1]; + constMask = pOp->p3; + for(i=0; ip2; i++, pMem++){ + if( i>=32 || (constMask & MASKBIT32(i))==0 ){ + pMem->pScopyFrom = 0; + if( i<32 && pOp->p5 ) MemSetTypeFlag(pMem, MEM_Undefined); + } + } + break; +} +#endif + /* Opcode: Noop * * * * * ** -** Do nothing. This instruction is often useful as a jump -** destination. +** Do nothing. Continue downward to the next opcode. */ -/* -** The magic Explain opcode are only inserted when explain==2 (which -** is to say when the EXPLAIN QUERY PLAN syntax is used.) -** This opcode records information from the optimizer. It is the -** the same as a no-op. This opcodesnever appears in a real VM program. +/* Opcode: Explain P1 P2 P3 P4 * +** +** This is the same as OP_Noop during normal query execution. The +** purpose of this opcode is to hold information about the query +** plan for the purpose of EXPLAIN QUERY PLAN output. +** +** The P4 value is human-readable text that describes the query plan +** element. Something like "SCAN t1" or "SEARCH t2 USING INDEX t2x1". +** +** The P1 value is the ID of the current element and P2 is the parent +** element for the case of nested query plan elements. If P2 is zero +** then this element is a top-level element. +** +** For loop elements, P3 is the estimated code of each invocation of this +** element. +** +** As with all opcodes, the meanings of the parameters for OP_Explain +** are subject to change from one release to the next. Applications +** should not attempt to interpret or use any of the information +** contained in the OP_Explain opcode. The information provided by this +** opcode is intended for testing and debugging use only. */ default: { /* This is really OP_Noop, OP_Explain */ assert( pOp->opcode==OP_Noop || pOp->opcode==OP_Explain ); @@ -91106,11 +105848,13 @@ default: { /* This is really OP_Noop, OP_Explain */ *****************************************************************************/ } -#ifdef VDBE_PROFILE - { - u64 endTime = sqlite3NProfileCnt ? sqlite3NProfileCnt : sqlite3Hwtime(); - if( endTime>start ) pOrigOp->cycles += endTime - start; - pOrigOp->cnt++; +#if defined(VDBE_PROFILE) + *pnCycle += sqlite3NProfileCnt ? sqlite3NProfileCnt : sqlite3Hwtime(); + pnCycle = 0; +#elif defined(SQLITE_ENABLE_STMT_SCANSTATUS) + if( pnCycle ){ + *pnCycle += sqlite3Hwtime(); + pnCycle = 0; } #endif @@ -91132,6 +105876,12 @@ default: { /* This is really OP_Noop, OP_Explain */ if( opProperty & OPFLG_OUT3 ){ registerTrace(pOrigOp->p3, &aMem[pOrigOp->p3]); } + if( opProperty==0xff ){ + /* Never happens. This code exists to avoid a harmless linkage + ** warning about sqlite3VdbeRegisterDump() being defined but not + ** used. */ + sqlite3VdbeRegisterDump(p); + } } #endif /* SQLITE_DEBUG */ #endif /* NDEBUG */ @@ -91141,18 +105891,36 @@ default: { /* This is really OP_Noop, OP_Explain */ ** an error of some kind. */ abort_due_to_error: - if( db->mallocFailed ) rc = SQLITE_NOMEM_BKPT; + if( db->mallocFailed ){ + rc = SQLITE_NOMEM_BKPT; + }else if( rc==SQLITE_IOERR_CORRUPTFS ){ + rc = SQLITE_CORRUPT_BKPT; + } assert( rc ); +#ifdef SQLITE_DEBUG + if( db->flags & SQLITE_VdbeTrace ){ + const char *zTrace = p->zSql; + if( zTrace==0 ){ + if( aOp[0].opcode==OP_Trace ){ + zTrace = aOp[0].p4.z; + } + if( zTrace==0 ) zTrace = "???"; + } + printf("ABORT-due-to-error (rc=%d): %s\n", rc, zTrace); + } +#endif if( p->zErrMsg==0 && rc!=SQLITE_IOERR_NOMEM ){ sqlite3VdbeError(p, "%s", sqlite3ErrStr(rc)); } p->rc = rc; sqlite3SystemError(db, rc); testcase( sqlite3GlobalConfig.xLog!=0 ); - sqlite3_log(rc, "statement aborts at %d: [%s] %s", - (int)(pOp - aOp), p->zSql, p->zErrMsg); - sqlite3VdbeHalt(p); + sqlite3VdbeLogAbort(p, rc, pOp, aOp); + if( p->eVdbeState==VDBE_RUN_STATE ) sqlite3VdbeHalt(p); if( rc==SQLITE_IOERR_NOMEM ) sqlite3OomFault(db); + if( rc==SQLITE_CORRUPT && db->autoCommit==0 ){ + db->flags |= SQLITE_CorruptRdOnly; + } rc = SQLITE_ERROR; if( resetSchemaOnFault>0 ){ sqlite3ResetOneSchema(db, resetSchemaOnFault-1); @@ -91162,20 +105930,34 @@ default: { /* This is really OP_Noop, OP_Explain */ ** release the mutexes on btrees that were acquired at the ** top. */ vdbe_return: +#if defined(VDBE_PROFILE) + if( pnCycle ){ + *pnCycle += sqlite3NProfileCnt ? sqlite3NProfileCnt : sqlite3Hwtime(); + pnCycle = 0; + } +#elif defined(SQLITE_ENABLE_STMT_SCANSTATUS) + if( pnCycle ){ + *pnCycle += sqlite3Hwtime(); + pnCycle = 0; + } +#endif + #ifndef SQLITE_OMIT_PROGRESS_CALLBACK while( nVmStep>=nProgressLimit && db->xProgress!=0 ){ nProgressLimit += db->nProgressOps; if( db->xProgress(db->pProgressArg) ){ - nProgressLimit = 0xffffffff; + nProgressLimit = LARGEST_UINT64; rc = SQLITE_INTERRUPT; goto abort_due_to_error; } } #endif p->aCounter[SQLITE_STMTSTATUS_VM_STEP] += (int)nVmStep; - sqlite3VdbeLeave(p); - assert( rc!=SQLITE_OK || nExtraDelete==0 - || sqlite3_strlike("DELETE%",p->zSql,0)!=0 + if( DbMaskNonZero(p->lockMask) ){ + sqlite3VdbeLeave(p); + } + assert( rc!=SQLITE_OK || nExtraDelete==0 + || sqlite3_strlike("DELETE%",p->zSql,0)!=0 ); return rc; @@ -91199,10 +105981,8 @@ default: { /* This is really OP_Noop, OP_Explain */ ** flag. */ abort_due_to_interrupt: - assert( db->u1.isInterrupted ); - rc = db->mallocFailed ? SQLITE_NOMEM_BKPT : SQLITE_INTERRUPT; - p->rc = rc; - sqlite3VdbeError(p, "%s", sqlite3ErrStr(rc)); + assert( AtomicLoad(&db->u1.isInterrupted) ); + rc = SQLITE_INTERRUPT; goto abort_due_to_error; } @@ -91259,7 +106039,7 @@ struct Incrblob { ** sqlite3DbFree(). ** ** If an error does occur, then the b-tree cursor is closed. All subsequent -** calls to sqlite3_blob_read(), blob_write() or blob_reopen() will +** calls to sqlite3_blob_read(), blob_write() or blob_reopen() will ** immediately return SQLITE_ABORT. */ static int blobSeekToRow(Incrblob *p, sqlite3_int64 iRow, char **pzErr){ @@ -91267,11 +106047,10 @@ static int blobSeekToRow(Incrblob *p, sqlite3_int64 iRow, char **pzErr){ char *zErr = 0; /* Error message */ Vdbe *v = (Vdbe *)p->pStmt; - /* Set the value of register r[1] in the SQL statement to integer iRow. + /* Set the value of register r[1] in the SQL statement to integer iRow. ** This is done directly as a performance optimization */ - v->aMem[1].flags = MEM_Int; - v->aMem[1].u.i = iRow; + sqlite3VdbeMemSetInt64(&v->aMem[1], iRow); /* If the statement has been run before (and is paused at the OP_ResultRow) ** then back it up to the point where it does the OP_NotExists. This could @@ -91286,7 +106065,10 @@ static int blobSeekToRow(Incrblob *p, sqlite3_int64 iRow, char **pzErr){ } if( rc==SQLITE_ROW ){ VdbeCursor *pC = v->apCsr[0]; - u32 type = pC->nHdrParsed>p->iCol ? pC->aType[p->iCol] : 0; + u32 type; + assert( pC!=0 ); + assert( pC->eCurType==CURTYPE_BTREE ); + type = pC->nHdrParsed>p->iCol ? pC->aType[p->iCol] : 0; testcase( pC->nHdrParsed==p->iCol ); testcase( pC->nHdrParsed==p->iCol+1 ); if( type<12 ){ @@ -91342,6 +106124,7 @@ SQLITE_API int sqlite3_blob_open( char *zErr = 0; Table *pTab; Incrblob *pBlob = 0; + int iDb; Parse sParse; #ifdef SQLITE_ENABLE_API_ARMOR @@ -91351,7 +106134,7 @@ SQLITE_API int sqlite3_blob_open( #endif *ppBlob = 0; #ifdef SQLITE_ENABLE_API_ARMOR - if( !sqlite3SafetyCheckOk(db) || zTable==0 ){ + if( !sqlite3SafetyCheckOk(db) || zTable==0 || zColumn==0 ){ return SQLITE_MISUSE_BKPT; } #endif @@ -91360,10 +106143,9 @@ SQLITE_API int sqlite3_blob_open( sqlite3_mutex_enter(db->mutex); pBlob = (Incrblob *)sqlite3DbMallocZero(db, sizeof(Incrblob)); - do { - memset(&sParse, 0, sizeof(Parse)); + while(1){ + sqlite3ParseObjectInit(&sParse,db); if( !pBlob ) goto blob_open_out; - sParse.db = db; sqlite3DbFree(db, zErr); zErr = 0; @@ -91377,13 +106159,21 @@ SQLITE_API int sqlite3_blob_open( pTab = 0; sqlite3ErrorMsg(&sParse, "cannot open table without rowid: %s", zTable); } + if( pTab && (pTab->tabFlags&TF_HasGenerated)!=0 ){ + pTab = 0; + sqlite3ErrorMsg(&sParse, "cannot open table with generated columns: %s", + zTable); + } #ifndef SQLITE_OMIT_VIEW - if( pTab && pTab->pSelect ){ + if( pTab && IsView(pTab) ){ pTab = 0; sqlite3ErrorMsg(&sParse, "cannot open view: %s", zTable); } #endif - if( !pTab ){ + if( pTab==0 + || ((iDb = sqlite3SchemaToIndex(db, pTab->pSchema))==1 && + sqlite3OpenTempDatabase(&sParse)) + ){ if( sParse.zErrMsg ){ sqlite3DbFree(db, zErr); zErr = sParse.zErrMsg; @@ -91394,15 +106184,11 @@ SQLITE_API int sqlite3_blob_open( goto blob_open_out; } pBlob->pTab = pTab; - pBlob->zDb = db->aDb[sqlite3SchemaToIndex(db, pTab->pSchema)].zDbSName; + pBlob->zDb = db->aDb[iDb].zDbSName; /* Now search pTab for the exact column. */ - for(iCol=0; iColnCol; iCol++) { - if( sqlite3StrICmp(pTab->aCol[iCol].zName, zColumn)==0 ){ - break; - } - } - if( iCol==pTab->nCol ){ + iCol = sqlite3ColumnIndex(pTab, zColumn); + if( iCol<0 ){ sqlite3DbFree(db, zErr); zErr = sqlite3MPrintf(db, "no such column: \"%s\"", zColumn); rc = SQLITE_ERROR; @@ -91411,7 +106197,7 @@ SQLITE_API int sqlite3_blob_open( } /* If the value is being opened for writing, check that the - ** column is not indexed, and that it is not part of a foreign key. + ** column is not indexed, and that it is not part of a foreign key. */ if( wrFlag ){ const char *zFault = 0; @@ -91420,10 +106206,11 @@ SQLITE_API int sqlite3_blob_open( if( db->flags&SQLITE_ForeignKeys ){ /* Check that the column is not part of an FK child key definition. It ** is not necessary to check if it is part of a parent key, as parent - ** key columns must be indexed. The check below will pick up this + ** key columns must be indexed. The check below will pick up this ** case. */ FKey *pFKey; - for(pFKey=pTab->pFKey; pFKey; pFKey=pFKey->pNextFrom){ + assert( IsOrdinaryTable(pTab) ); + for(pFKey=pTab->u.tab.pFKey; pFKey; pFKey=pFKey->pNextFrom){ int j; for(j=0; jnCol; j++){ if( pFKey->aCol[j].iFrom==iCol ){ @@ -91454,8 +106241,8 @@ SQLITE_API int sqlite3_blob_open( pBlob->pStmt = (sqlite3_stmt *)sqlite3VdbeCreate(&sParse); assert( pBlob->pStmt || db->mallocFailed ); if( pBlob->pStmt ){ - - /* This VDBE program seeks a btree cursor to the identified + + /* This VDBE program seeks a btree cursor to the identified ** db/table/row entry. The reason for using a vdbe program instead ** of writing code to use the b-tree layer directly is that the ** vdbe program will take advantage of the various transaction, @@ -91463,11 +106250,11 @@ SQLITE_API int sqlite3_blob_open( ** ** After seeking the cursor, the vdbe executes an OP_ResultRow. ** Code external to the Vdbe then "borrows" the b-tree cursor and - ** uses it to implement the blob_read(), blob_write() and + ** uses it to implement the blob_read(), blob_write() and ** blob_bytes() functions. ** ** The sqlite3_blob_close() function finalizes the vdbe program, - ** which closes the b-tree cursor and (possibly) commits the + ** which closes the b-tree cursor and (possibly) commits the ** transaction. */ static const int iLn = VDBE_OFFSET_LINENO(2); @@ -91481,10 +106268,9 @@ SQLITE_API int sqlite3_blob_open( {OP_Halt, 0, 0, 0}, /* 5 */ }; Vdbe *v = (Vdbe *)pBlob->pStmt; - int iDb = sqlite3SchemaToIndex(db, pTab->pSchema); VdbeOp *aOp; - sqlite3VdbeAddOp4Int(v, OP_Transaction, iDb, wrFlag, + sqlite3VdbeAddOp4Int(v, OP_Transaction, iDb, wrFlag, pTab->pSchema->schema_cookie, pTab->pSchema->iGeneration); sqlite3VdbeChangeP5(v, 1); @@ -91492,7 +106278,7 @@ SQLITE_API int sqlite3_blob_open( aOp = sqlite3VdbeAddOpList(v, ArraySize(openBlob), openBlob, iLn); /* Make sure a mutex is held on the table to be accessed */ - sqlite3VdbeUsesBtree(v, iDb); + sqlite3VdbeUsesBtree(v, iDb); if( db->mallocFailed==0 ){ assert( aOp!=0 ); @@ -91508,17 +106294,17 @@ SQLITE_API int sqlite3_blob_open( if( db->mallocFailed==0 ){ #endif - /* Remove either the OP_OpenWrite or OpenRead. Set the P2 + /* Remove either the OP_OpenWrite or OpenRead. Set the P2 ** parameter of the other to pTab->tnum. */ if( wrFlag ) aOp[1].opcode = OP_OpenWrite; aOp[1].p2 = pTab->tnum; - aOp[1].p3 = iDb; + aOp[1].p3 = iDb; /* Configure the number of columns. Configure the cursor to ** think that the table has one more column than it really ** does. An OP_Column to retrieve this imaginary column will ** always return an SQL NULL. This is useful because it means - ** we can invoke OP_Column to fill in the vdbe cursors type + ** we can invoke OP_Column to fill in the vdbe cursors type ** and offset cache without causing any IO. */ aOp[1].p4type = P4_INT32; @@ -91531,7 +106317,7 @@ SQLITE_API int sqlite3_blob_open( sqlite3VdbeMakeReady(v, &sParse); } } - + pBlob->iCol = iCol; pBlob->db = db; sqlite3BtreeLeaveAll(db); @@ -91539,7 +106325,9 @@ SQLITE_API int sqlite3_blob_open( goto blob_open_out; } rc = blobSeekToRow(pBlob, iRow, &zErr); - } while( (++nAttempt)=SQLITE_MAX_SCHEMA_RETRY || rc!=SQLITE_SCHEMA ) break; + sqlite3ParseObjectReset(&sParse); + } blob_open_out: if( rc==SQLITE_OK && db->mallocFailed==0 ){ @@ -91548,9 +106336,9 @@ SQLITE_API int sqlite3_blob_open( if( pBlob && pBlob->pStmt ) sqlite3VdbeFinalize((Vdbe *)pBlob->pStmt); sqlite3DbFree(db, pBlob); } - sqlite3ErrorWithMsg(db, rc, (zErr ? "%s" : 0), zErr); + sqlite3ErrorWithMsg(db, rc, (zErr ? "%s" : (char*)0), zErr); sqlite3DbFree(db, zErr); - sqlite3ParserReset(&sParse); + sqlite3ParseObjectReset(&sParse); rc = sqlite3ApiExit(db, rc); sqlite3_mutex_leave(db->mutex); return rc; @@ -91566,11 +106354,12 @@ SQLITE_API int sqlite3_blob_close(sqlite3_blob *pBlob){ sqlite3 *db; if( p ){ + sqlite3_stmt *pStmt = p->pStmt; db = p->db; sqlite3_mutex_enter(db->mutex); - rc = sqlite3_finalize(p->pStmt); sqlite3DbFree(db, p); sqlite3_mutex_leave(db->mutex); + rc = sqlite3_finalize(pStmt); }else{ rc = SQLITE_OK; } @@ -91581,13 +106370,13 @@ SQLITE_API int sqlite3_blob_close(sqlite3_blob *pBlob){ ** Perform a read or write operation on a blob */ static int blobReadWrite( - sqlite3_blob *pBlob, - void *z, - int n, - int iOffset, + sqlite3_blob *pBlob, + void *z, + int n, + int iOffset, int (*xCall)(BtCursor*, u32, u32, void*) ){ - int rc; + int rc = SQLITE_OK; Incrblob *p = (Incrblob *)pBlob; Vdbe *v; sqlite3 *db; @@ -91614,28 +106403,45 @@ static int blobReadWrite( #ifdef SQLITE_ENABLE_PREUPDATE_HOOK if( xCall==sqlite3BtreePutData && db->xPreUpdateCallback ){ - /* If a pre-update hook is registered and this is a write cursor, - ** invoke it here. - ** + /* If a pre-update hook is registered and this is a write cursor, + ** invoke it here. + ** ** TODO: The preupdate-hook is passed SQLITE_DELETE, even though this ** operation should really be an SQLITE_UPDATE. This is probably - ** incorrect, but is convenient because at this point the new.* values - ** are not easily obtainable. And for the sessions module, an - ** SQLITE_UPDATE where the PK columns do not change is handled in the + ** incorrect, but is convenient because at this point the new.* values + ** are not easily obtainable. And for the sessions module, an + ** SQLITE_UPDATE where the PK columns do not change is handled in the ** same way as an SQLITE_DELETE (the SQLITE_DELETE code is actually ** slightly more efficient). Since you cannot write to a PK column ** using the incremental-blob API, this works. For the sessions module ** anyhow. */ - sqlite3_int64 iKey; - iKey = sqlite3BtreeIntegerKey(p->pCsr); - sqlite3VdbePreUpdateHook( - v, v->apCsr[0], SQLITE_DELETE, p->zDb, p->pTab, iKey, -1 - ); + if( sqlite3BtreeCursorIsValidNN(p->pCsr)==0 ){ + /* If the cursor is not currently valid, try to reseek it. This + ** always either fails or finds the correct row - the cursor will + ** have been marked permanently CURSOR_INVALID if the open row has + ** been deleted. */ + int bDiff = 0; + rc = sqlite3BtreeCursorRestore(p->pCsr, &bDiff); + assert( bDiff==0 || sqlite3BtreeCursorIsValidNN(p->pCsr)==0 ); + } + if( sqlite3BtreeCursorIsValidNN(p->pCsr) ){ + sqlite3_int64 iKey; + iKey = sqlite3BtreeIntegerKey(p->pCsr); + assert( v->apCsr[0]!=0 ); + assert( v->apCsr[0]->eCurType==CURTYPE_BTREE ); + sqlite3VdbePreUpdateHook( + v, v->apCsr[0], SQLITE_DELETE, p->zDb, p->pTab, iKey, -1, p->iCol + ); + } + } + if( rc==SQLITE_OK ){ + rc = xCall(p->pCsr, iOffset+p->iOffset, n, z); } +#else + rc = xCall(p->pCsr, iOffset+p->iOffset, n, z); #endif - rc = xCall(p->pCsr, iOffset+p->iOffset, n, z); sqlite3BtreeLeaveCursor(p->pCsr); if( rc==SQLITE_ABORT ){ sqlite3VdbeFinalize(v); @@ -91681,8 +106487,8 @@ SQLITE_API int sqlite3_blob_bytes(sqlite3_blob *pBlob){ ** ** If an error occurs, or if the specified row does not exist or does not ** contain a blob or text value, then an error code is returned and the -** database handle error code and message set. If this happens, then all -** subsequent calls to sqlite3_blob_xxx() functions (except blob_close()) +** database handle error code and message set. If this happens, then all +** subsequent calls to sqlite3_blob_xxx() functions (except blob_close()) ** immediately return SQLITE_ABORT. */ SQLITE_API int sqlite3_blob_reopen(sqlite3_blob *pBlob, sqlite3_int64 iRow){ @@ -91701,9 +106507,10 @@ SQLITE_API int sqlite3_blob_reopen(sqlite3_blob *pBlob, sqlite3_int64 iRow){ rc = SQLITE_ABORT; }else{ char *zErr; + ((Vdbe*)p->pStmt)->rc = SQLITE_OK; rc = blobSeekToRow(p, iRow, &zErr); if( rc!=SQLITE_OK ){ - sqlite3ErrorWithMsg(db, rc, (zErr ? "%s" : 0), zErr); + sqlite3ErrorWithMsg(db, rc, (zErr ? "%s" : (char*)0), zErr); sqlite3DbFree(db, zErr); } assert( rc!=SQLITE_SCHEMA ); @@ -91776,7 +106583,7 @@ SQLITE_API int sqlite3_blob_reopen(sqlite3_blob *pBlob, sqlite3_int64 iRow){ ** is like Close() followed by Init() only ** much faster. ** -** The interfaces above must be called in a particular order. Write() can +** The interfaces above must be called in a particular order. Write() can ** only occur in between Init()/Reset() and Rewind(). Next(), Rowkey(), and ** Compare() can only occur in between Rewind() and Close()/Reset(). i.e. ** @@ -91784,16 +106591,16 @@ SQLITE_API int sqlite3_blob_reopen(sqlite3_blob *pBlob, sqlite3_int64 iRow){ ** for each record: Write() ** Rewind() ** Rowkey()/Compare() -** Next() +** Next() ** Close() ** ** Algorithm: ** -** Records passed to the sorter via calls to Write() are initially held +** Records passed to the sorter via calls to Write() are initially held ** unsorted in main memory. Assuming the amount of memory used never exceeds ** a threshold, when Rewind() is called the set of records is sorted using ** an in-memory merge sort. In this case, no temporary files are required -** and subsequent calls to Rowkey(), Next() and Compare() read records +** and subsequent calls to Rowkey(), Next() and Compare() read records ** directly from main memory. ** ** If the amount of space used to store records in main memory exceeds the @@ -91803,10 +106610,10 @@ SQLITE_API int sqlite3_blob_reopen(sqlite3_blob *pBlob, sqlite3_int64 iRow){ ** of PMAs may be created by merging existing PMAs together - for example ** merging two or more level-0 PMAs together creates a level-1 PMA. ** -** The threshold for the amount of main memory to use before flushing +** The threshold for the amount of main memory to use before flushing ** records to a PMA is roughly the same as the limit configured for the -** page-cache of the main database. Specifically, the threshold is set to -** the value returned by "PRAGMA main.page_size" multipled by +** page-cache of the main database. Specifically, the threshold is set to +** the value returned by "PRAGMA main.page_size" multiplied by ** that returned by "PRAGMA main.cache_size", in bytes. ** ** If the sorter is running in single-threaded mode, then all PMAs generated @@ -91823,28 +106630,28 @@ SQLITE_API int sqlite3_blob_reopen(sqlite3_blob *pBlob, sqlite3_int64 iRow){ ** than zero, and (b) worker threads have been enabled at runtime by calling ** "PRAGMA threads=N" with some value of N greater than 0. ** -** When Rewind() is called, any data remaining in memory is flushed to a +** When Rewind() is called, any data remaining in memory is flushed to a ** final PMA. So at this point the data is stored in some number of sorted ** PMAs within temporary files on disk. ** ** If there are fewer than SORTER_MAX_MERGE_COUNT PMAs in total and the ** sorter is running in single-threaded mode, then these PMAs are merged -** incrementally as keys are retreived from the sorter by the VDBE. The +** incrementally as keys are retrieved from the sorter by the VDBE. The ** MergeEngine object, described in further detail below, performs this ** merge. ** ** Or, if running in multi-threaded mode, then a background thread is ** launched to merge the existing PMAs. Once the background thread has -** merged T bytes of data into a single sorted PMA, the main thread +** merged T bytes of data into a single sorted PMA, the main thread ** begins reading keys from that PMA while the background thread proceeds ** with merging the next T bytes of data. And so on. ** -** Parameter T is set to half the value of the memory threshold used +** Parameter T is set to half the value of the memory threshold used ** by Write() above to determine when to create a new PMA. ** -** If there are more than SORTER_MAX_MERGE_COUNT PMAs in total when -** Rewind() is called, then a hierarchy of incremental-merges is used. -** First, T bytes of data from the first SORTER_MAX_MERGE_COUNT PMAs on +** If there are more than SORTER_MAX_MERGE_COUNT PMAs in total when +** Rewind() is called, then a hierarchy of incremental-merges is used. +** First, T bytes of data from the first SORTER_MAX_MERGE_COUNT PMAs on ** disk are merged together. Then T bytes of data from the second set, and ** so on, such that no operation ever merges more than SORTER_MAX_MERGE_COUNT ** PMAs at a time. This done is to improve locality. @@ -91859,7 +106666,7 @@ SQLITE_API int sqlite3_blob_reopen(sqlite3_blob *pBlob, sqlite3_int64 iRow){ /* #include "sqliteInt.h" */ /* #include "vdbeInt.h" */ -/* +/* ** If SQLITE_DEBUG_SORTER_THREADS is defined, this module outputs various ** messages to stderr that may be helpful in understanding the performance ** characteristics of the sorter in multi-threaded mode. @@ -91888,7 +106695,7 @@ typedef struct SorterList SorterList; /* In-memory list of records */ typedef struct IncrMerger IncrMerger; /* Read & merge multiple PMAs */ /* -** A container for a temp file handle and the current amount of data +** A container for a temp file handle and the current amount of data ** stored in the file. */ struct SorterFile { @@ -91907,7 +106714,7 @@ struct SorterFile { struct SorterList { SorterRecord *pList; /* Linked list of records */ u8 *aMemory; /* If non-NULL, bulk memory to hold pList */ - int szPMA; /* Size of pList as PMA in bytes */ + i64 szPMA; /* Size of pList as PMA in bytes */ }; /* @@ -91928,17 +106735,17 @@ struct SorterList { ** the MergeEngine.nTree variable. ** ** The final (N/2) elements of aTree[] contain the results of comparing -** pairs of PMA keys together. Element i contains the result of +** pairs of PMA keys together. Element i contains the result of ** comparing aReadr[2*i-N] and aReadr[2*i-N+1]. Whichever key is smaller, the -** aTree element is set to the index of it. +** aTree element is set to the index of it. ** ** For the purposes of this comparison, EOF is considered greater than any ** other key value. If the keys are equal (only possible with two EOF ** values), it doesn't matter which index is stored. ** -** The (N/4) elements of aTree[] that precede the final (N/2) described +** The (N/4) elements of aTree[] that precede the final (N/2) described ** above contains the index of the smallest of each block of 4 PmaReaders -** And so on. So that aTree[1] contains the index of the PmaReader that +** And so on. So that aTree[1] contains the index of the PmaReader that ** currently points to the smallest key value. aTree[0] is unused. ** ** Example: @@ -91954,7 +106761,7 @@ struct SorterList { ** ** aTree[] = { X, 5 0, 5 0, 3, 5, 6 } ** -** The current element is "Apple" (the value of the key indicated by +** The current element is "Apple" (the value of the key indicated by ** PmaReader 5). When the Next() operation is invoked, PmaReader 5 will ** be advanced to the next key in its segment. Say the next key is ** "Eggplant": @@ -91992,11 +106799,11 @@ struct MergeEngine { ** ** Essentially, this structure contains all those fields of the VdbeSorter ** structure for which each thread requires a separate instance. For example, -** each thread requries its own UnpackedRecord object to unpack records in +** each thread requeries its own UnpackedRecord object to unpack records in ** as part of comparison operations. ** -** Before a background thread is launched, variable bDone is set to 0. Then, -** right before it exits, the thread itself sets bDone to 1. This is used for +** Before a background thread is launched, variable bDone is set to 0. Then, +** right before it exits, the thread itself sets bDone to 1. This is used for ** two purposes: ** ** 1. When flushing the contents of memory to a level-0 PMA on disk, to @@ -92016,18 +106823,19 @@ typedef int (*SorterCompare)(SortSubtask*,int*,const void*,int,const void*,int); struct SortSubtask { SQLiteThread *pThread; /* Background thread, if any */ int bDone; /* Set if thread is finished but not joined */ + int nPMA; /* Number of PMAs currently in file */ VdbeSorter *pSorter; /* Sorter that owns this sub-task */ UnpackedRecord *pUnpacked; /* Space to unpack a record */ SorterList list; /* List for thread to write to a PMA */ - int nPMA; /* Number of PMAs currently in file */ SorterCompare xCompare; /* Compare function to use */ SorterFile file; /* Temp file for level-0 PMAs */ SorterFile file2; /* Space for other PMAs */ + u64 nSpill; /* Total bytes written by this task */ }; /* -** Main sorter structure. A single instance of this is allocated for each +** Main sorter structure. A single instance of this is allocated for each ** sorter cursor created by the VDBE. ** ** mxKeysize: @@ -92053,9 +106861,12 @@ struct VdbeSorter { u8 iPrev; /* Previous thread used to flush PMA */ u8 nTask; /* Size of aTask[] array */ u8 typeMask; - SortSubtask aTask[1]; /* One or more subtasks */ + SortSubtask aTask[FLEXARRAY]; /* One or more subtasks */ }; +/* Size (in bytes) of a VdbeSorter object that works with N or fewer subtasks */ +#define SZ_VDBESORTER(N) (offsetof(VdbeSorter,aTask)+(N)*sizeof(SortSubtask)) + #define SORTER_TYPE_INTEGER 0x01 #define SORTER_TYPE_TEXT 0x02 @@ -92064,7 +106875,7 @@ struct VdbeSorter { ** PMA, in sorted order. The next key to be read is cached in nKey/aKey. ** aKey might point into aMap or into aBuffer. If neither of those locations ** contain a contiguous representation of the key, then aAlloc is allocated -** and the key is copied into aAlloc and aKey is made to poitn to aAlloc. +** and the key is copied into aAlloc and aKey is made to point to aAlloc. ** ** pFd==0 at EOF. */ @@ -92083,21 +106894,21 @@ struct PmaReader { }; /* -** Normally, a PmaReader object iterates through an existing PMA stored +** Normally, a PmaReader object iterates through an existing PMA stored ** within a temp file. However, if the PmaReader.pIncr variable points to ** an object of the following type, it may be used to iterate/merge through ** multiple PMAs simultaneously. ** -** There are two types of IncrMerger object - single (bUseThread==0) and -** multi-threaded (bUseThread==1). +** There are two types of IncrMerger object - single (bUseThread==0) and +** multi-threaded (bUseThread==1). ** -** A multi-threaded IncrMerger object uses two temporary files - aFile[0] -** and aFile[1]. Neither file is allowed to grow to more than mxSz bytes in -** size. When the IncrMerger is initialized, it reads enough data from -** pMerger to populate aFile[0]. It then sets variables within the -** corresponding PmaReader object to read from that file and kicks off -** a background thread to populate aFile[1] with the next mxSz bytes of -** sorted record data from pMerger. +** A multi-threaded IncrMerger object uses two temporary files - aFile[0] +** and aFile[1]. Neither file is allowed to grow to more than mxSz bytes in +** size. When the IncrMerger is initialized, it reads enough data from +** pMerger to populate aFile[0]. It then sets variables within the +** corresponding PmaReader object to read from that file and kicks off +** a background thread to populate aFile[1] with the next mxSz bytes of +** sorted record data from pMerger. ** ** When the PmaReader reaches the end of aFile[0], it blocks until the ** background thread has finished populating aFile[1]. It then exchanges @@ -92108,7 +106919,7 @@ struct PmaReader { ** ** A single-threaded IncrMerger does not open any temporary files of its ** own. Instead, it has exclusive access to mxSz bytes of space beginning -** at offset iStartOff of file pTask->file2. And instead of using a +** at offset iStartOff of file pTask->file2. And instead of using a ** background thread to prepare data for the PmaReader, with a single ** threaded IncrMerger the allocate part of pTask->file2 is "refilled" with ** keys from pMerger by the calling thread whenever the PmaReader runs out @@ -92140,6 +106951,7 @@ struct PmaWriter { int iBufEnd; /* Last byte of buffer to write */ i64 iWriteOff; /* Offset of start of buffer in file */ sqlite3_file *pFd; /* File handle to write to */ + u64 nPmaSpill; /* Total number of bytes written */ }; /* @@ -92220,7 +107032,7 @@ static int vdbePmaReadBlob( assert( p->aBuffer ); - /* If there is no more data to be read from the buffer, read the next + /* If there is no more data to be read from the buffer, read the next ** p->nBuffer bytes of data from the file into it. Or, if there are less ** than p->nBuffer bytes remaining in the PMA, read all remaining data. */ iBuf = p->iReadOff % p->nBuffer; @@ -92241,11 +107053,11 @@ static int vdbePmaReadBlob( assert( rc!=SQLITE_IOERR_SHORT_READ ); if( rc!=SQLITE_OK ) return rc; } - nAvail = p->nBuffer - iBuf; + nAvail = p->nBuffer - iBuf; if( nByte<=nAvail ){ /* The requested data is available in the in-memory buffer. In this - ** case there is no need to make a copy of the data, just return a + ** case there is no need to make a copy of the data, just return a ** pointer into the buffer to the caller. */ *ppOut = &p->aBuffer[iBuf]; p->iReadOff += nByte; @@ -92277,13 +107089,14 @@ static int vdbePmaReadBlob( while( nRem>0 ){ int rc; /* vdbePmaReadBlob() return code */ int nCopy; /* Number of bytes to copy */ - u8 *aNext; /* Pointer to buffer to copy data from */ + u8 *aNext = 0; /* Pointer to buffer to copy data from */ nCopy = nRem; if( nRem>p->nBuffer ) nCopy = p->nBuffer; rc = vdbePmaReadBlob(p, nCopy, &aNext); if( rc!=SQLITE_OK ) return rc; assert( aNext!=p->aAlloc ); + assert( aNext!=0 ); memcpy(&p->aAlloc[nByte - nRem], aNext, nCopy); nRem -= nCopy; } @@ -92324,7 +107137,7 @@ static int vdbePmaReadVarint(PmaReader *p, u64 *pnOut){ /* ** Attempt to memory map file pFile. If successful, set *pp to point to the -** new mapping and return SQLITE_OK. If the mapping is not attempted +** new mapping and return SQLITE_OK. If the mapping is not attempted ** (because the file is too large or the VFS layer is configured not to use ** mmap), return SQLITE_OK and set *pp to NULL. ** @@ -92345,7 +107158,7 @@ static int vdbeSorterMapFile(SortSubtask *pTask, SorterFile *pFile, u8 **pp){ /* ** Attach PmaReader pReadr to file pFile (if it is not already attached to -** that file) and seek it to offset iOff within the file. Return SQLITE_OK +** that file) and seek it to offset iOff within the file. Return SQLITE_OK ** if successful, or an SQLite error code if an error occurs. */ static int vdbePmaReaderSeek( @@ -92435,11 +107248,11 @@ static int vdbePmaReaderNext(PmaReader *pReadr){ /* ** Initialize PmaReader pReadr to scan through the PMA stored in file pFile -** starting at offset iStart and ending at offset iEof-1. This function -** leaves the PmaReader pointing to the first key in the PMA (or EOF if the +** starting at offset iStart and ending at offset iEof-1. This function +** leaves the PmaReader pointing to the first key in the PMA (or EOF if the ** PMA is empty). ** -** If the pnByte parameter is NULL, then it is assumed that the file +** If the pnByte parameter is NULL, then it is assumed that the file ** contains a single PMA, and that that PMA omits the initial length varint. */ static int vdbePmaReaderInit( @@ -92472,7 +107285,7 @@ static int vdbePmaReaderInit( /* ** A version of vdbeSorterCompare() that assumes that it has already been -** determined that the first field of key1 is equal to the first field of +** determined that the first field of key1 is equal to the first field of ** key2. */ static int vdbeSorterCompareTail( @@ -92483,14 +107296,14 @@ static int vdbeSorterCompareTail( ){ UnpackedRecord *r2 = pTask->pUnpacked; if( *pbKey2Cached==0 ){ - sqlite3VdbeRecordUnpack(pTask->pSorter->pKeyInfo, nKey2, pKey2, r2); + sqlite3VdbeRecordUnpack(nKey2, pKey2, r2); *pbKey2Cached = 1; } return sqlite3VdbeRecordCompareWithSkip(nKey1, pKey1, r2, 1); } /* -** Compare key1 (buffer pKey1, size nKey1 bytes) with key2 (buffer pKey2, +** Compare key1 (buffer pKey1, size nKey1 bytes) with key2 (buffer pKey2, ** size nKey2 bytes). Use (pTask->pKeyInfo) for the collation sequences ** used by the comparison. Return the result of the comparison. ** @@ -92510,7 +107323,7 @@ static int vdbeSorterCompare( ){ UnpackedRecord *r2 = pTask->pUnpacked; if( !*pbKey2Cached ){ - sqlite3VdbeRecordUnpack(pTask->pSorter->pKeyInfo, nKey2, pKey2, r2); + sqlite3VdbeRecordUnpack(nKey2, pKey2, r2); *pbKey2Cached = 1; } return sqlite3VdbeRecordCompare(nKey1, pKey1, r2); @@ -92536,8 +107349,8 @@ static int vdbeSorterCompareText( int n2; int res; - getVarint32(&p1[1], n1); - getVarint32(&p2[1], n2); + getVarint32NR(&p1[1], n1); + getVarint32NR(&p2[1], n2); res = memcmp(v1, v2, (MIN(n1, n2) - 13)/2); if( res==0 ){ res = n1 - n2; @@ -92550,7 +107363,9 @@ static int vdbeSorterCompareText( ); } }else{ - if( pTask->pSorter->pKeyInfo->aSortOrder[0] ){ + assert( pTask->pSorter->pKeyInfo->aSortFlags!=0 ); + assert( !(pTask->pSorter->pKeyInfo->aSortFlags[0]&KEYINFO_ORDER_BIGNULL) ); + if( pTask->pSorter->pKeyInfo->aSortFlags[0] ){ res = res * -1; } } @@ -92612,13 +107427,15 @@ static int vdbeSorterCompareInt( } } + assert( pTask->pSorter->pKeyInfo->aSortFlags!=0 ); if( res==0 ){ if( pTask->pSorter->pKeyInfo->nKeyField>1 ){ res = vdbeSorterCompareTail( pTask, pbKey2Cached, pKey1, nKey1, pKey2, nKey2 ); } - }else if( pTask->pSorter->pKeyInfo->aSortOrder[0] ){ + }else if( pTask->pSorter->pKeyInfo->aSortFlags[0] ){ + assert( !(pTask->pSorter->pKeyInfo->aSortFlags[0]&KEYINFO_ORDER_BIGNULL) ); res = res * -1; } @@ -92634,7 +107451,7 @@ static int vdbeSorterCompareInt( ** is non-zero and the sorter is able to guarantee a stable sort, nField ** is used instead. This is used when sorting records for a CREATE INDEX ** statement. In this case, keys are always delivered to the sorter in -** order of the primary key, which happens to be make up the final part +** order of the primary key, which happens to be make up the final part ** of the records being sorted. So if the sort is stable, there is never ** any reason to compare PK fields and they can be ignored for a small ** performance boost. @@ -92654,7 +107471,7 @@ SQLITE_PRIVATE int sqlite3VdbeSorterInit( VdbeSorter *pSorter; /* The new sorter */ KeyInfo *pKeyInfo; /* Copy of pCsr->pKeyInfo with db==0 */ int szKeyInfo; /* Size of pCsr->pKeyInfo in bytes */ - int sz; /* Size of pSorter in bytes */ + i64 sz; /* Size of pSorter in bytes */ int rc = SQLITE_OK; #if SQLITE_MAX_WORKER_THREADS==0 # define nWorker 0 @@ -92679,23 +107496,35 @@ SQLITE_PRIVATE int sqlite3VdbeSorterInit( } #endif - assert( pCsr->pKeyInfo && pCsr->pBtx==0 ); + assert( pCsr->pKeyInfo ); + assert( !pCsr->isEphemeral ); assert( pCsr->eCurType==CURTYPE_SORTER ); - szKeyInfo = sizeof(KeyInfo) + (pCsr->pKeyInfo->nKeyField-1)*sizeof(CollSeq*); - sz = sizeof(VdbeSorter) + nWorker * sizeof(SortSubtask); + assert( sizeof(KeyInfo) + UMXV(pCsr->pKeyInfo->nKeyField)*sizeof(CollSeq*) + < 0x7fffffff ); + assert( pCsr->pKeyInfo->nKeyField<=pCsr->pKeyInfo->nAllField ); + szKeyInfo = SZ_KEYINFO(pCsr->pKeyInfo->nAllField); + sz = SZ_VDBESORTER(nWorker+1); pSorter = (VdbeSorter*)sqlite3DbMallocZero(db, sz + szKeyInfo); pCsr->uc.pSorter = pSorter; if( pSorter==0 ){ rc = SQLITE_NOMEM_BKPT; }else{ + Btree *pBt = db->aDb[0].pBt; pSorter->pKeyInfo = pKeyInfo = (KeyInfo*)((u8*)pSorter + sz); memcpy(pKeyInfo, pCsr->pKeyInfo, szKeyInfo); pKeyInfo->db = 0; if( nField && nWorker==0 ){ pKeyInfo->nKeyField = nField; + assert( nField<=pCsr->pKeyInfo->nAllField ); } - pSorter->pgsz = pgsz = sqlite3BtreeGetPageSize(db->aDb[0].pBt); + /* It is OK that pKeyInfo reuses the aSortFlags field from pCsr->pKeyInfo, + ** since the pCsr->pKeyInfo->aSortFlags[] array is invariant and lives + ** longer that pSorter. */ + assert( pKeyInfo->aSortFlags==pCsr->pKeyInfo->aSortFlags ); + sqlite3BtreeEnter(pBt); + pSorter->pgsz = pgsz = sqlite3BtreeGetPageSize(pBt); + sqlite3BtreeLeave(pBt); pSorter->nTask = nWorker + 1; pSorter->iPrev = (u8)(nWorker - 1); pSorter->bUseThreads = (pSorter->nTask>1); @@ -92731,8 +107560,9 @@ SQLITE_PRIVATE int sqlite3VdbeSorterInit( } } - if( pKeyInfo->nAllField<13 + if( pKeyInfo->nAllField<13 && (pKeyInfo->aColl[0]==0 || pKeyInfo->aColl[0]==db->pDfltColl) + && (pKeyInfo->aSortFlags[0] & KEYINFO_ORDER_BIGNULL)==0 ){ pSorter->typeMask = SORTER_TYPE_INTEGER | SORTER_TYPE_TEXT; } @@ -92755,7 +107585,7 @@ static void vdbeSorterRecordFree(sqlite3 *db, SorterRecord *pRecord){ } /* -** Free all resources owned by the object indicated by argument pTask. All +** Free all resources owned by the object indicated by argument pTask. All ** fields of *pTask are zeroed before returning. */ static void vdbeSortSubtaskCleanup(sqlite3 *db, SortSubtask *pTask){ @@ -92788,8 +107618,9 @@ static void vdbeSorterWorkDebug(SortSubtask *pTask, const char *zEvent){ fprintf(stderr, "%lld:%d %s\n", t, iTask, zEvent); } static void vdbeSorterRewindDebug(const char *zEvent){ - i64 t; - sqlite3OsCurrentTimeInt64(sqlite3_vfs_find(0), &t); + i64 t = 0; + sqlite3_vfs *pVfs = sqlite3_vfs_find(0); + if( ALWAYS(pVfs) ) sqlite3OsCurrentTimeInt64(pVfs, &t); fprintf(stderr, "%lld:X %s\n", t, zEvent); } static void vdbeSorterPopulateDebug( @@ -92854,7 +107685,7 @@ static int vdbeSorterCreateThread( } /* -** Join all outstanding threads launched by SorterWrite() to create +** Join all outstanding threads launched by SorterWrite() to create ** level-0 PMAs. */ static int vdbeSorterJoinAll(VdbeSorter *pSorter, int rcin){ @@ -92863,10 +107694,10 @@ static int vdbeSorterJoinAll(VdbeSorter *pSorter, int rcin){ /* This function is always called by the main user thread. ** - ** If this function is being called after SorterRewind() has been called, + ** If this function is being called after SorterRewind() has been called, ** it is possible that thread pSorter->aTask[pSorter->nTask-1].pThread ** is currently attempt to join one of the other threads. To avoid a race - ** condition where this thread also attempts to join the same object, join + ** condition where this thread also attempts to join the same object, join ** thread pSorter->aTask[pSorter->nTask-1].pThread first. */ for(i=pSorter->nTask-1; i>=0; i--){ SortSubtask *pTask = &pSorter->aTask[i]; @@ -92889,7 +107720,7 @@ static int vdbeSorterJoinAll(VdbeSorter *pSorter, int rcin){ */ static MergeEngine *vdbeMergeEngineNew(int nReader){ int N = 2; /* Smallest power of two >= nReader */ - int nByte; /* Total bytes of space to allocate */ + i64 nByte; /* Total bytes of space to allocate */ MergeEngine *pNew; /* Pointer to allocated object to return */ assert( nReader<=SORTER_MAX_MERGE_COUNT ); @@ -92979,6 +107810,12 @@ SQLITE_PRIVATE void sqlite3VdbeSorterClose(sqlite3 *db, VdbeCursor *pCsr){ assert( pCsr->eCurType==CURTYPE_SORTER ); pSorter = pCsr->uc.pSorter; if( pSorter ){ + /* Increment db->nSpill by the total number of bytes of data written + ** to temp files by this sort operation. */ + int ii; + for(ii=0; iinTask; ii++){ + db->nSpill += pSorter->aTask[ii].nSpill; + } sqlite3VdbeSorterReset(db, pSorter); sqlite3_free(pSorter->list.aMemory); sqlite3DbFree(db, pSorter); @@ -93003,7 +107840,7 @@ static void vdbeSorterExtendFile(sqlite3 *db, sqlite3_file *pFd, i64 nByte){ sqlite3OsFileControlHint(pFd, SQLITE_FCNTL_CHUNK_SIZE, &chunksize); sqlite3OsFileControlHint(pFd, SQLITE_FCNTL_SIZE_HINT, &nByte); sqlite3OsFetch(pFd, 0, (int)nByte, &p); - sqlite3OsUnfetch(pFd, 0, p); + if( p ) sqlite3OsUnfetch(pFd, 0, p); } } #else @@ -93038,8 +107875,8 @@ static int vdbeSorterOpenTempFile( } /* -** If it has not already been allocated, allocate the UnpackedRecord -** structure at pTask->pUnpacked. Return SQLITE_OK if successful (or +** If it has not already been allocated, allocate the UnpackedRecord +** structure at pTask->pUnpacked. Return SQLITE_OK if successful (or ** if no allocation was required), or SQLITE_NOMEM otherwise. */ static int vdbeSortAllocUnpacked(SortSubtask *pTask){ @@ -93102,32 +107939,28 @@ static SorterCompare vdbeSorterGetCompare(VdbeSorter *p){ if( p->typeMask==SORTER_TYPE_INTEGER ){ return vdbeSorterCompareInt; }else if( p->typeMask==SORTER_TYPE_TEXT ){ - return vdbeSorterCompareText; + return vdbeSorterCompareText; } return vdbeSorterCompare; } /* -** Sort the linked list of records headed at pTask->pList. Return -** SQLITE_OK if successful, or an SQLite error code (i.e. SQLITE_NOMEM) if +** Sort the linked list of records headed at pTask->pList. Return +** SQLITE_OK if successful, or an SQLite error code (i.e. SQLITE_NOMEM) if ** an error occurs. */ static int vdbeSorterSort(SortSubtask *pTask, SorterList *pList){ int i; - SorterRecord **aSlot; SorterRecord *p; int rc; + SorterRecord *aSlot[64]; rc = vdbeSortAllocUnpacked(pTask); if( rc!=SQLITE_OK ) return rc; p = pList->pList; pTask->xCompare = vdbeSorterGetCompare(pTask->pSorter); - - aSlot = (SorterRecord **)sqlite3MallocZero(64 * sizeof(SorterRecord *)); - if( !aSlot ){ - return SQLITE_NOMEM_BKPT; - } + memset(aSlot, 0, sizeof(aSlot)); while( p ){ SorterRecord *pNext; @@ -93145,6 +107978,10 @@ static int vdbeSorterSort(SortSubtask *pTask, SorterList *pList){ p->u.pNext = 0; for(i=0; aSlot[i]; i++){ p = vdbeSorterMerge(pTask, p, aSlot[i]); + /* ,--Each aSlot[] holds twice as much as the previous. So we cannot use + ** | up all 64 aSlots[] with only a 64-bit address space. + ** v */ + assert( ipList = p; - sqlite3_free(aSlot); - assert( pTask->pUnpacked->errCode==SQLITE_OK - || pTask->pUnpacked->errCode==SQLITE_NOMEM + assert( pTask->pUnpacked->errCode==SQLITE_OK + || pTask->pUnpacked->errCode==SQLITE_NOMEM ); return pTask->pUnpacked->errCode; } @@ -93201,10 +108037,11 @@ static void vdbePmaWriteBlob(PmaWriter *p, u8 *pData, int nData){ memcpy(&p->aBuffer[p->iBufEnd], &pData[nData-nRem], nCopy); p->iBufEnd += nCopy; if( p->iBufEnd==p->nBuffer ){ - p->eFWErr = sqlite3OsWrite(p->pFd, - &p->aBuffer[p->iBufStart], p->iBufEnd - p->iBufStart, + p->eFWErr = sqlite3OsWrite(p->pFd, + &p->aBuffer[p->iBufStart], p->iBufEnd - p->iBufStart, p->iWriteOff + p->iBufStart ); + p->nPmaSpill += (p->iBufEnd - p->iBufStart); p->iBufStart = p->iBufEnd = 0; p->iWriteOff += p->nBuffer; } @@ -93217,21 +108054,24 @@ static void vdbePmaWriteBlob(PmaWriter *p, u8 *pData, int nData){ /* ** Flush any buffered data to disk and clean up the PMA-writer object. ** The results of using the PMA-writer after this call are undefined. -** Return SQLITE_OK if flushing the buffered data succeeds or is not +** Return SQLITE_OK if flushing the buffered data succeeds or is not ** required. Otherwise, return an SQLite error code. ** ** Before returning, set *piEof to the offset immediately following the -** last byte written to the file. +** last byte written to the file. Also, increment (*pnSpill) by the total +** number of bytes written to the file. */ -static int vdbePmaWriterFinish(PmaWriter *p, i64 *piEof){ +static int vdbePmaWriterFinish(PmaWriter *p, i64 *piEof, u64 *pnSpill){ int rc; if( p->eFWErr==0 && ALWAYS(p->aBuffer) && p->iBufEnd>p->iBufStart ){ - p->eFWErr = sqlite3OsWrite(p->pFd, - &p->aBuffer[p->iBufStart], p->iBufEnd - p->iBufStart, + p->eFWErr = sqlite3OsWrite(p->pFd, + &p->aBuffer[p->iBufStart], p->iBufEnd - p->iBufStart, p->iWriteOff + p->iBufStart ); + p->nPmaSpill += (p->iBufEnd - p->iBufStart); } *piEof = (p->iWriteOff + p->iBufEnd); + *pnSpill += p->nPmaSpill; sqlite3_free(p->aBuffer); rc = p->eFWErr; memset(p, 0, sizeof(PmaWriter)); @@ -93239,11 +108079,11 @@ static int vdbePmaWriterFinish(PmaWriter *p, i64 *piEof){ } /* -** Write value iVal encoded as a varint to the PMA. Return +** Write value iVal encoded as a varint to the PMA. Return ** SQLITE_OK if successful, or an SQLite error code if an error occurs. */ static void vdbePmaWriteVarint(PmaWriter *p, u64 iVal){ - int nByte; + int nByte; u8 aByte[10]; nByte = sqlite3PutVarint(aByte, iVal); vdbePmaWriteBlob(p, aByte, nByte); @@ -93251,7 +108091,7 @@ static void vdbePmaWriteVarint(PmaWriter *p, u64 iVal){ /* ** Write the current contents of in-memory linked-list pList to a level-0 -** PMA in the temp file belonging to sub-task pTask. Return SQLITE_OK if +** PMA in the temp file belonging to sub-task pTask. Return SQLITE_OK if ** successful, or an SQLite error code otherwise. ** ** The format of a PMA is: @@ -93259,8 +108099,8 @@ static void vdbePmaWriteVarint(PmaWriter *p, u64 iVal){ ** * A varint. This varint contains the total number of bytes of content ** in the PMA (not including the varint itself). ** -** * One or more records packed end-to-end in order of ascending keys. -** Each record consists of a varint followed by a blob of data (the +** * One or more records packed end-to-end in order of ascending keys. +** Each record consists of a varint followed by a blob of data (the ** key). The varint is the number of bytes in the blob of data. */ static int vdbeSorterListToPMA(SortSubtask *pTask, SorterList *pList){ @@ -93269,7 +108109,7 @@ static int vdbeSorterListToPMA(SortSubtask *pTask, SorterList *pList){ PmaWriter writer; /* Object used to write to the file */ #ifdef SQLITE_DEBUG - /* Set iSz to the expected size of file pTask->file after writing the PMA. + /* Set iSz to the expected size of file pTask->file after writing the PMA. ** This is used by an assert() statement at the end of this function. */ i64 iSz = pList->szPMA + sqlite3VarintLen(pList->szPMA) + pTask->file.iEof; #endif @@ -93311,7 +108151,7 @@ static int vdbeSorterListToPMA(SortSubtask *pTask, SorterList *pList){ if( pList->aMemory==0 ) sqlite3_free(p); } pList->pList = p; - rc = vdbePmaWriterFinish(&writer, &pTask->file.iEof); + rc = vdbePmaWriterFinish(&writer, &pTask->file.iEof, &pTask->nSpill); } vdbeSorterWorkDebug(pTask, "exit"); @@ -93422,7 +108262,7 @@ static int vdbeSorterFlushPMA(VdbeSorter *pSorter){ SortSubtask *pTask = 0; /* Thread context used to create new PMA */ int nWorker = (pSorter->nTask-1); - /* Set the flag to indicate that at least one PMA has been written. + /* Set the flag to indicate that at least one PMA has been written. ** Or will be, anyhow. */ pSorter->bUsePMA = 1; @@ -93432,7 +108272,7 @@ static int vdbeSorterFlushPMA(VdbeSorter *pSorter){ ** the background thread from a sub-tasks previous turn is still running, ** skip it. If the first (pSorter->nTask-1) sub-tasks are all still busy, ** fall back to using the final sub-task. The first (pSorter->nTask-1) - ** sub-tasks are prefered as they use background threads - the final + ** sub-tasks are preferred as they use background threads - the final ** sub-task uses the main thread. */ for(i=0; iiPrev + i + 1) % nWorker; @@ -93449,13 +108289,16 @@ static int vdbeSorterFlushPMA(VdbeSorter *pSorter){ rc = vdbeSorterListToPMA(&pSorter->aTask[nWorker], &pSorter->list); }else{ /* Launch a background thread for this operation */ - u8 *aMem = pTask->list.aMemory; - void *pCtx = (void*)pTask; + u8 *aMem; + void *pCtx; + assert( pTask!=0 ); assert( pTask->pThread==0 && pTask->bDone==0 ); assert( pTask->list.pList==0 ); assert( pTask->list.aMemory==0 || pSorter->list.aMemory!=0 ); + aMem = pTask->list.aMemory; + pCtx = (void*)pTask; pSorter->iPrev = (u8)(pTask - pSorter->aTask); pTask->list = pSorter->list; pSorter->list.pList = 0; @@ -93487,13 +108330,13 @@ SQLITE_PRIVATE int sqlite3VdbeSorterWrite( int rc = SQLITE_OK; /* Return Code */ SorterRecord *pNew; /* New list element */ int bFlush; /* True to flush contents of memory to PMA */ - int nReq; /* Bytes of memory required */ - int nPMA; /* Bytes of PMA space required */ + i64 nReq; /* Bytes of memory required */ + i64 nPMA; /* Bytes of PMA space required */ int t; /* serial type of first record field */ assert( pCsr->eCurType==CURTYPE_SORTER ); pSorter = pCsr->uc.pSorter; - getVarint32((const u8*)&pVal->z[1], t); + getVarint32NR((const u8*)&pVal->z[1], t); if( t>0 && t<10 && t!=7 ){ pSorter->typeMask &= SORTER_TYPE_INTEGER; }else if( t>10 && (t & 0x01) ){ @@ -93510,14 +108353,14 @@ SQLITE_PRIVATE int sqlite3VdbeSorterWrite( ** If using the single large allocation mode (pSorter->aMemory!=0), then ** flush the contents of memory to a new PMA if (a) at least one value is ** already in memory and (b) the new value will not fit in memory. - ** + ** ** Or, if using separate allocations for each record, flush the contents ** of memory to a PMA if either of the following are true: ** - ** * The total memory allocated for the in-memory list is greater + ** * The total memory allocated for the in-memory list is greater ** than (page-size * cache-size), or ** - ** * The total memory allocated for the in-memory list is greater + ** * The total memory allocated for the in-memory list is greater ** than (page-size * 10) and sqlite3HeapNearlyFull() returns true. */ nReq = pVal->n + sizeof(SorterRecord); @@ -93622,7 +108465,7 @@ static int vdbeIncrPopulate(IncrMerger *pIncr){ rc = vdbeMergeEngineStep(pIncr->pMerger, &dummy); } - rc2 = vdbePmaWriterFinish(&writer, &pOut->iEof); + rc2 = vdbePmaWriterFinish(&writer, &pOut->iEof, &pTask->nSpill); if( rc==SQLITE_OK ) rc = rc2; vdbeSorterPopulateDebug(pTask, "exit"); return rc; @@ -93656,11 +108499,11 @@ static int vdbeIncrBgPopulate(IncrMerger *pIncr){ ** aFile[0] such that the PmaReader should start rereading it from the ** beginning. ** -** For single-threaded objects, this is accomplished by literally reading -** keys from pIncr->pMerger and repopulating aFile[0]. +** For single-threaded objects, this is accomplished by literally reading +** keys from pIncr->pMerger and repopulating aFile[0]. ** -** For multi-threaded objects, all that is required is to wait until the -** background thread is finished (if it is not already) and then swap +** For multi-threaded objects, all that is required is to wait until the +** background thread is finished (if it is not already) and then swap ** aFile[0] and aFile[1] in place. If the contents of pMerger have not ** been exhausted, this function also launches a new background thread ** to populate the new aFile[1]. @@ -93723,6 +108566,7 @@ static int vdbeIncrMergerNew( vdbeMergeEngineFree(pMerger); rc = SQLITE_NOMEM_BKPT; } + assert( *ppOut!=0 || rc!=SQLITE_OK ); return rc; } @@ -93800,7 +108644,7 @@ static void vdbeMergeEngineCompare( #define INCRINIT_TASK 1 #define INCRINIT_ROOT 2 -/* +/* ** Forward reference required as the vdbeIncrMergeInit() and ** vdbePmaReaderIncrInit() routines are called mutually recursively when ** building a merge tree. @@ -93809,7 +108653,7 @@ static int vdbePmaReaderIncrInit(PmaReader *pReadr, int eMode); /* ** Initialize the MergeEngine object passed as the second argument. Once this -** function returns, the first key of merged data may be read from the +** function returns, the first key of merged data may be read from the ** MergeEngine object in the usual fashion. ** ** If argument eMode is INCRINIT_ROOT, then it is assumed that any IncrMerge @@ -93819,8 +108663,8 @@ static int vdbePmaReaderIncrInit(PmaReader *pReadr, int eMode); ** required is to call vdbePmaReaderNext() on each PmaReader to point it at ** its first key. ** -** Otherwise, if eMode is any value other than INCRINIT_ROOT, then use -** vdbePmaReaderIncrMergeInit() to initialize each PmaReader that feeds data +** Otherwise, if eMode is any value other than INCRINIT_ROOT, then use +** vdbePmaReaderIncrMergeInit() to initialize each PmaReader that feeds data ** to pMerger. ** ** SQLITE_OK is returned if successful, or an SQLite error code otherwise. @@ -93875,19 +108719,19 @@ static int vdbeMergeEngineInit( ** object at (pReadr->pIncr). ** ** If argument eMode is set to INCRINIT_NORMAL, then all PmaReaders -** in the sub-tree headed by pReadr are also initialized. Data is then -** loaded into the buffers belonging to pReadr and it is set to point to +** in the sub-tree headed by pReadr are also initialized. Data is then +** loaded into the buffers belonging to pReadr and it is set to point to ** the first key in its range. ** ** If argument eMode is set to INCRINIT_TASK, then pReadr is guaranteed ** to be a multi-threaded PmaReader and this function is being called in a -** background thread. In this case all PmaReaders in the sub-tree are +** background thread. In this case all PmaReaders in the sub-tree are ** initialized as for INCRINIT_NORMAL and the aFile[1] buffer belonging to ** pReadr is populated. However, pReadr itself is not set up to point ** to its first key. A call to vdbePmaReaderNext() is still required to do -** that. +** that. ** -** The reason this function does not call vdbePmaReaderNext() immediately +** The reason this function does not call vdbePmaReaderNext() immediately ** in the INCRINIT_TASK case is that vdbePmaReaderNext() assumes that it has ** to block on thread (pTask->thread) before accessing aFile[1]. But, since ** this entire function is being run by thread (pTask->thread), that will @@ -93912,7 +108756,7 @@ static int vdbePmaReaderIncrMergeInit(PmaReader *pReadr, int eMode){ rc = vdbeMergeEngineInit(pTask, pIncr->pMerger, eMode); - /* Set up the required files for pIncr. A multi-theaded IncrMerge object + /* Set up the required files for pIncr. A multi-threaded IncrMerge object ** requires two temp files to itself, whereas a single-threaded object ** only requires a region of pTask->file2. */ if( rc==SQLITE_OK ){ @@ -93943,12 +108787,12 @@ static int vdbePmaReaderIncrMergeInit(PmaReader *pReadr, int eMode){ if( rc==SQLITE_OK && pIncr->bUseThread ){ /* Use the current thread to populate aFile[1], even though this ** PmaReader is multi-threaded. If this is an INCRINIT_TASK object, - ** then this function is already running in background thread - ** pIncr->pTask->thread. + ** then this function is already running in background thread + ** pIncr->pTask->thread. ** - ** If this is the INCRINIT_ROOT object, then it is running in the + ** If this is the INCRINIT_ROOT object, then it is running in the ** main VDBE thread. But that is Ok, as that thread cannot return - ** control to the VDBE or proceed with anything useful until the + ** control to the VDBE or proceed with anything useful until the ** first results are ready from this merger object anyway. */ assert( eMode==INCRINIT_ROOT || eMode==INCRINIT_TASK ); @@ -93965,7 +108809,7 @@ static int vdbePmaReaderIncrMergeInit(PmaReader *pReadr, int eMode){ #if SQLITE_MAX_WORKER_THREADS>0 /* -** The main routine for vdbePmaReaderIncrMergeInit() operations run in +** The main routine for vdbePmaReaderIncrMergeInit() operations run in ** background threads. */ static void *vdbePmaReaderBgIncrInit(void *pCtx){ @@ -93983,8 +108827,8 @@ static void *vdbePmaReaderBgIncrInit(void *pCtx){ ** (if pReadr->pIncr==0), then this function is a no-op. Otherwise, it invokes ** the vdbePmaReaderIncrMergeInit() function with the parameters passed to ** this routine to initialize the incremental merge. -** -** If the IncrMerger object is multi-threaded (IncrMerger.bUseThread==1), +** +** If the IncrMerger object is multi-threaded (IncrMerger.bUseThread==1), ** then a background thread is launched to call vdbePmaReaderIncrMergeInit(). ** Or, if the IncrMerger is single threaded, the same function is called ** using the current thread. @@ -94014,7 +108858,7 @@ static int vdbePmaReaderIncrInit(PmaReader *pReadr, int eMode){ ** to NULL and return an SQLite error code. ** ** When this function is called, *piOffset is set to the offset of the -** first PMA to read from pTask->file. Assuming no error occurs, it is +** first PMA to read from pTask->file. Assuming no error occurs, it is ** set to the offset immediately following the last byte of the last ** PMA before returning. If an error does occur, then the final value of ** *piOffset is undefined. @@ -94124,12 +108968,12 @@ static int vdbeSorterAddToTree( /* ** This function is called as part of a SorterRewind() operation on a sorter ** that has already written two or more level-0 PMAs to one or more temp -** files. It builds a tree of MergeEngine/IncrMerger/PmaReader objects that +** files. It builds a tree of MergeEngine/IncrMerger/PmaReader objects that ** can be used to incrementally merge all PMAs on disk. ** ** If successful, SQLITE_OK is returned and *ppOut set to point to the ** MergeEngine object at the root of the tree before returning. Or, if an -** error occurs, an SQLite error code is returned and the final value +** error occurs, an SQLite error code is returned and the final value ** of *ppOut is undefined. */ static int vdbeSorterMergeTreeBuild( @@ -94141,8 +108985,8 @@ static int vdbeSorterMergeTreeBuild( int iTask; #if SQLITE_MAX_WORKER_THREADS>0 - /* If the sorter uses more than one task, then create the top-level - ** MergeEngine here. This MergeEngine will read data from exactly + /* If the sorter uses more than one task, then create the top-level + ** MergeEngine here. This MergeEngine will read data from exactly ** one PmaReader per sub-task. */ assert( pSorter->bUseThreads || pSorter->nTask==1 ); if( pSorter->nTask>1 ){ @@ -94251,7 +109095,7 @@ static int vdbeSorterSetupMerge(VdbeSorter *pSorter){ } for(iTask=0; rc==SQLITE_OK && iTasknTask; iTask++){ /* Check that: - ** + ** ** a) The incremental merge object is configured to use the ** right task, and ** b) If it is using task (nTask-1), it is configured to run @@ -94314,7 +109158,7 @@ SQLITE_PRIVATE int sqlite3VdbeSorterRewind(const VdbeCursor *pCsr, int *pbEof){ return rc; } - /* Write the current in-memory list to a PMA. When the VdbeSorterWrite() + /* Write the current in-memory list to a PMA. When the VdbeSorterWrite() ** function flushes the contents of memory to disk, it immediately always ** creates a new list consisting of a single key immediately afterwards. ** So the list is never empty at this point. */ @@ -94326,7 +109170,7 @@ SQLITE_PRIVATE int sqlite3VdbeSorterRewind(const VdbeCursor *pCsr, int *pbEof){ vdbeSorterRewindDebug("rewind"); - /* Assuming no errors have occurred, set up a merger structure to + /* Assuming no errors have occurred, set up a merger structure to ** incrementally read and merge all remaining PMAs. */ assert( pSorter->pReader==0 ); if( rc==SQLITE_OK ){ @@ -94380,7 +109224,7 @@ SQLITE_PRIVATE int sqlite3VdbeSorterNext(sqlite3 *db, const VdbeCursor *pCsr){ } /* -** Return a pointer to a buffer owned by the sorter that contains the +** Return a pointer to a buffer owned by the sorter that contains the ** current key. */ static void *vdbeSorterRowkey( @@ -94467,7 +109311,7 @@ SQLITE_PRIVATE int sqlite3VdbeSorterCompare( assert( r2->nField==nKeyCol ); pKey = vdbeSorterRowkey(pSorter, &nKey); - sqlite3VdbeRecordUnpack(pKeyInfo, nKey, pKey, r2); + sqlite3VdbeRecordUnpack(nKey, pKey, r2); for(i=0; iaMem[i].flags & MEM_Null ){ *pRes = -1; @@ -94480,6 +109324,455 @@ SQLITE_PRIVATE int sqlite3VdbeSorterCompare( } /************** End of vdbesort.c ********************************************/ +/************** Begin file vdbevtab.c ****************************************/ +/* +** 2020-03-23 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file implements virtual-tables for examining the bytecode content +** of a prepared statement. +*/ +/* #include "sqliteInt.h" */ +#if defined(SQLITE_ENABLE_BYTECODE_VTAB) && !defined(SQLITE_OMIT_VIRTUALTABLE) +/* #include "vdbeInt.h" */ + +/* An instance of the bytecode() table-valued function. +*/ +typedef struct bytecodevtab bytecodevtab; +struct bytecodevtab { + sqlite3_vtab base; /* Base class - must be first */ + sqlite3 *db; /* Database connection */ + int bTablesUsed; /* 2 for tables_used(). 0 for bytecode(). */ +}; + +/* A cursor for scanning through the bytecode +*/ +typedef struct bytecodevtab_cursor bytecodevtab_cursor; +struct bytecodevtab_cursor { + sqlite3_vtab_cursor base; /* Base class - must be first */ + sqlite3_stmt *pStmt; /* The statement whose bytecode is displayed */ + int iRowid; /* The rowid of the output table */ + int iAddr; /* Address */ + int needFinalize; /* Cursors owns pStmt and must finalize it */ + int showSubprograms; /* Provide a listing of subprograms */ + Op *aOp; /* Operand array */ + char *zP4; /* Rendered P4 value */ + const char *zType; /* tables_used.type */ + const char *zSchema; /* tables_used.schema */ + const char *zName; /* tables_used.name */ + Mem sub; /* Subprograms */ +}; + +/* +** Create a new bytecode() table-valued function. +*/ +static int bytecodevtabConnect( + sqlite3 *db, + void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVtab, + char **pzErr +){ + bytecodevtab *pNew; + int rc; + int isTabUsed = pAux!=0; + const char *azSchema[2] = { + /* bytecode() schema */ + "CREATE TABLE x(" + "addr INT," + "opcode TEXT," + "p1 INT," + "p2 INT," + "p3 INT," + "p4 TEXT," + "p5 INT," + "comment TEXT," + "subprog TEXT," + "nexec INT," + "ncycle INT," + "stmt HIDDEN" + ");", + + /* Tables_used() schema */ + "CREATE TABLE x(" + "type TEXT," + "schema TEXT," + "name TEXT," + "wr INT," + "subprog TEXT," + "stmt HIDDEN" + ");" + }; + + (void)argc; + (void)argv; + (void)pzErr; + rc = sqlite3_declare_vtab(db, azSchema[isTabUsed]); + if( rc==SQLITE_OK ){ + pNew = sqlite3_malloc( sizeof(*pNew) ); + *ppVtab = (sqlite3_vtab*)pNew; + if( pNew==0 ) return SQLITE_NOMEM; + memset(pNew, 0, sizeof(*pNew)); + pNew->db = db; + pNew->bTablesUsed = isTabUsed*2; + } + return rc; +} + +/* +** This method is the destructor for bytecodevtab objects. +*/ +static int bytecodevtabDisconnect(sqlite3_vtab *pVtab){ + bytecodevtab *p = (bytecodevtab*)pVtab; + sqlite3_free(p); + return SQLITE_OK; +} + +/* +** Constructor for a new bytecodevtab_cursor object. +*/ +static int bytecodevtabOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ + bytecodevtab *pVTab = (bytecodevtab*)p; + bytecodevtab_cursor *pCur; + pCur = sqlite3_malloc( sizeof(*pCur) ); + if( pCur==0 ) return SQLITE_NOMEM; + memset(pCur, 0, sizeof(*pCur)); + sqlite3VdbeMemInit(&pCur->sub, pVTab->db, 1); + *ppCursor = &pCur->base; + return SQLITE_OK; +} + +/* +** Clear all internal content from a bytecodevtab cursor. +*/ +static void bytecodevtabCursorClear(bytecodevtab_cursor *pCur){ + sqlite3_free(pCur->zP4); + pCur->zP4 = 0; + sqlite3VdbeMemRelease(&pCur->sub); + sqlite3VdbeMemSetNull(&pCur->sub); + if( pCur->needFinalize ){ + sqlite3_finalize(pCur->pStmt); + } + pCur->pStmt = 0; + pCur->needFinalize = 0; + pCur->zType = 0; + pCur->zSchema = 0; + pCur->zName = 0; +} + +/* +** Destructor for a bytecodevtab_cursor. +*/ +static int bytecodevtabClose(sqlite3_vtab_cursor *cur){ + bytecodevtab_cursor *pCur = (bytecodevtab_cursor*)cur; + bytecodevtabCursorClear(pCur); + sqlite3_free(pCur); + return SQLITE_OK; +} + + +/* +** Advance a bytecodevtab_cursor to its next row of output. +*/ +static int bytecodevtabNext(sqlite3_vtab_cursor *cur){ + bytecodevtab_cursor *pCur = (bytecodevtab_cursor*)cur; + bytecodevtab *pTab = (bytecodevtab*)cur->pVtab; + int rc; + if( pCur->zP4 ){ + sqlite3_free(pCur->zP4); + pCur->zP4 = 0; + } + if( pCur->zName ){ + pCur->zName = 0; + pCur->zType = 0; + pCur->zSchema = 0; + } + rc = sqlite3VdbeNextOpcode( + (Vdbe*)pCur->pStmt, + pCur->showSubprograms ? &pCur->sub : 0, + pTab->bTablesUsed, + &pCur->iRowid, + &pCur->iAddr, + &pCur->aOp); + if( rc!=SQLITE_OK ){ + sqlite3VdbeMemSetNull(&pCur->sub); + pCur->aOp = 0; + } + return SQLITE_OK; +} + +/* +** Return TRUE if the cursor has been moved off of the last +** row of output. +*/ +static int bytecodevtabEof(sqlite3_vtab_cursor *cur){ + bytecodevtab_cursor *pCur = (bytecodevtab_cursor*)cur; + return pCur->aOp==0; +} + +/* +** Return values of columns for the row at which the bytecodevtab_cursor +** is currently pointing. +*/ +static int bytecodevtabColumn( + sqlite3_vtab_cursor *cur, /* The cursor */ + sqlite3_context *ctx, /* First argument to sqlite3_result_...() */ + int i /* Which column to return */ +){ + bytecodevtab_cursor *pCur = (bytecodevtab_cursor*)cur; + bytecodevtab *pVTab = (bytecodevtab*)cur->pVtab; + Op *pOp = pCur->aOp + pCur->iAddr; + if( pVTab->bTablesUsed ){ + if( i==4 ){ + i = 8; + }else{ + if( i<=2 && pCur->zType==0 ){ + Schema *pSchema; + HashElem *k; + int iDb = pOp->p3; + Pgno iRoot = (Pgno)pOp->p2; + sqlite3 *db = pVTab->db; + pSchema = db->aDb[iDb].pSchema; + pCur->zSchema = db->aDb[iDb].zDbSName; + for(k=sqliteHashFirst(&pSchema->tblHash); k; k=sqliteHashNext(k)){ + Table *pTab = (Table*)sqliteHashData(k); + if( !IsVirtual(pTab) && pTab->tnum==iRoot ){ + pCur->zName = pTab->zName; + pCur->zType = "table"; + break; + } + } + if( pCur->zName==0 ){ + for(k=sqliteHashFirst(&pSchema->idxHash); k; k=sqliteHashNext(k)){ + Index *pIdx = (Index*)sqliteHashData(k); + if( pIdx->tnum==iRoot ){ + pCur->zName = pIdx->zName; + pCur->zType = "index"; + } + } + } + } + i += 20; + } + } + switch( i ){ + case 0: /* addr */ + sqlite3_result_int(ctx, pCur->iAddr); + break; + case 1: /* opcode */ + sqlite3_result_text(ctx, (char*)sqlite3OpcodeName(pOp->opcode), + -1, SQLITE_STATIC); + break; + case 2: /* p1 */ + sqlite3_result_int(ctx, pOp->p1); + break; + case 3: /* p2 */ + sqlite3_result_int(ctx, pOp->p2); + break; + case 4: /* p3 */ + sqlite3_result_int(ctx, pOp->p3); + break; + case 5: /* p4 */ + case 7: /* comment */ + if( pCur->zP4==0 ){ + pCur->zP4 = sqlite3VdbeDisplayP4(pVTab->db, pOp); + } + if( i==5 ){ + sqlite3_result_text(ctx, pCur->zP4, -1, SQLITE_STATIC); + }else{ +#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS + char *zCom = sqlite3VdbeDisplayComment(pVTab->db, pOp, pCur->zP4); + sqlite3_result_text(ctx, zCom, -1, sqlite3_free); +#endif + } + break; + case 6: /* p5 */ + sqlite3_result_int(ctx, pOp->p5); + break; + case 8: { /* subprog */ + Op *aOp = pCur->aOp; + assert( aOp[0].opcode==OP_Init ); + assert( aOp[0].p4.z==0 || strncmp(aOp[0].p4.z,"-" "- ",3)==0 ); + if( pCur->iRowid==pCur->iAddr+1 ){ + break; /* Result is NULL for the main program */ + }else if( aOp[0].p4.z!=0 ){ + sqlite3_result_text(ctx, aOp[0].p4.z+3, -1, SQLITE_STATIC); + }else{ + sqlite3_result_text(ctx, "(FK)", 4, SQLITE_STATIC); + } + break; + } + +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + case 9: /* nexec */ + sqlite3_result_int64(ctx, pOp->nExec); + break; + case 10: /* ncycle */ + sqlite3_result_int64(ctx, pOp->nCycle); + break; +#else + case 9: /* nexec */ + case 10: /* ncycle */ + sqlite3_result_int(ctx, 0); + break; +#endif + + case 20: /* tables_used.type */ + sqlite3_result_text(ctx, pCur->zType, -1, SQLITE_STATIC); + break; + case 21: /* tables_used.schema */ + sqlite3_result_text(ctx, pCur->zSchema, -1, SQLITE_STATIC); + break; + case 22: /* tables_used.name */ + sqlite3_result_text(ctx, pCur->zName, -1, SQLITE_STATIC); + break; + case 23: /* tables_used.wr */ + sqlite3_result_int(ctx, pOp->opcode==OP_OpenWrite); + break; + } + return SQLITE_OK; +} + +/* +** Return the rowid for the current row. In this implementation, the +** rowid is the same as the output value. +*/ +static int bytecodevtabRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ + bytecodevtab_cursor *pCur = (bytecodevtab_cursor*)cur; + *pRowid = pCur->iRowid; + return SQLITE_OK; +} + +/* +** Initialize a cursor. +** +** idxNum==0 means show all subprograms +** idxNum==1 means show only the main bytecode and omit subprograms. +*/ +static int bytecodevtabFilter( + sqlite3_vtab_cursor *pVtabCursor, + int idxNum, const char *idxStr, + int argc, sqlite3_value **argv +){ + bytecodevtab_cursor *pCur = (bytecodevtab_cursor *)pVtabCursor; + bytecodevtab *pVTab = (bytecodevtab *)pVtabCursor->pVtab; + int rc = SQLITE_OK; + (void)idxStr; + + bytecodevtabCursorClear(pCur); + pCur->iRowid = 0; + pCur->iAddr = 0; + pCur->showSubprograms = idxNum==0; + assert( argc==1 ); + if( sqlite3_value_type(argv[0])==SQLITE_TEXT ){ + const char *zSql = (const char*)sqlite3_value_text(argv[0]); + if( zSql==0 ){ + rc = SQLITE_NOMEM; + }else{ + rc = sqlite3_prepare_v2(pVTab->db, zSql, -1, &pCur->pStmt, 0); + pCur->needFinalize = 1; + } + }else{ + pCur->pStmt = (sqlite3_stmt*)sqlite3_value_pointer(argv[0],"stmt-pointer"); + } + if( pCur->pStmt==0 ){ + pVTab->base.zErrMsg = sqlite3_mprintf( + "argument to %s() is not a valid SQL statement", + pVTab->bTablesUsed ? "tables_used" : "bytecode" + ); + rc = SQLITE_ERROR; + }else{ + bytecodevtabNext(pVtabCursor); + } + return rc; +} + +/* +** We must have a single stmt=? constraint that will be passed through +** into the xFilter method. If there is no valid stmt=? constraint, +** then return an SQLITE_CONSTRAINT error. +*/ +static int bytecodevtabBestIndex( + sqlite3_vtab *tab, + sqlite3_index_info *pIdxInfo +){ + int i; + int rc = SQLITE_CONSTRAINT; + struct sqlite3_index_constraint *p; + bytecodevtab *pVTab = (bytecodevtab*)tab; + int iBaseCol = pVTab->bTablesUsed ? 4 : 10; + pIdxInfo->estimatedCost = (double)100; + pIdxInfo->estimatedRows = 100; + pIdxInfo->idxNum = 0; + for(i=0, p=pIdxInfo->aConstraint; inConstraint; i++, p++){ + if( p->usable==0 ) continue; + if( p->op==SQLITE_INDEX_CONSTRAINT_EQ && p->iColumn==iBaseCol+1 ){ + rc = SQLITE_OK; + pIdxInfo->aConstraintUsage[i].omit = 1; + pIdxInfo->aConstraintUsage[i].argvIndex = 1; + } + if( p->op==SQLITE_INDEX_CONSTRAINT_ISNULL && p->iColumn==iBaseCol ){ + pIdxInfo->aConstraintUsage[i].omit = 1; + pIdxInfo->idxNum = 1; + } + } + return rc; +} + +/* +** This following structure defines all the methods for the +** virtual table. +*/ +static sqlite3_module bytecodevtabModule = { + /* iVersion */ 0, + /* xCreate */ 0, + /* xConnect */ bytecodevtabConnect, + /* xBestIndex */ bytecodevtabBestIndex, + /* xDisconnect */ bytecodevtabDisconnect, + /* xDestroy */ 0, + /* xOpen */ bytecodevtabOpen, + /* xClose */ bytecodevtabClose, + /* xFilter */ bytecodevtabFilter, + /* xNext */ bytecodevtabNext, + /* xEof */ bytecodevtabEof, + /* xColumn */ bytecodevtabColumn, + /* xRowid */ bytecodevtabRowid, + /* xUpdate */ 0, + /* xBegin */ 0, + /* xSync */ 0, + /* xCommit */ 0, + /* xRollback */ 0, + /* xFindMethod */ 0, + /* xRename */ 0, + /* xSavepoint */ 0, + /* xRelease */ 0, + /* xRollbackTo */ 0, + /* xShadowName */ 0, + /* xIntegrity */ 0 +}; + + +SQLITE_PRIVATE int sqlite3VdbeBytecodeVtabInit(sqlite3 *db){ + int rc; + rc = sqlite3_create_module(db, "bytecode", &bytecodevtabModule, 0); + if( rc==SQLITE_OK ){ + rc = sqlite3_create_module(db, "tables_used", &bytecodevtabModule, &db); + } + return rc; +} +#elif defined(SQLITE_ENABLE_BYTECODE_VTAB) +SQLITE_PRIVATE int sqlite3VdbeBytecodeVtabInit(sqlite3 *db){ return SQLITE_OK; } +#endif /* SQLITE_ENABLE_BYTECODE_VTAB */ + +/************** End of vdbevtab.c ********************************************/ /************** Begin file memjournal.c **************************************/ /* ** 2008 October 7 @@ -94553,7 +109846,6 @@ struct MemJournal { int nChunkSize; /* In-memory chunk-size */ int nSpill; /* Bytes of data before flushing */ - int nSize; /* Bytes of data currently in memory */ FileChunk *pFirst; /* Head of in-memory chunk-list */ FilePoint endpoint; /* Pointer to the end of the file */ FilePoint readpoint; /* Pointer to the end of the last xRead() */ @@ -94579,18 +109871,13 @@ static int memjrnlRead( int iChunkOffset; FileChunk *pChunk; -#if defined(SQLITE_ENABLE_ATOMIC_WRITE) \ - || defined(SQLITE_ENABLE_BATCH_ATOMIC_WRITE) if( (iAmt+iOfst)>p->endpoint.iOffset ){ return SQLITE_IOERR_SHORT_READ; } -#endif - - assert( (iAmt+iOfst)<=p->endpoint.iOffset ); assert( p->readpoint.iOffset==0 || p->readpoint.pChunk!=0 ); if( p->readpoint.iOffset!=iOfst || iOfst==0 ){ sqlite3_int64 iOff = 0; - for(pChunk=p->pFirst; + for(pChunk=p->pFirst; ALWAYS(pChunk) && (iOff+p->nChunkSize)<=iOfst; pChunk=pChunk->pNext ){ @@ -94619,14 +109906,13 @@ static int memjrnlRead( /* ** Free the list of FileChunk structures headed at MemJournal.pFirst. */ -static void memjrnlFreeChunks(MemJournal *p){ +static void memjrnlFreeChunks(FileChunk *pFirst){ FileChunk *pIter; FileChunk *pNext; - for(pIter=p->pFirst; pIter; pIter=pNext){ + for(pIter=pFirst; pIter; pIter=pNext){ pNext = pIter->pNext; sqlite3_free(pIter); - } - p->pFirst = 0; + } } /* @@ -94653,7 +109939,7 @@ static int memjrnlCreateFile(MemJournal *p){ } if( rc==SQLITE_OK ){ /* No error has occurred. Free the in-memory buffers. */ - memjrnlFreeChunks(©); + memjrnlFreeChunks(copy.pFirst); } } if( rc!=SQLITE_OK ){ @@ -94668,6 +109954,9 @@ static int memjrnlCreateFile(MemJournal *p){ } +/* Forward reference */ +static int memjrnlTruncate(sqlite3_file *pJfd, sqlite_int64 size); + /* ** Write data to the file. */ @@ -94697,23 +109986,21 @@ static int memjrnlWrite( ** access writes are not required. The only exception to this is when ** the in-memory journal is being used by a connection using the ** atomic-write optimization. In this case the first 28 bytes of the - ** journal file may be written as part of committing the transaction. */ - assert( iOfst==p->endpoint.iOffset || iOfst==0 ); -#if defined(SQLITE_ENABLE_ATOMIC_WRITE) \ - || defined(SQLITE_ENABLE_BATCH_ATOMIC_WRITE) + ** journal file may be written as part of committing the transaction. */ + assert( iOfst<=p->endpoint.iOffset ); + if( iOfst>0 && iOfst!=p->endpoint.iOffset ){ + memjrnlTruncate(pJfd, iOfst); + } if( iOfst==0 && p->pFirst ){ assert( p->nChunkSize>iAmt ); memcpy((u8*)p->pFirst->zChunk, zBuf, iAmt); - }else -#else - assert( iOfst>0 || p->pFirst==0 ); -#endif - { + }else{ while( nWrite>0 ){ FileChunk *pChunk = p->endpoint.pChunk; int iChunkOffset = (int)(p->endpoint.iOffset%p->nChunkSize); int iSpace = MIN(nWrite, p->nChunkSize - iChunkOffset); + assert( pChunk!=0 || iChunkOffset==0 ); if( iChunkOffset==0 ){ /* New chunk is required to extend the file. */ FileChunk *pNew = sqlite3_malloc(fileChunkSize(p->nChunkSize)); @@ -94728,15 +110015,15 @@ static int memjrnlWrite( assert( !p->pFirst ); p->pFirst = pNew; } - p->endpoint.pChunk = pNew; + pChunk = p->endpoint.pChunk = pNew; } - memcpy((u8*)p->endpoint.pChunk->zChunk + iChunkOffset, zWrite, iSpace); + assert( pChunk!=0 ); + memcpy((u8*)pChunk->zChunk + iChunkOffset, zWrite, iSpace); zWrite += iSpace; nWrite -= iSpace; p->endpoint.iOffset += iSpace; } - p->nSize = iAmt + iOfst; } } @@ -94744,19 +110031,29 @@ static int memjrnlWrite( } /* -** Truncate the file. -** -** If the journal file is already on disk, truncate it there. Or, if it -** is still in main memory but is being truncated to zero bytes in size, -** ignore +** Truncate the in-memory file. */ static int memjrnlTruncate(sqlite3_file *pJfd, sqlite_int64 size){ MemJournal *p = (MemJournal *)pJfd; - if( ALWAYS(size==0) ){ - memjrnlFreeChunks(p); - p->nSize = 0; - p->endpoint.pChunk = 0; - p->endpoint.iOffset = 0; + assert( p->endpoint.pChunk==0 || p->endpoint.pChunk->pNext==0 ); + if( sizeendpoint.iOffset ){ + FileChunk *pIter = 0; + if( size==0 ){ + memjrnlFreeChunks(p->pFirst); + p->pFirst = 0; + }else{ + i64 iOff = p->nChunkSize; + for(pIter=p->pFirst; ALWAYS(pIter) && iOffpNext){ + iOff += p->nChunkSize; + } + if( ALWAYS(pIter) ){ + memjrnlFreeChunks(pIter->pNext); + pIter->pNext = 0; + } + } + + p->endpoint.pChunk = pIter; + p->endpoint.iOffset = size; p->readpoint.pChunk = 0; p->readpoint.iOffset = 0; } @@ -94768,15 +110065,15 @@ static int memjrnlTruncate(sqlite3_file *pJfd, sqlite_int64 size){ */ static int memjrnlClose(sqlite3_file *pJfd){ MemJournal *p = (MemJournal *)pJfd; - memjrnlFreeChunks(p); + memjrnlFreeChunks(p->pFirst); return SQLITE_OK; } /* ** Sync the file. ** -** If the real file has been created, call its xSync method. Otherwise, -** syncing an in-memory journal is a no-op. +** If the real file has been created, call its xSync method. Otherwise, +** syncing an in-memory journal is a no-op. */ static int memjrnlSync(sqlite3_file *pJfd, int flags){ UNUSED_PARAMETER2(pJfd, flags); @@ -94817,11 +110114,11 @@ static const struct sqlite3_io_methods MemJournalMethods = { 0 /* xUnfetch */ }; -/* -** Open a journal file. +/* +** Open a journal file. ** -** The behaviour of the journal file depends on the value of parameter -** nSpill. If nSpill is 0, then the journal file is always create and +** The behaviour of the journal file depends on the value of parameter +** nSpill. If nSpill is 0, then the journal file is always create and ** accessed using the underlying VFS. If nSpill is less than zero, then ** all content is always stored in main-memory. Finally, if nSpill is a ** positive value, then the journal file is initially created in-memory @@ -94838,6 +110135,8 @@ SQLITE_PRIVATE int sqlite3JournalOpen( ){ MemJournal *p = (MemJournal*)pJfd; + assert( zName || nSpill<0 || (flags & SQLITE_OPEN_EXCLUSIVE) ); + /* Zero the file-handle object. If nSpill was passed zero, initialize ** it using the sqlite3OsOpen() function of the underlying VFS. In this ** case none of the code in this module is executed as a result of calls @@ -94854,7 +110153,7 @@ SQLITE_PRIVATE int sqlite3JournalOpen( assert( MEMJOURNAL_DFLT_FILECHUNKSIZE==fileChunkSize(p->nChunkSize) ); } - p->pMethod = (const sqlite3_io_methods*)&MemJournalMethods; + pJfd->pMethods = (const sqlite3_io_methods*)&MemJournalMethods; p->nSpill = nSpill; p->flags = flags; p->zJournal = zName; @@ -94872,15 +110171,15 @@ SQLITE_PRIVATE void sqlite3MemJournalOpen(sqlite3_file *pJfd){ #if defined(SQLITE_ENABLE_ATOMIC_WRITE) \ || defined(SQLITE_ENABLE_BATCH_ATOMIC_WRITE) /* -** If the argument p points to a MemJournal structure that is not an +** If the argument p points to a MemJournal structure that is not an ** in-memory-only journal file (i.e. is one that was opened with a +ve -** nSpill parameter or as SQLITE_OPEN_MAIN_JOURNAL), and the underlying +** nSpill parameter or as SQLITE_OPEN_MAIN_JOURNAL), and the underlying ** file has not yet been created, create it now. */ SQLITE_PRIVATE int sqlite3JournalCreate(sqlite3_file *pJfd){ int rc = SQLITE_OK; MemJournal *p = (MemJournal*)pJfd; - if( p->pMethod==&MemJournalMethods && ( + if( pJfd->pMethods==&MemJournalMethods && ( #ifdef SQLITE_ENABLE_ATOMIC_WRITE p->nSpill>0 #else @@ -94908,7 +110207,7 @@ SQLITE_PRIVATE int sqlite3JournalIsInMemory(sqlite3_file *p){ return p->pMethods==&MemJournalMethods; } -/* +/* ** Return the number of bytes required to store a JournalFile that uses vfs ** pVfs to create the underlying on-disk files. */ @@ -94942,12 +110241,21 @@ SQLITE_PRIVATE int sqlite3JournalSize(sqlite3_vfs *pVfs){ ** Walk all expressions linked into the list of Window objects passed ** as the second argument. */ -static int walkWindowList(Walker *pWalker, Window *pList){ +static int walkWindowList(Walker *pWalker, Window *pList, int bOneOnly){ Window *pWin; for(pWin=pList; pWin; pWin=pWin->pNextWin){ - if( sqlite3WalkExprList(pWalker, pWin->pOrderBy) ) return WRC_Abort; - if( sqlite3WalkExprList(pWalker, pWin->pPartition) ) return WRC_Abort; - if( sqlite3WalkExpr(pWalker, pWin->pFilter) ) return WRC_Abort; + int rc; + rc = sqlite3WalkExprList(pWalker, pWin->pOrderBy); + if( rc ) return WRC_Abort; + rc = sqlite3WalkExprList(pWalker, pWin->pPartition); + if( rc ) return WRC_Abort; + rc = sqlite3WalkExpr(pWalker, pWin->pFilter); + if( rc ) return WRC_Abort; + rc = sqlite3WalkExpr(pWalker, pWin->pStart); + if( rc ) return WRC_Abort; + rc = sqlite3WalkExpr(pWalker, pWin->pEnd); + if( rc ) return WRC_Abort; + if( bOneOnly ) break; } return WRC_Continue; } @@ -94972,7 +110280,7 @@ static int walkWindowList(Walker *pWalker, Window *pList){ ** The return value from this routine is WRC_Abort to abandon the tree walk ** and WRC_Continue to continue. */ -static SQLITE_NOINLINE int walkExpr(Walker *pWalker, Expr *pExpr){ +SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3WalkExprNN(Walker *pWalker, Expr *pExpr){ int rc; testcase( ExprHasProperty(pExpr, EP_TokenOnly) ); testcase( ExprHasProperty(pExpr, EP_Reduced) ); @@ -94980,28 +110288,34 @@ static SQLITE_NOINLINE int walkExpr(Walker *pWalker, Expr *pExpr){ rc = pWalker->xExprCallback(pWalker, pExpr); if( rc ) return rc & WRC_Abort; if( !ExprHasProperty(pExpr,(EP_TokenOnly|EP_Leaf)) ){ - if( pExpr->pLeft && walkExpr(pWalker, pExpr->pLeft) ) return WRC_Abort; - assert( pExpr->x.pList==0 || pExpr->pRight==0 ); + assert( pExpr->x.pList==0 || pExpr->pRight==0 ); + if( pExpr->pLeft && sqlite3WalkExprNN(pWalker, pExpr->pLeft) ){ + return WRC_Abort; + } if( pExpr->pRight ){ + assert( !ExprHasProperty(pExpr, EP_WinFunc) ); pExpr = pExpr->pRight; continue; - }else if( ExprHasProperty(pExpr, EP_xIsSelect) ){ + }else if( ExprUseXSelect(pExpr) ){ + assert( !ExprHasProperty(pExpr, EP_WinFunc) ); if( sqlite3WalkSelect(pWalker, pExpr->x.pSelect) ) return WRC_Abort; - }else if( pExpr->x.pList ){ - if( sqlite3WalkExprList(pWalker, pExpr->x.pList) ) return WRC_Abort; - } + }else{ + if( pExpr->x.pList ){ + if( sqlite3WalkExprList(pWalker, pExpr->x.pList) ) return WRC_Abort; + } #ifndef SQLITE_OMIT_WINDOWFUNC - if( ExprHasProperty(pExpr, EP_WinFunc) ){ - if( walkWindowList(pWalker, pExpr->y.pWin) ) return WRC_Abort; - } + if( ExprHasProperty(pExpr, EP_WinFunc) ){ + if( walkWindowList(pWalker, pExpr->y.pWin, 1) ) return WRC_Abort; + } #endif + } } break; } return WRC_Continue; } SQLITE_PRIVATE int sqlite3WalkExpr(Walker *pWalker, Expr *pExpr){ - return pExpr ? walkExpr(pWalker,pExpr) : WRC_Continue; + return pExpr ? sqlite3WalkExprNN(pWalker,pExpr) : WRC_Continue; } /* @@ -95019,6 +110333,16 @@ SQLITE_PRIVATE int sqlite3WalkExprList(Walker *pWalker, ExprList *p){ return WRC_Continue; } +/* +** This is a no-op callback for Walker->xSelectCallback2. If this +** callback is set, then the Select->pWinDefn list is traversed. +*/ +SQLITE_PRIVATE void sqlite3WalkWinDefnDummyCallback(Walker *pWalker, Select *p){ + UNUSED_PARAMETER(pWalker); + UNUSED_PARAMETER(p); + /* No-op */ +} + /* ** Walk all expressions associated with SELECT statement p. Do ** not invoke the SELECT callback on p, but do (of course) invoke @@ -95032,12 +110356,18 @@ SQLITE_PRIVATE int sqlite3WalkSelectExpr(Walker *pWalker, Select *p){ if( sqlite3WalkExpr(pWalker, p->pHaving) ) return WRC_Abort; if( sqlite3WalkExprList(pWalker, p->pOrderBy) ) return WRC_Abort; if( sqlite3WalkExpr(pWalker, p->pLimit) ) return WRC_Abort; -#if !defined(SQLITE_OMIT_WINDOWFUNC) && !defined(SQLITE_OMIT_ALTERTABLE) - { - Parse *pParse = pWalker->pParse; - if( pParse && IN_RENAME_OBJECT ){ - int rc = walkWindowList(pWalker, p->pWinDefn); - assert( rc==WRC_Continue ); +#if !defined(SQLITE_OMIT_WINDOWFUNC) + if( p->pWinDefn ){ + Parse *pParse; + if( pWalker->xSelectCallback2==sqlite3WalkWinDefnDummyCallback + || ((pParse = pWalker->pParse)!=0 && IN_RENAME_OBJECT) +#ifndef SQLITE_OMIT_CTE + || pWalker->xSelectCallback2==sqlite3SelectPopWith +#endif + ){ + /* The following may return WRC_Abort if there are unresolvable + ** symbols (e.g. a table that does not exist) in a window definition. */ + int rc = walkWindowList(pWalker, p->pWinDefn, 0); return rc; } } @@ -95049,33 +110379,36 @@ SQLITE_PRIVATE int sqlite3WalkSelectExpr(Walker *pWalker, Select *p){ ** Walk the parse trees associated with all subqueries in the ** FROM clause of SELECT statement p. Do not invoke the select ** callback on p, but do invoke it on each FROM clause subquery -** and on any subqueries further down in the tree. Return +** and on any subqueries further down in the tree. Return ** WRC_Abort or WRC_Continue; */ SQLITE_PRIVATE int sqlite3WalkSelectFrom(Walker *pWalker, Select *p){ SrcList *pSrc; int i; - struct SrcList_item *pItem; + SrcItem *pItem; pSrc = p->pSrc; - assert( pSrc!=0 ); - for(i=pSrc->nSrc, pItem=pSrc->a; i>0; i--, pItem++){ - if( pItem->pSelect && sqlite3WalkSelect(pWalker, pItem->pSelect) ){ - return WRC_Abort; - } - if( pItem->fg.isTabFunc - && sqlite3WalkExprList(pWalker, pItem->u1.pFuncArg) - ){ - return WRC_Abort; + if( ALWAYS(pSrc) ){ + for(i=pSrc->nSrc, pItem=pSrc->a; i>0; i--, pItem++){ + if( pItem->fg.isSubquery + && sqlite3WalkSelect(pWalker, pItem->u4.pSubq->pSelect) + ){ + return WRC_Abort; + } + if( pItem->fg.isTabFunc + && sqlite3WalkExprList(pWalker, pItem->u1.pFuncArg) + ){ + return WRC_Abort; + } } } return WRC_Continue; -} +} /* ** Call sqlite3WalkExpr() for every expression in Select statement p. ** Invoke sqlite3WalkSelect() for subqueries in the FROM clause and -** on the compound select chain, p->pPrior. +** on the compound select chain, p->pPrior. ** ** If it is not NULL, the xSelectCallback() callback is invoked before ** the walk of the expressions and FROM clause. The xSelectCallback2() @@ -95109,6 +110442,43 @@ SQLITE_PRIVATE int sqlite3WalkSelect(Walker *pWalker, Select *p){ return WRC_Continue; } +/* Increase the walkerDepth when entering a subquery, and +** decrease when leaving the subquery. +*/ +SQLITE_PRIVATE int sqlite3WalkerDepthIncrease(Walker *pWalker, Select *pSelect){ + UNUSED_PARAMETER(pSelect); + pWalker->walkerDepth++; + return WRC_Continue; +} +SQLITE_PRIVATE void sqlite3WalkerDepthDecrease(Walker *pWalker, Select *pSelect){ + UNUSED_PARAMETER(pSelect); + pWalker->walkerDepth--; +} + + +/* +** No-op routine for the parse-tree walker. +** +** When this routine is the Walker.xExprCallback then expression trees +** are walked without any actions being taken at each node. Presumably, +** when this routine is used for Walker.xExprCallback then +** Walker.xSelectCallback is set to do something useful for every +** subquery in the parser tree. +*/ +SQLITE_PRIVATE int sqlite3ExprWalkNoop(Walker *NotUsed, Expr *NotUsed2){ + UNUSED_PARAMETER2(NotUsed, NotUsed2); + return WRC_Continue; +} + +/* +** No-op routine for the parse-tree walker for SELECT statements. +** subquery in the parser tree. +*/ +SQLITE_PRIVATE int sqlite3SelectWalkNoop(Walker *NotUsed, Select *NotUsed2){ + UNUSED_PARAMETER2(NotUsed, NotUsed2); + return WRC_Continue; +} + /************** End of walker.c **********************************************/ /************** Begin file resolve.c *****************************************/ /* @@ -95129,6 +110499,11 @@ SQLITE_PRIVATE int sqlite3WalkSelect(Walker *pWalker, Select *p){ */ /* #include "sqliteInt.h" */ +/* +** Magic table number to mean the EXCLUDED table in an UPSERT statement. +*/ +#define EXCLUDED_TABLE_NUMBER 2 + /* ** Walk the expression tree pExpr and increase the aggregate function ** depth (the Expr.op2 field) by N on every TK_AGG_FUNCTION node. @@ -95137,6 +110512,8 @@ SQLITE_PRIVATE int sqlite3WalkSelect(Walker *pWalker, Select *p){ ** ** incrAggFunctionDepth(pExpr,n) is the main routine. incrAggDepth(..) ** is a helper function - a callback for the tree walker. +** +** See also the sqlite3WindowExtraAggFuncDepth() routine in window.c */ static int incrAggDepth(Walker *pWalker, Expr *pExpr){ if( pExpr->op==TK_AGG_FUNCTION ) pExpr->op2 += pWalker->u.n; @@ -95176,7 +110553,6 @@ static void resolveAlias( ExprList *pEList, /* A result set */ int iCol, /* A column in the result set. 0..pEList->nExpr-1 */ Expr *pExpr, /* Transform this into an alias to the result set */ - const char *zType, /* "GROUP" or "ORDER" or "" */ int nSubquery /* Number of subqueries that the label is moving */ ){ Expr *pOrig; /* The iCol-th column of the result set */ @@ -95186,65 +110562,64 @@ static void resolveAlias( assert( iCol>=0 && iColnExpr ); pOrig = pEList->a[iCol].pExpr; assert( pOrig!=0 ); + assert( !ExprHasProperty(pExpr, EP_Reduced|EP_TokenOnly) ); + if( pExpr->pAggInfo ) return; db = pParse->db; pDup = sqlite3ExprDup(db, pOrig, 0); - if( pDup!=0 ){ - if( zType[0]!='G' ) incrAggFunctionDepth(pDup, nSubquery); + if( db->mallocFailed ){ + sqlite3ExprDelete(db, pDup); + pDup = 0; + }else{ + Expr temp; + incrAggFunctionDepth(pDup, nSubquery); if( pExpr->op==TK_COLLATE ){ + assert( !ExprHasProperty(pExpr, EP_IntValue) ); pDup = sqlite3ExprAddCollateString(pParse, pDup, pExpr->u.zToken); } - - /* Before calling sqlite3ExprDelete(), set the EP_Static flag. This - ** prevents ExprDelete() from deleting the Expr structure itself, - ** allowing it to be repopulated by the memcpy() on the following line. - ** The pExpr->u.zToken might point into memory that will be freed by the - ** sqlite3DbFree(db, pDup) on the last line of this block, so be sure to - ** make a copy of the token before doing the sqlite3DbFree(). - */ - ExprSetProperty(pExpr, EP_Static); - sqlite3ExprDelete(db, pExpr); - memcpy(pExpr, pDup, sizeof(*pExpr)); - if( !ExprHasProperty(pExpr, EP_IntValue) && pExpr->u.zToken!=0 ){ - assert( (pExpr->flags & (EP_Reduced|EP_TokenOnly))==0 ); - pExpr->u.zToken = sqlite3DbStrDup(db, pExpr->u.zToken); - pExpr->flags |= EP_MemToken; + memcpy(&temp, pDup, sizeof(Expr)); + memcpy(pDup, pExpr, sizeof(Expr)); + memcpy(pExpr, &temp, sizeof(Expr)); + if( ExprHasProperty(pExpr, EP_WinFunc) ){ + if( ALWAYS(pExpr->y.pWin!=0) ){ + pExpr->y.pWin->pOwner = pExpr; + } } - sqlite3DbFree(db, pDup); + sqlite3ExprDeferredDelete(pParse, pDup); } - ExprSetProperty(pExpr, EP_Alias); } - /* -** Return TRUE if the name zCol occurs anywhere in the USING clause. +** Subqueries store the original database, table and column names for their +** result sets in ExprList.a[].zSpan, in the form "DATABASE.TABLE.COLUMN", +** and mark the expression-list item by setting ExprList.a[].fg.eEName +** to ENAME_TAB. ** -** Return FALSE if the USING clause is NULL or if it does not contain -** zCol. -*/ -static int nameInUsingClause(IdList *pUsing, const char *zCol){ - if( pUsing ){ - int k; - for(k=0; knId; k++){ - if( sqlite3StrICmp(pUsing->a[k].zName, zCol)==0 ) return 1; - } - } - return 0; -} - -/* -** Subqueries stores the original database, table and column names for their -** result sets in ExprList.a[].zSpan, in the form "DATABASE.TABLE.COLUMN". -** Check to see if the zSpan given to this routine matches the zDb, zTab, -** and zCol. If any of zDb, zTab, and zCol are NULL then those fields will -** match anything. +** Check to see if the zSpan/eEName of the expression-list item passed to this +** routine matches the zDb, zTab, and zCol. If any of zDb, zTab, and zCol are +** NULL then those fields will match anything. Return true if there is a match, +** or false otherwise. +** +** SF_NestedFrom subqueries also store an entry for the implicit rowid (or +** _rowid_, or oid) column by setting ExprList.a[].fg.eEName to ENAME_ROWID, +** and setting zSpan to "DATABASE.TABLE.". This type of pItem +** argument matches if zCol is a rowid alias. If it is not NULL, (*pbRowid) +** is set to 1 if there is this kind of match. */ -SQLITE_PRIVATE int sqlite3MatchSpanName( - const char *zSpan, +SQLITE_PRIVATE int sqlite3MatchEName( + const struct ExprList_item *pItem, const char *zCol, const char *zTab, - const char *zDb + const char *zDb, + int *pbRowid ){ int n; + const char *zSpan; + int eEName = pItem->fg.eEName; + if( eEName!=ENAME_TAB && (eEName!=ENAME_ROWID || NEVER(pbRowid==0)) ){ + return 0; + } + assert( pbRowid==0 || *pbRowid==0 ); + zSpan = pItem->zEName; for(n=0; ALWAYS(zSpan[n]) && zSpan[n]!='.'; n++){} if( zDb && (sqlite3StrNICmp(zSpan, zDb, n)!=0 || zDb[n]!=0) ){ return 0; @@ -95255,15 +110630,110 @@ SQLITE_PRIVATE int sqlite3MatchSpanName( return 0; } zSpan += n+1; - if( zCol && sqlite3StrICmp(zSpan, zCol)!=0 ){ - return 0; + if( zCol ){ + if( eEName==ENAME_TAB && sqlite3StrICmp(zSpan, zCol)!=0 ) return 0; + if( eEName==ENAME_ROWID && sqlite3IsRowid(zCol)==0 ) return 0; } + if( eEName==ENAME_ROWID ) *pbRowid = 1; return 1; } +/* +** Return TRUE if the double-quoted string mis-feature should be supported. +*/ +static int areDoubleQuotedStringsEnabled(sqlite3 *db, NameContext *pTopNC){ + if( db->init.busy ) return 1; /* Always support for legacy schemas */ + if( pTopNC->ncFlags & NC_IsDDL ){ + /* Currently parsing a DDL statement */ + if( sqlite3WritableSchema(db) && (db->flags & SQLITE_DqsDML)!=0 ){ + return 1; + } + return (db->flags & SQLITE_DqsDDL)!=0; + }else{ + /* Currently parsing a DML statement */ + return (db->flags & SQLITE_DqsDML)!=0; + } +} + +/* +** The argument is guaranteed to be a non-NULL Expr node of type TK_COLUMN. +** return the appropriate colUsed mask. +*/ +SQLITE_PRIVATE Bitmask sqlite3ExprColUsed(Expr *pExpr){ + int n; + Table *pExTab; + + n = pExpr->iColumn; + assert( ExprUseYTab(pExpr) ); + pExTab = pExpr->y.pTab; + assert( pExTab!=0 ); + assert( n < pExTab->nCol ); + if( (pExTab->tabFlags & TF_HasGenerated)!=0 + && (pExTab->aCol[n].colFlags & COLFLAG_GENERATED)!=0 + ){ + testcase( pExTab->nCol==BMS-1 ); + testcase( pExTab->nCol==BMS ); + return pExTab->nCol>=BMS ? ALLBITS : MASKBIT(pExTab->nCol)-1; + }else{ + testcase( n==BMS-1 ); + testcase( n==BMS ); + if( n>=BMS ) n = BMS-1; + return ((Bitmask)1)<db, TK_COLUMN, 0, 0); + if( pNew ){ + pNew->iTable = pMatch->iCursor; + pNew->iColumn = iColumn; + pNew->y.pTab = pMatch->pSTab; + assert( (pMatch->fg.jointype & (JT_LEFT|JT_LTORJ))!=0 ); + ExprSetProperty(pNew, EP_CanBeNull); + *ppList = sqlite3ExprListAppend(pParse, *ppList, pNew); + } +} + +/* +** Return TRUE (non-zero) if zTab is a valid name for the schema table pTab. +*/ +static SQLITE_NOINLINE int isValidSchemaTableName( + const char *zTab, /* Name as it appears in the SQL */ + Table *pTab, /* The schema table we are trying to match */ + const char *zDb /* non-NULL if a database qualifier is present */ +){ + const char *zLegacy; + assert( pTab!=0 ); + assert( pTab->tnum==1 ); + if( sqlite3StrNICmp(zTab, "sqlite_", 7)!=0 ) return 0; + zLegacy = pTab->zName; + if( strcmp(zLegacy+7, &LEGACY_TEMP_SCHEMA_TABLE[7])==0 ){ + if( sqlite3StrICmp(zTab+7, &PREFERRED_TEMP_SCHEMA_TABLE[7])==0 ){ + return 1; + } + if( zDb==0 ) return 0; + if( sqlite3StrICmp(zTab+7, &LEGACY_SCHEMA_TABLE[7])==0 ) return 1; + if( sqlite3StrICmp(zTab+7, &PREFERRED_SCHEMA_TABLE[7])==0 ) return 1; + }else{ + if( sqlite3StrICmp(zTab+7, &PREFERRED_SCHEMA_TABLE[7])==0 ) return 1; + } + return 0; +} + /* ** Given the name of a column of the form X.Y.Z or Y.Z or just Z, look up -** that name in the set of source tables in pSrcList and make the pExpr +** that name in the set of source tables in pSrcList and make the pExpr ** expression node refer back to that source column. The following changes ** are made to pExpr: ** @@ -95292,25 +110762,27 @@ static int lookupName( Parse *pParse, /* The parsing context */ const char *zDb, /* Name of the database containing table, or NULL */ const char *zTab, /* Name of table containing column, or NULL */ - const char *zCol, /* Name of the column. */ + const Expr *pRight, /* Name of the column. */ NameContext *pNC, /* The name context used to resolve the name */ Expr *pExpr /* Make this EXPR node point to the selected column */ ){ int i, j; /* Loop counters */ int cnt = 0; /* Number of matching column names */ - int cntTab = 0; /* Number of matching table names */ + int cntTab = 0; /* Number of potential "rowid" matches */ int nSubquery = 0; /* How many levels of subquery */ sqlite3 *db = pParse->db; /* The database connection */ - struct SrcList_item *pItem; /* Use for looping over pSrcList items */ - struct SrcList_item *pMatch = 0; /* The matching pSrcList item */ + SrcItem *pItem; /* Use for looping over pSrcList items */ + SrcItem *pMatch = 0; /* The matching pSrcList item */ NameContext *pTopNC = pNC; /* First namecontext in the list */ Schema *pSchema = 0; /* Schema of the expression */ int eNewExprOp = TK_COLUMN; /* New value for pExpr->op on success */ - Table *pTab = 0; /* Table hold the row */ - Column *pCol; /* A column of pTab */ + Table *pTab = 0; /* Table holding the row */ + ExprList *pFJMatch = 0; /* Matches for FULL JOIN .. USING */ + const char *zCol = pRight->u.zToken; assert( pNC ); /* the name context cannot be NULL. */ assert( zCol ); /* The Z in X.Y.Z cannot be NULL */ + assert( zDb==0 || zTab!=0 ); assert( !ExprHasProperty(pExpr, EP_TokenOnly|EP_Reduced) ); /* Initialize the node to no-match */ @@ -95338,6 +110810,12 @@ static int lookupName( break; } } + if( i==db->nDb && sqlite3StrICmp("main", zDb)==0 ){ + /* This branch is taken when the main database has been renamed + ** using SQLITE_DBCONFIG_MAINDBNAME. */ + pSchema = db->aDb[0].pSchema; + zDb = db->aDb[0].zDbSName; + } } } @@ -95349,63 +110827,169 @@ static int lookupName( if( pSrcList ){ for(i=0, pItem=pSrcList->a; inSrc; i++, pItem++){ - pTab = pItem->pTab; + pTab = pItem->pSTab; assert( pTab!=0 && pTab->zName!=0 ); - assert( pTab->nCol>0 ); - if( pItem->pSelect && (pItem->pSelect->selFlags & SF_NestedFrom)!=0 ){ + assert( pTab->nCol>0 || pParse->nErr ); + assert( (int)pItem->fg.isNestedFrom == IsNestedFrom(pItem)); + if( pItem->fg.isNestedFrom ){ + /* In this case, pItem is a subquery that has been formed from a + ** parenthesized subset of the FROM clause terms. Example: + ** .... FROM t1 LEFT JOIN (t2 RIGHT JOIN t3 USING(x)) USING(y) ... + ** \_________________________/ + ** This pItem -------------^ + */ int hit = 0; - pEList = pItem->pSelect->pEList; + Select *pSel; + assert( pItem->fg.isSubquery ); + assert( pItem->u4.pSubq!=0 ); + pSel = pItem->u4.pSubq->pSelect; + assert( pSel!=0 ); + pEList = pSel->pEList; + assert( pEList!=0 ); + assert( pEList->nExpr==pTab->nCol ); for(j=0; jnExpr; j++){ - if( sqlite3MatchSpanName(pEList->a[j].zSpan, zCol, zTab, zDb) ){ + int bRowid = 0; /* True if possible rowid match */ + if( !sqlite3MatchEName(&pEList->a[j], zCol, zTab, zDb, &bRowid) ){ + continue; + } + if( bRowid==0 ){ + if( cnt>0 ){ + if( pItem->fg.isUsing==0 + || sqlite3IdListIndex(pItem->u3.pUsing, zCol)<0 + || pMatch==pItem + ){ + /* Two or more tables have the same column name which is + ** not joined by USING. Or, a single table has two columns + ** that match a USING term (if pMatch==pItem). These are both + ** "ambiguous column name" errors. Signal as much by clearing + ** pFJMatch and letting cnt go above 1. */ + sqlite3ExprListDelete(db, pFJMatch); + pFJMatch = 0; + }else + if( (pItem->fg.jointype & JT_RIGHT)==0 ){ + /* An INNER or LEFT JOIN. Use the left-most table */ + continue; + }else + if( (pItem->fg.jointype & JT_LEFT)==0 ){ + /* A RIGHT JOIN. Use the right-most table */ + cnt = 0; + sqlite3ExprListDelete(db, pFJMatch); + pFJMatch = 0; + }else{ + /* For a FULL JOIN, we must construct a coalesce() func */ + extendFJMatch(pParse, &pFJMatch, pMatch, pExpr->iColumn); + } + } cnt++; - cntTab = 2; - pMatch = pItem; - pExpr->iColumn = j; hit = 1; + }else if( cnt>0 ){ + /* This is a potential rowid match, but there has already been + ** a real match found. So this can be ignored. */ + continue; } + cntTab++; + pMatch = pItem; + pExpr->iColumn = j; + pEList->a[j].fg.bUsed = 1; + + /* rowid cannot be part of a USING clause - assert() this. */ + assert( bRowid==0 || pEList->a[j].fg.bUsingTerm==0 ); + if( pEList->a[j].fg.bUsingTerm ) break; } if( hit || zTab==0 ) continue; } - if( zDb && pTab->pSchema!=pSchema ){ - continue; - } + assert( zDb==0 || zTab!=0 ); if( zTab ){ - const char *zTabName = pItem->zAlias ? pItem->zAlias : pTab->zName; - assert( zTabName!=0 ); - if( sqlite3StrICmp(zTabName, zTab)!=0 ){ - continue; + if( zDb ){ + if( pTab->pSchema!=pSchema ) continue; + if( pSchema==0 && strcmp(zDb,"*")!=0 ) continue; + } + if( pItem->zAlias!=0 ){ + if( sqlite3StrICmp(zTab, pItem->zAlias)!=0 ){ + continue; + } + }else if( sqlite3StrICmp(zTab, pTab->zName)!=0 ){ + if( pTab->tnum!=1 ) continue; + if( !isValidSchemaTableName(zTab, pTab, zDb) ) continue; } + assert( ExprUseYTab(pExpr) ); if( IN_RENAME_OBJECT && pItem->zAlias ){ sqlite3RenameTokenRemap(pParse, 0, (void*)&pExpr->y.pTab); } } - if( 0==(cntTab++) ){ + j = sqlite3ColumnIndex(pTab, zCol); + if( j>=0 ){ + if( cnt>0 ){ + if( pItem->fg.isUsing==0 + || sqlite3IdListIndex(pItem->u3.pUsing, zCol)<0 + ){ + /* Two or more tables have the same column name which is + ** not joined by USING. This is an error. Signal as much + ** by clearing pFJMatch and letting cnt go above 1. */ + sqlite3ExprListDelete(db, pFJMatch); + pFJMatch = 0; + }else + if( (pItem->fg.jointype & JT_RIGHT)==0 ){ + /* An INNER or LEFT JOIN. Use the left-most table */ + continue; + }else + if( (pItem->fg.jointype & JT_LEFT)==0 ){ + /* A RIGHT JOIN. Use the right-most table */ + cnt = 0; + sqlite3ExprListDelete(db, pFJMatch); + pFJMatch = 0; + }else{ + /* For a FULL JOIN, we must construct a coalesce() func */ + extendFJMatch(pParse, &pFJMatch, pMatch, pExpr->iColumn); + } + } + cnt++; pMatch = pItem; + /* Substitute the rowid (column -1) for the INTEGER PRIMARY KEY */ + pExpr->iColumn = j==pTab->iPKey ? -1 : (i16)j; + if( pItem->fg.isNestedFrom ){ + sqlite3SrcItemColumnUsed(pItem, j); + } } - for(j=0, pCol=pTab->aCol; jnCol; j++, pCol++){ - if( sqlite3StrICmp(pCol->zName, zCol)==0 ){ - /* If there has been exactly one prior match and this match - ** is for the right-hand table of a NATURAL JOIN or is in a - ** USING clause, then skip this match. - */ - if( cnt==1 ){ - if( pItem->fg.jointype & JT_NATURAL ) continue; - if( nameInUsingClause(pItem->pUsing, zCol) ) continue; - } - cnt++; + if( 0==cnt && VisibleRowid(pTab) ){ + /* pTab is a potential ROWID match. Keep track of it and match + ** the ROWID later if that seems appropriate. (Search for "cntTab" + ** to find related code.) Only allow a ROWID match if there is + ** a single ROWID match candidate. + */ +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + /* In SQLITE_ALLOW_ROWID_IN_VIEW mode, allow a ROWID match + ** if there is a single VIEW candidate or if there is a single + ** non-VIEW candidate plus multiple VIEW candidates. In other + ** words non-VIEW candidate terms take precedence over VIEWs. + */ + if( cntTab==0 + || (cntTab==1 + && pMatch!=0 + && ALWAYS(pMatch->pSTab!=0) + && (pMatch->pSTab->tabFlags & TF_Ephemeral)!=0 + && (pTab->tabFlags & TF_Ephemeral)==0) + ){ + cntTab = 1; pMatch = pItem; - /* Substitute the rowid (column -1) for the INTEGER PRIMARY KEY */ - pExpr->iColumn = j==pTab->iPKey ? -1 : (i16)j; - break; + }else{ + cntTab++; } +#else + /* The (much more common) non-SQLITE_ALLOW_ROWID_IN_VIEW case is + ** simpler since we require exactly one candidate, which will + ** always be a non-VIEW + */ + cntTab++; + pMatch = pItem; +#endif } } if( pMatch ){ pExpr->iTable = pMatch->iCursor; - pExpr->y.pTab = pMatch->pTab; - /* RIGHT JOIN not (yet) supported */ - assert( (pMatch->fg.jointype & JT_RIGHT)==0 ); - if( (pMatch->fg.jointype & JT_LEFT)!=0 ){ + assert( ExprUseYTab(pExpr) ); + pExpr->y.pTab = pMatch->pSTab; + if( (pMatch->fg.jointype & (JT_LEFT|JT_LTORJ))!=0 ){ ExprSetProperty(pExpr, EP_CanBeNull); } pSchema = pExpr->y.pTab->pSchema; @@ -95413,84 +110997,103 @@ static int lookupName( } /* if( pSrcList ) */ #if !defined(SQLITE_OMIT_TRIGGER) || !defined(SQLITE_OMIT_UPSERT) - /* If we have not already resolved the name, then maybe + /* If we have not already resolved the name, then maybe ** it is a new.* or old.* trigger argument reference. Or - ** maybe it is an excluded.* from an upsert. + ** maybe it is an excluded.* from an upsert. Or maybe it is + ** a reference in the RETURNING clause to a table being modified. */ - if( zDb==0 && zTab!=0 && cntTab==0 ){ + if( cnt==0 && zDb==0 ){ pTab = 0; #ifndef SQLITE_OMIT_TRIGGER if( pParse->pTriggerTab!=0 ){ int op = pParse->eTriggerOp; assert( op==TK_DELETE || op==TK_UPDATE || op==TK_INSERT ); - if( op!=TK_DELETE && sqlite3StrICmp("new",zTab) == 0 ){ + if( pParse->bReturning ){ + if( (pNC->ncFlags & NC_UBaseReg)!=0 + && ALWAYS(zTab==0 + || sqlite3StrICmp(zTab,pParse->pTriggerTab->zName)==0 + || isValidSchemaTableName(zTab, pParse->pTriggerTab, 0)) + ){ + pExpr->iTable = op!=TK_DELETE; + pTab = pParse->pTriggerTab; + } + }else if( op!=TK_DELETE && zTab && sqlite3StrICmp("new",zTab) == 0 ){ pExpr->iTable = 1; pTab = pParse->pTriggerTab; - }else if( op!=TK_INSERT && sqlite3StrICmp("old",zTab)==0 ){ + }else if( op!=TK_INSERT && zTab && sqlite3StrICmp("old",zTab)==0 ){ pExpr->iTable = 0; pTab = pParse->pTriggerTab; } } #endif /* SQLITE_OMIT_TRIGGER */ #ifndef SQLITE_OMIT_UPSERT - if( (pNC->ncFlags & NC_UUpsert)!=0 ){ + if( (pNC->ncFlags & NC_UUpsert)!=0 && zTab!=0 ){ Upsert *pUpsert = pNC->uNC.pUpsert; if( pUpsert && sqlite3StrICmp("excluded",zTab)==0 ){ - pTab = pUpsert->pUpsertSrc->a[0].pTab; - pExpr->iTable = 2; + pTab = pUpsert->pUpsertSrc->a[0].pSTab; + pExpr->iTable = EXCLUDED_TABLE_NUMBER; } } #endif /* SQLITE_OMIT_UPSERT */ - if( pTab ){ + if( pTab ){ int iCol; pSchema = pTab->pSchema; cntTab++; - for(iCol=0, pCol=pTab->aCol; iColnCol; iCol++, pCol++){ - if( sqlite3StrICmp(pCol->zName, zCol)==0 ){ - if( iCol==pTab->iPKey ){ - iCol = -1; - } - break; + iCol = sqlite3ColumnIndex(pTab, zCol); + if( iCol>=0 ){ + if( pTab->iPKey==iCol ) iCol = -1; + }else{ + if( sqlite3IsRowid(zCol) && VisibleRowid(pTab) ){ + iCol = -1; + }else{ + iCol = pTab->nCol; } } - if( iCol>=pTab->nCol && sqlite3IsRowid(zCol) && VisibleRowid(pTab) ){ - /* IMP: R-51414-32910 */ - iCol = -1; - } if( iColnCol ){ cnt++; + pMatch = 0; #ifndef SQLITE_OMIT_UPSERT - if( pExpr->iTable==2 ){ + if( pExpr->iTable==EXCLUDED_TABLE_NUMBER ){ testcase( iCol==(-1) ); + assert( ExprUseYTab(pExpr) ); if( IN_RENAME_OBJECT ){ pExpr->iColumn = iCol; pExpr->y.pTab = pTab; eNewExprOp = TK_COLUMN; }else{ - pExpr->iTable = pNC->uNC.pUpsert->regData + iCol; + pExpr->iTable = pNC->uNC.pUpsert->regData + + sqlite3TableColumnToStorage(pTab, iCol); eNewExprOp = TK_REGISTER; - ExprSetProperty(pExpr, EP_Alias); } }else #endif /* SQLITE_OMIT_UPSERT */ { -#ifndef SQLITE_OMIT_TRIGGER - if( iCol<0 ){ - pExpr->affinity = SQLITE_AFF_INTEGER; - }else if( pExpr->iTable==0 ){ - testcase( iCol==31 ); - testcase( iCol==32 ); - pParse->oldmask |= (iCol>=32 ? 0xffffffff : (((u32)1)<newmask |= (iCol>=32 ? 0xffffffff : (((u32)1)<y.pTab = pTab; - pExpr->iColumn = (i16)iCol; - eNewExprOp = TK_TRIGGER; + if( pParse->bReturning ){ + eNewExprOp = TK_REGISTER; + pExpr->op2 = TK_COLUMN; + pExpr->iColumn = iCol; + pExpr->iTable = pNC->uNC.iBaseReg + (pTab->nCol+1)*pExpr->iTable + + sqlite3TableColumnToStorage(pTab, iCol) + 1; + }else{ + pExpr->iColumn = (i16)iCol; + eNewExprOp = TK_TRIGGER; +#ifndef SQLITE_OMIT_TRIGGER + if( iCol<0 ){ + pExpr->affExpr = SQLITE_AFF_INTEGER; + }else if( pExpr->iTable==0 ){ + testcase( iCol==31 ); + testcase( iCol==32 ); + pParse->oldmask |= (iCol>=32 ? 0xffffffff : (((u32)1)<newmask |= (iCol>=32 ? 0xffffffff : (((u32)1)<=1 && pMatch - && (pNC->ncFlags & NC_IdxExpr)==0 + && (pNC->ncFlags & (NC_IdxExpr|NC_GenCol))==0 && sqlite3IsRowid(zCol) - && VisibleRowid(pMatch->pTab) + && ALWAYS(VisibleRowid(pMatch->pSTab) || pMatch->fg.isNestedFrom) ){ - cnt = 1; - pExpr->iColumn = -1; - pExpr->affinity = SQLITE_AFF_INTEGER; + cnt = cntTab; +#if SQLITE_ALLOW_ROWID_IN_VIEW+0==2 + if( pMatch->pSTab!=0 && IsView(pMatch->pSTab) ){ + eNewExprOp = TK_NULL; + } +#endif + if( pMatch->fg.isNestedFrom==0 ) pExpr->iColumn = -1; + pExpr->affExpr = SQLITE_AFF_INTEGER; } /* @@ -95530,25 +111138,29 @@ static int lookupName( ** is supported for backwards compatibility only. Hence, we issue a warning ** on sqlite3_log() whenever the capability is used. */ - if( (pNC->ncFlags & NC_UEList)!=0 - && cnt==0 + if( cnt==0 + && (pNC->ncFlags & NC_UEList)!=0 && zTab==0 ){ pEList = pNC->uNC.pEList; assert( pEList!=0 ); for(j=0; jnExpr; j++){ - char *zAs = pEList->a[j].zName; - if( zAs!=0 && sqlite3StrICmp(zAs, zCol)==0 ){ + char *zAs = pEList->a[j].zEName; + if( pEList->a[j].fg.eEName==ENAME_NAME + && sqlite3_stricmp(zAs, zCol)==0 + ){ Expr *pOrig; assert( pExpr->pLeft==0 && pExpr->pRight==0 ); - assert( pExpr->x.pList==0 ); - assert( pExpr->x.pSelect==0 ); + assert( ExprUseXList(pExpr)==0 || pExpr->x.pList==0 ); + assert( ExprUseXSelect(pExpr)==0 || pExpr->x.pSelect==0 ); pOrig = pEList->a[j].pExpr; if( (pNC->ncFlags&NC_AllowAgg)==0 && ExprHasProperty(pOrig, EP_Agg) ){ sqlite3ErrorMsg(pParse, "misuse of aliased aggregate %s", zAs); return WRC_Abort; } - if( (pNC->ncFlags&NC_AllowWin)==0 && ExprHasProperty(pOrig, EP_Win) ){ + if( ExprHasProperty(pOrig, EP_Win) + && ((pNC->ncFlags&NC_AllowWin)==0 || pNC!=pTopNC ) + ){ sqlite3ErrorMsg(pParse, "misuse of aliased window function %s",zAs); return WRC_Abort; } @@ -95556,7 +111168,7 @@ static int lookupName( sqlite3ErrorMsg(pParse, "row value misused"); return WRC_Abort; } - resolveAlias(pParse, pEList, j, pExpr, "", nSubquery); + resolveAlias(pParse, pEList, j, pExpr, nSubquery); cnt = 1; pMatch = 0; assert( zTab==0 && zDb==0 ); @@ -95565,7 +111177,7 @@ static int lookupName( } goto lookupname_end; } - } + } } /* Advance to the next name context. The loop will exit when either @@ -95589,7 +111201,9 @@ static int lookupName( */ if( cnt==0 && zTab==0 ){ assert( pExpr->op==TK_ID ); - if( ExprHasProperty(pExpr,EP_DblQuoted) ){ + if( ExprHasProperty(pExpr,EP_DblQuoted) + && areDoubleQuotedStringsEnabled(db, pTopNC) + ){ /* If a double-quoted identifier does not match any known column name, ** then treat it as a string. ** @@ -95610,7 +111224,7 @@ static int lookupName( sqlite3VdbeAddDblquoteStr(db, pParse->pVdbe, zCol); #endif pExpr->op = TK_STRING; - pExpr->y.pTab = 0; + memset(&pExpr->y, 0, sizeof(pExpr->y)); return WRC_Prune; } if( sqlite3ExprIdToTrueFalse(pExpr) ){ @@ -95619,53 +111233,99 @@ static int lookupName( } /* - ** cnt==0 means there was not match. cnt>1 means there were two or - ** more matches. Either way, we have an error. + ** cnt==0 means there was not match. + ** cnt>1 means there were two or more matches. + ** + ** cnt==0 is always an error. cnt>1 is often an error, but might + ** be multiple matches for a NATURAL LEFT JOIN or a LEFT JOIN USING. */ + assert( pFJMatch==0 || cnt>0 ); + assert( !ExprHasProperty(pExpr, EP_xIsSelect|EP_IntValue) ); if( cnt!=1 ){ const char *zErr; + if( pFJMatch ){ + if( pFJMatch->nExpr==cnt-1 ){ + if( ExprHasProperty(pExpr,EP_Leaf) ){ + ExprClearProperty(pExpr,EP_Leaf); + }else{ + sqlite3ExprDelete(db, pExpr->pLeft); + pExpr->pLeft = 0; + sqlite3ExprDelete(db, pExpr->pRight); + pExpr->pRight = 0; + } + extendFJMatch(pParse, &pFJMatch, pMatch, pExpr->iColumn); + pExpr->op = TK_FUNCTION; + pExpr->u.zToken = "coalesce"; + pExpr->x.pList = pFJMatch; + pExpr->affExpr = SQLITE_AFF_DEFER; + cnt = 1; + goto lookupname_end; + }else{ + sqlite3ExprListDelete(db, pFJMatch); + pFJMatch = 0; + } + } zErr = cnt==0 ? "no such column" : "ambiguous column name"; if( zDb ){ sqlite3ErrorMsg(pParse, "%s: %s.%s.%s", zErr, zDb, zTab, zCol); }else if( zTab ){ sqlite3ErrorMsg(pParse, "%s: %s.%s", zErr, zTab, zCol); + }else if( cnt==0 && ExprHasProperty(pRight,EP_DblQuoted) ){ + sqlite3ErrorMsg(pParse, "%s: \"%s\" - should this be a" + " string literal in single-quotes?", + zErr, zCol); }else{ sqlite3ErrorMsg(pParse, "%s: %s", zErr, zCol); } + sqlite3RecordErrorOffsetOfExpr(pParse->db, pExpr); pParse->checkSchema = 1; - pTopNC->nErr++; + pTopNC->nNcErr++; + eNewExprOp = TK_NULL; + } + assert( pFJMatch==0 ); + + /* Remove all substructure from pExpr */ + if( !ExprHasProperty(pExpr,(EP_TokenOnly|EP_Leaf)) ){ + sqlite3ExprDelete(db, pExpr->pLeft); + pExpr->pLeft = 0; + sqlite3ExprDelete(db, pExpr->pRight); + pExpr->pRight = 0; + ExprSetProperty(pExpr, EP_Leaf); } /* If a column from a table in pSrcList is referenced, then record ** this fact in the pSrcList.a[].colUsed bitmask. Column 0 causes - ** bit 0 to be set. Column 1 sets bit 1. And so forth. If the - ** column number is greater than the number of bits in the bitmask - ** then set the high-order bit of the bitmask. + ** bit 0 to be set. Column 1 sets bit 1. And so forth. Bit 63 is + ** set if the 63rd or any subsequent column is used. + ** + ** The colUsed mask is an optimization used to help determine if an + ** index is a covering index. The correct answer is still obtained + ** if the mask contains extra set bits. However, it is important to + ** avoid setting bits beyond the maximum column number of the table. + ** (See ticket [b92e5e8ec2cdbaa1]). + ** + ** If a generated column is referenced, set bits for every column + ** of the table. */ - if( pExpr->iColumn>=0 && pMatch!=0 ){ - int n = pExpr->iColumn; - testcase( n==BMS-1 ); - if( n>=BMS ){ - n = BMS-1; + if( pMatch ){ + if( pExpr->iColumn>=0 ){ + pMatch->colUsed |= sqlite3ExprColUsed(pExpr); + }else{ + pMatch->fg.rowidUsed = 1; } - assert( pMatch->iCursor==pExpr->iTable ); - pMatch->colUsed |= ((Bitmask)1)<pLeft); - pExpr->pLeft = 0; - sqlite3ExprDelete(db, pExpr->pRight); - pExpr->pRight = 0; pExpr->op = eNewExprOp; - ExprSetProperty(pExpr, EP_Leaf); lookupname_end: if( cnt==1 ){ assert( pNC!=0 ); - if( !ExprHasProperty(pExpr, EP_Alias) ){ +#ifndef SQLITE_OMIT_AUTHORIZATION + if( pParse->db->xAuth + && (pExpr->op==TK_COLUMN || pExpr->op==TK_TRIGGER) + ){ sqlite3AuthRead(pParse, pExpr, pSchema, pNC->pSrcList); } +#endif /* Increment the nRef value on all name contexts from TopNC up to ** the point where the name matched. */ for(;;){ @@ -95687,16 +111347,26 @@ static int lookupName( SQLITE_PRIVATE Expr *sqlite3CreateColumnExpr(sqlite3 *db, SrcList *pSrc, int iSrc, int iCol){ Expr *p = sqlite3ExprAlloc(db, TK_COLUMN, 0, 0); if( p ){ - struct SrcList_item *pItem = &pSrc->a[iSrc]; - p->y.pTab = pItem->pTab; + SrcItem *pItem = &pSrc->a[iSrc]; + Table *pTab; + assert( ExprUseYTab(p) ); + pTab = p->y.pTab = pItem->pSTab; p->iTable = pItem->iCursor; if( p->y.pTab->iPKey==iCol ){ p->iColumn = -1; }else{ p->iColumn = (ynVar)iCol; - testcase( iCol==BMS ); - testcase( iCol==BMS-1 ); - pItem->colUsed |= ((Bitmask)1)<<(iCol>=BMS ? BMS-1 : iCol); + if( (pTab->tabFlags & TF_HasGenerated)!=0 + && (pTab->aCol[iCol].colFlags & COLFLAG_GENERATED)!=0 + ){ + testcase( pTab->nCol==63 ); + testcase( pTab->nCol==64 ); + pItem->colUsed = pTab->nCol>=64 ? ALLBITS : MASKBIT(pTab->nCol)-1; + }else{ + testcase( iCol==BMS ); + testcase( iCol==BMS-1 ); + pItem->colUsed |= ((Bitmask)1)<<(iCol>=BMS ? BMS-1 : iCol); + } } } return p; @@ -95705,38 +111375,86 @@ SQLITE_PRIVATE Expr *sqlite3CreateColumnExpr(sqlite3 *db, SrcList *pSrc, int iSr /* ** Report an error that an expression is not valid for some set of ** pNC->ncFlags values determined by validMask. -*/ -static void notValid( - Parse *pParse, /* Leave error message here */ - NameContext *pNC, /* The name context */ - const char *zMsg, /* Type of error */ - int validMask /* Set of contexts for which prohibited */ -){ - assert( (validMask&~(NC_IsCheck|NC_PartIdx|NC_IdxExpr))==0 ); - if( (pNC->ncFlags & validMask)!=0 ){ - const char *zIn = "partial index WHERE clauses"; - if( pNC->ncFlags & NC_IdxExpr ) zIn = "index expressions"; +** +** static void notValid( +** Parse *pParse, // Leave error message here +** NameContext *pNC, // The name context +** const char *zMsg, // Type of error +** int validMask, // Set of contexts for which prohibited +** Expr *pExpr // Invalidate this expression on error +** ){...} +** +** As an optimization, since the conditional is almost always false +** (because errors are rare), the conditional is moved outside of the +** function call using a macro. +*/ +static void notValidImpl( + Parse *pParse, /* Leave error message here */ + NameContext *pNC, /* The name context */ + const char *zMsg, /* Type of error */ + Expr *pExpr, /* Invalidate this expression on error */ + Expr *pError /* Associate error with this expression */ +){ + const char *zIn = "partial index WHERE clauses"; + if( pNC->ncFlags & NC_IdxExpr ) zIn = "index expressions"; #ifndef SQLITE_OMIT_CHECK - else if( pNC->ncFlags & NC_IsCheck ) zIn = "CHECK constraints"; + else if( pNC->ncFlags & NC_IsCheck ) zIn = "CHECK constraints"; #endif - sqlite3ErrorMsg(pParse, "%s prohibited in %s", zMsg, zIn); - } +#ifndef SQLITE_OMIT_GENERATED_COLUMNS + else if( pNC->ncFlags & NC_GenCol ) zIn = "generated columns"; +#endif + sqlite3ErrorMsg(pParse, "%s prohibited in %s", zMsg, zIn); + if( pExpr ) pExpr->op = TK_NULL; + sqlite3RecordErrorOffsetOfExpr(pParse->db, pError); } +#define sqlite3ResolveNotValid(P,N,M,X,E,R) \ + assert( ((X)&~(NC_IsCheck|NC_PartIdx|NC_IdxExpr|NC_GenCol))==0 ); \ + if( ((N)->ncFlags & (X))!=0 ) notValidImpl(P,N,M,E,R); /* ** Expression p should encode a floating point value between 1.0 and 0.0. -** Return 1024 times this value. Or return -1 if p is not a floating point -** value between 1.0 and 0.0. +** Return 134,217,728 (2^27) times this value. Or return -1 if p is not +** a floating point value between 1.0 and 0.0. */ static int exprProbability(Expr *p){ double r = -1.0; if( p->op!=TK_FLOAT ) return -1; - sqlite3AtoF(p->u.zToken, &r, sqlite3Strlen30(p->u.zToken), SQLITE_UTF8); + assert( !ExprHasProperty(p, EP_IntValue) ); + sqlite3AtoF(p->u.zToken, &r); assert( r>=0.0 ); if( r>1.0 ) return -1; return (int)(r*134217728.0); } +/* +** Set the EP_SubtArg property on every expression inside of +** pList. If any subexpression is actually a subquery, then +** also set the EP_SubtArg property on the first result-set +** column of that subquery. +*/ +static SQLITE_NOINLINE void resolveSetExprSubtypeArg(ExprList *pList){ + int nn, ii; + nn = pList ? pList->nExpr : 0; + for(ii=0; iia[ii].pExpr; + while( 1 /*exit-by-break*/ ){ + ExprSetProperty(pExpr, EP_SubtArg); + if( pExpr->op==TK_SELECT ){ + assert( ExprUseXSelect(pExpr) ); + assert( pExpr->x.pSelect!=0 ); + resolveSetExprSubtypeArg(pExpr->x.pSelect->pEList); + break; + } + if( pExpr->op==TK_UPLUS ){ + pExpr = pExpr->pLeft; + assert( pExpr!=0 ); + }else{ + break; + } + } + } +} + /* ** This routine is callback for sqlite3WalkExpr(). ** @@ -95768,38 +111486,104 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ #endif switch( pExpr->op ){ -#if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) && !defined(SQLITE_OMIT_SUBQUERY) /* The special operator TK_ROW means use the rowid for the first ** column in the FROM clause. This is used by the LIMIT and ORDER BY - ** clause processing on UPDATE and DELETE statements. + ** clause processing on UPDATE and DELETE statements, and by + ** UPDATE ... FROM statement processing. */ case TK_ROW: { SrcList *pSrcList = pNC->pSrcList; - struct SrcList_item *pItem; - assert( pSrcList && pSrcList->nSrc==1 ); + SrcItem *pItem; + assert( pSrcList && pSrcList->nSrc>=1 ); pItem = pSrcList->a; - assert( HasRowid(pItem->pTab) && pItem->pTab->pSelect==0 ); pExpr->op = TK_COLUMN; - pExpr->y.pTab = pItem->pTab; + assert( ExprUseYTab(pExpr) ); + pExpr->y.pTab = pItem->pSTab; pExpr->iTable = pItem->iCursor; - pExpr->iColumn = -1; - pExpr->affinity = SQLITE_AFF_INTEGER; + pExpr->iColumn--; + pExpr->affExpr = SQLITE_AFF_INTEGER; break; } -#endif /* defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) - && !defined(SQLITE_OMIT_SUBQUERY) */ + + /* An optimization: Attempt to convert + ** + ** "expr IS NOT NULL" --> "TRUE" + ** "expr IS NULL" --> "FALSE" + ** + ** if we can prove that "expr" is never NULL. Call this the + ** "NOT NULL strength reduction optimization". + ** + ** If this optimization occurs, also restore the NameContext ref-counts + ** to the state they where in before the "column" LHS expression was + ** resolved. This prevents "column" from being counted as having been + ** referenced, which might prevent a SELECT from being erroneously + ** marked as correlated. + ** + ** 2024-03-28: Beware of aggregates. A bare column of aggregated table + ** can still evaluate to NULL even though it is marked as NOT NULL. + ** Example: + ** + ** CREATE TABLE t1(a INT NOT NULL); + ** SELECT a, a IS NULL, a IS NOT NULL, count(*) FROM t1; + ** + ** The "a IS NULL" and "a IS NOT NULL" expressions cannot be optimized + ** here because at the time this case is hit, we do not yet know whether + ** or not t1 is being aggregated. We have to assume the worst and omit + ** the optimization. The only time it is safe to apply this optimization + ** is within the WHERE clause. + */ + case TK_NOTNULL: + case TK_ISNULL: { + int anRef[8]; + NameContext *p; + int i; + for(i=0, p=pNC; p && ipNext, i++){ + anRef[i] = p->nRef; + } + sqlite3WalkExpr(pWalker, pExpr->pLeft); + if( IN_RENAME_OBJECT ) return WRC_Prune; + if( sqlite3ExprCanBeNull(pExpr->pLeft) ){ + /* The expression can be NULL. So the optimization does not apply */ + return WRC_Prune; + } + + for(i=0, p=pNC; p; p=p->pNext, i++){ + if( (p->ncFlags & NC_Where)==0 ){ + return WRC_Prune; /* Not in a WHERE clause. Unsafe to optimize. */ + } + } + testcase( ExprHasProperty(pExpr, EP_OuterON) ); + assert( !ExprHasProperty(pExpr, EP_IntValue) ); +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x80000 ){ + sqlite3DebugPrintf( + "NOT NULL strength reduction converts the following to %d:\n", + pExpr->op==TK_NOTNULL + ); + sqlite3ShowExpr(pExpr); + } +#endif /* TREETRACE_ENABLED */ + pExpr->u.iValue = (pExpr->op==TK_NOTNULL); + pExpr->flags |= EP_IntValue; + pExpr->op = TK_INTEGER; + for(i=0, p=pNC; p && ipNext, i++){ + p->nRef = anRef[i]; + } + sqlite3ExprDelete(pParse->db, pExpr->pLeft); + pExpr->pLeft = 0; + return WRC_Prune; + } /* A column name: ID ** Or table name and column name: ID.ID ** Or a database, table and column: ID.ID.ID ** ** The TK_ID and TK_OUT cases are combined so that there will only - ** be one call to lookupName(). Then the compiler will in-line + ** be one call to lookupName(). Then the compiler will in-line ** lookupName() for a size reduction and performance increase. */ case TK_ID: case TK_DOT: { - const char *zColumn; const char *zTable; const char *zDb; Expr *pRight; @@ -95807,46 +111591,55 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ if( pExpr->op==TK_ID ){ zDb = 0; zTable = 0; - zColumn = pExpr->u.zToken; + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + pRight = pExpr; }else{ Expr *pLeft = pExpr->pLeft; - notValid(pParse, pNC, "the \".\" operator", NC_IdxExpr); + testcase( pNC->ncFlags & NC_IdxExpr ); + testcase( pNC->ncFlags & NC_GenCol ); + sqlite3ResolveNotValid(pParse, pNC, "the \".\" operator", + NC_IdxExpr|NC_GenCol, 0, pExpr); pRight = pExpr->pRight; if( pRight->op==TK_ID ){ zDb = 0; }else{ assert( pRight->op==TK_DOT ); + assert( !ExprHasProperty(pRight, EP_IntValue) ); zDb = pLeft->u.zToken; pLeft = pRight->pLeft; pRight = pRight->pRight; } + assert( ExprUseUToken(pLeft) && ExprUseUToken(pRight) ); zTable = pLeft->u.zToken; - zColumn = pRight->u.zToken; + assert( ExprUseYTab(pExpr) ); if( IN_RENAME_OBJECT ){ sqlite3RenameTokenRemap(pParse, (void*)pExpr, (void*)pRight); sqlite3RenameTokenRemap(pParse, (void*)&pExpr->y.pTab, (void*)pLeft); } } - return lookupName(pParse, zDb, zTable, zColumn, pNC, pExpr); + return lookupName(pParse, zDb, zTable, pRight, pNC, pExpr); } /* Resolve function names */ case TK_FUNCTION: { - ExprList *pList = pExpr->x.pList; /* The argument list */ - int n = pList ? pList->nExpr : 0; /* Number of arguments */ + ExprList *pList; /* The argument list */ + int n; /* Number of arguments */ int no_such_func = 0; /* True if no such function exists */ int wrong_num_args = 0; /* True if wrong number of arguments */ int is_agg = 0; /* True if is an aggregate function */ - int nId; /* Number of characters in function name */ const char *zId; /* The function name. */ FuncDef *pDef; /* Information about the function */ u8 enc = ENC(pParse->db); /* The database encoding */ int savedAllowFlags = (pNC->ncFlags & (NC_AllowAgg | NC_AllowWin)); - - assert( !ExprHasProperty(pExpr, EP_xIsSelect) ); +#ifndef SQLITE_OMIT_WINDOWFUNC + Window *pWin = (IsWindowFunc(pExpr) ? pExpr->y.pWin : 0); +#endif + assert( !ExprHasProperty(pExpr, EP_xIsSelect|EP_IntValue) ); + assert( pExpr->pLeft==0 || pExpr->pLeft->op==TK_ORDER ); + pList = pExpr->x.pList; + n = pList ? pList->nExpr : 0; zId = pExpr->u.zToken; - nId = sqlite3Strlen30(zId); pDef = sqlite3FindFunction(pParse->db, zId, n, enc, 0); if( pDef==0 ){ pDef = sqlite3FindFunction(pParse->db, zId, -2, enc, 0); @@ -95858,14 +111651,14 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ }else{ is_agg = pDef->xFinalize!=0; if( pDef->funcFlags & SQLITE_FUNC_UNLIKELY ){ - ExprSetProperty(pExpr, EP_Unlikely|EP_Skip); + ExprSetProperty(pExpr, EP_Unlikely); if( n==2 ){ pExpr->iTable = exprProbability(pList->a[1].pExpr); if( pExpr->iTable<0 ){ sqlite3ErrorMsg(pParse, - "second argument to likelihood() must be a " - "constant between 0.0 and 1.0"); - pNC->nErr++; + "second argument to %#T() must be a " + "constant between 0.0 and 1.0", pExpr); + pNC->nNcErr++; } }else{ /* EVIDENCE-OF: R-61304-29449 The unlikely(X) function is @@ -95878,43 +111671,73 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ ** to likelihood(X,0.9375). */ /* TUNING: unlikely() probability is 0.0625. likely() is 0.9375 */ pExpr->iTable = pDef->zName[0]=='u' ? 8388608 : 125829120; - } + } } #ifndef SQLITE_OMIT_AUTHORIZATION { int auth = sqlite3AuthCheck(pParse, SQLITE_FUNCTION, 0,pDef->zName,0); if( auth!=SQLITE_OK ){ if( auth==SQLITE_DENY ){ - sqlite3ErrorMsg(pParse, "not authorized to use function: %s", - pDef->zName); - pNC->nErr++; + sqlite3ErrorMsg(pParse, "not authorized to use function: %#T", + pExpr); + pNC->nNcErr++; } pExpr->op = TK_NULL; return WRC_Prune; } } #endif + + /* If the function may call sqlite3_value_subtype(), then set the + ** EP_SubtArg flag on all of its argument expressions. This prevents + ** where.c from replacing the expression with a value read from an + ** index on the same expression, which will not have the correct + ** subtype. Also set the flag if the function expression itself is + ** an EP_SubtArg expression. In this case subtypes are required as + ** the function may return a value with a subtype back to its + ** caller using sqlite3_result_value(). */ + if( (pDef->funcFlags & SQLITE_SUBTYPE) + || ExprHasProperty(pExpr, EP_SubtArg) + ){ + resolveSetExprSubtypeArg(pList); + } + if( pDef->funcFlags & (SQLITE_FUNC_CONSTANT|SQLITE_FUNC_SLOCHNG) ){ /* For the purposes of the EP_ConstFunc flag, date and time ** functions and other functions that change slowly are considered - ** constant because they are constant for the duration of one query */ + ** constant because they are constant for the duration of one query. + ** This allows them to be factored out of inner loops. */ ExprSetProperty(pExpr,EP_ConstFunc); } if( (pDef->funcFlags & SQLITE_FUNC_CONSTANT)==0 ){ - /* Date/time functions that use 'now', and other functions like + /* Clearly non-deterministic functions like random(), but also + ** date/time functions that use 'now', and other functions like ** sqlite_version() that might change over time cannot be used - ** in an index. */ - notValid(pParse, pNC, "non-deterministic functions", - NC_IdxExpr|NC_PartIdx); + ** in an index or generated column. Curiously, they can be used + ** in a CHECK constraint. SQLServer, MySQL, and PostgreSQL all + ** allow this. */ + sqlite3ResolveNotValid(pParse, pNC, "non-deterministic functions", + NC_IdxExpr|NC_PartIdx|NC_GenCol, 0, pExpr); + }else{ + assert( (NC_SelfRef & 0xff)==NC_SelfRef ); /* Must fit in 8 bits */ + pExpr->op2 = pNC->ncFlags & NC_SelfRef; } if( (pDef->funcFlags & SQLITE_FUNC_INTERNAL)!=0 && pParse->nested==0 - && sqlite3Config.bInternalFunctions==0 + && (pParse->db->mDbFlags & DBFLAG_InternalFunc)==0 ){ /* Internal-use-only functions are disallowed unless the - ** SQL is being compiled using sqlite3NestedParse() */ + ** SQL is being compiled using sqlite3NestedParse() or + ** the SQLITE_TESTCTRL_INTERNAL_FUNCTIONS test-control has be + ** used to activate internal functions for testing purposes */ no_such_func = 1; pDef = 0; + }else + if( (pDef->funcFlags & (SQLITE_FUNC_DIRECT|SQLITE_FUNC_UNSAFE))!=0 + && !IN_RENAME_OBJECT + ){ + if( pNC->ncFlags & NC_FromDDL ) ExprSetProperty(pExpr, EP_FromDDL); + sqlite3ExprFunctionUsable(pParse, pExpr, pDef); } } @@ -95924,30 +111747,30 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ || (pDef->xValue==0 && pDef->xInverse==0) || (pDef->xValue && pDef->xInverse && pDef->xSFunc && pDef->xFinalize) ); - if( pDef && pDef->xValue==0 && ExprHasProperty(pExpr, EP_WinFunc) ){ - sqlite3ErrorMsg(pParse, - "%.*s() may not be used as a window function", nId, zId + if( pDef && pDef->xValue==0 && pWin ){ + sqlite3ErrorMsg(pParse, + "%#T() may not be used as a window function", pExpr ); - pNC->nErr++; - }else if( + pNC->nNcErr++; + }else if( (is_agg && (pNC->ncFlags & NC_AllowAgg)==0) - || (is_agg && (pDef->funcFlags&SQLITE_FUNC_WINDOW) && !pExpr->y.pWin) - || (is_agg && pExpr->y.pWin && (pNC->ncFlags & NC_AllowWin)==0) + || (is_agg && (pDef->funcFlags&SQLITE_FUNC_WINDOW) && !pWin) + || (is_agg && pWin && (pNC->ncFlags & NC_AllowWin)==0) ){ const char *zType; - if( (pDef->funcFlags & SQLITE_FUNC_WINDOW) || pExpr->y.pWin ){ + if( (pDef->funcFlags & SQLITE_FUNC_WINDOW) || pWin ){ zType = "window"; }else{ zType = "aggregate"; } - sqlite3ErrorMsg(pParse, "misuse of %s function %.*s()",zType,nId,zId); - pNC->nErr++; + sqlite3ErrorMsg(pParse, "misuse of %s function %#T()",zType,pExpr); + pNC->nNcErr++; is_agg = 0; } #else if( (is_agg && (pNC->ncFlags & NC_AllowAgg)==0) ){ - sqlite3ErrorMsg(pParse,"misuse of aggregate function %.*s()",nId,zId); - pNC->nErr++; + sqlite3ErrorMsg(pParse,"misuse of aggregate function %#T()",pExpr); + pNC->nNcErr++; is_agg = 0; } #endif @@ -95956,94 +111779,143 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ && pParse->explain==0 #endif ){ - sqlite3ErrorMsg(pParse, "no such function: %.*s", nId, zId); - pNC->nErr++; + sqlite3ErrorMsg(pParse, "no such function: %#T", pExpr); + pNC->nNcErr++; }else if( wrong_num_args ){ - sqlite3ErrorMsg(pParse,"wrong number of arguments to function %.*s()", - nId, zId); - pNC->nErr++; + sqlite3ErrorMsg(pParse,"wrong number of arguments to function %#T()", + pExpr); + pNC->nNcErr++; + } +#ifndef SQLITE_OMIT_WINDOWFUNC + else if( is_agg==0 && ExprHasProperty(pExpr, EP_WinFunc) ){ + sqlite3ErrorMsg(pParse, + "FILTER may not be used with non-aggregate %#T()", + pExpr + ); + pNC->nNcErr++; + } +#endif + else if( is_agg==0 && pExpr->pLeft ){ + sqlite3ExprOrderByAggregateError(pParse, pExpr); + pNC->nNcErr++; } if( is_agg ){ /* Window functions may not be arguments of aggregate functions. ** Or arguments of other window functions. But aggregate functions ** may be arguments for window functions. */ #ifndef SQLITE_OMIT_WINDOWFUNC - pNC->ncFlags &= ~(NC_AllowWin | (!pExpr->y.pWin ? NC_AllowAgg : 0)); + pNC->ncFlags &= ~(NC_AllowWin | (!pWin ? NC_AllowAgg : 0)); #else pNC->ncFlags &= ~NC_AllowAgg; #endif } } + else if( ExprHasProperty(pExpr, EP_WinFunc) || pExpr->pLeft ){ + is_agg = 1; + } sqlite3WalkExprList(pWalker, pList); if( is_agg ){ + if( pExpr->pLeft ){ + assert( pExpr->pLeft->op==TK_ORDER ); + assert( ExprUseXList(pExpr->pLeft) ); + sqlite3WalkExprList(pWalker, pExpr->pLeft->x.pList); + } #ifndef SQLITE_OMIT_WINDOWFUNC - if( pExpr->y.pWin ){ + if( pWin && pParse->nErr==0 ){ Select *pSel = pNC->pWinSelect; - sqlite3WindowUpdate(pParse, pSel->pWinDefn, pExpr->y.pWin, pDef); - sqlite3WalkExprList(pWalker, pExpr->y.pWin->pPartition); - sqlite3WalkExprList(pWalker, pExpr->y.pWin->pOrderBy); - sqlite3WalkExpr(pWalker, pExpr->y.pWin->pFilter); - if( 0==pSel->pWin - || 0==sqlite3WindowCompare(pParse, pSel->pWin, pExpr->y.pWin) - ){ - pExpr->y.pWin->pNextWin = pSel->pWin; - pSel->pWin = pExpr->y.pWin; + assert( ExprUseYWin(pExpr) && pWin==pExpr->y.pWin ); + if( IN_RENAME_OBJECT==0 ){ + sqlite3WindowUpdate(pParse, pSel ? pSel->pWinDefn : 0, pWin, pDef); + if( pParse->db->mallocFailed ) break; } + sqlite3WalkExprList(pWalker, pWin->pPartition); + sqlite3WalkExprList(pWalker, pWin->pOrderBy); + sqlite3WalkExpr(pWalker, pWin->pFilter); + sqlite3WindowLink(pSel, pWin); pNC->ncFlags |= NC_HasWin; }else #endif /* SQLITE_OMIT_WINDOWFUNC */ { - NameContext *pNC2 = pNC; + NameContext *pNC2; /* For looping up thru outer contexts */ pExpr->op = TK_AGG_FUNCTION; pExpr->op2 = 0; - while( pNC2 && !sqlite3FunctionUsesThisSrc(pExpr, pNC2->pSrcList) ){ - pExpr->op2++; +#ifndef SQLITE_OMIT_WINDOWFUNC + if( ExprHasProperty(pExpr, EP_WinFunc) ){ + sqlite3WalkExpr(pWalker, pExpr->y.pWin->pFilter); + } +#endif + pNC2 = pNC; + while( pNC2 + && sqlite3ReferencesSrcList(pParse, pExpr, pNC2->pSrcList)==0 + ){ + pExpr->op2 += (1 + pNC2->nNestedSelect); pNC2 = pNC2->pNext; } - assert( pDef!=0 ); - if( pNC2 ){ + assert( pDef!=0 || IN_RENAME_OBJECT ); + if( pNC2 && pDef ){ + pExpr->op2 += pNC2->nNestedSelect; assert( SQLITE_FUNC_MINMAX==NC_MinMaxAgg ); + assert( SQLITE_FUNC_ANYORDER==NC_OrderAgg ); testcase( (pDef->funcFlags & SQLITE_FUNC_MINMAX)!=0 ); - pNC2->ncFlags |= NC_HasAgg | (pDef->funcFlags & SQLITE_FUNC_MINMAX); - + testcase( (pDef->funcFlags & SQLITE_FUNC_ANYORDER)!=0 ); + pNC2->ncFlags |= NC_HasAgg + | ((pDef->funcFlags^SQLITE_FUNC_ANYORDER) + & (SQLITE_FUNC_MINMAX|SQLITE_FUNC_ANYORDER)); } } pNC->ncFlags |= savedAllowFlags; } /* FIX ME: Compute pExpr->affinity based on the expected return - ** type of the function + ** type of the function */ return WRC_Prune; } #ifndef SQLITE_OMIT_SUBQUERY + case TK_EXISTS: case TK_SELECT: - case TK_EXISTS: testcase( pExpr->op==TK_EXISTS ); #endif case TK_IN: { testcase( pExpr->op==TK_IN ); - if( ExprHasProperty(pExpr, EP_xIsSelect) ){ + testcase( pExpr->op==TK_EXISTS ); + testcase( pExpr->op==TK_SELECT ); + if( ExprUseXSelect(pExpr) ){ int nRef = pNC->nRef; - notValid(pParse, pNC, "subqueries", NC_IsCheck|NC_PartIdx|NC_IdxExpr); - sqlite3WalkSelect(pWalker, pExpr->x.pSelect); + testcase( pNC->ncFlags & NC_IsCheck ); + testcase( pNC->ncFlags & NC_PartIdx ); + testcase( pNC->ncFlags & NC_IdxExpr ); + testcase( pNC->ncFlags & NC_GenCol ); + assert( pExpr->x.pSelect ); + if( pExpr->op==TK_EXISTS ) pParse->bHasExists = 1; + if( pNC->ncFlags & NC_SelfRef ){ + notValidImpl(pParse, pNC, "subqueries", pExpr, pExpr); + }else{ + sqlite3WalkSelect(pWalker, pExpr->x.pSelect); + } assert( pNC->nRef>=nRef ); if( nRef!=pNC->nRef ){ ExprSetProperty(pExpr, EP_VarSelect); - pNC->ncFlags |= NC_VarSelect; + pExpr->x.pSelect->selFlags |= SF_Correlated; } + pNC->ncFlags |= NC_Subquery; } break; } case TK_VARIABLE: { - notValid(pParse, pNC, "parameters", NC_IsCheck|NC_PartIdx|NC_IdxExpr); + testcase( pNC->ncFlags & NC_IsCheck ); + testcase( pNC->ncFlags & NC_PartIdx ); + testcase( pNC->ncFlags & NC_IdxExpr ); + testcase( pNC->ncFlags & NC_GenCol ); + sqlite3ResolveNotValid(pParse, pNC, "parameters", + NC_IsCheck|NC_PartIdx|NC_IdxExpr|NC_GenCol, pExpr, pExpr); break; } case TK_IS: case TK_ISNOT: { - Expr *pRight; + Expr *pRight = sqlite3ExprSkipCollateAndLikely(pExpr->pRight); assert( !ExprHasProperty(pExpr, EP_Reduced) ); /* Handle special cases of "x IS TRUE", "x IS FALSE", "x IS NOT TRUE", ** and "x IS NOT FALSE". */ - if( (pRight = pExpr->pRight)->op==TK_ID ){ + if( ALWAYS(pRight) && (pRight->op==TK_ID || pRight->op==TK_TRUEFALSE) ){ int rc = resolveExprStep(pWalker, pRight); if( rc==WRC_Abort ) return WRC_Abort; if( pRight->op==TK_TRUEFALSE ){ @@ -96052,7 +111924,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ return WRC_Continue; } } - /* Fall thru */ + /* no break */ deliberate_fall_through } case TK_BETWEEN: case TK_EQ: @@ -96066,6 +111938,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ assert( pExpr->pLeft!=0 ); nLeft = sqlite3ExprVectorSize(pExpr->pLeft); if( pExpr->op==TK_BETWEEN ){ + assert( ExprUseXList(pExpr) ); nRight = sqlite3ExprVectorSize(pExpr->x.pList->a[0].pExpr); if( nRight==nLeft ){ nRight = sqlite3ExprVectorSize(pExpr->x.pList->a[1].pExpr); @@ -96085,11 +111958,13 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ testcase( pExpr->op==TK_ISNOT ); testcase( pExpr->op==TK_BETWEEN ); sqlite3ErrorMsg(pParse, "row value misused"); + sqlite3RecordErrorOffsetOfExpr(pParse->db, pExpr); } - break; + break; } } - return (pParse->nErr || pParse->db->mallocFailed) ? WRC_Abort : WRC_Continue; + assert( pParse->db->mallocFailed==0 || pParse->nErr!=0 ); + return pParse->nErr ? WRC_Abort : WRC_Continue; } /* @@ -96114,10 +111989,13 @@ static int resolveAsName( UNUSED_PARAMETER(pParse); if( pE->op==TK_ID ){ - char *zCol = pE->u.zToken; + const char *zCol; + assert( !ExprHasProperty(pE, EP_IntValue) ); + zCol = pE->u.zToken; for(i=0; inExpr; i++){ - char *zAs = pEList->a[i].zName; - if( zAs!=0 && sqlite3StrICmp(zAs, zCol)==0 ){ + if( pEList->a[i].fg.eEName==ENAME_NAME + && sqlite3_stricmp(pEList->a[i].zEName, zCol)==0 + ){ return i+1; } } @@ -96155,7 +112033,7 @@ static int resolveOrderByTermToExprList( int rc; /* Return code from subprocedures */ u8 savedSuppErr; /* Saved value of db->suppressErr */ - assert( sqlite3ExprIsInteger(pE, &i)==0 ); + assert( sqlite3ExprIsInteger(pE, &i, 0)==0 ); pEList = pSelect->pEList; /* Resolve all names in the ORDER BY term expression @@ -96164,8 +112042,8 @@ static int resolveOrderByTermToExprList( nc.pParse = pParse; nc.pSrcList = pSelect->pSrc; nc.uNC.pEList = pEList; - nc.ncFlags = NC_AllowAgg|NC_UEList; - nc.nErr = 0; + nc.ncFlags = NC_AllowAgg|NC_UEList|NC_NoSelect; + nc.nNcErr = 0; db = pParse->db; savedSuppErr = db->suppressErr; db->suppressErr = 1; @@ -96194,11 +112072,13 @@ static void resolveOutOfRangeError( Parse *pParse, /* The error context into which to write the error */ const char *zType, /* "ORDER" or "GROUP" */ int i, /* The index (1-based) of the term out of range */ - int mx /* Largest permissible value of i */ + int mx, /* Largest permissible value of i */ + Expr *pError /* Associate the error with the expression */ ){ - sqlite3ErrorMsg(pParse, + sqlite3ErrorMsg(pParse, "%r %s BY term out of range - should be " "between 1 and %d", i, zType, mx); + sqlite3RecordErrorOffsetOfExpr(pParse->db, pError); } /* @@ -96234,7 +112114,7 @@ static int resolveCompoundOrderBy( return 1; } for(i=0; inExpr; i++){ - pOrderBy->a[i].done = 0; + pOrderBy->a[i].fg.done = 0; } pSelect->pNext = 0; while( pSelect->pPrior ){ @@ -96249,51 +112129,45 @@ static int resolveCompoundOrderBy( for(i=0, pItem=pOrderBy->a; inExpr; i++, pItem++){ int iCol = -1; Expr *pE, *pDup; - if( pItem->done ) continue; - pE = sqlite3ExprSkipCollate(pItem->pExpr); - if( sqlite3ExprIsInteger(pE, &iCol) ){ + if( pItem->fg.done ) continue; + pE = sqlite3ExprSkipCollateAndLikely(pItem->pExpr); + if( NEVER(pE==0) ) continue; + if( sqlite3ExprIsInteger(pE, &iCol, 0) ){ if( iCol<=0 || iCol>pEList->nExpr ){ - resolveOutOfRangeError(pParse, "ORDER", i+1, pEList->nExpr); + resolveOutOfRangeError(pParse, "ORDER", i+1, pEList->nExpr, pE); return 1; } }else{ iCol = resolveAsName(pParse, pEList, pE); if( iCol==0 ){ /* Now test if expression pE matches one of the values returned - ** by pSelect. In the usual case this is done by duplicating the + ** by pSelect. In the usual case this is done by duplicating the ** expression, resolving any symbols in it, and then comparing ** it against each expression returned by the SELECT statement. ** Once the comparisons are finished, the duplicate expression ** is deleted. ** - ** Or, if this is running as part of an ALTER TABLE operation, - ** resolve the symbols in the actual expression, not a duplicate. - ** And, if one of the comparisons is successful, leave the expression - ** as is instead of transforming it to an integer as in the usual - ** case. This allows the code in alter.c to modify column - ** refererences within the ORDER BY expression as required. */ - if( IN_RENAME_OBJECT ){ - pDup = pE; - }else{ - pDup = sqlite3ExprDup(db, pE, 0); - } + ** If this is running as part of an ALTER TABLE operation and + ** the symbols resolve successfully, also resolve the symbols in the + ** actual expression. This allows the code in alter.c to modify + ** column references within the ORDER BY expression as required. */ + pDup = sqlite3ExprDup(db, pE, 0); if( !db->mallocFailed ){ assert(pDup); iCol = resolveOrderByTermToExprList(pParse, pSelect, pDup); + if( IN_RENAME_OBJECT && iCol>0 ){ + resolveOrderByTermToExprList(pParse, pSelect, pE); + } } - if( !IN_RENAME_OBJECT ){ - sqlite3ExprDelete(db, pDup); - } + sqlite3ExprDelete(db, pDup); } } if( iCol>0 ){ /* Convert the ORDER BY term into an integer column number iCol, - ** taking care to preserve the COLLATE clause if it exists */ + ** taking care to preserve the COLLATE clause if it exists. */ if( !IN_RENAME_OBJECT ){ - Expr *pNew = sqlite3Expr(db, TK_INTEGER, 0); + Expr *pNew = sqlite3ExprInt32(db, iCol); if( pNew==0 ) return 1; - pNew->flags |= EP_IntValue; - pNew->u.iValue = iCol; if( pItem->pExpr==pE ){ pItem->pExpr = pNew; }else{ @@ -96306,7 +112180,7 @@ static int resolveCompoundOrderBy( sqlite3ExprDelete(db, pE); pItem->u.x.iOrderByCol = (u16)iCol; } - pItem->done = 1; + pItem->fg.done = 1; }else{ moreToDo = 1; } @@ -96314,7 +112188,7 @@ static int resolveCompoundOrderBy( pSelect = pSelect->pNext; } for(i=0; inExpr; i++){ - if( pOrderBy->a[i].done==0 ){ + if( pOrderBy->a[i].fg.done==0 ){ sqlite3ErrorMsg(pParse, "%r ORDER BY term does not match any " "column in the result set", i+1); return 1; @@ -96344,7 +112218,7 @@ SQLITE_PRIVATE int sqlite3ResolveOrderGroupBy( ExprList *pEList; struct ExprList_item *pItem; - if( pOrderBy==0 || pParse->db->mallocFailed ) return 0; + if( pOrderBy==0 || pParse->db->mallocFailed || IN_RENAME_OBJECT ) return 0; if( pOrderBy->nExpr>db->aLimit[SQLITE_LIMIT_COLUMN] ){ sqlite3ErrorMsg(pParse, "too many terms in %s BY clause", zType); return 1; @@ -96354,11 +112228,10 @@ SQLITE_PRIVATE int sqlite3ResolveOrderGroupBy( for(i=0, pItem=pOrderBy->a; inExpr; i++, pItem++){ if( pItem->u.x.iOrderByCol ){ if( pItem->u.x.iOrderByCol>pEList->nExpr ){ - resolveOutOfRangeError(pParse, zType, i+1, pEList->nExpr); + resolveOutOfRangeError(pParse, zType, i+1, pEList->nExpr, 0); return 1; } - resolveAlias(pParse, pEList, pItem->u.x.iOrderByCol-1, pItem->pExpr, - zType,0); + resolveAlias(pParse, pEList, pItem->u.x.iOrderByCol-1, pItem->pExpr,0); } } return 0; @@ -96366,17 +112239,13 @@ SQLITE_PRIVATE int sqlite3ResolveOrderGroupBy( #ifndef SQLITE_OMIT_WINDOWFUNC /* -** Walker callback for resolveRemoveWindows(). +** Walker callback for windowRemoveExprFromSelect(). */ static int resolveRemoveWindowsCb(Walker *pWalker, Expr *pExpr){ + UNUSED_PARAMETER(pWalker); if( ExprHasProperty(pExpr, EP_WinFunc) ){ - Window **pp; - for(pp=&pWalker->u.pSelect->pWin; *pp; pp=&(*pp)->pNextWin){ - if( *pp==pExpr->y.pWin ){ - *pp = (*pp)->pNextWin; - break; - } - } + Window *pWin = pExpr->y.pWin; + sqlite3WindowUnlinkFromSelect(pWin); } return WRC_Continue; } @@ -96385,16 +112254,18 @@ static int resolveRemoveWindowsCb(Walker *pWalker, Expr *pExpr){ ** Remove any Window objects owned by the expression pExpr from the ** Select.pWin list of Select object pSelect. */ -static void resolveRemoveWindows(Select *pSelect, Expr *pExpr){ - Walker sWalker; - memset(&sWalker, 0, sizeof(Walker)); - sWalker.xExprCallback = resolveRemoveWindowsCb; - sWalker.u.pSelect = pSelect; - sqlite3WalkExpr(&sWalker, pExpr); +static void windowRemoveExprFromSelect(Select *pSelect, Expr *pExpr){ + if( pSelect->pWin ){ + Walker sWalker; + memset(&sWalker, 0, sizeof(Walker)); + sWalker.xExprCallback = resolveRemoveWindowsCb; + sWalker.u.pSelect = pSelect; + sqlite3WalkExpr(&sWalker, pExpr); + } } #else -# define resolveRemoveWindows(x,y) -#endif +# define windowRemoveExprFromSelect(a, b) +#endif /* SQLITE_OMIT_WINDOWFUNC */ /* ** pOrderBy is an ORDER BY or GROUP BY clause in SELECT statement pSelect. @@ -96426,12 +112297,13 @@ static int resolveOrderGroupBy( Parse *pParse; /* Parsing context */ int nResult; /* Number of terms in the result set */ - if( pOrderBy==0 ) return 0; + assert( pOrderBy!=0 ); nResult = pSelect->pEList->nExpr; pParse = pNC->pParse; for(i=0, pItem=pOrderBy->a; inExpr; i++, pItem++){ Expr *pE = pItem->pExpr; - Expr *pE2 = sqlite3ExprSkipCollate(pE); + Expr *pE2 = sqlite3ExprSkipCollateAndLikely(pE); + if( NEVER(pE2==0) ) continue; if( zType[0]!='G' ){ iCol = resolveAsName(pParse, pSelect->pEList, pE2); if( iCol>0 ){ @@ -96443,12 +112315,12 @@ static int resolveOrderGroupBy( continue; } } - if( sqlite3ExprIsInteger(pE2, &iCol) ){ + if( sqlite3ExprIsInteger(pE2, &iCol, 0) ){ /* The ORDER BY term is an integer constant. Again, set the column ** number so that sqlite3ResolveOrderGroupBy() will convert the ** order-by term to a copy of the result-set expression */ if( iCol<1 || iCol>0xffff ){ - resolveOutOfRangeError(pParse, zType, i+1, nResult); + resolveOutOfRangeError(pParse, zType, i+1, nResult, pE2); return 1; } pItem->u.x.iOrderByCol = (u16)iCol; @@ -96462,10 +112334,10 @@ static int resolveOrderGroupBy( } for(j=0; jpEList->nExpr; j++){ if( sqlite3ExprCompare(0, pE, pSelect->pEList->a[j].pExpr, -1)==0 ){ - /* Since this expresion is being changed into a reference + /* Since this expression is being changed into a reference ** to an identical expression in the result set, remove all Window ** objects belonging to the expression from the Select.pWin list. */ - resolveRemoveWindows(pSelect, pE); + windowRemoveExprFromSelect(pSelect, pE); pItem->u.x.iOrderByCol = j+1; } } @@ -96486,7 +112358,7 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ ExprList *pGroupBy; /* The GROUP BY clause */ Select *pLeftmost; /* Left-most of SELECT of a compound */ sqlite3 *db; /* Database connection */ - + assert( p!=0 ); if( p->selFlags & SF_Resolved ){ @@ -96506,7 +112378,7 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ */ if( (p->selFlags & SF_Expanded)==0 ){ sqlite3SelectPrep(pParse, p, pOuterNC); - return (pParse->nErr || db->mallocFailed) ? WRC_Abort : WRC_Prune; + return pParse->nErr ? WRC_Abort : WRC_Prune; } isCompound = p->pPrior!=0; @@ -96534,70 +112406,76 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ ** moves the pOrderBy down to the sub-query. It will be moved back ** after the names have been resolved. */ if( p->selFlags & SF_Converted ){ - Select *pSub = p->pSrc->a[0].pSelect; + Select *pSub; + assert( p->pSrc->a[0].fg.isSubquery ); + assert( p->pSrc->a[0].u4.pSubq!=0 ); + pSub = p->pSrc->a[0].u4.pSubq->pSelect; + assert( pSub!=0 ); assert( p->pSrc->nSrc==1 && p->pOrderBy ); assert( pSub->pPrior && pSub->pOrderBy==0 ); pSub->pOrderBy = p->pOrderBy; p->pOrderBy = 0; } - - /* Recursively resolve names in all subqueries + + /* Recursively resolve names in all subqueries in the FROM clause */ + if( pOuterNC ) pOuterNC->nNestedSelect++; for(i=0; ipSrc->nSrc; i++){ - struct SrcList_item *pItem = &p->pSrc->a[i]; - if( pItem->pSelect && (pItem->pSelect->selFlags & SF_Resolved)==0 ){ - NameContext *pNC; /* Used to iterate name contexts */ - int nRef = 0; /* Refcount for pOuterNC and outer contexts */ + SrcItem *pItem = &p->pSrc->a[i]; + assert( pItem->zName!=0 + || pItem->fg.isSubquery ); /* Test of tag-20240424-1*/ + if( pItem->fg.isSubquery + && (pItem->u4.pSubq->pSelect->selFlags & SF_Resolved)==0 + ){ + int nRef = pOuterNC ? pOuterNC->nRef : 0; const char *zSavedContext = pParse->zAuthContext; - /* Count the total number of references to pOuterNC and all of its - ** parent contexts. After resolving references to expressions in - ** pItem->pSelect, check if this value has changed. If so, then - ** SELECT statement pItem->pSelect must be correlated. Set the - ** pItem->fg.isCorrelated flag if this is the case. */ - for(pNC=pOuterNC; pNC; pNC=pNC->pNext) nRef += pNC->nRef; - if( pItem->zName ) pParse->zAuthContext = pItem->zName; - sqlite3ResolveSelectNames(pParse, pItem->pSelect, pOuterNC); + sqlite3ResolveSelectNames(pParse, pItem->u4.pSubq->pSelect, pOuterNC); pParse->zAuthContext = zSavedContext; - if( pParse->nErr || db->mallocFailed ) return WRC_Abort; - - for(pNC=pOuterNC; pNC; pNC=pNC->pNext) nRef -= pNC->nRef; - assert( pItem->fg.isCorrelated==0 && nRef<=0 ); - pItem->fg.isCorrelated = (nRef!=0); + if( pParse->nErr ) return WRC_Abort; + assert( db->mallocFailed==0 ); + + /* If the number of references to the outer context changed when + ** expressions in the sub-select were resolved, the sub-select + ** is correlated. It is not required to check the refcount on any + ** but the innermost outer context object, as lookupName() increments + ** the refcount on all contexts between the current one and the + ** context containing the column when it resolves a name. */ + if( pOuterNC ){ + assert( pItem->fg.isCorrelated==0 && pOuterNC->nRef>=nRef ); + pItem->fg.isCorrelated = (pOuterNC->nRef>nRef); + } } } - + if( pOuterNC && ALWAYS(pOuterNC->nNestedSelect>0) ){ + pOuterNC->nNestedSelect--; + } + /* Set up the local name-context to pass to sqlite3ResolveExprNames() to ** resolve the result-set expression list. */ sNC.ncFlags = NC_AllowAgg|NC_AllowWin; sNC.pSrcList = p->pSrc; sNC.pNext = pOuterNC; - + /* Resolve names in the result set. */ if( sqlite3ResolveExprListNames(&sNC, p->pEList) ) return WRC_Abort; sNC.ncFlags &= ~NC_AllowWin; - - /* If there are no aggregate functions in the result-set, and no GROUP BY + + /* If there are no aggregate functions in the result-set, and no GROUP BY ** expression, do not allow aggregates in any of the other expressions. */ assert( (p->selFlags & SF_Aggregate)==0 ); pGroupBy = p->pGroupBy; if( pGroupBy || (sNC.ncFlags & NC_HasAgg)!=0 ){ assert( NC_MinMaxAgg==SF_MinMaxAgg ); - p->selFlags |= SF_Aggregate | (sNC.ncFlags&NC_MinMaxAgg); + assert( NC_OrderAgg==SF_OrderByReqd ); + p->selFlags |= SF_Aggregate | (sNC.ncFlags&(NC_MinMaxAgg|NC_OrderAgg)); }else{ sNC.ncFlags &= ~NC_AllowAgg; } - - /* If a HAVING clause is present, then there must be a GROUP BY clause. - */ - if( p->pHaving && !pGroupBy ){ - sqlite3ErrorMsg(pParse, "a GROUP BY clause is required before HAVING"); - return WRC_Abort; - } - + /* Add the output column list to the name-context before parsing the ** other expressions in the SELECT statement. This is so that ** expressions in the WHERE clause (etc.) can refer to expressions by @@ -96606,35 +112484,55 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ ** Minor point: If this is the case, then the expression will be ** re-evaluated for each reference to it. */ - assert( (sNC.ncFlags & (NC_UAggInfo|NC_UUpsert))==0 ); + assert( (sNC.ncFlags & (NC_UAggInfo|NC_UUpsert|NC_UBaseReg))==0 ); sNC.uNC.pEList = p->pEList; sNC.ncFlags |= NC_UEList; - if( sqlite3ResolveExprNames(&sNC, p->pHaving) ) return WRC_Abort; + if( p->pHaving ){ + if( (p->selFlags & SF_Aggregate)==0 ){ + sqlite3ErrorMsg(pParse, "HAVING clause on a non-aggregate query"); + return WRC_Abort; + } + if( sqlite3ResolveExprNames(&sNC, p->pHaving) ) return WRC_Abort; + } + sNC.ncFlags |= NC_Where; if( sqlite3ResolveExprNames(&sNC, p->pWhere) ) return WRC_Abort; + sNC.ncFlags &= ~NC_Where; /* Resolve names in table-valued-function arguments */ for(i=0; ipSrc->nSrc; i++){ - struct SrcList_item *pItem = &p->pSrc->a[i]; + SrcItem *pItem = &p->pSrc->a[i]; if( pItem->fg.isTabFunc - && sqlite3ResolveExprListNames(&sNC, pItem->u1.pFuncArg) + && sqlite3ResolveExprListNames(&sNC, pItem->u1.pFuncArg) ){ return WRC_Abort; } } - /* The ORDER BY and GROUP BY clauses may not refer to terms in - ** outer queries - */ - sNC.pNext = 0; +#ifndef SQLITE_OMIT_WINDOWFUNC + if( IN_RENAME_OBJECT ){ + Window *pWin; + for(pWin=p->pWinDefn; pWin; pWin=pWin->pNextWin){ + if( sqlite3ResolveExprListNames(&sNC, pWin->pOrderBy) + || sqlite3ResolveExprListNames(&sNC, pWin->pPartition) + ){ + return WRC_Abort; + } + } + } +#endif + sNC.ncFlags |= NC_AllowAgg|NC_AllowWin; - /* If this is a converted compound query, move the ORDER BY clause from + /* If this is a converted compound query, move the ORDER BY clause from ** the sub-query back to the parent query. At this point each term ** within the ORDER BY clause has been transformed to an integer value. ** These integers will be replaced by copies of the corresponding result ** set expressions by the call to resolveOrderGroupBy() below. */ if( p->selFlags & SF_Converted ){ - Select *pSub = p->pSrc->a[0].pSelect; + Select *pSub; + assert( p->pSrc->a[0].fg.isSubquery ); + pSub = p->pSrc->a[0].u4.pSubq->pSelect; + assert( pSub!=0 ); p->pOrderBy = pSub->pOrderBy; pSub->pOrderBy = 0; } @@ -96649,7 +112547,8 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ ** is not detected until much later, and so we need to go ahead and ** resolve those symbols on the incorrect ORDER BY for consistency. */ - if( isCompound<=nCompound /* Defer right-most ORDER BY of a compound */ + if( p->pOrderBy!=0 + && isCompound<=nCompound /* Defer right-most ORDER BY of a compound */ && resolveOrderGroupBy(&sNC, p, p->pOrderBy, "ORDER") ){ return WRC_Abort; @@ -96658,13 +112557,13 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ return WRC_Abort; } sNC.ncFlags &= ~NC_AllowWin; - - /* Resolve the GROUP BY clause. At the same time, make sure + + /* Resolve the GROUP BY clause. At the same time, make sure ** the GROUP BY clause does not contain aggregate functions. */ if( pGroupBy ){ struct ExprList_item *pItem; - + if( resolveOrderGroupBy(&sNC, p, pGroupBy, "GROUP") || db->mallocFailed ){ return WRC_Abort; } @@ -96677,19 +112576,6 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ } } -#ifndef SQLITE_OMIT_WINDOWFUNC - if( IN_RENAME_OBJECT ){ - Window *pWin; - for(pWin=p->pWinDefn; pWin; pWin=pWin->pNextWin){ - if( sqlite3ResolveExprListNames(&sNC, pWin->pOrderBy) - || sqlite3ResolveExprListNames(&sNC, pWin->pPartition) - ){ - return WRC_Abort; - } - } - } -#endif - /* If this is part of a compound SELECT, check that it has the right ** number of expressions in the select list. */ if( p->pNext && p->pEList->nExpr!=p->pNext->pEList->nExpr ){ @@ -96697,6 +112583,14 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ return WRC_Abort; } + /* If the SELECT statement contains ON clauses that were moved into + ** the WHERE clause, go through and verify that none of the terms + ** in the ON clauses reference tables to the right of the ON clause. */ + if( (p->selFlags & SF_OnToWhere) ){ + sqlite3SelectCheckOnClauses(pParse, p); + if( pParse->nErr ) return WRC_Abort; + } + /* Advance to the next term of the compound */ p = p->pPrior; @@ -96719,7 +112613,7 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ ** checking on function usage and set a flag if any aggregate functions ** are seen. ** -** To resolve table columns references we look for nodes (or subtrees) of the +** To resolve table columns references we look for nodes (or subtrees) of the ** form X.Y.Z or Y.Z or just Z where ** ** X: The name of a database. Ex: "main" or "temp" or @@ -96751,7 +112645,7 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ ** ** SELECT a+b AS x, c+d AS y FROM t1 ORDER BY a+b; ** -** Function calls are checked to make sure that the function is +** Function calls are checked to make sure that the function is ** defined and that the correct number of arguments are specified. ** If the function is an aggregate function, then the NC_HasAgg flag is ** set and the opcode is changed from TK_FUNCTION to TK_AGG_FUNCTION. @@ -96761,19 +112655,19 @@ static int resolveSelectStep(Walker *pWalker, Select *p){ ** An error message is left in pParse if anything is amiss. The number ** if errors is returned. */ -SQLITE_PRIVATE int sqlite3ResolveExprNames( +SQLITE_PRIVATE int sqlite3ResolveExprNames( NameContext *pNC, /* Namespace to resolve expressions in. */ Expr *pExpr /* The expression to be analyzed. */ ){ - u16 savedHasAgg; + int savedHasAgg; Walker w; if( pExpr==0 ) return SQLITE_OK; - savedHasAgg = pNC->ncFlags & (NC_HasAgg|NC_MinMaxAgg|NC_HasWin); - pNC->ncFlags &= ~(NC_HasAgg|NC_MinMaxAgg|NC_HasWin); + savedHasAgg = pNC->ncFlags & (NC_HasAgg|NC_MinMaxAgg|NC_HasWin|NC_OrderAgg); + pNC->ncFlags &= ~(NC_HasAgg|NC_MinMaxAgg|NC_HasWin|NC_OrderAgg); w.pParse = pNC->pParse; w.xExprCallback = resolveExprStep; - w.xSelectCallback = resolveSelectStep; + w.xSelectCallback = (pNC->ncFlags & NC_NoSelect) ? 0 : resolveSelectStep; w.xSelectCallback2 = 0; w.u.pNC = pNC; #if SQLITE_MAX_EXPR_DEPTH>0 @@ -96782,7 +112676,8 @@ SQLITE_PRIVATE int sqlite3ResolveExprNames( return SQLITE_ERROR; } #endif - sqlite3WalkExpr(&w, pExpr); + assert( pExpr!=0 ); + sqlite3WalkExprNN(&w, pExpr); #if SQLITE_MAX_EXPR_DEPTH>0 w.pParse->nHeight -= pExpr->nHeight; #endif @@ -96792,30 +112687,64 @@ SQLITE_PRIVATE int sqlite3ResolveExprNames( testcase( pNC->ncFlags & NC_HasWin ); ExprSetProperty(pExpr, pNC->ncFlags & (NC_HasAgg|NC_HasWin) ); pNC->ncFlags |= savedHasAgg; - return pNC->nErr>0 || w.pParse->nErr>0; + return pNC->nNcErr>0 || w.pParse->nErr>0; } /* ** Resolve all names for all expression in an expression list. This is ** just like sqlite3ResolveExprNames() except that it works for an expression ** list rather than a single expression. +** +** The return value is SQLITE_OK (0) for success or SQLITE_ERROR (1) for a +** failure. */ -SQLITE_PRIVATE int sqlite3ResolveExprListNames( +SQLITE_PRIVATE int sqlite3ResolveExprListNames( NameContext *pNC, /* Namespace to resolve expressions in. */ ExprList *pList /* The expression list to be analyzed. */ ){ int i; - if( pList ){ - for(i=0; inExpr; i++){ - if( sqlite3ResolveExprNames(pNC, pList->a[i].pExpr) ) return WRC_Abort; + int savedHasAgg = 0; + Walker w; + if( pList==0 ) return SQLITE_OK; + w.pParse = pNC->pParse; + w.xExprCallback = resolveExprStep; + w.xSelectCallback = resolveSelectStep; + w.xSelectCallback2 = 0; + w.u.pNC = pNC; + savedHasAgg = pNC->ncFlags & (NC_HasAgg|NC_MinMaxAgg|NC_HasWin|NC_OrderAgg); + pNC->ncFlags &= ~(NC_HasAgg|NC_MinMaxAgg|NC_HasWin|NC_OrderAgg); + for(i=0; inExpr; i++){ + Expr *pExpr = pList->a[i].pExpr; + if( pExpr==0 ) continue; +#if SQLITE_MAX_EXPR_DEPTH>0 + w.pParse->nHeight += pExpr->nHeight; + if( sqlite3ExprCheckHeight(w.pParse, w.pParse->nHeight) ){ + return SQLITE_ERROR; } +#endif + sqlite3WalkExprNN(&w, pExpr); +#if SQLITE_MAX_EXPR_DEPTH>0 + w.pParse->nHeight -= pExpr->nHeight; +#endif + assert( EP_Agg==NC_HasAgg ); + assert( EP_Win==NC_HasWin ); + testcase( pNC->ncFlags & NC_HasAgg ); + testcase( pNC->ncFlags & NC_HasWin ); + if( pNC->ncFlags & (NC_HasAgg|NC_MinMaxAgg|NC_HasWin|NC_OrderAgg) ){ + ExprSetProperty(pExpr, pNC->ncFlags & (NC_HasAgg|NC_HasWin) ); + savedHasAgg |= pNC->ncFlags & + (NC_HasAgg|NC_MinMaxAgg|NC_HasWin|NC_OrderAgg); + pNC->ncFlags &= ~(NC_HasAgg|NC_MinMaxAgg|NC_HasWin|NC_OrderAgg); + } + if( w.pParse->nErr>0 ) return SQLITE_ERROR; } - return WRC_Continue; + pNC->ncFlags |= savedHasAgg; + return SQLITE_OK; } /* ** Resolve all names in all expressions of a SELECT and in all -** decendents of the SELECT, including compounds off of p->pPrior, +** descendants of the SELECT, including compounds off of p->pPrior, ** subqueries in expressions, and subqueries used as FROM clause ** terms. ** @@ -96845,10 +112774,13 @@ SQLITE_PRIVATE void sqlite3ResolveSelectNames( ** Resolve names in expressions that can only reference a single table ** or which cannot reference any tables at all. Examples: ** -** (1) CHECK constraints -** (2) WHERE clauses on partial indices -** (3) Expressions in indexes on expressions -** (4) Expression arguments to VACUUM INTO. +** "type" flag +** ------------ +** (1) CHECK constraints NC_IsCheck +** (2) WHERE clauses on partial indices NC_PartIdx +** (3) Expressions in indexes on expressions NC_IdxExpr +** (4) Expression arguments to VACUUM INTO. 0 +** (5) GENERATED ALWAYS as expressions NC_GenCol ** ** In all cases except (4), the Expr.iTable value for Expr.op==TK_COLUMN ** nodes of the expression is set to -1 and the Expr.iColumn value is @@ -96857,29 +112789,40 @@ SQLITE_PRIVATE void sqlite3ResolveSelectNames( ** Any errors cause an error message to be set in pParse. */ SQLITE_PRIVATE int sqlite3ResolveSelfReference( - Parse *pParse, /* Parsing context */ - Table *pTab, /* The table being referenced, or NULL */ - int type, /* NC_IsCheck or NC_PartIdx or NC_IdxExpr, or 0 */ - Expr *pExpr, /* Expression to resolve. May be NULL. */ - ExprList *pList /* Expression list to resolve. May be NULL. */ + Parse *pParse, /* Parsing context */ + Table *pTab, /* The table being referenced, or NULL */ + int type, /* NC_IsCheck, NC_PartIdx, NC_IdxExpr, NC_GenCol, or 0 */ + Expr *pExpr, /* Expression to resolve. May be NULL. */ + ExprList *pList /* Expression list to resolve. May be NULL. */ ){ - SrcList sSrc; /* Fake SrcList for pParse->pNewTable */ + SrcList *pSrc; /* Fake SrcList for pParse->pNewTable */ NameContext sNC; /* Name context for pParse->pNewTable */ int rc; + union { + SrcList sSrc; + u8 srcSpace[SZ_SRCLIST_1]; /* Memory space for the fake SrcList */ + } uSrc; assert( type==0 || pTab!=0 ); - assert( type==NC_IsCheck || type==NC_PartIdx || type==NC_IdxExpr || pTab==0 ); + assert( type==NC_IsCheck || type==NC_PartIdx || type==NC_IdxExpr + || type==NC_GenCol || pTab==0 ); memset(&sNC, 0, sizeof(sNC)); - memset(&sSrc, 0, sizeof(sSrc)); + memset(&uSrc, 0, sizeof(uSrc)); + pSrc = &uSrc.sSrc; if( pTab ){ - sSrc.nSrc = 1; - sSrc.a[0].zName = pTab->zName; - sSrc.a[0].pTab = pTab; - sSrc.a[0].iCursor = -1; + pSrc->nSrc = 1; + pSrc->a[0].zName = pTab->zName; + pSrc->a[0].pSTab = pTab; + pSrc->a[0].iCursor = -1; + if( pTab->pSchema!=pParse->db->aDb[1].pSchema ){ + /* Cause EP_FromDDL to be set on TK_FUNCTION nodes of non-TEMP + ** schema elements */ + type |= NC_FromDDL; + } } sNC.pParse = pParse; - sNC.pSrcList = &sSrc; - sNC.ncFlags = type; + sNC.pSrcList = pSrc; + sNC.ncFlags = type | NC_IsDDL; if( (rc = sqlite3ResolveExprNames(&sNC, pExpr))!=SQLITE_OK ) return rc; if( pList ) rc = sqlite3ResolveExprListNames(&sNC, pList); return rc; @@ -96910,16 +112853,16 @@ static int exprCodeVector(Parse *pParse, Expr *p, int *piToFree); /* ** Return the affinity character for a single column of a table. */ -SQLITE_PRIVATE char sqlite3TableColumnAffinity(Table *pTab, int iCol){ - assert( iColnCol ); - return iCol>=0 ? pTab->aCol[iCol].affinity : SQLITE_AFF_INTEGER; +SQLITE_PRIVATE char sqlite3TableColumnAffinity(const Table *pTab, int iCol){ + if( iCol<0 || NEVER(iCol>=pTab->nCol) ) return SQLITE_AFF_INTEGER; + return pTab->aCol[iCol].affinity; } /* ** Return the 'affinity' of the expression pExpr if any. ** ** If pExpr is a column, a reference to a column via an 'AS' alias, -** or a sub-select with a column as the return value, then the +** or a sub-select with a column as the return value, then the ** affinity of that column is returned. Otherwise, 0x00 is returned, ** indicating no affinity for the expression. ** @@ -96931,32 +112874,126 @@ SQLITE_PRIVATE char sqlite3TableColumnAffinity(Table *pTab, int iCol){ ** SELECT a AS b FROM t1 WHERE b; ** SELECT * FROM t1 WHERE (select a from t1); */ -SQLITE_PRIVATE char sqlite3ExprAffinity(Expr *pExpr){ +SQLITE_PRIVATE char sqlite3ExprAffinity(const Expr *pExpr){ int op; - pExpr = sqlite3ExprSkipCollate(pExpr); - if( pExpr->flags & EP_Generic ) return 0; op = pExpr->op; - if( op==TK_SELECT ){ - assert( pExpr->flags&EP_xIsSelect ); - return sqlite3ExprAffinity(pExpr->x.pSelect->pEList->a[0].pExpr); - } - if( op==TK_REGISTER ) op = pExpr->op2; + while( 1 /* exit-by-break */ ){ + if( op==TK_COLUMN || (op==TK_AGG_COLUMN && pExpr->y.pTab!=0) ){ + assert( ExprUseYTab(pExpr) ); + assert( pExpr->y.pTab!=0 ); + return sqlite3TableColumnAffinity(pExpr->y.pTab, pExpr->iColumn); + } + if( op==TK_SELECT ){ + assert( ExprUseXSelect(pExpr) ); + assert( pExpr->x.pSelect!=0 ); + assert( pExpr->x.pSelect->pEList!=0 ); + assert( pExpr->x.pSelect->pEList->a[0].pExpr!=0 ); + return sqlite3ExprAffinity(pExpr->x.pSelect->pEList->a[0].pExpr); + } #ifndef SQLITE_OMIT_CAST - if( op==TK_CAST ){ - assert( !ExprHasProperty(pExpr, EP_IntValue) ); - return sqlite3AffinityType(pExpr->u.zToken, 0); - } + if( op==TK_CAST ){ + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + return sqlite3AffinityType(pExpr->u.zToken, 0); + } #endif - if( (op==TK_AGG_COLUMN || op==TK_COLUMN) && pExpr->y.pTab ){ - return sqlite3TableColumnAffinity(pExpr->y.pTab, pExpr->iColumn); - } - if( op==TK_SELECT_COLUMN ){ - assert( pExpr->pLeft->flags&EP_xIsSelect ); - return sqlite3ExprAffinity( - pExpr->pLeft->x.pSelect->pEList->a[pExpr->iColumn].pExpr - ); + if( op==TK_SELECT_COLUMN ){ + assert( pExpr->pLeft!=0 && ExprUseXSelect(pExpr->pLeft) ); + assert( pExpr->iColumn < pExpr->iTable ); + assert( pExpr->iColumn >= 0 ); + assert( pExpr->iTable==pExpr->pLeft->x.pSelect->pEList->nExpr ); + return sqlite3ExprAffinity( + pExpr->pLeft->x.pSelect->pEList->a[pExpr->iColumn].pExpr + ); + } + if( op==TK_VECTOR + || (op==TK_FUNCTION && pExpr->affExpr==SQLITE_AFF_DEFER) + ){ + assert( ExprUseXList(pExpr) ); + return sqlite3ExprAffinity(pExpr->x.pList->a[0].pExpr); + } + if( ExprHasProperty(pExpr, EP_Skip|EP_IfNullRow) ){ + assert( pExpr->op==TK_COLLATE + || pExpr->op==TK_IF_NULL_ROW + || (pExpr->op==TK_REGISTER && pExpr->op2==TK_IF_NULL_ROW) ); + pExpr = pExpr->pLeft; + op = pExpr->op; + continue; + } + if( op!=TK_REGISTER ) break; + op = pExpr->op2; + if( NEVER( op==TK_REGISTER ) ) break; } - return pExpr->affinity; + return pExpr->affExpr; +} + +/* +** Make a guess at all the possible datatypes of the result that could +** be returned by an expression. Return a bitmask indicating the answer: +** +** 0x01 Numeric +** 0x02 Text +** 0x04 Blob +** +** If the expression must return NULL, then 0x00 is returned. +*/ +SQLITE_PRIVATE int sqlite3ExprDataType(const Expr *pExpr){ + while( pExpr ){ + switch( pExpr->op ){ + case TK_COLLATE: + case TK_IF_NULL_ROW: + case TK_UPLUS: { + pExpr = pExpr->pLeft; + break; + } + case TK_NULL: { + pExpr = 0; + break; + } + case TK_STRING: { + return 0x02; + } + case TK_BLOB: { + return 0x04; + } + case TK_CONCAT: { + return 0x06; + } + case TK_VARIABLE: + case TK_AGG_FUNCTION: + case TK_FUNCTION: { + return 0x07; + } + case TK_COLUMN: + case TK_AGG_COLUMN: + case TK_SELECT: + case TK_CAST: + case TK_SELECT_COLUMN: + case TK_VECTOR: { + int aff = sqlite3ExprAffinity(pExpr); + if( aff>=SQLITE_AFF_NUMERIC ) return 0x05; + if( aff==SQLITE_AFF_TEXT ) return 0x06; + return 0x07; + } + case TK_CASE: { + int res = 0; + int ii; + ExprList *pList = pExpr->x.pList; + assert( ExprUseXList(pExpr) && pList!=0 ); + assert( pList->nExpr > 0); + for(ii=1; iinExpr; ii+=2){ + res |= sqlite3ExprDataType(pList->a[ii].pExpr); + } + if( pList->nExpr % 2 ){ + res |= sqlite3ExprDataType(pList->a[pList->nExpr-1].pExpr); + } + return res; + } + default: { + return 0x01; + } + } /* End of switch(op) */ + } /* End of while(pExpr) */ + return 0x00; } /* @@ -96968,7 +113005,7 @@ SQLITE_PRIVATE char sqlite3ExprAffinity(Expr *pExpr){ ** and the pExpr parameter is returned unchanged. */ SQLITE_PRIVATE Expr *sqlite3ExprAddCollateToken( - Parse *pParse, /* Parsing context */ + const Parse *pParse, /* Parsing context */ Expr *pExpr, /* Add the "COLLATE" clause to this expression */ const Token *pCollName, /* Name of collating sequence */ int dequote /* True to dequote pCollName */ @@ -96983,7 +113020,11 @@ SQLITE_PRIVATE Expr *sqlite3ExprAddCollateToken( } return pExpr; } -SQLITE_PRIVATE Expr *sqlite3ExprAddCollateString(Parse *pParse, Expr *pExpr, const char *zC){ +SQLITE_PRIVATE Expr *sqlite3ExprAddCollateString( + const Parse *pParse, /* Parsing context */ + Expr *pExpr, /* Add the "COLLATE" clause to this expression */ + const char *zC /* The collating sequence name */ +){ Token s; assert( zC!=0 ); sqlite3TokenInit(&s, (char*)zC); @@ -96991,21 +113032,34 @@ SQLITE_PRIVATE Expr *sqlite3ExprAddCollateString(Parse *pParse, Expr *pExpr, con } /* -** Skip over any TK_COLLATE operators and any unlikely() -** or likelihood() function at the root of an expression. +** Skip over any TK_COLLATE operators. */ SQLITE_PRIVATE Expr *sqlite3ExprSkipCollate(Expr *pExpr){ while( pExpr && ExprHasProperty(pExpr, EP_Skip) ){ + assert( pExpr->op==TK_COLLATE ); + pExpr = pExpr->pLeft; + } + return pExpr; +} + +/* +** Skip over any TK_COLLATE operators and/or any unlikely() +** or likelihood() or likely() functions at the root of an +** expression. +*/ +SQLITE_PRIVATE Expr *sqlite3ExprSkipCollateAndLikely(Expr *pExpr){ + while( pExpr && ExprHasProperty(pExpr, EP_Skip|EP_Unlikely) ){ if( ExprHasProperty(pExpr, EP_Unlikely) ){ - assert( !ExprHasProperty(pExpr, EP_xIsSelect) ); + assert( ExprUseXList(pExpr) ); assert( pExpr->x.pList->nExpr>0 ); assert( pExpr->op==TK_FUNCTION ); pExpr = pExpr->x.pList->a[0].pExpr; - }else{ - assert( pExpr->op==TK_COLLATE ); + }else if( pExpr->op==TK_COLLATE ){ pExpr = pExpr->pLeft; + }else{ + break; } - } + } return pExpr; } @@ -97023,22 +113077,21 @@ SQLITE_PRIVATE Expr *sqlite3ExprSkipCollate(Expr *pExpr){ ** COLLATE operators take first precedence. Left operands take ** precedence over right operands. */ -SQLITE_PRIVATE CollSeq *sqlite3ExprCollSeq(Parse *pParse, Expr *pExpr){ +SQLITE_PRIVATE CollSeq *sqlite3ExprCollSeq(Parse *pParse, const Expr *pExpr){ sqlite3 *db = pParse->db; CollSeq *pColl = 0; - Expr *p = pExpr; + const Expr *p = pExpr; while( p ){ int op = p->op; - if( p->flags & EP_Generic ) break; if( op==TK_REGISTER ) op = p->op2; - if( (op==TK_AGG_COLUMN || op==TK_COLUMN || op==TK_TRIGGER) - && p->y.pTab!=0 + if( (op==TK_AGG_COLUMN && p->y.pTab!=0) + || op==TK_COLUMN || op==TK_TRIGGER ){ - /* op==TK_REGISTER && p->y.pTab!=0 happens when pExpr was originally - ** a TK_COLUMN but was previously evaluated and cached in a register */ - int j = p->iColumn; - if( j>=0 ){ - const char *zColl = p->y.pTab->aCol[j].zColl; + int j; + assert( ExprUseYTab(p) ); + assert( p->y.pTab!=0 ); + if( (j = p->iColumn)>=0 ){ + const char *zColl = sqlite3ColumnColl(&p->y.pTab->aCol[j]); pColl = sqlite3FindCollSeq(db, ENC(db), zColl, 0); } break; @@ -97047,7 +113100,15 @@ SQLITE_PRIVATE CollSeq *sqlite3ExprCollSeq(Parse *pParse, Expr *pExpr){ p = p->pLeft; continue; } + if( op==TK_VECTOR + || (op==TK_FUNCTION && p->affExpr==SQLITE_AFF_DEFER) + ){ + assert( ExprUseXList(p) ); + p = p->x.pList->a[0].pExpr; + continue; + } if( op==TK_COLLATE ){ + assert( !ExprHasProperty(p, EP_IntValue) ); pColl = sqlite3GetCollSeq(pParse, ENC(db), 0, p->u.zToken); break; } @@ -97057,13 +113118,10 @@ SQLITE_PRIVATE CollSeq *sqlite3ExprCollSeq(Parse *pParse, Expr *pExpr){ }else{ Expr *pNext = p->pRight; /* The Expr.x union is never used at the same time as Expr.pRight */ - assert( p->x.pList==0 || p->pRight==0 ); - /* p->flags holds EP_Collate and p->pLeft->flags does not. And - ** p->x.pSelect cannot. So if p->x.pLeft exists, it must hold at - ** least one EP_Collate. Thus the following two ALWAYS. */ - if( p->x.pList!=0 && ALWAYS(!ExprHasProperty(p, EP_xIsSelect)) ){ + assert( !ExprUseXList(p) || p->x.pList==0 || p->pRight==0 ); + if( ExprUseXList(p) && p->x.pList!=0 && !db->mallocFailed ){ int i; - for(i=0; ALWAYS(ix.pList->nExpr); i++){ + for(i=0; ix.pList->nExpr; i++){ if( ExprHasProperty(p->x.pList->a[i].pExpr, EP_Collate) ){ pNext = p->x.pList->a[i].pExpr; break; @@ -97076,7 +113134,7 @@ SQLITE_PRIVATE CollSeq *sqlite3ExprCollSeq(Parse *pParse, Expr *pExpr){ break; } } - if( sqlite3CheckCollSeq(pParse, pColl) ){ + if( sqlite3CheckCollSeq(pParse, pColl) ){ pColl = 0; } return pColl; @@ -97085,14 +113143,14 @@ SQLITE_PRIVATE CollSeq *sqlite3ExprCollSeq(Parse *pParse, Expr *pExpr){ /* ** Return the collation sequence for the expression pExpr. If ** there is no defined collating sequence, return a pointer to the -** defautl collation sequence. +** default collation sequence. ** ** See also: sqlite3ExprCollSeq() ** ** The sqlite3ExprCollSeq() routine works the same except that it ** returns NULL if there is no defined collation. */ -SQLITE_PRIVATE CollSeq *sqlite3ExprNNCollSeq(Parse *pParse, Expr *pExpr){ +SQLITE_PRIVATE CollSeq *sqlite3ExprNNCollSeq(Parse *pParse, const Expr *pExpr){ CollSeq *p = sqlite3ExprCollSeq(pParse, pExpr); if( p==0 ) p = pParse->db->pDfltColl; assert( p!=0 ); @@ -97102,7 +113160,7 @@ SQLITE_PRIVATE CollSeq *sqlite3ExprNNCollSeq(Parse *pParse, Expr *pExpr){ /* ** Return TRUE if the two expressions have equivalent collating sequences. */ -SQLITE_PRIVATE int sqlite3ExprCollSeqMatch(Parse *pParse, Expr *pE1, Expr *pE2){ +SQLITE_PRIVATE int sqlite3ExprCollSeqMatch(Parse *pParse, const Expr *pE1, const Expr *pE2){ CollSeq *pColl1 = sqlite3ExprNNCollSeq(pParse, pE1); CollSeq *pColl2 = sqlite3ExprNNCollSeq(pParse, pE2); return sqlite3StrICmp(pColl1->zName, pColl2->zName)==0; @@ -97113,9 +113171,9 @@ SQLITE_PRIVATE int sqlite3ExprCollSeqMatch(Parse *pParse, Expr *pE1, Expr *pE2){ ** type affinity of the other operand. This routine returns the ** type affinity that should be used for the comparison operator. */ -SQLITE_PRIVATE char sqlite3CompareAffinity(Expr *pExpr, char aff2){ +SQLITE_PRIVATE char sqlite3CompareAffinity(const Expr *pExpr, char aff2){ char aff1 = sqlite3ExprAffinity(pExpr); - if( aff1 && aff2 ){ + if( aff1>SQLITE_AFF_NONE && aff2>SQLITE_AFF_NONE ){ /* Both sides of the comparison are columns. If one has numeric ** affinity, use that. Otherwise use no affinity. */ @@ -97124,15 +113182,10 @@ SQLITE_PRIVATE char sqlite3CompareAffinity(Expr *pExpr, char aff2){ }else{ return SQLITE_AFF_BLOB; } - }else if( !aff1 && !aff2 ){ - /* Neither side of the comparison is a column. Compare the - ** results directly. - */ - return SQLITE_AFF_BLOB; }else{ /* One side is a column, the other is not. Use the columns affinity. */ - assert( aff1==0 || aff2==0 ); - return (aff1 + aff2); + assert( aff1<=SQLITE_AFF_NONE || aff2<=SQLITE_AFF_NONE ); + return (aff1<=SQLITE_AFF_NONE ? aff2 : aff1) | SQLITE_AFF_NONE; } } @@ -97140,7 +113193,7 @@ SQLITE_PRIVATE char sqlite3CompareAffinity(Expr *pExpr, char aff2){ ** pExpr is a comparison operator. Return the type affinity that should ** be applied to both operands prior to doing the comparison. */ -static char comparisonAffinity(Expr *pExpr){ +static char comparisonAffinity(const Expr *pExpr){ char aff; assert( pExpr->op==TK_EQ || pExpr->op==TK_IN || pExpr->op==TK_LT || pExpr->op==TK_GT || pExpr->op==TK_GE || pExpr->op==TK_LE || @@ -97149,7 +113202,7 @@ static char comparisonAffinity(Expr *pExpr){ aff = sqlite3ExprAffinity(pExpr->pLeft); if( pExpr->pRight ){ aff = sqlite3CompareAffinity(pExpr->pRight, aff); - }else if( ExprHasProperty(pExpr, EP_xIsSelect) ){ + }else if( ExprUseXSelect(pExpr) ){ aff = sqlite3CompareAffinity(pExpr->x.pSelect->pEList->a[0].pExpr, aff); }else if( aff==0 ){ aff = SQLITE_AFF_BLOB; @@ -97163,23 +113216,26 @@ static char comparisonAffinity(Expr *pExpr){ ** if the index with affinity idx_affinity may be used to implement ** the comparison in pExpr. */ -SQLITE_PRIVATE int sqlite3IndexAffinityOk(Expr *pExpr, char idx_affinity){ +SQLITE_PRIVATE int sqlite3IndexAffinityOk(const Expr *pExpr, char idx_affinity){ char aff = comparisonAffinity(pExpr); - switch( aff ){ - case SQLITE_AFF_BLOB: - return 1; - case SQLITE_AFF_TEXT: - return idx_affinity==SQLITE_AFF_TEXT; - default: - return sqlite3IsNumericAffinity(idx_affinity); + if( affpRight, p->pLeft); + }else{ + return sqlite3BinaryCompareCollSeq(pParse, p->pLeft, p->pRight); + } +} + /* ** Generate code for a comparison operator. */ @@ -97227,17 +113299,23 @@ static int codeCompare( int opcode, /* The comparison opcode */ int in1, int in2, /* Register holding operands */ int dest, /* Jump here if true. */ - int jumpIfNull /* If true, jump if either operand is NULL */ + int jumpIfNull, /* If true, jump if either operand is NULL */ + int isCommuted /* The comparison has been commuted */ ){ int p5; int addr; CollSeq *p4; - p4 = sqlite3BinaryCompareCollSeq(pParse, pLeft, pRight); + if( pParse->nErr ) return 0; + if( isCommuted ){ + p4 = sqlite3BinaryCompareCollSeq(pParse, pRight, pLeft); + }else{ + p4 = sqlite3BinaryCompareCollSeq(pParse, pLeft, pRight); + } p5 = binaryCompareP5(pLeft, pRight, jumpIfNull); addr = sqlite3VdbeAddOp4(pParse->pVdbe, opcode, in2, dest, in1, (void*)p4, P4_COLLSEQ); - sqlite3VdbeChangeP5(pParse->pVdbe, (u8)p5); + sqlite3VdbeChangeP5(pParse->pVdbe, (u16)p5); return addr; } @@ -97250,22 +113328,24 @@ static int codeCompare( ** But a TK_SELECT might be either a vector or a scalar. It is only ** considered a vector if it has two or more result columns. */ -SQLITE_PRIVATE int sqlite3ExprIsVector(Expr *pExpr){ +SQLITE_PRIVATE int sqlite3ExprIsVector(const Expr *pExpr){ return sqlite3ExprVectorSize(pExpr)>1; } /* -** If the expression passed as the only argument is of type TK_VECTOR +** If the expression passed as the only argument is of type TK_VECTOR ** return the number of expressions in the vector. Or, if the expression ** is a sub-select, return the number of columns in the sub-select. For ** any other type of expression, return 1. */ -SQLITE_PRIVATE int sqlite3ExprVectorSize(Expr *pExpr){ +SQLITE_PRIVATE int sqlite3ExprVectorSize(const Expr *pExpr){ u8 op = pExpr->op; if( op==TK_REGISTER ) op = pExpr->op2; if( op==TK_VECTOR ){ + assert( ExprUseXList(pExpr) ); return pExpr->x.pList->nExpr; }else if( op==TK_SELECT ){ + assert( ExprUseXSelect(pExpr) ); return pExpr->x.pSelect->pEList->nExpr; }else{ return 1; @@ -97288,12 +113368,14 @@ SQLITE_PRIVATE int sqlite3ExprVectorSize(Expr *pExpr){ ** been positioned. */ SQLITE_PRIVATE Expr *sqlite3VectorFieldSubexpr(Expr *pVector, int i){ - assert( iop==TK_ERROR ); if( sqlite3ExprIsVector(pVector) ){ assert( pVector->op2==0 || pVector->op==TK_REGISTER ); if( pVector->op==TK_SELECT || pVector->op2==TK_SELECT ){ + assert( ExprUseXSelect(pVector) ); return pVector->x.pSelect->pEList->a[i].pExpr; }else{ + assert( ExprUseXList(pVector) ); return pVector->x.pList->a[i].pExpr; } } @@ -97305,7 +113387,7 @@ SQLITE_PRIVATE Expr *sqlite3VectorFieldSubexpr(Expr *pVector, int i){ ** sqlite3ExprCode() will generate all necessary code to compute ** the iField-th column of the vector expression pVector. ** -** It is ok for pVector to be a scalar (as long as iField==0). +** It is ok for pVector to be a scalar (as long as iField==0). ** In that case, this routine works like sqlite3ExprDup(). ** ** The caller owns the returned Expr object and is responsible for @@ -97324,11 +113406,12 @@ SQLITE_PRIVATE Expr *sqlite3VectorFieldSubexpr(Expr *pVector, int i){ SQLITE_PRIVATE Expr *sqlite3ExprForVectorField( Parse *pParse, /* Parsing context */ Expr *pVector, /* The vector. List of expressions or a sub-SELECT */ - int iField /* Which column of the vector to return */ + int iField, /* Which column of the vector to return */ + int nField /* Total number of columns in the vector */ ){ Expr *pRet; if( pVector->op==TK_SELECT ){ - assert( pVector->flags & EP_xIsSelect ); + assert( ExprUseXSelect(pVector) ); /* The TK_SELECT_COLUMN Expr node: ** ** pLeft: pVector containing TK_SELECT. Not deleted. @@ -97347,21 +113430,31 @@ SQLITE_PRIVATE Expr *sqlite3ExprForVectorField( */ pRet = sqlite3PExpr(pParse, TK_SELECT_COLUMN, 0, 0); if( pRet ){ + ExprSetProperty(pRet, EP_FullSize); + pRet->iTable = nField; pRet->iColumn = iField; pRet->pLeft = pVector; } - assert( pRet==0 || pRet->iTable==0 ); }else{ - if( pVector->op==TK_VECTOR ) pVector = pVector->x.pList->a[iField].pExpr; + if( pVector->op==TK_VECTOR ){ + Expr **ppVector; + assert( ExprUseXList(pVector) ); + ppVector = &pVector->x.pList->a[iField].pExpr; + pVector = *ppVector; + if( IN_RENAME_OBJECT ){ + /* This must be a vector UPDATE inside a trigger */ + *ppVector = 0; + return pVector; + } + } pRet = sqlite3ExprDup(pParse->db, pVector, 0); - sqlite3RenameTokenRemap(pParse, pRet, pVector); } return pRet; } /* ** If expression pExpr is of type TK_SELECT, generate code to evaluate -** it. Return the register in which the result is stored (or, if the +** it. Return the register in which the result is stored (or, if the ** sub-select returns more than one column, the first in an array ** of registers in which the result is stored). ** @@ -97383,10 +113476,10 @@ static int exprCodeSubselect(Parse *pParse, Expr *pExpr){ ** the register number of a register that contains the value of ** element iField of the vector. ** -** If pVector is a TK_SELECT expression, then code for it must have +** If pVector is a TK_SELECT expression, then code for it must have ** already been generated using the exprCodeSubselect() routine. In this ** case parameter regSelect should be the first in an array of registers -** containing the results of the sub-select. +** containing the results of the sub-select. ** ** If pVector is of type TK_VECTOR, then code for the requested field ** is generated. In this case (*pRegFree) may be set to the number of @@ -97404,17 +113497,22 @@ static int exprVectorRegister( int *pRegFree /* OUT: Temp register to free */ ){ u8 op = pVector->op; - assert( op==TK_VECTOR || op==TK_REGISTER || op==TK_SELECT ); + assert( op==TK_VECTOR || op==TK_REGISTER || op==TK_SELECT || op==TK_ERROR ); if( op==TK_REGISTER ){ *ppExpr = sqlite3VectorFieldSubexpr(pVector, iField); return pVector->iTable+iField; } if( op==TK_SELECT ){ + assert( ExprUseXSelect(pVector) ); *ppExpr = pVector->x.pSelect->pEList->a[iField].pExpr; return regSelect+iField; } - *ppExpr = pVector->x.pList->a[iField].pExpr; - return sqlite3ExprCodeTemp(pParse, *ppExpr, pRegFree); + if( op==TK_VECTOR ){ + assert( ExprUseXList(pVector) ); + *ppExpr = pVector->x.pList->a[iField].pExpr; + return sqlite3ExprCodeTemp(pParse, *ppExpr, pRegFree); + } + return 0; } /* @@ -97443,37 +113541,44 @@ static void codeVectorCompare( int regLeft = 0; int regRight = 0; u8 opx = op; + int addrCmp = 0; int addrDone = sqlite3VdbeMakeLabel(pParse); + int isCommuted = ExprHasProperty(pExpr,EP_Commuted); + assert( !ExprHasVVAProperty(pExpr,EP_Immutable) ); + if( pParse->nErr ) return; if( nLeft!=sqlite3ExprVectorSize(pRight) ){ sqlite3ErrorMsg(pParse, "row value misused"); return; } - assert( pExpr->op==TK_EQ || pExpr->op==TK_NE - || pExpr->op==TK_IS || pExpr->op==TK_ISNOT - || pExpr->op==TK_LT || pExpr->op==TK_GT - || pExpr->op==TK_LE || pExpr->op==TK_GE + assert( pExpr->op==TK_EQ || pExpr->op==TK_NE + || pExpr->op==TK_IS || pExpr->op==TK_ISNOT + || pExpr->op==TK_LT || pExpr->op==TK_GT + || pExpr->op==TK_LE || pExpr->op==TK_GE ); assert( pExpr->op==op || (pExpr->op==TK_IS && op==TK_EQ) || (pExpr->op==TK_ISNOT && op==TK_NE) ); assert( p5==0 || pExpr->op!=op ); assert( p5==SQLITE_NULLEQ || pExpr->op==op ); - p5 |= SQLITE_STOREP2; - if( opx==TK_LE ) opx = TK_LT; - if( opx==TK_GE ) opx = TK_GT; + if( op==TK_LE ) opx = TK_LT; + if( op==TK_GE ) opx = TK_GT; + if( op==TK_NE ) opx = TK_EQ; regLeft = exprCodeSubselect(pParse, pLeft); regRight = exprCodeSubselect(pParse, pRight); + sqlite3VdbeAddOp2(v, OP_Integer, 1, dest); for(i=0; 1 /*Loop exits by "break"*/; i++){ int regFree1 = 0, regFree2 = 0; - Expr *pL, *pR; + Expr *pL = 0, *pR = 0; int r1, r2; assert( i>=0 && i0 @@ -97514,7 +113625,7 @@ SQLITE_PRIVATE int sqlite3ExprCheckHeight(Parse *pParse, int nHeight){ int rc = SQLITE_OK; int mxHeight = pParse->db->aLimit[SQLITE_LIMIT_EXPR_DEPTH]; if( nHeight>mxHeight ){ - sqlite3ErrorMsg(pParse, + sqlite3ErrorMsg(pParse, "Expression tree is too large (maximum depth %d)", mxHeight ); rc = SQLITE_ERROR; @@ -97531,14 +113642,14 @@ SQLITE_PRIVATE int sqlite3ExprCheckHeight(Parse *pParse, int nHeight){ ** to by pnHeight, the second parameter, then set *pnHeight to that ** value. */ -static void heightOfExpr(Expr *p, int *pnHeight){ +static void heightOfExpr(const Expr *p, int *pnHeight){ if( p ){ if( p->nHeight>*pnHeight ){ *pnHeight = p->nHeight; } } } -static void heightOfExprList(ExprList *p, int *pnHeight){ +static void heightOfExprList(const ExprList *p, int *pnHeight){ if( p ){ int i; for(i=0; inExpr; i++){ @@ -97546,8 +113657,8 @@ static void heightOfExprList(ExprList *p, int *pnHeight){ } } } -static void heightOfSelect(Select *pSelect, int *pnHeight){ - Select *p; +static void heightOfSelect(const Select *pSelect, int *pnHeight){ + const Select *p; for(p=pSelect; p; p=p->pPrior){ heightOfExpr(p->pWhere, pnHeight); heightOfExpr(p->pHaving, pnHeight); @@ -97559,20 +113670,21 @@ static void heightOfSelect(Select *pSelect, int *pnHeight){ } /* -** Set the Expr.nHeight variable in the structure passed as an -** argument. An expression with no children, Expr.pList or +** Set the Expr.nHeight variable in the structure passed as an +** argument. An expression with no children, Expr.pList or ** Expr.pSelect member has a height of 1. Any other expression -** has a height equal to the maximum height of any other +** has a height equal to the maximum height of any other ** referenced Expr plus one. ** ** Also propagate EP_Propagate flags up from Expr.x.pList to Expr.flags, ** if appropriate. */ static void exprSetHeight(Expr *p){ - int nHeight = 0; - heightOfExpr(p->pLeft, &nHeight); - heightOfExpr(p->pRight, &nHeight); - if( ExprHasProperty(p, EP_xIsSelect) ){ + int nHeight = p->pLeft ? p->pLeft->nHeight : 0; + if( NEVER(p->pRight) && p->pRight->nHeight>nHeight ){ + nHeight = p->pRight->nHeight; + } + if( ExprUseXSelect(p) ){ heightOfSelect(p->x.pSelect, &nHeight); }else if( p->x.pList ){ heightOfExprList(p->x.pList, &nHeight); @@ -97587,7 +113699,7 @@ static void exprSetHeight(Expr *p){ ** leave an error in pParse. ** ** Also propagate all EP_Propagate flags from the Expr.x.pList into -** Expr.flags. +** Expr.flags. */ SQLITE_PRIVATE void sqlite3ExprSetHeightAndFlags(Parse *pParse, Expr *p){ if( pParse->nErr ) return; @@ -97599,7 +113711,7 @@ SQLITE_PRIVATE void sqlite3ExprSetHeightAndFlags(Parse *pParse, Expr *p){ ** Return the maximum height of any expression tree referenced ** by the select statement passed as an argument. */ -SQLITE_PRIVATE int sqlite3SelectExprHeight(Select *p){ +SQLITE_PRIVATE int sqlite3SelectExprHeight(const Select *p){ int nHeight = 0; heightOfSelect(p, &nHeight); return nHeight; @@ -97607,16 +113719,26 @@ SQLITE_PRIVATE int sqlite3SelectExprHeight(Select *p){ #else /* ABOVE: Height enforcement enabled. BELOW: Height enforcement off */ /* ** Propagate all EP_Propagate flags from the Expr.x.pList into -** Expr.flags. +** Expr.flags. */ SQLITE_PRIVATE void sqlite3ExprSetHeightAndFlags(Parse *pParse, Expr *p){ - if( p && p->x.pList && !ExprHasProperty(p, EP_xIsSelect) ){ + if( pParse->nErr ) return; + if( p && ExprUseXList(p) && p->x.pList ){ p->flags |= EP_Propagate & sqlite3ExprListFlags(p->x.pList); } } #define exprSetHeight(y) #endif /* SQLITE_MAX_EXPR_DEPTH>0 */ +/* +** Set the error offset for an Expr node, if possible. +*/ +SQLITE_PRIVATE void sqlite3ExprSetErrorOffset(Expr *pExpr, int iOfst){ + if( pExpr==0 ) return; + if( NEVER(ExprUseWJoin(pExpr)) ) return; + pExpr->w.iOfst = iOfst; +} + /* ** This routine is the core allocator for Expr nodes. ** @@ -97631,11 +113753,12 @@ SQLITE_PRIVATE void sqlite3ExprSetHeightAndFlags(Parse *pParse, Expr *p){ ** appear to be quoted. If the quotes were of the form "..." (double-quotes) ** then the EP_DblQuoted flag is set on the expression node. ** -** Special case: If op==TK_INTEGER and pToken points to a string that -** can be translated into a 32-bit integer, then the token is not -** stored in u.zToken. Instead, the integer values is written -** into u.iValue and the EP_IntValue flag is set. No extra storage +** Special case (tag-20240227-a): If op==TK_INTEGER and pToken points to +** a string that can be translated into a 32-bit integer, then the token is +** not stored in u.zToken. Instead, the integer values is written +** into u.iValue and the EP_IntValue flag is set. No extra storage ** is allocated to hold the integer text and the dequote flag is ignored. +** See also tag-20240227-b. */ SQLITE_PRIVATE Expr *sqlite3ExprAlloc( sqlite3 *db, /* Handle for sqlite3DbMallocRawNN() */ @@ -97644,39 +113767,27 @@ SQLITE_PRIVATE Expr *sqlite3ExprAlloc( int dequote /* True to dequote */ ){ Expr *pNew; - int nExtra = 0; - int iValue = 0; + int nExtra = pToken ? pToken->n+1 : 0; assert( db!=0 ); - if( pToken ){ - if( op!=TK_INTEGER || pToken->z==0 - || sqlite3GetInt32(pToken->z, &iValue)==0 ){ - nExtra = pToken->n+1; - assert( iValue>=0 ); - } - } pNew = sqlite3DbMallocRawNN(db, sizeof(Expr)+nExtra); if( pNew ){ memset(pNew, 0, sizeof(Expr)); pNew->op = (u8)op; pNew->iAgg = -1; - if( pToken ){ - if( nExtra==0 ){ - pNew->flags |= EP_IntValue|EP_Leaf; - pNew->u.iValue = iValue; - }else{ - pNew->u.zToken = (char*)&pNew[1]; - assert( pToken->z!=0 || pToken->n==0 ); - if( pToken->n ) memcpy(pNew->u.zToken, pToken->z, pToken->n); - pNew->u.zToken[pToken->n] = 0; - if( dequote && sqlite3Isquote(pNew->u.zToken[0]) ){ - sqlite3DequoteExpr(pNew); - } + if( nExtra ){ + assert( pToken!=0 ); + pNew->u.zToken = (char*)&pNew[1]; + assert( pToken->z!=0 || pToken->n==0 ); + if( pToken->n ) memcpy(pNew->u.zToken, pToken->z, pToken->n); + pNew->u.zToken[pToken->n] = 0; + if( dequote && sqlite3Isquote(pNew->u.zToken[0]) ){ + sqlite3DequoteExpr(pNew); } } #if SQLITE_MAX_EXPR_DEPTH>0 pNew->nHeight = 1; -#endif +#endif } return pNew; } @@ -97696,6 +113807,24 @@ SQLITE_PRIVATE Expr *sqlite3Expr( return sqlite3ExprAlloc(db, op, &x, 0); } +/* +** Allocate an expression for a 32-bit signed integer literal. +*/ +SQLITE_PRIVATE Expr *sqlite3ExprInt32(sqlite3 *db, int iVal){ + Expr *pNew = sqlite3DbMallocRawNN(db, sizeof(Expr)); + if( pNew ){ + memset(pNew, 0, sizeof(Expr)); + pNew->op = TK_INTEGER; + pNew->iAgg = -1; + pNew->flags = EP_IntValue|EP_Leaf|(iVal?EP_IsTrue:EP_IsFalse); + pNew->u.iValue = iVal; +#if SQLITE_MAX_EXPR_DEPTH>0 + pNew->nHeight = 1; +#endif + } + return pNew; +} + /* ** Attach subtrees pLeft and pRight to the Expr node pRoot. ** @@ -97713,15 +113842,26 @@ SQLITE_PRIVATE void sqlite3ExprAttachSubtrees( sqlite3ExprDelete(db, pLeft); sqlite3ExprDelete(db, pRight); }else{ + assert( ExprUseXList(pRoot) ); + assert( pRoot->x.pSelect==0 ); if( pRight ){ pRoot->pRight = pRight; pRoot->flags |= EP_Propagate & pRight->flags; +#if SQLITE_MAX_EXPR_DEPTH>0 + pRoot->nHeight = pRight->nHeight+1; + }else{ + pRoot->nHeight = 1; +#endif } if( pLeft ){ pRoot->pLeft = pLeft; pRoot->flags |= EP_Propagate & pLeft->flags; +#if SQLITE_MAX_EXPR_DEPTH>0 + if( pLeft->nHeight>=pRoot->nHeight ){ + pRoot->nHeight = pLeft->nHeight+1; + } +#endif } - exprSetHeight(pRoot); } } @@ -97739,20 +113879,16 @@ SQLITE_PRIVATE Expr *sqlite3PExpr( Expr *pRight /* Right operand */ ){ Expr *p; - if( op==TK_AND && pParse->nErr==0 && !IN_RENAME_OBJECT ){ - /* Take advantage of short-circuit false optimization for AND */ - p = sqlite3ExprAnd(pParse->db, pLeft, pRight); - }else{ - p = sqlite3DbMallocRawNN(pParse->db, sizeof(Expr)); - if( p ){ - memset(p, 0, sizeof(Expr)); - p->op = op & 0xff; - p->iAgg = -1; - } + p = sqlite3DbMallocRawNN(pParse->db, sizeof(Expr)); + if( p ){ + memset(p, 0, sizeof(Expr)); + p->op = op & 0xff; + p->iAgg = -1; sqlite3ExprAttachSubtrees(pParse->db, p, pLeft, pRight); - } - if( p ) { sqlite3ExprCheckHeight(pParse, p->nHeight); + }else{ + sqlite3ExprDelete(pParse->db, pLeft); + sqlite3ExprDelete(pParse->db, pRight); } return p; } @@ -97772,55 +113908,89 @@ SQLITE_PRIVATE void sqlite3PExprAddSelect(Parse *pParse, Expr *pExpr, Select *pS } } - /* -** If the expression is always either TRUE or FALSE (respectively), -** then return 1. If one cannot determine the truth value of the -** expression at compile-time return 0. +** Expression list pEList is a list of vector values. This function +** converts the contents of pEList to a VALUES(...) Select statement +** returning 1 row for each element of the list. For example, the +** expression list: +** +** ( (1,2), (3,4) (5,6) ) +** +** is translated to the equivalent of: ** -** This is an optimization. If is OK to return 0 here even if -** the expression really is always false or false (a false negative). -** But it is a bug to return 1 if the expression might have different -** boolean values in different circumstances (a false positive.) +** VALUES(1,2), (3,4), (5,6) ** -** Note that if the expression is part of conditional for a -** LEFT JOIN, then we cannot determine at compile-time whether or not -** is it true or false, so always return 0. +** Each of the vector values in pEList must contain exactly nElem terms. +** If a list element that is not a vector or does not contain nElem terms, +** an error message is left in pParse. +** +** This is used as part of processing IN(...) expressions with a list +** of vectors on the RHS. e.g. "... IN ((1,2), (3,4), (5,6))". */ -static int exprAlwaysTrue(Expr *p){ - int v = 0; - if( ExprHasProperty(p, EP_FromJoin) ) return 0; - if( !sqlite3ExprIsInteger(p, &v) ) return 0; - return v!=0; -} -static int exprAlwaysFalse(Expr *p){ - int v = 0; - if( ExprHasProperty(p, EP_FromJoin) ) return 0; - if( !sqlite3ExprIsInteger(p, &v) ) return 0; - return v==0; +SQLITE_PRIVATE Select *sqlite3ExprListToValues(Parse *pParse, int nElem, ExprList *pEList){ + int ii; + Select *pRet = 0; + assert( nElem>1 ); + for(ii=0; iinExpr; ii++){ + Select *pSel; + Expr *pExpr = pEList->a[ii].pExpr; + int nExprElem; + if( pExpr->op==TK_VECTOR ){ + assert( ExprUseXList(pExpr) ); + nExprElem = pExpr->x.pList->nExpr; + }else{ + nExprElem = 1; + } + if( nExprElem!=nElem ){ + sqlite3ErrorMsg(pParse, "IN(...) element has %d term%s - expected %d", + nExprElem, nExprElem>1?"s":"", nElem + ); + break; + } + assert( ExprUseXList(pExpr) ); + pSel = sqlite3SelectNew(pParse, pExpr->x.pList, 0, 0, 0, 0, 0, SF_Values,0); + pExpr->x.pList = 0; + if( pSel ){ + if( pRet ){ + pSel->op = TK_ALL; + pSel->pPrior = pRet; + } + pRet = pSel; + } + } + + if( pRet && pRet->pPrior ){ + pRet->selFlags |= SF_MultiValue; + } + sqlite3ExprListDelete(pParse->db, pEList); + return pRet; } /* ** Join two expressions using an AND operator. If either expression is ** NULL, then just return the other expression. ** -** If one side or the other of the AND is known to be false, then instead -** of returning an AND expression, just return a constant expression with -** a value of false. +** If one side or the other of the AND is known to be false, and neither side +** is part of an ON clause, then instead of returning an AND expression, +** just return a constant expression with a value of false. */ -SQLITE_PRIVATE Expr *sqlite3ExprAnd(sqlite3 *db, Expr *pLeft, Expr *pRight){ - if( pLeft==0 ){ +SQLITE_PRIVATE Expr *sqlite3ExprAnd(Parse *pParse, Expr *pLeft, Expr *pRight){ + sqlite3 *db = pParse->db; + if( pLeft==0 ){ return pRight; }else if( pRight==0 ){ return pLeft; - }else if( exprAlwaysFalse(pLeft) || exprAlwaysFalse(pRight) ){ - sqlite3ExprDelete(db, pLeft); - sqlite3ExprDelete(db, pRight); - return sqlite3ExprAlloc(db, TK_INTEGER, &sqlite3IntTokens[0], 0); }else{ - Expr *pNew = sqlite3ExprAlloc(db, TK_AND, 0, 0); - sqlite3ExprAttachSubtrees(db, pNew, pLeft, pRight); - return pNew; + u32 f = pLeft->flags | pRight->flags; + if( (f&(EP_OuterON|EP_InnerON|EP_IsFalse|EP_HasFunc))==EP_IsFalse + && !IN_RENAME_OBJECT + ){ + sqlite3ExprDeferredDelete(pParse, pLeft); + sqlite3ExprDeferredDelete(pParse, pRight); + return sqlite3ExprInt32(db, 0); + }else{ + return sqlite3PExpr(pParse, TK_AND, pLeft, pRight); + } } } @@ -97831,7 +114001,7 @@ SQLITE_PRIVATE Expr *sqlite3ExprAnd(sqlite3 *db, Expr *pLeft, Expr *pRight){ SQLITE_PRIVATE Expr *sqlite3ExprFunction( Parse *pParse, /* Parsing context */ ExprList *pList, /* Argument list */ - Token *pToken, /* Name of the function */ + const Token *pToken, /* Name of the function */ int eDistinct /* SF_Distinct or SF_ALL or 0 */ ){ Expr *pNew; @@ -97842,20 +114012,127 @@ SQLITE_PRIVATE Expr *sqlite3ExprFunction( sqlite3ExprListDelete(db, pList); /* Avoid memory leak when malloc fails */ return 0; } - if( pList && pList->nExpr > pParse->db->aLimit[SQLITE_LIMIT_FUNCTION_ARG] ){ + assert( !ExprHasProperty(pNew, EP_InnerON|EP_OuterON) ); + pNew->w.iOfst = (int)(pToken->z - pParse->zTail); + if( pList + && pList->nExpr > pParse->db->aLimit[SQLITE_LIMIT_FUNCTION_ARG] + && !pParse->nested + ){ sqlite3ErrorMsg(pParse, "too many arguments on function %T", pToken); } pNew->x.pList = pList; ExprSetProperty(pNew, EP_HasFunc); - assert( !ExprHasProperty(pNew, EP_xIsSelect) ); + assert( ExprUseXList(pNew) ); sqlite3ExprSetHeightAndFlags(pParse, pNew); if( eDistinct==SF_Distinct ) ExprSetProperty(pNew, EP_Distinct); return pNew; } +/* +** Report an error when attempting to use an ORDER BY clause within +** the arguments of a non-aggregate function. +*/ +SQLITE_PRIVATE void sqlite3ExprOrderByAggregateError(Parse *pParse, Expr *p){ + sqlite3ErrorMsg(pParse, + "ORDER BY may not be used with non-aggregate %#T()", p + ); +} + +/* +** Attach an ORDER BY clause to a function call. +** +** functionname( arguments ORDER BY sortlist ) +** \_____________________/ \______/ +** pExpr pOrderBy +** +** The ORDER BY clause is inserted into a new Expr node of type TK_ORDER +** and added to the Expr.pLeft field of the parent TK_FUNCTION node. +*/ +SQLITE_PRIVATE void sqlite3ExprAddFunctionOrderBy( + Parse *pParse, /* Parsing context */ + Expr *pExpr, /* The function call to which ORDER BY is to be added */ + ExprList *pOrderBy /* The ORDER BY clause to add */ +){ + Expr *pOB; + sqlite3 *db = pParse->db; + if( NEVER(pOrderBy==0) ){ + assert( db->mallocFailed ); + return; + } + if( pExpr==0 ){ + assert( db->mallocFailed ); + sqlite3ExprListDelete(db, pOrderBy); + return; + } + assert( pExpr->op==TK_FUNCTION ); + assert( pExpr->pLeft==0 ); + assert( ExprUseXList(pExpr) ); + if( pExpr->x.pList==0 || NEVER(pExpr->x.pList->nExpr==0) ){ + /* Ignore ORDER BY on zero-argument aggregates */ + sqlite3ParserAddCleanup(pParse, sqlite3ExprListDeleteGeneric, pOrderBy); + return; + } + if( IsWindowFunc(pExpr) ){ + sqlite3ExprOrderByAggregateError(pParse, pExpr); + sqlite3ExprListDelete(db, pOrderBy); + return; + } + if( pOrderBy->nExpr>db->aLimit[SQLITE_LIMIT_COLUMN] ){ + sqlite3ErrorMsg(pParse, "too many terms in ORDER BY clause"); + sqlite3ExprListDelete(db, pOrderBy); + return; + } + + pOB = sqlite3ExprAlloc(db, TK_ORDER, 0, 0); + if( pOB==0 ){ + sqlite3ExprListDelete(db, pOrderBy); + return; + } + pOB->x.pList = pOrderBy; + assert( ExprUseXList(pOB) ); + pExpr->pLeft = pOB; + ExprSetProperty(pOB, EP_FullSize); +} + +/* +** Check to see if a function is usable according to current access +** rules: +** +** SQLITE_FUNC_DIRECT - Only usable from top-level SQL +** +** SQLITE_FUNC_UNSAFE - Usable if TRUSTED_SCHEMA or from +** top-level SQL +** +** If the function is not usable, create an error. +*/ +SQLITE_PRIVATE void sqlite3ExprFunctionUsable( + Parse *pParse, /* Parsing and code generating context */ + const Expr *pExpr, /* The function invocation */ + const FuncDef *pDef /* The function being invoked */ +){ + assert( !IN_RENAME_OBJECT ); + assert( (pDef->funcFlags & (SQLITE_FUNC_DIRECT|SQLITE_FUNC_UNSAFE))!=0 ); + if( ExprHasProperty(pExpr, EP_FromDDL) + || pParse->prepFlags & SQLITE_PREPARE_FROM_DDL + ){ + if( (pDef->funcFlags & SQLITE_FUNC_DIRECT)!=0 + || (pParse->db->flags & SQLITE_TrustedSchema)==0 + ){ + /* Functions prohibited in triggers and views if: + ** (1) tagged with SQLITE_DIRECTONLY + ** (2) not tagged with SQLITE_INNOCUOUS (which means it + ** is tagged with SQLITE_FUNC_UNSAFE) and + ** SQLITE_DBCONFIG_TRUSTED_SCHEMA is off (meaning + ** that the schema is possibly tainted). + */ + sqlite3ErrorMsg(pParse, "unsafe use of %#T()", pExpr); + } + } +} + /* ** Assign a variable number to an expression that encodes a wildcard -** in the original SQL statement. +** in the original SQL statement. ** ** Wildcards consisting of a single "?" are assigned the next sequential ** variable number. @@ -97904,6 +114181,7 @@ SQLITE_PRIVATE void sqlite3ExprAssignVarNumber(Parse *pParse, Expr *pExpr, u32 n if( bOk==0 || i<1 || i>db->aLimit[SQLITE_LIMIT_VARIABLE_NUMBER] ){ sqlite3ErrorMsg(pParse, "variable number must be between ?1 and ?%d", db->aLimit[SQLITE_LIMIT_VARIABLE_NUMBER]); + sqlite3RecordErrorOffsetOfExpr(pParse->db, pExpr); return; } x = (ynVar)i; @@ -97931,6 +114209,7 @@ SQLITE_PRIVATE void sqlite3ExprAssignVarNumber(Parse *pParse, Expr *pExpr, u32 n pExpr->iColumn = x; if( x>db->aLimit[SQLITE_LIMIT_VARIABLE_NUMBER] ){ sqlite3ErrorMsg(pParse, "too many SQL variables"); + sqlite3RecordErrorOffsetOfExpr(pParse->db, pExpr); } } @@ -97939,78 +114218,125 @@ SQLITE_PRIVATE void sqlite3ExprAssignVarNumber(Parse *pParse, Expr *pExpr, u32 n */ static SQLITE_NOINLINE void sqlite3ExprDeleteNN(sqlite3 *db, Expr *p){ assert( p!=0 ); - /* Sanity check: Assert that the IntValue is non-negative if it exists */ - assert( !ExprHasProperty(p, EP_IntValue) || p->u.iValue>=0 ); - - assert( !ExprHasProperty(p, EP_WinFunc) || p->y.pWin!=0 || db->mallocFailed ); - assert( p->op!=TK_FUNCTION || ExprHasProperty(p, EP_TokenOnly|EP_Reduced) - || p->y.pWin==0 || ExprHasProperty(p, EP_WinFunc) ); + assert( db!=0 ); +exprDeleteRestart: + assert( !ExprUseUValue(p) || p->u.iValue>=0 ); + assert( !ExprUseYWin(p) || !ExprUseYSub(p) ); + assert( !ExprUseYWin(p) || p->y.pWin!=0 || db->mallocFailed ); + assert( p->op!=TK_FUNCTION || !ExprUseYSub(p) ); #ifdef SQLITE_DEBUG if( ExprHasProperty(p, EP_Leaf) && !ExprHasProperty(p, EP_TokenOnly) ){ assert( p->pLeft==0 ); assert( p->pRight==0 ); - assert( p->x.pSelect==0 ); + assert( !ExprUseXSelect(p) || p->x.pSelect==0 ); + assert( !ExprUseXList(p) || p->x.pList==0 ); } #endif if( !ExprHasProperty(p, (EP_TokenOnly|EP_Leaf)) ){ /* The Expr.x union is never used at the same time as Expr.pRight */ - assert( p->x.pList==0 || p->pRight==0 ); - if( p->pLeft && p->op!=TK_SELECT_COLUMN ) sqlite3ExprDeleteNN(db, p->pLeft); + assert( (ExprUseXList(p) && p->x.pList==0) || p->pRight==0 ); if( p->pRight ){ + assert( !ExprHasProperty(p, EP_WinFunc) ); sqlite3ExprDeleteNN(db, p->pRight); - }else if( ExprHasProperty(p, EP_xIsSelect) ){ + }else if( ExprUseXSelect(p) ){ + assert( !ExprHasProperty(p, EP_WinFunc) ); sqlite3SelectDelete(db, p->x.pSelect); }else{ sqlite3ExprListDelete(db, p->x.pList); +#ifndef SQLITE_OMIT_WINDOWFUNC + if( ExprHasProperty(p, EP_WinFunc) ){ + sqlite3WindowDelete(db, p->y.pWin); + } +#endif } - if( ExprHasProperty(p, EP_WinFunc) ){ - assert( p->op==TK_FUNCTION ); - sqlite3WindowDelete(db, p->y.pWin); + if( p->pLeft && p->op!=TK_SELECT_COLUMN ){ + Expr *pLeft = p->pLeft; + if( !ExprHasProperty(p, EP_Static) + && !ExprHasProperty(pLeft, EP_Static) + ){ + /* Avoid unnecessary recursion on unary operators */ + sqlite3DbNNFreeNN(db, p); + p = pLeft; + goto exprDeleteRestart; + }else{ + sqlite3ExprDeleteNN(db, pLeft); + } } } - if( ExprHasProperty(p, EP_MemToken) ) sqlite3DbFree(db, p->u.zToken); if( !ExprHasProperty(p, EP_Static) ){ - sqlite3DbFreeNN(db, p); + sqlite3DbNNFreeNN(db, p); } } SQLITE_PRIVATE void sqlite3ExprDelete(sqlite3 *db, Expr *p){ if( p ) sqlite3ExprDeleteNN(db, p); } +SQLITE_PRIVATE void sqlite3ExprDeleteGeneric(sqlite3 *db, void *p){ + if( ALWAYS(p) ) sqlite3ExprDeleteNN(db, (Expr*)p); +} + +/* +** Clear both elements of an OnOrUsing object +*/ +SQLITE_PRIVATE void sqlite3ClearOnOrUsing(sqlite3 *db, OnOrUsing *p){ + if( p==0 ){ + /* Nothing to clear */ + }else if( p->pOn ){ + sqlite3ExprDeleteNN(db, p->pOn); + }else if( p->pUsing ){ + sqlite3IdListDelete(db, p->pUsing); + } +} + +/* +** Arrange to cause pExpr to be deleted when the pParse is deleted. +** This is similar to sqlite3ExprDelete() except that the delete is +** deferred until the pParse is deleted. +** +** The pExpr might be deleted immediately on an OOM error. +** +** Return 0 if the delete was successfully deferred. Return non-zero +** if the delete happened immediately because of an OOM. +*/ +SQLITE_PRIVATE int sqlite3ExprDeferredDelete(Parse *pParse, Expr *pExpr){ + return 0==sqlite3ParserAddCleanup(pParse, sqlite3ExprDeleteGeneric, pExpr); +} + +/* Invoke sqlite3RenameExprUnmap() and sqlite3ExprDelete() on the +** expression. +*/ +SQLITE_PRIVATE void sqlite3ExprUnmapAndDelete(Parse *pParse, Expr *p){ + if( p ){ + if( IN_RENAME_OBJECT ){ + sqlite3RenameExprUnmap(pParse, p); + } + sqlite3ExprDeleteNN(pParse->db, p); + } +} /* -** Return the number of bytes allocated for the expression structure +** Return the number of bytes allocated for the expression structure ** passed as the first argument. This is always one of EXPR_FULLSIZE, ** EXPR_REDUCEDSIZE or EXPR_TOKENONLYSIZE. */ -static int exprStructSize(Expr *p){ +static int exprStructSize(const Expr *p){ if( ExprHasProperty(p, EP_TokenOnly) ) return EXPR_TOKENONLYSIZE; if( ExprHasProperty(p, EP_Reduced) ) return EXPR_REDUCEDSIZE; return EXPR_FULLSIZE; } -/* -** Copy the complete content of an Expr node, taking care not to read -** past the end of the structure for a reduced-size version of the source -** Expr. -*/ -static void exprNodeCopy(Expr *pDest, Expr *pSrc){ - memset(pDest, 0, sizeof(Expr)); - memcpy(pDest, pSrc, exprStructSize(pSrc)); -} - /* ** The dupedExpr*Size() routines each return the number of bytes required ** to store a copy of an expression or expression tree. They differ in ** how much of the tree is measured. ** -** dupedExprStructSize() Size of only the Expr structure +** dupedExprStructSize() Size of only the Expr structure ** dupedExprNodeSize() Size of Expr + space for token ** dupedExprSize() Expr + token + subtree components ** *************************************************************************** ** -** The dupedExprStructSize() function returns two values OR-ed together: -** (1) the space required for a copy of the Expr structure only and +** The dupedExprStructSize() function returns two values OR-ed together: +** (1) the space required for a copy of the Expr structure only and ** (2) the EP_xxx flags that indicate what the structure size should be. ** The return values is always one of: ** @@ -98032,22 +114358,17 @@ static void exprNodeCopy(Expr *pDest, Expr *pSrc){ ** of dupedExprStructSize() contain multiple assert() statements that attempt ** to enforce this constraint. */ -static int dupedExprStructSize(Expr *p, int flags){ +static int dupedExprStructSize(const Expr *p, int flags){ int nSize; assert( flags==EXPRDUP_REDUCE || flags==0 ); /* Only one flag value allowed */ assert( EXPR_FULLSIZE<=0xfff ); assert( (0xfff & (EP_Reduced|EP_TokenOnly))==0 ); - if( 0==flags || p->op==TK_SELECT_COLUMN -#ifndef SQLITE_OMIT_WINDOWFUNC - || ExprHasProperty(p, EP_WinFunc) -#endif - ){ + if( 0==flags || ExprHasProperty(p, EP_FullSize) ){ nSize = EXPR_FULLSIZE; }else{ assert( !ExprHasProperty(p, EP_TokenOnly|EP_Reduced) ); - assert( !ExprHasProperty(p, EP_FromJoin) ); - assert( !ExprHasProperty(p, EP_MemToken) ); - assert( !ExprHasProperty(p, EP_NoReduce) ); + assert( !ExprHasProperty(p, EP_OuterON) ); + assert( !ExprHasVVAProperty(p, EP_NoReduce) ); if( p->pLeft || p->x.pList ){ nSize = EXPR_REDUCEDSIZE | EP_Reduced; }else{ @@ -98059,11 +114380,11 @@ static int dupedExprStructSize(Expr *p, int flags){ } /* -** This function returns the space in bytes required to store the copy +** This function returns the space in bytes required to store the copy ** of the Expr structure and a copy of the Expr.u.zToken string (if that ** string is defined.) */ -static int dupedExprNodeSize(Expr *p, int flags){ +static int dupedExprNodeSize(const Expr *p, int flags){ int nByte = dupedExprStructSize(p, flags) & 0xfff; if( !ExprHasProperty(p, EP_IntValue) && p->u.zToken ){ nByte += sqlite3Strlen30NN(p->u.zToken)+1; @@ -98072,56 +114393,94 @@ static int dupedExprNodeSize(Expr *p, int flags){ } /* -** Return the number of bytes required to create a duplicate of the -** expression passed as the first argument. The second argument is a -** mask containing EXPRDUP_XXX flags. +** Return the number of bytes required to create a duplicate of the +** expression passed as the first argument. ** ** The value returned includes space to create a copy of the Expr struct ** itself and the buffer referred to by Expr.u.zToken, if any. ** -** If the EXPRDUP_REDUCE flag is set, then the return value includes -** space to duplicate all Expr nodes in the tree formed by Expr.pLeft -** and Expr.pRight variables (but not for any structures pointed to or -** descended from the Expr.x.pList or Expr.x.pSelect variables). +** The return value includes space to duplicate all Expr nodes in the +** tree formed by Expr.pLeft and Expr.pRight, but not any other +** substructure such as Expr.x.pList, Expr.x.pSelect, and Expr.y.pWin. */ -static int dupedExprSize(Expr *p, int flags){ - int nByte = 0; - if( p ){ - nByte = dupedExprNodeSize(p, flags); - if( flags&EXPRDUP_REDUCE ){ - nByte += dupedExprSize(p->pLeft, flags) + dupedExprSize(p->pRight, flags); - } - } +static int dupedExprSize(const Expr *p){ + int nByte; + assert( p!=0 ); + nByte = dupedExprNodeSize(p, EXPRDUP_REDUCE); + if( p->pLeft ) nByte += dupedExprSize(p->pLeft); + if( p->pRight ) nByte += dupedExprSize(p->pRight); + assert( nByte==ROUND8(nByte) ); return nByte; } /* -** This function is similar to sqlite3ExprDup(), except that if pzBuffer -** is not NULL then *pzBuffer is assumed to point to a buffer large enough -** to store the copy of expression p, the copies of p->u.zToken -** (if applicable), and the copies of the p->pLeft and p->pRight expressions, -** if any. Before returning, *pzBuffer is set to the first byte past the -** portion of the buffer copied into by this function. +** An EdupBuf is a memory allocation used to stored multiple Expr objects +** together with their Expr.zToken content. This is used to help implement +** compression while doing sqlite3ExprDup(). The top-level Expr does the +** allocation for itself and many of its decendents, then passes an instance +** of the structure down into exprDup() so that they decendents can have +** access to that memory. +*/ +typedef struct EdupBuf EdupBuf; +struct EdupBuf { + u8 *zAlloc; /* Memory space available for storage */ +#ifdef SQLITE_DEBUG + u8 *zEnd; /* First byte past the end of memory */ +#endif +}; + +/* +** This function is similar to sqlite3ExprDup(), except that if pEdupBuf +** is not NULL then it points to memory that can be used to store a copy +** of the input Expr p together with its p->u.zToken (if any). pEdupBuf +** is updated with the new buffer tail prior to returning. */ -static Expr *exprDup(sqlite3 *db, Expr *p, int dupFlags, u8 **pzBuffer){ +static Expr *exprDup( + sqlite3 *db, /* Database connection (for memory allocation) */ + const Expr *p, /* Expr tree to be duplicated */ + int dupFlags, /* EXPRDUP_REDUCE for compression. 0 if not */ + EdupBuf *pEdupBuf /* Preallocated storage space, or NULL */ +){ Expr *pNew; /* Value to return */ - u8 *zAlloc; /* Memory space from which to build Expr object */ + EdupBuf sEdupBuf; /* Memory space from which to build Expr object */ u32 staticFlag; /* EP_Static if space not obtained from malloc */ + int nToken = -1; /* Space needed for p->u.zToken. -1 means unknown */ assert( db!=0 ); assert( p ); assert( dupFlags==0 || dupFlags==EXPRDUP_REDUCE ); - assert( pzBuffer==0 || dupFlags==EXPRDUP_REDUCE ); + assert( pEdupBuf==0 || dupFlags==EXPRDUP_REDUCE ); /* Figure out where to write the new Expr structure. */ - if( pzBuffer ){ - zAlloc = *pzBuffer; + if( pEdupBuf ){ + sEdupBuf.zAlloc = pEdupBuf->zAlloc; +#ifdef SQLITE_DEBUG + sEdupBuf.zEnd = pEdupBuf->zEnd; +#endif staticFlag = EP_Static; + assert( sEdupBuf.zAlloc!=0 ); + assert( dupFlags==EXPRDUP_REDUCE ); }else{ - zAlloc = sqlite3DbMallocRawNN(db, dupedExprSize(p, dupFlags)); + int nAlloc; + if( dupFlags ){ + nAlloc = dupedExprSize(p); + }else if( !ExprHasProperty(p, EP_IntValue) && p->u.zToken ){ + nToken = sqlite3Strlen30NN(p->u.zToken)+1; + nAlloc = ROUND8(EXPR_FULLSIZE + nToken); + }else{ + nToken = 0; + nAlloc = ROUND8(EXPR_FULLSIZE); + } + assert( nAlloc==ROUND8(nAlloc) ); + sEdupBuf.zAlloc = sqlite3DbMallocRawNN(db, nAlloc); +#ifdef SQLITE_DEBUG + sEdupBuf.zEnd = sEdupBuf.zAlloc ? sEdupBuf.zAlloc+nAlloc : 0; +#endif + staticFlag = 0; } - pNew = (Expr *)zAlloc; + pNew = (Expr *)sEdupBuf.zAlloc; + assert( EIGHT_BYTE_ALIGNMENT(pNew) ); if( pNew ){ /* Set nNewSize to the size allocated for the structure pointed to @@ -98130,68 +114489,83 @@ static Expr *exprDup(sqlite3 *db, Expr *p, int dupFlags, u8 **pzBuffer){ ** by the copy of the p->u.zToken string (if any). */ const unsigned nStructSize = dupedExprStructSize(p, dupFlags); - const int nNewSize = nStructSize & 0xfff; - int nToken; - if( !ExprHasProperty(p, EP_IntValue) && p->u.zToken ){ - nToken = sqlite3Strlen30(p->u.zToken) + 1; - }else{ - nToken = 0; + int nNewSize = nStructSize & 0xfff; + if( nToken<0 ){ + if( !ExprHasProperty(p, EP_IntValue) && p->u.zToken ){ + nToken = sqlite3Strlen30(p->u.zToken) + 1; + }else{ + nToken = 0; + } } if( dupFlags ){ + assert( (int)(sEdupBuf.zEnd - sEdupBuf.zAlloc) >= nNewSize+nToken ); assert( ExprHasProperty(p, EP_Reduced)==0 ); - memcpy(zAlloc, p, nNewSize); + memcpy(sEdupBuf.zAlloc, p, nNewSize); }else{ u32 nSize = (u32)exprStructSize(p); - memcpy(zAlloc, p, nSize); - if( nSize= + (int)EXPR_FULLSIZE+nToken ); + memcpy(sEdupBuf.zAlloc, p, nSize); + if( nSizeflags &= ~(EP_Reduced|EP_TokenOnly|EP_Static|EP_MemToken); + pNew->flags &= ~(EP_Reduced|EP_TokenOnly|EP_Static); pNew->flags |= nStructSize & (EP_Reduced|EP_TokenOnly); pNew->flags |= staticFlag; + ExprClearVVAProperties(pNew); + if( dupFlags ){ + ExprSetVVAProperty(pNew, EP_Immutable); + } /* Copy the p->u.zToken string, if any. */ - if( nToken ){ - char *zToken = pNew->u.zToken = (char*)&zAlloc[nNewSize]; + assert( nToken>=0 ); + if( nToken>0 ){ + char *zToken = pNew->u.zToken = (char*)&sEdupBuf.zAlloc[nNewSize]; memcpy(zToken, p->u.zToken, nToken); + nNewSize += nToken; } + sEdupBuf.zAlloc += ROUND8(nNewSize); + + if( ((p->flags|pNew->flags)&(EP_TokenOnly|EP_Leaf))==0 ){ - if( 0==((p->flags|pNew->flags) & (EP_TokenOnly|EP_Leaf)) ){ /* Fill in the pNew->x.pSelect or pNew->x.pList member. */ - if( ExprHasProperty(p, EP_xIsSelect) ){ + if( ExprUseXSelect(p) ){ pNew->x.pSelect = sqlite3SelectDup(db, p->x.pSelect, dupFlags); }else{ - pNew->x.pList = sqlite3ExprListDup(db, p->x.pList, dupFlags); + pNew->x.pList = sqlite3ExprListDup(db, p->x.pList, + p->op!=TK_ORDER ? dupFlags : 0); } - } - /* Fill in pNew->pLeft and pNew->pRight. */ - if( ExprHasProperty(pNew, EP_Reduced|EP_TokenOnly|EP_WinFunc) ){ - zAlloc += dupedExprNodeSize(p, dupFlags); - if( !ExprHasProperty(pNew, EP_TokenOnly|EP_Leaf) ){ - pNew->pLeft = p->pLeft ? - exprDup(db, p->pLeft, EXPRDUP_REDUCE, &zAlloc) : 0; - pNew->pRight = p->pRight ? - exprDup(db, p->pRight, EXPRDUP_REDUCE, &zAlloc) : 0; - } #ifndef SQLITE_OMIT_WINDOWFUNC if( ExprHasProperty(p, EP_WinFunc) ){ pNew->y.pWin = sqlite3WindowDup(db, pNew, p->y.pWin); assert( ExprHasProperty(pNew, EP_WinFunc) ); } #endif /* SQLITE_OMIT_WINDOWFUNC */ - if( pzBuffer ){ - *pzBuffer = zAlloc; - } - }else{ - if( !ExprHasProperty(p, EP_TokenOnly|EP_Leaf) ){ - if( pNew->op==TK_SELECT_COLUMN ){ + + /* Fill in pNew->pLeft and pNew->pRight. */ + if( dupFlags ){ + if( p->op==TK_SELECT_COLUMN ){ pNew->pLeft = p->pLeft; - assert( p->iColumn==0 || p->pRight==0 ); - assert( p->pRight==0 || p->pRight==p->pLeft ); + assert( p->pRight==0 + || p->pRight==p->pLeft + || ExprHasProperty(p->pLeft, EP_Subquery) ); + }else{ + pNew->pLeft = p->pLeft ? + exprDup(db, p->pLeft, EXPRDUP_REDUCE, &sEdupBuf) : 0; + } + pNew->pRight = p->pRight ? + exprDup(db, p->pRight, EXPRDUP_REDUCE, &sEdupBuf) : 0; + }else{ + if( p->op==TK_SELECT_COLUMN ){ + pNew->pLeft = p->pLeft; + assert( p->pRight==0 + || p->pRight==p->pLeft + || ExprHasProperty(p->pLeft, EP_Subquery) ); }else{ pNew->pLeft = sqlite3ExprDup(db, p->pLeft, 0); } @@ -98199,19 +114573,21 @@ static Expr *exprDup(sqlite3 *db, Expr *p, int dupFlags, u8 **pzBuffer){ } } } + if( pEdupBuf ) memcpy(pEdupBuf, &sEdupBuf, sizeof(sEdupBuf)); + assert( sEdupBuf.zAlloc <= sEdupBuf.zEnd ); return pNew; } /* -** Create and return a deep copy of the object passed as the second +** Create and return a deep copy of the object passed as the second ** argument. If an OOM condition is encountered, NULL is returned ** and the db->mallocFailed flag set. */ #ifndef SQLITE_OMIT_CTE -static With *withDup(sqlite3 *db, With *p){ +SQLITE_PRIVATE With *sqlite3WithDup(sqlite3 *db, With *p){ With *pRet = 0; if( p ){ - sqlite3_int64 nByte = sizeof(*p) + sizeof(p->a[0]) * (p->nCte-1); + sqlite3_int64 nByte = SZ_WITH(p->nCte); pRet = sqlite3DbMallocZero(db, nByte); if( pRet ){ int i; @@ -98220,13 +114596,14 @@ static With *withDup(sqlite3 *db, With *p){ pRet->a[i].pSelect = sqlite3SelectDup(db, p->a[i].pSelect, 0); pRet->a[i].pCols = sqlite3ExprListDup(db, p->a[i].pCols, 0); pRet->a[i].zName = sqlite3DbStrDup(db, p->a[i].zName); + pRet->a[i].eM10d = p->a[i].eM10d; } } } return pRet; } #else -# define withDup(x,y) 0 +# define sqlite3WithDup(x,y) 0 #endif #ifndef SQLITE_OMIT_WINDOWFUNC @@ -98237,10 +114614,13 @@ static With *withDup(sqlite3 *db, With *p){ ** objects found there, assembling them onto the linked list at Select->pWin. */ static int gatherSelectWindowsCallback(Walker *pWalker, Expr *pExpr){ - if( pExpr->op==TK_FUNCTION && pExpr->y.pWin!=0 ){ - assert( ExprHasProperty(pExpr, EP_WinFunc) ); - pExpr->y.pWin->pNextWin = pWalker->u.pSelect->pWin; - pWalker->u.pSelect->pWin = pExpr->y.pWin; + if( pExpr->op==TK_FUNCTION && ExprHasProperty(pExpr, EP_WinFunc) ){ + Select *pSelect = pWalker->u.pSelect; + Window *pWin = pExpr->y.pWin; + assert( pWin ); + assert( IsWindowFunc(pExpr) ); + assert( pWin->ppThis==0 ); + sqlite3WindowLink(pSelect, pWin); } return WRC_Continue; } @@ -98266,7 +114646,7 @@ static void gatherSelectWindows(Select *p){ ** without effecting the originals. ** ** The expression list, ID, and source lists return by sqlite3ExprListDup(), -** sqlite3IdListDup(), and sqlite3SrcListDup() can not be further expanded +** sqlite3IdListDup(), and sqlite3SrcListDup() can not be further expanded ** by subsequent calls to sqlite*ListAppend() routines. ** ** Any tables that the SrcList might point to are not duplicated. @@ -98276,48 +114656,48 @@ static void gatherSelectWindows(Select *p){ ** truncated version of the usual Expr structure that will be stored as ** part of the in-memory representation of the database schema. */ -SQLITE_PRIVATE Expr *sqlite3ExprDup(sqlite3 *db, Expr *p, int flags){ +SQLITE_PRIVATE Expr *sqlite3ExprDup(sqlite3 *db, const Expr *p, int flags){ assert( flags==0 || flags==EXPRDUP_REDUCE ); return p ? exprDup(db, p, flags, 0) : 0; } -SQLITE_PRIVATE ExprList *sqlite3ExprListDup(sqlite3 *db, ExprList *p, int flags){ +SQLITE_PRIVATE ExprList *sqlite3ExprListDup(sqlite3 *db, const ExprList *p, int flags){ ExprList *pNew; - struct ExprList_item *pItem, *pOldItem; + struct ExprList_item *pItem; + const struct ExprList_item *pOldItem; int i; - Expr *pPriorSelectCol = 0; + Expr *pPriorSelectColOld = 0; + Expr *pPriorSelectColNew = 0; assert( db!=0 ); if( p==0 ) return 0; pNew = sqlite3DbMallocRawNN(db, sqlite3DbMallocSize(db, p)); if( pNew==0 ) return 0; pNew->nExpr = p->nExpr; + pNew->nAlloc = p->nAlloc; pItem = pNew->a; pOldItem = p->a; for(i=0; inExpr; i++, pItem++, pOldItem++){ Expr *pOldExpr = pOldItem->pExpr; Expr *pNewExpr; pItem->pExpr = sqlite3ExprDup(db, pOldExpr, flags); - if( pOldExpr + if( pOldExpr && pOldExpr->op==TK_SELECT_COLUMN - && (pNewExpr = pItem->pExpr)!=0 + && (pNewExpr = pItem->pExpr)!=0 ){ - assert( pNewExpr->iColumn==0 || i>0 ); - if( pNewExpr->iColumn==0 ){ - assert( pOldExpr->pLeft==pOldExpr->pRight ); - pPriorSelectCol = pNewExpr->pLeft = pNewExpr->pRight; - }else{ - assert( i>0 ); - assert( pItem[-1].pExpr!=0 ); - assert( pNewExpr->iColumn==pItem[-1].pExpr->iColumn+1 ); - assert( pPriorSelectCol==pItem[-1].pExpr->pLeft ); - pNewExpr->pLeft = pPriorSelectCol; - } - } - pItem->zName = sqlite3DbStrDup(db, pOldItem->zName); - pItem->zSpan = sqlite3DbStrDup(db, pOldItem->zSpan); - pItem->sortOrder = pOldItem->sortOrder; - pItem->done = 0; - pItem->bSpanIsTab = pOldItem->bSpanIsTab; - pItem->bSorterRef = pOldItem->bSorterRef; + if( pNewExpr->pRight ){ + pPriorSelectColOld = pOldExpr->pRight; + pPriorSelectColNew = pNewExpr->pRight; + pNewExpr->pLeft = pNewExpr->pRight; + }else{ + if( pOldExpr->pLeft!=pPriorSelectColOld ){ + pPriorSelectColOld = pOldExpr->pLeft; + pPriorSelectColNew = sqlite3ExprDup(db, pPriorSelectColOld, flags); + pNewExpr->pRight = pPriorSelectColNew; + } + pNewExpr->pLeft = pPriorSelectColNew; + } + } + pItem->zEName = sqlite3DbStrDup(db, pOldItem->zEName); + pItem->fg = pOldItem->fg; pItem->u = pOldItem->u; } return pNew; @@ -98325,82 +114705,94 @@ SQLITE_PRIVATE ExprList *sqlite3ExprListDup(sqlite3 *db, ExprList *p, int flags) /* ** If cursors, triggers, views and subqueries are all omitted from -** the build, then none of the following routines, except for +** the build, then none of the following routines, except for ** sqlite3SelectDup(), can be called. sqlite3SelectDup() is sometimes ** called with a NULL argument. */ #if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_TRIGGER) \ || !defined(SQLITE_OMIT_SUBQUERY) -SQLITE_PRIVATE SrcList *sqlite3SrcListDup(sqlite3 *db, SrcList *p, int flags){ +SQLITE_PRIVATE SrcList *sqlite3SrcListDup(sqlite3 *db, const SrcList *p, int flags){ SrcList *pNew; int i; - int nByte; assert( db!=0 ); if( p==0 ) return 0; - nByte = sizeof(*p) + (p->nSrc>0 ? sizeof(p->a[0]) * (p->nSrc-1) : 0); - pNew = sqlite3DbMallocRawNN(db, nByte ); + pNew = sqlite3DbMallocRawNN(db, SZ_SRCLIST(p->nSrc) ); if( pNew==0 ) return 0; pNew->nSrc = pNew->nAlloc = p->nSrc; for(i=0; inSrc; i++){ - struct SrcList_item *pNewItem = &pNew->a[i]; - struct SrcList_item *pOldItem = &p->a[i]; + SrcItem *pNewItem = &pNew->a[i]; + const SrcItem *pOldItem = &p->a[i]; Table *pTab; - pNewItem->pSchema = pOldItem->pSchema; - pNewItem->zDatabase = sqlite3DbStrDup(db, pOldItem->zDatabase); + pNewItem->fg = pOldItem->fg; + if( pOldItem->fg.isSubquery ){ + Subquery *pNewSubq = sqlite3DbMallocRaw(db, sizeof(Subquery)); + if( pNewSubq==0 ){ + assert( db->mallocFailed ); + pNewItem->fg.isSubquery = 0; + }else{ + memcpy(pNewSubq, pOldItem->u4.pSubq, sizeof(*pNewSubq)); + pNewSubq->pSelect = sqlite3SelectDup(db, pNewSubq->pSelect, flags); + if( pNewSubq->pSelect==0 ){ + sqlite3DbFree(db, pNewSubq); + pNewSubq = 0; + pNewItem->fg.isSubquery = 0; + } + } + pNewItem->u4.pSubq = pNewSubq; + }else if( pOldItem->fg.fixedSchema ){ + pNewItem->u4.pSchema = pOldItem->u4.pSchema; + }else{ + pNewItem->u4.zDatabase = sqlite3DbStrDup(db, pOldItem->u4.zDatabase); + } pNewItem->zName = sqlite3DbStrDup(db, pOldItem->zName); pNewItem->zAlias = sqlite3DbStrDup(db, pOldItem->zAlias); - pNewItem->fg = pOldItem->fg; pNewItem->iCursor = pOldItem->iCursor; - pNewItem->addrFillSub = pOldItem->addrFillSub; - pNewItem->regReturn = pOldItem->regReturn; if( pNewItem->fg.isIndexedBy ){ pNewItem->u1.zIndexedBy = sqlite3DbStrDup(db, pOldItem->u1.zIndexedBy); - } - pNewItem->pIBIndex = pOldItem->pIBIndex; - if( pNewItem->fg.isTabFunc ){ - pNewItem->u1.pFuncArg = + }else if( pNewItem->fg.isTabFunc ){ + pNewItem->u1.pFuncArg = sqlite3ExprListDup(db, pOldItem->u1.pFuncArg, flags); + }else{ + pNewItem->u1.nRow = pOldItem->u1.nRow; + } + pNewItem->u2 = pOldItem->u2; + if( pNewItem->fg.isCte ){ + pNewItem->u2.pCteUse->nUse++; } - pTab = pNewItem->pTab = pOldItem->pTab; + pTab = pNewItem->pSTab = pOldItem->pSTab; if( pTab ){ pTab->nTabRef++; } - pNewItem->pSelect = sqlite3SelectDup(db, pOldItem->pSelect, flags); - pNewItem->pOn = sqlite3ExprDup(db, pOldItem->pOn, flags); - pNewItem->pUsing = sqlite3IdListDup(db, pOldItem->pUsing); + if( pOldItem->fg.isUsing ){ + assert( pNewItem->fg.isUsing ); + pNewItem->u3.pUsing = sqlite3IdListDup(db, pOldItem->u3.pUsing); + }else{ + pNewItem->u3.pOn = sqlite3ExprDup(db, pOldItem->u3.pOn, flags); + } pNewItem->colUsed = pOldItem->colUsed; } return pNew; } -SQLITE_PRIVATE IdList *sqlite3IdListDup(sqlite3 *db, IdList *p){ +SQLITE_PRIVATE IdList *sqlite3IdListDup(sqlite3 *db, const IdList *p){ IdList *pNew; int i; assert( db!=0 ); if( p==0 ) return 0; - pNew = sqlite3DbMallocRawNN(db, sizeof(*pNew) ); + pNew = sqlite3DbMallocRawNN(db, SZ_IDLIST(p->nId)); if( pNew==0 ) return 0; pNew->nId = p->nId; - pNew->a = sqlite3DbMallocRawNN(db, p->nId*sizeof(p->a[0]) ); - if( pNew->a==0 ){ - sqlite3DbFreeNN(db, pNew); - return 0; - } - /* Note that because the size of the allocation for p->a[] is not - ** necessarily a power of two, sqlite3IdListAppend() may not be called - ** on the duplicate created by this function. */ for(i=0; inId; i++){ struct IdList_item *pNewItem = &pNew->a[i]; - struct IdList_item *pOldItem = &p->a[i]; + const struct IdList_item *pOldItem = &p->a[i]; pNewItem->zName = sqlite3DbStrDup(db, pOldItem->zName); - pNewItem->idx = pOldItem->idx; } return pNew; } -SQLITE_PRIVATE Select *sqlite3SelectDup(sqlite3 *db, Select *pDup, int flags){ +SQLITE_PRIVATE Select *sqlite3SelectDup(sqlite3 *db, const Select *pDup, int flags){ Select *pRet = 0; Select *pNext = 0; Select **pp = &pRet; - Select *p; + const Select *p; assert( db!=0 ); for(p=pDup; p; p=p->pPrior){ @@ -98418,26 +114810,31 @@ SQLITE_PRIVATE Select *sqlite3SelectDup(sqlite3 *db, Select *pDup, int flags){ pNew->pLimit = sqlite3ExprDup(db, p->pLimit, flags); pNew->iLimit = 0; pNew->iOffset = 0; - pNew->selFlags = p->selFlags & ~SF_UsesEphemeral; - pNew->addrOpenEphm[0] = -1; - pNew->addrOpenEphm[1] = -1; + pNew->selFlags = p->selFlags; pNew->nSelectRow = p->nSelectRow; - pNew->pWith = withDup(db, p->pWith); + pNew->pWith = sqlite3WithDup(db, p->pWith); #ifndef SQLITE_OMIT_WINDOWFUNC pNew->pWin = 0; pNew->pWinDefn = sqlite3WindowListDup(db, p->pWinDefn); - if( p->pWin ) gatherSelectWindows(pNew); + if( p->pWin && db->mallocFailed==0 ) gatherSelectWindows(pNew); #endif pNew->selId = p->selId; + if( db->mallocFailed ){ + /* Any prior OOM might have left the Select object incomplete. + ** Delete the whole thing rather than allow an incomplete Select + ** to be used by the code generator. */ + pNew->pNext = 0; + sqlite3SelectDelete(db, pNew); + break; + } *pp = pNew; pp = &pNew->pPrior; pNext = pNew; } - return pRet; } #else -SQLITE_PRIVATE Select *sqlite3SelectDup(sqlite3 *db, Select *p, int flags){ +SQLITE_PRIVATE Select *sqlite3SelectDup(sqlite3 *db, const Select *p, int flags){ assert( p==0 ); return 0; } @@ -98449,51 +114846,69 @@ SQLITE_PRIVATE Select *sqlite3SelectDup(sqlite3 *db, Select *p, int flags){ ** initially NULL, then create a new expression list. ** ** The pList argument must be either NULL or a pointer to an ExprList -** obtained from a prior call to sqlite3ExprListAppend(). This routine -** may not be used with an ExprList obtained from sqlite3ExprListDup(). -** Reason: This routine assumes that the number of slots in pList->a[] -** is a power of two. That is true for sqlite3ExprListAppend() returns -** but is not necessarily true from the return value of sqlite3ExprListDup(). +** obtained from a prior call to sqlite3ExprListAppend(). ** ** If a memory allocation error occurs, the entire list is freed and ** NULL is returned. If non-NULL is returned, then it is guaranteed ** that the new entry was successfully appended. */ +static const struct ExprList_item zeroItem = {0}; +SQLITE_PRIVATE SQLITE_NOINLINE ExprList *sqlite3ExprListAppendNew( + sqlite3 *db, /* Database handle. Used for memory allocation */ + Expr *pExpr /* Expression to be appended. Might be NULL */ +){ + struct ExprList_item *pItem; + ExprList *pList; + + pList = sqlite3DbMallocRawNN(db, SZ_EXPRLIST(4)); + if( pList==0 ){ + sqlite3ExprDelete(db, pExpr); + return 0; + } + pList->nAlloc = 4; + pList->nExpr = 1; + pItem = &pList->a[0]; + *pItem = zeroItem; + pItem->pExpr = pExpr; + return pList; +} +SQLITE_PRIVATE SQLITE_NOINLINE ExprList *sqlite3ExprListAppendGrow( + sqlite3 *db, /* Database handle. Used for memory allocation */ + ExprList *pList, /* List to which to append. Might be NULL */ + Expr *pExpr /* Expression to be appended. Might be NULL */ +){ + struct ExprList_item *pItem; + ExprList *pNew; + pList->nAlloc *= 2; + pNew = sqlite3DbRealloc(db, pList, SZ_EXPRLIST(pList->nAlloc)); + if( pNew==0 ){ + sqlite3ExprListDelete(db, pList); + sqlite3ExprDelete(db, pExpr); + return 0; + }else{ + pList = pNew; + } + pItem = &pList->a[pList->nExpr++]; + *pItem = zeroItem; + pItem->pExpr = pExpr; + return pList; +} SQLITE_PRIVATE ExprList *sqlite3ExprListAppend( Parse *pParse, /* Parsing context */ ExprList *pList, /* List to which to append. Might be NULL */ Expr *pExpr /* Expression to be appended. Might be NULL */ ){ struct ExprList_item *pItem; - sqlite3 *db = pParse->db; - assert( db!=0 ); if( pList==0 ){ - pList = sqlite3DbMallocRawNN(db, sizeof(ExprList) ); - if( pList==0 ){ - goto no_mem; - } - pList->nExpr = 0; - }else if( (pList->nExpr & (pList->nExpr-1))==0 ){ - ExprList *pNew; - pNew = sqlite3DbRealloc(db, pList, - sizeof(*pList)+(2*(sqlite3_int64)pList->nExpr-1)*sizeof(pList->a[0])); - if( pNew==0 ){ - goto no_mem; - } - pList = pNew; + return sqlite3ExprListAppendNew(pParse->db,pExpr); + } + if( pList->nAllocnExpr+1 ){ + return sqlite3ExprListAppendGrow(pParse->db,pList,pExpr); } pItem = &pList->a[pList->nExpr++]; - assert( offsetof(struct ExprList_item,zName)==sizeof(pItem->pExpr) ); - assert( offsetof(struct ExprList_item,pExpr)==0 ); - memset(&pItem->zName,0,sizeof(*pItem)-offsetof(struct ExprList_item,zName)); + *pItem = zeroItem; pItem->pExpr = pExpr; return pList; - -no_mem: - /* Avoid leaking memory if malloc has failed. */ - sqlite3ExprDelete(db, pExpr); - sqlite3ExprListDelete(db, pList); - return 0; } /* @@ -98522,8 +114937,8 @@ SQLITE_PRIVATE ExprList *sqlite3ExprListAppendVector( if( NEVER(pColumns==0) ) goto vector_append_error; if( pExpr==0 ) goto vector_append_error; - /* If the RHS is a vector, then we can immediately check to see that - ** the size of the RHS and LHS match. But if the RHS is a SELECT, + /* If the RHS is a vector, then we can immediately check to see that + ** the size of the RHS and LHS match. But if the RHS is a SELECT, ** wildcards ("*") in the result set of the SELECT must be expanded before ** we can do the size check, so defer the size check until code generation. */ @@ -98534,11 +114949,13 @@ SQLITE_PRIVATE ExprList *sqlite3ExprListAppendVector( } for(i=0; inId; i++){ - Expr *pSubExpr = sqlite3ExprForVectorField(pParse, pExpr, i); + Expr *pSubExpr = sqlite3ExprForVectorField(pParse, pExpr, i, pColumns->nId); + assert( pSubExpr!=0 || db->mallocFailed ); + if( pSubExpr==0 ) continue; pList = sqlite3ExprListAppend(pParse, pList, pSubExpr); if( pList ){ assert( pList->nExpr==iFirst+i+1 ); - pList->a[pList->nExpr-1].zName = pColumns->a[i].zName; + pList->a[pList->nExpr-1].zEName = pColumns->a[i].zName; pColumns->a[i].zName = 0; } } @@ -98547,7 +114964,7 @@ SQLITE_PRIVATE ExprList *sqlite3ExprListAppendVector( Expr *pFirst = pList->a[iFirst].pExpr; assert( pFirst!=0 ); assert( pFirst->op==TK_SELECT_COLUMN ); - + /* Store the SELECT statement in pRight so it will be deleted when ** sqlite3ExprListDelete() is called */ pFirst->pRight = pExpr; @@ -98559,10 +114976,7 @@ SQLITE_PRIVATE ExprList *sqlite3ExprListAppendVector( } vector_append_error: - if( IN_RENAME_OBJECT ){ - sqlite3RenameExprUnmap(pParse, pExpr); - } - sqlite3ExprDelete(db, pExpr); + sqlite3ExprUnmapAndDelete(pParse, pExpr); sqlite3IdListDelete(db, pColumns); return pList; } @@ -98570,19 +114984,38 @@ SQLITE_PRIVATE ExprList *sqlite3ExprListAppendVector( /* ** Set the sort order for the last element on the given ExprList. */ -SQLITE_PRIVATE void sqlite3ExprListSetSortOrder(ExprList *p, int iSortOrder){ +SQLITE_PRIVATE void sqlite3ExprListSetSortOrder(ExprList *p, int iSortOrder, int eNulls){ + struct ExprList_item *pItem; if( p==0 ) return; - assert( SQLITE_SO_UNDEFINED<0 && SQLITE_SO_ASC>=0 && SQLITE_SO_DESC>0 ); assert( p->nExpr>0 ); - if( iSortOrder<0 ){ - assert( p->a[p->nExpr-1].sortOrder==SQLITE_SO_ASC ); - return; + + assert( SQLITE_SO_UNDEFINED<0 && SQLITE_SO_ASC==0 && SQLITE_SO_DESC>0 ); + assert( iSortOrder==SQLITE_SO_UNDEFINED + || iSortOrder==SQLITE_SO_ASC + || iSortOrder==SQLITE_SO_DESC + ); + assert( eNulls==SQLITE_SO_UNDEFINED + || eNulls==SQLITE_SO_ASC + || eNulls==SQLITE_SO_DESC + ); + + pItem = &p->a[p->nExpr-1]; + assert( pItem->fg.bNulls==0 ); + if( iSortOrder==SQLITE_SO_UNDEFINED ){ + iSortOrder = SQLITE_SO_ASC; + } + pItem->fg.sortFlags = (u8)iSortOrder; + + if( eNulls!=SQLITE_SO_UNDEFINED ){ + pItem->fg.bNulls = 1; + if( iSortOrder!=eNulls ){ + pItem->fg.sortFlags |= KEYINFO_ORDER_BIGNULL; + } } - p->a[p->nExpr-1].sortOrder = (u8)iSortOrder; } /* -** Set the ExprList.a[].zName element of the most recently added item +** Set the ExprList.a[].zEName element of the most recently added item ** on the expression list. ** ** pList might be NULL following an OOM error. But pName should never be @@ -98592,19 +115025,26 @@ SQLITE_PRIVATE void sqlite3ExprListSetSortOrder(ExprList *p, int iSortOrder){ SQLITE_PRIVATE void sqlite3ExprListSetName( Parse *pParse, /* Parsing context */ ExprList *pList, /* List to which to add the span. */ - Token *pName, /* Name to be added */ + const Token *pName, /* Name to be added */ int dequote /* True to cause the name to be dequoted */ ){ assert( pList!=0 || pParse->db->mallocFailed!=0 ); + assert( pParse->eParseMode!=PARSE_MODE_UNMAP || dequote==0 ); if( pList ){ struct ExprList_item *pItem; assert( pList->nExpr>0 ); pItem = &pList->a[pList->nExpr-1]; - assert( pItem->zName==0 ); - pItem->zName = sqlite3DbStrNDup(pParse->db, pName->z, pName->n); - if( dequote ) sqlite3Dequote(pItem->zName); - if( IN_RENAME_OBJECT ){ - sqlite3RenameTokenMap(pParse, (void*)pItem->zName, pName); + assert( pItem->zEName==0 ); + assert( pItem->fg.eEName==ENAME_NAME ); + pItem->zEName = sqlite3DbStrNDup(pParse->db, pName->z, pName->n); + if( dequote ){ + /* If dequote==0, then pName->z does not point to part of a DDL + ** statement handled by the parser. And so no token need be added + ** to the token-map. */ + sqlite3Dequote(pItem->zEName); + if( IN_RENAME_OBJECT ){ + sqlite3RenameTokenMap(pParse, (const void*)pItem->zEName, pName); + } } } } @@ -98628,8 +115068,10 @@ SQLITE_PRIVATE void sqlite3ExprListSetSpan( if( pList ){ struct ExprList_item *pItem = &pList->a[pList->nExpr-1]; assert( pList->nExpr>0 ); - sqlite3DbFree(db, pItem->zSpan); - pItem->zSpan = sqlite3DbSpanDup(db, zStart, zEnd); + if( pItem->zEName==0 ){ + pItem->zEName = sqlite3DbSpanDup(db, zStart, zEnd); + pItem->fg.eEName = ENAME_SPAN; + } } } @@ -98657,17 +115099,20 @@ static SQLITE_NOINLINE void exprListDeleteNN(sqlite3 *db, ExprList *pList){ int i = pList->nExpr; struct ExprList_item *pItem = pList->a; assert( pList->nExpr>0 ); + assert( db!=0 ); do{ sqlite3ExprDelete(db, pItem->pExpr); - sqlite3DbFree(db, pItem->zName); - sqlite3DbFree(db, pItem->zSpan); + if( pItem->zEName ) sqlite3DbNNFreeNN(db, pItem->zEName); pItem++; }while( --i>0 ); - sqlite3DbFreeNN(db, pList); + sqlite3DbNNFreeNN(db, pList); } SQLITE_PRIVATE void sqlite3ExprListDelete(sqlite3 *db, ExprList *pList){ if( pList ) exprListDeleteNN(db, pList); } +SQLITE_PRIVATE void sqlite3ExprListDeleteGeneric(sqlite3 *db, void *pList){ + if( ALWAYS(pList) ) exprListDeleteNN(db, (ExprList*)pList); +} /* ** Return the bitwise-OR of all Expr.flags fields in the given @@ -98698,18 +115143,34 @@ SQLITE_PRIVATE int sqlite3SelectWalkFail(Walker *pWalker, Select *NotUsed){ return WRC_Abort; } +/* +** Check the input string to see if it is "true" or "false" (in any case). +** +** If the string is.... Return +** "true" EP_IsTrue +** "false" EP_IsFalse +** anything else 0 +*/ +SQLITE_PRIVATE u32 sqlite3IsTrueOrFalse(const char *zIn){ + if( sqlite3StrICmp(zIn, "true")==0 ) return EP_IsTrue; + if( sqlite3StrICmp(zIn, "false")==0 ) return EP_IsFalse; + return 0; +} + + /* ** If the input expression is an ID with the name "true" or "false" ** then convert it into an TK_TRUEFALSE term. Return non-zero if ** the conversion happened, and zero if the expression is unaltered. */ SQLITE_PRIVATE int sqlite3ExprIdToTrueFalse(Expr *pExpr){ + u32 v; assert( pExpr->op==TK_ID || pExpr->op==TK_STRING ); - if( !ExprHasProperty(pExpr, EP_Quoted) - && (sqlite3StrICmp(pExpr->u.zToken, "true")==0 - || sqlite3StrICmp(pExpr->u.zToken, "false")==0) + if( !ExprHasProperty(pExpr, EP_Quoted|EP_IntValue) + && (v = sqlite3IsTrueOrFalse(pExpr->u.zToken))!=0 ){ pExpr->op = TK_TRUEFALSE; + ExprSetProperty(pExpr, v); return 1; } return 0; @@ -98720,12 +115181,168 @@ SQLITE_PRIVATE int sqlite3ExprIdToTrueFalse(Expr *pExpr){ ** and 0 if it is FALSE. */ SQLITE_PRIVATE int sqlite3ExprTruthValue(const Expr *pExpr){ + pExpr = sqlite3ExprSkipCollateAndLikely((Expr*)pExpr); assert( pExpr->op==TK_TRUEFALSE ); + assert( !ExprHasProperty(pExpr, EP_IntValue) ); assert( sqlite3StrICmp(pExpr->u.zToken,"true")==0 || sqlite3StrICmp(pExpr->u.zToken,"false")==0 ); return pExpr->u.zToken[4]==0; } +/* +** If pExpr is an AND or OR expression, try to simplify it by eliminating +** terms that are always true or false. Return the simplified expression. +** Or return the original expression if no simplification is possible. +** +** Examples: +** +** (x<10) AND true => (x<10) +** (x<10) AND false => false +** (x<10) AND (y=22 OR false) => (x<10) AND (y=22) +** (x<10) AND (y=22 OR true) => (x<10) +** (y=22) OR true => true +*/ +SQLITE_PRIVATE Expr *sqlite3ExprSimplifiedAndOr(Expr *pExpr){ + assert( pExpr!=0 ); + if( pExpr->op==TK_AND || pExpr->op==TK_OR ){ + Expr *pRight = sqlite3ExprSimplifiedAndOr(pExpr->pRight); + Expr *pLeft = sqlite3ExprSimplifiedAndOr(pExpr->pLeft); + if( ExprAlwaysTrue(pLeft) || ExprAlwaysFalse(pRight) ){ + pExpr = pExpr->op==TK_AND ? pRight : pLeft; + }else if( ExprAlwaysTrue(pRight) || ExprAlwaysFalse(pLeft) ){ + pExpr = pExpr->op==TK_AND ? pLeft : pRight; + } + } + return pExpr; +} + +/* +** Return true if it might be advantageous to compute the right operand +** of expression pExpr first, before the left operand. +** +** Normally the left operand is computed before the right operand. But if +** the left operand contains a subquery and the right does not, then it +** might be more efficient to compute the right operand first. +*/ +static int exprEvalRhsFirst(Expr *pExpr){ + if( ExprHasProperty(pExpr->pLeft, EP_Subquery) + && !ExprHasProperty(pExpr->pRight, EP_Subquery) + ){ + return 1; + }else{ + return 0; + } +} + +/* +** Compute the two operands of a binary operator. +** +** If either operand contains a subquery, then the code strives to +** compute the operand containing the subquery second. If the other +** operand evalutes to NULL, then a jump is made. The address of the +** IsNull operand that does this jump is returned. The caller can use +** this to optimize the computation so as to avoid doing the potentially +** expensive subquery. +** +** If no optimization opportunities exist, return 0. +*/ +static int exprComputeOperands( + Parse *pParse, /* Parsing context */ + Expr *pExpr, /* The comparison expression */ + int *pR1, /* OUT: Register holding the left operand */ + int *pR2, /* OUT: Register holding the right operand */ + int *pFree1, /* OUT: Temp register to free if not zero */ + int *pFree2 /* OUT: Another temp register to free if not zero */ +){ + int addrIsNull; + int r1, r2; + Vdbe *v = pParse->pVdbe; + + assert( v!=0 ); + /* + ** If the left operand contains a (possibly expensive) subquery and the + ** right operand does not and the right operation might be NULL, + ** then compute the right operand first and do an IsNull jump if the + ** right operand evalutes to NULL. + */ + if( exprEvalRhsFirst(pExpr) && sqlite3ExprCanBeNull(pExpr->pRight) ){ + r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, pFree2); + addrIsNull = sqlite3VdbeAddOp1(v, OP_IsNull, r2); + VdbeComment((v, "skip left operand")); + VdbeCoverage(v); + }else{ + r2 = 0; /* Silence a false-positive uninit-var warning in MSVC */ + addrIsNull = 0; + } + r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, pFree1); + if( addrIsNull==0 ){ + /* + ** If the right operand contains a subquery and the left operand does not + ** and the left operand might be NULL, then do an IsNull check + ** check on the left operand before computing the right operand. + */ + if( ExprHasProperty(pExpr->pRight, EP_Subquery) + && sqlite3ExprCanBeNull(pExpr->pLeft) + ){ + addrIsNull = sqlite3VdbeAddOp1(v, OP_IsNull, r1); + VdbeComment((v, "skip right operand")); + VdbeCoverage(v); + } + r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, pFree2); + } + *pR1 = r1; + *pR2 = r2; + return addrIsNull; +} + +/* +** pExpr is a TK_FUNCTION node. Try to determine whether or not the +** function is a constant function. A function is constant if all of +** the following are true: +** +** (1) It is a scalar function (not an aggregate or window function) +** (2) It has either the SQLITE_FUNC_CONSTANT or SQLITE_FUNC_SLOCHNG +** property. +** (3) All of its arguments are constants +** +** This routine sets pWalker->eCode to 0 if pExpr is not a constant. +** It makes no changes to pWalker->eCode if pExpr is constant. In +** every case, it returns WRC_Abort. +** +** Called as a service subroutine from exprNodeIsConstant(). +*/ +static SQLITE_NOINLINE int exprNodeIsConstantFunction( + Walker *pWalker, + Expr *pExpr +){ + int n; /* Number of arguments */ + ExprList *pList; /* List of arguments */ + FuncDef *pDef; /* The function */ + sqlite3 *db; /* The database */ + + assert( pExpr->op==TK_FUNCTION ); + if( ExprHasProperty(pExpr, EP_TokenOnly) + || (pList = pExpr->x.pList)==0 + ){; + n = 0; + }else{ + n = pList->nExpr; + sqlite3WalkExprList(pWalker, pList); + if( pWalker->eCode==0 ) return WRC_Abort; + } + db = pWalker->pParse->db; + pDef = sqlite3FindFunction(db, pExpr->u.zToken, n, ENC(db), 0); + if( pDef==0 + || pDef->xFinalize!=0 + || (pDef->funcFlags & (SQLITE_FUNC_CONSTANT|SQLITE_FUNC_SLOCHNG))==0 + || ExprHasProperty(pExpr, EP_WinFunc) + ){ + pWalker->eCode = 0; + return WRC_Abort; + } + return WRC_Prune; +} + /* ** These routines are Walker callbacks used to check expressions to @@ -98743,21 +115360,23 @@ SQLITE_PRIVATE int sqlite3ExprTruthValue(const Expr *pExpr){ ** In all cases, the callbacks set Walker.eCode=0 and abort if the expression ** is found to not be a constant. ** -** The sqlite3ExprIsConstantOrFunction() is used for evaluating expressions -** in a CREATE TABLE statement. The Walker.eCode value is 5 when parsing -** an existing schema and 4 when processing a new statement. A bound -** parameter raises an error for new statements, but is silently converted -** to NULL for existing schemas. This allows sqlite_master tables that +** The sqlite3ExprIsConstantOrFunction() is used for evaluating DEFAULT +** expressions in a CREATE TABLE statement. The Walker.eCode value is 5 +** when parsing an existing schema out of the sqlite_schema table and 4 +** when processing a new CREATE TABLE statement. A bound parameter raises +** an error for new statements, but is silently converted +** to NULL for existing schemas. This allows sqlite_schema tables that ** contain a bound parameter because they were generated by older versions ** of SQLite to be parsed by newer versions of SQLite without raising a ** malformed schema error. */ static int exprNodeIsConstant(Walker *pWalker, Expr *pExpr){ + assert( pWalker->eCode>0 ); /* If pWalker->eCode is 2 then any term of the expression that comes from - ** the ON or USING clauses of a left join disqualifies the expression + ** the ON or USING clauses of an outer join disqualifies the expression ** from being considered constant. */ - if( pWalker->eCode==2 && ExprHasProperty(pExpr, EP_FromJoin) ){ + if( pWalker->eCode==2 && ExprHasProperty(pExpr, EP_OuterON) ){ pWalker->eCode = 0; return WRC_Abort; } @@ -98767,8 +115386,13 @@ static int exprNodeIsConstant(Walker *pWalker, Expr *pExpr){ ** and either pWalker->eCode==4 or 5 or the function has the ** SQLITE_FUNC_CONST flag. */ case TK_FUNCTION: - if( pWalker->eCode>=4 || ExprHasProperty(pExpr,EP_ConstFunc) ){ + if( (pWalker->eCode>=4 || ExprHasProperty(pExpr,EP_ConstFunc)) + && !ExprHasProperty(pExpr, EP_WinFunc) + ){ + if( pWalker->eCode==5 ) ExprSetProperty(pExpr, EP_FromDDL); return WRC_Continue; + }else if( pWalker->pParse ){ + return exprNodeIsConstantFunction(pWalker, pExpr); }else{ pWalker->eCode = 0; return WRC_Abort; @@ -98779,7 +115403,7 @@ static int exprNodeIsConstant(Walker *pWalker, Expr *pExpr){ if( sqlite3ExprIdToTrueFalse(pExpr) ){ return WRC_Prune; } - /* Fall thru */ + /* no break */ deliberate_fall_through case TK_COLUMN: case TK_AGG_FUNCTION: case TK_AGG_COLUMN: @@ -98793,18 +115417,22 @@ static int exprNodeIsConstant(Walker *pWalker, Expr *pExpr){ if( pWalker->eCode==3 && pExpr->iTable==pWalker->u.iCur ){ return WRC_Continue; } - /* Fall through */ + /* no break */ deliberate_fall_through case TK_IF_NULL_ROW: case TK_REGISTER: + case TK_DOT: + case TK_RAISE: testcase( pExpr->op==TK_REGISTER ); testcase( pExpr->op==TK_IF_NULL_ROW ); + testcase( pExpr->op==TK_DOT ); + testcase( pExpr->op==TK_RAISE ); pWalker->eCode = 0; return WRC_Abort; case TK_VARIABLE: if( pWalker->eCode==5 ){ /* Silently convert bound parameters that appear inside of CREATE ** statements into a NULL when parsing the CREATE statement text out - ** of the sqlite_master table */ + ** of the sqlite_schema table */ pExpr->op = TK_NULL; }else if( pWalker->eCode==4 ){ /* A bound parameter in a CREATE statement that originates from @@ -98812,36 +115440,42 @@ static int exprNodeIsConstant(Walker *pWalker, Expr *pExpr){ pWalker->eCode = 0; return WRC_Abort; } - /* Fall through */ + /* no break */ deliberate_fall_through default: testcase( pExpr->op==TK_SELECT ); /* sqlite3SelectWalkFail() disallows */ testcase( pExpr->op==TK_EXISTS ); /* sqlite3SelectWalkFail() disallows */ return WRC_Continue; } } -static int exprIsConst(Expr *p, int initFlag, int iCur){ +static int exprIsConst(Parse *pParse, Expr *p, int initFlag){ Walker w; w.eCode = initFlag; + w.pParse = pParse; w.xExprCallback = exprNodeIsConstant; w.xSelectCallback = sqlite3SelectWalkFail; #ifdef SQLITE_DEBUG w.xSelectCallback2 = sqlite3SelectWalkAssert2; #endif - w.u.iCur = iCur; sqlite3WalkExpr(&w, p); return w.eCode; } /* ** Walk an expression tree. Return non-zero if the expression is constant -** and 0 if it involves variables or function calls. +** or return zero if the expression involves variables or function calls. ** ** For the purposes of this function, a double-quoted string (ex: "abc") ** is considered a variable but a single-quoted string (ex: 'abc') is ** a constant. +** +** The pParse parameter may be NULL. But if it is NULL, there is no way +** to determine if function calls are constant or not, and hence all +** function calls will be considered to be non-constant. If pParse is +** not NULL, then a function call might be constant, depending on the +** function and on its parameters. */ -SQLITE_PRIVATE int sqlite3ExprIsConstant(Expr *p){ - return exprIsConst(p, 1, 0); +SQLITE_PRIVATE int sqlite3ExprIsConstant(Parse *pParse, Expr *p){ + return exprIsConst(pParse, p, 1); } /* @@ -98855,10 +115489,26 @@ SQLITE_PRIVATE int sqlite3ExprIsConstant(Expr *p){ ** ** When this routine returns true, it indicates that the expression ** can be added to the pParse->pConstExpr list and evaluated once when -** the prepared statement starts up. See sqlite3ExprCodeAtInit(). +** the prepared statement starts up. See sqlite3ExprCodeRunJustOnce(). +*/ +static int sqlite3ExprIsConstantNotJoin(Parse *pParse, Expr *p){ + return exprIsConst(pParse, p, 2); +} + +/* +** This routine examines sub-SELECT statements as an expression is being +** walked as part of sqlite3ExprIsTableConstant(). Sub-SELECTs are considered +** constant as long as they are uncorrelated - meaning that they do not +** contain any terms from outer contexts. */ -SQLITE_PRIVATE int sqlite3ExprIsConstantNotJoin(Expr *p){ - return exprIsConst(p, 2, 0); +static int exprSelectWalkTableConstant(Walker *pWalker, Select *pSelect){ + assert( pSelect!=0 ); + assert( pWalker->eCode==3 || pWalker->eCode==0 ); + if( (pSelect->selFlags & SF_Correlated)!=0 ){ + pWalker->eCode = 0; + return WRC_Abort; + } + return WRC_Prune; } /* @@ -98866,9 +115516,103 @@ SQLITE_PRIVATE int sqlite3ExprIsConstantNotJoin(Expr *p){ ** for any single row of the table with cursor iCur. In other words, the ** expression must not refer to any non-deterministic function nor any ** table other than iCur. +** +** Consider uncorrelated subqueries to be constants if the bAllowSubq +** parameter is true. */ -SQLITE_PRIVATE int sqlite3ExprIsTableConstant(Expr *p, int iCur){ - return exprIsConst(p, 3, iCur); +static int sqlite3ExprIsTableConstant(Expr *p, int iCur, int bAllowSubq){ + Walker w; + w.eCode = 3; + w.pParse = 0; + w.xExprCallback = exprNodeIsConstant; + if( bAllowSubq ){ + w.xSelectCallback = exprSelectWalkTableConstant; + }else{ + w.xSelectCallback = sqlite3SelectWalkFail; +#ifdef SQLITE_DEBUG + w.xSelectCallback2 = sqlite3SelectWalkAssert2; +#endif + } + w.u.iCur = iCur; + sqlite3WalkExpr(&w, p); + return w.eCode; +} + +/* +** Check pExpr to see if it is an constraint on the single data source +** pSrc = &pSrcList->a[iSrc]. In other words, check to see if pExpr +** constrains pSrc but does not depend on any other tables or data +** sources anywhere else in the query. Return true (non-zero) if pExpr +** is a constraint on pSrc only. +** +** This is an optimization. False negatives will perhaps cause slower +** queries, but false positives will yield incorrect answers. So when in +** doubt, return 0. +** +** To be an single-source constraint, the following must be true: +** +** (1) pExpr cannot refer to any table other than pSrc->iCursor. +** +** (2a) pExpr cannot use subqueries unless the bAllowSubq parameter is +** true and the subquery is non-correlated +** +** (2b) pExpr cannot use non-deterministic functions. +** +** (3) pSrc cannot be part of the left operand for a RIGHT JOIN. +** (Is there some way to relax this constraint?) +** +** (4) If pSrc is the right operand of a LEFT JOIN, then... +** (4a) pExpr must come from an ON clause.. +** (4b) and specifically the ON clause associated with the LEFT JOIN. +** +** (5) If pSrc is the right operand of a LEFT JOIN or the left +** operand of a RIGHT JOIN, then pExpr must be from the WHERE +** clause, not an ON clause. +** +** (6) Either: +** +** (6a) pExpr does not originate in an ON or USING clause, or +** +** (6b) The ON or USING clause from which pExpr is derived is +** not to the left of a RIGHT JOIN (or FULL JOIN). +** +** Without this restriction, accepting pExpr as a single-table +** constraint might move the the ON/USING filter expression +** from the left side of a RIGHT JOIN over to the right side, +** which leads to incorrect answers. See also restriction (9) +** on push-down. +*/ +SQLITE_PRIVATE int sqlite3ExprIsSingleTableConstraint( + Expr *pExpr, /* The constraint */ + const SrcList *pSrcList, /* Complete FROM clause */ + int iSrc, /* Which element of pSrcList to use */ + int bAllowSubq /* Allow non-correlated subqueries */ +){ + const SrcItem *pSrc = &pSrcList->a[iSrc]; + if( pSrc->fg.jointype & JT_LTORJ ){ + return 0; /* rule (3) */ + } + if( pSrc->fg.jointype & JT_LEFT ){ + if( !ExprHasProperty(pExpr, EP_OuterON) ) return 0; /* rule (4a) */ + if( pExpr->w.iJoin!=pSrc->iCursor ) return 0; /* rule (4b) */ + }else{ + if( ExprHasProperty(pExpr, EP_OuterON) ) return 0; /* rule (5) */ + } + if( ExprHasProperty(pExpr, EP_OuterON|EP_InnerON) /* (6a) */ + && (pSrcList->a[0].fg.jointype & JT_LTORJ)!=0 /* Fast pre-test of (6b) */ + ){ + int jj; + for(jj=0; jjw.iJoin==pSrcList->a[jj].iCursor ){ + if( (pSrcList->a[jj].fg.jointype & JT_LTORJ)!=0 ){ + return 0; /* restriction (6) */ + } + break; + } + } + } + /* Rules (1), (2a), and (2b) handled by the following: */ + return sqlite3ExprIsTableConstant(pExpr, pSrc->iCursor, bAllowSubq); } @@ -98892,7 +115636,7 @@ static int exprNodeIsConstantOrGroupBy(Walker *pWalker, Expr *pExpr){ } /* Check if pExpr is a sub-select. If so, consider it variable. */ - if( ExprHasProperty(pExpr, EP_xIsSelect) ){ + if( ExprUseXSelect(pExpr) ){ pWalker->eCode = 0; return WRC_Abort; } @@ -98902,7 +115646,7 @@ static int exprNodeIsConstantOrGroupBy(Walker *pWalker, Expr *pExpr){ /* ** Walk the expression tree passed as the first argument. Return non-zero -** if the expression consists entirely of constants or copies of terms +** if the expression consists entirely of constants or copies of terms ** in pGroupBy that sort with the BINARY collation sequence. ** ** This routine is used to determine if a term of the HAVING clause can @@ -98931,9 +115675,21 @@ SQLITE_PRIVATE int sqlite3ExprIsConstantOrGroupBy(Parse *pParse, Expr *p, ExprLi } /* -** Walk an expression tree. Return non-zero if the expression is constant -** or a function call with constant arguments. Return and 0 if there -** are any variables. +** Walk an expression tree for the DEFAULT field of a column definition +** in a CREATE TABLE statement. Return non-zero if the expression is +** acceptable for use as a DEFAULT. That is to say, return non-zero if +** the expression is constant or a function call with constant arguments. +** Return and 0 if there are any variables. +** +** isInit is true when parsing from sqlite_schema. isInit is false when +** processing a new CREATE TABLE statement. When isInit is true, parameters +** (such as ? or $abc) in the expression are converted into NULL. When +** isInit is false, parameters raise an error. Parameters should not be +** allowed in a CREATE TABLE statement, but some legacy versions of SQLite +** allowed it, so we need to support it when reading sqlite_schema for +** backwards compatibility. +** +** If isInit is true, set EP_FromDDL on every TK_FUNCTION node. ** ** For the purposes of this function, a double-quoted string (ex: "abc") ** is considered a variable but a single-quoted string (ex: 'abc') is @@ -98941,7 +115697,7 @@ SQLITE_PRIVATE int sqlite3ExprIsConstantOrGroupBy(Parse *pParse, Expr *p, ExprLi */ SQLITE_PRIVATE int sqlite3ExprIsConstantOrFunction(Expr *p, u8 isInit){ assert( isInit==0 || isInit==1 ); - return exprIsConst(p, 4+isInit, 0); + return exprIsConst(0, p, 4+isInit); } #ifdef SQLITE_ENABLE_CURSOR_HINTS @@ -98967,10 +115723,14 @@ SQLITE_PRIVATE int sqlite3ExprContainsSubquery(Expr *p){ ** to fit in a 32-bit integer, return 1 and put the value of the integer ** in *pValue. If the expression is not an integer or if it is too big ** to fit in a signed 32-bit integer, return 0 and leave *pValue unchanged. +** +** If the pParse pointer is provided, then allow the expression p to be +** a parameter (TK_VARIABLE) that is bound to an integer. +** But if pParse is NULL, then p must be a pure integer literal. */ -SQLITE_PRIVATE int sqlite3ExprIsInteger(Expr *p, int *pValue){ +SQLITE_PRIVATE int sqlite3ExprIsInteger(const Expr *p, int *pValue, Parse *pParse){ int rc = 0; - if( p==0 ) return 0; /* Can only happen following on OOM */ + if( NEVER(p==0) ) return 0; /* Used to only happen following on OOM */ /* If an expression is an integer literal that fits in a signed 32-bit ** integer, then the EP_IntValue flag will have already been set */ @@ -98983,18 +115743,38 @@ SQLITE_PRIVATE int sqlite3ExprIsInteger(Expr *p, int *pValue){ } switch( p->op ){ case TK_UPLUS: { - rc = sqlite3ExprIsInteger(p->pLeft, pValue); + rc = sqlite3ExprIsInteger(p->pLeft, pValue, 0); break; } case TK_UMINUS: { - int v; - if( sqlite3ExprIsInteger(p->pLeft, &v) ){ - assert( v!=(-2147483647-1) ); + int v = 0; + if( sqlite3ExprIsInteger(p->pLeft, &v, 0) ){ + assert( ((unsigned int)v)!=0x80000000 ); *pValue = -v; rc = 1; } break; } + case TK_VARIABLE: { + sqlite3_value *pVal; + if( pParse==0 ) break; + if( NEVER(pParse->pVdbe==0) ) break; + if( (pParse->db->flags & SQLITE_EnableQPSG)!=0 ) break; + sqlite3VdbeSetVarmask(pParse->pVdbe, p->iColumn); + pVal = sqlite3VdbeGetBoundValue(pParse->pReprepare, p->iColumn, + SQLITE_AFF_BLOB); + if( pVal ){ + if( sqlite3_value_type(pVal)==SQLITE_INTEGER ){ + sqlite3_int64 vv = sqlite3_value_int64(pVal); + if( vv == (vv & 0x7fffffff) ){ /* non-negative numbers only */ + *pValue = (int)vv; + rc = 1; + } + } + sqlite3ValueFree(pVal); + } + break; + } default: break; } return rc; @@ -99004,7 +115784,7 @@ SQLITE_PRIVATE int sqlite3ExprIsInteger(Expr *p, int *pValue){ ** Return FALSE if there is no chance that the expression can be NULL. ** ** If the expression might be NULL or if the expression is too complex -** to tell return TRUE. +** to tell return TRUE. ** ** This routine is used as an optimization, to skip OP_IsNull opcodes ** when we know that a value cannot be NULL. Hence, a false positive @@ -99016,8 +115796,10 @@ SQLITE_PRIVATE int sqlite3ExprIsInteger(Expr *p, int *pValue){ */ SQLITE_PRIVATE int sqlite3ExprCanBeNull(const Expr *p){ u8 op; + assert( p!=0 ); while( p->op==TK_UPLUS || p->op==TK_UMINUS ){ p = p->pLeft; + assert( p!=0 ); } op = p->op; if( op==TK_REGISTER ) op = p->op2; @@ -99028,9 +115810,16 @@ SQLITE_PRIVATE int sqlite3ExprCanBeNull(const Expr *p){ case TK_BLOB: return 0; case TK_COLUMN: - return ExprHasProperty(p, EP_CanBeNull) || - p->y.pTab==0 || /* Reference to column of index on expression */ - (p->iColumn>=0 && p->y.pTab->aCol[p->iColumn].notNull==0); + assert( ExprUseYTab(p) ); + return ExprHasProperty(p, EP_CanBeNull) + || NEVER(p->y.pTab==0) /* Reference to column of index on expr */ +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + || (p->iColumn==XN_ROWID && IsView(p->y.pTab)) +#endif + || (p->iColumn>=0 + && p->y.pTab->aCol!=0 /* Possible due to prior error */ + && ALWAYS(p->iColumny.pTab->nCol) + && p->y.pTab->aCol[p->iColumn].notNull==0); default: return 1; } @@ -99048,27 +115837,30 @@ SQLITE_PRIVATE int sqlite3ExprCanBeNull(const Expr *p){ */ SQLITE_PRIVATE int sqlite3ExprNeedsNoAffinityChange(const Expr *p, char aff){ u8 op; + int unaryMinus = 0; if( aff==SQLITE_AFF_BLOB ) return 1; - while( p->op==TK_UPLUS || p->op==TK_UMINUS ){ p = p->pLeft; } + while( p->op==TK_UPLUS || p->op==TK_UMINUS ){ + if( p->op==TK_UMINUS ) unaryMinus = 1; + p = p->pLeft; + } op = p->op; if( op==TK_REGISTER ) op = p->op2; switch( op ){ case TK_INTEGER: { - return aff==SQLITE_AFF_INTEGER || aff==SQLITE_AFF_NUMERIC; + return aff>=SQLITE_AFF_NUMERIC; } case TK_FLOAT: { - return aff==SQLITE_AFF_REAL || aff==SQLITE_AFF_NUMERIC; + return aff>=SQLITE_AFF_NUMERIC; } case TK_STRING: { - return aff==SQLITE_AFF_TEXT; + return !unaryMinus && aff==SQLITE_AFF_TEXT; } case TK_BLOB: { - return 1; + return !unaryMinus; } case TK_COLUMN: { assert( p->iTable>=0 ); /* p cannot be part of a CHECK constraint */ - return p->iColumn<0 - && (aff==SQLITE_AFF_INTEGER || aff==SQLITE_AFF_NUMERIC); + return aff>=SQLITE_AFF_NUMERIC && p->iColumn<0; } default: { return 0; @@ -99087,20 +115879,35 @@ SQLITE_PRIVATE int sqlite3IsRowid(const char *z){ } /* -** pX is the RHS of an IN operator. If pX is a SELECT statement +** Return a pointer to a buffer containing a usable rowid alias for table +** pTab. An alias is usable if there is not an explicit user-defined column +** of the same name. +*/ +SQLITE_PRIVATE const char *sqlite3RowidAlias(Table *pTab){ + const char *azOpt[] = {"_ROWID_", "ROWID", "OID"}; + int ii; + assert( VisibleRowid(pTab) ); + for(ii=0; iix.pSelect; if( p->pPrior ) return 0; /* Not a compound SELECT */ @@ -99115,10 +115922,10 @@ static Select *isCandidateForInOpt(Expr *pX){ pSrc = p->pSrc; assert( pSrc!=0 ); if( pSrc->nSrc!=1 ) return 0; /* Single term in FROM clause */ - if( pSrc->a[0].pSelect ) return 0; /* FROM is not a subquery or view */ - pTab = pSrc->a[0].pTab; + if( pSrc->a[0].fg.isSubquery) return 0;/* FROM is not a subquery or view */ + pTab = pSrc->a[0].pSTab; assert( pTab!=0 ); - assert( pTab->pSelect==0 ); /* FROM clause is not a view */ + assert( !IsView(pTab) ); /* FROM clause is not a view */ if( IsVirtual(pTab) ) return 0; /* FROM clause not a virtual table */ pEList = p->pEList; assert( pEList!=0 ); @@ -99153,16 +115960,16 @@ static void sqlite3SetHasNullFlag(Vdbe *v, int iCur, int regHasNull){ #ifndef SQLITE_OMIT_SUBQUERY /* -** The argument is an IN operator with a list (not a subquery) on the +** The argument is an IN operator with a list (not a subquery) on the ** right-hand side. Return TRUE if that list is constant. */ -static int sqlite3InRhsIsConstant(Expr *pIn){ +static int sqlite3InRhsIsConstant(Parse *pParse, Expr *pIn){ Expr *pLHS; int res; assert( !ExprHasProperty(pIn, EP_xIsSelect) ); pLHS = pIn->pLeft; pIn->pLeft = 0; - res = sqlite3ExprIsConstant(pIn); + res = sqlite3ExprIsConstant(pParse, pIn); pIn->pLeft = pLHS; return res; } @@ -99178,7 +115985,7 @@ static int sqlite3InRhsIsConstant(Expr *pIn){ ** all members of the RHS set, skipping duplicates. ** ** A cursor is opened on the b-tree object that is the RHS of the IN operator -** and pX->iTable is set to the index of that cursor. +** and the *piTab parameter is set to the index of that cursor. ** ** The returned value of this function indicates the b-tree type, as follows: ** @@ -99186,7 +115993,7 @@ static int sqlite3InRhsIsConstant(Expr *pIn){ ** IN_INDEX_INDEX_ASC - The cursor was opened on an ascending index. ** IN_INDEX_INDEX_DESC - The cursor was opened on a descending index. ** IN_INDEX_EPH - The cursor was opened on a specially created and -** populated epheremal table. +** populated ephemeral table. ** IN_INDEX_NOOP - No cursor was allocated. The IN operator must be ** implemented as a sequence of comparisons. ** @@ -99198,7 +116005,10 @@ static int sqlite3InRhsIsConstant(Expr *pIn){ ** If the RHS of the IN operator is a list or a more complex subquery, then ** an ephemeral table might need to be generated from the RHS and then ** pX->iTable made to point to the ephemeral table instead of an -** existing table. +** existing table. In this case, the creation and initialization of the +** ephemeral table might be put inside of a subroutine, the EP_Subrtn flag +** will be set on pX and the pX->y.sub fields will be set to show where +** the subroutine is coded. ** ** The inFlags parameter must contain, at a minimum, one of the bits ** IN_INDEX_MEMBERSHIP or IN_INDEX_LOOP but not both. If inFlags contains @@ -99208,13 +116018,13 @@ static int sqlite3InRhsIsConstant(Expr *pIn){ ** ** When IN_INDEX_LOOP is used (and the b-tree will be used to iterate ** through the set members) then the b-tree must not contain duplicates. -** An epheremal table will be created unless the selected columns are guaranteed +** An ephemeral table will be created unless the selected columns are guaranteed ** to be unique - either because it is an INTEGER PRIMARY KEY or due to ** a UNIQUE constraint or index. ** -** When IN_INDEX_MEMBERSHIP is used (and the b-tree will be used -** for fast set membership tests) then an epheremal table must -** be used unless is a single INTEGER PRIMARY KEY column or an +** When IN_INDEX_MEMBERSHIP is used (and the b-tree will be used +** for fast set membership tests) then an ephemeral table must +** be used unless is a single INTEGER PRIMARY KEY column or an ** index can be found with the specified as its left-most. ** ** If the IN_INDEX_NOOP_OK and IN_INDEX_MEMBERSHIP are both set and @@ -99226,7 +116036,7 @@ static int sqlite3InRhsIsConstant(Expr *pIn){ ** ** When the b-tree is being used for membership tests, the calling function ** might need to know whether or not the RHS side of the IN operator -** contains a NULL. If prRhsHasNull is not a NULL pointer and +** contains a NULL. If prRhsHasNull is not a NULL pointer and ** if there is any chance that the (...) might contain a NULL value at ** runtime, then a register is allocated and the register number written ** to *prRhsHasNull. If there is no chance that the (...) contains a @@ -99251,7 +116061,7 @@ static int sqlite3InRhsIsConstant(Expr *pIn){ #ifndef SQLITE_OMIT_SUBQUERY SQLITE_PRIVATE int sqlite3FindInIndex( Parse *pParse, /* Parsing context */ - Expr *pX, /* The right-hand side (RHS) of the IN operator */ + Expr *pX, /* The IN expression */ u32 inFlags, /* IN_INDEX_LOOP, _MEMBERSHIP, and/or _NOOP_OK */ int *prRhsHasNull, /* Register holding NULL status. See notes */ int *aiMap, /* Mapping from Index fields to RHS fields */ @@ -99259,19 +116069,20 @@ SQLITE_PRIVATE int sqlite3FindInIndex( ){ Select *p; /* SELECT to the right of IN operator */ int eType = 0; /* Type of RHS table. IN_INDEX_* */ - int iTab = pParse->nTab++; /* Cursor of the RHS table */ + int iTab; /* Cursor of the RHS table */ int mustBeUnique; /* True if RHS must be unique */ Vdbe *v = sqlite3GetVdbe(pParse); /* Virtual machine being coded */ assert( pX->op==TK_IN ); mustBeUnique = (inFlags & IN_INDEX_LOOP)!=0; + iTab = pParse->nTab++; - /* If the RHS of this IN(...) operator is a SELECT, and if it matters + /* If the RHS of this IN(...) operator is a SELECT, and if it matters ** whether or not the SELECT result contains NULL values, check whether - ** or not NULL is actually possible (it may not be, for example, due + ** or not NULL is actually possible (it may not be, for example, due ** to NOT NULL constraints in the schema). If no NULL values are possible, ** set prRhsHasNull to 0 before continuing. */ - if( prRhsHasNull && (pX->flags & EP_xIsSelect) ){ + if( prRhsHasNull && ExprUseXSelect(pX) ){ int i; ExprList *pEList = pX->x.pSelect->pEList; for(i=0; inExpr; i++){ @@ -99283,22 +116094,23 @@ SQLITE_PRIVATE int sqlite3FindInIndex( } /* Check to see if an existing table or index can be used to - ** satisfy the query. This is preferable to generating a new + ** satisfy the query. This is preferable to generating a new ** ephemeral table. */ if( pParse->nErr==0 && (p = isCandidateForInOpt(pX))!=0 ){ sqlite3 *db = pParse->db; /* Database connection */ Table *pTab; /* Table . */ - i16 iDb; /* Database idx for pTab */ + int iDb; /* Database idx for pTab */ ExprList *pEList = p->pEList; int nExpr = pEList->nExpr; assert( p->pEList!=0 ); /* Because of isCandidateForInOpt(p) */ assert( p->pEList->a[0].pExpr!=0 ); /* Because of isCandidateForInOpt(p) */ assert( p->pSrc!=0 ); /* Because of isCandidateForInOpt(p) */ - pTab = p->pSrc->a[0].pTab; + pTab = p->pSrc->a[0].pSTab; /* Code an OP_Transaction and OP_TableLock for
                  . */ iDb = sqlite3SchemaToIndex(db, pTab->pSchema); + assert( iDb>=0 && iDbtnum, 0, pTab->zName); @@ -99318,7 +116130,7 @@ SQLITE_PRIVATE int sqlite3FindInIndex( int affinity_ok = 1; int i; - /* Check that the affinity that will be used to perform each + /* Check that the affinity that will be used to perform each ** comparison is the same as the affinity of each column in table ** on the RHS of the IN operator. If it not, it is not possible to ** use any index of the RHS table. */ @@ -99363,15 +116175,14 @@ SQLITE_PRIVATE int sqlite3FindInIndex( continue; /* This index is not unique over the IN RHS columns */ } } - + colUsed = 0; /* Columns of index used so far */ for(i=0; ipLeft, i); Expr *pRhs = pEList->a[i].pExpr; CollSeq *pReq = sqlite3BinaryCompareCollSeq(pParse, pLhs, pRhs); int j; - - assert( pReq!=0 || pRhs->iColumn==XN_ROWID || pParse->nErr ); + for(j=0; jaiColumn[j]!=pRhs->iColumn ) continue; assert( pIdx->azColl[j] ); @@ -99386,7 +116197,8 @@ SQLITE_PRIVATE int sqlite3FindInIndex( colUsed |= mCol; if( aiMap ) aiMap[i] = j; } - + + assert( nExpr>0 && nExprzName)); assert( IN_INDEX_INDEX_DESC == IN_INDEX_INDEX_ASC+1 ); eType = IN_INDEX_INDEX_ASC + pIdx->aSortOrder[0]; - + if( prRhsHasNull ){ #ifdef SQLITE_ENABLE_COLUMN_USED_MASK i64 mask = (1<nMem; @@ -99426,9 +116238,11 @@ SQLITE_PRIVATE int sqlite3FindInIndex( */ if( eType==0 && (inFlags & IN_INDEX_NOOP_OK) - && !ExprHasProperty(pX, EP_xIsSelect) - && (!sqlite3InRhsIsConstant(pX) || pX->x.pList->nExpr<=2) + && ExprUseXList(pX) + && (!sqlite3InRhsIsConstant(pParse,pX) || pX->x.pList->nExpr<=2) ){ + pParse->nTab--; /* Back out the allocation of the unused cursor */ + iTab = -1; /* Cursor is not allocated */ eType = IN_INDEX_NOOP; } @@ -99438,6 +116252,7 @@ SQLITE_PRIVATE int sqlite3FindInIndex( */ u32 savedNQueryLoop = pParse->nQueryLoop; int rMayHaveNull = 0; + int bloomOk = (inFlags & IN_INDEX_MEMBERSHIP)!=0; eType = IN_INDEX_EPH; if( inFlags & IN_INDEX_LOOP ){ pParse->nQueryLoop = 0; @@ -99445,7 +116260,13 @@ SQLITE_PRIVATE int sqlite3FindInIndex( *prRhsHasNull = rMayHaveNull = ++pParse->nMem; } assert( pX->op==TK_IN ); - sqlite3CodeRhsOfIN(pParse, pX, iTab); + if( !bloomOk + && ExprUseXSelect(pX) + && (pX->x.pSelect->selFlags & SF_ClonedRhsIn)!=0 + ){ + bloomOk = 1; + } + sqlite3CodeRhsOfIN(pParse, pX, iTab, bloomOk); if( rMayHaveNull ){ sqlite3SetHasNullFlag(v, iTab, rMayHaveNull); } @@ -99464,21 +116285,21 @@ SQLITE_PRIVATE int sqlite3FindInIndex( #ifndef SQLITE_OMIT_SUBQUERY /* -** Argument pExpr is an (?, ?...) IN(...) expression. This -** function allocates and returns a nul-terminated string containing +** Argument pExpr is an (?, ?...) IN(...) expression. This +** function allocates and returns a nul-terminated string containing ** the affinities to be used for each column of the comparison. ** ** It is the responsibility of the caller to ensure that the returned ** string is eventually freed using sqlite3DbFree(). */ -static char *exprINAffinity(Parse *pParse, Expr *pExpr){ +static char *exprINAffinity(Parse *pParse, const Expr *pExpr){ Expr *pLeft = pExpr->pLeft; int nVal = sqlite3ExprVectorSize(pLeft); - Select *pSelect = (pExpr->flags & EP_xIsSelect) ? pExpr->x.pSelect : 0; + Select *pSelect = ExprUseXSelect(pExpr) ? pExpr->x.pSelect : 0; char *zRet; assert( pExpr->op==TK_IN ); - zRet = sqlite3DbMallocRaw(pParse->db, nVal+1); + zRet = sqlite3DbMallocRaw(pParse->db, 1+(i64)nVal); if( zRet ){ int i; for(i=0; inErr==0 ){ + const char *zFmt = "sub-select returns %d columns - expected %d"; + sqlite3ErrorMsg(pParse, zFmt, nActual, nExpect); + } } #endif /* ** Expression pExpr is a vector that has been used in a context where -** it is not permitted. If pExpr is a sub-select vector, this routine +** it is not permitted. If pExpr is a sub-select vector, this routine ** loads the Parse object with a message of the form: ** ** "sub-select returns N columns - expected 1" @@ -99519,10 +116342,10 @@ SQLITE_PRIVATE void sqlite3SubselectError(Parse *pParse, int nActual, int nExpec ** Or, if it is a regular scalar vector: ** ** "row value misused" -*/ +*/ SQLITE_PRIVATE void sqlite3VectorErrorMsg(Parse *pParse, Expr *pExpr){ #ifndef SQLITE_OMIT_SUBQUERY - if( pExpr->flags & EP_xIsSelect ){ + if( ExprUseXSelect(pExpr) ){ sqlite3SubselectError(pParse, pExpr->x.pSelect->pEList->nExpr, 1); }else #endif @@ -99531,6 +116354,50 @@ SQLITE_PRIVATE void sqlite3VectorErrorMsg(Parse *pParse, Expr *pExpr){ } } +#ifndef SQLITE_OMIT_SUBQUERY +/* +** Scan all previously generated bytecode looking for an OP_BeginSubrtn +** that is compatible with pExpr. If found, add the y.sub values +** to pExpr and return true. If not found, return false. +*/ +static int findCompatibleInRhsSubrtn( + Parse *pParse, /* Parsing context */ + Expr *pExpr, /* IN operator with RHS that we want to reuse */ + SubrtnSig *pNewSig /* Signature for the IN operator */ +){ + VdbeOp *pOp, *pEnd; + SubrtnSig *pSig; + Vdbe *v; + + if( pNewSig==0 ) return 0; + if( (pParse->mSubrtnSig & (1<<(pNewSig->selId&7)))==0 ) return 0; + assert( pExpr->op==TK_IN ); + assert( !ExprUseYSub(pExpr) ); + assert( ExprUseXSelect(pExpr) ); + assert( pExpr->x.pSelect!=0 ); + assert( (pExpr->x.pSelect->selFlags & SF_All)==0 ); + v = pParse->pVdbe; + assert( v!=0 ); + pOp = sqlite3VdbeGetOp(v, 1); + pEnd = sqlite3VdbeGetLastOp(v); + for(; pOpp4type!=P4_SUBRTNSIG ) continue; + assert( pOp->opcode==OP_BeginSubrtn ); + pSig = pOp->p4.pSubrtnSig; + assert( pSig!=0 ); + if( !pSig->bComplete ) continue; + if( pNewSig->selId!=pSig->selId ) continue; + if( strcmp(pNewSig->zAff,pSig->zAff)!=0 ) continue; + pExpr->y.sub.iAddr = pSig->iAddr; + pExpr->y.sub.regReturn = pSig->regReturn; + pExpr->iTable = pSig->iTable; + ExprSetProperty(pExpr, EP_Subrtn); + return 1; + } + return 0; +} +#endif /* SQLITE_OMIT_SUBQUERY */ + #ifndef SQLITE_OMIT_SUBQUERY /* ** Generate code that will construct an ephemeral table containing all terms @@ -99541,7 +116408,7 @@ SQLITE_PRIVATE void sqlite3VectorErrorMsg(Parse *pParse, Expr *pExpr){ ** x IN (SELECT a FROM b) -- IN operator with subquery on the right ** ** The pExpr parameter is the IN operator. The cursor number for the -** constructed ephermeral table is returned. The first time the ephemeral +** constructed ephemeral table is returned. The first time the ephemeral ** table is computed, the cursor number is also stored in pExpr->iTable, ** however the cursor number returned might not be the same, as it might ** have been duplicated using OP_OpenDup. @@ -99557,7 +116424,8 @@ SQLITE_PRIVATE void sqlite3VectorErrorMsg(Parse *pParse, Expr *pExpr){ SQLITE_PRIVATE void sqlite3CodeRhsOfIN( Parse *pParse, /* Parsing context */ Expr *pExpr, /* The IN operator */ - int iTab /* Use this cursor number */ + int iTab, /* Use this cursor number */ + int allowBloom /* True to allow the use of a Bloom filter */ ){ int addrOnce = 0; /* Address of the OP_Once instruction at top */ int addr; /* Address of OP_OpenEphemeral instruction */ @@ -99565,6 +116433,7 @@ SQLITE_PRIVATE void sqlite3CodeRhsOfIN( KeyInfo *pKeyInfo = 0; /* Key information */ int nVal; /* Size of vector pLeft */ Vdbe *v; /* The prepared statement under construction */ + SubrtnSig *pSig = 0; /* Signature for this subroutine */ v = pParse->pVdbe; assert( v!=0 ); @@ -99580,30 +116449,60 @@ SQLITE_PRIVATE void sqlite3CodeRhsOfIN( ** and reuse it many names. */ if( !ExprHasProperty(pExpr, EP_VarSelect) && pParse->iSelfTab==0 ){ - /* Reuse of the RHS is allowed */ - /* If this routine has already been coded, but the previous code - ** might not have been invoked yet, so invoke it now as a subroutine. + /* Reuse of the RHS is allowed + ** + ** Compute a signature for the RHS of the IN operator to facility + ** finding and reusing prior instances of the same IN operator. */ - if( ExprHasProperty(pExpr, EP_Subrtn) ){ + assert( !ExprUseXSelect(pExpr) || pExpr->x.pSelect!=0 ); + if( ExprUseXSelect(pExpr) && (pExpr->x.pSelect->selFlags & SF_All)==0 ){ + pSig = sqlite3DbMallocRawNN(pParse->db, sizeof(pSig[0])); + if( pSig ){ + pSig->selId = pExpr->x.pSelect->selId; + pSig->zAff = exprINAffinity(pParse, pExpr); + } + } + + /* Check to see if there is a prior materialization of the RHS of + ** this IN operator. If there is, then make use of that prior + ** materialization rather than recomputing it. + */ + if( ExprHasProperty(pExpr, EP_Subrtn) + || findCompatibleInRhsSubrtn(pParse, pExpr, pSig) + ){ addrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); - if( ExprHasProperty(pExpr, EP_xIsSelect) ){ + if( ExprUseXSelect(pExpr) ){ ExplainQueryPlan((pParse, 0, "REUSE LIST SUBQUERY %d", pExpr->x.pSelect->selId)); } + assert( ExprUseYSub(pExpr) ); sqlite3VdbeAddOp2(v, OP_Gosub, pExpr->y.sub.regReturn, pExpr->y.sub.iAddr); + assert( iTab!=pExpr->iTable ); sqlite3VdbeAddOp2(v, OP_OpenDup, iTab, pExpr->iTable); sqlite3VdbeJumpHere(v, addrOnce); + if( pSig ){ + sqlite3DbFree(pParse->db, pSig->zAff); + sqlite3DbFree(pParse->db, pSig); + } return; } /* Begin coding the subroutine */ + assert( !ExprUseYWin(pExpr) ); ExprSetProperty(pExpr, EP_Subrtn); + assert( !ExprHasProperty(pExpr, EP_TokenOnly|EP_Reduced) ); pExpr->y.sub.regReturn = ++pParse->nMem; pExpr->y.sub.iAddr = - sqlite3VdbeAddOp2(v, OP_Integer, 0, pExpr->y.sub.regReturn) + 1; - VdbeComment((v, "return address")); - + sqlite3VdbeAddOp2(v, OP_BeginSubrtn, 0, pExpr->y.sub.regReturn) + 1; + if( pSig ){ + pSig->bComplete = 0; + pSig->iAddr = pExpr->y.sub.iAddr; + pSig->regReturn = pExpr->y.sub.regReturn; + pSig->iTable = iTab; + pParse->mSubrtnSig = 1 << (pSig->selId&7); + sqlite3VdbeChangeP4(v, -1, (const char*)pSig, P4_SUBRTNSIG); + } addrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); } @@ -99617,7 +116516,7 @@ SQLITE_PRIVATE void sqlite3CodeRhsOfIN( pExpr->iTable = iTab; addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pExpr->iTable, nVal); #ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS - if( ExprHasProperty(pExpr, EP_xIsSelect) ){ + if( ExprUseXSelect(pExpr) ){ VdbeComment((v, "Result of SELECT %u", pExpr->x.pSelect->selId)); }else{ VdbeComment((v, "RHS of IN operator")); @@ -99625,7 +116524,7 @@ SQLITE_PRIVATE void sqlite3CodeRhsOfIN( #endif pKeyInfo = sqlite3KeyInfoAlloc(pParse->db, nVal, 1); - if( ExprHasProperty(pExpr, EP_xIsSelect) ){ + if( ExprUseXSelect(pExpr) ){ /* Case 1: expr IN (SELECT ...) ** ** Generate code to write the results of the select into the temporary @@ -99640,19 +116539,42 @@ SQLITE_PRIVATE void sqlite3CodeRhsOfIN( /* If the LHS and RHS of the IN operator do not match, that ** error will have been caught long before we reach this point. */ if( ALWAYS(pEList->nExpr==nVal) ){ + Select *pCopy; SelectDest dest; int i; + int rc; + int addrBloom = 0; sqlite3SelectDestInit(&dest, SRT_Set, iTab); dest.zAffSdst = exprINAffinity(pParse, pExpr); pSelect->iLimit = 0; + if( addrOnce + && allowBloom + && OptimizationEnabled(pParse->db, SQLITE_BloomFilter) + ){ + int regBloom = ++pParse->nMem; + addrBloom = sqlite3VdbeAddOp2(v, OP_Blob, 10000, regBloom); + VdbeComment((v, "Bloom filter")); + dest.iSDParm2 = regBloom; + } testcase( pSelect->selFlags & SF_Distinct ); testcase( pKeyInfo==0 ); /* Caused by OOM in sqlite3KeyInfoAlloc() */ - if( sqlite3Select(pParse, pSelect, &dest) ){ - sqlite3DbFree(pParse->db, dest.zAffSdst); + pCopy = sqlite3SelectDup(pParse->db, pSelect, 0); + rc = pParse->db->mallocFailed ? 1 :sqlite3Select(pParse, pCopy, &dest); + sqlite3SelectDelete(pParse->db, pCopy); + sqlite3DbFree(pParse->db, dest.zAffSdst); + if( addrBloom ){ + /* Remember that location of the Bloom filter in the P3 operand + ** of the OP_Once that began this subroutine. tag-202407032019 */ + sqlite3VdbeGetOp(v, addrOnce)->p3 = dest.iSDParm2; + if( dest.iSDParm2==0 ){ + /* If the Bloom filter won't actually be used, keep it small */ + sqlite3VdbeGetOp(v, addrBloom)->p1 = 10; + } + } + if( rc ){ sqlite3KeyInfoUnref(pKeyInfo); return; } - sqlite3DbFree(pParse->db, dest.zAffSdst); assert( pKeyInfo!=0 ); /* OOM will cause exit after sqlite3Select() */ assert( pEList!=0 ); assert( pEList->nExpr>0 ); @@ -99676,10 +116598,12 @@ SQLITE_PRIVATE void sqlite3CodeRhsOfIN( int i; ExprList *pList = pExpr->x.pList; struct ExprList_item *pItem; - int r1, r2, r3; + int r1, r2; affinity = sqlite3ExprAffinity(pLeft); - if( !affinity ){ + if( affinity<=SQLITE_AFF_NONE ){ affinity = SQLITE_AFF_BLOB; + }else if( affinity==SQLITE_AFF_REAL ){ + affinity = SQLITE_AFF_NUMERIC; } if( pKeyInfo ){ assert( sqlite3KeyInfoIsWriteable(pKeyInfo) ); @@ -99697,27 +116621,36 @@ SQLITE_PRIVATE void sqlite3CodeRhsOfIN( ** this code only executes once. Because for a non-constant ** expression we need to rerun this code each time. */ - if( addrOnce && !sqlite3ExprIsConstant(pE2) ){ + if( addrOnce && !sqlite3ExprIsConstant(pParse, pE2) ){ + sqlite3VdbeChangeToNoop(v, addrOnce-1); sqlite3VdbeChangeToNoop(v, addrOnce); + ExprClearProperty(pExpr, EP_Subrtn); addrOnce = 0; } /* Evaluate the expression and insert it into the temp table */ - r3 = sqlite3ExprCodeTarget(pParse, pE2, r1); - sqlite3VdbeAddOp4(v, OP_MakeRecord, r3, 1, r2, &affinity, 1); - sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iTab, r2, r3, 1); + sqlite3ExprCode(pParse, pE2, r1); + sqlite3VdbeAddOp4(v, OP_MakeRecord, r1, 1, r2, &affinity, 1); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iTab, r2, r1, 1); } sqlite3ReleaseTempReg(pParse, r1); sqlite3ReleaseTempReg(pParse, r2); } + if( pSig ) pSig->bComplete = 1; if( pKeyInfo ){ sqlite3VdbeChangeP4(v, addr, (void *)pKeyInfo, P4_KEYINFO); } if( addrOnce ){ + sqlite3VdbeAddOp1(v, OP_NullRow, iTab); sqlite3VdbeJumpHere(v, addrOnce); /* Subroutine return */ - sqlite3VdbeAddOp1(v, OP_Return, pExpr->y.sub.regReturn); - sqlite3VdbeChangeP1(v, pExpr->y.sub.iAddr-1, sqlite3VdbeCurrentAddr(v)-1); + assert( ExprUseYSub(pExpr) ); + assert( sqlite3VdbeGetOp(v,pExpr->y.sub.iAddr-1)->opcode==OP_BeginSubrtn + || pParse->nErr ); + sqlite3VdbeAddOp3(v, OP_Return, pExpr->y.sub.regReturn, + pExpr->y.sub.iAddr, 1); + VdbeCoverage(v); + sqlite3ClearTempRegCache(pParse); } } #endif /* SQLITE_OMIT_SUBQUERY */ @@ -99731,7 +116664,7 @@ SQLITE_PRIVATE void sqlite3CodeRhsOfIN( ** ** The pExpr parameter is the SELECT or EXISTS operator to be coded. ** -** The register that holds the result. For a multi-column SELECT, +** Return the register that holds the result. For a multi-column SELECT, ** the result is stored in a contiguous array of registers and the ** return value is the register of the left-most result column. ** Return 0 if an error occurs. @@ -99744,15 +116677,37 @@ SQLITE_PRIVATE int sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){ SelectDest dest; /* How to deal with SELECT result */ int nReg; /* Registers to allocate */ Expr *pLimit; /* New limit expression */ +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + int addrExplain; /* Address of OP_Explain instruction */ +#endif Vdbe *v = pParse->pVdbe; assert( v!=0 ); + if( pParse->nErr ) return 0; testcase( pExpr->op==TK_EXISTS ); testcase( pExpr->op==TK_SELECT ); assert( pExpr->op==TK_EXISTS || pExpr->op==TK_SELECT ); - assert( ExprHasProperty(pExpr, EP_xIsSelect) ); + assert( ExprUseXSelect(pExpr) ); pSel = pExpr->x.pSelect; + /* If this routine has already been coded, then invoke it as a + ** subroutine. */ + if( ExprHasProperty(pExpr, EP_Subrtn) ){ + ExplainQueryPlan((pParse, 0, "REUSE SUBQUERY %d", pSel->selId)); + assert( ExprUseYSub(pExpr) ); + sqlite3VdbeAddOp2(v, OP_Gosub, pExpr->y.sub.regReturn, + pExpr->y.sub.iAddr); + return pExpr->iTable; + } + + /* Begin coding the subroutine */ + assert( !ExprUseYWin(pExpr) ); + assert( !ExprHasProperty(pExpr, EP_Reduced|EP_TokenOnly) ); + ExprSetProperty(pExpr, EP_Subrtn); + pExpr->y.sub.regReturn = ++pParse->nMem; + pExpr->y.sub.iAddr = + sqlite3VdbeAddOp2(v, OP_BeginSubrtn, 0, pExpr->y.sub.regReturn) + 1; + /* The evaluation of the EXISTS/SELECT must be repeated every time it ** is encountered if any of the following is true: ** @@ -99764,25 +116719,9 @@ SQLITE_PRIVATE int sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){ ** save the results, and reuse the same result on subsequent invocations. */ if( !ExprHasProperty(pExpr, EP_VarSelect) ){ - /* If this routine has already been coded, then invoke it as a - ** subroutine. */ - if( ExprHasProperty(pExpr, EP_Subrtn) ){ - ExplainQueryPlan((pParse, 0, "REUSE SUBQUERY %d", pSel->selId)); - sqlite3VdbeAddOp2(v, OP_Gosub, pExpr->y.sub.regReturn, - pExpr->y.sub.iAddr); - return pExpr->iTable; - } - - /* Begin coding the subroutine */ - ExprSetProperty(pExpr, EP_Subrtn); - pExpr->y.sub.regReturn = ++pParse->nMem; - pExpr->y.sub.iAddr = - sqlite3VdbeAddOp2(v, OP_Integer, 0, pExpr->y.sub.regReturn) + 1; - VdbeComment((v, "return address")); - addrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); } - + /* For a SELECT, generate code to put the values for all columns of ** the first row into an array of registers and return the index of ** the first register. @@ -99790,60 +116729,97 @@ SQLITE_PRIVATE int sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){ ** If this is an EXISTS, write an integer 0 (not exists) or 1 (exists) ** into a register and return that register number. ** - ** In both cases, the query is augmented with "LIMIT 1". Any + ** In both cases, the query is augmented with "LIMIT 1". Any ** preexisting limit is discarded in place of the new LIMIT 1. */ - ExplainQueryPlan((pParse, 1, "%sSCALAR SUBQUERY %d", + ExplainQueryPlan2(addrExplain, (pParse, 1, "%sSCALAR SUBQUERY %d", addrOnce?"":"CORRELATED ", pSel->selId)); + sqlite3VdbeScanStatusCounters(v, addrExplain, addrExplain, -1); nReg = pExpr->op==TK_SELECT ? pSel->pEList->nExpr : 1; sqlite3SelectDestInit(&dest, 0, pParse->nMem+1); pParse->nMem += nReg; if( pExpr->op==TK_SELECT ){ dest.eDest = SRT_Mem; - dest.iSdst = dest.iSDParm; + if( (pSel->selFlags&SF_Distinct) && pSel->pLimit && pSel->pLimit->pRight ){ + /* If there is both a DISTINCT and an OFFSET clause, then allocate + ** a separate dest.iSdst array for sqlite3Select() and other + ** routines to populate. In this case results will be copied over + ** into the dest.iSDParm array only after OFFSET processing. This + ** ensures that in the case where OFFSET excludes all rows, the + ** dest.iSDParm array is not left populated with the contents of the + ** last row visited - it should be all NULLs if all rows were + ** excluded by OFFSET. */ + dest.iSdst = pParse->nMem+1; + pParse->nMem += nReg; + }else{ + dest.iSdst = dest.iSDParm; + } dest.nSdst = nReg; - sqlite3VdbeAddOp3(v, OP_Null, 0, dest.iSDParm, dest.iSDParm+nReg-1); + sqlite3VdbeAddOp3(v, OP_Null, 0, dest.iSDParm, pParse->nMem); VdbeComment((v, "Init subquery result")); }else{ dest.eDest = SRT_Exists; sqlite3VdbeAddOp2(v, OP_Integer, 0, dest.iSDParm); VdbeComment((v, "Init EXISTS result")); } - pLimit = sqlite3ExprAlloc(pParse->db, TK_INTEGER,&sqlite3IntTokens[1], 0); if( pSel->pLimit ){ - sqlite3ExprDelete(pParse->db, pSel->pLimit->pLeft); - pSel->pLimit->pLeft = pLimit; + /* The subquery already has a limit. If the pre-existing limit X is + ** not already integer value 1 or 0, then make the new limit X<>0 so that + ** the new limit is either 1 or 0 */ + Expr *pLeft = pSel->pLimit->pLeft; + if( ExprHasProperty(pLeft, EP_IntValue)==0 + || (pLeft->u.iValue!=1 && pLeft->u.iValue!=0) + ){ + sqlite3 *db = pParse->db; + pLimit = sqlite3ExprInt32(db, 0); + if( pLimit ){ + pLimit->affExpr = SQLITE_AFF_NUMERIC; + pLimit = sqlite3PExpr(pParse, TK_NE, + sqlite3ExprDup(db, pLeft, 0), pLimit); + } + sqlite3ExprDeferredDelete(pParse, pLeft); + pSel->pLimit->pLeft = pLimit; + } }else{ + /* If there is no pre-existing limit add a limit of 1 */ + pLimit = sqlite3ExprInt32(pParse->db, 1); pSel->pLimit = sqlite3PExpr(pParse, TK_LIMIT, pLimit, 0); } pSel->iLimit = 0; if( sqlite3Select(pParse, pSel, &dest) ){ + pExpr->op2 = pExpr->op; + pExpr->op = TK_ERROR; return 0; } pExpr->iTable = rReg = dest.iSDParm; ExprSetVVAProperty(pExpr, EP_NoReduce); if( addrOnce ){ sqlite3VdbeJumpHere(v, addrOnce); - - /* Subroutine return */ - sqlite3VdbeAddOp1(v, OP_Return, pExpr->y.sub.regReturn); - sqlite3VdbeChangeP1(v, pExpr->y.sub.iAddr-1, sqlite3VdbeCurrentAddr(v)-1); } + sqlite3VdbeScanStatusRange(v, addrExplain, addrExplain, -1); + /* Subroutine return */ + assert( ExprUseYSub(pExpr) ); + assert( sqlite3VdbeGetOp(v,pExpr->y.sub.iAddr-1)->opcode==OP_BeginSubrtn + || pParse->nErr ); + sqlite3VdbeAddOp3(v, OP_Return, pExpr->y.sub.regReturn, + pExpr->y.sub.iAddr, 1); + VdbeCoverage(v); + sqlite3ClearTempRegCache(pParse); return rReg; } #endif /* SQLITE_OMIT_SUBQUERY */ #ifndef SQLITE_OMIT_SUBQUERY /* -** Expr pIn is an IN(...) expression. This function checks that the -** sub-select on the RHS of the IN() operator has the same number of -** columns as the vector on the LHS. Or, if the RHS of the IN() is not +** Expr pIn is an IN(...) expression. This function checks that the +** sub-select on the RHS of the IN() operator has the same number of +** columns as the vector on the LHS. Or, if the RHS of the IN() is not ** a sub-query, that the LHS is a vector of size 1. */ SQLITE_PRIVATE int sqlite3ExprCheckIN(Parse *pParse, Expr *pIn){ int nVector = sqlite3ExprVectorSize(pIn->pLeft); - if( (pIn->flags & EP_xIsSelect) ){ + if( ExprUseXSelect(pIn) && !pParse->db->mallocFailed ){ if( nVector!=pIn->x.pSelect->pEList->nExpr ){ sqlite3SubselectError(pParse, pIn->x.pSelect->pEList->nExpr, nVector); return 1; @@ -99863,18 +116839,18 @@ SQLITE_PRIVATE int sqlite3ExprCheckIN(Parse *pParse, Expr *pIn){ ** x IN (SELECT ...) ** x IN (value, value, ...) ** -** The left-hand side (LHS) is a scalar or vector expression. The +** The left-hand side (LHS) is a scalar or vector expression. The ** right-hand side (RHS) is an array of zero or more scalar values, or a ** subquery. If the RHS is a subquery, the number of result columns must ** match the number of columns in the vector on the LHS. If the RHS is -** a list of values, the LHS must be a scalar. +** a list of values, the LHS must be a scalar. ** ** The IN operator is true if the LHS value is contained within the RHS. -** The result is false if the LHS is definitely not in the RHS. The -** result is NULL if the presence of the LHS in the RHS cannot be +** The result is false if the LHS is definitely not in the RHS. The +** result is NULL if the presence of the LHS in the RHS cannot be ** determined due to NULLs. ** -** This routine generates code that jumps to destIfFalse if the LHS is not +** This routine generates code that jumps to destIfFalse if the LHS is not ** contained within the RHS. If due to NULLs we cannot determine if the LHS ** is contained in the RHS then jump to destIfNull. If the LHS is contained ** within the RHS then fall through. @@ -99891,7 +116867,6 @@ static void sqlite3ExprCodeIN( int rRhsHasNull = 0; /* Register that is true if RHS contains NULL values */ int eType; /* Type of the RHS */ int rLhs; /* Register(s) holding the LHS values */ - int rLhsOrig; /* LHS values prior to reordering by aiMap[] */ Vdbe *v; /* Statement under construction */ int *aiMap = 0; /* Map from vector field to index column */ char *zAff = 0; /* Affinity string for comparisons */ @@ -99903,16 +116878,16 @@ static void sqlite3ExprCodeIN( int destStep6 = 0; /* Start of code for Step 6 */ int addrTruthOp; /* Address of opcode that determines the IN is true */ int destNotNull; /* Jump here if a comparison is not true in step 6 */ - int addrTop; /* Top of the step-6 loop */ + int addrTop; /* Top of the step-6 loop */ int iTab = 0; /* Index to use */ + u8 okConstFactor = pParse->okConstFactor; + assert( !ExprHasVVAProperty(pExpr,EP_Immutable) ); pLeft = pExpr->pLeft; if( sqlite3ExprCheckIN(pParse, pExpr) ) return; zAff = exprINAffinity(pParse, pExpr); nVector = sqlite3ExprVectorSize(pExpr->pLeft); - aiMap = (int*)sqlite3DbMallocZero( - pParse->db, nVector*(sizeof(int) + sizeof(char)) + 1 - ); + aiMap = (int*)sqlite3DbMallocZero(pParse->db, nVector*sizeof(int)); if( pParse->db->mallocFailed ) goto sqlite3ExprCodeIN_oom_error; /* Attempt to compute the RHS. After this step, if anything other than @@ -99928,7 +116903,7 @@ static void sqlite3ExprCodeIN( aiMap, &iTab); assert( pParse->nErr || nVector==1 || eType==IN_INDEX_EPH - || eType==IN_INDEX_INDEX_ASC || eType==IN_INDEX_INDEX_DESC + || eType==IN_INDEX_INDEX_ASC || eType==IN_INDEX_INDEX_DESC ); #ifdef SQLITE_DEBUG /* Confirm that aiMap[] contains nVector integer values between 0 and @@ -99940,27 +116915,22 @@ static void sqlite3ExprCodeIN( } #endif - /* Code the LHS, the from " IN (...)". If the LHS is a - ** vector, then it is stored in an array of nVector registers starting + /* Code the LHS, the from " IN (...)". If the LHS is a + ** vector, then it is stored in an array of nVector registers starting ** at r1. ** ** sqlite3FindInIndex() might have reordered the fields of the LHS vector ** so that the fields are in the same order as an existing index. The ** aiMap[] array contains a mapping from the original LHS field order to ** the field order that matches the RHS index. - */ - rLhsOrig = exprCodeVector(pParse, pLeft, &iDummy); - for(i=0; iokConstFactor==okConstFactor ); + pParse->okConstFactor = 0; + rLhs = exprCodeVector(pParse, pLeft, &iDummy); + pParse->okConstFactor = okConstFactor; /* If sqlite3FindInIndex() did not find or create an index that is ** suitable for evaluating the IN operator, then evaluate using a @@ -99969,13 +116939,16 @@ static void sqlite3ExprCodeIN( ** This is step (1) in the in-operator.md optimized algorithm. */ if( eType==IN_INDEX_NOOP ){ - ExprList *pList = pExpr->x.pList; - CollSeq *pColl = sqlite3ExprCollSeq(pParse, pExpr->pLeft); + ExprList *pList; + CollSeq *pColl; int labelOk = sqlite3VdbeMakeLabel(pParse); int r2, regToFree; int regCkNull = 0; int ii; - assert( !ExprHasProperty(pExpr, EP_xIsSelect) ); + assert( nVector==1 ); + assert( ExprUseXList(pExpr) ); + pList = pExpr->x.pList; + pColl = sqlite3ExprCollSeq(pParse, pExpr->pLeft); if( destIfNull!=destIfFalse ){ regCkNull = sqlite3GetTempReg(pParse); sqlite3VdbeAddOp3(v, OP_BitAnd, rLhs, rLhs, regCkNull); @@ -99985,19 +116958,25 @@ static void sqlite3ExprCodeIN( if( regCkNull && sqlite3ExprCanBeNull(pList->a[ii].pExpr) ){ sqlite3VdbeAddOp3(v, OP_BitAnd, regCkNull, r2, regCkNull); } + sqlite3ReleaseTempReg(pParse, regToFree); if( iinExpr-1 || destIfNull!=destIfFalse ){ - sqlite3VdbeAddOp4(v, OP_Eq, rLhs, labelOk, r2, + int op = rLhs!=r2 ? OP_Eq : OP_NotNull; + sqlite3VdbeAddOp4(v, op, rLhs, labelOk, r2, (void*)pColl, P4_COLLSEQ); - VdbeCoverageIf(v, iinExpr-1); - VdbeCoverageIf(v, ii==pList->nExpr-1); + VdbeCoverageIf(v, iinExpr-1 && op==OP_Eq); + VdbeCoverageIf(v, ii==pList->nExpr-1 && op==OP_Eq); + VdbeCoverageIf(v, iinExpr-1 && op==OP_NotNull); + VdbeCoverageIf(v, ii==pList->nExpr-1 && op==OP_NotNull); sqlite3VdbeChangeP5(v, zAff[0]); }else{ + int op = rLhs!=r2 ? OP_Ne : OP_IsNull; assert( destIfNull==destIfFalse ); - sqlite3VdbeAddOp4(v, OP_Ne, rLhs, destIfFalse, r2, - (void*)pColl, P4_COLLSEQ); VdbeCoverage(v); + sqlite3VdbeAddOp4(v, op, rLhs, destIfFalse, r2, + (void*)pColl, P4_COLLSEQ); + VdbeCoverageIf(v, op==OP_Ne); + VdbeCoverageIf(v, op==OP_IsNull); sqlite3VdbeChangeP5(v, zAff[0] | SQLITE_JUMPIFNULL); } - sqlite3ReleaseTempReg(pParse, regToFree); } if( regCkNull ){ sqlite3VdbeAddOp2(v, OP_IsNull, regCkNull, destIfNull); VdbeCoverage(v); @@ -100008,6 +116987,26 @@ static void sqlite3ExprCodeIN( goto sqlite3ExprCodeIN_finished; } + if( eType!=IN_INDEX_ROWID ){ + /* If this IN operator will use an index, then the order of columns in the + ** vector might be different from the order in the index. In that case, + ** we need to reorder the LHS values to be in index order. Run Affinity + ** before reordering the columns, so that the affinity is correct. + */ + sqlite3VdbeAddOp4(v, OP_Affinity, rLhs, nVector, 0, zAff, nVector); + for(i=0; ipLeft, i); + if( pParse->nErr ) goto sqlite3ExprCodeIN_oom_error; if( sqlite3ExprCanBeNull(p) ){ - sqlite3VdbeAddOp2(v, OP_IsNull, rLhs+i, destStep2); + sqlite3VdbeAddOp2(v, OP_IsNull, rLhs+aiMap[i], destStep2); VdbeCoverage(v); } } @@ -100033,13 +117033,23 @@ static void sqlite3ExprCodeIN( /* In this case, the RHS is the ROWID of table b-tree and so we also ** know that the RHS is non-NULL. Hence, we combine steps 3 and 4 ** into a single opcode. */ + assert( nVector==1 ); sqlite3VdbeAddOp3(v, OP_SeekRowid, iTab, destIfFalse, rLhs); VdbeCoverage(v); addrTruthOp = sqlite3VdbeAddOp0(v, OP_Goto); /* Return True */ }else{ - sqlite3VdbeAddOp4(v, OP_Affinity, rLhs, nVector, 0, zAff, nVector); if( destIfFalse==destIfNull ){ /* Combine Step 3 and Step 5 into a single opcode */ + if( ExprHasProperty(pExpr, EP_Subrtn) ){ + const VdbeOp *pOp = sqlite3VdbeGetOp(v, pExpr->y.sub.iAddr); + assert( pOp->opcode==OP_Once || pParse->nErr ); + if( pOp->p3>0 ){ /* tag-202407032019 */ + assert( OptimizationEnabled(pParse->db, SQLITE_BloomFilter) + || pParse->nErr ); + sqlite3VdbeAddOp4Int(v, OP_Filter, pOp->p3, destIfFalse, + rLhs, nVector); VdbeCoverage(v); + } + } sqlite3VdbeAddOp4Int(v, OP_NotFound, iTab, destIfFalse, rLhs, nVector); VdbeCoverage(v); goto sqlite3ExprCodeIN_finished; @@ -100058,7 +117068,7 @@ static void sqlite3ExprCodeIN( } /* Step 5. If we do not care about the difference between NULL and - ** FALSE, then just return false. + ** FALSE, then just return false. */ if( destIfFalse==destIfNull ) sqlite3VdbeGoto(v, destIfFalse); @@ -100084,9 +117094,18 @@ static void sqlite3ExprCodeIN( CollSeq *pColl; int r3 = sqlite3GetTempReg(pParse); p = sqlite3VectorFieldSubexpr(pLeft, i); - pColl = sqlite3ExprCollSeq(pParse, p); - sqlite3VdbeAddOp3(v, OP_Column, iTab, i, r3); - sqlite3VdbeAddOp4(v, OP_Ne, rLhs+i, destNotNull, r3, + if( ExprUseXSelect(pExpr) ){ + Expr *pRhs = pExpr->x.pSelect->pEList->a[i].pExpr; + pColl = sqlite3BinaryCompareCollSeq(pParse, p, pRhs); + }else{ + /* If the RHS of the IN(...) expression are scalar expressions, do + ** not consider their collation sequences. The documentation says + ** "The collating sequence used for expressions of the form "x IN (y, z, + ** ...)" is the collating sequence of x.". */ + pColl = sqlite3ExprCollSeq(pParse, p); + } + sqlite3VdbeAddOp3(v, OP_Column, iTab, aiMap[i], r3); + sqlite3VdbeAddOp4(v, OP_Ne, rLhs+aiMap[i], destNotNull, r3, (void*)pColl, P4_COLLSEQ); VdbeCoverage(v); sqlite3ReleaseTempReg(pParse, r3); @@ -100106,7 +117125,6 @@ static void sqlite3ExprCodeIN( sqlite3VdbeJumpHere(v, addrTruthOp); sqlite3ExprCodeIN_finished: - if( rLhs!=rLhsOrig ) sqlite3ReleaseTempReg(pParse, rLhs); VdbeComment((v, "end IN expr")); sqlite3ExprCodeIN_oom_error: sqlite3DbFree(pParse->db, aiMap); @@ -100119,14 +117137,14 @@ static void sqlite3ExprCodeIN( ** Generate an instruction that will put the floating point ** value described by z[0..n-1] into register iMem. ** -** The z[] string will probably not be zero-terminated. But the +** The z[] string will probably not be zero-terminated. But the ** z[n] character is guaranteed to be something that does not look ** like the continuation of the number. */ static void codeReal(Vdbe *v, const char *z, int negateFlag, int iMem){ if( ALWAYS(z!=0) ){ double value; - sqlite3AtoF(z, &value, sqlite3Strlen30(z), SQLITE_UTF8); + sqlite3AtoF(z, &value); assert( !sqlite3IsNaN(value) ); /* The new AtoF never returns NaN */ if( negateFlag ) value = -value; sqlite3VdbeAddOp4Dup8(v, OP_Real, 0, iMem, 0, (u8*)&value, P4_REAL); @@ -100156,11 +117174,12 @@ static void codeInteger(Parse *pParse, Expr *pExpr, int negFlag, int iMem){ c = sqlite3DecOrHexToI64(z, &value); if( (c==3 && !negFlag) || (c==2) || (negFlag && value==SMALLEST_INT64)){ #ifdef SQLITE_OMIT_FLOATING_POINT - sqlite3ErrorMsg(pParse, "oversized integer: %s%s", negFlag ? "-" : "", z); + sqlite3ErrorMsg(pParse, "oversized integer: %s%#T", negFlag?"-":"",pExpr); #else #ifndef SQLITE_OMIT_HEX_INTEGER if( sqlite3_strnicmp(z,"0x",2)==0 ){ - sqlite3ErrorMsg(pParse, "hex literal too big: %s%s", negFlag?"-":"",z); + sqlite3ErrorMsg(pParse, "hex literal too big: %s%#T", + negFlag?"-":"",pExpr); }else #endif { @@ -100198,38 +117217,97 @@ SQLITE_PRIVATE void sqlite3ExprCodeLoadIndexColumn( } } +#ifndef SQLITE_OMIT_GENERATED_COLUMNS +/* +** Generate code that will compute the value of generated column pCol +** and store the result in register regOut +*/ +SQLITE_PRIVATE void sqlite3ExprCodeGeneratedColumn( + Parse *pParse, /* Parsing context */ + Table *pTab, /* Table containing the generated column */ + Column *pCol, /* The generated column */ + int regOut /* Put the result in this register */ +){ + int iAddr; + Vdbe *v = pParse->pVdbe; + int nErr = pParse->nErr; + assert( v!=0 ); + assert( pParse->iSelfTab!=0 ); + if( pParse->iSelfTab>0 ){ + iAddr = sqlite3VdbeAddOp3(v, OP_IfNullRow, pParse->iSelfTab-1, 0, regOut); + }else{ + iAddr = 0; + } + sqlite3ExprCodeCopy(pParse, sqlite3ColumnExpr(pTab,pCol), regOut); + if( (pCol->colFlags & COLFLAG_VIRTUAL)!=0 + && (pTab->tabFlags & TF_Strict)!=0 + ){ + int p3 = 2+(int)(pCol - pTab->aCol); + sqlite3VdbeAddOp4(v, OP_TypeCheck, regOut, 1, p3, (char*)pTab, P4_TABLE); + }else if( pCol->affinity>=SQLITE_AFF_TEXT ){ + sqlite3VdbeAddOp4(v, OP_Affinity, regOut, 1, 0, &pCol->affinity, 1); + } + if( iAddr ) sqlite3VdbeJumpHere(v, iAddr); + if( pParse->nErr>nErr ) pParse->db->errByteOffset = -1; +} +#endif /* SQLITE_OMIT_GENERATED_COLUMNS */ + /* ** Generate code to extract the value of the iCol-th column of a table. */ SQLITE_PRIVATE void sqlite3ExprCodeGetColumnOfTable( - Vdbe *v, /* The VDBE under construction */ + Vdbe *v, /* Parsing context */ Table *pTab, /* The table containing the value */ int iTabCur, /* The table cursor. Or the PK cursor for WITHOUT ROWID */ int iCol, /* Index of the column to extract */ int regOut /* Extract the value into this register */ ){ - if( pTab==0 ){ - sqlite3VdbeAddOp3(v, OP_Column, iTabCur, iCol, regOut); - return; - } + Column *pCol; + assert( v!=0 ); + assert( pTab!=0 ); + assert( iCol!=XN_EXPR ); if( iCol<0 || iCol==pTab->iPKey ){ sqlite3VdbeAddOp2(v, OP_Rowid, iTabCur, regOut); + VdbeComment((v, "%s.rowid", pTab->zName)); }else{ - int op = IsVirtual(pTab) ? OP_VColumn : OP_Column; - int x = iCol; - if( !HasRowid(pTab) && !IsVirtual(pTab) ){ - x = sqlite3ColumnOfIndex(sqlite3PrimaryKeyIndex(pTab), iCol); + int op; + int x; + if( IsVirtual(pTab) ){ + op = OP_VColumn; + x = iCol; +#ifndef SQLITE_OMIT_GENERATED_COLUMNS + }else if( (pCol = &pTab->aCol[iCol])->colFlags & COLFLAG_VIRTUAL ){ + Parse *pParse = sqlite3VdbeParser(v); + if( pCol->colFlags & COLFLAG_BUSY ){ + sqlite3ErrorMsg(pParse, "generated column loop on \"%s\"", + pCol->zCnName); + }else{ + int savedSelfTab = pParse->iSelfTab; + pCol->colFlags |= COLFLAG_BUSY; + pParse->iSelfTab = iTabCur+1; + sqlite3ExprCodeGeneratedColumn(pParse, pTab, pCol, regOut); + pParse->iSelfTab = savedSelfTab; + pCol->colFlags &= ~COLFLAG_BUSY; + } + return; +#endif + }else if( !HasRowid(pTab) ){ + testcase( iCol!=sqlite3TableColumnToStorage(pTab, iCol) ); + x = sqlite3TableColumnToIndex(sqlite3PrimaryKeyIndex(pTab), iCol); + op = OP_Column; + }else{ + x = sqlite3TableColumnToStorage(pTab,iCol); + testcase( x!=iCol ); + op = OP_Column; } sqlite3VdbeAddOp3(v, op, iTabCur, x, regOut); - } - if( iCol>=0 ){ sqlite3ColumnDefault(v, pTab, iCol, regOut); } } /* ** Generate code that will extract the iColumn-th column from -** table pTab and store the column value in register iReg. +** table pTab and store the column value in register iReg. ** ** There must be an open cursor to pTab in iTable when this routine ** is called. If iColumn<0 then code is generated that extracts the rowid. @@ -100242,11 +117320,14 @@ SQLITE_PRIVATE int sqlite3ExprCodeGetColumn( int iReg, /* Store results here */ u8 p5 /* P5 value for OP_Column + FLAGS */ ){ - Vdbe *v = pParse->pVdbe; - assert( v!=0 ); - sqlite3ExprCodeGetColumnOfTable(v, pTab, iTable, iColumn, iReg); + assert( pParse->pVdbe!=0 ); + assert( (p5 & (OPFLAG_NOCHNG|OPFLAG_TYPEOFARG|OPFLAG_LENGTHARG))==p5 ); + assert( IsVirtual(pTab) || (p5 & OPFLAG_NOCHNG)==0 ); + sqlite3ExprCodeGetColumnOfTable(pParse->pVdbe, pTab, iTable, iColumn, iReg); if( p5 ){ - sqlite3VdbeChangeP5(v, p5); + VdbeOp *pOp = sqlite3VdbeGetLastOp(pParse->pVdbe); + if( pOp->opcode==OP_Column ) pOp->p5 = p5; + if( pOp->opcode==OP_VColumn ) pOp->p5 = (p5 & OPFLAG_NOCHNG); } return iReg; } @@ -100256,7 +117337,6 @@ SQLITE_PRIVATE int sqlite3ExprCodeGetColumn( ** over to iTo..iTo+nReg-1. */ SQLITE_PRIVATE void sqlite3ExprCodeMove(Parse *pParse, int iFrom, int iTo, int nReg){ - assert( iFrom>=iTo+nReg || iFrom+nReg<=iTo ); sqlite3VdbeAddOp3(pParse->pVdbe, OP_Move, iFrom, iTo, nReg); } @@ -100265,16 +117345,22 @@ SQLITE_PRIVATE void sqlite3ExprCodeMove(Parse *pParse, int iFrom, int iTo, int n ** register iReg. The caller must ensure that iReg already contains ** the correct value for the expression. */ -static void exprToRegister(Expr *p, int iReg){ - p->op2 = p->op; - p->op = TK_REGISTER; - p->iTable = iReg; - ExprClearProperty(p, EP_Skip); +SQLITE_PRIVATE void sqlite3ExprToRegister(Expr *pExpr, int iReg){ + Expr *p = sqlite3ExprSkipCollateAndLikely(pExpr); + if( NEVER(p==0) ) return; + if( p->op==TK_REGISTER ){ + assert( p->iTable==iReg ); + }else{ + p->op2 = p->op; + p->op = TK_REGISTER; + p->iTable = iReg; + ExprClearProperty(p, EP_Skip); + } } /* ** Evaluate an expression (either a vector or a scalar expression) and store -** the result in continguous temporary registers. Return the index of +** the result in contiguous temporary registers. Return the index of ** the first register used to store the result. ** ** If the returned result register is a temporary scalar, then also write @@ -100299,6 +117385,7 @@ static int exprCodeVector(Parse *pParse, Expr *p, int *piFreeable){ int i; iResult = pParse->nMem+1; pParse->nMem += nResult; + assert( ExprUseXList(p) ); for(i=0; ix.pList->a[i].pExpr, i+iResult); } @@ -100307,6 +117394,379 @@ static int exprCodeVector(Parse *pParse, Expr *p, int *piFreeable){ return iResult; } +/* +** If the last opcode is a OP_Copy, then set the do-not-merge flag (p5) +** so that a subsequent copy will not be merged into this one. +*/ +static void setDoNotMergeFlagOnCopy(Vdbe *v){ + if( sqlite3VdbeGetLastOp(v)->opcode==OP_Copy ){ + sqlite3VdbeChangeP5(v, 1); /* Tag trailing OP_Copy as not mergeable */ + } +} + +/* +** Generate code to implement special SQL functions that are implemented +** in-line rather than by using the usual callbacks. +*/ +static int exprCodeInlineFunction( + Parse *pParse, /* Parsing context */ + ExprList *pFarg, /* List of function arguments */ + int iFuncId, /* Function ID. One of the INTFUNC_... values */ + int target /* Store function result in this register */ +){ + int nFarg; + Vdbe *v = pParse->pVdbe; + assert( v!=0 ); + assert( pFarg!=0 ); + nFarg = pFarg->nExpr; + assert( nFarg>0 ); /* All in-line functions have at least one argument */ + switch( iFuncId ){ + case INLINEFUNC_coalesce: { + /* Attempt a direct implementation of the built-in COALESCE() and + ** IFNULL() functions. This avoids unnecessary evaluation of + ** arguments past the first non-NULL argument. + */ + int endCoalesce = sqlite3VdbeMakeLabel(pParse); + int i; + assert( nFarg>=2 ); + sqlite3ExprCode(pParse, pFarg->a[0].pExpr, target); + for(i=1; ia[i].pExpr, target); + } + setDoNotMergeFlagOnCopy(v); + sqlite3VdbeResolveLabel(v, endCoalesce); + break; + } + case INLINEFUNC_iif: { + Expr caseExpr; + memset(&caseExpr, 0, sizeof(caseExpr)); + caseExpr.op = TK_CASE; + caseExpr.x.pList = pFarg; + return sqlite3ExprCodeTarget(pParse, &caseExpr, target); + } +#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC + case INLINEFUNC_sqlite_offset: { + Expr *pArg = pFarg->a[0].pExpr; + if( pArg->op==TK_COLUMN && pArg->iTable>=0 ){ + sqlite3VdbeAddOp3(v, OP_Offset, pArg->iTable, pArg->iColumn, target); + }else{ + sqlite3VdbeAddOp2(v, OP_Null, 0, target); + } + break; + } +#endif + default: { + /* The UNLIKELY() function is a no-op. The result is the value + ** of the first argument. + */ + assert( nFarg==1 || nFarg==2 ); + target = sqlite3ExprCodeTarget(pParse, pFarg->a[0].pExpr, target); + break; + } + + /*********************************************************************** + ** Test-only SQL functions that are only usable if enabled + ** via SQLITE_TESTCTRL_INTERNAL_FUNCTIONS + */ +#if !defined(SQLITE_UNTESTABLE) + case INLINEFUNC_expr_compare: { + /* Compare two expressions using sqlite3ExprCompare() */ + assert( nFarg==2 ); + sqlite3VdbeAddOp2(v, OP_Integer, + sqlite3ExprCompare(0,pFarg->a[0].pExpr, pFarg->a[1].pExpr,-1), + target); + break; + } + + case INLINEFUNC_expr_implies_expr: { + /* Compare two expressions using sqlite3ExprImpliesExpr() */ + assert( nFarg==2 ); + sqlite3VdbeAddOp2(v, OP_Integer, + sqlite3ExprImpliesExpr(pParse,pFarg->a[0].pExpr, pFarg->a[1].pExpr,-1), + target); + break; + } + + case INLINEFUNC_implies_nonnull_row: { + /* Result of sqlite3ExprImpliesNonNullRow() */ + Expr *pA1; + assert( nFarg==2 ); + pA1 = pFarg->a[1].pExpr; + if( pA1->op==TK_COLUMN ){ + sqlite3VdbeAddOp2(v, OP_Integer, + sqlite3ExprImpliesNonNullRow(pFarg->a[0].pExpr,pA1->iTable,1), + target); + }else{ + sqlite3VdbeAddOp2(v, OP_Null, 0, target); + } + break; + } + + case INLINEFUNC_affinity: { + /* The AFFINITY() function evaluates to a string that describes + ** the type affinity of the argument. This is used for testing of + ** the SQLite type logic. + */ + const char *azAff[] = { "blob", "text", "numeric", "integer", + "real", "flexnum" }; + char aff; + assert( nFarg==1 ); + aff = sqlite3ExprAffinity(pFarg->a[0].pExpr); + assert( aff<=SQLITE_AFF_NONE + || (aff>=SQLITE_AFF_BLOB && aff<=SQLITE_AFF_FLEXNUM) ); + sqlite3VdbeLoadString(v, target, + (aff<=SQLITE_AFF_NONE) ? "none" : azAff[aff-SQLITE_AFF_BLOB]); + break; + } +#endif /* !defined(SQLITE_UNTESTABLE) */ + } + return target; +} + +/* +** Expression Node callback for sqlite3ExprCanReturnSubtype(). If +** pExpr is able to return a subtype, set pWalker->eCode and abort +** the search. If pExpr can never return a subtype, prune search. +** +** The only expressions that can return a subtype are: +** +** 1. A function +** 2. The no-op "+" operator +** 3. A CASE...END expression +** 4. A CAST() expression +** 5. A "expr COLLATE colseq" expression. +** +** For any other kind of expression, prune the search. +** +** For case 1, the expression can yield a subtype if the function has +** the SQLITE_RESULT_SUBTYPE property. Functions can also return +** a subtype (via sqlite3_result_value()) if any of the arguments can +** return a subtype. +** +** In all cases 1 through 5, the expression might also return a subtype +** if any operand can return a subtype. +*/ +static int exprNodeCanReturnSubtype(Walker *pWalker, Expr *pExpr){ + int n; + FuncDef *pDef; + sqlite3 *db; + if( pExpr->op==TK_CASE || pExpr->op==TK_UPLUS + || pExpr->op==TK_COLLATE || pExpr->op==TK_CAST + ){ + return WRC_Continue; + } + if( pExpr->op!=TK_FUNCTION ){ + return WRC_Prune; + } + assert( ExprUseXList(pExpr) ); + db = pWalker->pParse->db; + n = ALWAYS(pExpr->x.pList) ? pExpr->x.pList->nExpr : 0; + pDef = sqlite3FindFunction(db, pExpr->u.zToken, n, ENC(db), 0); + if( NEVER(pDef==0) || (pDef->funcFlags & SQLITE_RESULT_SUBTYPE)!=0 ){ + pWalker->eCode = 1; + return WRC_Abort; + } + return WRC_Continue; +} + +/* +** Return TRUE if expression pExpr is able to return a subtype. +** +** A TRUE return does not guarantee that a subtype will be returned. +** It only indicates that a subtype return is possible. False positives +** are acceptable as they only disable an optimization. False negatives, +** on the other hand, can lead to incorrect answers. +*/ +static int sqlite3ExprCanReturnSubtype(Parse *pParse, Expr *pExpr){ + Walker w; + memset(&w, 0, sizeof(w)); + w.pParse = pParse; + w.xExprCallback = exprNodeCanReturnSubtype; + sqlite3WalkExpr(&w, pExpr); + return w.eCode; +} + + +/* +** Check to see if pExpr is one of the indexed expressions on pParse->pIdxEpr. +** If it is, then resolve the expression by reading from the index and +** return the register into which the value has been read. If pExpr is +** not an indexed expression, then return negative. +*/ +static SQLITE_NOINLINE int sqlite3IndexedExprLookup( + Parse *pParse, /* The parsing context */ + Expr *pExpr, /* The expression to potentially bypass */ + int target /* Where to store the result of the expression */ +){ + IndexedExpr *p; + Vdbe *v; + for(p=pParse->pIdxEpr; p; p=p->pIENext){ + u8 exprAff; + int iDataCur = p->iDataCur; + if( iDataCur<0 ) continue; + if( pParse->iSelfTab ){ + if( p->iDataCur!=pParse->iSelfTab-1 ) continue; + iDataCur = -1; + } + if( sqlite3ExprCompare(0, pExpr, p->pExpr, iDataCur)!=0 ) continue; + assert( p->aff>=SQLITE_AFF_BLOB && p->aff<=SQLITE_AFF_NUMERIC ); + exprAff = sqlite3ExprAffinity(pExpr); + if( (exprAff<=SQLITE_AFF_BLOB && p->aff!=SQLITE_AFF_BLOB) + || (exprAff==SQLITE_AFF_TEXT && p->aff!=SQLITE_AFF_TEXT) + || (exprAff>=SQLITE_AFF_NUMERIC && p->aff!=SQLITE_AFF_NUMERIC) + ){ + /* Affinity mismatch on a generated column */ + continue; + } + + + /* Functions that might set a subtype should not be replaced by the + ** value taken from an expression index if they are themselves an + ** argument to another scalar function or aggregate. + ** https://sqlite.org/forum/forumpost/68d284c86b082c3e */ + if( ExprHasProperty(pExpr, EP_SubtArg) + && sqlite3ExprCanReturnSubtype(pParse, pExpr) + ){ + continue; + } + + v = pParse->pVdbe; + assert( v!=0 ); + if( p->bMaybeNullRow ){ + /* If the index is on a NULL row due to an outer join, then we + ** cannot extract the value from the index. The value must be + ** computed using the original expression. */ + int addr = sqlite3VdbeCurrentAddr(v); + sqlite3VdbeAddOp3(v, OP_IfNullRow, p->iIdxCur, addr+3, target); + VdbeCoverage(v); + sqlite3VdbeAddOp3(v, OP_Column, p->iIdxCur, p->iIdxCol, target); + VdbeComment((v, "%s expr-column %d", p->zIdxName, p->iIdxCol)); + sqlite3VdbeGoto(v, 0); + p = pParse->pIdxEpr; + pParse->pIdxEpr = 0; + sqlite3ExprCode(pParse, pExpr, target); + pParse->pIdxEpr = p; + sqlite3VdbeJumpHere(v, addr+2); + }else{ + sqlite3VdbeAddOp3(v, OP_Column, p->iIdxCur, p->iIdxCol, target); + VdbeComment((v, "%s expr-column %d", p->zIdxName, p->iIdxCol)); + } + return target; + } + return -1; /* Not found */ +} + + +/* +** Expression pExpr is guaranteed to be a TK_COLUMN or equivalent. This +** function checks the Parse.pIdxPartExpr list to see if this column +** can be replaced with a constant value. If so, it generates code to +** put the constant value in a register (ideally, but not necessarily, +** register iTarget) and returns the register number. +** +** Or, if the TK_COLUMN cannot be replaced by a constant, zero is +** returned. +*/ +static int exprPartidxExprLookup(Parse *pParse, Expr *pExpr, int iTarget){ + IndexedExpr *p; + for(p=pParse->pIdxPartExpr; p; p=p->pIENext){ + if( pExpr->iColumn==p->iIdxCol && pExpr->iTable==p->iDataCur ){ + Vdbe *v = pParse->pVdbe; + int addr = 0; + int ret; + + if( p->bMaybeNullRow ){ + addr = sqlite3VdbeAddOp1(v, OP_IfNullRow, p->iIdxCur); + } + ret = sqlite3ExprCodeTarget(pParse, p->pExpr, iTarget); + sqlite3VdbeAddOp4(pParse->pVdbe, OP_Affinity, ret, 1, 0, + (const char*)&p->aff, 1); + if( addr ){ + sqlite3VdbeJumpHere(v, addr); + sqlite3VdbeChangeP3(v, addr, ret); + } + return ret; + } + } + return 0; +} + +/* +** Generate code that evaluates an AND or OR operator leaving a +** boolean result in a register. pExpr is the AND/OR expression. +** Store the result in the "target" register. Use short-circuit +** evaluation to avoid computing both operands, if possible. +** +** The code generated might require the use of a temporary register. +** If it does, then write the number of that temporary register +** into *pTmpReg. If not, leave *pTmpReg unchanged. +*/ +static SQLITE_NOINLINE int exprCodeTargetAndOr( + Parse *pParse, /* Parsing context */ + Expr *pExpr, /* AND or OR expression to be coded */ + int target, /* Put result in this register, guaranteed */ + int *pTmpReg /* Write a temporary register here */ +){ + int op; /* The opcode. TK_AND or TK_OR */ + int skipOp; /* Opcode for the branch that skips one operand */ + int addrSkip; /* Branch instruction that skips one of the operands */ + int regSS = 0; /* Register holding computed operand when other omitted */ + int r1, r2; /* Registers for left and right operands, respectively */ + Expr *pAlt; /* Alternative, simplified expression */ + Vdbe *v; /* statement being coded */ + + assert( pExpr!=0 ); + op = pExpr->op; + assert( op==TK_AND || op==TK_OR ); + assert( TK_AND==OP_And ); testcase( op==TK_AND ); + assert( TK_OR==OP_Or ); testcase( op==TK_OR ); + assert( pParse->pVdbe!=0 ); + v = pParse->pVdbe; + pAlt = sqlite3ExprSimplifiedAndOr(pExpr); + if( pAlt!=pExpr ){ + r1 = sqlite3ExprCodeTarget(pParse, pAlt, target); + sqlite3VdbeAddOp3(v, OP_And, r1, r1, target); + return target; + } + skipOp = op==TK_AND ? OP_IfNot : OP_If; + if( exprEvalRhsFirst(pExpr) ){ + /* Compute the right operand first. Skip the computation of the left + ** operand if the right operand fully determines the result */ + r2 = regSS = sqlite3ExprCodeTarget(pParse, pExpr->pRight, target); + addrSkip = sqlite3VdbeAddOp1(v, skipOp, r2); + VdbeComment((v, "skip left operand")); + VdbeCoverage(v); + r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, pTmpReg); + }else{ + /* Compute the left operand first */ + r1 = sqlite3ExprCodeTarget(pParse, pExpr->pLeft, target); + if( ExprHasProperty(pExpr->pRight, EP_Subquery) ){ + /* Skip over the computation of the right operand if the right + ** operand is a subquery and the left operand completely determines + ** the result */ + regSS = r1; + addrSkip = sqlite3VdbeAddOp1(v, skipOp, r1); + VdbeComment((v, "skip right operand")); + VdbeCoverage(v); + }else{ + addrSkip = regSS = 0; + } + r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, pTmpReg); + } + sqlite3VdbeAddOp3(v, op, r2, r1, target); + testcase( (*pTmpReg)==0 ); + if( addrSkip ){ + sqlite3VdbeAddOp2(v, OP_Goto, 0, sqlite3VdbeCurrentAddr(v)+2); + sqlite3VdbeJumpHere(v, addrSkip); + sqlite3VdbeAddOp3(v, OP_Or, regSS, regSS, target); + VdbeComment((v, "short-circut value")); + } + return target; +} + + /* ** Generate code into the current Vdbe to evaluate the given @@ -100330,50 +117790,86 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) int p5 = 0; assert( target>0 && target<=pParse->nMem ); - if( v==0 ){ - assert( pParse->db->mallocFailed ); - return 0; - } + assert( v!=0 ); expr_code_doover: if( pExpr==0 ){ op = TK_NULL; + }else if( pParse->pIdxEpr!=0 + && !ExprHasProperty(pExpr, EP_Leaf) + && (r1 = sqlite3IndexedExprLookup(pParse, pExpr, target))>=0 + ){ + return r1; }else{ + assert( !ExprHasVVAProperty(pExpr,EP_Immutable) ); op = pExpr->op; } + assert( op!=TK_ORDER ); switch( op ){ case TK_AGG_COLUMN: { AggInfo *pAggInfo = pExpr->pAggInfo; - struct AggInfo_col *pCol = &pAggInfo->aCol[pExpr->iAgg]; + struct AggInfo_col *pCol; + assert( pAggInfo!=0 ); + assert( pExpr->iAgg>=0 ); + if( pExpr->iAgg>=pAggInfo->nColumn ){ + /* Happens when the left table of a RIGHT JOIN is null and + ** is using an expression index */ + sqlite3VdbeAddOp2(v, OP_Null, 0, target); +#ifdef SQLITE_VDBE_COVERAGE + /* Verify that the OP_Null above is exercised by tests + ** tag-20230325-2 */ + sqlite3VdbeAddOp3(v, OP_NotNull, target, 1, 20230325); + VdbeCoverageNeverTaken(v); +#endif + break; + } + pCol = &pAggInfo->aCol[pExpr->iAgg]; if( !pAggInfo->directMode ){ - assert( pCol->iMem>0 ); - return pCol->iMem; + return AggInfoColumnReg(pAggInfo, pExpr->iAgg); }else if( pAggInfo->useSortingIdx ){ + Table *pTab = pCol->pTab; sqlite3VdbeAddOp3(v, OP_Column, pAggInfo->sortingIdxPTab, pCol->iSorterColumn, target); + if( pTab==0 ){ + /* No comment added */ + }else if( pCol->iColumn<0 ){ + VdbeComment((v,"%s.rowid",pTab->zName)); + }else{ + VdbeComment((v,"%s.%s", + pTab->zName, pTab->aCol[pCol->iColumn].zCnName)); + if( pTab->aCol[pCol->iColumn].affinity==SQLITE_AFF_REAL ){ + sqlite3VdbeAddOp1(v, OP_RealAffinity, target); + } + } + return target; + }else if( pExpr->y.pTab==0 ){ + /* This case happens when the argument to an aggregate function + ** is rewritten by aggregateConvertIndexedExprRefToColumn() */ + sqlite3VdbeAddOp3(v, OP_Column, pExpr->iTable, pExpr->iColumn, target); return target; } /* Otherwise, fall thru into the TK_COLUMN case */ + /* no break */ deliberate_fall_through } case TK_COLUMN: { int iTab = pExpr->iTable; + int iReg; if( ExprHasProperty(pExpr, EP_FixedCol) ){ /* This COLUMN expression is really a constant due to WHERE clause ** constraints, and that constant is coded by the pExpr->pLeft - ** expresssion. However, make sure the constant has the correct + ** expression. However, make sure the constant has the correct ** datatype by applying the Affinity of the table column to the ** constant. */ - int iReg = sqlite3ExprCodeTarget(pParse, pExpr->pLeft,target); - int aff = sqlite3TableColumnAffinity(pExpr->y.pTab, pExpr->iColumn); - if( aff!=SQLITE_AFF_BLOB ){ - static const char zAff[] = "B\000C\000D\000E"; + int aff; + iReg = sqlite3ExprCodeTarget(pParse, pExpr->pLeft,target); + assert( ExprUseYTab(pExpr) ); + assert( pExpr->y.pTab!=0 ); + aff = sqlite3TableColumnAffinity(pExpr->y.pTab, pExpr->iColumn); + if( aff>SQLITE_AFF_BLOB ){ + static const char zAff[] = "B\000C\000D\000E\000F"; assert( SQLITE_AFF_BLOB=='A' ); assert( SQLITE_AFF_TEXT=='B' ); - if( iReg!=target ){ - sqlite3VdbeAddOp2(v, OP_SCopy, iReg, target); - iReg = target; - } sqlite3VdbeAddOp4(v, OP_Affinity, iReg, 1, 0, &zAff[(aff-'B')*2], P4_STATIC); } @@ -100381,17 +117877,66 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) } if( iTab<0 ){ if( pParse->iSelfTab<0 ){ - /* Generating CHECK constraints or inserting into partial index */ - return pExpr->iColumn - pParse->iSelfTab; + /* Other columns in the same row for CHECK constraints or + ** generated columns or for inserting into partial index. + ** The row is unpacked into registers beginning at + ** 0-(pParse->iSelfTab). The rowid (if any) is in a register + ** immediately prior to the first column. + */ + Column *pCol; + Table *pTab; + int iSrc; + int iCol = pExpr->iColumn; + assert( ExprUseYTab(pExpr) ); + pTab = pExpr->y.pTab; + assert( pTab!=0 ); + assert( iCol>=XN_ROWID ); + assert( iColnCol ); + if( iCol<0 ){ + return -1-pParse->iSelfTab; + } + pCol = pTab->aCol + iCol; + testcase( iCol!=sqlite3TableColumnToStorage(pTab,iCol) ); + iSrc = sqlite3TableColumnToStorage(pTab, iCol) - pParse->iSelfTab; +#ifndef SQLITE_OMIT_GENERATED_COLUMNS + if( pCol->colFlags & COLFLAG_GENERATED ){ + if( pCol->colFlags & COLFLAG_BUSY ){ + sqlite3ErrorMsg(pParse, "generated column loop on \"%s\"", + pCol->zCnName); + return 0; + } + pCol->colFlags |= COLFLAG_BUSY; + if( pCol->colFlags & COLFLAG_NOTAVAIL ){ + sqlite3ExprCodeGeneratedColumn(pParse, pTab, pCol, iSrc); + } + pCol->colFlags &= ~(COLFLAG_BUSY|COLFLAG_NOTAVAIL); + return iSrc; + }else +#endif /* SQLITE_OMIT_GENERATED_COLUMNS */ + if( pCol->affinity==SQLITE_AFF_REAL ){ + sqlite3VdbeAddOp2(v, OP_SCopy, iSrc, target); + sqlite3VdbeAddOp1(v, OP_RealAffinity, target); + return target; + }else{ + return iSrc; + } }else{ /* Coding an expression that is part of an index where column names ** in the index refer to the table to which the index belongs */ iTab = pParse->iSelfTab - 1; } } - return sqlite3ExprCodeGetColumn(pParse, pExpr->y.pTab, + else if( pParse->pIdxPartExpr + && 0!=(r1 = exprPartidxExprLookup(pParse, pExpr, target)) + ){ + return r1; + } + assert( ExprUseYTab(pExpr) ); + assert( pExpr->y.pTab!=0 ); + iReg = sqlite3ExprCodeGetColumn(pParse, pExpr->y.pTab, pExpr->iColumn, iTab, target, pExpr->op2); + return iReg; } case TK_INTEGER: { codeInteger(pParse, pExpr, 0, target); @@ -100413,7 +117958,18 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) sqlite3VdbeLoadString(v, target, pExpr->u.zToken); return target; } - case TK_NULL: { + case TK_NULLS: { + /* Set a range of registers to NULL. pExpr->y.nReg registers starting + ** with target */ + sqlite3VdbeAddOp3(v, OP_Null, 0, target, target + pExpr->y.nReg - 1); + return target; + } + default: { + /* Make NULL the default case so that if a bug causes an illegal + ** Expr node to be passed into this function, it will be handled + ** sanely and not crash. But keep the assert() to bring the problem + ** to the attention of the developers. */ + assert( op==TK_NULL || op==TK_ERROR || pParse->db->mallocFailed ); sqlite3VdbeAddOp2(v, OP_Null, 0, target); return target; } @@ -100438,12 +117994,6 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) assert( pExpr->u.zToken!=0 ); assert( pExpr->u.zToken[0]!=0 ); sqlite3VdbeAddOp2(v, OP_Variable, pExpr->iColumn, target); - if( pExpr->u.zToken[1]!=0 ){ - const char *z = sqlite3VListNumToName(pParse->pVList, pExpr->iColumn); - assert( pExpr->u.zToken[0]=='?' || strcmp(pExpr->u.zToken, z)==0 ); - pParse->pVList[0] = 0; /* Indicate VList may no longer be enlarged */ - sqlite3VdbeAppendP4(v, (char*)z, P4_STATIC); - } return target; } case TK_REGISTER: { @@ -100452,11 +118002,9 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) #ifndef SQLITE_OMIT_CAST case TK_CAST: { /* Expressions of the form: CAST(pLeft AS token) */ - inReg = sqlite3ExprCodeTarget(pParse, pExpr->pLeft, target); - if( inReg!=target ){ - sqlite3VdbeAddOp2(v, OP_SCopy, inReg, target); - inReg = target; - } + sqlite3ExprCode(pParse, pExpr->pLeft, target); + assert( inReg==target ); + assert( !ExprHasProperty(pExpr, EP_IntValue) ); sqlite3VdbeAddOp2(v, OP_Cast, target, sqlite3AffinityType(pExpr->u.zToken, 0)); return inReg; @@ -100466,7 +118014,7 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) case TK_ISNOT: op = (op==TK_IS) ? TK_EQ : TK_NE; p5 = SQLITE_NULLEQ; - /* fall-through */ + /* no break */ deliberate_fall_through case TK_LT: case TK_LE: case TK_GT: @@ -100474,26 +118022,47 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) case TK_NE: case TK_EQ: { Expr *pLeft = pExpr->pLeft; + int addrIsNull = 0; if( sqlite3ExprIsVector(pLeft) ){ codeVectorCompare(pParse, pExpr, target, op, p5); }else{ - r1 = sqlite3ExprCodeTemp(pParse, pLeft, ®Free1); - r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, ®Free2); - codeCompare(pParse, pLeft, pExpr->pRight, op, - r1, r2, inReg, SQLITE_STOREP2 | p5); + if( ExprHasProperty(pExpr, EP_Subquery) && p5!=SQLITE_NULLEQ ){ + addrIsNull = exprComputeOperands(pParse, pExpr, + &r1, &r2, ®Free1, ®Free2); + }else{ + r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free1); + r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, ®Free2); + } + sqlite3VdbeAddOp2(v, OP_Integer, 1, inReg); + codeCompare(pParse, pLeft, pExpr->pRight, op, r1, r2, + sqlite3VdbeCurrentAddr(v)+2, p5, + ExprHasProperty(pExpr,EP_Commuted)); assert(TK_LT==OP_Lt); testcase(op==OP_Lt); VdbeCoverageIf(v,op==OP_Lt); assert(TK_LE==OP_Le); testcase(op==OP_Le); VdbeCoverageIf(v,op==OP_Le); assert(TK_GT==OP_Gt); testcase(op==OP_Gt); VdbeCoverageIf(v,op==OP_Gt); assert(TK_GE==OP_Ge); testcase(op==OP_Ge); VdbeCoverageIf(v,op==OP_Ge); assert(TK_EQ==OP_Eq); testcase(op==OP_Eq); VdbeCoverageIf(v,op==OP_Eq); assert(TK_NE==OP_Ne); testcase(op==OP_Ne); VdbeCoverageIf(v,op==OP_Ne); + if( p5==SQLITE_NULLEQ ){ + sqlite3VdbeAddOp2(v, OP_Integer, 0, inReg); + }else{ + sqlite3VdbeAddOp3(v, OP_ZeroOrNull, r1, inReg, r2); + if( addrIsNull ){ + sqlite3VdbeAddOp2(v, OP_Goto, 0, sqlite3VdbeCurrentAddr(v)+2); + sqlite3VdbeJumpHere(v, addrIsNull); + sqlite3VdbeAddOp2(v, OP_Null, 0, inReg); + } + } testcase( regFree1==0 ); testcase( regFree2==0 ); } break; } case TK_AND: - case TK_OR: + case TK_OR: { + inReg = exprCodeTargetAndOr(pParse, pExpr, target, ®Free1); + break; + } case TK_PLUS: case TK_STAR: case TK_MINUS: @@ -100502,10 +118071,9 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) case TK_BITOR: case TK_SLASH: case TK_LSHIFT: - case TK_RSHIFT: + case TK_RSHIFT: case TK_CONCAT: { - assert( TK_AND==OP_And ); testcase( op==TK_AND ); - assert( TK_OR==OP_Or ); testcase( op==TK_OR ); + int addrIsNull; assert( TK_PLUS==OP_Add ); testcase( op==TK_PLUS ); assert( TK_MINUS==OP_Subtract ); testcase( op==TK_MINUS ); assert( TK_REM==OP_Remainder ); testcase( op==TK_REM ); @@ -100515,11 +118083,23 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) assert( TK_LSHIFT==OP_ShiftLeft ); testcase( op==TK_LSHIFT ); assert( TK_RSHIFT==OP_ShiftRight ); testcase( op==TK_RSHIFT ); assert( TK_CONCAT==OP_Concat ); testcase( op==TK_CONCAT ); - r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free1); - r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, ®Free2); + if( ExprHasProperty(pExpr, EP_Subquery) ){ + addrIsNull = exprComputeOperands(pParse, pExpr, + &r1, &r2, ®Free1, ®Free2); + }else{ + r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free1); + r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, ®Free2); + addrIsNull = 0; + } sqlite3VdbeAddOp3(v, op, r2, r1, target); testcase( regFree1==0 ); testcase( regFree2==0 ); + if( addrIsNull ){ + sqlite3VdbeAddOp2(v, OP_Goto, 0, sqlite3VdbeCurrentAddr(v)+2); + sqlite3VdbeJumpHere(v, addrIsNull); + sqlite3VdbeAddOp2(v, OP_Null, 0, target); + VdbeComment((v, "short-circut value")); + } break; } case TK_UMINUS: { @@ -100538,6 +118118,7 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) tempX.op = TK_INTEGER; tempX.flags = EP_IntValue|EP_TokenOnly; tempX.u.iValue = 0; + ExprClearVVAProperties(&tempX); r1 = sqlite3ExprCodeTemp(pParse, &tempX, ®Free1); r2 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free2); sqlite3VdbeAddOp3(v, OP_Subtract, r2, r1, target); @@ -100583,11 +118164,14 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) } case TK_AGG_FUNCTION: { AggInfo *pInfo = pExpr->pAggInfo; - if( pInfo==0 ){ + if( pInfo==0 + || NEVER(pExpr->iAgg<0) + || NEVER(pExpr->iAgg>=pInfo->nFunc) + ){ assert( !ExprHasProperty(pExpr, EP_IntValue) ); - sqlite3ErrorMsg(pParse, "misuse of aggregate: %s()", pExpr->u.zToken); + sqlite3ErrorMsg(pParse, "misuse of aggregate: %#T()", pExpr); }else{ - return pInfo->aFunc[pExpr->iAgg].iMem; + return AggInfoFuncReg(pInfo, pExpr->iAgg); } break; } @@ -100608,17 +118192,16 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) } #endif - if( ConstFactorOk(pParse) && sqlite3ExprIsConstantNotJoin(pExpr) ){ - /* SQL functions can be expensive. So try to move constant functions - ** out of the inner loop, even if that means an extra OP_Copy. */ - return sqlite3ExprCodeAtInit(pParse, pExpr, -1); - } - assert( !ExprHasProperty(pExpr, EP_xIsSelect) ); - if( ExprHasProperty(pExpr, EP_TokenOnly) ){ - pFarg = 0; - }else{ - pFarg = pExpr->x.pList; + if( ConstFactorOk(pParse) + && sqlite3ExprIsConstantNotJoin(pParse,pExpr) + ){ + /* SQL functions can be expensive. So try to avoid running them + ** multiple times if we know they always give the same result */ + return sqlite3ExprCodeRunJustOnce(pParse, pExpr, -1); } + assert( !ExprHasProperty(pExpr, EP_TokenOnly) ); + assert( ExprUseXList(pExpr) ); + pFarg = pExpr->x.pList; nFarg = pFarg ? pFarg->nExpr : 0; assert( !ExprHasProperty(pExpr, EP_IntValue) ); zId = pExpr->u.zToken; @@ -100629,53 +118212,20 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) } #endif if( pDef==0 || pDef->xFinalize!=0 ){ - sqlite3ErrorMsg(pParse, "unknown function: %s()", zId); - break; - } - - /* Attempt a direct implementation of the built-in COALESCE() and - ** IFNULL() functions. This avoids unnecessary evaluation of - ** arguments past the first non-NULL argument. - */ - if( pDef->funcFlags & SQLITE_FUNC_COALESCE ){ - int endCoalesce = sqlite3VdbeMakeLabel(pParse); - assert( nFarg>=2 ); - sqlite3ExprCode(pParse, pFarg->a[0].pExpr, target); - for(i=1; ia[i].pExpr, target); - } - sqlite3VdbeResolveLabel(v, endCoalesce); + sqlite3ErrorMsg(pParse, "unknown function: %#T()", pExpr); break; } - - /* The UNLIKELY() function is a no-op. The result is the value - ** of the first argument. - */ - if( pDef->funcFlags & SQLITE_FUNC_UNLIKELY ){ - assert( nFarg>=1 ); - return sqlite3ExprCodeTarget(pParse, pFarg->a[0].pExpr, target); - } - -#ifdef SQLITE_DEBUG - /* The AFFINITY() function evaluates to a string that describes - ** the type affinity of the argument. This is used for testing of - ** the SQLite type logic. - */ - if( pDef->funcFlags & SQLITE_FUNC_AFFINITY ){ - const char *azAff[] = { "blob", "text", "numeric", "integer", "real" }; - char aff; - assert( nFarg==1 ); - aff = sqlite3ExprAffinity(pFarg->a[0].pExpr); - sqlite3VdbeLoadString(v, target, - aff ? azAff[aff-SQLITE_AFF_BLOB] : "none"); - return target; + if( (pDef->funcFlags & SQLITE_FUNC_INLINE)!=0 && ALWAYS(pFarg!=0) ){ + assert( (pDef->funcFlags & SQLITE_FUNC_UNSAFE)==0 ); + assert( (pDef->funcFlags & SQLITE_FUNC_DIRECT)==0 ); + return exprCodeInlineFunction(pParse, pFarg, + SQLITE_PTR_TO_INT(pDef->pUserData), target); + }else if( pDef->funcFlags & (SQLITE_FUNC_DIRECT|SQLITE_FUNC_UNSAFE) ){ + sqlite3ExprFunctionUsable(pParse, pExpr, pDef); } -#endif for(i=0; ia[i].pExpr) ){ + if( i<32 && sqlite3ExprIsConstant(pParse, pFarg->a[i].pExpr) ){ testcase( i==31 ); constMask |= MASKBIT32(i); } @@ -100691,10 +118241,10 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) r1 = sqlite3GetTempRange(pParse, nFarg); } - /* For length() and typeof() functions with a column argument, + /* For length() and typeof() and octet_length() functions, ** set the P5 parameter to the OP_Column opcode to OPFLAG_LENGTHARG - ** or OPFLAG_TYPEOFARG respectively, to avoid unnecessary data - ** loading. + ** or OPFLAG_TYPEOFARG or OPFLAG_BYTELENARG respectively, to avoid + ** unnecessary data loading. */ if( (pDef->funcFlags & (SQLITE_FUNC_LENGTH|SQLITE_FUNC_TYPEOF))!=0 ){ u8 exprOp; @@ -100704,14 +118254,16 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) if( exprOp==TK_COLUMN || exprOp==TK_AGG_COLUMN ){ assert( SQLITE_FUNC_LENGTH==OPFLAG_LENGTHARG ); assert( SQLITE_FUNC_TYPEOF==OPFLAG_TYPEOFARG ); - testcase( pDef->funcFlags & OPFLAG_LENGTHARG ); - pFarg->a[0].pExpr->op2 = - pDef->funcFlags & (OPFLAG_LENGTHARG|OPFLAG_TYPEOFARG); + assert( SQLITE_FUNC_BYTELEN==OPFLAG_BYTELENARG ); + assert( (OPFLAG_LENGTHARG|OPFLAG_TYPEOFARG)==OPFLAG_BYTELENARG ); + testcase( (pDef->funcFlags & OPFLAG_BYTELENARG)==OPFLAG_LENGTHARG ); + testcase( (pDef->funcFlags & OPFLAG_BYTELENARG)==OPFLAG_TYPEOFARG ); + testcase( (pDef->funcFlags & OPFLAG_BYTELENARG)==OPFLAG_BYTELENARG); + pFarg->a[0].pExpr->op2 = pDef->funcFlags & OPFLAG_BYTELENARG; } } - sqlite3ExprCodeExprList(pParse, pFarg, r1, 0, - SQLITE_ECEL_DUP|SQLITE_ECEL_FACTOR); + sqlite3ExprCodeExprList(pParse, pFarg, r1, 0, SQLITE_ECEL_FACTOR); }else{ r1 = 0; } @@ -100724,7 +118276,7 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) ** see if it is a column in a virtual table. This is done because ** the left operand of infix functions (the operand we want to ** control overloading) ends up as the second argument to the - ** function. The expression "A glob B" is equivalent to + ** function. The expression "A glob B" is equivalent to ** "glob(B,A). We want to use the A in "A glob B" to test ** for function overloading. But we use the B term in "glob(B,A)". */ @@ -100735,26 +118287,17 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) } #endif if( pDef->funcFlags & SQLITE_FUNC_NEEDCOLL ){ - if( !pColl ) pColl = db->pDfltColl; + if( !pColl ) pColl = db->pDfltColl; sqlite3VdbeAddOp4(v, OP_CollSeq, 0, 0, 0, (char *)pColl, P4_COLLSEQ); } -#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC - if( pDef->funcFlags & SQLITE_FUNC_OFFSET ){ - Expr *pArg = pFarg->a[0].pExpr; - if( pArg->op==TK_COLUMN ){ - sqlite3VdbeAddOp3(v, OP_Offset, pArg->iTable, pArg->iColumn, target); + sqlite3VdbeAddFunctionCall(pParse, constMask, r1, target, nFarg, + pDef, pExpr->op2); + if( nFarg ){ + if( constMask==0 ){ + sqlite3ReleaseTempRange(pParse, r1, nFarg); }else{ - sqlite3VdbeAddOp2(v, OP_Null, 0, target); + sqlite3VdbeReleaseRegisters(pParse, r1, nFarg, constMask, 1); } - }else -#endif - { - sqlite3VdbeAddOp4(v, pParse->iSelfTab ? OP_PureFunc0 : OP_Function0, - constMask, r1, target, (char*)pDef, P4_FUNCDEF); - sqlite3VdbeChangeP5(v, (u8)nFarg); - } - if( nFarg && constMask==0 ){ - sqlite3ReleaseTempRange(pParse, r1, nFarg); } return target; } @@ -100764,7 +118307,12 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) int nCol; testcase( op==TK_EXISTS ); testcase( op==TK_SELECT ); - if( op==TK_SELECT && (nCol = pExpr->x.pSelect->pEList->nExpr)!=1 ){ + if( pParse->db->mallocFailed ){ + return 0; + }else if( op==TK_SELECT + && ALWAYS( ExprUseXSelect(pExpr) ) + && (nCol = pExpr->x.pSelect->pEList->nExpr)!=1 + ){ sqlite3SubselectError(pParse, nCol, 1); }else{ return sqlite3CodeSubselect(pParse, pExpr); @@ -100773,17 +118321,18 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) } case TK_SELECT_COLUMN: { int n; - if( pExpr->pLeft->iTable==0 ){ - pExpr->pLeft->iTable = sqlite3CodeSubselect(pParse, pExpr->pLeft); + Expr *pLeft = pExpr->pLeft; + if( pLeft->iTable==0 || pParse->withinRJSubrtn > pLeft->op2 ){ + pLeft->iTable = sqlite3CodeSubselect(pParse, pLeft); + pLeft->op2 = pParse->withinRJSubrtn; } - assert( pExpr->iTable==0 || pExpr->pLeft->op==TK_SELECT ); - if( pExpr->iTable - && pExpr->iTable!=(n = sqlite3ExprVectorSize(pExpr->pLeft)) - ){ + assert( pLeft->op==TK_SELECT || pLeft->op==TK_ERROR ); + n = sqlite3ExprVectorSize(pLeft); + if( pExpr->iTable!=n ){ sqlite3ErrorMsg(pParse, "%d columns assigned %d values", pExpr->iTable, n); } - return pExpr->pLeft->iTable + pExpr->iColumn; + return pLeft->iTable + pExpr->iColumn; } case TK_IN: { int destIfFalse = sqlite3VdbeMakeLabel(pParse); @@ -100814,8 +118363,24 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) exprCodeBetween(pParse, pExpr, target, 0, 0); return target; } + case TK_COLLATE: { + if( !ExprHasProperty(pExpr, EP_Collate) ){ + /* A TK_COLLATE Expr node without the EP_Collate tag is a so-called + ** "SOFT-COLLATE" that is added to constraints that are pushed down + ** from outer queries into sub-queries by the WHERE-clause push-down + ** optimization. Clear subtypes as subtypes may not cross a subquery + ** boundary. + */ + assert( pExpr->pLeft ); + sqlite3ExprCode(pParse, pExpr->pLeft, target); + sqlite3VdbeAddOp1(v, OP_ClrSubtype, target); + return target; + }else{ + pExpr = pExpr->pLeft; + goto expr_code_doover; /* 2018-04-28: Prevent deep recursion. */ + } + } case TK_SPAN: - case TK_COLLATE: case TK_UPLUS: { pExpr = pExpr->pLeft; goto expr_code_doover; /* 2018-04-28: Prevent deep recursion. OSSFuzz. */ @@ -100831,7 +118396,7 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) ** ** The expression is implemented using an OP_Param opcode. The p1 ** parameter is set to 0 for an old.rowid reference, or to (i+1) - ** to reference another column of the old.* pseudo-table, where + ** to reference another column of the old.* pseudo-table, where ** i is the index of the column. For a new.rowid reference, p1 is ** set to (n+1), where n is the number of columns in each pseudo-table. ** For a reference to any other column in the new.* pseudo-table, p1 @@ -100845,20 +118410,27 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) ** ** p1==0 -> old.rowid p1==3 -> new.rowid ** p1==1 -> old.a p1==4 -> new.a - ** p1==2 -> old.b p1==5 -> new.b + ** p1==2 -> old.b p1==5 -> new.b */ - Table *pTab = pExpr->y.pTab; - int p1 = pExpr->iTable * (pTab->nCol+1) + 1 + pExpr->iColumn; + Table *pTab; + int iCol; + int p1; + + assert( ExprUseYTab(pExpr) ); + pTab = pExpr->y.pTab; + iCol = pExpr->iColumn; + p1 = pExpr->iTable * (pTab->nCol+1) + 1 + + sqlite3TableColumnToStorage(pTab, iCol); assert( pExpr->iTable==0 || pExpr->iTable==1 ); - assert( pExpr->iColumn>=-1 && pExpr->iColumnnCol ); - assert( pTab->iPKey<0 || pExpr->iColumn!=pTab->iPKey ); + assert( iCol>=-1 && iColnCol ); + assert( pTab->iPKey<0 || iCol!=pTab->iPKey ); assert( p1>=0 && p1<(pTab->nCol*2+2) ); sqlite3VdbeAddOp2(v, OP_Param, p1, target); VdbeComment((v, "r[%d]=%s.%s", target, (pExpr->iTable ? "new" : "old"), - (pExpr->iColumn<0 ? "rowid" : pExpr->y.pTab->aCol[pExpr->iColumn].zName) + (pExpr->iColumn<0 ? "rowid" : pExpr->y.pTab->aCol[iCol].zCnName) )); #ifndef SQLITE_OMIT_FLOATING_POINT @@ -100867,9 +118439,7 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) ** ** EVIDENCE-OF: R-60985-57662 SQLite will convert the value back to ** floating point when extracting it from the record. */ - if( pExpr->iColumn>=0 - && pTab->aCol[pExpr->iColumn].affinity==SQLITE_AFF_REAL - ){ + if( iCol>=0 && pTab->aCol[iCol].affinity==SQLITE_AFF_REAL ){ sqlite3VdbeAddOp1(v, OP_RealAffinity, target); } #endif @@ -100881,12 +118451,43 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) break; } + /* TK_IF_NULL_ROW Expr nodes are inserted ahead of expressions + ** that derive from the right-hand table of a LEFT JOIN. The + ** Expr.iTable value is the table number for the right-hand table. + ** The expression is only evaluated if that table is not currently + ** on a LEFT JOIN NULL row. + */ case TK_IF_NULL_ROW: { int addrINR; - addrINR = sqlite3VdbeAddOp1(v, OP_IfNullRow, pExpr->iTable); - inReg = sqlite3ExprCodeTarget(pParse, pExpr->pLeft, target); + u8 okConstFactor = pParse->okConstFactor; + AggInfo *pAggInfo = pExpr->pAggInfo; + if( pAggInfo ){ + assert( pExpr->iAgg>=0 && pExpr->iAggnColumn ); + if( !pAggInfo->directMode ){ + inReg = AggInfoColumnReg(pAggInfo, pExpr->iAgg); + break; + } + if( pExpr->pAggInfo->useSortingIdx ){ + sqlite3VdbeAddOp3(v, OP_Column, pAggInfo->sortingIdxPTab, + pAggInfo->aCol[pExpr->iAgg].iSorterColumn, + target); + inReg = target; + break; + } + } + addrINR = sqlite3VdbeAddOp3(v, OP_IfNullRow, pExpr->iTable, 0, target); + /* The OP_IfNullRow opcode above can overwrite the result register with + ** NULL. So we have to ensure that the result register is not a value + ** that is suppose to be a constant. Two defenses are needed: + ** (1) Temporarily disable factoring of constant expressions + ** (2) Make sure the computed value really is stored in register + ** "target" and not someplace else. + */ + pParse->okConstFactor = 0; /* note (1) above */ + sqlite3ExprCode(pParse, pExpr->pLeft, target); + assert( target==inReg ); + pParse->okConstFactor = okConstFactor; sqlite3VdbeJumpHere(v, addrINR); - sqlite3VdbeChangeP3(v, addrINR, inReg); break; } @@ -100911,7 +118512,7 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) ** or if there is no matching Ei, the ELSE term Y, or if there is ** no ELSE term, NULL. */ - default: assert( op==TK_CASE ); { + case TK_CASE: { int endLabel; /* GOTO label for end of CASE stmt */ int nextCase; /* GOTO label for next WHEN clause */ int nExpr; /* 2x number of WHEN terms */ @@ -100921,21 +118522,27 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) Expr opCompare; /* The X==Ei expression */ Expr *pX; /* The X expression */ Expr *pTest = 0; /* X==Ei (form A) or just Ei (form B) */ + Expr *pDel = 0; + sqlite3 *db = pParse->db; - assert( !ExprHasProperty(pExpr, EP_xIsSelect) && pExpr->x.pList ); + assert( ExprUseXList(pExpr) && pExpr->x.pList!=0 ); assert(pExpr->x.pList->nExpr > 0); pEList = pExpr->x.pList; aListelem = pEList->a; nExpr = pEList->nExpr; endLabel = sqlite3VdbeMakeLabel(pParse); if( (pX = pExpr->pLeft)!=0 ){ - exprNodeCopy(&tempX, pX); + pDel = sqlite3ExprDup(db, pX, 0); + if( db->mallocFailed ){ + sqlite3ExprDelete(db, pDel); + break; + } testcase( pX->op==TK_COLUMN ); - exprToRegister(&tempX, exprCodeVector(pParse, &tempX, ®Free1)); + sqlite3ExprToRegister(pDel, exprCodeVector(pParse, pDel, ®Free1)); testcase( regFree1==0 ); memset(&opCompare, 0, sizeof(opCompare)); opCompare.op = TK_EQ; - opCompare.pLeft = &tempX; + opCompare.pLeft = pDel; pTest = &opCompare; /* Ticket b351d95f9cd5ef17e9d9dbae18f5ca8611190001: ** The value in regFree1 might get SCopy-ed into the file result. @@ -100963,34 +118570,36 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) }else{ sqlite3VdbeAddOp2(v, OP_Null, 0, target); } + sqlite3ExprDelete(db, pDel); + setDoNotMergeFlagOnCopy(v); sqlite3VdbeResolveLabel(v, endLabel); break; } #ifndef SQLITE_OMIT_TRIGGER case TK_RAISE: { - assert( pExpr->affinity==OE_Rollback - || pExpr->affinity==OE_Abort - || pExpr->affinity==OE_Fail - || pExpr->affinity==OE_Ignore + assert( pExpr->affExpr==OE_Rollback + || pExpr->affExpr==OE_Abort + || pExpr->affExpr==OE_Fail + || pExpr->affExpr==OE_Ignore ); - if( !pParse->pTriggerTab ){ + if( !pParse->pTriggerTab && !pParse->nested ){ sqlite3ErrorMsg(pParse, "RAISE() may only be used within a trigger-program"); return 0; } - if( pExpr->affinity==OE_Abort ){ + if( pExpr->affExpr==OE_Abort ){ sqlite3MayAbort(pParse); } assert( !ExprHasProperty(pExpr, EP_IntValue) ); - if( pExpr->affinity==OE_Ignore ){ - sqlite3VdbeAddOp4( - v, OP_Halt, SQLITE_OK, OE_Ignore, 0, pExpr->u.zToken,0); + if( pExpr->affExpr==OE_Ignore ){ + sqlite3VdbeAddOp2(v, OP_Halt, SQLITE_OK, OE_Ignore); VdbeCoverage(v); }else{ - sqlite3HaltConstraint(pParse, SQLITE_CONSTRAINT_TRIGGER, - pExpr->affinity, pExpr->u.zToken, 0, 0); + r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free1); + sqlite3VdbeAddOp3(v, OP_Halt, + pParse->pTriggerTab ? SQLITE_CONSTRAINT_TRIGGER : SQLITE_ERROR, + pExpr->affExpr, r1); } - break; } #endif @@ -101001,43 +118610,88 @@ SQLITE_PRIVATE int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target) } /* -** Factor out the code of the given expression to initialization time. +** Generate code that will evaluate expression pExpr just one time +** per prepared statement execution. ** -** If regDest>=0 then the result is always stored in that register and the -** result is not reusable. If regDest<0 then this routine is free to -** store the value whereever it wants. The register where the expression -** is stored is returned. When regDest<0, two identical expressions will -** code to the same register. +** If the expression uses functions (that might throw an exception) then +** guard them with an OP_Once opcode to ensure that the code is only executed +** once. If no functions are involved, then factor the code out and put it at +** the end of the prepared statement in the initialization section. +** +** If regDest>0 then the result is always stored in that register and the +** result is not reusable. If regDest<0 then this routine is free to +** store the value wherever it wants. The register where the expression +** is stored is returned. When regDest<0, two identical expressions might +** code to the same register, if they do not contain function calls and hence +** are factored out into the initialization section at the end of the +** prepared statement. */ -SQLITE_PRIVATE int sqlite3ExprCodeAtInit( +SQLITE_PRIVATE int sqlite3ExprCodeRunJustOnce( Parse *pParse, /* Parsing context */ Expr *pExpr, /* The expression to code when the VDBE initializes */ int regDest /* Store the value in this register */ ){ ExprList *p; assert( ConstFactorOk(pParse) ); + assert( regDest!=0 ); p = pParse->pConstExpr; if( regDest<0 && p ){ struct ExprList_item *pItem; int i; for(pItem=p->a, i=p->nExpr; i>0; pItem++, i--){ - if( pItem->reusable && sqlite3ExprCompare(0,pItem->pExpr,pExpr,-1)==0 ){ + if( pItem->fg.reusable + && sqlite3ExprCompare(0,pItem->pExpr,pExpr,-1)==0 + ){ return pItem->u.iConstExprReg; } } } pExpr = sqlite3ExprDup(pParse->db, pExpr, 0); - p = sqlite3ExprListAppend(pParse, p, pExpr); - if( p ){ - struct ExprList_item *pItem = &p->a[p->nExpr-1]; - pItem->reusable = regDest<0; - if( regDest<0 ) regDest = ++pParse->nMem; - pItem->u.iConstExprReg = regDest; + if( pExpr!=0 && ExprHasProperty(pExpr, EP_HasFunc) ){ + Vdbe *v = pParse->pVdbe; + int addr; + assert( v ); + addr = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); + pParse->okConstFactor = 0; + if( !pParse->db->mallocFailed ){ + if( regDest<0 ) regDest = ++pParse->nMem; + sqlite3ExprCode(pParse, pExpr, regDest); + } + pParse->okConstFactor = 1; + sqlite3ExprDelete(pParse->db, pExpr); + sqlite3VdbeJumpHere(v, addr); + }else{ + p = sqlite3ExprListAppend(pParse, p, pExpr); + if( p ){ + struct ExprList_item *pItem = &p->a[p->nExpr-1]; + pItem->fg.reusable = regDest<0; + if( regDest<0 ) regDest = ++pParse->nMem; + pItem->u.iConstExprReg = regDest; + } + pParse->pConstExpr = p; } - pParse->pConstExpr = p; return regDest; } +/* +** Make arrangements to invoke OP_Null on a range of registers +** during initialization. +*/ +SQLITE_PRIVATE SQLITE_NOINLINE void sqlite3ExprNullRegisterRange( + Parse *pParse, /* Parsing context */ + int iReg, /* First register to set to NULL */ + int nReg /* Number of sequential registers to NULL out */ +){ + u8 okConstFactor = pParse->okConstFactor; + Expr t; + memset(&t, 0, sizeof(t)); + t.op = TK_NULLS; + t.y.nReg = nReg; + pParse->okConstFactor = 1; + sqlite3ExprCodeRunJustOnce(pParse, &t, iReg); + pParse->okConstFactor = okConstFactor; +} + /* ** Generate code to evaluate an expression and store the results ** into a register. Return the register number where the results @@ -101053,13 +118707,14 @@ SQLITE_PRIVATE int sqlite3ExprCodeAtInit( */ SQLITE_PRIVATE int sqlite3ExprCodeTemp(Parse *pParse, Expr *pExpr, int *pReg){ int r2; - pExpr = sqlite3ExprSkipCollate(pExpr); + pExpr = sqlite3ExprSkipCollateAndLikely(pExpr); if( ConstFactorOk(pParse) + && ALWAYS(pExpr!=0) && pExpr->op!=TK_REGISTER - && sqlite3ExprIsConstantNotJoin(pExpr) + && sqlite3ExprIsConstantNotJoin(pParse, pExpr) ){ *pReg = 0; - r2 = sqlite3ExprCodeAtInit(pParse, pExpr, -1); + r2 = sqlite3ExprCodeRunJustOnce(pParse, pExpr, -1); }else{ int r1 = sqlite3GetTempReg(pParse); r2 = sqlite3ExprCodeTarget(pParse, pExpr, r1); @@ -101081,15 +118736,23 @@ SQLITE_PRIVATE int sqlite3ExprCodeTemp(Parse *pParse, Expr *pExpr, int *pReg){ SQLITE_PRIVATE void sqlite3ExprCode(Parse *pParse, Expr *pExpr, int target){ int inReg; + assert( pExpr==0 || !ExprHasVVAProperty(pExpr,EP_Immutable) ); assert( target>0 && target<=pParse->nMem ); - if( pExpr && pExpr->op==TK_REGISTER ){ - sqlite3VdbeAddOp2(pParse->pVdbe, OP_Copy, pExpr->iTable, target); - }else{ - inReg = sqlite3ExprCodeTarget(pParse, pExpr, target); - assert( pParse->pVdbe!=0 || pParse->db->mallocFailed ); - if( inReg!=target && pParse->pVdbe ){ - sqlite3VdbeAddOp2(pParse->pVdbe, OP_SCopy, inReg, target); + assert( pParse->pVdbe!=0 || pParse->db->mallocFailed ); + if( pParse->pVdbe==0 ) return; + inReg = sqlite3ExprCodeTarget(pParse, pExpr, target); + if( inReg!=target ){ + u8 op; + Expr *pX = sqlite3ExprSkipCollateAndLikely(pExpr); + testcase( pX!=pExpr ); + if( ALWAYS(pX) + && (ExprHasProperty(pX,EP_Subquery) || pX->op==TK_REGISTER) + ){ + op = OP_Copy; + }else{ + op = OP_SCopy; } + sqlite3VdbeAddOp2(pParse->pVdbe, op, inReg, target); } } @@ -101112,37 +118775,13 @@ SQLITE_PRIVATE void sqlite3ExprCodeCopy(Parse *pParse, Expr *pExpr, int target){ ** might choose to code the expression at initialization time. */ SQLITE_PRIVATE void sqlite3ExprCodeFactorable(Parse *pParse, Expr *pExpr, int target){ - if( pParse->okConstFactor && sqlite3ExprIsConstantNotJoin(pExpr) ){ - sqlite3ExprCodeAtInit(pParse, pExpr, target); + if( pParse->okConstFactor && sqlite3ExprIsConstantNotJoin(pParse,pExpr) ){ + sqlite3ExprCodeRunJustOnce(pParse, pExpr, target); }else{ - sqlite3ExprCode(pParse, pExpr, target); + sqlite3ExprCodeCopy(pParse, pExpr, target); } } -/* -** Generate code that evaluates the given expression and puts the result -** in register target. -** -** Also make a copy of the expression results into another "cache" register -** and modify the expression so that the next time it is evaluated, -** the result is a copy of the cache register. -** -** This routine is used for expressions that are used multiple -** times. They are evaluated once and the results of the expression -** are reused. -*/ -SQLITE_PRIVATE void sqlite3ExprCodeAndCache(Parse *pParse, Expr *pExpr, int target){ - Vdbe *v = pParse->pVdbe; - int iMem; - - assert( target>0 ); - assert( pExpr->op!=TK_REGISTER ); - sqlite3ExprCode(pParse, pExpr, target); - iMem = ++pParse->nMem; - sqlite3VdbeAddOp2(v, OP_Copy, target, iMem); - exprToRegister(pExpr, iMem); -} - /* ** Generate code that pushes the value of every element of the given ** expression list into a sequence of registers beginning at target. @@ -101182,7 +118821,7 @@ SQLITE_PRIVATE int sqlite3ExprCodeExprList( for(pItem=pList->a, i=0; ipExpr; #ifdef SQLITE_ENABLE_SORTER_REFERENCES - if( pItem->bSorterRef ){ + if( pItem->fg.bSorterRef ){ i--; n--; }else @@ -101195,17 +118834,18 @@ SQLITE_PRIVATE int sqlite3ExprCodeExprList( sqlite3VdbeAddOp2(v, copyOp, j+srcReg-1, target+i); } }else if( (flags & SQLITE_ECEL_FACTOR)!=0 - && sqlite3ExprIsConstantNotJoin(pExpr) + && sqlite3ExprIsConstantNotJoin(pParse,pExpr) ){ - sqlite3ExprCodeAtInit(pParse, pExpr, target+i); + sqlite3ExprCodeRunJustOnce(pParse, pExpr, target+i); }else{ int inReg = sqlite3ExprCodeTarget(pParse, pExpr, target+i); if( inReg!=target+i ){ VdbeOp *pOp; if( copyOp==OP_Copy - && (pOp=sqlite3VdbeGetOp(v, -1))->opcode==OP_Copy + && (pOp=sqlite3VdbeGetLastOp(v))->opcode==OP_Copy && pOp->p1+pOp->p3+1==inReg && pOp->p2+pOp->p3+1==target+i + && pOp->p5==0 /* The do-not-merge flag must be clear */ ){ pOp->p3++; }else{ @@ -101222,7 +118862,7 @@ SQLITE_PRIVATE int sqlite3ExprCodeExprList( ** ** x BETWEEN y AND z ** -** The above is equivalent to +** The above is equivalent to ** ** x>=y AND x<=z ** @@ -101244,40 +118884,44 @@ static void exprCodeBetween( void (*xJump)(Parse*,Expr*,int,int), /* Action to take */ int jumpIfNull /* Take the jump if the BETWEEN is NULL */ ){ - Expr exprAnd; /* The AND operator in x>=y AND x<=z */ + Expr exprAnd; /* The AND operator in x>=y AND x<=z */ Expr compLeft; /* The x>=y term */ Expr compRight; /* The x<=z term */ - Expr exprX; /* The x subexpression */ int regFree1 = 0; /* Temporary use register */ + Expr *pDel = 0; + sqlite3 *db = pParse->db; memset(&compLeft, 0, sizeof(Expr)); memset(&compRight, 0, sizeof(Expr)); memset(&exprAnd, 0, sizeof(Expr)); - assert( !ExprHasProperty(pExpr, EP_xIsSelect) ); - exprNodeCopy(&exprX, pExpr->pLeft); - exprAnd.op = TK_AND; - exprAnd.pLeft = &compLeft; - exprAnd.pRight = &compRight; - compLeft.op = TK_GE; - compLeft.pLeft = &exprX; - compLeft.pRight = pExpr->x.pList->a[0].pExpr; - compRight.op = TK_LE; - compRight.pLeft = &exprX; - compRight.pRight = pExpr->x.pList->a[1].pExpr; - exprToRegister(&exprX, exprCodeVector(pParse, &exprX, ®Free1)); - if( xJump ){ - xJump(pParse, &exprAnd, dest, jumpIfNull); - }else{ - /* Mark the expression is being from the ON or USING clause of a join - ** so that the sqlite3ExprCodeTarget() routine will not attempt to move - ** it into the Parse.pConstExpr list. We should use a new bit for this, - ** for clarity, but we are out of bits in the Expr.flags field so we - ** have to reuse the EP_FromJoin bit. Bummer. */ - exprX.flags |= EP_FromJoin; - sqlite3ExprCodeTarget(pParse, &exprAnd, dest); + assert( ExprUseXList(pExpr) ); + pDel = sqlite3ExprDup(db, pExpr->pLeft, 0); + if( db->mallocFailed==0 ){ + exprAnd.op = TK_AND; + exprAnd.pLeft = &compLeft; + exprAnd.pRight = &compRight; + compLeft.op = TK_GE; + compLeft.pLeft = pDel; + compLeft.pRight = pExpr->x.pList->a[0].pExpr; + compRight.op = TK_LE; + compRight.pLeft = pDel; + compRight.pRight = pExpr->x.pList->a[1].pExpr; + sqlite3ExprToRegister(pDel, exprCodeVector(pParse, pDel, ®Free1)); + if( xJump ){ + xJump(pParse, &exprAnd, dest, jumpIfNull); + }else{ + /* Mark the expression is being from the ON or USING clause of a join + ** so that the sqlite3ExprCodeTarget() routine will not attempt to move + ** it into the Parse.pConstExpr list. We should use a new bit for this, + ** for clarity, but we are out of bits in the Expr.flags field so we + ** have to reuse the EP_OuterON bit. Bummer. */ + pDel->flags |= EP_OuterON; + sqlite3ExprCodeTarget(pParse, &exprAnd, dest); + } + sqlite3ReleaseTempReg(pParse, regFree1); } - sqlite3ReleaseTempReg(pParse, regFree1); + sqlite3ExprDelete(db, pDel); /* Ensure adequate test coverage */ testcase( xJump==sqlite3ExprIfTrue && jumpIfNull==0 && regFree1==0 ); @@ -101315,20 +118959,36 @@ SQLITE_PRIVATE void sqlite3ExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int assert( jumpIfNull==SQLITE_JUMPIFNULL || jumpIfNull==0 ); if( NEVER(v==0) ) return; /* Existence of VDBE checked by caller */ if( NEVER(pExpr==0) ) return; /* No way this can happen */ + assert( !ExprHasVVAProperty(pExpr, EP_Immutable) ); op = pExpr->op; switch( op ){ - case TK_AND: { - int d2 = sqlite3VdbeMakeLabel(pParse); - testcase( jumpIfNull==0 ); - sqlite3ExprIfFalse(pParse, pExpr->pLeft, d2,jumpIfNull^SQLITE_JUMPIFNULL); - sqlite3ExprIfTrue(pParse, pExpr->pRight, dest, jumpIfNull); - sqlite3VdbeResolveLabel(v, d2); - break; - } + case TK_AND: case TK_OR: { - testcase( jumpIfNull==0 ); - sqlite3ExprIfTrue(pParse, pExpr->pLeft, dest, jumpIfNull); - sqlite3ExprIfTrue(pParse, pExpr->pRight, dest, jumpIfNull); + Expr *pAlt = sqlite3ExprSimplifiedAndOr(pExpr); + if( pAlt!=pExpr ){ + sqlite3ExprIfTrue(pParse, pAlt, dest, jumpIfNull); + }else{ + Expr *pFirst, *pSecond; + if( exprEvalRhsFirst(pExpr) ){ + pFirst = pExpr->pRight; + pSecond = pExpr->pLeft; + }else{ + pFirst = pExpr->pLeft; + pSecond = pExpr->pRight; + } + if( op==TK_AND ){ + int d2 = sqlite3VdbeMakeLabel(pParse); + testcase( jumpIfNull==0 ); + sqlite3ExprIfFalse(pParse, pFirst, d2, + jumpIfNull^SQLITE_JUMPIFNULL); + sqlite3ExprIfTrue(pParse, pSecond, dest, jumpIfNull); + sqlite3VdbeResolveLabel(v, d2); + }else{ + testcase( jumpIfNull==0 ); + sqlite3ExprIfTrue(pParse, pFirst, dest, jumpIfNull); + sqlite3ExprIfTrue(pParse, pSecond, dest, jumpIfNull); + } + } break; } case TK_NOT: { @@ -101359,19 +119019,25 @@ SQLITE_PRIVATE void sqlite3ExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int testcase( op==TK_ISNOT ); op = (op==TK_IS) ? TK_EQ : TK_NE; jumpIfNull = SQLITE_NULLEQ; - /* Fall thru */ + /* no break */ deliberate_fall_through case TK_LT: case TK_LE: case TK_GT: case TK_GE: case TK_NE: case TK_EQ: { + int addrIsNull; if( sqlite3ExprIsVector(pExpr->pLeft) ) goto default_expr; - testcase( jumpIfNull==0 ); - r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free1); - r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, ®Free2); + if( ExprHasProperty(pExpr, EP_Subquery) && jumpIfNull!=SQLITE_NULLEQ ){ + addrIsNull = exprComputeOperands(pParse, pExpr, + &r1, &r2, ®Free1, ®Free2); + }else{ + r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free1); + r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, ®Free2); + addrIsNull = 0; + } codeCompare(pParse, pExpr->pLeft, pExpr->pRight, op, - r1, r2, dest, jumpIfNull); + r1, r2, dest, jumpIfNull, ExprHasProperty(pExpr,EP_Commuted)); assert(TK_LT==OP_Lt); testcase(op==OP_Lt); VdbeCoverageIf(v,op==OP_Lt); assert(TK_LE==OP_Le); testcase(op==OP_Le); VdbeCoverageIf(v,op==OP_Le); assert(TK_GT==OP_Gt); testcase(op==OP_Gt); VdbeCoverageIf(v,op==OP_Gt); @@ -101384,6 +119050,13 @@ SQLITE_PRIVATE void sqlite3ExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int VdbeCoverageIf(v, op==OP_Ne && jumpIfNull!=SQLITE_NULLEQ); testcase( regFree1==0 ); testcase( regFree2==0 ); + if( addrIsNull ){ + if( jumpIfNull ){ + sqlite3VdbeChangeP2(v, addrIsNull, dest); + }else{ + sqlite3VdbeJumpHere(v, addrIsNull); + } + } break; } case TK_ISNULL: @@ -101391,10 +119064,11 @@ SQLITE_PRIVATE void sqlite3ExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int assert( TK_ISNULL==OP_IsNull ); testcase( op==TK_ISNULL ); assert( TK_NOTNULL==OP_NotNull ); testcase( op==TK_NOTNULL ); r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free1); + assert( regFree1==0 || regFree1==r1 ); + if( regFree1 ) sqlite3VdbeTypeofColumn(v, r1); sqlite3VdbeAddOp2(v, op, r1, dest); VdbeCoverageIf(v, op==TK_ISNULL); VdbeCoverageIf(v, op==TK_NOTNULL); - testcase( regFree1==0 ); break; } case TK_BETWEEN: { @@ -101414,9 +119088,9 @@ SQLITE_PRIVATE void sqlite3ExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int #endif default: { default_expr: - if( exprAlwaysTrue(pExpr) ){ + if( ExprAlwaysTrue(pExpr) ){ sqlite3VdbeGoto(v, dest); - }else if( exprAlwaysFalse(pExpr) ){ + }else if( ExprAlwaysFalse(pExpr) ){ /* No-op */ }else{ r1 = sqlite3ExprCodeTemp(pParse, pExpr, ®Free1); @@ -101429,7 +119103,7 @@ SQLITE_PRIVATE void sqlite3ExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int } } sqlite3ReleaseTempReg(pParse, regFree1); - sqlite3ReleaseTempReg(pParse, regFree2); + sqlite3ReleaseTempReg(pParse, regFree2); } /* @@ -101451,6 +119125,7 @@ SQLITE_PRIVATE void sqlite3ExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int assert( jumpIfNull==SQLITE_JUMPIFNULL || jumpIfNull==0 ); if( NEVER(v==0) ) return; /* Existence of VDBE checked by caller */ if( pExpr==0 ) return; + assert( !ExprHasVVAProperty(pExpr,EP_Immutable) ); /* The value of pExpr->op and op are related as follows: ** @@ -101484,18 +119159,33 @@ SQLITE_PRIVATE void sqlite3ExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int assert( pExpr->op!=TK_GE || op==OP_Lt ); switch( pExpr->op ){ - case TK_AND: { - testcase( jumpIfNull==0 ); - sqlite3ExprIfFalse(pParse, pExpr->pLeft, dest, jumpIfNull); - sqlite3ExprIfFalse(pParse, pExpr->pRight, dest, jumpIfNull); - break; - } + case TK_AND: case TK_OR: { - int d2 = sqlite3VdbeMakeLabel(pParse); - testcase( jumpIfNull==0 ); - sqlite3ExprIfTrue(pParse, pExpr->pLeft, d2, jumpIfNull^SQLITE_JUMPIFNULL); - sqlite3ExprIfFalse(pParse, pExpr->pRight, dest, jumpIfNull); - sqlite3VdbeResolveLabel(v, d2); + Expr *pAlt = sqlite3ExprSimplifiedAndOr(pExpr); + if( pAlt!=pExpr ){ + sqlite3ExprIfFalse(pParse, pAlt, dest, jumpIfNull); + }else{ + Expr *pFirst, *pSecond; + if( exprEvalRhsFirst(pExpr) ){ + pFirst = pExpr->pRight; + pSecond = pExpr->pLeft; + }else{ + pFirst = pExpr->pLeft; + pSecond = pExpr->pRight; + } + if( pExpr->op==TK_AND ){ + testcase( jumpIfNull==0 ); + sqlite3ExprIfFalse(pParse, pFirst, dest, jumpIfNull); + sqlite3ExprIfFalse(pParse, pSecond, dest, jumpIfNull); + }else{ + int d2 = sqlite3VdbeMakeLabel(pParse); + testcase( jumpIfNull==0 ); + sqlite3ExprIfTrue(pParse, pFirst, d2, + jumpIfNull^SQLITE_JUMPIFNULL); + sqlite3ExprIfFalse(pParse, pSecond, dest, jumpIfNull); + sqlite3VdbeResolveLabel(v, d2); + } + } break; } case TK_NOT: { @@ -101529,19 +119219,25 @@ SQLITE_PRIVATE void sqlite3ExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int testcase( pExpr->op==TK_ISNOT ); op = (pExpr->op==TK_IS) ? TK_NE : TK_EQ; jumpIfNull = SQLITE_NULLEQ; - /* Fall thru */ + /* no break */ deliberate_fall_through case TK_LT: case TK_LE: case TK_GT: case TK_GE: case TK_NE: case TK_EQ: { + int addrIsNull; if( sqlite3ExprIsVector(pExpr->pLeft) ) goto default_expr; - testcase( jumpIfNull==0 ); - r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free1); - r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, ®Free2); + if( ExprHasProperty(pExpr, EP_Subquery) && jumpIfNull!=SQLITE_NULLEQ ){ + addrIsNull = exprComputeOperands(pParse, pExpr, + &r1, &r2, ®Free1, ®Free2); + }else{ + r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free1); + r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, ®Free2); + addrIsNull = 0; + } codeCompare(pParse, pExpr->pLeft, pExpr->pRight, op, - r1, r2, dest, jumpIfNull); + r1, r2, dest, jumpIfNull,ExprHasProperty(pExpr,EP_Commuted)); assert(TK_LT==OP_Lt); testcase(op==OP_Lt); VdbeCoverageIf(v,op==OP_Lt); assert(TK_LE==OP_Le); testcase(op==OP_Le); VdbeCoverageIf(v,op==OP_Le); assert(TK_GT==OP_Gt); testcase(op==OP_Gt); VdbeCoverageIf(v,op==OP_Gt); @@ -101554,15 +119250,23 @@ SQLITE_PRIVATE void sqlite3ExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int VdbeCoverageIf(v, op==OP_Ne && jumpIfNull==SQLITE_NULLEQ); testcase( regFree1==0 ); testcase( regFree2==0 ); + if( addrIsNull ){ + if( jumpIfNull ){ + sqlite3VdbeChangeP2(v, addrIsNull, dest); + }else{ + sqlite3VdbeJumpHere(v, addrIsNull); + } + } break; } case TK_ISNULL: case TK_NOTNULL: { r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free1); + assert( regFree1==0 || regFree1==r1 ); + if( regFree1 ) sqlite3VdbeTypeofColumn(v, r1); sqlite3VdbeAddOp2(v, op, r1, dest); testcase( op==TK_ISNULL ); VdbeCoverageIf(v, op==TK_ISNULL); testcase( op==TK_NOTNULL ); VdbeCoverageIf(v, op==TK_NOTNULL); - testcase( regFree1==0 ); break; } case TK_BETWEEN: { @@ -101583,10 +119287,10 @@ SQLITE_PRIVATE void sqlite3ExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int } #endif default: { - default_expr: - if( exprAlwaysFalse(pExpr) ){ + default_expr: + if( ExprAlwaysFalse(pExpr) ){ sqlite3VdbeGoto(v, dest); - }else if( exprAlwaysTrue(pExpr) ){ + }else if( ExprAlwaysTrue(pExpr) ){ /* no-op */ }else{ r1 = sqlite3ExprCodeTemp(pParse, pExpr, ®Free1); @@ -101628,12 +119332,23 @@ SQLITE_PRIVATE void sqlite3ExprIfFalseDup(Parse *pParse, Expr *pExpr, int dest,i ** same as that currently bound to variable pVar, non-zero is returned. ** Otherwise, if the values are not the same or if pExpr is not a simple ** SQL value, zero is returned. +** +** If the SQLITE_EnableQPSG flag is set on the database connection, then +** this routine always returns false. */ -static int exprCompareVariable(Parse *pParse, Expr *pVar, Expr *pExpr){ - int res = 0; +static SQLITE_NOINLINE int exprCompareVariable( + const Parse *pParse, + const Expr *pVar, + const Expr *pExpr +){ + int res = 2; int iVar; sqlite3_value *pL, *pR = 0; - + + if( pExpr->op==TK_VARIABLE && pVar->iColumn==pExpr->iColumn ){ + return 0; + } + if( (pParse->db->flags & SQLITE_EnableQPSG)!=0 ) return 2; sqlite3ValueFromExpr(pParse->db, pExpr, SQLITE_UTF8, SQLITE_AFF_BLOB, &pR); if( pR ){ iVar = pVar->iColumn; @@ -101643,12 +119358,11 @@ static int exprCompareVariable(Parse *pParse, Expr *pVar, Expr *pExpr){ if( sqlite3_value_type(pL)==SQLITE_TEXT ){ sqlite3_value_text(pL); /* Make sure the encoding is UTF-8 */ } - res = 0==sqlite3MemCompare(pL, pR, 0); + res = sqlite3MemCompare(pL, pR, 0) ? 2 : 0; } sqlite3ValueFree(pR); sqlite3ValueFree(pL); } - return res; } @@ -101674,20 +119388,23 @@ static int exprCompareVariable(Parse *pParse, Expr *pVar, Expr *pExpr){ ** just might result in some slightly slower code. But returning ** an incorrect 0 or 1 could lead to a malfunction. ** -** If pParse is not NULL then TK_VARIABLE terms in pA with bindings in -** pParse->pReprepare can be matched against literals in pB. The -** pParse->pVdbe->expmask bitmask is updated for each variable referenced. -** If pParse is NULL (the normal case) then any TK_VARIABLE term in -** Argument pParse should normally be NULL. If it is not NULL and pA or -** pB causes a return value of 2. +** If pParse is not NULL and SQLITE_EnableQPSG is off then TK_VARIABLE +** terms in pA with bindings in pParse->pReprepare can be matched against +** literals in pB. The pParse->pVdbe->expmask bitmask is updated for +** each variable referenced. */ -SQLITE_PRIVATE int sqlite3ExprCompare(Parse *pParse, Expr *pA, Expr *pB, int iTab){ +SQLITE_PRIVATE int sqlite3ExprCompare( + const Parse *pParse, + const Expr *pA, + const Expr *pB, + int iTab +){ u32 combinedFlags; if( pA==0 || pB==0 ){ return pB==pA ? 0 : 2; } - if( pParse && pA->op==TK_VARIABLE && exprCompareVariable(pParse, pA, pB) ){ - return 0; + if( pParse && pA->op==TK_VARIABLE ){ + return exprCompareVariable(pParse, pA, pB); } combinedFlags = pA->flags | pB->flags; if( combinedFlags & EP_IntValue ){ @@ -101703,35 +119420,46 @@ SQLITE_PRIVATE int sqlite3ExprCompare(Parse *pParse, Expr *pA, Expr *pB, int iTa if( pB->op==TK_COLLATE && sqlite3ExprCompare(pParse, pA,pB->pLeft,iTab)<2 ){ return 1; } - return 2; + if( pA->op==TK_AGG_COLUMN && pB->op==TK_COLUMN + && pB->iTable<0 && pA->iTable==iTab + ){ + /* fall through */ + }else{ + return 2; + } } - if( pA->op!=TK_COLUMN && pA->op!=TK_AGG_COLUMN && pA->u.zToken ){ - if( pA->op==TK_FUNCTION ){ + assert( !ExprHasProperty(pA, EP_IntValue) ); + assert( !ExprHasProperty(pB, EP_IntValue) ); + if( pA->u.zToken ){ + if( pA->op==TK_FUNCTION || pA->op==TK_AGG_FUNCTION ){ if( sqlite3StrICmp(pA->u.zToken,pB->u.zToken)!=0 ) return 2; #ifndef SQLITE_OMIT_WINDOWFUNC - /* Justification for the assert(): - ** window functions have p->op==TK_FUNCTION but aggregate functions - ** have p->op==TK_AGG_FUNCTION. So any comparison between an aggregate - ** function and a window function should have failed before reaching - ** this point. And, it is not possible to have a window function and - ** a scalar function with the same name and number of arguments. So - ** if we reach this point, either A and B both window functions or - ** neither are a window functions. */ - assert( ExprHasProperty(pA,EP_WinFunc)==ExprHasProperty(pB,EP_WinFunc) ); + assert( pA->op==pB->op ); + if( ExprHasProperty(pA,EP_WinFunc)!=ExprHasProperty(pB,EP_WinFunc) ){ + return 2; + } if( ExprHasProperty(pA,EP_WinFunc) ){ - if( sqlite3WindowCompare(pParse,pA->y.pWin,pB->y.pWin)!=0 ) return 2; + if( sqlite3WindowCompare(pParse, pA->y.pWin, pB->y.pWin, 1)!=0 ){ + return 2; + } } #endif }else if( pA->op==TK_NULL ){ return 0; }else if( pA->op==TK_COLLATE ){ if( sqlite3_stricmp(pA->u.zToken,pB->u.zToken)!=0 ) return 2; - }else if( ALWAYS(pB->u.zToken!=0) && strcmp(pA->u.zToken,pB->u.zToken)!=0 ){ + }else + if( pB->u.zToken!=0 + && pA->op!=TK_COLUMN + && pA->op!=TK_AGG_COLUMN + && strcmp(pA->u.zToken,pB->u.zToken)!=0 + ){ return 2; } } - if( (pA->flags & EP_Distinct)!=(pB->flags & EP_Distinct) ) return 2; - if( (combinedFlags & EP_TokenOnly)==0 ){ + if( (pA->flags & (EP_Distinct|EP_Commuted)) + != (pB->flags & (EP_Distinct|EP_Commuted)) ) return 2; + if( ALWAYS((combinedFlags & EP_TokenOnly)==0) ){ if( combinedFlags & EP_xIsSelect ) return 2; if( (combinedFlags & EP_FixedCol)==0 && sqlite3ExprCompare(pParse, pA->pLeft, pB->pLeft, iTab) ) return 2; @@ -101739,19 +119467,22 @@ SQLITE_PRIVATE int sqlite3ExprCompare(Parse *pParse, Expr *pA, Expr *pB, int iTa if( sqlite3ExprListCompare(pA->x.pList, pB->x.pList, iTab) ) return 2; if( pA->op!=TK_STRING && pA->op!=TK_TRUEFALSE - && (combinedFlags & EP_Reduced)==0 + && ALWAYS((combinedFlags & EP_Reduced)==0) ){ if( pA->iColumn!=pB->iColumn ) return 2; - if( pA->iTable!=pB->iTable - && (pA->iTable!=iTab || NEVER(pB->iTable>=0)) ) return 2; + if( pA->op2!=pB->op2 && pA->op==TK_TRUTH ) return 2; + if( pA->op!=TK_IN && pA->iTable!=pB->iTable && pA->iTable!=iTab ){ + return 2; + } } } return 0; } /* -** Compare two ExprList objects. Return 0 if they are identical and -** non-zero if they differ in any way. +** Compare two ExprList objects. Return 0 if they are identical, 1 +** if they are certainly different, or 2 if it is not possible to +** determine if they are identical or not. ** ** If any subelement of pB has Expr.iTable==(-1) then it is allowed ** to compare equal to an equivalent element in pA with Expr.iTable==iTab. @@ -101764,16 +119495,17 @@ SQLITE_PRIVATE int sqlite3ExprCompare(Parse *pParse, Expr *pA, Expr *pB, int iTa ** Two NULL pointers are considered to be the same. But a NULL pointer ** always differs from a non-NULL pointer. */ -SQLITE_PRIVATE int sqlite3ExprListCompare(ExprList *pA, ExprList *pB, int iTab){ +SQLITE_PRIVATE int sqlite3ExprListCompare(const ExprList *pA, const ExprList *pB, int iTab){ int i; if( pA==0 && pB==0 ) return 0; if( pA==0 || pB==0 ) return 1; if( pA->nExpr!=pB->nExpr ) return 1; for(i=0; inExpr; i++){ + int res; Expr *pExprA = pA->a[i].pExpr; Expr *pExprB = pB->a[i].pExpr; - if( pA->a[i].sortOrder!=pB->a[i].sortOrder ) return 1; - if( sqlite3ExprCompare(0, pExprA, pExprB, iTab) ) return 1; + if( pA->a[i].fg.sortFlags!=pB->a[i].fg.sortFlags ) return 1; + if( (res = sqlite3ExprCompare(0, pExprA, pExprB, iTab)) ) return res; } return 0; } @@ -101782,39 +119514,174 @@ SQLITE_PRIVATE int sqlite3ExprListCompare(ExprList *pA, ExprList *pB, int iTab){ ** Like sqlite3ExprCompare() except COLLATE operators at the top-level ** are ignored. */ -SQLITE_PRIVATE int sqlite3ExprCompareSkip(Expr *pA, Expr *pB, int iTab){ +SQLITE_PRIVATE int sqlite3ExprCompareSkip(Expr *pA,Expr *pB, int iTab){ return sqlite3ExprCompare(0, sqlite3ExprSkipCollate(pA), sqlite3ExprSkipCollate(pB), iTab); } +/* +** Return non-zero if Expr p can only be true if pNN is not NULL. +** +** Or if seenNot is true, return non-zero if Expr p can only be +** non-NULL if pNN is not NULL +*/ +static int exprImpliesNotNull( + const Parse *pParse,/* Parsing context */ + const Expr *p, /* The expression to be checked */ + const Expr *pNN, /* The expression that is NOT NULL */ + int iTab, /* Table being evaluated */ + int seenNot /* Return true only if p can be any non-NULL value */ +){ + assert( p ); + assert( pNN ); + if( sqlite3ExprCompare(pParse, p, pNN, iTab)==0 ){ + return pNN->op!=TK_NULL; + } + switch( p->op ){ + case TK_IN: { + if( seenNot && ExprHasProperty(p, EP_xIsSelect) ) return 0; + assert( ExprUseXSelect(p) || (p->x.pList!=0 && p->x.pList->nExpr>0) ); + return exprImpliesNotNull(pParse, p->pLeft, pNN, iTab, 1); + } + case TK_BETWEEN: { + ExprList *pList; + assert( ExprUseXList(p) ); + pList = p->x.pList; + assert( pList!=0 ); + assert( pList->nExpr==2 ); + if( seenNot ) return 0; + if( exprImpliesNotNull(pParse, pList->a[0].pExpr, pNN, iTab, 1) + || exprImpliesNotNull(pParse, pList->a[1].pExpr, pNN, iTab, 1) + ){ + return 1; + } + return exprImpliesNotNull(pParse, p->pLeft, pNN, iTab, 1); + } + case TK_EQ: + case TK_NE: + case TK_LT: + case TK_LE: + case TK_GT: + case TK_GE: + case TK_PLUS: + case TK_MINUS: + case TK_BITOR: + case TK_LSHIFT: + case TK_RSHIFT: + case TK_CONCAT: + seenNot = 1; + /* no break */ deliberate_fall_through + case TK_STAR: + case TK_REM: + case TK_BITAND: + case TK_SLASH: { + if( exprImpliesNotNull(pParse, p->pRight, pNN, iTab, seenNot) ) return 1; + /* no break */ deliberate_fall_through + } + case TK_SPAN: + case TK_COLLATE: + case TK_UPLUS: + case TK_UMINUS: { + return exprImpliesNotNull(pParse, p->pLeft, pNN, iTab, seenNot); + } + case TK_TRUTH: { + if( seenNot ) return 0; + if( p->op2!=TK_IS ) return 0; + return exprImpliesNotNull(pParse, p->pLeft, pNN, iTab, 1); + } + case TK_BITNOT: + case TK_NOT: { + return exprImpliesNotNull(pParse, p->pLeft, pNN, iTab, 1); + } + } + return 0; +} + +/* +** Return true if the boolean value of the expression is always either +** FALSE or NULL. +*/ +static int sqlite3ExprIsNotTrue(Expr *pExpr){ + int v; + if( pExpr->op==TK_NULL ) return 1; + if( pExpr->op==TK_TRUEFALSE && sqlite3ExprTruthValue(pExpr)==0 ) return 1; + v = 1; + if( sqlite3ExprIsInteger(pExpr, &v, 0) && v==0 ) return 1; + return 0; +} + +/* +** Return true if the expression is one of the following: +** +** CASE WHEN x THEN y END +** CASE WHEN x THEN y ELSE NULL END +** CASE WHEN x THEN y ELSE false END +** iif(x,y) +** iif(x,y,NULL) +** iif(x,y,false) +*/ +static int sqlite3ExprIsIIF(sqlite3 *db, const Expr *pExpr){ + ExprList *pList; + if( pExpr->op==TK_FUNCTION ){ + const char *z = pExpr->u.zToken; + FuncDef *pDef; + if( (z[0]!='i' && z[0]!='I') ) return 0; + if( pExpr->x.pList==0 ) return 0; + pDef = sqlite3FindFunction(db, z, pExpr->x.pList->nExpr, ENC(db), 0); +#ifdef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION + if( pDef==0 ) return 0; +#else + if( NEVER(pDef==0) ) return 0; +#endif + if( (pDef->funcFlags & SQLITE_FUNC_INLINE)==0 ) return 0; + if( SQLITE_PTR_TO_INT(pDef->pUserData)!=INLINEFUNC_iif ) return 0; + }else if( pExpr->op==TK_CASE ){ + if( pExpr->pLeft!=0 ) return 0; + }else{ + return 0; + } + pList = pExpr->x.pList; + assert( pList!=0 ); + if( pList->nExpr==2 ) return 1; + if( pList->nExpr==3 && sqlite3ExprIsNotTrue(pList->a[2].pExpr) ) return 1; + return 0; +} + /* ** Return true if we can prove the pE2 will always be true if pE1 is ** true. Return false if we cannot complete the proof or if pE2 might ** be false. Examples: ** -** pE1: x==5 pE2: x==5 Result: true -** pE1: x>0 pE2: x==5 Result: false -** pE1: x=21 pE2: x=21 OR y=43 Result: true -** pE1: x!=123 pE2: x IS NOT NULL Result: true -** pE1: x!=?1 pE2: x IS NOT NULL Result: true -** pE1: x IS NULL pE2: x IS NOT NULL Result: false -** pE1: x IS ?2 pE2: x IS NOT NULL Reuslt: false +** pE1: x==5 pE2: x==5 Result: true +** pE1: x>0 pE2: x==5 Result: false +** pE1: x=21 pE2: x=21 OR y=43 Result: true +** pE1: x!=123 pE2: x IS NOT NULL Result: true +** pE1: x!=?1 pE2: x IS NOT NULL Result: true +** pE1: x IS NULL pE2: x IS NOT NULL Result: false +** pE1: x IS ?2 pE2: x IS NOT NULL Result: false +** pE1: iif(x,y) pE2: x Result: true +** PE1: iif(x,y,0) pE2: x Result: true ** ** When comparing TK_COLUMN nodes between pE1 and pE2, if pE2 has ** Expr.iTable<0 then assume a table number given by iTab. ** -** If pParse is not NULL, then the values of bound variables in pE1 are +** If pParse is not NULL, then the values of bound variables in pE1 are ** compared against literal values in pE2 and pParse->pVdbe->expmask is -** modified to record which bound variables are referenced. If pParse +** modified to record which bound variables are referenced. If pParse ** is NULL, then false will be returned if pE1 contains any bound variables. ** ** When in doubt, return false. Returning true might give a performance ** improvement. Returning false might cause a performance reduction, but ** it will always give the correct answer and is hence always safe. */ -SQLITE_PRIVATE int sqlite3ExprImpliesExpr(Parse *pParse, Expr *pE1, Expr *pE2, int iTab){ +SQLITE_PRIVATE int sqlite3ExprImpliesExpr( + const Parse *pParse, + const Expr *pE1, + const Expr *pE2, + int iTab +){ if( sqlite3ExprCompare(pParse, pE1, pE2, iTab)==0 ){ return 1; } @@ -101824,19 +119691,40 @@ SQLITE_PRIVATE int sqlite3ExprImpliesExpr(Parse *pParse, Expr *pE1, Expr *pE2, i ){ return 1; } - if( pE2->op==TK_NOTNULL && pE1->op!=TK_ISNULL && pE1->op!=TK_IS ){ - Expr *pX = sqlite3ExprSkipCollate(pE1->pLeft); - testcase( pX!=pE1->pLeft ); - if( sqlite3ExprCompare(pParse, pX, pE2->pLeft, iTab)==0 ) return 1; + if( pE2->op==TK_NOTNULL + && exprImpliesNotNull(pParse, pE1, pE2->pLeft, iTab, 0) + ){ + return 1; + } + if( sqlite3ExprIsIIF(pParse->db, pE1) ){ + return sqlite3ExprImpliesExpr(pParse,pE1->x.pList->a[0].pExpr,pE2,iTab); } return 0; } +/* This is a helper function to impliesNotNullRow(). In this routine, +** set pWalker->eCode to one only if *both* of the input expressions +** separately have the implies-not-null-row property. +*/ +static void bothImplyNotNullRow(Walker *pWalker, Expr *pE1, Expr *pE2){ + if( pWalker->eCode==0 ){ + sqlite3WalkExpr(pWalker, pE1); + if( pWalker->eCode ){ + pWalker->eCode = 0; + sqlite3WalkExpr(pWalker, pE2); + } + } +} + /* -** This is the Expr node callback for sqlite3ExprImpliesNotNullRow(). +** This is the Expr node callback for sqlite3ExprImpliesNonNullRow(). ** If the expression node requires that the table at pWalker->iCur ** have one or more non-NULL column, then set pWalker->eCode to 1 and abort. ** +** pWalker->mWFlags is non-zero if this inquiry is being undertaking on +** behalf of a RIGHT JOIN (or FULL JOIN). That makes a difference when +** evaluating terms in the ON clause of an inner join. +** ** This routine controls an optimization. False positives (setting ** pWalker->eCode to 1 when it should not be) are deadly, but false-negatives ** (never setting pWalker->eCode) is a harmless missed optimization. @@ -101844,27 +119732,34 @@ SQLITE_PRIVATE int sqlite3ExprImpliesExpr(Parse *pParse, Expr *pE1, Expr *pE2, i static int impliesNotNullRow(Walker *pWalker, Expr *pExpr){ testcase( pExpr->op==TK_AGG_COLUMN ); testcase( pExpr->op==TK_AGG_FUNCTION ); - if( ExprHasProperty(pExpr, EP_FromJoin) ) return WRC_Prune; + if( ExprHasProperty(pExpr, EP_OuterON) ) return WRC_Prune; + if( ExprHasProperty(pExpr, EP_InnerON) && pWalker->mWFlags ){ + /* If iCur is used in an inner-join ON clause to the left of a + ** RIGHT JOIN, that does *not* mean that the table must be non-null. + ** But it is difficult to check for that condition precisely. + ** To keep things simple, any use of iCur from any inner-join is + ** ignored while attempting to simplify a RIGHT JOIN. */ + return WRC_Prune; + } switch( pExpr->op ){ case TK_ISNOT: - case TK_NOT: case TK_ISNULL: case TK_NOTNULL: case TK_IS: - case TK_OR: - case TK_CASE: - case TK_IN: + case TK_VECTOR: case TK_FUNCTION: + case TK_TRUTH: + case TK_CASE: testcase( pExpr->op==TK_ISNOT ); - testcase( pExpr->op==TK_NOT ); testcase( pExpr->op==TK_ISNULL ); testcase( pExpr->op==TK_NOTNULL ); testcase( pExpr->op==TK_IS ); - testcase( pExpr->op==TK_OR ); - testcase( pExpr->op==TK_CASE ); - testcase( pExpr->op==TK_IN ); + testcase( pExpr->op==TK_VECTOR ); testcase( pExpr->op==TK_FUNCTION ); + testcase( pExpr->op==TK_TRUTH ); + testcase( pExpr->op==TK_CASE ); return WRC_Prune; + case TK_COLUMN: if( pWalker->u.iCur==pExpr->iTable ){ pWalker->eCode = 1; @@ -101872,6 +119767,40 @@ static int impliesNotNullRow(Walker *pWalker, Expr *pExpr){ } return WRC_Prune; + case TK_OR: + case TK_AND: + /* Both sides of an AND or OR must separately imply non-null-row. + ** Consider these cases: + ** 1. NOT (x AND y) + ** 2. x OR y + ** If only one of x or y is non-null-row, then the overall expression + ** can be true if the other arm is false (case 1) or true (case 2). + */ + testcase( pExpr->op==TK_OR ); + testcase( pExpr->op==TK_AND ); + bothImplyNotNullRow(pWalker, pExpr->pLeft, pExpr->pRight); + return WRC_Prune; + + case TK_IN: + /* Beware of "x NOT IN ()" and "x NOT IN (SELECT 1 WHERE false)", + ** both of which can be true. But apart from these cases, if + ** the left-hand side of the IN is NULL then the IN itself will be + ** NULL. */ + if( ExprUseXList(pExpr) && ALWAYS(pExpr->x.pList->nExpr>0) ){ + sqlite3WalkExpr(pWalker, pExpr->pLeft); + } + return WRC_Prune; + + case TK_BETWEEN: + /* In "x NOT BETWEEN y AND z" either x must be non-null-row or else + ** both y and z must be non-null row */ + assert( ExprUseXList(pExpr) ); + assert( pExpr->x.pList->nExpr==2 ); + sqlite3WalkExpr(pWalker, pExpr->pLeft); + bothImplyNotNullRow(pWalker, pExpr->x.pList->a[0].pExpr, + pExpr->x.pList->a[1].pExpr); + return WRC_Prune; + /* Virtual tables are allowed to use constraints like x=NULL. So ** a term of the form x=y does not prove that y is not null if x ** is the column of a virtual table */ @@ -101880,18 +119809,30 @@ static int impliesNotNullRow(Walker *pWalker, Expr *pExpr){ case TK_LT: case TK_LE: case TK_GT: - case TK_GE: + case TK_GE: { + Expr *pLeft = pExpr->pLeft; + Expr *pRight = pExpr->pRight; testcase( pExpr->op==TK_EQ ); testcase( pExpr->op==TK_NE ); testcase( pExpr->op==TK_LT ); testcase( pExpr->op==TK_LE ); testcase( pExpr->op==TK_GT ); testcase( pExpr->op==TK_GE ); - if( (pExpr->pLeft->op==TK_COLUMN && IsVirtual(pExpr->pLeft->y.pTab)) - || (pExpr->pRight->op==TK_COLUMN && IsVirtual(pExpr->pRight->y.pTab)) + /* The y.pTab=0 assignment in wherecode.c always happens after the + ** impliesNotNullRow() test */ + assert( pLeft->op!=TK_COLUMN || ExprUseYTab(pLeft) ); + assert( pRight->op!=TK_COLUMN || ExprUseYTab(pRight) ); + if( (pLeft->op==TK_COLUMN + && ALWAYS(pLeft->y.pTab!=0) + && IsVirtual(pLeft->y.pTab)) + || (pRight->op==TK_COLUMN + && ALWAYS(pRight->y.pTab!=0) + && IsVirtual(pRight->y.pTab)) ){ - return WRC_Prune; + return WRC_Prune; } + /* no break */ deliberate_fall_through + } default: return WRC_Continue; } @@ -101910,8 +119851,8 @@ static int impliesNotNullRow(Walker *pWalker, Expr *pExpr){ ** False positives are not allowed, however. A false positive may result ** in an incorrect answer. ** -** Terms of p that are marked with EP_FromJoin (and hence that come from -** the ON or USING clauses of LEFT JOINS) are excluded from the analysis. +** Terms of p that are marked with EP_OuterON (and hence that come from +** the ON or USING clauses of OUTER JOINS) are excluded from the analysis. ** ** This routine is used to check if a LEFT JOIN can be converted into ** an ordinary JOIN. The p argument is the WHERE clause. If the WHERE @@ -101919,23 +119860,23 @@ static int impliesNotNullRow(Walker *pWalker, Expr *pExpr){ ** be non-NULL, then the LEFT JOIN can be safely converted into an ** ordinary join. */ -SQLITE_PRIVATE int sqlite3ExprImpliesNonNullRow(Expr *p, int iTab){ +SQLITE_PRIVATE int sqlite3ExprImpliesNonNullRow(Expr *p, int iTab, int isRJ){ Walker w; - p = sqlite3ExprSkipCollate(p); - while( p ){ - if( p->op==TK_NOTNULL ){ - p = p->pLeft; - }else if( p->op==TK_AND ){ - if( sqlite3ExprImpliesNonNullRow(p->pLeft, iTab) ) return 1; + p = sqlite3ExprSkipCollateAndLikely(p); + if( p==0 ) return 0; + if( p->op==TK_NOTNULL ){ + p = p->pLeft; + }else{ + while( p->op==TK_AND ){ + if( sqlite3ExprImpliesNonNullRow(p->pLeft, iTab, isRJ) ) return 1; p = p->pRight; - }else{ - break; } } w.xExprCallback = impliesNotNullRow; w.xSelectCallback = 0; w.xSelectCallback2 = 0; w.eCode = 0; + w.mWFlags = isRJ!=0; w.u.iCur = iTab; sqlite3WalkExpr(&w, p); return w.eCode; @@ -101954,14 +119895,14 @@ struct IdxCover { }; /* -** Check to see if there are references to columns in table +** Check to see if there are references to columns in table ** pWalker->u.pIdxCover->iCur can be satisfied using the index ** pWalker->u.pIdxCover->pIdx. */ static int exprIdxCover(Walker *pWalker, Expr *pExpr){ if( pExpr->op==TK_COLUMN && pExpr->iTable==pWalker->u.pIdxCover->iCur - && sqlite3ColumnOfIndex(pWalker->u.pIdxCover->pIdx, pExpr->iColumn)<0 + && sqlite3TableColumnToIndex(pWalker->u.pIdxCover->pIdx, pExpr->iColumn)<0 ){ pWalker->eCode = 1; return WRC_Abort; @@ -101996,62 +119937,187 @@ SQLITE_PRIVATE int sqlite3ExprCoveredByIndex( } -/* -** An instance of the following structure is used by the tree walker -** to count references to table columns in the arguments of an -** aggregate function, in order to implement the -** sqlite3FunctionThisSrc() routine. -*/ -struct SrcCount { - SrcList *pSrc; /* One particular FROM clause in a nested query */ - int nThis; /* Number of references to columns in pSrcList */ - int nOther; /* Number of references to columns in other FROM clauses */ +/* Structure used to pass information throughout the Walker in order to +** implement sqlite3ReferencesSrcList(). +*/ +struct RefSrcList { + sqlite3 *db; /* Database connection used for sqlite3DbRealloc() */ + SrcList *pRef; /* Looking for references to these tables */ + i64 nExclude; /* Number of tables to exclude from the search */ + int *aiExclude; /* Cursor IDs for tables to exclude from the search */ }; /* -** Count the number of references to columns. +** Walker SELECT callbacks for sqlite3ReferencesSrcList(). +** +** When entering a new subquery on the pExpr argument, add all FROM clause +** entries for that subquery to the exclude list. +** +** When leaving the subquery, remove those entries from the exclude list. */ -static int exprSrcCount(Walker *pWalker, Expr *pExpr){ - /* The NEVER() on the second term is because sqlite3FunctionUsesThisSrc() - ** is always called before sqlite3ExprAnalyzeAggregates() and so the - ** TK_COLUMNs have not yet been converted into TK_AGG_COLUMN. If - ** sqlite3FunctionUsesThisSrc() is used differently in the future, the - ** NEVER() will need to be removed. */ - if( pExpr->op==TK_COLUMN || NEVER(pExpr->op==TK_AGG_COLUMN) ){ +static int selectRefEnter(Walker *pWalker, Select *pSelect){ + struct RefSrcList *p = pWalker->u.pRefSrcList; + SrcList *pSrc = pSelect->pSrc; + i64 i, j; + int *piNew; + if( pSrc->nSrc==0 ) return WRC_Continue; + j = p->nExclude; + p->nExclude += pSrc->nSrc; + piNew = sqlite3DbRealloc(p->db, p->aiExclude, p->nExclude*sizeof(int)); + if( piNew==0 ){ + p->nExclude = 0; + return WRC_Abort; + }else{ + p->aiExclude = piNew; + } + for(i=0; inSrc; i++, j++){ + p->aiExclude[j] = pSrc->a[i].iCursor; + } + return WRC_Continue; +} +static void selectRefLeave(Walker *pWalker, Select *pSelect){ + struct RefSrcList *p = pWalker->u.pRefSrcList; + SrcList *pSrc = pSelect->pSrc; + if( p->nExclude ){ + assert( p->nExclude>=pSrc->nSrc ); + p->nExclude -= pSrc->nSrc; + } +} + +/* This is the Walker EXPR callback for sqlite3ReferencesSrcList(). +** +** Set the 0x01 bit of pWalker->eCode if there is a reference to any +** of the tables shown in RefSrcList.pRef. +** +** Set the 0x02 bit of pWalker->eCode if there is a reference to a +** table is in neither RefSrcList.pRef nor RefSrcList.aiExclude. +*/ +static int exprRefToSrcList(Walker *pWalker, Expr *pExpr){ + if( pExpr->op==TK_COLUMN + || pExpr->op==TK_AGG_COLUMN + ){ int i; - struct SrcCount *p = pWalker->u.pSrcCount; - SrcList *pSrc = p->pSrc; + struct RefSrcList *p = pWalker->u.pRefSrcList; + SrcList *pSrc = p->pRef; int nSrc = pSrc ? pSrc->nSrc : 0; for(i=0; iiTable==pSrc->a[i].iCursor ) break; + if( pExpr->iTable==pSrc->a[i].iCursor ){ + pWalker->eCode |= 1; + return WRC_Continue; + } } - if( inThis++; - }else{ - p->nOther++; + for(i=0; inExclude && p->aiExclude[i]!=pExpr->iTable; i++){} + if( i>=p->nExclude ){ + pWalker->eCode |= 2; } } return WRC_Continue; } /* -** Determine if any of the arguments to the pExpr Function reference -** pSrcList. Return true if they do. Also return true if the function -** has no arguments or has only constant arguments. Return false if pExpr -** references columns but not columns of tables found in pSrcList. +** Check to see if pExpr references any tables in pSrcList. +** Possible return values: +** +** 1 pExpr does references a table in pSrcList. +** +** 0 pExpr references some table that is not defined in either +** pSrcList or in subqueries of pExpr itself. +** +** -1 pExpr only references no tables at all, or it only +** references tables defined in subqueries of pExpr itself. +** +** As currently used, pExpr is always an aggregate function call. That +** fact is exploited for efficiency. */ -SQLITE_PRIVATE int sqlite3FunctionUsesThisSrc(Expr *pExpr, SrcList *pSrcList){ +SQLITE_PRIVATE int sqlite3ReferencesSrcList(Parse *pParse, Expr *pExpr, SrcList *pSrcList){ Walker w; - struct SrcCount cnt; + struct RefSrcList x; + assert( pParse->db!=0 ); + memset(&w, 0, sizeof(w)); + memset(&x, 0, sizeof(x)); + w.xExprCallback = exprRefToSrcList; + w.xSelectCallback = selectRefEnter; + w.xSelectCallback2 = selectRefLeave; + w.u.pRefSrcList = &x; + x.db = pParse->db; + x.pRef = pSrcList; assert( pExpr->op==TK_AGG_FUNCTION ); - w.xExprCallback = exprSrcCount; - w.xSelectCallback = 0; - w.u.pSrcCount = &cnt; - cnt.pSrc = pSrcList; - cnt.nThis = 0; - cnt.nOther = 0; + assert( ExprUseXList(pExpr) ); sqlite3WalkExprList(&w, pExpr->x.pList); - return cnt.nThis>0 || cnt.nOther==0; + if( pExpr->pLeft ){ + assert( pExpr->pLeft->op==TK_ORDER ); + assert( ExprUseXList(pExpr->pLeft) ); + assert( pExpr->pLeft->x.pList!=0 ); + sqlite3WalkExprList(&w, pExpr->pLeft->x.pList); + } +#ifndef SQLITE_OMIT_WINDOWFUNC + if( ExprHasProperty(pExpr, EP_WinFunc) ){ + sqlite3WalkExpr(&w, pExpr->y.pWin->pFilter); + } +#endif + if( x.aiExclude ) sqlite3DbNNFreeNN(pParse->db, x.aiExclude); + if( w.eCode & 0x01 ){ + return 1; + }else if( w.eCode ){ + return 0; + }else{ + return -1; + } +} + +/* +** This is a Walker expression node callback. +** +** For Expr nodes that contain pAggInfo pointers, make sure the AggInfo +** object that is referenced does not refer directly to the Expr. If +** it does, make a copy. This is done because the pExpr argument is +** subject to change. +** +** The copy is scheduled for deletion using the sqlite3ExprDeferredDelete() +** which builds on the sqlite3ParserAddCleanup() mechanism. +*/ +static int agginfoPersistExprCb(Walker *pWalker, Expr *pExpr){ + if( ALWAYS(!ExprHasProperty(pExpr, EP_TokenOnly|EP_Reduced)) + && pExpr->pAggInfo!=0 + ){ + AggInfo *pAggInfo = pExpr->pAggInfo; + int iAgg = pExpr->iAgg; + Parse *pParse = pWalker->pParse; + sqlite3 *db = pParse->db; + assert( iAgg>=0 ); + if( pExpr->op!=TK_AGG_FUNCTION ){ + if( iAggnColumn + && pAggInfo->aCol[iAgg].pCExpr==pExpr + ){ + pExpr = sqlite3ExprDup(db, pExpr, 0); + if( pExpr && !sqlite3ExprDeferredDelete(pParse, pExpr) ){ + pAggInfo->aCol[iAgg].pCExpr = pExpr; + } + } + }else{ + assert( pExpr->op==TK_AGG_FUNCTION ); + if( ALWAYS(iAggnFunc) + && pAggInfo->aFunc[iAgg].pFExpr==pExpr + ){ + pExpr = sqlite3ExprDup(db, pExpr, 0); + if( pExpr && !sqlite3ExprDeferredDelete(pParse, pExpr) ){ + pAggInfo->aFunc[iAgg].pFExpr = pExpr; + } + } + } + } + return WRC_Continue; +} + +/* +** Initialize a Walker object so that will persist AggInfo entries referenced +** by the tree that is walked. +*/ +SQLITE_PRIVATE void sqlite3AggInfoPersistWalkerInit(Walker *pWalker, Parse *pParse){ + memset(pWalker, 0, sizeof(*pWalker)); + pWalker->pParse = pParse; + pWalker->xExprCallback = agginfoPersistExprCb; + pWalker->xSelectCallback = sqlite3SelectWalkNoop; } /* @@ -102068,7 +120134,7 @@ static int addAggInfoColumn(sqlite3 *db, AggInfo *pInfo){ &i ); return i; -} +} /* ** Add a new element to the pAggInfo->aFunc[] array. Return the index of @@ -102077,14 +120143,89 @@ static int addAggInfoColumn(sqlite3 *db, AggInfo *pInfo){ static int addAggInfoFunc(sqlite3 *db, AggInfo *pInfo){ int i; pInfo->aFunc = sqlite3ArrayAllocate( - db, + db, pInfo->aFunc, sizeof(pInfo->aFunc[0]), &pInfo->nFunc, &i ); return i; -} +} + +/* +** Search the AggInfo object for an aCol[] entry that has iTable and iColumn. +** Return the index in aCol[] of the entry that describes that column. +** +** If no prior entry is found, create a new one and return -1. The +** new column will have an index of pAggInfo->nColumn-1. +*/ +static void findOrCreateAggInfoColumn( + Parse *pParse, /* Parsing context */ + AggInfo *pAggInfo, /* The AggInfo object to search and/or modify */ + Expr *pExpr /* Expr describing the column to find or insert */ +){ + struct AggInfo_col *pCol; + int k; + int mxTerm = pParse->db->aLimit[SQLITE_LIMIT_COLUMN]; + + assert( mxTerm <= SMXV(i16) ); + assert( pAggInfo->iFirstReg==0 ); + pCol = pAggInfo->aCol; + for(k=0; knColumn; k++, pCol++){ + if( pCol->pCExpr==pExpr ) return; + if( pCol->iTable==pExpr->iTable + && pCol->iColumn==pExpr->iColumn + && pExpr->op!=TK_IF_NULL_ROW + ){ + goto fix_up_expr; + } + } + k = addAggInfoColumn(pParse->db, pAggInfo); + if( k<0 ){ + /* OOM on resize */ + assert( pParse->db->mallocFailed ); + return; + } + if( k>mxTerm ){ + sqlite3ErrorMsg(pParse, "more than %d aggregate terms", mxTerm); + k = mxTerm; + } + pCol = &pAggInfo->aCol[k]; + assert( ExprUseYTab(pExpr) ); + pCol->pTab = pExpr->y.pTab; + pCol->iTable = pExpr->iTable; + pCol->iColumn = pExpr->iColumn; + pCol->iSorterColumn = -1; + pCol->pCExpr = pExpr; + if( pAggInfo->pGroupBy && pExpr->op!=TK_IF_NULL_ROW ){ + int j, n; + ExprList *pGB = pAggInfo->pGroupBy; + struct ExprList_item *pTerm = pGB->a; + n = pGB->nExpr; + for(j=0; jpExpr; + if( pE->op==TK_COLUMN + && pE->iTable==pExpr->iTable + && pE->iColumn==pExpr->iColumn + ){ + pCol->iSorterColumn = j; + break; + } + } + } + if( pCol->iSorterColumn<0 ){ + pCol->iSorterColumn = pAggInfo->nSortingColumn++; + } +fix_up_expr: + ExprSetVVAProperty(pExpr, EP_NoReduce); + assert( pExpr->pAggInfo==0 || pExpr->pAggInfo==pAggInfo ); + pExpr->pAggInfo = pAggInfo; + if( pExpr->op==TK_COLUMN ){ + pExpr->op = TK_AGG_COLUMN; + } + assert( k <= SMXV(pExpr->iAgg) ); + pExpr->iAgg = (i16)k; +} /* ** This is the xExprCallback for a tree walker. It is used to @@ -102099,104 +120240,133 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){ AggInfo *pAggInfo = pNC->uNC.pAggInfo; assert( pNC->ncFlags & NC_UAggInfo ); + assert( pAggInfo->iFirstReg==0 ); switch( pExpr->op ){ + default: { + IndexedExpr *pIEpr; + Expr tmp; + assert( pParse->iSelfTab==0 ); + if( (pNC->ncFlags & NC_InAggFunc)==0 ) break; + if( pParse->pIdxEpr==0 ) break; + for(pIEpr=pParse->pIdxEpr; pIEpr; pIEpr=pIEpr->pIENext){ + int iDataCur = pIEpr->iDataCur; + if( iDataCur<0 ) continue; + if( sqlite3ExprCompare(0, pExpr, pIEpr->pExpr, iDataCur)==0 ) break; + } + if( pIEpr==0 ) break; + if( NEVER(!ExprUseYTab(pExpr)) ) break; + for(i=0; inSrc; i++){ + if( pSrcList->a[i].iCursor==pIEpr->iDataCur ){ + testcase( i>0 ); + break; + } + } + if( i>=pSrcList->nSrc ) break; + if( NEVER(pExpr->pAggInfo!=0) ) break; /* Resolved by outer context */ + if( pParse->nErr ){ return WRC_Abort; } + + /* If we reach this point, it means that expression pExpr can be + ** translated into a reference to an index column as described by + ** pIEpr. + */ + memset(&tmp, 0, sizeof(tmp)); + tmp.op = TK_AGG_COLUMN; + tmp.iTable = pIEpr->iIdxCur; + tmp.iColumn = pIEpr->iIdxCol; + findOrCreateAggInfoColumn(pParse, pAggInfo, &tmp); + if( pParse->nErr ){ return WRC_Abort; } + assert( pAggInfo->aCol!=0 ); + assert( tmp.iAggnColumn ); + pAggInfo->aCol[tmp.iAgg].pCExpr = pExpr; + pExpr->pAggInfo = pAggInfo; + pExpr->iAgg = tmp.iAgg; + return WRC_Prune; + } + case TK_IF_NULL_ROW: case TK_AGG_COLUMN: case TK_COLUMN: { testcase( pExpr->op==TK_AGG_COLUMN ); testcase( pExpr->op==TK_COLUMN ); + testcase( pExpr->op==TK_IF_NULL_ROW ); /* Check to see if the column is in one of the tables in the FROM ** clause of the aggregate query */ if( ALWAYS(pSrcList!=0) ){ - struct SrcList_item *pItem = pSrcList->a; + SrcItem *pItem = pSrcList->a; for(i=0; inSrc; i++, pItem++){ - struct AggInfo_col *pCol; assert( !ExprHasProperty(pExpr, EP_TokenOnly|EP_Reduced) ); if( pExpr->iTable==pItem->iCursor ){ - /* If we reach this point, it means that pExpr refers to a table - ** that is in the FROM clause of the aggregate query. - ** - ** Make an entry for the column in pAggInfo->aCol[] if there - ** is not an entry there already. - */ - int k; - pCol = pAggInfo->aCol; - for(k=0; knColumn; k++, pCol++){ - if( pCol->iTable==pExpr->iTable && - pCol->iColumn==pExpr->iColumn ){ - break; - } - } - if( (k>=pAggInfo->nColumn) - && (k = addAggInfoColumn(pParse->db, pAggInfo))>=0 - ){ - pCol = &pAggInfo->aCol[k]; - pCol->pTab = pExpr->y.pTab; - pCol->iTable = pExpr->iTable; - pCol->iColumn = pExpr->iColumn; - pCol->iMem = ++pParse->nMem; - pCol->iSorterColumn = -1; - pCol->pExpr = pExpr; - if( pAggInfo->pGroupBy ){ - int j, n; - ExprList *pGB = pAggInfo->pGroupBy; - struct ExprList_item *pTerm = pGB->a; - n = pGB->nExpr; - for(j=0; jpExpr; - if( pE->op==TK_COLUMN && pE->iTable==pExpr->iTable && - pE->iColumn==pExpr->iColumn ){ - pCol->iSorterColumn = j; - break; - } - } - } - if( pCol->iSorterColumn<0 ){ - pCol->iSorterColumn = pAggInfo->nSortingColumn++; - } - } - /* There is now an entry for pExpr in pAggInfo->aCol[] (either - ** because it was there before or because we just created it). - ** Convert the pExpr to be a TK_AGG_COLUMN referring to that - ** pAggInfo->aCol[] entry. - */ - ExprSetVVAProperty(pExpr, EP_NoReduce); - pExpr->pAggInfo = pAggInfo; - pExpr->op = TK_AGG_COLUMN; - pExpr->iAgg = (i16)k; + findOrCreateAggInfoColumn(pParse, pAggInfo, pExpr); break; } /* endif pExpr->iTable==pItem->iCursor */ } /* end loop over pSrcList */ } - return WRC_Prune; + return WRC_Continue; } case TK_AGG_FUNCTION: { if( (pNC->ncFlags & NC_InAggFunc)==0 && pWalker->walkerDepth==pExpr->op2 + && pExpr->pAggInfo==0 ){ - /* Check to see if pExpr is a duplicate of another aggregate + /* Check to see if pExpr is a duplicate of another aggregate ** function that is already in the pAggInfo structure */ struct AggInfo_func *pItem = pAggInfo->aFunc; + int mxTerm = pParse->db->aLimit[SQLITE_LIMIT_COLUMN]; + assert( mxTerm <= SMXV(i16) ); for(i=0; inFunc; i++, pItem++){ - if( sqlite3ExprCompare(0, pItem->pExpr, pExpr, -1)==0 ){ + if( NEVER(pItem->pFExpr==pExpr) ) break; + if( sqlite3ExprCompare(0, pItem->pFExpr, pExpr, -1)==0 ){ break; } } - if( i>=pAggInfo->nFunc ){ + if( i>mxTerm ){ + sqlite3ErrorMsg(pParse, "more than %d aggregate terms", mxTerm); + i = mxTerm; + assert( inFunc ); + }else if( i>=pAggInfo->nFunc ){ /* pExpr is original. Make a new entry in pAggInfo->aFunc[] */ u8 enc = ENC(pParse->db); i = addAggInfoFunc(pParse->db, pAggInfo); if( i>=0 ){ + int nArg; assert( !ExprHasProperty(pExpr, EP_xIsSelect) ); pItem = &pAggInfo->aFunc[i]; - pItem->pExpr = pExpr; - pItem->iMem = ++pParse->nMem; - assert( !ExprHasProperty(pExpr, EP_IntValue) ); + pItem->pFExpr = pExpr; + assert( ExprUseUToken(pExpr) ); + nArg = pExpr->x.pList ? pExpr->x.pList->nExpr : 0; pItem->pFunc = sqlite3FindFunction(pParse->db, - pExpr->u.zToken, - pExpr->x.pList ? pExpr->x.pList->nExpr : 0, enc, 0); - if( pExpr->flags & EP_Distinct ){ + pExpr->u.zToken, nArg, enc, 0); + assert( pItem->bOBUnique==0 ); + if( pExpr->pLeft + && (pItem->pFunc->funcFlags & SQLITE_FUNC_NEEDCOLL)==0 + ){ + /* The NEEDCOLL test above causes any ORDER BY clause on + ** aggregate min() or max() to be ignored. */ + ExprList *pOBList; + assert( nArg>0 ); + assert( pExpr->pLeft->op==TK_ORDER ); + assert( ExprUseXList(pExpr->pLeft) ); + pItem->iOBTab = pParse->nTab++; + pOBList = pExpr->pLeft->x.pList; + assert( pOBList->nExpr>0 ); + assert( pItem->bOBUnique==0 ); + if( pOBList->nExpr==1 + && nArg==1 + && sqlite3ExprCompare(0,pOBList->a[0].pExpr, + pExpr->x.pList->a[0].pExpr,0)==0 + ){ + pItem->bOBPayload = 0; + pItem->bOBUnique = ExprHasProperty(pExpr, EP_Distinct); + }else{ + pItem->bOBPayload = 1; + } + pItem->bUseSubtype = + (pItem->pFunc->funcFlags & SQLITE_SUBTYPE)!=0; + }else{ + pItem->iOBTab = -1; + } + if( ExprHasProperty(pExpr, EP_Distinct) && !pItem->bOBUnique ){ pItem->iDistinct = pParse->nTab++; }else{ pItem->iDistinct = -1; @@ -102207,6 +120377,7 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){ */ assert( !ExprHasProperty(pExpr, EP_TokenOnly|EP_Reduced) ); ExprSetVVAProperty(pExpr, EP_NoReduce); + assert( i <= SMXV(pExpr->iAgg) ); pExpr->iAgg = (i16)i; pExpr->pAggInfo = pAggInfo; return WRC_Prune; @@ -102217,15 +120388,6 @@ static int analyzeAggregate(Walker *pWalker, Expr *pExpr){ } return WRC_Continue; } -static int analyzeAggregatesInSelect(Walker *pWalker, Select *pSelect){ - UNUSED_PARAMETER(pSelect); - pWalker->walkerDepth++; - return WRC_Continue; -} -static void analyzeAggregatesInSelectEnd(Walker *pWalker, Select *pSelect){ - UNUSED_PARAMETER(pSelect); - pWalker->walkerDepth--; -} /* ** Analyze the pExpr expression looking for aggregate functions and @@ -102239,8 +120401,8 @@ static void analyzeAggregatesInSelectEnd(Walker *pWalker, Select *pSelect){ SQLITE_PRIVATE void sqlite3ExprAnalyzeAggregates(NameContext *pNC, Expr *pExpr){ Walker w; w.xExprCallback = analyzeAggregate; - w.xSelectCallback = analyzeAggregatesInSelect; - w.xSelectCallback2 = analyzeAggregatesInSelectEnd; + w.xSelectCallback = sqlite3WalkerDepthIncrease; + w.xSelectCallback2 = sqlite3WalkerDepthDecrease; w.walkerDepth = 0; w.u.pNC = pNC; w.pParse = 0; @@ -102279,8 +120441,11 @@ SQLITE_PRIVATE int sqlite3GetTempReg(Parse *pParse){ ** purpose. */ SQLITE_PRIVATE void sqlite3ReleaseTempReg(Parse *pParse, int iReg){ - if( iReg && pParse->nTempRegaTempReg) ){ - pParse->aTempReg[pParse->nTempReg++] = iReg; + if( iReg ){ + sqlite3VdbeReleaseRegisters(pParse, iReg, 1, 0, 0); + if( pParse->nTempRegaTempReg) ){ + pParse->aTempReg[pParse->nTempReg++] = iReg; + } } } @@ -102306,6 +120471,7 @@ SQLITE_PRIVATE void sqlite3ReleaseTempRange(Parse *pParse, int iReg, int nReg){ sqlite3ReleaseTempReg(pParse, iReg); return; } + sqlite3VdbeReleaseRegisters(pParse, iReg, nReg, 0, 0); if( nReg>pParse->nRangeReg ){ pParse->nRangeReg = nReg; pParse->iRangeReg = iReg; @@ -102314,12 +120480,48 @@ SQLITE_PRIVATE void sqlite3ReleaseTempRange(Parse *pParse, int iReg, int nReg){ /* ** Mark all temporary registers as being unavailable for reuse. +** +** Always invoke this procedure after coding a subroutine or co-routine +** that might be invoked from other parts of the code, to ensure that +** the sub/co-routine does not use registers in common with the code that +** invokes the sub/co-routine. */ SQLITE_PRIVATE void sqlite3ClearTempRegCache(Parse *pParse){ pParse->nTempReg = 0; pParse->nRangeReg = 0; } +/* +** Make sure sufficient registers have been allocated so that +** iReg is a valid register number. +*/ +SQLITE_PRIVATE void sqlite3TouchRegister(Parse *pParse, int iReg){ + if( pParse->nMemnMem = iReg; +} + +#if defined(SQLITE_ENABLE_STAT4) || defined(SQLITE_DEBUG) +/* +** Return the latest reusable register in the set of all registers. +** The value returned is no less than iMin. If any register iMin or +** greater is in permanent use, then return one more than that last +** permanent register. +*/ +SQLITE_PRIVATE int sqlite3FirstAvailableRegister(Parse *pParse, int iMin){ + const ExprList *pList = pParse->pConstExpr; + if( pList ){ + int i; + for(i=0; inExpr; i++){ + if( pList->a[i].u.iConstExprReg>=iMin ){ + iMin = pList->a[i].u.iConstExprReg + 1; + } + } + } + pParse->nTempReg = 0; + pParse->nRangeReg = 0; + return iMin; +} +#endif /* SQLITE_ENABLE_STAT4 || SQLITE_DEBUG */ + /* ** Validate that no temporary register falls within the range of ** iFirst..iLast, inclusive. This routine is only call from within assert() @@ -102339,6 +120541,14 @@ SQLITE_PRIVATE int sqlite3NoTempsInRange(Parse *pParse, int iFirst, int iLast){ return 0; } } + if( pParse->pConstExpr ){ + ExprList *pList = pParse->pConstExpr; + for(i=0; inExpr; i++){ + int iReg = pList->a[i].u.iConstExprReg; + if( iReg==0 ) continue; + if( iReg>=iFirst && iReg<=iLast ) return 0; + } + } return 1; } #endif /* SQLITE_DEBUG */ @@ -102376,11 +120586,11 @@ SQLITE_PRIVATE int sqlite3NoTempsInRange(Parse *pParse, int iFirst, int iLast){ ** Or, if zName is not a system table, zero is returned. */ static int isAlterableTable(Parse *pParse, Table *pTab){ - if( 0==sqlite3StrNICmp(pTab->zName, "sqlite_", 7) + if( 0==sqlite3StrNICmp(pTab->zName, "sqlite_", 7) #ifndef SQLITE_OMIT_VIRTUALTABLE - || ( (pTab->tabFlags & TF_Shadow) - && (pParse->db->flags & SQLITE_Defensive) - && pParse->db->nVdbeExec==0 + || (pTab->tabFlags & TF_Eponymous)!=0 + || ( (pTab->tabFlags & TF_Shadow)!=0 + && sqlite3ReadOnlyShadowTables(pParse->db) ) #endif ){ @@ -102397,25 +120607,56 @@ static int isAlterableTable(Parse *pParse, Table *pTab){ ** statement to ensure that the operation has not rendered any schema ** objects unusable. */ -static void renameTestSchema(Parse *pParse, const char *zDb, int bTemp){ - sqlite3NestedParse(pParse, +static void renameTestSchema( + Parse *pParse, /* Parse context */ + const char *zDb, /* Name of db to verify schema of */ + int bTemp, /* True if this is the temp db */ + const char *zWhen, /* "when" part of error message */ + int bNoDQS /* Do not allow DQS in the schema */ +){ + pParse->colNamesSet = 1; + sqlite3NestedParse(pParse, "SELECT 1 " - "FROM \"%w\".%s " - "WHERE name NOT LIKE 'sqlite_%%'" + "FROM \"%w\"." LEGACY_SCHEMA_TABLE " " + "WHERE name NOT LIKE 'sqliteX_%%' ESCAPE 'X'" " AND sql NOT LIKE 'create virtual%%'" - " AND sqlite_rename_test(%Q, sql, type, name, %d)=NULL ", - zDb, MASTER_NAME, - zDb, bTemp + " AND sqlite_rename_test(%Q, sql, type, name, %d, %Q, %d)=NULL ", + zDb, + zDb, bTemp, zWhen, bNoDQS ); if( bTemp==0 ){ - sqlite3NestedParse(pParse, + sqlite3NestedParse(pParse, "SELECT 1 " - "FROM temp.%s " - "WHERE name NOT LIKE 'sqlite_%%'" + "FROM temp." LEGACY_SCHEMA_TABLE " " + "WHERE name NOT LIKE 'sqliteX_%%' ESCAPE 'X'" " AND sql NOT LIKE 'create virtual%%'" - " AND sqlite_rename_test(%Q, sql, type, name, 1)=NULL ", - MASTER_NAME, zDb + " AND sqlite_rename_test(%Q, sql, type, name, 1, %Q, %d)=NULL ", + zDb, zWhen, bNoDQS + ); + } +} + +/* +** Generate VM code to replace any double-quoted strings (but not double-quoted +** identifiers) within the "sql" column of the sqlite_schema table in +** database zDb with their single-quoted equivalents. If argument bTemp is +** not true, similarly update all SQL statements in the sqlite_schema table +** of the temp db. +*/ +static void renameFixQuotes(Parse *pParse, const char *zDb, int bTemp){ + sqlite3NestedParse(pParse, + "UPDATE \"%w\"." LEGACY_SCHEMA_TABLE + " SET sql = sqlite_rename_quotefix(%Q, sql)" + "WHERE name NOT LIKE 'sqliteX_%%' ESCAPE 'X'" + " AND sql NOT LIKE 'create virtual%%'" , zDb, zDb + ); + if( bTemp==0 ){ + sqlite3NestedParse(pParse, + "UPDATE temp." LEGACY_SCHEMA_TABLE + " SET sql = sqlite_rename_quotefix('temp', sql)" + "WHERE name NOT LIKE 'sqliteX_%%' ESCAPE 'X'" + " AND sql NOT LIKE 'create virtual%%'" ); } } @@ -102424,18 +120665,18 @@ static void renameTestSchema(Parse *pParse, const char *zDb, int bTemp){ ** Generate code to reload the schema for database iDb. And, if iDb!=1, for ** the temp database as well. */ -static void renameReloadSchema(Parse *pParse, int iDb){ +static void renameReloadSchema(Parse *pParse, int iDb, u16 p5){ Vdbe *v = pParse->pVdbe; if( v ){ sqlite3ChangeCookie(pParse, iDb); - sqlite3VdbeAddParseSchemaOp(pParse->pVdbe, iDb, 0); - if( iDb!=1 ) sqlite3VdbeAddParseSchemaOp(pParse->pVdbe, 1, 0); + sqlite3VdbeAddParseSchemaOp(pParse->pVdbe, iDb, 0, p5); + if( iDb!=1 ) sqlite3VdbeAddParseSchemaOp(pParse->pVdbe, 1, 0, p5); } } /* -** Generate code to implement the "ALTER TABLE xxx RENAME TO yyy" -** command. +** Generate code to implement the "ALTER TABLE xxx RENAME TO yyy" +** command. */ SQLITE_PRIVATE void sqlite3AlterRenameTable( Parse *pParse, /* Parser context. */ @@ -102445,15 +120686,13 @@ SQLITE_PRIVATE void sqlite3AlterRenameTable( int iDb; /* Database that contains the table */ char *zDb; /* Name of database iDb */ Table *pTab; /* Table being renamed */ - char *zName = 0; /* NULL-terminated version of pName */ + char *zName = 0; /* NULL-terminated version of pName */ sqlite3 *db = pParse->db; /* Database connection */ int nTabName; /* Number of UTF-8 characters in zTabName */ const char *zTabName; /* Original name of the table */ Vdbe *v; VTable *pVTab = 0; /* Non-zero if this is a v-tab with an xRename() */ - u32 savedDbFlags; /* Saved value of db->mDbFlags */ - savedDbFlags = db->mDbFlags; if( NEVER(db->mallocFailed) ) goto exit_rename_table; assert( pSrc->nSrc==1 ); assert( sqlite3BtreeHoldsAllMutexes(pParse->db) ); @@ -102462,7 +120701,6 @@ SQLITE_PRIVATE void sqlite3AlterRenameTable( if( !pTab ) goto exit_rename_table; iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema); zDb = db->aDb[iDb].zDbSName; - db->mDbFlags |= DBFLAG_PreferBuiltin; /* Get a NULL terminated version of the new table name. */ zName = sqlite3NameFromToken(db, pName); @@ -102471,8 +120709,11 @@ SQLITE_PRIVATE void sqlite3AlterRenameTable( /* Check that a table or index named 'zName' does not already exist ** in database iDb. If so, this is an error. */ - if( sqlite3FindTable(db, zName, zDb) || sqlite3FindIndex(db, zName, zDb) ){ - sqlite3ErrorMsg(pParse, + if( sqlite3FindTable(db, zName, zDb) + || sqlite3FindIndex(db, zName, zDb) + || sqlite3IsShadowTableOf(db, pTab, zName) + ){ + sqlite3ErrorMsg(pParse, "there is already another table or index with this name: %s", zName); goto exit_rename_table; } @@ -102483,12 +120724,12 @@ SQLITE_PRIVATE void sqlite3AlterRenameTable( if( SQLITE_OK!=isAlterableTable(pParse, pTab) ){ goto exit_rename_table; } - if( SQLITE_OK!=sqlite3CheckObjectName(pParse, zName) ){ goto - exit_rename_table; + if( SQLITE_OK!=sqlite3CheckObjectName(pParse,zName,"table",zName) ){ + goto exit_rename_table; } #ifndef SQLITE_OMIT_VIEW - if( pTab->pSelect ){ + if( IsView(pTab) ){ sqlite3ErrorMsg(pParse, "view %s may not be altered", pTab->zName); goto exit_rename_table; } @@ -102515,7 +120756,7 @@ SQLITE_PRIVATE void sqlite3AlterRenameTable( /* Begin a transaction for database iDb. Then modify the schema cookie ** (since the ALTER TABLE modifies the schema). Call sqlite3MayAbort(), - ** as the scalar functions (e.g. sqlite_rename_table()) invoked by the + ** as the scalar functions (e.g. sqlite_rename_table()) invoked by the ** nested SQL may raise an exception. */ v = sqlite3GetVdbe(pParse); if( v==0 ){ @@ -102529,33 +120770,34 @@ SQLITE_PRIVATE void sqlite3AlterRenameTable( /* Rewrite all CREATE TABLE, INDEX, TRIGGER or VIEW statements in ** the schema to use the new table name. */ - sqlite3NestedParse(pParse, - "UPDATE \"%w\".%s SET " + sqlite3NestedParse(pParse, + "UPDATE \"%w\"." LEGACY_SCHEMA_TABLE " SET " "sql = sqlite_rename_table(%Q, type, name, sql, %Q, %Q, %d) " "WHERE (type!='index' OR tbl_name=%Q COLLATE nocase)" - "AND name NOT LIKE 'sqlite_%%'" - , zDb, MASTER_NAME, zDb, zTabName, zName, (iDb==1), zTabName + "AND name NOT LIKE 'sqliteX_%%' ESCAPE 'X'" + , zDb, zDb, zTabName, zName, (iDb==1), zTabName ); - /* Update the tbl_name and name columns of the sqlite_master table + /* Update the tbl_name and name columns of the sqlite_schema table ** as required. */ sqlite3NestedParse(pParse, - "UPDATE %Q.%s SET " + "UPDATE %Q." LEGACY_SCHEMA_TABLE " SET " "tbl_name = %Q, " "name = CASE " "WHEN type='table' THEN %Q " - "WHEN name LIKE 'sqlite_autoindex%%' AND type='index' THEN " + "WHEN name LIKE 'sqliteX_autoindex%%' ESCAPE 'X' " + " AND type='index' THEN " "'sqlite_autoindex_' || %Q || substr(name,%d+18) " "ELSE name END " "WHERE tbl_name=%Q COLLATE nocase AND " - "(type='table' OR type='index' OR type='trigger');", - zDb, MASTER_NAME, - zName, zName, zName, + "(type='table' OR type='index' OR type='trigger');", + zDb, + zName, zName, zName, nTabName, zTabName ); #ifndef SQLITE_OMIT_AUTOINCREMENT - /* If the sqlite_sequence table exists in this database, then update + /* If the sqlite_sequence table exists in this database, then update ** it with the new table name. */ if( sqlite3FindTable(db, "sqlite_sequence", zDb) ){ @@ -102566,15 +120808,15 @@ SQLITE_PRIVATE void sqlite3AlterRenameTable( #endif /* If the table being renamed is not itself part of the temp database, - ** edit view and trigger definitions within the temp database + ** edit view and trigger definitions within the temp database ** as required. */ if( iDb!=1 ){ - sqlite3NestedParse(pParse, - "UPDATE sqlite_temp_master SET " + sqlite3NestedParse(pParse, + "UPDATE sqlite_temp_schema SET " "sql = sqlite_rename_table(%Q, type, name, sql, %Q, %Q, 1), " "tbl_name = " "CASE WHEN tbl_name=%Q COLLATE nocase AND " - " sqlite_rename_test(%Q, sql, type, name, 1) " + " sqlite_rename_test(%Q, sql, type, name, 1, 'after rename', 0) " "THEN %Q ELSE tbl_name END " "WHERE type IN ('view', 'trigger')" , zDb, zTabName, zName, zTabName, zDb, zName); @@ -102593,13 +120835,28 @@ SQLITE_PRIVATE void sqlite3AlterRenameTable( } #endif - renameReloadSchema(pParse, iDb); - renameTestSchema(pParse, zDb, iDb==1); + renameReloadSchema(pParse, iDb, INITFLAG_AlterRename); + renameTestSchema(pParse, zDb, iDb==1, "after rename", 0); exit_rename_table: sqlite3SrcListDelete(db, pSrc); sqlite3DbFree(db, zName); - db->mDbFlags = savedDbFlags; +} + +/* +** Write code that will raise an error if the table described by +** zDb and zTab is not empty. +*/ +static void sqlite3ErrorIfNotEmpty( + Parse *pParse, /* Parsing context */ + const char *zDb, /* Schema holding the table */ + const char *zTab, /* Table to check for empty */ + const char *zErr /* Error message text */ +){ + sqlite3NestedParse(pParse, + "SELECT raise(ABORT,%Q) FROM \"%w\".\"%w\"", + zErr, zDb, zTab + ); } /* @@ -102624,7 +120881,9 @@ SQLITE_PRIVATE void sqlite3AlterFinishAddColumn(Parse *pParse, Token *pColDef){ int r1; /* Temporary registers */ db = pParse->db; - if( pParse->nErr || db->mallocFailed ) return; + assert( db->pParse==pParse ); + if( pParse->nErr ) return; + assert( db->mallocFailed==0 ); pNew = pParse->pNewTable; assert( pNew ); @@ -102633,7 +120892,7 @@ SQLITE_PRIVATE void sqlite3AlterFinishAddColumn(Parse *pParse, Token *pColDef){ zDb = db->aDb[iDb].zDbSName; zTab = &pNew->zName[16]; /* Skip the "sqlite_altertab_" prefix on the name */ pCol = &pNew->aCol[pNew->nCol-1]; - pDflt = pCol->pDflt; + pDflt = sqlite3ColumnExpr(pNew, pCol); pTab = sqlite3FindTable(db, zTab, zDb); assert( pTab ); @@ -102644,14 +120903,6 @@ SQLITE_PRIVATE void sqlite3AlterFinishAddColumn(Parse *pParse, Token *pColDef){ } #endif - /* If the default value for the new column was specified with a - ** literal NULL, then set pDflt to 0. This simplifies checking - ** for an SQL NULL default below. - */ - assert( pDflt==0 || pDflt->op==TK_SPAN ); - if( pDflt && pDflt->pLeft->op==TK_NULL ){ - pDflt = 0; - } /* Check that the new column is not specified as PRIMARY KEY or UNIQUE. ** If there is a NOT NULL constraint, then the default value for the @@ -102662,65 +120913,81 @@ SQLITE_PRIVATE void sqlite3AlterFinishAddColumn(Parse *pParse, Token *pColDef){ return; } if( pNew->pIndex ){ - sqlite3ErrorMsg(pParse, "Cannot add a UNIQUE column"); - return; - } - if( (db->flags&SQLITE_ForeignKeys) && pNew->pFKey && pDflt ){ - sqlite3ErrorMsg(pParse, - "Cannot add a REFERENCES column with non-NULL default value"); - return; - } - if( pCol->notNull && !pDflt ){ - sqlite3ErrorMsg(pParse, - "Cannot add a NOT NULL column with default value NULL"); + sqlite3ErrorMsg(pParse, + "Cannot add a UNIQUE column"); return; } - - /* Ensure the default expression is something that sqlite3ValueFromExpr() - ** can handle (i.e. not CURRENT_TIME etc.) - */ - if( pDflt ){ - sqlite3_value *pVal = 0; - int rc; - rc = sqlite3ValueFromExpr(db, pDflt, SQLITE_UTF8, SQLITE_AFF_BLOB, &pVal); - assert( rc==SQLITE_OK || rc==SQLITE_NOMEM ); - if( rc!=SQLITE_OK ){ - assert( db->mallocFailed == 1 ); - return; + if( (pCol->colFlags & COLFLAG_GENERATED)==0 ){ + /* If the default value for the new column was specified with a + ** literal NULL, then set pDflt to 0. This simplifies checking + ** for an SQL NULL default below. + */ + assert( pDflt==0 || pDflt->op==TK_SPAN ); + if( pDflt && pDflt->pLeft->op==TK_NULL ){ + pDflt = 0; } - if( !pVal ){ - sqlite3ErrorMsg(pParse, "Cannot add a column with non-constant default"); - return; + assert( IsOrdinaryTable(pNew) ); + if( (db->flags&SQLITE_ForeignKeys) && pNew->u.tab.pFKey && pDflt ){ + sqlite3ErrorIfNotEmpty(pParse, zDb, zTab, + "Cannot add a REFERENCES column with non-NULL default value"); + } + if( pCol->notNull && !pDflt ){ + sqlite3ErrorIfNotEmpty(pParse, zDb, zTab, + "Cannot add a NOT NULL column with default value NULL"); + } + + + /* Ensure the default expression is something that sqlite3ValueFromExpr() + ** can handle (i.e. not CURRENT_TIME etc.) + */ + if( pDflt ){ + sqlite3_value *pVal = 0; + int rc; + rc = sqlite3ValueFromExpr(db, pDflt, SQLITE_UTF8, SQLITE_AFF_BLOB, &pVal); + assert( rc==SQLITE_OK || rc==SQLITE_NOMEM ); + if( rc!=SQLITE_OK ){ + assert( db->mallocFailed == 1 ); + return; + } + if( !pVal ){ + sqlite3ErrorIfNotEmpty(pParse, zDb, zTab, + "Cannot add a column with non-constant default"); + } + sqlite3ValueFree(pVal); } - sqlite3ValueFree(pVal); + }else if( pCol->colFlags & COLFLAG_STORED ){ + sqlite3ErrorIfNotEmpty(pParse, zDb, zTab, "cannot add a STORED column"); } + /* Modify the CREATE TABLE statement. */ zCol = sqlite3DbStrNDup(db, (char*)pColDef->z, pColDef->n); if( zCol ){ char *zEnd = &zCol[pColDef->n-1]; - u32 savedDbFlags = db->mDbFlags; while( zEnd>zCol && (*zEnd==';' || sqlite3Isspace(*zEnd)) ){ *zEnd-- = '\0'; } - db->mDbFlags |= DBFLAG_PreferBuiltin; - sqlite3NestedParse(pParse, - "UPDATE \"%w\".%s SET " - "sql = substr(sql,1,%d) || ', ' || %Q || substr(sql,%d) " - "WHERE type = 'table' AND name = %Q", - zDb, MASTER_NAME, pNew->addColOffset, zCol, pNew->addColOffset+1, + /* substr() operations on characters, but addColOffset is in bytes. So we + ** have to use printf() to translate between these units: */ + assert( IsOrdinaryTable(pTab) ); + assert( IsOrdinaryTable(pNew) ); + sqlite3NestedParse(pParse, + "UPDATE \"%w\"." LEGACY_SCHEMA_TABLE " SET " + "sql = printf('%%.%ds, ',sql) || %Q" + " || substr(sql,1+length(printf('%%.%ds',sql))) " + "WHERE type = 'table' AND name = %Q", + zDb, pNew->u.tab.addColOffset, zCol, pNew->u.tab.addColOffset, zTab ); sqlite3DbFree(db, zCol); - db->mDbFlags = savedDbFlags; } - /* Make sure the schema version is at least 3. But do not upgrade - ** from less than 3 to 4, as that will corrupt any preexisting DESC - ** index. - */ v = sqlite3GetVdbe(pParse); if( v ){ + /* Make sure the schema version is at least 3. But do not upgrade + ** from less than 3 to 4, as that will corrupt any preexisting DESC + ** index. + */ r1 = sqlite3GetTempReg(pParse); sqlite3VdbeAddOp3(v, OP_ReadCookie, iDb, r1, BTREE_FILE_FORMAT); sqlite3VdbeUsesBtree(v, iDb); @@ -102729,22 +120996,42 @@ SQLITE_PRIVATE void sqlite3AlterFinishAddColumn(Parse *pParse, Token *pColDef){ VdbeCoverage(v); sqlite3VdbeAddOp3(v, OP_SetCookie, iDb, BTREE_FILE_FORMAT, 3); sqlite3ReleaseTempReg(pParse, r1); - } - /* Reload the table definition */ - renameReloadSchema(pParse, iDb); + /* Reload the table definition */ + renameReloadSchema(pParse, iDb, INITFLAG_AlterAdd); + + /* Verify that constraints are still satisfied */ + if( pNew->pCheck!=0 + || (pCol->notNull && (pCol->colFlags & COLFLAG_GENERATED)!=0) + || (pTab->tabFlags & TF_Strict)!=0 + ){ + sqlite3NestedParse(pParse, + "SELECT CASE WHEN quick_check GLOB 'CHECK*'" + " THEN raise(ABORT,'CHECK constraint failed')" + " WHEN quick_check GLOB 'non-* value in*'" + " THEN raise(ABORT,'type mismatch on DEFAULT')" + " ELSE raise(ABORT,'NOT NULL constraint failed')" + " END" + " FROM pragma_quick_check(%Q,%Q)" + " WHERE quick_check GLOB 'CHECK*'" + " OR quick_check GLOB 'NULL*'" + " OR quick_check GLOB 'non-* value in*'", + zTab, zDb + ); + } + } } /* ** This function is called by the parser after the table-name in -** an "ALTER TABLE ADD" statement is parsed. Argument +** an "ALTER TABLE ADD" statement is parsed. Argument ** pSrc is the full-name of the table being altered. ** ** This routine makes a (partial) copy of the Table structure ** for the table being altered and sets Parse.pNewTable to point ** to it. Routines called by the parser as the column definition -** is parsed (i.e. sqlite3AddColumn()) add the new Column data to -** the copy. The copy of the Table structure is deleted by tokenize.c +** is parsed (i.e. sqlite3AddColumn()) add the new Column data to +** the copy. The copy of the Table structure is deleted by tokenize.c ** after parsing is finished. ** ** Routine sqlite3AlterFinishAddColumn() will be called to complete @@ -102761,7 +121048,7 @@ SQLITE_PRIVATE void sqlite3AlterBeginAddColumn(Parse *pParse, SrcList *pSrc){ /* Look up the table being altered. */ assert( pParse->pNewTable==0 ); assert( sqlite3BtreeHoldsAllMutexes(db) ); - if( db->mallocFailed ) goto exit_begin_add_column; + if( NEVER(db->mallocFailed) ) goto exit_begin_add_column; pTab = sqlite3LocateTableItem(pParse, 0, &pSrc->a[0]); if( !pTab ) goto exit_begin_add_column; @@ -102773,7 +121060,7 @@ SQLITE_PRIVATE void sqlite3AlterBeginAddColumn(Parse *pParse, SrcList *pSrc){ #endif /* Make sure this is not an attempt to ALTER a view. */ - if( pTab->pSelect ){ + if( IsView(pTab) ){ sqlite3ErrorMsg(pParse, "Cannot add a column to a view"); goto exit_begin_add_column; } @@ -102781,7 +121068,9 @@ SQLITE_PRIVATE void sqlite3AlterBeginAddColumn(Parse *pParse, SrcList *pSrc){ goto exit_begin_add_column; } - assert( pTab->addColOffset>0 ); + sqlite3MayAbort(pParse); + assert( IsOrdinaryTable(pTab) ); + assert( pTab->u.tab.addColOffset>0 ); iDb = sqlite3SchemaToIndex(db, pTab->pSchema); /* Put a copy of the Table struct in Parse.pNewTable for the @@ -102799,22 +121088,23 @@ SQLITE_PRIVATE void sqlite3AlterBeginAddColumn(Parse *pParse, SrcList *pSrc){ assert( pNew->nCol>0 ); nAlloc = (((pNew->nCol-1)/8)*8)+8; assert( nAlloc>=pNew->nCol && nAlloc%8==0 && nAlloc-pNew->nCol<8 ); - pNew->aCol = (Column*)sqlite3DbMallocZero(db, sizeof(Column)*nAlloc); + pNew->aCol = (Column*)sqlite3DbMallocZero(db, sizeof(Column)*(u32)nAlloc); pNew->zName = sqlite3MPrintf(db, "sqlite_altertab_%s", pTab->zName); if( !pNew->aCol || !pNew->zName ){ assert( db->mallocFailed ); goto exit_begin_add_column; } - memcpy(pNew->aCol, pTab->aCol, sizeof(Column)*pNew->nCol); + memcpy(pNew->aCol, pTab->aCol, sizeof(Column)*(size_t)pNew->nCol); for(i=0; inCol; i++){ Column *pCol = &pNew->aCol[i]; - pCol->zName = sqlite3DbStrDup(db, pCol->zName); - pCol->zColl = 0; - pCol->pDflt = 0; + pCol->zCnName = sqlite3DbStrDup(db, pCol->zCnName); + pCol->hName = sqlite3StrIHash(pCol->zCnName); } + assert( IsOrdinaryTable(pNew) ); + pNew->u.tab.pDfltList = sqlite3ExprListDup(db, pTab->u.tab.pDfltList, 0); pNew->pSchema = db->aDb[iDb].pSchema; - pNew->addColOffset = pTab->addColOffset; - pNew->nTabRef = 1; + pNew->u.tab.addColOffset = pTab->u.tab.addColOffset; + assert( pNew->nTabRef==1 ); exit_begin_add_column: sqlite3SrcListDelete(db, pSrc); @@ -102830,10 +121120,10 @@ SQLITE_PRIVATE void sqlite3AlterBeginAddColumn(Parse *pParse, SrcList *pSrc){ ** Or, if pTab is not a view or virtual table, zero is returned. */ #if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_VIRTUALTABLE) -static int isRealTable(Parse *pParse, Table *pTab){ +static int isRealTable(Parse *pParse, Table *pTab, int iOp){ const char *zType = 0; #ifndef SQLITE_OMIT_VIEW - if( pTab->pSelect ){ + if( IsView(pTab) ){ zType = "view"; } #endif @@ -102843,15 +121133,19 @@ static int isRealTable(Parse *pParse, Table *pTab){ } #endif if( zType ){ - sqlite3ErrorMsg( - pParse, "cannot rename columns of %s \"%s\"", zType, pTab->zName + const char *azMsg[] = { + "rename columns of", "drop column from", "edit constraints of" + }; + assert( iOp>=0 && iOpzName ); return 1; } return 0; } #else /* !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_VIRTUALTABLE) */ -# define isRealTable(x,y) (0) +# define isRealTable(x,y,z) (0) #endif /* @@ -102880,9 +121174,9 @@ SQLITE_PRIVATE void sqlite3AlterRenameColumn( /* Cannot alter a system table */ if( SQLITE_OK!=isAlterableTable(pParse, pTab) ) goto exit_rename_column; - if( SQLITE_OK!=isRealTable(pParse, pTab) ) goto exit_rename_column; + if( SQLITE_OK!=isRealTable(pParse, pTab, 0) ) goto exit_rename_column; - /* Which schema holds the table to be altered */ + /* Which schema holds the table to be altered */ iSchema = sqlite3SchemaToIndex(db, pTab->pSchema); assert( iSchema>=0 ); zDb = db->aDb[iSchema].zDbSName; @@ -102898,44 +121192,45 @@ SQLITE_PRIVATE void sqlite3AlterRenameColumn( ** altered. Set iCol to be the index of the column being renamed */ zOld = sqlite3NameFromToken(db, pOld); if( !zOld ) goto exit_rename_column; - for(iCol=0; iColnCol; iCol++){ - if( 0==sqlite3StrICmp(pTab->aCol[iCol].zName, zOld) ) break; - } - if( iCol==pTab->nCol ){ - sqlite3ErrorMsg(pParse, "no such column: \"%s\"", zOld); + iCol = sqlite3ColumnIndex(pTab, zOld); + if( iCol<0 ){ + sqlite3ErrorMsg(pParse, "no such column: \"%T\"", pOld); goto exit_rename_column; } + /* Ensure the schema contains no double-quoted strings */ + renameTestSchema(pParse, zDb, iSchema==1, "", 0); + renameFixQuotes(pParse, zDb, iSchema==1); + /* Do the rename operation using a recursive UPDATE statement that ** uses the sqlite_rename_column() SQL function to compute the new - ** CREATE statement text for the sqlite_master table. + ** CREATE statement text for the sqlite_schema table. */ sqlite3MayAbort(pParse); zNew = sqlite3NameFromToken(db, pNew); if( !zNew ) goto exit_rename_column; assert( pNew->n>0 ); bQuote = sqlite3Isquote(pNew->z[0]); - sqlite3NestedParse(pParse, - "UPDATE \"%w\".%s SET " + sqlite3NestedParse(pParse, + "UPDATE \"%w\"." LEGACY_SCHEMA_TABLE " SET " "sql = sqlite_rename_column(sql, type, name, %Q, %Q, %d, %Q, %d, %d) " - "WHERE name NOT LIKE 'sqlite_%%' AND (type != 'index' OR tbl_name = %Q)" - " AND sql NOT LIKE 'create virtual%%'", - zDb, MASTER_NAME, + "WHERE name NOT LIKE 'sqliteX_%%' ESCAPE 'X' " + " AND (type != 'index' OR tbl_name = %Q)", + zDb, zDb, pTab->zName, iCol, zNew, bQuote, iSchema==1, pTab->zName ); - sqlite3NestedParse(pParse, - "UPDATE temp.%s SET " + sqlite3NestedParse(pParse, + "UPDATE temp." LEGACY_SCHEMA_TABLE " SET " "sql = sqlite_rename_column(sql, type, name, %Q, %Q, %d, %Q, %d, 1) " "WHERE type IN ('trigger', 'view')", - MASTER_NAME, zDb, pTab->zName, iCol, zNew, bQuote ); /* Drop and reload the database schema. */ - renameReloadSchema(pParse, iSchema); - renameTestSchema(pParse, zDb, iSchema==1); + renameReloadSchema(pParse, iSchema, INITFLAG_AlterRename); + renameTestSchema(pParse, zDb, iSchema==1, "after rename", 1); exit_rename_column: sqlite3SrcListDelete(db, pSrc); @@ -102962,7 +121257,7 @@ SQLITE_PRIVATE void sqlite3AlterRenameColumn( ** the parse tree. */ struct RenameToken { - void *p; /* Parse tree element created by token t */ + const void *p; /* Parse tree element created by token t */ Token t; /* The token that created parse tree element p */ RenameToken *pNext; /* Next is a list of all RenameToken objects */ }; @@ -102976,7 +121271,7 @@ struct RenameCtx { RenameToken *pList; /* List of tokens to overwrite */ int nList; /* Number of tokens in pList */ int iCol; /* Index of column being renamed */ - Table *pTab; /* Table being ALTERed */ + Table *pTab; /* Table being ALTERed */ const char *zOld; /* Old column name */ }; @@ -102984,14 +121279,14 @@ struct RenameCtx { /* ** This function is only for debugging. It performs two tasks: ** -** 1. Checks that pointer pPtr does not already appear in the +** 1. Checks that pointer pPtr does not already appear in the ** rename-token list. ** ** 2. Dereferences each pointer in the rename-token list. ** ** The second is most effective when debugging under valgrind or -** address-sanitizer or similar. If any of these pointers no longer -** point to valid objects, an exception is raised by the memory-checking +** address-sanitizer or similar. If any of these pointers no longer +** point to valid objects, an exception is raised by the memory-checking ** tool. ** ** The point of this is to prevent comparisons of invalid pointer values. @@ -103004,16 +121299,19 @@ struct RenameCtx { ** Technically, as x no longer points into a valid object or to the byte ** following a valid object, it may not be used in comparison operations. */ -static void renameTokenCheckAll(Parse *pParse, void *pPtr){ - if( pParse->nErr==0 && pParse->db->mallocFailed==0 ){ - RenameToken *p; - u8 i = 0; +static void renameTokenCheckAll(Parse *pParse, const void *pPtr){ + assert( pParse==pParse->db->pParse ); + assert( pParse->db->mallocFailed==0 || pParse->nErr!=0 ); + if( pParse->nErr==0 ){ + const RenameToken *p; + u32 i = 1; for(p=pParse->pRename; p; p=p->pNext){ if( p->p ){ assert( p->p!=pPtr ); - i += *(u8*)(p->p); + i += *(u8*)(p->p) | 1; } } + assert( i>0 ); } } #else @@ -103032,16 +121330,22 @@ static void renameTokenCheckAll(Parse *pParse, void *pPtr){ ** with tail recursion in tokenExpr() routine, for a small performance ** improvement. */ -SQLITE_PRIVATE void *sqlite3RenameTokenMap(Parse *pParse, void *pPtr, Token *pToken){ +SQLITE_PRIVATE const void *sqlite3RenameTokenMap( + Parse *pParse, + const void *pPtr, + const Token *pToken +){ RenameToken *pNew; assert( pPtr || pParse->db->mallocFailed ); renameTokenCheckAll(pParse, pPtr); - pNew = sqlite3DbMallocZero(pParse->db, sizeof(RenameToken)); - if( pNew ){ - pNew->p = pPtr; - pNew->t = *pToken; - pNew->pNext = pParse->pRename; - pParse->pRename = pNew; + if( ALWAYS(pParse->eParseMode!=PARSE_MODE_UNMAP) ){ + pNew = sqlite3DbMallocZero(pParse->db, sizeof(RenameToken)); + if( pNew ){ + pNew->p = pPtr; + pNew->t = *pToken; + pNew->pNext = pParse->pRename; + pParse->pRename = pNew; + } } return pPtr; @@ -103052,7 +121356,7 @@ SQLITE_PRIVATE void *sqlite3RenameTokenMap(Parse *pParse, void *pPtr, Token *pTo ** with parse tree element pFrom. This function remaps the associated token ** to parse tree element pTo. */ -SQLITE_PRIVATE void sqlite3RenameTokenRemap(Parse *pParse, void *pTo, void *pFrom){ +SQLITE_PRIVATE void sqlite3RenameTokenRemap(Parse *pParse, const void *pTo, const void *pFrom){ RenameToken *p; renameTokenCheckAll(pParse, pTo); for(p=pParse->pRename; p; p=p->pNext){ @@ -103068,7 +121372,96 @@ SQLITE_PRIVATE void sqlite3RenameTokenRemap(Parse *pParse, void *pTo, void *pFro */ static int renameUnmapExprCb(Walker *pWalker, Expr *pExpr){ Parse *pParse = pWalker->pParse; - sqlite3RenameTokenRemap(pParse, 0, (void*)pExpr); + sqlite3RenameTokenRemap(pParse, 0, (const void*)pExpr); + if( ExprUseYTab(pExpr) ){ + sqlite3RenameTokenRemap(pParse, 0, (const void*)&pExpr->y.pTab); + } + return WRC_Continue; +} + +/* +** Iterate through the Select objects that are part of WITH clauses attached +** to select statement pSelect. +*/ +static void renameWalkWith(Walker *pWalker, Select *pSelect){ + With *pWith = pSelect->pWith; + if( pWith ){ + Parse *pParse = pWalker->pParse; + int i; + With *pCopy = 0; + assert( pWith->nCte>0 ); + if( (pWith->a[0].pSelect->selFlags & SF_Expanded)==0 ){ + /* Push a copy of the With object onto the with-stack. We use a copy + ** here as the original will be expanded and resolved (flags SF_Expanded + ** and SF_Resolved) below. And the parser code that uses the with-stack + ** fails if the Select objects on it have already been expanded and + ** resolved. */ + pCopy = sqlite3WithDup(pParse->db, pWith); + pCopy = sqlite3WithPush(pParse, pCopy, 1); + } + for(i=0; inCte; i++){ + Select *p = pWith->a[i].pSelect; + NameContext sNC; + memset(&sNC, 0, sizeof(sNC)); + sNC.pParse = pParse; + if( pCopy ) sqlite3SelectPrep(sNC.pParse, p, &sNC); + if( sNC.pParse->db->mallocFailed ) return; + sqlite3WalkSelect(pWalker, p); + sqlite3RenameExprlistUnmap(pParse, pWith->a[i].pCols); + } + if( pCopy && pParse->pWith==pCopy ){ + pParse->pWith = pCopy->pOuter; + } + } +} + +/* +** Unmap all tokens in the IdList object passed as the second argument. +*/ +static void unmapColumnIdlistNames( + Parse *pParse, + const IdList *pIdList +){ + int ii; + assert( pIdList!=0 ); + for(ii=0; iinId; ii++){ + sqlite3RenameTokenRemap(pParse, 0, (const void*)pIdList->a[ii].zName); + } +} + +/* +** Walker callback used by sqlite3RenameExprUnmap(). +*/ +static int renameUnmapSelectCb(Walker *pWalker, Select *p){ + Parse *pParse = pWalker->pParse; + int i; + if( pParse->nErr ) return WRC_Abort; + testcase( p->selFlags & SF_View ); + testcase( p->selFlags & SF_CopyCte ); + if( p->selFlags & (SF_View|SF_CopyCte) ){ + return WRC_Prune; + } + if( ALWAYS(p->pEList) ){ + ExprList *pList = p->pEList; + for(i=0; inExpr; i++){ + if( pList->a[i].zEName && pList->a[i].fg.eEName==ENAME_NAME ){ + sqlite3RenameTokenRemap(pParse, 0, (void*)pList->a[i].zEName); + } + } + } + if( ALWAYS(p->pSrc) ){ /* Every Select as a SrcList, even if it is empty */ + SrcList *pSrc = p->pSrc; + for(i=0; inSrc; i++){ + sqlite3RenameTokenRemap(pParse, 0, (void*)pSrc->a[i].zName); + if( pSrc->a[i].fg.isUsing==0 ){ + sqlite3WalkExpr(pWalker, pSrc->a[i].u3.pOn); + }else{ + unmapColumnIdlistNames(pParse, pSrc->a[i].u3.pUsing); + } + } + } + + renameWalkWith(pWalker, p); return WRC_Continue; } @@ -103076,15 +121469,19 @@ static int renameUnmapExprCb(Walker *pWalker, Expr *pExpr){ ** Remove all nodes that are part of expression pExpr from the rename list. */ SQLITE_PRIVATE void sqlite3RenameExprUnmap(Parse *pParse, Expr *pExpr){ + u8 eMode = pParse->eParseMode; Walker sWalker; memset(&sWalker, 0, sizeof(Walker)); sWalker.pParse = pParse; sWalker.xExprCallback = renameUnmapExprCb; + sWalker.xSelectCallback = renameUnmapSelectCb; + pParse->eParseMode = PARSE_MODE_UNMAP; sqlite3WalkExpr(&sWalker, pExpr); + pParse->eParseMode = eMode; } /* -** Remove all nodes that are part of expression-list pEList from the +** Remove all nodes that are part of expression-list pEList from the ** rename list. */ SQLITE_PRIVATE void sqlite3RenameExprlistUnmap(Parse *pParse, ExprList *pEList){ @@ -103096,7 +121493,9 @@ SQLITE_PRIVATE void sqlite3RenameExprlistUnmap(Parse *pParse, ExprList *pEList){ sWalker.xExprCallback = renameUnmapExprCb; sqlite3WalkExprList(&sWalker, pEList); for(i=0; inExpr; i++){ - sqlite3RenameTokenRemap(pParse, 0, (void*)pEList->a[i].zName); + if( ALWAYS(pEList->a[i].fg.eEName==ENAME_NAME) ){ + sqlite3RenameTokenRemap(pParse, 0, (void*)pEList->a[i].zEName); + } } } } @@ -103115,41 +121514,35 @@ static void renameTokenFree(sqlite3 *db, RenameToken *pToken){ /* ** Search the Parse object passed as the first argument for a RenameToken -** object associated with parse tree element pPtr. If found, remove it -** from the Parse object and add it to the list maintained by the -** RenameCtx object passed as the second argument. +** object associated with parse tree element pPtr. If found, return a pointer +** to it. Otherwise, return NULL. +** +** If the second argument passed to this function is not NULL and a matching +** RenameToken object is found, remove it from the Parse object and add it to +** the list maintained by the RenameCtx object. */ -static void renameTokenFind(Parse *pParse, struct RenameCtx *pCtx, void *pPtr){ +static RenameToken *renameTokenFind( + Parse *pParse, + struct RenameCtx *pCtx, + const void *pPtr +){ RenameToken **pp; - assert( pPtr!=0 ); + if( NEVER(pPtr==0) ){ + return 0; + } for(pp=&pParse->pRename; (*pp); pp=&(*pp)->pNext){ if( (*pp)->p==pPtr ){ RenameToken *pToken = *pp; - *pp = pToken->pNext; - pToken->pNext = pCtx->pList; - pCtx->pList = pToken; - pCtx->nList++; - break; - } - } -} - -/* -** Iterate through the Select objects that are part of WITH clauses attached -** to select statement pSelect. -*/ -static void renameWalkWith(Walker *pWalker, Select *pSelect){ - if( pSelect->pWith ){ - int i; - for(i=0; ipWith->nCte; i++){ - Select *p = pSelect->pWith->a[i].pSelect; - NameContext sNC; - memset(&sNC, 0, sizeof(sNC)); - sNC.pParse = pWalker->pParse; - sqlite3SelectPrep(sNC.pParse, p, &sNC); - sqlite3WalkSelect(pWalker, p); + if( pCtx ){ + *pp = pToken->pNext; + pToken->pNext = pCtx->pList; + pCtx->pList = pToken; + pCtx->nList++; + } + return pToken; } } + return 0; } /* @@ -103158,6 +121551,11 @@ static void renameWalkWith(Walker *pWalker, Select *pSelect){ ** descend into sub-select statements. */ static int renameColumnSelectCb(Walker *pWalker, Select *p){ + if( p->selFlags & (SF_View|SF_CopyCte) ){ + testcase( p->selFlags & SF_View ); + testcase( p->selFlags & SF_CopyCte ); + return WRC_Prune; + } renameWalkWith(pWalker, p); return WRC_Continue; } @@ -103173,13 +121571,14 @@ static int renameColumnSelectCb(Walker *pWalker, Select *p){ */ static int renameColumnExprCb(Walker *pWalker, Expr *pExpr){ RenameCtx *p = pWalker->u.pRename; - if( pExpr->op==TK_TRIGGER - && pExpr->iColumn==p->iCol + if( pExpr->op==TK_TRIGGER + && pExpr->iColumn==p->iCol && pWalker->pParse->pTriggerTab==p->pTab ){ renameTokenFind(pWalker->pParse, p, (void*)pExpr); - }else if( pExpr->op==TK_COLUMN - && pExpr->iColumn==p->iCol + }else if( pExpr->op==TK_COLUMN + && pExpr->iColumn==p->iCol + && ALWAYS(ExprUseYTab(pExpr)) && p->pTab==pExpr->y.pTab ){ renameTokenFind(pWalker->pParse, p, (void*)pExpr); @@ -103211,15 +121610,34 @@ static RenameToken *renameColumnTokenNext(RenameCtx *pCtx){ } /* -** An error occured while parsing or otherwise processing a database +** Set the error message of the context passed as the first argument to +** the result of formatting zFmt using printf() style formatting. +*/ +static void errorMPrintf(sqlite3_context *pCtx, const char *zFmt, ...){ + sqlite3 *db = sqlite3_context_db_handle(pCtx); + char *zErr = 0; + va_list ap; + va_start(ap, zFmt); + zErr = sqlite3VMPrintf(db, zFmt, ap); + va_end(ap); + if( zErr ){ + sqlite3_result_error(pCtx, zErr, -1); + sqlite3DbFree(db, zErr); + }else{ + sqlite3_result_error_nomem(pCtx); + } +} + +/* +** An error occurred while parsing or otherwise processing a database ** object (either pParse->pNewTable, pNewIndex or pNewTrigger) as part of an ** ALTER TABLE RENAME COLUMN program. The error message emitted by the ** sub-routine is currently stored in pParse->zErrMsg. This function ** adds context to the error message and then stores it in pCtx. */ static void renameColumnParseError( - sqlite3_context *pCtx, - int bPost, + sqlite3_context *pCtx, + const char *zWhen, sqlite3_value *pType, sqlite3_value *pObject, Parse *pParse @@ -103228,59 +121646,63 @@ static void renameColumnParseError( const char *zN = (const char*)sqlite3_value_text(pObject); char *zErr; - zErr = sqlite3_mprintf("error in %s %s%s: %s", - zT, zN, (bPost ? " after rename" : ""), + zErr = sqlite3MPrintf(pParse->db, "error in %s %s%s%s: %s", + zT, zN, (zWhen[0] ? " " : ""), zWhen, pParse->zErrMsg ); sqlite3_result_error(pCtx, zErr, -1); - sqlite3_free(zErr); + sqlite3DbFree(pParse->db, zErr); } /* ** For each name in the the expression-list pEList (i.e. each -** pEList->a[i].zName) that matches the string in zOld, extract the +** pEList->a[i].zName) that matches the string in zOld, extract the ** corresponding rename-token from Parse object pParse and add it ** to the RenameCtx pCtx. */ static void renameColumnElistNames( - Parse *pParse, - RenameCtx *pCtx, - ExprList *pEList, + Parse *pParse, + RenameCtx *pCtx, + const ExprList *pEList, const char *zOld ){ if( pEList ){ int i; for(i=0; inExpr; i++){ - char *zName = pEList->a[i].zName; - if( 0==sqlite3_stricmp(zName, zOld) ){ - renameTokenFind(pParse, pCtx, (void*)zName); + const char *zName = pEList->a[i].zEName; + if( ALWAYS(pEList->a[i].fg.eEName==ENAME_NAME) + && ALWAYS(zName!=0) + && 0==sqlite3_stricmp(zName, zOld) + ){ + renameTokenFind(pParse, pCtx, (const void*)zName); } } } } /* -** For each name in the the id-list pIdList (i.e. each pIdList->a[i].zName) -** that matches the string in zOld, extract the corresponding rename-token +** For each name in the the id-list pIdList (i.e. each pIdList->a[i].zName) +** that matches the string in zOld, extract the corresponding rename-token ** from Parse object pParse and add it to the RenameCtx pCtx. */ static void renameColumnIdlistNames( - Parse *pParse, - RenameCtx *pCtx, - IdList *pIdList, + Parse *pParse, + RenameCtx *pCtx, + const IdList *pIdList, const char *zOld ){ if( pIdList ){ int i; for(i=0; inId; i++){ - char *zName = pIdList->a[i].zName; + const char *zName = pIdList->a[i].zName; if( 0==sqlite3_stricmp(zName, zOld) ){ - renameTokenFind(pParse, pCtx, (void*)zName); + renameTokenFind(pParse, pCtx, (const void*)zName); } } } } + /* ** Parse the SQL statement zSql using Parse object (*p). The Parse object ** is initialized by this function before it is used. @@ -103288,30 +121710,38 @@ static void renameColumnIdlistNames( static int renameParseSql( Parse *p, /* Memory to use for Parse object */ const char *zDb, /* Name of schema SQL belongs to */ - int bTable, /* 1 -> RENAME TABLE, 0 -> RENAME COLUMN */ sqlite3 *db, /* Database handle */ const char *zSql, /* SQL to parse */ int bTemp /* True if SQL is from temp schema */ ){ int rc; - char *zErr = 0; - - db->init.iDb = bTemp ? 1 : sqlite3FindDbName(db, zDb); + u64 flags; - /* Parse the SQL statement passed as the first argument. If no error - ** occurs and the parse does not result in a new table, index or - ** trigger object, the database must be corrupt. */ - memset(p, 0, sizeof(Parse)); - p->eParseMode = (bTable ? PARSE_MODE_RENAME_TABLE : PARSE_MODE_RENAME_COLUMN); + sqlite3ParseObjectInit(p, db); + if( zSql==0 ){ + return SQLITE_NOMEM; + } + if( sqlite3StrNICmp(zSql,"CREATE ",7)!=0 ){ + return SQLITE_CORRUPT_BKPT; + } + if( bTemp ){ + db->init.iDb = 1; + }else{ + int iDb = sqlite3FindDbName(db, zDb); + assert( iDb>=0 && iDb<=0xff ); + db->init.iDb = (u8)iDb; + } + p->eParseMode = PARSE_MODE_RENAME; p->db = db; p->nQueryLoop = 1; - rc = sqlite3RunParser(p, zSql, &zErr); - assert( p->zErrMsg==0 ); - assert( rc!=SQLITE_OK || zErr==0 ); - p->zErrMsg = zErr; + flags = db->flags; + testcase( (db->flags & SQLITE_Comments)==0 && strstr(zSql," /* ")!=0 ); + db->flags |= SQLITE_Comments; + rc = sqlite3RunParser(p, zSql); + db->flags = flags; if( db->mallocFailed ) rc = SQLITE_NOMEM; - if( rc==SQLITE_OK - && p->pNewTable==0 && p->pNewIndex==0 && p->pNewTrigger==0 + if( rc==SQLITE_OK + && NEVER(p->pNewTable==0 && p->pNewIndex==0 && p->pNewTrigger==0) ){ rc = SQLITE_CORRUPT_BKPT; } @@ -103348,56 +121778,84 @@ static int renameEditSql( const char *zNew, /* New token text */ int bQuote /* True to always quote token */ ){ - int nNew = sqlite3Strlen30(zNew); - int nSql = sqlite3Strlen30(zSql); + i64 nNew = sqlite3Strlen30(zNew); + i64 nSql = sqlite3Strlen30(zSql); sqlite3 *db = sqlite3_context_db_handle(pCtx); int rc = SQLITE_OK; - char *zQuot; + char *zQuot = 0; char *zOut; - int nQuot; - - /* Set zQuot to point to a buffer containing a quoted copy of the - ** identifier zNew. If the corresponding identifier in the original - ** ALTER TABLE statement was quoted (bQuote==1), then set zNew to - ** point to zQuot so that all substitutions are made using the - ** quoted version of the new column name. */ - zQuot = sqlite3MPrintf(db, "\"%w\"", zNew); - if( zQuot==0 ){ - return SQLITE_NOMEM; + i64 nQuot = 0; + char *zBuf1 = 0; + char *zBuf2 = 0; + + if( zNew ){ + /* Set zQuot to point to a buffer containing a quoted copy of the + ** identifier zNew. If the corresponding identifier in the original + ** ALTER TABLE statement was quoted (bQuote==1), then set zNew to + ** point to zQuot so that all substitutions are made using the + ** quoted version of the new column name. */ + zQuot = sqlite3MPrintf(db, "\"%w\" ", zNew); + if( zQuot==0 ){ + return SQLITE_NOMEM; + }else{ + nQuot = sqlite3Strlen30(zQuot)-1; + } + + assert( nQuot>=nNew && nSql>=0 && nNew>=0 ); + zOut = sqlite3DbMallocZero(db, (u64)nSql + pRename->nList*(u64)nQuot + 1); }else{ - nQuot = sqlite3Strlen30(zQuot); - } - if( bQuote ){ - zNew = zQuot; - nNew = nQuot; + assert( nSql>0 ); + zOut = (char*)sqlite3DbMallocZero(db, (2*(u64)nSql + 1) * 3); + if( zOut ){ + zBuf1 = &zOut[nSql*2+1]; + zBuf2 = &zOut[nSql*4+2]; + } } /* At this point pRename->pList contains a list of RenameToken objects ** corresponding to all tokens in the input SQL that must be replaced - ** with the new column name. All that remains is to construct and - ** return the edited SQL string. */ - assert( nQuot>=nNew ); - zOut = sqlite3DbMallocZero(db, nSql + pRename->nList*nQuot + 1); + ** with the new column name, or with single-quoted versions of themselves. + ** All that remains is to construct and return the edited SQL string. */ if( zOut ){ - int nOut = nSql; - memcpy(zOut, zSql, nSql); + i64 nOut = nSql; + assert( nSql>0 ); + memcpy(zOut, zSql, (size_t)nSql); while( pRename->pList ){ int iOff; /* Offset of token to replace in zOut */ + i64 nReplace; + const char *zReplace; RenameToken *pBest = renameColumnTokenNext(pRename); - u32 nReplace; - const char *zReplace; - if( sqlite3IsIdChar(*pBest->t.z) ){ - nReplace = nNew; - zReplace = zNew; + if( zNew ){ + if( bQuote==0 && sqlite3IsIdChar(*(u8*)pBest->t.z) ){ + nReplace = nNew; + zReplace = zNew; + }else{ + nReplace = nQuot; + zReplace = zQuot; + if( pBest->t.z[pBest->t.n]=='"' ) nReplace++; + } }else{ - nReplace = nQuot; - zReplace = zQuot; + /* Dequote the double-quoted token. Then requote it again, this time + ** using single quotes. If the character immediately following the + ** original token within the input SQL was a single quote ('), then + ** add another space after the new, single-quoted version of the + ** token. This is so that (SELECT "string"'alias') maps to + ** (SELECT 'string' 'alias'), and not (SELECT 'string''alias'). */ + memcpy(zBuf1, pBest->t.z, pBest->t.n); + zBuf1[pBest->t.n] = 0; + sqlite3Dequote(zBuf1); + assert( nSql < 0x15555554 /* otherwise malloc would have failed */ ); + sqlite3_snprintf((int)(nSql*2), zBuf2, "%Q%s", zBuf1, + pBest->t.z[pBest->t.n]=='\'' ? " " : "" + ); + zReplace = zBuf2; + nReplace = sqlite3Strlen30(zReplace); } - iOff = pBest->t.z - zSql; + iOff = (int)(pBest->t.z - zSql); if( pBest->t.n!=nReplace ){ - memmove(&zOut[iOff + nReplace], &zOut[iOff + pBest->t.n], + memmove(&zOut[iOff + nReplace], &zOut[iOff + pBest->t.n], nOut - (iOff + pBest->t.n) ); nOut += nReplace - pBest->t.n; @@ -103417,13 +121875,27 @@ static int renameEditSql( return rc; } +/* +** Set all pEList->a[].fg.eEName fields in the expression-list to val. +*/ +static void renameSetENames(ExprList *pEList, int val){ + assert( val==ENAME_NAME || val==ENAME_TAB || val==ENAME_SPAN ); + if( pEList ){ + int i; + for(i=0; inExpr; i++){ + assert( val==ENAME_NAME || pEList->a[i].fg.eEName==ENAME_NAME ); + pEList->a[i].fg.eEName = val&0x3; + } + } +} + /* ** Resolve all symbols in the trigger at pParse->pNewTrigger, assuming -** it was read from the schema of database zDb. Return SQLITE_OK if +** it was read from the schema of database zDb. Return SQLITE_OK if ** successful. Otherwise, return an SQLite error code and leave an error ** message in the Parse object. */ -static int renameResolveTrigger(Parse *pParse, const char *zDb){ +static int renameResolveTrigger(Parse *pParse){ sqlite3 *db = pParse->db; Trigger *pNew = pParse->pNewTrigger; TriggerStep *pStep; @@ -103433,14 +121905,14 @@ static int renameResolveTrigger(Parse *pParse, const char *zDb){ memset(&sNC, 0, sizeof(sNC)); sNC.pParse = pParse; assert( pNew->pTabSchema ); - pParse->pTriggerTab = sqlite3FindTable(db, pNew->table, + pParse->pTriggerTab = sqlite3FindTable(db, pNew->table, db->aDb[sqlite3SchemaToIndex(db, pNew->pTabSchema)].zDbSName ); pParse->eTriggerOp = pNew->op; /* ALWAYS() because if the table of the trigger does not exist, the ** error would have been hit before this point */ if( ALWAYS(pParse->pTriggerTab) ){ - rc = sqlite3ViewGetColumnNames(pParse, pParse->pTriggerTab); + rc = sqlite3ViewGetColumnNames(pParse, pParse->pTriggerTab)!=0; } /* Resolve symbols in WHEN clause */ @@ -103453,28 +121925,60 @@ static int renameResolveTrigger(Parse *pParse, const char *zDb){ sqlite3SelectPrep(pParse, pStep->pSelect, &sNC); if( pParse->nErr ) rc = pParse->rc; } - if( rc==SQLITE_OK && pStep->zTarget ){ - Table *pTarget = sqlite3LocateTable(pParse, 0, pStep->zTarget, zDb); - if( pTarget==0 ){ - rc = SQLITE_ERROR; - }else if( SQLITE_OK==(rc = sqlite3ViewGetColumnNames(pParse, pTarget)) ){ - SrcList sSrc; - memset(&sSrc, 0, sizeof(sSrc)); - sSrc.nSrc = 1; - sSrc.a[0].zName = pStep->zTarget; - sSrc.a[0].pTab = pTarget; - sNC.pSrcList = &sSrc; - if( pStep->pWhere ){ + if( rc==SQLITE_OK && pStep->pSrc ){ + SrcList *pSrc = sqlite3SrcListDup(db, pStep->pSrc, 0); + if( pSrc ){ + Select *pSel = sqlite3SelectNew( + pParse, pStep->pExprList, pSrc, 0, 0, 0, 0, 0, 0 + ); + if( pSel==0 ){ + pStep->pExprList = 0; + pSrc = 0; + rc = SQLITE_NOMEM; + }else{ + /* pStep->pExprList contains an expression-list used for an UPDATE + ** statement. So the a[].zEName values are the RHS of the + ** "= " clauses of the UPDATE statement. So, before + ** running SelectPrep(), change all the eEName values in + ** pStep->pExprList to ENAME_SPAN (from their current value of + ** ENAME_NAME). This is to prevent any ids in ON() clauses that are + ** part of pSrc from being incorrectly resolved against the + ** a[].zEName values as if they were column aliases. */ + renameSetENames(pStep->pExprList, ENAME_SPAN); + sqlite3SelectPrep(pParse, pSel, 0); + renameSetENames(pStep->pExprList, ENAME_NAME); + rc = pParse->nErr ? SQLITE_ERROR : SQLITE_OK; + assert( pStep->pExprList==0 || pStep->pExprList==pSel->pEList ); + assert( pSrc==pSel->pSrc ); + if( pStep->pExprList ) pSel->pEList = 0; + pSel->pSrc = 0; + sqlite3SelectDelete(db, pSel); + } + if( ALWAYS(pStep->pSrc) ){ + int i; + for(i=0; ipSrc->nSrc && rc==SQLITE_OK; i++){ + SrcItem *p = &pStep->pSrc->a[i]; + if( p->fg.isSubquery ){ + assert( p->u4.pSubq!=0 ); + sqlite3SelectPrep(pParse, p->u4.pSubq->pSelect, 0); + } + } + } + + if( db->mallocFailed ){ + rc = SQLITE_NOMEM; + } + sNC.pSrcList = pSrc; + if( rc==SQLITE_OK && pStep->pWhere ){ rc = sqlite3ResolveExprNames(&sNC, pStep->pWhere); } if( rc==SQLITE_OK ){ rc = sqlite3ResolveExprListNames(&sNC, pStep->pExprList); } assert( !pStep->pUpsert || (!pStep->pWhere && !pStep->pExprList) ); - if( pStep->pUpsert ){ + if( pStep->pUpsert && rc==SQLITE_OK ){ Upsert *pUpsert = pStep->pUpsert; - assert( rc==SQLITE_OK ); - pUpsert->pUpsertSrc = &sSrc; + pUpsert->pUpsertSrc = pSrc; sNC.uNC.pUpsert = pUpsert; sNC.ncFlags = NC_UUpsert; rc = sqlite3ResolveExprListNames(&sNC, pUpsert->pUpsertTarget); @@ -103491,6 +121995,9 @@ static int renameResolveTrigger(Parse *pParse, const char *zDb){ sNC.ncFlags = 0; } sNC.pSrcList = 0; + sqlite3SrcListDelete(db, pSrc); + }else{ + rc = SQLITE_NOMEM; } } } @@ -103519,6 +122026,16 @@ static void renameWalkTrigger(Walker *pWalker, Trigger *pTrigger){ sqlite3WalkExpr(pWalker, pUpsert->pUpsertWhere); sqlite3WalkExpr(pWalker, pUpsert->pUpsertTargetWhere); } + if( pStep->pSrc ){ + int i; + SrcList *pSrc = pStep->pSrc; + for(i=0; inSrc; i++){ + if( pSrc->a[i].fg.isSubquery ){ + assert( pSrc->a[i].u4.pSubq!=0 ); + sqlite3WalkSelect(pWalker, pSrc->a[i].u4.pSubq->pSelect); + } + } + } } } @@ -103540,13 +122057,13 @@ static void renameParseCleanup(Parse *pParse){ sqlite3DeleteTrigger(db, pParse->pNewTrigger); sqlite3DbFree(db, pParse->zErrMsg); renameTokenFree(db, pParse->pRename); - sqlite3ParserReset(pParse); + sqlite3ParseObjectReset(pParse); } /* ** SQL function: ** -** sqlite_rename_column(zSql, iCol, bQuote, zNew, zTable, zOld) +** sqlite_rename_column(SQL,TYPE,OBJ,DB,TABLE,COL,NEWNAME,QUOTE,TEMP) ** ** 0. zSql: SQL statement to rewrite ** 1. type: Type of object ("table", "view" etc.) @@ -103564,7 +122081,8 @@ static void renameParseCleanup(Parse *pParse){ ** ** This function is used internally by the ALTER TABLE RENAME COLUMN command. ** It is only accessible to SQL created using sqlite3NestedParse(). It is -** not reachable from ordinary SQL passed into sqlite3_prepare(). +** not reachable from ordinary SQL passed into sqlite3_prepare() unless the +** SQLITE_TESTCTRL_INTERNAL_FUNCTIONS test setting is enabled. */ static void renameColumnFunc( sqlite3_context *context, @@ -103602,14 +122120,14 @@ static void renameColumnFunc( sqlite3BtreeLeaveAll(db); return; } - zOld = pTab->aCol[iCol].zName; + zOld = pTab->aCol[iCol].zCnName; memset(&sCtx, 0, sizeof(sCtx)); sCtx.iCol = ((iCol==pTab->iPKey) ? -1 : iCol); #ifndef SQLITE_OMIT_AUTHORIZATION db->xAuth = 0; #endif - rc = renameParseSql(&sParse, zDb, 0, db, zSql, bTemp); + rc = renameParseSql(&sParse, zDb, db, zSql, bTemp); /* Find tokens that need to be replaced. */ memset(&sWalker, 0, sizeof(Walker)); @@ -103621,25 +122139,27 @@ static void renameColumnFunc( sCtx.pTab = pTab; if( rc!=SQLITE_OK ) goto renameColumnFunc_done; if( sParse.pNewTable ){ - Select *pSelect = sParse.pNewTable->pSelect; - if( pSelect ){ + if( IsView(sParse.pNewTable) ){ + Select *pSelect = sParse.pNewTable->u.view.pSelect; + pSelect->selFlags &= ~(u32)SF_View; sParse.rc = SQLITE_OK; - sqlite3SelectPrep(&sParse, sParse.pNewTable->pSelect, 0); + sqlite3SelectPrep(&sParse, pSelect, 0); rc = (db->mallocFailed ? SQLITE_NOMEM : sParse.rc); if( rc==SQLITE_OK ){ sqlite3WalkSelect(&sWalker, pSelect); } if( rc!=SQLITE_OK ) goto renameColumnFunc_done; - }else{ + }else if( IsOrdinaryTable(sParse.pNewTable) ){ /* A regular table */ int bFKOnly = sqlite3_stricmp(zTable, sParse.pNewTable->zName); FKey *pFKey; - assert( sParse.pNewTable->pSelect==0 ); sCtx.pTab = sParse.pNewTable; if( bFKOnly==0 ){ - renameTokenFind( - &sParse, &sCtx, (void*)sParse.pNewTable->aCol[iCol].zName - ); + if( iColnCol ){ + renameTokenFind( + &sParse, &sCtx, (void*)sParse.pNewTable->aCol[iCol].zCnName + ); + } if( sCtx.iCol<0 ){ renameTokenFind(&sParse, &sCtx, (void*)&sParse.pNewTable->iPKey); } @@ -103650,9 +122170,17 @@ static void renameColumnFunc( for(pIdx=sParse.pNewIndex; pIdx; pIdx=pIdx->pNext){ sqlite3WalkExprList(&sWalker, pIdx->aColExpr); } +#ifndef SQLITE_OMIT_GENERATED_COLUMNS + for(i=0; inCol; i++){ + Expr *pExpr = sqlite3ColumnExpr(sParse.pNewTable, + &sParse.pNewTable->aCol[i]); + sqlite3WalkExpr(&sWalker, pExpr); + } +#endif } - for(pFKey=sParse.pNewTable->pFKey; pFKey; pFKey=pFKey->pNextFrom){ + assert( IsOrdinaryTable(sParse.pNewTable) ); + for(pFKey=sParse.pNewTable->u.tab.pFKey; pFKey; pFKey=pFKey->pNextFrom){ for(i=0; inCol; i++){ if( bFKOnly==0 && pFKey->aCol[i].iFrom==iCol ){ renameTokenFind(&sParse, &sCtx, (void*)&pFKey->aCol[i]); @@ -103671,12 +122199,12 @@ static void renameColumnFunc( }else{ /* A trigger */ TriggerStep *pStep; - rc = renameResolveTrigger(&sParse, (bTemp ? 0 : zDb)); + rc = renameResolveTrigger(&sParse); if( rc!=SQLITE_OK ) goto renameColumnFunc_done; for(pStep=sParse.pNewTrigger->step_list; pStep; pStep=pStep->pNext){ - if( pStep->zTarget ){ - Table *pTarget = sqlite3LocateTable(&sParse, 0, pStep->zTarget, zDb); + if( pStep->pSrc ){ + Table *pTarget = sqlite3LocateTableItem(&sParse, 0, &pStep->pSrc->a[0]); if( pTarget==pTab ){ if( pStep->pUpsert ){ ExprList *pUpsertSet = pStep->pUpsert->pUpsertSet; @@ -103688,7 +122216,6 @@ static void renameColumnFunc( } } - /* Find tokens to edit in UPDATE OF clause */ if( sParse.pTriggerTab==pTab ){ renameColumnIdlistNames(&sParse, &sCtx,sParse.pNewTrigger->pColumns,zOld); @@ -103703,8 +122230,10 @@ static void renameColumnFunc( renameColumnFunc_done: if( rc!=SQLITE_OK ){ - if( sParse.zErrMsg ){ - renameColumnParseError(context, 0, argv[1], argv[2], &sParse); + if( rc==SQLITE_ERROR && sqlite3WritableSchema(db) ){ + sqlite3_result_value(context, argv[0]); + }else if( sParse.zErrMsg ){ + renameColumnParseError(context, "", argv[1], argv[2], &sParse); }else{ sqlite3_result_error_code(context, rc); } @@ -103719,30 +122248,38 @@ static void renameColumnFunc( } /* -** Walker expression callback used by "RENAME TABLE". +** Walker expression callback used by "RENAME TABLE". */ static int renameTableExprCb(Walker *pWalker, Expr *pExpr){ RenameCtx *p = pWalker->u.pRename; - if( pExpr->op==TK_COLUMN && p->pTab==pExpr->y.pTab ){ + if( pExpr->op==TK_COLUMN + && ALWAYS(ExprUseYTab(pExpr)) + && p->pTab==pExpr->y.pTab + ){ renameTokenFind(pWalker->pParse, p, (void*)&pExpr->y.pTab); } return WRC_Continue; } /* -** Walker select callback used by "RENAME TABLE". +** Walker select callback used by "RENAME TABLE". */ static int renameTableSelectCb(Walker *pWalker, Select *pSelect){ int i; RenameCtx *p = pWalker->u.pRename; SrcList *pSrc = pSelect->pSrc; - if( pSrc==0 ){ + if( pSelect->selFlags & (SF_View|SF_CopyCte) ){ + testcase( pSelect->selFlags & SF_View ); + testcase( pSelect->selFlags & SF_CopyCte ); + return WRC_Prune; + } + if( NEVER(pSrc==0) ){ assert( pWalker->pParse->db->mallocFailed ); return WRC_Abort; } for(i=0; inSrc; i++){ - struct SrcList_item *pItem = &pSrc->a[i]; - if( pItem->pTab==p->pTab ){ + SrcItem *pItem = &pSrc->a[i]; + if( pItem->pSTab==p->pTab ){ renameTokenFind(pWalker->pParse, p, pItem->zName); } } @@ -103755,7 +122292,7 @@ static int renameTableSelectCb(Walker *pWalker, Select *pSelect){ /* ** This C function implements an SQL user function that is used by SQL code ** generated by the ALTER TABLE ... RENAME command to modify the definition -** of any foreign key constraints that use the table being renamed as the +** of any foreign key constraints that use the table being renamed as the ** parent table. It is passed three arguments: ** ** 0: The database containing the table being renamed. @@ -103806,29 +122343,38 @@ static void renameTableFunc( sWalker.xSelectCallback = renameTableSelectCb; sWalker.u.pRename = &sCtx; - rc = renameParseSql(&sParse, zDb, 1, db, zInput, bTemp); + rc = renameParseSql(&sParse, zDb, db, zInput, bTemp); if( rc==SQLITE_OK ){ int isLegacy = (db->flags & SQLITE_LegacyAlter); if( sParse.pNewTable ){ Table *pTab = sParse.pNewTable; - if( pTab->pSelect ){ + if( IsView(pTab) ){ if( isLegacy==0 ){ + Select *pSelect = pTab->u.view.pSelect; NameContext sNC; memset(&sNC, 0, sizeof(sNC)); sNC.pParse = &sParse; - sqlite3SelectPrep(&sParse, pTab->pSelect, &sNC); - if( sParse.nErr ) rc = sParse.rc; - sqlite3WalkSelect(&sWalker, pTab->pSelect); + assert( pSelect->selFlags & SF_View ); + pSelect->selFlags &= ~(u32)SF_View; + sqlite3SelectPrep(&sParse, pTab->u.view.pSelect, &sNC); + if( sParse.nErr ){ + rc = sParse.rc; + }else{ + sqlite3WalkSelect(&sWalker, pTab->u.view.pSelect); + } } }else{ /* Modify any FK definitions to point to the new table. */ #ifndef SQLITE_OMIT_FOREIGN_KEY - if( isLegacy==0 || (db->flags & SQLITE_ForeignKeys) ){ + if( (isLegacy==0 || (db->flags & SQLITE_ForeignKeys)) + && !IsVirtual(pTab) + ){ FKey *pFKey; - for(pFKey=pTab->pFKey; pFKey; pFKey=pFKey->pNextFrom){ + assert( IsOrdinaryTable(pTab) ); + for(pFKey=pTab->u.tab.pFKey; pFKey; pFKey=pFKey->pNextFrom){ if( sqlite3_stricmp(pFKey->zTo, zOld)==0 ){ renameTokenFind(&sParse, &sCtx, (void*)pFKey->zTo); } @@ -103860,19 +122406,25 @@ static void renameTableFunc( else{ Trigger *pTrigger = sParse.pNewTrigger; TriggerStep *pStep; - if( 0==sqlite3_stricmp(sParse.pNewTrigger->table, zOld) + if( 0==sqlite3_stricmp(sParse.pNewTrigger->table, zOld) && sCtx.pTab->pSchema==pTrigger->pTabSchema ){ renameTokenFind(&sParse, &sCtx, sParse.pNewTrigger->table); } if( isLegacy==0 ){ - rc = renameResolveTrigger(&sParse, bTemp ? 0 : zDb); + rc = renameResolveTrigger(&sParse); if( rc==SQLITE_OK ){ renameWalkTrigger(&sWalker, pTrigger); for(pStep=pTrigger->step_list; pStep; pStep=pStep->pNext){ - if( pStep->zTarget && 0==sqlite3_stricmp(pStep->zTarget, zOld) ){ - renameTokenFind(&sParse, &sCtx, pStep->zTarget); + if( pStep->pSrc ){ + int i; + for(i=0; ipSrc->nSrc; i++){ + SrcItem *pItem = &pStep->pSrc->a[i]; + if( 0==sqlite3_stricmp(pItem->zName, zOld) ){ + renameTokenFind(&sParse, &sCtx, pItem->zName); + } + } } } } @@ -103885,8 +122437,10 @@ static void renameTableFunc( rc = renameEditSql(context, &sCtx, zInput, zNew, bQuote); } if( rc!=SQLITE_OK ){ - if( sParse.zErrMsg ){ - renameColumnParseError(context, 0, argv[1], argv[2], &sParse); + if( rc==SQLITE_ERROR && sqlite3WritableSchema(db) ){ + sqlite3_result_value(context, argv[3]); + }else if( sParse.zErrMsg ){ + renameColumnParseError(context, "", argv[1], argv[2], &sParse); }else{ sqlite3_result_error_code(context, rc); } @@ -103903,7 +122457,131 @@ static void renameTableFunc( return; } -/* +static int renameQuotefixExprCb(Walker *pWalker, Expr *pExpr){ + if( pExpr->op==TK_STRING && (pExpr->flags & EP_DblQuoted) ){ + renameTokenFind(pWalker->pParse, pWalker->u.pRename, (const void*)pExpr); + } + return WRC_Continue; +} + +/* SQL function: sqlite_rename_quotefix(DB,SQL) +** +** Rewrite the DDL statement "SQL" so that any string literals that use +** double-quotes use single quotes instead. +** +** Two arguments must be passed: +** +** 0: Database name ("main", "temp" etc.). +** 1: SQL statement to edit. +** +** The returned value is the modified SQL statement. For example, given +** the database schema: +** +** CREATE TABLE t1(a, b, c); +** +** SELECT sqlite_rename_quotefix('main', +** 'CREATE VIEW v1 AS SELECT "a", "string" FROM t1' +** ); +** +** returns the string: +** +** CREATE VIEW v1 AS SELECT "a", 'string' FROM t1 +** +** If there is a error in the input SQL, then raise an error, except +** if PRAGMA writable_schema=ON, then just return the input string +** unmodified following an error. +*/ +static void renameQuotefixFunc( + sqlite3_context *context, + int NotUsed, + sqlite3_value **argv +){ + sqlite3 *db = sqlite3_context_db_handle(context); + char const *zDb = (const char*)sqlite3_value_text(argv[0]); + char const *zInput = (const char*)sqlite3_value_text(argv[1]); + +#ifndef SQLITE_OMIT_AUTHORIZATION + sqlite3_xauth xAuth = db->xAuth; + db->xAuth = 0; +#endif + + sqlite3BtreeEnterAll(db); + + UNUSED_PARAMETER(NotUsed); + if( zDb && zInput ){ + int rc; + Parse sParse; + rc = renameParseSql(&sParse, zDb, db, zInput, 0); + + if( rc==SQLITE_OK ){ + RenameCtx sCtx; + Walker sWalker; + + /* Walker to find tokens that need to be replaced. */ + memset(&sCtx, 0, sizeof(RenameCtx)); + memset(&sWalker, 0, sizeof(Walker)); + sWalker.pParse = &sParse; + sWalker.xExprCallback = renameQuotefixExprCb; + sWalker.xSelectCallback = renameColumnSelectCb; + sWalker.u.pRename = &sCtx; + + if( sParse.pNewTable ){ + if( IsView(sParse.pNewTable) ){ + Select *pSelect = sParse.pNewTable->u.view.pSelect; + pSelect->selFlags &= ~(u32)SF_View; + sParse.rc = SQLITE_OK; + sqlite3SelectPrep(&sParse, pSelect, 0); + rc = (db->mallocFailed ? SQLITE_NOMEM : sParse.rc); + if( rc==SQLITE_OK ){ + sqlite3WalkSelect(&sWalker, pSelect); + } + }else{ + int i; + sqlite3WalkExprList(&sWalker, sParse.pNewTable->pCheck); +#ifndef SQLITE_OMIT_GENERATED_COLUMNS + for(i=0; inCol; i++){ + sqlite3WalkExpr(&sWalker, + sqlite3ColumnExpr(sParse.pNewTable, + &sParse.pNewTable->aCol[i])); + } +#endif /* SQLITE_OMIT_GENERATED_COLUMNS */ + } + }else if( sParse.pNewIndex ){ + sqlite3WalkExprList(&sWalker, sParse.pNewIndex->aColExpr); + sqlite3WalkExpr(&sWalker, sParse.pNewIndex->pPartIdxWhere); + }else{ +#ifndef SQLITE_OMIT_TRIGGER + rc = renameResolveTrigger(&sParse); + if( rc==SQLITE_OK ){ + renameWalkTrigger(&sWalker, sParse.pNewTrigger); + } +#endif /* SQLITE_OMIT_TRIGGER */ + } + + if( rc==SQLITE_OK ){ + rc = renameEditSql(context, &sCtx, zInput, 0, 0); + } + renameTokenFree(db, sCtx.pList); + } + if( rc!=SQLITE_OK ){ + if( sqlite3WritableSchema(db) && rc==SQLITE_ERROR ){ + sqlite3_result_value(context, argv[1]); + }else{ + sqlite3_result_error_code(context, rc); + } + } + renameParseCleanup(&sParse); + } + +#ifndef SQLITE_OMIT_AUTHORIZATION + db->xAuth = xAuth; +#endif + + sqlite3BtreeLeaveAll(db); +} + +/* Function: sqlite_rename_test(DB,SQL,TYPE,NAME,ISTEMP,WHEN,DQS) +** ** An SQL user function that checks that there are no parse or symbol ** resolution problems in a CREATE TRIGGER|TABLE|VIEW|INDEX statement. ** After an ALTER TABLE .. RENAME operation is performed and the schema @@ -103915,12 +122593,16 @@ static void renameTableFunc( ** 2: Object type ("view", "table", "trigger" or "index"). ** 3: Object name. ** 4: True if object is from temp schema. +** 5: "when" part of error message. +** 6: True to disable the DQS quirk when parsing SQL. ** -** Unless it finds an error, this function normally returns NULL. However, it -** returns integer value 1 if: +** The return value is computed as follows: ** -** * the SQL argument creates a trigger, and -** * the table that the trigger is attached to is in database zDb. +** A. If an error is seen and not in PRAGMA writable_schema=ON mode, +** then raise the error. +** B. Else if a trigger is created and the the table that the trigger is +** attached to is in database zDb, then return 1. +** C. Otherwise return NULL. */ static void renameTableTest( sqlite3_context *context, @@ -103932,6 +122614,8 @@ static void renameTableTest( char const *zInput = (const char*)sqlite3_value_text(argv[1]); int bTemp = sqlite3_value_int(argv[4]); int isLegacy = (db->flags & SQLITE_LegacyAlter); + char const *zWhen = (const char*)sqlite3_value_text(argv[5]); + int bNoDQS = sqlite3_value_int(argv[6]); #ifndef SQLITE_OMIT_AUTHORIZATION sqlite3_xauth xAuth = db->xAuth; @@ -103939,33 +122623,41 @@ static void renameTableTest( #endif UNUSED_PARAMETER(NotUsed); + if( zDb && zInput ){ int rc; Parse sParse; - rc = renameParseSql(&sParse, zDb, 1, db, zInput, bTemp); + u64 flags = db->flags; + if( bNoDQS ) db->flags &= ~(SQLITE_DqsDML|SQLITE_DqsDDL); + rc = renameParseSql(&sParse, zDb, db, zInput, bTemp); + db->flags = flags; if( rc==SQLITE_OK ){ - if( isLegacy==0 && sParse.pNewTable && sParse.pNewTable->pSelect ){ + if( isLegacy==0 && sParse.pNewTable && IsView(sParse.pNewTable) ){ NameContext sNC; memset(&sNC, 0, sizeof(sNC)); sNC.pParse = &sParse; - sqlite3SelectPrep(&sParse, sParse.pNewTable->pSelect, &sNC); + sqlite3SelectPrep(&sParse, sParse.pNewTable->u.view.pSelect, &sNC); if( sParse.nErr ) rc = sParse.rc; } else if( sParse.pNewTrigger ){ if( isLegacy==0 ){ - rc = renameResolveTrigger(&sParse, bTemp ? 0 : zDb); + rc = renameResolveTrigger(&sParse); } if( rc==SQLITE_OK ){ int i1 = sqlite3SchemaToIndex(db, sParse.pNewTrigger->pTabSchema); int i2 = sqlite3FindDbName(db, zDb); - if( i1==i2 ) sqlite3_result_int(context, 1); + if( i1==i2 ){ + /* Handle output case B */ + sqlite3_result_int(context, 1); + } } } } - if( rc!=SQLITE_OK ){ - renameColumnParseError(context, 1, argv[2], argv[3], &sParse); + if( rc!=SQLITE_OK && zWhen && !sqlite3WritableSchema(db) ){ + /* Output case A */ + renameColumnParseError(context, zWhen, argv[2], argv[3],&sParse); } renameParseCleanup(&sParse); } @@ -103975,14 +122667,960 @@ static void renameTableTest( #endif } + +/* +** Return the number of bytes until the end of the next non-whitespace and +** non-comment token. For the purpose of this function, a "(" token includes +** all of the bytes through and including the matching ")", or until the +** first illegal token, whichever comes first. +** +** Write the token type into *piToken. +** +** The value returned is the number of bytes in the token itself plus +** the number of bytes of leading whitespace and comments skipped plus +** all bytes through the next matching ")" if the token is TK_LP. +** +** Example: (Note: '.' used in place of '*' in the example z[] text) +** +** ,--------- *piToken := TK_RP +** v +** z[] = " /.comment./ --comment\n (two three four) five" +** | | +** |<-------------------------------------->| +** | +** `--- return value +*/ +static int getConstraintToken(const u8 *z, int *piToken){ + int iOff = 0; + int t = 0; + do { + iOff += sqlite3GetToken(&z[iOff], &t); + }while( t==TK_SPACE || t==TK_COMMENT ); + + *piToken = t; + + if( t==TK_LP ){ + int nNest = 1; + while( nNest>0 ){ + iOff += sqlite3GetToken(&z[iOff], &t); + if( t==TK_LP ){ + nNest++; + }else if( t==TK_RP ){ + t = TK_LP; + nNest--; + }else if( t==TK_ILLEGAL ){ + break; + } + } + } + + *piToken = t; + return iOff; +} + +/* +** The implementation of internal UDF sqlite_drop_column(). +** +** Arguments: +** +** argv[0]: An integer - the index of the schema containing the table +** argv[1]: CREATE TABLE statement to modify. +** argv[2]: An integer - the index of the column to remove. +** +** The value returned is a string containing the CREATE TABLE statement +** with column argv[2] removed. +*/ +static void dropColumnFunc( + sqlite3_context *context, + int NotUsed, + sqlite3_value **argv +){ + sqlite3 *db = sqlite3_context_db_handle(context); + int iSchema = sqlite3_value_int(argv[0]); + const char *zSql = (const char*)sqlite3_value_text(argv[1]); + int iCol = sqlite3_value_int(argv[2]); + const char *zDb = db->aDb[iSchema].zDbSName; + int rc; + Parse sParse; + RenameToken *pCol; + Table *pTab; + const char *zEnd; + char *zNew = 0; + +#ifndef SQLITE_OMIT_AUTHORIZATION + sqlite3_xauth xAuth = db->xAuth; + db->xAuth = 0; +#endif + + UNUSED_PARAMETER(NotUsed); + rc = renameParseSql(&sParse, zDb, db, zSql, iSchema==1); + if( rc!=SQLITE_OK ) goto drop_column_done; + pTab = sParse.pNewTable; + if( pTab==0 || pTab->nCol==1 || iCol>=pTab->nCol ){ + /* This can happen if the sqlite_schema table is corrupt */ + rc = SQLITE_CORRUPT_BKPT; + goto drop_column_done; + } + + if( iColnCol-1 ){ + RenameToken *pEnd; + pCol = renameTokenFind(&sParse, 0, (void*)pTab->aCol[iCol].zCnName); + pEnd = renameTokenFind(&sParse, 0, (void*)pTab->aCol[iCol+1].zCnName); + zEnd = (const char*)pEnd->t.z; + }else{ + int eTok; + assert( IsOrdinaryTable(pTab) ); + assert( iCol!=0 ); + /* Point pCol->t.z at the "," immediately preceding the definition of + ** the column being dropped. To do this, start at the name of the + ** previous column, and tokenize until the next ",". */ + pCol = renameTokenFind(&sParse, 0, (void*)pTab->aCol[iCol-1].zCnName); + do { + pCol->t.z += getConstraintToken((const u8*)pCol->t.z, &eTok); + }while( eTok!=TK_COMMA ); + pCol->t.z--; + zEnd = (const char*)&zSql[pTab->u.tab.addColOffset]; + } + + zNew = sqlite3MPrintf(db, "%.*s%s", pCol->t.z-zSql, zSql, zEnd); + sqlite3_result_text(context, zNew, -1, SQLITE_TRANSIENT); + sqlite3_free(zNew); + +drop_column_done: + renameParseCleanup(&sParse); +#ifndef SQLITE_OMIT_AUTHORIZATION + db->xAuth = xAuth; +#endif + if( rc!=SQLITE_OK ){ + sqlite3_result_error_code(context, rc); + } +} + +/* +** This function is called by the parser upon parsing an +** +** ALTER TABLE pSrc DROP COLUMN pName +** +** statement. Argument pSrc contains the possibly qualified name of the +** table being edited, and token pName the name of the column to drop. +*/ +SQLITE_PRIVATE void sqlite3AlterDropColumn(Parse *pParse, SrcList *pSrc, const Token *pName){ + sqlite3 *db = pParse->db; /* Database handle */ + Table *pTab; /* Table to modify */ + int iDb; /* Index of db containing pTab in aDb[] */ + const char *zDb; /* Database containing pTab ("main" etc.) */ + char *zCol = 0; /* Name of column to drop */ + int iCol; /* Index of column zCol in pTab->aCol[] */ + + /* Look up the table being altered. */ + assert( pParse->pNewTable==0 ); + assert( sqlite3BtreeHoldsAllMutexes(db) ); + if( NEVER(db->mallocFailed) ) goto exit_drop_column; + pTab = sqlite3LocateTableItem(pParse, 0, &pSrc->a[0]); + if( !pTab ) goto exit_drop_column; + + /* Make sure this is not an attempt to ALTER a view, virtual table or + ** system table. */ + if( SQLITE_OK!=isAlterableTable(pParse, pTab) ) goto exit_drop_column; + if( SQLITE_OK!=isRealTable(pParse, pTab, 1) ) goto exit_drop_column; + + /* Find the index of the column being dropped. */ + zCol = sqlite3NameFromToken(db, pName); + if( zCol==0 ){ + assert( db->mallocFailed ); + goto exit_drop_column; + } + iCol = sqlite3ColumnIndex(pTab, zCol); + if( iCol<0 ){ + sqlite3ErrorMsg(pParse, "no such column: \"%T\"", pName); + goto exit_drop_column; + } + + /* Do not allow the user to drop a PRIMARY KEY column or a column + ** constrained by a UNIQUE constraint. */ + if( pTab->aCol[iCol].colFlags & (COLFLAG_PRIMKEY|COLFLAG_UNIQUE) ){ + sqlite3ErrorMsg(pParse, "cannot drop %s column: \"%s\"", + (pTab->aCol[iCol].colFlags&COLFLAG_PRIMKEY) ? "PRIMARY KEY" : "UNIQUE", + zCol + ); + goto exit_drop_column; + } + + /* Do not allow the number of columns to go to zero */ + if( pTab->nCol<=1 ){ + sqlite3ErrorMsg(pParse, "cannot drop column \"%s\": no other columns exist",zCol); + goto exit_drop_column; + } + + /* Edit the sqlite_schema table */ + iDb = sqlite3SchemaToIndex(db, pTab->pSchema); + assert( iDb>=0 ); + zDb = db->aDb[iDb].zDbSName; +#ifndef SQLITE_OMIT_AUTHORIZATION + /* Invoke the authorization callback. */ + if( sqlite3AuthCheck(pParse, SQLITE_ALTER_TABLE, zDb, pTab->zName, zCol) ){ + goto exit_drop_column; + } +#endif + renameTestSchema(pParse, zDb, iDb==1, "", 0); + renameFixQuotes(pParse, zDb, iDb==1); + sqlite3NestedParse(pParse, + "UPDATE \"%w\"." LEGACY_SCHEMA_TABLE " SET " + "sql = sqlite_drop_column(%d, sql, %d) " + "WHERE (type=='table' AND tbl_name=%Q COLLATE nocase)" + , zDb, iDb, iCol, pTab->zName + ); + + /* Drop and reload the database schema. */ + renameReloadSchema(pParse, iDb, INITFLAG_AlterDrop); + renameTestSchema(pParse, zDb, iDb==1, "after drop column", 1); + + /* Edit rows of table on disk */ + if( pParse->nErr==0 && (pTab->aCol[iCol].colFlags & COLFLAG_VIRTUAL)==0 ){ + int i; + int addr; + int reg; + int regRec; + Index *pPk = 0; + int nField = 0; /* Number of non-virtual columns after drop */ + int iCur; + Vdbe *v = sqlite3GetVdbe(pParse); + iCur = pParse->nTab++; + sqlite3OpenTable(pParse, iCur, iDb, pTab, OP_OpenWrite); + addr = sqlite3VdbeAddOp1(v, OP_Rewind, iCur); VdbeCoverage(v); + reg = ++pParse->nMem; + if( HasRowid(pTab) ){ + sqlite3VdbeAddOp2(v, OP_Rowid, iCur, reg); + pParse->nMem += pTab->nCol; + }else{ + pPk = sqlite3PrimaryKeyIndex(pTab); + pParse->nMem += pPk->nColumn; + for(i=0; inKeyCol; i++){ + sqlite3VdbeAddOp3(v, OP_Column, iCur, i, reg+i+1); + } + nField = pPk->nKeyCol; + } + regRec = ++pParse->nMem; + for(i=0; inCol; i++){ + if( i!=iCol && (pTab->aCol[i].colFlags & COLFLAG_VIRTUAL)==0 ){ + int regOut; + if( pPk ){ + int iPos = sqlite3TableColumnToIndex(pPk, i); + int iColPos = sqlite3TableColumnToIndex(pPk, iCol); + if( iPosnKeyCol ) continue; + regOut = reg+1+iPos-(iPos>iColPos); + }else{ + regOut = reg+1+nField; + } + if( i==pTab->iPKey ){ + sqlite3VdbeAddOp2(v, OP_Null, 0, regOut); + }else{ + char aff = pTab->aCol[i].affinity; + if( aff==SQLITE_AFF_REAL ){ + pTab->aCol[i].affinity = SQLITE_AFF_NUMERIC; + } + sqlite3ExprCodeGetColumnOfTable(v, pTab, iCur, i, regOut); + pTab->aCol[i].affinity = aff; + } + nField++; + } + } + if( nField==0 ){ + /* dbsqlfuzz 5f09e7bcc78b4954d06bf9f2400d7715f48d1fef */ + pParse->nMem++; + sqlite3VdbeAddOp2(v, OP_Null, 0, reg+1); + nField = 1; + } + sqlite3VdbeAddOp3(v, OP_MakeRecord, reg+1, nField, regRec); + if( pPk ){ + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iCur, regRec, reg+1, pPk->nKeyCol); + }else{ + sqlite3VdbeAddOp3(v, OP_Insert, iCur, regRec, reg); + } + sqlite3VdbeChangeP5(v, OPFLAG_SAVEPOSITION); + + sqlite3VdbeAddOp2(v, OP_Next, iCur, addr+1); VdbeCoverage(v); + sqlite3VdbeJumpHere(v, addr); + } + +exit_drop_column: + sqlite3DbFree(db, zCol); + sqlite3SrcListDelete(db, pSrc); +} + +/* +** Return the number of bytes of leading whitespace/comments in string z[]. +*/ +static int getWhitespace(const u8 *z){ + int nRet = 0; + while( 1 ){ + int t = 0; + int n = sqlite3GetToken(&z[nRet], &t); + if( t!=TK_SPACE && t!=TK_COMMENT ) break; + nRet += n; + } + return nRet; +} + + +/* +** Argument z points into the body of a constraint - specifically the +** second token of the constraint definition. For a named constraint, +** z points to the first token past the CONSTRAINT keyword. For an +** unnamed NOT NULL constraint, z points to the first byte past the NOT +** keyword. +** +** Return the number of bytes until the end of the constraint. +*/ +static int getConstraint(const u8 *z){ + int iOff = 0; + int t = 0; + + /* Now, the current constraint proceeds until the next occurence of one + ** of the following tokens: + ** + ** CONSTRAINT, PRIMARY, NOT, UNIQUE, CHECK, DEFAULT, + ** COLLATE, REFERENCES, FOREIGN, GENERATED, AS, RP, or COMMA + ** + ** Also exit the loop if ILLEGAL turns up. + */ + while( 1 ){ + int n = getConstraintToken(&z[iOff], &t); + if( t==TK_CONSTRAINT || t==TK_PRIMARY || t==TK_NOT || t==TK_UNIQUE + || t==TK_CHECK || t==TK_DEFAULT || t==TK_COLLATE || t==TK_REFERENCES + || t==TK_FOREIGN || t==TK_RP || t==TK_COMMA || t==TK_ILLEGAL + || t==TK_AS || t==TK_GENERATED + ){ + break; + } + iOff += n; + } + + return iOff; +} + +/* +** Compare two constraint names. +** +** Summary: *pRes := zQuote != zCmp +** +** Details: +** Compare the (possibly quoted) constraint name zQuote[0..nQuote-1] +** against zCmp[]. Write zero into *pRes if they are the same and +** non-zero if they differ. Normally return SQLITE_OK, except if there +** is an OOM, set the OOM error condition on ctx and return SQLITE_NOMEM. +*/ +static int quotedCompare( + sqlite3_context *ctx, /* Function context on which to report errors */ + int t, /* Token type */ + const u8 *zQuote, /* Possibly quoted text. Not zero-terminated. */ + int nQuote, /* Length of zQuote in bytes */ + const u8 *zCmp, /* Zero-terminated, unquoted name to compare against */ + int *pRes /* OUT: Set to 0 if equal, non-zero if unequal */ +){ + char *zCopy = 0; /* De-quoted, zero-terminated copy of zQuote[] */ + + if( t==TK_ILLEGAL ){ + *pRes = 1; + return SQLITE_OK; + } + zCopy = sqlite3MallocZero(nQuote+1); + if( zCopy==0 ){ + sqlite3_result_error_nomem(ctx); + return SQLITE_NOMEM_BKPT; + } + memcpy(zCopy, zQuote, nQuote); + sqlite3Dequote(zCopy); + *pRes = sqlite3_stricmp((const char*)zCopy, (const char*)zCmp); + sqlite3_free(zCopy); + return SQLITE_OK; +} + +/* +** zSql[] is a CREATE TABLE statement, supposedly. Find the offset +** into zSql[] of the first character past the first "(" and write +** that offset into *piOff and return SQLITE_OK. Or, if not found, +** set the SQLITE_CORRUPT error code and return SQLITE_ERROR. +*/ +static int skipCreateTable(sqlite3_context *ctx, const u8 *zSql, int *piOff){ + int iOff = 0; + + if( zSql==0 ) return SQLITE_ERROR; + + /* Jump past the "CREATE TABLE" bit. */ + while( 1 ){ + int t = 0; + iOff += sqlite3GetToken(&zSql[iOff], &t); + if( t==TK_LP ) break; + if( t==TK_ILLEGAL ){ + sqlite3_result_error_code(ctx, SQLITE_CORRUPT_BKPT); + return SQLITE_ERROR; + } + } + + *piOff = iOff; + return SQLITE_OK; +} + +/* +** Internal SQL function sqlite3_drop_constraint(): Given an input +** CREATE TABLE statement, return a revised CREATE TABLE statement +** with a constraint removed. Two forms, depending on the datatype +** of argv[2]: +** +** sqlite_drop_constraint(SQL, INT) -- Omit NOT NULL from the INT-th column +** sqlite_drop_constraint(SQL, TEXT) -- OMIT constraint with name TEXT +** +** In the first case, the left-most column is 0. +*/ +static void dropConstraintFunc( + sqlite3_context *ctx, + int NotUsed, + sqlite3_value **argv +){ + const u8 *zSql = sqlite3_value_text(argv[0]); + const u8 *zCons = 0; + int iNotNull = -1; + int ii; + int iOff = 0; + int iStart = 0; + int iEnd = 0; + char *zNew = 0; + int t = 0; + sqlite3 *db; + UNUSED_PARAMETER(NotUsed); + + if( zSql==0 ) return; + + /* Jump past the "CREATE TABLE" bit. */ + if( skipCreateTable(ctx, zSql, &iOff) ) return; + + if( sqlite3_value_type(argv[1])==SQLITE_INTEGER ){ + iNotNull = sqlite3_value_int(argv[1]); + }else{ + zCons = sqlite3_value_text(argv[1]); + } + + /* Search for the named constraint within column definitions. */ + for(ii=0; iEnd==0; ii++){ + + /* Now parse the column or table constraint definition. Search + ** for the token CONSTRAINT if this is a DROP CONSTRAINT command, or + ** NOT in the right column if this is a DROP NOT NULL. */ + while( 1 ){ + iStart = iOff; + iOff += getConstraintToken(&zSql[iOff], &t); + if( t==TK_CONSTRAINT && (zCons || iNotNull==ii) ){ + /* Check if this is the constraint we are searching for. */ + int nTok = 0; + int cmp = 1; + + /* Skip past any whitespace. */ + iOff += getWhitespace(&zSql[iOff]); + + /* Compare the next token - which may be quoted - with the name of + ** the constraint being dropped. */ + nTok = getConstraintToken(&zSql[iOff], &t); + if( zCons ){ + if( quotedCompare(ctx, t, &zSql[iOff], nTok, zCons, &cmp) ) return; + } + iOff += nTok; + + /* The next token is usually the first token of the constraint + ** definition. This is enough to tell the type of the constraint - + ** TK_NOT means it is a NOT NULL, TK_CHECK a CHECK constraint etc. + ** + ** There is also the chance that the next token is TK_CONSTRAINT + ** (or TK_DEFAULT or TK_COLLATE), for example if a table has been + ** created as follows: + ** + ** CREATE TABLE t1(cols, CONSTRAINT one CONSTRAINT two NOT NULL); + ** + ** In this case, allow the "CONSTRAINT one" bit to be dropped by + ** this command if that is what is requested, or to advance to + ** the next iteration of the loop with &zSql[iOff] still pointing + ** to the CONSTRAINT keyword. */ + nTok = getConstraintToken(&zSql[iOff], &t); + if( t==TK_CONSTRAINT || t==TK_DEFAULT || t==TK_COLLATE + || t==TK_COMMA || t==TK_RP || t==TK_GENERATED || t==TK_AS + ){ + t = TK_CHECK; + }else{ + iOff += nTok; + iOff += getConstraint(&zSql[iOff]); + } + + if( cmp==0 || (iNotNull>=0 && t==TK_NOT) ){ + if( t!=TK_NOT && t!=TK_CHECK ){ + errorMPrintf(ctx, "constraint may not be dropped: %s", zCons); + return; + } + iEnd = iOff; + break; + } + + }else if( t==TK_NOT && iNotNull==ii ){ + iEnd = iOff + getConstraint(&zSql[iOff]); + break; + }else if( t==TK_RP || t==TK_ILLEGAL ){ + iEnd = -1; + break; + }else if( t==TK_COMMA ){ + break; + } + } + } + + /* If the constraint has not been found it is an error. */ + if( iEnd<=0 ){ + if( zCons ){ + errorMPrintf(ctx, "no such constraint: %s", zCons); + }else{ + /* SQLite follows postgres in that a DROP NOT NULL on a column that is + ** not NOT NULL is not an error. So just return the original SQL here. */ + sqlite3_result_text(ctx, (const char*)zSql, -1, SQLITE_TRANSIENT); + } + }else{ + + /* Figure out if an extra space should be inserted after the constraint + ** is removed. And if an additional comma preceding the constraint + ** should be removed. */ + const char *zSpace = " "; + iEnd += getWhitespace(&zSql[iEnd]); + sqlite3GetToken(&zSql[iEnd], &t); + if( t==TK_RP || t==TK_COMMA ){ + zSpace = ""; + if( zSql[iStart-1]==',' ) iStart--; + } + + db = sqlite3_context_db_handle(ctx); + zNew = sqlite3MPrintf(db, "%.*s%s%s", iStart, zSql, zSpace, &zSql[iEnd]); + sqlite3_result_text(ctx, zNew, -1, SQLITE_DYNAMIC); + } +} + +/* +** Internal SQL function: +** +** sqlite_add_constraint(SQL, CONSTRAINT-TEXT, ICOL) +** +** SQL is a CREATE TABLE statement. Return a modified version of +** SQL that adds CONSTRAINT-TEXT at the end of the ICOL-th column +** definition. (The left-most column defintion is 0.) +*/ +static void addConstraintFunc( + sqlite3_context *ctx, + int NotUsed, + sqlite3_value **argv +){ + const u8 *zSql = sqlite3_value_text(argv[0]); + const char *zCons = (const char*)sqlite3_value_text(argv[1]); + int iCol = sqlite3_value_int(argv[2]); + int iOff = 0; + int ii; + char *zNew = 0; + int t = 0; + sqlite3 *db; + UNUSED_PARAMETER(NotUsed); + + if( skipCreateTable(ctx, zSql, &iOff) ) return; + + for(ii=0; ii<=iCol || (iCol<0 && t!=TK_RP); ii++){ + iOff += getConstraintToken(&zSql[iOff], &t); + while( 1 ){ + int nTok = getConstraintToken(&zSql[iOff], &t); + if( t==TK_COMMA || t==TK_RP ) break; + if( t==TK_ILLEGAL ){ + sqlite3_result_error_code(ctx, SQLITE_CORRUPT_BKPT); + return; + } + iOff += nTok; + } + } + + iOff += getWhitespace(&zSql[iOff]); + + db = sqlite3_context_db_handle(ctx); + if( iCol<0 ){ + zNew = sqlite3MPrintf(db, "%.*s, %s%s", iOff, zSql, zCons, &zSql[iOff]); + }else{ + zNew = sqlite3MPrintf(db, "%.*s %s%s", iOff, zSql, zCons, &zSql[iOff]); + } + sqlite3_result_text(ctx, zNew, -1, SQLITE_DYNAMIC); +} + +/* +** Find a column named pCol in table pTab. If successful, set output +** parameter *piCol to the index of the column in the table and return +** SQLITE_OK. Otherwise, set *piCol to -1 and return an SQLite error +** code. +*/ +static int alterFindCol(Parse *pParse, Table *pTab, Token *pCol, int *piCol){ + sqlite3 *db = pParse->db; + char *zName = sqlite3NameFromToken(db, pCol); + int rc = SQLITE_NOMEM; + int iCol = -1; + + if( zName ){ + iCol = sqlite3ColumnIndex(pTab, zName); + if( iCol<0 ){ + sqlite3ErrorMsg(pParse, "no such column: %s", zName); + rc = SQLITE_ERROR; + }else{ + rc = SQLITE_OK; + } + } + +#ifndef SQLITE_OMIT_AUTHORIZATION + if( rc==SQLITE_OK ){ + const char *zDb = db->aDb[sqlite3SchemaToIndex(db, pTab->pSchema)].zDbSName; + const char *zCol = pTab->aCol[iCol].zCnName; + if( sqlite3AuthCheck(pParse, SQLITE_ALTER_TABLE, zDb, pTab->zName, zCol) ){ + pTab = 0; + } + } +#endif + + sqlite3DbFree(db, zName); + *piCol = iCol; + return rc; +} + + +/* +** Find the table named by the first entry in source list pSrc. If successful, +** return a pointer to the Table structure and set output variable (*pzDb) +** to point to the name of the database containin the table (i.e. "main", +** "temp" or the name of an attached database). +** +** If the table cannot be located, return NULL. The value of the two output +** parameters is undefined in this case. +*/ +static Table *alterFindTable( + Parse *pParse, /* Parsing context */ + SrcList *pSrc, /* Name of the table to look for */ + int *piDb, /* OUT: write the iDb here */ + const char **pzDb, /* OUT: write name of schema here */ + int bAuth /* Do ALTER TABLE authorization checks if true */ +){ + sqlite3 *db = pParse->db; + Table *pTab = 0; + assert( sqlite3BtreeHoldsAllMutexes(db) ); + pTab = sqlite3LocateTableItem(pParse, 0, &pSrc->a[0]); + if( pTab ){ + int iDb = sqlite3SchemaToIndex(db, pTab->pSchema); + *pzDb = db->aDb[iDb].zDbSName; + *piDb = iDb; + + if( SQLITE_OK!=isRealTable(pParse, pTab, 2) + || SQLITE_OK!=isAlterableTable(pParse, pTab) + ){ + pTab = 0; + } + } +#ifndef SQLITE_OMIT_AUTHORIZATION + if( pTab && bAuth ){ + if( sqlite3AuthCheck(pParse, SQLITE_ALTER_TABLE, *pzDb, pTab->zName, 0) ){ + pTab = 0; + } + } +#endif + sqlite3SrcListDelete(db, pSrc); + return pTab; +} + +/* +** Generate bytecode for one of: +** +** (1) ALTER TABLE pSrc DROP CONSTRAINT pCons +** (2) ALTER TABLE pSrc ALTER pCol DROP NOT NULL +** +** One of pCons and pCol must be NULL and the other non-null. +*/ +SQLITE_PRIVATE void sqlite3AlterDropConstraint( + Parse *pParse, /* Parsing context */ + SrcList *pSrc, /* The table being altered */ + Token *pCons, /* Name of the constraint to drop */ + Token *pCol /* Name of the column from which to remove the NOT NULL */ +){ + sqlite3 *db = pParse->db; + Table *pTab = 0; + int iDb = 0; + const char *zDb = 0; + char *zArg = 0; + + assert( (pCol==0)!=(pCons==0) ); + assert( pSrc->nSrc==1 ); + pTab = alterFindTable(pParse, pSrc, &iDb, &zDb, pCons!=0); + if( !pTab ) return; + + if( pCons ){ + char *z = sqlite3NameFromToken(db, pCons); + zArg = sqlite3MPrintf(db, "%Q", z); + sqlite3DbFree(db, z); + }else{ + int iCol; + if( alterFindCol(pParse, pTab, pCol, &iCol) ) return; + zArg = sqlite3MPrintf(db, "%d", iCol); + } + + /* Edit the SQL for the named table. */ + sqlite3NestedParse(pParse, + "UPDATE \"%w\"." LEGACY_SCHEMA_TABLE " SET " + "sql = sqlite_drop_constraint(sql, %s) " + "WHERE type='table' AND tbl_name=%Q COLLATE nocase" + , zDb, zArg, pTab->zName + ); + sqlite3DbFree(db, zArg); + + /* Finally, reload the database schema. */ + renameReloadSchema(pParse, iDb, INITFLAG_AlterDropCons); +} + +/* +** The implementation of SQL function sqlite_fail(MSG). This takes a single +** argument, and returns it as an error message with the error code set to +** SQLITE_CONSTRAINT. +*/ +static void failConstraintFunc( + sqlite3_context *ctx, + int NotUsed, + sqlite3_value **argv +){ + const char *zText = (const char*)sqlite3_value_text(argv[0]); + int err = sqlite3_value_int(argv[1]); + (void)NotUsed; + sqlite3_result_error(ctx, zText, -1); + sqlite3_result_error_code(ctx, err); +} + +/* +** Buffer pCons, which is nCons bytes in size, contains the text of a +** NOT NULL or CHECK constraint that will be inserted into a CREATE TABLE +** statement. If successful, this function returns the size of the buffer in +** bytes not including any trailing whitespace or "--" style comments. Or, +** if an OOM occurs, it returns 0 and sets db->mallocFailed to true. +** +** C-style comments at the end are preserved. "--" style comments are +** removed because the comment terminator might be \000, and we are about +** to insert the pCons[] text into the middle of a larger string, and that +** will have the effect of removing the comment terminator and messing up +** the syntax. +*/ +static int alterRtrimConstraint( + sqlite3 *db, /* used to record OOM error */ + const char *pCons, /* Buffer containing constraint */ + int nCons /* Size of pCons in bytes */ +){ + u8 *zTmp = (u8*)sqlite3MPrintf(db, "%.*s", nCons, pCons); + int iOff = 0; + int iEnd = 0; + + if( zTmp==0 ) return 0; + + while( 1 ){ + int t = 0; + int nToken = sqlite3GetToken(&zTmp[iOff], &t); + if( t==TK_ILLEGAL ) break; + if( t!=TK_SPACE && (t!=TK_COMMENT || zTmp[iOff]!='-') ){ + iEnd = iOff+nToken; + } + iOff += nToken; + } + + sqlite3DbFree(db, zTmp); + return iEnd; +} + +/* +** Prepare a statement of the form: +** +** ALTER TABLE pSrc ALTER pCol SET NOT NULL +*/ +SQLITE_PRIVATE void sqlite3AlterSetNotNull( + Parse *pParse, /* Parsing context */ + SrcList *pSrc, /* Name of the table being altered */ + Token *pCol, /* Name of the column to add a NOT NULL constraint to */ + Token *pFirst /* The NOT token of the NOT NULL constraint text */ +){ + Table *pTab = 0; + int iCol = 0; + int iDb = 0; + const char *zDb = 0; + const char *pCons = 0; + int nCons = 0; + + /* Look up the table being altered. */ + assert( pSrc->nSrc==1 ); + pTab = alterFindTable(pParse, pSrc, &iDb, &zDb, 0); + if( !pTab ) return; + + /* Find the column being altered. */ + if( alterFindCol(pParse, pTab, pCol, &iCol) ){ + return; + } + + /* Find the length in bytes of the constraint definition */ + pCons = pFirst->z; + nCons = alterRtrimConstraint(pParse->db, pCons, pParse->sLastToken.z - pCons); + + /* Search for a constraint violation. Throw an exception if one is found. */ + sqlite3NestedParse(pParse, + "SELECT sqlite_fail('constraint failed', %d) " + "FROM %Q.%Q AS x WHERE x.%.*s IS NULL", + SQLITE_CONSTRAINT, zDb, pTab->zName, (int)pCol->n, pCol->z + ); + + /* Edit the SQL for the named table. */ + sqlite3NestedParse(pParse, + "UPDATE \"%w\"." LEGACY_SCHEMA_TABLE " SET " + "sql = sqlite_add_constraint(sqlite_drop_constraint(sql, %d), %.*Q, %d) " + "WHERE type='table' AND tbl_name=%Q COLLATE nocase" + , zDb, iCol, nCons, pCons, iCol, pTab->zName + ); + + /* Finally, reload the database schema. */ + renameReloadSchema(pParse, iDb, INITFLAG_AlterDropCons); +} + +/* +** Implementation of internal SQL function: +** +** sqlite_find_constraint(SQL, CONSTRAINT-NAME) +** +** This function returns true if the SQL passed as the first argument is a +** CREATE TABLE that contains a constraint with the name CONSTRAINT-NAME, +** or false otherwise. +*/ +static void findConstraintFunc( + sqlite3_context *ctx, + int NotUsed, + sqlite3_value **argv +){ + const u8 *zSql = 0; + const u8 *zCons = 0; + int iOff = 0; + int t = 0; + + (void)NotUsed; + zSql = sqlite3_value_text(argv[0]); + zCons = sqlite3_value_text(argv[1]); + + if( zSql==0 || zCons==0 ) return; + while( t!=TK_LP && t!=TK_ILLEGAL ){ + iOff += sqlite3GetToken(&zSql[iOff], &t); + } + + while( 1 ){ + iOff += getConstraintToken(&zSql[iOff], &t); + if( t==TK_CONSTRAINT ){ + int nTok = 0; + int cmp = 0; + iOff += getWhitespace(&zSql[iOff]); + nTok = getConstraintToken(&zSql[iOff], &t); + if( quotedCompare(ctx, t, &zSql[iOff], nTok, zCons, &cmp) ) return; + if( cmp==0 ){ + sqlite3_result_int(ctx, 1); + return; + } + }else if( t==TK_ILLEGAL ){ + break; + } + } + + sqlite3_result_int(ctx, 0); +} + +/* +** Generate bytecode to implement: +** +** ALTER TABLE pSrc ADD [CONSTRAINT pName] CHECK(pExpr) +** +** Any "ON CONFLICT" text that occurs after the "CHECK(...)", up +** until pParse->sLastToken, is included as part of the new constraint. +*/ +SQLITE_PRIVATE void sqlite3AlterAddConstraint( + Parse *pParse, /* Parse context */ + SrcList *pSrc, /* Table to add constraint to */ + Token *pFirst, /* First token of new constraint */ + Token *pName, /* Name of new constraint. NULL if name omitted. */ + const char *zExpr, /* Text of CHECK expression */ + int nExpr, /* Size of pExpr in bytes */ + Expr *pExpr /* The parsed CHECK expression */ +){ + Table *pTab = 0; /* Table identified by pSrc */ + int iDb = 0; /* Which schema does pTab live in */ + const char *zDb = 0; /* Name of the schema in which pTab lives */ + const char *pCons = 0; /* Text of the constraint */ + int nCons; /* Bytes of text to use from pCons[] */ + int rc; /* Result from error checking pExpr */ + + /* Look up the table being altered. */ + assert( pSrc->nSrc==1 ); + pTab = alterFindTable(pParse, pSrc, &iDb, &zDb, 1); + if( !pTab ){ + sqlite3ExprDelete(pParse->db, pExpr); + return; + } + + /* Verify that the new CHECK constraint does not contain any + ** internal-use-only function. Forum post 2026-05-10T01:11:28Z + */ + rc = sqlite3ResolveSelfReference(pParse, pTab, NC_IsCheck, pExpr, 0); + sqlite3ExprDelete(pParse->db, pExpr); + if( rc ) return; + + /* If this new constraint has a name, check that it is not a duplicate of + ** an existing constraint. It is an error if it is. */ + if( pName ){ + char *zName = sqlite3NameFromToken(pParse->db, pName); + + sqlite3NestedParse(pParse, + "SELECT sqlite_fail('constraint %q already exists', %d) " + "FROM \"%w\"." LEGACY_SCHEMA_TABLE " " + "WHERE type='table' AND tbl_name=%Q COLLATE nocase " + "AND sqlite_find_constraint(sql, %Q)", + zName, SQLITE_ERROR, zDb, pTab->zName, zName + ); + sqlite3DbFree(pParse->db, zName); + } + + /* Search for a constraint violation. Throw an exception if one is found. */ + sqlite3NestedParse(pParse, + "SELECT sqlite_fail('constraint failed', %d) " + "FROM %Q.%Q WHERE (%.*s) IS NOT TRUE", + SQLITE_CONSTRAINT, zDb, pTab->zName, nExpr, zExpr + ); + + /* Edit the SQL for the named table. */ + pCons = pFirst->z; + nCons = alterRtrimConstraint(pParse->db, pCons, pParse->sLastToken.z - pCons); + + sqlite3NestedParse(pParse, + "UPDATE \"%w\"." LEGACY_SCHEMA_TABLE " SET " + "sql = sqlite_add_constraint(sql, %.*Q, -1) " + "WHERE type='table' AND tbl_name=%Q COLLATE nocase" + , zDb, nCons, pCons, pTab->zName + ); + + /* Finally, reload the database schema. */ + renameReloadSchema(pParse, iDb, INITFLAG_AlterDropCons); +} + /* ** Register built-in functions used to help implement ALTER TABLE */ SQLITE_PRIVATE void sqlite3AlterFunctions(void){ static FuncDef aAlterTableFuncs[] = { - INTERNAL_FUNCTION(sqlite_rename_column, 9, renameColumnFunc), - INTERNAL_FUNCTION(sqlite_rename_table, 7, renameTableFunc), - INTERNAL_FUNCTION(sqlite_rename_test, 5, renameTableTest), + INTERNAL_FUNCTION(sqlite_rename_column, 9, renameColumnFunc), + INTERNAL_FUNCTION(sqlite_rename_table, 7, renameTableFunc), + INTERNAL_FUNCTION(sqlite_rename_test, 7, renameTableTest), + INTERNAL_FUNCTION(sqlite_drop_column, 3, dropColumnFunc), + INTERNAL_FUNCTION(sqlite_rename_quotefix,2, renameQuotefixFunc), + INTERNAL_FUNCTION(sqlite_drop_constraint,2, dropConstraintFunc), + INTERNAL_FUNCTION(sqlite_fail, 2, failConstraintFunc), + INTERNAL_FUNCTION(sqlite_add_constraint, 3, addConstraintFunc), + INTERNAL_FUNCTION(sqlite_find_constraint,2, findConstraintFunc), }; sqlite3InsertBuiltinFuncs(aAlterTableFuncs, ArraySize(aAlterTableFuncs)); } @@ -104019,13 +123657,13 @@ SQLITE_PRIVATE void sqlite3AlterFunctions(void){ ** is between 3.6.18 and 3.7.8, inclusive, and unless SQLite is compiled ** with SQLITE_ENABLE_STAT2. The sqlite_stat2 table is deprecated. ** The sqlite_stat2 table is superseded by sqlite_stat3, which is only -** created and used by SQLite versions 3.7.9 and later and with +** created and used by SQLite versions 3.7.9 through 3.29.0 when ** SQLITE_ENABLE_STAT3 defined. The functionality of sqlite_stat3 -** is a superset of sqlite_stat2. The sqlite_stat4 is an enhanced -** version of sqlite_stat3 and is only available when compiled with -** SQLITE_ENABLE_STAT4 and in SQLite versions 3.8.1 and later. It is -** not possible to enable both STAT3 and STAT4 at the same time. If they -** are both enabled, then STAT4 takes precedence. +** is a superset of sqlite_stat2 and is also now deprecated. The +** sqlite_stat4 is an enhanced version of sqlite_stat3 and is only +** available when compiled with SQLITE_ENABLE_STAT4 and in SQLite +** versions 3.8.1 and later. STAT4 is the only variant that is still +** supported. ** ** For most applications, sqlite_stat1 provides all the statistics required ** for the query planner to make good choices. @@ -104041,7 +123679,7 @@ SQLITE_PRIVATE void sqlite3AlterFunctions(void){ ** integer is the average number of rows in the index that have the same ** value in the first column of the index. The third integer is the average ** number of rows in the index that have the same value for the first two -** columns. The N-th integer (for N>1) is the average number of rows in +** columns. The N-th integer (for N>1) is the average number of rows in ** the index which have the same value for the first N-1 columns. For ** a K-column index, there will be K+1 integers in the stat column. If ** the index is unique, then the last integer will be 1. @@ -104051,7 +123689,7 @@ SQLITE_PRIVATE void sqlite3AlterFunctions(void){ ** must be separated from the last integer by a single space. If the ** "unordered" keyword is present, then the query planner assumes that ** the index is unordered and will not use the index for a range query. -** +** ** If the sqlite_stat1.idx column is NULL, then the sqlite_stat1.stat ** column contains a single integer which is the (estimated) number of ** rows in the table identified by sqlite_stat1.tbl. @@ -104109,9 +123747,9 @@ SQLITE_PRIVATE void sqlite3AlterFunctions(void){ ** number of entries that are strictly less than the sample. The first ** integer in nLt contains the number of entries in the index where the ** left-most column is less than the left-most column of the sample. -** The K-th integer in the nLt entry is the number of index entries +** The K-th integer in the nLt entry is the number of index entries ** where the first K columns are less than the first K columns of the -** sample. The nDLt column is like nLt except that it contains the +** sample. The nDLt column is like nLt except that it contains the ** number of distinct entries in the index that are less than the ** sample. ** @@ -104136,17 +123774,11 @@ SQLITE_PRIVATE void sqlite3AlterFunctions(void){ #if defined(SQLITE_ENABLE_STAT4) # define IsStat4 1 -# define IsStat3 0 -#elif defined(SQLITE_ENABLE_STAT3) -# define IsStat4 0 -# define IsStat3 1 #else # define IsStat4 0 -# define IsStat3 0 # undef SQLITE_STAT4_SAMPLES # define SQLITE_STAT4_SAMPLES 1 #endif -#define IsStat34 (IsStat3+IsStat4) /* 1 for STAT3 or STAT4. 0 otherwise */ /* ** This routine generates code that opens the sqlite_statN tables. @@ -104175,21 +123807,22 @@ static void openStatTable( { "sqlite_stat1", "tbl,idx,stat" }, #if defined(SQLITE_ENABLE_STAT4) { "sqlite_stat4", "tbl,idx,neq,nlt,ndlt,sample" }, - { "sqlite_stat3", 0 }, -#elif defined(SQLITE_ENABLE_STAT3) - { "sqlite_stat3", "tbl,idx,neq,nlt,ndlt,sample" }, - { "sqlite_stat4", 0 }, #else - { "sqlite_stat3", 0 }, { "sqlite_stat4", 0 }, #endif + { "sqlite_stat3", 0 }, }; int i; sqlite3 *db = pParse->db; Db *pDb; Vdbe *v = sqlite3GetVdbe(pParse); - int aRoot[ArraySize(aTable)]; + u32 aRoot[ArraySize(aTable)]; u8 aCreateTbl[ArraySize(aTable)]; +#ifdef SQLITE_ENABLE_STAT4 + const int nToOpen = OptimizationEnabled(db,SQLITE_Stat4) ? 2 : 1; +#else + const int nToOpen = 1; +#endif if( v==0 ) return; assert( sqlite3BtreeHoldsAllMutexes(db) ); @@ -104202,24 +123835,25 @@ static void openStatTable( for(i=0; izDbSName))==0 ){ - if( aTable[i].zCols ){ - /* The sqlite_statN table does not exist. Create it. Note that a - ** side-effect of the CREATE TABLE statement is to leave the rootpage - ** of the new table in register pParse->regRoot. This is important + if( iregRoot. This is important ** because the OpenWrite opcode below will be needing it. */ sqlite3NestedParse(pParse, "CREATE TABLE %Q.%s(%s)", pDb->zDbSName, zTab, aTable[i].zCols ); - aRoot[i] = pParse->regRoot; + assert( pParse->isCreate || pParse->nErr ); + aRoot[i] = (u32)pParse->u1.cr.regRoot; aCreateTbl[i] = OPFLAG_P2ISREG; } }else{ - /* The table already exists. If zWhere is not NULL, delete all entries + /* The table already exists. If zWhere is not NULL, delete all entries ** associated with the table zWhere. If zWhere is NULL, delete the ** entire contents of the table. */ aRoot[i] = pStat->tnum; - aCreateTbl[i] = 0; sqlite3TableLock(pParse, iDb, aRoot[i], 1, zTab); if( zWhere ){ sqlite3NestedParse(pParse, @@ -104232,15 +123866,15 @@ static void openStatTable( #endif }else{ /* The sqlite_stat[134] table already exists. Delete all rows. */ - sqlite3VdbeAddOp2(v, OP_Clear, aRoot[i], iDb); + sqlite3VdbeAddOp2(v, OP_Clear, (int)aRoot[i], iDb); } } } /* Open the sqlite_stat[134] tables for writing. */ - for(i=0; aTable[i].zCols; i++){ + for(i=0; inRowid ){ sqlite3DbFree(db, p->u.aRowid); @@ -104306,8 +123945,8 @@ static void sampleClear(sqlite3 *db, Stat4Sample *p){ /* Initialize the BLOB value of a ROWID */ -#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 -static void sampleSetRowid(sqlite3 *db, Stat4Sample *p, int n, const u8 *pData){ +#ifdef SQLITE_ENABLE_STAT4 +static void sampleSetRowid(sqlite3 *db, StatSample *p, int n, const u8 *pData){ assert( db!=0 ); if( p->nRowid ) sqlite3DbFree(db, p->u.aRowid); p->u.aRowid = sqlite3DbMallocRawNN(db, n); @@ -104322,8 +123961,8 @@ static void sampleSetRowid(sqlite3 *db, Stat4Sample *p, int n, const u8 *pData){ /* Initialize the INTEGER value of a ROWID. */ -#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 -static void sampleSetRowidInt64(sqlite3 *db, Stat4Sample *p, i64 iRowid){ +#ifdef SQLITE_ENABLE_STAT4 +static void sampleSetRowidInt64(sqlite3 *db, StatSample *p, i64 iRowid){ assert( db!=0 ); if( p->nRowid ) sqlite3DbFree(db, p->u.aRowid); p->nRowid = 0; @@ -104335,8 +123974,8 @@ static void sampleSetRowidInt64(sqlite3 *db, Stat4Sample *p, i64 iRowid){ /* ** Copy the contents of object (*pFrom) into (*pTo). */ -#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 -static void sampleCopy(Stat4Accum *p, Stat4Sample *pTo, Stat4Sample *pFrom){ +#ifdef SQLITE_ENABLE_STAT4 +static void sampleCopy(StatAccum *p, StatSample *pTo, StatSample *pFrom){ pTo->isPSample = pFrom->isPSample; pTo->iCol = pFrom->iCol; pTo->iHash = pFrom->iHash; @@ -104352,40 +123991,41 @@ static void sampleCopy(Stat4Accum *p, Stat4Sample *pTo, Stat4Sample *pFrom){ #endif /* -** Reclaim all memory of a Stat4Accum structure. +** Reclaim all memory of a StatAccum structure. */ -static void stat4Destructor(void *pOld){ - Stat4Accum *p = (Stat4Accum*)pOld; -#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 - int i; - for(i=0; inCol; i++) sampleClear(p->db, p->aBest+i); - for(i=0; imxSample; i++) sampleClear(p->db, p->a+i); - sampleClear(p->db, &p->current); +static void statAccumDestructor(void *pOld){ + StatAccum *p = (StatAccum*)pOld; +#ifdef SQLITE_ENABLE_STAT4 + if( p->mxSample ){ + int i; + for(i=0; inCol; i++) sampleClear(p->db, p->aBest+i); + for(i=0; imxSample; i++) sampleClear(p->db, p->a+i); + sampleClear(p->db, &p->current); + } #endif sqlite3DbFree(p->db, p); } /* -** Implementation of the stat_init(N,K,C) SQL function. The three parameters +** Implementation of the stat_init(N,K,C,L) SQL function. The four parameters ** are: ** N: The number of columns in the index including the rowid/pk (note 1) ** K: The number of columns in the index excluding the rowid/pk. -** C: The number of rows in the index (note 2) +** C: Estimated number of rows in the index +** L: A limit on the number of rows to scan, or 0 for no-limit ** ** Note 1: In the special case of the covering index that implements a ** WITHOUT ROWID table, N is the number of PRIMARY KEY columns, not the ** total number of columns in the table. ** -** Note 2: C is only used for STAT3 and STAT4. -** ** For indexes on ordinary rowid tables, N==K+1. But for indexes on ** WITHOUT ROWID tables, N=K+P where P is the number of columns in the ** PRIMARY KEY of the table. The covering index that implements the ** original WITHOUT ROWID table as N==K as a special case. ** -** This routine allocates the Stat4Accum object in heap memory. The return -** value is a pointer to the Stat4Accum object. The datatype of the -** return value is BLOB, but it is really just a pointer to the Stat4Accum +** This routine allocates the StatAccum object in heap memory. The return +** value is a pointer to the StatAccum object. The datatype of the +** return value is BLOB, but it is really just a pointer to the StatAccum ** object. */ static void statInit( @@ -104393,14 +124033,15 @@ static void statInit( int argc, sqlite3_value **argv ){ - Stat4Accum *p; + StatAccum *p; int nCol; /* Number of columns in index being sampled */ int nKeyCol; /* Number of key columns */ int nColUp; /* nCol rounded up for alignment */ - int n; /* Bytes of space to allocate */ - sqlite3 *db; /* Database connection */ -#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 - int mxSample = SQLITE_STAT4_SAMPLES; + i64 n; /* Bytes of space to allocate */ + sqlite3 *db = sqlite3_context_db_handle(context); /* Database connection */ +#ifdef SQLITE_ENABLE_STAT4 + /* Maximum number of samples. 0 if STAT4 data is not collected */ + int mxSample = OptimizationEnabled(db,SQLITE_Stat4) ?SQLITE_STAT4_SAMPLES :0; #endif /* Decode the three function arguments */ @@ -104412,17 +124053,17 @@ static void statInit( assert( nKeyCol<=nCol ); assert( nKeyCol>0 ); - /* Allocate the space required for the Stat4Accum object */ - n = sizeof(*p) - + sizeof(tRowcnt)*nColUp /* Stat4Accum.anEq */ - + sizeof(tRowcnt)*nColUp /* Stat4Accum.anDLt */ -#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 - + sizeof(tRowcnt)*nColUp /* Stat4Accum.anLt */ - + sizeof(Stat4Sample)*(nCol+mxSample) /* Stat4Accum.aBest[], a[] */ - + sizeof(tRowcnt)*3*nColUp*(nCol+mxSample) + /* Allocate the space required for the StatAccum object */ + n = sizeof(*p) + + sizeof(tRowcnt)*nColUp; /* StatAccum.anDLt */ +#ifdef SQLITE_ENABLE_STAT4 + n += sizeof(tRowcnt)*nColUp; /* StatAccum.anEq */ + if( mxSample ){ + n += sizeof(tRowcnt)*nColUp /* StatAccum.anLt */ + + sizeof(StatSample)*(nCol+mxSample) /* StatAccum.aBest[], a[] */ + + sizeof(tRowcnt)*3*nColUp*(nCol+mxSample); + } #endif - ; - db = sqlite3_context_db_handle(context); p = sqlite3DbMallocZero(db, n); if( p==0 ){ sqlite3_result_error_nomem(context); @@ -104430,25 +124071,28 @@ static void statInit( } p->db = db; + p->nEst = sqlite3_value_int64(argv[2]); p->nRow = 0; + p->nLimit = sqlite3_value_int(argv[3]); p->nCol = nCol; p->nKeyCol = nKeyCol; + p->nSkipAhead = 0; p->current.anDLt = (tRowcnt*)&p[1]; - p->current.anEq = &p->current.anDLt[nColUp]; -#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 - { +#ifdef SQLITE_ENABLE_STAT4 + p->current.anEq = &p->current.anDLt[nColUp]; + p->mxSample = p->nLimit==0 ? mxSample : 0; + if( mxSample ){ u8 *pSpace; /* Allocated space not yet assigned */ int i; /* Used to iterate through p->aSample[] */ p->iGet = -1; - p->mxSample = mxSample; - p->nPSample = (tRowcnt)(sqlite3_value_int64(argv[2])/(mxSample/3+1) + 1); + p->nPSample = (tRowcnt)(p->nEst/(mxSample/3+1) + 1); p->current.anLt = &p->current.anEq[nColUp]; p->iPrn = 0x689e962d*(u32)nCol ^ 0xd0944565*(u32)sqlite3_value_int(argv[2]); - - /* Set up the Stat4Accum.a[] and aBest[] arrays */ - p->a = (struct Stat4Sample*)&p->current.anLt[nColUp]; + + /* Set up the StatAccum.a[] and aBest[] arrays */ + p->a = (struct StatSample*)&p->current.anLt[nColUp]; p->aBest = &p->a[mxSample]; pSpace = (u8*)(&p->a[mxSample+nCol]); for(i=0; i<(mxSample+nCol); i++){ @@ -104457,7 +124101,7 @@ static void statInit( p->a[i].anDLt = (tRowcnt *)pSpace; pSpace += (sizeof(tRowcnt) * nColUp); } assert( (pSpace - (u8*)p)==n ); - + for(i=0; iaBest[i].iCol = i; } @@ -104468,10 +124112,10 @@ static void statInit( ** only the pointer (the 2nd parameter) matters. The size of the object ** (given by the 3rd parameter) is never used and can be any positive ** value. */ - sqlite3_result_blob(context, p, sizeof(*p), stat4Destructor); + sqlite3_result_blob(context, p, sizeof(*p), statAccumDestructor); } static const FuncDef statInitFuncdef = { - 2+IsStat34, /* nArg */ + 4, /* nArg */ SQLITE_UTF8, /* funcFlags */ 0, /* pUserData */ 0, /* pNext */ @@ -104484,20 +124128,20 @@ static const FuncDef statInitFuncdef = { #ifdef SQLITE_ENABLE_STAT4 /* -** pNew and pOld are both candidate non-periodic samples selected for -** the same column (pNew->iCol==pOld->iCol). Ignoring this column and +** pNew and pOld are both candidate non-periodic samples selected for +** the same column (pNew->iCol==pOld->iCol). Ignoring this column and ** considering only any trailing columns and the sample hash value, this ** function returns true if sample pNew is to be preferred over pOld. ** In other words, if we assume that the cardinalities of the selected ** column for pNew and pOld are equal, is pNew to be preferred over pOld. ** ** This function assumes that for each argument sample, the contents of -** the anEq[] array from pSample->anEq[pSample->iCol+1] onwards are valid. +** the anEq[] array from pSample->anEq[pSample->iCol+1] onwards are valid. */ static int sampleIsBetterPost( - Stat4Accum *pAccum, - Stat4Sample *pNew, - Stat4Sample *pOld + StatAccum *pAccum, + StatSample *pNew, + StatSample *pOld ){ int nCol = pAccum->nCol; int i; @@ -104511,17 +124155,17 @@ static int sampleIsBetterPost( } #endif -#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 +#ifdef SQLITE_ENABLE_STAT4 /* ** Return true if pNew is to be preferred over pOld. ** ** This function assumes that for each argument sample, the contents of -** the anEq[] array from pSample->anEq[pSample->iCol] onwards are valid. +** the anEq[] array from pSample->anEq[pSample->iCol] onwards are valid. */ static int sampleIsBetter( - Stat4Accum *pAccum, - Stat4Sample *pNew, - Stat4Sample *pOld + StatAccum *pAccum, + StatSample *pNew, + StatSample *pOld ){ tRowcnt nEqNew = pNew->anEq[pNew->iCol]; tRowcnt nEqOld = pOld->anEq[pOld->iCol]; @@ -104530,46 +124174,41 @@ static int sampleIsBetter( assert( IsStat4 || (pNew->iCol==0 && pOld->iCol==0) ); if( (nEqNew>nEqOld) ) return 1; -#ifdef SQLITE_ENABLE_STAT4 if( nEqNew==nEqOld ){ if( pNew->iColiCol ) return 1; return (pNew->iCol==pOld->iCol && sampleIsBetterPost(pAccum, pNew, pOld)); } return 0; -#else - return (nEqNew==nEqOld && pNew->iHash>pOld->iHash); -#endif } /* ** Copy the contents of sample *pNew into the p->a[] array. If necessary, ** remove the least desirable sample from p->a[] to make room. */ -static void sampleInsert(Stat4Accum *p, Stat4Sample *pNew, int nEqZero){ - Stat4Sample *pSample = 0; +static void sampleInsert(StatAccum *p, StatSample *pNew, int nEqZero){ + StatSample *pSample = 0; int i; assert( IsStat4 || nEqZero==0 ); -#ifdef SQLITE_ENABLE_STAT4 - /* Stat4Accum.nMaxEqZero is set to the maximum number of leading 0 - ** values in the anEq[] array of any sample in Stat4Accum.a[]. In + /* StatAccum.nMaxEqZero is set to the maximum number of leading 0 + ** values in the anEq[] array of any sample in StatAccum.a[]. In ** other words, if nMaxEqZero is n, then it is guaranteed that there - ** are no samples with Stat4Sample.anEq[m]==0 for (m>=n). */ + ** are no samples with StatSample.anEq[m]==0 for (m>=n). */ if( nEqZero>p->nMaxEqZero ){ p->nMaxEqZero = nEqZero; } if( pNew->isPSample==0 ){ - Stat4Sample *pUpgrade = 0; + StatSample *pUpgrade = 0; assert( pNew->anEq[pNew->iCol]>0 ); - /* This sample is being added because the prefix that ends in column + /* This sample is being added because the prefix that ends in column ** iCol occurs many times in the table. However, if we have already ** added a sample that shares this prefix, there is no need to add ** this one. Instead, upgrade the priority of the highest priority ** existing sample that shares this prefix. */ for(i=p->nSample-1; i>=0; i--){ - Stat4Sample *pOld = &p->a[i]; + StatSample *pOld = &p->a[i]; if( pOld->anEq[pNew->iCol]==0 ){ if( pOld->isPSample ) return; assert( pOld->iCol>pNew->iCol ); @@ -104585,11 +124224,10 @@ static void sampleInsert(Stat4Accum *p, Stat4Sample *pNew, int nEqZero){ goto find_new_min; } } -#endif /* If necessary, remove sample iMin to make room for the new sample. */ if( p->nSample>=p->mxSample ){ - Stat4Sample *pMin = &p->a[p->iMin]; + StatSample *pMin = &p->a[p->iMin]; tRowcnt *anEq = pMin->anEq; tRowcnt *anLt = pMin->anLt; tRowcnt *anDLt = pMin->anDLt; @@ -104606,10 +124244,8 @@ static void sampleInsert(Stat4Accum *p, Stat4Sample *pNew, int nEqZero){ /* The "rows less-than" for the rowid column must be greater than that ** for the last sample in the p->a[] array. Otherwise, the samples would ** be out of order. */ -#ifdef SQLITE_ENABLE_STAT4 - assert( p->nSample==0 + assert( p->nSample==0 || pNew->anLt[p->nCol-1] > p->a[p->nSample-1].anLt[p->nCol-1] ); -#endif /* Insert the new sample */ pSample = &p->a[p->nSample]; @@ -104619,9 +124255,7 @@ static void sampleInsert(Stat4Accum *p, Stat4Sample *pNew, int nEqZero){ /* Zero the first nEqZero entries in the anEq[] array. */ memset(pSample->anEq, 0, sizeof(tRowcnt)*nEqZero); -#ifdef SQLITE_ENABLE_STAT4 - find_new_min: -#endif +find_new_min: if( p->nSample>=p->mxSample ){ int iMin = -1; for(i=0; imxSample; i++){ @@ -104634,22 +124268,22 @@ static void sampleInsert(Stat4Accum *p, Stat4Sample *pNew, int nEqZero){ p->iMin = iMin; } } -#endif /* SQLITE_ENABLE_STAT3_OR_STAT4 */ +#endif /* SQLITE_ENABLE_STAT4 */ +#ifdef SQLITE_ENABLE_STAT4 /* ** Field iChng of the index being scanned has changed. So at this point ** p->current contains a sample that reflects the previous row of the ** index. The value of anEq[iChng] and subsequent anEq[] elements are ** correct at this point. */ -static void samplePushPrevious(Stat4Accum *p, int iChng){ -#ifdef SQLITE_ENABLE_STAT4 +static void samplePushPrevious(StatAccum *p, int iChng){ int i; /* Check if any samples from the aBest[] array should be pushed ** into IndexSample.a[] at this point. */ for(i=(p->nCol-2); i>=iChng; i--){ - Stat4Sample *pBest = &p->aBest[i]; + StatSample *pBest = &p->aBest[i]; pBest->anEq[i] = p->current.anEq[i]; if( p->nSamplemxSample || sampleIsBetter(p, pBest, &p->a[p->iMin]) ){ sampleInsert(p, pBest, i); @@ -104673,50 +124307,27 @@ static void samplePushPrevious(Stat4Accum *p, int iChng){ } p->nMaxEqZero = iChng; } -#endif - -#if defined(SQLITE_ENABLE_STAT3) && !defined(SQLITE_ENABLE_STAT4) - if( iChng==0 ){ - tRowcnt nLt = p->current.anLt[0]; - tRowcnt nEq = p->current.anEq[0]; - - /* Check if this is to be a periodic sample. If so, add it. */ - if( (nLt/p->nPSample)!=(nLt+nEq)/p->nPSample ){ - p->current.isPSample = 1; - sampleInsert(p, &p->current, 0); - p->current.isPSample = 0; - }else - - /* Or if it is a non-periodic sample. Add it in this case too. */ - if( p->nSamplemxSample - || sampleIsBetter(p, &p->current, &p->a[p->iMin]) - ){ - sampleInsert(p, &p->current, 0); - } - } -#endif - -#ifndef SQLITE_ENABLE_STAT3_OR_STAT4 - UNUSED_PARAMETER( p ); - UNUSED_PARAMETER( iChng ); -#endif } +#endif /* SQLITE_ENABLE_STAT4 */ /* ** Implementation of the stat_push SQL function: stat_push(P,C,R) ** Arguments: ** -** P Pointer to the Stat4Accum object created by stat_init() +** P Pointer to the StatAccum object created by stat_init() ** C Index of left-most column to differ from previous row ** R Rowid for the current row. Might be a key record for ** WITHOUT ROWID tables. ** -** This SQL function always returns NULL. It's purpose it to accumulate -** statistical data and/or samples in the Stat4Accum object about the -** index being analyzed. The stat_get() SQL function will later be used to -** extract relevant information for constructing the sqlite_statN tables. +** The purpose of this routine is to collect statistical data and/or +** samples from the index being analyzed into the StatAccum object. +** The stat_get() SQL function will be used afterwards to +** retrieve the information gathered. ** -** The R parameter is only used for STAT3 and STAT4 +** This SQL function usually returns NULL, but might return an integer +** if it wants the byte-code to do special processing. +** +** The R parameter is only used for STAT4 */ static void statPush( sqlite3_context *context, @@ -104726,7 +124337,7 @@ static void statPush( int i; /* The three function arguments */ - Stat4Accum *p = (Stat4Accum*)sqlite3_value_blob(argv[0]); + StatAccum *p = (StatAccum*)sqlite3_value_blob(argv[0]); int iChng = sqlite3_value_int(argv[1]); UNUSED_PARAMETER( argc ); @@ -104736,39 +124347,44 @@ static void statPush( if( p->nRow==0 ){ /* This is the first call to this function. Do initialization. */ +#ifdef SQLITE_ENABLE_STAT4 for(i=0; inCol; i++) p->current.anEq[i] = 1; +#endif }else{ /* Second and subsequent calls get processed here */ - samplePushPrevious(p, iChng); +#ifdef SQLITE_ENABLE_STAT4 + if( p->mxSample ) samplePushPrevious(p, iChng); +#endif /* Update anDLt[], anLt[] and anEq[] to reflect the values that apply ** to the current row of the index. */ +#ifdef SQLITE_ENABLE_STAT4 for(i=0; icurrent.anEq[i]++; } +#endif for(i=iChng; inCol; i++){ p->current.anDLt[i]++; -#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 - p->current.anLt[i] += p->current.anEq[i]; -#endif +#ifdef SQLITE_ENABLE_STAT4 + if( p->mxSample ) p->current.anLt[i] += p->current.anEq[i]; p->current.anEq[i] = 1; +#endif } } - p->nRow++; -#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 - if( sqlite3_value_type(argv[2])==SQLITE_INTEGER ){ - sampleSetRowidInt64(p->db, &p->current, sqlite3_value_int64(argv[2])); - }else{ - sampleSetRowid(p->db, &p->current, sqlite3_value_bytes(argv[2]), - sqlite3_value_blob(argv[2])); - } - p->current.iHash = p->iPrn = p->iPrn*1103515245 + 12345; -#endif + p->nRow++; #ifdef SQLITE_ENABLE_STAT4 - { - tRowcnt nLt = p->current.anLt[p->nCol-1]; + if( p->mxSample ){ + tRowcnt nLt; + if( sqlite3_value_type(argv[2])==SQLITE_INTEGER ){ + sampleSetRowidInt64(p->db, &p->current, sqlite3_value_int64(argv[2])); + }else{ + sampleSetRowid(p->db, &p->current, sqlite3_value_bytes(argv[2]), + sqlite3_value_blob(argv[2])); + } + p->current.iHash = p->iPrn = p->iPrn*1103515245 + 12345; + nLt = p->current.anLt[p->nCol-1]; /* Check if this is to be a periodic sample. If so, add it. */ if( (nLt/p->nPSample)!=(nLt+1)/p->nPSample ){ p->current.isPSample = 1; @@ -104784,11 +124400,16 @@ static void statPush( sampleCopy(p, &p->aBest[i], &p->current); } } - } + }else #endif + if( p->nLimit && p->nRow>(tRowcnt)p->nLimit*(p->nSkipAhead+1) ){ + p->nSkipAhead++; + sqlite3_result_int(context, p->current.anDLt[0]>0); + } } + static const FuncDef statPushFuncdef = { - 2+IsStat34, /* nArg */ + 2+IsStat4, /* nArg */ SQLITE_UTF8, /* funcFlags */ 0, /* pUserData */ 0, /* pNext */ @@ -104808,18 +124429,18 @@ static const FuncDef statPushFuncdef = { /* ** Implementation of the stat_get(P,J) SQL function. This routine is ** used to query statistical information that has been gathered into -** the Stat4Accum object by prior calls to stat_push(). The P parameter -** has type BLOB but it is really just a pointer to the Stat4Accum object. +** the StatAccum object by prior calls to stat_push(). The P parameter +** has type BLOB but it is really just a pointer to the StatAccum object. ** The content to returned is determined by the parameter J ** which is one of the STAT_GET_xxxx values defined above. ** ** The stat_get(P,J) function is not available to generic SQL. It is ** inserted as part of a manually constructed bytecode program. (See ** the callStatGet() routine below.) It is guaranteed that the P -** parameter will always be a poiner to a Stat4Accum object, never a +** parameter will always be a pointer to a StatAccum object, never a ** NULL. ** -** If neither STAT3 nor STAT4 are enabled, then J is always +** If STAT4 is not enabled, then J is always ** STAT_GET_STAT1 and is hence omitted and this routine becomes ** a one-parameter function, stat_get(P), that always returns the ** stat1 table entry information. @@ -104829,15 +124450,16 @@ static void statGet( int argc, sqlite3_value **argv ){ - Stat4Accum *p = (Stat4Accum*)sqlite3_value_blob(argv[0]); -#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 - /* STAT3 and STAT4 have a parameter on this routine. */ + StatAccum *p = (StatAccum*)sqlite3_value_blob(argv[0]); +#ifdef SQLITE_ENABLE_STAT4 + /* STAT4 has a parameter on this routine. */ int eCall = sqlite3_value_int(argv[1]); assert( argc==2 ); - assert( eCall==STAT_GET_STAT1 || eCall==STAT_GET_NEQ + assert( eCall==STAT_GET_STAT1 || eCall==STAT_GET_NEQ || eCall==STAT_GET_ROWID || eCall==STAT_GET_NLT - || eCall==STAT_GET_NDLT + || eCall==STAT_GET_NDLT ); + assert( eCall==STAT_GET_STAT1 || p->mxSample ); if( eCall==STAT_GET_STAT1 ) #else assert( argc==1 ); @@ -104846,54 +124468,54 @@ static void statGet( /* Return the value to store in the "stat" column of the sqlite_stat1 ** table for this index. ** - ** The value is a string composed of a list of integers describing - ** the index. The first integer in the list is the total number of - ** entries in the index. There is one additional integer in the list + ** The value is a string composed of a list of integers describing + ** the index. The first integer in the list is the total number of + ** entries in the index. There is one additional integer in the list ** for each indexed column. This additional integer is an estimate of - ** the number of rows matched by a stabbing query on the index using + ** the number of rows matched by a equality query on the index using ** a key with the corresponding number of fields. In other words, - ** if the index is on columns (a,b) and the sqlite_stat1 value is + ** if the index is on columns (a,b) and the sqlite_stat1 value is ** "100 10 2", then SQLite estimates that: ** ** * the index contains 100 rows, ** * "WHERE a=?" matches 10 rows, and ** * "WHERE a=? AND b=?" matches 2 rows. ** - ** If D is the count of distinct values and K is the total number of - ** rows, then each estimate is computed as: + ** If D is the count of distinct values and K is the total number of + ** rows, then each estimate is usually computed as: ** ** I = (K+D-1)/D + ** + ** In other words, I is K/D rounded up to the next whole integer. + ** However, if I is between 1.0 and 1.1 (in other words if I is + ** close to 1.0 but just a little larger) then do not round up but + ** instead keep the I value at 1.0. */ - char *z; - int i; - - char *zRet = sqlite3MallocZero( (p->nKeyCol+1)*25 ); - if( zRet==0 ){ - sqlite3_result_error_nomem(context); - return; - } + sqlite3_str sStat; /* Text of the constructed "stat" line */ + int i; /* Loop counter */ - sqlite3_snprintf(24, zRet, "%llu", (u64)p->nRow); - z = zRet + sqlite3Strlen30(zRet); + sqlite3StrAccumInit(&sStat, 0, 0, 0, (p->nKeyCol+1)*100); + sqlite3_str_appendf(&sStat, "%llu", + p->nSkipAhead ? (u64)p->nEst : (u64)p->nRow); for(i=0; inKeyCol; i++){ u64 nDistinct = p->current.anDLt[i] + 1; u64 iVal = (p->nRow + nDistinct - 1) / nDistinct; - sqlite3_snprintf(24, z, " %llu", iVal); - z += sqlite3Strlen30(z); - assert( p->current.anEq[i] ); + if( iVal==2 && p->nRow*10 <= nDistinct*11 ) iVal = 1; + sqlite3_str_appendf(&sStat, " %llu", iVal); +#ifdef SQLITE_ENABLE_STAT4 + assert( p->current.anEq[i] || p->nRow==0 ); +#endif } - assert( z[0]=='\0' && z>zRet ); - - sqlite3_result_text(context, zRet, -1, sqlite3_free); + sqlite3ResultStrAccum(context, &sStat); } -#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 +#ifdef SQLITE_ENABLE_STAT4 else if( eCall==STAT_GET_ROWID ){ if( p->iGet<0 ){ samplePushPrevious(p, 0); p->iGet = 0; } if( p->iGetnSample ){ - Stat4Sample *pS = p->a + p->iGet; + StatSample *pS = p->a + p->iGet; if( pS->nRowid==0 ){ sqlite3_result_int64(context, pS->u.iRowid); }else{ @@ -104903,44 +124525,33 @@ static void statGet( } }else{ tRowcnt *aCnt = 0; + sqlite3_str sStat; + int i; assert( p->iGetnSample ); switch( eCall ){ case STAT_GET_NEQ: aCnt = p->a[p->iGet].anEq; break; case STAT_GET_NLT: aCnt = p->a[p->iGet].anLt; break; default: { - aCnt = p->a[p->iGet].anDLt; + aCnt = p->a[p->iGet].anDLt; p->iGet++; break; } } - - if( IsStat3 ){ - sqlite3_result_int64(context, (i64)aCnt[0]); - }else{ - char *zRet = sqlite3MallocZero(p->nCol * 25); - if( zRet==0 ){ - sqlite3_result_error_nomem(context); - }else{ - int i; - char *z = zRet; - for(i=0; inCol; i++){ - sqlite3_snprintf(24, z, "%llu ", (u64)aCnt[i]); - z += sqlite3Strlen30(z); - } - assert( z[0]=='\0' && z>zRet ); - z[-1] = '\0'; - sqlite3_result_text(context, zRet, -1, sqlite3_free); - } + sqlite3StrAccumInit(&sStat, 0, 0, 0, p->nCol*100); + for(i=0; inCol; i++){ + sqlite3_str_appendf(&sStat, "%llu ", (u64)aCnt[i]); } + if( sStat.nChar ) sStat.nChar--; + sqlite3ResultStrAccum(context, &sStat); } -#endif /* SQLITE_ENABLE_STAT3_OR_STAT4 */ +#endif /* SQLITE_ENABLE_STAT4 */ #ifndef SQLITE_DEBUG UNUSED_PARAMETER( argc ); #endif } static const FuncDef statGetFuncdef = { - 1+IsStat34, /* nArg */ + 1+IsStat4, /* nArg */ SQLITE_UTF8, /* funcFlags */ 0, /* pUserData */ 0, /* pNext */ @@ -104951,19 +124562,43 @@ static const FuncDef statGetFuncdef = { {0} }; -static void callStatGet(Vdbe *v, int regStat4, int iParam, int regOut){ - assert( regOut!=regStat4 && regOut!=regStat4+1 ); -#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 - sqlite3VdbeAddOp2(v, OP_Integer, iParam, regStat4+1); +static void callStatGet(Parse *pParse, int regStat, int iParam, int regOut){ +#ifdef SQLITE_ENABLE_STAT4 + sqlite3VdbeAddOp2(pParse->pVdbe, OP_Integer, iParam, regStat+1); #elif SQLITE_DEBUG assert( iParam==STAT_GET_STAT1 ); #else UNUSED_PARAMETER( iParam ); #endif - sqlite3VdbeAddOp4(v, OP_Function0, 0, regStat4, regOut, - (char*)&statGetFuncdef, P4_FUNCDEF); - sqlite3VdbeChangeP5(v, 1 + IsStat34); + assert( regOut!=regStat && regOut!=regStat+1 ); + sqlite3VdbeAddFunctionCall(pParse, 0, regStat, regOut, 1+IsStat4, + &statGetFuncdef, 0); +} + +#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS +/* Add a comment to the most recent VDBE opcode that is the name +** of the k-th column of the pIdx index. +*/ +static void analyzeVdbeCommentIndexWithColumnName( + Vdbe *v, /* Prepared statement under construction */ + Index *pIdx, /* Index whose column is being loaded */ + int k /* Which column index */ +){ + int i; /* Index of column in the table */ + assert( k>=0 && knColumn ); + i = pIdx->aiColumn[k]; + if( NEVER(i==XN_ROWID) ){ + VdbeComment((v,"%s.rowid",pIdx->zName)); + }else if( i==XN_EXPR ){ + assert( pIdx->bHasExpr ); + VdbeComment((v,"%s.expr(%d)",pIdx->zName, k)); + }else{ + VdbeComment((v,"%s.%s", pIdx->zName, pIdx->pTable->aCol[i].zCnName)); + } } +#else +# define analyzeVdbeCommentIndexWithColumnName(a,b,c) +#endif /* SQLITE_DEBUG */ /* ** Generate code to do an analysis of all indices associated with @@ -104987,26 +124622,29 @@ static void analyzeOneTable( int iDb; /* Index of database containing pTab */ u8 needTableCnt = 1; /* True to count the table */ int regNewRowid = iMem++; /* Rowid for the inserted record */ - int regStat4 = iMem++; /* Register to hold Stat4Accum object */ + int regStat = iMem++; /* Register to hold StatAccum object */ int regChng = iMem++; /* Index of changed index field */ -#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 int regRowid = iMem++; /* Rowid argument passed to stat_push() */ -#endif int regTemp = iMem++; /* Temporary use register */ + int regTemp2 = iMem++; /* Second temporary use register */ int regTabname = iMem++; /* Register containing table name */ int regIdxname = iMem++; /* Register containing index name */ int regStat1 = iMem++; /* Value for the stat column of sqlite_stat1 */ int regPrev = iMem; /* MUST BE LAST (see below) */ +#ifdef SQLITE_ENABLE_STAT4 + int doOnce = 1; /* Flag for a one-time computation */ +#endif #ifdef SQLITE_ENABLE_PREUPDATE_HOOK - Table *pStat1 = 0; + Table *pStat1 = 0; #endif - pParse->nMem = MAX(pParse->nMem, iMem); + sqlite3TouchRegister(pParse, iMem); + assert( sqlite3NoTempsInRange(pParse, regNewRowid, iMem) ); v = sqlite3GetVdbe(pParse); if( v==0 || NEVER(pTab==0) ){ return; } - if( pTab->tnum==0 ){ + if( !IsOrdinaryTable(pTab) ){ /* Do not gather statistics on views or virtual tables */ return; } @@ -105033,11 +124671,11 @@ static void analyzeOneTable( memcpy(pStat1->zName, "sqlite_stat1", 13); pStat1->nCol = 3; pStat1->iPKey = -1; - sqlite3VdbeAddOp4(pParse->pVdbe, OP_Noop, 0, 0, 0,(char*)pStat1,P4_DYNBLOB); + sqlite3VdbeAddOp4(pParse->pVdbe, OP_Noop, 0, 0, 0,(char*)pStat1,P4_DYNAMIC); } #endif - /* Establish a read-lock on the table at the shared-cache level. + /* Establish a read-lock on the table at the shared-cache level. ** Open a read-only cursor on the table. Also allocate a cursor number ** to use for scanning indexes (iIdxCur). No index cursor is opened at ** this time though. */ @@ -105050,7 +124688,7 @@ static void analyzeOneTable( for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ int nCol; /* Number of columns in pIdx. "N" */ - int addrRewind; /* Address of "OP_Rewind iIdxCur" */ + int addrGotoEnd; /* Address of "OP_Rewind iIdxCur" */ int addrNextRow; /* Address of "next_row:" */ const char *zIdxName; /* Name of the index */ int nColTest; /* Number of columns to test for changes */ @@ -105074,9 +124712,14 @@ static void analyzeOneTable( /* ** Pseudo-code for loop that calls stat_push(): ** - ** Rewind csr - ** if eof(csr) goto end_of_scan; ** regChng = 0 + ** Rewind csr + ** if eof(csr){ + ** stat_init() with count = 0; + ** goto end_of_scan; + ** } + ** count() + ** stat_init() ** goto chng_addr_0; ** ** next_row: @@ -105103,11 +124746,11 @@ static void analyzeOneTable( ** end_of_scan: */ - /* Make sure there are enough memory cells allocated to accommodate + /* Make sure there are enough memory cells allocated to accommodate ** the regPrev array and a trailing rowid (the rowid slot is required - ** when building a record to insert into the sample column of + ** when building a record to insert into the sample column of ** the sqlite_stat4 table. */ - pParse->nMem = MAX(pParse->nMem, regPrev+nColTest); + sqlite3TouchRegister(pParse, regPrev+nColTest); /* Open a read-only cursor on the index being analyzed. */ assert( iDb==sqlite3SchemaToIndex(db, pIdx->pSchema) ); @@ -105115,35 +124758,36 @@ static void analyzeOneTable( sqlite3VdbeSetP4KeyInfo(pParse, pIdx); VdbeComment((v, "%s", pIdx->zName)); - /* Invoke the stat_init() function. The arguments are: - ** - ** (1) the number of columns in the index including the rowid - ** (or for a WITHOUT ROWID table, the number of PK columns), - ** (2) the number of columns in the key without the rowid/pk - ** (3) the number of rows in the index, - ** - ** - ** The third argument is only used for STAT3 and STAT4 - */ -#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 - sqlite3VdbeAddOp2(v, OP_Count, iIdxCur, regStat4+3); -#endif - sqlite3VdbeAddOp2(v, OP_Integer, nCol, regStat4+1); - sqlite3VdbeAddOp2(v, OP_Integer, pIdx->nKeyCol, regStat4+2); - sqlite3VdbeAddOp4(v, OP_Function0, 0, regStat4+1, regStat4, - (char*)&statInitFuncdef, P4_FUNCDEF); - sqlite3VdbeChangeP5(v, 2+IsStat34); - /* Implementation of the following: ** - ** Rewind csr - ** if eof(csr) goto end_of_scan; ** regChng = 0 - ** goto next_push_0; - ** + ** Rewind csr + ** if eof(csr){ + ** stat_init() with count = 0; + ** goto end_of_scan; + ** } + ** count() + ** stat_init() + ** goto chng_addr_0; */ - addrRewind = sqlite3VdbeAddOp1(v, OP_Rewind, iIdxCur); + assert( regTemp2==regStat+4 ); + sqlite3VdbeAddOp2(v, OP_Integer, db->nAnalysisLimit, regTemp2); + + /* Arguments to stat_init(): + ** (1) the number of columns in the index including the rowid + ** (or for a WITHOUT ROWID table, the number of PK columns), + ** (2) the number of columns in the key without the rowid/pk + ** (3) estimated number of rows in the index. */ + sqlite3VdbeAddOp2(v, OP_Integer, nCol, regStat+1); + assert( regRowid==regStat+2 ); + sqlite3VdbeAddOp2(v, OP_Integer, pIdx->nKeyCol, regRowid); + sqlite3VdbeAddOp3(v, OP_Count, iIdxCur, regTemp, + OptimizationDisabled(db, SQLITE_Stat4)); + sqlite3VdbeAddFunctionCall(pParse, 0, regStat+1, regStat, 4, + &statInitFuncdef, 0); + addrGotoEnd = sqlite3VdbeAddOp1(v, OP_Rewind, iIdxCur); VdbeCoverage(v); + sqlite3VdbeAddOp2(v, OP_Integer, 0, regChng); addrNextRow = sqlite3VdbeCurrentAddr(v); @@ -105167,7 +124811,7 @@ static void analyzeOneTable( addrNextRow = sqlite3VdbeCurrentAddr(v); if( nColTest==1 && pIdx->nKeyCol==1 && IsUniqueIndex(pIdx) ){ /* For a single-column UNIQUE index, once we have found a non-NULL - ** row, we know that all the rest will be distinct, so skip + ** row, we know that all the rest will be distinct, so skip ** subsequent distinctness tests. */ sqlite3VdbeAddOp2(v, OP_NotNull, regPrev, endDistinctTest); VdbeCoverage(v); @@ -105176,15 +124820,16 @@ static void analyzeOneTable( char *pColl = (char*)sqlite3LocateCollSeq(pParse, pIdx->azColl[i]); sqlite3VdbeAddOp2(v, OP_Integer, i, regChng); sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, i, regTemp); - aGotoChng[i] = + analyzeVdbeCommentIndexWithColumnName(v,pIdx,i); + aGotoChng[i] = sqlite3VdbeAddOp4(v, OP_Ne, regTemp, 0, regPrev+i, pColl, P4_COLLSEQ); sqlite3VdbeChangeP5(v, SQLITE_NULLEQ); VdbeCoverage(v); } sqlite3VdbeAddOp2(v, OP_Integer, nColTest, regChng); sqlite3VdbeGoto(v, endDistinctTest); - - + + /* ** chng_addr_0: ** regPrev(0) = idx(0) @@ -105196,44 +124841,66 @@ static void analyzeOneTable( for(i=0; ipTable); - int j, k, regKey; - regKey = sqlite3GetTempRange(pParse, pPk->nKeyCol); - for(j=0; jnKeyCol; j++){ - k = sqlite3ColumnOfIndex(pIdx, pPk->aiColumn[j]); - assert( k>=0 && knColumn ); - sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, k, regKey+j); - VdbeComment((v, "%s", pTab->aCol[pPk->aiColumn[j]].zName)); +#ifdef SQLITE_ENABLE_STAT4 + if( OptimizationEnabled(db, SQLITE_Stat4) ){ + assert( regRowid==(regStat+2) ); + if( HasRowid(pTab) ){ + sqlite3VdbeAddOp2(v, OP_IdxRowid, iIdxCur, regRowid); + }else{ + Index *pPk = sqlite3PrimaryKeyIndex(pIdx->pTable); + int j, k, regKey; + regKey = sqlite3GetTempRange(pParse, pPk->nKeyCol); + for(j=0; jnKeyCol; j++){ + k = sqlite3TableColumnToIndex(pIdx, pPk->aiColumn[j]); + assert( k>=0 && knColumn ); + sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, k, regKey+j); + analyzeVdbeCommentIndexWithColumnName(v,pIdx,k); + } + sqlite3VdbeAddOp3(v, OP_MakeRecord, regKey, pPk->nKeyCol, regRowid); + sqlite3ReleaseTempRange(pParse, regKey, pPk->nKeyCol); } - sqlite3VdbeAddOp3(v, OP_MakeRecord, regKey, pPk->nKeyCol, regRowid); - sqlite3ReleaseTempRange(pParse, regKey, pPk->nKeyCol); } #endif - assert( regChng==(regStat4+1) ); - sqlite3VdbeAddOp4(v, OP_Function0, 1, regStat4, regTemp, - (char*)&statPushFuncdef, P4_FUNCDEF); - sqlite3VdbeChangeP5(v, 2+IsStat34); - sqlite3VdbeAddOp2(v, OP_Next, iIdxCur, addrNextRow); VdbeCoverage(v); + assert( regChng==(regStat+1) ); + { + sqlite3VdbeAddFunctionCall(pParse, 1, regStat, regTemp, 2+IsStat4, + &statPushFuncdef, 0); + if( db->nAnalysisLimit ){ + int j1, j2, j3; + j1 = sqlite3VdbeAddOp1(v, OP_IsNull, regTemp); VdbeCoverage(v); + j2 = sqlite3VdbeAddOp1(v, OP_If, regTemp); VdbeCoverage(v); + j3 = sqlite3VdbeAddOp4Int(v, OP_SeekGT, iIdxCur, 0, regPrev, 1); + VdbeCoverage(v); + sqlite3VdbeJumpHere(v, j1); + sqlite3VdbeAddOp2(v, OP_Next, iIdxCur, addrNextRow); VdbeCoverage(v); + sqlite3VdbeJumpHere(v, j2); + sqlite3VdbeJumpHere(v, j3); + }else{ + sqlite3VdbeAddOp2(v, OP_Next, iIdxCur, addrNextRow); VdbeCoverage(v); + } + } /* Add the entry to the stat1 table. */ - callStatGet(v, regStat4, STAT_GET_STAT1, regStat1); + if( pIdx->pPartIdxWhere ){ + /* Partial indexes might get a zero-entry in sqlite_stat1. But + ** an empty table is omitted from sqlite_stat1. */ + sqlite3VdbeJumpHere(v, addrGotoEnd); + addrGotoEnd = 0; + } + callStatGet(pParse, regStat, STAT_GET_STAT1, regStat1); assert( "BBB"[0]==SQLITE_AFF_TEXT ); sqlite3VdbeAddOp4(v, OP_MakeRecord, regTabname, 3, regTemp, "BBB", 0); sqlite3VdbeAddOp2(v, OP_NewRowid, iStatCur, regNewRowid); @@ -105243,9 +124910,9 @@ static void analyzeOneTable( #endif sqlite3VdbeChangeP5(v, OPFLAG_APPEND); - /* Add the entries to the stat3 or stat4 table. */ -#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 - { + /* Add the entries to the stat4 table. */ +#ifdef SQLITE_ENABLE_STAT4 + if( OptimizationEnabled(db, SQLITE_Stat4) && db->nAnalysisLimit==0 ){ int regEq = regStat1; int regLt = regStat1+1; int regDLt = regStat1+2; @@ -105256,35 +124923,66 @@ static void analyzeOneTable( int addrIsNull; u8 seekOp = HasRowid(pTab) ? OP_NotExists : OP_NotFound; - pParse->nMem = MAX(pParse->nMem, regCol+nCol); + /* No STAT4 data is generated if the number of rows is zero */ + if( addrGotoEnd==0 ){ + sqlite3VdbeAddOp2(v, OP_Cast, regStat1, SQLITE_AFF_INTEGER); + addrGotoEnd = sqlite3VdbeAddOp1(v, OP_IfNot, regStat1); + VdbeCoverage(v); + } + + if( doOnce ){ + int mxCol = nCol; + Index *pX; + + /* Compute the maximum number of columns in any index */ + for(pX=pTab->pIndex; pX; pX=pX->pNext){ + int nColX; /* Number of columns in pX */ + if( !HasRowid(pTab) && IsPrimaryKeyIndex(pX) ){ + nColX = pX->nKeyCol; + }else{ + nColX = pX->nColumn; + } + if( nColX>mxCol ) mxCol = nColX; + } + + /* Allocate space to compute results for the largest index */ + sqlite3TouchRegister(pParse, regCol+mxCol); + doOnce = 0; +#ifdef SQLITE_DEBUG + /* Verify that the call to sqlite3ClearTempRegCache() below + ** really is needed. + ** https://sqlite.org/forum/forumpost/83cb4a95a0 (2023-03-25) + */ + testcase( !sqlite3NoTempsInRange(pParse, regEq, regCol+mxCol) ); +#endif + sqlite3ClearTempRegCache(pParse); /* tag-20230325-1 */ + assert( sqlite3NoTempsInRange(pParse, regEq, regCol+mxCol) ); + } + assert( sqlite3NoTempsInRange(pParse, regEq, regCol+nCol) ); addrNext = sqlite3VdbeCurrentAddr(v); - callStatGet(v, regStat4, STAT_GET_ROWID, regSampleRowid); + callStatGet(pParse, regStat, STAT_GET_ROWID, regSampleRowid); addrIsNull = sqlite3VdbeAddOp1(v, OP_IsNull, regSampleRowid); VdbeCoverage(v); - callStatGet(v, regStat4, STAT_GET_NEQ, regEq); - callStatGet(v, regStat4, STAT_GET_NLT, regLt); - callStatGet(v, regStat4, STAT_GET_NDLT, regDLt); + callStatGet(pParse, regStat, STAT_GET_NEQ, regEq); + callStatGet(pParse, regStat, STAT_GET_NLT, regLt); + callStatGet(pParse, regStat, STAT_GET_NDLT, regDLt); sqlite3VdbeAddOp4Int(v, seekOp, iTabCur, addrNext, regSampleRowid, 0); VdbeCoverage(v); -#ifdef SQLITE_ENABLE_STAT3 - sqlite3ExprCodeLoadIndexColumn(pParse, pIdx, iTabCur, 0, regSample); -#else for(i=0; itblHash); k; k=sqliteHashNext(k)){ Table *pTab = (Table*)sqliteHashData(k); analyzeOneTable(pParse, pTab, 0, iStatCur, iMem, iTab); +#ifdef SQLITE_ENABLE_STAT4 + iMem = sqlite3FirstAvailableRegister(pParse, iMem); +#else + assert( iMem==sqlite3FirstAvailableRegister(pParse,iMem) ); +#endif } loadAnalysis(pParse, iDb); } @@ -105456,7 +125159,7 @@ static void decodeIntArray( int i; tRowcnt v; -#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 +#ifdef SQLITE_ENABLE_STAT4 if( z==0 ) z = ""; #else assert( z!=0 ); @@ -105467,7 +125170,7 @@ static void decodeIntArray( v = v*10 + c - '0'; z++; } -#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 +#ifdef SQLITE_ENABLE_STAT4 if( aOut ) aOut[i] = v; if( aLog ) aLog[i] = sqlite3LogEst(v); #else @@ -105478,7 +125181,7 @@ static void decodeIntArray( #endif if( *z==' ' ) z++; } -#ifndef SQLITE_ENABLE_STAT3_OR_STAT4 +#ifndef SQLITE_ENABLE_STAT4 assert( pIndex!=0 ); { #else if( pIndex ){ @@ -105489,7 +125192,9 @@ static void decodeIntArray( if( sqlite3_strglob("unordered*", z)==0 ){ pIndex->bUnordered = 1; }else if( sqlite3_strglob("sz=[0-9]*", z)==0 ){ - pIndex->szIdxRow = sqlite3LogEst(sqlite3Atoi(z+3)); + int sz = sqlite3Atoi(z+3); + if( sz<2 ) sz = 2; + pIndex->szIdxRow = sqlite3LogEst(sz); }else if( sqlite3_strglob("noskipscan*", z)==0 ){ pIndex->noSkipScan = 1; } @@ -105506,7 +125211,7 @@ static void decodeIntArray( /* ** This callback is invoked once for each index when reading the -** sqlite_stat1 table. +** sqlite_stat1 table. ** ** argv[0] = name of the table ** argv[1] = name of the index (might be NULL) @@ -105543,8 +125248,8 @@ static int analysisLoader(void *pData, int argc, char **argv, char **NotUsed){ if( pIndex ){ tRowcnt *aiRowEst = 0; int nCol = pIndex->nKeyCol+1; -#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 - /* Index.aiRowEst may already be set here if there are duplicate +#ifdef SQLITE_ENABLE_STAT4 + /* Index.aiRowEst may already be set here if there are duplicate ** sqlite_stat1 entries for this index. In that case just clobber ** the old data with the new instead of allocating a new array. */ if( pIndex->aiRowEst==0 ){ @@ -105579,7 +125284,9 @@ static int analysisLoader(void *pData, int argc, char **argv, char **NotUsed){ ** and its contents. */ SQLITE_PRIVATE void sqlite3DeleteIndexSamples(sqlite3 *db, Index *pIdx){ -#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 + assert( db!=0 ); + assert( pIdx!=0 ); +#ifdef SQLITE_ENABLE_STAT4 if( pIdx->aSample ){ int j; for(j=0; jnSample; j++){ @@ -105588,20 +125295,20 @@ SQLITE_PRIVATE void sqlite3DeleteIndexSamples(sqlite3 *db, Index *pIdx){ } sqlite3DbFree(db, pIdx->aSample); } - if( db && db->pnBytesFreed==0 ){ + if( db->pnBytesFreed==0 ){ pIdx->nSample = 0; pIdx->aSample = 0; } #else UNUSED_PARAMETER(db); UNUSED_PARAMETER(pIdx); -#endif /* SQLITE_ENABLE_STAT3_OR_STAT4 */ +#endif /* SQLITE_ENABLE_STAT4 */ } -#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 +#ifdef SQLITE_ENABLE_STAT4 /* ** Populate the pIdx->aAvgEq[] array based on the samples currently -** stored in pIdx->aSample[]. +** stored in pIdx->aSample[]. */ static void initAvgEq(Index *pIdx){ if( pIdx ){ @@ -105637,12 +125344,12 @@ static void initAvgEq(Index *pIdx){ pIdx->nRowEst0 = nRow; /* Set nSum to the number of distinct (iCol+1) field prefixes that - ** occur in the stat4 table for this index. Set sumEq to the sum of - ** the nEq values for column iCol for the same set (adding the value + ** occur in the stat4 table for this index. Set sumEq to the sum of + ** the nEq values for column iCol for the same set (adding the value ** only once where there exist duplicate prefixes). */ for(i=0; inSample-1) - || aSample[i].anDLt[iCol]!=aSample[i+1].anDLt[iCol] + || aSample[i].anDLt[iCol]!=aSample[i+1].anDLt[iCol] ){ sumEq += aSample[i].anEq[iCol]; nSum100 += 100; @@ -105676,12 +125383,11 @@ static Index *findIndexOrPrimaryKey( } /* -** Load the content from either the sqlite_stat4 or sqlite_stat3 table +** Load the content from either the sqlite_stat4 ** into the relevant Index.aSample[] arrays. ** ** Arguments zSql1 and zSql2 must point to SQL statements that return -** data equivalent to the following (statements are different for stat3, -** see the caller of this function for details): +** data equivalent to the following: ** ** zSql1: SELECT idx,count(*) FROM %Q.sqlite_stat4 GROUP BY idx ** zSql2: SELECT idx,neq,nlt,ndlt,sample FROM %Q.sqlite_stat4 @@ -105690,7 +125396,6 @@ static Index *findIndexOrPrimaryKey( */ static int loadStatTbl( sqlite3 *db, /* Database handle */ - int bStat3, /* Assume single column records only */ const char *zSql1, /* SQL statement 1 (see above) */ const char *zSql2, /* SQL statement 2 (see above) */ const char *zDb /* Database name (e.g. "main") */ @@ -105713,41 +125418,47 @@ static int loadStatTbl( while( sqlite3_step(pStmt)==SQLITE_ROW ){ int nIdxCol = 1; /* Number of columns in stat4 records */ - char *zIndex; /* Index name */ - Index *pIdx; /* Pointer to the index object */ - int nSample; /* Number of samples */ - int nByte; /* Bytes of space required */ - int i; /* Bytes of space required */ - tRowcnt *pSpace; + char *zIndex; /* Index name */ + Index *pIdx; /* Pointer to the index object */ + int nSample; /* Number of samples */ + i64 nByte; /* Bytes of space required */ + i64 i; /* Bytes of space required */ + tRowcnt *pSpace; /* Available allocated memory space */ + u8 *pPtr; /* Available memory as a u8 for easier manipulation */ zIndex = (char *)sqlite3_column_text(pStmt, 0); if( zIndex==0 ) continue; nSample = sqlite3_column_int(pStmt, 1); pIdx = findIndexOrPrimaryKey(db, zIndex, zDb); - assert( pIdx==0 || bStat3 || pIdx->nSample==0 ); - /* Index.nSample is non-zero at this point if data has already been - ** loaded from the stat4 table. In this case ignore stat3 data. */ - if( pIdx==0 || pIdx->nSample ) continue; - if( bStat3==0 ){ - assert( !HasRowid(pIdx->pTable) || pIdx->nColumn==pIdx->nKeyCol+1 ); - if( !HasRowid(pIdx->pTable) && IsPrimaryKeyIndex(pIdx) ){ - nIdxCol = pIdx->nKeyCol; - }else{ - nIdxCol = pIdx->nColumn; - } + assert( pIdx==0 || pIdx->nSample==0 ); + if( pIdx==0 ) continue; + if( pIdx->aSample!=0 ){ + /* The same index appears in sqlite_stat4 under multiple names */ + continue; + } + assert( !HasRowid(pIdx->pTable) || pIdx->nColumn==pIdx->nKeyCol+1 ); + if( !HasRowid(pIdx->pTable) && IsPrimaryKeyIndex(pIdx) ){ + nIdxCol = pIdx->nKeyCol; + }else{ + nIdxCol = pIdx->nColumn; } pIdx->nSampleCol = nIdxCol; - nByte = sizeof(IndexSample) * nSample; - nByte += sizeof(tRowcnt) * nIdxCol * 3 * nSample; - nByte += nIdxCol * sizeof(tRowcnt); /* Space for Index.aAvgEq[] */ + pIdx->mxSample = nSample; + nByte = ROUND8(sizeof64(IndexSample) * nSample); + nByte += sizeof64(tRowcnt) * nIdxCol * 3 * nSample; + nByte += nIdxCol * sizeof64(tRowcnt); /* Space for Index.aAvgEq[] */ pIdx->aSample = sqlite3DbMallocZero(db, nByte); if( pIdx->aSample==0 ){ sqlite3_finalize(pStmt); return SQLITE_NOMEM_BKPT; } - pSpace = (tRowcnt*)&pIdx->aSample[nSample]; + pPtr = (u8*)pIdx->aSample; + pPtr += ROUND8(nSample*sizeof64(pIdx->aSample[0])); + pSpace = (tRowcnt*)pPtr; + assert( EIGHT_BYTE_ALIGNMENT( pSpace ) ); pIdx->aAvgEq = pSpace; pSpace += nIdxCol; + pIdx->pTable->tabFlags |= TF_HasStat4; for(i=0; iaSample[i].anEq = pSpace; pSpace += nIdxCol; pIdx->aSample[i].anLt = pSpace; pSpace += nIdxCol; @@ -105775,10 +125486,14 @@ static int loadStatTbl( if( zIndex==0 ) continue; pIdx = findIndexOrPrimaryKey(db, zIndex, zDb); if( pIdx==0 ) continue; - /* This next condition is true if data has already been loaded from - ** the sqlite_stat4 table. In this case ignore stat3 data. */ + if( pIdx->nSample>=pIdx->mxSample ){ + /* Too many slots used because the same index appears in + ** sqlite_stat4 using multiple names */ + continue; + } + /* This next condition is true if data has already been loaded from + ** the sqlite_stat4 table. */ nCol = pIdx->nSampleCol; - if( bStat3 && nCol>1 ) continue; if( pIdx!=pPrevIdx ){ initAvgEq(pPrevIdx); pPrevIdx = pIdx; @@ -105788,14 +125503,15 @@ static int loadStatTbl( decodeIntArray((char*)sqlite3_column_text(pStmt,2),nCol,pSample->anLt,0,0); decodeIntArray((char*)sqlite3_column_text(pStmt,3),nCol,pSample->anDLt,0,0); - /* Take a copy of the sample. Add two 0x00 bytes the end of the buffer. + /* Take a copy of the sample. Add 8 extra 0x00 bytes the end of the buffer. ** This is in case the sample record is corrupted. In that case, the ** sqlite3VdbeRecordCompare() may read up to two varints past the ** end of the allocated buffer before it realizes it is dealing with - ** a corrupt record. Adding the two 0x00 bytes prevents this from causing + ** a corrupt record. Or it might try to read a large integer from the + ** buffer. In any case, eight 0x00 bytes prevents this from causing ** a buffer overread. */ pSample->n = sqlite3_column_bytes(pStmt, 4); - pSample->p = sqlite3DbMallocZero(db, pSample->n + 2); + pSample->p = sqlite3DbMallocZero(db, pSample->n + 8); if( pSample->p==0 ){ sqlite3_finalize(pStmt); return SQLITE_NOMEM_BKPT; @@ -105811,45 +125527,40 @@ static int loadStatTbl( } /* -** Load content from the sqlite_stat4 and sqlite_stat3 tables into +** Load content from the sqlite_stat4 table into ** the Index.aSample[] arrays of all indices. */ static int loadStat4(sqlite3 *db, const char *zDb){ int rc = SQLITE_OK; /* Result codes from subroutines */ + const Table *pStat4; assert( db->lookaside.bDisable ); - if( sqlite3FindTable(db, "sqlite_stat4", zDb) ){ - rc = loadStatTbl(db, 0, - "SELECT idx,count(*) FROM %Q.sqlite_stat4 GROUP BY idx", + if( OptimizationEnabled(db, SQLITE_Stat4) + && (pStat4 = sqlite3FindTable(db, "sqlite_stat4", zDb))!=0 + && IsOrdinaryTable(pStat4) + ){ + rc = loadStatTbl(db, + "SELECT idx,count(*) FROM %Q.sqlite_stat4 GROUP BY idx COLLATE nocase", "SELECT idx,neq,nlt,ndlt,sample FROM %Q.sqlite_stat4", zDb ); } - - if( rc==SQLITE_OK && sqlite3FindTable(db, "sqlite_stat3", zDb) ){ - rc = loadStatTbl(db, 1, - "SELECT idx,count(*) FROM %Q.sqlite_stat3 GROUP BY idx", - "SELECT idx,neq,nlt,ndlt,sqlite_record(sample) FROM %Q.sqlite_stat3", - zDb - ); - } - return rc; } -#endif /* SQLITE_ENABLE_STAT3_OR_STAT4 */ +#endif /* SQLITE_ENABLE_STAT4 */ /* -** Load the content of the sqlite_stat1 and sqlite_stat3/4 tables. The +** Load the content of the sqlite_stat1 and sqlite_stat4 tables. The ** contents of sqlite_stat1 are used to populate the Index.aiRowEst[] -** arrays. The contents of sqlite_stat3/4 are used to populate the +** arrays. The contents of sqlite_stat4 are used to populate the ** Index.aSample[] arrays. ** ** If the sqlite_stat1 table is not present in the database, SQLITE_ERROR -** is returned. In this case, even if SQLITE_ENABLE_STAT3/4 was defined -** during compilation and the sqlite_stat3/4 table is present, no data is +** is returned. In this case, even if SQLITE_ENABLE_STAT4 was defined +** during compilation and the sqlite_stat4 table is present, no data is ** read from it. ** -** If SQLITE_ENABLE_STAT3/4 was defined during compilation and the +** If SQLITE_ENABLE_STAT4 was defined during compilation and the ** sqlite_stat4 table is not present in the database, SQLITE_ERROR is ** returned. However, in this case, data is read from the sqlite_stat1 ** table (if it is present) before returning. @@ -105864,6 +125575,7 @@ SQLITE_PRIVATE int sqlite3AnalysisLoad(sqlite3 *db, int iDb){ char *zSql; int rc = SQLITE_OK; Schema *pSchema = db->aDb[iDb].pSchema; + const Table *pStat1; assert( iDb>=0 && iDbnDb ); assert( db->aDb[iDb].pBt!=0 ); @@ -105877,7 +125589,7 @@ SQLITE_PRIVATE int sqlite3AnalysisLoad(sqlite3 *db, int iDb){ for(i=sqliteHashFirst(&pSchema->idxHash); i; i=sqliteHashNext(i)){ Index *pIdx = sqliteHashData(i); pIdx->hasStat1 = 0; -#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 +#ifdef SQLITE_ENABLE_STAT4 sqlite3DeleteIndexSamples(db, pIdx); pIdx->aSample = 0; #endif @@ -105886,8 +125598,10 @@ SQLITE_PRIVATE int sqlite3AnalysisLoad(sqlite3 *db, int iDb){ /* Load new statistics out of the sqlite_stat1 table */ sInfo.db = db; sInfo.zDatabase = db->aDb[iDb].zDbSName; - if( sqlite3FindTable(db, "sqlite_stat1", sInfo.zDatabase)!=0 ){ - zSql = sqlite3MPrintf(db, + if( (pStat1 = sqlite3FindTable(db, "sqlite_stat1", sInfo.zDatabase)) + && IsOrdinaryTable(pStat1) + ){ + zSql = sqlite3MPrintf(db, "SELECT tbl,idx,stat FROM %Q.sqlite_stat1", sInfo.zDatabase); if( zSql==0 ){ rc = SQLITE_NOMEM_BKPT; @@ -105905,11 +125619,11 @@ SQLITE_PRIVATE int sqlite3AnalysisLoad(sqlite3 *db, int iDb){ } /* Load the statistics from the sqlite_stat4 table. */ -#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 +#ifdef SQLITE_ENABLE_STAT4 if( rc==SQLITE_OK ){ - db->lookaside.bDisable++; + DisableLookaside; rc = loadStat4(db, sInfo.zDatabase); - db->lookaside.bDisable--; + EnableLookaside; } for(i=sqliteHashFirst(&pSchema->idxHash); i; i=sqliteHashNext(i)){ Index *pIdx = sqliteHashData(i); @@ -105976,6 +125690,17 @@ static int resolveAttachExpr(NameContext *pName, Expr *pExpr) return rc; } +/* +** Return true if zName points to a name that may be used to refer to +** database iDb attached to handle db. +*/ +SQLITE_PRIVATE int sqlite3DbIsNamed(sqlite3 *db, int iDb, const char *zName){ + return ( + sqlite3StrICmp(db->aDb[iDb].zDbSName, zName)==0 + || (iDb==0 && sqlite3StrICmp("main", zName)==0) + ); +} + /* ** An SQL user-function registered to do the work of an ATTACH statement. The ** three arguments to the function come directly from an attach statement: @@ -106005,7 +125730,7 @@ static void attachFunc( char *zErr = 0; unsigned int flags; Db *aNew; /* New array of Db pointers */ - Db *pNew; /* Db object for the newly attached database */ + Db *pNew = 0; /* Db object for the newly attached database */ char *zErrDyn = 0; sqlite3_vfs *pVfs; @@ -106015,7 +125740,7 @@ static void attachFunc( if( zFile==0 ) zFile = ""; if( zName==0 ) zName = ""; -#ifdef SQLITE_ENABLE_DESERIALIZE +#ifndef SQLITE_OMIT_DESERIALIZE # define REOPEN_AS_MEMDB(db) (db->init.reopenMemdb) #else # define REOPEN_AS_MEMDB(db) (0) @@ -106025,13 +125750,35 @@ static void attachFunc( /* This is not a real ATTACH. Instead, this routine is being called ** from sqlite3_deserialize() to close database db->init.iDb and ** reopen it as a MemDB */ + Btree *pNewBt = 0; + + pNew = &db->aDb[db->init.iDb]; + assert( pNew->pBt!=0 ); + if( sqlite3BtreeTxnState(pNew->pBt)!=SQLITE_TXN_NONE + || sqlite3BtreeIsInBackup(pNew->pBt) + ){ + rc = SQLITE_BUSY; + goto attach_error; + } + pVfs = sqlite3_vfs_find("memdb"); if( pVfs==0 ) return; - pNew = &db->aDb[db->init.iDb]; - if( pNew->pBt ) sqlite3BtreeClose(pNew->pBt); - pNew->pBt = 0; - pNew->pSchema = 0; - rc = sqlite3BtreeOpen(pVfs, "x\0", db, &pNew->pBt, 0, SQLITE_OPEN_MAIN_DB); + rc = sqlite3BtreeOpen(pVfs, "x\0", db, &pNewBt, 0, SQLITE_OPEN_MAIN_DB); + if( rc==SQLITE_OK ){ + Schema *pNewSchema = sqlite3SchemaGet(db, pNewBt); + if( pNewSchema ){ + /* Both the Btree and the new Schema were allocated successfully. + ** Close the old db and update the aDb[] slot with the new memdb + ** values. */ + sqlite3BtreeClose(pNew->pBt); + pNew->pBt = pNewBt; + pNew->pSchema = pNewSchema; + }else{ + sqlite3BtreeClose(pNewBt); + rc = SQLITE_NOMEM; + } + } + if( rc ) goto attach_error; }else{ /* This is a real ATTACH ** @@ -106042,20 +125789,19 @@ static void attachFunc( ** * Specified database name already being used. */ if( db->nDb>=db->aLimit[SQLITE_LIMIT_ATTACHED]+2 ){ - zErrDyn = sqlite3MPrintf(db, "too many attached databases - max %d", + zErrDyn = sqlite3MPrintf(db, "too many attached databases - max %d", db->aLimit[SQLITE_LIMIT_ATTACHED] ); goto attach_error; } for(i=0; inDb; i++){ - char *z = db->aDb[i].zDbSName; - assert( z && zName ); - if( sqlite3StrICmp(z, zName)==0 ){ + assert( zName ); + if( sqlite3DbIsNamed(db, i, zName) ){ zErrDyn = sqlite3MPrintf(db, "database %s is already in use", zName); goto attach_error; } } - + /* Allocate the new entry in the db->aDb[] array and initialize the schema ** hash tables. */ @@ -106064,13 +125810,13 @@ static void attachFunc( if( aNew==0 ) return; memcpy(aNew, db->aDb, sizeof(db->aDb[0])*2); }else{ - aNew = sqlite3DbRealloc(db, db->aDb, sizeof(db->aDb[0])*(db->nDb+1) ); + aNew = sqlite3DbRealloc(db, db->aDb, sizeof(db->aDb[0])*(1+(i64)db->nDb)); if( aNew==0 ) return; } db->aDb = aNew; pNew = &db->aDb[db->nDb]; memset(pNew, 0, sizeof(*pNew)); - + /* Open the database file. If the btree is successfully opened, use ** it to obtain the database schema. At this point the schema may ** or may not be initialized. @@ -106083,6 +125829,12 @@ static void attachFunc( sqlite3_free(zErr); return; } + if( (db->flags & SQLITE_AttachWrite)==0 ){ + flags &= ~(SQLITE_OPEN_CREATE|SQLITE_OPEN_READWRITE); + flags |= SQLITE_OPEN_READONLY; + }else if( (db->flags & SQLITE_AttachCreate)==0 ){ + flags &= ~SQLITE_OPEN_CREATE; + } assert( pVfs ); flags |= SQLITE_OPEN_MAIN_DB; rc = sqlite3BtreeOpen(pVfs, zPath, db, &pNew->pBt, 0, flags); @@ -106099,7 +125851,7 @@ static void attachFunc( if( !pNew->pSchema ){ rc = SQLITE_NOMEM_BKPT; }else if( pNew->pSchema->file_format && pNew->pSchema->enc!=ENC(db) ){ - zErrDyn = sqlite3MPrintf(db, + zErrDyn = sqlite3MPrintf(db, "attached databases must use the same text encoding as main database"); rc = SQLITE_ERROR; } @@ -106118,46 +125870,10 @@ static void attachFunc( if( rc==SQLITE_OK && pNew->zDbSName==0 ){ rc = SQLITE_NOMEM_BKPT; } - - -#ifdef SQLITE_HAS_CODEC - if( rc==SQLITE_OK ){ - extern int sqlite3CodecAttach(sqlite3*, int, const void*, int); - extern void sqlite3CodecGetKey(sqlite3*, int, void**, int*); - int nKey; - char *zKey; - int t = sqlite3_value_type(argv[2]); - switch( t ){ - case SQLITE_INTEGER: - case SQLITE_FLOAT: - zErrDyn = sqlite3DbStrDup(db, "Invalid key value"); - rc = SQLITE_ERROR; - break; - - case SQLITE_TEXT: - case SQLITE_BLOB: - nKey = sqlite3_value_bytes(argv[2]); - zKey = (char *)sqlite3_value_blob(argv[2]); - rc = sqlite3CodecAttach(db, db->nDb-1, zKey, nKey); - break; - - case SQLITE_NULL: - /* No key specified. Use the key from URI filename, or if none, - ** use the key from the main database. */ - if( sqlite3CodecQueryParameters(db, zName, zPath)==0 ){ - sqlite3CodecGetKey(db, 0, (void**)&zKey, &nKey); - if( nKey || sqlite3BtreeGetOptimalReserve(db->aDb[0].pBt)>0 ){ - rc = sqlite3CodecAttach(db, db->nDb-1, zKey, nKey); - } - } - break; - } - } -#endif - sqlite3_free( zPath ); + sqlite3_free_filename( zPath ); /* If the file was opened successfully, read the schema for the new database. - ** If this fails, or if opening the file failed, then close the file and + ** If this fails, or if opening the file failed, then close the file and ** remove the entry from the db->aDb[] array. i.e. put everything back the ** way we found it. */ @@ -106165,23 +125881,21 @@ static void attachFunc( sqlite3BtreeEnterAll(db); db->init.iDb = 0; db->mDbFlags &= ~(DBFLAG_SchemaKnownOk); +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + if( db->setlkFlags & SQLITE_SETLK_BLOCK_ON_CONNECT ){ + int val = 1; + sqlite3_file *fd = sqlite3PagerFile(sqlite3BtreePager(pNew->pBt)); + sqlite3OsFileControlHint(fd, SQLITE_FCNTL_BLOCK_ON_CONNECT, &val); + } +#endif if( !REOPEN_AS_MEMDB(db) ){ rc = sqlite3Init(db, &zErrDyn); } sqlite3BtreeLeaveAll(db); assert( zErrDyn==0 || rc!=SQLITE_OK ); } -#ifdef SQLITE_USER_AUTHENTICATION - if( rc==SQLITE_OK && !REOPEN_AS_MEMDB(db) ){ - u8 newAuth = 0; - rc = sqlite3UserAuthCheckLogin(db, zName, &newAuth); - if( newAuthauth.authLevel ){ - rc = SQLITE_AUTH_USER; - } - } -#endif if( rc ){ - if( !REOPEN_AS_MEMDB(db) ){ + if( ALWAYS(!REOPEN_AS_MEMDB(db)) ){ int iDb = db->nDb - 1; assert( iDb>=2 ); if( db->aDb[iDb].pBt ){ @@ -106201,7 +125915,7 @@ static void attachFunc( } goto attach_error; } - + return; attach_error: @@ -106230,6 +125944,7 @@ static void detachFunc( sqlite3 *db = sqlite3_context_db_handle(context); int i; Db *pDb = 0; + HashElem *pEntry; char zErr[128]; UNUSED_PARAMETER(NotUsed); @@ -106238,7 +125953,7 @@ static void detachFunc( for(i=0; inDb; i++){ pDb = &db->aDb[i]; if( pDb->pBt==0 ) continue; - if( sqlite3StrICmp(pDb->zDbSName, zName)==0 ) break; + if( sqlite3DbIsNamed(db, i, zName) ) break; } if( i>=db->nDb ){ @@ -106249,11 +125964,25 @@ static void detachFunc( sqlite3_snprintf(sizeof(zErr),zErr, "cannot detach database %s", zName); goto detach_error; } - if( sqlite3BtreeIsInReadTrans(pDb->pBt) || sqlite3BtreeIsInBackup(pDb->pBt) ){ + if( sqlite3BtreeTxnState(pDb->pBt)!=SQLITE_TXN_NONE + || sqlite3BtreeIsInBackup(pDb->pBt) + ){ sqlite3_snprintf(sizeof(zErr),zErr, "database %s is locked", zName); goto detach_error; } + /* If any TEMP triggers reference the schema being detached, move those + ** triggers to reference the TEMP schema itself. */ + assert( db->aDb[1].pSchema ); + pEntry = sqliteHashFirst(&db->aDb[1].pSchema->trigHash); + while( pEntry ){ + Trigger *pTrig = (Trigger*)sqliteHashData(pEntry); + if( pTrig->pTabSchema==pDb->pSchema ){ + pTrig->pTabSchema = pTrig->pSchema; + } + pEntry = sqliteHashNext(pEntry); + } + sqlite3BtreeClose(pDb->pBt); pDb->pBt = 0; pDb->pSchema = 0; @@ -106283,22 +126012,25 @@ static void codeAttach( sqlite3* db = pParse->db; int regArgs; + if( SQLITE_OK!=sqlite3ReadSchema(pParse) ) goto attach_end; + if( pParse->nErr ) goto attach_end; memset(&sName, 0, sizeof(NameContext)); sName.pParse = pParse; - if( - SQLITE_OK!=(rc = resolveAttachExpr(&sName, pFilename)) || - SQLITE_OK!=(rc = resolveAttachExpr(&sName, pDbname)) || - SQLITE_OK!=(rc = resolveAttachExpr(&sName, pKey)) + if( + SQLITE_OK!=resolveAttachExpr(&sName, pFilename) || + SQLITE_OK!=resolveAttachExpr(&sName, pDbname) || + SQLITE_OK!=resolveAttachExpr(&sName, pKey) ){ goto attach_end; } #ifndef SQLITE_OMIT_AUTHORIZATION - if( pAuthArg ){ + if( ALWAYS(pAuthArg) ){ char *zAuthArg; if( pAuthArg->op==TK_STRING ){ + assert( !ExprHasProperty(pAuthArg, EP_IntValue) ); zAuthArg = pAuthArg->u.zToken; }else{ zAuthArg = 0; @@ -106319,18 +126051,15 @@ static void codeAttach( assert( v || db->mallocFailed ); if( v ){ - sqlite3VdbeAddOp4(v, OP_Function0, 0, regArgs+3-pFunc->nArg, regArgs+3, - (char *)pFunc, P4_FUNCDEF); - assert( pFunc->nArg==-1 || (pFunc->nArg&0xff)==pFunc->nArg ); - sqlite3VdbeChangeP5(v, (u8)(pFunc->nArg)); - + sqlite3VdbeAddFunctionCall(pParse, 0, regArgs+3-pFunc->nArg, regArgs+3, + pFunc->nArg, pFunc, 0); /* Code an OP_Expire. For an ATTACH statement, set P1 to true (expire this ** statement only). For DETACH, set it to false (expire all existing ** statements). */ sqlite3VdbeAddOp1(v, OP_Expire, (type==SQLITE_ATTACH)); } - + attach_end: sqlite3ExprDelete(db, pFilename); sqlite3ExprDelete(db, pDbname); @@ -106378,6 +126107,70 @@ SQLITE_PRIVATE void sqlite3Attach(Parse *pParse, Expr *p, Expr *pDbname, Expr *p } #endif /* SQLITE_OMIT_ATTACH */ +/* +** Expression callback used by sqlite3FixAAAA() routines. +*/ +static int fixExprCb(Walker *p, Expr *pExpr){ + DbFixer *pFix = p->u.pFix; + if( !pFix->bTemp ) ExprSetProperty(pExpr, EP_FromDDL); + if( pExpr->op==TK_VARIABLE ){ + if( pFix->pParse->db->init.busy ){ + pExpr->op = TK_NULL; + }else{ + sqlite3ErrorMsg(pFix->pParse, "%s cannot use variables", pFix->zType); + return WRC_Abort; + } + } + return WRC_Continue; +} + +/* +** Select callback used by sqlite3FixAAAA() routines. +*/ +static int fixSelectCb(Walker *p, Select *pSelect){ + DbFixer *pFix = p->u.pFix; + int i; + SrcItem *pItem; + sqlite3 *db = pFix->pParse->db; + int iDb = sqlite3FindDbName(db, pFix->zDb); + SrcList *pList = pSelect->pSrc; + + if( NEVER(pList==0) ) return WRC_Continue; + for(i=0, pItem=pList->a; inSrc; i++, pItem++){ + if( pFix->bTemp==0 && pItem->fg.isSubquery==0 ){ + if( pItem->fg.fixedSchema==0 && pItem->u4.zDatabase!=0 ){ + if( iDb!=sqlite3FindDbName(db, pItem->u4.zDatabase) ){ + sqlite3ErrorMsg(pFix->pParse, + "%s %T cannot reference objects in database %s", + pFix->zType, pFix->pName, pItem->u4.zDatabase); + return WRC_Abort; + } + sqlite3DbFree(db, pItem->u4.zDatabase); + pItem->fg.notCte = 1; + pItem->fg.hadSchema = 1; + } + pItem->u4.pSchema = pFix->pSchema; + pItem->fg.fromDDL = 1; + pItem->fg.fixedSchema = 1; + } +#if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_TRIGGER) + if( pList->a[i].fg.isUsing==0 + && sqlite3WalkExpr(&pFix->w, pList->a[i].u3.pOn) + ){ + return WRC_Abort; + } +#endif + } + if( pSelect->pWith ){ + for(i=0; ipWith->nCte; i++){ + if( sqlite3WalkSelect(p, pSelect->pWith->a[i].pSelect) ){ + return WRC_Abort; + } + } + } + return WRC_Continue; +} + /* ** Initialize a DbFixer structure. This routine must be called prior ** to passing the structure to one of the sqliteFixAAAA() routines below. @@ -106389,16 +126182,21 @@ SQLITE_PRIVATE void sqlite3FixInit( const char *zType, /* "view", "trigger", or "index" */ const Token *pName /* Name of the view, trigger, or index */ ){ - sqlite3 *db; - - db = pParse->db; + sqlite3 *db = pParse->db; assert( db->nDb>iDb ); pFix->pParse = pParse; pFix->zDb = db->aDb[iDb].zDbSName; pFix->pSchema = db->aDb[iDb].pSchema; pFix->zType = zType; pFix->pName = pName; - pFix->bVarOnly = (iDb==1); + pFix->bTemp = (iDb==1); + pFix->w.pParse = pParse; + pFix->w.xExprCallback = fixExprCb; + pFix->w.xSelectCallback = fixSelectCb; + pFix->w.xSelectCallback2 = sqlite3WalkWinDefnDummyCallback; + pFix->w.walkerDepth = 0; + pFix->w.eCode = 0; + pFix->w.u.pFix = pFix; } /* @@ -106419,112 +126217,27 @@ SQLITE_PRIVATE int sqlite3FixSrcList( DbFixer *pFix, /* Context of the fixation */ SrcList *pList /* The Source list to check and modify */ ){ - int i; - const char *zDb; - struct SrcList_item *pItem; - - if( NEVER(pList==0) ) return 0; - zDb = pFix->zDb; - for(i=0, pItem=pList->a; inSrc; i++, pItem++){ - if( pFix->bVarOnly==0 ){ - if( pItem->zDatabase && sqlite3StrICmp(pItem->zDatabase, zDb) ){ - sqlite3ErrorMsg(pFix->pParse, - "%s %T cannot reference objects in database %s", - pFix->zType, pFix->pName, pItem->zDatabase); - return 1; - } - sqlite3DbFree(pFix->pParse->db, pItem->zDatabase); - pItem->zDatabase = 0; - pItem->pSchema = pFix->pSchema; - } -#if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_TRIGGER) - if( sqlite3FixSelect(pFix, pItem->pSelect) ) return 1; - if( sqlite3FixExpr(pFix, pItem->pOn) ) return 1; -#endif - if( pItem->fg.isTabFunc && sqlite3FixExprList(pFix, pItem->u1.pFuncArg) ){ - return 1; - } + int res = 0; + if( pList ){ + Select s; + memset(&s, 0, sizeof(s)); + s.pSrc = pList; + res = sqlite3WalkSelect(&pFix->w, &s); } - return 0; + return res; } #if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_TRIGGER) SQLITE_PRIVATE int sqlite3FixSelect( DbFixer *pFix, /* Context of the fixation */ Select *pSelect /* The SELECT statement to be fixed to one database */ ){ - while( pSelect ){ - if( sqlite3FixExprList(pFix, pSelect->pEList) ){ - return 1; - } - if( sqlite3FixSrcList(pFix, pSelect->pSrc) ){ - return 1; - } - if( sqlite3FixExpr(pFix, pSelect->pWhere) ){ - return 1; - } - if( sqlite3FixExprList(pFix, pSelect->pGroupBy) ){ - return 1; - } - if( sqlite3FixExpr(pFix, pSelect->pHaving) ){ - return 1; - } - if( sqlite3FixExprList(pFix, pSelect->pOrderBy) ){ - return 1; - } - if( sqlite3FixExpr(pFix, pSelect->pLimit) ){ - return 1; - } - if( pSelect->pWith ){ - int i; - for(i=0; ipWith->nCte; i++){ - if( sqlite3FixSelect(pFix, pSelect->pWith->a[i].pSelect) ){ - return 1; - } - } - } - pSelect = pSelect->pPrior; - } - return 0; + return sqlite3WalkSelect(&pFix->w, pSelect); } SQLITE_PRIVATE int sqlite3FixExpr( DbFixer *pFix, /* Context of the fixation */ Expr *pExpr /* The expression to be fixed to one database */ ){ - while( pExpr ){ - if( pExpr->op==TK_VARIABLE ){ - if( pFix->pParse->db->init.busy ){ - pExpr->op = TK_NULL; - }else{ - sqlite3ErrorMsg(pFix->pParse, "%s cannot use variables", pFix->zType); - return 1; - } - } - if( ExprHasProperty(pExpr, EP_TokenOnly|EP_Leaf) ) break; - if( ExprHasProperty(pExpr, EP_xIsSelect) ){ - if( sqlite3FixSelect(pFix, pExpr->x.pSelect) ) return 1; - }else{ - if( sqlite3FixExprList(pFix, pExpr->x.pList) ) return 1; - } - if( sqlite3FixExpr(pFix, pExpr->pRight) ){ - return 1; - } - pExpr = pExpr->pLeft; - } - return 0; -} -SQLITE_PRIVATE int sqlite3FixExprList( - DbFixer *pFix, /* Context of the fixation */ - ExprList *pList /* The expression to be fixed to one database */ -){ - int i; - struct ExprList_item *pItem; - if( pList==0 ) return 0; - for(i=0, pItem=pList->a; inExpr; i++, pItem++){ - if( sqlite3FixExpr(pFix, pItem->pExpr) ){ - return 1; - } - } - return 0; + return sqlite3WalkExpr(&pFix->w, pExpr); } #endif @@ -106534,29 +126247,30 @@ SQLITE_PRIVATE int sqlite3FixTriggerStep( TriggerStep *pStep /* The trigger step be fixed to one database */ ){ while( pStep ){ - if( sqlite3FixSelect(pFix, pStep->pSelect) ){ - return 1; - } - if( sqlite3FixExpr(pFix, pStep->pWhere) ){ - return 1; - } - if( sqlite3FixExprList(pFix, pStep->pExprList) ){ + if( sqlite3WalkSelect(&pFix->w, pStep->pSelect) + || sqlite3WalkExpr(&pFix->w, pStep->pWhere) + || sqlite3WalkExprList(&pFix->w, pStep->pExprList) + || sqlite3FixSrcList(pFix, pStep->pSrc) + ){ return 1; } #ifndef SQLITE_OMIT_UPSERT - if( pStep->pUpsert ){ - Upsert *pUp = pStep->pUpsert; - if( sqlite3FixExprList(pFix, pUp->pUpsertTarget) - || sqlite3FixExpr(pFix, pUp->pUpsertTargetWhere) - || sqlite3FixExprList(pFix, pUp->pUpsertSet) - || sqlite3FixExpr(pFix, pUp->pUpsertWhere) - ){ - return 1; + { + Upsert *pUp; + for(pUp=pStep->pUpsert; pUp; pUp=pUp->pNextUpsert){ + if( sqlite3WalkExprList(&pFix->w, pUp->pUpsertTarget) + || sqlite3WalkExpr(&pFix->w, pUp->pUpsertTargetWhere) + || sqlite3WalkExprList(&pFix->w, pUp->pUpsertSet) + || sqlite3WalkExpr(&pFix->w, pUp->pUpsertWhere) + ){ + return 1; + } } } #endif pStep = pStep->pNext; } + return 0; } #endif @@ -106643,7 +126357,7 @@ SQLITE_API int sqlite3_set_authorizer( sqlite3_mutex_enter(db->mutex); db->xAuth = (sqlite3_xauth)xAuth; db->pAuthArg = pArg; - sqlite3ExpirePreparedStatements(db, 0); + sqlite3ExpirePreparedStatements(db, 1); sqlite3_mutex_leave(db->mutex); return SQLITE_OK; } @@ -106677,11 +126391,7 @@ SQLITE_PRIVATE int sqlite3AuthReadCol( int rc; /* Auth callback return code */ if( db->init.busy ) return SQLITE_OK; - rc = db->xAuth(db->pAuthArg, SQLITE_READ, zTab,zCol,zDb,pParse->zAuthContext -#ifdef SQLITE_USER_AUTHENTICATION - ,db->auth.zAuthUser -#endif - ); + rc = db->xAuth(db->pAuthArg, SQLITE_READ, zTab,zCol,zDb,pParse->zAuthContext); if( rc==SQLITE_DENY ){ char *z = sqlite3_mprintf("%s.%s", zTab, zCol); if( db->nDb>2 || iDb!=0 ) z = sqlite3_mprintf("%s.%z", zDb, z); @@ -106695,10 +126405,10 @@ SQLITE_PRIVATE int sqlite3AuthReadCol( /* ** The pExpr should be a TK_COLUMN expression. The table referred to -** is in pTabList or else it is the NEW or OLD table of a trigger. +** is in pTabList or else it is the NEW or OLD table of a trigger. ** Check to see if it is OK to read this particular column. ** -** If the auth function returns SQLITE_IGNORE, change the TK_COLUMN +** If the auth function returns SQLITE_IGNORE, change the TK_COLUMN ** instruction into a TK_NULL. If the auth function returns SQLITE_DENY, ** then generate an error. */ @@ -106708,7 +126418,6 @@ SQLITE_PRIVATE void sqlite3AuthRead( Schema *pSchema, /* The schema of the expression */ SrcList *pTabList /* All table that pExpr might refer to */ ){ - sqlite3 *db = pParse->db; Table *pTab = 0; /* The table being read */ const char *zCol; /* Name of the column of the table */ int iSrc; /* Index in pTabList->a[] of table being read */ @@ -106716,8 +126425,8 @@ SQLITE_PRIVATE void sqlite3AuthRead( int iCol; /* Index of column in table */ assert( pExpr->op==TK_COLUMN || pExpr->op==TK_TRIGGER ); - assert( !IN_RENAME_OBJECT || db->xAuth==0 ); - if( db->xAuth==0 ) return; + assert( !IN_RENAME_OBJECT ); + assert( pParse->db->xAuth!=0 ); iDb = sqlite3SchemaToIndex(pParse->db, pSchema); if( iDb<0 ){ /* An attempt to read a column out of a subquery or other @@ -106729,26 +126438,26 @@ SQLITE_PRIVATE void sqlite3AuthRead( pTab = pParse->pTriggerTab; }else{ assert( pTabList ); - for(iSrc=0; ALWAYS(iSrcnSrc); iSrc++){ + for(iSrc=0; iSrcnSrc; iSrc++){ if( pExpr->iTable==pTabList->a[iSrc].iCursor ){ - pTab = pTabList->a[iSrc].pTab; + pTab = pTabList->a[iSrc].pSTab; break; } } } iCol = pExpr->iColumn; - if( NEVER(pTab==0) ) return; + if( pTab==0 ) return; if( iCol>=0 ){ assert( iColnCol ); - zCol = pTab->aCol[iCol].zName; + zCol = pTab->aCol[iCol].zCnName; }else if( pTab->iPKey>=0 ){ assert( pTab->iPKeynCol ); - zCol = pTab->aCol[pTab->iPKey].zName; + zCol = pTab->aCol[pTab->iPKey].zCnName; }else{ zCol = "ROWID"; } - assert( iDb>=0 && iDbnDb ); + assert( iDb>=0 && iDbdb->nDb ); if( SQLITE_IGNORE==sqlite3AuthReadCol(pParse, pTab->zName, zCol, iDb) ){ pExpr->op = TK_NULL; } @@ -106770,15 +126479,11 @@ SQLITE_PRIVATE int sqlite3AuthCheck( sqlite3 *db = pParse->db; int rc; - /* Don't do any authorization checks if the database is initialising + /* Don't do any authorization checks if the database is initializing ** or if the parser is being invoked from within sqlite3_declare_vtab. */ assert( !IN_RENAME_OBJECT || db->xAuth==0 ); - if( db->init.busy || IN_SPECIAL_PARSE ){ - return SQLITE_OK; - } - - if( db->xAuth==0 ){ + if( db->xAuth==0 || db->init.busy || IN_SPECIAL_PARSE ){ return SQLITE_OK; } @@ -106793,11 +126498,7 @@ SQLITE_PRIVATE int sqlite3AuthCheck( testcase( zArg3==0 ); testcase( pParse->zAuthContext==0 ); - rc = db->xAuth(db->pAuthArg, code, zArg1, zArg2, zArg3, pParse->zAuthContext -#ifdef SQLITE_USER_AUTHENTICATION - ,db->auth.zAuthUser -#endif - ); + rc = db->xAuth(db->pAuthArg,code,zArg1,zArg2,zArg3,pParse->zAuthContext); if( rc==SQLITE_DENY ){ sqlite3ErrorMsg(pParse, "not authorized"); pParse->rc = SQLITE_AUTH; @@ -106815,7 +126516,7 @@ SQLITE_PRIVATE int sqlite3AuthCheck( */ SQLITE_PRIVATE void sqlite3AuthContextPush( Parse *pParse, - AuthContext *pContext, + AuthContext *pContext, const char *zContext ){ assert( pParse ); @@ -106872,13 +126573,13 @@ SQLITE_PRIVATE void sqlite3AuthContextPop(AuthContext *pContext){ */ struct TableLock { int iDb; /* The database containing the table to be locked */ - int iTab; /* The root page of the table to be locked */ + Pgno iTab; /* The root page of the table to be locked */ u8 isWriteLock; /* True for write lock. False for a read lock */ const char *zLockName; /* Name of the table */ }; /* -** Record the fact that we want to lock a table at run-time. +** Record the fact that we want to lock a table at run-time. ** ** The table to be locked has root page iTab and is found in database iDb. ** A read or a write lock can be taken depending on isWritelock. @@ -106887,21 +126588,20 @@ struct TableLock { ** code to make the lock occur is generated by a later call to ** codeTableLocks() which occurs during sqlite3FinishCoding(). */ -SQLITE_PRIVATE void sqlite3TableLock( +static SQLITE_NOINLINE void lockTable( Parse *pParse, /* Parsing context */ int iDb, /* Index of the database containing the table to lock */ - int iTab, /* Root page number of the table to be locked */ + Pgno iTab, /* Root page number of the table to be locked */ u8 isWriteLock, /* True for a write lock */ const char *zName /* Name of the table to be locked */ ){ - Parse *pToplevel = sqlite3ParseToplevel(pParse); + Parse *pToplevel; int i; int nBytes; TableLock *p; assert( iDb>=0 ); - if( iDb==1 ) return; - if( !sqlite3BtreeSharable(pParse->db->aDb[iDb].pBt) ) return; + pToplevel = sqlite3ParseToplevel(pParse); for(i=0; inTableLock; i++){ p = &pToplevel->aTableLock[i]; if( p->iDb==iDb && p->iTab==iTab ){ @@ -106910,6 +126610,7 @@ SQLITE_PRIVATE void sqlite3TableLock( } } + assert( pToplevel->nTableLock < 0x7fff0000 ); nBytes = sizeof(TableLock) * (pToplevel->nTableLock+1); pToplevel->aTableLock = sqlite3DbReallocOrFree(pToplevel->db, pToplevel->aTableLock, nBytes); @@ -106924,6 +126625,17 @@ SQLITE_PRIVATE void sqlite3TableLock( sqlite3OomFault(pToplevel->db); } } +SQLITE_PRIVATE void sqlite3TableLock( + Parse *pParse, /* Parsing context */ + int iDb, /* Index of the database containing the table to lock */ + Pgno iTab, /* Root page number of the table to be locked */ + u8 isWriteLock, /* True for a write lock */ + const char *zName /* Name of the table to be locked */ +){ + if( iDb==1 ) return; + if( !sqlite3BtreeSharable(pParse->db->aDb[iDb].pBt) ) return; + lockTable(pParse, iDb, iTab, isWriteLock, zName); +} /* ** Code an OP_TableLock instruction for each table locked by the @@ -106931,10 +126643,8 @@ SQLITE_PRIVATE void sqlite3TableLock( */ static void codeTableLocks(Parse *pParse){ int i; - Vdbe *pVdbe; - - pVdbe = sqlite3GetVdbe(pParse); - assert( pVdbe!=0 ); /* sqlite3GetVdbe cannot fail: VDBE already allocated */ + Vdbe *pVdbe = pParse->pVdbe; + assert( pVdbe!=0 ); for(i=0; inTableLock; i++){ TableLock *p = &pParse->aTableLock[i]; @@ -106973,34 +126683,56 @@ SQLITE_PRIVATE int sqlite3DbMaskAllZero(yDbMask m){ SQLITE_PRIVATE void sqlite3FinishCoding(Parse *pParse){ sqlite3 *db; Vdbe *v; + int iDb, i; assert( pParse->pToplevel==0 ); db = pParse->db; + assert( db->pParse==pParse ); if( pParse->nested ) return; - if( db->mallocFailed || pParse->nErr ){ - if( pParse->rc==SQLITE_OK ) pParse->rc = SQLITE_ERROR; + if( pParse->nErr ){ + if( db->mallocFailed ) pParse->rc = SQLITE_NOMEM; return; } + assert( db->mallocFailed==0 ); /* Begin by generating some termination code at the end of the ** vdbe program */ - v = sqlite3GetVdbe(pParse); - assert( !pParse->isMultiWrite + v = pParse->pVdbe; + if( v==0 ){ + if( db->init.busy ){ + pParse->rc = SQLITE_DONE; + return; + } + v = sqlite3GetVdbe(pParse); + if( v==0 ) pParse->rc = SQLITE_ERROR; + } + assert( !pParse->isMultiWrite || sqlite3VdbeAssertMayAbort(v, pParse->mayAbort)); if( v ){ - sqlite3VdbeAddOp0(v, OP_Halt); - -#if SQLITE_USER_AUTHENTICATION - if( pParse->nTableLock>0 && db->init.busy==0 ){ - sqlite3UserAuthInit(db); - if( db->auth.authLevelrc = SQLITE_AUTH_USER; - return; + if( pParse->bReturning ){ + Returning *pReturning; + int addrRewind; + int reg; + + assert( !pParse->isCreate ); + pReturning = pParse->u1.d.pReturning; + if( pReturning->nRetCol ){ + sqlite3VdbeAddOp0(v, OP_FkCheck); + addrRewind = + sqlite3VdbeAddOp1(v, OP_Rewind, pReturning->iRetCur); + VdbeCoverage(v); + reg = pReturning->iRetReg; + for(i=0; inRetCol; i++){ + sqlite3VdbeAddOp3(v, OP_Column, pReturning->iRetCur, i, reg+i); + } + sqlite3VdbeAddOp2(v, OP_ResultRow, reg, i); + sqlite3VdbeAddOp2(v, OP_Next, pReturning->iRetCur, addrRewind+1); + VdbeCoverage(v); + sqlite3VdbeJumpHere(v, addrRewind); } } -#endif + sqlite3VdbeAddOp0(v, OP_Halt); /* The cookie mask contains one bit for each database file open. ** (Bit 0 is for main, bit 1 is for temp, and so forth.) Bits are @@ -107008,64 +126740,75 @@ SQLITE_PRIVATE void sqlite3FinishCoding(Parse *pParse){ ** transaction on each used database and to verify the schema cookie ** on each used database. */ - if( db->mallocFailed==0 - && (DbMaskNonZero(pParse->cookieMask) || pParse->pConstExpr) - ){ - int iDb, i; - assert( sqlite3VdbeGetOp(v, 0)->opcode==OP_Init ); - sqlite3VdbeJumpHere(v, 0); - for(iDb=0; iDbnDb; iDb++){ - Schema *pSchema; - if( DbMaskTest(pParse->cookieMask, iDb)==0 ) continue; - sqlite3VdbeUsesBtree(v, iDb); - pSchema = db->aDb[iDb].pSchema; - sqlite3VdbeAddOp4Int(v, - OP_Transaction, /* Opcode */ - iDb, /* P1 */ - DbMaskTest(pParse->writeMask,iDb), /* P2 */ - pSchema->schema_cookie, /* P3 */ - pSchema->iGeneration /* P4 */ - ); - if( db->init.busy==0 ) sqlite3VdbeChangeP5(v, 1); - VdbeComment((v, - "usesStmtJournal=%d", pParse->mayAbort && pParse->isMultiWrite)); - } + assert( pParse->nErr>0 || sqlite3VdbeGetOp(v, 0)->opcode==OP_Init ); + sqlite3VdbeJumpHere(v, 0); + assert( db->nDb>0 ); + iDb = 0; + do{ + Schema *pSchema; + if( DbMaskTest(pParse->cookieMask, iDb)==0 ) continue; + sqlite3VdbeUsesBtree(v, iDb); + pSchema = db->aDb[iDb].pSchema; + sqlite3VdbeAddOp4Int(v, + OP_Transaction, /* Opcode */ + iDb, /* P1 */ + DbMaskTest(pParse->writeMask,iDb), /* P2 */ + pSchema->schema_cookie, /* P3 */ + pSchema->iGeneration /* P4 */ + ); + if( db->init.busy==0 ) sqlite3VdbeChangeP5(v, 1); + VdbeComment((v, + "usesStmtJournal=%d", pParse->mayAbort && pParse->isMultiWrite)); + }while( ++iDbnDb ); #ifndef SQLITE_OMIT_VIRTUALTABLE - for(i=0; inVtabLock; i++){ - char *vtab = (char *)sqlite3GetVTable(db, pParse->apVtabLock[i]); - sqlite3VdbeAddOp4(v, OP_VBegin, 0, 0, 0, vtab, P4_VTAB); - } - pParse->nVtabLock = 0; + for(i=0; inVtabLock; i++){ + char *vtab = (char *)sqlite3GetVTable(db, pParse->apVtabLock[i]); + sqlite3VdbeAddOp4(v, OP_VBegin, 0, 0, 0, vtab, P4_VTAB); + } + pParse->nVtabLock = 0; #endif - /* Once all the cookies have been verified and transactions opened, - ** obtain the required table-locks. This is a no-op unless the - ** shared-cache feature is enabled. - */ - codeTableLocks(pParse); +#ifndef SQLITE_OMIT_SHARED_CACHE + /* Once all the cookies have been verified and transactions opened, + ** obtain the required table-locks. This is a no-op unless the + ** shared-cache feature is enabled. + */ + if( pParse->nTableLock ) codeTableLocks(pParse); +#endif - /* Initialize any AUTOINCREMENT data structures required. - */ - sqlite3AutoincrementBegin(pParse); + /* Initialize any AUTOINCREMENT data structures required. + */ + if( pParse->pAinc ) sqlite3AutoincrementBegin(pParse); - /* Code constant expressions that where factored out of inner loops */ - if( pParse->pConstExpr ){ - ExprList *pEL = pParse->pConstExpr; - pParse->okConstFactor = 0; - for(i=0; inExpr; i++){ - sqlite3ExprCode(pParse, pEL->a[i].pExpr, pEL->a[i].u.iConstExprReg); - } + /* Code constant expressions that were factored out of inner loops. + */ + if( pParse->pConstExpr ){ + ExprList *pEL = pParse->pConstExpr; + pParse->okConstFactor = 0; + for(i=0; inExpr; i++){ + assert( pEL->a[i].u.iConstExprReg>0 ); + sqlite3ExprCode(pParse, pEL->a[i].pExpr, pEL->a[i].u.iConstExprReg); } + } - /* Finally, jump back to the beginning of the executable code. */ - sqlite3VdbeGoto(v, 1); + if( pParse->bReturning ){ + Returning *pRet; + assert( !pParse->isCreate ); + pRet = pParse->u1.d.pReturning; + if( pRet->nRetCol ){ + sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pRet->iRetCur, pRet->nRetCol); + } } - } + /* Finally, jump back to the beginning of the executable code. */ + sqlite3VdbeGoto(v, 1); + } /* Get the VDBE program ready for execution */ - if( v && pParse->nErr==0 && !db->mallocFailed ){ + assert( v!=0 || pParse->nErr ); + assert( db->mallocFailed==0 || pParse->nErr ); + if( pParse->nErr==0 ){ /* A minimum of one cursor is required if autoincrement is used * See ticket [a696379c1f08866] */ assert( pParse->pAinc==0 || pParse->nTab>0 ); @@ -107079,23 +126822,25 @@ SQLITE_PRIVATE void sqlite3FinishCoding(Parse *pParse){ /* ** Run the parser and code generator recursively in order to generate ** code for the SQL statement given onto the end of the pParse context -** currently under construction. When the parser is run recursively -** this way, the final OP_Halt is not appended and other initialization -** and finalization steps are omitted because those are handling by the -** outermost parser. +** currently under construction. Notes: +** +** * The final OP_Halt is not appended and other initialization +** and finalization steps are omitted because those are handling by the +** outermost parser. ** -** Not everything is nestable. This facility is designed to permit -** INSERT, UPDATE, and DELETE operations against SQLITE_MASTER. Use -** care if you decide to try to use this routine for some other purposes. +** * Built-in SQL functions always take precedence over application-defined +** SQL functions. In other words, it is not possible to override a +** built-in function. */ SQLITE_PRIVATE void sqlite3NestedParse(Parse *pParse, const char *zFormat, ...){ va_list ap; char *zSql; - char *zErrMsg = 0; sqlite3 *db = pParse->db; + u32 savedDbFlags = db->mDbFlags; char saveBuf[PARSE_TAIL_SZ]; if( pParse->nErr ) return; + if( pParse->eParseMode ) return; assert( pParse->nested<10 ); /* Nesting should only be of limited depth */ va_start(ap, zFormat); zSql = sqlite3VMPrintf(db, zFormat, ap); @@ -107111,23 +126856,14 @@ SQLITE_PRIVATE void sqlite3NestedParse(Parse *pParse, const char *zFormat, ...){ pParse->nested++; memcpy(saveBuf, PARSE_TAIL(pParse), PARSE_TAIL_SZ); memset(PARSE_TAIL(pParse), 0, PARSE_TAIL_SZ); - sqlite3RunParser(pParse, zSql, &zErrMsg); - sqlite3DbFree(db, zErrMsg); + db->mDbFlags |= DBFLAG_PreferBuiltin; + sqlite3RunParser(pParse, zSql); + db->mDbFlags = savedDbFlags; sqlite3DbFree(db, zSql); memcpy(PARSE_TAIL(pParse), saveBuf, PARSE_TAIL_SZ); pParse->nested--; } -#if SQLITE_USER_AUTHENTICATION -/* -** Return TRUE if zTable is the name of the system table that stores the -** list of users and their access credentials. -*/ -SQLITE_PRIVATE int sqlite3UserAuthTable(const char *zTable){ - return sqlite3_stricmp(zTable, "sqlite_user")==0; -} -#endif - /* ** Locate the in-memory structure that describes a particular database ** table given the name of that table and (optionally) the name of the @@ -107146,29 +126882,59 @@ SQLITE_PRIVATE Table *sqlite3FindTable(sqlite3 *db, const char *zName, const cha /* All mutexes are required for schema access. Make sure we hold them. */ assert( zDatabase!=0 || sqlite3BtreeHoldsAllMutexes(db) ); -#if SQLITE_USER_AUTHENTICATION - /* Only the admin user is allowed to know that the sqlite_user table - ** exists */ - if( db->auth.authLevelnDb; i++){ - int j = (i<2) ? i^1 : i; /* Search TEMP before MAIN */ - if( zDatabase==0 || sqlite3StrICmp(zDatabase, db->aDb[j].zDbSName)==0 ){ - assert( sqlite3SchemaMutexHeld(db, j, 0) ); - p = sqlite3HashFind(&db->aDb[j].pSchema->tblHash, zName); - if( p ) return p; + if( zDatabase ){ + for(i=0; inDb; i++){ + if( sqlite3StrICmp(zDatabase, db->aDb[i].zDbSName)==0 ) break; + } + if( i>=db->nDb ){ + /* No match against the official names. But always match "main" + ** to schema 0 as a legacy fallback. */ + if( sqlite3StrICmp(zDatabase,"main")==0 ){ + i = 0; + }else{ + return 0; + } + } + p = sqlite3HashFind(&db->aDb[i].pSchema->tblHash, zName); + if( p==0 && sqlite3StrNICmp(zName, "sqlite_", 7)==0 ){ + if( i==1 ){ + if( sqlite3StrICmp(zName+7, &PREFERRED_TEMP_SCHEMA_TABLE[7])==0 + || sqlite3StrICmp(zName+7, &PREFERRED_SCHEMA_TABLE[7])==0 + || sqlite3StrICmp(zName+7, &LEGACY_SCHEMA_TABLE[7])==0 + ){ + p = sqlite3HashFind(&db->aDb[1].pSchema->tblHash, + LEGACY_TEMP_SCHEMA_TABLE); + } + }else{ + if( sqlite3StrICmp(zName+7, &PREFERRED_SCHEMA_TABLE[7])==0 ){ + p = sqlite3HashFind(&db->aDb[i].pSchema->tblHash, + LEGACY_SCHEMA_TABLE); + } + } + } + }else{ + /* Match against TEMP first */ + p = sqlite3HashFind(&db->aDb[1].pSchema->tblHash, zName); + if( p ) return p; + /* The main database is second */ + p = sqlite3HashFind(&db->aDb[0].pSchema->tblHash, zName); + if( p ) return p; + /* Attached databases are in order of attachment */ + for(i=2; inDb; i++){ + assert( sqlite3SchemaMutexHeld(db, i, 0) ); + p = sqlite3HashFind(&db->aDb[i].pSchema->tblHash, zName); + if( p ) break; + } + if( p==0 && sqlite3StrNICmp(zName, "sqlite_", 7)==0 ){ + if( sqlite3StrICmp(zName+7, &PREFERRED_SCHEMA_TABLE[7])==0 ){ + p = sqlite3HashFind(&db->aDb[0].pSchema->tblHash, LEGACY_SCHEMA_TABLE); + }else if( sqlite3StrICmp(zName+7, &PREFERRED_TEMP_SCHEMA_TABLE[7])==0 ){ + p = sqlite3HashFind(&db->aDb[1].pSchema->tblHash, + LEGACY_TEMP_SCHEMA_TABLE); } } - /* Not found. If the name we were looking for was temp.sqlite_master - ** then change the name to sqlite_temp_master and try again. */ - if( sqlite3StrICmp(zName, MASTER_NAME)!=0 ) break; - if( sqlite3_stricmp(zDatabase, db->aDb[1].zDbSName)!=0 ) break; - zName = TEMP_MASTER_NAME; } - return 0; + return p; } /* @@ -107192,7 +126958,7 @@ SQLITE_PRIVATE Table *sqlite3LocateTable( /* Read the database schema. If an error occurs, leave an error message ** and code in pParse and return NULL. */ - if( (db->mDbFlags & DBFLAG_SchemaKnownOk)==0 + if( (db->mDbFlags & DBFLAG_SchemaKnownOk)==0 && SQLITE_OK!=sqlite3ReadSchema(pParse) ){ return 0; @@ -107204,19 +126970,30 @@ SQLITE_PRIVATE Table *sqlite3LocateTable( /* If zName is the not the name of a table in the schema created using ** CREATE, then check to see if it is the name of an virtual table that ** can be an eponymous virtual table. */ - if( pParse->disableVtab==0 ){ + if( (pParse->prepFlags & SQLITE_PREPARE_NO_VTAB)==0 && db->init.busy==0 ){ Module *pMod = (Module*)sqlite3HashFind(&db->aModule, zName); if( pMod==0 && sqlite3_strnicmp(zName, "pragma_", 7)==0 ){ pMod = sqlite3PragmaVtabRegister(db, zName); } +#ifndef SQLITE_OMIT_JSON + if( pMod==0 && sqlite3_strnicmp(zName, "json", 4)==0 ){ + pMod = sqlite3JsonVtabRegister(db, zName); + } +#endif +#ifdef SQLITE_ENABLE_CARRAY + if( pMod==0 && sqlite3_stricmp(zName, "carray")==0 ){ + pMod = sqlite3CarrayRegister(db); + } +#endif if( pMod && sqlite3VtabEponymousTableInit(pParse, pMod) ){ + testcase( pMod->pEpoTab==0 ); return pMod->pEpoTab; } } #endif if( flags & LOCATE_NOERR ) return 0; pParse->checkSchema = 1; - }else if( IsVirtual(p) && pParse->disableVtab ){ + }else if( IsVirtual(p) && (pParse->prepFlags & SQLITE_PREPARE_NO_VTAB)!=0 ){ p = 0; } @@ -107227,6 +127004,8 @@ SQLITE_PRIVATE Table *sqlite3LocateTable( }else{ sqlite3ErrorMsg(pParse, "%s: %s", zMsg, zName); } + }else{ + assert( HasRowid(p) || p->iPKey<0 ); } return p; @@ -107242,23 +127021,40 @@ SQLITE_PRIVATE Table *sqlite3LocateTable( ** sqlite3FixSrcList() for details. */ SQLITE_PRIVATE Table *sqlite3LocateTableItem( - Parse *pParse, + Parse *pParse, u32 flags, - struct SrcList_item *p + SrcItem *p ){ const char *zDb; - assert( p->pSchema==0 || p->zDatabase==0 ); - if( p->pSchema ){ - int iDb = sqlite3SchemaToIndex(pParse->db, p->pSchema); + if( p->fg.fixedSchema ){ + int iDb = sqlite3SchemaToIndex(pParse->db, p->u4.pSchema); + assert( iDb>=0 && iDbdb->nDb ); zDb = pParse->db->aDb[iDb].zDbSName; }else{ - zDb = p->zDatabase; + assert( !p->fg.isSubquery ); + zDb = p->u4.zDatabase; } return sqlite3LocateTable(pParse, flags, p->zName, zDb); } /* -** Locate the in-memory structure that describes +** Return the preferred table name for system tables. Translate legacy +** names into the new preferred names, as appropriate. +*/ +SQLITE_PRIVATE const char *sqlite3PreferredTableName(const char *zName){ + if( sqlite3StrNICmp(zName, "sqlite_", 7)==0 ){ + if( sqlite3StrICmp(zName+7, &LEGACY_SCHEMA_TABLE[7])==0 ){ + return PREFERRED_SCHEMA_TABLE; + } + if( sqlite3StrICmp(zName+7, &LEGACY_TEMP_SCHEMA_TABLE[7])==0 ){ + return PREFERRED_TEMP_SCHEMA_TABLE; + } + } + return zName; +} + +/* +** Locate the in-memory structure that describes ** a particular index given the name of that index ** and the name of the database that contains the index. ** Return NULL if not found. @@ -107278,7 +127074,7 @@ SQLITE_PRIVATE Index *sqlite3FindIndex(sqlite3 *db, const char *zName, const cha int j = (i<2) ? i^1 : i; /* Search TEMP before MAIN */ Schema *pSchema = db->aDb[j].pSchema; assert( pSchema ); - if( zDb && sqlite3StrICmp(zDb, db->aDb[j].zDbSName) ) continue; + if( zDb && sqlite3DbIsNamed(db, j, zDb)==0 ) continue; assert( sqlite3SchemaMutexHeld(db, j, 0) ); p = sqlite3HashFind(&pSchema->idxHash, zName); if( p ) break; @@ -107297,7 +127093,7 @@ SQLITE_PRIVATE void sqlite3FreeIndex(sqlite3 *db, Index *p){ sqlite3ExprListDelete(db, p->aColExpr); sqlite3DbFree(db, p->zColAff); if( p->isResized ) sqlite3DbFree(db, (void *)p->azColl); -#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 +#ifdef SQLITE_ENABLE_STAT4 sqlite3_free(p->aiRowEst); #endif sqlite3DbFree(db, p); @@ -107421,6 +127217,84 @@ SQLITE_PRIVATE void sqlite3CommitInternalChanges(sqlite3 *db){ db->mDbFlags &= ~DBFLAG_SchemaChange; } +/* +** Set the expression associated with a column. This is usually +** the DEFAULT value, but might also be the expression that computes +** the value for a generated column. +*/ +SQLITE_PRIVATE void sqlite3ColumnSetExpr( + Parse *pParse, /* Parsing context */ + Table *pTab, /* The table containing the column */ + Column *pCol, /* The column to receive the new DEFAULT expression */ + Expr *pExpr /* The new default expression */ +){ + ExprList *pList; + assert( IsOrdinaryTable(pTab) ); + pList = pTab->u.tab.pDfltList; + if( pCol->iDflt==0 + || NEVER(pList==0) + || NEVER(pList->nExpriDflt) + ){ + pCol->iDflt = pList==0 ? 1 : pList->nExpr+1; + pTab->u.tab.pDfltList = sqlite3ExprListAppend(pParse, pList, pExpr); + }else{ + sqlite3ExprDelete(pParse->db, pList->a[pCol->iDflt-1].pExpr); + pList->a[pCol->iDflt-1].pExpr = pExpr; + } +} + +/* +** Return the expression associated with a column. The expression might be +** the DEFAULT clause or the AS clause of a generated column. +** Return NULL if the column has no associated expression. +*/ +SQLITE_PRIVATE Expr *sqlite3ColumnExpr(Table *pTab, Column *pCol){ + if( pCol->iDflt==0 ) return 0; + if( !IsOrdinaryTable(pTab) ) return 0; + if( NEVER(pTab->u.tab.pDfltList==0) ) return 0; + if( NEVER(pTab->u.tab.pDfltList->nExpriDflt) ) return 0; + return pTab->u.tab.pDfltList->a[pCol->iDflt-1].pExpr; +} + +/* +** Set the collating sequence name for a column. +*/ +SQLITE_PRIVATE void sqlite3ColumnSetColl( + sqlite3 *db, + Column *pCol, + const char *zColl +){ + i64 nColl; + i64 n; + char *zNew; + assert( zColl!=0 ); + n = sqlite3Strlen30(pCol->zCnName) + 1; + if( pCol->colFlags & COLFLAG_HASTYPE ){ + n += sqlite3Strlen30(pCol->zCnName+n) + 1; + } + nColl = sqlite3Strlen30(zColl) + 1; + zNew = sqlite3DbRealloc(db, pCol->zCnName, nColl+n); + if( zNew ){ + pCol->zCnName = zNew; + memcpy(pCol->zCnName + n, zColl, nColl); + pCol->colFlags |= COLFLAG_HASCOLL; + } +} + +/* +** Return the collating sequence name for a column +*/ +SQLITE_PRIVATE const char *sqlite3ColumnColl(Column *pCol){ + const char *z; + if( (pCol->colFlags & COLFLAG_HASCOLL)==0 ) return 0; + z = pCol->zCnName; + while( *z ){ z++; } + if( pCol->colFlags & COLFLAG_HASTYPE ){ + do{ z++; }while( *z ); + } + return z+1; +} + /* ** Delete memory allocated for the column names of a table or view (the ** Table.aCol[] array). @@ -107429,13 +127303,23 @@ SQLITE_PRIVATE void sqlite3DeleteColumnNames(sqlite3 *db, Table *pTable){ int i; Column *pCol; assert( pTable!=0 ); + assert( db!=0 ); if( (pCol = pTable->aCol)!=0 ){ for(i=0; inCol; i++, pCol++){ - sqlite3DbFree(db, pCol->zName); - sqlite3ExprDelete(db, pCol->pDflt); - sqlite3DbFree(db, pCol->zColl); + assert( pCol->zCnName==0 || pCol->hName==sqlite3StrIHash(pCol->zCnName) ); + sqlite3DbFree(db, pCol->zCnName); + } + sqlite3DbNNFreeNN(db, pTable->aCol); + if( IsOrdinaryTable(pTable) ){ + sqlite3ExprListDelete(db, pTable->u.tab.pDfltList); + } + if( db->pnBytesFreed==0 ){ + pTable->aCol = 0; + pTable->nCol = 0; + if( IsOrdinaryTable(pTable) ){ + pTable->u.tab.pDfltList = 0; + } } - sqlite3DbFree(db, pTable->aCol); } } @@ -107445,10 +127329,10 @@ SQLITE_PRIVATE void sqlite3DeleteColumnNames(sqlite3 *db, Table *pTable){ ** ** This routine just deletes the data structure. It does not unlink ** the table data structure from the hash table. But it does destroy -** memory structures of the indices and foreign keys associated with +** memory structures of the indices and foreign keys associated with ** the table. ** -** The db parameter is optional. It is needed if the Table object +** The db parameter is optional. It is needed if the Table object ** contains lookaside memory. (Table objects in the schema do not use ** lookaside memory, but some ephemeral Table objects do.) Or the ** db parameter can be used with db->pnBytesFreed to measure the memory @@ -107459,10 +127343,15 @@ static void SQLITE_NOINLINE deleteTable(sqlite3 *db, Table *pTable){ #ifdef SQLITE_DEBUG /* Record the number of outstanding lookaside allocations in schema Tables - ** prior to doing any free() operations. Since schema Tables do not use - ** lookaside, this number should not change. */ + ** prior to doing any free() operations. Since schema Tables do not use + ** lookaside, this number should not change. + ** + ** If malloc has already failed, it may be that it failed while allocating + ** a Table object that was going to be marked ephemeral. So do not check + ** that no lookaside memory is used in this case either. */ int nLookaside = 0; - if( db && (pTable->tabFlags & TF_Ephemeral)==0 ){ + assert( db!=0 ); + if( !db->mallocFailed && (pTable->tabFlags & TF_Ephemeral)==0 ){ nLookaside = sqlite3LookasideUsed(db, 0); } #endif @@ -107472,8 +127361,8 @@ static void SQLITE_NOINLINE deleteTable(sqlite3 *db, Table *pTable){ pNext = pIndex->pNext; assert( pIndex->pSchema==pTable->pSchema || (IsVirtual(pTable) && pIndex->idxType!=SQLITE_IDXTYPE_APPDEF) ); - if( (db==0 || db->pnBytesFreed==0) && !IsVirtual(pTable) ){ - char *zName = pIndex->zName; + if( db->pnBytesFreed==0 && !IsVirtual(pTable) ){ + char *zName = pIndex->zName; TESTONLY ( Index *pOld = ) sqlite3HashInsert( &pIndex->pSchema->idxHash, zName, 0 ); @@ -107483,19 +127372,25 @@ static void SQLITE_NOINLINE deleteTable(sqlite3 *db, Table *pTable){ sqlite3FreeIndex(db, pIndex); } - /* Delete any foreign keys attached to this table. */ - sqlite3FkDelete(db, pTable); + if( IsOrdinaryTable(pTable) ){ + sqlite3FkDelete(db, pTable); + } +#ifndef SQLITE_OMIT_VIRTUALTABLE + else if( IsVirtual(pTable) ){ + sqlite3VtabClear(db, pTable); + } +#endif + else{ + assert( IsView(pTable) ); + sqlite3SelectDelete(db, pTable->u.view.pSelect); + } /* Delete the Table structure itself. */ sqlite3DeleteColumnNames(db, pTable); sqlite3DbFree(db, pTable->zName); sqlite3DbFree(db, pTable->zColAff); - sqlite3SelectDelete(db, pTable->pSelect); sqlite3ExprListDelete(db, pTable->pCheck); -#ifndef SQLITE_OMIT_VIRTUALTABLE - sqlite3VtabClear(db, pTable); -#endif sqlite3DbFree(db, pTable); /* Verify that no lookaside memory was used by schema tables */ @@ -107503,10 +127398,14 @@ static void SQLITE_NOINLINE deleteTable(sqlite3 *db, Table *pTable){ } SQLITE_PRIVATE void sqlite3DeleteTable(sqlite3 *db, Table *pTable){ /* Do not delete the table until the reference count reaches zero. */ + assert( db!=0 ); if( !pTable ) return; - if( ((!db || db->pnBytesFreed==0) && (--pTable->nTabRef)>0) ) return; + if( db->pnBytesFreed==0 && (--pTable->nTabRef)>0 ) return; deleteTable(db, pTable); } +SQLITE_PRIVATE void sqlite3DeleteTableGeneric(sqlite3 *db, void *pTable){ + sqlite3DeleteTable(db, (Table*)pTable); +} /* @@ -107541,10 +127440,10 @@ SQLITE_PRIVATE void sqlite3UnlinkAndDeleteTable(sqlite3 *db, int iDb, const char ** are not \000 terminated and are not persistent. The returned string ** is \000 terminated and is persistent. */ -SQLITE_PRIVATE char *sqlite3NameFromToken(sqlite3 *db, Token *pName){ +SQLITE_PRIVATE char *sqlite3NameFromToken(sqlite3 *db, const Token *pName){ char *zName; if( pName ){ - zName = sqlite3DbStrNDup(db, (char*)pName->z, pName->n); + zName = sqlite3DbStrNDup(db, (const char*)pName->z, pName->n); sqlite3Dequote(zName); }else{ zName = 0; @@ -107553,13 +127452,13 @@ SQLITE_PRIVATE char *sqlite3NameFromToken(sqlite3 *db, Token *pName){ } /* -** Open the sqlite_master table stored in database number iDb for +** Open the sqlite_schema table stored in database number iDb for ** writing. The table is opened using cursor 0. */ -SQLITE_PRIVATE void sqlite3OpenMasterTable(Parse *p, int iDb){ +SQLITE_PRIVATE void sqlite3OpenSchemaTable(Parse *p, int iDb){ Vdbe *v = sqlite3GetVdbe(p); - sqlite3TableLock(p, iDb, MASTER_ROOT, 1, MASTER_NAME); - sqlite3VdbeAddOp4Int(v, OP_OpenWrite, 0, MASTER_ROOT, iDb, 5); + sqlite3TableLock(p, iDb, SCHEMA_ROOT, 1, LEGACY_SCHEMA_TABLE); + sqlite3VdbeAddOp4Int(v, OP_OpenWrite, 0, SCHEMA_ROOT, iDb, 5); if( p->nTab==0 ){ p->nTab = 1; } @@ -107588,7 +127487,7 @@ SQLITE_PRIVATE int sqlite3FindDbName(sqlite3 *db, const char *zName){ /* ** The token *pName contains the name of a database (either "main" or ** "temp" or the name of an attached db). This routine returns the -** index of the named database in db->aDb[], or -1 if the named db +** index of the named database in db->aDb[], or -1 if the named db ** does not exist. */ SQLITE_PRIVATE int sqlite3FindDb(sqlite3 *db, Token *pName){ @@ -107604,7 +127503,7 @@ SQLITE_PRIVATE int sqlite3FindDb(sqlite3 *db, Token *pName){ ** pName1 and pName2. If the table name was fully qualified, for example: ** ** CREATE TABLE xxx.yyy (...); -** +** ** Then pName1 is set to "xxx" and pName2 "yyy". On the other hand if ** the table name is not fully qualified, i.e.: ** @@ -107638,7 +127537,7 @@ SQLITE_PRIVATE int sqlite3TwoPartName( return -1; } }else{ - assert( db->init.iDb==0 || db->init.busy || IN_RENAME_OBJECT + assert( db->init.iDb==0 || db->init.busy || IN_SPECIAL_PARSE || (db->mDbFlags & DBFLAG_Vacuum)!=0); iDb = db->init.iDb; *pUnqual = pName1; @@ -107666,13 +127565,42 @@ SQLITE_PRIVATE int sqlite3WritableSchema(sqlite3 *db){ ** trigger). All names are legal except those that begin with the string ** "sqlite_" (in upper, lower or mixed case). This portion of the namespace ** is reserved for internal use. +** +** When parsing the sqlite_schema table, this routine also checks to +** make sure the "type", "name", and "tbl_name" columns are consistent +** with the SQL. */ -SQLITE_PRIVATE int sqlite3CheckObjectName(Parse *pParse, const char *zName){ - if( !pParse->db->init.busy && pParse->nested==0 - && sqlite3WritableSchema(pParse->db)==0 - && 0==sqlite3StrNICmp(zName, "sqlite_", 7) ){ - sqlite3ErrorMsg(pParse, "object name reserved for internal use: %s", zName); - return SQLITE_ERROR; +SQLITE_PRIVATE int sqlite3CheckObjectName( + Parse *pParse, /* Parsing context */ + const char *zName, /* Name of the object to check */ + const char *zType, /* Type of this object */ + const char *zTblName /* Parent table name for triggers and indexes */ +){ + sqlite3 *db = pParse->db; + if( sqlite3WritableSchema(db) + || db->init.imposterTable + || !sqlite3Config.bExtraSchemaChecks + ){ + /* Skip these error checks for writable_schema=ON */ + return SQLITE_OK; + } + if( db->init.busy ){ + if( sqlite3_stricmp(zType, db->init.azInit[0]) + || sqlite3_stricmp(zName, db->init.azInit[1]) + || sqlite3_stricmp(zTblName, db->init.azInit[2]) + ){ + sqlite3ErrorMsg(pParse, ""); /* corruptSchema() will supply the error */ + return SQLITE_ERROR; + } + }else{ + if( (pParse->nested==0 && 0==sqlite3StrNICmp(zName, "sqlite_", 7)) + || (sqlite3ReadOnlyShadowTables(db) && sqlite3ShadowTableName(db, zName)) + ){ + sqlite3ErrorMsg(pParse, "object name reserved for internal use: %s", + zName); + return SQLITE_ERROR; + } + } return SQLITE_OK; } @@ -107687,17 +127615,120 @@ SQLITE_PRIVATE Index *sqlite3PrimaryKeyIndex(Table *pTab){ } /* -** Return the column of index pIdx that corresponds to table -** column iCol. Return -1 if not found. +** Convert an table column number into a index column number. That is, +** for the column iCol in the table (as defined by the CREATE TABLE statement) +** find the (first) offset of that column in index pIdx. Or return -1 +** if column iCol is not used in index pIdx. */ -SQLITE_PRIVATE i16 sqlite3ColumnOfIndex(Index *pIdx, i16 iCol){ +SQLITE_PRIVATE int sqlite3TableColumnToIndex(Index *pIdx, int iCol){ int i; + i16 iCol16; + assert( iCol>=(-1) && iCol<=SQLITE_MAX_COLUMN ); + assert( pIdx->nColumn<=SQLITE_MAX_COLUMN*2 ); + iCol16 = iCol; for(i=0; inColumn; i++){ - if( iCol==pIdx->aiColumn[i] ) return i; + if( iCol16==pIdx->aiColumn[i] ){ + return i; + } } return -1; } +#ifndef SQLITE_OMIT_GENERATED_COLUMNS +/* Convert a storage column number into a table column number. +** +** The storage column number (0,1,2,....) is the index of the value +** as it appears in the record on disk. The true column number +** is the index (0,1,2,...) of the column in the CREATE TABLE statement. +** +** The storage column number is less than the table column number if +** and only there are VIRTUAL columns to the left. +** +** If SQLITE_OMIT_GENERATED_COLUMNS, this routine is a no-op macro. +*/ +SQLITE_PRIVATE i16 sqlite3StorageColumnToTable(Table *pTab, i16 iCol){ + if( pTab->tabFlags & TF_HasVirtual ){ + int i; + for(i=0; i<=iCol; i++){ + if( pTab->aCol[i].colFlags & COLFLAG_VIRTUAL ) iCol++; + } + } + return iCol; +} +#endif + +#ifndef SQLITE_OMIT_GENERATED_COLUMNS +/* Convert a table column number into a storage column number. +** +** The storage column number (0,1,2,....) is the index of the value +** as it appears in the record on disk. Or, if the input column is +** the N-th virtual column (zero-based) then the storage number is +** the number of non-virtual columns in the table plus N. +** +** The true column number is the index (0,1,2,...) of the column in +** the CREATE TABLE statement. +** +** If the input column is a VIRTUAL column, then it should not appear +** in storage. But the value sometimes is cached in registers that +** follow the range of registers used to construct storage. This +** avoids computing the same VIRTUAL column multiple times, and provides +** values for use by OP_Param opcodes in triggers. Hence, if the +** input column is a VIRTUAL table, put it after all the other columns. +** +** In the following, N means "normal column", S means STORED, and +** V means VIRTUAL. Suppose the CREATE TABLE has columns like this: +** +** CREATE TABLE ex(N,S,V,N,S,V,N,S,V); +** -- 0 1 2 3 4 5 6 7 8 +** +** Then the mapping from this function is as follows: +** +** INPUTS: 0 1 2 3 4 5 6 7 8 +** OUTPUTS: 0 1 6 2 3 7 4 5 8 +** +** So, in other words, this routine shifts all the virtual columns to +** the end. +** +** If SQLITE_OMIT_GENERATED_COLUMNS then there are no virtual columns and +** this routine is a no-op macro. If the pTab does not have any virtual +** columns, then this routine is no-op that always return iCol. If iCol +** is negative (indicating the ROWID column) then this routine return iCol. +*/ +SQLITE_PRIVATE i16 sqlite3TableColumnToStorage(Table *pTab, i16 iCol){ + int i; + i16 n; + assert( iColnCol ); + if( (pTab->tabFlags & TF_HasVirtual)==0 || iCol<0 ) return iCol; + for(i=0, n=0; iaCol[i].colFlags & COLFLAG_VIRTUAL)==0 ) n++; + } + if( pTab->aCol[i].colFlags & COLFLAG_VIRTUAL ){ + /* iCol is a virtual column itself */ + return pTab->nNVCol + i - n; + }else{ + /* iCol is a normal or stored column */ + return n; + } +} +#endif + +/* +** Insert a single OP_JournalMode query opcode in order to force the +** prepared statement to return false for sqlite3_stmt_readonly(). This +** is used by CREATE TABLE IF NOT EXISTS and similar if the table already +** exists, so that the prepared statement for CREATE TABLE IF NOT EXISTS +** will return false for sqlite3_stmt_readonly() even if that statement +** is a read-only no-op. +*/ +static void sqlite3ForceNotReadOnly(Parse *pParse){ + int iReg = ++pParse->nMem; + Vdbe *v = sqlite3GetVdbe(pParse); + if( v ){ + sqlite3VdbeAddOp3(v, OP_JournalMode, 0, iReg, PAGER_JOURNALMODE_QUERY); + sqlite3VdbeUsesBtree(v, 0); + } +} + /* ** Begin constructing a new table representation in memory. This is ** the first of several action routines that get called in response @@ -107731,7 +127762,7 @@ SQLITE_PRIVATE void sqlite3StartTable( Token *pName; /* Unqualified name of the table to create */ if( db->init.busy && db->init.newTnum==1 ){ - /* Special case: Parsing the sqlite_master or sqlite_temp_master schema */ + /* Special case: Parsing the sqlite_schema or sqlite_temp_schema schema */ iDb = db->init.iDb; zName = sqlite3DbStrDup(db, SCHEMA_TABLE(iDb)); pName = pName1; @@ -107740,7 +127771,7 @@ SQLITE_PRIVATE void sqlite3StartTable( iDb = sqlite3TwoPartName(pParse, pName1, pName2, &pName); if( iDb<0 ) return; if( !OMIT_TEMPDB && isTemp && pName2->n>0 && iDb!=1 ){ - /* If creating a temp table, the name may not be qualified. Unless + /* If creating a temp table, the name may not be qualified. Unless ** the database name is "temp" anyway. */ sqlite3ErrorMsg(pParse, "temporary table name must be unqualified"); return; @@ -107753,7 +127784,7 @@ SQLITE_PRIVATE void sqlite3StartTable( } pParse->sNameToken = *pName; if( zName==0 ) return; - if( SQLITE_OK!=sqlite3CheckObjectName(pParse, zName) ){ + if( sqlite3CheckObjectName(pParse, zName, isView?"view":"table", zName) ){ goto begin_table_error; } if( db->init.iDb==1 ) isTemp = 1; @@ -107793,10 +127824,12 @@ SQLITE_PRIVATE void sqlite3StartTable( pTable = sqlite3FindTable(db, zName, zDb); if( pTable ){ if( !noErr ){ - sqlite3ErrorMsg(pParse, "table %T already exists", pName); + sqlite3ErrorMsg(pParse, "%s %T already exists", + (IsView(pTable)? "view" : "table"), pName); }else{ assert( !db->init.busy || CORRUPT_DB ); sqlite3CodeVerifySchema(pParse, iDb); + sqlite3ForceNotReadOnly(pParse); } goto begin_table_error; } @@ -107825,22 +127858,11 @@ SQLITE_PRIVATE void sqlite3StartTable( assert( pParse->pNewTable==0 ); pParse->pNewTable = pTable; - /* If this is the magic sqlite_sequence table used by autoincrement, - ** then record a pointer to this table in the main database structure - ** so that INSERT can find the table easily. - */ -#ifndef SQLITE_OMIT_AUTOINCREMENT - if( !pParse->nested && strcmp(zName, "sqlite_sequence")==0 ){ - assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); - pTable->pSchema->pSeqTab = pTable; - } -#endif - /* Begin generating the code that will insert the table record into - ** the SQLITE_MASTER table. Note in particular that we must go ahead + ** the schema table. Note in particular that we must go ahead ** and allocate the record number for the table entry now. Before any ** PRIMARY KEY or UNIQUE keywords are parsed. Those keywords will cause - ** indices to be created and the table record must come before the + ** indices to be created and the table record must come before the ** indices. Hence, the record number for the table must be allocated ** now. */ @@ -107858,11 +127880,12 @@ SQLITE_PRIVATE void sqlite3StartTable( } #endif - /* If the file format and encoding in the database have not been set, + /* If the file format and encoding in the database have not been set, ** set them now. */ - reg1 = pParse->regRowid = ++pParse->nMem; - reg2 = pParse->regRoot = ++pParse->nMem; + assert( pParse->isCreate ); + reg1 = pParse->u1.cr.regRowid = ++pParse->nMem; + reg2 = pParse->u1.cr.regRoot = ++pParse->nMem; reg3 = ++pParse->nMem; sqlite3VdbeAddOp3(v, OP_ReadCookie, iDb, reg3, BTREE_FILE_FORMAT); sqlite3VdbeUsesBtree(v, iDb); @@ -107873,12 +127896,12 @@ SQLITE_PRIVATE void sqlite3StartTable( sqlite3VdbeAddOp3(v, OP_SetCookie, iDb, BTREE_TEXT_ENCODING, ENC(db)); sqlite3VdbeJumpHere(v, addr1); - /* This just creates a place-holder record in the sqlite_master table. + /* This just creates a place-holder record in the sqlite_schema table. ** The record created does not contain anything yet. It will be replaced ** by the real entry in code generated at sqlite3EndTable(). ** - ** The rowid for the new entry is left in register pParse->regRowid. - ** The root page number of the new table is left in reg pParse->regRoot. + ** The rowid for the new entry is left in register pParse->u1.cr.regRowid. + ** The root page of the new table is left in reg pParse->u1.cr.regRoot. ** The rowid and root page number values are needed by the code that ** sqlite3EndTable will generate. */ @@ -107888,15 +127911,19 @@ SQLITE_PRIVATE void sqlite3StartTable( }else #endif { - pParse->addrCrTab = + assert( !pParse->bReturning ); + pParse->u1.cr.addrCrTab = sqlite3VdbeAddOp3(v, OP_CreateBtree, iDb, reg2, BTREE_INTKEY); } - sqlite3OpenMasterTable(pParse, iDb); + sqlite3OpenSchemaTable(pParse, iDb); sqlite3VdbeAddOp2(v, OP_NewRowid, 0, reg1); sqlite3VdbeAddOp4(v, OP_Blob, 6, reg3, 0, nullRow, P4_STATIC); sqlite3VdbeAddOp3(v, OP_Insert, 0, reg3, reg1); sqlite3VdbeChangeP5(v, OPFLAG_APPEND); sqlite3VdbeAddOp0(v, OP_Close); + }else if( db->init.imposterTable ){ + pTable->tabFlags |= TF_Imposter; + if( db->init.imposterTable>=2 ) pTable->tabFlags |= TF_Readonly; } /* Normal (non-error) return. */ @@ -107904,6 +127931,7 @@ SQLITE_PRIVATE void sqlite3StartTable( /* If an error occurs, we jump here */ begin_table_error: + pParse->checkSchema = 1; sqlite3DbFree(db, zName); return; } @@ -107913,14 +127941,85 @@ SQLITE_PRIVATE void sqlite3StartTable( */ #if SQLITE_ENABLE_HIDDEN_COLUMNS SQLITE_PRIVATE void sqlite3ColumnPropertiesFromName(Table *pTab, Column *pCol){ - if( sqlite3_strnicmp(pCol->zName, "__hidden__", 10)==0 ){ + if( sqlite3_strnicmp(pCol->zCnName, "__hidden__", 10)==0 ){ pCol->colFlags |= COLFLAG_HIDDEN; + if( pTab ) pTab->tabFlags |= TF_HasHidden; }else if( pTab && pCol!=pTab->aCol && (pCol[-1].colFlags & COLFLAG_HIDDEN) ){ pTab->tabFlags |= TF_OOOHidden; } } #endif +/* +** Clean up the data structures associated with the RETURNING clause. +*/ +static void sqlite3DeleteReturning(sqlite3 *db, void *pArg){ + Returning *pRet = (Returning*)pArg; + Hash *pHash; + pHash = &(db->aDb[1].pSchema->trigHash); + sqlite3HashInsert(pHash, pRet->zName, 0); + sqlite3ExprListDelete(db, pRet->pReturnEL); + sqlite3DbFree(db, pRet); +} + +/* +** Add the RETURNING clause to the parse currently underway. +** +** This routine creates a special TEMP trigger that will fire for each row +** of the DML statement. That TEMP trigger contains a single SELECT +** statement with a result set that is the argument of the RETURNING clause. +** The trigger has the Trigger.bReturning flag and an opcode of +** TK_RETURNING instead of TK_SELECT, so that the trigger code generator +** knows to handle it specially. The TEMP trigger is automatically +** removed at the end of the parse. +** +** When this routine is called, we do not yet know if the RETURNING clause +** is attached to a DELETE, INSERT, or UPDATE, so construct it as a +** RETURNING trigger instead. It will then be converted into the appropriate +** type on the first call to sqlite3TriggersExist(). +*/ +SQLITE_PRIVATE void sqlite3AddReturning(Parse *pParse, ExprList *pList){ + Returning *pRet; + Hash *pHash; + sqlite3 *db = pParse->db; + if( pParse->pNewTrigger ){ + sqlite3ErrorMsg(pParse, "cannot use RETURNING in a trigger"); + }else{ + assert( pParse->bReturning==0 || pParse->ifNotExists ); + } + pParse->bReturning = 1; + pRet = sqlite3DbMallocZero(db, sizeof(*pRet)); + if( pRet==0 ){ + sqlite3ExprListDelete(db, pList); + return; + } + assert( !pParse->isCreate ); + pParse->u1.d.pReturning = pRet; + pRet->pParse = pParse; + pRet->pReturnEL = pList; + sqlite3ParserAddCleanup(pParse, sqlite3DeleteReturning, pRet); + testcase( pParse->earlyCleanup ); + if( db->mallocFailed ) return; + sqlite3_snprintf(sizeof(pRet->zName), pRet->zName, + "sqlite_returning_%p", pParse); + pRet->retTrig.zName = pRet->zName; + pRet->retTrig.op = TK_RETURNING; + pRet->retTrig.tr_tm = TRIGGER_AFTER; + pRet->retTrig.bReturning = 1; + pRet->retTrig.pSchema = db->aDb[1].pSchema; + pRet->retTrig.pTabSchema = db->aDb[1].pSchema; + pRet->retTrig.step_list = &pRet->retTStep; + pRet->retTStep.op = TK_RETURNING; + pRet->retTStep.pTrig = &pRet->retTrig; + pRet->retTStep.pExprList = pList; + pHash = &(db->aDb[1].pSchema->trigHash); + assert( sqlite3HashFind(pHash, pRet->zName)==0 + || pParse->nErr || pParse->ifNotExists ); + if( sqlite3HashInsert(pHash, pRet->zName, &pRet->retTrig) + ==&pRet->retTrig ){ + sqlite3OomFault(db); + } +} /* ** Add a new column to the table currently being constructed. @@ -107930,65 +128029,112 @@ SQLITE_PRIVATE void sqlite3ColumnPropertiesFromName(Table *pTab, Column *pCol){ ** first to get things going. Then this routine is called for each ** column. */ -SQLITE_PRIVATE void sqlite3AddColumn(Parse *pParse, Token *pName, Token *pType){ +SQLITE_PRIVATE void sqlite3AddColumn(Parse *pParse, Token sName, Token sType){ Table *p; int i; char *z; char *zType; Column *pCol; sqlite3 *db = pParse->db; + Column *aNew; + u8 eType = COLTYPE_CUSTOM; + u8 szEst = 1; + char affinity = SQLITE_AFF_BLOB; + if( (p = pParse->pNewTable)==0 ) return; if( p->nCol+1>db->aLimit[SQLITE_LIMIT_COLUMN] ){ sqlite3ErrorMsg(pParse, "too many columns on %s", p->zName); return; } - z = sqlite3DbMallocRaw(db, pName->n + pType->n + 2); + if( !IN_RENAME_OBJECT ) sqlite3DequoteToken(&sName); + + /* Because keywords GENERATE ALWAYS can be converted into identifiers + ** by the parser, we can sometimes end up with a typename that ends + ** with "generated always". Check for this case and omit the surplus + ** text. */ + if( sType.n>=16 + && sqlite3_strnicmp(sType.z+(sType.n-6),"always",6)==0 + ){ + sType.n -= 6; + while( ALWAYS(sType.n>0) && sqlite3Isspace(sType.z[sType.n-1]) ) sType.n--; + if( sType.n>=9 + && sqlite3_strnicmp(sType.z+(sType.n-9),"generated",9)==0 + ){ + sType.n -= 9; + while( sType.n>0 && sqlite3Isspace(sType.z[sType.n-1]) ) sType.n--; + } + } + + /* Check for standard typenames. For standard typenames we will + ** set the Column.eType field rather than storing the typename after + ** the column name, in order to save space. */ + if( sType.n>=3 ){ + sqlite3DequoteToken(&sType); + for(i=0; i0) ); if( z==0 ) return; - if( IN_RENAME_OBJECT ) sqlite3RenameTokenMap(pParse, (void*)z, pName); - memcpy(z, pName->z, pName->n); - z[pName->n] = 0; + if( IN_RENAME_OBJECT ) sqlite3RenameTokenMap(pParse, (void*)z, &sName); + memcpy(z, sName.z, sName.n); + z[sName.n] = 0; sqlite3Dequote(z); - for(i=0; inCol; i++){ - if( sqlite3_stricmp(z, p->aCol[i].zName)==0 ){ - sqlite3ErrorMsg(pParse, "duplicate column name: %s", z); - sqlite3DbFree(db, z); - return; - } + if( p->nCol && sqlite3ColumnIndex(p, z)>=0 ){ + sqlite3ErrorMsg(pParse, "duplicate column name: %s", z); + sqlite3DbFree(db, z); + return; } - if( (p->nCol & 0x7)==0 ){ - Column *aNew; - aNew = sqlite3DbRealloc(db,p->aCol,(p->nCol+8)*sizeof(p->aCol[0])); - if( aNew==0 ){ - sqlite3DbFree(db, z); - return; - } - p->aCol = aNew; + aNew = sqlite3DbRealloc(db,p->aCol,((i64)p->nCol+1)*sizeof(p->aCol[0])); + if( aNew==0 ){ + sqlite3DbFree(db, z); + return; } + p->aCol = aNew; pCol = &p->aCol[p->nCol]; memset(pCol, 0, sizeof(p->aCol[0])); - pCol->zName = z; + pCol->zCnName = z; + pCol->hName = sqlite3StrIHash(z); sqlite3ColumnPropertiesFromName(p, pCol); - - if( pType->n==0 ){ + + if( sType.n==0 ){ /* If there is no type specified, columns have the default affinity ** 'BLOB' with a default size of 4 bytes. */ - pCol->affinity = SQLITE_AFF_BLOB; - pCol->szEst = 1; + pCol->affinity = affinity; + pCol->eCType = eType; + pCol->szEst = szEst; #ifdef SQLITE_ENABLE_SORTER_REFERENCES - if( 4>=sqlite3GlobalConfig.szSorterRef ){ - pCol->colFlags |= COLFLAG_SORTERREF; + if( affinity==SQLITE_AFF_BLOB ){ + if( 4>=sqlite3GlobalConfig.szSorterRef ){ + pCol->colFlags |= COLFLAG_SORTERREF; + } } #endif }else{ zType = z + sqlite3Strlen30(z) + 1; - memcpy(zType, pType->z, pType->n); - zType[pType->n] = 0; + memcpy(zType, sType.z, sType.n); + zType[sType.n] = 0; sqlite3Dequote(zType); pCol->affinity = sqlite3AffinityType(zType, pCol); pCol->colFlags |= COLFLAG_HASTYPE; } + if( p->nCol<=0xff ){ + u8 h = pCol->hName % sizeof(p->aHx); + p->aHx[h] = p->nCol; + } p->nCol++; - pParse->constraintName.n = 0; + p->nNVCol++; + assert( pParse->isCreate ); + pParse->u1.cr.constraintName.n = 0; } /* @@ -108023,11 +128169,11 @@ SQLITE_PRIVATE void sqlite3AddNotNull(Parse *pParse, int onError){ ** Scan the column type name zType (length nType) and return the ** associated affinity type. ** -** This routine does a case-independent search of zType for the +** This routine does a case-independent search of zType for the ** substrings in the following table. If one of the substrings is ** found, the corresponding affinity is returned. If zType contains -** more than one of the substrings, entries toward the top of -** the table take priority. For example, if zType is 'BLOBINT', +** more than one of the substrings, entries toward the top of +** the table take priority. For example, if zType is 'BLOBINT', ** SQLITE_AFF_INTEGER is returned. ** ** Substring | Affinity @@ -108051,7 +128197,8 @@ SQLITE_PRIVATE char sqlite3AffinityType(const char *zIn, Column *pCol){ assert( zIn!=0 ); while( zIn[0] ){ - h = (h<<8) + sqlite3UpperToLower[(*zIn)&0xff]; + u8 x = *(u8*)zIn; + h = (h<<8) + sqlite3UpperToLower[x]; zIn++; if( h==(('c'<<24)+('h'<<16)+('a'<<8)+'r') ){ /* CHAR */ aff = SQLITE_AFF_TEXT; @@ -108125,30 +128272,37 @@ SQLITE_PRIVATE void sqlite3AddDefaultValue( Parse *pParse, /* Parsing context */ Expr *pExpr, /* The parsed expression of the default value */ const char *zStart, /* Start of the default value text */ - const char *zEnd /* First character past end of defaut value text */ + const char *zEnd /* First character past end of default value text */ ){ Table *p; Column *pCol; sqlite3 *db = pParse->db; p = pParse->pNewTable; if( p!=0 ){ + int isInit = db->init.busy && db->init.iDb!=1; pCol = &(p->aCol[p->nCol-1]); - if( !sqlite3ExprIsConstantOrFunction(pExpr, db->init.busy) ){ + if( !sqlite3ExprIsConstantOrFunction(pExpr, isInit) ){ sqlite3ErrorMsg(pParse, "default value of column [%s] is not constant", - pCol->zName); + pCol->zCnName); +#ifndef SQLITE_OMIT_GENERATED_COLUMNS + }else if( pCol->colFlags & COLFLAG_GENERATED ){ + testcase( pCol->colFlags & COLFLAG_VIRTUAL ); + testcase( pCol->colFlags & COLFLAG_STORED ); + sqlite3ErrorMsg(pParse, "cannot use DEFAULT on a generated column"); +#endif }else{ /* A copy of pExpr is used instead of the original, as pExpr contains ** tokens that point to volatile memory. */ - Expr x; - sqlite3ExprDelete(db, pCol->pDflt); + Expr x, *pDfltExpr; memset(&x, 0, sizeof(x)); x.op = TK_SPAN; x.u.zToken = sqlite3DbSpanDup(db, zStart, zEnd); x.pLeft = pExpr; x.flags = EP_Skip; - pCol->pDflt = sqlite3ExprDup(db, &x, EXPRDUP_REDUCE); + pDfltExpr = sqlite3ExprDup(db, &x, EXPRDUP_REDUCE); sqlite3DbFree(db, x.u.zToken); + sqlite3ColumnSetExpr(pParse, p, pCol, pDfltExpr); } } if( IN_RENAME_OBJECT ){ @@ -108159,7 +128313,7 @@ SQLITE_PRIVATE void sqlite3AddDefaultValue( /* ** Backwards Compatibility Hack: -** +** ** Historical versions of SQLite accepted strings as column names in ** indexes and PRIMARY KEY constraints and in UNIQUE constraints. Example: ** @@ -108170,7 +128324,7 @@ SQLITE_PRIVATE void sqlite3AddDefaultValue( ** accept it. This routine does the necessary conversion. It converts ** the expression given in its argument from a TK_STRING into a TK_ID ** if the expression is just a TK_STRING with an optional COLLATE clause. -** If the epxression is anything other than TK_STRING, the expression is +** If the expression is anything other than TK_STRING, the expression is ** unchanged. */ static void sqlite3StringToId(Expr *p){ @@ -108182,7 +128336,22 @@ static void sqlite3StringToId(Expr *p){ } /* -** Designate the PRIMARY KEY for the table. pList is a list of names +** Tag the given column as being part of the PRIMARY KEY +*/ +static void makeColumnPartOfPrimaryKey(Parse *pParse, Column *pCol){ + pCol->colFlags |= COLFLAG_PRIMKEY; +#ifndef SQLITE_OMIT_GENERATED_COLUMNS + if( pCol->colFlags & COLFLAG_GENERATED ){ + testcase( pCol->colFlags & COLFLAG_VIRTUAL ); + testcase( pCol->colFlags & COLFLAG_STORED ); + sqlite3ErrorMsg(pParse, + "generated columns cannot be part of the PRIMARY KEY"); + } +#endif +} + +/* +** Designate the PRIMARY KEY for the table. pList is a list of names ** of columns that form the primary key. If pList is NULL, then the ** most recently added column of the table is the primary key. ** @@ -108212,7 +128381,7 @@ SQLITE_PRIVATE void sqlite3AddPrimaryKey( int nTerm; if( pTab==0 ) goto primary_key_exit; if( pTab->tabFlags & TF_HasPrimaryKey ){ - sqlite3ErrorMsg(pParse, + sqlite3ErrorMsg(pParse, "table \"%s\" has more than one primary key", pTab->zName); goto primary_key_exit; } @@ -108220,7 +128389,7 @@ SQLITE_PRIVATE void sqlite3AddPrimaryKey( if( pList==0 ){ iCol = pTab->nCol - 1; pCol = &pTab->aCol[iCol]; - pCol->colFlags |= COLFLAG_PRIMKEY; + makeColumnPartOfPrimaryKey(pParse, pCol); nTerm = 1; }else{ nTerm = pList->nExpr; @@ -108229,20 +128398,18 @@ SQLITE_PRIVATE void sqlite3AddPrimaryKey( assert( pCExpr!=0 ); sqlite3StringToId(pCExpr); if( pCExpr->op==TK_ID ){ - const char *zCName = pCExpr->u.zToken; - for(iCol=0; iColnCol; iCol++){ - if( sqlite3StrICmp(zCName, pTab->aCol[iCol].zName)==0 ){ - pCol = &pTab->aCol[iCol]; - pCol->colFlags |= COLFLAG_PRIMKEY; - break; - } + assert( !ExprHasProperty(pCExpr, EP_IntValue) ); + iCol = sqlite3ColumnIndex(pTab, pCExpr->u.zToken); + if( iCol>=0 ){ + pCol = &pTab->aCol[iCol]; + makeColumnPartOfPrimaryKey(pParse, pCol); } } } } if( nTerm==1 && pCol - && sqlite3StrICmp(sqlite3ColumnType(pCol,""), "INTEGER")==0 + && pCol->eCType==COLTYPE_INTEGER && sortOrder!=SQLITE_SO_DESC ){ if( IN_RENAME_OBJECT && pList ){ @@ -108253,7 +128420,8 @@ SQLITE_PRIVATE void sqlite3AddPrimaryKey( pTab->keyConf = (u8)onError; assert( autoInc==0 || autoInc==1 ); pTab->tabFlags |= autoInc*TF_Autoincrement; - if( pList ) pParse->iPkSortOrder = pList->a[0].sortOrder; + if( pList ) pParse->iPkSortOrder = pList->a[0].fg.sortFlags; + (void)sqlite3HasExplicitNulls(pParse, pList); }else if( autoInc ){ #ifndef SQLITE_OMIT_AUTOINCREMENT sqlite3ErrorMsg(pParse, "AUTOINCREMENT is only allowed on an " @@ -108274,8 +128442,10 @@ SQLITE_PRIVATE void sqlite3AddPrimaryKey( ** Add a new CHECK constraint to the table currently under construction. */ SQLITE_PRIVATE void sqlite3AddCheckConstraint( - Parse *pParse, /* Parsing context */ - Expr *pCheckExpr /* The check expression */ + Parse *pParse, /* Parsing context */ + Expr *pCheckExpr, /* The check expression */ + const char *zStart, /* Opening "(" */ + const char *zEnd /* Closing ")" */ ){ #ifndef SQLITE_OMIT_CHECK Table *pTab = pParse->pNewTable; @@ -108284,8 +128454,17 @@ SQLITE_PRIVATE void sqlite3AddCheckConstraint( && !sqlite3BtreeIsReadonly(db->aDb[db->init.iDb].pBt) ){ pTab->pCheck = sqlite3ExprListAppend(pParse, pTab->pCheck, pCheckExpr); - if( pParse->constraintName.n ){ - sqlite3ExprListSetName(pParse, pTab->pCheck, &pParse->constraintName, 1); + assert( pParse->isCreate ); + if( pParse->u1.cr.constraintName.n ){ + sqlite3ExprListSetName(pParse, pTab->pCheck, + &pParse->u1.cr.constraintName, 1); + }else{ + Token t; + for(zStart++; sqlite3Isspace(zStart[0]); zStart++){} + while( sqlite3Isspace(zEnd[-1]) ){ zEnd--; } + t.z = zStart; + t.n = (int)(zEnd - t.z); + sqlite3ExprListSetName(pParse, pTab->pCheck, &t, 1); } }else #endif @@ -108304,7 +128483,7 @@ SQLITE_PRIVATE void sqlite3AddCollateType(Parse *pParse, Token *pToken){ char *zColl; /* Dequoted name of collation sequence */ sqlite3 *db; - if( (p = pParse->pNewTable)==0 ) return; + if( (p = pParse->pNewTable)==0 || IN_RENAME_OBJECT ) return; i = p->nCol-1; db = pParse->db; zColl = sqlite3NameFromToken(db, pToken); @@ -108312,9 +128491,8 @@ SQLITE_PRIVATE void sqlite3AddCollateType(Parse *pParse, Token *pToken){ if( sqlite3LocateCollSeq(pParse, zColl) ){ Index *pIdx; - sqlite3DbFree(db, p->aCol[i].zColl); - p->aCol[i].zColl = zColl; - + sqlite3ColumnSetColl(db, &p->aCol[i], zColl); + /* If the column is declared as " PRIMARY KEY COLLATE ", ** then an index may have been created on this column before the ** collation type was added. Correct this if it is the case. @@ -108322,49 +128500,73 @@ SQLITE_PRIVATE void sqlite3AddCollateType(Parse *pParse, Token *pToken){ for(pIdx=p->pIndex; pIdx; pIdx=pIdx->pNext){ assert( pIdx->nKeyCol==1 ); if( pIdx->aiColumn[0]==i ){ - pIdx->azColl[0] = p->aCol[i].zColl; + pIdx->azColl[0] = sqlite3ColumnColl(&p->aCol[i]); } } - }else{ - sqlite3DbFree(db, zColl); } + sqlite3DbFree(db, zColl); } -/* -** This function returns the collation sequence for database native text -** encoding identified by the string zName, length nName. -** -** If the requested collation sequence is not available, or not available -** in the database native encoding, the collation factory is invoked to -** request it. If the collation factory does not supply such a sequence, -** and the sequence is available in another text encoding, then that is -** returned instead. -** -** If no versions of the requested collations sequence are available, or -** another error occurs, NULL is returned and an error message written into -** pParse. -** -** This routine is a wrapper around sqlite3FindCollSeq(). This routine -** invokes the collation factory if the named collation cannot be found -** and generates an error message. -** -** See also: sqlite3FindCollSeq(), sqlite3GetCollSeq() +/* Change the most recently parsed column to be a GENERATED ALWAYS AS +** column. */ -SQLITE_PRIVATE CollSeq *sqlite3LocateCollSeq(Parse *pParse, const char *zName){ - sqlite3 *db = pParse->db; - u8 enc = ENC(db); - u8 initbusy = db->init.busy; - CollSeq *pColl; - - pColl = sqlite3FindCollSeq(db, enc, zName, initbusy); - if( !initbusy && (!pColl || !pColl->xCmp) ){ - pColl = sqlite3GetCollSeq(pParse, enc, pColl, zName); +SQLITE_PRIVATE void sqlite3AddGenerated(Parse *pParse, Expr *pExpr, Token *pType){ +#ifndef SQLITE_OMIT_GENERATED_COLUMNS + u8 eType = COLFLAG_VIRTUAL; + Table *pTab = pParse->pNewTable; + Column *pCol; + if( pTab==0 ){ + /* generated column in an CREATE TABLE IF NOT EXISTS that already exists */ + goto generated_done; + } + pCol = &(pTab->aCol[pTab->nCol-1]); + if( IN_DECLARE_VTAB ){ + sqlite3ErrorMsg(pParse, "virtual tables cannot use computed columns"); + goto generated_done; } + if( pCol->iDflt>0 ) goto generated_error; + if( pType ){ + if( pType->n==7 && sqlite3StrNICmp("virtual",pType->z,7)==0 ){ + /* no-op */ + }else if( pType->n==6 && sqlite3StrNICmp("stored",pType->z,6)==0 ){ + eType = COLFLAG_STORED; + }else{ + goto generated_error; + } + } + if( eType==COLFLAG_VIRTUAL ) pTab->nNVCol--; + pCol->colFlags |= eType; + assert( TF_HasVirtual==COLFLAG_VIRTUAL ); + assert( TF_HasStored==COLFLAG_STORED ); + pTab->tabFlags |= eType; + if( pCol->colFlags & COLFLAG_PRIMKEY ){ + makeColumnPartOfPrimaryKey(pParse, pCol); /* For the error message */ + } + if( ALWAYS(pExpr) && pExpr->op==TK_ID ){ + /* The value of a generated column needs to be a real expression, not + ** just a reference to another column, in order for covering index + ** optimizations to work correctly. So if the value is not an expression, + ** turn it into one by adding a unary "+" operator. */ + pExpr = sqlite3PExpr(pParse, TK_UPLUS, pExpr, 0); + } + if( pExpr && pExpr->op!=TK_RAISE ) pExpr->affExpr = pCol->affinity; + sqlite3ColumnSetExpr(pParse, pTab, pCol, pExpr); + pExpr = 0; + goto generated_done; - return pColl; +generated_error: + sqlite3ErrorMsg(pParse, "error in generated column \"%s\"", + pCol->zCnName); +generated_done: + sqlite3ExprDelete(pParse->db, pExpr); +#else + /* Throw and error for the GENERATED ALWAYS AS clause if the + ** SQLITE_OMIT_GENERATED_COLUMNS compile-time option is used. */ + sqlite3ErrorMsg(pParse, "generated columns not supported"); + sqlite3ExprDelete(pParse->db, pExpr); +#endif } - /* ** Generate code that will increment the schema cookie. ** @@ -108388,7 +128590,7 @@ SQLITE_PRIVATE void sqlite3ChangeCookie(Parse *pParse, int iDb){ sqlite3 *db = pParse->db; Vdbe *v = pParse->pVdbe; assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); - sqlite3VdbeAddOp3(v, OP_SetCookie, iDb, BTREE_SCHEMA_VERSION, + sqlite3VdbeAddOp3(v, OP_SetCookie, iDb, BTREE_SCHEMA_VERSION, (int)(1+(unsigned)db->aDb[iDb].pSchema->schema_cookie)); } @@ -108400,8 +128602,8 @@ SQLITE_PRIVATE void sqlite3ChangeCookie(Parse *pParse, int iDb){ ** The estimate is conservative. It might be larger that what is ** really needed. */ -static int identLength(const char *z){ - int n; +static i64 identLength(const char *z){ + i64 n; for(n=0; *z; n++, z++){ if( *z=='"' ){ n++; } } @@ -108409,14 +128611,14 @@ static int identLength(const char *z){ } /* -** The first parameter is a pointer to an output buffer. The second +** The first parameter is a pointer to an output buffer. The second ** parameter is a pointer to an integer that contains the offset at ** which to write into the output buffer. This function copies the ** nul-terminated string pointed to by the third parameter, zSignedIdent, ** to the specified offset in the buffer and updates *pIdx to refer ** to the first byte after the last byte written before returning. -** -** If the string zSignedIdent consists entirely of alpha-numeric +** +** If the string zSignedIdent consists entirely of alphanumeric ** characters, does not begin with a digit and is not an SQL keyword, ** then it is copied to the output buffer exactly as it is. Otherwise, ** it is quoted using double-quotes. @@ -108450,16 +128652,17 @@ static void identPut(char *z, int *pIdx, char *zSignedIdent){ ** from sqliteMalloc() and must be freed by the calling function. */ static char *createTableStmt(sqlite3 *db, Table *p){ - int i, k, n; + int i, k, len; + i64 n; char *zStmt; char *zSep, *zSep2, *zEnd; Column *pCol; n = 0; for(pCol = p->aCol, i=0; inCol; i++, pCol++){ - n += identLength(pCol->zName) + 5; + n += identLength(pCol->zCnName) + 5; } n += identLength(p->zName); - if( n<50 ){ + if( n<50 ){ zSep = ""; zSep2 = ","; zEnd = ")"; @@ -108474,8 +128677,9 @@ static char *createTableStmt(sqlite3 *db, Table *p){ sqlite3OomFault(db); return 0; } - sqlite3_snprintf(n, zStmt, "CREATE TABLE "); - k = sqlite3Strlen30(zStmt); + assert( n>14 && n<=0x7fffffff ); + memcpy(zStmt, "CREATE TABLE ", 13); + k = 13; identPut(zStmt, &k, p->zName); zStmt[k++] = '('; for(pCol=p->aCol, i=0; inCol; i++, pCol++){ @@ -108484,15 +128688,18 @@ static char *createTableStmt(sqlite3 *db, Table *p){ /* SQLITE_AFF_TEXT */ " TEXT", /* SQLITE_AFF_NUMERIC */ " NUM", /* SQLITE_AFF_INTEGER */ " INT", - /* SQLITE_AFF_REAL */ " REAL" + /* SQLITE_AFF_REAL */ " REAL", + /* SQLITE_AFF_FLEXNUM */ " NUM", }; - int len; const char *zType; - sqlite3_snprintf(n-k, &zStmt[k], zSep); - k += sqlite3Strlen30(&zStmt[k]); + len = sqlite3Strlen30(zSep); + assert( k+lenzName); + identPut(zStmt, &k, pCol->zCnName); + assert( kaffinity-SQLITE_AFF_BLOB >= 0 ); assert( pCol->affinity-SQLITE_AFF_BLOB < ArraySize(azType) ); testcase( pCol->affinity==SQLITE_AFF_BLOB ); @@ -108500,16 +128707,21 @@ static char *createTableStmt(sqlite3 *db, Table *p){ testcase( pCol->affinity==SQLITE_AFF_NUMERIC ); testcase( pCol->affinity==SQLITE_AFF_INTEGER ); testcase( pCol->affinity==SQLITE_AFF_REAL ); - + testcase( pCol->affinity==SQLITE_AFF_FLEXNUM ); + zType = azType[pCol->affinity - SQLITE_AFF_BLOB]; len = sqlite3Strlen30(zType); - assert( pCol->affinity==SQLITE_AFF_BLOB + assert( pCol->affinity==SQLITE_AFF_BLOB + || pCol->affinity==SQLITE_AFF_FLEXNUM || pCol->affinity==sqlite3AffinityType(zType, 0) ); + assert( k+lennColumn>=N ) return SQLITE_OK; + db = pParse->db; + assert( N>0 ); + assert( N <= SQLITE_MAX_COLUMN*2 /* tag-20250221-1 */ ); + testcase( N==2*pParse->db->aLimit[SQLITE_LIMIT_COLUMN] ); assert( pIdx->isResized==0 ); - nByte = (sizeof(char*) + sizeof(i16) + 1)*N; + nByte = (sizeof(char*) + sizeof(LogEst) + sizeof(i16) + 1)*(u64)N; zExtra = sqlite3DbMallocZero(db, nByte); if( zExtra==0 ) return SQLITE_NOMEM_BKPT; memcpy(zExtra, pIdx->azColl, sizeof(char*)*pIdx->nColumn); pIdx->azColl = (const char**)zExtra; zExtra += sizeof(char*)*N; + memcpy(zExtra, pIdx->aiRowLogEst, sizeof(LogEst)*(pIdx->nKeyCol+1)); + pIdx->aiRowLogEst = (LogEst*)zExtra; + zExtra += sizeof(LogEst)*N; memcpy(zExtra, pIdx->aiColumn, sizeof(i16)*pIdx->nColumn); pIdx->aiColumn = (i16*)zExtra; zExtra += sizeof(i16)*N; memcpy(zExtra, pIdx->aSortOrder, pIdx->nColumn); pIdx->aSortOrder = (u8*)zExtra; - pIdx->nColumn = N; + pIdx->nColumn = (u16)N; /* See tag-20250221-1 above for proof of safety */ pIdx->isResized = 1; return SQLITE_OK; } @@ -108562,41 +128782,91 @@ static void estimateIndexWidth(Index *pIdx){ for(i=0; inColumn; i++){ i16 x = pIdx->aiColumn[i]; assert( xpTable->nCol ); - wIndex += x<0 ? 1 : aCol[pIdx->aiColumn[i]].szEst; + wIndex += x<0 ? 1 : aCol[x].szEst; } pIdx->szIdxRow = sqlite3LogEst(wIndex*4); } -/* Return true if value x is found any of the first nCol entries of aiCol[] +/* Return true if column number x is any of the first nCol entries of aiCol[]. +** This is used to determine if the column number x appears in any of the +** first nCol entries of an index. */ static int hasColumn(const i16 *aiCol, int nCol, int x){ - while( nCol-- > 0 ) if( x==*(aiCol++) ) return 1; + while( nCol-- > 0 ){ + if( x==*(aiCol++) ){ + return 1; + } + } + return 0; +} + +/* +** Return true if any of the first nKey entries of index pIdx exactly +** match the iCol-th entry of pPk. pPk is always a WITHOUT ROWID +** PRIMARY KEY index. pIdx is an index on the same table. pIdx may +** or may not be the same index as pPk. +** +** The first nKey entries of pIdx are guaranteed to be ordinary columns, +** not a rowid or expression. +** +** This routine differs from hasColumn() in that both the column and the +** collating sequence must match for this routine, but for hasColumn() only +** the column name must match. +*/ +static int isDupColumn(Index *pIdx, int nKey, Index *pPk, int iCol){ + int i, j; + assert( nKey<=pIdx->nColumn ); + assert( iColnColumn,pPk->nKeyCol) ); + assert( pPk->idxType==SQLITE_IDXTYPE_PRIMARYKEY ); + assert( pPk->pTable->tabFlags & TF_WithoutRowid ); + assert( pPk->pTable==pIdx->pTable ); + testcase( pPk==pIdx ); + j = pPk->aiColumn[iCol]; + assert( j!=XN_ROWID && j!=XN_EXPR ); + for(i=0; iaiColumn[i]>=0 || j>=0 ); + if( pIdx->aiColumn[i]==j + && sqlite3StrICmp(pIdx->azColl[i], pPk->azColl[iCol])==0 + ){ + return 1; + } + } return 0; } /* Recompute the colNotIdxed field of the Index. ** ** colNotIdxed is a bitmask that has a 0 bit representing each indexed -** columns that are within the first 63 columns of the table. The +** columns that are within the first 63 columns of the table and a 1 for +** all other bits (all columns that are not in the index). The ** high-order bit of colNotIdxed is always 1. All unindexed columns ** of the table have a 1. ** +** 2019-10-24: For the purpose of this computation, virtual columns are +** not considered to be covered by the index, even if they are in the +** index, because we do not trust the logic in whereIndexExprTrans() to be +** able to find all instances of a reference to the indexed table column +** and convert them into references to the index. Hence we always want +** the actual table at hand in order to recompute the virtual column, if +** necessary. +** ** The colNotIdxed mask is AND-ed with the SrcList.a[].colUsed mask ** to determine if the index is covering index. */ static void recomputeColumnsNotIndexed(Index *pIdx){ Bitmask m = 0; int j; + Table *pTab = pIdx->pTable; for(j=pIdx->nColumn-1; j>=0; j--){ int x = pIdx->aiColumn[j]; - if( x>=0 ){ + if( x>=0 && (pTab->aCol[x].colFlags & COLFLAG_VIRTUAL)==0 ){ testcase( x==BMS-1 ); testcase( x==BMS-2 ); if( xcolNotIdxed = ~m; - assert( (pIdx->colNotIdxed>>63)==1 ); + assert( (pIdx->colNotIdxed>>63)==1 ); /* See note-20221022-a */ } /* @@ -108607,11 +128877,11 @@ static void recomputeColumnsNotIndexed(Index *pIdx){ ** Changes include: ** ** (1) Set all columns of the PRIMARY KEY schema object to be NOT NULL. -** (2) Convert P3 parameter of the OP_CreateBtree from BTREE_INTKEY +** (2) Convert P3 parameter of the OP_CreateBtree from BTREE_INTKEY ** into BTREE_BLOBKEY. -** (3) Bypass the creation of the sqlite_master table entry +** (3) Bypass the creation of the sqlite_schema table entry ** for the PRIMARY KEY as the primary key index is now -** identified by the sqlite_master table entry of the table itself. +** identified by the sqlite_schema table entry of the table itself. ** (4) Set the Index.tnum of the PRIMARY KEY Index object in the ** schema to the rootpage from the main table. ** (5) Add all table columns to the PRIMARY KEY Index object @@ -108627,6 +128897,7 @@ static void convertToWithoutRowidTable(Parse *pParse, Table *pTab){ Index *pIdx; Index *pPk; int nPk; + int nExtra; int i, j; sqlite3 *db = pParse->db; Vdbe *v = pParse->pVdbe; @@ -108635,37 +128906,52 @@ static void convertToWithoutRowidTable(Parse *pParse, Table *pTab){ */ if( !db->init.imposterTable ){ for(i=0; inCol; i++){ - if( (pTab->aCol[i].colFlags & COLFLAG_PRIMKEY)!=0 ){ + if( (pTab->aCol[i].colFlags & COLFLAG_PRIMKEY)!=0 + && (pTab->aCol[i].notNull==OE_None) + ){ pTab->aCol[i].notNull = OE_Abort; } } + pTab->tabFlags |= TF_HasNotNull; } /* Convert the P3 operand of the OP_CreateBtree opcode from BTREE_INTKEY ** into BTREE_BLOBKEY. */ - if( pParse->addrCrTab ){ + assert( !pParse->bReturning ); + if( pParse->u1.cr.addrCrTab ){ assert( v ); - sqlite3VdbeChangeP3(v, pParse->addrCrTab, BTREE_BLOBKEY); + sqlite3VdbeChangeP3(v, pParse->u1.cr.addrCrTab, BTREE_BLOBKEY); } /* Locate the PRIMARY KEY index. Or, if this table was originally - ** an INTEGER PRIMARY KEY table, create a new PRIMARY KEY index. + ** an INTEGER PRIMARY KEY table, create a new PRIMARY KEY index. */ if( pTab->iPKey>=0 ){ ExprList *pList; Token ipkToken; - sqlite3TokenInit(&ipkToken, pTab->aCol[pTab->iPKey].zName); - pList = sqlite3ExprListAppend(pParse, 0, + sqlite3TokenInit(&ipkToken, pTab->aCol[pTab->iPKey].zCnName); + pList = sqlite3ExprListAppend(pParse, 0, sqlite3ExprAlloc(db, TK_ID, &ipkToken, 0)); - if( pList==0 ) return; - pList->a[0].sortOrder = pParse->iPkSortOrder; + if( pList==0 ){ + pTab->tabFlags &= ~TF_WithoutRowid; + return; + } + if( IN_RENAME_OBJECT ){ + sqlite3RenameTokenRemap(pParse, pList->a[0].pExpr, &pTab->iPKey); + } + pList->a[0].fg.sortFlags = pParse->iPkSortOrder; assert( pParse->pNewTable==pTab ); + pTab->iPKey = -1; sqlite3CreateIndex(pParse, 0, 0, 0, pList, pTab->keyConf, 0, 0, 0, 0, SQLITE_IDXTYPE_PRIMARYKEY); - if( db->mallocFailed || pParse->nErr ) return; + if( pParse->nErr ){ + pTab->tabFlags &= ~TF_WithoutRowid; + return; + } + assert( db->mallocFailed==0 ); pPk = sqlite3PrimaryKeyIndex(pTab); - pTab->iPKey = -1; + assert( pPk->nKeyCol==1 ); }else{ pPk = sqlite3PrimaryKeyIndex(pTab); assert( pPk!=0 ); @@ -108676,9 +128962,12 @@ static void convertToWithoutRowidTable(Parse *pParse, Table *pTab){ ** code assumes the PRIMARY KEY contains no repeated columns. */ for(i=j=1; inKeyCol; i++){ - if( hasColumn(pPk->aiColumn, j, pPk->aiColumn[i]) ){ + if( isDupColumn(pPk, j, pPk, i) ){ pPk->nColumn--; }else{ + testcase( hasColumn(pPk->aiColumn, j, pPk->aiColumn[i]) ); + pPk->azColl[j] = pPk->azColl[i]; + pPk->aSortOrder[j] = pPk->aSortOrder[i]; pPk->aiColumn[j++] = pPk->aiColumn[i]; } } @@ -108687,15 +128976,15 @@ static void convertToWithoutRowidTable(Parse *pParse, Table *pTab){ assert( pPk!=0 ); pPk->isCovering = 1; if( !db->init.imposterTable ) pPk->uniqNotNull = 1; - nPk = pPk->nKeyCol; + nPk = pPk->nColumn = pPk->nKeyCol; - /* Bypass the creation of the PRIMARY KEY btree and the sqlite_master + /* Bypass the creation of the PRIMARY KEY btree and the sqlite_schema ** table entry. This is only required if currently generating VDBE ** code for a CREATE TABLE (not when parsing one as part of reading ** a database schema). */ if( v && pPk->tnum>0 ){ assert( db->init.busy==0 ); - sqlite3VdbeChangeOpcode(v, pPk->tnum, OP_Goto); + sqlite3VdbeChangeOpcode(v, (int)pPk->tnum, OP_Goto); } /* The root page of the PRIMARY KEY is the table root page */ @@ -108708,18 +128997,26 @@ static void convertToWithoutRowidTable(Parse *pParse, Table *pTab){ int n; if( IsPrimaryKeyIndex(pIdx) ) continue; for(i=n=0; iaiColumn, pIdx->nKeyCol, pPk->aiColumn[i]) ) n++; + if( !isDupColumn(pIdx, pIdx->nKeyCol, pPk, i) ){ + testcase( hasColumn(pIdx->aiColumn, pIdx->nKeyCol, pPk->aiColumn[i]) ); + n++; + } } if( n==0 ){ /* This index is a superset of the primary key */ pIdx->nColumn = pIdx->nKeyCol; continue; } - if( resizeIndexObject(db, pIdx, pIdx->nKeyCol+n) ) return; + if( resizeIndexObject(pParse, pIdx, pIdx->nKeyCol+n) ) return; for(i=0, j=pIdx->nKeyCol; iaiColumn, pIdx->nKeyCol, pPk->aiColumn[i]) ){ + if( !isDupColumn(pIdx, pIdx->nKeyCol, pPk, i) ){ + testcase( hasColumn(pIdx->aiColumn, pIdx->nKeyCol, pPk->aiColumn[i]) ); pIdx->aiColumn[j] = pPk->aiColumn[i]; pIdx->azColl[j] = pPk->azColl[i]; + if( pPk->aSortOrder[i] ){ + /* See ticket https://sqlite.org/src/info/bba7b69f9849b5bf */ + pIdx->bAscKeyBug = 1; + } j++; } } @@ -108729,24 +129026,85 @@ static void convertToWithoutRowidTable(Parse *pParse, Table *pTab){ /* Add all table columns to the PRIMARY KEY index */ - if( nPknCol ){ - if( resizeIndexObject(db, pPk, pTab->nCol) ) return; - for(i=0, j=nPk; inCol; i++){ - if( !hasColumn(pPk->aiColumn, j, i) ){ - assert( jnColumn ); - pPk->aiColumn[j] = i; - pPk->azColl[j] = sqlite3StrBINARY; - j++; - } + nExtra = 0; + for(i=0; inCol; i++){ + if( !hasColumn(pPk->aiColumn, nPk, i) + && (pTab->aCol[i].colFlags & COLFLAG_VIRTUAL)==0 ) nExtra++; + } + if( resizeIndexObject(pParse, pPk, nPk+nExtra) ) return; + for(i=0, j=nPk; inCol; i++){ + if( !hasColumn(pPk->aiColumn, j, i) + && (pTab->aCol[i].colFlags & COLFLAG_VIRTUAL)==0 + ){ + const char *zColl = sqlite3ColumnColl(&pTab->aCol[i]); + assert( jnColumn ); + pPk->aiColumn[j] = i; + pPk->azColl[j] = zColl ? zColl : sqlite3StrBINARY; + j++; } - assert( pPk->nColumn==j ); - assert( pTab->nCol==j ); - }else{ - pPk->nColumn = pTab->nCol; } + assert( pPk->nColumn==j ); + assert( pTab->nNVCol<=j ); recomputeColumnsNotIndexed(pPk); } + +#ifndef SQLITE_OMIT_VIRTUALTABLE +/* +** Return true if pTab is a virtual table and zName is a shadow table name +** for that virtual table. +*/ +SQLITE_PRIVATE int sqlite3IsShadowTableOf(sqlite3 *db, Table *pTab, const char *zName){ + int nName; /* Length of zName */ + Module *pMod; /* Module for the virtual table */ + + if( !IsVirtual(pTab) ) return 0; + nName = sqlite3Strlen30(pTab->zName); + if( sqlite3_strnicmp(zName, pTab->zName, nName)!=0 ) return 0; + if( zName[nName]!='_' ) return 0; + pMod = (Module*)sqlite3HashFind(&db->aModule, pTab->u.vtab.azArg[0]); + if( pMod==0 ) return 0; + if( pMod->pModule->iVersion<3 ) return 0; + if( pMod->pModule->xShadowName==0 ) return 0; + return pMod->pModule->xShadowName(zName+nName+1); +} +#endif /* ifndef SQLITE_OMIT_VIRTUALTABLE */ + +#ifndef SQLITE_OMIT_VIRTUALTABLE +/* +** Table pTab is a virtual table. If it the virtual table implementation +** exists and has an xShadowName method, then loop over all other ordinary +** tables within the same schema looking for shadow tables of pTab, and mark +** any shadow tables seen using the TF_Shadow flag. +*/ +SQLITE_PRIVATE void sqlite3MarkAllShadowTablesOf(sqlite3 *db, Table *pTab){ + int nName; /* Length of pTab->zName */ + Module *pMod; /* Module for the virtual table */ + HashElem *k; /* For looping through the symbol table */ + + assert( IsVirtual(pTab) ); + pMod = (Module*)sqlite3HashFind(&db->aModule, pTab->u.vtab.azArg[0]); + if( pMod==0 ) return; + if( NEVER(pMod->pModule==0) ) return; + if( pMod->pModule->iVersion<3 ) return; + if( pMod->pModule->xShadowName==0 ) return; + assert( pTab->zName!=0 ); + nName = sqlite3Strlen30(pTab->zName); + for(k=sqliteHashFirst(&pTab->pSchema->tblHash); k; k=sqliteHashNext(k)){ + Table *pOther = sqliteHashData(k); + assert( pOther->zName!=0 ); + if( !IsOrdinaryTable(pOther) ) continue; + if( pOther->tabFlags & TF_Shadow ) continue; + if( sqlite3StrNICmp(pOther->zName, pTab->zName, nName)==0 + && pOther->zName[nName]=='_' + && pMod->pModule->xShadowName(pOther->zName+nName+1) + ){ + pOther->tabFlags |= TF_Shadow; + } + } +} +#endif /* ifndef SQLITE_OMIT_VIRTUALTABLE */ + #ifndef SQLITE_OMIT_VIRTUALTABLE /* ** Return true if zName is a shadow table name in the current database @@ -108755,28 +129113,49 @@ static void convertToWithoutRowidTable(Parse *pParse, Table *pTab){ ** zName is temporarily modified while this routine is running, but is ** restored to its original value prior to this routine returning. */ -static int isShadowTableName(sqlite3 *db, char *zName){ - char *zTail; /* Pointer to the last "_" in zName */ +SQLITE_PRIVATE int sqlite3ShadowTableName(sqlite3 *db, const char *zName){ + const char *zTail; /* Pointer to the last "_" in zName */ Table *pTab; /* Table that zName is a shadow of */ - Module *pMod; /* Module for the virtual table */ - + char *zCopy; zTail = strrchr(zName, '_'); if( zTail==0 ) return 0; - *zTail = 0; - pTab = sqlite3FindTable(db, zName, 0); - *zTail = '_'; + zCopy = sqlite3DbStrNDup(db, zName, (int)(zTail-zName)); + pTab = zCopy ? sqlite3FindTable(db, zCopy, 0) : 0; + sqlite3DbFree(db, zCopy); if( pTab==0 ) return 0; if( !IsVirtual(pTab) ) return 0; - pMod = (Module*)sqlite3HashFind(&db->aModule, pTab->azModuleArg[0]); - if( pMod==0 ) return 0; - if( pMod->pModule->iVersion<3 ) return 0; - if( pMod->pModule->xShadowName==0 ) return 0; - return pMod->pModule->xShadowName(zTail+1); + return sqlite3IsShadowTableOf(db, pTab, zName); } -#else -# define isShadowTableName(x,y) 0 #endif /* ifndef SQLITE_OMIT_VIRTUALTABLE */ + +#ifdef SQLITE_DEBUG +/* +** Mark all nodes of an expression as EP_Immutable, indicating that +** they should not be changed. Expressions attached to a table or +** index definition are tagged this way to help ensure that we do +** not pass them into code generator routines by mistake. +*/ +static int markImmutableExprStep(Walker *pWalker, Expr *pExpr){ + (void)pWalker; + ExprSetVVAProperty(pExpr, EP_Immutable); + return WRC_Continue; +} +static void markExprListImmutable(ExprList *pList){ + if( pList ){ + Walker w; + memset(&w, 0, sizeof(w)); + w.xExprCallback = markImmutableExprStep; + w.xSelectCallback = sqlite3SelectWalkNoop; + w.xSelectCallback2 = 0; + sqlite3WalkExprList(&w, pList); + } +} +#else +#define markExprListImmutable(X) /* no-op */ +#endif /* SQLITE_DEBUG */ + + /* ** This routine is called to report the final ")" that terminates ** a CREATE TABLE statement. @@ -108785,15 +129164,15 @@ static int isShadowTableName(sqlite3 *db, char *zName){ ** is added to the internal hash tables, assuming no errors have ** occurred. ** -** An entry for the table is made in the master table on disk, unless +** An entry for the table is made in the schema table on disk, unless ** this is a temporary table or db->init.busy==1. When db->init.busy==1 -** it means we are reading the sqlite_master table because we just -** connected to the database or because the sqlite_master table has +** it means we are reading the sqlite_schema table because we just +** connected to the database or because the sqlite_schema table has ** recently changed, so the entry for this table already exists in -** the sqlite_master table. We do not want to create it again. +** the sqlite_schema table. We do not want to create it again. ** ** If the pSelect argument is not NULL, it means that this routine -** was called to create a table generated from a +** was called to create a table generated from a ** "CREATE TABLE ... AS SELECT ..." statement. The column names of ** the new table will match the result set of the SELECT. */ @@ -108801,7 +129180,7 @@ SQLITE_PRIVATE void sqlite3EndTable( Parse *pParse, /* Parse context */ Token *pCons, /* The ',' token after the last column defn. */ Token *pEnd, /* The ')' before options in the CREATE TABLE */ - u8 tabOpts, /* Extra table options. Usually 0. */ + u32 tabOpts, /* Extra table options. Usually 0. */ Select *pSelect /* Select from a "CREATE ... AS SELECT" */ ){ Table *p; /* The new table */ @@ -108812,25 +129191,24 @@ SQLITE_PRIVATE void sqlite3EndTable( if( pEnd==0 && pSelect==0 ){ return; } - assert( !db->mallocFailed ); p = pParse->pNewTable; if( p==0 ) return; - if( pSelect==0 && isShadowTableName(db, p->zName) ){ + if( pSelect==0 && sqlite3ShadowTableName(db, p->zName) ){ p->tabFlags |= TF_Shadow; } /* If the db->init.busy is 1 it means we are reading the SQL off the - ** "sqlite_master" or "sqlite_temp_master" table on the disk. + ** "sqlite_schema" or "sqlite_temp_schema" table on the disk. ** So do not write to the disk again. Extract the root page number ** for the table from the db->init.newTnum field. (The page number ** should have been put there by the sqliteOpenCb routine.) ** - ** If the root page number is 1, that means this is the sqlite_master + ** If the root page number is 1, that means this is the sqlite_schema ** table itself. So mark it read-only. */ if( db->init.busy ){ - if( pSelect ){ + if( pSelect || (!IsOrdinaryTable(p) && db->init.newTnum) ){ sqlite3ErrorMsg(pParse, ""); return; } @@ -108838,6 +129216,44 @@ SQLITE_PRIVATE void sqlite3EndTable( if( p->tnum==1 ) p->tabFlags |= TF_Readonly; } + /* Special processing for tables that include the STRICT keyword: + ** + ** * Do not allow custom column datatypes. Every column must have + ** a datatype that is one of INT, INTEGER, REAL, TEXT, or BLOB. + ** + ** * If a PRIMARY KEY is defined, other than the INTEGER PRIMARY KEY, + ** then all columns of the PRIMARY KEY must have a NOT NULL + ** constraint. + */ + if( tabOpts & TF_Strict ){ + int ii; + p->tabFlags |= TF_Strict; + for(ii=0; iinCol; ii++){ + Column *pCol = &p->aCol[ii]; + if( pCol->eCType==COLTYPE_CUSTOM ){ + if( pCol->colFlags & COLFLAG_HASTYPE ){ + sqlite3ErrorMsg(pParse, + "unknown datatype for %s.%s: \"%s\"", + p->zName, pCol->zCnName, sqlite3ColumnType(pCol, "") + ); + }else{ + sqlite3ErrorMsg(pParse, "missing datatype for %s.%s", + p->zName, pCol->zCnName); + } + return; + }else if( pCol->eCType==COLTYPE_ANY ){ + pCol->affinity = SQLITE_AFF_BLOB; + } + if( (pCol->colFlags & COLFLAG_PRIMKEY)!=0 + && p->iPKey!=ii + && pCol->notNull == OE_None + ){ + pCol->notNull = OE_Abort; + p->tabFlags |= TF_HasNotNull; + } + } + } + assert( (p->tabFlags & TF_HasPrimaryKey)==0 || p->iPKey>=0 || sqlite3PrimaryKeyIndex(p)!=0 ); assert( (p->tabFlags & TF_HasPrimaryKey)!=0 @@ -108852,21 +129268,60 @@ SQLITE_PRIVATE void sqlite3EndTable( } if( (p->tabFlags & TF_HasPrimaryKey)==0 ){ sqlite3ErrorMsg(pParse, "PRIMARY KEY missing on table %s", p->zName); - }else{ - p->tabFlags |= TF_WithoutRowid | TF_NoVisibleRowid; - convertToWithoutRowidTable(pParse, p); + return; } + p->tabFlags |= TF_WithoutRowid | TF_NoVisibleRowid; + convertToWithoutRowidTable(pParse, p); } - iDb = sqlite3SchemaToIndex(db, p->pSchema); + assert( iDb>=0 && iDb<=db->nDb ); #ifndef SQLITE_OMIT_CHECK /* Resolve names in all CHECK constraint expressions. */ if( p->pCheck ){ sqlite3ResolveSelfReference(pParse, p, NC_IsCheck, 0, p->pCheck); + if( pParse->nErr ){ + /* If errors are seen, delete the CHECK constraints now, else they might + ** actually be used if PRAGMA writable_schema=ON is set. */ + sqlite3ExprListDelete(db, p->pCheck); + p->pCheck = 0; + }else{ + markExprListImmutable(p->pCheck); + } } #endif /* !defined(SQLITE_OMIT_CHECK) */ +#ifndef SQLITE_OMIT_GENERATED_COLUMNS + if( p->tabFlags & TF_HasGenerated ){ + int ii, nNG = 0; + testcase( p->tabFlags & TF_HasVirtual ); + testcase( p->tabFlags & TF_HasStored ); + for(ii=0; iinCol; ii++){ + u32 colFlags = p->aCol[ii].colFlags; + if( (colFlags & COLFLAG_GENERATED)!=0 ){ + Expr *pX = sqlite3ColumnExpr(p, &p->aCol[ii]); + testcase( colFlags & COLFLAG_VIRTUAL ); + testcase( colFlags & COLFLAG_STORED ); + if( sqlite3ResolveSelfReference(pParse, p, NC_GenCol, pX, 0) ){ + /* If there are errors in resolving the expression, change the + ** expression to a NULL. This prevents code generators that operate + ** on the expression from inserting extra parts into the expression + ** tree that have been allocated from lookaside memory, which is + ** illegal in a schema and will lead to errors or heap corruption + ** when the database connection closes. */ + sqlite3ColumnSetExpr(pParse, p, &p->aCol[ii], + sqlite3ExprAlloc(db, TK_NULL, 0, 0)); + } + }else{ + nNG++; + } + } + if( nNG==0 ){ + sqlite3ErrorMsg(pParse, "must have at least one non-generated column"); + return; + } + } +#endif /* Estimate the average row size for the table and for all implied indices */ estimateTableWidth(p); @@ -108875,7 +129330,7 @@ SQLITE_PRIVATE void sqlite3EndTable( } /* If not initializing, then create a record for the new table - ** in the SQLITE_MASTER table of the database. + ** in the schema table of the database. ** ** If this is a TEMPORARY table, write the entry into the auxiliary ** file instead of into the main database file. @@ -108892,10 +129347,10 @@ SQLITE_PRIVATE void sqlite3EndTable( sqlite3VdbeAddOp1(v, OP_Close, 0); - /* + /* ** Initialize zType for the new view or table. */ - if( p->pSelect==0 ){ + if( IsOrdinaryTable(p) ){ /* A regular table */ zType = "table"; zType2 = "TABLE"; @@ -108909,7 +129364,7 @@ SQLITE_PRIVATE void sqlite3EndTable( /* If this is a CREATE TABLE xx AS SELECT ..., execute the SELECT ** statement to populate the new table. The root-page number for the - ** new table is in register pParse->regRoot. + ** new table is in register pParse->u1.cr.regRoot. ** ** Once the SELECT has been coded by sqlite3Select(), it is in a ** suitable state to query for the column names and types to be used @@ -108928,22 +129383,28 @@ SQLITE_PRIVATE void sqlite3EndTable( int regRowid; /* Rowid of the next row to insert */ int addrInsLoop; /* Top of the loop for inserting rows */ Table *pSelTab; /* A table that describes the SELECT results */ + int iCsr; /* Write cursor on the new table */ + if( IN_SPECIAL_PARSE ){ + pParse->rc = SQLITE_ERROR; + pParse->nErr++; + return; + } + iCsr = pParse->nTab++; regYield = ++pParse->nMem; regRec = ++pParse->nMem; regRowid = ++pParse->nMem; - assert(pParse->nTab==1); sqlite3MayAbort(pParse); - sqlite3VdbeAddOp3(v, OP_OpenWrite, 1, pParse->regRoot, iDb); + assert( pParse->isCreate ); + sqlite3VdbeAddOp3(v, OP_OpenWrite, iCsr, pParse->u1.cr.regRoot, iDb); sqlite3VdbeChangeP5(v, OPFLAG_P2ISREG); - pParse->nTab = 2; addrTop = sqlite3VdbeCurrentAddr(v) + 1; sqlite3VdbeAddOp3(v, OP_InitCoroutine, regYield, 0, addrTop); if( pParse->nErr ) return; - pSelTab = sqlite3ResultSetOfSelect(pParse, pSelect); + pSelTab = sqlite3ResultSetOfSelect(pParse, pSelect, SQLITE_AFF_BLOB); if( pSelTab==0 ) return; assert( p->aCol==0 ); - p->nCol = pSelTab->nCol; + p->nCol = p->nNVCol = pSelTab->nCol; p->aCol = pSelTab->aCol; pSelTab->nCol = 0; pSelTab->aCol = 0; @@ -108957,11 +129418,11 @@ SQLITE_PRIVATE void sqlite3EndTable( VdbeCoverage(v); sqlite3VdbeAddOp3(v, OP_MakeRecord, dest.iSdst, dest.nSdst, regRec); sqlite3TableAffinity(v, p, 0); - sqlite3VdbeAddOp2(v, OP_NewRowid, 1, regRowid); - sqlite3VdbeAddOp3(v, OP_Insert, 1, regRec, regRowid); + sqlite3VdbeAddOp2(v, OP_NewRowid, iCsr, regRowid); + sqlite3VdbeAddOp3(v, OP_Insert, iCsr, regRec, regRowid); sqlite3VdbeGoto(v, addrInsLoop); sqlite3VdbeJumpHere(v, addrInsLoop); - sqlite3VdbeAddOp1(v, OP_Close, 1); + sqlite3VdbeAddOp1(v, OP_Close, iCsr); } /* Compute the complete text of the CREATE statement */ @@ -108971,26 +129432,27 @@ SQLITE_PRIVATE void sqlite3EndTable( Token *pEnd2 = tabOpts ? &pParse->sLastToken : pEnd; n = (int)(pEnd2->z - pParse->sNameToken.z); if( pEnd2->z[0]!=';' ) n += pEnd2->n; - zStmt = sqlite3MPrintf(db, + zStmt = sqlite3MPrintf(db, "CREATE %s %.*s", zType2, n, pParse->sNameToken.z ); } - /* A slot for the record has already been allocated in the - ** SQLITE_MASTER table. We just need to update that slot with all + /* A slot for the record has already been allocated in the + ** schema table. We just need to update that slot with all ** the information we've collected. */ + assert( pParse->isCreate ); sqlite3NestedParse(pParse, - "UPDATE %Q.%s " - "SET type='%s', name=%Q, tbl_name=%Q, rootpage=#%d, sql=%Q " - "WHERE rowid=#%d", - db->aDb[iDb].zDbSName, MASTER_NAME, + "UPDATE %Q." LEGACY_SCHEMA_TABLE + " SET type='%s', name=%Q, tbl_name=%Q, rootpage=#%d, sql=%Q" + " WHERE rowid=#%d", + db->aDb[iDb].zDbSName, zType, p->zName, p->zName, - pParse->regRoot, + pParse->u1.cr.regRoot, zStmt, - pParse->regRowid + pParse->u1.cr.regRowid ); sqlite3DbFree(db, zStmt); sqlite3ChangeCookie(pParse, iDb); @@ -108999,7 +129461,7 @@ SQLITE_PRIVATE void sqlite3EndTable( /* Check to see if we need to create an sqlite_sequence table for ** keeping track of autoincrement keys. */ - if( (p->tabFlags & TF_Autoincrement)!=0 ){ + if( (p->tabFlags & TF_Autoincrement)!=0 && !IN_SPECIAL_PARSE ){ Db *pDb = &db->aDb[iDb]; assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); if( pDb->pSchema->pSeqTab==0 ){ @@ -109013,9 +129475,16 @@ SQLITE_PRIVATE void sqlite3EndTable( /* Reparse everything to update our internal data structures */ sqlite3VdbeAddParseSchemaOp(v, iDb, - sqlite3MPrintf(db, "tbl_name='%q' AND type!='trigger'", p->zName)); - } + sqlite3MPrintf(db, "tbl_name='%q' AND type!='trigger'", p->zName),0); + /* Test for cycles in generated columns and illegal expressions + ** in CHECK constraints and in DEFAULT clauses. */ + if( p->tabFlags & TF_HasGenerated ){ + sqlite3VdbeAddOp4(v, OP_SqlExec, 0x0001, 0, 0, + sqlite3MPrintf(db, "SELECT*FROM\"%w\".\"%w\"", + db->aDb[iDb].zDbSName, p->zName), P4_DYNAMIC); + } + } /* Add the table to the in-memory representation of the database. */ @@ -109023,6 +129492,7 @@ SQLITE_PRIVATE void sqlite3EndTable( Table *pOld; Schema *pSchema = p->pSchema; assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); + assert( HasRowid(p) || p->iPKey<0 ); pOld = sqlite3HashInsert(&pSchema->tblHash, p->zName, p); if( pOld ){ assert( p==pOld ); /* Malloc must have failed inside HashInsert() */ @@ -109032,19 +129502,27 @@ SQLITE_PRIVATE void sqlite3EndTable( pParse->pNewTable = 0; db->mDbFlags |= DBFLAG_SchemaChange; -#ifndef SQLITE_OMIT_ALTERTABLE - if( !p->pSelect ){ - const char *zName = (const char *)pParse->sNameToken.z; - int nName; - assert( !pSelect && pCons && pEnd ); - if( pCons->z==0 ){ - pCons = pEnd; - } - nName = (int)((const char *)pCons->z - zName); - p->addColOffset = 13 + sqlite3Utf8CharLen(zName, nName); + /* If this is the magic sqlite_sequence table used by autoincrement, + ** then record a pointer to this table in the main database structure + ** so that INSERT can find the table easily. */ + assert( !pParse->nested ); +#ifndef SQLITE_OMIT_AUTOINCREMENT + if( strcmp(p->zName, "sqlite_sequence")==0 ){ + assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); + p->pSchema->pSeqTab = p; } #endif } + +#ifndef SQLITE_OMIT_ALTERTABLE + if( !pSelect && IsOrdinaryTable(p) ){ + assert( pCons && pEnd ); + if( pCons->z==0 ){ + pCons = pEnd; + } + p->u.tab.addColOffset = 13 + (int)(pCons->z - pParse->sNameToken.z); + } +#endif } #ifndef SQLITE_OMIT_VIEW @@ -109077,8 +129555,22 @@ SQLITE_PRIVATE void sqlite3CreateView( sqlite3StartTable(pParse, pName1, pName2, isTemp, 1, 0, noErr); p = pParse->pNewTable; if( p==0 || pParse->nErr ) goto create_view_fail; + + /* Legacy versions of SQLite allowed the use of the magic "rowid" column + ** on a view, even though views do not have rowids. The following flag + ** setting fixes this problem. But the fix can be disabled by compiling + ** with -DSQLITE_ALLOW_ROWID_IN_VIEW in case there are legacy apps that + ** depend upon the old buggy behavior. The ability can also be toggled + ** using sqlite3_config(SQLITE_CONFIG_ROWID_IN_VIEW,...) */ +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + p->tabFlags |= sqlite3Config.mNoVisibleRowid; /* Optional. Allow by default */ +#else + p->tabFlags |= TF_NoVisibleRowid; /* Never allow rowid in view */ +#endif + sqlite3TwoPartName(pParse, pName1, pName2, &pName); iDb = sqlite3SchemaToIndex(db, p->pSchema); + assert( iDb>=0 && iDbnDb ); sqlite3FixInit(&sFix, pParse, iDb, "view", pName); if( sqlite3FixSelect(&sFix, pSelect) ) goto create_view_fail; @@ -109087,13 +129579,15 @@ SQLITE_PRIVATE void sqlite3CreateView( ** allocated rather than point to the input string - which means that ** they will persist after the current sqlite3_exec() call returns. */ + pSelect->selFlags |= SF_View; if( IN_RENAME_OBJECT ){ - p->pSelect = pSelect; + p->u.view.pSelect = pSelect; pSelect = 0; }else{ - p->pSelect = sqlite3SelectDup(db, pSelect, EXPRDUP_REDUCE); + p->u.view.pSelect = sqlite3SelectDup(db, pSelect, EXPRDUP_REDUCE); } p->pCheck = sqlite3ExprListDup(db, pCNames, EXPRDUP_REDUCE); + p->eTabType = TABTYP_VIEW; if( db->mallocFailed ) goto create_view_fail; /* Locate the end of the CREATE VIEW statement. Make sEnd point to @@ -109112,7 +129606,7 @@ SQLITE_PRIVATE void sqlite3CreateView( sEnd.z = &z[n-1]; sEnd.n = 1; - /* Use sqlite3EndTable() to add the view to the SQLITE_MASTER table */ + /* Use sqlite3EndTable() to add the view to the schema table */ sqlite3EndTable(pParse, 0, &sEnd, 0, 0); create_view_fail: @@ -109128,14 +129622,14 @@ SQLITE_PRIVATE void sqlite3CreateView( #if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_VIRTUALTABLE) /* ** The Table structure pTable is really a VIEW. Fill in the names of -** the columns of the view in the pTable structure. Return the number -** of errors. If an error is seen leave an error message in pParse->zErrMsg. +** the columns of the view in the pTable structure. Return non-zero if +** there are errors. If an error is seen an error message is left +** in pParse->zErrMsg. */ -SQLITE_PRIVATE int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){ +static SQLITE_NOINLINE int viewGetColumnNames(Parse *pParse, Table *pTable){ Table *pSelTab; /* A fake table from which we get the result set */ Select *pSel; /* Copy of the SELECT that implements the view */ int nErr = 0; /* Number of errors encountered */ - int n; /* Temporarily holds the number of cursors assigned */ sqlite3 *db = pParse->db; /* Database connection for malloc errors */ #ifndef SQLITE_OMIT_VIRTUALTABLE int rc; @@ -109147,20 +129641,20 @@ SQLITE_PRIVATE int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){ assert( pTable ); #ifndef SQLITE_OMIT_VIRTUALTABLE - db->nSchemaLock++; - rc = sqlite3VtabCallConnect(pParse, pTable); - db->nSchemaLock--; - if( rc ){ - return 1; + if( IsVirtual(pTable) ){ + db->nSchemaLock++; + rc = sqlite3VtabCallConnect(pParse, pTable); + db->nSchemaLock--; + return rc; } - if( IsVirtual(pTable) ) return 0; #endif #ifndef SQLITE_OMIT_VIEW /* A positive nCol means the columns names for this view are - ** already known. + ** already known. This routine is not called unless either the + ** table is virtual or nCol is zero. */ - if( pTable->nCol>0 ) return 0; + assert( pTable->nCol<=0 ); /* A negative nCol is a special marker meaning that we are currently ** trying to compute the column names. If we enter this routine with @@ -109172,7 +129666,7 @@ SQLITE_PRIVATE int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){ ** Actually, the error above is now caught prior to reaching this point. ** But the following test is still important as it does come up ** in the following: - ** + ** ** CREATE TABLE main.ex1(a); ** CREATE TEMP VIEW ex1 AS SELECT a FROM ex1; ** SELECT * FROM temp.ex1; @@ -109190,72 +129684,75 @@ SQLITE_PRIVATE int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){ ** to be permanent. So the computation is done on a copy of the SELECT ** statement that defines the view. */ - assert( pTable->pSelect ); - pSel = sqlite3SelectDup(db, pTable->pSelect, 0); + assert( IsView(pTable) ); + pSel = sqlite3SelectDup(db, pTable->u.view.pSelect, 0); if( pSel ){ -#ifndef SQLITE_OMIT_ALTERTABLE u8 eParseMode = pParse->eParseMode; + int nTab = pParse->nTab; + int nSelect = pParse->nSelect; pParse->eParseMode = PARSE_MODE_NORMAL; -#endif - n = pParse->nTab; sqlite3SrcListAssignCursors(pParse, pSel->pSrc); pTable->nCol = -1; - db->lookaside.bDisable++; + DisableLookaside; #ifndef SQLITE_OMIT_AUTHORIZATION xAuth = db->xAuth; db->xAuth = 0; - pSelTab = sqlite3ResultSetOfSelect(pParse, pSel); + pSelTab = sqlite3ResultSetOfSelect(pParse, pSel, SQLITE_AFF_NONE); db->xAuth = xAuth; #else - pSelTab = sqlite3ResultSetOfSelect(pParse, pSel); + pSelTab = sqlite3ResultSetOfSelect(pParse, pSel, SQLITE_AFF_NONE); #endif - pParse->nTab = n; - if( pTable->pCheck ){ + pParse->nTab = nTab; + pParse->nSelect = nSelect; + if( pSelTab==0 ){ + pTable->nCol = 0; + nErr++; + }else if( pTable->pCheck ){ /* CREATE VIEW name(arglist) AS ... ** The names of the columns in the table are taken from ** arglist which is stored in pTable->pCheck. The pCheck field ** normally holds CHECK constraints on an ordinary table, but for ** a VIEW it holds the list of column names. */ - sqlite3ColumnsFromExprList(pParse, pTable->pCheck, + sqlite3ColumnsFromExprList(pParse, pTable->pCheck, &pTable->nCol, &pTable->aCol); - if( db->mallocFailed==0 - && pParse->nErr==0 + if( pParse->nErr==0 && pTable->nCol==pSel->pEList->nExpr ){ - sqlite3SelectAddColumnTypeAndCollation(pParse, pTable, pSel); + assert( db->mallocFailed==0 ); + sqlite3SubqueryColumnTypes(pParse, pTable, pSel, SQLITE_AFF_NONE); } - }else if( pSelTab ){ + }else{ /* CREATE VIEW name AS... without an argument list. Construct ** the column names from the SELECT statement that defines the view. */ assert( pTable->aCol==0 ); pTable->nCol = pSelTab->nCol; pTable->aCol = pSelTab->aCol; + pTable->tabFlags |= (pSelTab->tabFlags & COLFLAG_NOINSERT); pSelTab->nCol = 0; pSelTab->aCol = 0; assert( sqlite3SchemaMutexHeld(db, 0, pTable->pSchema) ); - }else{ - pTable->nCol = 0; - nErr++; } + pTable->nNVCol = pTable->nCol; sqlite3DeleteTable(db, pSelTab); sqlite3SelectDelete(db, pSel); - db->lookaside.bDisable--; -#ifndef SQLITE_OMIT_ALTERTABLE + EnableLookaside; pParse->eParseMode = eParseMode; -#endif } else { nErr++; } pTable->pSchema->schemaFlags |= DB_UnresetViews; if( db->mallocFailed ){ sqlite3DeleteColumnNames(db, pTable); - pTable->aCol = 0; - pTable->nCol = 0; } #endif /* SQLITE_OMIT_VIEW */ - return nErr; + return nErr + pParse->nErr; +} +SQLITE_PRIVATE int sqlite3ViewGetColumnNames(Parse *pParse, Table *pTable){ + assert( pTable!=0 ); + if( !IsVirtual(pTable) && pTable->nCol>0 ) return 0; + return viewGetColumnNames(pParse, pTable); } #endif /* !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_VIRTUALTABLE) */ @@ -109269,10 +129766,8 @@ static void sqliteViewResetAll(sqlite3 *db, int idx){ if( !DbHasProperty(db, idx, DB_UnresetViews) ) return; for(i=sqliteHashFirst(&db->aDb[idx].pSchema->tblHash); i;i=sqliteHashNext(i)){ Table *pTab = sqliteHashData(i); - if( pTab->pSelect ){ + if( IsView(pTab) ){ sqlite3DeleteColumnNames(db, pTab); - pTab->aCol = 0; - pTab->nCol = 0; } } DbClearProperty(db, idx, DB_UnresetViews); @@ -109291,7 +129786,7 @@ static void sqliteViewResetAll(sqlite3 *db, int idx){ ** on tables and/or indices that are the process of being deleted. ** If you are unlucky, one of those deleted indices or tables might ** have the same rootpage number as the real table or index that is -** being moved. So we cannot stop searching after the first match +** being moved. So we cannot stop searching after the first match ** because the first match might be for one of the deleted indices ** or tables and not the table/index that is actually being moved. ** We must continue looping until all tables and indices with @@ -109299,7 +129794,7 @@ static void sqliteViewResetAll(sqlite3 *db, int idx){ ** in order to be certain that we got the right one. */ #ifndef SQLITE_OMIT_AUTOVACUUM -SQLITE_PRIVATE void sqlite3RootPageMoved(sqlite3 *db, int iDb, int iFrom, int iTo){ +SQLITE_PRIVATE void sqlite3RootPageMoved(sqlite3 *db, int iDb, Pgno iFrom, Pgno iTo){ HashElem *pElem; Hash *pHash; Db *pDb; @@ -109325,10 +129820,10 @@ SQLITE_PRIVATE void sqlite3RootPageMoved(sqlite3 *db, int iDb, int iFrom, int iT /* ** Write code to erase the table with root-page iTable from database iDb. -** Also write code to modify the sqlite_master table and internal schema +** Also write code to modify the sqlite_schema table and internal schema ** if a root-page of another table is moved by the btree-layer whilst ** erasing iTable (this can happen with an auto-vacuum database). -*/ +*/ static void destroyRootPage(Parse *pParse, int iTable, int iDb){ Vdbe *v = sqlite3GetVdbe(pParse); int r1 = sqlite3GetTempReg(pParse); @@ -109338,30 +129833,31 @@ static void destroyRootPage(Parse *pParse, int iTable, int iDb){ #ifndef SQLITE_OMIT_AUTOVACUUM /* OP_Destroy stores an in integer r1. If this integer ** is non-zero, then it is the root page number of a table moved to - ** location iTable. The following code modifies the sqlite_master table to + ** location iTable. The following code modifies the sqlite_schema table to ** reflect this. ** ** The "#NNN" in the SQL is a special constant that means whatever value ** is in register NNN. See grammar rules associated with the TK_REGISTER ** token for additional information. */ - sqlite3NestedParse(pParse, - "UPDATE %Q.%s SET rootpage=%d WHERE #%d AND rootpage=#%d", - pParse->db->aDb[iDb].zDbSName, MASTER_NAME, iTable, r1, r1); + sqlite3NestedParse(pParse, + "UPDATE %Q." LEGACY_SCHEMA_TABLE + " SET rootpage=%d WHERE #%d AND rootpage=#%d", + pParse->db->aDb[iDb].zDbSName, iTable, r1, r1); #endif sqlite3ReleaseTempReg(pParse, r1); } /* ** Write VDBE code to erase table pTab and all associated indices on disk. -** Code to update the sqlite_master tables and internal schema definitions +** Code to update the sqlite_schema tables and internal schema definitions ** in case a root-page belonging to another table is moved by the btree layer ** is also added (this can happen with an auto-vacuum database). */ static void destroyTable(Parse *pParse, Table *pTab){ /* If the database may be auto-vacuum capable (if SQLITE_OMIT_AUTOVACUUM ** is not defined), then it is important to call OP_Destroy on the - ** table and index root-pages in order, starting with the numerically + ** table and index root-pages in order, starting with the numerically ** largest root-page number. This guarantees that none of the root-pages ** to be destroyed is relocated by an earlier OP_Destroy. i.e. if the ** following were coded: @@ -109371,22 +129867,22 @@ static void destroyTable(Parse *pParse, Table *pTab){ ** OP_Destroy 5 0 ** ** and root page 5 happened to be the largest root-page number in the - ** database, then root page 5 would be moved to page 4 by the + ** database, then root page 5 would be moved to page 4 by the ** "OP_Destroy 4 0" opcode. The subsequent "OP_Destroy 5 0" would hit ** a free-list page. */ - int iTab = pTab->tnum; - int iDestroyed = 0; + Pgno iTab = pTab->tnum; + Pgno iDestroyed = 0; while( 1 ){ Index *pIdx; - int iLargest = 0; + Pgno iLargest = 0; if( iDestroyed==0 || iTabpIndex; pIdx; pIdx=pIdx->pNext){ - int iIdx = pIdx->tnum; + Pgno iIdx = pIdx->tnum; assert( pIdx->pSchema==pTab->pSchema ); if( (iDestroyed==0 || (iIdxiLargest ){ iLargest = iIdx; @@ -109447,12 +129943,12 @@ SQLITE_PRIVATE void sqlite3CodeDropTable(Parse *pParse, Table *pTab, int iDb, in #endif /* Drop all triggers associated with the table being dropped. Code - ** is generated to remove entries from sqlite_master and/or - ** sqlite_temp_master if required. + ** is generated to remove entries from sqlite_schema and/or + ** sqlite_temp_schema if required. */ pTrigger = sqlite3TriggerList(pParse, pTab); while( pTrigger ){ - assert( pTrigger->pSchema==pTab->pSchema || + assert( pTrigger->pSchema==pTab->pSchema || pTrigger->pSchema==db->aDb[1].pSchema ); sqlite3DropTriggerPtr(pParse, pTrigger); pTrigger = pTrigger->pNext; @@ -109472,16 +129968,17 @@ SQLITE_PRIVATE void sqlite3CodeDropTable(Parse *pParse, Table *pTab, int iDb, in } #endif - /* Drop all SQLITE_MASTER table and index entries that refer to the - ** table. The program name loops through the master table and deletes + /* Drop all entries in the schema table that refer to the + ** table. The program name loops through the schema table and deletes ** every row that refers to a table of the same name as the one being ** dropped. Triggers are handled separately because a trigger can be ** created in the temp database that refers to a table in another ** database. */ - sqlite3NestedParse(pParse, - "DELETE FROM %Q.%s WHERE tbl_name=%Q and type!='trigger'", - pDb->zDbSName, MASTER_NAME, pTab->zName); + sqlite3NestedParse(pParse, + "DELETE FROM %Q." LEGACY_SCHEMA_TABLE + " WHERE tbl_name=%Q and type!='trigger'", + pDb->zDbSName, pTab->zName); if( !isView && !IsVirtual(pTab) ){ destroyTable(pParse, pTab); } @@ -109498,6 +129995,41 @@ SQLITE_PRIVATE void sqlite3CodeDropTable(Parse *pParse, Table *pTab, int iDb, in sqliteViewResetAll(db, iDb); } +/* +** Return TRUE if shadow tables should be read-only in the current +** context. +*/ +SQLITE_PRIVATE int sqlite3ReadOnlyShadowTables(sqlite3 *db){ +#ifndef SQLITE_OMIT_VIRTUALTABLE + if( (db->flags & SQLITE_Defensive)!=0 + && db->pVtabCtx==0 + && db->nVdbeExec==0 + && !sqlite3VtabInSync(db) + ){ + return 1; + } +#endif + return 0; +} + +/* +** Return true if it is not allowed to drop the given table +*/ +static int tableMayNotBeDropped(sqlite3 *db, Table *pTab){ + if( sqlite3StrNICmp(pTab->zName, "sqlite_", 7)==0 ){ + if( sqlite3StrNICmp(pTab->zName+7, "stat", 4)==0 ) return 0; + if( sqlite3StrNICmp(pTab->zName+7, "parameters", 10)==0 ) return 0; + return 1; + } + if( (pTab->tabFlags & TF_Shadow)!=0 && sqlite3ReadOnlyShadowTables(db) ){ + return 1; + } + if( pTab->tabFlags & TF_Eponymous ){ + return 1; + } + return 0; +} + /* ** This routine is called to do the work of a DROP TABLE statement. ** pName is the name of the table to be dropped. @@ -109513,6 +130045,8 @@ SQLITE_PRIVATE void sqlite3DropTable(Parse *pParse, SrcList *pName, int isView, } assert( pParse->nErr==0 ); assert( pName->nSrc==1 ); + assert( pName->a[0].fg.fixedSchema==0 ); + assert( pName->a[0].fg.isSubquery==0 ); if( sqlite3ReadSchema(pParse) ) goto exit_drop_table; if( noErr ) db->suppressErr++; assert( isView==0 || isView==LOCATE_VIEW ); @@ -109520,7 +130054,10 @@ SQLITE_PRIVATE void sqlite3DropTable(Parse *pParse, SrcList *pName, int isView, if( noErr ) db->suppressErr--; if( pTab==0 ){ - if( noErr ) sqlite3CodeVerifyNamedSchema(pParse, pName->a[0].zDatabase); + if( noErr ){ + sqlite3CodeVerifyNamedSchema(pParse, pName->a[0].u4.zDatabase); + sqlite3ForceNotReadOnly(pParse); + } goto exit_drop_table; } iDb = sqlite3SchemaToIndex(db, pTab->pSchema); @@ -109567,8 +130104,7 @@ SQLITE_PRIVATE void sqlite3DropTable(Parse *pParse, SrcList *pName, int isView, } } #endif - if( sqlite3StrNICmp(pTab->zName, "sqlite_", 7)==0 - && sqlite3StrNICmp(pTab->zName, "sqlite_stat", 11)!=0 ){ + if( tableMayNotBeDropped(db, pTab) ){ sqlite3ErrorMsg(pParse, "table %s may not be dropped", pTab->zName); goto exit_drop_table; } @@ -109577,17 +130113,17 @@ SQLITE_PRIVATE void sqlite3DropTable(Parse *pParse, SrcList *pName, int isView, /* Ensure DROP TABLE is not used on a view, and DROP VIEW is not used ** on a table. */ - if( isView && pTab->pSelect==0 ){ + if( isView && !IsView(pTab) ){ sqlite3ErrorMsg(pParse, "use DROP TABLE to delete table %s", pTab->zName); goto exit_drop_table; } - if( !isView && pTab->pSelect ){ + if( !isView && IsView(pTab) ){ sqlite3ErrorMsg(pParse, "use DROP VIEW to delete view %s", pTab->zName); goto exit_drop_table; } #endif - /* Generate code to remove the table from the master table + /* Generate code to remove the table from the schema table ** on disk. */ v = sqlite3GetVdbe(pParse); @@ -109632,7 +130168,7 @@ SQLITE_PRIVATE void sqlite3CreateForeignKey( FKey *pFKey = 0; FKey *pNextTo; Table *p = pParse->pNewTable; - int nByte; + i64 nByte; int i; int nCol; char *z; @@ -109645,7 +130181,7 @@ SQLITE_PRIVATE void sqlite3CreateForeignKey( if( pToCol && pToCol->nExpr!=1 ){ sqlite3ErrorMsg(pParse, "foreign key on %s" " should reference only one column of table %T", - p->aCol[iCol].zName, pTo); + p->aCol[iCol].zCnName, pTo); goto fk_end; } nCol = 1; @@ -109657,10 +130193,10 @@ SQLITE_PRIVATE void sqlite3CreateForeignKey( }else{ nCol = pFromCol->nExpr; } - nByte = sizeof(*pFKey) + (nCol-1)*sizeof(pFKey->aCol[0]) + pTo->n + 1; + nByte = SZ_FKEY(nCol) + pTo->n + 1; if( pToCol ){ for(i=0; inExpr; i++){ - nByte += sqlite3Strlen30(pToCol->a[i].zName) + 1; + nByte += sqlite3Strlen30(pToCol->a[i].zEName) + 1; } } pFKey = sqlite3DbMallocZero(db, nByte ); @@ -109668,7 +130204,8 @@ SQLITE_PRIVATE void sqlite3CreateForeignKey( goto fk_end; } pFKey->pFrom = p; - pFKey->pNextFrom = p->pFKey; + assert( IsOrdinaryTable(p) ); + pFKey->pNextFrom = p->u.tab.pFKey; z = (char*)&pFKey->aCol[nCol]; pFKey->zTo = z; if( IN_RENAME_OBJECT ){ @@ -109685,30 +130222,30 @@ SQLITE_PRIVATE void sqlite3CreateForeignKey( for(i=0; inCol; j++){ - if( sqlite3StrICmp(p->aCol[j].zName, pFromCol->a[i].zName)==0 ){ + if( sqlite3StrICmp(p->aCol[j].zCnName, pFromCol->a[i].zEName)==0 ){ pFKey->aCol[i].iFrom = j; break; } } if( j>=p->nCol ){ - sqlite3ErrorMsg(pParse, - "unknown column \"%s\" in foreign key definition", - pFromCol->a[i].zName); + sqlite3ErrorMsg(pParse, + "unknown column \"%s\" in foreign key definition", + pFromCol->a[i].zEName); goto fk_end; } if( IN_RENAME_OBJECT ){ - sqlite3RenameTokenRemap(pParse, &pFKey->aCol[i], pFromCol->a[i].zName); + sqlite3RenameTokenRemap(pParse, &pFKey->aCol[i], pFromCol->a[i].zEName); } } } if( pToCol ){ for(i=0; ia[i].zName); + int n = sqlite3Strlen30(pToCol->a[i].zEName); pFKey->aCol[i].zCol = z; if( IN_RENAME_OBJECT ){ - sqlite3RenameTokenRemap(pParse, z, pToCol->a[i].zName); + sqlite3RenameTokenRemap(pParse, z, pToCol->a[i].zEName); } - memcpy(z, pToCol->a[i].zName, n); + memcpy(z, pToCol->a[i].zEName, n); z[n] = 0; z += n+1; } @@ -109718,7 +130255,7 @@ SQLITE_PRIVATE void sqlite3CreateForeignKey( pFKey->aAction[1] = (u8)((flags >> 8 ) & 0xff); /* ON UPDATE action */ assert( sqlite3SchemaMutexHeld(db, 0, p->pSchema) ); - pNextTo = (FKey *)sqlite3HashInsert(&p->pSchema->fkeyHash, + pNextTo = (FKey *)sqlite3HashInsert(&p->pSchema->fkeyHash, pFKey->zTo, (void *)pFKey ); if( pNextTo==pFKey ){ @@ -109733,7 +130270,8 @@ SQLITE_PRIVATE void sqlite3CreateForeignKey( /* Link the foreign key to the table as the last step. */ - p->pFKey = pFKey; + assert( IsOrdinaryTable(p) ); + p->u.tab.pFKey = pFKey; pFKey = 0; fk_end: @@ -109754,7 +130292,9 @@ SQLITE_PRIVATE void sqlite3DeferForeignKey(Parse *pParse, int isDeferred){ #ifndef SQLITE_OMIT_FOREIGN_KEY Table *pTab; FKey *pFKey; - if( (pTab = pParse->pNewTable)==0 || (pFKey = pTab->pFKey)==0 ) return; + if( (pTab = pParse->pNewTable)==0 ) return; + if( NEVER(!IsOrdinaryTable(pTab)) ) return; + if( (pFKey = pTab->u.tab.pFKey)==0 ) return; assert( isDeferred==0 || isDeferred==1 ); /* EV: R-30323-21917 */ pFKey->isDeferred = (u8)isDeferred; #endif @@ -109778,7 +130318,7 @@ static void sqlite3RefillIndex(Parse *pParse, Index *pIndex, int memRootPage){ int iSorter; /* Cursor opened by OpenSorter (if in use) */ int addr1; /* Address of top of loop */ int addr2; /* Address to jump to for next iteration */ - int tnum; /* Root page of index */ + Pgno tnum; /* Root page of index */ int iPartIdxLabel; /* Jump to this label to skip a row */ Vdbe *v; /* Generate code into this virtual machine */ KeyInfo *pKey; /* KeyInfo for index */ @@ -109799,12 +130339,12 @@ static void sqlite3RefillIndex(Parse *pParse, Index *pIndex, int memRootPage){ v = sqlite3GetVdbe(pParse); if( v==0 ) return; if( memRootPage>=0 ){ - tnum = memRootPage; + tnum = (Pgno)memRootPage; }else{ tnum = pIndex->tnum; } pKey = sqlite3KeyInfoOfIndex(pParse, pIndex); - assert( pKey!=0 || db->mallocFailed || pParse->nErr ); + assert( pKey!=0 || pParse->nErr ); /* Open the sorter cursor if we are to use one. */ iSorter = pParse->nTab++; @@ -109824,7 +130364,7 @@ static void sqlite3RefillIndex(Parse *pParse, Index *pIndex, int memRootPage){ sqlite3VdbeAddOp2(v, OP_Next, iTab, addr1+1); VdbeCoverage(v); sqlite3VdbeJumpHere(v, addr1); if( memRootPage<0 ) sqlite3VdbeAddOp2(v, OP_Clear, tnum, iDb); - sqlite3VdbeAddOp4(v, OP_OpenWrite, iIdx, tnum, iDb, + sqlite3VdbeAddOp4(v, OP_OpenWrite, iIdx, (int)tnum, iDb, (char *)pKey, P4_KEYINFO); sqlite3VdbeChangeP5(v, OPFLAG_BULKCSR|((memRootPage>=0)?OPFLAG_P2ISREG:0)); @@ -109838,10 +130378,27 @@ static void sqlite3RefillIndex(Parse *pParse, Index *pIndex, int memRootPage){ sqlite3UniqueConstraint(pParse, OE_Abort, pIndex); sqlite3VdbeJumpHere(v, j2); }else{ + /* Most CREATE INDEX and REINDEX statements that are not UNIQUE can not + ** abort. The exception is if one of the indexed expressions contains a + ** user function that throws an exception when it is evaluated. But the + ** overhead of adding a statement journal to a CREATE INDEX statement is + ** very small (since most of the pages written do not contain content that + ** needs to be restored if the statement aborts), so we call + ** sqlite3MayAbort() for all CREATE INDEX statements. */ + sqlite3MayAbort(pParse); addr2 = sqlite3VdbeCurrentAddr(v); } sqlite3VdbeAddOp3(v, OP_SorterData, iSorter, regRecord, iIdx); - sqlite3VdbeAddOp1(v, OP_SeekEnd, iIdx); + if( !pIndex->bAscKeyBug ){ + /* This OP_SeekEnd opcode makes index insert for a REINDEX go much + ** faster by avoiding unnecessary seeks. But the optimization does + ** not work for UNIQUE constraint indexes on WITHOUT ROWID tables + ** with DESC primary keys, since those indexes have there keys in + ** a different order from the main table. + ** See ticket: https://sqlite.org/src/info/bba7b69f9849b5bf + */ + sqlite3VdbeAddOp1(v, OP_SeekEnd, iIdx); + } sqlite3VdbeAddOp2(v, OP_IdxInsert, iIdx, regRecord); sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT); sqlite3ReleaseTempReg(pParse, regRecord); @@ -109862,13 +130419,14 @@ static void sqlite3RefillIndex(Parse *pParse, Index *pIndex, int memRootPage){ */ SQLITE_PRIVATE Index *sqlite3AllocateIndexObject( sqlite3 *db, /* Database connection */ - i16 nCol, /* Total number of columns in the index */ + int nCol, /* Total number of columns in the index */ int nExtra, /* Number of bytes of extra space to alloc */ char **ppExtra /* Pointer to the "extra" space */ ){ Index *p; /* Allocated index object */ - int nByte; /* Bytes of space for Index object + arrays */ + i64 nByte; /* Bytes of space for Index object + arrays */ + assert( nCol <= 2*db->aLimit[SQLITE_LIMIT_COLUMN] ); nByte = ROUND8(sizeof(Index)) + /* Index structure */ ROUND8(sizeof(char*)*nCol) + /* Index.azColl */ ROUND8(sizeof(LogEst)*(nCol+1) + /* Index.aiRowLogEst */ @@ -109881,16 +130439,38 @@ SQLITE_PRIVATE Index *sqlite3AllocateIndexObject( p->aiRowLogEst = (LogEst*)pExtra; pExtra += sizeof(LogEst)*(nCol+1); p->aiColumn = (i16*)pExtra; pExtra += sizeof(i16)*nCol; p->aSortOrder = (u8*)pExtra; - p->nColumn = nCol; - p->nKeyCol = nCol - 1; + assert( nCol>0 ); + p->nColumn = (u16)nCol; + p->nKeyCol = (u16)(nCol - 1); *ppExtra = ((char*)p) + nByte; } return p; } /* -** Create a new index for an SQL table. pName1.pName2 is the name of the index -** and pTblList is the name of the table that is to be indexed. Both will +** If expression list pList contains an expression that was parsed with +** an explicit "NULLS FIRST" or "NULLS LAST" clause, leave an error in +** pParse and return non-zero. Otherwise, return zero. +*/ +SQLITE_PRIVATE int sqlite3HasExplicitNulls(Parse *pParse, ExprList *pList){ + if( pList ){ + int i; + for(i=0; inExpr; i++){ + if( pList->a[i].fg.bNulls ){ + u8 sf = pList->a[i].fg.sortFlags; + sqlite3ErrorMsg(pParse, "unsupported use of NULLS %s", + (sf==0 || sf==3) ? "FIRST" : "LAST" + ); + return 1; + } + } + } + return 0; +} + +/* +** Create a new index for an SQL table. pName1.pName2 is the name of the index +** and pTblList is the name of the table that is to be indexed. Both will ** be NULL for a primary key or an index that is created to satisfy a ** UNIQUE constraint. If pTable and pIndex are NULL, use pParse->pNewTable ** as the table to be indexed. pParse->pNewTable is a table that is @@ -109898,7 +130478,7 @@ SQLITE_PRIVATE Index *sqlite3AllocateIndexObject( ** ** pList is a list of columns to be indexed. pList will be NULL if this ** is a primary key or unique-constraint on the most recent column added -** to the table currently under construction. +** to the table currently under construction. */ SQLITE_PRIVATE void sqlite3CreateIndex( Parse *pParse, /* All information about this parse */ @@ -109930,22 +130510,27 @@ SQLITE_PRIVATE void sqlite3CreateIndex( char *zExtra = 0; /* Extra space after the Index object */ Index *pPk = 0; /* PRIMARY KEY index for WITHOUT ROWID tables */ - if( db->mallocFailed || pParse->nErr>0 ){ + assert( db->pParse==pParse ); + if( pParse->nErr ){ goto exit_create_index; } + assert( db->mallocFailed==0 ); if( IN_DECLARE_VTAB && idxType!=SQLITE_IDXTYPE_PRIMARYKEY ){ goto exit_create_index; } if( SQLITE_OK!=sqlite3ReadSchema(pParse) ){ goto exit_create_index; } + if( sqlite3HasExplicitNulls(pParse, pList) ){ + goto exit_create_index; + } /* ** Find the table that is to be indexed. Return early if not found. */ if( pTblName!=0 ){ - /* Use the two-part index name to determine the database + /* Use the two-part index name to determine the database ** to search for the table. 'Fix' the table name to this db ** before looking up the table. */ @@ -109957,7 +130542,7 @@ SQLITE_PRIVATE void sqlite3CreateIndex( #ifndef SQLITE_OMIT_TEMPDB /* If the index name was unqualified, check if the table ** is a temp table. If so, set the database to 1. Do not do this - ** if initialising a database schema. + ** if initializing a database schema. */ if( !db->init.busy ){ pTab = sqlite3SrcListLookup(pParse, pTblName); @@ -109977,7 +130562,7 @@ SQLITE_PRIVATE void sqlite3CreateIndex( assert( db->mallocFailed==0 || pTab==0 ); if( pTab==0 ) goto exit_create_index; if( iDb==1 && db->aDb[iDb].pSchema!=pTab->pSchema ){ - sqlite3ErrorMsg(pParse, + sqlite3ErrorMsg(pParse, "cannot create a TEMP index on non-TEMP table \"%s\"", pTab->zName); goto exit_create_index; @@ -109993,22 +130578,15 @@ SQLITE_PRIVATE void sqlite3CreateIndex( pDb = &db->aDb[iDb]; assert( pTab!=0 ); - assert( pParse->nErr==0 ); - if( sqlite3StrNICmp(pTab->zName, "sqlite_", 7)==0 + if( sqlite3StrNICmp(pTab->zName, "sqlite_", 7)==0 && db->init.busy==0 && pTblName!=0 -#if SQLITE_USER_AUTHENTICATION - && sqlite3UserAuthTable(pTab->zName)==0 -#endif -#ifdef SQLITE_ALLOW_SQLITE_MASTER_INDEX - && sqlite3StrICmp(&pTab->zName[7],"master")!=0 -#endif - ){ + ){ sqlite3ErrorMsg(pParse, "table %s may not be indexed", pTab->zName); goto exit_create_index; } #ifndef SQLITE_OMIT_VIEW - if( pTab->pSelect ){ + if( IsView(pTab) ){ sqlite3ErrorMsg(pParse, "views may not be indexed"); goto exit_create_index; } @@ -110022,10 +130600,10 @@ SQLITE_PRIVATE void sqlite3CreateIndex( /* ** Find the name of the index. Make sure there is not already another - ** index or table with the same name. + ** index or table with the same name. ** ** Exception: If we are reading the names of permanent indices from the - ** sqlite_master table (because some other process changed the schema) and + ** sqlite_schema table (because some other process changed the schema) and ** one of the index names collides with the name of a temporary table or ** index, then we will continue to process this index. ** @@ -110037,12 +130615,12 @@ SQLITE_PRIVATE void sqlite3CreateIndex( zName = sqlite3NameFromToken(db, pName); if( zName==0 ) goto exit_create_index; assert( pName->z!=0 ); - if( SQLITE_OK!=sqlite3CheckObjectName(pParse, zName) ){ + if( SQLITE_OK!=sqlite3CheckObjectName(pParse, zName,"index",pTab->zName) ){ goto exit_create_index; } if( !IN_RENAME_OBJECT ){ if( !db->init.busy ){ - if( sqlite3FindTable(db, zName, 0)!=0 ){ + if( sqlite3FindTable(db, zName, pDb->zDbSName)!=0 ){ sqlite3ErrorMsg(pParse, "there is already a table named %s", zName); goto exit_create_index; } @@ -110053,6 +130631,7 @@ SQLITE_PRIVATE void sqlite3CreateIndex( }else{ assert( !db->init.busy ); sqlite3CodeVerifySchema(pParse, iDb); + sqlite3ForceNotReadOnly(pParse); } goto exit_create_index; } @@ -110098,12 +130677,12 @@ SQLITE_PRIVATE void sqlite3CreateIndex( Token prevCol; Column *pCol = &pTab->aCol[pTab->nCol-1]; pCol->colFlags |= COLFLAG_UNIQUE; - sqlite3TokenInit(&prevCol, pCol->zName); + sqlite3TokenInit(&prevCol, pCol->zCnName); pList = sqlite3ExprListAppend(pParse, 0, sqlite3ExprAlloc(db, TK_ID, &prevCol, 0)); if( pList==0 ) goto exit_create_index; assert( pList->nExpr==1 ); - sqlite3ExprListSetSortOrder(pList, sortOrder); + sqlite3ExprListSetSortOrder(pList, sortOrder, SQLITE_SO_UNDEFINED); }else{ sqlite3ExprListCheckLength(pParse, pList, "index"); if( pParse->nErr ) goto exit_create_index; @@ -110116,12 +130695,13 @@ SQLITE_PRIVATE void sqlite3CreateIndex( Expr *pExpr = pList->a[i].pExpr; assert( pExpr!=0 ); if( pExpr->op==TK_COLLATE ){ + assert( !ExprHasProperty(pExpr, EP_IntValue) ); nExtra += (1 + sqlite3Strlen30(pExpr->u.zToken)); } } - /* - ** Allocate the index structure. + /* + ** Allocate the index structure. */ nName = sqlite3Strlen30(zName); nExtraCol = pPk ? pPk->nKeyCol : 1; @@ -110193,19 +130773,27 @@ SQLITE_PRIVATE void sqlite3CreateIndex( j = XN_EXPR; pIndex->aiColumn[i] = XN_EXPR; pIndex->uniqNotNull = 0; + pIndex->bHasExpr = 1; }else{ j = pCExpr->iColumn; assert( j<=0x7fff ); if( j<0 ){ j = pTab->iPKey; - }else if( pTab->aCol[j].notNull==0 ){ - pIndex->uniqNotNull = 0; + }else{ + if( pTab->aCol[j].notNull==0 ){ + pIndex->uniqNotNull = 0; + } + if( pTab->aCol[j].colFlags & COLFLAG_VIRTUAL ){ + pIndex->bHasVCol = 1; + pIndex->bHasExpr = 1; + } } pIndex->aiColumn[i] = (i16)j; } zColl = 0; if( pListItem->pExpr->op==TK_COLLATE ){ int nColl; + assert( !ExprHasProperty(pListItem->pExpr, EP_IntValue) ); zColl = pListItem->pExpr->u.zToken; nColl = sqlite3Strlen30(zColl) + 1; assert( nExtra>=nColl ); @@ -110214,14 +130802,14 @@ SQLITE_PRIVATE void sqlite3CreateIndex( zExtra += nColl; nExtra -= nColl; }else if( j>=0 ){ - zColl = pTab->aCol[j].zColl; + zColl = sqlite3ColumnColl(&pTab->aCol[j]); } if( !zColl ) zColl = sqlite3StrBINARY; if( !db->init.busy && !sqlite3LocateCollSeq(pParse, zColl) ){ goto exit_create_index; } pIndex->azColl[i] = zColl; - requestedSortOrder = pListItem->sortOrder & sortOrderMask; + requestedSortOrder = pListItem->fg.sortFlags & sortOrderMask; pIndex->aSortOrder[i] = (u8)requestedSortOrder; } @@ -110233,9 +130821,10 @@ SQLITE_PRIVATE void sqlite3CreateIndex( for(j=0; jnKeyCol; j++){ int x = pPk->aiColumn[j]; assert( x>=0 ); - if( hasColumn(pIndex->aiColumn, pIndex->nKeyCol, x) ){ - pIndex->nColumn--; + if( isDupColumn(pIndex, pIndex->nKeyCol, pPk, j) ){ + pIndex->nColumn--; }else{ + testcase( hasColumn(pIndex->aiColumn,pIndex->nKeyCol,x) ); pIndex->aiColumn[i] = x; pIndex->azColl[i] = pPk->azColl[j]; pIndex->aSortOrder[i] = pPk->aSortOrder[j]; @@ -110252,14 +130841,14 @@ SQLITE_PRIVATE void sqlite3CreateIndex( /* If this index contains every column of its table, then mark ** it as a covering index */ - assert( HasRowid(pTab) - || pTab->iPKey<0 || sqlite3ColumnOfIndex(pIndex, pTab->iPKey)>=0 ); + assert( HasRowid(pTab) + || pTab->iPKey<0 || sqlite3TableColumnToIndex(pIndex, pTab->iPKey)>=0 ); recomputeColumnsNotIndexed(pIndex); if( pTblName!=0 && pIndex->nColumn>=pTab->nCol ){ pIndex->isCovering = 1; for(j=0; jnCol; j++){ if( j==pTab->iPKey ) continue; - if( sqlite3ColumnOfIndex(pIndex,j)>=0 ) continue; + if( sqlite3TableColumnToIndex(pIndex,j)>=0 ) continue; pIndex->isCovering = 0; break; } @@ -110308,13 +130897,13 @@ SQLITE_PRIVATE void sqlite3CreateIndex( if( pIdx->onError!=pIndex->onError ){ /* This constraint creates the same index as a previous ** constraint specified somewhere in the CREATE TABLE statement. - ** However the ON CONFLICT clauses are different. If both this + ** However the ON CONFLICT clauses are different. If both this ** constraint and the previous equivalent constraint have explicit ** ON CONFLICT clauses this is an error. Otherwise, use the ** explicitly specified behavior for the index. */ if( !(pIdx->onError==OE_Default || pIndex->onError==OE_Default) ){ - sqlite3ErrorMsg(pParse, + sqlite3ErrorMsg(pParse, "conflicting ON CONFLICT clauses specified", 0); } if( pIdx->onError==OE_Default ){ @@ -110335,7 +130924,7 @@ SQLITE_PRIVATE void sqlite3CreateIndex( if( !IN_RENAME_OBJECT ){ /* Link the new Index structure to its table and to the other - ** in-memory database structures. + ** in-memory database structures. */ assert( pParse->nErr==0 ); if( db->init.busy ){ @@ -110350,7 +130939,7 @@ SQLITE_PRIVATE void sqlite3CreateIndex( goto exit_create_index; } } - p = sqlite3HashInsert(&pIndex->pSchema->idxHash, + p = sqlite3HashInsert(&pIndex->pSchema->idxHash, pIndex->zName, pIndex); if( p ){ assert( p==pIndex ); /* Malloc must have failed */ @@ -110363,8 +130952,8 @@ SQLITE_PRIVATE void sqlite3CreateIndex( /* If this is the initial CREATE INDEX statement (or CREATE TABLE if the ** index is an implied index for a UNIQUE or PRIMARY KEY constraint) then ** emit code to allocate the index rootpage on disk and make an entry for - ** the index in the sqlite_master table and populate the index with - ** content. But, do not do this if we are simply reading the sqlite_master + ** the index in the sqlite_schema table and populate the index with + ** content. But, do not do this if we are simply reading the sqlite_schema ** table to parse the schema, or if this index is the PRIMARY KEY index ** of a WITHOUT ROWID table. ** @@ -110384,17 +130973,18 @@ SQLITE_PRIVATE void sqlite3CreateIndex( sqlite3BeginWriteOperation(pParse, 1, iDb); /* Create the rootpage for the index using CreateIndex. But before - ** doing so, code a Noop instruction and store its address in - ** Index.tnum. This is required in case this index is actually a - ** PRIMARY KEY and the table is actually a WITHOUT ROWID table. In + ** doing so, code a Noop instruction and store its address in + ** Index.tnum. This is required in case this index is actually a + ** PRIMARY KEY and the table is actually a WITHOUT ROWID table. In ** that case the convertToWithoutRowidTable() routine will replace ** the Noop with a Goto to jump over the VDBE code generated below. */ - pIndex->tnum = sqlite3VdbeAddOp0(v, OP_Noop); + pIndex->tnum = (Pgno)sqlite3VdbeAddOp0(v, OP_Noop); sqlite3VdbeAddOp3(v, OP_CreateBtree, iDb, iMem, BTREE_BLOBKEY); /* Gather the complete text of the CREATE INDEX statement into ** the zStmt variable */ + assert( pName!=0 || pStart==0 ); if( pStart ){ int n = (int)(pParse->sLastToken.z - pName->z) + pParse->sLastToken.n; if( pName->z[n-1]==';' ) n--; @@ -110407,16 +130997,16 @@ SQLITE_PRIVATE void sqlite3CreateIndex( zStmt = 0; } - /* Add an entry in sqlite_master for this index + /* Add an entry in sqlite_schema for this index */ - sqlite3NestedParse(pParse, - "INSERT INTO %Q.%s VALUES('index',%Q,%Q,#%d,%Q);", - db->aDb[iDb].zDbSName, MASTER_NAME, - pIndex->zName, - pTab->zName, - iMem, - zStmt - ); + sqlite3NestedParse(pParse, + "INSERT INTO %Q." LEGACY_SCHEMA_TABLE " VALUES('index',%Q,%Q,#%d,%Q);", + db->aDb[iDb].zDbSName, + pIndex->zName, + pTab->zName, + iMem, + zStmt + ); sqlite3DbFree(db, zStmt); /* Fill the index with data and reparse the schema. Code an OP_Expire @@ -110426,33 +131016,16 @@ SQLITE_PRIVATE void sqlite3CreateIndex( sqlite3RefillIndex(pParse, pIndex, iMem); sqlite3ChangeCookie(pParse, iDb); sqlite3VdbeAddParseSchemaOp(v, iDb, - sqlite3MPrintf(db, "name='%q' AND type='index'", pIndex->zName)); + sqlite3MPrintf(db, "name='%q' AND type='index'", pIndex->zName), 0); sqlite3VdbeAddOp2(v, OP_Expire, 0, 1); } - sqlite3VdbeJumpHere(v, pIndex->tnum); + sqlite3VdbeJumpHere(v, (int)pIndex->tnum); } } - - /* When adding an index to the list of indices for a table, make - ** sure all indices labeled OE_Replace come after all those labeled - ** OE_Ignore. This is necessary for the correct constraint check - ** processing (in sqlite3GenerateConstraintChecks()) as part of - ** UPDATE and INSERT statements. - */ if( db->init.busy || pTblName==0 ){ - if( onError!=OE_Replace || pTab->pIndex==0 - || pTab->pIndex->onError==OE_Replace){ - pIndex->pNext = pTab->pIndex; - pTab->pIndex = pIndex; - }else{ - Index *pOther = pTab->pIndex; - while( pOther->pNext && pOther->pNext->onError!=OE_Replace ){ - pOther = pOther->pNext; - } - pIndex->pNext = pOther->pNext; - pOther->pNext = pIndex; - } + pIndex->pNext = pTab->pIndex; + pTab->pIndex = pIndex; pIndex = 0; } else if( IN_RENAME_OBJECT ){ @@ -110464,6 +131037,35 @@ SQLITE_PRIVATE void sqlite3CreateIndex( /* Clean up before exiting */ exit_create_index: if( pIndex ) sqlite3FreeIndex(db, pIndex); + if( pTab ){ + /* Ensure all REPLACE indexes on pTab are at the end of the pIndex list. + ** The list was already ordered when this routine was entered, so at this + ** point at most a single index (the newly added index) will be out of + ** order. So we have to reorder at most one index. */ + Index **ppFrom; + Index *pThis; + for(ppFrom=&pTab->pIndex; (pThis = *ppFrom)!=0; ppFrom=&pThis->pNext){ + Index *pNext; + if( pThis->onError!=OE_Replace ) continue; + while( (pNext = pThis->pNext)!=0 && pNext->onError!=OE_Replace ){ + *ppFrom = pNext; + pThis->pNext = pNext->pNext; + pNext->pNext = pThis; + ppFrom = &pNext->pNext; + } + break; + } +#ifdef SQLITE_DEBUG + /* Verify that all REPLACE indexes really are now at the end + ** of the index list. In other words, no other index type ever + ** comes after a REPLACE index on the list. */ + for(pThis = pTab->pIndex; pThis; pThis=pThis->pNext){ + assert( pThis->onError!=OE_Replace + || pThis->pNext==0 + || pThis->pNext->onError==OE_Replace ); + } +#endif + } sqlite3ExprDelete(db, pPIWhere); sqlite3ExprListDelete(db, pList); sqlite3SrcListDelete(db, pTblName); @@ -110489,21 +131091,33 @@ SQLITE_PRIVATE void sqlite3CreateIndex( ** are based on typical values found in actual indices. */ SQLITE_PRIVATE void sqlite3DefaultRowEst(Index *pIdx){ - /* 10, 9, 8, 7, 6 */ - LogEst aVal[] = { 33, 32, 30, 28, 26 }; + /* 10, 9, 8, 7, 6 */ + static const LogEst aVal[] = { 33, 32, 30, 28, 26 }; LogEst *a = pIdx->aiRowLogEst; + LogEst x; int nCopy = MIN(ArraySize(aVal), pIdx->nKeyCol); int i; /* Indexes with default row estimates should not have stat1 data */ assert( !pIdx->hasStat1 ); - /* Set the first entry (number of rows in the index) to the estimated + /* Set the first entry (number of rows in the index) to the estimated ** number of rows in the table, or half the number of rows in the table - ** for a partial index. But do not let the estimate drop below 10. */ - a[0] = pIdx->pTable->nRowLogEst; - if( pIdx->pPartIdxWhere!=0 ) a[0] -= 10; assert( 10==sqlite3LogEst(2) ); - if( a[0]<33 ) a[0] = 33; assert( 33==sqlite3LogEst(10) ); + ** for a partial index. + ** + ** 2020-05-27: If some of the stat data is coming from the sqlite_stat1 + ** table but other parts we are having to guess at, then do not let the + ** estimated number of rows in the table be less than 1000 (LogEst 99). + ** Failure to do this can cause the indexes for which we do not have + ** stat1 data to be ignored by the query planner. + */ + x = pIdx->pTable->nRowLogEst; + assert( 99==sqlite3LogEst(1000) ); + if( x<99 ){ + pIdx->pTable->nRowLogEst = x = 99; + } + if( pIdx->pPartIdxWhere!=0 ){ x -= 10; assert( 10==sqlite3LogEst(2) ); } + a[0] = x; /* Estimate that a[1] is 10, a[2] is 9, a[3] is 8, a[4] is 7, a[5] is ** 6 and each subsequent value (if any) is 5. */ @@ -110526,20 +131140,23 @@ SQLITE_PRIVATE void sqlite3DropIndex(Parse *pParse, SrcList *pName, int ifExists sqlite3 *db = pParse->db; int iDb; - assert( pParse->nErr==0 ); /* Never called with prior errors */ if( db->mallocFailed ){ goto exit_drop_index; } + assert( pParse->nErr==0 ); /* Never called with prior non-OOM errors */ assert( pName->nSrc==1 ); + assert( pName->a[0].fg.fixedSchema==0 ); + assert( pName->a[0].fg.isSubquery==0 ); if( SQLITE_OK!=sqlite3ReadSchema(pParse) ){ goto exit_drop_index; } - pIndex = sqlite3FindIndex(db, pName->a[0].zName, pName->a[0].zDatabase); + pIndex = sqlite3FindIndex(db, pName->a[0].zName, pName->a[0].u4.zDatabase); if( pIndex==0 ){ if( !ifExists ){ - sqlite3ErrorMsg(pParse, "no such index: %S", pName, 0); + sqlite3ErrorMsg(pParse, "no such index: %S", pName->a); }else{ - sqlite3CodeVerifyNamedSchema(pParse, pName->a[0].zDatabase); + sqlite3CodeVerifyNamedSchema(pParse, pName->a[0].u4.zDatabase); + sqlite3ForceNotReadOnly(pParse); } pParse->checkSchema = 1; goto exit_drop_index; @@ -110550,6 +131167,7 @@ SQLITE_PRIVATE void sqlite3DropIndex(Parse *pParse, SrcList *pName, int ifExists goto exit_drop_index; } iDb = sqlite3SchemaToIndex(db, pIndex->pSchema); + assert( iDb>=0 && iDbnDb ); #ifndef SQLITE_OMIT_AUTHORIZATION { int code = SQLITE_DROP_INDEX; @@ -110559,20 +131177,20 @@ SQLITE_PRIVATE void sqlite3DropIndex(Parse *pParse, SrcList *pName, int ifExists if( sqlite3AuthCheck(pParse, SQLITE_DELETE, zTab, 0, zDb) ){ goto exit_drop_index; } - if( !OMIT_TEMPDB && iDb ) code = SQLITE_DROP_TEMP_INDEX; + if( !OMIT_TEMPDB && iDb==1 ) code = SQLITE_DROP_TEMP_INDEX; if( sqlite3AuthCheck(pParse, code, pIndex->zName, pTab->zName, zDb) ){ goto exit_drop_index; } } #endif - /* Generate code to remove the index and from the master table */ + /* Generate code to remove the index and from the schema table */ v = sqlite3GetVdbe(pParse); if( v ){ sqlite3BeginWriteOperation(pParse, 1, iDb); sqlite3NestedParse(pParse, - "DELETE FROM %Q.%s WHERE name=%Q AND type='index'", - db->aDb[iDb].zDbSName, MASTER_NAME, pIndex->zName + "DELETE FROM %Q." LEGACY_SCHEMA_TABLE " WHERE name=%Q AND type='index'", + db->aDb[iDb].zDbSName, pIndex->zName ); sqlite3ClearStatTables(pParse, iDb, "idx", pIndex->zName); sqlite3ChangeCookie(pParse, iDb); @@ -110635,20 +131253,18 @@ SQLITE_PRIVATE IdList *sqlite3IdListAppend(Parse *pParse, IdList *pList, Token * sqlite3 *db = pParse->db; int i; if( pList==0 ){ - pList = sqlite3DbMallocZero(db, sizeof(IdList) ); + pList = sqlite3DbMallocZero(db, SZ_IDLIST(1)); if( pList==0 ) return 0; + }else{ + IdList *pNew; + pNew = sqlite3DbRealloc(db, pList, SZ_IDLIST(pList->nId+1)); + if( pNew==0 ){ + sqlite3IdListDelete(db, pList); + return 0; + } + pList = pNew; } - pList->a = sqlite3ArrayAllocate( - db, - pList->a, - sizeof(pList->a[0]), - &pList->nId, - &i - ); - if( i<0 ){ - sqlite3IdListDelete(db, pList); - return 0; - } + i = pList->nId++; pList->a[i].zName = sqlite3NameFromToken(db, pToken); if( IN_RENAME_OBJECT && pList->a[i].zName ){ sqlite3RenameTokenMap(pParse, (void*)pList->a[i].zName, pToken); @@ -110661,12 +131277,12 @@ SQLITE_PRIVATE IdList *sqlite3IdListAppend(Parse *pParse, IdList *pList, Token * */ SQLITE_PRIVATE void sqlite3IdListDelete(sqlite3 *db, IdList *pList){ int i; + assert( db!=0 ); if( pList==0 ) return; for(i=0; inId; i++){ sqlite3DbFree(db, pList->a[i].zName); } - sqlite3DbFree(db, pList->a); - sqlite3DbFreeNN(db, pList); + sqlite3DbNNFreeNN(db, pList); } /* @@ -110675,7 +131291,7 @@ SQLITE_PRIVATE void sqlite3IdListDelete(sqlite3 *db, IdList *pList){ */ SQLITE_PRIVATE int sqlite3IdListIndex(IdList *pList, const char *zName){ int i; - if( pList==0 ) return -1; + assert( pList!=0 ); for(i=0; inId; i++){ if( sqlite3StrICmp(pList->a[i].zName, zName)==0 ) return i; } @@ -110740,8 +131356,7 @@ SQLITE_PRIVATE SrcList *sqlite3SrcListEnlarge( return 0; } if( nAlloc>SQLITE_MAX_SRCLIST ) nAlloc = SQLITE_MAX_SRCLIST; - pNew = sqlite3DbRealloc(db, pSrc, - sizeof(*pSrc) + (nAlloc-1)*sizeof(pSrc->a[0]) ); + pNew = sqlite3DbRealloc(db, pSrc, SZ_SRCLIST(nAlloc)); if( pNew==0 ){ assert( db->mallocFailed ); return 0; @@ -110782,7 +131397,7 @@ SQLITE_PRIVATE SrcList *sqlite3SrcListEnlarge( ** database name prefix. Like this: "database.table". The pDatabase ** points to the table name and the pTable points to the database name. ** The SrcList.a[].zName field is filled with the table name which might -** come from pTable (if pDatabase is NULL) or from pDatabase. +** come from pTable (if pDatabase is NULL) or from pDatabase. ** SrcList.a[].zDatabase is filled with the database name from pTable, ** or with NULL if no database is specified. ** @@ -110809,14 +131424,14 @@ SQLITE_PRIVATE SrcList *sqlite3SrcListAppend( Token *pTable, /* Table to append */ Token *pDatabase /* Database of the table */ ){ - struct SrcList_item *pItem; + SrcItem *pItem; sqlite3 *db; assert( pDatabase==0 || pTable!=0 ); /* Cannot have C without B */ assert( pParse!=0 ); assert( pParse->db!=0 ); db = pParse->db; if( pList==0 ){ - pList = sqlite3DbMallocRawNN(pParse->db, sizeof(SrcList) ); + pList = sqlite3DbMallocRawNN(pParse->db, SZ_SRCLIST(1)); if( pList==0 ) return 0; pList->nAlloc = 1; pList->nSrc = 1; @@ -110835,12 +131450,14 @@ SQLITE_PRIVATE SrcList *sqlite3SrcListAppend( if( pDatabase && pDatabase->z==0 ){ pDatabase = 0; } + assert( pItem->fg.fixedSchema==0 ); + assert( pItem->fg.isSubquery==0 ); if( pDatabase ){ pItem->zName = sqlite3NameFromToken(db, pDatabase); - pItem->zDatabase = sqlite3NameFromToken(db, pTable); + pItem->u4.zDatabase = sqlite3NameFromToken(db, pTable); }else{ pItem->zName = sqlite3NameFromToken(db, pTable); - pItem->zDatabase = 0; + pItem->u4.zDatabase = 0; } return pList; } @@ -110850,40 +131467,130 @@ SQLITE_PRIVATE SrcList *sqlite3SrcListAppend( */ SQLITE_PRIVATE void sqlite3SrcListAssignCursors(Parse *pParse, SrcList *pList){ int i; - struct SrcList_item *pItem; - assert(pList || pParse->db->mallocFailed ); - if( pList ){ + SrcItem *pItem; + assert( pList || pParse->db->mallocFailed ); + if( ALWAYS(pList) ){ for(i=0, pItem=pList->a; inSrc; i++, pItem++){ - if( pItem->iCursor>=0 ) break; + if( pItem->iCursor>=0 ) continue; pItem->iCursor = pParse->nTab++; - if( pItem->pSelect ){ - sqlite3SrcListAssignCursors(pParse, pItem->pSelect->pSrc); + if( pItem->fg.isSubquery ){ + assert( pItem->u4.pSubq!=0 ); + assert( pItem->u4.pSubq->pSelect!=0 ); + assert( pItem->u4.pSubq->pSelect->pSrc!=0 ); + sqlite3SrcListAssignCursors(pParse, pItem->u4.pSubq->pSelect->pSrc); } } } } +/* +** Delete a Subquery object and its substructure. +*/ +SQLITE_PRIVATE void sqlite3SubqueryDelete(sqlite3 *db, Subquery *pSubq){ + assert( pSubq!=0 && pSubq->pSelect!=0 ); + sqlite3SelectDelete(db, pSubq->pSelect); + sqlite3DbFree(db, pSubq); +} + +/* +** Remove a Subquery from a SrcItem. Return the associated Select object. +** The returned Select becomes the responsibility of the caller. +*/ +SQLITE_PRIVATE Select *sqlite3SubqueryDetach(sqlite3 *db, SrcItem *pItem){ + Select *pSel; + assert( pItem!=0 ); + assert( pItem->fg.isSubquery ); + pSel = pItem->u4.pSubq->pSelect; + sqlite3DbFree(db, pItem->u4.pSubq); + pItem->u4.pSubq = 0; + pItem->fg.isSubquery = 0; + return pSel; +} + /* ** Delete an entire SrcList including all its substructure. */ SQLITE_PRIVATE void sqlite3SrcListDelete(sqlite3 *db, SrcList *pList){ int i; - struct SrcList_item *pItem; + SrcItem *pItem; + assert( db!=0 ); if( pList==0 ) return; for(pItem=pList->a, i=0; inSrc; i++, pItem++){ - sqlite3DbFree(db, pItem->zDatabase); - sqlite3DbFree(db, pItem->zName); - sqlite3DbFree(db, pItem->zAlias); + + /* Check invariants on SrcItem */ + assert( !pItem->fg.isIndexedBy || !pItem->fg.isTabFunc ); + assert( !pItem->fg.isCte || !pItem->fg.isIndexedBy ); + assert( !pItem->fg.fixedSchema || !pItem->fg.isSubquery ); + assert( !pItem->fg.isSubquery || (pItem->u4.pSubq!=0 && + pItem->u4.pSubq->pSelect!=0) ); + + if( pItem->zName ) sqlite3DbNNFreeNN(db, pItem->zName); + if( pItem->zAlias ) sqlite3DbNNFreeNN(db, pItem->zAlias); + if( pItem->fg.isSubquery ){ + sqlite3SubqueryDelete(db, pItem->u4.pSubq); + }else if( pItem->fg.fixedSchema==0 && pItem->u4.zDatabase!=0 ){ + sqlite3DbNNFreeNN(db, pItem->u4.zDatabase); + } if( pItem->fg.isIndexedBy ) sqlite3DbFree(db, pItem->u1.zIndexedBy); if( pItem->fg.isTabFunc ) sqlite3ExprListDelete(db, pItem->u1.pFuncArg); - sqlite3DeleteTable(db, pItem->pTab); - sqlite3SelectDelete(db, pItem->pSelect); - sqlite3ExprDelete(db, pItem->pOn); - sqlite3IdListDelete(db, pItem->pUsing); + sqlite3DeleteTable(db, pItem->pSTab); + if( pItem->fg.isUsing ){ + sqlite3IdListDelete(db, pItem->u3.pUsing); + }else if( pItem->u3.pOn ){ + sqlite3ExprDelete(db, pItem->u3.pOn); + } + } + sqlite3DbNNFreeNN(db, pList); +} + +/* +** Attach a Subquery object to pItem->uv.pSubq. Set the +** pSelect value but leave all the other values initialized +** to zero. +** +** A copy of the Select object is made if dupSelect is true, and the +** SrcItem takes responsibility for deleting the copy. If dupSelect is +** false, ownership of the Select passes to the SrcItem. Either way, +** the SrcItem will take responsibility for deleting the Select. +** +** When dupSelect is zero, that means the Select might get deleted right +** away if there is an OOM error. Beware. +** +** Return non-zero on success. Return zero on an OOM error. +*/ +SQLITE_PRIVATE int sqlite3SrcItemAttachSubquery( + Parse *pParse, /* Parsing context */ + SrcItem *pItem, /* Item to which the subquery is to be attached */ + Select *pSelect, /* The subquery SELECT. Must be non-NULL */ + int dupSelect /* If true, attach a copy of pSelect, not pSelect itself.*/ +){ + Subquery *p; + assert( pSelect!=0 ); + assert( pItem->fg.isSubquery==0 ); + if( pItem->fg.fixedSchema ){ + pItem->u4.pSchema = 0; + pItem->fg.fixedSchema = 0; + }else if( pItem->u4.zDatabase!=0 ){ + sqlite3DbFree(pParse->db, pItem->u4.zDatabase); + pItem->u4.zDatabase = 0; + } + if( dupSelect ){ + pSelect = sqlite3SelectDup(pParse->db, pSelect, 0); + if( pSelect==0 ) return 0; + } + p = pItem->u4.pSubq = sqlite3DbMallocRawNN(pParse->db, sizeof(Subquery)); + if( p==0 ){ + sqlite3SelectDelete(pParse->db, pSelect); + return 0; } - sqlite3DbFreeNN(db, pList); + pItem->fg.isSubquery = 1; + p->pSelect = pSelect; + assert( offsetof(Subquery, pSelect)==0 ); + memset(((char*)p)+sizeof(p->pSelect), 0, sizeof(*p)-sizeof(p->pSelect)); + return 1; } + /* ** This routine is called by the parser to add a new term to the ** end of a growing FROM clause. The "p" parameter is the part of @@ -110907,14 +131614,13 @@ SQLITE_PRIVATE SrcList *sqlite3SrcListAppendFromTerm( Token *pDatabase, /* Name of the database containing pTable */ Token *pAlias, /* The right-hand side of the AS subexpression */ Select *pSubquery, /* A subquery used in place of a table name */ - Expr *pOn, /* The ON clause of a join */ - IdList *pUsing /* The USING clause of a join */ + OnOrUsing *pOnUsing /* Either the ON clause or the USING clause */ ){ - struct SrcList_item *pItem; + SrcItem *pItem; sqlite3 *db = pParse->db; - if( !p && (pOn || pUsing) ){ - sqlite3ErrorMsg(pParse, "a JOIN clause is required before %s", - (pOn ? "ON" : "USING") + if( !p && pOnUsing!=0 && (pOnUsing->pOn || pOnUsing->pUsing) ){ + sqlite3ErrorMsg(pParse, "a JOIN clause is required before %s", + (pOnUsing->pOn ? "ON" : "USING") ); goto append_from_error; } @@ -110934,50 +131640,92 @@ SQLITE_PRIVATE SrcList *sqlite3SrcListAppendFromTerm( if( pAlias->n ){ pItem->zAlias = sqlite3NameFromToken(db, pAlias); } - pItem->pSelect = pSubquery; - pItem->pOn = pOn; - pItem->pUsing = pUsing; + assert( pSubquery==0 || pDatabase==0 ); + if( pSubquery ){ + if( sqlite3SrcItemAttachSubquery(pParse, pItem, pSubquery, 0) ){ + if( pSubquery->selFlags & SF_NestedFrom ){ + pItem->fg.isNestedFrom = 1; + } + } + } + assert( pOnUsing==0 || pOnUsing->pOn==0 || pOnUsing->pUsing==0 ); + assert( pItem->fg.isUsing==0 ); + if( pOnUsing==0 ){ + pItem->u3.pOn = 0; + }else if( pOnUsing->pUsing ){ + pItem->fg.isUsing = 1; + pItem->u3.pUsing = pOnUsing->pUsing; + }else{ + pItem->u3.pOn = pOnUsing->pOn; + } return p; - append_from_error: +append_from_error: assert( p==0 ); - sqlite3ExprDelete(db, pOn); - sqlite3IdListDelete(db, pUsing); + sqlite3ClearOnOrUsing(db, pOnUsing); sqlite3SelectDelete(db, pSubquery); return 0; } /* -** Add an INDEXED BY or NOT INDEXED clause to the most recently added +** Add an INDEXED BY or NOT INDEXED clause to the most recently added ** element of the source-list passed as the second argument. */ SQLITE_PRIVATE void sqlite3SrcListIndexedBy(Parse *pParse, SrcList *p, Token *pIndexedBy){ assert( pIndexedBy!=0 ); if( p && pIndexedBy->n>0 ){ - struct SrcList_item *pItem; + SrcItem *pItem; assert( p->nSrc>0 ); pItem = &p->a[p->nSrc-1]; assert( pItem->fg.notIndexed==0 ); assert( pItem->fg.isIndexedBy==0 ); assert( pItem->fg.isTabFunc==0 ); if( pIndexedBy->n==1 && !pIndexedBy->z ){ - /* A "NOT INDEXED" clause was supplied. See parse.y + /* A "NOT INDEXED" clause was supplied. See parse.y ** construct "indexed_opt" for details. */ pItem->fg.notIndexed = 1; }else{ pItem->u1.zIndexedBy = sqlite3NameFromToken(pParse->db, pIndexedBy); pItem->fg.isIndexedBy = 1; + assert( pItem->fg.isCte==0 ); /* No collision on union u2 */ } } } +/* +** Append the contents of SrcList p2 to SrcList p1 and return the resulting +** SrcList. Or, if an error occurs, return NULL. In all cases, p1 and p2 +** are deleted by this function. +*/ +SQLITE_PRIVATE SrcList *sqlite3SrcListAppendList(Parse *pParse, SrcList *p1, SrcList *p2){ + assert( p1 ); + assert( p2 || pParse->nErr ); + assert( p2==0 || p2->nSrc>=1 ); + testcase( p1->nSrc==0 ); + if( p2 ){ + int nOld = p1->nSrc; + SrcList *pNew = sqlite3SrcListEnlarge(pParse, p1, p2->nSrc, nOld); + if( pNew==0 ){ + sqlite3SrcListDelete(pParse->db, p2); + }else{ + p1 = pNew; + memcpy(&p1->a[nOld], p2->a, p2->nSrc*sizeof(SrcItem)); + assert( nOld==1 || (p2->a[0].fg.jointype & JT_LTORJ)==0 ); + assert( p1->nSrc>=1 ); + p1->a[0].fg.jointype |= (JT_LTORJ & p2->a[0].fg.jointype); + sqlite3DbFree(pParse->db, p2); + } + } + return p1; +} + /* ** Add the list of function arguments to the SrcList entry for a ** table-valued-function. */ SQLITE_PRIVATE void sqlite3SrcListFuncArgs(Parse *pParse, SrcList *p, ExprList *pList){ if( p ){ - struct SrcList_item *pItem = &p->a[p->nSrc-1]; + SrcItem *pItem = &p->a[p->nSrc-1]; assert( pItem->fg.notIndexed==0 ); assert( pItem->fg.isIndexedBy==0 ); assert( pItem->fg.isTabFunc==0 ); @@ -111002,14 +131750,34 @@ SQLITE_PRIVATE void sqlite3SrcListFuncArgs(Parse *pParse, SrcList *p, ExprList * ** The operator is "natural cross join". The A and B operands are stored ** in p->a[0] and p->a[1], respectively. The parser initially stores the ** operator with A. This routine shifts that operator over to B. +** +** Additional changes: +** +** * All tables to the left of the right-most RIGHT JOIN are tagged with +** JT_LTORJ (mnemonic: Left Table Of Right Join) so that the +** code generator can easily tell that the table is part of +** the left operand of at least one RIGHT JOIN. */ -SQLITE_PRIVATE void sqlite3SrcListShiftJoinType(SrcList *p){ - if( p ){ - int i; - for(i=p->nSrc-1; i>0; i--){ - p->a[i].fg.jointype = p->a[i-1].fg.jointype; - } +SQLITE_PRIVATE void sqlite3SrcListShiftJoinType(Parse *pParse, SrcList *p){ + (void)pParse; + if( p && p->nSrc>1 ){ + int i = p->nSrc-1; + u8 allFlags = 0; + do{ + allFlags |= p->a[i].fg.jointype = p->a[i-1].fg.jointype; + }while( (--i)>0 ); p->a[0].fg.jointype = 0; + + /* All terms to the left of a RIGHT JOIN should be tagged with the + ** JT_LTORJ flags */ + if( allFlags & JT_RIGHT ){ + for(i=p->nSrc-1; ALWAYS(i>0) && (p->a[i].fg.jointype&JT_RIGHT)==0; i--){} + i--; + assert( i>=0 ); + do{ + p->a[i].fg.jointype |= JT_LTORJ; + }while( (--i)>=0 ); + } } } @@ -111031,7 +131799,16 @@ SQLITE_PRIVATE void sqlite3BeginTransaction(Parse *pParse, int type){ if( !v ) return; if( type!=TK_DEFERRED ){ for(i=0; inDb; i++){ - sqlite3VdbeAddOp2(v, OP_Transaction, i, (type==TK_EXCLUSIVE)+1); + int eTxnType; + Btree *pBt = db->aDb[i].pBt; + if( pBt && sqlite3BtreeIsReadonly(pBt) ){ + eTxnType = 0; /* Read txn */ + }else if( type==TK_EXCLUSIVE ){ + eTxnType = 2; /* Exclusive txn */ + }else{ + eTxnType = 1; /* Write txn */ + } + sqlite3VdbeAddOp2(v, OP_Transaction, i, eTxnType); sqlite3VdbeUsesBtree(v, i); } } @@ -111051,7 +131828,7 @@ SQLITE_PRIVATE void sqlite3EndTransaction(Parse *pParse, int eType){ assert( pParse->db!=0 ); assert( eType==TK_COMMIT || eType==TK_END || eType==TK_ROLLBACK ); isRollback = eType==TK_ROLLBACK; - if( sqlite3AuthCheck(pParse, SQLITE_TRANSACTION, + if( sqlite3AuthCheck(pParse, SQLITE_TRANSACTION, isRollback ? "ROLLBACK" : "COMMIT", 0, 0) ){ return; } @@ -111063,7 +131840,7 @@ SQLITE_PRIVATE void sqlite3EndTransaction(Parse *pParse, int eType){ /* ** This function is called by the parser when it parses a command to create, -** release or rollback an SQL savepoint. +** release or rollback an SQL savepoint. */ SQLITE_PRIVATE void sqlite3Savepoint(Parse *pParse, int op, Token *pName){ char *zName = sqlite3NameFromToken(pParse->db, pName); @@ -111090,7 +131867,7 @@ SQLITE_PRIVATE int sqlite3OpenTempDatabase(Parse *pParse){ if( db->aDb[1].pBt==0 && !pParse->explain ){ int rc; Btree *pBt; - static const int flags = + static const int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_EXCLUSIVE | @@ -111106,7 +131883,7 @@ SQLITE_PRIVATE int sqlite3OpenTempDatabase(Parse *pParse){ } db->aDb[1].pBt = pBt; assert( db->aDb[1].pSchema ); - if( SQLITE_NOMEM==sqlite3BtreeSetPageSize(pBt, db->nextPagesize, -1, 0) ){ + if( SQLITE_NOMEM==sqlite3BtreeSetPageSize(pBt, db->nextPagesize, 0, 0) ){ sqlite3OomFault(db); return 1; } @@ -111120,13 +131897,11 @@ SQLITE_PRIVATE int sqlite3OpenTempDatabase(Parse *pParse){ ** will occur at the end of the top-level VDBE and will be generated ** later, by sqlite3FinishCoding(). */ -SQLITE_PRIVATE void sqlite3CodeVerifySchema(Parse *pParse, int iDb){ - Parse *pToplevel = sqlite3ParseToplevel(pParse); - - assert( iDb>=0 && iDbdb->nDb ); - assert( pParse->db->aDb[iDb].pBt!=0 || iDb==1 ); - assert( iDbdb, iDb, 0) ); +static void sqlite3CodeVerifySchemaAtToplevel(Parse *pToplevel, int iDb){ + assert( iDb>=0 && iDbdb->nDb ); + assert( pToplevel->db->aDb[iDb].pBt!=0 || iDb==1 ); + assert( iDbdb, iDb, 0) ); if( DbMaskTest(pToplevel->cookieMask, iDb)==0 ){ DbMaskSet(pToplevel->cookieMask, iDb); if( !OMIT_TEMPDB && iDb==1 ){ @@ -111134,9 +131909,13 @@ SQLITE_PRIVATE void sqlite3CodeVerifySchema(Parse *pParse, int iDb){ } } } +SQLITE_PRIVATE void sqlite3CodeVerifySchema(Parse *pParse, int iDb){ + sqlite3CodeVerifySchemaAtToplevel(sqlite3ParseToplevel(pParse), iDb); +} + /* -** If argument zDb is NULL, then call sqlite3CodeVerifySchema() for each +** If argument zDb is NULL, then call sqlite3CodeVerifySchema() for each ** attached database. Otherwise, invoke it for the database named zDb only. */ SQLITE_PRIVATE void sqlite3CodeVerifyNamedSchema(Parse *pParse, const char *zDb){ @@ -111165,7 +131944,7 @@ SQLITE_PRIVATE void sqlite3CodeVerifyNamedSchema(Parse *pParse, const char *zDb) */ SQLITE_PRIVATE void sqlite3BeginWriteOperation(Parse *pParse, int setStatement, int iDb){ Parse *pToplevel = sqlite3ParseToplevel(pParse); - sqlite3CodeVerifySchema(pParse, iDb); + sqlite3CodeVerifySchemaAtToplevel(pToplevel, iDb); DbMaskSet(pToplevel->writeMask, iDb); pToplevel->isMultiWrite |= setStatement; } @@ -111182,9 +131961,9 @@ SQLITE_PRIVATE void sqlite3MultiWrite(Parse *pParse){ pToplevel->isMultiWrite = 1; } -/* +/* ** The code generator calls this routine if is discovers that it is -** possible to abort a statement prior to completion. In order to +** possible to abort a statement prior to completion. In order to ** perform this abort without corrupting the database, we need to make ** sure that the statement is protected by a statement transaction. ** @@ -111193,7 +131972,7 @@ SQLITE_PRIVATE void sqlite3MultiWrite(Parse *pParse){ ** such that the abort must occur after the multiwrite. This makes ** some statements involving the REPLACE conflict resolution algorithm ** go a little faster. But taking advantage of this time dependency -** makes it more difficult to prove that the code is correct (in +** makes it more difficult to prove that the code is correct (in ** particular, it prevents us from writing an effective ** implementation of sqlite3AssertMayAbort()) and so we have chosen ** to take the safe route and skip the optimization. @@ -111216,8 +131995,10 @@ SQLITE_PRIVATE void sqlite3HaltConstraint( i8 p4type, /* P4_STATIC or P4_TRANSIENT */ u8 p5Errmsg /* P5_ErrMsg type */ ){ - Vdbe *v = sqlite3GetVdbe(pParse); - assert( (errCode&0xff)==SQLITE_CONSTRAINT ); + Vdbe *v; + assert( pParse->pVdbe!=0 ); + v = sqlite3GetVdbe(pParse); + assert( (errCode&0xff)==SQLITE_CONSTRAINT || pParse->nested ); if( onError==OE_Abort ){ sqlite3MayAbort(pParse); } @@ -111238,7 +132019,7 @@ SQLITE_PRIVATE void sqlite3UniqueConstraint( StrAccum errMsg; Table *pTab = pIdx->pTable; - sqlite3StrAccumInit(&errMsg, pParse->db, 0, 0, + sqlite3StrAccumInit(&errMsg, pParse->db, 0, 0, pParse->db->aLimit[SQLITE_LIMIT_LENGTH]); if( pIdx->aColExpr ){ sqlite3_str_appendf(&errMsg, "index '%q'", pIdx->zName); @@ -111246,7 +132027,7 @@ SQLITE_PRIVATE void sqlite3UniqueConstraint( for(j=0; jnKeyCol; j++){ char *zCol; assert( pIdx->aiColumn[j]>=0 ); - zCol = pTab->aCol[pIdx->aiColumn[j]].zName; + zCol = pTab->aCol[pIdx->aiColumn[j]].zCnName; if( j ) sqlite3_str_append(&errMsg, ", ", 2); sqlite3_str_appendall(&errMsg, pTab->zName); sqlite3_str_append(&errMsg, ".", 1); @@ -111254,8 +132035,8 @@ SQLITE_PRIVATE void sqlite3UniqueConstraint( } } zErr = sqlite3StrAccumFinish(&errMsg); - sqlite3HaltConstraint(pParse, - IsPrimaryKeyIndex(pIdx) ? SQLITE_CONSTRAINT_PRIMARYKEY + sqlite3HaltConstraint(pParse, + IsPrimaryKeyIndex(pIdx) ? SQLITE_CONSTRAINT_PRIMARYKEY : SQLITE_CONSTRAINT_UNIQUE, onError, zErr, P4_DYNAMIC, P5_ConstraintUnique); } @@ -111267,13 +132048,13 @@ SQLITE_PRIVATE void sqlite3UniqueConstraint( SQLITE_PRIVATE void sqlite3RowidConstraint( Parse *pParse, /* Parsing context */ int onError, /* Conflict resolution algorithm */ - Table *pTab /* The table with the non-unique rowid */ + Table *pTab /* The table with the non-unique rowid */ ){ char *zMsg; int rc; if( pTab->iPKey>=0 ){ zMsg = sqlite3MPrintf(pParse->db, "%s.%s", pTab->zName, - pTab->aCol[pTab->iPKey].zName); + pTab->aCol[pTab->iPKey].zCnName); rc = SQLITE_CONSTRAINT_PRIMARYKEY; }else{ zMsg = sqlite3MPrintf(pParse->db, "%s.rowid", pTab->zName); @@ -111284,8 +132065,7 @@ SQLITE_PRIVATE void sqlite3RowidConstraint( } /* -** Check to see if pIndex uses the collating sequence pColl. Return -** true if it does and false if it does not. +** Return true if any column of pIndex uses the zColl collation */ #ifndef SQLITE_OMIT_REINDEX static int collationMatch(const char *zColl, Index *pIndex){ @@ -111293,8 +132073,8 @@ static int collationMatch(const char *zColl, Index *pIndex){ assert( zColl!=0 ); for(i=0; inColumn; i++){ const char *z = pIndex->azColl[i]; - assert( z!=0 || pIndex->aiColumn[i]<0 ); - if( pIndex->aiColumn[i]>=0 && 0==sqlite3StrICmp(z, zColl) ){ + assert( z!=0 ); + if( 0==sqlite3StrICmp(z, zColl) ){ return 1; } } @@ -111302,73 +132082,39 @@ static int collationMatch(const char *zColl, Index *pIndex){ } #endif -/* -** Recompute all indices of pTab that use the collating sequence pColl. -** If pColl==0 then recompute all indices of pTab. -*/ -#ifndef SQLITE_OMIT_REINDEX -static void reindexTable(Parse *pParse, Table *pTab, char const *zColl){ - if( !IsVirtual(pTab) ){ - Index *pIndex; /* An index associated with pTab */ - - for(pIndex=pTab->pIndex; pIndex; pIndex=pIndex->pNext){ - if( zColl==0 || collationMatch(zColl, pIndex) ){ - int iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema); - sqlite3BeginWriteOperation(pParse, 0, iDb); - sqlite3RefillIndex(pParse, pIndex, -1); - } - } - } -} -#endif - -/* -** Recompute all indices of all tables in all databases where the -** indices use the collating sequence pColl. If pColl==0 then recompute -** all indices everywhere. -*/ -#ifndef SQLITE_OMIT_REINDEX -static void reindexDatabases(Parse *pParse, char const *zColl){ - Db *pDb; /* A single database */ - int iDb; /* The database index number */ - sqlite3 *db = pParse->db; /* The database connection */ - HashElem *k; /* For looping over tables in pDb */ - Table *pTab; /* A table in the database */ - - assert( sqlite3BtreeHoldsAllMutexes(db) ); /* Needed for schema access */ - for(iDb=0, pDb=db->aDb; iDbnDb; iDb++, pDb++){ - assert( pDb!=0 ); - for(k=sqliteHashFirst(&pDb->pSchema->tblHash); k; k=sqliteHashNext(k)){ - pTab = (Table*)sqliteHashData(k); - reindexTable(pParse, pTab, zColl); - } - } -} -#endif - /* ** Generate code for the REINDEX command. ** ** REINDEX -- 1 ** REINDEX -- 2 -** REINDEX ?.? -- 3 -** REINDEX ?.? -- 4 +** REINDEX ?.? -- 3 +** REINDEX ?.? -- 4 +** REINDEX EXPRESSIONS -- 5 ** -** Form 1 causes all indices in all attached databases to be rebuilt. -** Form 2 rebuilds all indices in all databases that use the named +** Form 1 causes all indexes in all attached databases to be rebuilt. +** Form 2 rebuilds all indexes in all databases that use the named ** collating function. Forms 3 and 4 rebuild the named index or all -** indices associated with the named table. +** indexes associated with the named table, respectively. Form 5 +** rebuilds all expression indexes in addition to all collations, +** indexes, or tables named "EXPRESSIONS". +** +** If the name is ambiguous such that it matches two or more of +** forms 2 through 5, then rebuild the union of all matching indexes, +** taken care to avoid rebuilding the same index more than once. */ #ifndef SQLITE_OMIT_REINDEX SQLITE_PRIVATE void sqlite3Reindex(Parse *pParse, Token *pName1, Token *pName2){ - CollSeq *pColl; /* Collating sequence to be reindexed, or NULL */ - char *z; /* Name of a table or index */ - const char *zDb; /* Name of the database */ - Table *pTab; /* A table in the database */ - Index *pIndex; /* An index associated with pTab */ - int iDb; /* The database index number */ + char *z = 0; /* Name of a table or index or collation */ + const char *zDb = 0; /* Name of the database */ + int iReDb = -1; /* The database index number */ sqlite3 *db = pParse->db; /* The database connection */ Token *pObjName; /* Name of the table or index to be reindexed */ + int bMatch = 0; /* At least one name match */ + const char *zColl = 0; /* Rebuild indexes using this collation */ + Table *pReTab = 0; /* Rebuild all indexes of this table */ + Index *pReIndex = 0; /* Rebuild this index */ + int isExprIdx = 0; /* Rebuild all expression indexes */ + int bAll = 0; /* Rebuild all indexes */ /* Read the database schema. If an error occurs, leave an error message ** and code in pParse and return NULL. */ @@ -111377,40 +132123,66 @@ SQLITE_PRIVATE void sqlite3Reindex(Parse *pParse, Token *pName1, Token *pName2){ } if( pName1==0 ){ - reindexDatabases(pParse, 0); - return; + /* rebuild all indexes */ + bMatch = 1; + bAll = 1; }else if( NEVER(pName2==0) || pName2->z==0 ){ - char *zColl; assert( pName1->z ); - zColl = sqlite3NameFromToken(pParse->db, pName1); - if( !zColl ) return; - pColl = sqlite3FindCollSeq(db, ENC(db), zColl, 0); - if( pColl ){ - reindexDatabases(pParse, zColl); - sqlite3DbFree(db, zColl); - return; + z = sqlite3NameFromToken(pParse->db, pName1); + if( z==0 ) return; + }else{ + iReDb = sqlite3TwoPartName(pParse, pName1, pName2, &pObjName); + if( iReDb<0 ) return; + z = sqlite3NameFromToken(db, pObjName); + if( z==0 ) return; + zDb = db->aDb[iReDb].zDbSName; + } + if( !bAll ){ + if( zDb==0 && sqlite3StrICmp(z, "expressions")==0 ){ + isExprIdx = 1; + bMatch = 1; + } + if( zDb==0 && sqlite3FindCollSeq(db, ENC(db), z, 0)!=0 ){ + zColl = z; + bMatch = 1; + } + if( zColl==0 && (pReTab = sqlite3FindTable(db, z, zDb))!=0 ){ + bMatch = 1; + } + if( zColl==0 && (pReIndex = sqlite3FindIndex(db, z, zDb))!=0 ){ + bMatch = 1; } - sqlite3DbFree(db, zColl); } - iDb = sqlite3TwoPartName(pParse, pName1, pName2, &pObjName); - if( iDb<0 ) return; - z = sqlite3NameFromToken(db, pObjName); - if( z==0 ) return; - zDb = db->aDb[iDb].zDbSName; - pTab = sqlite3FindTable(db, z, zDb); - if( pTab ){ - reindexTable(pParse, pTab, 0); - sqlite3DbFree(db, z); - return; + if( bMatch ){ + int iDb; + HashElem *k; + Table *pTab; + Index *pIdx; + Db *pDb; + for(iDb=0, pDb=db->aDb; iDbnDb; iDb++, pDb++){ + assert( pDb!=0 ); + if( iReDb>=0 && iReDb!=iDb ) continue; + for(k=sqliteHashFirst(&pDb->pSchema->tblHash); k; k=sqliteHashNext(k)){ + pTab = (Table*)sqliteHashData(k); + if( IsVirtual(pTab) ) continue; + for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + if( bAll + || pTab==pReTab + || pIdx==pReIndex + || (isExprIdx && pIdx->bHasExpr) + || (zColl!=0 && collationMatch(zColl,pIdx)) + ){ + sqlite3BeginWriteOperation(pParse, 0, iDb); + sqlite3RefillIndex(pParse, pIdx, -1); + } + } /* End loop over indexes of pTab */ + } /* End loop over tables of iDb */ + } /* End loop over databases */ + }else{ + sqlite3ErrorMsg(pParse, "unable to identify the object to be reindexed"); } - pIndex = sqlite3FindIndex(db, z, zDb); sqlite3DbFree(db, z); - if( pIndex ){ - sqlite3BeginWriteOperation(pParse, 0, iDb); - sqlite3RefillIndex(pParse, pIndex, -1); - return; - } - sqlite3ErrorMsg(pParse, "unable to identify the object to be reindexed"); + return; } #endif @@ -111437,18 +132209,24 @@ SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoOfIndex(Parse *pParse, Index *pIdx){ const char *zColl = pIdx->azColl[i]; pKey->aColl[i] = zColl==sqlite3StrBINARY ? 0 : sqlite3LocateCollSeq(pParse, zColl); - pKey->aSortOrder[i] = pIdx->aSortOrder[i]; + pKey->aSortFlags[i] = pIdx->aSortOrder[i]; + assert( 0==(pKey->aSortFlags[i] & KEYINFO_ORDER_BIGNULL) ); } if( pParse->nErr ){ assert( pParse->rc==SQLITE_ERROR_MISSING_COLLSEQ ); - if( pIdx->bNoQuery==0 ){ + if( pIdx->bNoQuery==0 + && sqlite3HashFind(&pIdx->pSchema->idxHash, pIdx->zName) + ){ /* Deactivate the index because it contains an unknown collating ** sequence. The only way to reactive the index is to reload the ** schema. Adding the missing collating sequence later does not ** reactive the index. The application had the chance to register ** the missing index using the collation-needed callback. For ** simplicity, SQLite will not give the application a second chance. - */ + ** + ** Except, do not do this if the index is not in the schema hash + ** table. In this case the index is currently being constructed + ** by a CREATE INDEX statement, and retrying will not help. */ pIdx->bNoQuery = 1; pParse->rc = SQLITE_ERROR_RETRY; } @@ -111460,24 +132238,76 @@ SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoOfIndex(Parse *pParse, Index *pIdx){ } #ifndef SQLITE_OMIT_CTE -/* -** This routine is invoked once per CTE by the parser while parsing a -** WITH clause. +/* +** Create a new CTE object */ -SQLITE_PRIVATE With *sqlite3WithAdd( +SQLITE_PRIVATE Cte *sqlite3CteNew( Parse *pParse, /* Parsing context */ - With *pWith, /* Existing WITH clause, or NULL */ Token *pName, /* Name of the common-table */ ExprList *pArglist, /* Optional column name list for the table */ - Select *pQuery /* Query used to initialize the table */ + Select *pQuery, /* Query used to initialize the table */ + u8 eM10d /* The MATERIALIZED flag */ +){ + Cte *pNew; + sqlite3 *db = pParse->db; + + pNew = sqlite3DbMallocZero(db, sizeof(*pNew)); + assert( pNew!=0 || db->mallocFailed ); + + if( db->mallocFailed ){ + sqlite3ExprListDelete(db, pArglist); + sqlite3SelectDelete(db, pQuery); + }else{ + pNew->pSelect = pQuery; + pNew->pCols = pArglist; + pNew->zName = sqlite3NameFromToken(pParse->db, pName); + pNew->eM10d = eM10d; + } + return pNew; +} + +/* +** Clear information from a Cte object, but do not deallocate storage +** for the object itself. +*/ +static void cteClear(sqlite3 *db, Cte *pCte){ + assert( pCte!=0 ); + sqlite3ExprListDelete(db, pCte->pCols); + sqlite3SelectDelete(db, pCte->pSelect); + sqlite3DbFree(db, pCte->zName); +} + +/* +** Free the contents of the CTE object passed as the second argument. +*/ +SQLITE_PRIVATE void sqlite3CteDelete(sqlite3 *db, Cte *pCte){ + assert( pCte!=0 ); + cteClear(db, pCte); + sqlite3DbFree(db, pCte); +} + +/* +** This routine is invoked once per CTE by the parser while parsing a +** WITH clause. The CTE described by the third argument is added to +** the WITH clause of the second argument. If the second argument is +** NULL, then a new WITH argument is created. +*/ +SQLITE_PRIVATE With *sqlite3WithAdd( + Parse *pParse, /* Parsing context */ + With *pWith, /* Existing WITH clause, or NULL */ + Cte *pCte /* CTE to add to the WITH clause */ ){ sqlite3 *db = pParse->db; With *pNew; char *zName; + if( pCte==0 ){ + return pWith; + } + /* Check that the CTE name is unique within this WITH clause. If ** not, store an error in the Parse structure. */ - zName = sqlite3NameFromToken(pParse->db, pName); + zName = pCte->zName; if( zName && pWith ){ int i; for(i=0; inCte; i++){ @@ -111488,24 +132318,18 @@ SQLITE_PRIVATE With *sqlite3WithAdd( } if( pWith ){ - sqlite3_int64 nByte = sizeof(*pWith) + (sizeof(pWith->a[1]) * pWith->nCte); - pNew = sqlite3DbRealloc(db, pWith, nByte); + pNew = sqlite3DbRealloc(db, pWith, SZ_WITH(pWith->nCte+1)); }else{ - pNew = sqlite3DbMallocZero(db, sizeof(*pWith)); + pNew = sqlite3DbMallocZero(db, SZ_WITH(1)); } assert( (pNew!=0 && zName!=0) || db->mallocFailed ); if( db->mallocFailed ){ - sqlite3ExprListDelete(db, pArglist); - sqlite3SelectDelete(db, pQuery); - sqlite3DbFree(db, zName); + sqlite3CteDelete(db, pCte); pNew = pWith; }else{ - pNew->a[pNew->nCte].pSelect = pQuery; - pNew->a[pNew->nCte].pCols = pArglist; - pNew->a[pNew->nCte].zName = zName; - pNew->a[pNew->nCte].zCteErr = 0; - pNew->nCte++; + pNew->a[pNew->nCte++] = *pCte; + sqlite3DbFree(db, pCte); } return pNew; @@ -111518,20 +132342,20 @@ SQLITE_PRIVATE void sqlite3WithDelete(sqlite3 *db, With *pWith){ if( pWith ){ int i; for(i=0; inCte; i++){ - struct Cte *pCte = &pWith->a[i]; - sqlite3ExprListDelete(db, pCte->pCols); - sqlite3SelectDelete(db, pCte->pSelect); - sqlite3DbFree(db, pCte->zName); + cteClear(db, &pWith->a[i]); } sqlite3DbFree(db, pWith); } } +SQLITE_PRIVATE void sqlite3WithDeleteGeneric(sqlite3 *db, void *pWith){ + sqlite3WithDelete(db, (With*)pWith); +} #endif /* !defined(SQLITE_OMIT_CTE) */ /************** End of build.c ***********************************************/ /************** Begin file callback.c ****************************************/ /* -** 2005 May 23 +** 2005 May 23 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: @@ -111597,51 +132421,6 @@ static int synthCollSeq(sqlite3 *db, CollSeq *pColl){ return SQLITE_ERROR; } -/* -** This function is responsible for invoking the collation factory callback -** or substituting a collation sequence of a different encoding when the -** requested collation sequence is not available in the desired encoding. -** -** If it is not NULL, then pColl must point to the database native encoding -** collation sequence with name zName, length nName. -** -** The return value is either the collation sequence to be used in database -** db for collation type name zName, length nName, or NULL, if no collation -** sequence can be found. If no collation is found, leave an error message. -** -** See also: sqlite3LocateCollSeq(), sqlite3FindCollSeq() -*/ -SQLITE_PRIVATE CollSeq *sqlite3GetCollSeq( - Parse *pParse, /* Parsing context */ - u8 enc, /* The desired encoding for the collating sequence */ - CollSeq *pColl, /* Collating sequence with native encoding, or NULL */ - const char *zName /* Collating sequence name */ -){ - CollSeq *p; - sqlite3 *db = pParse->db; - - p = pColl; - if( !p ){ - p = sqlite3FindCollSeq(db, enc, zName, 0); - } - if( !p || !p->xCmp ){ - /* No collation sequence of this type for this encoding is registered. - ** Call the collation factory to see if it can supply us with one. - */ - callCollNeeded(db, enc, zName); - p = sqlite3FindCollSeq(db, enc, zName, 0); - } - if( p && !p->xCmp && synthCollSeq(db, p) ){ - p = 0; - } - assert( !p || p->xCmp ); - if( p==0 ){ - sqlite3ErrorMsg(pParse, "no such collation sequence: %s", zName); - pParse->rc = SQLITE_ERROR_MISSING_COLLSEQ; - } - return p; -} - /* ** This routine is called on a collation sequence before it is used to ** check that it is defined. An undefined collation sequence exists when @@ -111649,7 +132428,7 @@ SQLITE_PRIVATE CollSeq *sqlite3GetCollSeq( ** that have not been defined by sqlite3_create_collation() etc. ** ** If required, this routine calls the 'collation needed' callback to -** request a definition of the collating sequence. If this doesn't work, +** request a definition of the collating sequence. If this doesn't work, ** an equivalent collating sequence that uses a text encoding different ** from the main database is substituted, if one is available. */ @@ -111703,7 +132482,7 @@ static CollSeq *findCollSeqEntry( memcpy(pColl[0].zName, zName, nName); pDel = sqlite3HashInsert(&db->aCollSeq, pColl[0].zName, pColl); - /* If a malloc() failure occurred in sqlite3HashInsert(), it will + /* If a malloc() failure occurred in sqlite3HashInsert(), it will ** return the pColl pointer to be deleted (because it wasn't added ** to the hash table). */ @@ -111734,20 +132513,113 @@ static CollSeq *findCollSeqEntry( ** See also: sqlite3LocateCollSeq(), sqlite3GetCollSeq() */ SQLITE_PRIVATE CollSeq *sqlite3FindCollSeq( - sqlite3 *db, - u8 enc, - const char *zName, - int create + sqlite3 *db, /* Database connection to search */ + u8 enc, /* Desired text encoding */ + const char *zName, /* Name of the collating sequence. Might be NULL */ + int create /* True to create CollSeq if doesn't already exist */ ){ CollSeq *pColl; + assert( SQLITE_UTF8==1 && SQLITE_UTF16LE==2 && SQLITE_UTF16BE==3 ); + assert( enc>=SQLITE_UTF8 && enc<=SQLITE_UTF16BE ); if( zName ){ pColl = findCollSeqEntry(db, zName, create); + if( pColl ) pColl += enc-1; }else{ pColl = db->pDfltColl; } - assert( SQLITE_UTF8==1 && SQLITE_UTF16LE==2 && SQLITE_UTF16BE==3 ); - assert( enc>=SQLITE_UTF8 && enc<=SQLITE_UTF16BE ); - if( pColl ) pColl += enc-1; + return pColl; +} + +/* +** Change the text encoding for a database connection. This means that +** the pDfltColl must change as well. +*/ +SQLITE_PRIVATE void sqlite3SetTextEncoding(sqlite3 *db, u8 enc){ + assert( enc==SQLITE_UTF8 || enc==SQLITE_UTF16LE || enc==SQLITE_UTF16BE ); + db->enc = enc; + /* EVIDENCE-OF: R-08308-17224 The default collating function for all + ** strings is BINARY. + */ + db->pDfltColl = sqlite3FindCollSeq(db, enc, sqlite3StrBINARY, 0); + sqlite3ExpirePreparedStatements(db, 1); +} + +/* +** This function is responsible for invoking the collation factory callback +** or substituting a collation sequence of a different encoding when the +** requested collation sequence is not available in the desired encoding. +** +** If it is not NULL, then pColl must point to the database native encoding +** collation sequence with name zName, length nName. +** +** The return value is either the collation sequence to be used in database +** db for collation type name zName, length nName, or NULL, if no collation +** sequence can be found. If no collation is found, leave an error message. +** +** See also: sqlite3LocateCollSeq(), sqlite3FindCollSeq() +*/ +SQLITE_PRIVATE CollSeq *sqlite3GetCollSeq( + Parse *pParse, /* Parsing context */ + u8 enc, /* The desired encoding for the collating sequence */ + CollSeq *pColl, /* Collating sequence with native encoding, or NULL */ + const char *zName /* Collating sequence name */ +){ + CollSeq *p; + sqlite3 *db = pParse->db; + + p = pColl; + if( !p ){ + p = sqlite3FindCollSeq(db, enc, zName, 0); + } + if( !p || !p->xCmp ){ + /* No collation sequence of this type for this encoding is registered. + ** Call the collation factory to see if it can supply us with one. + */ + callCollNeeded(db, enc, zName); + p = sqlite3FindCollSeq(db, enc, zName, 0); + } + if( p && !p->xCmp && synthCollSeq(db, p) ){ + p = 0; + } + assert( !p || p->xCmp ); + if( p==0 ){ + sqlite3ErrorMsg(pParse, "no such collation sequence: %s", zName); + pParse->rc = SQLITE_ERROR_MISSING_COLLSEQ; + } + return p; +} + +/* +** This function returns the collation sequence for database native text +** encoding identified by the string zName. +** +** If the requested collation sequence is not available, or not available +** in the database native encoding, the collation factory is invoked to +** request it. If the collation factory does not supply such a sequence, +** and the sequence is available in another text encoding, then that is +** returned instead. +** +** If no versions of the requested collations sequence are available, or +** another error occurs, NULL is returned and an error message written into +** pParse. +** +** This routine is a wrapper around sqlite3FindCollSeq(). This routine +** invokes the collation factory if the named collation cannot be found +** and generates an error message. +** +** See also: sqlite3FindCollSeq(), sqlite3GetCollSeq() +*/ +SQLITE_PRIVATE CollSeq *sqlite3LocateCollSeq(Parse *pParse, const char *zName){ + sqlite3 *db = pParse->db; + u8 enc = ENC(db); + u8 initbusy = db->init.busy; + CollSeq *pColl; + + pColl = sqlite3FindCollSeq(db, enc, zName, initbusy); + if( !initbusy && (!pColl || !pColl->xCmp) ){ + pColl = sqlite3GetCollSeq(pParse, enc, pColl, zName); + } + return pColl; } @@ -111761,7 +132633,7 @@ SQLITE_PRIVATE CollSeq *sqlite3FindCollSeq( ** is also -1. In other words, we are searching for a function that ** takes a variable number of arguments. ** -** If nArg is -2 that means that we are searching for any function +** If nArg is -2 that means that we are searching for any function ** regardless of the number of arguments it uses, so return a positive ** match score for any ** @@ -111786,12 +132658,19 @@ static int matchQuality( u8 enc /* Desired text encoding */ ){ int match; - - /* nArg of -2 is a special case */ - if( nArg==(-2) ) return (p->xSFunc==0) ? 0 : FUNC_PERFECT_MATCH; + assert( p->nArg>=(-4) && p->nArg!=(-2) ); + assert( nArg>=(-2) ); /* Wrong number of arguments means "no match" */ - if( p->nArg!=nArg && p->nArg>=0 ) return 0; + if( p->nArg!=nArg ){ + if( nArg==(-2) ) return p->xSFunc==0 ? 0 : FUNC_PERFECT_MATCH; + if( p->nArg>=0 ) return 0; + /* Special p->nArg values available to built-in functions only: + ** -3 1 or more arguments required + ** -4 2 or more arguments required + */ + if( p->nArg<(-2) && nArg<(-2-p->nArg) ) return 0; + } /* Give a better score to a function with a specific number of arguments ** than to function that accepts any number of arguments. */ @@ -111821,6 +132700,7 @@ SQLITE_PRIVATE FuncDef *sqlite3FunctionSearch( ){ FuncDef *p; for(p=sqlite3BuiltinFunctions.a[h]; p; p=p->u.pHash){ + assert( p->funcFlags & SQLITE_FUNC_BUILTIN ); if( sqlite3StrICmp(p->zName, zFunc)==0 ){ return p; } @@ -111841,7 +132721,7 @@ SQLITE_PRIVATE void sqlite3InsertBuiltinFuncs( const char *zName = aDef[i].zName; int nName = sqlite3Strlen30(zName); int h = SQLITE_FUNC_HASH(zName[0], nName); - assert( zName[0]>='a' && zName[0]<='z' ); + assert( aDef[i].funcFlags & SQLITE_FUNC_BUILTIN ); pOther = sqlite3FunctionSearch(h, zName); if( pOther ){ assert( pOther!=&aDef[i] && pOther->pNext!=&aDef[i] ); @@ -111854,8 +132734,8 @@ SQLITE_PRIVATE void sqlite3InsertBuiltinFuncs( } } } - - + + /* ** Locate a user function given a name, a number of arguments and a flag @@ -111916,7 +132796,7 @@ SQLITE_PRIVATE FuncDef *sqlite3FindFunction( ** have fields overwritten with new information appropriate for the ** new function. But the FuncDefs for built-in functions are read-only. ** So we must not search for built-ins when creating a new function. - */ + */ if( !createFlag && (pBest==0 || (db->mDbFlags & DBFLAG_PreferBuiltin)!=0) ){ bestScore = 0; h = SQLITE_FUNC_HASH(sqlite3UpperToLower[(u8)zName[0]], nName); @@ -111935,7 +132815,7 @@ SQLITE_PRIVATE FuncDef *sqlite3FindFunction( ** exact match for the name, number of arguments and encoding, then add a ** new entry to the hash table and return it. */ - if( createFlag && bestScoretblHash; temp2 = pSchema->trigHash; sqlite3HashInit(&pSchema->trigHash); sqlite3HashClear(&pSchema->idxHash); for(pElem=sqliteHashFirst(&temp2); pElem; pElem=sqliteHashNext(pElem)){ - sqlite3DeleteTrigger(0, (Trigger*)sqliteHashData(pElem)); + sqlite3DeleteTrigger(&xdb, (Trigger*)sqliteHashData(pElem)); } + sqlite3HashClear(&temp2); sqlite3HashInit(&pSchema->tblHash); for(pElem=sqliteHashFirst(&temp1); pElem; pElem=sqliteHashNext(pElem)){ Table *pTab = sqliteHashData(pElem); - sqlite3DeleteTable(0, pTab); + sqlite3DeleteTable(&xdb, pTab); } sqlite3HashClear(&temp1); sqlite3HashClear(&pSchema->fkeyHash); @@ -112042,31 +132925,42 @@ SQLITE_PRIVATE Schema *sqlite3SchemaGet(sqlite3 *db, Btree *pBt){ ** (as in the FROM clause of a SELECT statement) in this case it contains ** the name of a single table, as one might find in an INSERT, DELETE, ** or UPDATE statement. Look up that table in the symbol table and -** return a pointer. Set an error message and return NULL if the table +** return a pointer. Set an error message and return NULL if the table ** name is not found or if any other error occurs. ** ** The following fields are initialized appropriate in pSrc: ** -** pSrc->a[0].pTab Pointer to the Table object -** pSrc->a[0].pIndex Pointer to the INDEXED BY index, if there is one +** pSrc->a[0].spTab Pointer to the Table object +** pSrc->a[0].u2.pIBIndex Pointer to the INDEXED BY index, if there is one ** */ SQLITE_PRIVATE Table *sqlite3SrcListLookup(Parse *pParse, SrcList *pSrc){ - struct SrcList_item *pItem = pSrc->a; + SrcItem *pItem = pSrc->a; Table *pTab; - assert( pItem && pSrc->nSrc==1 ); + assert( pItem && pSrc->nSrc>=1 ); pTab = sqlite3LocateTableItem(pParse, 0, pItem); - sqlite3DeleteTable(pParse->db, pItem->pTab); - pItem->pTab = pTab; + if( pItem->pSTab ) sqlite3DeleteTable(pParse->db, pItem->pSTab); + pItem->pSTab = pTab; + pItem->fg.notCte = 1; if( pTab ){ pTab->nTabRef++; - } - if( sqlite3IndexedByLookup(pParse, pItem) ){ - pTab = 0; + if( pItem->fg.isIndexedBy && sqlite3IndexedByLookup(pParse, pItem) ){ + pTab = 0; + } } return pTab; } +/* Generate byte-code that will report the number of rows modified +** by a DELETE, INSERT, or UPDATE statement. +*/ +SQLITE_PRIVATE void sqlite3CodeChangeCount(Vdbe *v, int regCounter, const char *zColName){ + sqlite3VdbeAddOp0(v, OP_FkCheck); + sqlite3VdbeAddOp2(v, OP_ResultRow, regCounter, 1); + sqlite3VdbeSetNumCols(v, 1); + sqlite3VdbeSetColName(v, 0, COLNAME_NAME, zColName, SQLITE_STATIC); +} + /* Return true if table pTab is read-only. ** ** A table is read-only if any of the following are true: @@ -112074,18 +132968,43 @@ SQLITE_PRIVATE Table *sqlite3SrcListLookup(Parse *pParse, SrcList *pSrc){ ** 1) It is a virtual table and no implementation of the xUpdate method ** has been provided ** -** 2) It is a system table (i.e. sqlite_master), this call is not -** part of a nested parse and writable_schema pragma has not +** 2) A trigger is currently being coded and the table is a virtual table +** that is SQLITE_VTAB_DIRECTONLY or if PRAGMA trusted_schema=OFF and +** the table is not SQLITE_VTAB_INNOCUOUS. +** +** 3) It is a system table (i.e. sqlite_schema), this call is not +** part of a nested parse and writable_schema pragma has not ** been specified ** -** 3) The table is a shadow table, the database connection is in +** 4) The table is a shadow table, the database connection is in ** defensive mode, and the current sqlite3_prepare() ** is for a top-level SQL statement. */ +static int vtabIsReadOnly(Parse *pParse, Table *pTab){ + assert( IsVirtual(pTab) ); + if( sqlite3GetVTable(pParse->db, pTab)->pMod->pModule->xUpdate==0 ){ + return 1; + } + + /* Within triggers: + ** * Do not allow DELETE, INSERT, or UPDATE of SQLITE_VTAB_DIRECTONLY + ** virtual tables + ** * Only allow DELETE, INSERT, or UPDATE of non-SQLITE_VTAB_INNOCUOUS + ** virtual tables if PRAGMA trusted_schema=ON. + */ + if( (pParse->pToplevel!=0 || (pParse->prepFlags & SQLITE_PREPARE_FROM_DDL)) + && pTab->u.vtab.p->eVtabRisk > + ((pParse->db->flags & SQLITE_TrustedSchema)!=0) + ){ + sqlite3ErrorMsg(pParse, "unsafe use of virtual table \"%s\"", + pTab->zName); + } + return 0; +} static int tabIsReadOnly(Parse *pParse, Table *pTab){ sqlite3 *db; if( IsVirtual(pTab) ){ - return sqlite3GetVTable(pParse->db, pTab)->pMod->pModule->xUpdate==0; + return vtabIsReadOnly(pParse, pTab); } if( (pTab->tabFlags & (TF_Readonly|TF_Shadow))==0 ) return 0; db = pParse->db; @@ -112093,25 +133012,25 @@ static int tabIsReadOnly(Parse *pParse, Table *pTab){ return sqlite3WritableSchema(db)==0 && pParse->nested==0; } assert( pTab->tabFlags & TF_Shadow ); - return (db->flags & SQLITE_Defensive)!=0 -#ifndef SQLITE_OMIT_VIRTUALTABLE - && db->pVtabCtx==0 -#endif - && db->nVdbeExec==0; + return sqlite3ReadOnlyShadowTables(db); } /* -** Check to make sure the given table is writable. If it is not -** writable, generate an error message and return 1. If it is -** writable return 0; +** Check to make sure the given table is writable. +** +** If pTab is not writable -> generate an error message and return 1. +** If pTab is writable but other errors have occurred -> return 1. +** If pTab is writable and no prior errors -> return 0; */ -SQLITE_PRIVATE int sqlite3IsReadOnly(Parse *pParse, Table *pTab, int viewOk){ +SQLITE_PRIVATE int sqlite3IsReadOnly(Parse *pParse, Table *pTab, Trigger *pTrigger){ if( tabIsReadOnly(pParse, pTab) ){ sqlite3ErrorMsg(pParse, "table %s may not be modified", pTab->zName); return 1; } #ifndef SQLITE_OMIT_VIEW - if( !viewOk && pTab->pSelect ){ + if( IsView(pTab) + && (pTrigger==0 || (pTrigger->bReturning && pTrigger->pNext==0)) + ){ sqlite3ErrorMsg(pParse,"cannot modify %s because it is a view",pTab->zName); return 1; } @@ -112144,11 +133063,12 @@ SQLITE_PRIVATE void sqlite3MaterializeView( if( pFrom ){ assert( pFrom->nSrc==1 ); pFrom->a[0].zName = sqlite3DbStrDup(db, pView->zName); - pFrom->a[0].zDatabase = sqlite3DbStrDup(db, db->aDb[iDb].zDbSName); - assert( pFrom->a[0].pOn==0 ); - assert( pFrom->a[0].pUsing==0 ); + assert( pFrom->a[0].fg.fixedSchema==0 && pFrom->a[0].fg.isSubquery==0 ); + pFrom->a[0].u4.zDatabase = sqlite3DbStrDup(db, db->aDb[iDb].zDbSName); + assert( pFrom->a[0].fg.isUsing==0 ); + assert( pFrom->a[0].u3.pOn==0 ); } - pSel = sqlite3SelectNew(pParse, 0, pFrom, pWhere, 0, 0, pOrderBy, + pSel = sqlite3SelectNew(pParse, 0, pFrom, pWhere, 0, 0, pOrderBy, SF_IncludeHidden, pLimit); sqlite3SelectDestInit(&dest, SRT_EphemTab, iCur); sqlite3Select(pParse, pSel, &dest); @@ -112176,7 +133096,7 @@ SQLITE_PRIVATE Expr *sqlite3LimitWhere( sqlite3 *db = pParse->db; Expr *pLhs = NULL; /* LHS of IN(SELECT...) operator */ Expr *pInClause = NULL; /* WHERE rowid IN ( select ) */ - ExprList *pEList = NULL; /* Expression list contaning only pSelectRowid */ + ExprList *pEList = NULL; /* Expression list containing only pSelectRowid*/ SrcList *pSelectSrc = NULL; /* SELECT rowid FROM x ... (dup of pSrc) */ Select *pSelect = NULL; /* Complete SELECT tree */ Table *pTab; @@ -112197,16 +133117,16 @@ SQLITE_PRIVATE Expr *sqlite3LimitWhere( return pWhere; } - /* Generate a select expression tree to enforce the limit/offset + /* Generate a select expression tree to enforce the limit/offset ** term for the DELETE or UPDATE statement. For example: ** DELETE FROM table_a WHERE col1=1 ORDER BY col2 LIMIT 1 OFFSET 1 ** becomes: - ** DELETE FROM table_a WHERE rowid IN ( + ** DELETE FROM table_a WHERE rowid IN ( ** SELECT rowid FROM table_a WHERE col1=1 ORDER BY col2 LIMIT 1 OFFSET 1 ** ); */ - pTab = pSrc->a[0].pTab; + pTab = pSrc->a[0].pSTab; if( HasRowid(pTab) ){ pLhs = sqlite3PExpr(pParse, TK_ROW, 0, 0); pEList = sqlite3ExprListAppend( @@ -112214,14 +133134,20 @@ SQLITE_PRIVATE Expr *sqlite3LimitWhere( ); }else{ Index *pPk = sqlite3PrimaryKeyIndex(pTab); + assert( pPk!=0 ); + assert( pPk->nKeyCol>=1 ); if( pPk->nKeyCol==1 ){ - const char *zName = pTab->aCol[pPk->aiColumn[0]].zName; + const char *zName; + assert( pPk->aiColumn[0]>=0 && pPk->aiColumn[0]nCol ); + zName = pTab->aCol[pPk->aiColumn[0]].zCnName; pLhs = sqlite3Expr(db, TK_ID, zName); pEList = sqlite3ExprListAppend(pParse, 0, sqlite3Expr(db, TK_ID, zName)); }else{ int i; for(i=0; inKeyCol; i++){ - Expr *p = sqlite3Expr(db, TK_ID, pTab->aCol[pPk->aiColumn[i]].zName); + Expr *p; + assert( pPk->aiColumn[i]>=0 && pPk->aiColumn[i]nCol ); + p = sqlite3Expr(db, TK_ID, pTab->aCol[pPk->aiColumn[i]].zCnName); pEList = sqlite3ExprListAppend(pParse, pEList, p); } pLhs = sqlite3PExpr(pParse, TK_VECTOR, 0, 0); @@ -112233,17 +133159,24 @@ SQLITE_PRIVATE Expr *sqlite3LimitWhere( /* duplicate the FROM clause as it is needed by both the DELETE/UPDATE tree ** and the SELECT subtree. */ - pSrc->a[0].pTab = 0; - pSelectSrc = sqlite3SrcListDup(pParse->db, pSrc, 0); - pSrc->a[0].pTab = pTab; - pSrc->a[0].pIBIndex = 0; + pSrc->a[0].pSTab = 0; + pSelectSrc = sqlite3SrcListDup(db, pSrc, 0); + pSrc->a[0].pSTab = pTab; + if( pSrc->a[0].fg.isIndexedBy ){ + assert( pSrc->a[0].fg.isCte==0 ); + pSrc->a[0].u2.pIBIndex = 0; + pSrc->a[0].fg.isIndexedBy = 0; + sqlite3DbFree(db, pSrc->a[0].u1.zIndexedBy); + }else if( pSrc->a[0].fg.isCte ){ + pSrc->a[0].u2.pCteUse->nUse++; + } /* generate the SELECT expression tree. */ - pSelect = sqlite3SelectNew(pParse, pEList, pSelectSrc, pWhere, 0 ,0, + pSelect = sqlite3SelectNew(pParse, pEList, pSelectSrc, pWhere, 0 ,0, pOrderBy,0,pLimit ); - /* now generate the new WHERE rowid IN clause for the DELETE/UDPATE */ + /* now generate the new WHERE rowid IN clause for the DELETE/UPDATE */ pInClause = sqlite3PExpr(pParse, TK_IN, pLhs, 0); sqlite3PExprAddSelect(pParse, pInClause, pSelect); return pInClause; @@ -112295,7 +133228,7 @@ SQLITE_PRIVATE void sqlite3DeleteFrom( int addrEphOpen = 0; /* Instruction to open the Ephemeral table */ int bComplex; /* True if there are triggers or FKs or ** subqueries in the WHERE clause */ - + #ifndef SQLITE_OMIT_TRIGGER int isView; /* True if attempting to delete from a view */ Trigger *pTrigger; /* List of table triggers, if required */ @@ -112303,12 +133236,13 @@ SQLITE_PRIVATE void sqlite3DeleteFrom( memset(&sContext, 0, sizeof(sContext)); db = pParse->db; - if( pParse->nErr || db->mallocFailed ){ + assert( db->pParse==pParse ); + if( pParse->nErr ){ goto delete_from_cleanup; } + assert( db->mallocFailed==0 ); assert( pTabList->nSrc==1 ); - /* Locate the table which we want to delete. This table has to be ** put in an SrcList structure because some of the subroutines we ** will be calling are designed to work with multiple tables and expect @@ -112322,7 +133256,7 @@ SQLITE_PRIVATE void sqlite3DeleteFrom( */ #ifndef SQLITE_OMIT_TRIGGER pTrigger = sqlite3TriggersExist(pParse, pTab, TK_DELETE, 0, 0); - isView = pTab->pSelect!=0; + isView = IsView(pTab); #else # define pTrigger 0 # define isView 0 @@ -112333,6 +133267,14 @@ SQLITE_PRIVATE void sqlite3DeleteFrom( # define isView 0 #endif +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x10000 ){ + sqlite3TreeViewLine(0, "In sqlite3Delete() at %s:%d", __FILE__, __LINE__); + sqlite3TreeViewDelete(pParse->pWith, pTabList, pWhere, + pOrderBy, pLimit, pTrigger); + } +#endif + #ifdef SQLITE_ENABLE_UPDATE_DELETE_LIMIT if( !isView ){ pWhere = sqlite3LimitWhere( @@ -112349,12 +133291,12 @@ SQLITE_PRIVATE void sqlite3DeleteFrom( goto delete_from_cleanup; } - if( sqlite3IsReadOnly(pParse, pTab, (pTrigger?1:0)) ){ + if( sqlite3IsReadOnly(pParse, pTab, pTrigger) ){ goto delete_from_cleanup; } iDb = sqlite3SchemaToIndex(db, pTab->pSchema); assert( iDbnDb ); - rcauth = sqlite3AuthCheck(pParse, SQLITE_DELETE, pTab->zName, 0, + rcauth = sqlite3AuthCheck(pParse, SQLITE_DELETE, pTab->zName, 0, db->aDb[iDb].zDbSName); assert( rcauth==SQLITE_OK || rcauth==SQLITE_DENY || rcauth==SQLITE_IGNORE ); if( rcauth==SQLITE_DENY ){ @@ -112390,7 +133332,7 @@ SQLITE_PRIVATE void sqlite3DeleteFrom( */ #if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER) if( isView ){ - sqlite3MaterializeView(pParse, pTab, + sqlite3MaterializeView(pParse, pTab, pWhere, pOrderBy, pLimit, iTabCur ); iDataCur = iIdxCur = iTabCur; @@ -112414,6 +133356,7 @@ SQLITE_PRIVATE void sqlite3DeleteFrom( if( (db->flags & SQLITE_CountRows)!=0 && !pParse->nested && !pParse->pTriggerTab + && !pParse->bReturning ){ memCnt = ++pParse->nMem; sqlite3VdbeAddOp2(v, OP_Integer, 0, memCnt); @@ -112422,7 +133365,7 @@ SQLITE_PRIVATE void sqlite3DeleteFrom( #ifndef SQLITE_OMIT_TRUNCATE_OPTIMIZATION /* Special case: A DELETE without a WHERE clause deletes everything. ** It is easier just to erase the whole table. Prior to version 3.6.5, - ** this optimization caused the row change count (the value returned by + ** this optimization caused the row change count (the value returned by ** API function sqlite3_count_changes) to be set incorrectly. ** ** The "rcauth==SQLITE_OK" terms is the @@ -112447,18 +133390,22 @@ SQLITE_PRIVATE void sqlite3DeleteFrom( } for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ assert( pIdx->pSchema==pTab->pSchema ); - sqlite3VdbeAddOp2(v, OP_Clear, pIdx->tnum, iDb); + if( IsPrimaryKeyIndex(pIdx) && !HasRowid(pTab) ){ + sqlite3VdbeAddOp3(v, OP_Clear, pIdx->tnum, iDb, memCnt ? memCnt : -1); + }else{ + sqlite3VdbeAddOp2(v, OP_Clear, pIdx->tnum, iDb); + } } }else #endif /* SQLITE_OMIT_TRUNCATE_OPTIMIZATION */ { - u16 wcf = WHERE_ONEPASS_DESIRED|WHERE_DUPLICATES_OK|WHERE_SEEK_TABLE; - if( sNC.ncFlags & NC_VarSelect ) bComplex = 1; + u16 wcf = WHERE_ONEPASS_DESIRED|WHERE_DUPLICATES_OK; + if( sNC.ncFlags & NC_Subquery ) bComplex = 1; wcf |= (bComplex ? 0 : WHERE_ONEPASS_MULTIROW); if( HasRowid(pTab) ){ /* For a rowid table, initialize the RowSet to an empty set */ pPk = 0; - nPk = 1; + assert( nPk==1 ); iRowSet = ++pParse->nMem; sqlite3VdbeAddOp2(v, OP_Null, 0, iRowSet); }else{ @@ -112473,7 +133420,7 @@ SQLITE_PRIVATE void sqlite3DeleteFrom( addrEphOpen = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, iEphCur, nPk); sqlite3VdbeSetP4KeyInfo(pParse, pPk); } - + /* Construct a query to find the rowid or primary key for every row ** to be deleted, based on the WHERE clause. Set variable eOnePass ** to indicate the strategy used to implement this delete: @@ -112482,18 +133429,22 @@ SQLITE_PRIVATE void sqlite3DeleteFrom( ** ONEPASS_SINGLE: One-pass approach - at most one row deleted. ** ONEPASS_MULTI: One-pass approach - any number of rows may be deleted. */ - pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0, wcf, iTabCur+1); + pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0,0,wcf,iTabCur+1); if( pWInfo==0 ) goto delete_from_cleanup; eOnePass = sqlite3WhereOkOnePass(pWInfo, aiCurOnePass); assert( IsVirtual(pTab)==0 || eOnePass!=ONEPASS_MULTI ); - assert( IsVirtual(pTab) || bComplex || eOnePass!=ONEPASS_OFF ); + assert( IsVirtual(pTab) || bComplex || eOnePass!=ONEPASS_OFF + || OptimizationDisabled(db, SQLITE_OnePass) ); if( eOnePass!=ONEPASS_SINGLE ) sqlite3MultiWrite(pParse); - + if( sqlite3WhereUsesDeferredSeek(pWInfo) ){ + sqlite3VdbeAddOp1(v, OP_FinishSeek, iTabCur); + } + /* Keep track of the number of rows to be deleted */ if( memCnt ){ sqlite3VdbeAddOp2(v, OP_AddImm, memCnt, 1); } - + /* Extract the rowid or primary key for the current row */ if( pPk ){ for(i=0; inMem; sqlite3ExprCodeGetColumnOfTable(v, pTab, iTabCur, -1, iKey); } - + if( eOnePass!=ONEPASS_OFF ){ /* For ONEPASS, no need to store the rowid/primary-key. There is only ** one, so just keep it in its register(s) and fall through to the @@ -112522,6 +133473,7 @@ SQLITE_PRIVATE void sqlite3DeleteFrom( if( aiCurOnePass[0]>=0 ) aToOpen[aiCurOnePass[0]-iTabCur] = 0; if( aiCurOnePass[1]>=0 ) aToOpen[aiCurOnePass[1]-iTabCur] = 0; if( addrEphOpen ) sqlite3VdbeChangeToNoop(v, addrEphOpen); + addrBypass = sqlite3VdbeMakeLabel(pParse); }else{ if( pPk ){ /* Add the PK key for this row to the temporary table */ @@ -112535,19 +133487,12 @@ SQLITE_PRIVATE void sqlite3DeleteFrom( nKey = 1; /* OP_DeferredSeek always uses a single rowid */ sqlite3VdbeAddOp2(v, OP_RowSetAdd, iRowSet, iKey); } - } - - /* If this DELETE cannot use the ONEPASS strategy, this is the - ** end of the WHERE loop */ - if( eOnePass!=ONEPASS_OFF ){ - addrBypass = sqlite3VdbeMakeLabel(pParse); - }else{ sqlite3WhereEnd(pWInfo); } - - /* Unless this is a view, open cursors for the table we are + + /* Unless this is a view, open cursors for the table we are ** deleting from and all its indices. If this is a view, then the - ** only effect this statement has is to fire the INSTEAD OF + ** only effect this statement has is to fire the INSTEAD OF ** triggers. */ if( !isView ){ @@ -112560,16 +133505,18 @@ SQLITE_PRIVATE void sqlite3DeleteFrom( iTabCur, aToOpen, &iDataCur, &iIdxCur); assert( pPk || IsVirtual(pTab) || iDataCur==iTabCur ); assert( pPk || IsVirtual(pTab) || iIdxCur==iDataCur+1 ); - if( eOnePass==ONEPASS_MULTI ) sqlite3VdbeJumpHere(v, iAddrOnce); + if( eOnePass==ONEPASS_MULTI ){ + sqlite3VdbeJumpHereOrPopInst(v, iAddrOnce); + } } - + /* Set up a loop over the rowids/primary-keys that were found in the ** where-clause loop above. */ if( eOnePass!=ONEPASS_OFF ){ assert( nKey==nPk ); /* OP_Found will use an unpacked key */ if( !IsVirtual(pTab) && aToOpen[iDataCur-iTabCur] ){ - assert( pPk!=0 || pTab->pSelect!=0 ); + assert( pPk!=0 || IsView(pTab) ); sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, addrBypass, iKey, nKey); VdbeCoverage(v); } @@ -112585,8 +133532,8 @@ SQLITE_PRIVATE void sqlite3DeleteFrom( addrLoop = sqlite3VdbeAddOp3(v, OP_RowSetRead, iRowSet, 0, iKey); VdbeCoverage(v); assert( nKey==1 ); - } - + } + /* Delete the row */ #ifndef SQLITE_OMIT_VIRTUALTABLE if( IsVirtual(pTab) ){ @@ -112609,7 +133556,7 @@ SQLITE_PRIVATE void sqlite3DeleteFrom( sqlite3GenerateRowDelete(pParse, pTab, pTrigger, iDataCur, iIdxCur, iKey, nKey, count, OE_Default, eOnePass, aiCurOnePass[1]); } - + /* End of the loop over all rowids/primary-keys. */ if( eOnePass!=ONEPASS_OFF ){ sqlite3VdbeResolveLabel(v, addrBypass); @@ -112620,7 +133567,7 @@ SQLITE_PRIVATE void sqlite3DeleteFrom( }else{ sqlite3VdbeGoto(v, addrLoop); sqlite3VdbeJumpHere(v, addrLoop); - } + } } /* End non-truncate path */ /* Update the sqlite_sequence table by storing the content of the @@ -112631,25 +133578,23 @@ SQLITE_PRIVATE void sqlite3DeleteFrom( sqlite3AutoincrementEnd(pParse); } - /* Return the number of rows that were deleted. If this routine is + /* Return the number of rows that were deleted. If this routine is ** generating code because of a call to sqlite3NestedParse(), do not ** invoke the callback function. */ if( memCnt ){ - sqlite3VdbeAddOp2(v, OP_ResultRow, memCnt, 1); - sqlite3VdbeSetNumCols(v, 1); - sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "rows deleted", SQLITE_STATIC); + sqlite3CodeChangeCount(v, memCnt, "rows deleted"); } delete_from_cleanup: sqlite3AuthContextPop(&sContext); sqlite3SrcListDelete(db, pTabList); sqlite3ExprDelete(db, pWhere); -#if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) +#if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) sqlite3ExprListDelete(db, pOrderBy); sqlite3ExprDelete(db, pLimit); #endif - sqlite3DbFree(db, aToOpen); + if( aToOpen ) sqlite3DbNNFreeNN(db, aToOpen); return; } /* Make sure "isView" and other macros defined above are undefined. Otherwise @@ -112690,7 +133635,7 @@ SQLITE_PRIVATE void sqlite3DeleteFrom( ** and nPk before reading from it. ** ** If eMode is ONEPASS_MULTI, then this call is being made as part -** of a ONEPASS delete that affects multiple rows. In this case, if +** of a ONEPASS delete that affects multiple rows. In this case, if ** iIdxNoSeek is a valid cursor number (>=0) and is not the same as ** iDataCur, then its position should be preserved following the delete ** operation. Or, if iIdxNoSeek is not a valid cursor number, the @@ -112726,7 +133671,7 @@ SQLITE_PRIVATE void sqlite3GenerateRowDelete( VdbeModuleComment((v, "BEGIN: GenRowDel(%d,%d,%d,%d)", iDataCur, iIdxCur, iPk, (int)nPk)); - /* Seek cursor iCur to the row to delete. If this row no longer exists + /* Seek cursor iCur to the row to delete. If this row no longer exists ** (this can happen if a trigger program has already deleted it), do ** not attempt to delete it or fire any DELETE triggers. */ iLabel = sqlite3VdbeMakeLabel(pParse); @@ -112736,7 +133681,7 @@ SQLITE_PRIVATE void sqlite3GenerateRowDelete( VdbeCoverageIf(v, opSeek==OP_NotExists); VdbeCoverageIf(v, opSeek==OP_NotFound); } - + /* If there are any triggers to fire, allocate a range of registers to ** use for the old.* references in the triggers. */ if( sqlite3FkRequired(pParse, pTab, 0, 0) || pTrigger ){ @@ -112753,24 +133698,25 @@ SQLITE_PRIVATE void sqlite3GenerateRowDelete( iOld = pParse->nMem+1; pParse->nMem += (1 + pTab->nCol); - /* Populate the OLD.* pseudo-table register array. These values will be + /* Populate the OLD.* pseudo-table register array. These values will be ** used by any BEFORE and AFTER triggers that exist. */ sqlite3VdbeAddOp2(v, OP_Copy, iPk, iOld); for(iCol=0; iColnCol; iCol++){ testcase( mask!=0xffffffff && iCol==31 ); testcase( mask!=0xffffffff && iCol==32 ); if( mask==0xffffffff || (iCol<=31 && (mask & MASKBIT32(iCol))!=0) ){ - sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, iCol, iOld+iCol+1); + int kk = sqlite3TableColumnToStorage(pTab, iCol); + sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, iCol, iOld+kk+1); } } /* Invoke BEFORE DELETE trigger programs. */ addrStart = sqlite3VdbeCurrentAddr(v); - sqlite3CodeRowTrigger(pParse, pTrigger, + sqlite3CodeRowTrigger(pParse, pTrigger, TK_DELETE, 0, TRIGGER_BEFORE, pTab, iOld, onconf, iLabel ); - /* If any BEFORE triggers were coded, then seek the cursor to the + /* If any BEFORE triggers were coded, then seek the cursor to the ** row to be deleted again. It may be that the BEFORE triggers moved ** the cursor or already deleted the row that the cursor was ** pointing to. @@ -112787,22 +133733,22 @@ SQLITE_PRIVATE void sqlite3GenerateRowDelete( } /* Do FK processing. This call checks that any FK constraints that - ** refer to this table (i.e. constraints attached to other tables) + ** refer to this table (i.e. constraints attached to other tables) ** are not violated by deleting this row. */ sqlite3FkCheck(pParse, pTab, iOld, 0, 0, 0); } /* Delete the index and table entries. Skip this step if pTab is really ** a view (in which case the only effect of the DELETE statement is to - ** fire the INSTEAD OF triggers). + ** fire the INSTEAD OF triggers). ** ** If variable 'count' is non-zero, then this OP_Delete instruction should ** invoke the update-hook. The pre-update-hook, on the other hand should ** be invoked unless table pTab is a system table. The difference is that - ** the update-hook is not invoked for rows removed by REPLACE, but the + ** the update-hook is not invoked for rows removed by REPLACE, but the ** pre-update-hook is. - */ - if( pTab->pSelect==0 ){ + */ + if( !IsView(pTab) ){ u8 p5 = 0; sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur,0,iIdxNoSeek); sqlite3VdbeAddOp2(v, OP_Delete, iDataCur, (count?OPFLAG_NCHANGE:0)); @@ -112821,16 +133767,18 @@ SQLITE_PRIVATE void sqlite3GenerateRowDelete( /* Do any ON CASCADE, SET NULL or SET DEFAULT operations required to ** handle rows (possibly in other tables) that refer via a foreign key - ** to the row just deleted. */ + ** to the row just deleted. */ sqlite3FkActions(pParse, pTab, 0, iOld, 0, 0); /* Invoke AFTER DELETE trigger programs. */ - sqlite3CodeRowTrigger(pParse, pTrigger, - TK_DELETE, 0, TRIGGER_AFTER, pTab, iOld, onconf, iLabel - ); + if( pTrigger ){ + sqlite3CodeRowTrigger(pParse, pTrigger, + TK_DELETE, 0, TRIGGER_AFTER, pTab, iOld, onconf, iLabel + ); + } /* Jump here if the row had already been deleted before any BEFORE - ** trigger programs were invoked. Or if a trigger program throws a + ** trigger programs were invoked. Or if a trigger program throws a ** RAISE(IGNORE) exception. */ sqlite3VdbeResolveLabel(v, iLabel); VdbeModuleComment((v, "END: GenRowDel()")); @@ -112881,7 +133829,9 @@ SQLITE_PRIVATE void sqlite3GenerateRowIndexDelete( r1 = sqlite3GenerateIndexKey(pParse, pIdx, iDataCur, 0, 1, &iPartIdxLabel, pPrior, r1); sqlite3VdbeAddOp3(v, OP_IdxDelete, iIdxCur+i, r1, - pIdx->uniqNotNull ? pIdx->nKeyCol : pIdx->nColumn); + pIdx->uniqNotNull ? pIdx->nKeyCol : pIdx->nColumn + ); + sqlite3VdbeChangeP4(v, -1, (const char*)pIdx, P4_INDEX); sqlite3ResolvePartIdxLabel(pParse, iPartIdxLabel); pPrior = pIdx; } @@ -112914,7 +133864,7 @@ SQLITE_PRIVATE void sqlite3GenerateRowIndexDelete( ** its key into the same sequence of registers and if pPrior and pIdx share ** a column in common, then the register corresponding to that column already ** holds the correct value and the loading of that register is skipped. -** This optimization is helpful when doing a DELETE or an INTEGRITY_CHECK +** This optimization is helpful when doing a DELETE or an INTEGRITY_CHECK ** on a table with multiple indices, and especially with the ROWID or ** PRIMARY KEY columns of the index. */ @@ -112937,9 +133887,11 @@ SQLITE_PRIVATE int sqlite3GenerateIndexKey( if( pIdx->pPartIdxWhere ){ *piPartIdxLabel = sqlite3VdbeMakeLabel(pParse); pParse->iSelfTab = iDataCur + 1; - sqlite3ExprIfFalseDup(pParse, pIdx->pPartIdxWhere, *piPartIdxLabel, + sqlite3ExprIfFalseDup(pParse, pIdx->pPartIdxWhere, *piPartIdxLabel, SQLITE_JUMPIFNULL); pParse->iSelfTab = 0; + pPrior = 0; /* Ticket a9efb42811fa41ee 2019-11-02; + ** pPartIdxWhere may have corrupted regPrior registers */ }else{ *piPartIdxLabel = 0; } @@ -112956,20 +133908,18 @@ SQLITE_PRIVATE int sqlite3GenerateIndexKey( continue; } sqlite3ExprCodeLoadIndexColumn(pParse, pIdx, iDataCur, j, regBase+j); - /* If the column affinity is REAL but the number is an integer, then it - ** might be stored in the table as an integer (using a compact - ** representation) then converted to REAL by an OP_RealAffinity opcode. - ** But we are getting ready to store this value back into an index, where - ** it should be converted by to INTEGER again. So omit the OP_RealAffinity - ** opcode if it is present */ - sqlite3VdbeDeletePriorOpcode(v, OP_RealAffinity); + if( pIdx->aiColumn[j]>=0 ){ + /* If the column affinity is REAL but the number is an integer, then it + ** might be stored in the table as an integer (using a compact + ** representation) then converted to REAL by an OP_RealAffinity opcode. + ** But we are getting ready to store this value back into an index, where + ** it should be converted by to INTEGER again. So omit the + ** OP_RealAffinity opcode if it is present */ + sqlite3VdbeDeletePriorOpcode(v, OP_RealAffinity); + } } if( regOut ){ sqlite3VdbeAddOp3(v, OP_MakeRecord, regBase, nCol, regOut); - if( pIdx->pTable->pSelect ){ - const char *zAff = sqlite3IndexAffinityStr(pParse->db, pIdx); - sqlite3VdbeChangeP4(v, -1, zAff, P4_TRANSIENT); - } } sqlite3ReleaseTempRange(pParse, regBase, nCol); return regBase; @@ -113006,6 +133956,9 @@ SQLITE_PRIVATE void sqlite3ResolvePartIdxLabel(Parse *pParse, int iLabel){ /* #include "sqliteInt.h" */ /* #include */ /* #include */ +#ifndef SQLITE_OMIT_FLOATING_POINT +/* #include */ +#endif /* #include "vdbeInt.h" */ /* @@ -113084,6 +134037,18 @@ static void typeofFunc( sqlite3_result_text(context, azType[i], -1, SQLITE_STATIC); } +/* subtype(X) +** +** Return the subtype of X +*/ +static void subtypeFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + UNUSED_PARAMETER(argc); + sqlite3_result_int(context, sqlite3_value_subtype(argv[0])); +} /* ** Implementation of the length() function @@ -113124,11 +134089,47 @@ static void lengthFunc( } } +/* +** Implementation of the octet_length() function +*/ +static void bytelengthFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + assert( argc==1 ); + UNUSED_PARAMETER(argc); + switch( sqlite3_value_type(argv[0]) ){ + case SQLITE_BLOB: { + sqlite3_result_int(context, sqlite3_value_bytes(argv[0])); + break; + } + case SQLITE_INTEGER: + case SQLITE_FLOAT: { + i64 m = sqlite3_context_db_handle(context)->enc<=SQLITE_UTF8 ? 1 : 2; + sqlite3_result_int64(context, sqlite3_value_bytes(argv[0])*m); + break; + } + case SQLITE_TEXT: { + if( sqlite3_value_encoding(argv[0])<=SQLITE_UTF8 ){ + sqlite3_result_int(context, sqlite3_value_bytes(argv[0])); + }else{ + sqlite3_result_int(context, sqlite3_value_bytes16(argv[0])); + } + break; + } + default: { + sqlite3_result_null(context); + break; + } + } +} + /* ** Implementation of the abs() function. ** ** IMP: R-23979-26855 The abs(X) function returns the absolute value of -** the numeric argument X. +** the numeric argument X. */ static void absFunc(sqlite3_context *context, int argc, sqlite3_value **argv){ assert( argc==1 ); @@ -113145,7 +134146,7 @@ static void absFunc(sqlite3_context *context, int argc, sqlite3_value **argv){ return; } iVal = -iVal; - } + } sqlite3_result_int64(context, iVal); break; } @@ -113192,6 +134193,8 @@ static void instrFunc( int N = 1; int isText; unsigned char firstChar; + sqlite3_value *pC1 = 0; + sqlite3_value *pC2 = 0; UNUSED_PARAMETER(argc); typeHaystack = sqlite3_value_type(argv[0]); @@ -113204,12 +134207,22 @@ static void instrFunc( zHaystack = sqlite3_value_blob(argv[0]); zNeedle = sqlite3_value_blob(argv[1]); isText = 0; - }else{ + }else if( typeHaystack!=SQLITE_BLOB && typeNeedle!=SQLITE_BLOB ){ zHaystack = sqlite3_value_text(argv[0]); zNeedle = sqlite3_value_text(argv[1]); isText = 1; + }else{ + pC1 = sqlite3_value_dup(argv[0]); + zHaystack = sqlite3_value_text(pC1); + if( zHaystack==0 ) goto endInstrOOM; + nHaystack = sqlite3_value_bytes(pC1); + pC2 = sqlite3_value_dup(argv[1]); + zNeedle = sqlite3_value_text(pC2); + if( zNeedle==0 ) goto endInstrOOM; + nNeedle = sqlite3_value_bytes(pC2); + isText = 1; } - if( zNeedle==0 || (nHaystack && zHaystack==0) ) return; + if( zNeedle==0 || (nHaystack && zHaystack==0) ) goto endInstrOOM; firstChar = zNeedle[0]; while( nNeedle<=nHaystack && (zHaystack[0]!=firstChar || memcmp(zHaystack, zNeedle, nNeedle)!=0) @@ -113223,10 +134236,17 @@ static void instrFunc( if( nNeedle>nHaystack ) N = 0; } sqlite3_result_int(context, N); +endInstr: + sqlite3_value_free(pC1); + sqlite3_value_free(pC2); + return; +endInstrOOM: + sqlite3_result_error_nomem(context); + goto endInstr; } /* -** Implementation of the printf() function. +** Implementation of the printf() (a.k.a. format()) SQL function. */ static void printfFunc( sqlite3_context *context, @@ -113246,9 +134266,18 @@ static void printfFunc( sqlite3StrAccumInit(&str, db, 0, 0, db->aLimit[SQLITE_LIMIT_LENGTH]); str.printfFlags = SQLITE_PRINTF_SQLFUNC; sqlite3_str_appendf(&str, zFormat, &x); - n = str.nChar; - sqlite3_result_text(context, sqlite3StrAccumFinish(&str), n, - SQLITE_DYNAMIC); + if( str.accError==SQLITE_OK ){ + n = str.nChar; + sqlite3_result_text(context, sqlite3StrAccumFinish(&str), n, + SQLITE_DYNAMIC); + }else{ + if( str.accError==SQLITE_NOMEM ){ + sqlite3_result_error_nomem(context); + }else{ + sqlite3_result_error_toobig(context); + } + sqlite3_str_reset(&str); + } } } @@ -113274,16 +134303,10 @@ static void substrFunc( int len; int p0type; i64 p1, p2; - int negP2 = 0; assert( argc==3 || argc==2 ); - if( sqlite3_value_type(argv[1])==SQLITE_NULL - || (argc==3 && sqlite3_value_type(argv[2])==SQLITE_NULL) - ){ - return; - } p0type = sqlite3_value_type(argv[0]); - p1 = sqlite3_value_int(argv[1]); + p1 = sqlite3_value_int64(argv[1]); if( p0type==SQLITE_BLOB ){ len = sqlite3_value_bytes(argv[0]); z = sqlite3_value_blob(argv[0]); @@ -113299,28 +134322,31 @@ static void substrFunc( } } } -#ifdef SQLITE_SUBSTR_COMPATIBILITY - /* If SUBSTR_COMPATIBILITY is defined then substr(X,0,N) work the same as - ** as substr(X,1,N) - it returns the first N characters of X. This - ** is essentially a back-out of the bug-fix in check-in [5fc125d362df4b8] - ** from 2009-02-02 for compatibility of applications that exploited the - ** old buggy behavior. */ - if( p1==0 ) p1 = 1; /* */ -#endif if( argc==3 ){ - p2 = sqlite3_value_int(argv[2]); - if( p2<0 ){ - p2 = -p2; - negP2 = 1; - } + p2 = sqlite3_value_int64(argv[2]); + if( p2==0 && sqlite3_value_type(argv[2])==SQLITE_NULL ) return; }else{ p2 = sqlite3_context_db_handle(context)->aLimit[SQLITE_LIMIT_LENGTH]; } + if( p1==0 ){ +#ifdef SQLITE_SUBSTR_COMPATIBILITY + /* If SUBSTR_COMPATIBILITY is defined then substr(X,0,N) work the same as + ** as substr(X,1,N) - it returns the first N characters of X. This + ** is essentially a back-out of the bug-fix in check-in [5fc125d362df4b8] + ** from 2009-02-02 for compatibility of applications that exploited the + ** old buggy behavior. */ + p1 = 1; /* */ +#endif + if( sqlite3_value_type(argv[1])==SQLITE_NULL ) return; + } if( p1<0 ){ p1 += len; if( p1<0 ){ - p2 += p1; - if( p2<0 ) p2 = 0; + if( p2<0 ){ + p2 = 0; + }else{ + p2 += p1; + } p1 = 0; } }else if( p1>0 ){ @@ -113328,12 +134354,13 @@ static void substrFunc( }else if( p2>0 ){ p2--; } - if( negP2 ){ - p1 -= p2; - if( p1<0 ){ - p2 += p1; - p1 = 0; + if( p2<0 ){ + if( p2<-p1 ){ + p2 = p1; + }else{ + p2 = -p2; } + p1 -= p2; } assert( p1>=0 && p2>=0 ); if( p0type!=SQLITE_BLOB ){ @@ -113347,9 +134374,11 @@ static void substrFunc( sqlite3_result_text64(context, (char*)z, z2-z, SQLITE_TRANSIENT, SQLITE_UTF8); }else{ - if( p1+p2>len ){ + if( p1>=len ){ + p1 = p2 = 0; + }else if( p2>len-p1 ){ p2 = len-p1; - if( p2<0 ) p2 = 0; + assert( p2>0 ); } sqlite3_result_blob64(context, (char*)&z[p1], (u64)p2, SQLITE_TRANSIENT); } @@ -113360,13 +134389,13 @@ static void substrFunc( */ #ifndef SQLITE_OMIT_FLOATING_POINT static void roundFunc(sqlite3_context *context, int argc, sqlite3_value **argv){ - int n = 0; + i64 n = 0; double r; char *zBuf; assert( argc==1 || argc==2 ); if( argc==2 ){ if( SQLITE_NULL==sqlite3_value_type(argv[1]) ) return; - n = sqlite3_value_int(argv[1]); + n = sqlite3_value_int64(argv[1]); if( n>30 ) n = 30; if( n<0 ) n = 0; } @@ -113376,17 +134405,17 @@ static void roundFunc(sqlite3_context *context, int argc, sqlite3_value **argv){ ** handle the rounding directly, ** otherwise use printf. */ - if( n==0 && r>=0 && r+4503599627370496.0 ){ + /* The value has no fractional part so there is nothing to round */ + }else if( n==0 ){ + r = (double)((sqlite_int64)(r+(r<0?-0.5:+0.5))); }else{ - zBuf = sqlite3_mprintf("%.*f",n,r); + zBuf = sqlite3_mprintf("%!.*f",(int)n,r); if( zBuf==0 ){ sqlite3_result_error_nomem(context); return; } - sqlite3AtoF(zBuf, &r, sqlite3Strlen30(zBuf), SQLITE_UTF8); + sqlite3AtoF(zBuf, &r); sqlite3_free(zBuf); } sqlite3_result_double(context, r); @@ -113405,7 +134434,7 @@ static void *contextMalloc(sqlite3_context *context, i64 nByte){ sqlite3 *db = sqlite3_context_db_handle(context); assert( nByte>0 ); testcase( nByte==db->aLimit[SQLITE_LIMIT_LENGTH] ); - testcase( nByte==db->aLimit[SQLITE_LIMIT_LENGTH]+1 ); + testcase( nByte==(i64)db->aLimit[SQLITE_LIMIT_LENGTH]+1 ); if( nByte>db->aLimit[SQLITE_LIMIT_LENGTH] ){ sqlite3_result_error_toobig(context); z = 0; @@ -113471,7 +134500,7 @@ static void lowerFunc(sqlite3_context *context, int argc, sqlite3_value **argv){ #define noopFunc versionFunc /* Substitute function - never called */ /* -** Implementation of random(). Return a random integer. +** Implementation of random(). Return a random integer. */ static void randomFunc( sqlite3_context *context, @@ -113482,11 +134511,11 @@ static void randomFunc( UNUSED_PARAMETER2(NotUsed, NotUsed2); sqlite3_randomness(sizeof(r), &r); if( r<0 ){ - /* We need to prevent a random number of 0x8000000000000000 + /* We need to prevent a random number of 0x8000000000000000 ** (or -9223372036854775808) since when you do abs() of that ** number of you get the same value back again. To do this ** in a way that is testable, mask the sign bit off of negative - ** values, resulting in a positive value. Then take the + ** values, resulting in a positive value. Then take the ** 2s complement of that positive value. The end result can ** therefore be no less than -9223372036854775807. */ @@ -113524,8 +134553,8 @@ static void randomBlob( ** value is the same as the sqlite3_last_insert_rowid() API function. */ static void last_insert_rowid( - sqlite3_context *context, - int NotUsed, + sqlite3_context *context, + int NotUsed, sqlite3_value **NotUsed2 ){ sqlite3 *db = sqlite3_context_db_handle(context); @@ -113539,9 +134568,9 @@ static void last_insert_rowid( /* ** Implementation of the changes() SQL function. ** -** IMP: R-62073-11209 The changes() SQL function is a wrapper -** around the sqlite3_changes() C/C++ function and hence follows the same -** rules for counting changes. +** IMP: R-32760-32347 The changes() SQL function is a wrapper +** around the sqlite3_changes64() C/C++ function and hence follows the +** same rules for counting changes. */ static void changes( sqlite3_context *context, @@ -113550,12 +134579,12 @@ static void changes( ){ sqlite3 *db = sqlite3_context_db_handle(context); UNUSED_PARAMETER2(NotUsed, NotUsed2); - sqlite3_result_int(context, sqlite3_changes(db)); + sqlite3_result_int64(context, sqlite3_changes64(db)); } /* ** Implementation of the total_changes() SQL function. The return value is -** the same as the sqlite3_total_changes() API function. +** the same as the sqlite3_total_changes64() API function. */ static void total_changes( sqlite3_context *context, @@ -113564,9 +134593,9 @@ static void total_changes( ){ sqlite3 *db = sqlite3_context_db_handle(context); UNUSED_PARAMETER2(NotUsed, NotUsed2); - /* IMP: R-52756-41993 This function is a wrapper around the - ** sqlite3_total_changes() C/C++ interface. */ - sqlite3_result_int(context, sqlite3_total_changes(db)); + /* IMP: R-11217-42568 This function is a wrapper around the + ** sqlite3_total_changes64() C/C++ interface. */ + sqlite3_result_int64(context, sqlite3_total_changes64(db)); } /* @@ -113581,7 +134610,7 @@ struct compareInfo { /* ** For LIKE and GLOB matching on EBCDIC machines, assume that every -** character is exactly one byte in size. Also, provde the Utf8Read() +** character is exactly one byte in size. Also, provide the Utf8Read() ** macro for fast reading of the next character in the common case where ** the next character is ASCII. */ @@ -113633,7 +134662,7 @@ static const struct compareInfo likeInfoAlt = { '%', '_', 0, 0 }; ** it the last character in the list. ** ** Like matching rules: -** +** ** '%' Matches any sequence of zero or more characters ** *** '_' Matches any one character @@ -113656,13 +134685,14 @@ static int patternCompare( u32 matchAll = pInfo->matchAll; /* "*" or "%" */ u8 noCase = pInfo->noCase; /* True if uppercase==lowercase */ const u8 *zEscaped = 0; /* One past the last escaped input char */ - + while( (c = Utf8Read(zPattern))!=0 ){ if( c==matchAll ){ /* Match "*" */ /* Skip over multiple "*" characters in the pattern. If there ** are also "?" characters, skip those as well, but consume a ** single character of the input string for each "?" skipped */ - while( (c=Utf8Read(zPattern)) == matchAll || c == matchOne ){ + while( (c=Utf8Read(zPattern)) == matchAll + || (c == matchOne && matchOne!=0) ){ if( c==matchOne && sqlite3Utf8Read(&zString)==0 ){ return SQLITE_NOWILDCARDMATCH; } @@ -113695,7 +134725,7 @@ static int patternCompare( ** c but in the other case and search the input string for either ** c or cx. */ - if( c<=0x80 ){ + if( c<0x80 ){ char zStop[3]; int bMatch; if( noCase ){ @@ -113778,7 +134808,13 @@ static int patternCompare( ** non-zero if there is no match. */ SQLITE_API int sqlite3_strglob(const char *zGlobPattern, const char *zString){ - return patternCompare((u8*)zGlobPattern, (u8*)zString, &globInfo, '['); + if( zString==0 ){ + return zGlobPattern!=0; + }else if( zGlobPattern==0 ){ + return 1; + }else { + return patternCompare((u8*)zGlobPattern, (u8*)zString, &globInfo, '['); + } } /* @@ -113786,7 +134822,13 @@ SQLITE_API int sqlite3_strglob(const char *zGlobPattern, const char *zString){ ** a miss - like strcmp(). */ SQLITE_API int sqlite3_strlike(const char *zPattern, const char *zStr, unsigned int esc){ - return patternCompare((u8*)zPattern, (u8*)zStr, &likeInfoNorm, esc); + if( zStr==0 ){ + return zPattern!=0; + }else if( zPattern==0 ){ + return 1; + }else{ + return patternCompare((u8*)zPattern, (u8*)zStr, &likeInfoNorm, esc); + } } /* @@ -113801,7 +134843,7 @@ SQLITE_API int sqlite3_like_count = 0; /* ** Implementation of the like() SQL function. This function implements -** the build-in LIKE operator. The first argument to the function is the +** the built-in LIKE operator. The first argument to the function is the ** pattern and the second argument is the string. So, the SQL statements: ** ** A LIKE B @@ -113812,8 +134854,8 @@ SQLITE_API int sqlite3_like_count = 0; ** the GLOB operator. */ static void likeFunc( - sqlite3_context *context, - int argc, + sqlite3_context *context, + int argc, sqlite3_value **argv ){ const unsigned char *zA, *zB; @@ -113821,6 +134863,7 @@ static void likeFunc( int nPat; sqlite3 *db = sqlite3_context_db_handle(context); struct compareInfo *pInfo = sqlite3_user_data(context); + struct compareInfo backupInfo; #ifdef SQLITE_LIKE_DOESNT_MATCH_BLOBS if( sqlite3_value_type(argv[0])==SQLITE_BLOB @@ -113833,8 +134876,6 @@ static void likeFunc( return; } #endif - zB = sqlite3_value_text(argv[0]); - zA = sqlite3_value_text(argv[1]); /* Limit the length of the LIKE or GLOB pattern to avoid problems ** of deep recursion and N*N behavior in patternCompare(). @@ -113846,8 +134887,6 @@ static void likeFunc( sqlite3_result_error(context, "LIKE or GLOB pattern too complex", -1); return; } - assert( zB==sqlite3_value_text(argv[0]) ); /* Encoding did not change */ - if( argc==3 ){ /* The escape character string must consist of a single UTF-8 character. ** Otherwise, return an error. @@ -113855,14 +134894,22 @@ static void likeFunc( const unsigned char *zEsc = sqlite3_value_text(argv[2]); if( zEsc==0 ) return; if( sqlite3Utf8CharLen((char*)zEsc, -1)!=1 ){ - sqlite3_result_error(context, + sqlite3_result_error(context, "ESCAPE expression must be a single character", -1); return; } escape = sqlite3Utf8Read(&zEsc); + if( escape==pInfo->matchAll || escape==pInfo->matchOne ){ + memcpy(&backupInfo, pInfo, sizeof(backupInfo)); + pInfo = &backupInfo; + if( escape==pInfo->matchAll ) pInfo->matchAll = 0; + if( escape==pInfo->matchOne ) pInfo->matchOne = 0; + } }else{ escape = pInfo->matchSet; } + zB = sqlite3_value_text(argv[0]); + zA = sqlite3_value_text(argv[1]); if( zA && zB ){ #ifdef SQLITE_TEST sqlite3_like_count++; @@ -113960,8 +135007,8 @@ static void compileoptionusedFunc( #endif /* SQLITE_OMIT_COMPILEOPTION_DIAGS */ /* -** Implementation of the sqlite_compileoption_get() function. -** The result is a string that identifies the compiler options +** Implementation of the sqlite_compileoption_get() function. +** The result is a string that identifies the compiler options ** used to build SQLite. */ #ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS @@ -113985,43 +135032,39 @@ static void compileoptiongetFunc( ** digits. */ static const char hexdigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; /* -** Implementation of the QUOTE() function. This function takes a single -** argument. If the argument is numeric, the return value is the same as -** the argument. If the argument is NULL, the return value is the string -** "NULL". Otherwise, the argument is enclosed in single quotes with -** single-quote escapes. +** Append to pStr text that is the SQL literal representation of the +** value contained in pValue. */ -static void quoteFunc(sqlite3_context *context, int argc, sqlite3_value **argv){ - assert( argc==1 ); - UNUSED_PARAMETER(argc); - switch( sqlite3_value_type(argv[0]) ){ +SQLITE_PRIVATE void sqlite3QuoteValue(StrAccum *pStr, sqlite3_value *pValue, int bEscape){ + /* As currently implemented, the string must be initially empty. + ** we might relax this requirement in the future, but that will + ** require enhancements to the implementation. */ + assert( pStr!=0 && pStr->nChar==0 ); + + switch( sqlite3_value_type(pValue) ){ case SQLITE_FLOAT: { - double r1, r2; - char zBuf[50]; - r1 = sqlite3_value_double(argv[0]); - sqlite3_snprintf(sizeof(zBuf), zBuf, "%!.15g", r1); - sqlite3AtoF(zBuf, &r2, 20, SQLITE_UTF8); - if( r1!=r2 ){ - sqlite3_snprintf(sizeof(zBuf), zBuf, "%!.20e", r1); - } - sqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT); + /* ,--- Show infinity as 9.0e+999 + ** | + ** | ,--- 17 precision guarantees round-trip + ** v v */ + sqlite3_str_appendf(pStr, "%!0.17g", sqlite3_value_double(pValue)); break; } case SQLITE_INTEGER: { - sqlite3_result_value(context, argv[0]); + sqlite3_str_appendf(pStr, "%lld", sqlite3_value_int64(pValue)); break; } case SQLITE_BLOB: { - char *zText = 0; - char const *zBlob = sqlite3_value_blob(argv[0]); - int nBlob = sqlite3_value_bytes(argv[0]); - assert( zBlob==sqlite3_value_blob(argv[0]) ); /* No encoding change */ - zText = (char *)contextMalloc(context, (2*(i64)nBlob)+4); - if( zText ){ + char const *zBlob = sqlite3_value_blob(pValue); + i64 nBlob = sqlite3_value_bytes(pValue); + assert( zBlob==sqlite3_value_blob(pValue) ); /* No encoding change */ + sqlite3StrAccumEnlarge(pStr, nBlob*2 + 4); + if( pStr->accError==0 ){ + char *zText = pStr->zText; int i; for(i=0; i>4)&0x0F]; @@ -114031,45 +135074,154 @@ static void quoteFunc(sqlite3_context *context, int argc, sqlite3_value **argv){ zText[(nBlob*2)+3] = '\0'; zText[0] = 'X'; zText[1] = '\''; - sqlite3_result_text(context, zText, -1, SQLITE_TRANSIENT); - sqlite3_free(zText); + pStr->nChar = nBlob*2 + 3; } break; } case SQLITE_TEXT: { - int i,j; - u64 n; - const unsigned char *zArg = sqlite3_value_text(argv[0]); - char *z; - - if( zArg==0 ) return; - for(i=0, n=0; zArg[i]; i++){ if( zArg[i]=='\'' ) n++; } - z = contextMalloc(context, ((i64)i)+((i64)n)+3); - if( z ){ - z[0] = '\''; - for(i=0, j=1; zArg[i]; i++){ - z[j++] = zArg[i]; - if( zArg[i]=='\'' ){ - z[j++] = '\''; - } - } - z[j++] = '\''; - z[j] = 0; - sqlite3_result_text(context, z, j, sqlite3_free); - } + const unsigned char *zArg = sqlite3_value_text(pValue); + sqlite3_str_appendf(pStr, bEscape ? "%#Q" : "%Q", zArg); break; } default: { - assert( sqlite3_value_type(argv[0])==SQLITE_NULL ); - sqlite3_result_text(context, "NULL", 4, SQLITE_STATIC); + assert( sqlite3_value_type(pValue)==SQLITE_NULL ); + sqlite3_str_append(pStr, "NULL", 4); break; } } } +/* +** Return true if z[] begins with N hexadecimal digits, and write +** a decoding of those digits into *pVal. Or return false if any +** one of the first N characters in z[] is not a hexadecimal digit. +*/ +static int isNHex(const char *z, int N, u32 *pVal){ + int i; + u32 v = 0; + for(i=0; i0 ){ + memmove(&zOut[j], &zIn[i], n); + j += n; + i += n; + } + if( zIn[i+1]=='\\' ){ + i += 2; + zOut[j++] = '\\'; + }else if( sqlite3Isxdigit(zIn[i+1]) ){ + if( !isNHex(&zIn[i+1], 4, &v) ) goto unistr_error; + i += 5; + j += sqlite3AppendOneUtf8Character(&zOut[j], v); + }else if( zIn[i+1]=='+' ){ + if( !isNHex(&zIn[i+2], 6, &v) ) goto unistr_error; + i += 8; + j += sqlite3AppendOneUtf8Character(&zOut[j], v); + }else if( zIn[i+1]=='u' ){ + if( !isNHex(&zIn[i+2], 4, &v) ) goto unistr_error; + i += 6; + j += sqlite3AppendOneUtf8Character(&zOut[j], v); + }else if( zIn[i+1]=='U' ){ + if( !isNHex(&zIn[i+2], 8, &v) ) goto unistr_error; + i += 10; + j += sqlite3AppendOneUtf8Character(&zOut[j], v); + }else{ + goto unistr_error; + } + } + zOut[j] = 0; + sqlite3_result_text64(context, zOut, j, sqlite3_free, SQLITE_UTF8_ZT); + return; + +unistr_error: + sqlite3_free(zOut); + sqlite3_result_error(context, "invalid Unicode escape", -1); + return; +} + + +/* +** Implementation of the QUOTE() function. +** +** The quote(X) function returns the text of an SQL literal which is the +** value of its argument suitable for inclusion into an SQL statement. +** Strings are surrounded by single-quotes with escapes on interior quotes +** as needed. BLOBs are encoded as hexadecimal literals. Strings with +** embedded NUL characters cannot be represented as string literals in SQL +** and hence the returned string literal is truncated prior to the first NUL. +** +** If sqlite3_user_data() is non-zero, then the UNISTR_QUOTE() function is +** implemented instead. The difference is that UNISTR_QUOTE() uses the +** UNISTR() function to escape control characters. +*/ +static void quoteFunc(sqlite3_context *context, int argc, sqlite3_value **argv){ + sqlite3_str str; + sqlite3 *db = sqlite3_context_db_handle(context); + assert( argc==1 ); + UNUSED_PARAMETER(argc); + sqlite3StrAccumInit(&str, db, 0, 0, db->aLimit[SQLITE_LIMIT_LENGTH]); + sqlite3QuoteValue(&str,argv[0],SQLITE_PTR_TO_INT(sqlite3_user_data(context))); + sqlite3_result_text(context, sqlite3StrAccumFinish(&str), str.nChar, + SQLITE_DYNAMIC); + if( str.accError!=SQLITE_OK ){ + sqlite3_result_null(context); + sqlite3_result_error_code(context, str.accError); + } +} + /* ** The unicode() function. Return the integer unicode code-point value -** for the first character of the input string. +** for the first character of the input string. */ static void unicodeFunc( sqlite3_context *context, @@ -114120,7 +135272,8 @@ static void charFunc( *zOut++ = 0x80 + (u8)(c & 0x3F); } \ } - sqlite3_result_text64(context, (char*)z, zOut-z, sqlite3_free, SQLITE_UTF8); + *zOut = 0; + sqlite3_result_text64(context, (char*)z, zOut-z,sqlite3_free,SQLITE_UTF8_ZT); } /* @@ -114148,10 +135301,101 @@ static void hexFunc( *(z++) = hexdigits[c&0xf]; } *z = 0; - sqlite3_result_text(context, zHex, n*2, sqlite3_free); + sqlite3_result_text64(context, zHex, (u64)(z-zHex), + sqlite3_free, SQLITE_UTF8_ZT); } } +/* +** Buffer zStr contains nStr bytes of utf-8 encoded text. Return 1 if zStr +** contains character ch, or 0 if it does not. +*/ +static int strContainsChar(const u8 *zStr, int nStr, u32 ch){ + const u8 *zEnd = &zStr[nStr]; + const u8 *z = zStr; + while( z0 ){ - azChar = contextMalloc(context, ((i64)nChar)*(sizeof(char*)+1)); + azChar = contextMalloc(context, + ((i64)nChar)*(sizeof(char*)+sizeof(unsigned))); if( azChar==0 ){ return; } - aLen = (unsigned char*)&azChar[nChar]; + aLen = (unsigned*)&azChar[nChar]; for(z=zCharSet, nChar=0; *z; nChar++){ azChar[nChar] = (unsigned char *)z; SQLITE_SKIP_UTF8(z); - aLen[nChar] = (u8)(z - azChar[nChar]); + aLen[nChar] = (unsigned)(z - azChar[nChar]); } } } @@ -114323,7 +135568,7 @@ static void trimFunc( flags = SQLITE_PTR_TO_INT(sqlite3_user_data(context)); if( flags & 1 ){ while( nIn>0 ){ - int len = 0; + unsigned int len = 0; for(i=0; i0 ){ - int len = 0; + unsigned int len = 0; for(i=0; i0 ){ + memcpy(&z[j], zSep, nSep); + j += nSep; + } + memcpy(&z[j], v, k); + j += k; + bNotNull = 1; + } + } + } + z[j] = 0; + assert( j<=n ); + sqlite3_result_text64(context, z, j, sqlite3_free, SQLITE_UTF8_ZT); +} + +/* +** The CONCAT(...) function. Generate a string result that is the +** concatentation of all non-null arguments. +*/ +static void concatFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + concatFuncCore(context, argc, argv, 0, ""); +} + +/* +** The CONCAT_WS(separator, ...) function. +** +** Generate a string that is the concatenation of 2nd through the Nth +** argument. Use the first argument (which must be non-NULL) as the +** separator. +*/ +static void concatwsFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + int nSep = sqlite3_value_bytes(argv[0]); + const char *zSep = (const char*)sqlite3_value_text(argv[0]); + if( zSep==0 ) return; + concatFuncCore(context, argc-1, argv+1, nSep, zSep); +} + #ifdef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION /* ** The "unknown" function is automatically substituted in place of ** any unrecognized function name when doing an EXPLAIN or EXPLAIN QUERY PLAN -** when the SQLITE_ENABLE_UNKNOWN_FUNCTION compile-time option is used. +** when the SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION compile-time option is used. ** When the "sqlite3" command-line shell is built using this functionality, ** that allows an EXPLAIN or EXPLAIN QUERY PLAN for complex queries ** involving application-defined functions to be examined in a generic @@ -114368,6 +135690,9 @@ static void unknownFunc( sqlite3_value **argv ){ /* no-op */ + (void)context; + (void)argc; + (void)argv; } #endif /*SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION*/ @@ -114381,7 +135706,7 @@ static void unknownFunc( ** Compute the soundex encoding of a word. ** ** IMP: R-59782-00072 The soundex(X) function returns a string that is the -** soundex encoding of the string X. +** soundex encoding of the string X. */ static void soundexFunc( sqlite3_context *context, @@ -114469,13 +135794,68 @@ static void loadExt(sqlite3_context *context, int argc, sqlite3_value **argv){ */ typedef struct SumCtx SumCtx; struct SumCtx { - double rSum; /* Floating point sum */ - i64 iSum; /* Integer sum */ + double rSum; /* Running sum as as a double */ + double rErr; /* Error term for Kahan-Babushka-Neumaier summation */ + i64 iSum; /* Running sum as a signed integer */ i64 cnt; /* Number of elements summed */ - u8 overflow; /* True if integer overflow seen */ - u8 approx; /* True if non-integer value was input to the sum */ + u8 approx; /* True if any non-integer value was input to the sum */ + u8 ovrfl; /* Integer overflow seen */ }; +/* +** Do one step of the Kahan-Babushka-Neumaier summation. +** +** https://en.wikipedia.org/wiki/Kahan_summation_algorithm +** +** Variables are marked "volatile" to defeat c89 x86 floating point +** optimizations can mess up this algorithm. +*/ +static void kahanBabuskaNeumaierStep( + volatile SumCtx *pSum, + volatile double r +){ + volatile double s = pSum->rSum; + volatile double t = s + r; + if( fabs(s) > fabs(r) ){ + pSum->rErr += (s - t) + r; + }else{ + pSum->rErr += (r - t) + s; + } + pSum->rSum = t; +} + +/* +** Add a (possibly large) integer to the running sum. +*/ +static void kahanBabuskaNeumaierStepInt64(volatile SumCtx *pSum, i64 iVal){ + if( iVal<=-4503599627370496LL || iVal>=+4503599627370496LL ){ + i64 iBig, iSm; + iSm = iVal % 16384; + iBig = iVal - iSm; + kahanBabuskaNeumaierStep(pSum, iBig); + kahanBabuskaNeumaierStep(pSum, iSm); + }else{ + kahanBabuskaNeumaierStep(pSum, (double)iVal); + } +} + +/* +** Initialize the Kahan-Babaska-Neumaier sum from a 64-bit integer +*/ +static void kahanBabuskaNeumaierInit( + volatile SumCtx *p, + i64 iVal +){ + if( iVal<=-4503599627370496LL || iVal>=+4503599627370496LL ){ + i64 iSm = iVal % 16384; + p->rSum = (double)(iVal - iSm); + p->rErr = (double)iSm; + }else{ + p->rSum = (double)iVal; + p->rErr = 0.0; + } +} + /* ** Routines used to compute the sum, average, and total. ** @@ -114483,7 +135863,7 @@ struct SumCtx { ** that it returns NULL if it sums over no inputs. TOTAL returns ** 0.0 in that case. In addition, TOTAL always returns a float where ** SUM might return an integer if it never encounters a floating point -** value. TOTAL never fails, but SUM might through an exception if +** value. TOTAL never fails, but SUM might throw an exception if ** it overflows an integer. */ static void sumStep(sqlite3_context *context, int argc, sqlite3_value **argv){ @@ -114495,15 +135875,29 @@ static void sumStep(sqlite3_context *context, int argc, sqlite3_value **argv){ type = sqlite3_value_numeric_type(argv[0]); if( p && type!=SQLITE_NULL ){ p->cnt++; - if( type==SQLITE_INTEGER ){ - i64 v = sqlite3_value_int64(argv[0]); - p->rSum += v; - if( (p->approx|p->overflow)==0 && sqlite3AddInt64(&p->iSum, v) ){ - p->approx = p->overflow = 1; + if( p->approx==0 ){ + if( type!=SQLITE_INTEGER ){ + kahanBabuskaNeumaierInit(p, p->iSum); + p->approx = 1; + kahanBabuskaNeumaierStep(p, sqlite3_value_double(argv[0])); + }else{ + i64 x = p->iSum; + if( sqlite3AddInt64(&x, sqlite3_value_int64(argv[0]))==0 ){ + p->iSum = x; + }else{ + p->ovrfl = 1; + kahanBabuskaNeumaierInit(p, p->iSum); + p->approx = 1; + kahanBabuskaNeumaierStepInt64(p, sqlite3_value_int64(argv[0])); + } } }else{ - p->rSum += sqlite3_value_double(argv[0]); - p->approx = 1; + if( type==SQLITE_INTEGER ){ + kahanBabuskaNeumaierStepInt64(p, sqlite3_value_int64(argv[0])); + }else{ + p->ovrfl = 0; + kahanBabuskaNeumaierStep(p, sqlite3_value_double(argv[0])); + } } } } @@ -114520,13 +135914,26 @@ static void sumInverse(sqlite3_context *context, int argc, sqlite3_value**argv){ if( ALWAYS(p) && type!=SQLITE_NULL ){ assert( p->cnt>0 ); p->cnt--; - assert( type==SQLITE_INTEGER || p->approx ); - if( type==SQLITE_INTEGER && p->approx==0 ){ - i64 v = sqlite3_value_int64(argv[0]); - p->rSum -= v; - p->iSum -= v; + if( !p->approx ){ + i64 x = p->iSum; + if( sqlite3SubInt64(&x, sqlite3_value_int64(argv[0]))==0 ){ + p->iSum = x; + return; + } + p->ovrfl = 1; + p->approx = 1; + kahanBabuskaNeumaierInit(p, p->iSum); + } + if( type==SQLITE_INTEGER ){ + i64 iVal = sqlite3_value_int64(argv[0]); + if( iVal!=SMALLEST_INT64 ){ + kahanBabuskaNeumaierStepInt64(p, -iVal); + }else{ + kahanBabuskaNeumaierStepInt64(p, LARGEST_INT64); + kahanBabuskaNeumaierStepInt64(p, 1); + } }else{ - p->rSum -= sqlite3_value_double(argv[0]); + kahanBabuskaNeumaierStep(p, -sqlite3_value_double(argv[0])); } } } @@ -114537,10 +135944,14 @@ static void sumFinalize(sqlite3_context *context){ SumCtx *p; p = sqlite3_aggregate_context(context, 0); if( p && p->cnt>0 ){ - if( p->overflow ){ - sqlite3_result_error(context,"integer overflow",-1); - }else if( p->approx ){ - sqlite3_result_double(context, p->rSum); + if( p->approx ){ + if( p->ovrfl ){ + sqlite3_result_error(context,"integer overflow",-1); + }else if( !sqlite3IsOverflow(p->rErr) ){ + sqlite3_result_double(context, p->rSum+p->rErr); + }else{ + sqlite3_result_double(context, p->rSum); + } }else{ sqlite3_result_int64(context, p->iSum); } @@ -114550,14 +135961,29 @@ static void avgFinalize(sqlite3_context *context){ SumCtx *p; p = sqlite3_aggregate_context(context, 0); if( p && p->cnt>0 ){ - sqlite3_result_double(context, p->rSum/(double)p->cnt); + double r; + if( p->approx ){ + r = p->rSum; + if( !sqlite3IsOverflow(p->rErr) ) r += p->rErr; + }else{ + r = (double)(p->iSum); + } + sqlite3_result_double(context, r/(double)p->cnt); } } static void totalFinalize(sqlite3_context *context){ SumCtx *p; + double r = 0.0; p = sqlite3_aggregate_context(context, 0); - /* (double)0 In case of SQLITE_OMIT_FLOATING_POINT... */ - sqlite3_result_double(context, p ? p->rSum : (double)0); + if( p ){ + if( p->approx ){ + r = p->rSum; + if( !sqlite3IsOverflow(p->rErr) ) r += p->rErr; + }else{ + r = (double)(p->iSum); + } + } + sqlite3_result_double(context, r); } /* @@ -114584,13 +136010,13 @@ static void countStep(sqlite3_context *context, int argc, sqlite3_value **argv){ #ifndef SQLITE_OMIT_DEPRECATED /* The sqlite3_aggregate_count() function is deprecated. But just to make - ** sure it still operates correctly, verify that its count agrees with our + ** sure it still operates correctly, verify that its count agrees with our ** internal count when using count(*) and when the total count can be ** expressed as a 32-bit integer. */ assert( argc==1 || p==0 || p->n>0x7fffffff || p->bInverse || p->n==sqlite3_aggregate_count(context) ); #endif -} +} static void countFinalize(sqlite3_context *context){ CountCtx *p; p = sqlite3_aggregate_context(context, 0); @@ -114607,7 +136033,7 @@ static void countInverse(sqlite3_context *ctx, int argc, sqlite3_value **argv){ p->bInverse = 1; #endif } -} +} #else # define countInverse 0 #endif /* SQLITE_OMIT_WINDOWFUNC */ @@ -114616,8 +136042,8 @@ static void countInverse(sqlite3_context *ctx, int argc, sqlite3_value **argv){ ** Routines to implement min() and max() aggregate functions. */ static void minmaxStep( - sqlite3_context *context, - int NotUsed, + sqlite3_context *context, + int NotUsed, sqlite3_value **argv ){ Mem *pArg = (Mem *)argv[0]; @@ -114676,97 +136102,174 @@ static void minMaxFinalize(sqlite3_context *context){ /* ** group_concat(EXPR, ?SEPARATOR?) +** string_agg(EXPR, SEPARATOR) +** +** Content is accumulated in GroupConcatCtx.str with the SEPARATOR +** coming before the EXPR value, except for the first entry which +** omits the SEPARATOR. +** +** It is tragic that the SEPARATOR goes before the EXPR string. The +** groupConcatInverse() implementation would have been easier if the +** SEPARATOR were appended after EXPR. And the order is undocumented, +** so we could change it, in theory. But the old behavior has been +** around for so long that we dare not, for fear of breaking something. */ +typedef struct { + StrAccum str; /* The accumulated concatenation */ +#ifndef SQLITE_OMIT_WINDOWFUNC + int nAccum; /* Number of strings presently concatenated */ + int nFirstSepLength; /* Used to detect separator length change */ + /* If pnSepLengths!=0, refs an array of inter-string separator lengths, + ** stored as actually incorporated into presently accumulated result. + ** (Hence, its slots in use number nAccum-1 between method calls.) + ** If pnSepLengths==0, nFirstSepLength is the length used throughout. + */ + int *pnSepLengths; +#endif +} GroupConcatCtx; + static void groupConcatStep( sqlite3_context *context, int argc, sqlite3_value **argv ){ const char *zVal; - StrAccum *pAccum; + GroupConcatCtx *pGCC; const char *zSep; int nVal, nSep; assert( argc==1 || argc==2 ); if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return; - pAccum = (StrAccum*)sqlite3_aggregate_context(context, sizeof(*pAccum)); - - if( pAccum ){ + pGCC = (GroupConcatCtx*)sqlite3_aggregate_context(context, sizeof(*pGCC)); + if( pGCC ){ sqlite3 *db = sqlite3_context_db_handle(context); - int firstTerm = pAccum->mxAlloc==0; - pAccum->mxAlloc = db->aLimit[SQLITE_LIMIT_LENGTH]; - if( !firstTerm ){ - if( argc==2 ){ - zSep = (char*)sqlite3_value_text(argv[1]); - nSep = sqlite3_value_bytes(argv[1]); - }else{ - zSep = ","; - nSep = 1; + int firstTerm = pGCC->str.mxAlloc==0; + pGCC->str.mxAlloc = db->aLimit[SQLITE_LIMIT_LENGTH]; + if( argc==1 ){ + if( !firstTerm ){ + sqlite3_str_appendchar(&pGCC->str, 1, ','); + } +#ifndef SQLITE_OMIT_WINDOWFUNC + else{ + pGCC->nFirstSepLength = 1; + } +#endif + }else if( !firstTerm ){ + zSep = (char*)sqlite3_value_text(argv[1]); + nSep = sqlite3_value_bytes(argv[1]); + if( zSep ){ + sqlite3_str_append(&pGCC->str, zSep, nSep); } - if( zSep ) sqlite3_str_append(pAccum, zSep, nSep); +#ifndef SQLITE_OMIT_WINDOWFUNC + else{ + nSep = 0; + } + if( nSep != pGCC->nFirstSepLength || pGCC->pnSepLengths != 0 ){ + int *pnsl = pGCC->pnSepLengths; + if( pnsl == 0 ){ + /* First separator length variation seen, start tracking them. */ + pnsl = (int*)sqlite3_malloc64((pGCC->nAccum+1) * sizeof(int)); + if( pnsl!=0 ){ + int i = 0, nA = pGCC->nAccum-1; + while( inFirstSepLength; + } + }else{ + pnsl = (int*)sqlite3_realloc64(pnsl, pGCC->nAccum * sizeof(int)); + } + if( pnsl!=0 ){ + if( ALWAYS(pGCC->nAccum>0) ){ + pnsl[pGCC->nAccum-1] = nSep; + } + pGCC->pnSepLengths = pnsl; + }else{ + sqlite3StrAccumSetError(&pGCC->str, SQLITE_NOMEM); + } + } +#endif + } +#ifndef SQLITE_OMIT_WINDOWFUNC + else{ + pGCC->nFirstSepLength = sqlite3_value_bytes(argv[1]); } + pGCC->nAccum += 1; +#endif zVal = (char*)sqlite3_value_text(argv[0]); nVal = sqlite3_value_bytes(argv[0]); - if( zVal ) sqlite3_str_append(pAccum, zVal, nVal); + if( zVal ) sqlite3_str_append(&pGCC->str, zVal, nVal); } } + #ifndef SQLITE_OMIT_WINDOWFUNC static void groupConcatInverse( sqlite3_context *context, int argc, sqlite3_value **argv ){ - int n; - StrAccum *pAccum; + GroupConcatCtx *pGCC; assert( argc==1 || argc==2 ); + (void)argc; /* Suppress unused parameter warning */ if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return; - pAccum = (StrAccum*)sqlite3_aggregate_context(context, sizeof(*pAccum)); - /* pAccum is always non-NULL since groupConcatStep() will have always - ** run frist to initialize it */ - if( ALWAYS(pAccum) ){ - n = sqlite3_value_bytes(argv[0]); - if( argc==2 ){ - n += sqlite3_value_bytes(argv[1]); + pGCC = (GroupConcatCtx*)sqlite3_aggregate_context(context, sizeof(*pGCC)); + /* pGCC is always non-NULL since groupConcatStep() will have always + ** run first to initialize it */ + if( ALWAYS(pGCC) ){ + int nVS; /* Number of characters to remove */ + /* Must call sqlite3_value_text() to convert the argument into text prior + ** to invoking sqlite3_value_bytes(), in case the text encoding is UTF16 */ + (void)sqlite3_value_text(argv[0]); + nVS = sqlite3_value_bytes(argv[0]); + pGCC->nAccum -= 1; + if( pGCC->pnSepLengths!=0 ){ + assert(pGCC->nAccum >= 0); + if( pGCC->nAccum>0 ){ + nVS += *pGCC->pnSepLengths; + memmove(pGCC->pnSepLengths, pGCC->pnSepLengths+1, + (pGCC->nAccum-1)*sizeof(int)); + } }else{ - n++; + /* If removing single accumulated string, harmlessly over-do. */ + nVS += pGCC->nFirstSepLength; } - if( n>=(int)pAccum->nChar ){ - pAccum->nChar = 0; + if( nVS>=(int)pGCC->str.nChar ){ + pGCC->str.nChar = 0; }else{ - pAccum->nChar -= n; - memmove(pAccum->zText, &pAccum->zText[n], pAccum->nChar); + pGCC->str.nChar -= nVS; + memmove(pGCC->str.zText, &pGCC->str.zText[nVS], pGCC->str.nChar); + } + if( pGCC->str.nChar==0 ){ + pGCC->str.mxAlloc = 0; + sqlite3_free(pGCC->pnSepLengths); + pGCC->pnSepLengths = 0; } - if( pAccum->nChar==0 ) pAccum->mxAlloc = 0; } } #else # define groupConcatInverse 0 #endif /* SQLITE_OMIT_WINDOWFUNC */ static void groupConcatFinalize(sqlite3_context *context){ - StrAccum *pAccum; - pAccum = sqlite3_aggregate_context(context, 0); - if( pAccum ){ - if( pAccum->accError==SQLITE_TOOBIG ){ - sqlite3_result_error_toobig(context); - }else if( pAccum->accError==SQLITE_NOMEM ){ - sqlite3_result_error_nomem(context); - }else{ - sqlite3_result_text(context, sqlite3StrAccumFinish(pAccum), -1, - sqlite3_free); - } + GroupConcatCtx *pGCC + = (GroupConcatCtx*)sqlite3_aggregate_context(context, 0); + if( pGCC ){ + sqlite3ResultStrAccum(context, &pGCC->str); +#ifndef SQLITE_OMIT_WINDOWFUNC + sqlite3_free(pGCC->pnSepLengths); +#endif } } #ifndef SQLITE_OMIT_WINDOWFUNC static void groupConcatValue(sqlite3_context *context){ - sqlite3_str *pAccum; - pAccum = (sqlite3_str*)sqlite3_aggregate_context(context, 0); - if( pAccum ){ + GroupConcatCtx *pGCC + = (GroupConcatCtx*)sqlite3_aggregate_context(context, 0); + if( pGCC ){ + StrAccum *pAccum = &pGCC->str; if( pAccum->accError==SQLITE_TOOBIG ){ sqlite3_result_error_toobig(context); }else if( pAccum->accError==SQLITE_NOMEM ){ sqlite3_result_error_nomem(context); - }else{ + }else if( pGCC->nAccum>0 && pAccum->nChar==0 ){ + sqlite3_result_text(context, "", 1, SQLITE_STATIC); + }else{ const char *zText = sqlite3_str_value(pAccum); - sqlite3_result_text(context, zText, -1, SQLITE_TRANSIENT); + sqlite3_result_text(context, zText, pAccum->nChar, SQLITE_TRANSIENT); } } } @@ -114788,46 +136291,38 @@ SQLITE_PRIVATE void sqlite3RegisterPerConnectionBuiltinFunctions(sqlite3 *db){ } /* -** Set the LIKEOPT flag on the 2-argument function with the given name. -*/ -static void setLikeOptFlag(sqlite3 *db, const char *zName, u8 flagVal){ - FuncDef *pDef; - pDef = sqlite3FindFunction(db, zName, 2, SQLITE_UTF8, 0); - if( ALWAYS(pDef) ){ - pDef->funcFlags |= flagVal; - } - pDef = sqlite3FindFunction(db, zName, 3, SQLITE_UTF8, 0); - if( pDef ){ - pDef->funcFlags |= flagVal; - } -} - -/* -** Register the built-in LIKE and GLOB functions. The caseSensitive +** Re-register the built-in LIKE functions. The caseSensitive ** parameter determines whether or not the LIKE operator is case -** sensitive. GLOB is always case sensitive. +** sensitive. */ SQLITE_PRIVATE void sqlite3RegisterLikeFunctions(sqlite3 *db, int caseSensitive){ + FuncDef *pDef; struct compareInfo *pInfo; + int flags; + int nArg; if( caseSensitive ){ pInfo = (struct compareInfo*)&likeInfoAlt; + flags = SQLITE_FUNC_LIKE | SQLITE_FUNC_CASE; }else{ pInfo = (struct compareInfo*)&likeInfoNorm; + flags = SQLITE_FUNC_LIKE; + } + for(nArg=2; nArg<=3; nArg++){ + sqlite3CreateFunc(db, "like", nArg, SQLITE_UTF8, pInfo, likeFunc, + 0, 0, 0, 0, 0); + pDef = sqlite3FindFunction(db, "like", nArg, SQLITE_UTF8, 0); + assert( pDef!=0 ); /* The sqlite3CreateFunc() call above cannot fail + ** because the "like" SQL-function already exists */ + pDef->funcFlags |= flags; + pDef->funcFlags &= ~SQLITE_FUNC_UNSAFE; } - sqlite3CreateFunc(db, "like", 2, SQLITE_UTF8, pInfo, likeFunc, 0, 0, 0, 0, 0); - sqlite3CreateFunc(db, "like", 3, SQLITE_UTF8, pInfo, likeFunc, 0, 0, 0, 0, 0); - sqlite3CreateFunc(db, "glob", 2, SQLITE_UTF8, - (struct compareInfo*)&globInfo, likeFunc, 0, 0, 0, 0, 0); - setLikeOptFlag(db, "glob", SQLITE_FUNC_LIKE | SQLITE_FUNC_CASE); - setLikeOptFlag(db, "like", - caseSensitive ? (SQLITE_FUNC_LIKE | SQLITE_FUNC_CASE) : SQLITE_FUNC_LIKE); } /* ** pExpr points to an expression which implements a function. If ** it is appropriate to apply the LIKE optimization to that function ** then set aWc[0] through aWc[2] to the wildcard characters and the -** escape character and then return TRUE. If the function is not a +** escape character and then return TRUE. If the function is not a ** LIKE-style function then return FALSE. ** ** The expression "a LIKE b ESCAPE c" is only considered a valid LIKE @@ -114843,38 +136338,864 @@ SQLITE_PRIVATE void sqlite3RegisterLikeFunctions(sqlite3 *db, int caseSensitive) SQLITE_PRIVATE int sqlite3IsLikeFunction(sqlite3 *db, Expr *pExpr, int *pIsNocase, char *aWc){ FuncDef *pDef; int nExpr; - if( pExpr->op!=TK_FUNCTION || !pExpr->x.pList ){ + assert( pExpr!=0 ); + assert( pExpr->op==TK_FUNCTION ); + assert( ExprUseXList(pExpr) ); + if( !pExpr->x.pList ){ return 0; } - assert( !ExprHasProperty(pExpr, EP_xIsSelect) ); nExpr = pExpr->x.pList->nExpr; + assert( !ExprHasProperty(pExpr, EP_IntValue) ); pDef = sqlite3FindFunction(db, pExpr->u.zToken, nExpr, SQLITE_UTF8, 0); +#ifdef SQLITE_ENABLE_UNKNOWN_SQL_FUNCTION + if( pDef==0 ) return 0; +#endif if( NEVER(pDef==0) || (pDef->funcFlags & SQLITE_FUNC_LIKE)==0 ){ return 0; } + + /* The memcpy() statement assumes that the wildcard characters are + ** the first three statements in the compareInfo structure. The + ** asserts() that follow verify that assumption + */ + memcpy(aWc, pDef->pUserData, 3); + assert( (char*)&likeInfoAlt == (char*)&likeInfoAlt.matchAll ); + assert( &((char*)&likeInfoAlt)[1] == (char*)&likeInfoAlt.matchOne ); + assert( &((char*)&likeInfoAlt)[2] == (char*)&likeInfoAlt.matchSet ); + if( nExpr<3 ){ aWc[3] = 0; }else{ Expr *pEscape = pExpr->x.pList->a[2].pExpr; char *zEscape; if( pEscape->op!=TK_STRING ) return 0; + assert( !ExprHasProperty(pEscape, EP_IntValue) ); zEscape = pEscape->u.zToken; if( zEscape[0]==0 || zEscape[1]!=0 ) return 0; + if( zEscape[0]==aWc[0] ) return 0; + if( zEscape[0]==aWc[1] ) return 0; aWc[3] = zEscape[0]; } - /* The memcpy() statement assumes that the wildcard characters are - ** the first three statements in the compareInfo structure. The - ** asserts() that follow verify that assumption - */ - memcpy(aWc, pDef->pUserData, 3); - assert( (char*)&likeInfoAlt == (char*)&likeInfoAlt.matchAll ); - assert( &((char*)&likeInfoAlt)[1] == (char*)&likeInfoAlt.matchOne ); - assert( &((char*)&likeInfoAlt)[2] == (char*)&likeInfoAlt.matchSet ); *pIsNocase = (pDef->funcFlags & SQLITE_FUNC_CASE)==0; return 1; } +/* Mathematical Constants */ +#ifndef M_PI +# define M_PI 3.141592653589793238462643383279502884 +#endif +#ifndef M_LN10 +# define M_LN10 2.302585092994045684017991454684364208 +#endif +#ifndef M_LN2 +# define M_LN2 0.693147180559945309417232121458176568 +#endif + + +/* Extra math functions that require linking with -lm +*/ +#ifdef SQLITE_ENABLE_MATH_FUNCTIONS +/* +** Implementation SQL functions: +** +** ceil(X) +** ceiling(X) +** floor(X) +** +** The sqlite3_user_data() pointer is a pointer to the libm implementation +** of the underlying C function. +*/ +static void ceilingFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + assert( argc==1 ); + switch( sqlite3_value_numeric_type(argv[0]) ){ + case SQLITE_INTEGER: { + sqlite3_result_int64(context, sqlite3_value_int64(argv[0])); + break; + } + case SQLITE_FLOAT: { + double (*x)(double) = (double(*)(double))sqlite3_user_data(context); + sqlite3_result_double(context, x(sqlite3_value_double(argv[0]))); + break; + } + default: { + break; + } + } +} + +/* +** On some systems, ceil() and floor() are intrinsic function. You are +** unable to take a pointer to these functions. Hence, we here wrap them +** in our own actual functions. +*/ +static double xCeil(double x){ return ceil(x); } +static double xFloor(double x){ return floor(x); } + +/* +** Some systems do not have log2() and log10() in their standard math +** libraries. +*/ +#if defined(HAVE_LOG10) && HAVE_LOG10==0 +# define log10(X) (0.4342944819032517867*log(X)) +#endif +#if defined(HAVE_LOG2) && HAVE_LOG2==0 +# define log2(X) (1.442695040888963456*log(X)) +#endif + + +/* +** Implementation of SQL functions: +** +** ln(X) - natural logarithm +** log(X) - log X base 10 +** log10(X) - log X base 10 +** log(B,X) - log X base B +*/ +static void logFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + double x, b, ans; + assert( argc==1 || argc==2 ); + switch( sqlite3_value_numeric_type(argv[0]) ){ + case SQLITE_INTEGER: + case SQLITE_FLOAT: + x = sqlite3_value_double(argv[0]); + if( x<=0.0 ) return; + break; + default: + return; + } + if( argc==2 ){ + switch( sqlite3_value_numeric_type(argv[0]) ){ + case SQLITE_INTEGER: + case SQLITE_FLOAT: + b = log(x); + if( b<=0.0 ) return; + x = sqlite3_value_double(argv[1]); + if( x<=0.0 ) return; + break; + default: + return; + } + ans = log(x)/b; + }else{ + switch( SQLITE_PTR_TO_INT(sqlite3_user_data(context)) ){ + case 1: + ans = log10(x); + break; + case 2: + ans = log2(x); + break; + default: + ans = log(x); + break; + } + } + sqlite3_result_double(context, ans); +} + +/* +** Functions to converts degrees to radians and radians to degrees. +*/ +static double degToRad(double x){ return x*(M_PI/180.0); } +static double radToDeg(double x){ return x*(180.0/M_PI); } + +/* +** Implementation of 1-argument SQL math functions: +** +** exp(X) - Compute e to the X-th power +*/ +static void math1Func( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + int type0; + double v0, ans; + double (*x)(double); + assert( argc==1 ); + type0 = sqlite3_value_numeric_type(argv[0]); + if( type0!=SQLITE_INTEGER && type0!=SQLITE_FLOAT ) return; + v0 = sqlite3_value_double(argv[0]); + x = (double(*)(double))sqlite3_user_data(context); + ans = x(v0); + sqlite3_result_double(context, ans); +} + +/* +** Implementation of 2-argument SQL math functions: +** +** power(X,Y) - Compute X to the Y-th power +*/ +static void math2Func( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + int type0, type1; + double v0, v1, ans; + double (*x)(double,double); + assert( argc==2 ); + type0 = sqlite3_value_numeric_type(argv[0]); + if( type0!=SQLITE_INTEGER && type0!=SQLITE_FLOAT ) return; + type1 = sqlite3_value_numeric_type(argv[1]); + if( type1!=SQLITE_INTEGER && type1!=SQLITE_FLOAT ) return; + v0 = sqlite3_value_double(argv[0]); + v1 = sqlite3_value_double(argv[1]); + x = (double(*)(double,double))sqlite3_user_data(context); + ans = x(v0, v1); + sqlite3_result_double(context, ans); +} + +/* +** Implementation of 0-argument pi() function. +*/ +static void piFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + assert( argc==0 ); + (void)argv; + sqlite3_result_double(context, M_PI); +} + +#endif /* SQLITE_ENABLE_MATH_FUNCTIONS */ + +/* +** Implementation of sign(X) function. +*/ +static void signFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + int type0; + double x; + UNUSED_PARAMETER(argc); + assert( argc==1 ); + type0 = sqlite3_value_numeric_type(argv[0]); + if( type0!=SQLITE_INTEGER && type0!=SQLITE_FLOAT ) return; + x = sqlite3_value_double(argv[0]); + sqlite3_result_int(context, x<0.0 ? -1 : x>0.0 ? +1 : 0); +} + +#if defined(SQLITE_ENABLE_PERCENTILE) +/*********************************************************************** +** This section implements the percentile(Y,P) SQL function and similar. +** Requirements: +** +** (1) The percentile(Y,P) function is an aggregate function taking +** exactly two arguments. +** +** (2) If the P argument to percentile(Y,P) is not the same for every +** row in the aggregate then an error is thrown. The word "same" +** in the previous sentence means that the value differ by less +** than 0.001. +** +** (3) If the P argument to percentile(Y,P) evaluates to anything other +** than a number in the range of 0.0 to 100.0 inclusive then an +** error is thrown. +** +** (4) If any Y argument to percentile(Y,P) evaluates to a value that +** is not NULL and is not numeric then an error is thrown. +** +** (5) If any Y argument to percentile(Y,P) evaluates to plus or minus +** infinity then an error is thrown. (SQLite always interprets NaN +** values as NULL.) +** +** (6) Both Y and P in percentile(Y,P) can be arbitrary expressions, +** including CASE WHEN expressions. +** +** (7) The percentile(Y,P) aggregate is able to handle inputs of at least +** one million (1,000,000) rows. +** +** (8) If there are no non-NULL values for Y, then percentile(Y,P) +** returns NULL. +** +** (9) If there is exactly one non-NULL value for Y, the percentile(Y,P) +** returns the one Y value. +** +** (10) If there N non-NULL values of Y where N is two or more and +** the Y values are ordered from least to greatest and a graph is +** drawn from 0 to N-1 such that the height of the graph at J is +** the J-th Y value and such that straight lines are drawn between +** adjacent Y values, then the percentile(Y,P) function returns +** the height of the graph at P*(N-1)/100. +** +** (11) The percentile(Y,P) function always returns either a floating +** point number or NULL. +** +** (12) The percentile(Y,P) is implemented as a single C99 source-code +** file that compiles into a shared-library or DLL that can be loaded +** into SQLite using the sqlite3_load_extension() interface. +** +** (13) A separate median(Y) function is the equivalent percentile(Y,50). +** +** (14) A separate percentile_cont(Y,P) function is equivalent to +** percentile(Y,P/100.0). In other words, the fraction value in +** the second argument is in the range of 0 to 1 instead of 0 to 100. +** +** (15) A separate percentile_disc(Y,P) function is like +** percentile_cont(Y,P) except that instead of returning the weighted +** average of the nearest two input values, it returns the next lower +** value. So the percentile_disc(Y,P) will always return a value +** that was one of the inputs. +** +** (16) All of median(), percentile(Y,P), percentile_cont(Y,P) and +** percentile_disc(Y,P) can be used as window functions. +** +** Differences from standard SQL: +** +** * The percentile_cont(X,P) function is equivalent to the following in +** standard SQL: +** +** (percentile_cont(P) WITHIN GROUP (ORDER BY X)) +** +** The SQLite syntax is much more compact. The standard SQL syntax +** is also supported if SQLite is compiled with the +** -DSQLITE_ENABLE_ORDERED_SET_AGGREGATES option. +** +** * No median(X) function exists in the SQL standard. App developers +** are expected to write "percentile_cont(0.5)WITHIN GROUP(ORDER BY X)". +** +** * No percentile(Y,P) function exists in the SQL standard. Instead of +** percential(Y,P), developers must write this: +** "percentile_cont(P/100.0) WITHIN GROUP (ORDER BY Y)". Note that +** the fraction parameter to percentile() goes from 0 to 100 whereas +** the fraction parameter in SQL standard percentile_cont() goes from +** 0 to 1. +** +** Implementation notes as of 2024-08-31: +** +** * The regular aggregate-function versions of these routines work +** by accumulating all values in an array of doubles, then sorting +** that array using quicksort before computing the answer. Thus +** the runtime is O(NlogN) where N is the number of rows of input. +** +** * For the window-function versions of these routines, the array of +** inputs is sorted as soon as the first value is computed. Thereafter, +** the array is kept in sorted order using an insert-sort. This +** results in O(N*K) performance where K is the size of the window. +** One can imagine alternative implementations that give O(N*logN*logK) +** performance, but they require more complex logic and data structures. +** The developers have elected to keep the asymptotically slower +** algorithm for now, for simplicity, under the theory that window +** functions are seldom used and when they are, the window size K is +** often small. The developers might revisit that decision later, +** should the need arise. +*/ + +/* The following object is the group context for a single percentile() +** aggregate. Remember all input Y values until the very end. +** Those values are accumulated in the Percentile.a[] array. +*/ +typedef struct Percentile Percentile; +struct Percentile { + u64 nAlloc; /* Number of slots allocated for a[] */ + u64 nUsed; /* Number of slots actually used in a[] */ + char bSorted; /* True if a[] is already in sorted order */ + char bKeepSorted; /* True if advantageous to keep a[] sorted */ + char bPctValid; /* True if rPct is valid */ + double rPct; /* Fraction. 0.0 to 1.0 */ + double *a; /* Array of Y values */ +}; + +/* +** Return TRUE if the input floating-point number is an infinity. +*/ +static int percentIsInfinity(double r){ + sqlite3_uint64 u; + assert( sizeof(u)==sizeof(r) ); + memcpy(&u, &r, sizeof(u)); + return ((u>>52)&0x7ff)==0x7ff; +} + +/* +** Return TRUE if two doubles differ by 0.001 or less. +*/ +static int percentSameValue(double a, double b){ + a -= b; + return a>=-0.001 && a<=0.001; +} + +/* +** Search p (which must have p->bSorted) looking for an entry with +** value y. Return the index of that entry. +** +** If bExact is true, return -1 if the entry is not found. +** +** If bExact is false, return the index at which a new entry with +** value y should be insert in order to keep the values in sorted +** order. The smallest return value in this case will be 0, and +** the largest return value will be p->nUsed. +*/ +static i64 percentBinarySearch(Percentile *p, double y, int bExact){ + i64 iFirst = 0; /* First element of search range */ + i64 iLast = (i64)p->nUsed - 1; /* Last element of search range */ + while( iLast>=iFirst ){ + i64 iMid = (iFirst+iLast)/2; + double x = p->a[iMid]; + if( xy ){ + iLast = iMid - 1; + }else{ + return iMid; + } + } + if( bExact ) return -1; + return iFirst; +} + +/* +** Generate an error for a percentile function. +** +** The error format string must have exactly one occurrence of "%%s()" +** (with two '%' characters). That substring will be replaced by the name +** of the function. +*/ +static void percentError(sqlite3_context *pCtx, const char *zFormat, ...){ + char *zMsg1; + char *zMsg2; + va_list ap; + + va_start(ap, zFormat); + zMsg1 = sqlite3_vmprintf(zFormat, ap); + va_end(ap); + zMsg2 = zMsg1 ? sqlite3_mprintf(zMsg1, sqlite3VdbeFuncName(pCtx)) : 0; + sqlite3_result_error(pCtx, zMsg2, -1); + sqlite3_free(zMsg1); + sqlite3_free(zMsg2); +} + +/* +** The "step" function for percentile(Y,P) is called once for each +** input row. +*/ +static void percentStep(sqlite3_context *pCtx, int argc, sqlite3_value **argv){ + Percentile *p; + double rPct; + int eType; + double y; + assert( argc==2 || argc==1 ); + + if( argc==1 ){ + /* Requirement 13: median(Y) is the same as percentile(Y,50). */ + rPct = 0.5; + }else{ + /* P must be a number between 0 and 100 for percentile() or between + ** 0.0 and 1.0 for percentile_cont() and percentile_disc(). + ** + ** The user-data is an integer which is 10 times the upper bound. + */ + double mxFrac = (SQLITE_PTR_TO_INT(sqlite3_user_data(pCtx))&2)? 100.0 : 1.0; + eType = sqlite3_value_numeric_type(argv[1]); + rPct = sqlite3_value_double(argv[1])/mxFrac; + if( (eType!=SQLITE_INTEGER && eType!=SQLITE_FLOAT) + || rPct<0.0 || rPct>1.0 + ){ + percentError(pCtx, "the fraction argument to %%s()" + " is not between 0.0 and %.1f", + (double)mxFrac); + return; + } + } + + /* Allocate the session context. */ + p = (Percentile*)sqlite3_aggregate_context(pCtx, sizeof(*p)); + if( p==0 ) return; + + /* Remember the P value. Throw an error if the P value is different + ** from any prior row, per Requirement (2). */ + if( !p->bPctValid ){ + p->rPct = rPct; + p->bPctValid = 1; + }else if( !percentSameValue(p->rPct,rPct) ){ + percentError(pCtx, "the fraction argument to %%s()" + " is not the same for all input rows"); + return; + } + + /* Ignore rows for which Y is NULL */ + eType = sqlite3_value_type(argv[0]); + if( eType==SQLITE_NULL ) return; + + /* If not NULL, then Y must be numeric. Otherwise throw an error. + ** Requirement 4 */ + if( eType!=SQLITE_INTEGER && eType!=SQLITE_FLOAT ){ + percentError(pCtx, "input to %%s() is not numeric"); + return; + } + + /* Throw an error if the Y value is infinity or NaN */ + y = sqlite3_value_double(argv[0]); + if( percentIsInfinity(y) ){ + percentError(pCtx, "Inf input to %%s()"); + return; + } + + /* Allocate and store the Y */ + if( p->nUsed>=p->nAlloc ){ + u64 n = p->nAlloc*2 + 250; + double *a = sqlite3_realloc64(p->a, sizeof(double)*n); + if( a==0 ){ + sqlite3_free(p->a); + memset(p, 0, sizeof(*p)); + sqlite3_result_error_nomem(pCtx); + return; + } + p->nAlloc = n; + p->a = a; + } + if( p->nUsed==0 ){ + p->a[p->nUsed++] = y; + p->bSorted = 1; + }else if( !p->bSorted || y>=p->a[p->nUsed-1] ){ + p->a[p->nUsed++] = y; + }else if( p->bKeepSorted ){ + i64 i; + i = percentBinarySearch(p, y, 0); + if( i<(int)p->nUsed ){ + memmove(&p->a[i+1], &p->a[i], (p->nUsed-i)*sizeof(p->a[0])); + } + p->a[i] = y; + p->nUsed++; + }else{ + p->a[p->nUsed++] = y; + p->bSorted = 0; + } +} + +/* +** Interchange two doubles. +*/ +#define SWAP_DOUBLE(X,Y) {double ttt=(X);(X)=(Y);(Y)=ttt;} + +/* +** Sort an array of doubles. +** +** Algorithm: quicksort +** +** This is implemented separately rather than using the qsort() routine +** from the standard library because: +** +** (1) To avoid a dependency on qsort() +** (2) To avoid the function call to the comparison routine for each +** comparison. +*/ +static void percentSort(double *a, unsigned int n){ + int iLt; /* Entries before a[iLt] are less than rPivot */ + int iGt; /* Entries at or after a[iGt] are greater than rPivot */ + int i; /* Loop counter */ + double rPivot; /* The pivot value */ + + while( n>=2 ){ + if( a[0]>a[n-1] ){ + SWAP_DOUBLE(a[0],a[n-1]) + } + if( n==2 ) return; + iGt = n-1; + i = n/2; + if( a[0]>a[i] ){ + SWAP_DOUBLE(a[0],a[i]) + }else if( a[i]>a[iGt] ){ + SWAP_DOUBLE(a[i],a[iGt]) + } + if( n==3 ) return; + rPivot = a[i]; + iLt = i = 1; + do{ + if( a[i]iLt ) SWAP_DOUBLE(a[i],a[iLt]) + iLt++; + i++; + }else if( a[i]>rPivot ){ + do{ + iGt--; + }while( iGt>i && a[iGt]>rPivot ); + SWAP_DOUBLE(a[i],a[iGt]) + }else{ + i++; + } + }while( i(int)(n/2) ){ + if( n-iGt>=2 ) percentSort(a+iGt, n-iGt); + n = iLt; + }else{ + if( iLt>=2 ) percentSort(a, iLt); + a += iGt; + n -= iGt; + } + } +} + +/* +** The "inverse" function for percentile(Y,P) is called to remove a +** row that was previously inserted by "step". +*/ +static void percentInverse(sqlite3_context *pCtx,int argc,sqlite3_value **argv){ + Percentile *p; + int eType; + double y; + i64 i; + assert( argc==2 || argc==1 ); + + /* Allocate the session context. */ + p = (Percentile*)sqlite3_aggregate_context(pCtx, sizeof(*p)); + assert( p!=0 ); + + /* Ignore rows for which Y is NULL */ + eType = sqlite3_value_type(argv[0]); + if( eType==SQLITE_NULL ) return; + + /* If not NULL, then Y must be numeric. Otherwise throw an error. + ** Requirement 4 */ + if( eType!=SQLITE_INTEGER && eType!=SQLITE_FLOAT ){ + return; + } + + /* Ignore the Y value if it is infinity or NaN */ + y = sqlite3_value_double(argv[0]); + if( percentIsInfinity(y) ){ + return; + } + if( p->bSorted==0 ){ + assert( p->nUsed>1 ); + percentSort(p->a, p->nUsed); + p->bSorted = 1; + } + p->bKeepSorted = 1; + + /* Find and remove the row */ + i = percentBinarySearch(p, y, 1); + if( i>=0 ){ + p->nUsed--; + if( i<(int)p->nUsed ){ + memmove(&p->a[i], &p->a[i+1], (p->nUsed - i)*sizeof(p->a[0])); + } + } +} + +/* +** Compute the final output of percentile(). Clean up all allocated +** memory if and only if bIsFinal is true. +*/ +static void percentCompute(sqlite3_context *pCtx, int bIsFinal){ + Percentile *p; + int settings = SQLITE_PTR_TO_INT(sqlite3_user_data(pCtx))&1; /* Discrete? */ + unsigned i1, i2; + double v1, v2; + double ix, vx; + p = (Percentile*)sqlite3_aggregate_context(pCtx, 0); + if( p==0 ) return; + if( p->a==0 ) return; + if( p->nUsed ){ + if( p->bSorted==0 ){ + assert( p->nUsed>1 ); + percentSort(p->a, p->nUsed); + p->bSorted = 1; + } + ix = p->rPct*(p->nUsed-1); + i1 = (unsigned)ix; + if( settings & 1 ){ + vx = p->a[i1]; + }else{ + i2 = ix==(double)i1 || i1==p->nUsed-1 ? i1 : i1+1; + v1 = p->a[i1]; + v2 = p->a[i2]; + vx = v1 + (v2-v1)*(ix-i1); + } + sqlite3_result_double(pCtx, vx); + } + if( bIsFinal ){ + sqlite3_free(p->a); + memset(p, 0, sizeof(*p)); + }else{ + p->bKeepSorted = 1; + } +} +static void percentFinal(sqlite3_context *pCtx){ + percentCompute(pCtx, 1); +} +static void percentValue(sqlite3_context *pCtx){ + percentCompute(pCtx, 0); +} +/****** End of percentile family of functions ******/ +#endif /* SQLITE_ENABLE_PERCENTILE */ + +#if defined(SQLITE_DEBUG) || defined(SQLITE_ENABLE_FILESTAT) +/* +** Implementation of sqlite_filestat(SCHEMA). +** +** Return JSON text that describes low-level debug/diagnostic information +** about the sqlite3_file object associated with SCHEMA. +*/ +static void filestatFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + sqlite3 *db = sqlite3_context_db_handle(context); + const char *zDbName; + sqlite3_str *pStr; + Btree *pBtree; + + zDbName = (const char*)sqlite3_value_text(argv[0]); + pBtree = sqlite3DbNameToBtree(db, zDbName); + if( pBtree ){ + Pager *pPager; + sqlite3_file *fd; + int rc; + sqlite3BtreeEnter(pBtree); + pPager = sqlite3BtreePager(pBtree); + assert( pPager!=0 ); + fd = sqlite3PagerFile(pPager); + pStr = sqlite3_str_new(db); + if( pStr==0 ){ + sqlite3_result_error_nomem(context); + }else{ + sqlite3_str_append(pStr, "{\"db\":", 6); + rc = sqlite3OsFileControl(fd, SQLITE_FCNTL_FILESTAT, pStr); + if( rc ) sqlite3_str_append(pStr, "null", 4); + fd = sqlite3PagerJrnlFile(pPager); + if( fd && fd->pMethods!=0 ){ + sqlite3_str_appendall(pStr, ",\"journal\":"); + rc = sqlite3OsFileControl(fd, SQLITE_FCNTL_FILESTAT, pStr); + if( rc ) sqlite3_str_append(pStr, "null", 4); + } + sqlite3_str_append(pStr, "}", 1); + sqlite3_result_text(context, sqlite3_str_finish(pStr), -1, + sqlite3_free); + } + sqlite3BtreeLeave(pBtree); + }else{ + sqlite3_result_text(context, "{}", 2, SQLITE_STATIC); + } +} +#endif /* SQLITE_DEBUG || SQLITE_ENABLE_FILESTAT */ + +#ifdef SQLITE_DEBUG +/* +** Implementation of fpdecode(x,y,z) function. +** +** x is a real number that is to be decoded. y is the precision. +** z is the maximum real precision. Return a string that shows the +** results of the sqlite3FpDecode() function. +** +** Used for testing and debugging only, specifically testing and debugging +** of the sqlite3FpDecode() function. This SQL function does not appear +** in production builds. This function is not an API and is subject to +** modification or removal in future versions of SQLite. +*/ +static void fpdecodeFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + FpDecode s; + double x; + int y, z; + char zBuf[100]; + UNUSED_PARAMETER(argc); + assert( argc==3 ); + x = sqlite3_value_double(argv[0]); + y = sqlite3_value_int(argv[1]); + z = sqlite3_value_int(argv[2]); + if( z<=0 ) z = 1; + sqlite3FpDecode(&s, x, y, z); + if( s.isSpecial==2 ){ + sqlite3_snprintf(sizeof(zBuf), zBuf, "NaN"); + }else{ + sqlite3_snprintf(sizeof(zBuf), zBuf, "%c%.*s/%d", s.sign, s.n, s.z, s.iDP); + } + sqlite3_result_text(context, zBuf, -1, SQLITE_TRANSIENT); +} +#endif /* SQLITE_DEBUG */ + +#ifdef SQLITE_DEBUG +/* +** Implementation of parseuri(uri,flags) function. +** +** Required Arguments: +** "uri" The URI to parse. +** "flags" Bitmask of flags, as if to sqlite3_open_v2(). +** +** Additional arguments beyond the first two make calls to +** sqlite3_uri_key() for integers and sqlite3_uri_parameter for +** anything else. +** +** The result is a string showing the results of calling sqlite3ParseUri(). +** +** Used for testing and debugging only, specifically testing and debugging +** of the sqlite3ParseUri() function. This SQL function does not appear +** in production builds. This function is not an API and is subject to +** modification or removal in future versions of SQLite. +*/ +static void parseuriFunc( + sqlite3_context *ctx, + int argc, + sqlite3_value **argv +){ + sqlite3_str *pResult; + const char *zVfs; + const char *zUri; + unsigned int flgs; + int rc; + sqlite3_vfs *pVfs = 0; + char *zFile = 0; + char *zErr = 0; + + if( argc<2 ) return; + pVfs = sqlite3_vfs_find(0); + assert( pVfs ); + zVfs = pVfs->zName; + zUri = (const char*)sqlite3_value_text(argv[0]); + if( zUri==0 ) return; + flgs = (unsigned int)sqlite3_value_int(argv[1]); + rc = sqlite3ParseUri(zVfs, zUri, &flgs, &pVfs, &zFile, &zErr); + pResult = sqlite3_str_new(0); + if( pResult ){ + int i; + sqlite3_str_appendf(pResult, "rc=%d", rc); + sqlite3_str_appendf(pResult, ", flags=0x%x", flgs); + sqlite3_str_appendf(pResult, ", vfs=%Q", pVfs ? pVfs->zName: 0); + sqlite3_str_appendf(pResult, ", err=%Q", zErr); + sqlite3_str_appendf(pResult, ", file=%Q", zFile); + if( zFile ){ + const char *z = zFile; + z += sqlite3Strlen30(z)+1; + while( z[0] ){ + sqlite3_str_appendf(pResult, ", %Q", z); + z += sqlite3Strlen30(z)+1; + } + for(i=2; iu.pHash){ int n = sqlite3Strlen30(p->zName); int h = p->zName[0] + n; + assert( p->funcFlags & SQLITE_FUNC_BUILTIN ); printf(" %s(%d)", p->zName, h); } printf("\n"); @@ -115042,25 +137435,25 @@ SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(void){ ** Foreign keys in SQLite come in two flavours: deferred and immediate. ** If an immediate foreign key constraint is violated, ** SQLITE_CONSTRAINT_FOREIGNKEY is returned and the current -** statement transaction rolled back. If a -** deferred foreign key constraint is violated, no action is taken -** immediately. However if the application attempts to commit the +** statement transaction rolled back. If a +** deferred foreign key constraint is violated, no action is taken +** immediately. However if the application attempts to commit the ** transaction before fixing the constraint violation, the attempt fails. ** ** Deferred constraints are implemented using a simple counter associated -** with the database handle. The counter is set to zero each time a -** database transaction is opened. Each time a statement is executed +** with the database handle. The counter is set to zero each time a +** database transaction is opened. Each time a statement is executed ** that causes a foreign key violation, the counter is incremented. Each ** time a statement is executed that removes an existing violation from ** the database, the counter is decremented. When the transaction is ** committed, the commit fails if the current value of the counter is ** greater than zero. This scheme has two big drawbacks: ** -** * When a commit fails due to a deferred foreign key constraint, +** * When a commit fails due to a deferred foreign key constraint, ** there is no way to tell which foreign constraint is not satisfied, ** or which row it is not satisfied for. ** -** * If the database contains foreign key violations when the +** * If the database contains foreign key violations when the ** transaction is opened, this may cause the mechanism to malfunction. ** ** Despite these problems, this approach is adopted as it seems simpler @@ -115072,26 +137465,26 @@ SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(void){ ** the parent table for a match. If none is found increment the ** constraint counter. ** -** I.2) For each FK for which the table is the parent table, +** I.2) For each FK for which the table is the parent table, ** search the child table for rows that correspond to the new ** row in the parent table. Decrement the counter for each row ** found (as the constraint is now satisfied). ** ** DELETE operations: ** -** D.1) For each FK for which the table is the child table, -** search the parent table for a row that corresponds to the -** deleted row in the child table. If such a row is not found, +** D.1) For each FK for which the table is the child table, +** search the parent table for a row that corresponds to the +** deleted row in the child table. If such a row is not found, ** decrement the counter. ** -** D.2) For each FK for which the table is the parent table, search -** the child table for rows that correspond to the deleted row +** D.2) For each FK for which the table is the parent table, search +** the child table for rows that correspond to the deleted row ** in the parent table. For each found increment the counter. ** ** UPDATE operations: ** ** An UPDATE command requires that all 4 steps above are taken, but only -** for FK constraints for which the affected columns are actually +** for FK constraints for which the affected columns are actually ** modified (values must be compared at runtime). ** ** Note that I.1 and D.1 are very similar operations, as are I.2 and D.2. @@ -115100,10 +137493,10 @@ SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(void){ ** For the purposes of immediate FK constraints, the OR REPLACE conflict ** resolution is considered to delete rows before the new row is inserted. ** If a delete caused by OR REPLACE violates an FK constraint, an exception -** is thrown, even if the FK constraint would be satisfied after the new +** is thrown, even if the FK constraint would be satisfied after the new ** row is inserted. ** -** Immediate constraints are usually handled similarly. The only difference +** Immediate constraints are usually handled similarly. The only difference ** is that the counter used is stored as part of each individual statement ** object (struct Vdbe). If, after the statement has run, its immediate ** constraint counter is greater than zero, @@ -115114,7 +137507,7 @@ SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(void){ ** INSERT violates a foreign key constraint. This is necessary as such ** an INSERT does not open a statement transaction. ** -** TODO: How should dropping a table be handled? How should renaming a +** TODO: How should dropping a table be handled? How should renaming a ** table be handled? ** ** @@ -115125,7 +137518,7 @@ SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(void){ ** for those two operations needs to know whether or not the operation ** requires any FK processing and, if so, which columns of the original ** row are required by the FK processing VDBE code (i.e. if FKs were -** implemented using triggers, which of the old.* columns would be +** implemented using triggers, which of the old.* columns would be ** accessed). No information is required by the code-generator before ** coding an INSERT operation. The functions used by the UPDATE/DELETE ** generation code to query for this information are: @@ -115162,13 +137555,13 @@ SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(void){ /* ** A foreign key constraint requires that the key columns in the parent ** table are collectively subject to a UNIQUE or PRIMARY KEY constraint. -** Given that pParent is the parent table for foreign key constraint pFKey, -** search the schema for a unique index on the parent key columns. +** Given that pParent is the parent table for foreign key constraint pFKey, +** search the schema for a unique index on the parent key columns. +** +** If successful, zero is returned. If the parent key is an INTEGER PRIMARY +** KEY column, then output variable *ppIdx is set to NULL. Otherwise, *ppIdx +** is set to point to the unique index. ** -** If successful, zero is returned. If the parent key is an INTEGER PRIMARY -** KEY column, then output variable *ppIdx is set to NULL. Otherwise, *ppIdx -** is set to point to the unique index. -** ** If the parent key consists of a single column (the foreign key constraint ** is not a composite foreign key), output variable *paiCol is set to NULL. ** Otherwise, it is set to point to an allocated array of size N, where @@ -115191,8 +137584,8 @@ SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(void){ ** PRIMARY KEY, or ** ** 4) No parent key columns were provided explicitly as part of the -** foreign key definition, and the PRIMARY KEY of the parent table -** consists of a different number of columns to the child key in +** foreign key definition, and the PRIMARY KEY of the parent table +** consists of a different number of columns to the child key in ** the child table. ** ** then non-zero is returned, and a "foreign key mismatch" error loaded @@ -115216,9 +137609,9 @@ SQLITE_PRIVATE int sqlite3FkLocateIndex( assert( !paiCol || *paiCol==0 ); assert( pParse ); - /* If this is a non-composite (single column) foreign key, check if it - ** maps to the INTEGER PRIMARY KEY of table pParent. If so, leave *ppIdx - ** and *paiCol set to zero and return early. + /* If this is a non-composite (single column) foreign key, check if it + ** maps to the INTEGER PRIMARY KEY of table pParent. If so, leave *ppIdx + ** and *paiCol set to zero and return early. ** ** Otherwise, for a composite foreign key (more than one column), allocate ** space for the aiCol array (returned via output parameter *paiCol). @@ -115227,14 +137620,16 @@ SQLITE_PRIVATE int sqlite3FkLocateIndex( if( nCol==1 ){ /* The FK maps to the IPK if any of the following are true: ** - ** 1) There is an INTEGER PRIMARY KEY column and the FK is implicitly + ** 1) There is an INTEGER PRIMARY KEY column and the FK is implicitly ** mapped to the primary key of table pParent, or ** 2) The FK is explicitly mapped to a column declared as INTEGER ** PRIMARY KEY. */ if( pParent->iPKey>=0 ){ if( !zKey ) return 0; - if( !sqlite3StrICmp(pParent->aCol[pParent->iPKey].zName, zKey) ) return 0; + if( !sqlite3StrICmp(pParent->aCol[pParent->iPKey].zCnName, zKey) ){ + return 0; + } } }else if( paiCol ){ assert( nCol>1 ); @@ -115244,14 +137639,14 @@ SQLITE_PRIVATE int sqlite3FkLocateIndex( } for(pIdx=pParent->pIndex; pIdx; pIdx=pIdx->pNext){ - if( pIdx->nKeyCol==nCol && IsUniqueIndex(pIdx) && pIdx->pPartIdxWhere==0 ){ + if( pIdx->nKeyCol==nCol && IsUniqueIndex(pIdx) && pIdx->pPartIdxWhere==0 ){ /* pIdx is a UNIQUE index (or a PRIMARY KEY) and has the right number ** of columns. If each indexed column corresponds to a foreign key ** column of pFKey, then this index is a winner. */ if( zKey==0 ){ - /* If zKey is NULL, then this foreign key is implicitly mapped to - ** the PRIMARY KEY of table pParent. The PRIMARY KEY index may be + /* If zKey is NULL, then this foreign key is implicitly mapped to + ** the PRIMARY KEY of table pParent. The PRIMARY KEY index may be ** identified by the test. */ if( IsPrimaryKeyIndex(pIdx) ){ if( aiCol ){ @@ -115276,11 +137671,11 @@ SQLITE_PRIVATE int sqlite3FkLocateIndex( /* If the index uses a collation sequence that is different from ** the default collation sequence for the column, this index is ** unusable. Bail out early in this case. */ - zDfltColl = pParent->aCol[iCol].zColl; + zDfltColl = sqlite3ColumnColl(&pParent->aCol[iCol]); if( !zDfltColl ) zDfltColl = sqlite3StrBINARY; if( sqlite3StrICmp(pIdx->azColl[i], zDfltColl) ) break; - zIdxCol = pParent->aCol[iCol].zName; + zIdxCol = pParent->aCol[iCol].zCnName; for(j=0; jaCol[j].zCol, zIdxCol)==0 ){ if( aiCol ) aiCol[i] = pFKey->aCol[j].iFrom; @@ -115309,15 +137704,15 @@ SQLITE_PRIVATE int sqlite3FkLocateIndex( } /* -** This function is called when a row is inserted into or deleted from the -** child table of foreign key constraint pFKey. If an SQL UPDATE is executed +** This function is called when a row is inserted into or deleted from the +** child table of foreign key constraint pFKey. If an SQL UPDATE is executed ** on the child table of pFKey, this function is invoked twice for each row ** affected - once to "delete" the old row, and then again to "insert" the ** new row. ** ** Each time it is called, this function generates VDBE code to locate the -** row in the parent table that corresponds to the row being inserted into -** or deleted from the child table. If the parent row can be found, no +** row in the parent table that corresponds to the row being inserted into +** or deleted from the child table. If the parent row can be found, no ** special action is taken. Otherwise, if the parent row can *not* be ** found in the parent table: ** @@ -115331,7 +137726,7 @@ SQLITE_PRIVATE int sqlite3FkLocateIndex( ** ** DELETE deferred Decrement the "deferred constraint counter". ** -** These operations are identified in the comment at the top of this file +** These operations are identified in the comment at the top of this file ** (fkey.c) as "I.1" and "D.1". */ static void fkLookupParent( @@ -115353,22 +137748,22 @@ static void fkLookupParent( sqlite3VdbeVerifyAbortable(v, (!pFKey->isDeferred && !(pParse->db->flags & SQLITE_DeferFKs) - && !pParse->pToplevel + && !pParse->pToplevel && !pParse->isMultiWrite) ? OE_Abort : OE_Ignore); /* If nIncr is less than zero, then check at runtime if there are any ** outstanding constraints to resolve. If there are not, there is no need ** to check if deleting this row resolves any outstanding violations. ** - ** Check if any of the key columns in the child table row are NULL. If - ** any are, then the constraint is considered satisfied. No need to + ** Check if any of the key columns in the child table row are NULL. If + ** any are, then the constraint is considered satisfied. No need to ** search for a matching row in the parent table. */ if( nIncr<0 ){ sqlite3VdbeAddOp2(v, OP_FkIfZero, pFKey->isDeferred, iOk); VdbeCoverage(v); } for(i=0; inCol; i++){ - int iReg = aiCol[i] + regData + 1; + int iReg = sqlite3TableColumnToStorage(pFKey->pFrom,aiCol[i]) + regData + 1; sqlite3VdbeAddOp2(v, OP_IsNull, iReg, iOk); VdbeCoverage(v); } @@ -115378,16 +137773,17 @@ static void fkLookupParent( ** column of the parent table (table pTab). */ int iMustBeInt; /* Address of MustBeInt instruction */ int regTemp = sqlite3GetTempReg(pParse); - - /* Invoke MustBeInt to coerce the child key value to an integer (i.e. + + /* Invoke MustBeInt to coerce the child key value to an integer (i.e. ** apply the affinity of the parent key). If this fails, then there ** is no matching parent key. Before using MustBeInt, make a copy of ** the value. Otherwise, the value inserted into the child key column ** will have INTEGER affinity applied to it, which may not be correct. */ - sqlite3VdbeAddOp2(v, OP_SCopy, aiCol[0]+1+regData, regTemp); + sqlite3VdbeAddOp2(v, OP_SCopy, + sqlite3TableColumnToStorage(pFKey->pFrom,aiCol[0])+1+regData, regTemp); iMustBeInt = sqlite3VdbeAddOp2(v, OP_MustBeInt, regTemp, 0); VdbeCoverage(v); - + /* If the parent table is the same as the child table, and we are about ** to increment the constraint-counter (i.e. this is an INSERT operation), ** then check if the row being inserted matches itself. If so, do not @@ -115396,7 +137792,7 @@ static void fkLookupParent( sqlite3VdbeAddOp3(v, OP_Eq, regData, iOk, regTemp); VdbeCoverage(v); sqlite3VdbeChangeP5(v, SQLITE_NOTNULL); } - + sqlite3OpenTable(pParse, iCur, iDb, pTab, OP_OpenRead); sqlite3VdbeAddOp3(v, OP_NotExists, iCur, 0, regTemp); VdbeCoverage(v); sqlite3VdbeGoto(v, iOk); @@ -115406,20 +137802,21 @@ static void fkLookupParent( }else{ int nCol = pFKey->nCol; int regTemp = sqlite3GetTempRange(pParse, nCol); - int regRec = sqlite3GetTempReg(pParse); - + sqlite3VdbeAddOp3(v, OP_OpenRead, iCur, pIdx->tnum, iDb); sqlite3VdbeSetP4KeyInfo(pParse, pIdx); for(i=0; ipFrom, aiCol[i])+1+regData, + regTemp+i); } - + /* If the parent table is the same as the child table, and we are about ** to increment the constraint-counter (i.e. this is an INSERT operation), ** then check if the row being inserted matches itself. If so, do not - ** increment the constraint-counter. + ** increment the constraint-counter. ** - ** If any of the parent-key values are NULL, then the row cannot match + ** If any of the parent-key values are NULL, then the row cannot match ** itself. So set JUMPIFNULL to make sure we do the OP_Found if any ** of the parent-key values are NULL (at this point it is known that ** none of the child key values are). @@ -115427,8 +137824,11 @@ static void fkLookupParent( if( pTab==pFKey->pFrom && nIncr==1 ){ int iJump = sqlite3VdbeCurrentAddr(v) + nCol + 1; for(i=0; iaiColumn[i]+1+regData; + int iChild = sqlite3TableColumnToStorage(pFKey->pFrom,aiCol[i]) + +1+regData; + int iParent = 1+regData; + iParent += sqlite3TableColumnToStorage(pIdx->pTable, + pIdx->aiColumn[i]); assert( pIdx->aiColumn[i]>=0 ); assert( aiCol[i]!=pTab->iPKey ); if( pIdx->aiColumn[i]==pTab->iPKey ){ @@ -115440,19 +137840,18 @@ static void fkLookupParent( } sqlite3VdbeGoto(v, iOk); } - - sqlite3VdbeAddOp4(v, OP_MakeRecord, regTemp, nCol, regRec, + + sqlite3VdbeAddOp4(v, OP_Affinity, regTemp, nCol, 0, sqlite3IndexAffinityStr(pParse->db,pIdx), nCol); - sqlite3VdbeAddOp4Int(v, OP_Found, iCur, iOk, regRec, 0); VdbeCoverage(v); - - sqlite3ReleaseTempReg(pParse, regRec); + sqlite3VdbeAddOp4Int(v, OP_Found, iCur, iOk, regTemp, nCol); + VdbeCoverage(v); sqlite3ReleaseTempRange(pParse, regTemp, nCol); } } if( !pFKey->isDeferred && !(pParse->db->flags & SQLITE_DeferFKs) - && !pParse->pToplevel - && !pParse->isMultiWrite + && !pParse->pToplevel + && !pParse->isMultiWrite ){ /* Special case: If this is an INSERT statement that will insert exactly ** one row into the table, raise a constraint immediately instead of @@ -115496,14 +137895,14 @@ static Expr *exprTableRegister( if( pExpr ){ if( iCol>=0 && iCol!=pTab->iPKey ){ pCol = &pTab->aCol[iCol]; - pExpr->iTable = regBase + iCol + 1; - pExpr->affinity = pCol->affinity; - zColl = pCol->zColl; + pExpr->iTable = regBase + sqlite3TableColumnToStorage(pTab,iCol) + 1; + pExpr->affExpr = pCol->affinity; + zColl = sqlite3ColumnColl(pCol); if( zColl==0 ) zColl = db->pDfltColl->zName; pExpr = sqlite3ExprAddCollateString(pParse, pExpr, zColl); }else{ pExpr->iTable = regBase; - pExpr->affinity = SQLITE_AFF_INTEGER; + pExpr->affExpr = SQLITE_AFF_INTEGER; } } return pExpr; @@ -115521,6 +137920,7 @@ static Expr *exprTableColumn( ){ Expr *pExpr = sqlite3Expr(db, TK_COLUMN, 0); if( pExpr ){ + assert( ExprUseYTab(pExpr) ); pExpr->y.pTab = pTab; pExpr->iTable = iCursor; pExpr->iColumn = iCol; @@ -115530,7 +137930,7 @@ static Expr *exprTableColumn( /* ** This function is called to generate code executed when a row is deleted -** from the parent table of foreign key constraint pFKey and, if pFKey is +** from the parent table of foreign key constraint pFKey and, if pFKey is ** deferred, when a row is inserted into the same table. When generating ** code for an SQL UPDATE operation, this function may be called twice - ** once to "delete" the old row and once to "insert" the new row. @@ -115546,18 +137946,14 @@ static Expr *exprTableColumn( ** Operation | FK type | Action taken ** -------------------------------------------------------------------------- ** DELETE immediate Increment the "immediate constraint counter". -** Or, if the ON (UPDATE|DELETE) action is RESTRICT, -** throw a "FOREIGN KEY constraint failed" exception. ** ** INSERT immediate Decrement the "immediate constraint counter". ** ** DELETE deferred Increment the "deferred constraint counter". -** Or, if the ON (UPDATE|DELETE) action is RESTRICT, -** throw a "FOREIGN KEY constraint failed" exception. ** ** INSERT deferred Decrement the "deferred constraint counter". ** -** These operations are identified in the comment at the top of this file +** These operations are identified in the comment at the top of this file ** (fkey.c) as "I.2" and "D.2". */ static void fkScanChildren( @@ -115600,17 +137996,17 @@ static void fkScanChildren( Expr *pLeft; /* Value from parent table row */ Expr *pRight; /* Column ref to child table */ Expr *pEq; /* Expression (pLeft = pRight) */ - i16 iCol; /* Index of column in child table */ + i16 iCol; /* Index of column in child table */ const char *zCol; /* Name of column in child table */ iCol = pIdx ? pIdx->aiColumn[i] : -1; pLeft = exprTableRegister(pParse, pTab, regData, iCol); iCol = aiCol ? aiCol[i] : pFKey->aCol[0].iFrom; assert( iCol>=0 ); - zCol = pFKey->pFrom->aCol[iCol].zName; + zCol = pFKey->pFrom->aCol[iCol].zCnName; pRight = sqlite3Expr(db, TK_ID, zCol); pEq = sqlite3PExpr(pParse, TK_EQ, pLeft, pRight); - pWhere = sqlite3ExprAnd(db, pWhere, pEq); + pWhere = sqlite3ExprAnd(pParse, pWhere, pEq); } /* If the child table is the same as the parent table, then add terms @@ -115622,7 +138018,7 @@ static void fkScanChildren( ** ** The first form is used for rowid tables. The second form is used ** for WITHOUT ROWID tables. In the second form, the *parent* key is - ** (a,b,...). Either the parent or primary key could be used to + ** (a,b,...). Either the parent or primary key could be used to ** uniquely identify the current row, but the parent key is more convenient ** as the required values have already been loaded into registers ** by the caller. @@ -115642,13 +138038,13 @@ static void fkScanChildren( i16 iCol = pIdx->aiColumn[i]; assert( iCol>=0 ); pLeft = exprTableRegister(pParse, pTab, regData, iCol); - pRight = sqlite3Expr(db, TK_ID, pTab->aCol[iCol].zName); + pRight = sqlite3Expr(db, TK_ID, pTab->aCol[iCol].zCnName); pEq = sqlite3PExpr(pParse, TK_IS, pLeft, pRight); - pAll = sqlite3ExprAnd(db, pAll, pEq); + pAll = sqlite3ExprAnd(pParse, pAll, pEq); } pNe = sqlite3PExpr(pParse, TK_NOT, pAll, 0); } - pWhere = sqlite3ExprAnd(db, pWhere, pNe); + pWhere = sqlite3ExprAnd(pParse, pWhere, pNe); } /* Resolve the references in the WHERE clause. */ @@ -115661,7 +138057,7 @@ static void fkScanChildren( ** clause. For each row found, increment either the deferred or immediate ** foreign key constraint counter. */ if( pParse->nErr==0 ){ - pWInfo = sqlite3WhereBegin(pParse, pSrc, pWhere, 0, 0, 0, 0); + pWInfo = sqlite3WhereBegin(pParse, pSrc, pWhere, 0, 0, 0, 0, 0); sqlite3VdbeAddOp2(v, OP_FkCounter, pFKey->isDeferred, nIncr); if( pWInfo ){ sqlite3WhereEnd(pWInfo); @@ -115671,7 +138067,7 @@ static void fkScanChildren( /* Clean up the WHERE clause constructed above. */ sqlite3ExprDelete(db, pWhere); if( iFkIfZero ){ - sqlite3VdbeJumpHere(v, iFkIfZero); + sqlite3VdbeJumpHereOrPopInst(v, iFkIfZero); } } @@ -115694,7 +138090,7 @@ SQLITE_PRIVATE FKey *sqlite3FkReferences(Table *pTab){ } /* -** The second argument is a Trigger structure allocated by the +** The second argument is a Trigger structure allocated by the ** fkActionTrigger() routine. This function deletes the Trigger structure ** and all of its sub-components. ** @@ -115704,6 +138100,7 @@ SQLITE_PRIVATE FKey *sqlite3FkReferences(Table *pTab){ static void fkTriggerDelete(sqlite3 *dbMem, Trigger *p){ if( p ){ TriggerStep *pStep = p->step_list; + sqlite3SrcListDelete(dbMem, pStep->pSrc); sqlite3ExprDelete(dbMem, pStep->pWhere); sqlite3ExprListDelete(dbMem, pStep->pExprList); sqlite3SelectDelete(dbMem, pStep->pSelect); @@ -115712,6 +138109,25 @@ static void fkTriggerDelete(sqlite3 *dbMem, Trigger *p){ } } +/* +** Clear the apTrigger[] cache of CASCADE triggers for all foreign keys +** in a particular database. This needs to happen when the schema +** changes. +*/ +SQLITE_PRIVATE void sqlite3FkClearTriggerCache(sqlite3 *db, int iDb){ + HashElem *k; + Hash *pHash = &db->aDb[iDb].pSchema->tblHash; + for(k=sqliteHashFirst(pHash); k; k=sqliteHashNext(k)){ + Table *pTab = sqliteHashData(k); + FKey *pFKey; + if( !IsOrdinaryTable(pTab) ) continue; + for(pFKey=pTab->u.tab.pFKey; pFKey; pFKey=pFKey->pNextFrom){ + fkTriggerDelete(db, pFKey->apTrigger[0]); pFKey->apTrigger[0] = 0; + fkTriggerDelete(db, pFKey->apTrigger[1]); pFKey->apTrigger[1] = 0; + } + } +} + /* ** This function is called to generate code that runs when table pTab is ** being dropped from the database. The SrcList passed as the second argument @@ -115722,7 +138138,7 @@ static void fkTriggerDelete(sqlite3 *dbMem, Trigger *p){ ** ** (a) The table is the parent table of a FK constraint, or ** (b) The table is the child table of a deferred FK constraint and it is -** determined at runtime that there are outstanding deferred FK +** determined at runtime that there are outstanding deferred FK ** constraint violations in the database, ** ** then the equivalent of "DELETE FROM " is executed before dropping @@ -115731,20 +138147,20 @@ static void fkTriggerDelete(sqlite3 *dbMem, Trigger *p){ */ SQLITE_PRIVATE void sqlite3FkDropTable(Parse *pParse, SrcList *pName, Table *pTab){ sqlite3 *db = pParse->db; - if( (db->flags&SQLITE_ForeignKeys) && !IsVirtual(pTab) ){ + if( (db->flags&SQLITE_ForeignKeys) && IsOrdinaryTable(pTab) ){ int iSkip = 0; Vdbe *v = sqlite3GetVdbe(pParse); assert( v ); /* VDBE has already been allocated */ - assert( pTab->pSelect==0 ); /* Not a view */ + assert( IsOrdinaryTable(pTab) ); if( sqlite3FkReferences(pTab)==0 ){ /* Search for a deferred foreign key constraint for which this table - ** is the child table. If one cannot be found, return without + ** is the child table. If one cannot be found, return without ** generating any VDBE code. If one can be found, then jump over ** the entire DELETE if there are no outstanding deferred constraints ** when this statement is run. */ FKey *p; - for(p=pTab->pFKey; p; p=p->pNextFrom){ + for(p=pTab->u.tab.pFKey; p; p=p->pNextFrom){ if( p->isDeferred || (db->flags & SQLITE_DeferFKs) ) break; } if( !p ) return; @@ -115756,10 +138172,10 @@ SQLITE_PRIVATE void sqlite3FkDropTable(Parse *pParse, SrcList *pName, Table *pTa sqlite3DeleteFrom(pParse, sqlite3SrcListDup(db, pName, 0), 0, 0, 0); pParse->disableTriggers = 0; - /* If the DELETE has generated immediate foreign key constraint + /* If the DELETE has generated immediate foreign key constraint ** violations, halt the VDBE and return an error at this point, before ** any modifications to the schema are made. This is because statement - ** transactions are not able to rollback schema changes. + ** transactions are not able to rollback schema changes. ** ** If the SQLITE_DeferFKs flag is set, then this is not required, as ** the statement transaction will not be rolled back even if FK @@ -115783,7 +138199,7 @@ SQLITE_PRIVATE void sqlite3FkDropTable(Parse *pParse, SrcList *pName, Table *pTa /* ** The second argument points to an FKey object representing a foreign key ** for which pTab is the child table. An UPDATE statement against pTab -** is currently being processed. For each column of the table that is +** is currently being processed. For each column of the table that is ** actually updated, the corresponding element in the aChange[] array ** is zero or greater (if a column is unmodified the corresponding element ** is set to -1). If the rowid column is modified by the UPDATE statement @@ -115810,7 +138226,7 @@ static int fkChildIsModified( /* ** The second argument points to an FKey object representing a foreign key ** for which pTab is the parent table. An UPDATE statement against pTab -** is currently being processed. For each column of the table that is +** is currently being processed. For each column of the table that is ** actually updated, the corresponding element in the aChange[] array ** is zero or greater (if a column is unmodified the corresponding element ** is set to -1). If the rowid column is modified by the UPDATE statement @@ -115820,9 +138236,9 @@ static int fkChildIsModified( ** parent key for FK constraint *p are modified. */ static int fkParentIsModified( - Table *pTab, - FKey *p, - int *aChange, + Table *pTab, + FKey *p, + int *aChange, int bChngRowid ){ int i; @@ -115833,7 +138249,7 @@ static int fkParentIsModified( if( aChange[iKey]>=0 || (iKey==pTab->iPKey && bChngRowid) ){ Column *pCol = &pTab->aCol[iKey]; if( zKey ){ - if( 0==sqlite3StrICmp(pCol->zName, zKey) ) return 1; + if( 0==sqlite3StrICmp(pCol->zCnName, zKey) ) return 1; }else if( pCol->colFlags & COLFLAG_PRIMKEY ){ return 1; } @@ -115855,6 +138271,7 @@ static int isSetNullAction(Parse *pParse, FKey *pFKey){ if( (p==pFKey->apTrigger[0] && pFKey->aAction[0]==OE_SetNull) || (p==pFKey->apTrigger[1] && pFKey->aAction[1]==OE_SetNull) ){ + assert( (pTop->db->flags & SQLITE_FkNoAction)==0 ); return 1; } } @@ -115863,7 +138280,7 @@ static int isSetNullAction(Parse *pParse, FKey *pFKey){ /* ** This function is called when inserting, deleting or updating a row of -** table pTab to generate VDBE code to perform foreign key constraint +** table pTab to generate VDBE code to perform foreign key constraint ** processing for the operation. ** ** For a DELETE operation, parameter regOld is passed the index of the @@ -115879,11 +138296,11 @@ static int isSetNullAction(Parse *pParse, FKey *pFKey){ ** For an UPDATE operation, this function is called twice. Once before ** the original record is deleted from the table using the calling convention ** described for DELETE. Then again after the original record is deleted -** but before the new record is inserted using the INSERT convention. +** but before the new record is inserted using the INSERT convention. */ SQLITE_PRIVATE void sqlite3FkCheck( Parse *pParse, /* Parse context */ - Table *pTab, /* Row is being deleted from this table */ + Table *pTab, /* Row is being deleted from this table */ int regOld, /* Previous row data is stored here */ int regNew, /* New row data is stored here */ int *aChange, /* Array indicating UPDATEd columns (or 0) */ @@ -115900,13 +138317,15 @@ SQLITE_PRIVATE void sqlite3FkCheck( /* If foreign-keys are disabled, this function is a no-op. */ if( (db->flags&SQLITE_ForeignKeys)==0 ) return; + if( !IsOrdinaryTable(pTab) ) return; iDb = sqlite3SchemaToIndex(db, pTab->pSchema); + assert( iDb>=00 && iDbnDb ); zDb = db->aDb[iDb].zDbSName; /* Loop through all the foreign key constraints for which pTab is the ** child table (the table that the foreign key definition is part of). */ - for(pFKey=pTab->pFKey; pFKey; pFKey=pFKey->pNextFrom){ + for(pFKey=pTab->u.tab.pFKey; pFKey; pFKey=pFKey->pNextFrom){ Table *pTo; /* Parent table of foreign key pFKey */ Index *pIdx = 0; /* Index on key columns in pTo */ int *aiFree = 0; @@ -115915,16 +138334,16 @@ SQLITE_PRIVATE void sqlite3FkCheck( int i; int bIgnore = 0; - if( aChange + if( aChange && sqlite3_stricmp(pTab->zName, pFKey->zTo)!=0 - && fkChildIsModified(pTab, pFKey, aChange, bChngRowid)==0 + && fkChildIsModified(pTab, pFKey, aChange, bChngRowid)==0 ){ continue; } - /* Find the parent table of this foreign key. Also find a unique index - ** on the parent key columns in the parent table. If either of these - ** schema items cannot be located, set an error in pParse and return + /* Find the parent table of this foreign key. Also find a unique index + ** on the parent key columns in the parent table. If either of these + ** schema items cannot be located, set an error in pParse and return ** early. */ if( pParse->disableTriggers ){ pTo = sqlite3FindTable(db, pFKey->zTo, zDb); @@ -115945,7 +138364,9 @@ SQLITE_PRIVATE void sqlite3FkCheck( Vdbe *v = sqlite3GetVdbe(pParse); int iJump = sqlite3VdbeCurrentAddr(v) + pFKey->nCol + 1; for(i=0; inCol; i++){ - int iReg = pFKey->aCol[i].iFrom + regOld + 1; + int iFromCol, iReg; + iFromCol = pFKey->aCol[i].iFrom; + iReg = sqlite3TableColumnToStorage(pFKey->pFrom,iFromCol) + regOld+1; sqlite3VdbeAddOp2(v, OP_IsNull, iReg, iJump); VdbeCoverage(v); } sqlite3VdbeAddOp2(v, OP_FkCounter, pFKey->isDeferred, -1); @@ -115966,36 +138387,36 @@ SQLITE_PRIVATE void sqlite3FkCheck( } assert( pIdx==0 || pIdx->aiColumn[i]>=0 ); #ifndef SQLITE_OMIT_AUTHORIZATION - /* Request permission to read the parent key columns. If the + /* Request permission to read the parent key columns. If the ** authorization callback returns SQLITE_IGNORE, behave as if any ** values read from the parent table are NULL. */ if( db->xAuth ){ int rcauth; - char *zCol = pTo->aCol[pIdx ? pIdx->aiColumn[i] : pTo->iPKey].zName; + char *zCol = pTo->aCol[pIdx ? pIdx->aiColumn[i] : pTo->iPKey].zCnName; rcauth = sqlite3AuthReadCol(pParse, pTo->zName, zCol, iDb); bIgnore = (rcauth==SQLITE_IGNORE); } #endif } - /* Take a shared-cache advisory read-lock on the parent table. Allocate - ** a cursor to use to search the unique index on the parent key columns + /* Take a shared-cache advisory read-lock on the parent table. Allocate + ** a cursor to use to search the unique index on the parent key columns ** in the parent table. */ sqlite3TableLock(pParse, iDb, pTo->tnum, 0, pTo->zName); pParse->nTab++; if( regOld!=0 ){ /* A row is being removed from the child table. Search for the parent. - ** If the parent does not exist, removing the child row resolves an + ** If the parent does not exist, removing the child row resolves an ** outstanding foreign key constraint violation. */ fkLookupParent(pParse, iDb, pTo, pIdx, pFKey, aiCol, regOld, -1, bIgnore); } if( regNew!=0 && !isSetNullAction(pParse, pFKey) ){ /* A row is being added to the child table. If a parent row cannot - ** be found, adding the child row has violated the FK constraint. + ** be found, adding the child row has violated the FK constraint. ** ** If this operation is being performed as part of a trigger program - ** that is actually a "SET NULL" action belonging to this very + ** that is actually a "SET NULL" action belonging to this very ** foreign key, then omit this scan altogether. As all child key ** values are guaranteed to be NULL, it is not possible for adding ** this row to cause an FK violation. */ @@ -116016,8 +138437,8 @@ SQLITE_PRIVATE void sqlite3FkCheck( continue; } - if( !pFKey->isDeferred && !(db->flags & SQLITE_DeferFKs) - && !pParse->pToplevel && !pParse->isMultiWrite + if( !pFKey->isDeferred && !(db->flags & SQLITE_DeferFKs) + && !pParse->pToplevel && !pParse->isMultiWrite ){ assert( regOld==0 && regNew!=0 ); /* Inserting a single row into a parent table cannot cause (or fix) @@ -116035,17 +138456,19 @@ SQLITE_PRIVATE void sqlite3FkCheck( ** child table as a SrcList for sqlite3WhereBegin() */ pSrc = sqlite3SrcListAppend(pParse, 0, 0, 0); if( pSrc ){ - struct SrcList_item *pItem = pSrc->a; - pItem->pTab = pFKey->pFrom; + SrcItem *pItem = pSrc->a; + pItem->pSTab = pFKey->pFrom; pItem->zName = pFKey->pFrom->zName; - pItem->pTab->nTabRef++; + pItem->pSTab->nTabRef++; pItem->iCursor = pParse->nTab++; - + if( regNew!=0 ){ fkScanChildren(pParse, pSrc, pTab, pIdx, pFKey, aiCol, regNew, -1); } if( regOld!=0 ){ int eAction = pFKey->aAction[aChange!=0]; + if( (db->flags & SQLITE_FkNoAction) ) eAction = OE_None; + fkScanChildren(pParse, pSrc, pTab, pIdx, pFKey, aiCol, regOld, 1); /* If this is a deferred FK constraint, or a CASCADE or SET NULL ** action applies, then any foreign key violations caused by @@ -116059,10 +138482,10 @@ SQLITE_PRIVATE void sqlite3FkCheck( ** ** Note 2: At first glance it may seem like SQLite could simply omit ** all OP_FkCounter related scans when either CASCADE or SET NULL - ** applies. The trouble starts if the CASCADE or SET NULL action - ** trigger causes other triggers or action rules attached to the + ** applies. The trouble starts if the CASCADE or SET NULL action + ** trigger causes other triggers or action rules attached to the ** child table to fire. In these cases the fk constraint counters - ** might be set incorrectly if any OP_FkCounter related scans are + ** might be set incorrectly if any OP_FkCounter related scans are ** omitted. */ if( !pFKey->isDeferred && eAction!=OE_Cascade && eAction!=OE_SetNull ){ sqlite3MayAbort(pParse); @@ -116078,7 +138501,7 @@ SQLITE_PRIVATE void sqlite3FkCheck( #define COLUMN_MASK(x) (((x)>31) ? 0xffffffff : ((u32)1<<(x))) /* -** This function is called before generating code to update or delete a +** This function is called before generating code to update or delete a ** row contained in table pTab. */ SQLITE_PRIVATE u32 sqlite3FkOldmask( @@ -116086,10 +138509,10 @@ SQLITE_PRIVATE u32 sqlite3FkOldmask( Table *pTab /* Table being modified */ ){ u32 mask = 0; - if( pParse->db->flags&SQLITE_ForeignKeys ){ + if( pParse->db->flags&SQLITE_ForeignKeys && IsOrdinaryTable(pTab) ){ FKey *p; int i; - for(p=pTab->pFKey; p; p=p->pNextFrom){ + for(p=pTab->u.tab.pFKey; p; p=p->pNextFrom){ for(i=0; inCol; i++) mask |= COLUMN_MASK(p->aCol[i].iFrom); } for(p=sqlite3FkReferences(pTab); p; p=p->pNextTo){ @@ -116108,22 +138531,24 @@ SQLITE_PRIVATE u32 sqlite3FkOldmask( /* -** This function is called before generating code to update or delete a +** This function is called before generating code to update or delete a ** row contained in table pTab. If the operation is a DELETE, then ** parameter aChange is passed a NULL value. For an UPDATE, aChange points ** to an array of size N, where N is the number of columns in table pTab. -** If the i'th column is not modified by the UPDATE, then the corresponding +** If the i'th column is not modified by the UPDATE, then the corresponding ** entry in the aChange[] array is set to -1. If the column is modified, ** the value is 0 or greater. Parameter chngRowid is set to true if the ** UPDATE statement modifies the rowid fields of the table. ** ** If any foreign key processing will be required, this function returns -** non-zero. If there is no foreign key related processing, this function +** non-zero. If there is no foreign key related processing, this function ** returns zero. ** ** For an UPDATE, this function returns 2 if: ** -** * There are any FKs for which pTab is the child and the parent table, or +** * There are any FKs for which pTab is the child and the parent table +** and any FK processing at all is required (even of a different FK), or +** ** * the UPDATE modifies one or more parent keys for which the action is ** not "NO ACTION" (i.e. is CASCADE, SET DEFAULT or SET NULL). ** @@ -116135,40 +138560,45 @@ SQLITE_PRIVATE int sqlite3FkRequired( int *aChange, /* Non-NULL for UPDATE operations */ int chngRowid /* True for UPDATE that affects rowid */ ){ - int eRet = 0; - if( pParse->db->flags&SQLITE_ForeignKeys ){ + int eRet = 1; /* Value to return if bHaveFK is true */ + int bHaveFK = 0; /* If FK processing is required */ + if( pParse->db->flags&SQLITE_ForeignKeys && IsOrdinaryTable(pTab) ){ if( !aChange ){ - /* A DELETE operation. Foreign key processing is required if the - ** table in question is either the child or parent table for any + /* A DELETE operation. Foreign key processing is required if the + ** table in question is either the child or parent table for any ** foreign key constraint. */ - eRet = (sqlite3FkReferences(pTab) || pTab->pFKey); + bHaveFK = (sqlite3FkReferences(pTab) || pTab->u.tab.pFKey); }else{ /* This is an UPDATE. Foreign key processing is only required if the ** operation modifies one or more child or parent key columns. */ FKey *p; /* Check if any child key columns are being modified. */ - for(p=pTab->pFKey; p; p=p->pNextFrom){ - if( 0==sqlite3_stricmp(pTab->zName, p->zTo) ) return 2; + for(p=pTab->u.tab.pFKey; p; p=p->pNextFrom){ if( fkChildIsModified(pTab, p, aChange, chngRowid) ){ - eRet = 1; + if( 0==sqlite3_stricmp(pTab->zName, p->zTo) ) eRet = 2; + bHaveFK = 1; } } /* Check if any parent key columns are being modified. */ for(p=sqlite3FkReferences(pTab); p; p=p->pNextTo){ if( fkParentIsModified(pTab, p, aChange, chngRowid) ){ - if( p->aAction[1]!=OE_None ) return 2; - eRet = 1; + if( (pParse->db->flags & SQLITE_FkNoAction)==0 + && p->aAction[1]!=OE_None + ){ + return 2; + } + bHaveFK = 1; } } } } - return eRet; + return bHaveFK ? eRet : 0; } /* -** This function is called when an UPDATE or DELETE operation is being +** This function is called when an UPDATE or DELETE operation is being ** compiled on table pTab, which is the parent table of foreign-key pFKey. ** If the current operation is an UPDATE, then the pChanges parameter is ** passed a pointer to the list of columns being modified. If it is a @@ -116176,11 +138606,11 @@ SQLITE_PRIVATE int sqlite3FkRequired( ** ** It returns a pointer to a Trigger structure containing a trigger ** equivalent to the ON UPDATE or ON DELETE action specified by pFKey. -** If the action is "NO ACTION" or "RESTRICT", then a NULL pointer is -** returned (these actions require no special handling by the triggers -** sub-system, code for them is created by fkScanChildren()). +** If the action is "NO ACTION" then a NULL pointer is returned (these actions +** require no special handling by the triggers sub-system, code for them is +** created by fkScanChildren()). ** -** For example, if pFKey is the foreign key and pTab is table "p" in +** For example, if pFKey is the foreign key and pTab is table "p" in ** the following schema: ** ** CREATE TABLE p(pk PRIMARY KEY); @@ -116193,7 +138623,7 @@ SQLITE_PRIVATE int sqlite3FkRequired( ** END; ** ** The returned pointer is cached as part of the foreign key object. It -** is eventually freed along with the rest of the foreign key object by +** is eventually freed along with the rest of the foreign key object by ** sqlite3FkDelete(). */ static Trigger *fkActionTrigger( @@ -116208,6 +138638,7 @@ static Trigger *fkActionTrigger( int iAction = (pChanges!=0); /* 1 for UPDATE, 0 for DELETE */ action = pFKey->aAction[iAction]; + if( (db->flags & SQLITE_FkNoAction) ) action = OE_None; if( action==OE_Restrict && (db->flags & SQLITE_DeferFKs) ){ return 0; } @@ -116241,20 +138672,20 @@ static Trigger *fkActionTrigger( assert( pIdx!=0 || (pTab->iPKey>=0 && pTab->iPKeynCol) ); assert( pIdx==0 || pIdx->aiColumn[i]>=0 ); sqlite3TokenInit(&tToCol, - pTab->aCol[pIdx ? pIdx->aiColumn[i] : pTab->iPKey].zName); - sqlite3TokenInit(&tFromCol, pFKey->pFrom->aCol[iFromCol].zName); + pTab->aCol[pIdx ? pIdx->aiColumn[i] : pTab->iPKey].zCnName); + sqlite3TokenInit(&tFromCol, pFKey->pFrom->aCol[iFromCol].zCnName); /* Create the expression "OLD.zToCol = zFromCol". It is important ** that the "OLD.zToCol" term is on the LHS of the = operator, so ** that the affinity and collation sequence associated with the ** parent table are used for the comparison. */ pEq = sqlite3PExpr(pParse, TK_EQ, - sqlite3PExpr(pParse, TK_DOT, + sqlite3PExpr(pParse, TK_DOT, sqlite3ExprAlloc(db, TK_ID, &tOld, 0), sqlite3ExprAlloc(db, TK_ID, &tToCol, 0)), sqlite3ExprAlloc(db, TK_ID, &tFromCol, 0) ); - pWhere = sqlite3ExprAnd(db, pWhere, pEq); + pWhere = sqlite3ExprAnd(pParse, pWhere, pEq); /* For ON UPDATE, construct the next term of the WHEN clause. ** The final WHEN clause will be like this: @@ -116263,24 +138694,32 @@ static Trigger *fkActionTrigger( */ if( pChanges ){ pEq = sqlite3PExpr(pParse, TK_IS, - sqlite3PExpr(pParse, TK_DOT, + sqlite3PExpr(pParse, TK_DOT, sqlite3ExprAlloc(db, TK_ID, &tOld, 0), sqlite3ExprAlloc(db, TK_ID, &tToCol, 0)), - sqlite3PExpr(pParse, TK_DOT, + sqlite3PExpr(pParse, TK_DOT, sqlite3ExprAlloc(db, TK_ID, &tNew, 0), sqlite3ExprAlloc(db, TK_ID, &tToCol, 0)) ); - pWhen = sqlite3ExprAnd(db, pWhen, pEq); + pWhen = sqlite3ExprAnd(pParse, pWhen, pEq); } - + if( action!=OE_Restrict && (action!=OE_Cascade || pChanges) ){ Expr *pNew; if( action==OE_Cascade ){ - pNew = sqlite3PExpr(pParse, TK_DOT, + pNew = sqlite3PExpr(pParse, TK_DOT, sqlite3ExprAlloc(db, TK_ID, &tNew, 0), sqlite3ExprAlloc(db, TK_ID, &tToCol, 0)); }else if( action==OE_SetDflt ){ - Expr *pDflt = pFKey->pFrom->aCol[iFromCol].pDflt; + Column *pCol = pFKey->pFrom->aCol + iFromCol; + Expr *pDflt; + if( pCol->colFlags & COLFLAG_GENERATED ){ + testcase( pCol->colFlags & COLFLAG_VIRTUAL ); + testcase( pCol->colFlags & COLFLAG_STORED ); + pDflt = 0; + }else{ + pDflt = sqlite3ColumnExpr(pFKey->pFrom, pCol); + } if( pDflt ){ pNew = sqlite3ExprDup(db, pDflt, 0); }else{ @@ -116299,18 +138738,24 @@ static Trigger *fkActionTrigger( nFrom = sqlite3Strlen30(zFrom); if( action==OE_Restrict ){ - Token tFrom; - Expr *pRaise; + SrcList *pSrc; + Expr *pRaise; - tFrom.z = zFrom; - tFrom.n = nFrom; - pRaise = sqlite3Expr(db, TK_RAISE, "FOREIGN KEY constraint failed"); + pRaise = sqlite3Expr(db, TK_STRING, "FOREIGN KEY constraint failed"), + pRaise = sqlite3PExpr(pParse, TK_RAISE, pRaise, 0); if( pRaise ){ - pRaise->affinity = OE_Abort; + pRaise->affExpr = OE_Abort; + } + pSrc = sqlite3SrcListAppend(pParse, 0, 0, 0); + if( pSrc ){ + SrcItem *pItem = &pSrc->a[0]; + pItem->zName = sqlite3DbStrDup(db, zFrom); + pItem->fg.fixedSchema = 1; + pItem->u4.pSchema = pTab->pSchema; } - pSelect = sqlite3SelectNew(pParse, + pSelect = sqlite3SelectNew(pParse, sqlite3ExprListAppend(pParse, 0, pRaise), - sqlite3SrcListAppend(pParse, 0, &tFrom, 0), + pSrc, pWhere, 0, 0, 0, 0, 0 ); @@ -116318,18 +138763,21 @@ static Trigger *fkActionTrigger( } /* Disable lookaside memory allocation */ - db->lookaside.bDisable++; + DisableLookaside; - pTrigger = (Trigger *)sqlite3DbMallocZero(db, + pTrigger = (Trigger *)sqlite3DbMallocZero(db, sizeof(Trigger) + /* struct Trigger */ - sizeof(TriggerStep) + /* Single step in trigger program */ - nFrom + 1 /* Space for pStep->zTarget */ + sizeof(TriggerStep) /* Single step in trigger program */ ); if( pTrigger ){ pStep = pTrigger->step_list = (TriggerStep *)&pTrigger[1]; - pStep->zTarget = (char *)&pStep[1]; - memcpy((char *)pStep->zTarget, zFrom, nFrom); - + pStep->pSrc = sqlite3SrcListAppend(pParse, 0, 0, 0); + if( pStep->pSrc ){ + SrcItem *pItem = &pStep->pSrc->a[0]; + pItem->zName = sqlite3DbStrNDup(db, zFrom, nFrom); + pItem->u4.pSchema = pTab->pSchema; + pItem->fg.fixedSchema = 1; + } pStep->pWhere = sqlite3ExprDup(db, pWhere, EXPRDUP_REDUCE); pStep->pExprList = sqlite3ExprListDup(db, pList, EXPRDUP_REDUCE); pStep->pSelect = sqlite3SelectDup(db, pSelect, EXPRDUP_REDUCE); @@ -116340,7 +138788,7 @@ static Trigger *fkActionTrigger( } /* Re-enable the lookaside buffer, if it was disabled earlier. */ - db->lookaside.bDisable--; + EnableLookaside; sqlite3ExprDelete(db, pWhere); sqlite3ExprDelete(db, pWhen); @@ -116351,16 +138799,18 @@ static Trigger *fkActionTrigger( return 0; } assert( pStep!=0 ); + assert( pTrigger!=0 ); switch( action ){ case OE_Restrict: - pStep->op = TK_SELECT; + pStep->op = TK_SELECT; break; - case OE_Cascade: - if( !pChanges ){ - pStep->op = TK_DELETE; - break; + case OE_Cascade: + if( !pChanges ){ + pStep->op = TK_DELETE; + break; } + /* no break */ deliberate_fall_through default: pStep->op = TK_UPDATE; } @@ -116386,9 +138836,9 @@ SQLITE_PRIVATE void sqlite3FkActions( int *aChange, /* Array indicating UPDATEd columns (or 0) */ int bChngRowid /* True if rowid is UPDATEd */ ){ - /* If foreign-key support is enabled, iterate through all FKs that - ** refer to table pTab. If there is an action associated with the FK - ** for this operation (either update or delete), invoke the associated + /* If foreign-key support is enabled, iterate through all FKs that + ** refer to table pTab. If there is an action associated with the FK + ** for this operation (either update or delete), invoke the associated ** trigger sub-program. */ if( pParse->db->flags&SQLITE_ForeignKeys ){ FKey *pFKey; /* Iterator variable */ @@ -116414,18 +138864,18 @@ SQLITE_PRIVATE void sqlite3FkDelete(sqlite3 *db, Table *pTab){ FKey *pFKey; /* Iterator variable */ FKey *pNext; /* Copy of pFKey->pNextFrom */ - assert( db==0 || IsVirtual(pTab) - || sqlite3SchemaMutexHeld(db, 0, pTab->pSchema) ); - for(pFKey=pTab->pFKey; pFKey; pFKey=pNext){ + assert( IsOrdinaryTable(pTab) ); + assert( db!=0 ); + for(pFKey=pTab->u.tab.pFKey; pFKey; pFKey=pNext){ + assert( db==0 || sqlite3SchemaMutexHeld(db, 0, pTab->pSchema) ); /* Remove the FK from the fkeyHash hash table. */ - if( !db || db->pnBytesFreed==0 ){ + if( db->pnBytesFreed==0 ){ if( pFKey->pPrevTo ){ pFKey->pPrevTo->pNextTo = pFKey->pNextTo; }else{ - void *p = (void *)pFKey->pNextTo; - const char *z = (p ? pFKey->pNextTo->zTo : pFKey->zTo); - sqlite3HashInsert(&pTab->pSchema->fkeyHash, z, p); + const char *z = (pFKey->pNextTo ? pFKey->pNextTo->zTo : pFKey->zTo); + sqlite3HashInsert(&pTab->pSchema->fkeyHash, z, pFKey->pNextTo); } if( pFKey->pNextTo ){ pFKey->pNextTo->pPrevTo = pFKey->pPrevTo; @@ -116468,7 +138918,7 @@ SQLITE_PRIVATE void sqlite3FkDelete(sqlite3 *db, Table *pTab){ /* #include "sqliteInt.h" */ /* -** Generate code that will +** Generate code that will ** ** (1) acquire a lock for table pTab then ** (2) open pTab as cursor iCur. @@ -116485,17 +138935,20 @@ SQLITE_PRIVATE void sqlite3OpenTable( ){ Vdbe *v; assert( !IsVirtual(pTab) ); - v = sqlite3GetVdbe(pParse); + assert( pParse->pVdbe!=0 ); + v = pParse->pVdbe; assert( opcode==OP_OpenWrite || opcode==OP_OpenRead ); - sqlite3TableLock(pParse, iDb, pTab->tnum, - (opcode==OP_OpenWrite)?1:0, pTab->zName); + if( !pParse->db->noSharedCache ){ + sqlite3TableLock(pParse, iDb, pTab->tnum, + (opcode==OP_OpenWrite)?1:0, pTab->zName); + } if( HasRowid(pTab) ){ - sqlite3VdbeAddOp4Int(v, opcode, iCur, pTab->tnum, iDb, pTab->nCol); + sqlite3VdbeAddOp4Int(v, opcode, iCur, pTab->tnum, iDb, pTab->nNVCol); VdbeComment((v, "%s", pTab->zName)); }else{ Index *pPk = sqlite3PrimaryKeyIndex(pTab); assert( pPk!=0 ); - assert( pPk->tnum==pTab->tnum ); + assert( pPk->tnum==pTab->tnum || CORRUPT_DB ); sqlite3VdbeAddOp3(v, opcode, iCur, pPk->tnum, iDb); sqlite3VdbeSetP4KeyInfo(pParse, pPk); VdbeComment((v, "%s", pTab->zName)); @@ -116504,7 +138957,7 @@ SQLITE_PRIVATE void sqlite3OpenTable( /* ** Return a pointer to the column affinity string associated with index -** pIdx. A column affinity string has one character for each column in +** pIdx. A column affinity string has one character for each column in ** the table, according to the affinity of the column: ** ** Character Column affinity @@ -116522,81 +138975,142 @@ SQLITE_PRIVATE void sqlite3OpenTable( ** is managed along with the rest of the Index structure. It will be ** released when sqlite3DeleteIndex() is called. */ -SQLITE_PRIVATE const char *sqlite3IndexAffinityStr(sqlite3 *db, Index *pIdx){ +static SQLITE_NOINLINE const char *computeIndexAffStr(sqlite3 *db, Index *pIdx){ + /* The first time a column affinity string for a particular index is + ** required, it is allocated and populated here. It is then stored as + ** a member of the Index structure for subsequent use. + ** + ** The column affinity string will eventually be deleted by + ** sqliteDeleteIndex() when the Index structure itself is cleaned + ** up. + */ + int n; + Table *pTab = pIdx->pTable; + pIdx->zColAff = (char *)sqlite3DbMallocRaw(0, pIdx->nColumn+1); if( !pIdx->zColAff ){ - /* The first time a column affinity string for a particular index is - ** required, it is allocated and populated here. It is then stored as - ** a member of the Index structure for subsequent use. - ** - ** The column affinity string will eventually be deleted by - ** sqliteDeleteIndex() when the Index structure itself is cleaned - ** up. - */ - int n; - Table *pTab = pIdx->pTable; - pIdx->zColAff = (char *)sqlite3DbMallocRaw(0, pIdx->nColumn+1); - if( !pIdx->zColAff ){ - sqlite3OomFault(db); - return 0; + sqlite3OomFault(db); + return 0; + } + for(n=0; nnColumn; n++){ + i16 x = pIdx->aiColumn[n]; + char aff; + if( x>=0 ){ + aff = pTab->aCol[x].affinity; + }else if( x==XN_ROWID ){ + aff = SQLITE_AFF_INTEGER; + }else{ + assert( x==XN_EXPR ); + assert( pIdx->bHasExpr ); + assert( pIdx->aColExpr!=0 ); + aff = sqlite3ExprAffinity(pIdx->aColExpr->a[n].pExpr); } - for(n=0; nnColumn; n++){ - i16 x = pIdx->aiColumn[n]; - if( x>=0 ){ - pIdx->zColAff[n] = pTab->aCol[x].affinity; - }else if( x==XN_ROWID ){ - pIdx->zColAff[n] = SQLITE_AFF_INTEGER; - }else{ - char aff; - assert( x==XN_EXPR ); - assert( pIdx->aColExpr!=0 ); - aff = sqlite3ExprAffinity(pIdx->aColExpr->a[n].pExpr); - if( aff==0 ) aff = SQLITE_AFF_BLOB; - pIdx->zColAff[n] = aff; + if( affSQLITE_AFF_NUMERIC) aff = SQLITE_AFF_NUMERIC; + pIdx->zColAff[n] = aff; + } + pIdx->zColAff[n] = 0; + return pIdx->zColAff; +} +SQLITE_PRIVATE const char *sqlite3IndexAffinityStr(sqlite3 *db, Index *pIdx){ + if( !pIdx->zColAff ) return computeIndexAffStr(db, pIdx); + return pIdx->zColAff; +} + + +/* +** Compute an affinity string for a table. Space is obtained +** from sqlite3DbMalloc(). The caller is responsible for freeing +** the space when done. +*/ +SQLITE_PRIVATE char *sqlite3TableAffinityStr(sqlite3 *db, const Table *pTab){ + char *zColAff; + zColAff = (char *)sqlite3DbMallocRaw(db, pTab->nCol+1); + if( zColAff ){ + int i, j; + for(i=j=0; inCol; i++){ + if( (pTab->aCol[i].colFlags & COLFLAG_VIRTUAL)==0 ){ + zColAff[j++] = pTab->aCol[i].affinity; } } - pIdx->zColAff[n] = 0; + do{ + zColAff[j--] = 0; + }while( j>=0 && zColAff[j]<=SQLITE_AFF_BLOB ); } - - return pIdx->zColAff; + return zColAff; } /* +** Make changes to the evolving bytecode to do affinity transformations +** of values that are about to be gathered into a row for table pTab. +** +** For ordinary (legacy, non-strict) tables: +** ----------------------------------------- +** ** Compute the affinity string for table pTab, if it has not already been ** computed. As an optimization, omit trailing SQLITE_AFF_BLOB affinities. ** -** If the affinity exists (if it is no entirely SQLITE_AFF_BLOB values) and -** if iReg>0 then code an OP_Affinity opcode that will set the affinities -** for register iReg and following. Or if affinities exists and iReg==0, +** If the affinity string is empty (because it was all SQLITE_AFF_BLOB entries +** which were then optimized out) then this routine becomes a no-op. +** +** Otherwise if iReg>0 then code an OP_Affinity opcode that will set the +** affinities for register iReg and following. Or if iReg==0, ** then just set the P4 operand of the previous opcode (which should be ** an OP_MakeRecord) to the affinity string. ** ** A column affinity string has one character per column: ** -** Character Column affinity -** ------------------------------ -** 'A' BLOB -** 'B' TEXT -** 'C' NUMERIC -** 'D' INTEGER -** 'E' REAL +** Character Column affinity +** --------- --------------- +** 'A' BLOB +** 'B' TEXT +** 'C' NUMERIC +** 'D' INTEGER +** 'E' REAL +** +** For STRICT tables: +** ------------------ +** +** Generate an appropriate OP_TypeCheck opcode that will verify the +** datatypes against the column definitions in pTab. If iReg==0, that +** means an OP_MakeRecord opcode has already been generated and should be +** the last opcode generated. The new OP_TypeCheck needs to be inserted +** before the OP_MakeRecord. The new OP_TypeCheck should use the same +** register set as the OP_MakeRecord. If iReg>0 then register iReg is +** the first of a series of registers that will form the new record. +** Apply the type checking to that array of registers. */ SQLITE_PRIVATE void sqlite3TableAffinity(Vdbe *v, Table *pTab, int iReg){ int i; - char *zColAff = pTab->zColAff; + char *zColAff; + if( pTab->tabFlags & TF_Strict ){ + if( iReg==0 ){ + /* Move the previous opcode (which should be OP_MakeRecord) forward + ** by one slot and insert a new OP_TypeCheck where the current + ** OP_MakeRecord is found */ + VdbeOp *pPrev; + int p3; + sqlite3VdbeAppendP4(v, pTab, P4_TABLE); + pPrev = sqlite3VdbeGetLastOp(v); + assert( pPrev!=0 ); + assert( pPrev->opcode==OP_MakeRecord || sqlite3VdbeDb(v)->mallocFailed ); + pPrev->opcode = OP_TypeCheck; + p3 = pPrev->p3; + pPrev->p3 = 0; + sqlite3VdbeAddOp3(v, OP_MakeRecord, pPrev->p1, pPrev->p2, p3); + }else{ + /* Insert an isolated OP_Typecheck */ + sqlite3VdbeAddOp2(v, OP_TypeCheck, iReg, pTab->nNVCol); + sqlite3VdbeAppendP4(v, pTab, P4_TABLE); + } + return; + } + zColAff = pTab->zColAff; if( zColAff==0 ){ - sqlite3 *db = sqlite3VdbeDb(v); - zColAff = (char *)sqlite3DbMallocRaw(0, pTab->nCol+1); + zColAff = sqlite3TableAffinityStr(0, pTab); if( !zColAff ){ - sqlite3OomFault(db); + sqlite3OomFault(sqlite3VdbeDb(v)); return; } - - for(i=0; inCol; i++){ - zColAff[i] = pTab->aCol[i].affinity; - } - do{ - zColAff[i--] = 0; - }while( i>=0 && zColAff[i]==SQLITE_AFF_BLOB ); pTab->zColAff = zColAff; } assert( zColAff!=0 ); @@ -116605,6 +139119,8 @@ SQLITE_PRIVATE void sqlite3TableAffinity(Vdbe *v, Table *pTab, int iReg){ if( iReg ){ sqlite3VdbeAddOp4(v, OP_Affinity, iReg, i, 0, zColAff, i); }else{ + assert( sqlite3VdbeGetLastOp(v)->opcode==OP_MakeRecord + || sqlite3VdbeDb(v)->mallocFailed ); sqlite3VdbeChangeP4(v, -1, zColAff, i); } } @@ -116612,9 +139128,9 @@ SQLITE_PRIVATE void sqlite3TableAffinity(Vdbe *v, Table *pTab, int iReg){ /* ** Return non-zero if the table pTab in database iDb or any of its indices -** have been opened at any point in the VDBE program. This is used to see if -** a statement of the form "INSERT INTO SELECT ..." can -** run without using a temporary table for the results of the SELECT. +** have been opened at any point in the VDBE program. This is used to see if +** a statement of the form "INSERT INTO SELECT ..." can +** run without using a temporary table for the results of the SELECT. */ static int readsTable(Parse *p, int iDb, Table *pTab){ Vdbe *v = sqlite3GetVdbe(p); @@ -116629,7 +139145,7 @@ static int readsTable(Parse *p, int iDb, Table *pTab){ assert( pOp!=0 ); if( pOp->opcode==OP_OpenRead && pOp->p3==iDb ){ Index *pIndex; - int tnum = pOp->p2; + Pgno tnum = pOp->p2; if( tnum==pTab->tnum ){ return 1; } @@ -116650,6 +139166,125 @@ static int readsTable(Parse *p, int iDb, Table *pTab){ return 0; } +/* This walker callback will compute the union of colFlags flags for all +** referenced columns in a CHECK constraint or generated column expression. +*/ +static int exprColumnFlagUnion(Walker *pWalker, Expr *pExpr){ + if( pExpr->op==TK_COLUMN && pExpr->iColumn>=0 ){ + assert( pExpr->iColumn < pWalker->u.pTab->nCol ); + pWalker->eCode |= pWalker->u.pTab->aCol[pExpr->iColumn].colFlags; + } + return WRC_Continue; +} + +#ifndef SQLITE_OMIT_GENERATED_COLUMNS +/* +** All regular columns for table pTab have been puts into registers +** starting with iRegStore. The registers that correspond to STORED +** or VIRTUAL columns have not yet been initialized. This routine goes +** back and computes the values for those columns based on the previously +** computed normal columns. +*/ +SQLITE_PRIVATE void sqlite3ComputeGeneratedColumns( + Parse *pParse, /* Parsing context */ + int iRegStore, /* Register holding the first column */ + Table *pTab /* The table */ +){ + int i; + Walker w; + Column *pRedo; + int eProgress; + VdbeOp *pOp; + + assert( pTab->tabFlags & TF_HasGenerated ); + testcase( pTab->tabFlags & TF_HasVirtual ); + testcase( pTab->tabFlags & TF_HasStored ); + + /* Before computing generated columns, first go through and make sure + ** that appropriate affinity has been applied to the regular columns + */ + sqlite3TableAffinity(pParse->pVdbe, pTab, iRegStore); + if( (pTab->tabFlags & TF_HasStored)!=0 ){ + pOp = sqlite3VdbeGetLastOp(pParse->pVdbe); + if( pOp->opcode==OP_Affinity ){ + /* Change the OP_Affinity argument to '@' (NONE) for all stored + ** columns. '@' is the no-op affinity and those columns have not + ** yet been computed. */ + int ii, jj; + char *zP4 = pOp->p4.z; + assert( zP4!=0 ); + assert( pOp->p4type==P4_DYNAMIC ); + for(ii=jj=0; zP4[jj]; ii++){ + if( pTab->aCol[ii].colFlags & COLFLAG_VIRTUAL ){ + continue; + } + if( pTab->aCol[ii].colFlags & COLFLAG_STORED ){ + zP4[jj] = SQLITE_AFF_NONE; + } + jj++; + } + }else if( pOp->opcode==OP_TypeCheck ){ + /* If an OP_TypeCheck was generated because the table is STRICT, + ** then set the P3 operand to indicate that generated columns should + ** not be checked */ + pOp->p3 = 1; + } + } + + /* Because there can be multiple generated columns that refer to one another, + ** this is a two-pass algorithm. On the first pass, mark all generated + ** columns as "not available". + */ + for(i=0; inCol; i++){ + if( pTab->aCol[i].colFlags & COLFLAG_GENERATED ){ + testcase( pTab->aCol[i].colFlags & COLFLAG_VIRTUAL ); + testcase( pTab->aCol[i].colFlags & COLFLAG_STORED ); + pTab->aCol[i].colFlags |= COLFLAG_NOTAVAIL; + } + } + + w.u.pTab = pTab; + w.xExprCallback = exprColumnFlagUnion; + w.xSelectCallback = 0; + w.xSelectCallback2 = 0; + + /* On the second pass, compute the value of each NOT-AVAILABLE column. + ** Companion code in the TK_COLUMN case of sqlite3ExprCodeTarget() will + ** compute dependencies and mark remove the COLSPAN_NOTAVAIL mark, as + ** they are needed. + */ + pParse->iSelfTab = -iRegStore; + do{ + eProgress = 0; + pRedo = 0; + for(i=0; inCol; i++){ + Column *pCol = pTab->aCol + i; + if( (pCol->colFlags & COLFLAG_NOTAVAIL)!=0 ){ + int x; + pCol->colFlags |= COLFLAG_BUSY; + w.eCode = 0; + sqlite3WalkExpr(&w, sqlite3ColumnExpr(pTab, pCol)); + pCol->colFlags &= ~COLFLAG_BUSY; + if( w.eCode & COLFLAG_NOTAVAIL ){ + pRedo = pCol; + continue; + } + eProgress = 1; + assert( pCol->colFlags & COLFLAG_GENERATED ); + x = sqlite3TableColumnToStorage(pTab, i) + iRegStore; + sqlite3ExprCodeGeneratedColumn(pParse, pTab, pCol, x); + pCol->colFlags &= ~COLFLAG_NOTAVAIL; + } + } + }while( pRedo && eProgress ); + if( pRedo ){ + sqlite3ErrorMsg(pParse, "generated column loop on \"%s\"", pRedo->zCnName); + } + pParse->iSelfTab = 0; +} +#endif /* SQLITE_OMIT_GENERATED_COLUMNS */ + + #ifndef SQLITE_OMIT_AUTOINCREMENT /* ** Locate or create an AutoincInfo structure associated with table pTab @@ -116693,7 +139328,7 @@ static int autoIncBegin( ** Ticket d8dc2b3a58cd5dc2918a1d4acb 2018-05-23 */ if( pSeqTab==0 || !HasRowid(pSeqTab) - || IsVirtual(pSeqTab) + || NEVER(IsVirtual(pSeqTab)) || pSeqTab->nCol!=2 ){ pParse->nErr++; @@ -116705,7 +139340,9 @@ static int autoIncBegin( while( pInfo && pInfo->pTab!=pTab ){ pInfo = pInfo->pNext; } if( pInfo==0 ){ pInfo = sqlite3DbMallocRawNN(pParse->db, sizeof(*pInfo)); - if( pInfo==0 ) return 0; + sqlite3ParserAddCleanup(pToplevel, sqlite3DbFree, pInfo); + testcase( pParse->earlyCleanup ); + if( pParse->db->mallocFailed ) return 0; pInfo->pNext = pToplevel->pAinc; pToplevel->pAinc = pInfo; pInfo->pTab = pTab; @@ -116721,7 +139358,7 @@ static int autoIncBegin( /* ** This routine generates code that will initialize all of the -** register used by the autoincrement tracker. +** register used by the autoincrement tracker. */ SQLITE_PRIVATE void sqlite3AutoincrementBegin(Parse *pParse){ AutoincInfo *p; /* Information about an AUTOINCREMENT */ @@ -116750,7 +139387,7 @@ SQLITE_PRIVATE void sqlite3AutoincrementBegin(Parse *pParse){ /* 8 */ {OP_Goto, 0, 11, 0}, /* 9 */ {OP_Next, 0, 2, 0}, /* 10 */ {OP_Integer, 0, 0, 0}, - /* 11 */ {OP_Close, 0, 0, 0} + /* 11 */ {OP_Close, 0, 0, 0} }; VdbeOp *aOp; pDb = &db->aDb[p->iDb]; @@ -116846,6 +139483,210 @@ SQLITE_PRIVATE void sqlite3AutoincrementEnd(Parse *pParse){ # define autoIncStep(A,B,C) #endif /* SQLITE_OMIT_AUTOINCREMENT */ +/* +** If argument pVal is a Select object returned by an sqlite3MultiValues() +** that was able to use the co-routine optimization, finish coding the +** co-routine. +*/ +SQLITE_PRIVATE void sqlite3MultiValuesEnd(Parse *pParse, Select *pVal){ + if( ALWAYS(pVal) && pVal->pSrc->nSrc>0 ){ + SrcItem *pItem = &pVal->pSrc->a[0]; + assert( (pItem->fg.isSubquery && pItem->u4.pSubq!=0) || pParse->nErr ); + if( pItem->fg.isSubquery ){ + sqlite3VdbeEndCoroutine(pParse->pVdbe, pItem->u4.pSubq->regReturn); + sqlite3VdbeJumpHere(pParse->pVdbe, pItem->u4.pSubq->addrFillSub - 1); + } + } +} + +/* +** Return true if all expressions in the expression-list passed as the +** only argument are constant. +*/ +static int exprListIsConstant(Parse *pParse, ExprList *pRow){ + int ii; + for(ii=0; iinExpr; ii++){ + if( 0==sqlite3ExprIsConstant(pParse, pRow->a[ii].pExpr) ) return 0; + } + return 1; +} + +/* +** Return true if all expressions in the expression-list passed as the +** only argument are both constant and have no affinity. +*/ +static int exprListIsNoAffinity(Parse *pParse, ExprList *pRow){ + int ii; + if( exprListIsConstant(pParse,pRow)==0 ) return 0; + for(ii=0; iinExpr; ii++){ + Expr *pExpr = pRow->a[ii].pExpr; + assert( pExpr->op!=TK_RAISE ); + assert( pExpr->affExpr==0 ); + if( 0!=sqlite3ExprAffinity(pExpr) ) return 0; + } + return 1; + +} + +/* +** This function is called by the parser for the second and subsequent +** rows of a multi-row VALUES clause. Argument pLeft is the part of +** the VALUES clause already parsed, argument pRow is the vector of values +** for the new row. The Select object returned represents the complete +** VALUES clause, including the new row. +** +** There are two ways in which this may be achieved - by incremental +** coding of a co-routine (the "co-routine" method) or by returning a +** Select object equivalent to the following (the "UNION ALL" method): +** +** "pLeft UNION ALL SELECT pRow" +** +** If the VALUES clause contains a lot of rows, this compound Select +** object may consume a lot of memory. +** +** When the co-routine method is used, each row that will be returned +** by the VALUES clause is coded into part of a co-routine as it is +** passed to this function. The returned Select object is equivalent to: +** +** SELECT * FROM ( +** Select object to read co-routine +** ) +** +** The co-routine method is used in most cases. Exceptions are: +** +** a) If the current statement has a WITH clause. This is to avoid +** statements like: +** +** WITH cte AS ( VALUES('x'), ('y') ... ) +** SELECT * FROM cte AS a, cte AS b; +** +** This will not work, as the co-routine uses a hard-coded register +** for its OP_Yield instructions, and so it is not possible for two +** cursors to iterate through it concurrently. +** +** b) The schema is currently being parsed (i.e. the VALUES clause is part +** of a schema item like a VIEW or TRIGGER). In this case there is no VM +** being generated when parsing is taking place, and so generating +** a co-routine is not possible. +** +** c) There are non-constant expressions in the VALUES clause (e.g. +** the VALUES clause is part of a correlated sub-query). +** +** d) One or more of the values in the first row of the VALUES clause +** has an affinity (i.e. is a CAST expression). This causes problems +** because the complex rules SQLite uses (see function +** sqlite3SubqueryColumnTypes() in select.c) to determine the effective +** affinity of such a column for all rows require access to all values in +** the column simultaneously. +*/ +SQLITE_PRIVATE Select *sqlite3MultiValues(Parse *pParse, Select *pLeft, ExprList *pRow){ + + if( pParse->bHasWith /* condition (a) above */ + || pParse->db->init.busy /* condition (b) above */ + || exprListIsConstant(pParse,pRow)==0 /* condition (c) above */ + || (pLeft->pSrc->nSrc==0 && + exprListIsNoAffinity(pParse,pLeft->pEList)==0) /* condition (d) above */ + || IN_SPECIAL_PARSE + ){ + /* The co-routine method cannot be used. Fall back to UNION ALL. */ + Select *pSelect = 0; + int f = SF_Values | SF_MultiValue; + if( pLeft->pSrc->nSrc ){ + sqlite3MultiValuesEnd(pParse, pLeft); + f = SF_Values; + }else if( pLeft->pPrior ){ + /* In this case set the SF_MultiValue flag only if it was set on pLeft */ + f = (f & pLeft->selFlags); + } + pSelect = sqlite3SelectNew(pParse, pRow, 0, 0, 0, 0, 0, f, 0); + pLeft->selFlags &= ~(u32)SF_MultiValue; + if( pSelect ){ + pSelect->op = TK_ALL; + pSelect->pPrior = pLeft; + pLeft = pSelect; + } + }else{ + SrcItem *p = 0; /* SrcItem that reads from co-routine */ + + if( pLeft->pSrc->nSrc==0 ){ + /* Co-routine has not yet been started and the special Select object + ** that accesses the co-routine has not yet been created. This block + ** does both those things. */ + Vdbe *v = sqlite3GetVdbe(pParse); + Select *pRet = sqlite3SelectNew(pParse, 0, 0, 0, 0, 0, 0, 0, 0); + + /* Ensure the database schema has been read. This is to ensure we have + ** the correct text encoding. */ + if( (pParse->db->mDbFlags & DBFLAG_SchemaKnownOk)==0 ){ + sqlite3ReadSchema(pParse); + } + + if( pRet ){ + SelectDest dest; + Subquery *pSubq; + pRet->pSrc->nSrc = 1; + pRet->pPrior = pLeft->pPrior; + pRet->op = pLeft->op; + if( pRet->pPrior ) pRet->selFlags |= SF_Values; + pLeft->pPrior = 0; + pLeft->op = TK_SELECT; + assert( pLeft->pNext==0 ); + assert( pRet->pNext==0 ); + p = &pRet->pSrc->a[0]; + p->fg.viaCoroutine = 1; + p->iCursor = -1; + assert( !p->fg.isIndexedBy && !p->fg.isTabFunc ); + p->u1.nRow = 2; + if( sqlite3SrcItemAttachSubquery(pParse, p, pLeft, 0) ){ + pSubq = p->u4.pSubq; + pSubq->addrFillSub = sqlite3VdbeCurrentAddr(v) + 1; + pSubq->regReturn = ++pParse->nMem; + sqlite3VdbeAddOp3(v, OP_InitCoroutine, + pSubq->regReturn, 0, pSubq->addrFillSub); + sqlite3SelectDestInit(&dest, SRT_Coroutine, pSubq->regReturn); + + /* Allocate registers for the output of the co-routine. Do so so + ** that there are two unused registers immediately before those + ** used by the co-routine. This allows the code in sqlite3Insert() + ** to use these registers directly, instead of copying the output + ** of the co-routine to a separate array for processing. */ + dest.iSdst = pParse->nMem + 3; + dest.nSdst = pLeft->pEList->nExpr; + pParse->nMem += 2 + dest.nSdst; + + pLeft->selFlags |= SF_MultiValue; + sqlite3Select(pParse, pLeft, &dest); + pSubq->regResult = dest.iSdst; + assert( pParse->nErr || dest.iSdst>0 ); + } + pLeft = pRet; + } + }else{ + p = &pLeft->pSrc->a[0]; + assert( !p->fg.isTabFunc && !p->fg.isIndexedBy ); + p->u1.nRow++; + } + + if( pParse->nErr==0 ){ + Subquery *pSubq; + assert( p!=0 ); + assert( p->fg.isSubquery ); + pSubq = p->u4.pSubq; + assert( pSubq!=0 ); + assert( pSubq->pSelect!=0 ); + assert( pSubq->pSelect->pEList!=0 ); + if( pSubq->pSelect->pEList->nExpr!=pRow->nExpr ){ + sqlite3SelectWrongNumTermsError(pParse, pSubq->pSelect); + }else{ + sqlite3ExprCodeExprList(pParse, pRow, pSubq->regResult, 0, 0); + sqlite3VdbeAddOp1(pParse->pVdbe, OP_Yield, pSubq->regReturn); + } + } + sqlite3ExprListDelete(pParse->db, pRow); + } + + return pLeft; +} /* Forward declaration */ static int xferOptimization( @@ -116957,7 +139798,7 @@ SQLITE_PRIVATE void sqlite3Insert( Parse *pParse, /* Parser context */ SrcList *pTabList, /* Name of table into which we are inserting */ Select *pSelect, /* A SELECT statement to use as the data source */ - IdList *pColumn, /* Column names corresponding to IDLIST. */ + IdList *pColumn, /* Column names corresponding to IDLIST, or NULL. */ int onError, /* How to handle constraint errors */ Upsert *pUpsert /* ON CONFLICT clauses for upsert, or NULL */ ){ @@ -116982,6 +139823,7 @@ SQLITE_PRIVATE void sqlite3Insert( u8 withoutRowid; /* 0 for normal table. 1 for WITHOUT ROWID table */ u8 bIdListInOrder; /* True if IDLIST is in table order */ ExprList *pList = 0; /* List of VALUES() to be inserted */ + int iRegStore; /* Register in which to store next column */ /* Register allocations */ int regFromSelect = 0;/* Base register for data coming from SELECT */ @@ -116991,6 +139833,7 @@ SQLITE_PRIVATE void sqlite3Insert( int regRowid; /* registers holding insert rowid */ int regData; /* register holding first column to insert */ int *aRegIdx = 0; /* One register allocated to each index */ + int *aTabColMap = 0; /* Mapping from pTab columns to pCol entries */ #ifndef SQLITE_OMIT_TRIGGER int isView; /* True if attempting to insert into a view */ @@ -116999,9 +139842,11 @@ SQLITE_PRIVATE void sqlite3Insert( #endif db = pParse->db; - if( pParse->nErr || db->mallocFailed ){ + assert( db->pParse==pParse ); + if( pParse->nErr ){ goto insert_cleanup; } + assert( db->mallocFailed==0 ); dest.iSDParm = 0; /* Suppress a harmless compiler warning */ /* If the Select object is really just a simple VALUES() list with a @@ -117035,7 +139880,7 @@ SQLITE_PRIVATE void sqlite3Insert( */ #ifndef SQLITE_OMIT_TRIGGER pTrigger = sqlite3TriggersExist(pParse, pTab, TK_INSERT, 0, &tmask); - isView = pTab->pSelect!=0; + isView = IsView(pTab); #else # define pTrigger 0 # define tmask 0 @@ -117047,6 +139892,14 @@ SQLITE_PRIVATE void sqlite3Insert( #endif assert( (pTrigger && tmask) || (pTrigger==0 && tmask==0) ); +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x10000 ){ + sqlite3TreeViewLine(0, "In sqlite3Insert() at %s:%d", __FILE__, __LINE__); + sqlite3TreeViewInsert(pParse->pWith, pTabList, pColumn, pSelect, pList, + onError, pUpsert, pTrigger); + } +#endif + /* If pTab is really a view, make sure it has been initialized. ** ViewGetColumnNames() is a no-op if pTab is not a view. */ @@ -117056,7 +139909,7 @@ SQLITE_PRIVATE void sqlite3Insert( /* Cannot insert into a read-only table. */ - if( sqlite3IsReadOnly(pParse, pTab, tmask) ){ + if( sqlite3IsReadOnly(pParse, pTab, pTrigger) ){ goto insert_cleanup; } @@ -117077,7 +139930,11 @@ SQLITE_PRIVATE void sqlite3Insert( ** ** This is the 2nd template. */ - if( pColumn==0 && xferOptimization(pParse, pTab, pSelect, onError, iDb) ){ + if( pColumn==0 + && pSelect!=0 + && pTrigger==0 + && xferOptimization(pParse, pTab, pSelect, onError, iDb) + ){ assert( !pTrigger ); assert( pList==0 ); goto insert_end; @@ -117089,8 +139946,8 @@ SQLITE_PRIVATE void sqlite3Insert( */ regAutoinc = autoIncBegin(pParse, iDb, pTab); - /* Allocate registers for holding the rowid of the new row, - ** the content of the new row, and the assembled row record. + /* Allocate a block registers to hold the rowid and the values + ** for all columns of the new row. */ regRowid = regIns = pParse->nMem+1; pParse->nMem += pTab->nCol + 1; @@ -117101,7 +139958,7 @@ SQLITE_PRIVATE void sqlite3Insert( regData = regRowid+1; /* If the INSERT statement included an IDLIST term, then make sure - ** all elements of the IDLIST really are columns of the table and + ** all elements of the IDLIST really are columns of the table and ** remember the column indices. ** ** If the table has an INTEGER PRIMARY KEY column and that column @@ -117109,31 +139966,43 @@ SQLITE_PRIVATE void sqlite3Insert( ** the index into IDLIST of the primary key column. ipkColumn is ** the index of the primary key as it appears in IDLIST, not as ** is appears in the original table. (The index of the INTEGER - ** PRIMARY KEY in the original table is pTab->iPKey.) + ** PRIMARY KEY in the original table is pTab->iPKey.) After this + ** loop, if ipkColumn==(-1), that means that integer primary key + ** is unspecified, and hence the table is either WITHOUT ROWID or + ** it will automatically generated an integer primary key. + ** + ** bIdListInOrder is true if the columns in IDLIST are in storage + ** order. This enables an optimization that avoids shuffling the + ** columns into storage order. False negatives are harmless, + ** but false positives will cause database corruption. */ - bIdListInOrder = (pTab->tabFlags & TF_OOOHidden)==0; + bIdListInOrder = (pTab->tabFlags & (TF_OOOHidden|TF_HasStored))==0; if( pColumn ){ + aTabColMap = sqlite3DbMallocZero(db, pTab->nCol*sizeof(int)); + if( aTabColMap==0 ) goto insert_cleanup; for(i=0; inId; i++){ - pColumn->a[i].idx = -1; - } - for(i=0; inId; i++){ - for(j=0; jnCol; j++){ - if( sqlite3StrICmp(pColumn->a[i].zName, pTab->aCol[j].zName)==0 ){ - pColumn->a[i].idx = j; - if( i!=j ) bIdListInOrder = 0; - if( j==pTab->iPKey ){ - ipkColumn = i; assert( !withoutRowid ); - } - break; + j = sqlite3ColumnIndex(pTab, pColumn->a[i].zName); + if( j>=0 ){ + if( aTabColMap[j]==0 ) aTabColMap[j] = i+1; + if( i!=j ) bIdListInOrder = 0; + if( j==pTab->iPKey ){ + ipkColumn = i; assert( !withoutRowid ); } - } - if( j>=pTab->nCol ){ +#ifndef SQLITE_OMIT_GENERATED_COLUMNS + if( pTab->aCol[j].colFlags & (COLFLAG_STORED|COLFLAG_VIRTUAL) ){ + sqlite3ErrorMsg(pParse, + "cannot INSERT into generated column \"%s\"", + pTab->aCol[j].zCnName); + goto insert_cleanup; + } +#endif + }else{ if( sqlite3IsRowid(pColumn->a[i].zName) && !withoutRowid ){ ipkColumn = i; bIdListInOrder = 0; }else{ sqlite3ErrorMsg(pParse, "table %S has no column named %s", - pTabList, 0, pColumn->a[i].zName); + pTabList->a, pColumn->a[i].zName); pParse->checkSchema = 1; goto insert_cleanup; } @@ -117149,23 +140018,45 @@ SQLITE_PRIVATE void sqlite3Insert( if( pSelect ){ /* Data is coming from a SELECT or from a multi-row VALUES clause. ** Generate a co-routine to run the SELECT. */ - int regYield; /* Register holding co-routine entry-point */ - int addrTop; /* Top of the co-routine */ int rc; /* Result code */ - regYield = ++pParse->nMem; - addrTop = sqlite3VdbeCurrentAddr(v) + 1; - sqlite3VdbeAddOp3(v, OP_InitCoroutine, regYield, 0, addrTop); - sqlite3SelectDestInit(&dest, SRT_Coroutine, regYield); - dest.iSdst = bIdListInOrder ? regData : 0; - dest.nSdst = pTab->nCol; - rc = sqlite3Select(pParse, pSelect, &dest); - regFromSelect = dest.iSdst; - if( rc || db->mallocFailed || pParse->nErr ) goto insert_cleanup; - sqlite3VdbeEndCoroutine(v, regYield); - sqlite3VdbeJumpHere(v, addrTop - 1); /* label B: */ - assert( pSelect->pEList ); - nColumn = pSelect->pEList->nExpr; + if( pSelect->pSrc->nSrc==1 + && pSelect->pSrc->a[0].fg.viaCoroutine + && pSelect->pPrior==0 + ){ + SrcItem *pItem = &pSelect->pSrc->a[0]; + Subquery *pSubq; + assert( pItem->fg.isSubquery ); + pSubq = pItem->u4.pSubq; + dest.iSDParm = pSubq->regReturn; + regFromSelect = pSubq->regResult; + assert( pSubq->pSelect!=0 ); + assert( pSubq->pSelect->pEList!=0 ); + nColumn = pSubq->pSelect->pEList->nExpr; + ExplainQueryPlan((pParse, 0, "SCAN %S", pItem)); + if( bIdListInOrder && nColumn==pTab->nCol ){ + regData = regFromSelect; + regRowid = regData - 1; + regIns = regRowid - (IsVirtual(pTab) ? 1 : 0); + } + }else{ + int addrTop; /* Top of the co-routine */ + int regYield = ++pParse->nMem; + addrTop = sqlite3VdbeCurrentAddr(v) + 1; + sqlite3VdbeAddOp3(v, OP_InitCoroutine, regYield, 0, addrTop); + sqlite3SelectDestInit(&dest, SRT_Coroutine, regYield); + dest.iSdst = bIdListInOrder ? regData : 0; + dest.nSdst = pTab->nCol; + rc = sqlite3Select(pParse, pSelect, &dest); + regFromSelect = dest.iSdst; + assert( db->pParse==pParse ); + if( rc || pParse->nErr ) goto insert_cleanup; + assert( db->mallocFailed==0 ); + sqlite3VdbeEndCoroutine(v, regYield); + sqlite3VdbeJumpHere(v, addrTop - 1); /* label B: */ + assert( pSelect->pEList ); + nColumn = pSelect->pEList->nExpr; + } /* Set useTempTable to TRUE if the result of the SELECT statement ** should be written into a temporary table (template 4). Set to @@ -117173,7 +140064,7 @@ SQLITE_PRIVATE void sqlite3Insert( ** the destination table (template 3). ** ** A temp table must be used if the table being updated is also one - ** of the tables being read by the SELECT statement. Also use a + ** of the tables being read by the SELECT statement. Also use a ** temp table in the case of row triggers. */ if( pTrigger || readsTable(pParse, iDb, pTab) ){ @@ -117209,7 +140100,7 @@ SQLITE_PRIVATE void sqlite3Insert( sqlite3ReleaseTempReg(pParse, regTempRowid); } }else{ - /* This is the case if the data for the INSERT is coming from a + /* This is the case if the data for the INSERT is coming from a ** single-row VALUES clause */ NameContext sNC; @@ -117228,35 +140119,54 @@ SQLITE_PRIVATE void sqlite3Insert( } /* If there is no IDLIST term but the table has an integer primary - ** key, the set the ipkColumn variable to the integer primary key + ** key, the set the ipkColumn variable to the integer primary key ** column index in the original table definition. */ if( pColumn==0 && nColumn>0 ){ ipkColumn = pTab->iPKey; - } +#ifndef SQLITE_OMIT_GENERATED_COLUMNS + if( ipkColumn>=0 && (pTab->tabFlags & TF_HasGenerated)!=0 ){ + testcase( pTab->tabFlags & TF_HasVirtual ); + testcase( pTab->tabFlags & TF_HasStored ); + for(i=ipkColumn-1; i>=0; i--){ + if( pTab->aCol[i].colFlags & COLFLAG_GENERATED ){ + testcase( pTab->aCol[i].colFlags & COLFLAG_VIRTUAL ); + testcase( pTab->aCol[i].colFlags & COLFLAG_STORED ); + ipkColumn--; + } + } + } +#endif - /* Make sure the number of columns in the source data matches the number - ** of columns to be inserted into the table. - */ - for(i=0; inCol; i++){ - nHidden += (IsHiddenColumn(&pTab->aCol[i]) ? 1 : 0); - } - if( pColumn==0 && nColumn && nColumn!=(pTab->nCol-nHidden) ){ - sqlite3ErrorMsg(pParse, - "table %S has %d columns but %d values were supplied", - pTabList, 0, pTab->nCol-nHidden, nColumn); - goto insert_cleanup; + /* Make sure the number of columns in the source data matches the number + ** of columns to be inserted into the table. + */ + assert( TF_HasHidden==COLFLAG_HIDDEN ); + assert( TF_HasGenerated==COLFLAG_GENERATED ); + assert( COLFLAG_NOINSERT==(COLFLAG_GENERATED|COLFLAG_HIDDEN) ); + if( (pTab->tabFlags & (TF_HasGenerated|TF_HasHidden))!=0 ){ + for(i=0; inCol; i++){ + if( pTab->aCol[i].colFlags & COLFLAG_NOINSERT ) nHidden++; + } + } + if( nColumn!=(pTab->nCol-nHidden) ){ + sqlite3ErrorMsg(pParse, + "table %S has %d columns but %d values were supplied", + pTabList->a, pTab->nCol-nHidden, nColumn); + goto insert_cleanup; + } } if( pColumn!=0 && nColumn!=pColumn->nId ){ sqlite3ErrorMsg(pParse, "%d values for %d columns", nColumn, pColumn->nId); goto insert_cleanup; } - + /* Initialize the count of rows to be inserted */ if( (db->flags & SQLITE_CountRows)!=0 && !pParse->nested && !pParse->pTriggerTab + && !pParse->bReturning ){ regRowCount = ++pParse->nMem; sqlite3VdbeAddOp2(v, OP_Integer, 0, regRowCount); @@ -117267,7 +140177,7 @@ SQLITE_PRIVATE void sqlite3Insert( int nIdx; nIdx = sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite, 0, -1, 0, &iDataCur, &iIdxCur); - aRegIdx = sqlite3DbMallocRawNN(db, sizeof(int)*(nIdx+1)); + aRegIdx = sqlite3DbMallocRawNN(db, sizeof(int)*(nIdx+2)); if( aRegIdx==0 ){ goto insert_cleanup; } @@ -117276,22 +140186,37 @@ SQLITE_PRIVATE void sqlite3Insert( aRegIdx[i] = ++pParse->nMem; pParse->nMem += pIdx->nColumn; } + aRegIdx[i] = ++pParse->nMem; /* Register to store the table record */ } #ifndef SQLITE_OMIT_UPSERT if( pUpsert ){ + Upsert *pNx; if( IsVirtual(pTab) ){ sqlite3ErrorMsg(pParse, "UPSERT not implemented for virtual table \"%s\"", pTab->zName); goto insert_cleanup; } - pTabList->a[0].iCursor = iDataCur; - pUpsert->pUpsertSrc = pTabList; - pUpsert->regData = regData; - pUpsert->iDataCur = iDataCur; - pUpsert->iIdxCur = iIdxCur; - if( pUpsert->pUpsertTarget ){ - sqlite3UpsertAnalyzeTarget(pParse, pTabList, pUpsert); + if( IsView(pTab) ){ + sqlite3ErrorMsg(pParse, "cannot UPSERT a view"); + goto insert_cleanup; + } + if( sqlite3HasExplicitNulls(pParse, pUpsert->pUpsertTarget) ){ + goto insert_cleanup; } + pTabList->a[0].iCursor = iDataCur; + pNx = pUpsert; + do{ + pNx->pUpsertSrc = pTabList; + pNx->regData = regData; + pNx->iDataCur = iDataCur; + pNx->iIdxCur = iIdxCur; + if( pNx->pUpsertTarget ){ + if( sqlite3UpsertAnalyzeTarget(pParse, pTabList, pNx, pUpsert) ){ + goto insert_cleanup; + } + } + pNx = pNx->pNextUpsert; + }while( pNx!=0 ); } #endif @@ -117318,10 +140243,103 @@ SQLITE_PRIVATE void sqlite3Insert( ** goto C ** D: ... */ + sqlite3VdbeReleaseRegisters(pParse, regData, pTab->nCol, 0, 0); addrInsTop = addrCont = sqlite3VdbeAddOp1(v, OP_Yield, dest.iSDParm); VdbeCoverage(v); + if( ipkColumn>=0 ){ + /* tag-20191021-001: If the INTEGER PRIMARY KEY is being generated by the + ** SELECT, go ahead and copy the value into the rowid slot now, so that + ** the value does not get overwritten by a NULL at tag-20191021-002. */ + sqlite3VdbeAddOp2(v, OP_Copy, regFromSelect+ipkColumn, regRowid); + } } + /* Compute data for ordinary columns of the new entry. Values + ** are written in storage order into registers starting with regData. + ** Only ordinary columns are computed in this loop. The rowid + ** (if there is one) is computed later and generated columns are + ** computed after the rowid since they might depend on the value + ** of the rowid. + */ + nHidden = 0; + iRegStore = regData; assert( regData==regRowid+1 ); + for(i=0; inCol; i++, iRegStore++){ + int k; + u32 colFlags; + assert( i>=nHidden ); + if( i==pTab->iPKey ){ + /* tag-20191021-002: References to the INTEGER PRIMARY KEY are filled + ** using the rowid. So put a NULL in the IPK slot of the record to avoid + ** using excess space. The file format definition requires this extra + ** NULL - we cannot optimize further by skipping the column completely */ + sqlite3VdbeAddOp1(v, OP_SoftNull, iRegStore); + continue; + } + if( ((colFlags = pTab->aCol[i].colFlags) & COLFLAG_NOINSERT)!=0 ){ + nHidden++; + if( (colFlags & COLFLAG_VIRTUAL)!=0 ){ + /* Virtual columns do not participate in OP_MakeRecord. So back up + ** iRegStore by one slot to compensate for the iRegStore++ in the + ** outer for() loop */ + iRegStore--; + continue; + }else if( (colFlags & COLFLAG_STORED)!=0 ){ + /* Stored columns are computed later. But if there are BEFORE + ** triggers, the slots used for stored columns will be OP_Copy-ed + ** to a second block of registers, so the register needs to be + ** initialized to NULL to avoid an uninitialized register read */ + if( tmask & TRIGGER_BEFORE ){ + sqlite3VdbeAddOp1(v, OP_SoftNull, iRegStore); + } + continue; + }else if( pColumn==0 ){ + /* Hidden columns that are not explicitly named in the INSERT + ** get their default value */ + sqlite3ExprCodeFactorable(pParse, + sqlite3ColumnExpr(pTab, &pTab->aCol[i]), + iRegStore); + continue; + } + } + if( pColumn ){ + j = aTabColMap[i]; + assert( j>=0 && j<=pColumn->nId ); + if( j==0 ){ + /* A column not named in the insert column list gets its + ** default value */ + sqlite3ExprCodeFactorable(pParse, + sqlite3ColumnExpr(pTab, &pTab->aCol[i]), + iRegStore); + continue; + } + k = j - 1; + }else if( nColumn==0 ){ + /* This is INSERT INTO ... DEFAULT VALUES. Load the default value. */ + sqlite3ExprCodeFactorable(pParse, + sqlite3ColumnExpr(pTab, &pTab->aCol[i]), + iRegStore); + continue; + }else{ + k = i - nHidden; + } + + if( useTempTable ){ + sqlite3VdbeAddOp3(v, OP_Column, srcTab, k, iRegStore); + }else if( pSelect ){ + if( regFromSelect!=regData ){ + sqlite3VdbeAddOp2(v, OP_SCopy, regFromSelect+k, iRegStore); + } + }else{ + Expr *pX = pList->a[k].pExpr; + int y = sqlite3ExprCodeTarget(pParse, pX, iRegStore); + if( y!=iRegStore ){ + sqlite3VdbeAddOp2(v, + ExprHasProperty(pX, EP_Subquery) ? OP_Copy : OP_SCopy, y, iRegStore); + } + } + } + + /* Run the BEFORE and INSTEAD OF triggers, if there are any */ endOfLoop = sqlite3VdbeMakeLabel(pParse); @@ -117351,30 +140369,21 @@ SQLITE_PRIVATE void sqlite3Insert( sqlite3VdbeAddOp1(v, OP_MustBeInt, regCols); VdbeCoverage(v); } - /* Cannot have triggers on a virtual table. If it were possible, - ** this block would have to account for hidden column. - */ - assert( !IsVirtual(pTab) ); + /* Copy the new data already generated. */ + assert( pTab->nNVCol>0 || pParse->nErr>0 ); + sqlite3VdbeAddOp3(v, OP_Copy, regRowid+1, regCols+1, pTab->nNVCol-1); - /* Create the new column data - */ - for(i=j=0; inCol; i++){ - if( pColumn ){ - for(j=0; jnId; j++){ - if( pColumn->a[j].idx==i ) break; - } - } - if( (!useTempTable && !pList) || (pColumn && j>=pColumn->nId) - || (pColumn==0 && IsOrdinaryHiddenColumn(&pTab->aCol[i])) ){ - sqlite3ExprCode(pParse, pTab->aCol[i].pDflt, regCols+i+1); - }else if( useTempTable ){ - sqlite3VdbeAddOp3(v, OP_Column, srcTab, j, regCols+i+1); - }else{ - assert( pSelect==0 ); /* Otherwise useTempTable is true */ - sqlite3ExprCodeAndCache(pParse, pList->a[j].pExpr, regCols+i+1); - } - if( pColumn==0 && !IsOrdinaryHiddenColumn(&pTab->aCol[i]) ) j++; +#ifndef SQLITE_OMIT_GENERATED_COLUMNS + /* Compute the new value for generated columns after all other + ** columns have already been computed. This must be done after + ** computing the ROWID in case one of the generated columns + ** refers to the ROWID. */ + if( pTab->tabFlags & TF_HasGenerated ){ + testcase( pTab->tabFlags & TF_HasVirtual ); + testcase( pTab->tabFlags & TF_HasStored ); + sqlite3ComputeGeneratedColumns(pParse, regCols+1, pTab); } +#endif /* If this is an INSERT on a view with an INSTEAD OF INSERT trigger, ** do not attempt any conversions before assembling the record. @@ -117386,25 +140395,23 @@ SQLITE_PRIVATE void sqlite3Insert( } /* Fire BEFORE or INSTEAD OF triggers */ - sqlite3CodeRowTrigger(pParse, pTrigger, TK_INSERT, 0, TRIGGER_BEFORE, + sqlite3CodeRowTrigger(pParse, pTrigger, TK_INSERT, 0, TRIGGER_BEFORE, pTab, regCols-pTab->nCol-1, onError, endOfLoop); sqlite3ReleaseTempRange(pParse, regCols, pTab->nCol+1); } - /* Compute the content of the next row to insert into a range of - ** registers beginning at regIns. - */ if( !isView ){ if( IsVirtual(pTab) ){ /* The row that the VUpdate opcode will delete: none */ sqlite3VdbeAddOp2(v, OP_Null, 0, regIns); } if( ipkColumn>=0 ){ + /* Compute the new rowid */ if( useTempTable ){ sqlite3VdbeAddOp3(v, OP_Column, srcTab, ipkColumn, regRowid); }else if( pSelect ){ - sqlite3VdbeAddOp2(v, OP_Copy, regFromSelect+ipkColumn, regRowid); + /* Rowid already initialized at tag-20191021-001 */ }else{ Expr *pIpk = pList->a[ipkColumn].pExpr; if( pIpk->op==TK_NULL && !IsVirtual(pTab) ){ @@ -117437,45 +140444,15 @@ SQLITE_PRIVATE void sqlite3Insert( } autoIncStep(pParse, regAutoinc, regRowid); - /* Compute data for all columns of the new entry, beginning - ** with the first column. - */ - nHidden = 0; - for(i=0; inCol; i++){ - int iRegStore = regRowid+1+i; - if( i==pTab->iPKey ){ - /* The value of the INTEGER PRIMARY KEY column is always a NULL. - ** Whenever this column is read, the rowid will be substituted - ** in its place. Hence, fill this column with a NULL to avoid - ** taking up data space with information that will never be used. - ** As there may be shallow copies of this value, make it a soft-NULL */ - sqlite3VdbeAddOp1(v, OP_SoftNull, iRegStore); - continue; - } - if( pColumn==0 ){ - if( IsHiddenColumn(&pTab->aCol[i]) ){ - j = -1; - nHidden++; - }else{ - j = i - nHidden; - } - }else{ - for(j=0; jnId; j++){ - if( pColumn->a[j].idx==i ) break; - } - } - if( j<0 || nColumn==0 || (pColumn && j>=pColumn->nId) ){ - sqlite3ExprCodeFactorable(pParse, pTab->aCol[i].pDflt, iRegStore); - }else if( useTempTable ){ - sqlite3VdbeAddOp3(v, OP_Column, srcTab, j, iRegStore); - }else if( pSelect ){ - if( regFromSelect!=regData ){ - sqlite3VdbeAddOp2(v, OP_SCopy, regFromSelect+j, iRegStore); - } - }else{ - sqlite3ExprCode(pParse, pList->a[j].pExpr, iRegStore); - } +#ifndef SQLITE_OMIT_GENERATED_COLUMNS + /* Compute the new value for generated columns after all other + ** columns have already been computed. This must be done after + ** computing the ROWID in case one of the generated columns + ** is derived from the INTEGER PRIMARY KEY. */ + if( pTab->tabFlags & TF_HasGenerated ){ + sqlite3ComputeGeneratedColumns(pParse, regRowid+1, pTab); } +#endif /* Generate code to check constraints and generate index keys and ** do the insertion. @@ -117490,28 +140467,35 @@ SQLITE_PRIVATE void sqlite3Insert( }else #endif { - int isReplace; /* Set to true if constraints may cause a replace */ + int isReplace = 0;/* Set to true if constraints may cause a replace */ int bUseSeek; /* True to use OPFLAG_SEEKRESULT */ sqlite3GenerateConstraintChecks(pParse, pTab, aRegIdx, iDataCur, iIdxCur, regIns, 0, ipkColumn>=0, onError, endOfLoop, &isReplace, 0, pUpsert ); - sqlite3FkCheck(pParse, pTab, 0, regIns, 0, 0); + if( db->flags & SQLITE_ForeignKeys ){ + sqlite3FkCheck(pParse, pTab, 0, regIns, 0, 0); + } /* Set the OPFLAG_USESEEKRESULT flag if either (a) there are no REPLACE ** constraints or (b) there are no triggers and this table is not a ** parent table in a foreign key constraint. It is safe to set the ** flag in the second case as if any REPLACE constraint is hit, an - ** OP_Delete or OP_IdxDelete instruction will be executed on each + ** OP_Delete or OP_IdxDelete instruction will be executed on each ** cursor that is disturbed. And these instructions both clear the ** VdbeCursor.seekResult variable, disabling the OPFLAG_USESEEKRESULT ** functionality. */ - bUseSeek = (isReplace==0 || (pTrigger==0 && - ((db->flags & SQLITE_ForeignKeys)==0 || sqlite3FkReferences(pTab)==0) - )); + bUseSeek = (isReplace==0 || !sqlite3VdbeHasSubProgram(v)); sqlite3CompleteInsertion(pParse, pTab, iDataCur, iIdxCur, regIns, aRegIdx, 0, appendFlag, bUseSeek ); } +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + }else if( pParse->bReturning ){ + /* If there is a RETURNING clause, populate the rowid register with + ** constant value -1, in case one or more of the returned expressions + ** refer to the "rowid" of the view. */ + sqlite3VdbeAddOp2(v, OP_Integer, -1, regRowid); +#endif } /* Update the count of rows that are inserted @@ -117522,7 +140506,7 @@ SQLITE_PRIVATE void sqlite3Insert( if( pTrigger ){ /* Code AFTER triggers */ - sqlite3CodeRowTrigger(pParse, pTrigger, TK_INSERT, 0, TRIGGER_AFTER, + sqlite3CodeRowTrigger(pParse, pTrigger, TK_INSERT, 0, TRIGGER_AFTER, pTab, regData-2-pTab->nCol, onError, endOfLoop); } @@ -117536,10 +140520,21 @@ SQLITE_PRIVATE void sqlite3Insert( sqlite3VdbeAddOp1(v, OP_Close, srcTab); }else if( pSelect ){ sqlite3VdbeGoto(v, addrCont); +#ifdef SQLITE_DEBUG + /* If we are jumping back to an OP_Yield that is preceded by an + ** OP_ReleaseReg, set the p5 flag on the OP_Goto so that the + ** OP_ReleaseReg will be included in the loop. */ + if( sqlite3VdbeGetOp(v, addrCont-1)->opcode==OP_ReleaseReg ){ + assert( sqlite3VdbeGetOp(v, addrCont)->opcode==OP_Yield ); + sqlite3VdbeChangeP5(v, 1); + } +#endif sqlite3VdbeJumpHere(v, addrInsTop); } +#ifndef SQLITE_OMIT_XFER_OPT insert_end: +#endif /* SQLITE_OMIT_XFER_OPT */ /* Update the sqlite_sequence table by storing the content of the ** maximum rowid counter values recorded while inserting into ** autoincrement tables. @@ -117549,14 +140544,12 @@ SQLITE_PRIVATE void sqlite3Insert( } /* - ** Return the number of rows inserted. If this routine is + ** Return the number of rows inserted. If this routine is ** generating code because of a call to sqlite3NestedParse(), do not ** invoke the callback function. */ if( regRowCount ){ - sqlite3VdbeAddOp2(v, OP_ResultRow, regRowCount, 1); - sqlite3VdbeSetNumCols(v, 1); - sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "rows inserted", SQLITE_STATIC); + sqlite3CodeChangeCount(v, regRowCount, "rows inserted"); } insert_cleanup: @@ -117564,8 +140557,11 @@ SQLITE_PRIVATE void sqlite3Insert( sqlite3ExprListDelete(db, pList); sqlite3UpsertDelete(db, pUpsert); sqlite3SelectDelete(db, pSelect); - sqlite3IdListDelete(db, pColumn); - sqlite3DbFree(db, aRegIdx); + if( pColumn ){ + sqlite3IdListDelete(db, pColumn); + sqlite3DbFree(db, aTabColMap); + } + if( aRegIdx ) sqlite3DbNNFreeNN(db, aRegIdx); } /* Make sure "isView" and other macros defined above are undefined. Otherwise @@ -117582,7 +140578,7 @@ SQLITE_PRIVATE void sqlite3Insert( #endif /* -** Meanings of bits in of pWalker->eCode for +** Meanings of bits in of pWalker->eCode for ** sqlite3ExprReferencesUpdatedColumn() */ #define CKCNSTRNT_COLUMN 0x01 /* CHECK constraint uses a changing column */ @@ -117591,7 +140587,7 @@ SQLITE_PRIVATE void sqlite3Insert( /* This is the Walker callback from sqlite3ExprReferencesUpdatedColumn(). * Set bit 0x01 of pWalker->eCode if pWalker->eCode to 0 and if this ** expression node references any of the -** columns that are being modifed by an UPDATE statement. +** columns that are being modified by an UPDATE statement. */ static int checkConstraintExprNode(Walker *pWalker, Expr *pExpr){ if( pExpr->op==TK_COLUMN ){ @@ -117644,6 +140640,70 @@ SQLITE_PRIVATE int sqlite3ExprReferencesUpdatedColumn( return w.eCode!=0; } +/* +** The sqlite3GenerateConstraintChecks() routine usually wants to visit +** the indexes of a table in the order provided in the Table->pIndex list. +** However, sometimes (rarely - when there is an upsert) it wants to visit +** the indexes in a different order. The following data structures accomplish +** this. +** +** The IndexIterator object is used to walk through all of the indexes +** of a table in either Index.pNext order, or in some other order established +** by an array of IndexListTerm objects. +*/ +typedef struct IndexListTerm IndexListTerm; +typedef struct IndexIterator IndexIterator; +struct IndexIterator { + int eType; /* 0 for Index.pNext list. 1 for an array of IndexListTerm */ + int i; /* Index of the current item from the list */ + union { + struct { /* Use this object for eType==0: A Index.pNext list */ + Index *pIdx; /* The current Index */ + } lx; + struct { /* Use this object for eType==1; Array of IndexListTerm */ + int nIdx; /* Size of the array */ + IndexListTerm *aIdx; /* Array of IndexListTerms */ + } ax; + } u; +}; + +/* When IndexIterator.eType==1, then each index is an array of instances +** of the following object +*/ +struct IndexListTerm { + Index *p; /* The index */ + int ix; /* Which entry in the original Table.pIndex list is this index*/ +}; + +/* Return the first index on the list */ +static Index *indexIteratorFirst(IndexIterator *pIter, int *pIx){ + assert( pIter->i==0 ); + if( pIter->eType ){ + *pIx = pIter->u.ax.aIdx[0].ix; + return pIter->u.ax.aIdx[0].p; + }else{ + *pIx = 0; + return pIter->u.lx.pIdx; + } +} + +/* Return the next index from the list. Return NULL when out of indexes */ +static Index *indexIteratorNext(IndexIterator *pIter, int *pIx){ + if( pIter->eType ){ + int i = ++pIter->i; + if( i>=pIter->u.ax.nIdx ){ + *pIx = i; + return 0; + } + *pIx = pIter->u.ax.aIdx[i].ix; + return pIter->u.ax.aIdx[i].p; + }else{ + ++(*pIx); + pIter->u.lx.pIdx = pIter->u.lx.pIdx->pNext; + return pIter->u.lx.pIdx; + } +} + /* ** Generate code to do constraint checks prior to an INSERT or an UPDATE ** on table pTab. @@ -117679,6 +140739,14 @@ SQLITE_PRIVATE int sqlite3ExprReferencesUpdatedColumn( ** the same as the order of indices on the linked list of indices ** at pTab->pIndex. ** +** (2019-05-07) The generated code also creates a new record for the +** main table, if pTab is a rowid table, and stores that record in the +** register identified by aRegIdx[nIdx] - in other words in the first +** entry of aRegIdx[] past the last index. It is important that the +** record be generated during constraint checks to avoid affinity changes +** to the register content that occur after constraint checks but before +** the new record is inserted. +** ** The caller must have already opened writeable cursors on the main ** table and all applicable indices (that is to say, all indices for which ** aRegIdx[] is not zero). iDataCur is the cursor for the main table when @@ -117742,34 +140810,41 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( int *aiChng, /* column i is unchanged if aiChng[i]<0 */ Upsert *pUpsert /* ON CONFLICT clauses, if any. NULL otherwise */ ){ - Vdbe *v; /* VDBE under constrution */ + Vdbe *v; /* VDBE under construction */ Index *pIdx; /* Pointer to one of the indices */ - Index *pPk = 0; /* The PRIMARY KEY index */ + Index *pPk = 0; /* The PRIMARY KEY index for WITHOUT ROWID tables */ sqlite3 *db; /* Database connection */ int i; /* loop counter */ int ix; /* Index loop counter */ int nCol; /* Number of columns */ int onError; /* Conflict resolution strategy */ - int addr1; /* Address of jump instruction */ int seenReplace = 0; /* True if REPLACE is used to resolve INT PK conflict */ int nPkField; /* Number of fields in PRIMARY KEY. 1 for ROWID tables */ - Index *pUpIdx = 0; /* Index to which to apply the upsert */ - u8 isUpdate; /* True if this is an UPDATE operation */ + Upsert *pUpsertClause = 0; /* The specific ON CONFLICT clause for pIdx */ + u8 isUpdate; /* True if this is an UPDATE operation */ u8 bAffinityDone = 0; /* True if the OP_Affinity operation has been run */ - int upsertBypass = 0; /* Address of Goto to bypass upsert subroutine */ - int upsertJump = 0; /* Address of Goto that jumps into upsert subroutine */ + int upsertIpkReturn = 0; /* Address of Goto at end of IPK uniqueness check */ + int upsertIpkDelay = 0; /* Address of Goto to bypass initial IPK check */ int ipkTop = 0; /* Top of the IPK uniqueness check */ int ipkBottom = 0; /* OP_Goto at the end of the IPK uniqueness check */ + /* Variables associated with retesting uniqueness constraints after + ** replace triggers fire have run */ + int regTrigCnt; /* Register used to count replace trigger invocations */ + int addrRecheck = 0; /* Jump here to recheck all uniqueness constraints */ + int lblRecheckOk = 0; /* Each recheck jumps to this label if it passes */ + Trigger *pTrigger; /* List of DELETE triggers on the table pTab */ + int nReplaceTrig = 0; /* Number of replace triggers coded */ + IndexIterator sIdxIter; /* Index iterator */ isUpdate = regOldData!=0; db = pParse->db; - v = sqlite3GetVdbe(pParse); + v = pParse->pVdbe; assert( v!=0 ); - assert( pTab->pSelect==0 ); /* This table is not a VIEW */ + assert( !IsView(pTab) ); /* This table is not a VIEW */ nCol = pTab->nCol; - + /* pPk is the PRIMARY KEY index for WITHOUT ROWID tables and NULL for - ** normal rowid tables. nPkField is the number of key fields in the + ** normal rowid tables. nPkField is the number of key fields in the ** pPk index or 1 for a rowid table. In other words, nPkField is the ** number of fields in the true primary key of the table. */ if( HasRowid(pTab) ){ @@ -117786,63 +140861,105 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( /* Test all NOT NULL constraints. */ - for(i=0; iiPKey ){ - continue; /* ROWID is never NULL */ - } - if( aiChng && aiChng[i]<0 ){ - /* Don't bother checking for NOT NULL on columns that do not change */ - continue; - } - onError = pTab->aCol[i].notNull; - if( onError==OE_None ) continue; /* This column is allowed to be NULL */ - if( overrideError!=OE_Default ){ - onError = overrideError; - }else if( onError==OE_Default ){ - onError = OE_Abort; - } - if( onError==OE_Replace && pTab->aCol[i].pDflt==0 ){ - onError = OE_Abort; - } - assert( onError==OE_Rollback || onError==OE_Abort || onError==OE_Fail - || onError==OE_Ignore || onError==OE_Replace ); - addr1 = 0; - switch( onError ){ - case OE_Replace: { - assert( onError==OE_Replace ); - addr1 = sqlite3VdbeMakeLabel(pParse); - sqlite3VdbeAddOp2(v, OP_NotNull, regNewData+1+i, addr1); - VdbeCoverage(v); - sqlite3ExprCode(pParse, pTab->aCol[i].pDflt, regNewData+1+i); - sqlite3VdbeAddOp2(v, OP_NotNull, regNewData+1+i, addr1); - VdbeCoverage(v); - onError = OE_Abort; - /* Fall through into the OE_Abort case to generate code that runs - ** if both the input and the default value are NULL */ - } - case OE_Abort: - sqlite3MayAbort(pParse); - /* Fall through */ - case OE_Rollback: - case OE_Fail: { - char *zMsg = sqlite3MPrintf(db, "%s.%s", pTab->zName, - pTab->aCol[i].zName); - sqlite3VdbeAddOp3(v, OP_HaltIfNull, SQLITE_CONSTRAINT_NOTNULL, onError, - regNewData+1+i); - sqlite3VdbeAppendP4(v, zMsg, P4_DYNAMIC); - sqlite3VdbeChangeP5(v, P5_ConstraintNotNull); - VdbeCoverage(v); - if( addr1 ) sqlite3VdbeResolveLabel(v, addr1); + if( pTab->tabFlags & TF_HasNotNull ){ + int b2ndPass = 0; /* True if currently running 2nd pass */ + int nSeenReplace = 0; /* Number of ON CONFLICT REPLACE operations */ + int nGenerated = 0; /* Number of generated columns with NOT NULL */ + while(1){ /* Make 2 passes over columns. Exit loop via "break" */ + for(i=0; iaCol[i]; /* The column to check for NOT NULL */ + int isGenerated; /* non-zero if column is generated */ + onError = pCol->notNull; + if( onError==OE_None ) continue; /* No NOT NULL on this column */ + if( i==pTab->iPKey ){ + continue; /* ROWID is never NULL */ + } + isGenerated = pCol->colFlags & COLFLAG_GENERATED; + if( isGenerated && !b2ndPass ){ + nGenerated++; + continue; /* Generated columns processed on 2nd pass */ + } + if( aiChng && aiChng[i]<0 && !isGenerated ){ + /* Do not check NOT NULL on columns that do not change */ + continue; + } + if( overrideError!=OE_Default ){ + onError = overrideError; + }else if( onError==OE_Default ){ + onError = OE_Abort; + } + if( onError==OE_Replace ){ + if( b2ndPass /* REPLACE becomes ABORT on the 2nd pass */ + || pCol->iDflt==0 /* REPLACE is ABORT if no DEFAULT value */ + ){ + testcase( pCol->colFlags & COLFLAG_VIRTUAL ); + testcase( pCol->colFlags & COLFLAG_STORED ); + testcase( pCol->colFlags & COLFLAG_GENERATED ); + onError = OE_Abort; + }else{ + assert( !isGenerated ); + } + }else if( b2ndPass && !isGenerated ){ + continue; + } + assert( onError==OE_Rollback || onError==OE_Abort || onError==OE_Fail + || onError==OE_Ignore || onError==OE_Replace ); + testcase( i!=sqlite3TableColumnToStorage(pTab, i) ); + iReg = sqlite3TableColumnToStorage(pTab, i) + regNewData + 1; + switch( onError ){ + case OE_Replace: { + int addr1 = sqlite3VdbeAddOp1(v, OP_NotNull, iReg); + VdbeCoverage(v); + assert( (pCol->colFlags & COLFLAG_GENERATED)==0 ); + nSeenReplace++; + sqlite3ExprCodeCopy(pParse, + sqlite3ColumnExpr(pTab, pCol), iReg); + sqlite3VdbeJumpHere(v, addr1); + break; + } + case OE_Abort: + sqlite3MayAbort(pParse); + /* no break */ deliberate_fall_through + case OE_Rollback: + case OE_Fail: { + char *zMsg = sqlite3MPrintf(db, "%s.%s", pTab->zName, + pCol->zCnName); + testcase( zMsg==0 && db->mallocFailed==0 ); + sqlite3VdbeAddOp3(v, OP_HaltIfNull, SQLITE_CONSTRAINT_NOTNULL, + onError, iReg); + sqlite3VdbeAppendP4(v, zMsg, P4_DYNAMIC); + sqlite3VdbeChangeP5(v, P5_ConstraintNotNull); + VdbeCoverage(v); + break; + } + default: { + assert( onError==OE_Ignore ); + sqlite3VdbeAddOp2(v, OP_IsNull, iReg, ignoreDest); + VdbeCoverage(v); + break; + } + } /* end switch(onError) */ + } /* end loop i over columns */ + if( nGenerated==0 && nSeenReplace==0 ){ + /* If there are no generated columns with NOT NULL constraints + ** and no NOT NULL ON CONFLICT REPLACE constraints, then a single + ** pass is sufficient */ break; } - default: { - assert( onError==OE_Ignore ); - sqlite3VdbeAddOp2(v, OP_IsNull, regNewData+1+i, ignoreDest); - VdbeCoverage(v); - break; + if( b2ndPass ) break; /* Never need more than 2 passes */ + b2ndPass = 1; +#ifndef SQLITE_OMIT_GENERATED_COLUMNS + if( nSeenReplace>0 && (pTab->tabFlags & TF_HasGenerated)!=0 ){ + /* If any NOT NULL ON CONFLICT REPLACE constraints fired on the + ** first pass, recomputed values for all generated columns, as + ** those values might depend on columns affected by the REPLACE. + */ + sqlite3ComputeGeneratedColumns(pParse, regNewData+1, pTab); } - } - } +#endif + } /* end of 2-pass loop */ + } /* end if( has-not-null-constraints ) */ /* Test all CHECK constraints */ @@ -117853,6 +140970,7 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( onError = overrideError!=OE_Default ? overrideError : OE_Abort; for(i=0; inExpr; i++){ int allOk; + Expr *pCopy; Expr *pExpr = pCheck->a[i].pExpr; if( aiChng && !sqlite3ExprReferencesUpdatedColumn(pExpr, aiChng, pkChng) @@ -117861,15 +140979,23 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( ** updated so there is no point it verifying the check constraint */ continue; } + if( bAffinityDone==0 ){ + sqlite3TableAffinity(v, pTab, regNewData+1); + bAffinityDone = 1; + } allOk = sqlite3VdbeMakeLabel(pParse); sqlite3VdbeVerifyAbortable(v, onError); - sqlite3ExprIfTrue(pParse, pExpr, allOk, SQLITE_JUMPIFNULL); + pCopy = sqlite3ExprDup(db, pExpr, 0); + if( !db->mallocFailed ){ + sqlite3ExprIfTrue(pParse, pCopy, allOk, SQLITE_JUMPIFNULL); + } + sqlite3ExprDelete(db, pCopy); if( onError==OE_Ignore ){ sqlite3VdbeGoto(v, ignoreDest); }else{ - char *zName = pCheck->a[i].zName; - if( zName==0 ) zName = pTab->zName; - if( onError==OE_Replace ) onError = OE_Abort; /* IMP: R-15569-63625 */ + char *zName = pCheck->a[i].zEName; + assert( zName!=0 || pParse->db->mallocFailed ); + if( onError==OE_Replace ) onError = OE_Abort; /* IMP: R-26383-51744 */ sqlite3HaltConstraint(pParse, SQLITE_CONSTRAINT_CHECK, onError, zName, P4_TRANSIENT, P5_ConstraintCheck); @@ -117893,7 +141019,7 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( ** could happen in any order, but they are grouped up front for ** convenience. ** - ** 2018-08-14: Ticket https://www.sqlite.org/src/info/908f001483982c43 + ** 2018-08-14: Ticket https://sqlite.org/src/info/908f001483982c43 ** The order of constraints used to have OE_Update as (2) and OE_Abort ** and so forth as (1). But apparently PostgreSQL checks the OE_Update ** constraint before any others, so it had to be moved. @@ -117908,19 +141034,107 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( ** list of indexes attached to a table puts all OE_Replace indexes last ** in the list. See sqlite3CreateIndex() for where that happens. */ - + sIdxIter.eType = 0; + sIdxIter.i = 0; + sIdxIter.u.ax.aIdx = 0; /* Silence harmless compiler warning */ + sIdxIter.u.lx.pIdx = pTab->pIndex; if( pUpsert ){ if( pUpsert->pUpsertTarget==0 ){ - /* An ON CONFLICT DO NOTHING clause, without a constraint-target. - ** Make all unique constraint resolution be OE_Ignore */ - assert( pUpsert->pUpsertSet==0 ); - overrideError = OE_Ignore; - pUpsert = 0; - }else if( (pUpIdx = pUpsert->pUpsertIdx)!=0 ){ - /* If the constraint-target uniqueness check must be run first. - ** Jump to that uniqueness check now */ - upsertJump = sqlite3VdbeAddOp0(v, OP_Goto); - VdbeComment((v, "UPSERT constraint goes first")); + /* There is just on ON CONFLICT clause and it has no constraint-target */ + assert( pUpsert->pNextUpsert==0 ); + if( pUpsert->isDoUpdate==0 ){ + /* A single ON CONFLICT DO NOTHING clause, without a constraint-target. + ** Make all unique constraint resolution be OE_Ignore */ + overrideError = OE_Ignore; + pUpsert = 0; + }else{ + /* A single ON CONFLICT DO UPDATE. Make all resolutions OE_Update */ + overrideError = OE_Update; + } + }else if( pTab->pIndex!=0 ){ + /* Otherwise, we'll need to run the IndexListTerm array version of the + ** iterator to ensure that all of the ON CONFLICT conditions are + ** checked first and in order. */ + int nIdx, jj; + u64 nByte; + Upsert *pTerm; + u8 *bUsed; + for(nIdx=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, nIdx++){ + assert( aRegIdx[nIdx]>0 ); + } + sIdxIter.eType = 1; + sIdxIter.u.ax.nIdx = nIdx; + nByte = (sizeof(IndexListTerm)+1)*nIdx + nIdx; + sIdxIter.u.ax.aIdx = sqlite3DbMallocZero(db, nByte); + if( sIdxIter.u.ax.aIdx==0 ) return; /* OOM */ + bUsed = (u8*)&sIdxIter.u.ax.aIdx[nIdx]; + pUpsert->pToFree = sIdxIter.u.ax.aIdx; + for(i=0, pTerm=pUpsert; pTerm; pTerm=pTerm->pNextUpsert){ + if( pTerm->pUpsertTarget==0 ) break; + if( pTerm->pUpsertIdx==0 ) continue; /* Skip ON CONFLICT for the IPK */ + jj = 0; + pIdx = pTab->pIndex; + while( ALWAYS(pIdx!=0) && pIdx!=pTerm->pUpsertIdx ){ + pIdx = pIdx->pNext; + jj++; + } + if( bUsed[jj] ) continue; /* Duplicate ON CONFLICT clause ignored */ + bUsed[jj] = 1; + sIdxIter.u.ax.aIdx[i].p = pIdx; + sIdxIter.u.ax.aIdx[i].ix = jj; + i++; + } + for(jj=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, jj++){ + if( bUsed[jj] ) continue; + sIdxIter.u.ax.aIdx[i].p = pIdx; + sIdxIter.u.ax.aIdx[i].ix = jj; + i++; + } + assert( i==nIdx ); + } + } + + /* Determine if it is possible that triggers (either explicitly coded + ** triggers or FK resolution actions) might run as a result of deletes + ** that happen when OE_Replace conflict resolution occurs. (Call these + ** "replace triggers".) If any replace triggers run, we will need to + ** recheck all of the uniqueness constraints after they have all run. + ** But on the recheck, the resolution is OE_Abort instead of OE_Replace. + ** + ** If replace triggers are a possibility, then + ** + ** (1) Allocate register regTrigCnt and initialize it to zero. + ** That register will count the number of replace triggers that + ** fire. Constraint recheck only occurs if the number is positive. + ** (2) Initialize pTrigger to the list of all DELETE triggers on pTab. + ** (3) Initialize addrRecheck and lblRecheckOk + ** + ** The uniqueness rechecking code will create a series of tests to run + ** in a second pass. The addrRecheck and lblRecheckOk variables are + ** used to link together these tests which are separated from each other + ** in the generate bytecode. + */ + if( (db->flags & (SQLITE_RecTriggers|SQLITE_ForeignKeys))==0 ){ + /* There are not DELETE triggers nor FK constraints. No constraint + ** rechecks are needed. */ + pTrigger = 0; + regTrigCnt = 0; + }else{ + if( db->flags&SQLITE_RecTriggers ){ + pTrigger = sqlite3TriggersExist(pParse, pTab, TK_DELETE, 0, 0); + regTrigCnt = pTrigger!=0 || sqlite3FkRequired(pParse, pTab, 0, 0); + }else{ + pTrigger = 0; + regTrigCnt = sqlite3FkRequired(pParse, pTab, 0, 0); + } + if( regTrigCnt ){ + /* Replace triggers might exist. Allocate the counter and + ** initialize it to zero. */ + regTrigCnt = ++pParse->nMem; + sqlite3VdbeAddOp2(v, OP_Integer, 0, regTrigCnt); + VdbeComment((v, "trigger count")); + lblRecheckOk = sqlite3VdbeMakeLabel(pParse); + addrRecheck = lblRecheckOk; } } @@ -117939,11 +141153,20 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( } /* figure out whether or not upsert applies in this case */ - if( pUpsert && pUpsert->pUpsertIdx==0 ){ - if( pUpsert->pUpsertSet==0 ){ - onError = OE_Ignore; /* DO NOTHING is the same as INSERT OR IGNORE */ - }else{ - onError = OE_Update; /* DO UPDATE */ + if( pUpsert ){ + pUpsertClause = sqlite3UpsertOfIndex(pUpsert,0); + if( pUpsertClause!=0 ){ + if( pUpsertClause->isDoUpdate==0 ){ + onError = OE_Ignore; /* DO NOTHING is the same as INSERT OR IGNORE */ + }else{ + onError = OE_Update; /* DO UPDATE */ + } + } + if( pUpsertClause!=pUpsert ){ + /* The first ON CONFLICT clause has a conflict target other than + ** the IPK. We have to jump ahead to that first ON CONFLICT clause + ** and then come back here and deal with the IPK afterwards */ + upsertIpkDelay = sqlite3VdbeAddOp0(v, OP_Goto); } } @@ -117953,8 +141176,9 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( ** the UNIQUE constraints have run. */ if( onError==OE_Replace /* IPK rule is REPLACE */ - && onError!=overrideError /* Rules for other contraints are different */ + && onError!=overrideError /* Rules for other constraints are different */ && pTab->pIndex /* There exist other constraints */ + && !upsertIpkDelay /* IPK check already deferred by UPSERT */ ){ ipkTop = sqlite3VdbeAddOp0(v, OP_Goto)+1; VdbeComment((v, "defer IPK REPLACE until last")); @@ -117979,7 +141203,7 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( switch( onError ){ default: { onError = OE_Abort; - /* Fall thru into the next case */ + /* no break */ deliberate_fall_through } case OE_Rollback: case OE_Abort: @@ -117997,10 +141221,10 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( ** the triggers and remove both the table and index b-tree entries. ** ** Otherwise, if there are no triggers or the recursive-triggers - ** flag is not set, but the table has one or more indexes, call - ** GenerateRowIndexDelete(). This removes the index b-tree entries - ** only. The table b-tree entry will be replaced by the new entry - ** when it is inserted. + ** flag is not set, but the table has one or more indexes, call + ** GenerateRowIndexDelete(). This removes the index b-tree entries + ** only. The table b-tree entry will be replaced by the new entry + ** when it is inserted. ** ** If either GenerateRowDelete() or GenerateRowIndexDelete() is called, ** also invoke MultiWrite() to indicate that this VDBE may require @@ -118013,14 +141237,12 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( ** to run without a statement journal if there are no indexes on the ** table. */ - Trigger *pTrigger = 0; - if( db->flags&SQLITE_RecTriggers ){ - pTrigger = sqlite3TriggersExist(pParse, pTab, TK_DELETE, 0, 0); - } - if( pTrigger || sqlite3FkRequired(pParse, pTab, 0, 0) ){ + if( regTrigCnt ){ sqlite3MultiWrite(pParse); sqlite3GenerateRowDelete(pParse, pTab, pTrigger, iDataCur, iIdxCur, regNewData, 1, 0, OE_Replace, 1, -1); + sqlite3VdbeAddOp2(v, OP_AddImm, regTrigCnt, 1); /* incr trigger cnt */ + nReplaceTrig++; }else{ #ifdef SQLITE_ENABLE_PREUPDATE_HOOK assert( HasRowid(pTab) ); @@ -118042,7 +141264,7 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( #ifndef SQLITE_OMIT_UPSERT case OE_Update: { sqlite3UpsertDoUpdate(pParse, pUpsert, pTab, 0, iDataCur); - /* Fall through */ + /* no break */ deliberate_fall_through } #endif case OE_Ignore: { @@ -118052,7 +141274,9 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( } } sqlite3VdbeResolveLabel(v, addrRowidOk); - if( ipkTop ){ + if( pUpsert && pUpsertClause!=pUpsert ){ + upsertIpkReturn = sqlite3VdbeAddOp0(v, OP_Goto); + }else if( ipkTop ){ ipkBottom = sqlite3VdbeAddOp0(v, OP_Goto); sqlite3VdbeJumpHere(v, ipkTop-1); } @@ -118065,26 +141289,29 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( ** This loop also handles the case of the PRIMARY KEY index for a ** WITHOUT ROWID table. */ - for(ix=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, ix++){ - int regIdx; /* Range of registers hold conent for pIdx */ + for(pIdx = indexIteratorFirst(&sIdxIter, &ix); + pIdx; + pIdx = indexIteratorNext(&sIdxIter, &ix) + ){ + int regIdx; /* Range of registers holding content for pIdx */ int regR; /* Range of registers holding conflicting PK */ int iThisCur; /* Cursor for this UNIQUE index */ int addrUniqueOk; /* Jump here if the UNIQUE constraint is satisfied */ + int addrConflictCk; /* First opcode in the conflict check logic */ if( aRegIdx[ix]==0 ) continue; /* Skip indices that do not change */ - if( pUpIdx==pIdx ){ - addrUniqueOk = upsertJump+1; - upsertBypass = sqlite3VdbeGoto(v, 0); - VdbeComment((v, "Skip upsert subroutine")); - sqlite3VdbeJumpHere(v, upsertJump); - }else{ - addrUniqueOk = sqlite3VdbeMakeLabel(pParse); + if( pUpsert ){ + pUpsertClause = sqlite3UpsertOfIndex(pUpsert, pIdx); + if( upsertIpkDelay && pUpsertClause==pUpsert ){ + sqlite3VdbeJumpHere(v, upsertIpkDelay); + } } - if( bAffinityDone==0 && (pUpIdx==0 || pUpIdx==pIdx) ){ + addrUniqueOk = sqlite3VdbeMakeLabel(pParse); + if( bAffinityDone==0 ){ sqlite3TableAffinity(v, pTab, regNewData+1); bAffinityDone = 1; } - VdbeNoopComment((v, "uniqueness check for %s", pIdx->zName)); + VdbeNoopComment((v, "prep index %s", pIdx->zName)); iThisCur = iIdxCur+ix; @@ -118109,14 +141336,15 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( sqlite3ExprCodeCopy(pParse, pIdx->aColExpr->a[i].pExpr, regIdx+i); pParse->iSelfTab = 0; VdbeComment((v, "%s column %d", pIdx->zName, i)); + }else if( iField==XN_ROWID || iField==pTab->iPKey ){ + x = regNewData; + sqlite3VdbeAddOp2(v, OP_IntCopy, x, regIdx+i); + VdbeComment((v, "rowid")); }else{ - if( iField==XN_ROWID || iField==pTab->iPKey ){ - x = regNewData; - }else{ - x = iField + regNewData + 1; - } - sqlite3VdbeAddOp2(v, iField<0 ? OP_IntCopy : OP_SCopy, x, regIdx+i); - VdbeComment((v, "%s", iField<0 ? "rowid" : pTab->aCol[iField].zName)); + testcase( sqlite3TableColumnToStorage(pTab, iField)!=iField ); + x = sqlite3TableColumnToStorage(pTab, iField) + regNewData + 1; + sqlite3VdbeAddOp2(v, OP_SCopy, x, regIdx+i); + VdbeComment((v, "%s", pTab->aCol[iField].zCnName)); } } sqlite3VdbeAddOp3(v, OP_MakeRecord, regIdx, pIdx->nColumn, aRegIdx[ix]); @@ -118126,8 +141354,9 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( sqlite3SetMakeRecordP5(v, pIdx->pTable); } #endif + sqlite3VdbeReleaseRegisters(pParse, regIdx, pIdx->nColumn, 0, 0); - /* In an UPDATE operation, if this index is the PRIMARY KEY index + /* In an UPDATE operation, if this index is the PRIMARY KEY index ** of a WITHOUT ROWID table and there has been no change the ** primary key, then no collision is possible. The collision detection ** logic below can all be skipped. */ @@ -118138,7 +141367,7 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( /* Find out what action to take in case there is a uniqueness conflict */ onError = pIdx->onError; - if( onError==OE_None ){ + if( onError==OE_None ){ sqlite3VdbeResolveLabel(v, addrUniqueOk); continue; /* pIdx is not a UNIQUE index */ } @@ -118149,8 +141378,8 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( } /* Figure out if the upsert clause applies to this index */ - if( pUpIdx==pIdx ){ - if( pUpsert->pUpsertSet==0 ){ + if( pUpsertClause ){ + if( pUpsertClause->isDoUpdate==0 ){ onError = OE_Ignore; /* DO NOTHING is the same as INSERT OR IGNORE */ }else{ onError = OE_Update; /* DO UPDATE */ @@ -118166,7 +141395,8 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( ** ** This is not possible for ENABLE_PREUPDATE_HOOK builds, as the row ** must be explicitly deleted in order to ensure any pre-update hook - ** is invoked. */ + ** is invoked. */ + assert( IsOrdinaryTable(pTab) ); #ifndef SQLITE_ENABLE_PREUPDATE_HOOK if( (ix==0 && pIdx->pNext==0) /* Condition 3 */ && pPk==pIdx /* Condition 2 */ @@ -118174,7 +141404,7 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( && ( 0==(db->flags&SQLITE_RecTriggers) || /* Condition 4 */ 0==sqlite3TriggersExist(pParse, pTab, TK_DELETE, 0, 0)) && ( 0==(db->flags&SQLITE_ForeignKeys) || /* Condition 5 */ - (0==pTab->pFKey && 0==sqlite3FkReferences(pTab))) + (0==pTab->u.tab.pFKey && 0==sqlite3FkReferences(pTab))) ){ sqlite3VdbeResolveLabel(v, addrUniqueOk); continue; @@ -118183,11 +141413,12 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( /* Check to see if the new index entry will be unique */ sqlite3VdbeVerifyAbortable(v, onError); - sqlite3VdbeAddOp4Int(v, OP_NoConflict, iThisCur, addrUniqueOk, - regIdx, pIdx->nKeyCol); VdbeCoverage(v); + addrConflictCk = + sqlite3VdbeAddOp4Int(v, OP_NoConflict, iThisCur, addrUniqueOk, + regIdx, pIdx->nKeyCol); VdbeCoverage(v); /* Generate code to handle collisions */ - regR = (pIdx==pPk) ? regIdx : sqlite3GetTempRange(pParse, nPkField); + regR = pIdx==pPk ? regIdx : sqlite3GetTempRange(pParse, nPkField); if( isUpdate || onError==OE_Replace ){ if( HasRowid(pTab) ){ sqlite3VdbeAddOp2(v, OP_IdxRowid, iThisCur, regR); @@ -118205,16 +141436,16 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( if( pIdx!=pPk ){ for(i=0; inKeyCol; i++){ assert( pPk->aiColumn[i]>=0 ); - x = sqlite3ColumnOfIndex(pIdx, pPk->aiColumn[i]); + x = sqlite3TableColumnToIndex(pIdx, pPk->aiColumn[i]); sqlite3VdbeAddOp3(v, OP_Column, iThisCur, x, regR+i); VdbeComment((v, "%s.%s", pTab->zName, - pTab->aCol[pPk->aiColumn[i]].zName)); + pTab->aCol[pPk->aiColumn[i]].zCnName)); } } if( isUpdate ){ - /* If currently processing the PRIMARY KEY of a WITHOUT ROWID + /* If currently processing the PRIMARY KEY of a WITHOUT ROWID ** table, only conflict if the new PRIMARY KEY values are actually - ** different from the old. + ** different from the old. See TH3 withoutrowid04.test. ** ** For a UNIQUE index, only conflict if the PRIMARY KEY values ** of the matched index row are different from the original PRIMARY @@ -118222,7 +141453,7 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( int addrJump = sqlite3VdbeCurrentAddr(v)+pPk->nKeyCol; int op = OP_Ne; int regCmp = (IsPrimaryKeyIndex(pIdx) ? regIdx : regR); - + for(i=0; inKeyCol; i++){ char *p4 = (char*)sqlite3LocateCollSeq(pParse, pPk->azColl[i]); x = pPk->aiColumn[i]; @@ -118231,7 +141462,8 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( addrJump = addrUniqueOk; op = OP_Eq; } - sqlite3VdbeAddOp4(v, op, + x = sqlite3TableColumnToStorage(pTab, x); + sqlite3VdbeAddOp4(v, op, regOldData+1+x, addrJump, regCmp+i, p4, P4_COLLSEQ ); sqlite3VdbeChangeP5(v, SQLITE_NOTNULL); @@ -118258,7 +141490,7 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( #ifndef SQLITE_OMIT_UPSERT case OE_Update: { sqlite3UpsertDoUpdate(pParse, pUpsert, pTab, pIdx, iIdxCur+ix); - /* Fall through */ + /* no break */ deliberate_fall_through } #endif case OE_Ignore: { @@ -118267,37 +141499,128 @@ SQLITE_PRIVATE void sqlite3GenerateConstraintChecks( break; } default: { - Trigger *pTrigger = 0; + int nConflictCk; /* Number of opcodes in conflict check logic */ + assert( onError==OE_Replace ); - if( db->flags&SQLITE_RecTriggers ){ - pTrigger = sqlite3TriggersExist(pParse, pTab, TK_DELETE, 0, 0); - } - if( pTrigger || sqlite3FkRequired(pParse, pTab, 0, 0) ){ + nConflictCk = sqlite3VdbeCurrentAddr(v) - addrConflictCk; + assert( nConflictCk>0 || db->mallocFailed ); + testcase( nConflictCk<=0 ); + testcase( nConflictCk>1 ); + if( regTrigCnt ){ sqlite3MultiWrite(pParse); + nReplaceTrig++; + } + if( pTrigger && isUpdate ){ + sqlite3VdbeAddOp1(v, OP_CursorLock, iDataCur); } sqlite3GenerateRowDelete(pParse, pTab, pTrigger, iDataCur, iIdxCur, regR, nPkField, 0, OE_Replace, (pIdx==pPk ? ONEPASS_SINGLE : ONEPASS_OFF), iThisCur); + if( pTrigger && isUpdate ){ + sqlite3VdbeAddOp1(v, OP_CursorUnlock, iDataCur); + } + if( regTrigCnt ){ + int addrBypass; /* Jump destination to bypass recheck logic */ + + sqlite3VdbeAddOp2(v, OP_AddImm, regTrigCnt, 1); /* incr trigger cnt */ + addrBypass = sqlite3VdbeAddOp0(v, OP_Goto); /* Bypass recheck */ + VdbeComment((v, "bypass recheck")); + + /* Here we insert code that will be invoked after all constraint + ** checks have run, if and only if one or more replace triggers + ** fired. */ + sqlite3VdbeResolveLabel(v, lblRecheckOk); + lblRecheckOk = sqlite3VdbeMakeLabel(pParse); + if( pIdx->pPartIdxWhere ){ + /* Bypass the recheck if this partial index is not defined + ** for the current row */ + sqlite3VdbeAddOp2(v, OP_IsNull, regIdx-1, lblRecheckOk); + VdbeCoverage(v); + } + /* Copy the constraint check code from above, except change + ** the constraint-ok jump destination to be the address of + ** the next retest block */ + while( nConflictCk>0 ){ + VdbeOp x; /* Conflict check opcode to copy */ + /* The sqlite3VdbeAddOp4() call might reallocate the opcode array. + ** Hence, make a complete copy of the opcode, rather than using + ** a pointer to the opcode. */ + x = *sqlite3VdbeGetOp(v, addrConflictCk); + if( x.opcode!=OP_IdxRowid ){ + int p2; /* New P2 value for copied conflict check opcode */ + const char *zP4; + if( sqlite3OpcodeProperty[x.opcode]&OPFLG_JUMP ){ + p2 = lblRecheckOk; + }else{ + p2 = x.p2; + } + zP4 = x.p4type==P4_INT32 ? SQLITE_INT_TO_PTR(x.p4.i) : x.p4.z; + sqlite3VdbeAddOp4(v, x.opcode, x.p1, p2, x.p3, zP4, x.p4type); + sqlite3VdbeChangeP5(v, x.p5); + VdbeCoverageIf(v, p2!=x.p2); + } + nConflictCk--; + addrConflictCk++; + } + /* If the retest fails, issue an abort */ + sqlite3UniqueConstraint(pParse, OE_Abort, pIdx); + + sqlite3VdbeJumpHere(v, addrBypass); /* Terminate the recheck bypass */ + } seenReplace = 1; break; } } - if( pUpIdx==pIdx ){ - sqlite3VdbeGoto(v, upsertJump+1); - sqlite3VdbeJumpHere(v, upsertBypass); - }else{ - sqlite3VdbeResolveLabel(v, addrUniqueOk); - } + sqlite3VdbeResolveLabel(v, addrUniqueOk); if( regR!=regIdx ) sqlite3ReleaseTempRange(pParse, regR, nPkField); + if( pUpsertClause + && upsertIpkReturn + && sqlite3UpsertNextIsIPK(pUpsertClause) + ){ + sqlite3VdbeGoto(v, upsertIpkDelay+1); + sqlite3VdbeJumpHere(v, upsertIpkReturn); + upsertIpkReturn = 0; + } } /* If the IPK constraint is a REPLACE, run it last */ if( ipkTop ){ sqlite3VdbeGoto(v, ipkTop); VdbeComment((v, "Do IPK REPLACE")); + assert( ipkBottom>0 ); sqlite3VdbeJumpHere(v, ipkBottom); } + /* Recheck all uniqueness constraints after replace triggers have run */ + testcase( regTrigCnt!=0 && nReplaceTrig==0 ); + assert( regTrigCnt!=0 || nReplaceTrig==0 ); + if( nReplaceTrig ){ + sqlite3VdbeAddOp2(v, OP_IfNot, regTrigCnt, lblRecheckOk);VdbeCoverage(v); + if( !pPk ){ + if( isUpdate ){ + sqlite3VdbeAddOp3(v, OP_Eq, regNewData, addrRecheck, regOldData); + sqlite3VdbeChangeP5(v, SQLITE_NOTNULL); + VdbeCoverage(v); + } + sqlite3VdbeAddOp3(v, OP_NotExists, iDataCur, addrRecheck, regNewData); + VdbeCoverage(v); + sqlite3RowidConstraint(pParse, OE_Abort, pTab); + }else{ + sqlite3VdbeGoto(v, addrRecheck); + } + sqlite3VdbeResolveLabel(v, lblRecheckOk); + } + + /* Generate the table record */ + if( HasRowid(pTab) ){ + int regRec = aRegIdx[ix]; + sqlite3VdbeAddOp3(v, OP_MakeRecord, regNewData+1, pTab->nNVCol, regRec); + sqlite3SetMakeRecordP5(v, pTab); + if( !bAffinityDone ){ + sqlite3TableAffinity(v, pTab, 0); + } + } + *pbMayReplace = seenReplace; VdbeModuleComment((v, "END: GenCnstCks(%d)", seenReplace)); } @@ -118317,13 +141640,39 @@ SQLITE_PRIVATE void sqlite3SetMakeRecordP5(Vdbe *v, Table *pTab){ if( pTab->pSchema->file_format<2 ) return; for(i=pTab->nCol-1; i>0; i--){ - if( pTab->aCol[i].pDflt!=0 ) break; + if( pTab->aCol[i].iDflt!=0 ) break; if( pTab->aCol[i].colFlags & COLFLAG_PRIMKEY ) break; } sqlite3VdbeChangeP5(v, i+1); } #endif +/* +** Table pTab is a WITHOUT ROWID table that is being written to. The cursor +** number is iCur, and register regData contains the new record for the +** PK index. This function adds code to invoke the pre-update hook, +** if one is registered. +*/ +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK +static void codeWithoutRowidPreupdate( + Parse *pParse, /* Parse context */ + Table *pTab, /* Table being updated */ + int iCur, /* Cursor number for table */ + int regData /* Data containing new record */ +){ + Vdbe *v = pParse->pVdbe; + int r = sqlite3GetTempReg(pParse); + assert( !HasRowid(pTab) ); + assert( 0==(pParse->db->mDbFlags & DBFLAG_Vacuum) || CORRUPT_DB ); + sqlite3VdbeAddOp2(v, OP_Integer, 0, r); + sqlite3VdbeAddOp4(v, OP_Insert, iCur, regData, r, (char*)pTab, P4_TABLE); + sqlite3VdbeChangeP5(v, OPFLAG_ISNOOP); + sqlite3ReleaseTempReg(pParse, r); +} +#else +# define codeWithoutRowidPreupdate(a,b,c,d) +#endif + /* ** This routine generates code to finish the INSERT or UPDATE operation ** that was started by a prior call to sqlite3GenerateConstraintChecks. @@ -118347,42 +141696,33 @@ SQLITE_PRIVATE void sqlite3CompleteInsertion( Vdbe *v; /* Prepared statements under construction */ Index *pIdx; /* An index being inserted or updated */ u8 pik_flags; /* flag values passed to the btree insert */ - int regData; /* Content registers (after the rowid) */ - int regRec; /* Register holding assembled record for the table */ int i; /* Loop counter */ - u8 bAffinityDone = 0; /* True if OP_Affinity has been run already */ assert( update_flags==0 || update_flags==OPFLAG_ISUPDATE || update_flags==(OPFLAG_ISUPDATE|OPFLAG_SAVEPOSITION) ); - v = sqlite3GetVdbe(pParse); + v = pParse->pVdbe; assert( v!=0 ); - assert( pTab->pSelect==0 ); /* This table is not a VIEW */ + assert( !IsView(pTab) ); /* This table is not a VIEW */ for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){ + /* All REPLACE indexes are at the end of the list */ + assert( pIdx->onError!=OE_Replace + || pIdx->pNext==0 + || pIdx->pNext->onError==OE_Replace ); if( aRegIdx[i]==0 ) continue; - bAffinityDone = 1; if( pIdx->pPartIdxWhere ){ sqlite3VdbeAddOp2(v, OP_IsNull, aRegIdx[i], sqlite3VdbeCurrentAddr(v)+2); VdbeCoverage(v); } pik_flags = (useSeekResult ? OPFLAG_USESEEKRESULT : 0); if( IsPrimaryKeyIndex(pIdx) && !HasRowid(pTab) ){ - assert( pParse->nested==0 ); pik_flags |= OPFLAG_NCHANGE; pik_flags |= (update_flags & OPFLAG_SAVEPOSITION); -#ifdef SQLITE_ENABLE_PREUPDATE_HOOK if( update_flags==0 ){ - int r = sqlite3GetTempReg(pParse); - sqlite3VdbeAddOp2(v, OP_Integer, 0, r); - sqlite3VdbeAddOp4(v, OP_Insert, - iIdxCur+i, aRegIdx[i], r, (char*)pTab, P4_TABLE - ); - sqlite3VdbeChangeP5(v, OPFLAG_ISNOOP); - sqlite3ReleaseTempReg(pParse, r); + codeWithoutRowidPreupdate(pParse, pTab, iIdxCur+i, aRegIdx[i]); } -#endif } sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iIdxCur+i, aRegIdx[i], aRegIdx[i]+1, @@ -118390,13 +141730,6 @@ SQLITE_PRIVATE void sqlite3CompleteInsertion( sqlite3VdbeChangeP5(v, pik_flags); } if( !HasRowid(pTab) ) return; - regData = regNewData + 1; - regRec = sqlite3GetTempReg(pParse); - sqlite3VdbeAddOp3(v, OP_MakeRecord, regData, pTab->nCol, regRec); - sqlite3SetMakeRecordP5(v, pTab); - if( !bAffinityDone ){ - sqlite3TableAffinity(v, pTab, 0); - } if( pParse->nested ){ pik_flags = 0; }else{ @@ -118409,7 +141742,7 @@ SQLITE_PRIVATE void sqlite3CompleteInsertion( if( useSeekResult ){ pik_flags |= OPFLAG_USESEEKRESULT; } - sqlite3VdbeAddOp3(v, OP_Insert, iDataCur, regRec, regNewData); + sqlite3VdbeAddOp3(v, OP_Insert, iDataCur, aRegIdx[i], regNewData); if( !pParse->nested ){ sqlite3VdbeAppendP4(v, pTab, P4_TABLE); } @@ -118455,29 +141788,32 @@ SQLITE_PRIVATE int sqlite3OpenTableAndIndices( assert( op==OP_OpenRead || op==OP_OpenWrite ); assert( op==OP_OpenWrite || p5==0 ); + assert( piDataCur!=0 ); + assert( piIdxCur!=0 ); if( IsVirtual(pTab) ){ /* This routine is a no-op for virtual tables. Leave the output - ** variables *piDataCur and *piIdxCur uninitialized so that valgrind - ** can detect if they are used by mistake in the caller. */ + ** variables *piDataCur and *piIdxCur set to illegal cursor numbers + ** for improved error detection. */ + *piDataCur = *piIdxCur = -999; return 0; } iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema); - v = sqlite3GetVdbe(pParse); + v = pParse->pVdbe; assert( v!=0 ); if( iBase<0 ) iBase = pParse->nTab; iDataCur = iBase++; - if( piDataCur ) *piDataCur = iDataCur; + *piDataCur = iDataCur; if( HasRowid(pTab) && (aToOpen==0 || aToOpen[0]) ){ sqlite3OpenTable(pParse, iDataCur, iDb, pTab, op); - }else{ + }else if( pParse->db->noSharedCache==0 ){ sqlite3TableLock(pParse, iDb, pTab->tnum, op==OP_OpenWrite, pTab->zName); } - if( piIdxCur ) *piIdxCur = iBase; + *piIdxCur = iBase; for(i=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, i++){ int iIdxCur = iBase++; assert( pIdx->pSchema==pTab->pSchema ); if( IsPrimaryKeyIndex(pIdx) && !HasRowid(pTab) ){ - if( piDataCur ) *piDataCur = iIdxCur; + *piDataCur = iIdxCur; p5 = 0; } if( aToOpen==0 || aToOpen[i+1] ){ @@ -118519,7 +141855,7 @@ static int xferCompatibleIndex(Index *pDest, Index *pSrc){ int i; assert( pDest && pSrc ); assert( pDest->pTable!=pSrc->pTable ); - if( pDest->nKeyCol!=pSrc->nKeyCol ){ + if( pDest->nKeyCol!=pSrc->nKeyCol || pDest->nColumn!=pSrc->nColumn ){ return 0; /* Different number of columns */ } if( pDest->onError!=pSrc->onError ){ @@ -118556,7 +141892,7 @@ static int xferCompatibleIndex(Index *pDest, Index *pSrc){ ** ** INSERT INTO tab1 SELECT * FROM tab2; ** -** The xfer optimization transfers raw records from tab2 over to tab1. +** The xfer optimization transfers raw records from tab2 over to tab1. ** Columns are not decoded and reassembled, which greatly improves ** performance. Raw index records are transferred in the same way. ** @@ -118587,7 +141923,7 @@ static int xferOptimization( ExprList *pEList; /* The result set of the SELECT */ Table *pSrc; /* The table in the FROM clause of SELECT */ Index *pSrcIdx, *pDestIdx; /* Source and destination indices */ - struct SrcList_item *pItem; /* An element of pSelect->pSrc */ + SrcItem *pItem; /* An element of pSelect->pSrc */ int i; /* Loop counter */ int iDbSrc; /* The database of pSrc */ int iSrc, iDest; /* Cursors from source and destination */ @@ -118599,18 +141935,13 @@ static int xferOptimization( int destHasUniqueIdx = 0; /* True if pDest has a UNIQUE index */ int regData, regRowid; /* Registers holding data and rowid */ - if( pSelect==0 ){ - return 0; /* Must be of the form INSERT INTO ... SELECT ... */ - } + assert( pSelect!=0 ); if( pParse->pWith || pSelect->pWith ){ /* Do not attempt to process this query if there are an WITH clauses ** attached to it. Proceeding may generate a false "no such table: xxx" ** error if pSelect reads from a CTE named "xxx". */ return 0; } - if( sqlite3TriggerList(pParse, pDest) ){ - return 0; /* tab1 must not have triggers */ - } #ifndef SQLITE_OMIT_VIRTUALTABLE if( IsVirtual(pDest) ){ return 0; /* tab1 must not be a virtual table */ @@ -118624,7 +141955,7 @@ static int xferOptimization( if( pSelect->pSrc->nSrc!=1 ){ return 0; /* FROM clause must have exactly one term */ } - if( pSelect->pSrc->a[0].pSelect ){ + if( pSelect->pSrc->a[0].fg.isSubquery ){ return 0; /* FROM clause cannot contain a subquery */ } if( pSelect->pWhere ){ @@ -118667,19 +141998,14 @@ static int xferOptimization( return 0; /* FROM clause does not contain a real table */ } if( pSrc->tnum==pDest->tnum && pSrc->pSchema==pDest->pSchema ){ - testcase( pSrc!=pDest ); /* Possible due to bad sqlite_master.rootpage */ + testcase( pSrc!=pDest ); /* Possible due to bad sqlite_schema.rootpage */ return 0; /* tab1 and tab2 may not be the same table */ } if( HasRowid(pDest)!=HasRowid(pSrc) ){ return 0; /* source and destination must both be WITHOUT ROWID or not */ } -#ifndef SQLITE_OMIT_VIRTUALTABLE - if( IsVirtual(pSrc) ){ - return 0; /* tab2 must not be a virtual table */ - } -#endif - if( pSrc->pSelect ){ - return 0; /* tab2 may not be a view */ + if( !IsOrdinaryTable(pSrc) ){ + return 0; /* tab2 may not be a view or virtual table */ } if( pDest->nCol!=pSrc->nCol ){ return 0; /* Number of columns must be the same in tab1 and tab2 */ @@ -118687,32 +142013,75 @@ static int xferOptimization( if( pDest->iPKey!=pSrc->iPKey ){ return 0; /* Both tables must have the same INTEGER PRIMARY KEY */ } + if( (pDest->tabFlags & TF_Strict)!=0 && (pSrc->tabFlags & TF_Strict)==0 ){ + return 0; /* Cannot feed from a non-strict into a strict table */ + } for(i=0; inCol; i++){ Column *pDestCol = &pDest->aCol[i]; Column *pSrcCol = &pSrc->aCol[i]; #ifdef SQLITE_ENABLE_HIDDEN_COLUMNS - if( (db->mDbFlags & DBFLAG_Vacuum)==0 - && (pDestCol->colFlags | pSrcCol->colFlags) & COLFLAG_HIDDEN + if( (db->mDbFlags & DBFLAG_Vacuum)==0 + && (pDestCol->colFlags | pSrcCol->colFlags) & COLFLAG_HIDDEN ){ return 0; /* Neither table may have __hidden__ columns */ } +#endif +#ifndef SQLITE_OMIT_GENERATED_COLUMNS + /* Even if tables t1 and t2 have identical schemas, if they contain + ** generated columns, then this statement is semantically incorrect: + ** + ** INSERT INTO t2 SELECT * FROM t1; + ** + ** The reason is that generated column values are returned by the + ** the SELECT statement on the right but the INSERT statement on the + ** left wants them to be omitted. + ** + ** Nevertheless, this is a useful notational shorthand to tell SQLite + ** to do a bulk transfer all of the content from t1 over to t2. + ** + ** We could, in theory, disable this (except for internal use by the + ** VACUUM command where it is actually needed). But why do that? It + ** seems harmless enough, and provides a useful service. + */ + if( (pDestCol->colFlags & COLFLAG_GENERATED) != + (pSrcCol->colFlags & COLFLAG_GENERATED) ){ + return 0; /* Both columns have the same generated-column type */ + } + /* But the transfer is only allowed if both the source and destination + ** tables have the exact same expressions for generated columns. + ** This requirement could be relaxed for VIRTUAL columns, I suppose. + */ + if( (pDestCol->colFlags & COLFLAG_GENERATED)!=0 ){ + if( sqlite3ExprCompare(0, + sqlite3ColumnExpr(pSrc, pSrcCol), + sqlite3ColumnExpr(pDest, pDestCol), -1)!=0 ){ + testcase( pDestCol->colFlags & COLFLAG_VIRTUAL ); + testcase( pDestCol->colFlags & COLFLAG_STORED ); + return 0; /* Different generator expressions */ + } + } #endif if( pDestCol->affinity!=pSrcCol->affinity ){ return 0; /* Affinity must be the same on all columns */ } - if( sqlite3_stricmp(pDestCol->zColl, pSrcCol->zColl)!=0 ){ + if( sqlite3_stricmp(sqlite3ColumnColl(pDestCol), + sqlite3ColumnColl(pSrcCol))!=0 ){ return 0; /* Collating sequence must be the same on all columns */ } if( pDestCol->notNull && !pSrcCol->notNull ){ return 0; /* tab2 must be NOT NULL if tab1 is */ } /* Default values for second and subsequent columns need to match. */ - if( i>0 ){ - assert( pDestCol->pDflt==0 || pDestCol->pDflt->op==TK_SPAN ); - assert( pSrcCol->pDflt==0 || pSrcCol->pDflt->op==TK_SPAN ); - if( (pDestCol->pDflt==0)!=(pSrcCol->pDflt==0) - || (pDestCol->pDflt && strcmp(pDestCol->pDflt->u.zToken, - pSrcCol->pDflt->u.zToken)!=0) + if( (pDestCol->colFlags & COLFLAG_GENERATED)==0 && i>0 ){ + Expr *pDestExpr = sqlite3ColumnExpr(pDest, pDestCol); + Expr *pSrcExpr = sqlite3ColumnExpr(pSrc, pSrcCol); + assert( pDestExpr==0 || pDestExpr->op==TK_SPAN ); + assert( pDestExpr==0 || !ExprHasProperty(pDestExpr, EP_IntValue) ); + assert( pSrcExpr==0 || pSrcExpr->op==TK_SPAN ); + assert( pSrcExpr==0 || !ExprHasProperty(pSrcExpr, EP_IntValue) ); + if( (pDestExpr==0)!=(pSrcExpr==0) + || (pDestExpr!=0 && strcmp(pDestExpr->u.zToken, + pSrcExpr->u.zToken)!=0) ){ return 0; /* Default values must be the same for all columns */ } @@ -118737,19 +142106,23 @@ static int xferOptimization( } } #ifndef SQLITE_OMIT_CHECK - if( pDest->pCheck && sqlite3ExprListCompare(pSrc->pCheck,pDest->pCheck,-1) ){ + if( pDest->pCheck + && (db->mDbFlags & DBFLAG_Vacuum)==0 + && sqlite3ExprListCompare(pSrc->pCheck,pDest->pCheck,-1) + ){ return 0; /* Tables have different CHECK constraints. Ticket #2252 */ } #endif #ifndef SQLITE_OMIT_FOREIGN_KEY - /* Disallow the transfer optimization if the destination table constains + /* Disallow the transfer optimization if the destination table contains ** any foreign key constraints. This is more restrictive than necessary. - ** But the main beneficiary of the transfer optimization is the VACUUM + ** But the main beneficiary of the transfer optimization is the VACUUM ** command, and the VACUUM command disables foreign key constraints. So ** the extra complication to make this rule less restrictive is probably ** not worth the effort. Ticket [6284df89debdfa61db8073e062908af0c9b6118e] */ - if( (db->flags & SQLITE_ForeignKeys)!=0 && pDest->pFKey!=0 ){ + assert( IsOrdinaryTable(pDest) ); + if( (db->flags & SQLITE_ForeignKeys)!=0 && pDest->u.tab.pFKey!=0 ){ return 0; } #endif @@ -118771,6 +142144,7 @@ static int xferOptimization( iDest = pParse->nTab++; regAutoinc = autoIncBegin(pParse, iDbDest, pDest); regData = sqlite3GetTempReg(pParse); + sqlite3VdbeAddOp2(v, OP_Null, 0, regData); regRowid = sqlite3GetTempReg(pParse); sqlite3OpenTable(pParse, iDest, iDbDest, pDest, OP_OpenWrite); assert( HasRowid(pDest) || destHasUniqueIdx ); @@ -118791,7 +142165,7 @@ static int xferOptimization( ** (If the destination is not initially empty, the rowid fields ** of index entries might need to change.) ** - ** (2) The destination has a unique index. (The xfer optimization + ** (2) The destination has a unique index. (The xfer optimization ** is unable to test uniqueness.) ** ** (3) onError is something other than OE_Abort and OE_Rollback. @@ -118806,11 +142180,13 @@ static int xferOptimization( emptySrcTest = sqlite3VdbeAddOp2(v, OP_Rewind, iSrc, 0); VdbeCoverage(v); if( pDest->iPKey>=0 ){ addr1 = sqlite3VdbeAddOp2(v, OP_Rowid, iSrc, regRowid); - sqlite3VdbeVerifyAbortable(v, onError); - addr2 = sqlite3VdbeAddOp3(v, OP_NotExists, iDest, 0, regRowid); - VdbeCoverage(v); - sqlite3RowidConstraint(pParse, onError, pDest); - sqlite3VdbeJumpHere(v, addr2); + if( (db->mDbFlags & DBFLAG_Vacuum)==0 ){ + sqlite3VdbeVerifyAbortable(v, onError); + addr2 = sqlite3VdbeAddOp3(v, OP_NotExists, iDest, 0, regRowid); + VdbeCoverage(v); + sqlite3RowidConstraint(pParse, onError, pDest); + sqlite3VdbeJumpHere(v, addr2); + } autoIncStep(pParse, regAutoinc, regRowid); }else if( pDest->pIndex==0 && !(db->mDbFlags & DBFLAG_VacuumInto) ){ addr1 = sqlite3VdbeAddOp2(v, OP_NewRowid, iDest, regRowid); @@ -118818,17 +142194,28 @@ static int xferOptimization( addr1 = sqlite3VdbeAddOp2(v, OP_Rowid, iSrc, regRowid); assert( (pDest->tabFlags & TF_Autoincrement)==0 ); } - sqlite3VdbeAddOp3(v, OP_RowData, iSrc, regData, 1); + if( db->mDbFlags & DBFLAG_Vacuum ){ sqlite3VdbeAddOp1(v, OP_SeekEnd, iDest); - insFlags = OPFLAG_NCHANGE|OPFLAG_LASTROWID| - OPFLAG_APPEND|OPFLAG_USESEEKRESULT; + insFlags = OPFLAG_APPEND|OPFLAG_USESEEKRESULT|OPFLAG_PREFORMAT; }else{ - insFlags = OPFLAG_NCHANGE|OPFLAG_LASTROWID|OPFLAG_APPEND; + insFlags = OPFLAG_NCHANGE|OPFLAG_LASTROWID|OPFLAG_APPEND|OPFLAG_PREFORMAT; + } +#ifdef SQLITE_ENABLE_PREUPDATE_HOOK + if( (db->mDbFlags & DBFLAG_Vacuum)==0 ){ + sqlite3VdbeAddOp3(v, OP_RowData, iSrc, regData, 1); + insFlags &= ~OPFLAG_PREFORMAT; + }else +#endif + { + sqlite3VdbeAddOp3(v, OP_RowCell, iDest, iSrc, regRowid); + } + sqlite3VdbeAddOp3(v, OP_Insert, iDest, regData, regRowid); + if( (db->mDbFlags & DBFLAG_Vacuum)==0 ){ + sqlite3VdbeChangeP4(v, -1, (char*)pDest, P4_TABLE); } - sqlite3VdbeAddOp4(v, OP_Insert, iDest, regData, regRowid, - (char*)pDest, P4_TABLE); sqlite3VdbeChangeP5(v, insFlags); + sqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1); VdbeCoverage(v); sqlite3VdbeAddOp2(v, OP_Close, iSrc, 0); sqlite3VdbeAddOp2(v, OP_Close, iDest, 0); @@ -118850,19 +142237,18 @@ static int xferOptimization( sqlite3VdbeChangeP5(v, OPFLAG_BULKCSR); VdbeComment((v, "%s", pDestIdx->zName)); addr1 = sqlite3VdbeAddOp2(v, OP_Rewind, iSrc, 0); VdbeCoverage(v); - sqlite3VdbeAddOp3(v, OP_RowData, iSrc, regData, 1); if( db->mDbFlags & DBFLAG_Vacuum ){ /* This INSERT command is part of a VACUUM operation, which guarantees ** that the destination table is empty. If all indexed columns use ** collation sequence BINARY, then it can also be assumed that the - ** index will be populated by inserting keys in strictly sorted + ** index will be populated by inserting keys in strictly sorted ** order. In this case, instead of seeking within the b-tree as part ** of every OP_IdxInsert opcode, an OP_SeekEnd is added before the - ** OP_IdxInsert to seek to the point within the b-tree where each key + ** OP_IdxInsert to seek to the point within the b-tree where each key ** should be inserted. This is faster. ** ** If any of the indexed columns use a collation sequence other than - ** BINARY, this optimization is disabled. This is because the user + ** BINARY, this optimization is disabled. This is because the user ** might change the definition of a collation sequence and then run ** a VACUUM command. In that case keys may not be written in strictly ** sorted order. */ @@ -118871,13 +142257,22 @@ static int xferOptimization( if( sqlite3_stricmp(sqlite3StrBINARY, zColl) ) break; } if( i==pSrcIdx->nColumn ){ - idxInsFlags = OPFLAG_USESEEKRESULT; + idxInsFlags = OPFLAG_USESEEKRESULT|OPFLAG_PREFORMAT; sqlite3VdbeAddOp1(v, OP_SeekEnd, iDest); + sqlite3VdbeAddOp2(v, OP_RowCell, iDest, iSrc); } - } - if( !HasRowid(pSrc) && pDestIdx->idxType==SQLITE_IDXTYPE_PRIMARYKEY ){ + }else if( !HasRowid(pSrc) && pDestIdx->idxType==SQLITE_IDXTYPE_PRIMARYKEY ){ idxInsFlags |= OPFLAG_NCHANGE; } + if( idxInsFlags!=(OPFLAG_USESEEKRESULT|OPFLAG_PREFORMAT) ){ + sqlite3VdbeAddOp3(v, OP_RowData, iSrc, regData, 1); + if( (db->mDbFlags & DBFLAG_Vacuum)==0 + && !HasRowid(pDest) + && IsPrimaryKeyIndex(pDestIdx) + ){ + codeWithoutRowidPreupdate(pParse, pDest, iDest, regData); + } + } sqlite3VdbeAddOp2(v, OP_IdxInsert, iDest, regData); sqlite3VdbeChangeP5(v, idxInsFlags|OPFLAG_APPEND); sqlite3VdbeAddOp2(v, OP_Next, iSrc, addr1+1); VdbeCoverage(v); @@ -118971,7 +142366,7 @@ SQLITE_API int sqlite3_exec( rc = sqlite3_step(pStmt); /* Invoke the callback function if required */ - if( xCallback && (SQLITE_ROW==rc || + if( xCallback && (SQLITE_ROW==rc || (SQLITE_DONE==rc && !callbackIsInit && db->flags&SQLITE_NullCallback)) ){ if( !callbackIsInit ){ @@ -119080,7 +142475,7 @@ SQLITE_API int sqlite3_exec( ** This header file defines the SQLite interface for use by ** shared libraries that want to be imported as extensions into ** an SQLite instance. Shared libraries that intend to be loaded -** as extensions by SQLite should #include this file instead of +** as extensions by SQLite should #include this file instead of ** sqlite3.h. */ #ifndef SQLITE3EXT_H @@ -119390,6 +142785,60 @@ struct sqlite3_api_routines { /* Version 3.28.0 and later */ int (*stmt_isexplain)(sqlite3_stmt*); int (*value_frombind)(sqlite3_value*); + /* Version 3.30.0 and later */ + int (*drop_modules)(sqlite3*,const char**); + /* Version 3.31.0 and later */ + sqlite3_int64 (*hard_heap_limit64)(sqlite3_int64); + const char *(*uri_key)(const char*,int); + const char *(*filename_database)(const char*); + const char *(*filename_journal)(const char*); + const char *(*filename_wal)(const char*); + /* Version 3.32.0 and later */ + const char *(*create_filename)(const char*,const char*,const char*, + int,const char**); + void (*free_filename)(const char*); + sqlite3_file *(*database_file_object)(const char*); + /* Version 3.34.0 and later */ + int (*txn_state)(sqlite3*,const char*); + /* Version 3.36.1 and later */ + sqlite3_int64 (*changes64)(sqlite3*); + sqlite3_int64 (*total_changes64)(sqlite3*); + /* Version 3.37.0 and later */ + int (*autovacuum_pages)(sqlite3*, + unsigned int(*)(void*,const char*,unsigned int,unsigned int,unsigned int), + void*, void(*)(void*)); + /* Version 3.38.0 and later */ + int (*error_offset)(sqlite3*); + int (*vtab_rhs_value)(sqlite3_index_info*,int,sqlite3_value**); + int (*vtab_distinct)(sqlite3_index_info*); + int (*vtab_in)(sqlite3_index_info*,int,int); + int (*vtab_in_first)(sqlite3_value*,sqlite3_value**); + int (*vtab_in_next)(sqlite3_value*,sqlite3_value**); + /* Version 3.39.0 and later */ + int (*deserialize)(sqlite3*,const char*,unsigned char*, + sqlite3_int64,sqlite3_int64,unsigned); + unsigned char *(*serialize)(sqlite3*,const char *,sqlite3_int64*, + unsigned int); + const char *(*db_name)(sqlite3*,int); + /* Version 3.40.0 and later */ + int (*value_encoding)(sqlite3_value*); + /* Version 3.41.0 and later */ + int (*is_interrupted)(sqlite3*); + /* Version 3.43.0 and later */ + int (*stmt_explain)(sqlite3_stmt*,int); + /* Version 3.44.0 and later */ + void *(*get_clientdata)(sqlite3*,const char*); + int (*set_clientdata)(sqlite3*, const char*, void*, void(*)(void*)); + /* Version 3.50.0 and later */ + int (*setlk_timeout)(sqlite3*,int,int); + /* Version 3.51.0 and later */ + int (*set_errmsg)(sqlite3*,int,const char*); + int (*db_status64)(sqlite3*,int,sqlite3_int64*,sqlite3_int64*,int); + /* Version 3.52.0 and later */ + void (*str_truncate)(sqlite3_str*,int); + void (*str_free)(sqlite3_str*); + int (*carray_bind)(sqlite3_stmt*,int,void*,int,int,void(*)(void*)); + int (*carray_bind_v2)(sqlite3_stmt*,int,void*,int,int,void(*)(void*),void*); }; /* @@ -119680,19 +143129,70 @@ typedef int (*sqlite3_loadext_entry)( /* Version 3.26.0 and later */ #define sqlite3_normalized_sql sqlite3_api->normalized_sql /* Version 3.28.0 and later */ -#define sqlite3_stmt_isexplain sqlite3_api->isexplain -#define sqlite3_value_frombind sqlite3_api->frombind +#define sqlite3_stmt_isexplain sqlite3_api->stmt_isexplain +#define sqlite3_value_frombind sqlite3_api->value_frombind +/* Version 3.30.0 and later */ +#define sqlite3_drop_modules sqlite3_api->drop_modules +/* Version 3.31.0 and later */ +#define sqlite3_hard_heap_limit64 sqlite3_api->hard_heap_limit64 +#define sqlite3_uri_key sqlite3_api->uri_key +#define sqlite3_filename_database sqlite3_api->filename_database +#define sqlite3_filename_journal sqlite3_api->filename_journal +#define sqlite3_filename_wal sqlite3_api->filename_wal +/* Version 3.32.0 and later */ +#define sqlite3_create_filename sqlite3_api->create_filename +#define sqlite3_free_filename sqlite3_api->free_filename +#define sqlite3_database_file_object sqlite3_api->database_file_object +/* Version 3.34.0 and later */ +#define sqlite3_txn_state sqlite3_api->txn_state +/* Version 3.36.1 and later */ +#define sqlite3_changes64 sqlite3_api->changes64 +#define sqlite3_total_changes64 sqlite3_api->total_changes64 +/* Version 3.37.0 and later */ +#define sqlite3_autovacuum_pages sqlite3_api->autovacuum_pages +/* Version 3.38.0 and later */ +#define sqlite3_error_offset sqlite3_api->error_offset +#define sqlite3_vtab_rhs_value sqlite3_api->vtab_rhs_value +#define sqlite3_vtab_distinct sqlite3_api->vtab_distinct +#define sqlite3_vtab_in sqlite3_api->vtab_in +#define sqlite3_vtab_in_first sqlite3_api->vtab_in_first +#define sqlite3_vtab_in_next sqlite3_api->vtab_in_next +/* Version 3.39.0 and later */ +#ifndef SQLITE_OMIT_DESERIALIZE +#define sqlite3_deserialize sqlite3_api->deserialize +#define sqlite3_serialize sqlite3_api->serialize +#endif +#define sqlite3_db_name sqlite3_api->db_name +/* Version 3.40.0 and later */ +#define sqlite3_value_encoding sqlite3_api->value_encoding +/* Version 3.41.0 and later */ +#define sqlite3_is_interrupted sqlite3_api->is_interrupted +/* Version 3.43.0 and later */ +#define sqlite3_stmt_explain sqlite3_api->stmt_explain +/* Version 3.44.0 and later */ +#define sqlite3_get_clientdata sqlite3_api->get_clientdata +#define sqlite3_set_clientdata sqlite3_api->set_clientdata +/* Version 3.50.0 and later */ +#define sqlite3_setlk_timeout sqlite3_api->setlk_timeout +/* Version 3.51.0 and later */ +#define sqlite3_set_errmsg sqlite3_api->set_errmsg +#define sqlite3_db_status64 sqlite3_api->db_status64 +/* Version 3.52.0 and later */ +#define sqlite3_str_truncate sqlite3_api->str_truncate +#define sqlite3_str_free sqlite3_api->str_free +#define sqlite3_carray_bind sqlite3_api->carray_bind +#define sqlite3_carray_bind_v2 sqlite3_api->carray_bind_v2 #endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */ #if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) - /* This case when the file really is being compiled as a loadable + /* This case when the file really is being compiled as a loadable ** extension */ # define SQLITE_EXTENSION_INIT1 const sqlite3_api_routines *sqlite3_api=0; # define SQLITE_EXTENSION_INIT2(v) sqlite3_api=v; # define SQLITE_EXTENSION_INIT3 \ extern const sqlite3_api_routines *sqlite3_api; #else - /* This case when the file is being statically linked into the + /* This case when the file is being statically linked into the ** application */ # define SQLITE_EXTENSION_INIT1 /*no-op*/ # define SQLITE_EXTENSION_INIT2(v) (void)v; /* unused parameter */ @@ -119984,8 +143484,8 @@ static const sqlite3_api_routines sqlite3Apis = { sqlite3_memory_highwater, sqlite3_memory_used, #ifdef SQLITE_MUTEX_OMIT - 0, - 0, + 0, + 0, 0, 0, 0, @@ -120147,9 +143647,88 @@ static const sqlite3_api_routines sqlite3Apis = { #endif /* Version 3.28.0 and later */ sqlite3_stmt_isexplain, - sqlite3_value_frombind + sqlite3_value_frombind, + /* Version 3.30.0 and later */ +#ifndef SQLITE_OMIT_VIRTUALTABLE + sqlite3_drop_modules, +#else + 0, +#endif + /* Version 3.31.0 and later */ + sqlite3_hard_heap_limit64, + sqlite3_uri_key, + sqlite3_filename_database, + sqlite3_filename_journal, + sqlite3_filename_wal, + /* Version 3.32.0 and later */ + sqlite3_create_filename, + sqlite3_free_filename, + sqlite3_database_file_object, + /* Version 3.34.0 and later */ + sqlite3_txn_state, + /* Version 3.36.1 and later */ + sqlite3_changes64, + sqlite3_total_changes64, + /* Version 3.37.0 and later */ + sqlite3_autovacuum_pages, + /* Version 3.38.0 and later */ + sqlite3_error_offset, +#ifndef SQLITE_OMIT_VIRTUALTABLE + sqlite3_vtab_rhs_value, + sqlite3_vtab_distinct, + sqlite3_vtab_in, + sqlite3_vtab_in_first, + sqlite3_vtab_in_next, +#else + 0, + 0, + 0, + 0, + 0, +#endif + /* Version 3.39.0 and later */ +#ifndef SQLITE_OMIT_DESERIALIZE + sqlite3_deserialize, + sqlite3_serialize, +#else + 0, + 0, +#endif + sqlite3_db_name, + /* Version 3.40.0 and later */ + sqlite3_value_encoding, + /* Version 3.41.0 and later */ + sqlite3_is_interrupted, + /* Version 3.43.0 and later */ + sqlite3_stmt_explain, + /* Version 3.44.0 and later */ + sqlite3_get_clientdata, + sqlite3_set_clientdata, + /* Version 3.50.0 and later */ + sqlite3_setlk_timeout, + /* Version 3.51.0 and later */ + sqlite3_set_errmsg, + sqlite3_db_status64, + /* Version 3.52.0 and later */ + sqlite3_str_truncate, + sqlite3_str_free, +#ifdef SQLITE_ENABLE_CARRAY + sqlite3_carray_bind, + sqlite3_carray_bind_v2 +#else + 0, + 0 +#endif }; +/* True if x is the directory separator character +*/ +#if SQLITE_OS_WIN +# define DirSep(X) ((X)=='/'||(X)=='\\') +#else +# define DirSep(X) ((X)=='/') +#endif + /* ** Attempt to load an SQLite extension library contained in the file ** zFile. The entry point is zProc. zProc may be 0 in which case a @@ -120158,7 +143737,7 @@ static const sqlite3_api_routines sqlite3Apis = { ** ** Return SQLITE_OK on success and SQLITE_ERROR if something goes wrong. ** -** If an error occurs and pzErrMsg is not 0, then fill *pzErrMsg with +** If an error occurs and pzErrMsg is not 0, then fill *pzErrMsg with ** error message text. The calling function should free this memory ** by calling sqlite3DbFree(db, ). */ @@ -120175,14 +143754,14 @@ static int sqlite3LoadExtension( const char *zEntry; char *zAltEntry = 0; void **aHandle; - u64 nMsg = 300 + sqlite3Strlen30(zFile); + u64 nMsg = strlen(zFile); int ii; int rc; /* Shared library endings to try if zFile cannot be loaded as written */ static const char *azEndings[] = { #if SQLITE_OS_WIN - "dll" + "dll" #elif defined(__APPLE__) "dylib" #else @@ -120209,66 +143788,82 @@ static int sqlite3LoadExtension( zEntry = zProc ? zProc : "sqlite3_extension_init"; + /* tag-20210611-1. Some dlopen() implementations will segfault if given + ** an oversize filename. Most filesystems have a pathname limit of 4K, + ** so limit the extension filename length to about twice that. + ** https://sqlite.org/forum/forumpost/08a0d6d9bf + ** + ** Later (2023-03-25): Save an extra 6 bytes for the filename suffix. + ** See https://sqlite.org/forum/forumpost/24083b579d. + */ + if( nMsg>SQLITE_MAX_PATHLEN ) goto extension_not_found; + + /* Do not allow sqlite3_load_extension() to link to a copy of the + ** running application, by passing in an empty filename. */ + if( nMsg==0 ) goto extension_not_found; + handle = sqlite3OsDlOpen(pVfs, zFile); #if SQLITE_OS_UNIX || SQLITE_OS_WIN for(ii=0; ii sqlite3_example_init ** C:/lib/mathfuncs.dll ==> sqlite3_mathfuncs_init + ** + ** If that still finds no entry point, repeat a second time but this + ** time include both alphabetic and numeric characters up to the first + ** ".". Example: + ** + ** /usr/local/lib/libExample5.4.3.so ==> sqlite3_example5_init */ if( xInit==0 && zProc==0 ){ int iFile, iEntry, c; int ncFile = sqlite3Strlen30(zFile); + int cnt = 0; zAltEntry = sqlite3_malloc64(ncFile+30); if( zAltEntry==0 ){ sqlite3OsDlClose(pVfs, handle); return SQLITE_NOMEM_BKPT; } - memcpy(zAltEntry, "sqlite3_", 8); - for(iFile=ncFile-1; iFile>=0 && zFile[iFile]!='/'; iFile--){} - iFile++; - if( sqlite3_strnicmp(zFile+iFile, "lib", 3)==0 ) iFile += 3; - for(iEntry=8; (c = zFile[iFile])!=0 && c!='.'; iFile++){ - if( sqlite3Isalpha(c) ){ - zAltEntry[iEntry++] = (char)sqlite3UpperToLower[(unsigned)c]; + do{ + memcpy(zAltEntry, "sqlite3_", 8); + for(iFile=ncFile-1; iFile>=0 && !DirSep(zFile[iFile]); iFile--){} + iFile++; + if( sqlite3_strnicmp(zFile+iFile, "lib", 3)==0 ) iFile += 3; + for(iEntry=8; (c = zFile[iFile])!=0 && c!='.'; iFile++){ + if( sqlite3Isalpha(c) || (cnt && sqlite3Isdigit(c)) ){ + zAltEntry[iEntry++] = (char)sqlite3UpperToLower[(unsigned)c]; + } } - } - memcpy(zAltEntry+iEntry, "_init", 6); - zEntry = zAltEntry; - xInit = (sqlite3_loadext_entry)sqlite3OsDlSym(pVfs, handle, zEntry); + memcpy(zAltEntry+iEntry, "_init", 6); + zEntry = zAltEntry; + xInit = (sqlite3_loadext_entry)sqlite3OsDlSym(pVfs, handle, zEntry); + }while( xInit==0 && (++cnt)<2 ); } if( xInit==0 ){ if( pzErrMsg ){ - nMsg += sqlite3Strlen30(zEntry); + nMsg += strlen(zEntry) + 300; *pzErrMsg = zErrmsg = sqlite3_malloc64(nMsg); if( zErrmsg ){ - sqlite3_snprintf(nMsg, zErrmsg, + assert( nMsg<0x7fffffff ); /* zErrmsg would be NULL if not so */ + sqlite3_snprintf((int)nMsg, zErrmsg, "no entry point [%s] in shared library [%s]", zEntry, zFile); sqlite3OsDlError(pVfs, nMsg-1, zErrmsg); } @@ -120302,6 +143897,19 @@ static int sqlite3LoadExtension( db->aExtension[db->nExtension++] = handle; return SQLITE_OK; + +extension_not_found: + if( pzErrMsg ){ + nMsg += 300; + *pzErrMsg = zErrmsg = sqlite3_malloc64(nMsg); + if( zErrmsg ){ + assert( nMsg<0x7fffffff ); /* zErrmsg would be NULL if not so */ + sqlite3_snprintf((int)nMsg, zErrmsg, + "unable to open shared library [%.*s]", SQLITE_MAX_PATHLEN, zFile); + sqlite3OsDlError(pVfs, nMsg-1, zErrmsg); + } + } + return SQLITE_ERROR; } SQLITE_API int sqlite3_load_extension( sqlite3 *db, /* Load the extension into this database connection */ @@ -120335,6 +143943,9 @@ SQLITE_PRIVATE void sqlite3CloseExtensions(sqlite3 *db){ ** default so as not to open security holes in older applications. */ SQLITE_API int sqlite3_enable_load_extension(sqlite3 *db, int onoff){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif sqlite3_mutex_enter(db->mutex); if( onoff ){ db->flags |= SQLITE_LoadExtension|SQLITE_LoadExtFunc; @@ -120351,12 +143962,12 @@ SQLITE_API int sqlite3_enable_load_extension(sqlite3 *db, int onoff){ ** The following object holds the list of automatically loaded ** extensions. ** -** This list is shared across threads. The SQLITE_MUTEX_STATIC_MASTER +** This list is shared across threads. The SQLITE_MUTEX_STATIC_MAIN ** mutex must be held while accessing this list. */ typedef struct sqlite3AutoExtList sqlite3AutoExtList; static SQLITE_WSD struct sqlite3AutoExtList { - u32 nExt; /* Number of entries in aExt[] */ + u32 nExt; /* Number of entries in aExt[] */ void (**aExt)(void); /* Pointers to the extension init functions */ } sqlite3Autoext = { 0, 0 }; @@ -120384,6 +143995,9 @@ SQLITE_API int sqlite3_auto_extension( void (*xInit)(void) ){ int rc = SQLITE_OK; +#ifdef SQLITE_ENABLE_API_ARMOR + if( xInit==0 ) return SQLITE_MISUSE_BKPT; +#endif #ifndef SQLITE_OMIT_AUTOINIT rc = sqlite3_initialize(); if( rc ){ @@ -120393,7 +144007,7 @@ SQLITE_API int sqlite3_auto_extension( { u32 i; #if SQLITE_THREADSAFE - sqlite3_mutex *mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); + sqlite3_mutex *mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MAIN); #endif wsdAutoextInit; sqlite3_mutex_enter(mutex); @@ -120431,11 +144045,14 @@ SQLITE_API int sqlite3_cancel_auto_extension( void (*xInit)(void) ){ #if SQLITE_THREADSAFE - sqlite3_mutex *mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); + sqlite3_mutex *mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MAIN); #endif int i; int n = 0; wsdAutoextInit; +#ifdef SQLITE_ENABLE_API_ARMOR + if( xInit==0 ) return 0; +#endif sqlite3_mutex_enter(mutex); for(i=(int)wsdAutoext.nExt-1; i>=0; i--){ if( wsdAutoext.aExt[i]==xInit ){ @@ -120458,7 +144075,7 @@ SQLITE_API void sqlite3_reset_auto_extension(void){ #endif { #if SQLITE_THREADSAFE - sqlite3_mutex *mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); + sqlite3_mutex *mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MAIN); #endif wsdAutoextInit; sqlite3_mutex_enter(mutex); @@ -120488,7 +144105,7 @@ SQLITE_PRIVATE void sqlite3AutoLoadExtensions(sqlite3 *db){ for(i=0; go; i++){ char *zErrmsg; #if SQLITE_THREADSAFE - sqlite3_mutex *mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); + sqlite3_mutex *mutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MAIN); #endif #ifdef SQLITE_OMIT_LOAD_EXTENSION const sqlite3_api_routines *pThunk = 0; @@ -120543,7 +144160,7 @@ SQLITE_PRIVATE void sqlite3AutoLoadExtensions(sqlite3 *db){ ** that includes the PragType_XXXX macro definitions and the aPragmaName[] ** object. This ensures that the aPragmaName[] table is arranged in ** lexicographical order to facility a binary search of the pragma name. -** Do not edit pragma.h directly. Edit and rerun the script in at +** Do not edit pragma.h directly. Edit and rerun the script in at ** ../tool/mkpragmatab.tcl. */ /************** Include pragma.h in the middle of pragma.c *******************/ /************** Begin file pragma.h ******************************************/ @@ -120554,51 +144171,52 @@ SQLITE_PRIVATE void sqlite3AutoLoadExtensions(sqlite3 *db){ */ /* The various pragma types */ -#define PragTyp_HEADER_VALUE 0 -#define PragTyp_AUTO_VACUUM 1 -#define PragTyp_FLAG 2 -#define PragTyp_BUSY_TIMEOUT 3 -#define PragTyp_CACHE_SIZE 4 -#define PragTyp_CACHE_SPILL 5 -#define PragTyp_CASE_SENSITIVE_LIKE 6 -#define PragTyp_COLLATION_LIST 7 -#define PragTyp_COMPILE_OPTIONS 8 -#define PragTyp_DATA_STORE_DIRECTORY 9 -#define PragTyp_DATABASE_LIST 10 -#define PragTyp_DEFAULT_CACHE_SIZE 11 -#define PragTyp_ENCODING 12 -#define PragTyp_FOREIGN_KEY_CHECK 13 -#define PragTyp_FOREIGN_KEY_LIST 14 -#define PragTyp_FUNCTION_LIST 15 -#define PragTyp_INCREMENTAL_VACUUM 16 -#define PragTyp_INDEX_INFO 17 -#define PragTyp_INDEX_LIST 18 -#define PragTyp_INTEGRITY_CHECK 19 -#define PragTyp_JOURNAL_MODE 20 -#define PragTyp_JOURNAL_SIZE_LIMIT 21 -#define PragTyp_LOCK_PROXY_FILE 22 -#define PragTyp_LOCKING_MODE 23 -#define PragTyp_PAGE_COUNT 24 -#define PragTyp_MMAP_SIZE 25 -#define PragTyp_MODULE_LIST 26 -#define PragTyp_OPTIMIZE 27 -#define PragTyp_PAGE_SIZE 28 -#define PragTyp_PRAGMA_LIST 29 -#define PragTyp_SECURE_DELETE 30 -#define PragTyp_SHRINK_MEMORY 31 -#define PragTyp_SOFT_HEAP_LIMIT 32 -#define PragTyp_SYNCHRONOUS 33 -#define PragTyp_TABLE_INFO 34 -#define PragTyp_TEMP_STORE 35 -#define PragTyp_TEMP_STORE_DIRECTORY 36 -#define PragTyp_THREADS 37 -#define PragTyp_WAL_AUTOCHECKPOINT 38 -#define PragTyp_WAL_CHECKPOINT 39 -#define PragTyp_ACTIVATE_EXTENSIONS 40 -#define PragTyp_HEXKEY 41 -#define PragTyp_KEY 42 -#define PragTyp_LOCK_STATUS 43 -#define PragTyp_STATS 44 +#define PragTyp_ACTIVATE_EXTENSIONS 0 +#define PragTyp_ANALYSIS_LIMIT 1 +#define PragTyp_HEADER_VALUE 2 +#define PragTyp_AUTO_VACUUM 3 +#define PragTyp_FLAG 4 +#define PragTyp_BUSY_TIMEOUT 5 +#define PragTyp_CACHE_SIZE 6 +#define PragTyp_CACHE_SPILL 7 +#define PragTyp_CASE_SENSITIVE_LIKE 8 +#define PragTyp_COLLATION_LIST 9 +#define PragTyp_COMPILE_OPTIONS 10 +#define PragTyp_DATA_STORE_DIRECTORY 11 +#define PragTyp_DATABASE_LIST 12 +#define PragTyp_DEFAULT_CACHE_SIZE 13 +#define PragTyp_ENCODING 14 +#define PragTyp_FOREIGN_KEY_CHECK 15 +#define PragTyp_FOREIGN_KEY_LIST 16 +#define PragTyp_FUNCTION_LIST 17 +#define PragTyp_HARD_HEAP_LIMIT 18 +#define PragTyp_INCREMENTAL_VACUUM 19 +#define PragTyp_INDEX_INFO 20 +#define PragTyp_INDEX_LIST 21 +#define PragTyp_INTEGRITY_CHECK 22 +#define PragTyp_JOURNAL_MODE 23 +#define PragTyp_JOURNAL_SIZE_LIMIT 24 +#define PragTyp_LOCK_PROXY_FILE 25 +#define PragTyp_LOCKING_MODE 26 +#define PragTyp_PAGE_COUNT 27 +#define PragTyp_MMAP_SIZE 28 +#define PragTyp_MODULE_LIST 29 +#define PragTyp_OPTIMIZE 30 +#define PragTyp_PAGE_SIZE 31 +#define PragTyp_PRAGMA_LIST 32 +#define PragTyp_SECURE_DELETE 33 +#define PragTyp_SHRINK_MEMORY 34 +#define PragTyp_SOFT_HEAP_LIMIT 35 +#define PragTyp_SYNCHRONOUS 36 +#define PragTyp_TABLE_INFO 37 +#define PragTyp_TABLE_LIST 38 +#define PragTyp_TEMP_STORE 39 +#define PragTyp_TEMP_STORE_DIRECTORY 40 +#define PragTyp_THREADS 41 +#define PragTyp_WAL_AUTOCHECKPOINT 42 +#define PragTyp_WAL_CHECKPOINT 43 +#define PragTyp_LOCK_STATUS 44 +#define PragTyp_STATS 45 /* Property flags associated with various pragma. */ #define PragFlg_NeedSchema 0x01 /* Force schema load before running */ @@ -120616,56 +144234,66 @@ SQLITE_PRIVATE void sqlite3AutoLoadExtensions(sqlite3 *db){ */ static const char *const pragCName[] = { /* 0 */ "id", /* Used by: foreign_key_list */ - /* 1 */ "seq", - /* 2 */ "table", - /* 3 */ "from", - /* 4 */ "to", - /* 5 */ "on_update", - /* 6 */ "on_delete", - /* 7 */ "match", + /* 1 */ "seq", + /* 2 */ "table", + /* 3 */ "from", + /* 4 */ "to", + /* 5 */ "on_update", + /* 6 */ "on_delete", + /* 7 */ "match", /* 8 */ "cid", /* Used by: table_xinfo */ - /* 9 */ "name", - /* 10 */ "type", - /* 11 */ "notnull", - /* 12 */ "dflt_value", - /* 13 */ "pk", - /* 14 */ "hidden", + /* 9 */ "name", + /* 10 */ "type", + /* 11 */ "notnull", + /* 12 */ "dflt_value", + /* 13 */ "pk", + /* 14 */ "hidden", /* table_info reuses 8 */ - /* 15 */ "seqno", /* Used by: index_xinfo */ - /* 16 */ "cid", - /* 17 */ "name", - /* 18 */ "desc", - /* 19 */ "coll", - /* 20 */ "key", - /* 21 */ "tbl", /* Used by: stats */ - /* 22 */ "idx", - /* 23 */ "wdth", - /* 24 */ "hght", - /* 25 */ "flgs", - /* 26 */ "seq", /* Used by: index_list */ - /* 27 */ "name", - /* 28 */ "unique", - /* 29 */ "origin", - /* 30 */ "partial", - /* 31 */ "table", /* Used by: foreign_key_check */ - /* 32 */ "rowid", - /* 33 */ "parent", - /* 34 */ "fkid", - /* index_info reuses 15 */ - /* 35 */ "seq", /* Used by: database_list */ - /* 36 */ "name", - /* 37 */ "file", - /* 38 */ "busy", /* Used by: wal_checkpoint */ - /* 39 */ "log", - /* 40 */ "checkpointed", - /* 41 */ "name", /* Used by: function_list */ - /* 42 */ "builtin", - /* collation_list reuses 26 */ - /* 43 */ "database", /* Used by: lock_status */ - /* 44 */ "status", - /* 45 */ "cache_size", /* Used by: default_cache_size */ + /* 15 */ "name", /* Used by: function_list */ + /* 16 */ "builtin", + /* 17 */ "type", + /* 18 */ "enc", + /* 19 */ "narg", + /* 20 */ "flags", + /* 21 */ "schema", /* Used by: table_list */ + /* 22 */ "name", + /* 23 */ "type", + /* 24 */ "ncol", + /* 25 */ "wr", + /* 26 */ "strict", + /* 27 */ "seqno", /* Used by: index_xinfo */ + /* 28 */ "cid", + /* 29 */ "name", + /* 30 */ "desc", + /* 31 */ "coll", + /* 32 */ "key", + /* 33 */ "seq", /* Used by: index_list */ + /* 34 */ "name", + /* 35 */ "unique", + /* 36 */ "origin", + /* 37 */ "partial", + /* 38 */ "tbl", /* Used by: stats */ + /* 39 */ "idx", + /* 40 */ "wdth", + /* 41 */ "hght", + /* 42 */ "flgs", + /* 43 */ "table", /* Used by: foreign_key_check */ + /* 44 */ "rowid", + /* 45 */ "parent", + /* 46 */ "fkid", + /* 47 */ "busy", /* Used by: wal_checkpoint */ + /* 48 */ "log", + /* 49 */ "checkpointed", + /* 50 */ "seq", /* Used by: database_list */ + /* 51 */ "name", + /* 52 */ "file", + /* index_info reuses 27 */ + /* 53 */ "database", /* Used by: lock_status */ + /* 54 */ "status", + /* collation_list reuses 33 */ + /* 55 */ "cache_size", /* Used by: default_cache_size */ /* module_list pragma_list reuses 9 */ - /* 46 */ "timeout", /* Used by: busy_timeout */ + /* 56 */ "timeout", /* Used by: busy_timeout */ }; /* Definitions of all built-in pragmas */ @@ -120678,13 +144306,18 @@ typedef struct PragmaName { u64 iArg; /* Extra argument */ } PragmaName; static const PragmaName aPragmaName[] = { -#if defined(SQLITE_HAS_CODEC) || defined(SQLITE_ENABLE_CEROD) +#if defined(SQLITE_ENABLE_CEROD) {/* zName: */ "activate_extensions", /* ePragTyp: */ PragTyp_ACTIVATE_EXTENSIONS, /* ePragFlg: */ 0, /* ColNames: */ 0, 0, /* iArg: */ 0 }, #endif + {/* zName: */ "analysis_limit", + /* ePragTyp: */ PragTyp_ANALYSIS_LIMIT, + /* ePragFlg: */ PragFlg_Result0, + /* ColNames: */ 0, 0, + /* iArg: */ 0 }, #if !defined(SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS) {/* zName: */ "application_id", /* ePragTyp: */ PragTyp_HEADER_VALUE, @@ -120711,7 +144344,7 @@ static const PragmaName aPragmaName[] = { {/* zName: */ "busy_timeout", /* ePragTyp: */ PragTyp_BUSY_TIMEOUT, /* ePragFlg: */ PragFlg_Result0, - /* ColNames: */ 46, 1, + /* ColNames: */ 56, 1, /* iArg: */ 0 }, #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) {/* zName: */ "cache_size", @@ -120727,11 +144360,13 @@ static const PragmaName aPragmaName[] = { /* ColNames: */ 0, 0, /* iArg: */ 0 }, #endif +#if !defined(SQLITE_OMIT_CASE_SENSITIVE_LIKE_PRAGMA) {/* zName: */ "case_sensitive_like", /* ePragTyp: */ PragTyp_CASE_SENSITIVE_LIKE, /* ePragFlg: */ PragFlg_NoColumns, /* ColNames: */ 0, 0, /* iArg: */ 0 }, +#endif {/* zName: */ "cell_size_check", /* ePragTyp: */ PragTyp_FLAG, /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, @@ -120748,7 +144383,7 @@ static const PragmaName aPragmaName[] = { {/* zName: */ "collation_list", /* ePragTyp: */ PragTyp_COLLATION_LIST, /* ePragFlg: */ PragFlg_Result0, - /* ColNames: */ 26, 2, + /* ColNames: */ 33, 2, /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_COMPILEOPTION_DIAGS) @@ -120782,15 +144417,15 @@ static const PragmaName aPragmaName[] = { #if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) {/* zName: */ "database_list", /* ePragTyp: */ PragTyp_DATABASE_LIST, - /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0, - /* ColNames: */ 35, 3, + /* ePragFlg: */ PragFlg_Result0, + /* ColNames: */ 50, 3, /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) && !defined(SQLITE_OMIT_DEPRECATED) {/* zName: */ "default_cache_size", /* ePragTyp: */ PragTyp_DEFAULT_CACHE_SIZE, /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq|PragFlg_NoColumns1, - /* ColNames: */ 45, 1, + /* ColNames: */ 55, 1, /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) @@ -120819,8 +144454,8 @@ static const PragmaName aPragmaName[] = { #if !defined(SQLITE_OMIT_FOREIGN_KEY) && !defined(SQLITE_OMIT_TRIGGER) {/* zName: */ "foreign_key_check", /* ePragTyp: */ PragTyp_FOREIGN_KEY_CHECK, - /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0, - /* ColNames: */ 31, 4, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_Result1|PragFlg_SchemaOpt, + /* ColNames: */ 43, 4, /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FOREIGN_KEY) @@ -120859,26 +144494,19 @@ static const PragmaName aPragmaName[] = { /* iArg: */ SQLITE_FullFSync }, #endif #if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) -#if defined(SQLITE_INTROSPECTION_PRAGMAS) +#if !defined(SQLITE_OMIT_INTROSPECTION_PRAGMAS) {/* zName: */ "function_list", /* ePragTyp: */ PragTyp_FUNCTION_LIST, /* ePragFlg: */ PragFlg_Result0, - /* ColNames: */ 41, 2, + /* ColNames: */ 15, 6, /* iArg: */ 0 }, #endif #endif -#if defined(SQLITE_HAS_CODEC) - {/* zName: */ "hexkey", - /* ePragTyp: */ PragTyp_HEXKEY, - /* ePragFlg: */ 0, - /* ColNames: */ 0, 0, - /* iArg: */ 2 }, - {/* zName: */ "hexrekey", - /* ePragTyp: */ PragTyp_HEXKEY, - /* ePragFlg: */ 0, + {/* zName: */ "hard_heap_limit", + /* ePragTyp: */ PragTyp_HARD_HEAP_LIMIT, + /* ePragFlg: */ PragFlg_Result0, /* ColNames: */ 0, 0, - /* iArg: */ 3 }, -#endif + /* iArg: */ 0 }, #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) #if !defined(SQLITE_OMIT_CHECK) {/* zName: */ "ignore_check_constraints", @@ -120899,23 +144527,23 @@ static const PragmaName aPragmaName[] = { {/* zName: */ "index_info", /* ePragTyp: */ PragTyp_INDEX_INFO, /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt, - /* ColNames: */ 15, 3, + /* ColNames: */ 27, 3, /* iArg: */ 0 }, {/* zName: */ "index_list", /* ePragTyp: */ PragTyp_INDEX_LIST, /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt, - /* ColNames: */ 26, 5, + /* ColNames: */ 33, 5, /* iArg: */ 0 }, {/* zName: */ "index_xinfo", /* ePragTyp: */ PragTyp_INDEX_INFO, /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt, - /* ColNames: */ 15, 6, + /* ColNames: */ 27, 6, /* iArg: */ 1 }, #endif #if !defined(SQLITE_OMIT_INTEGRITY_CHECK) {/* zName: */ "integrity_check", /* ePragTyp: */ PragTyp_INTEGRITY_CHECK, - /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_Result1, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_Result1|PragFlg_SchemaOpt, /* ColNames: */ 0, 0, /* iArg: */ 0 }, #endif @@ -120931,24 +144559,12 @@ static const PragmaName aPragmaName[] = { /* ColNames: */ 0, 0, /* iArg: */ 0 }, #endif -#if defined(SQLITE_HAS_CODEC) - {/* zName: */ "key", - /* ePragTyp: */ PragTyp_KEY, - /* ePragFlg: */ 0, - /* ColNames: */ 0, 0, - /* iArg: */ 0 }, -#endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) {/* zName: */ "legacy_alter_table", /* ePragTyp: */ PragTyp_FLAG, /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, /* ColNames: */ 0, 0, /* iArg: */ SQLITE_LegacyAlter }, - {/* zName: */ "legacy_file_format", - /* ePragTyp: */ PragTyp_FLAG, - /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, - /* ColNames: */ 0, 0, - /* iArg: */ SQLITE_LegacyFileFmt }, #endif #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) && SQLITE_ENABLE_LOCKING_STYLE {/* zName: */ "lock_proxy_file", @@ -120961,7 +144577,7 @@ static const PragmaName aPragmaName[] = { {/* zName: */ "lock_status", /* ePragTyp: */ PragTyp_LOCK_STATUS, /* ePragFlg: */ PragFlg_Result0, - /* ColNames: */ 43, 2, + /* ColNames: */ 53, 2, /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) @@ -120983,7 +144599,7 @@ static const PragmaName aPragmaName[] = { #endif #if !defined(SQLITE_OMIT_SCHEMA_PRAGMAS) #if !defined(SQLITE_OMIT_VIRTUALTABLE) -#if defined(SQLITE_INTROSPECTION_PRAGMAS) +#if !defined(SQLITE_OMIT_INTROSPECTION_PRAGMAS) {/* zName: */ "module_list", /* ePragTyp: */ PragTyp_MODULE_LIST, /* ePragFlg: */ PragFlg_Result0, @@ -121018,7 +144634,7 @@ static const PragmaName aPragmaName[] = { /* iArg: */ SQLITE_ParserTrace }, #endif #endif -#if defined(SQLITE_INTROSPECTION_PRAGMAS) +#if !defined(SQLITE_OMIT_INTROSPECTION_PRAGMAS) {/* zName: */ "pragma_list", /* ePragTyp: */ PragTyp_PRAGMA_LIST, /* ePragFlg: */ PragFlg_Result0, @@ -121035,7 +144651,7 @@ static const PragmaName aPragmaName[] = { #if !defined(SQLITE_OMIT_INTEGRITY_CHECK) {/* zName: */ "quick_check", /* ePragTyp: */ PragTyp_INTEGRITY_CHECK, - /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_Result1, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_Result1|PragFlg_SchemaOpt, /* ColNames: */ 0, 0, /* iArg: */ 0 }, #endif @@ -121050,15 +144666,6 @@ static const PragmaName aPragmaName[] = { /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, /* ColNames: */ 0, 0, /* iArg: */ SQLITE_RecTriggers }, -#endif -#if defined(SQLITE_HAS_CODEC) - {/* zName: */ "rekey", - /* ePragTyp: */ PragTyp_KEY, - /* ePragFlg: */ 0, - /* ColNames: */ 0, 0, - /* iArg: */ 1 }, -#endif -#if !defined(SQLITE_OMIT_FLAG_PRAGMAS) {/* zName: */ "reverse_unordered_selects", /* ePragTyp: */ PragTyp_FLAG, /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, @@ -121109,7 +144716,7 @@ static const PragmaName aPragmaName[] = { {/* zName: */ "stats", /* ePragTyp: */ PragTyp_STATS, /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result0|PragFlg_SchemaReq, - /* ColNames: */ 21, 5, + /* ColNames: */ 38, 5, /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) @@ -121125,6 +144732,11 @@ static const PragmaName aPragmaName[] = { /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt, /* ColNames: */ 8, 6, /* iArg: */ 0 }, + {/* zName: */ "table_list", + /* ePragTyp: */ PragTyp_TABLE_LIST, + /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1, + /* ColNames: */ 21, 6, + /* iArg: */ 0 }, {/* zName: */ "table_xinfo", /* ePragTyp: */ PragTyp_TABLE_INFO, /* ePragFlg: */ PragFlg_NeedSchema|PragFlg_Result1|PragFlg_SchemaOpt, @@ -121142,24 +144754,19 @@ static const PragmaName aPragmaName[] = { /* ePragFlg: */ PragFlg_NoColumns1, /* ColNames: */ 0, 0, /* iArg: */ 0 }, -#endif -#if defined(SQLITE_HAS_CODEC) - {/* zName: */ "textkey", - /* ePragTyp: */ PragTyp_KEY, - /* ePragFlg: */ 0, - /* ColNames: */ 0, 0, - /* iArg: */ 4 }, - {/* zName: */ "textrekey", - /* ePragTyp: */ PragTyp_KEY, - /* ePragFlg: */ 0, - /* ColNames: */ 0, 0, - /* iArg: */ 5 }, #endif {/* zName: */ "threads", /* ePragTyp: */ PragTyp_THREADS, /* ePragFlg: */ PragFlg_Result0, /* ColNames: */ 0, 0, /* iArg: */ 0 }, +#if !defined(SQLITE_OMIT_FLAG_PRAGMAS) + {/* zName: */ "trusted_schema", + /* ePragTyp: */ PragTyp_FLAG, + /* ePragFlg: */ PragFlg_Result0|PragFlg_NoColumns1, + /* ColNames: */ 0, 0, + /* iArg: */ SQLITE_TrustedSchema }, +#endif #if !defined(SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS) {/* zName: */ "user_version", /* ePragTyp: */ PragTyp_HEADER_VALUE, @@ -121205,7 +144812,7 @@ static const PragmaName aPragmaName[] = { {/* zName: */ "wal_checkpoint", /* ePragTyp: */ PragTyp_WAL_CHECKPOINT, /* ePragFlg: */ PragFlg_NeedSchema, - /* ColNames: */ 38, 3, + /* ColNames: */ 47, 3, /* iArg: */ 0 }, #endif #if !defined(SQLITE_OMIT_FLAG_PRAGMAS) @@ -121216,14 +144823,42 @@ static const PragmaName aPragmaName[] = { /* iArg: */ SQLITE_WriteSchema|SQLITE_NoSchemaError }, #endif }; -/* Number of pragmas: 62 on by default, 81 total. */ +/* Number of pragmas: 68 on by default, 78 total. */ /************** End of pragma.h **********************************************/ /************** Continuing where we left off in pragma.c *********************/ +/* +** When the 0x10 bit of PRAGMA optimize is set, any ANALYZE commands +** will be run with an analysis_limit set to the lessor of the value of +** the following macro or to the actual analysis_limit if it is non-zero, +** in order to prevent PRAGMA optimize from running for too long. +** +** The value of 2000 is chosen empirically so that the worst-case run-time +** for PRAGMA optimize does not exceed 100 milliseconds against a variety +** of test databases on a RaspberryPI-4 compiled using -Os and without +** -DSQLITE_DEBUG. Of course, your mileage may vary. For the purpose of +** this paragraph, "worst-case" means that ANALYZE ends up being +** run on every table in the database. The worst case typically only +** happens if PRAGMA optimize is run on a database file for which ANALYZE +** has not been previously run and the 0x10000 flag is included so that +** all tables are analyzed. The usual case for PRAGMA optimize is that +** no ANALYZE commands will be run at all, or if any ANALYZE happens it +** will be against a single table, so that expected timing for PRAGMA +** optimize on a PI-4 is more like 1 millisecond or less with the 0x10000 +** flag or less than 100 microseconds without the 0x10000 flag. +** +** An analysis limit of 2000 is almost always sufficient for the query +** planner to fully characterize an index. The additional accuracy from +** a larger analysis is not usually helpful. +*/ +#ifndef SQLITE_DEFAULT_OPTIMIZE_LIMIT +# define SQLITE_DEFAULT_OPTIMIZE_LIMIT 2000 +#endif + /* ** Interpret the given string as a safety level. Return 0 for OFF, -** 1 for ON or NORMAL, 2 for FULL, and 3 for EXTRA. Return 1 for an empty or +** 1 for ON or NORMAL, 2 for FULL, and 3 for EXTRA. Return 1 for an empty or ** unrecognized string argument. The FULL and EXTRA option is disallowed ** if the omitFull parameter it 1. ** @@ -121282,7 +144917,7 @@ static int getLockingMode(const char *z){ /* ** Interpret the given string as an auto-vacuum mode value. ** -** The following strings, "none", "full" and "incremental" are +** The following strings, "none", "full" and "incremental" are ** acceptable, as are their numeric equivalents: 0, 1 and 2 respectively. */ static int getAutoVacuum(const char *z){ @@ -121322,7 +144957,9 @@ static int getTempStore(const char *z){ static int invalidateTempStorage(Parse *pParse){ sqlite3 *db = pParse->db; if( db->aDb[1].pBt!=0 ){ - if( !db->autoCommit || sqlite3BtreeIsInReadTrans(db->aDb[1].pBt) ){ + if( !db->autoCommit + || sqlite3BtreeTxnState(db->aDb[1].pBt)!=SQLITE_TXN_NONE + ){ sqlite3ErrorMsg(pParse, "temporary storage cannot be changed " "from within a transaction"); return SQLITE_ERROR; @@ -121434,7 +145071,7 @@ static const char *actionName(u8 action){ case OE_SetDflt: zName = "SET DEFAULT"; break; case OE_Cascade: zName = "CASCADE"; break; case OE_Restrict: zName = "RESTRICT"; break; - default: zName = "NO ACTION"; + default: zName = "NO ACTION"; assert( action==OE_None ); break; } return zName; @@ -121486,6 +145123,56 @@ static const PragmaName *pragmaLocate(const char *zName){ return lwr>upr ? 0 : &aPragmaName[mid]; } +/* +** Create zero or more entries in the output for the SQL functions +** defined by FuncDef p. +*/ +static void pragmaFunclistLine( + Vdbe *v, /* The prepared statement being created */ + FuncDef *p, /* A particular function definition */ + int isBuiltin, /* True if this is a built-in function */ + int showInternFuncs /* True if showing internal functions */ +){ + u32 mask = + SQLITE_DETERMINISTIC | + SQLITE_DIRECTONLY | + SQLITE_SUBTYPE | + SQLITE_INNOCUOUS | + SQLITE_FUNC_INTERNAL + ; + if( showInternFuncs ) mask = 0xffffffff; + for(; p; p=p->pNext){ + const char *zType; + static const char *azEnc[] = { 0, "utf8", "utf16le", "utf16be" }; + + assert( SQLITE_FUNC_ENCMASK==0x3 ); + assert( strcmp(azEnc[SQLITE_UTF8],"utf8")==0 ); + assert( strcmp(azEnc[SQLITE_UTF16LE],"utf16le")==0 ); + assert( strcmp(azEnc[SQLITE_UTF16BE],"utf16be")==0 ); + + if( p->xSFunc==0 ) continue; + if( (p->funcFlags & SQLITE_FUNC_INTERNAL)!=0 + && showInternFuncs==0 + ){ + continue; + } + if( p->xValue!=0 ){ + zType = "w"; + }else if( p->xFinalize!=0 ){ + zType = "a"; + }else{ + zType = "s"; + } + sqlite3VdbeMultiLoad(v, 1, "sissii", + p->zName, isBuiltin, + zType, azEnc[p->funcFlags&SQLITE_FUNC_ENCMASK], + p->nArg, + (p->funcFlags & mask) ^ SQLITE_INNOCUOUS + ); + } +} + + /* ** Helper subroutine for PRAGMA integrity_check: ** @@ -121503,7 +145190,23 @@ static int integrityCheckResultRow(Vdbe *v){ } /* -** Process a pragma statement. +** Should table pTab be skipped when doing an integrity_check? +** Return true or false. +** +** If pObjTab is not null, the return true if pTab matches pObjTab. +** +** If pObjTab is null, then return true only if pTab is an imposter table. +*/ +static int tableSkipIntegrityCheck(const Table *pTab, const Table *pObjTab){ + if( pObjTab ){ + return pTab!=pObjTab; + }else{ + return (pTab->tabFlags & TF_Imposter)!=0; + } +} + +/* +** Process a pragma statement. ** ** Pragmas are of this form: ** @@ -121518,7 +145221,7 @@ static int integrityCheckResultRow(Vdbe *v){ ** id and pId2 is any empty string. */ SQLITE_PRIVATE void sqlite3Pragma( - Parse *pParse, + Parse *pParse, Token *pId1, /* First part of [schema.]id field */ Token *pId2, /* Second part of [schema.]id field, or NULL */ Token *pValue, /* Token for , or NULL */ @@ -121546,8 +145249,8 @@ SQLITE_PRIVATE void sqlite3Pragma( if( iDb<0 ) return; pDb = &db->aDb[iDb]; - /* If the temp database has been explicitly named as part of the - ** pragma, make sure it is open. + /* If the temp database has been explicitly named as part of the + ** pragma, make sure it is open. */ if( iDb==1 && sqlite3OpenTempDatabase(pParse) ){ return; @@ -121607,7 +145310,11 @@ SQLITE_PRIVATE void sqlite3Pragma( /* Locate the pragma in the lookup table */ pPragma = pragmaLocate(zLeft); - if( pPragma==0 ) goto pragma_out; + if( pPragma==0 ){ + /* IMP: R-43042-22504 No error messages are generated if an + ** unknown pragma is issued. */ + goto pragma_out; + } /* Make sure the database schema is loaded if the pragma requires that */ if( (pPragma->mPragFlg & PragFlg_NeedSchema)!=0 ){ @@ -121615,7 +145322,7 @@ SQLITE_PRIVATE void sqlite3Pragma( } /* Register the result column names for pragmas that return results */ - if( (pPragma->mPragFlg & PragFlg_NoColumns)==0 + if( (pPragma->mPragFlg & PragFlg_NoColumns)==0 && ((pPragma->mPragFlg & PragFlg_NoColumns1)==0 || zRight==0) ){ setPragmaResultColumnNames(v, pPragma); @@ -121623,7 +145330,7 @@ SQLITE_PRIVATE void sqlite3Pragma( /* Jump to the appropriate pragma handler */ switch( pPragma->ePragTyp ){ - + #if !defined(SQLITE_OMIT_PAGER_PRAGMAS) && !defined(SQLITE_OMIT_DEPRECATED) /* ** PRAGMA [schema.]default_cache_size @@ -121697,7 +145404,7 @@ SQLITE_PRIVATE void sqlite3Pragma( ** buffer that the pager module resizes using sqlite3_realloc(). */ db->nextPagesize = sqlite3Atoi(zRight); - if( SQLITE_NOMEM==sqlite3BtreeSetPageSize(pBt, db->nextPagesize,-1,0) ){ + if( SQLITE_NOMEM==sqlite3BtreeSetPageSize(pBt, db->nextPagesize,0,0) ){ sqlite3OomFault(db); } } @@ -121739,7 +145446,7 @@ SQLITE_PRIVATE void sqlite3Pragma( ** PRAGMA [schema.]max_page_count=N ** ** The first form reports the current setting for the - ** maximum number of pages in the database file. The + ** maximum number of pages in the database file. The ** second form attempts to change this setting. Both ** forms return the current setting. ** @@ -121753,13 +145460,19 @@ SQLITE_PRIVATE void sqlite3Pragma( */ case PragTyp_PAGE_COUNT: { int iReg; + i64 x = 0; sqlite3CodeVerifySchema(pParse, iDb); iReg = ++pParse->nMem; if( sqlite3Tolower(zLeft[0])=='p' ){ sqlite3VdbeAddOp2(v, OP_Pagecount, iDb, iReg); }else{ - sqlite3VdbeAddOp3(v, OP_MaxPgcnt, iDb, iReg, - sqlite3AbsInt32(sqlite3Atoi(zRight))); + if( zRight && sqlite3DecOrHexToI64(zRight,&x)==0 ){ + if( x<0 ) x = 0; + else if( x>0xfffffffe ) x = 0xfffffffe; + }else{ + x = 0; + } + sqlite3VdbeAddOp3(v, OP_MaxPgcnt, iDb, iReg, (int)x); } sqlite3VdbeAddOp2(v, OP_ResultRow, iReg, 1); break; @@ -121835,6 +145548,11 @@ SQLITE_PRIVATE void sqlite3Pragma( ** then do a query */ eMode = PAGER_JOURNALMODE_QUERY; } + if( eMode==PAGER_JOURNALMODE_OFF && (db->flags & SQLITE_Defensive)!=0 ){ + /* Do not allow journal-mode "OFF" in defensive since the database + ** can become corrupted using ordinary SQL when the journal is off */ + eMode = PAGER_JOURNALMODE_QUERY; + } } if( eMode==PAGER_JOURNALMODE_QUERY && pId2->n==0 ){ /* Convert "PRAGMA journal_mode" into "PRAGMA main.journal_mode" */ @@ -121895,7 +145613,7 @@ SQLITE_PRIVATE void sqlite3Pragma( */ rc = sqlite3BtreeSetAutoVacuum(pBt, eAuto); if( rc==SQLITE_OK && (eAuto==1 || eAuto==2) ){ - /* When setting the auto_vacuum mode to either "full" or + /* When setting the auto_vacuum mode to either "full" or ** "incremental", write the value of meta[6] in the database ** file. Before writing to meta[6], check that meta[3] indicates ** that this really is an auto-vacuum capable database. @@ -121932,7 +145650,7 @@ SQLITE_PRIVATE void sqlite3Pragma( */ #ifndef SQLITE_OMIT_AUTOVACUUM case PragTyp_INCREMENTAL_VACUUM: { - int iLimit, addr; + int iLimit = 0, addr; if( zRight==0 || !sqlite3GetInt32(zRight, &iLimit) || iLimit<=0 ){ iLimit = 0x7fffffff; } @@ -121978,7 +145696,7 @@ SQLITE_PRIVATE void sqlite3Pragma( ** ** The first form reports the current local setting for the ** page cache spill size. The second form turns cache spill on - ** or off. When turnning cache spill on, the size is set to the + ** or off. When turning cache spill on, the size is set to the ** current cache_size. The third form sets a spill size that ** may be different form the cache size. ** If N is positive then that is the @@ -121997,7 +145715,7 @@ SQLITE_PRIVATE void sqlite3Pragma( assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); if( !zRight ){ returnSingleInt(v, - (db->flags & SQLITE_CacheSpill)==0 ? 0 : + (db->flags & SQLITE_CacheSpill)==0 ? 0 : sqlite3BtreeSetSpillSize(pDb->pBt,0)); }else{ int size = 1; @@ -122089,6 +145807,7 @@ SQLITE_PRIVATE void sqlite3Pragma( ** */ case PragTyp_TEMP_STORE_DIRECTORY: { + sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); if( !zRight ){ returnSingleText(v, sqlite3_temp_directory); }else{ @@ -122098,6 +145817,7 @@ SQLITE_PRIVATE void sqlite3Pragma( rc = sqlite3OsAccess(db->pVfs, zRight, SQLITE_ACCESS_READWRITE, &res); if( rc!=SQLITE_OK || res==0 ){ sqlite3ErrorMsg(pParse, "not a writable directory"); + sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); goto pragma_out; } } @@ -122115,6 +145835,7 @@ SQLITE_PRIVATE void sqlite3Pragma( } #endif /* SQLITE_OMIT_WSD */ } + sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); break; } @@ -122133,6 +145854,7 @@ SQLITE_PRIVATE void sqlite3Pragma( ** */ case PragTyp_DATA_STORE_DIRECTORY: { + sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); if( !zRight ){ returnSingleText(v, sqlite3_data_directory); }else{ @@ -122142,6 +145864,7 @@ SQLITE_PRIVATE void sqlite3Pragma( rc = sqlite3OsAccess(db->pVfs, zRight, SQLITE_ACCESS_READWRITE, &res); if( rc!=SQLITE_OK || res==0 ){ sqlite3ErrorMsg(pParse, "not a writable directory"); + sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); goto pragma_out; } } @@ -122153,6 +145876,7 @@ SQLITE_PRIVATE void sqlite3Pragma( } #endif /* SQLITE_OMIT_WSD */ } + sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_TEMPDIR)); break; } #endif @@ -122171,7 +145895,7 @@ SQLITE_PRIVATE void sqlite3Pragma( Pager *pPager = sqlite3BtreePager(pDb->pBt); char *proxy_file_path = NULL; sqlite3_file *pFile = sqlite3PagerFile(pPager); - sqlite3OsFileControlHint(pFile, SQLITE_GET_LOCKPROXYFILE, + sqlite3OsFileControlHint(pFile, SQLITE_GET_LOCKPROXYFILE, &proxy_file_path); returnSingleText(v, proxy_file_path); }else{ @@ -122179,10 +145903,10 @@ SQLITE_PRIVATE void sqlite3Pragma( sqlite3_file *pFile = sqlite3PagerFile(pPager); int res; if( zRight[0] ){ - res=sqlite3OsFileControl(pFile, SQLITE_SET_LOCKPROXYFILE, + res=sqlite3OsFileControl(pFile, SQLITE_SET_LOCKPROXYFILE, zRight); } else { - res=sqlite3OsFileControl(pFile, SQLITE_SET_LOCKPROXYFILE, + res=sqlite3OsFileControl(pFile, SQLITE_SET_LOCKPROXYFILE, NULL); } if( res!=SQLITE_OK ){ @@ -122192,8 +145916,8 @@ SQLITE_PRIVATE void sqlite3Pragma( } break; } -#endif /* SQLITE_ENABLE_LOCKING_STYLE */ - +#endif /* SQLITE_ENABLE_LOCKING_STYLE */ + /* ** PRAGMA [schema.]synchronous ** PRAGMA [schema.]synchronous=OFF|ON|NORMAL|FULL|EXTRA @@ -122208,7 +145932,7 @@ SQLITE_PRIVATE void sqlite3Pragma( returnSingleInt(v, pDb->safety_level-1); }else{ if( !db->autoCommit ){ - sqlite3ErrorMsg(pParse, + sqlite3ErrorMsg(pParse, "Safety level may not be changed inside a transaction"); }else if( iDb!=1 ){ int iLevel = (getSafetyLevel(zRight,0,1)+1) & PAGER_SYNCHRONOUS_MASK; @@ -122234,21 +145958,30 @@ SQLITE_PRIVATE void sqlite3Pragma( ** in auto-commit mode. */ mask &= ~(SQLITE_ForeignKeys); } -#if SQLITE_USER_AUTHENTICATION - if( db->auth.authLevel==UAUTH_User ){ - /* Do not allow non-admin users to modify the schema arbitrarily */ - mask &= ~(SQLITE_WriteSchema); - } -#endif if( sqlite3GetBoolean(zRight, 0) ){ - db->flags |= mask; + if( (mask & SQLITE_WriteSchema)==0 + || (db->flags & SQLITE_Defensive)==0 + ){ + db->flags |= mask; + } }else{ db->flags &= ~mask; - if( mask==SQLITE_DeferFKs ) db->nDeferredImmCons = 0; + if( mask==SQLITE_DeferFKs ){ + db->nDeferredImmCons = 0; + db->nDeferredCons = 0; + } + if( (mask & SQLITE_WriteSchema)!=0 + && sqlite3_stricmp(zRight, "reset")==0 + ){ + /* IMP: R-60817-01178 If the argument is "RESET" then schema + ** writing is disabled (as with "PRAGMA writable_schema=OFF") and, + ** in addition, the schema is reloaded. */ + sqlite3ResetAllSchemasOfConnection(db); + } } - /* Many of the flag-pragmas modify the code generated by the SQL + /* Many of the flag-pragmas modify the code generated by the SQL ** compiler (eg. count_changes). So add an opcode to expire all ** compiled SQL statements after modifying a pragma value. */ @@ -122275,21 +146008,30 @@ SQLITE_PRIVATE void sqlite3Pragma( */ case PragTyp_TABLE_INFO: if( zRight ){ Table *pTab; + sqlite3CodeVerifyNamedSchema(pParse, zDb); pTab = sqlite3LocateTable(pParse, LOCATE_NOERR, zRight, zDb); if( pTab ){ - int iTabDb = sqlite3SchemaToIndex(db, pTab->pSchema); int i, k; int nHidden = 0; Column *pCol; Index *pPk = sqlite3PrimaryKeyIndex(pTab); pParse->nMem = 7; - sqlite3CodeVerifySchema(pParse, iTabDb); sqlite3ViewGetColumnNames(pParse, pTab); for(i=0, pCol=pTab->aCol; inCol; i++, pCol++){ - int isHidden = IsHiddenColumn(pCol); - if( isHidden && pPragma->iArg==0 ){ - nHidden++; - continue; + int isHidden = 0; + const Expr *pColExpr; + if( pCol->colFlags & COLFLAG_NOINSERT ){ + if( pPragma->iArg==0 ){ + nHidden++; + continue; + } + if( pCol->colFlags & COLFLAG_VIRTUAL ){ + isHidden = 2; /* GENERATED ALWAYS AS ... VIRTUAL */ + }else if( pCol->colFlags & COLFLAG_STORED ){ + isHidden = 3; /* GENERATED ALWAYS AS ... STORED */ + }else{ assert( pCol->colFlags & COLFLAG_HIDDEN ); + isHidden = 1; /* HIDDEN */ + } } if( (pCol->colFlags & COLFLAG_PRIMKEY)==0 ){ k = 0; @@ -122298,13 +146040,16 @@ SQLITE_PRIVATE void sqlite3Pragma( }else{ for(k=1; k<=pTab->nCol && pPk->aiColumn[k-1]!=i; k++){} } - assert( pCol->pDflt==0 || pCol->pDflt->op==TK_SPAN ); + pColExpr = sqlite3ColumnExpr(pTab,pCol); + assert( pColExpr==0 || pColExpr->op==TK_SPAN || isHidden>=2 ); + assert( pColExpr==0 || !ExprHasProperty(pColExpr, EP_IntValue) + || isHidden>=2 ); sqlite3VdbeMultiLoad(v, 1, pPragma->iArg ? "issisii" : "issisi", i-nHidden, - pCol->zName, + pCol->zCnName, sqlite3ColumnType(pCol,""), pCol->notNull ? 1 : 0, - pCol->pDflt ? pCol->pDflt->u.zToken : 0, + (isHidden>=2 || pColExpr==0) ? 0 : pColExpr->u.zToken, k, isHidden); } @@ -122312,6 +146057,86 @@ SQLITE_PRIVATE void sqlite3Pragma( } break; + /* + ** PRAGMA table_list + ** + ** Return a single row for each table, virtual table, or view in the + ** entire schema. + ** + ** schema: Name of attached database hold this table + ** name: Name of the table itself + ** type: "table", "view", "virtual", "shadow" + ** ncol: Number of columns + ** wr: True for a WITHOUT ROWID table + ** strict: True for a STRICT table + */ + case PragTyp_TABLE_LIST: { + int ii; + pParse->nMem = 6; + sqlite3CodeVerifyNamedSchema(pParse, zDb); + for(ii=0; iinDb; ii++){ + HashElem *k; + Hash *pHash; + int initNCol; + if( zDb && sqlite3_stricmp(zDb, db->aDb[ii].zDbSName)!=0 ) continue; + + /* Ensure that the Table.nCol field is initialized for all views + ** and virtual tables. Each time we initialize a Table.nCol value + ** for a table, that can potentially disrupt the hash table, so restart + ** the initialization scan. + */ + pHash = &db->aDb[ii].pSchema->tblHash; + initNCol = sqliteHashCount(pHash); + while( initNCol-- ){ + for(k=sqliteHashFirst(pHash); 1; k=sqliteHashNext(k) ){ + Table *pTab; + if( k==0 ){ initNCol = 0; break; } + pTab = sqliteHashData(k); + if( pTab->nCol==0 ){ + char *zSql = sqlite3MPrintf(db, "SELECT*FROM\"%w\"", pTab->zName); + if( zSql ){ + sqlite3_stmt *pDummy = 0; + (void)sqlite3_prepare_v3(db, zSql, -1, SQLITE_PREPARE_DONT_LOG, + &pDummy, 0); + (void)sqlite3_finalize(pDummy); + sqlite3DbFree(db, zSql); + } + if( db->mallocFailed ){ + sqlite3ErrorMsg(db->pParse, "out of memory"); + db->pParse->rc = SQLITE_NOMEM_BKPT; + } + pHash = &db->aDb[ii].pSchema->tblHash; + break; + } + } + } + + for(k=sqliteHashFirst(pHash); k; k=sqliteHashNext(k) ){ + Table *pTab = sqliteHashData(k); + const char *zType; + if( zRight && sqlite3_stricmp(zRight, pTab->zName)!=0 ) continue; + if( IsView(pTab) ){ + zType = "view"; + }else if( IsVirtual(pTab) ){ + zType = "virtual"; + }else if( pTab->tabFlags & TF_Shadow ){ + zType = "shadow"; + }else{ + zType = "table"; + } + sqlite3VdbeMultiLoad(v, 1, "sssiii", + db->aDb[ii].zDbSName, + sqlite3PreferredTableName(pTab->zName), + zType, + pTab->nCol, + (pTab->tabFlags & TF_WithoutRowid)!=0, + (pTab->tabFlags & TF_Strict)!=0 + ); + } + } + } + break; + #ifdef SQLITE_DEBUG case PragTyp_STATS: { Index *pIdx; @@ -122321,7 +146146,7 @@ SQLITE_PRIVATE void sqlite3Pragma( for(i=sqliteHashFirst(&pDb->pSchema->tblHash); i; i=sqliteHashNext(i)){ Table *pTab = sqliteHashData(i); sqlite3VdbeMultiLoad(v, 1, "ssiii", - pTab->zName, + sqlite3PreferredTableName(pTab->zName), 0, pTab->szTabRow, pTab->nRowLogEst, @@ -122343,6 +146168,15 @@ SQLITE_PRIVATE void sqlite3Pragma( Index *pIdx; Table *pTab; pIdx = sqlite3FindIndex(db, zRight, zDb); + if( pIdx==0 ){ + /* If there is no index named zRight, check to see if there is a + ** WITHOUT ROWID table named zRight, and if there is, show the + ** structure of the PRIMARY KEY index for that table. */ + pTab = sqlite3LocateTable(pParse, LOCATE_NOERR, zRight, zDb); + if( pTab && !HasRowid(pTab) ){ + pIdx = sqlite3PrimaryKeyIndex(pTab); + } + } if( pIdx ){ int iIdxDb = sqlite3SchemaToIndex(db, pIdx->pSchema); int i; @@ -122362,7 +146196,7 @@ SQLITE_PRIVATE void sqlite3Pragma( for(i=0; iaiColumn[i]; sqlite3VdbeMultiLoad(v, 1, "iisX", i, cnum, - cnum<0 ? 0 : pTab->aCol[cnum].zName); + cnum<0 ? 0 : pTab->aCol[cnum].zCnName); if( pPragma->iArg ){ sqlite3VdbeMultiLoad(v, 4, "isiX", pIdx->aSortOrder[i], @@ -122422,21 +146256,23 @@ SQLITE_PRIVATE void sqlite3Pragma( } break; -#ifdef SQLITE_INTROSPECTION_PRAGMAS +#ifndef SQLITE_OMIT_INTROSPECTION_PRAGMAS case PragTyp_FUNCTION_LIST: { int i; HashElem *j; FuncDef *p; - pParse->nMem = 2; + int showInternFunc = (db->mDbFlags & DBFLAG_InternalFunc)!=0; + pParse->nMem = 6; for(i=0; iu.pHash ){ - if( p->funcFlags & SQLITE_FUNC_INTERNAL ) continue; - sqlite3VdbeMultiLoad(v, 1, "si", p->zName, 1); + assert( p->funcFlags & SQLITE_FUNC_BUILTIN ); + pragmaFunclistLine(v, p, 1, showInternFunc); } } for(j=sqliteHashFirst(&db->aFunc); j; j=sqliteHashNext(j)){ p = (FuncDef*)sqliteHashData(j); - sqlite3VdbeMultiLoad(v, 1, "si", p->zName, 0); + assert( (p->funcFlags & SQLITE_FUNC_BUILTIN)==0 ); + pragmaFunclistLine(v, p, 0, showInternFunc); } } break; @@ -122469,11 +146305,11 @@ SQLITE_PRIVATE void sqlite3Pragma( FKey *pFK; Table *pTab; pTab = sqlite3FindTable(db, zRight, zDb); - if( pTab ){ - pFK = pTab->pFKey; + if( pTab && IsOrdinaryTable(pTab) ){ + pFK = pTab->u.tab.pFKey; if( pFK ){ int iTabDb = sqlite3SchemaToIndex(db, pTab->pSchema); - int i = 0; + int i = 0; pParse->nMem = 8; sqlite3CodeVerifySchema(pParse, iTabDb); while(pFK){ @@ -122483,7 +146319,7 @@ SQLITE_PRIVATE void sqlite3Pragma( i, j, pFK->zTo, - pTab->aCol[pFK->aCol[j].iFrom].zName, + pTab->aCol[pFK->aCol[j].iFrom].zCnName, pFK->aCol[j].zCol, actionName(pFK->aAction[1]), /* ON UPDATE */ actionName(pFK->aAction[0]), /* ON DELETE */ @@ -122510,7 +146346,6 @@ SQLITE_PRIVATE void sqlite3Pragma( HashElem *k; /* Loop counter: Next table in schema */ int x; /* result variable */ int regResult; /* 3 registers to hold a result row */ - int regKey; /* Register to hold key for checking the FK */ int regRow; /* Registers to hold a row from pTab */ int addrTop; /* Top of a loop checking foreign keys */ int addrOk; /* Jump here if the key is OK */ @@ -122518,11 +146353,9 @@ SQLITE_PRIVATE void sqlite3Pragma( regResult = pParse->nMem+1; pParse->nMem += 4; - regKey = ++pParse->nMem; regRow = ++pParse->nMem; k = sqliteHashFirst(&db->aDb[iDb].pSchema->tblHash); while( k ){ - int iTabDb; if( zRight ){ pTab = sqlite3LocateTable(pParse, 0, zRight, zDb); k = 0; @@ -122530,24 +146363,26 @@ SQLITE_PRIVATE void sqlite3Pragma( pTab = (Table*)sqliteHashData(k); k = sqliteHashNext(k); } - if( pTab==0 || pTab->pFKey==0 ) continue; - iTabDb = sqlite3SchemaToIndex(db, pTab->pSchema); - sqlite3CodeVerifySchema(pParse, iTabDb); - sqlite3TableLock(pParse, iTabDb, pTab->tnum, 0, pTab->zName); - if( pTab->nCol+regRow>pParse->nMem ) pParse->nMem = pTab->nCol + regRow; - sqlite3OpenTable(pParse, 0, iTabDb, pTab, OP_OpenRead); + if( pTab==0 || !IsOrdinaryTable(pTab) || pTab->u.tab.pFKey==0 ) continue; + iDb = sqlite3SchemaToIndex(db, pTab->pSchema); + zDb = db->aDb[iDb].zDbSName; + sqlite3CodeVerifySchema(pParse, iDb); + sqlite3TableLock(pParse, iDb, pTab->tnum, 0, pTab->zName); + sqlite3TouchRegister(pParse, pTab->nCol+regRow); + sqlite3OpenTable(pParse, 0, iDb, pTab, OP_OpenRead); sqlite3VdbeLoadString(v, regResult, pTab->zName); - for(i=1, pFK=pTab->pFKey; pFK; i++, pFK=pFK->pNextFrom){ + assert( IsOrdinaryTable(pTab) ); + for(i=1, pFK=pTab->u.tab.pFKey; pFK; i++, pFK=pFK->pNextFrom){ pParent = sqlite3FindTable(db, pFK->zTo, zDb); if( pParent==0 ) continue; pIdx = 0; - sqlite3TableLock(pParse, iTabDb, pParent->tnum, 0, pParent->zName); + sqlite3TableLock(pParse, iDb, pParent->tnum, 0, pParent->zName); x = sqlite3FkLocateIndex(pParse, pParent, pFK, &pIdx, 0); if( x==0 ){ if( pIdx==0 ){ - sqlite3OpenTable(pParse, i, iTabDb, pParent, OP_OpenRead); + sqlite3OpenTable(pParse, i, iDb, pParent, OP_OpenRead); }else{ - sqlite3VdbeAddOp3(v, OP_OpenRead, i, pIdx->tnum, iTabDb); + sqlite3VdbeAddOp3(v, OP_OpenRead, i, pIdx->tnum, iDb); sqlite3VdbeSetP4KeyInfo(pParse, pIdx); } }else{ @@ -122559,20 +146394,22 @@ SQLITE_PRIVATE void sqlite3Pragma( if( pFK ) break; if( pParse->nTabnTab = i; addrTop = sqlite3VdbeAddOp1(v, OP_Rewind, 0); VdbeCoverage(v); - for(i=1, pFK=pTab->pFKey; pFK; i++, pFK=pFK->pNextFrom){ + assert( IsOrdinaryTable(pTab) ); + for(i=1, pFK=pTab->u.tab.pFKey; pFK; i++, pFK=pFK->pNextFrom){ pParent = sqlite3FindTable(db, pFK->zTo, zDb); pIdx = 0; aiCols = 0; if( pParent ){ x = sqlite3FkLocateIndex(pParse, pParent, pFK, &pIdx, &aiCols); - assert( x==0 ); + assert( x==0 || db->mallocFailed ); } addrOk = sqlite3VdbeMakeLabel(pParse); /* Generate code to read the child key values into registers - ** regRow..regRow+n. If any of the child key values are NULL, this - ** row cannot cause an FK violation. Jump directly to addrOk in + ** regRow..regRow+n. If any of the child key values are NULL, this + ** row cannot cause an FK violation. Jump directly to addrOk in ** this case. */ + sqlite3TouchRegister(pParse, regRow + pFK->nCol); for(j=0; jnCol; j++){ int iCol = aiCols ? aiCols[j] : pFK->aCol[j].iFrom; sqlite3ExprCodeGetColumnOfTable(v, pTab, 0, iCol, regRow+j); @@ -122582,15 +146419,15 @@ SQLITE_PRIVATE void sqlite3Pragma( /* Generate code to query the parent index for a matching parent ** key. If a match is found, jump to addrOk. */ if( pIdx ){ - sqlite3VdbeAddOp4(v, OP_MakeRecord, regRow, pFK->nCol, regKey, + sqlite3VdbeAddOp4(v, OP_Affinity, regRow, pFK->nCol, 0, sqlite3IndexAffinityStr(db,pIdx), pFK->nCol); - sqlite3VdbeAddOp4Int(v, OP_Found, i, addrOk, regKey, 0); + sqlite3VdbeAddOp4Int(v, OP_Found, i, addrOk, regRow, pFK->nCol); VdbeCoverage(v); }else if( pParent ){ int jmp = sqlite3VdbeCurrentAddr(v)+2; sqlite3VdbeAddOp3(v, OP_SeekRowid, i, jmp, regRow); VdbeCoverage(v); sqlite3VdbeGoto(v, addrOk); - assert( pFK->nCol==1 ); + assert( pFK->nCol==1 || db->mallocFailed ); } /* Generate code to report an FK violation to the caller. */ @@ -122612,6 +146449,7 @@ SQLITE_PRIVATE void sqlite3Pragma( #endif /* !defined(SQLITE_OMIT_TRIGGER) */ #endif /* !defined(SQLITE_OMIT_FOREIGN_KEY) */ +#ifndef SQLITE_OMIT_CASE_SENSITIVE_LIKE_PRAGMA /* Reinstall the LIKE and GLOB functions. The variant of LIKE ** used will be case sensitive or not depending on the RHS. */ @@ -122621,6 +146459,7 @@ SQLITE_PRIVATE void sqlite3Pragma( } } break; +#endif /* SQLITE_OMIT_CASE_SENSITIVE_LIKE_PRAGMA */ #ifndef SQLITE_INTEGRITY_CHECK_ERROR_MAX # define SQLITE_INTEGRITY_CHECK_ERROR_MAX 100 @@ -122634,13 +146473,26 @@ SQLITE_PRIVATE void sqlite3Pragma( ** ** Verify the integrity of the database. ** - ** The "quick_check" is reduced version of + ** The "quick_check" is reduced version of ** integrity_check designed to detect most database corruption ** without the overhead of cross-checking indexes. Quick_check - ** is linear time wherease integrity_check is O(NlogN). + ** is linear time whereas integrity_check is O(NlogN). + ** + ** The maximum number of errors is 100 by default. A different default + ** can be specified using a numeric parameter N. + ** + ** Or, the parameter N can be the name of a table. In that case, only + ** the one table named is verified. The freelist is only verified if + ** the named table is "sqlite_schema" (or one of its aliases). + ** + ** All schemas are checked by default. To check just a single + ** schema, use the form: + ** + ** PRAGMA schema.integrity_check; */ case PragTyp_INTEGRITY_CHECK: { int i, j, addr, mxErr; + Table *pObjTab = 0; /* Check only this one table, if not NULL */ int isQuick = (sqlite3Tolower(zLeft[0])=='q'); @@ -122663,9 +146515,13 @@ SQLITE_PRIVATE void sqlite3Pragma( /* Set the maximum error count */ mxErr = SQLITE_INTEGRITY_CHECK_ERROR_MAX; if( zRight ){ - sqlite3GetInt32(zRight, &mxErr); - if( mxErr<=0 ){ - mxErr = SQLITE_INTEGRITY_CHECK_ERROR_MAX; + if( sqlite3GetInt32(pValue->z, &mxErr) ){ + if( mxErr<=0 ){ + mxErr = SQLITE_INTEGRITY_CHECK_ERROR_MAX; + } + }else{ + pObjTab = sqlite3LocateTable(pParse, 0, zRight, + iDb>=0 ? db->aDb[iDb].zDbSName : 0); } } sqlite3VdbeAddOp2(v, OP_Integer, mxErr-1, 1); /* reg[1] holds errors left */ @@ -122676,12 +146532,12 @@ SQLITE_PRIVATE void sqlite3Pragma( Hash *pTbls; /* Set of all tables in the schema */ int *aRoot; /* Array of root page numbers of all btrees */ int cnt = 0; /* Number of entries in aRoot[] */ - int mxIdx = 0; /* Maximum number of indexes for any table */ if( OMIT_TEMPDB && i==1 ) continue; if( iDb>=0 && i!=iDb ) continue; sqlite3CodeVerifySchema(pParse, i); + pParse->okConstFactor = 0; /* tag-20230327-1 */ /* Do an integrity check of the B-Tree ** @@ -122694,15 +146550,20 @@ SQLITE_PRIVATE void sqlite3Pragma( Table *pTab = sqliteHashData(x); /* Current table */ Index *pIdx; /* An index on pTab */ int nIdx; /* Number of indexes on pTab */ + if( tableSkipIntegrityCheck(pTab,pObjTab) ) continue; if( HasRowid(pTab) ) cnt++; for(nIdx=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, nIdx++){ cnt++; } - if( nIdx>mxIdx ) mxIdx = nIdx; } + if( cnt==0 ) continue; + if( pObjTab ) cnt++; aRoot = sqlite3DbMallocRawNN(db, sizeof(int)*(cnt+1)); if( aRoot==0 ) break; - for(cnt=0, x=sqliteHashFirst(pTbls); x; x=sqliteHashNext(x)){ + cnt = 0; + if( pObjTab ) aRoot[++cnt] = 0; + for(x=sqliteHashFirst(pTbls); x; x=sqliteHashNext(x)){ Table *pTab = sqliteHashData(x); Index *pIdx; + if( tableSkipIntegrityCheck(pTab,pObjTab) ) continue; if( HasRowid(pTab) ) aRoot[++cnt] = pTab->tnum; for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ aRoot[++cnt] = pIdx->tnum; @@ -122711,12 +146572,13 @@ SQLITE_PRIVATE void sqlite3Pragma( aRoot[0] = cnt; /* Make sure sufficient number of registers have been allocated */ - pParse->nMem = MAX( pParse->nMem, 8+mxIdx ); + sqlite3TouchRegister(pParse, 8+cnt); + sqlite3VdbeAddOp3(v, OP_Null, 0, 8, 8+cnt); sqlite3ClearTempRegCache(pParse); /* Do the b-tree integrity checks */ - sqlite3VdbeAddOp4(v, OP_IntegrityCk, 2, cnt, 1, (char*)aRoot,P4_INTARRAY); - sqlite3VdbeChangeP5(v, (u8)i); + sqlite3VdbeAddOp4(v, OP_IntegrityCk, 1, cnt, 8, (char*)aRoot,P4_INTARRAY); + sqlite3VdbeChangeP5(v, (u16)i); addr = sqlite3VdbeAddOp1(v, OP_IsNull, 2); VdbeCoverage(v); sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, sqlite3MPrintf(db, "*** in database %s ***\n", db->aDb[i].zDbSName), @@ -122725,22 +146587,63 @@ SQLITE_PRIVATE void sqlite3Pragma( integrityCheckResultRow(v); sqlite3VdbeJumpHere(v, addr); + /* Check that the indexes all have the right number of rows */ + cnt = pObjTab ? 1 : 0; + sqlite3VdbeLoadString(v, 2, "wrong # of entries in index "); + for(x=sqliteHashFirst(pTbls); x; x=sqliteHashNext(x)){ + int iTab = 0; + Table *pTab = sqliteHashData(x); + Index *pIdx; + if( tableSkipIntegrityCheck(pTab,pObjTab) ) continue; + if( HasRowid(pTab) ){ + iTab = cnt++; + }else{ + iTab = cnt; + for(pIdx=pTab->pIndex; ALWAYS(pIdx); pIdx=pIdx->pNext){ + if( IsPrimaryKeyIndex(pIdx) ) break; + iTab++; + } + } + for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + if( pIdx->pPartIdxWhere==0 ){ + addr = sqlite3VdbeAddOp3(v, OP_Eq, 8+cnt, 0, 8+iTab); + VdbeCoverageNeverNull(v); + sqlite3VdbeLoadString(v, 4, pIdx->zName); + sqlite3VdbeAddOp3(v, OP_Concat, 4, 2, 3); + integrityCheckResultRow(v); + sqlite3VdbeJumpHere(v, addr); + } + cnt++; + } + } + /* Make sure all the indices are constructed correctly. */ for(x=sqliteHashFirst(pTbls); x; x=sqliteHashNext(x)){ Table *pTab = sqliteHashData(x); Index *pIdx, *pPk; - Index *pPrior = 0; + Index *pPrior = 0; /* Previous index */ int loopTop; int iDataCur, iIdxCur; int r1 = -1; - - if( pTab->tnum<1 ) continue; /* Skip VIEWs or VIRTUAL TABLEs */ - pPk = HasRowid(pTab) ? 0 : sqlite3PrimaryKeyIndex(pTab); + int bStrict; /* True for a STRICT table */ + int r2; /* Previous key for WITHOUT ROWID tables */ + int mxCol; /* Maximum non-virtual column number */ + + if( tableSkipIntegrityCheck(pTab,pObjTab) ) continue; + if( !IsOrdinaryTable(pTab) ) continue; + if( isQuick || HasRowid(pTab) ){ + pPk = 0; + r2 = 0; + }else{ + pPk = sqlite3PrimaryKeyIndex(pTab); + r2 = sqlite3GetTempRange(pParse, pPk->nKeyCol); + sqlite3VdbeAddOp3(v, OP_Null, 1, r2, r2+pPk->nKeyCol-1); + } sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenRead, 0, 1, 0, &iDataCur, &iIdxCur); /* reg[7] counts the number of entries in the table. - ** reg[8+i] counts the number of entries in the i-th index + ** reg[8+i] counts the number of entries in the i-th index */ sqlite3VdbeAddOp2(v, OP_Integer, 0, 7); for(j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){ @@ -122750,25 +146653,181 @@ SQLITE_PRIVATE void sqlite3Pragma( assert( sqlite3NoTempsInRange(pParse,1,7+j) ); sqlite3VdbeAddOp2(v, OP_Rewind, iDataCur, 0); VdbeCoverage(v); loopTop = sqlite3VdbeAddOp2(v, OP_AddImm, 7, 1); + + /* Fetch the right-most column from the table. This will cause + ** the entire record header to be parsed and sanity checked. It + ** will also prepopulate the cursor column cache that is used + ** by the OP_IsType code, so it is a required step. + */ + assert( !IsVirtual(pTab) ); + if( HasRowid(pTab) ){ + mxCol = -1; + for(j=0; jnCol; j++){ + if( (pTab->aCol[j].colFlags & COLFLAG_VIRTUAL)==0 ) mxCol++; + } + if( mxCol==pTab->iPKey ) mxCol--; + }else{ + /* COLFLAG_VIRTUAL columns are not included in the WITHOUT ROWID + ** PK index column-count, so there is no need to account for them + ** in this case. */ + mxCol = sqlite3PrimaryKeyIndex(pTab)->nColumn-1; + } + if( mxCol>=0 ){ + sqlite3VdbeAddOp3(v, OP_Column, iDataCur, mxCol, 3); + sqlite3VdbeTypeofColumn(v, 3); + } + if( !isQuick ){ - /* Sanity check on record header decoding */ - sqlite3VdbeAddOp3(v, OP_Column, iDataCur, pTab->nCol-1, 3); - sqlite3VdbeChangeP5(v, OPFLAG_TYPEOFARG); + if( pPk ){ + /* Verify WITHOUT ROWID keys are in ascending order */ + int a1; + char *zErr; + a1 = sqlite3VdbeAddOp4Int(v, OP_IdxGT, iDataCur, 0,r2,pPk->nKeyCol); + VdbeCoverage(v); + sqlite3VdbeAddOp1(v, OP_IsNull, r2); VdbeCoverage(v); + zErr = sqlite3MPrintf(db, + "row not in PRIMARY KEY order for %s", + pTab->zName); + sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, zErr, P4_DYNAMIC); + integrityCheckResultRow(v); + sqlite3VdbeJumpHere(v, a1); + sqlite3VdbeJumpHere(v, a1+1); + for(j=0; jnKeyCol; j++){ + sqlite3ExprCodeLoadIndexColumn(pParse, pPk, iDataCur, j, r2+j); + } + } } - /* Verify that all NOT NULL columns really are NOT NULL */ + /* Verify datatypes for all columns: + ** + ** (1) NOT NULL columns may not contain a NULL + ** (2) Datatype must be exact for non-ANY columns in STRICT tables + ** (3) Datatype for TEXT columns in non-STRICT tables must be + ** NULL, TEXT, or BLOB. + ** (4) Datatype for numeric columns in non-STRICT tables must not + ** be a TEXT value that can be losslessly converted to numeric. + */ + bStrict = (pTab->tabFlags & TF_Strict)!=0; for(j=0; jnCol; j++){ char *zErr; - int jmp2; + Column *pCol = pTab->aCol + j; /* The column to be checked */ + int labelError; /* Jump here to report an error */ + int labelOk; /* Jump here if all looks ok */ + int p1, p3, p4; /* Operands to the OP_IsType opcode */ + int doTypeCheck; /* Check datatypes (besides NOT NULL) */ + if( j==pTab->iPKey ) continue; - if( pTab->aCol[j].notNull==0 ) continue; - sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, j, 3); - sqlite3VdbeChangeP5(v, OPFLAG_TYPEOFARG); - jmp2 = sqlite3VdbeAddOp1(v, OP_NotNull, 3); VdbeCoverage(v); - zErr = sqlite3MPrintf(db, "NULL value in %s.%s", pTab->zName, - pTab->aCol[j].zName); - sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, zErr, P4_DYNAMIC); + if( bStrict ){ + doTypeCheck = pCol->eCType>COLTYPE_ANY; + }else{ + doTypeCheck = pCol->affinity>SQLITE_AFF_BLOB; + } + if( pCol->notNull==0 && !doTypeCheck ) continue; + + /* Compute the operands that will be needed for OP_IsType */ + p4 = SQLITE_NULL; + if( pCol->colFlags & COLFLAG_VIRTUAL ){ + sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, j, 3); + p1 = -1; + p3 = 3; + }else{ + if( pCol->iDflt ){ + sqlite3_value *pDfltValue = 0; + sqlite3ValueFromExpr(db, sqlite3ColumnExpr(pTab,pCol), ENC(db), + pCol->affinity, &pDfltValue); + if( pDfltValue ){ + p4 = sqlite3_value_type(pDfltValue); + sqlite3ValueFree(pDfltValue); + } + } + p1 = iDataCur; + if( !HasRowid(pTab) ){ + testcase( j!=sqlite3TableColumnToStorage(pTab, j) ); + p3 = sqlite3TableColumnToIndex(sqlite3PrimaryKeyIndex(pTab), j); + }else{ + p3 = sqlite3TableColumnToStorage(pTab,j); + testcase( p3!=j); + } + } + + labelError = sqlite3VdbeMakeLabel(pParse); + labelOk = sqlite3VdbeMakeLabel(pParse); + if( pCol->notNull ){ + /* (1) NOT NULL columns may not contain a NULL */ + int jmp3; + int jmp2 = sqlite3VdbeAddOp4Int(v, OP_IsType, p1, labelOk, p3, p4); + VdbeCoverage(v); + if( p1<0 ){ + sqlite3VdbeChangeP5(v, 0x0f); /* INT, REAL, TEXT, or BLOB */ + jmp3 = jmp2; + }else{ + sqlite3VdbeChangeP5(v, 0x0d); /* INT, TEXT, or BLOB */ + /* OP_IsType does not detect NaN values in the database file + ** which should be treated as a NULL. So if the header type + ** is REAL, we have to load the actual data using OP_Column + ** to reliably determine if the value is a NULL. */ + sqlite3VdbeAddOp3(v, OP_Column, p1, p3, 3); + sqlite3ColumnDefault(v, pTab, j, 3); + jmp3 = sqlite3VdbeAddOp2(v, OP_NotNull, 3, labelOk); + VdbeCoverage(v); + } + zErr = sqlite3MPrintf(db, "NULL value in %s.%s", pTab->zName, + pCol->zCnName); + sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, zErr, P4_DYNAMIC); + if( doTypeCheck ){ + sqlite3VdbeGoto(v, labelError); + sqlite3VdbeJumpHere(v, jmp2); + sqlite3VdbeJumpHere(v, jmp3); + }else{ + /* VDBE byte code will fall thru */ + } + } + if( bStrict && doTypeCheck ){ + /* (2) Datatype must be exact for non-ANY columns in STRICT tables*/ + static unsigned char aStdTypeMask[] = { + 0x1f, /* ANY */ + 0x18, /* BLOB */ + 0x11, /* INT */ + 0x11, /* INTEGER */ + 0x13, /* REAL */ + 0x14 /* TEXT */ + }; + sqlite3VdbeAddOp4Int(v, OP_IsType, p1, labelOk, p3, p4); + assert( pCol->eCType>=1 && pCol->eCType<=sizeof(aStdTypeMask) ); + sqlite3VdbeChangeP5(v, aStdTypeMask[pCol->eCType-1]); + VdbeCoverage(v); + zErr = sqlite3MPrintf(db, "non-%s value in %s.%s", + sqlite3StdType[pCol->eCType-1], + pTab->zName, pTab->aCol[j].zCnName); + sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, zErr, P4_DYNAMIC); + }else if( !bStrict && pCol->affinity==SQLITE_AFF_TEXT ){ + /* (3) Datatype for TEXT columns in non-STRICT tables must be + ** NULL, TEXT, or BLOB. */ + sqlite3VdbeAddOp4Int(v, OP_IsType, p1, labelOk, p3, p4); + sqlite3VdbeChangeP5(v, 0x1c); /* NULL, TEXT, or BLOB */ + VdbeCoverage(v); + zErr = sqlite3MPrintf(db, "NUMERIC value in %s.%s", + pTab->zName, pTab->aCol[j].zCnName); + sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, zErr, P4_DYNAMIC); + }else if( !bStrict && pCol->affinity>=SQLITE_AFF_NUMERIC ){ + /* (4) Datatype for numeric columns in non-STRICT tables must not + ** be a TEXT value that can be converted to numeric. */ + sqlite3VdbeAddOp4Int(v, OP_IsType, p1, labelOk, p3, p4); + sqlite3VdbeChangeP5(v, 0x1b); /* NULL, INT, FLOAT, or BLOB */ + VdbeCoverage(v); + if( p1>=0 ){ + sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, j, 3); + } + sqlite3VdbeAddOp4(v, OP_Affinity, 3, 1, 0, "C", P4_STATIC); + sqlite3VdbeAddOp4Int(v, OP_IsType, -1, labelOk, 3, p4); + sqlite3VdbeChangeP5(v, 0x1c); /* NULL, TEXT, or BLOB */ + VdbeCoverage(v); + zErr = sqlite3MPrintf(db, "TEXT value in %s.%s", + pTab->zName, pTab->aCol[j].zCnName); + sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, zErr, P4_DYNAMIC); + } + sqlite3VdbeResolveLabel(v, labelError); integrityCheckResultRow(v); - sqlite3VdbeJumpHere(v, jmp2); + sqlite3VdbeResolveLabel(v, labelOk); } /* Verify CHECK constraints */ if( pTab->pCheck && (db->flags & SQLITE_IgnoreChecks)==0 ){ @@ -122782,7 +146841,7 @@ SQLITE_PRIVATE void sqlite3Pragma( for(k=pCheck->nExpr-1; k>0; k--){ sqlite3ExprIfFalse(pParse, pCheck->a[k].pExpr, addrCkFault, 0); } - sqlite3ExprIfTrue(pParse, pCheck->a[0].pExpr, addrCkOk, + sqlite3ExprIfTrue(pParse, pCheck->a[0].pExpr, addrCkOk, SQLITE_JUMPIFNULL); sqlite3VdbeResolveLabel(v, addrCkFault); pParse->iSelfTab = 0; @@ -122797,7 +146856,8 @@ SQLITE_PRIVATE void sqlite3Pragma( if( !isQuick ){ /* Omit the remaining tests for quick_check */ /* Validate index entries for the current row */ for(j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){ - int jmp2, jmp3, jmp4, jmp5; + int jmp2, jmp3, jmp4, jmp5, label6; + int kk; int ckUniq = sqlite3VdbeMakeLabel(pParse); if( pPk==pIdx ) continue; r1 = sqlite3GenerateIndexKey(pParse, pIdx, iDataCur, 0, 0, &jmp3, @@ -122805,8 +146865,20 @@ SQLITE_PRIVATE void sqlite3Pragma( pPrior = pIdx; sqlite3VdbeAddOp2(v, OP_AddImm, 8+j, 1);/* increment entry count */ /* Verify that an index entry exists for the current table row */ - jmp2 = sqlite3VdbeAddOp4Int(v, OP_Found, iIdxCur+j, ckUniq, r1, + sqlite3VdbeAddOp4Int(v, OP_Found, iIdxCur+j, ckUniq, r1, pIdx->nColumn); VdbeCoverage(v); + jmp2 = sqlite3VdbeAddOp3(v, OP_IFindKey, iIdxCur+j, ckUniq, r1); + VdbeCoverage(v); + sqlite3VdbeChangeP4(v, -1, (const char*)pIdx, P4_INDEX); + sqlite3VdbeAddOp4(v, OP_String8, 0, 3, 0, + sqlite3MPrintf(db, "index %s stores an imprecise floating-point " + "value for row ", pIdx->zName), + P4_DYNAMIC); + sqlite3VdbeAddOp3(v, OP_Concat, 7, 3, 3); + integrityCheckResultRow(v); + sqlite3VdbeAddOp2(v, OP_Goto, 0, ckUniq); + + sqlite3VdbeJumpHere(v, jmp2); sqlite3VdbeLoadString(v, 3, "row "); sqlite3VdbeAddOp3(v, OP_Concat, 7, 3, 3); sqlite3VdbeLoadString(v, 4, " missing from index "); @@ -122814,14 +146886,50 @@ SQLITE_PRIVATE void sqlite3Pragma( jmp5 = sqlite3VdbeLoadString(v, 4, pIdx->zName); sqlite3VdbeAddOp3(v, OP_Concat, 4, 3, 3); jmp4 = integrityCheckResultRow(v); - sqlite3VdbeJumpHere(v, jmp2); + sqlite3VdbeResolveLabel(v, ckUniq); + + /* The OP_IdxRowid opcode is an optimized version of OP_Column + ** that extracts the rowid off the end of the index record. + ** But it only works correctly if index record does not have + ** any extra bytes at the end. Verify that this is the case. */ + if( HasRowid(pTab) ){ + int jmp7; + sqlite3VdbeAddOp2(v, OP_IdxRowid, iIdxCur+j, 3); + jmp7 = sqlite3VdbeAddOp3(v, OP_Eq, 3, 0, r1+pIdx->nColumn-1); + VdbeCoverageNeverNull(v); + sqlite3VdbeLoadString(v, 3, + "rowid not at end-of-record for row "); + sqlite3VdbeAddOp3(v, OP_Concat, 7, 3, 3); + sqlite3VdbeLoadString(v, 4, " of index "); + sqlite3VdbeGoto(v, jmp5-1); + sqlite3VdbeJumpHere(v, jmp7); + } + + /* Any indexed columns with non-BINARY collations must still hold + ** the exact same text value as the table. */ + label6 = 0; + for(kk=0; kknKeyCol; kk++){ + if( pIdx->azColl[kk]==sqlite3StrBINARY ) continue; + if( label6==0 ) label6 = sqlite3VdbeMakeLabel(pParse); + sqlite3VdbeAddOp3(v, OP_Column, iIdxCur+j, kk, 3); + sqlite3VdbeAddOp3(v, OP_Ne, 3, label6, r1+kk); VdbeCoverage(v); + } + if( label6 ){ + int jmp6 = sqlite3VdbeAddOp0(v, OP_Goto); + sqlite3VdbeResolveLabel(v, label6); + sqlite3VdbeLoadString(v, 3, "row "); + sqlite3VdbeAddOp3(v, OP_Concat, 7, 3, 3); + sqlite3VdbeLoadString(v, 4, " values differ from index "); + sqlite3VdbeGoto(v, jmp5-1); + sqlite3VdbeJumpHere(v, jmp6); + } + /* For UNIQUE indexes, verify that only one entry exists with the ** current key. The entry is unique if (1) any column is NULL ** or (2) the next entry has a different key */ if( IsUniqueIndex(pIdx) ){ int uniqOk = sqlite3VdbeMakeLabel(pParse); int jmp6; - int kk; for(kk=0; kknKeyCol; kk++){ int iCol = pIdx->aiColumn[kk]; assert( iCol!=XN_ROWID && iColnCol ); @@ -122844,22 +146952,43 @@ SQLITE_PRIVATE void sqlite3Pragma( } sqlite3VdbeAddOp2(v, OP_Next, iDataCur, loopTop); VdbeCoverage(v); sqlite3VdbeJumpHere(v, loopTop-1); -#ifndef SQLITE_OMIT_BTREECOUNT - if( !isQuick ){ - sqlite3VdbeLoadString(v, 2, "wrong # of entries in index "); - for(j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){ - if( pPk==pIdx ) continue; - sqlite3VdbeAddOp2(v, OP_Count, iIdxCur+j, 3); - addr = sqlite3VdbeAddOp3(v, OP_Eq, 8+j, 0, 3); VdbeCoverage(v); - sqlite3VdbeChangeP5(v, SQLITE_NOTNULL); - sqlite3VdbeLoadString(v, 4, pIdx->zName); - sqlite3VdbeAddOp3(v, OP_Concat, 4, 2, 3); - integrityCheckResultRow(v); - sqlite3VdbeJumpHere(v, addr); - } + if( pPk ){ + assert( !isQuick ); + sqlite3ReleaseTempRange(pParse, r2, pPk->nKeyCol); + } + } + +#ifndef SQLITE_OMIT_VIRTUALTABLE + /* Second pass to invoke the xIntegrity method on all virtual + ** tables. + */ + for(x=sqliteHashFirst(pTbls); x; x=sqliteHashNext(x)){ + Table *pTab = sqliteHashData(x); + sqlite3_vtab *pVTab; + int a1; + if( tableSkipIntegrityCheck(pTab,pObjTab) ) continue; + if( IsOrdinaryTable(pTab) ) continue; + if( !IsVirtual(pTab) ) continue; + if( pTab->nCol<=0 ){ + const char *zMod = pTab->u.vtab.azArg[0]; + if( sqlite3HashFind(&db->aModule, zMod)==0 ) continue; } -#endif /* SQLITE_OMIT_BTREECOUNT */ - } + sqlite3ViewGetColumnNames(pParse, pTab); + if( pTab->u.vtab.p==0 ) continue; + pVTab = pTab->u.vtab.p->pVtab; + if( NEVER(pVTab==0) ) continue; + if( NEVER(pVTab->pModule==0) ) continue; + if( pVTab->pModule->iVersion<4 ) continue; + if( pVTab->pModule->xIntegrity==0 ) continue; + sqlite3VdbeAddOp3(v, OP_VCheck, i, 3, isQuick); + pTab->nTabRef++; + sqlite3VdbeAppendP4(v, pTab, P4_TABLEREF); + a1 = sqlite3VdbeAddOp1(v, OP_IsNull, 3); VdbeCoverage(v); + integrityCheckResultRow(v); + sqlite3VdbeJumpHere(v, a1); + continue; + } +#endif } { static const int iLn = VDBE_OFFSET_LINENO(2); @@ -122901,7 +147030,7 @@ SQLITE_PRIVATE void sqlite3Pragma( ** encoding that will be used for the main database file if a new file ** is created. If an existing main database file is opened, then the ** default text encoding for the existing database is used. - ** + ** ** In all cases new databases created using the ATTACH command are ** created to use the same default text encoding as the main database. If ** the main database has not been initialized and/or created when ATTACH @@ -122939,14 +147068,12 @@ SQLITE_PRIVATE void sqlite3Pragma( ** will be overwritten when the schema is next loaded. If it does not ** already exists, it will be created to use the new encoding value. */ - if( - !(DbHasProperty(db, 0, DB_SchemaLoaded)) || - DbHasProperty(db, 0, DB_Empty) - ){ + if( (db->mDbFlags & DBFLAG_EncodingFixed)==0 ){ for(pEnc=&encnames[0]; pEnc->zName; pEnc++){ if( 0==sqlite3StrICmp(zRight, pEnc->zName) ){ - SCHEMA_ENC(db) = ENC(db) = - pEnc->enc ? pEnc->enc : SQLITE_UTF16NATIVE; + u8 enc = pEnc->enc ? pEnc->enc : SQLITE_UTF16NATIVE; + SCHEMA_ENC(db) = enc; + sqlite3SetTextEncoding(db, enc); break; } } @@ -123009,6 +147136,12 @@ SQLITE_PRIVATE void sqlite3Pragma( aOp[1].p1 = iDb; aOp[1].p2 = iCookie; aOp[1].p3 = sqlite3Atoi(zRight); + aOp[1].p5 = 1; + if( iCookie==BTREE_SCHEMA_VERSION && (db->flags & SQLITE_Defensive)!=0 ){ + /* Do not allow the use of PRAGMA schema_version=VALUE in defensive + ** mode. Change the OP_SetCookie opcode into a no-op. */ + aOp[1].opcode = OP_Noop; + } }else{ /* Read the specified cookie value */ static const VdbeOpList readCookie[] = { @@ -123056,7 +147189,7 @@ SQLITE_PRIVATE void sqlite3Pragma( ** Checkpoint the database. */ case PragTyp_WAL_CHECKPOINT: { - int iBt = (pId2->z?iDb:SQLITE_MAX_ATTACHED); + int iBt = (pId2->z?iDb:SQLITE_MAX_DB); int eMode = SQLITE_CHECKPOINT_PASSIVE; if( zRight ){ if( sqlite3StrICmp(zRight, "full")==0 ){ @@ -123065,6 +147198,8 @@ SQLITE_PRIVATE void sqlite3Pragma( eMode = SQLITE_CHECKPOINT_RESTART; }else if( sqlite3StrICmp(zRight, "truncate")==0 ){ eMode = SQLITE_CHECKPOINT_TRUNCATE; + }else if( sqlite3StrICmp(zRight, "noop")==0 ){ + eMode = SQLITE_CHECKPOINT_NOOP; } } pParse->nMem = 3; @@ -123085,8 +147220,8 @@ SQLITE_PRIVATE void sqlite3Pragma( if( zRight ){ sqlite3_wal_autocheckpoint(db, sqlite3Atoi(zRight)); } - returnSingleInt(v, - db->xWalCallback==sqlite3WalDefaultHook ? + returnSingleInt(v, + db->xWalCallback==sqlite3WalDefaultHook ? SQLITE_PTR_TO_INT(db->pWalArg) : 0); } break; @@ -123119,44 +147254,63 @@ SQLITE_PRIVATE void sqlite3Pragma( ** ** The optional argument is a bitmask of optimizations to perform: ** - ** 0x0001 Debugging mode. Do not actually perform any optimizations - ** but instead return one line of text for each optimization - ** that would have been done. Off by default. + ** 0x00001 Debugging mode. Do not actually perform any optimizations + ** but instead return one line of text for each optimization + ** that would have been done. Off by default. ** - ** 0x0002 Run ANALYZE on tables that might benefit. On by default. - ** See below for additional information. + ** 0x00002 Run ANALYZE on tables that might benefit. On by default. + ** See below for additional information. ** - ** 0x0004 (Not yet implemented) Record usage and performance - ** information from the current session in the - ** database file so that it will be available to "optimize" - ** pragmas run by future database connections. + ** 0x00010 Run all ANALYZE operations using an analysis_limit that + ** is the lessor of the current analysis_limit and the + ** SQLITE_DEFAULT_OPTIMIZE_LIMIT compile-time option. + ** The default value of SQLITE_DEFAULT_OPTIMIZE_LIMIT is + ** currently (2024-02-19) set to 2000, which is such that + ** the worst case run-time for PRAGMA optimize on a 100MB + ** database will usually be less than 100 milliseconds on + ** a RaspberryPI-4 class machine. On by default. ** - ** 0x0008 (Not yet implemented) Create indexes that might have - ** been helpful to recent queries + ** 0x10000 Look at tables to see if they need to be reanalyzed + ** due to growth or shrinkage even if they have not been + ** queried during the current connection. Off by default. ** - ** The default MASK is and always shall be 0xfffe. 0xfffe means perform all - ** of the optimizations listed above except Debug Mode, including new - ** optimizations that have not yet been invented. If new optimizations are - ** ever added that should be off by default, those off-by-default - ** optimizations will have bitmasks of 0x10000 or larger. + ** The default MASK is and always shall be 0x0fffe. In the current + ** implementation, the default mask only covers the 0x00002 optimization, + ** though additional optimizations that are covered by 0x0fffe might be + ** added in the future. Optimizations that are off by default and must + ** be explicitly requested have masks of 0x10000 or greater. ** ** DETERMINATION OF WHEN TO RUN ANALYZE ** ** In the current implementation, a table is analyzed if only if all of ** the following are true: ** - ** (1) MASK bit 0x02 is set. + ** (1) MASK bit 0x00002 is set. + ** + ** (2) The table is an ordinary table, not a virtual table or view. ** - ** (2) The query planner used sqlite_stat1-style statistics for one or - ** more indexes of the table at some point during the lifetime of - ** the current connection. + ** (3) The table name does not begin with "sqlite_". ** - ** (3) One or more indexes of the table are currently unanalyzed OR - ** the number of rows in the table has increased by 25 times or more - ** since the last time ANALYZE was run. + ** (4) One or more of the following is true: + ** (4a) The 0x10000 MASK bit is set. + ** (4b) One or more indexes on the table lacks an entry + ** in the sqlite_stat1 table. + ** (4c) The query planner used sqlite_stat1-style statistics for one + ** or more indexes of the table at some point during the lifetime + ** of the current connection. + ** + ** (5) One or more of the following is true: + ** (5a) One or more indexes on the table lacks an entry + ** in the sqlite_stat1 table. (Same as 4a) + ** (5b) The number of rows in the table has increased or decreased by + ** 10-fold. In other words, the current size of the table is + ** 10 times larger than the size in sqlite_stat1 or else the + ** current size is less than 1/10th the size in sqlite_stat1. ** ** The rules for when tables are analyzed are likely to change in - ** future releases. + ** future releases. Future versions of SQLite might accept a string + ** literal argument to this pragma that contains a mnemonic description + ** of the options rather than a bitmap. */ case PragTyp_OPTIMIZE: { int iDbLast; /* Loop termination point for the schema loop */ @@ -123165,9 +147319,13 @@ SQLITE_PRIVATE void sqlite3Pragma( Schema *pSchema; /* The current schema */ Table *pTab; /* A table in the schema */ Index *pIdx; /* An index of the table */ - LogEst szThreshold; /* Size threshold above which reanalysis is needd */ + LogEst szThreshold; /* Size threshold above which reanalysis needed */ char *zSubSql; /* SQL statement for the OP_SqlExec opcode */ u32 opMask; /* Mask of operations to perform */ + int nLimit; /* Analysis limit to use */ + int nCheck = 0; /* Number of tables to be optimized */ + int nBtree = 0; /* Number of btrees to scan */ + int nIndex; /* Number of indexes on the current table */ if( zRight ){ opMask = (u32)sqlite3Atoi(zRight); @@ -123175,6 +147333,14 @@ SQLITE_PRIVATE void sqlite3Pragma( }else{ opMask = 0xfffe; } + if( (opMask & 0x10)==0 ){ + nLimit = 0; + }else if( db->nAnalysisLimit>0 + && db->nAnalysisLimitnTab++; for(iDbLast = zDb?iDb:db->nDb-1; iDb<=iDbLast; iDb++){ if( iDb==1 ) continue; @@ -123183,23 +147349,61 @@ SQLITE_PRIVATE void sqlite3Pragma( for(k=sqliteHashFirst(&pSchema->tblHash); k; k=sqliteHashNext(k)){ pTab = (Table*)sqliteHashData(k); - /* If table pTab has not been used in a way that would benefit from - ** having analysis statistics during the current session, then skip it. - ** This also has the effect of skipping virtual tables and views */ - if( (pTab->tabFlags & TF_StatsUsed)==0 ) continue; + /* This only works for ordinary tables */ + if( !IsOrdinaryTable(pTab) ) continue; - /* Reanalyze if the table is 25 times larger than the last analysis */ - szThreshold = pTab->nRowLogEst + 46; assert( sqlite3LogEst(25)==46 ); + /* Do not scan system tables */ + if( 0==sqlite3StrNICmp(pTab->zName, "sqlite_", 7) ) continue; + + /* Find the size of the table as last recorded in sqlite_stat1. + ** If any index is unanalyzed, then the threshold is -1 to + ** indicate a new, unanalyzed index + */ + szThreshold = pTab->nRowLogEst; + nIndex = 0; for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + nIndex++; if( !pIdx->hasStat1 ){ - szThreshold = 0; /* Always analyze if any index lacks statistics */ - break; + szThreshold = -1; /* Always analyze if any index lacks statistics */ } } - if( szThreshold ){ - sqlite3OpenTable(pParse, iTabCur, iDb, pTab, OP_OpenRead); - sqlite3VdbeAddOp3(v, OP_IfSmaller, iTabCur, - sqlite3VdbeCurrentAddr(v)+2+(opMask&1), szThreshold); + + /* If table pTab has not been used in a way that would benefit from + ** having analysis statistics during the current session, then skip it, + ** unless the 0x10000 MASK bit is set. */ + if( (pTab->tabFlags & TF_MaybeReanalyze)!=0 ){ + /* Check for size change if stat1 has been used for a query */ + }else if( opMask & 0x10000 ){ + /* Check for size change if 0x10000 is set */ + }else if( pTab->pIndex!=0 && szThreshold<0 ){ + /* Do analysis if unanalyzed indexes exists */ + }else{ + /* Otherwise, we can skip this table */ + continue; + } + + nCheck++; + if( nCheck==2 ){ + /* If ANALYZE might be invoked two or more times, hold a write + ** transaction for efficiency */ + sqlite3BeginWriteOperation(pParse, 0, iDb); + } + nBtree += nIndex+1; + + /* Reanalyze if the table is 10 times larger or smaller than + ** the last analysis. Unconditional reanalysis if there are + ** unanalyzed indexes. */ + sqlite3OpenTable(pParse, iTabCur, iDb, pTab, OP_OpenRead); + if( szThreshold>=0 ){ + const LogEst iRange = 33; /* 10x size change */ + sqlite3VdbeAddOp4Int(v, OP_IfSizeBetween, iTabCur, + sqlite3VdbeCurrentAddr(v)+2+(opMask&1), + szThreshold>=iRange ? szThreshold-iRange : -1, + szThreshold+iRange); + VdbeCoverage(v); + }else{ + sqlite3VdbeAddOp2(v, OP_Rewind, iTabCur, + sqlite3VdbeCurrentAddr(v)+2+(opMask&1)); VdbeCoverage(v); } zSubSql = sqlite3MPrintf(db, "ANALYZE \"%w\".\"%w\"", @@ -123209,11 +147413,27 @@ SQLITE_PRIVATE void sqlite3Pragma( sqlite3VdbeAddOp4(v, OP_String8, 0, r1, 0, zSubSql, P4_DYNAMIC); sqlite3VdbeAddOp2(v, OP_ResultRow, r1, 1); }else{ - sqlite3VdbeAddOp4(v, OP_SqlExec, 0, 0, 0, zSubSql, P4_DYNAMIC); + sqlite3VdbeAddOp4(v, OP_SqlExec, nLimit ? 0x02 : 00, nLimit, 0, + zSubSql, P4_DYNAMIC); } } } sqlite3VdbeAddOp0(v, OP_Expire); + + /* In a schema with a large number of tables and indexes, scale back + ** the analysis_limit to avoid excess run-time in the worst case. + */ + if( !db->mallocFailed && nLimit>0 && nBtree>100 ){ + int iAddr, iEnd; + VdbeOp *aOp; + nLimit = 100*nLimit/nBtree; + if( nLimit<100 ) nLimit = 100; + aOp = sqlite3VdbeGetOp(v, 0); + iEnd = sqlite3VdbeCurrentAddr(v); + for(iAddr=0; iAddr0 && (iPrior==0 || iPrior>N) ) sqlite3_hard_heap_limit64(N); + } + returnSingleInt(v, sqlite3_hard_heap_limit64(-1)); + break; + } + /* ** PRAGMA threads ** PRAGMA threads = N @@ -123274,6 +147515,25 @@ SQLITE_PRIVATE void sqlite3Pragma( break; } + /* + ** PRAGMA analysis_limit + ** PRAGMA analysis_limit = N + ** + ** Configure the maximum number of rows that ANALYZE will examine + ** in each index that it looks at. Return the new limit. + */ + case PragTyp_ANALYSIS_LIMIT: { + sqlite3_int64 N; + if( zRight + && sqlite3DecOrHexToI64(zRight, &N)==SQLITE_OK /* IMP: R-40975-20399 */ + && N>=0 + ){ + db->nAnalysisLimit = (int)(N&0x7fffffff); + } + returnSingleInt(v, db->nAnalysisLimit); /* IMP: R-57594-65522 */ + break; + } + #if defined(SQLITE_DEBUG) || defined(SQLITE_TEST) /* ** Report the current state of file logs for all databases @@ -123292,7 +147552,7 @@ SQLITE_PRIVATE void sqlite3Pragma( pBt = db->aDb[i].pBt; if( pBt==0 || sqlite3BtreePager(pBt)==0 ){ zState = "closed"; - }else if( sqlite3_file_control(db, i ? db->aDb[i].zDbSName : 0, + }else if( sqlite3_file_control(db, i ? db->aDb[i].zDbSName : 0, SQLITE_FCNTL_LOCKSTATE, &j)==SQLITE_OK ){ zState = azLockName[j]; } @@ -123302,57 +147562,11 @@ SQLITE_PRIVATE void sqlite3Pragma( } #endif -#ifdef SQLITE_HAS_CODEC - /* Pragma iArg - ** ---------- ------ - ** key 0 - ** rekey 1 - ** hexkey 2 - ** hexrekey 3 - ** textkey 4 - ** textrekey 5 - */ - case PragTyp_KEY: { - if( zRight ){ - int n = pPragma->iArg<4 ? sqlite3Strlen30(zRight) : -1; - if( (pPragma->iArg & 1)==0 ){ - sqlite3_key_v2(db, zDb, zRight, n); - }else{ - sqlite3_rekey_v2(db, zDb, zRight, n); - } - } - break; - } - case PragTyp_HEXKEY: { - if( zRight ){ - u8 iByte; - int i; - char zKey[40]; - for(i=0, iByte=0; iiArg & 1)==0 ){ - sqlite3_key_v2(db, zDb, zKey, i/2); - }else{ - sqlite3_rekey_v2(db, zDb, zKey, i/2); - } - } - break; - } -#endif -#if defined(SQLITE_HAS_CODEC) || defined(SQLITE_ENABLE_CEROD) +#if defined(SQLITE_ENABLE_CEROD) case PragTyp_ACTIVATE_EXTENSIONS: if( zRight ){ -#ifdef SQLITE_HAS_CODEC - if( sqlite3StrNICmp(zRight, "see-", 4)==0 ){ - sqlite3_activate_see(&zRight[4]); - } -#endif -#ifdef SQLITE_ENABLE_CEROD if( sqlite3StrNICmp(zRight, "cerod-", 6)==0 ){ sqlite3_activate_cerod(&zRight[6]); } -#endif } break; #endif @@ -123362,7 +147576,7 @@ SQLITE_PRIVATE void sqlite3Pragma( /* The following block is a no-op unless SQLITE_DEBUG is defined. Its only ** purpose is to execute assert() statements to verify that if the ** PragFlg_NoColumns1 flag is set and the caller specified an argument - ** to the PRAGMA, the implementation has not added any OP_ResultRow + ** to the PRAGMA, the implementation has not added any OP_ResultRow ** instructions to the VM. */ if( (pPragma->mPragFlg & PragFlg_NoColumns1) && zRight ){ sqlite3VdbeVerifyNoResultRow(v); @@ -123393,7 +147607,7 @@ struct PragmaVtabCursor { char *azArg[2]; /* Value of the argument and schema */ }; -/* +/* ** Pragma virtual table module xConnect method. */ static int pragmaVtabConnect( @@ -123455,7 +147669,7 @@ static int pragmaVtabConnect( return rc; } -/* +/* ** Pragma virtual table module xDisconnect method. */ static int pragmaVtabDisconnect(sqlite3_vtab *pVtab){ @@ -123483,9 +147697,9 @@ static int pragmaVtabBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ seen[0] = 0; seen[1] = 0; for(i=0; inConstraint; i++, pConstraint++){ - if( pConstraint->usable==0 ) continue; - if( pConstraint->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue; if( pConstraint->iColumn < pTab->iHidden ) continue; + if( pConstraint->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue; + if( pConstraint->usable==0 ) return SQLITE_CONSTRAINT; j = pConstraint->iColumn - pTab->iHidden; assert( j < 2 ); seen[j] = i+1; @@ -123498,12 +147712,13 @@ static int pragmaVtabBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ j = seen[0]-1; pIdxInfo->aConstraintUsage[j].argvIndex = 1; pIdxInfo->aConstraintUsage[j].omit = 1; - if( seen[1]==0 ) return SQLITE_OK; pIdxInfo->estimatedCost = (double)20; pIdxInfo->estimatedRows = 20; - j = seen[1]-1; - pIdxInfo->aConstraintUsage[j].argvIndex = 2; - pIdxInfo->aConstraintUsage[j].omit = 1; + if( seen[1] ){ + j = seen[1]-1; + pIdxInfo->aConstraintUsage[j].argvIndex = 2; + pIdxInfo->aConstraintUsage[j].omit = 1; + } return SQLITE_OK; } @@ -123523,6 +147738,7 @@ static void pragmaVtabCursorClear(PragmaVtabCursor *pCsr){ int i; sqlite3_finalize(pCsr->pPragma); pCsr->pPragma = 0; + pCsr->iRowid = 0; for(i=0; iazArg); i++){ sqlite3_free(pCsr->azArg[i]); pCsr->azArg[i] = 0; @@ -123553,11 +147769,11 @@ static int pragmaVtabNext(sqlite3_vtab_cursor *pVtabCursor){ return rc; } -/* +/* ** Pragma virtual table module xFilter method. */ static int pragmaVtabFilter( - sqlite3_vtab_cursor *pVtabCursor, + sqlite3_vtab_cursor *pVtabCursor, int idxNum, const char *idxStr, int argc, sqlite3_value **argv ){ @@ -123612,11 +147828,11 @@ static int pragmaVtabEof(sqlite3_vtab_cursor *pVtabCursor){ } /* The xColumn method simply returns the corresponding column from -** the PRAGMA. +** the PRAGMA. */ static int pragmaVtabColumn( - sqlite3_vtab_cursor *pVtabCursor, - sqlite3_context *ctx, + sqlite3_vtab_cursor *pVtabCursor, + sqlite3_context *ctx, int i ){ PragmaVtabCursor *pCsr = (PragmaVtabCursor*)pVtabCursor; @@ -123629,7 +147845,7 @@ static int pragmaVtabColumn( return SQLITE_OK; } -/* +/* ** Pragma virtual table module xRowid method. */ static int pragmaVtabRowid(sqlite3_vtab_cursor *pVtabCursor, sqlite_int64 *p){ @@ -123663,7 +147879,8 @@ static const sqlite3_module pragmaVtabModule = { 0, /* xSavepoint */ 0, /* xRelease */ 0, /* xRollbackTo */ - 0 /* xShadowName */ + 0, /* xShadowName */ + 0 /* xIntegrity */ }; /* @@ -123710,7 +147927,7 @@ SQLITE_PRIVATE Module *sqlite3PragmaVtabRegister(sqlite3 *db, const char *zName) */ static void corruptSchema( InitData *pData, /* Initialization context */ - const char *zObj, /* Object being parsed at the point of error */ + char **azObj, /* Type and name of object being parsed */ const char *zExtra /* Error information */ ){ sqlite3 *db = pData->db; @@ -123718,14 +147935,24 @@ static void corruptSchema( pData->rc = SQLITE_NOMEM_BKPT; }else if( pData->pzErrMsg[0]!=0 ){ /* A error message has already been generated. Do not overwrite it */ - }else if( pData->mInitFlags & INITFLAG_AlterTable ){ - *pData->pzErrMsg = sqlite3DbStrDup(db, zExtra); + }else if( pData->mInitFlags & (INITFLAG_AlterMask) ){ + static const char *azAlterType[] = { + "rename", + "drop column", + "add column", + "drop constraint" + }; + *pData->pzErrMsg = sqlite3MPrintf(db, + "error in %s %s after %s: %s", azObj[0], azObj[1], + azAlterType[(pData->mInitFlags&INITFLAG_AlterMask)-1], + zExtra + ); pData->rc = SQLITE_ERROR; }else if( db->flags & SQLITE_WriteSchema ){ pData->rc = SQLITE_CORRUPT_BKPT; }else{ char *z; - if( zObj==0 ) zObj = "?"; + const char *zObj = azObj[1] ? azObj[1] : "?"; z = sqlite3MPrintf(db, "malformed database schema (%s)", zObj); if( zExtra && zExtra[0] ) z = sqlite3MPrintf(db, "%z - %s", z, zExtra); *pData->pzErrMsg = z; @@ -123746,6 +147973,18 @@ SQLITE_PRIVATE int sqlite3IndexHasDuplicateRootPage(Index *pIndex){ return 0; } +/* forward declaration */ +static int sqlite3Prepare( + sqlite3 *db, /* Database handle. */ + const char *zSql, /* UTF-8 encoded SQL statement. */ + int nBytes, /* Length of zSql in bytes. */ + u32 prepFlags, /* Zero or more SQLITE_PREPARE_* flags */ + Vdbe *pReprepare, /* VM being reprepared */ + sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */ + const char **pzTail /* OUT: End of parsed string */ +); + + /* ** This is the callback routine for the code that initializes the ** database. See sqlite3Init() below for additional information. @@ -123753,9 +147992,11 @@ SQLITE_PRIVATE int sqlite3IndexHasDuplicateRootPage(Index *pIndex){ ** ** Each callback contains the following information: ** -** argv[0] = name of thing being created -** argv[1] = root page number for table or index. 0 for trigger or view. -** argv[2] = SQL text for the CREATE statement. +** argv[0] = type of object: "table", "index", "trigger", or "view". +** argv[1] = name of thing being created +** argv[2] = associated table if an index or trigger +** argv[3] = root page number for table or index. 0 for trigger or view. +** argv[4] = SQL text for the CREATE statement. ** */ SQLITE_PRIVATE int sqlite3InitCallback(void *pInit, int argc, char **argv, char **NotUsed){ @@ -123763,25 +148004,32 @@ SQLITE_PRIVATE int sqlite3InitCallback(void *pInit, int argc, char **argv, char sqlite3 *db = pData->db; int iDb = pData->iDb; - assert( argc==3 ); + assert( argc==5 ); UNUSED_PARAMETER2(NotUsed, argc); assert( sqlite3_mutex_held(db->mutex) ); - DbClearProperty(db, iDb, DB_Empty); + db->mDbFlags |= DBFLAG_EncodingFixed; + if( argv==0 ) return 0; /* Might happen if EMPTY_RESULT_CALLBACKS are on */ pData->nInitRow++; if( db->mallocFailed ){ - corruptSchema(pData, argv[0], 0); + corruptSchema(pData, argv, 0); return 1; } assert( iDb>=0 && iDbnDb ); - if( argv==0 ) return 0; /* Might happen if EMPTY_RESULT_CALLBACKS are on */ - if( argv[1]==0 ){ - corruptSchema(pData, argv[0], 0); - }else if( sqlite3_strnicmp(argv[2],"create ",7)==0 ){ + if( argv[3]==0 ){ + corruptSchema(pData, argv, 0); + }else if( argv[4] + && 'c'==sqlite3UpperToLower[(unsigned char)argv[4][0]] + && 'r'==sqlite3UpperToLower[(unsigned char)argv[4][1]] ){ /* Call the parser to process a CREATE TABLE, INDEX or VIEW. ** But because db->init.busy is set to 1, no VDBE code is generated ** or executed. All the parser does is build the internal data ** structures that describe the table, index, or view. + ** + ** No other valid SQL statement, other than the variable CREATE statements, + ** can begin with the letters "C" and "R". Thus, it is not possible run + ** any other kind of statement while parsing the schema, even a corrupt + ** schema. */ int rc; u8 saved_iDb = db->init.iDb; @@ -123790,9 +148038,17 @@ SQLITE_PRIVATE int sqlite3InitCallback(void *pInit, int argc, char **argv, char assert( db->init.busy ); db->init.iDb = iDb; - db->init.newTnum = sqlite3Atoi(argv[1]); + if( sqlite3GetUInt32(argv[3], &db->init.newTnum)==0 + || (db->init.newTnum>pData->mxPage && pData->mxPage>0) + ){ + if( sqlite3Config.bExtraSchemaChecks ){ + corruptSchema(pData, argv, "invalid rootpage"); + } + } db->init.orphanTrigger = 0; - TESTONLY(rcp = ) sqlite3_prepare(db, argv[2], -1, &pStmt, 0); + db->init.azInit = (const char**)argv; + pStmt = 0; + TESTONLY(rcp = ) sqlite3Prepare(db, argv[4], -1, 0, 0, &pStmt, 0); rc = db->errCode; assert( (rc&0xFF)==(rcp&0xFF) ); db->init.iDb = saved_iDb; @@ -123801,17 +148057,18 @@ SQLITE_PRIVATE int sqlite3InitCallback(void *pInit, int argc, char **argv, char if( db->init.orphanTrigger ){ assert( iDb==1 ); }else{ - pData->rc = rc; + if( rc > pData->rc ) pData->rc = rc; if( rc==SQLITE_NOMEM ){ sqlite3OomFault(db); }else if( rc!=SQLITE_INTERRUPT && (rc&0xFF)!=SQLITE_LOCKED ){ - corruptSchema(pData, argv[0], sqlite3_errmsg(db)); + corruptSchema(pData, argv, sqlite3_errmsg(db)); } } } + db->init.azInit = sqlite3StdType; /* Any array of string ptrs will do */ sqlite3_finalize(pStmt); - }else if( argv[0]==0 || (argv[2]!=0 && argv[2][0]!=0) ){ - corruptSchema(pData, argv[0], 0); + }else if( argv[1]==0 || (argv[4]!=0 && argv[4][0]!=0) ){ + corruptSchema(pData, argv, 0); }else{ /* If the SQL column is blank it means this is an index that ** was created to be the PRIMARY KEY or to fulfill a UNIQUE @@ -123820,13 +148077,18 @@ SQLITE_PRIVATE int sqlite3InitCallback(void *pInit, int argc, char **argv, char ** to do here is record the root page number for that index. */ Index *pIndex; - pIndex = sqlite3FindIndex(db, argv[0], db->aDb[iDb].zDbSName); - if( pIndex==0 - || sqlite3GetInt32(argv[1],&pIndex->tnum)==0 + pIndex = sqlite3FindIndex(db, argv[1], db->aDb[iDb].zDbSName); + if( pIndex==0 ){ + corruptSchema(pData, argv, "orphan index"); + }else + if( sqlite3GetUInt32(argv[3],&pIndex->tnum)==0 || pIndex->tnum<2 + || pIndex->tnum>pData->mxPage || sqlite3IndexHasDuplicateRootPage(pIndex) ){ - corruptSchema(pData, argv[0], pIndex?"invalid rootpage":"orphan index"); + if( sqlite3Config.bExtraSchemaChecks ){ + corruptSchema(pData, argv, "invalid rootpage"); + } } } return 0; @@ -123847,11 +148109,12 @@ SQLITE_PRIVATE int sqlite3InitOne(sqlite3 *db, int iDb, char **pzErrMsg, u32 mFl int size; #endif Db *pDb; - char const *azArg[4]; + char const *azArg[6]; int meta[5]; InitData initData; - const char *zMasterName; + const char *zSchemaTabName; int openedTransaction = 0; + int mask = ((db->mDbFlags & DBFLAG_EncodingFixed) | ~DBFLAG_EncodingFixed); assert( (db->mDbFlags & DBFLAG_SchemaKnownOk)==0 ); assert( iDb>=0 && iDbnDb ); @@ -123861,23 +148124,27 @@ SQLITE_PRIVATE int sqlite3InitOne(sqlite3 *db, int iDb, char **pzErrMsg, u32 mFl db->init.busy = 1; - /* Construct the in-memory representation schema tables (sqlite_master or - ** sqlite_temp_master) by invoking the parser directly. The appropriate + /* Construct the in-memory representation schema tables (sqlite_schema or + ** sqlite_temp_schema) by invoking the parser directly. The appropriate ** table name will be inserted automatically by the parser so we can just ** use the abbreviation "x" here. The parser will also automatically tag ** the schema table as read-only. */ - azArg[0] = zMasterName = SCHEMA_TABLE(iDb); - azArg[1] = "1"; - azArg[2] = "CREATE TABLE x(type text,name text,tbl_name text," + azArg[0] = "table"; + azArg[1] = zSchemaTabName = SCHEMA_TABLE(iDb); + azArg[2] = azArg[1]; + azArg[3] = "1"; + azArg[4] = "CREATE TABLE x(type text,name text,tbl_name text," "rootpage int,sql text)"; - azArg[3] = 0; + azArg[5] = 0; initData.db = db; initData.iDb = iDb; initData.rc = SQLITE_OK; initData.pzErrMsg = pzErrMsg; initData.mInitFlags = mFlags; initData.nInitRow = 0; - sqlite3InitCallback(&initData, 3, (char **)azArg, 0); + initData.mxPage = 0; + sqlite3InitCallback(&initData, 5, (char **)azArg, 0); + db->mDbFlags &= mask; if( initData.rc ){ rc = initData.rc; goto error_out; @@ -123894,10 +148161,10 @@ SQLITE_PRIVATE int sqlite3InitOne(sqlite3 *db, int iDb, char **pzErrMsg, u32 mFl } /* If there is not already a read-only (or read-write) transaction opened - ** on the b-tree database, open one now. If a transaction is opened, it + ** on the b-tree database, open one now. If a transaction is opened, it ** will be closed before this function returns. */ sqlite3BtreeEnter(pDb->pBt); - if( !sqlite3BtreeIsInReadTrans(pDb->pBt) ){ + if( sqlite3BtreeTxnState(pDb->pBt)==SQLITE_TXN_NONE ){ rc = sqlite3BtreeBeginTrans(pDb->pBt, 0, 0); if( rc!=SQLITE_OK ){ sqlite3SetString(pzErrMsg, db, sqlite3ErrStr(rc)); @@ -123937,27 +148204,25 @@ SQLITE_PRIVATE int sqlite3InitOne(sqlite3 *db, int iDb, char **pzErrMsg, u32 mFl ** as sqlite3.enc. */ if( meta[BTREE_TEXT_ENCODING-1] ){ /* text encoding */ - if( iDb==0 ){ -#ifndef SQLITE_OMIT_UTF16 + if( iDb==0 && (db->mDbFlags & DBFLAG_EncodingFixed)==0 ){ u8 encoding; +#ifndef SQLITE_OMIT_UTF16 /* If opening the main database, set ENC(db). */ encoding = (u8)meta[BTREE_TEXT_ENCODING-1] & 3; if( encoding==0 ) encoding = SQLITE_UTF8; - ENC(db) = encoding; #else - ENC(db) = SQLITE_UTF8; + encoding = SQLITE_UTF8; #endif + sqlite3SetTextEncoding(db, encoding); }else{ /* If opening an attached database, the encoding much match ENC(db) */ - if( meta[BTREE_TEXT_ENCODING-1]!=ENC(db) ){ + if( (meta[BTREE_TEXT_ENCODING-1] & 3)!=ENC(db) ){ sqlite3SetString(pzErrMsg, db, "attached databases must use the same" " text encoding as main database"); rc = SQLITE_ERROR; goto initone_error_out; } } - }else{ - DbSetProperty(db, iDb, DB_Empty); } pDb->pSchema->enc = ENC(db); @@ -124000,11 +148265,12 @@ SQLITE_PRIVATE int sqlite3InitOne(sqlite3 *db, int iDb, char **pzErrMsg, u32 mFl /* Read the schema information out of the schema tables */ assert( db->init.busy ); + initData.mxPage = sqlite3BtreeLastPage(pDb->pBt); { char *zSql; - zSql = sqlite3MPrintf(db, - "SELECT name, rootpage, sql FROM \"%w\".%s ORDER BY rowid", - db->aDb[iDb].zDbSName, zMasterName); + zSql = sqlite3MPrintf(db, + "SELECT*FROM\"%w\".%s ORDER BY rowid", + db->aDb[iDb].zDbSName, zSchemaTabName); #ifndef SQLITE_OMIT_AUTHORIZATION { sqlite3_xauth xAuth; @@ -124024,18 +148290,22 @@ SQLITE_PRIVATE int sqlite3InitOne(sqlite3 *db, int iDb, char **pzErrMsg, u32 mFl } #endif } + assert( pDb == &(db->aDb[iDb]) ); if( db->mallocFailed ){ rc = SQLITE_NOMEM_BKPT; sqlite3ResetAllSchemasOfConnection(db); - } - if( rc==SQLITE_OK || (db->flags&SQLITE_NoSchemaError)){ - /* Black magic: If the SQLITE_NoSchemaError flag is set, then consider - ** the schema loaded, even if errors occurred. In this situation the - ** current sqlite3_prepare() operation will fail, but the following one - ** will attempt to compile the supplied statement against whatever subset - ** of the schema was loaded before the error occurred. The primary - ** purpose of this is to allow access to the sqlite_master table - ** even when its contents have been corrupted. + pDb = &db->aDb[iDb]; + }else + if( rc==SQLITE_OK || ((db->flags&SQLITE_NoSchemaError) && rc!=SQLITE_NOMEM)){ + /* Hack: If the SQLITE_NoSchemaError flag is set, then consider + ** the schema loaded, even if errors (other than OOM) occurred. In + ** this situation the current sqlite3_prepare() operation will fail, + ** but the following one will attempt to compile the supplied statement + ** against whatever subset of the schema was loaded before the error + ** occurred. + ** + ** The primary purpose of this is to allow access to the sqlite_schema + ** table even when its contents have been corrupted. */ DbSetProperty(db, iDb, DB_SchemaLoaded); rc = SQLITE_OK; @@ -124069,13 +148339,12 @@ SQLITE_PRIVATE int sqlite3InitOne(sqlite3 *db, int iDb, char **pzErrMsg, u32 mFl ** error occurs, write an error message into *pzErrMsg. ** ** After a database is initialized, the DB_SchemaLoaded bit is set -** bit is set in the flags field of the Db structure. If the database -** file was of zero-length, then the DB_Empty flag is also set. +** bit is set in the flags field of the Db structure. */ SQLITE_PRIVATE int sqlite3Init(sqlite3 *db, char **pzErrMsg){ int i, rc; int commit_internal = !(db->mDbFlags&DBFLAG_SchemaChange); - + assert( sqlite3_mutex_held(db->mutex) ); assert( sqlite3BtreeHoldsMutex(db->aDb[0].pBt) ); assert( db->init.busy==0 ); @@ -124140,25 +148409,26 @@ static void schemaIsValid(Parse *pParse){ if( pBt==0 ) continue; /* If there is not already a read-only (or read-write) transaction opened - ** on the b-tree database, open one now. If a transaction is opened, it + ** on the b-tree database, open one now. If a transaction is opened, it ** will be closed immediately after reading the meta-value. */ - if( !sqlite3BtreeIsInReadTrans(pBt) ){ + if( sqlite3BtreeTxnState(pBt)==SQLITE_TXN_NONE ){ rc = sqlite3BtreeBeginTrans(pBt, 0, 0); if( rc==SQLITE_NOMEM || rc==SQLITE_IOERR_NOMEM ){ sqlite3OomFault(db); + pParse->rc = SQLITE_NOMEM; } if( rc!=SQLITE_OK ) return; openedTransaction = 1; } - /* Read the schema cookie from the database. If it does not match the + /* Read the schema cookie from the database. If it does not match the ** value stored as part of the in-memory schema representation, ** set Parse.rc to SQLITE_SCHEMA. */ sqlite3BtreeGetMeta(pBt, BTREE_SCHEMA_VERSION, (u32 *)&cookie); assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); if( cookie!=db->aDb[iDb].pSchema->schema_cookie ){ + if( DbHasProperty(db, iDb, DB_SchemaLoaded) ) pParse->rc = SQLITE_SCHEMA; sqlite3ResetOneSchema(db, iDb); - pParse->rc = SQLITE_SCHEMA; } /* Close the transaction, if one was opened. */ @@ -124176,17 +148446,18 @@ static void schemaIsValid(Parse *pParse){ ** attached database is returned. */ SQLITE_PRIVATE int sqlite3SchemaToIndex(sqlite3 *db, Schema *pSchema){ - int i = -1000000; + int i = -32768; - /* If pSchema is NULL, then return -1000000. This happens when code in + /* If pSchema is NULL, then return -32768. This happens when code in ** expr.c is trying to resolve a reference to a transient table (i.e. one - ** created by a sub-select). In this case the return value of this + ** created by a sub-select). In this case the return value of this ** function should never be used. ** - ** We return -1000000 instead of the more usual -1 simply because using - ** -1000000 as the incorrect index into db->aDb[] is much + ** We return -32768 instead of the more usual -1 simply because using + ** -32768 as the incorrect index into db->aDb[] is much ** more likely to cause a segfault than -1 (of course there are assert() - ** statements too, but it never hurts to play the odds). + ** statements too, but it never hurts to play the odds) and + ** -32768 will still fit into a 16-bit signed integer. */ assert( sqlite3_mutex_held(db->mutex) ); if( pSchema ){ @@ -124204,17 +148475,113 @@ SQLITE_PRIVATE int sqlite3SchemaToIndex(sqlite3 *db, Schema *pSchema){ /* ** Free all memory allocations in the pParse object */ -SQLITE_PRIVATE void sqlite3ParserReset(Parse *pParse){ +SQLITE_PRIVATE void sqlite3ParseObjectReset(Parse *pParse){ sqlite3 *db = pParse->db; - sqlite3DbFree(db, pParse->aLabel); - sqlite3ExprListDelete(db, pParse->pConstExpr); - if( db ){ - assert( db->lookaside.bDisable >= pParse->disableLookaside ); - db->lookaside.bDisable -= pParse->disableLookaside; + assert( db!=0 ); + assert( db->pParse==pParse ); + assert( pParse->nested==0 ); +#ifndef SQLITE_OMIT_SHARED_CACHE + if( pParse->aTableLock ) sqlite3DbNNFreeNN(db, pParse->aTableLock); +#endif + while( pParse->pCleanup ){ + ParseCleanup *pCleanup = pParse->pCleanup; + pParse->pCleanup = pCleanup->pNext; + pCleanup->xCleanup(db, pCleanup->pPtr); + sqlite3DbNNFreeNN(db, pCleanup); + } + if( pParse->aLabel ) sqlite3DbNNFreeNN(db, pParse->aLabel); + if( pParse->pConstExpr ){ + sqlite3ExprListDelete(db, pParse->pConstExpr); + } + assert( db->lookaside.bDisable >= pParse->disableLookaside ); + db->lookaside.bDisable -= pParse->disableLookaside; + db->lookaside.sz = db->lookaside.bDisable ? 0 : db->lookaside.szTrue; + assert( pParse->db->pParse==pParse ); + db->pParse = pParse->pOuterParse; +} + +/* +** Add a new cleanup operation to a Parser. The cleanup should happen when +** the parser object is destroyed. But, beware: the cleanup might happen +** immediately. +** +** Use this mechanism for uncommon cleanups. There is a higher setup +** cost for this mechanism (an extra malloc), so it should not be used +** for common cleanups that happen on most calls. But for less +** common cleanups, we save a single NULL-pointer comparison in +** sqlite3ParseObjectReset(), which reduces the total CPU cycle count. +** +** If a memory allocation error occurs, then the cleanup happens immediately. +** When either SQLITE_DEBUG or SQLITE_COVERAGE_TEST are defined, the +** pParse->earlyCleanup flag is set in that case. Calling code show verify +** that test cases exist for which this happens, to guard against possible +** use-after-free errors following an OOM. The preferred way to do this is +** to immediately follow the call to this routine with: +** +** testcase( pParse->earlyCleanup ); +** +** This routine returns a copy of its pPtr input (the third parameter) +** except if an early cleanup occurs, in which case it returns NULL. So +** another way to check for early cleanup is to check the return value. +** Or, stop using the pPtr parameter with this call and use only its +** return value thereafter. Something like this: +** +** pObj = sqlite3ParserAddCleanup(pParse, destructor, pObj); +*/ +SQLITE_PRIVATE void *sqlite3ParserAddCleanup( + Parse *pParse, /* Destroy when this Parser finishes */ + void (*xCleanup)(sqlite3*,void*), /* The cleanup routine */ + void *pPtr /* Pointer to object to be cleaned up */ +){ + ParseCleanup *pCleanup; + if( sqlite3FaultSim(300) ){ + pCleanup = 0; + sqlite3OomFault(pParse->db); + }else{ + pCleanup = sqlite3DbMallocRaw(pParse->db, sizeof(*pCleanup)); + } + if( pCleanup ){ + pCleanup->pNext = pParse->pCleanup; + pParse->pCleanup = pCleanup; + pCleanup->pPtr = pPtr; + pCleanup->xCleanup = xCleanup; + }else{ + xCleanup(pParse->db, pPtr); + pPtr = 0; +#if defined(SQLITE_DEBUG) || defined(SQLITE_COVERAGE_TEST) + pParse->earlyCleanup = 1; +#endif } - pParse->disableLookaside = 0; + return pPtr; } +/* +** Turn bulk memory into a valid Parse object and link that Parse object +** into database connection db. +** +** Call sqlite3ParseObjectReset() to undo this operation. +** +** Caution: Do not confuse this routine with sqlite3ParseObjectInit() which +** is generated by Lemon. +*/ +SQLITE_PRIVATE void sqlite3ParseObjectInit(Parse *pParse, sqlite3 *db){ + memset(PARSE_HDR(pParse), 0, PARSE_HDR_SZ); + memset(PARSE_TAIL(pParse), 0, PARSE_TAIL_SZ); + assert( db->pParse!=pParse ); + pParse->pOuterParse = db->pParse; + db->pParse = pParse; + pParse->db = db; + if( db->mallocFailed ) sqlite3ErrorMsg(pParse, "out of memory"); +} + +/* +** Maximum number of times that we will try again to prepare a statement +** that returns SQLITE_ERROR_RETRY. +*/ +#ifndef SQLITE_MAX_PREPARE_RETRY +# define SQLITE_MAX_PREPARE_RETRY 25 +#endif + /* ** Compile the UTF-8 encoded SQL statement zSql into a statement handle. */ @@ -124227,16 +148594,28 @@ static int sqlite3Prepare( sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */ const char **pzTail /* OUT: End of parsed string */ ){ - char *zErrMsg = 0; /* Error message */ int rc = SQLITE_OK; /* Result code */ int i; /* Loop counter */ Parse sParse; /* Parsing context */ - memset(&sParse, 0, PARSE_HDR_SZ); + /* sqlite3ParseObjectInit(&sParse, db); // inlined for performance */ + memset(PARSE_HDR(&sParse), 0, PARSE_HDR_SZ); memset(PARSE_TAIL(&sParse), 0, PARSE_TAIL_SZ); - sParse.pReprepare = pReprepare; + sParse.pOuterParse = db->pParse; + db->pParse = &sParse; + sParse.db = db; + if( pReprepare ){ + sParse.pReprepare = pReprepare; + sParse.explain = sqlite3_stmt_isexplain((sqlite3_stmt*)pReprepare); + }else{ + assert( sParse.pReprepare==0 ); + } assert( ppStmt && *ppStmt==0 ); - /* assert( !db->mallocFailed ); // not true with SQLITE_USE_ALLOCA */ + if( db->mallocFailed ){ + sqlite3ErrorMsg(&sParse, "out of memory"); + db->errCode = rc = SQLITE_NOMEM; + goto end_prepare; + } assert( sqlite3_mutex_held(db->mutex) ); /* For a long-term use prepared statement avoid the use of @@ -124244,9 +148623,9 @@ static int sqlite3Prepare( */ if( prepFlags & SQLITE_PREPARE_PERSISTENT ){ sParse.disableLookaside++; - db->lookaside.bDisable++; + DisableLookaside; } - sParse.disableVtab = (prepFlags & SQLITE_PREPARE_NO_VTAB)!=0; + sParse.prepFlags = prepFlags & 0xff; /* Check to verify that it is possible to get a read lock on all ** database schemas. The inability to get a read lock indicates that @@ -124263,31 +148642,34 @@ static int sqlite3Prepare( ** This thread is currently holding mutexes on all Btrees (because ** of the sqlite3BtreeEnterAll() in sqlite3LockAndPrepare()) so it ** is not possible for another thread to start a new schema change - ** while this routine is running. Hence, we do not need to hold - ** locks on the schema, we just need to make sure nobody else is + ** while this routine is running. Hence, we do not need to hold + ** locks on the schema, we just need to make sure nobody else is ** holding them. ** ** Note that setting READ_UNCOMMITTED overrides most lock detection, ** but it does *not* override schema lock detection, so this all still ** works even if READ_UNCOMMITTED is set. */ - for(i=0; inDb; i++) { - Btree *pBt = db->aDb[i].pBt; - if( pBt ){ - assert( sqlite3BtreeHoldsMutex(pBt) ); - rc = sqlite3BtreeSchemaLocked(pBt); - if( rc ){ - const char *zDb = db->aDb[i].zDbSName; - sqlite3ErrorWithMsg(db, rc, "database schema is locked: %s", zDb); - testcase( db->flags & SQLITE_ReadUncommit ); - goto end_prepare; + if( !db->noSharedCache ){ + for(i=0; inDb; i++) { + Btree *pBt = db->aDb[i].pBt; + if( pBt ){ + assert( sqlite3BtreeHoldsMutex(pBt) ); + rc = sqlite3BtreeSchemaLocked(pBt); + if( rc ){ + const char *zDb = db->aDb[i].zDbSName; + sqlite3ErrorWithMsg(db, rc, "database schema is locked: %s", zDb); + testcase( db->flags & SQLITE_ReadUncommit ); + goto end_prepare; + } } } } - sqlite3VtabUnlockList(db); +#ifndef SQLITE_OMIT_VIRTUALTABLE + if( db->pDisconnect ) sqlite3VtabUnlockList(db); +#endif - sParse.db = db; if( nBytes>=0 && (nBytes==0 || zSql[nBytes-1]!=0) ){ char *zSqlCopy; int mxLen = db->aLimit[SQLITE_LIMIT_SQL_LENGTH]; @@ -124300,68 +148682,50 @@ static int sqlite3Prepare( } zSqlCopy = sqlite3DbStrNDup(db, zSql, nBytes); if( zSqlCopy ){ - sqlite3RunParser(&sParse, zSqlCopy, &zErrMsg); + sqlite3RunParser(&sParse, zSqlCopy); sParse.zTail = &zSql[sParse.zTail-zSqlCopy]; sqlite3DbFree(db, zSqlCopy); }else{ sParse.zTail = &zSql[nBytes]; } }else{ - sqlite3RunParser(&sParse, zSql, &zErrMsg); + sqlite3RunParser(&sParse, zSql); } assert( 0==sParse.nQueryLoop ); - if( sParse.rc==SQLITE_DONE ) sParse.rc = SQLITE_OK; - if( sParse.checkSchema ){ - schemaIsValid(&sParse); - } - if( db->mallocFailed ){ - sParse.rc = SQLITE_NOMEM_BKPT; - } if( pzTail ){ *pzTail = sParse.zTail; } - rc = sParse.rc; - -#ifndef SQLITE_OMIT_EXPLAIN - if( rc==SQLITE_OK && sParse.pVdbe && sParse.explain ){ - static const char * const azColName[] = { - "addr", "opcode", "p1", "p2", "p3", "p4", "p5", "comment", - "id", "parent", "notused", "detail" - }; - int iFirst, mx; - if( sParse.explain==2 ){ - sqlite3VdbeSetNumCols(sParse.pVdbe, 4); - iFirst = 8; - mx = 12; - }else{ - sqlite3VdbeSetNumCols(sParse.pVdbe, 8); - iFirst = 0; - mx = 8; - } - for(i=iFirst; iinit.busy==0 ){ sqlite3VdbeSetSql(sParse.pVdbe, zSql, (int)(sParse.zTail-zSql), prepFlags); } - if( sParse.pVdbe && (rc!=SQLITE_OK || db->mallocFailed) ){ - sqlite3VdbeFinalize(sParse.pVdbe); - assert(!(*ppStmt)); + if( db->mallocFailed ){ + sParse.rc = SQLITE_NOMEM_BKPT; + sParse.checkSchema = 0; + } + if( sParse.rc!=SQLITE_OK && sParse.rc!=SQLITE_DONE ){ + if( sParse.checkSchema && db->init.busy==0 ){ + schemaIsValid(&sParse); + } + if( sParse.pVdbe ){ + sqlite3VdbeFinalize(sParse.pVdbe); + } + assert( 0==(*ppStmt) ); + rc = sParse.rc; + if( sParse.zErrMsg ){ + sqlite3ErrorWithMsg(db, rc, "%s", sParse.zErrMsg); + sqlite3DbFree(db, sParse.zErrMsg); + }else{ + sqlite3Error(db, rc); + } }else{ + assert( sParse.zErrMsg==0 ); *ppStmt = (sqlite3_stmt*)sParse.pVdbe; + rc = SQLITE_OK; + sqlite3ErrorClear(db); } - if( zErrMsg ){ - sqlite3ErrorWithMsg(db, rc, "%s", zErrMsg); - sqlite3DbFree(db, zErrMsg); - }else{ - sqlite3Error(db, rc); - } /* Delete any TriggerPrg structures allocated while parsing this statement. */ while( sParse.pTriggerPrg ){ @@ -124372,7 +148736,7 @@ static int sqlite3Prepare( end_prepare: - sqlite3ParserReset(&sParse); + sqlite3ParseObjectReset(&sParse); return rc; } static int sqlite3LockAndPrepare( @@ -124402,12 +148766,17 @@ static int sqlite3LockAndPrepare( ** reset is considered a permanent error. */ rc = sqlite3Prepare(db, zSql, nBytes, prepFlags, pOld, ppStmt, pzTail); assert( rc==SQLITE_OK || *ppStmt==0 ); - }while( rc==SQLITE_ERROR_RETRY - || (rc==SQLITE_SCHEMA && (sqlite3ResetOneSchema(db,-1), cnt++)==0) ); + if( rc==SQLITE_OK || db->mallocFailed ) break; + cnt++; + }while( (rc==SQLITE_ERROR_RETRY && ALWAYS(cnt<=SQLITE_MAX_PREPARE_RETRY)) + || (rc==SQLITE_SCHEMA && (sqlite3ResetOneSchema(db,-1), cnt)==1) ); sqlite3BtreeLeaveAll(db); + assert( rc!=SQLITE_ERROR_RETRY ); rc = sqlite3ApiExit(db, rc); assert( (rc&db->errMask)==rc ); + db->busyHandler.nBusy = 0; sqlite3_mutex_leave(db->mutex); + assert( rc==SQLITE_OK || (*ppStmt)==0 ); return rc; } @@ -124417,7 +148786,7 @@ static int sqlite3LockAndPrepare( ** ** If the statement is successfully recompiled, return SQLITE_OK. Otherwise, ** if the statement cannot be recompiled because another connection has -** locked the sqlite3_master table, return SQLITE_LOCKED. If any other error +** locked the sqlite3_schema table, return SQLITE_LOCKED. If any other error ** occurs, return SQLITE_SCHEMA. */ SQLITE_PRIVATE int sqlite3Reprepare(Vdbe *p){ @@ -124518,7 +148887,7 @@ SQLITE_API int sqlite3_prepare_v3( ** Compile the UTF-16 encoded SQL statement zSql into a statement handle. */ static int sqlite3Prepare16( - sqlite3 *db, /* Database handle. */ + sqlite3 *db, /* Database handle. */ const void *zSql, /* UTF-16 encoded SQL statement. */ int nBytes, /* Length of zSql in bytes. */ u32 prepFlags, /* Zero or more SQLITE_PREPARE_* flags */ @@ -124540,12 +148909,24 @@ static int sqlite3Prepare16( if( !sqlite3SafetyCheckOk(db)||zSql==0 ){ return SQLITE_MISUSE_BKPT; } + + /* Make sure nBytes is non-negative and correct. It should be the + ** number of bytes until the end of the input buffer or until the first + ** U+0000 character. If the input nBytes is odd, convert it into + ** an even number. If the input nBytes is negative, then the input + ** must be terminated by at least one U+0000 character */ if( nBytes>=0 ){ int sz; const char *z = (const char*)zSql; for(sz=0; szmutex); zSql8 = sqlite3Utf16to8(db, zSql, nBytes, SQLITE_UTF16NATIVE); if( zSql8 ){ @@ -124559,9 +148940,9 @@ static int sqlite3Prepare16( ** the same number of characters into the UTF-16 string. */ int chars_parsed = sqlite3Utf8CharLen(zSql8, (int)(zTail8-zSql8)); - *pzTail = (u8 *)zSql + sqlite3Utf16ByteLen(zSql, chars_parsed); + *pzTail = (u8 *)zSql + sqlite3Utf16ByteLen(zSql, nBytes, chars_parsed); } - sqlite3DbFree(db, zSql8); + sqlite3DbFree(db, zSql8); rc = sqlite3ApiExit(db, rc); sqlite3_mutex_leave(db->mutex); return rc; @@ -124576,7 +148957,7 @@ static int sqlite3Prepare16( ** occurs. */ SQLITE_API int sqlite3_prepare16( - sqlite3 *db, /* Database handle. */ + sqlite3 *db, /* Database handle. */ const void *zSql, /* UTF-16 encoded SQL statement. */ int nBytes, /* Length of zSql in bytes. */ sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */ @@ -124588,7 +148969,7 @@ SQLITE_API int sqlite3_prepare16( return rc; } SQLITE_API int sqlite3_prepare16_v2( - sqlite3 *db, /* Database handle. */ + sqlite3 *db, /* Database handle. */ const void *zSql, /* UTF-16 encoded SQL statement. */ int nBytes, /* Length of zSql in bytes. */ sqlite3_stmt **ppStmt, /* OUT: A pointer to the prepared statement */ @@ -124600,7 +148981,7 @@ SQLITE_API int sqlite3_prepare16_v2( return rc; } SQLITE_API int sqlite3_prepare16_v3( - sqlite3 *db, /* Database handle. */ + sqlite3 *db, /* Database handle. */ const void *zSql, /* UTF-16 encoded SQL statement. */ int nBytes, /* Length of zSql in bytes. */ unsigned int prepFlags, /* Zero or more SQLITE_PREPARE_* flags */ @@ -124635,20 +149016,6 @@ SQLITE_API int sqlite3_prepare16_v3( */ /* #include "sqliteInt.h" */ -/* -** Trace output macros -*/ -#if SELECTTRACE_ENABLED -/***/ int sqlite3SelectTrace = 0; -# define SELECTTRACE(K,P,S,X) \ - if(sqlite3SelectTrace&(K)) \ - sqlite3DebugPrintf("%u/%d/%p: ",(S)->selId,(P)->addrExplain,(S)),\ - sqlite3DebugPrintf X -#else -# define SELECTTRACE(K,P,S,X) -#endif - - /* ** An instance of the following object is used to record information about ** how to process the DISTINCT keyword, to simplify passing that information @@ -124656,7 +149023,7 @@ SQLITE_API int sqlite3_prepare16_v3( */ typedef struct DistinctCtx DistinctCtx; struct DistinctCtx { - u8 isTnct; /* True if the DISTINCT keyword is present */ + u8 isTnct; /* 0: Not distinct. 1: DISTINCT 2: DISTINCT and ORDER BY */ u8 eTnctType; /* One of the WHERE_DISTINCT_* operators */ int tabTnct; /* Ephemeral table used for DISTINCT processing */ int addrTnct; /* Address of OP_OpenEphemeral opcode for tabTnct */ @@ -124700,14 +149067,22 @@ struct SortCtx { } aDefer[4]; #endif struct RowLoadInfo *pDeferredRowLoad; /* Deferred row loading info or NULL */ +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + int addrPush; /* First instruction to push data into sorter */ + int addrPushEnd; /* Last instruction that pushes data into sorter */ +#endif }; #define SORTFLAG_UseSorter 0x01 /* Use SorterOpen instead of OpenEphemeral */ /* ** Delete all the content of a Select structure. Deallocate the structure -** itself only if bFree is true. +** itself depending on the value of bFree +** +** If bFree==1, call sqlite3DbFree() on the p object. +** If bFree==0, Leave the first Select object unfreed */ static void clearSelect(sqlite3 *db, Select *p, int bFree){ + assert( db!=0 ); while( p ){ Select *pPrior = p->pPrior; sqlite3ExprListDelete(db, p->pEList); @@ -124717,13 +149092,17 @@ static void clearSelect(sqlite3 *db, Select *p, int bFree){ sqlite3ExprDelete(db, p->pHaving); sqlite3ExprListDelete(db, p->pOrderBy); sqlite3ExprDelete(db, p->pLimit); + if( OK_IF_ALWAYS_TRUE(p->pWith) ) sqlite3WithDelete(db, p->pWith); #ifndef SQLITE_OMIT_WINDOWFUNC if( OK_IF_ALWAYS_TRUE(p->pWinDefn) ){ sqlite3WindowListDelete(db, p->pWinDefn); } + while( p->pWin ){ + assert( p->pWin->ppThis==&p->pWin ); + sqlite3WindowUnlinkFromSelect(p->pWin); + } #endif - if( OK_IF_ALWAYS_TRUE(p->pWith) ) sqlite3WithDelete(db, p->pWith); - if( bFree ) sqlite3DbFreeNN(db, p); + if( bFree ) sqlite3DbNNFreeNN(db, p); p = pPrior; bFree = 1; } @@ -124735,6 +149114,7 @@ static void clearSelect(sqlite3 *db, Select *p, int bFree){ SQLITE_PRIVATE void sqlite3SelectDestInit(SelectDest *pDest, int eDest, int iParm){ pDest->eDest = (u8)eDest; pDest->iSDParm = iParm; + pDest->iSDParm2 = 0; pDest->zAffSdst = 0; pDest->iSdst = 0; pDest->nSdst = 0; @@ -124756,9 +149136,9 @@ SQLITE_PRIVATE Select *sqlite3SelectNew( u32 selFlags, /* Flag parameters, such as SF_Distinct */ Expr *pLimit /* LIMIT value. NULL means not used */ ){ - Select *pNew; + Select *pNew, *pAllocated; Select standin; - pNew = sqlite3DbMallocRawNN(pParse->db, sizeof(*pNew) ); + pAllocated = pNew = sqlite3DbMallocRawNN(pParse->db, sizeof(*pNew) ); if( pNew==0 ){ assert( pParse->db->mallocFailed ); pNew = &standin; @@ -124773,10 +149153,8 @@ SQLITE_PRIVATE Select *sqlite3SelectNew( pNew->iLimit = 0; pNew->iOffset = 0; pNew->selId = ++pParse->nSelect; - pNew->addrOpenEphm[0] = -1; - pNew->addrOpenEphm[1] = -1; pNew->nSelectRow = 0; - if( pSrc==0 ) pSrc = sqlite3DbMallocZero(pParse->db, sizeof(*pSrc)); + if( pSrc==0 ) pSrc = sqlite3DbMallocZero(pParse->db, SZ_SRCLIST_1); pNew->pSrc = pSrc; pNew->pWhere = pWhere; pNew->pGroupBy = pGroupBy; @@ -124792,12 +149170,11 @@ SQLITE_PRIVATE Select *sqlite3SelectNew( #endif if( pParse->db->mallocFailed ) { clearSelect(pParse->db, pNew, pNew!=&standin); - pNew = 0; + pAllocated = 0; }else{ assert( pNew->pSrc!=0 || pParse->nErr>0 ); } - assert( pNew!=&standin ); - return pNew; + return pAllocated; } @@ -124807,6 +149184,9 @@ SQLITE_PRIVATE Select *sqlite3SelectNew( SQLITE_PRIVATE void sqlite3SelectDelete(sqlite3 *db, Select *p){ if( OK_IF_ALWAYS_TRUE(p) ) clearSelect(db, p, 1); } +SQLITE_PRIVATE void sqlite3SelectDeleteGeneric(sqlite3 *db, void *p){ + if( ALWAYS(p) ) clearSelect(db, (Select*)p, 1); +} /* ** Return a pointer to the right-most SELECT statement in a compound. @@ -124832,6 +149212,52 @@ static Select *findRightmost(Select *p){ ** ** If an illegal or unsupported join type is seen, then still return ** a join type, but put an error in the pParse structure. +** +** These are the valid join types: +** +** +** pA pB pC Return Value +** ------- ----- ----- ------------ +** CROSS - - JT_CROSS +** INNER - - JT_INNER +** LEFT - - JT_LEFT|JT_OUTER +** LEFT OUTER - JT_LEFT|JT_OUTER +** RIGHT - - JT_RIGHT|JT_OUTER +** RIGHT OUTER - JT_RIGHT|JT_OUTER +** FULL - - JT_LEFT|JT_RIGHT|JT_OUTER +** FULL OUTER - JT_LEFT|JT_RIGHT|JT_OUTER +** NATURAL INNER - JT_NATURAL|JT_INNER +** NATURAL LEFT - JT_NATURAL|JT_LEFT|JT_OUTER +** NATURAL LEFT OUTER JT_NATURAL|JT_LEFT|JT_OUTER +** NATURAL RIGHT - JT_NATURAL|JT_RIGHT|JT_OUTER +** NATURAL RIGHT OUTER JT_NATURAL|JT_RIGHT|JT_OUTER +** NATURAL FULL - JT_NATURAL|JT_LEFT|JT_RIGHT +** NATURAL FULL OUTER JT_NATRUAL|JT_LEFT|JT_RIGHT +** +** To preserve historical compatibly, SQLite also accepts a variety +** of other non-standard and in many cases nonsensical join types. +** This routine makes as much sense at it can from the nonsense join +** type and returns a result. Examples of accepted nonsense join types +** include but are not limited to: +** +** INNER CROSS JOIN -> same as JOIN +** NATURAL CROSS JOIN -> same as NATURAL JOIN +** OUTER LEFT JOIN -> same as LEFT JOIN +** LEFT NATURAL JOIN -> same as NATURAL LEFT JOIN +** LEFT RIGHT JOIN -> same as FULL JOIN +** RIGHT OUTER FULL JOIN -> same as FULL JOIN +** CROSS CROSS CROSS JOIN -> same as JOIN +** +** The only restrictions on the join type name are: +** +** * "INNER" cannot appear together with "OUTER", "LEFT", "RIGHT", +** or "FULL". +** +** * "CROSS" cannot appear together with "OUTER", "LEFT", "RIGHT, +** or "FULL". +** +** * If "OUTER" is present then there must also be one of +** "LEFT", "RIGHT", or "FULL" */ SQLITE_PRIVATE int sqlite3JoinType(Parse *pParse, Token *pA, Token *pB, Token *pC){ int jointype = 0; @@ -124844,13 +149270,13 @@ SQLITE_PRIVATE int sqlite3JoinType(Parse *pParse, Token *pA, Token *pB, Token *p u8 nChar; /* Length of the keyword in characters */ u8 code; /* Join type mask */ } aKeyword[] = { - /* natural */ { 0, 7, JT_NATURAL }, - /* left */ { 6, 4, JT_LEFT|JT_OUTER }, - /* outer */ { 10, 5, JT_OUTER }, - /* right */ { 14, 5, JT_RIGHT|JT_OUTER }, - /* full */ { 19, 4, JT_LEFT|JT_RIGHT|JT_OUTER }, - /* inner */ { 23, 5, JT_INNER }, - /* cross */ { 28, 5, JT_INNER|JT_CROSS }, + /* (0) natural */ { 0, 7, JT_NATURAL }, + /* (1) left */ { 6, 4, JT_LEFT|JT_OUTER }, + /* (2) outer */ { 10, 5, JT_OUTER }, + /* (3) right */ { 14, 5, JT_RIGHT|JT_OUTER }, + /* (4) full */ { 19, 4, JT_LEFT|JT_RIGHT|JT_OUTER }, + /* (5) inner */ { 23, 5, JT_INNER }, + /* (6) cross */ { 28, 5, JT_INNER|JT_CROSS }, }; int i, j; apAll[0] = pA; @@ -124859,7 +149285,7 @@ SQLITE_PRIVATE int sqlite3JoinType(Parse *pParse, Token *pA, Token *pB, Token *p for(i=0; i<3 && apAll[i]; i++){ p = apAll[i]; for(j=0; jn==aKeyword[j].nChar + if( p->n==aKeyword[j].nChar && sqlite3StrNICmp((char*)p->z, &zKeyText[aKeyword[j].i], p->n)==0 ){ jointype |= aKeyword[j].code; break; @@ -124873,18 +149299,15 @@ SQLITE_PRIVATE int sqlite3JoinType(Parse *pParse, Token *pA, Token *pB, Token *p } if( (jointype & (JT_INNER|JT_OUTER))==(JT_INNER|JT_OUTER) || - (jointype & JT_ERROR)!=0 + (jointype & JT_ERROR)!=0 || + (jointype & (JT_OUTER|JT_LEFT|JT_RIGHT))==JT_OUTER ){ - const char *zSp = " "; - assert( pB!=0 ); - if( pC==0 ){ zSp++; } - sqlite3ErrorMsg(pParse, "unknown or unsupported join type: " - "%T %T%s%T", pA, pB, zSp, pC); - jointype = JT_INNER; - }else if( (jointype & JT_OUTER)!=0 - && (jointype & (JT_LEFT|JT_RIGHT))!=JT_LEFT ){ - sqlite3ErrorMsg(pParse, - "RIGHT and FULL OUTER JOINs are not currently supported"); + const char *zSp1 = " "; + const char *zSp2 = " "; + if( pB==0 ){ zSp1++; } + if( pC==0 ){ zSp2++; } + sqlite3ErrorMsg(pParse, "unknown join type: " + "%T%s%T%s%T", pA, zSp1, pB, zSp2, pC); jointype = JT_INNER; } return jointype; @@ -124894,17 +149317,61 @@ SQLITE_PRIVATE int sqlite3JoinType(Parse *pParse, Token *pA, Token *pB, Token *p ** Return the index of a column in a table. Return -1 if the column ** is not contained in the table. */ -static int columnIndex(Table *pTab, const char *zCol){ +SQLITE_PRIVATE int sqlite3ColumnIndex(Table *pTab, const char *zCol){ int i; - for(i=0; inCol; i++){ - if( sqlite3StrICmp(pTab->aCol[i].zName, zCol)==0 ) return i; + u8 h; + const Column *aCol; + int nCol; + + h = sqlite3StrIHash(zCol); + aCol = pTab->aCol; + nCol = pTab->nCol; + + /* See if the aHx gives us a lucky match */ + i = pTab->aHx[h % sizeof(pTab->aHx)]; + assert( i=nCol ) break; } return -1; } /* -** Search the first N tables in pSrc, from left to right, looking for a -** table that has a column named zCol. +** Mark a subquery result column as having been used. +*/ +SQLITE_PRIVATE void sqlite3SrcItemColumnUsed(SrcItem *pItem, int iCol){ + assert( pItem!=0 ); + assert( (int)pItem->fg.isNestedFrom == IsNestedFrom(pItem) ); + if( pItem->fg.isNestedFrom ){ + ExprList *pResults; + assert( pItem->fg.isSubquery ); + assert( pItem->u4.pSubq!=0 ); + assert( pItem->u4.pSubq->pSelect!=0 ); + pResults = pItem->u4.pSubq->pSelect->pEList; + assert( pResults!=0 ); + assert( iCol>=0 && iColnExpr ); + pResults->a[iCol].fg.bUsed = 1; + } +} + +/* +** Search the tables iStart..iEnd (inclusive) in pSrc, looking for a +** table that has a column named zCol. The search is left-to-right. +** The first match found is returned. ** ** When found, set *piTab and *piCol to the table index and column index ** of the matching column and return TRUE. @@ -124913,19 +149380,27 @@ static int columnIndex(Table *pTab, const char *zCol){ */ static int tableAndColumnIndex( SrcList *pSrc, /* Array of tables to search */ - int N, /* Number of tables in pSrc->a[] to search */ + int iStart, /* First member of pSrc->a[] to check */ + int iEnd, /* Last member of pSrc->a[] to check */ const char *zCol, /* Name of the column we are looking for */ int *piTab, /* Write index of pSrc->a[] here */ - int *piCol /* Write index of pSrc->a[*piTab].pTab->aCol[] here */ + int *piCol, /* Write index of pSrc->a[*piTab].pSTab->aCol[] here */ + int bIgnoreHidden /* Ignore hidden columns */ ){ int i; /* For looping over tables in pSrc */ int iCol; /* Index of column matching zCol */ + assert( iEndnSrc ); + assert( iStart>=0 ); assert( (piTab==0)==(piCol==0) ); /* Both or neither are NULL */ - for(i=0; ia[i].pTab, zCol); - if( iCol>=0 ){ + + for(i=iStart; i<=iEnd; i++){ + iCol = sqlite3ColumnIndex(pSrc->a[i].pSTab, zCol); + if( iCol>=0 + && (bIgnoreHidden==0 || IsHiddenColumn(&pSrc->a[i].pSTab->aCol[iCol])==0) + ){ if( piTab ){ + sqlite3SrcItemColumnUsed(&pSrc->a[i], iCol); *piTab = i; *piCol = iCol; } @@ -124936,63 +149411,19 @@ static int tableAndColumnIndex( } /* -** This function is used to add terms implied by JOIN syntax to the -** WHERE clause expression of a SELECT statement. The new term, which -** is ANDed with the existing WHERE clause, is of the form: -** -** (tab1.col1 = tab2.col2) -** -** where tab1 is the iSrc'th table in SrcList pSrc and tab2 is the -** (iSrc+1)'th. Column col1 is column iColLeft of tab1, and col2 is -** column iColRight of tab2. -*/ -static void addWhereTerm( - Parse *pParse, /* Parsing context */ - SrcList *pSrc, /* List of tables in FROM clause */ - int iLeft, /* Index of first table to join in pSrc */ - int iColLeft, /* Index of column in first table */ - int iRight, /* Index of second table in pSrc */ - int iColRight, /* Index of column in second table */ - int isOuterJoin, /* True if this is an OUTER join */ - Expr **ppWhere /* IN/OUT: The WHERE clause to add to */ -){ - sqlite3 *db = pParse->db; - Expr *pE1; - Expr *pE2; - Expr *pEq; - - assert( iLeftnSrc>iRight ); - assert( pSrc->a[iLeft].pTab ); - assert( pSrc->a[iRight].pTab ); - - pE1 = sqlite3CreateColumnExpr(db, pSrc, iLeft, iColLeft); - pE2 = sqlite3CreateColumnExpr(db, pSrc, iRight, iColRight); - - pEq = sqlite3PExpr(pParse, TK_EQ, pE1, pE2); - if( pEq && isOuterJoin ){ - ExprSetProperty(pEq, EP_FromJoin); - assert( !ExprHasProperty(pEq, EP_TokenOnly|EP_Reduced) ); - ExprSetVVAProperty(pEq, EP_NoReduce); - pEq->iRightJoinTable = (i16)pE2->iTable; - } - *ppWhere = sqlite3ExprAnd(db, *ppWhere, pEq); -} - -/* -** Set the EP_FromJoin property on all terms of the given expression. -** And set the Expr.iRightJoinTable to iTable for every term in the +** Set the EP_OuterON property on all terms of the given expression. +** And set the Expr.w.iJoin to iTable for every term in the ** expression. ** -** The EP_FromJoin property is used on terms of an expression to tell -** the LEFT OUTER JOIN processing logic that this term is part of the +** The EP_OuterON property is used on terms of an expression to tell +** the OUTER JOIN processing logic that this term is part of the ** join restriction specified in the ON or USING clause and not a part ** of the more general WHERE clause. These terms are moved over to the ** WHERE clause during join processing but we need to remember that they ** originated in the ON or USING clause. ** -** The Expr.iRightJoinTable tells the WHERE clause processing that the -** expression depends on table iRightJoinTable even if that table is not +** The Expr.w.iJoin tells the WHERE clause processing that the +** expression depends on table w.iJoin even if that table is not ** explicitly mentioned in the expression. That information is needed ** for cases like this: ** @@ -125005,143 +149436,235 @@ static void addWhereTerm( ** after the t1 loop and rows with t1.x!=5 will never appear in ** the output, which is incorrect. */ -static void setJoinExpr(Expr *p, int iTable){ +SQLITE_PRIVATE void sqlite3SetJoinExpr(Expr *p, int iTable, u32 joinFlag){ + assert( joinFlag==EP_OuterON || joinFlag==EP_InnerON ); while( p ){ - ExprSetProperty(p, EP_FromJoin); + ExprSetProperty(p, joinFlag); assert( !ExprHasProperty(p, EP_TokenOnly|EP_Reduced) ); ExprSetVVAProperty(p, EP_NoReduce); - p->iRightJoinTable = (i16)iTable; - if( p->op==TK_FUNCTION && p->x.pList ){ - int i; - for(i=0; ix.pList->nExpr; i++){ - setJoinExpr(p->x.pList->a[i].pExpr, iTable); + p->w.iJoin = iTable; + if( ExprUseXList(p) ){ + if( p->x.pList ){ + int i; + for(i=0; ix.pList->nExpr; i++){ + sqlite3SetJoinExpr(p->x.pList->a[i].pExpr, iTable, joinFlag); + } } } - setJoinExpr(p->pLeft, iTable); + sqlite3SetJoinExpr(p->pLeft, iTable, joinFlag); p = p->pRight; - } + } } -/* Undo the work of setJoinExpr(). In the expression tree p, convert every -** term that is marked with EP_FromJoin and iRightJoinTable==iTable into -** an ordinary term that omits the EP_FromJoin mark. +/* Undo the work of sqlite3SetJoinExpr(). This is used when a LEFT JOIN +** is simplified into an ordinary JOIN, and when an ON expression is +** "pushed down" into the WHERE clause of a subquery. ** -** This happens when a LEFT JOIN is simplified into an ordinary JOIN. +** Convert every term that is marked with EP_OuterON and w.iJoin==iTable into +** an ordinary term that omits the EP_OuterON mark. Or if iTable<0, then +** just clear every EP_OuterON and EP_InnerON mark from the expression tree. +** +** If nullable is true, that means that Expr p might evaluate to NULL even +** if it is a reference to a NOT NULL column. This can happen, for example, +** if the table that p references is on the left side of a RIGHT JOIN. +** If nullable is true, then take care to not remove the EP_CanBeNull bit. +** See forum thread https://sqlite.org/forum/forumpost/b40696f50145d21c */ -static void unsetJoinExpr(Expr *p, int iTable){ +static void unsetJoinExpr(Expr *p, int iTable, int nullable){ while( p ){ - if( ExprHasProperty(p, EP_FromJoin) - && (iTable<0 || p->iRightJoinTable==iTable) ){ - ExprClearProperty(p, EP_FromJoin); + if( iTable<0 || (ExprHasProperty(p, EP_OuterON) && p->w.iJoin==iTable) ){ + ExprClearProperty(p, EP_OuterON|EP_InnerON); + if( iTable>=0 ) ExprSetProperty(p, EP_InnerON); } - if( p->op==TK_FUNCTION && p->x.pList ){ - int i; - for(i=0; ix.pList->nExpr; i++){ - unsetJoinExpr(p->x.pList->a[i].pExpr, iTable); + if( p->op==TK_COLUMN && p->iTable==iTable && !nullable ){ + ExprClearProperty(p, EP_CanBeNull); + } + if( p->op==TK_FUNCTION ){ + assert( ExprUseXList(p) ); + assert( p->pLeft==0 ); + if( p->x.pList ){ + int i; + for(i=0; ix.pList->nExpr; i++){ + unsetJoinExpr(p->x.pList->a[i].pExpr, iTable, nullable); + } } } - unsetJoinExpr(p->pLeft, iTable); + unsetJoinExpr(p->pLeft, iTable, nullable); p = p->pRight; - } + } } /* ** This routine processes the join information for a SELECT statement. -** ON and USING clauses are converted into extra terms of the WHERE clause. -** NATURAL joins also create extra WHERE clause terms. +** +** * A NATURAL join is converted into a USING join. After that, we +** do not need to be concerned with NATURAL joins and we only have +** think about USING joins. +** +** * ON and USING clauses result in extra terms being added to the +** WHERE clause to enforce the specified constraints. The extra +** WHERE clause terms will be tagged with EP_OuterON or +** EP_InnerON so that we know that they originated in ON/USING. ** ** The terms of a FROM clause are contained in the Select.pSrc structure. ** The left most table is the first entry in Select.pSrc. The right-most ** table is the last entry. The join operator is held in the entry to -** the left. Thus entry 0 contains the join operator for the join between +** the right. Thus entry 1 contains the join operator for the join between ** entries 0 and 1. Any ON or USING clauses associated with the join are -** also attached to the left entry. +** also attached to the right entry. ** ** This routine returns the number of errors encountered. */ -static int sqliteProcessJoin(Parse *pParse, Select *p){ +static int sqlite3ProcessJoin(Parse *pParse, Select *p){ SrcList *pSrc; /* All tables in the FROM clause */ int i, j; /* Loop counters */ - struct SrcList_item *pLeft; /* Left table being joined */ - struct SrcList_item *pRight; /* Right table being joined */ + SrcItem *pLeft; /* Left table being joined */ + SrcItem *pRight; /* Right table being joined */ pSrc = p->pSrc; pLeft = &pSrc->a[0]; pRight = &pLeft[1]; for(i=0; inSrc-1; i++, pRight++, pLeft++){ - Table *pRightTab = pRight->pTab; - int isOuter; + Table *pRightTab = pRight->pSTab; + u32 joinType; - if( NEVER(pLeft->pTab==0 || pRightTab==0) ) continue; - isOuter = (pRight->fg.jointype & JT_OUTER)!=0; + if( NEVER(pLeft->pSTab==0 || pRightTab==0) ) continue; + joinType = (pRight->fg.jointype & JT_OUTER)!=0 ? EP_OuterON : EP_InnerON; - /* When the NATURAL keyword is present, add WHERE clause terms for - ** every column that the two tables have in common. + /* If this is a NATURAL join, synthesize an appropriate USING clause + ** to specify which columns should be joined. */ if( pRight->fg.jointype & JT_NATURAL ){ - if( pRight->pOn || pRight->pUsing ){ + IdList *pUsing = 0; + if( pRight->fg.isUsing || pRight->u3.pOn ){ sqlite3ErrorMsg(pParse, "a NATURAL join may not have " "an ON or USING clause", 0); return 1; } for(j=0; jnCol; j++){ char *zName; /* Name of column in the right table */ - int iLeft; /* Matching left table */ - int iLeftCol; /* Matching column in the left table */ - zName = pRightTab->aCol[j].zName; - if( tableAndColumnIndex(pSrc, i+1, zName, &iLeft, &iLeftCol) ){ - addWhereTerm(pParse, pSrc, iLeft, iLeftCol, i+1, j, - isOuter, &p->pWhere); + if( IsHiddenColumn(&pRightTab->aCol[j]) ) continue; + zName = pRightTab->aCol[j].zCnName; + if( tableAndColumnIndex(pSrc, 0, i, zName, 0, 0, 1) ){ + pUsing = sqlite3IdListAppend(pParse, pUsing, 0); + if( pUsing ){ + assert( pUsing->nId>0 ); + assert( pUsing->a[pUsing->nId-1].zName==0 ); + pUsing->a[pUsing->nId-1].zName = sqlite3DbStrDup(pParse->db, zName); + } } } - } - - /* Disallow both ON and USING clauses in the same join - */ - if( pRight->pOn && pRight->pUsing ){ - sqlite3ErrorMsg(pParse, "cannot have both ON and USING " - "clauses in the same join"); - return 1; - } - - /* Add the ON clause to the end of the WHERE clause, connected by - ** an AND operator. - */ - if( pRight->pOn ){ - if( isOuter ) setJoinExpr(pRight->pOn, pRight->iCursor); - p->pWhere = sqlite3ExprAnd(pParse->db, p->pWhere, pRight->pOn); - pRight->pOn = 0; + if( pUsing ){ + pRight->fg.isUsing = 1; + pRight->fg.isSynthUsing = 1; + pRight->u3.pUsing = pUsing; + } + if( pParse->nErr ) return 1; } /* Create extra terms on the WHERE clause for each column named - ** in the USING clause. Example: If the two tables to be joined are + ** in the USING clause. Example: If the two tables to be joined are ** A and B and the USING clause names X, Y, and Z, then add this ** to the WHERE clause: A.X=B.X AND A.Y=B.Y AND A.Z=B.Z ** Report an error if any column mentioned in the USING clause is ** not contained in both tables to be joined. */ - if( pRight->pUsing ){ - IdList *pList = pRight->pUsing; + if( pRight->fg.isUsing ){ + IdList *pList = pRight->u3.pUsing; + sqlite3 *db = pParse->db; + assert( pList!=0 ); for(j=0; jnId; j++){ char *zName; /* Name of the term in the USING clause */ int iLeft; /* Table on the left with matching column name */ int iLeftCol; /* Column number of matching column on the left */ int iRightCol; /* Column number of matching column on the right */ + Expr *pE1; /* Reference to the column on the LEFT of the join */ + Expr *pE2; /* Reference to the column on the RIGHT of the join */ + Expr *pEq; /* Equality constraint. pE1 == pE2 */ zName = pList->a[j].zName; - iRightCol = columnIndex(pRightTab, zName); + iRightCol = sqlite3ColumnIndex(pRightTab, zName); if( iRightCol<0 - || !tableAndColumnIndex(pSrc, i+1, zName, &iLeft, &iLeftCol) + || tableAndColumnIndex(pSrc, 0, i, zName, &iLeft, &iLeftCol, + pRight->fg.isSynthUsing)==0 ){ sqlite3ErrorMsg(pParse, "cannot join using column %s - column " "not present in both tables", zName); return 1; } - addWhereTerm(pParse, pSrc, iLeft, iLeftCol, i+1, iRightCol, - isOuter, &p->pWhere); + pE1 = sqlite3CreateColumnExpr(db, pSrc, iLeft, iLeftCol); + sqlite3SrcItemColumnUsed(&pSrc->a[iLeft], iLeftCol); + if( (pSrc->a[0].fg.jointype & JT_LTORJ)!=0 && pParse->nErr==0 ){ + /* This branch runs if the query contains one or more RIGHT or FULL + ** JOINs. If only a single table on the left side of this join + ** contains the zName column, then this branch is a no-op. + ** But if there are two or more tables on the left side + ** of the join, construct a coalesce() function that gathers all + ** such tables. Raise an error if more than one of those references + ** to zName is not also within a prior USING clause. + ** + ** We really ought to raise an error if there are two or more + ** non-USING references to zName on the left of an INNER or LEFT + ** JOIN. But older versions of SQLite do not do that, so we avoid + ** adding a new error so as to not break legacy applications. + */ + ExprList *pFuncArgs = 0; /* Arguments to the coalesce() */ + static const Token tkCoalesce = { "coalesce", 8 }; + assert( pE1!=0 ); + ExprSetProperty(pE1, EP_CanBeNull); + while( tableAndColumnIndex(pSrc, iLeft+1, i, zName, &iLeft, &iLeftCol, + pRight->fg.isSynthUsing)!=0 ){ + if( pSrc->a[iLeft].fg.isUsing==0 + || sqlite3IdListIndex(pSrc->a[iLeft].u3.pUsing, zName)<0 + ){ + sqlite3ErrorMsg(pParse, "ambiguous reference to %s in USING()", + zName); + break; + } + pFuncArgs = sqlite3ExprListAppend(pParse, pFuncArgs, pE1); + pE1 = sqlite3CreateColumnExpr(db, pSrc, iLeft, iLeftCol); + sqlite3SrcItemColumnUsed(&pSrc->a[iLeft], iLeftCol); + } + if( pFuncArgs ){ + pFuncArgs = sqlite3ExprListAppend(pParse, pFuncArgs, pE1); + pE1 = sqlite3ExprFunction(pParse, pFuncArgs, &tkCoalesce, 0); + if( pE1 ){ + pE1->affExpr = SQLITE_AFF_DEFER; + } + } + }else if( (pSrc->a[i+1].fg.jointype & JT_LEFT)!=0 && pParse->nErr==0 ){ + assert( pE1!=0 ); + ExprSetProperty(pE1, EP_CanBeNull); + } + pE2 = sqlite3CreateColumnExpr(db, pSrc, i+1, iRightCol); + sqlite3SrcItemColumnUsed(pRight, iRightCol); + pEq = sqlite3PExpr(pParse, TK_EQ, pE1, pE2); + assert( pE2!=0 || pEq==0 ); + if( pEq ){ + ExprSetProperty(pEq, joinType); + assert( !ExprHasProperty(pEq, EP_TokenOnly|EP_Reduced) ); + ExprSetVVAProperty(pEq, EP_NoReduce); + pEq->w.iJoin = pE2->iTable; + } + p->pWhere = sqlite3ExprAnd(pParse, p->pWhere, pEq); } } + + /* Add the ON clause to the end of the WHERE clause, connected by + ** an AND operator. + */ + else if( pRight->u3.pOn ){ + sqlite3SetJoinExpr(pRight->u3.pOn, pRight->iCursor, joinType); + p->pWhere = sqlite3ExprAnd(pParse, p->pWhere, pRight->u3.pOn); + pRight->u3.pOn = 0; + pRight->fg.isOn = 1; + p->selFlags |= SF_OnToWhere; + } + + if( IsVirtual(pRightTab) && joinType==EP_OuterON && pRight->u1.pFuncArg ){ + p->selFlags |= SF_OnToWhere; + } } return 0; } @@ -125235,14 +149758,18 @@ static void pushOntoSorter( ** (2) All output columns are included in the sort record. In that ** case regData==regOrigData. ** (3) Some output columns are omitted from the sort record due to - ** the SQLITE_ENABLE_SORTER_REFERENCE optimization, or due to the - ** SQLITE_ECEL_OMITREF optimization, or due to the - ** SortCtx.pDeferredRowLoad optimiation. In any of these cases + ** the SQLITE_ENABLE_SORTER_REFERENCES optimization, or due to the + ** SQLITE_ECEL_OMITREF optimization, or due to the + ** SortCtx.pDeferredRowLoad optimization. In any of these cases ** regOrigData is 0 to prevent this routine from trying to copy ** values that might not yet exist. */ assert( nData==1 || regData==regOrigData || regOrigData==0 ); +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + pSort->addrPush = sqlite3VdbeCurrentAddr(v); +#endif + if( nPrefixReg ){ assert( nPrefixReg==nExpr+bSeq ); regBase = regData - nPrefixReg; @@ -125274,7 +149801,7 @@ static void pushOntoSorter( pParse->nMem += pSort->nOBSat; nKey = nExpr - pSort->nOBSat + bSeq; if( bSeq ){ - addrFirst = sqlite3VdbeAddOp1(v, OP_IfNot, regBase+nExpr); + addrFirst = sqlite3VdbeAddOp1(v, OP_IfNot, regBase+nExpr); }else{ addrFirst = sqlite3VdbeAddOp1(v, OP_SequenceTest, pSort->iECursor); } @@ -125284,11 +149811,12 @@ static void pushOntoSorter( if( pParse->db->mallocFailed ) return; pOp->p2 = nKey + nData; pKI = pOp->p4.pKeyInfo; - memset(pKI->aSortOrder, 0, pKI->nKeyField); /* Makes OP_Jump testable */ + memset(pKI->aSortFlags, 0, pKI->nKeyField); /* Makes OP_Jump testable */ sqlite3VdbeChangeP4(v, -1, (char*)pKI, P4_KEYINFO); testcase( pKI->nAllField > pKI->nKeyField+2 ); pOp->p4.pKeyInfo = sqlite3KeyInfoFromExprList(pParse,pSort->pOrderBy,nOBSat, pKI->nAllField-pKI->nKeyField-1); + pOp = 0; /* Ensure pOp not used after sqlite3VdbeAddOp3() */ addrJmp = sqlite3VdbeCurrentAddr(v); sqlite3VdbeAddOp3(v, OP_Jump, addrJmp+1, 0, addrJmp+1); VdbeCoverage(v); pSort->labelBkOut = sqlite3VdbeMakeLabel(pParse); @@ -125307,10 +149835,10 @@ static void pushOntoSorter( /* At this point the values for the new sorter entry are stored ** in an array of registers. They need to be composed into a record ** and inserted into the sorter if either (a) there are currently - ** less than LIMIT+OFFSET items or (b) the new record is smaller than + ** less than LIMIT+OFFSET items or (b) the new record is smaller than ** the largest record currently in the sorter. If (b) is true and there ** are already LIMIT+OFFSET items in the sorter, delete the largest - ** entry before inserting the new one. This way there are never more + ** entry before inserting the new one. This way there are never more ** than LIMIT+OFFSET items in the sorter. ** ** If the new record does not need to be inserted into the sorter, @@ -125342,6 +149870,9 @@ static void pushOntoSorter( sqlite3VdbeChangeP2(v, iSkip, pSort->labelOBLopt ? pSort->labelOBLopt : sqlite3VdbeCurrentAddr(v)); } +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + pSort->addrPushEnd = sqlite3VdbeCurrentAddr(v)-1; +#endif } /* @@ -125359,38 +149890,164 @@ static void codeOffset( } /* -** Add code that will check to make sure the N registers starting at iMem -** form a distinct entry. iTab is a sorting index that holds previously -** seen combinations of the N values. A new entry is made in iTab -** if the current N values are new. +** Add code that will check to make sure the array of registers starting at +** iMem form a distinct entry. This is used by both "SELECT DISTINCT ..." and +** distinct aggregates ("SELECT count(DISTINCT ) ..."). Three strategies +** are available. Which is used depends on the value of parameter eTnctType, +** as follows: ** -** A jump to addrRepeat is made and the N+1 values are popped from the -** stack if the top N elements are not distinct. -*/ -static void codeDistinct( +** WHERE_DISTINCT_UNORDERED/WHERE_DISTINCT_NOOP: +** Build an ephemeral table that contains all entries seen before and +** skip entries which have been seen before. +** +** Parameter iTab is the cursor number of an ephemeral table that must +** be opened before the VM code generated by this routine is executed. +** The ephemeral cursor table is queried for a record identical to the +** record formed by the current array of registers. If one is found, +** jump to VM address addrRepeat. Otherwise, insert a new record into +** the ephemeral cursor and proceed. +** +** The returned value in this case is a copy of parameter iTab. +** +** WHERE_DISTINCT_ORDERED: +** In this case rows are being delivered sorted order. The ephemeral +** table is not required. Instead, the current set of values +** is compared against previous row. If they match, the new row +** is not distinct and control jumps to VM address addrRepeat. Otherwise, +** the VM program proceeds with processing the new row. +** +** The returned value in this case is the register number of the first +** in an array of registers used to store the previous result row so that +** it can be compared to the next. The caller must ensure that this +** register is initialized to NULL. (The fixDistinctOpenEph() routine +** will take care of this initialization.) +** +** WHERE_DISTINCT_UNIQUE: +** In this case it has already been determined that the rows are distinct. +** No special action is required. The return value is zero. +** +** Parameter pEList is the list of expressions used to generated the +** contents of each row. It is used by this routine to determine (a) +** how many elements there are in the array of registers and (b) the +** collation sequences that should be used for the comparisons if +** eTnctType is WHERE_DISTINCT_ORDERED. +*/ +static int codeDistinct( Parse *pParse, /* Parsing and code generating context */ + int eTnctType, /* WHERE_DISTINCT_* value */ int iTab, /* A sorting index used to test for distinctness */ int addrRepeat, /* Jump to here if not distinct */ - int N, /* Number of elements */ - int iMem /* First element */ + ExprList *pEList, /* Expression for each element */ + int regElem /* First element */ ){ - Vdbe *v; - int r1; + int iRet = 0; + int nResultCol = pEList->nExpr; + Vdbe *v = pParse->pVdbe; - v = pParse->pVdbe; - r1 = sqlite3GetTempReg(pParse); - sqlite3VdbeAddOp4Int(v, OP_Found, iTab, addrRepeat, iMem, N); VdbeCoverage(v); - sqlite3VdbeAddOp3(v, OP_MakeRecord, iMem, N, r1); - sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iTab, r1, iMem, N); - sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT); - sqlite3ReleaseTempReg(pParse, r1); + switch( eTnctType ){ + case WHERE_DISTINCT_ORDERED: { + int i; + int iJump; /* Jump destination */ + int regPrev; /* Previous row content */ + + /* Allocate space for the previous row */ + iRet = regPrev = pParse->nMem+1; + pParse->nMem += nResultCol; + + iJump = sqlite3VdbeCurrentAddr(v) + nResultCol; + for(i=0; ia[i].pExpr); + if( idb->mallocFailed ); + sqlite3VdbeAddOp3(v, OP_Copy, regElem, regPrev, nResultCol-1); + break; + } + + case WHERE_DISTINCT_UNIQUE: { + /* nothing to do */ + break; + } + + default: { + int r1 = sqlite3GetTempReg(pParse); + sqlite3VdbeAddOp4Int(v, OP_Found, iTab, addrRepeat, regElem, nResultCol); + VdbeCoverage(v); + sqlite3VdbeAddOp3(v, OP_MakeRecord, regElem, nResultCol, r1); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iTab, r1, regElem, nResultCol); + sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT); + sqlite3ReleaseTempReg(pParse, r1); + iRet = iTab; + break; + } + } + + return iRet; +} + +/* +** This routine runs after codeDistinct(). It makes necessary +** adjustments to the OP_OpenEphemeral opcode that the codeDistinct() +** routine made use of. This processing must be done separately since +** sometimes codeDistinct is called before the OP_OpenEphemeral is actually +** laid down. +** +** WHERE_DISTINCT_NOOP: +** WHERE_DISTINCT_UNORDERED: +** +** No adjustments necessary. This function is a no-op. +** +** WHERE_DISTINCT_UNIQUE: +** +** The ephemeral table is not needed. So change the +** OP_OpenEphemeral opcode into an OP_Noop. +** +** WHERE_DISTINCT_ORDERED: +** +** The ephemeral table is not needed. But we do need register +** iVal to be initialized to NULL. So change the OP_OpenEphemeral +** into an OP_Null on the iVal register. +*/ +static void fixDistinctOpenEph( + Parse *pParse, /* Parsing and code generating context */ + int eTnctType, /* WHERE_DISTINCT_* value */ + int iVal, /* Value returned by codeDistinct() */ + int iOpenEphAddr /* Address of OP_OpenEphemeral instruction for iTab */ +){ + if( pParse->nErr==0 + && (eTnctType==WHERE_DISTINCT_UNIQUE || eTnctType==WHERE_DISTINCT_ORDERED) + ){ + Vdbe *v = pParse->pVdbe; + sqlite3VdbeChangeToNoop(v, iOpenEphAddr); + if( sqlite3VdbeGetOp(v, iOpenEphAddr+1)->opcode==OP_Explain ){ + sqlite3VdbeChangeToNoop(v, iOpenEphAddr+1); + } + if( eTnctType==WHERE_DISTINCT_ORDERED ){ + /* Change the OP_OpenEphemeral to an OP_Null that sets the MEM_Cleared + ** bit on the first register of the previous value. This will cause the + ** OP_Ne added in codeDistinct() to always fail on the first iteration of + ** the loop even if the first row is all NULLs. */ + VdbeOp *pOp = sqlite3VdbeGetOp(v, iOpenEphAddr); + pOp->opcode = OP_Null; + pOp->p1 = 1; + pOp->p2 = iVal; + } + } } #ifdef SQLITE_ENABLE_SORTER_REFERENCES /* ** This function is called as part of inner-loop generation for a SELECT -** statement with an ORDER BY that is not optimized by an index. It -** determines the expressions, if any, that the sorter-reference +** statement with an ORDER BY that is not optimized by an index. It +** determines the expressions, if any, that the sorter-reference ** optimization should be used for. The sorter-reference optimization ** is used for SELECT queries like: ** @@ -125400,11 +150057,11 @@ static void codeDistinct( ** storing values read from that column in the sorter records, the PK of ** the row from table t1 is stored instead. Then, as records are extracted from ** the sorter to return to the user, the required value of bigblob is -** retrieved directly from table t1. If the values are very large, this +** retrieved directly from table t1. If the values are very large, this ** can be more efficient than storing them directly in the sorter records. ** -** The ExprList_item.bSorterRef flag is set for each expression in pEList -** for which the sorter-reference optimization should be enabled. +** The ExprList_item.fg.bSorterRef flag is set for each expression in pEList +** for which the sorter-reference optimization should be enabled. ** Additionally, the pSort->aDefer[] array is populated with entries ** for all cursors required to evaluate all selected expressions. Finally. ** output variable (*ppExtra) is set to an expression list containing @@ -125424,9 +150081,13 @@ static void selectExprDefer( struct ExprList_item *pItem = &pEList->a[i]; if( pItem->u.x.iOrderByCol==0 ){ Expr *pExpr = pItem->pExpr; - Table *pTab = pExpr->y.pTab; - if( pExpr->op==TK_COLUMN && pExpr->iColumn>=0 && pTab && !IsVirtual(pTab) - && (pTab->aCol[pExpr->iColumn].colFlags & COLFLAG_SORTERREF) + Table *pTab; + if( pExpr->op==TK_COLUMN + && pExpr->iColumn>=0 + && ALWAYS( ExprUseYTab(pExpr) ) + && (pTab = pExpr->y.pTab)!=0 + && IsOrdinaryTable(pTab) + && (pTab->aCol[pExpr->iColumn].colFlags & COLFLAG_SORTERREF)!=0 ){ int j; for(j=0; jiTable = pExpr->iTable; + assert( ExprUseYTab(pNew) ); pNew->y.pTab = pExpr->y.pTab; pNew->iColumn = pPk ? pPk->aiColumn[k] : -1; pExtra = sqlite3ExprListAppend(pParse, pExtra, pNew); @@ -125458,7 +150120,7 @@ static void selectExprDefer( nDefer++; } } - pItem->bSorterRef = 1; + pItem->fg.bSorterRef = 1; } } } @@ -125473,7 +150135,7 @@ static void selectExprDefer( ** ** If srcTab is negative, then the p->pEList expressions ** are evaluated in order to get the data for this row. If srcTab is -** zero or more, then data is pulled from srcTab and p->pEList is used only +** zero or more, then data is pulled from srcTab and p->pEList is used only ** to get the number of columns and the collation sequence for each column. */ static void selectInnerLoop( @@ -125537,7 +150199,7 @@ static void selectInnerLoop( if( srcTab>=0 ){ for(i=0; ipEList->a[i].zName)); + VdbeComment((v, "%s", p->pEList->a[i].zEName)); } }else if( eDest!=SRT_Exists ){ #ifdef SQLITE_ENABLE_SORTER_REFERENCES @@ -125555,8 +150217,8 @@ static void selectInnerLoop( } if( pSort && hasDistinct==0 && eDest!=SRT_EphemTab && eDest!=SRT_Table ){ /* For each expression in p->pEList that is a copy of an expression in - ** the ORDER BY clause (pSort->pOrderBy), set the associated - ** iOrderByCol value to one more than the index of the ORDER BY + ** the ORDER BY clause (pSort->pOrderBy), set the associated + ** iOrderByCol value to one more than the index of the ORDER BY ** expression within the sort-key that pushOntoSorter() will generate. ** This allows the p->pEList field to be omitted from the sorted record, ** saving space and CPU cycles. */ @@ -125572,7 +150234,7 @@ static void selectInnerLoop( selectExprDefer(pParse, pSort, p->pEList, &pExtra); if( pExtra && pParse->db->mallocFailed==0 ){ /* If there are any extra PK columns to add to the sorter records, - ** allocate extra memory cells and adjust the OpenEphemeral + ** allocate extra memory cells and adjust the OpenEphemeral ** instruction to account for the larger records. This is only ** required if there are one or more WITHOUT ROWID tables with ** composite primary keys in the SortCtx.aDefer[] array. */ @@ -125589,7 +150251,7 @@ static void selectInnerLoop( for(i=0; inExpr; i++){ if( pEList->a[i].u.x.iOrderByCol>0 #ifdef SQLITE_ENABLE_SORTER_REFERENCES - || pEList->a[i].bSorterRef + || pEList->a[i].fg.bSorterRef #endif ){ nResultCol--; @@ -125602,8 +150264,9 @@ static void selectInnerLoop( testcase( eDest==SRT_Mem ); testcase( eDest==SRT_Coroutine ); testcase( eDest==SRT_Output ); - assert( eDest==SRT_Set || eDest==SRT_Mem - || eDest==SRT_Coroutine || eDest==SRT_Output ); + assert( eDest==SRT_Set || eDest==SRT_Mem + || eDest==SRT_Coroutine || eDest==SRT_Output + || eDest==SRT_Upfrom ); } sRowLoadInfo.regResult = regResult; sRowLoadInfo.ecelFlags = ecelFlags; @@ -125613,7 +150276,7 @@ static void selectInnerLoop( if( pExtra ) nResultCol += pExtra->nExpr; #endif if( p->iLimit - && (ecelFlags & SQLITE_ECEL_OMITREF)!=0 + && (ecelFlags & SQLITE_ECEL_OMITREF)!=0 && nPrefixReg>0 ){ assert( pSort!=0 ); @@ -125630,87 +150293,17 @@ static void selectInnerLoop( ** part of the result. */ if( hasDistinct ){ - switch( pDistinct->eTnctType ){ - case WHERE_DISTINCT_ORDERED: { - VdbeOp *pOp; /* No longer required OpenEphemeral instr. */ - int iJump; /* Jump destination */ - int regPrev; /* Previous row content */ - - /* Allocate space for the previous row */ - regPrev = pParse->nMem+1; - pParse->nMem += nResultCol; - - /* Change the OP_OpenEphemeral coded earlier to an OP_Null - ** sets the MEM_Cleared bit on the first register of the - ** previous value. This will cause the OP_Ne below to always - ** fail on the first iteration of the loop even if the first - ** row is all NULLs. - */ - sqlite3VdbeChangeToNoop(v, pDistinct->addrTnct); - pOp = sqlite3VdbeGetOp(v, pDistinct->addrTnct); - pOp->opcode = OP_Null; - pOp->p1 = 1; - pOp->p2 = regPrev; - - iJump = sqlite3VdbeCurrentAddr(v) + nResultCol; - for(i=0; ipEList->a[i].pExpr); - if( idb->mallocFailed ); - sqlite3VdbeAddOp3(v, OP_Copy, regResult, regPrev, nResultCol-1); - break; - } - - case WHERE_DISTINCT_UNIQUE: { - sqlite3VdbeChangeToNoop(v, pDistinct->addrTnct); - break; - } - - default: { - assert( pDistinct->eTnctType==WHERE_DISTINCT_UNORDERED ); - codeDistinct(pParse, pDistinct->tabTnct, iContinue, nResultCol, - regResult); - break; - } - } + int eType = pDistinct->eTnctType; + int iTab = pDistinct->tabTnct; + assert( nResultCol==p->pEList->nExpr ); + iTab = codeDistinct(pParse, eType, iTab, iContinue, p->pEList, regResult); + fixDistinctOpenEph(pParse, eType, iTab, pDistinct->addrTnct); if( pSort==0 ){ codeOffset(v, p->iOffset, iContinue); } } switch( eDest ){ - /* In this mode, write each query result to the key of the temporary - ** table iParm. - */ -#ifndef SQLITE_OMIT_COMPOUND_SELECT - case SRT_Union: { - int r1; - r1 = sqlite3GetTempReg(pParse); - sqlite3VdbeAddOp3(v, OP_MakeRecord, regResult, nResultCol, r1); - sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iParm, r1, regResult, nResultCol); - sqlite3ReleaseTempReg(pParse, r1); - break; - } - - /* Construct a record from the query result, but instead of - ** saving that record, use it as a key to delete elements from - ** the temporary table iParm. - */ - case SRT_Except: { - sqlite3VdbeAddOp3(v, OP_IdxDelete, iParm, regResult, nResultCol); - break; - } -#endif /* SQLITE_OMIT_COMPOUND_SELECT */ - /* Store the result as data using a unique key. */ case SRT_Fifo: @@ -125723,6 +150316,16 @@ static void selectInnerLoop( testcase( eDest==SRT_Fifo ); testcase( eDest==SRT_DistFifo ); sqlite3VdbeAddOp3(v, OP_MakeRecord, regResult, nResultCol, r1+nPrefixReg); +#if !defined(SQLITE_ENABLE_NULL_TRIM) && defined(SQLITE_DEBUG) + /* A destination of SRT_Table and a non-zero iSDParm2 parameter means + ** that this is an "UPDATE ... FROM" on a virtual table or view. In this + ** case set the p5 parameter of the OP_MakeRecord to OPFLAG_NOCHNG_MAGIC. + ** This does not affect operation in any way - it just allows MakeRecord + ** to process OPFLAG_NOCHANGE values without an assert() failing. */ + if( eDest==SRT_Table && pDest->iSDParm2 ){ + sqlite3VdbeChangeP5(v, OPFLAG_NOCHNG_MAGIC); + } +#endif #ifndef SQLITE_OMIT_CTE if( eDest==SRT_DistFifo ){ /* If the destination is DistFifo, then cursor (iParm+1) is open @@ -125751,6 +150354,30 @@ static void selectInnerLoop( break; } + case SRT_Upfrom: { + if( pSort ){ + pushOntoSorter( + pParse, pSort, p, regResult, regOrig, nResultCol, nPrefixReg); + }else{ + int i2 = pDest->iSDParm2; + int r1 = sqlite3GetTempReg(pParse); + + /* If the UPDATE FROM join is an aggregate that matches no rows, it + ** might still be trying to return one row, because that is what + ** aggregates do. Don't record that empty row in the output table. */ + sqlite3VdbeAddOp2(v, OP_IsNull, regResult, iBreak); VdbeCoverage(v); + + sqlite3VdbeAddOp3(v, OP_MakeRecord, + regResult+(i2<0), nResultCol-(i2<0), r1); + if( i2<0 ){ + sqlite3VdbeAddOp3(v, OP_Insert, iParm, r1, regResult); + }else{ + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iParm, r1, regResult, i2); + } + } + break; + } + #ifndef SQLITE_OMIT_SUBQUERY /* If we are creating a set for an "expr IN (SELECT ...)" construct, ** then there should be a single item on the stack. Write this @@ -125764,17 +150391,24 @@ static void selectInnerLoop( ** case the order does matter */ pushOntoSorter( pParse, pSort, p, regResult, regOrig, nResultCol, nPrefixReg); + pDest->iSDParm2 = 0; /* Signal that any Bloom filter is unpopulated */ }else{ int r1 = sqlite3GetTempReg(pParse); assert( sqlite3Strlen30(pDest->zAffSdst)==nResultCol ); - sqlite3VdbeAddOp4(v, OP_MakeRecord, regResult, nResultCol, + sqlite3VdbeAddOp4(v, OP_MakeRecord, regResult, nResultCol, r1, pDest->zAffSdst, nResultCol); sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iParm, r1, regResult, nResultCol); + if( pDest->iSDParm2 ){ + sqlite3VdbeAddOp4Int(v, OP_FilterAdd, pDest->iSDParm2, 0, + regResult, nResultCol); + ExplainQueryPlan((pParse, 0, "CREATE BLOOM FILTER")); + } sqlite3ReleaseTempReg(pParse, r1); } break; } + /* If any row exist in the result set, record that fact and abort. */ case SRT_Exists: { @@ -125784,7 +150418,7 @@ static void selectInnerLoop( } /* If this is a scalar select that is part of an expression, then - ** store the results in the appropriate memory cell or array of + ** store the results in the appropriate memory cell or array of ** memory cells and break out of the scan loop. */ case SRT_Mem: { @@ -125792,9 +150426,14 @@ static void selectInnerLoop( assert( nResultCol<=pDest->nSdst ); pushOntoSorter( pParse, pSort, p, regResult, regOrig, nResultCol, nPrefixReg); + pDest->iSDParm = regResult; }else{ assert( nResultCol==pDest->nSdst ); - assert( regResult==iParm ); + if( regResult!=iParm ){ + /* This occurs in cases where the SELECT had both a DISTINCT and + ** an OFFSET clause. */ + sqlite3VdbeAddOp3(v, OP_Copy, regResult, iParm, nResultCol-1); + } /* The LIMIT clause will jump out of the loop for us */ } break; @@ -125839,7 +150478,7 @@ static void selectInnerLoop( /* If the destination is DistQueue, then cursor (iParm+1) is open ** on a second ephemeral index that holds all values every previously ** added to the queue. */ - addrTest = sqlite3VdbeAddOp4Int(v, OP_Found, iParm+1, 0, + addrTest = sqlite3VdbeAddOp4Int(v, OP_Found, iParm+1, 0, regResult, nResultCol); VdbeCoverage(v); } @@ -125892,18 +150531,21 @@ static void selectInnerLoop( ** X extra columns. */ SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoAlloc(sqlite3 *db, int N, int X){ - int nExtra = (N+X)*(sizeof(CollSeq*)+1) - sizeof(CollSeq*); - KeyInfo *p = sqlite3DbMallocRawNN(db, sizeof(KeyInfo) + nExtra); + int nExtra = (N+X)*(sizeof(CollSeq*)+1); + KeyInfo *p; + assert( X>=0 ); + if( NEVER(N+X>0xffff) ) return (KeyInfo*)sqlite3OomFault(db); + p = sqlite3DbMallocRawNN(db, SZ_KEYINFO(0) + nExtra); if( p ){ - p->aSortOrder = (u8*)&p->aColl[N+X]; + p->aSortFlags = (u8*)&p->aColl[N+X]; p->nKeyField = (u16)N; p->nAllField = (u16)(N+X); p->enc = ENC(db); p->db = db; p->nRef = 1; - memset(&p[1], 0, nExtra); + memset(p->aColl, 0, nExtra); }else{ - sqlite3OomFault(db); + return (KeyInfo*)sqlite3OomFault(db); } return p; } @@ -125913,9 +150555,10 @@ SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoAlloc(sqlite3 *db, int N, int X){ */ SQLITE_PRIVATE void sqlite3KeyInfoUnref(KeyInfo *p){ if( p ){ + assert( p->db!=0 ); assert( p->nRef>0 ); p->nRef--; - if( p->nRef==0 ) sqlite3DbFreeNN(p->db, p); + if( p->nRef==0 ) sqlite3DbNNFreeNN(p->db, p); } } @@ -125972,7 +150615,7 @@ SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoFromExprList( assert( sqlite3KeyInfoIsWriteable(pInfo) ); for(i=iStart, pItem=pList->a+iStart; iaColl[i-iStart] = sqlite3ExprNNCollSeq(pParse, pItem->pExpr); - pInfo->aSortOrder[i-iStart] = pItem->sortOrder; + pInfo->aSortFlags[i-iStart] = pItem->fg.sortFlags; } } return pInfo; @@ -125981,7 +150624,7 @@ SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoFromExprList( /* ** Name of the connection operator, used for error messages. */ -static const char *selectOpName(int id){ +SQLITE_PRIVATE const char *sqlite3SelectOpName(int id){ char *z; switch( id ){ case TK_ALL: z = "UNION ALL"; break; @@ -126054,6 +150697,23 @@ static void generateSortTail( int bSeq; /* True if sorter record includes seq. no. */ int nRefKey = 0; struct ExprList_item *aOutEx = p->pEList->a; +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + int addrExplain; /* Address of OP_Explain instruction */ +#endif + + nKey = pOrderBy->nExpr - pSort->nOBSat; + if( pSort->nOBSat==0 || nKey==1 ){ + ExplainQueryPlan2(addrExplain, (pParse, 0, + "USE TEMP B-TREE FOR %sORDER BY", pSort->nOBSat?"LAST TERM OF ":"" + )); + }else{ + ExplainQueryPlan2(addrExplain, (pParse, 0, + "USE TEMP B-TREE FOR LAST %d TERMS OF ORDER BY", nKey + )); + } + sqlite3VdbeScanStatusRange(v, addrExplain,pSort->addrPush,pSort->addrPushEnd); + sqlite3VdbeScanStatusCounters(v, addrExplain, addrExplain, pSort->addrPush); + assert( addrBreak<0 ); if( pSort->labelBkOut ){ @@ -126074,6 +150734,9 @@ static void generateSortTail( iTab = pSort->iECursor; if( eDest==SRT_Output || eDest==SRT_Coroutine || eDest==SRT_Mem ){ + if( eDest==SRT_Mem && p->iOffset ){ + sqlite3VdbeAddOp2(v, OP_Null, 0, pDest->iSdst); + } regRowid = 0; regRow = pDest->iSdst; }else{ @@ -126085,19 +150748,18 @@ static void generateSortTail( regRow = sqlite3GetTempRange(pParse, nColumn); } } - nKey = pOrderBy->nExpr - pSort->nOBSat; if( pSort->sortFlags & SORTFLAG_UseSorter ){ int regSortOut = ++pParse->nMem; iSortTab = pParse->nTab++; if( pSort->labelBkOut ){ addrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); } - sqlite3VdbeAddOp3(v, OP_OpenPseudo, iSortTab, regSortOut, + sqlite3VdbeAddOp3(v, OP_OpenPseudo, iSortTab, regSortOut, nKey+1+nColumn+nRefKey); if( addrOnce ) sqlite3VdbeJumpHere(v, addrOnce); addr = 1 + sqlite3VdbeAddOp2(v, OP_SorterSort, iTab, addrBreak); VdbeCoverage(v); - codeOffset(v, p->iOffset, addrContinue); + assert( p->iLimit==0 && p->iOffset==0 ); sqlite3VdbeAddOp3(v, OP_SorterData, iTab, regSortOut, iSortTab); bSeq = 0; }else{ @@ -126105,10 +150767,13 @@ static void generateSortTail( codeOffset(v, p->iOffset, addrContinue); iSortTab = iTab; bSeq = 1; + if( p->iOffset>0 ){ + sqlite3VdbeAddOp2(v, OP_AddImm, p->iLimit, -1); + } } for(i=0, iCol=nKey+bSeq-1; i=0; i--){ #ifdef SQLITE_ENABLE_SORTER_REFERENCES - if( aOutEx[i].bSorterRef ){ + if( aOutEx[i].fg.bSorterRef ){ sqlite3ExprCode(pParse, aOutEx[i].pExpr, regRow+i); }else #endif @@ -126157,9 +150822,10 @@ static void generateSortTail( iRead = iCol--; } sqlite3VdbeAddOp3(v, OP_Column, iSortTab, iRead, regRow+i); - VdbeComment((v, "%s", aOutEx[i].zName?aOutEx[i].zName : aOutEx[i].zSpan)); + VdbeComment((v, "%s", aOutEx[i].zEName)); } } + sqlite3VdbeScanStatusRange(v, addrExplain, addrExplain, -1); switch( eDest ){ case SRT_Table: case SRT_EphemTab: { @@ -126182,8 +150848,19 @@ static void generateSortTail( break; } #endif + case SRT_Upfrom: { + int i2 = pDest->iSDParm2; + int r1 = sqlite3GetTempReg(pParse); + sqlite3VdbeAddOp3(v, OP_MakeRecord,regRow+(i2<0),nColumn-(i2<0),r1); + if( i2<0 ){ + sqlite3VdbeAddOp3(v, OP_Insert, iParm, r1, regRow); + }else{ + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iParm, r1, regRow, i2); + } + break; + } default: { - assert( eDest==SRT_Output || eDest==SRT_Coroutine ); + assert( eDest==SRT_Output || eDest==SRT_Coroutine ); testcase( eDest==SRT_Output ); testcase( eDest==SRT_Coroutine ); if( eDest==SRT_Output ){ @@ -126210,6 +150887,7 @@ static void generateSortTail( }else{ sqlite3VdbeAddOp2(v, OP_Next, iTab, addr); VdbeCoverage(v); } + sqlite3VdbeScanStatusRange(v, addrExplain, sqlite3VdbeCurrentAddr(v)-1, -1); if( pSort->regReturn ) sqlite3VdbeAddOp1(v, OP_Return, pSort->regReturn); sqlite3VdbeResolveLabel(v, addrBreak); } @@ -126218,21 +150896,18 @@ static void generateSortTail( ** Return a pointer to a string containing the 'declaration type' of the ** expression pExpr. The string may be treated as static by the caller. ** -** Also try to estimate the size of the returned value and return that -** result in *pEstWidth. -** ** The declaration type is the exact datatype definition extracted from the ** original CREATE TABLE statement if the expression is a column. The ** declaration type for a ROWID field is INTEGER. Exactly when an expression ** is considered a column can be complex in the presence of subqueries. The -** result-set expression in all of the following SELECT statements is +** result-set expression in all of the following SELECT statements is ** considered a column by this function. ** ** SELECT col FROM tbl; ** SELECT (SELECT col FROM tbl; ** SELECT (SELECT col FROM tbl); ** SELECT abc FROM (SELECT col AS abc FROM tbl); -** +** ** The declaration type for any expression other than a column is NULL. ** ** This routine has either 3 or 6 parameters depending on whether or not @@ -126244,7 +150919,7 @@ static void generateSortTail( # define columnType(A,B,C,D,E) columnTypeImpl(A,B) #endif static const char *columnTypeImpl( - NameContext *pNC, + NameContext *pNC, #ifndef SQLITE_ENABLE_COLUMN_METADATA Expr *pExpr #else @@ -126264,8 +150939,6 @@ static const char *columnTypeImpl( assert( pExpr!=0 ); assert( pNC->pSrcList!=0 ); - assert( pExpr->op!=TK_AGG_COLUMN ); /* This routine runes before aggregates - ** are processed */ switch( pExpr->op ){ case TK_COLUMN: { /* The expression is a column. Locate the table the column is being @@ -126279,8 +150952,12 @@ static const char *columnTypeImpl( SrcList *pTabList = pNC->pSrcList; for(j=0;jnSrc && pTabList->a[j].iCursor!=pExpr->iTable;j++); if( jnSrc ){ - pTab = pTabList->a[j].pTab; - pS = pTabList->a[j].pSelect; + pTab = pTabList->a[j].pSTab; + if( pTabList->a[j].fg.isSubquery ){ + pS = pTabList->a[j].u4.pSubq->pSelect; + }else{ + pS = 0; + } }else{ pNC = pNC->pNext; } @@ -126289,33 +150966,35 @@ static const char *columnTypeImpl( if( pTab==0 ){ /* At one time, code such as "SELECT new.x" within a trigger would ** cause this condition to run. Since then, we have restructured how - ** trigger code is generated and so this condition is no longer + ** trigger code is generated and so this condition is no longer ** possible. However, it can still be true for statements like ** the following: ** ** CREATE TABLE t1(col INTEGER); ** SELECT (SELECT t1.col) FROM FROM t1; ** - ** when columnType() is called on the expression "t1.col" in the + ** when columnType() is called on the expression "t1.col" in the ** sub-select. In this case, set the column type to NULL, even ** though it should really be "INTEGER". ** ** This is not a problem, as the column type of "t1.col" is never - ** used. When columnType() is called on the expression + ** used. When columnType() is called on the expression ** "(SELECT t1.col)", the correct type is returned (see the TK_SELECT ** branch below. */ break; } - assert( pTab && pExpr->y.pTab==pTab ); + assert( pTab && ExprUseYTab(pExpr) && pExpr->y.pTab==pTab ); if( pS ){ /* The "table" is actually a sub-select or a view in the FROM clause ** of the SELECT statement. Return the declaration type and origin ** data for the result-set column of the sub-select. */ - if( iCol>=0 && iColpEList->nExpr ){ + if( iColpEList->nExpr + && (!ViewCanHaveRowid || iCol>=0) + ){ /* If iCol is less than zero, then the expression requests the - ** rowid of the sub-select or view. This expression is legal (see + ** rowid of the sub-select or view. This expression is legal (see ** test case misc2.2.2) - it always evaluates to NULL. */ NameContext sNC; @@ -126323,7 +151002,7 @@ static const char *columnTypeImpl( sNC.pSrcList = pS->pSrc; sNC.pNext = pNC; sNC.pParse = pNC->pParse; - zType = columnType(&sNC, p,&zOrigDb,&zOrigTab,&zOrigCol); + zType = columnType(&sNC, p,&zOrigDb,&zOrigTab,&zOrigCol); } }else{ /* A real table or a CTE table */ @@ -126335,7 +151014,7 @@ static const char *columnTypeImpl( zType = "INTEGER"; zOrigCol = "rowid"; }else{ - zOrigCol = pTab->aCol[iCol].zName; + zOrigCol = pTab->aCol[iCol].zCnName; zType = sqlite3ColumnType(&pTab->aCol[iCol],0); } zOrigTab = pTab->zName; @@ -126361,19 +151040,21 @@ static const char *columnTypeImpl( ** statement. */ NameContext sNC; - Select *pS = pExpr->x.pSelect; - Expr *p = pS->pEList->a[0].pExpr; - assert( ExprHasProperty(pExpr, EP_xIsSelect) ); + Select *pS; + Expr *p; + assert( ExprUseXSelect(pExpr) ); + pS = pExpr->x.pSelect; + p = pS->pEList->a[0].pExpr; sNC.pSrcList = pS->pSrc; sNC.pNext = pNC; sNC.pParse = pNC->pParse; - zType = columnType(&sNC, p, &zOrigDb, &zOrigTab, &zOrigCol); + zType = columnType(&sNC, p, &zOrigDb, &zOrigTab, &zOrigCol); break; } #endif } -#ifdef SQLITE_ENABLE_COLUMN_METADATA +#ifdef SQLITE_ENABLE_COLUMN_METADATA if( pzOrigDb ){ assert( pzOrigTab && pzOrigCol ); *pzOrigDb = zOrigDb; @@ -126409,7 +151090,7 @@ static void generateColumnTypes( const char *zOrigCol = 0; zType = columnType(&sNC, p, &zOrigDb, &zOrigTab, &zOrigCol); - /* The vdbe must make its own copy of the column-type and other + /* The vdbe must make its own copy of the column-type and other ** column specific strings, in case the schema is reset before this ** virtual machine is deleted. */ @@ -126421,6 +151102,10 @@ static void generateColumnTypes( #endif sqlite3VdbeSetColName(v, i, COLNAME_DECLTYPE, zType, SQLITE_TRANSIENT); } +#else + UNUSED_PARAMETER(pParse); + UNUSED_PARAMETER(pTabList); + UNUSED_PARAMETER(pEList); #endif /* !defined(SQLITE_OMIT_DECLTYPE) */ } @@ -126455,7 +151140,7 @@ static void generateColumnTypes( ** then the result column name with the table name ** prefix, ex: TABLE.COLUMN. Otherwise use zSpan. */ -static void generateColumnNames( +SQLITE_PRIVATE void sqlite3GenerateColumnNames( Parse *pParse, /* Parser context */ Select *pSelect /* Generate column names for this SELECT statement */ ){ @@ -126468,17 +151153,10 @@ static void generateColumnNames( int fullName; /* TABLE.COLUMN if no AS clause and is a direct table ref */ int srcName; /* COLUMN or TABLE.COLUMN if no AS clause and is direct */ -#ifndef SQLITE_OMIT_EXPLAIN - /* If this is an EXPLAIN, skip this step */ - if( pParse->explain ){ - return; - } -#endif - if( pParse->colNamesSet ) return; /* Column names are determined by the left-most term of a compound select */ while( pSelect->pPrior ) pSelect = pSelect->pPrior; - SELECTTRACE(1,pParse,pSelect,("generating column names\n")); + TREETRACE(0x80,pParse,pSelect,("generating column names\n")); pTabList = pSelect->pSrc; pEList = pSelect->pEList; assert( v!=0 ); @@ -126492,10 +151170,11 @@ static void generateColumnNames( assert( p!=0 ); assert( p->op!=TK_AGG_COLUMN ); /* Agg processing has not run yet */ - assert( p->op!=TK_COLUMN || p->y.pTab!=0 ); /* Covering idx not yet coded */ - if( pEList->a[i].zName ){ + assert( p->op!=TK_COLUMN + || (ExprUseYTab(p) && p->y.pTab!=0) ); /* Covering idx not yet coded */ + if( pEList->a[i].zEName && pEList->a[i].fg.eEName==ENAME_NAME ){ /* An AS clause always takes first priority */ - char *zName = pEList->a[i].zName; + char *zName = pEList->a[i].zEName; sqlite3VdbeSetColName(v, i, COLNAME_NAME, zName, SQLITE_TRANSIENT); }else if( srcName && p->op==TK_COLUMN ){ char *zCol; @@ -126507,7 +151186,7 @@ static void generateColumnNames( if( iCol<0 ){ zCol = "rowid"; }else{ - zCol = pTab->aCol[iCol].zName; + zCol = pTab->aCol[iCol].zCnName; } if( fullName ){ char *zName = 0; @@ -126517,7 +151196,7 @@ static void generateColumnNames( sqlite3VdbeSetColName(v, i, COLNAME_NAME, zCol, SQLITE_TRANSIENT); } }else{ - const char *z = pEList->a[i].zSpan; + const char *z = pEList->a[i].zEName; z = z==0 ? sqlite3MPrintf(db, "column%d", i+1) : sqlite3DbStrDup(db, z); sqlite3VdbeSetColName(v, i, COLNAME_NAME, z, SQLITE_DYNAMIC); } @@ -126545,7 +151224,7 @@ static void generateColumnNames( ** and will break if those assumptions changes. Hence, use extreme caution ** when modifying this routine to avoid breaking legacy. ** -** See Also: generateColumnNames() +** See Also: sqlite3GenerateColumnNames() */ SQLITE_PRIVATE int sqlite3ColumnsFromExprList( Parse *pParse, /* Parsing context */ @@ -126561,13 +151240,14 @@ SQLITE_PRIVATE int sqlite3ColumnsFromExprList( char *zName; /* Column name */ int nName; /* Size of name in zName[] */ Hash ht; /* Hash table of column names */ + Table *pTab; sqlite3HashInit(&ht); if( pEList ){ nCol = pEList->nExpr; aCol = sqlite3DbMallocZero(db, sizeof(aCol[0])*nCol); testcase( aCol==0 ); - if( nCol>32767 ) nCol = 32767; + if( NEVER(nCol>32767) ) nCol = 32767; }else{ nCol = 0; aCol = 0; @@ -126576,34 +151256,37 @@ SQLITE_PRIVATE int sqlite3ColumnsFromExprList( *pnCol = nCol; *paCol = aCol; - for(i=0, pCol=aCol; imallocFailed; i++, pCol++){ + for(i=0, pCol=aCol; inErr; i++, pCol++){ + struct ExprList_item *pX = &pEList->a[i]; + struct ExprList_item *pCollide; /* Get an appropriate name for the column */ - if( (zName = pEList->a[i].zName)!=0 ){ + if( (zName = pX->zEName)!=0 && pX->fg.eEName==ENAME_NAME ){ /* If the column contains an "AS " phrase, use as the name */ }else{ - Expr *pColExpr = sqlite3ExprSkipCollate(pEList->a[i].pExpr); - while( pColExpr->op==TK_DOT ){ + Expr *pColExpr = sqlite3ExprSkipCollateAndLikely(pX->pExpr); + while( ALWAYS(pColExpr!=0) && pColExpr->op==TK_DOT ){ pColExpr = pColExpr->pRight; assert( pColExpr!=0 ); } - assert( pColExpr->op!=TK_AGG_COLUMN ); - if( pColExpr->op==TK_COLUMN ){ + if( pColExpr->op==TK_COLUMN + && ALWAYS( ExprUseYTab(pColExpr) ) + && ALWAYS( pColExpr->y.pTab!=0 ) + ){ /* For columns use the column name name */ int iCol = pColExpr->iColumn; - Table *pTab = pColExpr->y.pTab; - assert( pTab!=0 ); + pTab = pColExpr->y.pTab; if( iCol<0 ) iCol = pTab->iPKey; - zName = iCol>=0 ? pTab->aCol[iCol].zName : "rowid"; + zName = iCol>=0 ? pTab->aCol[iCol].zCnName : "rowid"; }else if( pColExpr->op==TK_ID ){ assert( !ExprHasProperty(pColExpr, EP_IntValue) ); zName = pColExpr->u.zToken; }else{ /* Use the original text of the column expression as its name */ - zName = pEList->a[i].zSpan; + assert( zName==pX->zEName ); /* pointer comparison intended */ } } - if( zName ){ + if( zName && !sqlite3IsTrueOrFalse(zName) ){ zName = sqlite3DbStrDup(db, zName); }else{ zName = sqlite3MPrintf(db,"column%d",i+1); @@ -126613,85 +151296,139 @@ SQLITE_PRIVATE int sqlite3ColumnsFromExprList( ** append an integer to the name so that it becomes unique. */ cnt = 0; - while( zName && sqlite3HashFind(&ht, zName)!=0 ){ + while( zName && (pCollide = sqlite3HashFind(&ht, zName))!=0 ){ + if( pCollide->fg.bUsingTerm ){ + pCol->colFlags |= COLFLAG_NOEXPAND; + } nName = sqlite3Strlen30(zName); if( nName>0 ){ for(j=nName-1; j>0 && sqlite3Isdigit(zName[j]); j--){} if( zName[j]==':' ) nName = j; } zName = sqlite3MPrintf(db, "%.*z:%u", nName, zName, ++cnt); - if( cnt>3 ) sqlite3_randomness(sizeof(cnt), &cnt); + sqlite3ProgressCheck(pParse); + if( cnt>3 ){ + sqlite3_randomness(sizeof(cnt), &cnt); + } + } + pCol->zCnName = zName; + pCol->hName = sqlite3StrIHash(zName); + if( pX->fg.bNoExpand ){ + pCol->colFlags |= COLFLAG_NOEXPAND; } - pCol->zName = zName; sqlite3ColumnPropertiesFromName(0, pCol); - if( zName && sqlite3HashInsert(&ht, zName, pCol)==pCol ){ + if( zName && sqlite3HashInsert(&ht, zName, pX)==pX ){ sqlite3OomFault(db); } } sqlite3HashClear(&ht); - if( db->mallocFailed ){ + if( pParse->nErr ){ for(j=0; jrc; } return SQLITE_OK; } /* -** Add type and collation information to a column list based on -** a SELECT statement. -** -** The column list presumably came from selectColumnNamesFromExprList(). -** The column list has only names, not types or collations. This -** routine goes through and adds the types and collations. +** pTab is a transient Table object that represents a subquery of some +** kind (maybe a parenthesized subquery in the FROM clause of a larger +** query, or a VIEW, or a CTE). This routine computes type information +** for that Table object based on the Select object that implements the +** subquery. For the purposes of this routine, "type information" means: ** -** This routine requires that all identifiers in the SELECT -** statement be resolved. +** * The datatype name, as it might appear in a CREATE TABLE statement +** * Which collating sequence to use for the column +** * The affinity of the column */ -SQLITE_PRIVATE void sqlite3SelectAddColumnTypeAndCollation( - Parse *pParse, /* Parsing contexts */ - Table *pTab, /* Add column type information to this table */ - Select *pSelect /* SELECT used to determine types and collations */ +SQLITE_PRIVATE void sqlite3SubqueryColumnTypes( + Parse *pParse, /* Parsing contexts */ + Table *pTab, /* Add column type information to this table */ + Select *pSelect, /* SELECT used to determine types and collations */ + char aff /* Default affinity. */ ){ sqlite3 *db = pParse->db; - NameContext sNC; Column *pCol; CollSeq *pColl; - int i; + int i,j; Expr *p; struct ExprList_item *a; + NameContext sNC; assert( pSelect!=0 ); assert( (pSelect->selFlags & SF_Resolved)!=0 ); - assert( pTab->nCol==pSelect->pEList->nExpr || db->mallocFailed ); - if( db->mallocFailed ) return; + assert( pTab->nCol==pSelect->pEList->nExpr || pParse->nErr>0 ); + assert( aff==SQLITE_AFF_NONE || aff==SQLITE_AFF_BLOB ); + if( db->mallocFailed || IN_RENAME_OBJECT ) return; + while( pSelect->pPrior ) pSelect = pSelect->pPrior; + a = pSelect->pEList->a; memset(&sNC, 0, sizeof(sNC)); sNC.pSrcList = pSelect->pSrc; - a = pSelect->pEList->a; for(i=0, pCol=pTab->aCol; inCol; i++, pCol++){ const char *zType; - int n, m; + i64 n; + int m = 0; + Select *pS2 = pSelect; + pTab->tabFlags |= (pCol->colFlags & COLFLAG_NOINSERT); p = a[i].pExpr; - zType = columnType(&sNC, p, 0, 0, 0); /* pCol->szEst = ... // Column size est for SELECT tables never used */ pCol->affinity = sqlite3ExprAffinity(p); + while( pCol->affinity<=SQLITE_AFF_NONE && pS2->pNext!=0 ){ + m |= sqlite3ExprDataType(pS2->pEList->a[i].pExpr); + pS2 = pS2->pNext; + pCol->affinity = sqlite3ExprAffinity(pS2->pEList->a[i].pExpr); + } + if( pCol->affinity<=SQLITE_AFF_NONE ){ + pCol->affinity = aff; + } + if( pCol->affinity>=SQLITE_AFF_TEXT && (pS2->pNext || pS2!=pSelect) ){ + for(pS2=pS2->pNext; pS2; pS2=pS2->pNext){ + m |= sqlite3ExprDataType(pS2->pEList->a[i].pExpr); + } + if( pCol->affinity==SQLITE_AFF_TEXT && (m&0x01)!=0 ){ + pCol->affinity = SQLITE_AFF_BLOB; + }else + if( pCol->affinity>=SQLITE_AFF_NUMERIC && (m&0x02)!=0 ){ + pCol->affinity = SQLITE_AFF_BLOB; + } + if( pCol->affinity>=SQLITE_AFF_NUMERIC && p->op==TK_CAST ){ + pCol->affinity = SQLITE_AFF_FLEXNUM; + } + } + zType = columnType(&sNC, p, 0, 0, 0); + if( zType==0 || pCol->affinity!=sqlite3AffinityType(zType, 0) ){ + if( pCol->affinity==SQLITE_AFF_NUMERIC + || pCol->affinity==SQLITE_AFF_FLEXNUM + ){ + zType = "NUM"; + }else{ + zType = 0; + for(j=1; jaffinity ){ + zType = sqlite3StdType[j]; + break; + } + } + } + } if( zType ){ - m = sqlite3Strlen30(zType); - n = sqlite3Strlen30(pCol->zName); - pCol->zName = sqlite3DbReallocOrFree(db, pCol->zName, n+m+2); - if( pCol->zName ){ - memcpy(&pCol->zName[n+1], zType, m+1); + const i64 k = strlen(zType); + n = strlen(pCol->zCnName); + pCol->zCnName = sqlite3DbReallocOrFree(db, pCol->zCnName, n+k+2); + pCol->colFlags &= ~(COLFLAG_HASTYPE|COLFLAG_HASCOLL); + if( pCol->zCnName ){ + memcpy(&pCol->zCnName[n+1], zType, k+1); pCol->colFlags |= COLFLAG_HASTYPE; } } - if( pCol->affinity==0 ) pCol->affinity = SQLITE_AFF_BLOB; pColl = sqlite3ExprCollSeq(pParse, p); - if( pColl && pCol->zColl==0 ){ - pCol->zColl = sqlite3DbStrDup(db, pColl->zName); + if( pColl ){ + assert( pTab->pIndex==0 ); + sqlite3ColumnSetColl(db, pCol, pColl->zName); } } pTab->szTabRow = 1; /* Any non-zero value works */ @@ -126701,11 +151438,18 @@ SQLITE_PRIVATE void sqlite3SelectAddColumnTypeAndCollation( ** Given a SELECT statement, generate a Table structure that describes ** the result set of that SELECT. */ -SQLITE_PRIVATE Table *sqlite3ResultSetOfSelect(Parse *pParse, Select *pSelect){ +SQLITE_PRIVATE Table *sqlite3ResultSetOfSelect(Parse *pParse, Select *pSelect, char aff){ Table *pTab; sqlite3 *db = pParse->db; u64 savedFlags; + pParse->nNestSel++; +#if SQLITE_MAX_EXPR_DEPTH>0 + if( pParse->nNestSel >= db->aLimit[SQLITE_LIMIT_EXPR_DEPTH] ){ + sqlite3ErrorMsg(pParse, "VIEWs and/or subqueries nested too deep"); + return 0; + } +#endif savedFlags = db->flags; db->flags &= ~(u64)SQLITE_FullColNames; db->flags |= SQLITE_ShortColNames; @@ -126717,19 +151461,18 @@ SQLITE_PRIVATE Table *sqlite3ResultSetOfSelect(Parse *pParse, Select *pSelect){ if( pTab==0 ){ return 0; } - /* The sqlite3ResultSetOfSelect() is only used n contexts where lookaside - ** is disabled */ - assert( db->lookaside.bDisable ); pTab->nTabRef = 1; pTab->zName = 0; pTab->nRowLogEst = 200; assert( 200==sqlite3LogEst(1048576) ); sqlite3ColumnsFromExprList(pParse, pSelect->pEList, &pTab->nCol, &pTab->aCol); - sqlite3SelectAddColumnTypeAndCollation(pParse, pTab, pSelect); + sqlite3SubqueryColumnTypes(pParse, pTab, pSelect, aff); pTab->iPKey = -1; if( db->mallocFailed ){ sqlite3DeleteTable(db, pTab); return 0; } + pParse->nNestSel--; + assert( pParse->nNestSel>=0 ); return pTab; } @@ -126754,9 +151497,9 @@ SQLITE_PRIVATE Vdbe *sqlite3GetVdbe(Parse *pParse){ ** Compute the iLimit and iOffset fields of the SELECT based on the ** pLimit expressions. pLimit->pLeft and pLimit->pRight hold the expressions ** that appear in the original SQL statement after the LIMIT and OFFSET -** keywords. Or NULL if those keywords are omitted. iLimit and iOffset -** are the integer memory register numbers for counters used to compute -** the limit and offset. If there is no limit and/or offset, then +** keywords. Or NULL if those keywords are omitted. iLimit and iOffset +** are the integer memory register numbers for counters used to compute +** the limit and offset. If there is no limit and/or offset, then ** iLimit and iOffset are negative. ** ** This routine changes the values of iLimit and iOffset only if @@ -126782,7 +151525,7 @@ static void computeLimitRegisters(Parse *pParse, Select *p, int iBreak){ if( p->iLimit ) return; - /* + /* ** "LIMIT -1" always shows all rows. There is some ** controversy about what the correct behavior should be. ** The current implementation interprets "LIMIT 0" to mean @@ -126794,7 +151537,7 @@ static void computeLimitRegisters(Parse *pParse, Select *p, int iBreak){ p->iLimit = iLimit = ++pParse->nMem; v = sqlite3GetVdbe(pParse); assert( v!=0 ); - if( sqlite3ExprIsInteger(pLimit->pLeft, &n) ){ + if( sqlite3ExprIsInteger(pLimit->pLeft, &n, pParse) ){ sqlite3VdbeAddOp2(v, OP_Integer, n, iLimit); VdbeComment((v, "LIMIT counter")); if( n==0 ){ @@ -126856,9 +151599,9 @@ static CollSeq *multiSelectCollSeq(Parse *pParse, Select *p, int iCol){ ** function is responsible for ensuring that this structure is eventually ** freed. */ -static KeyInfo *multiSelectOrderByKeyInfo(Parse *pParse, Select *p, int nExtra){ +static KeyInfo *multiSelectByMergeKeyInfo(Parse *pParse, Select *p, int nExtra){ ExprList *pOrderBy = p->pOrderBy; - int nOrderBy = p->pOrderBy->nExpr; + int nOrderBy = (pOrderBy!=0) ? pOrderBy->nExpr : 0; sqlite3 *db = pParse->db; KeyInfo *pRet = sqlite3KeyInfoAlloc(db, nOrderBy+nExtra, 1); if( pRet ){ @@ -126878,7 +151621,7 @@ static KeyInfo *multiSelectOrderByKeyInfo(Parse *pParse, Select *p, int nExtra){ } assert( sqlite3KeyInfoIsWriteable(pRet) ); pRet->aColl[i] = pColl; - pRet->aSortOrder[i] = pOrderBy->a[i].sortOrder; + pRet->aSortFlags[i] = pOrderBy->a[i].fg.sortFlags; } } @@ -126910,7 +151653,7 @@ static KeyInfo *multiSelectOrderByKeyInfo(Parse *pParse, Select *p, int nExtra){ ** inserted into the Queue table. The iDistinct table keeps a copy of all rows ** that have ever been inserted into Queue and causes duplicates to be ** discarded. If the operator is UNION ALL, then duplicates are allowed. -** +** ** If the query has an ORDER BY, then entries in the Queue table are kept in ** ORDER BY order and the first entry is extracted for each cycle. Without ** an ORDER BY, the Queue table is just a FIFO. @@ -126930,7 +151673,8 @@ static void generateWithRecursiveQuery( SrcList *pSrc = p->pSrc; /* The FROM clause of the recursive query */ int nCol = p->pEList->nExpr; /* Number of columns in the recursive table */ Vdbe *v = pParse->pVdbe; /* The prepared statement under construction */ - Select *pSetup = p->pPrior; /* The setup query */ + Select *pSetup; /* The setup query */ + Select *pFirstRec; /* Left-most recursive term */ int addrTop; /* Top of the loop */ int addrCont, addrBreak; /* CONTINUE and BREAK addresses */ int iCurrent = 0; /* The Current table */ @@ -126938,7 +151682,7 @@ static void generateWithRecursiveQuery( int iQueue; /* The Queue table */ int iDistinct = 0; /* To ensure unique results if UNION */ int eDest = SRT_Fifo; /* How to write to Queue */ - SelectDest destQueue; /* SelectDest targetting the Queue table */ + SelectDest destQueue; /* SelectDest targeting the Queue table */ int i; /* Loop counter */ int rc; /* Result code */ ExprList *pOrderBy; /* The ORDER BY clause */ @@ -126990,7 +151734,7 @@ static void generateWithRecursiveQuery( regCurrent = ++pParse->nMem; sqlite3VdbeAddOp3(v, OP_OpenPseudo, iCurrent, regCurrent, nCol); if( pOrderBy ){ - KeyInfo *pKeyInfo = multiSelectOrderByKeyInfo(pParse, p, 1); + KeyInfo *pKeyInfo = multiSelectByMergeKeyInfo(pParse, p, 1); sqlite3VdbeAddOp4(v, OP_OpenEphemeral, iQueue, pOrderBy->nExpr+2, 0, (char*)pKeyInfo, P4_KEYINFO); destQueue.pOrderBy = pOrderBy; @@ -126999,14 +151743,51 @@ static void generateWithRecursiveQuery( } VdbeComment((v, "Queue table")); if( iDistinct ){ - p->addrOpenEphm[0] = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, iDistinct, 0); - p->selFlags |= SF_UsesEphemeral; + /* Generate an ephemeral table used to enforce distinctness on the + ** output of the recursive part of the CTE. + */ + KeyInfo *pKeyInfo; /* Collating sequence for the result set */ + CollSeq **apColl; /* For looping through pKeyInfo->aColl[] */ + + assert( p->pNext==0 ); + assert( p->pEList!=0 ); + nCol = p->pEList->nExpr; + pKeyInfo = sqlite3KeyInfoAlloc(pParse->db, nCol, 1); + if( pKeyInfo ){ + for(i=0, apColl=pKeyInfo->aColl; idb->pDfltColl; + } + } + sqlite3VdbeAddOp4(v, OP_OpenEphemeral, iDistinct, nCol, 0, + (void*)pKeyInfo, P4_KEYINFO); + }else{ + assert( pParse->nErr>0 ); + } } /* Detach the ORDER BY clause from the compound SELECT */ p->pOrderBy = 0; + /* Figure out how many elements of the compound SELECT are part of the + ** recursive query. Make sure no recursive elements use aggregate + ** functions. Mark the recursive elements as UNION ALL even if they + ** are really UNION because the distinctness will be enforced by the + ** iDistinct table. pFirstRec is left pointing to the left-most + ** recursive term of the CTE. + */ + for(pFirstRec=p; ALWAYS(pFirstRec!=0); pFirstRec=pFirstRec->pPrior){ + if( pFirstRec->selFlags & SF_Aggregate ){ + sqlite3ErrorMsg(pParse, "recursive aggregate queries not supported"); + goto end_of_recursive_query; + } + pFirstRec->op = TK_ALL; + if( (pFirstRec->pPrior->selFlags & SF_Recursive)==0 ) break; + } + /* Store the results of the setup-query in Queue. */ + pSetup = pFirstRec->pPrior; pSetup->pNext = 0; ExplainQueryPlan((pParse, 1, "SETUP")); rc = sqlite3Select(pParse, pSetup, &destQueue); @@ -127039,15 +151820,11 @@ static void generateWithRecursiveQuery( /* Execute the recursive SELECT taking the single row in Current as ** the value for the recursive-table. Store the results in the Queue. */ - if( p->selFlags & SF_Aggregate ){ - sqlite3ErrorMsg(pParse, "recursive aggregate queries not supported"); - }else{ - p->pPrior = 0; - ExplainQueryPlan((pParse, 1, "RECURSIVE STEP")); - sqlite3Select(pParse, p, &destQueue); - assert( p->pPrior==0 ); - p->pPrior = pSetup; - } + pFirstRec->pPrior = 0; + ExplainQueryPlan((pParse, 1, "RECURSIVE STEP")); + sqlite3Select(pParse, p, &destQueue); + assert( pFirstRec->pPrior==0 ); + pFirstRec->pPrior = pSetup; /* Keep running the loop until the Queue is empty */ sqlite3VdbeGoto(v, addrTop); @@ -127062,7 +151839,7 @@ static void generateWithRecursiveQuery( #endif /* SQLITE_OMIT_CTE */ /* Forward references */ -static int multiSelectOrderBy( +static int multiSelectByMerge( Parse *pParse, /* Parsing context */ Select *p, /* The right-most of SELECTs to be coded */ SelectDest *pDest /* What to do with query results */ @@ -127082,7 +151859,7 @@ static int multiSelectOrderBy( ** The "LIMIT of exactly 1" case of condition (1) comes about when a VALUES ** clause occurs within scalar expression (ex: "SELECT (VALUES(1),(2),(3))"). ** The sqlite3CodeSubselect will have added the LIMIT 1 clause in tht case. -** Since the limit is exactly 1, we only need to evalutes the left-most VALUES. +** Since the limit is exactly 1, we only need to evaluate the left-most VALUES. */ static int multiSelectValues( Parse *pParse, /* Parsing context */ @@ -127097,6 +151874,9 @@ static int multiSelectValues( assert( p->selFlags & SF_Values ); assert( p->op==TK_ALL || (p->op==TK_SELECT && p->pPrior==0) ); assert( p->pNext==0 || p->pEList->nExpr==p->pNext->pEList->nExpr ); +#ifndef SQLITE_OMIT_WINDOWFUNC + if( p->pWin ) return -1; +#endif if( p->pPrior==0 ) break; assert( p->pPrior->pNext==p ); p = p->pPrior; @@ -127113,6 +151893,16 @@ static int multiSelectValues( return rc; } +/* +** Return true if the SELECT statement which is known to be the recursive +** part of a recursive CTE still has its anchor terms attached. If the +** anchor terms have already been removed, then return false. +*/ +static int hasAnchor(Select *p){ + while( p && (p->selFlags & SF_Recursive)!=0 ){ p = p->pPrior; } + return p!=0; +} + /* ** This routine is called to process a compound query form from ** two or more separate queries using UNION, UNION ALL, EXCEPT, or @@ -127120,7 +151910,7 @@ static int multiSelectValues( ** ** "p" points to the right-most of the two queries. the query on the ** left is p->pPrior. The left query could also be a compound query -** in which case this routine will be called recursively. +** in which case this routine will be called recursively. ** ** The results of the total query are to be written into a destination ** of type eDest with parameter iParm. @@ -127161,15 +151951,12 @@ static int multiSelect( */ assert( p && p->pPrior ); /* Calling function guarantees this much */ assert( (p->selFlags & SF_Recursive)==0 || p->op==TK_ALL || p->op==TK_UNION ); + assert( p->selFlags & SF_Compound ); db = pParse->db; pPrior = p->pPrior; dest = *pDest; - if( pPrior->pOrderBy || pPrior->pLimit ){ - sqlite3ErrorMsg(pParse,"%s clause should come after %s not before", - pPrior->pOrderBy!=0 ? "ORDER BY" : "LIMIT", selectOpName(p->op)); - rc = 1; - goto multi_select_end; - } + assert( pPrior->pOrderBy==0 ); + assert( pPrior->pLimit==0 ); v = sqlite3GetVdbe(pParse); assert( v!=0 ); /* The VDBE already created by calling function */ @@ -127186,7 +151973,8 @@ static int multiSelect( */ if( p->selFlags & SF_MultiValue ){ rc = multiSelectValues(pParse, p, &dest); - goto multi_select_end; + if( rc>=0 ) goto multi_select_end; + rc = SQLITE_OK; } /* Make sure all SELECTs in the statement have the same number of elements @@ -127196,16 +151984,30 @@ static int multiSelect( assert( p->pEList->nExpr==pPrior->pEList->nExpr ); #ifndef SQLITE_OMIT_CTE - if( p->selFlags & SF_Recursive ){ + if( (p->selFlags & SF_Recursive)!=0 && hasAnchor(p) ){ generateWithRecursiveQuery(pParse, p, &dest); }else #endif - - /* Compound SELECTs that have an ORDER BY clause are handled separately. - */ if( p->pOrderBy ){ - return multiSelectOrderBy(pParse, p, pDest); + /* If the compound has an ORDER BY clause, then always use the merge + ** algorithm. */ + return multiSelectByMerge(pParse, p, pDest); + }else if( p->op!=TK_ALL ){ + /* If the compound is EXCEPT, INTERSECT, or UNION (anything other than + ** UNION ALL) then also always use the merge algorithm. However, the + ** multiSelectByMerge() routine requires that the compound have an + ** ORDER BY clause, and it doesn't right now. So invent one first. */ + Expr *pOne = sqlite3ExprInt32(db, 1); + p->pOrderBy = sqlite3ExprListAppend(pParse, 0, pOne); + if( pParse->nErr ) goto multi_select_end; + assert( p->pOrderBy!=0 ); + p->pOrderBy->a[0].u.x.iOrderByCol = 1; + return multiSelectByMerge(pParse, p, pDest); }else{ + /* For a UNION ALL compound without ORDER BY, simply run the left + ** query, then run the right query */ + int addr = 0; + int nLimit = 0; /* Initialize to suppress harmless compiler warning */ #ifndef SQLITE_OMIT_EXPLAIN if( pPrior->pPrior==0 ){ @@ -127213,275 +152015,58 @@ static int multiSelect( ExplainQueryPlan((pParse, 1, "LEFT-MOST SUBQUERY")); } #endif - - /* Generate code for the left and right SELECT statements. - */ - switch( p->op ){ - case TK_ALL: { - int addr = 0; - int nLimit; - assert( !pPrior->pLimit ); - pPrior->iLimit = p->iLimit; - pPrior->iOffset = p->iOffset; - pPrior->pLimit = p->pLimit; - rc = sqlite3Select(pParse, pPrior, &dest); - p->pLimit = 0; - if( rc ){ - goto multi_select_end; - } - p->pPrior = 0; - p->iLimit = pPrior->iLimit; - p->iOffset = pPrior->iOffset; - if( p->iLimit ){ - addr = sqlite3VdbeAddOp1(v, OP_IfNot, p->iLimit); VdbeCoverage(v); - VdbeComment((v, "Jump ahead if LIMIT reached")); - if( p->iOffset ){ - sqlite3VdbeAddOp3(v, OP_OffsetLimit, - p->iLimit, p->iOffset+1, p->iOffset); - } - } - ExplainQueryPlan((pParse, 1, "UNION ALL")); - rc = sqlite3Select(pParse, p, &dest); - testcase( rc!=SQLITE_OK ); - pDelete = p->pPrior; - p->pPrior = pPrior; - p->nSelectRow = sqlite3LogEstAdd(p->nSelectRow, pPrior->nSelectRow); - if( pPrior->pLimit - && sqlite3ExprIsInteger(pPrior->pLimit->pLeft, &nLimit) - && nLimit>0 && p->nSelectRow > sqlite3LogEst((u64)nLimit) - ){ - p->nSelectRow = sqlite3LogEst((u64)nLimit); - } - if( addr ){ - sqlite3VdbeJumpHere(v, addr); - } - break; - } - case TK_EXCEPT: - case TK_UNION: { - int unionTab; /* Cursor number of the temp table holding result */ - u8 op = 0; /* One of the SRT_ operations to apply to self */ - int priorOp; /* The SRT_ operation to apply to prior selects */ - Expr *pLimit; /* Saved values of p->nLimit */ - int addr; - SelectDest uniondest; - - testcase( p->op==TK_EXCEPT ); - testcase( p->op==TK_UNION ); - priorOp = SRT_Union; - if( dest.eDest==priorOp ){ - /* We can reuse a temporary table generated by a SELECT to our - ** right. - */ - assert( p->pLimit==0 ); /* Not allowed on leftward elements */ - unionTab = dest.iSDParm; - }else{ - /* We will need to create our own temporary table to hold the - ** intermediate results. - */ - unionTab = pParse->nTab++; - assert( p->pOrderBy==0 ); - addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, unionTab, 0); - assert( p->addrOpenEphm[0] == -1 ); - p->addrOpenEphm[0] = addr; - findRightmost(p)->selFlags |= SF_UsesEphemeral; - assert( p->pEList ); - } - - /* Code the SELECT statements to our left - */ - assert( !pPrior->pOrderBy ); - sqlite3SelectDestInit(&uniondest, priorOp, unionTab); - rc = sqlite3Select(pParse, pPrior, &uniondest); - if( rc ){ - goto multi_select_end; - } - - /* Code the current SELECT statement - */ - if( p->op==TK_EXCEPT ){ - op = SRT_Except; - }else{ - assert( p->op==TK_UNION ); - op = SRT_Union; - } - p->pPrior = 0; - pLimit = p->pLimit; - p->pLimit = 0; - uniondest.eDest = op; - ExplainQueryPlan((pParse, 1, "%s USING TEMP B-TREE", - selectOpName(p->op))); - rc = sqlite3Select(pParse, p, &uniondest); - testcase( rc!=SQLITE_OK ); - /* Query flattening in sqlite3Select() might refill p->pOrderBy. - ** Be sure to delete p->pOrderBy, therefore, to avoid a memory leak. */ - sqlite3ExprListDelete(db, p->pOrderBy); - pDelete = p->pPrior; - p->pPrior = pPrior; - p->pOrderBy = 0; - if( p->op==TK_UNION ){ - p->nSelectRow = sqlite3LogEstAdd(p->nSelectRow, pPrior->nSelectRow); - } - sqlite3ExprDelete(db, p->pLimit); - p->pLimit = pLimit; - p->iLimit = 0; - p->iOffset = 0; - - /* Convert the data in the temporary table into whatever form - ** it is that we currently need. - */ - assert( unionTab==dest.iSDParm || dest.eDest!=priorOp ); - if( dest.eDest!=priorOp ){ - int iCont, iBreak, iStart; - assert( p->pEList ); - iBreak = sqlite3VdbeMakeLabel(pParse); - iCont = sqlite3VdbeMakeLabel(pParse); - computeLimitRegisters(pParse, p, iBreak); - sqlite3VdbeAddOp2(v, OP_Rewind, unionTab, iBreak); VdbeCoverage(v); - iStart = sqlite3VdbeCurrentAddr(v); - selectInnerLoop(pParse, p, unionTab, - 0, 0, &dest, iCont, iBreak); - sqlite3VdbeResolveLabel(v, iCont); - sqlite3VdbeAddOp2(v, OP_Next, unionTab, iStart); VdbeCoverage(v); - sqlite3VdbeResolveLabel(v, iBreak); - sqlite3VdbeAddOp2(v, OP_Close, unionTab, 0); - } - break; - } - default: assert( p->op==TK_INTERSECT ); { - int tab1, tab2; - int iCont, iBreak, iStart; - Expr *pLimit; - int addr; - SelectDest intersectdest; - int r1; - - /* INTERSECT is different from the others since it requires - ** two temporary tables. Hence it has its own case. Begin - ** by allocating the tables we will need. - */ - tab1 = pParse->nTab++; - tab2 = pParse->nTab++; - assert( p->pOrderBy==0 ); - - addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, tab1, 0); - assert( p->addrOpenEphm[0] == -1 ); - p->addrOpenEphm[0] = addr; - findRightmost(p)->selFlags |= SF_UsesEphemeral; - assert( p->pEList ); - - /* Code the SELECTs to our left into temporary table "tab1". - */ - sqlite3SelectDestInit(&intersectdest, SRT_Union, tab1); - rc = sqlite3Select(pParse, pPrior, &intersectdest); - if( rc ){ - goto multi_select_end; - } - - /* Code the current SELECT into temporary table "tab2" - */ - addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, tab2, 0); - assert( p->addrOpenEphm[1] == -1 ); - p->addrOpenEphm[1] = addr; - p->pPrior = 0; - pLimit = p->pLimit; - p->pLimit = 0; - intersectdest.iSDParm = tab2; - ExplainQueryPlan((pParse, 1, "%s USING TEMP B-TREE", - selectOpName(p->op))); - rc = sqlite3Select(pParse, p, &intersectdest); - testcase( rc!=SQLITE_OK ); - pDelete = p->pPrior; - p->pPrior = pPrior; - if( p->nSelectRow>pPrior->nSelectRow ){ - p->nSelectRow = pPrior->nSelectRow; - } - sqlite3ExprDelete(db, p->pLimit); - p->pLimit = pLimit; - - /* Generate code to take the intersection of the two temporary - ** tables. - */ - assert( p->pEList ); - iBreak = sqlite3VdbeMakeLabel(pParse); - iCont = sqlite3VdbeMakeLabel(pParse); - computeLimitRegisters(pParse, p, iBreak); - sqlite3VdbeAddOp2(v, OP_Rewind, tab1, iBreak); VdbeCoverage(v); - r1 = sqlite3GetTempReg(pParse); - iStart = sqlite3VdbeAddOp2(v, OP_RowData, tab1, r1); - sqlite3VdbeAddOp4Int(v, OP_NotFound, tab2, iCont, r1, 0); - VdbeCoverage(v); - sqlite3ReleaseTempReg(pParse, r1); - selectInnerLoop(pParse, p, tab1, - 0, 0, &dest, iCont, iBreak); - sqlite3VdbeResolveLabel(v, iCont); - sqlite3VdbeAddOp2(v, OP_Next, tab1, iStart); VdbeCoverage(v); - sqlite3VdbeResolveLabel(v, iBreak); - sqlite3VdbeAddOp2(v, OP_Close, tab2, 0); - sqlite3VdbeAddOp2(v, OP_Close, tab1, 0); - break; - } - } - - #ifndef SQLITE_OMIT_EXPLAIN - if( p->pNext==0 ){ - ExplainQueryPlanPop(pParse); - } - #endif - } - - /* Compute collating sequences used by - ** temporary tables needed to implement the compound select. - ** Attach the KeyInfo structure to all temporary tables. - ** - ** This section is run by the right-most SELECT statement only. - ** SELECT statements to the left always skip this part. The right-most - ** SELECT might also skip this part if it has no ORDER BY clause and - ** no temp tables are required. - */ - if( p->selFlags & SF_UsesEphemeral ){ - int i; /* Loop counter */ - KeyInfo *pKeyInfo; /* Collating sequence for the result set */ - Select *pLoop; /* For looping through SELECT statements */ - CollSeq **apColl; /* For looping through pKeyInfo->aColl[] */ - int nCol; /* Number of columns in result set */ - - assert( p->pNext==0 ); - nCol = p->pEList->nExpr; - pKeyInfo = sqlite3KeyInfoAlloc(db, nCol, 1); - if( !pKeyInfo ){ - rc = SQLITE_NOMEM_BKPT; + assert( !pPrior->pLimit ); + pPrior->iLimit = p->iLimit; + pPrior->iOffset = p->iOffset; + pPrior->pLimit = sqlite3ExprDup(db, p->pLimit, 0); + TREETRACE(0x200, pParse, p, ("multiSelect UNION ALL left...\n")); + rc = sqlite3Select(pParse, pPrior, &dest); + sqlite3ExprDelete(db, pPrior->pLimit); + pPrior->pLimit = 0; + if( rc ){ goto multi_select_end; } - for(i=0, apColl=pKeyInfo->aColl; ipDfltColl; - } + p->pPrior = 0; + p->iLimit = pPrior->iLimit; + p->iOffset = pPrior->iOffset; + if( p->iLimit ){ + addr = sqlite3VdbeAddOp1(v, OP_IfNot, p->iLimit); VdbeCoverage(v); + VdbeComment((v, "Jump ahead if LIMIT reached")); + if( p->iOffset ){ + sqlite3VdbeAddOp3(v, OP_OffsetLimit, + p->iLimit, p->iOffset+1, p->iOffset); + } + } + ExplainQueryPlan((pParse, 1, "UNION ALL")); + TREETRACE(0x200, pParse, p, ("multiSelect UNION ALL right...\n")); + rc = sqlite3Select(pParse, p, &dest); + testcase( rc!=SQLITE_OK ); + pDelete = p->pPrior; + p->pPrior = pPrior; + p->nSelectRow = sqlite3LogEstAdd(p->nSelectRow, pPrior->nSelectRow); + if( p->pLimit + && sqlite3ExprIsInteger(p->pLimit->pLeft, &nLimit, pParse) + && nLimit>0 && p->nSelectRow > sqlite3LogEst((u64)nLimit) + ){ + p->nSelectRow = sqlite3LogEst((u64)nLimit); } - - for(pLoop=p; pLoop; pLoop=pLoop->pPrior){ - for(i=0; i<2; i++){ - int addr = pLoop->addrOpenEphm[i]; - if( addr<0 ){ - /* If [0] is unused then [1] is also unused. So we can - ** always safely abort as soon as the first unused slot is found */ - assert( pLoop->addrOpenEphm[1]<0 ); - break; - } - sqlite3VdbeChangeP2(v, addr, nCol); - sqlite3VdbeChangeP4(v, addr, (char*)sqlite3KeyInfoRef(pKeyInfo), - P4_KEYINFO); - pLoop->addrOpenEphm[i] = -1; - } + if( addr ){ + sqlite3VdbeJumpHere(v, addr); + } +#ifndef SQLITE_OMIT_EXPLAIN + if( p->pNext==0 ){ + ExplainQueryPlanPop(pParse); } - sqlite3KeyInfoUnref(pKeyInfo); +#endif } multi_select_end: pDest->iSdst = dest.iSdst; pDest->nSdst = dest.nSdst; - sqlite3SelectDelete(db, pDelete); + pDest->iSDParm2 = dest.iSDParm2; + if( pDelete ){ + sqlite3ParserAddCleanup(pParse, sqlite3SelectDeleteGeneric, pDelete); + } return rc; } #endif /* SQLITE_OMIT_COMPOUND_SELECT */ @@ -127495,16 +152080,17 @@ SQLITE_PRIVATE void sqlite3SelectWrongNumTermsError(Parse *pParse, Select *p){ sqlite3ErrorMsg(pParse, "all VALUES must have the same number of terms"); }else{ sqlite3ErrorMsg(pParse, "SELECTs to the left and right of %s" - " do not have the same number of result columns", selectOpName(p->op)); + " do not have the same number of result columns", + sqlite3SelectOpName(p->op)); } } /* ** Code an output subroutine for a coroutine implementation of a -** SELECT statment. +** SELECT statement. ** -** The data to be output is contained in pIn->iSdst. There are -** pIn->nSdst columns to be output. pDest is where the output should +** The data to be output is contained in an array of pIn->nSdst registers +** starting at register pIn->iSdst. pDest is where the output should ** be sent. ** ** regReturn is the number of the register holding the subroutine @@ -127533,10 +152119,12 @@ static int generateOutputSubroutine( int iContinue; int addr; + assert( pIn->eDest==SRT_Coroutine ); + addr = sqlite3VdbeCurrentAddr(v); iContinue = sqlite3VdbeMakeLabel(pParse); - /* Suppress duplicates for UNION, EXCEPT, and INTERSECT + /* Suppress duplicates for UNION, EXCEPT, and INTERSECT */ if( regPrev ){ int addr1, addr2; @@ -127554,23 +152142,60 @@ static int generateOutputSubroutine( */ codeOffset(v, p->iOffset, iContinue); - assert( pDest->eDest!=SRT_Exists ); - assert( pDest->eDest!=SRT_Table ); switch( pDest->eDest ){ /* Store the result as data using a unique key. */ + case SRT_Fifo: + case SRT_DistFifo: + case SRT_Table: case SRT_EphemTab: { int r1 = sqlite3GetTempReg(pParse); int r2 = sqlite3GetTempReg(pParse); + int iParm = pDest->iSDParm; + testcase( pDest->eDest==SRT_Table ); + testcase( pDest->eDest==SRT_EphemTab ); + testcase( pDest->eDest==SRT_Fifo ); + testcase( pDest->eDest==SRT_DistFifo ); sqlite3VdbeAddOp3(v, OP_MakeRecord, pIn->iSdst, pIn->nSdst, r1); - sqlite3VdbeAddOp2(v, OP_NewRowid, pDest->iSDParm, r2); - sqlite3VdbeAddOp3(v, OP_Insert, pDest->iSDParm, r1, r2); +#if !defined(SQLITE_ENABLE_NULL_TRIM) && defined(SQLITE_DEBUG) + /* A destination of SRT_Table and a non-zero iSDParm2 parameter means + ** that this is an "UPDATE ... FROM" on a virtual table or view. In this + ** case set the p5 parameter of the OP_MakeRecord to OPFLAG_NOCHNG_MAGIC. + ** This does not affect operation in any way - it just allows MakeRecord + ** to process OPFLAG_NOCHANGE values without an assert() failing. */ + if( pDest->eDest==SRT_Table && pDest->iSDParm2 ){ + sqlite3VdbeChangeP5(v, OPFLAG_NOCHNG_MAGIC); + } +#endif +#ifndef SQLITE_OMIT_CTE + if( pDest->eDest==SRT_DistFifo ){ + /* If the destination is DistFifo, then cursor (iParm+1) is open + ** on an ephemeral index that is used to enforce uniqueness on the + ** total result. At this point, we are processing the setup portion + ** of the recursive CTE using the merge algorithm, so the results are + ** guaranteed to be unique anyhow. But we still need to populate the + ** (iParm+1) cursor for use by the subsequent recursive phase. + */ + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iParm+1, r1, + pIn->iSdst, pIn->nSdst); + } +#endif + sqlite3VdbeAddOp2(v, OP_NewRowid, iParm, r2); + sqlite3VdbeAddOp3(v, OP_Insert, iParm, r1, r2); sqlite3VdbeChangeP5(v, OPFLAG_APPEND); sqlite3ReleaseTempReg(pParse, r2); sqlite3ReleaseTempReg(pParse, r1); break; } + /* If any row exist in the result set, record that fact and abort. + */ + case SRT_Exists: { + sqlite3VdbeAddOp2(v, OP_Integer, 1, pDest->iSDParm); + /* The LIMIT clause will terminate the loop for us */ + break; + } + #ifndef SQLITE_OMIT_SUBQUERY /* If we are creating a set for an "expr IN (SELECT ...)". */ @@ -127578,21 +152203,27 @@ static int generateOutputSubroutine( int r1; testcase( pIn->nSdst>1 ); r1 = sqlite3GetTempReg(pParse); - sqlite3VdbeAddOp4(v, OP_MakeRecord, pIn->iSdst, pIn->nSdst, + sqlite3VdbeAddOp4(v, OP_MakeRecord, pIn->iSdst, pIn->nSdst, r1, pDest->zAffSdst, pIn->nSdst); sqlite3VdbeAddOp4Int(v, OP_IdxInsert, pDest->iSDParm, r1, pIn->iSdst, pIn->nSdst); + if( pDest->iSDParm2>0 ){ + sqlite3VdbeAddOp4Int(v, OP_FilterAdd, pDest->iSDParm2, 0, + pIn->iSdst, pIn->nSdst); + ExplainQueryPlan((pParse, 0, "CREATE BLOOM FILTER")); + } sqlite3ReleaseTempReg(pParse, r1); break; } /* If this is a scalar select that is part of an expression, then ** store the results in the appropriate memory cell and break out - ** of the scan loop. + ** of the scan loop. Note that the select might return multiple columns + ** if it is the RHS of a row-value IN operator. */ case SRT_Mem: { - assert( pIn->nSdst==1 || pParse->nErr>0 ); testcase( pIn->nSdst!=1 ); - sqlite3ExprCodeMove(pParse, pIn->iSdst, pDest->iSDParm, 1); + testcase( pIn->nSdst>1 ); + sqlite3ExprCodeMove(pParse, pIn->iSdst, pDest->iSDParm, pIn->nSdst); /* The LIMIT clause will jump out of the loop for us */ break; } @@ -127611,11 +152242,53 @@ static int generateOutputSubroutine( break; } +#ifndef SQLITE_OMIT_CTE + /* Write the results into a priority queue that is order according to + ** pDest->pOrderBy (in pSO). pDest->iSDParm (in iParm) is the cursor for an + ** index with pSO->nExpr+2 columns. Build a key using pSO for the first + ** pSO->nExpr columns, then make sure all keys are unique by adding a + ** final OP_Sequence column. The last column is the record as a blob. + */ + case SRT_DistQueue: + case SRT_Queue: { + int nKey; + int r1, r2, r3, ii; + ExprList *pSO; + int iParm = pDest->iSDParm; + pSO = pDest->pOrderBy; + assert( pSO ); + nKey = pSO->nExpr; + r1 = sqlite3GetTempReg(pParse); + r2 = sqlite3GetTempRange(pParse, nKey+2); + r3 = r2+nKey+1; + + sqlite3VdbeAddOp3(v, OP_MakeRecord, pIn->iSdst, pIn->nSdst, r3); + if( pDest->eDest==SRT_DistQueue ){ + sqlite3VdbeAddOp2(v, OP_IdxInsert, iParm+1, r3); + } + for(ii=0; iiiSdst + pSO->a[ii].u.x.iOrderByCol - 1, + r2+ii); + } + sqlite3VdbeAddOp2(v, OP_Sequence, iParm, r2+nKey); + sqlite3VdbeAddOp3(v, OP_MakeRecord, r2, nKey+2, r1); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iParm, r1, r2, nKey+2); + sqlite3ReleaseTempReg(pParse, r1); + sqlite3ReleaseTempRange(pParse, r2, nKey+2); + break; + } +#endif /* SQLITE_OMIT_CTE */ + + /* Ignore the output */ + case SRT_Discard: { + break; + } + /* If none of the above, then the result destination must be - ** SRT_Output. This routine is never called with any other - ** destination other than the ones handled above or SRT_Output. + ** SRT_Output. ** - ** For SRT_Output, results are stored in a sequence of registers. + ** For SRT_Output, results are stored in a sequence of registers. ** Then the OP_ResultRow opcode is used to cause sqlite3_step() to ** return the next row of result. */ @@ -127641,8 +152314,9 @@ static int generateOutputSubroutine( } /* -** Alternative compound select code generator for cases when there -** is an ORDER BY clause. +** Generate code for a compound SELECT statement using a merge +** algorithm. The compound must have an ORDER BY clause for this +** to work. ** ** We assume a query of the following form: ** @@ -127659,7 +152333,7 @@ static int generateOutputSubroutine( ** ** outB: Move the output of the selectB coroutine into the output ** of the compound query. (Only generated for UNION and -** UNION ALL. EXCEPT and INSERTSECT never output a row that +** UNION ALL. EXCEPT and INTERSECT never output a row that ** appears only in B.) ** ** AltB: Called when there is data from both coroutines and A is used: ** ** @@ -127712,27 +152386,27 @@ static int generateOutputSubroutine( ** AeqB: ... ** AgtB: ... ** Init: initialize coroutine registers -** yield coA -** if eof(A) goto EofA -** yield coB -** if eof(B) goto EofB +** yield coA, on eof goto EofA +** yield coB, on eof goto EofB ** Cmpr: Compare A, B ** Jump AltB, AeqB, AgtB ** End: ... ** ** We call AltB, AeqB, AgtB, EofA, and EofB "subroutines" but they are not ** actually called using Gosub and they do not Return. EofA and EofB loop -** until all data is exhausted then jump to the "end" labe. AltB, AeqB, -** and AgtB jump to either L2 or to one of EofA or EofB. +** until all data is exhausted then jump to the "end" label. AltB, AeqB, +** and AgtB jump to either Cmpr or to one of EofA or EofB. */ #ifndef SQLITE_OMIT_COMPOUND_SELECT -static int multiSelectOrderBy( +static int multiSelectByMerge( Parse *pParse, /* Parsing context */ Select *p, /* The right-most of SELECTs to be coded */ SelectDest *pDest /* What to do with query results */ ){ int i, j; /* Loop counters */ Select *pPrior; /* Another SELECT immediately to our left */ + Select *pSplit; /* Left-most SELECT in the right-hand group */ + int nSelect; /* Number of SELECT statements in the compound */ Vdbe *v; /* Generate code to this VDBE */ SelectDest destA; /* Destination for coroutine A */ SelectDest destB; /* Destination for coroutine B */ @@ -127757,14 +152431,14 @@ static int multiSelectOrderBy( int savedOffset; /* Saved value of p->iOffset */ int labelCmpr; /* Label for the start of the merge algorithm */ int labelEnd; /* Label for the end of the overall SELECT stmt */ - int addr1; /* Jump instructions that get retargetted */ + int addr1; /* Jump instructions that get retargeted */ int op; /* One of TK_ALL, TK_UNION, TK_EXCEPT, TK_INTERSECT */ KeyInfo *pKeyDup = 0; /* Comparison information for duplicate removal */ KeyInfo *pKeyMerge; /* Comparison information for merging rows */ sqlite3 *db; /* Database connection */ ExprList *pOrderBy; /* The ORDER BY clause */ int nOrderBy; /* Number of terms in the ORDER BY clause */ - int *aPermute; /* Mapping from ORDER BY terms to result set columns */ + u32 *aPermute; /* Mapping from ORDER BY terms to result set columns */ assert( p->pOrderBy!=0 ); assert( pKeyDup==0 ); /* "Managed" code needs this. Ticket #3382. */ @@ -127777,9 +152451,8 @@ static int multiSelectOrderBy( /* Patch up the ORDER BY clause */ - op = p->op; - pPrior = p->pPrior; - assert( pPrior->pOrderBy==0 ); + op = p->op; + assert( p->pPrior->pOrderBy==0 ); pOrderBy = p->pOrderBy; assert( pOrderBy ); nOrderBy = pOrderBy->nExpr; @@ -127792,14 +152465,13 @@ static int multiSelectOrderBy( for(i=1; db->mallocFailed==0 && i<=p->pEList->nExpr; i++){ struct ExprList_item *pItem; for(j=0, pItem=pOrderBy->a; ju.x.iOrderByCol>0 ); if( pItem->u.x.iOrderByCol==i ) break; } if( j==nOrderBy ){ - Expr *pNew = sqlite3Expr(db, TK_INTEGER, 0); + Expr *pNew = sqlite3ExprInt32(db, i); if( pNew==0 ) return SQLITE_NOMEM_BKPT; - pNew->flags |= EP_IntValue; - pNew->u.iValue = i; p->pOrderBy = pOrderBy = sqlite3ExprListAppend(pParse, pOrderBy, pNew); if( pOrderBy ) pOrderBy->a[nOrderBy++].u.x.iOrderByCol = (u16)i; } @@ -127807,30 +152479,29 @@ static int multiSelectOrderBy( } /* Compute the comparison permutation and keyinfo that is used with - ** the permutation used to determine if the next - ** row of results comes from selectA or selectB. Also add explicit - ** collations to the ORDER BY clause terms so that when the subqueries - ** to the right and the left are evaluated, they use the correct - ** collation. + ** the permutation to determine if the next row of results comes + ** from selectA or selectB. Also add literal collations to the + ** ORDER BY clause terms so that when selectA and selectB are + ** evaluated, they use the correct collation. */ - aPermute = sqlite3DbMallocRawNN(db, sizeof(int)*(nOrderBy + 1)); + aPermute = sqlite3DbMallocRawNN(db, sizeof(u32)*(nOrderBy + 1)); if( aPermute ){ struct ExprList_item *pItem; + int bKeep = 0; aPermute[0] = nOrderBy; for(i=1, pItem=pOrderBy->a; i<=nOrderBy; i++, pItem++){ + assert( pItem!=0 ); assert( pItem->u.x.iOrderByCol>0 ); assert( pItem->u.x.iOrderByCol<=p->pEList->nExpr ); aPermute[i] = pItem->u.x.iOrderByCol - 1; + if( aPermute[i]!=(u32)i-1 ) bKeep = 1; + } + if( bKeep==0 ){ + sqlite3DbFreeNN(db, aPermute); + aPermute = 0; } - pKeyMerge = multiSelectOrderByKeyInfo(pParse, p, 1); - }else{ - pKeyMerge = 0; } - - /* Reattach the ORDER BY clause to the query. - */ - p->pOrderBy = pOrderBy; - pPrior->pOrderBy = sqlite3ExprListDup(pParse->db, pOrderBy, 0); + pKeyMerge = multiSelectByMergeKeyInfo(pParse, p, 1); /* Allocate a range of temporary registers and the KeyInfo needed ** for the logic that removes duplicate result rows when the @@ -127849,19 +152520,37 @@ static int multiSelectOrderBy( assert( sqlite3KeyInfoIsWriteable(pKeyDup) ); for(i=0; iaColl[i] = multiSelectCollSeq(pParse, p, i); - pKeyDup->aSortOrder[i] = 0; + pKeyDup->aSortFlags[i] = 0; } } } - + /* Separate the left and the right query from one another */ - p->pPrior = 0; + nSelect = 1; + if( (op==TK_ALL || op==TK_UNION) + && OptimizationEnabled(db, SQLITE_BalancedMerge) + ){ + for(pSplit=p; pSplit->pPrior!=0 && pSplit->op==op; pSplit=pSplit->pPrior){ + nSelect++; + assert( pSplit->pPrior->pNext==pSplit ); + } + } + if( nSelect<=3 ){ + pSplit = p; + }else{ + pSplit = p; + for(i=2; ipPrior; } + } + pPrior = pSplit->pPrior; + assert( pPrior!=0 ); + pSplit->pPrior = 0; pPrior->pNext = 0; + assert( p->pOrderBy == pOrderBy ); + assert( pOrderBy!=0 || db->mallocFailed ); + pPrior->pOrderBy = sqlite3ExprListDup(pParse->db, pOrderBy, 0); sqlite3ResolveOrderGroupBy(pParse, p, p->pOrderBy, "ORDER"); - if( pPrior->pPrior==0 ){ - sqlite3ResolveOrderGroupBy(pParse, pPrior, pPrior->pOrderBy, "ORDER"); - } + sqlite3ResolveOrderGroupBy(pParse, pPrior, pPrior->pOrderBy, "ORDER"); /* Compute the limit registers */ computeLimitRegisters(pParse, p, labelEnd); @@ -127884,30 +152573,30 @@ static int multiSelectOrderBy( sqlite3SelectDestInit(&destA, SRT_Coroutine, regAddrA); sqlite3SelectDestInit(&destB, SRT_Coroutine, regAddrB); - ExplainQueryPlan((pParse, 1, "MERGE (%s)", selectOpName(p->op))); + ExplainQueryPlan((pParse, 1, "MERGE (%s)", sqlite3SelectOpName(p->op))); /* Generate a coroutine to evaluate the SELECT statement to the ** left of the compound operator - the "A" select. */ addrSelectA = sqlite3VdbeCurrentAddr(v) + 1; addr1 = sqlite3VdbeAddOp3(v, OP_InitCoroutine, regAddrA, 0, addrSelectA); - VdbeComment((v, "left SELECT")); + VdbeComment((v, "SUBR: next-A")); pPrior->iLimit = regLimitA; ExplainQueryPlan((pParse, 1, "LEFT")); sqlite3Select(pParse, pPrior, &destA); sqlite3VdbeEndCoroutine(v, regAddrA); sqlite3VdbeJumpHere(v, addr1); - /* Generate a coroutine to evaluate the SELECT statement on + /* Generate a coroutine to evaluate the SELECT statement on ** the right - the "B" select */ addrSelectB = sqlite3VdbeCurrentAddr(v) + 1; addr1 = sqlite3VdbeAddOp3(v, OP_InitCoroutine, regAddrB, 0, addrSelectB); - VdbeComment((v, "right SELECT")); + VdbeComment((v, "SUBR: next-B")); savedLimit = p->iLimit; savedOffset = p->iOffset; p->iLimit = regLimitB; - p->iOffset = 0; + p->iOffset = 0; ExplainQueryPlan((pParse, 1, "RIGHT")); sqlite3Select(pParse, p, &destB); p->iLimit = savedLimit; @@ -127917,16 +152606,16 @@ static int multiSelectOrderBy( /* Generate a subroutine that outputs the current row of the A ** select as the next output row of the compound select. */ - VdbeNoopComment((v, "Output routine for A")); + VdbeNoopComment((v, "SUBR: out-A")); addrOutA = generateOutputSubroutine(pParse, p, &destA, pDest, regOutA, regPrev, pKeyDup, labelEnd); - + /* Generate a subroutine that outputs the current row of the B ** select as the next output row of the compound select. */ if( op==TK_ALL || op==TK_UNION ){ - VdbeNoopComment((v, "Output routine for B")); + VdbeNoopComment((v, "SUBR: out-B")); addrOutB = generateOutputSubroutine(pParse, p, &destB, pDest, regOutB, regPrev, pKeyDup, labelEnd); @@ -127938,11 +152627,13 @@ static int multiSelectOrderBy( */ if( op==TK_EXCEPT || op==TK_INTERSECT ){ addrEofA_noB = addrEofA = labelEnd; - }else{ - VdbeNoopComment((v, "eof-A subroutine")); + }else{ + VdbeNoopComment((v, "SUBR: eof-A")); addrEofA = sqlite3VdbeAddOp2(v, OP_Gosub, regOutB, addrOutB); + VdbeComment((v, "out-B")); addrEofA_noB = sqlite3VdbeAddOp2(v, OP_Yield, regAddrB, labelEnd); VdbeCoverage(v); + VdbeComment((v, "next-B")); sqlite3VdbeGoto(v, addrEofA); p->nSelectRow = sqlite3LogEstAdd(p->nSelectRow, pPrior->nSelectRow); } @@ -127953,18 +152644,21 @@ static int multiSelectOrderBy( if( op==TK_INTERSECT ){ addrEofB = addrEofA; if( p->nSelectRow > pPrior->nSelectRow ) p->nSelectRow = pPrior->nSelectRow; - }else{ - VdbeNoopComment((v, "eof-B subroutine")); + }else{ + VdbeNoopComment((v, "SUBR: eof-B")); addrEofB = sqlite3VdbeAddOp2(v, OP_Gosub, regOutA, addrOutA); + VdbeComment((v, "out-A")); sqlite3VdbeAddOp2(v, OP_Yield, regAddrA, labelEnd); VdbeCoverage(v); + VdbeComment((v, "next-A")); sqlite3VdbeGoto(v, addrEofB); } /* Generate code to handle the case of AB */ - VdbeNoopComment((v, "A-gt-B subroutine")); addrAgtB = sqlite3VdbeCurrentAddr(v); if( op==TK_ALL || op==TK_UNION ){ sqlite3VdbeAddOp2(v, OP_Gosub, regOutB, addrOutB); + VdbeComment((v, "out-B")); + sqlite3VdbeAddOp2(v, OP_Yield, regAddrB, addrEofB); VdbeCoverage(v); + VdbeComment((v, "next-B")); + sqlite3VdbeGoto(v, labelCmpr); + }else{ + addrAgtB++; /* Just do next-B. Might as well use the next-B call + ** in the next code block */ } - sqlite3VdbeAddOp2(v, OP_Yield, regAddrB, addrEofB); VdbeCoverage(v); - sqlite3VdbeGoto(v, labelCmpr); /* This code runs once to initialize everything. */ sqlite3VdbeJumpHere(v, addr1); sqlite3VdbeAddOp2(v, OP_Yield, regAddrA, addrEofA_noB); VdbeCoverage(v); + VdbeComment((v, "next-A")); + /* v--- Also the A>B case for EXCEPT and INTERSECT */ sqlite3VdbeAddOp2(v, OP_Yield, regAddrB, addrEofB); VdbeCoverage(v); + VdbeComment((v, "next-B")); /* Implement the main merge loop */ + if( aPermute!=0 ){ + sqlite3VdbeAddOp4(v, OP_Permutation, 0, 0, 0, (char*)aPermute, P4_INTARRAY); + } sqlite3VdbeResolveLabel(v, labelCmpr); - sqlite3VdbeAddOp4(v, OP_Permutation, 0, 0, 0, (char*)aPermute, P4_INTARRAY); sqlite3VdbeAddOp4(v, OP_Compare, destA.iSdst, destB.iSdst, nOrderBy, (char*)pKeyMerge, P4_KEYINFO); - sqlite3VdbeChangeP5(v, OPFLAG_PERMUTE); - sqlite3VdbeAddOp3(v, OP_Jump, addrAltB, addrAeqB, addrAgtB); VdbeCoverage(v); + if( aPermute!=0 ){ + sqlite3VdbeChangeP5(v, OPFLAG_PERMUTE); + } + sqlite3VdbeAddOp3(v, OP_Jump, addrAltB, addrAeqB, addrAgtB); + VdbeCoverageIf(v, op==TK_ALL); + VdbeCoverageIf(v, op==TK_UNION); + VdbeCoverageIf(v, op==TK_EXCEPT); + VdbeCoverageIf(v, op==TK_INTERSECT); /* Jump to the this point in order to terminate the query. */ sqlite3VdbeResolveLabel(v, labelEnd); - /* Reassembly the compound query so that it will be freed correctly - ** by the calling function */ - if( p->pPrior ){ - sqlite3SelectDelete(db, p->pPrior); + /* Make arrangements to free the 2nd and subsequent arms of the compound + ** after the parse has finished */ + if( pSplit->pPrior ){ + sqlite3ParserAddCleanup(pParse, sqlite3SelectDeleteGeneric, pSplit->pPrior); } - p->pPrior = pPrior; - pPrior->pNext = p; + pSplit->pPrior = pPrior; + pPrior->pNext = pSplit; + sqlite3ExprListDelete(db, pPrior->pOrderBy); + pPrior->pOrderBy = 0; /*** TBD: Insert subroutine calls to close cursors on incomplete **** subqueries ****/ @@ -128032,13 +152740,43 @@ static int multiSelectOrderBy( ** ** All references to columns in table iTable are to be replaced by corresponding ** expressions in pEList. +** +** ## About "isOuterJoin": +** +** The isOuterJoin column indicates that the replacement will occur into a +** position in the parent that is NULL-able due to an OUTER JOIN. Either the +** target slot in the parent is the right operand of a LEFT JOIN, or one of +** the left operands of a RIGHT JOIN. In either case, we need to potentially +** bypass the substituted expression with OP_IfNullRow. +** +** Suppose the original expression is an integer constant. Even though the table +** has the nullRow flag set, because the expression is an integer constant, +** it will not be NULLed out. So instead, we insert an OP_IfNullRow opcode +** that checks to see if the nullRow flag is set on the table. If the nullRow +** flag is set, then the value in the register is set to NULL and the original +** expression is bypassed. If the nullRow flag is not set, then the original +** expression runs to populate the register. +** +** Example where this is needed: +** +** CREATE TABLE t1(a INTEGER PRIMARY KEY, b INT); +** CREATE TABLE t2(x INT UNIQUE); +** +** SELECT a,b,m,x FROM t1 LEFT JOIN (SELECT 59 AS m,x FROM t2) ON b=x; +** +** When the subquery on the right side of the LEFT JOIN is flattened, we +** have to add OP_IfNullRow in front of the OP_Integer that implements the +** "m" value of the subquery so that a NULL will be loaded instead of 59 +** when processing a non-matched row of the left. */ typedef struct SubstContext { Parse *pParse; /* The parsing context */ int iTable; /* Replace references to this table */ int iNewTable; /* New table number */ - int isLeftJoin; /* Add TK_IF_NULL_ROW opcodes on each replacement */ + int isOuterJoin; /* Add TK_IF_NULL_ROW opcodes on each replacement */ + int nSelDepth; /* Depth of sub-query recursion. Top==1 */ ExprList *pEList; /* Replacement expressions */ + ExprList *pCList; /* Collation sequences for replacement expr */ } SubstContext; /* Forward Declarations */ @@ -128048,13 +152786,13 @@ static void substSelect(SubstContext*, Select*, int); /* ** Scan through the expression pExpr. Replace every reference to ** a column in table number iTable with a copy of the iColumn-th -** entry in pEList. (But leave references to the ROWID column +** entry in pEList. (But leave references to the ROWID column ** unchanged.) ** ** This routine is part of the flattening procedure. A subquery ** whose result set is defined by pEList appears as entry in the ** FROM clause of a SELECT such that the VDBE cursor assigned to that -** FORM clause entry is iTable. This routine makes the necessary +** FORM clause entry is iTable. This routine makes the necessary ** changes to pExpr so that it refers directly to the source table ** of the subquery rather the result set of the subquery. */ @@ -128063,39 +152801,78 @@ static Expr *substExpr( Expr *pExpr /* Expr in which substitution occurs */ ){ if( pExpr==0 ) return 0; - if( ExprHasProperty(pExpr, EP_FromJoin) - && pExpr->iRightJoinTable==pSubst->iTable + if( ExprHasProperty(pExpr, EP_OuterON|EP_InnerON) + && pExpr->w.iJoin==pSubst->iTable ){ - pExpr->iRightJoinTable = pSubst->iNewTable; + testcase( ExprHasProperty(pExpr, EP_InnerON) ); + pExpr->w.iJoin = pSubst->iNewTable; } - if( pExpr->op==TK_COLUMN && pExpr->iTable==pSubst->iTable ){ + if( pExpr->op==TK_COLUMN + && pExpr->iTable==pSubst->iTable + && !ExprHasProperty(pExpr, EP_FixedCol) + ){ +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW if( pExpr->iColumn<0 ){ pExpr->op = TK_NULL; - }else{ + }else +#endif + { Expr *pNew; - Expr *pCopy = pSubst->pEList->a[pExpr->iColumn].pExpr; + int iColumn; + Expr *pCopy; Expr ifNullRow; - assert( pSubst->pEList!=0 && pExpr->iColumnpEList->nExpr ); + iColumn = pExpr->iColumn; + assert( iColumn>=0 ); + assert( pSubst->pEList!=0 && iColumnpEList->nExpr ); assert( pExpr->pRight==0 ); + pCopy = pSubst->pEList->a[iColumn].pExpr; if( sqlite3ExprIsVector(pCopy) ){ sqlite3VectorErrorMsg(pSubst->pParse, pCopy); }else{ sqlite3 *db = pSubst->pParse->db; - if( pSubst->isLeftJoin && pCopy->op!=TK_COLUMN ){ + if( pSubst->isOuterJoin + && (pCopy->op!=TK_COLUMN || pCopy->iTable!=pSubst->iNewTable) + ){ memset(&ifNullRow, 0, sizeof(ifNullRow)); ifNullRow.op = TK_IF_NULL_ROW; ifNullRow.pLeft = pCopy; ifNullRow.iTable = pSubst->iNewTable; + ifNullRow.iColumn = -99; + ifNullRow.flags = EP_IfNullRow; pCopy = &ifNullRow; } testcase( ExprHasProperty(pCopy, EP_Subquery) ); pNew = sqlite3ExprDup(db, pCopy, 0); - if( pNew && pSubst->isLeftJoin ){ + if( db->mallocFailed ){ + sqlite3ExprDelete(db, pNew); + return pExpr; + } + if( pSubst->isOuterJoin ){ ExprSetProperty(pNew, EP_CanBeNull); } - if( pNew && ExprHasProperty(pExpr,EP_FromJoin) ){ - pNew->iRightJoinTable = pExpr->iRightJoinTable; - ExprSetProperty(pNew, EP_FromJoin); + if( pNew->op==TK_TRUEFALSE ){ + pNew->u.iValue = sqlite3ExprTruthValue(pNew); + pNew->op = TK_INTEGER; + ExprSetProperty(pNew, EP_IntValue); + } + + /* Ensure that the expression now has an implicit collation sequence, + ** just as it did when it was a column of a view or sub-query. */ + { + CollSeq *pNat = sqlite3ExprCollSeq(pSubst->pParse, pNew); + CollSeq *pColl = sqlite3ExprCollSeq(pSubst->pParse, + pSubst->pCList->a[iColumn].pExpr + ); + if( pNat!=pColl || (pNew->op!=TK_COLUMN && pNew->op!=TK_COLLATE) ){ + pNew = sqlite3ExprAddCollateString(pSubst->pParse, pNew, + (pColl ? pColl->zName : "BINARY") + ); + } + } + ExprClearProperty(pNew, EP_Collate); + if( ExprHasProperty(pExpr,EP_OuterON|EP_InnerON) ){ + sqlite3SetJoinExpr(pNew, pExpr->w.iJoin, + pExpr->flags & (EP_OuterON|EP_InnerON)); } sqlite3ExprDelete(db, pExpr); pExpr = pNew; @@ -128105,13 +152882,24 @@ static Expr *substExpr( if( pExpr->op==TK_IF_NULL_ROW && pExpr->iTable==pSubst->iTable ){ pExpr->iTable = pSubst->iNewTable; } + if( pExpr->op==TK_AGG_FUNCTION && pExpr->op2>=pSubst->nSelDepth ){ + pExpr->op2--; + } pExpr->pLeft = substExpr(pSubst, pExpr->pLeft); pExpr->pRight = substExpr(pSubst, pExpr->pRight); - if( ExprHasProperty(pExpr, EP_xIsSelect) ){ + if( ExprUseXSelect(pExpr) ){ substSelect(pSubst, pExpr->x.pSelect, 1); }else{ substExprList(pSubst, pExpr->x.pList); } +#ifndef SQLITE_OMIT_WINDOWFUNC + if( ExprHasProperty(pExpr, EP_WinFunc) ){ + Window *pWin = pExpr->y.pWin; + pWin->pFilter = substExpr(pSubst, pWin->pFilter); + substExprList(pSubst, pWin->pPartition); + substExprList(pSubst, pWin->pOrderBy); + } +#endif } return pExpr; } @@ -128131,9 +152919,10 @@ static void substSelect( int doPrior /* Do substitutes on p->pPrior too */ ){ SrcList *pSrc; - struct SrcList_item *pItem; + SrcItem *pItem; int i; if( !p ) return; + pSubst->nSelDepth++; do{ substExprList(pSubst, p->pEList); substExprList(pSubst, p->pGroupBy); @@ -128143,15 +152932,189 @@ static void substSelect( pSrc = p->pSrc; assert( pSrc!=0 ); for(i=pSrc->nSrc, pItem=pSrc->a; i>0; i--, pItem++){ - substSelect(pSubst, pItem->pSelect, 1); + if( pItem->fg.isSubquery ){ + substSelect(pSubst, pItem->u4.pSubq->pSelect, 1); + } if( pItem->fg.isTabFunc ){ substExprList(pSubst, pItem->u1.pFuncArg); } } }while( doPrior && (p = p->pPrior)!=0 ); + pSubst->nSelDepth--; +} +#endif /* !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) */ + +#if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) +/* +** pSelect is a SELECT statement and pSrcItem is one item in the FROM +** clause of that SELECT. +** +** This routine scans the entire SELECT statement and recomputes the +** pSrcItem->colUsed mask. +*/ +static int recomputeColumnsUsedExpr(Walker *pWalker, Expr *pExpr){ + SrcItem *pItem; + if( pExpr->op!=TK_COLUMN ) return WRC_Continue; + pItem = pWalker->u.pSrcItem; + if( pItem->iCursor!=pExpr->iTable ) return WRC_Continue; + if( pExpr->iColumn<0 ) return WRC_Continue; + pItem->colUsed |= sqlite3ExprColUsed(pExpr); + return WRC_Continue; +} +static void recomputeColumnsUsed( + Select *pSelect, /* The complete SELECT statement */ + SrcItem *pSrcItem /* Which FROM clause item to recompute */ +){ + Walker w; + if( NEVER(pSrcItem->pSTab==0) ) return; + memset(&w, 0, sizeof(w)); + w.xExprCallback = recomputeColumnsUsedExpr; + w.xSelectCallback = sqlite3SelectWalkNoop; + w.u.pSrcItem = pSrcItem; + pSrcItem->colUsed = 0; + sqlite3WalkSelect(&w, pSelect); +} +#endif /* !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) */ + +#if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) +/* +** Assign new cursor numbers to each of the items in pSrc. For each +** new cursor number assigned, set an entry in the aCsrMap[] array +** to map the old cursor number to the new: +** +** aCsrMap[iOld+1] = iNew; +** +** The array is guaranteed by the caller to be large enough for all +** existing cursor numbers in pSrc. aCsrMap[0] is the array size. +** +** If pSrc contains any sub-selects, call this routine recursively +** on the FROM clause of each such sub-select, with iExcept set to -1. +*/ +static void srclistRenumberCursors( + Parse *pParse, /* Parse context */ + int *aCsrMap, /* Array to store cursor mappings in */ + SrcList *pSrc, /* FROM clause to renumber */ + int iExcept /* FROM clause item to skip */ +){ + int i; + SrcItem *pItem; + for(i=0, pItem=pSrc->a; inSrc; i++, pItem++){ + if( i!=iExcept ){ + Select *p; + assert( pItem->iCursor < aCsrMap[0] ); + if( !pItem->fg.isRecursive || aCsrMap[pItem->iCursor+1]==0 ){ + aCsrMap[pItem->iCursor+1] = pParse->nTab++; + } + pItem->iCursor = aCsrMap[pItem->iCursor+1]; + if( pItem->fg.isSubquery ){ + for(p=pItem->u4.pSubq->pSelect; p; p=p->pPrior){ + srclistRenumberCursors(pParse, aCsrMap, p->pSrc, -1); + } + } + } + } +} + +/* +** *piCursor is a cursor number. Change it if it needs to be mapped. +*/ +static void renumberCursorDoMapping(Walker *pWalker, int *piCursor){ + int *aCsrMap = pWalker->u.aiCol; + int iCsr = *piCursor; + if( iCsr < aCsrMap[0] && aCsrMap[iCsr+1]>0 ){ + *piCursor = aCsrMap[iCsr+1]; + } +} + +/* +** Expression walker callback used by renumberCursors() to update +** Expr objects to match newly assigned cursor numbers. +*/ +static int renumberCursorsCb(Walker *pWalker, Expr *pExpr){ + int op = pExpr->op; + if( op==TK_COLUMN || op==TK_IF_NULL_ROW ){ + renumberCursorDoMapping(pWalker, &pExpr->iTable); + } + if( ExprHasProperty(pExpr, EP_OuterON) ){ + renumberCursorDoMapping(pWalker, &pExpr->w.iJoin); + } + return WRC_Continue; +} + +/* +** Assign a new cursor number to each cursor in the FROM clause (Select.pSrc) +** of the SELECT statement passed as the second argument, and to each +** cursor in the FROM clause of any FROM clause sub-selects, recursively. +** Except, do not assign a new cursor number to the iExcept'th element in +** the FROM clause of (*p). Update all expressions and other references +** to refer to the new cursor numbers. +** +** Argument aCsrMap is an array that may be used for temporary working +** space. Two guarantees are made by the caller: +** +** * the array is larger than the largest cursor number used within the +** select statement passed as an argument, and +** +** * the array entries for all cursor numbers that do *not* appear in +** FROM clauses of the select statement as described above are +** initialized to zero. +*/ +static void renumberCursors( + Parse *pParse, /* Parse context */ + Select *p, /* Select to renumber cursors within */ + int iExcept, /* FROM clause item to skip */ + int *aCsrMap /* Working space */ +){ + Walker w; + srclistRenumberCursors(pParse, aCsrMap, p->pSrc, iExcept); + memset(&w, 0, sizeof(w)); + w.u.aiCol = aCsrMap; + w.xExprCallback = renumberCursorsCb; + w.xSelectCallback = sqlite3SelectWalkNoop; + sqlite3WalkSelect(&w, p); } #endif /* !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) */ +/* +** If pSel is not part of a compound SELECT, return a pointer to its +** expression list. Otherwise, return a pointer to the expression list +** of the leftmost SELECT in the compound. +*/ +static ExprList *findLeftmostExprlist(Select *pSel){ + while( pSel->pPrior ){ + pSel = pSel->pPrior; + } + return pSel->pEList; +} + +/* +** Return true if any of the result-set columns in the compound query +** have incompatible affinities on one or more arms of the compound. +*/ +static int compoundHasDifferentAffinities(Select *p){ + int ii; + ExprList *pList; + assert( p!=0 ); + assert( p->pEList!=0 ); + assert( p->pPrior!=0 ); + pList = p->pEList; + for(ii=0; iinExpr; ii++){ + char aff; + Select *pSub1; + assert( pList->a[ii].pExpr!=0 ); + aff = sqlite3ExprAffinity(pList->a[ii].pExpr); + for(pSub1=p->pPrior; pSub1; pSub1=pSub1->pPrior){ + assert( pSub1->pEList!=0 ); + assert( pSub1->pEList->nExpr>ii ); + assert( pSub1->pEList->a[ii].pExpr!=0 ); + if( sqlite3ExprAffinity(pSub1->pEList->a[ii].pExpr)!=aff ){ + return 1; + } + } + } + return 0; +} + #if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) /* ** This routine attempts to flatten subqueries as a performance optimization. @@ -128175,7 +153138,7 @@ static void substSelect( ** SELECT x+y AS a FROM t1 WHERE z<100 AND a>5 ** ** The code generated for this simplification gives the same result -** but only has to scan the data once. And because indices might +** but only has to scan the data once. And because indices might ** exist on the table t1, a complete scan of the data might be ** avoided. ** @@ -128193,15 +153156,18 @@ static void substSelect( ** from 2015-02-09.) ** ** (3) If the subquery is the right operand of a LEFT JOIN then -** (3a) the subquery may not be a join and -** (3b) the FROM clause of the subquery may not contain a virtual -** table and -** (3c) the outer query may not be an aggregate. +** (3a) the subquery may not be a join +** (**) Was (3b): "the FROM clause of the subquery may not contain +** a virtual table" +** (**) Was: "The outer query may not have a GROUP BY." This case +** is now managed correctly +** (3d) the outer query may not be DISTINCT. +** See also (26) for restrictions on RIGHT JOIN. ** ** (4) The subquery can not be DISTINCT. ** ** (**) At one point restrictions (4) and (5) defined a subset of DISTINCT -** sub-queries that were excluded from this optimization. Restriction +** sub-queries that were excluded from this optimization. Restriction ** (4) has since been expanded to exclude all DISTINCT subqueries. ** ** (**) We no longer attempt to flatten aggregate subqueries. Was: @@ -128217,8 +153183,8 @@ static void substSelect( ** (9) If the subquery uses LIMIT then the outer query may not be aggregate. ** ** (**) Restriction (10) was removed from the code on 2005-02-05 but we -** accidently carried the comment forward until 2014-09-15. Original -** constraint: "If the subquery is aggregate then the outer query +** accidentally carried the comment forward until 2014-09-15. Original +** constraint: "If the subquery is aggregate then the outer query ** may not use LIMIT." ** ** (11) The subquery and the outer query may not both have ORDER BY clauses. @@ -128236,7 +153202,7 @@ static void substSelect( ** ** (16) If the outer query is aggregate, then the subquery may not ** use ORDER BY. (Ticket #2942) This used to not matter -** until we introduced the group_concat() function. +** until we introduced the group_concat() function. ** ** (17) If the subquery is a compound select, then ** (17a) all compound operators must be a UNION ALL, and @@ -128245,8 +153211,14 @@ static void substSelect( ** (17c) every term within the subquery compound must have a FROM clause ** (17d) the outer query may not be ** (17d1) aggregate, or -** (17d2) DISTINCT, or -** (17d3) a join. +** (17d2) DISTINCT +** (17e) the subquery may not contain window functions, and +** (17f) the subquery must not be the RHS of a LEFT JOIN. +** (17g) either the subquery is the first element of the outer +** query or there are no RIGHT or FULL JOINs in any arm +** of the subquery. (This is a duplicate of condition (27b).) +** (17h) The corresponding result set expressions in all arms of the +** compound must have the same affinity. ** ** The parent and sub-query may contain WHERE clauses. Subject to ** rules (11), (13) and (14), they may also contain ORDER BY, @@ -128262,8 +153234,8 @@ static void substSelect( ** syntax error and return a detailed message. ** ** (18) If the sub-query is a compound select, then all terms of the -** ORDER BY clause of the parent must be simple references to -** columns of the sub-query. +** ORDER BY clause of the parent must be copies of a term returned +** by the parent query. ** ** (19) If the subquery uses LIMIT then the outer query may not ** have a WHERE clause. @@ -128279,14 +153251,13 @@ static void substSelect( ** ** (22) The subquery may not be a recursive CTE. ** -** (**) Subsumed into restriction (17d3). Was: If the outer query is -** a recursive CTE, then the sub-query may not be a compound query. -** This restriction is because transforming the +** (23) If the outer query is a recursive CTE, then the sub-query may not be +** a compound query. This restriction is because transforming the ** parent to a compound query confuses the code that handles ** recursive queries in multiSelect(). ** ** (**) We no longer attempt to flatten aggregate subqueries. Was: -** The subquery may not be an aggregate that uses the built-in min() or +** The subquery may not be an aggregate that uses the built-in min() or ** or max() functions. (Without this restriction, a query like: ** "SELECT x FROM (SELECT max(y), x FROM t1)" would not necessarily ** return the value X for which Y was maximal.) @@ -128295,6 +153266,18 @@ static void substSelect( ** function in the select list or ORDER BY clause, flattening ** is not attempted. ** +** (26) The subquery may not be the right operand of a RIGHT JOIN. +** See also (3) for restrictions on LEFT JOIN. +** +** (27) The subquery may not contain a FULL or RIGHT JOIN unless it +** is the first element of the parent query. Two subcases: +** (27a) the subquery is not a compound query. +** (27b) the subquery is a compound query and the RIGHT JOIN occurs +** in any arm of the compound query. (See also (17g).) +** +** (28) The subquery is not a MATERIALIZED CTE. (This is handled +** in the caller before ever reaching this routine.) +** ** ** In this routine, the "p" parameter is a pointer to the outer query. ** The subquery is p->pSrc->a[iFrom]. isAgg is true if the outer query @@ -128320,11 +153303,13 @@ static int flattenSubquery( SrcList *pSubSrc; /* The FROM clause of the subquery */ int iParent; /* VDBE cursor number of the pSub result set temp table */ int iNewParent = -1;/* Replacement table for iParent */ - int isLeftJoin = 0; /* True if pSub is the right side of a LEFT JOIN */ + int isOuterJoin = 0; /* True if pSub is the right side of a LEFT JOIN */ int i; /* Loop counter */ Expr *pWhere; /* The WHERE clause */ - struct SrcList_item *pSubitem; /* The subquery */ + SrcItem *pSubitem; /* The subquery */ sqlite3 *db = pParse->db; + Walker w; /* Walker to persist agginfo data */ + int *aCsrMap = 0; /* Check to see if flattening is permitted. Return 0 if not. */ @@ -128335,7 +153320,8 @@ static int flattenSubquery( assert( pSrc && iFrom>=0 && iFromnSrc ); pSubitem = &pSrc->a[iFrom]; iParent = pSubitem->iCursor; - pSub = pSubitem->pSelect; + assert( pSubitem->fg.isSubquery ); + pSub = pSubitem->u4.pSubq->pSelect; assert( pSub!=0 ); #ifndef SQLITE_OMIT_WINDOWFUNC @@ -128384,29 +153370,26 @@ static int flattenSubquery( ** ** which is not at all the same thing. ** - ** If the subquery is the right operand of a LEFT JOIN, then the outer - ** query cannot be an aggregate. (3c) This is an artifact of the way - ** aggregates are processed - there is no mechanism to determine if - ** the LEFT JOIN table should be all-NULL. - ** ** See also tickets #306, #350, and #3300. */ - if( (pSubitem->fg.jointype & JT_OUTER)!=0 ){ - isLeftJoin = 1; - if( pSubSrc->nSrc>1 || isAgg || IsVirtual(pSubSrc->a[0].pTab) ){ - /* (3a) (3c) (3b) */ + if( (pSubitem->fg.jointype & (JT_OUTER|JT_LTORJ))!=0 ){ + if( pSubSrc->nSrc>1 /* (3a) */ + /**** || IsVirtual(pSubSrc->a[0].pSTab) (3b)-omitted */ + || (p->selFlags & SF_Distinct)!=0 /* (3d) */ + || (pSubitem->fg.jointype & JT_RIGHT)!=0 /* (26) */ + ){ return 0; } + isOuterJoin = 1; } -#ifdef SQLITE_EXTRA_IFNULLROW - else if( iFrom>0 && !isAgg ){ - /* Setting isLeftJoin to -1 causes OP_IfNullRow opcodes to be generated for - ** every reference to any result column from subquery in a join, even - ** though they are not necessary. This will stress-test the OP_IfNullRow - ** opcode. */ - isLeftJoin = -1; + + assert( pSubSrc->nSrc>0 ); /* True by restriction (7) */ + if( iFrom>0 && (pSubSrc->a[0].fg.jointype & JT_LTORJ)!=0 ){ + return 0; /* Restriction (27a) */ } -#endif + + /* Condition (28) is blocked by the caller */ + assert( !pSubitem->fg.isCte || pSubitem->u2.pCteUse->eM10d!=M10d_Yes ); /* Restriction (17): If the sub-query is a compound SELECT, then it must ** use only the UNION ALL operator. And none of the simple select queries @@ -128414,45 +153397,60 @@ static int flattenSubquery( ** queries. */ if( pSub->pPrior ){ + int ii; if( pSub->pOrderBy ){ return 0; /* Restriction (20) */ } - if( isAgg || (p->selFlags & SF_Distinct)!=0 || pSrc->nSrc!=1 ){ - return 0; /* (17d1), (17d2), or (17d3) */ + if( isAgg || (p->selFlags & SF_Distinct)!=0 || isOuterJoin>0 ){ + return 0; /* (17d1), (17d2), or (17f) */ } for(pSub1=pSub; pSub1; pSub1=pSub1->pPrior){ testcase( (pSub1->selFlags & (SF_Distinct|SF_Aggregate))==SF_Distinct ); testcase( (pSub1->selFlags & (SF_Distinct|SF_Aggregate))==SF_Aggregate ); assert( pSub->pSrc!=0 ); + assert( (pSub->selFlags & SF_Recursive)==0 ); assert( pSub->pEList->nExpr==pSub1->pEList->nExpr ); if( (pSub1->selFlags & (SF_Distinct|SF_Aggregate))!=0 /* (17b) */ || (pSub1->pPrior && pSub1->op!=TK_ALL) /* (17a) */ || pSub1->pSrc->nSrc<1 /* (17c) */ +#ifndef SQLITE_OMIT_WINDOWFUNC + || pSub1->pWin /* (17e) */ +#endif ){ return 0; } + if( iFrom>0 && (pSub1->pSrc->a[0].fg.jointype & JT_LTORJ)!=0 ){ + /* Without this restriction, the JT_LTORJ flag would end up being + ** omitted on left-hand tables of the right join that is being + ** flattened. */ + return 0; /* Restrictions (17g), (27b) */ + } testcase( pSub1->pSrc->nSrc>1 ); } /* Restriction (18). */ if( p->pOrderBy ){ - int ii; for(ii=0; iipOrderBy->nExpr; ii++){ if( p->pOrderBy->a[ii].u.x.iOrderByCol==0 ) return 0; } } - } - /* Ex-restriction (23): - ** The only way that the recursive part of a CTE can contain a compound - ** subquery is for the subquery to be one term of a join. But if the - ** subquery is a join, then the flattening has already been stopped by - ** restriction (17d3) - */ - assert( (p->selFlags & SF_Recursive)==0 || pSub->pPrior==0 ); + /* Restriction (23) */ + if( (p->selFlags & SF_Recursive) ) return 0; + + /* Restriction (17h) */ + if( compoundHasDifferentAffinities(pSub) ) return 0; + + if( pSrc->nSrc>1 ){ + if( pParse->nSelect>500 ) return 0; + if( OptimizationDisabled(db, SQLITE_FlttnUnionAll) ) return 0; + aCsrMap = sqlite3DbMallocZero(db, ((i64)pParse->nTab+1)*sizeof(int)); + if( aCsrMap ) aCsrMap[0] = pParse->nTab; + } + } /***** If we reach this point, flattening is permitted. *****/ - SELECTTRACE(1,pParse,p,("flatten %u.%p from term %d\n", + TREETRACE(0x4,pParse,p,("flatten %u.%p from term %d\n", pSub->selId, pSub, iFrom)); /* Authorize the subquery */ @@ -128461,14 +153459,29 @@ static int flattenSubquery( testcase( i==SQLITE_DENY ); pParse->zAuthContext = zSavedAuthContext; + /* Delete the transient structures associated with the subquery */ + + if( ALWAYS(pSubitem->fg.isSubquery) ){ + pSub1 = sqlite3SubqueryDetach(db, pSubitem); + }else{ + pSub1 = 0; + } + assert( pSubitem->fg.isSubquery==0 ); + assert( pSubitem->fg.fixedSchema==0 ); + sqlite3DbFree(db, pSubitem->zName); + sqlite3DbFree(db, pSubitem->zAlias); + pSubitem->zName = 0; + pSubitem->zAlias = 0; + assert( pSubitem->fg.isUsing!=0 || pSubitem->u3.pOn==0 ); + /* If the sub-query is a compound SELECT statement, then (by restrictions - ** 17 and 18 above) it must be a UNION ALL and the parent query must + ** 17 and 18 above) it must be a UNION ALL and the parent query must ** be of the form: ** - ** SELECT FROM () + ** SELECT FROM () ** ** followed by any ORDER BY, LIMIT and/or OFFSET clauses. This block - ** creates N-1 copies of the parent query without any ORDER BY, LIMIT or + ** creates N-1 copies of the parent query without any ORDER BY, LIMIT or ** OFFSET clauses and joins them to the left-hand-side of the original ** using UNION ALL operators. In this case N is the number of simple ** select statements in the compound sub-query. @@ -128499,61 +153512,58 @@ static int flattenSubquery( ExprList *pOrderBy = p->pOrderBy; Expr *pLimit = p->pLimit; Select *pPrior = p->pPrior; + Table *pItemTab = pSubitem->pSTab; + pSubitem->pSTab = 0; p->pOrderBy = 0; - p->pSrc = 0; p->pPrior = 0; p->pLimit = 0; pNew = sqlite3SelectDup(db, p, 0); p->pLimit = pLimit; p->pOrderBy = pOrderBy; - p->pSrc = pSrc; p->op = TK_ALL; + pSubitem->pSTab = pItemTab; if( pNew==0 ){ p->pPrior = pPrior; }else{ + pNew->selId = ++pParse->nSelect; + if( aCsrMap && ALWAYS(db->mallocFailed==0) ){ + renumberCursors(pParse, pNew, iFrom, aCsrMap); + } pNew->pPrior = pPrior; if( pPrior ) pPrior->pNext = pNew; pNew->pNext = p; p->pPrior = pNew; - SELECTTRACE(2,pParse,p,("compound-subquery flattener" + TREETRACE(0x4,pParse,p,("compound-subquery flattener" " creates %u as peer\n",pNew->selId)); } - if( db->mallocFailed ) return 1; + assert( pSubitem->fg.isSubquery==0 ); + } + sqlite3DbFree(db, aCsrMap); + if( db->mallocFailed ){ + assert( pSubitem->fg.fixedSchema==0 ); + assert( pSubitem->fg.isSubquery==0 ); + assert( pSubitem->u4.zDatabase==0 ); + sqlite3SrcItemAttachSubquery(pParse, pSubitem, pSub1, 0); + return 1; } - - /* Begin flattening the iFrom-th entry of the FROM clause - ** in the outer query. - */ - pSub = pSub1 = pSubitem->pSelect; - - /* Delete the transient table structure associated with the - ** subquery - */ - sqlite3DbFree(db, pSubitem->zDatabase); - sqlite3DbFree(db, pSubitem->zName); - sqlite3DbFree(db, pSubitem->zAlias); - pSubitem->zDatabase = 0; - pSubitem->zName = 0; - pSubitem->zAlias = 0; - pSubitem->pSelect = 0; /* Defer deleting the Table object associated with the ** subquery until code generation is ** complete, since there may still exist Expr.pTab entries that ** refer to the subquery even after flattening. Ticket #3346. ** - ** pSubitem->pTab is always non-NULL by test restrictions and tests above. + ** pSubitem->pSTab is always non-NULL by test restrictions and tests above. */ - if( ALWAYS(pSubitem->pTab!=0) ){ - Table *pTabToDel = pSubitem->pTab; + if( ALWAYS(pSubitem->pSTab!=0) ){ + Table *pTabToDel = pSubitem->pSTab; if( pTabToDel->nTabRef==1 ){ Parse *pToplevel = sqlite3ParseToplevel(pParse); - pTabToDel->pNextZombie = pToplevel->pZombieTab; - pToplevel->pZombieTab = pTabToDel; + sqlite3ParserAddCleanup(pToplevel, sqlite3DeleteTableGeneric, pTabToDel); + testcase( pToplevel->earlyCleanup ); }else{ pTabToDel->nTabRef--; } - pSubitem->pTab = 0; + pSubitem->pSTab = 0; } /* The following loop runs once for each term in a compound-subquery @@ -128569,23 +153579,15 @@ static int flattenSubquery( ** those references with expressions that resolve to the subquery FROM ** elements we are now copying in. */ + pSub = pSub1; for(pParent=p; pParent; pParent=pParent->pPrior, pSub=pSub->pPrior){ int nSubSrc; - u8 jointype = 0; + u8 jointype = pSubitem->fg.jointype; + assert( pSub!=0 ); pSubSrc = pSub->pSrc; /* FROM clause of subquery */ nSubSrc = pSubSrc->nSrc; /* Number of terms in subquery FROM clause */ pSrc = pParent->pSrc; /* FROM clause of the outer query */ - if( pSrc ){ - assert( pParent==p ); /* First time through the loop */ - jointype = pSubitem->fg.jointype; - }else{ - assert( pParent!=p ); /* 2nd and subsequent times through the loop */ - pSrc = sqlite3SrcListAppend(pParse, 0, 0, 0); - if( pSrc==0 ) break; - pParent->pSrc = pSrc; - } - /* The subquery uses a single slot of the FROM clause of the outer ** query. If the subquery has more than one element in its FROM clause, ** then expand the outer query to make space for it to hold all elements @@ -128605,23 +153607,29 @@ static int flattenSubquery( pSrc = sqlite3SrcListEnlarge(pParse, pSrc, nSubSrc-1,iFrom+1); if( pSrc==0 ) break; pParent->pSrc = pSrc; + pSubitem = &pSrc->a[iFrom]; } /* Transfer the FROM clause terms from the subquery into the ** outer query. */ + iNewParent = pSubSrc->a[0].iCursor; for(i=0; ia[i+iFrom].pUsing); - assert( pSrc->a[i+iFrom].fg.isTabFunc==0 ); - pSrc->a[i+iFrom] = pSubSrc->a[i]; - iNewParent = pSubSrc->a[i].iCursor; + SrcItem *pItem = &pSrc->a[i+iFrom]; + assert( pItem->fg.isTabFunc==0 ); + assert( pItem->fg.isSubquery + || pItem->fg.fixedSchema + || pItem->u4.zDatabase==0 ); + if( pItem->fg.isUsing ) sqlite3IdListDelete(db, pItem->u3.pUsing); + *pItem = pSubSrc->a[i]; + pItem->fg.jointype |= (jointype & JT_LTORJ); memset(&pSubSrc->a[i], 0, sizeof(pSubSrc->a[i])); } - pSrc->a[iFrom].fg.jointype = jointype; - - /* Now begin substituting subquery result set expressions for + pSubitem->fg.jointype |= jointype; + + /* Begin substituting subquery result set expressions for ** references to the iParent in the outer query. - ** + ** ** Example: ** ** SELECT a+5, b*10 FROM (SELECT x*3 AS a, y+10 AS b FROM t1) WHERE a>b; @@ -128636,12 +153644,12 @@ static int flattenSubquery( ** ORDER BY column expression is identical to the iOrderByCol'th ** expression returned by SELECT statement pSub. Since these values ** do not necessarily correspond to columns in SELECT statement pParent, - ** zero them before transfering the ORDER BY clause. + ** zero them before transferring the ORDER BY clause. ** ** Not doing this may cause an error if a subsequent call to this - ** function attempts to flatten a compound sub-query into pParent - ** (the only way this can happen is if the compound sub-query is - ** currently part of pSub->pSrc). See ticket [d11a6e908f]. */ + ** function attempts to flatten a compound sub-query into pParent. + ** See ticket [d11a6e908f]. + */ ExprList *pOrderBy = pSub->pOrderBy; for(i=0; inExpr; i++){ pOrderBy->a[i].u.x.iOrderByCol = 0; @@ -128652,25 +153660,34 @@ static int flattenSubquery( } pWhere = pSub->pWhere; pSub->pWhere = 0; - if( isLeftJoin>0 ){ - setJoinExpr(pWhere, iNewParent); + if( isOuterJoin>0 ){ + assert( pSubSrc->nSrc==1 ); + sqlite3SetJoinExpr(pWhere, iNewParent, EP_OuterON); + } + if( pWhere ){ + if( pParent->pWhere ){ + pParent->pWhere = sqlite3PExpr(pParse, TK_AND, pWhere, pParent->pWhere); + }else{ + pParent->pWhere = pWhere; + } } - pParent->pWhere = sqlite3ExprAnd(db, pWhere, pParent->pWhere); if( db->mallocFailed==0 ){ SubstContext x; x.pParse = pParse; x.iTable = iParent; x.iNewTable = iNewParent; - x.isLeftJoin = isLeftJoin; + x.isOuterJoin = isOuterJoin; + x.nSelDepth = 0; x.pEList = pSub->pEList; + x.pCList = findLeftmostExprlist(pSub); substSelect(&x, pParent, 0); } - - /* The flattened query is distinct if either the inner or the - ** outer query is distinct. - */ - pParent->selFlags |= pSub->selFlags & SF_Distinct; - + + /* The flattened query is a compound if either the inner or the + ** outer query is a compound. */ + pParent->selFlags |= pSub->selFlags & SF_Compound; + assert( (pSub->selFlags & SF_Distinct)==0 ); /* restriction (17b) */ + /* ** SELECT ... FROM (SELECT ... LIMIT a OFFSET b) LIMIT x OFFSET y; ** @@ -128681,16 +153698,23 @@ static int flattenSubquery( pParent->pLimit = pSub->pLimit; pSub->pLimit = 0; } + + /* Recompute the SrcItem.colUsed masks for the flattened + ** tables. */ + for(i=0; ia[i+iFrom]); + } } - /* Finially, delete what is left of the subquery and return - ** success. + /* Finally, delete what is left of the subquery and return success. */ + sqlite3AggInfoPersistWalkerInit(&w, pParse); + sqlite3WalkSelect(&w,pSub1); sqlite3SelectDelete(db, pSub1); -#if SELECTTRACE_ENABLED - if( sqlite3SelectTrace & 0x100 ){ - SELECTTRACE(0x100,pParse,p,("After flattening:\n")); +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x4 ){ + TREETRACE(0x4,pParse,p,("After flattening:\n")); sqlite3TreeViewSelect(0, p, 0); } #endif @@ -128706,34 +153730,54 @@ static int flattenSubquery( typedef struct WhereConst WhereConst; struct WhereConst { Parse *pParse; /* Parsing context */ + u8 *pOomFault; /* Pointer to pParse->db->mallocFailed */ int nConst; /* Number for COLUMN=CONSTANT terms */ int nChng; /* Number of times a constant is propagated */ + int bHasAffBlob; /* At least one column in apExpr[] as affinity BLOB */ + u32 mExcludeOn; /* Which ON expressions to exclude from considertion. + ** Either EP_OuterON or EP_InnerON|EP_OuterON */ Expr **apExpr; /* [i*2] is COLUMN and [i*2+1] is VALUE */ }; /* ** Add a new entry to the pConst object. Except, do not add duplicate -** pColumn entires. +** pColumn entries. Also, do not add if doing so would not be appropriate. +** +** The caller guarantees the pColumn is a column and pValue is a constant. +** This routine has to do some additional checks before completing the +** insert. */ static void constInsert( - WhereConst *pConst, /* The WhereConst into which we are inserting */ - Expr *pColumn, /* The COLUMN part of the constraint */ - Expr *pValue /* The VALUE part of the constraint */ + WhereConst *pConst, /* The WhereConst into which we are inserting */ + Expr *pColumn, /* The COLUMN part of the constraint */ + Expr *pValue, /* The VALUE part of the constraint */ + Expr *pExpr /* Overall expression: COLUMN=VALUE or VALUE=COLUMN */ ){ int i; assert( pColumn->op==TK_COLUMN ); + assert( sqlite3ExprIsConstant(pConst->pParse, pValue) ); + + if( ExprHasProperty(pColumn, EP_FixedCol) ) return; + if( sqlite3ExprAffinity(pValue)!=0 ) return; + if( !sqlite3IsBinary(sqlite3ExprCompareCollSeq(pConst->pParse,pExpr)) ){ + return; + } /* 2018-10-25 ticket [cf5ed20f] ** Make sure the same pColumn is not inserted more than once */ for(i=0; inConst; i++){ - const Expr *pExpr = pConst->apExpr[i*2]; - assert( pExpr->op==TK_COLUMN ); - if( pExpr->iTable==pColumn->iTable - && pExpr->iColumn==pColumn->iColumn + const Expr *pE2 = pConst->apExpr[i*2]; + assert( pE2->op==TK_COLUMN ); + if( pE2->iTable==pColumn->iTable + && pE2->iColumn==pColumn->iColumn ){ return; /* Already present. Return without doing anything. */ } } + assert( SQLITE_AFF_NONEbHasAffBlob = 1; + } pConst->nConst++; pConst->apExpr = sqlite3DbReallocOrFree(pConst->pParse->db, pConst->apExpr, @@ -128741,7 +153785,6 @@ static void constInsert( if( pConst->apExpr==0 ){ pConst->nConst = 0; }else{ - if( ExprHasProperty(pValue, EP_FixedCol) ) pValue = pValue->pLeft; pConst->apExpr[pConst->nConst*2-2] = pColumn; pConst->apExpr[pConst->nConst*2-1] = pValue; } @@ -128755,8 +153798,12 @@ static void constInsert( */ static void findConstInWhere(WhereConst *pConst, Expr *pExpr){ Expr *pRight, *pLeft; - if( pExpr==0 ) return; - if( ExprHasProperty(pExpr, EP_FromJoin) ) return; + if( NEVER(pExpr==0) ) return; + if( ExprHasProperty(pExpr, pConst->mExcludeOn) ){ + testcase( ExprHasProperty(pExpr, EP_OuterON) ); + testcase( ExprHasProperty(pExpr, EP_InnerON) ); + return; + } if( pExpr->op==TK_AND ){ findConstInWhere(pConst, pExpr->pRight); findConstInWhere(pConst, pExpr->pLeft); @@ -128767,58 +153814,101 @@ static void findConstInWhere(WhereConst *pConst, Expr *pExpr){ pLeft = pExpr->pLeft; assert( pRight!=0 ); assert( pLeft!=0 ); - if( pRight->op==TK_COLUMN - && !ExprHasProperty(pRight, EP_FixedCol) - && sqlite3ExprIsConstant(pLeft) - && sqlite3IsBinary(sqlite3BinaryCompareCollSeq(pConst->pParse,pLeft,pRight)) - ){ - constInsert(pConst, pRight, pLeft); - }else - if( pLeft->op==TK_COLUMN - && !ExprHasProperty(pLeft, EP_FixedCol) - && sqlite3ExprIsConstant(pRight) - && sqlite3IsBinary(sqlite3BinaryCompareCollSeq(pConst->pParse,pLeft,pRight)) - ){ - constInsert(pConst, pLeft, pRight); + if( pRight->op==TK_COLUMN && sqlite3ExprIsConstant(pConst->pParse, pLeft) ){ + constInsert(pConst,pRight,pLeft,pExpr); + } + if( pLeft->op==TK_COLUMN && sqlite3ExprIsConstant(pConst->pParse, pRight) ){ + constInsert(pConst,pLeft,pRight,pExpr); } } /* -** This is a Walker expression callback. pExpr is a candidate expression -** to be replaced by a value. If pExpr is equivalent to one of the -** columns named in pWalker->u.pConst, then overwrite it with its -** corresponding value. +** This is a helper function for Walker callback propagateConstantExprRewrite(). +** +** Argument pExpr is a candidate expression to be replaced by a value. If +** pExpr is equivalent to one of the columns named in pWalker->u.pConst, +** then overwrite it with the corresponding value. Except, do not do so +** if argument bIgnoreAffBlob is non-zero and the affinity of pExpr +** is SQLITE_AFF_BLOB. */ -static int propagateConstantExprRewrite(Walker *pWalker, Expr *pExpr){ +static int propagateConstantExprRewriteOne( + WhereConst *pConst, + Expr *pExpr, + int bIgnoreAffBlob +){ int i; - WhereConst *pConst; + if( pConst->pOomFault[0] ) return WRC_Prune; if( pExpr->op!=TK_COLUMN ) return WRC_Continue; - if( ExprHasProperty(pExpr, EP_FixedCol) ) return WRC_Continue; - pConst = pWalker->u.pConst; + if( ExprHasProperty(pExpr, EP_FixedCol|pConst->mExcludeOn) ){ + testcase( ExprHasProperty(pExpr, EP_FixedCol) ); + testcase( ExprHasProperty(pExpr, EP_OuterON) ); + testcase( ExprHasProperty(pExpr, EP_InnerON) ); + return WRC_Continue; + } for(i=0; inConst; i++){ Expr *pColumn = pConst->apExpr[i*2]; if( pColumn==pExpr ) continue; if( pColumn->iTable!=pExpr->iTable ) continue; if( pColumn->iColumn!=pExpr->iColumn ) continue; + assert( SQLITE_AFF_NONEnChng++; ExprClearProperty(pExpr, EP_Leaf); ExprSetProperty(pExpr, EP_FixedCol); assert( pExpr->pLeft==0 ); pExpr->pLeft = sqlite3ExprDup(pConst->pParse->db, pConst->apExpr[i*2+1], 0); + if( pConst->pParse->db->mallocFailed ) return WRC_Prune; break; } return WRC_Prune; } +/* +** This is a Walker expression callback. pExpr is a node from the WHERE +** clause of a SELECT statement. This function examines pExpr to see if +** any substitutions based on the contents of pWalker->u.pConst should +** be made to pExpr or its immediate children. +** +** A substitution is made if: +** +** + pExpr is a column with an affinity other than BLOB that matches +** one of the columns in pWalker->u.pConst, or +** +** + pExpr is a binary comparison operator (=, <=, >=, <, >) that +** uses an affinity other than TEXT and one of its immediate +** children is a column that matches one of the columns in +** pWalker->u.pConst. +*/ +static int propagateConstantExprRewrite(Walker *pWalker, Expr *pExpr){ + WhereConst *pConst = pWalker->u.pConst; + assert( TK_GT==TK_EQ+1 ); + assert( TK_LE==TK_EQ+2 ); + assert( TK_LT==TK_EQ+3 ); + assert( TK_GE==TK_EQ+4 ); + if( pConst->bHasAffBlob ){ + if( (pExpr->op>=TK_EQ && pExpr->op<=TK_GE) + || pExpr->op==TK_IS + ){ + propagateConstantExprRewriteOne(pConst, pExpr->pLeft, 0); + if( pConst->pOomFault[0] ) return WRC_Prune; + if( sqlite3ExprAffinity(pExpr->pLeft)!=SQLITE_AFF_TEXT ){ + propagateConstantExprRewriteOne(pConst, pExpr->pRight, 0); + } + } + } + return propagateConstantExprRewriteOne(pConst, pExpr, pConst->bHasAffBlob); +} + /* ** The WHERE-clause constant propagation optimization. ** ** If the WHERE clause contains terms of the form COLUMN=CONSTANT or -** CONSTANT=COLUMN that must be tree (in other words, if the terms top-level -** AND-connected terms that are not part of a ON clause from a LEFT JOIN) -** then throughout the query replace all other occurrences of COLUMN -** with CONSTANT within the WHERE clause. +** CONSTANT=COLUMN that are top-level AND-connected terms that are not +** part of a ON clause from a LEFT JOIN, then throughout the query +** replace all other occurrences of COLUMN with CONSTANT. ** ** For example, the query: ** @@ -128839,7 +153929,7 @@ static int propagateConstantExprRewrite(Walker *pWalker, Expr *pExpr){ ** SELECT * FROM t1 WHERE a=123 AND b=123; ** ** The two SELECT statements above should return different answers. b=a -** is alway true because the comparison uses numeric affinity, but b=123 +** is always true because the comparison uses numeric affinity, but b=123 ** is false because it uses text affinity and '0123' is not the same as '123'. ** To work around this, the expression tree is not actually changed from ** "b=a" to "b=123" but rather the "a" in "b=a" is tagged with EP_FixedCol @@ -128847,6 +153937,21 @@ static int propagateConstantExprRewrite(Walker *pWalker, Expr *pExpr){ ** routines know to generate the constant "123" instead of looking up the ** column value. Also, to avoid collation problems, this optimization is ** only attempted if the "a=123" term uses the default BINARY collation. +** +** 2021-05-25 forum post 6a06202608: Another troublesome case is... +** +** CREATE TABLE t1(x); +** INSERT INTO t1 VALUES(10.0); +** SELECT 1 FROM t1 WHERE x=10 AND x LIKE 10; +** +** The query should return no rows, because the t1.x value is '10.0' not '10' +** and '10.0' is not LIKE '10'. But if we are not careful, the first WHERE +** term "x=10" will cause the second WHERE term to become "10 LIKE 10", +** resulting in a false positive. To avoid this, constant propagation for +** columns with BLOB affinity is only allowed if the constant is used with +** operators ==, <=, <, >=, >, or IS in a way that will cause the correct +** type conversions to occur. See logic associated with the bHasAffBlob flag +** for details. */ static int propagateConstants( Parse *pParse, /* The parsing context */ @@ -128856,10 +153961,23 @@ static int propagateConstants( Walker w; int nChng = 0; x.pParse = pParse; + x.pOomFault = &pParse->db->mallocFailed; do{ x.nConst = 0; x.nChng = 0; x.apExpr = 0; + x.bHasAffBlob = 0; + if( ALWAYS(p->pSrc!=0) + && p->pSrc->nSrc>0 + && (p->pSrc->a[0].fg.jointype & JT_LTORJ)!=0 + ){ + /* Do not propagate constants on any ON clause if there is a + ** RIGHT JOIN anywhere in the query */ + x.mExcludeOn = EP_InnerON | EP_OuterON; + }else{ + /* Do not propagate constants through the ON clause of a LEFT JOIN */ + x.mExcludeOn = EP_OuterON; + } findConstInWhere(&x, p->pWhere); if( x.nConst ){ memset(&w, 0, sizeof(w)); @@ -128873,10 +153991,39 @@ static int propagateConstants( sqlite3DbFree(x.pParse->db, x.apExpr); nChng += x.nChng; } - }while( x.nChng ); + }while( x.nChng ); return nChng; } +#if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) +# if !defined(SQLITE_OMIT_WINDOWFUNC) +/* +** This function is called to determine whether or not it is safe to +** push WHERE clause expression pExpr down to FROM clause sub-query +** pSubq, which contains at least one window function. Return 1 +** if it is safe and the expression should be pushed down, or 0 +** otherwise. +** +** It is only safe to push the expression down if it consists only +** of constants and copies of expressions that appear in the PARTITION +** BY clause of all window function used by the sub-query. It is safe +** to filter out entire partitions, but not rows within partitions, as +** this may change the results of the window functions. +** +** At the time this function is called it is guaranteed that +** +** * the sub-query uses only one distinct window frame, and +** * that the window frame has a PARTITION BY clause. +*/ +static int pushDownWindowCheck(Parse *pParse, Select *pSubq, Expr *pExpr){ + assert( pSubq->pWin->pPartition ); + assert( (pSubq->selFlags & SF_MultiPart)==0 ); + assert( pSubq->pPrior==0 ); + return sqlite3ExprIsConstantOrGroupBy(pParse, pExpr, pSubq->pWin->pPartition); +} +# endif /* SQLITE_OMIT_WINDOWFUNC */ +#endif /* !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) */ + #if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) /* ** Make copies of relevant WHERE clause terms of the outer query into @@ -128892,6 +154039,19 @@ static int propagateConstants( ** The hope is that the terms added to the inner query will make it more ** efficient. ** +** NAME AMBIGUITY +** +** This optimization is called the "WHERE-clause push-down optimization" +** or sometimes the "predicate push-down optimization". +** +** Do not confuse this optimization with another unrelated optimization +** with a similar name: The "MySQL push-down optimization" causes WHERE +** clause terms that can be evaluated using only the index and without +** reference to the table are run first, so that if they are false, +** unnecessary table seeks are avoided. +** +** RULES +** ** Do not attempt this optimization if: ** ** (1) (** This restriction was removed on 2017-09-29. We used to @@ -128924,9 +154084,51 @@ static int propagateConstants( ** But if the (b2=2) term were to be pushed down into the bb subquery, ** then the (1,1,NULL) row would be suppressed. ** -** (6) The inner query features one or more window-functions (since -** changes to the WHERE clause of the inner query could change the -** window over which window functions are calculated). +** (6) Window functions make things tricky as changes to the WHERE clause +** of the inner query could change the window over which window +** functions are calculated. Therefore, do not attempt the optimization +** if: +** +** (6a) The inner query uses multiple incompatible window partitions. +** +** (6b) The inner query is a compound and uses window-functions. +** +** (6c) The WHERE clause does not consist entirely of constants and +** copies of expressions found in the PARTITION BY clause of +** all window-functions used by the sub-query. It is safe to +** filter out entire partitions, as this does not change the +** window over which any window-function is calculated. +** +** (7) The inner query is a Common Table Expression (CTE) that should +** be materialized. (This restriction is implemented in the calling +** routine.) +** +** (8) If the subquery is a compound that uses UNION, INTERSECT, +** or EXCEPT, then all of the result set columns for all arms of +** the compound must use the BINARY collating sequence. +** +** (9) All three of the following are true: +** +** (9a) The WHERE clause expression originates in the ON or USING clause +** of a join (either an INNER or an OUTER join), and +** +** (9b) The subquery is to the right of the ON/USING clause +** +** (9c) There is a RIGHT JOIN (or FULL JOIN) in between the ON/USING +** clause and the subquery. +** +** Without this restriction, the WHERE-clause push-down optimization +** might move the ON/USING filter expression from the left side of a +** RIGHT JOIN over to the right side, which leads to incorrect answers. +** See also restriction (6) in sqlite3ExprIsSingleTableConstraint(). +** +** (10) The inner query is not the right-hand table of a RIGHT JOIN. +** +** (11) The subquery is not a VALUES clause +** +** (12) The WHERE clause is not "rowid ISNULL" or the equivalent. This +** case only comes up if SQLite is compiled using +** SQLITE_ALLOW_ROWID_IN_VIEW. ** ** Return 0 if no changes are made and non-zero if one or more WHERE clause ** terms are duplicated into the subquery. @@ -128935,17 +154137,56 @@ static int pushDownWhereTerms( Parse *pParse, /* Parse context (for malloc() and error reporting) */ Select *pSubq, /* The subquery whose WHERE clause is to be augmented */ Expr *pWhere, /* The WHERE clause of the outer query */ - int iCursor, /* Cursor number of the subquery */ - int isLeftJoin /* True if pSubq is the right term of a LEFT JOIN */ + SrcList *pSrcList, /* The complete from clause of the outer query */ + int iSrc /* Which FROM clause term to try to push into */ ){ Expr *pNew; + SrcItem *pSrc; /* The subquery FROM term into which WHERE is pushed */ int nChng = 0; + pSrc = &pSrcList->a[iSrc]; if( pWhere==0 ) return 0; - if( pSubq->selFlags & SF_Recursive ) return 0; /* restriction (2) */ + if( pSubq->selFlags & (SF_Recursive|SF_MultiPart) ){ + return 0; /* restrictions (2) and (11) */ + } + if( pSrc->fg.jointype & (JT_LTORJ|JT_RIGHT) ){ + return 0; /* restrictions (10) */ + } + if( pSubq->pPrior ){ + Select *pSel; + int notUnionAll = 0; + for(pSel=pSubq; pSel; pSel=pSel->pPrior){ + u8 op = pSel->op; + assert( op==TK_ALL || op==TK_SELECT + || op==TK_UNION || op==TK_INTERSECT || op==TK_EXCEPT ); + if( op!=TK_ALL && op!=TK_SELECT ){ + notUnionAll = 1; + } +#ifndef SQLITE_OMIT_WINDOWFUNC + if( pSel->pWin ) return 0; /* restriction (6b) */ +#endif + } + if( notUnionAll ){ + /* If any of the compound arms are connected using UNION, INTERSECT, + ** or EXCEPT, then we must ensure that none of the columns use a + ** non-BINARY collating sequence. */ + for(pSel=pSubq; pSel; pSel=pSel->pPrior){ + int ii; + const ExprList *pList = pSel->pEList; + assert( pList!=0 ); + for(ii=0; iinExpr; ii++){ + CollSeq *pColl = sqlite3ExprCollSeq(pParse, pList->a[ii].pExpr); + if( !sqlite3IsBinary(pColl) ){ + return 0; /* Restriction (8) */ + } + } + } + } + }else{ #ifndef SQLITE_OMIT_WINDOWFUNC - if( pSubq->pWin ) return 0; /* restriction (6) */ + if( pSubq->pWin && pSubq->pWin->pPartition==0 ) return 0; #endif + } #ifdef SQLITE_DEBUG /* Only the first term of a compound can have a WITH clause. But make @@ -128953,7 +154194,7 @@ static int pushDownWhereTerms( ** in the future. */ { - Select *pX; + Select *pX; for(pX=pSubq; pX; pX=pX->pPrior){ assert( (pX->selFlags & (SF_Recursive))==0 ); } @@ -128964,35 +154205,90 @@ static int pushDownWhereTerms( return 0; /* restriction (3) */ } while( pWhere->op==TK_AND ){ - nChng += pushDownWhereTerms(pParse, pSubq, pWhere->pRight, - iCursor, isLeftJoin); + nChng += pushDownWhereTerms(pParse, pSubq, pWhere->pRight, pSrcList, iSrc); pWhere = pWhere->pLeft; } + +#if 0 /* These checks now done by sqlite3ExprIsSingleTableConstraint() */ + if( ExprHasProperty(pWhere, EP_OuterON|EP_InnerON) /* (9a) */ + && (pSrcList->a[0].fg.jointype & JT_LTORJ)!=0 /* Fast pre-test of (9c) */ + ){ + int jj; + for(jj=0; jjw.iJoin==pSrcList->a[jj].iCursor ){ + /* If we reach this point, both (9a) and (9b) are satisfied. + ** The following loop checks (9c): + */ + for(jj++; jja[jj].fg.jointype & JT_RIGHT)!=0 ){ + return 0; /* restriction (9) */ + } + } + } + } + } if( isLeftJoin - && (ExprHasProperty(pWhere,EP_FromJoin)==0 - || pWhere->iRightJoinTable!=iCursor) + && (ExprHasProperty(pWhere,EP_OuterON)==0 + || pWhere->w.iJoin!=iCursor) ){ return 0; /* restriction (4) */ } - if( ExprHasProperty(pWhere,EP_FromJoin) && pWhere->iRightJoinTable!=iCursor ){ + if( ExprHasProperty(pWhere,EP_OuterON) + && pWhere->w.iJoin!=iCursor + ){ return 0; /* restriction (5) */ } - if( sqlite3ExprIsTableConstant(pWhere, iCursor) ){ +#endif + +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + if( ViewCanHaveRowid && (pWhere->op==TK_ISNULL || pWhere->op==TK_NOTNULL) ){ + Expr *pLeft = pWhere->pLeft; + if( ALWAYS(pLeft) + && pLeft->op==TK_COLUMN + && pLeft->iColumn < 0 + ){ + return 0; /* Restriction (12) */ + } + } +#endif + + if( sqlite3ExprIsSingleTableConstraint(pWhere, pSrcList, iSrc, 1) ){ nChng++; + pSubq->selFlags |= SF_PushDown; while( pSubq ){ SubstContext x; pNew = sqlite3ExprDup(pParse->db, pWhere, 0); - unsetJoinExpr(pNew, -1); + unsetJoinExpr(pNew, -1, 1); x.pParse = pParse; - x.iTable = iCursor; - x.iNewTable = iCursor; - x.isLeftJoin = 0; + x.iTable = pSrc->iCursor; + x.iNewTable = pSrc->iCursor; + x.isOuterJoin = 0; + x.nSelDepth = 0; x.pEList = pSubq->pEList; + x.pCList = findLeftmostExprlist(pSubq); pNew = substExpr(&x, pNew); + assert( pNew!=0 || pParse->nErr!=0 ); + if( pParse->nErr==0 && pNew->op==TK_IN && ExprUseXSelect(pNew) ){ + assert( pNew->x.pSelect!=0 ); + pNew->x.pSelect->selFlags |= SF_ClonedRhsIn; + assert( pWhere!=0 ); + assert( pWhere->op==TK_IN ); + assert( ExprUseXSelect(pWhere) ); + assert( pWhere->x.pSelect!=0 ); + pWhere->x.pSelect->selFlags |= SF_ClonedRhsIn; + } +#ifndef SQLITE_OMIT_WINDOWFUNC + if( pSubq->pWin && 0==pushDownWindowCheck(pParse, pSubq, pNew) ){ + /* Restriction 6c has prevented push-down in this case */ + sqlite3ExprDelete(pParse->db, pNew); + nChng--; + break; + } +#endif if( pSubq->selFlags & SF_Aggregate ){ - pSubq->pHaving = sqlite3ExprAnd(pParse->db, pSubq->pHaving, pNew); + pSubq->pHaving = sqlite3ExprAnd(pParse, pSubq->pHaving, pNew); }else{ - pSubq->pWhere = sqlite3ExprAnd(pParse->db, pSubq->pWhere, pNew); + pSubq->pWhere = sqlite3ExprAnd(pParse, pSubq->pWhere, pNew); } pSubq = pSubq->pPrior; } @@ -129001,9 +154297,81 @@ static int pushDownWhereTerms( } #endif /* !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) */ +/* +** Check to see if a subquery contains result-set columns that are +** never used. If it does, change the value of those result-set columns +** to NULL so that they do not cause unnecessary work to compute. +** +** Return the number of column that were changed to NULL. +*/ +static int disableUnusedSubqueryResultColumns(SrcItem *pItem){ + int nCol; + Select *pSub; /* The subquery to be simplified */ + Select *pX; /* For looping over compound elements of pSub */ + Table *pTab; /* The table that describes the subquery */ + int j; /* Column number */ + int nChng = 0; /* Number of columns converted to NULL */ + Bitmask colUsed; /* Columns that may not be NULLed out */ + + assert( pItem!=0 ); + if( pItem->fg.isCorrelated || pItem->fg.isCte ){ + return 0; + } + assert( pItem->pSTab!=0 ); + pTab = pItem->pSTab; + assert( pItem->fg.isSubquery ); + pSub = pItem->u4.pSubq->pSelect; + assert( pSub->pEList->nExpr==pTab->nCol ); + for(pX=pSub; pX; pX=pX->pPrior){ + if( (pX->selFlags & (SF_Distinct|SF_Aggregate))!=0 ){ + testcase( pX->selFlags & SF_Distinct ); + testcase( pX->selFlags & SF_Aggregate ); + return 0; + } + if( pX->pPrior && pX->op!=TK_ALL ){ + /* This optimization does not work for compound subqueries that + ** use UNION, INTERSECT, or EXCEPT. Only UNION ALL is allowed. */ + return 0; + } +#ifndef SQLITE_OMIT_WINDOWFUNC + if( pX->pWin ){ + /* This optimization does not work for subqueries that use window + ** functions. */ + return 0; + } +#endif + } + colUsed = pItem->colUsed; + if( pSub->pOrderBy ){ + ExprList *pList = pSub->pOrderBy; + for(j=0; jnExpr; j++){ + u16 iCol = pList->a[j].u.x.iOrderByCol; + if( iCol>0 ){ + iCol--; + colUsed |= ((Bitmask)1)<<(iCol>=BMS ? BMS-1 : iCol); + } + } + } + nCol = pTab->nCol; + for(j=0; jpPrior) { + Expr *pY = pX->pEList->a[j].pExpr; + if( pY->op==TK_NULL ) continue; + pY->op = TK_NULL; + ExprClearProperty(pY, EP_Skip|EP_Unlikely); + pX->selFlags |= SF_PushDown; + nChng++; + } + } + return nChng; +} + + /* ** The pFunc is the only aggregate function in the query. Check to see -** if the query is a candidate for the min/max optimization. +** if the query is a candidate for the min/max optimization. ** ** If the query is a candidate for the min/max optimization, then set ** *ppMinMax to be an ORDER BY clause to be used for the optimization @@ -129019,40 +154387,58 @@ static int pushDownWhereTerms( */ static u8 minMaxQuery(sqlite3 *db, Expr *pFunc, ExprList **ppMinMax){ int eRet = WHERE_ORDERBY_NORMAL; /* Return value */ - ExprList *pEList = pFunc->x.pList; /* Arguments to agg function */ + ExprList *pEList; /* Arguments to agg function */ const char *zFunc; /* Name of aggregate function pFunc */ ExprList *pOrderBy; - u8 sortOrder; + u8 sortFlags = 0; assert( *ppMinMax==0 ); assert( pFunc->op==TK_AGG_FUNCTION ); - if( pEList==0 || pEList->nExpr!=1 ) return eRet; + assert( !IsWindowFunc(pFunc) ); + assert( ExprUseXList(pFunc) ); + pEList = pFunc->x.pList; + if( pEList==0 + || pEList->nExpr!=1 + || ExprHasProperty(pFunc, EP_WinFunc) + || OptimizationDisabled(db, SQLITE_MinMaxOpt) + ){ + return eRet; + } + assert( !ExprHasProperty(pFunc, EP_IntValue) ); zFunc = pFunc->u.zToken; if( sqlite3StrICmp(zFunc, "min")==0 ){ eRet = WHERE_ORDERBY_MIN; - sortOrder = SQLITE_SO_ASC; + if( sqlite3ExprCanBeNull(pEList->a[0].pExpr) ){ + sortFlags = KEYINFO_ORDER_BIGNULL; + } }else if( sqlite3StrICmp(zFunc, "max")==0 ){ eRet = WHERE_ORDERBY_MAX; - sortOrder = SQLITE_SO_DESC; + sortFlags = KEYINFO_ORDER_DESC; }else{ return eRet; } *ppMinMax = pOrderBy = sqlite3ExprListDup(db, pEList, 0); assert( pOrderBy!=0 || db->mallocFailed ); - if( pOrderBy ) pOrderBy->a[0].sortOrder = sortOrder; + if( pOrderBy ) pOrderBy->a[0].fg.sortFlags = sortFlags; return eRet; } /* ** The select statement passed as the first argument is an aggregate query. -** The second argument is the associated aggregate-info object. This +** The second argument is the associated aggregate-info object. This ** function tests if the SELECT is of the form: ** ** SELECT count(*) FROM ** ** where table is a database table, not a sub-select or view. If the query ** does match this pattern, then a pointer to the Table object representing -** is returned. Otherwise, 0 is returned. +** is returned. Otherwise, NULL is returned. +** +** This routine checks to see if it is safe to use the count optimization. +** A correct answer is still obtained (though perhaps more slowly) if +** this routine returns NULL when it could have returned a table pointer. +** But returning the pointer when NULL should have been returned can +** result in incorrect answers and/or crashes. So, when in doubt, return NULL. */ static Table *isSimpleCount(Select *p, AggInfo *pAggInfo){ Table *pTab; @@ -129060,20 +154446,28 @@ static Table *isSimpleCount(Select *p, AggInfo *pAggInfo){ assert( !p->pGroupBy ); - if( p->pWhere || p->pEList->nExpr!=1 - || p->pSrc->nSrc!=1 || p->pSrc->a[0].pSelect + if( p->pWhere + || p->pEList->nExpr!=1 + || p->pSrc->nSrc!=1 + || p->pSrc->a[0].fg.isSubquery + || pAggInfo->nFunc!=1 + || p->pHaving ){ return 0; } - pTab = p->pSrc->a[0].pTab; + pTab = p->pSrc->a[0].pSTab; + assert( pTab!=0 ); + assert( !IsView(pTab) ); + if( !IsOrdinaryTable(pTab) ) return 0; pExpr = p->pEList->a[0].pExpr; - assert( pTab && !pTab->pSelect && pExpr ); - - if( IsVirtual(pTab) ) return 0; + assert( pExpr!=0 ); if( pExpr->op!=TK_AGG_FUNCTION ) return 0; - if( NEVER(pAggInfo->nFunc==0) ) return 0; + if( pExpr->pAggInfo!=pAggInfo ) return 0; if( (pAggInfo->aFunc[0].pFunc->funcFlags&SQLITE_FUNC_COUNT)==0 ) return 0; - if( pExpr->flags&EP_Distinct ) return 0; + assert( pAggInfo->aFunc[0].pFExpr==pExpr ); + testcase( ExprHasProperty(pExpr, EP_Distinct) ); + testcase( ExprHasProperty(pExpr, EP_WinFunc) ); + if( ExprHasProperty(pExpr, EP_Distinct|EP_WinFunc) ) return 0; return pTab; } @@ -129081,30 +154475,33 @@ static Table *isSimpleCount(Select *p, AggInfo *pAggInfo){ /* ** If the source-list item passed as an argument was augmented with an ** INDEXED BY clause, then try to locate the specified index. If there -** was such a clause and the named index cannot be found, return -** SQLITE_ERROR and leave an error in pParse. Otherwise, populate +** was such a clause and the named index cannot be found, return +** SQLITE_ERROR and leave an error in pParse. Otherwise, populate ** pFrom->pIndex and return SQLITE_OK. */ -SQLITE_PRIVATE int sqlite3IndexedByLookup(Parse *pParse, struct SrcList_item *pFrom){ - if( pFrom->pTab && pFrom->fg.isIndexedBy ){ - Table *pTab = pFrom->pTab; - char *zIndexedBy = pFrom->u1.zIndexedBy; - Index *pIdx; - for(pIdx=pTab->pIndex; - pIdx && sqlite3StrICmp(pIdx->zName, zIndexedBy); - pIdx=pIdx->pNext - ); - if( !pIdx ){ - sqlite3ErrorMsg(pParse, "no such index: %s", zIndexedBy, 0); - pParse->checkSchema = 1; - return SQLITE_ERROR; - } - pFrom->pIBIndex = pIdx; +SQLITE_PRIVATE int sqlite3IndexedByLookup(Parse *pParse, SrcItem *pFrom){ + Table *pTab = pFrom->pSTab; + char *zIndexedBy = pFrom->u1.zIndexedBy; + Index *pIdx; + assert( pTab!=0 ); + assert( pFrom->fg.isIndexedBy!=0 ); + + for(pIdx=pTab->pIndex; + pIdx && sqlite3StrICmp(pIdx->zName, zIndexedBy); + pIdx=pIdx->pNext + ); + if( !pIdx ){ + sqlite3ErrorMsg(pParse, "no such index: %s", zIndexedBy, 0); + pParse->checkSchema = 1; + return SQLITE_ERROR; } + assert( pFrom->fg.isCte==0 ); + pFrom->u2.pIBIndex = pIdx; return SQLITE_OK; } + /* -** Detect compound SELECT statements that use an ORDER BY clause with +** Detect compound SELECT statements that use an ORDER BY clause with ** an alternative collating sequence. ** ** SELECT ... FROM t1 EXCEPT SELECT ... FROM t2 ORDER BY .. COLLATE ... @@ -129114,14 +154511,14 @@ SQLITE_PRIVATE int sqlite3IndexedByLookup(Parse *pParse, struct SrcList_item *pF ** SELECT * FROM (SELECT ... FROM t1 EXCEPT SELECT ... FROM t2) ** ORDER BY ... COLLATE ... ** -** This transformation is necessary because the multiSelectOrderBy() routine +** This transformation is necessary because the multiSelectByMerge() routine ** above that generates the code for a compound SELECT with an ORDER BY clause ** uses a merge algorithm that requires the same collating sequence on the ** result columns as on the ORDER BY clause. See ticket -** http://www.sqlite.org/src/info/6709574d2a +** http://sqlite.org/src/info/6709574d2a ** ** This transformation is only needed for EXCEPT, INTERSECT, and UNION. -** The UNION ALL operator works fine with multiSelectOrderBy() even when +** The UNION ALL operator works fine with multiSelectByMerge() even when ** there are COLLATE terms in the ORDER BY. */ static int convertCompoundSelectToSubquery(Walker *pWalker, Select *p){ @@ -129139,6 +154536,14 @@ static int convertCompoundSelectToSubquery(Walker *pWalker, Select *p){ for(pX=p; pX && (pX->op==TK_ALL || pX->op==TK_SELECT); pX=pX->pPrior){} if( pX==0 ) return WRC_Continue; a = p->pOrderBy->a; +#ifndef SQLITE_OMIT_WINDOWFUNC + /* If iOrderByCol is already non-zero, then it has already been matched + ** to a result column of the SELECT statement. This occurs when the + ** SELECT is rewritten for window-functions processing and then passed + ** to sqlite3SelectPrep() and similar a second time. The rewriting done + ** by this function is not required in this case. */ + if( a[0].u.x.iOrderByCol ) return WRC_Continue; +#endif for(i=p->pOrderBy->nExpr-1; i>=0; i--){ if( a[i].pExpr->flags & EP_Collate ) break; } @@ -129151,8 +154556,12 @@ static int convertCompoundSelectToSubquery(Walker *pWalker, Select *p){ pNew = sqlite3DbMallocZero(db, sizeof(*pNew) ); if( pNew==0 ) return WRC_Abort; memset(&dummy, 0, sizeof(dummy)); - pNewSrc = sqlite3SrcListAppendFromTerm(pParse,0,0,0,&dummy,pNew,0,0); - if( pNewSrc==0 ) return WRC_Abort; + pNewSrc = sqlite3SrcListAppendFromTerm(pParse,0,0,0,&dummy,pNew,0); + assert( pNewSrc!=0 || pParse->nErr ); + if( pParse->nErr ){ + sqlite3SrcListDelete(db, pNewSrc); + return WRC_Abort; + } *pNew = *p; p->pSrc = pNewSrc; p->pEList = sqlite3ExprListAppend(pParse, 0, sqlite3Expr(db, TK_ASTERISK, 0)); @@ -129164,7 +154573,10 @@ static int convertCompoundSelectToSubquery(Walker *pWalker, Select *p){ p->pPrior = 0; p->pNext = 0; p->pWith = 0; - p->selFlags &= ~SF_Compound; +#ifndef SQLITE_OMIT_WINDOWFUNC + p->pWinDefn = 0; +#endif + p->selFlags &= ~(u32)SF_Compound; assert( (p->selFlags & SF_Converted)==0 ); p->selFlags |= SF_Converted; assert( pNew->pPrior!=0 ); @@ -129178,7 +154590,7 @@ static int convertCompoundSelectToSubquery(Walker *pWalker, Select *p){ ** arguments. If it does, leave an error message in pParse and return ** non-zero, since pFrom is not allowed to be a table-valued function. */ -static int cannotBeFunction(Parse *pParse, struct SrcList_item *pFrom){ +static int cannotBeFunction(Parse *pParse, SrcItem *pFrom){ if( pFrom->fg.isTabFunc ){ sqlite3ErrorMsg(pParse, "'%s' is not a function", pFrom->zName); return 1; @@ -129188,9 +154600,9 @@ static int cannotBeFunction(Parse *pParse, struct SrcList_item *pFrom){ #ifndef SQLITE_OMIT_CTE /* -** Argument pWith (which may be NULL) points to a linked list of nested -** WITH contexts, from inner to outermost. If the table identified by -** FROM clause element pItem is really a common-table-expression (CTE) +** Argument pWith (which may be NULL) points to a linked list of nested +** WITH contexts, from inner to outermost. If the table identified by +** FROM clause element pItem is really a common-table-expression (CTE) ** then return a pointer to the CTE definition for that table. Otherwise ** return NULL. ** @@ -129199,21 +154611,22 @@ static int cannotBeFunction(Parse *pParse, struct SrcList_item *pFrom){ */ static struct Cte *searchWith( With *pWith, /* Current innermost WITH clause */ - struct SrcList_item *pItem, /* FROM clause element to resolve */ + SrcItem *pItem, /* FROM clause element to resolve */ With **ppContext /* OUT: WITH clause return value belongs to */ ){ - const char *zName; - if( pItem->zDatabase==0 && (zName = pItem->zName)!=0 ){ - With *p; - for(p=pWith; p; p=p->pOuter){ - int i; - for(i=0; inCte; i++){ - if( sqlite3StrICmp(zName, p->a[i].zName)==0 ){ - *ppContext = p; - return &p->a[i]; - } + const char *zName = pItem->zName; + With *p; + assert( pItem->fg.fixedSchema || pItem->u4.zDatabase==0 ); + assert( zName!=0 ); + for(p=pWith; p; p=p->pOuter){ + int i; + for(i=0; inCte; i++){ + if( sqlite3StrICmp(zName, p->a[i].zName)==0 ){ + *ppContext = p; + return &p->a[i]; } } + if( p->bView ) break; } return 0; } @@ -129223,55 +154636,92 @@ static struct Cte *searchWith( ** ** This routine pushes the WITH clause passed as the second argument ** onto the top of the stack. If argument bFree is true, then this -** WITH clause will never be popped from the stack. In this case it -** should be freed along with the Parse object. In other cases, when -** bFree==0, the With object will be freed along with the SELECT +** WITH clause will never be popped from the stack but should instead +** be freed along with the Parse object. In other cases, when +** bFree==0, the With object will be freed along with the SELECT ** statement with which it is associated. +** +** This routine returns a copy of pWith. Or, if bFree is true and +** the pWith object is destroyed immediately due to an OOM condition, +** then this routine return NULL. +** +** If bFree is true, do not continue to use the pWith pointer after +** calling this routine, Instead, use only the return value. */ -SQLITE_PRIVATE void sqlite3WithPush(Parse *pParse, With *pWith, u8 bFree){ - assert( bFree==0 || (pParse->pWith==0 && pParse->pWithToFree==0) ); +SQLITE_PRIVATE With *sqlite3WithPush(Parse *pParse, With *pWith, u8 bFree){ if( pWith ){ - assert( pParse->pWith!=pWith ); - pWith->pOuter = pParse->pWith; - pParse->pWith = pWith; - if( bFree ) pParse->pWithToFree = pWith; + if( bFree ){ + pWith = (With*)sqlite3ParserAddCleanup(pParse, sqlite3WithDeleteGeneric, + pWith); + if( pWith==0 ) return 0; + } + if( pParse->nErr==0 ){ + assert( pParse->pWith!=pWith ); + pWith->pOuter = pParse->pWith; + pParse->pWith = pWith; + } } + return pWith; } /* -** This function checks if argument pFrom refers to a CTE declared by -** a WITH clause on the stack currently maintained by the parser. And, -** if currently processing a CTE expression, if it is a recursive -** reference to the current CTE. +** This function checks if argument pFrom refers to a CTE declared by +** a WITH clause on the stack currently maintained by the parser (on the +** pParse->pWith linked list). And if currently processing a CTE +** CTE expression, through routine checks to see if the reference is +** a recursive reference to the CTE. ** -** If pFrom falls into either of the two categories above, pFrom->pTab -** and other fields are populated accordingly. The caller should check -** (pFrom->pTab!=0) to determine whether or not a successful match -** was found. +** If pFrom matches a CTE according to either of these two above, pFrom->pSTab +** and other fields are populated accordingly. ** -** Whether or not a match is found, SQLITE_OK is returned if no error -** occurs. If an error does occur, an error message is stored in the -** parser and some error code other than SQLITE_OK returned. +** Return 0 if no match is found. +** Return 1 if a match is found. +** Return 2 if an error condition is detected. */ -static int withExpand( - Walker *pWalker, - struct SrcList_item *pFrom +static int resolveFromTermToCte( + Parse *pParse, /* The parsing context */ + Walker *pWalker, /* Current tree walker */ + SrcItem *pFrom /* The FROM clause term to check */ ){ - Parse *pParse = pWalker->pParse; - sqlite3 *db = pParse->db; - struct Cte *pCte; /* Matched CTE (or NULL if no match) */ - With *pWith; /* WITH clause that pCte belongs to */ - - assert( pFrom->pTab==0 ); + Cte *pCte; /* Matched CTE (or NULL if no match) */ + With *pWith; /* The matching WITH */ + assert( pFrom->pSTab==0 ); + if( pParse->pWith==0 ){ + /* There are no WITH clauses in the stack. No match is possible */ + return 0; + } + if( pParse->nErr ){ + /* Prior errors might have left pParse->pWith in a goofy state, so + ** go no further. */ + return 0; + } + assert( pFrom->fg.hadSchema==0 || pFrom->fg.notCte!=0 ); + if( pFrom->fg.fixedSchema==0 && pFrom->u4.zDatabase!=0 ){ + /* The FROM term contains a schema qualifier (ex: main.t1) and so + ** it cannot possibly be a CTE reference. */ + return 0; + } + if( pFrom->fg.notCte ){ + /* The FROM term is specifically excluded from matching a CTE. + ** (1) It is part of a trigger that used to have zDatabase but had + ** zDatabase removed by sqlite3FixTriggerStep(). + ** (2) This is the first term in the FROM clause of an UPDATE. + */ + return 0; + } pCte = searchWith(pParse->pWith, pFrom, &pWith); if( pCte ){ + sqlite3 *db = pParse->db; Table *pTab; ExprList *pEList; Select *pSel; Select *pLeft; /* Left-most SELECT statement */ + Select *pRecTerm; /* Left-most recursive term */ int bMayRecursive; /* True if compound joined by UNION [ALL] */ With *pSavedWith; /* Initial value of pParse->pWith */ + int iRecTab = -1; /* Cursor for recursive table */ + CteUse *pCteUse; /* If pCte->zCteErr is non-NULL at this point, then this is an illegal ** recursive reference to CTE pCte. Leave an error in pParse and return @@ -129279,63 +154729,100 @@ static int withExpand( ** In this case, proceed. */ if( pCte->zCteErr ){ sqlite3ErrorMsg(pParse, pCte->zCteErr, pCte->zName); - return SQLITE_ERROR; + return 2; } - if( cannotBeFunction(pParse, pFrom) ) return SQLITE_ERROR; + if( cannotBeFunction(pParse, pFrom) ) return 2; - assert( pFrom->pTab==0 ); - pFrom->pTab = pTab = sqlite3DbMallocZero(db, sizeof(Table)); - if( pTab==0 ) return WRC_Abort; + assert( pFrom->pSTab==0 ); + pTab = sqlite3DbMallocZero(db, sizeof(Table)); + if( pTab==0 ) return 2; + pCteUse = pCte->pUse; + if( pCteUse==0 ){ + pCte->pUse = pCteUse = sqlite3DbMallocZero(db, sizeof(pCteUse[0])); + if( pCteUse==0 + || sqlite3ParserAddCleanup(pParse,sqlite3DbFree,pCteUse)==0 + ){ + sqlite3DbFree(db, pTab); + return 2; + } + pCteUse->eM10d = pCte->eM10d; + } + pFrom->pSTab = pTab; pTab->nTabRef = 1; pTab->zName = sqlite3DbStrDup(db, pCte->zName); pTab->iPKey = -1; pTab->nRowLogEst = 200; assert( 200==sqlite3LogEst(1048576) ); pTab->tabFlags |= TF_Ephemeral | TF_NoVisibleRowid; - pFrom->pSelect = sqlite3SelectDup(db, pCte->pSelect, 0); - if( db->mallocFailed ) return SQLITE_NOMEM_BKPT; - assert( pFrom->pSelect ); + sqlite3SrcItemAttachSubquery(pParse, pFrom, pCte->pSelect, 1); + if( db->mallocFailed ) return 2; + assert( pFrom->fg.isSubquery && pFrom->u4.pSubq ); + pSel = pFrom->u4.pSubq->pSelect; + assert( pSel!=0 ); + pSel->selFlags |= SF_CopyCte; + if( pFrom->fg.isIndexedBy ){ + sqlite3ErrorMsg(pParse, "no such index: \"%s\"", pFrom->u1.zIndexedBy); + return 2; + } + assert( !pFrom->fg.isIndexedBy ); + pFrom->fg.isCte = 1; + pFrom->u2.pCteUse = pCteUse; + pCteUse->nUse++; /* Check if this is a recursive CTE. */ - pSel = pFrom->pSelect; + pRecTerm = pSel; bMayRecursive = ( pSel->op==TK_ALL || pSel->op==TK_UNION ); - if( bMayRecursive ){ + while( bMayRecursive && pRecTerm->op==pSel->op ){ int i; - SrcList *pSrc = pFrom->pSelect->pSrc; + SrcList *pSrc = pRecTerm->pSrc; + assert( pRecTerm->pPrior!=0 ); for(i=0; inSrc; i++){ - struct SrcList_item *pItem = &pSrc->a[i]; - if( pItem->zDatabase==0 - && pItem->zName!=0 + SrcItem *pItem = &pSrc->a[i]; + if( pItem->zName!=0 + && !pItem->fg.hadSchema + && ALWAYS( !pItem->fg.isSubquery ) + && (pItem->fg.fixedSchema || pItem->u4.zDatabase==0) && 0==sqlite3StrICmp(pItem->zName, pCte->zName) - ){ - pItem->pTab = pTab; - pItem->fg.isRecursive = 1; + ){ + pItem->pSTab = pTab; pTab->nTabRef++; - pSel->selFlags |= SF_Recursive; + pItem->fg.isRecursive = 1; + if( pRecTerm->selFlags & SF_Recursive ){ + sqlite3ErrorMsg(pParse, + "multiple references to recursive table: %s", pCte->zName + ); + return 2; + } + pRecTerm->selFlags |= SF_Recursive; + if( iRecTab<0 ) iRecTab = pParse->nTab++; + pItem->iCursor = iRecTab; } } + if( (pRecTerm->selFlags & SF_Recursive)==0 ) break; + pRecTerm = pRecTerm->pPrior; } - /* Only one recursive reference is permitted. */ - if( pTab->nTabRef>2 ){ - sqlite3ErrorMsg( - pParse, "multiple references to recursive table: %s", pCte->zName - ); - return SQLITE_ERROR; - } - assert( pTab->nTabRef==1 || - ((pSel->selFlags&SF_Recursive) && pTab->nTabRef==2 )); - pCte->zCteErr = "circular reference: %s"; pSavedWith = pParse->pWith; pParse->pWith = pWith; - if( bMayRecursive ){ - Select *pPrior = pSel->pPrior; - assert( pPrior->pWith==0 ); - pPrior->pWith = pSel->pWith; - sqlite3WalkSelect(pWalker, pPrior); - pPrior->pWith = 0; + if( pSel->selFlags & SF_Recursive ){ + int rc; + assert( pRecTerm!=0 ); + assert( (pRecTerm->selFlags & SF_Recursive)==0 ); + assert( pRecTerm->pNext!=0 ); + assert( (pRecTerm->pNext->selFlags & SF_Recursive)!=0 ); + assert( pRecTerm->pWith==0 ); + pRecTerm->pWith = pSel->pWith; + rc = sqlite3WalkSelect(pWalker, pRecTerm); + pRecTerm->pWith = 0; + if( rc ){ + pParse->pWith = pSavedWith; + return 2; + } }else{ - sqlite3WalkSelect(pWalker, pSel); + if( sqlite3WalkSelect(pWalker, pSel) ){ + pParse->pWith = pSavedWith; + return 2; + } } pParse->pWith = pWith; @@ -129347,7 +154834,7 @@ static int withExpand( pCte->zName, pEList->nExpr, pCte->pCols->nExpr ); pParse->pWith = pSavedWith; - return SQLITE_ERROR; + return 2; } pEList = pCte->pCols; } @@ -129363,64 +154850,97 @@ static int withExpand( } pCte->zCteErr = 0; pParse->pWith = pSavedWith; + return 1; /* Success */ } - - return SQLITE_OK; + return 0; /* No match */ } #endif #ifndef SQLITE_OMIT_CTE /* -** If the SELECT passed as the second argument has an associated WITH +** If the SELECT passed as the second argument has an associated WITH ** clause, pop it from the stack stored as part of the Parse object. ** ** This function is used as the xSelectCallback2() callback by ** sqlite3SelectExpand() when walking a SELECT tree to resolve table -** names and other FROM clause elements. +** names and other FROM clause elements. */ -static void selectPopWith(Walker *pWalker, Select *p){ +SQLITE_PRIVATE void sqlite3SelectPopWith(Walker *pWalker, Select *p){ Parse *pParse = pWalker->pParse; if( OK_IF_ALWAYS_TRUE(pParse->pWith) && p->pPrior==0 ){ With *pWith = findRightmost(p)->pWith; if( pWith!=0 ){ - assert( pParse->pWith==pWith ); + assert( pParse->pWith==pWith || pParse->nErr ); pParse->pWith = pWith->pOuter; } } } -#else -#define selectPopWith 0 #endif /* -** The SrcList_item structure passed as the second argument represents a +** The SrcItem structure passed as the second argument represents a ** sub-query in the FROM clause of a SELECT statement. This function -** allocates and populates the SrcList_item.pTab object. If successful, +** allocates and populates the SrcItem.pTab object. If successful, ** SQLITE_OK is returned. Otherwise, if an OOM error is encountered, ** SQLITE_NOMEM. */ -SQLITE_PRIVATE int sqlite3ExpandSubquery(Parse *pParse, struct SrcList_item *pFrom){ - Select *pSel = pFrom->pSelect; +SQLITE_PRIVATE int sqlite3ExpandSubquery(Parse *pParse, SrcItem *pFrom){ + Select *pSel; Table *pTab; + assert( pFrom->fg.isSubquery ); + assert( pFrom->u4.pSubq!=0 ); + pSel = pFrom->u4.pSubq->pSelect; assert( pSel ); - pFrom->pTab = pTab = sqlite3DbMallocZero(pParse->db, sizeof(Table)); + pFrom->pSTab = pTab = sqlite3DbMallocZero(pParse->db, sizeof(Table)); if( pTab==0 ) return SQLITE_NOMEM; pTab->nTabRef = 1; if( pFrom->zAlias ){ pTab->zName = sqlite3DbStrDup(pParse->db, pFrom->zAlias); }else{ - pTab->zName = sqlite3MPrintf(pParse->db, "subquery_%u", pSel->selId); + pTab->zName = sqlite3MPrintf(pParse->db, "%!S", pFrom); } while( pSel->pPrior ){ pSel = pSel->pPrior; } sqlite3ColumnsFromExprList(pParse, pSel->pEList,&pTab->nCol,&pTab->aCol); pTab->iPKey = -1; + pTab->eTabType = TABTYP_VIEW; pTab->nRowLogEst = 200; assert( 200==sqlite3LogEst(1048576) ); - pTab->tabFlags |= TF_Ephemeral; +#ifndef SQLITE_ALLOW_ROWID_IN_VIEW + /* The usual case - do not allow ROWID on a subquery */ + pTab->tabFlags |= TF_Ephemeral | TF_NoVisibleRowid; +#else + /* Legacy compatibility mode */ + pTab->tabFlags |= TF_Ephemeral | sqlite3Config.mNoVisibleRowid; +#endif + return pParse->nErr ? SQLITE_ERROR : SQLITE_OK; +} - return SQLITE_OK; + +/* +** Check the N SrcItem objects to the right of pBase. (N might be zero!) +** If any of those SrcItem objects have a USING clause containing zName +** then return true. +** +** If N is zero, or none of the N SrcItem objects to the right of pBase +** contains a USING clause, or if none of the USING clauses contain zName, +** then return false. +*/ +static int inAnyUsingClause( + const char *zName, /* Name we are looking for */ + SrcItem *pBase, /* The base SrcItem. Looking at pBase[1] and following */ + int N /* How many SrcItems to check */ +){ + while( N>0 ){ + N--; + pBase++; + if( pBase->fg.isUsing==0 ) continue; + if( NEVER(pBase->u3.pUsing==0) ) continue; + if( sqlite3IdListIndex(pBase->u3.pUsing, zName)>=0 ) return 1; + } + return 0; } + /* ** This routine is a Walker callback for "expanding" a SELECT statement. ** "Expanding" means to do the following: @@ -129428,7 +154948,7 @@ SQLITE_PRIVATE int sqlite3ExpandSubquery(Parse *pParse, struct SrcList_item *pFr ** (1) Make sure VDBE cursor numbers have been assigned to every ** element of the FROM clause. ** -** (2) Fill in the pTabList->a[].pTab fields in the SrcList that +** (2) Fill in the pTabList->a[].pTab fields in the SrcList that ** defines FROM clause. When views appear in the FROM clause, ** fill pTabList->a[].pSelect with a copy of the SELECT statement ** that implements the view. A copy is made of the view's SELECT @@ -129447,10 +154967,10 @@ SQLITE_PRIVATE int sqlite3ExpandSubquery(Parse *pParse, struct SrcList_item *pFr */ static int selectExpander(Walker *pWalker, Select *p){ Parse *pParse = pWalker->pParse; - int i, j, k; + int i, j, k, rc; SrcList *pTabList; ExprList *pEList; - struct SrcList_item *pFrom; + SrcItem *pFrom; sqlite3 *db = pParse->db; Expr *pE, *pRight, *pExpr; u16 selFlags = p->selFlags; @@ -129464,8 +154984,21 @@ static int selectExpander(Walker *pWalker, Select *p){ if( (selFlags & SF_Expanded)!=0 ){ return WRC_Prune; } + if( pWalker->eCode ){ + /* Renumber selId because it has been copied from a view */ + p->selId = ++pParse->nSelect; + } pTabList = p->pSrc; pEList = p->pEList; + if( pParse->pWith && (p->selFlags & SF_View) ){ + if( p->pWith==0 ){ + p->pWith = (With*)sqlite3DbMallocZero(db, SZ_WITH(1) ); + if( p->pWith==0 ){ + return WRC_Abort; + } + } + p->pWith->bView = 1; + } sqlite3WithPush(pParse, p->pWith, 0); /* Make sure cursor numbers have been assigned to all entries in @@ -129479,60 +155012,89 @@ static int selectExpander(Walker *pWalker, Select *p){ */ for(i=0, pFrom=pTabList->a; inSrc; i++, pFrom++){ Table *pTab; - assert( pFrom->fg.isRecursive==0 || pFrom->pTab!=0 ); - if( pFrom->fg.isRecursive ) continue; - assert( pFrom->pTab==0 ); -#ifndef SQLITE_OMIT_CTE - if( withExpand(pWalker, pFrom) ) return WRC_Abort; - if( pFrom->pTab ) {} else -#endif + assert( pFrom->fg.isRecursive==0 || pFrom->pSTab!=0 ); + if( pFrom->pSTab ) continue; + assert( pFrom->fg.isRecursive==0 ); if( pFrom->zName==0 ){ #ifndef SQLITE_OMIT_SUBQUERY - Select *pSel = pFrom->pSelect; + Select *pSel; + assert( pFrom->fg.isSubquery && pFrom->u4.pSubq!=0 ); + pSel = pFrom->u4.pSubq->pSelect; /* A sub-query in the FROM clause of a SELECT */ assert( pSel!=0 ); - assert( pFrom->pTab==0 ); + assert( pFrom->pSTab==0 ); if( sqlite3WalkSelect(pWalker, pSel) ) return WRC_Abort; if( sqlite3ExpandSubquery(pParse, pFrom) ) return WRC_Abort; +#endif +#ifndef SQLITE_OMIT_CTE + }else if( (rc = resolveFromTermToCte(pParse, pWalker, pFrom))!=0 ){ + if( rc>1 ) return WRC_Abort; + pTab = pFrom->pSTab; + assert( pTab!=0 ); #endif }else{ /* An ordinary table or view name in the FROM clause */ - assert( pFrom->pTab==0 ); - pFrom->pTab = pTab = sqlite3LocateTableItem(pParse, 0, pFrom); + assert( pFrom->pSTab==0 ); + pFrom->pSTab = pTab = sqlite3LocateTableItem(pParse, 0, pFrom); if( pTab==0 ) return WRC_Abort; if( pTab->nTabRef>=0xffff ){ sqlite3ErrorMsg(pParse, "too many references to \"%s\": max 65535", pTab->zName); - pFrom->pTab = 0; + pFrom->pSTab = 0; return WRC_Abort; } pTab->nTabRef++; if( !IsVirtual(pTab) && cannotBeFunction(pParse, pFrom) ){ return WRC_Abort; } -#if !defined(SQLITE_OMIT_VIEW) || !defined (SQLITE_OMIT_VIRTUALTABLE) - if( IsVirtual(pTab) || pTab->pSelect ){ +#if !defined(SQLITE_OMIT_VIEW) || !defined(SQLITE_OMIT_VIRTUALTABLE) + if( !IsOrdinaryTable(pTab) ){ i16 nCol; + u8 eCodeOrig = pWalker->eCode; if( sqlite3ViewGetColumnNames(pParse, pTab) ) return WRC_Abort; - assert( pFrom->pSelect==0 ); - pFrom->pSelect = sqlite3SelectDup(db, pTab->pSelect, 0); + assert( pFrom->fg.isSubquery==0 ); + if( IsView(pTab) ){ + if( (db->flags & SQLITE_EnableView)==0 + && pTab->pSchema!=db->aDb[1].pSchema + ){ + sqlite3ErrorMsg(pParse, "access to view \"%s\" prohibited", + pTab->zName); + } + sqlite3SrcItemAttachSubquery(pParse, pFrom, pTab->u.view.pSelect, 1); + } +#ifndef SQLITE_OMIT_VIRTUALTABLE + else if( ALWAYS(IsVirtual(pTab)) + && (pFrom->fg.fromDDL || (pParse->prepFlags & SQLITE_PREPARE_FROM_DDL)) + && ALWAYS(pTab->u.vtab.p!=0) + && pTab->u.vtab.p->eVtabRisk > ((db->flags & SQLITE_TrustedSchema)!=0) + ){ + sqlite3ErrorMsg(pParse, "unsafe use of virtual table \"%s\"", + pTab->zName); + } + assert( SQLITE_VTABRISK_Normal==1 && SQLITE_VTABRISK_High==2 ); +#endif nCol = pTab->nCol; pTab->nCol = -1; - sqlite3WalkSelect(pWalker, pFrom->pSelect); + pWalker->eCode = 1; /* Turn on Select.selId renumbering */ + if( pFrom->fg.isSubquery ){ + sqlite3WalkSelect(pWalker, pFrom->u4.pSubq->pSelect); + } + pWalker->eCode = eCodeOrig; pTab->nCol = nCol; } #endif } /* Locate the index named by the INDEXED BY clause, if any. */ - if( sqlite3IndexedByLookup(pParse, pFrom) ){ + if( pFrom->fg.isIndexedBy && sqlite3IndexedByLookup(pParse, pFrom) ){ return WRC_Abort; } } /* Process NATURAL keywords, and ON and USING clauses of joins. */ - if( db->mallocFailed || sqliteProcessJoin(pParse, p) ){ + assert( db->mallocFailed==0 || pParse->nErr!=0 ); + if( pParse->nErr || sqlite3ProcessJoin(pParse, p) ){ return WRC_Abort; } @@ -129579,10 +155141,9 @@ static int selectExpander(Walker *pWalker, Select *p){ */ pNew = sqlite3ExprListAppend(pParse, pNew, a[k].pExpr); if( pNew ){ - pNew->a[pNew->nExpr-1].zName = a[k].zName; - pNew->a[pNew->nExpr-1].zSpan = a[k].zSpan; - a[k].zName = 0; - a[k].zSpan = 0; + pNew->a[pNew->nExpr-1].zEName = a[k].zEName; + pNew->a[pNew->nExpr-1].fg.eEName = a[k].fg.eEName; + a[k].zEName = 0; } a[k].pExpr = 0; }else{ @@ -129590,101 +155151,177 @@ static int selectExpander(Walker *pWalker, Select *p){ ** expanded. */ int tableSeen = 0; /* Set to 1 when TABLE matches */ char *zTName = 0; /* text of name of TABLE */ + int iErrOfst; if( pE->op==TK_DOT ){ + assert( (selFlags & SF_NestedFrom)==0 ); assert( pE->pLeft!=0 ); assert( !ExprHasProperty(pE->pLeft, EP_IntValue) ); zTName = pE->pLeft->u.zToken; + assert( ExprUseWOfst(pE->pLeft) ); + iErrOfst = pE->pRight->w.iOfst; + }else{ + assert( ExprUseWOfst(pE) ); + iErrOfst = pE->w.iOfst; } for(i=0, pFrom=pTabList->a; inSrc; i++, pFrom++){ - Table *pTab = pFrom->pTab; - Select *pSub = pFrom->pSelect; - char *zTabName = pFrom->zAlias; - const char *zSchemaName = 0; - int iDb; - if( zTabName==0 ){ + int nAdd; /* Number of cols including rowid */ + Table *pTab = pFrom->pSTab; /* Table for this data source */ + ExprList *pNestedFrom; /* Result-set of a nested FROM clause */ + char *zTabName; /* AS name for this data source */ + const char *zSchemaName = 0; /* Schema name for this data source */ + int iDb; /* Schema index for this data src */ + IdList *pUsing; /* USING clause for pFrom[1] */ + + if( (zTabName = pFrom->zAlias)==0 ){ zTabName = pTab->zName; } if( db->mallocFailed ) break; - if( pSub==0 || (pSub->selFlags & SF_NestedFrom)==0 ){ - pSub = 0; + assert( (int)pFrom->fg.isNestedFrom == IsNestedFrom(pFrom) ); + if( pFrom->fg.isNestedFrom ){ + assert( pFrom->fg.isSubquery && pFrom->u4.pSubq ); + assert( pFrom->u4.pSubq->pSelect!=0 ); + pNestedFrom = pFrom->u4.pSubq->pSelect->pEList; + assert( pNestedFrom!=0 ); + assert( pNestedFrom->nExpr==pTab->nCol ); + assert( VisibleRowid(pTab)==0 || ViewCanHaveRowid ); + }else{ if( zTName && sqlite3StrICmp(zTName, zTabName)!=0 ){ continue; } + pNestedFrom = 0; iDb = sqlite3SchemaToIndex(db, pTab->pSchema); zSchemaName = iDb>=0 ? db->aDb[iDb].zDbSName : "*"; } - for(j=0; jnCol; j++){ - char *zName = pTab->aCol[j].zName; - char *zColname; /* The computed column name */ - char *zToFree; /* Malloced string that needs to be freed */ - Token sColname; /* Computed column name as a token */ - - assert( zName ); - if( zTName && pSub - && sqlite3MatchSpanName(pSub->pEList->a[j].zSpan, 0, zTName, 0)==0 - ){ - continue; + if( i+1nSrc + && pFrom[1].fg.isUsing + && (selFlags & SF_NestedFrom)!=0 + ){ + int ii; + pUsing = pFrom[1].u3.pUsing; + for(ii=0; iinId; ii++){ + const char *zUName = pUsing->a[ii].zName; + pRight = sqlite3Expr(db, TK_ID, zUName); + sqlite3ExprSetErrorOffset(pRight, iErrOfst); + pNew = sqlite3ExprListAppend(pParse, pNew, pRight); + if( pNew ){ + struct ExprList_item *pX = &pNew->a[pNew->nExpr-1]; + assert( pX->zEName==0 ); + pX->zEName = sqlite3MPrintf(db,"..%s", zUName); + pX->fg.eEName = ENAME_TAB; + pX->fg.bUsingTerm = 1; + } } + }else{ + pUsing = 0; + } - /* If a column is marked as 'hidden', omit it from the expanded - ** result-set list unless the SELECT has the SF_IncludeHidden - ** bit set. - */ - if( (p->selFlags & SF_IncludeHidden)==0 - && IsHiddenColumn(&pTab->aCol[j]) - ){ - continue; - } - tableSeen = 1; + nAdd = pTab->nCol; + if( VisibleRowid(pTab) && (selFlags & SF_NestedFrom)!=0 ) nAdd++; + for(j=0; jnCol ){ + zName = sqlite3RowidAlias(pTab); + if( zName==0 ) continue; + }else{ + zName = pTab->aCol[j].zCnName; + + /* If pTab is actually an SF_NestedFrom sub-select, do not + ** expand any ENAME_ROWID columns. */ + if( pNestedFrom && pNestedFrom->a[j].fg.eEName==ENAME_ROWID ){ + continue; + } + + if( zTName + && pNestedFrom + && sqlite3MatchEName(&pNestedFrom->a[j], 0, zTName, 0, 0)==0 + ){ + continue; + } - if( i>0 && zTName==0 ){ - if( (pFrom->fg.jointype & JT_NATURAL)!=0 - && tableAndColumnIndex(pTabList, i, zName, 0, 0) + /* If a column is marked as 'hidden', omit it from the expanded + ** result-set list unless the SELECT has the SF_IncludeHidden + ** bit set. + */ + if( (p->selFlags & SF_IncludeHidden)==0 + && IsHiddenColumn(&pTab->aCol[j]) + ){ + continue; + } + if( (pTab->aCol[j].colFlags & COLFLAG_NOEXPAND)!=0 + && zTName==0 + && (selFlags & (SF_NestedFrom))==0 ){ - /* In a NATURAL join, omit the join columns from the - ** table to the right of the join */ continue; } - if( sqlite3IdListIndex(pFrom->pUsing, zName)>=0 ){ + } + assert( zName ); + tableSeen = 1; + + if( i>0 && zTName==0 && (selFlags & SF_NestedFrom)==0 ){ + if( pFrom->fg.isUsing + && sqlite3IdListIndex(pFrom->u3.pUsing, zName)>=0 + ){ /* In a join with a USING clause, omit columns in the ** using clause from the table on the right. */ continue; } } pRight = sqlite3Expr(db, TK_ID, zName); - zColname = zName; - zToFree = 0; - if( longNames || pTabList->nSrc>1 ){ + if( (pTabList->nSrc>1 + && ( (pFrom->fg.jointype & JT_LTORJ)==0 + || (selFlags & SF_NestedFrom)!=0 + || !inAnyUsingClause(zName,pFrom,pTabList->nSrc-i-1) + ) + ) + || IN_RENAME_OBJECT + ){ Expr *pLeft; pLeft = sqlite3Expr(db, TK_ID, zTabName); pExpr = sqlite3PExpr(pParse, TK_DOT, pLeft, pRight); + if( IN_RENAME_OBJECT && pE->pLeft ){ + sqlite3RenameTokenRemap(pParse, pLeft, pE->pLeft); + } if( zSchemaName ){ pLeft = sqlite3Expr(db, TK_ID, zSchemaName); pExpr = sqlite3PExpr(pParse, TK_DOT, pLeft, pExpr); } - if( longNames ){ - zColname = sqlite3MPrintf(db, "%s.%s", zTabName, zName); - zToFree = zColname; - } }else{ pExpr = pRight; } + sqlite3ExprSetErrorOffset(pExpr, iErrOfst); pNew = sqlite3ExprListAppend(pParse, pNew, pExpr); - sqlite3TokenInit(&sColname, zColname); - sqlite3ExprListSetName(pParse, pNew, &sColname, 0); - if( pNew && (p->selFlags & SF_NestedFrom)!=0 ){ - struct ExprList_item *pX = &pNew->a[pNew->nExpr-1]; - if( pSub ){ - pX->zSpan = sqlite3DbStrDup(db, pSub->pEList->a[j].zSpan); - testcase( pX->zSpan==0 ); + if( pNew==0 ){ + break; /* OOM */ + } + pX = &pNew->a[pNew->nExpr-1]; + assert( pX->zEName==0 ); + if( (selFlags & SF_NestedFrom)!=0 && !IN_RENAME_OBJECT ){ + if( pNestedFrom && (!ViewCanHaveRowid || jnExpr) ){ + assert( jnExpr ); + pX->zEName = sqlite3DbStrDup(db, pNestedFrom->a[j].zEName); + testcase( pX->zEName==0 ); }else{ - pX->zSpan = sqlite3MPrintf(db, "%s.%s.%s", - zSchemaName, zTabName, zColname); - testcase( pX->zSpan==0 ); + pX->zEName = sqlite3MPrintf(db, "%s.%s.%s", + zSchemaName, zTabName, zName); + testcase( pX->zEName==0 ); + } + pX->fg.eEName = (j==pTab->nCol ? ENAME_ROWID : ENAME_TAB); + if( (pFrom->fg.isUsing + && sqlite3IdListIndex(pFrom->u3.pUsing, zName)>=0) + || (pUsing && sqlite3IdListIndex(pUsing, zName)>=0) + || (jnCol && (pTab->aCol[j].colFlags & COLFLAG_NOEXPAND)) + ){ + pX->fg.bNoExpand = 1; } - pX->bSpanIsTab = 1; + }else if( longNames ){ + pX->zEName = sqlite3MPrintf(db, "%s.%s", zTabName, zName); + pX->fg.eEName = ENAME_NAME; + }else{ + pX->zEName = sqlite3DbStrDup(db, zName); + pX->fg.eEName = ENAME_NAME; } - sqlite3DbFree(db, zToFree); } } if( !tableSeen ){ @@ -129708,29 +155345,12 @@ static int selectExpander(Walker *pWalker, Select *p){ p->selFlags |= SF_ComplexResult; } } - return WRC_Continue; -} - -/* -** No-op routine for the parse-tree walker. -** -** When this routine is the Walker.xExprCallback then expression trees -** are walked without any actions being taken at each node. Presumably, -** when this routine is used for Walker.xExprCallback then -** Walker.xSelectCallback is set to do something useful for every -** subquery in the parser tree. -*/ -SQLITE_PRIVATE int sqlite3ExprWalkNoop(Walker *NotUsed, Expr *NotUsed2){ - UNUSED_PARAMETER2(NotUsed, NotUsed2); - return WRC_Continue; -} - -/* -** No-op routine for the parse-tree walker for SELECT statements. -** subquery in the parser tree. -*/ -SQLITE_PRIVATE int sqlite3SelectWalkNoop(Walker *NotUsed, Select *NotUsed2){ - UNUSED_PARAMETER2(NotUsed, NotUsed2); +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x8 ){ + TREETRACE(0x8,pParse,p,("After result-set wildcard expansion:\n")); + sqlite3TreeViewSelect(0, p, 0); + } +#endif return WRC_Continue; } @@ -129767,7 +155387,8 @@ static void sqlite3SelectExpand(Parse *pParse, Select *pSelect){ sqlite3WalkSelect(&w, pSelect); } w.xSelectCallback = selectExpander; - w.xSelectCallback2 = selectPopWith; + w.xSelectCallback2 = sqlite3SelectPopWith; + w.eCode = 0; sqlite3WalkSelect(&w, pSelect); } @@ -129777,36 +155398,33 @@ static void sqlite3SelectExpand(Parse *pParse, Select *pSelect){ ** This is a Walker.xSelectCallback callback for the sqlite3SelectTypeInfo() ** interface. ** -** For each FROM-clause subquery, add Column.zType and Column.zColl -** information to the Table structure that represents the result set -** of that subquery. +** For each FROM-clause subquery, add Column.zType, Column.zColl, and +** Column.affinity information to the Table structure that represents +** the result set of that subquery. ** ** The Table structure that represents the result set was constructed -** by selectExpander() but the type and collation information was omitted -** at that point because identifiers had not yet been resolved. This -** routine is called after identifier resolution. +** by selectExpander() but the type and collation and affinity information +** was omitted at that point because identifiers had not yet been resolved. +** This routine is called after identifier resolution. */ static void selectAddSubqueryTypeInfo(Walker *pWalker, Select *p){ Parse *pParse; int i; SrcList *pTabList; - struct SrcList_item *pFrom; + SrcItem *pFrom; - assert( p->selFlags & SF_Resolved ); if( p->selFlags & SF_HasTypeInfo ) return; p->selFlags |= SF_HasTypeInfo; pParse = pWalker->pParse; + assert( (p->selFlags & SF_Resolved) ); pTabList = p->pSrc; for(i=0, pFrom=pTabList->a; inSrc; i++, pFrom++){ - Table *pTab = pFrom->pTab; + Table *pTab = pFrom->pSTab; assert( pTab!=0 ); - if( (pTab->tabFlags & TF_Ephemeral)!=0 ){ + if( (pTab->tabFlags & TF_Ephemeral)!=0 && pFrom->fg.isSubquery ){ /* A sub-query in the FROM clause of a SELECT */ - Select *pSel = pFrom->pSelect; - if( pSel ){ - while( pSel->pPrior ) pSel = pSel->pPrior; - sqlite3SelectAddColumnTypeAndCollation(pParse, pTab, pSel); - } + Select *pSel = pFrom->u4.pSubq->pSelect; + sqlite3SubqueryColumnTypes(pParse, pTab, pSel, SQLITE_AFF_NONE); } } } @@ -129850,15 +155468,196 @@ SQLITE_PRIVATE void sqlite3SelectPrep( NameContext *pOuterNC /* Name context for container */ ){ assert( p!=0 || pParse->db->mallocFailed ); + assert( pParse->db->pParse==pParse ); if( pParse->db->mallocFailed ) return; if( p->selFlags & SF_HasTypeInfo ) return; sqlite3SelectExpand(pParse, p); - if( pParse->nErr || pParse->db->mallocFailed ) return; + if( pParse->nErr ) return; sqlite3ResolveSelectNames(pParse, p, pOuterNC); - if( pParse->nErr || pParse->db->mallocFailed ) return; + if( pParse->nErr ) return; sqlite3SelectAddTypeInfo(pParse, p); } +#if TREETRACE_ENABLED +/* +** Display all information about an AggInfo object +*/ +static void printAggInfo(AggInfo *pAggInfo){ + int ii; + sqlite3DebugPrintf("AggInfo %d/%p:\n", + pAggInfo->selId, pAggInfo); + for(ii=0; iinColumn; ii++){ + struct AggInfo_col *pCol = &pAggInfo->aCol[ii]; + sqlite3DebugPrintf( + "agg-column[%d] pTab=%s iTable=%d iColumn=%d iMem=%d" + " iSorterColumn=%d %s\n", + ii, pCol->pTab ? pCol->pTab->zName : "NULL", + pCol->iTable, pCol->iColumn, pAggInfo->iFirstReg+ii, + pCol->iSorterColumn, + ii>=pAggInfo->nAccumulator ? "" : " Accumulator"); + sqlite3TreeViewExpr(0, pAggInfo->aCol[ii].pCExpr, 0); + } + for(ii=0; iinFunc; ii++){ + sqlite3DebugPrintf("agg-func[%d]: iMem=%d\n", + ii, pAggInfo->iFirstReg+pAggInfo->nColumn+ii); + sqlite3TreeViewExpr(0, pAggInfo->aFunc[ii].pFExpr, 0); + } +} +#endif /* TREETRACE_ENABLED */ + +/* +** Analyze the arguments to aggregate functions. Create new pAggInfo->aCol[] +** entries for columns that are arguments to aggregate functions but which +** are not otherwise used. +** +** The aCol[] entries in AggInfo prior to nAccumulator are columns that +** are referenced outside of aggregate functions. These might be columns +** that are part of the GROUP by clause, for example. Other database engines +** would throw an error if there is a column reference that is not in the +** GROUP BY clause and that is not part of an aggregate function argument. +** But SQLite allows this. +** +** The aCol[] entries beginning with the aCol[nAccumulator] and following +** are column references that are used exclusively as arguments to +** aggregate functions. This routine is responsible for computing +** (or recomputing) those aCol[] entries. +*/ +static void analyzeAggFuncArgs( + AggInfo *pAggInfo, + NameContext *pNC +){ + int i; + assert( pAggInfo!=0 ); + assert( pAggInfo->iFirstReg==0 ); + pNC->ncFlags |= NC_InAggFunc; + for(i=0; inFunc; i++){ + Expr *pExpr = pAggInfo->aFunc[i].pFExpr; + assert( pExpr->op==TK_FUNCTION || pExpr->op==TK_AGG_FUNCTION ); + assert( ExprUseXList(pExpr) ); + sqlite3ExprAnalyzeAggList(pNC, pExpr->x.pList); + if( pExpr->pLeft ){ + assert( pExpr->pLeft->op==TK_ORDER ); + assert( ExprUseXList(pExpr->pLeft) ); + sqlite3ExprAnalyzeAggList(pNC, pExpr->pLeft->x.pList); + } +#ifndef SQLITE_OMIT_WINDOWFUNC + assert( !IsWindowFunc(pExpr) ); + if( ExprHasProperty(pExpr, EP_WinFunc) ){ + sqlite3ExprAnalyzeAggregates(pNC, pExpr->y.pWin->pFilter); + } +#endif + } + pNC->ncFlags &= ~NC_InAggFunc; +} + +/* +** An index on expressions is being used in the inner loop of an +** aggregate query with a GROUP BY clause. This routine attempts +** to adjust the AggInfo object to take advantage of index and to +** perhaps use the index as a covering index. +** +*/ +static void optimizeAggregateUseOfIndexedExpr( + Parse *pParse, /* Parsing context */ + Select *pSelect, /* The SELECT statement being processed */ + AggInfo *pAggInfo, /* The aggregate info */ + NameContext *pNC /* Name context used to resolve agg-func args */ +){ + assert( pAggInfo->iFirstReg==0 ); + assert( pSelect!=0 ); + assert( pSelect->pGroupBy!=0 ); + pAggInfo->nColumn = pAggInfo->nAccumulator; + if( ALWAYS(pAggInfo->nSortingColumn>0) ){ + int mx = pSelect->pGroupBy->nExpr - 1; + int j, k; + for(j=0; jnColumn; j++){ + k = pAggInfo->aCol[j].iSorterColumn; + if( k>mx ) mx = k; + } + pAggInfo->nSortingColumn = mx+1; + } + analyzeAggFuncArgs(pAggInfo, pNC); +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x20 ){ + IndexedExpr *pIEpr; + TREETRACE(0x20, pParse, pSelect, + ("AggInfo (possibly) adjusted for Indexed Exprs\n")); + sqlite3TreeViewSelect(0, pSelect, 0); + for(pIEpr=pParse->pIdxEpr; pIEpr; pIEpr=pIEpr->pIENext){ + printf("data-cursor=%d index={%d,%d}\n", + pIEpr->iDataCur, pIEpr->iIdxCur, pIEpr->iIdxCol); + sqlite3TreeViewExpr(0, pIEpr->pExpr, 0); + } + printAggInfo(pAggInfo); + } +#else + UNUSED_PARAMETER(pSelect); + UNUSED_PARAMETER(pParse); +#endif +} + +/* +** Walker callback for aggregateConvertIndexedExprRefToColumn(). +*/ +static int aggregateIdxEprRefToColCallback(Walker *pWalker, Expr *pExpr){ + AggInfo *pAggInfo; + struct AggInfo_col *pCol; + UNUSED_PARAMETER(pWalker); + if( pExpr->pAggInfo==0 ) return WRC_Continue; + if( pExpr->op==TK_AGG_COLUMN ) return WRC_Continue; + if( pExpr->op==TK_AGG_FUNCTION ) return WRC_Continue; + if( pExpr->op==TK_IF_NULL_ROW ) return WRC_Continue; + pAggInfo = pExpr->pAggInfo; + if( NEVER(pExpr->iAgg>=pAggInfo->nColumn) ) return WRC_Continue; + assert( pExpr->iAgg>=0 ); + pCol = &pAggInfo->aCol[pExpr->iAgg]; + pExpr->op = TK_AGG_COLUMN; + pExpr->iTable = pCol->iTable; + pExpr->iColumn = pCol->iColumn; + ExprClearProperty(pExpr, EP_Skip|EP_Collate|EP_Unlikely); + return WRC_Prune; +} + +/* +** Convert every pAggInfo->aFunc[].pExpr such that any node within +** those expressions that has pAppInfo set is changed into a TK_AGG_COLUMN +** opcode. +*/ +static void aggregateConvertIndexedExprRefToColumn(AggInfo *pAggInfo){ + int i; + Walker w; + memset(&w, 0, sizeof(w)); + w.xExprCallback = aggregateIdxEprRefToColCallback; + for(i=0; inFunc; i++){ + sqlite3WalkExpr(&w, pAggInfo->aFunc[i].pFExpr); + } +} + + +/* +** Allocate a block of registers so that there is one register for each +** pAggInfo->aCol[] and pAggInfo->aFunc[] entry in pAggInfo. The first +** register in this block is stored in pAggInfo->iFirstReg. +** +** This routine may only be called once for each AggInfo object. Prior +** to calling this routine: +** +** * The aCol[] and aFunc[] arrays may be modified +** * The AggInfoColumnReg() and AggInfoFuncReg() macros may not be used +** +** After calling this routine: +** +** * The aCol[] and aFunc[] arrays are fixed +** * The AggInfoColumnReg() and AggInfoFuncReg() macros may be used +** +*/ +static void assignAggregateRegisters(Parse *pParse, AggInfo *pAggInfo){ + assert( pAggInfo!=0 ); + assert( pAggInfo->iFirstReg==0 ); + pAggInfo->iFirstReg = pParse->nMem + 1; + pParse->nMem += pAggInfo->nColumn + pAggInfo->nFunc; +} + /* ** Reset the aggregate accumulator. ** @@ -129872,34 +155671,59 @@ static void resetAccumulator(Parse *pParse, AggInfo *pAggInfo){ int i; struct AggInfo_func *pFunc; int nReg = pAggInfo->nFunc + pAggInfo->nColumn; + assert( pAggInfo->iFirstReg>0 ); + assert( pParse->db->pParse==pParse ); + assert( pParse->db->mallocFailed==0 || pParse->nErr!=0 ); if( nReg==0 ) return; -#ifdef SQLITE_DEBUG - /* Verify that all AggInfo registers are within the range specified by - ** AggInfo.mnReg..AggInfo.mxReg */ - assert( nReg==pAggInfo->mxReg-pAggInfo->mnReg+1 ); - for(i=0; inColumn; i++){ - assert( pAggInfo->aCol[i].iMem>=pAggInfo->mnReg - && pAggInfo->aCol[i].iMem<=pAggInfo->mxReg ); - } - for(i=0; inFunc; i++){ - assert( pAggInfo->aFunc[i].iMem>=pAggInfo->mnReg - && pAggInfo->aFunc[i].iMem<=pAggInfo->mxReg ); - } -#endif - sqlite3VdbeAddOp3(v, OP_Null, 0, pAggInfo->mnReg, pAggInfo->mxReg); + if( pParse->nErr ) return; + sqlite3VdbeAddOp3(v, OP_Null, 0, pAggInfo->iFirstReg, + pAggInfo->iFirstReg+nReg-1); for(pFunc=pAggInfo->aFunc, i=0; inFunc; i++, pFunc++){ if( pFunc->iDistinct>=0 ){ - Expr *pE = pFunc->pExpr; - assert( !ExprHasProperty(pE, EP_xIsSelect) ); + Expr *pE = pFunc->pFExpr; + assert( ExprUseXList(pE) ); if( pE->x.pList==0 || pE->x.pList->nExpr!=1 ){ sqlite3ErrorMsg(pParse, "DISTINCT aggregates must have exactly one " "argument"); pFunc->iDistinct = -1; }else{ KeyInfo *pKeyInfo = sqlite3KeyInfoFromExprList(pParse, pE->x.pList,0,0); - sqlite3VdbeAddOp4(v, OP_OpenEphemeral, pFunc->iDistinct, 0, 0, - (char*)pKeyInfo, P4_KEYINFO); + pFunc->iDistAddr = sqlite3VdbeAddOp4(v, OP_OpenEphemeral, + pFunc->iDistinct, 0, 0, (char*)pKeyInfo, P4_KEYINFO); + ExplainQueryPlan((pParse, 0, "USE TEMP B-TREE FOR %s(DISTINCT)", + pFunc->pFunc->zName)); + } + } + if( pFunc->iOBTab>=0 ){ + ExprList *pOBList; + KeyInfo *pKeyInfo; + int nExtra = 0; + assert( pFunc->pFExpr->pLeft!=0 ); + assert( pFunc->pFExpr->pLeft->op==TK_ORDER ); + assert( ExprUseXList(pFunc->pFExpr->pLeft) ); + assert( pFunc->pFunc!=0 ); + pOBList = pFunc->pFExpr->pLeft->x.pList; + if( !pFunc->bOBUnique ){ + nExtra++; /* One extra column for the OP_Sequence */ + } + if( pFunc->bOBPayload ){ + /* extra columns for the function arguments */ + assert( ExprUseXList(pFunc->pFExpr) ); + assert( pFunc->pFExpr->x.pList!=0 ); + nExtra += pFunc->pFExpr->x.pList->nExpr; + } + if( pFunc->bUseSubtype ){ + nExtra += pFunc->pFExpr->x.pList->nExpr; + } + pKeyInfo = sqlite3KeyInfoFromExprList(pParse, pOBList, 0, nExtra); + if( !pFunc->bOBUnique && pParse->nErr==0 ){ + pKeyInfo->nKeyField++; } + sqlite3VdbeAddOp4(v, OP_OpenEphemeral, + pFunc->iOBTab, pOBList->nExpr+nExtra, 0, + (char*)pKeyInfo, P4_KEYINFO); + ExplainQueryPlan((pParse, 0, "USE TEMP B-TREE FOR %s(ORDER BY)", + pFunc->pFunc->zName)); } } } @@ -129913,24 +155737,82 @@ static void finalizeAggFunctions(Parse *pParse, AggInfo *pAggInfo){ int i; struct AggInfo_func *pF; for(i=0, pF=pAggInfo->aFunc; inFunc; i++, pF++){ - ExprList *pList = pF->pExpr->x.pList; - assert( !ExprHasProperty(pF->pExpr, EP_xIsSelect) ); - sqlite3VdbeAddOp2(v, OP_AggFinal, pF->iMem, pList ? pList->nExpr : 0); + ExprList *pList; + assert( ExprUseXList(pF->pFExpr) ); + if( pParse->nErr ) return; + pList = pF->pFExpr->x.pList; + if( pF->iOBTab>=0 ){ + /* For an ORDER BY aggregate, calls to OP_AggStep were deferred. Inputs + ** were stored in emphermal table pF->iOBTab. Here, we extract those + ** inputs (in ORDER BY order) and make all calls to OP_AggStep + ** before doing the OP_AggFinal call. */ + int iTop; /* Start of loop for extracting columns */ + int nArg; /* Number of columns to extract */ + int nKey; /* Key columns to be skipped */ + int regAgg; /* Extract into this array */ + int j; /* Loop counter */ + + assert( pF->pFunc!=0 ); + nArg = pList->nExpr; + regAgg = sqlite3GetTempRange(pParse, nArg); + + if( pF->bOBPayload==0 ){ + nKey = 0; + }else{ + assert( pF->pFExpr->pLeft!=0 ); + assert( ExprUseXList(pF->pFExpr->pLeft) ); + assert( pF->pFExpr->pLeft->x.pList!=0 ); + nKey = pF->pFExpr->pLeft->x.pList->nExpr; + if( ALWAYS(!pF->bOBUnique) ) nKey++; + } + iTop = sqlite3VdbeAddOp1(v, OP_Rewind, pF->iOBTab); VdbeCoverage(v); + for(j=nArg-1; j>=0; j--){ + sqlite3VdbeAddOp3(v, OP_Column, pF->iOBTab, nKey+j, regAgg+j); + } + if( pF->bUseSubtype ){ + int regSubtype = sqlite3GetTempReg(pParse); + int iBaseCol = nKey + nArg + (pF->bOBPayload==0 && pF->bOBUnique==0); + for(j=nArg-1; j>=0; j--){ + sqlite3VdbeAddOp3(v, OP_Column, pF->iOBTab, iBaseCol+j, regSubtype); + sqlite3VdbeAddOp2(v, OP_SetSubtype, regSubtype, regAgg+j); + } + sqlite3ReleaseTempReg(pParse, regSubtype); + } + sqlite3VdbeAddOp3(v, OP_AggStep, 0, regAgg, AggInfoFuncReg(pAggInfo,i)); + sqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF); + sqlite3VdbeChangeP5(v, (u16)nArg); + sqlite3VdbeAddOp2(v, OP_Next, pF->iOBTab, iTop+1); VdbeCoverage(v); + sqlite3VdbeJumpHere(v, iTop); + sqlite3ReleaseTempRange(pParse, regAgg, nArg); + } + sqlite3VdbeAddOp2(v, OP_AggFinal, AggInfoFuncReg(pAggInfo,i), + pList ? pList->nExpr : 0); sqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF); } } - /* -** Update the accumulator memory cells for an aggregate based on -** the current cursor position. +** Generate code that will update the accumulator memory cells for an +** aggregate based on the current cursor position. ** ** If regAcc is non-zero and there are no min() or max() aggregates ** in pAggInfo, then only populate the pAggInfo->nAccumulator accumulator ** registers if register regAcc contains 0. The caller will take care ** of setting and clearing regAcc. +** +** For an ORDER BY aggregate, the actual accumulator memory cell update +** is deferred until after all input rows have been received, so that they +** can be run in the requested order. In that case, instead of invoking +** OP_AggStep to update the accumulator, just add the arguments that would +** have been passed into OP_AggStep into the sorting ephemeral table +** (along with the appropriate sort key). */ -static void updateAccumulator(Parse *pParse, int regAcc, AggInfo *pAggInfo){ +static void updateAccumulator( + Parse *pParse, + int regAcc, + AggInfo *pAggInfo, + int eDistinctType +){ Vdbe *v = pParse->pVdbe; int i; int regHit = 0; @@ -129938,48 +155820,136 @@ static void updateAccumulator(Parse *pParse, int regAcc, AggInfo *pAggInfo){ struct AggInfo_func *pF; struct AggInfo_col *pC; + assert( pAggInfo->iFirstReg>0 ); + if( pParse->nErr ) return; pAggInfo->directMode = 1; for(i=0, pF=pAggInfo->aFunc; inFunc; i++, pF++){ int nArg; int addrNext = 0; int regAgg; - ExprList *pList = pF->pExpr->x.pList; - assert( !ExprHasProperty(pF->pExpr, EP_xIsSelect) ); - if( pList ){ + int regAggSz = 0; + int regDistinct = 0; + ExprList *pList; + assert( ExprUseXList(pF->pFExpr) ); + assert( !IsWindowFunc(pF->pFExpr) ); + assert( pF->pFunc!=0 ); + pList = pF->pFExpr->x.pList; + if( ExprHasProperty(pF->pFExpr, EP_WinFunc) ){ + Expr *pFilter = pF->pFExpr->y.pWin->pFilter; + if( pAggInfo->nAccumulator + && (pF->pFunc->funcFlags & SQLITE_FUNC_NEEDCOLL) + && regAcc + ){ + /* If regAcc==0, there there exists some min() or max() function + ** without a FILTER clause that will ensure the magnet registers + ** are populated. */ + if( regHit==0 ) regHit = ++pParse->nMem; + /* If this is the first row of the group (regAcc contains 0), clear the + ** "magnet" register regHit so that the accumulator registers + ** are populated if the FILTER clause jumps over the the + ** invocation of min() or max() altogether. Or, if this is not + ** the first row (regAcc contains 1), set the magnet register so that + ** the accumulators are not populated unless the min()/max() is invoked + ** and indicates that they should be. */ + sqlite3VdbeAddOp2(v, OP_Copy, regAcc, regHit); + } + addrNext = sqlite3VdbeMakeLabel(pParse); + sqlite3ExprIfFalse(pParse, pFilter, addrNext, SQLITE_JUMPIFNULL); + } + if( pF->iOBTab>=0 ){ + /* Instead of invoking AggStep, we must push the arguments that would + ** have been passed to AggStep onto the sorting table. */ + int jj; /* Registered used so far in building the record */ + ExprList *pOBList; /* The ORDER BY clause */ + assert( pList!=0 ); + nArg = pList->nExpr; + assert( nArg>0 ); + assert( pF->pFExpr->pLeft!=0 ); + assert( pF->pFExpr->pLeft->op==TK_ORDER ); + assert( ExprUseXList(pF->pFExpr->pLeft) ); + pOBList = pF->pFExpr->pLeft->x.pList; + assert( pOBList!=0 ); + assert( pOBList->nExpr>0 ); + regAggSz = pOBList->nExpr; + if( !pF->bOBUnique ){ + regAggSz++; /* One register for OP_Sequence */ + } + if( pF->bOBPayload ){ + regAggSz += nArg; + } + if( pF->bUseSubtype ){ + regAggSz += nArg; + } + regAggSz++; /* One extra register to hold result of MakeRecord */ + regAgg = sqlite3GetTempRange(pParse, regAggSz); + regDistinct = regAgg; + sqlite3ExprCodeExprList(pParse, pOBList, regAgg, 0, SQLITE_ECEL_DUP); + jj = pOBList->nExpr; + if( !pF->bOBUnique ){ + sqlite3VdbeAddOp2(v, OP_Sequence, pF->iOBTab, regAgg+jj); + jj++; + } + if( pF->bOBPayload ){ + regDistinct = regAgg+jj; + sqlite3ExprCodeExprList(pParse, pList, regDistinct, 0, SQLITE_ECEL_DUP); + jj += nArg; + } + if( pF->bUseSubtype ){ + int kk; + int regBase = pF->bOBPayload ? regDistinct : regAgg; + for(kk=0; kknExpr; regAgg = sqlite3GetTempRange(pParse, nArg); + regDistinct = regAgg; sqlite3ExprCodeExprList(pParse, pList, regAgg, 0, SQLITE_ECEL_DUP); }else{ nArg = 0; regAgg = 0; } - if( pF->iDistinct>=0 ){ - addrNext = sqlite3VdbeMakeLabel(pParse); - testcase( nArg==0 ); /* Error condition */ - testcase( nArg>1 ); /* Also an error */ - codeDistinct(pParse, pF->iDistinct, addrNext, 1, regAgg); - } - if( pF->pFunc->funcFlags & SQLITE_FUNC_NEEDCOLL ){ - CollSeq *pColl = 0; - struct ExprList_item *pItem; - int j; - assert( pList!=0 ); /* pList!=0 if pF->pFunc has NEEDCOLL */ - for(j=0, pItem=pList->a; !pColl && jpExpr); + if( pF->iDistinct>=0 && pList ){ + if( addrNext==0 ){ + addrNext = sqlite3VdbeMakeLabel(pParse); } - if( !pColl ){ - pColl = pParse->db->pDfltColl; + pF->iDistinct = codeDistinct(pParse, eDistinctType, + pF->iDistinct, addrNext, pList, regDistinct); + } + if( pF->iOBTab>=0 ){ + /* Insert a new record into the ORDER BY table */ + sqlite3VdbeAddOp3(v, OP_MakeRecord, regAgg, regAggSz-1, + regAgg+regAggSz-1); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, pF->iOBTab, regAgg+regAggSz-1, + regAgg, regAggSz-1); + sqlite3ReleaseTempRange(pParse, regAgg, regAggSz); + }else{ + /* Invoke the AggStep function */ + if( pF->pFunc->funcFlags & SQLITE_FUNC_NEEDCOLL ){ + CollSeq *pColl = 0; + struct ExprList_item *pItem; + int j; + assert( pList!=0 ); /* pList!=0 if pF->pFunc has NEEDCOLL */ + for(j=0, pItem=pList->a; !pColl && jpExpr); + } + if( !pColl ){ + pColl = pParse->db->pDfltColl; + } + if( regHit==0 && pAggInfo->nAccumulator ) regHit = ++pParse->nMem; + sqlite3VdbeAddOp4(v, OP_CollSeq, regHit, 0, 0, + (char *)pColl, P4_COLLSEQ); } - if( regHit==0 && pAggInfo->nAccumulator ) regHit = ++pParse->nMem; - sqlite3VdbeAddOp4(v, OP_CollSeq, regHit, 0, 0, (char *)pColl, P4_COLLSEQ); + sqlite3VdbeAddOp3(v, OP_AggStep, 0, regAgg, AggInfoFuncReg(pAggInfo,i)); + sqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF); + sqlite3VdbeChangeP5(v, (u16)nArg); + sqlite3ReleaseTempRange(pParse, regAgg, nArg); } - sqlite3VdbeAddOp3(v, OP_AggStep, 0, regAgg, pF->iMem); - sqlite3VdbeAppendP4(v, pF->pFunc, P4_FUNCDEF); - sqlite3VdbeChangeP5(v, (u8)nArg); - sqlite3ReleaseTempRange(pParse, regAgg, nArg); if( addrNext ){ sqlite3VdbeResolveLabel(v, addrNext); } + if( pParse->nErr ) return; } if( regHit==0 && pAggInfo->nAccumulator ){ regHit = regAcc; @@ -129988,11 +155958,13 @@ static void updateAccumulator(Parse *pParse, int regAcc, AggInfo *pAggInfo){ addrHitTest = sqlite3VdbeAddOp1(v, OP_If, regHit); VdbeCoverage(v); } for(i=0, pC=pAggInfo->aCol; inAccumulator; i++, pC++){ - sqlite3ExprCode(pParse, pC->pExpr, pC->iMem); + sqlite3ExprCode(pParse, pC->pCExpr, AggInfoColumnReg(pAggInfo,i)); + if( pParse->nErr ) return; } + pAggInfo->directMode = 0; if( addrHitTest ){ - sqlite3VdbeJumpHere(v, addrHitTest); + sqlite3VdbeJumpHereOrPopInst(v, addrHitTest); } } @@ -130008,7 +155980,7 @@ static void explainSimpleCount( ){ if( pParse->explain==2 ){ int bCover = (pIdx!=0 && (HasRowid(pTab) || !IsPrimaryKeyIndex(pIdx))); - sqlite3VdbeExplain(pParse, 0, "SCAN TABLE %s%s%s", + sqlite3VdbeExplain(pParse, 0, "SCAN %s%s%s", pTab->zName, bCover ? " USING COVERING INDEX " : "", bCover ? pIdx->zName : "" @@ -130022,10 +155994,10 @@ static void explainSimpleCount( /* ** sqlite3WalkExpr() callback used by havingToWhere(). ** -** If the node passed to the callback is a TK_AND node, return +** If the node passed to the callback is a TK_AND node, return ** WRC_Continue to tell sqlite3WalkExpr() to iterate through child nodes. ** -** Otherwise, return WRC_Prune. In this case, also check if the +** Otherwise, return WRC_Prune. In this case, also check if the ** sub-expression matches the criteria for being moved to the WHERE ** clause. If so, add it to the WHERE clause and replace the sub-expression ** within the HAVING expression with a constant "1". @@ -130033,13 +156005,23 @@ static void explainSimpleCount( static int havingToWhereExprCb(Walker *pWalker, Expr *pExpr){ if( pExpr->op!=TK_AND ){ Select *pS = pWalker->u.pSelect; - if( sqlite3ExprIsConstantOrGroupBy(pWalker->pParse, pExpr, pS->pGroupBy) ){ + /* This routine is called before the HAVING clause of the current + ** SELECT is analyzed for aggregates. So if pExpr->pAggInfo is set + ** here, it indicates that the expression is a correlated reference to a + ** column from an outer aggregate query, or an aggregate function that + ** belongs to an outer query. Do not move the expression to the WHERE + ** clause in this obscure case, as doing so may corrupt the outer Select + ** statements AggInfo structure. */ + if( sqlite3ExprIsConstantOrGroupBy(pWalker->pParse, pExpr, pS->pGroupBy) + && ExprAlwaysFalse(pExpr)==0 + && pExpr->pAggInfo==0 + ){ sqlite3 *db = pWalker->pParse->db; - Expr *pNew = sqlite3ExprAlloc(db, TK_INTEGER, &sqlite3IntTokens[1], 0); + Expr *pNew = sqlite3ExprInt32(db, 1); if( pNew ){ Expr *pWhere = pS->pWhere; SWAP(Expr, *pNew, *pExpr); - pNew = sqlite3ExprAnd(db, pWhere, pNew); + pNew = sqlite3ExprAnd(pWalker->pParse, pWhere, pNew); pS->pWhere = pNew; pWalker->eCode = 1; } @@ -130071,38 +156053,50 @@ static void havingToWhere(Parse *pParse, Select *p){ sWalker.xExprCallback = havingToWhereExprCb; sWalker.u.pSelect = p; sqlite3WalkExpr(&sWalker, p->pHaving); -#if SELECTTRACE_ENABLED - if( sWalker.eCode && (sqlite3SelectTrace & 0x100)!=0 ){ - SELECTTRACE(0x100,pParse,p,("Move HAVING terms into WHERE:\n")); +#if TREETRACE_ENABLED + if( sWalker.eCode && (sqlite3TreeTrace & 0x100)!=0 ){ + TREETRACE(0x100,pParse,p,("Move HAVING terms into WHERE:\n")); sqlite3TreeViewSelect(0, p, 0); } #endif } /* -** Check to see if the pThis entry of pTabList is a self-join of a prior view. -** If it is, then return the SrcList_item for the prior view. If it is not, -** then return 0. +** Check to see if the pThis entry of pTabList is a self-join of another view. +** Search FROM-clause entries in the range of iFirst..iEnd, including iFirst +** but stopping before iEnd. +** +** If pThis is a self-join, then return the SrcItem for the first other +** instance of that view found. If pThis is not a self-join then return 0. */ -static struct SrcList_item *isSelfJoinView( +static SrcItem *isSelfJoinView( SrcList *pTabList, /* Search for self-joins in this FROM clause */ - struct SrcList_item *pThis /* Search for prior reference to this subquery */ + SrcItem *pThis, /* Search for prior reference to this subquery */ + int iFirst, int iEnd /* Range of FROM-clause entries to search. */ ){ - struct SrcList_item *pItem; - for(pItem = pTabList->a; pItemfg.isSubquery ); + pSel = pThis->u4.pSubq->pSelect; + assert( pSel!=0 ); + if( pSel->selFlags & SF_PushDown ) return 0; + while( iFirstpSelect==0 ) continue; + pItem = &pTabList->a[iFirst++]; + if( !pItem->fg.isSubquery ) continue; if( pItem->fg.viaCoroutine ) continue; if( pItem->zName==0 ) continue; - if( sqlite3_stricmp(pItem->zDatabase, pThis->zDatabase)!=0 ) continue; + assert( pItem->pSTab!=0 ); + assert( pThis->pSTab!=0 ); + if( pItem->pSTab->pSchema!=pThis->pSTab->pSchema ) continue; if( sqlite3_stricmp(pItem->zName, pThis->zName)!=0 ) continue; - pS1 = pItem->pSelect; - if( pThis->pSelect->selId!=pS1->selId ){ + pS1 = pItem->u4.pSubq->pSelect; + if( pItem->pSTab->pSchema==0 && pSel->selId!=pS1->selId ){ /* The query flattener left two different CTE tables with identical ** names in the same FROM clause. */ continue; } - if( sqlite3ExprCompare(0, pThis->pSelect->pWhere, pS1->pWhere, -1) ){ + if( pS1->selFlags & SF_PushDown ){ /* The view was modified by some other optimization such as ** pushDownWhereTerms() */ continue; @@ -130112,7 +156106,16 @@ static struct SrcList_item *isSelfJoinView( return 0; } -#ifdef SQLITE_COUNTOFVIEW_OPTIMIZATION +/* +** Deallocate a single AggInfo object +*/ +static void agginfoFree(sqlite3 *db, void *pArg){ + AggInfo *p = (AggInfo*)pArg; + sqlite3DbFree(db, p->aCol); + sqlite3DbFree(db, p->aFunc); + sqlite3DbFreeNN(db, p); +} + /* ** Attempt to transform a query of the form ** @@ -130127,7 +156130,9 @@ static struct SrcList_item *isSelfJoinView( ** * The subquery is a UNION ALL of two or more terms ** * The subquery does not have a LIMIT clause ** * There is no WHERE or GROUP BY or HAVING clauses on the subqueries -** * The outer query is a simple count(*) +** * The outer query is a simple count(*) with no WHERE clause or other +** extraneous syntax. +** * None of the subqueries are DISTINCT (forumpost/a860f5fb2e 2025-03-10) ** ** Return TRUE if the optimization is undertaken. */ @@ -130136,21 +156141,36 @@ static int countOfViewOptimization(Parse *pParse, Select *p){ Expr *pExpr; Expr *pCount; sqlite3 *db; + SrcItem *pFrom; if( (p->selFlags & SF_Aggregate)==0 ) return 0; /* This is an aggregate */ if( p->pEList->nExpr!=1 ) return 0; /* Single result column */ + if( p->pWhere ) return 0; + if( p->pHaving ) return 0; + if( p->pGroupBy ) return 0; + if( p->pOrderBy ) return 0; pExpr = p->pEList->a[0].pExpr; if( pExpr->op!=TK_AGG_FUNCTION ) return 0; /* Result is an aggregate */ + assert( ExprUseUToken(pExpr) ); if( sqlite3_stricmp(pExpr->u.zToken,"count") ) return 0; /* Is count() */ + assert( ExprUseXList(pExpr) ); if( pExpr->x.pList!=0 ) return 0; /* Must be count(*) */ if( p->pSrc->nSrc!=1 ) return 0; /* One table in FROM */ - pSub = p->pSrc->a[0].pSelect; - if( pSub==0 ) return 0; /* The FROM is a subquery */ - if( pSub->pPrior==0 ) return 0; /* Must be a compound ry */ + if( ExprHasProperty(pExpr, EP_WinFunc) ) return 0;/* Not a window function */ + pFrom = p->pSrc->a; + if( pFrom->fg.isSubquery==0 ) return 0; /* FROM is a subquery */ + pSub = pFrom->u4.pSubq->pSelect; + if( pSub->pPrior==0 ) return 0; /* Must be a compound */ + if( pSub->selFlags & SF_CopyCte ) return 0; /* Not a CTE */ do{ if( pSub->op!=TK_ALL && pSub->pPrior ) return 0; /* Must be UNION ALL */ if( pSub->pWhere ) return 0; /* No WHERE clause */ if( pSub->pLimit ) return 0; /* No LIMIT clause */ - if( pSub->selFlags & SF_Aggregate ) return 0; /* Not an aggregate */ + if( pSub->selFlags & (SF_Aggregate|SF_Distinct) ){ + testcase( pSub->selFlags & SF_Aggregate ); + testcase( pSub->selFlags & SF_Distinct ); + return 0; /* Not an aggregate nor DISTINCT */ + } + assert( pSub->pHaving==0 ); /* Due to the previous */ pSub = pSub->pPrior; /* Repeat over compound */ }while( pSub ); @@ -130159,19 +156179,18 @@ static int countOfViewOptimization(Parse *pParse, Select *p){ db = pParse->db; pCount = pExpr; pExpr = 0; - pSub = p->pSrc->a[0].pSelect; - p->pSrc->a[0].pSelect = 0; + pSub = sqlite3SubqueryDetach(db, pFrom); sqlite3SrcListDelete(db, p->pSrc); - p->pSrc = sqlite3DbMallocZero(pParse->db, sizeof(*p->pSrc)); + p->pSrc = sqlite3DbMallocZero(pParse->db, SZ_SRCLIST_1); while( pSub ){ Expr *pTerm; pPrior = pSub->pPrior; pSub->pPrior = 0; pSub->pNext = 0; pSub->selFlags |= SF_Aggregate; - pSub->selFlags &= ~SF_Compound; + pSub->selFlags &= ~(u32)SF_Compound; pSub->nSelectRow = 0; - sqlite3ExprListDelete(db, pSub->pEList); + sqlite3ParserAddCleanup(pParse, sqlite3ExprListDeleteGeneric, pSub->pEList); pTerm = pPrior ? sqlite3ExprDup(db, pCount, 0) : pCount; pSub->pEList = sqlite3ExprListAppend(pParse, 0, pTerm); pTerm = sqlite3PExpr(pParse, TK_SELECT, 0, 0); @@ -130184,20 +156203,347 @@ static int countOfViewOptimization(Parse *pParse, Select *p){ pSub = pPrior; } p->pEList->a[0].pExpr = pExpr; - p->selFlags &= ~SF_Aggregate; + p->selFlags &= ~(u32)SF_Aggregate; -#if SELECTTRACE_ENABLED - if( sqlite3SelectTrace & 0x400 ){ - SELECTTRACE(0x400,pParse,p,("After count-of-view optimization:\n")); +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x200 ){ + TREETRACE(0x200,pParse,p,("After count-of-view optimization:\n")); sqlite3TreeViewSelect(0, p, 0); } #endif return 1; } -#endif /* SQLITE_COUNTOFVIEW_OPTIMIZATION */ /* -** Generate code for the SELECT statement given in the p argument. +** If any term of pSrc, or any SF_NestedFrom sub-query, is not the same +** as pSrcItem but has the same alias as p0, then return true. +** Otherwise return false. +*/ +static int sameSrcAlias(SrcItem *p0, SrcList *pSrc){ + int i; + for(i=0; inSrc; i++){ + SrcItem *p1 = &pSrc->a[i]; + if( p1==p0 ) continue; + if( p0->pSTab==p1->pSTab && 0==sqlite3_stricmp(p0->zAlias, p1->zAlias) ){ + return 1; + } + if( p1->fg.isSubquery + && (p1->u4.pSubq->pSelect->selFlags & SF_NestedFrom)!=0 + && sameSrcAlias(p0, p1->u4.pSubq->pSelect->pSrc) + ){ + return 1; + } + } + return 0; +} + +/* +** Return TRUE (non-zero) if the i-th entry in the pTabList SrcList can +** be implemented as a co-routine. The i-th entry is guaranteed to be +** a subquery. +** +** The subquery is implemented as a co-routine if all of the following are +** true: +** +** (1) The subquery will likely be implemented in the outer loop of +** the query. This will be the case if any one of the following +** conditions hold: +** (a) The subquery is the only term in the FROM clause +** (b) The subquery is the left-most term and a CROSS JOIN or similar +** requires it to be the outer loop +** (c) All of the following are true: +** (i) The subquery is the left-most subquery in the FROM clause +** (ii) There is nothing that would prevent the subquery from +** being used as the outer loop if the sqlite3WhereBegin() +** routine nominates it to that position. +** (iii) The query is not a UPDATE ... FROM +** (2) The subquery is not a CTE that should be materialized because +** (a) the AS MATERIALIZED keyword is used, or +** (b) the CTE is used multiple times and does not have the +** NOT MATERIALIZED keyword +** (3) The subquery is not part of a left operand for a RIGHT JOIN +** (4) The SQLITE_Coroutine optimization disable flag is not set +** (5) The subquery is not self-joined +*/ +static int fromClauseTermCanBeCoroutine( + Parse *pParse, /* Parsing context */ + SrcList *pTabList, /* FROM clause */ + int i, /* Which term of the FROM clause holds the subquery */ + int selFlags /* Flags on the SELECT statement */ +){ + SrcItem *pItem = &pTabList->a[i]; + if( pItem->fg.isCte ){ + const CteUse *pCteUse = pItem->u2.pCteUse; + if( pCteUse->eM10d==M10d_Yes ) return 0; /* (2a) */ + if( pCteUse->nUse>=2 && pCteUse->eM10d!=M10d_No ) return 0; /* (2b) */ + } + if( pTabList->a[0].fg.jointype & JT_LTORJ ) return 0; /* (3) */ + if( OptimizationDisabled(pParse->db, SQLITE_Coroutines) ) return 0; /* (4) */ + if( isSelfJoinView(pTabList, pItem, i+1, pTabList->nSrc)!=0 ){ + return 0; /* (5) */ + } + if( i==0 ){ + if( pTabList->nSrc==1 ) return 1; /* (1a) */ + if( pTabList->a[1].fg.jointype & JT_CROSS ) return 1; /* (1b) */ + if( selFlags & SF_UpdateFrom ) return 0; /* (1c-iii) */ + return 1; + } + if( selFlags & SF_UpdateFrom ) return 0; /* (1c-iii) */ + while( 1 /*exit-by-break*/ ){ + if( pItem->fg.jointype & (JT_OUTER|JT_CROSS) ) return 0; /* (1c-ii) */ + if( i==0 ) break; + i--; + pItem--; + if( pItem->fg.isSubquery ) return 0; /* (1c-i) */ + } + return 1; +} + +/* +** Argument pWhere is the WHERE clause belonging to SELECT statement p. This +** function attempts to transform expressions of the form: +** +** EXISTS (SELECT ...) +** +** into joins. For example, given +** +** CREATE TABLE sailors(sid INTEGER PRIMARY KEY, name TEXT); +** CREATE TABLE reserves(sid INT, day DATE, PRIMARY KEY(sid, day)); +** +** SELECT name FROM sailors AS S WHERE EXISTS ( +** SELECT * FROM reserves AS R WHERE S.sid = R.sid AND R.day = '2022-10-25' +** ); +** +** the SELECT statement may be transformed as follows: +** +** SELECT name FROM sailors AS S, reserves AS R +** WHERE S.sid = R.sid AND R.day = '2022-10-25'; +** +** **Approximately**. Really, we have to ensure that the FROM-clause term +** that was formerly inside the EXISTS is only executed once. This is handled +** by setting the SrcItem.fg.fromExists flag, which then causes code in +** the where.c file to exit the corresponding loop after the first successful +** match (if any). +*/ +static SQLITE_NOINLINE void existsToJoin( + Parse *pParse, /* Parsing context */ + Select *p, /* The SELECT statement being optimized */ + Expr *pWhere /* part of the WHERE clause currently being examined */ +){ + if( pParse->nErr==0 + && pWhere!=0 + && !ExprHasProperty(pWhere, EP_OuterON|EP_InnerON) + && ALWAYS(p->pSrc!=0) + && p->pSrc->nSrcpLimit==0 || p->pLimit->pRight==0) + ){ + if( pWhere->op==TK_AND ){ + Expr *pRight = pWhere->pRight; + existsToJoin(pParse, p, pWhere->pLeft); + existsToJoin(pParse, p, pRight); + } + else if( pWhere->op==TK_EXISTS ){ + Select *pSub = pWhere->x.pSelect; + Expr *pSubWhere = pSub->pWhere; + if( pSub->pSrc->nSrc==1 + && (pSub->selFlags & SF_Aggregate)==0 + && !pSub->pSrc->a[0].fg.isSubquery + && pSub->pLimit==0 + && pSub->pPrior==0 + ){ + /* Before combining the sub-select with the parent, renumber the + ** cursor used by the subselect. This is because the EXISTS expression + ** might be a copy of another EXISTS expression from somewhere + ** else in the tree, and in this case it is important that it use + ** a unique cursor number. */ + sqlite3 *db = pParse->db; + int *aCsrMap = sqlite3DbMallocZero(db, (pParse->nTab+2)*sizeof(int)); + if( aCsrMap==0 ) return; + aCsrMap[0] = (pParse->nTab+1); + renumberCursors(pParse, pSub, -1, aCsrMap); + sqlite3DbFree(db, aCsrMap); + + memset(pWhere, 0, sizeof(*pWhere)); + pWhere->op = TK_INTEGER; + pWhere->u.iValue = 1; + ExprSetProperty(pWhere, EP_IntValue); + assert( p->pWhere!=0 ); + pSub->pSrc->a[0].fg.fromExists = 1; + p->pSrc = sqlite3SrcListAppendList(pParse, p->pSrc, pSub->pSrc); + if( pSubWhere ){ + p->pWhere = sqlite3PExpr(pParse, TK_AND, p->pWhere, pSubWhere); + pSub->pWhere = 0; + } + pSub->pSrc = 0; + sqlite3ParserAddCleanup(pParse, sqlite3SelectDeleteGeneric, pSub); +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x100000 ){ + TREETRACE(0x100000,pParse,p, + ("After EXISTS-to-JOIN optimization:\n")); + sqlite3TreeViewSelect(0, p, 0); + } +#endif + } + } + } +} + +/* +** Type used for Walker callbacks by selectCheckOnClauses(). +*/ +typedef struct CheckOnCtx CheckOnCtx; +struct CheckOnCtx { + SrcList *pSrc; /* SrcList for this context */ + int iJoin; /* Cursor numbers must be =< than this */ + int bFuncArg; /* True for table-function arg */ + CheckOnCtx *pParent; /* Parent context */ +}; + +/* +** True if the SrcList passed as the only argument contains at least +** one RIGHT or FULL JOIN. False otherwise. +*/ +#define hasRightJoin(pSrc) (((pSrc)->a[0].fg.jointype & JT_LTORJ)!=0) + +/* +** The xExpr callback for the search of invalid ON clause terms. +*/ +static int selectCheckOnClausesExpr(Walker *pWalker, Expr *pExpr){ + CheckOnCtx *pCtx = pWalker->u.pCheckOnCtx; + + /* Check if pExpr is root or near-root of an ON clause constraint that needs + ** to be checked to ensure that it does not refer to tables in its FROM + ** clause to the right of itself. i.e. it is either: + ** + ** + an ON clause on an OUTER join, or + ** + an ON clause on an INNER join within a FROM that features at + ** least one RIGHT or FULL join. + */ + if( (ExprHasProperty(pExpr, EP_OuterON)) + || (ExprHasProperty(pExpr, EP_InnerON) && hasRightJoin(pCtx->pSrc)) + ){ + /* If CheckOnCtx.iJoin is already set, then fall through and process + ** this expression node as normal. Or, if CheckOnCtx.iJoin is still 0, + ** set it to the cursor number of the RHS of the join to which this + ** ON expression was attached and then iterate through the entire + ** expression. */ + assert( pCtx->iJoin==0 || pCtx->iJoin==pExpr->w.iJoin ); + if( pCtx->iJoin==0 ){ + pCtx->iJoin = pExpr->w.iJoin; + sqlite3WalkExprNN(pWalker, pExpr); + pCtx->iJoin = 0; + return WRC_Prune; + } + } + + if( pExpr->op==TK_COLUMN ){ + /* A column expression. Find the SrcList (if any) to which it refers. + ** Then, if CheckOnCtx.iJoin indicates that this expression is part of an + ** ON clause from that SrcList (i.e. if iJoin is non-zero), check that it + ** does not refer to a table to the right of CheckOnCtx.iJoin. */ + do { + SrcList *pSrc = pCtx->pSrc; + int nSrc = pSrc->nSrc; + int iTab = pExpr->iTable; + int ii; + for(ii=0; iia[ii].iCursor!=iTab; ii++){} + if( iiiJoin && iTab>pCtx->iJoin ){ + sqlite3ErrorMsg(pWalker->pParse, + "%s references tables to its right", + (pCtx->bFuncArg ? "table-function argument" : "ON clause") + ); + return WRC_Abort; + } + break; + } + pCtx = pCtx->pParent; + }while( pCtx ); + } + return WRC_Continue; +} + +/* +** The xSelect callback for the search of invalid ON clause terms. +*/ +static int selectCheckOnClausesSelect(Walker *pWalker, Select *pSelect){ + CheckOnCtx *pCtx = pWalker->u.pCheckOnCtx; + if( pSelect->pSrc==pCtx->pSrc || pSelect->pSrc->nSrc==0 ){ + return WRC_Continue; + }else{ + CheckOnCtx sCtx; + memset(&sCtx, 0, sizeof(sCtx)); + sCtx.pSrc = pSelect->pSrc; + sCtx.pParent = pCtx; + pWalker->u.pCheckOnCtx = &sCtx; + sqlite3WalkSelect(pWalker, pSelect); + pWalker->u.pCheckOnCtx = pCtx; + pSelect->selFlags &= ~SF_OnToWhere; + return WRC_Prune; + } +} + +/* +** Check all ON clauses in pSelect to verify that they do not reference +** columns to the right. +*/ +SQLITE_PRIVATE void sqlite3SelectCheckOnClauses(Parse *pParse, Select *pSelect){ + Walker w; + CheckOnCtx sCtx; + int ii; + assert( pSelect->selFlags & SF_OnToWhere ); + assert( pSelect->pSrc!=0 && pSelect->pSrc->nSrc>=2 ); + memset(&w, 0, sizeof(w)); + w.pParse = pParse; + w.xExprCallback = selectCheckOnClausesExpr; + w.xSelectCallback = selectCheckOnClausesSelect; + w.u.pCheckOnCtx = &sCtx; + memset(&sCtx, 0, sizeof(sCtx)); + sCtx.pSrc = pSelect->pSrc; + sqlite3WalkExpr(&w, pSelect->pWhere); + pSelect->selFlags &= ~SF_OnToWhere; + + /* Check for any table-function args that are attached to virtual tables + ** on the RHS of an outer join. They are subject to the same constraints + ** as ON clauses. */ + sCtx.bFuncArg = 1; + for(ii=0; iipSrc->nSrc; ii++){ + SrcItem *pItem = &pSelect->pSrc->a[ii]; + if( pItem->fg.isTabFunc + && (pItem->fg.jointype & JT_OUTER) + ){ + sCtx.iJoin = pItem->iCursor; + sqlite3WalkExprList(&w, pItem->u1.pFuncArg); + } + } +} + +/* +** If p2 exists and p1 and p2 have the same number of terms, then change +** every term of p1 to have the same sort order as p2 and return true. +** +** If p2 is NULL or p1 and p2 are different lengths, then make no changes +** and return false. +** +** p1 must be non-NULL. +*/ +static int sqlite3CopySortOrder(ExprList *p1, ExprList *p2){ + assert( p1 ); + if( p2 && p1->nExpr==p2->nExpr ){ + int ii; + for(ii=0; iinExpr; ii++){ + u8 sortFlags; + sortFlags = p2->a[ii].fg.sortFlags & KEYINFO_ORDER_DESC; + p1->a[ii].fg.sortFlags = sortFlags; + } + return 1; + }else{ + return 0; + } +} + +/* +** Generate byte-code for the SELECT statement given in the p argument. ** ** The results are returned according to the SelectDest structure. ** See comments in sqliteInt.h for further information. @@ -130208,6 +156554,40 @@ static int countOfViewOptimization(Parse *pParse, Select *p){ ** ** This routine does NOT free the Select structure passed in. The ** calling function needs to do that. +** +** This is a long function. The following is an outline of the processing +** steps, with tags referencing various milestones: +** +** * Resolve names and similar preparation tag-select-0100 +** * Scan of the FROM clause tag-select-0200 +** + OUTER JOIN strength reduction tag-select-0220 +** + Sub-query ORDER BY removal tag-select-0230 +** + Query flattening tag-select-0240 +** * Separate subroutine for compound-SELECT tag-select-0300 +** * WHERE-clause constant propagation tag-select-0330 +** * Count()-of-VIEW optimization tag-select-0350 +** * Scan of the FROM clause again tag-select-0400 +** + Authorize unreferenced tables tag-select-0410 +** + Predicate push-down optimization tag-select-0420 +** + Omit unused subquery columns optimization tag-select-0440 +** + Generate code to implement subqueries tag-select-0480 +** - Co-routines tag-select-0482 +** - Reuse previously computed CTE tag-select-0484 +** - REuse previously computed VIEW tag-select-0486 +** - Materialize a VIEW or CTE tag-select-0488 +** * DISTINCT ORDER BY -> GROUP BY optimization tag-select-0500 +** * Set up for ORDER BY tag-select-0600 +** * Create output table tag-select-0630 +** * Prepare registers for LIMIT tag-select-0650 +** * Setup for DISTINCT tag-select-0680 +** * Generate code for non-aggregate and non-GROUP BY tag-select-0700 +** * Generate code for aggregate and/or GROUP BY tag-select-0800 +** + GROUP BY queries tag-select-0810 +** + non-GROUP BY queries tag-select-0820 +** - Special case of count() w/o GROUP BY tag-select-0821 +** - General case of non-GROUP BY aggregates tag-select-0822 +** * Sort results, as needed tag-select-0900 +** * Internal self-checks tag-select-1000 */ SQLITE_PRIVATE int sqlite3Select( Parse *pParse, /* The parser context */ @@ -130223,67 +156603,108 @@ SQLITE_PRIVATE int sqlite3Select( Expr *pWhere; /* The WHERE clause. May be NULL */ ExprList *pGroupBy; /* The GROUP BY clause. May be NULL */ Expr *pHaving; /* The HAVING clause. May be NULL */ + AggInfo *pAggInfo = 0; /* Aggregate information */ int rc = 1; /* Value to return from this function */ DistinctCtx sDistinct; /* Info on how to code the DISTINCT keyword */ SortCtx sSort; /* Info on how to code the ORDER BY clause */ - AggInfo sAggInfo; /* Information used by aggregate queries */ int iEnd; /* Address of the end of the query */ sqlite3 *db; /* The database connection */ ExprList *pMinMaxOrderBy = 0; /* Added ORDER BY for min/max queries */ u8 minMaxFlag; /* Flag for min/max queries */ db = pParse->db; + assert( pParse==db->pParse ); v = sqlite3GetVdbe(pParse); - if( p==0 || db->mallocFailed || pParse->nErr ){ + if( p==0 || pParse->nErr ){ return 1; } + assert( db->mallocFailed==0 ); if( sqlite3AuthCheck(pParse, SQLITE_SELECT, 0, 0, 0) ) return 1; - memset(&sAggInfo, 0, sizeof(sAggInfo)); -#if SELECTTRACE_ENABLED - SELECTTRACE(1,pParse,p, ("begin processing:\n", pParse->addrExplain)); - if( sqlite3SelectTrace & 0x100 ){ - sqlite3TreeViewSelect(0, p, 0); +#if TREETRACE_ENABLED + TREETRACE(0x1,pParse,p, ("begin processing:\n", pParse->addrExplain)); + if( sqlite3TreeTrace & 0x10000 ){ + if( (sqlite3TreeTrace & 0x10001)==0x10000 ){ + sqlite3TreeViewLine(0, "In sqlite3Select() at %s:%d", + __FILE__, __LINE__); + } + sqlite3ShowSelect(p); } #endif + /* tag-select-0100 */ assert( p->pOrderBy==0 || pDest->eDest!=SRT_DistFifo ); assert( p->pOrderBy==0 || pDest->eDest!=SRT_Fifo ); assert( p->pOrderBy==0 || pDest->eDest!=SRT_DistQueue ); assert( p->pOrderBy==0 || pDest->eDest!=SRT_Queue ); - if( IgnorableOrderby(pDest) ){ - assert(pDest->eDest==SRT_Exists || pDest->eDest==SRT_Union || - pDest->eDest==SRT_Except || pDest->eDest==SRT_Discard || - pDest->eDest==SRT_Queue || pDest->eDest==SRT_DistFifo || - pDest->eDest==SRT_DistQueue || pDest->eDest==SRT_Fifo); - /* If ORDER BY makes no difference in the output then neither does - ** DISTINCT so it can be removed too. */ - sqlite3ExprListDelete(db, p->pOrderBy); - p->pOrderBy = 0; - p->selFlags &= ~SF_Distinct; + if( IgnorableDistinct(pDest) ){ + assert(pDest->eDest==SRT_Exists || pDest->eDest==SRT_Discard || + pDest->eDest==SRT_DistQueue || pDest->eDest==SRT_DistFifo ); + /* All of these destinations are also able to ignore the ORDER BY clause */ + if( p->pOrderBy ){ +#if TREETRACE_ENABLED + TREETRACE(0x800,pParse,p, ("dropping superfluous ORDER BY:\n")); + if( sqlite3TreeTrace & 0x800 ){ + sqlite3TreeViewExprList(0, p->pOrderBy, 0, "ORDERBY"); + } +#endif + sqlite3ParserAddCleanup(pParse, sqlite3ExprListDeleteGeneric, + p->pOrderBy); + testcase( pParse->earlyCleanup ); + p->pOrderBy = 0; + } + p->selFlags &= ~(u32)SF_Distinct; } sqlite3SelectPrep(pParse, p, 0); - if( pParse->nErr || db->mallocFailed ){ + if( pParse->nErr ){ goto select_end; } + assert( db->mallocFailed==0 ); assert( p->pEList!=0 ); -#if SELECTTRACE_ENABLED - if( sqlite3SelectTrace & 0x104 ){ - SELECTTRACE(0x104,pParse,p, ("after name resolution:\n")); +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x10 ){ + TREETRACE(0x10,pParse,p, ("after name resolution:\n")); sqlite3TreeViewSelect(0, p, 0); } #endif + /* If the SF_UFSrcCheck flag is set, then this function is being called + ** as part of populating the temp table for an UPDATE...FROM statement. + ** In this case, it is an error if the target object (pSrc->a[0]) name + ** or alias is duplicated within FROM clause (pSrc->a[1..n]). + ** + ** Postgres disallows this case too. The reason is that some other + ** systems handle this case differently, and not all the same way, + ** which is just confusing. To avoid this, we follow PG's lead and + ** disallow it altogether. */ + if( p->selFlags & SF_UFSrcCheck ){ + SrcItem *p0 = &p->pSrc->a[0]; + if( sameSrcAlias(p0, p->pSrc) ){ + sqlite3ErrorMsg(pParse, + "target object/alias may not appear in FROM clause: %s", + p0->zAlias ? p0->zAlias : p0->pSTab->zName + ); + goto select_end; + } + + /* Clear the SF_UFSrcCheck flag. The check has already been performed, + ** and leaving this flag set can cause errors if a compound sub-query + ** in p->pSrc is flattened into this query and this function called + ** again as part of compound SELECT processing. */ + p->selFlags &= ~(u32)SF_UFSrcCheck; + } + if( pDest->eDest==SRT_Output ){ - generateColumnNames(pParse, p); + sqlite3GenerateColumnNames(pParse, p); } #ifndef SQLITE_OMIT_WINDOWFUNC if( sqlite3WindowRewrite(pParse, p) ){ + assert( pParse->nErr ); goto select_end; } -#if SELECTTRACE_ENABLED - if( sqlite3SelectTrace & 0x108 ){ - SELECTTRACE(0x104,pParse,p, ("after window rewrite:\n")); +#if TREETRACE_ENABLED + if( p->pWin && (sqlite3TreeTrace & 0x40)!=0 ){ + TREETRACE(0x40,pParse,p, ("after window rewrite:\n")); sqlite3TreeViewSelect(0, p, 0); } #endif @@ -130293,29 +156714,74 @@ SQLITE_PRIVATE int sqlite3Select( memset(&sSort, 0, sizeof(sSort)); sSort.pOrderBy = p->pOrderBy; - /* Try to various optimizations (flattening subqueries, and strength + /* Try to do various optimizations (flattening subqueries, and strength ** reduction of join operators) in the FROM clause up into the main query + ** tag-select-0200 */ #if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) for(i=0; !p->pPrior && inSrc; i++){ - struct SrcList_item *pItem = &pTabList->a[i]; - Select *pSub = pItem->pSelect; - Table *pTab = pItem->pTab; + SrcItem *pItem = &pTabList->a[i]; + Select *pSub = pItem->fg.isSubquery ? pItem->u4.pSubq->pSelect : 0; + Table *pTab = pItem->pSTab; - /* Convert LEFT JOIN into JOIN if there are terms of the right table - ** of the LEFT JOIN used in the WHERE clause. + /* The expander should have already created transient Table objects + ** even for FROM clause elements such as subqueries that do not correspond + ** to a real table */ + assert( pTab!=0 ); + + /* Try to simplify joins: + ** + ** LEFT JOIN -> JOIN + ** RIGHT JOIN -> JOIN + ** FULL JOIN -> RIGHT JOIN + ** + ** If terms of the i-th table are used in the WHERE clause in such a + ** way that the i-th table cannot be the NULL row of a join, then + ** perform the appropriate simplification. This is called + ** "OUTER JOIN strength reduction" in the SQLite documentation. + ** tag-select-0220 */ - if( (pItem->fg.jointype & JT_LEFT)!=0 - && sqlite3ExprImpliesNonNullRow(p->pWhere, pItem->iCursor) + if( (pItem->fg.jointype & (JT_LEFT|JT_LTORJ))!=0 + && sqlite3ExprImpliesNonNullRow(p->pWhere, pItem->iCursor, + pItem->fg.jointype & JT_LTORJ) && OptimizationEnabled(db, SQLITE_SimplifyJoin) ){ - SELECTTRACE(0x100,pParse,p, - ("LEFT-JOIN simplifies to JOIN on term %d\n",i)); - pItem->fg.jointype &= ~(JT_LEFT|JT_OUTER); - unsetJoinExpr(p->pWhere, pItem->iCursor); + if( pItem->fg.jointype & JT_LEFT ){ + if( pItem->fg.jointype & JT_RIGHT ){ + TREETRACE(0x1000,pParse,p, + ("FULL-JOIN simplifies to RIGHT-JOIN on term %d\n",i)); + pItem->fg.jointype &= ~JT_LEFT; + }else{ + TREETRACE(0x1000,pParse,p, + ("LEFT-JOIN simplifies to JOIN on term %d\n",i)); + pItem->fg.jointype &= ~(JT_LEFT|JT_OUTER); + unsetJoinExpr(p->pWhere, pItem->iCursor, 0); + } + } + if( pItem->fg.jointype & JT_LTORJ ){ + for(j=i+1; jnSrc; j++){ + SrcItem *pI2 = &pTabList->a[j]; + if( pI2->fg.jointype & JT_RIGHT ){ + if( pI2->fg.jointype & JT_LEFT ){ + TREETRACE(0x1000,pParse,p, + ("FULL-JOIN simplifies to LEFT-JOIN on term %d\n",j)); + pI2->fg.jointype &= ~JT_RIGHT; + }else{ + TREETRACE(0x1000,pParse,p, + ("RIGHT-JOIN simplifies to JOIN on term %d\n",j)); + pI2->fg.jointype &= ~(JT_RIGHT|JT_OUTER); + unsetJoinExpr(p->pWhere, pI2->iCursor, 1); + } + } + } + for(j=pTabList->nSrc-1; j>=0; j--){ + pTabList->a[j].fg.jointype &= ~JT_LTORJ; + if( pTabList->a[j].fg.jointype & JT_RIGHT ) break; + } + } } - /* No futher action if this term of the FROM clause is no a subquery */ + /* No further action if this term of the FROM clause is not a subquery */ if( pSub==0 ) continue; /* Catch mismatch in the declared columns of a view and the number of @@ -130326,6 +156792,14 @@ SQLITE_PRIVATE int sqlite3Select( goto select_end; } + /* Do not attempt the usual optimizations (flattening and ORDER BY + ** elimination) on a MATERIALIZED common table expression because + ** a MATERIALIZED common table expression is an optimization fence. + */ + if( pItem->fg.isCte && pItem->u2.pCteUse->eM10d==M10d_Yes ){ + continue; + } + /* Do not try to flatten an aggregate subquery. ** ** Flattening an aggregate subquery is only possible if the outer query @@ -130336,6 +156810,46 @@ SQLITE_PRIVATE int sqlite3Select( if( (pSub->selFlags & SF_Aggregate)!=0 ) continue; assert( pSub->pGroupBy==0 ); + /* tag-select-0230: + ** If a FROM-clause subquery has an ORDER BY clause that is not + ** really doing anything, then delete it now so that it does not + ** interfere with query flattening. See the discussion at + ** https://sqlite.org/forum/forumpost/2d76f2bcf65d256a + ** + ** Beware of these cases where the ORDER BY clause may not be safely + ** omitted: + ** + ** (1) There is also a LIMIT clause + ** (2) The subquery was added to help with window-function + ** processing + ** (3) The subquery is in the FROM clause of an UPDATE + ** (4) The outer query uses an aggregate function other than + ** the built-in count(), min(), or max(). + ** (5) The ORDER BY isn't going to accomplish anything because + ** one of: + ** (a) The outer query has a different ORDER BY clause + ** (b) The subquery is part of a join + ** See forum post 062d576715d277c8 + ** (6) The subquery is not a recursive CTE. ORDER BY has a different + ** meaning for recursive CTEs and this optimization does not + ** apply. + ** + ** Also retain the ORDER BY if the OmitOrderBy optimization is disabled. + */ + if( pSub->pOrderBy!=0 + && (p->pOrderBy!=0 || pTabList->nSrc>1) /* Condition (5) */ + && pSub->pLimit==0 /* Condition (1) */ + && (pSub->selFlags & (SF_OrderByReqd|SF_Recursive))==0 /* (2) and (6) */ + && (p->selFlags & SF_OrderByReqd)==0 /* Condition (3) and (4) */ + && OptimizationEnabled(db, SQLITE_OmitOrderBy) + ){ + TREETRACE(0x800,pParse,p, + ("omit superfluous ORDER BY on %r FROM-clause subquery\n",i+1)); + sqlite3ParserAddCleanup(pParse, sqlite3ExprListDeleteGeneric, + pSub->pOrderBy); + pSub->pOrderBy = 0; + } + /* If the outer query contains a "complex" result set (that is, ** if the result set of the outer query uses functions or subqueries) ** and if the subquery contains an ORDER BY clause and if @@ -130358,11 +156872,12 @@ SQLITE_PRIVATE int sqlite3Select( && i==0 && (p->selFlags & SF_ComplexResult)!=0 && (pTabList->nSrc==1 - || (pTabList->a[1].fg.jointype&(JT_LEFT|JT_CROSS))!=0) + || (pTabList->a[1].fg.jointype&(JT_OUTER|JT_CROSS))!=0) ){ continue; } + /* tag-select-0240 */ if( flattenSubquery(pParse, p, i, isAgg) ){ if( pParse->nErr ) goto select_end; /* This subquery can be absorbed into its parent. */ @@ -130378,13 +156893,13 @@ SQLITE_PRIVATE int sqlite3Select( #ifndef SQLITE_OMIT_COMPOUND_SELECT /* Handle compound SELECT statements using the separate multiSelect() - ** procedure. + ** procedure. tag-select-0300 */ if( p->pPrior ){ rc = multiSelect(pParse, p, pDest); -#if SELECTTRACE_ENABLED - SELECTTRACE(0x1,pParse,p,("end compound-select processing\n")); - if( (sqlite3SelectTrace & 0x2000)!=0 && ExplainQueryPlanParent(pParse)==0 ){ +#if TREETRACE_ENABLED + TREETRACE(0x400,pParse,p,("end compound-select processing\n")); + if( (sqlite3TreeTrace & 0x400)!=0 && ExplainQueryPlanParent(pParse)==0 ){ sqlite3TreeViewSelect(0, p, 0); } #endif @@ -130393,48 +156908,61 @@ SQLITE_PRIVATE int sqlite3Select( } #endif + /* If there may be an "EXISTS (SELECT ...)" in the WHERE clause, attempt + ** to change it into a join. */ + if( pParse->bHasExists && OptimizationEnabled(db,SQLITE_ExistsToJoin) ){ + existsToJoin(pParse, p, p->pWhere); + pTabList = p->pSrc; + } + /* Do the WHERE-clause constant propagation optimization if this is - ** a join. No need to speed time on this operation for non-join queries + ** a join. No need to spend time on this operation for non-join queries ** as the equivalent optimization will be handled by query planner in - ** sqlite3WhereBegin(). + ** sqlite3WhereBegin(). tag-select-0330 */ - if( pTabList->nSrc>1 + if( p->pWhere!=0 + && p->pWhere->op==TK_AND && OptimizationEnabled(db, SQLITE_PropagateConst) && propagateConstants(pParse, p) ){ -#if SELECTTRACE_ENABLED - if( sqlite3SelectTrace & 0x100 ){ - SELECTTRACE(0x100,pParse,p,("After constant propagation:\n")); +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x2000 ){ + TREETRACE(0x2000,pParse,p,("After constant propagation:\n")); sqlite3TreeViewSelect(0, p, 0); } #endif }else{ - SELECTTRACE(0x100,pParse,p,("Constant propagation not helpful\n")); + TREETRACE(0x2000,pParse,p,("Constant propagation not helpful\n")); } -#ifdef SQLITE_COUNTOFVIEW_OPTIMIZATION + /* tag-select-0350 */ if( OptimizationEnabled(db, SQLITE_QueryFlattener|SQLITE_CountOfView) && countOfViewOptimization(pParse, p) ){ if( db->mallocFailed ) goto select_end; - pEList = p->pEList; pTabList = p->pSrc; } -#endif - /* For each term in the FROM clause, do two things: - ** (1) Authorized unreferenced tables - ** (2) Generate code for all sub-queries + /* Loop over all terms in the FROM clause and do two things for each term: + ** + ** (1) Authorize unreferenced tables + ** (2) Generate code for all sub-queries + ** + ** tag-select-0400 */ for(i=0; inSrc; i++){ - struct SrcList_item *pItem = &pTabList->a[i]; + SrcItem *pItem = &pTabList->a[i]; + SrcItem *pPrior; SelectDest dest; + Subquery *pSubq; Select *pSub; #if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) const char *zSavedAuthContext; #endif - /* Issue SQLITE_READ authorizations with a fake column name for any + /* Authorized unreferenced tables. tag-select-0410 + ** + ** Issue SQLITE_READ authorizations with a fake column name for any ** tables that are referenced but from which no values are extracted. ** Examples of where these kinds of null SQLITE_READ authorizations ** would occur: @@ -130450,22 +156978,29 @@ SQLITE_PRIVATE int sqlite3Select( ** assume the column name is non-NULL and segfault. The use of an empty ** string for the fake column name seems safer. */ - if( pItem->colUsed==0 ){ - sqlite3AuthCheck(pParse, SQLITE_READ, pItem->zName, "", pItem->zDatabase); + if( pItem->colUsed==0 && pItem->zName!=0 ){ + const char *zDb; + if( pItem->fg.fixedSchema ){ + int iDb = sqlite3SchemaToIndex(pParse->db, pItem->u4.pSchema); + zDb = db->aDb[iDb].zDbSName; + }else if( pItem->fg.isSubquery ){ + zDb = 0; + }else{ + zDb = pItem->u4.zDatabase; + } + sqlite3AuthCheck(pParse, SQLITE_READ, pItem->zName, "", zDb); } #if !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) /* Generate code for all sub-queries in the FROM clause */ - pSub = pItem->pSelect; - if( pSub==0 ) continue; + if( pItem->fg.isSubquery==0 ) continue; + pSubq = pItem->u4.pSubq; + assert( pSubq!=0 ); + pSub = pSubq->pSelect; - /* The code for a subquery should only be generated once, though it is - ** technically harmless for it to be generated multiple times. The - ** following assert() will detect if something changes to cause - ** the same subquery to be coded multiple times, as a signal to the - ** developers to try to optimize the situation. */ - assert( pItem->addrFillSub==0 ); + /* The code for a subquery should only be generated once. */ + if( pSubq->addrFillSub!=0 ) continue; /* Increment Parse.nHeight by the height of the largest expression ** tree referred to by this, the parent select. The child select @@ -130478,96 +157013,133 @@ SQLITE_PRIVATE int sqlite3Select( /* Make copies of constant WHERE-clause terms in the outer query down ** inside the subquery. This can help the subquery to run more efficiently. + ** This is the "predicate push-down optimization". tag-select-0420 */ if( OptimizationEnabled(db, SQLITE_PushDown) - && pushDownWhereTerms(pParse, pSub, p->pWhere, pItem->iCursor, - (pItem->fg.jointype & JT_OUTER)!=0) + && (pItem->fg.isCte==0 + || (pItem->u2.pCteUse->eM10d!=M10d_Yes && pItem->u2.pCteUse->nUse<2)) + && pushDownWhereTerms(pParse, pSub, p->pWhere, pTabList, i) ){ -#if SELECTTRACE_ENABLED - if( sqlite3SelectTrace & 0x100 ){ - SELECTTRACE(0x100,pParse,p, +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x4000 ){ + TREETRACE(0x4000,pParse,p, ("After WHERE-clause push-down into subquery %d:\n", pSub->selId)); sqlite3TreeViewSelect(0, p, 0); } #endif + assert( pSubq->pSelect && (pSub->selFlags & SF_PushDown)!=0 ); }else{ - SELECTTRACE(0x100,pParse,p,("Push-down not possible\n")); + TREETRACE(0x4000,pParse,p,("WHERE-clause push-down not possible\n")); + } + + /* Convert unused result columns of the subquery into simple NULL + ** expressions, to avoid unneeded searching and computation. + ** tag-select-0440 + */ + if( OptimizationEnabled(db, SQLITE_NullUnusedCols) + && disableUnusedSubqueryResultColumns(pItem) + ){ +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x4000 ){ + TREETRACE(0x4000,pParse,p, + ("Change unused result columns to NULL for subquery %d:\n", + pSub->selId)); + sqlite3TreeViewSelect(0, p, 0); + } +#endif } zSavedAuthContext = pParse->zAuthContext; pParse->zAuthContext = pItem->zName; - /* Generate code to implement the subquery - ** - ** The subquery is implemented as a co-routine if the subquery is - ** guaranteed to be the outer loop (so that it does not need to be - ** computed more than once) - ** - ** TODO: Are there other reasons beside (1) to use a co-routine - ** implementation? + /* Generate byte-code to implement the subquery tag-select-0480 */ - if( i==0 - && (pTabList->nSrc==1 - || (pTabList->a[1].fg.jointype&(JT_LEFT|JT_CROSS))!=0) /* (1) */ - ){ + if( fromClauseTermCanBeCoroutine(pParse, pTabList, i, p->selFlags) ){ /* Implement a co-routine that will return a single row of the result - ** set on each invocation. + ** set on each invocation. tag-select-0482 */ int addrTop = sqlite3VdbeCurrentAddr(v)+1; - - pItem->regReturn = ++pParse->nMem; - sqlite3VdbeAddOp3(v, OP_InitCoroutine, pItem->regReturn, 0, addrTop); - VdbeComment((v, "%s", pItem->pTab->zName)); - pItem->addrFillSub = addrTop; - sqlite3SelectDestInit(&dest, SRT_Coroutine, pItem->regReturn); - ExplainQueryPlan((pParse, 1, "CO-ROUTINE %u", pSub->selId)); + + pSubq->regReturn = ++pParse->nMem; + sqlite3VdbeAddOp3(v, OP_InitCoroutine, pSubq->regReturn, 0, addrTop); + VdbeComment((v, "%!S", pItem)); + pSubq->addrFillSub = addrTop; + sqlite3SelectDestInit(&dest, SRT_Coroutine, pSubq->regReturn); + ExplainQueryPlan((pParse, 1, "CO-ROUTINE %!S", pItem)); sqlite3Select(pParse, pSub, &dest); - pItem->pTab->nRowLogEst = pSub->nSelectRow; + pItem->pSTab->nRowLogEst = pSub->nSelectRow; pItem->fg.viaCoroutine = 1; - pItem->regResult = dest.iSdst; - sqlite3VdbeEndCoroutine(v, pItem->regReturn); + pSubq->regResult = dest.iSdst; + sqlite3VdbeEndCoroutine(v, pSubq->regReturn); + VdbeComment((v, "end %!S", pItem)); sqlite3VdbeJumpHere(v, addrTop-1); sqlite3ClearTempRegCache(pParse); + }else if( pItem->fg.isCte && pItem->u2.pCteUse->addrM9e>0 ){ + /* This is a CTE for which materialization code has already been + ** generated. Invoke the subroutine to compute the materialization, + ** then make the pItem->iCursor be a copy of the ephemeral table that + ** holds the result of the materialization. tag-select-0484 */ + CteUse *pCteUse = pItem->u2.pCteUse; + sqlite3VdbeAddOp2(v, OP_Gosub, pCteUse->regRtn, pCteUse->addrM9e); + if( pItem->iCursor!=pCteUse->iCur ){ + sqlite3VdbeAddOp2(v, OP_OpenDup, pItem->iCursor, pCteUse->iCur); + VdbeComment((v, "%!S", pItem)); + } + pSub->nSelectRow = pCteUse->nRowEst; + }else if( (pPrior = isSelfJoinView(pTabList, pItem, 0, i))!=0 ){ + /* This view has already been materialized by a prior entry in + ** this same FROM clause. Reuse it. tag-select-0486 */ + Subquery *pPriorSubq; + assert( pPrior->fg.isSubquery ); + pPriorSubq = pPrior->u4.pSubq; + assert( pPriorSubq!=0 ); + if( pPriorSubq->addrFillSub ){ + sqlite3VdbeAddOp2(v, OP_Gosub, pPriorSubq->regReturn, + pPriorSubq->addrFillSub); + } + sqlite3VdbeAddOp2(v, OP_OpenDup, pItem->iCursor, pPrior->iCursor); + pSub->nSelectRow = pPriorSubq->pSelect->nSelectRow; }else{ - /* Generate a subroutine that will fill an ephemeral table with - ** the content of this subquery. pItem->addrFillSub will point - ** to the address of the generated subroutine. pItem->regReturn - ** is a register allocated to hold the subroutine return address - */ + /* Materialize the view. If the view is not correlated, generate a + ** subroutine to do the materialization so that subsequent uses of + ** the same view can reuse the materialization. tag-select-0488 */ int topAddr; int onceAddr = 0; - int retAddr; - struct SrcList_item *pPrior; +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + int addrExplain; +#endif - assert( pItem->addrFillSub==0 ); - pItem->regReturn = ++pParse->nMem; - topAddr = sqlite3VdbeAddOp2(v, OP_Integer, 0, pItem->regReturn); - pItem->addrFillSub = topAddr+1; + pSubq->regReturn = ++pParse->nMem; + topAddr = sqlite3VdbeAddOp0(v, OP_Goto); + pSubq->addrFillSub = topAddr+1; + pItem->fg.isMaterialized = 1; if( pItem->fg.isCorrelated==0 ){ /* If the subquery is not correlated and if we are not inside of ** a trigger, then we only need to compute the value of the subquery ** once. */ onceAddr = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); - VdbeComment((v, "materialize \"%s\"", pItem->pTab->zName)); - }else{ - VdbeNoopComment((v, "materialize \"%s\"", pItem->pTab->zName)); - } - pPrior = isSelfJoinView(pTabList, pItem); - if( pPrior ){ - sqlite3VdbeAddOp2(v, OP_OpenDup, pItem->iCursor, pPrior->iCursor); - assert( pPrior->pSelect!=0 ); - pSub->nSelectRow = pPrior->pSelect->nSelectRow; + VdbeComment((v, "materialize %!S", pItem)); }else{ - sqlite3SelectDestInit(&dest, SRT_EphemTab, pItem->iCursor); - ExplainQueryPlan((pParse, 1, "MATERIALIZE %u", pSub->selId)); - sqlite3Select(pParse, pSub, &dest); + VdbeNoopComment((v, "materialize %!S", pItem)); } - pItem->pTab->nRowLogEst = pSub->nSelectRow; + sqlite3SelectDestInit(&dest, SRT_EphemTab, pItem->iCursor); + + ExplainQueryPlan2(addrExplain, (pParse, 1, "MATERIALIZE %!S", pItem)); + sqlite3Select(pParse, pSub, &dest); + pItem->pSTab->nRowLogEst = pSub->nSelectRow; if( onceAddr ) sqlite3VdbeJumpHere(v, onceAddr); - retAddr = sqlite3VdbeAddOp1(v, OP_Return, pItem->regReturn); - VdbeComment((v, "end %s", pItem->pTab->zName)); - sqlite3VdbeChangeP1(v, topAddr, retAddr); + sqlite3VdbeAddOp2(v, OP_Return, pSubq->regReturn, topAddr+1); + VdbeComment((v, "end %!S", pItem)); + sqlite3VdbeScanStatusRange(v, addrExplain, addrExplain, -1); + sqlite3VdbeJumpHere(v, topAddr); sqlite3ClearTempRegCache(pParse); + if( pItem->fg.isCte && pItem->fg.isCorrelated==0 ){ + CteUse *pCteUse = pItem->u2.pCteUse; + pCteUse->addrM9e = pSubq->addrFillSub; + pCteUse->regRtn = pSubq->regReturn; + pCteUse->iCur = pItem->iCursor; + pCteUse->nRowEst = pSub->nSelectRow; + } } if( db->mallocFailed ) goto select_end; pParse->nHeight -= sqlite3SelectExprHeight(p); @@ -130583,14 +157155,16 @@ SQLITE_PRIVATE int sqlite3Select( pHaving = p->pHaving; sDistinct.isTnct = (p->selFlags & SF_Distinct)!=0; -#if SELECTTRACE_ENABLED - if( sqlite3SelectTrace & 0x400 ){ - SELECTTRACE(0x400,pParse,p,("After all FROM-clause analysis:\n")); +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x8000 ){ + TREETRACE(0x8000,pParse,p,("After all FROM-clause analysis:\n")); sqlite3TreeViewSelect(0, p, 0); } #endif - /* If the query is DISTINCT with an ORDER BY but is not an aggregate, and + /* tag-select-0500 + ** + ** If the query is DISTINCT with an ORDER BY but is not an aggregate, and ** if the select-list is the same as the ORDER BY list, then this query ** can be rewritten as a GROUP BY. In other words, this: ** @@ -130600,24 +157174,36 @@ SQLITE_PRIVATE int sqlite3Select( ** ** SELECT xyz FROM ... GROUP BY xyz ORDER BY xyz ** - ** The second form is preferred as a single index (or temp-table) may be - ** used for both the ORDER BY and DISTINCT processing. As originally - ** written the query must use a temp-table for at least one of the ORDER + ** The second form is preferred as a single index (or temp-table) may be + ** used for both the ORDER BY and DISTINCT processing. As originally + ** written the query must use a temp-table for at least one of the ORDER ** BY and DISTINCT, and an index or separate temp-table for the other. */ - if( (p->selFlags & (SF_Distinct|SF_Aggregate))==SF_Distinct - && sqlite3ExprListCompare(sSort.pOrderBy, pEList, -1)==0 + if( (p->selFlags & (SF_Distinct|SF_Aggregate))==SF_Distinct + && sqlite3CopySortOrder(pEList, sSort.pOrderBy) + && sqlite3ExprListCompare(pEList, sSort.pOrderBy, -1)==0 + && OptimizationEnabled(db, SQLITE_GroupByOrder) +#ifndef SQLITE_OMIT_WINDOWFUNC + && p->pWin==0 +#endif ){ - p->selFlags &= ~SF_Distinct; + p->selFlags &= ~(u32)SF_Distinct; pGroupBy = p->pGroupBy = sqlite3ExprListDup(db, pEList, 0); + if( pGroupBy ){ + for(i=0; inExpr; i++){ + pGroupBy->a[i].u.x.iOrderByCol = i+1; + } + } + p->selFlags |= SF_Aggregate; /* Notice that even thought SF_Distinct has been cleared from p->selFlags, ** the sDistinct.isTnct is still set. Hence, isTnct represents the ** original setting of the SF_Distinct flag, not the current setting */ assert( sDistinct.isTnct ); + sDistinct.isTnct = 2; -#if SELECTTRACE_ENABLED - if( sqlite3SelectTrace & 0x400 ){ - SELECTTRACE(0x400,pParse,p,("Transform DISTINCT into GROUP BY:\n")); +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x20000 ){ + TREETRACE(0x20000,pParse,p,("Transform DISTINCT into GROUP BY:\n")); sqlite3TreeViewSelect(0, p, 0); } #endif @@ -130629,7 +157215,7 @@ SQLITE_PRIVATE int sqlite3Select( ** If that is the case, then the OP_OpenEphemeral instruction will be ** changed to an OP_Noop once we figure out that the sorting index is ** not needed. The sSort.addrSortIndex variable is used to facilitate - ** that change. + ** that change. tag-select-0600 */ if( sSort.pOrderBy ){ KeyInfo *pKeyInfo; @@ -130646,24 +157232,37 @@ SQLITE_PRIVATE int sqlite3Select( } /* If the output is destined for a temporary table, open that table. + ** tag-select-0630 */ if( pDest->eDest==SRT_EphemTab ){ sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pDest->iSDParm, pEList->nExpr); + if( p->selFlags & SF_NestedFrom ){ + /* Delete or NULL-out result columns that will never be used */ + int ii; + for(ii=pEList->nExpr-1; ii>0 && pEList->a[ii].fg.bUsed==0; ii--){ + sqlite3ExprDelete(db, pEList->a[ii].pExpr); + sqlite3DbFree(db, pEList->a[ii].zEName); + pEList->nExpr--; + } + for(ii=0; iinExpr; ii++){ + if( pEList->a[ii].fg.bUsed==0 ) pEList->a[ii].pExpr->op = TK_NULL; + } + } } - /* Set the limiter. + /* Set the limiter. tag-select-0650 */ iEnd = sqlite3VdbeMakeLabel(pParse); if( (p->selFlags & SF_FixedLimit)==0 ){ p->nSelectRow = 320; /* 4 billion rows */ } - computeLimitRegisters(pParse, p, iEnd); + if( p->pLimit ) computeLimitRegisters(pParse, p, iEnd); if( p->iLimit==0 && sSort.addrSortIndex>=0 ){ sqlite3VdbeChangeOpcode(v, sSort.addrSortIndex, OP_SorterOpen); sSort.sortFlags |= SORTFLAG_UseSorter; } - /* Open an ephemeral index to use for the distinct set. + /* Open an ephemeral index to use for the distinct set. tag-select-0680 */ if( p->selFlags & SF_Distinct ){ sDistinct.tabTnct = pParse->nTab++; @@ -130678,25 +157277,31 @@ SQLITE_PRIVATE int sqlite3Select( } if( !isAgg && pGroupBy==0 ){ - /* No aggregate functions and no GROUP BY clause */ + /* No aggregate functions and no GROUP BY clause. tag-select-0700 */ u16 wctrlFlags = (sDistinct.isTnct ? WHERE_WANT_DISTINCT : 0) | (p->selFlags & SF_FixedLimit); #ifndef SQLITE_OMIT_WINDOWFUNC - Window *pWin = p->pWin; /* Master window object (or NULL) */ + Window *pWin = p->pWin; /* Main window object (or NULL) */ if( pWin ){ - sqlite3WindowCodeInit(pParse, pWin); + sqlite3WindowCodeInit(pParse, p); } #endif assert( WHERE_USE_LIMIT==SF_FixedLimit ); /* Begin the database scan. */ - SELECTTRACE(1,pParse,p,("WhereBegin\n")); + TREETRACE(0x2,pParse,p,("WhereBegin\n")); pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, sSort.pOrderBy, - p->pEList, wctrlFlags, p->nSelectRow); + p->pEList, p, wctrlFlags, p->nSelectRow); if( pWInfo==0 ) goto select_end; if( sqlite3WhereOutputRowCount(pWInfo) < p->nSelectRow ){ p->nSelectRow = sqlite3WhereOutputRowCount(pWInfo); + if( pDest->eDest<=SRT_DistQueue && pDest->eDest>=SRT_DistFifo ){ + /* TUNING: For a UNION CTE, because UNION is implies DISTINCT, + ** reduce the estimated output row count by 8 (LogEst 30). + ** Search for tag-20250414a to see other cases */ + p->nSelectRow -= 30; + } } if( sDistinct.isTnct && sqlite3WhereIsDistinct(pWInfo) ){ sDistinct.eTnctType = sqlite3WhereIsDistinct(pWInfo); @@ -130708,8 +157313,9 @@ SQLITE_PRIVATE int sqlite3Select( sSort.pOrderBy = 0; } } + TREETRACE(0x2,pParse,p,("WhereBegin returns\n")); - /* If sorting index that was created by a prior OP_OpenEphemeral + /* If sorting index that was created by a prior OP_OpenEphemeral ** instruction ended up not being needed, then change the OP_OpenEphemeral ** into an OP_Noop. */ @@ -130746,11 +157352,12 @@ SQLITE_PRIVATE int sqlite3Select( /* End the database scan loop. */ + TREETRACE(0x2,pParse,p,("WhereEnd\n")); sqlite3WhereEnd(pWInfo); } }else{ - /* This case when there exist aggregate functions or a GROUP BY clause - ** or both */ + /* This case is for when there exist aggregate functions or a GROUP BY + ** clause or both. tag-select-0800 */ NameContext sNC; /* Name context for processing aggregate information */ int iAMem; /* First Mem address for storing current GROUP BY */ int iBMem; /* First Mem address for previous GROUP BY */ @@ -130779,23 +157386,25 @@ SQLITE_PRIVATE int sqlite3Select( } assert( 66==sqlite3LogEst(100) ); if( p->nSelectRow>66 ) p->nSelectRow = 66; + + /* If there is both a GROUP BY and an ORDER BY clause and they are + ** identical, then it may be possible to disable the ORDER BY clause + ** on the grounds that the GROUP BY will cause elements to come out + ** in the correct order. It also may not - the GROUP BY might use a + ** database index that causes rows to be grouped together as required + ** but not actually sorted. Either way, record the fact that the + ** ORDER BY and GROUP BY clauses are the same by setting the orderByGrp + ** variable. */ + if( sqlite3CopySortOrder(pGroupBy, sSort.pOrderBy) + && sqlite3ExprListCompare(pGroupBy, sSort.pOrderBy, -1)==0 + ){ + orderByGrp = 1; + } }else{ assert( 0==sqlite3LogEst(1) ); p->nSelectRow = 0; } - /* If there is both a GROUP BY and an ORDER BY clause and they are - ** identical, then it may be possible to disable the ORDER BY clause - ** on the grounds that the GROUP BY will cause elements to come out - ** in the correct order. It also may not - the GROUP BY might use a - ** database index that causes rows to be grouped together as required - ** but not actually sorted. Either way, record the fact that the - ** ORDER BY and GROUP BY clauses are the same by setting the orderByGrp - ** variable. */ - if( sqlite3ExprListCompare(pGroupBy, sSort.pOrderBy, -1)==0 ){ - orderByGrp = 1; - } - /* Create a label to jump to when we want to abort the query */ addrEnd = sqlite3VdbeMakeLabel(pParse); @@ -130803,14 +157412,25 @@ SQLITE_PRIVATE int sqlite3Select( ** sAggInfo for all TK_AGG_FUNCTION nodes in expressions of the ** SELECT statement. */ + pAggInfo = sqlite3DbMallocZero(db, sizeof(*pAggInfo) ); + if( pAggInfo ){ + sqlite3ParserAddCleanup(pParse, agginfoFree, pAggInfo); + testcase( pParse->earlyCleanup ); + } + if( db->mallocFailed ){ + goto select_end; + } + pAggInfo->selId = p->selId; +#ifdef SQLITE_DEBUG + pAggInfo->pSelect = p; +#endif memset(&sNC, 0, sizeof(sNC)); sNC.pParse = pParse; sNC.pSrcList = pTabList; - sNC.uNC.pAggInfo = &sAggInfo; + sNC.uNC.pAggInfo = pAggInfo; VVA_ONLY( sNC.ncFlags = NC_UAggInfo; ) - sAggInfo.mnReg = pParse->nMem+1; - sAggInfo.nSortingColumn = pGroupBy ? pGroupBy->nExpr : 0; - sAggInfo.pGroupBy = pGroupBy; + pAggInfo->nSortingColumn = pGroupBy ? pGroupBy->nExpr : 0; + pAggInfo->pGroupBy = pGroupBy; sqlite3ExprAnalyzeAggList(&sNC, pEList); sqlite3ExprAnalyzeAggList(&sNC, sSort.pOrderBy); if( pHaving ){ @@ -130823,45 +157443,33 @@ SQLITE_PRIVATE int sqlite3Select( } sqlite3ExprAnalyzeAggregates(&sNC, pHaving); } - sAggInfo.nAccumulator = sAggInfo.nColumn; - if( p->pGroupBy==0 && p->pHaving==0 && sAggInfo.nFunc==1 ){ - minMaxFlag = minMaxQuery(db, sAggInfo.aFunc[0].pExpr, &pMinMaxOrderBy); + pAggInfo->nAccumulator = pAggInfo->nColumn; + if( p->pGroupBy==0 && p->pHaving==0 && pAggInfo->nFunc==1 ){ + minMaxFlag = minMaxQuery(db, pAggInfo->aFunc[0].pFExpr, &pMinMaxOrderBy); }else{ minMaxFlag = WHERE_ORDERBY_NORMAL; } - for(i=0; ix.pList); - sNC.ncFlags &= ~NC_InAggFunc; - } - sAggInfo.mxReg = pParse->nMem; + analyzeAggFuncArgs(pAggInfo, &sNC); if( db->mallocFailed ) goto select_end; -#if SELECTTRACE_ENABLED - if( sqlite3SelectTrace & 0x400 ){ - int ii; - SELECTTRACE(0x400,pParse,p,("After aggregate analysis:\n")); +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x20 ){ + TREETRACE(0x20,pParse,p,("After aggregate analysis %p:\n", pAggInfo)); sqlite3TreeViewSelect(0, p, 0); - for(ii=0; iinFunc==1 + && pAggInfo->aFunc[0].iDistinct>=0 + && ALWAYS(pAggInfo->aFunc[0].pFExpr!=0) + && ALWAYS(ExprUseXList(pAggInfo->aFunc[0].pFExpr)) + && pAggInfo->aFunc[0].pFExpr->x.pList!=0 + ){ + Expr *pExpr = pAggInfo->aFunc[0].pFExpr->x.pList->a[0].pExpr; + pExpr = sqlite3ExprDup(db, pExpr, 0); + pDistinct = sqlite3ExprListDup(db, pGroupBy, 0); + pDistinct = sqlite3ExprListAppend(pParse, pDistinct, pExpr); + distFlag = pDistinct ? (WHERE_WANT_DISTINCT|WHERE_AGG_DISTINCT) : 0; + } /* If there is a GROUP BY clause we might need a sorting index to ** implement it. Allocate that sorting index now. If it turns out ** that we do not need it after all, the OP_SorterOpen instruction - ** will be converted into a Noop. + ** will be converted into a Noop. */ - sAggInfo.sortingIdx = pParse->nTab++; - pKeyInfo = sqlite3KeyInfoFromExprList(pParse,pGroupBy,0,sAggInfo.nColumn); - addrSortingIdx = sqlite3VdbeAddOp4(v, OP_SorterOpen, - sAggInfo.sortingIdx, sAggInfo.nSortingColumn, + pAggInfo->sortingIdx = pParse->nTab++; + pKeyInfo = sqlite3KeyInfoFromExprList(pParse, pGroupBy, + 0, pAggInfo->nColumn); + addrSortingIdx = sqlite3VdbeAddOp4(v, OP_SorterOpen, + pAggInfo->sortingIdx, pAggInfo->nSortingColumn, 0, (char*)pKeyInfo, P4_KEYINFO); /* Initialize memory locations used by GROUP BY aggregate processing @@ -130896,6 +157521,7 @@ SQLITE_PRIVATE int sqlite3Select( sqlite3VdbeAddOp2(v, OP_Integer, 0, iAbortFlag); VdbeComment((v, "clear abort flag")); sqlite3VdbeAddOp3(v, OP_Null, 0, iAMem, iAMem+pGroupBy->nExpr-1); + sqlite3ExprNullRegisterRange(pParse, iAMem, pGroupBy->nExpr); /* Begin a loop that will extract all source rows in GROUP BY order. ** This might involve two separate loops with an OP_Sort in between, or @@ -130903,11 +157529,21 @@ SQLITE_PRIVATE int sqlite3Select( ** in the right order to begin with. */ sqlite3VdbeAddOp2(v, OP_Gosub, regReset, addrReset); - SELECTTRACE(1,pParse,p,("WhereBegin\n")); - pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, pGroupBy, 0, - WHERE_GROUPBY | (orderByGrp ? WHERE_SORTBYGROUP : 0), 0 + TREETRACE(0x2,pParse,p,("WhereBegin\n")); + pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, pGroupBy, pDistinct, + p, (sDistinct.isTnct==2 ? WHERE_DISTINCTBY : WHERE_GROUPBY) + | (orderByGrp ? WHERE_SORTBYGROUP : 0) | distFlag, 0 ); - if( pWInfo==0 ) goto select_end; + if( pWInfo==0 ){ + sqlite3ExprListDelete(db, pDistinct); + goto select_end; + } + if( pParse->pIdxEpr ){ + optimizeAggregateUseOfIndexedExpr(pParse, p, pAggInfo, &sNC); + } + assignAggregateRegisters(pParse, pAggInfo); + eDist = sqlite3WhereIsDistinct(pWInfo); + TREETRACE(0x2,pParse,p,("WhereBegin returns\n")); if( sqlite3WhereIsOrdered(pWInfo)==pGroupBy->nExpr ){ /* The optimizer is able to deliver rows in group by order so ** we do not have to sort. The OP_OpenEphemeral table will be @@ -130925,16 +157561,20 @@ SQLITE_PRIVATE int sqlite3Select( int nCol; int nGroupBy; - explainTempTable(pParse, +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + int addrExp; /* Address of OP_Explain instruction */ +#endif + ExplainQueryPlan2(addrExp, (pParse, 0, "USE TEMP B-TREE FOR %s", (sDistinct.isTnct && (p->selFlags&SF_Distinct)==0) ? - "DISTINCT" : "GROUP BY"); + "DISTINCT" : "GROUP BY" + )); groupBySort = 1; nGroupBy = pGroupBy->nExpr; nCol = nGroupBy; j = nGroupBy; - for(i=0; i=j ){ + for(i=0; inColumn; i++){ + if( pAggInfo->aCol[i].iSorterColumn>=j ){ nCol++; j++; } @@ -130942,27 +157582,50 @@ SQLITE_PRIVATE int sqlite3Select( regBase = sqlite3GetTempRange(pParse, nCol); sqlite3ExprCodeExprList(pParse, pGroupBy, regBase, 0, 0); j = nGroupBy; - for(i=0; idirectMode = 1; + for(i=0; inColumn; i++){ + struct AggInfo_col *pCol = &pAggInfo->aCol[i]; if( pCol->iSorterColumn>=j ){ - int r1 = j + regBase; - sqlite3ExprCodeGetColumnOfTable(v, - pCol->pTab, pCol->iTable, pCol->iColumn, r1); + sqlite3ExprCode(pParse, pCol->pCExpr, j + regBase); j++; } } + pAggInfo->directMode = 0; regRecord = sqlite3GetTempReg(pParse); + sqlite3VdbeScanStatusCounters(v, addrExp, 0, sqlite3VdbeCurrentAddr(v)); sqlite3VdbeAddOp3(v, OP_MakeRecord, regBase, nCol, regRecord); - sqlite3VdbeAddOp2(v, OP_SorterInsert, sAggInfo.sortingIdx, regRecord); + sqlite3VdbeAddOp2(v, OP_SorterInsert, pAggInfo->sortingIdx, regRecord); + sqlite3VdbeScanStatusRange(v, addrExp, sqlite3VdbeCurrentAddr(v)-2, -1); sqlite3ReleaseTempReg(pParse, regRecord); sqlite3ReleaseTempRange(pParse, regBase, nCol); + TREETRACE(0x2,pParse,p,("WhereEnd\n")); sqlite3WhereEnd(pWInfo); - sAggInfo.sortingIdxPTab = sortPTab = pParse->nTab++; + pAggInfo->sortingIdxPTab = sortPTab = pParse->nTab++; sortOut = sqlite3GetTempReg(pParse); + sqlite3VdbeScanStatusCounters(v, addrExp, sqlite3VdbeCurrentAddr(v), 0); sqlite3VdbeAddOp3(v, OP_OpenPseudo, sortPTab, sortOut, nCol); - sqlite3VdbeAddOp2(v, OP_SorterSort, sAggInfo.sortingIdx, addrEnd); + sqlite3VdbeAddOp2(v, OP_SorterSort, pAggInfo->sortingIdx, addrEnd); VdbeComment((v, "GROUP BY sort")); VdbeCoverage(v); - sAggInfo.useSortingIdx = 1; + pAggInfo->useSortingIdx = 1; + sqlite3VdbeScanStatusRange(v, addrExp, -1, sortPTab); + sqlite3VdbeScanStatusRange(v, addrExp, -1, pAggInfo->sortingIdx); + } + + /* If there are entries in pAgggInfo->aFunc[] that contain subexpressions + ** that are indexed (and that were previously identified and tagged + ** in optimizeAggregateUseOfIndexedExpr()) then those subexpressions + ** must now be converted into a TK_AGG_COLUMN node so that the value + ** is correctly pulled from the index rather than being recomputed. */ + if( pParse->pIdxEpr ){ + aggregateConvertIndexedExprRefToColumn(pAggInfo); +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x20 ){ + TREETRACE(0x20, pParse, p, + ("AggInfo function expressions converted to reference index\n")); + sqlite3TreeViewSelect(0, p, 0); + printAggInfo(pAggInfo); + } +#endif } /* If the index or temporary table used by the GROUP BY sort @@ -130970,9 +157633,9 @@ SQLITE_PRIVATE int sqlite3Select( ** clause, cancel the ephemeral table open coded earlier. ** ** This is an optimization - the correct answer should result regardless. - ** Use the SQLITE_GroupByOrder flag with SQLITE_TESTCTRL_OPTIMIZER to + ** Use the SQLITE_GroupByOrder flag with SQLITE_TESTCTRL_OPTIMIZER to ** disable this optimization for testing purposes. */ - if( orderByGrp && OptimizationEnabled(db, SQLITE_GroupByOrder) + if( orderByGrp && OptimizationEnabled(db, SQLITE_GroupByOrder) && (groupBySort || sqlite3WhereIsSorted(pWInfo)) ){ sSort.pOrderBy = 0; @@ -130986,16 +157649,33 @@ SQLITE_PRIVATE int sqlite3Select( */ addrTopOfLoop = sqlite3VdbeCurrentAddr(v); if( groupBySort ){ - sqlite3VdbeAddOp3(v, OP_SorterData, sAggInfo.sortingIdx, + sqlite3VdbeAddOp3(v, OP_SorterData, pAggInfo->sortingIdx, sortOut, sortPTab); } for(j=0; jnExpr; j++){ + int iOrderByCol = pGroupBy->a[j].u.x.iOrderByCol; + if( groupBySort ){ sqlite3VdbeAddOp3(v, OP_Column, sortPTab, j, iBMem+j); }else{ - sAggInfo.directMode = 1; + pAggInfo->directMode = 1; sqlite3ExprCode(pParse, pGroupBy->a[j].pExpr, iBMem+j); } + + if( iOrderByCol ){ + Expr *pX = p->pEList->a[iOrderByCol-1].pExpr; + Expr *pBase = sqlite3ExprSkipCollateAndLikely(pX); + while( ALWAYS(pBase!=0) && pBase->op==TK_IF_NULL_ROW ){ + pX = pBase->pLeft; + pBase = sqlite3ExprSkipCollateAndLikely(pX); + } + if( ALWAYS(pBase!=0) + && pBase->op!=TK_AGG_COLUMN + && pBase->op!=TK_REGISTER + ){ + sqlite3ExprToRegister(pX, iAMem+j); + } + } } sqlite3VdbeAddOp4(v, OP_Compare, iAMem, iBMem, pGroupBy->nExpr, (char*)sqlite3KeyInfoRef(pKeyInfo), P4_KEYINFO); @@ -131011,36 +157691,38 @@ SQLITE_PRIVATE int sqlite3Select( ** and resets the aggregate accumulator registers in preparation ** for the next GROUP BY batch. */ - sqlite3ExprCodeMove(pParse, iBMem, iAMem, pGroupBy->nExpr); sqlite3VdbeAddOp2(v, OP_Gosub, regOutputRow, addrOutputRow); - VdbeComment((v, "output one row")); + VdbeComment((v, "output one row of %d", p->selId)); + sqlite3ExprCodeMove(pParse, iBMem, iAMem, pGroupBy->nExpr); sqlite3VdbeAddOp2(v, OP_IfPos, iAbortFlag, addrEnd); VdbeCoverage(v); VdbeComment((v, "check abort flag")); sqlite3VdbeAddOp2(v, OP_Gosub, regReset, addrReset); - VdbeComment((v, "reset accumulator")); + VdbeComment((v, "reset accumulator %d", p->selId)); /* Update the aggregate accumulators based on the content of ** the current row */ sqlite3VdbeJumpHere(v, addr1); - updateAccumulator(pParse, iUseFlag, &sAggInfo); + updateAccumulator(pParse, iUseFlag, pAggInfo, eDist); sqlite3VdbeAddOp2(v, OP_Integer, 1, iUseFlag); - VdbeComment((v, "indicate data in accumulator")); + VdbeComment((v, "indicate data in accumulator %d", p->selId)); /* End of the loop */ if( groupBySort ){ - sqlite3VdbeAddOp2(v, OP_SorterNext, sAggInfo.sortingIdx, addrTopOfLoop); + sqlite3VdbeAddOp2(v, OP_SorterNext, pAggInfo->sortingIdx,addrTopOfLoop); VdbeCoverage(v); }else{ + TREETRACE(0x2,pParse,p,("WhereEnd\n")); sqlite3WhereEnd(pWInfo); sqlite3VdbeChangeToNoop(v, addrSortingIdx); } + sqlite3ExprListDelete(db, pDistinct); /* Output the final row of result */ sqlite3VdbeAddOp2(v, OP_Gosub, regOutputRow, addrOutputRow); - VdbeComment((v, "output final row")); + VdbeComment((v, "output final row of %d", p->selId)); /* Jump over the subroutines */ @@ -131061,30 +157743,36 @@ SQLITE_PRIVATE int sqlite3Select( addrOutputRow = sqlite3VdbeCurrentAddr(v); sqlite3VdbeAddOp2(v, OP_IfPos, iUseFlag, addrOutputRow+2); VdbeCoverage(v); - VdbeComment((v, "Groupby result generator entry point")); + VdbeComment((v, "Groupby result generator entry point %d", p->selId)); sqlite3VdbeAddOp1(v, OP_Return, regOutputRow); - finalizeAggFunctions(pParse, &sAggInfo); + finalizeAggFunctions(pParse, pAggInfo); sqlite3ExprIfFalse(pParse, pHaving, addrOutputRow+1, SQLITE_JUMPIFNULL); selectInnerLoop(pParse, p, -1, &sSort, &sDistinct, pDest, addrOutputRow+1, addrSetAbort); sqlite3VdbeAddOp1(v, OP_Return, regOutputRow); - VdbeComment((v, "end groupby result generator")); + VdbeComment((v, "end groupby result generator %d", p->selId)); /* Generate a subroutine that will reset the group-by accumulator */ sqlite3VdbeResolveLabel(v, addrReset); - resetAccumulator(pParse, &sAggInfo); + resetAccumulator(pParse, pAggInfo); sqlite3VdbeAddOp2(v, OP_Integer, 0, iUseFlag); - VdbeComment((v, "indicate accumulator empty")); + VdbeComment((v, "indicate accumulator %d empty", p->selId)); sqlite3VdbeAddOp1(v, OP_Return, regReset); - + + if( distFlag!=0 && eDist!=WHERE_DISTINCT_NOOP ){ + struct AggInfo_func *pF = &pAggInfo->aFunc[0]; + fixDistinctOpenEph(pParse, eDist, pF->iDistinct, pF->iDistAddr); + } } /* endif pGroupBy. Begin aggregate queries without GROUP BY: */ else { -#ifndef SQLITE_OMIT_BTREECOUNT + /* Aggregate functions without GROUP BY. tag-select-0820 */ Table *pTab; - if( (pTab = isSimpleCount(p, &sAggInfo))!=0 ){ - /* If isSimpleCount() returns a pointer to a Table structure, then + if( (pTab = isSimpleCount(p, pAggInfo))!=0 ){ + /* tag-select-0821 + ** + ** If isSimpleCount() returns a pointer to a Table structure, then ** the SQL statement is of the form: ** ** SELECT count(*) FROM @@ -131102,7 +157790,7 @@ SQLITE_PRIVATE int sqlite3Select( Index *pIdx; /* Iterator variable */ KeyInfo *pKeyInfo = 0; /* Keyinfo for scanned index */ Index *pBest = 0; /* Best index found so far */ - int iRoot = pTab->tnum; /* Root page of scanned b-tree */ + Pgno iRoot = pTab->tnum; /* Root page of scanned b-tree */ sqlite3CodeVerifySchema(pParse, iDb); sqlite3TableLock(pParse, iDb, pTab->tnum, 0, pTab->zName); @@ -131113,17 +157801,19 @@ SQLITE_PRIVATE int sqlite3Select( ** ** (2013-10-03) Do not count the entries in a partial index. ** - ** In practice the KeyInfo structure will not be used. It is only + ** In practice the KeyInfo structure will not be used. It is only ** passed to keep OP_OpenRead happy. */ if( !HasRowid(pTab) ) pBest = sqlite3PrimaryKeyIndex(pTab); - for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ - if( pIdx->bUnordered==0 - && pIdx->szIdxRowszTabRow - && pIdx->pPartIdxWhere==0 - && (!pBest || pIdx->szIdxRowszIdxRow) - ){ - pBest = pIdx; + if( !p->pSrc->a[0].fg.notIndexed ){ + for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + if( pIdx->bUnordered==0 + && pIdx->szIdxRowszTabRow + && pIdx->pPartIdxWhere==0 + && (!pBest || pIdx->szIdxRowszIdxRow) + ){ + pBest = pIdx; + } } } if( pBest ){ @@ -131132,39 +157822,57 @@ SQLITE_PRIVATE int sqlite3Select( } /* Open a read-only cursor, execute the OP_Count, close the cursor. */ - sqlite3VdbeAddOp4Int(v, OP_OpenRead, iCsr, iRoot, iDb, 1); + sqlite3VdbeAddOp4Int(v, OP_OpenRead, iCsr, (int)iRoot, iDb, 1); if( pKeyInfo ){ sqlite3VdbeChangeP4(v, -1, (char *)pKeyInfo, P4_KEYINFO); } - sqlite3VdbeAddOp2(v, OP_Count, iCsr, sAggInfo.aFunc[0].iMem); + assignAggregateRegisters(pParse, pAggInfo); + sqlite3VdbeAddOp2(v, OP_Count, iCsr, AggInfoFuncReg(pAggInfo,0)); sqlite3VdbeAddOp1(v, OP_Close, iCsr); explainSimpleCount(pParse, pTab, pBest); - }else -#endif /* SQLITE_OMIT_BTREECOUNT */ - { + }else{ + /* The general case of an aggregate query without GROUP BY + ** tag-select-0822 */ int regAcc = 0; /* "populate accumulators" flag */ - - /* If there are accumulator registers but no min() or max() functions, - ** allocate register regAcc. Register regAcc will contain 0 the first - ** time the inner loop runs, and 1 thereafter. The code generated - ** by updateAccumulator() only updates the accumulator registers if - ** regAcc contains 0. */ - if( sAggInfo.nAccumulator ){ - for(i=0; ifuncFlags&SQLITE_FUNC_NEEDCOLL ) break; + ExprList *pDistinct = 0; + u16 distFlag = 0; + int eDist; + + /* If there are accumulator registers but no min() or max() functions + ** without FILTER clauses, allocate register regAcc. Register regAcc + ** will contain 0 the first time the inner loop runs, and 1 thereafter. + ** The code generated by updateAccumulator() uses this to ensure + ** that the accumulator registers are (a) updated only once if + ** there are no min() or max functions or (b) always updated for the + ** first row visited by the aggregate, so that they are updated at + ** least once even if the FILTER clause means the min() or max() + ** function visits zero rows. */ + if( pAggInfo->nAccumulator ){ + for(i=0; inFunc; i++){ + if( ExprHasProperty(pAggInfo->aFunc[i].pFExpr, EP_WinFunc) ){ + continue; + } + if( pAggInfo->aFunc[i].pFunc->funcFlags&SQLITE_FUNC_NEEDCOLL ){ + break; + } } - if( i==sAggInfo.nFunc ){ + if( i==pAggInfo->nFunc ){ regAcc = ++pParse->nMem; sqlite3VdbeAddOp2(v, OP_Integer, 0, regAcc); } + }else if( pAggInfo->nFunc==1 && pAggInfo->aFunc[0].iDistinct>=0 ){ + assert( ExprUseXList(pAggInfo->aFunc[0].pFExpr) ); + pDistinct = pAggInfo->aFunc[0].pFExpr->x.pList; + distFlag = pDistinct ? (WHERE_WANT_DISTINCT|WHERE_AGG_DISTINCT) : 0; } + assignAggregateRegisters(pParse, pAggInfo); /* This case runs if the aggregate has no GROUP BY clause. The ** processing is much simpler since there is only a single row ** of output. */ assert( p->pGroupBy==0 ); - resetAccumulator(pParse, &sAggInfo); + resetAccumulator(pParse, pAggInfo); /* If this query is a candidate for the min/max optimization, then ** minMaxFlag will have been previously set to either @@ -131174,30 +157882,38 @@ SQLITE_PRIVATE int sqlite3Select( assert( minMaxFlag==WHERE_ORDERBY_NORMAL || pMinMaxOrderBy!=0 ); assert( pMinMaxOrderBy==0 || pMinMaxOrderBy->nExpr==1 ); - SELECTTRACE(1,pParse,p,("WhereBegin\n")); + TREETRACE(0x2,pParse,p,("WhereBegin\n")); pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, pMinMaxOrderBy, - 0, minMaxFlag, 0); + pDistinct, p, minMaxFlag|distFlag, 0); if( pWInfo==0 ){ goto select_end; } - updateAccumulator(pParse, regAcc, &sAggInfo); + TREETRACE(0x2,pParse,p,("WhereBegin returns\n")); + eDist = sqlite3WhereIsDistinct(pWInfo); + updateAccumulator(pParse, regAcc, pAggInfo, eDist); + if( eDist!=WHERE_DISTINCT_NOOP ){ + struct AggInfo_func *pF = pAggInfo->aFunc; + if( pF ){ + fixDistinctOpenEph(pParse, eDist, pF->iDistinct, pF->iDistAddr); + } + } + if( regAcc ) sqlite3VdbeAddOp2(v, OP_Integer, 1, regAcc); - if( sqlite3WhereIsOrdered(pWInfo)>0 ){ - sqlite3VdbeGoto(v, sqlite3WhereBreakLabel(pWInfo)); - VdbeComment((v, "%s() by index", - (minMaxFlag==WHERE_ORDERBY_MIN?"min":"max"))); + if( minMaxFlag ){ + sqlite3WhereMinMaxOptEarlyOut(v, pWInfo); } + TREETRACE(0x2,pParse,p,("WhereEnd\n")); sqlite3WhereEnd(pWInfo); - finalizeAggFunctions(pParse, &sAggInfo); + finalizeAggFunctions(pParse, pAggInfo); } sSort.pOrderBy = 0; sqlite3ExprIfFalse(pParse, pHaving, addrEnd, SQLITE_JUMPIFNULL); - selectInnerLoop(pParse, p, -1, 0, 0, + selectInnerLoop(pParse, p, -1, 0, 0, pDest, addrEnd, addrEnd); } sqlite3VdbeResolveLabel(v, addrEnd); - + } /* endif aggregate query */ if( sDistinct.eTnctType==WHERE_DISTINCT_UNORDERED ){ @@ -131205,11 +157921,9 @@ SQLITE_PRIVATE int sqlite3Select( } /* If there is an ORDER BY clause, then we need to sort the results - ** and send them to the callback one by one. + ** and send them to the callback one by one. tag-select-0900 */ if( sSort.pOrderBy ){ - explainTempTable(pParse, - sSort.nOBSat>0 ? "RIGHT PART OF ORDER BY":"ORDER BY"); assert( p->pEList==pEList ); generateSortTail(pParse, p, &sSort, pEList->nExpr, pDest); } @@ -131226,12 +157940,36 @@ SQLITE_PRIVATE int sqlite3Select( ** successful coding of the SELECT. */ select_end: + assert( db->mallocFailed==0 || db->mallocFailed==1 ); + assert( db->mallocFailed==0 || pParse->nErr!=0 ); sqlite3ExprListDelete(db, pMinMaxOrderBy); - sqlite3DbFree(db, sAggInfo.aCol); - sqlite3DbFree(db, sAggInfo.aFunc); -#if SELECTTRACE_ENABLED - SELECTTRACE(0x1,pParse,p,("end processing\n")); - if( (sqlite3SelectTrace & 0x2000)!=0 && ExplainQueryPlanParent(pParse)==0 ){ +#ifdef SQLITE_DEBUG + /* Internal self-checks. tag-select-1000 */ + if( pAggInfo && !db->mallocFailed ){ +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x20 ){ + TREETRACE(0x20,pParse,p,("Finished with AggInfo\n")); + printAggInfo(pAggInfo); + } +#endif + for(i=0; inColumn; i++){ + Expr *pExpr = pAggInfo->aCol[i].pCExpr; + if( pExpr==0 ) continue; + assert( pExpr->pAggInfo==pAggInfo ); + assert( pExpr->iAgg==i ); + } + for(i=0; inFunc; i++){ + Expr *pExpr = pAggInfo->aFunc[i].pFExpr; + assert( pExpr!=0 ); + assert( pExpr->pAggInfo==pAggInfo ); + assert( pExpr->iAgg==i ); + } + } +#endif + +#if TREETRACE_ENABLED + TREETRACE(0x1,pParse,p,("end processing\n")); + if( (sqlite3TreeTrace & 0x40000)!=0 && ExplainQueryPlanParent(pParse)==0 ){ sqlite3TreeViewSelect(0, p, 0); } #endif @@ -131299,7 +158037,7 @@ static int sqlite3_get_table_cb(void *pArg, int nCol, char **argv, char **colv){ if( p->nData + need > p->nAlloc ){ char **azNew; p->nAlloc = p->nAlloc*2 + need; - azNew = sqlite3_realloc64( p->azResult, sizeof(char*)*p->nAlloc ); + azNew = sqlite3Realloc( p->azResult, sizeof(char*)*p->nAlloc ); if( azNew==0 ) goto malloc_failed; p->azResult = azNew; } @@ -131352,7 +158090,7 @@ static int sqlite3_get_table_cb(void *pArg, int nCol, char **argv, char **colv){ ** at the conclusion of the call. ** ** The result that is written to ***pazResult is held in memory obtained -** from malloc(). But the caller cannot free this memory directly. +** from malloc(). But the caller cannot free this memory directly. ** Instead, the entire table should be passed to sqlite3_free_table() when ** the calling procedure is finished using it. */ @@ -131408,7 +158146,7 @@ SQLITE_API int sqlite3_get_table( } if( res.nAlloc>res.nData ){ char **azNew; - azNew = sqlite3_realloc64( res.azResult, sizeof(char*)*res.nData ); + azNew = sqlite3Realloc( res.azResult, sizeof(char*)*res.nData ); if( azNew==0 ){ sqlite3_free_table(&res.azResult[1]); db->errCode = SQLITE_NOMEM; @@ -131470,6 +158208,7 @@ SQLITE_PRIVATE void sqlite3DeleteTriggerStep(sqlite3 *db, TriggerStep *pTriggerS sqlite3SelectDelete(db, pTmp->pSelect); sqlite3IdListDelete(db, pTmp->pIdList); sqlite3UpsertDelete(db, pTmp->pUpsert); + sqlite3SrcListDelete(db, pTmp->pSrc); sqlite3DbFree(db, pTmp->zSpan); sqlite3DbFree(db, pTmp); @@ -131477,7 +158216,7 @@ SQLITE_PRIVATE void sqlite3DeleteTriggerStep(sqlite3 *db, TriggerStep *pTriggerS } /* -** Given table pTab, return a list of all the triggers attached to +** Given table pTab, return a list of all the triggers attached to ** the table. The list is connected by Trigger.pNext pointers. ** ** All of the triggers on pTab that are in the same database as pTab @@ -131491,28 +158230,49 @@ SQLITE_PRIVATE void sqlite3DeleteTriggerStep(sqlite3 *db, TriggerStep *pTriggerS ** pTab as well as the triggers lised in pTab->pTrigger. */ SQLITE_PRIVATE Trigger *sqlite3TriggerList(Parse *pParse, Table *pTab){ - Schema * const pTmpSchema = pParse->db->aDb[1].pSchema; - Trigger *pList = 0; /* List of triggers to return */ - - if( pParse->disableTriggers ){ - return 0; + Schema *pTmpSchema; /* Schema of the pTab table */ + Trigger *pList; /* List of triggers to return */ + HashElem *p; /* Loop variable for TEMP triggers */ + + assert( pParse->disableTriggers==0 ); + pTmpSchema = pParse->db->aDb[1].pSchema; + p = sqliteHashFirst(&pTmpSchema->trigHash); + pList = pTab->pTrigger; + while( p ){ + Trigger *pTrig = (Trigger *)sqliteHashData(p); + if( pTrig->pTabSchema==pTab->pSchema + && pTrig->table + && 0==sqlite3StrICmp(pTrig->table, pTab->zName) + && (pTrig->pTabSchema!=pTmpSchema || pTrig->bReturning) + ){ + pTrig->pNext = pList; + pList = pTrig; + }else if( pTrig->op==TK_RETURNING ){ +#ifndef SQLITE_OMIT_VIRTUALTABLE + assert( pParse->db->pVtabCtx==0 ); +#endif + assert( pParse->bReturning ); + assert( !pParse->isCreate ); + assert( &(pParse->u1.d.pReturning->retTrig) == pTrig ); + pTrig->table = pTab->zName; + pTrig->pTabSchema = pTab->pSchema; + pTrig->pNext = pList; + pList = pTrig; + } + p = sqliteHashNext(p); } - - if( pTmpSchema!=pTab->pSchema ){ - HashElem *p; - assert( sqlite3SchemaMutexHeld(pParse->db, 0, pTmpSchema) ); - for(p=sqliteHashFirst(&pTmpSchema->trigHash); p; p=sqliteHashNext(p)){ - Trigger *pTrig = (Trigger *)sqliteHashData(p); - if( pTrig->pTabSchema==pTab->pSchema - && 0==sqlite3StrICmp(pTrig->table, pTab->zName) - ){ - pTrig->pNext = (pList ? pList : pTab->pTrigger); - pList = pTrig; - } +#if 0 + if( pList ){ + Trigger *pX; + printf("Triggers for %s:", pTab->zName); + for(pX=pList; pX; pX=pX->pNext){ + printf(" %s", pX->zName); } + printf("\n"); + fflush(stdout); } - - return (pList ? pList : pTab->pTrigger); +#endif + return pList; } /* @@ -131572,11 +158332,13 @@ SQLITE_PRIVATE void sqlite3BeginTrigger( ** ^^^^^^^^ ** ** To maintain backwards compatibility, ignore the database - ** name on pTableName if we are reparsing out of SQLITE_MASTER. + ** name on pTableName if we are reparsing out of the schema table */ if( db->init.busy && iDb!=1 ){ - sqlite3DbFree(db, pTableName->a[0].zDatabase); - pTableName->a[0].zDatabase = 0; + assert( pTableName->a[0].fg.fixedSchema==0 ); + assert( pTableName->a[0].fg.isSubquery==0 ); + sqlite3DbFree(db, pTableName->a[0].u4.zDatabase); + pTableName->a[0].u4.zDatabase = 0; } /* If the trigger name was unqualified, and the table is a temp table, @@ -131600,28 +158362,25 @@ SQLITE_PRIVATE void sqlite3BeginTrigger( pTab = sqlite3SrcListLookup(pParse, pTableName); if( !pTab ){ /* The table does not exist. */ - if( db->init.iDb==1 ){ - /* Ticket #3810. - ** Normally, whenever a table is dropped, all associated triggers are - ** dropped too. But if a TEMP trigger is created on a non-TEMP table - ** and the table is dropped by a different database connection, the - ** trigger is not visible to the database connection that does the - ** drop so the trigger cannot be dropped. This results in an - ** "orphaned trigger" - a trigger whose associated table is missing. - */ - db->init.orphanTrigger = 1; - } - goto trigger_cleanup; + goto trigger_orphan_error; } if( IsVirtual(pTab) ){ sqlite3ErrorMsg(pParse, "cannot create triggers on virtual tables"); - goto trigger_cleanup; + goto trigger_orphan_error; + } + if( (pTab->tabFlags & TF_Shadow)!=0 && sqlite3ReadOnlyShadowTables(db) ){ + sqlite3ErrorMsg(pParse, "cannot create triggers on shadow tables"); + goto trigger_orphan_error; } /* Check that the trigger name is not reserved and that no trigger of the ** specified name exists */ zName = sqlite3NameFromToken(db, pName); - if( !zName || SQLITE_OK!=sqlite3CheckObjectName(pParse, zName) ){ + if( zName==0 ){ + assert( db->mallocFailed ); + goto trigger_cleanup; + } + if( sqlite3CheckObjectName(pParse, zName, "trigger", pTab->zName) ){ goto trigger_cleanup; } assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); @@ -131632,29 +158391,35 @@ SQLITE_PRIVATE void sqlite3BeginTrigger( }else{ assert( !db->init.busy ); sqlite3CodeVerifySchema(pParse, iDb); + VVA_ONLY( pParse->ifNotExists = 1; ) } goto trigger_cleanup; } } + /* NB: The SQLITE_ALLOW_TRIGGERS_ON_SYSTEM_TABLES compile-time option is + ** experimental and unsupported. Do not use it unless understand the + ** implications and you cannot get by without this capability. */ +#if !defined(SQLITE_ALLOW_TRIGGERS_ON_SYSTEM_TABLES) /* Experimental */ /* Do not create a trigger on a system table */ if( sqlite3StrNICmp(pTab->zName, "sqlite_", 7)==0 ){ sqlite3ErrorMsg(pParse, "cannot create trigger on system table"); goto trigger_cleanup; } +#endif /* INSTEAD of triggers are only for views and views only support INSTEAD ** of triggers. */ - if( pTab->pSelect && tr_tm!=TK_INSTEAD ){ - sqlite3ErrorMsg(pParse, "cannot create %s trigger on view: %S", - (tr_tm == TK_BEFORE)?"BEFORE":"AFTER", pTableName, 0); - goto trigger_cleanup; + if( IsView(pTab) && tr_tm!=TK_INSTEAD ){ + sqlite3ErrorMsg(pParse, "cannot create %s trigger on view: %S", + (tr_tm == TK_BEFORE)?"BEFORE":"AFTER", pTableName->a); + goto trigger_orphan_error; } - if( !pTab->pSelect && tr_tm==TK_INSTEAD ){ + if( !IsView(pTab) && tr_tm==TK_INSTEAD ){ sqlite3ErrorMsg(pParse, "cannot create INSTEAD OF" - " trigger on table: %S", pTableName, 0); - goto trigger_cleanup; + " trigger on table: %S", pTableName->a); + goto trigger_orphan_error; } #ifndef SQLITE_OMIT_AUTHORIZATION @@ -131714,6 +158479,23 @@ SQLITE_PRIVATE void sqlite3BeginTrigger( }else{ assert( pParse->pNewTrigger==pTrigger ); } + return; + +trigger_orphan_error: + if( db->init.iDb==1 ){ + /* Ticket #3810. + ** Normally, whenever a table is dropped, all associated triggers are + ** dropped too. But if a TEMP trigger is created on a non-TEMP table + ** and the table is dropped by a different database connection, the + ** trigger is not visible to the database connection that does the + ** drop so the trigger cannot be dropped. This results in an + ** "orphaned trigger" - a trigger whose associated table is missing. + ** + ** 2020-11-05 see also https://sqlite.org/forum/forumpost/157dc791df + */ + db->init.orphanTrigger = 1; + } + goto trigger_cleanup; } /* @@ -131736,6 +158518,7 @@ SQLITE_PRIVATE void sqlite3FinishTrigger( if( NEVER(pParse->nErr) || !pTrig ) goto triggerfinish_cleanup; zName = pTrig->zName; iDb = sqlite3SchemaToIndex(pParse->db, pTrig->pSchema); + assert( iDb>=00 && iDbnDb ); pTrig->step_list = pStepList; while( pStepList ){ pStepList->pTrig = pTrig; @@ -131743,8 +158526,8 @@ SQLITE_PRIVATE void sqlite3FinishTrigger( } sqlite3TokenInit(&nameToken, pTrig->zName); sqlite3FixInit(&sFix, pParse, iDb, "trigger", &nameToken); - if( sqlite3FixTriggerStep(&sFix, pTrig->step_list) - || sqlite3FixExpr(&sFix, pTrig->pWhen) + if( sqlite3FixTriggerStep(&sFix, pTrig->step_list) + || sqlite3FixExpr(&sFix, pTrig->pWhen) ){ goto triggerfinish_cleanup; } @@ -131758,32 +158541,51 @@ SQLITE_PRIVATE void sqlite3FinishTrigger( #endif /* if we are not initializing, - ** build the sqlite_master entry + ** build the sqlite_schema entry */ if( !db->init.busy ){ Vdbe *v; char *z; - /* Make an entry in the sqlite_master table */ + /* If this is a new CREATE TABLE statement, and if shadow tables + ** are read-only, and the trigger makes a change to a shadow table, + ** then raise an error - do not allow the trigger to be created. */ + if( sqlite3ReadOnlyShadowTables(db) ){ + TriggerStep *pStep; + for(pStep=pTrig->step_list; pStep; pStep=pStep->pNext){ + if( pStep->pSrc!=0 + && sqlite3ShadowTableName(db, pStep->pSrc->a[0].zName) + ){ + sqlite3ErrorMsg(pParse, + "trigger \"%s\" may not write to shadow table \"%s\"", + pTrig->zName, pStep->pSrc->a[0].zName); + goto triggerfinish_cleanup; + } + } + } + + /* Make an entry in the sqlite_schema table */ v = sqlite3GetVdbe(pParse); if( v==0 ) goto triggerfinish_cleanup; sqlite3BeginWriteOperation(pParse, 0, iDb); z = sqlite3DbStrNDup(db, (char*)pAll->z, pAll->n); testcase( z==0 ); sqlite3NestedParse(pParse, - "INSERT INTO %Q.%s VALUES('trigger',%Q,%Q,0,'CREATE TRIGGER %q')", - db->aDb[iDb].zDbSName, MASTER_NAME, zName, + "INSERT INTO %Q." LEGACY_SCHEMA_TABLE + " VALUES('trigger',%Q,%Q,0,'CREATE TRIGGER %q')", + db->aDb[iDb].zDbSName, zName, pTrig->table, z); sqlite3DbFree(db, z); sqlite3ChangeCookie(pParse, iDb); sqlite3VdbeAddParseSchemaOp(v, iDb, - sqlite3MPrintf(db, "type='trigger' AND name='%q'", zName)); + sqlite3MPrintf(db, "type='trigger' AND name='%q'", zName), 0); } if( db->init.busy ){ Trigger *pLink = pTrig; Hash *pHash = &db->aDb[iDb].pSchema->trigHash; assert( sqlite3SchemaMutexHeld(db, iDb, 0) ); + assert( pLink!=0 ); pTrig = sqlite3HashInsert(pHash, zName, pTrig); if( pTrig ){ sqlite3OomFault(db); @@ -131811,14 +158613,14 @@ static char *triggerSpanDup(sqlite3 *db, const char *zStart, const char *zEnd){ int i; if( z ) for(i=0; z[i]; i++) if( sqlite3Isspace(z[i]) ) z[i] = ' '; return z; -} +} /* ** Turn a SELECT statement (that the pSelect parameter points to) into ** a trigger step. Return a pointer to a TriggerStep structure. ** ** The parser calls this routine when it finds a SELECT statement in -** body of a TRIGGER. +** body of a TRIGGER. */ SQLITE_PRIVATE TriggerStep *sqlite3TriggerSelectStep( sqlite3 *db, /* Database connection */ @@ -131847,25 +158649,39 @@ SQLITE_PRIVATE TriggerStep *sqlite3TriggerSelectStep( static TriggerStep *triggerStepAllocate( Parse *pParse, /* Parser context */ u8 op, /* Trigger opcode */ - Token *pName, /* The target name */ + SrcList *pTabList, /* Target table */ const char *zStart, /* Start of SQL text */ const char *zEnd /* End of SQL text */ ){ + Trigger *pNew = pParse->pNewTrigger; sqlite3 *db = pParse->db; - TriggerStep *pTriggerStep; + TriggerStep *pTriggerStep = 0; - pTriggerStep = sqlite3DbMallocZero(db, sizeof(TriggerStep) + pName->n + 1); - if( pTriggerStep ){ - char *z = (char*)&pTriggerStep[1]; - memcpy(z, pName->z, pName->n); - sqlite3Dequote(z); - pTriggerStep->zTarget = z; - pTriggerStep->op = op; - pTriggerStep->zSpan = triggerSpanDup(db, zStart, zEnd); - if( IN_RENAME_OBJECT ){ - sqlite3RenameTokenMap(pParse, pTriggerStep->zTarget, pName); + if( pParse->nErr==0 ){ + if( pNew + && pNew->pSchema!=db->aDb[1].pSchema + && pTabList->a[0].u4.zDatabase + ){ + sqlite3ErrorMsg(pParse, + "qualified table names are not allowed on INSERT, UPDATE, and DELETE " + "statements within triggers"); + }else{ + pTriggerStep = sqlite3DbMallocZero(db, sizeof(TriggerStep)); + if( pTriggerStep ){ + pTriggerStep->pSrc = sqlite3SrcListDup(db, pTabList, EXPRDUP_REDUCE); + pTriggerStep->op = op; + pTriggerStep->zSpan = triggerSpanDup(db, zStart, zEnd); + if( pTriggerStep->pSrc && IN_RENAME_OBJECT ){ + sqlite3RenameTokenRemap(pParse, + pTriggerStep->pSrc->a[0].zName, + pTabList->a[0].zName + ); + } + } } } + + sqlite3SrcListDelete(db, pTabList); return pTriggerStep; } @@ -131878,7 +158694,7 @@ static TriggerStep *triggerStepAllocate( */ SQLITE_PRIVATE TriggerStep *sqlite3TriggerInsertStep( Parse *pParse, /* Parser */ - Token *pTableName, /* Name of the table into which we insert */ + SrcList *pTabList, /* Table to INSERT into */ IdList *pColumn, /* List of columns in pTableName to insert into */ Select *pSelect, /* A SELECT statement that supplies values */ u8 orconf, /* The conflict algorithm (OE_Abort, OE_Replace, etc.) */ @@ -131891,7 +158707,7 @@ SQLITE_PRIVATE TriggerStep *sqlite3TriggerInsertStep( assert(pSelect != 0 || db->mallocFailed); - pTriggerStep = triggerStepAllocate(pParse, TK_INSERT, pTableName,zStart,zEnd); + pTriggerStep = triggerStepAllocate(pParse, TK_INSERT, pTabList, zStart, zEnd); if( pTriggerStep ){ if( IN_RENAME_OBJECT ){ pTriggerStep->pSelect = pSelect; @@ -131902,6 +158718,9 @@ SQLITE_PRIVATE TriggerStep *sqlite3TriggerInsertStep( pTriggerStep->pIdList = pColumn; pTriggerStep->pUpsert = pUpsert; pTriggerStep->orconf = orconf; + if( pUpsert ){ + sqlite3HasExplicitNulls(pParse, pUpsert->pUpsertTarget); + } }else{ testcase( pColumn ); sqlite3IdListDelete(db, pColumn); @@ -131920,7 +158739,8 @@ SQLITE_PRIVATE TriggerStep *sqlite3TriggerInsertStep( */ SQLITE_PRIVATE TriggerStep *sqlite3TriggerUpdateStep( Parse *pParse, /* Parser */ - Token *pTableName, /* Name of the table to be updated */ + SrcList *pTabList, /* Name of the table to be updated */ + SrcList *pFrom, /* FROM clause for an UPDATE-FROM, or NULL */ ExprList *pEList, /* The SET clause: list of column and new values */ Expr *pWhere, /* The WHERE clause */ u8 orconf, /* The conflict algorithm. (OE_Abort, OE_Ignore, etc) */ @@ -131930,21 +158750,40 @@ SQLITE_PRIVATE TriggerStep *sqlite3TriggerUpdateStep( sqlite3 *db = pParse->db; TriggerStep *pTriggerStep; - pTriggerStep = triggerStepAllocate(pParse, TK_UPDATE, pTableName,zStart,zEnd); + pTriggerStep = triggerStepAllocate(pParse, TK_UPDATE, pTabList, zStart, zEnd); if( pTriggerStep ){ + SrcList *pFromDup = 0; if( IN_RENAME_OBJECT ){ pTriggerStep->pExprList = pEList; pTriggerStep->pWhere = pWhere; + pFromDup = pFrom; pEList = 0; pWhere = 0; + pFrom = 0; }else{ pTriggerStep->pExprList = sqlite3ExprListDup(db, pEList, EXPRDUP_REDUCE); pTriggerStep->pWhere = sqlite3ExprDup(db, pWhere, EXPRDUP_REDUCE); + pFromDup = sqlite3SrcListDup(db, pFrom, EXPRDUP_REDUCE); } pTriggerStep->orconf = orconf; + + if( pFromDup && !IN_RENAME_OBJECT){ + Select *pSub; + Token as = {0, 0}; + pSub = sqlite3SelectNew(pParse, 0, pFromDup, 0,0,0,0, SF_NestedFrom, 0); + pFromDup = sqlite3SrcListAppendFromTerm(pParse, 0, 0, 0, &as, pSub ,0); + } + if( pFromDup && pTriggerStep->pSrc ){ + pTriggerStep->pSrc = sqlite3SrcListAppendList( + pParse, pTriggerStep->pSrc, pFromDup + ); + }else{ + sqlite3SrcListDelete(db, pFromDup); + } } sqlite3ExprListDelete(db, pEList); sqlite3ExprDelete(db, pWhere); + sqlite3SrcListDelete(db, pFrom); return pTriggerStep; } @@ -131955,7 +158794,7 @@ SQLITE_PRIVATE TriggerStep *sqlite3TriggerUpdateStep( */ SQLITE_PRIVATE TriggerStep *sqlite3TriggerDeleteStep( Parse *pParse, /* Parser */ - Token *pTableName, /* The table from which rows are deleted */ + SrcList *pTabList, /* The table from which rows are deleted */ Expr *pWhere, /* The WHERE clause */ const char *zStart, /* Start of SQL text */ const char *zEnd /* End of SQL text */ @@ -131963,7 +158802,7 @@ SQLITE_PRIVATE TriggerStep *sqlite3TriggerDeleteStep( sqlite3 *db = pParse->db; TriggerStep *pTriggerStep; - pTriggerStep = triggerStepAllocate(pParse, TK_DELETE, pTableName,zStart,zEnd); + pTriggerStep = triggerStepAllocate(pParse, TK_DELETE, pTabList, zStart, zEnd); if( pTriggerStep ){ if( IN_RENAME_OBJECT ){ pTriggerStep->pWhere = pWhere; @@ -131977,11 +158816,11 @@ SQLITE_PRIVATE TriggerStep *sqlite3TriggerDeleteStep( return pTriggerStep; } -/* +/* ** Recursively delete a Trigger structure */ SQLITE_PRIVATE void sqlite3DeleteTrigger(sqlite3 *db, Trigger *pTrigger){ - if( pTrigger==0 ) return; + if( pTrigger==0 || pTrigger->bReturning ) return; sqlite3DeleteTriggerStep(db, pTrigger->step_list); sqlite3DbFree(db, pTrigger->zName); sqlite3DbFree(db, pTrigger->table); @@ -131991,7 +158830,7 @@ SQLITE_PRIVATE void sqlite3DeleteTrigger(sqlite3 *db, Trigger *pTrigger){ } /* -** This function is called to drop a trigger from the database schema. +** This function is called to drop a trigger from the database schema. ** ** This may be called directly from the parser and therefore identifies ** the trigger by name. The sqlite3DropTriggerPtr() routine does the @@ -132011,19 +158850,20 @@ SQLITE_PRIVATE void sqlite3DropTrigger(Parse *pParse, SrcList *pName, int noErr) } assert( pName->nSrc==1 ); - zDb = pName->a[0].zDatabase; + assert( pName->a[0].fg.fixedSchema==0 && pName->a[0].fg.isSubquery==0 ); + zDb = pName->a[0].u4.zDatabase; zName = pName->a[0].zName; assert( zDb!=0 || sqlite3BtreeHoldsAllMutexes(db) ); for(i=OMIT_TEMPDB; inDb; i++){ int j = (i<2) ? i^1 : i; /* Search TEMP before MAIN */ - if( zDb && sqlite3StrICmp(db->aDb[j].zDbSName, zDb) ) continue; + if( zDb && sqlite3DbIsNamed(db, j, zDb)==0 ) continue; assert( sqlite3SchemaMutexHeld(db, j, 0) ); pTrigger = sqlite3HashFind(&(db->aDb[j].pSchema->trigHash), zName); if( pTrigger ) break; } if( !pTrigger ){ if( !noErr ){ - sqlite3ErrorMsg(pParse, "no such trigger: %S", pName, 0); + sqlite3ErrorMsg(pParse, "no such trigger: %S", pName->a); }else{ sqlite3CodeVerifyNamedSchema(pParse, zDb); } @@ -132046,7 +158886,7 @@ static Table *tableOfTrigger(Trigger *pTrigger){ /* -** Drop a trigger given a pointer to that trigger. +** Drop a trigger given a pointer to that trigger. */ SQLITE_PRIVATE void sqlite3DropTriggerPtr(Parse *pParse, Trigger *pTrigger){ Table *pTable; @@ -132057,10 +158897,9 @@ SQLITE_PRIVATE void sqlite3DropTriggerPtr(Parse *pParse, Trigger *pTrigger){ iDb = sqlite3SchemaToIndex(pParse->db, pTrigger->pSchema); assert( iDb>=0 && iDbnDb ); pTable = tableOfTrigger(pTrigger); - assert( pTable ); - assert( pTable->pSchema==pTrigger->pSchema || iDb==1 ); + assert( (pTable && pTable->pSchema==pTrigger->pSchema) || iDb==1 ); #ifndef SQLITE_OMIT_AUTHORIZATION - { + if( pTable ){ int code = SQLITE_DROP_TRIGGER; const char *zDb = db->aDb[iDb].zDbSName; const char *zTab = SCHEMA_TABLE(iDb); @@ -132074,11 +158913,10 @@ SQLITE_PRIVATE void sqlite3DropTriggerPtr(Parse *pParse, Trigger *pTrigger){ /* Generate code to destroy the database record of the trigger. */ - assert( pTable!=0 ); if( (v = sqlite3GetVdbe(pParse))!=0 ){ sqlite3NestedParse(pParse, - "DELETE FROM %Q.%s WHERE name=%Q AND type='trigger'", - db->aDb[iDb].zDbSName, MASTER_NAME, pTrigger->zName + "DELETE FROM %Q." LEGACY_SCHEMA_TABLE " WHERE name=%Q AND type='trigger'", + db->aDb[iDb].zDbSName, pTrigger->zName ); sqlite3ChangeCookie(pParse, iDb); sqlite3VdbeAddOp4(v, OP_DropTrigger, iDb, 0, 0, pTrigger->zName, 0); @@ -132098,9 +158936,15 @@ SQLITE_PRIVATE void sqlite3UnlinkAndDeleteTrigger(sqlite3 *db, int iDb, const ch if( ALWAYS(pTrigger) ){ if( pTrigger->pSchema==pTrigger->pTabSchema ){ Table *pTab = tableOfTrigger(pTrigger); - Trigger **pp; - for(pp=&pTab->pTrigger; *pp!=pTrigger; pp=&((*pp)->pNext)); - *pp = (*pp)->pNext; + if( pTab ){ + Trigger **pp; + for(pp=&pTab->pTrigger; *pp; pp=&((*pp)->pNext)){ + if( *pp==pTrigger ){ + *pp = (*pp)->pNext; + break; + } + } + } } sqlite3DeleteTrigger(db, pTrigger); db->mDbFlags |= DBFLAG_SchemaChange; @@ -132120,18 +158964,27 @@ static int checkColumnOverlap(IdList *pIdList, ExprList *pEList){ int e; if( pIdList==0 || NEVER(pEList==0) ) return 1; for(e=0; enExpr; e++){ - if( sqlite3IdListIndex(pIdList, pEList->a[e].zName)>=0 ) return 1; + if( sqlite3IdListIndex(pIdList, pEList->a[e].zEName)>=0 ) return 1; } - return 0; + return 0; +} + +/* +** Return true if any TEMP triggers exist +*/ +static int tempTriggersExist(sqlite3 *db){ + if( NEVER(db->aDb[1].pSchema==0) ) return 0; + if( sqliteHashFirst(&db->aDb[1].pSchema->trigHash)==0 ) return 0; + return 1; } /* ** Return a list of all triggers on table pTab if there exists at least -** one trigger that must be fired when an operation of type 'op' is +** one trigger that must be fired when an operation of type 'op' is ** performed on the table, and, if that operation is an UPDATE, if at ** least one of the columns in pChanges is being modified. */ -SQLITE_PRIVATE Trigger *sqlite3TriggersExist( +static SQLITE_NOINLINE Trigger *triggersReallyExist( Parse *pParse, /* Parse context */ Table *pTab, /* The table the contains the triggers */ int op, /* one of TK_DELETE, TK_INSERT, TK_UPDATE */ @@ -132142,62 +158995,305 @@ SQLITE_PRIVATE Trigger *sqlite3TriggersExist( Trigger *pList = 0; Trigger *p; - if( (pParse->db->flags & SQLITE_EnableTrigger)!=0 ){ - pList = sqlite3TriggerList(pParse, pTab); - } - assert( pList==0 || IsVirtual(pTab)==0 ); - for(p=pList; p; p=p->pNext){ - if( p->op==op && checkColumnOverlap(p->pColumns, pChanges) ){ - mask |= p->tr_tm; + pList = sqlite3TriggerList(pParse, pTab); + assert( pList==0 || IsVirtual(pTab)==0 + || (pList->bReturning && pList->pNext==0) ); + if( pList!=0 ){ + p = pList; + if( (pParse->db->flags & SQLITE_EnableTrigger)==0 + && pTab->pTrigger!=0 + && sqlite3SchemaToIndex(pParse->db, pTab->pTrigger->pSchema)!=1 + ){ + /* The SQLITE_DBCONFIG_ENABLE_TRIGGER setting is off. That means that + ** only TEMP triggers are allowed. Truncate the pList so that it + ** includes only TEMP triggers */ + if( pList==pTab->pTrigger ){ + pList = 0; + goto exit_triggers_exist; + } + while( ALWAYS(p->pNext) && p->pNext!=pTab->pTrigger ) p = p->pNext; + p->pNext = 0; + p = pList; } + do{ + if( p->op==op && checkColumnOverlap(p->pColumns, pChanges) ){ + mask |= p->tr_tm; + }else if( p->op==TK_RETURNING ){ + /* The first time a RETURNING trigger is seen, the "op" value tells + ** us what time of trigger it should be. */ + assert( sqlite3IsToplevel(pParse) ); + p->op = op; + if( IsVirtual(pTab) ){ + if( op!=TK_INSERT ){ + sqlite3ErrorMsg(pParse, + "%s RETURNING is not available on virtual tables", + op==TK_DELETE ? "DELETE" : "UPDATE"); + } + p->tr_tm = TRIGGER_BEFORE; + }else{ + p->tr_tm = TRIGGER_AFTER; + } + mask |= p->tr_tm; + }else if( p->bReturning && p->op==TK_INSERT && op==TK_UPDATE + && sqlite3IsToplevel(pParse) ){ + /* Also fire a RETURNING trigger for an UPSERT */ + mask |= p->tr_tm; + } + p = p->pNext; + }while( p ); } +exit_triggers_exist: if( pMask ){ *pMask = mask; } return (mask ? pList : 0); } +SQLITE_PRIVATE Trigger *sqlite3TriggersExist( + Parse *pParse, /* Parse context */ + Table *pTab, /* The table the contains the triggers */ + int op, /* one of TK_DELETE, TK_INSERT, TK_UPDATE */ + ExprList *pChanges, /* Columns that change in an UPDATE statement */ + int *pMask /* OUT: Mask of TRIGGER_BEFORE|TRIGGER_AFTER */ +){ + assert( pTab!=0 ); + if( (pTab->pTrigger==0 && !tempTriggersExist(pParse->db)) + || pParse->disableTriggers + ){ + if( pMask ) *pMask = 0; + return 0; + } + return triggersReallyExist(pParse,pTab,op,pChanges,pMask); +} /* -** Convert the pStep->zTarget string into a SrcList and return a pointer -** to that SrcList. +** Return true if the pExpr term from the RETURNING clause argument +** list is of the form "*". Raise an error if the terms if of the +** form "table.*". +*/ +static int isAsteriskTerm( + Parse *pParse, /* Parsing context */ + Expr *pTerm /* A term in the RETURNING clause */ +){ + assert( pTerm!=0 ); + if( pTerm->op==TK_ASTERISK ) return 1; + if( pTerm->op!=TK_DOT ) return 0; + assert( pTerm->pRight!=0 ); + assert( pTerm->pLeft!=0 ); + if( pTerm->pRight->op!=TK_ASTERISK ) return 0; + sqlite3ErrorMsg(pParse, "RETURNING may not use \"TABLE.*\" wildcards"); + return 1; +} + +/* The input list pList is the list of result set terms from a RETURNING +** clause. The table that we are returning from is pTab. ** -** This routine adds a specific database name, if needed, to the target when -** forming the SrcList. This prevents a trigger in one database from -** referring to a target in another database. An exception is when the -** trigger is in TEMP in which case it can refer to any other database it -** wants. +** This routine makes a copy of the pList, and at the same time expands +** any "*" wildcards to be the complete set of columns from pTab. */ -static SrcList *targetSrcList( - Parse *pParse, /* The parsing context */ - TriggerStep *pStep /* The trigger containing the target token */ +static ExprList *sqlite3ExpandReturning( + Parse *pParse, /* Parsing context */ + ExprList *pList, /* The arguments to RETURNING */ + Table *pTab /* The table being updated */ ){ + ExprList *pNew = 0; sqlite3 *db = pParse->db; - int iDb; /* Index of the database to use */ - SrcList *pSrc; /* SrcList to be returned */ + int i; - pSrc = sqlite3SrcListAppend(pParse, 0, 0, 0); - if( pSrc ){ - assert( pSrc->nSrc>0 ); - pSrc->a[pSrc->nSrc-1].zName = sqlite3DbStrDup(db, pStep->zTarget); - iDb = sqlite3SchemaToIndex(db, pStep->pTrig->pSchema); - if( iDb==0 || iDb>=2 ){ - const char *zDb; - assert( iDbnDb ); - zDb = db->aDb[iDb].zDbSName; - pSrc->a[pSrc->nSrc-1].zDatabase = sqlite3DbStrDup(db, zDb); + for(i=0; inExpr; i++){ + Expr *pOldExpr = pList->a[i].pExpr; + if( NEVER(pOldExpr==0) ) continue; + if( isAsteriskTerm(pParse, pOldExpr) ){ + int jj; + for(jj=0; jjnCol; jj++){ + Expr *pNewExpr; + if( IsHiddenColumn(pTab->aCol+jj) ) continue; + pNewExpr = sqlite3Expr(db, TK_ID, pTab->aCol[jj].zCnName); + pNew = sqlite3ExprListAppend(pParse, pNew, pNewExpr); + if( !db->mallocFailed ){ + struct ExprList_item *pItem = &pNew->a[pNew->nExpr-1]; + pItem->zEName = sqlite3DbStrDup(db, pTab->aCol[jj].zCnName); + pItem->fg.eEName = ENAME_NAME; + } + } + }else{ + Expr *pNewExpr = sqlite3ExprDup(db, pOldExpr, 0); + pNew = sqlite3ExprListAppend(pParse, pNew, pNewExpr); + if( !db->mallocFailed && ALWAYS(pList->a[i].zEName!=0) ){ + struct ExprList_item *pItem = &pNew->a[pNew->nExpr-1]; + pItem->zEName = sqlite3DbStrDup(db, pList->a[i].zEName); + pItem->fg.eEName = pList->a[i].fg.eEName; + } } } - return pSrc; + return pNew; +} + +/* If the Expr node is a subquery or an EXISTS operator or an IN operator that +** uses a subquery, and if the subquery is SF_Correlated, then mark the +** expression as EP_VarSelect. +*/ +static int sqlite3ReturningSubqueryVarSelect(Walker *NotUsed, Expr *pExpr){ + UNUSED_PARAMETER(NotUsed); + if( ExprUseXSelect(pExpr) + && (pExpr->x.pSelect->selFlags & SF_Correlated)!=0 + ){ + testcase( ExprHasProperty(pExpr, EP_VarSelect) ); + ExprSetProperty(pExpr, EP_VarSelect); + } + return WRC_Continue; +} + + +/* +** If the SELECT references the table pWalker->u.pTab, then do two things: +** +** (1) Mark the SELECT as as SF_Correlated. +** (2) Set pWalker->eCode to non-zero so that the caller will know +** that (1) has happened. +*/ +static int sqlite3ReturningSubqueryCorrelated(Walker *pWalker, Select *pSelect){ + int i; + SrcList *pSrc; + assert( pSelect!=0 ); + pSrc = pSelect->pSrc; + assert( pSrc!=0 ); + for(i=0; inSrc; i++){ + if( pSrc->a[i].pSTab==pWalker->u.pTab ){ + testcase( pSelect->selFlags & SF_Correlated ); + pSelect->selFlags |= SF_Correlated; + pWalker->eCode = 1; + break; + } + } + return WRC_Continue; +} + +/* +** Scan the expression list that is the argument to RETURNING looking +** for subqueries that depend on the table which is being modified in the +** statement that is hosting the RETURNING clause (pTab). Mark all such +** subqueries as SF_Correlated. If the subqueries are part of an +** expression, mark the expression as EP_VarSelect. +** +** https://sqlite.org/forum/forumpost/2c83569ce8945d39 +*/ +static void sqlite3ProcessReturningSubqueries( + ExprList *pEList, + Table *pTab +){ + Walker w; + memset(&w, 0, sizeof(w)); + w.xExprCallback = sqlite3ExprWalkNoop; + w.xSelectCallback = sqlite3ReturningSubqueryCorrelated; + w.u.pTab = pTab; + sqlite3WalkExprList(&w, pEList); + if( w.eCode ){ + w.xExprCallback = sqlite3ReturningSubqueryVarSelect; + w.xSelectCallback = sqlite3SelectWalkNoop; + sqlite3WalkExprList(&w, pEList); + } } /* -** Generate VDBE code for the statements inside the body of a single +** Generate code for the RETURNING trigger. Unlike other triggers +** that invoke a subprogram in the bytecode, the code for RETURNING +** is generated in-line. +*/ +static void codeReturningTrigger( + Parse *pParse, /* Parse context */ + Trigger *pTrigger, /* The trigger step that defines the RETURNING */ + Table *pTab, /* The table to code triggers from */ + int regIn /* The first in an array of registers */ +){ + Vdbe *v = pParse->pVdbe; + sqlite3 *db = pParse->db; + ExprList *pNew; + Returning *pReturning; + Select sSelect; + SrcList *pFrom; + union { + SrcList sSrc; + u8 fromSpace[SZ_SRCLIST_1]; + } uSrc; + + assert( v!=0 ); + if( !pParse->bReturning ){ + /* This RETURNING trigger must be for a different statement as + ** this statement lacks a RETURNING clause. */ + return; + } + assert( db->pParse==pParse ); + assert( !pParse->isCreate ); + pReturning = pParse->u1.d.pReturning; + if( pTrigger != &(pReturning->retTrig) ){ + /* This RETURNING trigger is for a different statement */ + return; + } + memset(&sSelect, 0, sizeof(sSelect)); + memset(&uSrc, 0, sizeof(uSrc)); + pFrom = &uSrc.sSrc; + sSelect.pEList = sqlite3ExprListDup(db, pReturning->pReturnEL, 0); + sSelect.pSrc = pFrom; + pFrom->nSrc = 1; + pFrom->a[0].pSTab = pTab; + pFrom->a[0].zName = pTab->zName; /* tag-20240424-1 */ + pFrom->a[0].iCursor = -1; + sqlite3SelectPrep(pParse, &sSelect, 0); + if( pParse->nErr==0 ){ + assert( db->mallocFailed==0 ); + sqlite3GenerateColumnNames(pParse, &sSelect); + } + sqlite3ExprListDelete(db, sSelect.pEList); + pNew = sqlite3ExpandReturning(pParse, pReturning->pReturnEL, pTab); + if( pParse->nErr==0 ){ + NameContext sNC; + memset(&sNC, 0, sizeof(sNC)); + if( pReturning->nRetCol==0 ){ + pReturning->nRetCol = pNew->nExpr; + pReturning->iRetCur = pParse->nTab++; + } + sNC.pParse = pParse; + sNC.uNC.iBaseReg = regIn; + sNC.ncFlags = NC_UBaseReg; + pParse->eTriggerOp = pTrigger->op; + pParse->pTriggerTab = pTab; + if( sqlite3ResolveExprListNames(&sNC, pNew)==SQLITE_OK + && ALWAYS(!db->mallocFailed) + ){ + int i; + int nCol = pNew->nExpr; + int reg = pParse->nMem+1; + sqlite3ProcessReturningSubqueries(pNew, pTab); + pParse->nMem += nCol+2; + pReturning->iRetReg = reg; + for(i=0; ia[i].pExpr; + assert( pCol!=0 ); /* Due to !db->mallocFailed ~9 lines above */ + sqlite3ExprCodeFactorable(pParse, pCol, reg+i); + if( sqlite3ExprAffinity(pCol)==SQLITE_AFF_REAL ){ + sqlite3VdbeAddOp1(v, OP_RealAffinity, reg+i); + } + } + sqlite3VdbeAddOp3(v, OP_MakeRecord, reg, i, reg+i); + sqlite3VdbeAddOp2(v, OP_NewRowid, pReturning->iRetCur, reg+i+1); + sqlite3VdbeAddOp3(v, OP_Insert, pReturning->iRetCur, reg+i, reg+i+1); + } + } + sqlite3ExprListDelete(db, pNew); + pParse->eTriggerOp = 0; + pParse->pTriggerTab = 0; +} + + + +/* +** Generate VDBE code for the statements inside the body of a single ** trigger. */ static int codeTriggerProgram( Parse *pParse, /* The parser context */ TriggerStep *pStepList, /* List of statements inside the trigger body */ - int orconf /* Conflict algorithm. (OE_Abort, etc) */ + int orconf /* Conflict algorithm. (OE_Abort, etc) */ ){ TriggerStep *pStep; Vdbe *v = pParse->pVdbe; @@ -132233,29 +159329,32 @@ static int codeTriggerProgram( switch( pStep->op ){ case TK_UPDATE: { - sqlite3Update(pParse, - targetSrcList(pParse, pStep), - sqlite3ExprListDup(db, pStep->pExprList, 0), - sqlite3ExprDup(db, pStep->pWhere, 0), + sqlite3Update(pParse, + sqlite3SrcListDup(db, pStep->pSrc, 0), + sqlite3ExprListDup(db, pStep->pExprList, 0), + sqlite3ExprDup(db, pStep->pWhere, 0), pParse->eOrconf, 0, 0, 0 ); + sqlite3VdbeAddOp0(v, OP_ResetCount); break; } case TK_INSERT: { - sqlite3Insert(pParse, - targetSrcList(pParse, pStep), - sqlite3SelectDup(db, pStep->pSelect, 0), - sqlite3IdListDup(db, pStep->pIdList), + sqlite3Insert(pParse, + sqlite3SrcListDup(db, pStep->pSrc, 0), + sqlite3SelectDup(db, pStep->pSelect, 0), + sqlite3IdListDup(db, pStep->pIdList), pParse->eOrconf, sqlite3UpsertDup(db, pStep->pUpsert) ); + sqlite3VdbeAddOp0(v, OP_ResetCount); break; } case TK_DELETE: { - sqlite3DeleteFrom(pParse, - targetSrcList(pParse, pStep), + sqlite3DeleteFrom(pParse, + sqlite3SrcListDup(db, pStep->pSrc, 0), sqlite3ExprDup(db, pStep->pWhere, 0), 0, 0 ); + sqlite3VdbeAddOp0(v, OP_ResetCount); break; } default: assert( pStep->op==TK_SELECT ); { @@ -132266,9 +159365,6 @@ static int codeTriggerProgram( sqlite3SelectDelete(db, pSelect); break; } - } - if( pStep->op!=TK_SELECT ){ - sqlite3VdbeAddOp0(v, OP_ResetCount); } } @@ -132311,7 +159407,7 @@ static void transferParseError(Parse *pTo, Parse *pFrom){ } /* -** Create and populate a new TriggerPrg object with a sub-program +** Create and populate a new TriggerPrg object with a sub-program ** implementing trigger pTrigger with ON CONFLICT policy orconf. */ static TriggerPrg *codeRowTrigger( @@ -132320,21 +159416,35 @@ static TriggerPrg *codeRowTrigger( Table *pTab, /* The table pTrigger is attached to */ int orconf /* ON CONFLICT policy to code trigger program with */ ){ - Parse *pTop = sqlite3ParseToplevel(pParse); + Parse *pTop; /* Top level Parse object */ sqlite3 *db = pParse->db; /* Database handle */ TriggerPrg *pPrg; /* Value to return */ Expr *pWhen = 0; /* Duplicate of trigger WHEN expression */ Vdbe *v; /* Temporary VM */ NameContext sNC; /* Name context for sub-vdbe */ SubProgram *pProgram = 0; /* Sub-vdbe for trigger program */ - Parse *pSubParse; /* Parse context for sub-vdbe */ int iEndTrigger = 0; /* Label to jump to if WHEN is false */ + Parse sSubParse; /* Parse context for sub-vdbe */ + int nDepth; /* Trigger depth */ + + /* Ensure that triggers are not chained too deep. This test is linear + ** in the chaining depth, but sensible code ought not be chaining + ** triggers excessively, so that shouldn't be a problem. + */ + pTop = pParse; + for(nDepth=0; pTop->pOuterParse; pTop = pTop->pOuterParse, nDepth++){} + if( nDepth>=db->aLimit[SQLITE_LIMIT_TRIGGER_DEPTH] ){ + sqlite3ErrorMsg(pParse, "triggers nested too deep"); + return 0; + } + pTop = sqlite3ParseToplevel(pParse); assert( pTrigger->zName==0 || pTab==tableOfTrigger(pTrigger) ); assert( pTop->pVdbe ); + /* Allocate the TriggerPrg and SubProgram objects. To ensure that they - ** are freed if an error occurs, link them into the Parse.pTriggerPrg + ** are freed if an error occurs, link them into the Parse.pTriggerPrg ** list of the top-level Parse object sooner rather than later. */ pPrg = sqlite3DbMallocZero(db, sizeof(TriggerPrg)); if( !pPrg ) return 0; @@ -132348,23 +159458,23 @@ static TriggerPrg *codeRowTrigger( pPrg->aColmask[0] = 0xffffffff; pPrg->aColmask[1] = 0xffffffff; - /* Allocate and populate a new Parse context to use for coding the + /* Allocate and populate a new Parse context to use for coding the ** trigger sub-program. */ - pSubParse = sqlite3StackAllocZero(db, sizeof(Parse)); - if( !pSubParse ) return 0; + sqlite3ParseObjectInit(&sSubParse, db); memset(&sNC, 0, sizeof(sNC)); - sNC.pParse = pSubParse; - pSubParse->db = db; - pSubParse->pTriggerTab = pTab; - pSubParse->pToplevel = pTop; - pSubParse->zAuthContext = pTrigger->zName; - pSubParse->eTriggerOp = pTrigger->op; - pSubParse->nQueryLoop = pParse->nQueryLoop; - pSubParse->disableVtab = pParse->disableVtab; - - v = sqlite3GetVdbe(pSubParse); + sNC.pParse = &sSubParse; + sSubParse.pTriggerTab = pTab; + sSubParse.pToplevel = pTop; + sSubParse.zAuthContext = pTrigger->zName; + sSubParse.eTriggerOp = pTrigger->op; + sSubParse.nQueryLoop = pParse->nQueryLoop; + sSubParse.prepFlags = pParse->prepFlags; + sSubParse.oldmask = 0; + sSubParse.newmask = 0; + + v = sqlite3GetVdbe(&sSubParse); if( v ){ - VdbeComment((v, "Start: %s.%s (%s %s%s%s ON %s)", + VdbeComment((v, "Start: %s.%s (%s %s%s%s ON %s)", pTrigger->zName, onErrorText(orconf), (pTrigger->tr_tm==TRIGGER_BEFORE ? "BEFORE" : "AFTER"), (pTrigger->op==TK_UPDATE ? "UPDATE" : ""), @@ -132374,28 +159484,28 @@ static TriggerPrg *codeRowTrigger( )); #ifndef SQLITE_OMIT_TRACE if( pTrigger->zName ){ - sqlite3VdbeChangeP4(v, -1, + sqlite3VdbeChangeP4(v, -1, sqlite3MPrintf(db, "-- TRIGGER %s", pTrigger->zName), P4_DYNAMIC ); } #endif /* If one was specified, code the WHEN clause. If it evaluates to false - ** (or NULL) the sub-vdbe is immediately halted by jumping to the + ** (or NULL) the sub-vdbe is immediately halted by jumping to the ** OP_Halt inserted at the end of the program. */ if( pTrigger->pWhen ){ pWhen = sqlite3ExprDup(db, pTrigger->pWhen, 0); - if( SQLITE_OK==sqlite3ResolveExprNames(&sNC, pWhen) - && db->mallocFailed==0 + if( db->mallocFailed==0 + && SQLITE_OK==sqlite3ResolveExprNames(&sNC, pWhen) ){ - iEndTrigger = sqlite3VdbeMakeLabel(pSubParse); - sqlite3ExprIfFalse(pSubParse, pWhen, iEndTrigger, SQLITE_JUMPIFNULL); + iEndTrigger = sqlite3VdbeMakeLabel(&sSubParse); + sqlite3ExprIfFalse(&sSubParse, pWhen, iEndTrigger, SQLITE_JUMPIFNULL); } sqlite3ExprDelete(db, pWhen); } /* Code the trigger program into the sub-vdbe. */ - codeTriggerProgram(pSubParse, pTrigger->step_list, orconf); + codeTriggerProgram(&sSubParse, pTrigger->step_list, orconf); /* Insert an OP_Halt at the end of the sub-program. */ if( iEndTrigger ){ @@ -132403,27 +159513,27 @@ static TriggerPrg *codeRowTrigger( } sqlite3VdbeAddOp0(v, OP_Halt); VdbeComment((v, "End: %s.%s", pTrigger->zName, onErrorText(orconf))); + transferParseError(pParse, &sSubParse); - transferParseError(pParse, pSubParse); - if( db->mallocFailed==0 && pParse->nErr==0 ){ + if( pParse->nErr==0 ){ + assert( db->mallocFailed==0 ); pProgram->aOp = sqlite3VdbeTakeOpArray(v, &pProgram->nOp, &pTop->nMaxArg); } - pProgram->nMem = pSubParse->nMem; - pProgram->nCsr = pSubParse->nTab; + pProgram->nMem = sSubParse.nMem; + pProgram->nCsr = sSubParse.nTab; pProgram->token = (void *)pTrigger; - pPrg->aColmask[0] = pSubParse->oldmask; - pPrg->aColmask[1] = pSubParse->newmask; + pPrg->aColmask[0] = sSubParse.oldmask; + pPrg->aColmask[1] = sSubParse.newmask; sqlite3VdbeDelete(v); + }else{ + transferParseError(pParse, &sSubParse); } - assert( !pSubParse->pAinc && !pSubParse->pZombieTab ); - assert( !pSubParse->pTriggerPrg && !pSubParse->nMaxArg ); - sqlite3ParserReset(pSubParse); - sqlite3StackFree(db, pSubParse); - + assert( !sSubParse.pTriggerPrg && !sSubParse.nMaxArg ); + sqlite3ParseObjectReset(&sSubParse); return pPrg; } - + /* ** Return a pointer to a TriggerPrg object containing the sub-program for ** trigger pTrigger with default ON CONFLICT algorithm orconf. If no such @@ -132445,21 +159555,22 @@ static TriggerPrg *getRowTrigger( ** process of being coded). If this is the case, then an entry with ** a matching TriggerPrg.pTrigger field will be present somewhere ** in the Parse.pTriggerPrg list. Search for such an entry. */ - for(pPrg=pRoot->pTriggerPrg; - pPrg && (pPrg->pTrigger!=pTrigger || pPrg->orconf!=orconf); + for(pPrg=pRoot->pTriggerPrg; + pPrg && (pPrg->pTrigger!=pTrigger || pPrg->orconf!=orconf); pPrg=pPrg->pNext ); /* If an existing TriggerPrg could not be located, create a new one. */ if( !pPrg ){ pPrg = codeRowTrigger(pParse, pTrigger, pTab, orconf); + pParse->db->errByteOffset = -1; } return pPrg; } /* -** Generate code for the trigger program associated with trigger p on +** Generate code for the trigger program associated with trigger p on ** table pTab. The reg, orconf and ignoreJump parameters passed to this ** function are the same as those described in the header function for ** sqlite3CodeRowTrigger() @@ -132475,9 +159586,9 @@ SQLITE_PRIVATE void sqlite3CodeRowTriggerDirect( Vdbe *v = sqlite3GetVdbe(pParse); /* Main VM */ TriggerPrg *pPrg; pPrg = getRowTrigger(pParse, p, pTab, orconf); - assert( pPrg || pParse->nErr || pParse->db->mallocFailed ); + assert( pPrg || pParse->nErr ); - /* Code the OP_Program opcode in the parent VDBE. P4 of the OP_Program + /* Code the OP_Program opcode in the parent VDBE. P4 of the OP_Program ** is a pointer to the sub-vdbe containing the trigger program. */ if( pPrg ){ int bRecursive = (p->zName && 0==(pParse->db->flags&SQLITE_RecTriggers)); @@ -132492,7 +159603,7 @@ SQLITE_PRIVATE void sqlite3CodeRowTriggerDirect( ** invocation is disallowed if (a) the sub-program is really a trigger, ** not a foreign key action, and (b) the flag to enable recursive triggers ** is clear. */ - sqlite3VdbeChangeP5(v, (u8)bRecursive); + sqlite3VdbeChangeP5(v, (u16)bRecursive); } } @@ -132506,7 +159617,7 @@ SQLITE_PRIVATE void sqlite3CodeRowTriggerDirect( ** If there are no triggers that fire at the specified time for the specified ** operation on pTab, this function is a no-op. ** -** The reg argument is the address of the first in an array of registers +** The reg argument is the address of the first in an array of registers ** that contain the values substituted for the new.* and old.* references ** in the trigger program. If N is the number of columns in table pTab ** (a copy of pTab->nCol), then registers are populated as follows: @@ -132518,17 +159629,17 @@ SQLITE_PRIVATE void sqlite3CodeRowTriggerDirect( ** ... ... ** reg+N OLD.* value of right-most column of pTab ** reg+N+1 NEW.rowid -** reg+N+2 OLD.* value of left-most column of pTab +** reg+N+2 NEW.* value of left-most column of pTab ** ... ... ** reg+N+N+1 NEW.* value of right-most column of pTab ** ** For ON DELETE triggers, the registers containing the NEW.* values will -** never be accessed by the trigger program, so they are not allocated or -** populated by the caller (there is no data to populate them with anyway). +** never be accessed by the trigger program, so they are not allocated or +** populated by the caller (there is no data to populate them with anyway). ** Similarly, for ON INSERT triggers the values stored in the OLD.* registers ** are never accessed, and so are not allocated by the caller. So, for an ** ON INSERT trigger, the value passed to this function as parameter reg -** is not a readable register, although registers (reg+N) through +** is not a readable register, although registers (reg+N) through ** (reg+N+N+1) are. ** ** Parameter orconf is the default conflict resolution algorithm for the @@ -132560,23 +159671,31 @@ SQLITE_PRIVATE void sqlite3CodeRowTrigger( ** or else it must be a TEMP trigger. */ assert( p->pSchema!=0 ); assert( p->pTabSchema!=0 ); - assert( p->pSchema==p->pTabSchema + assert( p->pSchema==p->pTabSchema || p->pSchema==pParse->db->aDb[1].pSchema ); - /* Determine whether we should code this trigger */ - if( p->op==op - && p->tr_tm==tr_tm + /* Determine whether we should code this trigger. One of two choices: + ** 1. The trigger is an exact match to the current DML statement + ** 2. This is a RETURNING trigger for INSERT but we are currently + ** doing the UPDATE part of an UPSERT. + */ + if( (p->op==op || (p->bReturning && p->op==TK_INSERT && op==TK_UPDATE)) + && p->tr_tm==tr_tm && checkColumnOverlap(p->pColumns, pChanges) ){ - sqlite3CodeRowTriggerDirect(pParse, p, pTab, reg, orconf, ignoreJump); + if( !p->bReturning ){ + sqlite3CodeRowTriggerDirect(pParse, p, pTab, reg, orconf, ignoreJump); + }else if( sqlite3IsToplevel(pParse) ){ + codeReturningTrigger(pParse, p, pTab, reg); + } } } } /* -** Triggers may access values stored in the old.* or new.* pseudo-table. -** This function returns a 32-bit bitmask indicating which columns of the -** old.* or new.* tables actually are used by triggers. This information +** Triggers may access values stored in the old.* or new.* pseudo-table. +** This function returns a 32-bit bitmask indicating which columns of the +** old.* or new.* tables actually are used by triggers. This information ** may be used by the caller, for example, to avoid having to load the entire ** old.* record into memory when executing an UPDATE or DELETE command. ** @@ -132586,7 +159705,7 @@ SQLITE_PRIVATE void sqlite3CodeRowTrigger( ** are more than 32 columns in the table, and at least one of the columns ** with an index greater than 32 may be accessed, 0xffffffff is returned. ** -** It is not possible to determine if the old.rowid or new.rowid column is +** It is not possible to determine if the old.rowid or new.rowid column is ** accessed by triggers. The caller must always assume that it is. ** ** Parameter isNew must be either 1 or 0. If it is 0, then the mask returned @@ -132612,14 +159731,22 @@ SQLITE_PRIVATE u32 sqlite3TriggerColmask( Trigger *p; assert( isNew==1 || isNew==0 ); + if( IsView(pTab) ){ + return 0xffffffff; + } for(p=pTrigger; p; p=p->pNext){ - if( p->op==op && (tr_tm&p->tr_tm) + if( p->op==op + && (tr_tm&p->tr_tm) && checkColumnOverlap(p->pColumns,pChanges) ){ - TriggerPrg *pPrg; - pPrg = getRowTrigger(pParse, p, pTab, orconf); - if( pPrg ){ - mask |= pPrg->aColmask[isNew]; + if( p->bReturning ){ + mask = 0xffffffff; + }else{ + TriggerPrg *pPrg; + pPrg = getRowTrigger(pParse, p, pTab, orconf); + if( pPrg ){ + mask |= pPrg->aColmask[isNew]; + } } } } @@ -132663,10 +159790,10 @@ static void updateVirtualTable( /* ** The most recently coded instruction was an OP_Column to retrieve the -** i-th column of table pTab. This routine sets the P4 parameter of the +** i-th column of table pTab. This routine sets the P4 parameter of the ** OP_Column to the default value, if any. ** -** The default value of a column is specified by a DEFAULT clause in the +** The default value of a column is specified by a DEFAULT clause in the ** column definition. This was either supplied by the user when the table ** was created, or added later to the table definition by an ALTER TABLE ** command. If the latter, then the row-records in the table btree on disk @@ -132675,38 +159802,42 @@ static void updateVirtualTable( ** If the former, then all row-records are guaranteed to include a value ** for the column and the P4 value is not required. ** -** Column definitions created by an ALTER TABLE command may only have +** Column definitions created by an ALTER TABLE command may only have ** literal default values specified: a number, null or a string. (If a more -** complicated default expression value was provided, it is evaluated +** complicated default expression value was provided, it is evaluated ** when the ALTER TABLE is executed and one of the literal values written -** into the sqlite_master table.) +** into the sqlite_schema table.) ** ** Therefore, the P4 parameter is only required if the default value for ** the column is a literal number, string or null. The sqlite3ValueFromExpr() ** function is capable of transforming these types of expressions into ** sqlite3_value objects. ** -** If parameter iReg is not negative, code an OP_RealAffinity instruction -** on register iReg. This is used when an equivalent integer value is -** stored in place of an 8-byte floating point value in order to save -** space. +** If column as REAL affinity and the table is an ordinary b-tree table +** (not a virtual table) then the value might have been stored as an +** integer. In that case, add an OP_RealAffinity opcode to make sure +** it has been converted into REAL. */ SQLITE_PRIVATE void sqlite3ColumnDefault(Vdbe *v, Table *pTab, int i, int iReg){ + Column *pCol; assert( pTab!=0 ); - if( !pTab->pSelect ){ + assert( pTab->nCol>i ); + pCol = &pTab->aCol[i]; + if( pCol->iDflt ){ sqlite3_value *pValue = 0; u8 enc = ENC(sqlite3VdbeDb(v)); - Column *pCol = &pTab->aCol[i]; - VdbeComment((v, "%s.%s", pTab->zName, pCol->zName)); + assert( !IsView(pTab) ); + VdbeComment((v, "%s.%s", pTab->zName, pCol->zCnName)); assert( inCol ); - sqlite3ValueFromExpr(sqlite3VdbeDb(v), pCol->pDflt, enc, + sqlite3ValueFromExpr(sqlite3VdbeDb(v), + sqlite3ColumnExpr(pTab,pCol), enc, pCol->affinity, &pValue); if( pValue ){ sqlite3VdbeAppendP4(v, pValue, P4_MEM); } } #ifndef SQLITE_OMIT_FLOATING_POINT - if( pTab->aCol[i].affinity==SQLITE_AFF_REAL ){ + if( pCol->affinity==SQLITE_AFF_REAL && !IsVirtual(pTab) ){ sqlite3VdbeAddOp1(v, OP_RealAffinity, iReg); } #endif @@ -132763,12 +159894,153 @@ static int indexWhereClauseMightChange( aXRef, chngRowid); } +/* +** Allocate and return a pointer to an expression of type TK_ROW with +** Expr.iColumn set to value (iCol+1). The resolver will modify the +** expression to be a TK_COLUMN reading column iCol of the first +** table in the source-list (pSrc->a[0]). +*/ +static Expr *exprRowColumn(Parse *pParse, int iCol){ + Expr *pRet = sqlite3PExpr(pParse, TK_ROW, 0, 0); + if( pRet ) pRet->iColumn = iCol+1; + return pRet; +} + +/* +** Assuming both the pLimit and pOrderBy parameters are NULL, this function +** generates VM code to run the query: +** +** SELECT , pChanges FROM pTabList WHERE pWhere +** +** and write the results to the ephemeral table already opened as cursor +** iEph. None of pChanges, pTabList or pWhere are modified or consumed by +** this function, they must be deleted by the caller. +** +** Or, if pLimit and pOrderBy are not NULL, and pTab is not a view: +** +** SELECT , pChanges FROM pTabList +** WHERE pWhere +** GROUP BY +** ORDER BY pOrderBy LIMIT pLimit +** +** If pTab is a view, the GROUP BY clause is omitted. +** +** Exactly how results are written to table iEph, and exactly what +** the in the query above are is determined by the type +** of table pTabList->a[0].pTab. +** +** If the table is a WITHOUT ROWID table, then argument pPk must be its +** PRIMARY KEY. In this case are the primary key columns +** of the table, in order. The results of the query are written to ephemeral +** table iEph as index keys, using OP_IdxInsert. +** +** If the table is actually a view, then are all columns of +** the view. The results are written to the ephemeral table iEph as records +** with automatically assigned integer keys. +** +** If the table is a virtual or ordinary intkey table, then +** is its rowid. For a virtual table, the results are written to iEph as +** records with automatically assigned integer keys For intkey tables, the +** rowid value in is used as the integer key, and the +** remaining fields make up the table record. +*/ +static void updateFromSelect( + Parse *pParse, /* Parse context */ + int iEph, /* Cursor for open eph. table */ + Index *pPk, /* PK if table 0 is WITHOUT ROWID */ + ExprList *pChanges, /* List of expressions to return */ + SrcList *pTabList, /* List of tables to select from */ + Expr *pWhere, /* WHERE clause for query */ + ExprList *pOrderBy, /* ORDER BY clause */ + Expr *pLimit /* LIMIT clause */ +){ + int i; + SelectDest dest; + Select *pSelect = 0; + ExprList *pList = 0; + ExprList *pGrp = 0; + Expr *pLimit2 = 0; + ExprList *pOrderBy2 = 0; + sqlite3 *db = pParse->db; + Table *pTab = pTabList->a[0].pSTab; + SrcList *pSrc; + Expr *pWhere2; + int eDest; + +#ifdef SQLITE_ENABLE_UPDATE_DELETE_LIMIT + if( pOrderBy && pLimit==0 ) { + sqlite3ErrorMsg(pParse, "ORDER BY without LIMIT on UPDATE"); + return; + } + pOrderBy2 = sqlite3ExprListDup(db, pOrderBy, 0); + pLimit2 = sqlite3ExprDup(db, pLimit, 0); +#else + UNUSED_PARAMETER(pOrderBy); + UNUSED_PARAMETER(pLimit); +#endif + + pSrc = sqlite3SrcListDup(db, pTabList, 0); + pWhere2 = sqlite3ExprDup(db, pWhere, 0); + + assert( pTabList->nSrc>1 ); + if( pSrc ){ + assert( pSrc->a[0].fg.notCte ); + pSrc->a[0].iCursor = -1; + pSrc->a[0].pSTab->nTabRef--; + pSrc->a[0].pSTab = 0; + } + if( pPk ){ + for(i=0; inKeyCol; i++){ + Expr *pNew = exprRowColumn(pParse, pPk->aiColumn[i]); +#ifdef SQLITE_ENABLE_UPDATE_DELETE_LIMIT + if( pLimit ){ + pGrp = sqlite3ExprListAppend(pParse, pGrp, sqlite3ExprDup(db, pNew, 0)); + } +#endif + pList = sqlite3ExprListAppend(pParse, pList, pNew); + } + eDest = IsVirtual(pTab) ? SRT_Table : SRT_Upfrom; + }else if( IsView(pTab) ){ + for(i=0; inCol; i++){ + pList = sqlite3ExprListAppend(pParse, pList, exprRowColumn(pParse, i)); + } + eDest = SRT_Table; + }else{ + eDest = IsVirtual(pTab) ? SRT_Table : SRT_Upfrom; + pList = sqlite3ExprListAppend(pParse, 0, sqlite3PExpr(pParse,TK_ROW,0,0)); +#ifdef SQLITE_ENABLE_UPDATE_DELETE_LIMIT + if( pLimit ){ + pGrp = sqlite3ExprListAppend(pParse, 0, sqlite3PExpr(pParse,TK_ROW,0,0)); + } +#endif + } + assert( pChanges!=0 || pParse->db->mallocFailed ); + if( pChanges ){ + for(i=0; inExpr; i++){ + pList = sqlite3ExprListAppend(pParse, pList, + sqlite3ExprDup(db, pChanges->a[i].pExpr, 0) + ); + } + } + pSelect = sqlite3SelectNew(pParse, pList, + pSrc, pWhere2, pGrp, 0, pOrderBy2, + SF_UFSrcCheck|SF_IncludeHidden|SF_UpdateFrom, pLimit2 + ); + if( pSelect ) pSelect->selFlags |= SF_OrderByReqd; + sqlite3SelectDestInit(&dest, eDest, iEph); + dest.iSDParm2 = (pPk ? pPk->nKeyCol : -1); + sqlite3Select(pParse, pSelect, &dest); + sqlite3SelectDelete(db, pSelect); +} + /* ** Process an UPDATE statement. ** -** UPDATE OR IGNORE table_wxyz SET a=b, c=d WHERE e<5 AND f NOT NULL; -** \_______/ \________/ \______/ \________________/ -* onError pTabList pChanges pWhere +** UPDATE OR IGNORE tbl SET a=b, c=d FROM tbl2... WHERE e<5 AND f NOT NULL; +** \_______/ \_/ \______/ \_____/ \________________/ +** onError | pChanges | pWhere +** \_______________________/ +** pTabList */ SQLITE_PRIVATE void sqlite3Update( Parse *pParse, /* The parser context */ @@ -132780,19 +160052,20 @@ SQLITE_PRIVATE void sqlite3Update( Expr *pLimit, /* LIMIT clause. May be null */ Upsert *pUpsert /* ON CONFLICT clause, or null */ ){ - int i, j; /* Loop counters */ + int i, j, k; /* Loop counters */ Table *pTab; /* The table to be updated */ int addrTop = 0; /* VDBE instruction address of the start of the loop */ - WhereInfo *pWInfo; /* Information about the WHERE clause */ + WhereInfo *pWInfo = 0; /* Information about the WHERE clause */ Vdbe *v; /* The virtual database engine */ Index *pIdx; /* For looping over indices */ Index *pPk; /* The PRIMARY KEY index for WITHOUT ROWID tables */ int nIdx; /* Number of indices that need updating */ + int nAllIdx; /* Total number of indexes */ int iBaseCur; /* Base cursor number */ int iDataCur; /* Cursor for the canonical data btree */ int iIdxCur; /* Cursor for the first index */ sqlite3 *db; /* The database structure */ - int *aRegIdx = 0; /* First register in array assigned to each index */ + int *aRegIdx = 0; /* Registers for to each index and the main table */ int *aXRef = 0; /* aXRef[i] is the index in pChanges->a[] of the ** an expression for the i-th column of the table. ** aXRef[i]==-1 if the i-th column is not changed. */ @@ -132801,6 +160074,7 @@ SQLITE_PRIVATE void sqlite3Update( u8 chngRowid; /* Rowid changed in a normal table */ u8 chngKey; /* Either chngPk or chngRowid */ Expr *pRowidExpr = 0; /* Expression defining the new record number */ + int iRowidExpr = -1; /* Index of "rowid=" (or IPK) assignment in pChanges */ AuthContext sContext; /* The authorization context */ NameContext sNC; /* The name-context to resolve expressions in */ int iDb; /* Database containing the table being updated */ @@ -132823,6 +160097,8 @@ SQLITE_PRIVATE void sqlite3Update( int iPk = 0; /* First of nPk cells holding PRIMARY KEY value */ i16 nPk = 0; /* Number of components of the PRIMARY KEY */ int bReplace = 0; /* True if REPLACE conflict resolution might happen */ + int bFinishSeek = 1; /* The OP_FinishSeek opcode is needed */ + int nChangeFrom = 0; /* If there is a FROM, pChanges->nExpr, else 0 */ /* Register Allocations */ int regRowCount = 0; /* A count of rows changed */ @@ -132835,12 +160111,13 @@ SQLITE_PRIVATE void sqlite3Update( memset(&sContext, 0, sizeof(sContext)); db = pParse->db; - if( pParse->nErr || db->mallocFailed ){ + assert( db->pParse==pParse ); + if( pParse->nErr ){ goto update_cleanup; } - assert( pTabList->nSrc==1 ); + assert( db->mallocFailed==0 ); - /* Locate the table which we want to update. + /* Locate the table which we want to update. */ pTab = sqlite3SrcListLookup(pParse, pTabList); if( pTab==0 ) goto update_cleanup; @@ -132851,7 +160128,7 @@ SQLITE_PRIVATE void sqlite3Update( */ #ifndef SQLITE_OMIT_TRIGGER pTrigger = sqlite3TriggersExist(pParse, pTab, TK_UPDATE, pChanges, &tmask); - isView = pTab->pSelect!=0; + isView = IsView(pTab); assert( pTrigger || tmask==0 ); #else # define pTrigger 0 @@ -132863,8 +160140,23 @@ SQLITE_PRIVATE void sqlite3Update( # define isView 0 #endif +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x10000 ){ + sqlite3TreeViewLine(0, "In sqlite3Update() at %s:%d", __FILE__, __LINE__); + sqlite3TreeViewUpdate(pParse->pWith, pTabList, pChanges, pWhere, + onError, pOrderBy, pLimit, pUpsert, pTrigger); + } +#endif + + /* If there was a FROM clause, set nChangeFrom to the number of expressions + ** in the change-list. Otherwise, set it to 0. There cannot be a FROM + ** clause if this function is being called to generate code for part of + ** an UPSERT statement. */ + nChangeFrom = (pTabList->nSrc>1) ? pChanges->nExpr : 0; + assert( nChangeFrom==0 || pUpsert==0 ); + #ifdef SQLITE_ENABLE_UPDATE_DELETE_LIMIT - if( !isView ){ + if( !isView && nChangeFrom==0 ){ pWhere = sqlite3LimitWhere( pParse, pTabList, pWhere, pOrderBy, pLimit, "UPDATE" ); @@ -132876,7 +160168,7 @@ SQLITE_PRIVATE void sqlite3Update( if( sqlite3ViewGetColumnNames(pParse, pTab) ){ goto update_cleanup; } - if( sqlite3IsReadOnly(pParse, pTab, tmask) ){ + if( sqlite3IsReadOnly(pParse, pTab, pTrigger) ){ goto update_cleanup; } @@ -132903,13 +160195,13 @@ SQLITE_PRIVATE void sqlite3Update( } pTabList->a[0].iCursor = iDataCur; - /* Allocate space for aXRef[], aRegIdx[], and aToOpen[]. + /* Allocate space for aXRef[], aRegIdx[], and aToOpen[]. ** Initialize aXRef[] and aToOpen[] to their default values. */ - aXRef = sqlite3DbMallocRawNN(db, sizeof(int) * (pTab->nCol+nIdx) + nIdx+2 ); + aXRef = sqlite3DbMallocRawNN(db, sizeof(int) * (pTab->nCol+nIdx+1) + nIdx+2 ); if( aXRef==0 ) goto update_cleanup; aRegIdx = aXRef+pTab->nCol; - aToOpen = (u8*)(aRegIdx+nIdx); + aToOpen = (u8*)(aRegIdx+nIdx+1); memset(aToOpen, 1, nIdx+1); aToOpen[nIdx+1] = 0; for(i=0; inCol; i++) aXRef[i] = -1; @@ -132921,6 +160213,10 @@ SQLITE_PRIVATE void sqlite3Update( sNC.uNC.pUpsert = pUpsert; sNC.ncFlags = NC_UUpsert; + /* Begin generating code. */ + v = sqlite3GetVdbe(pParse); + if( v==0 ) goto update_cleanup; + /* Resolve the column names in all the expressions of the ** of the UPDATE statement. Also find the column index ** for each column to be updated in the pChanges array. For each @@ -132929,28 +160225,39 @@ SQLITE_PRIVATE void sqlite3Update( */ chngRowid = chngPk = 0; for(i=0; inExpr; i++){ - if( sqlite3ResolveExprNames(&sNC, pChanges->a[i].pExpr) ){ + /* If this is an UPDATE with a FROM clause, do not resolve expressions + ** here. The call to sqlite3Select() below will do that. */ + if( nChangeFrom==0 && sqlite3ResolveExprNames(&sNC, pChanges->a[i].pExpr) ){ goto update_cleanup; } - for(j=0; jnCol; j++){ - if( sqlite3StrICmp(pTab->aCol[j].zName, pChanges->a[i].zName)==0 ){ - if( j==pTab->iPKey ){ - chngRowid = 1; - pRowidExpr = pChanges->a[i].pExpr; - }else if( pPk && (pTab->aCol[j].colFlags & COLFLAG_PRIMKEY)!=0 ){ - chngPk = 1; - } - aXRef[j] = i; - break; + j = sqlite3ColumnIndex(pTab, pChanges->a[i].zEName); + if( j>=0 ){ + if( j==pTab->iPKey ){ + chngRowid = 1; + pRowidExpr = pChanges->a[i].pExpr; + iRowidExpr = i; + }else if( pPk && (pTab->aCol[j].colFlags & COLFLAG_PRIMKEY)!=0 ){ + chngPk = 1; + } +#ifndef SQLITE_OMIT_GENERATED_COLUMNS + else if( pTab->aCol[j].colFlags & COLFLAG_GENERATED ){ + testcase( pTab->aCol[j].colFlags & COLFLAG_VIRTUAL ); + testcase( pTab->aCol[j].colFlags & COLFLAG_STORED ); + sqlite3ErrorMsg(pParse, + "cannot UPDATE generated column \"%s\"", + pTab->aCol[j].zCnName); + goto update_cleanup; } - } - if( j>=pTab->nCol ){ - if( pPk==0 && sqlite3IsRowid(pChanges->a[i].zName) ){ +#endif + aXRef[j] = i; + }else{ + if( pPk==0 && sqlite3IsRowid(pChanges->a[i].zEName) ){ j = -1; chngRowid = 1; pRowidExpr = pChanges->a[i].pExpr; + iRowidExpr = i; }else{ - sqlite3ErrorMsg(pParse, "no such column: %s", pChanges->a[i].zName); + sqlite3ErrorMsg(pParse, "no such column: %s", pChanges->a[i].zEName); pParse->checkSchema = 1; goto update_cleanup; } @@ -132959,7 +160266,7 @@ SQLITE_PRIVATE void sqlite3Update( { int rc; rc = sqlite3AuthCheck(pParse, SQLITE_UPDATE, pTab->zName, - j<0 ? "ROWID" : pTab->aCol[j].zName, + j<0 ? "ROWID" : pTab->aCol[j].zCnName, db->aDb[iDb].zDbSName); if( rc==SQLITE_DENY ){ goto update_cleanup; @@ -132974,7 +160281,36 @@ SQLITE_PRIVATE void sqlite3Update( assert( chngPk==0 || chngPk==1 ); chngKey = chngRowid + chngPk; - /* The SET expressions are not actually used inside the WHERE loop. +#ifndef SQLITE_OMIT_GENERATED_COLUMNS + /* Mark generated columns as changing if their generator expressions + ** reference any changing column. The actual aXRef[] value for + ** generated expressions is not used, other than to check to see that it + ** is non-negative, so the value of aXRef[] for generated columns can be + ** set to any non-negative number. We use 99999 so that the value is + ** obvious when looking at aXRef[] in a symbolic debugger. + */ + if( pTab->tabFlags & TF_HasGenerated ){ + int bProgress; + testcase( pTab->tabFlags & TF_HasVirtual ); + testcase( pTab->tabFlags & TF_HasStored ); + do{ + bProgress = 0; + for(i=0; inCol; i++){ + if( aXRef[i]>=0 ) continue; + if( (pTab->aCol[i].colFlags & COLFLAG_GENERATED)==0 ) continue; + if( sqlite3ExprReferencesUpdatedColumn( + sqlite3ColumnExpr(pTab, &pTab->aCol[i]), + aXRef, chngRowid) + ){ + aXRef[i] = 99999; + bProgress = 1; + } + } + }while( bProgress ); + } +#endif + + /* The SET expressions are not actually used inside the WHERE loop. ** So reset the colUsed mask. Unless this is a virtual table. In that ** case, set all bits of the colUsed mask (to ensure that the virtual ** table implementation makes all columns available). @@ -132988,7 +160324,7 @@ SQLITE_PRIVATE void sqlite3Update( ** the key for accessing each index. */ if( onError==OE_Replace ) bReplace = 1; - for(j=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, j++){ + for(nAllIdx=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, nAllIdx++){ int reg; if( chngKey || hasFK>1 || pIdx==pPk || indexWhereClauseMightChange(pIdx,aXRef,chngRowid) @@ -133008,24 +160344,28 @@ SQLITE_PRIVATE void sqlite3Update( } } } - if( reg==0 ) aToOpen[j+1] = 0; - aRegIdx[j] = reg; + if( reg==0 ) aToOpen[nAllIdx+1] = 0; + aRegIdx[nAllIdx] = reg; } + aRegIdx[nAllIdx] = ++pParse->nMem; /* Register storing the table record */ if( bReplace ){ - /* If REPLACE conflict resolution might be invoked, open cursors on all + /* If REPLACE conflict resolution might be invoked, open cursors on all ** indexes in case they are needed to delete records. */ memset(aToOpen, 1, nIdx+1); } - /* Begin generating code. */ - v = sqlite3GetVdbe(pParse); - if( v==0 ) goto update_cleanup; if( pParse->nested==0 ) sqlite3VdbeCountChanges(v); sqlite3BeginWriteOperation(pParse, pTrigger || hasFK, iDb); /* Allocate required registers. */ if( !IsVirtual(pTab) ){ - regRowSet = ++pParse->nMem; + /* For now, regRowSet and aRegIdx[nAllIdx] share the same register. + ** If regRowSet turns out to be needed, then aRegIdx[nAllIdx] will be + ** reallocated. aRegIdx[nAllIdx] is the register in which the main + ** table record is written. regRowSet holds the RowSet for the + ** two-pass update algorithm. */ + assert( aRegIdx[nAllIdx]==pParse->nMem ); + regRowSet = aRegIdx[nAllIdx]; regOldRowid = regNewRowid = ++pParse->nMem; if( chngPk || pTrigger || hasFK ){ regOld = pParse->nMem + 1; @@ -133047,8 +160387,8 @@ SQLITE_PRIVATE void sqlite3Update( ** an ephemeral table. */ #if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER) - if( isView ){ - sqlite3MaterializeView(pParse, pTab, + if( nChangeFrom==0 && isView ){ + sqlite3MaterializeView(pParse, pTab, pWhere, pOrderBy, pLimit, iDataCur ); pOrderBy = 0; @@ -133059,7 +160399,7 @@ SQLITE_PRIVATE void sqlite3Update( /* Resolve the column names in all the expressions in the ** WHERE clause. */ - if( sqlite3ResolveExprNames(&sNC, pWhere) ){ + if( nChangeFrom==0 && sqlite3ResolveExprNames(&sNC, pWhere) ){ goto update_cleanup; } @@ -133080,129 +160420,180 @@ SQLITE_PRIVATE void sqlite3Update( if( (db->flags&SQLITE_CountRows)!=0 && !pParse->pTriggerTab && !pParse->nested + && !pParse->bReturning && pUpsert==0 ){ regRowCount = ++pParse->nMem; sqlite3VdbeAddOp2(v, OP_Integer, 0, regRowCount); } - if( HasRowid(pTab) ){ + if( nChangeFrom==0 && HasRowid(pTab) ){ sqlite3VdbeAddOp3(v, OP_Null, 0, regRowSet, regOldRowid); + iEph = pParse->nTab++; + addrOpen = sqlite3VdbeAddOp3(v, OP_OpenEphemeral, iEph, 0, regRowSet); }else{ - assert( pPk!=0 ); - nPk = pPk->nKeyCol; + assert( pPk!=0 || HasRowid(pTab) ); + nPk = pPk ? pPk->nKeyCol : 0; iPk = pParse->nMem+1; pParse->nMem += nPk; + pParse->nMem += nChangeFrom; regKey = ++pParse->nMem; if( pUpsert==0 ){ + int nEphCol = nPk + nChangeFrom + (isView ? pTab->nCol : 0); iEph = pParse->nTab++; - sqlite3VdbeAddOp3(v, OP_Null, 0, iPk, iPk+nPk-1); - addrOpen = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, iEph, nPk); - sqlite3VdbeSetP4KeyInfo(pParse, pPk); + if( pPk ) sqlite3VdbeAddOp3(v, OP_Null, 0, iPk, iPk+nPk-1); + addrOpen = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, iEph, nEphCol); + if( pPk ){ + KeyInfo *pKeyInfo = sqlite3KeyInfoOfIndex(pParse, pPk); + if( pKeyInfo ){ + pKeyInfo->nAllField = nEphCol; + sqlite3VdbeAppendP4(v, pKeyInfo, P4_KEYINFO); + } + } + if( nChangeFrom ){ + updateFromSelect( + pParse, iEph, pPk, pChanges, pTabList, pWhere, pOrderBy, pLimit + ); +#ifndef SQLITE_OMIT_SUBQUERY + if( isView ) iDataCur = iEph; +#endif + } } } - - if( pUpsert ){ - /* If this is an UPSERT, then all cursors have already been opened by - ** the outer INSERT and the data cursor should be pointing at the row - ** that is to be updated. So bypass the code that searches for the - ** row(s) to be updated. - */ - pWInfo = 0; - eOnePass = ONEPASS_SINGLE; - sqlite3ExprIfFalse(pParse, pWhere, labelBreak, SQLITE_JUMPIFNULL); + + if( nChangeFrom ){ + sqlite3MultiWrite(pParse); + eOnePass = ONEPASS_OFF; + nKey = nPk; + regKey = iPk; }else{ - /* Begin the database scan. - ** - ** Do not consider a single-pass strategy for a multi-row update if - ** there are any triggers or foreign keys to process, or rows may - ** be deleted as a result of REPLACE conflict handling. Any of these - ** things might disturb a cursor being used to scan through the table - ** or index, causing a single-pass approach to malfunction. */ - flags = WHERE_ONEPASS_DESIRED|WHERE_SEEK_UNIQ_TABLE; - if( !pParse->nested && !pTrigger && !hasFK && !chngKey && !bReplace ){ - flags |= WHERE_ONEPASS_MULTIROW; - } - pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0, flags, iIdxCur); - if( pWInfo==0 ) goto update_cleanup; - - /* A one-pass strategy that might update more than one row may not - ** be used if any column of the index used for the scan is being - ** updated. Otherwise, if there is an index on "b", statements like - ** the following could create an infinite loop: - ** - ** UPDATE t1 SET b=b+1 WHERE b>? - ** - ** Fall back to ONEPASS_OFF if where.c has selected a ONEPASS_MULTI - ** strategy that uses an index for which one or more columns are being - ** updated. */ - eOnePass = sqlite3WhereOkOnePass(pWInfo, aiCurOnePass); - if( eOnePass!=ONEPASS_SINGLE ){ - sqlite3MultiWrite(pParse); - if( eOnePass==ONEPASS_MULTI ){ - int iCur = aiCurOnePass[1]; - if( iCur>=0 && iCur!=iDataCur && aToOpen[iCur-iBaseCur] ){ - eOnePass = ONEPASS_OFF; + if( pUpsert ){ + /* If this is an UPSERT, then all cursors have already been opened by + ** the outer INSERT and the data cursor should be pointing at the row + ** that is to be updated. So bypass the code that searches for the + ** row(s) to be updated. + */ + pWInfo = 0; + eOnePass = ONEPASS_SINGLE; + sqlite3ExprIfFalse(pParse, pWhere, labelBreak, SQLITE_JUMPIFNULL); + bFinishSeek = 0; + }else{ + /* Begin the database scan. + ** + ** Do not consider a single-pass strategy for a multi-row update if + ** there is anything that might disrupt the cursor being used to do + ** the UPDATE: + ** (1) This is a nested UPDATE + ** (2) There are triggers + ** (3) There are FOREIGN KEY constraints + ** (4) There are REPLACE conflict handlers + ** (5) There are subqueries in the WHERE clause + */ + flags = WHERE_ONEPASS_DESIRED; + if( !pParse->nested + && !pTrigger + && !hasFK + && !chngKey + && !bReplace + && (pWhere==0 || !ExprHasProperty(pWhere, EP_Subquery)) + ){ + flags |= WHERE_ONEPASS_MULTIROW; + } + pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere,0,0,0,flags,iIdxCur); + if( pWInfo==0 ) goto update_cleanup; + + /* A one-pass strategy that might update more than one row may not + ** be used if any column of the index used for the scan is being + ** updated. Otherwise, if there is an index on "b", statements like + ** the following could create an infinite loop: + ** + ** UPDATE t1 SET b=b+1 WHERE b>? + ** + ** Fall back to ONEPASS_OFF if where.c has selected a ONEPASS_MULTI + ** strategy that uses an index for which one or more columns are being + ** updated. */ + eOnePass = sqlite3WhereOkOnePass(pWInfo, aiCurOnePass); + bFinishSeek = sqlite3WhereUsesDeferredSeek(pWInfo); + if( eOnePass!=ONEPASS_SINGLE ){ + sqlite3MultiWrite(pParse); + if( eOnePass==ONEPASS_MULTI ){ + int iCur = aiCurOnePass[1]; + if( iCur>=0 && iCur!=iDataCur && aToOpen[iCur-iBaseCur] ){ + eOnePass = ONEPASS_OFF; + } + assert( iCur!=iDataCur || !HasRowid(pTab) ); } - assert( iCur!=iDataCur || !HasRowid(pTab) ); } } - } - if( HasRowid(pTab) ){ - /* Read the rowid of the current row of the WHERE scan. In ONEPASS_OFF - ** mode, write the rowid into the FIFO. In either of the one-pass modes, - ** leave it in register regOldRowid. */ - sqlite3VdbeAddOp2(v, OP_Rowid, iDataCur, regOldRowid); - if( eOnePass==ONEPASS_OFF ){ - sqlite3VdbeAddOp2(v, OP_RowSetAdd, regRowSet, regOldRowid); - } - }else{ - /* Read the PK of the current row into an array of registers. In - ** ONEPASS_OFF mode, serialize the array into a record and store it in - ** the ephemeral table. Or, in ONEPASS_SINGLE or MULTI mode, change - ** the OP_OpenEphemeral instruction to a Noop (the ephemeral table - ** is not required) and leave the PK fields in the array of registers. */ - for(i=0; iaiColumn[i]>=0 ); - sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur,pPk->aiColumn[i],iPk+i); - } - if( eOnePass ){ - if( addrOpen ) sqlite3VdbeChangeToNoop(v, addrOpen); - nKey = nPk; - regKey = iPk; + if( HasRowid(pTab) ){ + /* Read the rowid of the current row of the WHERE scan. In ONEPASS_OFF + ** mode, write the rowid into the FIFO. In either of the one-pass modes, + ** leave it in register regOldRowid. */ + sqlite3VdbeAddOp2(v, OP_Rowid, iDataCur, regOldRowid); + if( eOnePass==ONEPASS_OFF ){ + aRegIdx[nAllIdx] = ++pParse->nMem; + sqlite3VdbeAddOp3(v, OP_Insert, iEph, regRowSet, regOldRowid); + }else{ + if( ALWAYS(addrOpen) ) sqlite3VdbeChangeToNoop(v, addrOpen); + } }else{ - sqlite3VdbeAddOp4(v, OP_MakeRecord, iPk, nPk, regKey, - sqlite3IndexAffinityStr(db, pPk), nPk); - sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iEph, regKey, iPk, nPk); + /* Read the PK of the current row into an array of registers. In + ** ONEPASS_OFF mode, serialize the array into a record and store it in + ** the ephemeral table. Or, in ONEPASS_SINGLE or MULTI mode, change + ** the OP_OpenEphemeral instruction to a Noop (the ephemeral table + ** is not required) and leave the PK fields in the array of registers. */ + for(i=0; iaiColumn[i]>=0 ); + sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, + pPk->aiColumn[i], iPk+i); + } + if( eOnePass ){ + if( addrOpen ) sqlite3VdbeChangeToNoop(v, addrOpen); + nKey = nPk; + regKey = iPk; + }else{ + sqlite3VdbeAddOp4(v, OP_MakeRecord, iPk, nPk, regKey, + sqlite3IndexAffinityStr(db, pPk), nPk); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iEph, regKey, iPk, nPk); + } } } if( pUpsert==0 ){ - if( eOnePass!=ONEPASS_MULTI ){ + if( nChangeFrom==0 && eOnePass!=ONEPASS_MULTI ){ sqlite3WhereEnd(pWInfo); } - + if( !isView ){ int addrOnce = 0; - + int iNotUsed1 = 0; + int iNotUsed2 = 0; + /* Open every index that needs updating. */ if( eOnePass!=ONEPASS_OFF ){ if( aiCurOnePass[0]>=0 ) aToOpen[aiCurOnePass[0]-iBaseCur] = 0; if( aiCurOnePass[1]>=0 ) aToOpen[aiCurOnePass[1]-iBaseCur] = 0; } - + if( eOnePass==ONEPASS_MULTI && (nIdx-(aiCurOnePass[1]>=0))>0 ){ addrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); } sqlite3OpenTableAndIndices(pParse, pTab, OP_OpenWrite, 0, iBaseCur, - aToOpen, 0, 0); - if( addrOnce ) sqlite3VdbeJumpHere(v, addrOnce); + aToOpen, &iNotUsed1, &iNotUsed2); + if( addrOnce ){ + sqlite3VdbeJumpHereOrPopInst(v, addrOnce); + } } - + /* Top of the update loop */ if( eOnePass!=ONEPASS_OFF ){ - if( !isView && aiCurOnePass[0]!=iDataCur && aiCurOnePass[1]!=iDataCur ){ + if( aiCurOnePass[0]!=iDataCur + && aiCurOnePass[1]!=iDataCur +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + && !isView +#endif + ){ assert( pPk ); sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, labelBreak, regKey,nKey); VdbeCoverage(v); @@ -133213,15 +160604,35 @@ SQLITE_PRIVATE void sqlite3Update( sqlite3VdbeAddOp2(v, OP_IsNull, pPk ? regKey : regOldRowid, labelBreak); VdbeCoverageIf(v, pPk==0); VdbeCoverageIf(v, pPk!=0); - }else if( pPk ){ + }else if( pPk || nChangeFrom ){ labelContinue = sqlite3VdbeMakeLabel(pParse); sqlite3VdbeAddOp2(v, OP_Rewind, iEph, labelBreak); VdbeCoverage(v); - addrTop = sqlite3VdbeAddOp2(v, OP_RowData, iEph, regKey); - sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, labelContinue, regKey, 0); - VdbeCoverage(v); + addrTop = sqlite3VdbeCurrentAddr(v); + if( nChangeFrom ){ + if( !isView ){ + if( pPk ){ + for(i=0; i=0 ); + if( nChangeFrom==0 ){ + sqlite3ExprCode(pParse, pRowidExpr, regNewRowid); + }else{ + sqlite3VdbeAddOp3(v, OP_Column, iEph, iRowidExpr, regNewRowid); + } sqlite3VdbeAddOp1(v, OP_MustBeInt, regNewRowid); VdbeCoverage(v); } @@ -133242,21 +160658,26 @@ SQLITE_PRIVATE void sqlite3Update( ** information is needed */ if( chngPk || hasFK || pTrigger ){ u32 oldmask = (hasFK ? sqlite3FkOldmask(pParse, pTab) : 0); - oldmask |= sqlite3TriggerColmask(pParse, + oldmask |= sqlite3TriggerColmask(pParse, pTrigger, pChanges, 0, TRIGGER_BEFORE|TRIGGER_AFTER, pTab, onError ); for(i=0; inCol; i++){ + u32 colFlags = pTab->aCol[i].colFlags; + k = sqlite3TableColumnToStorage(pTab, i) + regOld; if( oldmask==0xffffffff || (i<32 && (oldmask & MASKBIT32(i))!=0) - || (pTab->aCol[i].colFlags & COLFLAG_PRIMKEY)!=0 + || (colFlags & COLFLAG_PRIMKEY)!=0 ){ testcase( oldmask!=0xffffffff && i==31 ); - sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, i, regOld+i); + sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, i, k); }else{ - sqlite3VdbeAddOp2(v, OP_Null, 0, regOld+i); + sqlite3VdbeAddOp2(v, OP_Null, 0, k); } } if( chngRowid==0 && pPk==0 ){ +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + if( isView ) sqlite3VdbeAddOp2(v, OP_Null, 0, regOldRowid); +#endif sqlite3VdbeAddOp2(v, OP_Copy, regOldRowid, regNewRowid); } } @@ -133269,104 +160690,142 @@ SQLITE_PRIVATE void sqlite3Update( ** If there are one or more BEFORE triggers, then do not populate the ** registers associated with columns that are (a) not modified by ** this UPDATE statement and (b) not accessed by new.* references. The - ** values for registers not modified by the UPDATE must be reloaded from - ** the database after the BEFORE triggers are fired anyway (as the trigger + ** values for registers not modified by the UPDATE must be reloaded from + ** the database after the BEFORE triggers are fired anyway (as the trigger ** may have modified them). So not loading those that are not going to ** be used eliminates some redundant opcodes. */ newmask = sqlite3TriggerColmask( pParse, pTrigger, pChanges, 1, TRIGGER_BEFORE, pTab, onError ); - for(i=0; inCol; i++){ + for(i=0, k=regNew; inCol; i++, k++){ if( i==pTab->iPKey ){ - sqlite3VdbeAddOp2(v, OP_Null, 0, regNew+i); + sqlite3VdbeAddOp2(v, OP_Null, 0, k); + }else if( (pTab->aCol[i].colFlags & COLFLAG_GENERATED)!=0 ){ + if( pTab->aCol[i].colFlags & COLFLAG_VIRTUAL ) k--; }else{ j = aXRef[i]; if( j>=0 ){ - sqlite3ExprCode(pParse, pChanges->a[j].pExpr, regNew+i); + if( nChangeFrom ){ + int nOff = (isView ? pTab->nCol : nPk); + assert( eOnePass==ONEPASS_OFF ); + sqlite3VdbeAddOp3(v, OP_Column, iEph, nOff+j, k); + }else{ + sqlite3ExprCode(pParse, pChanges->a[j].pExpr, k); + } }else if( 0==(tmask&TRIGGER_BEFORE) || i>31 || (newmask & MASKBIT32(i)) ){ - /* This branch loads the value of a column that will not be changed + /* This branch loads the value of a column that will not be changed ** into a register. This is done if there are no BEFORE triggers, or ** if there are one or more BEFORE triggers that use this value via ** a new.* reference in a trigger program. */ testcase( i==31 ); testcase( i==32 ); - sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, i, regNew+i); + sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, i, k); + bFinishSeek = 0; }else{ - sqlite3VdbeAddOp2(v, OP_Null, 0, regNew+i); + sqlite3VdbeAddOp2(v, OP_Null, 0, k); } } } +#ifndef SQLITE_OMIT_GENERATED_COLUMNS + if( pTab->tabFlags & TF_HasGenerated ){ + testcase( pTab->tabFlags & TF_HasVirtual ); + testcase( pTab->tabFlags & TF_HasStored ); + sqlite3ComputeGeneratedColumns(pParse, regNew, pTab); + } +#endif /* Fire any BEFORE UPDATE triggers. This happens before constraints are ** verified. One could argue that this is wrong. */ if( tmask&TRIGGER_BEFORE ){ sqlite3TableAffinity(v, pTab, regNew); - sqlite3CodeRowTrigger(pParse, pTrigger, TK_UPDATE, pChanges, + sqlite3CodeRowTrigger(pParse, pTrigger, TK_UPDATE, pChanges, TRIGGER_BEFORE, pTab, regOldRowid, onError, labelContinue); - /* The row-trigger may have deleted the row being updated. In this - ** case, jump to the next row. No updates or AFTER triggers are - ** required. This behavior - what happens when the row being updated - ** is deleted or renamed by a BEFORE trigger - is left undefined in the - ** documentation. - */ - if( pPk ){ - sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, labelContinue,regKey,nKey); - VdbeCoverage(v); - }else{ - sqlite3VdbeAddOp3(v, OP_NotExists, iDataCur, labelContinue, regOldRowid); - VdbeCoverage(v); - } + if( !isView ){ + /* The row-trigger may have deleted the row being updated. In this + ** case, jump to the next row. No updates or AFTER triggers are + ** required. This behavior - what happens when the row being updated + ** is deleted or renamed by a BEFORE trigger - is left undefined in the + ** documentation. + */ + if( pPk ){ + sqlite3VdbeAddOp4Int(v, OP_NotFound,iDataCur,labelContinue,regKey,nKey); + VdbeCoverage(v); + }else{ + sqlite3VdbeAddOp3(v, OP_NotExists, iDataCur, labelContinue,regOldRowid); + VdbeCoverage(v); + } - /* After-BEFORE-trigger-reload-loop: - ** If it did not delete it, the BEFORE trigger may still have modified - ** some of the columns of the row being updated. Load the values for - ** all columns not modified by the update statement into their registers - ** in case this has happened. Only unmodified columns are reloaded. - ** The values computed for modified columns use the values before the - ** BEFORE trigger runs. See test case trigger1-18.0 (added 2018-04-26) - ** for an example. - */ - for(i=0; inCol; i++){ - if( aXRef[i]<0 && i!=pTab->iPKey ){ - sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, i, regNew+i); + /* After-BEFORE-trigger-reload-loop: + ** If it did not delete it, the BEFORE trigger may still have modified + ** some of the columns of the row being updated. Load the values for + ** all columns not modified by the update statement into their registers + ** in case this has happened. Only unmodified columns are reloaded. + ** The values computed for modified columns use the values before the + ** BEFORE trigger runs. See test case trigger1-18.0 (added 2018-04-26) + ** for an example. + */ + for(i=0, k=regNew; inCol; i++, k++){ + if( pTab->aCol[i].colFlags & COLFLAG_GENERATED ){ + if( pTab->aCol[i].colFlags & COLFLAG_VIRTUAL ) k--; + }else if( aXRef[i]<0 && i!=pTab->iPKey ){ + sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, i, k); + } + } +#ifndef SQLITE_OMIT_GENERATED_COLUMNS + if( pTab->tabFlags & TF_HasGenerated ){ + testcase( pTab->tabFlags & TF_HasVirtual ); + testcase( pTab->tabFlags & TF_HasStored ); + sqlite3ComputeGeneratedColumns(pParse, regNew, pTab); } +#endif } } if( !isView ){ - int addr1 = 0; /* Address of jump instruction */ - /* Do constraint checks. */ assert( regOldRowid>0 ); sqlite3GenerateConstraintChecks(pParse, pTab, aRegIdx, iDataCur, iIdxCur, regNewRowid, regOldRowid, chngKey, onError, labelContinue, &bReplace, aXRef, 0); + /* If REPLACE conflict handling may have been used, or if the PK of the + ** row is changing, then the GenerateConstraintChecks() above may have + ** moved cursor iDataCur. Reseek it. */ + if( bReplace || chngKey ){ + if( pPk ){ + sqlite3VdbeAddOp4Int(v, OP_NotFound,iDataCur,labelContinue,regKey,nKey); + }else{ + sqlite3VdbeAddOp3(v, OP_NotExists, iDataCur, labelContinue,regOldRowid); + } + VdbeCoverage(v); + } + /* Do FK constraint checks. */ if( hasFK ){ sqlite3FkCheck(pParse, pTab, regOldRowid, 0, aXRef, chngKey); } /* Delete the index entries associated with the current record. */ - if( bReplace || chngKey ){ - if( pPk ){ - addr1 = sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, 0, regKey, nKey); - }else{ - addr1 = sqlite3VdbeAddOp3(v, OP_NotExists, iDataCur, 0, regOldRowid); - } - VdbeCoverageNeverTaken(v); - } sqlite3GenerateRowIndexDelete(pParse, pTab, iDataCur, iIdxCur, aRegIdx, -1); + /* We must run the OP_FinishSeek opcode to resolve a prior + ** OP_DeferredSeek if there is any possibility that there have been + ** no OP_Column opcodes since the OP_DeferredSeek was issued. But + ** we want to avoid the OP_FinishSeek if possible, as running it + ** costs CPU cycles. */ + if( bFinishSeek ){ + sqlite3VdbeAddOp1(v, OP_FinishSeek, iDataCur); + } + /* If changing the rowid value, or if there are foreign key constraints ** to process, delete the old record. Otherwise, add a noop OP_Delete ** to invoke the pre-update hook. ** - ** That (regNew==regnewRowid+1) is true is also important for the + ** That (regNew==regnewRowid+1) is true is also important for the ** pre-update hook. If the caller invokes preupdate_new(), the returned ** value is copied from memory cell (regNewRowid+1+iCol), where iCol ** is the column index supplied by the user. @@ -133389,37 +160848,36 @@ SQLITE_PRIVATE void sqlite3Update( sqlite3VdbeAddOp2(v, OP_Delete, iDataCur, 0); } #endif - if( bReplace || chngKey ){ - sqlite3VdbeJumpHere(v, addr1); - } if( hasFK ){ sqlite3FkCheck(pParse, pTab, 0, regNewRowid, aXRef, chngKey); } - + /* Insert the new index entries and the new record. */ sqlite3CompleteInsertion( - pParse, pTab, iDataCur, iIdxCur, regNewRowid, aRegIdx, - OPFLAG_ISUPDATE | (eOnePass==ONEPASS_MULTI ? OPFLAG_SAVEPOSITION : 0), + pParse, pTab, iDataCur, iIdxCur, regNewRowid, aRegIdx, + OPFLAG_ISUPDATE | (eOnePass==ONEPASS_MULTI ? OPFLAG_SAVEPOSITION : 0), 0, 0 ); /* Do any ON CASCADE, SET NULL or SET DEFAULT operations required to ** handle rows (possibly in other tables) that refer via a foreign key - ** to the row just updated. */ + ** to the row just updated. */ if( hasFK ){ sqlite3FkActions(pParse, pTab, pChanges, regOldRowid, aXRef, chngKey); } } - /* Increment the row counter + /* Increment the row counter */ if( regRowCount ){ sqlite3VdbeAddOp2(v, OP_AddImm, regRowCount, 1); } - sqlite3CodeRowTrigger(pParse, pTrigger, TK_UPDATE, pChanges, - TRIGGER_AFTER, pTab, regOldRowid, onError, labelContinue); + if( pTrigger ){ + sqlite3CodeRowTrigger(pParse, pTrigger, TK_UPDATE, pChanges, + TRIGGER_AFTER, pTab, regOldRowid, onError, labelContinue); + } /* Repeat the above with the next record to be updated, until ** all record selected by the WHERE clause have been updated. @@ -133429,11 +160887,9 @@ SQLITE_PRIVATE void sqlite3Update( }else if( eOnePass==ONEPASS_MULTI ){ sqlite3VdbeResolveLabel(v, labelContinue); sqlite3WhereEnd(pWInfo); - }else if( pPk ){ + }else{ sqlite3VdbeResolveLabel(v, labelContinue); sqlite3VdbeAddOp2(v, OP_Next, iEph, addrTop); VdbeCoverage(v); - }else{ - sqlite3VdbeGoto(v, labelContinue); } sqlite3VdbeResolveLabel(v, labelBreak); @@ -133450,9 +160906,7 @@ SQLITE_PRIVATE void sqlite3Update( ** that information. */ if( regRowCount ){ - sqlite3VdbeAddOp2(v, OP_ResultRow, regRowCount, 1); - sqlite3VdbeSetNumCols(v, 1); - sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "rows updated", SQLITE_STATIC); + sqlite3CodeChangeCount(v, regRowCount, "rows updated"); } update_cleanup: @@ -133461,7 +160915,7 @@ SQLITE_PRIVATE void sqlite3Update( sqlite3SrcListDelete(db, pTabList); sqlite3ExprListDelete(db, pChanges); sqlite3ExprDelete(db, pWhere); -#if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) +#if defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) sqlite3ExprListDelete(db, pOrderBy); sqlite3ExprDelete(db, pLimit); #endif @@ -133481,8 +160935,8 @@ SQLITE_PRIVATE void sqlite3Update( /* ** Generate code for an UPDATE of a virtual table. ** -** There are two possible strategies - the default and the special -** "onepass" strategy. Onepass is only used if the virtual table +** There are two possible strategies - the default and the special +** "onepass" strategy. Onepass is only used if the virtual table ** implementation indicates that pWhere may match at most one row. ** ** The default strategy is to create an ephemeral table that contains @@ -133514,11 +160968,11 @@ static void updateVirtualTable( int i; /* Loop counter */ sqlite3 *db = pParse->db; /* Database connection */ const char *pVTab = (const char*)sqlite3GetVTable(db, pTab); - WhereInfo *pWInfo; + WhereInfo *pWInfo = 0; int nArg = 2 + pTab->nCol; /* Number of arguments to VUpdate */ int regArg; /* First register in VUpdate arg array */ int regRec; /* Register in which to assemble record */ - int regRowid; /* Register for ephem table rowid */ + int regRowid; /* Register for ephemeral table rowid */ int iCsr = pSrc->a[0].iCursor; /* Cursor used for virtual table scan */ int aDummy[2]; /* Unused arg for sqlite3WhereOkOnePass() */ int eOnePass; /* True to use onepass strategy */ @@ -133532,73 +160986,119 @@ static void updateVirtualTable( addr= sqlite3VdbeAddOp2(v, OP_OpenEphemeral, ephemTab, nArg); regArg = pParse->nMem + 1; pParse->nMem += nArg; - regRec = ++pParse->nMem; - regRowid = ++pParse->nMem; + if( pSrc->nSrc>1 ){ + Index *pPk = 0; + Expr *pRow; + ExprList *pList; + if( HasRowid(pTab) ){ + if( pRowid ){ + pRow = sqlite3ExprDup(db, pRowid, 0); + }else{ + pRow = sqlite3PExpr(pParse, TK_ROW, 0, 0); + } + }else{ + i16 iPk; /* PRIMARY KEY column */ + pPk = sqlite3PrimaryKeyIndex(pTab); + assert( pPk!=0 ); + assert( pPk->nKeyCol==1 ); + iPk = pPk->aiColumn[0]; + if( aXRef[iPk]>=0 ){ + pRow = sqlite3ExprDup(db, pChanges->a[aXRef[iPk]].pExpr, 0); + }else{ + pRow = exprRowColumn(pParse, iPk); + } + } + pList = sqlite3ExprListAppend(pParse, 0, pRow); - /* Start scanning the virtual table */ - pWInfo = sqlite3WhereBegin(pParse, pSrc, pWhere, 0,0,WHERE_ONEPASS_DESIRED,0); - if( pWInfo==0 ) return; + for(i=0; inCol; i++){ + if( aXRef[i]>=0 ){ + pList = sqlite3ExprListAppend(pParse, pList, + sqlite3ExprDup(db, pChanges->a[aXRef[i]].pExpr, 0) + ); + }else{ + Expr *pRowExpr = exprRowColumn(pParse, i); + if( pRowExpr ) pRowExpr->op2 = OPFLAG_NOCHNG; + pList = sqlite3ExprListAppend(pParse, pList, pRowExpr); + } + } - /* Populate the argument registers. */ - for(i=0; inCol; i++){ - if( aXRef[i]>=0 ){ - sqlite3ExprCode(pParse, pChanges->a[aXRef[i]].pExpr, regArg+2+i); - }else{ - sqlite3VdbeAddOp3(v, OP_VColumn, iCsr, i, regArg+2+i); - sqlite3VdbeChangeP5(v, OPFLAG_NOCHNG);/* Enable sqlite3_vtab_nochange() */ + updateFromSelect(pParse, ephemTab, pPk, pList, pSrc, pWhere, 0, 0); + sqlite3ExprListDelete(db, pList); + eOnePass = ONEPASS_OFF; + }else{ + regRec = ++pParse->nMem; + regRowid = ++pParse->nMem; + + /* Start scanning the virtual table */ + pWInfo = sqlite3WhereBegin( + pParse, pSrc, pWhere, 0, 0, 0, WHERE_ONEPASS_DESIRED, 0 + ); + if( pWInfo==0 ) return; + + /* Populate the argument registers. */ + for(i=0; inCol; i++){ + assert( (pTab->aCol[i].colFlags & COLFLAG_GENERATED)==0 ); + if( aXRef[i]>=0 ){ + sqlite3ExprCode(pParse, pChanges->a[aXRef[i]].pExpr, regArg+2+i); + }else{ + sqlite3VdbeAddOp3(v, OP_VColumn, iCsr, i, regArg+2+i); + sqlite3VdbeChangeP5(v, OPFLAG_NOCHNG);/* For sqlite3_vtab_nochange() */ + } } - } - if( HasRowid(pTab) ){ - sqlite3VdbeAddOp2(v, OP_Rowid, iCsr, regArg); - if( pRowid ){ - sqlite3ExprCode(pParse, pRowid, regArg+1); + if( HasRowid(pTab) ){ + sqlite3VdbeAddOp2(v, OP_Rowid, iCsr, regArg); + if( pRowid ){ + sqlite3ExprCode(pParse, pRowid, regArg+1); + }else{ + sqlite3VdbeAddOp2(v, OP_Rowid, iCsr, regArg+1); + } }else{ - sqlite3VdbeAddOp2(v, OP_Rowid, iCsr, regArg+1); + Index *pPk; /* PRIMARY KEY index */ + i16 iPk; /* PRIMARY KEY column */ + pPk = sqlite3PrimaryKeyIndex(pTab); + assert( pPk!=0 ); + assert( pPk->nKeyCol==1 ); + iPk = pPk->aiColumn[0]; + sqlite3VdbeAddOp3(v, OP_VColumn, iCsr, iPk, regArg); + sqlite3VdbeAddOp2(v, OP_SCopy, regArg+2+iPk, regArg+1); } - }else{ - Index *pPk; /* PRIMARY KEY index */ - i16 iPk; /* PRIMARY KEY column */ - pPk = sqlite3PrimaryKeyIndex(pTab); - assert( pPk!=0 ); - assert( pPk->nKeyCol==1 ); - iPk = pPk->aiColumn[0]; - sqlite3VdbeAddOp3(v, OP_VColumn, iCsr, iPk, regArg); - sqlite3VdbeAddOp2(v, OP_SCopy, regArg+2+iPk, regArg+1); - } - eOnePass = sqlite3WhereOkOnePass(pWInfo, aDummy); + eOnePass = sqlite3WhereOkOnePass(pWInfo, aDummy); - /* There is no ONEPASS_MULTI on virtual tables */ - assert( eOnePass==ONEPASS_OFF || eOnePass==ONEPASS_SINGLE ); + /* There is no ONEPASS_MULTI on virtual tables */ + assert( eOnePass==ONEPASS_OFF || eOnePass==ONEPASS_SINGLE ); - if( eOnePass ){ - /* If using the onepass strategy, no-op out the OP_OpenEphemeral coded - ** above. */ - sqlite3VdbeChangeToNoop(v, addr); - sqlite3VdbeAddOp1(v, OP_Close, iCsr); - }else{ - /* Create a record from the argument register contents and insert it into - ** the ephemeral table. */ - sqlite3MultiWrite(pParse); - sqlite3VdbeAddOp3(v, OP_MakeRecord, regArg, nArg, regRec); -#ifdef SQLITE_DEBUG - /* Signal an assert() within OP_MakeRecord that it is allowed to - ** accept no-change records with serial_type 10 */ - sqlite3VdbeChangeP5(v, OPFLAG_NOCHNG_MAGIC); + if( eOnePass ){ + /* If using the onepass strategy, no-op out the OP_OpenEphemeral coded + ** above. */ + sqlite3VdbeChangeToNoop(v, addr); + sqlite3VdbeAddOp1(v, OP_Close, iCsr); + }else{ + /* Create a record from the argument register contents and insert it into + ** the ephemeral table. */ + sqlite3MultiWrite(pParse); + sqlite3VdbeAddOp3(v, OP_MakeRecord, regArg, nArg, regRec); +#if defined(SQLITE_DEBUG) && !defined(SQLITE_ENABLE_NULL_TRIM) + /* Signal an assert() within OP_MakeRecord that it is allowed to + ** accept no-change records with serial_type 10 */ + sqlite3VdbeChangeP5(v, OPFLAG_NOCHNG_MAGIC); #endif - sqlite3VdbeAddOp2(v, OP_NewRowid, ephemTab, regRowid); - sqlite3VdbeAddOp3(v, OP_Insert, ephemTab, regRec, regRowid); + sqlite3VdbeAddOp2(v, OP_NewRowid, ephemTab, regRowid); + sqlite3VdbeAddOp3(v, OP_Insert, ephemTab, regRec, regRowid); + } } if( eOnePass==ONEPASS_OFF ){ /* End the virtual table scan */ - sqlite3WhereEnd(pWInfo); + if( pSrc->nSrc==1 ){ + sqlite3WhereEnd(pWInfo); + } - /* Begin scannning through the ephemeral table. */ + /* Begin scanning through the ephemeral table. */ addr = sqlite3VdbeAddOp1(v, OP_Rewind, ephemTab); VdbeCoverage(v); - /* Extract arguments from the current row of the ephemeral table and + /* Extract arguments from the current row of the ephemeral table and ** invoke the VUpdate method. */ for(i=0; ipNextUpsert; sqlite3ExprListDelete(db, p->pUpsertTarget); sqlite3ExprDelete(db, p->pUpsertTargetWhere); sqlite3ExprListDelete(db, p->pUpsertSet); sqlite3ExprDelete(db, p->pUpsertWhere); + sqlite3DbFree(db, p->pToFree); sqlite3DbFree(db, p); - } + p = pNext; + }while( p ); +} +SQLITE_PRIVATE void sqlite3UpsertDelete(sqlite3 *db, Upsert *p){ + if( p ) upsertDelete(db, p); } + /* ** Duplicate an Upsert object. */ @@ -133662,7 +161169,8 @@ SQLITE_PRIVATE Upsert *sqlite3UpsertDup(sqlite3 *db, Upsert *p){ sqlite3ExprListDup(db, p->pUpsertTarget, 0), sqlite3ExprDup(db, p->pUpsertTargetWhere, 0), sqlite3ExprListDup(db, p->pUpsertSet, 0), - sqlite3ExprDup(db, p->pUpsertWhere, 0) + sqlite3ExprDup(db, p->pUpsertWhere, 0), + sqlite3UpsertDup(db, p->pNextUpsert) ); } @@ -133674,22 +161182,25 @@ SQLITE_PRIVATE Upsert *sqlite3UpsertNew( ExprList *pTarget, /* Target argument to ON CONFLICT, or NULL */ Expr *pTargetWhere, /* Optional WHERE clause on the target */ ExprList *pSet, /* UPDATE columns, or NULL for a DO NOTHING */ - Expr *pWhere /* WHERE clause for the ON CONFLICT UPDATE */ + Expr *pWhere, /* WHERE clause for the ON CONFLICT UPDATE */ + Upsert *pNext /* Next ON CONFLICT clause in the list */ ){ Upsert *pNew; - pNew = sqlite3DbMallocRaw(db, sizeof(Upsert)); + pNew = sqlite3DbMallocZero(db, sizeof(Upsert)); if( pNew==0 ){ sqlite3ExprListDelete(db, pTarget); sqlite3ExprDelete(db, pTargetWhere); sqlite3ExprListDelete(db, pSet); sqlite3ExprDelete(db, pWhere); + sqlite3UpsertDelete(db, pNext); return 0; }else{ pNew->pUpsertTarget = pTarget; pNew->pUpsertTargetWhere = pTargetWhere; pNew->pUpsertSet = pSet; pNew->pUpsertWhere = pWhere; - pNew->pUpsertIdx = 0; + pNew->isDoUpdate = pSet!=0; + pNew->pNextUpsert = pNext; } return pNew; } @@ -133704,7 +161215,8 @@ SQLITE_PRIVATE Upsert *sqlite3UpsertNew( SQLITE_PRIVATE int sqlite3UpsertAnalyzeTarget( Parse *pParse, /* The parsing context */ SrcList *pTabList, /* Table into which we are inserting */ - Upsert *pUpsert /* The ON CONFLICT clauses */ + Upsert *pUpsert, /* The ON CONFLICT clauses */ + Upsert *pAll /* Complete list of all ON CONFLICT clauses */ ){ Table *pTab; /* That table into which we are inserting */ int rc; /* Result code */ @@ -133714,9 +161226,10 @@ SQLITE_PRIVATE int sqlite3UpsertAnalyzeTarget( Expr *pTerm; /* One term of the conflict-target clause */ NameContext sNC; /* Context for resolving symbolic names */ Expr sCol[2]; /* Index column converted into an Expr */ + int nClause = 0; /* Counter of ON CONFLICT clauses */ assert( pTabList->nSrc==1 ); - assert( pTabList->a[0].pTab!=0 ); + assert( pTabList->a[0].pSTab!=0 ); assert( pUpsert!=0 ); assert( pUpsert->pUpsertTarget!=0 ); @@ -133727,87 +161240,144 @@ SQLITE_PRIVATE int sqlite3UpsertAnalyzeTarget( memset(&sNC, 0, sizeof(sNC)); sNC.pParse = pParse; sNC.pSrcList = pTabList; - rc = sqlite3ResolveExprListNames(&sNC, pUpsert->pUpsertTarget); - if( rc ) return rc; - rc = sqlite3ResolveExprNames(&sNC, pUpsert->pUpsertTargetWhere); - if( rc ) return rc; + for(; pUpsert && pUpsert->pUpsertTarget; + pUpsert=pUpsert->pNextUpsert, nClause++){ + rc = sqlite3ResolveExprListNames(&sNC, pUpsert->pUpsertTarget); + if( rc ) return rc; + rc = sqlite3ResolveExprNames(&sNC, pUpsert->pUpsertTargetWhere); + if( rc ) return rc; - /* Check to see if the conflict target matches the rowid. */ - pTab = pTabList->a[0].pTab; - pTarget = pUpsert->pUpsertTarget; - iCursor = pTabList->a[0].iCursor; - if( HasRowid(pTab) - && pTarget->nExpr==1 - && (pTerm = pTarget->a[0].pExpr)->op==TK_COLUMN - && pTerm->iColumn==XN_ROWID - ){ - /* The conflict-target is the rowid of the primary table */ - assert( pUpsert->pUpsertIdx==0 ); - return SQLITE_OK; - } + /* Check to see if the conflict target matches the rowid. */ + pTab = pTabList->a[0].pSTab; + pTarget = pUpsert->pUpsertTarget; + iCursor = pTabList->a[0].iCursor; + if( HasRowid(pTab) + && pTarget->nExpr==1 + && (pTerm = pTarget->a[0].pExpr)->op==TK_COLUMN + && pTerm->iColumn==XN_ROWID + ){ + /* The conflict-target is the rowid of the primary table */ + assert( pUpsert->pUpsertIdx==0 ); + continue; + } - /* Initialize sCol[0..1] to be an expression parse tree for a - ** single column of an index. The sCol[0] node will be the TK_COLLATE - ** operator and sCol[1] will be the TK_COLUMN operator. Code below - ** will populate the specific collation and column number values - ** prior to comparing against the conflict-target expression. - */ - memset(sCol, 0, sizeof(sCol)); - sCol[0].op = TK_COLLATE; - sCol[0].pLeft = &sCol[1]; - sCol[1].op = TK_COLUMN; - sCol[1].iTable = pTabList->a[0].iCursor; + /* Initialize sCol[0..1] to be an expression parse tree for a + ** single column of an index. The sCol[0] node will be the TK_COLLATE + ** operator and sCol[1] will be the TK_COLUMN operator. Code below + ** will populate the specific collation and column number values + ** prior to comparing against the conflict-target expression. + */ + memset(sCol, 0, sizeof(sCol)); + sCol[0].op = TK_COLLATE; + sCol[0].pLeft = &sCol[1]; + sCol[1].op = TK_COLUMN; + sCol[1].iTable = pTabList->a[0].iCursor; - /* Check for matches against other indexes */ - for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ - int ii, jj, nn; - if( !IsUniqueIndex(pIdx) ) continue; - if( pTarget->nExpr!=pIdx->nKeyCol ) continue; - if( pIdx->pPartIdxWhere ){ - if( pUpsert->pUpsertTargetWhere==0 ) continue; - if( sqlite3ExprCompare(pParse, pUpsert->pUpsertTargetWhere, - pIdx->pPartIdxWhere, iCursor)!=0 ){ - continue; + /* Check for matches against other indexes */ + for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ + int ii, jj, nn; + if( !IsUniqueIndex(pIdx) ) continue; + if( pTarget->nExpr!=pIdx->nKeyCol ) continue; + if( pIdx->pPartIdxWhere ){ + if( pUpsert->pUpsertTargetWhere==0 ) continue; + if( sqlite3ExprCompare(pParse, pUpsert->pUpsertTargetWhere, + pIdx->pPartIdxWhere, iCursor)!=0 ){ + continue; + } } - } - nn = pIdx->nKeyCol; - for(ii=0; iiazColl[ii]; - if( pIdx->aiColumn[ii]==XN_EXPR ){ - assert( pIdx->aColExpr!=0 ); - assert( pIdx->aColExpr->nExpr>ii ); - pExpr = pIdx->aColExpr->a[ii].pExpr; - if( pExpr->op!=TK_COLLATE ){ - sCol[0].pLeft = pExpr; + nn = pIdx->nKeyCol; + for(ii=0; iiazColl[ii]; + if( pIdx->aiColumn[ii]==XN_EXPR ){ + assert( pIdx->aColExpr!=0 ); + assert( pIdx->aColExpr->nExpr>ii ); + assert( pIdx->bHasExpr ); + pExpr = pIdx->aColExpr->a[ii].pExpr; + if( pExpr->op!=TK_COLLATE ){ + sCol[0].pLeft = pExpr; + pExpr = &sCol[0]; + } + }else{ + sCol[0].pLeft = &sCol[1]; + sCol[1].iColumn = pIdx->aiColumn[ii]; pExpr = &sCol[0]; } - }else{ - sCol[0].pLeft = &sCol[1]; - sCol[1].iColumn = pIdx->aiColumn[ii]; - pExpr = &sCol[0]; - } - for(jj=0; jja[jj].pExpr, pExpr,iCursor)<2 ){ - break; /* Column ii of the index matches column jj of target */ + for(jj=0; jja[jj].pExpr,pExpr,iCursor)<2 ){ + break; /* Column ii of the index matches column jj of target */ + } + } + if( jj>=nn ){ + /* The target contains no match for column jj of the index */ + break; } } - if( jj>=nn ){ - /* The target contains no match for column jj of the index */ - break; + if( iipUpsertIdx = pIdx; + if( sqlite3UpsertOfIndex(pAll,pIdx)!=pUpsert ){ + /* Really this should be an error. The isDup ON CONFLICT clause will + ** never fire. But this problem was not discovered until three years + ** after multi-CONFLICT upsert was added, and so we silently ignore + ** the problem to prevent breaking applications that might actually + ** have redundant ON CONFLICT clauses. */ + pUpsert->isDup = 1; + } + break; } - if( iipUpsertIdx==0 ){ + char zWhich[16]; + if( nClause==0 && pUpsert->pNextUpsert==0 ){ + zWhich[0] = 0; + }else{ + sqlite3_snprintf(sizeof(zWhich),zWhich,"%r ", nClause+1); + } + sqlite3ErrorMsg(pParse, "%sON CONFLICT clause does not match any " + "PRIMARY KEY or UNIQUE constraint", zWhich); + return SQLITE_ERROR; } - pUpsert->pUpsertIdx = pIdx; - return SQLITE_OK; } - sqlite3ErrorMsg(pParse, "ON CONFLICT clause does not match any " - "PRIMARY KEY or UNIQUE constraint"); - return SQLITE_ERROR; + return SQLITE_OK; +} + +/* +** Return true if pUpsert is the last ON CONFLICT clause with a +** conflict target, or if pUpsert is followed by another ON CONFLICT +** clause that targets the INTEGER PRIMARY KEY. +*/ +SQLITE_PRIVATE int sqlite3UpsertNextIsIPK(Upsert *pUpsert){ + Upsert *pNext; + if( NEVER(pUpsert==0) ) return 0; + pNext = pUpsert->pNextUpsert; + while( 1 /*exit-by-return*/ ){ + if( pNext==0 ) return 1; + if( pNext->pUpsertTarget==0 ) return 1; + if( pNext->pUpsertIdx==0 ) return 1; + if( !pNext->isDup ) return 0; + pNext = pNext->pNextUpsert; + } + return 0; +} + +/* +** Given the list of ON CONFLICT clauses described by pUpsert, and +** a particular index pIdx, return a pointer to the particular ON CONFLICT +** clause that applies to the index. Or, if the index is not subject to +** any ON CONFLICT clause, return NULL. +*/ +SQLITE_PRIVATE Upsert *sqlite3UpsertOfIndex(Upsert *pUpsert, Index *pIdx){ + while( + pUpsert + && pUpsert->pUpsertTarget!=0 + && pUpsert->pUpsertIdx!=pIdx + ){ + pUpsert = pUpsert->pNextUpsert; + } + return pUpsert; } /* @@ -133830,11 +161400,14 @@ SQLITE_PRIVATE void sqlite3UpsertDoUpdate( sqlite3 *db = pParse->db; SrcList *pSrc; /* FROM clause for the UPDATE */ int iDataCur; + int i; + Upsert *pTop = pUpsert; assert( v!=0 ); assert( pUpsert!=0 ); - VdbeNoopComment((v, "Begin DO UPDATE of UPSERT")); iDataCur = pUpsert->iDataCur; + pUpsert = sqlite3UpsertOfIndex(pTop, pIdx); + VdbeNoopComment((v, "Begin DO UPDATE of UPSERT")); if( pIdx && iCur!=iDataCur ){ if( HasRowid(pTab) ){ int regRowid = sqlite3GetTempReg(pParse); @@ -133846,31 +161419,36 @@ SQLITE_PRIVATE void sqlite3UpsertDoUpdate( Index *pPk = sqlite3PrimaryKeyIndex(pTab); int nPk = pPk->nKeyCol; int iPk = pParse->nMem+1; - int i; pParse->nMem += nPk; for(i=0; iaiColumn[i]>=0 ); - k = sqlite3ColumnOfIndex(pIdx, pPk->aiColumn[i]); + k = sqlite3TableColumnToIndex(pIdx, pPk->aiColumn[i]); sqlite3VdbeAddOp3(v, OP_Column, iCur, k, iPk+i); VdbeComment((v, "%s.%s", pIdx->zName, - pTab->aCol[pPk->aiColumn[i]].zName)); + pTab->aCol[pPk->aiColumn[i]].zCnName)); } sqlite3VdbeVerifyAbortable(v, OE_Abort); i = sqlite3VdbeAddOp4Int(v, OP_Found, iDataCur, 0, iPk, nPk); VdbeCoverage(v); - sqlite3VdbeAddOp4(v, OP_Halt, SQLITE_CORRUPT, OE_Abort, 0, + sqlite3VdbeAddOp4(v, OP_Halt, SQLITE_CORRUPT, OE_Abort, 0, "corrupt database", P4_STATIC); + sqlite3MayAbort(pParse); sqlite3VdbeJumpHere(v, i); } } - /* pUpsert does not own pUpsertSrc - the outer INSERT statement does. So - ** we have to make a copy before passing it down into sqlite3Update() */ - pSrc = sqlite3SrcListDup(db, pUpsert->pUpsertSrc, 0); - sqlite3Update(pParse, pSrc, pUpsert->pUpsertSet, - pUpsert->pUpsertWhere, OE_Abort, 0, 0, pUpsert); - pUpsert->pUpsertSet = 0; /* Will have been deleted by sqlite3Update() */ - pUpsert->pUpsertWhere = 0; /* Will have been deleted by sqlite3Update() */ + /* pUpsert does not own pTop->pUpsertSrc - the outer INSERT statement does. + ** So we have to make a copy before passing it down into sqlite3Update() */ + pSrc = sqlite3SrcListDup(db, pTop->pUpsertSrc, 0); + /* excluded.* columns of type REAL need to be converted to a hard real */ + for(i=0; inCol; i++){ + if( pTab->aCol[i].affinity==SQLITE_AFF_REAL ){ + int iStorage = pTop->regData + sqlite3TableColumnToStorage(pTab, i); + sqlite3VdbeAddOp1(v, OP_RealAffinity, iStorage); + } + } + sqlite3Update(pParse, pSrc, sqlite3ExprListDup(db,pUpsert->pUpsertSet,0), + sqlite3ExprDup(db,pUpsert->pUpsertWhere,0), OE_Abort, 0, 0, pUpsert); VdbeNoopComment((v, "End DO UPDATE of UPSERT")); } @@ -133921,7 +161499,7 @@ static int execSql(sqlite3 *db, char **pzErrMsg, const char *zSql){ assert( sqlite3_strnicmp(zSql,"SELECT",6)==0 ); /* The secondary SQL must be one of CREATE TABLE, CREATE INDEX, ** or INSERT. Historically there have been attacks that first - ** corrupt the sqlite_master.sql field with other kinds of statements + ** corrupt the sqlite_schema.sql field with other kinds of statements ** then run VACUUM to get those statements to execute at inappropriate ** times. */ if( zSubSql @@ -133986,6 +161564,7 @@ SQLITE_PRIVATE void sqlite3Vacuum(Parse *pParse, Token *pNm, Expr *pInto){ Vdbe *v = sqlite3GetVdbe(pParse); int iDb = 0; if( v==0 ) goto build_vacuum_end; + if( pParse->nErr ) goto build_vacuum_end; if( pNm ){ #ifndef SQLITE_BUG_COMPATIBLE_20160819 /* Default behavior: Report an error if the argument to VACUUM is @@ -133995,7 +161574,7 @@ SQLITE_PRIVATE void sqlite3Vacuum(Parse *pParse, Token *pNm, Expr *pInto){ #else /* When SQLITE_BUG_COMPATIBLE_20160819 is defined, unrecognized arguments ** to VACUUM are silently ignored. This is a back-out of a bug fix that - ** occurred on 2016-08-19 (https://www.sqlite.org/src/info/083f9e6270). + ** occurred on 2016-08-19 (https://sqlite.org/src/info/083f9e6270). ** The buggy behavior is required for binary compatibility with some ** legacy applications. */ iDb = sqlite3FindDb(pParse->db, pNm); @@ -134030,8 +161609,8 @@ SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3RunVacuum( Btree *pTemp; /* The temporary database we vacuum into */ u32 saved_mDbFlags; /* Saved value of db->mDbFlags */ u64 saved_flags; /* Saved value of db->flags */ - int saved_nChange; /* Saved value of db->nChange */ - int saved_nTotalChange; /* Saved value of db->nTotalChange */ + i64 saved_nChange; /* Saved value of db->nChange */ + i64 saved_nTotalChange; /* Saved value of db->nTotalChange */ u32 saved_openFlags; /* Saved value of db->openFlags */ u8 saved_mTrace; /* Saved trace settings */ Db *pDb = 0; /* Database to detach at end of vacuum */ @@ -134040,6 +161619,10 @@ SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3RunVacuum( int nDb; /* Number of attached databases */ const char *zDbMain; /* Schema name of database to vacuum */ const char *zOut; /* Name of output file */ + u32 pgflags = PAGER_SYNCHRONOUS_OFF; /* sync flags for output db */ + u64 iRandom; /* Random value used for zDbVacuum[] */ + char zDbVacuum[42]; /* Name of the ATTACH-ed database used for vacuum */ + if( !db->autoCommit ){ sqlite3SetString(pzErrMsg, db, "cannot VACUUM from within a transaction"); @@ -134062,7 +161645,7 @@ SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3RunVacuum( zOut = ""; } - /* Save the current value of the database flags so that it can be + /* Save the current value of the database flags so that it can be ** restored before returning. Then set the writable-schema flag, and ** disable CHECK and foreign key constraints. */ saved_flags = db->flags; @@ -134070,7 +161653,8 @@ SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3RunVacuum( saved_nChange = db->nChange; saved_nTotalChange = db->nTotalChange; saved_mTrace = db->mTrace; - db->flags |= SQLITE_WriteSchema | SQLITE_IgnoreChecks; + db->flags |= SQLITE_WriteSchema | SQLITE_IgnoreChecks | SQLITE_Comments + | SQLITE_AttachCreate | SQLITE_AttachWrite; db->mDbFlags |= DBFLAG_PreferBuiltin | DBFLAG_Vacuum; db->flags &= ~(u64)(SQLITE_ForeignKeys | SQLITE_ReverseOrder | SQLITE_Defensive | SQLITE_CountRows); @@ -134080,54 +161664,60 @@ SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3RunVacuum( pMain = db->aDb[iDb].pBt; isMemDb = sqlite3PagerIsMemdb(sqlite3BtreePager(pMain)); - /* Attach the temporary database as 'vacuum_db'. The synchronous pragma + /* Attach the temporary database as 'vacuum_XXXXXX'. The synchronous pragma ** can be set to 'off' for this file, as it is not recovered if a crash ** occurs anyway. The integrity of the database is maintained by a ** (possibly synchronous) transaction opened on the main database before ** sqlite3BtreeCopyFile() is called. ** - ** An optimisation would be to use a non-journaled pager. - ** (Later:) I tried setting "PRAGMA vacuum_db.journal_mode=OFF" but + ** An optimization would be to use a non-journaled pager. + ** (Later:) I tried setting "PRAGMA vacuum_XXXXXX.journal_mode=OFF" but ** that actually made the VACUUM run slower. Very little journalling ** actually occurs when doing a vacuum since the vacuum_db is initially ** empty. Only the journal header is written. Apparently it takes more ** time to parse and run the PRAGMA to turn journalling off than it does ** to write the journal header file. */ + sqlite3_randomness(sizeof(iRandom),&iRandom); + sqlite3_snprintf(sizeof(zDbVacuum), zDbVacuum, "vacuum_%016llx", iRandom); nDb = db->nDb; - rc = execSqlF(db, pzErrMsg, "ATTACH %Q AS vacuum_db", zOut); + rc = execSqlF(db, pzErrMsg, "ATTACH %Q AS %s", zOut, zDbVacuum); db->openFlags = saved_openFlags; if( rc!=SQLITE_OK ) goto end_of_vacuum; assert( (db->nDb-1)==nDb ); pDb = &db->aDb[nDb]; - assert( strcmp(pDb->zDbSName,"vacuum_db")==0 ); + assert( strcmp(pDb->zDbSName,zDbVacuum)==0 ); pTemp = pDb->pBt; + nRes = sqlite3BtreeGetRequestedReserve(pMain); if( pOut ){ sqlite3_file *id = sqlite3PagerFile(sqlite3BtreePager(pTemp)); i64 sz = 0; + const char *zFilename; if( id->pMethods!=0 && (sqlite3OsFileSize(id, &sz)!=SQLITE_OK || sz>0) ){ rc = SQLITE_ERROR; sqlite3SetString(pzErrMsg, db, "output file already exists"); goto end_of_vacuum; } db->mDbFlags |= DBFLAG_VacuumInto; - } - nRes = sqlite3BtreeGetOptimalReserve(pMain); - /* A VACUUM cannot change the pagesize of an encrypted database. */ -#ifdef SQLITE_HAS_CODEC - if( db->nextPagesize ){ - extern void sqlite3CodecGetKey(sqlite3*, int, void**, int*); - int nKey; - char *zKey; - sqlite3CodecGetKey(db, iDb, (void**)&zKey, &nKey); - if( nKey ) db->nextPagesize = 0; + /* For a VACUUM INTO, the pager-flags are set to the same values as + ** they are for the database being vacuumed, except that PAGER_CACHESPILL + ** is always set. */ + pgflags = db->aDb[iDb].safety_level | (db->flags & PAGER_FLAGS_MASK); + + /* If the VACUUM INTO target file is a URI filename and if the + ** "reserve=N" query parameter is present, reset the reserve to the + ** amount specified, if the amount is within range */ + zFilename = sqlite3BtreeGetFilename(pTemp); + if( ALWAYS(zFilename) ){ + int nNew = (int)sqlite3_uri_int64(zFilename, "reserve", nRes); + if( nNew>=0 && nNew<=255 ) nRes = nNew; + } } -#endif sqlite3BtreeSetCacheSize(pTemp, db->aDb[iDb].pSchema->cache_size); sqlite3BtreeSetSpillSize(pTemp, sqlite3BtreeSetSpillSize(pMain,0)); - sqlite3BtreeSetPagerFlags(pTemp, PAGER_SYNCHRONOUS_OFF|PAGER_CACHESPILL); + sqlite3BtreeSetPagerFlags(pTemp, pgflags|PAGER_CACHESPILL); /* Begin a transaction and take an exclusive lock on the main database ** file. This is done before the sqlite3BtreeGetPageSize(pMain) call below, @@ -134140,7 +161730,9 @@ SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3RunVacuum( /* Do not attempt to change the page size for a WAL database */ if( sqlite3PagerGetJournalMode(sqlite3BtreePager(pMain)) - ==PAGER_JOURNALMODE_WAL ){ + ==PAGER_JOURNALMODE_WAL + && pOut==0 + ){ db->nextPagesize = 0; } @@ -134162,14 +161754,14 @@ SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3RunVacuum( */ db->init.iDb = nDb; /* force new CREATE statements into vacuum_db */ rc = execSqlF(db, pzErrMsg, - "SELECT sql FROM \"%w\".sqlite_master" + "SELECT sql FROM \"%w\".sqlite_schema" " WHERE type='table'AND name<>'sqlite_sequence'" " AND coalesce(rootpage,1)>0", zDbMain ); if( rc!=SQLITE_OK ) goto end_of_vacuum; rc = execSqlF(db, pzErrMsg, - "SELECT sql FROM \"%w\".sqlite_master" + "SELECT sql FROM \"%w\".sqlite_schema" " WHERE type='index'", zDbMain ); @@ -134181,11 +161773,11 @@ SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3RunVacuum( ** the contents to the temporary database. */ rc = execSqlF(db, pzErrMsg, - "SELECT'INSERT INTO vacuum_db.'||quote(name)" + "SELECT'INSERT INTO %s.'||quote(name)" "||' SELECT*FROM\"%w\".'||quote(name)" - "FROM vacuum_db.sqlite_master " + "FROM %s.sqlite_schema " "WHERE type='table'AND coalesce(rootpage,1)>0", - zDbMain + zDbVacuum, zDbMain, zDbVacuum ); assert( (db->mDbFlags & DBFLAG_Vacuum)!=0 ); db->mDbFlags &= ~DBFLAG_Vacuum; @@ -134194,18 +161786,18 @@ SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3RunVacuum( /* Copy the triggers, views, and virtual tables from the main database ** over to the temporary database. None of these objects has any ** associated storage, so all we have to do is copy their entries - ** from the SQLITE_MASTER table. + ** from the schema table. */ rc = execSqlF(db, pzErrMsg, - "INSERT INTO vacuum_db.sqlite_master" - " SELECT*FROM \"%w\".sqlite_master" + "INSERT INTO %s.sqlite_schema" + " SELECT*FROM \"%w\".sqlite_schema" " WHERE type IN('view','trigger')" " OR(type='table'AND rootpage=0)", - zDbMain + zDbVacuum, zDbMain ); if( rc ) goto end_of_vacuum; - /* At this point, there is a write transaction open on both the + /* At this point, there is a write transaction open on both the ** vacuum database and the main database. Assuming no error occurs, ** both transactions are closed by this block - the main database ** transaction by sqlite3BtreeCopyFile() and the other by an explicit @@ -134229,8 +161821,8 @@ SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3RunVacuum( BTREE_APPLICATION_ID, 0, /* Preserve the application id */ }; - assert( 1==sqlite3BtreeIsInTrans(pTemp) ); - assert( pOut!=0 || 1==sqlite3BtreeIsInTrans(pMain) ); + assert( SQLITE_TXN_WRITE==sqlite3BtreeTxnState(pTemp) ); + assert( pOut!=0 || SQLITE_TXN_WRITE==sqlite3BtreeTxnState(pMain) ); /* Copy Btree meta values */ for(i=0; inChange = saved_nChange; db->nTotalChange = saved_nTotalChange; db->mTrace = saved_mTrace; - sqlite3BtreeSetPageSize(pMain, -1, -1, 1); + sqlite3BtreeSetPageSize(pMain, -1, 0, 1); /* Currently there is an SQL level transaction open on the vacuum ** database. No locks are held on any other files (since the main file @@ -134285,7 +161878,7 @@ SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3RunVacuum( } /* This both clears the schemas and reduces the size of the db->aDb[] - ** array. */ + ** array. */ sqlite3ResetAllSchemasOfConnection(db); return rc; @@ -134314,7 +161907,7 @@ SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3RunVacuum( /* ** Before a virtual table xCreate() or xConnect() method is invoked, the ** sqlite3.pVtabCtx member variable is set to point to an instance of -** this struct allocated on the stack. It is used by the implementation of +** this struct allocated on the stack. It is used by the implementation of ** the sqlite3_declare_vtab() and sqlite3_vtab_config() APIs, both of which ** are invoked only from within xCreate and xConnect methods. */ @@ -134329,6 +161922,9 @@ struct VtabCtx { ** Construct and install a Module object for a virtual table. When this ** routine is called, it is guaranteed that all appropriate locks are held ** and the module is not already part of the connection. +** +** If there already exists a module with zName, replace it with the new one. +** If pModule==0, then delete the module zName if it exists. */ SQLITE_PRIVATE Module *sqlite3VtabCreateModule( sqlite3 *db, /* Database in which module is registered */ @@ -134338,25 +161934,36 @@ SQLITE_PRIVATE Module *sqlite3VtabCreateModule( void (*xDestroy)(void *) /* Module destructor function */ ){ Module *pMod; - int nName = sqlite3Strlen30(zName); - pMod = (Module *)sqlite3Malloc(sizeof(Module) + nName + 1); - if( pMod==0 ){ - sqlite3OomFault(db); + Module *pDel; + char *zCopy; + if( pModule==0 ){ + zCopy = (char*)zName; + pMod = 0; }else{ - Module *pDel; - char *zCopy = (char *)(&pMod[1]); + int nName = sqlite3Strlen30(zName); + pMod = (Module *)sqlite3Malloc(sizeof(Module) + nName + 1); + if( pMod==0 ){ + sqlite3OomFault(db); + return 0; + } + zCopy = (char *)(&pMod[1]); memcpy(zCopy, zName, nName+1); pMod->zName = zCopy; pMod->pModule = pModule; pMod->pAux = pAux; pMod->xDestroy = xDestroy; pMod->pEpoTab = 0; - pDel = (Module *)sqlite3HashInsert(&db->aModule,zCopy,(void*)pMod); - assert( pDel==0 || pDel==pMod ); - if( pDel ){ + pMod->nRefModule = 1; + } + pDel = (Module *)sqlite3HashInsert(&db->aModule,zCopy,(void*)pMod); + if( pDel ){ + if( pDel==pMod ){ sqlite3OomFault(db); sqlite3DbFree(db, pDel); pMod = 0; + }else{ + sqlite3VtabEponymousTableClear(db, pDel); + sqlite3VtabModuleUnref(db, pDel); } } return pMod; @@ -134377,11 +161984,7 @@ static int createModule( int rc = SQLITE_OK; sqlite3_mutex_enter(db->mutex); - if( sqlite3HashFind(&db->aModule, zName) ){ - rc = SQLITE_MISUSE_BKPT; - }else{ - (void)sqlite3VtabCreateModule(db, zName, pModule, pAux, xDestroy); - } + (void)sqlite3VtabCreateModule(db, zName, pModule, pAux, xDestroy); rc = sqlite3ApiExit(db, rc); if( rc!=SQLITE_OK && xDestroy ) xDestroy(pAux); sqlite3_mutex_leave(db->mutex); @@ -134420,10 +162023,50 @@ SQLITE_API int sqlite3_create_module_v2( return createModule(db, zName, pModule, pAux, xDestroy); } +/* +** External API to drop all virtual-table modules, except those named +** on the azNames list. +*/ +SQLITE_API int sqlite3_drop_modules(sqlite3 *db, const char** azNames){ + HashElem *pThis, *pNext; +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif + sqlite3_mutex_enter(db->mutex); + for(pThis=sqliteHashFirst(&db->aModule); pThis; pThis=pNext){ + Module *pMod = (Module*)sqliteHashData(pThis); + pNext = sqliteHashNext(pThis); + if( azNames ){ + int ii; + for(ii=0; azNames[ii]!=0 && strcmp(azNames[ii],pMod->zName)!=0; ii++){} + if( azNames[ii]!=0 ) continue; + } + createModule(db, pMod->zName, 0, 0, 0); + } + sqlite3_mutex_leave(db->mutex); + return SQLITE_OK; +} + +/* +** Decrement the reference count on a Module object. Destroy the +** module when the reference count reaches zero. +*/ +SQLITE_PRIVATE void sqlite3VtabModuleUnref(sqlite3 *db, Module *pMod){ + assert( pMod->nRefModule>0 ); + pMod->nRefModule--; + if( pMod->nRefModule==0 ){ + if( pMod->xDestroy ){ + pMod->xDestroy(pMod->pAux); + } + assert( pMod->pEpoTab==0 ); + sqlite3DbFree(db, pMod); + } +} + /* ** Lock the virtual table so that it cannot be disconnected. ** Locks nest. Every lock should have a corresponding unlock. -** If an unlock is omitted, resources leaks will occur. +** If an unlock is omitted, resources leaks will occur. ** ** If a disconnect is attempted while a virtual table is locked, ** the disconnect is deferred until all locks have been removed. @@ -134435,13 +162078,13 @@ SQLITE_PRIVATE void sqlite3VtabLock(VTable *pVTab){ /* ** pTab is a pointer to a Table structure representing a virtual-table. -** Return a pointer to the VTable object used by connection db to access +** Return a pointer to the VTable object used by connection db to access ** this virtual-table, if one has been created, or NULL otherwise. */ SQLITE_PRIVATE VTable *sqlite3GetVTable(sqlite3 *db, Table *pTab){ VTable *pVtab; assert( IsVirtual(pTab) ); - for(pVtab=pTab->pVTable; pVtab && pVtab->db!=db; pVtab=pVtab->pNext); + for(pVtab=pTab->u.vtab.p; pVtab && pVtab->db!=db; pVtab=pVtab->pNext); return pVtab; } @@ -134454,7 +162097,8 @@ SQLITE_PRIVATE void sqlite3VtabUnlock(VTable *pVTab){ assert( db ); assert( pVTab->nRef>0 ); - assert( db->magic==SQLITE_MAGIC_OPEN || db->magic==SQLITE_MAGIC_ZOMBIE ); + assert( db->eOpenState==SQLITE_STATE_OPEN + || db->eOpenState==SQLITE_STATE_ZOMBIE ); pVTab->nRef--; if( pVTab->nRef==0 ){ @@ -134462,27 +162106,31 @@ SQLITE_PRIVATE void sqlite3VtabUnlock(VTable *pVTab){ if( p ){ p->pModule->xDisconnect(p); } + sqlite3VtabModuleUnref(pVTab->db, pVTab->pMod); sqlite3DbFree(db, pVTab); } } /* ** Table p is a virtual table. This function moves all elements in the -** p->pVTable list to the sqlite3.pDisconnect lists of their associated -** database connections to be disconnected at the next opportunity. +** p->u.vtab.p list to the sqlite3.pDisconnect lists of their associated +** database connections to be disconnected at the next opportunity. ** Except, if argument db is not NULL, then the entry associated with -** connection db is left in the p->pVTable list. +** connection db is left in the p->u.vtab.p list. */ static VTable *vtabDisconnectAll(sqlite3 *db, Table *p){ VTable *pRet = 0; - VTable *pVTable = p->pVTable; - p->pVTable = 0; + VTable *pVTable; + + assert( IsVirtual(p) ); + pVTable = p->u.vtab.p; + p->u.vtab.p = 0; - /* Assert that the mutex (if any) associated with the BtShared database - ** that contains table p is held by the caller. See header comments + /* Assert that the mutex (if any) associated with the BtShared database + ** that contains table p is held by the caller. See header comments ** above function sqlite3VtabUnlockList() for an explanation of why ** this makes it safe to access the sqlite3.pDisconnect list of any - ** database connection that may have an entry in the p->pVTable list. + ** database connection that may have an entry in the p->u.vtab.p list. */ assert( db==0 || sqlite3SchemaMutexHeld(db, 0, p->pSchema) ); @@ -134492,7 +162140,7 @@ static VTable *vtabDisconnectAll(sqlite3 *db, Table *p){ assert( db2 ); if( db2==db ){ pRet = pVTable; - p->pVTable = pRet; + p->u.vtab.p = pRet; pRet->pNext = 0; }else{ pVTable->pNext = db2->pDisconnect; @@ -134520,7 +162168,7 @@ SQLITE_PRIVATE void sqlite3VtabDisconnect(sqlite3 *db, Table *p){ assert( sqlite3BtreeHoldsAllMutexes(db) ); assert( sqlite3_mutex_held(db->mutex) ); - for(ppVTab=&p->pVTable; *ppVTab; ppVTab=&(*ppVTab)->pNext){ + for(ppVTab=&p->u.vtab.p; *ppVTab; ppVTab=&(*ppVTab)->pNext){ if( (*ppVTab)->db==db ){ VTable *pVTab = *ppVTab; *ppVTab = pVTab->pNext; @@ -134535,7 +162183,7 @@ SQLITE_PRIVATE void sqlite3VtabDisconnect(sqlite3 *db, Table *p){ ** Disconnect all the virtual table objects in the sqlite3.pDisconnect list. ** ** This function may only be called when the mutexes associated with all -** shared b-tree databases opened using connection db are held by the +** shared b-tree databases opened using connection db are held by the ** caller. This is done to protect the sqlite3.pDisconnect list. The ** sqlite3.pDisconnect list is accessed only as follows: ** @@ -134548,18 +162196,17 @@ SQLITE_PRIVATE void sqlite3VtabDisconnect(sqlite3 *db, Table *p){ ** or, if the virtual table is stored in a non-sharable database, then ** the database handle mutex is held. ** -** As a result, a sqlite3.pDisconnect cannot be accessed simultaneously +** As a result, a sqlite3.pDisconnect cannot be accessed simultaneously ** by multiple threads. It is thread-safe. */ SQLITE_PRIVATE void sqlite3VtabUnlockList(sqlite3 *db){ VTable *p = db->pDisconnect; - db->pDisconnect = 0; assert( sqlite3BtreeHoldsAllMutexes(db) ); assert( sqlite3_mutex_held(db->mutex) ); if( p ){ - sqlite3ExpirePreparedStatements(db, 0); + db->pDisconnect = 0; do { VTable *pNext = p->pNext; sqlite3VtabUnlock(p); @@ -134574,46 +162221,51 @@ SQLITE_PRIVATE void sqlite3VtabUnlockList(sqlite3 *db){ ** record. ** ** Since it is a virtual-table, the Table structure contains a pointer -** to the head of a linked list of VTable structures. Each VTable +** to the head of a linked list of VTable structures. Each VTable ** structure is associated with a single sqlite3* user of the schema. -** The reference count of the VTable structure associated with database -** connection db is decremented immediately (which may lead to the +** The reference count of the VTable structure associated with database +** connection db is decremented immediately (which may lead to the ** structure being xDisconnected and free). Any other VTable structures -** in the list are moved to the sqlite3.pDisconnect list of the associated +** in the list are moved to the sqlite3.pDisconnect list of the associated ** database connection. */ SQLITE_PRIVATE void sqlite3VtabClear(sqlite3 *db, Table *p){ - if( !db || db->pnBytesFreed==0 ) vtabDisconnectAll(0, p); - if( p->azModuleArg ){ + assert( IsVirtual(p) ); + assert( db!=0 ); + if( db->pnBytesFreed==0 ) vtabDisconnectAll(0, p); + if( p->u.vtab.azArg ){ int i; - for(i=0; inModuleArg; i++){ - if( i!=1 ) sqlite3DbFree(db, p->azModuleArg[i]); + for(i=0; iu.vtab.nArg; i++){ + if( i!=1 ) sqlite3DbFree(db, p->u.vtab.azArg[i]); } - sqlite3DbFree(db, p->azModuleArg); + sqlite3DbFree(db, p->u.vtab.azArg); } } /* -** Add a new module argument to pTable->azModuleArg[]. +** Add a new module argument to pTable->u.vtab.azArg[]. ** The string is not copied - the pointer is stored. The ** string will be freed automatically when the table is ** deleted. */ static void addModuleArgument(Parse *pParse, Table *pTable, char *zArg){ - sqlite3_int64 nBytes = sizeof(char *)*(2+pTable->nModuleArg); + sqlite3_int64 nBytes; char **azModuleArg; sqlite3 *db = pParse->db; - if( pTable->nModuleArg+3>=db->aLimit[SQLITE_LIMIT_COLUMN] ){ + + assert( IsVirtual(pTable) ); + nBytes = sizeof(char *)*(2+pTable->u.vtab.nArg); + if( pTable->u.vtab.nArg+3>=db->aLimit[SQLITE_LIMIT_COLUMN] ){ sqlite3ErrorMsg(pParse, "too many columns on %s", pTable->zName); } - azModuleArg = sqlite3DbRealloc(db, pTable->azModuleArg, nBytes); + azModuleArg = sqlite3DbRealloc(db, pTable->u.vtab.azArg, nBytes); if( azModuleArg==0 ){ sqlite3DbFree(db, zArg); }else{ - int i = pTable->nModuleArg++; + int i = pTable->u.vtab.nArg++; azModuleArg[i] = zArg; azModuleArg[i+1] = 0; - pTable->azModuleArg = azModuleArg; + pTable->u.vtab.azArg = azModuleArg; } } @@ -134636,10 +162288,11 @@ SQLITE_PRIVATE void sqlite3VtabBeginParse( pTable = pParse->pNewTable; if( pTable==0 ) return; assert( 0==pTable->pIndex ); + pTable->eTabType = TABTYP_VTAB; db = pParse->db; - assert( pTable->nModuleArg==0 ); + assert( pTable->u.vtab.nArg==0 ); addModuleArgument(pParse, pTable, sqlite3NameFromToken(db, pModuleName)); addModuleArgument(pParse, pTable, 0); addModuleArgument(pParse, pTable, sqlite3DbStrDup(db, pTable->zName)); @@ -134653,14 +162306,14 @@ SQLITE_PRIVATE void sqlite3VtabBeginParse( #ifndef SQLITE_OMIT_AUTHORIZATION /* Creating a virtual table invokes the authorization callback twice. ** The first invocation, to obtain permission to INSERT a row into the - ** sqlite_master table, has already been made by sqlite3StartTable(). + ** sqlite_schema table, has already been made by sqlite3StartTable(). ** The second call, to obtain permission to create the table, is made now. */ - if( pTable->azModuleArg ){ + if( pTable->u.vtab.azArg ){ int iDb = sqlite3SchemaToIndex(db, pTable->pSchema); assert( iDb>=0 ); /* The database the table is being created in */ - sqlite3AuthCheck(pParse, SQLITE_CREATE_VTABLE, pTable->zName, - pTable->azModuleArg[0], pParse->db->aDb[iDb].zDbSName); + sqlite3AuthCheck(pParse, SQLITE_CREATE_VTABLE, pTable->zName, + pTable->u.vtab.azArg[0], pParse->db->aDb[iDb].zDbSName); } #endif } @@ -134688,15 +162341,16 @@ SQLITE_PRIVATE void sqlite3VtabFinishParse(Parse *pParse, Token *pEnd){ sqlite3 *db = pParse->db; /* The database connection */ if( pTab==0 ) return; + assert( IsVirtual(pTab) ); addArgumentToVtab(pParse); pParse->sArg.z = 0; - if( pTab->nModuleArg<1 ) return; - + if( pTab->u.vtab.nArg<1 ) return; + /* If the CREATE VIRTUAL TABLE statement is being entered for the ** first time (in other words if the virtual table is actually being - ** created now instead of just being read out of sqlite_master) then + ** created now instead of just being read out of sqlite_schema) then ** do additional initialization work and store the statement text - ** in the sqlite_master table. + ** in the sqlite_schema table. */ if( !db->init.busy ){ char *zStmt; @@ -134705,54 +162359,53 @@ SQLITE_PRIVATE void sqlite3VtabFinishParse(Parse *pParse, Token *pEnd){ int iReg; Vdbe *v; + sqlite3MayAbort(pParse); + /* Compute the complete text of the CREATE VIRTUAL TABLE statement */ if( pEnd ){ pParse->sNameToken.n = (int)(pEnd->z - pParse->sNameToken.z) + pEnd->n; } zStmt = sqlite3MPrintf(db, "CREATE VIRTUAL TABLE %T", &pParse->sNameToken); - /* A slot for the record has already been allocated in the - ** SQLITE_MASTER table. We just need to update that slot with all - ** the information we've collected. + /* A slot for the record has already been allocated in the + ** schema table. We just need to update that slot with all + ** the information we've collected. ** - ** The VM register number pParse->regRowid holds the rowid of an - ** entry in the sqlite_master table tht was created for this vtab + ** The VM register number pParse->u1.cr.regRowid holds the rowid of an + ** entry in the sqlite_schema table that was created for this vtab ** by sqlite3StartTable(). */ iDb = sqlite3SchemaToIndex(db, pTab->pSchema); + assert( pParse->isCreate ); sqlite3NestedParse(pParse, - "UPDATE %Q.%s " + "UPDATE %Q." LEGACY_SCHEMA_TABLE " " "SET type='table', name=%Q, tbl_name=%Q, rootpage=0, sql=%Q " "WHERE rowid=#%d", - db->aDb[iDb].zDbSName, MASTER_NAME, + db->aDb[iDb].zDbSName, pTab->zName, pTab->zName, zStmt, - pParse->regRowid + pParse->u1.cr.regRowid ); - sqlite3DbFree(db, zStmt); v = sqlite3GetVdbe(pParse); sqlite3ChangeCookie(pParse, iDb); sqlite3VdbeAddOp0(v, OP_Expire); - zWhere = sqlite3MPrintf(db, "name='%q' AND type='table'", pTab->zName); - sqlite3VdbeAddParseSchemaOp(v, iDb, zWhere); + zWhere = sqlite3MPrintf(db, "name=%Q AND sql=%Q", pTab->zName, zStmt); + sqlite3VdbeAddParseSchemaOp(v, iDb, zWhere, 0); + sqlite3DbFree(db, zStmt); iReg = ++pParse->nMem; sqlite3VdbeLoadString(v, iReg, pTab->zName); sqlite3VdbeAddOp2(v, OP_VCreate, iDb, iReg); - } - - /* If we are rereading the sqlite_master table create the in-memory - ** record of the table. The xConnect() method is not called until - ** the first time the virtual table is used in an SQL statement. This - ** allows a schema that contains virtual tables to be loaded before - ** the required virtual table implementations are registered. */ - else { + }else{ + /* If we are rereading the sqlite_schema table create the in-memory + ** record of the table. */ Table *pOld; Schema *pSchema = pTab->pSchema; const char *zName = pTab->zName; - assert( sqlite3SchemaMutexHeld(db, 0, pSchema) ); + assert( zName!=0 ); + sqlite3MarkAllShadowTablesOf(db, pTab); pOld = sqlite3HashInsert(&pSchema->tblHash, zName, pTab); if( pOld ){ sqlite3OomFault(db); @@ -134794,7 +162447,7 @@ SQLITE_PRIVATE void sqlite3VtabArgExtend(Parse *pParse, Token *p){ ** to this procedure. */ static int vtabCallConstructor( - sqlite3 *db, + sqlite3 *db, Table *pTab, Module *pMod, int (*xConstruct)(sqlite3*,void*,int,const char*const*,sqlite3_vtab**,char**), @@ -134803,17 +162456,20 @@ static int vtabCallConstructor( VtabCtx sCtx; VTable *pVTable; int rc; - const char *const*azArg = (const char *const*)pTab->azModuleArg; - int nArg = pTab->nModuleArg; + const char *const*azArg; + int nArg = pTab->u.vtab.nArg; char *zErr = 0; char *zModuleName; int iDb; VtabCtx *pCtx; + assert( IsVirtual(pTab) ); + azArg = (const char *const*)pTab->u.vtab.azArg; + /* Check that the virtual-table is not already being initialized */ for(pCtx=db->pVtabCtx; pCtx; pCtx=pCtx->pPrior){ if( pCtx->pTab==pTab ){ - *pzErr = sqlite3MPrintf(db, + *pzErr = sqlite3MPrintf(db, "vtable constructor called recursively: %s", pTab->zName ); return SQLITE_LOCKED; @@ -134833,9 +162489,10 @@ static int vtabCallConstructor( } pVTable->db = db; pVTable->pMod = pMod; + pVTable->eVtabRisk = SQLITE_VTABRISK_Normal; iDb = sqlite3SchemaToIndex(db, pTab->pSchema); - pTab->azModuleArg[1] = db->aDb[iDb].zDbSName; + pTab->u.vtab.azArg[1] = db->aDb[iDb].zDbSName; /* Invoke the virtual table constructor */ assert( &db->pVtabCtx ); @@ -134845,7 +162502,11 @@ static int vtabCallConstructor( sCtx.pPrior = db->pVtabCtx; sCtx.bDeclared = 0; db->pVtabCtx = &sCtx; + pTab->nTabRef++; rc = xConstruct(db, pMod->pAux, nArg, azArg, &pVTable->pVtab, &zErr); + assert( pTab!=0 ); + assert( pTab->nTabRef>1 || rc!=SQLITE_OK ); + sqlite3DeleteTable(db, pTab); db->pVtabCtx = sCtx.pPrior; if( rc==SQLITE_NOMEM ) sqlite3OomFault(db); assert( sCtx.pTab==pTab ); @@ -134863,22 +162524,23 @@ static int vtabCallConstructor( ** the sqlite3_vtab object if successful. */ memset(pVTable->pVtab, 0, sizeof(pVTable->pVtab[0])); pVTable->pVtab->pModule = pMod->pModule; + pMod->nRefModule++; pVTable->nRef = 1; if( sCtx.bDeclared==0 ){ const char *zFormat = "vtable constructor did not declare schema: %s"; - *pzErr = sqlite3MPrintf(db, zFormat, pTab->zName); + *pzErr = sqlite3MPrintf(db, zFormat, zModuleName); sqlite3VtabUnlock(pVTable); rc = SQLITE_ERROR; }else{ int iCol; - u8 oooHidden = 0; + u16 oooHidden = 0; /* If everything went according to plan, link the new VTable structure - ** into the linked list headed by pTab->pVTable. Then loop through the + ** into the linked list headed by pTab->u.vtab.p. Then loop through the ** columns of the table to see if any of them contain the token "hidden". ** If so, set the Column COLFLAG_HIDDEN flag and remove the token from ** the type string. */ - pVTable->pNext = pTab->pVTable; - pTab->pVTable = pVTable; + pVTable->pNext = pTab->u.vtab.p; + pTab->u.vtab.p = pVTable; for(iCol=0; iColnCol; iCol++){ char *zType = sqlite3ColumnType(&pTab->aCol[iCol], ""); @@ -134904,6 +162566,7 @@ static int vtabCallConstructor( zType[i-1] = '\0'; } pTab->aCol[iCol].colFlags |= COLFLAG_HIDDEN; + pTab->tabFlags |= TF_HasHidden; oooHidden = TF_OOOHidden; }else{ pTab->tabFlags |= oooHidden; @@ -134918,7 +162581,7 @@ static int vtabCallConstructor( /* ** This function is invoked by the parser to call the xConnect() method -** of the virtual table pTab. If an error occurs, an error code is returned +** of the virtual table pTab. If an error occurs, an error code is returned ** and an error left in pParse. ** ** This call is a no-op if table pTab is not a virtual table. @@ -134930,16 +162593,17 @@ SQLITE_PRIVATE int sqlite3VtabCallConnect(Parse *pParse, Table *pTab){ int rc; assert( pTab ); - if( !IsVirtual(pTab) || sqlite3GetVTable(db, pTab) ){ + assert( IsVirtual(pTab) ); + if( sqlite3GetVTable(db, pTab) ){ return SQLITE_OK; } /* Locate the required virtual table module */ - zMod = pTab->azModuleArg[0]; + zMod = pTab->u.vtab.azArg[0]; pMod = (Module*)sqlite3HashFind(&db->aModule, zMod); if( !pMod ){ - const char *zModule = pTab->azModuleArg[0]; + const char *zModule = pTab->u.vtab.azArg[0]; sqlite3ErrorMsg(pParse, "no such module: %s", zModule); rc = SQLITE_ERROR; }else{ @@ -134989,7 +162653,7 @@ static void addToVTrans(sqlite3 *db, VTable *pVTab){ /* ** This function is invoked by the vdbe to call the xCreate method -** of the virtual table named zTab in database iDb. +** of the virtual table named zTab in database iDb. ** ** If an error occurs, *pzErr is set to point to an English language ** description of the error and an SQLITE_XXX error code is returned. @@ -135002,14 +162666,14 @@ SQLITE_PRIVATE int sqlite3VtabCallCreate(sqlite3 *db, int iDb, const char *zTab, const char *zMod; pTab = sqlite3FindTable(db, zTab, db->aDb[iDb].zDbSName); - assert( pTab && IsVirtual(pTab) && !pTab->pVTable ); + assert( pTab && IsVirtual(pTab) && !pTab->u.vtab.p ); /* Locate the required virtual table module */ - zMod = pTab->azModuleArg[0]; + zMod = pTab->u.vtab.azArg[0]; pMod = (Module*)sqlite3HashFind(&db->aModule, zMod); - /* If the module has been registered and includes a Create method, - ** invoke it now. If the module has not been registered, return an + /* If the module has been registered and includes a Create method, + ** invoke it now. If the module has not been registered, return an ** error. Otherwise, do nothing. */ if( pMod==0 || pMod->pModule->xCreate==0 || pMod->pModule->xDestroy==0 ){ @@ -135040,39 +162704,67 @@ SQLITE_API int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){ VtabCtx *pCtx; int rc = SQLITE_OK; Table *pTab; - char *zErr = 0; Parse sParse; + int initBusy; + int i; + const unsigned char *z; + static const u8 aKeyword[] = { TK_CREATE, TK_TABLE, 0 }; #ifdef SQLITE_ENABLE_API_ARMOR if( !sqlite3SafetyCheckOk(db) || zCreateTable==0 ){ return SQLITE_MISUSE_BKPT; } #endif + + /* Verify that the first two keywords in the CREATE TABLE statement + ** really are "CREATE" and "TABLE". If this is not the case, then + ** sqlite3_declare_vtab() is being misused. + */ + z = (const unsigned char*)zCreateTable; + for(i=0; aKeyword[i]; i++){ + int tokenType = 0; + do{ + z += sqlite3GetToken(z, &tokenType); + }while( tokenType==TK_SPACE || tokenType==TK_COMMENT ); + if( tokenType!=aKeyword[i] ){ + sqlite3ErrorWithMsg(db, SQLITE_ERROR, "syntax error"); + return SQLITE_ERROR; + } + } + sqlite3_mutex_enter(db->mutex); pCtx = db->pVtabCtx; if( !pCtx || pCtx->bDeclared ){ - sqlite3Error(db, SQLITE_MISUSE); + sqlite3Error(db, SQLITE_MISUSE_BKPT); sqlite3_mutex_leave(db->mutex); return SQLITE_MISUSE_BKPT; } + pTab = pCtx->pTab; assert( IsVirtual(pTab) ); - memset(&sParse, 0, sizeof(sParse)); + sqlite3ParseObjectInit(&sParse, db); sParse.eParseMode = PARSE_MODE_DECLARE_VTAB; - sParse.db = db; + sParse.disableTriggers = 1; + /* We should never be able to reach this point while loading the + ** schema. Nevertheless, defend against that (turn off db->init.busy) + ** in case a bug arises. */ + assert( db->init.busy==0 ); + initBusy = db->init.busy; + db->init.busy = 0; sParse.nQueryLoop = 1; - if( SQLITE_OK==sqlite3RunParser(&sParse, zCreateTable, &zErr) - && sParse.pNewTable - && !db->mallocFailed - && !sParse.pNewTable->pSelect - && !IsVirtual(sParse.pNewTable) - ){ + if( SQLITE_OK==sqlite3RunParser(&sParse, zCreateTable) ){ + assert( sParse.pNewTable!=0 ); + assert( !db->mallocFailed ); + assert( IsOrdinaryTable(sParse.pNewTable) ); + assert( sParse.zErrMsg==0 ); if( !pTab->aCol ){ Table *pNew = sParse.pNewTable; Index *pIdx; pTab->aCol = pNew->aCol; - pTab->nCol = pNew->nCol; + assert( IsOrdinaryTable(pNew) ); + sqlite3ExprListDelete(db, pNew->u.tab.pDfltList); + pTab->nNVCol = pTab->nCol = pNew->nCol; pTab->tabFlags |= pNew->tabFlags & (TF_WithoutRowid|TF_NoVisibleRowid); pNew->nCol = 0; pNew->aCol = 0; @@ -135096,8 +162788,9 @@ SQLITE_API int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){ } pCtx->bDeclared = 1; }else{ - sqlite3ErrorWithMsg(db, SQLITE_ERROR, (zErr ? "%s" : 0), zErr); - sqlite3DbFree(db, zErr); + sqlite3ErrorWithMsg(db, SQLITE_ERROR, + (sParse.zErrMsg ? "%s" : 0), sParse.zErrMsg); + sqlite3DbFree(db, sParse.zErrMsg); rc = SQLITE_ERROR; } sParse.eParseMode = PARSE_MODE_NORMAL; @@ -135106,7 +162799,8 @@ SQLITE_API int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){ sqlite3VdbeFinalize(sParse.pVdbe); } sqlite3DeleteTable(db, sParse.pNewTable); - sqlite3ParserReset(&sParse); + sqlite3ParseObjectReset(&sParse); + db->init.busy = initBusy; assert( (rc&0xff)==rc ); rc = sqlite3ApiExit(db, rc); @@ -135126,10 +162820,13 @@ SQLITE_PRIVATE int sqlite3VtabCallDestroy(sqlite3 *db, int iDb, const char *zTab Table *pTab; pTab = sqlite3FindTable(db, zTab, db->aDb[iDb].zDbSName); - if( pTab!=0 && ALWAYS(pTab->pVTable!=0) ){ + if( ALWAYS(pTab!=0) + && ALWAYS(IsVirtual(pTab)) + && ALWAYS(pTab->u.vtab.p!=0) + ){ VTable *p; int (*xDestroy)(sqlite3_vtab *); - for(p=pTab->pVTable; p; p=p->pNext){ + for(p=pTab->u.vtab.p; p; p=p->pNext){ assert( p->pVtab ); if( p->pVtab->nRef>0 ){ return SQLITE_LOCKED; @@ -135137,15 +162834,18 @@ SQLITE_PRIVATE int sqlite3VtabCallDestroy(sqlite3 *db, int iDb, const char *zTab } p = vtabDisconnectAll(db, pTab); xDestroy = p->pMod->pModule->xDestroy; - assert( xDestroy!=0 ); /* Checked before the virtual table is created */ + if( xDestroy==0 ) xDestroy = p->pMod->pModule->xDisconnect; + assert( xDestroy!=0 ); + pTab->nTabRef++; rc = xDestroy(p->pVtab); /* Remove the sqlite3_vtab* from the aVTrans[] array, if applicable */ if( rc==SQLITE_OK ){ - assert( pTab->pVTable==p && p->pNext==0 ); + assert( pTab->u.vtab.p==p && p->pNext==0 ); p->pVtab = 0; - pTab->pVTable = 0; + pTab->u.vtab.p = 0; sqlite3VtabUnlock(p); } + sqlite3DeleteTable(db, pTab); } return rc; @@ -135157,7 +162857,7 @@ SQLITE_PRIVATE int sqlite3VtabCallDestroy(sqlite3 *db, int iDb, const char *zTab ** called is identified by the second argument, "offset", which is ** the offset of the method to call in the sqlite3_module structure. ** -** The array is cleared after invoking the callbacks. +** The array is cleared after invoking the callbacks. */ static void callFinaliser(sqlite3 *db, int offset){ int i; @@ -135206,7 +162906,7 @@ SQLITE_PRIVATE int sqlite3VtabSync(sqlite3 *db, Vdbe *p){ } /* -** Invoke the xRollback method of all virtual tables in the +** Invoke the xRollback method of all virtual tables in the ** sqlite3.aVTrans array. Then clear the array itself. */ SQLITE_PRIVATE int sqlite3VtabRollback(sqlite3 *db){ @@ -135215,7 +162915,7 @@ SQLITE_PRIVATE int sqlite3VtabRollback(sqlite3 *db){ } /* -** Invoke the xCommit method of all virtual tables in the +** Invoke the xCommit method of all virtual tables in the ** sqlite3.aVTrans array. Then clear the array itself. */ SQLITE_PRIVATE int sqlite3VtabCommit(sqlite3 *db){ @@ -135237,7 +162937,7 @@ SQLITE_PRIVATE int sqlite3VtabBegin(sqlite3 *db, VTable *pVTab){ /* Special case: If db->aVTrans is NULL and db->nVTrans is greater ** than zero, then this function is being called from within a - ** virtual module xSync() callback. It is illegal to write to + ** virtual module xSync() callback. It is illegal to write to ** virtual module tables in this case, so return SQLITE_LOCKED. */ if( sqlite3VtabInSync(db) ){ @@ -135245,7 +162945,7 @@ SQLITE_PRIVATE int sqlite3VtabBegin(sqlite3 *db, VTable *pVTab){ } if( !pVTab ){ return SQLITE_OK; - } + } pModule = pVTab->pVtab->pModule; if( pModule->xBegin ){ @@ -135258,7 +162958,7 @@ SQLITE_PRIVATE int sqlite3VtabBegin(sqlite3 *db, VTable *pVTab){ } } - /* Invoke the xBegin method. If successful, add the vtab to the + /* Invoke the xBegin method. If successful, add the vtab to the ** sqlite3.aVTrans[] array. */ rc = growVTrans(db); if( rc==SQLITE_OK ){ @@ -135282,11 +162982,11 @@ SQLITE_PRIVATE int sqlite3VtabBegin(sqlite3 *db, VTable *pVTab){ ** as the second argument to the virtual table method invoked. ** ** If op is SAVEPOINT_BEGIN, the xSavepoint method is invoked. If it is -** SAVEPOINT_ROLLBACK, the xRollbackTo method. Otherwise, if op is +** SAVEPOINT_ROLLBACK, the xRollbackTo method. Otherwise, if op is ** SAVEPOINT_RELEASE, then the xRelease method of each virtual table with ** an open transaction is invoked. ** -** If any virtual table method returns an error code other than SQLITE_OK, +** If any virtual table method returns an error code other than SQLITE_OK, ** processing is abandoned and the error returned to the caller of this ** function immediately. If all calls to virtual table methods are successful, ** SQLITE_OK is returned. @@ -135317,7 +163017,10 @@ SQLITE_PRIVATE int sqlite3VtabSavepoint(sqlite3 *db, int op, int iSavepoint){ break; } if( xMethod && pVTab->iSavepoint>iSavepoint ){ + u64 savedFlags = (db->flags & SQLITE_Defensive); + db->flags &= ~(u64)SQLITE_Defensive; rc = xMethod(pVTab->pVtab, iSavepoint); + db->flags |= savedFlags; } sqlite3VtabUnlock(pVTab); } @@ -135335,7 +163038,7 @@ SQLITE_PRIVATE int sqlite3VtabSavepoint(sqlite3 *db, int op, int iSavepoint){ ** This routine is used to allow virtual table implementations to ** overload MATCH, LIKE, GLOB, and REGEXP operators. ** -** Return either the pDef argument (indicating no change) or a +** Return either the pDef argument (indicating no change) or a ** new FuncDef structure that is marked as ephemeral using the ** SQLITE_FUNC_EPHEM flag. */ @@ -135356,15 +163059,16 @@ SQLITE_PRIVATE FuncDef *sqlite3VtabOverloadFunction( /* Check to see the left operand is a column in a virtual table */ if( NEVER(pExpr==0) ) return pDef; if( pExpr->op!=TK_COLUMN ) return pDef; + assert( ExprUseYTab(pExpr) ); pTab = pExpr->y.pTab; - if( pTab==0 ) return pDef; + if( NEVER(pTab==0) ) return pDef; if( !IsVirtual(pTab) ) return pDef; pVtab = sqlite3GetVTable(db, pTab)->pVtab; assert( pVtab!=0 ); assert( pVtab->pModule!=0 ); pMod = (sqlite3_module *)pVtab->pModule; if( pMod->xFindFunction==0 ) return pDef; - + /* Call the xFindFunction method on the virtual table implementation ** to see if the implementation wants to overload this function. ** @@ -135418,7 +163122,7 @@ SQLITE_PRIVATE void sqlite3VtabMakeWritable(Parse *pParse, Table *pTab){ if( pTab==pToplevel->apVtabLock[i] ) return; } n = (pToplevel->nVtabLock+1)*sizeof(pToplevel->apVtabLock[0]); - apVtabLock = sqlite3_realloc64(pToplevel->apVtabLock, n); + apVtabLock = sqlite3Realloc(pToplevel->apVtabLock, n); if( apVtabLock ){ pToplevel->apVtabLock = apVtabLock; pToplevel->apVtabLock[pToplevel->nVtabLock++] = pTab; @@ -135430,12 +163134,13 @@ SQLITE_PRIVATE void sqlite3VtabMakeWritable(Parse *pParse, Table *pTab){ /* ** Check to see if virtual table module pMod can be have an eponymous ** virtual table instance. If it can, create one if one does not already -** exist. Return non-zero if the eponymous virtual table instance exists -** when this routine returns, and return zero if it does not exist. +** exist. Return non-zero if either the eponymous virtual table instance +** exists when this routine returns or if an attempt to create it failed +** and an error message was left in pParse. ** ** An eponymous virtual table instance is one that is named after its ** module, and more importantly, does not require a CREATE VIRTUAL TABLE -** statement in order to come into existance. Eponymous virtual table +** statement in order to come into existence. Eponymous virtual table ** instances always exist. They cannot be DROP-ed. ** ** Any virtual table module for which xConnect and xCreate are the same @@ -135458,18 +163163,22 @@ SQLITE_PRIVATE int sqlite3VtabEponymousTableInit(Parse *pParse, Module *pMod){ } pMod->pEpoTab = pTab; pTab->nTabRef = 1; + pTab->eTabType = TABTYP_VTAB; pTab->pSchema = db->aDb[0].pSchema; - assert( pTab->nModuleArg==0 ); + assert( pTab->u.vtab.nArg==0 ); pTab->iPKey = -1; + pTab->tabFlags |= TF_Eponymous; addModuleArgument(pParse, pTab, sqlite3DbStrDup(db, pTab->zName)); addModuleArgument(pParse, pTab, 0); addModuleArgument(pParse, pTab, sqlite3DbStrDup(db, pTab->zName)); + db->nSchemaLock++; rc = vtabCallConstructor(db, pTab, pMod, pModule->xConnect, &zErr); + db->nSchemaLock--; if( rc ){ sqlite3ErrorMsg(pParse, "%s", zErr); + pParse->rc = rc; sqlite3DbFree(db, zErr); sqlite3VtabEponymousTableClear(db, pMod); - return 0; } return 1; } @@ -135482,7 +163191,7 @@ SQLITE_PRIVATE void sqlite3VtabEponymousTableClear(sqlite3 *db, Module *pMod){ Table *pTab = pMod->pEpoTab; if( pTab!=0 ){ /* Mark the table as Ephemeral prior to deleting it, so that the - ** sqlite3DeleteTable() routine will know that it is not stored in + ** sqlite3DeleteTable() routine will know that it is not stored in ** the schema. */ pTab->tabFlags |= TF_Ephemeral; sqlite3DeleteTable(db, pTab); @@ -135498,8 +163207,8 @@ SQLITE_PRIVATE void sqlite3VtabEponymousTableClear(sqlite3 *db, Module *pMod){ ** within an xUpdate method. */ SQLITE_API int sqlite3_vtab_on_conflict(sqlite3 *db){ - static const unsigned char aMap[] = { - SQLITE_ROLLBACK, SQLITE_ABORT, SQLITE_FAIL, SQLITE_IGNORE, SQLITE_REPLACE + static const unsigned char aMap[] = { + SQLITE_ROLLBACK, SQLITE_ABORT, SQLITE_FAIL, SQLITE_IGNORE, SQLITE_REPLACE }; #ifdef SQLITE_ENABLE_API_ARMOR if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; @@ -135511,35 +163220,49 @@ SQLITE_API int sqlite3_vtab_on_conflict(sqlite3 *db){ } /* -** Call from within the xCreate() or xConnect() methods to provide +** Call from within the xCreate() or xConnect() methods to provide ** the SQLite core with additional information about the behavior ** of the virtual table being implemented. */ SQLITE_API int sqlite3_vtab_config(sqlite3 *db, int op, ...){ va_list ap; int rc = SQLITE_OK; + VtabCtx *p; #ifdef SQLITE_ENABLE_API_ARMOR if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; #endif sqlite3_mutex_enter(db->mutex); - va_start(ap, op); - switch( op ){ - case SQLITE_VTAB_CONSTRAINT_SUPPORT: { - VtabCtx *p = db->pVtabCtx; - if( !p ){ - rc = SQLITE_MISUSE_BKPT; - }else{ - assert( p->pTab==0 || IsVirtual(p->pTab) ); + p = db->pVtabCtx; + if( !p ){ + rc = SQLITE_MISUSE_BKPT; + }else{ + assert( p->pTab==0 || IsVirtual(p->pTab) ); + va_start(ap, op); + switch( op ){ + case SQLITE_VTAB_CONSTRAINT_SUPPORT: { p->pVTable->bConstraint = (u8)va_arg(ap, int); + break; + } + case SQLITE_VTAB_INNOCUOUS: { + p->pVTable->eVtabRisk = SQLITE_VTABRISK_Low; + break; + } + case SQLITE_VTAB_DIRECTONLY: { + p->pVTable->eVtabRisk = SQLITE_VTABRISK_High; + break; + } + case SQLITE_VTAB_USES_ALL_SCHEMAS: { + p->pVTable->bAllSchemas = 1; + break; + } + default: { + rc = SQLITE_MISUSE_BKPT; + break; } - break; } - default: - rc = SQLITE_MISUSE_BKPT; - break; + va_end(ap); } - va_end(ap); if( rc!=SQLITE_OK ) sqlite3Error(db, rc); sqlite3_mutex_leave(db->mutex); @@ -135588,20 +163311,9 @@ SQLITE_API int sqlite3_vtab_config(sqlite3 *db, int op, ...){ ** planner logic in "where.c". These definitions are broken out into ** a separate source file for easier editing. */ +#ifndef SQLITE_WHEREINT_H +#define SQLITE_WHEREINT_H -/* -** Trace output macros -*/ -#if defined(SQLITE_TEST) || defined(SQLITE_DEBUG) -/***/ extern int sqlite3WhereTrace; -#endif -#if defined(SQLITE_DEBUG) \ - && (defined(SQLITE_TEST) || defined(SQLITE_ENABLE_WHERETRACE)) -# define WHERETRACE(K,X) if(sqlite3WhereTrace&(K)) sqlite3DebugPrintf X -# define WHERETRACE_ENABLED 1 -#else -# define WHERETRACE(K,X) -#endif /* Forward references */ @@ -135617,6 +163329,28 @@ typedef struct WhereLoopBuilder WhereLoopBuilder; typedef struct WhereScan WhereScan; typedef struct WhereOrCost WhereOrCost; typedef struct WhereOrSet WhereOrSet; +typedef struct WhereMemBlock WhereMemBlock; +typedef struct WhereRightJoin WhereRightJoin; + +/* +** This object is a header on a block of allocated memory that will be +** automatically freed when its WInfo object is destructed. +*/ +struct WhereMemBlock { + WhereMemBlock *pNext; /* Next block in the chain */ + u64 sz; /* Bytes of space */ +}; + +/* +** Extra information attached to a WhereLevel that is a RIGHT JOIN. +*/ +struct WhereRightJoin { + int iMatch; /* Cursor used to determine prior matched rows */ + int regBloom; /* Bloom filter for iRJMatch */ + int regReturn; /* Return register for the interior subroutine */ + int addrSubrtn; /* Starting address for the interior subroutine */ + int endSubrtn; /* The last opcode in the interior subroutine */ +}; /* ** This object contains information needed to implement a single nested @@ -135638,18 +163372,23 @@ struct WhereLevel { int iTabCur; /* The VDBE cursor used to access the table */ int iIdxCur; /* The VDBE cursor used to access pIdx */ int addrBrk; /* Jump here to break out of the loop */ + int addrHalt; /* Abort the query due to empty table or similar */ int addrNxt; /* Jump here to start the next IN combination */ int addrSkip; /* Jump here for next iteration of skip-scan */ int addrCont; /* Jump here to continue with the next loop cycle */ int addrFirst; /* First instruction of interior of the loop */ int addrBody; /* Beginning of the body of this loop */ + int regBignull; /* big-null flag reg. True if a NULL-scan is needed */ + int addrBignull; /* Jump here for next part of big-null scan */ #ifndef SQLITE_LIKE_DOESNT_MATCH_BLOBS u32 iLikeRepCntr; /* LIKE range processing counter register (times 2) */ int addrLikeRep; /* LIKE range processing address */ #endif + int regFilter; /* Bloom filter */ + WhereRightJoin *pRJ; /* Extra information for RIGHT JOIN */ u8 iFrom; /* Which entry in the FROM clause */ u8 op, p3, p5; /* Opcode, P3 & P5 of the opcode that ends the loop */ - int p1, p2; /* Operands of the opcode used to ends the loop */ + int p1, p2; /* Operands of the opcode used to end the loop */ union { /* Information that depends on pWLoop->wsFlags */ struct { int nIn; /* Number of entries in aInLoop[] */ @@ -135657,11 +163396,11 @@ struct WhereLevel { int iCur; /* The VDBE cursor used by this IN operator */ int addrInTop; /* Top of the IN loop */ int iBase; /* Base register of multi-key index record */ - int nPrefix; /* Number of prior entires in the key */ + int nPrefix; /* Number of prior entries in the key */ u8 eEndLoopOp; /* IN Loop terminator. OP_Next or OP_Prev */ } *aInLoop; /* Information about each nested IN operator */ } in; /* Used when pWLoop->wsFlags&WHERE_IN_ABLE */ - Index *pCovidx; /* Possible covering index for WHERE_MULTI_OR */ + Index *pCoveringIdx; /* Possible covering index for WHERE_MULTI_OR */ } u; struct WhereLoop *pWLoop; /* The selected WhereLoop object */ Bitmask notReady; /* FROM entries not usable at this level */ @@ -135700,15 +163439,19 @@ struct WhereLoop { u16 nEq; /* Number of equality constraints */ u16 nBtm; /* Size of BTM vector */ u16 nTop; /* Size of TOP vector */ - u16 nIdxCol; /* Index column used for ORDER BY */ + u16 nDistinctCol; /* Index columns used to sort for DISTINCT */ Index *pIndex; /* Index used, or NULL */ + ExprList *pOrderBy; /* ORDER BY clause if this is really a subquery */ } btree; struct { /* Information for virtual tables */ int idxNum; /* Index number */ - u8 needFree; /* True if sqlite3_free(idxStr) is needed */ + u32 needFree : 1; /* True if sqlite3_free(idxStr) is needed */ + u32 bOmitOffset : 1; /* True to let virtual table handle offset */ + u32 bIdxNumHex : 1; /* Show idxNum as hex in EXPLAIN QUERY PLAN */ i8 isOrdered; /* True if satisfies ORDER BY */ u16 omitMask; /* Terms that may be omitted */ char *idxStr; /* Index identifier string */ + u32 mHandleIn; /* Terms to handle as IN(...) instead of == */ } vtab; } u; u32 wsFlags; /* WHERE_* flags describing the plan */ @@ -135717,6 +163460,10 @@ struct WhereLoop { /**** whereLoopXfer() copies fields above ***********************/ # define WHERE_LOOP_XFER_SZ offsetof(WhereLoop,nLSlot) u16 nLSlot; /* Number of slots allocated for aLTerm[] */ +#ifdef WHERETRACE_ENABLED + LogEst rStarDelta; /* Cost delta due to star-schema heuristic. Not + ** initialized unless pWInfo->bStarUsed */ +#endif WhereTerm **aLTerm; /* WhereTerms used */ WhereLoop *pNextLoop; /* Next WhereLoop object in the WhereClause */ WhereTerm *aLTermSpace[3]; /* Initial aLTerm[] space */ @@ -135724,7 +163471,7 @@ struct WhereLoop { /* This object holds the prerequisites and the cost of running a ** subquery on one operand of an OR operator in the WHERE clause. -** See WhereOrSet for additional information +** See WhereOrSet for additional information */ struct WhereOrCost { Bitmask prereq; /* Prerequisites */ @@ -135765,7 +163512,7 @@ struct WherePath { Bitmask revLoop; /* aLoop[]s that should be reversed for ORDER BY */ LogEst nRow; /* Estimated number of rows generated by this path */ LogEst rCost; /* Total cost of this path */ - LogEst rUnsorted; /* Total cost of this path ignoring sorting costs */ + LogEst rUnsort; /* Total cost of this path ignoring sorting costs */ i8 isOrdered; /* No. of ORDER BY terms satisfied. -1 for unknown */ WhereLoop **aLoop; /* Array of WhereLoop objects implementing this path */ }; @@ -135776,7 +163523,7 @@ struct WherePath { ** clause subexpression is separated from the others by AND operators, ** usually, or sometimes subexpressions separated by OR. ** -** All WhereTerms are collected into a single WhereClause structure. +** All WhereTerms are collected into a single WhereClause structure. ** The following identity holds: ** ** WhereTerm.pWC->a[WhereTerm.idx] == WhereTerm @@ -135831,9 +163578,14 @@ struct WhereTerm { u8 eMatchOp; /* Op for vtab MATCH/LIKE/GLOB/REGEXP terms */ int iParent; /* Disable pWC->a[iParent] when this term disabled */ int leftCursor; /* Cursor number of X in "X " */ - int iField; /* Field in (?,?,?) IN (SELECT...) vector */ +#ifdef SQLITE_DEBUG + int iTerm; /* Which WhereTerm is this, for debug purposes */ +#endif union { - int leftColumn; /* Column number of X in "X " */ + struct { + int leftColumn; /* Column number of X in "X " */ + int iField; /* Field in (?,?,?) IN (SELECT...) vector */ + } x; /* Opcode other than OP_OR or OP_AND */ WhereOrInfo *pOrInfo; /* Extra information if (eOperator & WO_OR)!=0 */ WhereAndInfo *pAndInfo; /* Extra information if (eOperator& WO_AND)!=0 */ } u; @@ -135844,23 +163596,26 @@ struct WhereTerm { /* ** Allowed values of WhereTerm.wtFlags */ -#define TERM_DYNAMIC 0x01 /* Need to call sqlite3ExprDelete(db, pExpr) */ -#define TERM_VIRTUAL 0x02 /* Added by the optimizer. Do not code */ -#define TERM_CODED 0x04 /* This term is already coded */ -#define TERM_COPIED 0x08 /* Has a child */ -#define TERM_ORINFO 0x10 /* Need to free the WhereTerm.u.pOrInfo object */ -#define TERM_ANDINFO 0x20 /* Need to free the WhereTerm.u.pAndInfo obj */ -#define TERM_OR_OK 0x40 /* Used during OR-clause processing */ -#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 -# define TERM_VNULL 0x80 /* Manufactured x>NULL or x<=NULL term */ -#else -# define TERM_VNULL 0x00 /* Disabled if not using stat3 */ -#endif -#define TERM_LIKEOPT 0x100 /* Virtual terms from the LIKE optimization */ -#define TERM_LIKECOND 0x200 /* Conditionally this LIKE operator term */ -#define TERM_LIKE 0x400 /* The original LIKE operator */ -#define TERM_IS 0x800 /* Term.pExpr is an IS operator */ +#define TERM_DYNAMIC 0x0001 /* Need to call sqlite3ExprDelete(db, pExpr) */ +#define TERM_VIRTUAL 0x0002 /* Added by the optimizer. Do not code */ +#define TERM_CODED 0x0004 /* This term is already coded */ +#define TERM_COPIED 0x0008 /* Has a child */ +#define TERM_ORINFO 0x0010 /* Need to free the WhereTerm.u.pOrInfo object */ +#define TERM_ANDINFO 0x0020 /* Need to free the WhereTerm.u.pAndInfo obj */ +#define TERM_OK 0x0040 /* Used during OR-clause processing */ +#define TERM_VNULL 0x0080 /* Manufactured x>NULL or x<=NULL term */ +#define TERM_LIKEOPT 0x0100 /* Virtual terms from the LIKE optimization */ +#define TERM_LIKECOND 0x0200 /* Conditionally this LIKE operator term */ +#define TERM_LIKE 0x0400 /* The original LIKE operator */ +#define TERM_IS 0x0800 /* Term.pExpr is an IS operator */ #define TERM_VARSELECT 0x1000 /* Term.pExpr contains a correlated sub-query */ +#define TERM_HEURTRUTH 0x2000 /* Heuristic truthProb used */ +#ifdef SQLITE_ENABLE_STAT4 +# define TERM_HIGHTRUTH 0x4000 /* Term excludes few rows */ +#else +# define TERM_HIGHTRUTH 0 /* Only used with STAT4 */ +#endif +#define TERM_SLICE 0x8000 /* One slice of a row-value/vector comparison */ /* ** An instance of the WhereScan object is used as an iterator for locating @@ -135871,11 +163626,11 @@ struct WhereScan { WhereClause *pWC; /* WhereClause currently being scanned */ const char *zCollName; /* Required collating sequence, if not NULL */ Expr *pIdxExpr; /* Search for this index expression */ - char idxaff; /* Must match this affinity, if zCollName!=NULL */ - unsigned char nEquiv; /* Number of entries in aEquiv[] */ - unsigned char iEquiv; /* Next unused slot in aEquiv[] */ - u32 opMask; /* Acceptable operators */ int k; /* Resume scanning at this->pWC->a[this->k] */ + u32 opMask; /* Acceptable operators */ + char idxaff; /* Must match this affinity, if zCollName!=NULL */ + unsigned char iEquiv; /* Current slot in aiCur[] and aiColumn[] */ + unsigned char nEquiv; /* Number of entries in aiCur[] and aiColumn[] */ int aiCur[11]; /* Cursors in the equivalence class */ i16 aiColumn[11]; /* Corresponding column number in the eq-class */ }; @@ -135899,7 +163654,8 @@ struct WhereClause { u8 hasOr; /* True if any a[].eOperator is WO_OR */ int nTerm; /* Number of terms */ int nSlot; /* Number of entries in a[] */ - WhereTerm *a; /* Each a[] describes a term of the WHERE cluase */ + int nBase; /* Number of terms through the last non-Virtual */ + WhereTerm *a; /* Each a[] describes a term of the WHERE clause */ #if defined(SQLITE_SMALL_STACK) WhereTerm aStatic[1]; /* Initial static space for a[] */ #else @@ -135928,8 +163684,8 @@ struct WhereAndInfo { ** An instance of the following structure keeps track of a mapping ** between VDBE cursor numbers and bits of the bitmasks in WhereTerm. ** -** The VDBE cursor numbers are small integers contained in -** SrcList_item.iCursor and Expr.iTable fields. For any given WHERE +** The VDBE cursor numbers are small integers contained in +** SrcItem.iCursor and Expr.iTable fields. For any given WHERE ** clause, the cursor numbers might not begin with 0 and they might ** contain gaps in the numbering sequence. But we want to make maximum ** use of the bits in our bitmasks. This structure provides a mapping @@ -135956,11 +163712,6 @@ struct WhereMaskSet { int ix[BMS]; /* Cursor assigned to each bit */ }; -/* -** Initialize a WhereMaskSet object -*/ -#define initMaskSet(P) (P)->n=0 - /* ** This object is a convenience wrapper holding all information needed ** to construct WhereLoop objects for a particular query. @@ -135968,20 +163719,22 @@ struct WhereMaskSet { struct WhereLoopBuilder { WhereInfo *pWInfo; /* Information about this WHERE */ WhereClause *pWC; /* WHERE clause terms */ - ExprList *pOrderBy; /* ORDER BY clause */ WhereLoop *pNew; /* Template WhereLoop */ WhereOrSet *pOrSet; /* Record best loops here, if not NULL */ -#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 +#ifdef SQLITE_ENABLE_STAT4 UnpackedRecord *pRec; /* Probe for stat4 (if required) */ int nRecValid; /* Number of valid fields currently in pRec */ #endif - unsigned int bldFlags; /* SQLITE_BLDF_* flags */ + unsigned char bldFlags1; /* First set of SQLITE_BLDF_* flags */ + unsigned char bldFlags2; /* Second set of SQLITE_BLDF_* flags */ unsigned int iPlanLimit; /* Search limiter */ }; /* Allowed values for WhereLoopBuider.bldFlags */ -#define SQLITE_BLDF_INDEXED 0x0001 /* An index is used */ -#define SQLITE_BLDF_UNIQUE 0x0002 /* All keys of a UNIQUE index used */ +#define SQLITE_BLDF1_INDEXED 0x0001 /* An index is used */ +#define SQLITE_BLDF1_UNIQUE 0x0002 /* All keys of a UNIQUE index used */ + +#define SQLITE_BLDF2_2NDPASS 0x0004 /* Second builder pass needed */ /* The WhereLoopBuilder.iPlanLimit is used to limit the number of ** index+constraint combinations the query planner will consider for a @@ -136018,29 +163771,45 @@ struct WhereInfo { SrcList *pTabList; /* List of tables in the join */ ExprList *pOrderBy; /* The ORDER BY clause or NULL */ ExprList *pResultSet; /* Result set of the query */ +#if WHERETRACE_ENABLED Expr *pWhere; /* The complete WHERE clause */ - LogEst iLimit; /* LIMIT if wctrlFlags has WHERE_USE_LIMIT */ +#endif + Select *pSelect; /* The entire SELECT statement containing WHERE */ int aiCurOnePass[2]; /* OP_OpenWrite cursors for the ONEPASS opt */ int iContinue; /* Jump here to continue with next record */ int iBreak; /* Jump here to break out of the loop */ int savedNQueryLoop; /* pParse->nQueryLoop outside the WHERE loop */ u16 wctrlFlags; /* Flags originally passed to sqlite3WhereBegin() */ + LogEst iLimit; /* LIMIT if wctrlFlags has WHERE_USE_LIMIT */ u8 nLevel; /* Number of nested loop */ i8 nOBSat; /* Number of ORDER BY terms satisfied by indices */ - u8 sorted; /* True if really sorted (not just grouped) */ u8 eOnePass; /* ONEPASS_OFF, or _SINGLE, or _MULTI */ - u8 untestedTerms; /* Not all WHERE terms resolved by outer loop */ u8 eDistinct; /* One of the WHERE_DISTINCT_* values */ - u8 bOrderedInnerLoop; /* True if only the inner-most loop is ordered */ + unsigned bDeferredSeek :1; /* Uses OP_DeferredSeek */ + unsigned untestedTerms :1; /* Not all WHERE terms resolved by outer loop */ + unsigned bOrderedInnerLoop:1;/* True if only the inner-most loop is ordered */ + unsigned sorted :1; /* True if really sorted (not just grouped) */ + unsigned bStarDone :1; /* True if check for star-query is complete */ + unsigned bStarUsed :1; /* True if star-query heuristic is used */ + LogEst nRowOut; /* Estimated number of output rows */ +#ifdef WHERETRACE_ENABLED + LogEst rTotalCost; /* Total cost of the solution */ +#endif int iTop; /* The very beginning of the WHERE loop */ + int iEndWhere; /* End of the WHERE clause itself */ WhereLoop *pLoops; /* List of all WhereLoop objects */ + WhereMemBlock *pMemToFree;/* Memory to free when this object destroyed */ Bitmask revMask; /* Mask of ORDER BY terms that need reversing */ - LogEst nRowOut; /* Estimated number of output rows */ WhereClause sWC; /* Decomposition of the WHERE clause */ WhereMaskSet sMaskSet; /* Map cursor numbers to bitmasks */ - WhereLevel a[1]; /* Information about each nest loop in WHERE */ + WhereLevel a[FLEXARRAY]; /* Information about each nest loop in WHERE */ }; +/* +** The size (in bytes) of a WhereInfo object that holds N WhereLevels. +*/ +#define SZ_WHEREINFO(N) ROUND8(offsetof(WhereInfo,a)+(N)*sizeof(WhereLevel)) + /* ** Private interfaces - callable only by other where.c routines. ** @@ -136049,6 +163818,8 @@ struct WhereInfo { SQLITE_PRIVATE Bitmask sqlite3WhereGetMask(WhereMaskSet*,int); #ifdef WHERETRACE_ENABLED SQLITE_PRIVATE void sqlite3WhereClausePrint(WhereClause *pWC); +SQLITE_PRIVATE void sqlite3WhereTermPrint(WhereTerm *pTerm, int iTerm); +SQLITE_PRIVATE void sqlite3WhereLoopPrint(const WhereLoop *p, const WhereClause *pWC); #endif SQLITE_PRIVATE WhereTerm *sqlite3WhereFindTerm( WhereClause *pWC, /* The WHERE clause to be searched */ @@ -136058,6 +163829,8 @@ SQLITE_PRIVATE WhereTerm *sqlite3WhereFindTerm( u32 op, /* Mask of WO_xx values describing operator */ Index *pIdx /* Must be compatible with this index, if not NULL */ ); +SQLITE_PRIVATE void *sqlite3WhereMalloc(WhereInfo *pWInfo, u64 nByte); +SQLITE_PRIVATE void *sqlite3WhereRealloc(WhereInfo *pWInfo, void *pOld, u64 nByte); /* wherecode.c: */ #ifndef SQLITE_OMIT_EXPLAIN @@ -136067,8 +163840,22 @@ SQLITE_PRIVATE int sqlite3WhereExplainOneScan( WhereLevel *pLevel, /* Scan to write OP_Explain opcode for */ u16 wctrlFlags /* Flags passed to sqlite3WhereBegin() */ ); +SQLITE_PRIVATE int sqlite3WhereExplainBloomFilter( + const Parse *pParse, /* Parse context */ + const WhereInfo *pWInfo, /* WHERE clause */ + const WhereLevel *pLevel /* Bloom filter on this level */ +); +SQLITE_PRIVATE void sqlite3WhereAddExplainText( + Parse *pParse, /* Parse context */ + int addr, + SrcList *pTabList, /* Table list this loop refers to */ + WhereLevel *pLevel, /* Scan to write OP_Explain opcode for */ + u16 wctrlFlags /* Flags passed to sqlite3WhereBegin() */ +); #else # define sqlite3WhereExplainOneScan(u,v,w,x) 0 +# define sqlite3WhereExplainBloomFilter(u,v,w) 0 +# define sqlite3WhereAddExplainText(u,v,w,x,y) #endif /* SQLITE_OMIT_EXPLAIN */ #ifdef SQLITE_ENABLE_STMT_SCANSTATUS SQLITE_PRIVATE void sqlite3WhereAddScanStatus( @@ -136088,16 +163875,22 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( WhereLevel *pLevel, /* The current level pointer */ Bitmask notReady /* Which tables are currently available */ ); +SQLITE_PRIVATE SQLITE_NOINLINE void sqlite3WhereRightJoinLoop( + WhereInfo *pWInfo, + int iLevel, + WhereLevel *pLevel +); /* whereexpr.c: */ SQLITE_PRIVATE void sqlite3WhereClauseInit(WhereClause*,WhereInfo*); SQLITE_PRIVATE void sqlite3WhereClauseClear(WhereClause*); SQLITE_PRIVATE void sqlite3WhereSplit(WhereClause*,Expr*,u8); +SQLITE_PRIVATE void sqlite3WhereAddLimit(WhereClause*, Select*); SQLITE_PRIVATE Bitmask sqlite3WhereExprUsage(WhereMaskSet*, Expr*); SQLITE_PRIVATE Bitmask sqlite3WhereExprUsageNN(WhereMaskSet*, Expr*); SQLITE_PRIVATE Bitmask sqlite3WhereExprListUsage(WhereMaskSet*, ExprList*); SQLITE_PRIVATE void sqlite3WhereExprAnalyze(SrcList*, WhereClause*); -SQLITE_PRIVATE void sqlite3WhereTabFuncArgs(Parse*, struct SrcList_item*, WhereClause*); +SQLITE_PRIVATE void sqlite3WhereTabFuncArgs(Parse*, SrcItem*, WhereClause*); @@ -136129,8 +163922,9 @@ SQLITE_PRIVATE void sqlite3WhereTabFuncArgs(Parse*, struct SrcList_item*, WhereC #define WO_AND 0x0400 /* Two or more AND-connected terms */ #define WO_EQUIV 0x0800 /* Of the form A==B, both columns */ #define WO_NOOP 0x1000 /* This term does not restrict search space */ +#define WO_ROWVAL 0x2000 /* A row-value term */ -#define WO_ALL 0x1fff /* Mask of all possible WO_* values */ +#define WO_ALL 0x3fff /* Mask of all possible WO_* values */ #define WO_SINGLE 0x01ff /* Mask of all non-compound WO_* values */ /* @@ -136158,6 +163952,17 @@ SQLITE_PRIVATE void sqlite3WhereTabFuncArgs(Parse*, struct SrcList_item*, WhereC #define WHERE_UNQ_WANTED 0x00010000 /* WHERE_ONEROW would have been helpful*/ #define WHERE_PARTIALIDX 0x00020000 /* The automatic index is partial */ #define WHERE_IN_EARLYOUT 0x00040000 /* Perhaps quit IN loops early */ +#define WHERE_BIGNULL_SORT 0x00080000 /* Column nEq of index is BIGNULL */ +#define WHERE_IN_SEEKSCAN 0x00100000 /* Seek-scan optimization for IN */ +#define WHERE_TRANSCONS 0x00200000 /* Uses a transitive constraint */ +#define WHERE_BLOOMFILTER 0x00400000 /* Consider using a Bloom-filter */ +#define WHERE_SELFCULL 0x00800000 /* nOut reduced by extra WHERE terms */ +#define WHERE_OMIT_OFFSET 0x01000000 /* Set offset counter to zero */ +#define WHERE_COROUTINE 0x02000000 /* Implemented by co-routine. + ** NB: False-negatives are possible */ +#define WHERE_EXPRIDX 0x04000000 /* Uses an index-on-expressions */ + +#endif /* !defined(SQLITE_WHEREINT_H) */ /************** End of whereInt.h ********************************************/ /************** Continuing where we left off in wherecode.c ******************/ @@ -136171,7 +163976,7 @@ static const char *explainIndexColumnName(Index *pIdx, int i){ i = pIdx->aiColumn[i]; if( i==XN_EXPR ) return ""; if( i==XN_ROWID ) return "rowid"; - return pIdx->pTable->aCol[i].zName; + return pIdx->pTable->aCol[i].zCnName; } /* @@ -136213,7 +164018,7 @@ static void explainAppendTerm( } /* -** Argument pLevel describes a strategy for scanning table pTab. This +** Argument pLevel describes a strategy for scanning table pTab. This ** function appends text to pStr that describes the subset of table ** rows scanned by the strategy in the form of an SQL expression. ** @@ -136252,54 +164057,48 @@ static void explainIndexRange(StrAccum *pStr, WhereLoop *pLoop){ } /* -** This function is a no-op unless currently processing an EXPLAIN QUERY PLAN -** command, or if either SQLITE_DEBUG or SQLITE_ENABLE_STMT_SCANSTATUS was -** defined at compile-time. If it is not a no-op, a single OP_Explain opcode -** is added to the output to describe the table scan strategy in pLevel. -** -** If an OP_Explain opcode is added to the VM, its address is returned. -** Otherwise, if no OP_Explain is coded, zero is returned. +** This function sets the P4 value of an existing OP_Explain opcode to +** text describing the loop in pLevel. If the OP_Explain opcode already has +** a P4 value, it is freed before it is overwritten. */ -SQLITE_PRIVATE int sqlite3WhereExplainOneScan( +SQLITE_PRIVATE void sqlite3WhereAddExplainText( Parse *pParse, /* Parse context */ + int addr, /* Address of OP_Explain opcode */ SrcList *pTabList, /* Table list this loop refers to */ WhereLevel *pLevel, /* Scan to write OP_Explain opcode for */ u16 wctrlFlags /* Flags passed to sqlite3WhereBegin() */ ){ - int ret = 0; -#if !defined(SQLITE_DEBUG) && !defined(SQLITE_ENABLE_STMT_SCANSTATUS) - if( sqlite3ParseToplevel(pParse)->explain==2 ) +#if !defined(SQLITE_DEBUG) + if( sqlite3ParseToplevel(pParse)->explain==2 || IS_STMT_SCANSTATUS(pParse->db) ) #endif { - struct SrcList_item *pItem = &pTabList->a[pLevel->iFrom]; - Vdbe *v = pParse->pVdbe; /* VM being constructed */ + VdbeOp *pOp = sqlite3VdbeGetOp(pParse->pVdbe, addr); + SrcItem *pItem = &pTabList->a[pLevel->iFrom]; sqlite3 *db = pParse->db; /* Database handle */ int isSearch; /* True for a SEARCH. False for SCAN. */ WhereLoop *pLoop; /* The controlling WhereLoop object */ u32 flags; /* Flags that describe this loop */ +#if defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_EXPLAIN) char *zMsg; /* Text to add to EQP output */ +#endif StrAccum str; /* EQP output string */ char zBuf[100]; /* Initial space for EQP output string */ + if( db->mallocFailed ) return; + pLoop = pLevel->pWLoop; flags = pLoop->wsFlags; - if( (flags&WHERE_MULTI_OR) || (wctrlFlags&WHERE_OR_SUBCLAUSE) ) return 0; isSearch = (flags&(WHERE_BTM_LIMIT|WHERE_TOP_LIMIT))!=0 || ((flags&WHERE_VIRTUALTABLE)==0 && (pLoop->u.btree.nEq>0)) || (wctrlFlags&(WHERE_ORDERBY_MIN|WHERE_ORDERBY_MAX)); sqlite3StrAccumInit(&str, db, zBuf, sizeof(zBuf), SQLITE_MAX_LENGTH); - sqlite3_str_appendall(&str, isSearch ? "SEARCH" : "SCAN"); - if( pItem->pSelect ){ - sqlite3_str_appendf(&str, " SUBQUERY %u", pItem->pSelect->selId); - }else{ - sqlite3_str_appendf(&str, " TABLE %s", pItem->zName); - } - - if( pItem->zAlias ){ - sqlite3_str_appendf(&str, " AS %s", pItem->zAlias); - } + str.printfFlags = SQLITE_PRINTF_INTERNAL; + sqlite3_str_appendf(&str, "%s %S%s", + isSearch ? "SEARCH" : "SCAN", + pItem, + pItem->fg.fromExists ? " EXISTS" : ""); if( (flags & (WHERE_IPK|WHERE_VIRTUALTABLE))==0 ){ const char *zFmt = 0; Index *pIdx; @@ -136307,7 +164106,7 @@ SQLITE_PRIVATE int sqlite3WhereExplainOneScan( assert( pLoop->u.btree.pIndex!=0 ); pIdx = pLoop->u.btree.pIndex; assert( !(flags&WHERE_AUTO_INDEX) || (flags&WHERE_IDX_ONLY) ); - if( !HasRowid(pItem->pTab) && IsPrimaryKeyIndex(pIdx) ){ + if( !HasRowid(pItem->pSTab) && IsPrimaryKeyIndex(pIdx) ){ if( isSearch ){ zFmt = "PRIMARY KEY"; } @@ -136315,7 +164114,7 @@ SQLITE_PRIVATE int sqlite3WhereExplainOneScan( zFmt = "AUTOMATIC PARTIAL COVERING INDEX"; }else if( flags & WHERE_AUTO_INDEX ){ zFmt = "AUTOMATIC COVERING INDEX"; - }else if( flags & WHERE_IDX_ONLY ){ + }else if( flags & (WHERE_IDX_ONLY|WHERE_EXPRIDX) ){ zFmt = "COVERING INDEX %s"; }else{ zFmt = "INDEX %s"; @@ -136326,26 +164125,39 @@ SQLITE_PRIVATE int sqlite3WhereExplainOneScan( explainIndexRange(&str, pLoop); } }else if( (flags & WHERE_IPK)!=0 && (flags & WHERE_CONSTRAINT)!=0 ){ - const char *zRangeOp; + char cRangeOp; +#if 0 /* Better output, but breaks many tests */ + const Table *pTab = pItem->pTab; + const char *zRowid = pTab->iPKey>=0 ? pTab->aCol[pTab->iPKey].zCnName: + "rowid"; +#else + const char *zRowid = "rowid"; +#endif + sqlite3_str_appendf(&str, " USING INTEGER PRIMARY KEY (%s", zRowid); if( flags&(WHERE_COLUMN_EQ|WHERE_COLUMN_IN) ){ - zRangeOp = "="; + cRangeOp = '='; }else if( (flags&WHERE_BOTH_LIMIT)==WHERE_BOTH_LIMIT ){ - zRangeOp = ">? AND rowid<"; + sqlite3_str_appendf(&str, ">? AND %s", zRowid); + cRangeOp = '<'; }else if( flags&WHERE_BTM_LIMIT ){ - zRangeOp = ">"; + cRangeOp = '>'; }else{ assert( flags&WHERE_TOP_LIMIT); - zRangeOp = "<"; + cRangeOp = '<'; } - sqlite3_str_appendf(&str, - " USING INTEGER PRIMARY KEY (rowid%s?)",zRangeOp); + sqlite3_str_appendf(&str, "%c?)", cRangeOp); } #ifndef SQLITE_OMIT_VIRTUALTABLE else if( (flags & WHERE_VIRTUALTABLE)!=0 ){ - sqlite3_str_appendf(&str, " VIRTUAL TABLE INDEX %d:%s", + sqlite3_str_appendall(&str, " VIRTUAL TABLE INDEX "); + sqlite3_str_appendf(&str, + pLoop->u.vtab.bIdxNumHex ? "0x%x:%s" : "%d:%s", pLoop->u.vtab.idxNum, pLoop->u.vtab.idxStr); } #endif + if( pItem->fg.jointype & JT_LEFT ){ + sqlite3_str_appendf(&str, " LEFT-JOIN"); + } #ifdef SQLITE_EXPLAIN_ESTIMATED_ROWS if( pLoop->nOut>=10 ){ sqlite3_str_appendf(&str, " (~%llu rows)", @@ -136354,23 +164166,115 @@ SQLITE_PRIVATE int sqlite3WhereExplainOneScan( sqlite3_str_append(&str, " (~1 row)", 9); } #endif +#if defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_EXPLAIN) zMsg = sqlite3StrAccumFinish(&str); sqlite3ExplainBreakpoint("",zMsg); - ret = sqlite3VdbeAddOp4(v, OP_Explain, sqlite3VdbeCurrentAddr(v), - pParse->addrExplain, 0, zMsg,P4_DYNAMIC); +#endif + + assert( pOp->opcode==OP_Explain ); + assert( pOp->p4type==P4_DYNAMIC || pOp->p4.z==0 ); + sqlite3DbFree(db, pOp->p4.z); + pOp->p4type = P4_DYNAMIC; + pOp->p4.z = sqlite3StrAccumFinish(&str); + } +} + + +/* +** This function is a no-op unless currently processing an EXPLAIN QUERY PLAN +** command, or if stmt_scanstatus_v2() stats are enabled, or if SQLITE_DEBUG +** was defined at compile-time. If it is not a no-op, a single OP_Explain +** opcode is added to the output to describe the table scan strategy in pLevel. +** +** If an OP_Explain opcode is added to the VM, its address is returned. +** Otherwise, if no OP_Explain is coded, zero is returned. +*/ +SQLITE_PRIVATE int sqlite3WhereExplainOneScan( + Parse *pParse, /* Parse context */ + SrcList *pTabList, /* Table list this loop refers to */ + WhereLevel *pLevel, /* Scan to write OP_Explain opcode for */ + u16 wctrlFlags /* Flags passed to sqlite3WhereBegin() */ +){ + int ret = 0; +#if !defined(SQLITE_DEBUG) + if( sqlite3ParseToplevel(pParse)->explain==2 || IS_STMT_SCANSTATUS(pParse->db) ) +#endif + { + if( (pLevel->pWLoop->wsFlags & WHERE_MULTI_OR)==0 + && (wctrlFlags & WHERE_OR_SUBCLAUSE)==0 + ){ + Vdbe *v = pParse->pVdbe; + int addr = sqlite3VdbeCurrentAddr(v); + ret = sqlite3VdbeAddOp3( + v, OP_Explain, addr, pParse->addrExplain, pLevel->pWLoop->rRun + ); + sqlite3WhereAddExplainText(pParse, addr, pTabList, pLevel, wctrlFlags); + } } return ret; } + +/* +** Add a single OP_Explain opcode that describes a Bloom filter. +** +** Or if not processing EXPLAIN QUERY PLAN and not in a SQLITE_DEBUG and/or +** SQLITE_ENABLE_STMT_SCANSTATUS build, then OP_Explain opcodes are not +** required and this routine is a no-op. +** +** If an OP_Explain opcode is added to the VM, its address is returned. +** Otherwise, if no OP_Explain is coded, zero is returned. +*/ +SQLITE_PRIVATE int sqlite3WhereExplainBloomFilter( + const Parse *pParse, /* Parse context */ + const WhereInfo *pWInfo, /* WHERE clause */ + const WhereLevel *pLevel /* Bloom filter on this level */ +){ + int ret = 0; + SrcItem *pItem = &pWInfo->pTabList->a[pLevel->iFrom]; + Vdbe *v = pParse->pVdbe; /* VM being constructed */ + sqlite3 *db = pParse->db; /* Database handle */ + char *zMsg; /* Text to add to EQP output */ + int i; /* Loop counter */ + WhereLoop *pLoop; /* The where loop */ + StrAccum str; /* EQP output string */ + char zBuf[100]; /* Initial space for EQP output string */ + + sqlite3StrAccumInit(&str, db, zBuf, sizeof(zBuf), SQLITE_MAX_LENGTH); + str.printfFlags = SQLITE_PRINTF_INTERNAL; + sqlite3_str_appendf(&str, "BLOOM FILTER ON %S (", pItem); + pLoop = pLevel->pWLoop; + if( pLoop->wsFlags & WHERE_IPK ){ + const Table *pTab = pItem->pSTab; + if( pTab->iPKey>=0 ){ + sqlite3_str_appendf(&str, "%s=?", pTab->aCol[pTab->iPKey].zCnName); + }else{ + sqlite3_str_appendf(&str, "rowid=?"); + } + }else{ + for(i=pLoop->nSkip; iu.btree.nEq; i++){ + const char *z = explainIndexColumnName(pLoop->u.btree.pIndex, i); + if( i>pLoop->nSkip ) sqlite3_str_append(&str, " AND ", 5); + sqlite3_str_appendf(&str, "%s=?", z); + } + } + sqlite3_str_append(&str, ")", 1); + zMsg = sqlite3StrAccumFinish(&str); + ret = sqlite3VdbeAddOp4(v, OP_Explain, sqlite3VdbeCurrentAddr(v), + pParse->addrExplain, 0, zMsg,P4_DYNAMIC); + + sqlite3VdbeScanStatus(v, sqlite3VdbeCurrentAddr(v)-1, 0, 0, 0, 0); + return ret; +} #endif /* SQLITE_OMIT_EXPLAIN */ #ifdef SQLITE_ENABLE_STMT_SCANSTATUS /* ** Configure the VM passed as the first argument with an -** sqlite3_stmt_scanstatus() entry corresponding to the scan used to -** implement level pLvl. Argument pSrclist is a pointer to the FROM +** sqlite3_stmt_scanstatus() entry corresponding to the scan used to +** implement level pLvl. Argument pSrclist is a pointer to the FROM ** clause that the scan reads data from. ** -** If argument addrExplain is not 0, it must be the address of an +** If argument addrExplain is not 0, it must be the address of an ** OP_Explain instruction that describes the same loop. */ SQLITE_PRIVATE void sqlite3WhereAddScanStatus( @@ -136379,16 +164283,40 @@ SQLITE_PRIVATE void sqlite3WhereAddScanStatus( WhereLevel *pLvl, /* Level to add scanstatus() entry for */ int addrExplain /* Address of OP_Explain (or 0) */ ){ - const char *zObj = 0; - WhereLoop *pLoop = pLvl->pWLoop; - if( (pLoop->wsFlags & WHERE_VIRTUALTABLE)==0 && pLoop->u.btree.pIndex!=0 ){ - zObj = pLoop->u.btree.pIndex->zName; - }else{ - zObj = pSrclist->a[pLvl->iFrom].zName; + if( IS_STMT_SCANSTATUS( sqlite3VdbeDb(v) ) ){ + const char *zObj = 0; + WhereLoop *pLoop = pLvl->pWLoop; + int wsFlags = pLoop->wsFlags; + int viaCoroutine = 0; + + if( (wsFlags & WHERE_VIRTUALTABLE)==0 && pLoop->u.btree.pIndex!=0 ){ + zObj = pLoop->u.btree.pIndex->zName; + }else{ + zObj = pSrclist->a[pLvl->iFrom].zName; + viaCoroutine = pSrclist->a[pLvl->iFrom].fg.viaCoroutine; + } + sqlite3VdbeScanStatus( + v, addrExplain, pLvl->addrBody, pLvl->addrVisit, pLoop->nOut, zObj + ); + + if( viaCoroutine==0 ){ + if( (wsFlags & (WHERE_MULTI_OR|WHERE_AUTO_INDEX))==0 ){ + sqlite3VdbeScanStatusRange(v, addrExplain, -1, pLvl->iTabCur); + } + if( wsFlags & WHERE_INDEXED ){ + sqlite3VdbeScanStatusRange(v, addrExplain, -1, pLvl->iIdxCur); + } + }else{ + int addr; + VdbeOp *pOp; + assert( pSrclist->a[pLvl->iFrom].fg.isSubquery ); + addr = pSrclist->a[pLvl->iFrom].u4.pSubq->addrFillSub; + pOp = sqlite3VdbeGetOp(v, addr-1); + assert( sqlite3VdbeDb(v)->mallocFailed || pOp->opcode==OP_InitCoroutine ); + assert( sqlite3VdbeDb(v)->mallocFailed || pOp->p2>addr ); + sqlite3VdbeScanStatusRange(v, addrExplain, addr, pOp->p2-1); + } } - sqlite3VdbeScanStatus( - v, addrExplain, pLvl->addrBody, pLvl->addrVisit, pLoop->nOut, zObj - ); } #endif @@ -136426,7 +164354,7 @@ SQLITE_PRIVATE void sqlite3WhereAddScanStatus( ** ** Only the parent term was in the original WHERE clause. The child1 ** and child2 terms were added by the LIKE optimization. If both of -** the virtual child terms are valid, then testing of the parent can be +** the virtual child terms are valid, then testing of the parent can be ** skipped. ** ** Usually the parent term is marked as TERM_CODED. But if the parent @@ -136439,7 +164367,7 @@ static void disableTerm(WhereLevel *pLevel, WhereTerm *pTerm){ int nLoop = 0; assert( pTerm!=0 ); while( (pTerm->wtFlags & TERM_CODED)==0 - && (pLevel->iLeftJoin==0 || ExprHasProperty(pTerm->pExpr, EP_FromJoin)) + && (pLevel->iLeftJoin==0 || ExprHasProperty(pTerm->pExpr, EP_OuterON)) && (pLevel->notReady & pTerm->prereqAll)==0 ){ if( nLoop && (pTerm->wtFlags & TERM_LIKE)!=0 ){ @@ -136447,6 +164375,12 @@ static void disableTerm(WhereLevel *pLevel, WhereTerm *pTerm){ }else{ pTerm->wtFlags |= TERM_CODED; } +#ifdef WHERETRACE_ENABLED + if( (sqlite3WhereTrace & 0x4001)==0x4001 ){ + sqlite3DebugPrintf("DISABLE-"); + sqlite3WhereTermPrint(pTerm, (int)(pTerm - (pTerm->pWC->a))); + } +#endif if( pTerm->iParent<0 ) break; pTerm = &pTerm->pWC->a[pTerm->iParent]; assert( pTerm!=0 ); @@ -136458,11 +164392,11 @@ static void disableTerm(WhereLevel *pLevel, WhereTerm *pTerm){ /* ** Code an OP_Affinity opcode to apply the column affinity string zAff -** to the n registers starting at base. +** to the n registers starting at base. ** -** As an optimization, SQLITE_AFF_BLOB entries (which are no-ops) at the -** beginning and end of zAff are ignored. If all entries in zAff are -** SQLITE_AFF_BLOB, then no code gets generated. +** As an optimization, SQLITE_AFF_BLOB and SQLITE_AFF_NONE entries (which +** are no-ops) at the beginning and end of zAff are ignored. If all entries +** in zAff are SQLITE_AFF_BLOB or SQLITE_AFF_NONE, then no code gets generated. ** ** This routine makes its own copy of zAff so that the caller is free ** to modify zAff after this routine returns. @@ -136475,15 +164409,16 @@ static void codeApplyAffinity(Parse *pParse, int base, int n, char *zAff){ } assert( v!=0 ); - /* Adjust base and n to skip over SQLITE_AFF_BLOB entries at the beginning - ** and end of the affinity string. + /* Adjust base and n to skip over SQLITE_AFF_BLOB and SQLITE_AFF_NONE + ** entries at the beginning and end of the affinity string. */ - while( n>0 && zAff[0]==SQLITE_AFF_BLOB ){ + assert( SQLITE_AFF_NONE0 && zAff[0]<=SQLITE_AFF_BLOB ){ n--; base++; zAff++; } - while( n>1 && zAff[n-1]==SQLITE_AFF_BLOB ){ + while( n>1 && zAff[n-1]<=SQLITE_AFF_BLOB ){ n--; } @@ -136494,7 +164429,7 @@ static void codeApplyAffinity(Parse *pParse, int base, int n, char *zAff){ } /* -** Expression pRight, which is the RHS of a comparison operation, is +** Expression pRight, which is the RHS of a comparison operation, is ** either a vector of n elements or, if n==1, a scalar expression. ** Before the comparison operation, affinity zAff is to be applied ** to the pRight values. This function modifies characters within the @@ -136519,11 +164454,44 @@ static void updateRangeAffinityStr( } } +/* +** The pOrderBy->a[].u.x.iOrderByCol values might be incorrect because +** columns might have been rearranged in the result set. This routine +** fixes them up. +** +** pEList is the new result set. The pEList->a[].u.x.iOrderByCol values +** contain the *old* locations of each expression. This is a temporary +** use of u.x.iOrderByCol, not its intended use. The caller must reset +** u.x.iOrderByCol back to zero for all entries in pEList before the +** caller returns. +** +** This routine changes pOrderBy->a[].u.x.iOrderByCol values from +** pEList->a[N].u.x.iOrderByCol into N+1. (The "+1" is because of the 1-based +** indexing used by iOrderByCol.) Or if no match, iOrderByCol is set to zero. +*/ +static void adjustOrderByCol(ExprList *pOrderBy, ExprList *pEList){ + int i, j; + if( pOrderBy==0 ) return; + for(i=0; inExpr; i++){ + int t = pOrderBy->a[i].u.x.iOrderByCol; + if( t==0 ) continue; + for(j=0; jnExpr; j++){ + if( pEList->a[j].u.x.iOrderByCol==t ){ + pOrderBy->a[i].u.x.iOrderByCol = j+1; + break; + } + } + if( j>=pEList->nExpr ){ + pOrderBy->a[i].u.x.iOrderByCol = 0; + } + } +} + /* ** pX is an expression of the form: (vector) IN (SELECT ...) ** In other words, it is a vector IN operator with a SELECT clause on the -** LHS. But not all terms in the vector are indexable and the terms might +** RHS. But not all terms in the vector are indexable and the terms might ** not be in the correct order for indexing. ** ** This routine makes a copy of the input pX expression and then adjusts @@ -136556,68 +164524,217 @@ static Expr *removeUnindexableInClauseTerms( Expr *pX /* The IN expression to be reduced */ ){ sqlite3 *db = pParse->db; - Expr *pNew = sqlite3ExprDup(db, pX, 0); + Select *pSelect; /* Pointer to the SELECT on the RHS */ + Expr *pNew; + pNew = sqlite3ExprDup(db, pX, 0); if( db->mallocFailed==0 ){ - ExprList *pOrigRhs = pNew->x.pSelect->pEList; /* Original unmodified RHS */ - ExprList *pOrigLhs = pNew->pLeft->x.pList; /* Original unmodified LHS */ - ExprList *pRhs = 0; /* New RHS after modifications */ - ExprList *pLhs = 0; /* New LHS after mods */ - int i; /* Loop counter */ - Select *pSelect; /* Pointer to the SELECT on the RHS */ - - for(i=iEq; inLTerm; i++){ - if( pLoop->aLTerm[i]->pExpr==pX ){ - int iField = pLoop->aLTerm[i]->iField - 1; - if( pOrigRhs->a[iField].pExpr==0 ) continue; /* Duplicate PK column */ - pRhs = sqlite3ExprListAppend(pParse, pRhs, pOrigRhs->a[iField].pExpr); - pOrigRhs->a[iField].pExpr = 0; - assert( pOrigLhs->a[iField].pExpr!=0 ); - pLhs = sqlite3ExprListAppend(pParse, pLhs, pOrigLhs->a[iField].pExpr); - pOrigLhs->a[iField].pExpr = 0; - } - } - sqlite3ExprListDelete(db, pOrigRhs); - sqlite3ExprListDelete(db, pOrigLhs); - pNew->pLeft->x.pList = pLhs; - pNew->x.pSelect->pEList = pRhs; - if( pLhs && pLhs->nExpr==1 ){ - /* Take care here not to generate a TK_VECTOR containing only a - ** single value. Since the parser never creates such a vector, some - ** of the subroutines do not handle this case. */ - Expr *p = pLhs->a[0].pExpr; - pLhs->a[0].pExpr = 0; - sqlite3ExprDelete(db, pNew->pLeft); - pNew->pLeft = p; - } - pSelect = pNew->x.pSelect; - if( pSelect->pOrderBy ){ - /* If the SELECT statement has an ORDER BY clause, zero the - ** iOrderByCol variables. These are set to non-zero when an - ** ORDER BY term exactly matches one of the terms of the - ** result-set. Since the result-set of the SELECT statement may - ** have been modified or reordered, these variables are no longer - ** set correctly. Since setting them is just an optimization, - ** it's easiest just to zero them here. */ - ExprList *pOrderBy = pSelect->pOrderBy; - for(i=0; inExpr; i++){ - pOrderBy->a[i].u.x.iOrderByCol = 0; + for(pSelect=pNew->x.pSelect; pSelect; pSelect=pSelect->pPrior){ + ExprList *pOrigRhs; /* Original unmodified RHS */ + ExprList *pOrigLhs = 0; /* Original unmodified LHS */ + ExprList *pRhs = 0; /* New RHS after modifications */ + ExprList *pLhs = 0; /* New LHS after mods */ + int i; /* Loop counter */ + + assert( ExprUseXSelect(pNew) ); + pOrigRhs = pSelect->pEList; + assert( pNew->pLeft!=0 ); + assert( ExprUseXList(pNew->pLeft) ); + if( pSelect==pNew->x.pSelect ){ + pOrigLhs = pNew->pLeft->x.pList; + } + for(i=iEq; inLTerm; i++){ + if( pLoop->aLTerm[i]->pExpr==pX ){ + int iField; + assert( (pLoop->aLTerm[i]->eOperator & (WO_OR|WO_AND))==0 ); + iField = pLoop->aLTerm[i]->u.x.iField - 1; + if( NEVER(pOrigRhs->a[iField].pExpr==0) ){ + continue; /* Duplicate PK column */ + } + pRhs = sqlite3ExprListAppend(pParse, pRhs, pOrigRhs->a[iField].pExpr); + pOrigRhs->a[iField].pExpr = 0; + if( pRhs ) pRhs->a[pRhs->nExpr-1].u.x.iOrderByCol = iField+1; + if( pOrigLhs ){ + assert( pOrigLhs->a[iField].pExpr!=0 ); + pLhs = sqlite3ExprListAppend(pParse,pLhs,pOrigLhs->a[iField].pExpr); + pOrigLhs->a[iField].pExpr = 0; + } + } + } + sqlite3ExprListDelete(db, pOrigRhs); + if( pOrigLhs ){ + sqlite3ExprListDelete(db, pOrigLhs); + pNew->pLeft->x.pList = pLhs; + } + pSelect->pEList = pRhs; + pSelect->selId = ++pParse->nSelect; /* Req'd for SubrtnSig validity */ + if( pLhs && pLhs->nExpr==1 ){ + /* Take care here not to generate a TK_VECTOR containing only a + ** single value. Since the parser never creates such a vector, some + ** of the subroutines do not handle this case. */ + Expr *p = pLhs->a[0].pExpr; + pLhs->a[0].pExpr = 0; + sqlite3ExprDelete(db, pNew->pLeft); + pNew->pLeft = p; + } + + /* If either the ORDER BY clause or the GROUP BY clause contains + ** references to result-set columns, those references might now be + ** obsolete. So fix them up. + */ + assert( pRhs!=0 || db->mallocFailed ); + if( pRhs ){ + adjustOrderByCol(pSelect->pOrderBy, pRhs); + adjustOrderByCol(pSelect->pGroupBy, pRhs); + for(i=0; inExpr; i++) pRhs->a[i].u.x.iOrderByCol = 0; } - } #if 0 - printf("For indexing, change the IN expr:\n"); - sqlite3TreeViewExpr(0, pX, 0); - printf("Into:\n"); - sqlite3TreeViewExpr(0, pNew, 0); + printf("For indexing, change the IN expr:\n"); + sqlite3TreeViewExpr(0, pX, 0); + printf("Into:\n"); + sqlite3TreeViewExpr(0, pNew, 0); #endif + } } return pNew; } +#ifndef SQLITE_OMIT_SUBQUERY +/* +** Generate code for a single X IN (....) term of the WHERE clause. +** +** This is a special-case of codeEqualityTerm() that works for IN operators +** only. It is broken out into a subroutine because this case is +** uncommon and by splitting it off into a subroutine, the common case +** runs faster. +** +** The current value for the constraint is left in register iTarget. +** This routine sets up a loop that will iterate over all values of X. +*/ +static SQLITE_NOINLINE void codeINTerm( + Parse *pParse, /* The parsing context */ + WhereTerm *pTerm, /* The term of the WHERE clause to be coded */ + WhereLevel *pLevel, /* The level of the FROM clause we are working on */ + int iEq, /* Index of the equality term within this level */ + int bRev, /* True for reverse-order IN operations */ + int iTarget /* Attempt to leave results in this register */ +){ + Expr *pX = pTerm->pExpr; + int eType = IN_INDEX_NOOP; + int iTab; + struct InLoop *pIn; + WhereLoop *pLoop = pLevel->pWLoop; + Vdbe *v = pParse->pVdbe; + int i; + int nEq = 0; + int *aiMap = 0; + + if( (pLoop->wsFlags & WHERE_VIRTUALTABLE)==0 + && pLoop->u.btree.pIndex!=0 + && pLoop->u.btree.pIndex->aSortOrder[iEq] + ){ + testcase( iEq==0 ); + testcase( bRev ); + bRev = !bRev; + } + assert( pX->op==TK_IN ); + + for(i=0; iaLTerm[i] && pLoop->aLTerm[i]->pExpr==pX ){ + disableTerm(pLevel, pTerm); + return; + } + } + for(i=iEq; inLTerm; i++){ + assert( pLoop->aLTerm[i]!=0 ); + if( pLoop->aLTerm[i]->pExpr==pX ) nEq++; + } + + iTab = 0; + if( !ExprUseXSelect(pX) || pX->x.pSelect->pEList->nExpr==1 ){ + eType = sqlite3FindInIndex(pParse, pX, IN_INDEX_LOOP, 0, 0, &iTab); + }else{ + sqlite3 *db = pParse->db; + Expr *pXMod = removeUnindexableInClauseTerms(pParse, iEq, pLoop, pX); + if( !db->mallocFailed ){ + aiMap = (int*)sqlite3DbMallocZero(db, sizeof(int)*nEq); + eType = sqlite3FindInIndex(pParse, pXMod, IN_INDEX_LOOP, 0, aiMap, &iTab); + } + sqlite3ExprDelete(db, pXMod); + } + + if( eType==IN_INDEX_INDEX_DESC ){ + testcase( bRev ); + bRev = !bRev; + } + sqlite3VdbeAddOp2(v, bRev ? OP_Last : OP_Rewind, iTab, 0); + VdbeCoverageIf(v, bRev); + VdbeCoverageIf(v, !bRev); + + assert( (pLoop->wsFlags & WHERE_MULTI_OR)==0 ); + pLoop->wsFlags |= WHERE_IN_ABLE; + if( pLevel->u.in.nIn==0 ){ + pLevel->addrNxt = sqlite3VdbeMakeLabel(pParse); + } + if( iEq>0 && (pLoop->wsFlags & WHERE_IN_SEEKSCAN)==0 ){ + pLoop->wsFlags |= WHERE_IN_EARLYOUT; + } + + i = pLevel->u.in.nIn; + pLevel->u.in.nIn += nEq; + pLevel->u.in.aInLoop = + sqlite3WhereRealloc(pTerm->pWC->pWInfo, + pLevel->u.in.aInLoop, + sizeof(pLevel->u.in.aInLoop[0])*pLevel->u.in.nIn); + pIn = pLevel->u.in.aInLoop; + if( pIn ){ + int iMap = 0; /* Index in aiMap[] */ + pIn += i; + for(i=iEq; inLTerm; i++){ + if( pLoop->aLTerm[i]->pExpr==pX ){ + int iOut = iTarget + i - iEq; + if( eType==IN_INDEX_ROWID ){ + pIn->addrInTop = sqlite3VdbeAddOp2(v, OP_Rowid, iTab, iOut); + }else{ + int iCol = aiMap ? aiMap[iMap++] : 0; + pIn->addrInTop = sqlite3VdbeAddOp3(v,OP_Column,iTab, iCol, iOut); + } + sqlite3VdbeAddOp1(v, OP_IsNull, iOut); VdbeCoverage(v); + if( i==iEq ){ + pIn->iCur = iTab; + pIn->eEndLoopOp = bRev ? OP_Prev : OP_Next; + if( iEq>0 ){ + pIn->iBase = iTarget - i; + pIn->nPrefix = i; + }else{ + pIn->nPrefix = 0; + } + }else{ + pIn->eEndLoopOp = OP_Noop; + } + pIn++; + } + } + testcase( iEq>0 + && (pLoop->wsFlags & WHERE_IN_SEEKSCAN)==0 + && (pLoop->wsFlags & WHERE_VIRTUALTABLE)!=0 ); + if( iEq>0 + && (pLoop->wsFlags & (WHERE_IN_SEEKSCAN|WHERE_VIRTUALTABLE))==0 + ){ + sqlite3VdbeAddOp3(v, OP_SeekHit, pLevel->iIdxCur, 0, iEq); + } + }else{ + pLevel->u.in.nIn = 0; + } + sqlite3DbFree(pParse->db, aiMap); +} +#endif + + /* ** Generate code for a single equality term of the WHERE clause. An equality -** term can be either X=expr or X IN (...). pTerm is the term to be +** term can be either X=expr or X IN (...). pTerm is the term to be ** coded. ** ** The current value for the constraint is left in a register, the index @@ -136639,7 +164756,6 @@ static int codeEqualityTerm( int iTarget /* Attempt to leave results in this register */ ){ Expr *pX = pTerm->pExpr; - Vdbe *v = pParse->pVdbe; int iReg; /* Register holding results */ assert( pLevel->pWLoop->aLTerm[iEq]==pTerm ); @@ -136648,111 +164764,30 @@ static int codeEqualityTerm( iReg = sqlite3ExprCodeTarget(pParse, pX->pRight, iTarget); }else if( pX->op==TK_ISNULL ){ iReg = iTarget; - sqlite3VdbeAddOp2(v, OP_Null, 0, iReg); + sqlite3VdbeAddOp2(pParse->pVdbe, OP_Null, 0, iReg); #ifndef SQLITE_OMIT_SUBQUERY }else{ - int eType = IN_INDEX_NOOP; - int iTab; - struct InLoop *pIn; - WhereLoop *pLoop = pLevel->pWLoop; - int i; - int nEq = 0; - int *aiMap = 0; - - if( (pLoop->wsFlags & WHERE_VIRTUALTABLE)==0 - && pLoop->u.btree.pIndex!=0 - && pLoop->u.btree.pIndex->aSortOrder[iEq] - ){ - testcase( iEq==0 ); - testcase( bRev ); - bRev = !bRev; - } assert( pX->op==TK_IN ); iReg = iTarget; - - for(i=0; iaLTerm[i] && pLoop->aLTerm[i]->pExpr==pX ){ - disableTerm(pLevel, pTerm); - return iTarget; - } - } - for(i=iEq;inLTerm; i++){ - assert( pLoop->aLTerm[i]!=0 ); - if( pLoop->aLTerm[i]->pExpr==pX ) nEq++; - } - - iTab = 0; - if( (pX->flags & EP_xIsSelect)==0 || pX->x.pSelect->pEList->nExpr==1 ){ - eType = sqlite3FindInIndex(pParse, pX, IN_INDEX_LOOP, 0, 0, &iTab); - }else{ - sqlite3 *db = pParse->db; - pX = removeUnindexableInClauseTerms(pParse, iEq, pLoop, pX); - - if( !db->mallocFailed ){ - aiMap = (int*)sqlite3DbMallocZero(pParse->db, sizeof(int)*nEq); - eType = sqlite3FindInIndex(pParse, pX, IN_INDEX_LOOP, 0, aiMap, &iTab); - pTerm->pExpr->iTable = iTab; - } - sqlite3ExprDelete(db, pX); - pX = pTerm->pExpr; - } - - if( eType==IN_INDEX_INDEX_DESC ){ - testcase( bRev ); - bRev = !bRev; - } - sqlite3VdbeAddOp2(v, bRev ? OP_Last : OP_Rewind, iTab, 0); - VdbeCoverageIf(v, bRev); - VdbeCoverageIf(v, !bRev); - assert( (pLoop->wsFlags & WHERE_MULTI_OR)==0 ); - - pLoop->wsFlags |= WHERE_IN_ABLE; - if( pLevel->u.in.nIn==0 ){ - pLevel->addrNxt = sqlite3VdbeMakeLabel(pParse); - } - - i = pLevel->u.in.nIn; - pLevel->u.in.nIn += nEq; - pLevel->u.in.aInLoop = - sqlite3DbReallocOrFree(pParse->db, pLevel->u.in.aInLoop, - sizeof(pLevel->u.in.aInLoop[0])*pLevel->u.in.nIn); - pIn = pLevel->u.in.aInLoop; - if( pIn ){ - int iMap = 0; /* Index in aiMap[] */ - pIn += i; - for(i=iEq;inLTerm; i++){ - if( pLoop->aLTerm[i]->pExpr==pX ){ - int iOut = iReg + i - iEq; - if( eType==IN_INDEX_ROWID ){ - pIn->addrInTop = sqlite3VdbeAddOp2(v, OP_Rowid, iTab, iOut); - }else{ - int iCol = aiMap ? aiMap[iMap++] : 0; - pIn->addrInTop = sqlite3VdbeAddOp3(v,OP_Column,iTab, iCol, iOut); - } - sqlite3VdbeAddOp1(v, OP_IsNull, iOut); VdbeCoverage(v); - if( i==iEq ){ - pIn->iCur = iTab; - pIn->eEndLoopOp = bRev ? OP_Prev : OP_Next; - if( iEq>0 && (pLoop->wsFlags & WHERE_VIRTUALTABLE)==0 ){ - pIn->iBase = iReg - i; - pIn->nPrefix = i; - pLoop->wsFlags |= WHERE_IN_EARLYOUT; - }else{ - pIn->nPrefix = 0; - } - }else{ - pIn->eEndLoopOp = OP_Noop; - } - pIn++; - } - } - }else{ - pLevel->u.in.nIn = 0; - } - sqlite3DbFree(pParse->db, aiMap); + codeINTerm(pParse, pTerm, pLevel, iEq, bRev, iTarget); #endif } - disableTerm(pLevel, pTerm); + + /* As an optimization, try to disable the WHERE clause term that is + ** driving the index as it will always be true. The correct answer is + ** obtained regardless, but we might get the answer with fewer CPU cycles + ** by omitting the term. + ** + ** But do not disable the term unless we are certain that the term is + ** not a transitive constraint. For an example of where that does not + ** work, see https://sqlite.org/forum/forumpost/eb8613976a (2021-05-04) + */ + if( (pLevel->pWLoop->wsFlags & WHERE_TRANSCONS)==0 + || (pTerm->eOperator & WO_EQUIV)==0 + ){ + disableTerm(pLevel, pTerm); + } + return iReg; } @@ -136763,7 +164798,7 @@ static int codeEqualityTerm( ** For example, consider table t1(a,b,c,d,e,f) with index i1(a,b,c). ** Suppose the WHERE clause is this: a==5 AND b IN (1,2,3) AND c>5 AND c<10 ** The index has as many as three equality constraints, but in this -** example, the third "c" value is an inequality. So only two +** example, the third "c" value is an inequality. So only two ** constraints are coded. This routine will generate code to evaluate ** a==5 and b IN (1,2,3). The current values for a and b will be stored ** in consecutive registers and the index of the first register is returned. @@ -136830,7 +164865,7 @@ static int codeAllEqualityTerms( /* Figure out how many memory cells we will need then allocate them. */ regBase = pParse->nMem + 1; - nReg = pLoop->u.btree.nEq + nExtraReg; + nReg = nEq + nExtraReg; pParse->nMem += nReg; zAff = sqlite3DbStrDup(pParse->db,sqlite3IndexAffinityStr(pParse->db,pIdx)); @@ -136838,11 +164873,13 @@ static int codeAllEqualityTerms( if( nSkip ){ int iIdxCur = pLevel->iIdxCur; + sqlite3VdbeAddOp3(v, OP_Null, 0, regBase, regBase+nSkip-1); sqlite3VdbeAddOp1(v, (bRev?OP_Last:OP_Rewind), iIdxCur); VdbeCoverageIf(v, bRev==0); VdbeCoverageIf(v, bRev!=0); VdbeComment((v, "begin skip-scan on %s", pIdx->zName)); j = sqlite3VdbeAddOp0(v, OP_Goto); + assert( pLevel->addrSkip==0 ); pLevel->addrSkip = sqlite3VdbeAddOp4Int(v, (bRev?OP_SeekLT:OP_SeekGT), iIdxCur, 0, regBase, nSkip); VdbeCoverageIf(v, bRev==0); @@ -136853,7 +164890,7 @@ static int codeAllEqualityTerms( testcase( pIdx->aiColumn[j]==XN_EXPR ); VdbeComment((v, "%s", explainIndexColumnName(pIdx, j))); } - } + } /* Evaluate the equality constraints */ @@ -136862,7 +164899,7 @@ static int codeAllEqualityTerms( int r1; pTerm = pLoop->aLTerm[j]; assert( pTerm!=0 ); - /* The following testcase is true for indices with redundant columns. + /* The following testcase is true for indices with redundant columns. ** Ex: CREATE INDEX i1 ON t1(a,b,a); SELECT * FROM t1 WHERE a=0 AND b=0; */ testcase( (pTerm->wtFlags & TERM_CODED)!=0 ); testcase( pTerm->wtFlags & TERM_VIRTUAL ); @@ -136872,14 +164909,14 @@ static int codeAllEqualityTerms( sqlite3ReleaseTempReg(pParse, regBase); regBase = r1; }else{ - sqlite3VdbeAddOp2(v, OP_SCopy, r1, regBase+j); + sqlite3VdbeAddOp2(v, OP_Copy, r1, regBase+j); } } if( pTerm->eOperator & WO_IN ){ if( pTerm->pExpr->flags & EP_xIsSelect ){ /* No affinity ever needs to be (or should be) applied to a value - ** from the RHS of an "? IN (SELECT ...)" expression. The - ** sqlite3FindInIndex() routine has already ensured that the + ** from the RHS of an "? IN (SELECT ...)" expression. The + ** sqlite3FindInIndex() routine has already ensured that the ** affinity of the comparison has been applied to the value. */ if( zAff ) zAff[j] = SQLITE_AFF_BLOB; } @@ -136889,7 +164926,8 @@ static int codeAllEqualityTerms( sqlite3VdbeAddOp2(v, OP_IsNull, regBase+j, pLevel->addrBrk); VdbeCoverage(v); } - if( zAff ){ + if( pParse->nErr==0 ){ + assert( pParse->db->mallocFailed==0 ); if( sqlite3CompareAffinity(pRight, zAff[j])==SQLITE_AFF_BLOB ){ zAff[j] = SQLITE_AFF_BLOB; } @@ -136906,7 +164944,7 @@ static int codeAllEqualityTerms( #ifndef SQLITE_LIKE_DOESNT_MATCH_BLOBS /* ** If the most recently coded instruction is a constant range constraint -** (a string literal) that originated from the LIKE optimization, then +** (a string literal) that originated from the LIKE optimization, then ** set P3 and P5 on the OP_String opcode so that the string will be cast ** to a BLOB at appropriate times. ** @@ -136929,9 +164967,9 @@ static void whereLikeOptimizationStringFixup( if( pTerm->wtFlags & TERM_LIKEOPT ){ VdbeOp *pOp; assert( pLevel->iLikeRepCntr>0 ); - pOp = sqlite3VdbeGetOp(v, -1); + pOp = sqlite3VdbeGetLastOp(v); assert( pOp!=0 ); - assert( pOp->opcode==OP_String8 + assert( pOp->opcode==OP_String8 || pTerm->pWC->pWInfo->pParse->db->mallocFailed ); pOp->p3 = (int)(pLevel->iLikeRepCntr>>1); /* Register holding counter */ pOp->p5 = (u8)(pLevel->iLikeRepCntr&1); /* ASC or DESC */ @@ -136964,7 +165002,7 @@ static int codeCursorHintCheckExpr(Walker *pWalker, Expr *pExpr){ assert( pHint->pIdx!=0 ); if( pExpr->op==TK_COLUMN && pExpr->iTable==pHint->iTabCur - && sqlite3ColumnOfIndex(pHint->pIdx, pExpr->iColumn)<0 + && sqlite3TableColumnToIndex(pHint->pIdx, pExpr->iColumn)<0 ){ pWalker->eCode = 1; } @@ -136974,7 +165012,7 @@ static int codeCursorHintCheckExpr(Walker *pWalker, Expr *pExpr){ /* ** Test whether or not expression pExpr, which was part of a WHERE clause, ** should be included in the cursor-hint for a table that is on the rhs -** of a LEFT JOIN. Set Walker.eCode to non-zero before returning if the +** of a LEFT JOIN. Set Walker.eCode to non-zero before returning if the ** expression is not suitable. ** ** An expression is unsuitable if it might evaluate to non NULL even if @@ -136987,9 +165025,9 @@ static int codeCursorHintCheckExpr(Walker *pWalker, Expr *pExpr){ ** CASE WHEN col THEN 0 ELSE 1 END */ static int codeCursorHintIsOrFunction(Walker *pWalker, Expr *pExpr){ - if( pExpr->op==TK_IS - || pExpr->op==TK_ISNULL || pExpr->op==TK_ISNOT - || pExpr->op==TK_NOTNULL || pExpr->op==TK_CASE + if( pExpr->op==TK_IS + || pExpr->op==TK_ISNULL || pExpr->op==TK_ISNOT + || pExpr->op==TK_NOTNULL || pExpr->op==TK_CASE ){ pWalker->eCode = 1; }else if( pExpr->op==TK_FUNCTION ){ @@ -137010,40 +165048,41 @@ static int codeCursorHintIsOrFunction(Walker *pWalker, Expr *pExpr){ ** that accesses any table other than the one identified by ** CCurHint.iTabCur, then do the following: ** -** 1) allocate a register and code an OP_Column instruction to read +** 1) allocate a register and code an OP_Column instruction to read ** the specified column into the new register, and ** -** 2) transform the expression node to a TK_REGISTER node that reads +** 2) transform the expression node to a TK_REGISTER node that reads ** from the newly populated register. ** -** Also, if the node is a TK_COLUMN that does access the table idenified +** Also, if the node is a TK_COLUMN that does access the table identified ** by pCCurHint.iTabCur, and an index is being used (which we will ** know because CCurHint.pIdx!=0) then transform the TK_COLUMN into ** an access of the index rather than the original table. */ static int codeCursorHintFixExpr(Walker *pWalker, Expr *pExpr){ int rc = WRC_Continue; + int reg; struct CCurHint *pHint = pWalker->u.pCCurHint; if( pExpr->op==TK_COLUMN ){ if( pExpr->iTable!=pHint->iTabCur ){ - int reg = ++pWalker->pParse->nMem; /* Register for column value */ - sqlite3ExprCode(pWalker->pParse, pExpr, reg); + reg = ++pWalker->pParse->nMem; /* Register for column value */ + reg = sqlite3ExprCodeTarget(pWalker->pParse, pExpr, reg); pExpr->op = TK_REGISTER; pExpr->iTable = reg; }else if( pHint->pIdx!=0 ){ pExpr->iTable = pHint->iIdxCur; - pExpr->iColumn = sqlite3ColumnOfIndex(pHint->pIdx, pExpr->iColumn); + pExpr->iColumn = sqlite3TableColumnToIndex(pHint->pIdx, pExpr->iColumn); assert( pExpr->iColumn>=0 ); } - }else if( pExpr->op==TK_AGG_FUNCTION ){ - /* An aggregate function in the WHERE clause of a query means this must - ** be a correlated sub-query, and expression pExpr is an aggregate from - ** the parent context. Do not walk the function arguments in this case. - ** - ** todo: It should be possible to replace this node with a TK_REGISTER - ** expression, as the result of the expression must be stored in a - ** register at this point. The same holds for TK_AGG_COLUMN nodes. */ + }else if( pExpr->pAggInfo ){ rc = WRC_Prune; + reg = ++pWalker->pParse->nMem; /* Register for column value */ + reg = sqlite3ExprCodeTarget(pWalker->pParse, pExpr, reg); + pExpr->op = TK_REGISTER; + pExpr->iTable = reg; + }else if( pExpr->op==TK_TRUEFALSE ){ + /* Do not walk disabled expressions. tag-20230504-1 */ + return WRC_Prune; } return rc; } @@ -137052,7 +165091,7 @@ static int codeCursorHintFixExpr(Walker *pWalker, Expr *pExpr){ ** Insert an OP_CursorHint instruction if it is appropriate to do so. */ static void codeCursorHint( - struct SrcList_item *pTabItem, /* FROM clause item */ + SrcItem *pTabItem, /* FROM clause item */ WhereInfo *pWInfo, /* The where clause */ WhereLevel *pLevel, /* Which loop to provide hints for */ WhereTerm *pEndRange /* Hint this end-of-scan boundary term if not NULL */ @@ -137079,23 +165118,23 @@ static void codeCursorHint( sWalker.pParse = pParse; sWalker.u.pCCurHint = &sHint; pWC = &pWInfo->sWC; - for(i=0; inTerm; i++){ + for(i=0; inBase; i++){ pTerm = &pWC->a[i]; if( pTerm->wtFlags & (TERM_VIRTUAL|TERM_CODED) ) continue; if( pTerm->prereqAll & pLevel->notReady ) continue; - /* Any terms specified as part of the ON(...) clause for any LEFT + /* Any terms specified as part of the ON(...) clause for any LEFT ** JOIN for which the current table is not the rhs are omitted - ** from the cursor-hint. + ** from the cursor-hint. ** - ** If this table is the rhs of a LEFT JOIN, "IS" or "IS NULL" terms + ** If this table is the rhs of a LEFT JOIN, "IS" or "IS NULL" terms ** that were specified as part of the WHERE clause must be excluded. ** This is to address the following: ** ** SELECT ... t1 LEFT JOIN t2 ON (t1.a=t2.b) WHERE t2.c IS NULL; ** ** Say there is a single row in t2 that matches (t1.a=t2.b), but its - ** t2.c values is not NULL. If the (t2.c IS NULL) constraint is + ** t2.c values is not NULL. If the (t2.c IS NULL) constraint is ** pushed down to the cursor, this row is filtered out, causing ** SQLite to synthesize a row of NULL values. Which does match the ** WHERE clause, and so the query returns a row. Which is incorrect. @@ -137108,8 +165147,8 @@ static void codeCursorHint( */ if( pTabItem->fg.jointype & JT_LEFT ){ Expr *pExpr = pTerm->pExpr; - if( !ExprHasProperty(pExpr, EP_FromJoin) - || pExpr->iRightJoinTable!=pTabItem->iCursor + if( !ExprHasProperty(pExpr, EP_OuterON) + || pExpr->w.iJoin!=pTabItem->iCursor ){ sWalker.eCode = 0; sWalker.xExprCallback = codeCursorHintIsOrFunction; @@ -137117,7 +165156,7 @@ static void codeCursorHint( if( sWalker.eCode ) continue; } }else{ - if( ExprHasProperty(pTerm->pExpr, EP_FromJoin) ) continue; + if( ExprHasProperty(pTerm->pExpr, EP_OuterON) ) continue; } /* All terms in pWLoop->aLTerm[] except pEndRange are used to initialize @@ -137141,12 +165180,12 @@ static void codeCursorHint( } /* If we survive all prior tests, that means this term is worth hinting */ - pExpr = sqlite3ExprAnd(db, pExpr, sqlite3ExprDup(db, pTerm->pExpr, 0)); + pExpr = sqlite3ExprAnd(pParse, pExpr, sqlite3ExprDup(db, pTerm->pExpr, 0)); } if( pExpr!=0 ){ sWalker.xExprCallback = codeCursorHintFixExpr; - sqlite3WalkExpr(&sWalker, pExpr); - sqlite3VdbeAddOp4(v, OP_CursorHint, + if( pParse->nErr==0 ) sqlite3WalkExpr(&sWalker, pExpr); + sqlite3VdbeAddOp4(v, OP_CursorHint, (sHint.pIdx ? sHint.iIdxCur : sHint.iTabCur), 0, 0, (const char*)pExpr, P4_EXPR); } @@ -137158,20 +165197,28 @@ static void codeCursorHint( /* ** Cursor iCur is open on an intkey b-tree (a table). Register iRowid contains ** a rowid value just read from cursor iIdxCur, open on index pIdx. This -** function generates code to do a deferred seek of cursor iCur to the +** function generates code to do a deferred seek of cursor iCur to the ** rowid stored in register iRowid. ** ** Normally, this is just: ** ** OP_DeferredSeek $iCur $iRowid ** +** Which causes a seek on $iCur to the row with rowid $iRowid. +** ** However, if the scan currently being coded is a branch of an OR-loop and -** the statement currently being coded is a SELECT, then P3 of OP_DeferredSeek -** is set to iIdxCur and P4 is set to point to an array of integers -** containing one entry for each column of the table cursor iCur is open -** on. For each table column, if the column is the i'th column of the -** index, then the corresponding array entry is set to (i+1). If the column -** does not appear in the index at all, the array entry is set to 0. +** the statement currently being coded is a SELECT, then additional information +** is added that might allow OP_Column to omit the seek and instead do its +** lookup on the index, thus avoiding an expensive seek operation. To +** enable this optimization, the P3 of OP_DeferredSeek is set to iIdxCur +** and P4 is set to an array of integers containing one entry for each column +** in the table. For each table column, if the column is the i'th +** column of the index, then the corresponding array entry is set to (i+1). +** If the column does not appear in the index at all, the array entry is set +** to 0. The OP_Column opcode can check this array to see if the column it +** wants is in the index and if it is, it will substitute the index cursor +** and column number and continue with those new values, rather than seeking +** the table cursor. */ static void codeDeferredSeek( WhereInfo *pWInfo, /* Where clause context */ @@ -137184,19 +165231,24 @@ static void codeDeferredSeek( assert( iIdxCur>0 ); assert( pIdx->aiColumn[pIdx->nColumn-1]==-1 ); - + + pWInfo->bDeferredSeek = 1; sqlite3VdbeAddOp3(v, OP_DeferredSeek, iIdxCur, 0, iCur); - if( (pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE) + if( (pWInfo->wctrlFlags & (WHERE_OR_SUBCLAUSE|WHERE_RIGHT_JOIN)) && DbMaskAllZero(sqlite3ParseToplevel(pParse)->writeMask) ){ int i; Table *pTab = pIdx->pTable; - int *ai = (int*)sqlite3DbMallocZero(pParse->db, sizeof(int)*(pTab->nCol+1)); + u32 *ai = (u32*)sqlite3DbMallocZero(pParse->db, sizeof(u32)*(pTab->nCol+1)); if( ai ){ ai[0] = pTab->nCol; for(i=0; inColumn-1; i++){ + int x1, x2; assert( pIdx->aiColumn[i]nCol ); - if( pIdx->aiColumn[i]>=0 ) ai[pIdx->aiColumn[i]+1] = i+1; + x1 = pIdx->aiColumn[i]; + x2 = sqlite3TableColumnToStorage(pTab, x1); + testcase( x1!=x2 ); + if( x1>=0 ) ai[x2+1] = i+1; } sqlite3VdbeChangeP4(v, -1, (char*)ai, P4_INTARRAY); } @@ -137216,7 +165268,7 @@ static void codeExprOrVector(Parse *pParse, Expr *p, int iReg, int nReg){ assert( nReg>0 ); if( p && sqlite3ExprIsVector(p) ){ #ifndef SQLITE_OMIT_SUBQUERY - if( (p->flags & EP_xIsSelect) ){ + if( ExprUseXSelect(p) ){ Vdbe *v = pParse->pVdbe; int iSelect; assert( p->op==TK_SELECT ); @@ -137226,81 +165278,20 @@ static void codeExprOrVector(Parse *pParse, Expr *p, int iReg, int nReg){ #endif { int i; - ExprList *pList = p->x.pList; + const ExprList *pList; + assert( ExprUseXList(p) ); + pList = p->x.pList; assert( nReg<=pList->nExpr ); for(i=0; ia[i].pExpr, iReg+i); } } }else{ - assert( nReg==1 ); + assert( nReg==1 || pParse->nErr ); sqlite3ExprCode(pParse, p, iReg); } } -/* An instance of the IdxExprTrans object carries information about a -** mapping from an expression on table columns into a column in an index -** down through the Walker. -*/ -typedef struct IdxExprTrans { - Expr *pIdxExpr; /* The index expression */ - int iTabCur; /* The cursor of the corresponding table */ - int iIdxCur; /* The cursor for the index */ - int iIdxCol; /* The column for the index */ -} IdxExprTrans; - -/* The walker node callback used to transform matching expressions into -** a reference to an index column for an index on an expression. -** -** If pExpr matches, then transform it into a reference to the index column -** that contains the value of pExpr. -*/ -static int whereIndexExprTransNode(Walker *p, Expr *pExpr){ - IdxExprTrans *pX = p->u.pIdxTrans; - if( sqlite3ExprCompare(0, pExpr, pX->pIdxExpr, pX->iTabCur)==0 ){ - pExpr->op = TK_COLUMN; - pExpr->iTable = pX->iIdxCur; - pExpr->iColumn = pX->iIdxCol; - pExpr->y.pTab = 0; - return WRC_Prune; - }else{ - return WRC_Continue; - } -} - -/* -** For an indexes on expression X, locate every instance of expression X -** in pExpr and change that subexpression into a reference to the appropriate -** column of the index. -*/ -static void whereIndexExprTrans( - Index *pIdx, /* The Index */ - int iTabCur, /* Cursor of the table that is being indexed */ - int iIdxCur, /* Cursor of the index itself */ - WhereInfo *pWInfo /* Transform expressions in this WHERE clause */ -){ - int iIdxCol; /* Column number of the index */ - ExprList *aColExpr; /* Expressions that are indexed */ - Walker w; - IdxExprTrans x; - aColExpr = pIdx->aColExpr; - if( aColExpr==0 ) return; /* Not an index on expressions */ - memset(&w, 0, sizeof(w)); - w.xExprCallback = whereIndexExprTransNode; - w.u.pIdxTrans = &x; - x.iTabCur = iTabCur; - x.iIdxCur = iIdxCur; - for(iIdxCol=0; iIdxColnExpr; iIdxCol++){ - if( pIdx->aiColumn[iIdxCol]!=XN_EXPR ) continue; - assert( aColExpr->a[iIdxCol].pExpr!=0 ); - x.iIdxCol = iIdxCol; - x.pIdxExpr = aColExpr->a[iIdxCol].pExpr; - sqlite3WalkExpr(&w, pWInfo->pWhere); - sqlite3WalkExprList(&w, pWInfo->pOrderBy); - sqlite3WalkExprList(&w, pWInfo->pResultSet); - } -} - /* ** The pTruth expression is always true because it is the WHERE clause ** a partial index that is driving a query loop. Look through all of the @@ -137329,6 +165320,92 @@ static void whereApplyPartialIndexConstraints( } } +/* +** This routine is called right after An OP_Filter has been generated and +** before the corresponding index search has been performed. This routine +** checks to see if there are additional Bloom filters in inner loops that +** can be checked prior to doing the index lookup. If there are available +** inner-loop Bloom filters, then evaluate those filters now, before the +** index lookup. The idea is that a Bloom filter check is way faster than +** an index lookup, and the Bloom filter might return false, meaning that +** the index lookup can be skipped. +** +** We know that an inner loop uses a Bloom filter because it has the +** WhereLevel.regFilter set. If an inner-loop Bloom filter is checked, +** then clear the WhereLevel.regFilter value to prevent the Bloom filter +** from being checked a second time when the inner loop is evaluated. +*/ +static SQLITE_NOINLINE void filterPullDown( + Parse *pParse, /* Parsing context */ + WhereInfo *pWInfo, /* Complete information about the WHERE clause */ + int iLevel, /* Which level of pWInfo->a[] should be coded */ + int addrNxt, /* Jump here to bypass inner loops */ + Bitmask notReady /* Loops that are not ready */ +){ + int saved_addrBrk; + while( ++iLevel < pWInfo->nLevel ){ + WhereLevel *pLevel = &pWInfo->a[iLevel]; + WhereLoop *pLoop = pLevel->pWLoop; + if( pLevel->regFilter==0 ) continue; + if( pLevel->pWLoop->nSkip ) continue; + /* ,--- Because sqlite3ConstructBloomFilter() has will not have set + ** vvvvv--' pLevel->regFilter if this were true. */ + if( NEVER(pLoop->prereq & notReady) ) continue; + saved_addrBrk = pLevel->addrBrk; + pLevel->addrBrk = addrNxt; + if( pLoop->wsFlags & WHERE_IPK ){ + WhereTerm *pTerm = pLoop->aLTerm[0]; + int regRowid; + assert( pTerm!=0 ); + assert( pTerm->pExpr!=0 ); + testcase( pTerm->wtFlags & TERM_VIRTUAL ); + regRowid = sqlite3GetTempReg(pParse); + regRowid = codeEqualityTerm(pParse, pTerm, pLevel, 0, 0, regRowid); + sqlite3VdbeAddOp2(pParse->pVdbe, OP_MustBeInt, regRowid, addrNxt); + VdbeCoverage(pParse->pVdbe); + sqlite3VdbeAddOp4Int(pParse->pVdbe, OP_Filter, pLevel->regFilter, + addrNxt, regRowid, 1); + VdbeCoverage(pParse->pVdbe); + }else{ + u16 nEq = pLoop->u.btree.nEq; + int r1; + char *zStartAff; + + assert( pLoop->wsFlags & WHERE_INDEXED ); + assert( (pLoop->wsFlags & WHERE_COLUMN_IN)==0 ); + r1 = codeAllEqualityTerms(pParse,pLevel,0,0,&zStartAff); + codeApplyAffinity(pParse, r1, nEq, zStartAff); + sqlite3DbFree(pParse->db, zStartAff); + sqlite3VdbeAddOp4Int(pParse->pVdbe, OP_Filter, pLevel->regFilter, + addrNxt, r1, nEq); + VdbeCoverage(pParse->pVdbe); + } + pLevel->regFilter = 0; + pLevel->addrBrk = saved_addrBrk; + } +} + +/* +** Loop pLoop is a WHERE_INDEXED level that uses at least one IN(...) +** operator. Return true if level pLoop is guaranteed to visit only one +** row for each key generated for the index. +*/ +static int whereLoopIsOneRow(WhereLoop *pLoop){ + if( pLoop->u.btree.pIndex->onError + && pLoop->nSkip==0 + && pLoop->u.btree.nEq==pLoop->u.btree.pIndex->nKeyCol + ){ + int ii; + for(ii=0; iiu.btree.nEq; ii++){ + if( pLoop->aLTerm[ii]->eOperator & (WO_IS|WO_ISNULL) ){ + return 0; + } + } + return 1; + } + return 0; +} + /* ** Generate code for the start of the iLevel-th loop in the WHERE clause ** implementation described by pWInfo. @@ -137349,9 +165426,8 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( WhereClause *pWC; /* Decomposition of the entire WHERE clause */ WhereTerm *pTerm; /* A WHERE clause term */ sqlite3 *db; /* Database connection */ - struct SrcList_item *pTabItem; /* FROM clause term being coded */ + SrcItem *pTabItem; /* FROM clause term being coded */ int addrBrk; /* Jump here to break out of the loop */ - int addrHalt; /* addrBrk for the outermost loop */ int addrCont; /* Jump here to continue with next cycle */ int iRowidReg = 0; /* Rowid is stored in this register, if not zero */ int iReleaseReg = 0; /* Temp register to free before returning */ @@ -137365,7 +165441,25 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( iCur = pTabItem->iCursor; pLevel->notReady = notReady & ~sqlite3WhereGetMask(&pWInfo->sMaskSet, iCur); bRev = (pWInfo->revMask>>iLevel)&1; - VdbeModuleComment((v, "Begin WHERE-loop%d: %s",iLevel,pTabItem->pTab->zName)); + VdbeModuleComment((v, "Begin WHERE-loop%d: %s", + iLevel, pTabItem->pSTab->zName)); +#if WHERETRACE_ENABLED /* 0x4001 */ + if( sqlite3WhereTrace & 0x1 ){ + sqlite3DebugPrintf("Coding level %d of %d: notReady=%llx iFrom=%d\n", + iLevel, pWInfo->nLevel, (u64)notReady, pLevel->iFrom); + if( sqlite3WhereTrace & 0x1000 ){ + sqlite3WhereLoopPrint(pLoop, pWC); + } + } + if( (sqlite3WhereTrace & 0x4001)==0x4001 ){ + if( iLevel==0 ){ + sqlite3DebugPrintf("WHERE clause being coded:\n"); + sqlite3TreeViewExpr(0, pWInfo->pWhere, 0); + } + sqlite3DebugPrintf("All WHERE-clause terms before coding:\n"); + sqlite3WhereClausePrint(pWC); + } +#endif /* Create labels for the "break" and "continue" instructions ** for the current loop. Jump to addrBrk to break out of a loop. @@ -137377,34 +165471,33 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( ** there are no IN operators in the constraints, the "addrNxt" label ** is the same as "addrBrk". */ - addrBrk = pLevel->addrBrk = pLevel->addrNxt = sqlite3VdbeMakeLabel(pParse); + addrBrk = pLevel->addrNxt = pLevel->addrBrk; addrCont = pLevel->addrCont = sqlite3VdbeMakeLabel(pParse); /* If this is the right table of a LEFT OUTER JOIN, allocate and ** initialize a memory cell that records if this table matches any ** row of the left table of the join. */ - assert( (pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE) + assert( (pWInfo->wctrlFlags & (WHERE_OR_SUBCLAUSE|WHERE_RIGHT_JOIN)) || pLevel->iFrom>0 || (pTabItem[0].fg.jointype & JT_LEFT)==0 ); if( pLevel->iFrom>0 && (pTabItem[0].fg.jointype & JT_LEFT)!=0 ){ pLevel->iLeftJoin = ++pParse->nMem; sqlite3VdbeAddOp2(v, OP_Integer, 0, pLevel->iLeftJoin); - VdbeComment((v, "init LEFT JOIN no-match flag")); + VdbeComment((v, "init LEFT JOIN match flag")); } - /* Compute a safe address to jump to if we discover that the table for - ** this loop is empty and can never contribute content. */ - for(j=iLevel; j>0 && pWInfo->a[j].iLeftJoin==0; j--){} - addrHalt = pWInfo->a[j].addrBrk; - /* Special case of a FROM clause subquery implemented as a co-routine */ if( pTabItem->fg.viaCoroutine ){ - int regYield = pTabItem->regReturn; - sqlite3VdbeAddOp3(v, OP_InitCoroutine, regYield, 0, pTabItem->addrFillSub); + int regYield; + Subquery *pSubq; + assert( pTabItem->fg.isSubquery && pTabItem->u4.pSubq!=0 ); + pSubq = pTabItem->u4.pSubq; + regYield = pSubq->regReturn; + sqlite3VdbeAddOp3(v, OP_InitCoroutine, regYield, 0, pSubq->addrFillSub); pLevel->p2 = sqlite3VdbeAddOp2(v, OP_Yield, regYield, addrBrk); VdbeCoverage(v); - VdbeComment((v, "next row of %s", pTabItem->pTab->zName)); + VdbeComment((v, "next row of %s", pTabItem->pSTab->zName)); pLevel->op = OP_Goto; }else @@ -137416,7 +165509,6 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( int iReg; /* P3 Value for OP_VFilter */ int addrNotFound; int nConstraint = pLoop->nLTerm; - int iIn; /* Counter for IN constraints */ iReg = sqlite3GetTempRange(pParse, nConstraint+2); addrNotFound = pLevel->addrBrk; @@ -137425,64 +165517,107 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( pTerm = pLoop->aLTerm[j]; if( NEVER(pTerm==0) ) continue; if( pTerm->eOperator & WO_IN ){ - codeEqualityTerm(pParse, pTerm, pLevel, j, bRev, iTarget); - addrNotFound = pLevel->addrNxt; + if( SMASKBIT32(j) & pLoop->u.vtab.mHandleIn ){ + int iTab = pParse->nTab++; + int iCache = ++pParse->nMem; + sqlite3CodeRhsOfIN(pParse, pTerm->pExpr, iTab, 0); + sqlite3VdbeAddOp3(v, OP_VInitIn, iTab, iTarget, iCache); + }else{ + codeEqualityTerm(pParse, pTerm, pLevel, j, bRev, iTarget); + addrNotFound = pLevel->addrNxt; + } }else{ Expr *pRight = pTerm->pExpr->pRight; codeExprOrVector(pParse, pRight, iTarget, 1); + if( pTerm->eMatchOp==SQLITE_INDEX_CONSTRAINT_OFFSET + && pLoop->u.vtab.bOmitOffset + ){ + assert( pTerm->eOperator==WO_AUX ); + assert( pWInfo->pSelect!=0 ); + assert( pWInfo->pSelect->iOffset>0 ); + sqlite3VdbeAddOp2(v, OP_Integer, 0, pWInfo->pSelect->iOffset); + VdbeComment((v,"Zero OFFSET counter")); + } } } sqlite3VdbeAddOp2(v, OP_Integer, pLoop->u.vtab.idxNum, iReg); sqlite3VdbeAddOp2(v, OP_Integer, nConstraint, iReg+1); + /* The instruction immediately prior to OP_VFilter must be an OP_Integer + ** that sets the "argc" value for xVFilter. This is necessary for + ** resolveP2() to work correctly. See tag-20250207a. */ sqlite3VdbeAddOp4(v, OP_VFilter, iCur, addrNotFound, iReg, pLoop->u.vtab.idxStr, pLoop->u.vtab.needFree ? P4_DYNAMIC : P4_STATIC); VdbeCoverage(v); pLoop->u.vtab.needFree = 0; + /* An OOM inside of AddOp4(OP_VFilter) instruction above might have freed + ** the u.vtab.idxStr. NULL it out to prevent a use-after-free */ + if( db->mallocFailed ) pLoop->u.vtab.idxStr = 0; pLevel->p1 = iCur; pLevel->op = pWInfo->eOnePass ? OP_Noop : OP_VNext; pLevel->p2 = sqlite3VdbeCurrentAddr(v); - iIn = pLevel->u.in.nIn; - for(j=nConstraint-1; j>=0; j--){ + assert( (pLoop->wsFlags & WHERE_MULTI_OR)==0 ); + + for(j=0; jaLTerm[j]; if( j<16 && (pLoop->u.vtab.omitMask>>j)&1 ){ disableTerm(pLevel, pTerm); - }else if( (pTerm->eOperator & WO_IN)!=0 ){ + continue; + } + if( (pTerm->eOperator & WO_IN)!=0 + && (SMASKBIT32(j) & pLoop->u.vtab.mHandleIn)==0 + && !db->mallocFailed + ){ Expr *pCompare; /* The comparison operator */ Expr *pRight; /* RHS of the comparison */ VdbeOp *pOp; /* Opcode to access the value of the IN constraint */ + int iIn; /* IN loop corresponding to the j-th constraint */ /* Reload the constraint value into reg[iReg+j+2]. The same value ** was loaded into the same register prior to the OP_VFilter, but ** the xFilter implementation might have changed the datatype or - ** encoding of the value in the register, so it *must* be reloaded. */ - assert( pLevel->u.in.aInLoop!=0 || db->mallocFailed ); - if( !db->mallocFailed ){ - assert( iIn>0 ); - pOp = sqlite3VdbeGetOp(v, pLevel->u.in.aInLoop[--iIn].addrInTop); - assert( pOp->opcode==OP_Column || pOp->opcode==OP_Rowid ); - assert( pOp->opcode!=OP_Column || pOp->p3==iReg+j+2 ); - assert( pOp->opcode!=OP_Rowid || pOp->p2==iReg+j+2 ); - testcase( pOp->opcode==OP_Rowid ); - sqlite3VdbeAddOp3(v, pOp->opcode, pOp->p1, pOp->p2, pOp->p3); + ** encoding of the value in the register, so it *must* be reloaded. + */ + for(iIn=0; ALWAYS(iInu.in.nIn); iIn++){ + pOp = sqlite3VdbeGetOp(v, pLevel->u.in.aInLoop[iIn].addrInTop); + if( (pOp->opcode==OP_Column && pOp->p3==iReg+j+2) + || (pOp->opcode==OP_Rowid && pOp->p2==iReg+j+2) + ){ + testcase( pOp->opcode==OP_Rowid ); + sqlite3VdbeAddOp3(v, pOp->opcode, pOp->p1, pOp->p2, pOp->p3); + break; + } } - /* Generate code that will continue to the next row if - ** the IN constraint is not satisfied */ + /* Generate code that will continue to the next row if + ** the IN constraint is not satisfied + */ pCompare = sqlite3PExpr(pParse, TK_EQ, 0, 0); - assert( pCompare!=0 || db->mallocFailed ); - if( pCompare ){ - pCompare->pLeft = pTerm->pExpr->pLeft; + if( !db->mallocFailed ){ + int iFld = pTerm->u.x.iField; + Expr *pLeft = pTerm->pExpr->pLeft; + assert( pLeft!=0 ); + if( iFld>0 ){ + assert( pLeft->op==TK_VECTOR ); + assert( ExprUseXList(pLeft) ); + assert( iFld<=pLeft->x.pList->nExpr ); + pCompare->pLeft = pLeft->x.pList->a[iFld-1].pExpr; + }else{ + pCompare->pLeft = pLeft; + } pCompare->pRight = pRight = sqlite3Expr(db, TK_REGISTER, 0); if( pRight ){ pRight->iTable = iReg+j+2; - sqlite3ExprIfFalse(pParse, pCompare, pLevel->addrCont, 0); + sqlite3ExprIfFalse( + pParse, pCompare, pLevel->addrCont, SQLITE_JUMPIFNULL + ); } pCompare->pLeft = 0; - sqlite3ExprDelete(db, pCompare); } + sqlite3ExprDelete(db, pCompare); } } + /* These registers need to be preserved in case there is an IN operator ** loop. So we could deallocate the registers here (and potentially ** reuse them later) if (pLoop->wsFlags & WHERE_IN_ABLE)==0. But it seems @@ -137510,12 +165645,17 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( iRowidReg = codeEqualityTerm(pParse, pTerm, pLevel, 0, bRev, iReleaseReg); if( iRowidReg!=iReleaseReg ) sqlite3ReleaseTempReg(pParse, iReleaseReg); addrNxt = pLevel->addrNxt; + if( pLevel->regFilter ){ + sqlite3VdbeAddOp2(v, OP_MustBeInt, iRowidReg, addrNxt); + VdbeCoverage(v); + sqlite3VdbeAddOp4Int(v, OP_Filter, pLevel->regFilter, addrNxt, + iRowidReg, 1); + VdbeCoverage(v); + filterPullDown(pParse, pWInfo, iLevel, addrNxt, notReady); + } sqlite3VdbeAddOp3(v, OP_SeekRowid, iCur, addrNxt, iRowidReg); VdbeCoverage(v); pLevel->op = OP_Noop; - if( (pTerm->prereqAll & pLevel->notReady)==0 ){ - pTerm->wtFlags |= TERM_CODED; - } }else if( (pLoop->wsFlags & WHERE_IPK)!=0 && (pLoop->wsFlags & WHERE_COLUMN_RANGE)!=0 ){ @@ -137542,7 +165682,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( int r1, rTemp; /* Registers for holding the start boundary */ int op; /* Cursor seek operation */ - /* The following constant maps TK_xx codes into corresponding + /* The following constant maps TK_xx codes into corresponding ** seek opcodes. It depends on a particular ordering of TK_xx */ const u8 aMoveOp[] = { @@ -137553,7 +165693,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( }; assert( TK_LE==TK_GT+1 ); /* Make sure the ordering.. */ assert( TK_LT==TK_GT+2 ); /* ... of the TK_xx values... */ - assert( TK_GE==TK_GT+3 ); /* ... is correcct. */ + assert( TK_GE==TK_GT+3 ); /* ... is correct. */ assert( (pStart->wtFlags & TERM_VNULL)==0 ); testcase( pStart->wtFlags & TERM_VIRTUAL ); @@ -137585,7 +165725,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( VdbeCoverageIf(v, pX->op==TK_GE); sqlite3ReleaseTempReg(pParse, rTemp); }else{ - sqlite3VdbeAddOp2(v, bRev ? OP_Last : OP_Rewind, iCur, addrHalt); + sqlite3VdbeAddOp2(v, bRev ? OP_Last : OP_Rewind, iCur, pLevel->addrHalt); VdbeCoverageIf(v, bRev==0); VdbeCoverageIf(v, bRev!=0); } @@ -137598,8 +165738,8 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( testcase( pEnd->wtFlags & TERM_VIRTUAL ); memEndValue = ++pParse->nMem; codeExprOrVector(pParse, pX->pRight, memEndValue, 1); - if( 0==sqlite3ExprIsVector(pX->pRight) - && (pX->op==TK_LT || pX->op==TK_GT) + if( 0==sqlite3ExprIsVector(pX->pRight) + && (pX->op==TK_LT || pX->op==TK_GT) ){ testOp = bRev ? OP_Le : OP_Ge; }else{ @@ -137625,37 +165765,37 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( sqlite3VdbeChangeP5(v, SQLITE_AFF_NUMERIC | SQLITE_JUMPIFNULL); } }else if( pLoop->wsFlags & WHERE_INDEXED ){ - /* Case 4: A scan using an index. + /* Case 4: Search using an index. ** - ** The WHERE clause may contain zero or more equality - ** terms ("==" or "IN" operators) that refer to the N - ** left-most columns of the index. It may also contain - ** inequality constraints (>, <, >= or <=) on the indexed - ** column that immediately follows the N equalities. Only - ** the right-most column can be an inequality - the rest must - ** use the "==" and "IN" operators. For example, if the - ** index is on (x,y,z), then the following clauses are all - ** optimized: + ** The WHERE clause may contain zero or more equality + ** terms ("==" or "IN" or "IS" operators) that refer to the N + ** left-most columns of the index. It may also contain + ** inequality constraints (>, <, >= or <=) on the indexed + ** column that immediately follows the N equalities. Only + ** the right-most column can be an inequality - the rest must + ** use the "==", "IN", or "IS" operators. For example, if the + ** index is on (x,y,z), then the following clauses are all + ** optimized: ** - ** x=5 - ** x=5 AND y=10 - ** x=5 AND y<10 - ** x=5 AND y>5 AND y<10 - ** x=5 AND y=5 AND z<=10 + ** x=5 + ** x=5 AND y=10 + ** x=5 AND y<10 + ** x=5 AND y>5 AND y<10 + ** x=5 AND y=5 AND z<=10 ** - ** The z<10 term of the following cannot be used, only - ** the x=5 term: + ** The z<10 term of the following cannot be used, only + ** the x=5 term: ** - ** x=5 AND z<10 + ** x=5 AND z<10 ** - ** N may be zero if there are inequality constraints. - ** If there are no inequality constraints, then N is at - ** least one. + ** N may be zero if there are inequality constraints. + ** If there are no inequality constraints, then N is at + ** least one. ** - ** This case is also used when there are no WHERE clause - ** constraints but an index is selected anyway, in order - ** to force the output order to conform to an ORDER BY. - */ + ** This case is also used when there are no WHERE clause + ** constraints but an index is selected anyway, in order + ** to force the output order to conform to an ORDER BY. + */ static const u8 aStartOp[] = { 0, 0, @@ -137690,41 +165830,22 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( u8 bSeekPastNull = 0; /* True to seek past initial nulls */ u8 bStopAtNull = 0; /* Add condition to terminate at NULLs */ int omitTable; /* True if we use the index only */ - + int regBignull = 0; /* big-null flag register */ + int addrSeekScan = 0; /* Opcode of the OP_SeekScan, if any */ pIdx = pLoop->u.btree.pIndex; iIdxCur = pLevel->iIdxCur; assert( nEq>=pLoop->nSkip ); - /* If this loop satisfies a sort order (pOrderBy) request that - ** was passed to this function to implement a "SELECT min(x) ..." - ** query, then the caller will only allow the loop to run for - ** a single iteration. This means that the first row returned - ** should not have a NULL value stored in 'x'. If column 'x' is - ** the first one after the nEq equality constraints in the index, - ** this requires some special handling. - */ - assert( pWInfo->pOrderBy==0 - || pWInfo->pOrderBy->nExpr==1 - || (pWInfo->wctrlFlags&WHERE_ORDERBY_MIN)==0 ); - if( (pWInfo->wctrlFlags&WHERE_ORDERBY_MIN)!=0 - && pWInfo->nOBSat>0 - && (pIdx->nKeyCol>nEq) - ){ - assert( pLoop->nSkip==0 ); - bSeekPastNull = 1; - nExtraReg = 1; - } - - /* Find any inequality constraint terms for the start and end - ** of the range. + /* Find any inequality constraint terms for the start and end + ** of the range. */ j = nEq; if( pLoop->wsFlags & WHERE_BTM_LIMIT ){ pRangeStart = pLoop->aLTerm[j++]; nExtraReg = MAX(nExtraReg, pLoop->u.btree.nBtm); /* Like optimization range constraints always occur in pairs */ - assert( (pRangeStart->wtFlags & TERM_LIKEOPT)==0 || + assert( (pRangeStart->wtFlags & TERM_LIKEOPT)==0 || (pLoop->wsFlags & WHERE_TOP_LIMIT)!=0 ); } if( pLoop->wsFlags & WHERE_TOP_LIMIT ){ @@ -137756,18 +165877,44 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( } assert( pRangeEnd==0 || (pRangeEnd->wtFlags & TERM_VNULL)==0 ); + /* If the WHERE_BIGNULL_SORT flag is set, then index column nEq uses + ** a non-default "big-null" sort (either ASC NULLS LAST or DESC NULLS + ** FIRST). In both cases separate ordered scans are made of those + ** index entries for which the column is null and for those for which + ** it is not. For an ASC sort, the non-NULL entries are scanned first. + ** For DESC, NULL entries are scanned first. + */ + if( (pLoop->wsFlags & (WHERE_TOP_LIMIT|WHERE_BTM_LIMIT))==0 + && (pLoop->wsFlags & WHERE_BIGNULL_SORT)!=0 + ){ + assert( bSeekPastNull==0 && nExtraReg==0 && nBtm==0 && nTop==0 ); + assert( pRangeEnd==0 && pRangeStart==0 ); + testcase( pLoop->nSkip>0 ); + nExtraReg = 1; + bSeekPastNull = 1; + pLevel->regBignull = regBignull = ++pParse->nMem; + if( pLevel->iLeftJoin ){ + sqlite3VdbeAddOp2(v, OP_Integer, 0, regBignull); + } + pLevel->addrBignull = sqlite3VdbeMakeLabel(pParse); + } + /* If we are doing a reverse order scan on an ascending index, or - ** a forward order scan on a descending index, interchange the + ** a forward order scan on a descending index, interchange the ** start and end terms (pRangeStart and pRangeEnd). */ - if( (nEqnKeyCol && bRev==(pIdx->aSortOrder[nEq]==SQLITE_SO_ASC)) - || (bRev && pIdx->nKeyCol==nEq) - ){ + if( (nEqnColumn && bRev==(pIdx->aSortOrder[nEq]==SQLITE_SO_ASC)) ){ SWAP(WhereTerm *, pRangeEnd, pRangeStart); SWAP(u8, bSeekPastNull, bStopAtNull); SWAP(u8, nBtm, nTop); } + if( iLevel>0 && (pLoop->wsFlags & WHERE_IN_SEEKSCAN)!=0 ){ + /* In case OP_SeekScan is used, ensure that the index cursor does not + ** point to a valid row for the first iteration of this loop. */ + sqlite3VdbeAddOp1(v, OP_NullRow, iIdxCur); + } + /* Generate code to evaluate all constraint terms using == or IN ** and store the values of those terms in an array of registers ** starting at regBase. @@ -137778,7 +165925,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( if( zStartAff && nTop ){ zEndAff = sqlite3DbStrDup(db, &zStartAff[nEq]); } - addrNxt = pLevel->addrNxt; + addrNxt = (regBignull ? pLevel->addrBignull : pLevel->addrNxt); testcase( pRangeStart && (pRangeStart->eOperator & WO_LE)!=0 ); testcase( pRangeStart && (pRangeStart->eOperator & WO_GE)!=0 ); @@ -137802,7 +165949,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( } if( zStartAff ){ updateRangeAffinityStr(pRight, nBtm, &zStartAff[nEq]); - } + } nConstraint += nBtm; testcase( pRangeStart->wtFlags & TERM_VIRTUAL ); if( sqlite3ExprIsVector(pRight)==0 ){ @@ -137812,10 +165959,14 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( } bSeekPastNull = 0; }else if( bSeekPastNull ){ + startEq = 0; sqlite3VdbeAddOp2(v, OP_Null, 0, regBase+nEq); + start_constraints = 1; nConstraint++; - startEq = 0; + }else if( regBignull ){ + sqlite3VdbeAddOp2(v, OP_Null, 0, regBase+nEq); start_constraints = 1; + nConstraint++; } codeApplyAffinity(pParse, regBase, nConstraint - bSeekPastNull, zStartAff); if( pLoop->nSkip>0 && nConstraint==pLoop->nSkip ){ @@ -137823,11 +165974,38 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( ** above has already left the cursor sitting on the correct row, ** so no further seeking is needed */ }else{ - if( pLoop->wsFlags & WHERE_IN_EARLYOUT ){ - sqlite3VdbeAddOp1(v, OP_SeekHit, iIdxCur); + if( regBignull ){ + sqlite3VdbeAddOp2(v, OP_Integer, 1, regBignull); + VdbeComment((v, "NULL-scan pass ctr")); } + if( pLevel->regFilter ){ + sqlite3VdbeAddOp4Int(v, OP_Filter, pLevel->regFilter, addrNxt, + regBase, nEq); + VdbeCoverage(v); + filterPullDown(pParse, pWInfo, iLevel, addrNxt, notReady); + } + op = aStartOp[(start_constraints<<2) + (startEq<<1) + bRev]; assert( op!=0 ); + if( (pLoop->wsFlags & WHERE_IN_SEEKSCAN)!=0 && op==OP_SeekGE ){ + assert( regBignull==0 ); + /* TUNING: The OP_SeekScan opcode seeks to reduce the number + ** of expensive seek operations by replacing a single seek with + ** 1 or more step operations. The question is, how many steps + ** should we try before giving up and going with a seek. The cost + ** of a seek is proportional to the logarithm of the of the number + ** of entries in the tree, so basing the number of steps to try + ** on the estimated number of rows in the btree seems like a good + ** guess. */ + addrSeekScan = sqlite3VdbeAddOp1(v, OP_SeekScan, + (pIdx->aiRowLogEst[0]+9)/10); + if( pRangeStart || pRangeEnd ){ + sqlite3VdbeChangeP5(v, 1); + sqlite3VdbeChangeP2(v, addrSeekScan, sqlite3VdbeCurrentAddr(v)+1); + addrSeekScan = 0; + } + VdbeCoverage(v); + } sqlite3VdbeAddOp4Int(v, op, iIdxCur, addrNxt, regBase, nConstraint); VdbeCoverage(v); VdbeCoverageIf(v, op==OP_Rewind); testcase( op==OP_Rewind ); @@ -137836,14 +166014,33 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( VdbeCoverageIf(v, op==OP_SeekGE); testcase( op==OP_SeekGE ); VdbeCoverageIf(v, op==OP_SeekLE); testcase( op==OP_SeekLE ); VdbeCoverageIf(v, op==OP_SeekLT); testcase( op==OP_SeekLT ); + + assert( bSeekPastNull==0 || bStopAtNull==0 ); + if( regBignull ){ + assert( bSeekPastNull==1 || bStopAtNull==1 ); + assert( bSeekPastNull==!bStopAtNull ); + assert( bStopAtNull==startEq ); + sqlite3VdbeAddOp2(v, OP_Goto, 0, sqlite3VdbeCurrentAddr(v)+2); + op = aStartOp[(nConstraint>1)*4 + 2 + bRev]; + sqlite3VdbeAddOp4Int(v, op, iIdxCur, addrNxt, regBase, + nConstraint-startEq); + VdbeCoverage(v); + VdbeCoverageIf(v, op==OP_Rewind); testcase( op==OP_Rewind ); + VdbeCoverageIf(v, op==OP_Last); testcase( op==OP_Last ); + VdbeCoverageIf(v, op==OP_SeekGE); testcase( op==OP_SeekGE ); + VdbeCoverageIf(v, op==OP_SeekLE); testcase( op==OP_SeekLE ); + assert( op==OP_Rewind || op==OP_Last || op==OP_SeekGE || op==OP_SeekLE); + } } /* Load the value for the inequality constraint at the end of the ** range (if any). */ nConstraint = nEq; + assert( pLevel->p2==0 ); if( pRangeEnd ){ Expr *pRight = pRangeEnd->pExpr->pRight; + assert( addrSeekScan==0 ); codeExprOrVector(pParse, pRight, regBase+nEq, nTop); whereLikeOptimizationStringFixup(v, pLevel, pRangeEnd); if( (pRangeEnd->wtFlags & TERM_VNULL)==0 @@ -137867,87 +166064,98 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( endEq = 1; } }else if( bStopAtNull ){ - sqlite3VdbeAddOp2(v, OP_Null, 0, regBase+nEq); - endEq = 0; + if( regBignull==0 ){ + sqlite3VdbeAddOp2(v, OP_Null, 0, regBase+nEq); + endEq = 0; + } nConstraint++; } - sqlite3DbFree(db, zStartAff); - sqlite3DbFree(db, zEndAff); + if( zStartAff ) sqlite3DbNNFreeNN(db, zStartAff); + if( zEndAff ) sqlite3DbNNFreeNN(db, zEndAff); /* Top of the loop body */ pLevel->p2 = sqlite3VdbeCurrentAddr(v); /* Check if the index cursor is past the end of the range. */ if( nConstraint ){ + if( regBignull ){ + /* Except, skip the end-of-range check while doing the NULL-scan */ + sqlite3VdbeAddOp2(v, OP_IfNot, regBignull, sqlite3VdbeCurrentAddr(v)+3); + VdbeComment((v, "If NULL-scan 2nd pass")); + VdbeCoverage(v); + } op = aEndOp[bRev*2 + endEq]; sqlite3VdbeAddOp4Int(v, op, iIdxCur, addrNxt, regBase, nConstraint); testcase( op==OP_IdxGT ); VdbeCoverageIf(v, op==OP_IdxGT ); testcase( op==OP_IdxGE ); VdbeCoverageIf(v, op==OP_IdxGE ); testcase( op==OP_IdxLT ); VdbeCoverageIf(v, op==OP_IdxLT ); testcase( op==OP_IdxLE ); VdbeCoverageIf(v, op==OP_IdxLE ); + if( addrSeekScan ) sqlite3VdbeJumpHere(v, addrSeekScan); + } + if( regBignull ){ + /* During a NULL-scan, check to see if we have reached the end of + ** the NULLs */ + assert( bSeekPastNull==!bStopAtNull ); + assert( bSeekPastNull+bStopAtNull==1 ); + assert( nConstraint+bSeekPastNull>0 ); + sqlite3VdbeAddOp2(v, OP_If, regBignull, sqlite3VdbeCurrentAddr(v)+2); + VdbeComment((v, "If NULL-scan 1st pass")); + VdbeCoverage(v); + op = aEndOp[bRev*2 + bSeekPastNull]; + sqlite3VdbeAddOp4Int(v, op, iIdxCur, addrNxt, regBase, + nConstraint+bSeekPastNull); + testcase( op==OP_IdxGT ); VdbeCoverageIf(v, op==OP_IdxGT ); + testcase( op==OP_IdxGE ); VdbeCoverageIf(v, op==OP_IdxGE ); + testcase( op==OP_IdxLT ); VdbeCoverageIf(v, op==OP_IdxLT ); + testcase( op==OP_IdxLE ); VdbeCoverageIf(v, op==OP_IdxLE ); } - if( pLoop->wsFlags & WHERE_IN_EARLYOUT ){ - sqlite3VdbeAddOp2(v, OP_SeekHit, iIdxCur, 1); + if( (pLoop->wsFlags & WHERE_IN_EARLYOUT)!=0 ){ + sqlite3VdbeAddOp3(v, OP_SeekHit, iIdxCur, nEq, nEq); } /* Seek the table cursor, if required */ - omitTable = (pLoop->wsFlags & WHERE_IDX_ONLY)!=0 - && (pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE)==0; + omitTable = (pLoop->wsFlags & WHERE_IDX_ONLY)!=0 + && (pWInfo->wctrlFlags & (WHERE_OR_SUBCLAUSE|WHERE_RIGHT_JOIN))==0; if( omitTable ){ /* pIdx is a covering index. No need to access the main table. */ }else if( HasRowid(pIdx->pTable) ){ - if( (pWInfo->wctrlFlags & WHERE_SEEK_TABLE) || ( - (pWInfo->wctrlFlags & WHERE_SEEK_UNIQ_TABLE) - && (pWInfo->eOnePass==ONEPASS_SINGLE) - )){ - iRowidReg = ++pParse->nMem; - sqlite3VdbeAddOp2(v, OP_IdxRowid, iIdxCur, iRowidReg); - sqlite3VdbeAddOp3(v, OP_NotExists, iCur, 0, iRowidReg); - VdbeCoverage(v); - }else{ - codeDeferredSeek(pWInfo, pIdx, iCur, iIdxCur); - } + codeDeferredSeek(pWInfo, pIdx, iCur, iIdxCur); }else if( iCur!=iIdxCur ){ Index *pPk = sqlite3PrimaryKeyIndex(pIdx->pTable); iRowidReg = sqlite3GetTempRange(pParse, pPk->nKeyCol); for(j=0; jnKeyCol; j++){ - k = sqlite3ColumnOfIndex(pIdx, pPk->aiColumn[j]); + k = sqlite3TableColumnToIndex(pIdx, pPk->aiColumn[j]); sqlite3VdbeAddOp3(v, OP_Column, iIdxCur, k, iRowidReg+j); } sqlite3VdbeAddOp4Int(v, OP_NotFound, iCur, addrCont, iRowidReg, pPk->nKeyCol); VdbeCoverage(v); } - /* If pIdx is an index on one or more expressions, then look through - ** all the expressions in pWInfo and try to transform matching expressions - ** into reference to index columns. - ** - ** Do not do this for the RHS of a LEFT JOIN. This is because the - ** expression may be evaluated after OP_NullRow has been executed on - ** the cursor. In this case it is important to do the full evaluation, - ** as the result of the expression may not be NULL, even if all table - ** column values are. https://www.sqlite.org/src/info/7fa8049685b50b5a - ** - ** Also, do not do this when processing one index an a multi-index - ** OR clause, since the transformation will become invalid once we - ** move forward to the next index. - ** https://sqlite.org/src/info/4e8e4857d32d401f - */ - if( pLevel->iLeftJoin==0 && (pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE)==0 ){ - whereIndexExprTrans(pIdx, iCur, iIdxCur, pWInfo); - } - - /* If a partial index is driving the loop, try to eliminate WHERE clause - ** terms from the query that must be true due to the WHERE clause of - ** the partial index - */ - if( pIdx->pPartIdxWhere ){ - whereApplyPartialIndexConstraints(pIdx->pPartIdxWhere, iCur, pWC); + if( pLevel->iLeftJoin==0 ){ + /* If a partial index is driving the loop, try to eliminate WHERE clause + ** terms from the query that must be true due to the WHERE clause of + ** the partial index. This optimization does not work on an outer join, + ** as shown by: + ** + ** 2019-11-02 ticket 623eff57e76d45f6 (LEFT JOIN) + ** 2025-05-29 forum post 7dee41d32506c4ae (RIGHT JOIN) + */ + if( pIdx->pPartIdxWhere && pLevel->pRJ==0 ){ + whereApplyPartialIndexConstraints(pIdx->pPartIdxWhere, iCur, pWC); + } + }else{ + testcase( pIdx->pPartIdxWhere ); + /* The following assert() is not a requirement, merely an observation: + ** The OR-optimization doesn't work for the right hand table of + ** a LEFT JOIN: */ + assert( (pWInfo->wctrlFlags & (WHERE_OR_SUBCLAUSE|WHERE_RIGHT_JOIN))==0 ); } /* Record the instruction used to terminate the loop. */ - if( pLoop->wsFlags & WHERE_ONEROW ){ + if( (pLoop->wsFlags & WHERE_ONEROW) + || (pLevel->u.in.nIn && regBignull==0 && whereLoopIsOneRow(pLoop)) + ){ pLevel->op = OP_Noop; }else if( bRev ){ pLevel->op = OP_Prev; @@ -138021,9 +166229,8 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( int iRetInit; /* Address of regReturn init */ int untestedTerms = 0; /* Some terms not completely tested */ int ii; /* Loop counter */ - u16 wctrlFlags; /* Flags for sub-WHERE clause */ Expr *pAndExpr = 0; /* An ".. AND (...)" expression */ - Table *pTab = pTabItem->pTab; + Table *pTab = pTabItem->pSTab; pTerm = pLoop->aLTerm[0]; assert( pTerm!=0 ); @@ -138037,12 +166244,11 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( ** by this loop in the a[0] slot and all notReady tables in a[1..] slots. ** This becomes the SrcList in the recursive call to sqlite3WhereBegin(). */ - if( pWInfo->nLevel>1 ){ + if( pWInfo->nLevel>1 || pTabItem->fg.fromExists ){ int nNotReady; /* The number of notReady tables */ - struct SrcList_item *origSrc; /* Original list of tables */ + SrcItem *origSrc; /* Original list of tables */ nNotReady = pWInfo->nLevel - iLevel - 1; - pOrTab = sqlite3StackAllocRaw(db, - sizeof(*pOrTab)+ nNotReady*sizeof(pOrTab->a[0])); + pOrTab = sqlite3DbMallocRawNN(db, SZ_SRCLIST(nNotReady+1)); if( pOrTab==0 ) return notReady; pOrTab->nAlloc = (u8)(nNotReady + 1); pOrTab->nSrc = pOrTab->nAlloc; @@ -138051,19 +166257,26 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( for(k=1; k<=nNotReady; k++){ memcpy(&pOrTab->a[k], &origSrc[pLevel[k].iFrom], sizeof(pOrTab->a[k])); } + + /* Clear the fromExists flag on the OR-optimized table entry so that + ** the calls to sqlite3WhereEnd() do not code early-exits after the + ** first row is visited. The early exit applies to this table's + ** overall loop - including the multiple OR branches and any WHERE + ** conditions not passed to the sub-loops - not to the sub-loops. */ + pOrTab->a[0].fg.fromExists = 0; }else{ pOrTab = pWInfo->pTabList; } - /* Initialize the rowset register to contain NULL. An SQL NULL is + /* Initialize the rowset register to contain NULL. An SQL NULL is ** equivalent to an empty rowset. Or, create an ephemeral index ** capable of holding primary keys in the case of a WITHOUT ROWID. ** - ** Also initialize regReturn to contain the address of the instruction + ** Also initialize regReturn to contain the address of the instruction ** immediately following the OP_Return at the bottom of the loop. This ** is required in a few obscure LEFT JOIN cases where control jumps - ** over the top of the loop into the body of it. In this case the - ** correct response for the end-of-loop code (the OP_Return) is to + ** over the top of the loop into the body of it. In this case the + ** correct response for the end-of-loop code (the OP_Return) is to ** fall through to the next instruction, just as an OP_Next does if ** called on an uninitialized cursor. */ @@ -138082,18 +166295,32 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( iRetInit = sqlite3VdbeAddOp2(v, OP_Integer, 0, regReturn); /* If the original WHERE clause is z of the form: (x1 OR x2 OR ...) AND y - ** Then for every term xN, evaluate as the subexpression: xN AND z + ** Then for every term xN, evaluate as the subexpression: xN AND y ** That way, terms in y that are factored into the disjunction will ** be picked up by the recursive calls to sqlite3WhereBegin() below. ** ** Actually, each subexpression is converted to "xN AND w" where w is ** the "interesting" terms of z - terms that did not originate in the - ** ON or USING clause of a LEFT JOIN, and terms that are usable as + ** ON or USING clause of a LEFT JOIN, and terms that are usable as ** indices. ** ** This optimization also only applies if the (x1 OR x2 OR ...) term ** is not contained in the ON clause of a LEFT JOIN. - ** See ticket http://www.sqlite.org/src/info/f2369304e4 + ** See ticket http://sqlite.org/src/info/f2369304e4 + ** + ** 2022-02-04: Do not push down slices of a row-value comparison. + ** In other words, "w" or "y" may not be a slice of a vector. Otherwise, + ** the initialization of the right-hand operand of the vector comparison + ** might not occur, or might occur only in an OR branch that is not + ** taken. dbsqlfuzz 80a9fade844b4fb43564efc972bcb2c68270f5d1. + ** + ** 2022-03-03: Do not push down expressions that involve subqueries. + ** The subquery might get coded as a subroutine. Any table-references + ** in the subquery might be resolved to index-references for the index on + ** the OR branch in which the subroutine is coded. But if the subroutine + ** is invoked from a different OR branch that uses a different index, such + ** index-references will not work. tag-20220303a + ** https://sqlite.org/forum/forumpost/36937b197273d403 */ if( pWC->nTerm>1 ){ int iTerm; @@ -138102,17 +166329,20 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( if( &pWC->a[iTerm] == pTerm ) continue; testcase( pWC->a[iTerm].wtFlags & TERM_VIRTUAL ); testcase( pWC->a[iTerm].wtFlags & TERM_CODED ); - if( (pWC->a[iTerm].wtFlags & (TERM_VIRTUAL|TERM_CODED))!=0 ) continue; + testcase( pWC->a[iTerm].wtFlags & TERM_SLICE ); + if( (pWC->a[iTerm].wtFlags & (TERM_VIRTUAL|TERM_CODED|TERM_SLICE))!=0 ){ + continue; + } if( (pWC->a[iTerm].eOperator & WO_ALL)==0 ) continue; - testcase( pWC->a[iTerm].wtFlags & TERM_ORINFO ); + if( ExprHasProperty(pExpr, EP_Subquery) ) continue; /* tag-20220303a */ pExpr = sqlite3ExprDup(db, pExpr, 0); - pAndExpr = sqlite3ExprAnd(db, pAndExpr, pExpr); + pAndExpr = sqlite3ExprAnd(pParse, pAndExpr, pExpr); } if( pAndExpr ){ /* The extra 0x10000 bit on the opcode is masked off and does not ** become part of the new Expr.op. However, it does make the ** op==TK_AND comparison inside of sqlite3PExpr() false, and this - ** prevents sqlite3PExpr() from implementing AND short-circuit + ** prevents sqlite3PExpr() from applying the AND short-circuit ** optimization, which we do not want here. */ pAndExpr = sqlite3PExpr(pParse, TK_AND|0x10000, 0, pAndExpr); } @@ -138122,27 +166352,32 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( ** eliminating duplicates from other WHERE clauses, the action for each ** sub-WHERE clause is to to invoke the main loop body as a subroutine. */ - wctrlFlags = WHERE_OR_SUBCLAUSE | (pWInfo->wctrlFlags & WHERE_SEEK_TABLE); ExplainQueryPlan((pParse, 1, "MULTI-INDEX OR")); for(ii=0; iinTerm; ii++){ WhereTerm *pOrTerm = &pOrWc->a[ii]; if( pOrTerm->leftCursor==iCur || (pOrTerm->eOperator & WO_AND)!=0 ){ WhereInfo *pSubWInfo; /* Info for single OR-term scan */ Expr *pOrExpr = pOrTerm->pExpr; /* Current OR clause term */ + Expr *pDelete; /* Local copy of OR clause term */ int jmp1 = 0; /* Address of jump operation */ - assert( (pTabItem[0].fg.jointype & JT_LEFT)==0 - || ExprHasProperty(pOrExpr, EP_FromJoin) - ); + testcase( (pTabItem[0].fg.jointype & JT_LEFT)!=0 + && !ExprHasProperty(pOrExpr, EP_OuterON) + ); /* See TH3 vtab25.400 and ticket 614b25314c766238 */ + pDelete = pOrExpr = sqlite3ExprDup(db, pOrExpr, 0); + if( db->mallocFailed ){ + sqlite3ExprDelete(db, pDelete); + continue; + } if( pAndExpr ){ pAndExpr->pLeft = pOrExpr; pOrExpr = pAndExpr; } /* Loop through table entries that match term pOrTerm. */ ExplainQueryPlan((pParse, 1, "INDEX %d", ii+1)); - WHERETRACE(0xffff, ("Subplan for OR-clause:\n")); - pSubWInfo = sqlite3WhereBegin(pParse, pOrTab, pOrExpr, 0, 0, - wctrlFlags, iCovCur); - assert( pSubWInfo || pParse->nErr || db->mallocFailed ); + WHERETRACE(0xffffffff, ("Subplan for OR-clause:\n")); + pSubWInfo = sqlite3WhereBegin(pParse, pOrTab, pOrExpr, 0, 0, 0, + WHERE_OR_SUBCLAUSE, iCovCur); + assert( pSubWInfo || pParse->nErr ); if( pSubWInfo ){ WhereLoop *pSubLoop; int addrExplain = sqlite3WhereExplainOneScan( @@ -138172,7 +166407,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( r = sqlite3GetTempRange(pParse, nPk); for(iPk=0; iPkaiColumn[iPk]; - sqlite3ExprCodeGetColumnOfTable(v, pTab, iCur, iCol, r+iPk); + sqlite3ExprCodeGetColumnOfTable(v, pTab, iCur, iCol,r+iPk); } /* Check if the temp table already contains this key. If so, @@ -138183,9 +166418,9 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( ** ** Use some of the same optimizations as OP_RowSetTest: If iSet ** is zero, assume that the key cannot already be present in - ** the temp table. And if iSet is -1, assume that there is no - ** need to insert the key into the temp table, as it will never - ** be tested for. */ + ** the temp table. And if iSet is -1, assume that there is no + ** need to insert the key into the temp table, as it will never + ** be tested for. */ if( iSet ){ jmp1 = sqlite3VdbeAddOp4Int(v, OP_Found, regRowset, 0, r, nPk); VdbeCoverage(v); @@ -138224,8 +166459,8 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( ** If the call to sqlite3WhereBegin() above resulted in a scan that ** uses an index, and this is either the first OR-connected term ** processed or the index is the same as that used by all previous - ** terms, set pCov to the candidate covering index. Otherwise, set - ** pCov to NULL to indicate that no candidate covering index will + ** terms, set pCov to the candidate covering index. Otherwise, set + ** pCov to NULL to indicate that no candidate covering index will ** be available. */ pSubLoop = pSubWInfo->a[0].pWLoop; @@ -138239,15 +166474,22 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( }else{ pCov = 0; } + if( sqlite3WhereUsesDeferredSeek(pSubWInfo) ){ + pWInfo->bDeferredSeek = 1; + } /* Finish the loop through table entries that match term pOrTerm. */ sqlite3WhereEnd(pSubWInfo); ExplainQueryPlanPop(pParse); } + sqlite3ExprDelete(db, pDelete); } } ExplainQueryPlanPop(pParse); - pLevel->u.pCovidx = pCov; + assert( pLevel->pWLoop==pLoop ); + assert( (pLoop->wsFlags & WHERE_MULTI_OR)!=0 ); + assert( (pLoop->wsFlags & WHERE_IN_ABLE)==0 ); + pLevel->u.pCoveringIdx = pCov; if( pCov ) pLevel->iIdxCur = iCovCur; if( pAndExpr ){ pAndExpr->pLeft = 0; @@ -138257,7 +166499,15 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( sqlite3VdbeGoto(v, pLevel->addrBrk); sqlite3VdbeResolveLabel(v, iLoopBody); - if( pWInfo->nLevel>1 ) sqlite3StackFree(db, pOrTab); + /* Set the P2 operand of the OP_Return opcode that will end the current + ** loop to point to this spot, which is the top of the next containing + ** loop. The byte-code formatter will use that P2 value as a hint to + ** indent everything in between the this point and the final OP_Return. + ** See tag-20220407a in vdbe.c and shell.c */ + assert( pLevel->op==OP_Return ); + pLevel->p2 = sqlite3VdbeCurrentAddr(v); + + if( pWInfo->pTabList!=pOrTab ){ sqlite3DbFreeNN(db, pOrTab); } if( !untestedTerms ) disableTerm(pLevel, pTerm); }else #endif /* SQLITE_OMIT_OR_OPTIMIZATION */ @@ -138277,7 +166527,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( codeCursorHint(pTabItem, pWInfo, pLevel, 0); pLevel->op = aStep[bRev]; pLevel->p1 = iCur; - pLevel->p2 = 1 + sqlite3VdbeAddOp2(v, aStart[bRev], iCur, addrHalt); + pLevel->p2 = 1 + sqlite3VdbeAddOp2(v, aStart[bRev],iCur,pLevel->addrHalt); VdbeCoverageIf(v, bRev==0); VdbeCoverageIf(v, bRev!=0); pLevel->p5 = SQLITE_STMTSTATUS_FULLSCAN_STEP; @@ -138297,10 +166547,16 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( ** ** iLoop==1: Code only expressions that are entirely covered by pIdx. ** iLoop==2: Code remaining expressions that do not contain correlated - ** sub-queries. + ** sub-queries. ** iLoop==3: Code all remaining expressions. ** ** An effort is made to skip unnecessary iterations of the loop. + ** + ** This optimization of causing simple query restrictions to occur before + ** more complex one is call the "push-down" optimization in MySQL. Here + ** in SQLite, the name is "MySQL push-down", since there is also another + ** totally unrelated optimization called "WHERE-clause push-down". + ** Sometimes the qualifier is omitted, resulting in an ambiguity, so beware. */ iLoop = (pIdx ? 1 : 2); do{ @@ -138319,10 +166575,22 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( } pE = pTerm->pExpr; assert( pE!=0 ); - if( (pTabItem->fg.jointype&JT_LEFT) && !ExprHasProperty(pE,EP_FromJoin) ){ - continue; + if( pTabItem->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT) ){ + if( !ExprHasProperty(pE,EP_OuterON|EP_InnerON) ){ + /* Defer processing WHERE clause constraints until after outer + ** join processing. tag-20220513a */ + continue; + }else if( (pTabItem->fg.jointype & JT_LEFT)==JT_LEFT + && !ExprHasProperty(pE,EP_OuterON) ){ + continue; + }else{ + Bitmask m = sqlite3WhereGetMask(&pWInfo->sMaskSet, pE->w.iJoin); + if( m & pLevel->notReady ){ + /* An ON clause that is not ripe */ + continue; + } + } } - if( iLoop==1 && !sqlite3ExprCoveredByIndex(pE, pLevel->iTabCur, pIdx) ){ iNext = 2; continue; @@ -138349,11 +166617,15 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( } #endif } -#ifdef WHERETRACE_ENABLED /* 0xffff */ +#ifdef WHERETRACE_ENABLED /* 0xffffffff */ if( sqlite3WhereTrace ){ VdbeNoopComment((v, "WhereTerm[%d] (%p) priority=%d", pWC->nTerm-j, pTerm, iLoop)); } + if( sqlite3WhereTrace & 0x4000 ){ + sqlite3DebugPrintf("Coding auxiliary constraint:\n"); + sqlite3WhereTermPrint(pTerm, pWC->nTerm-j); + } #endif sqlite3ExprIfFalse(pParse, pE, addrCont, SQLITE_JUMPIFNULL); if( skipLikeAddr ) sqlite3VdbeJumpHere(v, skipLikeAddr); @@ -138370,23 +166642,31 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( ** then we cannot use the "t1.a=t2.b" constraint, but we can code ** the implied "t1.a=123" constraint. */ - for(pTerm=pWC->a, j=pWC->nTerm; j>0; j--, pTerm++){ + for(pTerm=pWC->a, j=pWC->nBase; j>0; j--, pTerm++){ Expr *pE, sEAlt; WhereTerm *pAlt; if( pTerm->wtFlags & (TERM_VIRTUAL|TERM_CODED) ) continue; if( (pTerm->eOperator & (WO_EQ|WO_IS))==0 ) continue; if( (pTerm->eOperator & WO_EQUIV)==0 ) continue; if( pTerm->leftCursor!=iCur ) continue; - if( pLevel->iLeftJoin ) continue; + if( pTabItem->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT) ) continue; pE = pTerm->pExpr; - assert( !ExprHasProperty(pE, EP_FromJoin) ); +#ifdef WHERETRACE_ENABLED /* 0x4001 */ + if( (sqlite3WhereTrace & 0x4001)==0x4001 ){ + sqlite3DebugPrintf("Coding transitive constraint:\n"); + sqlite3WhereTermPrint(pTerm, pWC->nTerm-j); + } +#endif + assert( !ExprHasProperty(pE, EP_OuterON) ); assert( (pTerm->prereqRight & pLevel->notReady)!=0 ); - pAlt = sqlite3WhereFindTerm(pWC, iCur, pTerm->u.leftColumn, notReady, + assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 ); + pAlt = sqlite3WhereFindTerm(pWC, iCur, pTerm->u.x.leftColumn, notReady, WO_EQ|WO_IN|WO_IS, 0); if( pAlt==0 ) continue; if( pAlt->wtFlags & (TERM_CODED) ) continue; - if( (pAlt->eOperator & WO_IN) - && (pAlt->pExpr->flags & EP_xIsSelect) + if( ExprHasProperty(pAlt->pExpr, EP_Collate) ) continue; + if( (pAlt->eOperator & WO_IN) + && ExprUseXSelect(pAlt->pExpr) && (pAlt->pExpr->x.pSelect->pEList->nExpr>1) ){ continue; @@ -138398,16 +166678,82 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( sEAlt = *pAlt->pExpr; sEAlt.pLeft = pE->pLeft; sqlite3ExprIfFalse(pParse, &sEAlt, addrCont, SQLITE_JUMPIFNULL); + pAlt->wtFlags |= TERM_CODED; + } + + /* For a RIGHT OUTER JOIN, record the fact that the current row has + ** been matched at least once. + */ + if( pLevel->pRJ ){ + Table *pTab; + int nPk; + int r; + int jmp1 = 0; + WhereRightJoin *pRJ = pLevel->pRJ; + + /* pTab is the right-hand table of the RIGHT JOIN. Generate code that + ** will record that the current row of that table has been matched at + ** least once. This is accomplished by storing the PK for the row in + ** both the iMatch index and the regBloom Bloom filter. + */ + pTab = pWInfo->pTabList->a[pLevel->iFrom].pSTab; + if( HasRowid(pTab) ){ + r = sqlite3GetTempRange(pParse, 2); + sqlite3ExprCodeGetColumnOfTable(v, pTab, pLevel->iTabCur, -1, r+1); + nPk = 1; + }else{ + int iPk; + Index *pPk = sqlite3PrimaryKeyIndex(pTab); + nPk = pPk->nKeyCol; + r = sqlite3GetTempRange(pParse, nPk+1); + for(iPk=0; iPkaiColumn[iPk]; + sqlite3ExprCodeGetColumnOfTable(v, pTab, iCur, iCol,r+1+iPk); + } + } + jmp1 = sqlite3VdbeAddOp4Int(v, OP_Found, pRJ->iMatch, 0, r+1, nPk); + VdbeCoverage(v); + VdbeComment((v, "match against %s", pTab->zName)); + sqlite3VdbeAddOp3(v, OP_MakeRecord, r+1, nPk, r); + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, pRJ->iMatch, r, r+1, nPk); + sqlite3VdbeAddOp4Int(v, OP_FilterAdd, pRJ->regBloom, 0, r+1, nPk); + sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT); + sqlite3VdbeJumpHere(v, jmp1); + sqlite3ReleaseTempRange(pParse, r, nPk+1); } /* For a LEFT OUTER JOIN, generate code that will record the fact that - ** at least one row of the right table has matched the left table. + ** at least one row of the right table has matched the left table. */ if( pLevel->iLeftJoin ){ pLevel->addrFirst = sqlite3VdbeCurrentAddr(v); sqlite3VdbeAddOp2(v, OP_Integer, 1, pLevel->iLeftJoin); VdbeComment((v, "record LEFT JOIN hit")); - for(pTerm=pWC->a, j=0; jnTerm; j++, pTerm++){ + if( pLevel->pRJ==0 ){ + goto code_outer_join_constraints; /* WHERE clause constraints */ + } + } + + if( pLevel->pRJ ){ + /* Create a subroutine used to process all interior loops and code + ** of the RIGHT JOIN. During normal operation, the subroutine will + ** be in-line with the rest of the code. But at the end, a separate + ** loop will run that invokes this subroutine for unmatched rows + ** of pTab, with all tables to left begin set to NULL. + */ + WhereRightJoin *pRJ = pLevel->pRJ; + sqlite3VdbeAddOp2(v, OP_BeginSubrtn, 0, pRJ->regReturn); + pRJ->addrSubrtn = sqlite3VdbeCurrentAddr(v); + assert( pParse->withinRJSubrtn < 255 ); + pParse->withinRJSubrtn++; + + /* WHERE clause constraints must be deferred until after outer join + ** row elimination has completed, since WHERE clause constraints apply + ** to the results of the OUTER JOIN. The following loop generates the + ** appropriate WHERE clause constraint checks. tag-20220513a. + */ + code_outer_join_constraints: + for(pTerm=pWC->a, j=0; jnBase; j++, pTerm++){ testcase( pTerm->wtFlags & TERM_VIRTUAL ); testcase( pTerm->wtFlags & TERM_CODED ); if( pTerm->wtFlags & (TERM_VIRTUAL|TERM_CODED) ) continue; @@ -138415,15 +166761,144 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( assert( pWInfo->untestedTerms ); continue; } + if( pTabItem->fg.jointype & JT_LTORJ ) continue; assert( pTerm->pExpr ); sqlite3ExprIfFalse(pParse, pTerm->pExpr, addrCont, SQLITE_JUMPIFNULL); pTerm->wtFlags |= TERM_CODED; } } +#if WHERETRACE_ENABLED /* 0x4001 */ + if( sqlite3WhereTrace & 0x4000 ){ + sqlite3DebugPrintf("All WHERE-clause terms after coding level %d:\n", + iLevel); + sqlite3WhereClausePrint(pWC); + } + if( sqlite3WhereTrace & 0x1 ){ + sqlite3DebugPrintf("End Coding level %d: notReady=%llx\n", + iLevel, (u64)pLevel->notReady); + } +#endif return pLevel->notReady; } +/* +** Generate the code for the loop that finds all non-matched terms +** for a RIGHT JOIN. +*/ +SQLITE_PRIVATE SQLITE_NOINLINE void sqlite3WhereRightJoinLoop( + WhereInfo *pWInfo, + int iLevel, + WhereLevel *pLevel +){ + Parse *pParse = pWInfo->pParse; + Vdbe *v = pParse->pVdbe; + WhereRightJoin *pRJ = pLevel->pRJ; + Expr *pSubWhere = 0; + WhereClause *pWC = &pWInfo->sWC; + WhereInfo *pSubWInfo; + WhereLoop *pLoop = pLevel->pWLoop; + SrcItem *pTabItem = &pWInfo->pTabList->a[pLevel->iFrom]; + SrcList *pFrom; + union { + SrcList sSrc; + u8 fromSpace[SZ_SRCLIST_1]; + } uSrc; + Bitmask mAll = 0; + int k; + + ExplainQueryPlan((pParse, 1, "RIGHT-JOIN %s", pTabItem->pSTab->zName)); + sqlite3VdbeNoJumpsOutsideSubrtn(v, pRJ->addrSubrtn, pRJ->endSubrtn, + pRJ->regReturn); + for(k=0; ka[k].pWLoop->iTab == pWInfo->a[k].iFrom ); + pRight = &pWInfo->pTabList->a[pWInfo->a[k].iFrom]; + mAll |= pWInfo->a[k].pWLoop->maskSelf; + if( pRight->fg.viaCoroutine ){ + Subquery *pSubq; + assert( pRight->fg.isSubquery && pRight->u4.pSubq!=0 ); + pSubq = pRight->u4.pSubq; + assert( pSubq->pSelect!=0 && pSubq->pSelect->pEList!=0 ); + sqlite3VdbeAddOp3( + v, OP_Null, 0, pSubq->regResult, + pSubq->regResult + pSubq->pSelect->pEList->nExpr-1 + ); + } + sqlite3VdbeAddOp1(v, OP_NullRow, pWInfo->a[k].iTabCur); + iIdxCur = pWInfo->a[k].iIdxCur; + if( iIdxCur ){ + sqlite3VdbeAddOp1(v, OP_NullRow, iIdxCur); + } + } + if( (pTabItem->fg.jointype & JT_LTORJ)==0 ){ + mAll |= pLoop->maskSelf; + for(k=0; knTerm; k++){ + WhereTerm *pTerm = &pWC->a[k]; + if( (pTerm->wtFlags & (TERM_VIRTUAL|TERM_SLICE))!=0 + && pTerm->eOperator!=WO_ROWVAL + ){ + break; + } + if( pTerm->prereqAll & ~mAll ) continue; + if( ExprHasProperty(pTerm->pExpr, EP_OuterON|EP_InnerON) ) continue; + pSubWhere = sqlite3ExprAnd(pParse, pSubWhere, + sqlite3ExprDup(pParse->db, pTerm->pExpr, 0)); + } + } + if( pLevel->iIdxCur ){ + /* pSubWhere may contain expressions that read from an index on the + ** table on the RHS of the right join. All such expressions first test + ** if the index is pointing at a NULL row, and if so, read from the + ** table cursor instead. So ensure that the index cursor really is + ** pointing at a NULL row here, so that no values are read from it during + ** the scan of the RHS of the RIGHT join below. */ + sqlite3VdbeAddOp1(v, OP_NullRow, pLevel->iIdxCur); + } + pFrom = &uSrc.sSrc; + pFrom->nSrc = 1; + pFrom->nAlloc = 1; + memcpy(&pFrom->a[0], pTabItem, sizeof(SrcItem)); + pFrom->a[0].fg.jointype = 0; + assert( pParse->withinRJSubrtn < 100 ); + pParse->withinRJSubrtn++; + pSubWInfo = sqlite3WhereBegin(pParse, pFrom, pSubWhere, 0, 0, 0, + WHERE_RIGHT_JOIN, 0); + if( pSubWInfo ){ + int iCur = pLevel->iTabCur; + int r = ++pParse->nMem; + int nPk; + int jmp; + int addrCont = sqlite3WhereContinueLabel(pSubWInfo); + Table *pTab = pTabItem->pSTab; + if( HasRowid(pTab) ){ + sqlite3ExprCodeGetColumnOfTable(v, pTab, iCur, -1, r); + nPk = 1; + }else{ + int iPk; + Index *pPk = sqlite3PrimaryKeyIndex(pTab); + nPk = pPk->nKeyCol; + pParse->nMem += nPk - 1; + for(iPk=0; iPkaiColumn[iPk]; + sqlite3ExprCodeGetColumnOfTable(v, pTab, iCur, iCol,r+iPk); + } + } + jmp = sqlite3VdbeAddOp4Int(v, OP_Filter, pRJ->regBloom, 0, r, nPk); + VdbeCoverage(v); + sqlite3VdbeAddOp4Int(v, OP_Found, pRJ->iMatch, addrCont, r, nPk); + VdbeCoverage(v); + sqlite3VdbeJumpHere(v, jmp); + sqlite3VdbeAddOp2(v, OP_Gosub, pRJ->regReturn, pRJ->addrSubrtn); + sqlite3WhereEnd(pSubWInfo); + } + sqlite3ExprDelete(pParse->db, pSubWhere); + ExplainQueryPlanPop(pParse); + assert( pParse->withinRJSubrtn>0 ); + pParse->withinRJSubrtn--; +} + /************** End of wherecode.c *******************************************/ /************** Begin file whereexpr.c ***************************************/ /* @@ -138441,7 +166916,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( ** the WHERE clause of SQL statements. ** ** This file was originally part of where.c but was split out to improve -** readability and editabiliity. This file contains utility routines for +** readability and editability. This file contains utility routines for ** analyzing Expr objects in the WHERE clause. */ /* #include "sqliteInt.h" */ @@ -138492,7 +166967,7 @@ static int whereClauseInsert(WhereClause *pWC, Expr *p, u16 wtFlags){ if( pWC->nTerm>=pWC->nSlot ){ WhereTerm *pOld = pWC->a; sqlite3 *db = pWC->pWInfo->pParse->db; - pWC->a = sqlite3DbMallocRawNN(db, sizeof(pWC->a[0])*pWC->nSlot*2 ); + pWC->a = sqlite3WhereMalloc(pWC->pWInfo, sizeof(pWC->a[0])*pWC->nSlot*2 ); if( pWC->a==0 ){ if( wtFlags & TERM_DYNAMIC ){ sqlite3ExprDelete(db, p); @@ -138501,18 +166976,16 @@ static int whereClauseInsert(WhereClause *pWC, Expr *p, u16 wtFlags){ return 0; } memcpy(pWC->a, pOld, sizeof(pWC->a[0])*pWC->nTerm); - if( pOld!=pWC->aStatic ){ - sqlite3DbFree(db, pOld); - } - pWC->nSlot = sqlite3DbMallocSize(db, pWC->a)/sizeof(pWC->a[0]); + pWC->nSlot = pWC->nSlot*2; } pTerm = &pWC->a[idx = pWC->nTerm++]; + if( (wtFlags & TERM_VIRTUAL)==0 ) pWC->nBase = pWC->nTerm; if( p && ExprHasProperty(p, EP_Unlikely) ){ pTerm->truthProb = sqlite3LogEst(p->iTable) - 270; }else{ pTerm->truthProb = 1; } - pTerm->pExpr = sqlite3ExprSkipCollate(p); + pTerm->pExpr = sqlite3ExprSkipCollateAndLikely(p); pTerm->wtFlags = wtFlags; pTerm->pWC = pWC; pTerm->iParent = -1; @@ -138531,37 +167004,25 @@ static int allowedOp(int op){ assert( TK_LT>TK_EQ && TK_LTTK_EQ && TK_LE=TK_EQ && op<=TK_GE) || op==TK_ISNULL || op==TK_IS; + assert( TK_INTK_GE ) return 0; + if( op>=TK_EQ ) return 1; + return op==TK_IN || op==TK_ISNULL || op==TK_IS; } /* ** Commute a comparison operator. Expressions of the form "X op Y" ** are converted into "Y op X". -** -** If left/right precedence rules come into play when determining the -** collating sequence, then COLLATE operators are adjusted to ensure -** that the collating sequence does not change. For example: -** "Y collate NOCASE op X" becomes "X op Y" because any collation sequence on -** the left hand side of a comparison overrides any collation sequence -** attached to the right. For the same reason the EP_Collate flag -** is not commuted. -*/ -static void exprCommute(Parse *pParse, Expr *pExpr){ - u16 expRight = (pExpr->pRight->flags & EP_Collate); - u16 expLeft = (pExpr->pLeft->flags & EP_Collate); - assert( allowedOp(pExpr->op) && pExpr->op!=TK_IN ); - if( expRight==expLeft ){ - /* Either X and Y both have COLLATE operator or neither do */ - if( expRight ){ - /* Both X and Y have COLLATE operators. Make sure X is always - ** used by clearing the EP_Collate flag from Y. */ - pExpr->pRight->flags &= ~EP_Collate; - }else if( sqlite3ExprCollSeq(pParse, pExpr->pLeft)!=0 ){ - /* Neither X nor Y have COLLATE operators, but X has a non-default - ** collating sequence. So add the EP_Collate marker on X to cause - ** it to be searched first. */ - pExpr->pLeft->flags |= EP_Collate; - } +*/ +static u16 exprCommute(Parse *pParse, Expr *pExpr){ + if( pExpr->pLeft->op==TK_VECTOR + || pExpr->pRight->op==TK_VECTOR + || sqlite3BinaryCompareCollSeq(pParse, pExpr->pLeft, pExpr->pRight) != + sqlite3BinaryCompareCollSeq(pParse, pExpr->pRight, pExpr->pLeft) + ){ + pExpr->flags ^= EP_Commuted; } SWAP(Expr*,pExpr->pRight,pExpr->pLeft); if( pExpr->op>=TK_GT ){ @@ -138572,6 +167033,7 @@ static void exprCommute(Parse *pParse, Expr *pExpr){ assert( pExpr->op>=TK_GT && pExpr->op<=TK_GE ); pExpr->op = ((pExpr->op-TK_GT)^2)+TK_GT; } + return 0; } /* @@ -138580,15 +167042,16 @@ static void exprCommute(Parse *pParse, Expr *pExpr){ static u16 operatorMask(int op){ u16 c; assert( allowedOp(op) ); - if( op==TK_IN ){ + if( op>=TK_EQ ){ + assert( (WO_EQ<<(op-TK_EQ)) < 0x7fff ); + c = (u16)(WO_EQ<<(op-TK_EQ)); + }else if( op==TK_IN ){ c = WO_IN; }else if( op==TK_ISNULL ){ c = WO_ISNULL; - }else if( op==TK_IS ){ - c = WO_IS; }else{ - assert( (WO_EQ<<(op-TK_EQ)) < 0x7fff ); - c = (u16)(WO_EQ<<(op-TK_EQ)); + assert( op==TK_IS ); + c = WO_IS; } assert( op!=TK_ISNULL || c==WO_ISNULL ); assert( op!=TK_IN || c==WO_IN ); @@ -138639,6 +167102,7 @@ static int isLikeOrGlob( #ifdef SQLITE_EBCDIC if( *pnoCase ) return 0; #endif + assert( ExprUseXList(pExpr) ); pList = pExpr->x.pList; pLeft = pList->a[1].pExpr; @@ -138654,15 +167118,32 @@ static int isLikeOrGlob( sqlite3VdbeSetVarmask(pParse->pVdbe, iCol); assert( pRight->op==TK_VARIABLE || pRight->op==TK_REGISTER ); }else if( op==TK_STRING ){ - z = (u8*)pRight->u.zToken; + assert( !ExprHasProperty(pRight, EP_IntValue) ); + z = (u8*)pRight->u.zToken; } if( z ){ - - /* Count the number of prefix characters prior to the first wildcard */ + /* Count the number of prefix bytes prior to the first wildcard, + ** U+fffd character, or malformed utf-8. If the underlying database + ** has a UTF16LE encoding, then only consider ASCII characters. Note that + ** the encoding of z[] is UTF8 - we are dealing with only UTF8 here in this + ** code, but the database engine itself might be processing content using a + ** different encoding. */ cnt = 0; while( (c=z[cnt])!=0 && c!=wc[0] && c!=wc[1] && c!=wc[2] ){ cnt++; - if( c==wc[3] && z[cnt]!=0 ) cnt++; + if( c==wc[3] && z[cnt]>0 && z[cnt]<0x80 ){ + cnt++; + }else if( c>=0x80 ){ + const u8 *z2 = z+cnt-1; + if( c==0xff || sqlite3Utf8Read(&z2)==0xfffd /* bad utf-8 */ + || ENC(db)==SQLITE_UTF16LE + ){ + cnt--; + break; + }else{ + cnt = (int)(z2-z); + } + } } /* The optimization is possible only if (1) the pattern does not begin @@ -138673,44 +167154,60 @@ static int isLikeOrGlob( ** range search. The third is because the caller assumes that the pattern ** consists of at least one character after all escapes have been ** removed. */ - if( cnt!=0 && 255!=(u8)z[cnt-1] && (cnt>1 || z[0]!=wc[3]) ){ + if( (cnt>1 || (cnt>0 && z[0]!=wc[3])) && ALWAYS(255!=(u8)z[cnt-1]) ){ Expr *pPrefix; /* A "complete" match if the pattern ends with "*" or "%" */ - *pisComplete = c==wc[0] && z[cnt+1]==0; + *pisComplete = c==wc[0] && z[cnt+1]==0 && ENC(db)!=SQLITE_UTF16LE; /* Get the pattern prefix. Remove all escapes from the prefix. */ pPrefix = sqlite3Expr(db, TK_STRING, (char*)z); if( pPrefix ){ int iFrom, iTo; - char *zNew = pPrefix->u.zToken; + char *zNew; + assert( !ExprHasProperty(pPrefix, EP_IntValue) ); + zNew = pPrefix->u.zToken; zNew[cnt] = 0; for(iFrom=iTo=0; iFrom0 ); - /* If the RHS begins with a digit or a minus sign, then the LHS must be - ** an ordinary column (not a virtual table column) with TEXT affinity. - ** Otherwise the LHS might be numeric and "lhs >= rhs" would be false - ** even though "lhs LIKE rhs" is true. But if the RHS does not start - ** with a digit or '-', then "lhs LIKE rhs" will always be false if - ** the LHS is numeric and so the optimization still works. + /* If the LHS is not an ordinary column with TEXT affinity, then the + ** pattern prefix boundaries (both the start and end boundaries) must + ** not look like a number. Otherwise the pattern might be treated as + ** a number, which will invalidate the LIKE optimization. ** - ** 2018-09-10 ticket c94369cae9b561b1f996d0054bfab11389f9d033 - ** The RHS pattern must not be '/%' because the termination condition - ** will then become "x<'0'" and if the affinity is numeric, will then - ** be converted into "x<0", which is incorrect. + ** Getting this right has been a persistent source of bugs in the + ** LIKE optimization. See, for example: + ** 2018-09-10 https://sqlite.org/src/info/c94369cae9b561b1 + ** 2019-05-02 https://sqlite.org/src/info/b043a54c3de54b28 + ** 2019-06-10 https://sqlite.org/src/info/fd76310a5e843e07 + ** 2019-06-14 https://sqlite.org/src/info/ce8717f0885af975 + ** 2019-09-03 https://sqlite.org/src/info/0f0428096f17252a */ - if( sqlite3Isdigit(zNew[0]) - || zNew[0]=='-' - || (zNew[0]+1=='0' && iTo==1) + if( pLeft->op!=TK_COLUMN + || sqlite3ExprAffinity(pLeft)!=SQLITE_AFF_TEXT + || (ALWAYS( ExprUseYTab(pLeft) ) + && ALWAYS(pLeft->y.pTab) + && IsVirtual(pLeft->y.pTab)) /* Might be numeric */ ){ - if( pLeft->op!=TK_COLUMN - || sqlite3ExprAffinity(pLeft)!=SQLITE_AFF_TEXT - || IsVirtual(pLeft->y.pTab) /* Value might be numeric */ - ){ + int isNum; + double rDummy; + assert( zNew[iTo]==0 ); + isNum = sqlite3AtoF(zNew, &rDummy); + if( isNum<=0 ){ + if( iTo==1 && zNew[0]=='-' ){ + isNum = +1; + }else{ + zNew[iTo-1]++; + isNum = sqlite3AtoF(zNew, &rDummy); + zNew[iTo-1]--; + } + } + if( isNum>0 ){ sqlite3ExprDelete(db, pPrefix); sqlite3ValueFree(pVal); return 0; @@ -138724,13 +167221,14 @@ static int isLikeOrGlob( if( op==TK_VARIABLE ){ Vdbe *v = pParse->pVdbe; sqlite3VdbeSetVarmask(v, pRight->iColumn); + assert( !ExprHasProperty(pRight, EP_IntValue) ); if( *pisComplete && pRight->u.zToken[1] ){ /* If the rhs of the LIKE expression is a variable, and the current ** value of the variable means there is no need to invoke the LIKE ** function, then no OP_Variable will be added to the program. ** This causes problems for the sqlite3_bind_parameter_name() ** API. To work around them, add a dummy OP_Variable here. - */ + */ int r1 = sqlite3GetTempReg(pParse); sqlite3ExprCodeTarget(pParse, pRight, r1); sqlite3VdbeChangeP3(v, sqlite3VdbeCurrentAddr(v)-1, 0); @@ -138748,6 +167246,34 @@ static int isLikeOrGlob( } #endif /* SQLITE_OMIT_LIKE_OPTIMIZATION */ +/* +** If pExpr is one of "like", "glob", "match", or "regexp", then +** return the corresponding SQLITE_INDEX_CONSTRAINT_xxxx value. +** If not, return 0. +** +** pExpr is guaranteed to be a TK_FUNCTION. +*/ +SQLITE_PRIVATE int sqlite3ExprIsLikeOperator(const Expr *pExpr){ + static const struct { + const char *zOp; + unsigned char eOp; + } aOp[] = { + { "match", SQLITE_INDEX_CONSTRAINT_MATCH }, + { "glob", SQLITE_INDEX_CONSTRAINT_GLOB }, + { "like", SQLITE_INDEX_CONSTRAINT_LIKE }, + { "regexp", SQLITE_INDEX_CONSTRAINT_REGEXP } + }; + int i; + assert( pExpr->op==TK_FUNCTION ); + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + for(i=0; iu.zToken, aOp[i].zOp)==0 ){ + return aOp[i].eOp; + } + } + return 0; +} + #ifndef SQLITE_OMIT_VIRTUALTABLE /* @@ -138767,7 +167293,7 @@ static int isLikeOrGlob( ** 9. column IS NOT NULL SQLITE_INDEX_CONSTRAINT_ISNOTNULL ** ** In every case, "column" must be a column of a virtual table. If there -** is a match, set *ppLeft to the "column" expression, set *ppRight to the +** is a match, set *ppLeft to the "column" expression, set *ppRight to the ** "expr" expression (even though in forms (6) and (8) the column is on the ** right and the expression is on the left). Also set *peOp2 to the ** appropriate virtual table operator. The return value is 1 or 2 if there @@ -138784,19 +167310,11 @@ static int isAuxiliaryVtabOperator( Expr **ppRight /* Expression to left of MATCH/op2 */ ){ if( pExpr->op==TK_FUNCTION ){ - static const struct Op2 { - const char *zOp; - unsigned char eOp2; - } aOp[] = { - { "match", SQLITE_INDEX_CONSTRAINT_MATCH }, - { "glob", SQLITE_INDEX_CONSTRAINT_GLOB }, - { "like", SQLITE_INDEX_CONSTRAINT_LIKE }, - { "regexp", SQLITE_INDEX_CONSTRAINT_REGEXP } - }; ExprList *pList; Expr *pCol; /* Column reference */ int i; + assert( ExprUseXList(pExpr) ); pList = pExpr->x.pList; if( pList==0 || pList->nExpr!=2 ){ return 0; @@ -138810,15 +167328,12 @@ static int isAuxiliaryVtabOperator( ** MATCH(expression,vtab_column) */ pCol = pList->a[1].pExpr; - if( pCol->op==TK_COLUMN && IsVirtual(pCol->y.pTab) ){ - for(i=0; iu.zToken, aOp[i].zOp)==0 ){ - *peOp2 = aOp[i].eOp2; - *ppRight = pList->a[0].pExpr; - *ppLeft = pCol; - return 1; - } - } + assert( pCol->op!=TK_COLUMN || (ExprUseYTab(pCol) && pCol->y.pTab!=0) ); + if( ExprIsVtab(pCol) && (i = sqlite3ExprIsLikeOperator(pExpr))!=0 ){ + *peOp2 = i; + *ppRight = pList->a[0].pExpr; + *ppLeft = pCol; + return 1; } /* We can also match against the first column of overloaded @@ -138832,7 +167347,9 @@ static int isAuxiliaryVtabOperator( ** with function names in an arbitrary case. */ pCol = pList->a[0].pExpr; - if( pCol->op==TK_COLUMN && IsVirtual(pCol->y.pTab) ){ + assert( pCol->op!=TK_COLUMN || ExprUseYTab(pCol) ); + assert( pCol->op!=TK_COLUMN || (ExprUseYTab(pCol) && pCol->y.pTab!=0) ); + if( ExprIsVtab(pCol) ){ sqlite3_vtab *pVtab; sqlite3_module *pMod; void (*xNotUsed)(sqlite3_context*,int,sqlite3_value**); @@ -138840,6 +167357,7 @@ static int isAuxiliaryVtabOperator( pVtab = sqlite3GetVTable(db, pCol->y.pTab)->pVtab; assert( pVtab!=0 ); assert( pVtab->pModule!=0 ); + assert( !ExprHasProperty(pExpr, EP_IntValue) ); pMod = (sqlite3_module *)pVtab->pModule; if( pMod->xFindFunction!=0 ){ i = pMod->xFindFunction(pVtab,2, pExpr->u.zToken, &xNotUsed, &pNotUsed); @@ -138851,14 +167369,24 @@ static int isAuxiliaryVtabOperator( } } } + }else if( pExpr->op>=TK_EQ ){ + /* Comparison operators are a common case. Save a few comparisons for + ** that common case by terminating early. */ + assert( TK_NE < TK_EQ ); + assert( TK_ISNOT < TK_EQ ); + assert( TK_NOTNULL < TK_EQ ); + return 0; }else if( pExpr->op==TK_NE || pExpr->op==TK_ISNOT || pExpr->op==TK_NOTNULL ){ int res = 0; Expr *pLeft = pExpr->pLeft; Expr *pRight = pExpr->pRight; - if( pLeft->op==TK_COLUMN && IsVirtual(pLeft->y.pTab) ){ + assert( pLeft->op!=TK_COLUMN || (ExprUseYTab(pLeft) && pLeft->y.pTab!=0) ); + if( ExprIsVtab(pLeft) ){ res++; } - if( pRight && pRight->op==TK_COLUMN && IsVirtual(pRight->y.pTab) ){ + assert( pRight==0 || pRight->op!=TK_COLUMN + || (ExprUseYTab(pRight) && pRight->y.pTab!=0) ); + if( pRight && ExprIsVtab(pRight) ){ res++; SWAP(Expr*, pLeft, pRight); } @@ -138878,9 +167406,9 @@ static int isAuxiliaryVtabOperator( ** a join, then transfer the appropriate markings over to derived. */ static void transferJoinMarkings(Expr *pDerived, Expr *pBase){ - if( pDerived ){ - pDerived->flags |= pBase->flags & EP_FromJoin; - pDerived->iRightJoinTable = pBase->iRightJoinTable; + if( pDerived && ExprHasProperty(pBase, EP_OuterON|EP_InnerON) ){ + pDerived->flags |= pBase->flags & (EP_OuterON|EP_InnerON); + pDerived->w.iJoin = pBase->w.iJoin; } } @@ -138890,7 +167418,10 @@ static void transferJoinMarkings(Expr *pDerived, Expr *pBase){ static void markTermAsChild(WhereClause *pWC, int iChild, int iParent){ pWC->a[iChild].iParent = iParent; pWC->a[iChild].truthProb = pWC->a[iParent].truthProb; + assert( pWC->a[iParent].nChild < UMXV(pWC->a[0].nChild) ); pWC->a[iParent].nChild++; + testcase( pWC->a[iParent].nChild == UMXV(pWC->a[0].nChild) ); + } /* @@ -138926,7 +167457,7 @@ static WhereTerm *whereNthSubterm(WhereTerm *pTerm, int N){ ** ** The following is NOT generated: ** -** xy --> x!=y +** xy --> x!=y */ static void whereCombineDisjuncts( SrcList *pSrc, /* the FROM clause */ @@ -138939,15 +167470,22 @@ static void whereCombineDisjuncts( Expr *pNew; /* New virtual expression */ int op; /* Operator for the combined expression */ int idxNew; /* Index in pWC of the next virtual term */ + Expr *pA, *pB; /* Expressions associated with pOne and pTwo */ + if( (pOne->wtFlags | pTwo->wtFlags) & TERM_VNULL ) return; if( (pOne->eOperator & (WO_EQ|WO_LT|WO_LE|WO_GT|WO_GE))==0 ) return; if( (pTwo->eOperator & (WO_EQ|WO_LT|WO_LE|WO_GT|WO_GE))==0 ) return; if( (eOp & (WO_EQ|WO_LT|WO_LE))!=eOp && (eOp & (WO_EQ|WO_GT|WO_GE))!=eOp ) return; - assert( pOne->pExpr->pLeft!=0 && pOne->pExpr->pRight!=0 ); - assert( pTwo->pExpr->pLeft!=0 && pTwo->pExpr->pRight!=0 ); - if( sqlite3ExprCompare(0,pOne->pExpr->pLeft, pTwo->pExpr->pLeft, -1) ) return; - if( sqlite3ExprCompare(0,pOne->pExpr->pRight, pTwo->pExpr->pRight,-1) )return; + pA = pOne->pExpr; + pB = pTwo->pExpr; + assert( pA->pLeft!=0 && pA->pRight!=0 ); + assert( pB->pLeft!=0 && pB->pRight!=0 ); + if( sqlite3ExprCompare(0,pA->pLeft, pB->pLeft, -1) ) return; + if( sqlite3ExprCompare(0,pA->pRight, pB->pRight,-1) ) return; + if( ExprHasProperty(pA,EP_Commuted)!=ExprHasProperty(pB,EP_Commuted) ){ + return; + } /* If we reach this point, it means the two subterms can be combined */ if( (eOp & (eOp-1))!=0 ){ if( eOp & (WO_LT|WO_LE) ){ @@ -138958,7 +167496,7 @@ static void whereCombineDisjuncts( } } db = pWC->pWInfo->pParse->db; - pNew = sqlite3ExprDup(db, pOne->pExpr, 0); + pNew = sqlite3ExprDup(db, pA, 0); if( pNew==0 ) return; for(op=TK_EQ; eOp!=(WO_EQ<<(op-TK_EQ)); op++){ assert( opop = op; @@ -139023,10 +167561,10 @@ static void whereCombineDisjuncts( ** WhereTerm.u.pOrInfo->indexable |= the cursor number for table T ** ** A subterm is "indexable" if it is of the form -** "T.C " where C is any column of table T and +** "T.C " where C is any column of table T and ** is one of "=", "<", "<=", ">", ">=", "IS NULL", or "IN". ** A subterm is also indexable if it is an AND of two or more -** subsubterms at least one of which is indexable. Indexable AND +** subsubterms at least one of which is indexable. Indexable AND ** subterms have their eOperator set to WO_AND and they have ** u.pAndInfo set to a dynamically allocated WhereAndTerm object. ** @@ -139108,6 +167646,7 @@ static void exprAnalyzeOrTerm( pOrTerm->u.pAndInfo = pAndInfo; pOrTerm->wtFlags |= TERM_ANDINFO; pOrTerm->eOperator = WO_AND; + pOrTerm->leftCursor = -1; pAndWC = &pAndInfo->wc; memset(pAndWC->aStatic, 0, sizeof(pAndWC->aStatic)); sqlite3WhereClauseInit(pAndWC, pWC->pWInfo); @@ -139117,7 +167656,7 @@ static void exprAnalyzeOrTerm( if( !db->mallocFailed ){ for(j=0, pAndTerm=pAndWC->a; jnTerm; j++, pAndTerm++){ assert( pAndTerm->pExpr ); - if( allowedOp(pAndTerm->pExpr->op) + if( allowedOp(pAndTerm->pExpr->op) || pAndTerm->eOperator==WO_AUX ){ b |= sqlite3WhereGetMask(&pWInfo->sMaskSet, pAndTerm->leftCursor); @@ -139150,11 +167689,10 @@ static void exprAnalyzeOrTerm( ** empty. */ pOrInfo->indexable = indexable; + pTerm->eOperator = WO_OR; + pTerm->leftCursor = -1; if( indexable ){ - pTerm->eOperator = WO_OR; pWC->hasOr = 1; - }else{ - pTerm->eOperator = WO_OR; } /* For a two-way OR, attempt to implementation case 2. @@ -139209,7 +167747,7 @@ static void exprAnalyzeOrTerm( pOrTerm = pOrWc->a; for(i=pOrWc->nTerm-1; i>=0; i--, pOrTerm++){ assert( pOrTerm->eOperator & WO_EQ ); - pOrTerm->wtFlags &= ~TERM_OR_OK; + pOrTerm->wtFlags &= ~TERM_OK; if( pOrTerm->leftCursor==iCursor ){ /* This is the 2-bit case and we are on the second iteration and ** current term is from the first iteration. So skip this term. */ @@ -139220,14 +167758,15 @@ static void exprAnalyzeOrTerm( pOrTerm->leftCursor))==0 ){ /* This term must be of the form t1.a==t2.b where t2 is in the ** chngToIN set but t1 is not. This term will be either preceded - ** or follwed by an inverted copy (t2.b==t1.a). Skip this term + ** or followed by an inverted copy (t2.b==t1.a). Skip this term ** and use its inversion. */ testcase( pOrTerm->wtFlags & TERM_COPIED ); testcase( pOrTerm->wtFlags & TERM_VIRTUAL ); assert( pOrTerm->wtFlags & (TERM_COPIED|TERM_VIRTUAL) ); continue; } - iColumn = pOrTerm->u.leftColumn; + assert( (pOrTerm->eOperator & (WO_OR|WO_AND))==0 ); + iColumn = pOrTerm->u.x.leftColumn; iCursor = pOrTerm->leftCursor; pLeft = pOrTerm->pExpr->pLeft; break; @@ -139247,9 +167786,10 @@ static void exprAnalyzeOrTerm( okToChngToIN = 1; for(; i>=0 && okToChngToIN; i--, pOrTerm++){ assert( pOrTerm->eOperator & WO_EQ ); + assert( (pOrTerm->eOperator & (WO_OR|WO_AND))==0 ); if( pOrTerm->leftCursor!=iCursor ){ - pOrTerm->wtFlags &= ~TERM_OR_OK; - }else if( pOrTerm->u.leftColumn!=iColumn || (iColumn==XN_EXPR + pOrTerm->wtFlags &= ~TERM_OK; + }else if( pOrTerm->u.x.leftColumn!=iColumn || (iColumn==XN_EXPR && sqlite3ExprCompare(pParse, pOrTerm->pExpr->pLeft, pLeft, -1) )){ okToChngToIN = 0; @@ -139264,14 +167804,14 @@ static void exprAnalyzeOrTerm( if( affRight!=0 && affRight!=affLeft ){ okToChngToIN = 0; }else{ - pOrTerm->wtFlags |= TERM_OR_OK; + pOrTerm->wtFlags |= TERM_OK; } } } } /* At this point, okToChngToIN is true if original pTerm satisfies - ** case 1. In that case, construct a new virtual term that is + ** case 1. In that case, construct a new virtual term that is ** pTerm converted into an IN operator. */ if( okToChngToIN ){ @@ -139281,10 +167821,11 @@ static void exprAnalyzeOrTerm( Expr *pNew; /* The complete IN operator */ for(i=pOrWc->nTerm-1, pOrTerm=pOrWc->a; i>=0; i--, pOrTerm++){ - if( (pOrTerm->wtFlags & TERM_OR_OK)==0 ) continue; + if( (pOrTerm->wtFlags & TERM_OK)==0 ) continue; assert( pOrTerm->eOperator & WO_EQ ); + assert( (pOrTerm->eOperator & (WO_OR|WO_AND))==0 ); assert( pOrTerm->leftCursor==iCursor ); - assert( pOrTerm->u.leftColumn==iColumn ); + assert( pOrTerm->u.x.leftColumn==iColumn ); pDup = sqlite3ExprDup(db, pOrTerm->pExpr->pRight, 0); pList = sqlite3ExprListAppend(pWInfo->pParse, pList, pDup); pLeft = pOrTerm->pExpr->pLeft; @@ -139295,12 +167836,12 @@ static void exprAnalyzeOrTerm( if( pNew ){ int idxNew; transferJoinMarkings(pNew, pExpr); - assert( !ExprHasProperty(pNew, EP_xIsSelect) ); + assert( ExprUseXList(pNew) ); pNew->x.pList = pList; idxNew = whereClauseInsert(pWC, pNew, TERM_VIRTUAL|TERM_DYNAMIC); testcase( idxNew==0 ); exprAnalyze(pSrc, pWC, idxNew); - /* pTerm = &pWC->a[idxTerm]; // would be needed if pTerm where used again */ + /* pTerm = &pWC->a[idxTerm]; // would be needed if pTerm where reused */ markTermAsChild(pWC, idxNew, idxTerm); }else{ sqlite3ExprListDelete(db, pList); @@ -139317,30 +167858,38 @@ static void exprAnalyzeOrTerm( ** 1. The SQLITE_Transitive optimization must be enabled ** 2. Must be either an == or an IS operator ** 3. Not originating in the ON clause of an OUTER JOIN -** 4. The affinities of A and B must be compatible -** 5a. Both operands use the same collating sequence OR -** 5b. The overall collating sequence is BINARY +** 4. The operator is not IS or else the query does not contain RIGHT JOIN +** 5. The affinities of A and B must be compatible +** 6. Both operands use the same collating sequence, and they must not +** use explicit COLLATE clauses. ** If this routine returns TRUE, that means that the RHS can be substituted ** for the LHS anyplace else in the WHERE clause where the LHS column occurs. ** This is an optimization. No harm comes from returning 0. But if 1 is ** returned when it should not be, then incorrect answers might result. */ -static int termIsEquivalence(Parse *pParse, Expr *pExpr){ +static int termIsEquivalence(Parse *pParse, Expr *pExpr, SrcList *pSrc){ char aff1, aff2; - CollSeq *pColl; - if( !OptimizationEnabled(pParse->db, SQLITE_Transitive) ) return 0; - if( pExpr->op!=TK_EQ && pExpr->op!=TK_IS ) return 0; - if( ExprHasProperty(pExpr, EP_FromJoin) ) return 0; + if( !OptimizationEnabled(pParse->db, SQLITE_Transitive) ) return 0; /* (1) */ + if( pExpr->op!=TK_EQ && pExpr->op!=TK_IS ) return 0; /* (2) */ + if( ExprHasProperty(pExpr, EP_OuterON|EP_Collate) ) return 0; /* (3) */ + assert( pSrc!=0 ); + if( pExpr->op==TK_IS + && pSrc->nSrc>=2 + && (pSrc->a[0].fg.jointype & JT_LTORJ)!=0 + ){ + return 0; /* (4) */ + } aff1 = sqlite3ExprAffinity(pExpr->pLeft); aff2 = sqlite3ExprAffinity(pExpr->pRight); if( aff1!=aff2 && (!sqlite3IsNumericAffinity(aff1) || !sqlite3IsNumericAffinity(aff2)) ){ - return 0; + return 0; /* (5) */ + } + if( !sqlite3ExprCollSeqMatch(pParse, pExpr->pLeft, pExpr->pRight) ){ + return 0; /* (6) */ } - pColl = sqlite3BinaryCompareCollSeq(pParse, pExpr->pLeft, pExpr->pRight); - if( sqlite3IsBinary(pColl) ) return 1; - return sqlite3ExprCollSeqMatch(pParse, pExpr->pLeft, pExpr->pRight); + return 1; } /* @@ -139360,8 +167909,12 @@ static Bitmask exprSelectUsage(WhereMaskSet *pMaskSet, Select *pS){ if( ALWAYS(pSrc!=0) ){ int i; for(i=0; inSrc; i++){ - mask |= exprSelectUsage(pMaskSet, pSrc->a[i].pSelect); - mask |= sqlite3WhereExprUsage(pMaskSet, pSrc->a[i].pOn); + if( pSrc->a[i].fg.isSubquery ){ + mask |= exprSelectUsage(pMaskSet, pSrc->a[i].u4.pSubq->pSelect); + } + if( pSrc->a[i].fg.isUsing==0 ){ + mask |= sqlite3WhereExprUsage(pMaskSet, pSrc->a[i].u3.pOn); + } if( pSrc->a[i].fg.isTabFunc ){ mask |= sqlite3WhereExprListUsage(pMaskSet, pSrc->a[i].u1.pFuncArg); } @@ -139387,42 +167940,48 @@ static Bitmask exprSelectUsage(WhereMaskSet *pMaskSet, Select *pS){ */ static SQLITE_NOINLINE int exprMightBeIndexed2( SrcList *pFrom, /* The FROM clause */ - Bitmask mPrereq, /* Bitmask of FROM clause terms referenced by pExpr */ int *aiCurCol, /* Write the referenced table cursor and column here */ - Expr *pExpr /* An operand of a comparison operator */ + Expr *pExpr, /* An operand of a comparison operator */ + int j /* Start looking with the j-th pFrom entry */ ){ Index *pIdx; int i; int iCur; - for(i=0; mPrereq>1; i++, mPrereq>>=1){} - iCur = pFrom->a[i].iCursor; - for(pIdx=pFrom->a[i].pTab->pIndex; pIdx; pIdx=pIdx->pNext){ - if( pIdx->aColExpr==0 ) continue; - for(i=0; inKeyCol; i++){ - if( pIdx->aiColumn[i]!=XN_EXPR ) continue; - if( sqlite3ExprCompareSkip(pExpr, pIdx->aColExpr->a[i].pExpr, iCur)==0 ){ - aiCurCol[0] = iCur; - aiCurCol[1] = XN_EXPR; - return 1; + do{ + iCur = pFrom->a[j].iCursor; + for(pIdx=pFrom->a[j].pSTab->pIndex; pIdx; pIdx=pIdx->pNext){ + if( pIdx->aColExpr==0 ) continue; + for(i=0; inKeyCol; i++){ + if( pIdx->aiColumn[i]!=XN_EXPR ) continue; + assert( pIdx->bHasExpr ); + if( sqlite3ExprCompareSkip(pExpr,pIdx->aColExpr->a[i].pExpr,iCur)==0 + && !sqlite3ExprIsConstant(0,pIdx->aColExpr->a[i].pExpr) + ){ + aiCurCol[0] = iCur; + aiCurCol[1] = XN_EXPR; + return 1; + } } } - } + }while( ++j < pFrom->nSrc ); return 0; } static int exprMightBeIndexed( SrcList *pFrom, /* The FROM clause */ - Bitmask mPrereq, /* Bitmask of FROM clause terms referenced by pExpr */ int *aiCurCol, /* Write the referenced table cursor & column here */ Expr *pExpr, /* An operand of a comparison operator */ int op /* The specific comparison operator */ ){ - /* If this expression is a vector to the left or right of a - ** inequality constraint (>, <, >= or <=), perform the processing + int i; + + /* If this expression is a vector to the left or right of a + ** inequality constraint (>, <, >= or <=), perform the processing ** on the first element of the vector. */ assert( TK_GT+1==TK_LE && TK_GT+2==TK_LT && TK_GT+3==TK_GE ); assert( TK_ISop==TK_VECTOR && (op>=TK_GT && ALWAYS(op<=TK_GE)) ){ + assert( ExprUseXList(pExpr) ); pExpr = pExpr->x.pList->a[0].pExpr; } @@ -139431,11 +167990,19 @@ static int exprMightBeIndexed( aiCurCol[1] = pExpr->iColumn; return 1; } - if( mPrereq==0 ) return 0; /* No table references */ - if( (mPrereq&(mPrereq-1))!=0 ) return 0; /* Refs more than one table */ - return exprMightBeIndexed2(pFrom,mPrereq,aiCurCol,pExpr); + + for(i=0; inSrc; i++){ + Index *pIdx; + for(pIdx=pFrom->a[i].pSTab->pIndex; pIdx; pIdx=pIdx->pNext){ + if( pIdx->aColExpr ){ + return exprMightBeIndexed2(pFrom,aiCurCol,pExpr,i); + } + } + } + return 0; } + /* ** The input to this routine is an WhereTerm structure with only the ** "pExpr" field filled in. The job of this routine is to analyze the @@ -139463,8 +168030,8 @@ static void exprAnalyze( WhereTerm *pTerm; /* The term to be analyzed */ WhereMaskSet *pMaskSet; /* Set of table index masks */ Expr *pExpr; /* The expression to be analyzed */ - Bitmask prereqLeft; /* Prerequesites of the pExpr->pLeft */ - Bitmask prereqAll; /* Prerequesites of pExpr */ + Bitmask prereqLeft; /* Prerequisites of the pExpr->pLeft */ + Bitmask prereqAll; /* Prerequisites of pExpr */ Bitmask extraRight = 0; /* Extra dependencies on LEFT JOIN */ Expr *pStr1 = 0; /* RHS of LIKE/GLOB operator */ int isComplete = 0; /* RHS of LIKE/GLOB ends with wildcard */ @@ -139478,36 +168045,56 @@ static void exprAnalyze( if( db->mallocFailed ){ return; } + assert( pWC->nTerm > idxTerm ); pTerm = &pWC->a[idxTerm]; +#ifdef SQLITE_DEBUG + pTerm->iTerm = idxTerm; +#endif pMaskSet = &pWInfo->sMaskSet; pExpr = pTerm->pExpr; + assert( pExpr!=0 ); /* Because malloc() has not failed */ assert( pExpr->op!=TK_AS && pExpr->op!=TK_COLLATE ); + pMaskSet->bVarSelect = 0; prereqLeft = sqlite3WhereExprUsage(pMaskSet, pExpr->pLeft); op = pExpr->op; if( op==TK_IN ){ assert( pExpr->pRight==0 ); if( sqlite3ExprCheckIN(pParse, pExpr) ) return; - if( ExprHasProperty(pExpr, EP_xIsSelect) ){ + if( ExprUseXSelect(pExpr) ){ pTerm->prereqRight = exprSelectUsage(pMaskSet, pExpr->x.pSelect); }else{ pTerm->prereqRight = sqlite3WhereExprListUsage(pMaskSet, pExpr->x.pList); } - }else if( op==TK_ISNULL ){ - pTerm->prereqRight = 0; + prereqAll = prereqLeft | pTerm->prereqRight; }else{ pTerm->prereqRight = sqlite3WhereExprUsage(pMaskSet, pExpr->pRight); + if( pExpr->pLeft==0 + || ExprHasProperty(pExpr, EP_xIsSelect|EP_IfNullRow) + || pExpr->x.pList!=0 + ){ + prereqAll = sqlite3WhereExprUsageNN(pMaskSet, pExpr); + }else{ + prereqAll = prereqLeft | pTerm->prereqRight; + } } - pMaskSet->bVarSelect = 0; - prereqAll = sqlite3WhereExprUsageNN(pMaskSet, pExpr); if( pMaskSet->bVarSelect ) pTerm->wtFlags |= TERM_VARSELECT; - if( ExprHasProperty(pExpr, EP_FromJoin) ){ - Bitmask x = sqlite3WhereGetMask(pMaskSet, pExpr->iRightJoinTable); - prereqAll |= x; - extraRight = x-1; /* ON clause terms may not be used with an index - ** on left table of a LEFT JOIN. Ticket #3015 */ - if( (prereqAll>>1)>=x ){ - sqlite3ErrorMsg(pParse, "ON clause references tables to its right"); - return; + +#ifdef SQLITE_DEBUG + if( prereqAll!=sqlite3WhereExprUsageNN(pMaskSet, pExpr) ){ + printf("\n*** Incorrect prereqAll computed for:\n"); + sqlite3TreeViewExpr(0,pExpr,0); + assert( 0 ); + } +#endif + + if( ExprHasProperty(pExpr, EP_OuterON|EP_InnerON) ){ + Bitmask x = sqlite3WhereGetMask(pMaskSet, pExpr->w.iJoin); + if( ExprHasProperty(pExpr, EP_OuterON) ){ + prereqAll |= x; + extraRight = x-1; /* ON clause terms may not be used with an index + ** on left table of a LEFT JOIN. Ticket #3015 */ + }else if( (prereqAll>>1)>=x ){ + ExprClearProperty(pExpr, EP_InnerON); } } pTerm->prereqAll = prereqAll; @@ -139520,25 +168107,28 @@ static void exprAnalyze( Expr *pRight = sqlite3ExprSkipCollate(pExpr->pRight); u16 opMask = (pTerm->prereqRight & prereqLeft)==0 ? WO_ALL : WO_EQUIV; - if( pTerm->iField>0 ){ + if( pTerm->u.x.iField>0 ){ assert( op==TK_IN ); assert( pLeft->op==TK_VECTOR ); - pLeft = pLeft->x.pList->a[pTerm->iField-1].pExpr; + assert( ExprUseXList(pLeft) ); + pLeft = pLeft->x.pList->a[pTerm->u.x.iField-1].pExpr; } - if( exprMightBeIndexed(pSrc, prereqLeft, aiCurCol, pLeft, op) ){ + if( exprMightBeIndexed(pSrc, aiCurCol, pLeft, op) ){ pTerm->leftCursor = aiCurCol[0]; - pTerm->u.leftColumn = aiCurCol[1]; + assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 ); + pTerm->u.x.leftColumn = aiCurCol[1]; pTerm->eOperator = operatorMask(op) & opMask; } if( op==TK_IS ) pTerm->wtFlags |= TERM_IS; - if( pRight - && exprMightBeIndexed(pSrc, pTerm->prereqRight, aiCurCol, pRight, op) + if( pRight + && exprMightBeIndexed(pSrc, aiCurCol, pRight, op) + && !ExprHasProperty(pRight, EP_FixedCol) ){ WhereTerm *pNew; Expr *pDup; u16 eExtraOp = 0; /* Extra bits for pNew->eOperator */ - assert( pTerm->iField==0 ); + assert( pTerm->u.x.iField==0 ); if( pTerm->leftCursor>=0 ){ int idxNew; pDup = sqlite3ExprDup(db, pExpr, 0); @@ -139553,8 +168143,8 @@ static void exprAnalyze( if( op==TK_IS ) pNew->wtFlags |= TERM_IS; pTerm = &pWC->a[idxTerm]; pTerm->wtFlags |= TERM_COPIED; - - if( termIsEquivalence(pParse, pDup) ){ + assert( pWInfo->pTabList!=0 ); + if( termIsEquivalence(pParse, pDup, pWInfo->pTabList) ){ pTerm->eOperator |= WO_EQUIV; eExtraOp = WO_EQUIV; } @@ -139562,13 +168152,25 @@ static void exprAnalyze( pDup = pExpr; pNew = pTerm; } - exprCommute(pParse, pDup); + pNew->wtFlags |= exprCommute(pParse, pDup); pNew->leftCursor = aiCurCol[0]; - pNew->u.leftColumn = aiCurCol[1]; + assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 ); + pNew->u.x.leftColumn = aiCurCol[1]; testcase( (prereqLeft | extraRight) != prereqLeft ); pNew->prereqRight = prereqLeft | extraRight; pNew->prereqAll = prereqAll; pNew->eOperator = (operatorMask(pDup->op) + eExtraOp) & opMask; + }else + if( op==TK_ISNULL + && !ExprHasProperty(pExpr,EP_OuterON) + && 0==sqlite3ExprCanBeNull(pLeft) + ){ + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + pExpr->op = TK_TRUEFALSE; /* See tag-20230504-1 */ + pExpr->u.zToken = "false"; + ExprSetProperty(pExpr, EP_IsFalse); + pTerm->prereqAll = 0; + pTerm->eOperator = 0; } } @@ -139589,15 +168191,18 @@ static void exprAnalyze( ** BETWEEN term is skipped. */ else if( pExpr->op==TK_BETWEEN && pWC->op==TK_AND ){ - ExprList *pList = pExpr->x.pList; + ExprList *pList; int i; static const u8 ops[] = {TK_GE, TK_LE}; + assert( ExprUseXList(pExpr) ); + pList = pExpr->x.pList; assert( pList!=0 ); assert( pList->nExpr==2 ); + assert( pWC->a[idxTerm].nChild==0 ); for(i=0; i<2; i++){ Expr *pNewExpr; int idxNew; - pNewExpr = sqlite3PExpr(pParse, ops[i], + pNewExpr = sqlite3PExpr(pParse, ops[i], sqlite3ExprDup(db, pExpr->pLeft, 0), sqlite3ExprDup(db, pList->a[i].pExpr, 0)); transferJoinMarkings(pNewExpr, pExpr); @@ -139614,12 +168219,48 @@ static void exprAnalyze( /* Analyze a term that is composed of two or more subterms connected by ** an OR operator. */ - else if( pExpr->op==TK_OR ){ + else if( pExpr->op==TK_OR && !ExprHasProperty(pExpr, EP_Collate) ){ assert( pWC->op==TK_AND ); exprAnalyzeOrTerm(pSrc, pWC, idxTerm); pTerm = &pWC->a[idxTerm]; } #endif /* SQLITE_OMIT_OR_OPTIMIZATION */ + /* The form "x IS NOT NULL" can sometimes be evaluated more efficiently + ** as "x>NULL" if x is not an INTEGER PRIMARY KEY. So construct a + ** virtual term of that form. + ** + ** The virtual term must be tagged with TERM_VNULL. + */ + else if( pExpr->op==TK_NOTNULL ){ + if( pExpr->pLeft->op==TK_COLUMN + && pExpr->pLeft->iColumn>=0 + && !ExprHasProperty(pExpr, EP_OuterON) + ){ + Expr *pNewExpr; + Expr *pLeft = pExpr->pLeft; + int idxNew; + WhereTerm *pNewTerm; + + pNewExpr = sqlite3PExpr(pParse, TK_GT, + sqlite3ExprDup(db, pLeft, 0), + sqlite3ExprAlloc(db, TK_NULL, 0, 0)); + + idxNew = whereClauseInsert(pWC, pNewExpr, + TERM_VIRTUAL|TERM_DYNAMIC|TERM_VNULL); + if( idxNew ){ + pNewTerm = &pWC->a[idxNew]; + pNewTerm->prereqRight = 0; + pNewTerm->leftCursor = pLeft->iTable; + pNewTerm->u.x.leftColumn = pLeft->iColumn; + pNewTerm->eOperator = WO_GT; + markTermAsChild(pWC, idxNew, idxTerm); + pTerm = &pWC->a[idxTerm]; + pTerm->wtFlags |= TERM_COPIED; + pNewTerm->prereqAll = pTerm->prereqAll; + } + } + } + #ifndef SQLITE_OMIT_LIKE_OPTIMIZATION /* Add constraints to reduce the search space on a LIKE or GLOB @@ -139635,7 +168276,8 @@ static void exprAnalyze( ** bound is made all lowercase so that the bounds also work when comparing ** BLOBs. */ - if( pWC->op==TK_AND + else if( pExpr->op==TK_FUNCTION + && pWC->op==TK_AND && isLikeOrGlob(pParse, pExpr, &pStr1, &isComplete, &noCase) ){ Expr *pLeft; /* LHS of LIKE/GLOB operator */ @@ -139647,8 +168289,12 @@ static void exprAnalyze( const char *zCollSeqName; /* Name of collating sequence */ const u16 wtFlags = TERM_LIKEOPT | TERM_VIRTUAL | TERM_DYNAMIC; + assert( ExprUseXList(pExpr) ); pLeft = pExpr->x.pList->a[1].pExpr; pStr2 = sqlite3ExprDup(db, pStr1, 0); + assert( pStr1==0 || !ExprHasProperty(pStr1, EP_IntValue) ); + assert( pStr2==0 || !ExprHasProperty(pStr2, EP_IntValue) ); + /* Convert the lower bound to upper-case and the upper bound to ** lower-case (upper-case is less than lower-case in ASCII) so that @@ -139665,20 +168311,26 @@ static void exprAnalyze( } if( !db->mallocFailed ){ - u8 c, *pC; /* Last character before the first wildcard */ + u8 *pC; /* Last character before the first wildcard */ pC = (u8*)&pStr2->u.zToken[sqlite3Strlen30(pStr2->u.zToken)-1]; - c = *pC; if( noCase ){ /* The point is to increment the last character before the first ** wildcard. But if we increment '@', that will push it into the - ** alphabetic range where case conversions will mess up the + ** alphabetic range where case conversions will mess up the ** inequality. To avoid this, make sure to also run the full ** LIKE on all candidate expressions by clearing the isComplete flag */ - if( c=='A'-1 ) isComplete = 0; - c = sqlite3UpperToLower[c]; + if( *pC=='A'-1 ) isComplete = 0; + *pC = sqlite3UpperToLower[*pC]; + } + + /* Increment the value of the last utf8 character in the prefix. */ + while( *pC==0xBF && pC>(u8*)pStr2->u.zToken ){ + *pC = 0x80; + pC--; } - *pC = c + 1; + assert( *pC!=0xFF ); /* isLikeOrGlob() guarantees this */ + (*pC)++; } zCollSeqName = noCase ? "NOCASE" : sqlite3StrBINARY; pNewExpr1 = sqlite3ExprDup(db, pLeft, 0); @@ -139688,7 +168340,6 @@ static void exprAnalyze( transferJoinMarkings(pNewExpr1, pExpr); idxNew1 = whereClauseInsert(pWC, pNewExpr1, wtFlags); testcase( idxNew1==0 ); - exprAnalyze(pSrc, pWC, idxNew1); pNewExpr2 = sqlite3ExprDup(db, pLeft, 0); pNewExpr2 = sqlite3PExpr(pParse, TK_LT, sqlite3ExprAddCollateString(pParse,pNewExpr2,zCollSeqName), @@ -139696,6 +168347,7 @@ static void exprAnalyze( transferJoinMarkings(pNewExpr2, pExpr); idxNew2 = whereClauseInsert(pWC, pNewExpr2, wtFlags); testcase( idxNew2==0 ); + exprAnalyze(pSrc, pWC, idxNew1); exprAnalyze(pSrc, pWC, idxNew2); pTerm = &pWC->a[idxTerm]; if( isComplete ){ @@ -139705,142 +168357,117 @@ static void exprAnalyze( } #endif /* SQLITE_OMIT_LIKE_OPTIMIZATION */ -#ifndef SQLITE_OMIT_VIRTUALTABLE - /* Add a WO_AUX auxiliary term to the constraint set if the - ** current expression is of the form "column OP expr" where OP - ** is an operator that gets passed into virtual tables but which is - ** not normally optimized for ordinary tables. In other words, OP - ** is one of MATCH, LIKE, GLOB, REGEXP, !=, IS, IS NOT, or NOT NULL. - ** This information is used by the xBestIndex methods of - ** virtual tables. The native query optimizer does not attempt - ** to do anything with MATCH functions. - */ - if( pWC->op==TK_AND ){ - Expr *pRight = 0, *pLeft = 0; - int res = isAuxiliaryVtabOperator(db, pExpr, &eOp2, &pLeft, &pRight); - while( res-- > 0 ){ - int idxNew; - WhereTerm *pNewTerm; - Bitmask prereqColumn, prereqExpr; - - prereqExpr = sqlite3WhereExprUsage(pMaskSet, pRight); - prereqColumn = sqlite3WhereExprUsage(pMaskSet, pLeft); - if( (prereqExpr & prereqColumn)==0 ){ - Expr *pNewExpr; - pNewExpr = sqlite3PExpr(pParse, TK_MATCH, - 0, sqlite3ExprDup(db, pRight, 0)); - if( ExprHasProperty(pExpr, EP_FromJoin) && pNewExpr ){ - ExprSetProperty(pNewExpr, EP_FromJoin); - } - idxNew = whereClauseInsert(pWC, pNewExpr, TERM_VIRTUAL|TERM_DYNAMIC); - testcase( idxNew==0 ); - pNewTerm = &pWC->a[idxNew]; - pNewTerm->prereqRight = prereqExpr; - pNewTerm->leftCursor = pLeft->iTable; - pNewTerm->u.leftColumn = pLeft->iColumn; - pNewTerm->eOperator = WO_AUX; - pNewTerm->eMatchOp = eOp2; - markTermAsChild(pWC, idxNew, idxTerm); - pTerm = &pWC->a[idxTerm]; - pTerm->wtFlags |= TERM_COPIED; - pNewTerm->prereqAll = pTerm->prereqAll; - } - SWAP(Expr*, pLeft, pRight); - } - } -#endif /* SQLITE_OMIT_VIRTUALTABLE */ - /* If there is a vector == or IS term - e.g. "(a, b) == (?, ?)" - create ** new terms for each component comparison - "a = ?" and "b = ?". The ** new terms completely replace the original vector comparison, which is ** no longer used. ** ** This is only required if at least one side of the comparison operation - ** is not a sub-select. */ - if( pWC->op==TK_AND - && (pExpr->op==TK_EQ || pExpr->op==TK_IS) - && (nLeft = sqlite3ExprVectorSize(pExpr->pLeft))>1 - && sqlite3ExprVectorSize(pExpr->pRight)==nLeft - && ( (pExpr->pLeft->flags & EP_xIsSelect)==0 - || (pExpr->pRight->flags & EP_xIsSelect)==0) + ** is not a sub-select. + ** + ** tag-20220128a + */ + if( (pExpr->op==TK_EQ || pExpr->op==TK_IS) + && (nLeft = sqlite3ExprVectorSize(pExpr->pLeft))>1 + && sqlite3ExprVectorSize(pExpr->pRight)==nLeft + && ( (pExpr->pLeft->flags & EP_xIsSelect)==0 + || (pExpr->pRight->flags & EP_xIsSelect)==0) + && pWC->op==TK_AND ){ int i; for(i=0; ipLeft, i); - Expr *pRight = sqlite3ExprForVectorField(pParse, pExpr->pRight, i); + Expr *pLeft = sqlite3ExprForVectorField(pParse, pExpr->pLeft, i, nLeft); + Expr *pRight = sqlite3ExprForVectorField(pParse, pExpr->pRight, i, nLeft); pNew = sqlite3PExpr(pParse, pExpr->op, pLeft, pRight); transferJoinMarkings(pNew, pExpr); - idxNew = whereClauseInsert(pWC, pNew, TERM_DYNAMIC); + idxNew = whereClauseInsert(pWC, pNew, TERM_DYNAMIC|TERM_SLICE); exprAnalyze(pSrc, pWC, idxNew); } pTerm = &pWC->a[idxTerm]; pTerm->wtFlags |= TERM_CODED|TERM_VIRTUAL; /* Disable the original */ - pTerm->eOperator = 0; + pTerm->eOperator = WO_ROWVAL; } /* If there is a vector IN term - e.g. "(a, b) IN (SELECT ...)" - create ** a virtual term for each vector component. The expression object - ** used by each such virtual term is pExpr (the full vector IN(...) - ** expression). The WhereTerm.iField variable identifies the index within + ** used by each such virtual term is pExpr (the full vector IN(...) + ** expression). The WhereTerm.u.x.iField variable identifies the index within ** the vector on the LHS that the virtual term represents. ** - ** This only works if the RHS is a simple SELECT, not a compound + ** This only works if the RHS is a simple SELECT (not a compound) that does + ** not use window functions. */ - if( pWC->op==TK_AND && pExpr->op==TK_IN && pTerm->iField==0 + else if( pExpr->op==TK_IN + && pTerm->u.x.iField==0 && pExpr->pLeft->op==TK_VECTOR - && pExpr->x.pSelect->pPrior==0 + && ALWAYS( ExprUseXSelect(pExpr) ) + && (pExpr->x.pSelect->pPrior==0 || (pExpr->x.pSelect->selFlags & SF_Values)) +#ifndef SQLITE_OMIT_WINDOWFUNC + && pExpr->x.pSelect->pWin==0 +#endif + && pWC->op==TK_AND + && pExpr->x.pSelect->pEList->nExpr <= UMXV(pTerm->nChild) + /* ^-- See bug 2026-06-04T10:00:49Z */ ){ int i; + assert( pTerm->nChild==0 ); for(i=0; ipLeft); i++){ int idxNew; - idxNew = whereClauseInsert(pWC, pExpr, TERM_VIRTUAL); - pWC->a[idxNew].iField = i+1; + idxNew = whereClauseInsert(pWC, pExpr, TERM_VIRTUAL|TERM_SLICE); + pWC->a[idxNew].u.x.iField = i+1; exprAnalyze(pSrc, pWC, idxNew); markTermAsChild(pWC, idxNew, idxTerm); } } -#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 - /* When sqlite_stat3 histogram data is available an operator of the - ** form "x IS NOT NULL" can sometimes be evaluated more efficiently - ** as "x>NULL" if x is not an INTEGER PRIMARY KEY. So construct a - ** virtual term of that form. - ** - ** Note that the virtual term must be tagged with TERM_VNULL. +#ifndef SQLITE_OMIT_VIRTUALTABLE + /* Add a WO_AUX auxiliary term to the constraint set if the + ** current expression is of the form "column OP expr" where OP + ** is an operator that gets passed into virtual tables but which is + ** not normally optimized for ordinary tables. In other words, OP + ** is one of MATCH, LIKE, GLOB, REGEXP, !=, IS, IS NOT, or NOT NULL. + ** This information is used by the xBestIndex methods of + ** virtual tables. The native query optimizer does not attempt + ** to do anything with MATCH functions. */ - if( pExpr->op==TK_NOTNULL - && pExpr->pLeft->op==TK_COLUMN - && pExpr->pLeft->iColumn>=0 - && !ExprHasProperty(pExpr, EP_FromJoin) - && OptimizationEnabled(db, SQLITE_Stat34) - ){ - Expr *pNewExpr; - Expr *pLeft = pExpr->pLeft; - int idxNew; - WhereTerm *pNewTerm; - - pNewExpr = sqlite3PExpr(pParse, TK_GT, - sqlite3ExprDup(db, pLeft, 0), - sqlite3ExprAlloc(db, TK_NULL, 0, 0)); - - idxNew = whereClauseInsert(pWC, pNewExpr, - TERM_VIRTUAL|TERM_DYNAMIC|TERM_VNULL); - if( idxNew ){ - pNewTerm = &pWC->a[idxNew]; - pNewTerm->prereqRight = 0; - pNewTerm->leftCursor = pLeft->iTable; - pNewTerm->u.leftColumn = pLeft->iColumn; - pNewTerm->eOperator = WO_GT; - markTermAsChild(pWC, idxNew, idxTerm); - pTerm = &pWC->a[idxTerm]; - pTerm->wtFlags |= TERM_COPIED; - pNewTerm->prereqAll = pTerm->prereqAll; + else if( pWC->op==TK_AND ){ + Expr *pRight = 0, *pLeft = 0; + int res = isAuxiliaryVtabOperator(db, pExpr, &eOp2, &pLeft, &pRight); + while( res-- > 0 ){ + int idxNew; + WhereTerm *pNewTerm; + Bitmask prereqColumn, prereqExpr; + + prereqExpr = sqlite3WhereExprUsage(pMaskSet, pRight); + prereqColumn = sqlite3WhereExprUsage(pMaskSet, pLeft); + if( (prereqExpr & prereqColumn)==0 ){ + Expr *pNewExpr; + pNewExpr = sqlite3PExpr(pParse, TK_MATCH, + 0, sqlite3ExprDup(db, pRight, 0)); + if( ExprHasProperty(pExpr, EP_OuterON) && pNewExpr ){ + ExprSetProperty(pNewExpr, EP_OuterON); + pNewExpr->w.iJoin = pExpr->w.iJoin; + } + idxNew = whereClauseInsert(pWC, pNewExpr, TERM_VIRTUAL|TERM_DYNAMIC); + testcase( idxNew==0 ); + pNewTerm = &pWC->a[idxNew]; + pNewTerm->prereqRight = prereqExpr | extraRight; + pNewTerm->leftCursor = pLeft->iTable; + pNewTerm->u.x.leftColumn = pLeft->iColumn; + pNewTerm->eOperator = WO_AUX; + pNewTerm->eMatchOp = eOp2; + markTermAsChild(pWC, idxNew, idxTerm); + pTerm = &pWC->a[idxTerm]; + pTerm->wtFlags |= TERM_COPIED; + pNewTerm->prereqAll = pTerm->prereqAll; + } + SWAP(Expr*, pLeft, pRight); } } -#endif /* SQLITE_ENABLE_STAT3_OR_STAT4 */ +#endif /* SQLITE_OMIT_VIRTUALTABLE */ /* Prevent ON clause terms of a LEFT JOIN from being used to drive ** an index for tables to the left of the join. @@ -139873,8 +168500,9 @@ static void exprAnalyze( ** all terms of the WHERE clause. */ SQLITE_PRIVATE void sqlite3WhereSplit(WhereClause *pWC, Expr *pExpr, u8 op){ - Expr *pE2 = sqlite3ExprSkipCollate(pExpr); + Expr *pE2 = sqlite3ExprSkipCollateAndLikely(pExpr); pWC->op = op; + assert( pE2!=0 || pExpr==0 ); if( pE2==0 ) return; if( pE2->op!=op ){ whereClauseInsert(pWC, pExpr, 0); @@ -139884,6 +168512,135 @@ SQLITE_PRIVATE void sqlite3WhereSplit(WhereClause *pWC, Expr *pExpr, u8 op){ } } +/* +** Add either a LIMIT (if eMatchOp==SQLITE_INDEX_CONSTRAINT_LIMIT) or +** OFFSET (if eMatchOp==SQLITE_INDEX_CONSTRAINT_OFFSET) term to the +** where-clause passed as the first argument. The value for the term +** is found in register iReg. +** +** In the common case where the value is a simple integer +** (example: "LIMIT 5 OFFSET 10") then the expression codes as a +** TK_INTEGER so that it will be available to sqlite3_vtab_rhs_value(). +** If not, then it codes as a TK_REGISTER expression. +*/ +static void whereAddLimitExpr( + WhereClause *pWC, /* Add the constraint to this WHERE clause */ + int iReg, /* Register that will hold value of the limit/offset */ + Expr *pExpr, /* Expression that defines the limit/offset */ + int iCsr, /* Cursor to which the constraint applies */ + int eMatchOp /* SQLITE_INDEX_CONSTRAINT_LIMIT or _OFFSET */ +){ + Parse *pParse = pWC->pWInfo->pParse; + sqlite3 *db = pParse->db; + Expr *pNew; + int iVal = 0; + + if( sqlite3ExprIsInteger(pExpr, &iVal, pParse) && iVal>=0 ){ + Expr *pVal = sqlite3ExprInt32(db, iVal); + if( pVal==0 ) return; + pNew = sqlite3PExpr(pParse, TK_MATCH, 0, pVal); + }else{ + Expr *pVal = sqlite3ExprAlloc(db, TK_REGISTER, 0, 0); + if( pVal==0 ) return; + pVal->iTable = iReg; + pNew = sqlite3PExpr(pParse, TK_MATCH, 0, pVal); + } + if( pNew ){ + WhereTerm *pTerm; + int idx; + idx = whereClauseInsert(pWC, pNew, TERM_DYNAMIC|TERM_VIRTUAL); + pTerm = &pWC->a[idx]; + pTerm->leftCursor = iCsr; + pTerm->eOperator = WO_AUX; + pTerm->eMatchOp = eMatchOp; + } +} + +/* +** Possibly add terms corresponding to the LIMIT and OFFSET clauses of the +** SELECT statement passed as the second argument. These terms are only +** added if: +** +** 1. The SELECT statement has a LIMIT clause, and +** 2. The SELECT statement is not an aggregate or DISTINCT query, and +** 3. The SELECT statement has exactly one object in its FROM clause, and +** that object is a virtual table, and +** 4. There are no terms in the WHERE clause that will not be passed +** to the virtual table xBestIndex method. +** 5. The ORDER BY clause, if any, will be made available to the xBestIndex +** method. +** +** LIMIT and OFFSET terms are ignored by most of the planner code. They +** exist only so that they may be passed to the xBestIndex method of the +** single virtual table in the FROM clause of the SELECT. +*/ +SQLITE_PRIVATE void SQLITE_NOINLINE sqlite3WhereAddLimit(WhereClause *pWC, Select *p){ + assert( p!=0 && p->pLimit!=0 ); /* 1 -- checked by caller */ + if( p->pGroupBy==0 + && (p->selFlags & (SF_Distinct|SF_Aggregate))==0 /* 2 */ + && (p->pSrc->nSrc==1 && IsVirtual(p->pSrc->a[0].pSTab)) /* 3 */ + ){ + ExprList *pOrderBy = p->pOrderBy; + int iCsr = p->pSrc->a[0].iCursor; + int ii; + + /* Check condition (4). Return early if it is not met. */ + for(ii=0; iinTerm; ii++){ + if( pWC->a[ii].wtFlags & TERM_CODED ){ + /* This term is a vector operation that has been decomposed into + ** other, subsequent terms. It can be ignored. See tag-20220128a */ + assert( pWC->a[ii].wtFlags & TERM_VIRTUAL ); + assert( pWC->a[ii].eOperator==WO_ROWVAL ); + continue; + } + if( pWC->a[ii].nChild ){ + /* If this term has child terms, then they are also part of the + ** pWC->a[] array. So this term can be ignored, as a LIMIT clause + ** will only be added if each of the child terms passes the + ** (leftCursor==iCsr) test below. */ + continue; + } + if( pWC->a[ii].leftCursor==iCsr && pWC->a[ii].prereqRight==0 ) continue; + + /* If this term has a parent with exactly one child, and the parent will + ** be passed through to xBestIndex, then this term can be ignored. */ + if( pWC->a[ii].iParent>=0 ){ + WhereTerm *pParent = &pWC->a[ pWC->a[ii].iParent ]; + if( pParent->leftCursor==iCsr + && pParent->prereqRight==0 + && pParent->nChild==1 + ){ + continue; + } + } + + /* This term will not be passed through. Do not add a LIMIT clause. */ + return; + } + + /* Check condition (5). Return early if it is not met. */ + if( pOrderBy ){ + for(ii=0; iinExpr; ii++){ + Expr *pExpr = pOrderBy->a[ii].pExpr; + if( pExpr->op!=TK_COLUMN ) return; + if( pExpr->iTable!=iCsr ) return; + if( pOrderBy->a[ii].fg.sortFlags & KEYINFO_ORDER_BIGNULL ) return; + } + } + + /* All conditions are met. Add the terms to the where-clause object. */ + assert( p->pLimit->op==TK_LIMIT ); + if( p->iOffset!=0 && (p->selFlags & SF_Compound)==0 ){ + whereAddLimitExpr(pWC, p->iOffset, p->pLimit->pRight, + iCsr, SQLITE_INDEX_CONSTRAINT_OFFSET); + } + if( p->iOffset==0 || (p->selFlags & SF_Compound)==0 ){ + whereAddLimitExpr(pWC, p->iLimit, p->pLimit->pLeft, + iCsr, SQLITE_INDEX_CONSTRAINT_LIMIT); + } + } +} + /* ** Initialize a preallocated WhereClause structure. */ @@ -139895,6 +168652,7 @@ SQLITE_PRIVATE void sqlite3WhereClauseInit( pWC->hasOr = 0; pWC->pOuter = 0; pWC->nTerm = 0; + pWC->nBase = 0; pWC->nSlot = ArraySize(pWC->aStatic); pWC->a = pWC->aStatic; } @@ -139905,22 +168663,36 @@ SQLITE_PRIVATE void sqlite3WhereClauseInit( ** sqlite3WhereClauseInit(). */ SQLITE_PRIVATE void sqlite3WhereClauseClear(WhereClause *pWC){ - int i; - WhereTerm *a; sqlite3 *db = pWC->pWInfo->pParse->db; - for(i=pWC->nTerm-1, a=pWC->a; i>=0; i--, a++){ - if( a->wtFlags & TERM_DYNAMIC ){ - sqlite3ExprDelete(db, a->pExpr); + assert( pWC->nTerm>=pWC->nBase ); + if( pWC->nTerm>0 ){ + WhereTerm *a = pWC->a; + WhereTerm *aLast = &pWC->a[pWC->nTerm-1]; +#ifdef SQLITE_DEBUG + int i; + /* Verify that every term past pWC->nBase is virtual */ + for(i=pWC->nBase; inTerm; i++){ + assert( (pWC->a[i].wtFlags & TERM_VIRTUAL)!=0 ); } - if( a->wtFlags & TERM_ORINFO ){ - whereOrInfoDelete(db, a->u.pOrInfo); - }else if( a->wtFlags & TERM_ANDINFO ){ - whereAndInfoDelete(db, a->u.pAndInfo); +#endif + while(1){ + assert( a->eMatchOp==0 || a->eOperator==WO_AUX ); + if( a->wtFlags & TERM_DYNAMIC ){ + sqlite3ExprDelete(db, a->pExpr); + } + if( a->wtFlags & (TERM_ORINFO|TERM_ANDINFO) ){ + if( a->wtFlags & TERM_ORINFO ){ + assert( (a->wtFlags & TERM_ANDINFO)==0 ); + whereOrInfoDelete(db, a->u.pOrInfo); + }else{ + assert( (a->wtFlags & TERM_ANDINFO)!=0 ); + whereAndInfoDelete(db, a->u.pAndInfo); + } + } + if( a==aLast ) break; + a++; } } - if( pWC->a!=pWC->aStatic ){ - sqlite3DbFree(db, pWC->a); - } } @@ -139928,34 +168700,68 @@ SQLITE_PRIVATE void sqlite3WhereClauseClear(WhereClause *pWC){ ** These routines walk (recursively) an expression tree and generate ** a bitmask indicating which tables are used in that expression ** tree. +** +** sqlite3WhereExprUsage(MaskSet, Expr) -> +** +** Return a Bitmask of all tables referenced by Expr. Expr can be +** be NULL, in which case 0 is returned. +** +** sqlite3WhereExprUsageNN(MaskSet, Expr) -> +** +** Same as sqlite3WhereExprUsage() except that Expr must not be +** NULL. The "NN" suffix on the name stands for "Not Null". +** +** sqlite3WhereExprListUsage(MaskSet, ExprList) -> +** +** Return a Bitmask of all tables referenced by every expression +** in the expression list ExprList. ExprList can be NULL, in which +** case 0 is returned. +** +** sqlite3WhereExprUsageFull(MaskSet, ExprList) -> +** +** Internal use only. Called only by sqlite3WhereExprUsageNN() for +** complex expressions that require pushing register values onto +** the stack. Many calls to sqlite3WhereExprUsageNN() do not need +** the more complex analysis done by this routine. Hence, the +** computations done by this routine are broken out into a separate +** "no-inline" function to avoid the stack push overhead in the +** common case where it is not needed. */ -SQLITE_PRIVATE Bitmask sqlite3WhereExprUsageNN(WhereMaskSet *pMaskSet, Expr *p){ +static SQLITE_NOINLINE Bitmask sqlite3WhereExprUsageFull( + WhereMaskSet *pMaskSet, + Expr *p +){ Bitmask mask; - if( p->op==TK_COLUMN && !ExprHasProperty(p, EP_FixedCol) ){ - return sqlite3WhereGetMask(pMaskSet, p->iTable); - }else if( ExprHasProperty(p, EP_TokenOnly|EP_Leaf) ){ - assert( p->op!=TK_IF_NULL_ROW ); - return 0; - } mask = (p->op==TK_IF_NULL_ROW) ? sqlite3WhereGetMask(pMaskSet, p->iTable) : 0; if( p->pLeft ) mask |= sqlite3WhereExprUsageNN(pMaskSet, p->pLeft); if( p->pRight ){ mask |= sqlite3WhereExprUsageNN(pMaskSet, p->pRight); assert( p->x.pList==0 ); - }else if( ExprHasProperty(p, EP_xIsSelect) ){ + }else if( ExprUseXSelect(p) ){ if( ExprHasProperty(p, EP_VarSelect) ) pMaskSet->bVarSelect = 1; mask |= exprSelectUsage(pMaskSet, p->x.pSelect); }else if( p->x.pList ){ mask |= sqlite3WhereExprListUsage(pMaskSet, p->x.pList); } #ifndef SQLITE_OMIT_WINDOWFUNC - if( p->op==TK_FUNCTION && p->y.pWin ){ + if( (p->op==TK_FUNCTION || p->op==TK_AGG_FUNCTION) && ExprUseYWin(p) ){ + assert( p->y.pWin!=0 ); mask |= sqlite3WhereExprListUsage(pMaskSet, p->y.pWin->pPartition); mask |= sqlite3WhereExprListUsage(pMaskSet, p->y.pWin->pOrderBy); + mask |= sqlite3WhereExprUsage(pMaskSet, p->y.pWin->pFilter); } #endif return mask; } +SQLITE_PRIVATE Bitmask sqlite3WhereExprUsageNN(WhereMaskSet *pMaskSet, Expr *p){ + if( p->op==TK_COLUMN && !ExprHasProperty(p, EP_FixedCol) ){ + return sqlite3WhereGetMask(pMaskSet, p->iTable); + }else if( ExprHasProperty(p, EP_TokenOnly|EP_Leaf) ){ + assert( p->op!=TK_IF_NULL_ROW ); + return 0; + } + return sqlite3WhereExprUsageFull(pMaskSet, p); +} SQLITE_PRIVATE Bitmask sqlite3WhereExprUsage(WhereMaskSet *pMaskSet, Expr *p){ return p ? sqlite3WhereExprUsageNN(pMaskSet,p) : 0; } @@ -139972,7 +168778,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereExprListUsage(WhereMaskSet *pMaskSet, ExprLis /* -** Call exprAnalyze on all terms in a WHERE clause. +** Call exprAnalyze on all terms in a WHERE clause. ** ** Note that exprAnalyze() might add new virtual terms onto the ** end of the WHERE clause. We do not want to analyze these new @@ -139991,14 +168797,14 @@ SQLITE_PRIVATE void sqlite3WhereExprAnalyze( /* ** For table-valued-functions, transform the function arguments into -** new WHERE clause terms. +** new WHERE clause terms. ** ** Each function argument translates into an equality constraint against ** a HIDDEN column in the table. */ SQLITE_PRIVATE void sqlite3WhereTabFuncArgs( Parse *pParse, /* Parsing context */ - struct SrcList_item *pItem, /* The FROM clause term to process */ + SrcItem *pItem, /* The FROM clause term to process */ WhereClause *pWC /* Xfer function arguments to here */ ){ Table *pTab; @@ -140007,12 +168813,13 @@ SQLITE_PRIVATE void sqlite3WhereTabFuncArgs( Expr *pColRef; Expr *pTerm; if( pItem->fg.isTabFunc==0 ) return; - pTab = pItem->pTab; + pTab = pItem->pSTab; assert( pTab!=0 ); pArgs = pItem->u1.pFuncArg; if( pArgs==0 ) return; for(j=k=0; jnExpr; j++){ Expr *pRhs; + u32 joinType; while( knCol && (pTab->aCol[k].colFlags & COLFLAG_HIDDEN)==0 ){k++;} if( k>=pTab->nCol ){ sqlite3ErrorMsg(pParse, "too many arguments on %s() - max %d", @@ -140023,10 +168830,21 @@ SQLITE_PRIVATE void sqlite3WhereTabFuncArgs( if( pColRef==0 ) return; pColRef->iTable = pItem->iCursor; pColRef->iColumn = k++; + assert( ExprUseYTab(pColRef) ); pColRef->y.pTab = pTab; - pRhs = sqlite3PExpr(pParse, TK_UPLUS, + pItem->colUsed |= sqlite3ExprColUsed(pColRef); + pRhs = sqlite3PExpr(pParse, TK_UPLUS, sqlite3ExprDup(pParse->db, pArgs->a[j].pExpr, 0), 0); pTerm = sqlite3PExpr(pParse, TK_EQ, pColRef, pRhs); + if( pItem->fg.jointype & (JT_LEFT|JT_RIGHT) ){ + testcase( pItem->fg.jointype & JT_LEFT ); /* testtag-20230227a */ + testcase( pItem->fg.jointype & JT_RIGHT ); /* testtag-20230227b */ + joinType = EP_OuterON; + }else{ + testcase( pItem->fg.jointype & JT_LTORJ ); /* testtag-20230227c */ + joinType = EP_InnerON; + } + sqlite3SetJoinExpr(pTerm, pItem->iCursor, joinType); whereClauseInsert(pWC, pTerm, TERM_DYNAMIC); } } @@ -140065,19 +168883,24 @@ SQLITE_PRIVATE void sqlite3WhereTabFuncArgs( */ typedef struct HiddenIndexInfo HiddenIndexInfo; struct HiddenIndexInfo { - WhereClause *pWC; /* The Where clause being analyzed */ - Parse *pParse; /* The parsing context */ + WhereClause *pWC; /* The Where clause being analyzed */ + Parse *pParse; /* The parsing context */ + int eDistinct; /* Value to return from sqlite3_vtab_distinct() */ + u32 mIn; /* Mask of terms that are IN (...) */ + u32 mHandleIn; /* Terms that vtab will handle as IN (...) */ + sqlite3_value *aRhs[FLEXARRAY]; /* RHS values for constraints. MUST BE LAST + ** Extra space is allocated to hold up + ** to nTerm such values */ }; +/* Size (in bytes) of a HiddenIndeInfo object sufficient to hold as +** many as N constraints */ +#define SZ_HIDDENINDEXINFO(N) \ + (offsetof(HiddenIndexInfo,aRhs) + (N)*sizeof(sqlite3_value*)) + /* Forward declaration of methods */ static int whereLoopResize(sqlite3*, WhereLoop*, int); -/* Test variable that can be set to enable WHERE tracing */ -#if defined(SQLITE_TEST) || defined(SQLITE_DEBUG) -/***/ int sqlite3WhereTrace = 0; -#endif - - /* ** Return the estimated number of output rows from a WHERE clause */ @@ -140094,11 +168917,15 @@ SQLITE_PRIVATE int sqlite3WhereIsDistinct(WhereInfo *pWInfo){ } /* -** Return TRUE if the WHERE clause returns rows in ORDER BY order. -** Return FALSE if the output needs to be sorted. +** Return the number of ORDER BY terms that are satisfied by the +** WHERE clause. A return of 0 means that the output must be +** completely sorted. A return equal to the number of ORDER BY +** terms means that no sorting is needed at all. A return that +** is positive but less than the number of ORDER BY terms means that +** block sorting is required. */ SQLITE_PRIVATE int sqlite3WhereIsOrdered(WhereInfo *pWInfo){ - return pWInfo->nOBSat; + return pWInfo->nOBSat<0 ? 0 : pWInfo->nOBSat; } /* @@ -140119,7 +168946,7 @@ SQLITE_PRIVATE int sqlite3WhereIsOrdered(WhereInfo *pWInfo){ ** be the continuation for the inner-most loop. ** ** It is always safe for this routine to return the continuation of the -** inner-most loop, in the sense that a correct answer will result. +** inner-most loop, in the sense that a correct answer will result. ** Returning the continuation the second inner loop is an optimization ** that might make the code run a little faster, but should not change ** the final answer. @@ -140127,13 +168954,39 @@ SQLITE_PRIVATE int sqlite3WhereIsOrdered(WhereInfo *pWInfo){ SQLITE_PRIVATE int sqlite3WhereOrderByLimitOptLabel(WhereInfo *pWInfo){ WhereLevel *pInner; if( !pWInfo->bOrderedInnerLoop ){ - /* The ORDER BY LIMIT optimization does not apply. Jump to the + /* The ORDER BY LIMIT optimization does not apply. Jump to the ** continuation of the inner-most loop. */ return pWInfo->iContinue; } pInner = &pWInfo->a[pWInfo->nLevel-1]; assert( pInner->addrNxt!=0 ); - return pInner->addrNxt; + return pInner->pRJ ? pWInfo->iContinue : pInner->addrNxt; +} + +/* +** While generating code for the min/max optimization, after handling +** the aggregate-step call to min() or max(), check to see if any +** additional looping is required. If the output order is such that +** we are certain that the correct answer has already been found, then +** code an OP_Goto to by pass subsequent processing. +** +** Any extra OP_Goto that is coded here is an optimization. The +** correct answer should be obtained regardless. This OP_Goto just +** makes the answer appear faster. +*/ +SQLITE_PRIVATE void sqlite3WhereMinMaxOptEarlyOut(Vdbe *v, WhereInfo *pWInfo){ + WhereLevel *pInner; + int i; + if( !pWInfo->bOrderedInnerLoop ) return; + if( pWInfo->nOBSat==0 ) return; + for(i=pWInfo->nLevel-1; i>=0; i--){ + pInner = &pWInfo->a[i]; + if( (pInner->pWLoop->wsFlags & WHERE_COLUMN_IN)!=0 ){ + sqlite3VdbeGoto(v, pInner->addrNxt); + return; + } + } + sqlite3VdbeGoto(v, pWInfo->iBreak); } /* @@ -140155,10 +169008,10 @@ SQLITE_PRIVATE int sqlite3WhereBreakLabel(WhereInfo *pWInfo){ /* ** Return ONEPASS_OFF (0) if an UPDATE or DELETE statement is unable to -** operate directly on the rowis returned by a WHERE clause. Return +** operate directly on the rowids returned by a WHERE clause. Return ** ONEPASS_SINGLE (1) if the statement can operation directly because only ** a single row is to be changed. Return ONEPASS_MULTI (2) if the one-pass -** optimization can be used on multiple +** optimization can be used on multiple ** ** If the ONEPASS optimization is used (if this routine returns true) ** then also write the indices of open cursors used by ONEPASS @@ -140182,6 +169035,14 @@ SQLITE_PRIVATE int sqlite3WhereOkOnePass(WhereInfo *pWInfo, int *aiCur){ return pWInfo->eOnePass; } +/* +** Return TRUE if the WHERE loop uses the OP_DeferredSeek opcode to move +** the data cursor to the row selected by the index cursor. +*/ +SQLITE_PRIVATE int sqlite3WhereUsesDeferredSeek(WhereInfo *pWInfo){ + return pWInfo->bDeferredSeek; +} + /* ** Move the content of pSrc into pDest */ @@ -140237,7 +169098,12 @@ static int whereOrInsert( SQLITE_PRIVATE Bitmask sqlite3WhereGetMask(WhereMaskSet *pMaskSet, int iCursor){ int i; assert( pMaskSet->n<=(int)sizeof(Bitmask)*8 ); - for(i=0; in; i++){ + assert( pMaskSet->n>0 || pMaskSet->ix[0]<0 ); + assert( iCursor>=-1 ); + if( pMaskSet->ix[0]==iCursor ){ + return 1; + } + for(i=1; in; i++){ if( pMaskSet->ix[i]==iCursor ){ return MASKBIT(i); } @@ -140245,6 +169111,30 @@ SQLITE_PRIVATE Bitmask sqlite3WhereGetMask(WhereMaskSet *pMaskSet, int iCursor){ return 0; } +/* Allocate memory that is automatically freed when pWInfo is freed. +*/ +SQLITE_PRIVATE void *sqlite3WhereMalloc(WhereInfo *pWInfo, u64 nByte){ + WhereMemBlock *pBlock; + pBlock = sqlite3DbMallocRawNN(pWInfo->pParse->db, nByte+sizeof(*pBlock)); + if( pBlock ){ + pBlock->pNext = pWInfo->pMemToFree; + pBlock->sz = nByte; + pWInfo->pMemToFree = pBlock; + pBlock++; + } + return (void*)pBlock; +} +SQLITE_PRIVATE void *sqlite3WhereRealloc(WhereInfo *pWInfo, void *pOld, u64 nByte){ + void *pNew = sqlite3WhereMalloc(pWInfo, nByte); + if( pNew && pOld ){ + WhereMemBlock *pOldBlk = (WhereMemBlock*)pOld; + pOldBlk--; + assert( pOldBlk->szsz); + } + return pNew; +} + /* ** Create a new mask for cursor iCursor. ** @@ -140258,6 +169148,54 @@ static void createMask(WhereMaskSet *pMaskSet, int iCursor){ pMaskSet->ix[pMaskSet->n++] = iCursor; } +/* +** If the right-hand branch of the expression is a TK_COLUMN, then return +** a pointer to the right-hand branch. Otherwise, return NULL. +*/ +static Expr *whereRightSubexprIsColumn(Expr *p){ + p = sqlite3ExprSkipCollateAndLikely(p->pRight); + if( ALWAYS(p!=0) && p->op==TK_COLUMN && !ExprHasProperty(p, EP_FixedCol) ){ + return p; + } + return 0; +} + +/* +** Term pTerm is guaranteed to be a WO_IN term. It may be a component term +** of a vector IN expression of the form "(x, y, ...) IN (SELECT ...)". +** This function checks to see if the term is compatible with an index +** column with affinity idxaff (one of the SQLITE_AFF_XYZ values). If so, +** it returns a pointer to the name of the collation sequence (e.g. "BINARY" +** or "NOCASE") used by the comparison in pTerm. If it is not compatible +** with affinity idxaff, NULL is returned. +*/ +static SQLITE_NOINLINE const char *indexInAffinityOk( + Parse *pParse, + WhereTerm *pTerm, + u8 idxaff +){ + Expr *pX = pTerm->pExpr; + Expr inexpr; + + assert( pTerm->eOperator & WO_IN ); + + if( sqlite3ExprIsVector(pX->pLeft) ){ + int iField = pTerm->u.x.iField - 1; + inexpr.flags = 0; + inexpr.op = TK_EQ; + inexpr.pLeft = pX->pLeft->x.pList->a[iField].pExpr; + assert( ExprUseXSelect(pX) ); + inexpr.pRight = pX->x.pSelect->pEList->a[iField].pExpr; + pX = &inexpr; + } + + if( sqlite3IndexAffinityOk(pX, idxaff) ){ + CollSeq *pRet = sqlite3ExprCompareCollSeq(pParse, pX); + return pRet ? pRet->zName : sqlite3StrBINARY; + } + return 0; +} + /* ** Advance to the next WhereTerm that matches according to the criteria ** established when the pScan object was initialized by whereScanInit(). @@ -140277,18 +169215,20 @@ static WhereTerm *whereScanNext(WhereScan *pScan){ iColumn = pScan->aiColumn[pScan->iEquiv-1]; iCur = pScan->aiCur[pScan->iEquiv-1]; assert( pWC!=0 ); + assert( iCur>=0 ); do{ for(pTerm=pWC->a+k; knTerm; k++, pTerm++){ + assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 || pTerm->leftCursor<0 ); if( pTerm->leftCursor==iCur - && pTerm->u.leftColumn==iColumn + && pTerm->u.x.leftColumn==iColumn && (iColumn!=XN_EXPR || sqlite3ExprCompareSkip(pTerm->pExpr->pLeft, pScan->pIdxExpr,iCur)==0) - && (pScan->iEquiv<=1 || !ExprHasProperty(pTerm->pExpr, EP_FromJoin)) + && (pScan->iEquiv<=1 || !ExprHasProperty(pTerm->pExpr, EP_OuterON)) ){ if( (pTerm->eOperator & WO_EQUIV)!=0 && pScan->nEquivaiCur) - && (pX = sqlite3ExprSkipCollate(pTerm->pExpr->pRight))->op==TK_COLUMN + && (pX = whereRightSubexprIsColumn(pTerm->pExpr))!=0 ){ int j; for(j=0; jnEquiv; j++){ @@ -140306,22 +169246,30 @@ static WhereTerm *whereScanNext(WhereScan *pScan){ if( (pTerm->eOperator & pScan->opMask)!=0 ){ /* Verify the affinity and collating sequence match */ if( pScan->zCollName && (pTerm->eOperator & WO_ISNULL)==0 ){ - CollSeq *pColl; + const char *zCollName; Parse *pParse = pWC->pWInfo->pParse; pX = pTerm->pExpr; - if( !sqlite3IndexAffinityOk(pX, pScan->idxaff) ){ - continue; + + if( (pTerm->eOperator & WO_IN) ){ + zCollName = indexInAffinityOk(pParse, pTerm, pScan->idxaff); + if( !zCollName ) continue; + }else{ + CollSeq *pColl; + if( !sqlite3IndexAffinityOk(pX, pScan->idxaff) ){ + continue; + } + assert(pX->pLeft); + pColl = sqlite3ExprCompareCollSeq(pParse, pX); + zCollName = pColl ? pColl->zName : sqlite3StrBINARY; } - assert(pX->pLeft); - pColl = sqlite3BinaryCompareCollSeq(pParse, - pX->pLeft, pX->pRight); - if( pColl==0 ) pColl = pParse->db->pDfltColl; - if( sqlite3StrICmp(pColl->zName, pScan->zCollName) ){ + + if( sqlite3StrICmp(zCollName, pScan->zCollName) ){ continue; } } if( (pTerm->eOperator & (WO_EQ|WO_IS))!=0 - && (pX = pTerm->pExpr->pRight)->op==TK_COLUMN + && (pX = pTerm->pExpr->pRight, ALWAYS(pX!=0)) + && pX->op==TK_COLUMN && pX->iTable==pScan->aiCur[0] && pX->iColumn==pScan->aiColumn[0] ){ @@ -140330,6 +169278,18 @@ static WhereTerm *whereScanNext(WhereScan *pScan){ } pScan->pWC = pWC; pScan->k = k+1; +#ifdef WHERETRACE_ENABLED + if( (sqlite3WhereTrace & 0x20000)!=0 && pScan->nEquiv>1 ){ + int ii; + sqlite3DebugPrintf("EQUIVALENT TO {%d:%d} (due to TERM-%d):", + pScan->aiCur[0], pScan->aiColumn[0], pTerm->iTerm); + for(ii=1; iinEquiv; ii++){ + sqlite3DebugPrintf(" {%d:%d}", + pScan->aiCur[ii], pScan->aiColumn[ii]); + } + sqlite3DebugPrintf("\n"); + } +#endif return pTerm; } } @@ -140396,16 +169356,16 @@ static WhereTerm *whereScanInit( if( pIdx ){ int j = iColumn; iColumn = pIdx->aiColumn[j]; - if( iColumn==XN_EXPR ){ - pScan->pIdxExpr = pIdx->aColExpr->a[j].pExpr; - pScan->zCollName = pIdx->azColl[j]; - pScan->aiColumn[0] = XN_EXPR; - return whereScanInitIndexExpr(pScan); - }else if( iColumn==pIdx->pTable->iPKey ){ + if( iColumn==pIdx->pTable->iPKey ){ iColumn = XN_ROWID; }else if( iColumn>=0 ){ pScan->idxaff = pIdx->pTable->aCol[iColumn].affinity; pScan->zCollName = pIdx->azColl[j]; + }else if( iColumn==XN_EXPR ){ + pScan->pIdxExpr = pIdx->aColExpr->a[j].pExpr; + pScan->zCollName = pIdx->azColl[j]; + pScan->aiColumn[0] = XN_EXPR; + return whereScanInitIndexExpr(pScan); } }else if( iColumn==XN_EXPR ){ return 0; @@ -140420,7 +169380,7 @@ static WhereTerm *whereScanInit( ** if pIdx!=0 and is one of the WO_xx operator codes specified by ** the op parameter. Return a pointer to the term. Return 0 if not found. ** -** If pIdx!=0 then it must be one of the indexes of table iCur. +** If pIdx!=0 then it must be one of the indexes of table iCur. ** Search for terms matching the iColumn-th column of pIdx ** rather than the iColumn-th column of table iCur. ** @@ -140484,8 +169444,9 @@ static int findIndexCol( const char *zColl = pIdx->azColl[iCol]; for(i=0; inExpr; i++){ - Expr *p = sqlite3ExprSkipCollate(pList->a[i].pExpr); - if( p->op==TK_COLUMN + Expr *p = sqlite3ExprSkipCollateAndLikely(pList->a[i].pExpr); + if( ALWAYS(p!=0) + && (p->op==TK_COLUMN || p->op==TK_AGG_COLUMN) && p->iColumn==pIdx->aiColumn[iCol] && p->iTable==iBase ){ @@ -140533,23 +169494,25 @@ static int isDistinctRedundant( ){ Table *pTab; Index *pIdx; - int i; + int i; int iBase; /* If there is more than one table or sub-select in the FROM clause of - ** this query, then it will not be possible to show that the DISTINCT + ** this query, then it will not be possible to show that the DISTINCT ** clause is redundant. */ if( pTabList->nSrc!=1 ) return 0; iBase = pTabList->a[0].iCursor; - pTab = pTabList->a[0].pTab; + pTab = pTabList->a[0].pSTab; - /* If any of the expressions is an IPK column on table iBase, then return + /* If any of the expressions is an IPK column on table iBase, then return ** true. Note: The (p->iTable==iBase) part of this test may be false if the ** current SELECT is a correlated sub-query. */ for(i=0; inExpr; i++){ - Expr *p = sqlite3ExprSkipCollate(pDistinct->a[i].pExpr); - if( p->op==TK_COLUMN && p->iTable==iBase && p->iColumn<0 ) return 1; + Expr *p = sqlite3ExprSkipCollateAndLikely(pDistinct->a[i].pExpr); + if( NEVER(p==0) ) continue; + if( p->op!=TK_COLUMN && p->op!=TK_AGG_COLUMN ) continue; + if( p->iTable==iBase && p->iColumn<0 ) return 1; } /* Loop through all indices on the table, checking each to see if it makes @@ -140567,6 +169530,7 @@ static int isDistinctRedundant( */ for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ if( !IsUniqueIndex(pIdx) ) continue; + if( pIdx->pPartIdxWhere ) continue; for(i=0; inKeyCol; i++){ if( 0==sqlite3WhereFindTerm(pWC, iBase, i, ~(Bitmask)0, WO_EQ, pIdx) ){ if( findIndexCol(pParse, pDistinct, iBase, pIdx, i)<0 ) break; @@ -140594,43 +169558,58 @@ static LogEst estLog(LogEst N){ ** Convert OP_Column opcodes to OP_Copy in previously generated code. ** ** This routine runs over generated VDBE code and translates OP_Column -** opcodes into OP_Copy when the table is being accessed via co-routine +** opcodes into OP_Copy when the table is being accessed via co-routine ** instead of via table lookup. ** -** If the bIncrRowid parameter is 0, then any OP_Rowid instructions on -** cursor iTabCur are transformed into OP_Null. Or, if bIncrRowid is non-zero, -** then each OP_Rowid is transformed into an instruction to increment the -** value stored in its output register. +** If the iAutoidxCur is not zero, then any OP_Rowid instructions on +** cursor iTabCur are transformed into OP_Sequence opcode for the +** iAutoidxCur cursor, in order to generate unique rowids for the +** automatic index being generated. */ static void translateColumnToCopy( Parse *pParse, /* Parsing context */ int iStart, /* Translate from this opcode to the end */ int iTabCur, /* OP_Column/OP_Rowid references to this table */ int iRegister, /* The first column is in this register */ - int bIncrRowid /* If non-zero, transform OP_rowid to OP_AddImm(1) */ + int iAutoidxCur /* If non-zero, cursor of autoindex being generated */ ){ Vdbe *v = pParse->pVdbe; VdbeOp *pOp = sqlite3VdbeGetOp(v, iStart); int iEnd = sqlite3VdbeCurrentAddr(v); if( pParse->db->mallocFailed ) return; +#ifdef SQLITE_DEBUG + if( pParse->db->flags & SQLITE_VdbeAddopTrace ){ + printf("CHECKING for column-to-copy on cursor %d for %d..%d\n", + iTabCur, iStart, iEnd); + } +#endif for(; iStartp1!=iTabCur ) continue; if( pOp->opcode==OP_Column ){ +#ifdef SQLITE_DEBUG + if( pParse->db->flags & SQLITE_VdbeAddopTrace ){ + printf("TRANSLATE OP_Column to OP_Copy at %d\n", iStart); + } +#endif pOp->opcode = OP_Copy; pOp->p1 = pOp->p2 + iRegister; pOp->p2 = pOp->p3; pOp->p3 = 0; + pOp->p5 = 2; /* Cause the MEM_Subtype flag to be cleared */ }else if( pOp->opcode==OP_Rowid ){ - if( bIncrRowid ){ - /* Increment the value stored in the P2 operand of the OP_Rowid. */ - pOp->opcode = OP_AddImm; - pOp->p1 = pOp->p2; - pOp->p2 = 1; - }else{ +#ifdef SQLITE_DEBUG + if( pParse->db->flags & SQLITE_VdbeAddopTrace ){ + printf("TRANSLATE OP_Rowid to OP_Sequence at %d\n", iStart); + } +#endif + pOp->opcode = OP_Sequence; + pOp->p1 = iAutoidxCur; +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + if( iAutoidxCur==0 ){ pOp->opcode = OP_Null; - pOp->p1 = 0; pOp->p3 = 0; } +#endif } } } @@ -140642,16 +169621,22 @@ static void translateColumnToCopy( ** are no-ops. */ #if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(WHERETRACE_ENABLED) -static void TRACE_IDX_INPUTS(sqlite3_index_info *p){ +static void whereTraceIndexInfoInputs( + sqlite3_index_info *p, /* The IndexInfo object */ + Table *pTab /* The TABLE that is the virtual table */ +){ int i; - if( !sqlite3WhereTrace ) return; + if( (sqlite3WhereTrace & 0x10)==0 ) return; + sqlite3DebugPrintf("sqlite3_index_info inputs for %s:\n", pTab->zName); for(i=0; inConstraint; i++){ - sqlite3DebugPrintf(" constraint[%d]: col=%d termid=%d op=%d usabled=%d\n", + sqlite3DebugPrintf( + " constraint[%d]: col=%d termid=%d op=%d usabled=%d collseq=%s\n", i, p->aConstraint[i].iColumn, p->aConstraint[i].iTermOffset, p->aConstraint[i].op, - p->aConstraint[i].usable); + p->aConstraint[i].usable, + sqlite3_vtab_collation(p,i)); } for(i=0; inOrderBy; i++){ sqlite3DebugPrintf(" orderby[%d]: col=%d desc=%d\n", @@ -140660,9 +169645,13 @@ static void TRACE_IDX_INPUTS(sqlite3_index_info *p){ p->aOrderBy[i].desc); } } -static void TRACE_IDX_OUTPUTS(sqlite3_index_info *p){ +static void whereTraceIndexInfoOutputs( + sqlite3_index_info *p, /* The IndexInfo object */ + Table *pTab /* The TABLE that is the virtual table */ +){ int i; - if( !sqlite3WhereTrace ) return; + if( (sqlite3WhereTrace & 0x10)==0 ) return; + sqlite3DebugPrintf("sqlite3_index_info outputs for %s:\n", pTab->zName); for(i=0; inConstraint; i++){ sqlite3DebugPrintf(" usage[%d]: argvIdx=%d omit=%d\n", i, @@ -140676,10 +169665,86 @@ static void TRACE_IDX_OUTPUTS(sqlite3_index_info *p){ sqlite3DebugPrintf(" estimatedRows=%lld\n", p->estimatedRows); } #else -#define TRACE_IDX_INPUTS(A) -#define TRACE_IDX_OUTPUTS(A) +#define whereTraceIndexInfoInputs(A,B) +#define whereTraceIndexInfoOutputs(A,B) #endif +/* +** We know that pSrc is an operand of an outer join. Return true if +** pTerm is a constraint that is compatible with that join. +** +** pTerm must be EP_OuterON if pSrc is the right operand of an +** outer join. pTerm can be either EP_OuterON or EP_InnerON if pSrc +** is the left operand of a RIGHT join. +** +** See https://sqlite.org/forum/forumpost/206d99a16dd9212f +** for an example of a WHERE clause constraints that may not be used on +** the right table of a RIGHT JOIN because the constraint implies a +** not-NULL condition on the left table of the RIGHT JOIN. +*/ +static int constraintCompatibleWithOuterJoin( + const WhereTerm *pTerm, /* WHERE clause term to check */ + const SrcItem *pSrc /* Table we are trying to access */ +){ + assert( (pSrc->fg.jointype&(JT_LEFT|JT_LTORJ|JT_RIGHT))!=0 ); /* By caller */ + testcase( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))==JT_LEFT ); + testcase( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))==JT_LTORJ ); + testcase( ExprHasProperty(pTerm->pExpr, EP_OuterON) ) + testcase( ExprHasProperty(pTerm->pExpr, EP_InnerON) ); + if( !ExprHasProperty(pTerm->pExpr, EP_OuterON|EP_InnerON) + || pTerm->pExpr->w.iJoin != pSrc->iCursor + ){ + return 0; + } + if( (pSrc->fg.jointype & (JT_LEFT|JT_RIGHT))!=0 + && NEVER(ExprHasProperty(pTerm->pExpr, EP_InnerON)) + ){ + return 0; + } + return 1; +} + +#ifndef SQLITE_OMIT_AUTOMATIC_INDEX +/* +** Return true if column iCol of table pTab seem like it might be a +** good column to use as part of a query-time index. +** +** Current algorithm (subject to improvement!): +** +** 1. If iCol is already the left-most column of some other index, +** then return false. +** +** 2. If iCol is part of an existing index that has an aiRowLogEst of +** more than 20, then return false. +** +** 3. If no disqualifying conditions above are found, return true. +** +** 2025-01-03: I experimented with a new rule that returns false if the +** the datatype of the column is "BOOLEAN". This did not improve +** performance on any queries at hand, but it did burn CPU cycles, so the +** idea was not committed. +*/ +static SQLITE_NOINLINE int columnIsGoodIndexCandidate( + const Table *pTab, + int iCol +){ + const Index *pIdx; + for(pIdx = pTab->pIndex; pIdx!=0; pIdx=pIdx->pNext){ + int j; + for(j=0; jnKeyCol; j++){ + if( pIdx->aiColumn[j]==iCol ){ + if( j==0 ) return 0; + if( pIdx->hasStat1 && pIdx->aiRowLogEst[j+1]>20 ) return 0; + break; + } + } + } + return 1; +} +#endif /* SQLITE_OMIT_AUTOMATIC_INDEX */ + + + #ifndef SQLITE_OMIT_AUTOMATIC_INDEX /* ** Return TRUE if the WHERE clause term pTerm is of a form where it @@ -140687,43 +169752,94 @@ static void TRACE_IDX_OUTPUTS(sqlite3_index_info *p){ ** index existed. */ static int termCanDriveIndex( - WhereTerm *pTerm, /* WHERE clause term to check */ - struct SrcList_item *pSrc, /* Table we are trying to access */ - Bitmask notReady /* Tables in outer loops of the join */ + const WhereTerm *pTerm, /* WHERE clause term to check */ + const SrcItem *pSrc, /* Table we are trying to access */ + const Bitmask notReady /* Tables in outer loops of the join */ ){ char aff; + int leftCol; + if( pTerm->leftCursor!=pSrc->iCursor ) return 0; if( (pTerm->eOperator & (WO_EQ|WO_IS))==0 ) return 0; - if( (pSrc->fg.jointype & JT_LEFT) - && !ExprHasProperty(pTerm->pExpr, EP_FromJoin) - && (pTerm->eOperator & WO_IS) + assert( (pSrc->fg.jointype & JT_RIGHT)==0 ); + if( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))!=0 + && !constraintCompatibleWithOuterJoin(pTerm,pSrc) ){ - /* Cannot use an IS term from the WHERE clause as an index driver for - ** the RHS of a LEFT JOIN. Such a term can only be used if it is from - ** the ON clause. */ - return 0; + return 0; /* See https://sqlite.org/forum/forumpost/51e6959f61 */ } if( (pTerm->prereqRight & notReady)!=0 ) return 0; - if( pTerm->u.leftColumn<0 ) return 0; - aff = pSrc->pTab->aCol[pTerm->u.leftColumn].affinity; + assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 ); + leftCol = pTerm->u.x.leftColumn; + if( leftCol<0 ) return 0; + aff = pSrc->pSTab->aCol[leftCol].affinity; if( !sqlite3IndexAffinityOk(pTerm->pExpr, aff) ) return 0; testcase( pTerm->pExpr->op==TK_IS ); - return 1; + return columnIsGoodIndexCandidate(pSrc->pSTab, leftCol); } #endif #ifndef SQLITE_OMIT_AUTOMATIC_INDEX + +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS +/* +** Argument pIdx represents an automatic index that the current statement +** will create and populate. Add an OP_Explain with text of the form: +** +** CREATE AUTOMATIC INDEX ON
                  () [WHERE ] +** +** This is only required if sqlite3_stmt_scanstatus() is enabled, to +** associate an SQLITE_SCANSTAT_NCYCLE and SQLITE_SCANSTAT_NLOOP +** values with. In order to avoid breaking legacy code and test cases, +** the OP_Explain is not added if this is an EXPLAIN QUERY PLAN command. +*/ +static void explainAutomaticIndex( + Parse *pParse, + Index *pIdx, /* Automatic index to explain */ + int bPartial, /* True if pIdx is a partial index */ + int *pAddrExplain /* OUT: Address of OP_Explain */ +){ + if( IS_STMT_SCANSTATUS(pParse->db) && pParse->explain!=2 ){ + Table *pTab = pIdx->pTable; + const char *zSep = ""; + char *zText = 0; + int ii = 0; + sqlite3_str *pStr = sqlite3_str_new(pParse->db); + sqlite3_str_appendf(pStr,"CREATE AUTOMATIC INDEX ON %s(", pTab->zName); + assert( pIdx->nColumn>1 ); + assert( pIdx->aiColumn[pIdx->nColumn-1]==XN_ROWID || !HasRowid(pTab) ); + for(ii=0; ii<(pIdx->nColumn-1); ii++){ + const char *zName = 0; + int iCol = pIdx->aiColumn[ii]; + + zName = pTab->aCol[iCol].zCnName; + sqlite3_str_appendf(pStr, "%s%s", zSep, zName); + zSep = ", "; + } + zText = sqlite3_str_finish(pStr); + if( zText==0 ){ + sqlite3OomFault(pParse->db); + }else{ + *pAddrExplain = sqlite3VdbeExplain( + pParse, 0, "%s)%s", zText, (bPartial ? " WHERE " : "") + ); + sqlite3_free(zText); + } + } +} +#else +# define explainAutomaticIndex(a,b,c,d) +#endif + /* ** Generate code to construct the Index object for an automatic index ** and to set up the WhereLevel object pLevel so that the code generator ** makes use of the automatic index. */ -static void constructAutomaticIndex( +static SQLITE_NOINLINE void constructAutomaticIndex( Parse *pParse, /* The parsing context */ WhereClause *pWC, /* The WHERE clause */ - struct SrcList_item *pSrc, /* The FROM clause term to get the next index */ - Bitmask notReady, /* Mask of cursors that are not available */ + const Bitmask notReady, /* Mask of cursors that are not available */ WhereLevel *pLevel /* Write new index here */ ){ int nKeyCol; /* Number of columns in the constructed index */ @@ -140743,12 +169859,17 @@ static void constructAutomaticIndex( char *zNotUsed; /* Extra space on the end of pIdx */ Bitmask idxCols; /* Bitmap of columns used for indexing */ Bitmask extraCols; /* Bitmap of additional columns */ - u8 sentWarning = 0; /* True if a warnning has been issued */ + u8 sentWarning = 0; /* True if a warning has been issued */ + u8 useBloomFilter = 0; /* True to also add a Bloom filter */ Expr *pPartial = 0; /* Partial Index Expression */ int iContinue = 0; /* Jump here to skip excluded rows */ - struct SrcList_item *pTabItem; /* FROM clause term being indexed */ + SrcList *pTabList; /* The complete FROM clause */ + SrcItem *pSrc; /* The FROM clause term to get the next index */ int addrCounter = 0; /* Address where integer counter is initialized */ int regBase; /* Array of registers where record is assembled */ +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + int addrExp = 0; /* Address of OP_Explain */ +#endif /* Generate code to skip over the creation and initialization of the ** transient index on 2nd and subsequent iterations of the loop. */ @@ -140759,31 +169880,35 @@ static void constructAutomaticIndex( /* Count the number of columns that will be added to the index ** and used to match WHERE clause constraints */ nKeyCol = 0; - pTable = pSrc->pTab; + pTabList = pWC->pWInfo->pTabList; + pSrc = &pTabList->a[pLevel->iFrom]; + pTable = pSrc->pSTab; pWCEnd = &pWC->a[pWC->nTerm]; pLoop = pLevel->pWLoop; idxCols = 0; for(pTerm=pWC->a; pTermpExpr; - assert( !ExprHasProperty(pExpr, EP_FromJoin) /* prereq always non-zero */ - || pExpr->iRightJoinTable!=pSrc->iCursor /* for the right-hand */ - || pLoop->prereq!=0 ); /* table of a LEFT JOIN */ - if( pLoop->prereq==0 - && (pTerm->wtFlags & TERM_VIRTUAL)==0 - && !ExprHasProperty(pExpr, EP_FromJoin) - && sqlite3ExprIsTableConstant(pExpr, pSrc->iCursor) ){ - pPartial = sqlite3ExprAnd(pParse->db, pPartial, + /* Make the automatic index a partial index if there are terms in the + ** WHERE clause (or the ON clause of a LEFT join) that constrain which + ** rows of the target table (pSrc) that can be used. */ + if( (pTerm->wtFlags & TERM_VIRTUAL)==0 + && sqlite3ExprIsSingleTableConstraint(pExpr, pTabList, pLevel->iFrom, 0) + ){ + pPartial = sqlite3ExprAnd(pParse, pPartial, sqlite3ExprDup(pParse->db, pExpr, 0)); } if( termCanDriveIndex(pTerm, pSrc, notReady) ){ - int iCol = pTerm->u.leftColumn; - Bitmask cMask = iCol>=BMS ? MASKBIT(BMS-1) : MASKBIT(iCol); + int iCol; + Bitmask cMask; + assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 ); + iCol = pTerm->u.x.leftColumn; + cMask = iCol>=BMS ? MASKBIT(BMS-1) : MASKBIT(iCol); testcase( iCol==BMS ); testcase( iCol==BMS-1 ); if( !sentWarning ){ sqlite3_log(SQLITE_WARNING_AUTOINDEX, "automatic index on %s(%s)", pTable->zName, - pTable->aCol[iCol].zName); + pTable->aCol[iCol].zCnName); sentWarning = 1; } if( (idxCols & cMask)==0 ){ @@ -140795,7 +169920,7 @@ static void constructAutomaticIndex( } } } - assert( nKeyCol>0 ); + assert( nKeyCol>0 || pParse->db->mallocFailed ); pLoop->u.btree.nEq = pLoop->nLTerm = nKeyCol; pLoop->wsFlags = WHERE_COLUMN_EQ | WHERE_IDX_ONLY | WHERE_INDEXED | WHERE_AUTO_INDEX; @@ -140808,7 +169933,24 @@ static void constructAutomaticIndex( ** original table changes and the index and table cannot both be used ** if they go out of sync. */ - extraCols = pSrc->colUsed & (~idxCols | MASKBIT(BMS-1)); + if( IsView(pTable) ){ + extraCols = ALLBITS & ~idxCols; + }else{ + extraCols = pSrc->colUsed & (~idxCols | MASKBIT(BMS-1)); + } + if( !HasRowid(pTable) ){ + /* For WITHOUT ROWID tables, ensure that all PRIMARY KEY columns are + ** either in the idxCols mask or in the extraCols mask */ + for(i=0; inCol; i++){ + if( (pTable->aCol[i].colFlags & COLFLAG_PRIMKEY)==0 ) continue; + if( i>=BMS-1 ){ + extraCols |= MASKBIT(BMS-1); + break; + } + if( idxCols & MASKBIT(i) ) continue; + extraCols |= MASKBIT(i); + } + } mxBitCol = MIN(BMS-1,pTable->nCol); testcase( pTable->nCol==BMS-1 ); testcase( pTable->nCol==BMS-2 ); @@ -140820,7 +169962,10 @@ static void constructAutomaticIndex( } /* Construct the Index object to describe this index */ - pIdx = sqlite3AllocateIndexObject(pParse->db, nKeyCol+1, 0, &zNotUsed); + assert( nKeyCol <= pTable->nCol + MAX(0, pTable->nCol - BMS + 1) ); + /* ^-- This guarantees that the number of index columns will fit in the u16 */ + pIdx = sqlite3AllocateIndexObject(pParse->db, nKeyCol+HasRowid(pTable), + 0, &zNotUsed); if( pIdx==0 ) goto end_auto_index_create; pLoop->u.btree.pIndex = pIdx; pIdx->zName = "auto-index"; @@ -140829,17 +169974,31 @@ static void constructAutomaticIndex( idxCols = 0; for(pTerm=pWC->a; pTermu.leftColumn; - Bitmask cMask = iCol>=BMS ? MASKBIT(BMS-1) : MASKBIT(iCol); + int iCol; + Bitmask cMask; + assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 ); + iCol = pTerm->u.x.leftColumn; + cMask = iCol>=BMS ? MASKBIT(BMS-1) : MASKBIT(iCol); testcase( iCol==BMS-1 ); testcase( iCol==BMS ); if( (idxCols & cMask)==0 ){ Expr *pX = pTerm->pExpr; idxCols |= cMask; - pIdx->aiColumn[n] = pTerm->u.leftColumn; - pColl = sqlite3BinaryCompareCollSeq(pParse, pX->pLeft, pX->pRight); + pIdx->aiColumn[n] = pTerm->u.x.leftColumn; + pColl = sqlite3ExprCompareCollSeq(pParse, pX); + assert( pColl!=0 || pParse->nErr>0 ); /* TH3 collate01.800 */ pIdx->azColl[n] = pColl ? pColl->zName : sqlite3StrBINARY; n++; + if( ALWAYS(pX->pLeft!=0) + && sqlite3ExprAffinity(pX->pLeft)!=SQLITE_AFF_TEXT + ){ + /* TUNING: only use a Bloom filter on an automatic index + ** if one or more key columns has the ability to hold numeric + ** values, since strings all have the same hash in the Bloom + ** filter implementation and hence a Bloom filter on a text column + ** is not usually helpful. */ + useBloomFilter = 1; + } } } } @@ -140862,27 +170021,42 @@ static void constructAutomaticIndex( } } assert( n==nKeyCol ); - pIdx->aiColumn[n] = XN_ROWID; - pIdx->azColl[n] = sqlite3StrBINARY; + if( HasRowid(pTable) ){ + pIdx->aiColumn[n] = XN_ROWID; + pIdx->azColl[n] = sqlite3StrBINARY; + } /* Create the automatic index */ + explainAutomaticIndex(pParse, pIdx, pPartial!=0, &addrExp); assert( pLevel->iIdxCur>=0 ); pLevel->iIdxCur = pParse->nTab++; sqlite3VdbeAddOp2(v, OP_OpenAutoindex, pLevel->iIdxCur, nKeyCol+1); sqlite3VdbeSetP4KeyInfo(pParse, pIdx); VdbeComment((v, "for %s", pTable->zName)); + if( OptimizationEnabled(pParse->db, SQLITE_BloomFilter) && useBloomFilter ){ + sqlite3WhereExplainBloomFilter(pParse, pWC->pWInfo, pLevel); + pLevel->regFilter = ++pParse->nMem; + sqlite3VdbeAddOp2(v, OP_Blob, 10000, pLevel->regFilter); + } /* Fill the automatic index with content */ - pTabItem = &pWC->pWInfo->pTabList->a[pLevel->iFrom]; - if( pTabItem->fg.viaCoroutine ){ - int regYield = pTabItem->regReturn; + assert( pSrc == &pWC->pWInfo->pTabList->a[pLevel->iFrom] ); + if( pSrc->fg.viaCoroutine ){ + int regYield; + Subquery *pSubq; + assert( pSrc->fg.isSubquery ); + pSubq = pSrc->u4.pSubq; + assert( pSubq!=0 ); + regYield = pSubq->regReturn; addrCounter = sqlite3VdbeAddOp2(v, OP_Integer, 0, 0); - sqlite3VdbeAddOp3(v, OP_InitCoroutine, regYield, 0, pTabItem->addrFillSub); + sqlite3VdbeAddOp3(v, OP_InitCoroutine, regYield, 0, pSubq->addrFillSub); addrTop = sqlite3VdbeAddOp1(v, OP_Yield, regYield); VdbeCoverage(v); - VdbeComment((v, "next row of %s", pTabItem->pTab->zName)); + VdbeComment((v, "next row of %s", pSrc->pSTab->zName)); }else{ - addrTop = sqlite3VdbeAddOp1(v, OP_Rewind, pLevel->iTabCur); VdbeCoverage(v); + assert( pLevel->addrHalt ); + addrTop = sqlite3VdbeAddOp2(v, OP_Rewind,pLevel->iTabCur,pLevel->addrHalt); + VdbeCoverage(v); } if( pPartial ){ iContinue = sqlite3VdbeMakeLabel(pParse); @@ -140893,47 +170067,212 @@ static void constructAutomaticIndex( regBase = sqlite3GenerateIndexKey( pParse, pIdx, pLevel->iTabCur, regRecord, 0, 0, 0, 0 ); + if( pLevel->regFilter ){ + sqlite3VdbeAddOp4Int(v, OP_FilterAdd, pLevel->regFilter, 0, + regBase, pLoop->u.btree.nEq); + } + sqlite3VdbeScanStatusCounters(v, addrExp, addrExp, sqlite3VdbeCurrentAddr(v)); sqlite3VdbeAddOp2(v, OP_IdxInsert, pLevel->iIdxCur, regRecord); sqlite3VdbeChangeP5(v, OPFLAG_USESEEKRESULT); if( pPartial ) sqlite3VdbeResolveLabel(v, iContinue); - if( pTabItem->fg.viaCoroutine ){ + if( pSrc->fg.viaCoroutine ){ + assert( pSrc->fg.isSubquery && pSrc->u4.pSubq!=0 ); sqlite3VdbeChangeP2(v, addrCounter, regBase+n); testcase( pParse->db->mallocFailed ); + assert( pLevel->iIdxCur>0 ); translateColumnToCopy(pParse, addrTop, pLevel->iTabCur, - pTabItem->regResult, 1); + pSrc->u4.pSubq->regResult, pLevel->iIdxCur); sqlite3VdbeGoto(v, addrTop); - pTabItem->fg.viaCoroutine = 0; + pSrc->fg.viaCoroutine = 0; + sqlite3VdbeJumpHere(v, addrTop); }else{ sqlite3VdbeAddOp2(v, OP_Next, pLevel->iTabCur, addrTop+1); VdbeCoverage(v); + sqlite3VdbeChangeP5(v, SQLITE_STMTSTATUS_AUTOINDEX); + if( (pSrc->fg.jointype & JT_LEFT)!=0 ){ + sqlite3VdbeJumpHere(v, addrTop); + } } - sqlite3VdbeChangeP5(v, SQLITE_STMTSTATUS_AUTOINDEX); - sqlite3VdbeJumpHere(v, addrTop); sqlite3ReleaseTempReg(pParse, regRecord); - + /* Jump here when skipping the initialization */ sqlite3VdbeJumpHere(v, addrInit); + sqlite3VdbeScanStatusRange(v, addrExp, addrExp, -1); end_auto_index_create: sqlite3ExprDelete(pParse->db, pPartial); } #endif /* SQLITE_OMIT_AUTOMATIC_INDEX */ +/* +** Generate bytecode that will initialize a Bloom filter that is appropriate +** for pLevel. +** +** If there are inner loops within pLevel that have the WHERE_BLOOMFILTER +** flag set, initialize a Bloomfilter for them as well. Except don't do +** this recursive initialization if the SQLITE_BloomPulldown optimization has +** been turned off. +** +** When the Bloom filter is initialized, the WHERE_BLOOMFILTER flag is cleared +** from the loop, but the regFilter value is set to a register that implements +** the Bloom filter. When regFilter is positive, the +** sqlite3WhereCodeOneLoopStart() will generate code to test the Bloom filter +** and skip the subsequence B-Tree seek if the Bloom filter indicates that +** no matching rows exist. +** +** This routine may only be called if it has previously been determined that +** the loop would benefit from a Bloom filter, and the WHERE_BLOOMFILTER bit +** is set. +*/ +static SQLITE_NOINLINE void sqlite3ConstructBloomFilter( + WhereInfo *pWInfo, /* The WHERE clause */ + int iLevel, /* Index in pWInfo->a[] that is pLevel */ + WhereLevel *pLevel, /* Make a Bloom filter for this FROM term */ + Bitmask notReady /* Loops that are not ready */ +){ + int addrOnce; /* Address of opening OP_Once */ + int addrTop; /* Address of OP_Rewind */ + int addrCont; /* Jump here to skip a row */ + const WhereTerm *pTerm; /* For looping over WHERE clause terms */ + const WhereTerm *pWCEnd; /* Last WHERE clause term */ + Parse *pParse = pWInfo->pParse; /* Parsing context */ + Vdbe *v = pParse->pVdbe; /* VDBE under construction */ + WhereLoop *pLoop = pLevel->pWLoop; /* The loop being coded */ + int iCur; /* Cursor for table getting the filter */ + IndexedExpr *saved_pIdxEpr; /* saved copy of Parse.pIdxEpr */ + IndexedExpr *saved_pIdxPartExpr; /* saved copy of Parse.pIdxPartExpr */ + + saved_pIdxEpr = pParse->pIdxEpr; + saved_pIdxPartExpr = pParse->pIdxPartExpr; + pParse->pIdxEpr = 0; + pParse->pIdxPartExpr = 0; + + assert( pLoop!=0 ); + assert( v!=0 ); + assert( pLoop->wsFlags & WHERE_BLOOMFILTER ); + assert( (pLoop->wsFlags & WHERE_IDX_ONLY)==0 ); + + addrOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); + do{ + const SrcList *pTabList; + const SrcItem *pItem; + const Table *pTab; + u64 sz; + int iSrc; + sqlite3WhereExplainBloomFilter(pParse, pWInfo, pLevel); + addrCont = sqlite3VdbeMakeLabel(pParse); + iCur = pLevel->iTabCur; + pLevel->regFilter = ++pParse->nMem; + + /* The Bloom filter is a Blob held in a register. Initialize it + ** to zero-filled blob of at least 80K bits, but maybe more if the + ** estimated size of the table is larger. We could actually + ** measure the size of the table at run-time using OP_Count with + ** P3==1 and use that value to initialize the blob. But that makes + ** testing complicated. By basing the blob size on the value in the + ** sqlite_stat1 table, testing is much easier. + */ + pTabList = pWInfo->pTabList; + iSrc = pLevel->iFrom; + pItem = &pTabList->a[iSrc]; + assert( pItem!=0 ); + pTab = pItem->pSTab; + assert( pTab!=0 ); + sz = sqlite3LogEstToInt(pTab->nRowLogEst); + if( sz<10000 ){ + sz = 10000; + }else if( sz>10000000 ){ + sz = 10000000; + } + sqlite3VdbeAddOp2(v, OP_Blob, (int)sz, pLevel->regFilter); + + addrTop = sqlite3VdbeAddOp1(v, OP_Rewind, iCur); VdbeCoverage(v); + pWCEnd = &pWInfo->sWC.a[pWInfo->sWC.nTerm]; + for(pTerm=pWInfo->sWC.a; pTermpExpr; + if( (pTerm->wtFlags & TERM_VIRTUAL)==0 + && sqlite3ExprIsSingleTableConstraint(pExpr, pTabList, iSrc, 0) + ){ + sqlite3ExprIfFalse(pParse, pTerm->pExpr, addrCont, SQLITE_JUMPIFNULL); + } + } + if( pLoop->wsFlags & WHERE_IPK ){ + int r1 = sqlite3GetTempReg(pParse); + sqlite3VdbeAddOp2(v, OP_Rowid, iCur, r1); + sqlite3VdbeAddOp4Int(v, OP_FilterAdd, pLevel->regFilter, 0, r1, 1); + sqlite3ReleaseTempReg(pParse, r1); + }else{ + Index *pIdx = pLoop->u.btree.pIndex; + int n = pLoop->u.btree.nEq; + int r1 = sqlite3GetTempRange(pParse, n); + int jj; + for(jj=0; jjpTable==pItem->pSTab ); + sqlite3ExprCodeLoadIndexColumn(pParse, pIdx, iCur, jj, r1+jj); + } + sqlite3VdbeAddOp4Int(v, OP_FilterAdd, pLevel->regFilter, 0, r1, n); + sqlite3ReleaseTempRange(pParse, r1, n); + } + sqlite3VdbeResolveLabel(v, addrCont); + sqlite3VdbeAddOp2(v, OP_Next, pLevel->iTabCur, addrTop+1); + VdbeCoverage(v); + sqlite3VdbeJumpHere(v, addrTop); + pLoop->wsFlags &= ~WHERE_BLOOMFILTER; + if( OptimizationDisabled(pParse->db, SQLITE_BloomPulldown) ) break; + while( ++iLevel < pWInfo->nLevel ){ + const SrcItem *pTabItem; + pLevel = &pWInfo->a[iLevel]; + pTabItem = &pWInfo->pTabList->a[pLevel->iFrom]; + if( pTabItem->fg.jointype & (JT_LEFT|JT_LTORJ) ) continue; + pLoop = pLevel->pWLoop; + if( NEVER(pLoop==0) ) continue; + if( pLoop->prereq & notReady ) continue; + if( (pLoop->wsFlags & (WHERE_BLOOMFILTER|WHERE_COLUMN_IN)) + ==WHERE_BLOOMFILTER + ){ + /* This is a candidate for bloom-filter pull-down (early evaluation). + ** The test that WHERE_COLUMN_IN is omitted is important, as we are + ** not able to do early evaluation of bloom filters that make use of + ** the IN operator */ + break; + } + } + }while( iLevel < pWInfo->nLevel ); + sqlite3VdbeJumpHere(v, addrOnce); + pParse->pIdxEpr = saved_pIdxEpr; + pParse->pIdxPartExpr = saved_pIdxPartExpr; +} + + #ifndef SQLITE_OMIT_VIRTUALTABLE /* -** Allocate and populate an sqlite3_index_info structure. It is the +** Return term iTerm of the WhereClause passed as the first argument. Terms +** are numbered from 0 upwards, starting with the terms in pWC->a[], then +** those in pWC->pOuter->a[] (if any), and so on. +*/ +static WhereTerm *termFromWhereClause(WhereClause *pWC, int iTerm){ + WhereClause *p; + for(p=pWC; p; p=p->pOuter){ + if( iTermnTerm ) return &p->a[iTerm]; + iTerm -= p->nTerm; + } + return 0; +} + +/* +** Allocate and populate an sqlite3_index_info structure. It is the ** responsibility of the caller to eventually release the structure -** by passing the pointer returned by this function to sqlite3_free(). +** by passing the pointer returned by this function to freeIndexInfo(). */ static sqlite3_index_info *allocateIndexInfo( - Parse *pParse, /* The parsing context */ + WhereInfo *pWInfo, /* The WHERE clause */ WhereClause *pWC, /* The WHERE clause being analyzed */ Bitmask mUnusable, /* Ignore terms with these prereqs */ - struct SrcList_item *pSrc, /* The FROM clause term that is the vtab */ - ExprList *pOrderBy, /* The ORDER BY clause */ + SrcItem *pSrc, /* The FROM clause term that is the vtab */ u16 *pmNoOmit /* Mask of terms not to omit */ ){ int i, j; int nTerm; + Parse *pParse = pWInfo->pParse; struct sqlite3_index_constraint *pIdxCons; struct sqlite3_index_orderby *pIdxOrderBy; struct sqlite3_index_constraint_usage *pUsage; @@ -140942,24 +170281,47 @@ static sqlite3_index_info *allocateIndexInfo( int nOrderBy; sqlite3_index_info *pIdxInfo; u16 mNoOmit = 0; + const Table *pTab; + int eDistinct = 0; + ExprList *pOrderBy = pWInfo->pOrderBy; + WhereClause *p; - /* Count the number of possible WHERE clause constraints referring - ** to this virtual table */ - for(i=nTerm=0, pTerm=pWC->a; inTerm; i++, pTerm++){ - if( pTerm->leftCursor != pSrc->iCursor ) continue; - if( pTerm->prereqRight & mUnusable ) continue; - assert( IsPowerOfTwo(pTerm->eOperator & ~WO_EQUIV) ); - testcase( pTerm->eOperator & WO_IN ); - testcase( pTerm->eOperator & WO_ISNULL ); - testcase( pTerm->eOperator & WO_IS ); - testcase( pTerm->eOperator & WO_ALL ); - if( (pTerm->eOperator & ~(WO_EQUIV))==0 ) continue; - if( pTerm->wtFlags & TERM_VNULL ) continue; - assert( pTerm->u.leftColumn>=(-1) ); - nTerm++; + assert( pSrc!=0 ); + pTab = pSrc->pSTab; + assert( pTab!=0 ); + assert( IsVirtual(pTab) ); + + /* Find all WHERE clause constraints referring to this virtual table. + ** Mark each term with the TERM_OK flag. Set nTerm to the number of + ** terms found. + */ + for(p=pWC, nTerm=0; p; p=p->pOuter){ + for(i=0, pTerm=p->a; inTerm; i++, pTerm++){ + pTerm->wtFlags &= ~TERM_OK; + if( pTerm->leftCursor != pSrc->iCursor ) continue; + if( pTerm->prereqRight & mUnusable ) continue; + assert( IsPowerOfTwo(pTerm->eOperator & ~WO_EQUIV) ); + testcase( pTerm->eOperator & WO_IN ); + testcase( pTerm->eOperator & WO_ISNULL ); + testcase( pTerm->eOperator & WO_IS ); + testcase( pTerm->eOperator & WO_ALL ); + if( (pTerm->eOperator & ~(WO_EQUIV))==0 ) continue; + if( pTerm->wtFlags & TERM_VNULL ) continue; + + assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 ); + assert( pTerm->u.x.leftColumn>=XN_ROWID ); + assert( pTerm->u.x.leftColumnnCol ); + if( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))!=0 + && !constraintCompatibleWithOuterJoin(pTerm,pSrc) + ){ + continue; + } + nTerm++; + pTerm->wtFlags |= TERM_OK; + } } - /* If the ORDER BY clause contains only columns in the current + /* If the ORDER BY clause contains only columns in the current ** virtual table then allocate space for the aOrderBy part of ** the sqlite3_index_info structure. */ @@ -140968,10 +170330,52 @@ static sqlite3_index_info *allocateIndexInfo( int n = pOrderBy->nExpr; for(i=0; ia[i].pExpr; - if( pExpr->op!=TK_COLUMN || pExpr->iTable!=pSrc->iCursor ) break; + Expr *pE2; + + /* Skip over constant terms in the ORDER BY clause */ + if( sqlite3ExprIsConstant(0, pExpr) ){ + continue; + } + + /* Virtual tables are unable to deal with NULLS FIRST */ + if( pOrderBy->a[i].fg.sortFlags & KEYINFO_ORDER_BIGNULL ) break; + + /* First case - a direct column references without a COLLATE operator */ + if( pExpr->op==TK_COLUMN && pExpr->iTable==pSrc->iCursor ){ + assert( pExpr->iColumn>=XN_ROWID && pExpr->iColumnnCol ); + continue; + } + + /* 2nd case - a column reference with a COLLATE operator. Only match + ** of the COLLATE operator matches the collation of the column. */ + if( pExpr->op==TK_COLLATE + && (pE2 = pExpr->pLeft)->op==TK_COLUMN + && pE2->iTable==pSrc->iCursor + ){ + const char *zColl; /* The collating sequence name */ + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + assert( pExpr->u.zToken!=0 ); + assert( pE2->iColumn>=XN_ROWID && pE2->iColumnnCol ); + pExpr->iColumn = pE2->iColumn; + if( pE2->iColumn<0 ) continue; /* Collseq does not matter for rowid */ + zColl = sqlite3ColumnColl(&pTab->aCol[pE2->iColumn]); + if( zColl==0 ) zColl = sqlite3StrBINARY; + if( sqlite3_stricmp(pExpr->u.zToken, zColl)==0 ) continue; + } + + /* No matches cause a break out of the loop */ + break; } - if( i==n){ + if( i==n ){ + int bSortByGroup = (pWInfo->wctrlFlags & WHERE_SORTBYGROUP)!=0; nOrderBy = n; + if( (pWInfo->wctrlFlags & WHERE_DISTINCTBY) && !pSrc->fg.rowidUsed ){ + eDistinct = 2 + bSortByGroup; + }else if( pWInfo->wctrlFlags & WHERE_GROUPBY ){ + eDistinct = 1 - bSortByGroup; + }else if( pWInfo->wctrlFlags & WHERE_WANT_DISTINCT ){ + eDistinct = 3; + } } } @@ -140979,101 +170383,131 @@ static sqlite3_index_info *allocateIndexInfo( */ pIdxInfo = sqlite3DbMallocZero(pParse->db, sizeof(*pIdxInfo) + (sizeof(*pIdxCons) + sizeof(*pUsage))*nTerm - + sizeof(*pIdxOrderBy)*nOrderBy + sizeof(*pHidden) ); + + sizeof(*pIdxOrderBy)*nOrderBy + + SZ_HIDDENINDEXINFO(nTerm) ); if( pIdxInfo==0 ){ sqlite3ErrorMsg(pParse, "out of memory"); return 0; } - - /* Initialize the structure. The sqlite3_index_info structure contains - ** many fields that are declared "const" to prevent xBestIndex from - ** changing them. We have to do some funky casting in order to - ** initialize those fields. - */ pHidden = (struct HiddenIndexInfo*)&pIdxInfo[1]; - pIdxCons = (struct sqlite3_index_constraint*)&pHidden[1]; + pIdxCons = (struct sqlite3_index_constraint*)&pHidden->aRhs[nTerm]; pIdxOrderBy = (struct sqlite3_index_orderby*)&pIdxCons[nTerm]; pUsage = (struct sqlite3_index_constraint_usage*)&pIdxOrderBy[nOrderBy]; - *(int*)&pIdxInfo->nConstraint = nTerm; - *(int*)&pIdxInfo->nOrderBy = nOrderBy; - *(struct sqlite3_index_constraint**)&pIdxInfo->aConstraint = pIdxCons; - *(struct sqlite3_index_orderby**)&pIdxInfo->aOrderBy = pIdxOrderBy; - *(struct sqlite3_index_constraint_usage**)&pIdxInfo->aConstraintUsage = - pUsage; - + pIdxInfo->aConstraint = pIdxCons; + pIdxInfo->aOrderBy = pIdxOrderBy; + pIdxInfo->aConstraintUsage = pUsage; + pIdxInfo->colUsed = (sqlite3_int64)pSrc->colUsed; + if( HasRowid(pTab)==0 ){ + /* Ensure that all bits associated with PK columns are set. This is to + ** ensure they are available for cases like RIGHT joins or OR loops. */ + Index *pPk = sqlite3PrimaryKeyIndex((Table*)pTab); + assert( pPk!=0 ); + for(i=0; inKeyCol; i++){ + int iCol = pPk->aiColumn[i]; + assert( iCol>=0 ); + if( iCol>=BMS-1 ) iCol = BMS-1; + pIdxInfo->colUsed |= MASKBIT(iCol); + } + } pHidden->pWC = pWC; pHidden->pParse = pParse; - for(i=j=0, pTerm=pWC->a; inTerm; i++, pTerm++){ - u16 op; - if( pTerm->leftCursor != pSrc->iCursor ) continue; - if( pTerm->prereqRight & mUnusable ) continue; - assert( IsPowerOfTwo(pTerm->eOperator & ~WO_EQUIV) ); - testcase( pTerm->eOperator & WO_IN ); - testcase( pTerm->eOperator & WO_IS ); - testcase( pTerm->eOperator & WO_ISNULL ); - testcase( pTerm->eOperator & WO_ALL ); - if( (pTerm->eOperator & ~(WO_EQUIV))==0 ) continue; - if( pTerm->wtFlags & TERM_VNULL ) continue; - if( (pSrc->fg.jointype & JT_LEFT)!=0 - && !ExprHasProperty(pTerm->pExpr, EP_FromJoin) - && (pTerm->eOperator & (WO_IS|WO_ISNULL)) - ){ - /* An "IS" term in the WHERE clause where the virtual table is the rhs - ** of a LEFT JOIN. Do not pass this term to the virtual table - ** implementation, as this can lead to incorrect results from SQL such - ** as: - ** - ** "LEFT JOIN vtab WHERE vtab.col IS NULL" */ - testcase( pTerm->eOperator & WO_ISNULL ); - testcase( pTerm->eOperator & WO_IS ); - continue; - } - assert( pTerm->u.leftColumn>=(-1) ); - pIdxCons[j].iColumn = pTerm->u.leftColumn; - pIdxCons[j].iTermOffset = i; - op = pTerm->eOperator & WO_ALL; - if( op==WO_IN ) op = WO_EQ; - if( op==WO_AUX ){ - pIdxCons[j].op = pTerm->eMatchOp; - }else if( op & (WO_ISNULL|WO_IS) ){ - if( op==WO_ISNULL ){ - pIdxCons[j].op = SQLITE_INDEX_CONSTRAINT_ISNULL; - }else{ - pIdxCons[j].op = SQLITE_INDEX_CONSTRAINT_IS; - } - }else{ - pIdxCons[j].op = (u8)op; - /* The direct assignment in the previous line is possible only because - ** the WO_ and SQLITE_INDEX_CONSTRAINT_ codes are identical. The - ** following asserts verify this fact. */ - assert( WO_EQ==SQLITE_INDEX_CONSTRAINT_EQ ); - assert( WO_LT==SQLITE_INDEX_CONSTRAINT_LT ); - assert( WO_LE==SQLITE_INDEX_CONSTRAINT_LE ); - assert( WO_GT==SQLITE_INDEX_CONSTRAINT_GT ); - assert( WO_GE==SQLITE_INDEX_CONSTRAINT_GE ); - assert( pTerm->eOperator&(WO_IN|WO_EQ|WO_LT|WO_LE|WO_GT|WO_GE|WO_AUX) ); - - if( op & (WO_LT|WO_LE|WO_GT|WO_GE) - && sqlite3ExprIsVector(pTerm->pExpr->pRight) - ){ - if( i<16 ) mNoOmit |= (1 << i); - if( op==WO_LT ) pIdxCons[j].op = WO_LE; - if( op==WO_GT ) pIdxCons[j].op = WO_GE; + pHidden->eDistinct = eDistinct; + pHidden->mIn = 0; + for(p=pWC, i=j=0; p; p=p->pOuter){ + int nLast = i+p->nTerm;; + for(pTerm=p->a; iwtFlags & TERM_OK)==0 ) continue; + pIdxCons[j].iColumn = pTerm->u.x.leftColumn; + pIdxCons[j].iTermOffset = i; + op = pTerm->eOperator & WO_ALL; + if( op==WO_IN ){ + if( (pTerm->wtFlags & TERM_SLICE)==0 ){ + pHidden->mIn |= SMASKBIT32(j); + } + op = WO_EQ; + } + if( op==WO_AUX ){ + pIdxCons[j].op = pTerm->eMatchOp; + }else if( op & (WO_ISNULL|WO_IS) ){ + if( op==WO_ISNULL ){ + pIdxCons[j].op = SQLITE_INDEX_CONSTRAINT_ISNULL; + }else{ + pIdxCons[j].op = SQLITE_INDEX_CONSTRAINT_IS; + } + }else{ + pIdxCons[j].op = (u8)op; + /* The direct assignment in the previous line is possible only because + ** the WO_ and SQLITE_INDEX_CONSTRAINT_ codes are identical. The + ** following asserts verify this fact. */ + assert( WO_EQ==SQLITE_INDEX_CONSTRAINT_EQ ); + assert( WO_LT==SQLITE_INDEX_CONSTRAINT_LT ); + assert( WO_LE==SQLITE_INDEX_CONSTRAINT_LE ); + assert( WO_GT==SQLITE_INDEX_CONSTRAINT_GT ); + assert( WO_GE==SQLITE_INDEX_CONSTRAINT_GE ); + assert( pTerm->eOperator&(WO_IN|WO_EQ|WO_LT|WO_LE|WO_GT|WO_GE|WO_AUX) ); + + if( op & (WO_LT|WO_LE|WO_GT|WO_GE) + && sqlite3ExprIsVector(pTerm->pExpr->pRight) + ){ + testcase( j!=i ); + if( j<16 ) mNoOmit |= (1 << j); + if( op==WO_LT ) pIdxCons[j].op = WO_LE; + if( op==WO_GT ) pIdxCons[j].op = WO_GE; + } } - } - j++; + j++; + } } - for(i=0; inConstraint = j; + for(i=j=0; ia[i].pExpr; - pIdxOrderBy[i].iColumn = pExpr->iColumn; - pIdxOrderBy[i].desc = pOrderBy->a[i].sortOrder; + if( sqlite3ExprIsConstant(0, pExpr) ) continue; + assert( pExpr->op==TK_COLUMN + || (pExpr->op==TK_COLLATE && pExpr->pLeft->op==TK_COLUMN + && pExpr->iColumn==pExpr->pLeft->iColumn) ); + pIdxOrderBy[j].iColumn = pExpr->iColumn; + pIdxOrderBy[j].desc = pOrderBy->a[i].fg.sortFlags & KEYINFO_ORDER_DESC; + j++; } + pIdxInfo->nOrderBy = j; *pmNoOmit = mNoOmit; return pIdxInfo; } +/* +** Free and zero the sqlite3_index_info.idxStr value if needed. +*/ +static void freeIdxStr(sqlite3_index_info *pIdxInfo){ + if( pIdxInfo->needToFreeIdxStr ){ + sqlite3_free(pIdxInfo->idxStr); + pIdxInfo->idxStr = 0; + pIdxInfo->needToFreeIdxStr = 0; + } +} + +/* +** Free an sqlite3_index_info structure allocated by allocateIndexInfo() +** and possibly modified by xBestIndex methods. +*/ +static void freeIndexInfo(sqlite3 *db, sqlite3_index_info *pIdxInfo){ + HiddenIndexInfo *pHidden; + int i; + assert( pIdxInfo!=0 ); + pHidden = (HiddenIndexInfo*)&pIdxInfo[1]; + assert( pHidden->pParse!=0 ); + assert( pHidden->pParse->db==db ); + for(i=0; inConstraint; i++){ + sqlite3ValueFree(pHidden->aRhs[i]); /* IMP: R-14553-25174 */ + pHidden->aRhs[i] = 0; + } + freeIdxStr(pIdxInfo); + sqlite3DbFree(db, pIdxInfo); +} + /* ** The table object reference passed as the second argument to this function ** must represent a virtual table. This function invokes the xBestIndex() @@ -141091,12 +170525,16 @@ static sqlite3_index_info *allocateIndexInfo( ** that this is required. */ static int vtabBestIndex(Parse *pParse, Table *pTab, sqlite3_index_info *p){ - sqlite3_vtab *pVtab = sqlite3GetVTable(pParse->db, pTab)->pVtab; int rc; + sqlite3_vtab *pVtab; - TRACE_IDX_INPUTS(p); + assert( IsVirtual(pTab) ); + pVtab = sqlite3GetVTable(pParse->db, pTab)->pVtab; + whereTraceIndexInfoInputs(p, pTab); + pParse->db->nSchemaLock++; rc = pVtab->pModule->xBestIndex(pVtab, p); - TRACE_IDX_OUTPUTS(p); + pParse->db->nSchemaLock--; + whereTraceIndexInfoOutputs(p, pTab); if( rc!=SQLITE_OK && rc!=SQLITE_CONSTRAINT ){ if( rc==SQLITE_NOMEM ){ @@ -141107,13 +170545,16 @@ static int vtabBestIndex(Parse *pParse, Table *pTab, sqlite3_index_info *p){ sqlite3ErrorMsg(pParse, "%s", pVtab->zErrMsg); } } + if( pTab->u.vtab.p->bAllSchemas ){ + sqlite3VtabUsesAllSchemas(pParse); + } sqlite3_free(pVtab->zErrMsg); pVtab->zErrMsg = 0; return rc; } #endif /* !defined(SQLITE_OMIT_VIRTUALTABLE) */ -#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 +#ifdef SQLITE_ENABLE_STAT4 /* ** Estimate the location of a particular key among all keys in an ** index. Store the results in aStat as follows: @@ -141124,8 +170565,8 @@ static int vtabBestIndex(Parse *pParse, Table *pTab, sqlite3_index_info *p){ ** Return the index of the sample that is the smallest sample that ** is greater than or equal to pRec. Note that this index is not an index ** into the aSample[] array - it is an index into a virtual set of samples -** based on the contents of aSample[] and the number of fields in record -** pRec. +** based on the contents of aSample[] and the number of fields in record +** pRec. */ static int whereKeyStats( Parse *pParse, /* Database connection */ @@ -141149,7 +170590,8 @@ static int whereKeyStats( #endif assert( pRec!=0 ); assert( pIdx->nSample>0 ); - assert( pRec->nField>0 && pRec->nField<=pIdx->nSampleCol ); + assert( pRec->nField>0 ); + /* Do a binary search to find the first sample greater than or equal ** to pRec. If pRec contains a single field, the set of samples to search @@ -141161,41 +170603,46 @@ static int whereKeyStats( ** consider prefixes of those samples. For example, if the set of samples ** in aSample is: ** - ** aSample[0] = (a, 5) - ** aSample[1] = (a, 10) - ** aSample[2] = (b, 5) - ** aSample[3] = (c, 100) + ** aSample[0] = (a, 5) + ** aSample[1] = (a, 10) + ** aSample[2] = (b, 5) + ** aSample[3] = (c, 100) ** aSample[4] = (c, 105) ** - ** Then the search space should ideally be the samples above and the - ** unique prefixes [a], [b] and [c]. But since that is hard to organize, + ** Then the search space should ideally be the samples above and the + ** unique prefixes [a], [b] and [c]. But since that is hard to organize, ** the code actually searches this set: ** - ** 0: (a) - ** 1: (a, 5) - ** 2: (a, 10) - ** 3: (a, 10) - ** 4: (b) - ** 5: (b, 5) - ** 6: (c) - ** 7: (c, 100) + ** 0: (a) + ** 1: (a, 5) + ** 2: (a, 10) + ** 3: (a, 10) + ** 4: (b) + ** 5: (b, 5) + ** 6: (c) + ** 7: (c, 100) ** 8: (c, 105) ** 9: (c, 105) ** ** For each sample in the aSample[] array, N samples are present in the - ** effective sample array. In the above, samples 0 and 1 are based on + ** effective sample array. In the above, samples 0 and 1 are based on ** sample aSample[0]. Samples 2 and 3 on aSample[1] etc. ** ** Often, sample i of each block of N effective samples has (i+1) fields. ** Except, each sample may be extended to ensure that it is greater than or - ** equal to the previous sample in the array. For example, in the above, - ** sample 2 is the first sample of a block of N samples, so at first it - ** appears that it should be 1 field in size. However, that would make it - ** smaller than sample 1, so the binary search would not work. As a result, - ** it is extended to two fields. The duplicates that this creates do not + ** equal to the previous sample in the array. For example, in the above, + ** sample 2 is the first sample of a block of N samples, so at first it + ** appears that it should be 1 field in size. However, that would make it + ** smaller than sample 1, so the binary search would not work. As a result, + ** it is extended to two fields. The duplicates that this creates do not ** cause any problems. */ - nField = pRec->nField; + if( !HasRowid(pIdx->pTable) && IsPrimaryKeyIndex(pIdx) ){ + nField = pIdx->nKeyCol; + }else{ + nField = pIdx->nColumn; + } + nField = MIN(pRec->nField, nField); iCol = 0; iSample = pIdx->nSample * nField; do{ @@ -141206,7 +170653,7 @@ static int whereKeyStats( iSamp = iTest / nField; if( iSamp>0 ){ /* The proposed effective sample is a prefix of sample aSample[iSamp]. - ** Specifically, the shortest prefix of at least (1 + iTest%nField) + ** Specifically, the shortest prefix of at least (1 + iTest%nField) ** fields that is greater than the previous effective sample. */ for(n=(iTest % nField) + 1; nnSample ); assert( iCol==nField-1 ); pRec->nField = nField; - assert( 0==sqlite3VdbeRecordCompare(aSample[i].n, aSample[i].p, pRec) - || pParse->db->mallocFailed + assert( 0==sqlite3VdbeRecordCompare(aSample[i].n, aSample[i].p, pRec) + || pParse->db->mallocFailed ); }else{ /* Unless i==pIdx->nSample, indicating that pRec is larger than @@ -141250,7 +170697,7 @@ static int whereKeyStats( ** (iCol+1) field prefix of sample i. */ assert( i<=pIdx->nSample && i>=0 ); pRec->nField = iCol+1; - assert( i==pIdx->nSample + assert( i==pIdx->nSample || sqlite3VdbeRecordCompare(aSample[i].n, aSample[i].p, pRec)>0 || pParse->db->mallocFailed ); @@ -141261,12 +170708,12 @@ static int whereKeyStats( if( iCol>0 ){ pRec->nField = iCol; assert( sqlite3VdbeRecordCompare(aSample[i].n, aSample[i].p, pRec)<=0 - || pParse->db->mallocFailed ); + || pParse->db->mallocFailed || CORRUPT_DB ); } if( i>0 ){ pRec->nField = nField; assert( sqlite3VdbeRecordCompare(aSample[i-1].n, aSample[i-1].p, pRec)<0 - || pParse->db->mallocFailed ); + || pParse->db->mallocFailed || CORRUPT_DB ); } } } @@ -141278,12 +170725,12 @@ static int whereKeyStats( aStat[0] = aSample[i].anLt[iCol]; aStat[1] = aSample[i].anEq[iCol]; }else{ - /* At this point, the (iCol+1) field prefix of aSample[i] is the first + /* At this point, the (iCol+1) field prefix of aSample[i] is the first ** sample that is greater than pRec. Or, if i==pIdx->nSample then pRec ** is larger than all samples in the array. */ tRowcnt iUpper, iGap; if( i>=pIdx->nSample ){ - iUpper = sqlite3LogEstToInt(pIdx->aiRowLogEst[0]); + iUpper = pIdx->nRowEst0; }else{ iUpper = aSample[i].anLt[iCol]; } @@ -141306,11 +170753,11 @@ static int whereKeyStats( pRec->nField = nField; return i; } -#endif /* SQLITE_ENABLE_STAT3_OR_STAT4 */ +#endif /* SQLITE_ENABLE_STAT4 */ /* ** If it is not NULL, pTerm is a term that provides an upper or lower -** bound on a range scan. Without considering pTerm, it is estimated +** bound on a range scan. Without considering pTerm, it is estimated ** that the scan will visit nNew rows. This function returns the number ** estimated to be visited after taking pTerm into account. ** @@ -141332,7 +170779,7 @@ static LogEst whereRangeAdjust(WhereTerm *pTerm, LogEst nNew){ } -#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 +#ifdef SQLITE_ENABLE_STAT4 /* ** Return the affinity for a single column of an index. */ @@ -141341,24 +170788,25 @@ SQLITE_PRIVATE char sqlite3IndexColumnAffinity(sqlite3 *db, Index *pIdx, int iCo if( !pIdx->zColAff ){ if( sqlite3IndexAffinityStr(db, pIdx)==0 ) return SQLITE_AFF_BLOB; } + assert( pIdx->zColAff[iCol]!=0 ); return pIdx->zColAff[iCol]; } #endif -#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 -/* +#ifdef SQLITE_ENABLE_STAT4 +/* ** This function is called to estimate the number of rows visited by a ** range-scan on a skip-scan index. For example: ** ** CREATE INDEX i1 ON t1(a, b, c); ** SELECT * FROM t1 WHERE a=? AND c BETWEEN ? AND ?; ** -** Value pLoop->nOut is currently set to the estimated number of rows -** visited for scanning (a=? AND b=?). This function reduces that estimate +** Value pLoop->nOut is currently set to the estimated number of rows +** visited for scanning (a=? AND b=?). This function reduces that estimate ** by some factor to account for the (c BETWEEN ? AND ?) expression based -** on the stat4 data for the index. this scan will be peformed multiple -** times (once for each (a,b) combination that matches a=?) is dealt with +** on the stat4 data for the index. this scan will be performed multiple +** times (once for each (a,b) combination that matches a=?) is dealt with ** by the caller. ** ** It does this by scanning through all stat4 samples, comparing values @@ -141379,7 +170827,7 @@ SQLITE_PRIVATE char sqlite3IndexColumnAffinity(sqlite3 *db, Index *pIdx, int iCo ** estimate of the number of rows delivered remains unchanged), *pbDone ** is left as is. ** -** If an error occurs, an SQLite error code is returned. Otherwise, +** If an error occurs, an SQLite error code is returned. Otherwise, ** SQLITE_OK. */ static int whereRangeSkipScanEst( @@ -141397,7 +170845,7 @@ static int whereRangeSkipScanEst( int rc = SQLITE_OK; u8 aff = sqlite3IndexColumnAffinity(db, p, nEq); CollSeq *pColl; - + sqlite3_value *p1 = 0; /* Value extracted from pLower */ sqlite3_value *p2 = 0; /* Value extracted from pUpper */ sqlite3_value *pVal = 0; /* Value extracted from record */ @@ -141429,7 +170877,7 @@ static int whereRangeSkipScanEst( nDiff = (nUpper - nLower); if( nDiff<=0 ) nDiff = 1; - /* If there is both an upper and lower bound specified, and the + /* If there is both an upper and lower bound specified, and the ** comparisons indicate that they are close together, use the fallback ** method (assume that the scan visits 1/64 of the rows) for estimating ** the number of rows visited. Otherwise, estimate the number of rows @@ -141438,7 +170886,7 @@ static int whereRangeSkipScanEst( int nAdjust = (sqlite3LogEst(p->nSample) - sqlite3LogEst(nDiff)); pLoop->nOut -= nAdjust; *pbDone = 1; - WHERETRACE(0x10, ("range skip-scan regions: %u..%u adjust=%d est=%d\n", + WHERETRACE(0x20, ("range skip-scan regions: %u..%u adjust=%d est=%d\n", nLower, nUpper, nAdjust*-1, pLoop->nOut)); } @@ -141452,7 +170900,7 @@ static int whereRangeSkipScanEst( return rc; } -#endif /* SQLITE_ENABLE_STAT3_OR_STAT4 */ +#endif /* SQLITE_ENABLE_STAT4 */ /* ** This function is used to estimate the number of rows that will be visited @@ -141476,7 +170924,7 @@ static int whereRangeSkipScanEst( ** ** ... FROM t1 WHERE a = ? AND b > ? AND b < ? ... ** -** then nEq is set to 1 (as the range restricted column, b, is the second +** then nEq is set to 1 (as the range restricted column, b, is the second ** left-most column of the index). Or, if the query is: ** ** ... FROM t1 WHERE a > ? AND a < ? ... @@ -141484,13 +170932,13 @@ static int whereRangeSkipScanEst( ** then nEq is set to 0. ** ** When this function is called, *pnOut is set to the sqlite3LogEst() of the -** number of rows that the index scan is expected to visit without -** considering the range constraints. If nEq is 0, then *pnOut is the number of +** number of rows that the index scan is expected to visit without +** considering the range constraints. If nEq is 0, then *pnOut is the number of ** rows in the index. Assuming no error occurs, *pnOut is adjusted (reduced) ** to account for the range constraints pLower and pUpper. -** +** ** In the absence of sqlite_stat4 ANALYZE data, or if such data cannot be -** used, a single range inequality reduces the search space by a factor of 4. +** used, a single range inequality reduces the search space by a factor of 4. ** and a pair of constraints (x>? AND xnOut; LogEst nNew; -#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 +#ifdef SQLITE_ENABLE_STAT4 Index *p = pLoop->u.btree.pIndex; int nEq = pLoop->u.btree.nEq; - if( p->nSample>0 && nEqnSampleCol - && OptimizationEnabled(pParse->db, SQLITE_Stat34) + if( p->nSample>0 && ALWAYS(nEqnSampleCol) + && OptimizationEnabled(pParse->db, SQLITE_Stat4) ){ if( nEq==pBuilder->nRecValid ){ UnpackedRecord *pRec = pBuilder->pRec; @@ -141518,7 +170966,7 @@ static int whereRangeScanEst( int nBtm = pLoop->u.btree.nBtm; int nTop = pLoop->u.btree.nTop; - /* Variable iLower will be set to the estimate of the number of rows in + /* Variable iLower will be set to the estimate of the number of rows in ** the index that are less than the lower bound of the range query. The ** lower bound being the concatenation of $P and $L, where $P is the ** key-prefix formed by the nEq values matched against the nEq left-most @@ -141527,7 +170975,7 @@ static int whereRangeScanEst( ** Or, if pLower is NULL or $L cannot be extracted from it (because it ** is not a simple variable or literal value), the lower bound of the ** range is $P. Due to a quirk in the way whereKeyStats() works, even - ** if $L is available, whereKeyStats() is called for both ($P) and + ** if $L is available, whereKeyStats() is called for both ($P) and ** ($P:$L) and the larger of the two returned values is used. ** ** Similarly, iUpper is to be set to the estimate of the number of rows @@ -141551,7 +170999,7 @@ static int whereRangeScanEst( iLower = 0; iUpper = p->nRowEst0; }else{ - /* Note: this call could be optimized away - since the same values must + /* Note: this call could be optimized away - since the same values must ** have been requested when testing key $P in whereEqualScanEst(). */ whereKeyStats(pParse, p, pRec, 0, a); iLower = a[0]; @@ -141608,15 +171056,16 @@ static int whereRangeScanEst( /* TUNING: If both iUpper and iLower are derived from the same ** sample, then assume they are 4x more selective. This brings ** the estimated selectivity more in line with what it would be - ** if estimated without the use of STAT3/4 tables. */ - if( iLwrIdx==iUprIdx ) nNew -= 20; assert( 20==sqlite3LogEst(4) ); + ** if estimated without the use of STAT4 tables. */ + if( iLwrIdx==iUprIdx ){ nNew -= 20; } + assert( 20==sqlite3LogEst(4) ); }else{ nNew = 10; assert( 10==sqlite3LogEst(2) ); } if( nNewwtFlags & TERM_VNULL)==0 ); + assert( pUpper==0 || (pUpper->wtFlags & TERM_VNULL)==0 || pParse->nErr>0 ); nNew = whereRangeAdjust(pLower, nOut); nNew = whereRangeAdjust(pUpper, nNew); @@ -141639,7 +171088,7 @@ static int whereRangeScanEst( ** reduced by an additional 75%. This means that, by default, an open-ended ** range query (e.g. col > ?) is assumed to match 1/4 of the rows in the ** index. While a closed range (e.g. col BETWEEN ? AND ?) is estimated to - ** match 1/64 of the index. */ + ** match 1/64 of the index. */ if( pLower && pLower->truthProb>0 && pUpper && pUpper->truthProb>0 ){ nNew -= 20; } @@ -141649,7 +171098,7 @@ static int whereRangeScanEst( if( nNewnOut>nOut ){ - WHERETRACE(0x10,("Range scan lowers nOut from %d to %d\n", + WHERETRACE(0x20,("Range scan lowers nOut from %d to %d\n", pLoop->nOut, nOut)); } #endif @@ -141657,16 +171106,16 @@ static int whereRangeScanEst( return rc; } -#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 +#ifdef SQLITE_ENABLE_STAT4 /* ** Estimate the number of rows that will be returned based on ** an equality constraint x=VALUE and where that VALUE occurs in ** the histogram data. This only works when x is the left-most -** column of an index and sqlite_stat3 histogram data is available +** column of an index and sqlite_stat4 histogram data is available ** for that index. When pExpr==NULL that means the constraint is ** "x IS NULL" instead of "x=VALUE". ** -** Write the estimated row count into *pnRow and return SQLITE_OK. +** Write the estimated row count into *pnRow and return SQLITE_OK. ** If unable to make an estimate, leave *pnRow unchanged and return ** non-zero. ** @@ -141714,15 +171163,15 @@ static int whereEqualScanEst( pBuilder->nRecValid = nEq; whereKeyStats(pParse, p, pRec, 0, a); - WHERETRACE(0x10,("equality scan regions %s(%d): %d\n", + WHERETRACE(0x20,("equality scan regions %s(%d): %d\n", p->zName, nEq-1, (int)a[1])); *pnRow = a[1]; - + return rc; } -#endif /* SQLITE_ENABLE_STAT3_OR_STAT4 */ +#endif /* SQLITE_ENABLE_STAT4 */ -#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 +#ifdef SQLITE_ENABLE_STAT4 /* ** Estimate the number of rows that will be returned based on ** an IN constraint where the right-hand side of the IN operator @@ -141730,7 +171179,7 @@ static int whereEqualScanEst( ** ** WHERE x IN (1,2,3,4) ** -** Write the estimated row count into *pnRow and return SQLITE_OK. +** Write the estimated row count into *pnRow and return SQLITE_OK. ** If unable to make an estimate, leave *pnRow unchanged and return ** non-zero. ** @@ -141762,51 +171211,64 @@ static int whereInScanEst( } if( rc==SQLITE_OK ){ - if( nRowEst > nRow0 ) nRowEst = nRow0; + if( nRowEst > (tRowcnt)nRow0 ) nRowEst = nRow0; *pnRow = nRowEst; - WHERETRACE(0x10,("IN row estimate: est=%d\n", nRowEst)); + WHERETRACE(0x20,("IN row estimate: est=%d\n", nRowEst)); } assert( pBuilder->nRecValid==nRecValid ); return rc; } -#endif /* SQLITE_ENABLE_STAT3_OR_STAT4 */ +#endif /* SQLITE_ENABLE_STAT4 */ -#ifdef WHERETRACE_ENABLED +#if defined(WHERETRACE_ENABLED) || defined(SQLITE_DEBUG) /* ** Print the content of a WhereTerm object */ -static void whereTermPrint(WhereTerm *pTerm, int iTerm){ +SQLITE_PRIVATE void sqlite3WhereTermPrint(WhereTerm *pTerm, int iTerm){ if( pTerm==0 ){ sqlite3DebugPrintf("TERM-%-3d NULL\n", iTerm); }else{ - char zType[4]; + char zType[8]; char zLeft[50]; - memcpy(zType, "...", 4); + memcpy(zType, "....", 5); if( pTerm->wtFlags & TERM_VIRTUAL ) zType[0] = 'V'; if( pTerm->eOperator & WO_EQUIV ) zType[1] = 'E'; - if( ExprHasProperty(pTerm->pExpr, EP_FromJoin) ) zType[2] = 'L'; + if( ExprHasProperty(pTerm->pExpr, EP_OuterON) ) zType[2] = 'L'; + if( pTerm->wtFlags & TERM_CODED ) zType[3] = 'C'; if( pTerm->eOperator & WO_SINGLE ){ + assert( (pTerm->eOperator & (WO_OR|WO_AND))==0 ); sqlite3_snprintf(sizeof(zLeft),zLeft,"left={%d:%d}", - pTerm->leftCursor, pTerm->u.leftColumn); + pTerm->leftCursor, pTerm->u.x.leftColumn); }else if( (pTerm->eOperator & WO_OR)!=0 && pTerm->u.pOrInfo!=0 ){ - sqlite3_snprintf(sizeof(zLeft),zLeft,"indexable=0x%lld", + sqlite3_snprintf(sizeof(zLeft),zLeft,"indexable=0x%llx", pTerm->u.pOrInfo->indexable); }else{ sqlite3_snprintf(sizeof(zLeft),zLeft,"left=%d", pTerm->leftCursor); } + iTerm = pTerm->iTerm = MAX(iTerm,pTerm->iTerm); sqlite3DebugPrintf( - "TERM-%-3d %p %s %-12s prob=%-3d op=0x%03x wtFlags=0x%04x", - iTerm, pTerm, zType, zLeft, pTerm->truthProb, - pTerm->eOperator, pTerm->wtFlags); - if( pTerm->iField ){ - sqlite3DebugPrintf(" iField=%d\n", pTerm->iField); - }else{ - sqlite3DebugPrintf("\n"); + "TERM-%-3d %p %s %-12s op=%03x wtFlags=%04x", + iTerm, pTerm, zType, zLeft, pTerm->eOperator, pTerm->wtFlags); + /* The 0x10000 .wheretrace flag causes extra information to be + ** shown about each Term */ + if( sqlite3WhereTrace & 0x10000 ){ + sqlite3DebugPrintf(" prob=%-3d prereq=%llx,%llx", + pTerm->truthProb, (u64)pTerm->prereqAll, (u64)pTerm->prereqRight); + } + if( (pTerm->eOperator & (WO_OR|WO_AND))==0 && pTerm->u.x.iField ){ + sqlite3DebugPrintf(" iField=%d", pTerm->u.x.iField); } + if( pTerm->iParent>=0 ){ + sqlite3DebugPrintf(" iParent=%d", pTerm->iParent); + } + sqlite3DebugPrintf("\n"); sqlite3TreeViewExpr(0, pTerm->pExpr, 0); } } +SQLITE_PRIVATE void sqlite3ShowWhereTerm(WhereTerm *pTerm){ + sqlite3WhereTermPrint(pTerm, 0); +} #endif #ifdef WHERETRACE_ENABLED @@ -141816,7 +171278,7 @@ static void whereTermPrint(WhereTerm *pTerm, int iTerm){ SQLITE_PRIVATE void sqlite3WhereClausePrint(WhereClause *pWC){ int i; for(i=0; inTerm; i++){ - whereTermPrint(&pWC->a[i], i); + sqlite3WhereTermPrint(&pWC->a[i], i); } } #endif @@ -141824,17 +171286,41 @@ SQLITE_PRIVATE void sqlite3WhereClausePrint(WhereClause *pWC){ #ifdef WHERETRACE_ENABLED /* ** Print a WhereLoop object for debugging purposes +** +** Format example: +** +** .--- Position in WHERE clause rSetup, rRun, nOut ---. +** | | +** | .--- selfMask nTerm ------. | +** | | | | +** | | .-- prereq Idx wsFlags----. | | +** | | | Name | | | +** | | | __|__ nEq ---. ___|__ | __|__ +** | / \ / \ / \ | / \ / \ / \ +** 1.002.001 t2.t2xy 2 f 010241 N 2 cost 0,56,31 */ -static void whereLoopPrint(WhereLoop *p, WhereClause *pWC){ - WhereInfo *pWInfo = pWC->pWInfo; - int nb = 1+(pWInfo->pTabList->nSrc+3)/4; - struct SrcList_item *pItem = pWInfo->pTabList->a + p->iTab; - Table *pTab = pItem->pTab; - Bitmask mAll = (((Bitmask)1)<<(nb*4)) - 1; - sqlite3DebugPrintf("%c%2d.%0*llx.%0*llx", p->cId, - p->iTab, nb, p->maskSelf, nb, p->prereq & mAll); - sqlite3DebugPrintf(" %12s", - pItem->zAlias ? pItem->zAlias : pTab->zName); +SQLITE_PRIVATE void sqlite3WhereLoopPrint(const WhereLoop *p, const WhereClause *pWC){ + WhereInfo *pWInfo; + if( pWC ){ + int nb; + SrcItem *pItem; + Table *pTab; + Bitmask mAll; + + pWInfo = pWC->pWInfo; + nb = 1+(pWInfo->pTabList->nSrc+3)/4; + pItem = pWInfo->pTabList->a + p->iTab; + pTab = pItem->pSTab; + mAll = (((Bitmask)1)<<(nb*4)) - 1; + sqlite3DebugPrintf("%c%2d.%0*llx.%0*llx", p->cId, + p->iTab, nb, p->maskSelf, nb, p->prereq & mAll); + sqlite3DebugPrintf(" %12s", + pItem->zAlias ? pItem->zAlias : pTab->zName); + }else{ + pWInfo = 0; + sqlite3DebugPrintf("%c%2d.%03llx.%03llx %c%d", + p->cId, p->iTab, p->maskSelf, p->prereq & 0xfff, p->cId, p->iTab); + } if( (p->wsFlags & WHERE_VIRTUALTABLE)==0 ){ const char *zName; if( p->u.btree.pIndex && (zName = p->u.btree.pIndex->zName)!=0 ){ @@ -141850,7 +171336,7 @@ static void whereLoopPrint(WhereLoop *p, WhereClause *pWC){ }else{ char *z; if( p->u.vtab.idxStr ){ - z = sqlite3_mprintf("(%d,\"%s\",%x)", + z = sqlite3_mprintf("(%d,\"%s\",%#x)", p->u.vtab.idxNum, p->u.vtab.idxStr, p->u.vtab.omitMask); }else{ z = sqlite3_mprintf("(%d,%x)", p->u.vtab.idxNum, p->u.vtab.omitMask); @@ -141859,18 +171345,32 @@ static void whereLoopPrint(WhereLoop *p, WhereClause *pWC){ sqlite3_free(z); } if( p->wsFlags & WHERE_SKIPSCAN ){ - sqlite3DebugPrintf(" f %05x %d-%d", p->wsFlags, p->nLTerm,p->nSkip); + sqlite3DebugPrintf(" f %06x %d-%d", p->wsFlags, p->nLTerm,p->nSkip); }else{ - sqlite3DebugPrintf(" f %05x N %d", p->wsFlags, p->nLTerm); + sqlite3DebugPrintf(" f %06x N %d", p->wsFlags, p->nLTerm); } - sqlite3DebugPrintf(" cost %d,%d,%d\n", p->rSetup, p->rRun, p->nOut); - if( p->nLTerm && (sqlite3WhereTrace & 0x100)!=0 ){ + if( pWInfo && pWInfo->bStarUsed && p->rStarDelta!=0 ){ + sqlite3DebugPrintf(" cost %d,%d,%d delta=%d\n", + p->rSetup, p->rRun, p->nOut, p->rStarDelta); + }else{ + sqlite3DebugPrintf(" cost %d,%d,%d\n", p->rSetup, p->rRun, p->nOut); + } + if( p->nLTerm && (sqlite3WhereTrace & 0x4000)!=0 ){ int i; for(i=0; inLTerm; i++){ - whereTermPrint(p->aLTerm[i], i); + sqlite3WhereTermPrint(p->aLTerm[i], i); } } } +SQLITE_PRIVATE void sqlite3ShowWhereLoop(const WhereLoop *p){ + if( p ) sqlite3WhereLoopPrint(p, 0); +} +SQLITE_PRIVATE void sqlite3ShowWhereLoopList(const WhereLoop *p){ + while( p ){ + sqlite3ShowWhereLoop(p); + p = p->pNextLoop; + } +} #endif /* @@ -141902,12 +171402,18 @@ static void whereLoopClearUnion(sqlite3 *db, WhereLoop *p){ } /* -** Deallocate internal memory used by a WhereLoop object +** Deallocate internal memory used by a WhereLoop object. Leave the +** object in an initialized state, as if it had been newly allocated. */ static void whereLoopClear(sqlite3 *db, WhereLoop *p){ - if( p->aLTerm!=p->aLTermSpace ) sqlite3DbFreeNN(db, p->aLTerm); + if( p->aLTerm!=p->aLTermSpace ){ + sqlite3DbFreeNN(db, p->aLTerm); + p->aLTerm = p->aLTermSpace; + p->nLSlot = ArraySize(p->aLTermSpace); + } whereLoopClearUnion(db, p); - whereLoopInit(p); + p->nLTerm = 0; + p->wsFlags = 0; } /* @@ -141931,8 +171437,10 @@ static int whereLoopResize(sqlite3 *db, WhereLoop *p, int n){ */ static int whereLoopXfer(sqlite3 *db, WhereLoop *pTo, WhereLoop *pFrom){ whereLoopClearUnion(db, pTo); - if( whereLoopResize(db, pTo, pFrom->nLTerm) ){ - memset(&pTo->u, 0, sizeof(pTo->u)); + if( pFrom->nLTerm > pTo->nLSlot + && whereLoopResize(db, pTo, pFrom->nLTerm) + ){ + memset(pTo, 0, WHERE_LOOP_XFER_SZ); return SQLITE_NOMEM_BKPT; } memcpy(pTo, pFrom, WHERE_LOOP_XFER_SZ); @@ -141949,79 +171457,91 @@ static int whereLoopXfer(sqlite3 *db, WhereLoop *pTo, WhereLoop *pFrom){ ** Delete a WhereLoop object */ static void whereLoopDelete(sqlite3 *db, WhereLoop *p){ + assert( db!=0 ); whereLoopClear(db, p); - sqlite3DbFreeNN(db, p); + sqlite3DbNNFreeNN(db, p); } /* ** Free a WhereInfo structure */ static void whereInfoFree(sqlite3 *db, WhereInfo *pWInfo){ - int i; assert( pWInfo!=0 ); - for(i=0; inLevel; i++){ - WhereLevel *pLevel = &pWInfo->a[i]; - if( pLevel->pWLoop && (pLevel->pWLoop->wsFlags & WHERE_IN_ABLE) ){ - sqlite3DbFree(db, pLevel->u.in.aInLoop); - } - } + assert( db!=0 ); sqlite3WhereClauseClear(&pWInfo->sWC); while( pWInfo->pLoops ){ WhereLoop *p = pWInfo->pLoops; pWInfo->pLoops = p->pNextLoop; whereLoopDelete(db, p); } - sqlite3DbFreeNN(db, pWInfo); + while( pWInfo->pMemToFree ){ + WhereMemBlock *pNext = pWInfo->pMemToFree->pNext; + sqlite3DbNNFreeNN(db, pWInfo->pMemToFree); + pWInfo->pMemToFree = pNext; + } + sqlite3DbNNFreeNN(db, pWInfo); } /* -** Return TRUE if all of the following are true: +** Return TRUE if X is a proper subset of Y but is of equal or less cost. +** In other words, return true if all constraints of X are also part of Y +** and Y has additional constraints that might speed the search that X lacks +** but the cost of running X is not more than the cost of running Y. +** +** In other words, return true if the cost relationship between X and Y +** is inverted and needs to be adjusted. ** -** (1) X has the same or lower cost that Y -** (2) X uses fewer WHERE clause terms than Y -** (3) Every WHERE clause term used by X is also used by Y -** (4) X skips at least as many columns as Y -** (5) If X is a covering index, than Y is too +** Case 1: ** -** Conditions (2) and (3) mean that X is a "proper subset" of Y. -** If X is a proper subset of Y then Y is a better choice and ought -** to have a lower cost. This routine returns TRUE when that cost -** relationship is inverted and needs to be adjusted. Constraint (4) -** was added because if X uses skip-scan less than Y it still might -** deserve a lower cost even if it is a proper subset of Y. Constraint (5) -** was added because a covering index probably deserves to have a lower cost -** than a non-covering index even if it is a proper subset. +** (1a) X and Y use the same index. +** (1b) X has fewer == terms than Y +** (1c) Neither X nor Y use skip-scan +** (1d) X does not have a a greater cost than Y +** +** Case 2: +** +** (2a) X has the same or lower cost, or returns the same or fewer rows, +** than Y. +** (2b) X uses fewer WHERE clause terms than Y +** (2c) Every WHERE clause term used by X is also used by Y +** (2d) X skips at least as many columns as Y +** (2e) If X is a covering index, than Y is too */ static int whereLoopCheaperProperSubset( const WhereLoop *pX, /* First WhereLoop to compare */ const WhereLoop *pY /* Compare against this WhereLoop */ ){ int i, j; - if( pX->nLTerm-pX->nSkip >= pY->nLTerm-pY->nSkip ){ - return 0; /* X is not a subset of Y */ + if( pX->rRun>pY->rRun && pX->nOut>pY->nOut ) return 0; /* (1d) and (2a) */ + assert( (pX->wsFlags & WHERE_VIRTUALTABLE)==0 ); + assert( (pY->wsFlags & WHERE_VIRTUALTABLE)==0 ); + if( pX->u.btree.nEq < pY->u.btree.nEq /* (1b) */ + && pX->u.btree.pIndex==pY->u.btree.pIndex /* (1a) */ + && pX->nSkip==0 && pY->nSkip==0 /* (1c) */ + ){ + return 1; /* Case 1 is true */ } - if( pY->nSkip > pX->nSkip ) return 0; - if( pX->rRun >= pY->rRun ){ - if( pX->rRun > pY->rRun ) return 0; /* X costs more than Y */ - if( pX->nOut > pY->nOut ) return 0; /* X costs more than Y */ + if( pX->nLTerm-pX->nSkip >= pY->nLTerm-pY->nSkip ){ + return 0; /* (2b) */ } + if( pY->nSkip > pX->nSkip ) return 0; /* (2d) */ for(i=pX->nLTerm-1; i>=0; i--){ if( pX->aLTerm[i]==0 ) continue; for(j=pY->nLTerm-1; j>=0; j--){ if( pY->aLTerm[j]==pX->aLTerm[i] ) break; } - if( j<0 ) return 0; /* X not a subset of Y since term X[i] not used by Y */ + if( j<0 ) return 0; /* (2c) */ } - if( (pX->wsFlags&WHERE_IDX_ONLY)!=0 + if( (pX->wsFlags&WHERE_IDX_ONLY)!=0 && (pY->wsFlags&WHERE_IDX_ONLY)==0 ){ - return 0; /* Constraint (5) */ + return 0; /* (2e) */ } - return 1; /* All conditions meet */ + return 1; /* Case 2 is true */ } /* -** Try to adjust the cost of WhereLoop pTemplate upwards or downwards so -** that: +** Try to adjust the cost and number of output rows of WhereLoop pTemplate +** upwards or downwards so that: ** ** (1) pTemplate costs less than any other WhereLoops that are a proper ** subset of pTemplate @@ -142039,19 +171559,23 @@ static void whereLoopAdjustCost(const WhereLoop *p, WhereLoop *pTemplate){ if( p->iTab!=pTemplate->iTab ) continue; if( (p->wsFlags & WHERE_INDEXED)==0 ) continue; if( whereLoopCheaperProperSubset(p, pTemplate) ){ - /* Adjust pTemplate cost downward so that it is cheaper than its + /* Adjust pTemplate cost downward so that it is cheaper than its ** subset p. */ WHERETRACE(0x80,("subset cost adjustment %d,%d to %d,%d\n", - pTemplate->rRun, pTemplate->nOut, p->rRun, p->nOut-1)); - pTemplate->rRun = p->rRun; - pTemplate->nOut = p->nOut - 1; + pTemplate->rRun, pTemplate->nOut, + MIN(p->rRun, pTemplate->rRun), + MIN(p->nOut - 1, pTemplate->nOut))); + pTemplate->rRun = MIN(p->rRun, pTemplate->rRun); + pTemplate->nOut = MIN(p->nOut - 1, pTemplate->nOut); }else if( whereLoopCheaperProperSubset(pTemplate, p) ){ /* Adjust pTemplate cost upward so that it is costlier than p since ** pTemplate is a proper subset of p */ WHERETRACE(0x80,("subset cost adjustment %d,%d to %d,%d\n", - pTemplate->rRun, pTemplate->nOut, p->rRun, p->nOut+1)); - pTemplate->rRun = p->rRun; - pTemplate->nOut = p->nOut + 1; + pTemplate->rRun, pTemplate->nOut, + MAX(p->rRun, pTemplate->rRun), + MAX(p->nOut + 1, pTemplate->nOut))); + pTemplate->rRun = MAX(p->rRun, pTemplate->rRun); + pTemplate->nOut = MAX(p->nOut + 1, pTemplate->nOut); } } } @@ -142085,7 +171609,7 @@ static WhereLoop **whereLoopFindLesser( /* In the current implementation, the rSetup value is either zero ** or the cost of building an automatic index (NlogN) and the NlogN ** is the same for compatible WhereLoops. */ - assert( p->rSetup==0 || pTemplate->rSetup==0 + assert( p->rSetup==0 || pTemplate->rSetup==0 || p->rSetup==pTemplate->rSetup ); /* whereLoopAddBtree() always generates and inserts the automatic index @@ -142093,7 +171617,7 @@ static WhereLoop **whereLoopFindLesser( ** rSetup. Call this SETUP-INVARIANT */ assert( p->rSetup>=pTemplate->rSetup ); - /* Any loop using an appliation-defined index (or PRIMARY KEY or + /* Any loop using an application-defined index (or PRIMARY KEY or ** UNIQUE constraint) with one or more == constraints is better ** than an automatic index. Unless it is a skip-scan. */ if( (p->wsFlags & WHERE_AUTO_INDEX)!=0 @@ -142120,7 +171644,7 @@ static WhereLoop **whereLoopFindLesser( /* If pTemplate is always better than p, then cause p to be overwritten ** with pTemplate. pTemplate is better than p if: - ** (1) pTemplate has no more dependences than p, and + ** (1) pTemplate has no more dependencies than p, and ** (2) pTemplate has an equal or lower cost than p. */ if( (p->prereq & pTemplate->prereq)==pTemplate->prereq /* (1) */ @@ -142150,7 +171674,7 @@ static WhereLoop **whereLoopFindLesser( ** ** When accumulating multiple loops (when pBuilder->pOrSet is NULL) we ** still might overwrite similar loops with the new template if the -** new template is better. Loops may be overwritten if the following +** new template is better. Loops may be overwritten if the following ** conditions are met: ** ** (1) They have the same iTab. @@ -142172,6 +171696,8 @@ static int whereLoopInsert(WhereLoopBuilder *pBuilder, WhereLoop *pTemplate){ } pBuilder->iPlanLimit--; + whereLoopAdjustCost(pWInfo->pLoops, pTemplate); + /* If pBuilder->pOrSet is defined, then only keep track of the costs ** and prereqs. */ @@ -142186,7 +171712,7 @@ static int whereLoopInsert(WhereLoopBuilder *pBuilder, WhereLoop *pTemplate){ #if WHERETRACE_ENABLED /* 0x8 */ if( sqlite3WhereTrace & 0x8 ){ sqlite3DebugPrintf(x?" or-%d: ":" or-X: ", n); - whereLoopPrint(pTemplate, pBuilder->pWC); + sqlite3WhereLoopPrint(pTemplate, pBuilder->pWC); } #endif } @@ -142195,7 +171721,6 @@ static int whereLoopInsert(WhereLoopBuilder *pBuilder, WhereLoop *pTemplate){ /* Look for an existing WhereLoop to replace with pTemplate */ - whereLoopAdjustCost(pWInfo->pLoops, pTemplate); ppPrev = whereLoopFindLesser(&pWInfo->pLoops, pTemplate); if( ppPrev==0 ){ @@ -142204,10 +171729,10 @@ static int whereLoopInsert(WhereLoopBuilder *pBuilder, WhereLoop *pTemplate){ #if WHERETRACE_ENABLED /* 0x8 */ if( sqlite3WhereTrace & 0x8 ){ sqlite3DebugPrintf(" skip: "); - whereLoopPrint(pTemplate, pBuilder->pWC); + sqlite3WhereLoopPrint(pTemplate, pBuilder->pWC); } #endif - return SQLITE_OK; + return SQLITE_OK; }else{ p = *ppPrev; } @@ -142220,12 +171745,12 @@ static int whereLoopInsert(WhereLoopBuilder *pBuilder, WhereLoop *pTemplate){ if( sqlite3WhereTrace & 0x8 ){ if( p!=0 ){ sqlite3DebugPrintf("replace: "); - whereLoopPrint(p, pBuilder->pWC); + sqlite3WhereLoopPrint(p, pBuilder->pWC); sqlite3DebugPrintf(" with: "); }else{ sqlite3DebugPrintf(" add: "); } - whereLoopPrint(pTemplate, pBuilder->pWC); + sqlite3WhereLoopPrint(pTemplate, pBuilder->pWC); } #endif if( p==0 ){ @@ -142237,7 +171762,7 @@ static int whereLoopInsert(WhereLoopBuilder *pBuilder, WhereLoop *pTemplate){ }else{ /* We will be overwriting WhereLoop p[]. But before we do, first ** go through the rest of the list and delete any other entries besides - ** p[] that are also supplated by pTemplate */ + ** p[] that are also supplanted by pTemplate */ WhereLoop **ppTail = &p->pNextLoop; WhereLoop *pToDel; while( *ppTail ){ @@ -142249,7 +171774,7 @@ static int whereLoopInsert(WhereLoopBuilder *pBuilder, WhereLoop *pTemplate){ #if WHERETRACE_ENABLED /* 0x8 */ if( sqlite3WhereTrace & 0x8 ){ sqlite3DebugPrintf(" delete: "); - whereLoopPrint(pToDel, pBuilder->pWC); + sqlite3WhereLoopPrint(pToDel, pBuilder->pWC); } #endif whereLoopDelete(db, pToDel); @@ -142265,6 +171790,67 @@ static int whereLoopInsert(WhereLoopBuilder *pBuilder, WhereLoop *pTemplate){ return rc; } +/* +** Callback for estLikePatternLength(). +** +** If this node is a string literal that is longer pWalker->sz, then set +** pWalker->sz to the byte length of that string literal. +** +** pWalker->eCode indicates how to count characters: +** +** eCode==0 Count as a GLOB pattern +** eCode==1 Count as a LIKE pattern +*/ +static int exprNodePatternLengthEst(Walker *pWalker, Expr *pExpr){ + if( pExpr->op==TK_STRING ){ + int sz = 0; /* Pattern size in bytes */ + u8 *z = (u8*)pExpr->u.zToken; /* The pattern */ + u8 c; /* Next character of the pattern */ + u8 c1, c2, c3; /* Wildcards */ + if( pWalker->eCode ){ + c1 = '%'; + c2 = '_'; + c3 = 0; + }else{ + c1 = '*'; + c2 = '?'; + c3 = '['; + } + while( (c = *(z++))!=0 ){ + if( c==c3 ){ + if( *z ) z++; + while( *z && *z!=']' ) z++; + }else if( c!=c1 && c!=c2 ){ + sz++; + } + } + if( sz>pWalker->u.sz ) pWalker->u.sz = sz; + } + return WRC_Continue; +} + +/* +** Return the length of the longest string literal in the given +** expression. +** +** eCode indicates how to count characters: +** +** eCode==0 Count as a GLOB pattern +** eCode==1 Count as a LIKE pattern +*/ +static int estLikePatternLength(Expr *p, u16 eCode){ + Walker w; + w.u.sz = 0; + w.eCode = eCode; + w.xExprCallback = exprNodePatternLengthEst; + w.xSelectCallback = sqlite3SelectWalkFail; +#ifdef SQLITE_DEBUG + w.xSelectCallback2 = sqlite3SelectWalkAssert2; +#endif + sqlite3WalkExpr(&w, p); + return w.u.sz; +} + /* ** Adjust the WhereLoop.nOut value downward to account for terms of the ** WHERE clause that reference the loop but which are not used by an @@ -142293,6 +171879,13 @@ static int whereLoopInsert(WhereLoopBuilder *pBuilder, WhereLoop *pTemplate){ ** "x" column is boolean or else -1 or 0 or 1 is a common default value ** on the "x" column and so in that case only cap the output row estimate ** at 1/2 instead of 1/4. +** +** Heuristic 3: If there is a LIKE or GLOB (or REGEXP or MATCH) operator +** with a large constant pattern, then reduce the size of the search +** space according to the length of the pattern, under the theory that +** longer patterns are less likely to match. This heuristic was added +** to give better output-row count estimates when preparing queries for +** the Join-Order Benchmarks. See forum thread 2026-01-30T09:57:54z */ static void whereLoopOutputAdjust( WhereClause *pWC, /* The WHERE clause */ @@ -142301,14 +171894,15 @@ static void whereLoopOutputAdjust( ){ WhereTerm *pTerm, *pX; Bitmask notAllowed = ~(pLoop->prereq|pLoop->maskSelf); - int i, j, k; + int i, j; LogEst iReduce = 0; /* pLoop->nOut should not exceed nRow-iReduce */ assert( (pLoop->wsFlags & WHERE_AUTO_INDEX)==0 ); - for(i=pWC->nTerm, pTerm=pWC->a; i>0; i--, pTerm++){ - if( (pTerm->wtFlags & TERM_VIRTUAL)!=0 ) break; - if( (pTerm->prereqAll & pLoop->maskSelf)==0 ) continue; + for(i=pWC->nBase, pTerm=pWC->a; i>0; i--, pTerm++){ + assert( pTerm!=0 ); if( (pTerm->prereqAll & notAllowed)!=0 ) continue; + if( (pTerm->prereqAll & pLoop->maskSelf)==0 ) continue; + if( (pTerm->wtFlags & TERM_VIRTUAL)!=0 ) continue; for(j=pLoop->nLTerm-1; j>=0; j--){ pX = pLoop->aLTerm[j]; if( pX==0 ) continue; @@ -142316,6 +171910,24 @@ static void whereLoopOutputAdjust( if( pX->iParent>=0 && (&pWC->a[pX->iParent])==pTerm ) break; } if( j<0 ){ + sqlite3ProgressCheck(pWC->pWInfo->pParse); + if( pLoop->maskSelf==pTerm->prereqAll ){ + /* If there are extra terms in the WHERE clause not used by an index + ** that depend only on the table being scanned, and that will tend to + ** cause many rows to be omitted, then mark that table as + ** "self-culling". + ** + ** 2022-03-24: Self-culling only applies if either the extra terms + ** are straight comparison operators that are non-true with NULL + ** operand, or if the loop is not an OUTER JOIN. + */ + if( (pTerm->eOperator & 0x3f)!=0 + || (pWC->pWInfo->pTabList->a[pLoop->iTab].fg.jointype + & (JT_LEFT|JT_LTORJ))==0 + ){ + pLoop->wsFlags |= WHERE_SELFCULL; + } + } if( pTerm->truthProb<=0 ){ /* If a truth probability is specified using the likelihood() hints, ** then use the probability provided by the application. */ @@ -142323,24 +171935,50 @@ static void whereLoopOutputAdjust( }else{ /* In the absence of explicit truth probabilities, use heuristics to ** guess a reasonable truth probability. */ + Expr *pOpExpr = pTerm->pExpr; pLoop->nOut--; - if( pTerm->eOperator&(WO_EQ|WO_IS) ){ - Expr *pRight = pTerm->pExpr->pRight; - testcase( pTerm->pExpr->op==TK_IS ); - if( sqlite3ExprIsInteger(pRight, &k) && k>=(-1) && k<=1 ){ + if( (pTerm->eOperator&(WO_EQ|WO_IS))!=0 + && (pTerm->wtFlags & TERM_HIGHTRUTH)==0 /* tag-20200224-1 */ + ){ + Expr *pRight = pOpExpr->pRight; + int k = 0; + testcase( pOpExpr->op==TK_IS ); + if( sqlite3ExprIsInteger(pRight, &k, 0) && k>=(-1) && k<=1 ){ k = 10; }else{ k = 20; } - if( iReducewtFlags |= TERM_HEURTRUTH; + iReduce = k; + } + }else + if( ExprHasProperty(pOpExpr, EP_InfixFunc) + && pOpExpr->op==TK_FUNCTION + ){ + int eOp; + assert( ExprUseXList(pOpExpr) ); + assert( pOpExpr->x.pList->nExpr>=2 ); + eOp = sqlite3ExprIsLikeOperator(pOpExpr); + if( ALWAYS(eOp>0) ){ + int szPattern; + Expr *pRHS = pOpExpr->x.pList->a[0].pExpr; + eOp = eOp==SQLITE_INDEX_CONSTRAINT_LIKE; + szPattern = estLikePatternLength(pRHS, eOp); + if( szPattern>0 ){ + pLoop->nOut -= szPattern*2; + } + } } } } } - if( pLoop->nOut > nRow-iReduce ) pLoop->nOut = nRow - iReduce; + if( pLoop->nOut > nRow-iReduce ){ + pLoop->nOut = nRow - iReduce; + } } -/* +/* ** Term pTerm is a vector range comparison operation. The first comparison ** in the vector can be optimized using column nEq of the index. This ** function returns the total number of vector elements that can be used @@ -142369,14 +172007,17 @@ static int whereRangeVectorLen( nCmp = MIN(nCmp, (pIdx->nColumn - nEq)); for(i=1; ipExpr->pLeft->x.pList->a[i].pExpr; - Expr *pRhs = pTerm->pExpr->pRight; - if( pRhs->flags & EP_xIsSelect ){ + Expr *pLhs, *pRhs; + + assert( ExprUseXList(pTerm->pExpr->pLeft) ); + pLhs = pTerm->pExpr->pLeft->x.pList->a[i].pExpr; + pRhs = pTerm->pExpr->pRight; + if( ExprUseXSelect(pRhs) ){ pRhs = pRhs->x.pSelect->pEList->a[i].pExpr; }else{ pRhs = pRhs->x.pList->a[i].pExpr; @@ -142386,9 +172027,9 @@ static int whereRangeVectorLen( ** the right column of the right source table. And that the sort ** order of the index column is the same as the sort order of the ** leftmost index column. */ - if( pLhs->op!=TK_COLUMN - || pLhs->iTable!=iCur - || pLhs->iColumn!=pIdx->aiColumn[i+nEq] + if( pLhs->op!=TK_COLUMN + || pLhs->iTable!=iCur + || pLhs->iColumn!=pIdx->aiColumn[i+nEq] || pIdx->aSortOrder[i+nEq]!=pIdx->aSortOrder[nEq] ){ break; @@ -142399,6 +172040,7 @@ static int whereRangeVectorLen( idxaff = sqlite3TableColumnAffinity(pIdx->pTable, pLhs->iColumn); if( aff!=idxaff ) break; + if( ExprHasProperty(pTerm->pExpr, EP_Commuted) ) SWAP(Expr*, pRhs, pLhs); pColl = sqlite3BinaryCompareCollSeq(pParse, pLhs, pRhs); if( pColl==0 ) break; if( sqlite3StrICmp(pColl->zName, pIdx->azColl[i+nEq]) ) break; @@ -142407,7 +172049,7 @@ static int whereRangeVectorLen( } /* -** Adjust the cost C by the costMult facter T. This only occurs if +** Adjust the cost C by the costMult factor T. This only occurs if ** compiled with -DSQLITE_ENABLE_COSTMULT */ #ifdef SQLITE_ENABLE_COSTMULT @@ -142417,24 +172059,24 @@ static int whereRangeVectorLen( #endif /* -** We have so far matched pBuilder->pNew->u.btree.nEq terms of the +** We have so far matched pBuilder->pNew->u.btree.nEq terms of the ** index pIndex. Try to match one more. ** -** When this function is called, pBuilder->pNew->nOut contains the -** number of rows expected to be visited by filtering using the nEq -** terms only. If it is modified, this value is restored before this +** When this function is called, pBuilder->pNew->nOut contains the +** number of rows expected to be visited by filtering using the nEq +** terms only. If it is modified, this value is restored before this ** function returns. ** -** If pProbe->idxType==SQLITE_IDXTYPE_IPK, that means pIndex is +** If pProbe->idxType==SQLITE_IDXTYPE_IPK, that means pIndex is ** a fake index used for the INTEGER PRIMARY KEY. */ static int whereLoopAddBtreeIndex( WhereLoopBuilder *pBuilder, /* The WhereLoop factory */ - struct SrcList_item *pSrc, /* FROM clause term being analyzed */ + SrcItem *pSrc, /* FROM clause term being analyzed */ Index *pProbe, /* An index on pSrc */ LogEst nInMul /* log(Number of iterations due to IN) */ ){ - WhereInfo *pWInfo = pBuilder->pWInfo; /* WHERE analyse context */ + WhereInfo *pWInfo = pBuilder->pWInfo; /* WHERE analyze context */ Parse *pParse = pWInfo->pParse; /* Parsing context */ sqlite3 *db = pParse->db; /* Database connection malloc context */ WhereLoop *pNew; /* Template WhereLoop under construction */ @@ -142455,9 +172097,13 @@ static int whereLoopAddBtreeIndex( WhereTerm *pTop = 0, *pBtm = 0; /* Top and bottom range constraints */ pNew = pBuilder->pNew; - if( db->mallocFailed ) return SQLITE_NOMEM_BKPT; - WHERETRACE(0x800, ("BEGIN %s.addBtreeIdx(%s), nEq=%d\n", - pProbe->pTable->zName,pProbe->zName, pNew->u.btree.nEq)); + assert( db->mallocFailed==0 || pParse->nErr>0 ); + if( pParse->nErr ){ + return pParse->rc; + } + WHERETRACE(0x800, ("BEGIN %s.addBtreeIdx(%s), nEq=%d, nSkip=%d, rRun=%d\n", + pProbe->pTable->zName,pProbe->zName, + pNew->u.btree.nEq, pNew->nSkip, pNew->rRun)); assert( (pNew->wsFlags & WHERE_VIRTUALTABLE)==0 ); assert( (pNew->wsFlags & WHERE_TOP_LIMIT)==0 ); @@ -142467,9 +172113,13 @@ static int whereLoopAddBtreeIndex( assert( pNew->u.btree.nBtm==0 ); opMask = WO_EQ|WO_IN|WO_GT|WO_GE|WO_LT|WO_LE|WO_ISNULL|WO_IS; } - if( pProbe->bUnordered ) opMask &= ~(WO_GT|WO_GE|WO_LT|WO_LE); + if( pProbe->bUnordered ){ + opMask &= ~(WO_GT|WO_GE|WO_LT|WO_LE); + } assert( pNew->u.btree.nEqnColumn ); + assert( pNew->u.btree.nEqnKeyCol + || pProbe->idxType!=SQLITE_IDXTYPE_PRIMARYKEY ); saved_nEq = pNew->u.btree.nEq; saved_nBtm = pNew->u.btree.nBtm; @@ -142489,7 +172139,7 @@ static int whereLoopAddBtreeIndex( LogEst rCostIdx; LogEst nOutUnadjusted; /* nOut before IN() and WHERE adjustments */ int nIn = 0; -#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 +#ifdef SQLITE_ENABLE_STAT4 int nRecValid = pBuilder->nRecValid; #endif if( (eOp==WO_ISNULL || (pTerm->wtFlags&TERM_VNULL)!=0) @@ -142503,40 +172153,41 @@ static int whereLoopAddBtreeIndex( ** to mix with a lower range bound from some other source */ if( pTerm->wtFlags & TERM_LIKEOPT && pTerm->eOperator==WO_LT ) continue; - /* Do not allow constraints from the WHERE clause to be used by the - ** right table of a LEFT JOIN. Only constraints in the ON clause are - ** allowed */ - if( (pSrc->fg.jointype & JT_LEFT)!=0 - && !ExprHasProperty(pTerm->pExpr, EP_FromJoin) + if( (pSrc->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))!=0 + && !constraintCompatibleWithOuterJoin(pTerm,pSrc) ){ continue; } - if( IsUniqueIndex(pProbe) && saved_nEq==pProbe->nKeyCol-1 ){ - pBuilder->bldFlags |= SQLITE_BLDF_UNIQUE; + pBuilder->bldFlags1 |= SQLITE_BLDF1_UNIQUE; }else{ - pBuilder->bldFlags |= SQLITE_BLDF_INDEXED; + pBuilder->bldFlags1 |= SQLITE_BLDF1_INDEXED; } pNew->wsFlags = saved_wsFlags; pNew->u.btree.nEq = saved_nEq; pNew->u.btree.nBtm = saved_nBtm; pNew->u.btree.nTop = saved_nTop; pNew->nLTerm = saved_nLTerm; - if( whereLoopResize(db, pNew, pNew->nLTerm+1) ) break; /* OOM */ + if( pNew->nLTerm>=pNew->nLSlot + && whereLoopResize(db, pNew, pNew->nLTerm+1) + ){ + break; /* OOM while trying to enlarge the pNew->aLTerm array */ + } pNew->aLTerm[pNew->nLTerm++] = pTerm; pNew->prereq = (saved_prereq | pTerm->prereqRight) & ~pNew->maskSelf; assert( nInMul==0 - || (pNew->wsFlags & WHERE_COLUMN_NULL)!=0 - || (pNew->wsFlags & WHERE_COLUMN_IN)!=0 - || (pNew->wsFlags & WHERE_SKIPSCAN)!=0 + || (pNew->wsFlags & WHERE_COLUMN_NULL)!=0 + || (pNew->wsFlags & WHERE_COLUMN_IN)!=0 + || (pNew->wsFlags & WHERE_SKIPSCAN)!=0 ); if( eOp & WO_IN ){ Expr *pExpr = pTerm->pExpr; - if( ExprHasProperty(pExpr, EP_xIsSelect) ){ + if( ExprUseXSelect(pExpr) ){ /* "x IN (SELECT ...)": TUNING: the SELECT returns 25 rows */ int i; + int bRedundant = 0; nIn = 46; assert( 46==sqlite3LogEst(25) ); /* The expression may actually be of the form (x, y) IN (SELECT...). @@ -142545,20 +172196,31 @@ static int whereLoopAddBtreeIndex( ** for each such term. The following loop checks that pTerm is the ** first such term in use, and sets nIn back to 0 if it is not. */ for(i=0; inLTerm-1; i++){ - if( pNew->aLTerm[i] && pNew->aLTerm[i]->pExpr==pExpr ) nIn = 0; + if( pNew->aLTerm[i] && pNew->aLTerm[i]->pExpr==pExpr ){ + nIn = 0; + if( pNew->aLTerm[i]->u.x.iField == pTerm->u.x.iField ){ + /* Detect when two or more columns of an index match the same + ** column of a vector IN operater, and avoid adding the column + ** to the WhereLoop more than once. See tag-20250707-01 + ** in test/rowvalue.test */ + bRedundant = 1; + } + } + } + if( bRedundant ){ + pNew->nLTerm--; + continue; } }else if( ALWAYS(pExpr->x.pList && pExpr->x.pList->nExpr) ){ /* "x IN (value, value, ...)" */ nIn = sqlite3LogEst(pExpr->x.pList->nExpr); - assert( nIn>0 ); /* RHS always has 2 or more terms... The parser - ** changes "x IN (?)" into "x=?". */ } - if( pProbe->hasStat1 ){ - LogEst M, logK, safetyMargin; + if( pProbe->hasStat1 && rLogSize>=10 ){ + LogEst M, logK, x; /* Let: ** N = the total number of rows in the table ** K = the number of entries on the RHS of the IN operator - ** M = the number of rows in the table that match terms to the + ** M = the number of rows in the table that match terms to the ** to the left in the same index. If the IN operator is on ** the left-most index column, M==N. ** @@ -142572,20 +172234,30 @@ static int whereLoopAddBtreeIndex( ** a safety margin of 2 (LogEst: 10) that favors using the IN operator ** with the index, as using an index has better worst-case behavior. ** If we do not have real sqlite_stat1 data, always prefer to use - ** the index. + ** the index. Do not bother with this optimization on very small + ** tables (less than 2 rows) as it is pointless in that case. */ M = pProbe->aiRowLogEst[saved_nEq]; logK = estLog(nIn); - safetyMargin = 10; /* TUNING: extra weight for indexed IN */ - if( M + logK + safetyMargin < nIn + rLogSize ){ + /* TUNING v----- 10 to bias toward indexed IN */ + x = M + logK + 10 - (nIn + rLogSize); + if( x>=0 ){ WHERETRACE(0x40, - ("Scan preferred over IN operator on column %d of \"%s\" (%d<%d)\n", - saved_nEq, pProbe->zName, M+logK+10, nIn+rLogSize)); - continue; + ("IN operator (N=%d M=%d logK=%d nIn=%d rLogSize=%d x=%d) " + "prefers indexed lookup\n", + saved_nEq, M, logK, nIn, rLogSize, x)); + }else if( nInMul<2 && OptimizationEnabled(db, SQLITE_SeekScan) ){ + WHERETRACE(0x40, + ("IN operator (N=%d M=%d logK=%d nIn=%d rLogSize=%d x=%d" + " nInMul=%d) prefers skip-scan\n", + saved_nEq, M, logK, nIn, rLogSize, x, nInMul)); + pNew->wsFlags |= WHERE_IN_SEEKSCAN; }else{ WHERETRACE(0x40, - ("IN operator preferred on column %d of \"%s\" (%d>=%d)\n", - saved_nEq, pProbe->zName, M+logK+10, nIn+rLogSize)); + ("IN operator (N=%d M=%d logK=%d nIn=%d rLogSize=%d x=%d" + " nInMul=%d) prefers normal scan\n", + saved_nEq, M, logK, nIn, rLogSize, x, nInMul)); + continue; } } pNew->wsFlags |= WHERE_COLUMN_IN; @@ -142593,61 +172265,63 @@ static int whereLoopAddBtreeIndex( int iCol = pProbe->aiColumn[saved_nEq]; pNew->wsFlags |= WHERE_COLUMN_EQ; assert( saved_nEq==pNew->u.btree.nEq ); - if( iCol==XN_ROWID + if( iCol==XN_ROWID || (iCol>=0 && nInMul==0 && saved_nEq==pProbe->nKeyCol-1) ){ - if( iCol==XN_ROWID || pProbe->uniqNotNull - || (pProbe->nKeyCol==1 && pProbe->onError && eOp==WO_EQ) + if( iCol==XN_ROWID || pProbe->uniqNotNull + || (pProbe->nKeyCol==1 && pProbe->onError && (eOp & WO_EQ)) ){ pNew->wsFlags |= WHERE_ONEROW; }else{ pNew->wsFlags |= WHERE_UNQ_WANTED; } } + if( scan.iEquiv>1 ) pNew->wsFlags |= WHERE_TRANSCONS; }else if( eOp & WO_ISNULL ){ pNew->wsFlags |= WHERE_COLUMN_NULL; - }else if( eOp & (WO_GT|WO_GE) ){ - testcase( eOp & WO_GT ); - testcase( eOp & WO_GE ); - pNew->wsFlags |= WHERE_COLUMN_RANGE|WHERE_BTM_LIMIT; - pNew->u.btree.nBtm = whereRangeVectorLen( - pParse, pSrc->iCursor, pProbe, saved_nEq, pTerm - ); - pBtm = pTerm; - pTop = 0; - if( pTerm->wtFlags & TERM_LIKEOPT ){ - /* Range contraints that come from the LIKE optimization are - ** always used in pairs. */ - pTop = &pTerm[1]; - assert( (pTop-(pTerm->pWC->a))pWC->nTerm ); - assert( pTop->wtFlags & TERM_LIKEOPT ); - assert( pTop->eOperator==WO_LT ); - if( whereLoopResize(db, pNew, pNew->nLTerm+1) ) break; /* OOM */ - pNew->aLTerm[pNew->nLTerm++] = pTop; - pNew->wsFlags |= WHERE_TOP_LIMIT; - pNew->u.btree.nTop = 1; - } - }else{ - assert( eOp & (WO_LT|WO_LE) ); - testcase( eOp & WO_LT ); - testcase( eOp & WO_LE ); - pNew->wsFlags |= WHERE_COLUMN_RANGE|WHERE_TOP_LIMIT; - pNew->u.btree.nTop = whereRangeVectorLen( + }else{ + int nVecLen = whereRangeVectorLen( pParse, pSrc->iCursor, pProbe, saved_nEq, pTerm ); - pTop = pTerm; - pBtm = (pNew->wsFlags & WHERE_BTM_LIMIT)!=0 ? - pNew->aLTerm[pNew->nLTerm-2] : 0; + if( eOp & (WO_GT|WO_GE) ){ + testcase( eOp & WO_GT ); + testcase( eOp & WO_GE ); + pNew->wsFlags |= WHERE_COLUMN_RANGE|WHERE_BTM_LIMIT; + pNew->u.btree.nBtm = nVecLen; + pBtm = pTerm; + pTop = 0; + if( pTerm->wtFlags & TERM_LIKEOPT ){ + /* Range constraints that come from the LIKE optimization are + ** always used in pairs. */ + pTop = &pTerm[1]; + assert( (pTop-(pTerm->pWC->a))pWC->nTerm ); + assert( pTop->wtFlags & TERM_LIKEOPT ); + assert( pTop->eOperator==WO_LT ); + if( whereLoopResize(db, pNew, pNew->nLTerm+1) ) break; /* OOM */ + pNew->aLTerm[pNew->nLTerm++] = pTop; + pNew->wsFlags |= WHERE_TOP_LIMIT; + pNew->u.btree.nTop = 1; + } + }else{ + assert( eOp & (WO_LT|WO_LE) ); + testcase( eOp & WO_LT ); + testcase( eOp & WO_LE ); + pNew->wsFlags |= WHERE_COLUMN_RANGE|WHERE_TOP_LIMIT; + pNew->u.btree.nTop = nVecLen; + pTop = pTerm; + pBtm = (pNew->wsFlags & WHERE_BTM_LIMIT)!=0 ? + pNew->aLTerm[pNew->nLTerm-2] : 0; + } } /* At this point pNew->nOut is set to the number of rows expected to ** be visited by the index scan before considering term pTerm, or the - ** values of nIn and nInMul. In other words, assuming that all + ** values of nIn and nInMul. In other words, assuming that all ** "x IN(...)" terms are replaced with "x = ?". This block updates ** the value of pNew->nOut to account for pTerm (but not nIn/nInMul). */ assert( pNew->nOut==saved_nOut ); if( pNew->wsFlags & WHERE_COLUMN_RANGE ){ - /* Adjust nOut using stat3/stat4 data. Or, if there is no stat3/stat4 + /* Adjust nOut using stat4 data. Or, if there is no stat4 ** data, using some other estimate. */ whereRangeScanEst(pParse, pBuilder, pBtm, pTop, pNew); }else{ @@ -142661,13 +172335,13 @@ static int whereLoopAddBtreeIndex( pNew->nOut += pTerm->truthProb; pNew->nOut -= nIn; }else{ -#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 +#ifdef SQLITE_ENABLE_STAT4 tRowcnt nOut = 0; - if( nInMul==0 - && pProbe->nSample - && pNew->u.btree.nEq<=pProbe->nSampleCol - && ((eOp & WO_IN)==0 || !ExprHasProperty(pTerm->pExpr, EP_xIsSelect)) - && OptimizationEnabled(db, SQLITE_Stat34) + if( nInMul==0 + && pProbe->nSample + && ALWAYS(pNew->u.btree.nEq<=pProbe->nSampleCol) + && ((eOp & WO_IN)==0 || ExprUseXList(pTerm->pExpr)) + && OptimizationEnabled(db, SQLITE_Stat4) ){ Expr *pExpr = pTerm->pExpr; if( (eOp & (WO_EQ|WO_ISNULL|WO_IS))!=0 ){ @@ -142682,6 +172356,27 @@ static int whereLoopAddBtreeIndex( if( rc!=SQLITE_OK ) break; /* Jump out of the pTerm loop */ if( nOut ){ pNew->nOut = sqlite3LogEst(nOut); + if( nEq==1 + /* TUNING: Mark terms as "low selectivity" if they seem likely + ** to be true for half or more of the rows in the table. + ** See tag-202002240-1 */ + && pNew->nOut+10 > pProbe->aiRowLogEst[0] + ){ +#if WHERETRACE_ENABLED /* 0x01 */ + if( sqlite3WhereTrace & 0x20 ){ + sqlite3DebugPrintf( + "STAT4 determines term has low selectivity:\n"); + sqlite3WhereTermPrint(pTerm, 999); + } +#endif + pTerm->wtFlags |= TERM_HIGHTRUTH; + if( pTerm->wtFlags & TERM_HEURTRUTH ){ + /* If the term has previously been used with an assumption of + ** higher selectivity, then set the flag to rerun the + ** loop computations. */ + pBuilder->bldFlags2 |= SQLITE_BLDF2_2NDPASS; + } + } if( pNew->nOut>saved_nOut ) pNew->nOut = saved_nOut; pNew->nOut -= nIn; } @@ -142691,8 +172386,8 @@ static int whereLoopAddBtreeIndex( { pNew->nOut += (pProbe->aiRowLogEst[nEq] - pProbe->aiRowLogEst[nEq-1]); if( eOp & WO_ISNULL ){ - /* TUNING: If there is no likelihood() value, assume that a - ** "col IS NULL" expression matches twice as many rows + /* TUNING: If there is no likelihood() value, assume that a + ** "col IS NULL" expression matches twice as many rows ** as (col=?). */ pNew->nOut += 10; } @@ -142700,13 +172395,33 @@ static int whereLoopAddBtreeIndex( } } - /* Set rCostIdx to the cost of visiting selected rows in index. Add - ** it to pNew->rRun, which is currently set to the cost of the index - ** seek only. Then, if this is a non-covering index, add the cost of - ** visiting the rows in the main table. */ - rCostIdx = pNew->nOut + 1 + (15*pProbe->szIdxRow)/pSrc->pTab->szTabRow; - pNew->rRun = sqlite3LogEstAdd(rLogSize, rCostIdx); - if( (pNew->wsFlags & (WHERE_IDX_ONLY|WHERE_IPK))==0 ){ + /* Set rCostIdx to the estimated cost of visiting selected rows in the + ** index. The estimate is the sum of two values: + ** 1. The cost of doing one search-by-key to find the first matching + ** entry + ** 2. Stepping forward in the index pNew->nOut times to find all + ** additional matching entries. + */ + assert( pSrc->pSTab->szTabRow>0 ); + if( pProbe->idxType==SQLITE_IDXTYPE_IPK ){ + /* The pProbe->szIdxRow is low for an IPK table since the interior + ** pages are small. Thus szIdxRow gives a good estimate of seek cost. + ** But the leaf pages are full-size, so pProbe->szIdxRow would badly + ** under-estimate the scanning cost. */ + rCostIdx = pNew->nOut + 16; + }else{ + rCostIdx = pNew->nOut + 1 + (15*pProbe->szIdxRow)/pSrc->pSTab->szTabRow; + } + rCostIdx = sqlite3LogEstAdd(rLogSize, rCostIdx); + + /* Estimate the cost of running the loop. If all data is coming + ** from the index, then this is just the cost of doing the index + ** lookup and scan. But if some data is coming out of the main table, + ** we also have to add in the cost of doing pNew->nOut searches to + ** locate the row in the main table that corresponds to the index entry. + */ + pNew->rRun = rCostIdx; + if( (pNew->wsFlags & (WHERE_IDX_ONLY|WHERE_IPK|WHERE_EXPRIDX))==0 ){ pNew->rRun = sqlite3LogEstAdd(pNew->rRun, pNew->nOut + 16); } ApplyCostMultiplier(pNew->rRun, pProbe->pTable->costMult); @@ -142715,6 +172430,7 @@ static int whereLoopAddBtreeIndex( pNew->rRun += nInMul + nIn; pNew->nOut += nInMul + nIn; whereLoopOutputAdjust(pBuilder->pWC, pNew, rSize); + if( pSrc->fg.fromExists ) pNew->nOut = 0; rc = whereLoopInsert(pBuilder, pNew); if( pNew->wsFlags & WHERE_COLUMN_RANGE ){ @@ -142725,11 +172441,16 @@ static int whereLoopAddBtreeIndex( if( (pNew->wsFlags & WHERE_TOP_LIMIT)==0 && pNew->u.btree.nEqnColumn + && (pNew->u.btree.nEqnKeyCol || + pProbe->idxType!=SQLITE_IDXTYPE_PRIMARYKEY) ){ + if( pNew->u.btree.nEq>3 ){ + sqlite3ProgressCheck(pParse); + } whereLoopAddBtreeIndex(pBuilder, pSrc, pProbe, nInMul+nIn); } pNew->nOut = saved_nOut; -#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 +#ifdef SQLITE_ENABLE_STAT4 pBuilder->nRecValid = nRecValid; #endif } @@ -142744,20 +172465,23 @@ static int whereLoopAddBtreeIndex( /* Consider using a skip-scan if there are no WHERE clause constraints ** available for the left-most terms of the index, and if the average - ** number of repeats in the left-most terms is at least 18. + ** number of repeats in the left-most terms is at least 18. ** ** The magic number 18 is selected on the basis that scanning 17 rows ** is almost always quicker than an index seek (even though if the index ** contains fewer than 2^17 rows we assume otherwise in other parts of - ** the code). And, even if it is not, it should not be too much slower. + ** the code). And, even if it is not, it should not be too much slower. ** On the other hand, the extra seeks could end up being significantly ** more expensive. */ assert( 42==sqlite3LogEst(18) ); if( saved_nEq==saved_nSkip && saved_nEq+1nKeyCol + && saved_nEq==pNew->nLTerm && pProbe->noSkipScan==0 + && pProbe->hasStat1!=0 && OptimizationEnabled(db, SQLITE_SkipScan) && pProbe->aiRowLogEst[saved_nEq+1]>=42 /* TUNING: Minimum for skip-scan */ + && pSrc->fg.fromExists==0 && (rc = whereLoopResize(db, pNew, pNew->nLTerm+1))==SQLITE_OK ){ LogEst nIter; @@ -142802,8 +172526,11 @@ static int indexMightHelpWithOrderBy( if( pIndex->bUnordered ) return 0; if( (pOB = pBuilder->pWInfo->pOrderBy)==0 ) return 0; for(ii=0; iinExpr; ii++){ - Expr *pExpr = sqlite3ExprSkipCollate(pOB->a[ii].pExpr); - if( pExpr->op==TK_COLUMN && pExpr->iTable==iCursor ){ + Expr *pExpr = sqlite3ExprSkipCollateAndLikely(pOB->a[ii].pExpr); + if( NEVER(pExpr==0) ) continue; + if( (pExpr->op==TK_COLUMN || pExpr->op==TK_AGG_COLUMN) + && pExpr->iTable==iCursor + ){ if( pExpr->iColumn<0 ) return 1; for(jj=0; jjnKeyCol; jj++){ if( pExpr->iColumn==pIndex->aiColumn[jj] ) return 1; @@ -142823,19 +172550,50 @@ static int indexMightHelpWithOrderBy( /* Check to see if a partial index with pPartIndexWhere can be used ** in the current query. Return true if it can be and false if not. */ -static int whereUsablePartialIndex(int iTab, WhereClause *pWC, Expr *pWhere){ +static int whereUsablePartialIndex( + int iTab, /* The table for which we want an index */ + u8 jointype, /* The JT_* flags on the join */ + WhereClause *pWC, /* The WHERE clause of the query */ + Expr *pWhere /* The WHERE clause from the partial index */ +){ int i; WhereTerm *pTerm; - Parse *pParse = pWC->pWInfo->pParse; + Parse *pParse; + + if( jointype & JT_LTORJ ) return 0; + pParse = pWC->pWInfo->pParse; while( pWhere->op==TK_AND ){ - if( !whereUsablePartialIndex(iTab,pWC,pWhere->pLeft) ) return 0; + if( !whereUsablePartialIndex(iTab,jointype,pWC,pWhere->pLeft) ) return 0; pWhere = pWhere->pRight; } - if( pParse->db->flags & SQLITE_EnableQPSG ) pParse = 0; for(i=0, pTerm=pWC->a; inTerm; i++, pTerm++){ - Expr *pExpr = pTerm->pExpr; - if( (!ExprHasProperty(pExpr, EP_FromJoin) || pExpr->iRightJoinTable==iTab) - && sqlite3ExprImpliesExpr(pParse, pExpr, pWhere, iTab) + Expr *pExpr; + pExpr = pTerm->pExpr; + if( (!ExprHasProperty(pExpr, EP_OuterON) || pExpr->w.iJoin==iTab) + && ((jointype & JT_OUTER)==0 || ExprHasProperty(pExpr, EP_OuterON)) + && sqlite3ExprImpliesExpr(pParse, pExpr, pWhere, iTab) + && !sqlite3ExprImpliesExpr(pParse, pExpr, pWhere, -1) + && (pTerm->wtFlags & TERM_VNULL)==0 + ){ + return 1; + } + } + return 0; +} + +/* +** pIdx is an index containing expressions. Check it see if any of the +** expressions in the index match the pExpr expression. +*/ +static int exprIsCoveredByIndex( + const Expr *pExpr, + const Index *pIdx, + int iTabCur +){ + int i; + for(i=0; inColumn; i++){ + if( pIdx->aiColumn[i]==XN_EXPR + && sqlite3ExprCompare(0, pExpr, pIdx->aColExpr->a[i].pExpr, iTabCur)==0 ){ return 1; } @@ -142843,6 +172601,223 @@ static int whereUsablePartialIndex(int iTab, WhereClause *pWC, Expr *pWhere){ return 0; } +/* +** Structure passed to the whereIsCoveringIndex Walker callback. +*/ +typedef struct CoveringIndexCheck CoveringIndexCheck; +struct CoveringIndexCheck { + Index *pIdx; /* The index */ + int iTabCur; /* Cursor number for the corresponding table */ + u8 bExpr; /* Uses an indexed expression */ + u8 bUnidx; /* Uses an unindexed column not within an indexed expr */ +}; + +/* +** Information passed in is pWalk->u.pCovIdxCk. Call it pCk. +** +** If the Expr node references the table with cursor pCk->iTabCur, then +** make sure that column is covered by the index pCk->pIdx. We know that +** all columns less than 63 (really BMS-1) are covered, so we don't need +** to check them. But we do need to check any column at 63 or greater. +** +** If the index does not cover the column, then set pWalk->eCode to +** non-zero and return WRC_Abort to stop the search. +** +** If this node does not disprove that the index can be a covering index, +** then just return WRC_Continue, to continue the search. +** +** If pCk->pIdx contains indexed expressions and one of those expressions +** matches pExpr, then prune the search. +*/ +static int whereIsCoveringIndexWalkCallback(Walker *pWalk, Expr *pExpr){ + int i; /* Loop counter */ + const Index *pIdx; /* The index of interest */ + const i16 *aiColumn; /* Columns contained in the index */ + u16 nColumn; /* Number of columns in the index */ + CoveringIndexCheck *pCk; /* Info about this search */ + + pCk = pWalk->u.pCovIdxCk; + pIdx = pCk->pIdx; + if( (pExpr->op==TK_COLUMN || pExpr->op==TK_AGG_COLUMN) ){ + /* if( pExpr->iColumn<(BMS-1) && pIdx->bHasExpr==0 ) return WRC_Continue;*/ + if( pExpr->iTable!=pCk->iTabCur ) return WRC_Continue; + pIdx = pWalk->u.pCovIdxCk->pIdx; + aiColumn = pIdx->aiColumn; + nColumn = pIdx->nColumn; + for(i=0; iiColumn ) return WRC_Continue; + } + pCk->bUnidx = 1; + return WRC_Abort; + }else if( pIdx->bHasExpr + && exprIsCoveredByIndex(pExpr, pIdx, pWalk->u.pCovIdxCk->iTabCur) ){ + pCk->bExpr = 1; + return WRC_Prune; + } + return WRC_Continue; +} + + +/* +** pIdx is an index that covers all of the low-number columns used by +** pWInfo->pSelect (columns from 0 through 62) or an index that has +** expressions terms. Hence, we cannot determine whether or not it is +** a covering index by using the colUsed bitmasks. We have to do a search +** to see if the index is covering. This routine does that search. +** +** The return value is one of these: +** +** 0 The index is definitely not a covering index +** +** WHERE_IDX_ONLY The index is definitely a covering index +** +** WHERE_EXPRIDX The index is likely a covering index, but it is +** difficult to determine precisely because of the +** expressions that are indexed. Score it as a +** covering index, but still keep the main table open +** just in case we need it. +** +** This routine is an optimization. It is always safe to return zero. +** But returning one of the other two values when zero should have been +** returned can lead to incorrect bytecode and assertion faults. +*/ +static SQLITE_NOINLINE u32 whereIsCoveringIndex( + WhereInfo *pWInfo, /* The WHERE clause context */ + Index *pIdx, /* Index that is being tested */ + int iTabCur /* Cursor for the table being indexed */ +){ + int i, rc; + struct CoveringIndexCheck ck; + Walker w; + if( pWInfo->pSelect==0 ){ + /* We don't have access to the full query, so we cannot check to see + ** if pIdx is covering. Assume it is not. */ + return 0; + } + if( pIdx->bHasExpr==0 ){ + for(i=0; inColumn; i++){ + if( pIdx->aiColumn[i]>=BMS-1 ) break; + } + if( i>=pIdx->nColumn ){ + /* pIdx does not index any columns greater than 62, but we know from + ** colMask that columns greater than 62 are used, so this is not a + ** covering index */ + return 0; + } + } + ck.pIdx = pIdx; + ck.iTabCur = iTabCur; + ck.bExpr = 0; + ck.bUnidx = 0; + memset(&w, 0, sizeof(w)); + w.xExprCallback = whereIsCoveringIndexWalkCallback; + w.xSelectCallback = sqlite3SelectWalkNoop; + w.u.pCovIdxCk = &ck; + sqlite3WalkSelect(&w, pWInfo->pSelect); + if( ck.bUnidx ){ + rc = 0; + }else if( ck.bExpr ){ + rc = WHERE_EXPRIDX; + }else{ + rc = WHERE_IDX_ONLY; + } + return rc; +} + +/* +** This is an sqlite3ParserAddCleanup() callback that is invoked to +** free the Parse->pIdxEpr list when the Parse object is destroyed. +*/ +static void whereIndexedExprCleanup(sqlite3 *db, void *pObject){ + IndexedExpr **pp = (IndexedExpr**)pObject; + while( *pp!=0 ){ + IndexedExpr *p = *pp; + *pp = p->pIENext; + sqlite3ExprDelete(db, p->pExpr); + sqlite3DbFreeNN(db, p); + } +} + +/* +** This function is called for a partial index - one with a WHERE clause - in +** two scenarios. In both cases, it determines whether or not the WHERE +** clause on the index implies that a column of the table may be safely +** replaced by a constant expression. For example, in the following +** SELECT: +** +** CREATE INDEX i1 ON t1(b, c) WHERE a=; +** SELECT a, b, c FROM t1 WHERE a= AND b=?; +** +** The "a" in the select-list may be replaced by , iff: +** +** (a) is a constant expression, and +** (b) The (a=) comparison uses the BINARY collation sequence, and +** (c) Column "a" has an affinity other than NONE or BLOB. +** +** If argument pItem is NULL, then pMask must not be NULL. In this case this +** function is being called as part of determining whether or not pIdx +** is a covering index. This function clears any bits in (*pMask) +** corresponding to columns that may be replaced by constants as described +** above. +** +** Otherwise, if pItem is not NULL, then this function is being called +** as part of coding a loop that uses index pIdx. In this case, add entries +** to the Parse.pIdxPartExpr list for each column that can be replaced +** by a constant. +*/ +static void wherePartIdxExpr( + Parse *pParse, /* Parse context */ + Index *pIdx, /* Partial index being processed */ + Expr *pPart, /* WHERE clause being processed */ + Bitmask *pMask, /* Mask to clear bits in */ + int iIdxCur, /* Cursor number for index */ + SrcItem *pItem /* The FROM clause entry for the table */ +){ + assert( pItem==0 || (pItem->fg.jointype & JT_RIGHT)==0 ); + assert( (pItem==0 || pMask==0) && (pMask!=0 || pItem!=0) ); + + if( pPart->op==TK_AND ){ + wherePartIdxExpr(pParse, pIdx, pPart->pRight, pMask, iIdxCur, pItem); + pPart = pPart->pLeft; + } + + if( (pPart->op==TK_EQ || pPart->op==TK_IS) ){ + Expr *pLeft = pPart->pLeft; + Expr *pRight = pPart->pRight; + u8 aff; + + if( pLeft->op!=TK_COLUMN ) return; + if( !sqlite3ExprIsConstant(0, pRight) ) return; + if( !sqlite3IsBinary(sqlite3ExprCompareCollSeq(pParse, pPart)) ) return; + if( pLeft->iColumn<0 ) return; + aff = pIdx->pTable->aCol[pLeft->iColumn].affinity; + if( aff>=SQLITE_AFF_TEXT ){ + if( pItem ){ + sqlite3 *db = pParse->db; + IndexedExpr *p = (IndexedExpr*)sqlite3DbMallocRaw(db, sizeof(*p)); + if( p ){ + int bNullRow = (pItem->fg.jointype&(JT_LEFT|JT_LTORJ))!=0; + p->pExpr = sqlite3ExprDup(db, pRight, 0); + p->iDataCur = pItem->iCursor; + p->iIdxCur = iIdxCur; + p->iIdxCol = pLeft->iColumn; + p->bMaybeNullRow = bNullRow; + p->pIENext = pParse->pIdxPartExpr; + p->aff = aff; + pParse->pIdxPartExpr = p; + if( p->pIENext==0 ){ + void *pArg = (void*)&pParse->pIdxPartExpr; + sqlite3ParserAddCleanup(pParse, whereIndexedExprCleanup, pArg); + } + } + }else if( pLeft->iColumn<(BMS-1) ){ + *pMask &= ~((Bitmask)1 << pLeft->iColumn); + } + } + } +} + + /* ** Add all WhereLoop objects for a single table of the join where the table ** is identified by pBuilder->pNew->iTab. That table is guaranteed to be @@ -142857,18 +172832,18 @@ static int whereUsablePartialIndex(int iTab, WhereClause *pWC, Expr *pWhere){ ** cost = nRow * K // scan of covering index ** cost = nRow * (K+3.0) // scan of non-covering index ** -** where K is a value between 1.1 and 3.0 set based on the relative +** where K is a value between 1.1 and 3.0 set based on the relative ** estimated average size of the index and table records. ** ** For an index scan, where nVisit is the number of index rows visited -** by the scan, and nSeek is the number of seek operations required on +** by the scan, and nSeek is the number of seek operations required on ** the index b-tree: ** ** cost = nSeek * (log(nRow) + K * nVisit) // covering index ** cost = nSeek * (log(nRow) + (K+3.0) * nVisit) // non-covering index ** -** Normally, nSeek is 1. nSeek values greater than 1 come about if the -** WHERE clause includes "x IN (....)" terms used in place of "x=?". Or when +** Normally, nSeek is 1. nSeek values greater than 1 come about if the +** WHERE clause includes "x IN (....)" terms used in place of "x=?". Or when ** implicit "x IN (SELECT x FROM tbl)" terms are added for skip-scans. ** ** The estimated values (nRow, nVisit, nSeek) often contain a large amount @@ -142881,7 +172856,7 @@ static int whereUsablePartialIndex(int iTab, WhereClause *pWC, Expr *pWhere){ */ static int whereLoopAddBtree( WhereLoopBuilder *pBuilder, /* WHERE clause information */ - Bitmask mPrereq /* Extra prerequesites for using this table */ + Bitmask mPrereq /* Extra prerequisites for using this table */ ){ WhereInfo *pWInfo; /* WHERE analysis context */ Index *pProbe; /* An index we are evaluating */ @@ -142889,27 +172864,27 @@ static int whereLoopAddBtree( LogEst aiRowEstPk[2]; /* The aiRowLogEst[] value for the sPk index */ i16 aiColumnPk = -1; /* The aColumn[] value for the sPk index */ SrcList *pTabList; /* The FROM clause */ - struct SrcList_item *pSrc; /* The FROM clause btree term to add */ + SrcItem *pSrc; /* The FROM clause btree term to add */ WhereLoop *pNew; /* Template WhereLoop object */ int rc = SQLITE_OK; /* Return code */ int iSortIdx = 1; /* Index number */ int b; /* A boolean value */ LogEst rSize; /* number of rows in the table */ - LogEst rLogSize; /* Logarithm of the number of rows in the table */ WhereClause *pWC; /* The parsed WHERE clause */ Table *pTab; /* Table being queried */ - + pNew = pBuilder->pNew; pWInfo = pBuilder->pWInfo; pTabList = pWInfo->pTabList; pSrc = pTabList->a + pNew->iTab; - pTab = pSrc->pTab; + pTab = pSrc->pSTab; pWC = pBuilder->pWC; - assert( !IsVirtual(pSrc->pTab) ); + assert( !IsVirtual(pSrc->pSTab) ); - if( pSrc->pIBIndex ){ + if( pSrc->fg.isIndexedBy ){ + assert( pSrc->fg.isCte==0 ); /* An INDEXED BY clause specifies a particular index to use */ - pProbe = pSrc->pIBIndex; + pProbe = pSrc->u2.pIBIndex; }else if( !HasRowid(pTab) ){ pProbe = pTab->pIndex; }else{ @@ -142925,11 +172900,11 @@ static int whereLoopAddBtree( sPk.aiRowLogEst = aiRowEstPk; sPk.onError = OE_Replace; sPk.pTable = pTab; - sPk.szIdxRow = pTab->szTabRow; + sPk.szIdxRow = 3; /* TUNING: Interior rows of IPK table are very small */ sPk.idxType = SQLITE_IDXTYPE_IPK; aiRowEstPk[0] = pTab->nRowLogEst; aiRowEstPk[1] = 0; - pFirst = pSrc->pTab->pIndex; + pFirst = pSrc->pSTab->pIndex; if( pSrc->fg.notIndexed==0 ){ /* The real indices of the table are only considered if the ** NOT INDEXED qualifier is omitted from the FROM clause */ @@ -142938,22 +172913,23 @@ static int whereLoopAddBtree( pProbe = &sPk; } rSize = pTab->nRowLogEst; - rLogSize = estLog(rSize); #ifndef SQLITE_OMIT_AUTOMATIC_INDEX /* Automatic indexes */ if( !pBuilder->pOrSet /* Not part of an OR optimization */ - && (pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE)==0 + && (pWInfo->wctrlFlags & (WHERE_RIGHT_JOIN|WHERE_OR_SUBCLAUSE))==0 && (pWInfo->pParse->db->flags & SQLITE_AutoIndex)!=0 - && pSrc->pIBIndex==0 /* Has no INDEXED BY clause */ + && !pSrc->fg.isIndexedBy /* Has no INDEXED BY clause */ && !pSrc->fg.notIndexed /* Has no NOT INDEXED clause */ - && HasRowid(pTab) /* Not WITHOUT ROWID table. (FIXME: Why not?) */ && !pSrc->fg.isCorrelated /* Not a correlated subquery */ && !pSrc->fg.isRecursive /* Not a recursive common table expression. */ + && (pSrc->fg.jointype & JT_RIGHT)==0 /* Not the right tab of a RIGHT JOIN */ ){ /* Generate auto-index WhereLoops */ + LogEst rLogSize; /* Logarithm of the number of rows in the table */ WhereTerm *pTerm; WhereTerm *pWCEnd = pWC->a + pWC->nTerm; + rLogSize = estLog(rSize); for(pTerm=pWC->a; rc==SQLITE_OK && pTermprereqRight & pNew->maskSelf ) continue; if( termCanDriveIndex(pTerm, pSrc, 0) ){ @@ -142971,10 +172947,11 @@ static int whereLoopAddBtree( ** those objects, since there is no opportunity to add schema ** indexes on subqueries and views. */ pNew->rSetup = rLogSize + rSize; - if( pTab->pSelect==0 && (pTab->tabFlags & TF_Ephemeral)==0 ){ + if( !IsView(pTab) && (pTab->tabFlags & TF_Ephemeral)==0 ){ pNew->rSetup += 28; }else{ - pNew->rSetup -= 10; + pNew->rSetup -= 25; /* Greatly reduced setup cost for auto indexes + ** on ephemeral materializations of views */ } ApplyCostMultiplier(pNew->rSetup, pTab->costMult); if( pNew->rSetup<0 ) pNew->rSetup = 0; @@ -142992,13 +172969,15 @@ static int whereLoopAddBtree( } #endif /* SQLITE_OMIT_AUTOMATIC_INDEX */ - /* Loop over all indices. If there was an INDEXED BY clause, then only + /* Loop over all indices. If there was an INDEXED BY clause, then only ** consider index pProbe. */ - for(; rc==SQLITE_OK && pProbe; - pProbe=(pSrc->pIBIndex ? 0 : pProbe->pNext), iSortIdx++ + for(; rc==SQLITE_OK && pProbe; + pProbe=(pSrc->fg.isIndexedBy ? 0 : pProbe->pNext), iSortIdx++ ){ if( pProbe->pPartIdxWhere!=0 - && !whereUsablePartialIndex(pSrc->iCursor, pWC, pProbe->pPartIdxWhere) ){ + && !whereUsablePartialIndex(pSrc->iCursor, pSrc->fg.jointype, pWC, + pProbe->pPartIdxWhere) + ){ testcase( pNew->iTab!=pSrc->iCursor ); /* See ticket [98d973b8f5] */ continue; /* Partial index inappropriate for this query */ } @@ -143007,6 +172986,7 @@ static int whereLoopAddBtree( pNew->u.btree.nEq = 0; pNew->u.btree.nBtm = 0; pNew->u.btree.nTop = 0; + pNew->u.btree.nDistinctCol = 0; pNew->nSkip = 0; pNew->nLTerm = 0; pNew->iSortIdx = 0; @@ -143014,7 +172994,9 @@ static int whereLoopAddBtree( pNew->prereq = mPrereq; pNew->nOut = rSize; pNew->u.btree.pIndex = pProbe; + pNew->u.btree.pOrderBy = 0; b = indexMightHelpWithOrderBy(pBuilder, pProbe, pSrc->iCursor); + /* The ONEPASS_DESIRED flags never occurs together with ORDER BY */ assert( (pWInfo->wctrlFlags & WHERE_ONEPASS_DESIRED)==0 || b==0 ); if( pProbe->idxType==SQLITE_IDXTYPE_IPK ){ @@ -143023,27 +173005,88 @@ static int whereLoopAddBtree( /* Full table scan */ pNew->iSortIdx = b ? iSortIdx : 0; - /* TUNING: Cost of full table scan is (N*3.0). */ + /* TUNING: Cost of full table scan is 3.0*N. The 3.0 factor is an + ** extra cost designed to discourage the use of full table scans, + ** since index lookups have better worst-case performance if our + ** stat guesses are wrong. Reduce the 3.0 penalty slightly + ** (to 2.75) if we have valid STAT4 information for the table. + ** At 2.75, a full table scan is preferred over using an index on + ** a column with just two distinct values where each value has about + ** an equal number of appearances. Without STAT4 data, we still want + ** to use an index in that case, since the constraint might be for + ** the scarcer of the two values, and in that case an index lookup is + ** better. + */ +#ifdef SQLITE_ENABLE_STAT4 + pNew->rRun = rSize + 16 - 2*((pTab->tabFlags & TF_HasStat4)!=0); +#else pNew->rRun = rSize + 16; +#endif ApplyCostMultiplier(pNew->rRun, pTab->costMult); whereLoopOutputAdjust(pWC, pNew, rSize); + if( pSrc->fg.isSubquery ){ + if( pSrc->fg.viaCoroutine ) pNew->wsFlags |= WHERE_COROUTINE; + /* Do not set btree.pOrderBy for a recursive CTE. In this case + ** the ORDER BY clause does not determine the overall order that + ** rows are emitted from the CTE in. */ + if( (pSrc->u4.pSubq->pSelect->selFlags & SF_Recursive)==0 ){ + pNew->u.btree.pOrderBy = pSrc->u4.pSubq->pSelect->pOrderBy; + } + }else if( pSrc->fg.fromExists ){ + pNew->nOut = 0; + } rc = whereLoopInsert(pBuilder, pNew); pNew->nOut = rSize; if( rc ) break; }else{ Bitmask m; if( pProbe->isCovering ){ - pNew->wsFlags = WHERE_IDX_ONLY | WHERE_INDEXED; m = 0; + pNew->wsFlags = WHERE_IDX_ONLY | WHERE_INDEXED; }else{ m = pSrc->colUsed & pProbe->colNotIdxed; - pNew->wsFlags = (m==0) ? (WHERE_IDX_ONLY|WHERE_INDEXED) : WHERE_INDEXED; + if( pProbe->pPartIdxWhere ){ + wherePartIdxExpr( + pWInfo->pParse, pProbe, pProbe->pPartIdxWhere, &m, 0, 0 + ); + } + pNew->wsFlags = WHERE_INDEXED; + if( m==TOPBIT || (pProbe->bHasExpr && !pProbe->bHasVCol && m!=0) ){ + u32 isCov = whereIsCoveringIndex(pWInfo, pProbe, pSrc->iCursor); + if( isCov==0 ){ + WHERETRACE(0x200, + ("-> %s is not a covering index" + " according to whereIsCoveringIndex()\n", pProbe->zName)); + assert( m!=0 ); + }else{ + m = 0; + pNew->wsFlags |= isCov; + if( isCov & WHERE_IDX_ONLY ){ + WHERETRACE(0x200, + ("-> %s is a covering expression index" + " according to whereIsCoveringIndex()\n", pProbe->zName)); + }else{ + assert( isCov==WHERE_EXPRIDX ); + WHERETRACE(0x200, + ("-> %s might be a covering expression index" + " according to whereIsCoveringIndex()\n", pProbe->zName)); + } + } + }else if( m==0 + && (HasRowid(pTab) || pWInfo->pSelect!=0 || sqlite3FaultSim(700)) + ){ + WHERETRACE(0x200, + ("-> %s is a covering index according to bitmasks\n", + pProbe->zName, m==0 ? "is" : "is not")); + pNew->wsFlags = WHERE_IDX_ONLY | WHERE_INDEXED; + } } /* Full scan via index */ if( b || !HasRowid(pTab) || pProbe->pPartIdxWhere!=0 + || pSrc->fg.isIndexedBy || ( m==0 && pProbe->bUnordered==0 && (pProbe->szIdxRowszTabRow) @@ -143082,27 +173125,35 @@ static int whereLoopAddBtree( if( pTerm->eOperator & (WO_EQ|WO_IS) ) nLookup -= 19; } } - + pNew->rRun = sqlite3LogEstAdd(pNew->rRun, nLookup); } ApplyCostMultiplier(pNew->rRun, pTab->costMult); whereLoopOutputAdjust(pWC, pNew, rSize); - rc = whereLoopInsert(pBuilder, pNew); + if( (pSrc->fg.jointype & JT_RIGHT)!=0 && pProbe->aColExpr ){ + /* Do not do an SCAN of a index-on-expression in a RIGHT JOIN + ** because the cursor used to access the index might not be + ** positioned to the correct row during the right-join no-match + ** loop. */ + }else{ + if( pSrc->fg.fromExists ) pNew->nOut = 0; + rc = whereLoopInsert(pBuilder, pNew); + } pNew->nOut = rSize; if( rc ) break; } } - pBuilder->bldFlags = 0; + pBuilder->bldFlags1 = 0; rc = whereLoopAddBtreeIndex(pBuilder, pSrc, pProbe, 0); - if( pBuilder->bldFlags==SQLITE_BLDF_INDEXED ){ + if( pBuilder->bldFlags1==SQLITE_BLDF1_INDEXED ){ /* If a non-unique index is used, or if a prefix of the key for ** unique index is used (making the index functionally non-unique) ** then the sqlite_stat1 data becomes important for scoring the ** plan */ - pTab->tabFlags |= TF_StatsUsed; + pTab->tabFlags |= TF_MaybeReanalyze; } -#ifdef SQLITE_ENABLE_STAT3_OR_STAT4 +#ifdef SQLITE_ENABLE_STAT4 sqlite3Stat4ProbeFree(pBuilder->pRec); pBuilder->nRecValid = 0; pBuilder->pRec = 0; @@ -143113,6 +173164,30 @@ static int whereLoopAddBtree( #ifndef SQLITE_OMIT_VIRTUALTABLE +/* +** Return true if pTerm is a virtual table LIMIT or OFFSET term. +*/ +static int isLimitTerm(WhereTerm *pTerm){ + assert( pTerm->eOperator==WO_AUX || pTerm->eMatchOp==0 ); + return pTerm->eMatchOp>=SQLITE_INDEX_CONSTRAINT_LIMIT + && pTerm->eMatchOp<=SQLITE_INDEX_CONSTRAINT_OFFSET; +} + +/* +** Return true if the first nCons constraints in the pUsage array are +** marked as in-use (have argvIndex>0). False otherwise. +*/ +static int allConstraintsUsed( + struct sqlite3_index_constraint_usage *aUsage, + int nCons +){ + int ii; + for(ii=0; iipNew->iTab. This @@ -143140,9 +173215,11 @@ static int whereLoopAddVirtualOne( u16 mExclude, /* Exclude terms using these operators */ sqlite3_index_info *pIdxInfo, /* Populated object for xBestIndex */ u16 mNoOmit, /* Do not omit these constraints */ - int *pbIn /* OUT: True if plan uses an IN(...) op */ + int *pbIn, /* OUT: True if plan uses an IN(...) op */ + int *pbRetryLimit /* OUT: Retry without LIMIT/OFFSET */ ){ WhereClause *pWC = pBuilder->pWC; + HiddenIndexInfo *pHidden = (HiddenIndexInfo*)&pIdxInfo[1]; struct sqlite3_index_constraint *pIdxCons; struct sqlite3_index_constraint_usage *pUsage = pIdxInfo->aConstraintUsage; int i; @@ -143150,21 +173227,22 @@ static int whereLoopAddVirtualOne( int rc = SQLITE_OK; WhereLoop *pNew = pBuilder->pNew; Parse *pParse = pBuilder->pWInfo->pParse; - struct SrcList_item *pSrc = &pBuilder->pWInfo->pTabList->a[pNew->iTab]; + SrcItem *pSrc = &pBuilder->pWInfo->pTabList->a[pNew->iTab]; int nConstraint = pIdxInfo->nConstraint; assert( (mUsable & mPrereq)==mPrereq ); *pbIn = 0; pNew->prereq = mPrereq; - /* Set the usable flag on the subset of constraints identified by + /* Set the usable flag on the subset of constraints identified by ** arguments mUsable and mExclude. */ pIdxCons = *(struct sqlite3_index_constraint**)&pIdxInfo->aConstraint; for(i=0; ia[pIdxCons->iTermOffset]; + WhereTerm *pTerm = termFromWhereClause(pWC, pIdxCons->iTermOffset); pIdxCons->usable = 0; - if( (pTerm->prereqRight & mUsable)==pTerm->prereqRight + if( (pTerm->prereqRight & mUsable)==pTerm->prereqRight && (pTerm->eOperator & mExclude)==0 + && (pbRetryLimit || !isLimitTerm(pTerm)) ){ pIdxCons->usable = 1; } @@ -143179,17 +173257,18 @@ static int whereLoopAddVirtualOne( pIdxInfo->estimatedCost = SQLITE_BIG_DBL / (double)2; pIdxInfo->estimatedRows = 25; pIdxInfo->idxFlags = 0; - pIdxInfo->colUsed = (sqlite3_int64)pSrc->colUsed; + pHidden->mHandleIn = 0; /* Invoke the virtual table xBestIndex() method */ - rc = vtabBestIndex(pParse, pSrc->pTab, pIdxInfo); + rc = vtabBestIndex(pParse, pSrc->pSTab, pIdxInfo); if( rc ){ if( rc==SQLITE_CONSTRAINT ){ /* If the xBestIndex method returns SQLITE_CONSTRAINT, that means ** that the particular combination of parameters provided is unusable. ** Make no entries in the loop table. */ - WHERETRACE(0xffff, (" ^^^^--- non-viable plan rejected!\n")); + WHERETRACE(0xffffffff, (" ^^^^--- non-viable plan rejected!\n")); + freeIdxStr(pIdxInfo); return SQLITE_OK; } return rc; @@ -143197,8 +173276,8 @@ static int whereLoopAddVirtualOne( mxTerm = -1; assert( pNew->nLSlot>=nConstraint ); - for(i=0; iaLTerm[i] = 0; - pNew->u.vtab.omitMask = 0; + memset(pNew->aLTerm, 0, sizeof(pNew->aLTerm[0])*nConstraint ); + memset(&pNew->u.vtab, 0, sizeof(pNew->u.vtab)); pIdxCons = *(struct sqlite3_index_constraint**)&pIdxInfo->aConstraint; for(i=0; iiTermOffset; if( iTerm>=nConstraint || j<0 - || j>=pWC->nTerm + || (pTerm = termFromWhereClause(pWC, j))==0 || pNew->aLTerm[iTerm]!=0 || pIdxCons->usable==0 ){ - sqlite3ErrorMsg(pParse,"%s.xBestIndex malfunction",pSrc->pTab->zName); - testcase( pIdxInfo->needToFreeIdxStr ); + sqlite3ErrorMsg(pParse,"%s.xBestIndex malfunction",pSrc->pSTab->zName); + freeIdxStr(pIdxInfo); return SQLITE_ERROR; } testcase( iTerm==nConstraint-1 ); testcase( j==0 ); testcase( j==pWC->nTerm-1 ); - pTerm = &pWC->a[j]; pNew->prereq |= pTerm->prereqRight; assert( iTermnLSlot ); pNew->aLTerm[iTerm] = pTerm; if( iTerm>mxTerm ) mxTerm = iTerm; testcase( iTerm==15 ); testcase( iTerm==16 ); - if( iTerm<16 && pUsage[i].omit ) pNew->u.vtab.omitMask |= 1<eOperator & WO_IN)!=0 ){ + if( pUsage[i].omit ){ + if( i<16 && ((1<u.vtab.omitMask |= 1<eMatchOp==SQLITE_INDEX_CONSTRAINT_OFFSET ){ + pNew->u.vtab.bOmitOffset = 1; + } + } + if( SMASKBIT32(i) & pHidden->mHandleIn ){ + pNew->u.vtab.mHandleIn |= MASKBIT32(iTerm); + }else if( (pTerm->eOperator & WO_IN)!=0 ){ /* A virtual table that is constrained by an IN clause may not ** consume the ORDER BY clause because (1) the order of IN terms ** is not necessarily related to the order of output terms and @@ -143236,17 +173326,35 @@ static int whereLoopAddVirtualOne( pIdxInfo->idxFlags &= ~SQLITE_INDEX_SCAN_UNIQUE; *pbIn = 1; assert( (mExclude & WO_IN)==0 ); } + + /* Unless pbRetryLimit is non-NULL, there should be no LIMIT/OFFSET + ** terms. And if there are any, they should follow all other terms. */ + assert( pbRetryLimit || !isLimitTerm(pTerm) ); + assert( !isLimitTerm(pTerm) || i>=nConstraint-2 ); + assert( !isLimitTerm(pTerm) || i==nConstraint-1 || isLimitTerm(pTerm+1) ); + + if( isLimitTerm(pTerm) && (*pbIn || !allConstraintsUsed(pUsage, i)) ){ + /* If there is an IN(...) term handled as an == (separate call to + ** xFilter for each value on the RHS of the IN) and a LIMIT or + ** OFFSET term handled as well, the plan is unusable. Similarly, + ** if there is a LIMIT/OFFSET and there are other unused terms, + ** the plan cannot be used. In these cases set variable *pbRetryLimit + ** to true to tell the caller to retry with LIMIT and OFFSET + ** disabled. */ + freeIdxStr(pIdxInfo); + *pbRetryLimit = 1; + return SQLITE_OK; + } } } - pNew->u.vtab.omitMask &= ~mNoOmit; pNew->nLTerm = mxTerm+1; for(i=0; i<=mxTerm; i++){ if( pNew->aLTerm[i]==0 ){ /* The non-zero argvIdx values must be contiguous. Raise an ** error if they are not */ - sqlite3ErrorMsg(pParse,"%s.xBestIndex malfunction",pSrc->pTab->zName); - testcase( pIdxInfo->needToFreeIdxStr ); + sqlite3ErrorMsg(pParse,"%s.xBestIndex malfunction",pSrc->pSTab->zName); + freeIdxStr(pIdxInfo); return SQLITE_ERROR; } } @@ -143257,6 +173365,7 @@ static int whereLoopAddVirtualOne( pNew->u.vtab.idxStr = pIdxInfo->idxStr; pNew->u.vtab.isOrdered = (i8)(pIdxInfo->orderByConsumed ? pIdxInfo->nOrderBy : 0); + pNew->u.vtab.bIdxNumHex = (pIdxInfo->idxFlags&SQLITE_INDEX_SCAN_HEX)!=0; pNew->rSetup = 0; pNew->rRun = sqlite3LogEstFromDouble(pIdxInfo->estimatedCost); pNew->nOut = sqlite3LogEst(pIdxInfo->estimatedRows); @@ -143273,7 +173382,7 @@ static int whereLoopAddVirtualOne( sqlite3_free(pNew->u.vtab.idxStr); pNew->u.vtab.needFree = 0; } - WHERETRACE(0xffff, (" bIn=%d prereqIn=%04llx prereqOut=%04llx\n", + WHERETRACE(0xffffffff, (" bIn=%d prereqIn=%04llx prereqOut=%04llx\n", *pbIn, (sqlite3_uint64)mPrereq, (sqlite3_uint64)(pNew->prereq & ~mPrereq))); @@ -143281,11 +173390,19 @@ static int whereLoopAddVirtualOne( } /* -** If this function is invoked from within an xBestIndex() callback, it -** returns a pointer to a buffer containing the name of the collation -** sequence associated with element iCons of the sqlite3_index_info.aConstraint -** array. Or, if iCons is out of range or there is no active xBestIndex -** call, return NULL. +** Return the collating sequence for a constraint passed into xBestIndex. +** +** pIdxInfo must be an sqlite3_index_info structure passed into xBestIndex. +** This routine depends on there being a HiddenIndexInfo structure immediately +** following the sqlite3_index_info structure. +** +** Return a pointer to the collation name: +** +** 1. If there is an explicit COLLATE operator on the constraint, return it. +** +** 2. Else, if the column has an alternative collation, return that. +** +** 3. Otherwise, return "BINARY". */ SQLITE_API const char *sqlite3_vtab_collation(sqlite3_index_info *pIdxInfo, int iCons){ HiddenIndexInfo *pHidden = (HiddenIndexInfo*)&pIdxInfo[1]; @@ -143293,15 +173410,103 @@ SQLITE_API const char *sqlite3_vtab_collation(sqlite3_index_info *pIdxInfo, int if( iCons>=0 && iConsnConstraint ){ CollSeq *pC = 0; int iTerm = pIdxInfo->aConstraint[iCons].iTermOffset; - Expr *pX = pHidden->pWC->a[iTerm].pExpr; + Expr *pX = termFromWhereClause(pHidden->pWC, iTerm)->pExpr; if( pX->pLeft ){ - pC = sqlite3BinaryCompareCollSeq(pHidden->pParse, pX->pLeft, pX->pRight); + pC = sqlite3ExprCompareCollSeq(pHidden->pParse, pX); } zRet = (pC ? pC->zName : sqlite3StrBINARY); } return zRet; } +/* +** Return true if constraint iCons is really an IN(...) constraint, or +** false otherwise. If iCons is an IN(...) constraint, set (if bHandle!=0) +** or clear (if bHandle==0) the flag to handle it using an iterator. +*/ +SQLITE_API int sqlite3_vtab_in(sqlite3_index_info *pIdxInfo, int iCons, int bHandle){ + HiddenIndexInfo *pHidden = (HiddenIndexInfo*)&pIdxInfo[1]; + u32 m = SMASKBIT32(iCons); + if( m & pHidden->mIn ){ + if( bHandle==0 ){ + pHidden->mHandleIn &= ~m; + }else if( bHandle>0 ){ + pHidden->mHandleIn |= m; + } + return 1; + } + return 0; +} + +/* +** This interface is callable from within the xBestIndex callback only. +** +** If possible, set (*ppVal) to point to an object containing the value +** on the right-hand-side of constraint iCons. +*/ +SQLITE_API int sqlite3_vtab_rhs_value( + sqlite3_index_info *pIdxInfo, /* Copy of first argument to xBestIndex */ + int iCons, /* Constraint for which RHS is wanted */ + sqlite3_value **ppVal /* Write value extracted here */ +){ + HiddenIndexInfo *pH = (HiddenIndexInfo*)&pIdxInfo[1]; + sqlite3_value *pVal = 0; + int rc = SQLITE_OK; + if( iCons<0 || iCons>=pIdxInfo->nConstraint ){ + rc = SQLITE_MISUSE_BKPT; /* EV: R-30545-25046 */ + }else{ + if( pH->aRhs[iCons]==0 ){ + WhereTerm *pTerm = termFromWhereClause( + pH->pWC, pIdxInfo->aConstraint[iCons].iTermOffset + ); + rc = sqlite3ValueFromExpr( + pH->pParse->db, pTerm->pExpr->pRight, ENC(pH->pParse->db), + SQLITE_AFF_BLOB, &pH->aRhs[iCons] + ); + testcase( rc!=SQLITE_OK ); + } + pVal = pH->aRhs[iCons]; + } + *ppVal = pVal; + + if( rc==SQLITE_OK && pVal==0 ){ /* IMP: R-19933-32160 */ + rc = SQLITE_NOTFOUND; /* IMP: R-36424-56542 */ + } + + return rc; +} + +/* +** Return true if ORDER BY clause may be handled as DISTINCT. +*/ +SQLITE_API int sqlite3_vtab_distinct(sqlite3_index_info *pIdxInfo){ + HiddenIndexInfo *pHidden = (HiddenIndexInfo*)&pIdxInfo[1]; + assert( pHidden->eDistinct>=0 && pHidden->eDistinct<=3 ); + return pHidden->eDistinct; +} + +/* +** Cause the prepared statement that is associated with a call to +** xBestIndex to potentially use all schemas. If the statement being +** prepared is read-only, then just start read transactions on all +** schemas. But if this is a write operation, start writes on all +** schemas. +** +** This is used by the (built-in) sqlite_dbpage virtual table. +*/ +SQLITE_PRIVATE void sqlite3VtabUsesAllSchemas(Parse *pParse){ + int nDb = pParse->db->nDb; + int i; + for(i=0; iwriteMask) ){ + for(i=0; ipNew->iTab. That table is guaranteed to be a virtual table. @@ -143311,8 +173516,8 @@ SQLITE_API const char *sqlite3_vtab_collation(sqlite3_index_info *pIdxInfo, int ** entries that occur before the virtual table in the FROM clause and are ** separated from it by at least one LEFT or CROSS JOIN. Similarly, the ** mUnusable mask contains all FROM clause entries that occur after the -** virtual table and are separated from it by at least one LEFT or -** CROSS JOIN. +** virtual table and are separated from it by at least one LEFT or +** CROSS JOIN. ** ** For example, if the query were: ** @@ -143320,9 +173525,9 @@ SQLITE_API const char *sqlite3_vtab_collation(sqlite3_index_info *pIdxInfo, int ** ** then mPrereq corresponds to (t1, t2) and mUnusable to (t5, t6). ** -** All the tables in mPrereq must be scanned before the current virtual -** table. So any terms for which all prerequisites are satisfied by -** mPrereq may be specified as "usable" in all calls to xBestIndex. +** All the tables in mPrereq must be scanned before the current virtual +** table. So any terms for which all prerequisites are satisfied by +** mPrereq may be specified as "usable" in all calls to xBestIndex. ** Conversely, all tables in mUnusable must be scanned after the current ** virtual table, so any terms for which the prerequisites overlap with ** mUnusable should always be configured as "not-usable" for xBestIndex. @@ -143336,13 +173541,14 @@ static int whereLoopAddVirtual( WhereInfo *pWInfo; /* WHERE analysis context */ Parse *pParse; /* The parsing context */ WhereClause *pWC; /* The WHERE clause */ - struct SrcList_item *pSrc; /* The FROM clause term to search */ + SrcItem *pSrc; /* The FROM clause term to search */ sqlite3_index_info *p; /* Object to pass to xBestIndex() */ int nConstraint; /* Number of constraints in p */ int bIn; /* True if plan uses IN(...) operator */ WhereLoop *pNew; Bitmask mBest; /* Tables used by best possible plan */ u16 mNoOmit; + int bRetry = 0; /* True to retry with LIMIT/OFFSET disabled */ assert( (mPrereq & mUnusable)==0 ); pWInfo = pBuilder->pWInfo; @@ -143350,9 +173556,8 @@ static int whereLoopAddVirtual( pWC = pBuilder->pWC; pNew = pBuilder->pNew; pSrc = &pWInfo->pTabList->a[pNew->iTab]; - assert( IsVirtual(pSrc->pTab) ); - p = allocateIndexInfo(pParse, pWC, mUnusable, pSrc, pBuilder->pOrderBy, - &mNoOmit); + assert( IsVirtual(pSrc->pSTab) ); + p = allocateIndexInfo(pWInfo, pWC, mUnusable, pSrc, &mNoOmit); if( p==0 ) return SQLITE_NOMEM_BKPT; pNew->rSetup = 0; pNew->wsFlags = WHERE_VIRTUALTABLE; @@ -143360,18 +173565,26 @@ static int whereLoopAddVirtual( pNew->u.vtab.needFree = 0; nConstraint = p->nConstraint; if( whereLoopResize(pParse->db, pNew, nConstraint) ){ - sqlite3DbFree(pParse->db, p); + freeIndexInfo(pParse->db, p); return SQLITE_NOMEM_BKPT; } /* First call xBestIndex() with all constraints usable. */ - WHERETRACE(0x800, ("BEGIN %s.addVirtual()\n", pSrc->pTab->zName)); - WHERETRACE(0x40, (" VirtualOne: all usable\n")); - rc = whereLoopAddVirtualOne(pBuilder, mPrereq, ALLBITS, 0, p, mNoOmit, &bIn); + WHERETRACE(0x800, ("BEGIN %s.addVirtual()\n", pSrc->pSTab->zName)); + WHERETRACE(0x800, (" VirtualOne: all usable\n")); + rc = whereLoopAddVirtualOne( + pBuilder, mPrereq, ALLBITS, 0, p, mNoOmit, &bIn, &bRetry + ); + if( bRetry ){ + assert( rc==SQLITE_OK ); + rc = whereLoopAddVirtualOne( + pBuilder, mPrereq, ALLBITS, 0, p, mNoOmit, &bIn, 0 + ); + } /* If the call to xBestIndex() with all terms enabled produced a plan ** that does not require any source tables (IOW: a plan with mBest==0) - ** and does not use an IN(...) operator, then there is no point in making + ** and does not use an IN(...) operator, then there is no point in making ** any further calls to xBestIndex() since they will all return the same ** result (if the xBestIndex() implementation is sane). */ if( rc==SQLITE_OK && ((mBest = (pNew->prereq & ~mPrereq))!=0 || bIn) ){ @@ -143383,9 +173596,9 @@ static int whereLoopAddVirtual( /* If the plan produced by the earlier call uses an IN(...) term, call ** xBestIndex again, this time with IN(...) terms disabled. */ if( bIn ){ - WHERETRACE(0x40, (" VirtualOne: all usable w/o IN\n")); + WHERETRACE(0x800, (" VirtualOne: all usable w/o IN\n")); rc = whereLoopAddVirtualOne( - pBuilder, mPrereq, ALLBITS, WO_IN, p, mNoOmit, &bIn); + pBuilder, mPrereq, ALLBITS, WO_IN, p, mNoOmit, &bIn, 0); assert( bIn==0 ); mBestNoIn = pNew->prereq & ~mPrereq; if( mBestNoIn==0 ){ @@ -143394,25 +173607,24 @@ static int whereLoopAddVirtual( } } - /* Call xBestIndex once for each distinct value of (prereqRight & ~mPrereq) + /* Call xBestIndex once for each distinct value of (prereqRight & ~mPrereq) ** in the set of terms that apply to the current virtual table. */ while( rc==SQLITE_OK ){ int i; Bitmask mNext = ALLBITS; assert( mNext>0 ); for(i=0; ia[p->aConstraint[i].iTermOffset].prereqRight & ~mPrereq - ); + int iTerm = p->aConstraint[i].iTermOffset; + Bitmask mThis = termFromWhereClause(pWC, iTerm)->prereqRight & ~mPrereq; if( mThis>mPrev && mThisprereq==mPrereq ){ seenZero = 1; if( bIn==0 ) seenZeroNoIN = 1; @@ -143423,9 +173635,9 @@ static int whereLoopAddVirtual( ** that requires no source tables at all (i.e. one guaranteed to be ** usable), make a call here with all source tables disabled */ if( rc==SQLITE_OK && seenZero==0 ){ - WHERETRACE(0x40, (" VirtualOne: all disabled\n")); + WHERETRACE(0x800, (" VirtualOne: all disabled\n")); rc = whereLoopAddVirtualOne( - pBuilder, mPrereq, mPrereq, 0, p, mNoOmit, &bIn); + pBuilder, mPrereq, mPrereq, 0, p, mNoOmit, &bIn, 0); if( bIn==0 ) seenZeroNoIN = 1; } @@ -143433,15 +173645,14 @@ static int whereLoopAddVirtual( ** that requires no source tables at all and does not use an IN(...) ** operator, make a final call to obtain one here. */ if( rc==SQLITE_OK && seenZeroNoIN==0 ){ - WHERETRACE(0x40, (" VirtualOne: all disabled and w/o IN\n")); + WHERETRACE(0x800, (" VirtualOne: all disabled and w/o IN\n")); rc = whereLoopAddVirtualOne( - pBuilder, mPrereq, mPrereq, WO_IN, p, mNoOmit, &bIn); + pBuilder, mPrereq, mPrereq, WO_IN, p, mNoOmit, &bIn, 0); } } - if( p->needToFreeIdxStr ) sqlite3_free(p->idxStr); - sqlite3DbFreeNN(pParse->db, p); - WHERETRACE(0x800, ("END %s.addVirtual(), rc=%d\n", pSrc->pTab->zName, rc)); + freeIndexInfo(pParse->db, p); + WHERETRACE(0x800, ("END %s.addVirtual(), rc=%d\n", pSrc->pSTab->zName, rc)); return rc; } #endif /* SQLITE_OMIT_VIRTUALTABLE */ @@ -143451,8 +173662,8 @@ static int whereLoopAddVirtual( ** btrees or virtual tables. */ static int whereLoopAddOr( - WhereLoopBuilder *pBuilder, - Bitmask mPrereq, + WhereLoopBuilder *pBuilder, + Bitmask mPrereq, Bitmask mUnusable ){ WhereInfo *pWInfo = pBuilder->pWInfo; @@ -143464,8 +173675,8 @@ static int whereLoopAddOr( WhereClause tempWC; WhereLoopBuilder sSubBuild; WhereOrSet sSum, sCur; - struct SrcList_item *pItem; - + SrcItem *pItem; + pWC = pBuilder->pWC; pWCEnd = pWC->a + pWC->nTerm; pNew = pBuilder->pNew; @@ -143473,21 +173684,23 @@ static int whereLoopAddOr( pItem = pWInfo->pTabList->a + pNew->iTab; iCur = pItem->iCursor; + /* The multi-index OR optimization does not work for RIGHT and FULL JOIN */ + if( pItem->fg.jointype & JT_RIGHT ) return SQLITE_OK; + for(pTerm=pWC->a; pTermeOperator & WO_OR)!=0 - && (pTerm->u.pOrInfo->indexable & pNew->maskSelf)!=0 + && (pTerm->u.pOrInfo->indexable & pNew->maskSelf)!=0 ){ WhereClause * const pOrWC = &pTerm->u.pOrInfo->wc; WhereTerm * const pOrWCEnd = &pOrWC->a[pOrWC->nTerm]; WhereTerm *pOrTerm; int once = 1; int i, j; - + sSubBuild = *pBuilder; - sSubBuild.pOrderBy = 0; sSubBuild.pOrSet = &sCur; - WHERETRACE(0x200, ("Begin processing OR-clause %p\n", pTerm)); + WHERETRACE(0x400, ("Begin processing OR-clause %p\n", pTerm)); for(pOrTerm=pOrWC->a; pOrTermeOperator & WO_AND)!=0 ){ sSubBuild.pWC = &pOrTerm->u.pAndInfo->wc; @@ -143496,6 +173709,7 @@ static int whereLoopAddOr( tempWC.pOuter = pWC; tempWC.op = TK_AND; tempWC.nTerm = 1; + tempWC.nBase = 1; tempWC.a = pOrTerm; sSubBuild.pWC = &tempWC; }else{ @@ -143503,14 +173717,14 @@ static int whereLoopAddOr( } sCur.n = 0; #ifdef WHERETRACE_ENABLED - WHERETRACE(0x200, ("OR-term %d of %p has %d subterms:\n", + WHERETRACE(0x400, ("OR-term %d of %p has %d subterms:\n", (int)(pOrTerm-pOrWC->a), pTerm, sSubBuild.pWC->nTerm)); - if( sqlite3WhereTrace & 0x400 ){ + if( sqlite3WhereTrace & 0x20000 ){ sqlite3WhereClausePrint(sSubBuild.pWC); } #endif #ifndef SQLITE_OMIT_VIRTUALTABLE - if( IsVirtual(pItem->pTab) ){ + if( IsVirtual(pItem->pSTab) ){ rc = whereLoopAddVirtual(&sSubBuild, mPrereq, mUnusable); }else #endif @@ -143520,7 +173734,8 @@ static int whereLoopAddOr( if( rc==SQLITE_OK ){ rc = whereLoopAddOr(&sSubBuild, mPrereq, mUnusable); } - assert( rc==SQLITE_OK || sCur.n==0 ); + testcase( rc==SQLITE_NOMEM && sCur.n>0 ); + testcase( rc==SQLITE_DONE ); if( sCur.n==0 ){ sSum.n = 0; break; @@ -143550,8 +173765,8 @@ static int whereLoopAddOr( /* TUNING: Currently sSum.a[i].rRun is set to the sum of the costs ** of all sub-scans required by the OR-scan. However, due to rounding ** errors, it may be that the cost of the OR-scan is equal to its - ** most expensive sub-scan. Add the smallest possible penalty - ** (equivalent to multiplying the cost by 1.07) to ensure that + ** most expensive sub-scan. Add the smallest possible penalty + ** (equivalent to multiplying the cost by 1.07) to ensure that ** this does not happen. Otherwise, for WHERE clauses such as the ** following where there is an index on "y": ** @@ -143564,14 +173779,14 @@ static int whereLoopAddOr( pNew->prereq = sSum.a[i].prereq; rc = whereLoopInsert(pBuilder, pNew); } - WHERETRACE(0x200, ("End processing OR-clause %p\n", pTerm)); + WHERETRACE(0x400, ("End processing OR-clause %p\n", pTerm)); } } return rc; } /* -** Add all WhereLoop objects for all tables +** Add all WhereLoop objects for all tables */ static int whereLoopAddAll(WhereLoopBuilder *pBuilder){ WhereInfo *pWInfo = pBuilder->pWInfo; @@ -143579,33 +173794,73 @@ static int whereLoopAddAll(WhereLoopBuilder *pBuilder){ Bitmask mPrior = 0; int iTab; SrcList *pTabList = pWInfo->pTabList; - struct SrcList_item *pItem; - struct SrcList_item *pEnd = &pTabList->a[pWInfo->nLevel]; + SrcItem *pItem; + SrcItem *pEnd = &pTabList->a[pWInfo->nLevel]; sqlite3 *db = pWInfo->pParse->db; int rc = SQLITE_OK; + int bFirstPastRJ = 0; + int hasRightCrossJoin = 0; WhereLoop *pNew; - u8 priorJointype = 0; + /* Loop over the tables in the join, from left to right */ pNew = pBuilder->pNew; - whereLoopInit(pNew); + + /* Verify that pNew has already been initialized */ + assert( pNew->nLTerm==0 ); + assert( pNew->wsFlags==0 ); + assert( pNew->nLSlot>=ArraySize(pNew->aLTermSpace) ); + assert( pNew->aLTerm!=0 ); + pBuilder->iPlanLimit = SQLITE_QUERY_PLANNER_LIMIT; for(iTab=0, pItem=pTabList->a; pItemiTab = iTab; pBuilder->iPlanLimit += SQLITE_QUERY_PLANNER_LIMIT_INCR; pNew->maskSelf = sqlite3WhereGetMask(&pWInfo->sMaskSet, pItem->iCursor); - if( ((pItem->fg.jointype|priorJointype) & (JT_LEFT|JT_CROSS))!=0 ){ - /* This condition is true when pItem is the FROM clause term on the - ** right-hand-side of a LEFT or CROSS JOIN. */ - mPrereq = mPrior; + if( bFirstPastRJ + || (pItem->fg.jointype & (JT_OUTER|JT_CROSS|JT_LTORJ))!=0 + ){ + /* Add prerequisites to prevent reordering of FROM clause terms + ** across CROSS joins and outer joins. The bFirstPastRJ boolean + ** prevents the right operand of a RIGHT JOIN from being swapped with + ** other elements even further to the right. + ** + ** The hasRightCrossJoin flag prevent FROM-clause terms from moving + ** from the right side of a LEFT JOIN or CROSS JOIN over to the + ** left side of that same join. This is a required restriction in + ** the case of LEFT JOIN - an incorrect answer may results if it is + ** not enforced. This restriction is not required for CROSS JOIN. + ** It is provided merely as a means of controlling join order, under + ** the theory that no real-world queries that care about performance + ** actually use the CROSS JOIN syntax. + */ + if( pItem->fg.jointype & (JT_LTORJ|JT_CROSS) ){ + testcase( pItem->fg.jointype & JT_LTORJ ); + testcase( pItem->fg.jointype & JT_CROSS ); + hasRightCrossJoin = 1; + } + mPrereq |= mPrior; + bFirstPastRJ = (pItem->fg.jointype & JT_RIGHT)!=0; + }else if( pItem->fg.fromExists ){ + /* joins that result from the EXISTS-to-JOIN optimization should not + ** be moved to the left of any of their dependencies */ + WhereClause *pWC = &pWInfo->sWC; + WhereTerm *pTerm; + int i; + for(i=pWC->nBase, pTerm=pWC->a; i>0; i--, pTerm++){ + if( (pNew->maskSelf & pTerm->prereqAll)!=0 ){ + mPrereq |= (pTerm->prereqAll & (pNew->maskSelf-1)); + } + } + }else if( !hasRightCrossJoin ){ + mPrereq = 0; } - priorJointype = pItem->fg.jointype; #ifndef SQLITE_OMIT_VIRTUALTABLE - if( IsVirtual(pItem->pTab) ){ - struct SrcList_item *p; + if( IsVirtual(pItem->pSTab) ){ + SrcItem *p; for(p=&pItem[1]; pfg.jointype & (JT_LEFT|JT_CROSS)) ){ + if( mUnusable || (p->fg.jointype & (JT_OUTER|JT_CROSS)) ){ mUnusable |= sqlite3WhereGetMask(&pWInfo->sMaskSet, p->iCursor); } } @@ -143634,21 +173889,112 @@ static int whereLoopAddAll(WhereLoopBuilder *pBuilder){ return rc; } +/* Implementation of the order-by-subquery optimization: +** +** WhereLoop pLoop, which the iLoop-th term of the nested loop, is really +** a subquery or CTE that has an ORDER BY clause. See if any of the terms +** in the subquery ORDER BY clause will satisfy pOrderBy from the outer +** query. Mark off all satisfied terms (by setting bits in *pOBSat) and +** return TRUE if they do. If not, return false. +** +** Example: +** +** CREATE TABLE t1(a,b,c, PRIMARY KEY(a,b)); +** CREATE TABLE t2(x,y); +** WITH t3(p,q) AS MATERIALIZED (SELECT x+y, x-y FROM t2 ORDER BY x+y) +** SELECT * FROM t3 JOIN t1 ON a=q ORDER BY p, b; +** +** The CTE named "t3" comes out in the natural order of "p", so the first +** first them of "ORDER BY p,b" is satisfied by a sequential scan of "t3" +** and sorting only needs to occur on the second term "b". +** +** Limitations: +** +** (1) The optimization is not applied if the outer ORDER BY contains +** a COLLATE clause. The optimization might be applied if the +** outer ORDER BY uses NULLS FIRST, NULLS LAST, ASC, and/or DESC as +** long as the subquery ORDER BY does the same. But if the +** outer ORDER BY uses COLLATE, even a redundant COLLATE, the +** optimization is bypassed. +** +** (2) The subquery ORDER BY terms must exactly match subquery result +** columns, including any COLLATE annotations. This routine relies +** on iOrderByCol to do matching between order by terms and result +** columns, and iOrderByCol will not be set if the result column +** and ORDER BY collations differ. +** +** (3) The subquery and outer ORDER BY can be in opposite directions as +** long as the subquery is materialized. If the subquery is +** implemented as a co-routine, the sort orders must be in the same +** direction because there is no way to run a co-routine backwards. +*/ +static SQLITE_NOINLINE int wherePathMatchSubqueryOB( + WhereInfo *pWInfo, /* The WHERE clause */ + WhereLoop *pLoop, /* The nested loop term that is a subquery */ + int iLoop, /* Which level of the nested loop. 0==outermost */ + int iCur, /* Cursor used by the this loop */ + ExprList *pOrderBy, /* The ORDER BY clause on the whole query */ + Bitmask *pRevMask, /* When loops need to go in reverse order */ + Bitmask *pOBSat /* Which terms of pOrderBy are satisfied so far */ +){ + int iOB; /* Index into pOrderBy->a[] */ + int jSub; /* Index into pSubOB->a[] */ + u8 rev = 0; /* True if iOB and jSub sort in opposite directions */ + u8 revIdx = 0; /* Sort direction for jSub */ + Expr *pOBExpr; /* Current term of outer ORDER BY */ + ExprList *pSubOB; /* Complete ORDER BY on the subquery */ + + pSubOB = pLoop->u.btree.pOrderBy; + assert( pSubOB!=0 ); + for(iOB=0; (MASKBIT(iOB) & *pOBSat)!=0; iOB++){} + for(jSub=0; jSubnExpr && iOBnExpr; jSub++, iOB++){ + if( pSubOB->a[jSub].u.x.iOrderByCol==0 ) break; + pOBExpr = pOrderBy->a[iOB].pExpr; + if( pOBExpr->op!=TK_COLUMN && pOBExpr->op!=TK_AGG_COLUMN ) break; + if( pOBExpr->iTable!=iCur ) break; + if( pOBExpr->iColumn!=pSubOB->a[jSub].u.x.iOrderByCol-1 ) break; + if( (pWInfo->wctrlFlags & WHERE_GROUPBY)==0 ){ + u8 sfOB = pOrderBy->a[iOB].fg.sortFlags; /* sortFlags for iOB */ + u8 sfSub = pSubOB->a[jSub].fg.sortFlags; /* sortFlags for jSub */ + if( (sfSub & KEYINFO_ORDER_BIGNULL) != (sfOB & KEYINFO_ORDER_BIGNULL) ){ + break; + } + revIdx = sfSub & KEYINFO_ORDER_DESC; + if( jSub>0 ){ + if( (rev^revIdx)!=(sfOB & KEYINFO_ORDER_DESC) ){ + break; + } + }else{ + rev = revIdx ^ (sfOB & KEYINFO_ORDER_DESC); + if( rev ){ + if( (pLoop->wsFlags & WHERE_COROUTINE)!=0 ){ + /* Cannot run a co-routine in reverse order */ + break; + } + *pRevMask |= MASKBIT(iLoop); + } + } + } + *pOBSat |= MASKBIT(iOB); + } + return jSub>0; +} + /* ** Examine a WherePath (with the addition of the extra WhereLoop of the 6th ** parameters) to see if it outputs rows in the requested ORDER BY ** (or GROUP BY) without requiring a separate sort operation. Return N: -** +** ** N>0: N terms of the ORDER BY clause are satisfied ** N==0: No terms of the ORDER BY clause are satisfied -** N<0: Unknown yet how many terms of ORDER BY might be satisfied. +** N<0: Unknown yet how many terms of ORDER BY might be satisfied. ** ** Note that processing for WHERE_GROUPBY and WHERE_DISTINCTBY is not as ** strict. With GROUP BY and DISTINCT the only requirement is that ** equivalent rows appear immediately adjacent to one another. GROUP BY ** and DISTINCT do not require rows to appear in any particular order as long ** as equivalent rows are grouped together. Thus for GROUP BY and DISTINCT -** the pOrderBy terms can be matched in any order. With ORDER BY, the +** the pOrderBy terms can be matched in any order. With ORDER BY, the ** pOrderBy terms must be matched in strict left-to-right order. */ static i8 wherePathSatisfiesOrderBy( @@ -143698,7 +174044,7 @@ static i8 wherePathSatisfiesOrderBy( ** row of the WhereLoop. Every one-row WhereLoop is automatically ** order-distinct. A WhereLoop that has no columns in the ORDER BY clause ** is not order-distinct. To be order-distinct is not quite the same as being - ** UNIQUE since a UNIQUE column or index can have multiple rows that + ** UNIQUE since a UNIQUE column or index can have multiple rows that ** are NULL and NULL values are equivalent for the purpose of order-distinct. ** To be order-distinct, the columns must be UNIQUE and NOT NULL. ** @@ -143718,7 +174064,9 @@ static i8 wherePathSatisfiesOrderBy( orderDistinctMask = 0; ready = 0; eqOpMask = WO_EQ | WO_IS | WO_ISNULL; - if( wctrlFlags & WHERE_ORDERBY_LIMIT ) eqOpMask |= WO_IN; + if( wctrlFlags & (WHERE_ORDERBY_LIMIT|WHERE_ORDERBY_MAX|WHERE_ORDERBY_MIN) ){ + eqOpMask |= WO_IN; + } for(iLoop=0; isOrderDistinct && obSat0 ) ready |= pLoop->maskSelf; if( iLoopwsFlags & WHERE_VIRTUALTABLE ){ - if( pLoop->u.vtab.isOrdered ) obSat = obDone; + if( pLoop->u.vtab.isOrdered && pWInfo->pOrderBy==pOrderBy ){ + obSat = obDone; + }else{ + /* No further ORDER BY terms may be matched. So this call should + ** return >=0, not -1. Clear isOrderDistinct to ensure it does so. */ + isOrderDistinct = 0; + } break; - }else{ - pLoop->u.btree.nIdxCol = 0; } iCur = pWInfo->pTabList->a[pLoop->iTab].iCursor; @@ -143742,23 +174094,28 @@ static i8 wherePathSatisfiesOrderBy( */ for(i=0; ia[i].pExpr); - if( pOBExpr->op!=TK_COLUMN ) continue; + pOBExpr = sqlite3ExprSkipCollateAndLikely(pOrderBy->a[i].pExpr); + if( NEVER(pOBExpr==0) ) continue; + if( pOBExpr->op!=TK_COLUMN && pOBExpr->op!=TK_AGG_COLUMN ) continue; if( pOBExpr->iTable!=iCur ) continue; pTerm = sqlite3WhereFindTerm(&pWInfo->sWC, iCur, pOBExpr->iColumn, ~ready, eqOpMask, 0); if( pTerm==0 ) continue; if( pTerm->eOperator==WO_IN ){ - /* IN terms are only valid for sorting in the ORDER BY LIMIT + /* IN terms are only valid for sorting in the ORDER BY LIMIT ** optimization, and then only if they are actually used ** by the query plan */ - assert( wctrlFlags & WHERE_ORDERBY_LIMIT ); + assert( wctrlFlags & + (WHERE_ORDERBY_LIMIT|WHERE_ORDERBY_MIN|WHERE_ORDERBY_MAX) ); for(j=0; jnLTerm && pTerm!=pLoop->aLTerm[j]; j++){} if( j>=pLoop->nLTerm ) continue; } if( (pTerm->eOperator&(WO_EQ|WO_IS))!=0 && pOBExpr->iColumn>=0 ){ - if( sqlite3ExprCollSeqMatch(pWInfo->pParse, - pOrderBy->a[i].pExpr, pTerm->pExpr)==0 ){ + Parse *pParse = pWInfo->pParse; + CollSeq *pColl1 = sqlite3ExprNNCollSeq(pParse, pOrderBy->a[i].pExpr); + CollSeq *pColl2 = sqlite3ExprCompareCollSeq(pParse, pTerm->pExpr); + assert( pColl1 ); + if( pColl2==0 || sqlite3StrICmp(pColl1->zName, pColl2->zName) ){ continue; } testcase( pTerm->pExpr->op==TK_IS ); @@ -143768,9 +174125,18 @@ static i8 wherePathSatisfiesOrderBy( if( (pLoop->wsFlags & WHERE_ONEROW)==0 ){ if( pLoop->wsFlags & WHERE_IPK ){ + if( pLoop->u.btree.pOrderBy + && OptimizationEnabled(db, SQLITE_OrderBySubq) + && wherePathMatchSubqueryOB(pWInfo,pLoop,iLoop,iCur, + pOrderBy,pRevMask, &obSat) + ){ + nColumn = 0; + isOrderDistinct = 0; + }else{ + nColumn = 1; + } pIndex = 0; nKeyCol = 0; - nColumn = 1; }else if( (pIndex = pLoop->u.btree.pIndex)==0 || pIndex->bUnordered ){ return 0; }else{ @@ -143779,7 +174145,12 @@ static i8 wherePathSatisfiesOrderBy( assert( nColumn==nKeyCol+1 || !HasRowid(pIndex->pTable) ); assert( pIndex->aiColumn[nColumn-1]==XN_ROWID || !HasRowid(pIndex->pTable)); - isOrderDistinct = IsUniqueIndex(pIndex); + /* All relevant terms of the index must also be non-NULL in order + ** for isOrderDistinct to be true. So the isOrderDistinct value + ** computed here might be a false positive. Corrections will be + ** made at tag-20210426-1 below */ + isOrderDistinct = IsUniqueIndex(pIndex) + && (pLoop->wsFlags & WHERE_SKIPSCAN)==0; } /* Loop through all columns of the index and deal with the ones @@ -143790,26 +174161,32 @@ static i8 wherePathSatisfiesOrderBy( for(j=0; j=pLoop->u.btree.nEq + assert( j>=pLoop->u.btree.nEq || (pLoop->aLTerm[j]==0)==(jnSkip) ); if( ju.btree.nEq && j>=pLoop->nSkip ){ u16 eOp = pLoop->aLTerm[j]->eOperator; /* Skip over == and IS and ISNULL terms. (Also skip IN terms when - ** doing WHERE_ORDERBY_LIMIT processing). + ** doing WHERE_ORDERBY_LIMIT processing). Except, IS and ISNULL + ** terms imply that the index is not UNIQUE NOT NULL in which case + ** the loop need to be marked as not order-distinct because it can + ** have repeated NULL rows. ** - ** If the current term is a column of an ((?,?) IN (SELECT...)) + ** If the current term is a column of an ((?,?) IN (SELECT...)) ** expression for which the SELECT returns more than one column, ** check that it is the only column used by this loop. Otherwise, ** if it is one of two or more, none of the columns can be - ** considered to match an ORDER BY term. */ + ** considered to match an ORDER BY term. + */ if( (eOp & eqOpMask)!=0 ){ - if( eOp & WO_ISNULL ){ + if( eOp & (WO_ISNULL|WO_IS) ){ + testcase( eOp & WO_ISNULL ); + testcase( eOp & WO_IS ); testcase( isOrderDistinct ); isOrderDistinct = 0; } - continue; + continue; }else if( ALWAYS(eOp & WO_IN) ){ /* ALWAYS() justification: eOp is an equality operator due to the ** ju.btree.nEq constraint above. Any equality other @@ -143831,7 +174208,7 @@ static i8 wherePathSatisfiesOrderBy( */ if( pIndex ){ iColumn = pIndex->aiColumn[j]; - revIdx = pIndex->aSortOrder[j]; + revIdx = pIndex->aSortOrder[j] & KEYINFO_ORDER_DESC; if( iColumn==pIndex->pTable->iPKey ) iColumn = XN_ROWID; }else{ iColumn = XN_ROWID; @@ -143839,33 +174216,38 @@ static i8 wherePathSatisfiesOrderBy( } /* An unconstrained column that might be NULL means that this - ** WhereLoop is not well-ordered + ** WhereLoop is not well-ordered. tag-20210426-1 */ - if( isOrderDistinct - && iColumn>=0 - && j>=pLoop->u.btree.nEq - && pIndex->pTable->aCol[iColumn].notNull==0 - ){ - isOrderDistinct = 0; + if( isOrderDistinct ){ + if( iColumn>=0 + && j>=pLoop->u.btree.nEq + && pIndex->pTable->aCol[iColumn].notNull==0 + ){ + isOrderDistinct = 0; + } + if( iColumn==XN_EXPR ){ + isOrderDistinct = 0; + } } /* Find the ORDER BY term that corresponds to the j-th column - ** of the index and mark that ORDER BY term off + ** of the index and mark that ORDER BY term having been satisfied. */ isMatch = 0; for(i=0; bOnce && ia[i].pExpr); + pOBExpr = sqlite3ExprSkipCollateAndLikely(pOrderBy->a[i].pExpr); testcase( wctrlFlags & WHERE_GROUPBY ); testcase( wctrlFlags & WHERE_DISTINCTBY ); + if( NEVER(pOBExpr==0) ) continue; if( (wctrlFlags & (WHERE_GROUPBY|WHERE_DISTINCTBY))==0 ) bOnce = 0; if( iColumn>=XN_ROWID ){ - if( pOBExpr->op!=TK_COLUMN ) continue; + if( pOBExpr->op!=TK_COLUMN && pOBExpr->op!=TK_AGG_COLUMN ) continue; if( pOBExpr->iTable!=iCur ) continue; if( pOBExpr->iColumn!=iColumn ) continue; }else{ - Expr *pIdxExpr = pIndex->aColExpr->a[j].pExpr; - if( sqlite3ExprCompareSkip(pOBExpr, pIdxExpr, iCur) ){ + Expr *pIxExpr = pIndex->aColExpr->a[j].pExpr; + if( sqlite3ExprCompareSkip(pOBExpr, pIxExpr, iCur) ){ continue; } } @@ -143873,7 +174255,9 @@ static i8 wherePathSatisfiesOrderBy( pColl = sqlite3ExprNNCollSeq(pWInfo->pParse, pOrderBy->a[i].pExpr); if( sqlite3StrICmp(pColl->zName, pIndex->azColl[j])!=0 ) continue; } - pLoop->u.btree.nIdxCol = j+1; + if( wctrlFlags & WHERE_DISTINCTBY ){ + pLoop->u.btree.nDistinctCol = j+1; + } isMatch = 1; break; } @@ -143881,13 +174265,24 @@ static i8 wherePathSatisfiesOrderBy( /* Make sure the sort order is compatible in an ORDER BY clause. ** Sort order is irrelevant for a GROUP BY clause. */ if( revSet ){ - if( (rev ^ revIdx)!=pOrderBy->a[i].sortOrder ) isMatch = 0; + if( (rev ^ revIdx) + != (pOrderBy->a[i].fg.sortFlags&KEYINFO_ORDER_DESC) + ){ + isMatch = 0; + } }else{ - rev = revIdx ^ pOrderBy->a[i].sortOrder; + rev = revIdx ^ (pOrderBy->a[i].fg.sortFlags & KEYINFO_ORDER_DESC); if( rev ) *pRevMask |= MASKBIT(iLoop); revSet = 1; } } + if( isMatch && (pOrderBy->a[i].fg.sortFlags & KEYINFO_ORDER_BIGNULL) ){ + if( j==pLoop->u.btree.nEq ){ + pLoop->wsFlags |= WHERE_BIGNULL_SORT; + }else{ + isMatch = 0; + } + } if( isMatch ){ if( iColumn==XN_ROWID ){ testcase( distinctColumns==0 ); @@ -143918,7 +174313,7 @@ static i8 wherePathSatisfiesOrderBy( if( MASKBIT(i) & obSat ) continue; p = pOrderBy->a[i].pExpr; mTerm = sqlite3WhereExprUsage(&pWInfo->sMaskSet,p); - if( mTerm==0 && !sqlite3ExprIsConstant(p) ) continue; + if( mTerm==0 && !sqlite3ExprIsConstant(0,p) ) continue; if( (mTerm&~orderDistinctMask)==0 ){ obSat |= MASKBIT(i); } @@ -143928,7 +174323,7 @@ static i8 wherePathSatisfiesOrderBy( if( obSat==obDone ) return (i8)nOrderBy; if( !isOrderDistinct ){ for(i=nOrderBy-1; i>0; i--){ - Bitmask m = MASKBIT(i) - 1; + Bitmask m = ALWAYS(iwctrlFlags & WHERE_GROUPBY ); + assert( pWInfo->wctrlFlags & (WHERE_GROUPBY|WHERE_DISTINCTBY) ); assert( pWInfo->wctrlFlags & WHERE_SORTBYGROUP ); return pWInfo->sorted; } @@ -143979,43 +174374,305 @@ static const char *wherePathName(WherePath *pPath, int nLoop, WhereLoop *pLast){ #endif /* -** Return the cost of sorting nRow rows, assuming that the keys have +** Return the cost of sorting nRow rows, assuming that the keys have ** nOrderby columns and that the first nSorted columns are already in ** order. */ static LogEst whereSortingCost( - WhereInfo *pWInfo, - LogEst nRow, - int nOrderBy, - int nSorted + WhereInfo *pWInfo, /* Query planning context */ + LogEst nRow, /* Estimated number of rows to sort */ + int nOrderBy, /* Number of ORDER BY clause terms */ + int nSorted /* Number of initial ORDER BY terms naturally in order */ ){ - /* TUNING: Estimated cost of a full external sort, where N is + /* Estimated cost of a full external sort, where N is ** the number of rows to sort is: ** - ** cost = (3.0 * N * log(N)). - ** - ** Or, if the order-by clause has X terms but only the last Y - ** terms are out of order, then block-sorting will reduce the + ** cost = (K * N * log(N)). + ** + ** Or, if the order-by clause has X terms but only the last Y + ** terms are out of order, then block-sorting will reduce the ** sorting cost to: ** - ** cost = (3.0 * N * log(N)) * (Y/X) + ** cost = (K * N * log(N)) * (Y/X) ** - ** The (Y/X) term is implemented using stack variable rScale - ** below. */ - LogEst rScale, rSortCost; - assert( nOrderBy>0 && 66==sqlite3LogEst(100) ); - rScale = sqlite3LogEst((nOrderBy-nSorted)*100/nOrderBy) - 66; - rSortCost = nRow + rScale + 16; + ** The constant K is at least 2.0 but will be larger if there are a + ** large number of columns to be sorted, as the sorting time is + ** proportional to the amount of content to be sorted. The algorithm + ** does not currently distinguish between fat columns (BLOBs and TEXTs) + ** and skinny columns (INTs). It just uses the number of columns as + ** an approximation for the row width. + ** + ** And extra factor of 2.0 or 3.0 is added to the sorting cost if the sort + ** is built using OP_IdxInsert and OP_Sort rather than with OP_SorterInsert. + */ + LogEst rSortCost, nCol; + assert( pWInfo->pSelect!=0 ); + assert( pWInfo->pSelect->pEList!=0 ); + /* TUNING: sorting cost proportional to the number of output columns: */ + nCol = sqlite3LogEst((pWInfo->pSelect->pEList->nExpr+59)/30); + rSortCost = nRow + nCol; + if( nSorted>0 ){ + /* Scale the result by (Y/X) */ + rSortCost += sqlite3LogEst((nOrderBy-nSorted)*100/nOrderBy) - 66; + } /* Multiple by log(M) where M is the number of output rows. - ** Use the LIMIT for M if it is smaller */ - if( (pWInfo->wctrlFlags & WHERE_USE_LIMIT)!=0 && pWInfo->iLimitiLimit; + ** Use the LIMIT for M if it is smaller. Or if this sort is for + ** a DISTINCT operator, M will be the number of distinct output + ** rows, so fudge it downwards a bit. + */ + if( (pWInfo->wctrlFlags & WHERE_USE_LIMIT)!=0 ){ + rSortCost += 10; /* TUNING: Extra 2.0x if using LIMIT */ + if( nSorted!=0 ){ + rSortCost += 6; /* TUNING: Extra 1.5x if also using partial sort */ + } + if( pWInfo->iLimitiLimit; + } + }else if( (pWInfo->wctrlFlags & WHERE_WANT_DISTINCT) ){ + /* TUNING: In the sort for a DISTINCT operator, assume that the DISTINCT + ** reduces the number of output rows by a factor of 2 */ + if( nRow>10 ){ nRow -= 10; assert( 10==sqlite3LogEst(2) ); } } rSortCost += estLog(nRow); return rSortCost; } +/* +** Compute the maximum number of paths in the solver algorithm, for +** queries that have three or more terms in the FROM clause. Queries with +** two or fewer FROM clause terms are handled by the caller. +** +** Query planning is NP-hard. We must limit the number of paths at +** each step of the solver search algorithm to avoid exponential behavior. +** +** The value returned is a tuning parameter. Currently the value is: +** +** 18 for star queries +** 12 otherwise +** +** For the purposes of this heuristic, a star-query is defined as a query +** with a central "fact" table that is joined against multiple +** "dimension" tables, subject to the following constraints: +** +** (aa) Only a five-way or larger join is considered for this +** optimization. If there are fewer than four terms in the FROM +** clause, this heuristic does not apply. +** +** (bb) The join between the fact table and the dimension tables must +** be an INNER join. CROSS and OUTER JOINs do not qualify. +** +** (cc) A table must have 3 or more dimension tables in order to be +** considered a fact table. (Was 4 prior to 2026-02-10.) +** +** (dd) A table that is a self-join cannot be a dimension table. +** Dimension tables are joined against fact tables. +** +** SIDE EFFECT: (and really the whole point of this subroutine) +** +** If pWInfo describes a star-query, then the cost for SCANs of dimension +** WhereLoops is increased to be slightly larger than the cost of a SCAN +** in the fact table. Only SCAN costs are increased. SEARCH costs are +** unchanged. This heuristic helps keep fact tables in outer loops. Without +** this heuristic, paths with fact tables in outer loops tend to get pruned +** by the mxChoice limit on the number of paths, resulting in poor query +** plans. See the starschema1.test test module for examples of queries +** that need this heuristic to find good query plans. +** +** This heuristic can be completely disabled, so that no query is +** considered a star-query, using SQLITE_TESTCTRL_OPTIMIZATION to +** disable the SQLITE_StarQuery optimization. In the CLI, the command +** to do that is: ".testctrl opt -starquery". +** +** HISTORICAL NOTES: +** +** This optimization was first added on 2024-05-09 by check-in 38db9b5c83d. +** The original optimization reduced the cost and output size estimate for +** fact tables to help them move to outer loops. But months later (as people +** started upgrading) performance regression reports started caming in, +** including: +** +** forum post b18ef983e68d06d1 (2024-12-21) +** forum post 0025389d0860af82 (2025-01-14) +** forum post d87570a145599033 (2025-01-17) +** +** To address these, the criteria for a star-query was tightened to exclude +** cases where the fact and dimensions are separated by an outer join, and +** the affect of star-schema detection was changed to increase the rRun cost +** on just full table scans of dimension tables, rather than reducing costs +** in the all access methods of the fact table. +*/ +static int computeMxChoice(WhereInfo *pWInfo){ + int nLoop = pWInfo->nLevel; /* Number of terms in the join */ + WhereLoop *pWLoop; /* For looping over WhereLoops */ + +#ifdef SQLITE_DEBUG + /* The star-query detection code below makes use of the following + ** properties of the WhereLoop list, so verify them before + ** continuing: + ** (1) .maskSelf is the bitmask corresponding to .iTab + ** (2) The WhereLoop list is in ascending .iTab order + */ + for(pWLoop=pWInfo->pLoops; pWLoop; pWLoop=pWLoop->pNextLoop){ + assert( pWLoop->maskSelf==MASKBIT(pWLoop->iTab) ); + assert( pWLoop->pNextLoop==0 || pWLoop->iTab<=pWLoop->pNextLoop->iTab ); + } +#endif /* SQLITE_DEBUG */ + + if( nLoop>=4 /* Constraint (aa) */ + && !pWInfo->bStarDone + && OptimizationEnabled(pWInfo->pParse->db, SQLITE_StarQuery) + ){ + SrcItem *aFromTabs; /* All terms of the FROM clause */ + int iFromIdx; /* Term of FROM clause is the candidate fact-table */ + Bitmask m; /* Bitmask for candidate fact-table */ + Bitmask mSelfJoin = 0; /* Tables that cannot be dimension tables */ + WhereLoop *pStart; /* Where to start searching for dimension-tables */ + + pWInfo->bStarDone = 1; /* Only do this computation once */ + + /* Look for fact tables with three or more dimensions where the + ** dimension tables are not separately from the fact tables by an outer + ** or cross join. Adjust cost weights if found. + */ + assert( !pWInfo->bStarUsed ); + aFromTabs = pWInfo->pTabList->a; + pStart = pWInfo->pLoops; + for(iFromIdx=0, m=1; iFromIdxfg.jointype & (JT_OUTER|JT_CROSS))!=0 ){ + /* If the candidate fact-table is the right table of an outer join + ** restrict the search for dimension-tables to be tables to the right + ** of the fact-table. Constraint (bb) */ + if( iFromIdx+3 > nLoop ){ + break; /* ^-- Impossible to reach nDep>=2 - Constraint (cc) */ + } + while( pStart && pStart->iTab<=iFromIdx ){ + pStart = pStart->pNextLoop; + } + } + for(pWLoop=pStart; pWLoop; pWLoop=pWLoop->pNextLoop){ + if( (aFromTabs[pWLoop->iTab].fg.jointype & (JT_OUTER|JT_CROSS))!=0 ){ + break; /* Constraint (bb) */ + } + if( (pWLoop->prereq & m)!=0 /* pWInfo depends on iFromIdx */ + && (pWLoop->maskSelf & mSeen)==0 /* pWInfo not already a dependency */ + && (pWLoop->maskSelf & mSelfJoin)==0 /* Not a self-join */ + ){ + if( aFromTabs[pWLoop->iTab].pSTab==pFactTab->pSTab ){ + mSelfJoin |= m; + }else{ + nDep++; + mSeen |= pWLoop->maskSelf; + } + } + } + if( nDep<=2 ){ + continue; /* Constraint (cc) */ + } + + /* If we reach this point, it means that pFactTab is a fact table + ** with four or more dimensions connected by inner joins. Proceed + ** to make cost adjustments. */ + +#ifdef WHERETRACE_ENABLED + /* Make sure rStarDelta values are initialized */ + if( !pWInfo->bStarUsed ){ + for(pWLoop=pWInfo->pLoops; pWLoop; pWLoop=pWLoop->pNextLoop){ + pWLoop->rStarDelta = 0; + } + } +#endif +#ifdef WHERETRACE_ENABLED /* 0x80000 */ + if( sqlite3WhereTrace & 0x80000 ){ + Bitmask mShow = mSeen; + sqlite3DebugPrintf("Fact table %s(%d), dimensions:", + pFactTab->zAlias ? pFactTab->zAlias : pFactTab->pSTab->zName, + iFromIdx); + for(pWLoop=pStart; pWLoop; pWLoop=pWLoop->pNextLoop){ + if( mShow & pWLoop->maskSelf ){ + SrcItem *pDim = aFromTabs + pWLoop->iTab; + mShow &= ~pWLoop->maskSelf; + sqlite3DebugPrintf(" %s(%d)", + pDim->zAlias ? pDim->zAlias: pDim->pSTab->zName, pWLoop->iTab); + } + } + sqlite3DebugPrintf("\n"); + } +#endif + pWInfo->bStarUsed = 1; + + /* Compute the maximum cost of any WhereLoop for the + ** fact table plus one epsilon */ + mxRun = LOGEST_MIN; + for(pWLoop=pStart; pWLoop; pWLoop=pWLoop->pNextLoop){ + if( pWLoop->iTabiTab>iFromIdx ) break; + if( pWLoop->rRun>mxRun ) mxRun = pWLoop->rRun; + } + if( ALWAYS(mxRunpNextLoop){ + if( (pWLoop->maskSelf & mSeen)==0 ) continue; + if( pWLoop->nLTerm ) continue; + if( pWLoop->rRuniTab; + sqlite3DebugPrintf( + "Increase SCAN cost of %s to %d\n", + pDim->zAlias ? pDim->zAlias: pDim->pSTab->zName, mxRun + ); + } + pWLoop->rStarDelta = mxRun - pWLoop->rRun; +#endif /* WHERETRACE_ENABLED */ + pWLoop->rRun = mxRun; + } + } + } +#ifdef WHERETRACE_ENABLED /* 0x80000 */ + if( (sqlite3WhereTrace & 0x80000)!=0 && pWInfo->bStarUsed ){ + sqlite3DebugPrintf("WhereLoops changed by star-query heuristic:\n"); + for(pWLoop=pWInfo->pLoops; pWLoop; pWLoop=pWLoop->pNextLoop){ + if( pWLoop->rStarDelta ){ + sqlite3WhereLoopPrint(pWLoop, &pWInfo->sWC); + } + } + } +#endif + } + return pWInfo->bStarUsed ? 18 : 12; +} + +/* +** Two WhereLoop objects, pCandidate and pBaseline, are known to have the +** same cost. Look deep into each to see if pCandidate is even slightly +** better than pBaseline. Return false if it is, if pCandidate is is preferred. +** Return true if pBaseline is preferred or if we cannot tell the difference. +** +** Result Meaning +** -------- ---------------------------------------------------------- +** true We cannot tell the difference in pCandidate and pBaseline +** false pCandidate seems like a better choice than pBaseline +*/ +static SQLITE_NOINLINE int whereLoopIsNoBetter( + const WhereLoop *pCandidate, + const WhereLoop *pBaseline +){ + if( (pCandidate->wsFlags & WHERE_INDEXED)==0 ) return 1; + if( (pBaseline->wsFlags & WHERE_INDEXED)==0 ) return 1; + if( pCandidate->u.btree.pIndex->szIdxRow < + pBaseline->u.btree.pIndex->szIdxRow ) return 0; + return 1; +} + /* ** Given the list of WhereLoop objects at pWInfo->pLoops, this routine ** attempts to find the lowest cost path that visits each WhereLoop @@ -144032,13 +174689,12 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ int mxChoice; /* Maximum number of simultaneous paths tracked */ int nLoop; /* Number of terms in the join */ Parse *pParse; /* Parsing context */ - sqlite3 *db; /* The database connection */ int iLoop; /* Loop counter over the terms of the join */ int ii, jj; /* Loop counters */ int mxI = 0; /* Index of next entry to replace */ int nOrderBy; /* Number of ORDER BY clause terms */ LogEst mxCost = 0; /* Maximum cost of a set of paths */ - LogEst mxUnsorted = 0; /* Maximum unsorted cost of a set of path */ + LogEst mxUnsort = 0; /* Maximum unsorted cost of a set of path */ int nTo, nFrom; /* Number of valid entries in aTo[] and aFrom[] */ WherePath *aFrom; /* All nFrom paths at the previous level */ WherePath *aTo; /* The nTo best paths at the current level */ @@ -144051,14 +174707,28 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ int nSpace; /* Bytes of space allocated at pSpace */ pParse = pWInfo->pParse; - db = pParse->db; nLoop = pWInfo->nLevel; - /* TUNING: For simple queries, only the best path is tracked. - ** For 2-way joins, the 5 best paths are followed. - ** For joins of 3 or more tables, track the 10 best paths */ - mxChoice = (nLoop<=1) ? 1 : (nLoop==2 ? 5 : 10); + WHERETRACE(0x002, ("---- begin solver. (nRowEst=%d, nQueryLoop=%d)\n", + nRowEst, pParse->nQueryLoop)); + /* TUNING: mxChoice is the maximum number of possible paths to preserve + ** at each step. Based on the number of loops in the FROM clause: + ** + ** nLoop mxChoice + ** ----- -------- + ** 1 1 // the most common case + ** 2 5 + ** 3+ 12 or 18 // see computeMxChoice() + */ + if( nLoop<=1 ){ + mxChoice = 1; + }else if( nLoop==2 ){ + mxChoice = 5; + }else if( pParse->nErr ){ + mxChoice = 1; + }else{ + mxChoice = computeMxChoice(pWInfo); + } assert( nLoop<=pWInfo->pTabList->nSrc ); - WHERETRACE(0x002, ("---- begin solver. (nRowEst=%d)\n", nRowEst)); /* If nRowEst is zero and there is an ORDER BY clause, ignore it. In this ** case the purpose of this call is to estimate the number of rows returned @@ -144074,7 +174744,7 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ /* Allocate and initialize space for aTo, aFrom and aSortCost[] */ nSpace = (sizeof(WherePath)+sizeof(WhereLoop*)*nLoop)*mxChoice*2; nSpace += sizeof(LogEst) * nOrderBy; - pSpace = sqlite3DbMallocRawNN(db, nSpace); + pSpace = sqlite3StackAllocRawNN(pParse->db, nSpace); if( pSpace==0 ) return SQLITE_NOMEM_BKPT; aTo = (WherePath*)pSpace; aFrom = aTo+mxChoice; @@ -144088,7 +174758,7 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ ** space for the aSortCost[] array. Each element of the aSortCost array ** is either zero - meaning it has not yet been initialized - or the ** cost of sorting nRowEst rows of data where the first X terms of - ** the ORDER BY clause are already in order, where X is the array + ** the ORDER BY clause are already in order, where X is the array ** index. */ aSortCost = (LogEst*)pX; memset(aSortCost, 0, sizeof(LogEst) * nOrderBy); @@ -144109,7 +174779,7 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ ** in this case the query may return a maximum of one row, the results ** are already in the requested order. Set isOrdered to nOrderBy to ** indicate this. Or, if nLoop is greater than zero, set isOrdered to - ** -1, indicating that the result set may or may not be ordered, + ** -1, indicating that the result set may or may not be ordered, ** depending on the loops added to the current plan. */ aFrom[0].isOrdered = nLoop>0 ? -1 : nOrderBy; } @@ -144123,10 +174793,10 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ for(pWLoop=pWInfo->pLoops; pWLoop; pWLoop=pWLoop->pNextLoop){ LogEst nOut; /* Rows visited by (pFrom+pWLoop) */ LogEst rCost; /* Cost of path (pFrom+pWLoop) */ - LogEst rUnsorted; /* Unsorted cost of (pFrom+pWLoop) */ - i8 isOrdered = pFrom->isOrdered; /* isOrdered for (pFrom+pWLoop) */ + LogEst rUnsort; /* Unsorted cost of (pFrom+pWLoop) */ + i8 isOrdered; /* isOrdered for (pFrom+pWLoop) */ Bitmask maskNew; /* Mask of src visited by (..) */ - Bitmask revMask = 0; /* Mask of rev-order loops for (..) */ + Bitmask revMask; /* Mask of rev-order loops for (..) */ if( (pWLoop->prereq & ~pFrom->maskLoop)!=0 ) continue; if( (pWLoop->maskSelf & pFrom->maskLoop)!=0 ) continue; @@ -144139,13 +174809,18 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ continue; } - /* At this point, pWLoop is a candidate to be the next loop. + /* At this point, pWLoop is a candidate to be the next loop. ** Compute its cost */ - rUnsorted = sqlite3LogEstAdd(pWLoop->rSetup,pWLoop->rRun + pFrom->nRow); - rUnsorted = sqlite3LogEstAdd(rUnsorted, pFrom->rUnsorted); + rUnsort = pWLoop->rRun + pFrom->nRow; + if( pWLoop->rSetup ){ + rUnsort = sqlite3LogEstAdd(pWLoop->rSetup, rUnsort); + } + rUnsort = sqlite3LogEstAdd(rUnsort, pFrom->rUnsort); nOut = pFrom->nRow + pWLoop->nOut; maskNew = pFrom->maskLoop | pWLoop->maskSelf; + isOrdered = pFrom->isOrdered; if( isOrdered<0 ){ + revMask = 0; isOrdered = wherePathSatisfiesOrderBy(pWInfo, pWInfo->pOrderBy, pFrom, pWInfo->wctrlFlags, iLoop, pWLoop, &revMask); @@ -144158,35 +174833,43 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ pWInfo, nRowEst, nOrderBy, isOrdered ); } - /* TUNING: Add a small extra penalty (5) to sorting as an - ** extra encouragment to the query planner to select a plan + /* TUNING: Add a small extra penalty (3) to sorting as an + ** extra encouragement to the query planner to select a plan ** where the rows emerge in the correct order without any sorting ** required. */ - rCost = sqlite3LogEstAdd(rUnsorted, aSortCost[isOrdered]) + 5; + rCost = sqlite3LogEstAdd(rUnsort, aSortCost[isOrdered]) + 3; WHERETRACE(0x002, ("---- sort cost=%-3d (%d/%d) increases cost %3d to %-3d\n", - aSortCost[isOrdered], (nOrderBy-isOrdered), nOrderBy, - rUnsorted, rCost)); + aSortCost[isOrdered], (nOrderBy-isOrdered), nOrderBy, + rUnsort, rCost)); }else{ - rCost = rUnsorted; - rUnsorted -= 2; /* TUNING: Slight bias in favor of no-sort plans */ + rCost = rUnsort; + rUnsort -= 2; /* TUNING: Slight bias in favor of no-sort plans */ } /* Check to see if pWLoop should be added to the set of ** mxChoice best-so-far paths. ** ** First look for an existing path among best-so-far paths - ** that covers the same set of loops and has the same isOrdered - ** setting as the current path candidate. + ** that: + ** (1) covers the same set of loops, and + ** (2) has a compatible isOrdered value. + ** + ** "Compatible isOrdered value" means either + ** (A) both have isOrdered==-1, or + ** (B) both have isOrder>=0, or + ** (C) ordering does not matter because this is the last round + ** of the solver. ** ** The term "((pTo->isOrdered^isOrdered)&0x80)==0" is equivalent ** to (pTo->isOrdered==(-1))==(isOrdered==(-1))" for the range ** of legal values for isOrdered, -1..64. */ + testcase( nTo==0 ); for(jj=0, pTo=aTo; jjmaskLoop==maskNew - && ((pTo->isOrdered^isOrdered)&0x80)==0 + && ( ((pTo->isOrdered^isOrdered)&0x80)==0 || iLoop==nLoop-1 ) ){ testcase( jj==nTo-1 ); break; @@ -144195,7 +174878,7 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ if( jj>=nTo ){ /* None of the existing best-so-far paths match the candidate. */ if( nTo>=mxChoice - && (rCost>mxCost || (rCost==mxCost && rUnsorted>=mxUnsorted)) + && (rCost>mxCost || (rCost==mxCost && rUnsort>=mxUnsort)) ){ /* The current candidate is no better than any of the mxChoice ** paths currently in the best-so-far buffer. So discard @@ -144203,7 +174886,7 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ #ifdef WHERETRACE_ENABLED /* 0x4 */ if( sqlite3WhereTrace&0x4 ){ sqlite3DebugPrintf("Skip %s cost=%-3d,%3d,%3d order=%c\n", - wherePathName(pFrom, iLoop, pWLoop), rCost, nOut, rUnsorted, + wherePathName(pFrom, iLoop, pWLoop), rCost, nOut, rUnsort, isOrdered>=0 ? isOrdered+'0' : '?'); } #endif @@ -144222,7 +174905,7 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ #ifdef WHERETRACE_ENABLED /* 0x4 */ if( sqlite3WhereTrace&0x4 ){ sqlite3DebugPrintf("New %s cost=%-3d,%3d,%3d order=%c\n", - wherePathName(pFrom, iLoop, pWLoop), rCost, nOut, rUnsorted, + wherePathName(pFrom, iLoop, pWLoop), rCost, nOut, rUnsort, isOrdered>=0 ? isOrdered+'0' : '?'); } #endif @@ -144231,26 +174914,25 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ ** same set of loops and has the same isOrdered setting as the ** candidate path. Check to see if the candidate should replace ** pTo or if the candidate should be skipped. - ** + ** ** The conditional is an expanded vector comparison equivalent to: - ** (pTo->rCost,pTo->nRow,pTo->rUnsorted) <= (rCost,nOut,rUnsorted) + ** (pTo->rCost,pTo->nRow,pTo->rUnsort) <= (rCost,nOut,rUnsort) */ - if( pTo->rCostrCost==rCost - && (pTo->nRownRow==nOut && pTo->rUnsorted<=rUnsorted) - ) - ) + if( (pTo->rCostrCost==rCost && pTo->nRowrCost==rCost && pTo->nRow==nOut && pTo->rUnsortrCost==rCost && pTo->nRow==nOut && pTo->rUnsort==rUnsort + && whereLoopIsNoBetter(pWLoop, pTo->aLoop[iLoop]) ) ){ #ifdef WHERETRACE_ENABLED /* 0x4 */ if( sqlite3WhereTrace&0x4 ){ sqlite3DebugPrintf( "Skip %s cost=%-3d,%3d,%3d order=%c", - wherePathName(pFrom, iLoop, pWLoop), rCost, nOut, rUnsorted, + wherePathName(pFrom, iLoop, pWLoop), rCost, nOut, rUnsort, isOrdered>=0 ? isOrdered+'0' : '?'); sqlite3DebugPrintf(" vs %s cost=%-3d,%3d,%3d order=%c\n", wherePathName(pTo, iLoop+1, 0), pTo->rCost, pTo->nRow, - pTo->rUnsorted, pTo->isOrdered>=0 ? pTo->isOrdered+'0' : '?'); + pTo->rUnsort, pTo->isOrdered>=0 ? pTo->isOrdered+'0' : '?'); } #endif /* Discard the candidate path from further consideration */ @@ -144264,11 +174946,11 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ if( sqlite3WhereTrace&0x4 ){ sqlite3DebugPrintf( "Update %s cost=%-3d,%3d,%3d order=%c", - wherePathName(pFrom, iLoop, pWLoop), rCost, nOut, rUnsorted, + wherePathName(pFrom, iLoop, pWLoop), rCost, nOut, rUnsort, isOrdered>=0 ? isOrdered+'0' : '?'); sqlite3DebugPrintf(" was %s cost=%-3d,%3d,%3d order=%c\n", wherePathName(pTo, iLoop+1, 0), pTo->rCost, pTo->nRow, - pTo->rUnsorted, pTo->isOrdered>=0 ? pTo->isOrdered+'0' : '?'); + pTo->rUnsort, pTo->isOrdered>=0 ? pTo->isOrdered+'0' : '?'); } #endif } @@ -144277,20 +174959,20 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ pTo->revLoop = revMask; pTo->nRow = nOut; pTo->rCost = rCost; - pTo->rUnsorted = rUnsorted; + pTo->rUnsort = rUnsort; pTo->isOrdered = isOrdered; memcpy(pTo->aLoop, pFrom->aLoop, sizeof(WhereLoop*)*iLoop); pTo->aLoop[iLoop] = pWLoop; if( nTo>=mxChoice ){ mxI = 0; mxCost = aTo[0].rCost; - mxUnsorted = aTo[0].nRow; + mxUnsort = aTo[0].nRow; for(jj=1, pTo=&aTo[1]; jjrCost>mxCost - || (pTo->rCost==mxCost && pTo->rUnsorted>mxUnsorted) + if( pTo->rCost>mxCost + || (pTo->rCost==mxCost && pTo->rUnsort>mxUnsort) ){ mxCost = pTo->rCost; - mxUnsorted = pTo->rUnsorted; + mxUnsort = pTo->rUnsort; mxI = jj; } } @@ -144300,17 +174982,32 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ #ifdef WHERETRACE_ENABLED /* >=2 */ if( sqlite3WhereTrace & 0x02 ){ + LogEst rMin, rFloor = 0; + int nDone = 0; + int nProgress; sqlite3DebugPrintf("---- after round %d ----\n", iLoop); - for(ii=0, pTo=aTo; iirCost, pTo->nRow, - pTo->isOrdered>=0 ? (pTo->isOrdered+'0') : '?'); - if( pTo->isOrdered>0 ){ - sqlite3DebugPrintf(" rev=0x%llx\n", pTo->revLoop); - }else{ - sqlite3DebugPrintf("\n"); + do{ + nProgress = 0; + rMin = 0x7fff; + for(ii=0, pTo=aTo; iirCost>rFloor && pTo->rCostrCost; } - } + for(ii=0, pTo=aTo; iirCost==rMin ){ + sqlite3DebugPrintf(" %s cost=%-3d nrow=%-3d order=%c", + wherePathName(pTo, iLoop+1, 0), pTo->rCost, pTo->nRow, + pTo->isOrdered>=0 ? (pTo->isOrdered+'0') : '?'); + if( pTo->isOrdered>0 ){ + sqlite3DebugPrintf(" rev=0x%llx\n", pTo->revLoop); + }else{ + sqlite3DebugPrintf("\n"); + } + nDone++; + nProgress++; + } + } + rFloor = rMin; + }while( nDone0 ); } #endif @@ -144323,15 +175020,14 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ if( nFrom==0 ){ sqlite3ErrorMsg(pParse, "no query solution"); - sqlite3DbFreeNN(db, pSpace); + sqlite3StackFreeNN(pParse->db, pSpace); return SQLITE_ERROR; } - - /* Find the lowest cost path. pFrom will be left pointing to that path */ + + /* Only one path is available, which is the best path */ + assert( nFrom==1 ); pFrom = aFrom; - for(ii=1; iirCost>aFrom[ii].rCost ) pFrom = &aFrom[ii]; - } + assert( pWInfo->nLevel==nLoop ); /* Load the lowest cost path into pWInfo */ for(iLoop=0; iLoopbOrderedInnerLoop = 0; if( pWInfo->pOrderBy ){ + pWInfo->nOBSat = pFrom->isOrdered; if( pWInfo->wctrlFlags & WHERE_DISTINCTBY ){ if( pFrom->isOrdered==pWInfo->pOrderBy->nExpr ){ pWInfo->eDistinct = WHERE_DISTINCT_ORDERED; } + /* vvv--- See check-in [12ad822d9b827777] on 2023-03-16 ---vvv */ + assert( pWInfo->pSelect->pOrderBy==0 + || pWInfo->nOBSat <= pWInfo->pSelect->pOrderBy->nExpr ); }else{ - pWInfo->nOBSat = pFrom->isOrdered; pWInfo->revMask = pFrom->revLoop; if( pWInfo->nOBSat<=0 ){ pWInfo->nOBSat = 0; if( nLoop>0 ){ u32 wsFlags = pFrom->aLoop[nLoop-1]->wsFlags; - if( (wsFlags & WHERE_ONEROW)==0 + if( (wsFlags & WHERE_ONEROW)==0 && (wsFlags&(WHERE_IPK|WHERE_COLUMN_IN))!=(WHERE_IPK|WHERE_COLUMN_IN) ){ Bitmask m = 0; @@ -144379,13 +175078,18 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ } } } + }else if( nLoop + && pWInfo->nOBSat==1 + && (pWInfo->wctrlFlags & (WHERE_ORDERBY_MIN|WHERE_ORDERBY_MAX))!=0 + ){ + pWInfo->bOrderedInnerLoop = 1; } } if( (pWInfo->wctrlFlags & WHERE_SORTBYGROUP) && pWInfo->nOBSat==pWInfo->pOrderBy->nExpr && nLoop>0 ){ Bitmask revMask = 0; - int nOrder = wherePathSatisfiesOrderBy(pWInfo, pWInfo->pOrderBy, + int nOrder = wherePathSatisfiesOrderBy(pWInfo, pWInfo->pOrderBy, pFrom, 0, nLoop-1, pFrom->aLoop[nLoop-1], &revMask ); assert( pWInfo->sorted==0 ); @@ -144396,14 +175100,96 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ } } - pWInfo->nRowOut = pFrom->nRow; +#ifdef WHERETRACE_ENABLED + pWInfo->rTotalCost = pFrom->rCost; +#endif /* Free temporary memory and return success */ - sqlite3DbFreeNN(db, pSpace); + sqlite3StackFreeNN(pParse->db, pSpace); return SQLITE_OK; } +/* +** This routine implements a heuristic designed to improve query planning. +** This routine is called in between the first and second call to +** wherePathSolver(). Hence the name "Interstage" "Heuristic". +** +** The first call to wherePathSolver() (hereafter just "solver()") computes +** the best path without regard to the order of the outputs. The second call +** to the solver() builds upon the first call to try to find an alternative +** path that satisfies the ORDER BY clause. +** +** This routine looks at the results of the first solver() run, and for +** every FROM clause term in the resulting query plan that uses an equality +** constraint against an index, disable other WhereLoops for that same +** FROM clause term that would try to do a full-table scan. This prevents +** an index search from being converted into a full-table scan in order to +** satisfy an ORDER BY clause, since even though we might get slightly better +** performance using the full-scan without sorting if the output size +** estimates are very precise, we might also get severe performance +** degradation using the full-scan if the output size estimate is too large. +** It is better to err on the side of caution. +** +** Except, if the first solver() call generated a full-table scan in an outer +** loop then stop this analysis at the first full-scan, since the second +** solver() run might try to swap that full-scan for another in order to +** get the output into the correct order. In other words, we allow a +** rewrite like this: +** +** First Solver() Second Solver() +** |-- SCAN t1 |-- SCAN t2 +** |-- SEARCH t2 `-- SEARCH t1 +** `-- SORT USING B-TREE +** +** The purpose of this routine is to disallow rewrites such as: +** +** First Solver() Second Solver() +** |-- SEARCH t1 |-- SCAN t2 <--- bad! +** |-- SEARCH t2 `-- SEARCH t1 +** `-- SORT USING B-TREE +** +** See test cases in test/whereN.test for the real-world query that +** originally provoked this heuristic. +*/ +static SQLITE_NOINLINE void whereInterstageHeuristic(WhereInfo *pWInfo){ + int i; +#ifdef WHERETRACE_ENABLED + int once = 0; +#endif + for(i=0; inLevel; i++){ + WhereLoop *p = pWInfo->a[i].pWLoop; + if( p==0 ) break; + if( (p->wsFlags & WHERE_VIRTUALTABLE)!=0 ){ + /* Treat a vtab scan as similar to a full-table scan */ + break; + } + if( (p->wsFlags & (WHERE_COLUMN_EQ|WHERE_COLUMN_NULL|WHERE_COLUMN_IN))!=0 ){ + u8 iTab = p->iTab; + WhereLoop *pLoop; + for(pLoop=pWInfo->pLoops; pLoop; pLoop=pLoop->pNextLoop){ + if( pLoop->iTab!=iTab ) continue; + if( (pLoop->wsFlags & (WHERE_CONSTRAINT|WHERE_AUTO_INDEX))!=0 ){ + /* Auto-index and index-constrained loops allowed to remain */ + continue; + } +#ifdef WHERETRACE_ENABLED + if( sqlite3WhereTrace & 0x80 ){ + if( once==0 ){ + sqlite3DebugPrintf("Loops disabled by interstage heuristic:\n"); + once = 1; + } + sqlite3WhereLoopPrint(pLoop, &pWInfo->sWC); + } +#endif /* WHERETRACE_ENABLED */ + pLoop->prereq = ALLBITS; /* Prevent 2nd solver() from using this one */ + } + }else{ + break; + } + } +} + /* ** Most queries use only a single table (they are not joins) and have ** simple == constraints against indexed fields. This routine attempts @@ -144412,12 +175198,12 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ ** times for the common case. ** ** Return non-zero on success, if this query can be handled by this -** no-frills query planner. Return zero if this query needs the +** no-frills query planner. Return zero if this query needs the ** general-purpose query planner. */ static int whereShortCut(WhereLoopBuilder *pBuilder){ WhereInfo *pWInfo; - struct SrcList_item *pItem; + SrcItem *pItem; WhereClause *pWC; WhereTerm *pTerm; WhereLoop *pLoop; @@ -144425,20 +175211,26 @@ static int whereShortCut(WhereLoopBuilder *pBuilder){ int j; Table *pTab; Index *pIdx; + WhereScan scan; pWInfo = pBuilder->pWInfo; if( pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE ) return 0; assert( pWInfo->pTabList->nSrc>=1 ); pItem = pWInfo->pTabList->a; - pTab = pItem->pTab; + pTab = pItem->pSTab; if( IsVirtual(pTab) ) return 0; - if( pItem->fg.isIndexedBy ) return 0; + if( pItem->fg.isIndexedBy || pItem->fg.notIndexed ){ + testcase( pItem->fg.isIndexedBy ); + testcase( pItem->fg.notIndexed ); + return 0; + } iCur = pItem->iCursor; pWC = &pWInfo->sWC; pLoop = pBuilder->pNew; pLoop->wsFlags = 0; pLoop->nSkip = 0; - pTerm = sqlite3WhereFindTerm(pWC, iCur, -1, 0, WO_EQ|WO_IS, 0); + pTerm = whereScanInit(&scan, pWC, iCur, -1, WO_EQ|WO_IS, 0); + while( pTerm && pTerm->prereqRight ) pTerm = whereScanNext(&scan); if( pTerm ){ testcase( pTerm->eOperator & WO_IS ); pLoop->wsFlags = WHERE_COLUMN_EQ|WHERE_IPK|WHERE_ONEROW; @@ -144452,12 +175244,13 @@ static int whereShortCut(WhereLoopBuilder *pBuilder){ int opMask; assert( pLoop->aLTermSpace==pLoop->aLTerm ); if( !IsUniqueIndex(pIdx) - || pIdx->pPartIdxWhere!=0 - || pIdx->nKeyCol>ArraySize(pLoop->aLTermSpace) + || pIdx->pPartIdxWhere!=0 + || pIdx->nKeyCol>ArraySize(pLoop->aLTermSpace) ) continue; opMask = pIdx->uniqNotNull ? (WO_EQ|WO_IS) : WO_EQ; for(j=0; jnKeyCol; j++){ - pTerm = sqlite3WhereFindTerm(pWC, iCur, j, 0, opMask, pIdx); + pTerm = whereScanInit(&scan, pWC, iCur, j, opMask, pIdx); + while( pTerm && pTerm->prereqRight ) pTerm = whereScanNext(&scan); if( pTerm==0 ) break; testcase( pTerm->eOperator & WO_IS ); pLoop->aLTerm[j] = pTerm; @@ -144486,8 +175279,14 @@ static int whereShortCut(WhereLoopBuilder *pBuilder){ if( pWInfo->wctrlFlags & WHERE_WANT_DISTINCT ){ pWInfo->eDistinct = WHERE_DISTINCT_UNIQUE; } + if( scan.iEquiv>1 ) pLoop->wsFlags |= WHERE_TRANSCONS; #ifdef SQLITE_DEBUG pLoop->cId = '0'; +#endif +#ifdef WHERETRACE_ENABLED + if( sqlite3WhereTrace & 0x02 ){ + sqlite3DebugPrintf("whereShortCut() used to compute solution\n"); + } #endif return 1; } @@ -144506,8 +175305,8 @@ static int exprNodeIsDeterministic(Walker *pWalker, Expr *pExpr){ } /* -** Return true if the expression contains no non-deterministic SQL -** functions. Do not consider non-deterministic SQL functions that are +** Return true if the expression contains no non-deterministic SQL +** functions. Do not consider non-deterministic SQL functions that are ** part of sub-select statements. */ static int exprIsDeterministic(Expr *p){ @@ -144520,6 +175319,278 @@ static int exprIsDeterministic(Expr *p){ return w.eCode; } + +#ifdef WHERETRACE_ENABLED +/* +** Display all WhereLoops in pWInfo +*/ +static void showAllWhereLoops(WhereInfo *pWInfo, WhereClause *pWC){ + if( sqlite3WhereTrace ){ /* Display all of the WhereLoop objects */ + WhereLoop *p; + int i; + static const char zLabel[] = "0123456789abcdefghijklmnopqrstuvwyxz" + "ABCDEFGHIJKLMNOPQRSTUVWYXZ"; + for(p=pWInfo->pLoops, i=0; p; p=p->pNextLoop, i++){ + p->cId = zLabel[i%(sizeof(zLabel)-1)]; + sqlite3WhereLoopPrint(p, pWC); + } + } +} +# define WHERETRACE_ALL_LOOPS(W,C) showAllWhereLoops(W,C) +#else +# define WHERETRACE_ALL_LOOPS(W,C) +#endif + +/* Attempt to omit tables from a join that do not affect the result. +** For a table to not affect the result, the following must be true: +** +** 1) The query must not be an aggregate. +** 2) The table must be the RHS of a LEFT JOIN. +** 3) Either the query must be DISTINCT, or else the ON or USING clause +** must contain a constraint that limits the scan of the table to +** at most a single row. +** 4) The table must not be referenced by any part of the query apart +** from its own USING or ON clause. +** 5) The table must not have an inner-join ON or USING clause if there is +** a RIGHT JOIN anywhere in the query. Otherwise the ON/USING clause +** might move from the right side to the left side of the RIGHT JOIN. +** Note: Due to (2), this condition can only arise if the table is +** the right-most table of a subquery that was flattened into the +** main query and that subquery was the right-hand operand of an +** inner join that held an ON or USING clause. +** 6) The ORDER BY clause has 63 or fewer terms +** 7) The omit-noop-join optimization is enabled. +** +** Items (1), (6), and (7) are checked by the caller. +** +** For example, given: +** +** CREATE TABLE t1(ipk INTEGER PRIMARY KEY, v1); +** CREATE TABLE t2(ipk INTEGER PRIMARY KEY, v2); +** CREATE TABLE t3(ipk INTEGER PRIMARY KEY, v3); +** +** then table t2 can be omitted from the following: +** +** SELECT v1, v3 FROM t1 +** LEFT JOIN t2 ON (t1.ipk=t2.ipk) +** LEFT JOIN t3 ON (t1.ipk=t3.ipk) +** +** or from: +** +** SELECT DISTINCT v1, v3 FROM t1 +** LEFT JOIN t2 +** LEFT JOIN t3 ON (t1.ipk=t3.ipk) +*/ +static SQLITE_NOINLINE Bitmask whereOmitNoopJoin( + WhereInfo *pWInfo, + Bitmask notReady +){ + int i; + Bitmask tabUsed; + int hasRightJoin; + + /* Preconditions checked by the caller */ + assert( pWInfo->nLevel>=2 ); + assert( OptimizationEnabled(pWInfo->pParse->db, SQLITE_OmitNoopJoin) ); + + /* These two preconditions checked by the caller combine to guarantee + ** condition (1) of the header comment */ + assert( pWInfo->pResultSet!=0 ); + assert( 0==(pWInfo->wctrlFlags & WHERE_AGG_DISTINCT) ); + + tabUsed = sqlite3WhereExprListUsage(&pWInfo->sMaskSet, pWInfo->pResultSet); + if( pWInfo->pOrderBy ){ + tabUsed |= sqlite3WhereExprListUsage(&pWInfo->sMaskSet, pWInfo->pOrderBy); + } + hasRightJoin = (pWInfo->pTabList->a[0].fg.jointype & JT_LTORJ)!=0; + for(i=pWInfo->nLevel-1; i>=1; i--){ + WhereTerm *pTerm, *pEnd; + SrcItem *pItem; + WhereLoop *pLoop; + Bitmask m1; + pLoop = pWInfo->a[i].pWLoop; + pItem = &pWInfo->pTabList->a[pLoop->iTab]; + if( (pItem->fg.jointype & (JT_LEFT|JT_RIGHT))!=JT_LEFT ) continue; + if( (pWInfo->wctrlFlags & WHERE_WANT_DISTINCT)==0 + && (pLoop->wsFlags & WHERE_ONEROW)==0 + ){ + continue; + } + if( (tabUsed & pLoop->maskSelf)!=0 ) continue; + pEnd = pWInfo->sWC.a + pWInfo->sWC.nTerm; + for(pTerm=pWInfo->sWC.a; pTermprereqAll & pLoop->maskSelf)!=0 ){ + if( !ExprHasProperty(pTerm->pExpr, EP_OuterON) + || pTerm->pExpr->w.iJoin!=pItem->iCursor + ){ + break; + } + } + if( hasRightJoin + && ExprHasProperty(pTerm->pExpr, EP_InnerON) + && NEVER(pTerm->pExpr->w.iJoin==pItem->iCursor) + ){ + break; /* restriction (5) */ + } + } + if( pTerm omit unused FROM-clause term %c\n",pLoop->cId)); + m1 = MASKBIT(i)-1; + testcase( ((pWInfo->revMask>>1) & ~m1)!=0 ); + pWInfo->revMask = (m1 & pWInfo->revMask) | ((pWInfo->revMask>>1) & ~m1); + notReady &= ~pLoop->maskSelf; + for(pTerm=pWInfo->sWC.a; pTermprereqAll & pLoop->maskSelf)!=0 ){ + pTerm->wtFlags |= TERM_CODED; + pTerm->prereqAll = 0; + } + } + if( i!=pWInfo->nLevel-1 ){ + int nByte = (pWInfo->nLevel-1-i) * sizeof(WhereLevel); + memmove(&pWInfo->a[i], &pWInfo->a[i+1], nByte); + } + pWInfo->nLevel--; + assert( pWInfo->nLevel>0 ); + } + return notReady; +} + +/* +** Check to see if there are any SEARCH loops that might benefit from +** using a Bloom filter. Consider a Bloom filter if: +** +** (1) The SEARCH happens more than N times where N is the number +** of rows in the table that is being considered for the Bloom +** filter. +** (2) Some searches are expected to find zero rows. (This is determined +** by the WHERE_SELFCULL flag on the term.) +** (3) Bloom-filter processing is not disabled. (Checked by the +** caller.) +** (4) The size of the table being searched is known by ANALYZE. +** +** This block of code merely checks to see if a Bloom filter would be +** appropriate, and if so sets the WHERE_BLOOMFILTER flag on the +** WhereLoop. The implementation of the Bloom filter comes further +** down where the code for each WhereLoop is generated. +*/ +static SQLITE_NOINLINE void whereCheckIfBloomFilterIsUseful( + const WhereInfo *pWInfo +){ + int i; + LogEst nSearch = 0; + + assert( pWInfo->nLevel>=2 ); + assert( OptimizationEnabled(pWInfo->pParse->db, SQLITE_BloomFilter) ); + for(i=0; inLevel; i++){ + WhereLoop *pLoop = pWInfo->a[i].pWLoop; + const unsigned int reqFlags = (WHERE_SELFCULL|WHERE_COLUMN_EQ); + SrcItem *pItem = &pWInfo->pTabList->a[pLoop->iTab]; + Table *pTab = pItem->pSTab; + if( (pTab->tabFlags & TF_HasStat1)==0 ) break; + pTab->tabFlags |= TF_MaybeReanalyze; + if( i>=1 + && (pLoop->wsFlags & reqFlags)==reqFlags + /* vvvvvv--- Always the case if WHERE_COLUMN_EQ is defined */ + && ALWAYS((pLoop->wsFlags & (WHERE_IPK|WHERE_INDEXED))!=0) + ){ + if( nSearch > pTab->nRowLogEst ){ + testcase( pItem->fg.jointype & JT_LEFT ); + pLoop->wsFlags |= WHERE_BLOOMFILTER; + pLoop->wsFlags &= ~WHERE_IDX_ONLY; + WHERETRACE(0xffffffff, ( + "-> use Bloom-filter on loop %c because there are ~%.1e " + "lookups into %s which has only ~%.1e rows\n", + pLoop->cId, (double)sqlite3LogEstToInt(nSearch), pTab->zName, + (double)sqlite3LogEstToInt(pTab->nRowLogEst))); + } + } + nSearch += pLoop->nOut; + } +} + +/* +** The index pIdx is used by a query and contains one or more expressions. +** In other words pIdx is an index on an expression. iIdxCur is the cursor +** number for the index and iDataCur is the cursor number for the corresponding +** table. +** +** This routine adds IndexedExpr entries to the Parse->pIdxEpr field for +** each of the expressions in the index so that the expression code generator +** will know to replace occurrences of the indexed expression with +** references to the corresponding column of the index. +*/ +static SQLITE_NOINLINE void whereAddIndexedExpr( + Parse *pParse, /* Add IndexedExpr entries to pParse->pIdxEpr */ + Index *pIdx, /* The index-on-expression that contains the expressions */ + int iIdxCur, /* Cursor number for pIdx */ + SrcItem *pTabItem /* The FROM clause entry for the table */ +){ + int i; + IndexedExpr *p; + Table *pTab; + assert( pIdx->bHasExpr ); + pTab = pIdx->pTable; + for(i=0; inColumn; i++){ + Expr *pExpr; + int j = pIdx->aiColumn[i]; + if( j==XN_EXPR ){ + pExpr = pIdx->aColExpr->a[i].pExpr; + }else if( j>=0 && (pTab->aCol[j].colFlags & COLFLAG_VIRTUAL)!=0 ){ + pExpr = sqlite3ColumnExpr(pTab, &pTab->aCol[j]); + }else{ + continue; + } + if( sqlite3ExprIsConstant(0,pExpr) ) continue; + p = sqlite3DbMallocRaw(pParse->db, sizeof(IndexedExpr)); + if( p==0 ) break; + p->pIENext = pParse->pIdxEpr; +#ifdef WHERETRACE_ENABLED + if( sqlite3WhereTrace & 0x200 ){ + sqlite3DebugPrintf("New pParse->pIdxEpr term {%d,%d}\n", iIdxCur, i); + if( sqlite3WhereTrace & 0x5000 ) sqlite3ShowExpr(pExpr); + } +#endif + p->pExpr = sqlite3ExprDup(pParse->db, pExpr, 0); + p->iDataCur = pTabItem->iCursor; + p->iIdxCur = iIdxCur; + p->iIdxCol = i; + p->bMaybeNullRow = (pTabItem->fg.jointype & (JT_LEFT|JT_LTORJ|JT_RIGHT))!=0; + if( sqlite3IndexAffinityStr(pParse->db, pIdx) ){ + p->aff = pIdx->zColAff[i]; + } +#ifdef SQLITE_ENABLE_EXPLAIN_COMMENTS + p->zIdxName = pIdx->zName; +#endif + pParse->pIdxEpr = p; + if( p->pIENext==0 ){ + void *pArg = (void*)&pParse->pIdxEpr; + sqlite3ParserAddCleanup(pParse, whereIndexedExprCleanup, pArg); + } + } +} + +/* +** Set the reverse-scan order mask to one for all tables in the query +** with the exception of MATERIALIZED common table expressions that have +** their own internal ORDER BY clauses. +** +** This implements the PRAGMA reverse_unordered_selects=ON setting. +** (Also SQLITE_DBCONFIG_REVERSE_SCANORDER). +*/ +static SQLITE_NOINLINE void whereReverseScanOrder(WhereInfo *pWInfo){ + int ii; + for(ii=0; iipTabList->nSrc; ii++){ + SrcItem *pItem = &pWInfo->pTabList->a[ii]; + if( !pItem->fg.isCte + || pItem->u2.pCteUse->eM10d!=M10d_Yes + || NEVER(pItem->fg.isSubquery==0) + || pItem->u4.pSubq->pSelect->pOrderBy==0 + ){ + pWInfo->revMask |= MASKBIT(ii); + } + } +} + /* ** Generate the beginning of the loop used for WHERE clause processing. ** The return value is a pointer to an opaque structure that contains @@ -144578,7 +175649,7 @@ static int exprIsDeterministic(Expr *p){ ** ** OUTER JOINS ** -** An outer join of tables t1 and t2 is conceptally coded as follows: +** An outer join of tables t1 and t2 is conceptually coded as follows: ** ** foreach row1 in t1 do ** flag = 0 @@ -144600,7 +175671,7 @@ static int exprIsDeterministic(Expr *p){ ** if there is one. If there is no ORDER BY clause or if this routine ** is called from an UPDATE or DELETE statement, then pOrderBy is NULL. ** -** The iIdxCur parameter is the cursor number of an index. If +** The iIdxCur parameter is the cursor number of an index. If ** WHERE_OR_SUBCLAUSE is set, iIdxCur is the cursor number of an index ** to use for OR clause processing. The WHERE clause should use this ** specific cursor. If WHERE_ONEPASS_DESIRED is set, then iIdxCur is @@ -144614,6 +175685,7 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( Expr *pWhere, /* The WHERE clause */ ExprList *pOrderBy, /* An ORDER BY (or GROUP BY) clause, or NULL */ ExprList *pResultSet, /* Query result set. Req'd for DISTINCT */ + Select *pSelect, /* The entire SELECT statement */ u16 wctrlFlags, /* The WHERE_* flags defined in sqliteInt.h */ int iAuxArg /* If WHERE_OR_SUBCLAUSE is set, index cursor number ** If WHERE_USE_LIMIT, then the limit amount */ @@ -144633,8 +175705,8 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( u8 bFordelete = 0; /* OPFLAG_FORDELETE or zero, as appropriate */ assert( (wctrlFlags & WHERE_ONEPASS_MULTIROW)==0 || ( - (wctrlFlags & WHERE_ONEPASS_DESIRED)!=0 - && (wctrlFlags & WHERE_OR_SUBCLAUSE)==0 + (wctrlFlags & WHERE_ONEPASS_DESIRED)!=0 + && (wctrlFlags & WHERE_OR_SUBCLAUSE)==0 )); /* Only one of WHERE_OR_SUBCLAUSE or WHERE_USE_LIMIT */ @@ -144647,17 +175719,14 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( /* An ORDER/GROUP BY clause of more than 63 terms cannot be optimized */ testcase( pOrderBy && pOrderBy->nExpr==BMS-1 ); - if( pOrderBy && pOrderBy->nExpr>=BMS ) pOrderBy = 0; - sWLB.pOrderBy = pOrderBy; - - /* Disable the DISTINCT optimization if SQLITE_DistinctOpt is set via - ** sqlite3_test_ctrl(SQLITE_TESTCTRL_OPTIMIZATIONS,...) */ - if( OptimizationDisabled(db, SQLITE_DistinctOpt) ){ + if( pOrderBy && pOrderBy->nExpr>=BMS ){ + pOrderBy = 0; wctrlFlags &= ~WHERE_WANT_DISTINCT; + wctrlFlags |= WHERE_KEEP_ALL_JOINS; /* Disable omit-noop-join opt */ } /* The number of tables in the FROM clause is limited by the number of - ** bits in a Bitmask + ** bits in a Bitmask */ testcase( pTabList->nSrc==BMS ); if( pTabList->nSrc>BMS ){ @@ -144665,7 +175734,7 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( return 0; } - /* This function normally generates a nested loop for all tables in + /* This function normally generates a nested loop for all tables in ** pTabList. But if the WHERE_OR_SUBCLAUSE flag is set, then we should ** only generate code for the first table in pTabList and assume that ** any cursors associated with subsequent tables are uninitialized. @@ -144679,7 +175748,7 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( ** field (type Bitmask) it must be aligned on an 8-byte boundary on ** some architectures. Hence the ROUND8() below. */ - nByteWInfo = ROUND8(sizeof(WhereInfo)+(nTabList-1)*sizeof(WhereLevel)); + nByteWInfo = SZ_WHEREINFO(nTabList); pWInfo = sqlite3DbMallocRawNN(db, nByteWInfo + sizeof(WhereLoop)); if( db->mallocFailed ){ sqlite3DbFree(db, pWInfo); @@ -144689,7 +175758,9 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( pWInfo->pParse = pParse; pWInfo->pTabList = pTabList; pWInfo->pOrderBy = pOrderBy; +#if WHERETRACE_ENABLED pWInfo->pWhere = pWhere; +#endif pWInfo->pResultSet = pResultSet; pWInfo->aiCurOnePass[0] = pWInfo->aiCurOnePass[1] = -1; pWInfo->nLevel = nTabList; @@ -144697,11 +175768,16 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( pWInfo->wctrlFlags = wctrlFlags; pWInfo->iLimit = iAuxArg; pWInfo->savedNQueryLoop = pParse->nQueryLoop; - memset(&pWInfo->nOBSat, 0, + pWInfo->pSelect = pSelect; + memset(&pWInfo->nOBSat, 0, offsetof(WhereInfo,sWC) - offsetof(WhereInfo,nOBSat)); memset(&pWInfo->a[0], 0, sizeof(WhereLoop)+nTabList*sizeof(WhereLevel)); assert( pWInfo->eOnePass==ONEPASS_OFF ); /* ONEPASS defaults to OFF */ pMaskSet = &pWInfo->sMaskSet; + pMaskSet->n = 0; + pMaskSet->ix[0] = -99; /* Initialize ix[0] to a value that can never be + ** a valid cursor number, to avoid an initial + ** test for pMaskSet->n==0 in sqlite3WhereGetMask() */ sWLB.pWInfo = pWInfo; sWLB.pWC = &pWInfo->sWC; sWLB.pNew = (WhereLoop*)(((char*)pWInfo)+nByteWInfo); @@ -144714,24 +175790,29 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( /* Split the WHERE clause into separate subexpressions where each ** subexpression is separated by an AND operator. */ - initMaskSet(pMaskSet); sqlite3WhereClauseInit(&pWInfo->sWC, pWInfo); sqlite3WhereSplit(&pWInfo->sWC, pWhere, TK_AND); - + /* Special case: No FROM clause */ if( nTabList==0 ){ if( pOrderBy ) pWInfo->nOBSat = pOrderBy->nExpr; - if( wctrlFlags & WHERE_WANT_DISTINCT ){ + if( (wctrlFlags & WHERE_WANT_DISTINCT)!=0 + && OptimizationEnabled(db, SQLITE_DistinctOpt) + ){ pWInfo->eDistinct = WHERE_DISTINCT_UNIQUE; } - ExplainQueryPlan((pParse, 0, "SCAN CONSTANT ROW")); + if( ALWAYS(pWInfo->pSelect) + && (pWInfo->pSelect->selFlags & SF_MultiValue)==0 + ){ + ExplainQueryPlan((pParse, 0, "SCAN CONSTANT ROW")); + } }else{ /* Assign a bit from the bitmask to every term in the FROM clause. ** ** The N-th term of the FROM clause is assigned a bitmask of 1<sWC); - if( db->mallocFailed ) goto whereBeginError; + if( pSelect && pSelect->pLimit ){ + sqlite3WhereAddLimit(&pWInfo->sWC, pSelect); + } + if( pParse->nErr ) goto whereBeginError; - /* Special case: WHERE terms that do not refer to any tables in the join - ** (constant expressions). Evaluate each such term, and jump over all the - ** generated code if the result is not true. + /* The False-WHERE-Term-Bypass optimization: + ** + ** If there are WHERE terms that are false, then no rows will be output, + ** so skip over all of the code generated here. + ** + ** Conditions: ** - ** Do not do this if the expression contains non-deterministic functions - ** that are not within a sub-select. This is not strictly required, but - ** preserves SQLite's legacy behaviour in the following two cases: + ** (1) The WHERE term must not refer to any tables in the join. + ** (2) The term must not come from an ON clause on the + ** right-hand side of a LEFT or FULL JOIN. + ** (3) The term must not come from an ON clause, or there must be + ** no RIGHT or FULL OUTER joins in pTabList. + ** (4) If the expression contains non-deterministic functions + ** that are not within a sub-select. This is not required + ** for correctness but rather to preserves SQLite's legacy + ** behaviour in the following two cases: ** - ** FROM ... WHERE random()>0; -- eval random() once per row - ** FROM ... WHERE (SELECT random())>0; -- eval random() once overall + ** WHERE random()>0; -- eval random() once per row + ** WHERE (SELECT random())>0; -- eval random() just once overall + ** + ** Note that the Where term need not be a constant in order for this + ** optimization to apply, though it does need to be constant relative to + ** the current subquery (condition 1). The term might include variables + ** from outer queries so that the value of the term changes from one + ** invocation of the current subquery to the next. */ - for(ii=0; iinTerm; ii++){ - WhereTerm *pT = &sWLB.pWC->a[ii]; + for(ii=0; iinBase; ii++){ + WhereTerm *pT = &sWLB.pWC->a[ii]; /* A term of the WHERE clause */ + Expr *pX; /* The expression of pT */ if( pT->wtFlags & TERM_VIRTUAL ) continue; - if( pT->prereqAll==0 && (nTabList==0 || exprIsDeterministic(pT->pExpr)) ){ - sqlite3ExprIfFalse(pParse, pT->pExpr, pWInfo->iBreak, SQLITE_JUMPIFNULL); + pX = pT->pExpr; + assert( pX!=0 ); + assert( pT->prereqAll!=0 || !ExprHasProperty(pX, EP_OuterON) ); + if( pT->prereqAll==0 /* Conditions (1) and (2) */ + && (nTabList==0 || exprIsDeterministic(pX)) /* Condition (4) */ + && !(ExprHasProperty(pX, EP_InnerON) /* Condition (3) */ + && (pTabList->a[0].fg.jointype & JT_LTORJ)!=0 ) + ){ + sqlite3ExprIfFalse(pParse, pX, pWInfo->iBreak, SQLITE_JUMPIFNULL); pT->wtFlags |= TERM_CODED; } } if( wctrlFlags & WHERE_WANT_DISTINCT ){ - if( isDistinctRedundant(pParse, pTabList, &pWInfo->sWC, pResultSet) ){ + if( OptimizationDisabled(db, SQLITE_DistinctOpt) ){ + /* Disable the DISTINCT optimization if SQLITE_DistinctOpt is set via + ** sqlite3_test_ctrl(SQLITE_TESTCTRL_OPTIMIZATIONS,...) */ + wctrlFlags &= ~WHERE_WANT_DISTINCT; + pWInfo->wctrlFlags &= ~WHERE_WANT_DISTINCT; + }else if( isDistinctRedundant(pParse, pTabList, &pWInfo->sWC, pResultSet) ){ /* The DISTINCT marking is pointless. Ignore it. */ pWInfo->eDistinct = WHERE_DISTINCT_UNIQUE; }else if( pOrderBy==0 ){ @@ -144795,51 +175907,86 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( /* Construct the WhereLoop objects */ #if defined(WHERETRACE_ENABLED) - if( sqlite3WhereTrace & 0xffff ){ + if( sqlite3WhereTrace & 0xffffffff ){ sqlite3DebugPrintf("*** Optimizer Start *** (wctrlFlags: 0x%x",wctrlFlags); if( wctrlFlags & WHERE_USE_LIMIT ){ sqlite3DebugPrintf(", limit: %d", iAuxArg); } sqlite3DebugPrintf(")\n"); - } - if( sqlite3WhereTrace & 0x100 ){ /* Display all terms of the WHERE clause */ - sqlite3WhereClausePrint(sWLB.pWC); + if( sqlite3WhereTrace & 0x8000 ){ + Select sSelect; + memset(&sSelect, 0, sizeof(sSelect)); + sSelect.selFlags = SF_WhereBegin; + sSelect.pSrc = pTabList; + sSelect.pWhere = pWhere; + sSelect.pOrderBy = pOrderBy; + sSelect.pEList = pResultSet; + sqlite3TreeViewSelect(0, &sSelect, 0); + } + if( sqlite3WhereTrace & 0x4000 ){ /* Display all WHERE clause terms */ + sqlite3DebugPrintf("---- WHERE clause at start of analysis:\n"); + sqlite3WhereClausePrint(sWLB.pWC); + } } #endif if( nTabList!=1 || whereShortCut(&sWLB)==0 ){ rc = whereLoopAddAll(&sWLB); if( rc ) goto whereBeginError; - -#ifdef WHERETRACE_ENABLED - if( sqlite3WhereTrace ){ /* Display all of the WhereLoop objects */ - WhereLoop *p; - int i; - static const char zLabel[] = "0123456789abcdefghijklmnopqrstuvwyxz" - "ABCDEFGHIJKLMNOPQRSTUVWYXZ"; - for(p=pWInfo->pLoops, i=0; p; p=p->pNextLoop, i++){ - p->cId = zLabel[i%(sizeof(zLabel)-1)]; - whereLoopPrint(p, sWLB.pWC); - } - } -#endif - + +#ifdef SQLITE_ENABLE_STAT4 + /* If one or more WhereTerm.truthProb values were used in estimating + ** loop parameters, but then those truthProb values were subsequently + ** changed based on STAT4 information while computing subsequent loops, + ** then we need to rerun the whole loop building process so that all + ** loops will be built using the revised truthProb values. */ + if( sWLB.bldFlags2 & SQLITE_BLDF2_2NDPASS ){ + WHERETRACE_ALL_LOOPS(pWInfo, sWLB.pWC); + WHERETRACE(0xffffffff, + ("**** Redo all loop computations due to" + " TERM_HIGHTRUTH changes ****\n")); + while( pWInfo->pLoops ){ + WhereLoop *p = pWInfo->pLoops; + pWInfo->pLoops = p->pNextLoop; + whereLoopDelete(db, p); + } + rc = whereLoopAddAll(&sWLB); + if( rc ) goto whereBeginError; + } +#endif + WHERETRACE_ALL_LOOPS(pWInfo, sWLB.pWC); + wherePathSolver(pWInfo, 0); if( db->mallocFailed ) goto whereBeginError; if( pWInfo->pOrderBy ){ - wherePathSolver(pWInfo, pWInfo->nRowOut+1); + whereInterstageHeuristic(pWInfo); + wherePathSolver(pWInfo, pWInfo->nRowOut<0 ? 1 : pWInfo->nRowOut+1); if( db->mallocFailed ) goto whereBeginError; } + + /* TUNING: Assume that a DISTINCT clause on a subquery reduces + ** the output size by a factor of 8 (LogEst -30). Search for + ** tag-20250414a to see other cases. + */ + if( (pWInfo->wctrlFlags & WHERE_WANT_DISTINCT)!=0 ){ + WHERETRACE(0x0080,("nRowOut reduced from %d to %d due to DISTINCT\n", + pWInfo->nRowOut, pWInfo->nRowOut-30)); + pWInfo->nRowOut -= 30; + } + } + assert( pWInfo->pTabList!=0 ); if( pWInfo->pOrderBy==0 && (db->flags & SQLITE_ReverseOrder)!=0 ){ - pWInfo->revMask = ALLBITS; + whereReverseScanOrder(pWInfo); } - if( pParse->nErr || NEVER(db->mallocFailed) ){ + if( pParse->nErr ){ goto whereBeginError; } + assert( db->mallocFailed==0 ); #ifdef WHERETRACE_ENABLED if( sqlite3WhereTrace ){ - sqlite3DebugPrintf("---- Solution nRow=%d", pWInfo->nRowOut); + sqlite3DebugPrintf("---- Solution cost=%d, nRow=%d", + pWInfo->rTotalCost, pWInfo->nRowOut); if( pWInfo->nOBSat>0 ){ sqlite3DebugPrintf(" ORDERBY=%d,0x%llx", pWInfo->nOBSat, pWInfo->revMask); } @@ -144859,89 +176006,48 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( } sqlite3DebugPrintf("\n"); for(ii=0; iinLevel; ii++){ - whereLoopPrint(pWInfo->a[ii].pWLoop, sWLB.pWC); + sqlite3WhereLoopPrint(pWInfo->a[ii].pWLoop, sWLB.pWC); } } #endif - /* Attempt to omit tables from the join that do not affect the result. - ** For a table to not affect the result, the following must be true: - ** - ** 1) The query must not be an aggregate. - ** 2) The table must be the RHS of a LEFT JOIN. - ** 3) Either the query must be DISTINCT, or else the ON or USING clause - ** must contain a constraint that limits the scan of the table to - ** at most a single row. - ** 4) The table must not be referenced by any part of the query apart - ** from its own USING or ON clause. + /* Attempt to omit tables from a join that do not affect the result. + ** See the comment on whereOmitNoopJoin() for further information. ** - ** For example, given: - ** - ** CREATE TABLE t1(ipk INTEGER PRIMARY KEY, v1); - ** CREATE TABLE t2(ipk INTEGER PRIMARY KEY, v2); - ** CREATE TABLE t3(ipk INTEGER PRIMARY KEY, v3); - ** - ** then table t2 can be omitted from the following: - ** - ** SELECT v1, v3 FROM t1 - ** LEFT JOIN t2 USING (t1.ipk=t2.ipk) - ** LEFT JOIN t3 USING (t1.ipk=t3.ipk) - ** - ** or from: - ** - ** SELECT DISTINCT v1, v3 FROM t1 - ** LEFT JOIN t2 - ** LEFT JOIN t3 USING (t1.ipk=t3.ipk) + ** This query optimization is factored out into a separate "no-inline" + ** procedure to keep the sqlite3WhereBegin() procedure from becoming + ** too large. If sqlite3WhereBegin() becomes too large, that prevents + ** some C-compiler optimizers from in-lining the + ** sqlite3WhereCodeOneLoopStart() procedure, and it is important to + ** in-line sqlite3WhereCodeOneLoopStart() for performance reasons. */ notReady = ~(Bitmask)0; + if( pWInfo->nLevel>=2 /* Must be a join, or this opt8n is pointless */ + && pResultSet!=0 /* Condition (1) */ + && 0==(wctrlFlags & (WHERE_AGG_DISTINCT|WHERE_KEEP_ALL_JOINS)) /* (1),(6) */ + && OptimizationEnabled(db, SQLITE_OmitNoopJoin) /* (7) */ + ){ + notReady = whereOmitNoopJoin(pWInfo, notReady); + nTabList = pWInfo->nLevel; + assert( nTabList>0 ); + } + + /* Check to see if there are any SEARCH loops that might benefit from + ** using a Bloom filter. + */ if( pWInfo->nLevel>=2 - && pResultSet!=0 /* guarantees condition (1) above */ - && OptimizationEnabled(db, SQLITE_OmitNoopJoin) + && OptimizationEnabled(db, SQLITE_BloomFilter) ){ - int i; - Bitmask tabUsed = sqlite3WhereExprListUsage(pMaskSet, pResultSet); - if( sWLB.pOrderBy ){ - tabUsed |= sqlite3WhereExprListUsage(pMaskSet, sWLB.pOrderBy); - } - for(i=pWInfo->nLevel-1; i>=1; i--){ - WhereTerm *pTerm, *pEnd; - struct SrcList_item *pItem; - pLoop = pWInfo->a[i].pWLoop; - pItem = &pWInfo->pTabList->a[pLoop->iTab]; - if( (pItem->fg.jointype & JT_LEFT)==0 ) continue; - if( (wctrlFlags & WHERE_WANT_DISTINCT)==0 - && (pLoop->wsFlags & WHERE_ONEROW)==0 - ){ - continue; - } - if( (tabUsed & pLoop->maskSelf)!=0 ) continue; - pEnd = sWLB.pWC->a + sWLB.pWC->nTerm; - for(pTerm=sWLB.pWC->a; pTermprereqAll & pLoop->maskSelf)!=0 ){ - if( !ExprHasProperty(pTerm->pExpr, EP_FromJoin) - || pTerm->pExpr->iRightJoinTable!=pItem->iCursor - ){ - break; - } - } - } - if( pTerm drop loop %c not used\n", pLoop->cId)); - notReady &= ~pLoop->maskSelf; - for(pTerm=sWLB.pWC->a; pTermprereqAll & pLoop->maskSelf)!=0 ){ - pTerm->wtFlags |= TERM_CODED; - } - } - if( i!=pWInfo->nLevel-1 ){ - int nByte = (pWInfo->nLevel-1-i) * sizeof(WhereLevel); - memmove(&pWInfo->a[i], &pWInfo->a[i+1], nByte); - } - pWInfo->nLevel--; - nTabList--; - } + whereCheckIfBloomFilterIsUseful(pWInfo); + } + +#if defined(WHERETRACE_ENABLED) + if( sqlite3WhereTrace & 0x4000 ){ /* Display all terms of the WHERE clause */ + sqlite3DebugPrintf("---- WHERE clause at end of analysis:\n"); + sqlite3WhereClausePrint(sWLB.pWC); } - WHERETRACE(0xffff,("*** Optimizer Finished ***\n")); + WHERETRACE(0xffffffff,("*** Optimizer Finished ***\n")); +#endif pWInfo->pParse->nQueryLoop += pWInfo->nRowOut; /* If the caller is an UPDATE or DELETE statement that is requesting @@ -144967,14 +176073,15 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( if( (wctrlFlags & WHERE_ONEPASS_DESIRED)!=0 ){ int wsFlags = pWInfo->a[0].pWLoop->wsFlags; int bOnerow = (wsFlags & WHERE_ONEROW)!=0; - assert( !(wsFlags & WHERE_VIRTUALTABLE) || IsVirtual(pTabList->a[0].pTab) ); + assert( !(wsFlags&WHERE_VIRTUALTABLE) || IsVirtual(pTabList->a[0].pSTab) ); if( bOnerow || ( 0!=(wctrlFlags & WHERE_ONEPASS_MULTIROW) - && !IsVirtual(pTabList->a[0].pTab) + && !IsVirtual(pTabList->a[0].pSTab) && (0==(wsFlags & WHERE_MULTI_OR) || (wctrlFlags & WHERE_DUPLICATES_OK)) + && OptimizationEnabled(db, SQLITE_OnePass) )){ pWInfo->eOnePass = bOnerow ? ONEPASS_SINGLE : ONEPASS_MULTI; - if( HasRowid(pTabList->a[0].pTab) && (wsFlags & WHERE_IDX_ONLY) ){ + if( HasRowid(pTabList->a[0].pSTab) && (wsFlags & WHERE_IDX_ONLY) ){ if( wctrlFlags & WHERE_ONEPASS_MULTIROW ){ bFordelete = OPFLAG_FORDELETE; } @@ -144989,13 +176096,21 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( for(ii=0, pLevel=pWInfo->a; iia[pLevel->iFrom]; - pTab = pTabItem->pTab; + pTab = pTabItem->pSTab; iDb = sqlite3SchemaToIndex(db, pTab->pSchema); pLoop = pLevel->pWLoop; - if( (pTab->tabFlags & TF_Ephemeral)!=0 || pTab->pSelect ){ + pLevel->addrBrk = sqlite3VdbeMakeLabel(pParse); + if( ii==0 || (pTabItem[0].fg.jointype & JT_LEFT)!=0 ){ + pLevel->addrHalt = pLevel->addrBrk; + }else if( pWInfo->a[ii-1].pRJ ){ + pLevel->addrHalt = pWInfo->a[ii-1].addrBrk; + }else{ + pLevel->addrHalt = pWInfo->a[ii-1].addrHalt; + } + if( (pTab->tabFlags & TF_Ephemeral)!=0 || IsView(pTab) ){ /* Do nothing */ }else #ifndef SQLITE_OMIT_VIRTUALTABLE @@ -145007,8 +176122,10 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( /* noop */ }else #endif - if( (pLoop->wsFlags & WHERE_IDX_ONLY)==0 - && (wctrlFlags & WHERE_OR_SUBCLAUSE)==0 ){ + if( ((pLoop->wsFlags & WHERE_IDX_ONLY)==0 + && (wctrlFlags & WHERE_OR_SUBCLAUSE)==0) + || (pTabItem->fg.jointype & (JT_LTORJ|JT_RIGHT))!=0 + ){ int op = OP_OpenRead; if( pWInfo->eOnePass!=ONEPASS_OFF ){ op = OP_OpenWrite; @@ -145018,7 +176135,14 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( assert( pTabItem->iCursor==pLevel->iTabCur ); testcase( pWInfo->eOnePass==ONEPASS_OFF && pTab->nCol==BMS-1 ); testcase( pWInfo->eOnePass==ONEPASS_OFF && pTab->nCol==BMS ); - if( pWInfo->eOnePass==ONEPASS_OFF && pTab->nColeOnePass==ONEPASS_OFF + && pTab->nColtabFlags & (TF_HasGenerated|TF_WithoutRowid))==0 + && (pLoop->wsFlags & (WHERE_AUTO_INDEX|WHERE_BLOOMFILTER))==0 + ){ + /* If we know that only a prefix of the record will be used, + ** it is advantageous to reduce the "column count" field in + ** the P4 operand of the OP_OpenRead/Write opcode. */ Bitmask b = pTabItem->colUsed; int n = 0; for(; b; b=b>>1, n++){} @@ -145026,7 +176150,7 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( assert( n<=pTab->nCol ); } #ifdef SQLITE_ENABLE_CURSOR_HINTS - if( pLoop->u.btree.pIndex!=0 ){ + if( pLoop->u.btree.pIndex!=0 && (pTab->tabFlags & TF_WithoutRowid)==0 ){ sqlite3VdbeChangeP5(v, OPFLAG_SEEKEQ|bFordelete); }else #endif @@ -145037,6 +176161,13 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( sqlite3VdbeAddOp4Dup8(v, OP_ColumnsUsed, pTabItem->iCursor, 0, 0, (const u8*)&pTabItem->colUsed, P4_INT64); #endif + if( ii>=2 + && (pTabItem[0].fg.jointype & (JT_LTORJ|JT_LEFT))==0 + && pLevel->addrHalt==pWInfo->a[0].addrHalt + ){ + sqlite3VdbeAddOp2(v, OP_IfEmpty, pTabItem->iCursor, pWInfo->iBreak); + VdbeCoverage(v); + } }else{ sqlite3TableLock(pParse, iDb, pTab->tnum, 0, pTab->zName); } @@ -145054,7 +176185,7 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( iIndexCur = pLevel->iTabCur; op = 0; }else if( pWInfo->eOnePass!=ONEPASS_OFF ){ - Index *pJ = pTabItem->pTab->pIndex; + Index *pJ = pTabItem->pSTab->pIndex; iIndexCur = iAuxArg; assert( wctrlFlags & WHERE_ONEPASS_DESIRED ); while( ALWAYS(pJ) && pJ!=pIx ){ @@ -145068,8 +176199,17 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( op = OP_ReopenIdx; }else{ iIndexCur = pParse->nTab++; + if( pIx->bHasExpr && OptimizationEnabled(db, SQLITE_IndexedExpr) ){ + whereAddIndexedExpr(pParse, pIx, iIndexCur, pTabItem); + } + if( pIx->pPartIdxWhere && (pTabItem->fg.jointype & JT_RIGHT)==0 ){ + wherePartIdxExpr( + pParse, pIx, pIx->pPartIdxWhere, 0, iIndexCur, pTabItem + ); + } } pLevel->iIdxCur = iIndexCur; + assert( pIx!=0 ); assert( pIx->pSchema==pTab->pSchema ); assert( iIndexCur>=0 ); if( op ){ @@ -145077,10 +176217,12 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( sqlite3VdbeSetP4KeyInfo(pParse, pIx); if( (pLoop->wsFlags & WHERE_CONSTRAINT)!=0 && (pLoop->wsFlags & (WHERE_COLUMN_RANGE|WHERE_SKIPSCAN))==0 + && (pLoop->wsFlags & WHERE_BIGNULL_SORT)==0 + && (pLoop->wsFlags & WHERE_IN_SEEKSCAN)==0 && (pWInfo->wctrlFlags&WHERE_ORDERBY_MIN)==0 && pWInfo->eDistinct!=WHERE_DISTINCT_ORDERED ){ - sqlite3VdbeChangeP5(v, OPFLAG_SEEKEQ); /* Hint to COMDB2 */ + sqlite3VdbeChangeP5(v, OPFLAG_SEEKEQ); } VdbeComment((v, "%s", pIx->zName)); #ifdef SQLITE_ENABLE_COLUMN_USED_MASK @@ -145101,6 +176243,37 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( } } if( iDb>=0 ) sqlite3CodeVerifySchema(pParse, iDb); + if( (pTabItem->fg.jointype & JT_RIGHT)!=0 + && (pLevel->pRJ = sqlite3WhereMalloc(pWInfo, sizeof(WhereRightJoin)))!=0 + ){ + WhereRightJoin *pRJ = pLevel->pRJ; + pRJ->iMatch = pParse->nTab++; + pRJ->regBloom = ++pParse->nMem; + sqlite3VdbeAddOp2(v, OP_Blob, 65536, pRJ->regBloom); + pRJ->regReturn = ++pParse->nMem; + sqlite3VdbeAddOp2(v, OP_Null, 0, pRJ->regReturn); + assert( pTab==pTabItem->pSTab ); + if( HasRowid(pTab) ){ + KeyInfo *pInfo; + sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pRJ->iMatch, 1); + pInfo = sqlite3KeyInfoAlloc(pParse->db, 1, 0); + if( pInfo ){ + pInfo->aColl[0] = 0; + pInfo->aSortFlags[0] = 0; + sqlite3VdbeAppendP4(v, pInfo, P4_KEYINFO); + } + }else{ + Index *pPk = sqlite3PrimaryKeyIndex(pTab); + sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pRJ->iMatch, pPk->nKeyCol); + sqlite3VdbeSetP4KeyInfo(pParse, pPk); + } + pLoop->wsFlags &= ~WHERE_IDX_ONLY; + /* The nature of RIGHT JOIN processing is such that it messes up + ** the output order. So omit any ORDER BY/GROUP BY elimination + ** optimizations. We need to do an actual sort for RIGHT JOIN. */ + pWInfo->nOBSat = 0; + pWInfo->eDistinct = WHERE_DISTINCT_UNORDERED; + } } pWInfo->iTop = sqlite3VdbeCurrentAddr(v); if( db->mallocFailed ) goto whereBeginError; @@ -145112,15 +176285,36 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( for(ii=0; iinErr ) goto whereBeginError; pLevel = &pWInfo->a[ii]; wsFlags = pLevel->pWLoop->wsFlags; + pSrc = &pTabList->a[pLevel->iFrom]; + if( pSrc->fg.isMaterialized ){ + Subquery *pSubq; + int iOnce = 0; + assert( pSrc->fg.isSubquery ); + pSubq = pSrc->u4.pSubq; + if( pSrc->fg.isCorrelated==0 ){ + iOnce = sqlite3VdbeAddOp0(v, OP_Once); VdbeCoverage(v); + }else{ + iOnce = 0; + } + sqlite3VdbeAddOp2(v, OP_Gosub, pSubq->regReturn, pSubq->addrFillSub); + VdbeComment((v, "materialize %!S", pSrc)); + if( iOnce ) sqlite3VdbeJumpHere(v, iOnce); + } + assert( pTabList == pWInfo->pTabList ); + if( (wsFlags & (WHERE_AUTO_INDEX|WHERE_BLOOMFILTER))!=0 ){ + if( (wsFlags & WHERE_AUTO_INDEX)!=0 ){ #ifndef SQLITE_OMIT_AUTOMATIC_INDEX - if( (pLevel->pWLoop->wsFlags & WHERE_AUTO_INDEX)!=0 ){ - constructAutomaticIndex(pParse, &pWInfo->sWC, - &pTabList->a[pLevel->iFrom], notReady, pLevel); + constructAutomaticIndex(pParse, &pWInfo->sWC, notReady, pLevel); +#endif + }else{ + sqlite3ConstructBloomFilter(pWInfo, ii, pLevel, notReady); + } if( db->mallocFailed ) goto whereBeginError; } -#endif addrExplain = sqlite3WhereExplainOneScan( pParse, pTabList, pLevel, wctrlFlags ); @@ -145134,6 +176328,7 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( /* Done. */ VdbeModuleComment((v, "Begin WHERE-core")); + pWInfo->iEndWhere = sqlite3VdbeCurrentAddr(v); return pWInfo; /* Jump here if malloc fails */ @@ -145142,6 +176337,11 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( pParse->nQueryLoop = pWInfo->savedNQueryLoop; whereInfoFree(db, pWInfo); } +#ifdef WHERETRACE_ENABLED + /* Prevent harmless compiler warnings about debugging routines + ** being declared but never used */ + sqlite3ShowWhereLoopList(0); +#endif /* WHERETRACE_ENABLED */ return 0; } @@ -145162,11 +176362,12 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( ){ if( (db->flags & SQLITE_VdbeAddopTrace)==0 ) return; sqlite3VdbePrintOp(0, pc, pOp); + sqlite3ShowWhereTerm(0); /* So compiler won't complain about unused func */ } #endif /* -** Generate the end of the WHERE loop. See comments on +** Generate the end of the WHERE loop. See comments on ** sqlite3WhereBegin() for additional information. */ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){ @@ -145177,6 +176378,11 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){ WhereLoop *pLoop; SrcList *pTabList = pWInfo->pTabList; sqlite3 *db = pParse->db; + int iEnd = sqlite3VdbeCurrentAddr(v); + int nRJ = 0; +#ifndef SQLITE_DISABLE_SKIPAHEAD_DISTINCT + int addrSeek = 0; +#endif /* Generate loop termination code. */ @@ -145184,21 +176390,38 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){ for(i=pWInfo->nLevel-1; i>=0; i--){ int addr; pLevel = &pWInfo->a[i]; + if( pLevel->pRJ ){ + /* Terminate the subroutine that forms the interior of the loop of + ** the RIGHT JOIN table */ + WhereRightJoin *pRJ = pLevel->pRJ; + sqlite3VdbeResolveLabel(v, pLevel->addrCont); + /* Replace addrCont with a new label that will never be used, just so + ** the subsequent call to resolve pLevel->addrCont will have something + ** to resolve. */ + pLevel->addrCont = sqlite3VdbeMakeLabel(pParse); + pRJ->endSubrtn = sqlite3VdbeCurrentAddr(v); + sqlite3VdbeAddOp3(v, OP_Return, pRJ->regReturn, pRJ->addrSubrtn, 1); + VdbeCoverage(v); + nRJ++; + } pLoop = pLevel->pWLoop; if( pLevel->op!=OP_Noop ){ #ifndef SQLITE_DISABLE_SKIPAHEAD_DISTINCT - int addrSeek = 0; Index *pIdx; int n; if( pWInfo->eDistinct==WHERE_DISTINCT_ORDERED && i==pWInfo->nLevel-1 /* Ticket [ef9318757b152e3] 2017-10-21 */ && (pLoop->wsFlags & WHERE_INDEXED)!=0 && (pIdx = pLoop->u.btree.pIndex)->hasStat1 - && (n = pLoop->u.btree.nIdxCol)>0 + && (n = pLoop->u.btree.nDistinctCol)>0 && pIdx->aiRowLogEst[n]>=36 ){ int r1 = pParse->nMem+1; int j, op; + int addrIfNull = 0; /* Init to avoid false-positive compiler warning */ + if( pLevel->iLeftJoin ){ + addrIfNull = sqlite3VdbeAddOp2(v, OP_IfNullRow, pLevel->iIdxCur, r1); + } for(j=0; jiIdxCur, j, r1+j); } @@ -145208,35 +176431,75 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){ VdbeCoverageIf(v, op==OP_SeekLT); VdbeCoverageIf(v, op==OP_SeekGT); sqlite3VdbeAddOp2(v, OP_Goto, 1, pLevel->p2); + if( pLevel->iLeftJoin ){ + sqlite3VdbeJumpHere(v, addrIfNull); + } } #endif /* SQLITE_DISABLE_SKIPAHEAD_DISTINCT */ - /* The common case: Advance to the next row */ - sqlite3VdbeResolveLabel(v, pLevel->addrCont); + } + if( pTabList->a[pLevel->iFrom].fg.fromExists ){ + /* This is an EXISTS-to-JOIN optimization loop. If this loop sees a + ** successful row, it should break out of itself. */ + sqlite3VdbeAddOp2(v, OP_Goto, 0, pLevel->addrBrk); + VdbeComment((v, "EXISTS break %d", i)); + } + sqlite3VdbeResolveLabel(v, pLevel->addrCont); + if( pLevel->op!=OP_Noop ){ sqlite3VdbeAddOp3(v, pLevel->op, pLevel->p1, pLevel->p2, pLevel->p3); sqlite3VdbeChangeP5(v, pLevel->p5); VdbeCoverage(v); VdbeCoverageIf(v, pLevel->op==OP_Next); VdbeCoverageIf(v, pLevel->op==OP_Prev); VdbeCoverageIf(v, pLevel->op==OP_VNext); + if( pLevel->regBignull ){ + sqlite3VdbeResolveLabel(v, pLevel->addrBignull); + sqlite3VdbeAddOp2(v, OP_DecrJumpZero, pLevel->regBignull, pLevel->p2-1); + VdbeCoverage(v); + } #ifndef SQLITE_DISABLE_SKIPAHEAD_DISTINCT - if( addrSeek ) sqlite3VdbeJumpHere(v, addrSeek); + if( addrSeek ){ + sqlite3VdbeJumpHere(v, addrSeek); + addrSeek = 0; + } #endif - }else{ - sqlite3VdbeResolveLabel(v, pLevel->addrCont); } - if( pLoop->wsFlags & WHERE_IN_ABLE && pLevel->u.in.nIn>0 ){ + if( (pLoop->wsFlags & WHERE_IN_ABLE)!=0 && pLevel->u.in.nIn>0 ){ struct InLoop *pIn; int j; sqlite3VdbeResolveLabel(v, pLevel->addrNxt); for(j=pLevel->u.in.nIn, pIn=&pLevel->u.in.aInLoop[j-1]; j>0; j--, pIn--){ + assert( sqlite3VdbeGetOp(v, pIn->addrInTop+1)->opcode==OP_IsNull + || pParse->db->mallocFailed ); sqlite3VdbeJumpHere(v, pIn->addrInTop+1); if( pIn->eEndLoopOp!=OP_Noop ){ if( pIn->nPrefix ){ - assert( pLoop->wsFlags & WHERE_IN_EARLYOUT ); - sqlite3VdbeAddOp4Int(v, OP_IfNoHope, pLevel->iIdxCur, - sqlite3VdbeCurrentAddr(v)+2, - pIn->iBase, pIn->nPrefix); - VdbeCoverage(v); + int bEarlyOut = + (pLoop->wsFlags & WHERE_VIRTUALTABLE)==0 + && (pLoop->wsFlags & WHERE_IN_EARLYOUT)!=0; + if( pLevel->iLeftJoin ){ + /* For LEFT JOIN queries, cursor pIn->iCur may not have been + ** opened yet. This occurs for WHERE clauses such as + ** "a = ? AND b IN (...)", where the index is on (a, b). If + ** the RHS of the (a=?) is NULL, then the "b IN (...)" may + ** never have been coded, but the body of the loop run to + ** return the null-row. So, if the cursor is not open yet, + ** jump over the OP_Next or OP_Prev instruction about to + ** be coded. */ + sqlite3VdbeAddOp2(v, OP_IfNotOpen, pIn->iCur, + sqlite3VdbeCurrentAddr(v) + 2 + bEarlyOut); + VdbeCoverage(v); + } + if( bEarlyOut ){ + sqlite3VdbeAddOp4Int(v, OP_IfNoHope, pLevel->iIdxCur, + sqlite3VdbeCurrentAddr(v)+2, + pIn->iBase, pIn->nPrefix); + VdbeCoverage(v); + /* Retarget the OP_IsNull against the left operand of IN so + ** it jumps past the OP_IfNoHope. This is because the + ** OP_IsNull also bypasses the OP_Affinity opcode that is + ** required by OP_IfNoHope. */ + sqlite3VdbeJumpHere(v, pIn->addrInTop+1); + } } sqlite3VdbeAddOp2(v, pIn->eEndLoopOp, pIn->iCur, pIn->addrInTop); VdbeCoverage(v); @@ -145247,6 +176510,10 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){ } } sqlite3VdbeResolveLabel(v, pLevel->addrBrk); + if( pLevel->pRJ ){ + sqlite3VdbeAddOp3(v, OP_Return, pLevel->pRJ->regReturn, 0, 1); + VdbeCoverage(v); + } if( pLevel->addrSkip ){ sqlite3VdbeGoto(v, pLevel->addrSkip); VdbeComment((v, "next skip-scan on %s", pLoop->u.btree.pIndex->zName)); @@ -145265,12 +176532,27 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){ addr = sqlite3VdbeAddOp1(v, OP_IfPos, pLevel->iLeftJoin); VdbeCoverage(v); assert( (ws & WHERE_IDX_ONLY)==0 || (ws & WHERE_INDEXED)!=0 ); if( (ws & WHERE_IDX_ONLY)==0 ){ - assert( pLevel->iTabCur==pTabList->a[pLevel->iFrom].iCursor ); + SrcItem *pSrc = &pTabList->a[pLevel->iFrom]; + assert( pLevel->iTabCur==pSrc->iCursor ); + if( pSrc->fg.viaCoroutine ){ + int m, n; + assert( pSrc->fg.isSubquery ); + n = pSrc->u4.pSubq->regResult; + assert( pSrc->pSTab!=0 ); + m = pSrc->pSTab->nCol; + sqlite3VdbeAddOp3(v, OP_Null, 0, n, n+m-1); + } sqlite3VdbeAddOp1(v, OP_NullRow, pLevel->iTabCur); } - if( (ws & WHERE_INDEXED) - || ((ws & WHERE_MULTI_OR) && pLevel->u.pCovidx) + if( (ws & WHERE_INDEXED) + || ((ws & WHERE_MULTI_OR) && pLevel->u.pCoveringIdx) ){ + if( ws & WHERE_MULTI_OR ){ + Index *pIx = pLevel->u.pCoveringIdx; + int iDb = sqlite3SchemaToIndex(db, pIx->pSchema); + sqlite3VdbeAddOp3(v, OP_ReopenIdx, pLevel->iIdxCur, pIx->tnum, iDb); + sqlite3VdbeSetP4KeyInfo(pParse, pIx); + } sqlite3VdbeAddOp1(v, OP_NullRow, pLevel->iIdxCur); } if( pLevel->op==OP_Return ){ @@ -145281,63 +176563,46 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){ sqlite3VdbeJumpHere(v, addr); } VdbeModuleComment((v, "End WHERE-loop%d: %s", i, - pWInfo->pTabList->a[pLevel->iFrom].pTab->zName)); + pWInfo->pTabList->a[pLevel->iFrom].pSTab->zName)); } - /* The "break" point is here, just past the end of the outer loop. - ** Set it. - */ - sqlite3VdbeResolveLabel(v, pWInfo->iBreak); - assert( pWInfo->nLevel<=pTabList->nSrc ); for(i=0, pLevel=pWInfo->a; inLevel; i++, pLevel++){ int k, last; - VdbeOp *pOp; + VdbeOp *pOp, *pLastOp; Index *pIdx = 0; - struct SrcList_item *pTabItem = &pTabList->a[pLevel->iFrom]; - Table *pTab = pTabItem->pTab; + SrcItem *pTabItem = &pTabList->a[pLevel->iFrom]; + Table *pTab = pTabItem->pSTab; assert( pTab!=0 ); pLoop = pLevel->pWLoop; + /* Do RIGHT JOIN processing. Generate code that will output the + ** unmatched rows of the right operand of the RIGHT JOIN with + ** all of the columns of the left operand set to NULL. + */ + if( pLevel->pRJ ){ + sqlite3WhereRightJoinLoop(pWInfo, i, pLevel); + continue; + } + /* For a co-routine, change all OP_Column references to the table of ** the co-routine into OP_Copy of result contained in a register. ** OP_Rowid becomes OP_Null. */ if( pTabItem->fg.viaCoroutine ){ testcase( pParse->db->mallocFailed ); + assert( pTabItem->fg.isSubquery ); + assert( pTabItem->u4.pSubq->regResult>=0 ); translateColumnToCopy(pParse, pLevel->addrBody, pLevel->iTabCur, - pTabItem->regResult, 0); + pTabItem->u4.pSubq->regResult, 0); continue; } -#ifdef SQLITE_ENABLE_EARLY_CURSOR_CLOSE - /* Close all of the cursors that were opened by sqlite3WhereBegin. - ** Except, do not close cursors that will be reused by the OR optimization - ** (WHERE_OR_SUBCLAUSE). And do not close the OP_OpenWrite cursors - ** created for the ONEPASS optimization. - */ - if( (pTab->tabFlags & TF_Ephemeral)==0 - && pTab->pSelect==0 - && (pWInfo->wctrlFlags & WHERE_OR_SUBCLAUSE)==0 - ){ - int ws = pLoop->wsFlags; - if( pWInfo->eOnePass==ONEPASS_OFF && (ws & WHERE_IDX_ONLY)==0 ){ - sqlite3VdbeAddOp1(v, OP_Close, pTabItem->iCursor); - } - if( (ws & WHERE_INDEXED)!=0 - && (ws & (WHERE_IPK|WHERE_AUTO_INDEX))==0 - && pLevel->iIdxCur!=pWInfo->aiCurOnePass[1] - ){ - sqlite3VdbeAddOp1(v, OP_Close, pLevel->iIdxCur); - } - } -#endif - /* If this scan uses an index, make VDBE code substitutions to read data ** from the index instead of from the table where possible. In some cases ** this optimization prevents the table from ever being read, which can ** yield a significant performance boost. - ** + ** ** Calls to the code generator in between sqlite3WhereBegin and ** sqlite3WhereEnd will have created code that references the table ** directly. This loop scans all that code looking for opcodes @@ -145347,42 +176612,100 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){ if( pLoop->wsFlags & (WHERE_INDEXED|WHERE_IDX_ONLY) ){ pIdx = pLoop->u.btree.pIndex; }else if( pLoop->wsFlags & WHERE_MULTI_OR ){ - pIdx = pLevel->u.pCovidx; + pIdx = pLevel->u.pCoveringIdx; } if( pIdx - && (pWInfo->eOnePass==ONEPASS_OFF || !HasRowid(pIdx->pTable)) && !db->mallocFailed ){ - last = sqlite3VdbeCurrentAddr(v); - k = pLevel->addrBody; + if( pWInfo->eOnePass==ONEPASS_OFF || !HasRowid(pIdx->pTable) ){ + last = iEnd; + }else{ + last = pWInfo->iEndWhere; + } + if( pIdx->bHasExpr ){ + IndexedExpr *p = pParse->pIdxEpr; + while( p ){ + if( p->iIdxCur==pLevel->iIdxCur ){ +#ifdef WHERETRACE_ENABLED + if( sqlite3WhereTrace & 0x200 ){ + sqlite3DebugPrintf("Disable pParse->pIdxEpr term {%d,%d}\n", + p->iIdxCur, p->iIdxCol); + if( sqlite3WhereTrace & 0x5000 ) sqlite3ShowExpr(p->pExpr); + } +#endif + p->iDataCur = -1; + p->iIdxCur = -1; + } + p = p->pIENext; + } + } + k = pLevel->addrBody + 1; #ifdef SQLITE_DEBUG if( db->flags & SQLITE_VdbeAddopTrace ){ - printf("TRANSLATE opcodes in range %d..%d\n", k, last-1); + printf("TRANSLATE cursor %d->%d in opcode range %d..%d\n", + pLevel->iTabCur, pLevel->iIdxCur, k, last-1); } + /* Proof that the "+1" on the k value above is safe */ + pOp = sqlite3VdbeGetOp(v, k - 1); + assert( pOp->opcode!=OP_Column || pOp->p1!=pLevel->iTabCur ); + assert( pOp->opcode!=OP_Rowid || pOp->p1!=pLevel->iTabCur ); + assert( pOp->opcode!=OP_IfNullRow || pOp->p1!=pLevel->iTabCur ); #endif pOp = sqlite3VdbeGetOp(v, k); - for(; kp1!=pLevel->iTabCur ) continue; - if( pOp->opcode==OP_Column + pLastOp = pOp + (last - k); + assert( pOp<=pLastOp ); + do{ + if( pOp->p1!=pLevel->iTabCur ){ + /* no-op */ + }else if( pOp->opcode==OP_Column #ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC || pOp->opcode==OP_Offset #endif ){ int x = pOp->p2; assert( pIdx->pTable==pTab ); +#ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC + if( pOp->opcode==OP_Offset ){ + /* Do not need to translate the column number */ + }else +#endif if( !HasRowid(pTab) ){ Index *pPk = sqlite3PrimaryKeyIndex(pTab); x = pPk->aiColumn[x]; assert( x>=0 ); + }else{ + testcase( x!=sqlite3StorageColumnToTable(pTab,x) ); + x = sqlite3StorageColumnToTable(pTab,x); } - x = sqlite3ColumnOfIndex(pIdx, x); + x = sqlite3TableColumnToIndex(pIdx, x); if( x>=0 ){ pOp->p2 = x; pOp->p1 = pLevel->iIdxCur; OpcodeRewriteTrace(db, k, pOp); + }else if( pLoop->wsFlags & (WHERE_IDX_ONLY|WHERE_EXPRIDX) ){ + if( pLoop->wsFlags & WHERE_IDX_ONLY ){ + /* An error. pLoop is supposed to be a covering index loop, + ** and yet the VM code refers to a column of the table that + ** is not part of the index. */ + sqlite3ErrorMsg(pParse, "internal query planner error"); + pParse->rc = SQLITE_INTERNAL; + }else{ + /* The WHERE_EXPRIDX flag is set by the planner when it is likely + ** that pLoop is a covering index loop, but it is not possible + ** to be 100% sure. In this case, any OP_Explain opcode + ** corresponding to this loop describes the index as a "COVERING + ** INDEX". But, pOp proves that pLoop is not actually a covering + ** index loop. So clear the WHERE_EXPRIDX flag and rewrite the + ** text that accompanies the OP_Explain opcode, if any. */ + pLoop->wsFlags &= ~WHERE_EXPRIDX; + sqlite3WhereAddExplainText(pParse, + pLevel->addrBody-1, + pTabList, + pLevel, + pWInfo->wctrlFlags + ); + } } - assert( (pLoop->wsFlags & WHERE_IDX_ONLY)==0 || x>=0 - || pWInfo->eOnePass ); }else if( pOp->opcode==OP_Rowid ){ pOp->p1 = pLevel->iIdxCur; pOp->opcode = OP_IdxRowid; @@ -145391,17 +176714,26 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){ pOp->p1 = pLevel->iIdxCur; OpcodeRewriteTrace(db, k, pOp); } - } +#ifdef SQLITE_DEBUG + k++; +#endif + }while( (++pOp)flags & SQLITE_VdbeAddopTrace ) printf("TRANSLATE complete\n"); #endif } } + /* The "break" point is here, just past the end of the outer loop. + ** Set it. + */ + sqlite3VdbeResolveLabel(v, pWInfo->iBreak); + /* Final cleanup */ pParse->nQueryLoop = pWInfo->savedNQueryLoop; whereInfoFree(db, pWInfo); + pParse->withinRJSubrtn -= nRJ; return; } @@ -145449,12 +176781,12 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){ ** (in this case max()) to process rows sorted in order of (c, d), which ** makes things easier for obvious reasons. More generally: ** -** * FROM, WHERE, GROUP BY and HAVING clauses are all moved to +** * FROM, WHERE, GROUP BY and HAVING clauses are all moved to ** the sub-query. ** ** * ORDER BY, LIMIT and OFFSET remain part of the parent query. ** -** * Terminals from each of the expression trees that make up the +** * Terminals from each of the expression trees that make up the ** select-list and ORDER BY expressions in the parent query are ** selected by the sub-query. For the purposes of the transformation, ** terminals are column references and aggregate functions. @@ -145463,14 +176795,14 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){ ** the same window declaration (the OVER bit), then a single scan may ** be used to process more than one window function. For example: ** -** SELECT max(b) OVER (PARTITION BY c ORDER BY d), -** min(e) OVER (PARTITION BY c ORDER BY d) +** SELECT max(b) OVER (PARTITION BY c ORDER BY d), +** min(e) OVER (PARTITION BY c ORDER BY d) ** FROM t1; ** ** is transformed in the same way as the example above. However: ** -** SELECT max(b) OVER (PARTITION BY c ORDER BY d), -** min(e) OVER (PARTITION BY a ORDER BY b) +** SELECT max(b) OVER (PARTITION BY c ORDER BY d), +** min(e) OVER (PARTITION BY a ORDER BY b) ** FROM t1; ** ** Must be transformed to: @@ -145523,15 +176855,15 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){ ** first_value(expr) ** last_value(expr) ** nth_value(expr, N) -** -** These are the same built-in window functions supported by Postgres. +** +** These are the same built-in window functions supported by Postgres. ** Although the behaviour of aggregate window functions (functions that -** can be used as either aggregates or window funtions) allows them to +** can be used as either aggregates or window functions) allows them to ** be implemented using an API, built-in window functions are much more -** esoteric. Additionally, some window functions (e.g. nth_value()) +** esoteric. Additionally, some window functions (e.g. nth_value()) ** may only be implemented by caching the entire partition in memory. ** As such, some built-in window functions use the same API as aggregate -** window functions and some are implemented directly using VDBE +** window functions and some are implemented directly using VDBE ** instructions. Additionally, for those functions that use the API, the ** window frame is sometimes modified before the SELECT statement is ** rewritten. For example, regardless of the specified window frame, the @@ -145543,7 +176875,7 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){ ** ** As well as some of the built-in window functions, aggregate window ** functions min() and max() are implemented using VDBE instructions if -** the start of the window frame is declared as anything other than +** the start of the window frame is declared as anything other than ** UNBOUNDED PRECEDING. */ @@ -145554,7 +176886,7 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){ ** ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW */ static void row_numberStepFunc( - sqlite3_context *pCtx, + sqlite3_context *pCtx, int nArg, sqlite3_value **apArg ){ @@ -145582,10 +176914,10 @@ struct CallCount { ** Implementation of built-in window function dense_rank(). Assumes that ** the window frame has been set to: ** -** RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW +** RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW */ static void dense_rankStepFunc( - sqlite3_context *pCtx, + sqlite3_context *pCtx, int nArg, sqlite3_value **apArg ){ @@ -145617,7 +176949,7 @@ struct NthValueCtx { sqlite3_value *pValue; }; static void nth_valueStepFunc( - sqlite3_context *pCtx, + sqlite3_context *pCtx, int nArg, sqlite3_value **apArg ){ @@ -145631,7 +176963,7 @@ static void nth_valueStepFunc( break; case SQLITE_FLOAT: { double fVal = sqlite3_value_double(apArg[1]); - if( ((i64)fVal)!=fVal ) goto error_out; + if( sqlite3RealToI64(fVal)!=fVal ) goto error_out; iVal = (i64)fVal; break; } @@ -145670,7 +177002,7 @@ static void nth_valueFinalizeFunc(sqlite3_context *pCtx){ #define nth_valueValueFunc noopValueFunc static void first_valueStepFunc( - sqlite3_context *pCtx, + sqlite3_context *pCtx, int nArg, sqlite3_value **apArg ){ @@ -145701,10 +177033,10 @@ static void first_valueFinalizeFunc(sqlite3_context *pCtx){ ** Implementation of built-in window function rank(). Assumes that ** the window frame has been set to: ** -** RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW +** RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW */ static void rankStepFunc( - sqlite3_context *pCtx, + sqlite3_context *pCtx, int nArg, sqlite3_value **apArg ){ @@ -145735,7 +177067,7 @@ static void rankValueFunc(sqlite3_context *pCtx){ ** GROUPS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING */ static void percent_rankStepFunc( - sqlite3_context *pCtx, + sqlite3_context *pCtx, int nArg, sqlite3_value **apArg ){ @@ -145748,7 +177080,7 @@ static void percent_rankStepFunc( } } static void percent_rankInvFunc( - sqlite3_context *pCtx, + sqlite3_context *pCtx, int nArg, sqlite3_value **apArg ){ @@ -145780,7 +177112,7 @@ static void percent_rankValueFunc(sqlite3_context *pCtx){ ** GROUPS BETWEEN 1 FOLLOWING AND UNBOUNDED FOLLOWING */ static void cume_distStepFunc( - sqlite3_context *pCtx, + sqlite3_context *pCtx, int nArg, sqlite3_value **apArg ){ @@ -145793,7 +177125,7 @@ static void cume_distStepFunc( } } static void cume_distInvFunc( - sqlite3_context *pCtx, + sqlite3_context *pCtx, int nArg, sqlite3_value **apArg ){ @@ -145829,7 +177161,7 @@ struct NtileCtx { ** ROWS CURRENT ROW AND UNBOUNDED FOLLOWING */ static void ntileStepFunc( - sqlite3_context *pCtx, + sqlite3_context *pCtx, int nArg, sqlite3_value **apArg ){ @@ -145849,7 +177181,7 @@ static void ntileStepFunc( } } static void ntileInvFunc( - sqlite3_context *pCtx, + sqlite3_context *pCtx, int nArg, sqlite3_value **apArg ){ @@ -145895,7 +177227,7 @@ struct LastValueCtx { ** Implementation of last_value(). */ static void last_valueStepFunc( - sqlite3_context *pCtx, + sqlite3_context *pCtx, int nArg, sqlite3_value **apArg ){ @@ -145913,7 +177245,7 @@ static void last_valueStepFunc( } } static void last_valueInvFunc( - sqlite3_context *pCtx, + sqlite3_context *pCtx, int nArg, sqlite3_value **apArg ){ @@ -145990,7 +177322,7 @@ static void noopValueFunc(sqlite3_context *p){ UNUSED_PARAMETER(p); /*no-op*/ } /* Window functions that use all window interfaces: xStep, xFinal, ** xValue, and xInverse */ #define WINDOWFUNCALL(name,nArg,extra) { \ - nArg, (SQLITE_UTF8|SQLITE_FUNC_WINDOW|extra), 0, 0, \ + nArg, (SQLITE_FUNC_BUILTIN|SQLITE_UTF8|SQLITE_FUNC_WINDOW|extra), 0, 0, \ name ## StepFunc, name ## FinalizeFunc, name ## ValueFunc, \ name ## InvFunc, name ## Name, {0} \ } @@ -145998,7 +177330,7 @@ static void noopValueFunc(sqlite3_context *p){ UNUSED_PARAMETER(p); /*no-op*/ } /* Window functions that are implemented using bytecode and thus have ** no-op routines for their methods */ #define WINDOWFUNCNOOP(name,nArg,extra) { \ - nArg, (SQLITE_UTF8|SQLITE_FUNC_WINDOW|extra), 0, 0, \ + nArg, (SQLITE_FUNC_BUILTIN|SQLITE_UTF8|SQLITE_FUNC_WINDOW|extra), 0, 0, \ noopStepFunc, noopValueFunc, noopValueFunc, \ noopStepFunc, name ## Name, {0} \ } @@ -146007,7 +177339,7 @@ static void noopValueFunc(sqlite3_context *p){ UNUSED_PARAMETER(p); /*no-op*/ } ** same routine for xFinalize and xValue and which never call ** xInverse. */ #define WINDOWFUNCX(name,nArg,extra) { \ - nArg, (SQLITE_UTF8|SQLITE_FUNC_WINDOW|extra), 0, 0, \ + nArg, (SQLITE_FUNC_BUILTIN|SQLITE_UTF8|SQLITE_FUNC_WINDOW|extra), 0, 0, \ name ## StepFunc, name ## ValueFunc, name ## ValueFunc, \ noopStepFunc, name ## Name, {0} \ } @@ -146056,7 +177388,7 @@ static Window *windowFind(Parse *pParse, Window *pList, const char *zName){ ** is the Window object representing the associated OVER clause. This ** function updates the contents of pWin as follows: ** -** * If the OVER clause refered to a named window (as in "max(x) OVER win"), +** * If the OVER clause referred to a named window (as in "max(x) OVER win"), ** search list pList for a matching WINDOW definition, and update pWin ** accordingly. If no such WINDOW clause can be found, leave an error ** in pParse. @@ -146066,7 +177398,7 @@ static Window *windowFind(Parse *pParse, Window *pList, const char *zName){ ** of this file), pWin is updated here. */ SQLITE_PRIVATE void sqlite3WindowUpdate( - Parse *pParse, + Parse *pParse, Window *pList, /* List of named windows for this SELECT */ Window *pWin, /* Window frame to update */ FuncDef *pFunc /* Window function definition */ @@ -146086,17 +177418,17 @@ SQLITE_PRIVATE void sqlite3WindowUpdate( sqlite3WindowChain(pParse, pWin, pList); } if( (pWin->eFrmType==TK_RANGE) - && (pWin->pStart || pWin->pEnd) + && (pWin->pStart || pWin->pEnd) && (pWin->pOrderBy==0 || pWin->pOrderBy->nExpr!=1) ){ - sqlite3ErrorMsg(pParse, + sqlite3ErrorMsg(pParse, "RANGE with offset PRECEDING/FOLLOWING requires one ORDER BY expression" ); }else if( pFunc->funcFlags & SQLITE_FUNC_WINDOW ){ sqlite3 *db = pParse->db; if( pWin->pFilter ){ - sqlite3ErrorMsg(pParse, + sqlite3ErrorMsg(pParse, "FILTER clause may only be used with aggregate window functions" ); }else{ @@ -146106,14 +177438,14 @@ SQLITE_PRIVATE void sqlite3WindowUpdate( int eStart; int eEnd; } aUp[] = { - { row_numberName, TK_ROWS, TK_UNBOUNDED, TK_CURRENT }, - { dense_rankName, TK_RANGE, TK_UNBOUNDED, TK_CURRENT }, - { rankName, TK_RANGE, TK_UNBOUNDED, TK_CURRENT }, - { percent_rankName, TK_GROUPS, TK_CURRENT, TK_UNBOUNDED }, - { cume_distName, TK_GROUPS, TK_FOLLOWING, TK_UNBOUNDED }, - { ntileName, TK_ROWS, TK_CURRENT, TK_UNBOUNDED }, - { leadName, TK_ROWS, TK_UNBOUNDED, TK_UNBOUNDED }, - { lagName, TK_ROWS, TK_UNBOUNDED, TK_CURRENT }, + { row_numberName, TK_ROWS, TK_UNBOUNDED, TK_CURRENT }, + { dense_rankName, TK_RANGE, TK_UNBOUNDED, TK_CURRENT }, + { rankName, TK_RANGE, TK_UNBOUNDED, TK_CURRENT }, + { percent_rankName, TK_GROUPS, TK_CURRENT, TK_UNBOUNDED }, + { cume_distName, TK_GROUPS, TK_FOLLOWING, TK_UNBOUNDED }, + { ntileName, TK_ROWS, TK_CURRENT, TK_UNBOUNDED }, + { leadName, TK_ROWS, TK_UNBOUNDED, TK_UNBOUNDED }, + { lagName, TK_ROWS, TK_UNBOUNDED, TK_CURRENT }, }; int i; for(i=0; ieEnd = aUp[i].eEnd; pWin->eExclude = 0; if( pWin->eStart==TK_FOLLOWING ){ - pWin->pStart = sqlite3Expr(db, TK_INTEGER, "1"); + pWin->pStart = sqlite3ExprInt32(db, 1); } break; } } } } - pWin->pFunc = pFunc; + pWin->pWFunc = pFunc; } /* @@ -146145,17 +177477,20 @@ struct WindowRewrite { Window *pWin; SrcList *pSrc; ExprList *pSub; + Table *pTab; Select *pSubSelect; /* Current sub-select, if any */ }; /* ** Callback function used by selectWindowRewriteEList(). If necessary, -** this function appends to the output expression-list and updates +** this function appends to the output expression-list and updates ** expression (*ppExpr) in place. */ static int selectWindowRewriteExprCb(Walker *pWalker, Expr *pExpr){ struct WindowRewrite *p = pWalker->u.pRewrite; Parse *pParse = pWalker->pParse; + assert( p!=0 ); + assert( p->pWin!=0 ); /* If this function is being called from within a scalar sub-select ** that used by the SELECT statement being processed, only process @@ -146189,13 +177524,29 @@ static int selectWindowRewriteExprCb(Walker *pWalker, Expr *pExpr){ } } } - /* Fall through. */ + /* no break */ deliberate_fall_through + case TK_IF_NULL_ROW: case TK_AGG_FUNCTION: case TK_COLUMN: { - Expr *pDup = sqlite3ExprDup(pParse->db, pExpr, 0); - p->pSub = sqlite3ExprListAppend(pParse, p->pSub, pDup); + int iCol = -1; + if( pParse->db->mallocFailed ) return WRC_Abort; + if( p->pSub ){ + int i; + for(i=0; ipSub->nExpr; i++){ + if( 0==sqlite3ExprCompare(0, p->pSub->a[i].pExpr, pExpr, -1) ){ + iCol = i; + break; + } + } + } + if( iCol<0 ){ + Expr *pDup = sqlite3ExprDup(pParse->db, pExpr, 0); + if( pDup && pDup->op==TK_AGG_FUNCTION ) pDup->op = TK_FUNCTION; + p->pSub = sqlite3ExprListAppend(pParse, p->pSub, pDup); + } if( p->pSub ){ + int f = pExpr->flags & EP_Collate; assert( ExprHasProperty(pExpr, EP_Static)==0 ); ExprSetProperty(pExpr, EP_Static); sqlite3ExprDelete(pParse->db, pExpr); @@ -146203,10 +177554,12 @@ static int selectWindowRewriteExprCb(Walker *pWalker, Expr *pExpr){ memset(pExpr, 0, sizeof(Expr)); pExpr->op = TK_COLUMN; - pExpr->iColumn = p->pSub->nExpr-1; + pExpr->iColumn = (iCol<0 ? p->pSub->nExpr-1: iCol); pExpr->iTable = p->pWin->iEphCsr; + pExpr->y.pTab = p->pTab; + pExpr->flags = f; } - + if( pParse->db->mallocFailed ) return WRC_Abort; break; } @@ -146235,30 +177588,33 @@ static int selectWindowRewriteSelectCb(Walker *pWalker, Select *pSelect){ ** ** * TK_COLUMN, ** * aggregate function, or -** * window function with a Window object that is not a member of the +** * window function with a Window object that is not a member of the ** Window list passed as the second argument (pWin). ** ** Append the node to output expression-list (*ppSub). And replace it -** with a TK_COLUMN that reads the (N-1)th element of table +** with a TK_COLUMN that reads the (N-1)th element of table ** pWin->iEphCsr, where N is the number of elements in (*ppSub) after ** appending the new one. */ static void selectWindowRewriteEList( - Parse *pParse, + Parse *pParse, Window *pWin, SrcList *pSrc, ExprList *pEList, /* Rewrite expressions in this list */ + Table *pTab, ExprList **ppSub /* IN/OUT: Sub-select expression-list */ ){ Walker sWalker; WindowRewrite sRewrite; + assert( pWin!=0 ); memset(&sWalker, 0, sizeof(Walker)); memset(&sRewrite, 0, sizeof(WindowRewrite)); sRewrite.pSub = *ppSub; sRewrite.pWin = pWin; sRewrite.pSrc = pSrc; + sRewrite.pTab = pTab; sWalker.pParse = pParse; sWalker.xExprCallback = selectWindowRewriteExprCb; @@ -146277,30 +177633,76 @@ static void selectWindowRewriteEList( static ExprList *exprListAppendList( Parse *pParse, /* Parsing context */ ExprList *pList, /* List to which to append. Might be NULL */ - ExprList *pAppend /* List of values to append. Might be NULL */ + ExprList *pAppend, /* List of values to append. Might be NULL */ + int bIntToNull ){ if( pAppend ){ int i; int nInit = pList ? pList->nExpr : 0; for(i=0; inExpr; i++){ - Expr *pDup = sqlite3ExprDup(pParse->db, pAppend->a[i].pExpr, 0); + sqlite3 *db = pParse->db; + Expr *pDup = sqlite3ExprDup(db, pAppend->a[i].pExpr, 0); + if( db->mallocFailed ){ + sqlite3ExprDelete(db, pDup); + break; + } + if( bIntToNull ){ + int iDummy; + Expr *pSub; + pSub = sqlite3ExprSkipCollateAndLikely(pDup); + if( sqlite3ExprIsInteger(pSub, &iDummy, 0) ){ + pSub->op = TK_NULL; + pSub->flags &= ~(EP_IntValue|EP_IsTrue|EP_IsFalse); + pSub->u.zToken = 0; + } + } pList = sqlite3ExprListAppend(pParse, pList, pDup); - if( pList ) pList->a[nInit+i].sortOrder = pAppend->a[i].sortOrder; + if( pList ) pList->a[nInit+i].fg.sortFlags = pAppend->a[i].fg.sortFlags; } } return pList; } +/* +** When rewriting a query, if the new subquery in the FROM clause +** contains TK_AGG_FUNCTION nodes that refer to an outer query, +** then we have to increase the Expr->op2 values of those nodes +** due to the extra subquery layer that was added. +** +** See also the incrAggDepth() routine in resolve.c +*/ +static int sqlite3WindowExtraAggFuncDepth(Walker *pWalker, Expr *pExpr){ + if( pExpr->op==TK_AGG_FUNCTION + && pExpr->op2>=pWalker->walkerDepth + ){ + pExpr->op2++; + } + return WRC_Continue; +} + +static int disallowAggregatesInOrderByCb(Walker *pWalker, Expr *pExpr){ + if( pExpr->op==TK_AGG_FUNCTION && pExpr->pAggInfo==0 ){ + assert( !ExprHasProperty(pExpr, EP_IntValue) ); + sqlite3ErrorMsg(pWalker->pParse, + "misuse of aggregate: %s()", pExpr->u.zToken); + } + return WRC_Continue; +} + /* ** If the SELECT statement passed as the second argument does not invoke -** any SQL window functions, this function is a no-op. Otherwise, it +** any SQL window functions, this function is a no-op. Otherwise, it ** rewrites the SELECT statement so that window function xStep functions ** are invoked in the correct order as described under "SELECT REWRITING" ** at the top of this file. */ SQLITE_PRIVATE int sqlite3WindowRewrite(Parse *pParse, Select *p){ int rc = SQLITE_OK; - if( p->pWin && p->pPrior==0 ){ + if( p->pWin + && p->pPrior==0 + && ALWAYS((p->selFlags & SF_WinRewrite)==0) + && ALWAYS(!IN_RENAME_OBJECT) + ){ Vdbe *v = sqlite3GetVdbe(pParse); sqlite3 *db = pParse->db; Select *pSub = 0; /* The subquery */ @@ -146311,24 +177713,45 @@ SQLITE_PRIVATE int sqlite3WindowRewrite(Parse *pParse, Select *p){ ExprList *pSort = 0; ExprList *pSublist = 0; /* Expression list for sub-query */ - Window *pMWin = p->pWin; /* Master window object */ + Window *pMWin = p->pWin; /* Main window object */ Window *pWin; /* Window object iterator */ + Table *pTab; + Walker w; + + u32 selFlags = p->selFlags; + + pTab = sqlite3DbMallocZero(db, sizeof(Table)); + if( pTab==0 ){ + return sqlite3ErrorToParser(db, SQLITE_NOMEM); + } + sqlite3AggInfoPersistWalkerInit(&w, pParse); + sqlite3WalkSelect(&w, p); + if( (p->selFlags & SF_Aggregate)==0 ){ + w.xExprCallback = disallowAggregatesInOrderByCb; + w.xSelectCallback = 0; + sqlite3WalkExprList(&w, p->pOrderBy); + } p->pSrc = 0; p->pWhere = 0; p->pGroupBy = 0; p->pHaving = 0; + p->selFlags &= ~(u32)SF_Aggregate; + p->selFlags |= SF_WinRewrite; /* Create the ORDER BY clause for the sub-select. This is the concatenation ** of the window PARTITION and ORDER BY clauses. Then, if this makes it ** redundant, remove the ORDER BY from the parent SELECT. */ - pSort = sqlite3ExprListDup(db, pMWin->pPartition, 0); - pSort = exprListAppendList(pParse, pSort, pMWin->pOrderBy); - if( pSort && p->pOrderBy ){ + pSort = exprListAppendList(pParse, 0, pMWin->pPartition, 1); + pSort = exprListAppendList(pParse, pSort, pMWin->pOrderBy, 1); + if( pSort && p->pOrderBy && p->pOrderBy->nExpr<=pSort->nExpr ){ + int nSave = pSort->nExpr; + pSort->nExpr = p->pOrderBy->nExpr; if( sqlite3ExprListCompare(pSort, p->pOrderBy, -1)==0 ){ sqlite3ExprListDelete(db, p->pOrderBy); p->pOrderBy = 0; } + pSort->nExpr = nSave; } /* Assign a cursor number for the ephemeral table used to buffer rows. @@ -146337,23 +177760,33 @@ SQLITE_PRIVATE int sqlite3WindowRewrite(Parse *pParse, Select *p){ pMWin->iEphCsr = pParse->nTab++; pParse->nTab += 3; - selectWindowRewriteEList(pParse, pMWin, pSrc, p->pEList, &pSublist); - selectWindowRewriteEList(pParse, pMWin, pSrc, p->pOrderBy, &pSublist); + selectWindowRewriteEList(pParse, pMWin, pSrc, p->pEList, pTab, &pSublist); + selectWindowRewriteEList(pParse, pMWin, pSrc, p->pOrderBy, pTab, &pSublist); pMWin->nBufferCol = (pSublist ? pSublist->nExpr : 0); - /* Append the PARTITION BY and ORDER BY expressions to the to the - ** sub-select expression list. They are required to figure out where + /* Append the PARTITION BY and ORDER BY expressions to the to the + ** sub-select expression list. They are required to figure out where ** boundaries for partitions and sets of peer rows lie. */ - pSublist = exprListAppendList(pParse, pSublist, pMWin->pPartition); - pSublist = exprListAppendList(pParse, pSublist, pMWin->pOrderBy); + pSublist = exprListAppendList(pParse, pSublist, pMWin->pPartition, 0); + pSublist = exprListAppendList(pParse, pSublist, pMWin->pOrderBy, 0); /* Append the arguments passed to each window function to the ** sub-select expression list. Also allocate two registers for each ** window function - one for the accumulator, another for interim ** results. */ for(pWin=pMWin; pWin; pWin=pWin->pNextWin){ - pWin->iArgCol = (pSublist ? pSublist->nExpr : 0); - pSublist = exprListAppendList(pParse, pSublist, pWin->pOwner->x.pList); + ExprList *pArgs; + assert( ExprUseXList(pWin->pOwner) ); + assert( pWin->pWFunc!=0 ); + pArgs = pWin->pOwner->x.pList; + if( pWin->pWFunc->funcFlags & SQLITE_SUBTYPE ){ + selectWindowRewriteEList(pParse, pMWin, pSrc, pArgs, pTab, &pSublist); + pWin->iArgCol = (pSublist ? pSublist->nExpr : 0); + pWin->bExprArgs = 1; + }else{ + pWin->iArgCol = (pSublist ? pSublist->nExpr : 0); + pSublist = exprListAppendList(pParse, pSublist, pArgs, 0); + } if( pWin->pFilter ){ Expr *pFilter = sqlite3ExprDup(db, pWin->pFilter, 0); pSublist = sqlite3ExprListAppend(pParse, pSublist, pFilter); @@ -146366,48 +177799,79 @@ SQLITE_PRIVATE int sqlite3WindowRewrite(Parse *pParse, Select *p){ /* If there is no ORDER BY or PARTITION BY clause, and the window ** function accepts zero arguments, and there are no other columns ** selected (e.g. "SELECT row_number() OVER () FROM t1"), it is possible - ** that pSublist is still NULL here. Add a constant expression here to - ** keep everything legal in this case. + ** that pSublist is still NULL here. Add a constant expression here to + ** keep everything legal in this case. */ if( pSublist==0 ){ - pSublist = sqlite3ExprListAppend(pParse, 0, - sqlite3ExprAlloc(db, TK_INTEGER, &sqlite3IntTokens[0], 0) - ); + pSublist = sqlite3ExprListAppend(pParse, 0, sqlite3ExprInt32(db, 0)); } pSub = sqlite3SelectNew( pParse, pSublist, pSrc, pWhere, pGroupBy, pHaving, pSort, 0, 0 ); + TREETRACE(0x40,pParse,pSub, + ("New window-function subquery in FROM clause of (%u/%p)\n", + p->selId, p)); p->pSrc = sqlite3SrcListAppend(pParse, 0, 0, 0); - if( p->pSrc ){ - p->pSrc->a[0].pSelect = pSub; + assert( pSub!=0 || p->pSrc==0 ); /* Due to db->mallocFailed test inside + ** of sqlite3DbMallocRawNN() called from + ** sqlite3SrcListAppend() */ + if( p->pSrc==0 ){ + sqlite3SelectDelete(db, pSub); + }else if( sqlite3SrcItemAttachSubquery(pParse, &p->pSrc->a[0], pSub, 0) ){ + Table *pTab2; + p->pSrc->a[0].fg.isCorrelated = 1; sqlite3SrcListAssignCursors(pParse, p->pSrc); - if( sqlite3ExpandSubquery(pParse, &p->pSrc->a[0]) ){ + pSub->selFlags |= SF_Expanded|SF_OrderByReqd; + pTab2 = sqlite3ResultSetOfSelect(pParse, pSub, SQLITE_AFF_NONE); + pSub->selFlags |= (selFlags & SF_Aggregate); + if( pTab2==0 ){ + /* Might actually be some other kind of error, but in that case + ** pParse->nErr will be set, so if SQLITE_NOMEM is set, we will get + ** the correct error message regardless. */ rc = SQLITE_NOMEM; }else{ - pSub->selFlags |= SF_Expanded; - p->selFlags &= ~SF_Aggregate; - sqlite3SelectPrep(pParse, pSub, 0); + memcpy(pTab, pTab2, sizeof(Table)); + pTab->tabFlags |= TF_Ephemeral; + p->pSrc->a[0].pSTab = pTab; + pTab = pTab2; + memset(&w, 0, sizeof(w)); + w.xExprCallback = sqlite3WindowExtraAggFuncDepth; + w.xSelectCallback = sqlite3WalkerDepthIncrease; + w.xSelectCallback2 = sqlite3WalkerDepthDecrease; + sqlite3WalkSelect(&w, pSub); } - - sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pMWin->iEphCsr, pSublist->nExpr); - sqlite3VdbeAddOp2(v, OP_OpenDup, pMWin->iEphCsr+1, pMWin->iEphCsr); - sqlite3VdbeAddOp2(v, OP_OpenDup, pMWin->iEphCsr+2, pMWin->iEphCsr); - sqlite3VdbeAddOp2(v, OP_OpenDup, pMWin->iEphCsr+3, pMWin->iEphCsr); - }else{ - sqlite3SelectDelete(db, pSub); } if( db->mallocFailed ) rc = SQLITE_NOMEM; + + /* Defer deleting the temporary table pTab because if an error occurred, + ** there could still be references to that table embedded in the + ** result-set or ORDER BY clause of the SELECT statement p. */ + sqlite3ParserAddCleanup(pParse, sqlite3DbFree, pTab); } + assert( rc==SQLITE_OK || pParse->nErr!=0 ); return rc; } +/* +** Unlink the Window object from the Select to which it is attached, +** if it is attached. +*/ +SQLITE_PRIVATE void sqlite3WindowUnlinkFromSelect(Window *p){ + if( p->ppThis ){ + *p->ppThis = p->pNextWin; + if( p->pNextWin ) p->pNextWin->ppThis = p->ppThis; + p->ppThis = 0; + } +} + /* ** Free the Window object passed as the second argument. */ SQLITE_PRIVATE void sqlite3WindowDelete(sqlite3 *db, Window *p){ if( p ){ + sqlite3WindowUnlinkFromSelect(p); sqlite3ExprDelete(db, p->pFilter); sqlite3ExprListDelete(db, p->pPartition); sqlite3ExprListDelete(db, p->pOrderBy); @@ -146438,7 +177902,7 @@ SQLITE_PRIVATE void sqlite3WindowListDelete(sqlite3 *db, Window *p){ ** variable values in the expression tree. */ static Expr *sqlite3WindowOffsetExpr(Parse *pParse, Expr *pExpr){ - if( 0==sqlite3ExprIsConstant(pExpr) ){ + if( 0==sqlite3ExprIsConstant(0,pExpr) ){ if( IN_RENAME_OBJECT ) sqlite3RenameExprUnmap(pParse, pExpr); sqlite3ExprDelete(pParse->db, pExpr); pExpr = sqlite3ExprAlloc(pParse->db, TK_NULL, 0, 0); @@ -146522,10 +177986,10 @@ SQLITE_PRIVATE Window *sqlite3WindowAlloc( ** equivalent nul-terminated string. */ SQLITE_PRIVATE Window *sqlite3WindowAssemble( - Parse *pParse, - Window *pWin, - ExprList *pPartition, - ExprList *pOrderBy, + Parse *pParse, + Window *pWin, + ExprList *pPartition, + ExprList *pOrderBy, Token *pBase ){ if( pWin ){ @@ -146542,7 +178006,7 @@ SQLITE_PRIVATE Window *sqlite3WindowAssemble( } /* -** Window *pWin has just been created from a WINDOW clause. Tokne pBase +** Window *pWin has just been created from a WINDOW clause. Token pBase ** is the base window. Earlier windows from the same WINDOW clause are ** stored in the linked list starting at pWin->pNextWin. This function ** either updates *pWin according to the base specification, or else @@ -146563,7 +178027,7 @@ SQLITE_PRIVATE void sqlite3WindowChain(Parse *pParse, Window *pWin, Window *pLis zErr = "frame specification"; } if( zErr ){ - sqlite3ErrorMsg(pParse, + sqlite3ErrorMsg(pParse, "cannot override %s of window: %s", zErr, pWin->zBase ); }else{ @@ -146585,17 +178049,15 @@ SQLITE_PRIVATE void sqlite3WindowChain(Parse *pParse, Window *pWin, Window *pLis SQLITE_PRIVATE void sqlite3WindowAttach(Parse *pParse, Expr *p, Window *pWin){ if( p ){ assert( p->op==TK_FUNCTION ); - /* This routine is only called for the parser. If pWin was not - ** allocated due to an OOM, then the parser would fail before ever - ** invoking this routine */ - if( ALWAYS(pWin) ){ - p->y.pWin = pWin; - ExprSetProperty(p, EP_WinFunc); - pWin->pOwner = p; - if( p->flags & EP_Distinct ){ - sqlite3ErrorMsg(pParse, - "DISTINCT is not supported for window functions"); - } + assert( pWin ); + assert( ExprIsFullSize(p) ); + p->y.pWin = pWin; + ExprSetProperty(p, EP_WinFunc|EP_FullSize); + pWin->pOwner = p; + if( (p->flags & EP_Distinct) && pWin->eFrmType!=TK_FILTER ){ + sqlite3ErrorMsg(pParse, + "DISTINCT is not supported for window functions" + ); } }else{ sqlite3WindowDelete(pParse->db, pWin); @@ -146603,18 +178065,58 @@ SQLITE_PRIVATE void sqlite3WindowAttach(Parse *pParse, Expr *p, Window *pWin){ } /* -** Return 0 if the two window objects are identical, or non-zero otherwise. -** Identical window objects can be processed in a single scan. +** Possibly link window pWin into the list at pSel->pWin (window functions +** to be processed as part of SELECT statement pSel). The window is linked +** in if either (a) there are no other windows already linked to this +** SELECT, or (b) the windows already linked use a compatible window frame. +*/ +SQLITE_PRIVATE void sqlite3WindowLink(Select *pSel, Window *pWin){ + if( pSel ){ + if( 0==pSel->pWin || 0==sqlite3WindowCompare(0, pSel->pWin, pWin, 0) ){ + pWin->pNextWin = pSel->pWin; + if( pSel->pWin ){ + pSel->pWin->ppThis = &pWin->pNextWin; + } + pSel->pWin = pWin; + pWin->ppThis = &pSel->pWin; + }else{ + if( sqlite3ExprListCompare(pWin->pPartition, pSel->pWin->pPartition,-1) ){ + pSel->selFlags |= SF_MultiPart; + } + } + } +} + +/* +** Return 0 if the two window objects are identical, 1 if they are +** different, or 2 if it cannot be determined if the objects are identical +** or not. Identical window objects can be processed in a single scan. */ -SQLITE_PRIVATE int sqlite3WindowCompare(Parse *pParse, Window *p1, Window *p2){ +SQLITE_PRIVATE int sqlite3WindowCompare( + const Parse *pParse, + const Window *p1, + const Window *p2, + int bFilter +){ + int res; + if( NEVER(p1==0) || NEVER(p2==0) ) return 1; if( p1->eFrmType!=p2->eFrmType ) return 1; if( p1->eStart!=p2->eStart ) return 1; if( p1->eEnd!=p2->eEnd ) return 1; if( p1->eExclude!=p2->eExclude ) return 1; if( sqlite3ExprCompare(pParse, p1->pStart, p2->pStart, -1) ) return 1; if( sqlite3ExprCompare(pParse, p1->pEnd, p2->pEnd, -1) ) return 1; - if( sqlite3ExprListCompare(p1->pPartition, p2->pPartition, -1) ) return 1; - if( sqlite3ExprListCompare(p1->pOrderBy, p2->pOrderBy, -1) ) return 1; + if( (res = sqlite3ExprListCompare(p1->pPartition, p2->pPartition, -1)) ){ + return res; + } + if( (res = sqlite3ExprListCompare(p1->pOrderBy, p2->pOrderBy, -1)) ){ + return res; + } + if( bFilter ){ + if( (res = sqlite3ExprCompare(pParse, p1->pFilter, p2->pFilter, -1)) ){ + return res; + } + } return 0; } @@ -146624,9 +178126,21 @@ SQLITE_PRIVATE int sqlite3WindowCompare(Parse *pParse, Window *p1, Window *p2){ ** to begin iterating through the sub-query results. It is used to allocate ** and initialize registers and cursors used by sqlite3WindowCodeStep(). */ -SQLITE_PRIVATE void sqlite3WindowCodeInit(Parse *pParse, Window *pMWin){ +SQLITE_PRIVATE void sqlite3WindowCodeInit(Parse *pParse, Select *pSelect){ Window *pWin; - Vdbe *v = sqlite3GetVdbe(pParse); + int nEphExpr; + Window *pMWin; + Vdbe *v; + + assert( pSelect->pSrc->a[0].fg.isSubquery ); + nEphExpr = pSelect->pSrc->a[0].u4.pSubq->pSelect->pEList->nExpr; + pMWin = pSelect->pWin; + v = sqlite3GetVdbe(pParse); + + sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pMWin->iEphCsr, nEphExpr); + sqlite3VdbeAddOp2(v, OP_OpenDup, pMWin->iEphCsr+1, pMWin->iEphCsr); + sqlite3VdbeAddOp2(v, OP_OpenDup, pMWin->iEphCsr+2, pMWin->iEphCsr); + sqlite3VdbeAddOp2(v, OP_OpenDup, pMWin->iEphCsr+3, pMWin->iEphCsr); /* Allocate registers to use for PARTITION BY values, if any. Initialize ** said registers to NULL. */ @@ -146651,7 +178165,7 @@ SQLITE_PRIVATE void sqlite3WindowCodeInit(Parse *pParse, Window *pMWin){ } for(pWin=pMWin; pWin; pWin=pWin->pNextWin){ - FuncDef *p = pWin->pFunc; + FuncDef *p = pWin->pWFunc; if( (p->funcFlags & SQLITE_FUNC_MINMAX) && pWin->eStart!=TK_UNBOUNDED ){ /* The inline versions of min() and max() require a single ephemeral ** table and 3 registers. The registers are used as follows: @@ -146660,14 +178174,17 @@ SQLITE_PRIVATE void sqlite3WindowCodeInit(Parse *pParse, Window *pMWin){ ** regApp+1: integer value used to ensure keys are unique ** regApp+2: output of MakeRecord */ - ExprList *pList = pWin->pOwner->x.pList; - KeyInfo *pKeyInfo = sqlite3KeyInfoFromExprList(pParse, pList, 0, 0); + ExprList *pList; + KeyInfo *pKeyInfo; + assert( ExprUseXList(pWin->pOwner) ); + pList = pWin->pOwner->x.pList; + pKeyInfo = sqlite3KeyInfoFromExprList(pParse, pList, 0, 0); pWin->csrApp = pParse->nTab++; pWin->regApp = pParse->nMem+1; pParse->nMem += 3; - if( pKeyInfo && pWin->pFunc->zName[1]=='i' ){ - assert( pKeyInfo->aSortOrder[0]==0 ); - pKeyInfo->aSortOrder[0] = 1; + if( pKeyInfo && pWin->pWFunc->zName[1]=='i' ){ + assert( pKeyInfo->aSortFlags[0]==0 ); + pKeyInfo->aSortFlags[0] = KEYINFO_ORDER_DESC; } sqlite3VdbeAddOp2(v, OP_OpenEphemeral, pWin->csrApp, 2); sqlite3VdbeAppendP4(v, pKeyInfo, P4_KEYINFO); @@ -146732,6 +178249,7 @@ static void windowCheckValue(Parse *pParse, int reg, int eCond){ VdbeCoverageIf(v, eCond==2); } sqlite3VdbeAddOp3(v, aOp[eCond], regZero, sqlite3VdbeCurrentAddr(v)+2, reg); + sqlite3VdbeChangeP5(v, SQLITE_AFF_NUMERIC); VdbeCoverageNeverNullIf(v, eCond==0); /* NULL case captured by */ VdbeCoverageNeverNullIf(v, eCond==1); /* the OP_MustBeInt */ VdbeCoverageNeverNullIf(v, eCond==2); @@ -146748,13 +178266,118 @@ static void windowCheckValue(Parse *pParse, int reg, int eCond){ ** with the object passed as the only argument to this function. */ static int windowArgCount(Window *pWin){ - ExprList *pList = pWin->pOwner->x.pList; + const ExprList *pList; + assert( ExprUseXList(pWin->pOwner) ); + pList = pWin->pOwner->x.pList; return (pList ? pList->nExpr : 0); } +typedef struct WindowCodeArg WindowCodeArg; +typedef struct WindowCsrAndReg WindowCsrAndReg; + +/* +** See comments above struct WindowCodeArg. +*/ +struct WindowCsrAndReg { + int csr; /* Cursor number */ + int reg; /* First in array of peer values */ +}; + +/* +** A single instance of this structure is allocated on the stack by +** sqlite3WindowCodeStep() and a pointer to it passed to the various helper +** routines. This is to reduce the number of arguments required by each +** helper function. +** +** regArg: +** Each window function requires an accumulator register (just as an +** ordinary aggregate function does). This variable is set to the first +** in an array of accumulator registers - one for each window function +** in the WindowCodeArg.pMWin list. +** +** eDelete: +** The window functions implementation sometimes caches the input rows +** that it processes in a temporary table. If it is not zero, this +** variable indicates when rows may be removed from the temp table (in +** order to reduce memory requirements - it would always be safe just +** to leave them there). Possible values for eDelete are: +** +** WINDOW_RETURN_ROW: +** An input row can be discarded after it is returned to the caller. +** +** WINDOW_AGGINVERSE: +** An input row can be discarded after the window functions xInverse() +** callbacks have been invoked in it. +** +** WINDOW_AGGSTEP: +** An input row can be discarded after the window functions xStep() +** callbacks have been invoked in it. +** +** start,current,end +** Consider a window-frame similar to the following: +** +** (ORDER BY a, b GROUPS BETWEEN 2 PRECEDING AND 2 FOLLOWING) +** +** The windows functions implementation caches the input rows in a temp +** table, sorted by "a, b" (it actually populates the cache lazily, and +** aggressively removes rows once they are no longer required, but that's +** a mere detail). It keeps three cursors open on the temp table. One +** (current) that points to the next row to return to the query engine +** once its window function values have been calculated. Another (end) +** points to the next row to call the xStep() method of each window function +** on (so that it is 2 groups ahead of current). And a third (start) that +** points to the next row to call the xInverse() method of each window +** function on. +** +** Each cursor (start, current and end) consists of a VDBE cursor +** (WindowCsrAndReg.csr) and an array of registers (starting at +** WindowCodeArg.reg) that always contains a copy of the peer values +** read from the corresponding cursor. +** +** Depending on the window-frame in question, all three cursors may not +** be required. In this case both WindowCodeArg.csr and reg are set to +** 0. +*/ +struct WindowCodeArg { + Parse *pParse; /* Parse context */ + Window *pMWin; /* First in list of functions being processed */ + Vdbe *pVdbe; /* VDBE object */ + int addrGosub; /* OP_Gosub to this address to return one row */ + int regGosub; /* Register used with OP_Gosub(addrGosub) */ + int regArg; /* First in array of accumulator registers */ + int eDelete; /* See above */ + int regRowid; + + WindowCsrAndReg start; + WindowCsrAndReg current; + WindowCsrAndReg end; +}; + +/* +** Generate VM code to read the window frames peer values from cursor csr into +** an array of registers starting at reg. +*/ +static void windowReadPeerValues( + WindowCodeArg *p, + int csr, + int reg +){ + Window *pMWin = p->pMWin; + ExprList *pOrderBy = pMWin->pOrderBy; + if( pOrderBy ){ + Vdbe *v = sqlite3GetVdbe(p->pParse); + ExprList *pPart = pMWin->pPartition; + int iColOff = pMWin->nBufferCol + (pPart ? pPart->nExpr : 0); + int i; + for(i=0; inExpr; i++){ + sqlite3VdbeAddOp3(v, OP_Column, csr, iColOff+i, reg+i); + } + } +} + /* -** Generate VM code to invoke either xStep() (if bInverse is 0) or -** xInverse (if bInverse is non-zero) for each window function in the +** Generate VM code to invoke either xStep() (if bInverse is 0) or +** xInverse (if bInverse is non-zero) for each window function in the ** linked list starting at pMWin. Or, for built-in window functions ** that do not use the standard function API, generate the required ** inline VM code. @@ -146772,19 +178395,27 @@ static int windowArgCount(Window *pWin){ ** number of rows in the current partition. */ static void windowAggStep( - Parse *pParse, + WindowCodeArg *p, Window *pMWin, /* Linked list of window functions */ int csr, /* Read arguments from this cursor */ int bInverse, /* True to invoke xInverse instead of xStep */ int reg /* Array of registers */ ){ + Parse *pParse = p->pParse; Vdbe *v = sqlite3GetVdbe(pParse); Window *pWin; for(pWin=pMWin; pWin; pWin=pWin->pNextWin){ - FuncDef *pFunc = pWin->pFunc; + FuncDef *pFunc = pWin->pWFunc; int regArg; - int nArg = windowArgCount(pWin); + int nArg = pWin->bExprArgs ? 0 : windowArgCount(pWin); int i; + int addrIf = 0; + + assert( bInverse==0 || pWin->eStart!=TK_UNBOUNDED ); + + /* All OVER clauses in the same window function aggregate step must + ** be the same. */ + assert( pWin==pMWin || sqlite3WindowCompare(pParse,pWin,pMWin,0)!=1 ); for(i=0; izName!=nth_valueName ){ @@ -146795,8 +178426,20 @@ static void windowAggStep( } regArg = reg; + if( pWin->pFilter ){ + int regTmp; + assert( ExprUseXList(pWin->pOwner) ); + assert( pWin->bExprArgs || !nArg ||nArg==pWin->pOwner->x.pList->nExpr ); + assert( pWin->bExprArgs || nArg ||pWin->pOwner->x.pList==0 ); + regTmp = sqlite3GetTempReg(pParse); + sqlite3VdbeAddOp3(v, OP_Column, csr, pWin->iArgCol+nArg,regTmp); + addrIf = sqlite3VdbeAddOp3(v, OP_IfNot, regTmp, 0, 1); + VdbeCoverage(v); + sqlite3ReleaseTempReg(pParse, regTmp); + } + if( pMWin->regStartRowid==0 - && (pFunc->funcFlags & SQLITE_FUNC_MINMAX) + && (pFunc->funcFlags & SQLITE_FUNC_MINMAX) && (pWin->eStart!=TK_UNBOUNDED) ){ int addrIsNull = sqlite3VdbeAddOp1(v, OP_IsNull, regArg); @@ -146814,59 +178457,49 @@ static void windowAggStep( } sqlite3VdbeJumpHere(v, addrIsNull); }else if( pWin->regApp ){ + assert( pWin->pFilter==0 ); assert( pFunc->zName==nth_valueName || pFunc->zName==first_valueName ); assert( bInverse==0 || bInverse==1 ); sqlite3VdbeAddOp2(v, OP_AddImm, pWin->regApp+1-bInverse, 1); }else if( pFunc->xSFunc!=noopStepFunc ){ - int addrIf = 0; - if( pWin->pFilter ){ - int regTmp; - assert( nArg==0 || nArg==pWin->pOwner->x.pList->nExpr ); - assert( nArg || pWin->pOwner->x.pList==0 ); - regTmp = sqlite3GetTempReg(pParse); - sqlite3VdbeAddOp3(v, OP_Column, csr, pWin->iArgCol+nArg,regTmp); - addrIf = sqlite3VdbeAddOp3(v, OP_IfNot, regTmp, 0, 1); - VdbeCoverage(v); - sqlite3ReleaseTempReg(pParse, regTmp); + if( pWin->bExprArgs ){ + int iOp = sqlite3VdbeCurrentAddr(v); + int iEnd; + + assert( ExprUseXList(pWin->pOwner) ); + nArg = pWin->pOwner->x.pList->nExpr; + regArg = sqlite3GetTempRange(pParse, nArg); + sqlite3ExprCodeExprList(pParse, pWin->pOwner->x.pList, regArg, 0, 0); + + for(iEnd=sqlite3VdbeCurrentAddr(v); iOpopcode==OP_Column && pOp->p1==pMWin->iEphCsr ){ + pOp->p1 = csr; + } + } } if( pFunc->funcFlags & SQLITE_FUNC_NEEDCOLL ){ CollSeq *pColl; assert( nArg>0 ); + assert( ExprUseXList(pWin->pOwner) ); pColl = sqlite3ExprNNCollSeq(pParse, pWin->pOwner->x.pList->a[0].pExpr); sqlite3VdbeAddOp4(v, OP_CollSeq, 0,0,0, (const char*)pColl, P4_COLLSEQ); } - sqlite3VdbeAddOp3(v, bInverse? OP_AggInverse : OP_AggStep, + sqlite3VdbeAddOp3(v, bInverse? OP_AggInverse : OP_AggStep, bInverse, regArg, pWin->regAccum); sqlite3VdbeAppendP4(v, pFunc, P4_FUNCDEF); - sqlite3VdbeChangeP5(v, (u8)nArg); - if( addrIf ) sqlite3VdbeJumpHere(v, addrIf); + sqlite3VdbeChangeP5(v, (u16)nArg); + if( pWin->bExprArgs ){ + sqlite3ReleaseTempRange(pParse, regArg, nArg); + } } + + if( addrIf ) sqlite3VdbeJumpHere(v, addrIf); } } -typedef struct WindowCodeArg WindowCodeArg; -typedef struct WindowCsrAndReg WindowCsrAndReg; -struct WindowCsrAndReg { - int csr; - int reg; -}; - -struct WindowCodeArg { - Parse *pParse; - Window *pMWin; - Vdbe *pVdbe; - int regGosub; - int addrGosub; - int regArg; - int eDelete; - - WindowCsrAndReg start; - WindowCsrAndReg current; - WindowCsrAndReg end; -}; - /* ** Values that may be passed as the second argument to windowCodeOp(). */ @@ -146874,28 +178507,6 @@ struct WindowCodeArg { #define WINDOW_AGGINVERSE 2 #define WINDOW_AGGSTEP 3 -/* -** Generate VM code to read the window frames peer values from cursor csr into -** an array of registers starting at reg. -*/ -static void windowReadPeerValues( - WindowCodeArg *p, - int csr, - int reg -){ - Window *pMWin = p->pMWin; - ExprList *pOrderBy = pMWin->pOrderBy; - if( pOrderBy ){ - Vdbe *v = sqlite3GetVdbe(p->pParse); - ExprList *pPart = pMWin->pPartition; - int iColOff = pMWin->nBufferCol + (pPart ? pPart->nExpr : 0); - int i; - for(i=0; inExpr; i++){ - sqlite3VdbeAddOp3(v, OP_Column, csr, iColOff+i, reg+i); - } - } -} - /* ** Generate VM code to invoke either xValue() (bFin==0) or xFinalize() ** (bFin==1) for each window function in the linked list starting at @@ -146910,7 +178521,7 @@ static void windowAggFinal(WindowCodeArg *p, int bFin){ for(pWin=pMWin; pWin; pWin=pWin->pNextWin){ if( pMWin->regStartRowid==0 - && (pWin->pFunc->funcFlags & SQLITE_FUNC_MINMAX) + && (pWin->pWFunc->funcFlags & SQLITE_FUNC_MINMAX) && (pWin->eStart!=TK_UNBOUNDED) ){ sqlite3VdbeAddOp2(v, OP_Null, 0, pWin->regResult); @@ -146924,12 +178535,12 @@ static void windowAggFinal(WindowCodeArg *p, int bFin){ int nArg = windowArgCount(pWin); if( bFin ){ sqlite3VdbeAddOp2(v, OP_AggFinal, pWin->regAccum, nArg); - sqlite3VdbeAppendP4(v, pWin->pFunc, P4_FUNCDEF); + sqlite3VdbeAppendP4(v, pWin->pWFunc, P4_FUNCDEF); sqlite3VdbeAddOp2(v, OP_Copy, pWin->regAccum, pWin->regResult); sqlite3VdbeAddOp2(v, OP_Null, 0, pWin->regAccum); }else{ sqlite3VdbeAddOp3(v, OP_AggValue,pWin->regAccum,nArg,pWin->regResult); - sqlite3VdbeAppendP4(v, pWin->pFunc, P4_FUNCDEF); + sqlite3VdbeAppendP4(v, pWin->pWFunc, P4_FUNCDEF); } } } @@ -146956,8 +178567,12 @@ static void windowFullScan(WindowCodeArg *p){ int lblNext; int lblBrk; int addrNext; - int csr = pMWin->csrApp; + int csr; + VdbeModuleComment((v, "windowFullScan begin")); + + assert( pMWin!=0 ); + csr = pMWin->csrApp; nPeer = (pMWin->pOrderBy ? pMWin->pOrderBy->nExpr : 0); lblNext = sqlite3VdbeMakeLabel(pParse); @@ -147012,7 +178627,7 @@ static void windowFullScan(WindowCodeArg *p){ if( addrEq ) sqlite3VdbeJumpHere(v, addrEq); } - windowAggStep(pParse, pMWin, csr, 0, p->regArg); + windowAggStep(p, pMWin, csr, 0, p->regArg); sqlite3VdbeResolveLabel(v, lblNext); sqlite3VdbeAddOp2(v, OP_Next, csr, addrNext); @@ -147027,6 +178642,7 @@ static void windowFullScan(WindowCodeArg *p){ } windowAggFinal(p, 1); + VdbeModuleComment((v, "windowFullScan end")); } /* @@ -147053,7 +178669,8 @@ static void windowReturnOneRow(WindowCodeArg *p){ Window *pWin; for(pWin=pMWin; pWin; pWin=pWin->pNextWin){ - FuncDef *pFunc = pWin->pFunc; + FuncDef *pFunc = pWin->pWFunc; + assert( ExprUseXList(pWin->pOwner) ); if( pFunc->zName==nth_valueName || pFunc->zName==first_valueName ){ @@ -147061,7 +178678,7 @@ static void windowReturnOneRow(WindowCodeArg *p){ int lbl = sqlite3VdbeMakeLabel(pParse); int tmpReg = sqlite3GetTempReg(pParse); sqlite3VdbeAddOp2(v, OP_Null, 0, pWin->regResult); - + if( pFunc->zName==nth_valueName ){ sqlite3VdbeAddOp3(v, OP_Column,pMWin->iEphCsr,pWin->iArgCol+1,tmpReg); windowCheckValue(pParse, tmpReg, 2); @@ -147083,7 +178700,7 @@ static void windowReturnOneRow(WindowCodeArg *p){ int lbl = sqlite3VdbeMakeLabel(pParse); int tmpReg = sqlite3GetTempReg(pParse); int iEph = pMWin->iEphCsr; - + if( nArg<3 ){ sqlite3VdbeAddOp2(v, OP_Null, 0, pWin->regResult); }else{ @@ -147100,7 +178717,7 @@ static void windowReturnOneRow(WindowCodeArg *p){ sqlite3VdbeAddOp3(v, op, tmpReg2, tmpReg, tmpReg); sqlite3ReleaseTempReg(pParse, tmpReg2); } - + sqlite3VdbeAddOp3(v, OP_SeekRowid, csr, lbl, tmpReg); VdbeCoverage(v); sqlite3VdbeAddOp3(v, OP_Column, csr, pWin->iArgCol, pWin->regResult); @@ -147124,7 +178741,8 @@ static int windowInitAccum(Parse *pParse, Window *pMWin){ int nArg = 0; Window *pWin; for(pWin=pMWin; pWin; pWin=pWin->pNextWin){ - FuncDef *pFunc = pWin->pFunc; + FuncDef *pFunc = pWin->pWFunc; + assert( pWin->regAccum ); sqlite3VdbeAddOp2(v, OP_Null, 0, pWin->regAccum); nArg = MAX(nArg, windowArgCount(pWin)); if( pMWin->regStartRowid==0 ){ @@ -147145,7 +178763,7 @@ static int windowInitAccum(Parse *pParse, Window *pMWin){ return regArg; } -/* +/* ** Return true if the current frame should be cached in the ephemeral table, ** even if there are no xInverse() calls required. */ @@ -147153,7 +178771,7 @@ static int windowCacheFrame(Window *pMWin){ Window *pWin; if( pMWin->regStartRowid ) return 1; for(pWin=pMWin; pWin; pWin=pWin->pNextWin){ - FuncDef *pFunc = pWin->pFunc; + FuncDef *pFunc = pWin->pWFunc; if( (pFunc->zName==nth_valueName) || (pFunc->zName==first_valueName) || (pFunc->zName==leadName) @@ -147169,9 +178787,9 @@ static int windowCacheFrame(Window *pMWin){ ** regOld and regNew are each the first register in an array of size ** pOrderBy->nExpr. This function generates code to compare the two ** arrays of registers using the collation sequences and other comparison -** parameters specified by pOrderBy. +** parameters specified by pOrderBy. ** -** If the two arrays are not equal, the contents of regNew is copied to +** If the two arrays are not equal, the contents of regNew is copied to ** regOld and control falls through. Otherwise, if the contents of the arrays ** are equal, an OP_Goto is executed. The address of the OP_Goto is returned. */ @@ -147188,7 +178806,7 @@ static void windowIfNewPeer( KeyInfo *pKeyInfo = sqlite3KeyInfoFromExprList(pParse, pOrderBy, 0, 0); sqlite3VdbeAddOp3(v, OP_Compare, regOld, regNew, nVal); sqlite3VdbeAppendP4(v, (void*)pKeyInfo, P4_KEYINFO); - sqlite3VdbeAddOp3(v, OP_Jump, + sqlite3VdbeAddOp3(v, OP_Jump, sqlite3VdbeCurrentAddr(v)+1, addr, sqlite3VdbeCurrentAddr(v)+1 ); VdbeCoverageEqNe(v); @@ -147201,34 +178819,52 @@ static void windowIfNewPeer( /* ** This function is called as part of generating VM programs for RANGE ** offset PRECEDING/FOLLOWING frame boundaries. Assuming "ASC" order for -** the ORDER BY term in the window, it generates code equivalent to: +** the ORDER BY term in the window, and that argument op is OP_Ge, it generates +** code equivalent to: ** ** if( csr1.peerVal + regVal >= csr2.peerVal ) goto lbl; ** -** A special type of arithmetic is used such that if csr.peerVal is not -** a numeric type (real or integer), then the result of the addition is -** a copy of csr1.peerVal. +** The value of parameter op may also be OP_Gt or OP_Le. In these cases the +** operator in the above pseudo-code is replaced with ">" or "<=", respectively. +** +** If the sort-order for the ORDER BY term in the window is DESC, then the +** comparison is reversed. Instead of adding regVal to csr1.peerVal, it is +** subtracted. And the comparison operator is inverted to - ">=" becomes "<=", +** ">" becomes "<", and so on. So, with DESC sort order, if the argument op +** is OP_Ge, the generated code is equivalent to: +** +** if( csr1.peerVal - regVal <= csr2.peerVal ) goto lbl; +** +** A special type of arithmetic is used such that if csr1.peerVal is not +** a numeric type (real or integer), then the result of the addition +** or subtraction is a a copy of csr1.peerVal. */ static void windowCodeRangeTest( - WindowCodeArg *p, - int op, /* OP_Ge or OP_Gt */ - int csr1, - int regVal, - int csr2, - int lbl + WindowCodeArg *p, + int op, /* OP_Ge, OP_Gt, or OP_Le */ + int csr1, /* Cursor number for cursor 1 */ + int regVal, /* Register containing non-negative number */ + int csr2, /* Cursor number for cursor 2 */ + int lbl /* Jump destination if condition is true */ ){ Parse *pParse = p->pParse; Vdbe *v = sqlite3GetVdbe(pParse); - int reg1 = sqlite3GetTempReg(pParse); - int reg2 = sqlite3GetTempReg(pParse); - int arith = OP_Add; - int addrGe; + ExprList *pOrderBy = p->pMWin->pOrderBy; /* ORDER BY clause for window */ + int reg1 = sqlite3GetTempReg(pParse); /* Reg. for csr1.peerVal+regVal */ + int reg2 = sqlite3GetTempReg(pParse); /* Reg. for csr2.peerVal */ + int regString = ++pParse->nMem; /* Reg. for constant value '' */ + int arith = OP_Add; /* OP_Add or OP_Subtract */ + int addrGe; /* Jump destination */ + int addrDone = sqlite3VdbeMakeLabel(pParse); /* Address past OP_Ge */ + CollSeq *pColl; - int regString = ++pParse->nMem; + /* Read the peer-value from each cursor into a register */ + windowReadPeerValues(p, csr1, reg1); + windowReadPeerValues(p, csr2, reg2); assert( op==OP_Ge || op==OP_Gt || op==OP_Le ); - assert( p->pMWin->pOrderBy && p->pMWin->pOrderBy->nExpr==1 ); - if( p->pMWin->pOrderBy->a[0].sortOrder ){ + assert( pOrderBy && pOrderBy->nExpr==1 ); + if( pOrderBy->a[0].fg.sortFlags & KEYINFO_ORDER_DESC ){ switch( op ){ case OP_Ge: op = OP_Le; break; case OP_Gt: op = OP_Lt; break; @@ -147237,32 +178873,101 @@ static void windowCodeRangeTest( arith = OP_Subtract; } - windowReadPeerValues(p, csr1, reg1); - windowReadPeerValues(p, csr2, reg2); + VdbeModuleComment((v, "CodeRangeTest: if( R%d %s R%d %s R%d ) goto lbl", + reg1, (arith==OP_Add ? "+" : "-"), regVal, + ((op==OP_Ge) ? ">=" : (op==OP_Le) ? "<=" : (op==OP_Gt) ? ">" : "<"), reg2 + )); - /* Check if the peer value for csr1 value is a text or blob by comparing - ** it to the smallest possible string - ''. If it is, jump over the - ** OP_Add or OP_Subtract operation and proceed directly to the comparison. */ + /* If the BIGNULL flag is set for the ORDER BY, then it is required to + ** consider NULL values to be larger than all other values, instead of + ** the usual smaller. The VDBE opcodes OP_Ge and so on do not handle this + ** (and adding that capability causes a performance regression), so + ** instead if the BIGNULL flag is set then cases where either reg1 or + ** reg2 are NULL are handled separately in the following block. The code + ** generated is equivalent to: + ** + ** if( reg1 IS NULL ){ + ** if( op==OP_Ge ) goto lbl; + ** if( op==OP_Gt && reg2 IS NOT NULL ) goto lbl; + ** if( op==OP_Le && reg2 IS NULL ) goto lbl; + ** }else if( reg2 IS NULL ){ + ** if( op==OP_Le ) goto lbl; + ** } + ** + ** Additionally, if either reg1 or reg2 are NULL but the jump to lbl is + ** not taken, control jumps over the comparison operator coded below this + ** block. */ + if( pOrderBy->a[0].fg.sortFlags & KEYINFO_ORDER_BIGNULL ){ + /* This block runs if reg1 contains a NULL. */ + int addr = sqlite3VdbeAddOp1(v, OP_NotNull, reg1); VdbeCoverage(v); + switch( op ){ + case OP_Ge: + sqlite3VdbeAddOp2(v, OP_Goto, 0, lbl); + break; + case OP_Gt: + sqlite3VdbeAddOp2(v, OP_NotNull, reg2, lbl); + VdbeCoverage(v); + break; + case OP_Le: + sqlite3VdbeAddOp2(v, OP_IsNull, reg2, lbl); + VdbeCoverage(v); + break; + default: assert( op==OP_Lt ); /* no-op */ break; + } + sqlite3VdbeAddOp2(v, OP_Goto, 0, addrDone); + + /* This block runs if reg1 is not NULL, but reg2 is. */ + sqlite3VdbeJumpHere(v, addr); + sqlite3VdbeAddOp2(v, OP_IsNull, reg2, + (op==OP_Gt || op==OP_Ge) ? addrDone : lbl); + VdbeCoverage(v); + } + + /* Register reg1 currently contains csr1.peerVal (the peer-value from csr1). + ** This block adds (or subtracts for DESC) the numeric value in regVal + ** from it. Or, if reg1 is not numeric (it is a NULL, a text value or a blob), + ** then leave reg1 as it is. In pseudo-code, this is implemented as: + ** + ** if( reg1>='' ) goto addrGe; + ** reg1 = reg1 +/- regVal + ** addrGe: + ** + ** Since all strings and blobs are greater-than-or-equal-to an empty string, + ** the add/subtract is skipped for these, as required. If reg1 is a NULL, + ** then the arithmetic is performed, but since adding or subtracting from + ** NULL is always NULL anyway, this case is handled as required too. */ sqlite3VdbeAddOp4(v, OP_String8, 0, regString, 0, "", P4_STATIC); addrGe = sqlite3VdbeAddOp3(v, OP_Ge, regString, 0, reg1); VdbeCoverage(v); + if( (op==OP_Ge && arith==OP_Add) || (op==OP_Le && arith==OP_Subtract) ){ + sqlite3VdbeAddOp3(v, op, reg2, lbl, reg1); VdbeCoverage(v); + } sqlite3VdbeAddOp3(v, arith, regVal, reg1, reg1); sqlite3VdbeJumpHere(v, addrGe); + + /* Compare registers reg2 and reg1, taking the jump if required. Note that + ** control skips over this test if the BIGNULL flag is set and either + ** reg1 or reg2 contain a NULL value. */ sqlite3VdbeAddOp3(v, op, reg2, lbl, reg1); VdbeCoverage(v); + pColl = sqlite3ExprNNCollSeq(pParse, pOrderBy->a[0].pExpr); + sqlite3VdbeAppendP4(v, (void*)pColl, P4_COLLSEQ); sqlite3VdbeChangeP5(v, SQLITE_NULLEQ); + sqlite3VdbeResolveLabel(v, addrDone); + assert( op==OP_Ge || op==OP_Gt || op==OP_Lt || op==OP_Le ); testcase(op==OP_Ge); VdbeCoverageIf(v, op==OP_Ge); testcase(op==OP_Lt); VdbeCoverageIf(v, op==OP_Lt); testcase(op==OP_Le); VdbeCoverageIf(v, op==OP_Le); testcase(op==OP_Gt); VdbeCoverageIf(v, op==OP_Gt); - sqlite3ReleaseTempReg(pParse, reg1); sqlite3ReleaseTempReg(pParse, reg2); + + VdbeModuleComment((v, "CodeRangeTest: end")); } /* ** Helper function for sqlite3WindowCodeStep(). Each call to this function -** generates VM code for a single RETURN_ROW, AGGSTEP or AGGINVERSE +** generates VM code for a single RETURN_ROW, AGGSTEP or AGGINVERSE ** operation. Refer to the header comment for sqlite3WindowCodeStep() for ** details. */ @@ -147277,9 +178982,7 @@ static int windowCodeOp( Window *pMWin = p->pMWin; int ret = 0; Vdbe *v = p->pVdbe; - int addrIf = 0; int addrContinue = 0; - int addrGoto = 0; int bPeer = (pMWin->eFrmType!=TK_ROWS); int lblDone = sqlite3VdbeMakeLabel(pParse); @@ -147312,7 +179015,7 @@ static int windowCodeOp( ); } }else{ - addrIf = sqlite3VdbeAddOp3(v, OP_IfPos, regCountdown, 0, 1); + sqlite3VdbeAddOp3(v, OP_IfPos, regCountdown, lblDone, 1); VdbeCoverage(v); } } @@ -147321,6 +179024,33 @@ static int windowCodeOp( windowAggFinal(p, 0); } addrContinue = sqlite3VdbeCurrentAddr(v); + + /* If this is a (RANGE BETWEEN a FOLLOWING AND b FOLLOWING) or + ** (RANGE BETWEEN b PRECEDING AND a PRECEDING) frame, ensure the + ** start cursor does not advance past the end cursor within the + ** temporary table. It otherwise might, if (a>b). Also ensure that, + ** if the input cursor is still finding new rows, that the end + ** cursor does not go past it to EOF. */ + if( pMWin->eStart==pMWin->eEnd && regCountdown + && pMWin->eFrmType==TK_RANGE + ){ + int regRowid1 = sqlite3GetTempReg(pParse); + int regRowid2 = sqlite3GetTempReg(pParse); + if( op==WINDOW_AGGINVERSE ){ + sqlite3VdbeAddOp2(v, OP_Rowid, p->start.csr, regRowid1); + sqlite3VdbeAddOp2(v, OP_Rowid, p->end.csr, regRowid2); + sqlite3VdbeAddOp3(v, OP_Ge, regRowid2, lblDone, regRowid1); + VdbeCoverage(v); + }else if( p->regRowid ){ + sqlite3VdbeAddOp2(v, OP_Rowid, p->end.csr, regRowid1); + sqlite3VdbeAddOp3(v, OP_Ge, p->regRowid, lblDone, regRowid1); + VdbeCoverageNeverNull(v); + } + sqlite3ReleaseTempReg(pParse, regRowid1); + sqlite3ReleaseTempReg(pParse, regRowid2); + assert( pMWin->eStart==TK_PRECEDING || pMWin->eStart==TK_FOLLOWING ); + } + switch( op ){ case WINDOW_RETURN_ROW: csr = p->current.csr; @@ -147335,7 +179065,7 @@ static int windowCodeOp( assert( pMWin->regEndRowid ); sqlite3VdbeAddOp2(v, OP_AddImm, pMWin->regStartRowid, 1); }else{ - windowAggStep(pParse, pMWin, csr, 1, p->regArg); + windowAggStep(p, pMWin, csr, 1, p->regArg); } break; @@ -147347,7 +179077,7 @@ static int windowCodeOp( assert( pMWin->regEndRowid ); sqlite3VdbeAddOp2(v, OP_AddImm, pMWin->regEndRowid, 1); }else{ - windowAggStep(pParse, pMWin, csr, 0, p->regArg); + windowAggStep(p, pMWin, csr, 0, p->regArg); } break; } @@ -147365,7 +179095,7 @@ static int windowCodeOp( sqlite3VdbeAddOp2(v, OP_Next, csr, sqlite3VdbeCurrentAddr(v)+1+bPeer); VdbeCoverage(v); if( bPeer ){ - addrGoto = sqlite3VdbeAddOp0(v, OP_Goto); + sqlite3VdbeAddOp2(v, OP_Goto, 0, lblDone); } } @@ -147381,8 +179111,6 @@ static int windowCodeOp( sqlite3VdbeAddOp2(v, OP_Goto, 0, addrNextRange); } sqlite3VdbeResolveLabel(v, lblDone); - if( addrGoto ) sqlite3VdbeJumpHere(v, addrGoto); - if( addrIf ) sqlite3VdbeJumpHere(v, addrIf); return ret; } @@ -147398,17 +179126,24 @@ SQLITE_PRIVATE Window *sqlite3WindowDup(sqlite3 *db, Expr *pOwner, Window *p){ pNew = sqlite3DbMallocZero(db, sizeof(Window)); if( pNew ){ pNew->zName = sqlite3DbStrDup(db, p->zName); + pNew->zBase = sqlite3DbStrDup(db, p->zBase); pNew->pFilter = sqlite3ExprDup(db, p->pFilter, 0); - pNew->pFunc = p->pFunc; + pNew->pWFunc = p->pWFunc; pNew->pPartition = sqlite3ExprListDup(db, p->pPartition, 0); pNew->pOrderBy = sqlite3ExprListDup(db, p->pOrderBy, 0); pNew->eFrmType = p->eFrmType; pNew->eEnd = p->eEnd; pNew->eStart = p->eStart; pNew->eExclude = p->eExclude; + pNew->regResult = p->regResult; + pNew->regAccum = p->regAccum; + pNew->iArgCol = p->iArgCol; + pNew->iEphCsr = p->iEphCsr; + pNew->bExprArgs = p->bExprArgs; pNew->pStart = sqlite3ExprDup(db, p->pStart, 0); pNew->pEnd = sqlite3ExprDup(db, p->pEnd, 0); pNew->pOwner = pOwner; + pNew->bImplicitFrame = p->bImplicitFrame; } } return pNew; @@ -147433,11 +179168,11 @@ SQLITE_PRIVATE Window *sqlite3WindowListDup(sqlite3 *db, Window *p){ } /* -** Return true if it can be determined at compile time that expression -** pExpr evaluates to a value that, when cast to an integer, is greater +** Return true if it can be determined at compile time that expression +** pExpr evaluates to a value that, when cast to an integer, is greater ** than zero. False otherwise. ** -** If an OOM error occurs, this function sets the Parse.db.mallocFailed +** If an OOM error occurs, this function sets the Parse.db.mallocFailed ** flag and returns zero. */ static int windowExprGtZero(Parse *pParse, Expr *pExpr){ @@ -147453,11 +179188,11 @@ static int windowExprGtZero(Parse *pParse, Expr *pExpr){ } /* -** sqlite3WhereBegin() has already been called for the SELECT statement +** sqlite3WhereBegin() has already been called for the SELECT statement ** passed as the second argument when this function is invoked. It generates -** code to populate the Window.regResult register for each window function +** code to populate the Window.regResult register for each window function ** and invoke the sub-routine at instruction addrGosub once for each row. -** sqlite3WhereEnd() is always called before returning. +** sqlite3WhereEnd() is always called before returning. ** ** This function handles several different types of window frames, which ** require slightly different processing. The following pseudo code is @@ -147472,17 +179207,17 @@ static int windowExprGtZero(Parse *pParse, Expr *pExpr){ ** Gosub flush ** } ** Insert new row into eph table. -** +** ** if( first row of partition ){ ** // Rewind three cursors, all open on the eph table. ** Rewind(csrEnd); ** Rewind(csrStart); ** Rewind(csrCurrent); -** +** ** regEnd = // FOLLOWING expression ** regStart = // PRECEDING expression ** }else{ -** // First time this branch is taken, the eph table contains two +** // First time this branch is taken, the eph table contains two ** // rows. The first row in the partition, which all three cursors ** // currently point to, and the following row. ** AGGSTEP @@ -147511,17 +179246,17 @@ static int windowExprGtZero(Parse *pParse, Expr *pExpr){ ** with arguments read from the current row of cursor csrEnd, then ** step cursor csrEnd forward one row (i.e. sqlite3BtreeNext()). ** -** RETURN_ROW: return a row to the caller based on the contents of the -** current row of csrCurrent and the current state of all +** RETURN_ROW: return a row to the caller based on the contents of the +** current row of csrCurrent and the current state of all ** aggregates. Then step cursor csrCurrent forward one row. ** -** AGGINVERSE: invoke the aggregate xInverse() function for each window +** AGGINVERSE: invoke the aggregate xInverse() function for each window ** functions with arguments read from the current row of cursor ** csrStart. Then step csrStart forward one row. ** ** There are two other ROWS window frames that are handled significantly ** differently from the above - "BETWEEN PRECEDING AND PRECEDING" -** and "BETWEEN FOLLOWING AND FOLLOWING". These are special +** and "BETWEEN FOLLOWING AND FOLLOWING". These are special ** cases because they change the order in which the three cursors (csrStart, ** csrCurrent and csrEnd) iterate through the ephemeral table. Cases that ** use UNBOUNDED or CURRENT ROW are much simpler variations on one of these @@ -147557,7 +179292,7 @@ static int windowExprGtZero(Parse *pParse, Expr *pExpr){ ** ** ROWS BETWEEN FOLLOWING AND FOLLOWING ** -** ... loop started by sqlite3WhereBegin() ... +** ... loop started by sqlite3WhereBegin() ... ** if( new partition ){ ** Gosub flush ** } @@ -147594,7 +179329,7 @@ static int windowExprGtZero(Parse *pParse, Expr *pExpr){ ** ** For the most part, the patterns above are adapted to support UNBOUNDED by ** assuming that it is equivalent to "infinity PRECEDING/FOLLOWING" and -** CURRENT ROW by assuming that it is equivilent to "0 PRECEDING/FOLLOWING". +** CURRENT ROW by assuming that it is equivalent to "0 PRECEDING/FOLLOWING". ** This is optimized of course - branches that will never be taken and ** conditions that are always true are omitted from the VM code. The only ** exceptional case is: @@ -147671,15 +179406,15 @@ static int windowExprGtZero(Parse *pParse, Expr *pExpr){ ** regEnd = ** regStart = ** }else if( new group ){ -** ... +** ... ** } ** } ** -** 2. Instead of processing a single row, each RETURN_ROW, AGGSTEP or +** 2. Instead of processing a single row, each RETURN_ROW, AGGSTEP or ** AGGINVERSE step processes the current row of the relevant cursor and ** all subsequent rows belonging to the same group. ** -** RANGE window frames are a little different again. As for GROUPS, the +** RANGE window frames are a little different again. As for GROUPS, the ** main loop runs once per group only. And RETURN_ROW, AGGSTEP and AGGINVERSE ** deal in groups instead of rows. As for ROWS and GROUPS, there are three ** basic cases: @@ -147716,7 +179451,7 @@ static int windowExprGtZero(Parse *pParse, Expr *pExpr){ ** } ** } ** -** In the above notation, "csr.key" means the current value of the ORDER BY +** In the above notation, "csr.key" means the current value of the ORDER BY ** expression (there is only ever 1 for a RANGE that uses an FOLLOWING ** or ** regStart = ** }else{ -** if( (csrEnd.key + regEnd) <= csrCurrent.key ){ +** while( (csrEnd.key + regEnd) <= csrCurrent.key ){ ** AGGSTEP ** } ** while( (csrStart.key + regStart) < csrCurrent.key ){ @@ -147799,28 +179534,27 @@ SQLITE_PRIVATE void sqlite3WindowCodeStep( Vdbe *v = sqlite3GetVdbe(pParse); int csrWrite; /* Cursor used to write to eph. table */ int csrInput = p->pSrc->a[0].iCursor; /* Cursor of sub-select */ - int nInput = p->pSrc->a[0].pTab->nCol; /* Number of cols returned by sub */ + int nInput = p->pSrc->a[0].pSTab->nCol; /* Number of cols returned by sub */ int iInput; /* To iterate through sub cols */ int addrNe; /* Address of OP_Ne */ int addrGosubFlush = 0; /* Address of OP_Gosub to flush: */ int addrInteger = 0; /* Address of OP_Integer */ int addrEmpty; /* Address of OP_Rewind in flush: */ - int regStart = 0; /* Value of PRECEDING */ - int regEnd = 0; /* Value of FOLLOWING */ int regNew; /* Array of registers holding new input row */ int regRecord; /* regNew array in record form */ - int regRowid; /* Rowid for regRecord in eph table */ int regNewPeer = 0; /* Peer values for new row (part of regNew) */ int regPeer = 0; /* Peer values for current row */ int regFlushPart = 0; /* Register for "Gosub flush_partition" */ WindowCodeArg s; /* Context object for sub-routines */ int lblWhereEnd; /* Label just before sqlite3WhereEnd() code */ + int regStart = 0; /* Value of PRECEDING */ + int regEnd = 0; /* Value of FOLLOWING */ - assert( pMWin->eStart==TK_PRECEDING || pMWin->eStart==TK_CURRENT - || pMWin->eStart==TK_FOLLOWING || pMWin->eStart==TK_UNBOUNDED + assert( pMWin->eStart==TK_PRECEDING || pMWin->eStart==TK_CURRENT + || pMWin->eStart==TK_FOLLOWING || pMWin->eStart==TK_UNBOUNDED ); - assert( pMWin->eEnd==TK_FOLLOWING || pMWin->eEnd==TK_CURRENT - || pMWin->eEnd==TK_UNBOUNDED || pMWin->eEnd==TK_PRECEDING + assert( pMWin->eEnd==TK_FOLLOWING || pMWin->eEnd==TK_CURRENT + || pMWin->eEnd==TK_UNBOUNDED || pMWin->eEnd==TK_PRECEDING ); assert( pMWin->eExclude==0 || pMWin->eExclude==TK_CURRENT || pMWin->eExclude==TK_GROUP || pMWin->eExclude==TK_TIES @@ -147842,9 +179576,9 @@ SQLITE_PRIVATE void sqlite3WindowCodeStep( s.end.csr = s.current.csr+3; /* Figure out when rows may be deleted from the ephemeral table. There - ** are four options - they may never be deleted (eDelete==0), they may + ** are four options - they may never be deleted (eDelete==0), they may ** be deleted as soon as they are no longer part of the window frame - ** (eDelete==WINDOW_AGGINVERSE), they may be deleted as after the row + ** (eDelete==WINDOW_AGGINVERSE), they may be deleted as after the row ** has been returned to the caller (WINDOW_RETURN_ROW), or they may ** be deleted after they enter the frame (WINDOW_AGGSTEP). */ switch( pMWin->eStart ){ @@ -147874,12 +179608,12 @@ SQLITE_PRIVATE void sqlite3WindowCodeStep( } /* Allocate registers for the array of values from the sub-query, the - ** samve values in record form, and the rowid used to insert said record + ** same values in record form, and the rowid used to insert said record ** into the ephemeral table. */ regNew = pParse->nMem+1; pParse->nMem += nInput; regRecord = ++pParse->nMem; - regRowid = ++pParse->nMem; + s.regRowid = ++pParse->nMem; /* If the window frame contains an " PRECEDING" or " FOLLOWING" ** clause, allocate registers to store the results of evaluating each @@ -147892,7 +179626,7 @@ SQLITE_PRIVATE void sqlite3WindowCodeStep( } /* If this is not a "ROWS BETWEEN ..." frame, then allocate arrays of - ** registers to store copies of the ORDER BY expressions (peer values) + ** registers to store copies of the ORDER BY expressions (peer values) ** for the main loop, and for each cursor (start, current and end). */ if( pMWin->eFrmType!=TK_ROWS ){ int nPeer = (pOrderBy ? pOrderBy->nExpr : 0); @@ -147913,7 +179647,7 @@ SQLITE_PRIVATE void sqlite3WindowCodeStep( sqlite3VdbeAddOp3(v, OP_MakeRecord, regNew, nInput, regRecord); /* An input row has just been read into an array of registers starting - ** at regNew. If the window has a PARTITION clause, this block generates + ** at regNew. If the window has a PARTITION clause, this block generates ** VM code to check if the input row is the start of a new partition. ** If so, it does an OP_Gosub to an address to be filled in later. The ** address of the OP_Gosub is stored in local variable addrGosubFlush. */ @@ -147935,9 +179669,9 @@ SQLITE_PRIVATE void sqlite3WindowCodeStep( } /* Insert the new row into the ephemeral table */ - sqlite3VdbeAddOp2(v, OP_NewRowid, csrWrite, regRowid); - sqlite3VdbeAddOp3(v, OP_Insert, csrWrite, regRecord, regRowid); - addrNe = sqlite3VdbeAddOp3(v, OP_Ne, pMWin->regOne, 0, regRowid); + sqlite3VdbeAddOp2(v, OP_NewRowid, csrWrite, s.regRowid); + sqlite3VdbeAddOp3(v, OP_Insert, csrWrite, regRecord, s.regRowid); + addrNe = sqlite3VdbeAddOp3(v, OP_Ne, pMWin->regOne, 0, s.regRowid); VdbeCoverageNeverNull(v); /* This block is run for the first row of each partition */ @@ -147945,21 +179679,20 @@ SQLITE_PRIVATE void sqlite3WindowCodeStep( if( regStart ){ sqlite3ExprCode(pParse, pMWin->pStart, regStart); - windowCheckValue(pParse, regStart, 0 + (pMWin->eFrmType==TK_RANGE ? 3 : 0)); + windowCheckValue(pParse, regStart, 0 + (pMWin->eFrmType==TK_RANGE?3:0)); } if( regEnd ){ sqlite3ExprCode(pParse, pMWin->pEnd, regEnd); - windowCheckValue(pParse, regEnd, 1 + (pMWin->eFrmType==TK_RANGE ? 3 : 0)); + windowCheckValue(pParse, regEnd, 1 + (pMWin->eFrmType==TK_RANGE?3:0)); } - if( pMWin->eStart==pMWin->eEnd && regStart ){ + if( pMWin->eFrmType!=TK_RANGE && pMWin->eStart==pMWin->eEnd && regStart ){ int op = ((pMWin->eStart==TK_FOLLOWING) ? OP_Ge : OP_Le); int addrGe = sqlite3VdbeAddOp3(v, op, regStart, 0, regEnd); VdbeCoverageNeverNullIf(v, op==OP_Ge); /* NeverNull because bound */ VdbeCoverageNeverNullIf(v, op==OP_Le); /* values previously checked */ windowAggFinal(&s, 0); - sqlite3VdbeAddOp2(v, OP_Rewind, s.current.csr, 1); - VdbeCoverageNeverTaken(v); + sqlite3VdbeAddOp1(v, OP_Rewind, s.current.csr); windowReturnOneRow(&s); sqlite3VdbeAddOp1(v, OP_ResetSorter, s.current.csr); sqlite3VdbeAddOp2(v, OP_Goto, 0, lblWhereEnd); @@ -147971,13 +179704,10 @@ SQLITE_PRIVATE void sqlite3WindowCodeStep( } if( pMWin->eStart!=TK_UNBOUNDED ){ - sqlite3VdbeAddOp2(v, OP_Rewind, s.start.csr, 1); - VdbeCoverageNeverTaken(v); + sqlite3VdbeAddOp1(v, OP_Rewind, s.start.csr); } - sqlite3VdbeAddOp2(v, OP_Rewind, s.current.csr, 1); - VdbeCoverageNeverTaken(v); - sqlite3VdbeAddOp2(v, OP_Rewind, s.end.csr, 1); - VdbeCoverageNeverTaken(v); + sqlite3VdbeAddOp1(v, OP_Rewind, s.current.csr); + sqlite3VdbeAddOp1(v, OP_Rewind, s.end.csr); if( regPeer && pOrderBy ){ sqlite3VdbeAddOp3(v, OP_Copy, regNewPeer, regPeer, pOrderBy->nExpr-1); sqlite3VdbeAddOp3(v, OP_Copy, regPeer, s.start.reg, pOrderBy->nExpr-1); @@ -148055,6 +179785,7 @@ SQLITE_PRIVATE void sqlite3WindowCodeStep( sqlite3VdbeJumpHere(v, addrGosubFlush); } + s.regRowid = 0; addrEmpty = sqlite3VdbeAddOp1(v, OP_Rewind, csrWrite); VdbeCoverage(v); if( pMWin->eEnd==TK_PRECEDING ){ @@ -148079,6 +179810,12 @@ SQLITE_PRIVATE void sqlite3WindowCodeStep( addrBreak2 = windowCodeOp(&s, WINDOW_AGGINVERSE, 0, 1); }else{ assert( pMWin->eEnd==TK_FOLLOWING ); + /* assert( regStart>=0 ); + ** regEnd = regEnd - regStart; + ** regStart = 0; */ + sqlite3VdbeAddOp3(v, OP_Subtract, regStart, regEnd, regEnd); + sqlite3VdbeAddOp2(v, OP_Integer, 0, regStart); + addrStart = sqlite3VdbeCurrentAddr(v); addrBreak1 = windowCodeOp(&s, WINDOW_RETURN_ROW, regEnd, 1); addrBreak2 = windowCodeOp(&s, WINDOW_AGGINVERSE, regStart, 1); @@ -148117,8 +179854,11 @@ SQLITE_PRIVATE void sqlite3WindowCodeStep( /************** End of window.c **********************************************/ /************** Begin file parse.c *******************************************/ +/* This file is automatically generated by Lemon from input grammar +** source file "parse.y". +*/ /* -** 2000-05-29 +** 2001-09-15 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: @@ -148128,25 +179868,23 @@ SQLITE_PRIVATE void sqlite3WindowCodeStep( ** May you share freely, never taking more than you give. ** ************************************************************************* -** Driver template for the LEMON parser generator. -** -** The "lemon" program processes an LALR(1) input grammar file, then uses -** this template to construct a parser. The "lemon" program inserts text -** at each "%%" line. Also, any "P-a-r-s-e" identifer prefix (without the -** interstitial "-" characters) contained in this template is changed into -** the value of the %name directive from the grammar. Otherwise, the content -** of this template is copied straight through into the generate parser -** source file. +** This file contains SQLite's SQL parser. ** -** The following is the concatenation of all %include directives from the -** input grammar file: +** The canonical source code to this file ("parse.y") is a Lemon grammar +** file that specifies the input grammar and actions to take while parsing. +** That input file is processed by Lemon to generate a C-language +** implementation of a parser for the given grammar. You might be reading +** this comment as part of the translated C-code. Edits should be made +** to the original parse.y sources. */ -/* #include */ -/* #include */ -/************ Begin %include sections from the grammar ************************/ /* #include "sqliteInt.h" */ +/* +** Verify that the pParse->isCreate field is set +*/ +#define ASSERT_IS_CREATE assert(pParse->isCreate) + /* ** Disable all error recovery processing in the parser push-down ** automaton. @@ -148196,15 +179934,48 @@ struct TrigEvent { int a; IdList * b; }; struct FrameBound { int eType; Expr *pExpr; }; +/* +** Generate a syntax error +*/ +static void parserSyntaxError(Parse *pParse, Token *p){ + sqlite3ErrorMsg(pParse, "near \"%T\": syntax error", p); +} + /* ** Disable lookaside memory allocation for objects that might be ** shared across database connections. */ static void disableLookaside(Parse *pParse){ + sqlite3 *db = pParse->db; pParse->disableLookaside++; - pParse->db->lookaside.bDisable++; +#ifdef SQLITE_DEBUG + pParse->isCreate = 1; +#endif + memset(&pParse->u1.cr, 0, sizeof(pParse->u1.cr)); + DisableLookaside; } +#if !defined(SQLITE_ENABLE_UPDATE_DELETE_LIMIT) \ + && defined(SQLITE_UDL_CAPABLE_PARSER) +/* +** Issue an error message if an ORDER BY or LIMIT clause occurs on an +** UPDATE or DELETE statement. +*/ +static void updateDeleteLimitError( + Parse *pParse, + ExprList *pOrderBy, + Expr *pLimit +){ + if( pOrderBy ){ + sqlite3ErrorMsg(pParse, "syntax error near \"ORDER BY\""); + }else{ + sqlite3ErrorMsg(pParse, "syntax error near \"LIMIT\""); + } + sqlite3ExprListDelete(pParse->db, pOrderBy); + sqlite3ExprDelete(pParse->db, pLimit); +} +#endif /* SQLITE_ENABLE_UPDATE_DELETE_LIMIT */ + /* ** For a compound SELECT statement, make sure p->pPrior->pNext==p for @@ -148212,51 +179983,97 @@ static void disableLookaside(Parse *pParse){ ** SQLITE_LIMIT_COMPOUND_SELECT. */ static void parserDoubleLinkSelect(Parse *pParse, Select *p){ + assert( p!=0 ); if( p->pPrior ){ - Select *pNext = 0, *pLoop; - int mxSelect, cnt = 0; - for(pLoop=p; pLoop; pNext=pLoop, pLoop=pLoop->pPrior, cnt++){ + Select *pNext = 0, *pLoop = p; + int mxSelect, cnt = 1; + while(1){ pLoop->pNext = pNext; pLoop->selFlags |= SF_Compound; + pNext = pLoop; + pLoop = pLoop->pPrior; + if( pLoop==0 ) break; + cnt++; + if( pLoop->pOrderBy || pLoop->pLimit ){ + sqlite3ErrorMsg(pParse,"%s clause should come after %s not before", + pLoop->pOrderBy!=0 ? "ORDER BY" : "LIMIT", + sqlite3SelectOpName(pNext->op)); + break; + } } - if( (p->selFlags & SF_MultiValue)==0 && - (mxSelect = pParse->db->aLimit[SQLITE_LIMIT_COMPOUND_SELECT])>0 && - cnt>mxSelect + if( (p->selFlags & (SF_MultiValue|SF_Values))==0 + && (mxSelect = pParse->db->aLimit[SQLITE_LIMIT_COMPOUND_SELECT])>0 + && cnt>mxSelect ){ sqlite3ErrorMsg(pParse, "too many terms in compound SELECT"); } } } + /* Attach a With object describing the WITH clause to a Select + ** object describing the query for which the WITH clause is a prefix. + */ + static Select *attachWithToSelect(Parse *pParse, Select *pSelect, With *pWith){ + if( pSelect ){ + pSelect->pWith = pWith; + parserDoubleLinkSelect(pParse, pSelect); + }else{ + sqlite3WithDelete(pParse->db, pWith); + } + return pSelect; + } - /* Construct a new Expr object from a single identifier. Use the - ** new Expr to populate pOut. Set the span of pOut to be the identifier - ** that created the expression. + /* Memory allocator for parser stack resizing. This is a thin wrapper around + ** sqlite3_realloc() that includes a call to sqlite3FaultSim() to facilitate + ** testing. */ + static void *parserStackRealloc( + void *pOld, /* Prior allocation */ + sqlite3_uint64 newSize, /* Requested new alloation size */ + Parse *pParse /* Parsing context */ + ){ + void *p = sqlite3FaultSim(700) ? 0 : sqlite3_realloc(pOld, newSize); + if( p==0 ) sqlite3OomFault(pParse->db); + return p; + } + static void parserStackFree(void *pOld, Parse *pParse){ + (void)pParse; + sqlite3_free(pOld); + } + + /* Return an integer that is the maximum allowed stack size */ + static int parserStackSizeLimit(Parse *pParse){ + return pParse->db->aLimit[SQLITE_LIMIT_PARSER_DEPTH]; + } + + + /* Construct a new Expr object from a single token */ static Expr *tokenExpr(Parse *pParse, int op, Token t){ Expr *p = sqlite3DbMallocRawNN(pParse->db, sizeof(Expr)+t.n+1); if( p ){ /* memset(p, 0, sizeof(Expr)); */ p->op = (u8)op; - p->affinity = 0; + p->affExpr = 0; p->flags = EP_Leaf; - p->iAgg = -1; + ExprClearVVAProperties(p); + /* p->iAgg = -1; // Not required */ p->pLeft = p->pRight = 0; - p->x.pList = 0; p->pAggInfo = 0; - p->y.pTab = 0; + memset(&p->x, 0, sizeof(p->x)); + memset(&p->y, 0, sizeof(p->y)); p->op2 = 0; p->iTable = 0; p->iColumn = 0; p->u.zToken = (char*)&p[1]; memcpy(p->u.zToken, t.z, t.n); p->u.zToken[t.n] = 0; + p->w.iOfst = (int)(t.z - pParse->zTail); if( sqlite3Isquote(p->u.zToken[0]) ){ sqlite3DequoteExpr(p); } #if SQLITE_MAX_EXPR_DEPTH>0 p->nHeight = 1; -#endif +#endif if( IN_RENAME_OBJECT ){ return (Expr*)sqlite3RenameTokenMap(pParse, (void*)p, &t); } @@ -148265,15 +180082,46 @@ static void disableLookaside(Parse *pParse){ } - /* A routine to convert a binary TK_IS or TK_ISNOT expression into a - ** unary TK_ISNULL or TK_NOTNULL expression. */ - static void binaryToUnaryIfNull(Parse *pParse, Expr *pY, Expr *pA, int op){ - sqlite3 *db = pParse->db; - if( pA && pY && pY->op==TK_NULL && !IN_RENAME_OBJECT ){ - pA->op = (u8)op; - sqlite3ExprDelete(db, pA->pRight); - pA->pRight = 0; + /* Create a TK_ISNULL or TK_NOTNULL expression, perhaps optimized to + ** to TK_TRUEFALSE, if possible */ + static Expr *sqlite3PExprIsNull( + Parse *pParse, /* Parsing context */ + int op, /* TK_ISNULL or TK_NOTNULL */ + Expr *pLeft /* Operand */ + ){ + Expr *p = pLeft; + assert( op==TK_ISNULL || op==TK_NOTNULL ); + assert( pLeft!=0 ); + while( p->op==TK_UPLUS || p->op==TK_UMINUS ){ + p = p->pLeft; + assert( p!=0 ); } + switch( p->op ){ + case TK_INTEGER: + case TK_STRING: + case TK_FLOAT: + case TK_BLOB: + sqlite3ExprDeferredDelete(pParse, pLeft); + return sqlite3ExprInt32(pParse->db, op==TK_NOTNULL); + default: + break; + } + return sqlite3PExpr(pParse, op, pLeft, 0); + } + + /* Create a TK_IS or TK_ISNOT operator, perhaps optimized to + ** TK_ISNULL or TK_NOTNULL or TK_TRUEFALSE. */ + static Expr *sqlite3PExprIs( + Parse *pParse, /* Parsing context */ + int op, /* TK_IS or TK_ISNOT */ + Expr *pLeft, /* Left operand */ + Expr *pRight /* Right operand */ + ){ + if( pRight && pRight->op==TK_NULL ){ + sqlite3ExprDeferredDelete(pParse, pRight); + return sqlite3PExprIsNull(pParse, op==TK_IS ? TK_ISNULL : TK_NOTNULL, pLeft); + } + return sqlite3PExpr(pParse, op, pLeft, pRight); } /* Add a single new term to an ExprList that is used to store a @@ -148303,11 +180151,197 @@ static void disableLookaside(Parse *pParse){ # error too many tokens in the grammar #endif /**************** End of %include directives **********************************/ -/* These constants specify the various numeric values for terminal symbols -** in a format understandable to "makeheaders". This section is blank unless -** "lemon" is run with the "-m" command-line option. -***************** Begin makeheaders token definitions *************************/ -/**************** End makeheaders token definitions ***************************/ +/* These constants specify the various numeric values for terminal symbols. +***************** Begin token definitions *************************************/ +#ifndef TK_SEMI +#define TK_SEMI 1 +#define TK_EXPLAIN 2 +#define TK_QUERY 3 +#define TK_PLAN 4 +#define TK_BEGIN 5 +#define TK_TRANSACTION 6 +#define TK_DEFERRED 7 +#define TK_IMMEDIATE 8 +#define TK_EXCLUSIVE 9 +#define TK_COMMIT 10 +#define TK_END 11 +#define TK_ROLLBACK 12 +#define TK_SAVEPOINT 13 +#define TK_RELEASE 14 +#define TK_TO 15 +#define TK_TABLE 16 +#define TK_CREATE 17 +#define TK_IF 18 +#define TK_NOT 19 +#define TK_EXISTS 20 +#define TK_TEMP 21 +#define TK_LP 22 +#define TK_RP 23 +#define TK_AS 24 +#define TK_COMMA 25 +#define TK_WITHOUT 26 +#define TK_ABORT 27 +#define TK_ACTION 28 +#define TK_AFTER 29 +#define TK_ANALYZE 30 +#define TK_ASC 31 +#define TK_ATTACH 32 +#define TK_BEFORE 33 +#define TK_BY 34 +#define TK_CASCADE 35 +#define TK_CAST 36 +#define TK_CONFLICT 37 +#define TK_DATABASE 38 +#define TK_DESC 39 +#define TK_DETACH 40 +#define TK_EACH 41 +#define TK_FAIL 42 +#define TK_OR 43 +#define TK_AND 44 +#define TK_IS 45 +#define TK_ISNOT 46 +#define TK_MATCH 47 +#define TK_LIKE_KW 48 +#define TK_BETWEEN 49 +#define TK_IN 50 +#define TK_ISNULL 51 +#define TK_NOTNULL 52 +#define TK_NE 53 +#define TK_EQ 54 +#define TK_GT 55 +#define TK_LE 56 +#define TK_LT 57 +#define TK_GE 58 +#define TK_ESCAPE 59 +#define TK_ID 60 +#define TK_COLUMNKW 61 +#define TK_DO 62 +#define TK_FOR 63 +#define TK_IGNORE 64 +#define TK_INITIALLY 65 +#define TK_INSTEAD 66 +#define TK_NO 67 +#define TK_KEY 68 +#define TK_OF 69 +#define TK_OFFSET 70 +#define TK_PRAGMA 71 +#define TK_RAISE 72 +#define TK_RECURSIVE 73 +#define TK_REPLACE 74 +#define TK_RESTRICT 75 +#define TK_ROW 76 +#define TK_ROWS 77 +#define TK_TRIGGER 78 +#define TK_VACUUM 79 +#define TK_VIEW 80 +#define TK_VIRTUAL 81 +#define TK_WITH 82 +#define TK_NULLS 83 +#define TK_FIRST 84 +#define TK_LAST 85 +#define TK_CURRENT 86 +#define TK_FOLLOWING 87 +#define TK_PARTITION 88 +#define TK_PRECEDING 89 +#define TK_RANGE 90 +#define TK_UNBOUNDED 91 +#define TK_EXCLUDE 92 +#define TK_GROUPS 93 +#define TK_OTHERS 94 +#define TK_TIES 95 +#define TK_GENERATED 96 +#define TK_ALWAYS 97 +#define TK_MATERIALIZED 98 +#define TK_REINDEX 99 +#define TK_RENAME 100 +#define TK_CTIME_KW 101 +#define TK_ANY 102 +#define TK_BITAND 103 +#define TK_BITOR 104 +#define TK_LSHIFT 105 +#define TK_RSHIFT 106 +#define TK_PLUS 107 +#define TK_MINUS 108 +#define TK_STAR 109 +#define TK_SLASH 110 +#define TK_REM 111 +#define TK_CONCAT 112 +#define TK_PTR 113 +#define TK_COLLATE 114 +#define TK_BITNOT 115 +#define TK_ON 116 +#define TK_INDEXED 117 +#define TK_STRING 118 +#define TK_JOIN_KW 119 +#define TK_CONSTRAINT 120 +#define TK_DEFAULT 121 +#define TK_NULL 122 +#define TK_PRIMARY 123 +#define TK_UNIQUE 124 +#define TK_CHECK 125 +#define TK_REFERENCES 126 +#define TK_AUTOINCR 127 +#define TK_INSERT 128 +#define TK_DELETE 129 +#define TK_UPDATE 130 +#define TK_SET 131 +#define TK_DEFERRABLE 132 +#define TK_FOREIGN 133 +#define TK_DROP 134 +#define TK_UNION 135 +#define TK_ALL 136 +#define TK_EXCEPT 137 +#define TK_INTERSECT 138 +#define TK_SELECT 139 +#define TK_VALUES 140 +#define TK_DISTINCT 141 +#define TK_DOT 142 +#define TK_FROM 143 +#define TK_JOIN 144 +#define TK_USING 145 +#define TK_ORDER 146 +#define TK_GROUP 147 +#define TK_HAVING 148 +#define TK_LIMIT 149 +#define TK_WHERE 150 +#define TK_RETURNING 151 +#define TK_INTO 152 +#define TK_NOTHING 153 +#define TK_FLOAT 154 +#define TK_BLOB 155 +#define TK_INTEGER 156 +#define TK_VARIABLE 157 +#define TK_CASE 158 +#define TK_WHEN 159 +#define TK_THEN 160 +#define TK_ELSE 161 +#define TK_INDEX 162 +#define TK_ALTER 163 +#define TK_ADD 164 +#define TK_WINDOW 165 +#define TK_OVER 166 +#define TK_FILTER 167 +#define TK_COLUMN 168 +#define TK_AGG_FUNCTION 169 +#define TK_AGG_COLUMN 170 +#define TK_TRUEFALSE 171 +#define TK_FUNCTION 172 +#define TK_UPLUS 173 +#define TK_UMINUS 174 +#define TK_TRUTH 175 +#define TK_REGISTER 176 +#define TK_VECTOR 177 +#define TK_SELECT_COLUMN 178 +#define TK_IF_NULL_ROW 179 +#define TK_ASTERISK 180 +#define TK_SPAN 181 +#define TK_ERROR 182 +#define TK_QNUMBER 183 +#define TK_SPACE 184 +#define TK_COMMENT 185 +#define TK_ILLEGAL 186 +#endif +/**************** End token definitions ***************************************/ /* The next sections is a series of control #defines. ** various aspects of the generated parser. @@ -148332,7 +180366,7 @@ static void disableLookaside(Parse *pParse){ ** the minor type might be the name of the identifier. ** Each non-terminal can have a different minor type. ** Terminal symbols all have the same minor type, though. -** This macros defines the minor type for terminal +** This macros defines the minor type for terminal ** symbols. ** YYMINORTYPE is the data type used for all minor types. ** This is typically a union of many types, one of @@ -148346,6 +180380,9 @@ static void disableLookaside(Parse *pParse){ ** sqlite3ParserARG_STORE Code to store %extra_argument into yypParser ** sqlite3ParserARG_FETCH Code to extract %extra_argument from yypParser ** sqlite3ParserCTX_* As sqlite3ParserARG_ except for %extra_context +** YYREALLOC Name of the realloc() function to use +** YYFREE Name of the free() function to use +** YYDYNSTACK True if stack space should be extended on heap ** YYERRORSYMBOL is the code number of the error symbol. If not ** defined, then do no error processing. ** YYNSTATE the combined number of states. @@ -148359,60 +180396,80 @@ static void disableLookaside(Parse *pParse){ ** YY_NO_ACTION The yy_action[] code for no-op ** YY_MIN_REDUCE Minimum value for reduce actions ** YY_MAX_REDUCE Maximum value for reduce actions +** YY_MIN_DSTRCTR Minimum symbol value that has a destructor +** YY_MAX_DSTRCTR Maximum symbol value that has a destructor */ #ifndef INTERFACE # define INTERFACE 1 #endif /************* Begin control #defines *****************************************/ #define YYCODETYPE unsigned short int -#define YYNOCODE 301 +#define YYNOCODE 322 #define YYACTIONTYPE unsigned short int -#define YYWILDCARD 95 +#define YYWILDCARD 102 #define sqlite3ParserTOKENTYPE Token typedef union { int yyinit; sqlite3ParserTOKENTYPE yy0; + ExprList* yy14; With* yy59; - IdList* yy62; - struct TrigEvent yy90; - Upsert* yy136; - struct FrameBound yy201; - u8 yy238; - const char* yy294; - Window* yy295; - struct {int value; int mask;} yy355; - ExprList* yy434; - TriggerStep* yy455; - Select* yy457; - SrcList* yy483; - int yy494; - Expr* yy524; + Cte* yy67; + Upsert* yy122; + IdList* yy132; + int yy144; + const char* yy168; + SrcList* yy203; + Window* yy211; + OnOrUsing yy269; + struct TrigEvent yy286; + struct {int value; int mask;} yy383; + u32 yy391; + TriggerStep* yy427; + Expr* yy454; + u8 yy462; + struct FrameBound yy509; + Select* yy555; } YYMINORTYPE; #ifndef YYSTACKDEPTH -#define YYSTACKDEPTH 100 +#define YYSTACKDEPTH 50 #endif #define sqlite3ParserARG_SDECL #define sqlite3ParserARG_PDECL #define sqlite3ParserARG_PARAM #define sqlite3ParserARG_FETCH #define sqlite3ParserARG_STORE +#undef YYREALLOC +#define YYREALLOC parserStackRealloc +#undef YYFREE +#define YYFREE parserStackFree +#undef YYDYNSTACK +#define YYDYNSTACK 1 +#undef YYSIZELIMIT +#define YYSIZELIMIT parserStackSizeLimit +#define sqlite3ParserCTX(P) ((P)->pParse) #define sqlite3ParserCTX_SDECL Parse *pParse; #define sqlite3ParserCTX_PDECL ,Parse *pParse #define sqlite3ParserCTX_PARAM ,pParse #define sqlite3ParserCTX_FETCH Parse *pParse=yypParser->pParse; #define sqlite3ParserCTX_STORE yypParser->pParse=pParse; +#undef YYERRORSYMBOL +#undef YYERRSYMDT +#undef YYFALLBACK #define YYFALLBACK 1 -#define YYNSTATE 541 -#define YYNRULE 375 -#define YYNTOKEN 176 -#define YY_MAX_SHIFT 540 -#define YY_MIN_SHIFTREDUCE 784 -#define YY_MAX_SHIFTREDUCE 1158 -#define YY_ERROR_ACTION 1159 -#define YY_ACCEPT_ACTION 1160 -#define YY_NO_ACTION 1161 -#define YY_MIN_REDUCE 1162 -#define YY_MAX_REDUCE 1536 +#define YYNSTATE 600 +#define YYNRULE 412 +#define YYNRULE_WITH_ACTION 348 +#define YYNTOKEN 187 +#define YY_MAX_SHIFT 599 +#define YY_MIN_SHIFTREDUCE 867 +#define YY_MAX_SHIFTREDUCE 1278 +#define YY_ERROR_ACTION 1279 +#define YY_ACCEPT_ACTION 1280 +#define YY_NO_ACTION 1281 +#define YY_MIN_REDUCE 1282 +#define YY_MAX_REDUCE 1693 +#define YY_MIN_DSTRCTR 206 +#define YY_MAX_DSTRCTR 319 /************* End control #defines *******************************************/ #define YY_NLOOKAHEAD ((int)(sizeof(yy_lookahead)/sizeof(yy_lookahead[0]))) @@ -148428,11 +180485,27 @@ typedef union { # define yytestcase(X) #endif +/* Macro to determine if stack space has the ability to grow using +** heap memory. +*/ +#if YYSTACKDEPTH<=0 || YYDYNSTACK +# define YYGROWABLESTACK 1 +#else +# define YYGROWABLESTACK 0 +#endif + +/* Guarantee a minimum number of initial stack slots. +*/ +#if YYSTACKDEPTH<=0 +# undef YYSTACKDEPTH +# define YYSTACKDEPTH 2 /* Need a minimum stack size */ +#endif + /* Next are the tables used to determine what action to take based on the ** current state and lookahead token. These tables are used to implement ** functions that take a state number and lookahead value and return an -** action integer. +** action integer. ** ** Suppose the action integer is N. Then the action is determined as ** follows @@ -148479,609 +180552,686 @@ typedef union { ** yy_default[] Default action for each state. ** *********** Begin parsing tables **********************************************/ -#define YY_ACTTAB_COUNT (2142) +#define YY_ACTTAB_COUNT (2379) static const YYACTIONTYPE yy_action[] = { - /* 0 */ 535, 1323, 112, 109, 209, 112, 109, 209, 1160, 1, - /* 10 */ 1, 540, 2, 1164, 535, 1292, 1228, 1207, 289, 384, - /* 20 */ 134, 42, 42, 1427, 382, 1228, 9, 1241, 242, 492, - /* 30 */ 1291, 915, 373, 379, 1026, 70, 70, 427, 1026, 916, - /* 40 */ 529, 529, 529, 119, 120, 110, 1136, 1136, 981, 984, - /* 50 */ 974, 974, 117, 117, 118, 118, 118, 118, 380, 264, - /* 60 */ 264, 264, 264, 1134, 264, 264, 112, 109, 209, 397, - /* 70 */ 454, 517, 532, 491, 532, 1233, 1233, 532, 239, 206, - /* 80 */ 493, 112, 109, 209, 464, 219, 118, 118, 118, 118, - /* 90 */ 111, 393, 440, 444, 16, 16, 116, 116, 116, 116, - /* 100 */ 115, 115, 114, 114, 114, 113, 415, 971, 971, 982, - /* 110 */ 985, 235, 1463, 351, 1134, 419, 384, 116, 116, 116, - /* 120 */ 116, 115, 115, 114, 114, 114, 113, 415, 116, 116, - /* 130 */ 116, 116, 115, 115, 114, 114, 114, 113, 415, 961, - /* 140 */ 119, 120, 110, 1136, 1136, 981, 984, 974, 974, 117, - /* 150 */ 117, 118, 118, 118, 118, 952, 415, 941, 298, 951, - /* 160 */ 941, 1480, 540, 2, 1164, 1115, 535, 1458, 160, 289, - /* 170 */ 6, 134, 1504, 389, 406, 975, 338, 1024, 1241, 337, - /* 180 */ 1089, 1476, 1089, 118, 118, 118, 118, 42, 42, 329, - /* 190 */ 951, 951, 953, 116, 116, 116, 116, 115, 115, 114, - /* 200 */ 114, 114, 113, 415, 311, 430, 299, 311, 881, 160, - /* 210 */ 264, 264, 401, 384, 324, 1115, 1116, 1117, 288, 526, - /* 220 */ 96, 159, 1441, 532, 141, 116, 116, 116, 116, 115, - /* 230 */ 115, 114, 114, 114, 113, 415, 219, 119, 120, 110, - /* 240 */ 1136, 1136, 981, 984, 974, 974, 117, 117, 118, 118, - /* 250 */ 118, 118, 115, 115, 114, 114, 114, 113, 415, 288, - /* 260 */ 526, 403, 533, 121, 870, 870, 419, 250, 267, 336, - /* 270 */ 475, 331, 474, 236, 160, 319, 1084, 322, 1465, 329, - /* 280 */ 350, 12, 535, 384, 502, 1115, 1084, 435, 312, 1084, - /* 290 */ 116, 116, 116, 116, 115, 115, 114, 114, 114, 113, - /* 300 */ 415, 535, 836, 42, 42, 138, 426, 119, 120, 110, - /* 310 */ 1136, 1136, 981, 984, 974, 974, 117, 117, 118, 118, - /* 320 */ 118, 118, 70, 70, 288, 526, 412, 411, 480, 1457, - /* 330 */ 335, 79, 6, 473, 1140, 1115, 1116, 1117, 501, 1142, - /* 340 */ 334, 837, 811, 1484, 512, 1164, 534, 1141, 123, 187, - /* 350 */ 289, 384, 134, 448, 434, 1115, 80, 349, 498, 1241, - /* 360 */ 116, 116, 116, 116, 115, 115, 114, 114, 114, 113, - /* 370 */ 415, 1143, 1115, 1143, 459, 119, 120, 110, 1136, 1136, - /* 380 */ 981, 984, 974, 974, 117, 117, 118, 118, 118, 118, - /* 390 */ 404, 264, 264, 811, 1463, 506, 368, 1156, 535, 114, - /* 400 */ 114, 114, 113, 415, 532, 1115, 1116, 1117, 231, 518, - /* 410 */ 1500, 472, 469, 468, 175, 497, 422, 219, 1202, 70, - /* 420 */ 70, 467, 1115, 1116, 1117, 176, 201, 200, 116, 116, - /* 430 */ 116, 116, 115, 115, 114, 114, 114, 113, 415, 535, - /* 440 */ 1115, 264, 264, 435, 312, 1115, 273, 419, 384, 513, - /* 450 */ 1450, 1115, 326, 1084, 532, 517, 82, 1084, 167, 388, - /* 460 */ 69, 69, 1115, 1084, 519, 509, 1084, 1084, 12, 1157, - /* 470 */ 1084, 420, 119, 120, 110, 1136, 1136, 981, 984, 974, - /* 480 */ 974, 117, 117, 118, 118, 118, 118, 258, 258, 535, - /* 490 */ 1115, 1116, 1117, 1045, 535, 1115, 1116, 1117, 1323, 535, - /* 500 */ 532, 1115, 1116, 1117, 296, 483, 1211, 818, 1046, 448, - /* 510 */ 70, 70, 1115, 1116, 1117, 50, 50, 448, 356, 500, - /* 520 */ 70, 70, 207, 1047, 32, 116, 116, 116, 116, 115, - /* 530 */ 115, 114, 114, 114, 113, 415, 453, 264, 264, 1115, - /* 540 */ 450, 449, 961, 508, 856, 384, 517, 5, 900, 822, - /* 550 */ 532, 484, 181, 1115, 857, 516, 517, 818, 952, 507, - /* 560 */ 3, 1115, 951, 1231, 1231, 482, 398, 1115, 1095, 119, - /* 570 */ 120, 110, 1136, 1136, 981, 984, 974, 974, 117, 117, - /* 580 */ 118, 118, 118, 118, 1115, 535, 238, 1115, 1391, 1115, - /* 590 */ 1116, 1117, 159, 951, 951, 953, 231, 1115, 259, 472, - /* 600 */ 469, 468, 310, 1115, 1116, 1117, 13, 13, 297, 467, - /* 610 */ 276, 1115, 1116, 1117, 412, 411, 1095, 1115, 1116, 1117, - /* 620 */ 395, 355, 116, 116, 116, 116, 115, 115, 114, 114, - /* 630 */ 114, 113, 415, 208, 1115, 1116, 1117, 1115, 1116, 1117, - /* 640 */ 264, 264, 384, 337, 902, 393, 815, 1115, 1116, 1117, - /* 650 */ 413, 413, 413, 532, 112, 109, 209, 309, 900, 1143, - /* 660 */ 535, 1143, 535, 393, 901, 1210, 119, 120, 110, 1136, - /* 670 */ 1136, 981, 984, 974, 974, 117, 117, 118, 118, 118, - /* 680 */ 118, 13, 13, 13, 13, 265, 265, 535, 143, 264, - /* 690 */ 264, 288, 526, 535, 1119, 400, 535, 402, 532, 510, - /* 700 */ 1457, 512, 532, 6, 113, 415, 1067, 1530, 70, 70, - /* 710 */ 1530, 535, 271, 535, 70, 70, 535, 13, 13, 116, - /* 720 */ 116, 116, 116, 115, 115, 114, 114, 114, 113, 415, - /* 730 */ 272, 277, 13, 13, 13, 13, 535, 13, 13, 384, - /* 740 */ 535, 304, 425, 1100, 284, 1119, 184, 801, 185, 338, - /* 750 */ 285, 514, 1532, 369, 1239, 1438, 1182, 70, 70, 425, - /* 760 */ 424, 70, 70, 119, 120, 110, 1136, 1136, 981, 984, - /* 770 */ 974, 974, 117, 117, 118, 118, 118, 118, 190, 1065, - /* 780 */ 1067, 1531, 442, 107, 1531, 408, 264, 264, 264, 264, - /* 790 */ 383, 1396, 261, 410, 95, 900, 485, 414, 421, 532, - /* 800 */ 1045, 532, 301, 1133, 303, 488, 433, 1451, 1396, 1398, - /* 810 */ 278, 535, 278, 520, 1435, 1046, 116, 116, 116, 116, - /* 820 */ 115, 115, 114, 114, 114, 113, 415, 425, 264, 264, - /* 830 */ 1047, 190, 54, 54, 535, 291, 384, 264, 264, 362, - /* 840 */ 962, 532, 1004, 376, 1084, 264, 264, 1029, 1029, 456, - /* 850 */ 532, 523, 270, 1065, 1084, 55, 55, 1084, 532, 442, - /* 860 */ 119, 120, 110, 1136, 1136, 981, 984, 974, 974, 117, - /* 870 */ 117, 118, 118, 118, 118, 535, 1396, 190, 302, 1383, - /* 880 */ 208, 535, 789, 790, 791, 535, 515, 535, 1323, 371, - /* 890 */ 337, 234, 233, 232, 459, 515, 15, 15, 459, 477, - /* 900 */ 459, 459, 44, 44, 136, 900, 56, 56, 57, 57, - /* 910 */ 1185, 390, 197, 116, 116, 116, 116, 115, 115, 114, - /* 920 */ 114, 114, 113, 415, 535, 876, 535, 442, 535, 274, - /* 930 */ 875, 1323, 357, 384, 353, 140, 1426, 946, 1455, 1323, - /* 940 */ 1390, 6, 1240, 1236, 292, 58, 58, 59, 59, 60, - /* 950 */ 60, 535, 1456, 384, 535, 6, 399, 119, 120, 110, - /* 960 */ 1136, 1136, 981, 984, 974, 974, 117, 117, 118, 118, - /* 970 */ 118, 118, 61, 61, 535, 45, 45, 119, 120, 110, - /* 980 */ 1136, 1136, 981, 984, 974, 974, 117, 117, 118, 118, - /* 990 */ 118, 118, 1477, 479, 202, 46, 46, 275, 95, 455, - /* 1000 */ 535, 212, 535, 337, 535, 1454, 535, 409, 6, 242, - /* 1010 */ 116, 116, 116, 116, 115, 115, 114, 114, 114, 113, - /* 1020 */ 415, 48, 48, 49, 49, 62, 62, 63, 63, 535, - /* 1030 */ 116, 116, 116, 116, 115, 115, 114, 114, 114, 113, - /* 1040 */ 415, 535, 459, 535, 1134, 535, 1151, 535, 142, 535, - /* 1050 */ 64, 64, 535, 1338, 535, 494, 535, 446, 535, 1264, - /* 1060 */ 535, 1337, 14, 14, 65, 65, 125, 125, 66, 66, - /* 1070 */ 51, 51, 535, 67, 67, 68, 68, 52, 52, 147, - /* 1080 */ 147, 148, 148, 1453, 317, 98, 6, 535, 1245, 481, - /* 1090 */ 535, 827, 535, 75, 75, 1134, 102, 481, 100, 535, - /* 1100 */ 532, 535, 368, 1066, 1503, 384, 535, 845, 53, 53, - /* 1110 */ 93, 71, 71, 126, 126, 295, 528, 390, 288, 526, - /* 1120 */ 72, 72, 127, 127, 139, 384, 38, 128, 128, 119, - /* 1130 */ 120, 110, 1136, 1136, 981, 984, 974, 974, 117, 117, - /* 1140 */ 118, 118, 118, 118, 535, 495, 535, 447, 535, 119, - /* 1150 */ 120, 110, 1136, 1136, 981, 984, 974, 974, 117, 117, - /* 1160 */ 118, 118, 118, 118, 235, 124, 124, 146, 146, 145, - /* 1170 */ 145, 287, 535, 1277, 535, 1157, 535, 391, 161, 263, - /* 1180 */ 206, 381, 116, 116, 116, 116, 115, 115, 114, 114, - /* 1190 */ 114, 113, 415, 132, 132, 131, 131, 129, 129, 535, - /* 1200 */ 30, 535, 116, 116, 116, 116, 115, 115, 114, 114, - /* 1210 */ 114, 113, 415, 535, 216, 1062, 1276, 535, 370, 535, - /* 1220 */ 130, 130, 74, 74, 535, 915, 389, 876, 17, 437, - /* 1230 */ 429, 31, 875, 916, 76, 76, 266, 101, 73, 73, - /* 1240 */ 43, 43, 835, 834, 308, 47, 47, 95, 825, 943, - /* 1250 */ 441, 938, 241, 241, 305, 443, 313, 384, 241, 95, - /* 1260 */ 842, 843, 193, 465, 1209, 327, 237, 436, 95, 1011, - /* 1270 */ 1007, 909, 873, 237, 241, 107, 1023, 384, 1023, 955, - /* 1280 */ 1415, 119, 120, 110, 1136, 1136, 981, 984, 974, 974, - /* 1290 */ 117, 117, 118, 118, 118, 118, 1022, 809, 1022, 825, - /* 1300 */ 137, 119, 108, 110, 1136, 1136, 981, 984, 974, 974, - /* 1310 */ 117, 117, 118, 118, 118, 118, 874, 1414, 451, 107, - /* 1320 */ 1011, 314, 1273, 318, 218, 321, 323, 325, 1224, 1208, - /* 1330 */ 955, 330, 339, 340, 116, 116, 116, 116, 115, 115, - /* 1340 */ 114, 114, 114, 113, 415, 1285, 1322, 1260, 1493, 1470, - /* 1350 */ 1271, 283, 521, 1328, 116, 116, 116, 116, 115, 115, - /* 1360 */ 114, 114, 114, 113, 415, 1191, 1184, 1173, 1172, 1174, - /* 1370 */ 522, 1487, 211, 460, 384, 256, 199, 367, 1257, 342, - /* 1380 */ 195, 470, 307, 344, 11, 333, 525, 445, 1307, 1315, - /* 1390 */ 375, 203, 1207, 1151, 384, 346, 1387, 188, 360, 120, - /* 1400 */ 110, 1136, 1136, 981, 984, 974, 974, 117, 117, 118, - /* 1410 */ 118, 118, 118, 1386, 428, 1490, 245, 300, 348, 1148, - /* 1420 */ 110, 1136, 1136, 981, 984, 974, 974, 117, 117, 118, - /* 1430 */ 118, 118, 118, 189, 198, 1434, 1432, 78, 81, 163, - /* 1440 */ 82, 392, 439, 1392, 173, 105, 527, 35, 4, 157, - /* 1450 */ 1312, 116, 116, 116, 116, 115, 115, 114, 114, 114, - /* 1460 */ 113, 415, 530, 165, 93, 1304, 431, 432, 168, 463, - /* 1470 */ 221, 116, 116, 116, 116, 115, 115, 114, 114, 114, - /* 1480 */ 113, 415, 169, 452, 170, 416, 171, 374, 372, 438, - /* 1490 */ 36, 1318, 177, 225, 1381, 87, 458, 524, 1403, 316, - /* 1500 */ 257, 105, 527, 227, 4, 182, 461, 160, 320, 228, - /* 1510 */ 377, 1175, 476, 229, 1227, 1226, 405, 1225, 530, 1218, - /* 1520 */ 961, 378, 1199, 1198, 827, 332, 103, 103, 1197, 407, - /* 1530 */ 8, 1217, 1502, 104, 487, 416, 537, 536, 281, 282, - /* 1540 */ 951, 416, 490, 1268, 496, 92, 341, 243, 1269, 343, - /* 1550 */ 244, 1267, 122, 524, 345, 1461, 515, 288, 526, 10, - /* 1560 */ 354, 1266, 1460, 352, 504, 1250, 99, 1367, 94, 503, - /* 1570 */ 499, 951, 951, 953, 954, 27, 961, 347, 1249, 194, - /* 1580 */ 251, 358, 103, 103, 359, 1181, 34, 538, 1110, 104, - /* 1590 */ 255, 416, 537, 536, 286, 252, 951, 254, 539, 149, - /* 1600 */ 1170, 1419, 1165, 1420, 1418, 150, 1417, 135, 279, 785, - /* 1610 */ 151, 417, 1195, 196, 290, 210, 386, 1194, 269, 387, - /* 1620 */ 162, 1021, 133, 77, 1192, 1019, 935, 951, 951, 953, - /* 1630 */ 954, 27, 1479, 1104, 418, 164, 153, 268, 217, 166, - /* 1640 */ 859, 306, 366, 366, 365, 253, 363, 220, 1035, 798, - /* 1650 */ 172, 939, 105, 527, 155, 4, 394, 174, 396, 156, - /* 1660 */ 83, 1038, 213, 84, 294, 85, 86, 223, 222, 530, - /* 1670 */ 1034, 144, 293, 18, 224, 315, 241, 1027, 1145, 178, - /* 1680 */ 457, 226, 179, 37, 800, 334, 462, 230, 328, 466, - /* 1690 */ 180, 471, 416, 88, 19, 20, 89, 280, 838, 158, - /* 1700 */ 191, 90, 215, 478, 524, 1097, 204, 192, 987, 91, - /* 1710 */ 152, 1070, 39, 154, 1071, 504, 486, 40, 489, 205, - /* 1720 */ 505, 260, 105, 527, 214, 4, 908, 961, 262, 183, - /* 1730 */ 240, 21, 903, 103, 103, 107, 22, 1086, 23, 530, - /* 1740 */ 104, 1088, 416, 537, 536, 24, 1093, 951, 25, 1074, - /* 1750 */ 1090, 1094, 7, 33, 511, 186, 26, 1002, 385, 95, - /* 1760 */ 988, 986, 416, 288, 526, 990, 1044, 246, 1043, 247, - /* 1770 */ 991, 28, 41, 106, 524, 956, 810, 29, 951, 951, - /* 1780 */ 953, 954, 27, 531, 361, 504, 423, 248, 869, 249, - /* 1790 */ 503, 1495, 364, 1105, 1161, 1494, 1161, 961, 1161, 1161, - /* 1800 */ 1161, 1161, 1161, 103, 103, 1161, 1161, 1161, 1161, 1161, - /* 1810 */ 104, 1161, 416, 537, 536, 1104, 418, 951, 1161, 268, - /* 1820 */ 1161, 1161, 1161, 1161, 366, 366, 365, 253, 363, 1161, - /* 1830 */ 1161, 798, 1161, 1161, 1161, 1161, 105, 527, 1161, 4, - /* 1840 */ 1161, 1161, 1161, 1161, 213, 1161, 294, 1161, 951, 951, - /* 1850 */ 953, 954, 27, 530, 293, 1161, 1161, 1161, 1161, 1161, - /* 1860 */ 1161, 1161, 1161, 1161, 1161, 1161, 1161, 1161, 1161, 1161, - /* 1870 */ 1161, 1161, 1161, 1161, 1161, 1161, 416, 1161, 1161, 1161, - /* 1880 */ 1161, 1161, 1161, 1161, 215, 1161, 1161, 1161, 524, 1161, - /* 1890 */ 1161, 1161, 152, 1161, 1161, 154, 105, 527, 1161, 4, - /* 1900 */ 1161, 1161, 1161, 1161, 1161, 1161, 214, 1161, 1161, 1161, - /* 1910 */ 1161, 961, 1161, 530, 1161, 1161, 1161, 103, 103, 880, - /* 1920 */ 1161, 1161, 1161, 1161, 104, 1161, 416, 537, 536, 1161, - /* 1930 */ 1161, 951, 1161, 1161, 1161, 1161, 416, 1161, 1161, 1161, - /* 1940 */ 385, 1161, 1161, 1161, 1161, 288, 526, 1161, 524, 1161, - /* 1950 */ 1161, 1161, 1161, 1161, 1161, 1161, 97, 527, 1161, 4, - /* 1960 */ 1161, 1161, 951, 951, 953, 954, 27, 1161, 423, 1161, - /* 1970 */ 1161, 961, 1161, 530, 1161, 1161, 1161, 103, 103, 1161, - /* 1980 */ 1161, 1161, 1161, 1161, 104, 1161, 416, 537, 536, 1161, - /* 1990 */ 1161, 951, 268, 1161, 1161, 1161, 416, 366, 366, 365, - /* 2000 */ 253, 363, 1161, 1161, 798, 1161, 1161, 1161, 524, 1161, - /* 2010 */ 1161, 1161, 1161, 1161, 1161, 1161, 1161, 213, 1161, 294, - /* 2020 */ 1161, 1161, 951, 951, 953, 954, 27, 293, 1161, 1161, - /* 2030 */ 1161, 961, 1161, 1161, 1161, 1161, 1161, 103, 103, 1161, - /* 2040 */ 1161, 1161, 1161, 1161, 104, 1161, 416, 537, 536, 1161, - /* 2050 */ 1161, 951, 1161, 1161, 1161, 1161, 1161, 215, 1161, 1161, - /* 2060 */ 1161, 1161, 1161, 1161, 1161, 152, 1161, 1161, 154, 1161, - /* 2070 */ 1161, 1161, 1161, 1161, 1161, 1161, 1161, 1161, 1161, 214, - /* 2080 */ 1161, 1161, 951, 951, 953, 954, 27, 1161, 1161, 1161, - /* 2090 */ 1161, 1161, 1161, 1161, 1161, 1161, 1161, 1161, 1161, 1161, - /* 2100 */ 1161, 1161, 1161, 1161, 1161, 1161, 1161, 1161, 1161, 1161, - /* 2110 */ 1161, 1161, 1161, 385, 1161, 1161, 1161, 1161, 288, 526, - /* 2120 */ 1161, 1161, 1161, 1161, 1161, 1161, 1161, 1161, 1161, 1161, - /* 2130 */ 1161, 1161, 1161, 1161, 1161, 1161, 1161, 1161, 1161, 1161, - /* 2140 */ 1161, 423, + /* 0 */ 134, 131, 238, 290, 290, 1353, 593, 1332, 478, 1606, + /* 10 */ 593, 1315, 593, 7, 593, 1353, 590, 593, 579, 424, + /* 20 */ 1566, 134, 131, 238, 1318, 541, 478, 477, 575, 84, + /* 30 */ 84, 1005, 303, 84, 84, 51, 51, 63, 63, 1006, + /* 40 */ 84, 84, 498, 141, 142, 93, 442, 1254, 1254, 1085, + /* 50 */ 1088, 1075, 1075, 139, 139, 140, 140, 140, 140, 424, + /* 60 */ 296, 296, 498, 296, 296, 567, 553, 296, 296, 1306, + /* 70 */ 574, 1358, 1358, 590, 542, 579, 590, 574, 579, 548, + /* 80 */ 590, 1304, 579, 141, 142, 93, 576, 1254, 1254, 1085, + /* 90 */ 1088, 1075, 1075, 139, 139, 140, 140, 140, 140, 399, + /* 100 */ 478, 395, 6, 138, 138, 138, 138, 137, 137, 136, + /* 110 */ 136, 136, 135, 132, 463, 44, 342, 593, 305, 1127, + /* 120 */ 1280, 1, 1, 599, 2, 1284, 598, 1200, 1284, 1200, + /* 130 */ 330, 424, 158, 330, 1613, 158, 390, 116, 308, 1366, + /* 140 */ 51, 51, 1366, 138, 138, 138, 138, 137, 137, 136, + /* 150 */ 136, 136, 135, 132, 463, 141, 142, 93, 515, 1254, + /* 160 */ 1254, 1085, 1088, 1075, 1075, 139, 139, 140, 140, 140, + /* 170 */ 140, 1230, 329, 584, 296, 296, 212, 296, 296, 568, + /* 180 */ 568, 488, 143, 1072, 1072, 1086, 1089, 590, 1195, 579, + /* 190 */ 590, 340, 579, 140, 140, 140, 140, 133, 392, 564, + /* 200 */ 536, 1195, 250, 425, 1195, 250, 137, 137, 136, 136, + /* 210 */ 136, 135, 132, 463, 291, 138, 138, 138, 138, 137, + /* 220 */ 137, 136, 136, 136, 135, 132, 463, 966, 1230, 1231, + /* 230 */ 1230, 412, 965, 467, 412, 424, 467, 489, 357, 1611, + /* 240 */ 391, 138, 138, 138, 138, 137, 137, 136, 136, 136, + /* 250 */ 135, 132, 463, 463, 134, 131, 238, 555, 1076, 141, + /* 260 */ 142, 93, 593, 1254, 1254, 1085, 1088, 1075, 1075, 139, + /* 270 */ 139, 140, 140, 140, 140, 1317, 134, 131, 238, 424, + /* 280 */ 549, 1597, 1531, 333, 97, 83, 83, 140, 140, 140, + /* 290 */ 140, 138, 138, 138, 138, 137, 137, 136, 136, 136, + /* 300 */ 135, 132, 463, 141, 142, 93, 1657, 1254, 1254, 1085, + /* 310 */ 1088, 1075, 1075, 139, 139, 140, 140, 140, 140, 138, + /* 320 */ 138, 138, 138, 137, 137, 136, 136, 136, 135, 132, + /* 330 */ 463, 591, 1230, 958, 958, 138, 138, 138, 138, 137, + /* 340 */ 137, 136, 136, 136, 135, 132, 463, 44, 398, 547, + /* 350 */ 1306, 136, 136, 136, 135, 132, 463, 386, 593, 442, + /* 360 */ 595, 145, 595, 138, 138, 138, 138, 137, 137, 136, + /* 370 */ 136, 136, 135, 132, 463, 500, 1230, 112, 550, 460, + /* 380 */ 459, 51, 51, 424, 296, 296, 479, 334, 1259, 1230, + /* 390 */ 1231, 1230, 1599, 1261, 388, 312, 444, 590, 246, 579, + /* 400 */ 546, 1260, 271, 235, 329, 584, 551, 141, 142, 93, + /* 410 */ 429, 1254, 1254, 1085, 1088, 1075, 1075, 139, 139, 140, + /* 420 */ 140, 140, 140, 22, 22, 1230, 1262, 424, 1262, 216, + /* 430 */ 296, 296, 98, 1230, 1231, 1230, 264, 884, 45, 528, + /* 440 */ 525, 524, 1041, 590, 1269, 579, 421, 420, 393, 523, + /* 450 */ 44, 141, 142, 93, 498, 1254, 1254, 1085, 1088, 1075, + /* 460 */ 1075, 139, 139, 140, 140, 140, 140, 138, 138, 138, + /* 470 */ 138, 137, 137, 136, 136, 136, 135, 132, 463, 593, + /* 480 */ 1611, 561, 1230, 1231, 1230, 23, 264, 515, 200, 528, + /* 490 */ 525, 524, 127, 585, 509, 4, 355, 487, 506, 523, + /* 500 */ 593, 498, 84, 84, 134, 131, 238, 329, 584, 588, + /* 510 */ 1627, 138, 138, 138, 138, 137, 137, 136, 136, 136, + /* 520 */ 135, 132, 463, 19, 19, 435, 1230, 1460, 297, 297, + /* 530 */ 311, 424, 1565, 464, 1631, 599, 2, 1284, 437, 574, + /* 540 */ 1107, 590, 330, 579, 158, 582, 489, 357, 573, 593, + /* 550 */ 592, 1366, 409, 1274, 1230, 141, 142, 93, 1364, 1254, + /* 560 */ 1254, 1085, 1088, 1075, 1075, 139, 139, 140, 140, 140, + /* 570 */ 140, 389, 84, 84, 1062, 567, 1230, 313, 1523, 593, + /* 580 */ 125, 125, 970, 1230, 1231, 1230, 296, 296, 126, 46, + /* 590 */ 464, 594, 464, 296, 296, 1050, 1230, 218, 439, 590, + /* 600 */ 1604, 579, 84, 84, 7, 403, 590, 515, 579, 325, + /* 610 */ 417, 1230, 1231, 1230, 250, 138, 138, 138, 138, 137, + /* 620 */ 137, 136, 136, 136, 135, 132, 463, 1050, 1050, 1052, + /* 630 */ 1053, 35, 1275, 1230, 1231, 1230, 424, 1370, 993, 574, + /* 640 */ 371, 414, 274, 412, 1597, 467, 1302, 552, 451, 590, + /* 650 */ 543, 579, 1530, 1230, 1231, 1230, 1214, 201, 409, 1174, + /* 660 */ 141, 142, 93, 223, 1254, 1254, 1085, 1088, 1075, 1075, + /* 670 */ 139, 139, 140, 140, 140, 140, 296, 296, 1250, 593, + /* 680 */ 424, 296, 296, 236, 529, 296, 296, 515, 100, 590, + /* 690 */ 1600, 579, 48, 1605, 590, 1230, 579, 7, 590, 577, + /* 700 */ 579, 904, 84, 84, 141, 142, 93, 496, 1254, 1254, + /* 710 */ 1085, 1088, 1075, 1075, 139, 139, 140, 140, 140, 140, + /* 720 */ 138, 138, 138, 138, 137, 137, 136, 136, 136, 135, + /* 730 */ 132, 463, 1365, 1230, 296, 296, 1250, 115, 1275, 326, + /* 740 */ 233, 539, 1062, 40, 282, 127, 585, 590, 4, 579, + /* 750 */ 329, 584, 1230, 1231, 1230, 1598, 593, 388, 904, 1051, + /* 760 */ 1356, 1356, 588, 1050, 138, 138, 138, 138, 137, 137, + /* 770 */ 136, 136, 136, 135, 132, 463, 185, 593, 1230, 19, + /* 780 */ 19, 1230, 971, 1597, 424, 1651, 464, 129, 908, 1195, + /* 790 */ 1230, 1231, 1230, 1325, 443, 1050, 1050, 1052, 582, 1603, + /* 800 */ 149, 149, 1195, 7, 5, 1195, 1687, 410, 141, 142, + /* 810 */ 93, 1536, 1254, 1254, 1085, 1088, 1075, 1075, 139, 139, + /* 820 */ 140, 140, 140, 140, 1214, 397, 593, 1062, 424, 1536, + /* 830 */ 1538, 50, 901, 125, 125, 1230, 1231, 1230, 1230, 1231, + /* 840 */ 1230, 126, 1230, 464, 594, 464, 515, 1230, 1050, 84, + /* 850 */ 84, 3, 141, 142, 93, 924, 1254, 1254, 1085, 1088, + /* 860 */ 1075, 1075, 139, 139, 140, 140, 140, 140, 138, 138, + /* 870 */ 138, 138, 137, 137, 136, 136, 136, 135, 132, 463, + /* 880 */ 1050, 1050, 1052, 1053, 35, 442, 457, 532, 433, 1230, + /* 890 */ 1062, 1361, 540, 540, 1598, 925, 388, 7, 1129, 1230, + /* 900 */ 1231, 1230, 1129, 1536, 1230, 1231, 1230, 1051, 570, 1214, + /* 910 */ 593, 1050, 138, 138, 138, 138, 137, 137, 136, 136, + /* 920 */ 136, 135, 132, 463, 6, 185, 1195, 1230, 231, 593, + /* 930 */ 382, 992, 424, 151, 151, 510, 1213, 557, 482, 1195, + /* 940 */ 381, 160, 1195, 1050, 1050, 1052, 1230, 1231, 1230, 422, + /* 950 */ 593, 447, 84, 84, 593, 217, 141, 142, 93, 593, + /* 960 */ 1254, 1254, 1085, 1088, 1075, 1075, 139, 139, 140, 140, + /* 970 */ 140, 140, 1214, 19, 19, 593, 424, 19, 19, 442, + /* 980 */ 1063, 442, 19, 19, 1230, 1231, 1230, 515, 445, 458, + /* 990 */ 1597, 386, 315, 1175, 1685, 556, 1685, 450, 84, 84, + /* 1000 */ 141, 142, 93, 505, 1254, 1254, 1085, 1088, 1075, 1075, + /* 1010 */ 139, 139, 140, 140, 140, 140, 138, 138, 138, 138, + /* 1020 */ 137, 137, 136, 136, 136, 135, 132, 463, 442, 1147, + /* 1030 */ 454, 1597, 362, 1041, 593, 462, 1460, 1233, 47, 1393, + /* 1040 */ 324, 565, 565, 115, 1148, 449, 7, 460, 459, 307, + /* 1050 */ 375, 354, 593, 113, 593, 329, 584, 19, 19, 1149, + /* 1060 */ 138, 138, 138, 138, 137, 137, 136, 136, 136, 135, + /* 1070 */ 132, 463, 209, 1173, 563, 19, 19, 19, 19, 49, + /* 1080 */ 424, 944, 1175, 1686, 1046, 1686, 218, 355, 484, 343, + /* 1090 */ 210, 945, 569, 562, 1262, 1233, 1262, 490, 314, 423, + /* 1100 */ 424, 1598, 1206, 388, 141, 142, 93, 440, 1254, 1254, + /* 1110 */ 1085, 1088, 1075, 1075, 139, 139, 140, 140, 140, 140, + /* 1120 */ 352, 316, 531, 316, 141, 142, 93, 549, 1254, 1254, + /* 1130 */ 1085, 1088, 1075, 1075, 139, 139, 140, 140, 140, 140, + /* 1140 */ 446, 10, 1598, 274, 388, 915, 281, 299, 383, 534, + /* 1150 */ 378, 533, 269, 593, 1206, 587, 587, 587, 374, 293, + /* 1160 */ 1579, 991, 1173, 302, 138, 138, 138, 138, 137, 137, + /* 1170 */ 136, 136, 136, 135, 132, 463, 53, 53, 520, 1250, + /* 1180 */ 593, 1147, 1576, 431, 138, 138, 138, 138, 137, 137, + /* 1190 */ 136, 136, 136, 135, 132, 463, 1148, 301, 593, 1577, + /* 1200 */ 593, 1307, 431, 54, 54, 593, 268, 593, 461, 461, + /* 1210 */ 461, 1149, 347, 492, 424, 135, 132, 463, 1146, 1195, + /* 1220 */ 474, 68, 68, 69, 69, 550, 332, 287, 21, 21, + /* 1230 */ 55, 55, 1195, 581, 424, 1195, 309, 1250, 141, 142, + /* 1240 */ 93, 119, 1254, 1254, 1085, 1088, 1075, 1075, 139, 139, + /* 1250 */ 140, 140, 140, 140, 593, 237, 480, 1476, 141, 142, + /* 1260 */ 93, 593, 1254, 1254, 1085, 1088, 1075, 1075, 139, 139, + /* 1270 */ 140, 140, 140, 140, 344, 430, 346, 70, 70, 494, + /* 1280 */ 991, 1132, 1132, 512, 56, 56, 1269, 593, 268, 593, + /* 1290 */ 369, 374, 593, 481, 215, 384, 1624, 481, 138, 138, + /* 1300 */ 138, 138, 137, 137, 136, 136, 136, 135, 132, 463, + /* 1310 */ 71, 71, 72, 72, 225, 73, 73, 593, 138, 138, + /* 1320 */ 138, 138, 137, 137, 136, 136, 136, 135, 132, 463, + /* 1330 */ 586, 431, 593, 872, 873, 874, 593, 911, 593, 1602, + /* 1340 */ 74, 74, 593, 7, 1460, 242, 593, 306, 424, 1578, + /* 1350 */ 472, 306, 364, 219, 367, 75, 75, 430, 345, 57, + /* 1360 */ 57, 58, 58, 432, 187, 59, 59, 593, 424, 61, + /* 1370 */ 61, 1475, 141, 142, 93, 123, 1254, 1254, 1085, 1088, + /* 1380 */ 1075, 1075, 139, 139, 140, 140, 140, 140, 424, 570, + /* 1390 */ 62, 62, 141, 142, 93, 911, 1254, 1254, 1085, 1088, + /* 1400 */ 1075, 1075, 139, 139, 140, 140, 140, 140, 161, 384, + /* 1410 */ 1624, 1474, 141, 130, 93, 441, 1254, 1254, 1085, 1088, + /* 1420 */ 1075, 1075, 139, 139, 140, 140, 140, 140, 267, 266, + /* 1430 */ 265, 1460, 138, 138, 138, 138, 137, 137, 136, 136, + /* 1440 */ 136, 135, 132, 463, 593, 1336, 593, 1269, 1460, 384, + /* 1450 */ 1624, 231, 138, 138, 138, 138, 137, 137, 136, 136, + /* 1460 */ 136, 135, 132, 463, 593, 163, 593, 76, 76, 77, + /* 1470 */ 77, 593, 138, 138, 138, 138, 137, 137, 136, 136, + /* 1480 */ 136, 135, 132, 463, 475, 593, 483, 78, 78, 20, + /* 1490 */ 20, 1249, 424, 491, 79, 79, 495, 422, 295, 235, + /* 1500 */ 1574, 38, 511, 896, 422, 335, 240, 422, 147, 147, + /* 1510 */ 112, 593, 424, 593, 101, 222, 991, 142, 93, 455, + /* 1520 */ 1254, 1254, 1085, 1088, 1075, 1075, 139, 139, 140, 140, + /* 1530 */ 140, 140, 593, 39, 148, 148, 80, 80, 93, 551, + /* 1540 */ 1254, 1254, 1085, 1088, 1075, 1075, 139, 139, 140, 140, + /* 1550 */ 140, 140, 328, 923, 922, 64, 64, 502, 1656, 1005, + /* 1560 */ 933, 896, 124, 422, 121, 254, 593, 1006, 593, 226, + /* 1570 */ 593, 127, 585, 164, 4, 16, 138, 138, 138, 138, + /* 1580 */ 137, 137, 136, 136, 136, 135, 132, 463, 588, 81, + /* 1590 */ 81, 65, 65, 82, 82, 593, 138, 138, 138, 138, + /* 1600 */ 137, 137, 136, 136, 136, 135, 132, 463, 593, 226, + /* 1610 */ 237, 966, 464, 593, 298, 593, 965, 593, 66, 66, + /* 1620 */ 593, 1170, 593, 411, 582, 353, 469, 115, 593, 471, + /* 1630 */ 169, 173, 173, 593, 44, 991, 174, 174, 89, 89, + /* 1640 */ 67, 67, 593, 85, 85, 150, 150, 1114, 1043, 593, + /* 1650 */ 273, 86, 86, 1062, 593, 503, 171, 171, 593, 125, + /* 1660 */ 125, 497, 593, 273, 336, 152, 152, 126, 1335, 464, + /* 1670 */ 594, 464, 146, 146, 1050, 593, 545, 172, 172, 593, + /* 1680 */ 1054, 165, 165, 256, 339, 156, 156, 127, 585, 1586, + /* 1690 */ 4, 329, 584, 499, 358, 273, 115, 348, 155, 155, + /* 1700 */ 930, 931, 153, 153, 588, 1114, 1050, 1050, 1052, 1053, + /* 1710 */ 35, 1554, 521, 593, 270, 1008, 1009, 9, 593, 372, + /* 1720 */ 593, 115, 593, 168, 593, 115, 593, 1110, 464, 270, + /* 1730 */ 996, 964, 273, 129, 1645, 1214, 154, 154, 1054, 1404, + /* 1740 */ 582, 88, 88, 90, 90, 87, 87, 52, 52, 60, + /* 1750 */ 60, 1405, 504, 537, 559, 1179, 961, 507, 129, 558, + /* 1760 */ 127, 585, 1126, 4, 1126, 1125, 894, 1125, 162, 1062, + /* 1770 */ 963, 359, 129, 1401, 363, 125, 125, 588, 366, 368, + /* 1780 */ 370, 1349, 1334, 126, 1333, 464, 594, 464, 377, 387, + /* 1790 */ 1050, 1391, 1414, 1618, 1459, 1387, 1399, 208, 580, 1464, + /* 1800 */ 1314, 464, 243, 516, 1305, 1293, 1384, 1292, 1294, 1638, + /* 1810 */ 288, 170, 228, 582, 12, 408, 321, 322, 241, 323, + /* 1820 */ 245, 1446, 1050, 1050, 1052, 1053, 35, 559, 304, 350, + /* 1830 */ 351, 501, 560, 127, 585, 1441, 4, 1451, 1434, 310, + /* 1840 */ 1450, 526, 1062, 1332, 415, 380, 232, 1527, 125, 125, + /* 1850 */ 588, 1214, 1396, 356, 1526, 583, 126, 1397, 464, 594, + /* 1860 */ 464, 1641, 535, 1050, 1581, 1395, 1269, 1583, 1582, 213, + /* 1870 */ 402, 277, 214, 227, 464, 1573, 239, 1571, 1266, 1394, + /* 1880 */ 434, 198, 100, 224, 96, 183, 582, 191, 485, 193, + /* 1890 */ 486, 194, 195, 196, 519, 1050, 1050, 1052, 1053, 35, + /* 1900 */ 559, 113, 252, 413, 1447, 558, 493, 13, 1455, 416, + /* 1910 */ 1453, 1452, 14, 202, 1521, 1062, 1532, 508, 258, 106, + /* 1920 */ 514, 125, 125, 99, 1214, 1543, 289, 260, 206, 126, + /* 1930 */ 365, 464, 594, 464, 361, 517, 1050, 261, 448, 1295, + /* 1940 */ 262, 418, 1352, 1351, 108, 1350, 1655, 1654, 1343, 915, + /* 1950 */ 419, 1322, 233, 452, 319, 379, 1321, 453, 1623, 320, + /* 1960 */ 1320, 275, 1653, 544, 276, 1609, 1608, 1342, 1050, 1050, + /* 1970 */ 1052, 1053, 35, 1630, 1218, 466, 385, 456, 300, 1419, + /* 1980 */ 144, 1418, 570, 407, 407, 406, 284, 404, 11, 1508, + /* 1990 */ 881, 396, 120, 127, 585, 394, 4, 1214, 327, 114, + /* 2000 */ 1375, 1374, 220, 247, 400, 338, 401, 554, 42, 1224, + /* 2010 */ 588, 596, 283, 337, 285, 286, 188, 597, 1290, 1285, + /* 2020 */ 175, 1558, 176, 1559, 1557, 1556, 159, 317, 229, 177, + /* 2030 */ 868, 230, 91, 465, 464, 221, 331, 468, 1165, 470, + /* 2040 */ 473, 94, 244, 95, 249, 189, 582, 1124, 1122, 341, + /* 2050 */ 427, 190, 178, 1249, 179, 43, 192, 947, 349, 428, + /* 2060 */ 1138, 197, 251, 180, 181, 436, 102, 182, 438, 103, + /* 2070 */ 104, 199, 248, 1140, 253, 1062, 105, 255, 1137, 166, + /* 2080 */ 24, 125, 125, 257, 1264, 273, 360, 513, 259, 126, + /* 2090 */ 15, 464, 594, 464, 204, 883, 1050, 518, 263, 373, + /* 2100 */ 381, 92, 585, 1130, 4, 203, 205, 426, 107, 522, + /* 2110 */ 25, 26, 329, 584, 913, 572, 527, 376, 588, 926, + /* 2120 */ 530, 109, 184, 318, 167, 110, 27, 538, 1050, 1050, + /* 2130 */ 1052, 1053, 35, 1211, 1091, 17, 476, 111, 1181, 234, + /* 2140 */ 292, 1180, 464, 294, 207, 994, 129, 1201, 272, 1000, + /* 2150 */ 28, 1197, 29, 30, 582, 1199, 1205, 1214, 31, 1204, + /* 2160 */ 32, 1186, 41, 566, 33, 1105, 211, 8, 115, 1092, + /* 2170 */ 1090, 1094, 34, 278, 578, 1095, 117, 122, 118, 1145, + /* 2180 */ 36, 18, 128, 1062, 1055, 895, 957, 37, 589, 125, + /* 2190 */ 125, 279, 186, 280, 1646, 157, 405, 126, 1220, 464, + /* 2200 */ 594, 464, 1218, 466, 1050, 1219, 300, 1281, 1281, 1281, + /* 2210 */ 1281, 407, 407, 406, 284, 404, 1281, 1281, 881, 1281, + /* 2220 */ 300, 1281, 1281, 571, 1281, 407, 407, 406, 284, 404, + /* 2230 */ 1281, 247, 881, 338, 1281, 1281, 1050, 1050, 1052, 1053, + /* 2240 */ 35, 337, 1281, 1281, 1281, 247, 1281, 338, 1281, 1281, + /* 2250 */ 1281, 1281, 1281, 1281, 1281, 337, 1281, 1281, 1281, 1281, + /* 2260 */ 1281, 1281, 1281, 1281, 1281, 1214, 1281, 1281, 1281, 1281, + /* 2270 */ 1281, 1281, 249, 1281, 1281, 1281, 1281, 1281, 1281, 1281, + /* 2280 */ 178, 1281, 1281, 43, 1281, 1281, 249, 1281, 1281, 1281, + /* 2290 */ 1281, 1281, 1281, 1281, 178, 1281, 1281, 43, 1281, 1281, + /* 2300 */ 248, 1281, 1281, 1281, 1281, 1281, 1281, 1281, 1281, 1281, + /* 2310 */ 1281, 1281, 1281, 1281, 248, 1281, 1281, 1281, 1281, 1281, + /* 2320 */ 1281, 1281, 1281, 1281, 1281, 1281, 1281, 1281, 1281, 1281, + /* 2330 */ 1281, 1281, 1281, 1281, 1281, 426, 1281, 1281, 1281, 1281, + /* 2340 */ 329, 584, 1281, 1281, 1281, 1281, 1281, 1281, 1281, 426, + /* 2350 */ 1281, 1281, 1281, 1281, 329, 584, 1281, 1281, 1281, 1281, + /* 2360 */ 1281, 1281, 1281, 1281, 476, 1281, 1281, 1281, 1281, 1281, + /* 2370 */ 1281, 1281, 1281, 1281, 1281, 1281, 1281, 1281, 476, }; static const YYCODETYPE yy_lookahead[] = { - /* 0 */ 184, 184, 259, 260, 261, 259, 260, 261, 176, 177, - /* 10 */ 178, 179, 180, 181, 184, 208, 212, 213, 186, 19, - /* 20 */ 188, 205, 206, 280, 205, 221, 22, 195, 24, 195, - /* 30 */ 208, 31, 195, 205, 29, 205, 206, 255, 33, 39, - /* 40 */ 200, 201, 202, 43, 44, 45, 46, 47, 48, 49, - /* 50 */ 50, 51, 52, 53, 54, 55, 56, 57, 205, 227, - /* 60 */ 228, 227, 228, 59, 227, 228, 259, 260, 261, 252, - /* 70 */ 65, 241, 240, 184, 240, 223, 224, 240, 244, 245, - /* 80 */ 250, 259, 260, 261, 19, 253, 54, 55, 56, 57, - /* 90 */ 58, 184, 255, 184, 205, 206, 96, 97, 98, 99, - /* 100 */ 100, 101, 102, 103, 104, 105, 106, 46, 47, 48, - /* 110 */ 49, 46, 296, 297, 110, 283, 19, 96, 97, 98, - /* 120 */ 99, 100, 101, 102, 103, 104, 105, 106, 96, 97, - /* 130 */ 98, 99, 100, 101, 102, 103, 104, 105, 106, 94, - /* 140 */ 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, - /* 150 */ 53, 54, 55, 56, 57, 110, 106, 73, 251, 114, - /* 160 */ 73, 178, 179, 180, 181, 59, 184, 292, 81, 186, - /* 170 */ 295, 188, 218, 108, 19, 114, 184, 11, 195, 184, - /* 180 */ 83, 184, 85, 54, 55, 56, 57, 205, 206, 124, - /* 190 */ 145, 146, 147, 96, 97, 98, 99, 100, 101, 102, - /* 200 */ 103, 104, 105, 106, 120, 121, 122, 120, 102, 81, - /* 210 */ 227, 228, 220, 19, 16, 109, 110, 111, 131, 132, - /* 220 */ 26, 184, 184, 240, 229, 96, 97, 98, 99, 100, - /* 230 */ 101, 102, 103, 104, 105, 106, 253, 43, 44, 45, - /* 240 */ 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, - /* 250 */ 56, 57, 100, 101, 102, 103, 104, 105, 106, 131, - /* 260 */ 132, 106, 127, 69, 129, 130, 283, 112, 113, 114, - /* 270 */ 115, 116, 117, 118, 81, 77, 76, 79, 296, 124, - /* 280 */ 298, 203, 184, 19, 84, 59, 86, 121, 122, 89, - /* 290 */ 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, - /* 300 */ 106, 184, 35, 205, 206, 22, 113, 43, 44, 45, - /* 310 */ 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, - /* 320 */ 56, 57, 205, 206, 131, 132, 100, 101, 291, 292, - /* 330 */ 114, 67, 295, 66, 108, 109, 110, 111, 138, 113, - /* 340 */ 124, 74, 59, 179, 184, 181, 184, 121, 22, 271, - /* 350 */ 186, 19, 188, 184, 276, 59, 24, 184, 241, 195, - /* 360 */ 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, - /* 370 */ 106, 145, 59, 147, 184, 43, 44, 45, 46, 47, - /* 380 */ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, - /* 390 */ 123, 227, 228, 110, 296, 297, 22, 23, 184, 102, - /* 400 */ 103, 104, 105, 106, 240, 109, 110, 111, 112, 195, - /* 410 */ 204, 115, 116, 117, 22, 184, 226, 253, 212, 205, - /* 420 */ 206, 125, 109, 110, 111, 22, 100, 101, 96, 97, - /* 430 */ 98, 99, 100, 101, 102, 103, 104, 105, 106, 184, - /* 440 */ 59, 227, 228, 121, 122, 59, 277, 283, 19, 289, - /* 450 */ 290, 59, 23, 76, 240, 241, 143, 76, 72, 189, - /* 460 */ 205, 206, 59, 86, 250, 84, 89, 86, 203, 95, - /* 470 */ 89, 281, 43, 44, 45, 46, 47, 48, 49, 50, - /* 480 */ 51, 52, 53, 54, 55, 56, 57, 227, 228, 184, - /* 490 */ 109, 110, 111, 12, 184, 109, 110, 111, 184, 184, - /* 500 */ 240, 109, 110, 111, 184, 195, 214, 59, 27, 184, - /* 510 */ 205, 206, 109, 110, 111, 205, 206, 184, 263, 138, - /* 520 */ 205, 206, 184, 42, 22, 96, 97, 98, 99, 100, - /* 530 */ 101, 102, 103, 104, 105, 106, 266, 227, 228, 59, - /* 540 */ 270, 276, 94, 66, 63, 19, 241, 22, 26, 23, - /* 550 */ 240, 241, 72, 59, 73, 250, 241, 109, 110, 82, - /* 560 */ 22, 59, 114, 223, 224, 250, 252, 59, 91, 43, - /* 570 */ 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, - /* 580 */ 54, 55, 56, 57, 59, 184, 26, 59, 268, 109, - /* 590 */ 110, 111, 184, 145, 146, 147, 112, 59, 203, 115, - /* 600 */ 116, 117, 277, 109, 110, 111, 205, 206, 195, 125, - /* 610 */ 277, 109, 110, 111, 100, 101, 139, 109, 110, 111, - /* 620 */ 219, 184, 96, 97, 98, 99, 100, 101, 102, 103, - /* 630 */ 104, 105, 106, 111, 109, 110, 111, 109, 110, 111, - /* 640 */ 227, 228, 19, 184, 136, 184, 23, 109, 110, 111, - /* 650 */ 200, 201, 202, 240, 259, 260, 261, 195, 136, 145, - /* 660 */ 184, 147, 184, 184, 136, 214, 43, 44, 45, 46, - /* 670 */ 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, - /* 680 */ 57, 205, 206, 205, 206, 227, 228, 184, 229, 227, - /* 690 */ 228, 131, 132, 184, 59, 219, 184, 219, 240, 291, - /* 700 */ 292, 184, 240, 295, 105, 106, 22, 23, 205, 206, - /* 710 */ 26, 184, 251, 184, 205, 206, 184, 205, 206, 96, - /* 720 */ 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, - /* 730 */ 251, 219, 205, 206, 205, 206, 184, 205, 206, 19, - /* 740 */ 184, 16, 184, 23, 241, 110, 219, 21, 219, 184, - /* 750 */ 241, 219, 286, 287, 195, 184, 195, 205, 206, 201, - /* 760 */ 202, 205, 206, 43, 44, 45, 46, 47, 48, 49, - /* 770 */ 50, 51, 52, 53, 54, 55, 56, 57, 184, 95, - /* 780 */ 22, 23, 184, 26, 26, 220, 227, 228, 227, 228, - /* 790 */ 196, 184, 23, 241, 26, 26, 195, 241, 184, 240, - /* 800 */ 12, 240, 77, 26, 79, 195, 80, 290, 201, 202, - /* 810 */ 216, 184, 218, 195, 184, 27, 96, 97, 98, 99, - /* 820 */ 100, 101, 102, 103, 104, 105, 106, 269, 227, 228, - /* 830 */ 42, 184, 205, 206, 184, 184, 19, 227, 228, 192, - /* 840 */ 23, 240, 116, 196, 76, 227, 228, 120, 121, 122, - /* 850 */ 240, 63, 254, 95, 86, 205, 206, 89, 240, 184, - /* 860 */ 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, - /* 870 */ 53, 54, 55, 56, 57, 184, 269, 184, 153, 153, - /* 880 */ 111, 184, 7, 8, 9, 184, 138, 184, 184, 196, - /* 890 */ 184, 120, 121, 122, 184, 138, 205, 206, 184, 102, - /* 900 */ 184, 184, 205, 206, 156, 136, 205, 206, 205, 206, - /* 910 */ 198, 199, 135, 96, 97, 98, 99, 100, 101, 102, - /* 920 */ 103, 104, 105, 106, 184, 128, 184, 184, 184, 254, - /* 930 */ 133, 184, 237, 19, 239, 229, 226, 23, 292, 184, - /* 940 */ 226, 295, 226, 226, 184, 205, 206, 205, 206, 205, - /* 950 */ 206, 184, 292, 19, 184, 295, 252, 43, 44, 45, - /* 960 */ 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, - /* 970 */ 56, 57, 205, 206, 184, 205, 206, 43, 44, 45, - /* 980 */ 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, - /* 990 */ 56, 57, 157, 158, 26, 205, 206, 254, 26, 252, - /* 1000 */ 184, 15, 184, 184, 184, 292, 184, 252, 295, 24, - /* 1010 */ 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, - /* 1020 */ 106, 205, 206, 205, 206, 205, 206, 205, 206, 184, - /* 1030 */ 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, - /* 1040 */ 106, 184, 184, 184, 59, 184, 60, 184, 229, 184, - /* 1050 */ 205, 206, 184, 258, 184, 19, 184, 19, 184, 246, - /* 1060 */ 184, 258, 205, 206, 205, 206, 205, 206, 205, 206, - /* 1070 */ 205, 206, 184, 205, 206, 205, 206, 205, 206, 205, - /* 1080 */ 206, 205, 206, 292, 226, 151, 295, 184, 228, 294, - /* 1090 */ 184, 119, 184, 205, 206, 110, 150, 294, 152, 184, - /* 1100 */ 240, 184, 22, 23, 23, 19, 184, 26, 205, 206, - /* 1110 */ 142, 205, 206, 205, 206, 184, 198, 199, 131, 132, - /* 1120 */ 205, 206, 205, 206, 22, 19, 24, 205, 206, 43, - /* 1130 */ 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, - /* 1140 */ 54, 55, 56, 57, 184, 109, 184, 109, 184, 43, - /* 1150 */ 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, - /* 1160 */ 54, 55, 56, 57, 46, 205, 206, 205, 206, 205, - /* 1170 */ 206, 232, 184, 184, 184, 95, 184, 284, 285, 244, - /* 1180 */ 245, 242, 96, 97, 98, 99, 100, 101, 102, 103, - /* 1190 */ 104, 105, 106, 205, 206, 205, 206, 205, 206, 184, - /* 1200 */ 22, 184, 96, 97, 98, 99, 100, 101, 102, 103, - /* 1210 */ 104, 105, 106, 184, 24, 23, 184, 184, 26, 184, - /* 1220 */ 205, 206, 205, 206, 184, 31, 108, 128, 22, 122, - /* 1230 */ 184, 53, 133, 39, 205, 206, 22, 151, 205, 206, - /* 1240 */ 205, 206, 113, 114, 23, 205, 206, 26, 59, 23, - /* 1250 */ 23, 144, 26, 26, 184, 23, 23, 19, 26, 26, - /* 1260 */ 7, 8, 24, 23, 214, 23, 26, 61, 26, 59, - /* 1270 */ 23, 23, 23, 26, 26, 26, 145, 19, 147, 59, - /* 1280 */ 184, 43, 44, 45, 46, 47, 48, 49, 50, 51, - /* 1290 */ 52, 53, 54, 55, 56, 57, 145, 23, 147, 110, - /* 1300 */ 26, 43, 44, 45, 46, 47, 48, 49, 50, 51, - /* 1310 */ 52, 53, 54, 55, 56, 57, 23, 184, 184, 26, - /* 1320 */ 110, 184, 184, 184, 134, 184, 184, 184, 184, 184, - /* 1330 */ 110, 184, 184, 184, 96, 97, 98, 99, 100, 101, - /* 1340 */ 102, 103, 104, 105, 106, 184, 184, 184, 134, 300, - /* 1350 */ 184, 243, 184, 184, 96, 97, 98, 99, 100, 101, - /* 1360 */ 102, 103, 104, 105, 106, 184, 184, 184, 184, 184, - /* 1370 */ 224, 184, 282, 273, 19, 272, 203, 182, 243, 243, - /* 1380 */ 230, 209, 278, 243, 231, 208, 265, 278, 234, 234, - /* 1390 */ 234, 217, 213, 60, 19, 243, 208, 237, 233, 44, - /* 1400 */ 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, - /* 1410 */ 55, 56, 57, 208, 247, 187, 134, 247, 247, 38, - /* 1420 */ 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, - /* 1430 */ 55, 56, 57, 237, 231, 191, 191, 279, 279, 282, - /* 1440 */ 143, 191, 108, 268, 22, 19, 20, 256, 22, 43, - /* 1450 */ 257, 96, 97, 98, 99, 100, 101, 102, 103, 104, - /* 1460 */ 105, 106, 36, 222, 142, 234, 18, 191, 225, 18, - /* 1470 */ 190, 96, 97, 98, 99, 100, 101, 102, 103, 104, - /* 1480 */ 105, 106, 225, 191, 225, 59, 225, 257, 234, 234, - /* 1490 */ 256, 222, 222, 190, 234, 150, 62, 71, 275, 274, - /* 1500 */ 191, 19, 20, 190, 22, 22, 210, 81, 191, 190, - /* 1510 */ 210, 191, 108, 190, 207, 207, 64, 207, 36, 215, - /* 1520 */ 94, 210, 207, 209, 119, 207, 100, 101, 207, 106, - /* 1530 */ 48, 215, 207, 107, 210, 109, 110, 111, 267, 267, - /* 1540 */ 114, 59, 210, 249, 137, 108, 248, 191, 249, 248, - /* 1550 */ 88, 249, 141, 71, 248, 299, 138, 131, 132, 22, - /* 1560 */ 191, 249, 299, 237, 82, 238, 150, 262, 140, 87, - /* 1570 */ 139, 145, 146, 147, 148, 149, 94, 248, 238, 236, - /* 1580 */ 25, 235, 100, 101, 234, 194, 26, 193, 13, 107, - /* 1590 */ 6, 109, 110, 111, 264, 185, 114, 185, 183, 197, - /* 1600 */ 183, 203, 183, 203, 203, 197, 203, 211, 211, 4, - /* 1610 */ 197, 3, 203, 22, 155, 15, 288, 203, 93, 288, - /* 1620 */ 285, 23, 16, 203, 203, 23, 132, 145, 146, 147, - /* 1630 */ 148, 149, 0, 1, 2, 143, 123, 5, 24, 135, - /* 1640 */ 20, 16, 10, 11, 12, 13, 14, 137, 1, 17, - /* 1650 */ 135, 144, 19, 20, 123, 22, 61, 143, 37, 123, - /* 1660 */ 53, 109, 30, 53, 32, 53, 53, 134, 34, 36, - /* 1670 */ 1, 5, 40, 22, 108, 153, 26, 68, 75, 68, - /* 1680 */ 41, 134, 108, 24, 20, 124, 19, 118, 23, 67, - /* 1690 */ 22, 67, 59, 22, 22, 22, 22, 67, 28, 37, - /* 1700 */ 23, 142, 70, 22, 71, 23, 157, 23, 23, 26, - /* 1710 */ 78, 23, 22, 81, 23, 82, 24, 22, 24, 134, - /* 1720 */ 87, 23, 19, 20, 92, 22, 109, 94, 23, 22, - /* 1730 */ 34, 34, 136, 100, 101, 26, 34, 85, 34, 36, - /* 1740 */ 107, 83, 109, 110, 111, 34, 90, 114, 34, 23, - /* 1750 */ 75, 75, 44, 22, 24, 26, 34, 23, 126, 26, - /* 1760 */ 23, 23, 59, 131, 132, 23, 23, 26, 23, 22, - /* 1770 */ 11, 22, 22, 22, 71, 23, 23, 22, 145, 146, - /* 1780 */ 147, 148, 149, 26, 23, 82, 154, 134, 128, 134, - /* 1790 */ 87, 134, 15, 1, 301, 134, 301, 94, 301, 301, - /* 1800 */ 301, 301, 301, 100, 101, 301, 301, 301, 301, 301, - /* 1810 */ 107, 301, 109, 110, 111, 1, 2, 114, 301, 5, - /* 1820 */ 301, 301, 301, 301, 10, 11, 12, 13, 14, 301, - /* 1830 */ 301, 17, 301, 301, 301, 301, 19, 20, 301, 22, - /* 1840 */ 301, 301, 301, 301, 30, 301, 32, 301, 145, 146, - /* 1850 */ 147, 148, 149, 36, 40, 301, 301, 301, 301, 301, - /* 1860 */ 301, 301, 301, 301, 301, 301, 301, 301, 301, 301, - /* 1870 */ 301, 301, 301, 301, 301, 301, 59, 301, 301, 301, - /* 1880 */ 301, 301, 301, 301, 70, 301, 301, 301, 71, 301, - /* 1890 */ 301, 301, 78, 301, 301, 81, 19, 20, 301, 22, - /* 1900 */ 301, 301, 301, 301, 301, 301, 92, 301, 301, 301, - /* 1910 */ 301, 94, 301, 36, 301, 301, 301, 100, 101, 102, - /* 1920 */ 301, 301, 301, 301, 107, 301, 109, 110, 111, 301, - /* 1930 */ 301, 114, 301, 301, 301, 301, 59, 301, 301, 301, - /* 1940 */ 126, 301, 301, 301, 301, 131, 132, 301, 71, 301, - /* 1950 */ 301, 301, 301, 301, 301, 301, 19, 20, 301, 22, - /* 1960 */ 301, 301, 145, 146, 147, 148, 149, 301, 154, 301, - /* 1970 */ 301, 94, 301, 36, 301, 301, 301, 100, 101, 301, - /* 1980 */ 301, 301, 301, 301, 107, 301, 109, 110, 111, 301, - /* 1990 */ 301, 114, 5, 301, 301, 301, 59, 10, 11, 12, - /* 2000 */ 13, 14, 301, 301, 17, 301, 301, 301, 71, 301, - /* 2010 */ 301, 301, 301, 301, 301, 301, 301, 30, 301, 32, - /* 2020 */ 301, 301, 145, 146, 147, 148, 149, 40, 301, 301, - /* 2030 */ 301, 94, 301, 301, 301, 301, 301, 100, 101, 301, - /* 2040 */ 301, 301, 301, 301, 107, 301, 109, 110, 111, 301, - /* 2050 */ 301, 114, 301, 301, 301, 301, 301, 70, 301, 301, - /* 2060 */ 301, 301, 301, 301, 301, 78, 301, 301, 81, 301, - /* 2070 */ 301, 301, 301, 301, 301, 301, 301, 301, 301, 92, - /* 2080 */ 301, 301, 145, 146, 147, 148, 149, 301, 301, 301, - /* 2090 */ 301, 301, 301, 301, 301, 301, 301, 301, 301, 301, - /* 2100 */ 301, 301, 301, 301, 301, 301, 301, 301, 301, 301, - /* 2110 */ 301, 301, 301, 126, 301, 301, 301, 301, 131, 132, - /* 2120 */ 301, 301, 301, 301, 301, 301, 301, 301, 301, 301, - /* 2130 */ 301, 301, 301, 301, 301, 301, 301, 301, 301, 301, - /* 2140 */ 301, 154, 301, 301, 301, 301, 301, 301, 301, 301, - /* 2150 */ 301, 301, 301, 301, 301, 301, 301, 301, 301, 301, - /* 2160 */ 301, 301, 301, 301, 301, 301, 301, 301, 301, + /* 0 */ 277, 278, 279, 241, 242, 225, 195, 227, 195, 312, + /* 10 */ 195, 218, 195, 316, 195, 235, 254, 195, 256, 19, + /* 20 */ 297, 277, 278, 279, 218, 206, 213, 214, 206, 218, + /* 30 */ 219, 31, 206, 218, 219, 218, 219, 218, 219, 39, + /* 40 */ 218, 219, 195, 43, 44, 45, 195, 47, 48, 49, + /* 50 */ 50, 51, 52, 53, 54, 55, 56, 57, 58, 19, + /* 60 */ 241, 242, 195, 241, 242, 195, 255, 241, 242, 195, + /* 70 */ 255, 237, 238, 254, 255, 256, 254, 255, 256, 264, + /* 80 */ 254, 207, 256, 43, 44, 45, 264, 47, 48, 49, + /* 90 */ 50, 51, 52, 53, 54, 55, 56, 57, 58, 251, + /* 100 */ 287, 253, 215, 103, 104, 105, 106, 107, 108, 109, + /* 110 */ 110, 111, 112, 113, 114, 82, 265, 195, 271, 11, + /* 120 */ 187, 188, 189, 190, 191, 192, 190, 87, 192, 89, + /* 130 */ 197, 19, 199, 197, 317, 199, 319, 25, 271, 206, + /* 140 */ 218, 219, 206, 103, 104, 105, 106, 107, 108, 109, + /* 150 */ 110, 111, 112, 113, 114, 43, 44, 45, 195, 47, + /* 160 */ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, + /* 170 */ 58, 60, 139, 140, 241, 242, 289, 241, 242, 309, + /* 180 */ 310, 294, 70, 47, 48, 49, 50, 254, 77, 256, + /* 190 */ 254, 195, 256, 55, 56, 57, 58, 59, 221, 88, + /* 200 */ 109, 90, 269, 240, 93, 269, 107, 108, 109, 110, + /* 210 */ 111, 112, 113, 114, 215, 103, 104, 105, 106, 107, + /* 220 */ 108, 109, 110, 111, 112, 113, 114, 136, 117, 118, + /* 230 */ 119, 298, 141, 300, 298, 19, 300, 129, 130, 317, + /* 240 */ 318, 103, 104, 105, 106, 107, 108, 109, 110, 111, + /* 250 */ 112, 113, 114, 114, 277, 278, 279, 146, 122, 43, + /* 260 */ 44, 45, 195, 47, 48, 49, 50, 51, 52, 53, + /* 270 */ 54, 55, 56, 57, 58, 218, 277, 278, 279, 19, + /* 280 */ 19, 195, 286, 23, 68, 218, 219, 55, 56, 57, + /* 290 */ 58, 103, 104, 105, 106, 107, 108, 109, 110, 111, + /* 300 */ 112, 113, 114, 43, 44, 45, 232, 47, 48, 49, + /* 310 */ 50, 51, 52, 53, 54, 55, 56, 57, 58, 103, + /* 320 */ 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, + /* 330 */ 114, 135, 60, 137, 138, 103, 104, 105, 106, 107, + /* 340 */ 108, 109, 110, 111, 112, 113, 114, 82, 281, 206, + /* 350 */ 195, 109, 110, 111, 112, 113, 114, 195, 195, 195, + /* 360 */ 205, 22, 207, 103, 104, 105, 106, 107, 108, 109, + /* 370 */ 110, 111, 112, 113, 114, 195, 60, 116, 117, 107, + /* 380 */ 108, 218, 219, 19, 241, 242, 121, 23, 116, 117, + /* 390 */ 118, 119, 306, 121, 308, 206, 234, 254, 15, 256, + /* 400 */ 195, 129, 259, 260, 139, 140, 145, 43, 44, 45, + /* 410 */ 200, 47, 48, 49, 50, 51, 52, 53, 54, 55, + /* 420 */ 56, 57, 58, 218, 219, 60, 154, 19, 156, 265, + /* 430 */ 241, 242, 24, 117, 118, 119, 120, 21, 73, 123, + /* 440 */ 124, 125, 74, 254, 61, 256, 107, 108, 221, 133, + /* 450 */ 82, 43, 44, 45, 195, 47, 48, 49, 50, 51, + /* 460 */ 52, 53, 54, 55, 56, 57, 58, 103, 104, 105, + /* 470 */ 106, 107, 108, 109, 110, 111, 112, 113, 114, 195, + /* 480 */ 317, 318, 117, 118, 119, 22, 120, 195, 22, 123, + /* 490 */ 124, 125, 19, 20, 284, 22, 128, 81, 288, 133, + /* 500 */ 195, 195, 218, 219, 277, 278, 279, 139, 140, 36, + /* 510 */ 195, 103, 104, 105, 106, 107, 108, 109, 110, 111, + /* 520 */ 112, 113, 114, 218, 219, 62, 60, 195, 241, 242, + /* 530 */ 271, 19, 240, 60, 189, 190, 191, 192, 233, 255, + /* 540 */ 124, 254, 197, 256, 199, 72, 129, 130, 264, 195, + /* 550 */ 195, 206, 22, 23, 60, 43, 44, 45, 206, 47, + /* 560 */ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, + /* 570 */ 58, 195, 218, 219, 101, 195, 60, 271, 162, 195, + /* 580 */ 107, 108, 109, 117, 118, 119, 241, 242, 115, 73, + /* 590 */ 117, 118, 119, 241, 242, 122, 60, 195, 266, 254, + /* 600 */ 312, 256, 218, 219, 316, 203, 254, 195, 256, 255, + /* 610 */ 208, 117, 118, 119, 269, 103, 104, 105, 106, 107, + /* 620 */ 108, 109, 110, 111, 112, 113, 114, 154, 155, 156, + /* 630 */ 157, 158, 102, 117, 118, 119, 19, 242, 144, 255, + /* 640 */ 23, 206, 24, 298, 195, 300, 206, 195, 264, 254, + /* 650 */ 206, 256, 240, 117, 118, 119, 183, 22, 22, 23, + /* 660 */ 43, 44, 45, 151, 47, 48, 49, 50, 51, 52, + /* 670 */ 53, 54, 55, 56, 57, 58, 241, 242, 60, 195, + /* 680 */ 19, 241, 242, 195, 23, 241, 242, 195, 152, 254, + /* 690 */ 310, 256, 243, 312, 254, 60, 256, 316, 254, 206, + /* 700 */ 256, 60, 218, 219, 43, 44, 45, 272, 47, 48, + /* 710 */ 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, + /* 720 */ 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, + /* 730 */ 113, 114, 240, 60, 241, 242, 118, 25, 102, 255, + /* 740 */ 166, 167, 101, 22, 26, 19, 20, 254, 22, 256, + /* 750 */ 139, 140, 117, 118, 119, 306, 195, 308, 117, 118, + /* 760 */ 237, 238, 36, 122, 103, 104, 105, 106, 107, 108, + /* 770 */ 109, 110, 111, 112, 113, 114, 195, 195, 60, 218, + /* 780 */ 219, 60, 109, 195, 19, 217, 60, 25, 23, 77, + /* 790 */ 117, 118, 119, 225, 233, 154, 155, 156, 72, 312, + /* 800 */ 218, 219, 90, 316, 22, 93, 303, 304, 43, 44, + /* 810 */ 45, 195, 47, 48, 49, 50, 51, 52, 53, 54, + /* 820 */ 55, 56, 57, 58, 183, 195, 195, 101, 19, 213, + /* 830 */ 214, 243, 23, 107, 108, 117, 118, 119, 117, 118, + /* 840 */ 119, 115, 60, 117, 118, 119, 195, 60, 122, 218, + /* 850 */ 219, 22, 43, 44, 45, 35, 47, 48, 49, 50, + /* 860 */ 51, 52, 53, 54, 55, 56, 57, 58, 103, 104, + /* 870 */ 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, + /* 880 */ 154, 155, 156, 157, 158, 195, 255, 67, 195, 60, + /* 890 */ 101, 240, 311, 312, 306, 75, 308, 316, 29, 117, + /* 900 */ 118, 119, 33, 287, 117, 118, 119, 118, 146, 183, + /* 910 */ 195, 122, 103, 104, 105, 106, 107, 108, 109, 110, + /* 920 */ 111, 112, 113, 114, 215, 195, 77, 60, 25, 195, + /* 930 */ 122, 144, 19, 218, 219, 66, 23, 88, 246, 90, + /* 940 */ 132, 25, 93, 154, 155, 156, 117, 118, 119, 257, + /* 950 */ 195, 131, 218, 219, 195, 265, 43, 44, 45, 195, + /* 960 */ 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + /* 970 */ 57, 58, 183, 218, 219, 195, 19, 218, 219, 195, + /* 980 */ 23, 195, 218, 219, 117, 118, 119, 195, 233, 255, + /* 990 */ 195, 195, 233, 22, 23, 146, 25, 233, 218, 219, + /* 1000 */ 43, 44, 45, 294, 47, 48, 49, 50, 51, 52, + /* 1010 */ 53, 54, 55, 56, 57, 58, 103, 104, 105, 106, + /* 1020 */ 107, 108, 109, 110, 111, 112, 113, 114, 195, 12, + /* 1030 */ 234, 195, 240, 74, 195, 255, 195, 60, 243, 262, + /* 1040 */ 263, 311, 312, 25, 27, 19, 316, 107, 108, 265, + /* 1050 */ 24, 265, 195, 150, 195, 139, 140, 218, 219, 42, + /* 1060 */ 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, + /* 1070 */ 113, 114, 233, 102, 67, 218, 219, 218, 219, 243, + /* 1080 */ 19, 64, 22, 23, 23, 25, 195, 128, 129, 130, + /* 1090 */ 233, 74, 233, 86, 154, 118, 156, 130, 265, 208, + /* 1100 */ 19, 306, 95, 308, 43, 44, 45, 266, 47, 48, + /* 1110 */ 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, + /* 1120 */ 153, 230, 96, 232, 43, 44, 45, 19, 47, 48, + /* 1130 */ 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, + /* 1140 */ 114, 22, 306, 24, 308, 127, 120, 121, 122, 123, + /* 1150 */ 124, 125, 126, 195, 147, 212, 213, 214, 132, 23, + /* 1160 */ 195, 25, 102, 100, 103, 104, 105, 106, 107, 108, + /* 1170 */ 109, 110, 111, 112, 113, 114, 218, 219, 19, 60, + /* 1180 */ 195, 12, 210, 211, 103, 104, 105, 106, 107, 108, + /* 1190 */ 109, 110, 111, 112, 113, 114, 27, 134, 195, 195, + /* 1200 */ 195, 210, 211, 218, 219, 195, 47, 195, 212, 213, + /* 1210 */ 214, 42, 16, 130, 19, 112, 113, 114, 23, 77, + /* 1220 */ 195, 218, 219, 218, 219, 117, 163, 164, 218, 219, + /* 1230 */ 218, 219, 90, 64, 19, 93, 153, 118, 43, 44, + /* 1240 */ 45, 160, 47, 48, 49, 50, 51, 52, 53, 54, + /* 1250 */ 55, 56, 57, 58, 195, 119, 272, 276, 43, 44, + /* 1260 */ 45, 195, 47, 48, 49, 50, 51, 52, 53, 54, + /* 1270 */ 55, 56, 57, 58, 78, 116, 80, 218, 219, 116, + /* 1280 */ 144, 128, 129, 130, 218, 219, 61, 195, 47, 195, + /* 1290 */ 16, 132, 195, 263, 195, 314, 315, 267, 103, 104, + /* 1300 */ 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, + /* 1310 */ 218, 219, 218, 219, 151, 218, 219, 195, 103, 104, + /* 1320 */ 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, + /* 1330 */ 210, 211, 195, 7, 8, 9, 195, 60, 195, 312, + /* 1340 */ 218, 219, 195, 316, 195, 120, 195, 263, 19, 195, + /* 1350 */ 125, 267, 78, 24, 80, 218, 219, 116, 162, 218, + /* 1360 */ 219, 218, 219, 301, 302, 218, 219, 195, 19, 218, + /* 1370 */ 219, 276, 43, 44, 45, 160, 47, 48, 49, 50, + /* 1380 */ 51, 52, 53, 54, 55, 56, 57, 58, 19, 146, + /* 1390 */ 218, 219, 43, 44, 45, 118, 47, 48, 49, 50, + /* 1400 */ 51, 52, 53, 54, 55, 56, 57, 58, 165, 314, + /* 1410 */ 315, 276, 43, 44, 45, 266, 47, 48, 49, 50, + /* 1420 */ 51, 52, 53, 54, 55, 56, 57, 58, 128, 129, + /* 1430 */ 130, 195, 103, 104, 105, 106, 107, 108, 109, 110, + /* 1440 */ 111, 112, 113, 114, 195, 228, 195, 61, 195, 314, + /* 1450 */ 315, 25, 103, 104, 105, 106, 107, 108, 109, 110, + /* 1460 */ 111, 112, 113, 114, 195, 22, 195, 218, 219, 218, + /* 1470 */ 219, 195, 103, 104, 105, 106, 107, 108, 109, 110, + /* 1480 */ 111, 112, 113, 114, 195, 195, 246, 218, 219, 218, + /* 1490 */ 219, 25, 19, 246, 218, 219, 246, 257, 259, 260, + /* 1500 */ 195, 22, 266, 60, 257, 195, 120, 257, 218, 219, + /* 1510 */ 116, 195, 19, 195, 150, 151, 25, 44, 45, 266, + /* 1520 */ 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + /* 1530 */ 57, 58, 195, 54, 218, 219, 218, 219, 45, 145, + /* 1540 */ 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + /* 1550 */ 57, 58, 246, 121, 122, 218, 219, 19, 23, 31, + /* 1560 */ 25, 118, 159, 257, 161, 24, 195, 39, 195, 143, + /* 1570 */ 195, 19, 20, 22, 22, 24, 103, 104, 105, 106, + /* 1580 */ 107, 108, 109, 110, 111, 112, 113, 114, 36, 218, + /* 1590 */ 219, 218, 219, 218, 219, 195, 103, 104, 105, 106, + /* 1600 */ 107, 108, 109, 110, 111, 112, 113, 114, 195, 143, + /* 1610 */ 119, 136, 60, 195, 22, 195, 141, 195, 218, 219, + /* 1620 */ 195, 23, 195, 25, 72, 23, 131, 25, 195, 134, + /* 1630 */ 23, 218, 219, 195, 82, 144, 218, 219, 218, 219, + /* 1640 */ 218, 219, 195, 218, 219, 218, 219, 60, 23, 195, + /* 1650 */ 25, 218, 219, 101, 195, 117, 218, 219, 195, 107, + /* 1660 */ 108, 23, 195, 25, 195, 218, 219, 115, 228, 117, + /* 1670 */ 118, 119, 218, 219, 122, 195, 19, 218, 219, 195, + /* 1680 */ 60, 218, 219, 142, 195, 218, 219, 19, 20, 195, + /* 1690 */ 22, 139, 140, 23, 23, 25, 25, 195, 218, 219, + /* 1700 */ 7, 8, 218, 219, 36, 118, 154, 155, 156, 157, + /* 1710 */ 158, 195, 23, 195, 25, 84, 85, 49, 195, 23, + /* 1720 */ 195, 25, 195, 23, 195, 25, 195, 23, 60, 25, + /* 1730 */ 23, 23, 25, 25, 142, 183, 218, 219, 118, 195, + /* 1740 */ 72, 218, 219, 218, 219, 218, 219, 218, 219, 218, + /* 1750 */ 219, 195, 195, 146, 86, 98, 23, 195, 25, 91, + /* 1760 */ 19, 20, 154, 22, 156, 154, 23, 156, 25, 101, + /* 1770 */ 23, 195, 25, 195, 195, 107, 108, 36, 195, 195, + /* 1780 */ 195, 195, 228, 115, 195, 117, 118, 119, 195, 195, + /* 1790 */ 122, 261, 195, 321, 195, 195, 195, 258, 238, 195, + /* 1800 */ 195, 60, 299, 291, 195, 195, 258, 195, 195, 195, + /* 1810 */ 290, 244, 216, 72, 245, 193, 258, 258, 299, 258, + /* 1820 */ 299, 274, 154, 155, 156, 157, 158, 86, 247, 295, + /* 1830 */ 248, 295, 91, 19, 20, 270, 22, 274, 270, 248, + /* 1840 */ 274, 222, 101, 227, 274, 221, 231, 221, 107, 108, + /* 1850 */ 36, 183, 262, 247, 221, 283, 115, 262, 117, 118, + /* 1860 */ 119, 198, 116, 122, 220, 262, 61, 220, 220, 251, + /* 1870 */ 247, 142, 251, 245, 60, 202, 299, 202, 38, 262, + /* 1880 */ 202, 22, 152, 151, 296, 43, 72, 236, 18, 239, + /* 1890 */ 202, 239, 239, 239, 18, 154, 155, 156, 157, 158, + /* 1900 */ 86, 150, 201, 248, 275, 91, 248, 273, 236, 248, + /* 1910 */ 275, 275, 273, 236, 248, 101, 286, 202, 201, 159, + /* 1920 */ 63, 107, 108, 296, 183, 293, 202, 201, 22, 115, + /* 1930 */ 202, 117, 118, 119, 292, 223, 122, 201, 65, 202, + /* 1940 */ 201, 223, 220, 220, 22, 220, 226, 226, 229, 127, + /* 1950 */ 223, 220, 166, 24, 285, 220, 222, 114, 315, 285, + /* 1960 */ 220, 202, 220, 307, 92, 320, 320, 229, 154, 155, + /* 1970 */ 156, 157, 158, 0, 1, 2, 223, 83, 5, 268, + /* 1980 */ 149, 268, 146, 10, 11, 12, 13, 14, 22, 280, + /* 1990 */ 17, 202, 159, 19, 20, 251, 22, 183, 282, 148, + /* 2000 */ 252, 252, 250, 30, 249, 32, 248, 147, 25, 13, + /* 2010 */ 36, 204, 196, 40, 196, 6, 302, 194, 194, 194, + /* 2020 */ 209, 215, 209, 215, 215, 215, 224, 224, 216, 209, + /* 2030 */ 4, 216, 215, 3, 60, 22, 122, 19, 122, 19, + /* 2040 */ 125, 22, 15, 22, 71, 16, 72, 23, 23, 140, + /* 2050 */ 305, 152, 79, 25, 131, 82, 143, 20, 16, 305, + /* 2060 */ 1, 143, 145, 131, 131, 62, 54, 131, 37, 54, + /* 2070 */ 54, 152, 99, 117, 34, 101, 54, 24, 1, 5, + /* 2080 */ 22, 107, 108, 116, 76, 25, 162, 41, 142, 115, + /* 2090 */ 24, 117, 118, 119, 116, 20, 122, 19, 126, 23, + /* 2100 */ 132, 19, 20, 69, 22, 69, 22, 134, 22, 68, + /* 2110 */ 22, 22, 139, 140, 60, 141, 68, 24, 36, 28, + /* 2120 */ 97, 22, 37, 68, 23, 150, 34, 22, 154, 155, + /* 2130 */ 156, 157, 158, 23, 23, 22, 163, 25, 23, 142, + /* 2140 */ 23, 98, 60, 23, 22, 144, 25, 76, 34, 117, + /* 2150 */ 34, 89, 34, 34, 72, 87, 76, 183, 34, 94, + /* 2160 */ 34, 23, 22, 24, 34, 23, 25, 44, 25, 23, + /* 2170 */ 23, 23, 22, 22, 25, 11, 143, 25, 143, 23, + /* 2180 */ 22, 22, 22, 101, 23, 23, 136, 22, 25, 107, + /* 2190 */ 108, 142, 25, 142, 142, 23, 15, 115, 1, 117, + /* 2200 */ 118, 119, 1, 2, 122, 1, 5, 322, 322, 322, + /* 2210 */ 322, 10, 11, 12, 13, 14, 322, 322, 17, 322, + /* 2220 */ 5, 322, 322, 141, 322, 10, 11, 12, 13, 14, + /* 2230 */ 322, 30, 17, 32, 322, 322, 154, 155, 156, 157, + /* 2240 */ 158, 40, 322, 322, 322, 30, 322, 32, 322, 322, + /* 2250 */ 322, 322, 322, 322, 322, 40, 322, 322, 322, 322, + /* 2260 */ 322, 322, 322, 322, 322, 183, 322, 322, 322, 322, + /* 2270 */ 322, 322, 71, 322, 322, 322, 322, 322, 322, 322, + /* 2280 */ 79, 322, 322, 82, 322, 322, 71, 322, 322, 322, + /* 2290 */ 322, 322, 322, 322, 79, 322, 322, 82, 322, 322, + /* 2300 */ 99, 322, 322, 322, 322, 322, 322, 322, 322, 322, + /* 2310 */ 322, 322, 322, 322, 99, 322, 322, 322, 322, 322, + /* 2320 */ 322, 322, 322, 322, 322, 322, 322, 322, 322, 322, + /* 2330 */ 322, 322, 322, 322, 322, 134, 322, 322, 322, 322, + /* 2340 */ 139, 140, 322, 322, 322, 322, 322, 322, 322, 134, + /* 2350 */ 322, 322, 322, 322, 139, 140, 322, 322, 322, 322, + /* 2360 */ 322, 322, 322, 322, 163, 322, 322, 322, 322, 322, + /* 2370 */ 322, 322, 322, 322, 322, 322, 322, 322, 163, 322, + /* 2380 */ 322, 322, 322, 322, 322, 322, 322, 322, 322, 322, + /* 2390 */ 322, 322, 322, 322, 322, 322, 322, 322, 322, 322, + /* 2400 */ 322, 322, 322, 322, 322, 322, 322, 322, 187, 187, + /* 2410 */ 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, + /* 2420 */ 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, + /* 2430 */ 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, + /* 2440 */ 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, + /* 2450 */ 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, + /* 2460 */ 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, + /* 2470 */ 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, + /* 2480 */ 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, + /* 2490 */ 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, + /* 2500 */ 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, + /* 2510 */ 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, + /* 2520 */ 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, + /* 2530 */ 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, + /* 2540 */ 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, + /* 2550 */ 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, + /* 2560 */ 187, 187, 187, 187, 187, 187, }; -#define YY_SHIFT_COUNT (540) +#define YY_SHIFT_COUNT (599) #define YY_SHIFT_MIN (0) -#define YY_SHIFT_MAX (1987) +#define YY_SHIFT_MAX (2215) static const unsigned short int yy_shift_ofst[] = { - /* 0 */ 1814, 1632, 1987, 1426, 1426, 128, 1482, 1633, 1703, 1877, - /* 10 */ 1877, 1877, 87, 0, 0, 264, 1106, 1877, 1877, 1877, - /* 20 */ 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, - /* 30 */ 226, 226, 381, 381, 296, 193, 128, 128, 128, 128, - /* 40 */ 128, 128, 97, 194, 332, 429, 526, 623, 720, 817, - /* 50 */ 914, 934, 1086, 1238, 1106, 1106, 1106, 1106, 1106, 1106, - /* 60 */ 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, 1106, - /* 70 */ 1106, 1106, 1258, 1106, 1355, 1375, 1375, 1817, 1877, 1877, - /* 80 */ 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, - /* 90 */ 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, - /* 100 */ 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, - /* 110 */ 1937, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, 1877, - /* 120 */ 1877, 1877, 1877, 1877, 32, 129, 129, 129, 129, 129, - /* 130 */ 21, 152, 297, 494, 726, 65, 494, 514, 514, 494, - /* 140 */ 560, 560, 560, 560, 322, 599, 50, 2142, 2142, 155, - /* 150 */ 155, 155, 313, 392, 386, 392, 392, 481, 481, 200, - /* 160 */ 480, 684, 758, 494, 494, 494, 494, 494, 494, 494, - /* 170 */ 494, 494, 494, 494, 494, 494, 494, 494, 494, 494, - /* 180 */ 494, 494, 494, 494, 768, 768, 494, 166, 377, 377, - /* 190 */ 635, 835, 835, 635, 748, 987, 2142, 2142, 2142, 448, - /* 200 */ 45, 45, 403, 484, 502, 106, 525, 508, 528, 538, - /* 210 */ 494, 494, 494, 494, 494, 494, 494, 494, 494, 84, - /* 220 */ 494, 494, 494, 494, 494, 494, 494, 494, 494, 494, - /* 230 */ 494, 494, 267, 267, 267, 494, 494, 494, 494, 769, - /* 240 */ 494, 494, 494, 4, 477, 494, 494, 788, 494, 494, - /* 250 */ 494, 494, 494, 494, 494, 494, 727, 5, 135, 985, - /* 260 */ 985, 985, 985, 522, 135, 135, 797, 326, 875, 986, - /* 270 */ 968, 1036, 1036, 1038, 968, 968, 1038, 972, 1081, 1118, - /* 280 */ 1194, 1194, 1194, 1036, 757, 757, 946, 777, 1099, 1102, - /* 290 */ 1333, 1282, 1282, 1381, 1381, 1282, 1297, 1334, 1422, 1406, - /* 300 */ 1322, 1448, 1448, 1448, 1448, 1282, 1451, 1322, 1322, 1334, - /* 310 */ 1422, 1406, 1406, 1322, 1282, 1451, 1345, 1434, 1282, 1451, - /* 320 */ 1483, 1282, 1451, 1282, 1451, 1483, 1404, 1404, 1404, 1452, - /* 330 */ 1483, 1404, 1405, 1404, 1452, 1404, 1404, 1483, 1423, 1423, - /* 340 */ 1483, 1407, 1437, 1407, 1437, 1407, 1437, 1407, 1437, 1282, - /* 350 */ 1462, 1462, 1411, 1418, 1537, 1282, 1416, 1411, 1428, 1431, - /* 360 */ 1322, 1555, 1560, 1575, 1575, 1584, 1584, 1584, 2142, 2142, - /* 370 */ 2142, 2142, 2142, 2142, 2142, 2142, 2142, 2142, 2142, 2142, - /* 380 */ 2142, 2142, 2142, 2142, 61, 725, 374, 1080, 198, 771, - /* 390 */ 283, 1192, 1178, 1190, 1107, 1221, 1206, 1226, 1227, 1232, - /* 400 */ 1233, 1240, 1242, 1189, 1129, 1253, 216, 1210, 1247, 1248, - /* 410 */ 1249, 1131, 1151, 1274, 1293, 1220, 1214, 1605, 1608, 1591, - /* 420 */ 1459, 1600, 1525, 1606, 1598, 1602, 1494, 1492, 1513, 1614, - /* 430 */ 1504, 1620, 1510, 1625, 1647, 1515, 1507, 1531, 1595, 1621, - /* 440 */ 1514, 1607, 1610, 1612, 1613, 1536, 1552, 1634, 1533, 1669, - /* 450 */ 1666, 1651, 1566, 1522, 1609, 1650, 1611, 1603, 1639, 1547, - /* 460 */ 1574, 1659, 1664, 1667, 1561, 1569, 1668, 1622, 1671, 1672, - /* 470 */ 1665, 1673, 1624, 1670, 1674, 1630, 1662, 1677, 1559, 1681, - /* 480 */ 1682, 1549, 1684, 1685, 1683, 1688, 1690, 1692, 1691, 1695, - /* 490 */ 1694, 1585, 1698, 1705, 1617, 1696, 1707, 1596, 1709, 1697, - /* 500 */ 1702, 1704, 1711, 1652, 1675, 1658, 1708, 1676, 1656, 1714, - /* 510 */ 1726, 1731, 1730, 1729, 1733, 1722, 1734, 1709, 1737, 1738, - /* 520 */ 1742, 1743, 1741, 1745, 1747, 1759, 1749, 1750, 1752, 1753, - /* 530 */ 1751, 1755, 1757, 1660, 1653, 1655, 1657, 1661, 1761, 1777, - /* 540 */ 1792, + /* 0 */ 2201, 1973, 2215, 1552, 1552, 33, 368, 1668, 1741, 1814, + /* 10 */ 726, 726, 726, 265, 33, 33, 33, 33, 33, 0, + /* 20 */ 0, 216, 1349, 726, 726, 726, 726, 726, 726, 726, + /* 30 */ 726, 726, 726, 726, 726, 726, 726, 726, 272, 272, + /* 40 */ 111, 111, 316, 365, 516, 867, 867, 916, 916, 916, + /* 50 */ 916, 40, 112, 260, 364, 408, 512, 617, 661, 765, + /* 60 */ 809, 913, 957, 1061, 1081, 1195, 1215, 1329, 1349, 1349, + /* 70 */ 1349, 1349, 1349, 1349, 1349, 1349, 1349, 1349, 1349, 1349, + /* 80 */ 1349, 1349, 1349, 1349, 1349, 1349, 1369, 1349, 1473, 1493, + /* 90 */ 1493, 473, 1974, 2082, 726, 726, 726, 726, 726, 726, + /* 100 */ 726, 726, 726, 726, 726, 726, 726, 726, 726, 726, + /* 110 */ 726, 726, 726, 726, 726, 726, 726, 726, 726, 726, + /* 120 */ 726, 726, 726, 726, 726, 726, 726, 726, 726, 726, + /* 130 */ 726, 726, 726, 726, 726, 726, 726, 726, 726, 726, + /* 140 */ 726, 726, 726, 726, 726, 726, 138, 232, 232, 232, + /* 150 */ 232, 232, 232, 232, 188, 99, 242, 718, 416, 1159, + /* 160 */ 867, 867, 940, 940, 867, 1103, 417, 574, 574, 574, + /* 170 */ 611, 139, 139, 2379, 2379, 1026, 1026, 1026, 536, 466, + /* 180 */ 466, 466, 466, 1017, 1017, 849, 718, 971, 1060, 867, + /* 190 */ 867, 867, 867, 867, 867, 867, 867, 867, 867, 867, + /* 200 */ 867, 867, 867, 867, 867, 867, 867, 867, 261, 712, + /* 210 */ 712, 867, 108, 1142, 1142, 977, 1108, 1108, 977, 977, + /* 220 */ 1243, 2379, 2379, 2379, 2379, 2379, 2379, 2379, 641, 789, + /* 230 */ 789, 635, 366, 721, 673, 782, 494, 787, 829, 867, + /* 240 */ 867, 867, 867, 867, 867, 867, 867, 867, 867, 867, + /* 250 */ 959, 867, 867, 867, 867, 867, 867, 867, 867, 867, + /* 260 */ 867, 867, 867, 867, 867, 820, 820, 820, 867, 867, + /* 270 */ 867, 1136, 867, 867, 867, 1119, 1007, 867, 1169, 867, + /* 280 */ 867, 867, 867, 867, 867, 867, 867, 1225, 1153, 869, + /* 290 */ 196, 618, 618, 618, 618, 1491, 196, 196, 91, 339, + /* 300 */ 1326, 1386, 383, 1163, 1364, 1426, 1364, 1538, 903, 1163, + /* 310 */ 1163, 903, 1163, 1426, 1538, 1018, 1535, 1241, 1528, 1528, + /* 320 */ 1528, 1394, 1394, 1394, 1394, 762, 762, 1403, 1466, 1475, + /* 330 */ 1551, 1746, 1805, 1746, 1746, 1729, 1729, 1840, 1840, 1729, + /* 340 */ 1730, 1732, 1859, 1842, 1870, 1870, 1870, 1870, 1729, 1876, + /* 350 */ 1751, 1732, 1732, 1751, 1859, 1842, 1751, 1842, 1751, 1729, + /* 360 */ 1876, 1760, 1857, 1729, 1876, 1906, 1729, 1876, 1729, 1876, + /* 370 */ 1906, 1746, 1746, 1746, 1873, 1922, 1922, 1906, 1746, 1822, + /* 380 */ 1746, 1873, 1746, 1746, 1786, 1929, 1843, 1843, 1906, 1729, + /* 390 */ 1872, 1872, 1894, 1894, 1831, 1836, 1966, 1729, 1833, 1831, + /* 400 */ 1851, 1860, 1751, 1983, 1996, 1996, 2009, 2009, 2009, 2379, + /* 410 */ 2379, 2379, 2379, 2379, 2379, 2379, 2379, 2379, 2379, 2379, + /* 420 */ 2379, 2379, 2379, 2379, 136, 1063, 1196, 530, 636, 1274, + /* 430 */ 1300, 1443, 1598, 1495, 1479, 967, 1083, 1602, 463, 1625, + /* 440 */ 1638, 1670, 1541, 1671, 1689, 1696, 1277, 1432, 1693, 808, + /* 450 */ 1700, 1607, 1657, 1587, 1704, 1707, 1631, 1708, 1733, 1608, + /* 460 */ 1611, 1743, 1747, 1620, 1592, 2026, 2030, 2013, 1914, 2018, + /* 470 */ 1916, 2020, 2019, 2021, 1915, 2027, 2029, 2024, 2025, 1909, + /* 480 */ 1899, 1923, 2028, 2028, 1913, 2037, 1917, 2042, 2059, 1918, + /* 490 */ 1932, 2028, 1933, 2003, 2031, 2028, 1919, 2012, 2015, 2016, + /* 500 */ 2022, 1936, 1956, 2040, 2053, 2077, 2074, 2058, 1967, 1924, + /* 510 */ 2034, 2060, 2036, 2008, 2046, 1946, 1978, 2066, 2075, 2078, + /* 520 */ 1968, 1972, 2084, 2041, 2086, 2088, 2076, 2089, 2048, 2054, + /* 530 */ 2093, 2023, 2091, 2099, 2055, 2085, 2101, 2092, 1975, 2105, + /* 540 */ 2110, 2111, 2112, 2115, 2113, 2043, 1997, 2117, 2120, 2032, + /* 550 */ 2114, 2122, 2001, 2121, 2116, 2118, 2119, 2124, 2062, 2071, + /* 560 */ 2068, 2123, 2080, 2065, 2126, 2138, 2140, 2139, 2141, 2143, + /* 570 */ 2130, 2033, 2035, 2142, 2121, 2146, 2147, 2148, 2150, 2149, + /* 580 */ 2152, 2156, 2151, 2164, 2158, 2159, 2161, 2162, 2160, 2165, + /* 590 */ 2163, 2050, 2049, 2051, 2052, 2167, 2172, 2181, 2197, 2204, }; -#define YY_REDUCE_COUNT (383) -#define YY_REDUCE_MIN (-257) -#define YY_REDUCE_MAX (1421) +#define YY_REDUCE_COUNT (423) +#define YY_REDUCE_MIN (-303) +#define YY_REDUCE_MAX (1825) static const short yy_reduce_ofst[] = { - /* 0 */ -168, -17, 164, 214, 310, -166, -184, -18, 98, -170, - /* 10 */ 305, 315, -163, -193, -178, -257, 395, 401, 476, 478, - /* 20 */ 512, 117, 527, 529, 503, 509, 532, 255, 552, 556, - /* 30 */ 558, 607, 37, 408, 594, 413, 462, 559, 561, 601, - /* 40 */ 610, 618, -254, -254, -254, -254, -254, -254, -254, -254, - /* 50 */ -254, -254, -254, -254, -254, -254, -254, -254, -254, -254, - /* 60 */ -254, -254, -254, -254, -254, -254, -254, -254, -254, -254, - /* 70 */ -254, -254, -254, -254, -254, -254, -254, -111, 627, 650, - /* 80 */ 691, 697, 701, 703, 740, 742, 744, 767, 770, 790, - /* 90 */ 816, 818, 820, 822, 845, 857, 859, 861, 863, 865, - /* 100 */ 868, 870, 872, 874, 876, 888, 903, 906, 908, 915, - /* 110 */ 917, 922, 960, 962, 964, 988, 990, 992, 1015, 1017, - /* 120 */ 1029, 1033, 1035, 1040, -254, -254, -254, -254, -254, -254, - /* 130 */ -254, -254, -254, 190, 270, -196, 160, -160, 450, 647, - /* 140 */ 260, 458, 260, 458, 78, -254, -254, -254, -254, 206, - /* 150 */ 206, 206, 320, 598, -5, 675, 743, -148, 340, -125, - /* 160 */ 459, 466, 466, 693, -93, 461, 479, 706, 710, 714, - /* 170 */ 716, 717, 169, -183, 325, 314, 704, 333, 747, 858, - /* 180 */ -8, 819, 565, 755, 646, 660, 517, 265, 713, 791, - /* 190 */ 712, 795, 803, 918, 695, 860, 893, 935, 939, -181, - /* 200 */ -172, -147, -91, -46, -3, 162, 173, 231, 338, 437, - /* 210 */ 571, 614, 630, 651, 760, 931, 989, 1032, 1046, -218, - /* 220 */ 38, 1070, 1096, 1133, 1134, 1137, 1138, 1139, 1141, 1142, - /* 230 */ 1143, 1144, 292, 451, 1050, 1145, 1147, 1148, 1149, 813, - /* 240 */ 1161, 1162, 1163, 1108, 1049, 1166, 1168, 1146, 1169, 162, - /* 250 */ 1181, 1182, 1183, 1184, 1185, 1187, 1100, 1103, 1150, 1135, - /* 260 */ 1136, 1140, 1152, 813, 1150, 1150, 1153, 1173, 1195, 1090, - /* 270 */ 1154, 1167, 1170, 1104, 1155, 1156, 1109, 1172, 1174, 1179, - /* 280 */ 1177, 1188, 1205, 1171, 1160, 1196, 1121, 1165, 1203, 1228, - /* 290 */ 1157, 1244, 1245, 1158, 1159, 1250, 1175, 1193, 1191, 1241, - /* 300 */ 1231, 1243, 1257, 1259, 1261, 1276, 1280, 1254, 1255, 1230, - /* 310 */ 1234, 1269, 1270, 1260, 1292, 1303, 1223, 1225, 1309, 1313, - /* 320 */ 1296, 1317, 1319, 1320, 1323, 1300, 1307, 1308, 1310, 1304, - /* 330 */ 1311, 1315, 1314, 1318, 1316, 1321, 1325, 1324, 1271, 1272, - /* 340 */ 1332, 1294, 1298, 1299, 1301, 1302, 1306, 1312, 1329, 1356, - /* 350 */ 1256, 1263, 1327, 1326, 1305, 1369, 1330, 1340, 1343, 1346, - /* 360 */ 1350, 1391, 1394, 1410, 1412, 1415, 1417, 1419, 1328, 1331, - /* 370 */ 1335, 1402, 1398, 1400, 1401, 1403, 1408, 1396, 1397, 1409, - /* 380 */ 1414, 1420, 1421, 1413, + /* 0 */ -67, 345, -64, -178, -181, 143, 435, -78, -183, 163, + /* 10 */ -185, 284, 384, -174, 189, 352, 440, 444, 493, -23, + /* 20 */ 227, -277, -1, 305, 561, 755, 759, 764, -189, 839, + /* 30 */ 857, 354, 484, 859, 631, 67, 734, 780, -187, 616, + /* 40 */ 581, 730, 891, 449, 588, 795, 836, -238, 287, -238, + /* 50 */ 287, -256, -256, -256, -256, -256, -256, -256, -256, -256, + /* 60 */ -256, -256, -256, -256, -256, -256, -256, -256, -256, -256, + /* 70 */ -256, -256, -256, -256, -256, -256, -256, -256, -256, -256, + /* 80 */ -256, -256, -256, -256, -256, -256, -256, -256, -256, -256, + /* 90 */ -256, 205, 582, 715, 958, 985, 1003, 1005, 1010, 1012, + /* 100 */ 1059, 1066, 1092, 1094, 1097, 1122, 1137, 1141, 1143, 1147, + /* 110 */ 1151, 1172, 1249, 1251, 1269, 1271, 1276, 1290, 1316, 1318, + /* 120 */ 1337, 1371, 1373, 1375, 1400, 1413, 1418, 1420, 1422, 1425, + /* 130 */ 1427, 1433, 1438, 1447, 1454, 1459, 1463, 1467, 1480, 1484, + /* 140 */ 1518, 1523, 1525, 1527, 1529, 1531, -256, -256, -256, -256, + /* 150 */ -256, -256, -256, -256, -256, -256, -256, 155, 210, -220, + /* 160 */ 86, -130, 943, 996, 402, -256, -113, 981, 1095, 1135, + /* 170 */ 395, -256, -256, -256, -256, 568, 568, 568, -4, -153, + /* 180 */ -133, 259, 306, -166, 523, -303, -126, 503, 503, -37, + /* 190 */ -149, 164, 690, 292, 412, 492, 651, 784, 332, 786, + /* 200 */ 841, 1149, 833, 1236, 792, 162, 796, 1253, 777, 288, + /* 210 */ 381, 380, 709, 487, 1027, 972, 1030, 1084, 991, 1120, + /* 220 */ -152, 1062, 692, 1240, 1247, 1250, 1239, 1306, -207, -194, + /* 230 */ 57, 180, 74, 315, 355, 376, 452, 488, 630, 693, + /* 240 */ 965, 1004, 1025, 1099, 1154, 1289, 1305, 1310, 1469, 1489, + /* 250 */ 984, 1494, 1502, 1516, 1544, 1556, 1557, 1562, 1576, 1578, + /* 260 */ 1579, 1583, 1584, 1585, 1586, 1217, 1440, 1554, 1589, 1593, + /* 270 */ 1594, 1530, 1597, 1599, 1600, 1539, 1472, 1601, 1560, 1604, + /* 280 */ 355, 1605, 1609, 1610, 1612, 1613, 1614, 1503, 1512, 1520, + /* 290 */ 1567, 1548, 1558, 1559, 1561, 1530, 1567, 1567, 1569, 1596, + /* 300 */ 1622, 1519, 1521, 1547, 1565, 1581, 1568, 1534, 1582, 1563, + /* 310 */ 1566, 1591, 1570, 1606, 1536, 1619, 1615, 1616, 1624, 1626, + /* 320 */ 1633, 1590, 1595, 1603, 1617, 1618, 1621, 1572, 1623, 1628, + /* 330 */ 1663, 1644, 1577, 1647, 1648, 1673, 1675, 1588, 1627, 1678, + /* 340 */ 1630, 1629, 1634, 1651, 1650, 1652, 1653, 1654, 1688, 1701, + /* 350 */ 1655, 1635, 1636, 1658, 1639, 1672, 1661, 1677, 1666, 1715, + /* 360 */ 1717, 1632, 1642, 1724, 1726, 1712, 1728, 1736, 1737, 1739, + /* 370 */ 1718, 1722, 1723, 1725, 1719, 1720, 1721, 1727, 1731, 1734, + /* 380 */ 1735, 1738, 1740, 1742, 1643, 1656, 1669, 1674, 1753, 1759, + /* 390 */ 1645, 1646, 1711, 1713, 1748, 1744, 1709, 1789, 1716, 1749, + /* 400 */ 1752, 1755, 1758, 1807, 1816, 1818, 1823, 1824, 1825, 1745, + /* 410 */ 1754, 1714, 1811, 1806, 1808, 1809, 1810, 1813, 1802, 1803, + /* 420 */ 1812, 1815, 1817, 1820, }; static const YYACTIONTYPE yy_default[] = { - /* 0 */ 1536, 1536, 1536, 1376, 1159, 1265, 1159, 1159, 1159, 1376, - /* 10 */ 1376, 1376, 1159, 1295, 1295, 1429, 1190, 1159, 1159, 1159, - /* 20 */ 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1375, 1159, 1159, - /* 30 */ 1159, 1159, 1459, 1459, 1159, 1159, 1159, 1159, 1159, 1159, - /* 40 */ 1159, 1159, 1159, 1301, 1159, 1159, 1159, 1159, 1159, 1377, - /* 50 */ 1378, 1159, 1159, 1159, 1428, 1430, 1393, 1311, 1310, 1309, - /* 60 */ 1308, 1411, 1282, 1306, 1299, 1303, 1371, 1372, 1370, 1374, - /* 70 */ 1378, 1377, 1159, 1302, 1342, 1356, 1341, 1159, 1159, 1159, - /* 80 */ 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, - /* 90 */ 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, - /* 100 */ 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, - /* 110 */ 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, - /* 120 */ 1159, 1159, 1159, 1159, 1350, 1355, 1361, 1354, 1351, 1344, - /* 130 */ 1343, 1345, 1346, 1159, 1180, 1229, 1159, 1159, 1159, 1159, - /* 140 */ 1447, 1446, 1159, 1159, 1190, 1347, 1348, 1358, 1357, 1436, - /* 150 */ 1492, 1491, 1394, 1159, 1159, 1159, 1159, 1159, 1159, 1459, - /* 160 */ 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, - /* 170 */ 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, - /* 180 */ 1159, 1159, 1159, 1159, 1459, 1459, 1159, 1190, 1459, 1459, - /* 190 */ 1186, 1336, 1335, 1186, 1289, 1159, 1442, 1265, 1256, 1159, - /* 200 */ 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, - /* 210 */ 1159, 1159, 1159, 1433, 1431, 1159, 1159, 1159, 1159, 1159, - /* 220 */ 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, - /* 230 */ 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, - /* 240 */ 1159, 1159, 1159, 1261, 1159, 1159, 1159, 1159, 1159, 1159, - /* 250 */ 1159, 1159, 1159, 1159, 1159, 1486, 1159, 1406, 1243, 1261, - /* 260 */ 1261, 1261, 1261, 1263, 1244, 1242, 1255, 1190, 1166, 1528, - /* 270 */ 1305, 1284, 1284, 1525, 1305, 1305, 1525, 1204, 1506, 1201, - /* 280 */ 1295, 1295, 1295, 1284, 1289, 1289, 1373, 1262, 1255, 1159, - /* 290 */ 1528, 1270, 1270, 1527, 1527, 1270, 1394, 1314, 1320, 1232, - /* 300 */ 1305, 1238, 1238, 1238, 1238, 1270, 1177, 1305, 1305, 1314, - /* 310 */ 1320, 1232, 1232, 1305, 1270, 1177, 1410, 1522, 1270, 1177, - /* 320 */ 1384, 1270, 1177, 1270, 1177, 1384, 1230, 1230, 1230, 1219, - /* 330 */ 1384, 1230, 1204, 1230, 1219, 1230, 1230, 1384, 1388, 1388, - /* 340 */ 1384, 1288, 1283, 1288, 1283, 1288, 1283, 1288, 1283, 1270, - /* 350 */ 1469, 1469, 1300, 1289, 1379, 1270, 1159, 1300, 1298, 1296, - /* 360 */ 1305, 1183, 1222, 1489, 1489, 1485, 1485, 1485, 1533, 1533, - /* 370 */ 1442, 1501, 1190, 1190, 1190, 1190, 1501, 1206, 1206, 1190, - /* 380 */ 1190, 1190, 1190, 1501, 1159, 1159, 1159, 1159, 1159, 1159, - /* 390 */ 1496, 1159, 1395, 1274, 1159, 1159, 1159, 1159, 1159, 1159, - /* 400 */ 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, - /* 410 */ 1159, 1159, 1159, 1159, 1159, 1159, 1325, 1159, 1162, 1439, - /* 420 */ 1159, 1159, 1437, 1159, 1159, 1159, 1159, 1159, 1159, 1275, - /* 430 */ 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, - /* 440 */ 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1524, 1159, - /* 450 */ 1159, 1159, 1159, 1159, 1159, 1409, 1408, 1159, 1159, 1272, - /* 460 */ 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, - /* 470 */ 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, - /* 480 */ 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, - /* 490 */ 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1297, 1159, - /* 500 */ 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, - /* 510 */ 1159, 1159, 1159, 1474, 1290, 1159, 1159, 1515, 1159, 1159, - /* 520 */ 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, - /* 530 */ 1159, 1159, 1510, 1246, 1327, 1159, 1326, 1330, 1159, 1171, - /* 540 */ 1159, + /* 0 */ 1691, 1691, 1691, 1516, 1279, 1392, 1279, 1279, 1279, 1279, + /* 10 */ 1516, 1516, 1516, 1279, 1279, 1279, 1279, 1279, 1279, 1422, + /* 20 */ 1422, 1568, 1312, 1279, 1279, 1279, 1279, 1279, 1279, 1279, + /* 30 */ 1279, 1279, 1279, 1279, 1279, 1515, 1279, 1279, 1279, 1279, + /* 40 */ 1607, 1607, 1279, 1279, 1279, 1279, 1279, 1592, 1591, 1279, + /* 50 */ 1279, 1279, 1431, 1279, 1279, 1279, 1438, 1279, 1279, 1279, + /* 60 */ 1279, 1279, 1517, 1518, 1279, 1279, 1279, 1279, 1567, 1569, + /* 70 */ 1533, 1445, 1444, 1443, 1442, 1551, 1410, 1436, 1429, 1433, + /* 80 */ 1512, 1513, 1511, 1670, 1518, 1517, 1279, 1432, 1480, 1496, + /* 90 */ 1479, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, + /* 100 */ 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, + /* 110 */ 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, + /* 120 */ 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, + /* 130 */ 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, + /* 140 */ 1279, 1279, 1279, 1279, 1279, 1279, 1488, 1495, 1494, 1493, + /* 150 */ 1502, 1492, 1489, 1482, 1481, 1483, 1484, 1303, 1300, 1354, + /* 160 */ 1279, 1279, 1279, 1279, 1279, 1485, 1312, 1473, 1472, 1471, + /* 170 */ 1279, 1499, 1486, 1498, 1497, 1575, 1644, 1643, 1534, 1279, + /* 180 */ 1279, 1279, 1279, 1279, 1279, 1607, 1279, 1279, 1279, 1279, + /* 190 */ 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, + /* 200 */ 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1412, 1607, + /* 210 */ 1607, 1279, 1312, 1607, 1607, 1308, 1413, 1413, 1308, 1308, + /* 220 */ 1416, 1587, 1383, 1383, 1383, 1383, 1392, 1383, 1279, 1279, + /* 230 */ 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, + /* 240 */ 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1572, 1570, 1279, + /* 250 */ 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, + /* 260 */ 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, + /* 270 */ 1279, 1279, 1279, 1279, 1279, 1388, 1279, 1279, 1279, 1279, + /* 280 */ 1279, 1279, 1279, 1279, 1279, 1279, 1637, 1683, 1279, 1546, + /* 290 */ 1368, 1388, 1388, 1388, 1388, 1390, 1369, 1367, 1382, 1313, + /* 300 */ 1286, 1683, 1683, 1448, 1437, 1389, 1437, 1680, 1435, 1448, + /* 310 */ 1448, 1435, 1448, 1389, 1680, 1329, 1659, 1324, 1422, 1422, + /* 320 */ 1422, 1412, 1412, 1412, 1412, 1416, 1416, 1514, 1389, 1382, + /* 330 */ 1279, 1355, 1683, 1355, 1355, 1398, 1398, 1682, 1682, 1398, + /* 340 */ 1534, 1667, 1457, 1357, 1363, 1363, 1363, 1363, 1398, 1297, + /* 350 */ 1435, 1667, 1667, 1435, 1457, 1357, 1435, 1357, 1435, 1398, + /* 360 */ 1297, 1550, 1678, 1398, 1297, 1524, 1398, 1297, 1398, 1297, + /* 370 */ 1524, 1355, 1355, 1355, 1344, 1279, 1279, 1524, 1355, 1329, + /* 380 */ 1355, 1344, 1355, 1355, 1625, 1279, 1528, 1528, 1524, 1398, + /* 390 */ 1617, 1617, 1425, 1425, 1430, 1416, 1519, 1398, 1279, 1430, + /* 400 */ 1428, 1426, 1435, 1347, 1640, 1640, 1636, 1636, 1636, 1688, + /* 410 */ 1688, 1587, 1652, 1312, 1312, 1312, 1312, 1652, 1331, 1331, + /* 420 */ 1313, 1313, 1312, 1652, 1279, 1279, 1279, 1279, 1279, 1279, + /* 430 */ 1279, 1647, 1279, 1279, 1535, 1279, 1279, 1279, 1279, 1279, + /* 440 */ 1279, 1279, 1402, 1279, 1279, 1279, 1279, 1279, 1279, 1279, + /* 450 */ 1279, 1279, 1593, 1279, 1279, 1279, 1279, 1279, 1279, 1279, + /* 460 */ 1279, 1279, 1279, 1279, 1462, 1279, 1282, 1584, 1279, 1279, + /* 470 */ 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, + /* 480 */ 1279, 1279, 1439, 1440, 1279, 1279, 1279, 1279, 1279, 1279, + /* 490 */ 1279, 1454, 1279, 1279, 1279, 1449, 1279, 1279, 1279, 1279, + /* 500 */ 1279, 1279, 1279, 1279, 1403, 1279, 1279, 1279, 1279, 1279, + /* 510 */ 1279, 1549, 1548, 1279, 1279, 1400, 1279, 1279, 1279, 1279, + /* 520 */ 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1327, + /* 530 */ 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, + /* 540 */ 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, + /* 550 */ 1279, 1279, 1279, 1427, 1279, 1279, 1279, 1279, 1279, 1279, + /* 560 */ 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1622, 1417, + /* 570 */ 1279, 1279, 1279, 1279, 1671, 1279, 1279, 1279, 1279, 1377, + /* 580 */ 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, 1279, + /* 590 */ 1663, 1371, 1463, 1279, 1466, 1301, 1279, 1291, 1279, 1279, }; /********** End of lemon-generated parsing tables *****************************/ -/* The next table maps tokens (terminal symbols) into fallback tokens. +/* The next table maps tokens (terminal symbols) into fallback tokens. ** If a construct like the following: -** +** ** %fallback ID X Y Z. ** ** appears in the grammar, then ID becomes a fallback token for X, Y, @@ -149097,52 +181247,53 @@ static const YYACTIONTYPE yy_default[] = { static const YYCODETYPE yyFallback[] = { 0, /* $ => nothing */ 0, /* SEMI => nothing */ - 59, /* EXPLAIN => ID */ - 59, /* QUERY => ID */ - 59, /* PLAN => ID */ - 59, /* BEGIN => ID */ + 60, /* EXPLAIN => ID */ + 60, /* QUERY => ID */ + 60, /* PLAN => ID */ + 60, /* BEGIN => ID */ 0, /* TRANSACTION => nothing */ - 59, /* DEFERRED => ID */ - 59, /* IMMEDIATE => ID */ - 59, /* EXCLUSIVE => ID */ + 60, /* DEFERRED => ID */ + 60, /* IMMEDIATE => ID */ + 60, /* EXCLUSIVE => ID */ 0, /* COMMIT => nothing */ - 59, /* END => ID */ - 59, /* ROLLBACK => ID */ - 59, /* SAVEPOINT => ID */ - 59, /* RELEASE => ID */ + 60, /* END => ID */ + 60, /* ROLLBACK => ID */ + 60, /* SAVEPOINT => ID */ + 60, /* RELEASE => ID */ 0, /* TO => nothing */ 0, /* TABLE => nothing */ 0, /* CREATE => nothing */ - 59, /* IF => ID */ + 60, /* IF => ID */ 0, /* NOT => nothing */ 0, /* EXISTS => nothing */ - 59, /* TEMP => ID */ + 60, /* TEMP => ID */ 0, /* LP => nothing */ 0, /* RP => nothing */ 0, /* AS => nothing */ - 59, /* WITHOUT => ID */ 0, /* COMMA => nothing */ - 59, /* ABORT => ID */ - 59, /* ACTION => ID */ - 59, /* AFTER => ID */ - 59, /* ANALYZE => ID */ - 59, /* ASC => ID */ - 59, /* ATTACH => ID */ - 59, /* BEFORE => ID */ - 59, /* BY => ID */ - 59, /* CASCADE => ID */ - 59, /* CAST => ID */ - 59, /* CONFLICT => ID */ - 59, /* DATABASE => ID */ - 59, /* DESC => ID */ - 59, /* DETACH => ID */ - 59, /* EACH => ID */ - 59, /* FAIL => ID */ + 60, /* WITHOUT => ID */ + 60, /* ABORT => ID */ + 60, /* ACTION => ID */ + 60, /* AFTER => ID */ + 60, /* ANALYZE => ID */ + 60, /* ASC => ID */ + 60, /* ATTACH => ID */ + 60, /* BEFORE => ID */ + 60, /* BY => ID */ + 60, /* CASCADE => ID */ + 60, /* CAST => ID */ + 60, /* CONFLICT => ID */ + 60, /* DATABASE => ID */ + 60, /* DESC => ID */ + 60, /* DETACH => ID */ + 60, /* EACH => ID */ + 60, /* FAIL => ID */ 0, /* OR => nothing */ 0, /* AND => nothing */ 0, /* IS => nothing */ - 59, /* MATCH => ID */ - 59, /* LIKE_KW => ID */ + 0, /* ISNOT => nothing */ + 60, /* MATCH => ID */ + 60, /* LIKE_KW => ID */ 0, /* BETWEEN => nothing */ 0, /* IN => nothing */ 0, /* ISNULL => nothing */ @@ -149155,41 +181306,132 @@ static const YYCODETYPE yyFallback[] = { 0, /* GE => nothing */ 0, /* ESCAPE => nothing */ 0, /* ID => nothing */ - 59, /* COLUMNKW => ID */ - 59, /* DO => ID */ - 59, /* FOR => ID */ - 59, /* IGNORE => ID */ - 59, /* INITIALLY => ID */ - 59, /* INSTEAD => ID */ - 59, /* NO => ID */ - 59, /* KEY => ID */ - 59, /* OF => ID */ - 59, /* OFFSET => ID */ - 59, /* PRAGMA => ID */ - 59, /* RAISE => ID */ - 59, /* RECURSIVE => ID */ - 59, /* REPLACE => ID */ - 59, /* RESTRICT => ID */ - 59, /* ROW => ID */ - 59, /* ROWS => ID */ - 59, /* TRIGGER => ID */ - 59, /* VACUUM => ID */ - 59, /* VIEW => ID */ - 59, /* VIRTUAL => ID */ - 59, /* WITH => ID */ - 59, /* CURRENT => ID */ - 59, /* FOLLOWING => ID */ - 59, /* PARTITION => ID */ - 59, /* PRECEDING => ID */ - 59, /* RANGE => ID */ - 59, /* UNBOUNDED => ID */ - 59, /* EXCLUDE => ID */ - 59, /* GROUPS => ID */ - 59, /* OTHERS => ID */ - 59, /* TIES => ID */ - 59, /* REINDEX => ID */ - 59, /* RENAME => ID */ - 59, /* CTIME_KW => ID */ + 60, /* COLUMNKW => ID */ + 60, /* DO => ID */ + 60, /* FOR => ID */ + 60, /* IGNORE => ID */ + 60, /* INITIALLY => ID */ + 60, /* INSTEAD => ID */ + 60, /* NO => ID */ + 60, /* KEY => ID */ + 60, /* OF => ID */ + 60, /* OFFSET => ID */ + 60, /* PRAGMA => ID */ + 60, /* RAISE => ID */ + 60, /* RECURSIVE => ID */ + 60, /* REPLACE => ID */ + 60, /* RESTRICT => ID */ + 60, /* ROW => ID */ + 60, /* ROWS => ID */ + 60, /* TRIGGER => ID */ + 60, /* VACUUM => ID */ + 60, /* VIEW => ID */ + 60, /* VIRTUAL => ID */ + 60, /* WITH => ID */ + 60, /* NULLS => ID */ + 60, /* FIRST => ID */ + 60, /* LAST => ID */ + 60, /* CURRENT => ID */ + 60, /* FOLLOWING => ID */ + 60, /* PARTITION => ID */ + 60, /* PRECEDING => ID */ + 60, /* RANGE => ID */ + 60, /* UNBOUNDED => ID */ + 60, /* EXCLUDE => ID */ + 60, /* GROUPS => ID */ + 60, /* OTHERS => ID */ + 60, /* TIES => ID */ + 60, /* GENERATED => ID */ + 60, /* ALWAYS => ID */ + 60, /* MATERIALIZED => ID */ + 60, /* REINDEX => ID */ + 60, /* RENAME => ID */ + 60, /* CTIME_KW => ID */ + 0, /* ANY => nothing */ + 0, /* BITAND => nothing */ + 0, /* BITOR => nothing */ + 0, /* LSHIFT => nothing */ + 0, /* RSHIFT => nothing */ + 0, /* PLUS => nothing */ + 0, /* MINUS => nothing */ + 0, /* STAR => nothing */ + 0, /* SLASH => nothing */ + 0, /* REM => nothing */ + 0, /* CONCAT => nothing */ + 0, /* PTR => nothing */ + 0, /* COLLATE => nothing */ + 0, /* BITNOT => nothing */ + 0, /* ON => nothing */ + 0, /* INDEXED => nothing */ + 0, /* STRING => nothing */ + 0, /* JOIN_KW => nothing */ + 0, /* CONSTRAINT => nothing */ + 0, /* DEFAULT => nothing */ + 0, /* NULL => nothing */ + 0, /* PRIMARY => nothing */ + 0, /* UNIQUE => nothing */ + 0, /* CHECK => nothing */ + 0, /* REFERENCES => nothing */ + 0, /* AUTOINCR => nothing */ + 0, /* INSERT => nothing */ + 0, /* DELETE => nothing */ + 0, /* UPDATE => nothing */ + 0, /* SET => nothing */ + 0, /* DEFERRABLE => nothing */ + 0, /* FOREIGN => nothing */ + 0, /* DROP => nothing */ + 0, /* UNION => nothing */ + 0, /* ALL => nothing */ + 0, /* EXCEPT => nothing */ + 0, /* INTERSECT => nothing */ + 0, /* SELECT => nothing */ + 0, /* VALUES => nothing */ + 0, /* DISTINCT => nothing */ + 0, /* DOT => nothing */ + 0, /* FROM => nothing */ + 0, /* JOIN => nothing */ + 0, /* USING => nothing */ + 0, /* ORDER => nothing */ + 0, /* GROUP => nothing */ + 0, /* HAVING => nothing */ + 0, /* LIMIT => nothing */ + 0, /* WHERE => nothing */ + 0, /* RETURNING => nothing */ + 0, /* INTO => nothing */ + 0, /* NOTHING => nothing */ + 0, /* FLOAT => nothing */ + 0, /* BLOB => nothing */ + 0, /* INTEGER => nothing */ + 0, /* VARIABLE => nothing */ + 0, /* CASE => nothing */ + 0, /* WHEN => nothing */ + 0, /* THEN => nothing */ + 0, /* ELSE => nothing */ + 0, /* INDEX => nothing */ + 0, /* ALTER => nothing */ + 0, /* ADD => nothing */ + 0, /* WINDOW => nothing */ + 0, /* OVER => nothing */ + 0, /* FILTER => nothing */ + 0, /* COLUMN => nothing */ + 0, /* AGG_FUNCTION => nothing */ + 0, /* AGG_COLUMN => nothing */ + 0, /* TRUEFALSE => nothing */ + 0, /* FUNCTION => nothing */ + 0, /* UPLUS => nothing */ + 0, /* UMINUS => nothing */ + 0, /* TRUTH => nothing */ + 0, /* REGISTER => nothing */ + 0, /* VECTOR => nothing */ + 0, /* SELECT_COLUMN => nothing */ + 0, /* IF_NULL_ROW => nothing */ + 0, /* ASTERISK => nothing */ + 0, /* SPAN => nothing */ + 0, /* ERROR => nothing */ + 0, /* QNUMBER => nothing */ + 0, /* SPACE => nothing */ + 0, /* COMMENT => nothing */ + 0, /* ILLEGAL => nothing */ }; #endif /* YYFALLBACK */ @@ -149230,17 +181472,13 @@ struct yyParser { #endif sqlite3ParserARG_SDECL /* A place to hold %extra_argument */ sqlite3ParserCTX_SDECL /* A place to hold %extra_context */ -#if YYSTACKDEPTH<=0 - int yystksz; /* Current side of the stack */ - yyStackEntry *yystack; /* The parser's stack */ - yyStackEntry yystk0; /* First stack entry */ -#else - yyStackEntry yystack[YYSTACKDEPTH]; /* The parser's stack */ - yyStackEntry *yystackEnd; /* Last entry in the stack */ -#endif + yyStackEntry *yystackEnd; /* Last entry in the stack */ + yyStackEntry *yystack; /* The parser stack */ + yyStackEntry yystk0[YYSTACKDEPTH]; /* Initial stack space */ }; typedef struct yyParser yyParser; +/* #include */ #ifndef NDEBUG /* #include */ static FILE *yyTraceFILE = 0; @@ -149248,10 +181486,10 @@ static char *yyTracePrompt = 0; #endif /* NDEBUG */ #ifndef NDEBUG -/* +/* ** Turn parser tracing on by giving a stream to which to write the trace ** and a prompt to preface each trace message. Tracing is turned off -** by making either argument NULL +** by making either argument NULL ** ** Inputs: **
                    @@ -149276,7 +181514,7 @@ SQLITE_PRIVATE void sqlite3ParserTrace(FILE *TraceFILE, char *zTracePrompt){ #if defined(YYCOVERAGE) || !defined(NDEBUG) /* For tracing shifts, the names of all terminals and nonterminals ** are required. The following table supplies these names */ -static const char *const yyTokenName[] = { +static const char *const yyTokenName[] = { /* 0 */ "$", /* 1 */ "SEMI", /* 2 */ "EXPLAIN", @@ -149302,8 +181540,8 @@ static const char *const yyTokenName[] = { /* 22 */ "LP", /* 23 */ "RP", /* 24 */ "AS", - /* 25 */ "WITHOUT", - /* 26 */ "COMMA", + /* 25 */ "COMMA", + /* 26 */ "WITHOUT", /* 27 */ "ABORT", /* 28 */ "ACTION", /* 29 */ "AFTER", @@ -149323,261 +181561,282 @@ static const char *const yyTokenName[] = { /* 43 */ "OR", /* 44 */ "AND", /* 45 */ "IS", - /* 46 */ "MATCH", - /* 47 */ "LIKE_KW", - /* 48 */ "BETWEEN", - /* 49 */ "IN", - /* 50 */ "ISNULL", - /* 51 */ "NOTNULL", - /* 52 */ "NE", - /* 53 */ "EQ", - /* 54 */ "GT", - /* 55 */ "LE", - /* 56 */ "LT", - /* 57 */ "GE", - /* 58 */ "ESCAPE", - /* 59 */ "ID", - /* 60 */ "COLUMNKW", - /* 61 */ "DO", - /* 62 */ "FOR", - /* 63 */ "IGNORE", - /* 64 */ "INITIALLY", - /* 65 */ "INSTEAD", - /* 66 */ "NO", - /* 67 */ "KEY", - /* 68 */ "OF", - /* 69 */ "OFFSET", - /* 70 */ "PRAGMA", - /* 71 */ "RAISE", - /* 72 */ "RECURSIVE", - /* 73 */ "REPLACE", - /* 74 */ "RESTRICT", - /* 75 */ "ROW", - /* 76 */ "ROWS", - /* 77 */ "TRIGGER", - /* 78 */ "VACUUM", - /* 79 */ "VIEW", - /* 80 */ "VIRTUAL", - /* 81 */ "WITH", - /* 82 */ "CURRENT", - /* 83 */ "FOLLOWING", - /* 84 */ "PARTITION", - /* 85 */ "PRECEDING", - /* 86 */ "RANGE", - /* 87 */ "UNBOUNDED", - /* 88 */ "EXCLUDE", - /* 89 */ "GROUPS", - /* 90 */ "OTHERS", - /* 91 */ "TIES", - /* 92 */ "REINDEX", - /* 93 */ "RENAME", - /* 94 */ "CTIME_KW", - /* 95 */ "ANY", - /* 96 */ "BITAND", - /* 97 */ "BITOR", - /* 98 */ "LSHIFT", - /* 99 */ "RSHIFT", - /* 100 */ "PLUS", - /* 101 */ "MINUS", - /* 102 */ "STAR", - /* 103 */ "SLASH", - /* 104 */ "REM", - /* 105 */ "CONCAT", - /* 106 */ "COLLATE", - /* 107 */ "BITNOT", - /* 108 */ "ON", - /* 109 */ "INDEXED", - /* 110 */ "STRING", - /* 111 */ "JOIN_KW", - /* 112 */ "CONSTRAINT", - /* 113 */ "DEFAULT", - /* 114 */ "NULL", - /* 115 */ "PRIMARY", - /* 116 */ "UNIQUE", - /* 117 */ "CHECK", - /* 118 */ "REFERENCES", - /* 119 */ "AUTOINCR", - /* 120 */ "INSERT", - /* 121 */ "DELETE", - /* 122 */ "UPDATE", - /* 123 */ "SET", - /* 124 */ "DEFERRABLE", - /* 125 */ "FOREIGN", - /* 126 */ "DROP", - /* 127 */ "UNION", - /* 128 */ "ALL", - /* 129 */ "EXCEPT", - /* 130 */ "INTERSECT", - /* 131 */ "SELECT", - /* 132 */ "VALUES", - /* 133 */ "DISTINCT", - /* 134 */ "DOT", - /* 135 */ "FROM", - /* 136 */ "JOIN", - /* 137 */ "USING", - /* 138 */ "ORDER", - /* 139 */ "GROUP", - /* 140 */ "HAVING", - /* 141 */ "LIMIT", - /* 142 */ "WHERE", - /* 143 */ "INTO", - /* 144 */ "NOTHING", - /* 145 */ "FLOAT", - /* 146 */ "BLOB", - /* 147 */ "INTEGER", - /* 148 */ "VARIABLE", - /* 149 */ "CASE", - /* 150 */ "WHEN", - /* 151 */ "THEN", - /* 152 */ "ELSE", - /* 153 */ "INDEX", - /* 154 */ "ALTER", - /* 155 */ "ADD", - /* 156 */ "WINDOW", - /* 157 */ "OVER", - /* 158 */ "FILTER", - /* 159 */ "TRUEFALSE", - /* 160 */ "ISNOT", - /* 161 */ "FUNCTION", - /* 162 */ "COLUMN", - /* 163 */ "AGG_FUNCTION", - /* 164 */ "AGG_COLUMN", - /* 165 */ "UMINUS", - /* 166 */ "UPLUS", - /* 167 */ "TRUTH", - /* 168 */ "REGISTER", - /* 169 */ "VECTOR", - /* 170 */ "SELECT_COLUMN", - /* 171 */ "IF_NULL_ROW", - /* 172 */ "ASTERISK", - /* 173 */ "SPAN", - /* 174 */ "SPACE", - /* 175 */ "ILLEGAL", - /* 176 */ "input", - /* 177 */ "cmdlist", - /* 178 */ "ecmd", - /* 179 */ "cmdx", - /* 180 */ "explain", - /* 181 */ "cmd", - /* 182 */ "transtype", - /* 183 */ "trans_opt", - /* 184 */ "nm", - /* 185 */ "savepoint_opt", - /* 186 */ "create_table", - /* 187 */ "create_table_args", - /* 188 */ "createkw", - /* 189 */ "temp", - /* 190 */ "ifnotexists", - /* 191 */ "dbnm", - /* 192 */ "columnlist", - /* 193 */ "conslist_opt", - /* 194 */ "table_options", - /* 195 */ "select", - /* 196 */ "columnname", - /* 197 */ "carglist", - /* 198 */ "typetoken", - /* 199 */ "typename", - /* 200 */ "signed", - /* 201 */ "plus_num", - /* 202 */ "minus_num", - /* 203 */ "scanpt", - /* 204 */ "ccons", - /* 205 */ "term", - /* 206 */ "expr", - /* 207 */ "onconf", - /* 208 */ "sortorder", - /* 209 */ "autoinc", - /* 210 */ "eidlist_opt", - /* 211 */ "refargs", - /* 212 */ "defer_subclause", - /* 213 */ "refarg", - /* 214 */ "refact", - /* 215 */ "init_deferred_pred_opt", - /* 216 */ "conslist", - /* 217 */ "tconscomma", - /* 218 */ "tcons", - /* 219 */ "sortlist", - /* 220 */ "eidlist", - /* 221 */ "defer_subclause_opt", - /* 222 */ "orconf", - /* 223 */ "resolvetype", - /* 224 */ "raisetype", - /* 225 */ "ifexists", - /* 226 */ "fullname", - /* 227 */ "selectnowith", - /* 228 */ "oneselect", - /* 229 */ "wqlist", - /* 230 */ "multiselect_op", - /* 231 */ "distinct", - /* 232 */ "selcollist", - /* 233 */ "from", - /* 234 */ "where_opt", - /* 235 */ "groupby_opt", - /* 236 */ "having_opt", - /* 237 */ "orderby_opt", - /* 238 */ "limit_opt", - /* 239 */ "window_clause", - /* 240 */ "values", - /* 241 */ "nexprlist", - /* 242 */ "sclp", - /* 243 */ "as", - /* 244 */ "seltablist", - /* 245 */ "stl_prefix", - /* 246 */ "joinop", - /* 247 */ "indexed_opt", - /* 248 */ "on_opt", - /* 249 */ "using_opt", - /* 250 */ "exprlist", - /* 251 */ "xfullname", - /* 252 */ "idlist", - /* 253 */ "with", - /* 254 */ "setlist", - /* 255 */ "insert_cmd", - /* 256 */ "idlist_opt", - /* 257 */ "upsert", - /* 258 */ "over_clause", - /* 259 */ "likeop", - /* 260 */ "between_op", - /* 261 */ "in_op", - /* 262 */ "paren_exprlist", - /* 263 */ "case_operand", - /* 264 */ "case_exprlist", - /* 265 */ "case_else", - /* 266 */ "uniqueflag", - /* 267 */ "collate", - /* 268 */ "vinto", - /* 269 */ "nmnum", - /* 270 */ "trigger_decl", - /* 271 */ "trigger_cmd_list", - /* 272 */ "trigger_time", - /* 273 */ "trigger_event", - /* 274 */ "foreach_clause", - /* 275 */ "when_clause", - /* 276 */ "trigger_cmd", - /* 277 */ "trnm", - /* 278 */ "tridxby", - /* 279 */ "database_kw_opt", - /* 280 */ "key_opt", - /* 281 */ "add_column_fullname", - /* 282 */ "kwcolumn_opt", - /* 283 */ "create_vtab", - /* 284 */ "vtabarglist", - /* 285 */ "vtabarg", - /* 286 */ "vtabargtoken", - /* 287 */ "lp", - /* 288 */ "anylist", - /* 289 */ "windowdefn_list", - /* 290 */ "windowdefn", - /* 291 */ "window", - /* 292 */ "frame_opt", - /* 293 */ "part_opt", - /* 294 */ "filter_opt", - /* 295 */ "range_or_rows", - /* 296 */ "frame_bound", - /* 297 */ "frame_bound_s", - /* 298 */ "frame_bound_e", - /* 299 */ "frame_exclude_opt", - /* 300 */ "frame_exclude", + /* 46 */ "ISNOT", + /* 47 */ "MATCH", + /* 48 */ "LIKE_KW", + /* 49 */ "BETWEEN", + /* 50 */ "IN", + /* 51 */ "ISNULL", + /* 52 */ "NOTNULL", + /* 53 */ "NE", + /* 54 */ "EQ", + /* 55 */ "GT", + /* 56 */ "LE", + /* 57 */ "LT", + /* 58 */ "GE", + /* 59 */ "ESCAPE", + /* 60 */ "ID", + /* 61 */ "COLUMNKW", + /* 62 */ "DO", + /* 63 */ "FOR", + /* 64 */ "IGNORE", + /* 65 */ "INITIALLY", + /* 66 */ "INSTEAD", + /* 67 */ "NO", + /* 68 */ "KEY", + /* 69 */ "OF", + /* 70 */ "OFFSET", + /* 71 */ "PRAGMA", + /* 72 */ "RAISE", + /* 73 */ "RECURSIVE", + /* 74 */ "REPLACE", + /* 75 */ "RESTRICT", + /* 76 */ "ROW", + /* 77 */ "ROWS", + /* 78 */ "TRIGGER", + /* 79 */ "VACUUM", + /* 80 */ "VIEW", + /* 81 */ "VIRTUAL", + /* 82 */ "WITH", + /* 83 */ "NULLS", + /* 84 */ "FIRST", + /* 85 */ "LAST", + /* 86 */ "CURRENT", + /* 87 */ "FOLLOWING", + /* 88 */ "PARTITION", + /* 89 */ "PRECEDING", + /* 90 */ "RANGE", + /* 91 */ "UNBOUNDED", + /* 92 */ "EXCLUDE", + /* 93 */ "GROUPS", + /* 94 */ "OTHERS", + /* 95 */ "TIES", + /* 96 */ "GENERATED", + /* 97 */ "ALWAYS", + /* 98 */ "MATERIALIZED", + /* 99 */ "REINDEX", + /* 100 */ "RENAME", + /* 101 */ "CTIME_KW", + /* 102 */ "ANY", + /* 103 */ "BITAND", + /* 104 */ "BITOR", + /* 105 */ "LSHIFT", + /* 106 */ "RSHIFT", + /* 107 */ "PLUS", + /* 108 */ "MINUS", + /* 109 */ "STAR", + /* 110 */ "SLASH", + /* 111 */ "REM", + /* 112 */ "CONCAT", + /* 113 */ "PTR", + /* 114 */ "COLLATE", + /* 115 */ "BITNOT", + /* 116 */ "ON", + /* 117 */ "INDEXED", + /* 118 */ "STRING", + /* 119 */ "JOIN_KW", + /* 120 */ "CONSTRAINT", + /* 121 */ "DEFAULT", + /* 122 */ "NULL", + /* 123 */ "PRIMARY", + /* 124 */ "UNIQUE", + /* 125 */ "CHECK", + /* 126 */ "REFERENCES", + /* 127 */ "AUTOINCR", + /* 128 */ "INSERT", + /* 129 */ "DELETE", + /* 130 */ "UPDATE", + /* 131 */ "SET", + /* 132 */ "DEFERRABLE", + /* 133 */ "FOREIGN", + /* 134 */ "DROP", + /* 135 */ "UNION", + /* 136 */ "ALL", + /* 137 */ "EXCEPT", + /* 138 */ "INTERSECT", + /* 139 */ "SELECT", + /* 140 */ "VALUES", + /* 141 */ "DISTINCT", + /* 142 */ "DOT", + /* 143 */ "FROM", + /* 144 */ "JOIN", + /* 145 */ "USING", + /* 146 */ "ORDER", + /* 147 */ "GROUP", + /* 148 */ "HAVING", + /* 149 */ "LIMIT", + /* 150 */ "WHERE", + /* 151 */ "RETURNING", + /* 152 */ "INTO", + /* 153 */ "NOTHING", + /* 154 */ "FLOAT", + /* 155 */ "BLOB", + /* 156 */ "INTEGER", + /* 157 */ "VARIABLE", + /* 158 */ "CASE", + /* 159 */ "WHEN", + /* 160 */ "THEN", + /* 161 */ "ELSE", + /* 162 */ "INDEX", + /* 163 */ "ALTER", + /* 164 */ "ADD", + /* 165 */ "WINDOW", + /* 166 */ "OVER", + /* 167 */ "FILTER", + /* 168 */ "COLUMN", + /* 169 */ "AGG_FUNCTION", + /* 170 */ "AGG_COLUMN", + /* 171 */ "TRUEFALSE", + /* 172 */ "FUNCTION", + /* 173 */ "UPLUS", + /* 174 */ "UMINUS", + /* 175 */ "TRUTH", + /* 176 */ "REGISTER", + /* 177 */ "VECTOR", + /* 178 */ "SELECT_COLUMN", + /* 179 */ "IF_NULL_ROW", + /* 180 */ "ASTERISK", + /* 181 */ "SPAN", + /* 182 */ "ERROR", + /* 183 */ "QNUMBER", + /* 184 */ "SPACE", + /* 185 */ "COMMENT", + /* 186 */ "ILLEGAL", + /* 187 */ "input", + /* 188 */ "cmdlist", + /* 189 */ "ecmd", + /* 190 */ "cmdx", + /* 191 */ "explain", + /* 192 */ "cmd", + /* 193 */ "transtype", + /* 194 */ "trans_opt", + /* 195 */ "nm", + /* 196 */ "savepoint_opt", + /* 197 */ "create_table", + /* 198 */ "create_table_args", + /* 199 */ "createkw", + /* 200 */ "temp", + /* 201 */ "ifnotexists", + /* 202 */ "dbnm", + /* 203 */ "columnlist", + /* 204 */ "conslist_opt", + /* 205 */ "table_option_set", + /* 206 */ "select", + /* 207 */ "table_option", + /* 208 */ "columnname", + /* 209 */ "carglist", + /* 210 */ "typetoken", + /* 211 */ "typename", + /* 212 */ "signed", + /* 213 */ "plus_num", + /* 214 */ "minus_num", + /* 215 */ "scanpt", + /* 216 */ "scantok", + /* 217 */ "ccons", + /* 218 */ "term", + /* 219 */ "expr", + /* 220 */ "onconf", + /* 221 */ "sortorder", + /* 222 */ "autoinc", + /* 223 */ "eidlist_opt", + /* 224 */ "refargs", + /* 225 */ "defer_subclause", + /* 226 */ "generated", + /* 227 */ "refarg", + /* 228 */ "refact", + /* 229 */ "init_deferred_pred_opt", + /* 230 */ "conslist", + /* 231 */ "tconscomma", + /* 232 */ "tcons", + /* 233 */ "sortlist", + /* 234 */ "eidlist", + /* 235 */ "defer_subclause_opt", + /* 236 */ "orconf", + /* 237 */ "resolvetype", + /* 238 */ "raisetype", + /* 239 */ "ifexists", + /* 240 */ "fullname", + /* 241 */ "selectnowith", + /* 242 */ "oneselect", + /* 243 */ "wqlist", + /* 244 */ "multiselect_op", + /* 245 */ "distinct", + /* 246 */ "selcollist", + /* 247 */ "from", + /* 248 */ "where_opt", + /* 249 */ "groupby_opt", + /* 250 */ "having_opt", + /* 251 */ "orderby_opt", + /* 252 */ "limit_opt", + /* 253 */ "window_clause", + /* 254 */ "values", + /* 255 */ "nexprlist", + /* 256 */ "mvalues", + /* 257 */ "sclp", + /* 258 */ "as", + /* 259 */ "seltablist", + /* 260 */ "stl_prefix", + /* 261 */ "joinop", + /* 262 */ "on_using", + /* 263 */ "indexed_by", + /* 264 */ "exprlist", + /* 265 */ "xfullname", + /* 266 */ "idlist", + /* 267 */ "indexed_opt", + /* 268 */ "nulls", + /* 269 */ "with", + /* 270 */ "where_opt_ret", + /* 271 */ "setlist", + /* 272 */ "insert_cmd", + /* 273 */ "idlist_opt", + /* 274 */ "upsert", + /* 275 */ "returning", + /* 276 */ "filter_over", + /* 277 */ "likeop", + /* 278 */ "between_op", + /* 279 */ "in_op", + /* 280 */ "paren_exprlist", + /* 281 */ "case_operand", + /* 282 */ "case_exprlist", + /* 283 */ "case_else", + /* 284 */ "uniqueflag", + /* 285 */ "collate", + /* 286 */ "vinto", + /* 287 */ "nmnum", + /* 288 */ "trigger_decl", + /* 289 */ "trigger_cmd_list", + /* 290 */ "trigger_time", + /* 291 */ "trigger_event", + /* 292 */ "foreach_clause", + /* 293 */ "when_clause", + /* 294 */ "trigger_cmd", + /* 295 */ "tridxby", + /* 296 */ "database_kw_opt", + /* 297 */ "key_opt", + /* 298 */ "alter_add", + /* 299 */ "kwcolumn_opt", + /* 300 */ "create_vtab", + /* 301 */ "vtabarglist", + /* 302 */ "vtabarg", + /* 303 */ "vtabargtoken", + /* 304 */ "lp", + /* 305 */ "anylist", + /* 306 */ "wqitem", + /* 307 */ "wqas", + /* 308 */ "withnm", + /* 309 */ "windowdefn_list", + /* 310 */ "windowdefn", + /* 311 */ "window", + /* 312 */ "frame_opt", + /* 313 */ "part_opt", + /* 314 */ "filter_clause", + /* 315 */ "over_clause", + /* 316 */ "range_or_rows", + /* 317 */ "frame_bound", + /* 318 */ "frame_bound_s", + /* 319 */ "frame_bound_e", + /* 320 */ "frame_exclude_opt", + /* 321 */ "frame_exclude", }; #endif /* defined(YYCOVERAGE) || !defined(NDEBUG) */ @@ -149604,397 +181863,451 @@ static const char *const yyRuleName[] = { /* 16 */ "ifnotexists ::= IF NOT EXISTS", /* 17 */ "temp ::= TEMP", /* 18 */ "temp ::=", - /* 19 */ "create_table_args ::= LP columnlist conslist_opt RP table_options", + /* 19 */ "create_table_args ::= LP columnlist conslist_opt RP table_option_set", /* 20 */ "create_table_args ::= AS select", - /* 21 */ "table_options ::=", - /* 22 */ "table_options ::= WITHOUT nm", - /* 23 */ "columnname ::= nm typetoken", - /* 24 */ "typetoken ::=", - /* 25 */ "typetoken ::= typename LP signed RP", - /* 26 */ "typetoken ::= typename LP signed COMMA signed RP", - /* 27 */ "typename ::= typename ID|STRING", - /* 28 */ "scanpt ::=", - /* 29 */ "ccons ::= CONSTRAINT nm", - /* 30 */ "ccons ::= DEFAULT scanpt term scanpt", - /* 31 */ "ccons ::= DEFAULT LP expr RP", - /* 32 */ "ccons ::= DEFAULT PLUS term scanpt", - /* 33 */ "ccons ::= DEFAULT MINUS term scanpt", - /* 34 */ "ccons ::= DEFAULT scanpt ID|INDEXED", - /* 35 */ "ccons ::= NOT NULL onconf", - /* 36 */ "ccons ::= PRIMARY KEY sortorder onconf autoinc", - /* 37 */ "ccons ::= UNIQUE onconf", - /* 38 */ "ccons ::= CHECK LP expr RP", - /* 39 */ "ccons ::= REFERENCES nm eidlist_opt refargs", - /* 40 */ "ccons ::= defer_subclause", - /* 41 */ "ccons ::= COLLATE ID|STRING", - /* 42 */ "autoinc ::=", - /* 43 */ "autoinc ::= AUTOINCR", - /* 44 */ "refargs ::=", - /* 45 */ "refargs ::= refargs refarg", - /* 46 */ "refarg ::= MATCH nm", - /* 47 */ "refarg ::= ON INSERT refact", - /* 48 */ "refarg ::= ON DELETE refact", - /* 49 */ "refarg ::= ON UPDATE refact", - /* 50 */ "refact ::= SET NULL", - /* 51 */ "refact ::= SET DEFAULT", - /* 52 */ "refact ::= CASCADE", - /* 53 */ "refact ::= RESTRICT", - /* 54 */ "refact ::= NO ACTION", - /* 55 */ "defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt", - /* 56 */ "defer_subclause ::= DEFERRABLE init_deferred_pred_opt", - /* 57 */ "init_deferred_pred_opt ::=", - /* 58 */ "init_deferred_pred_opt ::= INITIALLY DEFERRED", - /* 59 */ "init_deferred_pred_opt ::= INITIALLY IMMEDIATE", - /* 60 */ "conslist_opt ::=", - /* 61 */ "tconscomma ::= COMMA", - /* 62 */ "tcons ::= CONSTRAINT nm", - /* 63 */ "tcons ::= PRIMARY KEY LP sortlist autoinc RP onconf", - /* 64 */ "tcons ::= UNIQUE LP sortlist RP onconf", - /* 65 */ "tcons ::= CHECK LP expr RP onconf", - /* 66 */ "tcons ::= FOREIGN KEY LP eidlist RP REFERENCES nm eidlist_opt refargs defer_subclause_opt", - /* 67 */ "defer_subclause_opt ::=", - /* 68 */ "onconf ::=", - /* 69 */ "onconf ::= ON CONFLICT resolvetype", - /* 70 */ "orconf ::=", - /* 71 */ "orconf ::= OR resolvetype", - /* 72 */ "resolvetype ::= IGNORE", - /* 73 */ "resolvetype ::= REPLACE", - /* 74 */ "cmd ::= DROP TABLE ifexists fullname", - /* 75 */ "ifexists ::= IF EXISTS", - /* 76 */ "ifexists ::=", - /* 77 */ "cmd ::= createkw temp VIEW ifnotexists nm dbnm eidlist_opt AS select", - /* 78 */ "cmd ::= DROP VIEW ifexists fullname", - /* 79 */ "cmd ::= select", - /* 80 */ "select ::= WITH wqlist selectnowith", - /* 81 */ "select ::= WITH RECURSIVE wqlist selectnowith", - /* 82 */ "select ::= selectnowith", - /* 83 */ "selectnowith ::= selectnowith multiselect_op oneselect", - /* 84 */ "multiselect_op ::= UNION", - /* 85 */ "multiselect_op ::= UNION ALL", - /* 86 */ "multiselect_op ::= EXCEPT|INTERSECT", - /* 87 */ "oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt", - /* 88 */ "oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt window_clause orderby_opt limit_opt", - /* 89 */ "values ::= VALUES LP nexprlist RP", - /* 90 */ "values ::= values COMMA LP nexprlist RP", - /* 91 */ "distinct ::= DISTINCT", - /* 92 */ "distinct ::= ALL", - /* 93 */ "distinct ::=", - /* 94 */ "sclp ::=", - /* 95 */ "selcollist ::= sclp scanpt expr scanpt as", - /* 96 */ "selcollist ::= sclp scanpt STAR", - /* 97 */ "selcollist ::= sclp scanpt nm DOT STAR", - /* 98 */ "as ::= AS nm", - /* 99 */ "as ::=", - /* 100 */ "from ::=", - /* 101 */ "from ::= FROM seltablist", - /* 102 */ "stl_prefix ::= seltablist joinop", - /* 103 */ "stl_prefix ::=", - /* 104 */ "seltablist ::= stl_prefix nm dbnm as indexed_opt on_opt using_opt", - /* 105 */ "seltablist ::= stl_prefix nm dbnm LP exprlist RP as on_opt using_opt", - /* 106 */ "seltablist ::= stl_prefix LP select RP as on_opt using_opt", - /* 107 */ "seltablist ::= stl_prefix LP seltablist RP as on_opt using_opt", - /* 108 */ "dbnm ::=", - /* 109 */ "dbnm ::= DOT nm", - /* 110 */ "fullname ::= nm", - /* 111 */ "fullname ::= nm DOT nm", - /* 112 */ "xfullname ::= nm", - /* 113 */ "xfullname ::= nm DOT nm", - /* 114 */ "xfullname ::= nm DOT nm AS nm", - /* 115 */ "xfullname ::= nm AS nm", - /* 116 */ "joinop ::= COMMA|JOIN", - /* 117 */ "joinop ::= JOIN_KW JOIN", - /* 118 */ "joinop ::= JOIN_KW nm JOIN", - /* 119 */ "joinop ::= JOIN_KW nm nm JOIN", - /* 120 */ "on_opt ::= ON expr", - /* 121 */ "on_opt ::=", - /* 122 */ "indexed_opt ::=", - /* 123 */ "indexed_opt ::= INDEXED BY nm", - /* 124 */ "indexed_opt ::= NOT INDEXED", - /* 125 */ "using_opt ::= USING LP idlist RP", - /* 126 */ "using_opt ::=", - /* 127 */ "orderby_opt ::=", - /* 128 */ "orderby_opt ::= ORDER BY sortlist", - /* 129 */ "sortlist ::= sortlist COMMA expr sortorder", - /* 130 */ "sortlist ::= expr sortorder", - /* 131 */ "sortorder ::= ASC", - /* 132 */ "sortorder ::= DESC", - /* 133 */ "sortorder ::=", - /* 134 */ "groupby_opt ::=", - /* 135 */ "groupby_opt ::= GROUP BY nexprlist", - /* 136 */ "having_opt ::=", - /* 137 */ "having_opt ::= HAVING expr", - /* 138 */ "limit_opt ::=", - /* 139 */ "limit_opt ::= LIMIT expr", - /* 140 */ "limit_opt ::= LIMIT expr OFFSET expr", - /* 141 */ "limit_opt ::= LIMIT expr COMMA expr", - /* 142 */ "cmd ::= with DELETE FROM xfullname indexed_opt where_opt", - /* 143 */ "where_opt ::=", - /* 144 */ "where_opt ::= WHERE expr", - /* 145 */ "cmd ::= with UPDATE orconf xfullname indexed_opt SET setlist where_opt", - /* 146 */ "setlist ::= setlist COMMA nm EQ expr", - /* 147 */ "setlist ::= setlist COMMA LP idlist RP EQ expr", - /* 148 */ "setlist ::= nm EQ expr", - /* 149 */ "setlist ::= LP idlist RP EQ expr", - /* 150 */ "cmd ::= with insert_cmd INTO xfullname idlist_opt select upsert", - /* 151 */ "cmd ::= with insert_cmd INTO xfullname idlist_opt DEFAULT VALUES", - /* 152 */ "upsert ::=", - /* 153 */ "upsert ::= ON CONFLICT LP sortlist RP where_opt DO UPDATE SET setlist where_opt", - /* 154 */ "upsert ::= ON CONFLICT LP sortlist RP where_opt DO NOTHING", - /* 155 */ "upsert ::= ON CONFLICT DO NOTHING", - /* 156 */ "insert_cmd ::= INSERT orconf", - /* 157 */ "insert_cmd ::= REPLACE", - /* 158 */ "idlist_opt ::=", - /* 159 */ "idlist_opt ::= LP idlist RP", - /* 160 */ "idlist ::= idlist COMMA nm", - /* 161 */ "idlist ::= nm", - /* 162 */ "expr ::= LP expr RP", - /* 163 */ "expr ::= ID|INDEXED", - /* 164 */ "expr ::= JOIN_KW", - /* 165 */ "expr ::= nm DOT nm", - /* 166 */ "expr ::= nm DOT nm DOT nm", - /* 167 */ "term ::= NULL|FLOAT|BLOB", - /* 168 */ "term ::= STRING", - /* 169 */ "term ::= INTEGER", - /* 170 */ "expr ::= VARIABLE", - /* 171 */ "expr ::= expr COLLATE ID|STRING", - /* 172 */ "expr ::= CAST LP expr AS typetoken RP", - /* 173 */ "expr ::= ID|INDEXED LP distinct exprlist RP", - /* 174 */ "expr ::= ID|INDEXED LP STAR RP", - /* 175 */ "expr ::= ID|INDEXED LP distinct exprlist RP over_clause", - /* 176 */ "expr ::= ID|INDEXED LP STAR RP over_clause", - /* 177 */ "term ::= CTIME_KW", - /* 178 */ "expr ::= LP nexprlist COMMA expr RP", - /* 179 */ "expr ::= expr AND expr", - /* 180 */ "expr ::= expr OR expr", - /* 181 */ "expr ::= expr LT|GT|GE|LE expr", - /* 182 */ "expr ::= expr EQ|NE expr", - /* 183 */ "expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr", - /* 184 */ "expr ::= expr PLUS|MINUS expr", - /* 185 */ "expr ::= expr STAR|SLASH|REM expr", - /* 186 */ "expr ::= expr CONCAT expr", - /* 187 */ "likeop ::= NOT LIKE_KW|MATCH", - /* 188 */ "expr ::= expr likeop expr", - /* 189 */ "expr ::= expr likeop expr ESCAPE expr", - /* 190 */ "expr ::= expr ISNULL|NOTNULL", - /* 191 */ "expr ::= expr NOT NULL", - /* 192 */ "expr ::= expr IS expr", - /* 193 */ "expr ::= expr IS NOT expr", - /* 194 */ "expr ::= NOT expr", - /* 195 */ "expr ::= BITNOT expr", - /* 196 */ "expr ::= PLUS|MINUS expr", - /* 197 */ "between_op ::= BETWEEN", - /* 198 */ "between_op ::= NOT BETWEEN", - /* 199 */ "expr ::= expr between_op expr AND expr", - /* 200 */ "in_op ::= IN", - /* 201 */ "in_op ::= NOT IN", - /* 202 */ "expr ::= expr in_op LP exprlist RP", - /* 203 */ "expr ::= LP select RP", - /* 204 */ "expr ::= expr in_op LP select RP", - /* 205 */ "expr ::= expr in_op nm dbnm paren_exprlist", - /* 206 */ "expr ::= EXISTS LP select RP", - /* 207 */ "expr ::= CASE case_operand case_exprlist case_else END", - /* 208 */ "case_exprlist ::= case_exprlist WHEN expr THEN expr", - /* 209 */ "case_exprlist ::= WHEN expr THEN expr", - /* 210 */ "case_else ::= ELSE expr", - /* 211 */ "case_else ::=", - /* 212 */ "case_operand ::= expr", - /* 213 */ "case_operand ::=", - /* 214 */ "exprlist ::=", - /* 215 */ "nexprlist ::= nexprlist COMMA expr", - /* 216 */ "nexprlist ::= expr", - /* 217 */ "paren_exprlist ::=", - /* 218 */ "paren_exprlist ::= LP exprlist RP", - /* 219 */ "cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt", - /* 220 */ "uniqueflag ::= UNIQUE", - /* 221 */ "uniqueflag ::=", - /* 222 */ "eidlist_opt ::=", - /* 223 */ "eidlist_opt ::= LP eidlist RP", - /* 224 */ "eidlist ::= eidlist COMMA nm collate sortorder", - /* 225 */ "eidlist ::= nm collate sortorder", - /* 226 */ "collate ::=", - /* 227 */ "collate ::= COLLATE ID|STRING", - /* 228 */ "cmd ::= DROP INDEX ifexists fullname", - /* 229 */ "cmd ::= VACUUM vinto", - /* 230 */ "cmd ::= VACUUM nm vinto", - /* 231 */ "vinto ::= INTO expr", - /* 232 */ "vinto ::=", - /* 233 */ "cmd ::= PRAGMA nm dbnm", - /* 234 */ "cmd ::= PRAGMA nm dbnm EQ nmnum", - /* 235 */ "cmd ::= PRAGMA nm dbnm LP nmnum RP", - /* 236 */ "cmd ::= PRAGMA nm dbnm EQ minus_num", - /* 237 */ "cmd ::= PRAGMA nm dbnm LP minus_num RP", - /* 238 */ "plus_num ::= PLUS INTEGER|FLOAT", - /* 239 */ "minus_num ::= MINUS INTEGER|FLOAT", - /* 240 */ "cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END", - /* 241 */ "trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause", - /* 242 */ "trigger_time ::= BEFORE|AFTER", - /* 243 */ "trigger_time ::= INSTEAD OF", - /* 244 */ "trigger_time ::=", - /* 245 */ "trigger_event ::= DELETE|INSERT", - /* 246 */ "trigger_event ::= UPDATE", - /* 247 */ "trigger_event ::= UPDATE OF idlist", - /* 248 */ "when_clause ::=", - /* 249 */ "when_clause ::= WHEN expr", - /* 250 */ "trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI", - /* 251 */ "trigger_cmd_list ::= trigger_cmd SEMI", - /* 252 */ "trnm ::= nm DOT nm", - /* 253 */ "tridxby ::= INDEXED BY nm", - /* 254 */ "tridxby ::= NOT INDEXED", - /* 255 */ "trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist where_opt scanpt", - /* 256 */ "trigger_cmd ::= scanpt insert_cmd INTO trnm idlist_opt select upsert scanpt", - /* 257 */ "trigger_cmd ::= DELETE FROM trnm tridxby where_opt scanpt", - /* 258 */ "trigger_cmd ::= scanpt select scanpt", - /* 259 */ "expr ::= RAISE LP IGNORE RP", - /* 260 */ "expr ::= RAISE LP raisetype COMMA nm RP", - /* 261 */ "raisetype ::= ROLLBACK", - /* 262 */ "raisetype ::= ABORT", - /* 263 */ "raisetype ::= FAIL", - /* 264 */ "cmd ::= DROP TRIGGER ifexists fullname", - /* 265 */ "cmd ::= ATTACH database_kw_opt expr AS expr key_opt", - /* 266 */ "cmd ::= DETACH database_kw_opt expr", - /* 267 */ "key_opt ::=", - /* 268 */ "key_opt ::= KEY expr", - /* 269 */ "cmd ::= REINDEX", - /* 270 */ "cmd ::= REINDEX nm dbnm", - /* 271 */ "cmd ::= ANALYZE", - /* 272 */ "cmd ::= ANALYZE nm dbnm", - /* 273 */ "cmd ::= ALTER TABLE fullname RENAME TO nm", - /* 274 */ "cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist", - /* 275 */ "add_column_fullname ::= fullname", - /* 276 */ "cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm", - /* 277 */ "cmd ::= create_vtab", - /* 278 */ "cmd ::= create_vtab LP vtabarglist RP", - /* 279 */ "create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm", - /* 280 */ "vtabarg ::=", - /* 281 */ "vtabargtoken ::= ANY", - /* 282 */ "vtabargtoken ::= lp anylist RP", - /* 283 */ "lp ::= LP", - /* 284 */ "with ::= WITH wqlist", - /* 285 */ "with ::= WITH RECURSIVE wqlist", - /* 286 */ "wqlist ::= nm eidlist_opt AS LP select RP", - /* 287 */ "wqlist ::= wqlist COMMA nm eidlist_opt AS LP select RP", - /* 288 */ "windowdefn_list ::= windowdefn", - /* 289 */ "windowdefn_list ::= windowdefn_list COMMA windowdefn", - /* 290 */ "windowdefn ::= nm AS LP window RP", - /* 291 */ "window ::= PARTITION BY nexprlist orderby_opt frame_opt", - /* 292 */ "window ::= nm PARTITION BY nexprlist orderby_opt frame_opt", - /* 293 */ "window ::= ORDER BY sortlist frame_opt", - /* 294 */ "window ::= nm ORDER BY sortlist frame_opt", - /* 295 */ "window ::= frame_opt", - /* 296 */ "window ::= nm frame_opt", - /* 297 */ "frame_opt ::=", - /* 298 */ "frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt", - /* 299 */ "frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt", - /* 300 */ "range_or_rows ::= RANGE|ROWS|GROUPS", - /* 301 */ "frame_bound_s ::= frame_bound", - /* 302 */ "frame_bound_s ::= UNBOUNDED PRECEDING", - /* 303 */ "frame_bound_e ::= frame_bound", - /* 304 */ "frame_bound_e ::= UNBOUNDED FOLLOWING", - /* 305 */ "frame_bound ::= expr PRECEDING|FOLLOWING", - /* 306 */ "frame_bound ::= CURRENT ROW", - /* 307 */ "frame_exclude_opt ::=", - /* 308 */ "frame_exclude_opt ::= EXCLUDE frame_exclude", - /* 309 */ "frame_exclude ::= NO OTHERS", - /* 310 */ "frame_exclude ::= CURRENT ROW", - /* 311 */ "frame_exclude ::= GROUP|TIES", - /* 312 */ "window_clause ::= WINDOW windowdefn_list", - /* 313 */ "over_clause ::= filter_opt OVER LP window RP", - /* 314 */ "over_clause ::= filter_opt OVER nm", - /* 315 */ "filter_opt ::=", - /* 316 */ "filter_opt ::= FILTER LP WHERE expr RP", - /* 317 */ "input ::= cmdlist", - /* 318 */ "cmdlist ::= cmdlist ecmd", - /* 319 */ "cmdlist ::= ecmd", - /* 320 */ "ecmd ::= SEMI", - /* 321 */ "ecmd ::= cmdx SEMI", - /* 322 */ "ecmd ::= explain cmdx", - /* 323 */ "trans_opt ::=", - /* 324 */ "trans_opt ::= TRANSACTION", - /* 325 */ "trans_opt ::= TRANSACTION nm", - /* 326 */ "savepoint_opt ::= SAVEPOINT", - /* 327 */ "savepoint_opt ::=", - /* 328 */ "cmd ::= create_table create_table_args", - /* 329 */ "columnlist ::= columnlist COMMA columnname carglist", - /* 330 */ "columnlist ::= columnname carglist", - /* 331 */ "nm ::= ID|INDEXED", - /* 332 */ "nm ::= STRING", - /* 333 */ "nm ::= JOIN_KW", - /* 334 */ "typetoken ::= typename", - /* 335 */ "typename ::= ID|STRING", - /* 336 */ "signed ::= plus_num", - /* 337 */ "signed ::= minus_num", - /* 338 */ "carglist ::= carglist ccons", - /* 339 */ "carglist ::=", - /* 340 */ "ccons ::= NULL onconf", - /* 341 */ "conslist_opt ::= COMMA conslist", - /* 342 */ "conslist ::= conslist tconscomma tcons", - /* 343 */ "conslist ::= tcons", - /* 344 */ "tconscomma ::=", - /* 345 */ "defer_subclause_opt ::= defer_subclause", - /* 346 */ "resolvetype ::= raisetype", - /* 347 */ "selectnowith ::= oneselect", - /* 348 */ "oneselect ::= values", - /* 349 */ "sclp ::= selcollist COMMA", - /* 350 */ "as ::= ID|STRING", - /* 351 */ "expr ::= term", - /* 352 */ "likeop ::= LIKE_KW|MATCH", - /* 353 */ "exprlist ::= nexprlist", - /* 354 */ "nmnum ::= plus_num", - /* 355 */ "nmnum ::= nm", - /* 356 */ "nmnum ::= ON", - /* 357 */ "nmnum ::= DELETE", - /* 358 */ "nmnum ::= DEFAULT", - /* 359 */ "plus_num ::= INTEGER|FLOAT", - /* 360 */ "foreach_clause ::=", - /* 361 */ "foreach_clause ::= FOR EACH ROW", - /* 362 */ "trnm ::= nm", - /* 363 */ "tridxby ::=", - /* 364 */ "database_kw_opt ::= DATABASE", - /* 365 */ "database_kw_opt ::=", - /* 366 */ "kwcolumn_opt ::=", - /* 367 */ "kwcolumn_opt ::= COLUMNKW", - /* 368 */ "vtabarglist ::= vtabarg", - /* 369 */ "vtabarglist ::= vtabarglist COMMA vtabarg", - /* 370 */ "vtabarg ::= vtabarg vtabargtoken", - /* 371 */ "anylist ::=", - /* 372 */ "anylist ::= anylist LP anylist RP", - /* 373 */ "anylist ::= anylist ANY", - /* 374 */ "with ::=", + /* 21 */ "table_option_set ::=", + /* 22 */ "table_option_set ::= table_option_set COMMA table_option", + /* 23 */ "table_option ::= WITHOUT nm", + /* 24 */ "table_option ::= nm", + /* 25 */ "columnname ::= nm typetoken", + /* 26 */ "typetoken ::=", + /* 27 */ "typetoken ::= typename LP signed RP", + /* 28 */ "typetoken ::= typename LP signed COMMA signed RP", + /* 29 */ "typename ::= typename ID|STRING", + /* 30 */ "scanpt ::=", + /* 31 */ "scantok ::=", + /* 32 */ "ccons ::= CONSTRAINT nm", + /* 33 */ "ccons ::= DEFAULT scantok term", + /* 34 */ "ccons ::= DEFAULT LP expr RP", + /* 35 */ "ccons ::= DEFAULT PLUS scantok term", + /* 36 */ "ccons ::= DEFAULT MINUS scantok term", + /* 37 */ "ccons ::= DEFAULT scantok ID|INDEXED", + /* 38 */ "ccons ::= NOT NULL onconf", + /* 39 */ "ccons ::= PRIMARY KEY sortorder onconf autoinc", + /* 40 */ "ccons ::= UNIQUE onconf", + /* 41 */ "ccons ::= CHECK LP expr RP", + /* 42 */ "ccons ::= REFERENCES nm eidlist_opt refargs", + /* 43 */ "ccons ::= defer_subclause", + /* 44 */ "ccons ::= COLLATE ID|STRING", + /* 45 */ "generated ::= LP expr RP", + /* 46 */ "generated ::= LP expr RP ID", + /* 47 */ "autoinc ::=", + /* 48 */ "autoinc ::= AUTOINCR", + /* 49 */ "refargs ::=", + /* 50 */ "refargs ::= refargs refarg", + /* 51 */ "refarg ::= MATCH nm", + /* 52 */ "refarg ::= ON INSERT refact", + /* 53 */ "refarg ::= ON DELETE refact", + /* 54 */ "refarg ::= ON UPDATE refact", + /* 55 */ "refact ::= SET NULL", + /* 56 */ "refact ::= SET DEFAULT", + /* 57 */ "refact ::= CASCADE", + /* 58 */ "refact ::= RESTRICT", + /* 59 */ "refact ::= NO ACTION", + /* 60 */ "defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt", + /* 61 */ "defer_subclause ::= DEFERRABLE init_deferred_pred_opt", + /* 62 */ "init_deferred_pred_opt ::=", + /* 63 */ "init_deferred_pred_opt ::= INITIALLY DEFERRED", + /* 64 */ "init_deferred_pred_opt ::= INITIALLY IMMEDIATE", + /* 65 */ "conslist_opt ::=", + /* 66 */ "tconscomma ::= COMMA", + /* 67 */ "tcons ::= CONSTRAINT nm", + /* 68 */ "tcons ::= PRIMARY KEY LP sortlist autoinc RP onconf", + /* 69 */ "tcons ::= UNIQUE LP sortlist RP onconf", + /* 70 */ "tcons ::= CHECK LP expr RP onconf", + /* 71 */ "tcons ::= FOREIGN KEY LP eidlist RP REFERENCES nm eidlist_opt refargs defer_subclause_opt", + /* 72 */ "defer_subclause_opt ::=", + /* 73 */ "onconf ::=", + /* 74 */ "onconf ::= ON CONFLICT resolvetype", + /* 75 */ "orconf ::=", + /* 76 */ "orconf ::= OR resolvetype", + /* 77 */ "resolvetype ::= IGNORE", + /* 78 */ "resolvetype ::= REPLACE", + /* 79 */ "cmd ::= DROP TABLE ifexists fullname", + /* 80 */ "ifexists ::= IF EXISTS", + /* 81 */ "ifexists ::=", + /* 82 */ "cmd ::= createkw temp VIEW ifnotexists nm dbnm eidlist_opt AS select", + /* 83 */ "cmd ::= DROP VIEW ifexists fullname", + /* 84 */ "cmd ::= select", + /* 85 */ "select ::= WITH wqlist selectnowith", + /* 86 */ "select ::= WITH RECURSIVE wqlist selectnowith", + /* 87 */ "select ::= selectnowith", + /* 88 */ "selectnowith ::= selectnowith multiselect_op oneselect", + /* 89 */ "multiselect_op ::= UNION", + /* 90 */ "multiselect_op ::= UNION ALL", + /* 91 */ "multiselect_op ::= EXCEPT|INTERSECT", + /* 92 */ "oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt", + /* 93 */ "oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt window_clause orderby_opt limit_opt", + /* 94 */ "values ::= VALUES LP nexprlist RP", + /* 95 */ "oneselect ::= mvalues", + /* 96 */ "mvalues ::= values COMMA LP nexprlist RP", + /* 97 */ "mvalues ::= mvalues COMMA LP nexprlist RP", + /* 98 */ "distinct ::= DISTINCT", + /* 99 */ "distinct ::= ALL", + /* 100 */ "distinct ::=", + /* 101 */ "sclp ::=", + /* 102 */ "selcollist ::= sclp scanpt expr scanpt as", + /* 103 */ "selcollist ::= sclp scanpt STAR", + /* 104 */ "selcollist ::= sclp scanpt nm DOT STAR", + /* 105 */ "as ::= AS nm", + /* 106 */ "as ::=", + /* 107 */ "from ::=", + /* 108 */ "from ::= FROM seltablist", + /* 109 */ "stl_prefix ::= seltablist joinop", + /* 110 */ "stl_prefix ::=", + /* 111 */ "seltablist ::= stl_prefix nm dbnm as on_using", + /* 112 */ "seltablist ::= stl_prefix nm dbnm as indexed_by on_using", + /* 113 */ "seltablist ::= stl_prefix nm dbnm LP exprlist RP as on_using", + /* 114 */ "seltablist ::= stl_prefix LP select RP as on_using", + /* 115 */ "seltablist ::= stl_prefix LP seltablist RP as on_using", + /* 116 */ "dbnm ::=", + /* 117 */ "dbnm ::= DOT nm", + /* 118 */ "fullname ::= nm", + /* 119 */ "fullname ::= nm DOT nm", + /* 120 */ "xfullname ::= nm", + /* 121 */ "xfullname ::= nm DOT nm", + /* 122 */ "xfullname ::= nm AS nm", + /* 123 */ "xfullname ::= nm DOT nm AS nm", + /* 124 */ "joinop ::= COMMA|JOIN", + /* 125 */ "joinop ::= JOIN_KW JOIN", + /* 126 */ "joinop ::= JOIN_KW nm JOIN", + /* 127 */ "joinop ::= JOIN_KW nm nm JOIN", + /* 128 */ "on_using ::= ON expr", + /* 129 */ "on_using ::= USING LP idlist RP", + /* 130 */ "on_using ::=", + /* 131 */ "indexed_opt ::=", + /* 132 */ "indexed_by ::= INDEXED BY nm", + /* 133 */ "indexed_by ::= NOT INDEXED", + /* 134 */ "orderby_opt ::=", + /* 135 */ "orderby_opt ::= ORDER BY sortlist", + /* 136 */ "sortlist ::= sortlist COMMA expr sortorder nulls", + /* 137 */ "sortlist ::= expr sortorder nulls", + /* 138 */ "sortorder ::= ASC", + /* 139 */ "sortorder ::= DESC", + /* 140 */ "sortorder ::=", + /* 141 */ "nulls ::= NULLS FIRST", + /* 142 */ "nulls ::= NULLS LAST", + /* 143 */ "nulls ::=", + /* 144 */ "groupby_opt ::=", + /* 145 */ "groupby_opt ::= GROUP BY nexprlist", + /* 146 */ "having_opt ::=", + /* 147 */ "having_opt ::= HAVING expr", + /* 148 */ "limit_opt ::=", + /* 149 */ "limit_opt ::= LIMIT expr", + /* 150 */ "limit_opt ::= LIMIT expr OFFSET expr", + /* 151 */ "limit_opt ::= LIMIT expr COMMA expr", + /* 152 */ "cmd ::= with DELETE FROM xfullname indexed_opt where_opt_ret", + /* 153 */ "where_opt ::=", + /* 154 */ "where_opt ::= WHERE expr", + /* 155 */ "where_opt_ret ::=", + /* 156 */ "where_opt_ret ::= WHERE expr", + /* 157 */ "where_opt_ret ::= RETURNING selcollist", + /* 158 */ "where_opt_ret ::= WHERE expr RETURNING selcollist", + /* 159 */ "cmd ::= with UPDATE orconf xfullname indexed_opt SET setlist from where_opt_ret", + /* 160 */ "setlist ::= setlist COMMA nm EQ expr", + /* 161 */ "setlist ::= setlist COMMA LP idlist RP EQ expr", + /* 162 */ "setlist ::= nm EQ expr", + /* 163 */ "setlist ::= LP idlist RP EQ expr", + /* 164 */ "cmd ::= with insert_cmd INTO xfullname idlist_opt select upsert", + /* 165 */ "cmd ::= with insert_cmd INTO xfullname idlist_opt DEFAULT VALUES returning", + /* 166 */ "upsert ::=", + /* 167 */ "upsert ::= RETURNING selcollist", + /* 168 */ "upsert ::= ON CONFLICT LP sortlist RP where_opt DO UPDATE SET setlist where_opt upsert", + /* 169 */ "upsert ::= ON CONFLICT LP sortlist RP where_opt DO NOTHING upsert", + /* 170 */ "upsert ::= ON CONFLICT DO NOTHING returning", + /* 171 */ "upsert ::= ON CONFLICT DO UPDATE SET setlist where_opt returning", + /* 172 */ "returning ::= RETURNING selcollist", + /* 173 */ "insert_cmd ::= INSERT orconf", + /* 174 */ "insert_cmd ::= REPLACE", + /* 175 */ "idlist_opt ::=", + /* 176 */ "idlist_opt ::= LP idlist RP", + /* 177 */ "idlist ::= idlist COMMA nm", + /* 178 */ "idlist ::= nm", + /* 179 */ "expr ::= LP expr RP", + /* 180 */ "expr ::= ID|INDEXED|JOIN_KW", + /* 181 */ "expr ::= nm DOT nm", + /* 182 */ "expr ::= nm DOT nm DOT nm", + /* 183 */ "term ::= NULL|FLOAT|BLOB", + /* 184 */ "term ::= STRING", + /* 185 */ "term ::= INTEGER", + /* 186 */ "expr ::= VARIABLE", + /* 187 */ "expr ::= expr COLLATE ID|STRING", + /* 188 */ "expr ::= CAST LP expr AS typetoken RP", + /* 189 */ "expr ::= ID|INDEXED|JOIN_KW LP distinct exprlist RP", + /* 190 */ "expr ::= ID|INDEXED|JOIN_KW LP distinct exprlist ORDER BY sortlist RP", + /* 191 */ "expr ::= ID|INDEXED|JOIN_KW LP STAR RP", + /* 192 */ "expr ::= ID|INDEXED|JOIN_KW LP distinct exprlist RP filter_over", + /* 193 */ "expr ::= ID|INDEXED|JOIN_KW LP distinct exprlist ORDER BY sortlist RP filter_over", + /* 194 */ "expr ::= ID|INDEXED|JOIN_KW LP STAR RP filter_over", + /* 195 */ "term ::= CTIME_KW", + /* 196 */ "expr ::= LP nexprlist COMMA expr RP", + /* 197 */ "expr ::= expr AND expr", + /* 198 */ "expr ::= expr OR expr", + /* 199 */ "expr ::= expr LT|GT|GE|LE expr", + /* 200 */ "expr ::= expr EQ|NE expr", + /* 201 */ "expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr", + /* 202 */ "expr ::= expr PLUS|MINUS expr", + /* 203 */ "expr ::= expr STAR|SLASH|REM expr", + /* 204 */ "expr ::= expr CONCAT expr", + /* 205 */ "likeop ::= NOT LIKE_KW|MATCH", + /* 206 */ "expr ::= expr likeop expr", + /* 207 */ "expr ::= expr likeop expr ESCAPE expr", + /* 208 */ "expr ::= expr ISNULL|NOTNULL", + /* 209 */ "expr ::= expr NOT NULL", + /* 210 */ "expr ::= expr IS expr", + /* 211 */ "expr ::= expr IS NOT expr", + /* 212 */ "expr ::= expr IS NOT DISTINCT FROM expr", + /* 213 */ "expr ::= expr IS DISTINCT FROM expr", + /* 214 */ "expr ::= NOT expr", + /* 215 */ "expr ::= BITNOT expr", + /* 216 */ "expr ::= PLUS|MINUS expr", + /* 217 */ "expr ::= expr PTR expr", + /* 218 */ "between_op ::= BETWEEN", + /* 219 */ "between_op ::= NOT BETWEEN", + /* 220 */ "expr ::= expr between_op expr AND expr", + /* 221 */ "in_op ::= IN", + /* 222 */ "in_op ::= NOT IN", + /* 223 */ "expr ::= expr in_op LP exprlist RP", + /* 224 */ "expr ::= LP select RP", + /* 225 */ "expr ::= expr in_op LP select RP", + /* 226 */ "expr ::= expr in_op nm dbnm paren_exprlist", + /* 227 */ "expr ::= EXISTS LP select RP", + /* 228 */ "expr ::= CASE case_operand case_exprlist case_else END", + /* 229 */ "case_exprlist ::= case_exprlist WHEN expr THEN expr", + /* 230 */ "case_exprlist ::= WHEN expr THEN expr", + /* 231 */ "case_else ::= ELSE expr", + /* 232 */ "case_else ::=", + /* 233 */ "case_operand ::=", + /* 234 */ "exprlist ::=", + /* 235 */ "nexprlist ::= nexprlist COMMA expr", + /* 236 */ "nexprlist ::= expr", + /* 237 */ "paren_exprlist ::=", + /* 238 */ "paren_exprlist ::= LP exprlist RP", + /* 239 */ "cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt", + /* 240 */ "uniqueflag ::= UNIQUE", + /* 241 */ "uniqueflag ::=", + /* 242 */ "eidlist_opt ::=", + /* 243 */ "eidlist_opt ::= LP eidlist RP", + /* 244 */ "eidlist ::= eidlist COMMA nm collate sortorder", + /* 245 */ "eidlist ::= nm collate sortorder", + /* 246 */ "collate ::=", + /* 247 */ "collate ::= COLLATE ID|STRING", + /* 248 */ "cmd ::= DROP INDEX ifexists fullname", + /* 249 */ "cmd ::= VACUUM vinto", + /* 250 */ "cmd ::= VACUUM nm vinto", + /* 251 */ "vinto ::= INTO expr", + /* 252 */ "vinto ::=", + /* 253 */ "cmd ::= PRAGMA nm dbnm", + /* 254 */ "cmd ::= PRAGMA nm dbnm EQ nmnum", + /* 255 */ "cmd ::= PRAGMA nm dbnm LP nmnum RP", + /* 256 */ "cmd ::= PRAGMA nm dbnm EQ minus_num", + /* 257 */ "cmd ::= PRAGMA nm dbnm LP minus_num RP", + /* 258 */ "plus_num ::= PLUS INTEGER|FLOAT", + /* 259 */ "minus_num ::= MINUS INTEGER|FLOAT", + /* 260 */ "cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END", + /* 261 */ "trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause", + /* 262 */ "trigger_time ::= BEFORE|AFTER", + /* 263 */ "trigger_time ::= INSTEAD OF", + /* 264 */ "trigger_time ::=", + /* 265 */ "trigger_event ::= DELETE|INSERT", + /* 266 */ "trigger_event ::= UPDATE", + /* 267 */ "trigger_event ::= UPDATE OF idlist", + /* 268 */ "when_clause ::=", + /* 269 */ "when_clause ::= WHEN expr", + /* 270 */ "trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI", + /* 271 */ "trigger_cmd_list ::= trigger_cmd SEMI", + /* 272 */ "tridxby ::= INDEXED BY nm", + /* 273 */ "tridxby ::= NOT INDEXED", + /* 274 */ "trigger_cmd ::= UPDATE orconf xfullname tridxby SET setlist from where_opt scanpt", + /* 275 */ "trigger_cmd ::= scanpt insert_cmd INTO xfullname idlist_opt select upsert scanpt", + /* 276 */ "trigger_cmd ::= DELETE FROM xfullname tridxby where_opt scanpt", + /* 277 */ "trigger_cmd ::= scanpt select scanpt", + /* 278 */ "expr ::= RAISE LP IGNORE RP", + /* 279 */ "expr ::= RAISE LP raisetype COMMA expr RP", + /* 280 */ "raisetype ::= ROLLBACK", + /* 281 */ "raisetype ::= ABORT", + /* 282 */ "raisetype ::= FAIL", + /* 283 */ "cmd ::= DROP TRIGGER ifexists fullname", + /* 284 */ "cmd ::= ATTACH database_kw_opt expr AS expr key_opt", + /* 285 */ "cmd ::= DETACH database_kw_opt expr", + /* 286 */ "key_opt ::=", + /* 287 */ "key_opt ::= KEY expr", + /* 288 */ "cmd ::= REINDEX", + /* 289 */ "cmd ::= REINDEX nm dbnm", + /* 290 */ "cmd ::= ANALYZE", + /* 291 */ "cmd ::= ANALYZE nm dbnm", + /* 292 */ "cmd ::= ALTER TABLE fullname RENAME TO nm", + /* 293 */ "cmd ::= alter_add carglist", + /* 294 */ "alter_add ::= ALTER TABLE fullname ADD kwcolumn_opt nm typetoken", + /* 295 */ "cmd ::= ALTER TABLE fullname DROP kwcolumn_opt nm", + /* 296 */ "cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm", + /* 297 */ "cmd ::= ALTER TABLE fullname DROP CONSTRAINT nm", + /* 298 */ "cmd ::= ALTER TABLE fullname ALTER kwcolumn_opt nm DROP NOT NULL", + /* 299 */ "cmd ::= ALTER TABLE fullname ALTER kwcolumn_opt nm SET NOT NULL onconf", + /* 300 */ "cmd ::= ALTER TABLE fullname ADD CONSTRAINT nm CHECK LP expr RP onconf", + /* 301 */ "cmd ::= ALTER TABLE fullname ADD CHECK LP expr RP onconf", + /* 302 */ "cmd ::= create_vtab", + /* 303 */ "cmd ::= create_vtab LP vtabarglist RP", + /* 304 */ "create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm", + /* 305 */ "vtabarg ::=", + /* 306 */ "vtabargtoken ::= ANY", + /* 307 */ "vtabargtoken ::= lp anylist RP", + /* 308 */ "lp ::= LP", + /* 309 */ "with ::= WITH wqlist", + /* 310 */ "with ::= WITH RECURSIVE wqlist", + /* 311 */ "wqas ::= AS", + /* 312 */ "wqas ::= AS MATERIALIZED", + /* 313 */ "wqas ::= AS NOT MATERIALIZED", + /* 314 */ "wqitem ::= withnm eidlist_opt wqas LP select RP", + /* 315 */ "withnm ::= nm", + /* 316 */ "wqlist ::= wqitem", + /* 317 */ "wqlist ::= wqlist COMMA wqitem", + /* 318 */ "windowdefn_list ::= windowdefn_list COMMA windowdefn", + /* 319 */ "windowdefn ::= nm AS LP window RP", + /* 320 */ "window ::= PARTITION BY nexprlist orderby_opt frame_opt", + /* 321 */ "window ::= nm PARTITION BY nexprlist orderby_opt frame_opt", + /* 322 */ "window ::= ORDER BY sortlist frame_opt", + /* 323 */ "window ::= nm ORDER BY sortlist frame_opt", + /* 324 */ "window ::= nm frame_opt", + /* 325 */ "frame_opt ::=", + /* 326 */ "frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt", + /* 327 */ "frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt", + /* 328 */ "range_or_rows ::= RANGE|ROWS|GROUPS", + /* 329 */ "frame_bound_s ::= frame_bound", + /* 330 */ "frame_bound_s ::= UNBOUNDED PRECEDING", + /* 331 */ "frame_bound_e ::= frame_bound", + /* 332 */ "frame_bound_e ::= UNBOUNDED FOLLOWING", + /* 333 */ "frame_bound ::= expr PRECEDING|FOLLOWING", + /* 334 */ "frame_bound ::= CURRENT ROW", + /* 335 */ "frame_exclude_opt ::=", + /* 336 */ "frame_exclude_opt ::= EXCLUDE frame_exclude", + /* 337 */ "frame_exclude ::= NO OTHERS", + /* 338 */ "frame_exclude ::= CURRENT ROW", + /* 339 */ "frame_exclude ::= GROUP|TIES", + /* 340 */ "window_clause ::= WINDOW windowdefn_list", + /* 341 */ "filter_over ::= filter_clause over_clause", + /* 342 */ "filter_over ::= over_clause", + /* 343 */ "filter_over ::= filter_clause", + /* 344 */ "over_clause ::= OVER LP window RP", + /* 345 */ "over_clause ::= OVER nm", + /* 346 */ "filter_clause ::= FILTER LP WHERE expr RP", + /* 347 */ "term ::= QNUMBER", + /* 348 */ "input ::= cmdlist", + /* 349 */ "cmdlist ::= cmdlist ecmd", + /* 350 */ "cmdlist ::= ecmd", + /* 351 */ "ecmd ::= SEMI", + /* 352 */ "ecmd ::= cmdx SEMI", + /* 353 */ "ecmd ::= explain cmdx SEMI", + /* 354 */ "trans_opt ::=", + /* 355 */ "trans_opt ::= TRANSACTION", + /* 356 */ "trans_opt ::= TRANSACTION nm", + /* 357 */ "savepoint_opt ::= SAVEPOINT", + /* 358 */ "savepoint_opt ::=", + /* 359 */ "cmd ::= create_table create_table_args", + /* 360 */ "table_option_set ::= table_option", + /* 361 */ "columnlist ::= columnlist COMMA columnname carglist", + /* 362 */ "columnlist ::= columnname carglist", + /* 363 */ "nm ::= ID|INDEXED|JOIN_KW", + /* 364 */ "nm ::= STRING", + /* 365 */ "typetoken ::= typename", + /* 366 */ "typename ::= ID|STRING", + /* 367 */ "signed ::= plus_num", + /* 368 */ "signed ::= minus_num", + /* 369 */ "carglist ::= carglist ccons", + /* 370 */ "carglist ::=", + /* 371 */ "ccons ::= NULL onconf", + /* 372 */ "ccons ::= GENERATED ALWAYS AS generated", + /* 373 */ "ccons ::= AS generated", + /* 374 */ "conslist_opt ::= COMMA conslist", + /* 375 */ "conslist ::= conslist tconscomma tcons", + /* 376 */ "conslist ::= tcons", + /* 377 */ "tconscomma ::=", + /* 378 */ "defer_subclause_opt ::= defer_subclause", + /* 379 */ "resolvetype ::= raisetype", + /* 380 */ "selectnowith ::= oneselect", + /* 381 */ "oneselect ::= values", + /* 382 */ "sclp ::= selcollist COMMA", + /* 383 */ "as ::= ID|STRING", + /* 384 */ "indexed_opt ::= indexed_by", + /* 385 */ "returning ::=", + /* 386 */ "expr ::= term", + /* 387 */ "likeop ::= LIKE_KW|MATCH", + /* 388 */ "case_operand ::= expr", + /* 389 */ "exprlist ::= nexprlist", + /* 390 */ "nmnum ::= plus_num", + /* 391 */ "nmnum ::= nm", + /* 392 */ "nmnum ::= ON", + /* 393 */ "nmnum ::= DELETE", + /* 394 */ "nmnum ::= DEFAULT", + /* 395 */ "plus_num ::= INTEGER|FLOAT", + /* 396 */ "foreach_clause ::=", + /* 397 */ "foreach_clause ::= FOR EACH ROW", + /* 398 */ "tridxby ::=", + /* 399 */ "database_kw_opt ::= DATABASE", + /* 400 */ "database_kw_opt ::=", + /* 401 */ "kwcolumn_opt ::=", + /* 402 */ "kwcolumn_opt ::= COLUMNKW", + /* 403 */ "vtabarglist ::= vtabarg", + /* 404 */ "vtabarglist ::= vtabarglist COMMA vtabarg", + /* 405 */ "vtabarg ::= vtabarg vtabargtoken", + /* 406 */ "anylist ::=", + /* 407 */ "anylist ::= anylist LP anylist RP", + /* 408 */ "anylist ::= anylist ANY", + /* 409 */ "with ::=", + /* 410 */ "windowdefn_list ::= windowdefn", + /* 411 */ "window ::= frame_opt", }; #endif /* NDEBUG */ -#if YYSTACKDEPTH<=0 +#if YYGROWABLESTACK /* ** Try to increase the size of the parser stack. Return the number ** of errors. Return 0 on success. */ static int yyGrowStack(yyParser *p){ + int oldSize = 1 + (int)(p->yystackEnd - p->yystack); int newSize; int idx; yyStackEntry *pNew; +#ifdef YYSIZELIMIT + int nLimit = YYSIZELIMIT(sqlite3ParserCTX(p)); +#endif - newSize = p->yystksz*2 + 100; - idx = p->yytos ? (int)(p->yytos - p->yystack) : 0; - if( p->yystack==&p->yystk0 ){ - pNew = malloc(newSize*sizeof(pNew[0])); - if( pNew ) pNew[0] = p->yystk0; + newSize = oldSize*2 + 100; +#ifdef YYSIZELIMIT + if( newSize>nLimit ){ + newSize = nLimit; + if( newSize<=oldSize ) return 1; + } +#endif + idx = (int)(p->yytos - p->yystack); + if( p->yystack==p->yystk0 ){ + pNew = YYREALLOC(0, newSize*sizeof(pNew[0]), sqlite3ParserCTX(p)); + if( pNew==0 ) return 1; + memcpy(pNew, p->yystack, oldSize*sizeof(pNew[0])); }else{ - pNew = realloc(p->yystack, newSize*sizeof(pNew[0])); + pNew = YYREALLOC(p->yystack, newSize*sizeof(pNew[0]), sqlite3ParserCTX(p)); + if( pNew==0 ) return 1; } - if( pNew ){ - p->yystack = pNew; - p->yytos = &p->yystack[idx]; + p->yystack = pNew; + p->yytos = &p->yystack[idx]; #ifndef NDEBUG - if( yyTraceFILE ){ - fprintf(yyTraceFILE,"%sStack grows from %d to %d entries.\n", - yyTracePrompt, p->yystksz, newSize); - } -#endif - p->yystksz = newSize; + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sStack grows from %d to %d entries.\n", + yyTracePrompt, oldSize, newSize); } - return pNew==0; +#endif + p->yystackEnd = &p->yystack[newSize-1]; + return 0; } +#endif /* YYGROWABLESTACK */ + +#if !YYGROWABLESTACK +/* For builds that do no have a growable stack, yyGrowStack always +** returns an error. +*/ +# define yyGrowStack(X) 1 #endif /* Datatype of the argument to the memory allocated passed as the @@ -150014,28 +182327,18 @@ SQLITE_PRIVATE void sqlite3ParserInit(void *yypRawParser sqlite3ParserCTX_PDECL) #ifdef YYTRACKMAXSTACKDEPTH yypParser->yyhwm = 0; #endif -#if YYSTACKDEPTH<=0 - yypParser->yytos = NULL; - yypParser->yystack = NULL; - yypParser->yystksz = 0; - if( yyGrowStack(yypParser) ){ - yypParser->yystack = &yypParser->yystk0; - yypParser->yystksz = 1; - } -#endif + yypParser->yystack = yypParser->yystk0; + yypParser->yystackEnd = &yypParser->yystack[YYSTACKDEPTH-1]; #ifndef YYNOERRORRECOVERY yypParser->yyerrcnt = -1; #endif yypParser->yytos = yypParser->yystack; yypParser->yystack[0].stateno = 0; yypParser->yystack[0].major = 0; -#if YYSTACKDEPTH>0 - yypParser->yystackEnd = &yypParser->yystack[YYSTACKDEPTH-1]; -#endif } #ifndef sqlite3Parser_ENGINEALWAYSONSTACK -/* +/* ** This function allocates a new parser. ** The only argument is a pointer to a function which works like ** malloc. @@ -150062,7 +182365,7 @@ SQLITE_PRIVATE void *sqlite3ParserAlloc(void *(*mallocProc)(YYMALLOCARGTYPE) sql /* The following function deletes the "minor type" or semantic value ** associated with a symbol. The symbol can be either a terminal ** or nonterminal. "yymajor" is the symbol code, and "yypminor" is -** a pointer to the value to be deleted. The code used to do the +** a pointer to the value to be deleted. The code used to do the ** deletions is derived from the %destructor and/or %token_destructor ** directives of the input grammar. */ @@ -150077,7 +182380,7 @@ static void yy_destructor( /* Here is inserted the actions which take place when a ** terminal or non-terminal is destroyed. This can happen ** when the symbol is popped from the stack during a - ** reduce or during error processing or when a parser is + ** reduce or during error processing or when a parser is ** being destroyed before it is finished parsing. ** ** Note: during a reduce, the only symbols destroyed are those @@ -150085,97 +182388,98 @@ static void yy_destructor( ** inside the C code. */ /********* Begin destructor definitions ***************************************/ - case 195: /* select */ - case 227: /* selectnowith */ - case 228: /* oneselect */ - case 240: /* values */ + case 206: /* select */ + case 241: /* selectnowith */ + case 242: /* oneselect */ + case 254: /* values */ + case 256: /* mvalues */ { -sqlite3SelectDelete(pParse->db, (yypminor->yy457)); -} - break; - case 205: /* term */ - case 206: /* expr */ - case 234: /* where_opt */ - case 236: /* having_opt */ - case 248: /* on_opt */ - case 263: /* case_operand */ - case 265: /* case_else */ - case 268: /* vinto */ - case 275: /* when_clause */ - case 280: /* key_opt */ - case 294: /* filter_opt */ +sqlite3SelectDelete(pParse->db, (yypminor->yy555)); +} + break; + case 218: /* term */ + case 219: /* expr */ + case 248: /* where_opt */ + case 250: /* having_opt */ + case 270: /* where_opt_ret */ + case 281: /* case_operand */ + case 283: /* case_else */ + case 286: /* vinto */ + case 293: /* when_clause */ + case 297: /* key_opt */ + case 314: /* filter_clause */ { -sqlite3ExprDelete(pParse->db, (yypminor->yy524)); -} - break; - case 210: /* eidlist_opt */ - case 219: /* sortlist */ - case 220: /* eidlist */ - case 232: /* selcollist */ - case 235: /* groupby_opt */ - case 237: /* orderby_opt */ - case 241: /* nexprlist */ - case 242: /* sclp */ - case 250: /* exprlist */ - case 254: /* setlist */ - case 262: /* paren_exprlist */ - case 264: /* case_exprlist */ - case 293: /* part_opt */ +sqlite3ExprDelete(pParse->db, (yypminor->yy454)); +} + break; + case 223: /* eidlist_opt */ + case 233: /* sortlist */ + case 234: /* eidlist */ + case 246: /* selcollist */ + case 249: /* groupby_opt */ + case 251: /* orderby_opt */ + case 255: /* nexprlist */ + case 257: /* sclp */ + case 264: /* exprlist */ + case 271: /* setlist */ + case 280: /* paren_exprlist */ + case 282: /* case_exprlist */ + case 313: /* part_opt */ { -sqlite3ExprListDelete(pParse->db, (yypminor->yy434)); +sqlite3ExprListDelete(pParse->db, (yypminor->yy14)); } break; - case 226: /* fullname */ - case 233: /* from */ - case 244: /* seltablist */ - case 245: /* stl_prefix */ - case 251: /* xfullname */ + case 240: /* fullname */ + case 247: /* from */ + case 259: /* seltablist */ + case 260: /* stl_prefix */ + case 265: /* xfullname */ { -sqlite3SrcListDelete(pParse->db, (yypminor->yy483)); +sqlite3SrcListDelete(pParse->db, (yypminor->yy203)); } break; - case 229: /* wqlist */ + case 243: /* wqlist */ { sqlite3WithDelete(pParse->db, (yypminor->yy59)); } break; - case 239: /* window_clause */ - case 289: /* windowdefn_list */ + case 253: /* window_clause */ + case 309: /* windowdefn_list */ { -sqlite3WindowListDelete(pParse->db, (yypminor->yy295)); +sqlite3WindowListDelete(pParse->db, (yypminor->yy211)); } break; - case 249: /* using_opt */ - case 252: /* idlist */ - case 256: /* idlist_opt */ + case 266: /* idlist */ + case 273: /* idlist_opt */ { -sqlite3IdListDelete(pParse->db, (yypminor->yy62)); +sqlite3IdListDelete(pParse->db, (yypminor->yy132)); } break; - case 258: /* over_clause */ - case 290: /* windowdefn */ - case 291: /* window */ - case 292: /* frame_opt */ + case 276: /* filter_over */ + case 310: /* windowdefn */ + case 311: /* window */ + case 312: /* frame_opt */ + case 315: /* over_clause */ { -sqlite3WindowDelete(pParse->db, (yypminor->yy295)); +sqlite3WindowDelete(pParse->db, (yypminor->yy211)); } break; - case 271: /* trigger_cmd_list */ - case 276: /* trigger_cmd */ + case 289: /* trigger_cmd_list */ + case 294: /* trigger_cmd */ { -sqlite3DeleteTriggerStep(pParse->db, (yypminor->yy455)); +sqlite3DeleteTriggerStep(pParse->db, (yypminor->yy427)); } break; - case 273: /* trigger_event */ + case 291: /* trigger_event */ { -sqlite3IdListDelete(pParse->db, (yypminor->yy90).b); +sqlite3IdListDelete(pParse->db, (yypminor->yy286).b); } break; - case 296: /* frame_bound */ - case 297: /* frame_bound_s */ - case 298: /* frame_bound_e */ + case 317: /* frame_bound */ + case 318: /* frame_bound_s */ + case 319: /* frame_bound_e */ { -sqlite3ExprDelete(pParse->db, (yypminor->yy201).pExpr); +sqlite3ExprDelete(pParse->db, (yypminor->yy509).pExpr); } break; /********* End destructor definitions *****************************************/ @@ -150209,14 +182513,33 @@ static void yy_pop_parser_stack(yyParser *pParser){ */ SQLITE_PRIVATE void sqlite3ParserFinalize(void *p){ yyParser *pParser = (yyParser*)p; - while( pParser->yytos>pParser->yystack ) yy_pop_parser_stack(pParser); -#if YYSTACKDEPTH<=0 - if( pParser->yystack!=&pParser->yystk0 ) free(pParser->yystack); + + /* In-lined version of calling yy_pop_parser_stack() for each + ** element left in the stack */ + yyStackEntry *yytos = pParser->yytos; + while( yytos>pParser->yystack ){ +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sPopping %s\n", + yyTracePrompt, + yyTokenName[yytos->major]); + } +#endif + if( yytos->major>=YY_MIN_DSTRCTR ){ + yy_destructor(pParser, yytos->major, &yytos->minor); + } + yytos--; + } + +#if YYGROWABLESTACK + if( pParser->yystack!=pParser->yystk0 ){ + YYFREE(pParser->yystack, sqlite3ParserCTX(pParser)); + } #endif } #ifndef sqlite3Parser_ENGINEALWAYSONSTACK -/* +/* ** Deallocate and destroy a parser. Destructors are called for ** all stack elements before shutting the parser down. ** @@ -150301,15 +182624,18 @@ static YYACTIONTYPE yy_find_shift_action( do{ i = yy_shift_ofst[stateno]; assert( i>=0 ); - /* assert( i+YYNTOKEN<=(int)YY_NLOOKAHEAD ); */ + assert( i<=YY_ACTTAB_COUNT ); + assert( i+YYNTOKEN<=(int)YY_NLOOKAHEAD ); assert( iLookAhead!=YYNOCODE ); assert( iLookAhead < YYNTOKEN ); i += iLookAhead; - if( i>=YY_NLOOKAHEAD || yy_lookahead[i]!=iLookAhead ){ + assert( i<(int)YY_NLOOKAHEAD ); + if( yy_lookahead[i]!=iLookAhead ){ #ifdef YYFALLBACK YYCODETYPE iFallback; /* Fallback token */ - if( iLookAhead %s\n", @@ -150324,16 +182650,8 @@ static YYACTIONTYPE yy_find_shift_action( #ifdef YYWILDCARD { int j = i - iLookAhead + YYWILDCARD; - if( -#if YY_SHIFT_MIN+YYWILDCARD<0 - j>=0 && -#endif -#if YY_SHIFT_MAX+YYWILDCARD>=YY_ACTTAB_COUNT - j0 - ){ + assert( j<(int)(sizeof(yy_lookahead)/sizeof(yy_lookahead[0])) ); + if( yy_lookahead[j]==YYWILDCARD && iLookAhead>0 ){ #ifndef NDEBUG if( yyTraceFILE ){ fprintf(yyTraceFILE, "%sWILDCARD %s => %s\n", @@ -150347,6 +182665,7 @@ static YYACTIONTYPE yy_find_shift_action( #endif /* YYWILDCARD */ return yy_default[stateno]; }else{ + assert( i>=0 && i<(int)(sizeof(yy_action)/sizeof(yy_action[0])) ); return yy_action[i]; } }while(1); @@ -150398,7 +182717,7 @@ static void yyStackOverflow(yyParser *yypParser){ ** stack every overflows */ /******** Begin %stack_overflow code ******************************************/ - sqlite3ErrorMsg(pParse, "parser stack overflow"); + if( pParse->nErr==0 ) sqlite3ErrorMsg(pParse, "Recursion limit"); /******** End %stack_overflow code ********************************************/ sqlite3ParserARG_STORE /* Suppress warning about unused %extra_argument var */ sqlite3ParserCTX_STORE @@ -150442,25 +182761,19 @@ static void yy_shift( assert( yypParser->yyhwm == (int)(yypParser->yytos - yypParser->yystack) ); } #endif -#if YYSTACKDEPTH>0 - if( yypParser->yytos>yypParser->yystackEnd ){ - yypParser->yytos--; - yyStackOverflow(yypParser); - return; - } -#else - if( yypParser->yytos>=&yypParser->yystack[yypParser->yystksz] ){ + yytos = yypParser->yytos; + if( yytos>yypParser->yystackEnd ){ if( yyGrowStack(yypParser) ){ yypParser->yytos--; yyStackOverflow(yypParser); return; } + yytos = yypParser->yytos; + assert( yytos <= yypParser->yystackEnd ); } -#endif if( yyNewState > YY_MAX_SHIFT ){ yyNewState += YY_MIN_REDUCE - YY_MIN_SHIFTREDUCE; } - yytos = yypParser->yytos; yytos->stateno = yyNewState; yytos->major = yyMajor; yytos->minor.yy0 = yyMinor; @@ -150470,381 +182783,418 @@ static void yy_shift( /* For rule J, yyRuleInfoLhs[J] contains the symbol on the left-hand side ** of that rule */ static const YYCODETYPE yyRuleInfoLhs[] = { - 180, /* (0) explain ::= EXPLAIN */ - 180, /* (1) explain ::= EXPLAIN QUERY PLAN */ - 179, /* (2) cmdx ::= cmd */ - 181, /* (3) cmd ::= BEGIN transtype trans_opt */ - 182, /* (4) transtype ::= */ - 182, /* (5) transtype ::= DEFERRED */ - 182, /* (6) transtype ::= IMMEDIATE */ - 182, /* (7) transtype ::= EXCLUSIVE */ - 181, /* (8) cmd ::= COMMIT|END trans_opt */ - 181, /* (9) cmd ::= ROLLBACK trans_opt */ - 181, /* (10) cmd ::= SAVEPOINT nm */ - 181, /* (11) cmd ::= RELEASE savepoint_opt nm */ - 181, /* (12) cmd ::= ROLLBACK trans_opt TO savepoint_opt nm */ - 186, /* (13) create_table ::= createkw temp TABLE ifnotexists nm dbnm */ - 188, /* (14) createkw ::= CREATE */ - 190, /* (15) ifnotexists ::= */ - 190, /* (16) ifnotexists ::= IF NOT EXISTS */ - 189, /* (17) temp ::= TEMP */ - 189, /* (18) temp ::= */ - 187, /* (19) create_table_args ::= LP columnlist conslist_opt RP table_options */ - 187, /* (20) create_table_args ::= AS select */ - 194, /* (21) table_options ::= */ - 194, /* (22) table_options ::= WITHOUT nm */ - 196, /* (23) columnname ::= nm typetoken */ - 198, /* (24) typetoken ::= */ - 198, /* (25) typetoken ::= typename LP signed RP */ - 198, /* (26) typetoken ::= typename LP signed COMMA signed RP */ - 199, /* (27) typename ::= typename ID|STRING */ - 203, /* (28) scanpt ::= */ - 204, /* (29) ccons ::= CONSTRAINT nm */ - 204, /* (30) ccons ::= DEFAULT scanpt term scanpt */ - 204, /* (31) ccons ::= DEFAULT LP expr RP */ - 204, /* (32) ccons ::= DEFAULT PLUS term scanpt */ - 204, /* (33) ccons ::= DEFAULT MINUS term scanpt */ - 204, /* (34) ccons ::= DEFAULT scanpt ID|INDEXED */ - 204, /* (35) ccons ::= NOT NULL onconf */ - 204, /* (36) ccons ::= PRIMARY KEY sortorder onconf autoinc */ - 204, /* (37) ccons ::= UNIQUE onconf */ - 204, /* (38) ccons ::= CHECK LP expr RP */ - 204, /* (39) ccons ::= REFERENCES nm eidlist_opt refargs */ - 204, /* (40) ccons ::= defer_subclause */ - 204, /* (41) ccons ::= COLLATE ID|STRING */ - 209, /* (42) autoinc ::= */ - 209, /* (43) autoinc ::= AUTOINCR */ - 211, /* (44) refargs ::= */ - 211, /* (45) refargs ::= refargs refarg */ - 213, /* (46) refarg ::= MATCH nm */ - 213, /* (47) refarg ::= ON INSERT refact */ - 213, /* (48) refarg ::= ON DELETE refact */ - 213, /* (49) refarg ::= ON UPDATE refact */ - 214, /* (50) refact ::= SET NULL */ - 214, /* (51) refact ::= SET DEFAULT */ - 214, /* (52) refact ::= CASCADE */ - 214, /* (53) refact ::= RESTRICT */ - 214, /* (54) refact ::= NO ACTION */ - 212, /* (55) defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt */ - 212, /* (56) defer_subclause ::= DEFERRABLE init_deferred_pred_opt */ - 215, /* (57) init_deferred_pred_opt ::= */ - 215, /* (58) init_deferred_pred_opt ::= INITIALLY DEFERRED */ - 215, /* (59) init_deferred_pred_opt ::= INITIALLY IMMEDIATE */ - 193, /* (60) conslist_opt ::= */ - 217, /* (61) tconscomma ::= COMMA */ - 218, /* (62) tcons ::= CONSTRAINT nm */ - 218, /* (63) tcons ::= PRIMARY KEY LP sortlist autoinc RP onconf */ - 218, /* (64) tcons ::= UNIQUE LP sortlist RP onconf */ - 218, /* (65) tcons ::= CHECK LP expr RP onconf */ - 218, /* (66) tcons ::= FOREIGN KEY LP eidlist RP REFERENCES nm eidlist_opt refargs defer_subclause_opt */ - 221, /* (67) defer_subclause_opt ::= */ - 207, /* (68) onconf ::= */ - 207, /* (69) onconf ::= ON CONFLICT resolvetype */ - 222, /* (70) orconf ::= */ - 222, /* (71) orconf ::= OR resolvetype */ - 223, /* (72) resolvetype ::= IGNORE */ - 223, /* (73) resolvetype ::= REPLACE */ - 181, /* (74) cmd ::= DROP TABLE ifexists fullname */ - 225, /* (75) ifexists ::= IF EXISTS */ - 225, /* (76) ifexists ::= */ - 181, /* (77) cmd ::= createkw temp VIEW ifnotexists nm dbnm eidlist_opt AS select */ - 181, /* (78) cmd ::= DROP VIEW ifexists fullname */ - 181, /* (79) cmd ::= select */ - 195, /* (80) select ::= WITH wqlist selectnowith */ - 195, /* (81) select ::= WITH RECURSIVE wqlist selectnowith */ - 195, /* (82) select ::= selectnowith */ - 227, /* (83) selectnowith ::= selectnowith multiselect_op oneselect */ - 230, /* (84) multiselect_op ::= UNION */ - 230, /* (85) multiselect_op ::= UNION ALL */ - 230, /* (86) multiselect_op ::= EXCEPT|INTERSECT */ - 228, /* (87) oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt */ - 228, /* (88) oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt window_clause orderby_opt limit_opt */ - 240, /* (89) values ::= VALUES LP nexprlist RP */ - 240, /* (90) values ::= values COMMA LP nexprlist RP */ - 231, /* (91) distinct ::= DISTINCT */ - 231, /* (92) distinct ::= ALL */ - 231, /* (93) distinct ::= */ - 242, /* (94) sclp ::= */ - 232, /* (95) selcollist ::= sclp scanpt expr scanpt as */ - 232, /* (96) selcollist ::= sclp scanpt STAR */ - 232, /* (97) selcollist ::= sclp scanpt nm DOT STAR */ - 243, /* (98) as ::= AS nm */ - 243, /* (99) as ::= */ - 233, /* (100) from ::= */ - 233, /* (101) from ::= FROM seltablist */ - 245, /* (102) stl_prefix ::= seltablist joinop */ - 245, /* (103) stl_prefix ::= */ - 244, /* (104) seltablist ::= stl_prefix nm dbnm as indexed_opt on_opt using_opt */ - 244, /* (105) seltablist ::= stl_prefix nm dbnm LP exprlist RP as on_opt using_opt */ - 244, /* (106) seltablist ::= stl_prefix LP select RP as on_opt using_opt */ - 244, /* (107) seltablist ::= stl_prefix LP seltablist RP as on_opt using_opt */ - 191, /* (108) dbnm ::= */ - 191, /* (109) dbnm ::= DOT nm */ - 226, /* (110) fullname ::= nm */ - 226, /* (111) fullname ::= nm DOT nm */ - 251, /* (112) xfullname ::= nm */ - 251, /* (113) xfullname ::= nm DOT nm */ - 251, /* (114) xfullname ::= nm DOT nm AS nm */ - 251, /* (115) xfullname ::= nm AS nm */ - 246, /* (116) joinop ::= COMMA|JOIN */ - 246, /* (117) joinop ::= JOIN_KW JOIN */ - 246, /* (118) joinop ::= JOIN_KW nm JOIN */ - 246, /* (119) joinop ::= JOIN_KW nm nm JOIN */ - 248, /* (120) on_opt ::= ON expr */ - 248, /* (121) on_opt ::= */ - 247, /* (122) indexed_opt ::= */ - 247, /* (123) indexed_opt ::= INDEXED BY nm */ - 247, /* (124) indexed_opt ::= NOT INDEXED */ - 249, /* (125) using_opt ::= USING LP idlist RP */ - 249, /* (126) using_opt ::= */ - 237, /* (127) orderby_opt ::= */ - 237, /* (128) orderby_opt ::= ORDER BY sortlist */ - 219, /* (129) sortlist ::= sortlist COMMA expr sortorder */ - 219, /* (130) sortlist ::= expr sortorder */ - 208, /* (131) sortorder ::= ASC */ - 208, /* (132) sortorder ::= DESC */ - 208, /* (133) sortorder ::= */ - 235, /* (134) groupby_opt ::= */ - 235, /* (135) groupby_opt ::= GROUP BY nexprlist */ - 236, /* (136) having_opt ::= */ - 236, /* (137) having_opt ::= HAVING expr */ - 238, /* (138) limit_opt ::= */ - 238, /* (139) limit_opt ::= LIMIT expr */ - 238, /* (140) limit_opt ::= LIMIT expr OFFSET expr */ - 238, /* (141) limit_opt ::= LIMIT expr COMMA expr */ - 181, /* (142) cmd ::= with DELETE FROM xfullname indexed_opt where_opt */ - 234, /* (143) where_opt ::= */ - 234, /* (144) where_opt ::= WHERE expr */ - 181, /* (145) cmd ::= with UPDATE orconf xfullname indexed_opt SET setlist where_opt */ - 254, /* (146) setlist ::= setlist COMMA nm EQ expr */ - 254, /* (147) setlist ::= setlist COMMA LP idlist RP EQ expr */ - 254, /* (148) setlist ::= nm EQ expr */ - 254, /* (149) setlist ::= LP idlist RP EQ expr */ - 181, /* (150) cmd ::= with insert_cmd INTO xfullname idlist_opt select upsert */ - 181, /* (151) cmd ::= with insert_cmd INTO xfullname idlist_opt DEFAULT VALUES */ - 257, /* (152) upsert ::= */ - 257, /* (153) upsert ::= ON CONFLICT LP sortlist RP where_opt DO UPDATE SET setlist where_opt */ - 257, /* (154) upsert ::= ON CONFLICT LP sortlist RP where_opt DO NOTHING */ - 257, /* (155) upsert ::= ON CONFLICT DO NOTHING */ - 255, /* (156) insert_cmd ::= INSERT orconf */ - 255, /* (157) insert_cmd ::= REPLACE */ - 256, /* (158) idlist_opt ::= */ - 256, /* (159) idlist_opt ::= LP idlist RP */ - 252, /* (160) idlist ::= idlist COMMA nm */ - 252, /* (161) idlist ::= nm */ - 206, /* (162) expr ::= LP expr RP */ - 206, /* (163) expr ::= ID|INDEXED */ - 206, /* (164) expr ::= JOIN_KW */ - 206, /* (165) expr ::= nm DOT nm */ - 206, /* (166) expr ::= nm DOT nm DOT nm */ - 205, /* (167) term ::= NULL|FLOAT|BLOB */ - 205, /* (168) term ::= STRING */ - 205, /* (169) term ::= INTEGER */ - 206, /* (170) expr ::= VARIABLE */ - 206, /* (171) expr ::= expr COLLATE ID|STRING */ - 206, /* (172) expr ::= CAST LP expr AS typetoken RP */ - 206, /* (173) expr ::= ID|INDEXED LP distinct exprlist RP */ - 206, /* (174) expr ::= ID|INDEXED LP STAR RP */ - 206, /* (175) expr ::= ID|INDEXED LP distinct exprlist RP over_clause */ - 206, /* (176) expr ::= ID|INDEXED LP STAR RP over_clause */ - 205, /* (177) term ::= CTIME_KW */ - 206, /* (178) expr ::= LP nexprlist COMMA expr RP */ - 206, /* (179) expr ::= expr AND expr */ - 206, /* (180) expr ::= expr OR expr */ - 206, /* (181) expr ::= expr LT|GT|GE|LE expr */ - 206, /* (182) expr ::= expr EQ|NE expr */ - 206, /* (183) expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr */ - 206, /* (184) expr ::= expr PLUS|MINUS expr */ - 206, /* (185) expr ::= expr STAR|SLASH|REM expr */ - 206, /* (186) expr ::= expr CONCAT expr */ - 259, /* (187) likeop ::= NOT LIKE_KW|MATCH */ - 206, /* (188) expr ::= expr likeop expr */ - 206, /* (189) expr ::= expr likeop expr ESCAPE expr */ - 206, /* (190) expr ::= expr ISNULL|NOTNULL */ - 206, /* (191) expr ::= expr NOT NULL */ - 206, /* (192) expr ::= expr IS expr */ - 206, /* (193) expr ::= expr IS NOT expr */ - 206, /* (194) expr ::= NOT expr */ - 206, /* (195) expr ::= BITNOT expr */ - 206, /* (196) expr ::= PLUS|MINUS expr */ - 260, /* (197) between_op ::= BETWEEN */ - 260, /* (198) between_op ::= NOT BETWEEN */ - 206, /* (199) expr ::= expr between_op expr AND expr */ - 261, /* (200) in_op ::= IN */ - 261, /* (201) in_op ::= NOT IN */ - 206, /* (202) expr ::= expr in_op LP exprlist RP */ - 206, /* (203) expr ::= LP select RP */ - 206, /* (204) expr ::= expr in_op LP select RP */ - 206, /* (205) expr ::= expr in_op nm dbnm paren_exprlist */ - 206, /* (206) expr ::= EXISTS LP select RP */ - 206, /* (207) expr ::= CASE case_operand case_exprlist case_else END */ - 264, /* (208) case_exprlist ::= case_exprlist WHEN expr THEN expr */ - 264, /* (209) case_exprlist ::= WHEN expr THEN expr */ - 265, /* (210) case_else ::= ELSE expr */ - 265, /* (211) case_else ::= */ - 263, /* (212) case_operand ::= expr */ - 263, /* (213) case_operand ::= */ - 250, /* (214) exprlist ::= */ - 241, /* (215) nexprlist ::= nexprlist COMMA expr */ - 241, /* (216) nexprlist ::= expr */ - 262, /* (217) paren_exprlist ::= */ - 262, /* (218) paren_exprlist ::= LP exprlist RP */ - 181, /* (219) cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt */ - 266, /* (220) uniqueflag ::= UNIQUE */ - 266, /* (221) uniqueflag ::= */ - 210, /* (222) eidlist_opt ::= */ - 210, /* (223) eidlist_opt ::= LP eidlist RP */ - 220, /* (224) eidlist ::= eidlist COMMA nm collate sortorder */ - 220, /* (225) eidlist ::= nm collate sortorder */ - 267, /* (226) collate ::= */ - 267, /* (227) collate ::= COLLATE ID|STRING */ - 181, /* (228) cmd ::= DROP INDEX ifexists fullname */ - 181, /* (229) cmd ::= VACUUM vinto */ - 181, /* (230) cmd ::= VACUUM nm vinto */ - 268, /* (231) vinto ::= INTO expr */ - 268, /* (232) vinto ::= */ - 181, /* (233) cmd ::= PRAGMA nm dbnm */ - 181, /* (234) cmd ::= PRAGMA nm dbnm EQ nmnum */ - 181, /* (235) cmd ::= PRAGMA nm dbnm LP nmnum RP */ - 181, /* (236) cmd ::= PRAGMA nm dbnm EQ minus_num */ - 181, /* (237) cmd ::= PRAGMA nm dbnm LP minus_num RP */ - 201, /* (238) plus_num ::= PLUS INTEGER|FLOAT */ - 202, /* (239) minus_num ::= MINUS INTEGER|FLOAT */ - 181, /* (240) cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END */ - 270, /* (241) trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause */ - 272, /* (242) trigger_time ::= BEFORE|AFTER */ - 272, /* (243) trigger_time ::= INSTEAD OF */ - 272, /* (244) trigger_time ::= */ - 273, /* (245) trigger_event ::= DELETE|INSERT */ - 273, /* (246) trigger_event ::= UPDATE */ - 273, /* (247) trigger_event ::= UPDATE OF idlist */ - 275, /* (248) when_clause ::= */ - 275, /* (249) when_clause ::= WHEN expr */ - 271, /* (250) trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI */ - 271, /* (251) trigger_cmd_list ::= trigger_cmd SEMI */ - 277, /* (252) trnm ::= nm DOT nm */ - 278, /* (253) tridxby ::= INDEXED BY nm */ - 278, /* (254) tridxby ::= NOT INDEXED */ - 276, /* (255) trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist where_opt scanpt */ - 276, /* (256) trigger_cmd ::= scanpt insert_cmd INTO trnm idlist_opt select upsert scanpt */ - 276, /* (257) trigger_cmd ::= DELETE FROM trnm tridxby where_opt scanpt */ - 276, /* (258) trigger_cmd ::= scanpt select scanpt */ - 206, /* (259) expr ::= RAISE LP IGNORE RP */ - 206, /* (260) expr ::= RAISE LP raisetype COMMA nm RP */ - 224, /* (261) raisetype ::= ROLLBACK */ - 224, /* (262) raisetype ::= ABORT */ - 224, /* (263) raisetype ::= FAIL */ - 181, /* (264) cmd ::= DROP TRIGGER ifexists fullname */ - 181, /* (265) cmd ::= ATTACH database_kw_opt expr AS expr key_opt */ - 181, /* (266) cmd ::= DETACH database_kw_opt expr */ - 280, /* (267) key_opt ::= */ - 280, /* (268) key_opt ::= KEY expr */ - 181, /* (269) cmd ::= REINDEX */ - 181, /* (270) cmd ::= REINDEX nm dbnm */ - 181, /* (271) cmd ::= ANALYZE */ - 181, /* (272) cmd ::= ANALYZE nm dbnm */ - 181, /* (273) cmd ::= ALTER TABLE fullname RENAME TO nm */ - 181, /* (274) cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist */ - 281, /* (275) add_column_fullname ::= fullname */ - 181, /* (276) cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm */ - 181, /* (277) cmd ::= create_vtab */ - 181, /* (278) cmd ::= create_vtab LP vtabarglist RP */ - 283, /* (279) create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm */ - 285, /* (280) vtabarg ::= */ - 286, /* (281) vtabargtoken ::= ANY */ - 286, /* (282) vtabargtoken ::= lp anylist RP */ - 287, /* (283) lp ::= LP */ - 253, /* (284) with ::= WITH wqlist */ - 253, /* (285) with ::= WITH RECURSIVE wqlist */ - 229, /* (286) wqlist ::= nm eidlist_opt AS LP select RP */ - 229, /* (287) wqlist ::= wqlist COMMA nm eidlist_opt AS LP select RP */ - 289, /* (288) windowdefn_list ::= windowdefn */ - 289, /* (289) windowdefn_list ::= windowdefn_list COMMA windowdefn */ - 290, /* (290) windowdefn ::= nm AS LP window RP */ - 291, /* (291) window ::= PARTITION BY nexprlist orderby_opt frame_opt */ - 291, /* (292) window ::= nm PARTITION BY nexprlist orderby_opt frame_opt */ - 291, /* (293) window ::= ORDER BY sortlist frame_opt */ - 291, /* (294) window ::= nm ORDER BY sortlist frame_opt */ - 291, /* (295) window ::= frame_opt */ - 291, /* (296) window ::= nm frame_opt */ - 292, /* (297) frame_opt ::= */ - 292, /* (298) frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt */ - 292, /* (299) frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt */ - 295, /* (300) range_or_rows ::= RANGE|ROWS|GROUPS */ - 297, /* (301) frame_bound_s ::= frame_bound */ - 297, /* (302) frame_bound_s ::= UNBOUNDED PRECEDING */ - 298, /* (303) frame_bound_e ::= frame_bound */ - 298, /* (304) frame_bound_e ::= UNBOUNDED FOLLOWING */ - 296, /* (305) frame_bound ::= expr PRECEDING|FOLLOWING */ - 296, /* (306) frame_bound ::= CURRENT ROW */ - 299, /* (307) frame_exclude_opt ::= */ - 299, /* (308) frame_exclude_opt ::= EXCLUDE frame_exclude */ - 300, /* (309) frame_exclude ::= NO OTHERS */ - 300, /* (310) frame_exclude ::= CURRENT ROW */ - 300, /* (311) frame_exclude ::= GROUP|TIES */ - 239, /* (312) window_clause ::= WINDOW windowdefn_list */ - 258, /* (313) over_clause ::= filter_opt OVER LP window RP */ - 258, /* (314) over_clause ::= filter_opt OVER nm */ - 294, /* (315) filter_opt ::= */ - 294, /* (316) filter_opt ::= FILTER LP WHERE expr RP */ - 176, /* (317) input ::= cmdlist */ - 177, /* (318) cmdlist ::= cmdlist ecmd */ - 177, /* (319) cmdlist ::= ecmd */ - 178, /* (320) ecmd ::= SEMI */ - 178, /* (321) ecmd ::= cmdx SEMI */ - 178, /* (322) ecmd ::= explain cmdx */ - 183, /* (323) trans_opt ::= */ - 183, /* (324) trans_opt ::= TRANSACTION */ - 183, /* (325) trans_opt ::= TRANSACTION nm */ - 185, /* (326) savepoint_opt ::= SAVEPOINT */ - 185, /* (327) savepoint_opt ::= */ - 181, /* (328) cmd ::= create_table create_table_args */ - 192, /* (329) columnlist ::= columnlist COMMA columnname carglist */ - 192, /* (330) columnlist ::= columnname carglist */ - 184, /* (331) nm ::= ID|INDEXED */ - 184, /* (332) nm ::= STRING */ - 184, /* (333) nm ::= JOIN_KW */ - 198, /* (334) typetoken ::= typename */ - 199, /* (335) typename ::= ID|STRING */ - 200, /* (336) signed ::= plus_num */ - 200, /* (337) signed ::= minus_num */ - 197, /* (338) carglist ::= carglist ccons */ - 197, /* (339) carglist ::= */ - 204, /* (340) ccons ::= NULL onconf */ - 193, /* (341) conslist_opt ::= COMMA conslist */ - 216, /* (342) conslist ::= conslist tconscomma tcons */ - 216, /* (343) conslist ::= tcons */ - 217, /* (344) tconscomma ::= */ - 221, /* (345) defer_subclause_opt ::= defer_subclause */ - 223, /* (346) resolvetype ::= raisetype */ - 227, /* (347) selectnowith ::= oneselect */ - 228, /* (348) oneselect ::= values */ - 242, /* (349) sclp ::= selcollist COMMA */ - 243, /* (350) as ::= ID|STRING */ - 206, /* (351) expr ::= term */ - 259, /* (352) likeop ::= LIKE_KW|MATCH */ - 250, /* (353) exprlist ::= nexprlist */ - 269, /* (354) nmnum ::= plus_num */ - 269, /* (355) nmnum ::= nm */ - 269, /* (356) nmnum ::= ON */ - 269, /* (357) nmnum ::= DELETE */ - 269, /* (358) nmnum ::= DEFAULT */ - 201, /* (359) plus_num ::= INTEGER|FLOAT */ - 274, /* (360) foreach_clause ::= */ - 274, /* (361) foreach_clause ::= FOR EACH ROW */ - 277, /* (362) trnm ::= nm */ - 278, /* (363) tridxby ::= */ - 279, /* (364) database_kw_opt ::= DATABASE */ - 279, /* (365) database_kw_opt ::= */ - 282, /* (366) kwcolumn_opt ::= */ - 282, /* (367) kwcolumn_opt ::= COLUMNKW */ - 284, /* (368) vtabarglist ::= vtabarg */ - 284, /* (369) vtabarglist ::= vtabarglist COMMA vtabarg */ - 285, /* (370) vtabarg ::= vtabarg vtabargtoken */ - 288, /* (371) anylist ::= */ - 288, /* (372) anylist ::= anylist LP anylist RP */ - 288, /* (373) anylist ::= anylist ANY */ - 253, /* (374) with ::= */ + 191, /* (0) explain ::= EXPLAIN */ + 191, /* (1) explain ::= EXPLAIN QUERY PLAN */ + 190, /* (2) cmdx ::= cmd */ + 192, /* (3) cmd ::= BEGIN transtype trans_opt */ + 193, /* (4) transtype ::= */ + 193, /* (5) transtype ::= DEFERRED */ + 193, /* (6) transtype ::= IMMEDIATE */ + 193, /* (7) transtype ::= EXCLUSIVE */ + 192, /* (8) cmd ::= COMMIT|END trans_opt */ + 192, /* (9) cmd ::= ROLLBACK trans_opt */ + 192, /* (10) cmd ::= SAVEPOINT nm */ + 192, /* (11) cmd ::= RELEASE savepoint_opt nm */ + 192, /* (12) cmd ::= ROLLBACK trans_opt TO savepoint_opt nm */ + 197, /* (13) create_table ::= createkw temp TABLE ifnotexists nm dbnm */ + 199, /* (14) createkw ::= CREATE */ + 201, /* (15) ifnotexists ::= */ + 201, /* (16) ifnotexists ::= IF NOT EXISTS */ + 200, /* (17) temp ::= TEMP */ + 200, /* (18) temp ::= */ + 198, /* (19) create_table_args ::= LP columnlist conslist_opt RP table_option_set */ + 198, /* (20) create_table_args ::= AS select */ + 205, /* (21) table_option_set ::= */ + 205, /* (22) table_option_set ::= table_option_set COMMA table_option */ + 207, /* (23) table_option ::= WITHOUT nm */ + 207, /* (24) table_option ::= nm */ + 208, /* (25) columnname ::= nm typetoken */ + 210, /* (26) typetoken ::= */ + 210, /* (27) typetoken ::= typename LP signed RP */ + 210, /* (28) typetoken ::= typename LP signed COMMA signed RP */ + 211, /* (29) typename ::= typename ID|STRING */ + 215, /* (30) scanpt ::= */ + 216, /* (31) scantok ::= */ + 217, /* (32) ccons ::= CONSTRAINT nm */ + 217, /* (33) ccons ::= DEFAULT scantok term */ + 217, /* (34) ccons ::= DEFAULT LP expr RP */ + 217, /* (35) ccons ::= DEFAULT PLUS scantok term */ + 217, /* (36) ccons ::= DEFAULT MINUS scantok term */ + 217, /* (37) ccons ::= DEFAULT scantok ID|INDEXED */ + 217, /* (38) ccons ::= NOT NULL onconf */ + 217, /* (39) ccons ::= PRIMARY KEY sortorder onconf autoinc */ + 217, /* (40) ccons ::= UNIQUE onconf */ + 217, /* (41) ccons ::= CHECK LP expr RP */ + 217, /* (42) ccons ::= REFERENCES nm eidlist_opt refargs */ + 217, /* (43) ccons ::= defer_subclause */ + 217, /* (44) ccons ::= COLLATE ID|STRING */ + 226, /* (45) generated ::= LP expr RP */ + 226, /* (46) generated ::= LP expr RP ID */ + 222, /* (47) autoinc ::= */ + 222, /* (48) autoinc ::= AUTOINCR */ + 224, /* (49) refargs ::= */ + 224, /* (50) refargs ::= refargs refarg */ + 227, /* (51) refarg ::= MATCH nm */ + 227, /* (52) refarg ::= ON INSERT refact */ + 227, /* (53) refarg ::= ON DELETE refact */ + 227, /* (54) refarg ::= ON UPDATE refact */ + 228, /* (55) refact ::= SET NULL */ + 228, /* (56) refact ::= SET DEFAULT */ + 228, /* (57) refact ::= CASCADE */ + 228, /* (58) refact ::= RESTRICT */ + 228, /* (59) refact ::= NO ACTION */ + 225, /* (60) defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt */ + 225, /* (61) defer_subclause ::= DEFERRABLE init_deferred_pred_opt */ + 229, /* (62) init_deferred_pred_opt ::= */ + 229, /* (63) init_deferred_pred_opt ::= INITIALLY DEFERRED */ + 229, /* (64) init_deferred_pred_opt ::= INITIALLY IMMEDIATE */ + 204, /* (65) conslist_opt ::= */ + 231, /* (66) tconscomma ::= COMMA */ + 232, /* (67) tcons ::= CONSTRAINT nm */ + 232, /* (68) tcons ::= PRIMARY KEY LP sortlist autoinc RP onconf */ + 232, /* (69) tcons ::= UNIQUE LP sortlist RP onconf */ + 232, /* (70) tcons ::= CHECK LP expr RP onconf */ + 232, /* (71) tcons ::= FOREIGN KEY LP eidlist RP REFERENCES nm eidlist_opt refargs defer_subclause_opt */ + 235, /* (72) defer_subclause_opt ::= */ + 220, /* (73) onconf ::= */ + 220, /* (74) onconf ::= ON CONFLICT resolvetype */ + 236, /* (75) orconf ::= */ + 236, /* (76) orconf ::= OR resolvetype */ + 237, /* (77) resolvetype ::= IGNORE */ + 237, /* (78) resolvetype ::= REPLACE */ + 192, /* (79) cmd ::= DROP TABLE ifexists fullname */ + 239, /* (80) ifexists ::= IF EXISTS */ + 239, /* (81) ifexists ::= */ + 192, /* (82) cmd ::= createkw temp VIEW ifnotexists nm dbnm eidlist_opt AS select */ + 192, /* (83) cmd ::= DROP VIEW ifexists fullname */ + 192, /* (84) cmd ::= select */ + 206, /* (85) select ::= WITH wqlist selectnowith */ + 206, /* (86) select ::= WITH RECURSIVE wqlist selectnowith */ + 206, /* (87) select ::= selectnowith */ + 241, /* (88) selectnowith ::= selectnowith multiselect_op oneselect */ + 244, /* (89) multiselect_op ::= UNION */ + 244, /* (90) multiselect_op ::= UNION ALL */ + 244, /* (91) multiselect_op ::= EXCEPT|INTERSECT */ + 242, /* (92) oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt */ + 242, /* (93) oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt window_clause orderby_opt limit_opt */ + 254, /* (94) values ::= VALUES LP nexprlist RP */ + 242, /* (95) oneselect ::= mvalues */ + 256, /* (96) mvalues ::= values COMMA LP nexprlist RP */ + 256, /* (97) mvalues ::= mvalues COMMA LP nexprlist RP */ + 245, /* (98) distinct ::= DISTINCT */ + 245, /* (99) distinct ::= ALL */ + 245, /* (100) distinct ::= */ + 257, /* (101) sclp ::= */ + 246, /* (102) selcollist ::= sclp scanpt expr scanpt as */ + 246, /* (103) selcollist ::= sclp scanpt STAR */ + 246, /* (104) selcollist ::= sclp scanpt nm DOT STAR */ + 258, /* (105) as ::= AS nm */ + 258, /* (106) as ::= */ + 247, /* (107) from ::= */ + 247, /* (108) from ::= FROM seltablist */ + 260, /* (109) stl_prefix ::= seltablist joinop */ + 260, /* (110) stl_prefix ::= */ + 259, /* (111) seltablist ::= stl_prefix nm dbnm as on_using */ + 259, /* (112) seltablist ::= stl_prefix nm dbnm as indexed_by on_using */ + 259, /* (113) seltablist ::= stl_prefix nm dbnm LP exprlist RP as on_using */ + 259, /* (114) seltablist ::= stl_prefix LP select RP as on_using */ + 259, /* (115) seltablist ::= stl_prefix LP seltablist RP as on_using */ + 202, /* (116) dbnm ::= */ + 202, /* (117) dbnm ::= DOT nm */ + 240, /* (118) fullname ::= nm */ + 240, /* (119) fullname ::= nm DOT nm */ + 265, /* (120) xfullname ::= nm */ + 265, /* (121) xfullname ::= nm DOT nm */ + 265, /* (122) xfullname ::= nm AS nm */ + 265, /* (123) xfullname ::= nm DOT nm AS nm */ + 261, /* (124) joinop ::= COMMA|JOIN */ + 261, /* (125) joinop ::= JOIN_KW JOIN */ + 261, /* (126) joinop ::= JOIN_KW nm JOIN */ + 261, /* (127) joinop ::= JOIN_KW nm nm JOIN */ + 262, /* (128) on_using ::= ON expr */ + 262, /* (129) on_using ::= USING LP idlist RP */ + 262, /* (130) on_using ::= */ + 267, /* (131) indexed_opt ::= */ + 263, /* (132) indexed_by ::= INDEXED BY nm */ + 263, /* (133) indexed_by ::= NOT INDEXED */ + 251, /* (134) orderby_opt ::= */ + 251, /* (135) orderby_opt ::= ORDER BY sortlist */ + 233, /* (136) sortlist ::= sortlist COMMA expr sortorder nulls */ + 233, /* (137) sortlist ::= expr sortorder nulls */ + 221, /* (138) sortorder ::= ASC */ + 221, /* (139) sortorder ::= DESC */ + 221, /* (140) sortorder ::= */ + 268, /* (141) nulls ::= NULLS FIRST */ + 268, /* (142) nulls ::= NULLS LAST */ + 268, /* (143) nulls ::= */ + 249, /* (144) groupby_opt ::= */ + 249, /* (145) groupby_opt ::= GROUP BY nexprlist */ + 250, /* (146) having_opt ::= */ + 250, /* (147) having_opt ::= HAVING expr */ + 252, /* (148) limit_opt ::= */ + 252, /* (149) limit_opt ::= LIMIT expr */ + 252, /* (150) limit_opt ::= LIMIT expr OFFSET expr */ + 252, /* (151) limit_opt ::= LIMIT expr COMMA expr */ + 192, /* (152) cmd ::= with DELETE FROM xfullname indexed_opt where_opt_ret */ + 248, /* (153) where_opt ::= */ + 248, /* (154) where_opt ::= WHERE expr */ + 270, /* (155) where_opt_ret ::= */ + 270, /* (156) where_opt_ret ::= WHERE expr */ + 270, /* (157) where_opt_ret ::= RETURNING selcollist */ + 270, /* (158) where_opt_ret ::= WHERE expr RETURNING selcollist */ + 192, /* (159) cmd ::= with UPDATE orconf xfullname indexed_opt SET setlist from where_opt_ret */ + 271, /* (160) setlist ::= setlist COMMA nm EQ expr */ + 271, /* (161) setlist ::= setlist COMMA LP idlist RP EQ expr */ + 271, /* (162) setlist ::= nm EQ expr */ + 271, /* (163) setlist ::= LP idlist RP EQ expr */ + 192, /* (164) cmd ::= with insert_cmd INTO xfullname idlist_opt select upsert */ + 192, /* (165) cmd ::= with insert_cmd INTO xfullname idlist_opt DEFAULT VALUES returning */ + 274, /* (166) upsert ::= */ + 274, /* (167) upsert ::= RETURNING selcollist */ + 274, /* (168) upsert ::= ON CONFLICT LP sortlist RP where_opt DO UPDATE SET setlist where_opt upsert */ + 274, /* (169) upsert ::= ON CONFLICT LP sortlist RP where_opt DO NOTHING upsert */ + 274, /* (170) upsert ::= ON CONFLICT DO NOTHING returning */ + 274, /* (171) upsert ::= ON CONFLICT DO UPDATE SET setlist where_opt returning */ + 275, /* (172) returning ::= RETURNING selcollist */ + 272, /* (173) insert_cmd ::= INSERT orconf */ + 272, /* (174) insert_cmd ::= REPLACE */ + 273, /* (175) idlist_opt ::= */ + 273, /* (176) idlist_opt ::= LP idlist RP */ + 266, /* (177) idlist ::= idlist COMMA nm */ + 266, /* (178) idlist ::= nm */ + 219, /* (179) expr ::= LP expr RP */ + 219, /* (180) expr ::= ID|INDEXED|JOIN_KW */ + 219, /* (181) expr ::= nm DOT nm */ + 219, /* (182) expr ::= nm DOT nm DOT nm */ + 218, /* (183) term ::= NULL|FLOAT|BLOB */ + 218, /* (184) term ::= STRING */ + 218, /* (185) term ::= INTEGER */ + 219, /* (186) expr ::= VARIABLE */ + 219, /* (187) expr ::= expr COLLATE ID|STRING */ + 219, /* (188) expr ::= CAST LP expr AS typetoken RP */ + 219, /* (189) expr ::= ID|INDEXED|JOIN_KW LP distinct exprlist RP */ + 219, /* (190) expr ::= ID|INDEXED|JOIN_KW LP distinct exprlist ORDER BY sortlist RP */ + 219, /* (191) expr ::= ID|INDEXED|JOIN_KW LP STAR RP */ + 219, /* (192) expr ::= ID|INDEXED|JOIN_KW LP distinct exprlist RP filter_over */ + 219, /* (193) expr ::= ID|INDEXED|JOIN_KW LP distinct exprlist ORDER BY sortlist RP filter_over */ + 219, /* (194) expr ::= ID|INDEXED|JOIN_KW LP STAR RP filter_over */ + 218, /* (195) term ::= CTIME_KW */ + 219, /* (196) expr ::= LP nexprlist COMMA expr RP */ + 219, /* (197) expr ::= expr AND expr */ + 219, /* (198) expr ::= expr OR expr */ + 219, /* (199) expr ::= expr LT|GT|GE|LE expr */ + 219, /* (200) expr ::= expr EQ|NE expr */ + 219, /* (201) expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr */ + 219, /* (202) expr ::= expr PLUS|MINUS expr */ + 219, /* (203) expr ::= expr STAR|SLASH|REM expr */ + 219, /* (204) expr ::= expr CONCAT expr */ + 277, /* (205) likeop ::= NOT LIKE_KW|MATCH */ + 219, /* (206) expr ::= expr likeop expr */ + 219, /* (207) expr ::= expr likeop expr ESCAPE expr */ + 219, /* (208) expr ::= expr ISNULL|NOTNULL */ + 219, /* (209) expr ::= expr NOT NULL */ + 219, /* (210) expr ::= expr IS expr */ + 219, /* (211) expr ::= expr IS NOT expr */ + 219, /* (212) expr ::= expr IS NOT DISTINCT FROM expr */ + 219, /* (213) expr ::= expr IS DISTINCT FROM expr */ + 219, /* (214) expr ::= NOT expr */ + 219, /* (215) expr ::= BITNOT expr */ + 219, /* (216) expr ::= PLUS|MINUS expr */ + 219, /* (217) expr ::= expr PTR expr */ + 278, /* (218) between_op ::= BETWEEN */ + 278, /* (219) between_op ::= NOT BETWEEN */ + 219, /* (220) expr ::= expr between_op expr AND expr */ + 279, /* (221) in_op ::= IN */ + 279, /* (222) in_op ::= NOT IN */ + 219, /* (223) expr ::= expr in_op LP exprlist RP */ + 219, /* (224) expr ::= LP select RP */ + 219, /* (225) expr ::= expr in_op LP select RP */ + 219, /* (226) expr ::= expr in_op nm dbnm paren_exprlist */ + 219, /* (227) expr ::= EXISTS LP select RP */ + 219, /* (228) expr ::= CASE case_operand case_exprlist case_else END */ + 282, /* (229) case_exprlist ::= case_exprlist WHEN expr THEN expr */ + 282, /* (230) case_exprlist ::= WHEN expr THEN expr */ + 283, /* (231) case_else ::= ELSE expr */ + 283, /* (232) case_else ::= */ + 281, /* (233) case_operand ::= */ + 264, /* (234) exprlist ::= */ + 255, /* (235) nexprlist ::= nexprlist COMMA expr */ + 255, /* (236) nexprlist ::= expr */ + 280, /* (237) paren_exprlist ::= */ + 280, /* (238) paren_exprlist ::= LP exprlist RP */ + 192, /* (239) cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt */ + 284, /* (240) uniqueflag ::= UNIQUE */ + 284, /* (241) uniqueflag ::= */ + 223, /* (242) eidlist_opt ::= */ + 223, /* (243) eidlist_opt ::= LP eidlist RP */ + 234, /* (244) eidlist ::= eidlist COMMA nm collate sortorder */ + 234, /* (245) eidlist ::= nm collate sortorder */ + 285, /* (246) collate ::= */ + 285, /* (247) collate ::= COLLATE ID|STRING */ + 192, /* (248) cmd ::= DROP INDEX ifexists fullname */ + 192, /* (249) cmd ::= VACUUM vinto */ + 192, /* (250) cmd ::= VACUUM nm vinto */ + 286, /* (251) vinto ::= INTO expr */ + 286, /* (252) vinto ::= */ + 192, /* (253) cmd ::= PRAGMA nm dbnm */ + 192, /* (254) cmd ::= PRAGMA nm dbnm EQ nmnum */ + 192, /* (255) cmd ::= PRAGMA nm dbnm LP nmnum RP */ + 192, /* (256) cmd ::= PRAGMA nm dbnm EQ minus_num */ + 192, /* (257) cmd ::= PRAGMA nm dbnm LP minus_num RP */ + 213, /* (258) plus_num ::= PLUS INTEGER|FLOAT */ + 214, /* (259) minus_num ::= MINUS INTEGER|FLOAT */ + 192, /* (260) cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END */ + 288, /* (261) trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause */ + 290, /* (262) trigger_time ::= BEFORE|AFTER */ + 290, /* (263) trigger_time ::= INSTEAD OF */ + 290, /* (264) trigger_time ::= */ + 291, /* (265) trigger_event ::= DELETE|INSERT */ + 291, /* (266) trigger_event ::= UPDATE */ + 291, /* (267) trigger_event ::= UPDATE OF idlist */ + 293, /* (268) when_clause ::= */ + 293, /* (269) when_clause ::= WHEN expr */ + 289, /* (270) trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI */ + 289, /* (271) trigger_cmd_list ::= trigger_cmd SEMI */ + 295, /* (272) tridxby ::= INDEXED BY nm */ + 295, /* (273) tridxby ::= NOT INDEXED */ + 294, /* (274) trigger_cmd ::= UPDATE orconf xfullname tridxby SET setlist from where_opt scanpt */ + 294, /* (275) trigger_cmd ::= scanpt insert_cmd INTO xfullname idlist_opt select upsert scanpt */ + 294, /* (276) trigger_cmd ::= DELETE FROM xfullname tridxby where_opt scanpt */ + 294, /* (277) trigger_cmd ::= scanpt select scanpt */ + 219, /* (278) expr ::= RAISE LP IGNORE RP */ + 219, /* (279) expr ::= RAISE LP raisetype COMMA expr RP */ + 238, /* (280) raisetype ::= ROLLBACK */ + 238, /* (281) raisetype ::= ABORT */ + 238, /* (282) raisetype ::= FAIL */ + 192, /* (283) cmd ::= DROP TRIGGER ifexists fullname */ + 192, /* (284) cmd ::= ATTACH database_kw_opt expr AS expr key_opt */ + 192, /* (285) cmd ::= DETACH database_kw_opt expr */ + 297, /* (286) key_opt ::= */ + 297, /* (287) key_opt ::= KEY expr */ + 192, /* (288) cmd ::= REINDEX */ + 192, /* (289) cmd ::= REINDEX nm dbnm */ + 192, /* (290) cmd ::= ANALYZE */ + 192, /* (291) cmd ::= ANALYZE nm dbnm */ + 192, /* (292) cmd ::= ALTER TABLE fullname RENAME TO nm */ + 192, /* (293) cmd ::= alter_add carglist */ + 298, /* (294) alter_add ::= ALTER TABLE fullname ADD kwcolumn_opt nm typetoken */ + 192, /* (295) cmd ::= ALTER TABLE fullname DROP kwcolumn_opt nm */ + 192, /* (296) cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm */ + 192, /* (297) cmd ::= ALTER TABLE fullname DROP CONSTRAINT nm */ + 192, /* (298) cmd ::= ALTER TABLE fullname ALTER kwcolumn_opt nm DROP NOT NULL */ + 192, /* (299) cmd ::= ALTER TABLE fullname ALTER kwcolumn_opt nm SET NOT NULL onconf */ + 192, /* (300) cmd ::= ALTER TABLE fullname ADD CONSTRAINT nm CHECK LP expr RP onconf */ + 192, /* (301) cmd ::= ALTER TABLE fullname ADD CHECK LP expr RP onconf */ + 192, /* (302) cmd ::= create_vtab */ + 192, /* (303) cmd ::= create_vtab LP vtabarglist RP */ + 300, /* (304) create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm */ + 302, /* (305) vtabarg ::= */ + 303, /* (306) vtabargtoken ::= ANY */ + 303, /* (307) vtabargtoken ::= lp anylist RP */ + 304, /* (308) lp ::= LP */ + 269, /* (309) with ::= WITH wqlist */ + 269, /* (310) with ::= WITH RECURSIVE wqlist */ + 307, /* (311) wqas ::= AS */ + 307, /* (312) wqas ::= AS MATERIALIZED */ + 307, /* (313) wqas ::= AS NOT MATERIALIZED */ + 306, /* (314) wqitem ::= withnm eidlist_opt wqas LP select RP */ + 308, /* (315) withnm ::= nm */ + 243, /* (316) wqlist ::= wqitem */ + 243, /* (317) wqlist ::= wqlist COMMA wqitem */ + 309, /* (318) windowdefn_list ::= windowdefn_list COMMA windowdefn */ + 310, /* (319) windowdefn ::= nm AS LP window RP */ + 311, /* (320) window ::= PARTITION BY nexprlist orderby_opt frame_opt */ + 311, /* (321) window ::= nm PARTITION BY nexprlist orderby_opt frame_opt */ + 311, /* (322) window ::= ORDER BY sortlist frame_opt */ + 311, /* (323) window ::= nm ORDER BY sortlist frame_opt */ + 311, /* (324) window ::= nm frame_opt */ + 312, /* (325) frame_opt ::= */ + 312, /* (326) frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt */ + 312, /* (327) frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt */ + 316, /* (328) range_or_rows ::= RANGE|ROWS|GROUPS */ + 318, /* (329) frame_bound_s ::= frame_bound */ + 318, /* (330) frame_bound_s ::= UNBOUNDED PRECEDING */ + 319, /* (331) frame_bound_e ::= frame_bound */ + 319, /* (332) frame_bound_e ::= UNBOUNDED FOLLOWING */ + 317, /* (333) frame_bound ::= expr PRECEDING|FOLLOWING */ + 317, /* (334) frame_bound ::= CURRENT ROW */ + 320, /* (335) frame_exclude_opt ::= */ + 320, /* (336) frame_exclude_opt ::= EXCLUDE frame_exclude */ + 321, /* (337) frame_exclude ::= NO OTHERS */ + 321, /* (338) frame_exclude ::= CURRENT ROW */ + 321, /* (339) frame_exclude ::= GROUP|TIES */ + 253, /* (340) window_clause ::= WINDOW windowdefn_list */ + 276, /* (341) filter_over ::= filter_clause over_clause */ + 276, /* (342) filter_over ::= over_clause */ + 276, /* (343) filter_over ::= filter_clause */ + 315, /* (344) over_clause ::= OVER LP window RP */ + 315, /* (345) over_clause ::= OVER nm */ + 314, /* (346) filter_clause ::= FILTER LP WHERE expr RP */ + 218, /* (347) term ::= QNUMBER */ + 187, /* (348) input ::= cmdlist */ + 188, /* (349) cmdlist ::= cmdlist ecmd */ + 188, /* (350) cmdlist ::= ecmd */ + 189, /* (351) ecmd ::= SEMI */ + 189, /* (352) ecmd ::= cmdx SEMI */ + 189, /* (353) ecmd ::= explain cmdx SEMI */ + 194, /* (354) trans_opt ::= */ + 194, /* (355) trans_opt ::= TRANSACTION */ + 194, /* (356) trans_opt ::= TRANSACTION nm */ + 196, /* (357) savepoint_opt ::= SAVEPOINT */ + 196, /* (358) savepoint_opt ::= */ + 192, /* (359) cmd ::= create_table create_table_args */ + 205, /* (360) table_option_set ::= table_option */ + 203, /* (361) columnlist ::= columnlist COMMA columnname carglist */ + 203, /* (362) columnlist ::= columnname carglist */ + 195, /* (363) nm ::= ID|INDEXED|JOIN_KW */ + 195, /* (364) nm ::= STRING */ + 210, /* (365) typetoken ::= typename */ + 211, /* (366) typename ::= ID|STRING */ + 212, /* (367) signed ::= plus_num */ + 212, /* (368) signed ::= minus_num */ + 209, /* (369) carglist ::= carglist ccons */ + 209, /* (370) carglist ::= */ + 217, /* (371) ccons ::= NULL onconf */ + 217, /* (372) ccons ::= GENERATED ALWAYS AS generated */ + 217, /* (373) ccons ::= AS generated */ + 204, /* (374) conslist_opt ::= COMMA conslist */ + 230, /* (375) conslist ::= conslist tconscomma tcons */ + 230, /* (376) conslist ::= tcons */ + 231, /* (377) tconscomma ::= */ + 235, /* (378) defer_subclause_opt ::= defer_subclause */ + 237, /* (379) resolvetype ::= raisetype */ + 241, /* (380) selectnowith ::= oneselect */ + 242, /* (381) oneselect ::= values */ + 257, /* (382) sclp ::= selcollist COMMA */ + 258, /* (383) as ::= ID|STRING */ + 267, /* (384) indexed_opt ::= indexed_by */ + 275, /* (385) returning ::= */ + 219, /* (386) expr ::= term */ + 277, /* (387) likeop ::= LIKE_KW|MATCH */ + 281, /* (388) case_operand ::= expr */ + 264, /* (389) exprlist ::= nexprlist */ + 287, /* (390) nmnum ::= plus_num */ + 287, /* (391) nmnum ::= nm */ + 287, /* (392) nmnum ::= ON */ + 287, /* (393) nmnum ::= DELETE */ + 287, /* (394) nmnum ::= DEFAULT */ + 213, /* (395) plus_num ::= INTEGER|FLOAT */ + 292, /* (396) foreach_clause ::= */ + 292, /* (397) foreach_clause ::= FOR EACH ROW */ + 295, /* (398) tridxby ::= */ + 296, /* (399) database_kw_opt ::= DATABASE */ + 296, /* (400) database_kw_opt ::= */ + 299, /* (401) kwcolumn_opt ::= */ + 299, /* (402) kwcolumn_opt ::= COLUMNKW */ + 301, /* (403) vtabarglist ::= vtabarg */ + 301, /* (404) vtabarglist ::= vtabarglist COMMA vtabarg */ + 302, /* (405) vtabarg ::= vtabarg vtabargtoken */ + 305, /* (406) anylist ::= */ + 305, /* (407) anylist ::= anylist LP anylist RP */ + 305, /* (408) anylist ::= anylist ANY */ + 269, /* (409) with ::= */ + 309, /* (410) windowdefn_list ::= windowdefn */ + 311, /* (411) window ::= frame_opt */ }; /* For rule J, yyRuleInfoNRhs[J] contains the negative of the number @@ -150869,362 +183219,399 @@ static const signed char yyRuleInfoNRhs[] = { -3, /* (16) ifnotexists ::= IF NOT EXISTS */ -1, /* (17) temp ::= TEMP */ 0, /* (18) temp ::= */ - -5, /* (19) create_table_args ::= LP columnlist conslist_opt RP table_options */ + -5, /* (19) create_table_args ::= LP columnlist conslist_opt RP table_option_set */ -2, /* (20) create_table_args ::= AS select */ - 0, /* (21) table_options ::= */ - -2, /* (22) table_options ::= WITHOUT nm */ - -2, /* (23) columnname ::= nm typetoken */ - 0, /* (24) typetoken ::= */ - -4, /* (25) typetoken ::= typename LP signed RP */ - -6, /* (26) typetoken ::= typename LP signed COMMA signed RP */ - -2, /* (27) typename ::= typename ID|STRING */ - 0, /* (28) scanpt ::= */ - -2, /* (29) ccons ::= CONSTRAINT nm */ - -4, /* (30) ccons ::= DEFAULT scanpt term scanpt */ - -4, /* (31) ccons ::= DEFAULT LP expr RP */ - -4, /* (32) ccons ::= DEFAULT PLUS term scanpt */ - -4, /* (33) ccons ::= DEFAULT MINUS term scanpt */ - -3, /* (34) ccons ::= DEFAULT scanpt ID|INDEXED */ - -3, /* (35) ccons ::= NOT NULL onconf */ - -5, /* (36) ccons ::= PRIMARY KEY sortorder onconf autoinc */ - -2, /* (37) ccons ::= UNIQUE onconf */ - -4, /* (38) ccons ::= CHECK LP expr RP */ - -4, /* (39) ccons ::= REFERENCES nm eidlist_opt refargs */ - -1, /* (40) ccons ::= defer_subclause */ - -2, /* (41) ccons ::= COLLATE ID|STRING */ - 0, /* (42) autoinc ::= */ - -1, /* (43) autoinc ::= AUTOINCR */ - 0, /* (44) refargs ::= */ - -2, /* (45) refargs ::= refargs refarg */ - -2, /* (46) refarg ::= MATCH nm */ - -3, /* (47) refarg ::= ON INSERT refact */ - -3, /* (48) refarg ::= ON DELETE refact */ - -3, /* (49) refarg ::= ON UPDATE refact */ - -2, /* (50) refact ::= SET NULL */ - -2, /* (51) refact ::= SET DEFAULT */ - -1, /* (52) refact ::= CASCADE */ - -1, /* (53) refact ::= RESTRICT */ - -2, /* (54) refact ::= NO ACTION */ - -3, /* (55) defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt */ - -2, /* (56) defer_subclause ::= DEFERRABLE init_deferred_pred_opt */ - 0, /* (57) init_deferred_pred_opt ::= */ - -2, /* (58) init_deferred_pred_opt ::= INITIALLY DEFERRED */ - -2, /* (59) init_deferred_pred_opt ::= INITIALLY IMMEDIATE */ - 0, /* (60) conslist_opt ::= */ - -1, /* (61) tconscomma ::= COMMA */ - -2, /* (62) tcons ::= CONSTRAINT nm */ - -7, /* (63) tcons ::= PRIMARY KEY LP sortlist autoinc RP onconf */ - -5, /* (64) tcons ::= UNIQUE LP sortlist RP onconf */ - -5, /* (65) tcons ::= CHECK LP expr RP onconf */ - -10, /* (66) tcons ::= FOREIGN KEY LP eidlist RP REFERENCES nm eidlist_opt refargs defer_subclause_opt */ - 0, /* (67) defer_subclause_opt ::= */ - 0, /* (68) onconf ::= */ - -3, /* (69) onconf ::= ON CONFLICT resolvetype */ - 0, /* (70) orconf ::= */ - -2, /* (71) orconf ::= OR resolvetype */ - -1, /* (72) resolvetype ::= IGNORE */ - -1, /* (73) resolvetype ::= REPLACE */ - -4, /* (74) cmd ::= DROP TABLE ifexists fullname */ - -2, /* (75) ifexists ::= IF EXISTS */ - 0, /* (76) ifexists ::= */ - -9, /* (77) cmd ::= createkw temp VIEW ifnotexists nm dbnm eidlist_opt AS select */ - -4, /* (78) cmd ::= DROP VIEW ifexists fullname */ - -1, /* (79) cmd ::= select */ - -3, /* (80) select ::= WITH wqlist selectnowith */ - -4, /* (81) select ::= WITH RECURSIVE wqlist selectnowith */ - -1, /* (82) select ::= selectnowith */ - -3, /* (83) selectnowith ::= selectnowith multiselect_op oneselect */ - -1, /* (84) multiselect_op ::= UNION */ - -2, /* (85) multiselect_op ::= UNION ALL */ - -1, /* (86) multiselect_op ::= EXCEPT|INTERSECT */ - -9, /* (87) oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt */ - -10, /* (88) oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt window_clause orderby_opt limit_opt */ - -4, /* (89) values ::= VALUES LP nexprlist RP */ - -5, /* (90) values ::= values COMMA LP nexprlist RP */ - -1, /* (91) distinct ::= DISTINCT */ - -1, /* (92) distinct ::= ALL */ - 0, /* (93) distinct ::= */ - 0, /* (94) sclp ::= */ - -5, /* (95) selcollist ::= sclp scanpt expr scanpt as */ - -3, /* (96) selcollist ::= sclp scanpt STAR */ - -5, /* (97) selcollist ::= sclp scanpt nm DOT STAR */ - -2, /* (98) as ::= AS nm */ - 0, /* (99) as ::= */ - 0, /* (100) from ::= */ - -2, /* (101) from ::= FROM seltablist */ - -2, /* (102) stl_prefix ::= seltablist joinop */ - 0, /* (103) stl_prefix ::= */ - -7, /* (104) seltablist ::= stl_prefix nm dbnm as indexed_opt on_opt using_opt */ - -9, /* (105) seltablist ::= stl_prefix nm dbnm LP exprlist RP as on_opt using_opt */ - -7, /* (106) seltablist ::= stl_prefix LP select RP as on_opt using_opt */ - -7, /* (107) seltablist ::= stl_prefix LP seltablist RP as on_opt using_opt */ - 0, /* (108) dbnm ::= */ - -2, /* (109) dbnm ::= DOT nm */ - -1, /* (110) fullname ::= nm */ - -3, /* (111) fullname ::= nm DOT nm */ - -1, /* (112) xfullname ::= nm */ - -3, /* (113) xfullname ::= nm DOT nm */ - -5, /* (114) xfullname ::= nm DOT nm AS nm */ - -3, /* (115) xfullname ::= nm AS nm */ - -1, /* (116) joinop ::= COMMA|JOIN */ - -2, /* (117) joinop ::= JOIN_KW JOIN */ - -3, /* (118) joinop ::= JOIN_KW nm JOIN */ - -4, /* (119) joinop ::= JOIN_KW nm nm JOIN */ - -2, /* (120) on_opt ::= ON expr */ - 0, /* (121) on_opt ::= */ - 0, /* (122) indexed_opt ::= */ - -3, /* (123) indexed_opt ::= INDEXED BY nm */ - -2, /* (124) indexed_opt ::= NOT INDEXED */ - -4, /* (125) using_opt ::= USING LP idlist RP */ - 0, /* (126) using_opt ::= */ - 0, /* (127) orderby_opt ::= */ - -3, /* (128) orderby_opt ::= ORDER BY sortlist */ - -4, /* (129) sortlist ::= sortlist COMMA expr sortorder */ - -2, /* (130) sortlist ::= expr sortorder */ - -1, /* (131) sortorder ::= ASC */ - -1, /* (132) sortorder ::= DESC */ - 0, /* (133) sortorder ::= */ - 0, /* (134) groupby_opt ::= */ - -3, /* (135) groupby_opt ::= GROUP BY nexprlist */ - 0, /* (136) having_opt ::= */ - -2, /* (137) having_opt ::= HAVING expr */ - 0, /* (138) limit_opt ::= */ - -2, /* (139) limit_opt ::= LIMIT expr */ - -4, /* (140) limit_opt ::= LIMIT expr OFFSET expr */ - -4, /* (141) limit_opt ::= LIMIT expr COMMA expr */ - -6, /* (142) cmd ::= with DELETE FROM xfullname indexed_opt where_opt */ - 0, /* (143) where_opt ::= */ - -2, /* (144) where_opt ::= WHERE expr */ - -8, /* (145) cmd ::= with UPDATE orconf xfullname indexed_opt SET setlist where_opt */ - -5, /* (146) setlist ::= setlist COMMA nm EQ expr */ - -7, /* (147) setlist ::= setlist COMMA LP idlist RP EQ expr */ - -3, /* (148) setlist ::= nm EQ expr */ - -5, /* (149) setlist ::= LP idlist RP EQ expr */ - -7, /* (150) cmd ::= with insert_cmd INTO xfullname idlist_opt select upsert */ - -7, /* (151) cmd ::= with insert_cmd INTO xfullname idlist_opt DEFAULT VALUES */ - 0, /* (152) upsert ::= */ - -11, /* (153) upsert ::= ON CONFLICT LP sortlist RP where_opt DO UPDATE SET setlist where_opt */ - -8, /* (154) upsert ::= ON CONFLICT LP sortlist RP where_opt DO NOTHING */ - -4, /* (155) upsert ::= ON CONFLICT DO NOTHING */ - -2, /* (156) insert_cmd ::= INSERT orconf */ - -1, /* (157) insert_cmd ::= REPLACE */ - 0, /* (158) idlist_opt ::= */ - -3, /* (159) idlist_opt ::= LP idlist RP */ - -3, /* (160) idlist ::= idlist COMMA nm */ - -1, /* (161) idlist ::= nm */ - -3, /* (162) expr ::= LP expr RP */ - -1, /* (163) expr ::= ID|INDEXED */ - -1, /* (164) expr ::= JOIN_KW */ - -3, /* (165) expr ::= nm DOT nm */ - -5, /* (166) expr ::= nm DOT nm DOT nm */ - -1, /* (167) term ::= NULL|FLOAT|BLOB */ - -1, /* (168) term ::= STRING */ - -1, /* (169) term ::= INTEGER */ - -1, /* (170) expr ::= VARIABLE */ - -3, /* (171) expr ::= expr COLLATE ID|STRING */ - -6, /* (172) expr ::= CAST LP expr AS typetoken RP */ - -5, /* (173) expr ::= ID|INDEXED LP distinct exprlist RP */ - -4, /* (174) expr ::= ID|INDEXED LP STAR RP */ - -6, /* (175) expr ::= ID|INDEXED LP distinct exprlist RP over_clause */ - -5, /* (176) expr ::= ID|INDEXED LP STAR RP over_clause */ - -1, /* (177) term ::= CTIME_KW */ - -5, /* (178) expr ::= LP nexprlist COMMA expr RP */ - -3, /* (179) expr ::= expr AND expr */ - -3, /* (180) expr ::= expr OR expr */ - -3, /* (181) expr ::= expr LT|GT|GE|LE expr */ - -3, /* (182) expr ::= expr EQ|NE expr */ - -3, /* (183) expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr */ - -3, /* (184) expr ::= expr PLUS|MINUS expr */ - -3, /* (185) expr ::= expr STAR|SLASH|REM expr */ - -3, /* (186) expr ::= expr CONCAT expr */ - -2, /* (187) likeop ::= NOT LIKE_KW|MATCH */ - -3, /* (188) expr ::= expr likeop expr */ - -5, /* (189) expr ::= expr likeop expr ESCAPE expr */ - -2, /* (190) expr ::= expr ISNULL|NOTNULL */ - -3, /* (191) expr ::= expr NOT NULL */ - -3, /* (192) expr ::= expr IS expr */ - -4, /* (193) expr ::= expr IS NOT expr */ - -2, /* (194) expr ::= NOT expr */ - -2, /* (195) expr ::= BITNOT expr */ - -2, /* (196) expr ::= PLUS|MINUS expr */ - -1, /* (197) between_op ::= BETWEEN */ - -2, /* (198) between_op ::= NOT BETWEEN */ - -5, /* (199) expr ::= expr between_op expr AND expr */ - -1, /* (200) in_op ::= IN */ - -2, /* (201) in_op ::= NOT IN */ - -5, /* (202) expr ::= expr in_op LP exprlist RP */ - -3, /* (203) expr ::= LP select RP */ - -5, /* (204) expr ::= expr in_op LP select RP */ - -5, /* (205) expr ::= expr in_op nm dbnm paren_exprlist */ - -4, /* (206) expr ::= EXISTS LP select RP */ - -5, /* (207) expr ::= CASE case_operand case_exprlist case_else END */ - -5, /* (208) case_exprlist ::= case_exprlist WHEN expr THEN expr */ - -4, /* (209) case_exprlist ::= WHEN expr THEN expr */ - -2, /* (210) case_else ::= ELSE expr */ - 0, /* (211) case_else ::= */ - -1, /* (212) case_operand ::= expr */ - 0, /* (213) case_operand ::= */ - 0, /* (214) exprlist ::= */ - -3, /* (215) nexprlist ::= nexprlist COMMA expr */ - -1, /* (216) nexprlist ::= expr */ - 0, /* (217) paren_exprlist ::= */ - -3, /* (218) paren_exprlist ::= LP exprlist RP */ - -12, /* (219) cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt */ - -1, /* (220) uniqueflag ::= UNIQUE */ - 0, /* (221) uniqueflag ::= */ - 0, /* (222) eidlist_opt ::= */ - -3, /* (223) eidlist_opt ::= LP eidlist RP */ - -5, /* (224) eidlist ::= eidlist COMMA nm collate sortorder */ - -3, /* (225) eidlist ::= nm collate sortorder */ - 0, /* (226) collate ::= */ - -2, /* (227) collate ::= COLLATE ID|STRING */ - -4, /* (228) cmd ::= DROP INDEX ifexists fullname */ - -2, /* (229) cmd ::= VACUUM vinto */ - -3, /* (230) cmd ::= VACUUM nm vinto */ - -2, /* (231) vinto ::= INTO expr */ - 0, /* (232) vinto ::= */ - -3, /* (233) cmd ::= PRAGMA nm dbnm */ - -5, /* (234) cmd ::= PRAGMA nm dbnm EQ nmnum */ - -6, /* (235) cmd ::= PRAGMA nm dbnm LP nmnum RP */ - -5, /* (236) cmd ::= PRAGMA nm dbnm EQ minus_num */ - -6, /* (237) cmd ::= PRAGMA nm dbnm LP minus_num RP */ - -2, /* (238) plus_num ::= PLUS INTEGER|FLOAT */ - -2, /* (239) minus_num ::= MINUS INTEGER|FLOAT */ - -5, /* (240) cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END */ - -11, /* (241) trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause */ - -1, /* (242) trigger_time ::= BEFORE|AFTER */ - -2, /* (243) trigger_time ::= INSTEAD OF */ - 0, /* (244) trigger_time ::= */ - -1, /* (245) trigger_event ::= DELETE|INSERT */ - -1, /* (246) trigger_event ::= UPDATE */ - -3, /* (247) trigger_event ::= UPDATE OF idlist */ - 0, /* (248) when_clause ::= */ - -2, /* (249) when_clause ::= WHEN expr */ - -3, /* (250) trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI */ - -2, /* (251) trigger_cmd_list ::= trigger_cmd SEMI */ - -3, /* (252) trnm ::= nm DOT nm */ - -3, /* (253) tridxby ::= INDEXED BY nm */ - -2, /* (254) tridxby ::= NOT INDEXED */ - -8, /* (255) trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist where_opt scanpt */ - -8, /* (256) trigger_cmd ::= scanpt insert_cmd INTO trnm idlist_opt select upsert scanpt */ - -6, /* (257) trigger_cmd ::= DELETE FROM trnm tridxby where_opt scanpt */ - -3, /* (258) trigger_cmd ::= scanpt select scanpt */ - -4, /* (259) expr ::= RAISE LP IGNORE RP */ - -6, /* (260) expr ::= RAISE LP raisetype COMMA nm RP */ - -1, /* (261) raisetype ::= ROLLBACK */ - -1, /* (262) raisetype ::= ABORT */ - -1, /* (263) raisetype ::= FAIL */ - -4, /* (264) cmd ::= DROP TRIGGER ifexists fullname */ - -6, /* (265) cmd ::= ATTACH database_kw_opt expr AS expr key_opt */ - -3, /* (266) cmd ::= DETACH database_kw_opt expr */ - 0, /* (267) key_opt ::= */ - -2, /* (268) key_opt ::= KEY expr */ - -1, /* (269) cmd ::= REINDEX */ - -3, /* (270) cmd ::= REINDEX nm dbnm */ - -1, /* (271) cmd ::= ANALYZE */ - -3, /* (272) cmd ::= ANALYZE nm dbnm */ - -6, /* (273) cmd ::= ALTER TABLE fullname RENAME TO nm */ - -7, /* (274) cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist */ - -1, /* (275) add_column_fullname ::= fullname */ - -8, /* (276) cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm */ - -1, /* (277) cmd ::= create_vtab */ - -4, /* (278) cmd ::= create_vtab LP vtabarglist RP */ - -8, /* (279) create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm */ - 0, /* (280) vtabarg ::= */ - -1, /* (281) vtabargtoken ::= ANY */ - -3, /* (282) vtabargtoken ::= lp anylist RP */ - -1, /* (283) lp ::= LP */ - -2, /* (284) with ::= WITH wqlist */ - -3, /* (285) with ::= WITH RECURSIVE wqlist */ - -6, /* (286) wqlist ::= nm eidlist_opt AS LP select RP */ - -8, /* (287) wqlist ::= wqlist COMMA nm eidlist_opt AS LP select RP */ - -1, /* (288) windowdefn_list ::= windowdefn */ - -3, /* (289) windowdefn_list ::= windowdefn_list COMMA windowdefn */ - -5, /* (290) windowdefn ::= nm AS LP window RP */ - -5, /* (291) window ::= PARTITION BY nexprlist orderby_opt frame_opt */ - -6, /* (292) window ::= nm PARTITION BY nexprlist orderby_opt frame_opt */ - -4, /* (293) window ::= ORDER BY sortlist frame_opt */ - -5, /* (294) window ::= nm ORDER BY sortlist frame_opt */ - -1, /* (295) window ::= frame_opt */ - -2, /* (296) window ::= nm frame_opt */ - 0, /* (297) frame_opt ::= */ - -3, /* (298) frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt */ - -6, /* (299) frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt */ - -1, /* (300) range_or_rows ::= RANGE|ROWS|GROUPS */ - -1, /* (301) frame_bound_s ::= frame_bound */ - -2, /* (302) frame_bound_s ::= UNBOUNDED PRECEDING */ - -1, /* (303) frame_bound_e ::= frame_bound */ - -2, /* (304) frame_bound_e ::= UNBOUNDED FOLLOWING */ - -2, /* (305) frame_bound ::= expr PRECEDING|FOLLOWING */ - -2, /* (306) frame_bound ::= CURRENT ROW */ - 0, /* (307) frame_exclude_opt ::= */ - -2, /* (308) frame_exclude_opt ::= EXCLUDE frame_exclude */ - -2, /* (309) frame_exclude ::= NO OTHERS */ - -2, /* (310) frame_exclude ::= CURRENT ROW */ - -1, /* (311) frame_exclude ::= GROUP|TIES */ - -2, /* (312) window_clause ::= WINDOW windowdefn_list */ - -5, /* (313) over_clause ::= filter_opt OVER LP window RP */ - -3, /* (314) over_clause ::= filter_opt OVER nm */ - 0, /* (315) filter_opt ::= */ - -5, /* (316) filter_opt ::= FILTER LP WHERE expr RP */ - -1, /* (317) input ::= cmdlist */ - -2, /* (318) cmdlist ::= cmdlist ecmd */ - -1, /* (319) cmdlist ::= ecmd */ - -1, /* (320) ecmd ::= SEMI */ - -2, /* (321) ecmd ::= cmdx SEMI */ - -2, /* (322) ecmd ::= explain cmdx */ - 0, /* (323) trans_opt ::= */ - -1, /* (324) trans_opt ::= TRANSACTION */ - -2, /* (325) trans_opt ::= TRANSACTION nm */ - -1, /* (326) savepoint_opt ::= SAVEPOINT */ - 0, /* (327) savepoint_opt ::= */ - -2, /* (328) cmd ::= create_table create_table_args */ - -4, /* (329) columnlist ::= columnlist COMMA columnname carglist */ - -2, /* (330) columnlist ::= columnname carglist */ - -1, /* (331) nm ::= ID|INDEXED */ - -1, /* (332) nm ::= STRING */ - -1, /* (333) nm ::= JOIN_KW */ - -1, /* (334) typetoken ::= typename */ - -1, /* (335) typename ::= ID|STRING */ - -1, /* (336) signed ::= plus_num */ - -1, /* (337) signed ::= minus_num */ - -2, /* (338) carglist ::= carglist ccons */ - 0, /* (339) carglist ::= */ - -2, /* (340) ccons ::= NULL onconf */ - -2, /* (341) conslist_opt ::= COMMA conslist */ - -3, /* (342) conslist ::= conslist tconscomma tcons */ - -1, /* (343) conslist ::= tcons */ - 0, /* (344) tconscomma ::= */ - -1, /* (345) defer_subclause_opt ::= defer_subclause */ - -1, /* (346) resolvetype ::= raisetype */ - -1, /* (347) selectnowith ::= oneselect */ - -1, /* (348) oneselect ::= values */ - -2, /* (349) sclp ::= selcollist COMMA */ - -1, /* (350) as ::= ID|STRING */ - -1, /* (351) expr ::= term */ - -1, /* (352) likeop ::= LIKE_KW|MATCH */ - -1, /* (353) exprlist ::= nexprlist */ - -1, /* (354) nmnum ::= plus_num */ - -1, /* (355) nmnum ::= nm */ - -1, /* (356) nmnum ::= ON */ - -1, /* (357) nmnum ::= DELETE */ - -1, /* (358) nmnum ::= DEFAULT */ - -1, /* (359) plus_num ::= INTEGER|FLOAT */ - 0, /* (360) foreach_clause ::= */ - -3, /* (361) foreach_clause ::= FOR EACH ROW */ - -1, /* (362) trnm ::= nm */ - 0, /* (363) tridxby ::= */ - -1, /* (364) database_kw_opt ::= DATABASE */ - 0, /* (365) database_kw_opt ::= */ - 0, /* (366) kwcolumn_opt ::= */ - -1, /* (367) kwcolumn_opt ::= COLUMNKW */ - -1, /* (368) vtabarglist ::= vtabarg */ - -3, /* (369) vtabarglist ::= vtabarglist COMMA vtabarg */ - -2, /* (370) vtabarg ::= vtabarg vtabargtoken */ - 0, /* (371) anylist ::= */ - -4, /* (372) anylist ::= anylist LP anylist RP */ - -2, /* (373) anylist ::= anylist ANY */ - 0, /* (374) with ::= */ + 0, /* (21) table_option_set ::= */ + -3, /* (22) table_option_set ::= table_option_set COMMA table_option */ + -2, /* (23) table_option ::= WITHOUT nm */ + -1, /* (24) table_option ::= nm */ + -2, /* (25) columnname ::= nm typetoken */ + 0, /* (26) typetoken ::= */ + -4, /* (27) typetoken ::= typename LP signed RP */ + -6, /* (28) typetoken ::= typename LP signed COMMA signed RP */ + -2, /* (29) typename ::= typename ID|STRING */ + 0, /* (30) scanpt ::= */ + 0, /* (31) scantok ::= */ + -2, /* (32) ccons ::= CONSTRAINT nm */ + -3, /* (33) ccons ::= DEFAULT scantok term */ + -4, /* (34) ccons ::= DEFAULT LP expr RP */ + -4, /* (35) ccons ::= DEFAULT PLUS scantok term */ + -4, /* (36) ccons ::= DEFAULT MINUS scantok term */ + -3, /* (37) ccons ::= DEFAULT scantok ID|INDEXED */ + -3, /* (38) ccons ::= NOT NULL onconf */ + -5, /* (39) ccons ::= PRIMARY KEY sortorder onconf autoinc */ + -2, /* (40) ccons ::= UNIQUE onconf */ + -4, /* (41) ccons ::= CHECK LP expr RP */ + -4, /* (42) ccons ::= REFERENCES nm eidlist_opt refargs */ + -1, /* (43) ccons ::= defer_subclause */ + -2, /* (44) ccons ::= COLLATE ID|STRING */ + -3, /* (45) generated ::= LP expr RP */ + -4, /* (46) generated ::= LP expr RP ID */ + 0, /* (47) autoinc ::= */ + -1, /* (48) autoinc ::= AUTOINCR */ + 0, /* (49) refargs ::= */ + -2, /* (50) refargs ::= refargs refarg */ + -2, /* (51) refarg ::= MATCH nm */ + -3, /* (52) refarg ::= ON INSERT refact */ + -3, /* (53) refarg ::= ON DELETE refact */ + -3, /* (54) refarg ::= ON UPDATE refact */ + -2, /* (55) refact ::= SET NULL */ + -2, /* (56) refact ::= SET DEFAULT */ + -1, /* (57) refact ::= CASCADE */ + -1, /* (58) refact ::= RESTRICT */ + -2, /* (59) refact ::= NO ACTION */ + -3, /* (60) defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt */ + -2, /* (61) defer_subclause ::= DEFERRABLE init_deferred_pred_opt */ + 0, /* (62) init_deferred_pred_opt ::= */ + -2, /* (63) init_deferred_pred_opt ::= INITIALLY DEFERRED */ + -2, /* (64) init_deferred_pred_opt ::= INITIALLY IMMEDIATE */ + 0, /* (65) conslist_opt ::= */ + -1, /* (66) tconscomma ::= COMMA */ + -2, /* (67) tcons ::= CONSTRAINT nm */ + -7, /* (68) tcons ::= PRIMARY KEY LP sortlist autoinc RP onconf */ + -5, /* (69) tcons ::= UNIQUE LP sortlist RP onconf */ + -5, /* (70) tcons ::= CHECK LP expr RP onconf */ + -10, /* (71) tcons ::= FOREIGN KEY LP eidlist RP REFERENCES nm eidlist_opt refargs defer_subclause_opt */ + 0, /* (72) defer_subclause_opt ::= */ + 0, /* (73) onconf ::= */ + -3, /* (74) onconf ::= ON CONFLICT resolvetype */ + 0, /* (75) orconf ::= */ + -2, /* (76) orconf ::= OR resolvetype */ + -1, /* (77) resolvetype ::= IGNORE */ + -1, /* (78) resolvetype ::= REPLACE */ + -4, /* (79) cmd ::= DROP TABLE ifexists fullname */ + -2, /* (80) ifexists ::= IF EXISTS */ + 0, /* (81) ifexists ::= */ + -9, /* (82) cmd ::= createkw temp VIEW ifnotexists nm dbnm eidlist_opt AS select */ + -4, /* (83) cmd ::= DROP VIEW ifexists fullname */ + -1, /* (84) cmd ::= select */ + -3, /* (85) select ::= WITH wqlist selectnowith */ + -4, /* (86) select ::= WITH RECURSIVE wqlist selectnowith */ + -1, /* (87) select ::= selectnowith */ + -3, /* (88) selectnowith ::= selectnowith multiselect_op oneselect */ + -1, /* (89) multiselect_op ::= UNION */ + -2, /* (90) multiselect_op ::= UNION ALL */ + -1, /* (91) multiselect_op ::= EXCEPT|INTERSECT */ + -9, /* (92) oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt */ + -10, /* (93) oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt window_clause orderby_opt limit_opt */ + -4, /* (94) values ::= VALUES LP nexprlist RP */ + -1, /* (95) oneselect ::= mvalues */ + -5, /* (96) mvalues ::= values COMMA LP nexprlist RP */ + -5, /* (97) mvalues ::= mvalues COMMA LP nexprlist RP */ + -1, /* (98) distinct ::= DISTINCT */ + -1, /* (99) distinct ::= ALL */ + 0, /* (100) distinct ::= */ + 0, /* (101) sclp ::= */ + -5, /* (102) selcollist ::= sclp scanpt expr scanpt as */ + -3, /* (103) selcollist ::= sclp scanpt STAR */ + -5, /* (104) selcollist ::= sclp scanpt nm DOT STAR */ + -2, /* (105) as ::= AS nm */ + 0, /* (106) as ::= */ + 0, /* (107) from ::= */ + -2, /* (108) from ::= FROM seltablist */ + -2, /* (109) stl_prefix ::= seltablist joinop */ + 0, /* (110) stl_prefix ::= */ + -5, /* (111) seltablist ::= stl_prefix nm dbnm as on_using */ + -6, /* (112) seltablist ::= stl_prefix nm dbnm as indexed_by on_using */ + -8, /* (113) seltablist ::= stl_prefix nm dbnm LP exprlist RP as on_using */ + -6, /* (114) seltablist ::= stl_prefix LP select RP as on_using */ + -6, /* (115) seltablist ::= stl_prefix LP seltablist RP as on_using */ + 0, /* (116) dbnm ::= */ + -2, /* (117) dbnm ::= DOT nm */ + -1, /* (118) fullname ::= nm */ + -3, /* (119) fullname ::= nm DOT nm */ + -1, /* (120) xfullname ::= nm */ + -3, /* (121) xfullname ::= nm DOT nm */ + -3, /* (122) xfullname ::= nm AS nm */ + -5, /* (123) xfullname ::= nm DOT nm AS nm */ + -1, /* (124) joinop ::= COMMA|JOIN */ + -2, /* (125) joinop ::= JOIN_KW JOIN */ + -3, /* (126) joinop ::= JOIN_KW nm JOIN */ + -4, /* (127) joinop ::= JOIN_KW nm nm JOIN */ + -2, /* (128) on_using ::= ON expr */ + -4, /* (129) on_using ::= USING LP idlist RP */ + 0, /* (130) on_using ::= */ + 0, /* (131) indexed_opt ::= */ + -3, /* (132) indexed_by ::= INDEXED BY nm */ + -2, /* (133) indexed_by ::= NOT INDEXED */ + 0, /* (134) orderby_opt ::= */ + -3, /* (135) orderby_opt ::= ORDER BY sortlist */ + -5, /* (136) sortlist ::= sortlist COMMA expr sortorder nulls */ + -3, /* (137) sortlist ::= expr sortorder nulls */ + -1, /* (138) sortorder ::= ASC */ + -1, /* (139) sortorder ::= DESC */ + 0, /* (140) sortorder ::= */ + -2, /* (141) nulls ::= NULLS FIRST */ + -2, /* (142) nulls ::= NULLS LAST */ + 0, /* (143) nulls ::= */ + 0, /* (144) groupby_opt ::= */ + -3, /* (145) groupby_opt ::= GROUP BY nexprlist */ + 0, /* (146) having_opt ::= */ + -2, /* (147) having_opt ::= HAVING expr */ + 0, /* (148) limit_opt ::= */ + -2, /* (149) limit_opt ::= LIMIT expr */ + -4, /* (150) limit_opt ::= LIMIT expr OFFSET expr */ + -4, /* (151) limit_opt ::= LIMIT expr COMMA expr */ + -6, /* (152) cmd ::= with DELETE FROM xfullname indexed_opt where_opt_ret */ + 0, /* (153) where_opt ::= */ + -2, /* (154) where_opt ::= WHERE expr */ + 0, /* (155) where_opt_ret ::= */ + -2, /* (156) where_opt_ret ::= WHERE expr */ + -2, /* (157) where_opt_ret ::= RETURNING selcollist */ + -4, /* (158) where_opt_ret ::= WHERE expr RETURNING selcollist */ + -9, /* (159) cmd ::= with UPDATE orconf xfullname indexed_opt SET setlist from where_opt_ret */ + -5, /* (160) setlist ::= setlist COMMA nm EQ expr */ + -7, /* (161) setlist ::= setlist COMMA LP idlist RP EQ expr */ + -3, /* (162) setlist ::= nm EQ expr */ + -5, /* (163) setlist ::= LP idlist RP EQ expr */ + -7, /* (164) cmd ::= with insert_cmd INTO xfullname idlist_opt select upsert */ + -8, /* (165) cmd ::= with insert_cmd INTO xfullname idlist_opt DEFAULT VALUES returning */ + 0, /* (166) upsert ::= */ + -2, /* (167) upsert ::= RETURNING selcollist */ + -12, /* (168) upsert ::= ON CONFLICT LP sortlist RP where_opt DO UPDATE SET setlist where_opt upsert */ + -9, /* (169) upsert ::= ON CONFLICT LP sortlist RP where_opt DO NOTHING upsert */ + -5, /* (170) upsert ::= ON CONFLICT DO NOTHING returning */ + -8, /* (171) upsert ::= ON CONFLICT DO UPDATE SET setlist where_opt returning */ + -2, /* (172) returning ::= RETURNING selcollist */ + -2, /* (173) insert_cmd ::= INSERT orconf */ + -1, /* (174) insert_cmd ::= REPLACE */ + 0, /* (175) idlist_opt ::= */ + -3, /* (176) idlist_opt ::= LP idlist RP */ + -3, /* (177) idlist ::= idlist COMMA nm */ + -1, /* (178) idlist ::= nm */ + -3, /* (179) expr ::= LP expr RP */ + -1, /* (180) expr ::= ID|INDEXED|JOIN_KW */ + -3, /* (181) expr ::= nm DOT nm */ + -5, /* (182) expr ::= nm DOT nm DOT nm */ + -1, /* (183) term ::= NULL|FLOAT|BLOB */ + -1, /* (184) term ::= STRING */ + -1, /* (185) term ::= INTEGER */ + -1, /* (186) expr ::= VARIABLE */ + -3, /* (187) expr ::= expr COLLATE ID|STRING */ + -6, /* (188) expr ::= CAST LP expr AS typetoken RP */ + -5, /* (189) expr ::= ID|INDEXED|JOIN_KW LP distinct exprlist RP */ + -8, /* (190) expr ::= ID|INDEXED|JOIN_KW LP distinct exprlist ORDER BY sortlist RP */ + -4, /* (191) expr ::= ID|INDEXED|JOIN_KW LP STAR RP */ + -6, /* (192) expr ::= ID|INDEXED|JOIN_KW LP distinct exprlist RP filter_over */ + -9, /* (193) expr ::= ID|INDEXED|JOIN_KW LP distinct exprlist ORDER BY sortlist RP filter_over */ + -5, /* (194) expr ::= ID|INDEXED|JOIN_KW LP STAR RP filter_over */ + -1, /* (195) term ::= CTIME_KW */ + -5, /* (196) expr ::= LP nexprlist COMMA expr RP */ + -3, /* (197) expr ::= expr AND expr */ + -3, /* (198) expr ::= expr OR expr */ + -3, /* (199) expr ::= expr LT|GT|GE|LE expr */ + -3, /* (200) expr ::= expr EQ|NE expr */ + -3, /* (201) expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr */ + -3, /* (202) expr ::= expr PLUS|MINUS expr */ + -3, /* (203) expr ::= expr STAR|SLASH|REM expr */ + -3, /* (204) expr ::= expr CONCAT expr */ + -2, /* (205) likeop ::= NOT LIKE_KW|MATCH */ + -3, /* (206) expr ::= expr likeop expr */ + -5, /* (207) expr ::= expr likeop expr ESCAPE expr */ + -2, /* (208) expr ::= expr ISNULL|NOTNULL */ + -3, /* (209) expr ::= expr NOT NULL */ + -3, /* (210) expr ::= expr IS expr */ + -4, /* (211) expr ::= expr IS NOT expr */ + -6, /* (212) expr ::= expr IS NOT DISTINCT FROM expr */ + -5, /* (213) expr ::= expr IS DISTINCT FROM expr */ + -2, /* (214) expr ::= NOT expr */ + -2, /* (215) expr ::= BITNOT expr */ + -2, /* (216) expr ::= PLUS|MINUS expr */ + -3, /* (217) expr ::= expr PTR expr */ + -1, /* (218) between_op ::= BETWEEN */ + -2, /* (219) between_op ::= NOT BETWEEN */ + -5, /* (220) expr ::= expr between_op expr AND expr */ + -1, /* (221) in_op ::= IN */ + -2, /* (222) in_op ::= NOT IN */ + -5, /* (223) expr ::= expr in_op LP exprlist RP */ + -3, /* (224) expr ::= LP select RP */ + -5, /* (225) expr ::= expr in_op LP select RP */ + -5, /* (226) expr ::= expr in_op nm dbnm paren_exprlist */ + -4, /* (227) expr ::= EXISTS LP select RP */ + -5, /* (228) expr ::= CASE case_operand case_exprlist case_else END */ + -5, /* (229) case_exprlist ::= case_exprlist WHEN expr THEN expr */ + -4, /* (230) case_exprlist ::= WHEN expr THEN expr */ + -2, /* (231) case_else ::= ELSE expr */ + 0, /* (232) case_else ::= */ + 0, /* (233) case_operand ::= */ + 0, /* (234) exprlist ::= */ + -3, /* (235) nexprlist ::= nexprlist COMMA expr */ + -1, /* (236) nexprlist ::= expr */ + 0, /* (237) paren_exprlist ::= */ + -3, /* (238) paren_exprlist ::= LP exprlist RP */ + -12, /* (239) cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt */ + -1, /* (240) uniqueflag ::= UNIQUE */ + 0, /* (241) uniqueflag ::= */ + 0, /* (242) eidlist_opt ::= */ + -3, /* (243) eidlist_opt ::= LP eidlist RP */ + -5, /* (244) eidlist ::= eidlist COMMA nm collate sortorder */ + -3, /* (245) eidlist ::= nm collate sortorder */ + 0, /* (246) collate ::= */ + -2, /* (247) collate ::= COLLATE ID|STRING */ + -4, /* (248) cmd ::= DROP INDEX ifexists fullname */ + -2, /* (249) cmd ::= VACUUM vinto */ + -3, /* (250) cmd ::= VACUUM nm vinto */ + -2, /* (251) vinto ::= INTO expr */ + 0, /* (252) vinto ::= */ + -3, /* (253) cmd ::= PRAGMA nm dbnm */ + -5, /* (254) cmd ::= PRAGMA nm dbnm EQ nmnum */ + -6, /* (255) cmd ::= PRAGMA nm dbnm LP nmnum RP */ + -5, /* (256) cmd ::= PRAGMA nm dbnm EQ minus_num */ + -6, /* (257) cmd ::= PRAGMA nm dbnm LP minus_num RP */ + -2, /* (258) plus_num ::= PLUS INTEGER|FLOAT */ + -2, /* (259) minus_num ::= MINUS INTEGER|FLOAT */ + -5, /* (260) cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END */ + -11, /* (261) trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause */ + -1, /* (262) trigger_time ::= BEFORE|AFTER */ + -2, /* (263) trigger_time ::= INSTEAD OF */ + 0, /* (264) trigger_time ::= */ + -1, /* (265) trigger_event ::= DELETE|INSERT */ + -1, /* (266) trigger_event ::= UPDATE */ + -3, /* (267) trigger_event ::= UPDATE OF idlist */ + 0, /* (268) when_clause ::= */ + -2, /* (269) when_clause ::= WHEN expr */ + -3, /* (270) trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI */ + -2, /* (271) trigger_cmd_list ::= trigger_cmd SEMI */ + -3, /* (272) tridxby ::= INDEXED BY nm */ + -2, /* (273) tridxby ::= NOT INDEXED */ + -9, /* (274) trigger_cmd ::= UPDATE orconf xfullname tridxby SET setlist from where_opt scanpt */ + -8, /* (275) trigger_cmd ::= scanpt insert_cmd INTO xfullname idlist_opt select upsert scanpt */ + -6, /* (276) trigger_cmd ::= DELETE FROM xfullname tridxby where_opt scanpt */ + -3, /* (277) trigger_cmd ::= scanpt select scanpt */ + -4, /* (278) expr ::= RAISE LP IGNORE RP */ + -6, /* (279) expr ::= RAISE LP raisetype COMMA expr RP */ + -1, /* (280) raisetype ::= ROLLBACK */ + -1, /* (281) raisetype ::= ABORT */ + -1, /* (282) raisetype ::= FAIL */ + -4, /* (283) cmd ::= DROP TRIGGER ifexists fullname */ + -6, /* (284) cmd ::= ATTACH database_kw_opt expr AS expr key_opt */ + -3, /* (285) cmd ::= DETACH database_kw_opt expr */ + 0, /* (286) key_opt ::= */ + -2, /* (287) key_opt ::= KEY expr */ + -1, /* (288) cmd ::= REINDEX */ + -3, /* (289) cmd ::= REINDEX nm dbnm */ + -1, /* (290) cmd ::= ANALYZE */ + -3, /* (291) cmd ::= ANALYZE nm dbnm */ + -6, /* (292) cmd ::= ALTER TABLE fullname RENAME TO nm */ + -2, /* (293) cmd ::= alter_add carglist */ + -7, /* (294) alter_add ::= ALTER TABLE fullname ADD kwcolumn_opt nm typetoken */ + -6, /* (295) cmd ::= ALTER TABLE fullname DROP kwcolumn_opt nm */ + -8, /* (296) cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm */ + -6, /* (297) cmd ::= ALTER TABLE fullname DROP CONSTRAINT nm */ + -9, /* (298) cmd ::= ALTER TABLE fullname ALTER kwcolumn_opt nm DROP NOT NULL */ + -10, /* (299) cmd ::= ALTER TABLE fullname ALTER kwcolumn_opt nm SET NOT NULL onconf */ + -11, /* (300) cmd ::= ALTER TABLE fullname ADD CONSTRAINT nm CHECK LP expr RP onconf */ + -9, /* (301) cmd ::= ALTER TABLE fullname ADD CHECK LP expr RP onconf */ + -1, /* (302) cmd ::= create_vtab */ + -4, /* (303) cmd ::= create_vtab LP vtabarglist RP */ + -8, /* (304) create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm */ + 0, /* (305) vtabarg ::= */ + -1, /* (306) vtabargtoken ::= ANY */ + -3, /* (307) vtabargtoken ::= lp anylist RP */ + -1, /* (308) lp ::= LP */ + -2, /* (309) with ::= WITH wqlist */ + -3, /* (310) with ::= WITH RECURSIVE wqlist */ + -1, /* (311) wqas ::= AS */ + -2, /* (312) wqas ::= AS MATERIALIZED */ + -3, /* (313) wqas ::= AS NOT MATERIALIZED */ + -6, /* (314) wqitem ::= withnm eidlist_opt wqas LP select RP */ + -1, /* (315) withnm ::= nm */ + -1, /* (316) wqlist ::= wqitem */ + -3, /* (317) wqlist ::= wqlist COMMA wqitem */ + -3, /* (318) windowdefn_list ::= windowdefn_list COMMA windowdefn */ + -5, /* (319) windowdefn ::= nm AS LP window RP */ + -5, /* (320) window ::= PARTITION BY nexprlist orderby_opt frame_opt */ + -6, /* (321) window ::= nm PARTITION BY nexprlist orderby_opt frame_opt */ + -4, /* (322) window ::= ORDER BY sortlist frame_opt */ + -5, /* (323) window ::= nm ORDER BY sortlist frame_opt */ + -2, /* (324) window ::= nm frame_opt */ + 0, /* (325) frame_opt ::= */ + -3, /* (326) frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt */ + -6, /* (327) frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt */ + -1, /* (328) range_or_rows ::= RANGE|ROWS|GROUPS */ + -1, /* (329) frame_bound_s ::= frame_bound */ + -2, /* (330) frame_bound_s ::= UNBOUNDED PRECEDING */ + -1, /* (331) frame_bound_e ::= frame_bound */ + -2, /* (332) frame_bound_e ::= UNBOUNDED FOLLOWING */ + -2, /* (333) frame_bound ::= expr PRECEDING|FOLLOWING */ + -2, /* (334) frame_bound ::= CURRENT ROW */ + 0, /* (335) frame_exclude_opt ::= */ + -2, /* (336) frame_exclude_opt ::= EXCLUDE frame_exclude */ + -2, /* (337) frame_exclude ::= NO OTHERS */ + -2, /* (338) frame_exclude ::= CURRENT ROW */ + -1, /* (339) frame_exclude ::= GROUP|TIES */ + -2, /* (340) window_clause ::= WINDOW windowdefn_list */ + -2, /* (341) filter_over ::= filter_clause over_clause */ + -1, /* (342) filter_over ::= over_clause */ + -1, /* (343) filter_over ::= filter_clause */ + -4, /* (344) over_clause ::= OVER LP window RP */ + -2, /* (345) over_clause ::= OVER nm */ + -5, /* (346) filter_clause ::= FILTER LP WHERE expr RP */ + -1, /* (347) term ::= QNUMBER */ + -1, /* (348) input ::= cmdlist */ + -2, /* (349) cmdlist ::= cmdlist ecmd */ + -1, /* (350) cmdlist ::= ecmd */ + -1, /* (351) ecmd ::= SEMI */ + -2, /* (352) ecmd ::= cmdx SEMI */ + -3, /* (353) ecmd ::= explain cmdx SEMI */ + 0, /* (354) trans_opt ::= */ + -1, /* (355) trans_opt ::= TRANSACTION */ + -2, /* (356) trans_opt ::= TRANSACTION nm */ + -1, /* (357) savepoint_opt ::= SAVEPOINT */ + 0, /* (358) savepoint_opt ::= */ + -2, /* (359) cmd ::= create_table create_table_args */ + -1, /* (360) table_option_set ::= table_option */ + -4, /* (361) columnlist ::= columnlist COMMA columnname carglist */ + -2, /* (362) columnlist ::= columnname carglist */ + -1, /* (363) nm ::= ID|INDEXED|JOIN_KW */ + -1, /* (364) nm ::= STRING */ + -1, /* (365) typetoken ::= typename */ + -1, /* (366) typename ::= ID|STRING */ + -1, /* (367) signed ::= plus_num */ + -1, /* (368) signed ::= minus_num */ + -2, /* (369) carglist ::= carglist ccons */ + 0, /* (370) carglist ::= */ + -2, /* (371) ccons ::= NULL onconf */ + -4, /* (372) ccons ::= GENERATED ALWAYS AS generated */ + -2, /* (373) ccons ::= AS generated */ + -2, /* (374) conslist_opt ::= COMMA conslist */ + -3, /* (375) conslist ::= conslist tconscomma tcons */ + -1, /* (376) conslist ::= tcons */ + 0, /* (377) tconscomma ::= */ + -1, /* (378) defer_subclause_opt ::= defer_subclause */ + -1, /* (379) resolvetype ::= raisetype */ + -1, /* (380) selectnowith ::= oneselect */ + -1, /* (381) oneselect ::= values */ + -2, /* (382) sclp ::= selcollist COMMA */ + -1, /* (383) as ::= ID|STRING */ + -1, /* (384) indexed_opt ::= indexed_by */ + 0, /* (385) returning ::= */ + -1, /* (386) expr ::= term */ + -1, /* (387) likeop ::= LIKE_KW|MATCH */ + -1, /* (388) case_operand ::= expr */ + -1, /* (389) exprlist ::= nexprlist */ + -1, /* (390) nmnum ::= plus_num */ + -1, /* (391) nmnum ::= nm */ + -1, /* (392) nmnum ::= ON */ + -1, /* (393) nmnum ::= DELETE */ + -1, /* (394) nmnum ::= DEFAULT */ + -1, /* (395) plus_num ::= INTEGER|FLOAT */ + 0, /* (396) foreach_clause ::= */ + -3, /* (397) foreach_clause ::= FOR EACH ROW */ + 0, /* (398) tridxby ::= */ + -1, /* (399) database_kw_opt ::= DATABASE */ + 0, /* (400) database_kw_opt ::= */ + 0, /* (401) kwcolumn_opt ::= */ + -1, /* (402) kwcolumn_opt ::= COLUMNKW */ + -1, /* (403) vtabarglist ::= vtabarg */ + -3, /* (404) vtabarglist ::= vtabarglist COMMA vtabarg */ + -2, /* (405) vtabarg ::= vtabarg vtabargtoken */ + 0, /* (406) anylist ::= */ + -4, /* (407) anylist ::= anylist LP anylist RP */ + -2, /* (408) anylist ::= anylist ANY */ + 0, /* (409) with ::= */ + -1, /* (410) windowdefn_list ::= windowdefn */ + -1, /* (411) window ::= frame_opt */ }; static void yy_accept(yyParser*); /* Forward Declaration */ @@ -151254,51 +183641,6 @@ static YYACTIONTYPE yy_reduce( (void)yyLookahead; (void)yyLookaheadToken; yymsp = yypParser->yytos; -#ifndef NDEBUG - if( yyTraceFILE && yyruleno<(int)(sizeof(yyRuleName)/sizeof(yyRuleName[0])) ){ - yysize = yyRuleInfoNRhs[yyruleno]; - if( yysize ){ - fprintf(yyTraceFILE, "%sReduce %d [%s], go to state %d.\n", - yyTracePrompt, - yyruleno, yyRuleName[yyruleno], yymsp[yysize].stateno); - }else{ - fprintf(yyTraceFILE, "%sReduce %d [%s].\n", - yyTracePrompt, yyruleno, yyRuleName[yyruleno]); - } - } -#endif /* NDEBUG */ - - /* Check that the stack is large enough to grow by a single entry - ** if the RHS of the rule is empty. This ensures that there is room - ** enough on the stack to push the LHS value */ - if( yyRuleInfoNRhs[yyruleno]==0 ){ -#ifdef YYTRACKMAXSTACKDEPTH - if( (int)(yypParser->yytos - yypParser->yystack)>yypParser->yyhwm ){ - yypParser->yyhwm++; - assert( yypParser->yyhwm == (int)(yypParser->yytos - yypParser->yystack)); - } -#endif -#if YYSTACKDEPTH>0 - if( yypParser->yytos>=yypParser->yystackEnd ){ - yyStackOverflow(yypParser); - /* The call to yyStackOverflow() above pops the stack until it is - ** empty, causing the main parser loop to exit. So the return value - ** is never used and does not matter. */ - return 0; - } -#else - if( yypParser->yytos>=&yypParser->yystack[yypParser->yystksz-1] ){ - if( yyGrowStack(yypParser) ){ - yyStackOverflow(yypParser); - /* The call to yyStackOverflow() above pops the stack until it is - ** empty, causing the main parser loop to exit. So the return value - ** is never used and does not matter. */ - return 0; - } - yymsp = yypParser->yytos; - } -#endif - } switch( yyruleno ){ /* Beginning here are the reduction cases. A typical example @@ -151312,25 +183654,25 @@ static YYACTIONTYPE yy_reduce( /********** Begin reduce actions **********************************************/ YYMINORTYPE yylhsminor; case 0: /* explain ::= EXPLAIN */ -{ pParse->explain = 1; } +{ if( pParse->pReprepare==0 ) pParse->explain = 1; } break; case 1: /* explain ::= EXPLAIN QUERY PLAN */ -{ pParse->explain = 2; } +{ if( pParse->pReprepare==0 ) pParse->explain = 2; } break; case 2: /* cmdx ::= cmd */ { sqlite3FinishCoding(pParse); } break; case 3: /* cmd ::= BEGIN transtype trans_opt */ -{sqlite3BeginTransaction(pParse, yymsp[-1].minor.yy494);} +{sqlite3BeginTransaction(pParse, yymsp[-1].minor.yy144);} break; case 4: /* transtype ::= */ -{yymsp[1].minor.yy494 = TK_DEFERRED;} +{yymsp[1].minor.yy144 = TK_DEFERRED;} break; case 5: /* transtype ::= DEFERRED */ case 6: /* transtype ::= IMMEDIATE */ yytestcase(yyruleno==6); case 7: /* transtype ::= EXCLUSIVE */ yytestcase(yyruleno==7); - case 300: /* range_or_rows ::= RANGE|ROWS|GROUPS */ yytestcase(yyruleno==300); -{yymsp[0].minor.yy494 = yymsp[0].major; /*A-overwrites-X*/} + case 328: /* range_or_rows ::= RANGE|ROWS|GROUPS */ yytestcase(yyruleno==328); +{yymsp[0].minor.yy144 = yymsp[0].major; /*A-overwrites-X*/} break; case 8: /* cmd ::= COMMIT|END trans_opt */ case 9: /* cmd ::= ROLLBACK trans_opt */ yytestcase(yyruleno==9); @@ -151353,98 +183695,122 @@ static YYACTIONTYPE yy_reduce( break; case 13: /* create_table ::= createkw temp TABLE ifnotexists nm dbnm */ { - sqlite3StartTable(pParse,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0,yymsp[-4].minor.yy494,0,0,yymsp[-2].minor.yy494); + sqlite3StartTable(pParse,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0,yymsp[-4].minor.yy144,0,0,yymsp[-2].minor.yy144); } break; case 14: /* createkw ::= CREATE */ -{disableLookaside(pParse);} +{ + disableLookaside(pParse); +} break; case 15: /* ifnotexists ::= */ case 18: /* temp ::= */ yytestcase(yyruleno==18); - case 21: /* table_options ::= */ yytestcase(yyruleno==21); - case 42: /* autoinc ::= */ yytestcase(yyruleno==42); - case 57: /* init_deferred_pred_opt ::= */ yytestcase(yyruleno==57); - case 67: /* defer_subclause_opt ::= */ yytestcase(yyruleno==67); - case 76: /* ifexists ::= */ yytestcase(yyruleno==76); - case 93: /* distinct ::= */ yytestcase(yyruleno==93); - case 226: /* collate ::= */ yytestcase(yyruleno==226); -{yymsp[1].minor.yy494 = 0;} + case 47: /* autoinc ::= */ yytestcase(yyruleno==47); + case 62: /* init_deferred_pred_opt ::= */ yytestcase(yyruleno==62); + case 72: /* defer_subclause_opt ::= */ yytestcase(yyruleno==72); + case 81: /* ifexists ::= */ yytestcase(yyruleno==81); + case 100: /* distinct ::= */ yytestcase(yyruleno==100); + case 246: /* collate ::= */ yytestcase(yyruleno==246); +{yymsp[1].minor.yy144 = 0;} break; case 16: /* ifnotexists ::= IF NOT EXISTS */ -{yymsp[-2].minor.yy494 = 1;} +{yymsp[-2].minor.yy144 = 1;} break; case 17: /* temp ::= TEMP */ - case 43: /* autoinc ::= AUTOINCR */ yytestcase(yyruleno==43); -{yymsp[0].minor.yy494 = 1;} +{yymsp[0].minor.yy144 = pParse->db->init.busy==0;} break; - case 19: /* create_table_args ::= LP columnlist conslist_opt RP table_options */ + case 19: /* create_table_args ::= LP columnlist conslist_opt RP table_option_set */ { - sqlite3EndTable(pParse,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0,yymsp[0].minor.yy494,0); + sqlite3EndTable(pParse,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0,yymsp[0].minor.yy391,0); } break; case 20: /* create_table_args ::= AS select */ { - sqlite3EndTable(pParse,0,0,0,yymsp[0].minor.yy457); - sqlite3SelectDelete(pParse->db, yymsp[0].minor.yy457); + sqlite3EndTable(pParse,0,0,0,yymsp[0].minor.yy555); + sqlite3SelectDelete(pParse->db, yymsp[0].minor.yy555); } break; - case 22: /* table_options ::= WITHOUT nm */ + case 21: /* table_option_set ::= */ +{yymsp[1].minor.yy391 = 0;} + break; + case 22: /* table_option_set ::= table_option_set COMMA table_option */ +{yylhsminor.yy391 = yymsp[-2].minor.yy391|yymsp[0].minor.yy391;} + yymsp[-2].minor.yy391 = yylhsminor.yy391; + break; + case 23: /* table_option ::= WITHOUT nm */ { if( yymsp[0].minor.yy0.n==5 && sqlite3_strnicmp(yymsp[0].minor.yy0.z,"rowid",5)==0 ){ - yymsp[-1].minor.yy494 = TF_WithoutRowid | TF_NoVisibleRowid; + yymsp[-1].minor.yy391 = TF_WithoutRowid | TF_NoVisibleRowid; }else{ - yymsp[-1].minor.yy494 = 0; + yymsp[-1].minor.yy391 = 0; sqlite3ErrorMsg(pParse, "unknown table option: %.*s", yymsp[0].minor.yy0.n, yymsp[0].minor.yy0.z); } } break; - case 23: /* columnname ::= nm typetoken */ -{sqlite3AddColumn(pParse,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0);} + case 24: /* table_option ::= nm */ +{ + if( yymsp[0].minor.yy0.n==6 && sqlite3_strnicmp(yymsp[0].minor.yy0.z,"strict",6)==0 ){ + yylhsminor.yy391 = TF_Strict; + }else{ + yylhsminor.yy391 = 0; + sqlite3ErrorMsg(pParse, "unknown table option: %.*s", yymsp[0].minor.yy0.n, yymsp[0].minor.yy0.z); + } +} + yymsp[0].minor.yy391 = yylhsminor.yy391; break; - case 24: /* typetoken ::= */ - case 60: /* conslist_opt ::= */ yytestcase(yyruleno==60); - case 99: /* as ::= */ yytestcase(yyruleno==99); + case 25: /* columnname ::= nm typetoken */ +{sqlite3AddColumn(pParse,yymsp[-1].minor.yy0,yymsp[0].minor.yy0);} + break; + case 26: /* typetoken ::= */ + case 65: /* conslist_opt ::= */ yytestcase(yyruleno==65); + case 106: /* as ::= */ yytestcase(yyruleno==106); {yymsp[1].minor.yy0.n = 0; yymsp[1].minor.yy0.z = 0;} break; - case 25: /* typetoken ::= typename LP signed RP */ + case 27: /* typetoken ::= typename LP signed RP */ { yymsp[-3].minor.yy0.n = (int)(&yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n] - yymsp[-3].minor.yy0.z); } break; - case 26: /* typetoken ::= typename LP signed COMMA signed RP */ + case 28: /* typetoken ::= typename LP signed COMMA signed RP */ { yymsp[-5].minor.yy0.n = (int)(&yymsp[0].minor.yy0.z[yymsp[0].minor.yy0.n] - yymsp[-5].minor.yy0.z); } break; - case 27: /* typename ::= typename ID|STRING */ + case 29: /* typename ::= typename ID|STRING */ {yymsp[-1].minor.yy0.n=yymsp[0].minor.yy0.n+(int)(yymsp[0].minor.yy0.z-yymsp[-1].minor.yy0.z);} break; - case 28: /* scanpt ::= */ + case 30: /* scanpt ::= */ { assert( yyLookahead!=YYNOCODE ); - yymsp[1].minor.yy294 = yyLookaheadToken.z; + yymsp[1].minor.yy168 = yyLookaheadToken.z; } break; - case 29: /* ccons ::= CONSTRAINT nm */ - case 62: /* tcons ::= CONSTRAINT nm */ yytestcase(yyruleno==62); -{pParse->constraintName = yymsp[0].minor.yy0;} + case 31: /* scantok ::= */ +{ + assert( yyLookahead!=YYNOCODE ); + yymsp[1].minor.yy0 = yyLookaheadToken; +} break; - case 30: /* ccons ::= DEFAULT scanpt term scanpt */ -{sqlite3AddDefaultValue(pParse,yymsp[-1].minor.yy524,yymsp[-2].minor.yy294,yymsp[0].minor.yy294);} + case 32: /* ccons ::= CONSTRAINT nm */ + case 67: /* tcons ::= CONSTRAINT nm */ yytestcase(yyruleno==67); +{ASSERT_IS_CREATE; pParse->u1.cr.constraintName = yymsp[0].minor.yy0;} break; - case 31: /* ccons ::= DEFAULT LP expr RP */ -{sqlite3AddDefaultValue(pParse,yymsp[-1].minor.yy524,yymsp[-2].minor.yy0.z+1,yymsp[0].minor.yy0.z);} + case 33: /* ccons ::= DEFAULT scantok term */ +{sqlite3AddDefaultValue(pParse,yymsp[0].minor.yy454,yymsp[-1].minor.yy0.z,&yymsp[-1].minor.yy0.z[yymsp[-1].minor.yy0.n]);} break; - case 32: /* ccons ::= DEFAULT PLUS term scanpt */ -{sqlite3AddDefaultValue(pParse,yymsp[-1].minor.yy524,yymsp[-2].minor.yy0.z,yymsp[0].minor.yy294);} + case 34: /* ccons ::= DEFAULT LP expr RP */ +{sqlite3AddDefaultValue(pParse,yymsp[-1].minor.yy454,yymsp[-2].minor.yy0.z+1,yymsp[0].minor.yy0.z);} break; - case 33: /* ccons ::= DEFAULT MINUS term scanpt */ + case 35: /* ccons ::= DEFAULT PLUS scantok term */ +{sqlite3AddDefaultValue(pParse,yymsp[0].minor.yy454,yymsp[-2].minor.yy0.z,&yymsp[-1].minor.yy0.z[yymsp[-1].minor.yy0.n]);} + break; + case 36: /* ccons ::= DEFAULT MINUS scantok term */ { - Expr *p = sqlite3PExpr(pParse, TK_UMINUS, yymsp[-1].minor.yy524, 0); - sqlite3AddDefaultValue(pParse,p,yymsp[-2].minor.yy0.z,yymsp[0].minor.yy294); + Expr *p = sqlite3PExpr(pParse, TK_UMINUS, yymsp[0].minor.yy454, 0); + sqlite3AddDefaultValue(pParse,p,yymsp[-2].minor.yy0.z,&yymsp[-1].minor.yy0.z[yymsp[-1].minor.yy0.n]); } break; - case 34: /* ccons ::= DEFAULT scanpt ID|INDEXED */ + case 37: /* ccons ::= DEFAULT scantok ID|INDEXED */ { Expr *p = tokenExpr(pParse, TK_STRING, yymsp[0].minor.yy0); if( p ){ @@ -151454,564 +183820,622 @@ static YYACTIONTYPE yy_reduce( sqlite3AddDefaultValue(pParse,p,yymsp[0].minor.yy0.z,yymsp[0].minor.yy0.z+yymsp[0].minor.yy0.n); } break; - case 35: /* ccons ::= NOT NULL onconf */ -{sqlite3AddNotNull(pParse, yymsp[0].minor.yy494);} + case 38: /* ccons ::= NOT NULL onconf */ +{sqlite3AddNotNull(pParse, yymsp[0].minor.yy144);} break; - case 36: /* ccons ::= PRIMARY KEY sortorder onconf autoinc */ -{sqlite3AddPrimaryKey(pParse,0,yymsp[-1].minor.yy494,yymsp[0].minor.yy494,yymsp[-2].minor.yy494);} + case 39: /* ccons ::= PRIMARY KEY sortorder onconf autoinc */ +{sqlite3AddPrimaryKey(pParse,0,yymsp[-1].minor.yy144,yymsp[0].minor.yy144,yymsp[-2].minor.yy144);} break; - case 37: /* ccons ::= UNIQUE onconf */ -{sqlite3CreateIndex(pParse,0,0,0,0,yymsp[0].minor.yy494,0,0,0,0, + case 40: /* ccons ::= UNIQUE onconf */ +{sqlite3CreateIndex(pParse,0,0,0,0,yymsp[0].minor.yy144,0,0,0,0, SQLITE_IDXTYPE_UNIQUE);} break; - case 38: /* ccons ::= CHECK LP expr RP */ -{sqlite3AddCheckConstraint(pParse,yymsp[-1].minor.yy524);} + case 41: /* ccons ::= CHECK LP expr RP */ +{sqlite3AddCheckConstraint(pParse,yymsp[-1].minor.yy454,yymsp[-2].minor.yy0.z,yymsp[0].minor.yy0.z);} break; - case 39: /* ccons ::= REFERENCES nm eidlist_opt refargs */ -{sqlite3CreateForeignKey(pParse,0,&yymsp[-2].minor.yy0,yymsp[-1].minor.yy434,yymsp[0].minor.yy494);} + case 42: /* ccons ::= REFERENCES nm eidlist_opt refargs */ +{sqlite3CreateForeignKey(pParse,0,&yymsp[-2].minor.yy0,yymsp[-1].minor.yy14,yymsp[0].minor.yy144);} break; - case 40: /* ccons ::= defer_subclause */ -{sqlite3DeferForeignKey(pParse,yymsp[0].minor.yy494);} + case 43: /* ccons ::= defer_subclause */ +{sqlite3DeferForeignKey(pParse,yymsp[0].minor.yy144);} break; - case 41: /* ccons ::= COLLATE ID|STRING */ + case 44: /* ccons ::= COLLATE ID|STRING */ {sqlite3AddCollateType(pParse, &yymsp[0].minor.yy0);} break; - case 44: /* refargs ::= */ -{ yymsp[1].minor.yy494 = OE_None*0x0101; /* EV: R-19803-45884 */} + case 45: /* generated ::= LP expr RP */ +{sqlite3AddGenerated(pParse,yymsp[-1].minor.yy454,0);} + break; + case 46: /* generated ::= LP expr RP ID */ +{sqlite3AddGenerated(pParse,yymsp[-2].minor.yy454,&yymsp[0].minor.yy0);} + break; + case 48: /* autoinc ::= AUTOINCR */ +{yymsp[0].minor.yy144 = 1;} + break; + case 49: /* refargs ::= */ +{ yymsp[1].minor.yy144 = OE_None*0x0101; /* EV: R-19803-45884 */} break; - case 45: /* refargs ::= refargs refarg */ -{ yymsp[-1].minor.yy494 = (yymsp[-1].minor.yy494 & ~yymsp[0].minor.yy355.mask) | yymsp[0].minor.yy355.value; } + case 50: /* refargs ::= refargs refarg */ +{ yymsp[-1].minor.yy144 = (yymsp[-1].minor.yy144 & ~yymsp[0].minor.yy383.mask) | yymsp[0].minor.yy383.value; } break; - case 46: /* refarg ::= MATCH nm */ -{ yymsp[-1].minor.yy355.value = 0; yymsp[-1].minor.yy355.mask = 0x000000; } + case 51: /* refarg ::= MATCH nm */ +{ yymsp[-1].minor.yy383.value = 0; yymsp[-1].minor.yy383.mask = 0x000000; } break; - case 47: /* refarg ::= ON INSERT refact */ -{ yymsp[-2].minor.yy355.value = 0; yymsp[-2].minor.yy355.mask = 0x000000; } + case 52: /* refarg ::= ON INSERT refact */ +{ yymsp[-2].minor.yy383.value = 0; yymsp[-2].minor.yy383.mask = 0x000000; } break; - case 48: /* refarg ::= ON DELETE refact */ -{ yymsp[-2].minor.yy355.value = yymsp[0].minor.yy494; yymsp[-2].minor.yy355.mask = 0x0000ff; } + case 53: /* refarg ::= ON DELETE refact */ +{ yymsp[-2].minor.yy383.value = yymsp[0].minor.yy144; yymsp[-2].minor.yy383.mask = 0x0000ff; } break; - case 49: /* refarg ::= ON UPDATE refact */ -{ yymsp[-2].minor.yy355.value = yymsp[0].minor.yy494<<8; yymsp[-2].minor.yy355.mask = 0x00ff00; } + case 54: /* refarg ::= ON UPDATE refact */ +{ yymsp[-2].minor.yy383.value = yymsp[0].minor.yy144<<8; yymsp[-2].minor.yy383.mask = 0x00ff00; } break; - case 50: /* refact ::= SET NULL */ -{ yymsp[-1].minor.yy494 = OE_SetNull; /* EV: R-33326-45252 */} + case 55: /* refact ::= SET NULL */ +{ yymsp[-1].minor.yy144 = OE_SetNull; /* EV: R-33326-45252 */} break; - case 51: /* refact ::= SET DEFAULT */ -{ yymsp[-1].minor.yy494 = OE_SetDflt; /* EV: R-33326-45252 */} + case 56: /* refact ::= SET DEFAULT */ +{ yymsp[-1].minor.yy144 = OE_SetDflt; /* EV: R-33326-45252 */} break; - case 52: /* refact ::= CASCADE */ -{ yymsp[0].minor.yy494 = OE_Cascade; /* EV: R-33326-45252 */} + case 57: /* refact ::= CASCADE */ +{ yymsp[0].minor.yy144 = OE_Cascade; /* EV: R-33326-45252 */} break; - case 53: /* refact ::= RESTRICT */ -{ yymsp[0].minor.yy494 = OE_Restrict; /* EV: R-33326-45252 */} + case 58: /* refact ::= RESTRICT */ +{ yymsp[0].minor.yy144 = OE_Restrict; /* EV: R-33326-45252 */} break; - case 54: /* refact ::= NO ACTION */ -{ yymsp[-1].minor.yy494 = OE_None; /* EV: R-33326-45252 */} + case 59: /* refact ::= NO ACTION */ +{ yymsp[-1].minor.yy144 = OE_None; /* EV: R-33326-45252 */} break; - case 55: /* defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt */ -{yymsp[-2].minor.yy494 = 0;} + case 60: /* defer_subclause ::= NOT DEFERRABLE init_deferred_pred_opt */ +{yymsp[-2].minor.yy144 = 0;} break; - case 56: /* defer_subclause ::= DEFERRABLE init_deferred_pred_opt */ - case 71: /* orconf ::= OR resolvetype */ yytestcase(yyruleno==71); - case 156: /* insert_cmd ::= INSERT orconf */ yytestcase(yyruleno==156); -{yymsp[-1].minor.yy494 = yymsp[0].minor.yy494;} + case 61: /* defer_subclause ::= DEFERRABLE init_deferred_pred_opt */ + case 76: /* orconf ::= OR resolvetype */ yytestcase(yyruleno==76); + case 173: /* insert_cmd ::= INSERT orconf */ yytestcase(yyruleno==173); +{yymsp[-1].minor.yy144 = yymsp[0].minor.yy144;} break; - case 58: /* init_deferred_pred_opt ::= INITIALLY DEFERRED */ - case 75: /* ifexists ::= IF EXISTS */ yytestcase(yyruleno==75); - case 198: /* between_op ::= NOT BETWEEN */ yytestcase(yyruleno==198); - case 201: /* in_op ::= NOT IN */ yytestcase(yyruleno==201); - case 227: /* collate ::= COLLATE ID|STRING */ yytestcase(yyruleno==227); -{yymsp[-1].minor.yy494 = 1;} + case 63: /* init_deferred_pred_opt ::= INITIALLY DEFERRED */ + case 80: /* ifexists ::= IF EXISTS */ yytestcase(yyruleno==80); + case 219: /* between_op ::= NOT BETWEEN */ yytestcase(yyruleno==219); + case 222: /* in_op ::= NOT IN */ yytestcase(yyruleno==222); + case 247: /* collate ::= COLLATE ID|STRING */ yytestcase(yyruleno==247); +{yymsp[-1].minor.yy144 = 1;} break; - case 59: /* init_deferred_pred_opt ::= INITIALLY IMMEDIATE */ -{yymsp[-1].minor.yy494 = 0;} + case 64: /* init_deferred_pred_opt ::= INITIALLY IMMEDIATE */ +{yymsp[-1].minor.yy144 = 0;} break; - case 61: /* tconscomma ::= COMMA */ -{pParse->constraintName.n = 0;} + case 66: /* tconscomma ::= COMMA */ +{ASSERT_IS_CREATE; pParse->u1.cr.constraintName.n = 0;} break; - case 63: /* tcons ::= PRIMARY KEY LP sortlist autoinc RP onconf */ -{sqlite3AddPrimaryKey(pParse,yymsp[-3].minor.yy434,yymsp[0].minor.yy494,yymsp[-2].minor.yy494,0);} + case 68: /* tcons ::= PRIMARY KEY LP sortlist autoinc RP onconf */ +{sqlite3AddPrimaryKey(pParse,yymsp[-3].minor.yy14,yymsp[0].minor.yy144,yymsp[-2].minor.yy144,0);} break; - case 64: /* tcons ::= UNIQUE LP sortlist RP onconf */ -{sqlite3CreateIndex(pParse,0,0,0,yymsp[-2].minor.yy434,yymsp[0].minor.yy494,0,0,0,0, + case 69: /* tcons ::= UNIQUE LP sortlist RP onconf */ +{sqlite3CreateIndex(pParse,0,0,0,yymsp[-2].minor.yy14,yymsp[0].minor.yy144,0,0,0,0, SQLITE_IDXTYPE_UNIQUE);} break; - case 65: /* tcons ::= CHECK LP expr RP onconf */ -{sqlite3AddCheckConstraint(pParse,yymsp[-2].minor.yy524);} + case 70: /* tcons ::= CHECK LP expr RP onconf */ +{sqlite3AddCheckConstraint(pParse,yymsp[-2].minor.yy454,yymsp[-3].minor.yy0.z,yymsp[-1].minor.yy0.z);} break; - case 66: /* tcons ::= FOREIGN KEY LP eidlist RP REFERENCES nm eidlist_opt refargs defer_subclause_opt */ + case 71: /* tcons ::= FOREIGN KEY LP eidlist RP REFERENCES nm eidlist_opt refargs defer_subclause_opt */ { - sqlite3CreateForeignKey(pParse, yymsp[-6].minor.yy434, &yymsp[-3].minor.yy0, yymsp[-2].minor.yy434, yymsp[-1].minor.yy494); - sqlite3DeferForeignKey(pParse, yymsp[0].minor.yy494); + sqlite3CreateForeignKey(pParse, yymsp[-6].minor.yy14, &yymsp[-3].minor.yy0, yymsp[-2].minor.yy14, yymsp[-1].minor.yy144); + sqlite3DeferForeignKey(pParse, yymsp[0].minor.yy144); } break; - case 68: /* onconf ::= */ - case 70: /* orconf ::= */ yytestcase(yyruleno==70); -{yymsp[1].minor.yy494 = OE_Default;} + case 73: /* onconf ::= */ + case 75: /* orconf ::= */ yytestcase(yyruleno==75); +{yymsp[1].minor.yy144 = OE_Default;} break; - case 69: /* onconf ::= ON CONFLICT resolvetype */ -{yymsp[-2].minor.yy494 = yymsp[0].minor.yy494;} + case 74: /* onconf ::= ON CONFLICT resolvetype */ +{yymsp[-2].minor.yy144 = yymsp[0].minor.yy144;} break; - case 72: /* resolvetype ::= IGNORE */ -{yymsp[0].minor.yy494 = OE_Ignore;} + case 77: /* resolvetype ::= IGNORE */ +{yymsp[0].minor.yy144 = OE_Ignore;} break; - case 73: /* resolvetype ::= REPLACE */ - case 157: /* insert_cmd ::= REPLACE */ yytestcase(yyruleno==157); -{yymsp[0].minor.yy494 = OE_Replace;} + case 78: /* resolvetype ::= REPLACE */ + case 174: /* insert_cmd ::= REPLACE */ yytestcase(yyruleno==174); +{yymsp[0].minor.yy144 = OE_Replace;} break; - case 74: /* cmd ::= DROP TABLE ifexists fullname */ + case 79: /* cmd ::= DROP TABLE ifexists fullname */ { - sqlite3DropTable(pParse, yymsp[0].minor.yy483, 0, yymsp[-1].minor.yy494); + sqlite3DropTable(pParse, yymsp[0].minor.yy203, 0, yymsp[-1].minor.yy144); } break; - case 77: /* cmd ::= createkw temp VIEW ifnotexists nm dbnm eidlist_opt AS select */ + case 82: /* cmd ::= createkw temp VIEW ifnotexists nm dbnm eidlist_opt AS select */ { - sqlite3CreateView(pParse, &yymsp[-8].minor.yy0, &yymsp[-4].minor.yy0, &yymsp[-3].minor.yy0, yymsp[-2].minor.yy434, yymsp[0].minor.yy457, yymsp[-7].minor.yy494, yymsp[-5].minor.yy494); + sqlite3CreateView(pParse, &yymsp[-8].minor.yy0, &yymsp[-4].minor.yy0, &yymsp[-3].minor.yy0, yymsp[-2].minor.yy14, yymsp[0].minor.yy555, yymsp[-7].minor.yy144, yymsp[-5].minor.yy144); } break; - case 78: /* cmd ::= DROP VIEW ifexists fullname */ + case 83: /* cmd ::= DROP VIEW ifexists fullname */ { - sqlite3DropTable(pParse, yymsp[0].minor.yy483, 1, yymsp[-1].minor.yy494); + sqlite3DropTable(pParse, yymsp[0].minor.yy203, 1, yymsp[-1].minor.yy144); } break; - case 79: /* cmd ::= select */ + case 84: /* cmd ::= select */ { - SelectDest dest = {SRT_Output, 0, 0, 0, 0, 0}; - sqlite3Select(pParse, yymsp[0].minor.yy457, &dest); - sqlite3SelectDelete(pParse->db, yymsp[0].minor.yy457); -} - break; - case 80: /* select ::= WITH wqlist selectnowith */ -{ - Select *p = yymsp[0].minor.yy457; - if( p ){ - p->pWith = yymsp[-1].minor.yy59; - parserDoubleLinkSelect(pParse, p); - }else{ - sqlite3WithDelete(pParse->db, yymsp[-1].minor.yy59); + SelectDest dest = {SRT_Output, 0, 0, 0, 0, 0, 0}; + if( (pParse->db->mDbFlags & DBFLAG_EncodingFixed)!=0 + || sqlite3ReadSchema(pParse)==SQLITE_OK + ){ + sqlite3Select(pParse, yymsp[0].minor.yy555, &dest); } - yymsp[-2].minor.yy457 = p; + sqlite3SelectDelete(pParse->db, yymsp[0].minor.yy555); } break; - case 81: /* select ::= WITH RECURSIVE wqlist selectnowith */ -{ - Select *p = yymsp[0].minor.yy457; - if( p ){ - p->pWith = yymsp[-1].minor.yy59; - parserDoubleLinkSelect(pParse, p); - }else{ - sqlite3WithDelete(pParse->db, yymsp[-1].minor.yy59); - } - yymsp[-3].minor.yy457 = p; -} + case 85: /* select ::= WITH wqlist selectnowith */ +{yymsp[-2].minor.yy555 = attachWithToSelect(pParse,yymsp[0].minor.yy555,yymsp[-1].minor.yy59);} + break; + case 86: /* select ::= WITH RECURSIVE wqlist selectnowith */ +{yymsp[-3].minor.yy555 = attachWithToSelect(pParse,yymsp[0].minor.yy555,yymsp[-1].minor.yy59);} break; - case 82: /* select ::= selectnowith */ + case 87: /* select ::= selectnowith */ { - Select *p = yymsp[0].minor.yy457; + Select *p = yymsp[0].minor.yy555; if( p ){ parserDoubleLinkSelect(pParse, p); } - yymsp[0].minor.yy457 = p; /*A-overwrites-X*/ } break; - case 83: /* selectnowith ::= selectnowith multiselect_op oneselect */ + case 88: /* selectnowith ::= selectnowith multiselect_op oneselect */ { - Select *pRhs = yymsp[0].minor.yy457; - Select *pLhs = yymsp[-2].minor.yy457; + Select *pRhs = yymsp[0].minor.yy555; + Select *pLhs = yymsp[-2].minor.yy555; if( pRhs && pRhs->pPrior ){ SrcList *pFrom; Token x; x.n = 0; parserDoubleLinkSelect(pParse, pRhs); - pFrom = sqlite3SrcListAppendFromTerm(pParse,0,0,0,&x,pRhs,0,0); + pFrom = sqlite3SrcListAppendFromTerm(pParse,0,0,0,&x,pRhs,0); pRhs = sqlite3SelectNew(pParse,0,pFrom,0,0,0,0,0,0); } if( pRhs ){ - pRhs->op = (u8)yymsp[-1].minor.yy494; + pRhs->op = (u8)yymsp[-1].minor.yy144; pRhs->pPrior = pLhs; - if( ALWAYS(pLhs) ) pLhs->selFlags &= ~SF_MultiValue; - pRhs->selFlags &= ~SF_MultiValue; - if( yymsp[-1].minor.yy494!=TK_ALL ) pParse->hasCompound = 1; + if( ALWAYS(pLhs) ) pLhs->selFlags &= ~(u32)SF_MultiValue; + pRhs->selFlags &= ~(u32)SF_MultiValue; + if( yymsp[-1].minor.yy144!=TK_ALL ) pParse->hasCompound = 1; }else{ sqlite3SelectDelete(pParse->db, pLhs); } - yymsp[-2].minor.yy457 = pRhs; + yymsp[-2].minor.yy555 = pRhs; } break; - case 84: /* multiselect_op ::= UNION */ - case 86: /* multiselect_op ::= EXCEPT|INTERSECT */ yytestcase(yyruleno==86); -{yymsp[0].minor.yy494 = yymsp[0].major; /*A-overwrites-OP*/} + case 89: /* multiselect_op ::= UNION */ + case 91: /* multiselect_op ::= EXCEPT|INTERSECT */ yytestcase(yyruleno==91); +{yymsp[0].minor.yy144 = yymsp[0].major; /*A-overwrites-OP*/} break; - case 85: /* multiselect_op ::= UNION ALL */ -{yymsp[-1].minor.yy494 = TK_ALL;} + case 90: /* multiselect_op ::= UNION ALL */ +{yymsp[-1].minor.yy144 = TK_ALL;} break; - case 87: /* oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt */ + case 92: /* oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt orderby_opt limit_opt */ { - yymsp[-8].minor.yy457 = sqlite3SelectNew(pParse,yymsp[-6].minor.yy434,yymsp[-5].minor.yy483,yymsp[-4].minor.yy524,yymsp[-3].minor.yy434,yymsp[-2].minor.yy524,yymsp[-1].minor.yy434,yymsp[-7].minor.yy494,yymsp[0].minor.yy524); + yymsp[-8].minor.yy555 = sqlite3SelectNew(pParse,yymsp[-6].minor.yy14,yymsp[-5].minor.yy203,yymsp[-4].minor.yy454,yymsp[-3].minor.yy14,yymsp[-2].minor.yy454,yymsp[-1].minor.yy14,yymsp[-7].minor.yy144,yymsp[0].minor.yy454); } break; - case 88: /* oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt window_clause orderby_opt limit_opt */ + case 93: /* oneselect ::= SELECT distinct selcollist from where_opt groupby_opt having_opt window_clause orderby_opt limit_opt */ { - yymsp[-9].minor.yy457 = sqlite3SelectNew(pParse,yymsp[-7].minor.yy434,yymsp[-6].minor.yy483,yymsp[-5].minor.yy524,yymsp[-4].minor.yy434,yymsp[-3].minor.yy524,yymsp[-1].minor.yy434,yymsp[-8].minor.yy494,yymsp[0].minor.yy524); - if( yymsp[-9].minor.yy457 ){ - yymsp[-9].minor.yy457->pWinDefn = yymsp[-2].minor.yy295; + yymsp[-9].minor.yy555 = sqlite3SelectNew(pParse,yymsp[-7].minor.yy14,yymsp[-6].minor.yy203,yymsp[-5].minor.yy454,yymsp[-4].minor.yy14,yymsp[-3].minor.yy454,yymsp[-1].minor.yy14,yymsp[-8].minor.yy144,yymsp[0].minor.yy454); + if( yymsp[-9].minor.yy555 ){ + yymsp[-9].minor.yy555->pWinDefn = yymsp[-2].minor.yy211; }else{ - sqlite3WindowListDelete(pParse->db, yymsp[-2].minor.yy295); + sqlite3WindowListDelete(pParse->db, yymsp[-2].minor.yy211); } } break; - case 89: /* values ::= VALUES LP nexprlist RP */ + case 94: /* values ::= VALUES LP nexprlist RP */ { - yymsp[-3].minor.yy457 = sqlite3SelectNew(pParse,yymsp[-1].minor.yy434,0,0,0,0,0,SF_Values,0); + yymsp[-3].minor.yy555 = sqlite3SelectNew(pParse,yymsp[-1].minor.yy14,0,0,0,0,0,SF_Values,0); } break; - case 90: /* values ::= values COMMA LP nexprlist RP */ + case 95: /* oneselect ::= mvalues */ { - Select *pRight, *pLeft = yymsp[-4].minor.yy457; - pRight = sqlite3SelectNew(pParse,yymsp[-1].minor.yy434,0,0,0,0,0,SF_Values|SF_MultiValue,0); - if( ALWAYS(pLeft) ) pLeft->selFlags &= ~SF_MultiValue; - if( pRight ){ - pRight->op = TK_ALL; - pRight->pPrior = pLeft; - yymsp[-4].minor.yy457 = pRight; - }else{ - yymsp[-4].minor.yy457 = pLeft; - } + sqlite3MultiValuesEnd(pParse, yymsp[0].minor.yy555); } break; - case 91: /* distinct ::= DISTINCT */ -{yymsp[0].minor.yy494 = SF_Distinct;} + case 96: /* mvalues ::= values COMMA LP nexprlist RP */ + case 97: /* mvalues ::= mvalues COMMA LP nexprlist RP */ yytestcase(yyruleno==97); +{ + yymsp[-4].minor.yy555 = sqlite3MultiValues(pParse, yymsp[-4].minor.yy555, yymsp[-1].minor.yy14); +} break; - case 92: /* distinct ::= ALL */ -{yymsp[0].minor.yy494 = SF_All;} + case 98: /* distinct ::= DISTINCT */ +{yymsp[0].minor.yy144 = SF_Distinct;} break; - case 94: /* sclp ::= */ - case 127: /* orderby_opt ::= */ yytestcase(yyruleno==127); - case 134: /* groupby_opt ::= */ yytestcase(yyruleno==134); - case 214: /* exprlist ::= */ yytestcase(yyruleno==214); - case 217: /* paren_exprlist ::= */ yytestcase(yyruleno==217); - case 222: /* eidlist_opt ::= */ yytestcase(yyruleno==222); -{yymsp[1].minor.yy434 = 0;} + case 99: /* distinct ::= ALL */ +{yymsp[0].minor.yy144 = SF_All;} break; - case 95: /* selcollist ::= sclp scanpt expr scanpt as */ + case 101: /* sclp ::= */ + case 134: /* orderby_opt ::= */ yytestcase(yyruleno==134); + case 144: /* groupby_opt ::= */ yytestcase(yyruleno==144); + case 234: /* exprlist ::= */ yytestcase(yyruleno==234); + case 237: /* paren_exprlist ::= */ yytestcase(yyruleno==237); + case 242: /* eidlist_opt ::= */ yytestcase(yyruleno==242); +{yymsp[1].minor.yy14 = 0;} + break; + case 102: /* selcollist ::= sclp scanpt expr scanpt as */ { - yymsp[-4].minor.yy434 = sqlite3ExprListAppend(pParse, yymsp[-4].minor.yy434, yymsp[-2].minor.yy524); - if( yymsp[0].minor.yy0.n>0 ) sqlite3ExprListSetName(pParse, yymsp[-4].minor.yy434, &yymsp[0].minor.yy0, 1); - sqlite3ExprListSetSpan(pParse,yymsp[-4].minor.yy434,yymsp[-3].minor.yy294,yymsp[-1].minor.yy294); + yymsp[-4].minor.yy14 = sqlite3ExprListAppend(pParse, yymsp[-4].minor.yy14, yymsp[-2].minor.yy454); + if( yymsp[0].minor.yy0.n>0 ) sqlite3ExprListSetName(pParse, yymsp[-4].minor.yy14, &yymsp[0].minor.yy0, 1); + sqlite3ExprListSetSpan(pParse,yymsp[-4].minor.yy14,yymsp[-3].minor.yy168,yymsp[-1].minor.yy168); } break; - case 96: /* selcollist ::= sclp scanpt STAR */ + case 103: /* selcollist ::= sclp scanpt STAR */ { Expr *p = sqlite3Expr(pParse->db, TK_ASTERISK, 0); - yymsp[-2].minor.yy434 = sqlite3ExprListAppend(pParse, yymsp[-2].minor.yy434, p); + sqlite3ExprSetErrorOffset(p, (int)(yymsp[0].minor.yy0.z - pParse->zTail)); + yymsp[-2].minor.yy14 = sqlite3ExprListAppend(pParse, yymsp[-2].minor.yy14, p); } break; - case 97: /* selcollist ::= sclp scanpt nm DOT STAR */ + case 104: /* selcollist ::= sclp scanpt nm DOT STAR */ { - Expr *pRight = sqlite3PExpr(pParse, TK_ASTERISK, 0, 0); - Expr *pLeft = sqlite3ExprAlloc(pParse->db, TK_ID, &yymsp[-2].minor.yy0, 1); - Expr *pDot = sqlite3PExpr(pParse, TK_DOT, pLeft, pRight); - yymsp[-4].minor.yy434 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy434, pDot); + Expr *pRight, *pLeft, *pDot; + pRight = sqlite3PExpr(pParse, TK_ASTERISK, 0, 0); + sqlite3ExprSetErrorOffset(pRight, (int)(yymsp[0].minor.yy0.z - pParse->zTail)); + pLeft = tokenExpr(pParse, TK_ID, yymsp[-2].minor.yy0); + pDot = sqlite3PExpr(pParse, TK_DOT, pLeft, pRight); + yymsp[-4].minor.yy14 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy14, pDot); } break; - case 98: /* as ::= AS nm */ - case 109: /* dbnm ::= DOT nm */ yytestcase(yyruleno==109); - case 238: /* plus_num ::= PLUS INTEGER|FLOAT */ yytestcase(yyruleno==238); - case 239: /* minus_num ::= MINUS INTEGER|FLOAT */ yytestcase(yyruleno==239); + case 105: /* as ::= AS nm */ + case 117: /* dbnm ::= DOT nm */ yytestcase(yyruleno==117); + case 258: /* plus_num ::= PLUS INTEGER|FLOAT */ yytestcase(yyruleno==258); + case 259: /* minus_num ::= MINUS INTEGER|FLOAT */ yytestcase(yyruleno==259); {yymsp[-1].minor.yy0 = yymsp[0].minor.yy0;} break; - case 100: /* from ::= */ -{yymsp[1].minor.yy483 = sqlite3DbMallocZero(pParse->db, sizeof(*yymsp[1].minor.yy483));} + case 107: /* from ::= */ + case 110: /* stl_prefix ::= */ yytestcase(yyruleno==110); +{yymsp[1].minor.yy203 = 0;} break; - case 101: /* from ::= FROM seltablist */ + case 108: /* from ::= FROM seltablist */ { - yymsp[-1].minor.yy483 = yymsp[0].minor.yy483; - sqlite3SrcListShiftJoinType(yymsp[-1].minor.yy483); + yymsp[-1].minor.yy203 = yymsp[0].minor.yy203; + sqlite3SrcListShiftJoinType(pParse,yymsp[-1].minor.yy203); } break; - case 102: /* stl_prefix ::= seltablist joinop */ + case 109: /* stl_prefix ::= seltablist joinop */ { - if( ALWAYS(yymsp[-1].minor.yy483 && yymsp[-1].minor.yy483->nSrc>0) ) yymsp[-1].minor.yy483->a[yymsp[-1].minor.yy483->nSrc-1].fg.jointype = (u8)yymsp[0].minor.yy494; + if( ALWAYS(yymsp[-1].minor.yy203 && yymsp[-1].minor.yy203->nSrc>0) ) yymsp[-1].minor.yy203->a[yymsp[-1].minor.yy203->nSrc-1].fg.jointype = (u8)yymsp[0].minor.yy144; } break; - case 103: /* stl_prefix ::= */ -{yymsp[1].minor.yy483 = 0;} + case 111: /* seltablist ::= stl_prefix nm dbnm as on_using */ +{ + yymsp[-4].minor.yy203 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-4].minor.yy203,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0,0,&yymsp[0].minor.yy269); +} break; - case 104: /* seltablist ::= stl_prefix nm dbnm as indexed_opt on_opt using_opt */ + case 112: /* seltablist ::= stl_prefix nm dbnm as indexed_by on_using */ { - yymsp[-6].minor.yy483 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-6].minor.yy483,&yymsp[-5].minor.yy0,&yymsp[-4].minor.yy0,&yymsp[-3].minor.yy0,0,yymsp[-1].minor.yy524,yymsp[0].minor.yy62); - sqlite3SrcListIndexedBy(pParse, yymsp[-6].minor.yy483, &yymsp[-2].minor.yy0); + yymsp[-5].minor.yy203 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-5].minor.yy203,&yymsp[-4].minor.yy0,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,0,&yymsp[0].minor.yy269); + sqlite3SrcListIndexedBy(pParse, yymsp[-5].minor.yy203, &yymsp[-1].minor.yy0); } break; - case 105: /* seltablist ::= stl_prefix nm dbnm LP exprlist RP as on_opt using_opt */ + case 113: /* seltablist ::= stl_prefix nm dbnm LP exprlist RP as on_using */ { - yymsp[-8].minor.yy483 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-8].minor.yy483,&yymsp[-7].minor.yy0,&yymsp[-6].minor.yy0,&yymsp[-2].minor.yy0,0,yymsp[-1].minor.yy524,yymsp[0].minor.yy62); - sqlite3SrcListFuncArgs(pParse, yymsp[-8].minor.yy483, yymsp[-4].minor.yy434); + yymsp[-7].minor.yy203 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-7].minor.yy203,&yymsp[-6].minor.yy0,&yymsp[-5].minor.yy0,&yymsp[-1].minor.yy0,0,&yymsp[0].minor.yy269); + sqlite3SrcListFuncArgs(pParse, yymsp[-7].minor.yy203, yymsp[-3].minor.yy14); } break; - case 106: /* seltablist ::= stl_prefix LP select RP as on_opt using_opt */ + case 114: /* seltablist ::= stl_prefix LP select RP as on_using */ { - yymsp[-6].minor.yy483 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-6].minor.yy483,0,0,&yymsp[-2].minor.yy0,yymsp[-4].minor.yy457,yymsp[-1].minor.yy524,yymsp[0].minor.yy62); + yymsp[-5].minor.yy203 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-5].minor.yy203,0,0,&yymsp[-1].minor.yy0,yymsp[-3].minor.yy555,&yymsp[0].minor.yy269); } break; - case 107: /* seltablist ::= stl_prefix LP seltablist RP as on_opt using_opt */ + case 115: /* seltablist ::= stl_prefix LP seltablist RP as on_using */ { - if( yymsp[-6].minor.yy483==0 && yymsp[-2].minor.yy0.n==0 && yymsp[-1].minor.yy524==0 && yymsp[0].minor.yy62==0 ){ - yymsp[-6].minor.yy483 = yymsp[-4].minor.yy483; - }else if( yymsp[-4].minor.yy483->nSrc==1 ){ - yymsp[-6].minor.yy483 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-6].minor.yy483,0,0,&yymsp[-2].minor.yy0,0,yymsp[-1].minor.yy524,yymsp[0].minor.yy62); - if( yymsp[-6].minor.yy483 ){ - struct SrcList_item *pNew = &yymsp[-6].minor.yy483->a[yymsp[-6].minor.yy483->nSrc-1]; - struct SrcList_item *pOld = yymsp[-4].minor.yy483->a; + if( yymsp[-5].minor.yy203==0 && yymsp[-1].minor.yy0.n==0 && yymsp[0].minor.yy269.pOn==0 && yymsp[0].minor.yy269.pUsing==0 ){ + yymsp[-5].minor.yy203 = yymsp[-3].minor.yy203; + }else if( ALWAYS(yymsp[-3].minor.yy203!=0) && yymsp[-3].minor.yy203->nSrc==1 ){ + yymsp[-5].minor.yy203 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-5].minor.yy203,0,0,&yymsp[-1].minor.yy0,0,&yymsp[0].minor.yy269); + if( yymsp[-5].minor.yy203 ){ + SrcItem *pNew = &yymsp[-5].minor.yy203->a[yymsp[-5].minor.yy203->nSrc-1]; + SrcItem *pOld = yymsp[-3].minor.yy203->a; + assert( pOld->fg.fixedSchema==0 ); pNew->zName = pOld->zName; - pNew->zDatabase = pOld->zDatabase; - pNew->pSelect = pOld->pSelect; + assert( pOld->fg.fixedSchema==0 ); + if( pOld->fg.isSubquery ){ + pNew->fg.isSubquery = 1; + pNew->u4.pSubq = pOld->u4.pSubq; + pOld->u4.pSubq = 0; + pOld->fg.isSubquery = 0; + assert( pNew->u4.pSubq!=0 && pNew->u4.pSubq->pSelect!=0 ); + if( (pNew->u4.pSubq->pSelect->selFlags & SF_NestedFrom)!=0 ){ + pNew->fg.isNestedFrom = 1; + } + }else{ + pNew->u4.zDatabase = pOld->u4.zDatabase; + pOld->u4.zDatabase = 0; + } if( pOld->fg.isTabFunc ){ pNew->u1.pFuncArg = pOld->u1.pFuncArg; pOld->u1.pFuncArg = 0; pOld->fg.isTabFunc = 0; pNew->fg.isTabFunc = 1; } - pOld->zName = pOld->zDatabase = 0; - pOld->pSelect = 0; + pOld->zName = 0; } - sqlite3SrcListDelete(pParse->db, yymsp[-4].minor.yy483); + sqlite3SrcListDelete(pParse->db, yymsp[-3].minor.yy203); }else{ Select *pSubquery; - sqlite3SrcListShiftJoinType(yymsp[-4].minor.yy483); - pSubquery = sqlite3SelectNew(pParse,0,yymsp[-4].minor.yy483,0,0,0,0,SF_NestedFrom,0); - yymsp[-6].minor.yy483 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-6].minor.yy483,0,0,&yymsp[-2].minor.yy0,pSubquery,yymsp[-1].minor.yy524,yymsp[0].minor.yy62); + sqlite3SrcListShiftJoinType(pParse,yymsp[-3].minor.yy203); + pSubquery = sqlite3SelectNew(pParse,0,yymsp[-3].minor.yy203,0,0,0,0,SF_NestedFrom,0); + yymsp[-5].minor.yy203 = sqlite3SrcListAppendFromTerm(pParse,yymsp[-5].minor.yy203,0,0,&yymsp[-1].minor.yy0,pSubquery,&yymsp[0].minor.yy269); } } break; - case 108: /* dbnm ::= */ - case 122: /* indexed_opt ::= */ yytestcase(yyruleno==122); + case 116: /* dbnm ::= */ + case 131: /* indexed_opt ::= */ yytestcase(yyruleno==131); {yymsp[1].minor.yy0.z=0; yymsp[1].minor.yy0.n=0;} break; - case 110: /* fullname ::= nm */ + case 118: /* fullname ::= nm */ + case 120: /* xfullname ::= nm */ yytestcase(yyruleno==120); { - yylhsminor.yy483 = sqlite3SrcListAppend(pParse,0,&yymsp[0].minor.yy0,0); - if( IN_RENAME_OBJECT && yylhsminor.yy483 ) sqlite3RenameTokenMap(pParse, yylhsminor.yy483->a[0].zName, &yymsp[0].minor.yy0); + yylhsminor.yy203 = sqlite3SrcListAppend(pParse,0,&yymsp[0].minor.yy0,0); + if( IN_RENAME_OBJECT && yylhsminor.yy203 ) sqlite3RenameTokenMap(pParse, yylhsminor.yy203->a[0].zName, &yymsp[0].minor.yy0); } - yymsp[0].minor.yy483 = yylhsminor.yy483; + yymsp[0].minor.yy203 = yylhsminor.yy203; break; - case 111: /* fullname ::= nm DOT nm */ + case 119: /* fullname ::= nm DOT nm */ + case 121: /* xfullname ::= nm DOT nm */ yytestcase(yyruleno==121); { - yylhsminor.yy483 = sqlite3SrcListAppend(pParse,0,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0); - if( IN_RENAME_OBJECT && yylhsminor.yy483 ) sqlite3RenameTokenMap(pParse, yylhsminor.yy483->a[0].zName, &yymsp[0].minor.yy0); + yylhsminor.yy203 = sqlite3SrcListAppend(pParse,0,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0); + if( IN_RENAME_OBJECT && yylhsminor.yy203 ) sqlite3RenameTokenMap(pParse, yylhsminor.yy203->a[0].zName, &yymsp[0].minor.yy0); } - yymsp[-2].minor.yy483 = yylhsminor.yy483; - break; - case 112: /* xfullname ::= nm */ -{yymsp[0].minor.yy483 = sqlite3SrcListAppend(pParse,0,&yymsp[0].minor.yy0,0); /*A-overwrites-X*/} + yymsp[-2].minor.yy203 = yylhsminor.yy203; break; - case 113: /* xfullname ::= nm DOT nm */ -{yymsp[-2].minor.yy483 = sqlite3SrcListAppend(pParse,0,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0); /*A-overwrites-X*/} - break; - case 114: /* xfullname ::= nm DOT nm AS nm */ + case 122: /* xfullname ::= nm AS nm */ { - yymsp[-4].minor.yy483 = sqlite3SrcListAppend(pParse,0,&yymsp[-4].minor.yy0,&yymsp[-2].minor.yy0); /*A-overwrites-X*/ - if( yymsp[-4].minor.yy483 ) yymsp[-4].minor.yy483->a[0].zAlias = sqlite3NameFromToken(pParse->db, &yymsp[0].minor.yy0); + yylhsminor.yy203 = sqlite3SrcListAppend(pParse,0,&yymsp[-2].minor.yy0,0); + if( yylhsminor.yy203 ){ + if( IN_RENAME_OBJECT ){ + sqlite3RenameTokenMap(pParse, yylhsminor.yy203->a[0].zName, &yymsp[-2].minor.yy0); + }else{ + yylhsminor.yy203->a[0].zAlias = sqlite3NameFromToken(pParse->db, &yymsp[0].minor.yy0); + } + } } + yymsp[-2].minor.yy203 = yylhsminor.yy203; break; - case 115: /* xfullname ::= nm AS nm */ -{ - yymsp[-2].minor.yy483 = sqlite3SrcListAppend(pParse,0,&yymsp[-2].minor.yy0,0); /*A-overwrites-X*/ - if( yymsp[-2].minor.yy483 ) yymsp[-2].minor.yy483->a[0].zAlias = sqlite3NameFromToken(pParse->db, &yymsp[0].minor.yy0); + case 123: /* xfullname ::= nm DOT nm AS nm */ +{ + yylhsminor.yy203 = sqlite3SrcListAppend(pParse,0,&yymsp[-4].minor.yy0,&yymsp[-2].minor.yy0); + if( yylhsminor.yy203 ){ + if( IN_RENAME_OBJECT ){ + sqlite3RenameTokenMap(pParse, yylhsminor.yy203->a[0].zName, &yymsp[-2].minor.yy0); + }else{ + yylhsminor.yy203->a[0].zAlias = sqlite3NameFromToken(pParse->db, &yymsp[0].minor.yy0); + } + } } + yymsp[-4].minor.yy203 = yylhsminor.yy203; break; - case 116: /* joinop ::= COMMA|JOIN */ -{ yymsp[0].minor.yy494 = JT_INNER; } + case 124: /* joinop ::= COMMA|JOIN */ +{ yymsp[0].minor.yy144 = JT_INNER; } break; - case 117: /* joinop ::= JOIN_KW JOIN */ -{yymsp[-1].minor.yy494 = sqlite3JoinType(pParse,&yymsp[-1].minor.yy0,0,0); /*X-overwrites-A*/} + case 125: /* joinop ::= JOIN_KW JOIN */ +{yymsp[-1].minor.yy144 = sqlite3JoinType(pParse,&yymsp[-1].minor.yy0,0,0); /*X-overwrites-A*/} break; - case 118: /* joinop ::= JOIN_KW nm JOIN */ -{yymsp[-2].minor.yy494 = sqlite3JoinType(pParse,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0,0); /*X-overwrites-A*/} + case 126: /* joinop ::= JOIN_KW nm JOIN */ +{yymsp[-2].minor.yy144 = sqlite3JoinType(pParse,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0,0); /*X-overwrites-A*/} break; - case 119: /* joinop ::= JOIN_KW nm nm JOIN */ -{yymsp[-3].minor.yy494 = sqlite3JoinType(pParse,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0);/*X-overwrites-A*/} + case 127: /* joinop ::= JOIN_KW nm nm JOIN */ +{yymsp[-3].minor.yy144 = sqlite3JoinType(pParse,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0);/*X-overwrites-A*/} break; - case 120: /* on_opt ::= ON expr */ - case 137: /* having_opt ::= HAVING expr */ yytestcase(yyruleno==137); - case 144: /* where_opt ::= WHERE expr */ yytestcase(yyruleno==144); - case 210: /* case_else ::= ELSE expr */ yytestcase(yyruleno==210); - case 231: /* vinto ::= INTO expr */ yytestcase(yyruleno==231); -{yymsp[-1].minor.yy524 = yymsp[0].minor.yy524;} + case 128: /* on_using ::= ON expr */ +{yymsp[-1].minor.yy269.pOn = yymsp[0].minor.yy454; yymsp[-1].minor.yy269.pUsing = 0;} break; - case 121: /* on_opt ::= */ - case 136: /* having_opt ::= */ yytestcase(yyruleno==136); - case 138: /* limit_opt ::= */ yytestcase(yyruleno==138); - case 143: /* where_opt ::= */ yytestcase(yyruleno==143); - case 211: /* case_else ::= */ yytestcase(yyruleno==211); - case 213: /* case_operand ::= */ yytestcase(yyruleno==213); - case 232: /* vinto ::= */ yytestcase(yyruleno==232); -{yymsp[1].minor.yy524 = 0;} + case 129: /* on_using ::= USING LP idlist RP */ +{yymsp[-3].minor.yy269.pOn = 0; yymsp[-3].minor.yy269.pUsing = yymsp[-1].minor.yy132;} break; - case 123: /* indexed_opt ::= INDEXED BY nm */ + case 130: /* on_using ::= */ +{yymsp[1].minor.yy269.pOn = 0; yymsp[1].minor.yy269.pUsing = 0;} + break; + case 132: /* indexed_by ::= INDEXED BY nm */ {yymsp[-2].minor.yy0 = yymsp[0].minor.yy0;} break; - case 124: /* indexed_opt ::= NOT INDEXED */ + case 133: /* indexed_by ::= NOT INDEXED */ {yymsp[-1].minor.yy0.z=0; yymsp[-1].minor.yy0.n=1;} break; - case 125: /* using_opt ::= USING LP idlist RP */ -{yymsp[-3].minor.yy62 = yymsp[-1].minor.yy62;} - break; - case 126: /* using_opt ::= */ - case 158: /* idlist_opt ::= */ yytestcase(yyruleno==158); -{yymsp[1].minor.yy62 = 0;} + case 135: /* orderby_opt ::= ORDER BY sortlist */ + case 145: /* groupby_opt ::= GROUP BY nexprlist */ yytestcase(yyruleno==145); +{yymsp[-2].minor.yy14 = yymsp[0].minor.yy14;} break; - case 128: /* orderby_opt ::= ORDER BY sortlist */ - case 135: /* groupby_opt ::= GROUP BY nexprlist */ yytestcase(yyruleno==135); -{yymsp[-2].minor.yy434 = yymsp[0].minor.yy434;} - break; - case 129: /* sortlist ::= sortlist COMMA expr sortorder */ + case 136: /* sortlist ::= sortlist COMMA expr sortorder nulls */ { - yymsp[-3].minor.yy434 = sqlite3ExprListAppend(pParse,yymsp[-3].minor.yy434,yymsp[-1].minor.yy524); - sqlite3ExprListSetSortOrder(yymsp[-3].minor.yy434,yymsp[0].minor.yy494); + yymsp[-4].minor.yy14 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy14,yymsp[-2].minor.yy454); + sqlite3ExprListSetSortOrder(yymsp[-4].minor.yy14,yymsp[-1].minor.yy144,yymsp[0].minor.yy144); } break; - case 130: /* sortlist ::= expr sortorder */ + case 137: /* sortlist ::= expr sortorder nulls */ { - yymsp[-1].minor.yy434 = sqlite3ExprListAppend(pParse,0,yymsp[-1].minor.yy524); /*A-overwrites-Y*/ - sqlite3ExprListSetSortOrder(yymsp[-1].minor.yy434,yymsp[0].minor.yy494); + yymsp[-2].minor.yy14 = sqlite3ExprListAppend(pParse,0,yymsp[-2].minor.yy454); /*A-overwrites-Y*/ + sqlite3ExprListSetSortOrder(yymsp[-2].minor.yy14,yymsp[-1].minor.yy144,yymsp[0].minor.yy144); } break; - case 131: /* sortorder ::= ASC */ -{yymsp[0].minor.yy494 = SQLITE_SO_ASC;} + case 138: /* sortorder ::= ASC */ +{yymsp[0].minor.yy144 = SQLITE_SO_ASC;} + break; + case 139: /* sortorder ::= DESC */ +{yymsp[0].minor.yy144 = SQLITE_SO_DESC;} + break; + case 140: /* sortorder ::= */ + case 143: /* nulls ::= */ yytestcase(yyruleno==143); +{yymsp[1].minor.yy144 = SQLITE_SO_UNDEFINED;} + break; + case 141: /* nulls ::= NULLS FIRST */ +{yymsp[-1].minor.yy144 = SQLITE_SO_ASC;} break; - case 132: /* sortorder ::= DESC */ -{yymsp[0].minor.yy494 = SQLITE_SO_DESC;} + case 142: /* nulls ::= NULLS LAST */ +{yymsp[-1].minor.yy144 = SQLITE_SO_DESC;} break; - case 133: /* sortorder ::= */ -{yymsp[1].minor.yy494 = SQLITE_SO_UNDEFINED;} + case 146: /* having_opt ::= */ + case 148: /* limit_opt ::= */ yytestcase(yyruleno==148); + case 153: /* where_opt ::= */ yytestcase(yyruleno==153); + case 155: /* where_opt_ret ::= */ yytestcase(yyruleno==155); + case 232: /* case_else ::= */ yytestcase(yyruleno==232); + case 233: /* case_operand ::= */ yytestcase(yyruleno==233); + case 252: /* vinto ::= */ yytestcase(yyruleno==252); +{yymsp[1].minor.yy454 = 0;} break; - case 139: /* limit_opt ::= LIMIT expr */ -{yymsp[-1].minor.yy524 = sqlite3PExpr(pParse,TK_LIMIT,yymsp[0].minor.yy524,0);} + case 147: /* having_opt ::= HAVING expr */ + case 154: /* where_opt ::= WHERE expr */ yytestcase(yyruleno==154); + case 156: /* where_opt_ret ::= WHERE expr */ yytestcase(yyruleno==156); + case 231: /* case_else ::= ELSE expr */ yytestcase(yyruleno==231); + case 251: /* vinto ::= INTO expr */ yytestcase(yyruleno==251); +{yymsp[-1].minor.yy454 = yymsp[0].minor.yy454;} break; - case 140: /* limit_opt ::= LIMIT expr OFFSET expr */ -{yymsp[-3].minor.yy524 = sqlite3PExpr(pParse,TK_LIMIT,yymsp[-2].minor.yy524,yymsp[0].minor.yy524);} + case 149: /* limit_opt ::= LIMIT expr */ +{yymsp[-1].minor.yy454 = sqlite3PExpr(pParse,TK_LIMIT,yymsp[0].minor.yy454,0);} break; - case 141: /* limit_opt ::= LIMIT expr COMMA expr */ -{yymsp[-3].minor.yy524 = sqlite3PExpr(pParse,TK_LIMIT,yymsp[0].minor.yy524,yymsp[-2].minor.yy524);} + case 150: /* limit_opt ::= LIMIT expr OFFSET expr */ +{yymsp[-3].minor.yy454 = sqlite3PExpr(pParse,TK_LIMIT,yymsp[-2].minor.yy454,yymsp[0].minor.yy454);} break; - case 142: /* cmd ::= with DELETE FROM xfullname indexed_opt where_opt */ + case 151: /* limit_opt ::= LIMIT expr COMMA expr */ +{yymsp[-3].minor.yy454 = sqlite3PExpr(pParse,TK_LIMIT,yymsp[0].minor.yy454,yymsp[-2].minor.yy454);} + break; + case 152: /* cmd ::= with DELETE FROM xfullname indexed_opt where_opt_ret */ { - sqlite3SrcListIndexedBy(pParse, yymsp[-2].minor.yy483, &yymsp[-1].minor.yy0); - sqlite3DeleteFrom(pParse,yymsp[-2].minor.yy483,yymsp[0].minor.yy524,0,0); + sqlite3SrcListIndexedBy(pParse, yymsp[-2].minor.yy203, &yymsp[-1].minor.yy0); + sqlite3DeleteFrom(pParse,yymsp[-2].minor.yy203,yymsp[0].minor.yy454,0,0); } break; - case 145: /* cmd ::= with UPDATE orconf xfullname indexed_opt SET setlist where_opt */ + case 157: /* where_opt_ret ::= RETURNING selcollist */ +{sqlite3AddReturning(pParse,yymsp[0].minor.yy14); yymsp[-1].minor.yy454 = 0;} + break; + case 158: /* where_opt_ret ::= WHERE expr RETURNING selcollist */ +{sqlite3AddReturning(pParse,yymsp[0].minor.yy14); yymsp[-3].minor.yy454 = yymsp[-2].minor.yy454;} + break; + case 159: /* cmd ::= with UPDATE orconf xfullname indexed_opt SET setlist from where_opt_ret */ { - sqlite3SrcListIndexedBy(pParse, yymsp[-4].minor.yy483, &yymsp[-3].minor.yy0); - sqlite3ExprListCheckLength(pParse,yymsp[-1].minor.yy434,"set list"); - sqlite3Update(pParse,yymsp[-4].minor.yy483,yymsp[-1].minor.yy434,yymsp[0].minor.yy524,yymsp[-5].minor.yy494,0,0,0); + sqlite3SrcListIndexedBy(pParse, yymsp[-5].minor.yy203, &yymsp[-4].minor.yy0); + sqlite3ExprListCheckLength(pParse,yymsp[-2].minor.yy14,"set list"); + if( yymsp[-1].minor.yy203 ){ + SrcList *pFromClause = yymsp[-1].minor.yy203; + if( pFromClause->nSrc>1 ){ + Select *pSubquery; + Token as; + pSubquery = sqlite3SelectNew(pParse,0,pFromClause,0,0,0,0,SF_NestedFrom,0); + as.n = 0; + as.z = 0; + pFromClause = sqlite3SrcListAppendFromTerm(pParse,0,0,0,&as,pSubquery,0); + } + yymsp[-5].minor.yy203 = sqlite3SrcListAppendList(pParse, yymsp[-5].minor.yy203, pFromClause); + } + sqlite3Update(pParse,yymsp[-5].minor.yy203,yymsp[-2].minor.yy14,yymsp[0].minor.yy454,yymsp[-6].minor.yy144,0,0,0); } break; - case 146: /* setlist ::= setlist COMMA nm EQ expr */ + case 160: /* setlist ::= setlist COMMA nm EQ expr */ { - yymsp[-4].minor.yy434 = sqlite3ExprListAppend(pParse, yymsp[-4].minor.yy434, yymsp[0].minor.yy524); - sqlite3ExprListSetName(pParse, yymsp[-4].minor.yy434, &yymsp[-2].minor.yy0, 1); + yymsp[-4].minor.yy14 = sqlite3ExprListAppend(pParse, yymsp[-4].minor.yy14, yymsp[0].minor.yy454); + sqlite3ExprListSetName(pParse, yymsp[-4].minor.yy14, &yymsp[-2].minor.yy0, 1); } break; - case 147: /* setlist ::= setlist COMMA LP idlist RP EQ expr */ + case 161: /* setlist ::= setlist COMMA LP idlist RP EQ expr */ { - yymsp[-6].minor.yy434 = sqlite3ExprListAppendVector(pParse, yymsp[-6].minor.yy434, yymsp[-3].minor.yy62, yymsp[0].minor.yy524); + yymsp[-6].minor.yy14 = sqlite3ExprListAppendVector(pParse, yymsp[-6].minor.yy14, yymsp[-3].minor.yy132, yymsp[0].minor.yy454); } break; - case 148: /* setlist ::= nm EQ expr */ + case 162: /* setlist ::= nm EQ expr */ { - yylhsminor.yy434 = sqlite3ExprListAppend(pParse, 0, yymsp[0].minor.yy524); - sqlite3ExprListSetName(pParse, yylhsminor.yy434, &yymsp[-2].minor.yy0, 1); + yylhsminor.yy14 = sqlite3ExprListAppend(pParse, 0, yymsp[0].minor.yy454); + sqlite3ExprListSetName(pParse, yylhsminor.yy14, &yymsp[-2].minor.yy0, 1); } - yymsp[-2].minor.yy434 = yylhsminor.yy434; + yymsp[-2].minor.yy14 = yylhsminor.yy14; break; - case 149: /* setlist ::= LP idlist RP EQ expr */ + case 163: /* setlist ::= LP idlist RP EQ expr */ { - yymsp[-4].minor.yy434 = sqlite3ExprListAppendVector(pParse, 0, yymsp[-3].minor.yy62, yymsp[0].minor.yy524); + yymsp[-4].minor.yy14 = sqlite3ExprListAppendVector(pParse, 0, yymsp[-3].minor.yy132, yymsp[0].minor.yy454); } break; - case 150: /* cmd ::= with insert_cmd INTO xfullname idlist_opt select upsert */ + case 164: /* cmd ::= with insert_cmd INTO xfullname idlist_opt select upsert */ { - sqlite3Insert(pParse, yymsp[-3].minor.yy483, yymsp[-1].minor.yy457, yymsp[-2].minor.yy62, yymsp[-5].minor.yy494, yymsp[0].minor.yy136); + sqlite3Insert(pParse, yymsp[-3].minor.yy203, yymsp[-1].minor.yy555, yymsp[-2].minor.yy132, yymsp[-5].minor.yy144, yymsp[0].minor.yy122); } break; - case 151: /* cmd ::= with insert_cmd INTO xfullname idlist_opt DEFAULT VALUES */ + case 165: /* cmd ::= with insert_cmd INTO xfullname idlist_opt DEFAULT VALUES returning */ { - sqlite3Insert(pParse, yymsp[-3].minor.yy483, 0, yymsp[-2].minor.yy62, yymsp[-5].minor.yy494, 0); + sqlite3Insert(pParse, yymsp[-4].minor.yy203, 0, yymsp[-3].minor.yy132, yymsp[-6].minor.yy144, 0); } break; - case 152: /* upsert ::= */ -{ yymsp[1].minor.yy136 = 0; } + case 166: /* upsert ::= */ +{ yymsp[1].minor.yy122 = 0; } + break; + case 167: /* upsert ::= RETURNING selcollist */ +{ yymsp[-1].minor.yy122 = 0; sqlite3AddReturning(pParse,yymsp[0].minor.yy14); } + break; + case 168: /* upsert ::= ON CONFLICT LP sortlist RP where_opt DO UPDATE SET setlist where_opt upsert */ +{ yymsp[-11].minor.yy122 = sqlite3UpsertNew(pParse->db,yymsp[-8].minor.yy14,yymsp[-6].minor.yy454,yymsp[-2].minor.yy14,yymsp[-1].minor.yy454,yymsp[0].minor.yy122);} break; - case 153: /* upsert ::= ON CONFLICT LP sortlist RP where_opt DO UPDATE SET setlist where_opt */ -{ yymsp[-10].minor.yy136 = sqlite3UpsertNew(pParse->db,yymsp[-7].minor.yy434,yymsp[-5].minor.yy524,yymsp[-1].minor.yy434,yymsp[0].minor.yy524);} + case 169: /* upsert ::= ON CONFLICT LP sortlist RP where_opt DO NOTHING upsert */ +{ yymsp[-8].minor.yy122 = sqlite3UpsertNew(pParse->db,yymsp[-5].minor.yy14,yymsp[-3].minor.yy454,0,0,yymsp[0].minor.yy122); } break; - case 154: /* upsert ::= ON CONFLICT LP sortlist RP where_opt DO NOTHING */ -{ yymsp[-7].minor.yy136 = sqlite3UpsertNew(pParse->db,yymsp[-4].minor.yy434,yymsp[-2].minor.yy524,0,0); } + case 170: /* upsert ::= ON CONFLICT DO NOTHING returning */ +{ yymsp[-4].minor.yy122 = sqlite3UpsertNew(pParse->db,0,0,0,0,0); } break; - case 155: /* upsert ::= ON CONFLICT DO NOTHING */ -{ yymsp[-3].minor.yy136 = sqlite3UpsertNew(pParse->db,0,0,0,0); } + case 171: /* upsert ::= ON CONFLICT DO UPDATE SET setlist where_opt returning */ +{ yymsp[-7].minor.yy122 = sqlite3UpsertNew(pParse->db,0,0,yymsp[-2].minor.yy14,yymsp[-1].minor.yy454,0);} break; - case 159: /* idlist_opt ::= LP idlist RP */ -{yymsp[-2].minor.yy62 = yymsp[-1].minor.yy62;} + case 172: /* returning ::= RETURNING selcollist */ +{sqlite3AddReturning(pParse,yymsp[0].minor.yy14);} break; - case 160: /* idlist ::= idlist COMMA nm */ -{yymsp[-2].minor.yy62 = sqlite3IdListAppend(pParse,yymsp[-2].minor.yy62,&yymsp[0].minor.yy0);} + case 175: /* idlist_opt ::= */ +{yymsp[1].minor.yy132 = 0;} break; - case 161: /* idlist ::= nm */ -{yymsp[0].minor.yy62 = sqlite3IdListAppend(pParse,0,&yymsp[0].minor.yy0); /*A-overwrites-Y*/} + case 176: /* idlist_opt ::= LP idlist RP */ +{yymsp[-2].minor.yy132 = yymsp[-1].minor.yy132;} break; - case 162: /* expr ::= LP expr RP */ -{yymsp[-2].minor.yy524 = yymsp[-1].minor.yy524;} + case 177: /* idlist ::= idlist COMMA nm */ +{yymsp[-2].minor.yy132 = sqlite3IdListAppend(pParse,yymsp[-2].minor.yy132,&yymsp[0].minor.yy0);} break; - case 163: /* expr ::= ID|INDEXED */ - case 164: /* expr ::= JOIN_KW */ yytestcase(yyruleno==164); -{yymsp[0].minor.yy524=tokenExpr(pParse,TK_ID,yymsp[0].minor.yy0); /*A-overwrites-X*/} + case 178: /* idlist ::= nm */ +{yymsp[0].minor.yy132 = sqlite3IdListAppend(pParse,0,&yymsp[0].minor.yy0); /*A-overwrites-Y*/} break; - case 165: /* expr ::= nm DOT nm */ + case 179: /* expr ::= LP expr RP */ +{yymsp[-2].minor.yy454 = yymsp[-1].minor.yy454;} + break; + case 180: /* expr ::= ID|INDEXED|JOIN_KW */ +{yymsp[0].minor.yy454=tokenExpr(pParse,TK_ID,yymsp[0].minor.yy0); /*A-overwrites-X*/} + break; + case 181: /* expr ::= nm DOT nm */ { - Expr *temp1 = sqlite3ExprAlloc(pParse->db, TK_ID, &yymsp[-2].minor.yy0, 1); - Expr *temp2 = sqlite3ExprAlloc(pParse->db, TK_ID, &yymsp[0].minor.yy0, 1); - if( IN_RENAME_OBJECT ){ - sqlite3RenameTokenMap(pParse, (void*)temp2, &yymsp[0].minor.yy0); - sqlite3RenameTokenMap(pParse, (void*)temp1, &yymsp[-2].minor.yy0); - } - yylhsminor.yy524 = sqlite3PExpr(pParse, TK_DOT, temp1, temp2); + Expr *temp1 = tokenExpr(pParse,TK_ID,yymsp[-2].minor.yy0); + Expr *temp2 = tokenExpr(pParse,TK_ID,yymsp[0].minor.yy0); + yylhsminor.yy454 = sqlite3PExpr(pParse, TK_DOT, temp1, temp2); } - yymsp[-2].minor.yy524 = yylhsminor.yy524; + yymsp[-2].minor.yy454 = yylhsminor.yy454; break; - case 166: /* expr ::= nm DOT nm DOT nm */ + case 182: /* expr ::= nm DOT nm DOT nm */ { - Expr *temp1 = sqlite3ExprAlloc(pParse->db, TK_ID, &yymsp[-4].minor.yy0, 1); - Expr *temp2 = sqlite3ExprAlloc(pParse->db, TK_ID, &yymsp[-2].minor.yy0, 1); - Expr *temp3 = sqlite3ExprAlloc(pParse->db, TK_ID, &yymsp[0].minor.yy0, 1); + Expr *temp1 = tokenExpr(pParse,TK_ID,yymsp[-4].minor.yy0); + Expr *temp2 = tokenExpr(pParse,TK_ID,yymsp[-2].minor.yy0); + Expr *temp3 = tokenExpr(pParse,TK_ID,yymsp[0].minor.yy0); Expr *temp4 = sqlite3PExpr(pParse, TK_DOT, temp2, temp3); if( IN_RENAME_OBJECT ){ - sqlite3RenameTokenMap(pParse, (void*)temp3, &yymsp[0].minor.yy0); - sqlite3RenameTokenMap(pParse, (void*)temp2, &yymsp[-2].minor.yy0); + sqlite3RenameTokenRemap(pParse, 0, temp1); } - yylhsminor.yy524 = sqlite3PExpr(pParse, TK_DOT, temp1, temp4); + yylhsminor.yy454 = sqlite3PExpr(pParse, TK_DOT, temp1, temp4); } - yymsp[-4].minor.yy524 = yylhsminor.yy524; + yymsp[-4].minor.yy454 = yylhsminor.yy454; break; - case 167: /* term ::= NULL|FLOAT|BLOB */ - case 168: /* term ::= STRING */ yytestcase(yyruleno==168); -{yymsp[0].minor.yy524=tokenExpr(pParse,yymsp[0].major,yymsp[0].minor.yy0); /*A-overwrites-X*/} + case 183: /* term ::= NULL|FLOAT|BLOB */ + case 184: /* term ::= STRING */ yytestcase(yyruleno==184); +{yymsp[0].minor.yy454=tokenExpr(pParse,yymsp[0].major,yymsp[0].minor.yy0); /*A-overwrites-X*/} break; - case 169: /* term ::= INTEGER */ + case 185: /* term ::= INTEGER */ { - yylhsminor.yy524 = sqlite3ExprAlloc(pParse->db, TK_INTEGER, &yymsp[0].minor.yy0, 1); + int iValue; + if( sqlite3GetInt32(yymsp[0].minor.yy0.z, &iValue)==0 ){ + yylhsminor.yy454 = sqlite3ExprAlloc(pParse->db, TK_INTEGER, &yymsp[0].minor.yy0, 0); + }else{ + yylhsminor.yy454 = sqlite3ExprInt32(pParse->db, iValue); + } + if( yylhsminor.yy454 ) yylhsminor.yy454->w.iOfst = (int)(yymsp[0].minor.yy0.z - pParse->zTail); } - yymsp[0].minor.yy524 = yylhsminor.yy524; + yymsp[0].minor.yy454 = yylhsminor.yy454; break; - case 170: /* expr ::= VARIABLE */ + case 186: /* expr ::= VARIABLE */ { if( !(yymsp[0].minor.yy0.z[0]=='#' && sqlite3Isdigit(yymsp[0].minor.yy0.z[1])) ){ u32 n = yymsp[0].minor.yy0.n; - yymsp[0].minor.yy524 = tokenExpr(pParse, TK_VARIABLE, yymsp[0].minor.yy0); - sqlite3ExprAssignVarNumber(pParse, yymsp[0].minor.yy524, n); + yymsp[0].minor.yy454 = tokenExpr(pParse, TK_VARIABLE, yymsp[0].minor.yy0); + sqlite3ExprAssignVarNumber(pParse, yymsp[0].minor.yy454, n); }else{ /* When doing a nested parse, one can include terms in an expression ** that look like this: #1 #2 ... These terms refer to registers @@ -152019,706 +184443,820 @@ static YYACTIONTYPE yy_reduce( Token t = yymsp[0].minor.yy0; /*A-overwrites-X*/ assert( t.n>=2 ); if( pParse->nested==0 ){ - sqlite3ErrorMsg(pParse, "near \"%T\": syntax error", &t); - yymsp[0].minor.yy524 = 0; + parserSyntaxError(pParse, &t); + yymsp[0].minor.yy454 = 0; }else{ - yymsp[0].minor.yy524 = sqlite3PExpr(pParse, TK_REGISTER, 0, 0); - if( yymsp[0].minor.yy524 ) sqlite3GetInt32(&t.z[1], &yymsp[0].minor.yy524->iTable); + yymsp[0].minor.yy454 = sqlite3PExpr(pParse, TK_REGISTER, 0, 0); + if( yymsp[0].minor.yy454 ) sqlite3GetInt32(&t.z[1], &yymsp[0].minor.yy454->iTable); } } } break; - case 171: /* expr ::= expr COLLATE ID|STRING */ + case 187: /* expr ::= expr COLLATE ID|STRING */ +{ + yymsp[-2].minor.yy454 = sqlite3ExprAddCollateToken(pParse, yymsp[-2].minor.yy454, &yymsp[0].minor.yy0, 1); +} + break; + case 188: /* expr ::= CAST LP expr AS typetoken RP */ +{ + yymsp[-5].minor.yy454 = sqlite3ExprAlloc(pParse->db, TK_CAST, &yymsp[-1].minor.yy0, 1); + sqlite3ExprAttachSubtrees(pParse->db, yymsp[-5].minor.yy454, yymsp[-3].minor.yy454, 0); +} + break; + case 189: /* expr ::= ID|INDEXED|JOIN_KW LP distinct exprlist RP */ { - yymsp[-2].minor.yy524 = sqlite3ExprAddCollateToken(pParse, yymsp[-2].minor.yy524, &yymsp[0].minor.yy0, 1); + yylhsminor.yy454 = sqlite3ExprFunction(pParse, yymsp[-1].minor.yy14, &yymsp[-4].minor.yy0, yymsp[-2].minor.yy144); } + yymsp[-4].minor.yy454 = yylhsminor.yy454; break; - case 172: /* expr ::= CAST LP expr AS typetoken RP */ + case 190: /* expr ::= ID|INDEXED|JOIN_KW LP distinct exprlist ORDER BY sortlist RP */ { - yymsp[-5].minor.yy524 = sqlite3ExprAlloc(pParse->db, TK_CAST, &yymsp[-1].minor.yy0, 1); - sqlite3ExprAttachSubtrees(pParse->db, yymsp[-5].minor.yy524, yymsp[-3].minor.yy524, 0); + yylhsminor.yy454 = sqlite3ExprFunction(pParse, yymsp[-4].minor.yy14, &yymsp[-7].minor.yy0, yymsp[-5].minor.yy144); + sqlite3ExprAddFunctionOrderBy(pParse, yylhsminor.yy454, yymsp[-1].minor.yy14); } + yymsp[-7].minor.yy454 = yylhsminor.yy454; break; - case 173: /* expr ::= ID|INDEXED LP distinct exprlist RP */ + case 191: /* expr ::= ID|INDEXED|JOIN_KW LP STAR RP */ { - yylhsminor.yy524 = sqlite3ExprFunction(pParse, yymsp[-1].minor.yy434, &yymsp[-4].minor.yy0, yymsp[-2].minor.yy494); + yylhsminor.yy454 = sqlite3ExprFunction(pParse, 0, &yymsp[-3].minor.yy0, 0); } - yymsp[-4].minor.yy524 = yylhsminor.yy524; + yymsp[-3].minor.yy454 = yylhsminor.yy454; break; - case 174: /* expr ::= ID|INDEXED LP STAR RP */ + case 192: /* expr ::= ID|INDEXED|JOIN_KW LP distinct exprlist RP filter_over */ { - yylhsminor.yy524 = sqlite3ExprFunction(pParse, 0, &yymsp[-3].minor.yy0, 0); + yylhsminor.yy454 = sqlite3ExprFunction(pParse, yymsp[-2].minor.yy14, &yymsp[-5].minor.yy0, yymsp[-3].minor.yy144); + sqlite3WindowAttach(pParse, yylhsminor.yy454, yymsp[0].minor.yy211); } - yymsp[-3].minor.yy524 = yylhsminor.yy524; + yymsp[-5].minor.yy454 = yylhsminor.yy454; break; - case 175: /* expr ::= ID|INDEXED LP distinct exprlist RP over_clause */ + case 193: /* expr ::= ID|INDEXED|JOIN_KW LP distinct exprlist ORDER BY sortlist RP filter_over */ { - yylhsminor.yy524 = sqlite3ExprFunction(pParse, yymsp[-2].minor.yy434, &yymsp[-5].minor.yy0, yymsp[-3].minor.yy494); - sqlite3WindowAttach(pParse, yylhsminor.yy524, yymsp[0].minor.yy295); + yylhsminor.yy454 = sqlite3ExprFunction(pParse, yymsp[-5].minor.yy14, &yymsp[-8].minor.yy0, yymsp[-6].minor.yy144); + sqlite3WindowAttach(pParse, yylhsminor.yy454, yymsp[0].minor.yy211); + sqlite3ExprAddFunctionOrderBy(pParse, yylhsminor.yy454, yymsp[-2].minor.yy14); } - yymsp[-5].minor.yy524 = yylhsminor.yy524; + yymsp[-8].minor.yy454 = yylhsminor.yy454; break; - case 176: /* expr ::= ID|INDEXED LP STAR RP over_clause */ + case 194: /* expr ::= ID|INDEXED|JOIN_KW LP STAR RP filter_over */ { - yylhsminor.yy524 = sqlite3ExprFunction(pParse, 0, &yymsp[-4].minor.yy0, 0); - sqlite3WindowAttach(pParse, yylhsminor.yy524, yymsp[0].minor.yy295); + yylhsminor.yy454 = sqlite3ExprFunction(pParse, 0, &yymsp[-4].minor.yy0, 0); + sqlite3WindowAttach(pParse, yylhsminor.yy454, yymsp[0].minor.yy211); } - yymsp[-4].minor.yy524 = yylhsminor.yy524; + yymsp[-4].minor.yy454 = yylhsminor.yy454; break; - case 177: /* term ::= CTIME_KW */ + case 195: /* term ::= CTIME_KW */ { - yylhsminor.yy524 = sqlite3ExprFunction(pParse, 0, &yymsp[0].minor.yy0, 0); + yylhsminor.yy454 = sqlite3ExprFunction(pParse, 0, &yymsp[0].minor.yy0, 0); } - yymsp[0].minor.yy524 = yylhsminor.yy524; + yymsp[0].minor.yy454 = yylhsminor.yy454; break; - case 178: /* expr ::= LP nexprlist COMMA expr RP */ + case 196: /* expr ::= LP nexprlist COMMA expr RP */ { - ExprList *pList = sqlite3ExprListAppend(pParse, yymsp[-3].minor.yy434, yymsp[-1].minor.yy524); - yymsp[-4].minor.yy524 = sqlite3PExpr(pParse, TK_VECTOR, 0, 0); - if( yymsp[-4].minor.yy524 ){ - yymsp[-4].minor.yy524->x.pList = pList; + ExprList *pList = sqlite3ExprListAppend(pParse, yymsp[-3].minor.yy14, yymsp[-1].minor.yy454); + yymsp[-4].minor.yy454 = sqlite3PExpr(pParse, TK_VECTOR, 0, 0); + if( yymsp[-4].minor.yy454 ){ + int i; + yymsp[-4].minor.yy454->x.pList = pList; + for(i=0; inExpr; i++){ + assert( pList->a[i].pExpr!=0 ); + yymsp[-4].minor.yy454->flags |= pList->a[i].pExpr->flags & EP_Propagate; + } }else{ sqlite3ExprListDelete(pParse->db, pList); } } break; - case 179: /* expr ::= expr AND expr */ - case 180: /* expr ::= expr OR expr */ yytestcase(yyruleno==180); - case 181: /* expr ::= expr LT|GT|GE|LE expr */ yytestcase(yyruleno==181); - case 182: /* expr ::= expr EQ|NE expr */ yytestcase(yyruleno==182); - case 183: /* expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr */ yytestcase(yyruleno==183); - case 184: /* expr ::= expr PLUS|MINUS expr */ yytestcase(yyruleno==184); - case 185: /* expr ::= expr STAR|SLASH|REM expr */ yytestcase(yyruleno==185); - case 186: /* expr ::= expr CONCAT expr */ yytestcase(yyruleno==186); -{yymsp[-2].minor.yy524=sqlite3PExpr(pParse,yymsp[-1].major,yymsp[-2].minor.yy524,yymsp[0].minor.yy524);} + case 197: /* expr ::= expr AND expr */ +{yymsp[-2].minor.yy454=sqlite3ExprAnd(pParse,yymsp[-2].minor.yy454,yymsp[0].minor.yy454);} + break; + case 198: /* expr ::= expr OR expr */ + case 199: /* expr ::= expr LT|GT|GE|LE expr */ yytestcase(yyruleno==199); + case 200: /* expr ::= expr EQ|NE expr */ yytestcase(yyruleno==200); + case 201: /* expr ::= expr BITAND|BITOR|LSHIFT|RSHIFT expr */ yytestcase(yyruleno==201); + case 202: /* expr ::= expr PLUS|MINUS expr */ yytestcase(yyruleno==202); + case 203: /* expr ::= expr STAR|SLASH|REM expr */ yytestcase(yyruleno==203); + case 204: /* expr ::= expr CONCAT expr */ yytestcase(yyruleno==204); +{yymsp[-2].minor.yy454=sqlite3PExpr(pParse,yymsp[-1].major,yymsp[-2].minor.yy454,yymsp[0].minor.yy454);} break; - case 187: /* likeop ::= NOT LIKE_KW|MATCH */ + case 205: /* likeop ::= NOT LIKE_KW|MATCH */ {yymsp[-1].minor.yy0=yymsp[0].minor.yy0; yymsp[-1].minor.yy0.n|=0x80000000; /*yymsp[-1].minor.yy0-overwrite-yymsp[0].minor.yy0*/} break; - case 188: /* expr ::= expr likeop expr */ + case 206: /* expr ::= expr likeop expr */ { ExprList *pList; int bNot = yymsp[-1].minor.yy0.n & 0x80000000; yymsp[-1].minor.yy0.n &= 0x7fffffff; - pList = sqlite3ExprListAppend(pParse,0, yymsp[0].minor.yy524); - pList = sqlite3ExprListAppend(pParse,pList, yymsp[-2].minor.yy524); - yymsp[-2].minor.yy524 = sqlite3ExprFunction(pParse, pList, &yymsp[-1].minor.yy0, 0); - if( bNot ) yymsp[-2].minor.yy524 = sqlite3PExpr(pParse, TK_NOT, yymsp[-2].minor.yy524, 0); - if( yymsp[-2].minor.yy524 ) yymsp[-2].minor.yy524->flags |= EP_InfixFunc; + pList = sqlite3ExprListAppend(pParse,0, yymsp[0].minor.yy454); + pList = sqlite3ExprListAppend(pParse,pList, yymsp[-2].minor.yy454); + yymsp[-2].minor.yy454 = sqlite3ExprFunction(pParse, pList, &yymsp[-1].minor.yy0, 0); + if( bNot ) yymsp[-2].minor.yy454 = sqlite3PExpr(pParse, TK_NOT, yymsp[-2].minor.yy454, 0); + if( yymsp[-2].minor.yy454 ) yymsp[-2].minor.yy454->flags |= EP_InfixFunc; } break; - case 189: /* expr ::= expr likeop expr ESCAPE expr */ + case 207: /* expr ::= expr likeop expr ESCAPE expr */ { ExprList *pList; int bNot = yymsp[-3].minor.yy0.n & 0x80000000; yymsp[-3].minor.yy0.n &= 0x7fffffff; - pList = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy524); - pList = sqlite3ExprListAppend(pParse,pList, yymsp[-4].minor.yy524); - pList = sqlite3ExprListAppend(pParse,pList, yymsp[0].minor.yy524); - yymsp[-4].minor.yy524 = sqlite3ExprFunction(pParse, pList, &yymsp[-3].minor.yy0, 0); - if( bNot ) yymsp[-4].minor.yy524 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy524, 0); - if( yymsp[-4].minor.yy524 ) yymsp[-4].minor.yy524->flags |= EP_InfixFunc; + pList = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy454); + pList = sqlite3ExprListAppend(pParse,pList, yymsp[-4].minor.yy454); + pList = sqlite3ExprListAppend(pParse,pList, yymsp[0].minor.yy454); + yymsp[-4].minor.yy454 = sqlite3ExprFunction(pParse, pList, &yymsp[-3].minor.yy0, 0); + if( bNot ) yymsp[-4].minor.yy454 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy454, 0); + if( yymsp[-4].minor.yy454 ) yymsp[-4].minor.yy454->flags |= EP_InfixFunc; } break; - case 190: /* expr ::= expr ISNULL|NOTNULL */ -{yymsp[-1].minor.yy524 = sqlite3PExpr(pParse,yymsp[0].major,yymsp[-1].minor.yy524,0);} + case 208: /* expr ::= expr ISNULL|NOTNULL */ +{yymsp[-1].minor.yy454 = sqlite3PExprIsNull(pParse,yymsp[0].major,yymsp[-1].minor.yy454);} + break; + case 209: /* expr ::= expr NOT NULL */ +{yymsp[-2].minor.yy454 = sqlite3PExprIsNull(pParse,TK_NOTNULL,yymsp[-2].minor.yy454);} + break; + case 210: /* expr ::= expr IS expr */ +{ + yymsp[-2].minor.yy454 = sqlite3PExprIs(pParse, TK_IS, yymsp[-2].minor.yy454, yymsp[0].minor.yy454); +} break; - case 191: /* expr ::= expr NOT NULL */ -{yymsp[-2].minor.yy524 = sqlite3PExpr(pParse,TK_NOTNULL,yymsp[-2].minor.yy524,0);} + case 211: /* expr ::= expr IS NOT expr */ +{ + yymsp[-3].minor.yy454 = sqlite3PExprIs(pParse, TK_ISNOT, yymsp[-3].minor.yy454, yymsp[0].minor.yy454); +} break; - case 192: /* expr ::= expr IS expr */ + case 212: /* expr ::= expr IS NOT DISTINCT FROM expr */ { - yymsp[-2].minor.yy524 = sqlite3PExpr(pParse,TK_IS,yymsp[-2].minor.yy524,yymsp[0].minor.yy524); - binaryToUnaryIfNull(pParse, yymsp[0].minor.yy524, yymsp[-2].minor.yy524, TK_ISNULL); + yymsp[-5].minor.yy454 = sqlite3PExprIs(pParse, TK_IS, yymsp[-5].minor.yy454, yymsp[0].minor.yy454); } break; - case 193: /* expr ::= expr IS NOT expr */ + case 213: /* expr ::= expr IS DISTINCT FROM expr */ { - yymsp[-3].minor.yy524 = sqlite3PExpr(pParse,TK_ISNOT,yymsp[-3].minor.yy524,yymsp[0].minor.yy524); - binaryToUnaryIfNull(pParse, yymsp[0].minor.yy524, yymsp[-3].minor.yy524, TK_NOTNULL); + yymsp[-4].minor.yy454 = sqlite3PExprIs(pParse, TK_ISNOT, yymsp[-4].minor.yy454, yymsp[0].minor.yy454); } break; - case 194: /* expr ::= NOT expr */ - case 195: /* expr ::= BITNOT expr */ yytestcase(yyruleno==195); -{yymsp[-1].minor.yy524 = sqlite3PExpr(pParse, yymsp[-1].major, yymsp[0].minor.yy524, 0);/*A-overwrites-B*/} + case 214: /* expr ::= NOT expr */ + case 215: /* expr ::= BITNOT expr */ yytestcase(yyruleno==215); +{yymsp[-1].minor.yy454 = sqlite3PExpr(pParse, yymsp[-1].major, yymsp[0].minor.yy454, 0);/*A-overwrites-B*/} + break; + case 216: /* expr ::= PLUS|MINUS expr */ +{ + Expr *p = yymsp[0].minor.yy454; + u8 op = yymsp[-1].major + (TK_UPLUS-TK_PLUS); + assert( TK_UPLUS>TK_PLUS ); + assert( TK_UMINUS == TK_MINUS + (TK_UPLUS - TK_PLUS) ); + if( p && p->op==TK_UPLUS ){ + p->op = op; + yymsp[-1].minor.yy454 = p; + }else{ + yymsp[-1].minor.yy454 = sqlite3PExpr(pParse, op, p, 0); + /*A-overwrites-B*/ + } +} break; - case 196: /* expr ::= PLUS|MINUS expr */ + case 217: /* expr ::= expr PTR expr */ { - yymsp[-1].minor.yy524 = sqlite3PExpr(pParse, yymsp[-1].major==TK_PLUS ? TK_UPLUS : TK_UMINUS, yymsp[0].minor.yy524, 0); - /*A-overwrites-B*/ + ExprList *pList = sqlite3ExprListAppend(pParse, 0, yymsp[-2].minor.yy454); + pList = sqlite3ExprListAppend(pParse, pList, yymsp[0].minor.yy454); + yylhsminor.yy454 = sqlite3ExprFunction(pParse, pList, &yymsp[-1].minor.yy0, 0); } + yymsp[-2].minor.yy454 = yylhsminor.yy454; break; - case 197: /* between_op ::= BETWEEN */ - case 200: /* in_op ::= IN */ yytestcase(yyruleno==200); -{yymsp[0].minor.yy494 = 0;} + case 218: /* between_op ::= BETWEEN */ + case 221: /* in_op ::= IN */ yytestcase(yyruleno==221); +{yymsp[0].minor.yy144 = 0;} break; - case 199: /* expr ::= expr between_op expr AND expr */ + case 220: /* expr ::= expr between_op expr AND expr */ { - ExprList *pList = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy524); - pList = sqlite3ExprListAppend(pParse,pList, yymsp[0].minor.yy524); - yymsp[-4].minor.yy524 = sqlite3PExpr(pParse, TK_BETWEEN, yymsp[-4].minor.yy524, 0); - if( yymsp[-4].minor.yy524 ){ - yymsp[-4].minor.yy524->x.pList = pList; + ExprList *pList = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy454); + pList = sqlite3ExprListAppend(pParse,pList, yymsp[0].minor.yy454); + yymsp[-4].minor.yy454 = sqlite3PExpr(pParse, TK_BETWEEN, yymsp[-4].minor.yy454, 0); + if( yymsp[-4].minor.yy454 ){ + yymsp[-4].minor.yy454->x.pList = pList; + sqlite3ExprSetHeightAndFlags(pParse, yymsp[-4].minor.yy454); }else{ sqlite3ExprListDelete(pParse->db, pList); - } - if( yymsp[-3].minor.yy494 ) yymsp[-4].minor.yy524 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy524, 0); + } + if( yymsp[-3].minor.yy144 ) yymsp[-4].minor.yy454 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy454, 0); } break; - case 202: /* expr ::= expr in_op LP exprlist RP */ + case 223: /* expr ::= expr in_op LP exprlist RP */ { - if( yymsp[-1].minor.yy434==0 ){ + if( yymsp[-1].minor.yy14==0 ){ /* Expressions of the form ** ** expr1 IN () ** expr1 NOT IN () ** - ** simplify to constants 0 (false) and 1 (true), respectively, - ** regardless of the value of expr1. - */ - if( IN_RENAME_OBJECT==0 ){ - sqlite3ExprDelete(pParse->db, yymsp[-4].minor.yy524); - yymsp[-4].minor.yy524 = sqlite3ExprAlloc(pParse->db, TK_INTEGER,&sqlite3IntTokens[yymsp[-3].minor.yy494],1); - } - }else if( yymsp[-1].minor.yy434->nExpr==1 ){ - /* Expressions of the form: - ** - ** expr1 IN (?1) - ** expr1 NOT IN (?2) - ** - ** with exactly one value on the RHS can be simplified to something - ** like this: + ** simplify to constants 0 (false) and 1 (true), respectively. ** - ** expr1 == ?1 - ** expr1 <> ?2 - ** - ** But, the RHS of the == or <> is marked with the EP_Generic flag - ** so that it may not contribute to the computation of comparison - ** affinity or the collating sequence to use for comparison. Otherwise, - ** the semantics would be subtly different from IN or NOT IN. + ** Except, do not apply this optimization if expr1 contains a function + ** because that function might be an aggregate (we don't know yet whether + ** it is or not) and if it is an aggregate, that could change the meaning + ** of the whole query. */ - Expr *pRHS = yymsp[-1].minor.yy434->a[0].pExpr; - yymsp[-1].minor.yy434->a[0].pExpr = 0; - sqlite3ExprListDelete(pParse->db, yymsp[-1].minor.yy434); - /* pRHS cannot be NULL because a malloc error would have been detected - ** before now and control would have never reached this point */ - if( ALWAYS(pRHS) ){ - pRHS->flags &= ~EP_Collate; - pRHS->flags |= EP_Generic; + Expr *pB = sqlite3Expr(pParse->db, TK_STRING, yymsp[-3].minor.yy144 ? "true" : "false"); + if( pB ) sqlite3ExprIdToTrueFalse(pB); + if( !ExprHasProperty(yymsp[-4].minor.yy454, EP_HasFunc) ){ + sqlite3ExprUnmapAndDelete(pParse, yymsp[-4].minor.yy454); + yymsp[-4].minor.yy454 = pB; + }else{ + yymsp[-4].minor.yy454 = sqlite3PExpr(pParse, yymsp[-3].minor.yy144 ? TK_OR : TK_AND, pB, yymsp[-4].minor.yy454); } - yymsp[-4].minor.yy524 = sqlite3PExpr(pParse, yymsp[-3].minor.yy494 ? TK_NE : TK_EQ, yymsp[-4].minor.yy524, pRHS); }else{ - yymsp[-4].minor.yy524 = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy524, 0); - if( yymsp[-4].minor.yy524 ){ - yymsp[-4].minor.yy524->x.pList = yymsp[-1].minor.yy434; - sqlite3ExprSetHeightAndFlags(pParse, yymsp[-4].minor.yy524); + Expr *pRHS = yymsp[-1].minor.yy14->a[0].pExpr; + if( yymsp[-1].minor.yy14->nExpr==1 && sqlite3ExprIsConstant(pParse,pRHS) && yymsp[-4].minor.yy454->op!=TK_VECTOR ){ + yymsp[-1].minor.yy14->a[0].pExpr = 0; + sqlite3ExprListDelete(pParse->db, yymsp[-1].minor.yy14); + pRHS = sqlite3PExpr(pParse, TK_UPLUS, pRHS, 0); + yymsp[-4].minor.yy454 = sqlite3PExpr(pParse, TK_EQ, yymsp[-4].minor.yy454, pRHS); + }else if( yymsp[-1].minor.yy14->nExpr==1 && pRHS->op==TK_SELECT ){ + yymsp[-4].minor.yy454 = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy454, 0); + sqlite3PExprAddSelect(pParse, yymsp[-4].minor.yy454, pRHS->x.pSelect); + pRHS->x.pSelect = 0; + sqlite3ExprListDelete(pParse->db, yymsp[-1].minor.yy14); }else{ - sqlite3ExprListDelete(pParse->db, yymsp[-1].minor.yy434); + yymsp[-4].minor.yy454 = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy454, 0); + if( yymsp[-4].minor.yy454==0 ){ + sqlite3ExprListDelete(pParse->db, yymsp[-1].minor.yy14); + }else if( yymsp[-4].minor.yy454->pLeft->op==TK_VECTOR ){ + int nExpr = yymsp[-4].minor.yy454->pLeft->x.pList->nExpr; + Select *pSelectRHS = sqlite3ExprListToValues(pParse, nExpr, yymsp[-1].minor.yy14); + if( pSelectRHS ){ + parserDoubleLinkSelect(pParse, pSelectRHS); + sqlite3PExprAddSelect(pParse, yymsp[-4].minor.yy454, pSelectRHS); + } + }else{ + yymsp[-4].minor.yy454->x.pList = yymsp[-1].minor.yy14; + sqlite3ExprSetHeightAndFlags(pParse, yymsp[-4].minor.yy454); + } } - if( yymsp[-3].minor.yy494 ) yymsp[-4].minor.yy524 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy524, 0); + if( yymsp[-3].minor.yy144 ) yymsp[-4].minor.yy454 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy454, 0); } } break; - case 203: /* expr ::= LP select RP */ + case 224: /* expr ::= LP select RP */ { - yymsp[-2].minor.yy524 = sqlite3PExpr(pParse, TK_SELECT, 0, 0); - sqlite3PExprAddSelect(pParse, yymsp[-2].minor.yy524, yymsp[-1].minor.yy457); + yymsp[-2].minor.yy454 = sqlite3PExpr(pParse, TK_SELECT, 0, 0); + sqlite3PExprAddSelect(pParse, yymsp[-2].minor.yy454, yymsp[-1].minor.yy555); } break; - case 204: /* expr ::= expr in_op LP select RP */ + case 225: /* expr ::= expr in_op LP select RP */ { - yymsp[-4].minor.yy524 = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy524, 0); - sqlite3PExprAddSelect(pParse, yymsp[-4].minor.yy524, yymsp[-1].minor.yy457); - if( yymsp[-3].minor.yy494 ) yymsp[-4].minor.yy524 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy524, 0); + yymsp[-4].minor.yy454 = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy454, 0); + sqlite3PExprAddSelect(pParse, yymsp[-4].minor.yy454, yymsp[-1].minor.yy555); + if( yymsp[-3].minor.yy144 ) yymsp[-4].minor.yy454 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy454, 0); } break; - case 205: /* expr ::= expr in_op nm dbnm paren_exprlist */ + case 226: /* expr ::= expr in_op nm dbnm paren_exprlist */ { SrcList *pSrc = sqlite3SrcListAppend(pParse, 0,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy0); Select *pSelect = sqlite3SelectNew(pParse, 0,pSrc,0,0,0,0,0,0); - if( yymsp[0].minor.yy434 ) sqlite3SrcListFuncArgs(pParse, pSelect ? pSrc : 0, yymsp[0].minor.yy434); - yymsp[-4].minor.yy524 = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy524, 0); - sqlite3PExprAddSelect(pParse, yymsp[-4].minor.yy524, pSelect); - if( yymsp[-3].minor.yy494 ) yymsp[-4].minor.yy524 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy524, 0); + if( yymsp[0].minor.yy14 ) sqlite3SrcListFuncArgs(pParse, pSelect ? pSrc : 0, yymsp[0].minor.yy14); + yymsp[-4].minor.yy454 = sqlite3PExpr(pParse, TK_IN, yymsp[-4].minor.yy454, 0); + sqlite3PExprAddSelect(pParse, yymsp[-4].minor.yy454, pSelect); + if( yymsp[-3].minor.yy144 ) yymsp[-4].minor.yy454 = sqlite3PExpr(pParse, TK_NOT, yymsp[-4].minor.yy454, 0); } break; - case 206: /* expr ::= EXISTS LP select RP */ + case 227: /* expr ::= EXISTS LP select RP */ { Expr *p; - p = yymsp[-3].minor.yy524 = sqlite3PExpr(pParse, TK_EXISTS, 0, 0); - sqlite3PExprAddSelect(pParse, p, yymsp[-1].minor.yy457); + p = yymsp[-3].minor.yy454 = sqlite3PExpr(pParse, TK_EXISTS, 0, 0); + sqlite3PExprAddSelect(pParse, p, yymsp[-1].minor.yy555); } break; - case 207: /* expr ::= CASE case_operand case_exprlist case_else END */ + case 228: /* expr ::= CASE case_operand case_exprlist case_else END */ { - yymsp[-4].minor.yy524 = sqlite3PExpr(pParse, TK_CASE, yymsp[-3].minor.yy524, 0); - if( yymsp[-4].minor.yy524 ){ - yymsp[-4].minor.yy524->x.pList = yymsp[-1].minor.yy524 ? sqlite3ExprListAppend(pParse,yymsp[-2].minor.yy434,yymsp[-1].minor.yy524) : yymsp[-2].minor.yy434; - sqlite3ExprSetHeightAndFlags(pParse, yymsp[-4].minor.yy524); + yymsp[-4].minor.yy454 = sqlite3PExpr(pParse, TK_CASE, yymsp[-3].minor.yy454, 0); + if( yymsp[-4].minor.yy454 ){ + yymsp[-4].minor.yy454->x.pList = yymsp[-1].minor.yy454 ? sqlite3ExprListAppend(pParse,yymsp[-2].minor.yy14,yymsp[-1].minor.yy454) : yymsp[-2].minor.yy14; + sqlite3ExprSetHeightAndFlags(pParse, yymsp[-4].minor.yy454); }else{ - sqlite3ExprListDelete(pParse->db, yymsp[-2].minor.yy434); - sqlite3ExprDelete(pParse->db, yymsp[-1].minor.yy524); + sqlite3ExprListDelete(pParse->db, yymsp[-2].minor.yy14); + sqlite3ExprDelete(pParse->db, yymsp[-1].minor.yy454); } } break; - case 208: /* case_exprlist ::= case_exprlist WHEN expr THEN expr */ + case 229: /* case_exprlist ::= case_exprlist WHEN expr THEN expr */ { - yymsp[-4].minor.yy434 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy434, yymsp[-2].minor.yy524); - yymsp[-4].minor.yy434 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy434, yymsp[0].minor.yy524); + yymsp[-4].minor.yy14 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy14, yymsp[-2].minor.yy454); + yymsp[-4].minor.yy14 = sqlite3ExprListAppend(pParse,yymsp[-4].minor.yy14, yymsp[0].minor.yy454); } break; - case 209: /* case_exprlist ::= WHEN expr THEN expr */ + case 230: /* case_exprlist ::= WHEN expr THEN expr */ { - yymsp[-3].minor.yy434 = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy524); - yymsp[-3].minor.yy434 = sqlite3ExprListAppend(pParse,yymsp[-3].minor.yy434, yymsp[0].minor.yy524); + yymsp[-3].minor.yy14 = sqlite3ExprListAppend(pParse,0, yymsp[-2].minor.yy454); + yymsp[-3].minor.yy14 = sqlite3ExprListAppend(pParse,yymsp[-3].minor.yy14, yymsp[0].minor.yy454); } break; - case 212: /* case_operand ::= expr */ -{yymsp[0].minor.yy524 = yymsp[0].minor.yy524; /*A-overwrites-X*/} - break; - case 215: /* nexprlist ::= nexprlist COMMA expr */ -{yymsp[-2].minor.yy434 = sqlite3ExprListAppend(pParse,yymsp[-2].minor.yy434,yymsp[0].minor.yy524);} + case 235: /* nexprlist ::= nexprlist COMMA expr */ +{yymsp[-2].minor.yy14 = sqlite3ExprListAppend(pParse,yymsp[-2].minor.yy14,yymsp[0].minor.yy454);} break; - case 216: /* nexprlist ::= expr */ -{yymsp[0].minor.yy434 = sqlite3ExprListAppend(pParse,0,yymsp[0].minor.yy524); /*A-overwrites-Y*/} + case 236: /* nexprlist ::= expr */ +{yymsp[0].minor.yy14 = sqlite3ExprListAppend(pParse,0,yymsp[0].minor.yy454); /*A-overwrites-Y*/} break; - case 218: /* paren_exprlist ::= LP exprlist RP */ - case 223: /* eidlist_opt ::= LP eidlist RP */ yytestcase(yyruleno==223); -{yymsp[-2].minor.yy434 = yymsp[-1].minor.yy434;} + case 238: /* paren_exprlist ::= LP exprlist RP */ + case 243: /* eidlist_opt ::= LP eidlist RP */ yytestcase(yyruleno==243); +{yymsp[-2].minor.yy14 = yymsp[-1].minor.yy14;} break; - case 219: /* cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt */ + case 239: /* cmd ::= createkw uniqueflag INDEX ifnotexists nm dbnm ON nm LP sortlist RP where_opt */ { - sqlite3CreateIndex(pParse, &yymsp[-7].minor.yy0, &yymsp[-6].minor.yy0, - sqlite3SrcListAppend(pParse,0,&yymsp[-4].minor.yy0,0), yymsp[-2].minor.yy434, yymsp[-10].minor.yy494, - &yymsp[-11].minor.yy0, yymsp[0].minor.yy524, SQLITE_SO_ASC, yymsp[-8].minor.yy494, SQLITE_IDXTYPE_APPDEF); + sqlite3CreateIndex(pParse, &yymsp[-7].minor.yy0, &yymsp[-6].minor.yy0, + sqlite3SrcListAppend(pParse,0,&yymsp[-4].minor.yy0,0), yymsp[-2].minor.yy14, yymsp[-10].minor.yy144, + &yymsp[-11].minor.yy0, yymsp[0].minor.yy454, SQLITE_SO_ASC, yymsp[-8].minor.yy144, SQLITE_IDXTYPE_APPDEF); if( IN_RENAME_OBJECT && pParse->pNewIndex ){ sqlite3RenameTokenMap(pParse, pParse->pNewIndex->zName, &yymsp[-4].minor.yy0); } } break; - case 220: /* uniqueflag ::= UNIQUE */ - case 262: /* raisetype ::= ABORT */ yytestcase(yyruleno==262); -{yymsp[0].minor.yy494 = OE_Abort;} + case 240: /* uniqueflag ::= UNIQUE */ + case 281: /* raisetype ::= ABORT */ yytestcase(yyruleno==281); +{yymsp[0].minor.yy144 = OE_Abort;} break; - case 221: /* uniqueflag ::= */ -{yymsp[1].minor.yy494 = OE_None;} + case 241: /* uniqueflag ::= */ +{yymsp[1].minor.yy144 = OE_None;} break; - case 224: /* eidlist ::= eidlist COMMA nm collate sortorder */ + case 244: /* eidlist ::= eidlist COMMA nm collate sortorder */ { - yymsp[-4].minor.yy434 = parserAddExprIdListTerm(pParse, yymsp[-4].minor.yy434, &yymsp[-2].minor.yy0, yymsp[-1].minor.yy494, yymsp[0].minor.yy494); + yymsp[-4].minor.yy14 = parserAddExprIdListTerm(pParse, yymsp[-4].minor.yy14, &yymsp[-2].minor.yy0, yymsp[-1].minor.yy144, yymsp[0].minor.yy144); } break; - case 225: /* eidlist ::= nm collate sortorder */ + case 245: /* eidlist ::= nm collate sortorder */ { - yymsp[-2].minor.yy434 = parserAddExprIdListTerm(pParse, 0, &yymsp[-2].minor.yy0, yymsp[-1].minor.yy494, yymsp[0].minor.yy494); /*A-overwrites-Y*/ + yymsp[-2].minor.yy14 = parserAddExprIdListTerm(pParse, 0, &yymsp[-2].minor.yy0, yymsp[-1].minor.yy144, yymsp[0].minor.yy144); /*A-overwrites-Y*/ } break; - case 228: /* cmd ::= DROP INDEX ifexists fullname */ -{sqlite3DropIndex(pParse, yymsp[0].minor.yy483, yymsp[-1].minor.yy494);} + case 248: /* cmd ::= DROP INDEX ifexists fullname */ +{sqlite3DropIndex(pParse, yymsp[0].minor.yy203, yymsp[-1].minor.yy144);} break; - case 229: /* cmd ::= VACUUM vinto */ -{sqlite3Vacuum(pParse,0,yymsp[0].minor.yy524);} + case 249: /* cmd ::= VACUUM vinto */ +{sqlite3Vacuum(pParse,0,yymsp[0].minor.yy454);} break; - case 230: /* cmd ::= VACUUM nm vinto */ -{sqlite3Vacuum(pParse,&yymsp[-1].minor.yy0,yymsp[0].minor.yy524);} + case 250: /* cmd ::= VACUUM nm vinto */ +{sqlite3Vacuum(pParse,&yymsp[-1].minor.yy0,yymsp[0].minor.yy454);} break; - case 233: /* cmd ::= PRAGMA nm dbnm */ + case 253: /* cmd ::= PRAGMA nm dbnm */ {sqlite3Pragma(pParse,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0,0,0);} break; - case 234: /* cmd ::= PRAGMA nm dbnm EQ nmnum */ + case 254: /* cmd ::= PRAGMA nm dbnm EQ nmnum */ {sqlite3Pragma(pParse,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0,0);} break; - case 235: /* cmd ::= PRAGMA nm dbnm LP nmnum RP */ + case 255: /* cmd ::= PRAGMA nm dbnm LP nmnum RP */ {sqlite3Pragma(pParse,&yymsp[-4].minor.yy0,&yymsp[-3].minor.yy0,&yymsp[-1].minor.yy0,0);} break; - case 236: /* cmd ::= PRAGMA nm dbnm EQ minus_num */ + case 256: /* cmd ::= PRAGMA nm dbnm EQ minus_num */ {sqlite3Pragma(pParse,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy0,1);} break; - case 237: /* cmd ::= PRAGMA nm dbnm LP minus_num RP */ + case 257: /* cmd ::= PRAGMA nm dbnm LP minus_num RP */ {sqlite3Pragma(pParse,&yymsp[-4].minor.yy0,&yymsp[-3].minor.yy0,&yymsp[-1].minor.yy0,1);} break; - case 240: /* cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END */ + case 260: /* cmd ::= createkw trigger_decl BEGIN trigger_cmd_list END */ { Token all; all.z = yymsp[-3].minor.yy0.z; all.n = (int)(yymsp[0].minor.yy0.z - yymsp[-3].minor.yy0.z) + yymsp[0].minor.yy0.n; - sqlite3FinishTrigger(pParse, yymsp[-1].minor.yy455, &all); + sqlite3FinishTrigger(pParse, yymsp[-1].minor.yy427, &all); } break; - case 241: /* trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause */ + case 261: /* trigger_decl ::= temp TRIGGER ifnotexists nm dbnm trigger_time trigger_event ON fullname foreach_clause when_clause */ { - sqlite3BeginTrigger(pParse, &yymsp[-7].minor.yy0, &yymsp[-6].minor.yy0, yymsp[-5].minor.yy494, yymsp[-4].minor.yy90.a, yymsp[-4].minor.yy90.b, yymsp[-2].minor.yy483, yymsp[0].minor.yy524, yymsp[-10].minor.yy494, yymsp[-8].minor.yy494); + sqlite3BeginTrigger(pParse, &yymsp[-7].minor.yy0, &yymsp[-6].minor.yy0, yymsp[-5].minor.yy144, yymsp[-4].minor.yy286.a, yymsp[-4].minor.yy286.b, yymsp[-2].minor.yy203, yymsp[0].minor.yy454, yymsp[-10].minor.yy144, yymsp[-8].minor.yy144); yymsp[-10].minor.yy0 = (yymsp[-6].minor.yy0.n==0?yymsp[-7].minor.yy0:yymsp[-6].minor.yy0); /*A-overwrites-T*/ +#ifdef SQLITE_DEBUG + assert( pParse->isCreate ); /* Set by createkw reduce action */ + pParse->isCreate = 0; /* But, should not be set for CREATE TRIGGER */ +#endif } break; - case 242: /* trigger_time ::= BEFORE|AFTER */ -{ yymsp[0].minor.yy494 = yymsp[0].major; /*A-overwrites-X*/ } + case 262: /* trigger_time ::= BEFORE|AFTER */ +{ yymsp[0].minor.yy144 = yymsp[0].major; /*A-overwrites-X*/ } break; - case 243: /* trigger_time ::= INSTEAD OF */ -{ yymsp[-1].minor.yy494 = TK_INSTEAD;} + case 263: /* trigger_time ::= INSTEAD OF */ +{ yymsp[-1].minor.yy144 = TK_INSTEAD;} break; - case 244: /* trigger_time ::= */ -{ yymsp[1].minor.yy494 = TK_BEFORE; } + case 264: /* trigger_time ::= */ +{ yymsp[1].minor.yy144 = TK_BEFORE; } break; - case 245: /* trigger_event ::= DELETE|INSERT */ - case 246: /* trigger_event ::= UPDATE */ yytestcase(yyruleno==246); -{yymsp[0].minor.yy90.a = yymsp[0].major; /*A-overwrites-X*/ yymsp[0].minor.yy90.b = 0;} + case 265: /* trigger_event ::= DELETE|INSERT */ + case 266: /* trigger_event ::= UPDATE */ yytestcase(yyruleno==266); +{yymsp[0].minor.yy286.a = yymsp[0].major; /*A-overwrites-X*/ yymsp[0].minor.yy286.b = 0;} break; - case 247: /* trigger_event ::= UPDATE OF idlist */ -{yymsp[-2].minor.yy90.a = TK_UPDATE; yymsp[-2].minor.yy90.b = yymsp[0].minor.yy62;} + case 267: /* trigger_event ::= UPDATE OF idlist */ +{yymsp[-2].minor.yy286.a = TK_UPDATE; yymsp[-2].minor.yy286.b = yymsp[0].minor.yy132;} break; - case 248: /* when_clause ::= */ - case 267: /* key_opt ::= */ yytestcase(yyruleno==267); - case 315: /* filter_opt ::= */ yytestcase(yyruleno==315); -{ yymsp[1].minor.yy524 = 0; } + case 268: /* when_clause ::= */ + case 286: /* key_opt ::= */ yytestcase(yyruleno==286); +{ yymsp[1].minor.yy454 = 0; } break; - case 249: /* when_clause ::= WHEN expr */ - case 268: /* key_opt ::= KEY expr */ yytestcase(yyruleno==268); -{ yymsp[-1].minor.yy524 = yymsp[0].minor.yy524; } + case 269: /* when_clause ::= WHEN expr */ + case 287: /* key_opt ::= KEY expr */ yytestcase(yyruleno==287); +{ yymsp[-1].minor.yy454 = yymsp[0].minor.yy454; } break; - case 250: /* trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI */ + case 270: /* trigger_cmd_list ::= trigger_cmd_list trigger_cmd SEMI */ { - assert( yymsp[-2].minor.yy455!=0 ); - yymsp[-2].minor.yy455->pLast->pNext = yymsp[-1].minor.yy455; - yymsp[-2].minor.yy455->pLast = yymsp[-1].minor.yy455; + yymsp[-2].minor.yy427->pLast->pNext = yymsp[-1].minor.yy427; + yymsp[-2].minor.yy427->pLast = yymsp[-1].minor.yy427; } break; - case 251: /* trigger_cmd_list ::= trigger_cmd SEMI */ -{ - assert( yymsp[-1].minor.yy455!=0 ); - yymsp[-1].minor.yy455->pLast = yymsp[-1].minor.yy455; -} - break; - case 252: /* trnm ::= nm DOT nm */ + case 271: /* trigger_cmd_list ::= trigger_cmd SEMI */ { - yymsp[-2].minor.yy0 = yymsp[0].minor.yy0; - sqlite3ErrorMsg(pParse, - "qualified table names are not allowed on INSERT, UPDATE, and DELETE " - "statements within triggers"); + yymsp[-1].minor.yy427->pLast = yymsp[-1].minor.yy427; } break; - case 253: /* tridxby ::= INDEXED BY nm */ + case 272: /* tridxby ::= INDEXED BY nm */ { sqlite3ErrorMsg(pParse, "the INDEXED BY clause is not allowed on UPDATE or DELETE statements " "within triggers"); } break; - case 254: /* tridxby ::= NOT INDEXED */ + case 273: /* tridxby ::= NOT INDEXED */ { sqlite3ErrorMsg(pParse, "the NOT INDEXED clause is not allowed on UPDATE or DELETE statements " "within triggers"); } break; - case 255: /* trigger_cmd ::= UPDATE orconf trnm tridxby SET setlist where_opt scanpt */ -{yylhsminor.yy455 = sqlite3TriggerUpdateStep(pParse, &yymsp[-5].minor.yy0, yymsp[-2].minor.yy434, yymsp[-1].minor.yy524, yymsp[-6].minor.yy494, yymsp[-7].minor.yy0.z, yymsp[0].minor.yy294);} - yymsp[-7].minor.yy455 = yylhsminor.yy455; + case 274: /* trigger_cmd ::= UPDATE orconf xfullname tridxby SET setlist from where_opt scanpt */ +{yylhsminor.yy427 = sqlite3TriggerUpdateStep(pParse, yymsp[-6].minor.yy203, yymsp[-2].minor.yy203, yymsp[-3].minor.yy14, yymsp[-1].minor.yy454, yymsp[-7].minor.yy144, yymsp[-8].minor.yy0.z, yymsp[0].minor.yy168);} + yymsp[-8].minor.yy427 = yylhsminor.yy427; break; - case 256: /* trigger_cmd ::= scanpt insert_cmd INTO trnm idlist_opt select upsert scanpt */ + case 275: /* trigger_cmd ::= scanpt insert_cmd INTO xfullname idlist_opt select upsert scanpt */ { - yylhsminor.yy455 = sqlite3TriggerInsertStep(pParse,&yymsp[-4].minor.yy0,yymsp[-3].minor.yy62,yymsp[-2].minor.yy457,yymsp[-6].minor.yy494,yymsp[-1].minor.yy136,yymsp[-7].minor.yy294,yymsp[0].minor.yy294);/*yylhsminor.yy455-overwrites-yymsp[-6].minor.yy494*/ + yylhsminor.yy427 = sqlite3TriggerInsertStep(pParse,yymsp[-4].minor.yy203,yymsp[-3].minor.yy132,yymsp[-2].minor.yy555,yymsp[-6].minor.yy144,yymsp[-1].minor.yy122,yymsp[-7].minor.yy168,yymsp[0].minor.yy168);/*yylhsminor.yy427-overwrites-yymsp[-6].minor.yy144*/ } - yymsp[-7].minor.yy455 = yylhsminor.yy455; + yymsp[-7].minor.yy427 = yylhsminor.yy427; break; - case 257: /* trigger_cmd ::= DELETE FROM trnm tridxby where_opt scanpt */ -{yylhsminor.yy455 = sqlite3TriggerDeleteStep(pParse, &yymsp[-3].minor.yy0, yymsp[-1].minor.yy524, yymsp[-5].minor.yy0.z, yymsp[0].minor.yy294);} - yymsp[-5].minor.yy455 = yylhsminor.yy455; + case 276: /* trigger_cmd ::= DELETE FROM xfullname tridxby where_opt scanpt */ +{yylhsminor.yy427 = sqlite3TriggerDeleteStep(pParse, yymsp[-3].minor.yy203, yymsp[-1].minor.yy454, yymsp[-5].minor.yy0.z, yymsp[0].minor.yy168);} + yymsp[-5].minor.yy427 = yylhsminor.yy427; break; - case 258: /* trigger_cmd ::= scanpt select scanpt */ -{yylhsminor.yy455 = sqlite3TriggerSelectStep(pParse->db, yymsp[-1].minor.yy457, yymsp[-2].minor.yy294, yymsp[0].minor.yy294); /*yylhsminor.yy455-overwrites-yymsp[-1].minor.yy457*/} - yymsp[-2].minor.yy455 = yylhsminor.yy455; + case 277: /* trigger_cmd ::= scanpt select scanpt */ +{yylhsminor.yy427 = sqlite3TriggerSelectStep(pParse->db, yymsp[-1].minor.yy555, yymsp[-2].minor.yy168, yymsp[0].minor.yy168); /*yylhsminor.yy427-overwrites-yymsp[-1].minor.yy555*/} + yymsp[-2].minor.yy427 = yylhsminor.yy427; break; - case 259: /* expr ::= RAISE LP IGNORE RP */ + case 278: /* expr ::= RAISE LP IGNORE RP */ { - yymsp[-3].minor.yy524 = sqlite3PExpr(pParse, TK_RAISE, 0, 0); - if( yymsp[-3].minor.yy524 ){ - yymsp[-3].minor.yy524->affinity = OE_Ignore; + yymsp[-3].minor.yy454 = sqlite3PExpr(pParse, TK_RAISE, 0, 0); + if( yymsp[-3].minor.yy454 ){ + yymsp[-3].minor.yy454->affExpr = OE_Ignore; } } break; - case 260: /* expr ::= RAISE LP raisetype COMMA nm RP */ + case 279: /* expr ::= RAISE LP raisetype COMMA expr RP */ { - yymsp[-5].minor.yy524 = sqlite3ExprAlloc(pParse->db, TK_RAISE, &yymsp[-1].minor.yy0, 1); - if( yymsp[-5].minor.yy524 ) { - yymsp[-5].minor.yy524->affinity = (char)yymsp[-3].minor.yy494; + yymsp[-5].minor.yy454 = sqlite3PExpr(pParse, TK_RAISE, yymsp[-1].minor.yy454, 0); + if( yymsp[-5].minor.yy454 ) { + yymsp[-5].minor.yy454->affExpr = (char)yymsp[-3].minor.yy144; } } break; - case 261: /* raisetype ::= ROLLBACK */ -{yymsp[0].minor.yy494 = OE_Rollback;} + case 280: /* raisetype ::= ROLLBACK */ +{yymsp[0].minor.yy144 = OE_Rollback;} break; - case 263: /* raisetype ::= FAIL */ -{yymsp[0].minor.yy494 = OE_Fail;} + case 282: /* raisetype ::= FAIL */ +{yymsp[0].minor.yy144 = OE_Fail;} break; - case 264: /* cmd ::= DROP TRIGGER ifexists fullname */ + case 283: /* cmd ::= DROP TRIGGER ifexists fullname */ { - sqlite3DropTrigger(pParse,yymsp[0].minor.yy483,yymsp[-1].minor.yy494); + sqlite3DropTrigger(pParse,yymsp[0].minor.yy203,yymsp[-1].minor.yy144); } break; - case 265: /* cmd ::= ATTACH database_kw_opt expr AS expr key_opt */ + case 284: /* cmd ::= ATTACH database_kw_opt expr AS expr key_opt */ { - sqlite3Attach(pParse, yymsp[-3].minor.yy524, yymsp[-1].minor.yy524, yymsp[0].minor.yy524); + sqlite3Attach(pParse, yymsp[-3].minor.yy454, yymsp[-1].minor.yy454, yymsp[0].minor.yy454); } break; - case 266: /* cmd ::= DETACH database_kw_opt expr */ + case 285: /* cmd ::= DETACH database_kw_opt expr */ { - sqlite3Detach(pParse, yymsp[0].minor.yy524); + sqlite3Detach(pParse, yymsp[0].minor.yy454); } break; - case 269: /* cmd ::= REINDEX */ + case 288: /* cmd ::= REINDEX */ {sqlite3Reindex(pParse, 0, 0);} break; - case 270: /* cmd ::= REINDEX nm dbnm */ + case 289: /* cmd ::= REINDEX nm dbnm */ {sqlite3Reindex(pParse, &yymsp[-1].minor.yy0, &yymsp[0].minor.yy0);} break; - case 271: /* cmd ::= ANALYZE */ + case 290: /* cmd ::= ANALYZE */ {sqlite3Analyze(pParse, 0, 0);} break; - case 272: /* cmd ::= ANALYZE nm dbnm */ + case 291: /* cmd ::= ANALYZE nm dbnm */ {sqlite3Analyze(pParse, &yymsp[-1].minor.yy0, &yymsp[0].minor.yy0);} break; - case 273: /* cmd ::= ALTER TABLE fullname RENAME TO nm */ + case 292: /* cmd ::= ALTER TABLE fullname RENAME TO nm */ { - sqlite3AlterRenameTable(pParse,yymsp[-3].minor.yy483,&yymsp[0].minor.yy0); + sqlite3AlterRenameTable(pParse,yymsp[-3].minor.yy203,&yymsp[0].minor.yy0); } break; - case 274: /* cmd ::= ALTER TABLE add_column_fullname ADD kwcolumn_opt columnname carglist */ + case 293: /* cmd ::= alter_add carglist */ { yymsp[-1].minor.yy0.n = (int)(pParse->sLastToken.z-yymsp[-1].minor.yy0.z) + pParse->sLastToken.n; sqlite3AlterFinishAddColumn(pParse, &yymsp[-1].minor.yy0); } break; - case 275: /* add_column_fullname ::= fullname */ + case 294: /* alter_add ::= ALTER TABLE fullname ADD kwcolumn_opt nm typetoken */ { disableLookaside(pParse); - sqlite3AlterBeginAddColumn(pParse, yymsp[0].minor.yy483); + sqlite3AlterBeginAddColumn(pParse, yymsp[-4].minor.yy203); + sqlite3AddColumn(pParse, yymsp[-1].minor.yy0, yymsp[0].minor.yy0); + yymsp[-6].minor.yy0 = yymsp[-1].minor.yy0; +} + break; + case 295: /* cmd ::= ALTER TABLE fullname DROP kwcolumn_opt nm */ +{ + sqlite3AlterDropColumn(pParse, yymsp[-3].minor.yy203, &yymsp[0].minor.yy0); } break; - case 276: /* cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm */ + case 296: /* cmd ::= ALTER TABLE fullname RENAME kwcolumn_opt nm TO nm */ { - sqlite3AlterRenameColumn(pParse, yymsp[-5].minor.yy483, &yymsp[-2].minor.yy0, &yymsp[0].minor.yy0); + sqlite3AlterRenameColumn(pParse, yymsp[-5].minor.yy203, &yymsp[-2].minor.yy0, &yymsp[0].minor.yy0); } break; - case 277: /* cmd ::= create_vtab */ + case 297: /* cmd ::= ALTER TABLE fullname DROP CONSTRAINT nm */ +{ + sqlite3AlterDropConstraint(pParse, yymsp[-3].minor.yy203, &yymsp[0].minor.yy0, 0); +} + break; + case 298: /* cmd ::= ALTER TABLE fullname ALTER kwcolumn_opt nm DROP NOT NULL */ +{ + sqlite3AlterDropConstraint(pParse, yymsp[-6].minor.yy203, 0, &yymsp[-3].minor.yy0); +} + break; + case 299: /* cmd ::= ALTER TABLE fullname ALTER kwcolumn_opt nm SET NOT NULL onconf */ +{ + sqlite3AlterSetNotNull(pParse, yymsp[-7].minor.yy203, &yymsp[-4].minor.yy0, &yymsp[-2].minor.yy0); +} + break; + case 300: /* cmd ::= ALTER TABLE fullname ADD CONSTRAINT nm CHECK LP expr RP onconf */ +{ + sqlite3AlterAddConstraint(pParse, yymsp[-8].minor.yy203, &yymsp[-6].minor.yy0, &yymsp[-5].minor.yy0, yymsp[-3].minor.yy0.z+1, (yymsp[-1].minor.yy0.z-yymsp[-3].minor.yy0.z-1), yymsp[-2].minor.yy454); +} + break; + case 301: /* cmd ::= ALTER TABLE fullname ADD CHECK LP expr RP onconf */ +{ + sqlite3AlterAddConstraint(pParse, yymsp[-6].minor.yy203, &yymsp[-4].minor.yy0, 0, yymsp[-3].minor.yy0.z+1, (yymsp[-1].minor.yy0.z-yymsp[-3].minor.yy0.z-1), yymsp[-2].minor.yy454); +} + break; + case 302: /* cmd ::= create_vtab */ {sqlite3VtabFinishParse(pParse,0);} break; - case 278: /* cmd ::= create_vtab LP vtabarglist RP */ + case 303: /* cmd ::= create_vtab LP vtabarglist RP */ {sqlite3VtabFinishParse(pParse,&yymsp[0].minor.yy0);} break; - case 279: /* create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm */ + case 304: /* create_vtab ::= createkw VIRTUAL TABLE ifnotexists nm dbnm USING nm */ { - sqlite3VtabBeginParse(pParse, &yymsp[-3].minor.yy0, &yymsp[-2].minor.yy0, &yymsp[0].minor.yy0, yymsp[-4].minor.yy494); + sqlite3VtabBeginParse(pParse, &yymsp[-3].minor.yy0, &yymsp[-2].minor.yy0, &yymsp[0].minor.yy0, yymsp[-4].minor.yy144); } break; - case 280: /* vtabarg ::= */ + case 305: /* vtabarg ::= */ {sqlite3VtabArgInit(pParse);} break; - case 281: /* vtabargtoken ::= ANY */ - case 282: /* vtabargtoken ::= lp anylist RP */ yytestcase(yyruleno==282); - case 283: /* lp ::= LP */ yytestcase(yyruleno==283); + case 306: /* vtabargtoken ::= ANY */ + case 307: /* vtabargtoken ::= lp anylist RP */ yytestcase(yyruleno==307); + case 308: /* lp ::= LP */ yytestcase(yyruleno==308); {sqlite3VtabArgExtend(pParse,&yymsp[0].minor.yy0);} break; - case 284: /* with ::= WITH wqlist */ - case 285: /* with ::= WITH RECURSIVE wqlist */ yytestcase(yyruleno==285); + case 309: /* with ::= WITH wqlist */ + case 310: /* with ::= WITH RECURSIVE wqlist */ yytestcase(yyruleno==310); { sqlite3WithPush(pParse, yymsp[0].minor.yy59, 1); } break; - case 286: /* wqlist ::= nm eidlist_opt AS LP select RP */ + case 311: /* wqas ::= AS */ +{yymsp[0].minor.yy462 = M10d_Any;} + break; + case 312: /* wqas ::= AS MATERIALIZED */ +{yymsp[-1].minor.yy462 = M10d_Yes;} + break; + case 313: /* wqas ::= AS NOT MATERIALIZED */ +{yymsp[-2].minor.yy462 = M10d_No;} + break; + case 314: /* wqitem ::= withnm eidlist_opt wqas LP select RP */ { - yymsp[-5].minor.yy59 = sqlite3WithAdd(pParse, 0, &yymsp[-5].minor.yy0, yymsp[-4].minor.yy434, yymsp[-1].minor.yy457); /*A-overwrites-X*/ + yymsp[-5].minor.yy67 = sqlite3CteNew(pParse, &yymsp[-5].minor.yy0, yymsp[-4].minor.yy14, yymsp[-1].minor.yy555, yymsp[-3].minor.yy462); /*A-overwrites-X*/ } break; - case 287: /* wqlist ::= wqlist COMMA nm eidlist_opt AS LP select RP */ + case 315: /* withnm ::= nm */ +{pParse->bHasWith = 1;} + break; + case 316: /* wqlist ::= wqitem */ { - yymsp[-7].minor.yy59 = sqlite3WithAdd(pParse, yymsp[-7].minor.yy59, &yymsp[-5].minor.yy0, yymsp[-4].minor.yy434, yymsp[-1].minor.yy457); + yymsp[0].minor.yy59 = sqlite3WithAdd(pParse, 0, yymsp[0].minor.yy67); /*A-overwrites-X*/ } break; - case 288: /* windowdefn_list ::= windowdefn */ -{ yylhsminor.yy295 = yymsp[0].minor.yy295; } - yymsp[0].minor.yy295 = yylhsminor.yy295; + case 317: /* wqlist ::= wqlist COMMA wqitem */ +{ + yymsp[-2].minor.yy59 = sqlite3WithAdd(pParse, yymsp[-2].minor.yy59, yymsp[0].minor.yy67); +} break; - case 289: /* windowdefn_list ::= windowdefn_list COMMA windowdefn */ + case 318: /* windowdefn_list ::= windowdefn_list COMMA windowdefn */ { - assert( yymsp[0].minor.yy295!=0 ); - sqlite3WindowChain(pParse, yymsp[0].minor.yy295, yymsp[-2].minor.yy295); - yymsp[0].minor.yy295->pNextWin = yymsp[-2].minor.yy295; - yylhsminor.yy295 = yymsp[0].minor.yy295; + assert( yymsp[0].minor.yy211!=0 ); + sqlite3WindowChain(pParse, yymsp[0].minor.yy211, yymsp[-2].minor.yy211); + yymsp[0].minor.yy211->pNextWin = yymsp[-2].minor.yy211; + yylhsminor.yy211 = yymsp[0].minor.yy211; } - yymsp[-2].minor.yy295 = yylhsminor.yy295; + yymsp[-2].minor.yy211 = yylhsminor.yy211; break; - case 290: /* windowdefn ::= nm AS LP window RP */ + case 319: /* windowdefn ::= nm AS LP window RP */ { - if( ALWAYS(yymsp[-1].minor.yy295) ){ - yymsp[-1].minor.yy295->zName = sqlite3DbStrNDup(pParse->db, yymsp[-4].minor.yy0.z, yymsp[-4].minor.yy0.n); + if( ALWAYS(yymsp[-1].minor.yy211) ){ + yymsp[-1].minor.yy211->zName = sqlite3DbStrNDup(pParse->db, yymsp[-4].minor.yy0.z, yymsp[-4].minor.yy0.n); } - yylhsminor.yy295 = yymsp[-1].minor.yy295; + yylhsminor.yy211 = yymsp[-1].minor.yy211; } - yymsp[-4].minor.yy295 = yylhsminor.yy295; + yymsp[-4].minor.yy211 = yylhsminor.yy211; break; - case 291: /* window ::= PARTITION BY nexprlist orderby_opt frame_opt */ + case 320: /* window ::= PARTITION BY nexprlist orderby_opt frame_opt */ { - yymsp[-4].minor.yy295 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy295, yymsp[-2].minor.yy434, yymsp[-1].minor.yy434, 0); + yymsp[-4].minor.yy211 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy211, yymsp[-2].minor.yy14, yymsp[-1].minor.yy14, 0); } break; - case 292: /* window ::= nm PARTITION BY nexprlist orderby_opt frame_opt */ + case 321: /* window ::= nm PARTITION BY nexprlist orderby_opt frame_opt */ { - yylhsminor.yy295 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy295, yymsp[-2].minor.yy434, yymsp[-1].minor.yy434, &yymsp[-5].minor.yy0); + yylhsminor.yy211 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy211, yymsp[-2].minor.yy14, yymsp[-1].minor.yy14, &yymsp[-5].minor.yy0); } - yymsp[-5].minor.yy295 = yylhsminor.yy295; + yymsp[-5].minor.yy211 = yylhsminor.yy211; break; - case 293: /* window ::= ORDER BY sortlist frame_opt */ + case 322: /* window ::= ORDER BY sortlist frame_opt */ { - yymsp[-3].minor.yy295 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy295, 0, yymsp[-1].minor.yy434, 0); + yymsp[-3].minor.yy211 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy211, 0, yymsp[-1].minor.yy14, 0); } break; - case 294: /* window ::= nm ORDER BY sortlist frame_opt */ + case 323: /* window ::= nm ORDER BY sortlist frame_opt */ { - yylhsminor.yy295 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy295, 0, yymsp[-1].minor.yy434, &yymsp[-4].minor.yy0); + yylhsminor.yy211 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy211, 0, yymsp[-1].minor.yy14, &yymsp[-4].minor.yy0); } - yymsp[-4].minor.yy295 = yylhsminor.yy295; + yymsp[-4].minor.yy211 = yylhsminor.yy211; break; - case 295: /* window ::= frame_opt */ + case 324: /* window ::= nm frame_opt */ { - yylhsminor.yy295 = yymsp[0].minor.yy295; + yylhsminor.yy211 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy211, 0, 0, &yymsp[-1].minor.yy0); } - yymsp[0].minor.yy295 = yylhsminor.yy295; + yymsp[-1].minor.yy211 = yylhsminor.yy211; break; - case 296: /* window ::= nm frame_opt */ + case 325: /* frame_opt ::= */ { - yylhsminor.yy295 = sqlite3WindowAssemble(pParse, yymsp[0].minor.yy295, 0, 0, &yymsp[-1].minor.yy0); + yymsp[1].minor.yy211 = sqlite3WindowAlloc(pParse, 0, TK_UNBOUNDED, 0, TK_CURRENT, 0, 0); } - yymsp[-1].minor.yy295 = yylhsminor.yy295; break; - case 297: /* frame_opt ::= */ -{ - yymsp[1].minor.yy295 = sqlite3WindowAlloc(pParse, 0, TK_UNBOUNDED, 0, TK_CURRENT, 0, 0); + case 326: /* frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt */ +{ + yylhsminor.yy211 = sqlite3WindowAlloc(pParse, yymsp[-2].minor.yy144, yymsp[-1].minor.yy509.eType, yymsp[-1].minor.yy509.pExpr, TK_CURRENT, 0, yymsp[0].minor.yy462); } + yymsp[-2].minor.yy211 = yylhsminor.yy211; break; - case 298: /* frame_opt ::= range_or_rows frame_bound_s frame_exclude_opt */ -{ - yylhsminor.yy295 = sqlite3WindowAlloc(pParse, yymsp[-2].minor.yy494, yymsp[-1].minor.yy201.eType, yymsp[-1].minor.yy201.pExpr, TK_CURRENT, 0, yymsp[0].minor.yy238); + case 327: /* frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt */ +{ + yylhsminor.yy211 = sqlite3WindowAlloc(pParse, yymsp[-5].minor.yy144, yymsp[-3].minor.yy509.eType, yymsp[-3].minor.yy509.pExpr, yymsp[-1].minor.yy509.eType, yymsp[-1].minor.yy509.pExpr, yymsp[0].minor.yy462); } - yymsp[-2].minor.yy295 = yylhsminor.yy295; + yymsp[-5].minor.yy211 = yylhsminor.yy211; break; - case 299: /* frame_opt ::= range_or_rows BETWEEN frame_bound_s AND frame_bound_e frame_exclude_opt */ -{ - yylhsminor.yy295 = sqlite3WindowAlloc(pParse, yymsp[-5].minor.yy494, yymsp[-3].minor.yy201.eType, yymsp[-3].minor.yy201.pExpr, yymsp[-1].minor.yy201.eType, yymsp[-1].minor.yy201.pExpr, yymsp[0].minor.yy238); -} - yymsp[-5].minor.yy295 = yylhsminor.yy295; + case 329: /* frame_bound_s ::= frame_bound */ + case 331: /* frame_bound_e ::= frame_bound */ yytestcase(yyruleno==331); +{yylhsminor.yy509 = yymsp[0].minor.yy509;} + yymsp[0].minor.yy509 = yylhsminor.yy509; break; - case 301: /* frame_bound_s ::= frame_bound */ - case 303: /* frame_bound_e ::= frame_bound */ yytestcase(yyruleno==303); -{yylhsminor.yy201 = yymsp[0].minor.yy201;} - yymsp[0].minor.yy201 = yylhsminor.yy201; + case 330: /* frame_bound_s ::= UNBOUNDED PRECEDING */ + case 332: /* frame_bound_e ::= UNBOUNDED FOLLOWING */ yytestcase(yyruleno==332); + case 334: /* frame_bound ::= CURRENT ROW */ yytestcase(yyruleno==334); +{yylhsminor.yy509.eType = yymsp[-1].major; yylhsminor.yy509.pExpr = 0;} + yymsp[-1].minor.yy509 = yylhsminor.yy509; break; - case 302: /* frame_bound_s ::= UNBOUNDED PRECEDING */ - case 304: /* frame_bound_e ::= UNBOUNDED FOLLOWING */ yytestcase(yyruleno==304); - case 306: /* frame_bound ::= CURRENT ROW */ yytestcase(yyruleno==306); -{yylhsminor.yy201.eType = yymsp[-1].major; yylhsminor.yy201.pExpr = 0;} - yymsp[-1].minor.yy201 = yylhsminor.yy201; + case 333: /* frame_bound ::= expr PRECEDING|FOLLOWING */ +{yylhsminor.yy509.eType = yymsp[0].major; yylhsminor.yy509.pExpr = yymsp[-1].minor.yy454;} + yymsp[-1].minor.yy509 = yylhsminor.yy509; break; - case 305: /* frame_bound ::= expr PRECEDING|FOLLOWING */ -{yylhsminor.yy201.eType = yymsp[0].major; yylhsminor.yy201.pExpr = yymsp[-1].minor.yy524;} - yymsp[-1].minor.yy201 = yylhsminor.yy201; + case 335: /* frame_exclude_opt ::= */ +{yymsp[1].minor.yy462 = 0;} break; - case 307: /* frame_exclude_opt ::= */ -{yymsp[1].minor.yy238 = 0;} + case 336: /* frame_exclude_opt ::= EXCLUDE frame_exclude */ +{yymsp[-1].minor.yy462 = yymsp[0].minor.yy462;} break; - case 308: /* frame_exclude_opt ::= EXCLUDE frame_exclude */ -{yymsp[-1].minor.yy238 = yymsp[0].minor.yy238;} + case 337: /* frame_exclude ::= NO OTHERS */ + case 338: /* frame_exclude ::= CURRENT ROW */ yytestcase(yyruleno==338); +{yymsp[-1].minor.yy462 = yymsp[-1].major; /*A-overwrites-X*/} break; - case 309: /* frame_exclude ::= NO OTHERS */ - case 310: /* frame_exclude ::= CURRENT ROW */ yytestcase(yyruleno==310); -{yymsp[-1].minor.yy238 = yymsp[-1].major; /*A-overwrites-X*/} + case 339: /* frame_exclude ::= GROUP|TIES */ +{yymsp[0].minor.yy462 = yymsp[0].major; /*A-overwrites-X*/} break; - case 311: /* frame_exclude ::= GROUP|TIES */ -{yymsp[0].minor.yy238 = yymsp[0].major; /*A-overwrites-X*/} + case 340: /* window_clause ::= WINDOW windowdefn_list */ +{ yymsp[-1].minor.yy211 = yymsp[0].minor.yy211; } break; - case 312: /* window_clause ::= WINDOW windowdefn_list */ -{ yymsp[-1].minor.yy295 = yymsp[0].minor.yy295; } + case 341: /* filter_over ::= filter_clause over_clause */ +{ + if( yymsp[0].minor.yy211 ){ + yymsp[0].minor.yy211->pFilter = yymsp[-1].minor.yy454; + }else{ + sqlite3ExprDelete(pParse->db, yymsp[-1].minor.yy454); + } + yylhsminor.yy211 = yymsp[0].minor.yy211; +} + yymsp[-1].minor.yy211 = yylhsminor.yy211; break; - case 313: /* over_clause ::= filter_opt OVER LP window RP */ + case 342: /* filter_over ::= over_clause */ { - yylhsminor.yy295 = yymsp[-1].minor.yy295; - assert( yylhsminor.yy295!=0 ); - yylhsminor.yy295->pFilter = yymsp[-4].minor.yy524; + yylhsminor.yy211 = yymsp[0].minor.yy211; } - yymsp[-4].minor.yy295 = yylhsminor.yy295; + yymsp[0].minor.yy211 = yylhsminor.yy211; break; - case 314: /* over_clause ::= filter_opt OVER nm */ + case 343: /* filter_over ::= filter_clause */ { - yylhsminor.yy295 = (Window*)sqlite3DbMallocZero(pParse->db, sizeof(Window)); - if( yylhsminor.yy295 ){ - yylhsminor.yy295->zName = sqlite3DbStrNDup(pParse->db, yymsp[0].minor.yy0.z, yymsp[0].minor.yy0.n); - yylhsminor.yy295->pFilter = yymsp[-2].minor.yy524; + yylhsminor.yy211 = (Window*)sqlite3DbMallocZero(pParse->db, sizeof(Window)); + if( yylhsminor.yy211 ){ + yylhsminor.yy211->eFrmType = TK_FILTER; + yylhsminor.yy211->pFilter = yymsp[0].minor.yy454; }else{ - sqlite3ExprDelete(pParse->db, yymsp[-2].minor.yy524); + sqlite3ExprDelete(pParse->db, yymsp[0].minor.yy454); } } - yymsp[-2].minor.yy295 = yylhsminor.yy295; + yymsp[0].minor.yy211 = yylhsminor.yy211; break; - case 316: /* filter_opt ::= FILTER LP WHERE expr RP */ -{ yymsp[-4].minor.yy524 = yymsp[-1].minor.yy524; } + case 344: /* over_clause ::= OVER LP window RP */ +{ + yymsp[-3].minor.yy211 = yymsp[-1].minor.yy211; + assert( yymsp[-3].minor.yy211!=0 ); +} + break; + case 345: /* over_clause ::= OVER nm */ +{ + yymsp[-1].minor.yy211 = (Window*)sqlite3DbMallocZero(pParse->db, sizeof(Window)); + if( yymsp[-1].minor.yy211 ){ + yymsp[-1].minor.yy211->zName = sqlite3DbStrNDup(pParse->db, yymsp[0].minor.yy0.z, yymsp[0].minor.yy0.n); + } +} + break; + case 346: /* filter_clause ::= FILTER LP WHERE expr RP */ +{ yymsp[-4].minor.yy454 = yymsp[-1].minor.yy454; } + break; + case 347: /* term ::= QNUMBER */ +{ + yylhsminor.yy454=tokenExpr(pParse,yymsp[0].major,yymsp[0].minor.yy0); + sqlite3DequoteNumber(pParse, yylhsminor.yy454); +} + yymsp[0].minor.yy454 = yylhsminor.yy454; break; default: - /* (317) input ::= cmdlist */ yytestcase(yyruleno==317); - /* (318) cmdlist ::= cmdlist ecmd */ yytestcase(yyruleno==318); - /* (319) cmdlist ::= ecmd (OPTIMIZED OUT) */ assert(yyruleno!=319); - /* (320) ecmd ::= SEMI */ yytestcase(yyruleno==320); - /* (321) ecmd ::= cmdx SEMI */ yytestcase(yyruleno==321); - /* (322) ecmd ::= explain cmdx */ yytestcase(yyruleno==322); - /* (323) trans_opt ::= */ yytestcase(yyruleno==323); - /* (324) trans_opt ::= TRANSACTION */ yytestcase(yyruleno==324); - /* (325) trans_opt ::= TRANSACTION nm */ yytestcase(yyruleno==325); - /* (326) savepoint_opt ::= SAVEPOINT */ yytestcase(yyruleno==326); - /* (327) savepoint_opt ::= */ yytestcase(yyruleno==327); - /* (328) cmd ::= create_table create_table_args */ yytestcase(yyruleno==328); - /* (329) columnlist ::= columnlist COMMA columnname carglist */ yytestcase(yyruleno==329); - /* (330) columnlist ::= columnname carglist */ yytestcase(yyruleno==330); - /* (331) nm ::= ID|INDEXED */ yytestcase(yyruleno==331); - /* (332) nm ::= STRING */ yytestcase(yyruleno==332); - /* (333) nm ::= JOIN_KW */ yytestcase(yyruleno==333); - /* (334) typetoken ::= typename */ yytestcase(yyruleno==334); - /* (335) typename ::= ID|STRING */ yytestcase(yyruleno==335); - /* (336) signed ::= plus_num (OPTIMIZED OUT) */ assert(yyruleno!=336); - /* (337) signed ::= minus_num (OPTIMIZED OUT) */ assert(yyruleno!=337); - /* (338) carglist ::= carglist ccons */ yytestcase(yyruleno==338); - /* (339) carglist ::= */ yytestcase(yyruleno==339); - /* (340) ccons ::= NULL onconf */ yytestcase(yyruleno==340); - /* (341) conslist_opt ::= COMMA conslist */ yytestcase(yyruleno==341); - /* (342) conslist ::= conslist tconscomma tcons */ yytestcase(yyruleno==342); - /* (343) conslist ::= tcons (OPTIMIZED OUT) */ assert(yyruleno!=343); - /* (344) tconscomma ::= */ yytestcase(yyruleno==344); - /* (345) defer_subclause_opt ::= defer_subclause (OPTIMIZED OUT) */ assert(yyruleno!=345); - /* (346) resolvetype ::= raisetype (OPTIMIZED OUT) */ assert(yyruleno!=346); - /* (347) selectnowith ::= oneselect (OPTIMIZED OUT) */ assert(yyruleno!=347); - /* (348) oneselect ::= values */ yytestcase(yyruleno==348); - /* (349) sclp ::= selcollist COMMA */ yytestcase(yyruleno==349); - /* (350) as ::= ID|STRING */ yytestcase(yyruleno==350); - /* (351) expr ::= term (OPTIMIZED OUT) */ assert(yyruleno!=351); - /* (352) likeop ::= LIKE_KW|MATCH */ yytestcase(yyruleno==352); - /* (353) exprlist ::= nexprlist */ yytestcase(yyruleno==353); - /* (354) nmnum ::= plus_num (OPTIMIZED OUT) */ assert(yyruleno!=354); - /* (355) nmnum ::= nm (OPTIMIZED OUT) */ assert(yyruleno!=355); - /* (356) nmnum ::= ON */ yytestcase(yyruleno==356); - /* (357) nmnum ::= DELETE */ yytestcase(yyruleno==357); - /* (358) nmnum ::= DEFAULT */ yytestcase(yyruleno==358); - /* (359) plus_num ::= INTEGER|FLOAT */ yytestcase(yyruleno==359); - /* (360) foreach_clause ::= */ yytestcase(yyruleno==360); - /* (361) foreach_clause ::= FOR EACH ROW */ yytestcase(yyruleno==361); - /* (362) trnm ::= nm */ yytestcase(yyruleno==362); - /* (363) tridxby ::= */ yytestcase(yyruleno==363); - /* (364) database_kw_opt ::= DATABASE */ yytestcase(yyruleno==364); - /* (365) database_kw_opt ::= */ yytestcase(yyruleno==365); - /* (366) kwcolumn_opt ::= */ yytestcase(yyruleno==366); - /* (367) kwcolumn_opt ::= COLUMNKW */ yytestcase(yyruleno==367); - /* (368) vtabarglist ::= vtabarg */ yytestcase(yyruleno==368); - /* (369) vtabarglist ::= vtabarglist COMMA vtabarg */ yytestcase(yyruleno==369); - /* (370) vtabarg ::= vtabarg vtabargtoken */ yytestcase(yyruleno==370); - /* (371) anylist ::= */ yytestcase(yyruleno==371); - /* (372) anylist ::= anylist LP anylist RP */ yytestcase(yyruleno==372); - /* (373) anylist ::= anylist ANY */ yytestcase(yyruleno==373); - /* (374) with ::= */ yytestcase(yyruleno==374); + /* (348) input ::= cmdlist */ yytestcase(yyruleno==348); + /* (349) cmdlist ::= cmdlist ecmd */ yytestcase(yyruleno==349); + /* (350) cmdlist ::= ecmd (OPTIMIZED OUT) */ assert(yyruleno!=350); + /* (351) ecmd ::= SEMI */ yytestcase(yyruleno==351); + /* (352) ecmd ::= cmdx SEMI */ yytestcase(yyruleno==352); + /* (353) ecmd ::= explain cmdx SEMI (NEVER REDUCES) */ assert(yyruleno!=353); + /* (354) trans_opt ::= */ yytestcase(yyruleno==354); + /* (355) trans_opt ::= TRANSACTION */ yytestcase(yyruleno==355); + /* (356) trans_opt ::= TRANSACTION nm */ yytestcase(yyruleno==356); + /* (357) savepoint_opt ::= SAVEPOINT */ yytestcase(yyruleno==357); + /* (358) savepoint_opt ::= */ yytestcase(yyruleno==358); + /* (359) cmd ::= create_table create_table_args */ yytestcase(yyruleno==359); + /* (360) table_option_set ::= table_option (OPTIMIZED OUT) */ assert(yyruleno!=360); + /* (361) columnlist ::= columnlist COMMA columnname carglist */ yytestcase(yyruleno==361); + /* (362) columnlist ::= columnname carglist */ yytestcase(yyruleno==362); + /* (363) nm ::= ID|INDEXED|JOIN_KW */ yytestcase(yyruleno==363); + /* (364) nm ::= STRING */ yytestcase(yyruleno==364); + /* (365) typetoken ::= typename */ yytestcase(yyruleno==365); + /* (366) typename ::= ID|STRING */ yytestcase(yyruleno==366); + /* (367) signed ::= plus_num (OPTIMIZED OUT) */ assert(yyruleno!=367); + /* (368) signed ::= minus_num (OPTIMIZED OUT) */ assert(yyruleno!=368); + /* (369) carglist ::= carglist ccons */ yytestcase(yyruleno==369); + /* (370) carglist ::= */ yytestcase(yyruleno==370); + /* (371) ccons ::= NULL onconf */ yytestcase(yyruleno==371); + /* (372) ccons ::= GENERATED ALWAYS AS generated */ yytestcase(yyruleno==372); + /* (373) ccons ::= AS generated */ yytestcase(yyruleno==373); + /* (374) conslist_opt ::= COMMA conslist */ yytestcase(yyruleno==374); + /* (375) conslist ::= conslist tconscomma tcons */ yytestcase(yyruleno==375); + /* (376) conslist ::= tcons (OPTIMIZED OUT) */ assert(yyruleno!=376); + /* (377) tconscomma ::= */ yytestcase(yyruleno==377); + /* (378) defer_subclause_opt ::= defer_subclause (OPTIMIZED OUT) */ assert(yyruleno!=378); + /* (379) resolvetype ::= raisetype (OPTIMIZED OUT) */ assert(yyruleno!=379); + /* (380) selectnowith ::= oneselect (OPTIMIZED OUT) */ assert(yyruleno!=380); + /* (381) oneselect ::= values */ yytestcase(yyruleno==381); + /* (382) sclp ::= selcollist COMMA */ yytestcase(yyruleno==382); + /* (383) as ::= ID|STRING */ yytestcase(yyruleno==383); + /* (384) indexed_opt ::= indexed_by (OPTIMIZED OUT) */ assert(yyruleno!=384); + /* (385) returning ::= */ yytestcase(yyruleno==385); + /* (386) expr ::= term (OPTIMIZED OUT) */ assert(yyruleno!=386); + /* (387) likeop ::= LIKE_KW|MATCH */ yytestcase(yyruleno==387); + /* (388) case_operand ::= expr */ yytestcase(yyruleno==388); + /* (389) exprlist ::= nexprlist */ yytestcase(yyruleno==389); + /* (390) nmnum ::= plus_num (OPTIMIZED OUT) */ assert(yyruleno!=390); + /* (391) nmnum ::= nm (OPTIMIZED OUT) */ assert(yyruleno!=391); + /* (392) nmnum ::= ON */ yytestcase(yyruleno==392); + /* (393) nmnum ::= DELETE */ yytestcase(yyruleno==393); + /* (394) nmnum ::= DEFAULT */ yytestcase(yyruleno==394); + /* (395) plus_num ::= INTEGER|FLOAT */ yytestcase(yyruleno==395); + /* (396) foreach_clause ::= */ yytestcase(yyruleno==396); + /* (397) foreach_clause ::= FOR EACH ROW */ yytestcase(yyruleno==397); + /* (398) tridxby ::= */ yytestcase(yyruleno==398); + /* (399) database_kw_opt ::= DATABASE */ yytestcase(yyruleno==399); + /* (400) database_kw_opt ::= */ yytestcase(yyruleno==400); + /* (401) kwcolumn_opt ::= */ yytestcase(yyruleno==401); + /* (402) kwcolumn_opt ::= COLUMNKW */ yytestcase(yyruleno==402); + /* (403) vtabarglist ::= vtabarg */ yytestcase(yyruleno==403); + /* (404) vtabarglist ::= vtabarglist COMMA vtabarg */ yytestcase(yyruleno==404); + /* (405) vtabarg ::= vtabarg vtabargtoken */ yytestcase(yyruleno==405); + /* (406) anylist ::= */ yytestcase(yyruleno==406); + /* (407) anylist ::= anylist LP anylist RP */ yytestcase(yyruleno==407); + /* (408) anylist ::= anylist ANY */ yytestcase(yyruleno==408); + /* (409) with ::= */ yytestcase(yyruleno==409); + /* (410) windowdefn_list ::= windowdefn (OPTIMIZED OUT) */ assert(yyruleno!=410); + /* (411) window ::= frame_opt (OPTIMIZED OUT) */ assert(yyruleno!=411); break; /********** End reduce actions ************************************************/ }; @@ -152781,7 +185319,7 @@ static void yy_syntax_error( UNUSED_PARAMETER(yymajor); /* Silence some compiler warnings */ if( TOKEN.z[0] ){ - sqlite3ErrorMsg(pParse, "near \"%T\": syntax error", &TOKEN); + parserSyntaxError(pParse, &TOKEN); }else{ sqlite3ErrorMsg(pParse, "incomplete input"); } @@ -152870,12 +185408,49 @@ SQLITE_PRIVATE void sqlite3Parser( } #endif - do{ + while(1){ /* Exit by "break" */ + assert( yypParser->yytos>=yypParser->yystack ); assert( yyact==yypParser->yytos->stateno ); yyact = yy_find_shift_action((YYCODETYPE)yymajor,yyact); if( yyact >= YY_MIN_REDUCE ){ - yyact = yy_reduce(yypParser,yyact-YY_MIN_REDUCE,yymajor, - yyminor sqlite3ParserCTX_PARAM); + unsigned int yyruleno = yyact - YY_MIN_REDUCE; /* Reduce by this rule */ +#ifndef NDEBUG + assert( yyruleno<(int)(sizeof(yyRuleName)/sizeof(yyRuleName[0])) ); + if( yyTraceFILE ){ + int yysize = yyRuleInfoNRhs[yyruleno]; + if( yysize ){ + fprintf(yyTraceFILE, "%sReduce %d [%s]%s, pop back to state %d.\n", + yyTracePrompt, + yyruleno, yyRuleName[yyruleno], + yyrulenoyytos[yysize].stateno); + }else{ + fprintf(yyTraceFILE, "%sReduce %d [%s]%s.\n", + yyTracePrompt, yyruleno, yyRuleName[yyruleno], + yyrulenoyytos - yypParser->yystack)>yypParser->yyhwm ){ + yypParser->yyhwm++; + assert( yypParser->yyhwm == + (int)(yypParser->yytos - yypParser->yystack)); + } +#endif + if( yypParser->yytos>=yypParser->yystackEnd ){ + if( yyGrowStack(yypParser) ){ + yyStackOverflow(yypParser); + break; + } + } + } + yyact = yy_reduce(yypParser,yyruleno,yymajor,yyminor sqlite3ParserCTX_PARAM); }else if( yyact <= YY_MAX_SHIFTREDUCE ){ yy_shift(yypParser,yyact,(YYCODETYPE)yymajor,yyminor); #ifndef YYNOERRORRECOVERY @@ -152900,7 +185475,7 @@ SQLITE_PRIVATE void sqlite3Parser( #ifdef YYERRORSYMBOL /* A syntax error has occurred. ** The response to an error depends upon whether or not the - ** grammar defines an error token "ERROR". + ** grammar defines an error token "ERROR". ** ** This is what we do if the grammar does define ERROR: ** @@ -152931,14 +185506,13 @@ SQLITE_PRIVATE void sqlite3Parser( yy_destructor(yypParser, (YYCODETYPE)yymajor, &yyminorunion); yymajor = YYNOCODE; }else{ - while( yypParser->yytos >= yypParser->yystack - && (yyact = yy_find_reduce_action( - yypParser->yytos->stateno, - YYERRORSYMBOL)) > YY_MAX_SHIFTREDUCE - ){ + while( yypParser->yytos > yypParser->yystack ){ + yyact = yy_find_reduce_action(yypParser->yytos->stateno, + YYERRORSYMBOL); + if( yyact<=YY_MAX_SHIFTREDUCE ) break; yy_pop_parser_stack(yypParser); } - if( yypParser->yytos < yypParser->yystack || yymajor==0 ){ + if( yypParser->yytos <= yypParser->yystack || yymajor==0 ){ yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); yy_parse_failed(yypParser); #ifndef YYNOERRORRECOVERY @@ -152988,7 +185562,7 @@ SQLITE_PRIVATE void sqlite3Parser( break; #endif } - }while( yypParser->yytos>yypParser->yystack ); + } #ifndef NDEBUG if( yyTraceFILE ){ yyStackEntry *i; @@ -153010,13 +185584,12 @@ SQLITE_PRIVATE void sqlite3Parser( */ SQLITE_PRIVATE int sqlite3ParserFallback(int iToken){ #ifdef YYFALLBACK - if( iToken<(int)(sizeof(yyFallback)/sizeof(yyFallback[0])) ){ - return yyFallback[iToken]; - } + assert( iToken<(int)(sizeof(yyFallback)/sizeof(yyFallback[0])) ); + return yyFallback[iToken]; #else (void)iToken; -#endif return 0; +#endif } /************** End of parse.c ***********************************************/ @@ -153050,8 +185623,8 @@ SQLITE_PRIVATE int sqlite3ParserFallback(int iToken){ ** all of them need to be used within the switch. */ #define CC_X 0 /* The letter 'x', or start of BLOB literal */ -#define CC_KYWD 1 /* Alphabetics or '_'. Usable in a keyword */ -#define CC_ID 2 /* unicode characters usable in IDs */ +#define CC_KYWD0 1 /* First letter of a keyword */ +#define CC_KYWD 2 /* Alphabetics or '_'. Usable in a keyword */ #define CC_DIGIT 3 /* Digits */ #define CC_DOLLAR 4 /* '$' */ #define CC_VARALPHA 5 /* '@', '#', ':'. Alphabetic SQL variables */ @@ -153076,47 +185649,49 @@ SQLITE_PRIVATE int sqlite3ParserFallback(int iToken){ #define CC_AND 24 /* '&' */ #define CC_TILDA 25 /* '~' */ #define CC_DOT 26 /* '.' */ -#define CC_ILLEGAL 27 /* Illegal character */ -#define CC_NUL 28 /* 0x00 */ +#define CC_ID 27 /* unicode characters usable in IDs */ +#define CC_ILLEGAL 28 /* Illegal character */ +#define CC_NUL 29 /* 0x00 */ +#define CC_BOM 30 /* First byte of UTF8 BOM: 0xEF 0xBB 0xBF */ static const unsigned char aiClass[] = { #ifdef SQLITE_ASCII /* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xa xb xc xd xe xf */ -/* 0x */ 28, 27, 27, 27, 27, 27, 27, 27, 27, 7, 7, 27, 7, 7, 27, 27, -/* 1x */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, +/* 0x */ 29, 28, 28, 28, 28, 28, 28, 28, 28, 7, 7, 28, 7, 7, 28, 28, +/* 1x */ 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, /* 2x */ 7, 15, 8, 5, 4, 22, 24, 8, 17, 18, 21, 20, 23, 11, 26, 16, /* 3x */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 5, 19, 12, 14, 13, 6, /* 4x */ 5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -/* 5x */ 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 9, 27, 27, 27, 1, +/* 5x */ 1, 1, 1, 1, 1, 1, 1, 1, 0, 2, 2, 9, 28, 28, 28, 2, /* 6x */ 8, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -/* 7x */ 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 27, 10, 27, 25, 27, -/* 8x */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, -/* 9x */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, -/* Ax */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, -/* Bx */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, -/* Cx */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, -/* Dx */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, -/* Ex */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, -/* Fx */ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 +/* 7x */ 1, 1, 1, 1, 1, 1, 1, 1, 0, 2, 2, 28, 10, 28, 25, 28, +/* 8x */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, +/* 9x */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, +/* Ax */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, +/* Bx */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, +/* Cx */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, +/* Dx */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, +/* Ex */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 30, +/* Fx */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27 #endif #ifdef SQLITE_EBCDIC /* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xa xb xc xd xe xf */ -/* 0x */ 27, 27, 27, 27, 27, 7, 27, 27, 27, 27, 27, 27, 7, 7, 27, 27, -/* 1x */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, -/* 2x */ 27, 27, 27, 27, 27, 7, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, -/* 3x */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, -/* 4x */ 7, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 26, 12, 17, 20, 10, -/* 5x */ 24, 27, 27, 27, 27, 27, 27, 27, 27, 27, 15, 4, 21, 18, 19, 27, -/* 6x */ 11, 16, 27, 27, 27, 27, 27, 27, 27, 27, 27, 23, 22, 1, 13, 6, -/* 7x */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 8, 5, 5, 5, 8, 14, 8, -/* 8x */ 27, 1, 1, 1, 1, 1, 1, 1, 1, 1, 27, 27, 27, 27, 27, 27, -/* 9x */ 27, 1, 1, 1, 1, 1, 1, 1, 1, 1, 27, 27, 27, 27, 27, 27, -/* Ax */ 27, 25, 1, 1, 1, 1, 1, 0, 1, 1, 27, 27, 27, 27, 27, 27, -/* Bx */ 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 9, 27, 27, 27, 27, 27, -/* Cx */ 27, 1, 1, 1, 1, 1, 1, 1, 1, 1, 27, 27, 27, 27, 27, 27, -/* Dx */ 27, 1, 1, 1, 1, 1, 1, 1, 1, 1, 27, 27, 27, 27, 27, 27, -/* Ex */ 27, 27, 1, 1, 1, 1, 1, 0, 1, 1, 27, 27, 27, 27, 27, 27, -/* Fx */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 27, 27, 27, 27, 27, 27, +/* 0x */ 29, 28, 28, 28, 28, 7, 28, 28, 28, 28, 28, 28, 7, 7, 28, 28, +/* 1x */ 28, 28, 28, 28, 28, 7, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, +/* 2x */ 28, 28, 28, 28, 28, 7, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, +/* 3x */ 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, +/* 4x */ 7, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 26, 12, 17, 20, 10, +/* 5x */ 24, 28, 28, 28, 28, 28, 28, 28, 28, 28, 15, 4, 21, 18, 19, 28, +/* 6x */ 11, 16, 28, 28, 28, 28, 28, 28, 28, 28, 28, 23, 22, 2, 13, 6, +/* 7x */ 28, 28, 28, 28, 28, 28, 28, 28, 28, 8, 5, 5, 5, 8, 14, 8, +/* 8x */ 28, 1, 1, 1, 1, 1, 1, 1, 1, 1, 28, 28, 28, 28, 28, 28, +/* 9x */ 28, 1, 1, 1, 1, 1, 1, 1, 1, 1, 28, 28, 28, 28, 28, 28, +/* Ax */ 28, 25, 1, 1, 1, 1, 1, 0, 2, 2, 28, 28, 28, 28, 28, 28, +/* Bx */ 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 9, 28, 28, 28, 28, 28, +/* Cx */ 28, 1, 1, 1, 1, 1, 1, 1, 1, 1, 28, 28, 28, 28, 28, 28, +/* Dx */ 28, 1, 1, 1, 1, 1, 1, 1, 1, 1, 28, 28, 28, 28, 28, 28, +/* Ex */ 28, 28, 1, 1, 1, 1, 1, 0, 2, 2, 28, 28, 28, 28, 28, 28, +/* Fx */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 28, 28, 28, 28, 28, 28, #endif }; @@ -153125,7 +185700,7 @@ static const unsigned char aiClass[] = { ** lower-case ASCII equivalent. On ASCII machines, this is just ** an upper-to-lower case map. On EBCDIC machines we also need ** to adjust the encoding. The mapping is only valid for alphabetics -** which are the only characters for which this feature is used. +** which are the only characters for which this feature is used. ** ** Used by keywordhash.h */ @@ -153157,7 +185732,7 @@ const unsigned char ebcdicToAscii[] = { /* ** The sqlite3KeywordCode function looks up an identifier to determine if -** it is a keyword. If it is a keyword, the token code of that keyword is +** it is a keyword. If it is a keyword, the token code of that keyword is ** returned. If the input is not a keyword, TK_ID is returned. ** ** The implementation of this routine was generated by a program, @@ -153181,318 +185756,467 @@ const unsigned char ebcdicToAscii[] = { ** is substantially reduced. This is important for embedded applications ** on platforms with limited memory. */ -/* Hash score: 214 */ -/* zKWText[] encodes 950 bytes of keyword text in 629 bytes */ +/* Hash score: 231 */ +/* zKWText[] encodes 1007 bytes of keyword text in 667 bytes */ /* REINDEXEDESCAPEACHECKEYBEFOREIGNOREGEXPLAINSTEADDATABASELECT */ -/* ABLEFTHENDEFERRABLELSEXCLUDELETEMPORARYCONSTRAINTERSECTIES */ -/* AVEPOINTOFFSETRANSACTIONATURALTERAISEXCEPTRIGGEREFERENCES */ -/* UNIQUERYWITHOUTERELEASEXCLUSIVEXISTSATTACHAVINGLOBEGINNERANGE */ -/* BETWEENOTHINGROUPSCASCADETACHCASECOLLATECREATECURRENT_DATE */ -/* IMMEDIATEJOINSERTLIKEMATCHPLANALYZEPRAGMABORTUPDATEVALUES */ -/* VIRTUALIMITWHENOTNULLWHERECURSIVEAFTERENAMEANDEFAULT */ -/* AUTOINCREMENTCASTCOLUMNCOMMITCONFLICTCROSSCURRENT_TIMESTAMP */ -/* ARTITIONDEFERREDISTINCTDROPRECEDINGFAILFILTEREPLACEFOLLOWING */ -/* FROMFULLIFISNULLORDERESTRICTOTHERSOVERIGHTROLLBACKROWS */ -/* UNBOUNDEDUNIONUSINGVACUUMVIEWINDOWBYINITIALLYPRIMARY */ -static const char zKWText[628] = { +/* ABLEFTHENDEFERRABLELSEXCLUDELETEMPORARYISNULLSAVEPOINTERSECT */ +/* IESNOTNULLIKEXCEPTRANSACTIONATURALTERAISEXCLUSIVEXISTS */ +/* CONSTRAINTOFFSETRIGGERANGENERATEDETACHAVINGLOBEGINNEREFERENCES */ +/* UNIQUERYWITHOUTERELEASEATTACHBETWEENOTHINGROUPSCASCADEFAULT */ +/* CASECOLLATECREATECURRENT_DATEIMMEDIATEJOINSERTMATCHPLANALYZE */ +/* PRAGMATERIALIZEDEFERREDISTINCTUPDATEVALUESVIRTUALWAYSWHENWHERE */ +/* CURSIVEABORTAFTERENAMEANDROPARTITIONAUTOINCREMENTCASTCOLUMN */ +/* COMMITCONFLICTCROSSCURRENT_TIMESTAMPRECEDINGFAILASTFILTER */ +/* EPLACEFIRSTFOLLOWINGFROMFULLIMITIFORDERESTRICTOTHERSOVER */ +/* ETURNINGRIGHTROLLBACKROWSUNBOUNDEDUNIONUSINGVACUUMVIEWINDOWBY */ +/* INITIALLYPRIMARY */ +static const char zKWText[666] = { 'R','E','I','N','D','E','X','E','D','E','S','C','A','P','E','A','C','H', 'E','C','K','E','Y','B','E','F','O','R','E','I','G','N','O','R','E','G', 'E','X','P','L','A','I','N','S','T','E','A','D','D','A','T','A','B','A', 'S','E','L','E','C','T','A','B','L','E','F','T','H','E','N','D','E','F', 'E','R','R','A','B','L','E','L','S','E','X','C','L','U','D','E','L','E', - 'T','E','M','P','O','R','A','R','Y','C','O','N','S','T','R','A','I','N', - 'T','E','R','S','E','C','T','I','E','S','A','V','E','P','O','I','N','T', - 'O','F','F','S','E','T','R','A','N','S','A','C','T','I','O','N','A','T', - 'U','R','A','L','T','E','R','A','I','S','E','X','C','E','P','T','R','I', - 'G','G','E','R','E','F','E','R','E','N','C','E','S','U','N','I','Q','U', - 'E','R','Y','W','I','T','H','O','U','T','E','R','E','L','E','A','S','E', - 'X','C','L','U','S','I','V','E','X','I','S','T','S','A','T','T','A','C', - 'H','A','V','I','N','G','L','O','B','E','G','I','N','N','E','R','A','N', - 'G','E','B','E','T','W','E','E','N','O','T','H','I','N','G','R','O','U', - 'P','S','C','A','S','C','A','D','E','T','A','C','H','C','A','S','E','C', - 'O','L','L','A','T','E','C','R','E','A','T','E','C','U','R','R','E','N', - 'T','_','D','A','T','E','I','M','M','E','D','I','A','T','E','J','O','I', - 'N','S','E','R','T','L','I','K','E','M','A','T','C','H','P','L','A','N', - 'A','L','Y','Z','E','P','R','A','G','M','A','B','O','R','T','U','P','D', - 'A','T','E','V','A','L','U','E','S','V','I','R','T','U','A','L','I','M', - 'I','T','W','H','E','N','O','T','N','U','L','L','W','H','E','R','E','C', - 'U','R','S','I','V','E','A','F','T','E','R','E','N','A','M','E','A','N', - 'D','E','F','A','U','L','T','A','U','T','O','I','N','C','R','E','M','E', - 'N','T','C','A','S','T','C','O','L','U','M','N','C','O','M','M','I','T', - 'C','O','N','F','L','I','C','T','C','R','O','S','S','C','U','R','R','E', - 'N','T','_','T','I','M','E','S','T','A','M','P','A','R','T','I','T','I', - 'O','N','D','E','F','E','R','R','E','D','I','S','T','I','N','C','T','D', - 'R','O','P','R','E','C','E','D','I','N','G','F','A','I','L','F','I','L', - 'T','E','R','E','P','L','A','C','E','F','O','L','L','O','W','I','N','G', - 'F','R','O','M','F','U','L','L','I','F','I','S','N','U','L','L','O','R', - 'D','E','R','E','S','T','R','I','C','T','O','T','H','E','R','S','O','V', - 'E','R','I','G','H','T','R','O','L','L','B','A','C','K','R','O','W','S', - 'U','N','B','O','U','N','D','E','D','U','N','I','O','N','U','S','I','N', - 'G','V','A','C','U','U','M','V','I','E','W','I','N','D','O','W','B','Y', - 'I','N','I','T','I','A','L','L','Y','P','R','I','M','A','R','Y', + 'T','E','M','P','O','R','A','R','Y','I','S','N','U','L','L','S','A','V', + 'E','P','O','I','N','T','E','R','S','E','C','T','I','E','S','N','O','T', + 'N','U','L','L','I','K','E','X','C','E','P','T','R','A','N','S','A','C', + 'T','I','O','N','A','T','U','R','A','L','T','E','R','A','I','S','E','X', + 'C','L','U','S','I','V','E','X','I','S','T','S','C','O','N','S','T','R', + 'A','I','N','T','O','F','F','S','E','T','R','I','G','G','E','R','A','N', + 'G','E','N','E','R','A','T','E','D','E','T','A','C','H','A','V','I','N', + 'G','L','O','B','E','G','I','N','N','E','R','E','F','E','R','E','N','C', + 'E','S','U','N','I','Q','U','E','R','Y','W','I','T','H','O','U','T','E', + 'R','E','L','E','A','S','E','A','T','T','A','C','H','B','E','T','W','E', + 'E','N','O','T','H','I','N','G','R','O','U','P','S','C','A','S','C','A', + 'D','E','F','A','U','L','T','C','A','S','E','C','O','L','L','A','T','E', + 'C','R','E','A','T','E','C','U','R','R','E','N','T','_','D','A','T','E', + 'I','M','M','E','D','I','A','T','E','J','O','I','N','S','E','R','T','M', + 'A','T','C','H','P','L','A','N','A','L','Y','Z','E','P','R','A','G','M', + 'A','T','E','R','I','A','L','I','Z','E','D','E','F','E','R','R','E','D', + 'I','S','T','I','N','C','T','U','P','D','A','T','E','V','A','L','U','E', + 'S','V','I','R','T','U','A','L','W','A','Y','S','W','H','E','N','W','H', + 'E','R','E','C','U','R','S','I','V','E','A','B','O','R','T','A','F','T', + 'E','R','E','N','A','M','E','A','N','D','R','O','P','A','R','T','I','T', + 'I','O','N','A','U','T','O','I','N','C','R','E','M','E','N','T','C','A', + 'S','T','C','O','L','U','M','N','C','O','M','M','I','T','C','O','N','F', + 'L','I','C','T','C','R','O','S','S','C','U','R','R','E','N','T','_','T', + 'I','M','E','S','T','A','M','P','R','E','C','E','D','I','N','G','F','A', + 'I','L','A','S','T','F','I','L','T','E','R','E','P','L','A','C','E','F', + 'I','R','S','T','F','O','L','L','O','W','I','N','G','F','R','O','M','F', + 'U','L','L','I','M','I','T','I','F','O','R','D','E','R','E','S','T','R', + 'I','C','T','O','T','H','E','R','S','O','V','E','R','E','T','U','R','N', + 'I','N','G','R','I','G','H','T','R','O','L','L','B','A','C','K','R','O', + 'W','S','U','N','B','O','U','N','D','E','D','U','N','I','O','N','U','S', + 'I','N','G','V','A','C','U','U','M','V','I','E','W','I','N','D','O','W', + 'B','Y','I','N','I','T','I','A','L','L','Y','P','R','I','M','A','R','Y', }; /* aKWHash[i] is the hash value for the i-th keyword */ static const unsigned char aKWHash[127] = { - 75, 111, 127, 73, 108, 29, 0, 0, 83, 0, 77, 63, 0, - 37, 33, 78, 15, 0, 126, 86, 57, 120, 128, 19, 0, 0, - 133, 0, 131, 123, 0, 22, 98, 0, 9, 0, 0, 117, 71, - 0, 69, 6, 0, 49, 95, 140, 0, 129, 106, 0, 0, 54, - 0, 109, 24, 0, 17, 0, 134, 56, 23, 26, 5, 58, 135, - 101, 0, 0, 139, 112, 62, 138, 59, 115, 65, 0, 96, 0, - 105, 45, 0, 104, 0, 0, 0, 100, 97, 102, 107, 119, 14, - 31, 118, 0, 81, 0, 136, 116, 137, 61, 124, 132, 80, 121, - 88, 30, 85, 0, 0, 99, 35, 125, 122, 0, 130, 0, 0, - 41, 0, 91, 89, 90, 0, 20, 87, 113, 82, + 84, 92, 134, 82, 105, 29, 0, 0, 94, 0, 85, 72, 0, + 53, 35, 86, 15, 0, 42, 97, 54, 89, 135, 19, 0, 0, + 140, 0, 40, 129, 0, 22, 107, 0, 9, 0, 0, 123, 80, + 0, 78, 6, 0, 65, 103, 147, 0, 136, 115, 0, 0, 48, + 0, 90, 24, 0, 17, 0, 27, 70, 23, 26, 5, 60, 142, + 110, 122, 0, 73, 91, 71, 145, 61, 120, 74, 0, 49, 0, + 11, 41, 0, 113, 0, 0, 0, 109, 10, 111, 116, 125, 14, + 50, 124, 0, 100, 0, 18, 121, 144, 56, 130, 139, 88, 83, + 37, 30, 126, 0, 0, 108, 51, 131, 128, 0, 34, 0, 0, + 132, 0, 98, 38, 39, 0, 20, 45, 117, 93, }; /* aKWNext[] forms the hash collision chain. If aKWHash[i]==0 ** then the i-th keyword has no more hash collisions. Otherwise, ** the next keyword with the same hash is aKWHash[i]-1. */ -static const unsigned char aKWNext[140] = { - 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 2, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 0, - 0, 0, 0, 21, 0, 0, 12, 0, 0, 0, 0, 0, 0, - 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 51, 28, 0, 0, 38, 0, 0, 0, 44, 0, 0, 0, 3, - 0, 0, 67, 1, 66, 0, 0, 0, 36, 0, 47, 0, 0, - 0, 0, 0, 48, 50, 76, 0, 0, 42, 0, 60, 0, 0, - 0, 43, 0, 16, 55, 10, 0, 0, 0, 0, 0, 0, 0, - 11, 72, 93, 0, 0, 8, 0, 110, 0, 103, 40, 53, 70, - 0, 114, 0, 74, 52, 0, 0, 92, 39, 46, 0, 68, 32, - 84, 0, 34, 27, 25, 18, 94, 0, 64, 79, +static const unsigned char aKWNext[148] = {0, + 0, 0, 0, 0, 4, 0, 43, 0, 0, 106, 114, 0, 0, + 0, 2, 0, 0, 143, 0, 0, 0, 13, 0, 0, 0, 0, + 141, 0, 0, 119, 52, 0, 0, 137, 12, 0, 0, 62, 0, + 138, 0, 133, 0, 0, 36, 0, 0, 28, 77, 0, 0, 0, + 0, 59, 0, 47, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 69, 0, 0, 0, 0, 0, 146, 3, 0, 58, 0, 1, + 75, 0, 0, 0, 31, 0, 0, 0, 0, 0, 127, 0, 104, + 0, 64, 66, 63, 0, 0, 0, 0, 0, 46, 0, 16, 8, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 81, 101, 0, + 112, 21, 7, 67, 0, 79, 96, 118, 0, 0, 68, 0, 0, + 99, 44, 0, 55, 0, 76, 0, 95, 32, 33, 57, 25, 0, + 102, 0, 0, 87, }; /* aKWLen[i] is the length (in bytes) of the i-th keyword */ -static const unsigned char aKWLen[140] = { +static const unsigned char aKWLen[148] = {0, 7, 7, 5, 4, 6, 4, 5, 3, 6, 7, 3, 6, 6, 7, 7, 3, 8, 2, 6, 5, 4, 4, 3, 10, 4, 7, - 6, 9, 4, 2, 10, 9, 4, 9, 4, 6, 2, 3, 11, - 6, 2, 7, 5, 5, 6, 7, 10, 6, 5, 7, 4, 5, - 7, 9, 6, 6, 6, 4, 5, 5, 5, 7, 7, 6, 5, - 7, 3, 6, 4, 7, 6, 12, 9, 4, 6, 4, 5, 4, - 7, 6, 5, 6, 6, 7, 5, 4, 7, 3, 2, 4, 5, - 9, 5, 6, 3, 7, 13, 2, 2, 4, 6, 6, 8, 5, - 17, 12, 7, 9, 8, 8, 2, 4, 9, 4, 6, 7, 9, - 4, 4, 2, 6, 5, 8, 6, 4, 5, 8, 4, 3, 9, - 5, 5, 6, 4, 6, 2, 2, 9, 3, 7, + 6, 9, 4, 2, 6, 5, 9, 9, 4, 7, 3, 2, 4, + 4, 6, 11, 6, 2, 7, 5, 5, 9, 6, 10, 4, 6, + 2, 3, 7, 5, 9, 6, 6, 4, 5, 5, 10, 6, 5, + 7, 4, 5, 7, 6, 7, 7, 6, 5, 7, 3, 7, 4, + 7, 6, 12, 9, 4, 6, 5, 4, 7, 6, 12, 8, 8, + 2, 6, 6, 7, 6, 4, 5, 9, 5, 5, 6, 3, 4, + 9, 13, 2, 2, 4, 6, 6, 8, 5, 17, 12, 7, 9, + 4, 4, 6, 7, 5, 9, 4, 4, 5, 2, 5, 8, 6, + 4, 9, 5, 8, 4, 3, 9, 5, 5, 6, 4, 6, 2, + 2, 9, 3, 7, }; /* aKWOffset[i] is the index into zKWText[] of the start of ** the text for the i-th keyword. */ -static const unsigned short int aKWOffset[140] = { +static const unsigned short int aKWOffset[148] = {0, 0, 2, 2, 8, 9, 14, 16, 20, 23, 25, 25, 29, 33, 36, 41, 46, 48, 53, 54, 59, 62, 65, 67, 69, 78, 81, - 86, 90, 90, 94, 99, 106, 114, 117, 123, 126, 126, 129, 131, - 136, 140, 141, 146, 150, 154, 159, 165, 175, 178, 183, 183, 187, - 191, 197, 205, 211, 216, 221, 224, 227, 231, 236, 242, 248, 248, - 254, 255, 259, 265, 269, 276, 282, 294, 303, 305, 311, 315, 320, - 322, 329, 334, 339, 345, 351, 357, 362, 365, 365, 365, 368, 372, - 375, 384, 388, 394, 396, 403, 405, 407, 416, 420, 426, 432, 440, - 445, 445, 445, 461, 470, 477, 478, 485, 488, 497, 501, 506, 513, - 522, 526, 530, 532, 538, 542, 550, 556, 559, 564, 572, 572, 576, - 585, 590, 595, 601, 604, 607, 610, 612, 617, 621, + 86, 90, 90, 94, 99, 101, 105, 111, 119, 123, 123, 123, 126, + 129, 132, 137, 142, 146, 147, 152, 156, 160, 168, 174, 181, 184, + 184, 187, 189, 195, 198, 206, 211, 216, 219, 222, 226, 236, 239, + 244, 244, 248, 252, 259, 265, 271, 277, 277, 283, 284, 288, 295, + 299, 306, 312, 324, 333, 335, 341, 346, 348, 355, 359, 370, 377, + 378, 385, 391, 397, 402, 408, 412, 415, 424, 429, 433, 439, 441, + 444, 453, 455, 457, 466, 470, 476, 482, 490, 495, 495, 495, 511, + 520, 523, 527, 532, 539, 544, 553, 557, 560, 565, 567, 571, 579, + 585, 588, 597, 602, 610, 610, 614, 623, 628, 633, 639, 642, 645, + 648, 650, 655, 659, }; /* aKWCode[i] is the parser symbol code for the i-th keyword */ -static const unsigned char aKWCode[140] = { - TK_REINDEX, TK_INDEXED, TK_INDEX, TK_DESC, TK_ESCAPE, - TK_EACH, TK_CHECK, TK_KEY, TK_BEFORE, TK_FOREIGN, - TK_FOR, TK_IGNORE, TK_LIKE_KW, TK_EXPLAIN, TK_INSTEAD, - TK_ADD, TK_DATABASE, TK_AS, TK_SELECT, TK_TABLE, - TK_JOIN_KW, TK_THEN, TK_END, TK_DEFERRABLE, TK_ELSE, - TK_EXCLUDE, TK_DELETE, TK_TEMP, TK_TEMP, TK_OR, - TK_CONSTRAINT, TK_INTERSECT, TK_TIES, TK_SAVEPOINT, TK_INTO, - TK_OFFSET, TK_OF, TK_SET, TK_TRANSACTION,TK_ACTION, - TK_ON, TK_JOIN_KW, TK_ALTER, TK_RAISE, TK_EXCEPT, - TK_TRIGGER, TK_REFERENCES, TK_UNIQUE, TK_QUERY, TK_WITHOUT, - TK_WITH, TK_JOIN_KW, TK_RELEASE, TK_EXCLUSIVE, TK_EXISTS, - TK_ATTACH, TK_HAVING, TK_LIKE_KW, TK_BEGIN, TK_JOIN_KW, - TK_RANGE, TK_BETWEEN, TK_NOTHING, TK_GROUPS, TK_GROUP, - TK_CASCADE, TK_ASC, TK_DETACH, TK_CASE, TK_COLLATE, - TK_CREATE, TK_CTIME_KW, TK_IMMEDIATE, TK_JOIN, TK_INSERT, - TK_LIKE_KW, TK_MATCH, TK_PLAN, TK_ANALYZE, TK_PRAGMA, - TK_ABORT, TK_UPDATE, TK_VALUES, TK_VIRTUAL, TK_LIMIT, - TK_WHEN, TK_NOTNULL, TK_NOT, TK_NO, TK_NULL, - TK_WHERE, TK_RECURSIVE, TK_AFTER, TK_RENAME, TK_AND, - TK_DEFAULT, TK_AUTOINCR, TK_TO, TK_IN, TK_CAST, - TK_COLUMNKW, TK_COMMIT, TK_CONFLICT, TK_JOIN_KW, TK_CTIME_KW, - TK_CTIME_KW, TK_CURRENT, TK_PARTITION, TK_DEFERRED, TK_DISTINCT, - TK_IS, TK_DROP, TK_PRECEDING, TK_FAIL, TK_FILTER, - TK_REPLACE, TK_FOLLOWING, TK_FROM, TK_JOIN_KW, TK_IF, - TK_ISNULL, TK_ORDER, TK_RESTRICT, TK_OTHERS, TK_OVER, - TK_JOIN_KW, TK_ROLLBACK, TK_ROWS, TK_ROW, TK_UNBOUNDED, - TK_UNION, TK_USING, TK_VACUUM, TK_VIEW, TK_WINDOW, - TK_DO, TK_BY, TK_INITIALLY, TK_ALL, TK_PRIMARY, +static const unsigned char aKWCode[148] = {0, + TK_REINDEX, TK_INDEXED, TK_INDEX, TK_DESC, TK_ESCAPE, + TK_EACH, TK_CHECK, TK_KEY, TK_BEFORE, TK_FOREIGN, + TK_FOR, TK_IGNORE, TK_LIKE_KW, TK_EXPLAIN, TK_INSTEAD, + TK_ADD, TK_DATABASE, TK_AS, TK_SELECT, TK_TABLE, + TK_JOIN_KW, TK_THEN, TK_END, TK_DEFERRABLE, TK_ELSE, + TK_EXCLUDE, TK_DELETE, TK_TEMP, TK_TEMP, TK_OR, + TK_ISNULL, TK_NULLS, TK_SAVEPOINT, TK_INTERSECT, TK_TIES, + TK_NOTNULL, TK_NOT, TK_NO, TK_NULL, TK_LIKE_KW, + TK_EXCEPT, TK_TRANSACTION,TK_ACTION, TK_ON, TK_JOIN_KW, + TK_ALTER, TK_RAISE, TK_EXCLUSIVE, TK_EXISTS, TK_CONSTRAINT, + TK_INTO, TK_OFFSET, TK_OF, TK_SET, TK_TRIGGER, + TK_RANGE, TK_GENERATED, TK_DETACH, TK_HAVING, TK_LIKE_KW, + TK_BEGIN, TK_JOIN_KW, TK_REFERENCES, TK_UNIQUE, TK_QUERY, + TK_WITHOUT, TK_WITH, TK_JOIN_KW, TK_RELEASE, TK_ATTACH, + TK_BETWEEN, TK_NOTHING, TK_GROUPS, TK_GROUP, TK_CASCADE, + TK_ASC, TK_DEFAULT, TK_CASE, TK_COLLATE, TK_CREATE, + TK_CTIME_KW, TK_IMMEDIATE, TK_JOIN, TK_INSERT, TK_MATCH, + TK_PLAN, TK_ANALYZE, TK_PRAGMA, TK_MATERIALIZED, TK_DEFERRED, + TK_DISTINCT, TK_IS, TK_UPDATE, TK_VALUES, TK_VIRTUAL, + TK_ALWAYS, TK_WHEN, TK_WHERE, TK_RECURSIVE, TK_ABORT, + TK_AFTER, TK_RENAME, TK_AND, TK_DROP, TK_PARTITION, + TK_AUTOINCR, TK_TO, TK_IN, TK_CAST, TK_COLUMNKW, + TK_COMMIT, TK_CONFLICT, TK_JOIN_KW, TK_CTIME_KW, TK_CTIME_KW, + TK_CURRENT, TK_PRECEDING, TK_FAIL, TK_LAST, TK_FILTER, + TK_REPLACE, TK_FIRST, TK_FOLLOWING, TK_FROM, TK_JOIN_KW, + TK_LIMIT, TK_IF, TK_ORDER, TK_RESTRICT, TK_OTHERS, + TK_OVER, TK_RETURNING, TK_JOIN_KW, TK_ROLLBACK, TK_ROWS, + TK_ROW, TK_UNBOUNDED, TK_UNION, TK_USING, TK_VACUUM, + TK_VIEW, TK_WINDOW, TK_DO, TK_BY, TK_INITIALLY, + TK_ALL, TK_PRIMARY, }; +/* Hash table decoded: +** 0: INSERT +** 1: IS +** 2: ROLLBACK TRIGGER +** 3: IMMEDIATE +** 4: PARTITION +** 5: TEMP +** 6: +** 7: +** 8: VALUES WITHOUT +** 9: +** 10: MATCH +** 11: NOTHING +** 12: +** 13: OF +** 14: TIES IGNORE +** 15: PLAN +** 16: INSTEAD INDEXED +** 17: +** 18: TRANSACTION RIGHT +** 19: WHEN +** 20: SET HAVING +** 21: MATERIALIZED IF +** 22: ROWS +** 23: SELECT +** 24: +** 25: +** 26: VACUUM SAVEPOINT +** 27: +** 28: LIKE UNION VIRTUAL REFERENCES +** 29: RESTRICT +** 30: +** 31: THEN REGEXP +** 32: TO +** 33: +** 34: BEFORE +** 35: +** 36: +** 37: FOLLOWING COLLATE CASCADE +** 38: CREATE +** 39: +** 40: CASE REINDEX +** 41: EACH +** 42: +** 43: QUERY +** 44: AND ADD +** 45: PRIMARY ANALYZE +** 46: +** 47: ROW ASC DETACH +** 48: CURRENT_TIME CURRENT_DATE +** 49: +** 50: +** 51: EXCLUSIVE TEMPORARY +** 52: +** 53: DEFERRED +** 54: DEFERRABLE +** 55: +** 56: DATABASE +** 57: +** 58: DELETE VIEW GENERATED +** 59: ATTACH +** 60: END +** 61: EXCLUDE +** 62: ESCAPE DESC +** 63: GLOB +** 64: WINDOW ELSE +** 65: COLUMN +** 66: FIRST +** 67: +** 68: GROUPS ALL +** 69: DISTINCT DROP KEY +** 70: BETWEEN +** 71: INITIALLY +** 72: BEGIN +** 73: FILTER CHECK ACTION +** 74: GROUP INDEX +** 75: +** 76: EXISTS DEFAULT +** 77: +** 78: FOR CURRENT_TIMESTAMP +** 79: EXCEPT +** 80: +** 81: CROSS +** 82: +** 83: +** 84: +** 85: CAST +** 86: FOREIGN AUTOINCREMENT +** 87: COMMIT +** 88: CURRENT AFTER ALTER +** 89: FULL FAIL CONFLICT +** 90: EXPLAIN +** 91: CONSTRAINT +** 92: FROM ALWAYS +** 93: +** 94: ABORT +** 95: +** 96: AS DO +** 97: REPLACE WITH RELEASE +** 98: BY RENAME +** 99: RANGE RAISE +** 100: OTHERS +** 101: USING NULLS +** 102: PRAGMA +** 103: JOIN ISNULL OFFSET +** 104: NOT +** 105: OR LAST LEFT +** 106: LIMIT +** 107: +** 108: +** 109: IN +** 110: INTO +** 111: OVER RECURSIVE +** 112: ORDER OUTER +** 113: +** 114: INTERSECT UNBOUNDED +** 115: +** 116: +** 117: RETURNING ON +** 118: +** 119: WHERE +** 120: NO INNER +** 121: NULL +** 122: +** 123: TABLE +** 124: NATURAL NOTNULL +** 125: PRECEDING +** 126: UPDATE UNIQUE +*/ /* Check to see if z[0..n-1] is a keyword. If it is, write the ** parser symbol code for that keyword into *pType. Always ** return the integer n (the length of the token). */ -static int keywordCode(const char *z, int n, int *pType){ - int i, j; +static i64 keywordCode(const char *z, i64 n, int *pType){ + i64 i, j; const char *zKW; - if( n>=2 ){ - i = ((charMap(z[0])*4) ^ (charMap(z[n-1])*3) ^ n) % 127; - for(i=((int)aKWHash[i])-1; i>=0; i=((int)aKWNext[i])-1){ - if( aKWLen[i]!=n ) continue; - j = 0; - zKW = &zKWText[aKWOffset[i]]; + assert( n>=2 ); + i = ((charMap(z[0])*4) ^ (charMap(z[n-1])*3) ^ n*1) % 127; + for(i=(int)aKWHash[i]; i>0; i=aKWNext[i]){ + if( aKWLen[i]!=n ) continue; + zKW = &zKWText[aKWOffset[i]]; #ifdef SQLITE_ASCII - while( j=2 ) keywordCode((char*)z, n, &id); return id; } -#define SQLITE_N_KEYWORD 140 +#define SQLITE_N_KEYWORD 147 SQLITE_API int sqlite3_keyword_name(int i,const char **pzName,int *pnName){ if( i<0 || i>=SQLITE_N_KEYWORD ) return SQLITE_ERROR; + i++; *pzName = zKWText + aKWOffset[i]; *pnName = aKWLen[i]; return SQLITE_OK; @@ -153511,14 +186235,14 @@ SQLITE_API int sqlite3_keyword_check(const char *zName, int nName){ ** IdChar(X) will be true. Otherwise it is false. ** ** For ASCII, any character with the high-order bit set is -** allowed in an identifier. For 7-bit characters, +** allowed in an identifier. For 7-bit characters, ** sqlite3IsIdChar[X] must be 1. ** ** For EBCDIC, the rules are more complex but have the same ** end result. ** ** Ticket #1066. the SQL standard does not allow '$' in the -** middle of identifiers. But many SQL implementations do. +** middle of identifiers. But many SQL implementations do. ** SQLite will allow '$' in identifiers for compatibility. ** But the feature is undocumented. */ @@ -153557,13 +186281,13 @@ static int getToken(const unsigned char **pz){ int t; /* Token type to return */ do { z += sqlite3GetToken(z, &t); - }while( t==TK_SPACE ); - if( t==TK_ID - || t==TK_STRING - || t==TK_JOIN_KW - || t==TK_WINDOW - || t==TK_OVER - || sqlite3ParserFallback(t)==TK_ID + }while( t==TK_SPACE || t==TK_COMMENT ); + if( t==TK_ID + || t==TK_STRING + || t==TK_JOIN_KW + || t==TK_WINDOW + || t==TK_OVER + || sqlite3ParserFallback(t)==TK_ID ){ t = TK_ID; } @@ -153580,8 +186304,8 @@ static int getToken(const unsigned char **pz){ ** ** SELECT sum(x) OVER ... ** -** In the above, "OVER" might be a keyword, or it might be an alias for the -** sum(x) expression. If a "%fallback ID OVER" directive were added to +** In the above, "OVER" might be a keyword, or it might be an alias for the +** sum(x) expression. If a "%fallback ID OVER" directive were added to ** grammar, then SQLite would always treat "OVER" as an alias, making it ** impossible to call a window-function without a FILTER clause. ** @@ -153625,11 +186349,12 @@ static int analyzeFilterKeyword(const unsigned char *z, int lastToken){ #endif /* SQLITE_OMIT_WINDOWFUNC */ /* -** Return the length (in bytes) of the token that begins at z[0]. +** Return the length (in bytes) of the token that begins at z[0]. ** Store the token type in *tokenType before returning. */ -SQLITE_PRIVATE int sqlite3GetToken(const unsigned char *z, int *tokenType){ - int i, c; +SQLITE_PRIVATE i64 sqlite3GetToken(const unsigned char *z, int *tokenType){ + i64 i; + int c; switch( aiClass[*z] ){ /* Switch on the character-class of the first byte ** of the token. See the comment on the CC_ defines ** above. */ @@ -153646,8 +186371,11 @@ SQLITE_PRIVATE int sqlite3GetToken(const unsigned char *z, int *tokenType){ case CC_MINUS: { if( z[1]=='-' ){ for(i=2; (c=z[i])!=0 && c!='\n'; i++){} - *tokenType = TK_SPACE; /* IMP: R-22934-25134 */ + *tokenType = TK_COMMENT; return i; + }else if( z[1]=='>' ){ + *tokenType = TK_PTR; + return 2 + (z[2]=='>'); } *tokenType = TK_MINUS; return 1; @@ -153679,7 +186407,7 @@ SQLITE_PRIVATE int sqlite3GetToken(const unsigned char *z, int *tokenType){ } for(i=3, c=z[2]; (c!='*' || z[i]!='/') && (c=z[i])!=0; i++){} if( c ) i++; - *tokenType = TK_SPACE; /* IMP: R-22934-25134 */ + *tokenType = TK_COMMENT; return i; } case CC_PERCENT: { @@ -153782,36 +186510,68 @@ SQLITE_PRIVATE int sqlite3GetToken(const unsigned char *z, int *tokenType){ } /* If the next character is a digit, this is a floating point ** number that begins with ".". Fall thru into the next case */ + /* no break */ deliberate_fall_through } case CC_DIGIT: { testcase( z[0]=='0' ); testcase( z[0]=='1' ); testcase( z[0]=='2' ); testcase( z[0]=='3' ); testcase( z[0]=='4' ); testcase( z[0]=='5' ); testcase( z[0]=='6' ); testcase( z[0]=='7' ); testcase( z[0]=='8' ); - testcase( z[0]=='9' ); + testcase( z[0]=='9' ); testcase( z[0]=='.' ); *tokenType = TK_INTEGER; #ifndef SQLITE_OMIT_HEX_INTEGER if( z[0]=='0' && (z[1]=='x' || z[1]=='X') && sqlite3Isxdigit(z[2]) ){ - for(i=3; sqlite3Isxdigit(z[i]); i++){} - return i; - } + for(i=3; 1; i++){ + if( sqlite3Isxdigit(z[i])==0 ){ + if( z[i]==SQLITE_DIGIT_SEPARATOR ){ + *tokenType = TK_QNUMBER; + }else{ + break; + } + } + } + }else #endif - for(i=0; sqlite3Isdigit(z[i]); i++){} + { + for(i=0; 1; i++){ + if( sqlite3Isdigit(z[i])==0 ){ + if( z[i]==SQLITE_DIGIT_SEPARATOR ){ + *tokenType = TK_QNUMBER; + }else{ + break; + } + } + } #ifndef SQLITE_OMIT_FLOATING_POINT - if( z[i]=='.' ){ - i++; - while( sqlite3Isdigit(z[i]) ){ i++; } - *tokenType = TK_FLOAT; - } - if( (z[i]=='e' || z[i]=='E') && - ( sqlite3Isdigit(z[i+1]) - || ((z[i+1]=='+' || z[i+1]=='-') && sqlite3Isdigit(z[i+2])) - ) - ){ - i += 2; - while( sqlite3Isdigit(z[i]) ){ i++; } - *tokenType = TK_FLOAT; - } + if( z[i]=='.' ){ + if( *tokenType==TK_INTEGER ) *tokenType = TK_FLOAT; + for(i++; 1; i++){ + if( sqlite3Isdigit(z[i])==0 ){ + if( z[i]==SQLITE_DIGIT_SEPARATOR ){ + *tokenType = TK_QNUMBER; + }else{ + break; + } + } + } + } + if( (z[i]=='e' || z[i]=='E') && + ( sqlite3Isdigit(z[i+1]) + || ((z[i+1]=='+' || z[i+1]=='-') && sqlite3Isdigit(z[i+2])) + ) + ){ + if( *tokenType==TK_INTEGER ) *tokenType = TK_FLOAT; + for(i+=2; 1; i++){ + if( sqlite3Isdigit(z[i])==0 ){ + if( z[i]==SQLITE_DIGIT_SEPARATOR ){ + *tokenType = TK_QNUMBER; + }else{ + break; + } + } + } + } #endif + } while( IdChar(z[i]) ){ *tokenType = TK_ILLEGAL; i++; @@ -153830,7 +186590,7 @@ SQLITE_PRIVATE int sqlite3GetToken(const unsigned char *z, int *tokenType){ } case CC_DOLLAR: case CC_VARALPHA: { - int n = 0; + i64 n = 0; testcase( z[0]=='$' ); testcase( z[0]=='@' ); testcase( z[0]==':' ); testcase( z[0]=='#' ); *tokenType = TK_VARIABLE; @@ -153858,8 +186618,9 @@ SQLITE_PRIVATE int sqlite3GetToken(const unsigned char *z, int *tokenType){ if( n==0 ) *tokenType = TK_ILLEGAL; return i; } - case CC_KYWD: { - for(i=1; aiClass[z[i]]<=CC_KYWD; i++){} + case CC_KYWD0: { + if( aiClass[z[1]]>CC_KYWD ){ i = 1; break; } + for(i=2; aiClass[z[i]]<=CC_KYWD; i++){} if( IdChar(z[i]) ){ /* This token started out using characters that can appear in keywords, ** but z[i] is a character not allowed within keywords, so this must @@ -153886,11 +186647,21 @@ SQLITE_PRIVATE int sqlite3GetToken(const unsigned char *z, int *tokenType){ #endif /* If it is not a BLOB literal, then it must be an ID, since no ** SQL keywords start with the letter 'x'. Fall through */ + /* no break */ deliberate_fall_through } + case CC_KYWD: case CC_ID: { i = 1; break; } + case CC_BOM: { + if( z[1]==0xbb && z[2]==0xbf ){ + *tokenType = TK_SPACE; + return 3; + } + i = 1; + break; + } case CC_NUL: { *tokenType = TK_ILLEGAL; return 0; @@ -153906,20 +186677,17 @@ SQLITE_PRIVATE int sqlite3GetToken(const unsigned char *z, int *tokenType){ } /* -** Run the parser on the given SQL string. The parser structure is -** passed in. An SQLITE_ status code is returned. If an error occurs -** then an and attempt is made to write an error message into -** memory obtained from sqlite3_malloc() and to make *pzErrMsg point to that -** error message. +** Run the parser on the given SQL string. */ -SQLITE_PRIVATE int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzErrMsg){ +SQLITE_PRIVATE int sqlite3RunParser(Parse *pParse, const char *zSql){ int nErr = 0; /* Number of errors encountered */ void *pEngine; /* The LEMON-generated LALR(1) parser */ - int n = 0; /* Length of the next token token */ + i64 n = 0; /* Length of the next token token */ int tokenType; /* type of the next token */ int lastTokenParsed = -1; /* type of the previous token */ sqlite3 *db = pParse->db; /* The database connection */ - int mxSqlLen; /* Max length of an SQL string */ + i64 mxSqlLen; /* Max length of an SQL string */ + Parse *pParentParse = 0; /* Outer parse context, if any */ #ifdef sqlite3Parser_ENGINEALWAYSONSTACK yyParser sEngine; /* Space to hold the Lemon-generated Parser object */ #endif @@ -153928,11 +186696,10 @@ SQLITE_PRIVATE int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzEr assert( zSql!=0 ); mxSqlLen = db->aLimit[SQLITE_LIMIT_SQL_LENGTH]; if( db->nVdbeActive==0 ){ - db->u1.isInterrupted = 0; + AtomicStore(&db->u1.isInterrupted, 0); } pParse->rc = SQLITE_OK; pParse->zTail = zSql; - assert( pzErrMsg!=0 ); #ifdef SQLITE_DEBUG if( db->flags & SQLITE_ParserTrace ){ printf("parser: [[[%s]]]\n", zSql); @@ -153955,26 +186722,31 @@ SQLITE_PRIVATE int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzEr assert( pParse->pNewTrigger==0 ); assert( pParse->nVar==0 ); assert( pParse->pVList==0 ); - pParse->pParentParse = db->pParse; + pParentParse = db->pParse; db->pParse = pParse; while( 1 ){ n = sqlite3GetToken((u8*)zSql, &tokenType); mxSqlLen -= n; if( mxSqlLen<0 ){ pParse->rc = SQLITE_TOOBIG; + pParse->nErr++; break; } #ifndef SQLITE_OMIT_WINDOWFUNC if( tokenType>=TK_WINDOW ){ assert( tokenType==TK_SPACE || tokenType==TK_OVER || tokenType==TK_FILTER - || tokenType==TK_ILLEGAL || tokenType==TK_WINDOW + || tokenType==TK_ILLEGAL || tokenType==TK_WINDOW + || tokenType==TK_QNUMBER || tokenType==TK_COMMENT ); #else if( tokenType>=TK_SPACE ){ - assert( tokenType==TK_SPACE || tokenType==TK_ILLEGAL ); + assert( tokenType==TK_SPACE || tokenType==TK_ILLEGAL + || tokenType==TK_QNUMBER || tokenType==TK_COMMENT + ); #endif /* SQLITE_OMIT_WINDOWFUNC */ - if( db->u1.isInterrupted ){ + if( AtomicLoad(&db->u1.isInterrupted) ){ pParse->rc = SQLITE_INTERRUPT; + pParse->nErr++; break; } if( tokenType==TK_SPACE ){ @@ -154003,13 +186775,23 @@ SQLITE_PRIVATE int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzEr assert( n==6 ); tokenType = analyzeFilterKeyword((const u8*)&zSql[6], lastTokenParsed); #endif /* SQLITE_OMIT_WINDOWFUNC */ - }else{ - sqlite3ErrorMsg(pParse, "unrecognized token: \"%.*s\"", n, zSql); + }else if( tokenType==TK_COMMENT + && (db->init.busy || (db->flags & SQLITE_Comments)!=0) + ){ + /* Ignore SQL comments if either (1) we are reparsing the schema or + ** (2) SQLITE_DBCONFIG_ENABLE_COMMENTS is turned on (the default). */ + zSql += n; + continue; + }else if( tokenType!=TK_QNUMBER ){ + Token x; + x.z = zSql; + x.n = (u32)n; + sqlite3ErrorMsg(pParse, "unrecognized token: \"%T\"", &x); break; } } pParse->sLastToken.z = zSql; - pParse->sLastToken.n = n; + pParse->sLastToken.n = (u32)n; sqlite3Parser(pEngine, tokenType, pParse->sLastToken); lastTokenParsed = tokenType; zSql += n; @@ -154032,58 +186814,32 @@ SQLITE_PRIVATE int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzEr if( db->mallocFailed ){ pParse->rc = SQLITE_NOMEM_BKPT; } - if( pParse->rc!=SQLITE_OK && pParse->rc!=SQLITE_DONE && pParse->zErrMsg==0 ){ - pParse->zErrMsg = sqlite3MPrintf(db, "%s", sqlite3ErrStr(pParse->rc)); - } - assert( pzErrMsg!=0 ); - if( pParse->zErrMsg ){ - *pzErrMsg = pParse->zErrMsg; - sqlite3_log(pParse->rc, "%s in \"%s\"", - *pzErrMsg, pParse->zTail); - pParse->zErrMsg = 0; + if( pParse->zErrMsg || (pParse->rc!=SQLITE_OK && pParse->rc!=SQLITE_DONE) ){ + if( pParse->zErrMsg==0 ){ + pParse->zErrMsg = sqlite3DbStrDup(db, sqlite3ErrStr(pParse->rc)); + } + if( (pParse->prepFlags & SQLITE_PREPARE_DONT_LOG)==0 ){ + sqlite3_log(pParse->rc, "%s in \"%s\"", pParse->zErrMsg, pParse->zTail); + } nErr++; } pParse->zTail = zSql; - if( pParse->pVdbe && pParse->nErr>0 && pParse->nested==0 ){ - sqlite3VdbeDelete(pParse->pVdbe); - pParse->pVdbe = 0; - } -#ifndef SQLITE_OMIT_SHARED_CACHE - if( pParse->nested==0 ){ - sqlite3DbFree(db, pParse->aTableLock); - pParse->aTableLock = 0; - pParse->nTableLock = 0; - } -#endif #ifndef SQLITE_OMIT_VIRTUALTABLE sqlite3_free(pParse->apVtabLock); #endif - if( !IN_SPECIAL_PARSE ){ - /* If the pParse->declareVtab flag is set, do not delete any table + if( pParse->pNewTable && !IN_SPECIAL_PARSE ){ + /* If the pParse->declareVtab flag is set, do not delete any table ** structure built up in pParse->pNewTable. The calling code (see vtab.c) ** will take responsibility for freeing the Table structure. */ sqlite3DeleteTable(db, pParse->pNewTable); } - if( !IN_RENAME_OBJECT ){ + if( pParse->pNewTrigger && !IN_RENAME_OBJECT ){ sqlite3DeleteTrigger(db, pParse->pNewTrigger); } - - if( pParse->pWithToFree ) sqlite3WithDelete(db, pParse->pWithToFree); - sqlite3DbFree(db, pParse->pVList); - while( pParse->pAinc ){ - AutoincInfo *p = pParse->pAinc; - pParse->pAinc = p->pNext; - sqlite3DbFreeNN(db, p); - } - while( pParse->pZombieTab ){ - Table *p = pParse->pZombieTab; - pParse->pZombieTab = p->pNextZombie; - sqlite3DeleteTable(db, p); - } - db->pParse = pParse->pParentParse; - pParse->pParentParse = 0; + if( pParse->pVList ) sqlite3DbNNFreeNN(db, pParse->pVList); + db->pParse = pParentParse; assert( nErr==0 || pParse->rc!=SQLITE_OK ); return nErr; } @@ -154111,13 +186867,13 @@ SQLITE_PRIVATE char *sqlite3Normalize( ){ sqlite3 *db; /* The database connection */ int i; /* Next unread byte of zSql[] */ - int n; /* length of current token */ + i64 n; /* length of current token */ int tokenType; /* type of current token */ int prevType = 0; /* Previous non-whitespace token */ int nParen; /* Number of nested levels of parentheses */ int iStartIN; /* Start of RHS of IN operator in z[] */ int nParenAtIN; /* Value of nParent at start of RHS of IN operator */ - int j; /* Bytes of normalized SQL generated so far */ + u32 j; /* Bytes of normalized SQL generated so far */ sqlite3_str *pStr; /* The normalized SQL string under construction */ db = sqlite3VdbeDb(pVdbe); @@ -154132,6 +186888,7 @@ SQLITE_PRIVATE char *sqlite3Normalize( n = sqlite3GetToken((unsigned char*)zSql+i, &tokenType); if( NEVER(n<=0) ) break; switch( tokenType ){ + case TK_COMMENT: case TK_SPACE: { break; } @@ -154140,7 +186897,7 @@ SQLITE_PRIVATE char *sqlite3Normalize( sqlite3_str_append(pStr, " NULL", 5); break; } - /* Fall through */ + /* no break */ deliberate_fall_through } case TK_STRING: case TK_INTEGER: @@ -154161,7 +186918,7 @@ SQLITE_PRIVATE char *sqlite3Normalize( } case TK_RP: { if( iStartIN>0 && nParen==nParenAtIN ){ - assert( pStr->nChar>=iStartIN ); + assert( pStr->nChar>=(u32)iStartIN ); pStr->nChar = iStartIN+1; sqlite3_str_append(pStr, "?,?,?", 5); iStartIN = 0; @@ -154204,7 +186961,7 @@ SQLITE_PRIVATE char *sqlite3Normalize( } case TK_SELECT: { iStartIN = 0; - /* fall through */ + /* no break */ deliberate_fall_through } default: { if( sqlite3IsIdChar(zSql[i]) ) addSpaceSeparator(pStr); @@ -154293,7 +187050,7 @@ SQLITE_PRIVATE const char sqlite3IsEbcdicIdChar[]; ** (2) NORMAL We are in the middle of statement which ends with a single ** semicolon. ** -** (3) EXPLAIN The keyword EXPLAIN has been seen at the beginning of +** (3) EXPLAIN The keyword EXPLAIN has been seen at the beginning of ** a statement. ** ** (4) CREATE The keyword CREATE has been seen at the beginning of a @@ -154636,29 +187393,78 @@ SQLITE_PRIVATE int sqlite3IcuInit(sqlite3 *db); } /* extern "C" */ #endif /* __cplusplus */ - /************** End of sqliteicu.h *******************************************/ /************** Continuing where we left off in main.c ***********************/ #endif -#ifdef SQLITE_ENABLE_JSON1 -SQLITE_PRIVATE int sqlite3Json1Init(sqlite3*); + +/* +** This is an extension initializer that is a no-op and always +** succeeds, except that it fails if the fault-simulation is set +** to 500. +*/ +static int sqlite3TestExtInit(sqlite3 *db){ + (void)db; + return sqlite3FaultSim(500); +} + + +/* +** Forward declarations of external module initializer functions +** for modules that need them. +*/ +#ifdef SQLITE_ENABLE_FTS5 +SQLITE_PRIVATE int sqlite3Fts5Init(sqlite3*); #endif #ifdef SQLITE_ENABLE_STMTVTAB SQLITE_PRIVATE int sqlite3StmtVtabInit(sqlite3*); #endif +#ifdef SQLITE_EXTRA_AUTOEXT +int SQLITE_EXTRA_AUTOEXT(sqlite3*); +#endif +/* +** An array of pointers to extension initializer functions for +** built-in extensions. +*/ +static int (*const sqlite3BuiltinExtensions[])(sqlite3*) = { +#ifdef SQLITE_ENABLE_FTS3 + sqlite3Fts3Init, +#endif #ifdef SQLITE_ENABLE_FTS5 -SQLITE_PRIVATE int sqlite3Fts5Init(sqlite3*); + sqlite3Fts5Init, #endif +#if defined(SQLITE_ENABLE_ICU) || defined(SQLITE_ENABLE_ICU_COLLATIONS) + sqlite3IcuInit, +#endif +#ifdef SQLITE_ENABLE_RTREE + sqlite3RtreeInit, +#endif +#ifdef SQLITE_ENABLE_DBPAGE_VTAB + sqlite3DbpageRegister, +#endif +#ifdef SQLITE_ENABLE_DBSTAT_VTAB + sqlite3DbstatRegister, +#endif + sqlite3TestExtInit, +#ifdef SQLITE_ENABLE_STMTVTAB + sqlite3StmtVtabInit, +#endif +#ifdef SQLITE_ENABLE_BYTECODE_VTAB + sqlite3VdbeBytecodeVtabInit, +#endif +#ifdef SQLITE_EXTRA_AUTOEXT + SQLITE_EXTRA_AUTOEXT, +#endif +}; #ifndef SQLITE_AMALGAMATION /* IMPLEMENTATION-OF: R-46656-45156 The sqlite3_version[] string constant -** contains the text of SQLITE_VERSION macro. +** contains the text of SQLITE_VERSION macro. */ SQLITE_API const char sqlite3_version[] = SQLITE_VERSION; #endif /* IMPLEMENTATION-OF: R-53536-42575 The sqlite3_libversion() function returns -** a pointer to the to the sqlite3_version[] string constant. +** a pointer to the to the sqlite3_version[] string constant. */ SQLITE_API const char *sqlite3_libversion(void){ return sqlite3_version; } @@ -154722,13 +187528,13 @@ SQLITE_API char *sqlite3_temp_directory = 0; SQLITE_API char *sqlite3_data_directory = 0; /* -** Initialize SQLite. +** Initialize SQLite. ** ** This routine must be called to initialize the memory allocation, ** VFS, and mutex subsystems prior to doing any serious work with ** SQLite. But as long as you do not compile with SQLITE_OMIT_AUTOINIT ** this routine will be called automatically by key routines such as -** sqlite3_open(). +** sqlite3_open(). ** ** This routine is a no-op except on its very first call for the process, ** or for the first call after a call to sqlite3_shutdown. @@ -154753,7 +187559,7 @@ SQLITE_API char *sqlite3_data_directory = 0; ** without blocking. */ SQLITE_API int sqlite3_initialize(void){ - MUTEX_LOGIC( sqlite3_mutex *pMaster; ) /* The main static mutex */ + MUTEX_LOGIC( sqlite3_mutex *pMainMtx; ) /* The main static mutex */ int rc; /* Result code */ #ifdef SQLITE_EXTRA_INIT int bRunExtraInit = 0; /* Extra initialization needed */ @@ -154776,9 +187582,12 @@ SQLITE_API int sqlite3_initialize(void){ ** must be complete. So isInit must not be set until the very end ** of this routine. */ - if( sqlite3GlobalConfig.isInit ) return SQLITE_OK; + if( sqlite3GlobalConfig.isInit ){ + sqlite3MemoryBarrier(); + return SQLITE_OK; + } - /* Make sure the mutex subsystem is initialized. If unable to + /* Make sure the mutex subsystem is initialized. If unable to ** initialize the mutex subsystem, return early with the error. ** If the system is so sick that we are unable to allocate a mutex, ** there is not much SQLite is going to be able to do. @@ -154790,13 +187599,13 @@ SQLITE_API int sqlite3_initialize(void){ if( rc ) return rc; /* Initialize the malloc() system and the recursive pInitMutex mutex. - ** This operation is protected by the STATIC_MASTER mutex. Note that + ** This operation is protected by the STATIC_MAIN mutex. Note that ** MutexAlloc() is called for a static mutex prior to initializing the ** malloc subsystem - this implies that the allocation of a static ** mutex must not require support from the malloc subsystem. */ - MUTEX_LOGIC( pMaster = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER); ) - sqlite3_mutex_enter(pMaster); + MUTEX_LOGIC( pMainMtx = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MAIN); ) + sqlite3_mutex_enter(pMainMtx); sqlite3GlobalConfig.isMutexInit = 1; if( !sqlite3GlobalConfig.isMallocInit ){ rc = sqlite3MallocInit(); @@ -154814,7 +187623,7 @@ SQLITE_API int sqlite3_initialize(void){ if( rc==SQLITE_OK ){ sqlite3GlobalConfig.nRefInitMutex++; } - sqlite3_mutex_leave(pMaster); + sqlite3_mutex_leave(pMainMtx); /* If rc is not SQLITE_OK at this point, then either the malloc ** subsystem could not be initialized or the system failed to allocate @@ -154854,14 +187663,23 @@ SQLITE_API int sqlite3_initialize(void){ sqlite3GlobalConfig.isPCacheInit = 1; rc = sqlite3OsInit(); } -#ifdef SQLITE_ENABLE_DESERIALIZE +#ifndef SQLITE_OMIT_DESERIALIZE if( rc==SQLITE_OK ){ rc = sqlite3MemdbInit(); } #endif if( rc==SQLITE_OK ){ - sqlite3PCacheBufferSetup( sqlite3GlobalConfig.pPage, + sqlite3PCacheBufferSetup( sqlite3GlobalConfig.pPage, sqlite3GlobalConfig.szPage, sqlite3GlobalConfig.nPage); +#ifdef SQLITE_EXTRA_INIT_MUTEXED + { + int SQLITE_EXTRA_INIT_MUTEXED(const char*); + rc = SQLITE_EXTRA_INIT_MUTEXED(0); + } +#endif + } + if( rc==SQLITE_OK ){ + sqlite3MemoryBarrier(); sqlite3GlobalConfig.isInit = 1; #ifdef SQLITE_EXTRA_INIT bRunExtraInit = 1; @@ -154874,14 +187692,14 @@ SQLITE_API int sqlite3_initialize(void){ /* Go back under the static mutex and clean up the recursive ** mutex to prevent a resource leak. */ - sqlite3_mutex_enter(pMaster); + sqlite3_mutex_enter(pMainMtx); sqlite3GlobalConfig.nRefInitMutex--; if( sqlite3GlobalConfig.nRefInitMutex<=0 ){ assert( sqlite3GlobalConfig.nRefInitMutex==0 ); sqlite3_mutex_free(sqlite3GlobalConfig.pInitMutex); sqlite3GlobalConfig.pInitMutex = 0; } - sqlite3_mutex_leave(pMaster); + sqlite3_mutex_leave(pMainMtx); /* The following is just a sanity check to make sure SQLite has ** been compiled correctly. It is important to run this code, but @@ -154911,7 +187729,6 @@ SQLITE_API int sqlite3_initialize(void){ rc = SQLITE_EXTRA_INIT(0); } #endif - return rc; } @@ -154981,9 +187798,21 @@ SQLITE_API int sqlite3_config(int op, ...){ va_list ap; int rc = SQLITE_OK; - /* sqlite3_config() shall return SQLITE_MISUSE if it is invoked while - ** the SQLite library is in use. */ - if( sqlite3GlobalConfig.isInit ) return SQLITE_MISUSE_BKPT; + /* sqlite3_config() normally returns SQLITE_MISUSE if it is invoked while + ** the SQLite library is in use. Except, a few selected opcodes + ** are allowed. + */ + if( sqlite3GlobalConfig.isInit ){ + static const u64 mAnytimeConfigOption = 0 + | MASKBIT64( SQLITE_CONFIG_LOG ) + | MASKBIT64( SQLITE_CONFIG_PCACHE_HDRSZ ) + ; + if( op<0 || op>63 || (MASKBIT64(op) & mAnytimeConfigOption)==0 ){ + return SQLITE_MISUSE_BKPT; + } + testcase( op==SQLITE_CONFIG_LOG ); + testcase( op==SQLITE_CONFIG_PCACHE_HDRSZ ); + } va_start(ap, op); switch( op ){ @@ -155052,6 +187881,7 @@ SQLITE_API int sqlite3_config(int op, ...){ break; } case SQLITE_CONFIG_MEMSTATUS: { + assert( !sqlite3GlobalConfig.isInit ); /* Cannot change at runtime */ /* EVIDENCE-OF: R-61275-35157 The SQLITE_CONFIG_MEMSTATUS option takes ** single argument of type int, interpreted as a boolean, which enables ** or disables the collection of memory allocation statistics. */ @@ -155077,7 +187907,7 @@ SQLITE_API int sqlite3_config(int op, ...){ ** a single parameter which is a pointer to an integer and writes into ** that integer the number of extra bytes per page required for each page ** in SQLITE_CONFIG_PAGECACHE. */ - *va_arg(ap, int*) = + *va_arg(ap, int*) = sqlite3HeaderSizeBtree() + sqlite3HeaderSizePcache() + sqlite3HeaderSizePcache1(); @@ -155164,7 +187994,7 @@ SQLITE_API int sqlite3_config(int op, ...){ sqlite3GlobalConfig.nLookaside = va_arg(ap, int); break; } - + /* Record a pointer to the logger function and its first argument. ** The default is NULL. Logging is disabled if the function pointer is ** NULL. @@ -155175,8 +188005,10 @@ SQLITE_API int sqlite3_config(int op, ...){ ** sqlite3GlobalConfig.xLog = va_arg(ap, void(*)(void*,int,const char*)); */ typedef void(*LOGFUNC_t)(void*,int,const char*); - sqlite3GlobalConfig.xLog = va_arg(ap, LOGFUNC_t); - sqlite3GlobalConfig.pLogArg = va_arg(ap, void*); + LOGFUNC_t xLog = va_arg(ap, LOGFUNC_t); + void *pLogArg = va_arg(ap, void*); + AtomicStore(&sqlite3GlobalConfig.xLog, xLog); + AtomicStore(&sqlite3GlobalConfig.pLogArg, pLogArg); break; } @@ -155190,7 +188022,8 @@ SQLITE_API int sqlite3_config(int op, ...){ ** argument of type int. If non-zero, then URI handling is globally ** enabled. If the parameter is zero, then URI handling is globally ** disabled. */ - sqlite3GlobalConfig.bOpenUri = va_arg(ap, int); + int bOpenUri = va_arg(ap, int); + AtomicStore(&sqlite3GlobalConfig.bOpenUri, bOpenUri); break; } @@ -155268,12 +188101,24 @@ SQLITE_API int sqlite3_config(int op, ...){ } #endif /* SQLITE_ENABLE_SORTER_REFERENCES */ -#ifdef SQLITE_ENABLE_DESERIALIZE +#ifndef SQLITE_OMIT_DESERIALIZE case SQLITE_CONFIG_MEMDB_MAXSIZE: { sqlite3GlobalConfig.mxMemdbSize = va_arg(ap, sqlite3_int64); break; } -#endif /* SQLITE_ENABLE_DESERIALIZE */ +#endif /* SQLITE_OMIT_DESERIALIZE */ + + case SQLITE_CONFIG_ROWID_IN_VIEW: { + int *pVal = va_arg(ap,int*); +#ifdef SQLITE_ALLOW_ROWID_IN_VIEW + if( 0==*pVal ) sqlite3GlobalConfig.mNoVisibleRowid = TF_NoVisibleRowid; + if( 1==*pVal ) sqlite3GlobalConfig.mNoVisibleRowid = 0; + *pVal = (sqlite3GlobalConfig.mNoVisibleRowid==0); +#else + *pVal = 0; +#endif + break; + } default: { rc = SQLITE_ERROR; @@ -155286,71 +188131,119 @@ SQLITE_API int sqlite3_config(int op, ...){ /* ** Set up the lookaside buffers for a database connection. -** Return SQLITE_OK on success. +** Return SQLITE_OK on success. ** If lookaside is already active, return SQLITE_BUSY. ** ** The sz parameter is the number of bytes in each lookaside slot. -** The cnt parameter is the number of slots. If pStart is NULL the -** space for the lookaside memory is obtained from sqlite3_malloc(). -** If pStart is not NULL then it is sz*cnt bytes of memory to use for -** the lookaside memory. +** The cnt parameter is the number of slots. If pBuf is NULL the +** space for the lookaside memory is obtained from sqlite3_malloc() +** or similar. If pBuf is not NULL then it is sz*cnt bytes of memory +** to use for the lookaside memory. */ -static int setupLookaside(sqlite3 *db, void *pBuf, int sz, int cnt){ +static int setupLookaside( + sqlite3 *db, /* Database connection being configured */ + void *pBuf, /* Memory to use for lookaside. May be NULL */ + int sz, /* Desired size of each lookaside memory slot */ + int cnt /* Number of slots to allocate */ +){ #ifndef SQLITE_OMIT_LOOKASIDE - void *pStart; - + void *pStart; /* Start of the lookaside buffer */ + sqlite3_int64 szAlloc; /* Total space set aside for lookaside memory */ + int nBig; /* Number of full-size slots */ + int nSm; /* Number smaller LOOKASIDE_SMALL-byte slots */ + if( sqlite3LookasideUsed(db,0)>0 ){ return SQLITE_BUSY; } /* Free any existing lookaside buffer for this handle before - ** allocating a new one so we don't have to have space for + ** allocating a new one so we don't have to have space for ** both at the same time. */ if( db->lookaside.bMalloced ){ sqlite3_free(db->lookaside.pStart); } /* The size of a lookaside slot after ROUNDDOWN8 needs to be larger - ** than a pointer to be useful. + ** than a pointer and small enough to fit in a u16. */ - sz = ROUNDDOWN8(sz); /* IMP: R-33038-09382 */ + sz = ROUNDDOWN8(sz); if( sz<=(int)sizeof(LookasideSlot*) ) sz = 0; - if( cnt<0 ) cnt = 0; - if( sz==0 || cnt==0 ){ + if( sz>65528 ) sz = 65528; + /* Count must be at least 1 to be useful, but not so large as to use + ** more than 0x7fff0000 total bytes for lookaside. */ + if( cnt<1 ) cnt = 0; + if( sz>0 && cnt>(0x7fff0000/sz) ) cnt = 0x7fff0000/sz; + szAlloc = (i64)sz*(i64)cnt; + if( szAlloc==0 ){ sz = 0; pStart = 0; }else if( pBuf==0 ){ sqlite3BeginBenignMalloc(); - pStart = sqlite3Malloc( sz*(sqlite3_int64)cnt ); /* IMP: R-61949-35727 */ + pStart = sqlite3Malloc( szAlloc ); sqlite3EndBenignMalloc(); - if( pStart ) cnt = sqlite3MallocSize(pStart)/sz; + if( pStart ) szAlloc = sqlite3MallocSize(pStart); }else{ pStart = pBuf; } +#ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE + if( sz>=LOOKASIDE_SMALL*3 ){ + nBig = szAlloc/(3*LOOKASIDE_SMALL+sz); + nSm = (szAlloc - (i64)sz*(i64)nBig)/LOOKASIDE_SMALL; + }else if( sz>=LOOKASIDE_SMALL*2 ){ + nBig = szAlloc/(LOOKASIDE_SMALL+sz); + nSm = (szAlloc - (i64)sz*(i64)nBig)/LOOKASIDE_SMALL; + }else +#endif /* SQLITE_OMIT_TWOSIZE_LOOKASIDE */ + if( sz>0 ){ + nBig = szAlloc/sz; + nSm = 0; + }else{ + nBig = nSm = 0; + } db->lookaside.pStart = pStart; db->lookaside.pInit = 0; db->lookaside.pFree = 0; db->lookaside.sz = (u16)sz; + db->lookaside.szTrue = (u16)sz; if( pStart ){ int i; LookasideSlot *p; assert( sz > (int)sizeof(LookasideSlot*) ); - db->lookaside.nSlot = cnt; p = (LookasideSlot*)pStart; - for(i=cnt-1; i>=0; i--){ + for(i=0; ipNext = db->lookaside.pInit; db->lookaside.pInit = p; p = (LookasideSlot*)&((u8*)p)[sz]; } +#ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE + db->lookaside.pSmallInit = 0; + db->lookaside.pSmallFree = 0; + db->lookaside.pMiddle = p; + for(i=0; ipNext = db->lookaside.pSmallInit; + db->lookaside.pSmallInit = p; + p = (LookasideSlot*)&((u8*)p)[LOOKASIDE_SMALL]; + } +#endif /* SQLITE_OMIT_TWOSIZE_LOOKASIDE */ + assert( ((uptr)p)<=szAlloc + (uptr)pStart ); db->lookaside.pEnd = p; db->lookaside.bDisable = 0; db->lookaside.bMalloced = pBuf==0 ?1:0; + db->lookaside.nSlot = nBig+nSm; }else{ - db->lookaside.pStart = db; - db->lookaside.pEnd = db; + db->lookaside.pStart = 0; +#ifndef SQLITE_OMIT_TWOSIZE_LOOKASIDE + db->lookaside.pSmallInit = 0; + db->lookaside.pSmallFree = 0; + db->lookaside.pMiddle = 0; +#endif /* SQLITE_OMIT_TWOSIZE_LOOKASIDE */ + db->lookaside.pEnd = 0; db->lookaside.bDisable = 1; + db->lookaside.sz = 0; db->lookaside.bMalloced = 0; db->lookaside.nSlot = 0; } + db->lookaside.pTrueEnd = db->lookaside.pEnd; + assert( sqlite3LookasideUsed(db,0)==0 ); #endif /* SQLITE_OMIT_LOOKASIDE */ return SQLITE_OK; } @@ -155408,7 +188301,7 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3 *db){ sqlite3BtreeEnterAll(db); for(i=0; rc==SQLITE_OK && inDb; i++){ Btree *pBt = db->aDb[i].pBt; - if( pBt && sqlite3BtreeIsInTrans(pBt) ){ + if( pBt && sqlite3BtreeTxnState(pBt)==SQLITE_TXN_WRITE ){ Pager *pPager = sqlite3BtreePager(pBt); rc = sqlite3PagerFlush(pPager); if( rc==SQLITE_BUSY ){ @@ -155428,6 +188321,11 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3 *db){ SQLITE_API int sqlite3_db_config(sqlite3 *db, int op, ...){ va_list ap; int rc; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif + sqlite3_mutex_enter(db->mutex); va_start(ap, op); switch( op ){ case SQLITE_DBCONFIG_MAINDBNAME: { @@ -155444,13 +188342,22 @@ SQLITE_API int sqlite3_db_config(sqlite3 *db, int op, ...){ rc = setupLookaside(db, pBuf, sz, cnt); break; } + case SQLITE_DBCONFIG_FP_DIGITS: { + int nIn = va_arg(ap, int); + int *pOut = va_arg(ap, int*); + if( nIn>3 && nIn<24 ) db->nFpDigit = (u8)nIn; + if( pOut ) *pOut = db->nFpDigit; + rc = SQLITE_OK; + break; + } default: { static const struct { int op; /* The opcode */ - u32 mask; /* Mask of the bit in sqlite3.flags to set/clear */ + u64 mask; /* Mask of the bit in sqlite3.flags to set/clear */ } aFlagOp[] = { { SQLITE_DBCONFIG_ENABLE_FKEY, SQLITE_ForeignKeys }, { SQLITE_DBCONFIG_ENABLE_TRIGGER, SQLITE_EnableTrigger }, + { SQLITE_DBCONFIG_ENABLE_VIEW, SQLITE_EnableView }, { SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER, SQLITE_Fts3Tokenizer }, { SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, SQLITE_LoadExtension }, { SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE, SQLITE_NoCkptOnClose }, @@ -155460,6 +188367,16 @@ SQLITE_API int sqlite3_db_config(sqlite3 *db, int op, ...){ { SQLITE_DBCONFIG_DEFENSIVE, SQLITE_Defensive }, { SQLITE_DBCONFIG_WRITABLE_SCHEMA, SQLITE_WriteSchema| SQLITE_NoSchemaError }, + { SQLITE_DBCONFIG_LEGACY_ALTER_TABLE, SQLITE_LegacyAlter }, + { SQLITE_DBCONFIG_DQS_DDL, SQLITE_DqsDDL }, + { SQLITE_DBCONFIG_DQS_DML, SQLITE_DqsDML }, + { SQLITE_DBCONFIG_LEGACY_FILE_FORMAT, SQLITE_LegacyFileFmt }, + { SQLITE_DBCONFIG_TRUSTED_SCHEMA, SQLITE_TrustedSchema }, + { SQLITE_DBCONFIG_STMT_SCANSTATUS, SQLITE_StmtScanStatus }, + { SQLITE_DBCONFIG_REVERSE_SCANORDER, SQLITE_ReverseOrder }, + { SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE, SQLITE_AttachCreate }, + { SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE, SQLITE_AttachWrite }, + { SQLITE_DBCONFIG_ENABLE_COMMENTS, SQLITE_Comments }, }; unsigned int i; rc = SQLITE_ERROR; /* IMP: R-42790-23372 */ @@ -155487,31 +188404,21 @@ SQLITE_API int sqlite3_db_config(sqlite3 *db, int op, ...){ } } va_end(ap); + sqlite3_mutex_leave(db->mutex); return rc; } - -/* -** Return true if the buffer z[0..n-1] contains all spaces. -*/ -static int allSpaces(const char *z, int n){ - while( n>0 && z[n-1]==' ' ){ n--; } - return n==0; -} - /* ** This is the default collating function named "BINARY" which is always ** available. -** -** If the padFlag argument is not NULL then space padding at the end -** of strings is ignored. This implements the RTRIM collation. */ static int binCollFunc( - void *padFlag, + void *NotUsed, int nKey1, const void *pKey1, int nKey2, const void *pKey2 ){ int rc, n; + UNUSED_PARAMETER(NotUsed); n = nKey1xCmp!=binCollFunc || p->pUser!=0 - || strcmp(p->zName,"BINARY")==0 ); - return p==0 || (p->xCmp==binCollFunc && p->pUser==0); + assert( p==0 || p->xCmp!=binCollFunc || strcmp(p->zName,"BINARY")==0 ); + return p==0 || p->xCmp==binCollFunc; } /* -** Another built-in collating sequence: NOCASE. +** Another built-in collating sequence: NOCASE. ** ** This collating sequence is intended to be used for "case independent ** comparison". SQLite's knowledge of upper and lower case equivalents @@ -155571,13 +188482,17 @@ static int nocaseCollatingFunc( ** Return the ROWID of the most recent insert */ SQLITE_API sqlite_int64 sqlite3_last_insert_rowid(sqlite3 *db){ + i64 iRet; #ifdef SQLITE_ENABLE_API_ARMOR if( !sqlite3SafetyCheckOk(db) ){ (void)SQLITE_MISUSE_BKPT; return 0; } #endif - return db->lastRowid; + sqlite3_mutex_enter(db->mutex); + iRet = db->lastRowid; + sqlite3_mutex_leave(db->mutex); + return iRet; } /* @@ -155598,27 +188513,41 @@ SQLITE_API void sqlite3_set_last_insert_rowid(sqlite3 *db, sqlite3_int64 iRowid) /* ** Return the number of changes in the most recent call to sqlite3_exec(). */ -SQLITE_API int sqlite3_changes(sqlite3 *db){ +SQLITE_API sqlite3_int64 sqlite3_changes64(sqlite3 *db){ + i64 iRet; #ifdef SQLITE_ENABLE_API_ARMOR if( !sqlite3SafetyCheckOk(db) ){ (void)SQLITE_MISUSE_BKPT; return 0; } #endif - return db->nChange; + sqlite3_mutex_enter(db->mutex); + iRet = db->nChange; + sqlite3_mutex_leave(db->mutex); + return iRet; +} +SQLITE_API int sqlite3_changes(sqlite3 *db){ + return (int)sqlite3_changes64(db); } /* ** Return the number of changes since the database handle was opened. */ -SQLITE_API int sqlite3_total_changes(sqlite3 *db){ +SQLITE_API sqlite3_int64 sqlite3_total_changes64(sqlite3 *db){ + i64 iRet; #ifdef SQLITE_ENABLE_API_ARMOR if( !sqlite3SafetyCheckOk(db) ){ (void)SQLITE_MISUSE_BKPT; return 0; } #endif - return db->nTotalChange; + sqlite3_mutex_enter(db->mutex); + iRet = db->nTotalChange; + sqlite3_mutex_leave(db->mutex); + return iRet; +} +SQLITE_API int sqlite3_total_changes(sqlite3 *db){ + return (int)sqlite3_total_changes64(db); } /* @@ -155644,7 +188573,9 @@ SQLITE_PRIVATE void sqlite3CloseSavepoints(sqlite3 *db){ ** with SQLITE_ANY as the encoding. */ static void functionDestroy(sqlite3 *db, FuncDef *p){ - FuncDestructor *pDestructor = p->u.pDestructor; + FuncDestructor *pDestructor; + assert( (p->funcFlags & SQLITE_FUNC_BUILTIN)==0 ); + pDestructor = p->u.pDestructor; if( pDestructor ){ pDestructor->nRef--; if( pDestructor->nRef==0 ){ @@ -155687,7 +188618,7 @@ static void disconnectAllVtab(sqlite3 *db){ /* ** Return TRUE if database connection db has unfinalized prepared -** statements or unfinished sqlite3_backup objects. +** statements or unfinished sqlite3_backup objects. */ static int connectionIsBusy(sqlite3 *db){ int j; @@ -155714,7 +188645,7 @@ static int sqlite3Close(sqlite3 *db, int forceZombie){ } sqlite3_mutex_enter(db->mutex); if( db->mTrace & SQLITE_TRACE_CLOSE ){ - db->xTrace(SQLITE_TRACE_CLOSE, db->pTraceArg, db, 0); + db->trace.xV2(SQLITE_TRACE_CLOSE, db->pTraceArg, db, 0); } /* Force xDisconnect calls on all virtual tables */ @@ -155746,17 +188677,55 @@ static int sqlite3Close(sqlite3 *db, int forceZombie){ } #endif + while( db->pDbData ){ + DbClientData *p = db->pDbData; + db->pDbData = p->pNext; + assert( p->pData!=0 ); + if( p->xDestructor ) p->xDestructor(p->pData); + sqlite3_free(p); + } + /* Convert the connection into a zombie and then close it. */ - db->magic = SQLITE_MAGIC_ZOMBIE; + db->eOpenState = SQLITE_STATE_ZOMBIE; sqlite3LeaveMutexAndCloseZombie(db); return SQLITE_OK; } +/* +** Return the transaction state for a single databse, or the maximum +** transaction state over all attached databases if zSchema is null. +*/ +SQLITE_API int sqlite3_txn_state(sqlite3 *db, const char *zSchema){ + int iDb, nDb; + int iTxn = -1; +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return -1; + } +#endif + sqlite3_mutex_enter(db->mutex); + if( zSchema ){ + nDb = iDb = sqlite3FindDbName(db, zSchema); + if( iDb<0 ) nDb--; + }else{ + iDb = 0; + nDb = db->nDb-1; + } + for(; iDb<=nDb; iDb++){ + Btree *pBt = db->aDb[iDb].pBt; + int x = pBt!=0 ? sqlite3BtreeTxnState(pBt) : SQLITE_TXN_NONE; + if( x>iTxn ) iTxn = x; + } + sqlite3_mutex_leave(db->mutex); + return iTxn; +} + /* ** Two variations on the public interface for closing a database ** connection. The sqlite3_close() version returns SQLITE_BUSY and -** leaves the connection option if there are unfinalized prepared +** leaves the connection open if there are unfinalized prepared ** statements or unfinished sqlite3_backups. The sqlite3_close_v2() ** version forces the connection to become a zombie if there are ** unclosed resources, and arranges for deallocation when the last @@ -155782,7 +188751,7 @@ SQLITE_PRIVATE void sqlite3LeaveMutexAndCloseZombie(sqlite3 *db){ ** or if the connection has not yet been closed by sqlite3_close_v2(), ** then just leave the mutex and return. */ - if( db->magic!=SQLITE_MAGIC_ZOMBIE || connectionIsBusy(db) ){ + if( db->eOpenState!=SQLITE_STATE_ZOMBIE || connectionIsBusy(db) ){ sqlite3_mutex_leave(db->mutex); return; } @@ -155816,6 +188785,7 @@ SQLITE_PRIVATE void sqlite3LeaveMutexAndCloseZombie(sqlite3 *db){ /* Clear the TEMP schema separately and last */ if( db->aDb[1].pSchema ){ sqlite3SchemaClear(db->aDb[1].pSchema); + assert( db->aDb[1].pSchema->trigHash.count==0 ); } sqlite3VtabUnlockList(db); @@ -155854,11 +188824,8 @@ SQLITE_PRIVATE void sqlite3LeaveMutexAndCloseZombie(sqlite3 *db){ #ifndef SQLITE_OMIT_VIRTUALTABLE for(i=sqliteHashFirst(&db->aModule); i; i=sqliteHashNext(i)){ Module *pMod = (Module *)sqliteHashData(i); - if( pMod->xDestroy ){ - pMod->xDestroy(pMod->pAux); - } sqlite3VtabEponymousTableClear(db, pMod); - sqlite3DbFree(db, pMod); + sqlite3VtabModuleUnref(db, pMod); } sqlite3HashClear(&db->aModule); #endif @@ -155866,22 +188833,21 @@ SQLITE_PRIVATE void sqlite3LeaveMutexAndCloseZombie(sqlite3 *db){ sqlite3Error(db, SQLITE_OK); /* Deallocates any cached error strings. */ sqlite3ValueFree(db->pErr); sqlite3CloseExtensions(db); -#if SQLITE_USER_AUTHENTICATION - sqlite3_free(db->auth.zAuthUser); - sqlite3_free(db->auth.zAuthPW); -#endif - db->magic = SQLITE_MAGIC_ERROR; + db->eOpenState = SQLITE_STATE_ERROR; /* The temp-database schema is allocated differently from the other schema ** objects (using sqliteMalloc() directly, instead of sqlite3BtreeSchema()). ** So it needs to be freed here. Todo: Why not roll the temp schema into - ** the same sqliteMalloc() as the one that allocates the database + ** the same sqliteMalloc() as the one that allocates the database ** structure? */ sqlite3DbFree(db, db->aDb[1].pSchema); + if( db->xAutovacDestr ){ + db->xAutovacDestr(db->pAutovacPagesArg); + } sqlite3_mutex_leave(db->mutex); - db->magic = SQLITE_MAGIC_CLOSED; + db->eOpenState = SQLITE_STATE_CLOSED; sqlite3_mutex_free(db->mutex); assert( sqlite3LookasideUsed(db,0)==0 ); if( db->lookaside.bMalloced ){ @@ -155904,7 +188870,7 @@ SQLITE_PRIVATE void sqlite3RollbackAll(sqlite3 *db, int tripCode){ assert( sqlite3_mutex_held(db->mutex) ); sqlite3BeginBenignMalloc(); - /* Obtain all b-tree mutexes before making any calls to BtreeRollback(). + /* Obtain all b-tree mutexes before making any calls to BtreeRollback(). ** This is important in case the transaction being rolled back has ** modified the database schema. If the b-tree mutexes are not taken ** here, then another shared-cache connection might sneak in between @@ -155916,7 +188882,7 @@ SQLITE_PRIVATE void sqlite3RollbackAll(sqlite3 *db, int tripCode){ for(i=0; inDb; i++){ Btree *p = db->aDb[i].pBt; if( p ){ - if( sqlite3BtreeIsInTrans(p) ){ + if( sqlite3BtreeTxnState(p)==SQLITE_TXN_WRITE ){ inTrans = 1; } sqlite3BtreeRollback(p, tripCode, !schemaChange); @@ -155934,7 +188900,7 @@ SQLITE_PRIVATE void sqlite3RollbackAll(sqlite3 *db, int tripCode){ /* Any deferred constraint violations have now been resolved. */ db->nDeferredCons = 0; db->nDeferredImmCons = 0; - db->flags &= ~(u64)SQLITE_DeferFKs; + db->flags &= ~(u64)(SQLITE_DeferFKs|SQLITE_CorruptRdOnly); /* If one has been configured, invoke the rollback-hook callback */ if( db->xRollbackCallback && (inTrans || !db->autoCommit) ){ @@ -155955,6 +188921,9 @@ SQLITE_PRIVATE const char *sqlite3ErrName(int rc){ case SQLITE_OK: zName = "SQLITE_OK"; break; case SQLITE_ERROR: zName = "SQLITE_ERROR"; break; case SQLITE_ERROR_SNAPSHOT: zName = "SQLITE_ERROR_SNAPSHOT"; break; + case SQLITE_ERROR_RETRY: zName = "SQLITE_ERROR_RETRY"; break; + case SQLITE_ERROR_MISSING_COLLSEQ: + zName = "SQLITE_ERROR_MISSING_COLLSEQ"; break; case SQLITE_INTERNAL: zName = "SQLITE_INTERNAL"; break; case SQLITE_PERM: zName = "SQLITE_PERM"; break; case SQLITE_ABORT: zName = "SQLITE_ABORT"; break; @@ -156008,6 +188977,7 @@ SQLITE_PRIVATE const char *sqlite3ErrName(int rc){ case SQLITE_CANTOPEN_ISDIR: zName = "SQLITE_CANTOPEN_ISDIR"; break; case SQLITE_CANTOPEN_FULLPATH: zName = "SQLITE_CANTOPEN_FULLPATH"; break; case SQLITE_CANTOPEN_CONVPATH: zName = "SQLITE_CANTOPEN_CONVPATH"; break; + case SQLITE_CANTOPEN_SYMLINK: zName = "SQLITE_CANTOPEN_SYMLINK"; break; case SQLITE_PROTOCOL: zName = "SQLITE_PROTOCOL"; break; case SQLITE_EMPTY: zName = "SQLITE_EMPTY"; break; case SQLITE_SCHEMA: zName = "SQLITE_SCHEMA"; break; @@ -156039,6 +189009,7 @@ SQLITE_PRIVATE const char *sqlite3ErrName(int rc){ case SQLITE_NOTICE_RECOVER_WAL: zName = "SQLITE_NOTICE_RECOVER_WAL";break; case SQLITE_NOTICE_RECOVER_ROLLBACK: zName = "SQLITE_NOTICE_RECOVER_ROLLBACK"; break; + case SQLITE_NOTICE_RBU: zName = "SQLITE_NOTICE_RBU"; break; case SQLITE_WARNING: zName = "SQLITE_WARNING"; break; case SQLITE_WARNING_AUTOINDEX: zName = "SQLITE_WARNING_AUTOINDEX"; break; case SQLITE_DONE: zName = "SQLITE_DONE"; break; @@ -156129,12 +189100,11 @@ SQLITE_PRIVATE const char *sqlite3ErrStr(int rc){ */ static int sqliteDefaultBusyCallback( void *ptr, /* Database connection */ - int count, /* Number of times table has been busy */ - sqlite3_file *pFile /* The file on which the lock occurred */ + int count /* Number of times table has been busy */ ){ -#if SQLITE_OS_WIN || HAVE_USLEEP +#if SQLITE_OS_WIN || !defined(HAVE_NANOSLEEP) || HAVE_NANOSLEEP /* This case is for systems that have support for sleeping for fractions of - ** a second. Examples: All windows systems, unix systems with usleep() */ + ** a second. Examples: All windows systems, unix systems with nanosleep() */ static const u8 delays[] = { 1, 2, 5, 10, 15, 20, 25, 25, 25, 50, 50, 100 }; static const u8 totals[] = @@ -156144,19 +189114,6 @@ static int sqliteDefaultBusyCallback( int tmout = db->busyTimeout; int delay, prior; -#ifdef SQLITE_ENABLE_SETLK_TIMEOUT - if( sqlite3OsFileControl(pFile,SQLITE_FCNTL_LOCK_TIMEOUT,&tmout)==SQLITE_OK ){ - if( count ){ - tmout = 0; - sqlite3OsFileControl(pFile, SQLITE_FCNTL_LOCK_TIMEOUT, &tmout); - return 0; - }else{ - return 1; - } - } -#else - UNUSED_PARAMETER(pFile); -#endif assert( count>=0 ); if( count < NDELAY ){ delay = delays[count]; @@ -156176,7 +189133,6 @@ static int sqliteDefaultBusyCallback( ** must be done in increments of whole seconds */ sqlite3 *db = (sqlite3 *)ptr; int tmout = ((sqlite3 *)ptr)->busyTimeout; - UNUSED_PARAMETER(pFile); if( (count+1)*1000 > tmout ){ return 0; } @@ -156194,25 +189150,16 @@ static int sqliteDefaultBusyCallback( ** If this routine returns non-zero, the lock is retried. If it ** returns 0, the operation aborts with an SQLITE_BUSY error. */ -SQLITE_PRIVATE int sqlite3InvokeBusyHandler(BusyHandler *p, sqlite3_file *pFile){ +SQLITE_PRIVATE int sqlite3InvokeBusyHandler(BusyHandler *p){ int rc; if( p->xBusyHandler==0 || p->nBusy<0 ) return 0; - if( p->bExtraFileArg ){ - /* Add an extra parameter with the pFile pointer to the end of the - ** callback argument list */ - int (*xTra)(void*,int,sqlite3_file*); - xTra = (int(*)(void*,int,sqlite3_file*))p->xBusyHandler; - rc = xTra(p->pBusyArg, p->nBusy, pFile); - }else{ - /* Legacy style busy handler callback */ - rc = p->xBusyHandler(p->pBusyArg, p->nBusy); - } + rc = p->xBusyHandler(p->pBusyArg, p->nBusy); if( rc==0 ){ p->nBusy = -1; }else{ p->nBusy++; } - return rc; + return rc; } /* @@ -156231,8 +189178,10 @@ SQLITE_API int sqlite3_busy_handler( db->busyHandler.xBusyHandler = xBusy; db->busyHandler.pBusyArg = pArg; db->busyHandler.nBusy = 0; - db->busyHandler.bExtraFileArg = 0; db->busyTimeout = 0; +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + db->setlkTimeout = 0; +#endif sqlite3_mutex_leave(db->mutex); return SQLITE_OK; } @@ -156244,9 +189193,9 @@ SQLITE_API int sqlite3_busy_handler( ** be invoked every nOps opcodes. */ SQLITE_API void sqlite3_progress_handler( - sqlite3 *db, + sqlite3 *db, int nOps, - int (*xProgress)(void*), + int (*xProgress)(void*), void *pArg ){ #ifdef SQLITE_ENABLE_API_ARMOR @@ -156278,14 +189227,52 @@ SQLITE_API int sqlite3_busy_timeout(sqlite3 *db, int ms){ #ifdef SQLITE_ENABLE_API_ARMOR if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; #endif + sqlite3_mutex_enter(db->mutex); if( ms>0 ){ sqlite3_busy_handler(db, (int(*)(void*,int))sqliteDefaultBusyCallback, (void*)db); db->busyTimeout = ms; - db->busyHandler.bExtraFileArg = 1; +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + db->setlkTimeout = ms; +#endif }else{ sqlite3_busy_handler(db, 0, 0); } + sqlite3_mutex_leave(db->mutex); + return SQLITE_OK; +} + +/* +** Set the setlk timeout value. +*/ +SQLITE_API int sqlite3_setlk_timeout(sqlite3 *db, int ms, int flags){ +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + int iDb; + int bBOC = ((flags & SQLITE_SETLK_BLOCK_ON_CONNECT) ? 1 : 0); +#endif +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif + if( ms<-1 ) return SQLITE_RANGE; +#ifdef SQLITE_ENABLE_SETLK_TIMEOUT + sqlite3_mutex_enter(db->mutex); + db->setlkTimeout = ms; + db->setlkFlags = flags; + sqlite3BtreeEnterAll(db); + for(iDb=0; iDbnDb; iDb++){ + Btree *pBt = db->aDb[iDb].pBt; + if( pBt ){ + sqlite3_file *fd = sqlite3PagerFile(sqlite3BtreePager(pBt)); + sqlite3OsFileControlHint(fd, SQLITE_FCNTL_BLOCK_ON_CONNECT, (void*)&bBOC); + } + } + sqlite3BtreeLeaveAll(db); + sqlite3_mutex_leave(db->mutex); +#endif +#if !defined(SQLITE_ENABLE_API_ARMOR) && !defined(SQLITE_ENABLE_SETLK_TIMEOUT) + UNUSED_PARAMETER(db); + UNUSED_PARAMETER(flags); +#endif return SQLITE_OK; } @@ -156294,20 +189281,37 @@ SQLITE_API int sqlite3_busy_timeout(sqlite3 *db, int ms){ */ SQLITE_API void sqlite3_interrupt(sqlite3 *db){ #ifdef SQLITE_ENABLE_API_ARMOR - if( !sqlite3SafetyCheckOk(db) && (db==0 || db->magic!=SQLITE_MAGIC_ZOMBIE) ){ + if( !sqlite3SafetyCheckOk(db) + && (db==0 || db->eOpenState!=SQLITE_STATE_ZOMBIE) + ){ (void)SQLITE_MISUSE_BKPT; return; } #endif - db->u1.isInterrupted = 1; + AtomicStore(&db->u1.isInterrupted, 1); } +/* +** Return true or false depending on whether or not an interrupt is +** pending on connection db. +*/ +SQLITE_API int sqlite3_is_interrupted(sqlite3 *db){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) + && (db==0 || db->eOpenState!=SQLITE_STATE_ZOMBIE) + ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif + return AtomicLoad(&db->u1.isInterrupted)!=0; +} /* ** This function is exactly the same as sqlite3_create_function(), except ** that it is designed to be called by internal code. The difference is ** that if a malloc() fails in sqlite3_create_function(), an error code -** is returned and the mallocFailed flag cleared. +** is returned and the mallocFailed flag cleared. */ SQLITE_PRIVATE int sqlite3CreateFunc( sqlite3 *db, @@ -156323,7 +189327,6 @@ SQLITE_PRIVATE int sqlite3CreateFunc( FuncDestructor *pDestructor ){ FuncDef *p; - int nName; int extraFlags; assert( sqlite3_mutex_held(db->mutex) ); @@ -156333,15 +189336,24 @@ SQLITE_PRIVATE int sqlite3CreateFunc( || ((xFinal==0)!=(xStep==0)) /* Both or neither of xFinal and xStep */ || ((xValue==0)!=(xInverse==0)) /* Both or neither of xValue, xInverse */ || (nArg<-1 || nArg>SQLITE_MAX_FUNCTION_ARG) - || (255<(nName = sqlite3Strlen30( zFunctionName))) + || (255funcFlags & SQLITE_FUNC_ENCMASK)==(u32)enc && p->nArg==nArg ){ if( db->nVdbeActive ){ - sqlite3ErrorWithMsg(db, SQLITE_BUSY, + sqlite3ErrorWithMsg(db, SQLITE_BUSY, "unable to delete/modify user-function due to active statements"); assert( !db->mallocFailed ); return SQLITE_BUSY; }else{ sqlite3ExpirePreparedStatements(db, 0); } + }else if( xSFunc==0 && xFinal==0 ){ + /* Trying to delete a function that does not exist. This is a no-op. + ** https://sqlite.org/forum/forumpost/726219164b */ + return SQLITE_OK; } p = sqlite3FindFunction(db, zFunctionName, nArg, (u8)enc, 1); @@ -156402,6 +189431,7 @@ SQLITE_PRIVATE int sqlite3CreateFunc( p->u.pDestructor = pDestructor; p->funcFlags = (p->funcFlags & SQLITE_FUNC_ENCMASK) | extraFlags; testcase( p->funcFlags & SQLITE_DETERMINISTIC ); + testcase( p->funcFlags & SQLITE_DIRECTONLY ); p->xSFunc = xSFunc ? xSFunc : xStep; p->xFinalize = xFinal; p->xValue = xValue; @@ -156451,11 +189481,11 @@ static int createFunctionApi( pArg->xDestroy = xDestroy; pArg->pUserData = p; } - rc = sqlite3CreateFunc(db, zFunc, nArg, enc, p, + rc = sqlite3CreateFunc(db, zFunc, nArg, enc, p, xSFunc, xStep, xFinal, xValue, xInverse, pArg ); if( pArg && pArg->nRef==0 ){ - assert( rc!=SQLITE_OK ); + assert( rc!=SQLITE_OK || (xStep==0 && xFinal==0) ); xDestroy(p); sqlite3_free(pArg); } @@ -156568,7 +189598,7 @@ static void sqlite3InvalidFunction( ** ** If the function already exists as a regular global function, then ** this routine is a no-op. If the function does not exist, then create -** a new one that always throws a run-time error. +** a new one that always throws a run-time error. ** ** When virtual tables intend to provide an overloaded function, they ** should call this routine to make sure the global function exists. @@ -156592,7 +189622,7 @@ SQLITE_API int sqlite3_overload_function( rc = sqlite3FindFunction(db, zName, nArg, SQLITE_UTF8, 0)!=0; sqlite3_mutex_leave(db->mutex); if( rc ) return SQLITE_OK; - zCopy = sqlite3_mprintf(zName); + zCopy = sqlite3_mprintf("%s", zName); if( zCopy==0 ) return SQLITE_NOMEM; return sqlite3_create_function_v2(db, zName, nArg, SQLITE_UTF8, zCopy, sqlite3InvalidFunction, 0, 0, sqlite3_free); @@ -156601,7 +189631,7 @@ SQLITE_API int sqlite3_overload_function( #ifndef SQLITE_OMIT_TRACE /* ** Register a trace function. The pArg from the previously registered trace -** is returned. +** is returned. ** ** A NULL trace function means that no tracing is executes. A non-NULL ** trace is a pointer to a function that is invoked at the start of each @@ -156620,7 +189650,7 @@ SQLITE_API void *sqlite3_trace(sqlite3 *db, void(*xTrace)(void*,const char*), vo sqlite3_mutex_enter(db->mutex); pOld = db->pTraceArg; db->mTrace = xTrace ? SQLITE_TRACE_LEGACY : 0; - db->xTrace = (int(*)(u32,void*,void*,void*))xTrace; + db->trace.xLegacy = xTrace; db->pTraceArg = pArg; sqlite3_mutex_leave(db->mutex); return pOld; @@ -156644,7 +189674,7 @@ SQLITE_API int sqlite3_trace_v2( if( mTrace==0 ) xTrace = 0; if( xTrace==0 ) mTrace = 0; db->mTrace = mTrace; - db->xTrace = xTrace; + db->trace.xV2 = xTrace; db->pTraceArg = pArg; sqlite3_mutex_leave(db->mutex); return SQLITE_OK; @@ -156652,8 +189682,8 @@ SQLITE_API int sqlite3_trace_v2( #ifndef SQLITE_OMIT_DEPRECATED /* -** Register a profile function. The pArg from the previously registered -** profile function is returned. +** Register a profile function. The pArg from the previously registered +** profile function is returned. ** ** A NULL profile function means that no profiling is executes. A non-NULL ** profile is a pointer to a function that is invoked at the conclusion of @@ -156772,6 +189802,12 @@ SQLITE_API void *sqlite3_preupdate_hook( void *pArg /* First callback argument */ ){ void *pRet; + +#ifdef SQLITE_ENABLE_API_ARMOR + if( db==0 ){ + return 0; + } +#endif sqlite3_mutex_enter(db->mutex); pRet = db->pPreUpdateArg; db->xPreUpdateCallback = xCallback; @@ -156781,13 +189817,41 @@ SQLITE_API void *sqlite3_preupdate_hook( } #endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ +/* +** Register a function to be invoked prior to each autovacuum that +** determines the number of pages to vacuum. +*/ +SQLITE_API int sqlite3_autovacuum_pages( + sqlite3 *db, /* Attach the hook to this database */ + unsigned int (*xCallback)(void*,const char*,u32,u32,u32), + void *pArg, /* Argument to the function */ + void (*xDestructor)(void*) /* Destructor for pArg */ +){ +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + if( xDestructor ) xDestructor(pArg); + return SQLITE_MISUSE_BKPT; + } +#endif + sqlite3_mutex_enter(db->mutex); + if( db->xAutovacDestr ){ + db->xAutovacDestr(db->pAutovacPagesArg); + } + db->xAutovacPages = xCallback; + db->pAutovacPagesArg = pArg; + db->xAutovacDestr = xDestructor; + sqlite3_mutex_leave(db->mutex); + return SQLITE_OK; +} + + #ifndef SQLITE_OMIT_WAL /* ** The sqlite3_wal_hook() callback registered by sqlite3_wal_autocheckpoint(). ** Invoke sqlite3_wal_checkpoint if the number of frames in the log file ** is greater than sqlite3.pWalArg cast to an integer (the value configured by ** wal_autocheckpoint()). -*/ +*/ SQLITE_PRIVATE int sqlite3WalDefaultHook( void *pClientData, /* Argument */ sqlite3 *db, /* Connection */ @@ -156855,6 +189919,9 @@ SQLITE_API void *sqlite3_wal_hook( sqlite3_mutex_leave(db->mutex); return pRet; #else + UNUSED_PARAMETER(db); + UNUSED_PARAMETER(xCallback); + UNUSED_PARAMETER(pArg); return 0; #endif } @@ -156870,10 +189937,15 @@ SQLITE_API int sqlite3_wal_checkpoint_v2( int *pnCkpt /* OUT: Total number of frames checkpointed */ ){ #ifdef SQLITE_OMIT_WAL + UNUSED_PARAMETER(db); + UNUSED_PARAMETER(zDb); + UNUSED_PARAMETER(eMode); + UNUSED_PARAMETER(pnLog); + UNUSED_PARAMETER(pnCkpt); return SQLITE_OK; #else int rc; /* Return code */ - int iDb = SQLITE_MAX_ATTACHED; /* sqlite3.aDb[] index of db to checkpoint */ + int iDb; /* Schema to checkpoint */ #ifdef SQLITE_ENABLE_API_ARMOR if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; @@ -156883,19 +189955,22 @@ SQLITE_API int sqlite3_wal_checkpoint_v2( if( pnLog ) *pnLog = -1; if( pnCkpt ) *pnCkpt = -1; + assert( SQLITE_CHECKPOINT_NOOP==-1 ); assert( SQLITE_CHECKPOINT_PASSIVE==0 ); assert( SQLITE_CHECKPOINT_FULL==1 ); assert( SQLITE_CHECKPOINT_RESTART==2 ); assert( SQLITE_CHECKPOINT_TRUNCATE==3 ); - if( eModeSQLITE_CHECKPOINT_TRUNCATE ){ + if( eModeSQLITE_CHECKPOINT_TRUNCATE ){ /* EVIDENCE-OF: R-03996-12088 The M parameter must be a valid checkpoint ** mode: */ - return SQLITE_MISUSE; + return SQLITE_MISUSE_BKPT; } sqlite3_mutex_enter(db->mutex); if( zDb && zDb[0] ){ iDb = sqlite3FindDbName(db, zDb); + }else{ + iDb = SQLITE_MAX_DB; /* This means process all schemas */ } if( iDb<0 ){ rc = SQLITE_ERROR; @@ -156910,7 +189985,7 @@ SQLITE_API int sqlite3_wal_checkpoint_v2( /* If there are no active statements, clear the interrupt flag at this ** point. */ if( db->nVdbeActive==0 ){ - db->u1.isInterrupted = 0; + AtomicStore(&db->u1.isInterrupted, 0); } sqlite3_mutex_leave(db->mutex); @@ -156921,7 +189996,7 @@ SQLITE_API int sqlite3_wal_checkpoint_v2( /* ** Checkpoint database zDb. If zDb is NULL, or if the buffer zDb points -** to contains a zero-length string, all attached databases are +** to contains a zero-length string, all attached databases are ** checkpointed. */ SQLITE_API int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb){ @@ -156935,16 +190010,16 @@ SQLITE_API int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb){ ** Run a checkpoint on database iDb. This is a no-op if database iDb is ** not currently open in WAL mode. ** -** If a transaction is open on the database being checkpointed, this -** function returns SQLITE_LOCKED and a checkpoint is not attempted. If -** an error occurs while running the checkpoint, an SQLite error code is +** If a transaction is open on the database being checkpointed, this +** function returns SQLITE_LOCKED and a checkpoint is not attempted. If +** an error occurs while running the checkpoint, an SQLite error code is ** returned (i.e. SQLITE_IOERR). Otherwise, SQLITE_OK. ** ** The mutex on database handle db should be held by the caller. The mutex ** associated with the specific b-tree being checkpointed is taken by ** this function while the checkpoint is running. ** -** If iDb is passed SQLITE_MAX_ATTACHED, then all attached databases are +** If iDb is passed SQLITE_MAX_DB then all attached databases are ** checkpointed. If an error is encountered it is returned immediately - ** no attempt is made to checkpoint any remaining databases. ** @@ -156959,9 +190034,11 @@ SQLITE_PRIVATE int sqlite3Checkpoint(sqlite3 *db, int iDb, int eMode, int *pnLog assert( sqlite3_mutex_held(db->mutex) ); assert( !pnLog || *pnLog==-1 ); assert( !pnCkpt || *pnCkpt==-1 ); + testcase( iDb==SQLITE_MAX_ATTACHED ); /* See forum post a006d86f72 */ + testcase( iDb==SQLITE_MAX_DB ); for(i=0; inDb && rc==SQLITE_OK; i++){ - if( i==iDb || iDb==SQLITE_MAX_ATTACHED ){ + if( i==iDb || iDb==SQLITE_MAX_DB ){ rc = sqlite3BtreeCheckpoint(db->aDb[i].pBt, eMode, pnLog, pnCkpt); pnLog = 0; pnCkpt = 0; @@ -157039,6 +190116,44 @@ SQLITE_API const char *sqlite3_errmsg(sqlite3 *db){ return z; } +/* +** Set the error code and error message associated with the database handle. +** +** This routine is intended to be called by outside extensions (ex: the +** Session extension). Internal logic should invoke sqlite3Error() or +** sqlite3ErrorWithMsg() directly. +*/ +SQLITE_API int sqlite3_set_errmsg(sqlite3 *db, int errcode, const char *zMsg){ + int rc = SQLITE_OK; + if( !sqlite3SafetyCheckOk(db) ){ + return SQLITE_MISUSE_BKPT; + } + sqlite3_mutex_enter(db->mutex); + if( zMsg ){ + sqlite3ErrorWithMsg(db, errcode, "%s", zMsg); + }else{ + sqlite3Error(db, errcode); + } + rc = sqlite3ApiExit(db, rc); + sqlite3_mutex_leave(db->mutex); + return rc; +} + +/* +** Return the byte offset of the most recent error +*/ +SQLITE_API int sqlite3_error_offset(sqlite3 *db){ + int iOffset = -1; + if( db && sqlite3SafetyCheckSickOrOk(db) ){ + sqlite3_mutex_enter(db->mutex); + if( db->errCode ){ + iOffset = db->errByteOffset; + } + sqlite3_mutex_leave(db->mutex); + } + return iOffset; +} + #ifndef SQLITE_OMIT_UTF16 /* ** Return UTF-16 encoded English language explanation of the most recent @@ -157087,26 +190202,44 @@ SQLITE_API const void *sqlite3_errmsg16(sqlite3 *db){ ** passed to this function, we assume a malloc() failed during sqlite3_open(). */ SQLITE_API int sqlite3_errcode(sqlite3 *db){ - if( db && !sqlite3SafetyCheckSickOrOk(db) ){ + int iRet; + if( !db ) return SQLITE_NOMEM_BKPT; + if( !sqlite3SafetyCheckSickOrOk(db) ){ return SQLITE_MISUSE_BKPT; } - if( !db || db->mallocFailed ){ - return SQLITE_NOMEM_BKPT; + sqlite3_mutex_enter(db->mutex); + if( db->mallocFailed ){ + iRet = SQLITE_NOMEM_BKPT; + }else{ + iRet = db->errCode & db->errMask; } - return db->errCode & db->errMask; + sqlite3_mutex_leave(db->mutex); + return iRet; } SQLITE_API int sqlite3_extended_errcode(sqlite3 *db){ - if( db && !sqlite3SafetyCheckSickOrOk(db) ){ + int iRet; + if( !db ) return SQLITE_NOMEM_BKPT; + if( !sqlite3SafetyCheckSickOrOk(db) ){ return SQLITE_MISUSE_BKPT; } - if( !db || db->mallocFailed ){ - return SQLITE_NOMEM_BKPT; + sqlite3_mutex_enter(db->mutex); + if( db->mallocFailed ){ + iRet = SQLITE_NOMEM_BKPT; + }else{ + iRet = db->errCode; } - return db->errCode; + sqlite3_mutex_leave(db->mutex); + return iRet; } SQLITE_API int sqlite3_system_errno(sqlite3 *db){ - return db ? db->iSysErrno : 0; -} + int iRet = 0; + if( db ){ + sqlite3_mutex_enter(db->mutex); + iRet = db->iSysErrno; + sqlite3_mutex_leave(db->mutex); + } + return iRet; +} /* ** Return a string that describes the kind of error specified in the @@ -157123,7 +190256,7 @@ SQLITE_API const char *sqlite3_errstr(int rc){ */ static int createCollation( sqlite3* db, - const char *zName, + const char *zName, u8 enc, void* pCtx, int(*xCompare)(void*,int,const void*,int,const void*), @@ -157131,7 +190264,7 @@ static int createCollation( ){ CollSeq *pColl; int enc2; - + assert( sqlite3_mutex_held(db->mutex) ); /* If SQLITE_UTF16 is specified as the encoding type, transform this @@ -157148,14 +190281,14 @@ static int createCollation( return SQLITE_MISUSE_BKPT; } - /* Check if this call is removing or replacing an existing collation + /* Check if this call is removing or replacing an existing collation ** sequence. If so, and there are active VMs, return busy. If there ** are no active VMs, invalidate any pre-compiled statements. */ pColl = sqlite3FindCollSeq(db, (u8)enc2, zName, 0); if( pColl && pColl->xCmp ){ if( db->nVdbeActive ){ - sqlite3ErrorWithMsg(db, SQLITE_BUSY, + sqlite3ErrorWithMsg(db, SQLITE_BUSY, "unable to delete/modify collation sequence due to active statements"); return SQLITE_BUSY; } @@ -157166,7 +190299,7 @@ static int createCollation( ** then any copies made by synthCollSeq() need to be invalidated. ** Also, collation destructor - CollSeq.xDel() - function may need ** to be called. - */ + */ if( (pColl->enc & ~SQLITE_UTF16_ALIGNED)==enc2 ){ CollSeq *aColl = sqlite3HashFind(&db->aCollSeq, zName); int j; @@ -157211,6 +190344,7 @@ static const int aHardLimit[] = { SQLITE_MAX_VARIABLE_NUMBER, /* IMP: R-38091-32352 */ SQLITE_MAX_TRIGGER_DEPTH, SQLITE_MAX_WORKER_THREADS, + SQLITE_MAX_PARSER_DEPTH, }; /* @@ -157225,14 +190359,17 @@ static const int aHardLimit[] = { #if SQLITE_MAX_SQL_LENGTH>SQLITE_MAX_LENGTH # error SQLITE_MAX_SQL_LENGTH must not be greater than SQLITE_MAX_LENGTH #endif +#if SQLITE_MAX_SQL_LENGTH>2147482624 /* 1024 less than 2^31 */ +# error SQLITE_MAX_SQL_LENGTH must not be greater than 2147482624 +#endif #if SQLITE_MAX_COMPOUND_SELECT<2 # error SQLITE_MAX_COMPOUND_SELECT must be at least 2 #endif #if SQLITE_MAX_VDBE_OP<40 # error SQLITE_MAX_VDBE_OP must be at least 40 #endif -#if SQLITE_MAX_FUNCTION_ARG<0 || SQLITE_MAX_FUNCTION_ARG>127 -# error SQLITE_MAX_FUNCTION_ARG must be between 0 and 127 +#if SQLITE_MAX_FUNCTION_ARG<0 || SQLITE_MAX_FUNCTION_ARG>32767 +# error SQLITE_MAX_FUNCTION_ARG must be between 0 and 32767 #endif #if SQLITE_MAX_ATTACHED<0 || SQLITE_MAX_ATTACHED>125 # error SQLITE_MAX_ATTACHED must be between 0 and 125 @@ -157280,6 +190417,7 @@ SQLITE_API int sqlite3_limit(sqlite3 *db, int limitId, int newLimit){ assert( aHardLimit[SQLITE_LIMIT_SQL_LENGTH]==SQLITE_MAX_SQL_LENGTH ); assert( aHardLimit[SQLITE_LIMIT_COLUMN]==SQLITE_MAX_COLUMN ); assert( aHardLimit[SQLITE_LIMIT_EXPR_DEPTH]==SQLITE_MAX_EXPR_DEPTH ); + assert( aHardLimit[SQLITE_LIMIT_PARSER_DEPTH]==SQLITE_MAX_PARSER_DEPTH ); assert( aHardLimit[SQLITE_LIMIT_COMPOUND_SELECT]==SQLITE_MAX_COMPOUND_SELECT); assert( aHardLimit[SQLITE_LIMIT_VDBE_OP]==SQLITE_MAX_VDBE_OP ); assert( aHardLimit[SQLITE_LIMIT_FUNCTION_ARG]==SQLITE_MAX_FUNCTION_ARG ); @@ -157289,19 +190427,23 @@ SQLITE_API int sqlite3_limit(sqlite3 *db, int limitId, int newLimit){ assert( aHardLimit[SQLITE_LIMIT_VARIABLE_NUMBER]==SQLITE_MAX_VARIABLE_NUMBER); assert( aHardLimit[SQLITE_LIMIT_TRIGGER_DEPTH]==SQLITE_MAX_TRIGGER_DEPTH ); assert( aHardLimit[SQLITE_LIMIT_WORKER_THREADS]==SQLITE_MAX_WORKER_THREADS ); - assert( SQLITE_LIMIT_WORKER_THREADS==(SQLITE_N_LIMIT-1) ); + assert( SQLITE_LIMIT_PARSER_DEPTH==(SQLITE_N_LIMIT-1) ); if( limitId<0 || limitId>=SQLITE_N_LIMIT ){ return -1; } + sqlite3_mutex_enter(db->mutex); oldLimit = db->aLimit[limitId]; if( newLimit>=0 ){ /* IMP: R-52476-28732 */ if( newLimit>aHardLimit[limitId] ){ newLimit = aHardLimit[limitId]; /* IMP: R-51463-25634 */ + }else if( newLimitaLimit[limitId] = newLimit; } + sqlite3_mutex_leave(db->mutex); return oldLimit; /* IMP: R-53341-35419 */ } @@ -157315,17 +190457,19 @@ SQLITE_API int sqlite3_limit(sqlite3 *db, int limitId, int newLimit){ ** query parameter. The second argument contains the URI (or non-URI filename) ** itself. When this function is called the *pFlags variable should contain ** the default flags to open the database handle with. The value stored in -** *pFlags may be updated before returning if the URI filename contains +** *pFlags may be updated before returning if the URI filename contains ** "cache=xxx" or "mode=xxx" query parameters. ** ** If successful, SQLITE_OK is returned. In this case *ppVfs is set to point to ** the VFS that should be used to open the database file. *pzFile is set to -** point to a buffer containing the name of the file to open. It is the -** responsibility of the caller to eventually call sqlite3_free() to release -** this buffer. +** point to a buffer containing the name of the file to open. The value +** stored in *pzFile is a database name acceptable to sqlite3_uri_parameter() +** and is in the same format as names created using sqlite3_create_filename(). +** The caller must invoke sqlite3_free_filename() (not sqlite3_free()!) on +** the value returned in *pzFile to avoid a memory leak. ** ** If an error occurs, then an SQLite error code is returned and *pzErrMsg -** may be set to point to a buffer containing an English language error +** may be set to point to a buffer containing an English language error ** message. It is the responsibility of the caller to eventually release ** this buffer by calling sqlite3_free(). */ @@ -157333,7 +190477,7 @@ SQLITE_PRIVATE int sqlite3ParseUri( const char *zDefaultVfs, /* VFS to use if no "vfs=xxx" query option */ const char *zUri, /* Nul-terminated URI to parse */ unsigned int *pFlags, /* IN/OUT: SQLITE_OPEN_XXX flags */ - sqlite3_vfs **ppVfs, /* OUT: VFS to use */ + sqlite3_vfs **ppVfs, /* OUT: VFS to use */ char **pzFile, /* OUT: Filename component of URI */ char **pzErrMsg /* OUT: Error message (if rc!=SQLITE_OK) */ ){ @@ -157342,21 +190486,21 @@ SQLITE_PRIVATE int sqlite3ParseUri( const char *zVfs = zDefaultVfs; char *zFile; char c; - int nUri = sqlite3Strlen30(zUri); + i64 nUri = strlen(zUri); assert( *pzErrMsg==0 ); - if( ((flags & SQLITE_OPEN_URI) /* IMP: R-48725-32206 */ - || sqlite3GlobalConfig.bOpenUri) /* IMP: R-51689-46548 */ - && nUri>=5 && memcmp(zUri, "file:", 5)==0 /* IMP: R-57884-37496 */ + if( ((flags & SQLITE_OPEN_URI) /* IMP: R-48725-32206 */ + || AtomicLoad(&sqlite3GlobalConfig.bOpenUri)) /* IMP: R-51689-46548 */ + && nUri>=5 && memcmp(zUri, "file:", 5)==0 /* IMP: R-57884-37496 */ ){ char *zOpt; int eState; /* Parser state when parsing URI */ - int iIn; /* Input character index */ - int iOut = 0; /* Output character index */ - u64 nByte = nUri+2; /* Bytes of space to allocate */ + i64 iIn; /* Input character index */ + i64 iOut = 0; /* Output character index */ + u64 nByte = nUri+8; /* Bytes of space to allocate */ - /* Make sure the SQLITE_OPEN_URI flag is set to indicate to the VFS xOpen + /* Make sure the SQLITE_OPEN_URI flag is set to indicate to the VFS xOpen ** method that there may be extra parameters following the file-name. */ flags |= SQLITE_OPEN_URI; @@ -157364,6 +190508,9 @@ SQLITE_PRIVATE int sqlite3ParseUri( zFile = sqlite3_malloc64(nByte); if( !zFile ) return SQLITE_NOMEM_BKPT; + memset(zFile, 0, 4); /* 4-byte of 0x00 is the start of DB name marker */ + zFile += 4; + iIn = 5; #ifdef SQLITE_ALLOW_URI_AUTHORITY if( strncmp(zUri+5, "///", 3)==0 ){ @@ -157371,7 +190518,7 @@ SQLITE_PRIVATE int sqlite3ParseUri( /* The following condition causes URIs with five leading / characters ** like file://///host/path to be converted into UNCs like //host/path. ** The correct URI for that UNC has only two or four leading / characters - ** file://host/path or file:////host/path. But 5 leading slashes is a + ** file://host/path or file:////host/path. But 5 leading slashes is a ** common error, we are told, so we handle it as a special case. */ if( strncmp(zUri+7, "///", 3)==0 ){ iIn++; } }else if( strncmp(zUri+5, "//localhost/", 12)==0 ){ @@ -157383,16 +190530,16 @@ SQLITE_PRIVATE int sqlite3ParseUri( iIn = 7; while( zUri[iIn] && zUri[iIn]!='/' ) iIn++; if( iIn!=7 && (iIn!=16 || memcmp("localhost", &zUri[7], 9)) ){ - *pzErrMsg = sqlite3_mprintf("invalid uri authority: %.*s", - iIn-7, &zUri[7]); + *pzErrMsg = sqlite3_mprintf("invalid uri authority: %.*s", + (int)(iIn-7), &zUri[7]); rc = SQLITE_ERROR; goto parse_uri_out; } } #endif - /* Copy the filename and any query parameters into the zFile buffer. - ** Decode %HH escape codes along the way. + /* Copy the filename and any query parameters into the zFile buffer. + ** Decode %HH escape codes along the way. ** ** Within this loop, variable eState may be set to 0, 1 or 2, depending ** on the parsing context. As follows: @@ -157404,9 +190551,9 @@ SQLITE_PRIVATE int sqlite3ParseUri( eState = 0; while( (c = zUri[iIn])!=0 && c!='#' ){ iIn++; - if( c=='%' - && sqlite3Isxdigit(zUri[iIn]) - && sqlite3Isxdigit(zUri[iIn+1]) + if( c=='%' + && sqlite3Isxdigit(zUri[iIn]) + && sqlite3Isxdigit(zUri[iIn+1]) ){ int octet = (sqlite3HexToInt(zUri[iIn++]) << 4); octet += sqlite3HexToInt(zUri[iIn++]); @@ -157418,7 +190565,7 @@ SQLITE_PRIVATE int sqlite3ParseUri( ** case we ignore all text in the remainder of the path, name or ** value currently being parsed. So ignore the current character ** and skip to the next "?", "=" or "&", as appropriate. */ - while( (c = zUri[iIn])!=0 && c!='#' + while( (c = zUri[iIn])!=0 && c!='#' && (eState!=0 || c!='?') && (eState!=1 || (c!='=' && c!='&')) && (eState!=2 || c!='&') @@ -157453,18 +190600,17 @@ SQLITE_PRIVATE int sqlite3ParseUri( zFile[iOut++] = c; } if( eState==1 ) zFile[iOut++] = '\0'; - zFile[iOut++] = '\0'; - zFile[iOut++] = '\0'; + memset(zFile+iOut, 0, 4); /* end-of-options + empty journal filenames */ - /* Check if there were any options specified that should be interpreted + /* Check if there were any options specified that should be interpreted ** here. Options that are interpreted here include "vfs" and those that ** correspond to flags that may be passed to the sqlite3_open_v2() ** method. */ - zOpt = &zFile[sqlite3Strlen30(zFile)+1]; + zOpt = &zFile[strlen(zFile)+1]; while( zOpt[0] ){ - int nOpt = sqlite3Strlen30(zOpt); + i64 nOpt = strlen(zOpt); char *zVal = &zOpt[nOpt+1]; - int nVal = sqlite3Strlen30(zVal); + i64 nVal = strlen(zVal); if( nOpt==3 && memcmp("vfs", zOpt, 3)==0 ){ zVfs = zVal; @@ -157492,7 +190638,7 @@ SQLITE_PRIVATE int sqlite3ParseUri( if( nOpt==4 && memcmp("mode", zOpt, 4)==0 ){ static struct OpenMode aOpenMode[] = { { "ro", SQLITE_OPEN_READONLY }, - { "rw", SQLITE_OPEN_READWRITE }, + { "rw", SQLITE_OPEN_READWRITE }, { "rwc", SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE }, { "memory", SQLITE_OPEN_MEMORY }, { 0, 0 } @@ -157510,7 +190656,7 @@ SQLITE_PRIVATE int sqlite3ParseUri( int mode = 0; for(i=0; aMode[i].z; i++){ const char *z = aMode[i].z; - if( nVal==sqlite3Strlen30(z) && 0==memcmp(zVal, z, nVal) ){ + if( nVal==(i64)strlen(z) && 0==memcmp(zVal, z, nVal) ){ mode = aMode[i].mode; break; } @@ -157534,13 +190680,14 @@ SQLITE_PRIVATE int sqlite3ParseUri( } }else{ - zFile = sqlite3_malloc64(nUri+2); + zFile = sqlite3_malloc64(nUri+8); if( !zFile ) return SQLITE_NOMEM_BKPT; + memset(zFile, 0, 4); + zFile += 4; if( nUri ){ memcpy(zFile, zUri, nUri); } - zFile[nUri] = '\0'; - zFile[nUri+1] = '\0'; + memset(zFile+nUri, 0, 4); flags &= ~SQLITE_OPEN_URI; } @@ -157551,7 +190698,7 @@ SQLITE_PRIVATE int sqlite3ParseUri( } parse_uri_out: if( rc!=SQLITE_OK ){ - sqlite3_free(zFile); + sqlite3_free_filename(zFile); zFile = 0; } *pFlags = flags; @@ -157559,44 +190706,26 @@ SQLITE_PRIVATE int sqlite3ParseUri( return rc; } -#if defined(SQLITE_HAS_CODEC) /* -** Process URI filename query parameters relevant to the SQLite Encryption -** Extension. Return true if any of the relevant query parameters are -** seen and return false if not. +** This routine does the core work of extracting URI parameters from a +** database filename for the sqlite3_uri_parameter() interface. */ -SQLITE_PRIVATE int sqlite3CodecQueryParameters( - sqlite3 *db, /* Database connection */ - const char *zDb, /* Which schema is being created/attached */ - const char *zUri /* URI filename */ -){ - const char *zKey; - if( (zKey = sqlite3_uri_parameter(zUri, "hexkey"))!=0 && zKey[0] ){ - u8 iByte; - int i; - char zDecoded[40]; - for(i=0, iByte=0; imutex); - db->errMask = 0xff; + db->errMask = (flags & SQLITE_OPEN_EXRESCODE)!=0 ? 0xffffffff : 0xff; db->nDb = 2; - db->magic = SQLITE_MAGIC_BUSY; + db->eOpenState = SQLITE_STATE_BUSY; db->aDb = db->aDbStatic; db->lookaside.bDisable = 1; + db->lookaside.sz = 0; + db->nFpDigit = 17; assert( sizeof(db->aLimit)==sizeof(aHardLimit) ); memcpy(db->aLimit, aHardLimit, sizeof(db->aLimit)); @@ -157691,8 +190823,50 @@ static int openDatabase( db->nextAutovac = -1; db->szMmap = sqlite3GlobalConfig.szMmap; db->nextPagesize = 0; + db->init.azInit = sqlite3StdType; /* Any array of string ptrs will do */ +#ifdef SQLITE_ENABLE_SORTER_MMAP + /* Beginning with version 3.37.0, using the VFS xFetch() API to memory-map + ** the temporary files used to do external sorts (see code in vdbesort.c) + ** is disabled. It can still be used either by defining + ** SQLITE_ENABLE_SORTER_MMAP at compile time or by using the + ** SQLITE_TESTCTRL_SORTER_MMAP test-control at runtime. */ db->nMaxSorterMmap = 0x7FFFFFFF; - db->flags |= SQLITE_ShortColNames | SQLITE_EnableTrigger | SQLITE_CacheSpill +#endif + db->flags |= SQLITE_ShortColNames + | SQLITE_EnableTrigger + | SQLITE_EnableView + | SQLITE_CacheSpill + | SQLITE_AttachCreate + | SQLITE_AttachWrite + | SQLITE_Comments +#if !defined(SQLITE_TRUSTED_SCHEMA) || SQLITE_TRUSTED_SCHEMA+0!=0 + | SQLITE_TrustedSchema +#endif +/* The SQLITE_DQS compile-time option determines the default settings +** for SQLITE_DBCONFIG_DQS_DDL and SQLITE_DBCONFIG_DQS_DML. +** +** SQLITE_DQS SQLITE_DBCONFIG_DQS_DDL SQLITE_DBCONFIG_DQS_DML +** ---------- ----------------------- ----------------------- +** undefined on on +** 3 on on +** 2 on off +** 1 off on +** 0 off off +** +** Legacy behavior is 3 (double-quoted string literals are allowed anywhere) +** and so that is the default. But developers are encouraged to use +** -DSQLITE_DQS=0 (best) or -DSQLITE_DQS=1 (second choice) if possible. +*/ +#if !defined(SQLITE_DQS) +# define SQLITE_DQS 3 +#endif +#if (SQLITE_DQS&1)==1 + | SQLITE_DqsDML +#endif +#if (SQLITE_DQS&2)==2 + | SQLITE_DqsDDL +#endif + #if !defined(SQLITE_DEFAULT_AUTOMATIC_INDEX) || SQLITE_DEFAULT_AUTOMATIC_INDEX | SQLITE_AutoIndex #endif @@ -157725,6 +190899,12 @@ static int openDatabase( #endif #if defined(SQLITE_DEFAULT_DEFENSIVE) | SQLITE_Defensive +#endif +#if defined(SQLITE_DEFAULT_LEGACY_ALTER_TABLE) + | SQLITE_LegacyAlter +#endif +#if defined(SQLITE_ENABLE_STMT_SCANSTATUS) + | SQLITE_StmtScanStatus #endif ; sqlite3HashInit(&db->aCollSeq); @@ -157743,19 +190923,27 @@ static int openDatabase( createCollation(db, sqlite3StrBINARY, SQLITE_UTF16BE, 0, binCollFunc, 0); createCollation(db, sqlite3StrBINARY, SQLITE_UTF16LE, 0, binCollFunc, 0); createCollation(db, "NOCASE", SQLITE_UTF8, 0, nocaseCollatingFunc, 0); - createCollation(db, "RTRIM", SQLITE_UTF8, (void*)1, binCollFunc, 0); + createCollation(db, "RTRIM", SQLITE_UTF8, 0, rtrimCollFunc, 0); if( db->mallocFailed ){ goto opendb_out; } - /* EVIDENCE-OF: R-08308-17224 The default collating function for all - ** strings is BINARY. - */ - db->pDfltColl = sqlite3FindCollSeq(db, SQLITE_UTF8, sqlite3StrBINARY, 0); - assert( db->pDfltColl!=0 ); + +#if SQLITE_OS_UNIX && defined(SQLITE_OS_KV_OPTIONAL) + /* Process magic filenames ":localStorage:" and ":sessionStorage:" */ + if( zFilename && zFilename[0]==':' ){ + if( strcmp(zFilename, ":localStorage:")==0 ){ + zFilename = "file:local?vfs=kvvfs"; + flags |= SQLITE_OPEN_URI; + }else if( strcmp(zFilename, ":sessionStorage:")==0 ){ + zFilename = "file:session?vfs=kvvfs"; + flags |= SQLITE_OPEN_URI; + } + } +#endif /* SQLITE_OS_UNIX && defined(SQLITE_OS_KV_OPTIONAL) */ /* Parse the filename/URI argument ** - ** Only allow sensible combinations of bits in the flags argument. + ** Only allow sensible combinations of bits in the flags argument. ** Throw an error if any non-sense combination is used. If we ** do not block illegal combinations here, it could trigger ** assert() statements in deeper layers. Sensible combinations @@ -157773,8 +190961,9 @@ static int openDatabase( testcase( (1<<(flags&7))==0x04 ); /* READWRITE */ testcase( (1<<(flags&7))==0x40 ); /* READWRITE | CREATE */ if( ((1<<(flags&7)) & 0x46)==0 ){ - rc = SQLITE_MISUSE_BKPT; /* IMP: R-65497-44594 */ + rc = SQLITE_MISUSE_BKPT; /* IMP: R-18321-05872 */ }else{ + if( zFilename==0 ) zFilename = ":memory:"; rc = sqlite3ParseUri(zVfs, zFilename, &flags, &db->pVfs, &zOpen, &zErrMsg); } if( rc!=SQLITE_OK ){ @@ -157783,6 +190972,12 @@ static int openDatabase( sqlite3_free(zErrMsg); goto opendb_out; } + assert( db->pVfs!=0 ); +#if SQLITE_OS_KV || defined(SQLITE_OS_KV_OPTIONAL) + if( sqlite3_stricmp(db->pVfs->zName, "kvvfs")==0 ){ + db->temp_store = 2; + } +#endif /* Open the backend database driver */ rc = sqlite3BtreeOpen(db->pVfs, zOpen, db, &db->aDb[0].pBt, 0, @@ -157796,19 +190991,21 @@ static int openDatabase( } sqlite3BtreeEnter(db->aDb[0].pBt); db->aDb[0].pSchema = sqlite3SchemaGet(db, db->aDb[0].pBt); - if( !db->mallocFailed ) ENC(db) = SCHEMA_ENC(db); + if( !db->mallocFailed ){ + sqlite3SetTextEncoding(db, SCHEMA_ENC(db)); + } sqlite3BtreeLeave(db->aDb[0].pBt); db->aDb[1].pSchema = sqlite3SchemaGet(db, 0); /* The default safety_level for the main database is FULL; for the temp - ** database it is OFF. This matches the pager layer defaults. + ** database it is OFF. This matches the pager layer defaults. */ db->aDb[0].zDbSName = "main"; db->aDb[0].safety_level = SQLITE_DEFAULT_SYNCHRONOUS+1; db->aDb[1].zDbSName = "temp"; db->aDb[1].safety_level = PAGER_SYNCHRONOUS_OFF; - db->magic = SQLITE_MAGIC_OPEN; + db->eOpenState = SQLITE_STATE_OPEN; if( db->mallocFailed ){ goto opendb_out; } @@ -157821,14 +191018,11 @@ static int openDatabase( sqlite3RegisterPerConnectionBuiltinFunctions(db); rc = sqlite3_errcode(db); -#ifdef SQLITE_ENABLE_FTS5 - /* Register any built-in FTS5 module before loading the automatic - ** extensions. This allows automatic extensions to register FTS5 - ** tokenizers and auxiliary functions. */ - if( !db->mallocFailed && rc==SQLITE_OK ){ - rc = sqlite3Fts5Init(db); + + /* Load compiled-in extensions */ + for(i=0; rc==SQLITE_OK && imallocFailed ){ - extern int sqlite3Fts1Init(sqlite3*); - rc = sqlite3Fts1Init(db); - } -#endif - -#ifdef SQLITE_ENABLE_FTS2 - if( !db->mallocFailed && rc==SQLITE_OK ){ - extern int sqlite3Fts2Init(sqlite3*); - rc = sqlite3Fts2Init(db); - } -#endif - -#ifdef SQLITE_ENABLE_FTS3 /* automatically defined by SQLITE_ENABLE_FTS4 */ - if( !db->mallocFailed && rc==SQLITE_OK ){ - rc = sqlite3Fts3Init(db); - } -#endif - -#if defined(SQLITE_ENABLE_ICU) || defined(SQLITE_ENABLE_ICU_COLLATIONS) - if( !db->mallocFailed && rc==SQLITE_OK ){ - rc = sqlite3IcuInit(db); - } -#endif - -#ifdef SQLITE_ENABLE_RTREE - if( !db->mallocFailed && rc==SQLITE_OK){ - rc = sqlite3RtreeInit(db); - } -#endif - -#ifdef SQLITE_ENABLE_DBPAGE_VTAB - if( !db->mallocFailed && rc==SQLITE_OK){ - rc = sqlite3DbpageRegister(db); - } -#endif - -#ifdef SQLITE_ENABLE_DBSTAT_VTAB - if( !db->mallocFailed && rc==SQLITE_OK){ - rc = sqlite3DbstatRegister(db); - } -#endif - -#ifdef SQLITE_ENABLE_JSON1 - if( !db->mallocFailed && rc==SQLITE_OK){ - rc = sqlite3Json1Init(db); - } -#endif - -#ifdef SQLITE_ENABLE_STMTVTAB - if( !db->mallocFailed && rc==SQLITE_OK){ - rc = sqlite3StmtVtabInit(db); - } +#ifdef SQLITE_ENABLE_INTERNAL_FUNCTIONS + /* Testing use only!!! The -DSQLITE_ENABLE_INTERNAL_FUNCTIONS=1 compile-time + ** option gives access to internal functions by default. + ** Testing use only!!! */ + db->mDbFlags |= DBFLAG_InternalFunc; #endif /* -DSQLITE_DEFAULT_LOCKING_MODE=1 makes EXCLUSIVE the default locking @@ -157922,12 +191067,12 @@ static int openDatabase( sqlite3_mutex_leave(db->mutex); } rc = sqlite3_errcode(db); - assert( db!=0 || rc==SQLITE_NOMEM ); - if( rc==SQLITE_NOMEM ){ + assert( db!=0 || (rc&0xff)==SQLITE_NOMEM ); + if( (rc&0xff)==SQLITE_NOMEM ){ sqlite3_close(db); db = 0; }else if( rc!=SQLITE_OK ){ - db->magic = SQLITE_MAGIC_SICK; + db->eOpenState = SQLITE_STATE_SICK; } *ppDb = db; #ifdef SQLITE_ENABLE_SQLLOG @@ -157937,11 +191082,8 @@ static int openDatabase( sqlite3GlobalConfig.xSqllog(pArg, db, zFilename, 0); } #endif -#if defined(SQLITE_HAS_CODEC) - if( rc==SQLITE_OK ) sqlite3CodecQueryParameters(db, 0, zOpen); -#endif - sqlite3_free(zOpen); - return rc & 0xff; + sqlite3_free_filename(zOpen); + return rc; } @@ -157949,8 +191091,8 @@ static int openDatabase( ** Open a new database handle. */ SQLITE_API int sqlite3_open( - const char *zFilename, - sqlite3 **ppDb + const char *zFilename, + sqlite3 **ppDb ){ return openDatabase(zFilename, ppDb, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, 0); @@ -157969,7 +191111,7 @@ SQLITE_API int sqlite3_open_v2( ** Open a new database handle. */ SQLITE_API int sqlite3_open16( - const void *zFilename, + const void *zFilename, sqlite3 **ppDb ){ char const *zFilename8; /* zFilename encoded in UTF-8 instead of UTF-16 */ @@ -158008,9 +191150,9 @@ SQLITE_API int sqlite3_open16( ** Register a new collation sequence with the database handle db. */ SQLITE_API int sqlite3_create_collation( - sqlite3* db, - const char *zName, - int enc, + sqlite3* db, + const char *zName, + int enc, void* pCtx, int(*xCompare)(void*,int,const void*,int,const void*) ){ @@ -158021,9 +191163,9 @@ SQLITE_API int sqlite3_create_collation( ** Register a new collation sequence with the database handle db. */ SQLITE_API int sqlite3_create_collation_v2( - sqlite3* db, - const char *zName, - int enc, + sqlite3* db, + const char *zName, + int enc, void* pCtx, int(*xCompare)(void*,int,const void*,int,const void*), void(*xDel)(void*) @@ -158046,9 +191188,9 @@ SQLITE_API int sqlite3_create_collation_v2( ** Register a new collation sequence with the database handle db. */ SQLITE_API int sqlite3_create_collation16( - sqlite3* db, + sqlite3* db, const void *zName, - int enc, + int enc, void* pCtx, int(*xCompare)(void*,int,const void*,int,const void*) ){ @@ -158076,8 +191218,8 @@ SQLITE_API int sqlite3_create_collation16( ** db. Replace any previously installed collation sequence factory. */ SQLITE_API int sqlite3_collation_needed( - sqlite3 *db, - void *pCollNeededArg, + sqlite3 *db, + void *pCollNeededArg, void(*xCollNeeded)(void*,sqlite3*,int eTextRep,const char*) ){ #ifdef SQLITE_ENABLE_API_ARMOR @@ -158097,8 +191239,8 @@ SQLITE_API int sqlite3_collation_needed( ** db. Replace any previously installed collation sequence factory. */ SQLITE_API int sqlite3_collation_needed16( - sqlite3 *db, - void *pCollNeededArg, + sqlite3 *db, + void *pCollNeededArg, void(*xCollNeeded16)(void*,sqlite3*,int eTextRep,const void*) ){ #ifdef SQLITE_ENABLE_API_ARMOR @@ -158113,6 +191255,75 @@ SQLITE_API int sqlite3_collation_needed16( } #endif /* SQLITE_OMIT_UTF16 */ +/* +** Find existing client data. +*/ +SQLITE_API void *sqlite3_get_clientdata(sqlite3 *db, const char *zName){ + DbClientData *p; +#ifdef SQLITE_ENABLE_API_ARMOR + if( !zName || !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif + sqlite3_mutex_enter(db->mutex); + for(p=db->pDbData; p; p=p->pNext){ + if( strcmp(p->zName, zName)==0 ){ + void *pResult = p->pData; + sqlite3_mutex_leave(db->mutex); + return pResult; + } + } + sqlite3_mutex_leave(db->mutex); + return 0; +} + +/* +** Add new client data to a database connection. +*/ +SQLITE_API int sqlite3_set_clientdata( + sqlite3 *db, /* Attach client data to this connection */ + const char *zName, /* Name of the client data */ + void *pData, /* The client data itself */ + void (*xDestructor)(void*) /* Destructor */ +){ + DbClientData *p, **pp; + sqlite3_mutex_enter(db->mutex); + pp = &db->pDbData; + for(p=db->pDbData; p && strcmp(p->zName,zName); p=p->pNext){ + pp = &p->pNext; + } + if( p ){ + assert( p->pData!=0 ); + if( p->xDestructor ) p->xDestructor(p->pData); + if( pData==0 ){ + *pp = p->pNext; + sqlite3_free(p); + sqlite3_mutex_leave(db->mutex); + return SQLITE_OK; + } + }else if( pData==0 ){ + sqlite3_mutex_leave(db->mutex); + return SQLITE_OK; + }else{ + size_t n = strlen(zName); + p = sqlite3_malloc64( SZ_DBCLIENTDATA(n+1) ); + if( p==0 ){ + if( xDestructor ) xDestructor(pData); + sqlite3_mutex_leave(db->mutex); + return SQLITE_NOMEM; + } + memcpy(p->zName, zName, n+1); + p->pNext = db->pDbData; + db->pDbData = p; + } + p->pData = pData; + p->xDestructor = xDestructor; + sqlite3_mutex_leave(db->mutex); + return SQLITE_OK; +} + + #ifndef SQLITE_OMIT_DEPRECATED /* ** This function is now an anachronism. It used to be used to recover from a @@ -158130,13 +191341,17 @@ SQLITE_API int sqlite3_global_recover(void){ ** by the next COMMIT or ROLLBACK. */ SQLITE_API int sqlite3_get_autocommit(sqlite3 *db){ + int iRet; #ifdef SQLITE_ENABLE_API_ARMOR if( !sqlite3SafetyCheckOk(db) ){ (void)SQLITE_MISUSE_BKPT; return 0; } #endif - return db->autoCommit; + sqlite3_mutex_enter(db->mutex); + iRet = db->autoCommit; + sqlite3_mutex_leave(db->mutex); + return iRet; } /* @@ -158167,13 +191382,15 @@ SQLITE_PRIVATE int sqlite3CantopenError(int lineno){ testcase( sqlite3GlobalConfig.xLog!=0 ); return sqlite3ReportError(SQLITE_CANTOPEN, lineno, "cannot open file"); } -#ifdef SQLITE_DEBUG +#if defined(SQLITE_DEBUG) || defined(SQLITE_ENABLE_CORRUPT_PGNO) SQLITE_PRIVATE int sqlite3CorruptPgnoError(int lineno, Pgno pgno){ char zMsg[100]; sqlite3_snprintf(sizeof(zMsg), zMsg, "database corruption page %d", pgno); testcase( sqlite3GlobalConfig.xLog!=0 ); return sqlite3ReportError(SQLITE_CORRUPT, lineno, zMsg); } +#endif +#ifdef SQLITE_DEBUG SQLITE_PRIVATE int sqlite3NomemError(int lineno){ testcase( sqlite3GlobalConfig.xLog!=0 ); return sqlite3ReportError(SQLITE_NOMEM, lineno, "OOM"); @@ -158239,22 +191456,19 @@ SQLITE_API int sqlite3_table_column_metadata( /* Locate the table in question */ pTab = sqlite3FindTable(db, zTableName, zDbName); - if( !pTab || pTab->pSelect ){ + if( !pTab || IsView(pTab) ){ pTab = 0; goto error_out; } /* Find the column for which info is requested */ if( zColumnName==0 ){ - /* Query for existance of table only */ + /* Query for existence of table only */ }else{ - for(iCol=0; iColnCol; iCol++){ + iCol = sqlite3ColumnIndex(pTab, zColumnName); + if( iCol>=0 ){ pCol = &pTab->aCol[iCol]; - if( 0==sqlite3StrICmp(pCol->zName, zColumnName) ){ - break; - } - } - if( iCol==pTab->nCol ){ + }else{ if( HasRowid(pTab) && sqlite3IsRowid(zColumnName) ){ iCol = pTab->iPKey; pCol = iCol>=0 ? &pTab->aCol[iCol] : 0; @@ -158268,16 +191482,16 @@ SQLITE_API int sqlite3_table_column_metadata( /* The following block stores the meta information that will be returned ** to the caller in local variables zDataType, zCollSeq, notnull, primarykey ** and autoinc. At this point there are two possibilities: - ** - ** 1. The specified column name was rowid", "oid" or "_rowid_" - ** and there is no explicitly declared IPK column. ** - ** 2. The table is not a view and the column name identified an + ** 1. The specified column name was rowid", "oid" or "_rowid_" + ** and there is no explicitly declared IPK column. + ** + ** 2. The table is not a view and the column name identified an ** explicitly declared column. Copy meta information from *pCol. - */ + */ if( pCol ){ zDataType = sqlite3ColumnType(pCol,0); - zCollSeq = pCol->zColl; + zCollSeq = sqlite3ColumnColl(pCol); notnull = pCol->notNull!=0; primarykey = (pCol->colFlags & COLFLAG_PRIMKEY)!=0; autoinc = pTab->iPKey==iCol && (pTab->tabFlags & TF_Autoincrement)!=0; @@ -158324,10 +191538,10 @@ SQLITE_API int sqlite3_sleep(int ms){ pVfs = sqlite3_vfs_find(0); if( pVfs==0 ) return 0; - /* This function works in milliseconds, but the underlying OsSleep() + /* This function works in milliseconds, but the underlying OsSleep() ** API uses microseconds. Hence the 1000's. */ - rc = (sqlite3OsSleep(pVfs, 1000*ms)/1000); + rc = (sqlite3OsSleep(pVfs, ms<0 ? 0 : 1000*ms)/1000); return rc; } @@ -158376,8 +191590,20 @@ SQLITE_API int sqlite3_file_control(sqlite3 *db, const char *zDbName, int op, vo }else if( op==SQLITE_FCNTL_DATA_VERSION ){ *(unsigned int*)pArg = sqlite3PagerDataVersion(pPager); rc = SQLITE_OK; + }else if( op==SQLITE_FCNTL_RESERVE_BYTES ){ + int iNew = *(int*)pArg; + *(int*)pArg = sqlite3BtreeGetRequestedReserve(pBtree); + if( iNew>=0 && iNew<=255 ){ + sqlite3BtreeSetPageSize(pBtree, 0, iNew, 0); + } + rc = SQLITE_OK; + }else if( op==SQLITE_FCNTL_RESET_CACHE ){ + sqlite3BtreeClearCache(pBtree); + rc = SQLITE_OK; }else{ + int nSave = db->busyHandler.nBusy; rc = sqlite3OsFileControl(fd, op, pArg); + db->busyHandler.nBusy = nSave; } sqlite3BtreeLeave(pBtree); } @@ -158415,15 +191641,60 @@ SQLITE_API int sqlite3_test_control(int op, ...){ break; } - /* - ** Reset the PRNG back to its uninitialized state. The next call - ** to sqlite3_randomness() will reseed the PRNG using a single call - ** to the xRandomness method of the default VFS. + /* sqlite3_test_control(SQLITE_TESTCTRL_PRNG_SEED, int x, sqlite3 *db); + ** + ** Control the seed for the pseudo-random number generator (PRNG) that + ** is built into SQLite. Cases: + ** + ** x!=0 && db!=0 Seed the PRNG to the current value of the + ** schema cookie in the main database for db, or + ** x if the schema cookie is zero. This case + ** is convenient to use with database fuzzers + ** as it allows the fuzzer some control over the + ** the PRNG seed. + ** + ** x!=0 && db==0 Seed the PRNG to the value of x. + ** + ** x==0 && db==0 Revert to default behavior of using the + ** xRandomness method on the primary VFS. + ** + ** This test-control also resets the PRNG so that the new seed will + ** be used for the next call to sqlite3_randomness(). */ - case SQLITE_TESTCTRL_PRNG_RESET: { +#ifndef SQLITE_OMIT_WSD + case SQLITE_TESTCTRL_PRNG_SEED: { + int x = va_arg(ap, int); + int y; + sqlite3 *db = va_arg(ap, sqlite3*); + assert( db==0 || db->aDb[0].pSchema!=0 ); + if( db && (y = db->aDb[0].pSchema->schema_cookie)!=0 ){ x = y; } + sqlite3Config.iPrngSeed = x; sqlite3_randomness(0,0); break; } +#endif + + /* sqlite3_test_control(SQLITE_TESTCTRL_FK_NO_ACTION, sqlite3 *db, int b); + ** + ** If b is true, then activate the SQLITE_FkNoAction setting. If b is + ** false then clear that setting. If the SQLITE_FkNoAction setting is + ** enabled, all foreign key ON DELETE and ON UPDATE actions behave as if + ** they were NO ACTION, regardless of how they are defined. + ** + ** NB: One must usually run "PRAGMA writable_schema=RESET" after + ** using this test-control, before it will take full effect. failing + ** to reset the schema can result in some unexpected behavior. + */ + case SQLITE_TESTCTRL_FK_NO_ACTION: { + sqlite3 *db = va_arg(ap, sqlite3*); + int b = va_arg(ap, int); + if( b ){ + db->flags |= SQLITE_FkNoAction; + }else{ + db->flags &= ~SQLITE_FkNoAction; + } + break; + } /* ** sqlite3_test_control(BITVEC_TEST, size, program) @@ -158452,12 +191723,16 @@ SQLITE_API int sqlite3_test_control(int op, ...){ ** sqlite3_test_control(). */ case SQLITE_TESTCTRL_FAULT_INSTALL: { - /* MSVC is picky about pulling func ptrs from va lists. - ** http://support.microsoft.com/kb/47961 + /* A bug in MSVC prevents it from understanding pointers to functions + ** types in the second argument to va_arg(). Work around the problem + ** using a typedef. + ** http://support.microsoft.com/kb/47961 <-- dead hyperlink + ** Search at http://web.archive.org/ to find the 2015-03-16 archive + ** of the link above to see the original text. ** sqlite3GlobalConfig.xTestCallback = va_arg(ap, int(*)(int)); */ - typedef int(*TESTCALLBACKFUNC_t)(int); - sqlite3GlobalConfig.xTestCallback = va_arg(ap, TESTCALLBACKFUNC_t); + typedef int(*sqlite3FaultFuncType)(int); + sqlite3GlobalConfig.xTestCallback = va_arg(ap, sqlite3FaultFuncType); rc = sqlite3FaultSim(0); break; } @@ -158465,7 +191740,7 @@ SQLITE_API int sqlite3_test_control(int op, ...){ /* ** sqlite3_test_control(BENIGN_MALLOC_HOOKS, xBegin, xEnd) ** - ** Register hooks to call to indicate which malloc() failures + ** Register hooks to call to indicate which malloc() failures ** are benign. */ case SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS: { @@ -158516,6 +191791,29 @@ SQLITE_API int sqlite3_test_control(int op, ...){ volatile int x = 0; assert( /*side-effects-ok*/ (x = va_arg(ap,int))!=0 ); rc = x; +#if defined(SQLITE_DEBUG) + /* Invoke these debugging routines so that the compiler does not + ** issue "defined but not used" warnings. */ + if( x==9999 ){ + sqlite3ShowExpr(0); + sqlite3ShowExprList(0); + sqlite3ShowIdList(0); + sqlite3ShowSrcList(0); + sqlite3ShowWith(0); + sqlite3ShowUpsert(0); +#ifndef SQLITE_OMIT_TRIGGER + sqlite3ShowTriggerStep(0); + sqlite3ShowTriggerStepList(0); + sqlite3ShowTrigger(0); + sqlite3ShowTriggerList(0); +#endif +#ifndef SQLITE_OMIT_WINDOWFUNC + sqlite3ShowWindow(0); + sqlite3ShowWinFunc(0); +#endif + sqlite3ShowSelect(0); + } +#endif break; } @@ -158563,29 +191861,15 @@ SQLITE_API int sqlite3_test_control(int op, ...){ ** 10 little-endian, determined at run-time ** 432101 big-endian, determined at compile-time ** 123410 little-endian, determined at compile-time - */ + */ case SQLITE_TESTCTRL_BYTEORDER: { rc = SQLITE_BYTEORDER*100 + SQLITE_LITTLEENDIAN*10 + SQLITE_BIGENDIAN; break; } - /* sqlite3_test_control(SQLITE_TESTCTRL_RESERVE, sqlite3 *db, int N) - ** - ** Set the nReserve size to N for the main database on the database - ** connection db. - */ - case SQLITE_TESTCTRL_RESERVE: { - sqlite3 *db = va_arg(ap, sqlite3*); - int x = va_arg(ap,int); - sqlite3_mutex_enter(db->mutex); - sqlite3BtreeSetPageSize(db->aDb[0].pBt, 0, x, 0); - sqlite3_mutex_leave(db->mutex); - break; - } - /* sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS, sqlite3 *db, int N) ** - ** Enable or disable various optimizations for testing purposes. The + ** Enable or disable various optimizations for testing purposes. The ** argument N is a bitmask of optimizations to be disabled. For normal ** operation N should be 0. The idea is that a test program (like the ** SQL Logic Test or SLT test module) can run the same SQL multiple times @@ -158594,29 +191878,54 @@ SQLITE_API int sqlite3_test_control(int op, ...){ */ case SQLITE_TESTCTRL_OPTIMIZATIONS: { sqlite3 *db = va_arg(ap, sqlite3*); - db->dbOptFlags = (u16)(va_arg(ap, int) & 0xffff); + db->dbOptFlags = va_arg(ap, u32); + break; + } + + /* sqlite3_test_control(SQLITE_TESTCTRL_GETOPT, sqlite3 *db, int *N) + ** + ** Write the current optimization settings into *N. A zero bit means that + ** the optimization is on, and a 1 bit means that the optimization is off. + */ + case SQLITE_TESTCTRL_GETOPT: { + sqlite3 *db = va_arg(ap, sqlite3*); + int *pN = va_arg(ap, int*); + *pN = db->dbOptFlags; break; } - /* sqlite3_test_control(SQLITE_TESTCTRL_LOCALTIME_FAULT, int onoff); + /* sqlite3_test_control(SQLITE_TESTCTRL_LOCALTIME_FAULT, onoff, xAlt); + ** + ** If parameter onoff is 1, subsequent calls to localtime() fail. + ** If 2, then invoke xAlt() instead of localtime(). If 0, normal + ** processing. + ** + ** xAlt arguments are void pointers, but they really want to be: ** - ** If parameter onoff is non-zero, subsequent calls to localtime() - ** and its variants fail. If onoff is zero, undo this setting. + ** int xAlt(const time_t*, struct tm*); + ** + ** xAlt should write results in to struct tm object of its 2nd argument + ** and return zero on success, or return non-zero on failure. */ case SQLITE_TESTCTRL_LOCALTIME_FAULT: { sqlite3GlobalConfig.bLocaltimeFault = va_arg(ap, int); + if( sqlite3GlobalConfig.bLocaltimeFault==2 ){ + typedef int(*sqlite3LocaltimeType)(const void*,void*); + sqlite3GlobalConfig.xAltLocaltime = va_arg(ap, sqlite3LocaltimeType); + }else{ + sqlite3GlobalConfig.xAltLocaltime = 0; + } break; } - /* sqlite3_test_control(SQLITE_TESTCTRL_INTERNAL_FUNCS, int onoff); + /* sqlite3_test_control(SQLITE_TESTCTRL_INTERNAL_FUNCTIONS, sqlite3*); ** - ** If parameter onoff is non-zero, internal-use-only SQL functions - ** are visible to ordinary SQL. This is useful for testing but is - ** unsafe because invalid parameters to those internal-use-only functions - ** can result in crashes or segfaults. + ** Toggle the ability to use internal functions on or off for + ** the database connection given in the argument. */ case SQLITE_TESTCTRL_INTERNAL_FUNCTIONS: { - sqlite3GlobalConfig.bInternalFunctions = va_arg(ap, int); + sqlite3 *db = va_arg(ap, sqlite3*); + db->mDbFlags ^= DBFLAG_InternalFunc; break; } @@ -158626,13 +191935,30 @@ SQLITE_API int sqlite3_test_control(int op, ...){ ** formed and never corrupt. This flag is clear by default, indicating that ** database files might have arbitrary corruption. Setting the flag during ** testing causes certain assert() statements in the code to be activated - ** that demonstrat invariants on well-formed database files. + ** that demonstrate invariants on well-formed database files. */ case SQLITE_TESTCTRL_NEVER_CORRUPT: { sqlite3GlobalConfig.neverCorrupt = va_arg(ap, int); break; } + /* sqlite3_test_control(SQLITE_TESTCTRL_EXTRA_SCHEMA_CHECKS, int); + ** + ** Set or clear a flag that causes SQLite to verify that type, name, + ** and tbl_name fields of the sqlite_schema table. This is normally + ** on, but it is sometimes useful to turn it off for testing. + ** + ** 2020-07-22: Disabling EXTRA_SCHEMA_CHECKS also disables the + ** verification of rootpage numbers when parsing the schema. This + ** is useful to make it easier to reach strange internal error states + ** during testing. The EXTRA_SCHEMA_CHECKS setting is always enabled + ** in production. + */ + case SQLITE_TESTCTRL_EXTRA_SCHEMA_CHECKS: { + sqlite3GlobalConfig.bExtraSchemaChecks = va_arg(ap, int); + break; + } + /* Set the threshold at which OP_Once counters reset back to zero. ** By default this is 0x7ffffffe (over 2 billion), but that value is ** too big to test in a reasonable amount of time, so this control is @@ -158645,7 +191971,7 @@ SQLITE_API int sqlite3_test_control(int op, ...){ /* sqlite3_test_control(SQLITE_TESTCTRL_VDBE_COVERAGE, xCallback, ptr); ** - ** Set the VDBE coverage callback function to xCallback with context + ** Set the VDBE coverage callback function to xCallback with context ** pointer ptr. */ case SQLITE_TESTCTRL_VDBE_COVERAGE: { @@ -158675,13 +192001,15 @@ SQLITE_API int sqlite3_test_control(int op, ...){ break; } - /* sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, dbName, onOff, tnum); + /* sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, dbName, mode, tnum); ** ** This test control is used to create imposter tables. "db" is a pointer ** to the database connection. dbName is the database name (ex: "main" or - ** "temp") which will receive the imposter. "onOff" turns imposter mode on - ** or off. "tnum" is the root page of the b-tree to which the imposter - ** table should connect. + ** "temp") which will receive the imposter. "mode" turns imposter mode on + ** or off. mode==0 means imposter mode is off. mode==1 means imposter mode + ** is on. mode==2 means imposter mode is on but results in an imposter + ** table that is read-only unless writable_schema is on. "tnum" is the + ** root page of the b-tree to which the imposter table should connect. ** ** Enable imposter mode only when the schema has already been parsed. Then ** run a single CREATE TABLE statement to construct the imposter table in @@ -158693,12 +192021,16 @@ SQLITE_API int sqlite3_test_control(int op, ...){ */ case SQLITE_TESTCTRL_IMPOSTER: { sqlite3 *db = va_arg(ap, sqlite3*); + int iDb; sqlite3_mutex_enter(db->mutex); - db->init.iDb = sqlite3FindDbName(db, va_arg(ap,const char*)); - db->init.busy = db->init.imposterTable = va_arg(ap,int); - db->init.newTnum = va_arg(ap,int); - if( db->init.busy==0 && db->init.newTnum>0 ){ - sqlite3ResetAllSchemasOfConnection(db); + iDb = sqlite3FindDbName(db, va_arg(ap,const char*)); + if( iDb>=0 ){ + db->init.iDb = iDb; + db->init.busy = db->init.imposterTable = va_arg(ap,int); + db->init.newTnum = va_arg(ap,int); + if( db->init.busy==0 && db->init.newTnum>0 ){ + sqlite3ResetAllSchemasOfConnection(db); + } } sqlite3_mutex_leave(db->mutex); break; @@ -158719,15 +192051,230 @@ SQLITE_API int sqlite3_test_control(int op, ...){ break; } #endif /* defined(YYCOVERAGE) */ + + /* sqlite3_test_control(SQLITE_TESTCTRL_RESULT_INTREAL, sqlite3_context*); + ** + ** This test-control causes the most recent sqlite3_result_int64() value + ** to be interpreted as a MEM_IntReal instead of as an MEM_Int. Normally, + ** MEM_IntReal values only arise during an INSERT operation of integer + ** values into a REAL column, so they can be challenging to test. This + ** test-control enables us to write an intreal() SQL function that can + ** inject an intreal() value at arbitrary places in an SQL statement, + ** for testing purposes. + */ + case SQLITE_TESTCTRL_RESULT_INTREAL: { + sqlite3_context *pCtx = va_arg(ap, sqlite3_context*); + sqlite3ResultIntReal(pCtx); + break; + } + + /* sqlite3_test_control(SQLITE_TESTCTRL_SEEK_COUNT, + ** sqlite3 *db, // Database connection + ** u64 *pnSeek // Write seek count here + ** ); + ** + ** This test-control queries the seek-counter on the "main" database + ** file. The seek-counter is written into *pnSeek and is then reset. + ** The seek-count is only available if compiled with SQLITE_DEBUG. + */ + case SQLITE_TESTCTRL_SEEK_COUNT: { + sqlite3 *db = va_arg(ap, sqlite3*); + u64 *pn = va_arg(ap, sqlite3_uint64*); + *pn = sqlite3BtreeSeekCount(db->aDb->pBt); + (void)db; /* Silence harmless unused variable warning */ + break; + } + + /* sqlite3_test_control(SQLITE_TESTCTRL_TRACEFLAGS, op, ptr) + ** + ** "ptr" is a pointer to a u32. + ** + ** op==0 Store the current sqlite3TreeTrace in *ptr + ** op==1 Set sqlite3TreeTrace to the value *ptr + ** op==2 Store the current sqlite3WhereTrace in *ptr + ** op==3 Set sqlite3WhereTrace to the value *ptr + */ + case SQLITE_TESTCTRL_TRACEFLAGS: { + int opTrace = va_arg(ap, int); + u32 *ptr = va_arg(ap, u32*); + switch( opTrace ){ + case 0: *ptr = sqlite3TreeTrace; break; + case 1: sqlite3TreeTrace = *ptr; break; + case 2: *ptr = sqlite3WhereTrace; break; + case 3: sqlite3WhereTrace = *ptr; break; + } + break; + } + + /* sqlite3_test_control(SQLITE_TESTCTRL_LOGEST, + ** double fIn, // Input value + ** int *pLogEst, // sqlite3LogEstFromDouble(fIn) + ** u64 *pInt, // sqlite3LogEstToInt(*pLogEst) + ** int *pLogEst2 // sqlite3LogEst(*pInt) + ** ); + ** + ** Test access for the LogEst conversion routines. + */ + case SQLITE_TESTCTRL_LOGEST: { + double rIn = va_arg(ap, double); + LogEst rLogEst = sqlite3LogEstFromDouble(rIn); + int *pI1 = va_arg(ap,int*); + u64 *pU64 = va_arg(ap,u64*); + int *pI2 = va_arg(ap,int*); + *pI1 = rLogEst; + *pU64 = sqlite3LogEstToInt(rLogEst); + *pI2 = sqlite3LogEst(*pU64); + break; + } + + /* sqlite3_test_control(SQLITE_TESTCTRL_ATOF, const char *z, double *p); + ** + ** Test access to the sqlite3AtoF() routine. + */ + case SQLITE_TESTCTRL_ATOF: { + const char *z = va_arg(ap,const char*); + double *pR = va_arg(ap,double*); + rc = sqlite3AtoF(z,pR); + break; + } + +#if defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_WSD) + /* sqlite3_test_control(SQLITE_TESTCTRL_TUNE, id, *piValue) + ** + ** If "id" is an integer between 1 and SQLITE_NTUNE then set the value + ** of the id-th tuning parameter to *piValue. If "id" is between -1 + ** and -SQLITE_NTUNE, then write the current value of the (-id)-th + ** tuning parameter into *piValue. + ** + ** Tuning parameters are for use during transient development builds, + ** to help find the best values for constants in the query planner. + ** Access tuning parameters using the Tuning(ID) macro. Set the + ** parameters in the CLI using ".testctrl tune ID VALUE". + ** + ** Transient use only. Tuning parameters should not be used in + ** checked-in code. + */ + case SQLITE_TESTCTRL_TUNE: { + int id = va_arg(ap, int); + int *piValue = va_arg(ap, int*); + if( id>0 && id<=SQLITE_NTUNE ){ + Tuning(id) = *piValue; + }else if( id<0 && id>=-SQLITE_NTUNE ){ + *piValue = Tuning(-id); + }else{ + rc = SQLITE_NOTFOUND; + } + break; + } +#endif + + /* sqlite3_test_control(SQLITE_TESTCTRL_JSON_SELFCHECK, &onOff); + ** + ** Activate or deactivate validation of JSONB that is generated from + ** text. Off by default, as the validation is slow. Validation is + ** only available if compiled using SQLITE_DEBUG. + ** + ** If onOff is initially 1, then turn it on. If onOff is initially + ** off, turn it off. If onOff is initially -1, then change onOff + ** to be the current setting. + */ + case SQLITE_TESTCTRL_JSON_SELFCHECK: { +#if defined(SQLITE_DEBUG) && !defined(SQLITE_OMIT_WSD) + int *pOnOff = va_arg(ap, int*); + if( *pOnOff<0 ){ + *pOnOff = sqlite3Config.bJsonSelfcheck; + }else{ + sqlite3Config.bJsonSelfcheck = (u8)((*pOnOff)&0xff); + } +#endif + break; + } } va_end(ap); #endif /* SQLITE_UNTESTABLE */ return rc; } +/* +** The Pager stores the Database filename, Journal filename, and WAL filename +** consecutively in memory, in that order. The database filename is prefixed +** by four zero bytes. Locate the start of the database filename by searching +** backwards for the first byte following four consecutive zero bytes. +** +** This only works if the filename passed in was obtained from the Pager. +*/ +static const char *databaseName(const char *zName){ + while( zName[-1]!=0 || zName[-2]!=0 || zName[-3]!=0 || zName[-4]!=0 ){ + zName--; + } + return zName; +} + +/* +** Append text z[] to the end of p[]. Return a pointer to the first +** character after then zero terminator on the new text in p[]. +*/ +static char *appendText(char *p, const char *z){ + size_t n = strlen(z); + memcpy(p, z, n+1); + return p+n+1; +} + +/* +** Allocate memory to hold names for a database, journal file, WAL file, +** and query parameters. The pointer returned is valid for use by +** sqlite3_filename_database() and sqlite3_uri_parameter() and related +** functions. +** +** Memory layout must be compatible with that generated by the pager +** and expected by sqlite3_uri_parameter() and databaseName(). +*/ +SQLITE_API const char *sqlite3_create_filename( + const char *zDatabase, + const char *zJournal, + const char *zWal, + int nParam, + const char **azParam +){ + sqlite3_int64 nByte; + int i; + char *pResult, *p; + nByte = strlen(zDatabase) + strlen(zJournal) + strlen(zWal) + 10; + for(i=0; i0 ){ zFilename += sqlite3Strlen30(zFilename) + 1; - if( x==0 ) return zFilename; zFilename += sqlite3Strlen30(zFilename) + 1; } - return 0; + return zFilename[0] ? zFilename : 0; } /* @@ -158773,6 +192328,41 @@ SQLITE_API sqlite3_int64 sqlite3_uri_int64( return bDflt; } +/* +** Translate a filename that was handed to a VFS routine into the corresponding +** database, journal, or WAL file. +** +** It is an error to pass this routine a filename string that was not +** passed into the VFS from the SQLite core. Doing so is similar to +** passing free() a pointer that was not obtained from malloc() - it is +** an error that we cannot easily detect but that will likely cause memory +** corruption. +*/ +SQLITE_API const char *sqlite3_filename_database(const char *zFilename){ + if( zFilename==0 ) return 0; + return databaseName(zFilename); +} +SQLITE_API const char *sqlite3_filename_journal(const char *zFilename){ + if( zFilename==0 ) return 0; + zFilename = databaseName(zFilename); + zFilename += sqlite3Strlen30(zFilename) + 1; + while( ALWAYS(zFilename) && zFilename[0] ){ + zFilename += sqlite3Strlen30(zFilename) + 1; + zFilename += sqlite3Strlen30(zFilename) + 1; + } + return zFilename + 1; +} +SQLITE_API const char *sqlite3_filename_wal(const char *zFilename){ +#ifdef SQLITE_OMIT_WAL + UNUSED_PARAMETER(zFilename); + return 0; +#else + zFilename = sqlite3_filename_journal(zFilename); + if( zFilename ) zFilename += sqlite3Strlen30(zFilename) + 1; + return zFilename; +#endif +} + /* ** Return the Btree pointer identified by zDbName. Return NULL if not found. */ @@ -158781,6 +192371,26 @@ SQLITE_PRIVATE Btree *sqlite3DbNameToBtree(sqlite3 *db, const char *zDbName){ return iDb<0 ? 0 : db->aDb[iDb].pBt; } +/* +** Return the name of the N-th database schema. Return NULL if N is out +** of range. +*/ +SQLITE_API const char *sqlite3_db_name(sqlite3 *db, int N){ + const char *zRet = 0; +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ){ + (void)SQLITE_MISUSE_BKPT; + return 0; + } +#endif + sqlite3_mutex_enter(db->mutex); + if( N>=0 && NnDb ){ + zRet = db->aDb[N].zDbSName; + } + sqlite3_mutex_leave(db->mutex); + return zRet; +} + /* ** Return the filename of the database associated with a database ** connection. @@ -158815,11 +192425,11 @@ SQLITE_API int sqlite3_db_readonly(sqlite3 *db, const char *zDbName){ #ifdef SQLITE_ENABLE_SNAPSHOT /* -** Obtain a snapshot handle for the snapshot of database zDb currently +** Obtain a snapshot handle for the snapshot of database zDb currently ** being read by handle db. */ SQLITE_API int sqlite3_snapshot_get( - sqlite3 *db, + sqlite3 *db, const char *zDb, sqlite3_snapshot **ppSnapshot ){ @@ -158837,8 +192447,12 @@ SQLITE_API int sqlite3_snapshot_get( int iDb = sqlite3FindDbName(db, zDb); if( iDb==0 || iDb>1 ){ Btree *pBt = db->aDb[iDb].pBt; - if( 0==sqlite3BtreeIsInTrans(pBt) ){ + if( SQLITE_TXN_WRITE!=sqlite3BtreeTxnState(pBt) ){ + Pager *pPager = sqlite3BtreePager(pBt); + i64 dummy = 0; + sqlite3PagerSnapshotOpen(pPager, (sqlite3_snapshot*)&dummy); rc = sqlite3BtreeBeginTrans(pBt, 0, 0); + sqlite3PagerSnapshotOpen(pPager, 0); if( rc==SQLITE_OK ){ rc = sqlite3PagerSnapshotGet(sqlite3BtreePager(pBt), ppSnapshot); } @@ -158852,11 +192466,11 @@ SQLITE_API int sqlite3_snapshot_get( } /* -** Open a read-transaction on the snapshot idendified by pSnapshot. +** Open a read-transaction on the snapshot identified by pSnapshot. */ SQLITE_API int sqlite3_snapshot_open( - sqlite3 *db, - const char *zDb, + sqlite3 *db, + const char *zDb, sqlite3_snapshot *pSnapshot ){ int rc = SQLITE_ERROR; @@ -158873,10 +192487,10 @@ SQLITE_API int sqlite3_snapshot_open( iDb = sqlite3FindDbName(db, zDb); if( iDb==0 || iDb>1 ){ Btree *pBt = db->aDb[iDb].pBt; - if( sqlite3BtreeIsInTrans(pBt)==0 ){ + if( sqlite3BtreeTxnState(pBt)!=SQLITE_TXN_WRITE ){ Pager *pPager = sqlite3BtreePager(pBt); int bUnlock = 0; - if( sqlite3BtreeIsInReadTrans(pBt) ){ + if( sqlite3BtreeTxnState(pBt)!=SQLITE_TXN_NONE ){ if( db->nVdbeActive==0 ){ rc = sqlite3PagerSnapshotCheck(pPager, pSnapshot); if( rc==SQLITE_OK ){ @@ -158912,8 +192526,8 @@ SQLITE_API int sqlite3_snapshot_open( */ SQLITE_API int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb){ int rc = SQLITE_ERROR; - int iDb; #ifndef SQLITE_OMIT_WAL + int iDb; #ifdef SQLITE_ENABLE_API_ARMOR if( !sqlite3SafetyCheckOk(db) ){ @@ -158925,7 +192539,7 @@ SQLITE_API int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb){ iDb = sqlite3FindDbName(db, zDb); if( iDb==0 || iDb>1 ){ Btree *pBt = db->aDb[iDb].pBt; - if( 0==sqlite3BtreeIsInReadTrans(pBt) ){ + if( SQLITE_TXN_NONE==sqlite3BtreeTxnState(pBt) ){ rc = sqlite3BtreeBeginTrans(pBt, 0, 0); if( rc==SQLITE_OK ){ rc = sqlite3PagerSnapshotRecover(sqlite3BtreePager(pBt)); @@ -158958,8 +192572,8 @@ SQLITE_API int sqlite3_compileoption_used(const char *zOptName){ int i, n; int nOpt; const char **azCompileOpt; - -#if SQLITE_ENABLE_API_ARMOR + +#ifdef SQLITE_ENABLE_API_ARMOR if( zOptName==0 ){ (void)SQLITE_MISUSE_BKPT; return 0; @@ -158971,7 +192585,7 @@ SQLITE_API int sqlite3_compileoption_used(const char *zOptName){ if( sqlite3StrNICmp(zOptName, "SQLITE_", 7)==0 ) zOptName += 7; n = sqlite3Strlen30(zOptName); - /* Since nOpt is normally in single digits, a linear search is + /* Since nOpt is normally in single digits, a linear search is ** adequate. No need for a binary search. */ for(i=0; ixUnlockNotify!=db->xUnlockNotify; + pp=&sqlite3BlockedList; + *pp && (*pp)->xUnlockNotify!=db->xUnlockNotify; pp=&(*pp)->pNextBlocked ); db->pNextBlocked = *pp; @@ -159110,20 +192724,20 @@ static void addToBlockedList(sqlite3 *db){ } /* -** Obtain the STATIC_MASTER mutex. +** Obtain the STATIC_MAIN mutex. */ static void enterMutex(void){ - sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER)); + sqlite3_mutex_enter(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MAIN)); checkListProperties(0); } /* -** Release the STATIC_MASTER mutex. +** Release the STATIC_MAIN mutex. */ static void leaveMutex(void){ assertMutexHeld(); checkListProperties(0); - sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MASTER)); + sqlite3_mutex_leave(sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_MAIN)); } /* @@ -159154,6 +192768,9 @@ SQLITE_API int sqlite3_unlock_notify( ){ int rc = SQLITE_OK; +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) ) return SQLITE_MISUSE_BKPT; +#endif sqlite3_mutex_enter(db->mutex); enterMutex(); @@ -159164,9 +192781,9 @@ SQLITE_API int sqlite3_unlock_notify( db->xUnlockNotify = 0; db->pUnlockArg = 0; }else if( 0==db->pBlockingConnection ){ - /* The blocking transaction has been concluded. Or there never was a + /* The blocking transaction has been concluded. Or there never was a ** blocking transaction. In either case, invoke the notify callback - ** immediately. + ** immediately. */ xNotify(&pArg, 1); }else{ @@ -159192,7 +192809,7 @@ SQLITE_API int sqlite3_unlock_notify( } /* -** This function is called while stepping or preparing a statement +** This function is called while stepping or preparing a statement ** associated with connection db. The operation will return SQLITE_LOCKED ** to the user because it requires a lock that will not be available ** until connection pBlocker concludes its current transaction. @@ -159208,7 +192825,7 @@ SQLITE_PRIVATE void sqlite3ConnectionBlocked(sqlite3 *db, sqlite3 *pBlocker){ /* ** This function is called when -** the transaction opened by database db has just finished. Locks held +** the transaction opened by database db has just finished. Locks held ** by database connection db have been released. ** ** This function loops through each entry in the blocked connections @@ -159234,7 +192851,7 @@ SQLITE_PRIVATE void sqlite3ConnectionUnlocked(sqlite3 *db){ void *aStatic[16]; /* Starter space for aArg[]. No malloc required */ aArg = aStatic; - enterMutex(); /* Enter STATIC_MASTER mutex */ + enterMutex(); /* Enter STATIC_MAIN mutex */ /* This loop runs once for each entry in the blocked-connections list. */ for(pp=&sqlite3BlockedList; *pp; /* no-op */ ){ @@ -159268,7 +192885,7 @@ SQLITE_PRIVATE void sqlite3ConnectionUnlocked(sqlite3 *db){ }else{ /* This occurs when the array of context pointers that need to ** be passed to the unlock-notify callback is larger than the - ** aStatic[] array allocated on the stack and the attempt to + ** aStatic[] array allocated on the stack and the attempt to ** allocate a larger array from the heap has failed. ** ** This is a difficult situation to handle. Returning an error @@ -159276,17 +192893,17 @@ SQLITE_PRIVATE void sqlite3ConnectionUnlocked(sqlite3 *db){ ** is returned the transaction on connection db will still be ** closed and the unlock-notify callbacks on blocked connections ** will go unissued. This might cause the application to wait - ** indefinitely for an unlock-notify callback that will never + ** indefinitely for an unlock-notify callback that will never ** arrive. ** ** Instead, invoke the unlock-notify callback with the context ** array already accumulated. We can then clear the array and - ** begin accumulating any further context pointers without + ** begin accumulating any further context pointers without ** requiring any dynamic allocation. This is sub-optimal because ** it means that instead of one callback with a large array of ** context pointers the application will receive two or more ** callbacks with smaller arrays of context pointers, which will - ** reduce the applications ability to prioritize multiple + ** reduce the applications ability to prioritize multiple ** connections. But it is the best that can be done under the ** circumstances. */ @@ -159317,11 +192934,11 @@ SQLITE_PRIVATE void sqlite3ConnectionUnlocked(sqlite3 *db){ xUnlockNotify(aArg, nArg); } sqlite3_free(aDyn); - leaveMutex(); /* Leave STATIC_MASTER mutex */ + leaveMutex(); /* Leave STATIC_MAIN mutex */ } /* -** This is called when the database connection passed as an argument is +** This is called when the database connection passed as an argument is ** being closed. The connection is removed from the blocked list. */ SQLITE_PRIVATE void sqlite3ConnectionClosed(sqlite3 *db){ @@ -159398,7 +193015,7 @@ SQLITE_PRIVATE void sqlite3ConnectionClosed(sqlite3 *db){ ** A doclist (document list) holds a docid-sorted list of hits for a ** given term. Doclists hold docids and associated token positions. ** A docid is the unique integer identifier for a single document. -** A position is the index of a word within the document. The first +** A position is the index of a word within the document. The first ** word of the document has a position of 0. ** ** FTS3 used to optionally store character offsets using a compile-time @@ -159423,8 +193040,8 @@ SQLITE_PRIVATE void sqlite3ConnectionClosed(sqlite3 *db){ ** ** Here, array { X } means zero or more occurrences of X, adjacent in ** memory. A "position" is an index of a token in the token stream -** generated by the tokenizer. Note that POS_END and POS_COLUMN occur -** in the same logical place as the position element, and act as sentinals +** generated by the tokenizer. Note that POS_END and POS_COLUMN occur +** in the same logical place as the position element, and act as sentinels ** ending a position list array. POS_END is 0. POS_COLUMN is 1. ** The positions numbers are not stored literally but rather as two more ** than the difference from the prior position, or the just the position plus @@ -159447,7 +193064,7 @@ SQLITE_PRIVATE void sqlite3ConnectionClosed(sqlite3 *db){ ** a document record consists of a docid followed by a position-list and ** a doclist consists of one or more document records. ** -** A bare doclist omits the position information, becoming an +** A bare doclist omits the position information, becoming an ** array of varint-encoded docids. ** **** Segment leaf nodes **** @@ -159643,10 +193260,20 @@ SQLITE_PRIVATE void sqlite3ConnectionClosed(sqlite3 *db){ #ifndef _FTSINT_H #define _FTSINT_H -#if !defined(NDEBUG) && !defined(SQLITE_DEBUG) +/* +** Activate assert() only if SQLITE_TEST is enabled. +*/ +#if !defined(NDEBUG) && !defined(SQLITE_DEBUG) # define NDEBUG 1 #endif +/* #include */ +/* #include */ +/* #include */ +/* #include */ +/* #include */ +/* #include */ + /* FTS3/FTS4 require virtual tables */ #ifdef SQLITE_OMIT_VIRTUALTABLE # undef SQLITE_ENABLE_FTS3 @@ -159666,7 +193293,7 @@ SQLITE_PRIVATE void sqlite3ConnectionClosed(sqlite3 *db){ /* If not building as part of the core, include sqlite3ext.h. */ #ifndef SQLITE_CORE -/* # include "sqlite3ext.h" */ +/* # include "sqlite3ext.h" */ SQLITE_EXTENSION_INIT3 #endif @@ -159710,7 +193337,7 @@ SQLITE_EXTENSION_INIT3 ** When an fts3 table is created, it passes any arguments passed to ** the tokenizer clause of the CREATE VIRTUAL TABLE statement to the ** sqlite3_tokenizer_module.xCreate() function of the requested tokenizer -** implementation. The xCreate() function in turn returns an +** implementation. The xCreate() function in turn returns an ** sqlite3_tokenizer structure representing the specific tokenizer to ** be used for the fts3 table (customized by the tokenizer clause arguments). ** @@ -159742,7 +193369,7 @@ struct sqlite3_tokenizer_module { ** then argc is set to 2, and the argv[] array contains pointers ** to the strings "arg1" and "arg2". ** - ** This method should return either SQLITE_OK (0), or an SQLite error + ** This method should return either SQLITE_OK (0), or an SQLite error ** code. If SQLITE_OK is returned, then *ppTokenizer should be set ** to point at the newly created tokenizer structure. The generic ** sqlite3_tokenizer.pModule variable should not be initialized by @@ -159763,7 +193390,7 @@ struct sqlite3_tokenizer_module { /* ** Create a tokenizer cursor to tokenize an input buffer. The caller ** is responsible for ensuring that the input buffer remains valid - ** until the cursor is closed (using the xClose() method). + ** until the cursor is closed (using the xClose() method). */ int (*xOpen)( sqlite3_tokenizer *pTokenizer, /* Tokenizer object */ @@ -159772,7 +193399,7 @@ struct sqlite3_tokenizer_module { ); /* - ** Destroy an existing tokenizer cursor. The fts3 module calls this + ** Destroy an existing tokenizer cursor. The fts3 module calls this ** method exactly once for each successful call to xOpen(). */ int (*xClose)(sqlite3_tokenizer_cursor *pCursor); @@ -159783,7 +193410,7 @@ struct sqlite3_tokenizer_module { ** "OUT" variables identified below, or SQLITE_DONE to indicate that ** the end of the buffer has been reached, or an SQLite error code. ** - ** *ppToken should be set to point at a buffer containing the + ** *ppToken should be set to point at a buffer containing the ** normalized version of the token (i.e. after any case-folding and/or ** stemming has been performed). *pnBytes should be set to the length ** of this buffer in bytes. The input text that generated the token is @@ -159795,7 +193422,7 @@ struct sqlite3_tokenizer_module { ** ** The buffer *ppToken is set to point at is managed by the tokenizer ** implementation. It is only required to be valid until the next call - ** to xNext() or xClose(). + ** to xNext() or xClose(). */ /* TODO(shess) current implementation requires pInput to be ** nul-terminated. This should either be fixed, or pInput/nBytes @@ -159813,7 +193440,7 @@ struct sqlite3_tokenizer_module { ** Methods below this point are only available if iVersion>=1. */ - /* + /* ** Configure the language id of a tokenizer cursor. */ int (*xLanguageid)(sqlite3_tokenizer_cursor *pCsr, int iLangid); @@ -159882,7 +193509,7 @@ struct Fts3Hash { } *ht; }; -/* Each element in the hash table is an instance of the following +/* Each element in the hash table is an instance of the following ** structure. All elements are stored on a single doubly-linked list. ** ** Again, this structure is intended to be opaque, but it can't really @@ -159901,10 +193528,10 @@ struct Fts3HashElem { ** (including the null-terminator, if any). Case ** is respected in comparisons. ** -** FTS3_HASH_BINARY pKey points to binary data nKey bytes long. +** FTS3_HASH_BINARY pKey points to binary data nKey bytes long. ** memcmp() is used to compare keys. ** -** A copy of the key is made if the copyKey parameter to fts3HashInit is 1. +** A copy of the key is made if the copyKey parameter to fts3HashInit is 1. */ #define FTS3_HASH_STRING 1 #define FTS3_HASH_BINARY 2 @@ -159957,7 +193584,7 @@ SQLITE_PRIVATE Fts3HashElem *sqlite3Fts3HashFindElem(const Fts3Hash *, const voi /* ** This constant determines the maximum depth of an FTS expression tree -** that the library will create and use. FTS uses recursion to perform +** that the library will create and use. FTS uses recursion to perform ** various operations on the query tree, so the disadvantage of a large ** limit is that it may allow very large queries to use large amounts ** of stack space (perhaps causing a stack overflow). @@ -159975,11 +193602,11 @@ SQLITE_PRIVATE Fts3HashElem *sqlite3Fts3HashFindElem(const Fts3Hash *, const voi #define FTS3_MERGE_COUNT 16 /* -** This is the maximum amount of data (in bytes) to store in the +** This is the maximum amount of data (in bytes) to store in the ** Fts3Table.pendingTerms hash table. Normally, the hash table is ** populated as documents are inserted/updated/deleted in a transaction ** and used to create a new segment when the transaction is committed. -** However if this limit is reached midway through a transaction, a new +** However if this limit is reached midway through a transaction, a new ** segment is created and the hash table cleared immediately. */ #define FTS3_MAX_PENDING_DATA (1*1024*1024) @@ -160010,7 +193637,7 @@ SQLITE_PRIVATE Fts3HashElem *sqlite3Fts3HashFindElem(const Fts3Hash *, const voi /* ** FTS4 virtual tables may maintain multiple indexes - one index of all terms ** in the document set and zero or more prefix indexes. All indexes are stored -** as one or more b+-trees in the %_segments and %_segdir tables. +** as one or more b+-trees in the %_segments and %_segdir tables. ** ** It is possible to determine which index a b+-tree belongs to based on the ** value stored in the "%_segdir.level" column. Given this value L, the index @@ -160018,8 +193645,8 @@ SQLITE_PRIVATE Fts3HashElem *sqlite3Fts3HashFindElem(const Fts3Hash *, const voi ** level values between 0 and 1023 (inclusive) belong to index 0, all levels ** between 1024 and 2047 to index 1, and so on. ** -** It is considered impossible for an index to use more than 1024 levels. In -** theory though this may happen, but only after at least +** It is considered impossible for an index to use more than 1024 levels. In +** theory though this may happen, but only after at least ** (FTS3_MERGE_COUNT^1024) separate flushes of the pending-terms tables. */ #define FTS3_SEGDIR_MAXLEVEL 1024 @@ -160037,14 +193664,14 @@ SQLITE_PRIVATE Fts3HashElem *sqlite3Fts3HashFindElem(const Fts3Hash *, const voi ** Terminator values for position-lists and column-lists. */ #define POS_COLUMN (1) /* Column-list terminator */ -#define POS_END (0) /* Position-list terminator */ +#define POS_END (0) /* Position-list terminator */ /* ** The assert_fts3_nc() macro is similar to the assert() macro, except that it -** is used for assert() conditions that are true only if it can be +** is used for assert() conditions that are true only if it can be ** guranteed that the database is not corrupt. */ -#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST) +#ifdef SQLITE_DEBUG SQLITE_API extern int sqlite3_fts3_may_be_corrupt; # define assert_fts3_nc(x) assert(sqlite3_fts3_may_be_corrupt || (x)) #else @@ -160053,7 +193680,7 @@ SQLITE_API extern int sqlite3_fts3_may_be_corrupt; /* ** This section provides definitions to allow the -** FTS3 extension to be compiled outside of the +** FTS3 extension to be compiled outside of the ** amalgamation. */ #ifndef SQLITE_AMALGAMATION @@ -160061,17 +193688,18 @@ SQLITE_API extern int sqlite3_fts3_may_be_corrupt; ** Macros indicating that conditional expressions are always true or ** false. */ -#ifdef SQLITE_COVERAGE_TEST -# define ALWAYS(x) (1) -# define NEVER(X) (0) -#elif defined(SQLITE_DEBUG) -# define ALWAYS(x) sqlite3Fts3Always((x)!=0) -# define NEVER(x) sqlite3Fts3Never((x)!=0) -SQLITE_PRIVATE int sqlite3Fts3Always(int b); -SQLITE_PRIVATE int sqlite3Fts3Never(int b); +#if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_MUTATION_TEST) +# define SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS 1 +#endif +#if defined(SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS) +# define ALWAYS(X) (1) +# define NEVER(X) (0) +#elif !defined(NDEBUG) +# define ALWAYS(X) ((X)?1:(assert(0),0)) +# define NEVER(X) ((X)?(assert(0),1):0) #else -# define ALWAYS(x) (x) -# define NEVER(x) (x) +# define ALWAYS(X) (X) +# define NEVER(X) (X) #endif /* @@ -160088,13 +193716,6 @@ typedef sqlite3_int64 i64; /* 8-byte signed integer */ */ #define UNUSED_PARAMETER(x) (void)(x) -/* -** Activate assert() only if SQLITE_TEST is enabled. -*/ -#if !defined(NDEBUG) && !defined(SQLITE_DEBUG) -# define NDEBUG 1 -#endif - /* ** The TESTONLY macro is used to enclose variable declarations or ** other bits of code that are needed to support the arguments @@ -160106,6 +193727,33 @@ typedef sqlite3_int64 i64; /* 8-byte signed integer */ # define TESTONLY(X) #endif +#define LARGEST_INT64 (0xffffffff|(((i64)0x7fffffff)<<32)) +#define SMALLEST_INT64 (((i64)-1) - LARGEST_INT64) + +#if !defined(deliberate_fall_through) +# if defined(__has_attribute) +# if __has_attribute(fallthrough) +# define deliberate_fall_through __attribute__((fallthrough)); +# endif +# endif +#endif +#if !defined(deliberate_fall_through) +# define deliberate_fall_through +#endif + +/* +** Macros needed to provide flexible arrays in a portable way +*/ +#ifndef offsetof +# define offsetof(ST,M) ((size_t)((char*)&((ST*)0)->M - (char*)0)) +#endif +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) +# define FLEXARRAY +#else +# define FLEXARRAY 1 +#endif + + #endif /* SQLITE_AMALGAMATION */ #ifdef SQLITE_DEBUG @@ -160149,9 +193797,10 @@ struct Fts3Table { char *zLanguageid; /* languageid=xxx option, or NULL */ int nAutoincrmerge; /* Value configured by 'automerge' */ u32 nLeafAdd; /* Number of leaf blocks added this trans */ + int bLock; /* Used to prevent recursive content= tbls */ - /* Precompiled statements used by the implementation. Each of these - ** statements is run and reset within a single virtual table API call. + /* Precompiled statements used by the implementation. Each of these + ** statements is run and reset within a single virtual table API call. */ sqlite3_stmt *aStmt[40]; sqlite3_stmt *pSeekStmt; /* Cache for fts3CursorSeekStmt() */ @@ -160168,9 +193817,10 @@ struct Fts3Table { int nPgsz; /* Page size for host database */ char *zSegmentsTbl; /* Name of %_segments table */ sqlite3_blob *pSegments; /* Blob handle open on %_segments table */ + int iSavepoint; - /* - ** The following array of hash tables is used to buffer pending index + /* + ** The following array of hash tables is used to buffer pending index ** updates during transactions. All pending updates buffered at any one ** time must share a common language-id (see the FTS4 langid= feature). ** The current language id is stored in variable iPrevLangid. @@ -160180,10 +193830,10 @@ struct Fts3Table { ** terms that appear in the document set. Each subsequent index in aIndex[] ** is an index of prefixes of a specific length. ** - ** Variable nPendingData contains an estimate the memory consumed by the + ** Variable nPendingData contains an estimate the memory consumed by the ** pending data structures, including hash table overhead, but not including ** malloc overhead. When nPendingData exceeds nMaxPendingData, all hash - ** tables are flushed to disk. Variable iPrevDocid is the docid of the most + ** tables are flushed to disk. Variable iPrevDocid is the docid of the most ** recently inserted record. */ int nIndex; /* Size of aIndex[] */ @@ -160207,13 +193857,23 @@ struct Fts3Table { int mxSavepoint; /* Largest valid xSavepoint integer */ #endif -#ifdef SQLITE_TEST - /* True to disable the incremental doclist optimization. This is controled +#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST) + /* True to disable the incremental doclist optimization. This is controlled ** by special insert command 'test-no-incr-doclist'. */ int bNoIncrDoclist; + + /* Number of segments in a level */ + int nMergeCount; #endif }; +/* Macro to find the number of segments to merge */ +#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST) +# define MergeCount(P) ((P)->nMergeCount) +#else +# define MergeCount(P) FTS3_MERGE_COUNT +#endif + /* ** When the core wants to read from the virtual table, it creates a ** virtual table cursor (an instance of the following structure) using @@ -160250,16 +193910,16 @@ struct Fts3Cursor { /* ** The Fts3Cursor.eSearch member is always set to one of the following. -** Actualy, Fts3Cursor.eSearch can be greater than or equal to +** Actually, Fts3Cursor.eSearch can be greater than or equal to ** FTS3_FULLTEXT_SEARCH. If so, then Fts3Cursor.eSearch - 2 is the index ** of the column to be searched. For example, in ** ** CREATE VIRTUAL TABLE ex1 USING fts3(a,b,c,d); ** SELECT docid FROM ex1 WHERE b MATCH 'one two three'; -** +** ** Because the LHS of the MATCH operator is 2nd column "b", ** Fts3Cursor.eSearch will be set to FTS3_FULLTEXT_SEARCH+1. (+0 for a, -** +1 for b, +2 for c, +3 for d.) If the LHS of MATCH were "ex1" +** +1 for b, +2 for c, +3 for d.) If the LHS of MATCH were "ex1" ** indicating that all columns should be searched, ** then eSearch would be set to FTS3_FULLTEXT_SEARCH+4. */ @@ -160318,21 +193978,25 @@ struct Fts3Phrase { char *pOrPoslist; i64 iOrDocid; - /* Variables below this point are populated by fts3_expr.c when parsing - ** a MATCH expression. Everything above is part of the evaluation phase. + /* Variables below this point are populated by fts3_expr.c when parsing + ** a MATCH expression. Everything above is part of the evaluation phase. */ int nToken; /* Number of tokens in the phrase */ int iColumn; /* Index of column this phrase must match */ - Fts3PhraseToken aToken[1]; /* One entry for each token in the phrase */ + Fts3PhraseToken aToken[FLEXARRAY]; /* One for each token in the phrase */ }; +/* Size (in bytes) of an Fts3Phrase object large enough to hold N tokens */ +#define SZ_FTS3PHRASE(N) \ + (offsetof(Fts3Phrase,aToken)+(N)*sizeof(Fts3PhraseToken)) + /* ** A tree of these objects forms the RHS of a MATCH operator. ** -** If Fts3Expr.eType is FTSQUERY_PHRASE and isLoaded is true, then aDoclist -** points to a malloced buffer, size nDoclist bytes, containing the results -** of this phrase query in FTS3 doclist format. As usual, the initial -** "Length" field found in doclists stored on disk is omitted from this +** If Fts3Expr.eType is FTSQUERY_PHRASE and isLoaded is true, then aDoclist +** points to a malloced buffer, size nDoclist bytes, containing the results +** of this phrase query in FTS3 doclist format. As usual, the initial +** "Length" field found in doclists stored on disk is omitted from this ** buffer. ** ** Variable aMI is used only for FTSQUERY_NEAR nodes to store the global @@ -160344,7 +194008,7 @@ struct Fts3Phrase { ** aMI[iCol*3 + 1] = Number of occurrences ** aMI[iCol*3 + 2] = Number of rows containing at least one instance ** -** The aMI array is allocated using sqlite3_malloc(). It should be freed +** The aMI array is allocated using sqlite3_malloc(). It should be freed ** when the expression node is. */ struct Fts3Expr { @@ -160368,7 +194032,7 @@ struct Fts3Expr { /* ** Candidate values for Fts3Query.eType. Note that the order of the first -** four values is in order of precedence when parsing expressions. For +** four values is in order of precedence when parsing expressions. For ** example, the following: ** ** "a OR b AND c NOT d NEAR e" @@ -160425,7 +194089,7 @@ SQLITE_PRIVATE int sqlite3Fts3SegReaderStart(Fts3Table*, Fts3MultiSegReader*, Ft SQLITE_PRIVATE int sqlite3Fts3SegReaderStep(Fts3Table *, Fts3MultiSegReader *); SQLITE_PRIVATE void sqlite3Fts3SegReaderFinish(Fts3MultiSegReader *); -SQLITE_PRIVATE int sqlite3Fts3SegReaderCursor(Fts3Table *, +SQLITE_PRIVATE int sqlite3Fts3SegReaderCursor(Fts3Table *, int, int, int, const char *, int, int, int, Fts3MultiSegReader *); /* Flags allowed as part of the 4th argument to SegmentReaderIterate() */ @@ -160451,7 +194115,7 @@ struct Fts3MultiSegReader { int nAdvance; /* How many seg-readers to advance */ Fts3SegFilter *pFilter; /* Pointer to filter object */ char *aBuffer; /* Buffer to merge doclists in */ - int nBuffer; /* Allocated size of aBuffer[] in bytes */ + i64 nBuffer; /* Allocated size of aBuffer[] in bytes */ int iColFilter; /* If >=0, filter for this column */ int bRestart; @@ -160473,10 +194137,21 @@ SQLITE_PRIVATE int sqlite3Fts3Incrmerge(Fts3Table*,int,int); (*(u8*)(p)&0x80) ? sqlite3Fts3GetVarint32(p, piVal) : (*piVal=*(u8*)(p), 1) \ ) +SQLITE_PRIVATE int sqlite3Fts3PrepareStmt( + Fts3Table *p, /* Prepare for this connection */ + const char *zSql, /* SQL to prepare */ + int bPersist, /* True to set SQLITE_PREPARE_PERSISTENT */ + int bAllowVtab, /* True to omit SQLITE_PREPARE_NO_VTAB */ + sqlite3_stmt **pp /* OUT: Prepared statement */ +); + + /* fts3.c */ SQLITE_PRIVATE void sqlite3Fts3ErrMsg(char**,const char*,...); SQLITE_PRIVATE int sqlite3Fts3PutVarint(char *, sqlite3_int64); SQLITE_PRIVATE int sqlite3Fts3GetVarint(const char *, sqlite_int64 *); +SQLITE_PRIVATE int sqlite3Fts3GetVarintU(const char *, sqlite_uint64 *); +SQLITE_PRIVATE int sqlite3Fts3GetVarintBounded(const char*,const char*,sqlite3_int64*); SQLITE_PRIVATE int sqlite3Fts3GetVarint32(const char *, int *); SQLITE_PRIVATE int sqlite3Fts3VarintLen(sqlite3_uint64); SQLITE_PRIVATE void sqlite3Fts3Dequote(char *); @@ -160485,11 +194160,12 @@ SQLITE_PRIVATE int sqlite3Fts3EvalPhraseStats(Fts3Cursor *, Fts3Expr *, u32 *); SQLITE_PRIVATE int sqlite3Fts3FirstFilter(sqlite3_int64, char *, int, char *); SQLITE_PRIVATE void sqlite3Fts3CreateStatTable(int*, Fts3Table*); SQLITE_PRIVATE int sqlite3Fts3EvalTestDeferred(Fts3Cursor *pCsr, int *pRc); +SQLITE_PRIVATE int sqlite3Fts3ReadInt(const char *z, int *pnOut); /* fts3_tokenizer.c */ SQLITE_PRIVATE const char *sqlite3Fts3NextToken(const char *, int *); SQLITE_PRIVATE int sqlite3Fts3InitHashTable(sqlite3 *, Fts3Hash *, const char *); -SQLITE_PRIVATE int sqlite3Fts3InitTokenizer(Fts3Hash *pHash, const char *, +SQLITE_PRIVATE int sqlite3Fts3InitTokenizer(Fts3Hash *pHash, const char *, sqlite3_tokenizer **, char ** ); SQLITE_PRIVATE int sqlite3Fts3IsIdChar(char); @@ -160511,6 +194187,7 @@ SQLITE_PRIVATE void sqlite3Fts3ExprFree(Fts3Expr *); SQLITE_PRIVATE int sqlite3Fts3ExprInitTestInterface(sqlite3 *db, Fts3Hash*); SQLITE_PRIVATE int sqlite3Fts3InitTerm(sqlite3 *db); #endif +SQLITE_PRIVATE void *sqlite3Fts3MallocZero(i64 nByte); SQLITE_PRIVATE int sqlite3Fts3OpenTokenizer(sqlite3_tokenizer *, int, const char *, int, sqlite3_tokenizer_cursor ** @@ -160525,12 +194202,13 @@ SQLITE_PRIVATE int sqlite3Fts3MsrIncrStart( Fts3Table*, Fts3MultiSegReader*, int, const char*, int); SQLITE_PRIVATE int sqlite3Fts3MsrIncrNext( Fts3Table *, Fts3MultiSegReader *, sqlite3_int64 *, char **, int *); -SQLITE_PRIVATE int sqlite3Fts3EvalPhrasePoslist(Fts3Cursor *, Fts3Expr *, int iCol, char **); +SQLITE_PRIVATE int sqlite3Fts3EvalPhrasePoslist(Fts3Cursor *, Fts3Expr *, int iCol, char **); SQLITE_PRIVATE int sqlite3Fts3MsrOvfl(Fts3Cursor *, Fts3MultiSegReader *, int *); SQLITE_PRIVATE int sqlite3Fts3MsrIncrRestart(Fts3MultiSegReader *pCsr); +SQLITE_PRIVATE int sqlite3Fts3MsrCancel(Fts3Cursor*, Fts3Expr*); /* fts3_tokenize_vtab.c */ -SQLITE_PRIVATE int sqlite3Fts3InitTok(sqlite3*, Fts3Hash *); +SQLITE_PRIVATE int sqlite3Fts3InitTok(sqlite3*, Fts3Hash *, void(*xDestroy)(void*)); /* fts3_unicode2.c (functions generated by parsing unicode text files) */ #ifndef SQLITE_DISABLE_FTS3_UNICODE @@ -160539,6 +194217,10 @@ SQLITE_PRIVATE int sqlite3FtsUnicodeIsalnum(int); SQLITE_PRIVATE int sqlite3FtsUnicodeIsdiacritic(int); #endif +SQLITE_PRIVATE int sqlite3Fts3ExprIterate(Fts3Expr*, int (*x)(Fts3Expr*,int,void*), void*); + +SQLITE_PRIVATE int sqlite3Fts3IntegrityCheck(Fts3Table *p, int *pbOk); + #endif /* !SQLITE_CORE || SQLITE_ENABLE_FTS3 */ #endif /* _FTSINT_H */ @@ -160550,40 +194232,41 @@ SQLITE_PRIVATE int sqlite3FtsUnicodeIsdiacritic(int); # define SQLITE_CORE 1 #endif -/* #include */ -/* #include */ -/* #include */ -/* #include */ -/* #include */ -/* #include */ /* #include "fts3.h" */ -#ifndef SQLITE_CORE +#ifndef SQLITE_CORE /* # include "sqlite3ext.h" */ SQLITE_EXTENSION_INIT1 #endif + +/* +** Assume any b-tree layer with more levels than this is corrupt. +*/ +#define FTS3_MAX_BTREE_HEIGHT 48 + +typedef struct Fts3HashWrapper Fts3HashWrapper; +struct Fts3HashWrapper { + Fts3Hash hash; /* Hash table */ + int nRef; /* Number of pointers to this object */ +}; + static int fts3EvalNext(Fts3Cursor *pCsr); static int fts3EvalStart(Fts3Cursor *pCsr); static int fts3TermSegReaderCursor( Fts3Cursor *, const char *, int, int, Fts3MultiSegReader **); -#ifndef SQLITE_AMALGAMATION -# if defined(SQLITE_DEBUG) -SQLITE_PRIVATE int sqlite3Fts3Always(int b) { assert( b ); return b; } -SQLITE_PRIVATE int sqlite3Fts3Never(int b) { assert( !b ); return b; } -# endif -#endif - /* ** This variable is set to false when running tests for which the on disk ** structures should not be corrupt. Otherwise, true. If it is false, extra ** assert() conditions in the fts3 code are activated - conditions that are ** only true if it is guaranteed that the fts3 database is not corrupt. */ +#ifdef SQLITE_DEBUG SQLITE_API int sqlite3_fts3_may_be_corrupt = 1; +#endif -/* +/* ** Write a 64-bit variable-length integer to memory starting at p[0]. ** The length of data written will be between 1 and FTS3_VARINT_MAX bytes. ** The number of bytes written is returned. @@ -160607,12 +194290,7 @@ SQLITE_PRIVATE int sqlite3Fts3PutVarint(char *p, sqlite_int64 v){ v = (*ptr++); \ if( (v & mask2)==0 ){ var = v; return ret; } -/* -** Read a 64-bit variable-length integer from memory starting at p[0]. -** Return the number of bytes read, or 0 on error. -** The value is stored in *v. -*/ -SQLITE_PRIVATE int sqlite3Fts3GetVarint(const char *pBuf, sqlite_int64 *v){ +SQLITE_PRIVATE int sqlite3Fts3GetVarintU(const char *pBuf, sqlite_uint64 *v){ const unsigned char *p = (const unsigned char*)pBuf; const unsigned char *pStart = p; u32 a; @@ -160635,7 +194313,42 @@ SQLITE_PRIVATE int sqlite3Fts3GetVarint(const char *pBuf, sqlite_int64 *v){ } /* -** Similar to sqlite3Fts3GetVarint(), except that the output is truncated to +** Read a 64-bit variable-length integer from memory starting at p[0]. +** Return the number of bytes read, or 0 on error. +** The value is stored in *v. +*/ +SQLITE_PRIVATE int sqlite3Fts3GetVarint(const char *pBuf, sqlite_int64 *v){ + return sqlite3Fts3GetVarintU(pBuf, (sqlite3_uint64*)v); +} + +/* +** Read a 64-bit variable-length integer from memory starting at p[0] and +** not extending past pEnd[-1]. +** Return the number of bytes read, or 0 on error. +** The value is stored in *v. +*/ +SQLITE_PRIVATE int sqlite3Fts3GetVarintBounded( + const char *pBuf, + const char *pEnd, + sqlite_int64 *v +){ + const unsigned char *p = (const unsigned char*)pBuf; + const unsigned char *pStart = p; + const unsigned char *pX = (const unsigned char*)pEnd; + u64 b = 0; + int shift; + for(shift=0; shift<=63; shift+=7){ + u64 c = p=pStart && *p&0x80; p--); @@ -160824,7 +194537,7 @@ static int fts3DestroyMethod(sqlite3_vtab *pVtab){ sqlite3 *db = p->db; /* Database handle */ /* Drop the shadow tables */ - fts3DbExec(&rc, db, + fts3DbExec(&rc, db, "DROP TABLE IF EXISTS %Q.'%q_segments';" "DROP TABLE IF EXISTS %Q.'%q_segdir';" "DROP TABLE IF EXISTS %Q.'%q_docsize';" @@ -160850,7 +194563,7 @@ static int fts3DestroyMethod(sqlite3_vtab *pVtab){ ** passed as the first argument. This is done as part of the xConnect() ** and xCreate() methods. ** -** If *pRc is non-zero when this function is called, it is a no-op. +** If *pRc is non-zero when this function is called, it is a no-op. ** Otherwise, if an error occurs, an SQLite error code is stored in *pRc ** before returning. */ @@ -160864,6 +194577,7 @@ static void fts3DeclareVtab(int *pRc, Fts3Table *p){ zLanguageid = (p->zLanguageid ? p->zLanguageid : "__langid"); sqlite3_vtab_config(p->db, SQLITE_VTAB_CONSTRAINT_SUPPORT, 1); + sqlite3_vtab_config(p->db, SQLITE_VTAB_INNOCUOUS); /* Create a list of user columns for the virtual table */ zCols = sqlite3_mprintf("%Q, ", p->azColumn[0]); @@ -160873,7 +194587,7 @@ static void fts3DeclareVtab(int *pRc, Fts3Table *p){ /* Create the whole "CREATE TABLE" statement to pass to SQLite */ zSql = sqlite3_mprintf( - "CREATE TABLE x(%s %Q HIDDEN, docid HIDDEN, %Q HIDDEN)", + "CREATE TABLE x(%s %Q HIDDEN, docid HIDDEN, %Q HIDDEN)", zCols, p->zName, zLanguageid ); if( !zCols || !zSql ){ @@ -160892,7 +194606,7 @@ static void fts3DeclareVtab(int *pRc, Fts3Table *p){ ** Create the %_stat table if it does not already exist. */ SQLITE_PRIVATE void sqlite3Fts3CreateStatTable(int *pRc, Fts3Table *p){ - fts3DbExec(pRc, p->db, + fts3DbExec(pRc, p->db, "CREATE TABLE IF NOT EXISTS %Q.'%q_stat'" "(id INTEGER PRIMARY KEY, value BLOB);", p->zDb, p->zName @@ -160928,9 +194642,9 @@ static int fts3CreateTables(Fts3Table *p){ zContentCols = sqlite3_mprintf("%z, langid", zContentCols, zLanguageid); } if( zContentCols==0 ) rc = SQLITE_NOMEM; - + /* Create the content table */ - fts3DbExec(&rc, db, + fts3DbExec(&rc, db, "CREATE TABLE %Q.'%q_content'(%s)", p->zDb, p->zName, zContentCols ); @@ -160938,11 +194652,11 @@ static int fts3CreateTables(Fts3Table *p){ } /* Create other tables */ - fts3DbExec(&rc, db, + fts3DbExec(&rc, db, "CREATE TABLE %Q.'%q_segments'(blockid INTEGER PRIMARY KEY, block BLOB);", p->zDb, p->zName ); - fts3DbExec(&rc, db, + fts3DbExec(&rc, db, "CREATE TABLE %Q.'%q_segdir'(" "level INTEGER," "idx INTEGER," @@ -160955,7 +194669,7 @@ static int fts3CreateTables(Fts3Table *p){ p->zDb, p->zName ); if( p->bHasDocsize ){ - fts3DbExec(&rc, db, + fts3DbExec(&rc, db, "CREATE TABLE %Q.'%q_docsize'(docid INTEGER PRIMARY KEY, size BLOB);", p->zDb, p->zName ); @@ -160970,7 +194684,7 @@ static int fts3CreateTables(Fts3Table *p){ /* ** Store the current database page-size in bytes in p->nPgsz. ** -** If *pRc is non-zero when this function is called, it is a no-op. +** If *pRc is non-zero when this function is called, it is a no-op. ** Otherwise, if an error occurs, an SQLite error code is stored in *pRc ** before returning. */ @@ -160979,7 +194693,7 @@ static void fts3DatabasePageSize(int *pRc, Fts3Table *p){ int rc; /* Return code */ char *zSql; /* SQL text "PRAGMA %Q.page_size" */ sqlite3_stmt *pStmt; /* Compiled "PRAGMA %Q.page_size" statement */ - + zSql = sqlite3_mprintf("PRAGMA %Q.page_size", p->zDb); if( !zSql ){ rc = SQLITE_NOMEM; @@ -161005,11 +194719,11 @@ static void fts3DatabasePageSize(int *pRc, Fts3Table *p){ ** ** = ** -** There may not be whitespace surrounding the "=" character. The +** There may not be whitespace surrounding the "=" character. The ** term may be quoted, but the may not. */ static int fts3IsSpecialColumn( - const char *z, + const char *z, int *pnKey, char **pzValue ){ @@ -161086,7 +194800,7 @@ static char *fts3QuoteId(char const *zInput){ } /* -** Return a list of comma separated SQL expressions and a FROM clause that +** Return a list of comma separated SQL expressions and a FROM clause that ** could be used in a SELECT statement such as the following: ** ** SELECT FROM %_content AS x ... @@ -161137,7 +194851,7 @@ static char *fts3ReadExprList(Fts3Table *p, const char *zFunc, int *pRc){ fts3Appendf(pRc, &zRet, ", x.%Q", p->zLanguageid); } } - fts3Appendf(pRc, &zRet, " FROM '%q'.'%q%s' AS x", + fts3Appendf(pRc, &zRet, " FROM '%q'.'%q%s' AS x", p->zDb, (p->zContentTbl ? p->zContentTbl : p->zName), (p->zContentTbl ? "" : "_content") @@ -161152,7 +194866,7 @@ static char *fts3ReadExprList(Fts3Table *p, const char *zFunc, int *pRc){ ** ** If argument zFunc is not NULL, then all but the first question mark ** is preceded by zFunc and an open bracket, and followed by a closed -** bracket. For example, if zFunc is "zip" and the FTS3 table has three +** bracket. For example, if zFunc is "zip" and the FTS3 table has three ** user-defined text columns, the following string is returned: ** ** "?, zip(?), zip(?), zip(?)" @@ -161187,13 +194901,29 @@ static char *fts3WriteExprList(Fts3Table *p, const char *zFunc, int *pRc){ return zRet; } +/* +** Buffer z contains a positive integer value encoded as utf-8 text. +** Decode this value and store it in *pnOut, returning the number of bytes +** consumed. If an overflow error occurs return a negative value. +*/ +SQLITE_PRIVATE int sqlite3Fts3ReadInt(const char *z, int *pnOut){ + u64 iVal = 0; + int i; + for(i=0; z[i]>='0' && z[i]<='9'; i++){ + iVal = iVal*10 + (z[i] - '0'); + if( iVal>0x7FFFFFFF ) return -1; + } + *pnOut = (int)iVal; + return i; +} + /* ** This function interprets the string at (*pp) as a non-negative integer -** value. It reads the integer and sets *pnOut to the value read, then +** value. It reads the integer and sets *pnOut to the value read, then ** sets *pp to point to the byte immediately following the last byte of ** the integer value. ** -** Only decimal digits ('0'..'9') may be part of an integer value. +** Only decimal digits ('0'..'9') may be part of an integer value. ** ** If *pp does not being with a decimal digit SQLITE_ERROR is returned and ** the output value undefined. Otherwise SQLITE_OK is returned. @@ -161202,19 +194932,17 @@ static char *fts3WriteExprList(Fts3Table *p, const char *zFunc, int *pRc){ */ static int fts3GobbleInt(const char **pp, int *pnOut){ const int MAX_NPREFIX = 10000000; - const char *p; /* Iterator pointer */ int nInt = 0; /* Output value */ - - for(p=*pp; p[0]>='0' && p[0]<='9'; p++){ - nInt = nInt * 10 + (p[0] - '0'); - if( nInt>MAX_NPREFIX ){ - nInt = 0; - break; - } + int nByte; + nByte = sqlite3Fts3ReadInt(*pp, &nInt); + if( nInt>MAX_NPREFIX ){ + nInt = 0; + } + if( nByte==0 ){ + return SQLITE_ERROR; } - if( p==*pp ) return SQLITE_ERROR; *pnOut = nInt; - *pp = p; + *pp += nByte; return SQLITE_OK; } @@ -161314,7 +195042,7 @@ static int fts3ContentColumns( char **pzErr /* OUT: error message */ ){ int rc = SQLITE_OK; /* Return code */ - char *zSql; /* "SELECT *" statement on zTbl */ + char *zSql; /* "SELECT *" statement on zTbl */ sqlite3_stmt *pStmt = 0; /* Compiled version of zSql */ zSql = sqlite3_mprintf("SELECT * FROM %Q.%Q", zDb, zTbl); @@ -161388,7 +195116,7 @@ static int fts3InitVtab( sqlite3_vtab **ppVTab, /* Write the resulting vtab structure here */ char **pzErr /* Write any error message here */ ){ - Fts3Hash *pHash = (Fts3Hash *)pAux; + Fts3Hash *pHash = &((Fts3HashWrapper*)pAux)->hash; Fts3Table *p = 0; /* Pointer to allocated vtab */ int rc = SQLITE_OK; /* Return code */ int i; /* Iterator variable */ @@ -161456,9 +195184,9 @@ static int fts3InitVtab( char *zVal; /* Check if this is a tokenizer specification */ - if( !pTokenizer + if( !pTokenizer && strlen(z)>8 - && 0==sqlite3_strnicmp(z, "tokenize", 8) + && 0==sqlite3_strnicmp(z, "tokenize", 8) && 0==sqlite3Fts3IsIdChar(z[8]) ){ rc = sqlite3Fts3InitTokenizer(pHash, &z[9], &pTokenizer, pzErr); @@ -161518,8 +195246,8 @@ static int fts3InitVtab( break; case 4: /* ORDER */ - if( (strlen(zVal)!=3 || sqlite3_strnicmp(zVal, "asc", 3)) - && (strlen(zVal)!=4 || sqlite3_strnicmp(zVal, "desc", 4)) + if( (strlen(zVal)!=3 || sqlite3_strnicmp(zVal, "asc", 3)) + && (strlen(zVal)!=4 || sqlite3_strnicmp(zVal, "desc", 4)) ){ sqlite3Fts3ErrMsg(pzErr, "unrecognized order: %s", zVal); rc = SQLITE_ERROR; @@ -161570,17 +195298,17 @@ static int fts3InitVtab( ** TABLE statement, use all columns from the content table. */ if( rc==SQLITE_OK && zContent ){ - sqlite3_free(zCompress); - sqlite3_free(zUncompress); + sqlite3_free(zCompress); + sqlite3_free(zUncompress); zCompress = 0; zUncompress = 0; if( nCol==0 ){ - sqlite3_free((void*)aCol); + sqlite3_free((void*)aCol); aCol = 0; rc = fts3ContentColumns(db, argv[1], zContent,&aCol,&nCol,&nString,pzErr); /* If a languageid= option was specified, remove the language id - ** column from the aCol[] array. */ + ** column from the aCol[] array. */ if( rc==SQLITE_OK && zLanguageid ){ int j; for(j=0; j0 ){ @@ -161686,7 +195414,7 @@ static int fts3InitVtab( for(i=0; iazColumn[iCol], zNot, n) + && 0==sqlite3_strnicmp(p->azColumn[iCol], zNot, n) ){ p->abNotindexed[iCol] = 1; sqlite3_free(zNot); @@ -161710,7 +195438,7 @@ static int fts3InitVtab( p->zWriteExprlist = fts3WriteExprList(p, zCompress, &rc); if( rc!=SQLITE_OK ) goto fts3_init_out; - /* If this is an xCreate call, create the underlying tables in the + /* If this is an xCreate call, create the underlying tables in the ** database. TODO: For xConnect(), it could verify that said tables exist. */ if( isCreate ){ @@ -161729,6 +195457,10 @@ static int fts3InitVtab( fts3DatabasePageSize(&rc, p); p->nNodeSize = p->nPgsz-35; +#if defined(SQLITE_DEBUG)||defined(SQLITE_TEST) + p->nMergeCount = FTS3_MERGE_COUNT; +#endif + /* Declare the table schema to SQLite. */ fts3DeclareVtab(&rc, p); @@ -161806,11 +195538,11 @@ static void fts3SetUniqueFlag(sqlite3_index_info *pIdxInfo){ #endif } -/* +/* ** Implementation of the xBestIndex method for FTS3 tables. There ** are three possible strategies, in order of preference: ** -** 1. Direct lookup by rowid or docid. +** 1. Direct lookup by rowid or docid. ** 2. Full-text search using a MATCH operator on a non-docid column. ** 3. Linear scan of %_content table. */ @@ -161824,8 +195556,12 @@ static int fts3BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){ int iDocidLe = -1; /* Index of docid<=x constraint, if present */ int iIdx; + if( p->bLock ){ + return SQLITE_ERROR; + } + /* By default use a full table scan. This is an expensive option, - ** so search through the constraints to see if a more efficient + ** so search through the constraints to see if a more efficient ** strategy is possible. */ pInfo->idxNum = FTS3_FULLSCAN_SEARCH; @@ -161861,12 +195597,12 @@ static int fts3BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){ ** ** If there is more than one MATCH constraint available, use the first ** one encountered. If there is both a MATCH constraint and a direct - ** rowid/docid lookup, prefer the MATCH strategy. This is done even + ** rowid/docid lookup, prefer the MATCH strategy. This is done even ** though the rowid/docid lookup is faster than a MATCH query, selecting - ** it would lead to an "unable to use function MATCH in the requested + ** it would lead to an "unable to use function MATCH in the requested ** context" error. */ - if( pCons->op==SQLITE_INDEX_CONSTRAINT_MATCH + if( pCons->op==SQLITE_INDEX_CONSTRAINT_MATCH && pCons->iColumn>=0 && pCons->iColumn<=p->nColumn ){ pInfo->idxNum = FTS3_FULLTEXT_SEARCH + pCons->iColumn; @@ -161875,7 +195611,7 @@ static int fts3BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){ } /* Equality constraint on the langid column */ - if( pCons->op==SQLITE_INDEX_CONSTRAINT_EQ + if( pCons->op==SQLITE_INDEX_CONSTRAINT_EQ && pCons->iColumn==p->nColumn + 2 ){ iLangidCons = i; @@ -161903,22 +195639,22 @@ static int fts3BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){ if( iCons>=0 ){ pInfo->aConstraintUsage[iCons].argvIndex = iIdx++; pInfo->aConstraintUsage[iCons].omit = 1; - } + } if( iLangidCons>=0 ){ pInfo->idxNum |= FTS3_HAVE_LANGID; pInfo->aConstraintUsage[iLangidCons].argvIndex = iIdx++; - } + } if( iDocidGe>=0 ){ pInfo->idxNum |= FTS3_HAVE_DOCID_GE; pInfo->aConstraintUsage[iDocidGe].argvIndex = iIdx++; - } + } if( iDocidLe>=0 ){ pInfo->idxNum |= FTS3_HAVE_DOCID_LE; pInfo->aConstraintUsage[iDocidLe].argvIndex = iIdx++; - } + } /* Regardless of the strategy selected, FTS can deliver rows in rowid (or - ** docid) order. Both ascending and descending are possible. + ** docid) order. Both ascending and descending are possible. */ if( pInfo->nOrderBy==1 ){ struct sqlite3_index_orderby *pOrder = &pInfo->aOrderBy[0]; @@ -161945,7 +195681,7 @@ static int fts3OpenMethod(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCsr){ UNUSED_PARAMETER(pVTab); /* Allocate a buffer large enough for an Fts3Cursor structure. If the - ** allocation succeeds, zero it and return SQLITE_OK. Otherwise, + ** allocation succeeds, zero it and return SQLITE_OK. Otherwise, ** if the allocation fails, return SQLITE_NOMEM. */ *ppCsr = pCsr = (sqlite3_vtab_cursor *)sqlite3_malloc(sizeof(Fts3Cursor)); @@ -162022,7 +195758,9 @@ static int fts3CursorSeekStmt(Fts3Cursor *pCsr){ }else{ zSql = sqlite3_mprintf("SELECT %s WHERE rowid = ?", p->zReadExprlist); if( !zSql ) return SQLITE_NOMEM; - rc = sqlite3_prepare_v3(p->db, zSql,-1,SQLITE_PREPARE_PERSISTENT,&pCsr->pStmt,0); + p->bLock++; + rc = sqlite3Fts3PrepareStmt(p, zSql, 1, 1, &pCsr->pStmt); + p->bLock--; sqlite3_free(zSql); } if( rc==SQLITE_OK ) pCsr->bSeekStmt = 1; @@ -162033,18 +195771,22 @@ static int fts3CursorSeekStmt(Fts3Cursor *pCsr){ /* ** Position the pCsr->pStmt statement so that it is on the row ** of the %_content table that contains the last match. Return -** SQLITE_OK on success. +** SQLITE_OK on success. */ static int fts3CursorSeek(sqlite3_context *pContext, Fts3Cursor *pCsr){ int rc = SQLITE_OK; if( pCsr->isRequireSeek ){ rc = fts3CursorSeekStmt(pCsr); if( rc==SQLITE_OK ){ + Fts3Table *pTab = (Fts3Table*)pCsr->base.pVtab; + pTab->bLock++; sqlite3_bind_int64(pCsr->pStmt, 1, pCsr->iPrevId); pCsr->isRequireSeek = 0; if( SQLITE_ROW==sqlite3_step(pCsr->pStmt) ){ + pTab->bLock--; return SQLITE_OK; }else{ + pTab->bLock--; rc = sqlite3_reset(pCsr->pStmt); if( rc==SQLITE_OK && ((Fts3Table *)pCsr->base.pVtab)->zContentTbl==0 ){ /* If no row was found and no error has occurred, then the %_content @@ -162065,7 +195807,7 @@ static int fts3CursorSeek(sqlite3_context *pContext, Fts3Cursor *pCsr){ /* ** This function is used to process a single interior node when searching -** a b-tree for a term or term prefix. The node data is passed to this +** a b-tree for a term or term prefix. The node data is passed to this ** function via the zNode/nNode parameters. The term to search for is ** passed in zTerm/nTerm. ** @@ -162092,11 +195834,12 @@ static int fts3ScanInteriorNode( char *zBuffer = 0; /* Buffer to load terms into */ i64 nAlloc = 0; /* Size of allocated buffer */ int isFirstTerm = 1; /* True when processing first term on page */ - sqlite3_int64 iChild; /* Block id of child node to descend to */ + u64 iChild; /* Block id of child node to descend to */ + int nBuffer = 0; /* Total term size */ - /* Skip over the 'height' varint that occurs at the start of every + /* Skip over the 'height' varint that occurs at the start of every ** interior node. Then load the blockid of the left-child of the b-tree - ** node into variable iChild. + ** node into variable iChild. ** ** Even if the data structure on disk is corrupted, this (reading two ** varints from the buffer) does not risk an overread. If zNode is a @@ -162107,26 +195850,29 @@ static int fts3ScanInteriorNode( ** table, then there are always 20 bytes of zeroed padding following the ** nNode bytes of content (see sqlite3Fts3ReadBlock() for details). */ - zCsr += sqlite3Fts3GetVarint(zCsr, &iChild); - zCsr += sqlite3Fts3GetVarint(zCsr, &iChild); + zCsr += sqlite3Fts3GetVarintU(zCsr, &iChild); + zCsr += sqlite3Fts3GetVarintU(zCsr, &iChild); if( zCsr>zEnd ){ return FTS_CORRUPT_VTAB; } - + while( zCsrnBuffer ){ + rc = FTS_CORRUPT_VTAB; + goto finish_scan; + } } isFirstTerm = 0; zCsr += fts3GetVarint32(zCsr, &nSuffix); - + assert( nPrefix>=0 && nSuffix>=0 ); if( nPrefix>zCsr-zNode || nSuffix>zEnd-zCsr || nSuffix==0 ){ rc = FTS_CORRUPT_VTAB; @@ -162149,8 +195895,8 @@ static int fts3ScanInteriorNode( /* Compare the term we are searching for with the term just loaded from ** the interior node. If the specified term is greater than or equal - ** to the term from the interior node, then all terms on the sub-tree - ** headed by node iChild are smaller than zTerm. No need to search + ** to the term from the interior node, then all terms on the sub-tree + ** headed by node iChild are smaller than zTerm. No need to search ** iChild. ** ** If the interior node term is larger than the specified term, then @@ -162158,20 +195904,20 @@ static int fts3ScanInteriorNode( */ cmp = memcmp(zTerm, zBuffer, (nBuffer>nTerm ? nTerm : nBuffer)); if( piFirst && (cmp<0 || (cmp==0 && nBuffer>nTerm)) ){ - *piFirst = iChild; + *piFirst = (i64)iChild; piFirst = 0; } if( piLast && cmp<0 ){ - *piLast = iChild; + *piLast = (i64)iChild; piLast = 0; } iChild++; }; - if( piFirst ) *piFirst = iChild; - if( piLast ) *piLast = iChild; + if( piFirst ) *piFirst = (i64)iChild; + if( piLast ) *piLast = (i64)iChild; finish_scan: sqlite3_free(zBuffer); @@ -162186,20 +195932,20 @@ static int fts3ScanInteriorNode( ** node for the range of leaf nodes that may contain the specified term ** or terms for which the specified term is a prefix. ** -** If piLeaf is not NULL, then *piLeaf is set to the blockid of the +** If piLeaf is not NULL, then *piLeaf is set to the blockid of the ** left-most leaf node in the tree that may contain the specified term. ** If piLeaf2 is not NULL, then *piLeaf2 is set to the blockid of the ** right-most leaf node that may contain a term for which the specified ** term is a prefix. ** -** It is possible that the range of returned leaf nodes does not contain -** the specified term or any terms for which it is a prefix. However, if the +** It is possible that the range of returned leaf nodes does not contain +** the specified term or any terms for which it is a prefix. However, if the ** segment does contain any such terms, they are stored within the identified ** range. Because this function only inspects interior segment nodes (and ** never loads leaf nodes into memory), it is not possible to be sure. ** ** If an error occurs, an error code other than SQLITE_OK is returned. -*/ +*/ static int fts3SelectLeaf( Fts3Table *p, /* Virtual table handle */ const char *zTerm, /* Term to select leaves for */ @@ -162215,8 +195961,12 @@ static int fts3SelectLeaf( assert( piLeaf || piLeaf2 ); fts3GetVarint32(zNode, &iHeight); - rc = fts3ScanInteriorNode(zTerm, nTerm, zNode, nNode, piLeaf, piLeaf2); - assert( !piLeaf2 || !piLeaf || rc!=SQLITE_OK || (*piLeaf<=*piLeaf2) ); + if( iHeight>FTS3_MAX_BTREE_HEIGHT ){ + rc = FTS_CORRUPT_VTAB; + }else{ + rc = fts3ScanInteriorNode(zTerm, nTerm, zNode, nNode, piLeaf, piLeaf2); + } + assert_fts3_nc( !piLeaf2 || !piLeaf || rc!=SQLITE_OK || (*piLeaf<=*piLeaf2) ); if( rc==SQLITE_OK && iHeight>1 ){ char *zBlob = 0; /* Blob read from %_segments table */ @@ -162236,7 +195986,13 @@ static int fts3SelectLeaf( rc = sqlite3Fts3ReadBlock(p, piLeaf?*piLeaf:*piLeaf2, &zBlob, &nBlob, 0); } if( rc==SQLITE_OK ){ - rc = fts3SelectLeaf(p, zTerm, nTerm, zBlob, nBlob, piLeaf, piLeaf2); + int iNewHeight = 0; + fts3GetVarint32(zBlob, &iNewHeight); + if( iNewHeight>=iHeight ){ + rc = FTS_CORRUPT_VTAB; + }else{ + rc = fts3SelectLeaf(p, zTerm, nTerm, zBlob, nBlob, piLeaf, piLeaf2); + } } sqlite3_free(zBlob); } @@ -162245,7 +196001,7 @@ static int fts3SelectLeaf( } /* -** This function is used to create delta-encoded serialized lists of FTS3 +** This function is used to create delta-encoded serialized lists of FTS3 ** varints. Each call to this function appends a single varint to a list. */ static void fts3PutDeltaVarint( @@ -162253,17 +196009,22 @@ static void fts3PutDeltaVarint( sqlite3_int64 *piPrev, /* IN/OUT: Previous value written to list */ sqlite3_int64 iVal /* Write this value to the list */ ){ - assert( iVal-*piPrev > 0 || (*piPrev==0 && iVal==0) ); - *pp += sqlite3Fts3PutVarint(*pp, iVal-*piPrev); - *piPrev = iVal; + assert_fts3_nc( iVal-*piPrev > 0 || (*piPrev==0 && iVal==0) ); + if( iVal-(*piPrev)>=0 ){ + /* Refuse to write a negative delta integer. This only happens with a + ** corrupt db (see the assert above) and can cause buffer overwrites + ** in some cases. */ + *pp += sqlite3Fts3PutVarint(*pp, iVal-*piPrev); + *piPrev = iVal; + } } /* -** When this function is called, *ppPoslist is assumed to point to the +** When this function is called, *ppPoslist is assumed to point to the ** start of a position-list. After it returns, *ppPoslist points to the ** first byte after the position-list. ** -** A position list is list of positions (delta encoded) and columns for +** A position list is list of positions (delta encoded) and columns for ** a single document record of a doclist. So, in other words, this ** routine advances *ppPoslist so that it points to the next docid in ** the doclist, or to the first byte past the end of the doclist. @@ -162276,12 +196037,12 @@ static void fts3PoslistCopy(char **pp, char **ppPoslist){ char *pEnd = *ppPoslist; char c = 0; - /* The end of a position list is marked by a zero encoded as an FTS3 + /* The end of a position list is marked by a zero encoded as an FTS3 ** varint. A single POS_END (0) byte. Except, if the 0 byte is preceded by ** a byte with the 0x80 bit set, then it is not a varint 0, but the tail ** of some other, multi-byte, value. ** - ** The following while-loop moves pEnd to point to the first byte that is not + ** The following while-loop moves pEnd to point to the first byte that is not ** immediately preceded by a byte with the 0x80 bit set. Then increments ** pEnd once more so that it points to the byte immediately following the ** last byte in the position-list. @@ -162303,7 +196064,7 @@ static void fts3PoslistCopy(char **pp, char **ppPoslist){ } /* -** When this function is called, *ppPoslist is assumed to point to the +** When this function is called, *ppPoslist is assumed to point to the ** start of a column-list. After it returns, *ppPoslist points to the ** to the terminator (POS_COLUMN or POS_END) byte of the column-list. ** @@ -162341,10 +196102,11 @@ static void fts3ColumnlistCopy(char **pp, char **ppPoslist){ } /* -** Value used to signify the end of an position-list. This is safe because -** it is not possible to have a document with 2^31 terms. +** Value used to signify the end of an position-list. This must be +** as large or larger than any value that might appear on the +** position-list, even a position list that has been corrupted. */ -#define POSITION_LIST_END 0x7fffffff +#define POSITION_LIST_END LARGEST_INT64 /* ** This function is used to help parse position-lists. When this function is @@ -162353,7 +196115,7 @@ static void fts3ColumnlistCopy(char **pp, char **ppPoslist){ ** (in which case **pp will be a terminator bytes POS_END (0) or ** (1)). ** -** If *pp points past the end of the current position-list, set *pi to +** If *pp points past the end of the current position-list, set *pi to ** POSITION_LIST_END and return. Otherwise, read the next varint from *pp, ** increment the current value of *pi by the value read, and set *pp to ** point to the next value before returning. @@ -162369,7 +196131,9 @@ static void fts3ReadNextPos( sqlite3_int64 *pi /* IN/OUT: Value read from position-list */ ){ if( (**pp)&0xFE ){ - fts3GetDeltaVarint(pp, pi); + int iVal; + *pp += fts3GetVarint32((*pp), &iVal); + *pi += iVal; *pi -= 2; }else{ *pi = POSITION_LIST_END; @@ -162381,7 +196145,7 @@ static void fts3ReadNextPos( ** the value of iCol encoded as a varint to *pp. This will start a new ** column list. ** -** Set *pp to point to the byte just after the last byte written before +** Set *pp to point to the byte just after the last byte written before ** returning (do not modify it if iCol==0). Return the total number of bytes ** written (0 if iCol==0). */ @@ -162416,18 +196180,18 @@ static int fts3PoslistMerge( int iCol1; /* The current column index in pp1 */ int iCol2; /* The current column index in pp2 */ - if( *p1==POS_COLUMN ){ + if( *p1==POS_COLUMN ){ fts3GetVarint32(&p1[1], &iCol1); if( iCol1==0 ) return FTS_CORRUPT_VTAB; } - else if( *p1==POS_END ) iCol1 = POSITION_LIST_END; + else if( *p1==POS_END ) iCol1 = 0x7fffffff; else iCol1 = 0; if( *p2==POS_COLUMN ){ fts3GetVarint32(&p2[1], &iCol2); if( iCol2==0 ) return FTS_CORRUPT_VTAB; } - else if( *p2==POS_END ) iCol2 = POSITION_LIST_END; + else if( *p2==POS_END ) iCol2 = 0x7fffffff; else iCol2 = 0; if( iCol1==iCol2 ){ @@ -162440,7 +196204,7 @@ static int fts3PoslistMerge( /* At this point, both p1 and p2 point to the start of column-lists ** for the same column (the column with index iCol1 and iCol2). - ** A column-list is a list of non-negative delta-encoded varints, each + ** A column-list is a list of non-negative delta-encoded varints, each ** incremented by 2 before being stored. Each list is terminated by a ** POS_END (0) or POS_COLUMN (1). The following block merges the two lists ** and writes the results to buffer p. p is left pointing to the byte @@ -162449,8 +196213,11 @@ static int fts3PoslistMerge( */ fts3GetDeltaVarint(&p1, &i1); fts3GetDeltaVarint(&p2, &i2); + if( i1<2 || i2<2 ){ + break; + } do { - fts3PutDeltaVarint(&p, &iPrev, (i1pos(*pp1) && pos(*pp2)-pos(*pp1)<=nToken). i.e. ** when the *pp1 token appears before the *pp2 token, but not more than nToken @@ -162517,14 +196284,19 @@ static int fts3PoslistPhraseMerge( /* Never set both isSaveLeft and isExact for the same invocation. */ assert( isSaveLeft==0 || isExact==0 ); - assert( p!=0 && *p1!=0 && *p2!=0 ); - if( *p1==POS_COLUMN ){ + assert_fts3_nc( p!=0 && *p1!=0 && *p2!=0 ); + if( *p1==POS_COLUMN ){ p1++; p1 += fts3GetVarint32(p1, &iCol1); + /* iCol1==0 indicates corruption. Column 0 does not have a POS_COLUMN + ** entry, so this is actually end-of-doclist. */ + if( iCol1==0 ) return 0; } - if( *p2==POS_COLUMN ){ + if( *p2==POS_COLUMN ){ p2++; p2 += fts3GetVarint32(p2, &iCol2); + /* As above, iCol2==0 indicates corruption. */ + if( iCol2==0 ) return 0; } while( 1 ){ @@ -162544,8 +196316,8 @@ static int fts3PoslistPhraseMerge( if( iPos1<0 || iPos2<0 ) break; while( 1 ){ - if( iPos2==iPos1+nToken - || (isExact==0 && iPos2>iPos1 && iPos2<=iPos1+nToken) + if( iPos2==iPos1+nToken + || (isExact==0 && iPos2>iPos1 && iPos2<=iPos1+nToken) ){ sqlite3_int64 iSave; iSave = isSaveLeft ? iPos1 : iPos2; @@ -162580,8 +196352,8 @@ static int fts3PoslistPhraseMerge( /* Advance pointer p1 or p2 (whichever corresponds to the smaller of ** iCol1 and iCol2) so that it points to either the 0x00 that marks the - ** end of the position list, or the 0x01 that precedes the next - ** column-number in the position list. + ** end of the position list, or the 0x01 that precedes the next + ** column-number in the position list. */ else if( iCol1=pEnd ){ *pp = 0; }else{ - sqlite3_int64 iVal; - *pp += sqlite3Fts3GetVarint(*pp, &iVal); + u64 iVal; + *pp += sqlite3Fts3GetVarintU(*pp, &iVal); if( bDescIdx ){ - *pVal -= iVal; + *pVal = (i64)((u64)*pVal - iVal); }else{ - *pVal += iVal; + *pVal = (i64)((u64)*pVal + iVal); } } } @@ -162706,9 +196478,9 @@ static void fts3GetDeltaVarint3( ** end of the value written. ** ** If *pbFirst is zero when this function is called, the value written to -** the buffer is that of parameter iVal. +** the buffer is that of parameter iVal. ** -** If *pbFirst is non-zero when this function is called, then the value +** If *pbFirst is non-zero when this function is called, then the value ** written is either (iVal-*piPrev) (if bDescIdx is zero) or (*piPrev-iVal) ** (if bDescIdx is non-zero). ** @@ -162722,14 +196494,16 @@ static void fts3PutDeltaVarint3( int *pbFirst, /* IN/OUT: True after first int written */ sqlite3_int64 iVal /* Write this value to the list */ ){ - sqlite3_int64 iWrite; + sqlite3_uint64 iWrite; if( bDescIdx==0 || *pbFirst==0 ){ - iWrite = iVal - *piPrev; + assert_fts3_nc( *pbFirst==0 || iVal>=*piPrev ); + iWrite = (u64)iVal - (u64)*piPrev; }else{ - iWrite = *piPrev - iVal; + assert_fts3_nc( *piPrev>=iVal ); + iWrite = (u64)*piPrev - (u64)iVal; } assert( *pbFirst || *piPrev==0 ); - assert( *pbFirst==0 || iWrite>0 ); + assert_fts3_nc( *pbFirst==0 || iWrite>0 ); *pp += sqlite3Fts3PutVarint(*pp, iWrite); *piPrev = iVal; *pbFirst = 1; @@ -162739,17 +196513,18 @@ static void fts3PutDeltaVarint3( /* ** This macro is used by various functions that merge doclists. The two ** arguments are 64-bit docid values. If the value of the stack variable -** bDescDoclist is 0 when this macro is invoked, then it returns (i1-i2). +** bDescDoclist is 0 when this macro is invoked, then it returns (i1-i2). ** Otherwise, (i2-i1). ** ** Using this makes it easier to write code that can merge doclists that are ** sorted in either ascending or descending order. */ -#define DOCID_CMP(i1, i2) ((bDescDoclist?-1:1) * (i1-i2)) +/* #define DOCID_CMP(i1, i2) ((bDescDoclist?-1:1) * (i64)((u64)i1-i2)) */ +#define DOCID_CMP(i1, i2) ((bDescDoclist?-1:1) * (i1>i2?1:((i1==i2)?0:-1))) /* ** This function does an "OR" merge of two doclists (output contains all -** positions contained in either argument doclist). If the docids in the +** positions contained in either argument doclist). If the docids in the ** input doclists are sorted in ascending order, parameter bDescDoclist ** should be false. If they are sorted in ascending order, it should be ** passed a non-zero value. @@ -162789,12 +196564,12 @@ static int fts3DoclistOrMerge( ** current and previous docid (a positive number - since the list is in ** ascending order). ** - ** The first docid written to the output is therefore encoded using the + ** The first docid written to the output is therefore encoded using the ** same number of bytes as it is in whichever of the input lists it is - ** read from. And each subsequent docid read from the same input list + ** read from. And each subsequent docid read from the same input list ** consumes either the same or less bytes as it did in the input (since ** the difference between it and the previous value in the output must - ** be a positive value less than or equal to the delta value read from + ** be a positive value less than or equal to the delta value read from ** the input list). The same argument applies to all but the first docid ** read from the 'other' list. And to the contents of all position lists ** that will be copied and merged from the input to the output. @@ -162806,9 +196581,9 @@ static int fts3DoclistOrMerge( ** ** The space required to store the output is therefore the sum of the ** sizes of the two inputs, plus enough space for exactly one of the input - ** docids to grow. + ** docids to grow. ** - ** A symetric argument may be made if the doclists are in descending + ** A symmetric argument may be made if the doclists are in descending ** order. */ aOut = sqlite3_malloc64((i64)n1+n2+FTS3_VARINT_MAX-1+FTS3_BUFFER_PADDING); @@ -162835,6 +196610,8 @@ static int fts3DoclistOrMerge( fts3PoslistCopy(&p, &p2); fts3GetDeltaVarint3(&p2, pEnd2, bDescDoclist, &i2); } + + assert( (p-aOut)<=((p1?(p1-a1):n1)+(p2?(p2-a2):n2)+FTS3_VARINT_MAX-1) ); } if( rc!=SQLITE_OK ){ @@ -162856,7 +196633,7 @@ static int fts3DoclistOrMerge( ** exactly nDist tokens before it. ** ** If the docids in the input doclists are sorted in ascending order, -** parameter bDescDoclist should be false. If they are sorted in ascending +** parameter bDescDoclist should be false. If they are sorted in ascending ** order, it should be passed a non-zero value. ** ** The right-hand input doclist is overwritten by this function. @@ -163002,7 +196779,7 @@ static int fts3TermSelectFinishMerge(Fts3Table *p, TermSelect *pTS){ int nNew; char *aNew; - int rc = fts3DoclistOrMerge(p->bDescIdx, + int rc = fts3DoclistOrMerge(p->bDescIdx, pTS->aaOutput[i], pTS->anOutput[i], aOut, nOut, &aNew, &nNew ); if( rc!=SQLITE_OK ){ @@ -163046,22 +196823,22 @@ static int fts3TermSelectMerge( ){ if( pTS->aaOutput[0]==0 ){ /* If this is the first term selected, copy the doclist to the output - ** buffer using memcpy(). + ** buffer using memcpy(). ** - ** Add FTS3_VARINT_MAX bytes of unused space to the end of the + ** Add FTS3_VARINT_MAX bytes of unused space to the end of the ** allocation. This is so as to ensure that the buffer is big enough ** to hold the current doclist AND'd with any other doclist. If the ** doclists are stored in order=ASC order, this padding would not be ** required (since the size of [doclistA AND doclistB] is always less ** than or equal to the size of [doclistA] in that case). But this is - ** not true for order=DESC. For example, a doclist containing (1, -1) + ** not true for order=DESC. For example, a doclist containing (1, -1) ** may be smaller than (-1), as in the first example the -1 may be stored ** as a single-byte delta, whereas in the second it must be stored as a ** FTS3_VARINT_MAX byte varint. ** ** Similar padding is added in the fts3DoclistOrMerge() function. */ - pTS->aaOutput[0] = sqlite3_malloc(nDoclist + FTS3_VARINT_MAX + 1); + pTS->aaOutput[0] = sqlite3_malloc64((i64)nDoclist + FTS3_VARINT_MAX + 1); pTS->anOutput[0] = nDoclist; if( pTS->aaOutput[0] ){ memcpy(pTS->aaOutput[0], aDoclist, nDoclist); @@ -163084,7 +196861,7 @@ static int fts3TermSelectMerge( char *aNew; int nNew; - int rc = fts3DoclistOrMerge(p->bDescIdx, aMerge, nMerge, + int rc = fts3DoclistOrMerge(p->bDescIdx, aMerge, nMerge, pTS->aaOutput[iOut], pTS->anOutput[iOut], &aNew, &nNew ); if( rc!=SQLITE_OK ){ @@ -163095,7 +196872,7 @@ static int fts3TermSelectMerge( if( aMerge!=aDoclist ) sqlite3_free(aMerge); sqlite3_free(pTS->aaOutput[iOut]); pTS->aaOutput[iOut] = 0; - + aMerge = aNew; nMerge = nNew; if( (iOut+1)==SizeofArray(pTS->aaOutput) ){ @@ -163112,7 +196889,7 @@ static int fts3TermSelectMerge( ** Append SegReader object pNew to the end of the pCsr->apSegment[] array. */ static int fts3SegReaderCursorAppend( - Fts3MultiSegReader *pCsr, + Fts3MultiSegReader *pCsr, Fts3SegReader *pNew ){ if( (pCsr->nSegment%16)==0 ){ @@ -163151,13 +196928,13 @@ static int fts3SegReaderCursor( sqlite3_stmt *pStmt = 0; /* Statement to iterate through segments */ int rc2; /* Result of sqlite3_reset() */ - /* If iLevel is less than 0 and this is not a scan, include a seg-reader + /* If iLevel is less than 0 and this is not a scan, include a seg-reader ** for the pending-terms. If this is a scan, then this call must be being ** made by an fts4aux module, not an FTS table. In this case calling - ** Fts3SegReaderPending might segfault, as the data structures used by + ** Fts3SegReaderPending might segfault, as the data structures used by ** fts4aux are not completely populated. So it's easiest to filter these ** calls out here. */ - if( iLevel<0 && p->aIndex ){ + if( iLevel<0 && p->aIndex && p->iPrevLangid==iLangid ){ Fts3SegReader *pSeg = 0; rc = sqlite3Fts3SegReaderPending(p, iIndex, zTerm, nTerm, isPrefix||isScan, &pSeg); if( rc==SQLITE_OK && pSeg ){ @@ -163188,10 +196965,10 @@ static int fts3SegReaderCursor( if( rc!=SQLITE_OK ) goto finished; if( isPrefix==0 && isScan==0 ) iLeavesEndBlock = iStartBlock; } - - rc = sqlite3Fts3SegReaderNew(pCsr->nSegment+1, + + rc = sqlite3Fts3SegReaderNew(pCsr->nSegment+1, (isPrefix==0 && isScan==0), - iStartBlock, iLeavesEndBlock, + iStartBlock, iLeavesEndBlock, iEndBlock, zRoot, nRoot, &pSeg ); if( rc!=SQLITE_OK ) goto finished; @@ -163207,7 +196984,7 @@ static int fts3SegReaderCursor( } /* -** Set up a cursor object for iterating through a full-text index or a +** Set up a cursor object for iterating through a full-text index or a ** single level therein. */ SQLITE_PRIVATE int sqlite3Fts3SegReaderCursor( @@ -163223,7 +197000,7 @@ SQLITE_PRIVATE int sqlite3Fts3SegReaderCursor( ){ assert( iIndex>=0 && iIndexnIndex ); assert( iLevel==FTS3_SEGCURSOR_ALL - || iLevel==FTS3_SEGCURSOR_PENDING + || iLevel==FTS3_SEGCURSOR_PENDING || iLevel>=0 ); assert( iLevelnIndex; i++){ if( p->aIndex[i].nPrefix==nTerm ){ bFound = 1; - rc = sqlite3Fts3SegReaderCursor(p, pCsr->iLangid, + rc = sqlite3Fts3SegReaderCursor(p, pCsr->iLangid, i, FTS3_SEGCURSOR_ALL, zTerm, nTerm, 0, 0, pSegcsr ); pSegcsr->bLookup = 1; @@ -163297,7 +197074,7 @@ static int fts3TermSegReaderCursor( for(i=1; bFound==0 && inIndex; i++){ if( p->aIndex[i].nPrefix==nTerm+1 ){ bFound = 1; - rc = sqlite3Fts3SegReaderCursor(p, pCsr->iLangid, + rc = sqlite3Fts3SegReaderCursor(p, pCsr->iLangid, i, FTS3_SEGCURSOR_ALL, zTerm, nTerm, 1, 0, pSegcsr ); if( rc==SQLITE_OK ){ @@ -163310,7 +197087,7 @@ static int fts3TermSegReaderCursor( } if( bFound==0 ){ - rc = sqlite3Fts3SegReaderCursor(p, pCsr->iLangid, + rc = sqlite3Fts3SegReaderCursor(p, pCsr->iLangid, 0, FTS3_SEGCURSOR_ALL, zTerm, nTerm, isPrefix, 0, pSegcsr ); pSegcsr->bLookup = !isPrefix; @@ -163358,7 +197135,7 @@ static int fts3TermSelect( rc = sqlite3Fts3SegReaderStart(p, pSegcsr, &filter); while( SQLITE_OK==rc - && SQLITE_ROW==(rc = sqlite3Fts3SegReaderStep(p, pSegcsr)) + && SQLITE_ROW==(rc = sqlite3Fts3SegReaderStep(p, pSegcsr)) ){ rc = fts3TermSelectMerge(p, &tsc, pSegcsr->aDoclist, pSegcsr->nDoclist); } @@ -163387,7 +197164,7 @@ static int fts3TermSelect( ** ** If the isPoslist argument is true, then it is assumed that the doclist ** contains a position-list following each docid. Otherwise, it is assumed -** that the doclist is simply a list of docids stored as delta encoded +** that the doclist is simply a list of docids stored as delta encoded ** varints. */ static int fts3DoclistCountDocids(char *aList, int nList){ @@ -163420,6 +197197,8 @@ static int fts3NextMethod(sqlite3_vtab_cursor *pCursor){ int rc; Fts3Cursor *pCsr = (Fts3Cursor *)pCursor; if( pCsr->eSearch==FTS3_DOCID_SEARCH || pCsr->eSearch==FTS3_FULLSCAN_SEARCH ){ + Fts3Table *pTab = (Fts3Table*)pCursor->pVtab; + pTab->bLock++; if( SQLITE_ROW!=sqlite3_step(pCsr->pStmt) ){ pCsr->isEof = 1; rc = sqlite3_reset(pCsr->pStmt); @@ -163427,6 +197206,7 @@ static int fts3NextMethod(sqlite3_vtab_cursor *pCursor){ pCsr->iPrevId = sqlite3_column_int64(pCsr->pStmt, 0); rc = SQLITE_OK; } + pTab->bLock--; }else{ rc = fts3EvalNext((Fts3Cursor *)pCursor); } @@ -163434,18 +197214,6 @@ static int fts3NextMethod(sqlite3_vtab_cursor *pCursor){ return rc; } -/* -** The following are copied from sqliteInt.h. -** -** Constants for the largest and smallest possible 64-bit signed integers. -** These macros are designed to work correctly on both 32-bit and 64-bit -** compilers. -*/ -#ifndef SQLITE_AMALGAMATION -# define LARGEST_INT64 (0xffffffff|(((sqlite3_int64)0x7fffffff)<<32)) -# define SMALLEST_INT64 (((sqlite3_int64)-1) - LARGEST_INT64) -#endif - /* ** If the numeric type of argument pVal is "integer", then return it ** converted to a 64-bit signed integer. Otherwise, return a copy of @@ -163499,6 +197267,10 @@ static int fts3FilterMethod( UNUSED_PARAMETER(idxStr); UNUSED_PARAMETER(nVal); + if( p->bLock ){ + return SQLITE_ERROR; + } + eSearch = (idxNum & 0x0000FFFF); assert( eSearch>=0 && eSearch<=(FTS3_FULLTEXT_SEARCH+p->nColumn) ); assert( p->pSegments==0 ); @@ -163538,7 +197310,7 @@ static int fts3FilterMethod( assert( p->base.zErrMsg==0 ); rc = sqlite3Fts3ExprParse(p->pTokenizer, pCsr->iLangid, - p->azColumn, p->bFts4, p->nColumn, iCol, zQuery, -1, &pCsr->pExpr, + p->azColumn, p->bFts4, p->nColumn, iCol, zQuery, -1, &pCsr->pExpr, &p->base.zErrMsg ); if( rc!=SQLITE_OK ){ @@ -163565,12 +197337,14 @@ static int fts3FilterMethod( (pCsr->bDesc ? "DESC" : "ASC") ); }else{ - zSql = sqlite3_mprintf("SELECT %s ORDER BY rowid %s", + zSql = sqlite3_mprintf("SELECT %s ORDER BY rowid %s", p->zReadExprlist, (pCsr->bDesc ? "DESC" : "ASC") ); } if( zSql ){ - rc = sqlite3_prepare_v3(p->db,zSql,-1,SQLITE_PREPARE_PERSISTENT,&pCsr->pStmt,0); + p->bLock++; + rc = sqlite3Fts3PrepareStmt(p, zSql, 1, 1, &pCsr->pStmt); + p->bLock--; sqlite3_free(zSql); }else{ rc = SQLITE_NOMEM; @@ -163586,8 +197360,8 @@ static int fts3FilterMethod( return fts3NextMethod(pCursor); } -/* -** This is the xEof method of the virtual table. SQLite calls this +/* +** This is the xEof method of the virtual table. SQLite calls this ** routine to find out if it has reached the end of a result set. */ static int fts3EofMethod(sqlite3_vtab_cursor *pCursor){ @@ -163599,7 +197373,7 @@ static int fts3EofMethod(sqlite3_vtab_cursor *pCursor){ return pCsr->isEof; } -/* +/* ** This is the xRowid method. The SQLite core calls this routine to ** retrieve the rowid for the current row of the result set. fts3 ** exposes %_content.docid as the rowid for the virtual table. The @@ -163611,7 +197385,7 @@ static int fts3RowidMethod(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){ return SQLITE_OK; } -/* +/* ** This is the xColumn method, called by SQLite to request a value from ** the row that the supplied cursor currently points to. ** @@ -163654,7 +197428,7 @@ static int fts3ColumnMethod( break; }else{ iCol = p->nColumn; - /* fall-through */ + /* no break */ deliberate_fall_through } default: @@ -163671,8 +197445,8 @@ static int fts3ColumnMethod( return rc; } -/* -** This function is the implementation of the xUpdate callback used by +/* +** This function is the implementation of the xUpdate callback used by ** FTS3 virtual tables. It is invoked by SQLite each time a row is to be ** inserted, updated or deleted. */ @@ -163707,7 +197481,7 @@ static int fts3SyncMethod(sqlite3_vtab *pVtab){ ** ** Of course, updating the input segments also involves deleting a bunch ** of blocks from the segments table. But this is not considered overhead - ** as it would also be required by a crisis-merge that used the same input + ** as it would also be required by a crisis-merge that used the same input ** segments. */ const u32 nMinMerge = 64; /* Minimum amount of incr-merge work to do */ @@ -163717,8 +197491,8 @@ static int fts3SyncMethod(sqlite3_vtab *pVtab){ i64 iLastRowid = sqlite3_last_insert_rowid(p->db); rc = sqlite3Fts3PendingTermsFlush(p); - if( rc==SQLITE_OK - && p->nLeafAdd>(nMinMerge/16) + if( rc==SQLITE_OK + && p->nLeafAdd>(nMinMerge/16) && p->nAutoincrmerge && p->nAutoincrmerge!=0xff ){ int mxLevel = 0; /* Maximum relative level value in db */ @@ -163757,18 +197531,24 @@ static int fts3SetHasStat(Fts3Table *p){ } /* -** Implementation of xBegin() method. +** Implementation of xBegin() method. */ static int fts3BeginMethod(sqlite3_vtab *pVtab){ Fts3Table *p = (Fts3Table*)pVtab; + int rc; UNUSED_PARAMETER(pVtab); assert( p->pSegments==0 ); assert( p->nPendingData==0 ); assert( p->inTransaction!=1 ); - TESTONLY( p->inTransaction = 1 ); - TESTONLY( p->mxSavepoint = -1; ); p->nLeafAdd = 0; - return fts3SetHasStat(p); + rc = fts3SetHasStat(p); +#ifdef SQLITE_DEBUG + if( rc==SQLITE_OK ){ + p->inTransaction = 1; + p->mxSavepoint = -1; + } +#endif + return rc; } /* @@ -163813,17 +197593,17 @@ static void fts3ReversePoslist(char *pStart, char **ppPoslist){ /* Skip backwards passed any trailing 0x00 bytes added by NearTrim() */ while( p>pStart && (c=*p--)==0 ); - /* Search backwards for a varint with value zero (the end of the previous + /* Search backwards for a varint with value zero (the end of the previous ** poslist). This is an 0x00 byte preceded by some byte that does not ** have the 0x80 bit set. */ - while( p>pStart && (*p & 0x80) | c ){ - c = *p--; + while( p>pStart && (*p & 0x80) | c ){ + c = *p--; } assert( p==pStart || c==0 ); /* At this point p points to that preceding byte without the 0x80 bit ** set. So to find the start of the poslist, skip forward 2 bytes then - ** over a varint. + ** over a varint. ** ** Normally. The other case is that p==pStart and the poslist to return ** is the first in the doclist. In this case do not skip forward 2 bytes. @@ -163844,7 +197624,7 @@ static void fts3ReversePoslist(char *pStart, char **ppPoslist){ ** offsets() and optimize() SQL functions. ** ** If the value passed as the third argument is a blob of size -** sizeof(Fts3Cursor*), then the blob contents are copied to the +** sizeof(Fts3Cursor*), then the blob contents are copied to the ** output variable *ppCsr and SQLITE_OK is returned. Otherwise, an error ** message is written to context pContext and SQLITE_ERROR returned. The ** string passed via zFunc is used as part of the error message. @@ -163889,7 +197669,7 @@ static void fts3SnippetFunc( assert( nVal>=1 ); if( nVal>6 ){ - sqlite3_result_error(pContext, + sqlite3_result_error(pContext, "wrong number of arguments to function snippet()", -1); return; } @@ -163897,9 +197677,13 @@ static void fts3SnippetFunc( switch( nVal ){ case 6: nToken = sqlite3_value_int(apVal[5]); + /* no break */ deliberate_fall_through case 5: iCol = sqlite3_value_int(apVal[4]); + /* no break */ deliberate_fall_through case 4: zEllipsis = (const char*)sqlite3_value_text(apVal[3]); + /* no break */ deliberate_fall_through case 3: zEnd = (const char*)sqlite3_value_text(apVal[2]); + /* no break */ deliberate_fall_through case 2: zStart = (const char*)sqlite3_value_text(apVal[1]); } if( !zEllipsis || !zEnd || !zStart ){ @@ -163931,8 +197715,8 @@ static void fts3OffsetsFunc( } } -/* -** Implementation of the special optimize() function for FTS3. This +/* +** Implementation of the special optimize() function for FTS3. This ** function merges all segments in the database to a single segment. ** Example usage is: ** @@ -164041,10 +197825,10 @@ static int fts3RenameMethod( /* At this point it must be known if the %_stat table exists or not. ** So bHasStat may not be 2. */ rc = fts3SetHasStat(p); - + /* As it happens, the pending terms table is always empty here. This is - ** because an "ALTER TABLE RENAME TABLE" statement inside a transaction - ** always opens a savepoint transaction. And the xSavepoint() method + ** because an "ALTER TABLE RENAME TABLE" statement inside a transaction + ** always opens a savepoint transaction. And the xSavepoint() method ** flushes the pending terms table. But leave the (no-op) call to ** PendingTermsFlush() in in case that changes. */ @@ -164053,6 +197837,8 @@ static int fts3RenameMethod( rc = sqlite3Fts3PendingTermsFlush(p); } + p->bIgnoreSavepoint = 1; + if( p->zContentTbl==0 ){ fts3DbExec(&rc, db, "ALTER TABLE %Q.'%q_content' RENAME TO '%q_content';", @@ -164080,6 +197866,8 @@ static int fts3RenameMethod( "ALTER TABLE %Q.'%q_segdir' RENAME TO '%q_segdir';", p->zDb, p->zName, zName ); + + p->bIgnoreSavepoint = 0; return rc; } @@ -164090,12 +197878,28 @@ static int fts3RenameMethod( */ static int fts3SavepointMethod(sqlite3_vtab *pVtab, int iSavepoint){ int rc = SQLITE_OK; - UNUSED_PARAMETER(iSavepoint); - assert( ((Fts3Table *)pVtab)->inTransaction ); - assert( ((Fts3Table *)pVtab)->mxSavepoint <= iSavepoint ); - TESTONLY( ((Fts3Table *)pVtab)->mxSavepoint = iSavepoint ); - if( ((Fts3Table *)pVtab)->bIgnoreSavepoint==0 ){ - rc = fts3SyncMethod(pVtab); + Fts3Table *pTab = (Fts3Table*)pVtab; + assert( pTab->inTransaction ); + assert( pTab->mxSavepoint<=iSavepoint ); + TESTONLY( pTab->mxSavepoint = iSavepoint ); + + if( pTab->bIgnoreSavepoint==0 ){ + if( fts3HashCount(&pTab->aIndex[0].hPending)>0 ){ + char *zSql = sqlite3_mprintf("INSERT INTO %Q.%Q(%Q) VALUES('flush')", + pTab->zDb, pTab->zName, pTab->zName + ); + if( zSql ){ + pTab->bIgnoreSavepoint = 1; + rc = sqlite3_exec(pTab->db, zSql, 0, 0, 0); + pTab->bIgnoreSavepoint = 0; + sqlite3_free(zSql); + }else{ + rc = SQLITE_NOMEM; + } + } + if( rc==SQLITE_OK ){ + pTab->iSavepoint = iSavepoint+1; + } } return rc; } @@ -164106,12 +197910,11 @@ static int fts3SavepointMethod(sqlite3_vtab *pVtab, int iSavepoint){ ** This is a no-op. */ static int fts3ReleaseMethod(sqlite3_vtab *pVtab, int iSavepoint){ - TESTONLY( Fts3Table *p = (Fts3Table*)pVtab ); - UNUSED_PARAMETER(iSavepoint); - UNUSED_PARAMETER(pVtab); - assert( p->inTransaction ); - assert( p->mxSavepoint >= iSavepoint ); - TESTONLY( p->mxSavepoint = iSavepoint-1 ); + Fts3Table *pTab = (Fts3Table*)pVtab; + assert( pTab->inTransaction ); + assert( pTab->mxSavepoint >= iSavepoint ); + TESTONLY( pTab->mxSavepoint = iSavepoint-1 ); + pTab->iSavepoint = iSavepoint; return SQLITE_OK; } @@ -164121,11 +197924,13 @@ static int fts3ReleaseMethod(sqlite3_vtab *pVtab, int iSavepoint){ ** Discard the contents of the pending terms table. */ static int fts3RollbackToMethod(sqlite3_vtab *pVtab, int iSavepoint){ - Fts3Table *p = (Fts3Table*)pVtab; + Fts3Table *pTab = (Fts3Table*)pVtab; UNUSED_PARAMETER(iSavepoint); - assert( p->inTransaction ); - TESTONLY( p->mxSavepoint = iSavepoint ); - sqlite3Fts3PendingTermsClear(p); + assert( pTab->inTransaction ); + TESTONLY( pTab->mxSavepoint = iSavepoint ); + if( (iSavepoint+1)<=pTab->iSavepoint ){ + sqlite3Fts3PendingTermsClear(pTab); + } return SQLITE_OK; } @@ -164135,7 +197940,7 @@ static int fts3RollbackToMethod(sqlite3_vtab *pVtab, int iSavepoint){ */ static int fts3ShadowName(const char *zName){ static const char *azName[] = { - "content", "docsize", "segdir", "segments", "stat", + "content", "docsize", "segdir", "segments", "stat", }; unsigned int i; for(i=0; izErrMsg==0 || rc!=SQLITE_OK ); + assert( rc!=SQLITE_CORRUPT_VTAB ); + if( rc==SQLITE_ERROR || (rc&0xFF)==SQLITE_CORRUPT ){ + *pzErr = sqlite3_mprintf("unable to validate the inverted index for" + " FTS%d table %s.%s: %s", + p->bFts4 ? 4 : 3, zSchema, zTabname, sqlite3_errstr(rc)); + if( *pzErr ) rc = SQLITE_OK; + }else if( rc==SQLITE_OK && bOk==0 ){ + *pzErr = sqlite3_mprintf("malformed inverted index for FTS%d table %s.%s", + p->bFts4 ? 4 : 3, zSchema, zTabname); + if( *pzErr==0 ) rc = SQLITE_NOMEM; + } + sqlite3Fts3SegmentsClose(p); + return rc; +} + + + static const sqlite3_module fts3Module = { - /* iVersion */ 3, + /* iVersion */ 4, /* xCreate */ fts3CreateMethod, /* xConnect */ fts3ConnectMethod, /* xBestIndex */ fts3BestIndexMethod, @@ -164169,6 +198009,7 @@ static const sqlite3_module fts3Module = { /* xRelease */ fts3ReleaseMethod, /* xRollbackTo */ fts3RollbackToMethod, /* xShadowName */ fts3ShadowName, + /* xIntegrity */ fts3IntegrityMethod, }; /* @@ -164177,13 +198018,16 @@ static const sqlite3_module fts3Module = { ** allocated for the tokenizer hash table. */ static void hashDestroy(void *p){ - Fts3Hash *pHash = (Fts3Hash *)p; - sqlite3Fts3HashClear(pHash); - sqlite3_free(pHash); + Fts3HashWrapper *pHash = (Fts3HashWrapper *)p; + pHash->nRef--; + if( pHash->nRef<=0 ){ + sqlite3Fts3HashClear(&pHash->hash); + sqlite3_free(pHash); + } } /* -** The fts3 built-in tokenizers - "simple", "porter" and "icu"- are +** The fts3 built-in tokenizers - "simple", "porter" and "icu"- are ** implemented in files fts3_tokenizer1.c, fts3_porter.c and fts3_icu.c ** respectively. The following three forward declarations are for functions ** declared in these files used to retrieve the respective implementations. @@ -164209,7 +198053,7 @@ SQLITE_PRIVATE void sqlite3Fts3IcuTokenizerModule(sqlite3_tokenizer_module const */ SQLITE_PRIVATE int sqlite3Fts3Init(sqlite3 *db){ int rc = SQLITE_OK; - Fts3Hash *pHash = 0; + Fts3HashWrapper *pHash = 0; const sqlite3_tokenizer_module *pSimple = 0; const sqlite3_tokenizer_module *pPorter = 0; #ifndef SQLITE_DISABLE_FTS3_UNICODE @@ -164237,23 +198081,24 @@ SQLITE_PRIVATE int sqlite3Fts3Init(sqlite3 *db){ sqlite3Fts3PorterTokenizerModule(&pPorter); /* Allocate and initialize the hash-table used to store tokenizers. */ - pHash = sqlite3_malloc(sizeof(Fts3Hash)); + pHash = sqlite3_malloc(sizeof(Fts3HashWrapper)); if( !pHash ){ rc = SQLITE_NOMEM; }else{ - sqlite3Fts3HashInit(pHash, FTS3_HASH_STRING, 1); + sqlite3Fts3HashInit(&pHash->hash, FTS3_HASH_STRING, 1); + pHash->nRef = 0; } /* Load the built-in tokenizers into the hash table */ if( rc==SQLITE_OK ){ - if( sqlite3Fts3HashInsert(pHash, "simple", 7, (void *)pSimple) - || sqlite3Fts3HashInsert(pHash, "porter", 7, (void *)pPorter) + if( sqlite3Fts3HashInsert(&pHash->hash, "simple", 7, (void *)pSimple) + || sqlite3Fts3HashInsert(&pHash->hash, "porter", 7, (void *)pPorter) #ifndef SQLITE_DISABLE_FTS3_UNICODE - || sqlite3Fts3HashInsert(pHash, "unicode61", 10, (void *)pUnicode) + || sqlite3Fts3HashInsert(&pHash->hash, "unicode61", 10, (void *)pUnicode) #endif #ifdef SQLITE_ENABLE_ICU - || (pIcu && sqlite3Fts3HashInsert(pHash, "icu", 4, (void *)pIcu)) + || (pIcu && sqlite3Fts3HashInsert(&pHash->hash, "icu", 4, (void *)pIcu)) #endif ){ rc = SQLITE_NOMEM; @@ -164262,32 +198107,35 @@ SQLITE_PRIVATE int sqlite3Fts3Init(sqlite3 *db){ #ifdef SQLITE_TEST if( rc==SQLITE_OK ){ - rc = sqlite3Fts3ExprInitTestInterface(db, pHash); + rc = sqlite3Fts3ExprInitTestInterface(db, &pHash->hash); } #endif - /* Create the virtual table wrapper around the hash-table and overload + /* Create the virtual table wrapper around the hash-table and overload ** the four scalar functions. If this is successful, register the ** module with sqlite. */ - if( SQLITE_OK==rc - && SQLITE_OK==(rc = sqlite3Fts3InitHashTable(db, pHash, "fts3_tokenizer")) + if( SQLITE_OK==rc + && SQLITE_OK==(rc=sqlite3Fts3InitHashTable(db,&pHash->hash,"fts3_tokenizer")) && SQLITE_OK==(rc = sqlite3_overload_function(db, "snippet", -1)) && SQLITE_OK==(rc = sqlite3_overload_function(db, "offsets", 1)) && SQLITE_OK==(rc = sqlite3_overload_function(db, "matchinfo", 1)) && SQLITE_OK==(rc = sqlite3_overload_function(db, "matchinfo", 2)) && SQLITE_OK==(rc = sqlite3_overload_function(db, "optimize", 1)) ){ + pHash->nRef++; rc = sqlite3_create_module_v2( db, "fts3", &fts3Module, (void *)pHash, hashDestroy ); if( rc==SQLITE_OK ){ + pHash->nRef++; rc = sqlite3_create_module_v2( - db, "fts4", &fts3Module, (void *)pHash, 0 + db, "fts4", &fts3Module, (void *)pHash, hashDestroy ); } if( rc==SQLITE_OK ){ - rc = sqlite3Fts3InitTok(db, (void *)pHash); + pHash->nRef++; + rc = sqlite3Fts3InitTok(db, (void *)pHash, hashDestroy); } return rc; } @@ -164296,7 +198144,7 @@ SQLITE_PRIVATE int sqlite3Fts3Init(sqlite3 *db){ /* An error has occurred. Delete the hash table and return the error code. */ assert( rc!=SQLITE_OK ); if( pHash ){ - sqlite3Fts3HashClear(pHash); + sqlite3Fts3HashClear(&pHash->hash); sqlite3_free(pHash); } return rc; @@ -164304,7 +198152,7 @@ SQLITE_PRIVATE int sqlite3Fts3Init(sqlite3 *db){ /* ** Allocate an Fts3MultiSegReader for each token in the expression headed -** by pExpr. +** by pExpr. ** ** An Fts3SegReader object is a cursor that can seek or scan a range of ** entries within a single segment b-tree. An Fts3MultiSegReader uses multiple @@ -164314,7 +198162,7 @@ SQLITE_PRIVATE int sqlite3Fts3Init(sqlite3 *db){ ** If the allocated Fts3MultiSegReader just seeks to a single entry in a ** segment b-tree (if the term is not a prefix or it is a prefix for which ** there exists prefix b-tree of the right length) then it may be traversed -** and merged incrementally. Otherwise, it has to be merged into an in-memory +** and merged incrementally. Otherwise, it has to be merged into an in-memory ** doclist and then traversed. */ static void fts3EvalAllocateReaders( @@ -164331,7 +198179,7 @@ static void fts3EvalAllocateReaders( *pnToken += nToken; for(i=0; ipPhrase->aToken[i]; - int rc = fts3TermSegReaderCursor(pCsr, + int rc = fts3TermSegReaderCursor(pCsr, pToken->z, pToken->n, pToken->isPrefix, &pToken->pSegcsr ); if( rc!=SQLITE_OK ){ @@ -164465,8 +198313,7 @@ static int fts3EvalDeferredPhrase(Fts3Cursor *pCsr, Fts3Phrase *pPhrase){ char *aPoslist = 0; /* Position list for deferred tokens */ int nPoslist = 0; /* Number of bytes in aPoslist */ int iPrev = -1; /* Token number of previous deferred token */ - - assert( pPhrase->doclist.bFreeList==0 ); + char *aFree = (pPhrase->doclist.bFreeList ? pPhrase->doclist.pList : 0); for(iToken=0; iTokennToken; iToken++){ Fts3PhraseToken *pToken = &pPhrase->aToken[iToken]; @@ -164480,6 +198327,7 @@ static int fts3EvalDeferredPhrase(Fts3Cursor *pCsr, Fts3Phrase *pPhrase){ if( pList==0 ){ sqlite3_free(aPoslist); + sqlite3_free(aFree); pPhrase->doclist.pList = 0; pPhrase->doclist.nList = 0; return SQLITE_OK; @@ -164500,6 +198348,7 @@ static int fts3EvalDeferredPhrase(Fts3Cursor *pCsr, Fts3Phrase *pPhrase){ nPoslist = (int)(aOut - aPoslist); if( nPoslist==0 ){ sqlite3_free(aPoslist); + sqlite3_free(aFree); pPhrase->doclist.pList = 0; pPhrase->doclist.nList = 0; return SQLITE_OK; @@ -164521,6 +198370,7 @@ static int fts3EvalDeferredPhrase(Fts3Cursor *pCsr, Fts3Phrase *pPhrase){ char *p1; char *p2; char *aOut; + i64 nAlloc = (i64)nPoslist*2 + FTS3_BUFFER_PADDING; if( nMaxUndeferred>iPrev ){ p1 = aPoslist; @@ -164532,13 +198382,14 @@ static int fts3EvalDeferredPhrase(Fts3Cursor *pCsr, Fts3Phrase *pPhrase){ nDistance = iPrev - nMaxUndeferred; } - aOut = (char *)sqlite3_malloc(nPoslist+8); + aOut = (char *)sqlite3Fts3MallocZero(nAlloc); if( !aOut ){ sqlite3_free(aPoslist); return SQLITE_NOMEM; } - + pPhrase->doclist.pList = aOut; + assert( p1 && p2 ); if( fts3PoslistPhraseMerge(&aOut, nDistance, 0, 1, &p1, &p2) ){ pPhrase->doclist.bFreeList = 1; pPhrase->doclist.nList = (int)(aOut - pPhrase->doclist.pList); @@ -164551,6 +198402,7 @@ static int fts3EvalDeferredPhrase(Fts3Cursor *pCsr, Fts3Phrase *pPhrase){ } } + if( pPhrase->doclist.pList!=aFree ) sqlite3_free(aFree); return SQLITE_OK; } #endif /* SQLITE_DISABLE_FTS4_DEFERRED */ @@ -164562,7 +198414,7 @@ static int fts3EvalDeferredPhrase(Fts3Cursor *pCsr, Fts3Phrase *pPhrase){ #define MAX_INCR_PHRASE_TOKENS 4 /* -** This function is called for each Fts3Phrase in a full-text query +** This function is called for each Fts3Phrase in a full-text query ** expression to initialize the mechanism for returning rows. Once this ** function has been called successfully on an Fts3Phrase, it may be ** used with fts3EvalPhraseNext() to iterate through the matching docids. @@ -164580,14 +198432,14 @@ static int fts3EvalPhraseStart(Fts3Cursor *pCsr, int bOptOk, Fts3Phrase *p){ /* Determine if doclists may be loaded from disk incrementally. This is ** possible if the bOptOk argument is true, the FTS doclists will be - ** scanned in forward order, and the phrase consists of + ** scanned in forward order, and the phrase consists of ** MAX_INCR_PHRASE_TOKENS or fewer tokens, none of which are are "^first" ** tokens or prefix tokens that cannot use a prefix-index. */ int bHaveIncr = 0; - int bIncrOk = (bOptOk - && pCsr->bDesc==pTab->bDescIdx + int bIncrOk = (bOptOk + && pCsr->bDesc==pTab->bDescIdx && p->nToken<=MAX_INCR_PHRASE_TOKENS && p->nToken>0 -#ifdef SQLITE_TEST +#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST) && pTab->bNoIncrDoclist==0 #endif ); @@ -164621,12 +198473,12 @@ static int fts3EvalPhraseStart(Fts3Cursor *pCsr, int bOptOk, Fts3Phrase *p){ } /* -** This function is used to iterate backwards (from the end to start) +** This function is used to iterate backwards (from the end to start) ** through doclists. It is used by this module to iterate through phrase ** doclists in reverse and by the fts3_write.c module to iterate through ** pending-terms lists when writing to databases with "order=desc". ** -** The doclist may be sorted in ascending (parameter bDescIdx==0) or +** The doclist may be sorted in ascending (parameter bDescIdx==0) or ** descending (parameter bDescIdx==1) order of docid. Regardless, this ** function iterates from the end of the doclist to the beginning. */ @@ -164643,7 +198495,7 @@ SQLITE_PRIVATE void sqlite3Fts3DoclistPrev( assert( nDoclist>0 ); assert( *pbEof==0 ); - assert( p || *piDocid==0 ); + assert_fts3_nc( p || *piDocid==0 ); assert( !p || (p>aDoclist && p<&aDoclist[nDoclist]) ); if( p==0 ){ @@ -164698,7 +198550,7 @@ SQLITE_PRIVATE void sqlite3Fts3DoclistNext( assert( nDoclist>0 ); assert( *pbEof==0 ); - assert( p || *piDocid==0 ); + assert_fts3_nc( p || *piDocid==0 ); assert( !p || (p>=aDoclist && p<=&aDoclist[nDoclist]) ); if( p==0 ){ @@ -164706,7 +198558,7 @@ SQLITE_PRIVATE void sqlite3Fts3DoclistNext( p += sqlite3Fts3GetVarint(p, piDocid); }else{ fts3PoslistCopy(0, &p); - while( p<&aDoclist[nDoclist] && *p==0 ) p++; + while( p<&aDoclist[nDoclist] && *p==0 ) p++; if( p>=&aDoclist[nDoclist] ){ *pbEof = 1; }else{ @@ -164729,15 +198581,16 @@ static void fts3EvalDlPhraseNext( u8 *pbEof ){ char *pIter; /* Used to iterate through aAll */ - char *pEnd = &pDL->aAll[pDL->nAll]; /* 1 byte past end of aAll */ - + char *pEnd; /* 1 byte past end of aAll */ + if( pDL->pNextDocid ){ pIter = pDL->pNextDocid; + assert( pDL->aAll!=0 || pIter==0 ); }else{ pIter = pDL->aAll; } - if( pIter>=pEnd ){ + if( pIter==0 || pIter>=(pEnd = pDL->aAll + pDL->nAll) ){ /* We have already reached the end of this doclist. EOF. */ *pbEof = 1; }else{ @@ -164778,12 +198631,12 @@ struct TokenDoclist { }; /* -** Token pToken is an incrementally loaded token that is part of a +** Token pToken is an incrementally loaded token that is part of a ** multi-token phrase. Advance it to the next matching document in the ** database and populate output variable *p with the details of the new ** entry. Or, if the iterator has reached EOF, set *pbEof to true. ** -** If an error occurs, return an SQLite error code. Otherwise, return +** If an error occurs, return an SQLite error code. Otherwise, return ** SQLITE_OK. */ static int incrPhraseTokenNext( @@ -164824,18 +198677,18 @@ static int incrPhraseTokenNext( /* ** The phrase iterator passed as the second argument: ** -** * features at least one token that uses an incremental doclist, and +** * features at least one token that uses an incremental doclist, and ** ** * does not contain any deferred tokens. ** -** Advance it to the next matching documnent in the database and populate -** the Fts3Doclist.pList and nList fields. +** Advance it to the next matching document in the database and populate +** the Fts3Doclist.pList and nList fields. ** ** If there is no "next" entry and no error occurs, then *pbEof is set to ** 1 before returning. Otherwise, if no error occurs and the iterator is ** successfully advanced, *pbEof is set to 0. ** -** If an error occurs, return an SQLite error code. Otherwise, return +** If an error occurs, return an SQLite error code. Otherwise, return ** SQLITE_OK. */ static int fts3EvalIncrPhraseNext( @@ -164853,7 +198706,7 @@ static int fts3EvalIncrPhraseNext( assert( p->bIncr==1 ); if( p->nToken==1 ){ - rc = sqlite3Fts3MsrIncrNext(pTab, p->aToken[0].pSegcsr, + rc = sqlite3Fts3MsrIncrNext(pTab, p->aToken[0].pSegcsr, &pDL->iDocid, &pDL->pList, &pDL->nList ); if( pDL->pList==0 ) bEof = 1; @@ -164883,8 +198736,8 @@ static int fts3EvalIncrPhraseNext( /* Keep advancing iterators until they all point to the same document */ for(i=0; inToken; i++){ - while( rc==SQLITE_OK && bEof==0 - && a[i].bIgnore==0 && DOCID_CMP(a[i].iDocid, iMax)<0 + while( rc==SQLITE_OK && bEof==0 + && a[i].bIgnore==0 && DOCID_CMP(a[i].iDocid, iMax)<0 ){ rc = incrPhraseTokenNext(pTab, p, i, &a[i], &bEof); if( DOCID_CMP(a[i].iDocid, iMax)>0 ){ @@ -164898,7 +198751,7 @@ static int fts3EvalIncrPhraseNext( if( bEof==0 ){ int nList = 0; int nByte = a[p->nToken-1].nList; - char *aDoclist = sqlite3_malloc(nByte+FTS3_BUFFER_PADDING); + char *aDoclist = sqlite3_malloc64((i64)nByte+FTS3_BUFFER_PADDING); if( !aDoclist ) return SQLITE_NOMEM; memcpy(aDoclist, a[p->nToken-1].pList, nByte+1); memset(&aDoclist[nByte], 0, FTS3_BUFFER_PADDING); @@ -164931,8 +198784,8 @@ static int fts3EvalIncrPhraseNext( } /* -** Attempt to move the phrase iterator to point to the next matching docid. -** If an error occurs, return an SQLite error code. Otherwise, return +** Attempt to move the phrase iterator to point to the next matching docid. +** If an error occurs, return an SQLite error code. Otherwise, return ** SQLITE_OK. ** ** If there is no "next" entry and no error occurs, then *pbEof is set to @@ -164951,7 +198804,7 @@ static int fts3EvalPhraseNext( if( p->bIncr ){ rc = fts3EvalIncrPhraseNext(pCsr, p, pbEof); }else if( pCsr->bDesc!=pTab->bDescIdx && pDL->nAll ){ - sqlite3Fts3DoclistPrev(pTab->bDescIdx, pDL->aAll, pDL->nAll, + sqlite3Fts3DoclistPrev(pTab->bDescIdx, pDL->aAll, pDL->nAll, &pDL->pNextDocid, &pDL->iDocid, &pDL->nList, pbEof ); pDL->pList = pDL->pNextDocid; @@ -165011,7 +198864,7 @@ static void fts3EvalStartReaders( ** Tokens are divided into AND/NEAR clusters. All tokens in a cluster belong ** to phrases that are connected only by AND and NEAR operators (not OR or ** NOT). When determining tokens to defer, each AND/NEAR cluster is considered -** separately. The root of a tokens AND/NEAR cluster is stored in +** separately. The root of a tokens AND/NEAR cluster is stored in ** Fts3TokenAndCost.pRoot. */ typedef struct Fts3TokenAndCost Fts3TokenAndCost; @@ -165079,7 +198932,7 @@ static void fts3EvalTokenCosts( ** write this value to *pnPage and return SQLITE_OK. Otherwise, return ** an SQLite error code. ** -** The average document size in pages is calculated by first calculating +** The average document size in pages is calculated by first calculating ** determining the average size in bytes, B. If B is less than the amount ** of data that will fit on a single leaf page of an intkey table in ** this database, then the average docsize is 1. Otherwise, it is 1 plus @@ -165089,10 +198942,10 @@ static int fts3EvalAverageDocsize(Fts3Cursor *pCsr, int *pnPage){ int rc = SQLITE_OK; if( pCsr->nRowAvg==0 ){ /* The average document size, which is required to calculate the cost - ** of each doclist, has not yet been determined. Read the required + ** of each doclist, has not yet been determined. Read the required ** data from the %_stat table to calculate it. ** - ** Entry 0 of the %_stat table is a blob containing (nCol+1) FTS3 + ** Entry 0 of the %_stat table is a blob containing (nCol+1) FTS3 ** varints, where nCol is the number of columns in the FTS3 table. ** The first varint is the number of documents currently stored in ** the table. The following nCol varints contain the total amount of @@ -165109,12 +198962,13 @@ static int fts3EvalAverageDocsize(Fts3Cursor *pCsr, int *pnPage){ rc = sqlite3Fts3SelectDoctotal(p, &pStmt); if( rc!=SQLITE_OK ) return rc; a = sqlite3_column_blob(pStmt, 0); - assert( a ); - - pEnd = &a[sqlite3_column_bytes(pStmt, 0)]; - a += sqlite3Fts3GetVarint(a, &nDoc); - while( anDoc = nDoc; pCsr->nRowAvg = (int)(((nByte / nDoc) + p->nPgsz) / p->nPgsz); - assert( pCsr->nRowAvg>0 ); + assert( pCsr->nRowAvg>0 ); rc = sqlite3_reset(pStmt); } @@ -165132,11 +198986,11 @@ static int fts3EvalAverageDocsize(Fts3Cursor *pCsr, int *pnPage){ } /* -** This function is called to select the tokens (if any) that will be +** This function is called to select the tokens (if any) that will be ** deferred. The array aTC[] has already been populated when this is ** called. ** -** This function is called once for each AND/NEAR cluster in the +** This function is called once for each AND/NEAR cluster in the ** expression. Each invocation determines which tokens to defer within ** the cluster with root node pRoot. See comments above the definition ** of struct Fts3TokenAndCost for more details. @@ -165186,8 +199040,8 @@ static int fts3EvalSelectDeferred( assert( rc!=SQLITE_OK || nDocSize>0 ); - /* Iterate through all tokens in this AND/NEAR cluster, in ascending order - ** of the number of overflow pages that will be loaded by the pager layer + /* Iterate through all tokens in this AND/NEAR cluster, in ascending order + ** of the number of overflow pages that will be loaded by the pager layer ** to retrieve the entire doclist for the token from the full-text index. ** Load the doclists for tokens that are either: ** @@ -165198,7 +199052,7 @@ static int fts3EvalSelectDeferred( ** ** After each token doclist is loaded, merge it with the others from the ** same phrase and count the number of documents that the merged doclist - ** contains. Set variable "nMinEst" to the smallest number of documents in + ** contains. Set variable "nMinEst" to the smallest number of documents in ** any phrase doclist for which 1 or more token doclists have been loaded. ** Let nOther be the number of other phrases for which it is certain that ** one or more tokens will not be deferred. @@ -165214,8 +199068,8 @@ static int fts3EvalSelectDeferred( /* Set pTC to point to the cheapest remaining token. */ for(iTC=0; iTCnOvfl) + if( aTC[iTC].pToken && aTC[iTC].pRoot==pRoot + && (!pTC || aTC[iTC].nOvflnOvfl) ){ pTC = &aTC[iTC]; } @@ -165224,7 +199078,7 @@ static int fts3EvalSelectDeferred( if( ii && pTC->nOvfl>=((nMinEst+(nLoad4/4)-1)/(nLoad4/4))*nDocSize ){ /* The number of overflow pages to load for this (and therefore all - ** subsequent) tokens is greater than the estimated number of pages + ** subsequent) tokens is greater than the estimated number of pages ** that will be loaded if all subsequent tokens are deferred. */ Fts3PhraseToken *pToken = pTC->pToken; @@ -165233,7 +199087,7 @@ static int fts3EvalSelectDeferred( pToken->pSegcsr = 0; }else{ /* Set nLoad4 to the value of (4^nOther) for the next iteration of the - ** for-loop. Except, limit the value to 2^24 to prevent it from + ** for-loop. Except, limit the value to 2^24 to prevent it from ** overflowing the 32-bit integer it is stored in. */ if( ii<12 ) nLoad4 = nLoad4*4; @@ -165291,16 +199145,15 @@ static int fts3EvalStart(Fts3Cursor *pCsr){ #ifndef SQLITE_DISABLE_FTS4_DEFERRED if( rc==SQLITE_OK && nToken>1 && pTab->bFts4 ){ Fts3TokenAndCost *aTC; - Fts3Expr **apOr; aTC = (Fts3TokenAndCost *)sqlite3_malloc64( sizeof(Fts3TokenAndCost) * nToken + sizeof(Fts3Expr *) * nOr * 2 ); - apOr = (Fts3Expr **)&aTC[nToken]; if( !aTC ){ rc = SQLITE_NOMEM; }else{ + Fts3Expr **apOr = (Fts3Expr **)&aTC[nToken]; int ii; Fts3TokenAndCost *pTC = aTC; Fts3Expr **ppOr = apOr; @@ -165346,7 +199199,7 @@ static void fts3EvalInvalidatePoslist(Fts3Phrase *pPhrase){ ** ** Parameter nNear is passed the NEAR distance of the expression (5 in ** the example above). When this function is called, *paPoslist points to -** the position list, and *pnToken is the number of phrase tokens in, the +** the position list, and *pnToken is the number of phrase tokens in the ** phrase on the other side of the NEAR operator to pPhrase. For example, ** if pPhrase refers to the "def ghi" phrase, then *paPoslist points to ** the position list associated with phrase "abc". @@ -165355,7 +199208,7 @@ static void fts3EvalInvalidatePoslist(Fts3Phrase *pPhrase){ ** close to a position in the *paPoslist position list are removed. If this ** leaves 0 positions, zero is returned. Otherwise, non-zero. ** -** Before returning, *paPoslist is set to point to the position lsit +** Before returning, *paPoslist is set to point to the position lsit ** associated with pPhrase. And *pnToken is set to the number of tokens in ** pPhrase. */ @@ -165369,8 +199222,8 @@ static int fts3EvalNearTrim( int nParam1 = nNear + pPhrase->nToken; int nParam2 = nNear + *pnToken; int nNew; - char *p2; - char *pOut; + char *p2; + char *pOut; int res; assert( pPhrase->doclist.pList ); @@ -165381,10 +199234,12 @@ static int fts3EvalNearTrim( ); if( res ){ nNew = (int)(pOut - pPhrase->doclist.pList) - 1; - assert( pPhrase->doclist.pList[nNew]=='\0' ); - assert( nNew<=pPhrase->doclist.nList && nNew>0 ); - memset(&pPhrase->doclist.pList[nNew], 0, pPhrase->doclist.nList - nNew); - pPhrase->doclist.nList = nNew; + assert_fts3_nc( nNew<=pPhrase->doclist.nList && nNew>0 ); + if( nNew>=0 && nNew<=pPhrase->doclist.nList ){ + assert( pPhrase->doclist.pList[nNew]=='\0' ); + memset(&pPhrase->doclist.pList[nNew], 0, pPhrase->doclist.nList - nNew); + pPhrase->doclist.nList = nNew; + } *paPoslist = pPhrase->doclist.pList; *pnToken = pPhrase->nToken; } @@ -165417,19 +199272,19 @@ static int fts3EvalNearTrim( ** ** 1. Deferred tokens are not taken into account. If a phrase consists ** entirely of deferred tokens, it is assumed to match every row in -** the db. In this case the position-list is not populated at all. +** the db. In this case the position-list is not populated at all. ** ** Or, if a phrase contains one or more deferred tokens and one or -** more non-deferred tokens, then the expression is advanced to the +** more non-deferred tokens, then the expression is advanced to the ** next possible match, considering only non-deferred tokens. In other ** words, if the phrase is "A B C", and "B" is deferred, the expression -** is advanced to the next row that contains an instance of "A * C", +** is advanced to the next row that contains an instance of "A * C", ** where "*" may match any single token. The position list in this case ** is populated as for "A * C" before returning. ** -** 2. NEAR is treated as AND. If the expression is "x NEAR y", it is +** 2. NEAR is treated as AND. If the expression is "x NEAR y", it is ** advanced to point to the next row that matches "x AND y". -** +** ** See sqlite3Fts3EvalTestDeferred() for details on testing if a row is ** really a match, taking into account deferred tokens and NEAR operators. */ @@ -165438,9 +199293,8 @@ static void fts3EvalNextRow( Fts3Expr *pExpr, /* Expr. to advance to next matching row */ int *pRc /* IN/OUT: Error code */ ){ - if( *pRc==SQLITE_OK ){ + if( *pRc==SQLITE_OK && pExpr->bEof==0 ){ int bDescDoclist = pCsr->bDesc; /* Used by DOCID_CMP() macro */ - assert( pExpr->bEof==0 ); pExpr->bStart = 1; switch( pExpr->eType ){ @@ -165493,18 +199347,19 @@ static void fts3EvalNextRow( fts3EvalNextRow(pCsr, pLeft, pRc); } } + pRight->bEof = pLeft->bEof = 1; } } break; } - + case FTSQUERY_OR: { Fts3Expr *pLeft = pExpr->pLeft; Fts3Expr *pRight = pExpr->pRight; sqlite3_int64 iCmp = DOCID_CMP(pLeft->iDocid, pRight->iDocid); - assert( pLeft->bStart || pLeft->iDocid==pRight->iDocid ); - assert( pRight->bStart || pLeft->iDocid==pRight->iDocid ); + assert_fts3_nc( pLeft->bStart || pLeft->iDocid==pRight->iDocid ); + assert_fts3_nc( pRight->bStart || pLeft->iDocid==pRight->iDocid ); if( pRight->bEof || (pLeft->bEof==0 && iCmp<0) ){ fts3EvalNextRow(pCsr, pLeft, pRc); @@ -165537,9 +199392,9 @@ static void fts3EvalNextRow( fts3EvalNextRow(pCsr, pLeft, pRc); if( pLeft->bEof==0 ){ - while( !*pRc - && !pRight->bEof - && DOCID_CMP(pLeft->iDocid, pRight->iDocid)>0 + while( !*pRc + && !pRight->bEof + && DOCID_CMP(pLeft->iDocid, pRight->iDocid)>0 ){ fts3EvalNextRow(pCsr, pRight, pRc); } @@ -165564,14 +199419,14 @@ static void fts3EvalNextRow( ** If *pRc is not SQLITE_OK, or if pExpr is not the root node of a NEAR ** cluster, then this function returns 1 immediately. ** -** Otherwise, it checks if the current row really does match the NEAR -** expression, using the data currently stored in the position lists -** (Fts3Expr->pPhrase.doclist.pList/nList) for each phrase in the expression. +** Otherwise, it checks if the current row really does match the NEAR +** expression, using the data currently stored in the position lists +** (Fts3Expr->pPhrase.doclist.pList/nList) for each phrase in the expression. ** ** If the current row is a match, the position list associated with each ** phrase in the NEAR expression is edited in place to contain only those ** phrase instances sufficiently close to their peers to satisfy all NEAR -** constraints. In this case it returns 1. If the NEAR expression does not +** constraints. In this case it returns 1. If the NEAR expression does not ** match the current row, 0 is returned. The position lists may or may not ** be edited if 0 is returned. */ @@ -165594,15 +199449,15 @@ static int fts3EvalNearTest(Fts3Expr *pExpr, int *pRc){ ** | | ** "w" "x" ** - ** The right-hand child of a NEAR node is always a phrase. The + ** The right-hand child of a NEAR node is always a phrase. The ** left-hand child may be either a phrase or a NEAR node. There are ** no exceptions to this - it's the way the parser in fts3_expr.c works. */ - if( *pRc==SQLITE_OK - && pExpr->eType==FTSQUERY_NEAR + if( *pRc==SQLITE_OK + && pExpr->eType==FTSQUERY_NEAR && (pExpr->pParent==0 || pExpr->pParent->eType!=FTSQUERY_NEAR) ){ - Fts3Expr *p; + Fts3Expr *p; sqlite3_int64 nTmp = 0; /* Bytes of temp space */ char *aTmp; /* Temp space for PoslistNearMerge() */ @@ -165612,7 +199467,7 @@ static int fts3EvalNearTest(Fts3Expr *pExpr, int *pRc){ nTmp += p->pRight->pPhrase->doclist.nList; } nTmp += p->pPhrase->doclist.nList; - aTmp = sqlite3_malloc64(nTmp*2); + aTmp = sqlite3_malloc64(nTmp*2 + FTS3_VARINT_MAX); if( !aTmp ){ *pRc = SQLITE_NOMEM; res = 0; @@ -165649,12 +199504,12 @@ static int fts3EvalNearTest(Fts3Expr *pExpr, int *pRc){ /* ** This function is a helper function for sqlite3Fts3EvalTestDeferred(). ** Assuming no error occurs or has occurred, It returns non-zero if the -** expression passed as the second argument matches the row that pCsr +** expression passed as the second argument matches the row that pCsr ** currently points to, or zero if it does not. ** ** If *pRc is not SQLITE_OK when this function is called, it is a no-op. -** If an error occurs during execution of this function, *pRc is set to -** the appropriate SQLite error code. In this case the returned value is +** If an error occurs during execution of this function, *pRc is set to +** the appropriate SQLite error code. In this case the returned value is ** undefined. */ static int fts3EvalTestExpr( @@ -165673,10 +199528,10 @@ static int fts3EvalTestExpr( && fts3EvalNearTest(pExpr, pRc) ); - /* If the NEAR expression does not match any rows, zero the doclist for + /* If the NEAR expression does not match any rows, zero the doclist for ** all phrases involved in the NEAR. This is because the snippet(), - ** offsets() and matchinfo() functions are not supposed to recognize - ** any instances of phrases that are part of unmatched NEAR queries. + ** offsets() and matchinfo() functions are not supposed to recognize + ** any instances of phrases that are part of unmatched NEAR queries. ** For example if this expression: ** ** ... MATCH 'a OR (b NEAR c)' @@ -165688,8 +199543,8 @@ static int fts3EvalTestExpr( ** then any snippet() should ony highlight the "a" term, not the "b" ** (as "b" is part of a non-matching NEAR clause). */ - if( bHit==0 - && pExpr->eType==FTSQUERY_NEAR + if( bHit==0 + && pExpr->eType==FTSQUERY_NEAR && (pExpr->pParent==0 || pExpr->pParent->eType!=FTSQUERY_NEAR) ){ Fts3Expr *p; @@ -165721,11 +199576,10 @@ static int fts3EvalTestExpr( default: { #ifndef SQLITE_DISABLE_FTS4_DEFERRED - if( pCsr->pDeferred - && (pExpr->iDocid==pCsr->iPrevId || pExpr->bDeferred) - ){ + if( pCsr->pDeferred && (pExpr->bDeferred || ( + pExpr->iDocid==pCsr->iPrevId && pExpr->pPhrase->doclist.pList + ))){ Fts3Phrase *pPhrase = pExpr->pPhrase; - assert( pExpr->bDeferred || pPhrase->doclist.bFreeList==0 ); if( pExpr->bDeferred ){ fts3EvalInvalidatePoslist(pPhrase); } @@ -165735,7 +199589,10 @@ static int fts3EvalTestExpr( }else #endif { - bHit = (pExpr->bEof==0 && pExpr->iDocid==pCsr->iPrevId); + bHit = ( + pExpr->bEof==0 && pExpr->iDocid==pCsr->iPrevId + && pExpr->pPhrase->doclist.nList>0 + ); } break; } @@ -165777,7 +199634,7 @@ SQLITE_PRIVATE int sqlite3Fts3EvalTestDeferred(Fts3Cursor *pCsr, int *pRc){ ** memory and scan it to determine the position list for each deferred ** token. Then, see if this row is really a match, considering deferred ** tokens and NEAR operators (neither of which were taken into account - ** earlier, by fts3EvalNextRow()). + ** earlier, by fts3EvalNextRow()). */ if( pCsr->pDeferred ){ rc = fts3CursorSeek(0, pCsr); @@ -165831,8 +199688,8 @@ static int fts3EvalNext(Fts3Cursor *pCsr){ } /* -** Restart interation for expression pExpr so that the next call to -** fts3EvalNext() visits the first row. Do not allow incremental +** Restart iteration for expression pExpr so that the next call to +** fts3EvalNext() visits the first row. Do not allow incremental ** loading or merging of phrase doclists for this iteration. ** ** If *pRc is other than SQLITE_OK when this function is called, it is @@ -165875,11 +199732,29 @@ static void fts3EvalRestart( } /* -** After allocating the Fts3Expr.aMI[] array for each phrase in the +** Expression node pExpr is an MSR phrase. This function restarts pExpr +** so that it is a regular phrase query, not an MSR. SQLITE_OK is returned +** if successful, or an SQLite error code otherwise. +*/ +SQLITE_PRIVATE int sqlite3Fts3MsrCancel(Fts3Cursor *pCsr, Fts3Expr *pExpr){ + int rc = SQLITE_OK; + if( pExpr->bEof==0 ){ + i64 iDocid = pExpr->iDocid; + fts3EvalRestart(pCsr, pExpr, &rc); + while( rc==SQLITE_OK && pExpr->iDocid!=iDocid ){ + fts3EvalNextRow(pCsr, pExpr, &rc); + if( pExpr->bEof ) rc = FTS_CORRUPT_VTAB; + } + } + return rc; +} + +/* +** After allocating the Fts3Expr.aMI[] array for each phrase in the ** expression rooted at pExpr, the cursor iterates through all rows matched ** by pExpr, calling this function for each row. This function increments ** the values in Fts3Expr.aMI[] according to the position-list currently -** found in Fts3Expr.pPhrase->doclist.pList for each of the phrase +** found in Fts3Expr.pPhrase->doclist.pList for each of the phrase ** expression nodes. */ static void fts3EvalUpdateCounts(Fts3Expr *pExpr, int nCol){ @@ -165913,6 +199788,22 @@ static void fts3EvalUpdateCounts(Fts3Expr *pExpr, int nCol){ } } +/* +** This is an sqlite3Fts3ExprIterate() callback. If the Fts3Expr.aMI[] array +** has not yet been allocated, allocate and zero it. Otherwise, just zero +** it. +*/ +static int fts3AllocateMSI(Fts3Expr *pExpr, int iPhrase, void *pCtx){ + Fts3Table *pTab = (Fts3Table*)pCtx; + UNUSED_PARAMETER(iPhrase); + if( pExpr->aMI==0 ){ + pExpr->aMI = (u32 *)sqlite3_malloc64(pTab->nColumn * 3 * sizeof(u32)); + if( pExpr->aMI==0 ) return SQLITE_NOMEM; + } + memset(pExpr->aMI, 0, pTab->nColumn * 3 * sizeof(u32)); + return SQLITE_OK; +} + /* ** Expression pExpr must be of type FTSQUERY_PHRASE. ** @@ -165934,7 +199825,6 @@ static int fts3EvalGatherStats( if( pExpr->aMI==0 ){ Fts3Table *pTab = (Fts3Table *)pCsr->base.pVtab; Fts3Expr *pRoot; /* Root of NEAR expression */ - Fts3Expr *p; /* Iterator used for several purposes */ sqlite3_int64 iPrevId = pCsr->iPrevId; sqlite3_int64 iDocid; @@ -165942,7 +199832,9 @@ static int fts3EvalGatherStats( /* Find the root of the NEAR expression */ pRoot = pExpr; - while( pRoot->pParent && pRoot->pParent->eType==FTSQUERY_NEAR ){ + while( pRoot->pParent + && (pRoot->pParent->eType==FTSQUERY_NEAR || pRoot->bDeferred) + ){ pRoot = pRoot->pParent; } iDocid = pRoot->iDocid; @@ -165950,14 +199842,8 @@ static int fts3EvalGatherStats( assert( pRoot->bStart ); /* Allocate space for the aMSI[] array of each FTSQUERY_PHRASE node */ - for(p=pRoot; p; p=p->pLeft){ - Fts3Expr *pE = (p->eType==FTSQUERY_PHRASE?p:p->pRight); - assert( pE->aMI==0 ); - pE->aMI = (u32 *)sqlite3_malloc64(pTab->nColumn * 3 * sizeof(u32)); - if( !pE->aMI ) return SQLITE_NOMEM; - memset(pE->aMI, 0, pTab->nColumn * 3 * sizeof(u32)); - } - + rc = sqlite3Fts3ExprIterate(pRoot, fts3AllocateMSI, (void*)pTab); + if( rc!=SQLITE_OK ) return rc; fts3EvalRestart(pCsr, pRoot, &rc); while( pCsr->isEof==0 && rc==SQLITE_OK ){ @@ -165973,9 +199859,9 @@ static int fts3EvalGatherStats( pCsr->isRequireSeek = 1; pCsr->isMatchinfoNeeded = 1; pCsr->iPrevId = pRoot->iDocid; - }while( pCsr->isEof==0 - && pRoot->eType==FTSQUERY_NEAR - && sqlite3Fts3EvalTestDeferred(pCsr, &rc) + }while( pCsr->isEof==0 + && pRoot->eType==FTSQUERY_NEAR + && sqlite3Fts3EvalTestDeferred(pCsr, &rc) ); if( rc==SQLITE_OK && pCsr->isEof==0 ){ @@ -165990,7 +199876,7 @@ static int fts3EvalGatherStats( pRoot->bEof = bEof; }else{ /* Caution: pRoot may iterate through docids in ascending or descending - ** order. For this reason, even though it seems more defensive, the + ** order. For this reason, even though it seems more defensive, the ** do loop can not be written: ** ** do {...} while( pRoot->iDocidbEof==0 ); + assert_fts3_nc( pRoot->bEof==0 ); + if( pRoot->bEof ) rc = FTS_CORRUPT_VTAB; }while( pRoot->iDocid!=iDocid && rc==SQLITE_OK ); } } @@ -166006,10 +199893,10 @@ static int fts3EvalGatherStats( } /* -** This function is used by the matchinfo() module to query a phrase +** This function is used by the matchinfo() module to query a phrase ** expression node for the following information: ** -** 1. The total number of occurrences of the phrase in each column of +** 1. The total number of occurrences of the phrase in each column of ** the FTS table (considering all rows), and ** ** 2. For each column, the number of rows in the table for which the @@ -166023,12 +199910,12 @@ static int fts3EvalGatherStats( ** ** Caveats: ** -** * If a phrase consists entirely of deferred tokens, then all output +** * If a phrase consists entirely of deferred tokens, then all output ** values are set to the number of documents in the table. In other -** words we assume that very common tokens occur exactly once in each +** words we assume that very common tokens occur exactly once in each ** column of each row of the table. ** -** * If a phrase contains some deferred tokens (and some non-deferred +** * If a phrase contains some deferred tokens (and some non-deferred ** tokens), count the potential occurrence identified by considering ** the non-deferred tokens instead of actual phrase occurrences. ** @@ -166066,14 +199953,14 @@ SQLITE_PRIVATE int sqlite3Fts3EvalPhraseStats( /* ** The expression pExpr passed as the second argument to this function -** must be of type FTSQUERY_PHRASE. +** must be of type FTSQUERY_PHRASE. ** ** The returned value is either NULL or a pointer to a buffer containing ** a position-list indicating the occurrences of the phrase in column iCol -** of the current row. +** of the current row. ** -** More specifically, the returned buffer contains 1 varint for each -** occurrence of the phrase in the column, stored using the normal (delta+2) +** More specifically, the returned buffer contains 1 varint for each +** occurrence of the phrase in the column, stored using the normal (delta+2) ** compression and is terminated by either an 0x01 or 0x00 byte. For example, ** if the requested column contains "a b X c d X X" and the position-list ** for 'X' is requested, the buffer returned may contain: @@ -166095,7 +199982,7 @@ SQLITE_PRIVATE int sqlite3Fts3EvalPhrasePoslist( int iThis; sqlite3_int64 iDocid; - /* If this phrase is applies specifically to some column other than + /* If this phrase is applies specifically to some column other than ** column iCol, return a NULL pointer. */ *ppOut = 0; assert( iCol>=0 && iColnColumn ); @@ -166112,10 +199999,11 @@ SQLITE_PRIVATE int sqlite3Fts3EvalPhrasePoslist( u8 bTreeEof = 0; Fts3Expr *p; /* Used to iterate from pExpr to root */ Fts3Expr *pNear; /* Most senior NEAR ancestor (or pExpr) */ + Fts3Expr *pRun; /* Closest non-deferred ancestor of pNear */ int bMatch; - /* Check if this phrase descends from an OR expression node. If not, - ** return NULL. Otherwise, the entry that corresponds to docid + /* Check if this phrase descends from an OR expression node. If not, + ** return NULL. Otherwise, the entry that corresponds to docid ** pCsr->iPrevId may lie earlier in the doclist buffer. Or, if the ** tree that the node is part of has been marked as EOF, but the node ** itself is not EOF, then it may point to an earlier entry. */ @@ -166126,22 +200014,30 @@ SQLITE_PRIVATE int sqlite3Fts3EvalPhrasePoslist( if( p->bEof ) bTreeEof = 1; } if( bOr==0 ) return SQLITE_OK; + pRun = pNear; + while( pRun->bDeferred ){ + assert( pRun->pParent ); + pRun = pRun->pParent; + } /* This is the descendent of an OR node. In this case we cannot use ** an incremental phrase. Load the entire doclist for the phrase ** into memory in this case. */ if( pPhrase->bIncr ){ - int bEofSave = pNear->bEof; - fts3EvalRestart(pCsr, pNear, &rc); - while( rc==SQLITE_OK && !pNear->bEof ){ - fts3EvalNextRow(pCsr, pNear, &rc); - if( bEofSave==0 && pNear->iDocid==iDocid ) break; + int bEofSave = pRun->bEof; + fts3EvalRestart(pCsr, pRun, &rc); + while( rc==SQLITE_OK && !pRun->bEof ){ + fts3EvalNextRow(pCsr, pRun, &rc); + if( bEofSave==0 && pRun->iDocid==iDocid ) break; } assert( rc!=SQLITE_OK || pPhrase->bIncr==0 ); + if( rc==SQLITE_OK && pRun->bEof!=bEofSave ){ + rc = FTS_CORRUPT_VTAB; + } } if( bTreeEof ){ - while( rc==SQLITE_OK && !pNear->bEof ){ - fts3EvalNextRow(pCsr, pNear, &rc); + while( rc==SQLITE_OK && !pRun->bEof ){ + fts3EvalNextRow(pCsr, pRun, &rc); } } if( rc!=SQLITE_OK ) return rc; @@ -166163,7 +200059,7 @@ SQLITE_PRIVATE int sqlite3Fts3EvalPhrasePoslist( (pIter >= (pPh->doclist.aAll + pPh->doclist.nAll)); while( (pIter==0 || DOCID_CMP(iDocid, pCsr->iPrevId)<0 ) && bEof==0 ){ sqlite3Fts3DoclistNext( - bDescDoclist, pPh->doclist.aAll, pPh->doclist.nAll, + bDescDoclist, pPh->doclist.aAll, pPh->doclist.nAll, &pIter, &iDocid, &bEof ); } @@ -166172,7 +200068,7 @@ SQLITE_PRIVATE int sqlite3Fts3EvalPhrasePoslist( while( (pIter==0 || DOCID_CMP(iDocid, pCsr->iPrevId)>0 ) && bEof==0 ){ int dummy; sqlite3Fts3DoclistPrev( - bDescDoclist, pPh->doclist.aAll, pPh->doclist.nAll, + bDescDoclist, pPh->doclist.aAll, pPh->doclist.nAll, &pIter, &iDocid, &dummy, &bEof ); } @@ -166240,7 +200136,7 @@ SQLITE_PRIVATE int sqlite3Fts3Corrupt(){ } #endif -#if !SQLITE_CORE +#if !defined(SQLITE_CORE) /* ** Initialize API pointer table, if required. */ @@ -166248,7 +200144,7 @@ SQLITE_PRIVATE int sqlite3Fts3Corrupt(){ __declspec(dllexport) #endif SQLITE_API int sqlite3_fts3_init( - sqlite3 *db, + sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi ){ @@ -166342,11 +200238,11 @@ static int fts3auxConnectMethod( */ if( argc!=4 && argc!=5 ) goto bad_args; - zDb = argv[1]; + zDb = argv[1]; nDb = (int)strlen(zDb); if( argc==5 ){ if( nDb==4 && 0==sqlite3_strnicmp("temp", zDb, 4) ){ - zDb = argv[3]; + zDb = argv[3]; nDb = (int)strlen(zDb); zFts3 = argv[4]; }else{ @@ -166410,7 +200306,7 @@ static int fts3auxDisconnectMethod(sqlite3_vtab *pVtab){ ** xBestIndex - Analyze a WHERE and ORDER BY clause. */ static int fts3auxBestIndexMethod( - sqlite3_vtab *pVTab, + sqlite3_vtab *pVTab, sqlite3_index_info *pInfo ){ int i; @@ -166423,14 +200319,14 @@ static int fts3auxBestIndexMethod( UNUSED_PARAMETER(pVTab); /* This vtab delivers always results in "ORDER BY term ASC" order. */ - if( pInfo->nOrderBy==1 - && pInfo->aOrderBy[0].iColumn==0 + if( pInfo->nOrderBy==1 + && pInfo->aOrderBy[0].iColumn==0 && pInfo->aOrderBy[0].desc==0 ){ pInfo->orderByConsumed = 1; } - /* Search for equality and range constraints on the "term" column. + /* Search for equality and range constraints on the "term" column. ** And equality constraints on the hidden "languageid" column. */ for(i=0; inConstraint; i++){ if( pInfo->aConstraint[i].usable ){ @@ -166511,11 +200407,11 @@ static int fts3auxCloseMethod(sqlite3_vtab_cursor *pCursor){ static int fts3auxGrowStatArray(Fts3auxCursor *pCsr, int nSize){ if( nSize>pCsr->nStat ){ struct Fts3auxColstats *aNew; - aNew = (struct Fts3auxColstats *)sqlite3_realloc64(pCsr->aStat, + aNew = (struct Fts3auxColstats *)sqlite3_realloc64(pCsr->aStat, sizeof(struct Fts3auxColstats) * nSize ); if( aNew==0 ) return SQLITE_NOMEM; - memset(&aNew[pCsr->nStat], 0, + memset(&aNew[pCsr->nStat], 0, sizeof(struct Fts3auxColstats) * (nSize - pCsr->nStat) ); pCsr->aStat = aNew; @@ -166560,6 +200456,7 @@ static int fts3auxNextMethod(sqlite3_vtab_cursor *pCursor){ if( fts3auxGrowStatArray(pCsr, 2) ) return SQLITE_NOMEM; memset(pCsr->aStat, 0, sizeof(struct Fts3auxColstats) * pCsr->nStat); iCol = 0; + rc = SQLITE_OK; while( iaStat[1].nDoc++; } eState = 2; - /* fall through */ + /* no break */ deliberate_fall_through case 2: if( v==0 ){ /* 0x00. Next integer will be a docid. */ @@ -166603,6 +200500,10 @@ static int fts3auxNextMethod(sqlite3_vtab_cursor *pCursor){ /* State 3. The integer just read is a column number. */ default: assert( eState==3 ); iCol = (int)v; + if( iCol<1 || iCol>(pFts3->nColumn+1) ){ + rc = SQLITE_CORRUPT_VTAB; + break; + } if( fts3auxGrowStatArray(pCsr, iCol+2) ) return SQLITE_NOMEM; pCsr->aStat[iCol+1].nDoc++; eState = 2; @@ -166611,7 +200512,6 @@ static int fts3auxNextMethod(sqlite3_vtab_cursor *pCursor){ } pCsr->iCol = 0; - rc = SQLITE_OK; }else{ pCsr->isEof = 1; } @@ -166669,6 +200569,7 @@ static int fts3auxFilterMethod( sqlite3Fts3SegReaderFinish(&pCsr->csr); sqlite3_free((void *)pCsr->filter.zTerm); sqlite3_free(pCsr->aStat); + sqlite3_free(pCsr->zStop); memset(&pCsr->csr, 0, ((u8*)&pCsr[1]) - (u8*)&pCsr->csr); pCsr->filter.flags = FTS3_SEGMENT_REQUIRE_POS|FTS3_SEGMENT_IGNORE_EMPTY; @@ -166689,7 +200590,7 @@ static int fts3auxFilterMethod( if( pCsr->zStop==0 ) return SQLITE_NOMEM; pCsr->nStop = (int)strlen(pCsr->zStop); } - + if( iLangid>=0 ){ iLangVal = sqlite3_value_int(apVal[iLangid]); @@ -166803,7 +200704,8 @@ SQLITE_PRIVATE int sqlite3Fts3InitAux(sqlite3 *db){ 0, /* xSavepoint */ 0, /* xRelease */ 0, /* xRollbackTo */ - 0 /* xShadowName */ + 0, /* xShadowName */ + 0 /* xIntegrity */ }; int rc; /* Return code */ @@ -166828,15 +200730,15 @@ SQLITE_PRIVATE int sqlite3Fts3InitAux(sqlite3 *db){ ****************************************************************************** ** ** This module contains code that implements a parser for fts3 query strings -** (the right-hand argument to the MATCH operator). Because the supported +** (the right-hand argument to the MATCH operator). Because the supported ** syntax is relatively simple, the whole tokenizer/parser system is -** hand-coded. +** hand-coded. */ /* #include "fts3Int.h" */ #if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS3) /* -** By default, this module parses the legacy syntax that has been +** By default, this module parses the legacy syntax that has been ** traditionally used by fts3. Or, if SQLITE_ENABLE_FTS3_PARENTHESIS ** is defined, then it uses the new syntax. The differences between ** the new and the old syntaxes are: @@ -166845,7 +200747,7 @@ SQLITE_PRIVATE int sqlite3Fts3InitAux(sqlite3 *db){ ** ** b) The new syntax supports the AND and NOT operators. The old does not. ** -** c) The old syntax supports the "-" token qualifier. This is not +** c) The old syntax supports the "-" token qualifier. This is not ** supported by the new syntax (it is replaced by the NOT operator). ** ** d) When using the old syntax, the OR operator has a greater precedence @@ -166854,7 +200756,7 @@ SQLITE_PRIVATE int sqlite3Fts3InitAux(sqlite3 *db){ ** ** If compiled with SQLITE_TEST defined, then this module exports the ** symbol "int sqlite3_fts3_enable_parentheses". Setting this variable -** to zero causes the module to use the old syntax. If it is set to +** to zero causes the module to use the old syntax. If it is set to ** non-zero the new syntax is activated. This is so both syntaxes can ** be tested using a single build of testfixture. ** @@ -166883,7 +200785,7 @@ SQLITE_PRIVATE int sqlite3Fts3InitAux(sqlite3 *db){ #ifdef SQLITE_TEST SQLITE_API int sqlite3_fts3_enable_parentheses = 0; #else -# ifdef SQLITE_ENABLE_FTS3_PARENTHESIS +# ifdef SQLITE_ENABLE_FTS3_PARENTHESIS # define sqlite3_fts3_enable_parentheses 1 # else # define sqlite3_fts3_enable_parentheses 0 @@ -166901,7 +200803,7 @@ SQLITE_API int sqlite3_fts3_enable_parentheses = 0; /* ** isNot: ** This variable is used by function getNextNode(). When getNextNode() is -** called, it sets ParseContext.isNot to true if the 'next node' is a +** called, it sets ParseContext.isNot to true if the 'next node' is a ** FTSQUERY_PHRASE with a unary "-" attached to it. i.e. "mysql" in the ** FTS3 query "sqlite -mysql". Otherwise, ParseContext.isNot is set to ** zero. @@ -166920,7 +200822,7 @@ struct ParseContext { }; /* -** This function is equivalent to the standard isspace() function. +** This function is equivalent to the standard isspace() function. ** ** The standard isspace() can be awkward to use safely, because although it ** is defined to accept an argument of type int, its behavior when passed @@ -166936,10 +200838,10 @@ static int fts3isspace(char c){ /* ** Allocate nByte bytes of memory using sqlite3_malloc(). If successful, -** zero the memory before returning a pointer to it. If unsuccessful, +** zero the memory before returning a pointer to it. If unsuccessful, ** return NULL. */ -static void *fts3MallocZero(sqlite3_int64 nByte){ +SQLITE_PRIVATE void *sqlite3Fts3MallocZero(sqlite3_int64 nByte){ void *pRet = sqlite3_malloc64(nByte); if( pRet ) memset(pRet, 0, nByte); return pRet; @@ -166978,13 +200880,30 @@ SQLITE_PRIVATE int sqlite3Fts3OpenTokenizer( */ static int fts3ExprParse(ParseContext *, const char *, int, Fts3Expr **, int *); +/* +** Search buffer z[], size n, for a '"' character. Or, if enable_parenthesis +** is defined, search for '(' and ')' as well. Return the index of the first +** such character in the buffer. If there is no such character, return -1. +*/ +static int findBarredChar(const char *z, int n){ + int ii; + for(ii=0; iiiLangid, z, i, &pCursor); + *pnConsumed = n; + rc = sqlite3Fts3OpenTokenizer(pTokenizer, pParse->iLangid, z, n, &pCursor); if( rc==SQLITE_OK ){ const char *zToken; int nToken = 0, iStart = 0, iEnd = 0, iPosition = 0; @@ -167019,8 +200931,19 @@ static int getNextToken( rc = pModule->xNext(pCursor, &zToken, &nToken, &iStart, &iEnd, &iPosition); if( rc==SQLITE_OK ){ - nByte = sizeof(Fts3Expr) + sizeof(Fts3Phrase) + nToken; - pRet = (Fts3Expr *)fts3MallocZero(nByte); + /* Check that this tokenization did not gobble up any " characters. Or, + ** if enable_parenthesis is true, that it did not gobble up any + ** open or close parenthesis characters either. If it did, call + ** getNextToken() again, but pass only that part of the input buffer + ** up to the first such character. */ + int iBarred = findBarredChar(z, iEnd); + if( iBarred>=0 ){ + pModule->xClose(pCursor); + return getNextToken(pParse, iCol, z, iBarred, ppExpr, pnConsumed); + } + + nByte = sizeof(Fts3Expr) + SZ_FTS3PHRASE(1) + nToken; + pRet = (Fts3Expr *)sqlite3Fts3MallocZero(nByte); if( !pRet ){ rc = SQLITE_NOMEM; }else{ @@ -167029,7 +200952,7 @@ static int getNextToken( pRet->pPhrase->nToken = 1; pRet->pPhrase->iColumn = iCol; pRet->pPhrase->aToken[0].n = nToken; - pRet->pPhrase->aToken[0].z = (char *)&pRet->pPhrase[1]; + pRet->pPhrase->aToken[0].z = (char*)&pRet->pPhrase->aToken[1]; memcpy(pRet->pPhrase->aToken[0].z, zToken, nToken); if( iEnd0 && z[iStart-1]=='-' + if( !sqlite3_fts3_enable_parentheses + && iStart>0 && z[iStart-1]=='-' ){ pParse->isNot = 1; iStart--; @@ -167053,13 +200976,17 @@ static int getNextToken( } *pnConsumed = iEnd; - }else if( i && rc==SQLITE_DONE ){ + }else if( n && rc==SQLITE_DONE ){ + int iBarred = findBarredChar(z, n); + if( iBarred>=0 ){ + *pnConsumed = iBarred; + } rc = SQLITE_OK; } pModule->xClose(pCursor); } - + *ppExpr = pRet; return rc; } @@ -167081,7 +201008,7 @@ static void *fts3ReallocOrFree(void *pOrig, sqlite3_int64 nNew){ ** Buffer zInput, length nInput, contains the contents of a quoted string ** that appeared as part of an fts3 query expression. Neither quote character ** is included in the buffer. This function attempts to tokenize the entire -** input buffer and create an Fts3Expr structure of type FTSQUERY_PHRASE +** input buffer and create an Fts3Expr structure of type FTSQUERY_PHRASE ** containing the results. ** ** If successful, SQLITE_OK is returned and *ppExpr set to point at the @@ -167100,13 +201027,13 @@ static int getNextString( Fts3Expr *p = 0; sqlite3_tokenizer_cursor *pCursor = 0; char *zTemp = 0; - int nTemp = 0; + i64 nTemp = 0; - const int nSpace = sizeof(Fts3Expr) + sizeof(Fts3Phrase); + const int nSpace = sizeof(Fts3Expr) + SZ_FTS3PHRASE(1); int nToken = 0; /* The final Fts3Expr data structure, including the Fts3Phrase, - ** Fts3PhraseToken structures token buffers are all stored as a single + ** Fts3PhraseToken structures token buffers are all stored as a single ** allocation so that the expression can be freed with a single call to ** sqlite3_free(). Setting this up requires a two pass approach. ** @@ -167115,7 +201042,7 @@ static int getNextString( ** to assemble data in two dynamic buffers: ** ** Buffer p: Points to the Fts3Expr structure, followed by the Fts3Phrase - ** structure, followed by the array of Fts3PhraseToken + ** structure, followed by the array of Fts3PhraseToken ** structures. This pass only populates the Fts3PhraseToken array. ** ** Buffer zTemp: Contains copies of all tokens. @@ -167136,10 +201063,11 @@ static int getNextString( Fts3PhraseToken *pToken; p = fts3ReallocOrFree(p, nSpace + ii*sizeof(Fts3PhraseToken)); - if( !p ) goto no_mem; - zTemp = fts3ReallocOrFree(zTemp, nTemp + nByte); - if( !zTemp ) goto no_mem; + if( !zTemp || !p ){ + rc = SQLITE_NOMEM; + goto getnextstring_out; + } assert( nToken==ii ); pToken = &((Fts3Phrase *)(&p[1]))->aToken[ii]; @@ -167154,9 +201082,6 @@ static int getNextString( nToken = ii+1; } } - - pModule->xClose(pCursor); - pCursor = 0; } if( rc==SQLITE_DONE ){ @@ -167164,7 +201089,10 @@ static int getNextString( char *zBuf = 0; p = fts3ReallocOrFree(p, nSpace + nToken*sizeof(Fts3PhraseToken) + nTemp); - if( !p ) goto no_mem; + if( !p ){ + rc = SQLITE_NOMEM; + goto getnextstring_out; + } memset(p, 0, (char *)&(((Fts3Phrase *)&p[1])->aToken[0])-(char *)p); p->eType = FTSQUERY_PHRASE; p->pPhrase = (Fts3Phrase *)&p[1]; @@ -167172,11 +201100,9 @@ static int getNextString( p->pPhrase->nToken = nToken; zBuf = (char *)&p->pPhrase->aToken[nToken]; + assert( nTemp==0 || zTemp ); if( zTemp ){ memcpy(zBuf, zTemp, nTemp); - sqlite3_free(zTemp); - }else{ - assert( nTemp==0 ); } for(jj=0; jjpPhrase->nToken; jj++){ @@ -167186,21 +201112,21 @@ static int getNextString( rc = SQLITE_OK; } - *ppExpr = p; - return rc; -no_mem: - + getnextstring_out: if( pCursor ){ pModule->xClose(pCursor); } sqlite3_free(zTemp); - sqlite3_free(p); - *ppExpr = 0; - return SQLITE_NOMEM; + if( rc!=SQLITE_OK ){ + sqlite3_free(p); + p = 0; + } + *ppExpr = p; + return rc; } /* -** The output variable *ppExpr is populated with an allocated Fts3Expr +** The output variable *ppExpr is populated with an allocated Fts3Expr ** structure, or set to 0 if the end of the input buffer is reached. ** ** Returns an SQLite error code. SQLITE_OK if everything works, SQLITE_NOMEM @@ -167236,7 +201162,7 @@ static int getNextNode( pParse->isNot = 0; /* Skip over any whitespace before checking for a keyword, an open or - ** close bracket, or a quoted string. + ** close bracket, or a quoted string. */ while( nInput>0 && fts3isspace(*zInput) ){ nInput--; @@ -167263,22 +201189,20 @@ static int getNextNode( if( pKey->eType==FTSQUERY_NEAR ){ assert( nKey==4 ); if( zInput[4]=='/' && zInput[5]>='0' && zInput[5]<='9' ){ - nNear = 0; - for(nKey=5; zInput[nKey]>='0' && zInput[nKey]<='9'; nKey++){ - nNear = nNear * 10 + (zInput[nKey] - '0'); - } + nKey += 1+sqlite3Fts3ReadInt(&zInput[nKey+1], &nNear); + if( nNear>=1000000000 ) nNear = 1000000000; } } /* At this point this is probably a keyword. But for that to be true, ** the next byte must contain either whitespace, an open or close - ** parenthesis, a quote character, or EOF. + ** parenthesis, a quote character, or EOF. */ cNext = zInput[nKey]; - if( fts3isspace(cNext) + if( fts3isspace(cNext) || cNext=='"' || cNext=='(' || cNext==')' || cNext==0 ){ - pRet = (Fts3Expr *)fts3MallocZero(sizeof(Fts3Expr)); + pRet = (Fts3Expr *)sqlite3Fts3MallocZero(sizeof(Fts3Expr)); if( !pRet ){ return SQLITE_NOMEM; } @@ -167313,6 +201237,11 @@ static int getNextNode( if( *zInput=='(' ){ int nConsumed = 0; pParse->nNest++; +#if !defined(SQLITE_MAX_EXPR_DEPTH) + if( pParse->nNest>1000 ) return SQLITE_ERROR; +#elif SQLITE_MAX_EXPR_DEPTH>0 + if( pParse->nNest>SQLITE_MAX_EXPR_DEPTH ) return SQLITE_ERROR; +#endif rc = fts3ExprParse(pParse, zInput+1, nInput-1, ppExpr, &nConsumed); *pnConsumed = (int)(zInput - z) + 1 + nConsumed; return rc; @@ -167324,15 +201253,15 @@ static int getNextNode( } } - /* If control flows to this point, this must be a regular token, or + /* If control flows to this point, this must be a regular token, or ** the end of the input. Read a regular token using the sqlite3_tokenizer ** interface. Before doing so, figure out if there is an explicit - ** column specifier for the token. + ** column specifier for the token. ** ** TODO: Strangely, it is not possible to associate a column specifier ** with a quoted phrase, only with a single token. Not sure if this was ** an implementation artifact or an intentional decision when fts3 was - ** first implemented. Whichever it was, this module duplicates the + ** first implemented. Whichever it was, this module duplicates the ** limitation. */ iCol = pParse->iDefaultCol; @@ -167340,8 +201269,8 @@ static int getNextNode( for(ii=0; iinCol; ii++){ const char *zStr = pParse->azCol[ii]; int nStr = (int)strlen(zStr); - if( nInput>nStr && zInput[nStr]==':' - && sqlite3_strnicmp(zStr, zInput, nStr)==0 + if( nInput>nStr && zInput[nStr]==':' + && sqlite3_strnicmp(zStr, zInput, nStr)==0 ){ iCol = ii; iColLen = (int)((zInput - z) + nStr + 1); @@ -167386,7 +201315,7 @@ static int opPrecedence(Fts3Expr *p){ } /* -** Argument ppHead contains a pointer to the current head of a query +** Argument ppHead contains a pointer to the current head of a query ** expression tree being parsed. pPrev is the expression node most recently ** inserted into the tree. This function adds pNew, which is always a binary ** operator node, into the expression tree based on the relative precedence @@ -167416,7 +201345,7 @@ static void insertBinaryOperator( /* ** Parse the fts3 query expression found in buffer z, length n. This function -** returns either when the end of the buffer is reached or an unmatched +** returns either when the end of the buffer is reached or an unmatched ** closing bracket - ')' - is encountered. ** ** If successful, SQLITE_OK is returned, *ppExpr is set to point to the @@ -167448,11 +201377,11 @@ static int fts3ExprParse( if( p ){ int isPhrase; - if( !sqlite3_fts3_enable_parentheses - && p->eType==FTSQUERY_PHRASE && pParse->isNot + if( !sqlite3_fts3_enable_parentheses + && p->eType==FTSQUERY_PHRASE && pParse->isNot ){ /* Create an implicit NOT operator. */ - Fts3Expr *pNot = fts3MallocZero(sizeof(Fts3Expr)); + Fts3Expr *pNot = sqlite3Fts3MallocZero(sizeof(Fts3Expr)); if( !pNot ){ sqlite3Fts3ExprFree(p); rc = SQLITE_NOMEM; @@ -167473,7 +201402,7 @@ static int fts3ExprParse( /* The isRequirePhrase variable is set to true if a phrase or ** an expression contained in parenthesis is required. If a - ** binary operator (AND, OR, NOT or NEAR) is encounted when + ** binary operator (AND, OR, NOT or NEAR) is encountered when ** isRequirePhrase is set, this is a syntax error. */ if( !isPhrase && isRequirePhrase ){ @@ -167486,7 +201415,7 @@ static int fts3ExprParse( /* Insert an implicit AND operator. */ Fts3Expr *pAnd; assert( pRet && pPrev ); - pAnd = fts3MallocZero(sizeof(Fts3Expr)); + pAnd = sqlite3Fts3MallocZero(sizeof(Fts3Expr)); if( !pAnd ){ sqlite3Fts3ExprFree(p); rc = SQLITE_NOMEM; @@ -167570,13 +201499,13 @@ static int fts3ExprParse( } /* -** Return SQLITE_ERROR if the maximum depth of the expression tree passed +** Return SQLITE_ERROR if the maximum depth of the expression tree passed ** as the only argument is more than nMaxDepth. */ static int fts3ExprCheckDepth(Fts3Expr *p, int nMaxDepth){ int rc = SQLITE_OK; if( p ){ - if( nMaxDepth<0 ){ + if( nMaxDepth<0 ){ rc = SQLITE_TOOBIG; }else{ rc = fts3ExprCheckDepth(p->pLeft, nMaxDepth-1); @@ -167591,12 +201520,12 @@ static int fts3ExprCheckDepth(Fts3Expr *p, int nMaxDepth){ /* ** This function attempts to transform the expression tree at (*pp) to ** an equivalent but more balanced form. The tree is modified in place. -** If successful, SQLITE_OK is returned and (*pp) set to point to the -** new root expression node. +** If successful, SQLITE_OK is returned and (*pp) set to point to the +** new root expression node. ** ** nMaxDepth is the maximum allowable depth of the balanced sub-tree. ** -** Otherwise, if an error occurs, an SQLite error code is returned and +** Otherwise, if an error occurs, an SQLite error code is returned and ** expression (*pp) freed. */ static int fts3ExprBalance(Fts3Expr **pp, int nMaxDepth){ @@ -167711,7 +201640,7 @@ static int fts3ExprBalance(Fts3Expr **pp, int nMaxDepth){ } pRoot = p; }else{ - /* An error occurred. Delete the contents of the apLeaf[] array + /* An error occurred. Delete the contents of the apLeaf[] array ** and pFree list. Everything else is cleaned up by the call to ** sqlite3Fts3ExprFree(pRoot) below. */ Fts3Expr *pDel; @@ -167753,7 +201682,7 @@ static int fts3ExprBalance(Fts3Expr **pp, int nMaxDepth){ } } } - + if( rc!=SQLITE_OK ){ sqlite3Fts3ExprFree(pRoot); pRoot = 0; @@ -167767,9 +201696,9 @@ static int fts3ExprBalance(Fts3Expr **pp, int nMaxDepth){ ** differences: ** ** 1. It does not do expression rebalancing. -** 2. It does not check that the expression does not exceed the +** 2. It does not check that the expression does not exceed the ** maximum allowable depth. -** 3. Even if it fails, *ppExpr may still be set to point to an +** 3. Even if it fails, *ppExpr may still be set to point to an ** expression tree. It should be deleted using sqlite3Fts3ExprFree() ** in this case. */ @@ -167808,7 +201737,7 @@ static int fts3ExprParseUnbalanced( if( rc==SQLITE_OK && sParse.nNest ){ rc = SQLITE_ERROR; } - + return rc; } @@ -167827,7 +201756,7 @@ static int fts3ExprParseUnbalanced( ** The first parameter, pTokenizer, is passed the fts3 tokenizer module to ** use to normalize query tokens while parsing the expression. The azCol[] ** array, which is assumed to contain nCol entries, should contain the names -** of each column in the target fts3 table, in order from left to right. +** of each column in the target fts3 table, in order from left to right. ** Column names must be nul-terminated strings. ** ** The iDefaultCol parameter should be passed the index of the table column @@ -167850,7 +201779,7 @@ SQLITE_PRIVATE int sqlite3Fts3ExprParse( int rc = fts3ExprParseUnbalanced( pTokenizer, iLangid, azCol, bFts4, nCol, iDefaultCol, z, n, ppExpr ); - + /* Rebalance the expression. And check that its depth does not exceed ** SQLITE_FTS3_MAX_EXPR_DEPTH. */ if( rc==SQLITE_OK && *ppExpr ){ @@ -167865,7 +201794,7 @@ SQLITE_PRIVATE int sqlite3Fts3ExprParse( *ppExpr = 0; if( rc==SQLITE_TOOBIG ){ sqlite3Fts3ErrMsg(pzErr, - "FTS expression tree is too large (maximum depth %d)", + "FTS expression tree is too large (maximum depth %d)", SQLITE_FTS3_MAX_EXPR_DEPTH ); rc = SQLITE_ERROR; @@ -167927,11 +201856,11 @@ SQLITE_PRIVATE void sqlite3Fts3ExprFree(Fts3Expr *pDel){ /* ** Return a pointer to a buffer containing a text representation of the ** expression passed as the first argument. The buffer is obtained from -** sqlite3_malloc(). It is the responsibility of the caller to use +** sqlite3_malloc(). It is the responsibility of the caller to use ** sqlite3_free() to release the memory. If an OOM condition is encountered, ** NULL is returned. ** -** If the second argument is not NULL, then its contents are prepended to +** If the second argument is not NULL, then its contents are prepended to ** the returned expression text and then freed using sqlite3_free(). */ static char *exprToString(Fts3Expr *pExpr, char *zBuf){ @@ -167945,7 +201874,7 @@ static char *exprToString(Fts3Expr *pExpr, char *zBuf){ zBuf = sqlite3_mprintf( "%zPHRASE %d 0", zBuf, pPhrase->iColumn); for(i=0; zBuf && inToken; i++){ - zBuf = sqlite3_mprintf("%z %.*s%s", zBuf, + zBuf = sqlite3_mprintf("%z %.*s%s", zBuf, pPhrase->aToken[i].n, pPhrase->aToken[i].z, (pPhrase->aToken[i].isPrefix?"+":"") ); @@ -167978,7 +201907,7 @@ static char *exprToString(Fts3Expr *pExpr, char *zBuf){ } /* -** This is the implementation of a scalar SQL function used to test the +** This is the implementation of a scalar SQL function used to test the ** expression parser. It should be called as follows: ** ** fts3_exprtest(, , , ...); @@ -168011,7 +201940,7 @@ static void fts3ExprTestCommon( char *zErr = 0; if( argc<3 ){ - sqlite3_result_error(context, + sqlite3_result_error(context, "Usage: fts3_exprtest(tokenizer, expr, col1, ...", -1 ); return; @@ -168055,7 +201984,6 @@ static void fts3ExprTestCommon( } if( rc!=SQLITE_OK && rc!=SQLITE_NOMEM ){ - sqlite3Fts3ExprFree(pExpr); sqlite3_result_error(context, "Error parsing expression", -1); }else if( rc==SQLITE_NOMEM || !(zBuf = exprToString(pExpr, 0)) ){ sqlite3_result_error_nomem(context); @@ -168089,15 +202017,15 @@ static void fts3ExprTestRebalance( } /* -** Register the query expression parser test function fts3_exprtest() -** with database connection db. +** Register the query expression parser test function fts3_exprtest() +** with database connection db. */ SQLITE_PRIVATE int sqlite3Fts3ExprInitTestInterface(sqlite3 *db, Fts3Hash *pHash){ int rc = sqlite3_create_function( db, "fts3_exprtest", -1, SQLITE_UTF8, (void*)pHash, fts3ExprTest, 0, 0 ); if( rc==SQLITE_OK ){ - rc = sqlite3_create_function(db, "fts3_exprtest_rebalance", + rc = sqlite3_create_function(db, "fts3_exprtest_rebalance", -1, SQLITE_UTF8, (void*)pHash, fts3ExprTestRebalance, 0, 0 ); } @@ -168161,8 +202089,8 @@ static void fts3HashFree(void *p){ ** fields of the Hash structure. ** ** "pNew" is a pointer to the hash table that is to be initialized. -** keyClass is one of the constants -** FTS3_HASH_BINARY or FTS3_HASH_STRING. The value of keyClass +** keyClass is one of the constants +** FTS3_HASH_BINARY or FTS3_HASH_STRING. The value of keyClass ** determines what kind of key the hash table will use. "copyKey" is ** true if the hash table should make its own private copy of keys and ** false if it should just use the supplied pointer. @@ -168239,7 +202167,7 @@ static int fts3BinCompare(const void *pKey1, int n1, const void *pKey2, int n2){ /* ** Return a pointer to the appropriate hash function given the key class. ** -** The C syntax in this function definition may be unfamilar to some +** The C syntax in this function definition may be unfamilar to some ** programmers, so we provide the following additional explanation: ** ** The name of the function is "ftsHashFunction". The function takes a @@ -168298,8 +202226,8 @@ static void fts3HashInsertElement( } -/* Resize the hash table so that it cantains "new_size" buckets. -** "new_size" must be a power of 2. The hash table might fail +/* Resize the hash table so that it contains "new_size" buckets. +** "new_size" must be a power of 2. The hash table might fail ** to resize if sqliteMalloc() fails. ** ** Return non-zero if a memory allocation error occurs. @@ -168344,7 +202272,7 @@ static Fts3HashElem *fts3FindElementByHash( count = pEntry->count; xCompare = ftsCompareFunction(pH->keyClass); while( count-- && elem ){ - if( (*xCompare)(elem->pKey,elem->nKey,pKey,nKey)==0 ){ + if( (*xCompare)(elem->pKey,elem->nKey,pKey,nKey)==0 ){ return elem; } elem = elem->next; @@ -168363,7 +202291,7 @@ static void fts3RemoveElementByHash( ){ struct _fts3ht *pEntry; if( elem->prev ){ - elem->prev->next = elem->next; + elem->prev->next = elem->next; }else{ pH->first = elem->next; } @@ -168391,8 +202319,8 @@ static void fts3RemoveElementByHash( } SQLITE_PRIVATE Fts3HashElem *sqlite3Fts3HashFindElem( - const Fts3Hash *pH, - const void *pKey, + const Fts3Hash *pH, + const void *pKey, int nKey ){ int h; /* A hash on key */ @@ -168406,7 +202334,7 @@ SQLITE_PRIVATE Fts3HashElem *sqlite3Fts3HashFindElem( return fts3FindElementByHash(pH,pKey,nKey, h & (pH->htsize-1)); } -/* +/* ** Attempt to locate an element of the hash table pH with a key ** that matches pKey,nKey. Return the data for this element if it is ** found, or NULL if there is no match. @@ -168580,7 +202508,7 @@ static int porterDestroy(sqlite3_tokenizer *pTokenizer){ /* ** Prepare to begin tokenizing a particular string. The input ** string to be tokenized is zInput[0..nInput-1]. A cursor -** used to incrementally tokenize this string is returned in +** used to incrementally tokenize this string is returned in ** *ppCursor. */ static int porterOpen( @@ -168633,7 +202561,7 @@ static const char cType[] = { /* ** isConsonant() and isVowel() determine if their first character in ** the string they point to is a consonant or a vowel, according -** to Porter ruls. +** to Porter ruls. ** ** A consonate is any letter other than 'a', 'e', 'i', 'o', or 'u'. ** 'Y' is a consonant unless it follows another consonant, @@ -168753,11 +202681,11 @@ static int star_oh(const char *z){ /* ** If the word ends with zFrom and xCond() is true for the stem -** of the word that preceeds the zFrom ending, then change the +** of the word that precedes the zFrom ending, then change the ** ending to zTo. ** ** The input word *pz and zFrom are both in reverse order. zTo -** is in normal order. +** is in normal order. ** ** Return TRUE if zFrom matches. Return FALSE if zFrom does not ** match. Not that TRUE is returned even if xCond() fails and @@ -168826,9 +202754,9 @@ static void copy_stemmer(const char *zIn, int nIn, char *zOut, int *pnOut){ ** word contains digits, 3 bytes are taken from the beginning and ** 3 bytes from the end. For long words without digits, 10 bytes ** are taken from each end. US-ASCII case folding still applies. -** -** If the input word contains not digits but does characters not -** in [a-zA-Z] then no stemming is attempted and this routine just +** +** If the input word contains not digits but does characters not +** in [a-zA-Z] then no stemming is attempted and this routine just ** copies the input into the input into the output with US-ASCII ** case folding. ** @@ -168873,11 +202801,11 @@ static void porter_stemmer(const char *zIn, int nIn, char *zOut, int *pnOut){ } } - /* Step 1b */ + /* Step 1b */ z2 = z; if( stem(&z, "dee", "ee", m_gt_0) ){ /* Do nothing. The work was all in the test */ - }else if( + }else if( (stem(&z, "gni", "", hasVowel) || stem(&z, "de", "", hasVowel)) && z!=z2 ){ @@ -168916,7 +202844,7 @@ static void porter_stemmer(const char *zIn, int nIn, char *zOut, int *pnOut){ stem(&z, "igol", "log", m_gt_0); break; case 'l': - if( !stem(&z, "ilb", "ble", m_gt_0) + if( !stem(&z, "ilb", "ble", m_gt_0) && !stem(&z, "illa", "al", m_gt_0) && !stem(&z, "iltne", "ent", m_gt_0) && !stem(&z, "ile", "e", m_gt_0) @@ -169118,7 +203046,7 @@ static int porterNext( if( n>c->nAllocated ){ char *pNew; c->nAllocated = n+20; - pNew = sqlite3_realloc(c->zToken, c->nAllocated); + pNew = sqlite3_realloc64(c->zToken, c->nAllocated); if( !pNew ) return SQLITE_NOMEM; c->zToken = pNew; } @@ -169204,7 +203132,7 @@ static int fts3TokenizerEnabled(sqlite3_context *context){ } /* -** Implementation of the SQL scalar function for accessing the underlying +** Implementation of the SQL scalar function for accessing the underlying ** hash table. This function may be called as follows: ** ** SELECT (); @@ -169376,7 +203304,7 @@ SQLITE_PRIVATE int sqlite3Fts3InitTokenizer( if( rc!=SQLITE_OK ){ sqlite3Fts3ErrMsg(pzErr, "unknown tokenizer"); }else{ - (*ppTok)->pModule = m; + (*ppTok)->pModule = m; } sqlite3_free((void *)aArg); } @@ -169388,15 +203316,11 @@ SQLITE_PRIVATE int sqlite3Fts3InitTokenizer( #ifdef SQLITE_TEST -#if defined(INCLUDE_SQLITE_TCL_H) -# include "sqlite_tcl.h" -#else -# include "tcl.h" -#endif +#include "tclsqlite.h" /* #include */ /* -** Implementation of a special SQL scalar function for testing tokenizers +** Implementation of a special SQL scalar function for testing tokenizers ** designed to be used in concert with the Tcl testing framework. This ** function must be called with two or more arguments: ** @@ -169408,9 +203332,9 @@ SQLITE_PRIVATE int sqlite3Fts3InitTokenizer( ** ** The return value is a string that may be interpreted as a Tcl ** list. For each token in the , three elements are -** added to the returned list. The first is the token position, the +** added to the returned list. The first is the token position, the ** second is the token text (folded, stemmed, etc.) and the third is the -** substring of associated with the token. For example, +** substring of associated with the token. For example, ** using the built-in "simple" tokenizer: ** ** SELECT fts_tokenizer_test('simple', 'I don't see how'); @@ -169418,7 +203342,7 @@ SQLITE_PRIVATE int sqlite3Fts3InitTokenizer( ** will return the string: ** ** "{0 i I 1 dont don't 2 see see 3 how how}" -** +** */ static void testFunc( sqlite3_context *context, @@ -169513,8 +203437,8 @@ static void testFunc( static int registerTokenizer( - sqlite3 *db, - char *zName, + sqlite3 *db, + char *zName, const sqlite3_tokenizer_module *p ){ int rc; @@ -169536,8 +203460,8 @@ int registerTokenizer( static int queryTokenizer( - sqlite3 *db, - char *zName, + sqlite3 *db, + char *zName, const sqlite3_tokenizer_module **pp ){ int rc; @@ -169552,7 +203476,9 @@ int queryTokenizer( sqlite3_bind_text(pStmt, 1, zName, -1, SQLITE_STATIC); if( SQLITE_ROW==sqlite3_step(pStmt) ){ - if( sqlite3_column_type(pStmt, 0)==SQLITE_BLOB ){ + if( sqlite3_column_type(pStmt, 0)==SQLITE_BLOB + && sqlite3_column_bytes(pStmt, 0)==sizeof(*pp) + ){ memcpy((void *)pp, sqlite3_column_blob(pStmt, 0), sizeof(*pp)); } } @@ -169620,28 +203546,28 @@ static void intTestFunc( /* ** Set up SQL objects in database db used to access the contents of ** the hash table pointed to by argument pHash. The hash table must -** been initialized to use string keys, and to take a private copy +** been initialized to use string keys, and to take a private copy ** of the key when a value is inserted. i.e. by a call similar to: ** ** sqlite3Fts3HashInit(pHash, FTS3_HASH_STRING, 1); ** ** This function adds a scalar function (see header comment above ** fts3TokenizerFunc() in this file for details) and, if ENABLE_TABLE is -** defined at compilation time, a temporary virtual table (see header -** comment above struct HashTableVtab) to the database schema. Both +** defined at compilation time, a temporary virtual table (see header +** comment above struct HashTableVtab) to the database schema. Both ** provide read/write access to the contents of *pHash. ** ** The third argument to this function, zName, is used as the name ** of both the scalar and, if created, the virtual table. */ SQLITE_PRIVATE int sqlite3Fts3InitHashTable( - sqlite3 *db, - Fts3Hash *pHash, + sqlite3 *db, + Fts3Hash *pHash, const char *zName ){ int rc = SQLITE_OK; void *p = (void *)pHash; - const int any = SQLITE_ANY; + const int any = SQLITE_UTF8|SQLITE_DIRECTONLY; #ifdef SQLITE_TEST char *zTest = 0; @@ -169790,7 +203716,7 @@ static int simpleDestroy(sqlite3_tokenizer *pTokenizer){ /* ** Prepare to begin tokenizing a particular string. The input ** string to be tokenized is pInput[0..nBytes-1]. A cursor -** used to incrementally tokenize this string is returned in +** used to incrementally tokenize this string is returned in ** *ppCursor. */ static int simpleOpen( @@ -169868,7 +203794,7 @@ static int simpleNext( if( n>c->nTokenAllocated ){ char *pNew; c->nTokenAllocated = n+20; - pNew = sqlite3_realloc(c->pToken, c->nTokenAllocated); + pNew = sqlite3_realloc64(c->pToken, c->nTokenAllocated); if( !pNew ) return SQLITE_NOMEM; c->pToken = pNew; } @@ -169945,8 +203871,8 @@ SQLITE_PRIVATE void sqlite3Fts3SimpleTokenizerModule( ** ** input = ** -** The virtual table module tokenizes this , using the FTS3 -** tokenizer specified by the arguments to the CREATE VIRTUAL TABLE +** The virtual table module tokenizes this , using the FTS3 +** tokenizer specified by the arguments to the CREATE VIRTUAL TABLE ** statement and returns one row for each token in the result. With ** fields set as follows: ** @@ -170015,7 +203941,7 @@ static int fts3tokQueryTokenizer( /* ** The second argument, argv[], is an array of pointers to nul-terminated -** strings. This function makes a copy of the array and strings into a +** strings. This function makes a copy of the array and strings into a ** single block of memory. It then dequotes any of the strings that appear ** to be quoted. ** @@ -170071,7 +203997,7 @@ static int fts3tokDequoteArray( ** and xCreate are identical operations. ** ** argv[0]: module name -** argv[1]: database name +** argv[1]: database name ** argv[2]: table name ** argv[3]: first argument (tokenizer name) */ @@ -170108,7 +204034,8 @@ static int fts3tokConnectMethod( assert( (rc==SQLITE_OK)==(pMod!=0) ); if( rc==SQLITE_OK ){ - const char * const *azArg = (const char * const *)&azDequote[1]; + const char * const *azArg = 0; + if( nDequote>1 ) azArg = (const char * const *)&azDequote[1]; rc = pMod->xCreate((nDequote>1 ? nDequote-1 : 0), azArg, &pTok); } @@ -170151,16 +204078,16 @@ static int fts3tokDisconnectMethod(sqlite3_vtab *pVtab){ ** xBestIndex - Analyze a WHERE and ORDER BY clause. */ static int fts3tokBestIndexMethod( - sqlite3_vtab *pVTab, + sqlite3_vtab *pVTab, sqlite3_index_info *pInfo ){ int i; UNUSED_PARAMETER(pVTab); for(i=0; inConstraint; i++){ - if( pInfo->aConstraint[i].usable - && pInfo->aConstraint[i].iColumn==0 - && pInfo->aConstraint[i].op==SQLITE_INDEX_CONSTRAINT_EQ + if( pInfo->aConstraint[i].usable + && pInfo->aConstraint[i].iColumn==0 + && pInfo->aConstraint[i].op==SQLITE_INDEX_CONSTRAINT_EQ ){ pInfo->idxNum = 1; pInfo->aConstraintUsage[i].argvIndex = 1; @@ -170265,12 +204192,12 @@ static int fts3tokFilterMethod( fts3tokResetCursor(pCsr); if( idxNum==1 ){ const char *zByte = (const char *)sqlite3_value_text(apVal[0]); - int nByte = sqlite3_value_bytes(apVal[0]); + sqlite3_int64 nByte = sqlite3_value_bytes(apVal[0]); pCsr->zInput = sqlite3_malloc64(nByte+1); if( pCsr->zInput==0 ){ rc = SQLITE_NOMEM; }else{ - memcpy(pCsr->zInput, zByte, nByte); + if( nByte>0 ) memcpy(pCsr->zInput, zByte, nByte); pCsr->zInput[nByte] = 0; rc = pTab->pMod->xOpen(pTab->pTok, pCsr->zInput, nByte, &pCsr->pCsr); if( rc==SQLITE_OK ){ @@ -170339,7 +204266,7 @@ static int fts3tokRowidMethod( ** Register the fts3tok module with database connection db. Return SQLITE_OK ** if successful or an error code if sqlite3_create_module() fails. */ -SQLITE_PRIVATE int sqlite3Fts3InitTok(sqlite3 *db, Fts3Hash *pHash){ +SQLITE_PRIVATE int sqlite3Fts3InitTok(sqlite3 *db, Fts3Hash *pHash, void(*xDestroy)(void*)){ static const sqlite3_module fts3tok_module = { 0, /* iVersion */ fts3tokConnectMethod, /* xCreate */ @@ -170364,11 +204291,14 @@ SQLITE_PRIVATE int sqlite3Fts3InitTok(sqlite3 *db, Fts3Hash *pHash){ 0, /* xSavepoint */ 0, /* xRelease */ 0, /* xRollbackTo */ - 0 /* xShadowName */ + 0, /* xShadowName */ + 0 /* xIntegrity */ }; int rc; /* Return code */ - rc = sqlite3_create_module(db, "fts3tokenize", &fts3tok_module, (void*)pHash); + rc = sqlite3_create_module_v2( + db, "fts3tokenize", &fts3tok_module, (void*)pHash, xDestroy + ); return rc; } @@ -170391,7 +204321,7 @@ SQLITE_PRIVATE int sqlite3Fts3InitTok(sqlite3 *db, Fts3Hash *pHash){ ** This file is part of the SQLite FTS3 extension module. Specifically, ** this file contains code to insert, update and delete rows from FTS3 ** tables. It also contains code to merge FTS3 b-tree segments. Some -** of the sub-routines used to merge segments are also used by the query +** of the sub-routines used to merge segments are also used by the query ** code in fts3.c. */ @@ -170401,13 +204331,13 @@ SQLITE_PRIVATE int sqlite3Fts3InitTok(sqlite3 *db, Fts3Hash *pHash){ /* #include */ /* #include */ /* #include */ - +/* #include */ #define FTS_MAX_APPENDABLE_HEIGHT 16 /* ** When full-text index nodes are loaded from disk, the buffer that they -** are loaded into has the following number of bytes of padding at the end +** are loaded into has the following number of bytes of padding at the end ** of it. i.e. if a full-text index node is 900 bytes in size, then a buffer ** of 920 bytes is allocated for it. ** @@ -170424,10 +204354,10 @@ SQLITE_PRIVATE int sqlite3Fts3InitTok(sqlite3 *db, Fts3Hash *pHash){ ** method before retrieving all query results (as may happen, for example, ** if a query has a LIMIT clause). ** -** Incremental loading is used for b-tree nodes FTS3_NODE_CHUNK_THRESHOLD +** Incremental loading is used for b-tree nodes FTS3_NODE_CHUNK_THRESHOLD ** bytes and larger. Nodes are loaded in chunks of FTS3_NODE_CHUNKSIZE bytes. -** The code is written so that the hard lower-limit for each of these values -** is 1. Clearly such small values would be inefficient, but can be useful +** The code is written so that the hard lower-limit for each of these values +** is 1. Clearly such small values would be inefficient, but can be useful ** for testing purposes. ** ** If this module is built with SQLITE_TEST defined, these constants may @@ -170440,12 +204370,12 @@ int test_fts3_node_chunk_threshold = (4*1024)*4; # define FTS3_NODE_CHUNKSIZE test_fts3_node_chunksize # define FTS3_NODE_CHUNK_THRESHOLD test_fts3_node_chunk_threshold #else -# define FTS3_NODE_CHUNKSIZE (4*1024) +# define FTS3_NODE_CHUNKSIZE (4*1024) # define FTS3_NODE_CHUNK_THRESHOLD (FTS3_NODE_CHUNKSIZE*4) #endif /* -** The two values that may be meaningfully bound to the :1 parameter in +** The values that may be meaningfully bound to the :1 parameter in ** statements SQL_REPLACE_STAT and SQL_SELECT_STAT. */ #define FTS_STAT_DOCTOTAL 0 @@ -170454,7 +204384,7 @@ int test_fts3_node_chunk_threshold = (4*1024)*4; /* ** If FTS_LOG_MERGES is defined, call sqlite3_log() to report each automatic -** and incremental merge operation that takes place. This is used for +** and incremental merge operation that takes place. This is used for ** debugging FTS only, it should not usually be turned on in production ** systems. */ @@ -170476,9 +204406,9 @@ typedef struct SegmentWriter SegmentWriter; ** incrementally. See function fts3PendingListAppend() for details. */ struct PendingList { - int nData; + sqlite3_int64 nData; char *aData; - int nSpace; + sqlite3_int64 nSpace; sqlite3_int64 iLastDocid; sqlite3_int64 iLastCol; sqlite3_int64 iLastPos; @@ -170540,7 +204470,7 @@ struct Fts3SegReader { char *aDoclist; /* Pointer to doclist of current entry */ int nDoclist; /* Size of doclist in current entry */ - /* The following variables are used by fts3SegReaderNextDocid() to iterate + /* The following variables are used by fts3SegReaderNextDocid() to iterate ** through the current doclist (aDoclist/nDoclist). */ char *pOffsetList; @@ -170585,11 +204515,11 @@ struct SegmentWriter { ** fts3NodeFree() ** ** When a b+tree is written to the database (either as a result of a merge -** or the pending-terms table being flushed), leaves are written into the +** or the pending-terms table being flushed), leaves are written into the ** database file as soon as they are completely populated. The interior of ** the tree is assembled in memory and written out only once all leaves have ** been populated and stored. This is Ok, as the b+-tree fanout is usually -** very large, meaning that the interior of the tree consumes relatively +** very large, meaning that the interior of the tree consumes relatively ** little memory. */ struct SegmentNode { @@ -170610,7 +204540,7 @@ struct SegmentNode { */ #define SQL_DELETE_CONTENT 0 #define SQL_IS_EMPTY 1 -#define SQL_DELETE_ALL_CONTENT 2 +#define SQL_DELETE_ALL_CONTENT 2 #define SQL_DELETE_ALL_SEGMENTS 3 #define SQL_DELETE_ALL_SEGDIR 4 #define SQL_DELETE_ALL_DOCSIZE 5 @@ -170651,6 +204581,24 @@ struct SegmentNode { #define SQL_UPDATE_LEVEL_IDX 38 #define SQL_UPDATE_LEVEL 39 +/* +** Wrapper around sqlite3_prepare_v3() to ensure that SQLITE_PREPARE_FROM_DDL +** is always set. +*/ +SQLITE_PRIVATE int sqlite3Fts3PrepareStmt( + Fts3Table *p, /* Prepare for this connection */ + const char *zSql, /* SQL to prepare */ + int bPersist, /* True to set SQLITE_PREPARE_PERSISTENT */ + int bAllowVtab, /* True to omit SQLITE_PREPARE_NO_VTAB */ + sqlite3_stmt **pp /* OUT: Prepared statement */ +){ + int f = SQLITE_PREPARE_FROM_DDL + |((bAllowVtab==0) ? SQLITE_PREPARE_NO_VTAB : 0) + |(bPersist ? SQLITE_PREPARE_PERSISTENT : 0); + + return sqlite3_prepare_v3(p->db, zSql, -1, f, pp, NULL); +} + /* ** This function is used to obtain an SQLite prepared statement handle ** for the statement identified by the second argument. If successful, @@ -170658,7 +204606,7 @@ struct SegmentNode { ** Otherwise, an SQLite error code is returned and *pp is set to 0. ** ** If argument apVal is not NULL, then it must point to an array with -** at least as many entries as the requested statement has bound +** at least as many entries as the requested statement has bound ** parameters. The values are bound to the statements parameters before ** returning. */ @@ -170682,7 +204630,7 @@ static int fts3SqlStmt( /* 10 */ "SELECT coalesce((SELECT max(blockid) FROM %Q.'%q_segments') + 1, 1)", /* 11 */ "REPLACE INTO %Q.'%q_segdir' VALUES(?,?,?,?,?,?)", - /* Return segments in order from oldest to newest.*/ + /* Return segments in order from oldest to newest.*/ /* 12 */ "SELECT idx, start_block, leaves_end_block, end_block, root " "FROM %Q.'%q_segdir' WHERE level = ? ORDER BY idx ASC", /* 13 */ "SELECT idx, start_block, leaves_end_block, end_block, root " @@ -170713,13 +204661,15 @@ static int fts3SqlStmt( ** returns zero rows. */ /* 28 */ "SELECT level, count(*) AS cnt FROM %Q.'%q_segdir' " " GROUP BY level HAVING cnt>=?" - " ORDER BY (level %% 1024) ASC LIMIT 1", + " ORDER BY (level %% 1024) ASC, 2 DESC LIMIT 1", /* Estimate the upper limit on the number of leaf nodes in a new segment -** created by merging the oldest :2 segments from absolute level :1. See +** created by merging the oldest :2 segments from absolute level :1. See ** function sqlite3Fts3Incrmerge() for details. */ /* 29 */ "SELECT 2 * total(1 + leaves_end_block - start_block) " - " FROM %Q.'%q_segdir' WHERE level = ? AND idx < ?", + " FROM (SELECT * FROM %Q.'%q_segdir' " + " WHERE level = ? ORDER BY idx ASC LIMIT ?" + " )", /* SQL_DELETE_SEGDIR_ENTRY ** Delete the %_segdir entry on absolute level :1 with index :2. */ @@ -170731,7 +204681,7 @@ static int fts3SqlStmt( /* 31 */ "UPDATE %Q.'%q_segdir' SET idx = ? WHERE level=? AND idx=?", /* SQL_SELECT_SEGDIR -** Read a single entry from the %_segdir table. The entry from absolute +** Read a single entry from the %_segdir table. The entry from absolute ** level :1 with index value :2. */ /* 32 */ "SELECT idx, start_block, leaves_end_block, end_block, root " "FROM %Q.'%q_segdir' WHERE level = ? AND idx = ?", @@ -170755,7 +204705,7 @@ static int fts3SqlStmt( ** Return the largest relative level in the FTS index or indexes. */ /* 36 */ "SELECT max( level %% 1024 ) FROM %Q.'%q_segdir'", - /* Return segments in order from oldest to newest.*/ + /* Return segments in order from oldest to newest.*/ /* 37 */ "SELECT level, idx, end_block " "FROM %Q.'%q_segdir' WHERE level BETWEEN ? AND ? " "ORDER BY level DESC, idx ASC", @@ -170771,15 +204721,15 @@ static int fts3SqlStmt( assert( SizeofArray(azSql)==SizeofArray(p->aStmt) ); assert( eStmt=0 ); - + pStmt = p->aStmt[eStmt]; if( !pStmt ){ - int f = SQLITE_PREPARE_PERSISTENT|SQLITE_PREPARE_NO_VTAB; + int bAllowVtab = 0; char *zSql; if( eStmt==SQL_CONTENT_INSERT ){ zSql = sqlite3_mprintf(azSql[eStmt], p->zDb, p->zName, p->zWriteExprlist); }else if( eStmt==SQL_SELECT_CONTENT_BY_ROWID ){ - f &= ~SQLITE_PREPARE_NO_VTAB; + bAllowVtab = 1; zSql = sqlite3_mprintf(azSql[eStmt], p->zReadExprlist); }else{ zSql = sqlite3_mprintf(azSql[eStmt], p->zDb, p->zName); @@ -170787,7 +204737,7 @@ static int fts3SqlStmt( if( !zSql ){ rc = SQLITE_NOMEM; }else{ - rc = sqlite3_prepare_v3(p->db, zSql, -1, f, &pStmt, NULL); + rc = sqlite3Fts3PrepareStmt(p, zSql, 1, bAllowVtab, &pStmt); sqlite3_free(zSql); assert( rc==SQLITE_OK || pStmt==0 ); p->aStmt[eStmt] = pStmt; @@ -170876,7 +204826,7 @@ static void fts3SqlExec( sqlite3_stmt *pStmt; int rc; if( *pRC ) return; - rc = fts3SqlStmt(p, eStmt, &pStmt, apVal); + rc = fts3SqlStmt(p, eStmt, &pStmt, apVal); if( rc==SQLITE_OK ){ sqlite3_step(pStmt); rc = sqlite3_reset(pStmt); @@ -170886,22 +204836,22 @@ static void fts3SqlExec( /* -** This function ensures that the caller has obtained an exclusive -** shared-cache table-lock on the %_segdir table. This is required before +** This function ensures that the caller has obtained an exclusive +** shared-cache table-lock on the %_segdir table. This is required before ** writing data to the fts3 table. If this lock is not acquired first, then ** the caller may end up attempting to take this lock as part of committing -** a transaction, causing SQLite to return SQLITE_LOCKED or +** a transaction, causing SQLite to return SQLITE_LOCKED or ** LOCKED_SHAREDCACHEto a COMMIT command. ** -** It is best to avoid this because if FTS3 returns any error when -** committing a transaction, the whole transaction will be rolled back. -** And this is not what users expect when they get SQLITE_LOCKED_SHAREDCACHE. -** It can still happen if the user locks the underlying tables directly +** It is best to avoid this because if FTS3 returns any error when +** committing a transaction, the whole transaction will be rolled back. +** And this is not what users expect when they get SQLITE_LOCKED_SHAREDCACHE. +** It can still happen if the user locks the underlying tables directly ** instead of accessing them via FTS. */ static int fts3Writelock(Fts3Table *p){ int rc = SQLITE_OK; - + if( p->nPendingData==0 ){ sqlite3_stmt *pStmt; rc = fts3SqlStmt(p, SQL_DELETE_SEGDIR_LEVEL, &pStmt, 0); @@ -170918,7 +204868,7 @@ static int fts3Writelock(Fts3Table *p){ /* ** FTS maintains a separate indexes for each language-id (a 32-bit integer). ** Within each language id, a separate index is maintained to store the -** document terms, and each configured prefix size (configured the FTS +** document terms, and each configured prefix size (configured the FTS ** "prefix=" option). And each index consists of multiple levels ("relative ** levels"). ** @@ -170928,14 +204878,14 @@ static int fts3Writelock(Fts3Table *p){ ** separate component values into the single 64-bit integer value that ** can be used to query the %_segdir table. ** -** Specifically, each language-id/index combination is allocated 1024 +** Specifically, each language-id/index combination is allocated 1024 ** 64-bit integer level values ("absolute levels"). The main terms index ** for language-id 0 is allocate values 0-1023. The first prefix index ** (if any) for language-id 0 is allocated values 1024-2047. And so on. ** Language 1 indexes are allocated immediately following language 0. ** ** So, for a system with nPrefix prefix indexes configured, the block of -** absolute levels that corresponds to language-id iLangid and index +** absolute levels that corresponds to language-id iLangid and index ** iIndex starts at absolute level ((iLangid * (nPrefix+1) + iIndex) * 1024). */ static sqlite3_int64 getAbsoluteLevel( @@ -170956,7 +204906,7 @@ static sqlite3_int64 getAbsoluteLevel( /* ** Set *ppStmt to a statement handle that may be used to iterate through ** all rows in the %_segdir table, from oldest to newest. If successful, -** return SQLITE_OK. If an error occurs while preparing the statement, +** return SQLITE_OK. If an error occurs while preparing the statement, ** return an SQLite error code. ** ** There is only ever one instance of this SQL statement compiled for @@ -170987,16 +204937,16 @@ SQLITE_PRIVATE int sqlite3Fts3AllSegdirs( if( iLevel<0 ){ /* "SELECT * FROM %_segdir WHERE level BETWEEN ? AND ? ORDER BY ..." */ rc = fts3SqlStmt(p, SQL_SELECT_LEVEL_RANGE, &pStmt, 0); - if( rc==SQLITE_OK ){ + if( rc==SQLITE_OK ){ sqlite3_bind_int64(pStmt, 1, getAbsoluteLevel(p, iLangid, iIndex, 0)); - sqlite3_bind_int64(pStmt, 2, + sqlite3_bind_int64(pStmt, 2, getAbsoluteLevel(p, iLangid, iIndex, FTS3_SEGDIR_MAXLEVEL-1) ); } }else{ /* "SELECT * FROM %_segdir WHERE level = ? ORDER BY ..." */ rc = fts3SqlStmt(p, SQL_SELECT_LEVEL, &pStmt, 0); - if( rc==SQLITE_OK ){ + if( rc==SQLITE_OK ){ sqlite3_bind_int64(pStmt, 1, getAbsoluteLevel(p, iLangid, iIndex,iLevel)); } } @@ -171025,7 +204975,7 @@ static int fts3PendingListAppendVarint( /* Allocate or grow the PendingList as required. */ if( !p ){ - p = sqlite3_malloc(sizeof(*p) + 100); + p = sqlite3_malloc64(sizeof(*p) + 100); if( !p ){ return SQLITE_NOMEM; } @@ -171034,14 +204984,14 @@ static int fts3PendingListAppendVarint( p->nData = 0; } else if( p->nData+FTS3_VARINT_MAX+1>p->nSpace ){ - int nNew = p->nSpace * 2; - p = sqlite3_realloc(p, sizeof(*p) + nNew); + i64 nNew = p->nSpace * 2; + p = sqlite3_realloc64(p, sizeof(*p) + nNew); if( !p ){ sqlite3_free(*pp); *pp = 0; return SQLITE_NOMEM; } - p->nSpace = nNew; + p->nSpace = (int)nNew; p->aData = (char *)&p[1]; } @@ -171074,7 +205024,7 @@ static int fts3PendingListAppend( assert( !p || p->iLastDocid<=iDocid ); if( !p || p->iLastDocid!=iDocid ){ - sqlite3_int64 iDelta = iDocid - (p ? p->iLastDocid : 0); + u64 iDelta = (u64)iDocid - (u64)(p ? p->iLastDocid : 0); if( p ){ assert( p->nDatanSpace ); assert( p->aData[p->nData]==0 ); @@ -171136,11 +205086,13 @@ static int fts3PendingTermsAddOne( pList = (PendingList *)fts3HashFind(pHash, zToken, nToken); if( pList ){ - p->nPendingData -= (pList->nData + nToken + sizeof(Fts3HashElem)); + assert( (i64)pList->nData+(i64)nToken+(i64)sizeof(Fts3HashElem) + <= (i64)p->nPendingData ); + p->nPendingData -= (int)(pList->nData + nToken + sizeof(Fts3HashElem)); } if( fts3PendingListAppend(&pList, p->iPrevDocid, iCol, iPos, &rc) ){ if( pList==fts3HashInsert(pHash, zToken, nToken, pList) ){ - /* Malloc failed while inserting the new entry. This can only + /* Malloc failed while inserting the new entry. This can only ** happen if there was no previous entry for this token. */ assert( 0==fts3HashFind(pHash, zToken, nToken) ); @@ -171149,7 +205101,9 @@ static int fts3PendingTermsAddOne( } } if( rc==SQLITE_OK ){ - p->nPendingData += (pList->nData + nToken + sizeof(Fts3HashElem)); + assert( (i64)p->nPendingData + pList->nData + nToken + + sizeof(Fts3HashElem) <= 0x3fffffff ); + p->nPendingData += (int)(pList->nData + nToken + sizeof(Fts3HashElem)); } return rc; } @@ -171186,7 +205140,7 @@ static int fts3PendingTermsAdd( assert( pTokenizer && pModule ); /* If the user has inserted a NULL value, this function may be called with - ** zText==0. In this case, add zero token entries to the hash table and + ** zText==0. In this case, add zero token entries to the hash table and ** return early. */ if( zText==0 ){ *pnWord = 0; @@ -171217,8 +205171,8 @@ static int fts3PendingTermsAdd( rc = fts3PendingTermsAddOne( p, iCol, iPos, &p->aIndex[0].hPending, zToken, nToken ); - - /* Add the term to each of the prefix indexes that it is not too + + /* Add the term to each of the prefix indexes that it is not too ** short for. */ for(i=1; rc==SQLITE_OK && inIndex; i++){ struct Fts3Index *pIndex = &p->aIndex[i]; @@ -171234,8 +205188,8 @@ static int fts3PendingTermsAdd( return (rc==SQLITE_DONE ? SQLITE_OK : rc); } -/* -** Calling this function indicates that subsequent calls to +/* +** Calling this function indicates that subsequent calls to ** fts3PendingTermsAdd() are to add term/position-list pairs for the ** contents of the document with docid iDocid. */ @@ -171254,10 +205208,10 @@ static int fts3PendingTermsDocid( ** buffer was half empty, that would let the less frequent terms ** generate longer doclists. */ - if( iDocidiPrevDocid + if( iDocidiPrevDocid || (iDocid==p->iPrevDocid && p->bPrevDelete==0) || p->iPrevLangid!=iLangid - || p->nPendingData>p->nMaxPendingData + || p->nPendingData>p->nMaxPendingData ){ int rc = sqlite3Fts3PendingTermsFlush(p); if( rc!=SQLITE_OK ) return rc; @@ -171269,7 +205223,7 @@ static int fts3PendingTermsDocid( } /* -** Discard the contents of the pending-terms hash tables. +** Discard the contents of the pending-terms hash tables. */ SQLITE_PRIVATE void sqlite3Fts3PendingTermsClear(Fts3Table *p){ int i; @@ -171294,9 +205248,9 @@ SQLITE_PRIVATE void sqlite3Fts3PendingTermsClear(Fts3Table *p){ ** fts3InsertData(). Parameter iDocid is the docid of the new row. */ static int fts3InsertTerms( - Fts3Table *p, - int iLangid, - sqlite3_value **apVal, + Fts3Table *p, + int iLangid, + sqlite3_value **apVal, u32 *aSz ){ int i; /* Iterator variable */ @@ -171359,7 +205313,7 @@ static int fts3InsertData( rc = fts3SqlStmt(p, SQL_CONTENT_INSERT, &pContentInsert, &apVal[1]); if( rc==SQLITE_OK && p->zLanguageid ){ rc = sqlite3_bind_int( - pContentInsert, p->nColumn+2, + pContentInsert, p->nColumn+2, sqlite3_value_int(apVal[p->nColumn+4]) ); } @@ -171386,8 +205340,8 @@ static int fts3InsertData( if( rc!=SQLITE_OK ) return rc; } - /* Execute the statement to insert the record. Set *piDocid to the - ** new docid value. + /* Execute the statement to insert the record. Set *piDocid to the + ** new docid value. */ sqlite3_step(pContentInsert); rc = sqlite3_reset(pContentInsert); @@ -171437,7 +205391,7 @@ static int langidFromSelect(Fts3Table *p, sqlite3_stmt *pSelect){ ** (an integer) of a row about to be deleted. Remove all terms from the ** full-text index. */ -static void fts3DeleteTerms( +static void fts3DeleteTerms( int *pRC, /* Result code */ Fts3Table *p, /* The FTS table to delete from */ sqlite3_value *pRowid, /* The docid to be deleted */ @@ -171484,7 +205438,7 @@ static void fts3DeleteTerms( */ static int fts3SegmentMerge(Fts3Table *, int, int, int); -/* +/* ** This function allocates a new level iLevel index in the segdir table. ** Usually, indexes are allocated within a level sequentially starting ** with 0, so the allocated index is one greater than the value returned @@ -171493,17 +205447,17 @@ static int fts3SegmentMerge(Fts3Table *, int, int, int); ** SELECT max(idx) FROM %_segdir WHERE level = :iLevel ** ** However, if there are already FTS3_MERGE_COUNT indexes at the requested -** level, they are merged into a single level (iLevel+1) segment and the +** level, they are merged into a single level (iLevel+1) segment and the ** allocated index is 0. ** ** If successful, *piIdx is set to the allocated index slot and SQLITE_OK ** returned. Otherwise, an SQLite error code is returned. */ static int fts3AllocateSegdirIdx( - Fts3Table *p, + Fts3Table *p, int iLangid, /* Language id */ int iIndex, /* Index for p->aIndex */ - int iLevel, + int iLevel, int *piIdx ){ int rc; /* Return Code */ @@ -171531,7 +205485,7 @@ static int fts3AllocateSegdirIdx( ** segment and allocate (newly freed) index 0 at level iLevel. Otherwise, ** if iNext is less than FTS3_MERGE_COUNT, allocate index iNext. */ - if( iNext>=FTS3_MERGE_COUNT ){ + if( iNext>=MergeCount(p) ){ fts3LogMerge(16, getAbsoluteLevel(p, iLangid, iIndex, iLevel)); rc = fts3SegmentMerge(p, iLangid, iIndex, iLevel); *piIdx = 0; @@ -171551,7 +205505,7 @@ static int fts3AllocateSegdirIdx( ** This function reads data from a single row of the %_segments table. The ** specific row is identified by the iBlockid parameter. If paBlob is not ** NULL, then a buffer is allocated using sqlite3_malloc() and populated -** with the contents of the blob stored in the "block" column of the +** with the contents of the blob stored in the "block" column of the ** identified table row is. Whether or not paBlob is NULL, *pnBlob is set ** to the size of the blob in bytes before returning. ** @@ -171598,7 +205552,7 @@ SQLITE_PRIVATE int sqlite3Fts3ReadBlock( int nByte = sqlite3_blob_bytes(p->pSegments); *pnBlob = nByte; if( paBlob ){ - char *aByte = sqlite3_malloc(nByte + FTS3_NODE_PADDING); + char *aByte = sqlite3_malloc64((i64)nByte + FTS3_NODE_PADDING); if( !aByte ){ rc = SQLITE_NOMEM; }else{ @@ -171615,6 +205569,8 @@ SQLITE_PRIVATE int sqlite3Fts3ReadBlock( } *paBlob = aByte; } + }else if( rc==SQLITE_ERROR ){ + rc = FTS_CORRUPT_VTAB; } return rc; @@ -171628,14 +205584,14 @@ SQLITE_PRIVATE void sqlite3Fts3SegmentsClose(Fts3Table *p){ sqlite3_blob_close(p->pSegments); p->pSegments = 0; } - + static int fts3SegReaderIncrRead(Fts3SegReader *pReader){ int nRead; /* Number of bytes to read */ int rc; /* Return code */ nRead = MIN(pReader->nNode - pReader->nPopulate, FTS3_NODE_CHUNKSIZE); rc = sqlite3_blob_read( - pReader->pBlob, + pReader->pBlob, &pReader->aNode[pReader->nPopulate], nRead, pReader->nPopulate @@ -171655,10 +205611,10 @@ static int fts3SegReaderIncrRead(Fts3SegReader *pReader){ static int fts3SegReaderRequire(Fts3SegReader *pReader, char *pFrom, int nByte){ int rc = SQLITE_OK; - assert( !pReader->pBlob + assert( !pReader->pBlob || (pFrom>=pReader->aNode && pFrom<&pReader->aNode[pReader->nNode]) ); - while( pReader->pBlob && rc==SQLITE_OK + while( pReader->pBlob && rc==SQLITE_OK && (pFrom - pReader->aNode + nByte)>pReader->nPopulate ){ rc = fts3SegReaderIncrRead(pReader); @@ -171684,7 +205640,7 @@ static void fts3SegReaderSetEof(Fts3SegReader *pSeg){ ** SQLITE_DONE. Otherwise, an SQLite error code. */ static int fts3SegReaderNext( - Fts3Table *p, + Fts3Table *p, Fts3SegReader *pReader, int bIncr ){ @@ -171709,9 +205665,19 @@ static int fts3SegReaderNext( char *aCopy; PendingList *pList = (PendingList *)fts3HashData(pElem); int nCopy = pList->nData+1; - pReader->zTerm = (char *)fts3HashKey(pElem); - pReader->nTerm = fts3HashKeysize(pElem); - aCopy = (char*)sqlite3_malloc(nCopy); + + int nTerm = fts3HashKeysize(pElem); + if( (nTerm+1)>pReader->nTermAlloc ){ + sqlite3_free(pReader->zTerm); + pReader->zTerm = (char*)sqlite3_malloc64(((i64)nTerm+1)*2); + if( !pReader->zTerm ) return SQLITE_NOMEM; + pReader->nTermAlloc = (nTerm+1)*2; + } + memcpy(pReader->zTerm, fts3HashKey(pElem), nTerm); + pReader->zTerm[nTerm] = '\0'; + pReader->nTerm = nTerm; + + aCopy = (char*)sqlite3_malloc64(nCopy); if( !aCopy ) return SQLITE_NOMEM; memcpy(aCopy, pList->aData, nCopy); pReader->nNode = pReader->nDoclist = nCopy; @@ -171724,7 +205690,7 @@ static int fts3SegReaderNext( fts3SegReaderSetEof(pReader); - /* If iCurrentBlock>=iLeafEndBlock, this is an EOF condition. All leaf + /* If iCurrentBlock>=iLeafEndBlock, this is an EOF condition. All leaf ** blocks have already been traversed. */ #ifdef CORRUPT_DB assert( pReader->iCurrentBlock<=pReader->iLeafEndBlock || CORRUPT_DB ); @@ -171734,7 +205700,7 @@ static int fts3SegReaderNext( } rc = sqlite3Fts3ReadBlock( - p, ++pReader->iCurrentBlock, &pReader->aNode, &pReader->nNode, + p, ++pReader->iCurrentBlock, &pReader->aNode, &pReader->nNode, (bIncr ? &pReader->nPopulate : 0) ); if( rc!=SQLITE_OK ) return rc; @@ -171750,14 +205716,14 @@ static int fts3SegReaderNext( rc = fts3SegReaderRequire(pReader, pNext, FTS3_VARINT_MAX*2); if( rc!=SQLITE_OK ) return rc; - - /* Because of the FTS3_NODE_PADDING bytes of padding, the following is + + /* Because of the FTS3_NODE_PADDING bytes of padding, the following is ** safe (no risk of overread) even if the node data is corrupted. */ pNext += fts3GetVarint32(pNext, &nPrefix); pNext += fts3GetVarint32(pNext, &nSuffix); - if( nSuffix<=0 + if( nSuffix<=0 || (&pReader->aNode[pReader->nNode] - pNext)pReader->nTermAlloc + || nPrefix>pReader->nTerm ){ return FTS_CORRUPT_VTAB; } @@ -171786,11 +205752,12 @@ static int fts3SegReaderNext( pReader->pOffsetList = 0; /* Check that the doclist does not appear to extend past the end of the - ** b-tree node. And that the final byte of the doclist is 0x00. If either + ** b-tree node. And that the final byte of the doclist is 0x00. If either ** of these statements is untrue, then the data structure is corrupt. */ if( pReader->nDoclist > pReader->nNode-(pReader->aDoclist-pReader->aNode) || (pReader->nPopulate==0 && pReader->aDoclist[pReader->nDoclist-1]) + || pReader->nDoclist==0 ){ return FTS_CORRUPT_VTAB; } @@ -171810,7 +205777,7 @@ static int fts3SegReaderFirstDocid(Fts3Table *pTab, Fts3SegReader *pReader){ pReader->iDocid = 0; pReader->nOffsetList = 0; sqlite3Fts3DoclistPrev(0, - pReader->aDoclist, pReader->nDoclist, &pReader->pOffsetList, + pReader->aDoclist, pReader->nDoclist, &pReader->pOffsetList, &pReader->iDocid, &pReader->nOffsetList, &bEof ); }else{ @@ -171826,8 +205793,8 @@ static int fts3SegReaderFirstDocid(Fts3Table *pTab, Fts3SegReader *pReader){ /* ** Advance the SegReader to point to the next docid in the doclist ** associated with the current term. -** -** If arguments ppOffsetList and pnOffsetList are not NULL, then +** +** If arguments ppOffsetList and pnOffsetList are not NULL, then ** *ppOffsetList is set to point to the first column-offset list ** in the doclist entry (i.e. immediately past the docid varint). ** *pnOffsetList is set to the length of the set of column-offset @@ -171870,22 +205837,22 @@ static int fts3SegReaderNextDocid( ** following block advances it to point one byte past the end of ** the same offset list. */ while( 1 ){ - + /* The following line of code (and the "p++" below the while() loop) is - ** normally all that is required to move pointer p to the desired + ** normally all that is required to move pointer p to the desired ** position. The exception is if this node is being loaded from disk ** incrementally and pointer "p" now points to the first byte past ** the populated part of pReader->aNode[]. */ while( *p | c ) c = *p++ & 0x80; assert( *p==0 ); - + if( pReader->pBlob==0 || p<&pReader->aNode[pReader->nPopulate] ) break; rc = fts3SegReaderIncrRead(pReader); if( rc!=SQLITE_OK ) return rc; } p++; - + /* If required, populate the output variables with a pointer to and the ** size of the previous offset-list. */ @@ -171896,7 +205863,7 @@ static int fts3SegReaderNextDocid( /* List may have been edited in place by fts3EvalNearTrim() */ while( ppOffsetList = p + sqlite3Fts3GetVarint(p, &iDelta); + u64 iDelta; + pReader->pOffsetList = p + sqlite3Fts3GetVarintU(p, &iDelta); if( pTab->bDescIdx ){ - pReader->iDocid -= iDelta; + pReader->iDocid = (i64)((u64)pReader->iDocid - iDelta); }else{ - pReader->iDocid += iDelta; + pReader->iDocid = (i64)((u64)pReader->iDocid + iDelta); } } } } - return SQLITE_OK; + return rc; } SQLITE_PRIVATE int sqlite3Fts3MsrOvfl( - Fts3Cursor *pCsr, + Fts3Cursor *pCsr, Fts3MultiSegReader *pMsr, int *pnOvfl ){ @@ -171938,8 +205905,8 @@ SQLITE_PRIVATE int sqlite3Fts3MsrOvfl( for(ii=0; rc==SQLITE_OK && iinSegment; ii++){ Fts3SegReader *pReader = pMsr->apSegment[ii]; - if( !fts3SegReaderIsPending(pReader) - && !fts3SegReaderIsRootOnly(pReader) + if( !fts3SegReaderIsPending(pReader) + && !fts3SegReaderIsRootOnly(pReader) ){ sqlite3_int64 jj; for(jj=pReader->iStartBlock; jj<=pReader->iLeafEndBlock; jj++){ @@ -171957,14 +205924,12 @@ SQLITE_PRIVATE int sqlite3Fts3MsrOvfl( } /* -** Free all allocations associated with the iterator passed as the +** Free all allocations associated with the iterator passed as the ** second argument. */ SQLITE_PRIVATE void sqlite3Fts3SegReaderFree(Fts3SegReader *pReader){ if( pReader ){ - if( !fts3SegReaderIsPending(pReader) ){ - sqlite3_free(pReader->zTerm); - } + sqlite3_free(pReader->zTerm); if( !fts3SegReaderIsRootOnly(pReader) ){ sqlite3_free(pReader->aNode); } @@ -171999,7 +205964,7 @@ SQLITE_PRIVATE int sqlite3Fts3SegReaderNew( nExtra = nRoot + FTS3_NODE_PADDING; } - pReader = (Fts3SegReader *)sqlite3_malloc(sizeof(Fts3SegReader) + nExtra); + pReader = (Fts3SegReader *)sqlite3_malloc64(sizeof(Fts3SegReader) + nExtra); if( !pReader ){ return SQLITE_NOMEM; } @@ -172091,7 +206056,7 @@ SQLITE_PRIVATE int sqlite3Fts3SegReaderPending( if( nElem==nAlloc ){ Fts3HashElem **aElem2; nAlloc += 16; - aElem2 = (Fts3HashElem **)sqlite3_realloc( + aElem2 = (Fts3HashElem **)sqlite3_realloc64( aElem, nAlloc*sizeof(Fts3HashElem *) ); if( !aElem2 ){ @@ -172116,7 +206081,7 @@ SQLITE_PRIVATE int sqlite3Fts3SegReaderPending( }else{ /* The query is a simple term lookup that matches at most one term in - ** the index. All that is required is a straight hash-lookup. + ** the index. All that is required is a straight hash-lookup. ** ** Because the stack address of pE may be accessed via the aElem pointer ** below, the "Fts3HashElem *pE" must be declared so that it is valid @@ -172151,7 +206116,7 @@ SQLITE_PRIVATE int sqlite3Fts3SegReaderPending( } /* -** Compare the entries pointed to by two Fts3SegReader structures. +** Compare the entries pointed to by two Fts3SegReader structures. ** Comparison is as follows: ** ** 1) EOF is greater than not EOF. @@ -172180,7 +206145,7 @@ static int fts3SegReaderCmp(Fts3SegReader *pLhs, Fts3SegReader *pRhs){ if( rc==0 ){ rc = pRhs->iIdx - pLhs->iIdx; } - assert( rc!=0 ); + assert_fts3_nc( rc!=0 ); return rc; } @@ -172222,7 +206187,7 @@ static int fts3SegReaderDoclistCmpRev(Fts3SegReader *pLhs, Fts3SegReader *pRhs){ /* ** Compare the term that the Fts3SegReader object passed as the first argument -** points to with the term specified by arguments zTerm and nTerm. +** points to with the term specified by arguments zTerm and nTerm. ** ** If the pSeg iterator is already at EOF, return 0. Otherwise, return ** -ve if the pSeg term is less than zTerm/nTerm, 0 if the two terms are @@ -172283,7 +206248,7 @@ static void fts3SegReaderSort( #endif } -/* +/* ** Insert a record into the %_segments table. */ static int fts3WriteSegment( @@ -172325,7 +206290,7 @@ SQLITE_PRIVATE int sqlite3Fts3MaxLevel(Fts3Table *p, int *pnMax){ return rc; } -/* +/* ** Insert a record into the %_segdir table. */ static int fts3WriteSegdir( @@ -172363,7 +206328,7 @@ static int fts3WriteSegdir( /* ** Return the size of the common prefix (if any) shared by zPrev and -** zNext, in bytes. For example, +** zNext, in bytes. For example, ** ** fts3PrefixCompress("abc", 3, "abcdef", 6) // returns 3 ** fts3PrefixCompress("abX", 3, "abcdef", 6) // returns 2 @@ -172376,8 +206341,8 @@ static int fts3PrefixCompress( int nNext /* Size of buffer zNext in bytes */ ){ int n; - UNUSED_PARAMETER(nNext); - for(n=0; nzTerm, pTree->nTerm, zTerm, nTerm); nSuffix = nTerm-nPrefix; + /* If nSuffix is zero or less, then zTerm/nTerm must be a prefix of + ** pWriter->zTerm/pWriter->nTerm. i.e. must be equal to or less than when + ** compared with BINARY collation. This indicates corruption. */ + if( nSuffix<=0 ) return FTS_CORRUPT_VTAB; + nReq += sqlite3Fts3VarintLen(nPrefix)+sqlite3Fts3VarintLen(nSuffix)+nSuffix; if( nReq<=p->nNodeSize || !pTree->zTerm ){ @@ -172416,11 +206386,11 @@ static int fts3NodeAddTerm( ** and the static node buffer (p->nNodeSize bytes) is not large ** enough. Use a separately malloced buffer instead This wastes ** p->nNodeSize bytes, but since this scenario only comes about when - ** the database contain two terms that share a prefix of almost 2KB, - ** this is not expected to be a serious problem. + ** the database contain two terms that share a prefix of almost 2KB, + ** this is not expected to be a serious problem. */ assert( pTree->aData==(char *)&pTree[1] ); - pTree->aData = (char *)sqlite3_malloc(nReq); + pTree->aData = (char *)sqlite3_malloc64(nReq); if( !pTree->aData ){ return SQLITE_NOMEM; } @@ -172438,7 +206408,7 @@ static int fts3NodeAddTerm( if( isCopyTerm ){ if( pTree->nMalloczMalloc, nTerm*2); + char *zNew = sqlite3_realloc64(pTree->zMalloc, (i64)nTerm*2); if( !zNew ){ return SQLITE_NOMEM; } @@ -172461,10 +206431,10 @@ static int fts3NodeAddTerm( ** If this is the first node in the tree, the term is added to it. ** ** Otherwise, the term is not added to the new node, it is left empty for - ** now. Instead, the term is inserted into the parent of pTree. If pTree + ** now. Instead, the term is inserted into the parent of pTree. If pTree ** has no parent, one is created here. */ - pNew = (SegmentNode *)sqlite3_malloc(sizeof(SegmentNode) + p->nNodeSize); + pNew = (SegmentNode *)sqlite3_malloc64(sizeof(SegmentNode) + p->nNodeSize); if( !pNew ){ return SQLITE_NOMEM; } @@ -172486,7 +206456,7 @@ static int fts3NodeAddTerm( pTree->zMalloc = 0; }else{ pNew->pLeftmost = pNew; - rc = fts3NodeAddTerm(p, &pNew, isCopyTerm, zTerm, nTerm); + rc = fts3NodeAddTerm(p, &pNew, isCopyTerm, zTerm, nTerm); } *ppTree = pNew; @@ -172497,8 +206467,8 @@ static int fts3NodeAddTerm( ** Helper function for fts3NodeWrite(). */ static int fts3TreeFinishNode( - SegmentNode *pTree, - int iHeight, + SegmentNode *pTree, + int iHeight, sqlite3_int64 iLeftChild ){ int nStart; @@ -172511,15 +206481,15 @@ static int fts3TreeFinishNode( /* ** Write the buffer for the segment node pTree and all of its peers to the -** database. Then call this function recursively to write the parent of -** pTree and its peers to the database. +** database. Then call this function recursively to write the parent of +** pTree and its peers to the database. ** ** Except, if pTree is a root node, do not write it to the database. Instead, ** set output variables *paRoot and *pnRoot to contain the root node. ** ** If successful, SQLITE_OK is returned and output variable *piLast is ** set to the largest blockid written to the database (or zero if no -** blocks were written to the db). Otherwise, an SQLite error code is +** blocks were written to the db). Otherwise, an SQLite error code is ** returned. */ static int fts3NodeWrite( @@ -172547,7 +206517,7 @@ static int fts3NodeWrite( for(pIter=pTree->pLeftmost; pIter && rc==SQLITE_OK; pIter=pIter->pRight){ int nStart = fts3TreeFinishNode(pIter, iHeight, iNextLeaf); int nWrite = pIter->nData - nStart; - + rc = fts3WriteSegment(p, iNextFree, &pIter->aData[nStart], nWrite); iNextFree++; iNextLeaf += (pIter->nEntry+1); @@ -172593,7 +206563,7 @@ static void fts3NodeFree(SegmentNode *pTree){ */ static int fts3SegWriterAdd( Fts3Table *p, /* Virtual table handle */ - SegmentWriter **ppWriter, /* IN/OUT: SegmentWriter handle */ + SegmentWriter **ppWriter, /* IN/OUT: SegmentWriter handle */ int isCopyTerm, /* True if buffer zTerm must be copied */ const char *zTerm, /* Pointer to buffer containing term */ int nTerm, /* Size of term in bytes */ @@ -172602,7 +206572,7 @@ static int fts3SegWriterAdd( ){ int nPrefix; /* Size of term prefix in bytes */ int nSuffix; /* Size of term suffix in bytes */ - int nReq; /* Number of bytes required on leaf page */ + i64 nReq; /* Number of bytes required on leaf page */ int nData; SegmentWriter *pWriter = *ppWriter; @@ -172611,13 +206581,13 @@ static int fts3SegWriterAdd( sqlite3_stmt *pStmt; /* Allocate the SegmentWriter structure */ - pWriter = (SegmentWriter *)sqlite3_malloc(sizeof(SegmentWriter)); + pWriter = (SegmentWriter *)sqlite3_malloc64(sizeof(SegmentWriter)); if( !pWriter ) return SQLITE_NOMEM; memset(pWriter, 0, sizeof(SegmentWriter)); *ppWriter = pWriter; /* Allocate a buffer in which to accumulate data */ - pWriter->aData = (char *)sqlite3_malloc(p->nNodeSize); + pWriter->aData = (char *)sqlite3_malloc64(p->nNodeSize); if( !pWriter->aData ) return SQLITE_NOMEM; pWriter->nSize = p->nNodeSize; @@ -172636,7 +206606,7 @@ static int fts3SegWriterAdd( nPrefix = fts3PrefixCompress(pWriter->zTerm, pWriter->nTerm, zTerm, nTerm); nSuffix = nTerm-nPrefix; - /* If nSuffix is zero or less, then zTerm/nTerm must be a prefix of + /* If nSuffix is zero or less, then zTerm/nTerm must be a prefix of ** pWriter->zTerm/pWriter->nTerm. i.e. must be equal to or less than when ** compared with BINARY collation. This indicates corruption. */ if( nSuffix<=0 ) return FTS_CORRUPT_VTAB; @@ -172652,6 +206622,7 @@ static int fts3SegWriterAdd( int rc; /* The current leaf node is full. Write it out to the database. */ + if( pWriter->iFree==LARGEST_INT64 ) return FTS_CORRUPT_VTAB; rc = fts3WriteSegment(p, pWriter->iFree++, pWriter->aData, nData); if( rc!=SQLITE_OK ) return rc; p->nLeafAdd++; @@ -172691,7 +206662,7 @@ static int fts3SegWriterAdd( ** the buffer to make it large enough. */ if( nReq>pWriter->nSize ){ - char *aNew = sqlite3_realloc(pWriter->aData, nReq); + char *aNew = sqlite3_realloc64(pWriter->aData, nReq); if( !aNew ) return SQLITE_NOMEM; pWriter->aData = aNew; pWriter->nSize = nReq; @@ -172701,9 +206672,11 @@ static int fts3SegWriterAdd( /* Append the prefix-compressed term and doclist to the buffer. */ nData += sqlite3Fts3PutVarint(&pWriter->aData[nData], nPrefix); nData += sqlite3Fts3PutVarint(&pWriter->aData[nData], nSuffix); + assert( nSuffix>0 ); memcpy(&pWriter->aData[nData], &zTerm[nPrefix], nSuffix); nData += nSuffix; nData += sqlite3Fts3PutVarint(&pWriter->aData[nData], nDoclist); + assert( nDoclist>0 ); memcpy(&pWriter->aData[nData], aDoclist, nDoclist); pWriter->nData = nData + nDoclist; @@ -172714,7 +206687,7 @@ static int fts3SegWriterAdd( */ if( isCopyTerm ){ if( nTerm>pWriter->nMalloc ){ - char *zNew = sqlite3_realloc(pWriter->zMalloc, nTerm*2); + char *zNew = sqlite3_realloc64(pWriter->zMalloc, (i64)nTerm*2); if( !zNew ){ return SQLITE_NOMEM; } @@ -172723,6 +206696,7 @@ static int fts3SegWriterAdd( pWriter->zTerm = zNew; } assert( pWriter->zTerm==pWriter->zMalloc ); + assert( nTerm>0 ); memcpy(pWriter->zTerm, zTerm, nTerm); }else{ pWriter->zTerm = (char *)zTerm; @@ -172758,12 +206732,12 @@ static int fts3SegWriterFlush( pWriter->iFirst, pWriter->iFree, &iLast, &zRoot, &nRoot); } if( rc==SQLITE_OK ){ - rc = fts3WriteSegdir(p, iLevel, iIdx, + rc = fts3WriteSegdir(p, iLevel, iIdx, pWriter->iFirst, iLastLeaf, iLast, pWriter->nLeafData, zRoot, nRoot); } }else{ /* The entire tree fits on the root node. Write it to the segdir table. */ - rc = fts3WriteSegdir(p, iLevel, iIdx, + rc = fts3WriteSegdir(p, iLevel, iIdx, 0, 0, 0, pWriter->nLeafData, pWriter->aData, pWriter->nData); } p->nLeafAdd++; @@ -172771,7 +206745,7 @@ static int fts3SegWriterFlush( } /* -** Release all memory held by the SegmentWriter object passed as the +** Release all memory held by the SegmentWriter object passed as the ** first argument. */ static void fts3SegWriterFree(SegmentWriter *pWriter){ @@ -172821,9 +206795,9 @@ static int fts3IsEmpty(Fts3Table *p, sqlite3_value *pRowid, int *pisEmpty){ ** Return SQLITE_OK if successful, or an SQLite error code if not. */ static int fts3SegmentMaxLevel( - Fts3Table *p, + Fts3Table *p, int iLangid, - int iIndex, + int iIndex, sqlite3_int64 *pnMax ){ sqlite3_stmt *pStmt; @@ -172839,7 +206813,7 @@ static int fts3SegmentMaxLevel( rc = fts3SqlStmt(p, SQL_SELECT_SEGDIR_MAX_LEVEL, &pStmt, 0); if( rc!=SQLITE_OK ) return rc; sqlite3_bind_int64(pStmt, 1, getAbsoluteLevel(p, iLangid, iIndex, 0)); - sqlite3_bind_int64(pStmt, 2, + sqlite3_bind_int64(pStmt, 2, getAbsoluteLevel(p, iLangid, iIndex, FTS3_SEGDIR_MAXLEVEL-1) ); if( SQLITE_ROW==sqlite3_step(pStmt) ){ @@ -172868,8 +206842,8 @@ static int fts3SegmentIsMaxLevel(Fts3Table *p, i64 iAbsLevel, int *pbMax){ int rc = fts3SqlStmt(p, SQL_SELECT_SEGDIR_MAX_LEVEL, &pStmt, 0); if( rc!=SQLITE_OK ) return rc; sqlite3_bind_int64(pStmt, 1, iAbsLevel+1); - sqlite3_bind_int64(pStmt, 2, - ((iAbsLevel/FTS3_SEGDIR_MAXLEVEL)+1) * FTS3_SEGDIR_MAXLEVEL + sqlite3_bind_int64(pStmt, 2, + (((u64)iAbsLevel/FTS3_SEGDIR_MAXLEVEL)+1) * FTS3_SEGDIR_MAXLEVEL ); *pbMax = 0; @@ -172906,9 +206880,9 @@ static int fts3DeleteSegment( ** This function is used after merging multiple segments into a single large ** segment to delete the old, now redundant, segment b-trees. Specifically, ** it: -** -** 1) Deletes all %_segments entries for the segments associated with -** each of the SegReader objects in the array passed as the third +** +** 1) Deletes all %_segments entries for the segments associated with +** each of the SegReader objects in the array passed as the third ** argument, and ** ** 2) deletes all %_segdir entries with level iLevel, or all %_segdir @@ -172940,7 +206914,7 @@ static int fts3DeleteSegdir( rc = fts3SqlStmt(p, SQL_DELETE_SEGDIR_RANGE, &pDelete, 0); if( rc==SQLITE_OK ){ sqlite3_bind_int64(pDelete, 1, getAbsoluteLevel(p, iLangid, iIndex, 0)); - sqlite3_bind_int64(pDelete, 2, + sqlite3_bind_int64(pDelete, 2, getAbsoluteLevel(p, iLangid, iIndex, FTS3_SEGDIR_MAXLEVEL-1) ); } @@ -172962,7 +206936,7 @@ static int fts3DeleteSegdir( } /* -** When this function is called, buffer *ppList (size *pnList bytes) contains +** When this function is called, buffer *ppList (size *pnList bytes) contains ** a position list that may (or may not) feature multiple columns. This ** function adjusts the pointer *ppList and the length *pnList so that they ** identify the subset of the position list that corresponds to column iCol. @@ -172989,7 +206963,7 @@ static void fts3ColumnFilter( while( 1 ){ char c = 0; while( p0){ memset(&pList[nList], 0, pEnd - &pList[nList]); } *ppList = pList; @@ -173021,17 +206995,20 @@ static void fts3ColumnFilter( static int fts3MsrBufferData( Fts3MultiSegReader *pMsr, /* Multi-segment-reader handle */ char *pList, - int nList + i64 nList ){ - if( nList>pMsr->nBuffer ){ + if( (nList+FTS3_NODE_PADDING)>pMsr->nBuffer ){ char *pNew; - pMsr->nBuffer = nList*2; - pNew = (char *)sqlite3_realloc(pMsr->aBuffer, pMsr->nBuffer); + int nNew = nList*2 + FTS3_NODE_PADDING; + pNew = (char *)sqlite3_realloc64(pMsr->aBuffer, nNew); if( !pNew ) return SQLITE_NOMEM; pMsr->aBuffer = pNew; + pMsr->nBuffer = nNew; } + assert( nList>0 ); memcpy(pMsr->aBuffer, pList, nList); + memset(&pMsr->aBuffer[nList], 0, FTS3_NODE_PADDING); return SQLITE_OK; } @@ -173069,7 +207046,7 @@ SQLITE_PRIVATE int sqlite3Fts3MsrIncrNext( rc = fts3SegReaderNextDocid(p, apSegment[0], &pList, &nList); j = 1; - while( rc==SQLITE_OK + while( rc==SQLITE_OK && jpOffsetList && apSegment[j]->iDocid==iDocid @@ -173081,7 +207058,7 @@ SQLITE_PRIVATE int sqlite3Fts3MsrIncrNext( fts3SegReaderSort(pMsr->apSegment, nMerge, j, xCmp); if( nList>0 && fts3SegReaderIsPending(apSegment[0]) ){ - rc = fts3MsrBufferData(pMsr, pList, nList+1); + rc = fts3MsrBufferData(pMsr, pList, (i64)nList+1); if( rc!=SQLITE_OK ) return rc; assert( (pMsr->aBuffer[nList] & 0xFE)==0x00 ); pList = pMsr->aBuffer; @@ -173112,7 +207089,7 @@ static int fts3SegReaderStart( int i; int nSeg = pCsr->nSegment; - /* If the Fts3SegFilter defines a specific term (or term prefix) to search + /* If the Fts3SegFilter defines a specific term (or term prefix) to search ** for, then advance each segment iterator until it points to a term of ** equal or greater value than the specified term. This prevents many ** unnecessary merge/sort operations for the case where single segment @@ -173196,7 +207173,7 @@ SQLITE_PRIVATE int sqlite3Fts3MsrIncrStart( ** sqlite3Fts3SegReaderStart() ** sqlite3Fts3SegReaderStep() ** -** then the entire doclist for the term is available in +** then the entire doclist for the term is available in ** MultiSegReader.aDoclist/nDoclist. */ SQLITE_PRIVATE int sqlite3Fts3MsrIncrRestart(Fts3MultiSegReader *pCsr){ @@ -173218,6 +207195,19 @@ SQLITE_PRIVATE int sqlite3Fts3MsrIncrRestart(Fts3MultiSegReader *pCsr){ return SQLITE_OK; } +static int fts3GrowSegReaderBuffer(Fts3MultiSegReader *pCsr, i64 nReq){ + if( nReq>pCsr->nBuffer ){ + char *aNew; + pCsr->nBuffer = nReq*2; + aNew = sqlite3_realloc64(pCsr->aBuffer, pCsr->nBuffer); + if( !aNew ){ + return SQLITE_NOMEM; + } + pCsr->aBuffer = aNew; + } + return SQLITE_OK; +} + SQLITE_PRIVATE int sqlite3Fts3SegReaderStep( Fts3Table *p, /* Virtual table handle */ @@ -173244,9 +207234,9 @@ SQLITE_PRIVATE int sqlite3Fts3SegReaderStep( do { int nMerge; int i; - + /* Advance the first pCsr->nAdvance entries in the apSegment[] array - ** forward. Then sort the list in order of current term again. + ** forward. Then sort the list in order of current term again. */ for(i=0; inAdvance; i++){ Fts3SegReader *pSeg = apSegment[i]; @@ -173268,39 +207258,40 @@ SQLITE_PRIVATE int sqlite3Fts3SegReaderStep( pCsr->zTerm = apSegment[0]->zTerm; /* If this is a prefix-search, and if the term that apSegment[0] points - ** to does not share a suffix with pFilter->zTerm/nTerm, then all + ** to does not share a suffix with pFilter->zTerm/nTerm, then all ** required callbacks have been made. In this case exit early. ** ** Similarly, if this is a search for an exact match, and the first term ** of segment apSegment[0] is not a match, exit early. */ if( pFilter->zTerm && !isScan ){ - if( pCsr->nTermnTerm + if( pCsr->nTermnTerm || (!isPrefix && pCsr->nTerm>pFilter->nTerm) - || memcmp(pCsr->zTerm, pFilter->zTerm, pFilter->nTerm) + || memcmp(pCsr->zTerm, pFilter->zTerm, pFilter->nTerm) ){ break; } } nMerge = 1; - while( nMergeaNode - && apSegment[nMerge]->nTerm==pCsr->nTerm + && apSegment[nMerge]->nTerm==pCsr->nTerm && 0==memcmp(pCsr->zTerm, apSegment[nMerge]->zTerm, pCsr->nTerm) ){ nMerge++; } assert( isIgnoreEmpty || (isRequirePos && !isColFilter) ); - if( nMerge==1 - && !isIgnoreEmpty - && !isFirst + if( nMerge==1 + && !isIgnoreEmpty + && !isFirst && (p->bDescIdx==0 || fts3SegReaderIsPending(apSegment[0])==0) ){ pCsr->nDoclist = apSegment[0]->nDoclist; if( fts3SegReaderIsPending(apSegment[0]) ){ - rc = fts3MsrBufferData(pCsr, apSegment[0]->aDoclist, pCsr->nDoclist); + rc = fts3MsrBufferData(pCsr, apSegment[0]->aDoclist, + (i64)pCsr->nDoclist); pCsr->aDoclist = pCsr->aBuffer; }else{ pCsr->aDoclist = apSegment[0]->aDoclist; @@ -173340,34 +207331,27 @@ SQLITE_PRIVATE int sqlite3Fts3SegReaderStep( if( !isIgnoreEmpty || nList>0 ){ - /* Calculate the 'docid' delta value to write into the merged + /* Calculate the 'docid' delta value to write into the merged ** doclist. */ sqlite3_int64 iDelta; if( p->bDescIdx && nDoclist>0 ){ - iDelta = iPrev - iDocid; + if( iPrev<=iDocid ) return FTS_CORRUPT_VTAB; + iDelta = (i64)((u64)iPrev - (u64)iDocid); }else{ - iDelta = iDocid - iPrev; - } - if( iDelta<=0 && (nDoclist>0 || iDelta!=iDocid) ){ - return FTS_CORRUPT_VTAB; + if( nDoclist>0 && iPrev>=iDocid ) return FTS_CORRUPT_VTAB; + iDelta = (i64)((u64)iDocid - (u64)iPrev); } - assert( nDoclist>0 || iDelta==iDocid ); nByte = sqlite3Fts3VarintLen(iDelta) + (isRequirePos?nList+1:0); - if( nDoclist+nByte>pCsr->nBuffer ){ - char *aNew; - pCsr->nBuffer = (nDoclist+nByte)*2; - aNew = sqlite3_realloc(pCsr->aBuffer, pCsr->nBuffer); - if( !aNew ){ - return SQLITE_NOMEM; - } - pCsr->aBuffer = aNew; - } + + rc = fts3GrowSegReaderBuffer(pCsr, + (i64)nByte+nDoclist+FTS3_NODE_PADDING); + if( rc ) return rc; if( isFirst ){ char *a = &pCsr->aBuffer[nDoclist]; int nWrite; - + nWrite = sqlite3Fts3FirstFilter(iDelta, pList, nList, a); if( nWrite ){ iPrev = iDocid; @@ -173387,6 +207371,9 @@ SQLITE_PRIVATE int sqlite3Fts3SegReaderStep( fts3SegReaderSort(apSegment, nMerge, j, xCmp); } if( nDoclist>0 ){ + rc = fts3GrowSegReaderBuffer(pCsr, (i64)nDoclist+FTS3_NODE_PADDING); + if( rc ) return rc; + memset(&pCsr->aBuffer[nDoclist], 0, FTS3_NODE_PADDING); pCsr->aDoclist = pCsr->aBuffer; pCsr->nDoclist = nDoclist; rc = SQLITE_ROW; @@ -173417,18 +207404,18 @@ SQLITE_PRIVATE void sqlite3Fts3SegReaderFinish( } /* -** Decode the "end_block" field, selected by column iCol of the SELECT -** statement passed as the first argument. +** Decode the "end_block" field, selected by column iCol of the SELECT +** statement passed as the first argument. ** ** The "end_block" field may contain either an integer, or a text field -** containing the text representation of two non-negative integers separated -** by one or more space (0x20) characters. In the first case, set *piEndBlock -** to the integer value and *pnByte to zero before returning. In the second, +** containing the text representation of two non-negative integers separated +** by one or more space (0x20) characters. In the first case, set *piEndBlock +** to the integer value and *pnByte to zero before returning. In the second, ** set *piEndBlock to the first value and *pnByte to the second. */ static void fts3ReadEndBlockField( - sqlite3_stmt *pStmt, - int iCol, + sqlite3_stmt *pStmt, + int iCol, i64 *piEndBlock, i64 *pnByte ){ @@ -173436,11 +207423,11 @@ static void fts3ReadEndBlockField( if( zText ){ int i; int iMul = 1; - i64 iVal = 0; + u64 iVal = 0; for(i=0; zText[i]>='0' && zText[i]<='9'; i++){ iVal = iVal*10 + (zText[i] - '0'); } - *piEndBlock = iVal; + *piEndBlock = (i64)iVal; while( zText[i]==' ' ) i++; iVal = 0; if( zText[i]=='-' ){ @@ -173450,7 +207437,11 @@ static void fts3ReadEndBlockField( for(/* no-op */; zText[i]>='0' && zText[i]<='9'; i++){ iVal = iVal*10 + (zText[i] - '0'); } - *pnByte = (iVal * (i64)iMul); + + /* This if() clause is just to avoid an integer overflow. The record is + ** corrupt in this case. */ + if( (i64)iVal==SMALLEST_INT64 ) iMul = 1; + *pnByte = ((i64)iVal * (i64)iMul); } } @@ -173474,10 +207465,10 @@ static int fts3PromoteSegments( i64 iLast = (iAbsLevel/FTS3_SEGDIR_MAXLEVEL + 1) * FTS3_SEGDIR_MAXLEVEL - 1; i64 nLimit = (nByte*3)/2; - /* Loop through all entries in the %_segdir table corresponding to + /* Loop through all entries in the %_segdir table corresponding to ** segments in this index on levels greater than iAbsLevel. If there is - ** at least one such segment, and it is possible to determine that all - ** such segments are smaller than nLimit bytes in size, they will be + ** at least one such segment, and it is possible to determine that all + ** such segments are smaller than nLimit bytes in size, they will be ** promoted to level iAbsLevel. */ sqlite3_bind_int64(pRange, 1, iAbsLevel+1); sqlite3_bind_int64(pRange, 2, iLast); @@ -173485,7 +207476,7 @@ static int fts3PromoteSegments( i64 nSize = 0, dummy; fts3ReadEndBlockField(pRange, 2, &dummy, &nSize); if( nSize<=0 || nSize>nLimit ){ - /* If nSize==0, then the %_segdir.end_block field does not not + /* If nSize==0, then the %_segdir.end_block field does not not ** contain a size value. This happens if it was written by an ** old version of FTS. In this case it is not possible to determine ** the size of the segment, and so segment promotion does not @@ -173551,18 +207542,18 @@ static int fts3PromoteSegments( } /* -** Merge all level iLevel segments in the database into a single +** Merge all level iLevel segments in the database into a single ** iLevel+1 segment. Or, if iLevel<0, merge all segments into a -** single segment with a level equal to the numerically largest level +** single segment with a level equal to the numerically largest level ** currently present in the database. ** ** If this function is called with iLevel<0, but there is only one -** segment in the database, SQLITE_DONE is returned immediately. -** Otherwise, if successful, SQLITE_OK is returned. If an error occurs, +** segment in the database, SQLITE_DONE is returned immediately. +** Otherwise, if successful, SQLITE_OK is returned. If an error occurs, ** an SQLite error code is returned. */ static int fts3SegmentMerge( - Fts3Table *p, + Fts3Table *p, int iLangid, /* Language id to merge */ int iIndex, /* Index in p->aIndex[] to merge */ int iLevel /* Level to merge */ @@ -173606,7 +207597,7 @@ static int fts3SegmentMerge( }else{ /* This call is to merge all segments at level iLevel. find the next ** available segment index at level iLevel+1. The call to - ** fts3AllocateSegdirIdx() will merge the segments at level iLevel+1 to + ** fts3AllocateSegdirIdx() will merge the segments at level iLevel+1 to ** a single iLevel+2 segment if necessary. */ assert( FTS3_SEGCURSOR_PENDING==-1 ); iNewLevel = getAbsoluteLevel(p, iLangid, iIndex, iLevel+1); @@ -173617,8 +207608,8 @@ static int fts3SegmentMerge( assert( csr.nSegment>0 ); assert_fts3_nc( iNewLevel>=getAbsoluteLevel(p, iLangid, iIndex, 0) ); - assert_fts3_nc( - iNewLevelnIndex; i++){ rc = fts3SegmentMerge(p, p->iPrevLangid, i, FTS3_SEGCURSOR_PENDING); if( rc==SQLITE_DONE ) rc = SQLITE_OK; } - sqlite3Fts3PendingTermsClear(p); /* Determine the auto-incr-merge setting if unknown. If enabled, ** estimate the number of leaf blocks of content to be written @@ -173690,6 +207680,10 @@ SQLITE_PRIVATE int sqlite3Fts3PendingTermsFlush(Fts3Table *p){ rc = sqlite3_reset(pStmt); } } + + if( rc==SQLITE_OK ){ + sqlite3Fts3PendingTermsClear(p); + } return rc; } @@ -173767,7 +207761,7 @@ static void fts3InsertDocsize( /* ** Record 0 of the %_stat table contains a blob consisting of N varints, ** where N is the number of user defined columns in the fts3 table plus -** two. If nCol is the number of user defined columns, then values of the +** two. If nCol is the number of user defined columns, then values of the ** varints are set as follows: ** ** Varint 0: Total number of rows in the table. @@ -173852,7 +207846,7 @@ static void fts3UpdateDocTotals( } /* -** Merge the entire database so that there is one segment for each +** Merge the entire database so that there is one segment for each ** iIndex/iLangid combination. */ static int fts3DoOptimize(Fts3Table *p, int bReturnDone){ @@ -173860,7 +207854,10 @@ static int fts3DoOptimize(Fts3Table *p, int bReturnDone){ int rc; sqlite3_stmt *pAllLangid = 0; - rc = fts3SqlStmt(p, SQL_SELECT_ALL_LANGID, &pAllLangid, 0); + rc = sqlite3Fts3PendingTermsFlush(p); + if( rc==SQLITE_OK ){ + rc = fts3SqlStmt(p, SQL_SELECT_ALL_LANGID, &pAllLangid, 0); + } if( rc==SQLITE_OK ){ int rc2; sqlite3_bind_int(pAllLangid, 1, p->iPrevLangid); @@ -173881,7 +207878,6 @@ static int fts3DoOptimize(Fts3Table *p, int bReturnDone){ } sqlite3Fts3SegmentsClose(p); - sqlite3Fts3PendingTermsClear(p); return (rc==SQLITE_OK && bReturnDone && bSeenDone) ? SQLITE_DONE : rc; } @@ -173891,7 +207887,7 @@ static int fts3DoOptimize(Fts3Table *p, int bReturnDone){ ** ** INSERT INTO () VALUES('rebuild'); ** -** The entire FTS index is discarded and rebuilt. If the table is one +** The entire FTS index is discarded and rebuilt. If the table is one ** created using the content=xxx option, then the new index is based on ** the current contents of the xxx table. Otherwise, it is rebuilt based ** on the contents of the %_content table. @@ -173912,7 +207908,7 @@ static int fts3DoRebuild(Fts3Table *p){ if( !zSql ){ rc = SQLITE_NOMEM; }else{ - rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); + rc = sqlite3Fts3PrepareStmt(p, zSql, 0, 1, &pStmt); sqlite3_free(zSql); } @@ -173971,9 +207967,9 @@ static int fts3DoRebuild(Fts3Table *p){ /* -** This function opens a cursor used to read the input data for an +** This function opens a cursor used to read the input data for an ** incremental merge operation. Specifically, it opens a cursor to scan -** the oldest nSeg segments (idx=0 through idx=(nSeg-1)) in absolute +** the oldest nSeg segments (idx=0 through idx=(nSeg-1)) in absolute ** level iAbsLevel. */ static int fts3IncrmergeCsr( @@ -173983,7 +207979,7 @@ static int fts3IncrmergeCsr( Fts3MultiSegReader *pCsr /* Cursor object to populate */ ){ int rc; /* Return Code */ - sqlite3_stmt *pStmt = 0; /* Statement used to read %_segdir entry */ + sqlite3_stmt *pStmt = 0; /* Statement used to read %_segdir entry */ sqlite3_int64 nByte; /* Bytes allocated at pCsr->apSegment[] */ /* Allocate space for the Fts3MultiSegReader.aCsr[] array */ @@ -174038,7 +208034,7 @@ struct Blob { }; /* -** This structure is used to build up buffers containing segment b-tree +** This structure is used to build up buffers containing segment b-tree ** nodes (blocks). */ struct NodeWriter { @@ -174052,8 +208048,8 @@ struct NodeWriter { ** to an appendable b-tree segment. */ struct IncrmergeWriter { - int nLeafEst; /* Space allocated for leaf blocks */ - int nWork; /* Number of leaf pages flushed */ + i64 nLeafEst; /* Space allocated for leaf blocks */ + i64 nWork; /* Number of leaf pages flushed */ sqlite3_int64 iAbsLevel; /* Absolute level of input segments */ int iIdx; /* Index of *output* segment in iAbsLevel+1 */ sqlite3_int64 iStart; /* Block number of first allocated block */ @@ -174095,7 +208091,7 @@ struct NodeReader { static void blobGrowBuffer(Blob *pBlob, int nMin, int *pRc){ if( *pRc==SQLITE_OK && nMin>pBlob->nAlloc ){ int nAlloc = nMin; - char *a = (char *)sqlite3_realloc(pBlob->a, nAlloc); + char *a = (char *)sqlite3_realloc64(pBlob->a, nAlloc); if( a ){ pBlob->nAlloc = nAlloc; pBlob->a = a; @@ -174107,12 +208103,12 @@ static void blobGrowBuffer(Blob *pBlob, int nMin, int *pRc){ /* ** Attempt to advance the node-reader object passed as the first argument to -** the next entry on the node. +** the next entry on the node. ** -** Return an error code if an error occurs (SQLITE_NOMEM is possible). +** Return an error code if an error occurs (SQLITE_NOMEM is possible). ** Otherwise return SQLITE_OK. If there is no next entry on the node ** (e.g. because the current entry is the last) set NodeReader->aNode to -** NULL to indicate EOF. Otherwise, populate the NodeReader structure output +** NULL to indicate EOF. Otherwise, populate the NodeReader structure output ** variables for the new entry. */ static int nodeReaderNext(NodeReader *p){ @@ -174132,11 +208128,11 @@ static int nodeReaderNext(NodeReader *p){ } p->iOff += fts3GetVarint32(&p->aNode[p->iOff], &nSuffix); - if( nPrefix>p->iOff || nSuffix>p->nNode-p->iOff ){ + if( nPrefix>p->term.n || nSuffix>p->nNode-p->iOff || nSuffix==0 ){ return FTS_CORRUPT_VTAB; } blobGrowBuffer(&p->term, nPrefix+nSuffix, &rc); - if( rc==SQLITE_OK ){ + if( rc==SQLITE_OK && ALWAYS(p->term.a!=0) ){ memcpy(&p->term.a[nPrefix], &p->aNode[p->iOff], nSuffix); p->term.n = nPrefix+nSuffix; p->iOff += nSuffix; @@ -174151,7 +208147,7 @@ static int nodeReaderNext(NodeReader *p){ } } - assert( p->iOff<=p->nNode ); + assert_fts3_nc( p->iOff<=p->nNode ); return rc; } @@ -174165,7 +208161,7 @@ static void nodeReaderRelease(NodeReader *p){ /* ** Initialize a node-reader object to read the node in buffer aNode/nNode. ** -** If successful, SQLITE_OK is returned and the NodeReader object set to +** If successful, SQLITE_OK is returned and the NodeReader object set to ** point to the first entry on the node (if any). Otherwise, an SQLite ** error code is returned. */ @@ -174175,14 +208171,14 @@ static int nodeReaderInit(NodeReader *p, const char *aNode, int nNode){ p->nNode = nNode; /* Figure out if this is a leaf or an internal node. */ - if( p->aNode[0] ){ + if( aNode && aNode[0] ){ /* An internal node. */ p->iOff = 1 + sqlite3Fts3GetVarint(&p->aNode[1], &p->iChild); }else{ p->iOff = 1; } - return nodeReaderNext(p); + return aNode ? nodeReaderNext(p) : SQLITE_OK; } /* @@ -174214,17 +208210,18 @@ static int fts3IncrmergePush( int nSpace; /* Figure out how much space the key will consume if it is written to - ** the current node of layer iLayer. Due to the prefix compression, + ** the current node of layer iLayer. Due to the prefix compression, ** the space required changes depending on which node the key is to ** be added to. */ nPrefix = fts3PrefixCompress(pNode->key.a, pNode->key.n, zTerm, nTerm); nSuffix = nTerm - nPrefix; + if(nSuffix<=0 ) return FTS_CORRUPT_VTAB; nSpace = sqlite3Fts3VarintLen(nPrefix); nSpace += sqlite3Fts3VarintLen(nSuffix) + nSuffix; - if( pNode->key.n==0 || (pNode->block.n + nSpace)<=p->nNodeSize ){ + if( pNode->key.n==0 || (pNode->block.n + nSpace)<=p->nNodeSize ){ /* If the current node of layer iLayer contains zero keys, or if adding - ** the key to it will not cause it to grow to larger than nNodeSize + ** the key to it will not cause it to grow to larger than nNodeSize ** bytes in size, write the key here. */ Blob *pBlk = &pNode->block; @@ -174243,6 +208240,8 @@ static int fts3IncrmergePush( pBlk->n += sqlite3Fts3PutVarint(&pBlk->a[pBlk->n], nPrefix); } pBlk->n += sqlite3Fts3PutVarint(&pBlk->a[pBlk->n], nSuffix); + assert( nPrefix+nSuffix<=nTerm ); + assert( nPrefix>=0 ); memcpy(&pBlk->a[pBlk->n], &zTerm[nPrefix], nSuffix); pBlk->n += nSuffix; @@ -174280,18 +208279,18 @@ static int fts3IncrmergePush( ** A node header is a single 0x00 byte for a leaf node, or a height varint ** followed by the left-hand-child varint for an internal node. ** -** The term to be appended is passed via arguments zTerm/nTerm. For a +** The term to be appended is passed via arguments zTerm/nTerm. For a ** leaf node, the doclist is passed as aDoclist/nDoclist. For an internal ** node, both aDoclist and nDoclist must be passed 0. ** ** If the size of the value in blob pPrev is zero, then this is the first -** term written to the node. Otherwise, pPrev contains a copy of the +** term written to the node. Otherwise, pPrev contains a copy of the ** previous term. Before this function returns, it is updated to contain a ** copy of zTerm/nTerm. ** ** It is assumed that the buffer associated with pNode is already large ** enough to accommodate the new entry. The buffer associated with pPrev -** is extended by this function if requrired. +** is extended by this function if required. ** ** If an error (i.e. OOM condition) occurs, an SQLite error code is ** returned. Otherwise, SQLITE_OK. @@ -174302,7 +208301,7 @@ static int fts3AppendToNode( const char *zTerm, /* New term to write */ int nTerm, /* Size of zTerm in bytes */ const char *aDoclist, /* Doclist (or NULL) to write */ - int nDoclist /* Size of aDoclist in bytes */ + int nDoclist /* Size of aDoclist in bytes */ ){ int rc = SQLITE_OK; /* Return code */ int bFirst = (pPrev->n==0); /* True if this is the first term written */ @@ -174312,13 +208311,16 @@ static int fts3AppendToNode( /* Node must have already been started. There must be a doclist for a ** leaf node, and there must not be a doclist for an internal node. */ assert( pNode->n>0 ); - assert( (pNode->a[0]=='\0')==(aDoclist!=0) ); + assert_fts3_nc( (pNode->a[0]=='\0')==(aDoclist!=0) ); blobGrowBuffer(pPrev, nTerm, &rc); if( rc!=SQLITE_OK ) return rc; + assert( pPrev!=0 ); + assert( pPrev->a!=0 ); nPrefix = fts3PrefixCompress(pPrev->a, pPrev->n, zTerm, nTerm); nSuffix = nTerm - nPrefix; + if( nSuffix<=0 ) return FTS_CORRUPT_VTAB; memcpy(pPrev->a, zTerm, nTerm); pPrev->n = nTerm; @@ -174364,19 +208366,24 @@ static int fts3IncrmergeAppend( pLeaf = &pWriter->aNodeWriter[0]; nPrefix = fts3PrefixCompress(pLeaf->key.a, pLeaf->key.n, zTerm, nTerm); nSuffix = nTerm - nPrefix; + if(nSuffix<=0 ) return FTS_CORRUPT_VTAB; nSpace = sqlite3Fts3VarintLen(nPrefix); nSpace += sqlite3Fts3VarintLen(nSuffix) + nSuffix; nSpace += sqlite3Fts3VarintLen(nDoclist) + nDoclist; /* If the current block is not empty, and if adding this term/doclist - ** to the current block would make it larger than Fts3Table.nNodeSize - ** bytes, write this block out to the database. */ - if( pLeaf->block.n>0 && (pLeaf->block.n + nSpace)>p->nNodeSize ){ + ** to the current block would make it larger than Fts3Table.nNodeSize bytes, + ** and if there is still room for another leaf page, write this block out to + ** the database. */ + if( pLeaf->block.n>0 + && (pLeaf->block.n + nSpace)>p->nNodeSize + && pLeaf->iBlock < (pWriter->iStart + pWriter->nLeafEst) + ){ rc = fts3WriteSegment(p, pLeaf->iBlock, pLeaf->block.a, pLeaf->block.n); pWriter->nWork++; - /* Add the current term to the parent node. The term added to the + /* Add the current term to the parent node. The term added to the ** parent must: ** ** a) be greater than the largest term on the leaf node just written @@ -174441,7 +208448,7 @@ static void fts3IncrmergeRelease( NodeWriter *pRoot; /* NodeWriter for root node */ int rc = *pRc; /* Error code */ - /* Set iRoot to the index in pWriter->aNodeWriter[] of the output segment + /* Set iRoot to the index in pWriter->aNodeWriter[] of the output segment ** root node. If the segment fits entirely on a single leaf node, iRoot ** will be set to 0. If the root node is the parent of the leaves, iRoot ** will be 1. And so on. */ @@ -174459,17 +208466,17 @@ static void fts3IncrmergeRelease( /* The entire output segment fits on a single node. Normally, this means ** the node would be stored as a blob in the "root" column of the %_segdir - ** table. However, this is not permitted in this case. The problem is that - ** space has already been reserved in the %_segments table, and so the - ** start_block and end_block fields of the %_segdir table must be populated. - ** And, by design or by accident, released versions of FTS cannot handle + ** table. However, this is not permitted in this case. The problem is that + ** space has already been reserved in the %_segments table, and so the + ** start_block and end_block fields of the %_segdir table must be populated. + ** And, by design or by accident, released versions of FTS cannot handle ** segments that fit entirely on the root node with start_block!=0. ** - ** Instead, create a synthetic root node that contains nothing but a + ** Instead, create a synthetic root node that contains nothing but a ** pointer to the single content node. So that the segment consists of a ** single leaf and a single interior (root) node. ** - ** Todo: Better might be to defer allocating space in the %_segments + ** Todo: Better might be to defer allocating space in the %_segments ** table until we are sure it is needed. */ if( iRoot==0 ){ @@ -174497,7 +208504,7 @@ static void fts3IncrmergeRelease( /* Write the %_segdir record. */ if( rc==SQLITE_OK ){ - rc = fts3WriteSegdir(p, + rc = fts3WriteSegdir(p, pWriter->iAbsLevel+1, /* level */ pWriter->iIdx, /* idx */ pWriter->iStart, /* start_block */ @@ -174528,7 +208535,11 @@ static int fts3TermCmp( int nCmp = MIN(nLhs, nRhs); int res; - res = memcmp(zLhs, zRhs, nCmp); + if( nCmp && ALWAYS(zLhs) && ALWAYS(zRhs) ){ + res = memcmp(zLhs, zRhs, nCmp); + }else{ + res = 0; + } if( res==0 ) res = nLhs - nRhs; return res; @@ -174536,11 +208547,11 @@ static int fts3TermCmp( /* -** Query to see if the entry in the %_segments table with blockid iEnd is +** Query to see if the entry in the %_segments table with blockid iEnd is ** NULL. If no error occurs and the entry is NULL, set *pbRes 1 before -** returning. Otherwise, set *pbRes to 0. +** returning. Otherwise, set *pbRes to 0. ** -** Or, if an error occurs while querying the database, return an SQLite +** Or, if an error occurs while querying the database, return an SQLite ** error code. The final value of *pbRes is undefined in this case. ** ** This is used to test if a segment is an "appendable" segment. If it @@ -174558,14 +208569,14 @@ static int fts3IsAppendable(Fts3Table *p, sqlite3_int64 iEnd, int *pbRes){ if( SQLITE_ROW==sqlite3_step(pCheck) ) bRes = 1; rc = sqlite3_reset(pCheck); } - + *pbRes = bRes; return rc; } /* ** This function is called when initializing an incremental-merge operation. -** It checks if the existing segment with index value iIdx at absolute level +** It checks if the existing segment with index value iIdx at absolute level ** (iAbsLevel+1) can be appended to by the incremental merge. If it can, the ** merge-writer object *pWriter is initialized to write to it. ** @@ -174574,7 +208585,7 @@ static int fts3IsAppendable(Fts3Table *p, sqlite3_int64 iEnd, int *pbRes){ ** * It was initially created as an appendable segment (with all required ** space pre-allocated), and ** -** * The first key read from the input (arguments zKey and nKey) is +** * The first key read from the input (arguments zKey and nKey) is ** greater than the largest key currently stored in the potential ** output segment. */ @@ -174612,6 +208623,10 @@ static int fts3IncrmergeLoad( pWriter->bNoLeafData = (pWriter->nLeafData==0); nRoot = sqlite3_column_bytes(pSelect, 4); aRoot = sqlite3_column_blob(pSelect, 4); + if( aRoot==0 ){ + sqlite3_reset(pSelect); + return nRoot ? SQLITE_NOMEM : FTS_CORRUPT_VTAB; + } }else{ return sqlite3_reset(pSelect); } @@ -174647,8 +208662,12 @@ static int fts3IncrmergeLoad( int i; int nHeight = (int)aRoot[0]; NodeWriter *pNode; + if( nHeight<1 || nHeight>=FTS_MAX_APPENDABLE_HEIGHT ){ + sqlite3_reset(pSelect); + return FTS_CORRUPT_VTAB; + } - pWriter->nLeafEst = (int)((iEnd - iStart) + 1)/FTS_MAX_APPENDABLE_HEIGHT; + pWriter->nLeafEst = (int)(((iEnd - iStart)+1)/FTS_MAX_APPENDABLE_HEIGHT); pWriter->iStart = iStart; pWriter->iEnd = iEnd; pWriter->iAbsLevel = iAbsLevel; @@ -174660,34 +208679,46 @@ static int fts3IncrmergeLoad( pNode = &pWriter->aNodeWriter[nHeight]; pNode->iBlock = pWriter->iStart + pWriter->nLeafEst*nHeight; - blobGrowBuffer(&pNode->block, MAX(nRoot, p->nNodeSize), &rc); + blobGrowBuffer(&pNode->block, + MAX(nRoot, p->nNodeSize)+FTS3_NODE_PADDING, &rc + ); if( rc==SQLITE_OK ){ memcpy(pNode->block.a, aRoot, nRoot); pNode->block.n = nRoot; + memset(&pNode->block.a[nRoot], 0, FTS3_NODE_PADDING); } for(i=nHeight; i>=0 && rc==SQLITE_OK; i--){ NodeReader reader; + memset(&reader, 0, sizeof(reader)); pNode = &pWriter->aNodeWriter[i]; - rc = nodeReaderInit(&reader, pNode->block.a, pNode->block.n); - while( reader.aNode && rc==SQLITE_OK ) rc = nodeReaderNext(&reader); - blobGrowBuffer(&pNode->key, reader.term.n, &rc); - if( rc==SQLITE_OK ){ - memcpy(pNode->key.a, reader.term.a, reader.term.n); - pNode->key.n = reader.term.n; - if( i>0 ){ - char *aBlock = 0; - int nBlock = 0; - pNode = &pWriter->aNodeWriter[i-1]; - pNode->iBlock = reader.iChild; - rc = sqlite3Fts3ReadBlock(p, reader.iChild, &aBlock, &nBlock, 0); - blobGrowBuffer(&pNode->block, MAX(nBlock, p->nNodeSize), &rc); - if( rc==SQLITE_OK ){ - memcpy(pNode->block.a, aBlock, nBlock); - pNode->block.n = nBlock; + if( pNode->block.a){ + rc = nodeReaderInit(&reader, pNode->block.a, pNode->block.n); + while( reader.aNode && rc==SQLITE_OK ) rc = nodeReaderNext(&reader); + blobGrowBuffer(&pNode->key, reader.term.n, &rc); + if( rc==SQLITE_OK ){ + assert_fts3_nc( reader.term.n>0 || reader.aNode==0 ); + if( reader.term.n>0 ){ + memcpy(pNode->key.a, reader.term.a, reader.term.n); + } + pNode->key.n = reader.term.n; + if( i>0 ){ + char *aBlock = 0; + int nBlock = 0; + pNode = &pWriter->aNodeWriter[i-1]; + pNode->iBlock = reader.iChild; + rc = sqlite3Fts3ReadBlock(p, reader.iChild, &aBlock, &nBlock,0); + blobGrowBuffer(&pNode->block, + MAX(nBlock, p->nNodeSize)+FTS3_NODE_PADDING, &rc + ); + if( rc==SQLITE_OK ){ + memcpy(pNode->block.a, aBlock, nBlock); + pNode->block.n = nBlock; + memset(&pNode->block.a[nBlock], 0, FTS3_NODE_PADDING); + } + sqlite3_free(aBlock); } - sqlite3_free(aBlock); } } nodeReaderRelease(&reader); @@ -174704,13 +208735,13 @@ static int fts3IncrmergeLoad( /* ** Determine the largest segment index value that exists within absolute ** level iAbsLevel+1. If no error occurs, set *piIdx to this value plus -** one before returning SQLITE_OK. Or, if there are no segments at all +** one before returning SQLITE_OK. Or, if there are no segments at all ** within level iAbsLevel, set *piIdx to zero. ** ** If an error occurs, return an SQLite error code. The final value of ** *piIdx is undefined in this case. */ -static int fts3IncrmergeOutputIdx( +static int fts3IncrmergeOutputIdx( Fts3Table *p, /* FTS Table handle */ sqlite3_int64 iAbsLevel, /* Absolute index of input segments */ int *piIdx /* OUT: Next free index at iAbsLevel+1 */ @@ -174729,7 +208760,7 @@ static int fts3IncrmergeOutputIdx( return rc; } -/* +/* ** Allocate an appendable output segment on absolute level iAbsLevel+1 ** with idx value iIdx. ** @@ -174743,7 +208774,7 @@ static int fts3IncrmergeOutputIdx( ** When an appendable segment is allocated, it is estimated that the ** maximum number of leaf blocks that may be required is the sum of the ** number of leaf blocks consumed by the input segments, plus the number -** of input segments, multiplied by two. This value is stored in stack +** of input segments, multiplied by two. This value is stored in stack ** variable nLeafEst. ** ** A total of 16*nLeafEst blocks are allocated when an appendable segment @@ -174752,10 +208783,10 @@ static int fts3IncrmergeOutputIdx( ** of interior nodes that are parents of the leaf nodes start at block ** (start_block + (1 + end_block - start_block) / 16). And so on. ** -** In the actual code below, the value "16" is replaced with the +** In the actual code below, the value "16" is replaced with the ** pre-processor macro FTS_MAX_APPENDABLE_HEIGHT. */ -static int fts3IncrmergeWriter( +static int fts3IncrmergeWriter( Fts3Table *p, /* Fts3 table handle */ sqlite3_int64 iAbsLevel, /* Absolute level of input segments */ int iIdx, /* Index of new output segment */ @@ -174764,7 +208795,7 @@ static int fts3IncrmergeWriter( ){ int rc; /* Return Code */ int i; /* Iterator variable */ - int nLeafEst = 0; /* Blocks allocated for leaf nodes */ + i64 nLeafEst = 0; /* Blocks allocated for leaf nodes */ sqlite3_stmt *pLeafEst = 0; /* SQL used to determine nLeafEst */ sqlite3_stmt *pFirstBlock = 0; /* SQL used to determine first block */ @@ -174774,7 +208805,7 @@ static int fts3IncrmergeWriter( sqlite3_bind_int64(pLeafEst, 1, iAbsLevel); sqlite3_bind_int64(pLeafEst, 2, pCsr->nSegment); if( SQLITE_ROW==sqlite3_step(pLeafEst) ){ - nLeafEst = sqlite3_column_int(pLeafEst, 0); + nLeafEst = sqlite3_column_int64(pLeafEst, 0); } rc = sqlite3_reset(pLeafEst); } @@ -174793,7 +208824,7 @@ static int fts3IncrmergeWriter( if( rc!=SQLITE_OK ) return rc; /* Insert the marker in the %_segments table to make sure nobody tries - ** to steal the space just allocated. This is also used to identify + ** to steal the space just allocated. This is also used to identify ** appendable segments. */ rc = fts3WriteSegment(p, pWriter->iEnd, 0, 0); if( rc!=SQLITE_OK ) return rc; @@ -174810,13 +208841,13 @@ static int fts3IncrmergeWriter( } /* -** Remove an entry from the %_segdir table. This involves running the +** Remove an entry from the %_segdir table. This involves running the ** following two statements: ** ** DELETE FROM %_segdir WHERE level = :iAbsLevel AND idx = :iIdx ** UPDATE %_segdir SET idx = idx - 1 WHERE level = :iAbsLevel AND idx > :iIdx ** -** The DELETE statement removes the specific %_segdir level. The UPDATE +** The DELETE statement removes the specific %_segdir level. The UPDATE ** statement ensures that the remaining segments have contiguously allocated ** idx values. */ @@ -174864,7 +208895,7 @@ static int fts3RepackSegdirLevel( if( nIdx>=nAlloc ){ int *aNew; nAlloc += 16; - aNew = sqlite3_realloc(aIdx, nAlloc*sizeof(int)); + aNew = sqlite3_realloc64(aIdx, nAlloc*sizeof(int)); if( !aNew ){ rc = SQLITE_NOMEM; break; @@ -174930,7 +208961,10 @@ static int fts3TruncateNode( NodeReader reader; /* Reader object */ Blob prev = {0, 0, 0}; /* Previous term written to new node */ int rc = SQLITE_OK; /* Return code */ - int bLeaf = aNode[0]=='\0'; /* True for a leaf node */ + int bLeaf; /* True for a leaf node */ + + if( nNode<1 ) return FTS_CORRUPT_VTAB; + bLeaf = aNode[0]=='\0'; /* Allocate required output space */ blobGrowBuffer(pNew, nNode, &rc); @@ -174938,8 +208972,8 @@ static int fts3TruncateNode( pNew->n = 0; /* Populate new node buffer */ - for(rc = nodeReaderInit(&reader, aNode, nNode); - rc==SQLITE_OK && reader.aNode; + for(rc = nodeReaderInit(&reader, aNode, nNode); + rc==SQLITE_OK && reader.aNode; rc = nodeReaderNext(&reader) ){ if( pNew->n==0 ){ @@ -174966,7 +209000,7 @@ static int fts3TruncateNode( } /* -** Remove all terms smaller than zTerm/nTerm from segment iIdx in absolute +** Remove all terms smaller than zTerm/nTerm from segment iIdx in absolute ** level iAbsLevel. This may involve deleting entries from the %_segments ** table, and modifying existing entries in both the %_segments and %_segdir ** tables. @@ -175090,9 +209124,9 @@ static int fts3IncrmergeChomp( } *pnRem = 0; }else{ - /* The incremental merge did not copy all the data from this + /* The incremental merge did not copy all the data from this ** segment to the upper level. The segment is modified in place - ** so that it contains no keys smaller than zTerm/nTerm. */ + ** so that it contains no keys smaller than zTerm/nTerm. */ const char *zTerm = pSeg->zTerm; int nTerm = pSeg->nTerm; rc = fts3TruncateSegment(p, iAbsLevel, pSeg->iIdx, zTerm, nTerm); @@ -175128,7 +209162,7 @@ static int fts3IncrmergeHintStore(Fts3Table *p, Blob *pHint){ } /* -** Load an incr-merge hint from the database. The incr-merge hint, if one +** Load an incr-merge hint from the database. The incr-merge hint, if one ** exists, is stored in the rowid==1 row of the %_stat table. ** ** If successful, populate blob *pHint with the value read from the %_stat @@ -175150,7 +209184,7 @@ static int fts3IncrmergeHintLoad(Fts3Table *p, Blob *pHint){ if( aHint ){ blobGrowBuffer(pHint, nHint, &rc); if( rc==SQLITE_OK ){ - memcpy(pHint->a, aHint, nHint); + if( ALWAYS(pHint->a!=0) ) memcpy(pHint->a, aHint, nHint); pHint->n = nHint; } } @@ -175165,7 +209199,7 @@ static int fts3IncrmergeHintLoad(Fts3Table *p, Blob *pHint){ /* ** If *pRc is not SQLITE_OK when this function is called, it is a no-op. ** Otherwise, append an entry to the hint stored in blob *pHint. Each entry -** consists of two varints, the absolute level number of the input segments +** consists of two varints, the absolute level number of the input segments ** and the number of input segments. ** ** If successful, leave *pRc set to SQLITE_OK and return. If an error occurs, @@ -175186,7 +209220,7 @@ static void fts3IncrmergeHintPush( /* ** Read the last entry (most recently pushed) from the hint blob *pHint -** and then remove the entry. Write the two values read to *piAbsLevel and +** and then remove the entry. Write the two values read to *piAbsLevel and ** *pnInput before returning. ** ** If no error occurs, return SQLITE_OK. If the hint blob in *pHint does @@ -175196,13 +209230,17 @@ static int fts3IncrmergeHintPop(Blob *pHint, i64 *piAbsLevel, int *pnInput){ const int nHint = pHint->n; int i; - i = pHint->n-2; + i = pHint->n-1; + if( (pHint->a[i] & 0x80) ) return FTS_CORRUPT_VTAB; while( i>0 && (pHint->a[i-1] & 0x80) ) i--; + if( i==0 ) return FTS_CORRUPT_VTAB; + i--; while( i>0 && (pHint->a[i-1] & 0x80) ) i--; pHint->n = i; i += sqlite3Fts3GetVarint(&pHint->a[i], piAbsLevel); i += fts3GetVarint32(&pHint->a[i], pnInput); + assert( i<=nHint ); if( i!=nHint ) return FTS_CORRUPT_VTAB; return SQLITE_OK; @@ -175212,10 +209250,10 @@ static int fts3IncrmergeHintPop(Blob *pHint, i64 *piAbsLevel, int *pnInput){ /* ** Attempt an incremental merge that writes nMerge leaf blocks. ** -** Incremental merges happen nMin segments at a time. The segments -** to be merged are the nMin oldest segments (the ones with the smallest -** values for the _segdir.idx field) in the highest level that contains -** at least nMin segments. Multiple merges might occur in an attempt to +** Incremental merges happen nMin segments at a time. The segments +** to be merged are the nMin oldest segments (the ones with the smallest +** values for the _segdir.idx field) in the highest level that contains +** at least nMin segments. Multiple merges might occur in an attempt to ** write the quota of nMerge leaf blocks. */ SQLITE_PRIVATE int sqlite3Fts3Incrmerge(Fts3Table *p, int nMerge, int nMin){ @@ -175231,7 +209269,7 @@ SQLITE_PRIVATE int sqlite3Fts3Incrmerge(Fts3Table *p, int nMerge, int nMin){ /* Allocate space for the cursor, filter and writer objects */ const int nAlloc = sizeof(*pCsr) + sizeof(*pFilter) + sizeof(*pWriter); - pWriter = (IncrmergeWriter *)sqlite3_malloc(nAlloc); + pWriter = (IncrmergeWriter *)sqlite3_malloc64(nAlloc); if( !pWriter ) return SQLITE_NOMEM; pFilter = (Fts3SegFilter *)&pWriter[1]; pCsr = (Fts3MultiSegReader *)&pFilter[1]; @@ -175246,7 +209284,7 @@ SQLITE_PRIVATE int sqlite3Fts3Incrmerge(Fts3Table *p, int nMerge, int nMin){ /* Search the %_segdir table for the absolute level with the smallest ** relative level number that contains at least nMin segments, if any. ** If one is found, set iAbsLevel to the absolute level number and - ** nSeg to nMin. If no level with at least nMin segments can be found, + ** nSeg to nMin. If no level with at least nMin segments can be found, ** set nSeg to -1. */ rc = fts3SqlStmt(p, SQL_FIND_MERGE_LEVEL, &pFindLevel, 0); @@ -175262,7 +209300,7 @@ SQLITE_PRIVATE int sqlite3Fts3Incrmerge(Fts3Table *p, int nMerge, int nMin){ /* If the hint read from the %_stat table is not empty, check if the ** last entry in it specifies a relative level smaller than or equal - ** to the level identified by the block above (if any). If so, this + ** to the level identified by the block above (if any). If so, this ** iteration of the loop will work on merging at the hinted level. */ if( rc==SQLITE_OK && hint.n ){ @@ -175272,8 +209310,14 @@ SQLITE_PRIVATE int sqlite3Fts3Incrmerge(Fts3Table *p, int nMerge, int nMin){ rc = fts3IncrmergeHintPop(&hint, &iHintAbsLevel, &nHintSeg); if( nSeg<0 || (iAbsLevel % nMod) >= (iHintAbsLevel % nMod) ){ + /* Based on the scan in the block above, it is known that there + ** are no levels with a relative level smaller than that of + ** iAbsLevel with more than nSeg segments, or if nSeg is -1, + ** no levels with more than nMin segments. Use this to limit the + ** value of nHintSeg to avoid a large memory allocation in case the + ** merge-hint is corrupt*/ iAbsLevel = iHintAbsLevel; - nSeg = nHintSeg; + nSeg = MIN(MAX(nMin,nSeg), nHintSeg); bUseHint = 1; bDirtyHint = 1; }else{ @@ -175286,13 +209330,19 @@ SQLITE_PRIVATE int sqlite3Fts3Incrmerge(Fts3Table *p, int nMerge, int nMin){ /* If nSeg is less that zero, then there is no level with at least ** nMin segments and no hint in the %_stat table. No work to do. ** Exit early in this case. */ - if( nSeg<0 ) break; + if( nSeg<=0 ) break; - /* Open a cursor to iterate through the contents of the oldest nSeg - ** indexes of absolute level iAbsLevel. If this cursor is opened using + assert( nMod<=0x7FFFFFFF ); + if( iAbsLevel<0 || iAbsLevel>(nMod<<32) ){ + rc = FTS_CORRUPT_VTAB; + break; + } + + /* Open a cursor to iterate through the contents of the oldest nSeg + ** indexes of absolute level iAbsLevel. If this cursor is opened using ** the 'hint' parameters, it is possible that there are less than nSeg ** segments available in level iAbsLevel. In this case, no work is - ** done on iAbsLevel - fall through to the next iteration of the loop + ** done on iAbsLevel - fall through to the next iteration of the loop ** to start work on some other level. */ memset(pWriter, 0, nAlloc); pFilter->flags = FTS3_SEGMENT_REQUIRE_POS; @@ -175314,8 +209364,15 @@ SQLITE_PRIVATE int sqlite3Fts3Incrmerge(Fts3Table *p, int nMerge, int nMin){ } if( SQLITE_OK==rc && pCsr->nSegment==nSeg && SQLITE_OK==(rc = sqlite3Fts3SegReaderStart(p, pCsr, pFilter)) - && SQLITE_ROW==(rc = sqlite3Fts3SegReaderStep(p, pCsr)) ){ + int bEmpty = 0; + rc = sqlite3Fts3SegReaderStep(p, pCsr); + if( rc==SQLITE_OK ){ + bEmpty = 1; + }else if( rc!=SQLITE_ROW ){ + sqlite3Fts3SegReaderFinish(pCsr); + break; + } if( bUseHint && iIdx>0 ){ const char *zKey = pCsr->zTerm; int nKey = pCsr->nTerm; @@ -175326,11 +209383,13 @@ SQLITE_PRIVATE int sqlite3Fts3Incrmerge(Fts3Table *p, int nMerge, int nMin){ if( rc==SQLITE_OK && pWriter->nLeafEst ){ fts3LogMerge(nSeg, iAbsLevel); - do { - rc = fts3IncrmergeAppend(p, pWriter, pCsr); - if( rc==SQLITE_OK ) rc = sqlite3Fts3SegReaderStep(p, pCsr); - if( pWriter->nWork>=nRem && rc==SQLITE_ROW ) rc = SQLITE_OK; - }while( rc==SQLITE_ROW ); + if( bEmpty==0 ){ + do { + rc = fts3IncrmergeAppend(p, pWriter, pCsr); + if( rc==SQLITE_OK ) rc = sqlite3Fts3SegReaderStep(p, pCsr); + if( pWriter->nWork>=nRem && rc==SQLITE_ROW ) rc = SQLITE_OK; + }while( rc==SQLITE_ROW ); + } /* Update or delete the input segments */ if( rc==SQLITE_OK ){ @@ -175371,7 +209430,7 @@ SQLITE_PRIVATE int sqlite3Fts3Incrmerge(Fts3Table *p, int nMerge, int nMin){ ** the integer. ** ** This function used for parameters to merge= and incrmerge= -** commands. +** commands. */ static int fts3Getint(const char **pz){ const char *z = *pz; @@ -175395,7 +209454,7 @@ static int fts3DoIncrmerge( const char *zParam /* Nul-terminated string containing "A,B" */ ){ int rc; - int nMin = (FTS3_MERGE_COUNT / 2); + int nMin = (MergeCount(p) / 2); int nMerge = 0; const char *z = zParam; @@ -175440,7 +209499,7 @@ static int fts3DoAutoincrmerge( int rc = SQLITE_OK; sqlite3_stmt *pStmt = 0; p->nAutoincrmerge = fts3Getint(&zParam); - if( p->nAutoincrmerge==1 || p->nAutoincrmerge>FTS3_MERGE_COUNT ){ + if( p->nAutoincrmerge==1 || p->nAutoincrmerge>MergeCount(p) ){ p->nAutoincrmerge = 8; } if( !p->bHasStat ){ @@ -175502,7 +209561,7 @@ static u64 fts3ChecksumIndex( int rc; u64 cksum = 0; - assert( *pRc==SQLITE_OK ); + if( *pRc ) return 0; memset(&filter, 0, sizeof(filter)); memset(&csr, 0, sizeof(csr)); @@ -175523,12 +209582,12 @@ static u64 fts3ChecksumIndex( i64 iDocid = 0; i64 iCol = 0; - i64 iPos = 0; + u64 iPos = 0; pCsr += sqlite3Fts3GetVarint(pCsr, &iDocid); while( pCsrbDescIdx ){ + iDocid = (i64)((u64)iDocid - iVal); + }else{ + iDocid = (i64)((u64)iDocid + iVal); + } } }else{ iPos += (iVal - 2); @@ -175562,10 +209625,10 @@ static u64 fts3ChecksumIndex( ** to true and return SQLITE_OK. Or if the contents do not match, set *pbOk ** to false before returning. ** -** If an error occurs (e.g. an OOM or IO error), return an SQLite error +** If an error occurs (e.g. an OOM or IO error), return an SQLite error ** code. The final value of *pbOk is undefined in this case. */ -static int fts3IntegrityCheck(Fts3Table *p, int *pbOk){ +SQLITE_PRIVATE int sqlite3Fts3IntegrityCheck(Fts3Table *p, int *pbOk){ int rc = SQLITE_OK; /* Return code */ u64 cksum1 = 0; /* Checksum based on FTS index contents */ u64 cksum2 = 0; /* Checksum based on %_content contents */ @@ -175593,12 +209656,12 @@ static int fts3IntegrityCheck(Fts3Table *p, int *pbOk){ sqlite3_tokenizer_module const *pModule = p->pTokenizer->pModule; sqlite3_stmt *pStmt = 0; char *zSql; - + zSql = sqlite3_mprintf("SELECT %s" , p->zReadExprlist); if( !zSql ){ rc = SQLITE_NOMEM; }else{ - rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); + rc = sqlite3Fts3PrepareStmt(p, zSql, 0, 1, &pStmt); sqlite3_free(zSql); } @@ -175610,10 +209673,9 @@ static int fts3IntegrityCheck(Fts3Table *p, int *pbOk){ for(iCol=0; rc==SQLITE_OK && iColnColumn; iCol++){ if( p->abNotindexed[iCol]==0 ){ const char *zText = (const char *)sqlite3_column_text(pStmt, iCol+1); - int nText = sqlite3_column_bytes(pStmt, iCol+1); sqlite3_tokenizer_cursor *pT = 0; - rc = sqlite3Fts3OpenTokenizer(p->pTokenizer, iLang, zText, nText,&pT); + rc = sqlite3Fts3OpenTokenizer(p->pTokenizer, iLang, zText, -1, &pT); while( rc==SQLITE_OK ){ char const *zToken; /* Buffer containing token */ int nToken = 0; /* Number of bytes in token */ @@ -175644,7 +209706,12 @@ static int fts3IntegrityCheck(Fts3Table *p, int *pbOk){ sqlite3_finalize(pStmt); } - *pbOk = (cksum1==cksum2); + if( rc==SQLITE_CORRUPT_VTAB ){ + rc = SQLITE_OK; + *pbOk = 0; + }else{ + *pbOk = (rc==SQLITE_OK && cksum1==cksum2); + } return rc; } @@ -175653,7 +209720,7 @@ static int fts3IntegrityCheck(Fts3Table *p, int *pbOk){ ** the FTS index are correct, return SQLITE_OK. Or, if the contents of the ** FTS index are incorrect, return SQLITE_CORRUPT_VTAB. ** -** Or, if an error (e.g. an OOM or IO error) occurs, return an SQLite +** Or, if an error (e.g. an OOM or IO error) occurs, return an SQLite ** error code. ** ** The integrity-check works as follows. For each token and indexed token @@ -175662,7 +209729,7 @@ static int fts3IntegrityCheck(Fts3Table *p, int *pbOk){ ** ** + The index number (0 for the main index, 1 for the first prefix ** index etc.), -** + The token (or token prefix) text itself, +** + The token (or token prefix) text itself, ** + The language-id of the row it appears in, ** + The docid of the row it appears in, ** + The column it appears in, and @@ -175673,7 +209740,7 @@ static int fts3IntegrityCheck(Fts3Table *p, int *pbOk){ ** ** The integrity-check code calculates the same checksum in two ways: ** -** 1. By scanning the contents of the FTS index, and +** 1. By scanning the contents of the FTS index, and ** 2. By scanning and tokenizing the content table. ** ** If the two checksums are identical, the integrity-check is deemed to have @@ -175684,7 +209751,7 @@ static int fts3DoIntegrityCheck( ){ int rc; int bOk = 0; - rc = fts3IntegrityCheck(p, &bOk); + rc = sqlite3Fts3IntegrityCheck(p, &bOk); if( rc==SQLITE_OK && bOk==0 ) rc = FTS_CORRUPT_VTAB; return rc; } @@ -175694,11 +209761,11 @@ static int fts3DoIntegrityCheck( ** ** "INSERT INTO tbl(tbl) VALUES()" ** -** Argument pVal contains the result of . Currently the only +** Argument pVal contains the result of . Currently the only ** meaningful value to insert is the text 'optimize'. */ static int fts3SpecialInsert(Fts3Table *p, sqlite3_value *pVal){ - int rc; /* Return Code */ + int rc = SQLITE_ERROR; /* Return Code */ const char *zVal = (const char *)sqlite3_value_text(pVal); int nVal = sqlite3_value_bytes(pVal); @@ -175714,21 +209781,30 @@ static int fts3SpecialInsert(Fts3Table *p, sqlite3_value *pVal){ rc = fts3DoIncrmerge(p, &zVal[6]); }else if( nVal>10 && 0==sqlite3_strnicmp(zVal, "automerge=", 10) ){ rc = fts3DoAutoincrmerge(p, &zVal[10]); -#ifdef SQLITE_TEST - }else if( nVal>9 && 0==sqlite3_strnicmp(zVal, "nodesize=", 9) ){ - p->nNodeSize = atoi(&zVal[9]); - rc = SQLITE_OK; - }else if( nVal>11 && 0==sqlite3_strnicmp(zVal, "maxpending=", 9) ){ - p->nMaxPendingData = atoi(&zVal[11]); - rc = SQLITE_OK; - }else if( nVal>21 && 0==sqlite3_strnicmp(zVal, "test-no-incr-doclist=", 21) ){ - p->bNoIncrDoclist = atoi(&zVal[21]); - rc = SQLITE_OK; -#endif - }else{ - rc = SQLITE_ERROR; + }else if( nVal==5 && 0==sqlite3_strnicmp(zVal, "flush", 5) ){ + rc = sqlite3Fts3PendingTermsFlush(p); } - +#if defined(SQLITE_DEBUG) || defined(SQLITE_TEST) + else{ + int v; + if( nVal>9 && 0==sqlite3_strnicmp(zVal, "nodesize=", 9) ){ + v = atoi(&zVal[9]); + if( v>=24 && v<=p->nPgsz-35 ) p->nNodeSize = v; + rc = SQLITE_OK; + }else if( nVal>11 && 0==sqlite3_strnicmp(zVal, "maxpending=", 11) ){ + v = atoi(&zVal[11]); + if( v>=64 && v<=FTS3_MAX_PENDING_DATA ) p->nMaxPendingData = v; + rc = SQLITE_OK; + }else if( nVal>21 && 0==sqlite3_strnicmp(zVal,"test-no-incr-doclist=",21) ){ + p->bNoIncrDoclist = atoi(&zVal[21]); + rc = SQLITE_OK; + }else if( nVal>11 && 0==sqlite3_strnicmp(zVal,"mergecount=",11) ){ + v = atoi(&zVal[11]); + if( v>=4 && v<=FTS3_MERGE_COUNT && (v&1)==0 ) p->nMergeCount = v; + rc = SQLITE_OK; + } + } +#endif return rc; } @@ -175746,7 +209822,7 @@ SQLITE_PRIVATE void sqlite3Fts3FreeDeferredDoclists(Fts3Cursor *pCsr){ } /* -** Free all entries in the pCsr->pDeffered list. Entries are added to +** Free all entries in the pCsr->pDeffered list. Entries are added to ** this list using sqlite3Fts3DeferToken(). */ SQLITE_PRIVATE void sqlite3Fts3FreeDeferredTokens(Fts3Cursor *pCsr){ @@ -175774,14 +209850,14 @@ SQLITE_PRIVATE int sqlite3Fts3CacheDeferredDoclists(Fts3Cursor *pCsr){ int i; /* Used to iterate through table columns */ sqlite3_int64 iDocid; /* Docid of the row pCsr points to */ Fts3DeferredToken *pDef; /* Used to iterate through deferred tokens */ - + Fts3Table *p = (Fts3Table *)pCsr->base.pVtab; sqlite3_tokenizer *pT = p->pTokenizer; sqlite3_tokenizer_module const *pModule = pT->pModule; - + assert( pCsr->isRequireSeek==0 ); iDocid = sqlite3_column_int64(pCsr->pStmt, 0); - + for(i=0; inColumn && rc==SQLITE_OK; i++){ if( p->abNotindexed[i]==0 ){ const char *zText = (const char *)sqlite3_column_text(pCsr->pStmt, i+1); @@ -175822,8 +209898,8 @@ SQLITE_PRIVATE int sqlite3Fts3CacheDeferredDoclists(Fts3Cursor *pCsr){ } SQLITE_PRIVATE int sqlite3Fts3DeferredTokenList( - Fts3DeferredToken *p, - char **ppData, + Fts3DeferredToken *p, + char **ppData, int *pnData ){ char *pRet; @@ -175837,13 +209913,13 @@ SQLITE_PRIVATE int sqlite3Fts3DeferredTokenList( return SQLITE_OK; } - pRet = (char *)sqlite3_malloc(p->pList->nData); + pRet = (char *)sqlite3_malloc64(p->pList->nData); if( !pRet ) return SQLITE_NOMEM; nSkip = sqlite3Fts3GetVarint(p->pList->aData, &dummy); *pnData = p->pList->nData - nSkip; *ppData = pRet; - + memcpy(pRet, &p->pList->aData[nSkip], *pnData); return SQLITE_OK; } @@ -175857,13 +209933,13 @@ SQLITE_PRIVATE int sqlite3Fts3DeferToken( int iCol /* Column that token must appear in (or -1) */ ){ Fts3DeferredToken *pDeferred; - pDeferred = sqlite3_malloc(sizeof(*pDeferred)); + pDeferred = sqlite3_malloc64(sizeof(*pDeferred)); if( !pDeferred ){ return SQLITE_NOMEM; } memset(pDeferred, 0, sizeof(*pDeferred)); pDeferred->pToken = pToken; - pDeferred->pNext = pCsr->pDeferred; + pDeferred->pNext = pCsr->pDeferred; pDeferred->iCol = iCol; pCsr->pDeferred = pDeferred; @@ -175877,11 +209953,11 @@ SQLITE_PRIVATE int sqlite3Fts3DeferToken( /* ** SQLite value pRowid contains the rowid of a row that may or may not be ** present in the FTS3 table. If it is, delete it and adjust the contents -** of subsiduary data structures accordingly. +** of subsidiary data structures accordingly. */ static int fts3DeleteByRowid( - Fts3Table *p, - sqlite3_value *pRowid, + Fts3Table *p, + sqlite3_value *pRowid, int *pnChng, /* IN/OUT: Decrement if row is deleted */ u32 *aSzDel ){ @@ -175919,14 +209995,14 @@ static int fts3DeleteByRowid( ** This function does the work for the xUpdate method of FTS3 virtual ** tables. The schema of the virtual table being: ** -** CREATE TABLE
                  ( +** CREATE TABLE
                  ( ** , -**
                  HIDDEN, -** docid HIDDEN, +**
                  HIDDEN, +** docid HIDDEN, ** HIDDEN ** ); ** -** +** */ SQLITE_PRIVATE int sqlite3Fts3UpdateMethod( sqlite3_vtab *pVtab, /* FTS3 vtab object */ @@ -175946,7 +210022,7 @@ SQLITE_PRIVATE int sqlite3Fts3UpdateMethod( assert( p->bHasStat==0 || p->bHasStat==1 ); assert( p->pSegments==0 ); - assert( + assert( nArg==1 /* DELETE operations */ || nArg==(2 + p->nColumn + 3) /* INSERT or UPDATE operations */ ); @@ -175955,9 +210031,9 @@ SQLITE_PRIVATE int sqlite3Fts3UpdateMethod( ** ** INSERT INTO xyz(xyz) VALUES('command'); */ - if( nArg>1 - && sqlite3_value_type(apVal[0])==SQLITE_NULL - && sqlite3_value_type(apVal[p->nColumn+2])!=SQLITE_NULL + if( nArg>1 + && sqlite3_value_type(apVal[0])==SQLITE_NULL + && sqlite3_value_type(apVal[p->nColumn+2])!=SQLITE_NULL ){ rc = fts3SpecialInsert(p, apVal[p->nColumn+2]); goto update_out; @@ -175996,24 +210072,24 @@ SQLITE_PRIVATE int sqlite3Fts3UpdateMethod( pNewRowid = apVal[1]; } - if( sqlite3_value_type(pNewRowid)!=SQLITE_NULL && ( + if( sqlite3_value_type(pNewRowid)!=SQLITE_NULL && ( sqlite3_value_type(apVal[0])==SQLITE_NULL || sqlite3_value_int64(apVal[0])!=sqlite3_value_int64(pNewRowid) )){ /* The new rowid is not NULL (in this case the rowid will be - ** automatically assigned and there is no chance of a conflict), and + ** automatically assigned and there is no chance of a conflict), and ** the statement is either an INSERT or an UPDATE that modifies the ** rowid column. So if the conflict mode is REPLACE, then delete any - ** existing row with rowid=pNewRowid. + ** existing row with rowid=pNewRowid. ** - ** Or, if the conflict mode is not REPLACE, insert the new record into + ** Or, if the conflict mode is not REPLACE, insert the new record into ** the %_content table. If we hit the duplicate rowid constraint (or any ** other error) while doing so, return immediately. ** ** This branch may also run if pNewRowid contains a value that cannot - ** be losslessly converted to an integer. In this case, the eventual + ** be losslessly converted to an integer. In this case, the eventual ** call to fts3InsertData() (either just below or further on in this - ** function) will return SQLITE_MISMATCH. If fts3DeleteByRowid is + ** function) will return SQLITE_MISMATCH. If fts3DeleteByRowid is ** invoked, it will delete zero rows (since no row will have ** docid=$pNewRowid if $pNewRowid is not an integer value). */ @@ -176034,7 +210110,7 @@ SQLITE_PRIVATE int sqlite3Fts3UpdateMethod( assert( sqlite3_value_type(apVal[0])==SQLITE_INTEGER ); rc = fts3DeleteByRowid(p, apVal[0], &nChng, aSzDel); } - + /* If this is an INSERT or UPDATE operation, insert the new record. */ if( nArg>1 && rc==SQLITE_OK ){ int iLangid = sqlite3_value_int(apVal[2 + p->nColumn + 2]); @@ -176067,10 +210143,10 @@ SQLITE_PRIVATE int sqlite3Fts3UpdateMethod( return rc; } -/* +/* ** Flush any data in the pending-terms hash table to disk. If successful, -** merge all segments in the database (including the new segment, if -** there was any data to flush) into a single segment. +** merge all segments in the database (including the new segment, if +** there was any data to flush) into a single segment. */ SQLITE_PRIVATE int sqlite3Fts3Optimize(Fts3Table *p){ int rc; @@ -176126,13 +210202,13 @@ SQLITE_PRIVATE int sqlite3Fts3Optimize(Fts3Table *p){ #define FTS3_MATCHINFO_LHITS_BM 'b' /* nCol*nPhrase values */ /* -** The default value for the second argument to matchinfo(). +** The default value for the second argument to matchinfo(). */ #define FTS3_MATCHINFO_DEFAULT "pcx" /* -** Used as an fts3ExprIterate() context when loading phrase doclists to +** Used as an sqlite3Fts3ExprIterate() context when loading phrase doclists to ** Fts3Expr.aDoclist[]/nDoclist. */ typedef struct LoadDoclistCtx LoadDoclistCtx; @@ -176143,7 +210219,7 @@ struct LoadDoclistCtx { }; /* -** The following types are used as part of the implementation of the +** The following types are used as part of the implementation of the ** fts3BestSnippet() routine. */ typedef struct SnippetIter SnippetIter; @@ -176162,9 +210238,9 @@ struct SnippetIter { struct SnippetPhrase { int nToken; /* Number of tokens in phrase */ char *pList; /* Pointer to start of phrase position list */ - int iHead; /* Next value in position list */ + i64 iHead; /* Next value in position list */ char *pHead; /* Position list data following iHead */ - int iTail; /* Next value in trailing position list */ + i64 iTail; /* Next value in trailing position list */ char *pTail; /* Position list data following iTail */ }; @@ -176176,7 +210252,7 @@ struct SnippetFragment { }; /* -** This type is used as an fts3ExprIterate() context object while +** This type is used as an sqlite3Fts3ExprIterate() context object while ** accumulating the data returned by the matchinfo() function. */ typedef struct MatchInfo MatchInfo; @@ -176199,9 +210275,13 @@ struct MatchinfoBuffer { int nElem; int bGlobal; /* Set if global data is loaded */ char *zMatchinfo; - u32 aMatchinfo[1]; + u32 aMI[FLEXARRAY]; }; +/* Size (in bytes) of a MatchinfoBuffer sufficient for N elements */ +#define SZ_MATCHINFOBUFFER(N) \ + (offsetof(MatchinfoBuffer,aMI)+(((N)+1)/2)*sizeof(u64)) + /* ** The snippet() and offsets() functions both return text values. An instance @@ -176226,14 +210306,13 @@ struct StrBuffer { static MatchinfoBuffer *fts3MIBufferNew(size_t nElem, const char *zMatchinfo){ MatchinfoBuffer *pRet; sqlite3_int64 nByte = sizeof(u32) * (2*(sqlite3_int64)nElem + 1) - + sizeof(MatchinfoBuffer); + + SZ_MATCHINFOBUFFER(1); sqlite3_int64 nStr = strlen(zMatchinfo); - pRet = sqlite3_malloc64(nByte + nStr+1); + pRet = sqlite3Fts3MallocZero(nByte + nStr+1); if( pRet ){ - memset(pRet, 0, nByte); - pRet->aMatchinfo[0] = (u8*)(&pRet->aMatchinfo[1]) - (u8*)pRet; - pRet->aMatchinfo[1+nElem] = pRet->aMatchinfo[0] + pRet->aMI[0] = (u8*)(&pRet->aMI[1]) - (u8*)pRet; + pRet->aMI[1+nElem] = pRet->aMI[0] + sizeof(u32)*((int)nElem+1); pRet->nElem = (int)nElem; pRet->zMatchinfo = ((char*)pRet) + nByte; @@ -176247,10 +210326,10 @@ static MatchinfoBuffer *fts3MIBufferNew(size_t nElem, const char *zMatchinfo){ static void fts3MIBufferFree(void *p){ MatchinfoBuffer *pBuf = (MatchinfoBuffer*)((u8*)p - ((u32*)p)[-1]); - assert( (u32*)p==&pBuf->aMatchinfo[1] - || (u32*)p==&pBuf->aMatchinfo[pBuf->nElem+2] + assert( (u32*)p==&pBuf->aMI[1] + || (u32*)p==&pBuf->aMI[pBuf->nElem+2] ); - if( (u32*)p==&pBuf->aMatchinfo[1] ){ + if( (u32*)p==&pBuf->aMI[1] ){ pBuf->aRef[1] = 0; }else{ pBuf->aRef[2] = 0; @@ -176267,18 +210346,18 @@ static void (*fts3MIBufferAlloc(MatchinfoBuffer *p, u32 **paOut))(void*){ if( p->aRef[1]==0 ){ p->aRef[1] = 1; - aOut = &p->aMatchinfo[1]; + aOut = &p->aMI[1]; xRet = fts3MIBufferFree; } else if( p->aRef[2]==0 ){ p->aRef[2] = 1; - aOut = &p->aMatchinfo[p->nElem+2]; + aOut = &p->aMI[p->nElem+2]; xRet = fts3MIBufferFree; }else{ aOut = (u32*)sqlite3_malloc64(p->nElem * sizeof(u32)); if( aOut ){ xRet = sqlite3_free; - if( p->bGlobal ) memcpy(aOut, &p->aMatchinfo[1], p->nElem*sizeof(u32)); + if( p->bGlobal ) memcpy(aOut, &p->aMI[1], p->nElem*sizeof(u32)); } } @@ -176288,7 +210367,7 @@ static void (*fts3MIBufferAlloc(MatchinfoBuffer *p, u32 **paOut))(void*){ static void fts3MIBufferSetGlobal(MatchinfoBuffer *p){ p->bGlobal = 1; - memcpy(&p->aMatchinfo[2+p->nElem], &p->aMatchinfo[1], p->nElem*sizeof(u32)); + memcpy(&p->aMI[2+p->nElem], &p->aMI[1], p->nElem*sizeof(u32)); } /* @@ -176304,7 +210383,7 @@ SQLITE_PRIVATE void sqlite3Fts3MIBufferFree(MatchinfoBuffer *p){ } } -/* +/* ** End of MatchinfoBuffer code. *************************************************************************/ @@ -176329,14 +210408,14 @@ SQLITE_PRIVATE void sqlite3Fts3MIBufferFree(MatchinfoBuffer *p){ ** After it returns, *piPos contains the value of the next element of the ** list and *pp is advanced to the following varint. */ -static void fts3GetDeltaPosition(char **pp, int *piPos){ +static void fts3GetDeltaPosition(char **pp, i64 *piPos){ int iVal; *pp += fts3GetVarint32(*pp, &iVal); *piPos += (iVal-2); } /* -** Helper function for fts3ExprIterate() (see below). +** Helper function for sqlite3Fts3ExprIterate() (see below). */ static int fts3ExprIterate2( Fts3Expr *pExpr, /* Expression to iterate phrases of */ @@ -176365,12 +210444,12 @@ static int fts3ExprIterate2( ** are part of a sub-tree that is the right-hand-side of a NOT operator. ** For each phrase node found, the supplied callback function is invoked. ** -** If the callback function returns anything other than SQLITE_OK, +** If the callback function returns anything other than SQLITE_OK, ** the iteration is abandoned and the error code returned immediately. ** Otherwise, SQLITE_OK is returned after a callback has been made for ** all eligible phrase nodes. */ -static int fts3ExprIterate( +SQLITE_PRIVATE int sqlite3Fts3ExprIterate( Fts3Expr *pExpr, /* Expression to iterate phrases of */ int (*x)(Fts3Expr*,int,void*), /* Callback function to invoke for phrases */ void *pCtx /* Second argument to pass to callback */ @@ -176379,10 +210458,9 @@ static int fts3ExprIterate( return fts3ExprIterate2(pExpr, &iPhrase, x, pCtx); } - /* -** This is an fts3ExprIterate() callback used while loading the doclists -** for each phrase into Fts3Expr.aDoclist[]/nDoclist. See also +** This is an sqlite3Fts3ExprIterate() callback used while loading the +** doclists for each phrase into Fts3Expr.aDoclist[]/nDoclist. See also ** fts3ExprLoadDoclists(). */ static int fts3ExprLoadDoclistsCb(Fts3Expr *pExpr, int iPhrase, void *ctx){ @@ -176400,11 +210478,11 @@ static int fts3ExprLoadDoclistsCb(Fts3Expr *pExpr, int iPhrase, void *ctx){ /* ** Load the doclists for each phrase in the query associated with FTS3 cursor -** pCsr. +** pCsr. ** -** If pnPhrase is not NULL, then *pnPhrase is set to the number of matchable -** phrases in the expression (all phrases except those directly or -** indirectly descended from the right-hand-side of a NOT operator). If +** If pnPhrase is not NULL, then *pnPhrase is set to the number of matchable +** phrases in the expression (all phrases except those directly or +** indirectly descended from the right-hand-side of a NOT operator). If ** pnToken is not NULL, then it is set to the number of tokens in all ** matchable phrases of the expression. */ @@ -176414,9 +210492,9 @@ static int fts3ExprLoadDoclists( int *pnToken /* OUT: Number of tokens in query */ ){ int rc; /* Return Code */ - LoadDoclistCtx sCtx = {0,0,0}; /* Context for fts3ExprIterate() */ + LoadDoclistCtx sCtx = {0,0,0}; /* Context for sqlite3Fts3ExprIterate() */ sCtx.pCsr = pCsr; - rc = fts3ExprIterate(pCsr->pExpr, fts3ExprLoadDoclistsCb, (void *)&sCtx); + rc = sqlite3Fts3ExprIterate(pCsr->pExpr,fts3ExprLoadDoclistsCb,(void*)&sCtx); if( pnPhrase ) *pnPhrase = sCtx.nPhrase; if( pnToken ) *pnToken = sCtx.nToken; return rc; @@ -176429,19 +210507,19 @@ static int fts3ExprPhraseCountCb(Fts3Expr *pExpr, int iPhrase, void *ctx){ } static int fts3ExprPhraseCount(Fts3Expr *pExpr){ int nPhrase = 0; - (void)fts3ExprIterate(pExpr, fts3ExprPhraseCountCb, (void *)&nPhrase); + (void)sqlite3Fts3ExprIterate(pExpr, fts3ExprPhraseCountCb, (void *)&nPhrase); return nPhrase; } /* -** Advance the position list iterator specified by the first two +** Advance the position list iterator specified by the first two ** arguments so that it points to the first element with a value greater ** than or equal to parameter iNext. */ -static void fts3SnippetAdvance(char **ppIter, int *piIter, int iNext){ +static void fts3SnippetAdvance(char **ppIter, i64 *piIter, int iNext){ char *pIter = *ppIter; if( pIter ){ - int iIter = *piIter; + i64 iIter = *piIter; while( iIternSnippet>=0 ); pIter->iCurrent = iStart = iEnd - pIter->nSnippet + 1; for(i=0; inPhrase; i++){ SnippetPhrase *pPhrase = &pIter->aPhrase[i]; @@ -176503,7 +210582,7 @@ static int fts3SnippetNextCandidate(SnippetIter *pIter){ } /* -** Retrieve information about the current candidate snippet of snippet +** Retrieve information about the current candidate snippet of snippet ** iterator pIter. */ static void fts3SnippetDetails( @@ -176524,14 +210603,14 @@ static void fts3SnippetDetails( SnippetPhrase *pPhrase = &pIter->aPhrase[i]; if( pPhrase->pTail ){ char *pCsr = pPhrase->pTail; - int iCsr = pPhrase->iTail; + i64 iCsr = pPhrase->iTail; while( iCsr<(iStart+pIter->nSnippet) && iCsr>=iStart ){ int j; - u64 mPhrase = (u64)1 << i; + u64 mPhrase = (u64)1 << (i%64); u64 mPos = (u64)1 << (iCsr - iStart); assert( iCsr>=iStart && (iCsr - iStart)<=64 ); - assert( i>=0 && i<=64 ); + assert( i>=0 ); if( (mCover|mCovered)&mPhrase ){ iScore++; }else{ @@ -176539,7 +210618,7 @@ static void fts3SnippetDetails( } mCover |= mPhrase; - for(j=0; jnToken; j++){ + for(j=0; jnToken && jnSnippet; j++){ mHighlight |= (mPos>>j); } @@ -176557,8 +210636,9 @@ static void fts3SnippetDetails( } /* -** This function is an fts3ExprIterate() callback used by fts3BestSnippet(). -** Each invocation populates an element of the SnippetIter.aPhrase[] array. +** This function is an sqlite3Fts3ExprIterate() callback used by +** fts3BestSnippet(). Each invocation populates an element of the +** SnippetIter.aPhrase[] array. */ static int fts3SnippetFindPositions(Fts3Expr *pExpr, int iPhrase, void *ctx){ SnippetIter *p = (SnippetIter *)ctx; @@ -176570,7 +210650,7 @@ static int fts3SnippetFindPositions(Fts3Expr *pExpr, int iPhrase, void *ctx){ rc = sqlite3Fts3EvalPhrasePoslist(p->pCsr, pExpr, p->iCol, &pCsr); assert( rc==SQLITE_OK || pCsr==0 ); if( pCsr ){ - int iFirst = 0; + i64 iFirst = 0; pPhrase->pList = pCsr; fts3GetDeltaPosition(&pCsr, &iFirst); if( iFirst<0 ){ @@ -176583,7 +210663,7 @@ static int fts3SnippetFindPositions(Fts3Expr *pExpr, int iPhrase, void *ctx){ } }else{ assert( rc!=SQLITE_OK || ( - pPhrase->pList==0 && pPhrase->pHead==0 && pPhrase->pTail==0 + pPhrase->pList==0 && pPhrase->pHead==0 && pPhrase->pTail==0 )); } @@ -176591,14 +210671,14 @@ static int fts3SnippetFindPositions(Fts3Expr *pExpr, int iPhrase, void *ctx){ } /* -** Select the fragment of text consisting of nFragment contiguous tokens +** Select the fragment of text consisting of nFragment contiguous tokens ** from column iCol that represent the "best" snippet. The best snippet ** is the snippet with the highest score, where scores are calculated ** by adding: ** ** (a) +1 point for each occurrence of a matchable phrase in the snippet. ** -** (b) +1000 points for the first occurrence of each matchable phrase in +** (b) +1000 points for the first occurrence of each matchable phrase in ** the snippet for which the corresponding mCovered bit is not set. ** ** The selected snippet parameters are stored in structure *pFragment before @@ -176635,11 +210715,10 @@ static int fts3BestSnippet( ** the required space using malloc(). */ nByte = sizeof(SnippetPhrase) * nList; - sIter.aPhrase = (SnippetPhrase *)sqlite3_malloc64(nByte); + sIter.aPhrase = (SnippetPhrase *)sqlite3Fts3MallocZero(nByte); if( !sIter.aPhrase ){ return SQLITE_NOMEM; } - memset(sIter.aPhrase, 0, nByte); /* Initialize the contents of the SnippetIter object. Then iterate through ** the set of phrases in the expression to populate the aPhrase[] array. @@ -176649,17 +210728,19 @@ static int fts3BestSnippet( sIter.nSnippet = nSnippet; sIter.nPhrase = nList; sIter.iCurrent = -1; - rc = fts3ExprIterate(pCsr->pExpr, fts3SnippetFindPositions, (void*)&sIter); + rc = sqlite3Fts3ExprIterate( + pCsr->pExpr, fts3SnippetFindPositions, (void*)&sIter + ); if( rc==SQLITE_OK ){ /* Set the *pmSeen output variable. */ for(i=0; iiCol = iCol; @@ -176701,11 +210782,11 @@ static int fts3StringAppend( } /* If there is insufficient space allocated at StrBuffer.z, use realloc() - ** to grow the buffer until so that it is big enough to accomadate the + ** to grow the buffer until so that it is big enough to accommodate the ** appended data. */ - if( pStr->n+nAppend+1>=pStr->nAlloc ){ - sqlite3_int64 nAlloc = pStr->nAlloc+(sqlite3_int64)nAppend+100; + if( (i64)pStr->n+(i64)nAppend+1>=(i64)pStr->nAlloc ){ + i64 nAlloc = pStr->nAlloc+(i64)nAppend+100; char *zNew = sqlite3_realloc64(pStr->z, nAlloc); if( !zNew ){ return SQLITE_NOMEM; @@ -176731,8 +210812,8 @@ static int fts3StringAppend( ** ** ........X.....X ** -** This function "shifts" the beginning of the snippet forward in the -** document so that there are approximately the same number of +** This function "shifts" the beginning of the snippet forward in the +** document so that there are approximately the same number of ** non-highlighted terms to the right of the final highlighted term as there ** are to the left of the first highlighted term. For example, to this: ** @@ -176740,8 +210821,8 @@ static int fts3StringAppend( ** ** This is done as part of extracting the snippet text, not when selecting ** the snippet. Snippet selection is done based on doclists only, so there -** is no way for fts3BestSnippet() to know whether or not the document -** actually contains terms that follow the final highlighted term. +** is no way for fts3BestSnippet() to know whether or not the document +** actually contains terms that follow the final highlighted term. */ static int fts3SnippetShift( Fts3Table *pTab, /* FTS3 table snippet comes from */ @@ -176831,7 +210912,7 @@ static int fts3SnippetText( int iCol = pFragment->iCol+1; /* Query column to extract text from */ sqlite3_tokenizer_module *pMod; /* Tokenizer module methods object */ sqlite3_tokenizer_cursor *pC; /* Tokenizer cursor open on zDoc/nDoc */ - + zDoc = (const char *)sqlite3_column_text(pCsr->pStmt, iCol); if( zDoc==0 ){ if( sqlite3_column_type(pCsr->pStmt, iCol)!=SQLITE_NULL ){ @@ -176871,7 +210952,7 @@ static int fts3SnippetText( if( rc==SQLITE_DONE ){ /* Special case - the last token of the snippet is also the last token ** of the column. Append any punctuation that occurred between the end - ** of the previous token and the end of the document to the output. + ** of the previous token and the end of the document to the output. ** Then break out of the loop. */ rc = fts3StringAppend(pOut, &zDoc[iEnd], -1); } @@ -176888,7 +210969,7 @@ static int fts3SnippetText( /* Now that the shift has been done, check if the initial "..." are ** required. They are required if (a) this is not the first fragment, - ** or (b) this fragment does not begin at position 0 of its column. + ** or (b) this fragment does not begin at position 0 of its column. */ if( rc==SQLITE_OK ){ if( iPos>0 || iFragment>0 ){ @@ -176924,8 +211005,8 @@ static int fts3SnippetText( /* -** This function is used to count the entries in a column-list (a -** delta-encoded list of term offsets within a single column of a single +** This function is used to count the entries in a column-list (a +** delta-encoded list of term offsets within a single column of a single ** row). When this function is called, *ppCollist should point to the ** beginning of the first varint in the column-list (the varint that ** contains the position of the first matching term in the column data). @@ -176971,13 +211052,13 @@ static int fts3ExprLHits( iStart = pExpr->iPhrase * ((p->nCol + 31) / 32); } - while( 1 ){ + if( pIter ) while( 1 ){ int nHit = fts3ColumnlistCount(&pIter); if( (pPhrase->iColumn>=pTab->nColumn || pPhrase->iColumn==iCol) ){ if( p->flag==FTS3_MATCHINFO_LHITS ){ p->aMatchinfo[iStart + iCol] = (u32)nHit; }else if( nHit ){ - p->aMatchinfo[iStart + (iCol+1)/32] |= (1 << (iCol&0x1F)); + p->aMatchinfo[iStart + iCol/32] |= (1U << (iCol&0x1F)); } } assert( *pIter==0x00 || *pIter==0x01 ); @@ -177010,12 +211091,12 @@ static int fts3ExprLHitGather( } /* -** fts3ExprIterate() callback used to collect the "global" matchinfo stats -** for a single query. +** sqlite3Fts3ExprIterate() callback used to collect the "global" matchinfo +** stats for a single query. ** -** fts3ExprIterate() callback to load the 'global' elements of a -** FTS3_MATCHINFO_HITS matchinfo array. The global stats are those elements -** of the matchinfo array that are constant for all rows returned by the +** sqlite3Fts3ExprIterate() callback to load the 'global' elements of a +** FTS3_MATCHINFO_HITS matchinfo array. The global stats are those elements +** of the matchinfo array that are constant for all rows returned by the ** current query. ** ** Argument pCtx is actually a pointer to a struct of type MatchInfo. This @@ -177031,7 +211112,7 @@ static int fts3ExprLHitGather( ** at least one instance of phrase iPhrase. ** ** If the phrase pExpr consists entirely of deferred tokens, then all X and -** Y values are set to nDoc, where nDoc is the number of documents in the +** Y values are set to nDoc, where nDoc is the number of documents in the ** file system. This is done because the full-text index doclist is required ** to calculate these values properly, and the full-text index doclist is ** not available for deferred tokens. @@ -177048,8 +211129,8 @@ static int fts3ExprGlobalHitsCb( } /* -** fts3ExprIterate() callback used to collect the "local" part of the -** FTS3_MATCHINFO_HITS array. The local stats are those elements of the +** sqlite3Fts3ExprIterate() callback used to collect the "local" part of the +** FTS3_MATCHINFO_HITS array. The local stats are those elements of the ** array that are different for each row returned by the query. */ static int fts3ExprLocalHitsCb( @@ -177076,7 +211157,7 @@ static int fts3ExprLocalHitsCb( } static int fts3MatchinfoCheck( - Fts3Table *pTab, + Fts3Table *pTab, char cArg, char **pzErr ){ @@ -177101,8 +211182,8 @@ static size_t fts3MatchinfoSize(MatchInfo *pInfo, char cArg){ switch( cArg ){ case FTS3_MATCHINFO_NDOC: - case FTS3_MATCHINFO_NPHRASE: - case FTS3_MATCHINFO_NCOL: + case FTS3_MATCHINFO_NPHRASE: + case FTS3_MATCHINFO_NCOL: nVal = 1; break; @@ -177113,16 +211194,16 @@ static size_t fts3MatchinfoSize(MatchInfo *pInfo, char cArg){ break; case FTS3_MATCHINFO_LHITS: - nVal = pInfo->nCol * pInfo->nPhrase; + nVal = (size_t)pInfo->nCol * pInfo->nPhrase; break; case FTS3_MATCHINFO_LHITS_BM: - nVal = pInfo->nPhrase * ((pInfo->nCol + 31) / 32); + nVal = (size_t)pInfo->nPhrase * ((pInfo->nCol + 31) / 32); break; default: assert( cArg==FTS3_MATCHINFO_HITS ); - nVal = pInfo->nCol * pInfo->nPhrase * 3; + nVal = (size_t)pInfo->nCol * pInfo->nPhrase * 3; break; } @@ -177133,11 +211214,15 @@ static int fts3MatchinfoSelectDoctotal( Fts3Table *pTab, sqlite3_stmt **ppStmt, sqlite3_int64 *pnDoc, - const char **paLen + const char **paLen, + const char **ppEnd ){ sqlite3_stmt *pStmt; const char *a; + const char *pEnd; sqlite3_int64 nDoc; + int n; + if( !*ppStmt ){ int rc = sqlite3Fts3SelectDoctotal(pTab, ppStmt); @@ -177146,17 +211231,25 @@ static int fts3MatchinfoSelectDoctotal( pStmt = *ppStmt; assert( sqlite3_data_count(pStmt)==1 ); + n = sqlite3_column_bytes(pStmt, 0); a = sqlite3_column_blob(pStmt, 0); - a += sqlite3Fts3GetVarint(a, &nDoc); - if( nDoc==0 ) return FTS_CORRUPT_VTAB; - *pnDoc = (u32)nDoc; + if( a==0 ){ + return FTS_CORRUPT_VTAB; + } + pEnd = a + n; + a += sqlite3Fts3GetVarintBounded(a, pEnd, &nDoc); + if( nDoc<=0 || a>pEnd ){ + return FTS_CORRUPT_VTAB; + } + *pnDoc = nDoc; if( paLen ) *paLen = a; + if( ppEnd ) *ppEnd = pEnd; return SQLITE_OK; } /* -** An instance of the following structure is used to store state while +** An instance of the following structure is used to store state while ** iterating through a multi-column position-list corresponding to the ** hits for a single phrase on a single row in order to calculate the ** values for a matchinfo() FTS3_MATCHINFO_LCS request. @@ -177169,7 +211262,7 @@ struct LcsIterator { int iPos; /* Current position */ }; -/* +/* ** If LcsIterator.iCol is set to the following value, the iterator has ** finished iterating through all offsets for all columns. */ @@ -177191,10 +211284,12 @@ static int fts3MatchinfoLcsCb( ** position list for the next column. */ static int fts3LcsIteratorAdvance(LcsIterator *pIter){ - char *pRead = pIter->pRead; + char *pRead; sqlite3_int64 iRead; int rc = 0; + if( NEVER(pIter==0) ) return 1; + pRead = pIter->pRead; pRead += sqlite3Fts3GetVarint(pRead, &iRead); if( iRead==0 || iRead==1 ){ pRead = 0; @@ -177206,16 +211301,16 @@ static int fts3LcsIteratorAdvance(LcsIterator *pIter){ pIter->pRead = pRead; return rc; } - + /* -** This function implements the FTS3_MATCHINFO_LCS matchinfo() flag. +** This function implements the FTS3_MATCHINFO_LCS matchinfo() flag. ** ** If the call is successful, the longest-common-substring lengths for each -** column are written into the first nCol elements of the pInfo->aMatchinfo[] +** column are written into the first nCol elements of the pInfo->aMatchinfo[] ** array before returning. SQLITE_OK is returned in this case. ** ** Otherwise, if an error occurs, an SQLite error code is returned and the -** data written to the first nCol elements of pInfo->aMatchinfo[] is +** data written to the first nCol elements of pInfo->aMatchinfo[] is ** undefined. */ static int fts3MatchinfoLcs(Fts3Cursor *pCsr, MatchInfo *pInfo){ @@ -177228,10 +211323,9 @@ static int fts3MatchinfoLcs(Fts3Cursor *pCsr, MatchInfo *pInfo){ /* Allocate and populate the array of LcsIterator objects. The array ** contains one element for each matchable phrase in the query. **/ - aIter = sqlite3_malloc64(sizeof(LcsIterator) * pCsr->nPhrase); + aIter = sqlite3Fts3MallocZero(sizeof(LcsIterator) * pCsr->nPhrase); if( !aIter ) return SQLITE_NOMEM; - memset(aIter, 0, sizeof(LcsIterator) * pCsr->nPhrase); - (void)fts3ExprIterate(pCsr->pExpr, fts3MatchinfoLcsCb, (void*)aIter); + (void)sqlite3Fts3ExprIterate(pCsr->pExpr, fts3MatchinfoLcsCb, (void*)aIter); for(i=0; inPhrase; i++){ LcsIterator *pIter = &aIter[i]; @@ -177292,7 +211386,7 @@ static int fts3MatchinfoLcs(Fts3Cursor *pCsr, MatchInfo *pInfo){ /* ** Populate the buffer pInfo->aMatchinfo[] with an array of integers to -** be returned by the matchinfo() function. Argument zArg contains the +** be returned by the matchinfo() function. Argument zArg contains the ** format string passed as the second argument to matchinfo (or the ** default value "pcx" if no second argument was specified). The format ** string has already been validated and the pInfo->aMatchinfo[] array @@ -177303,7 +211397,7 @@ static int fts3MatchinfoLcs(Fts3Cursor *pCsr, MatchInfo *pInfo){ ** rows (i.e. FTS3_MATCHINFO_NPHRASE, NCOL, NDOC, AVGLENGTH and part of HITS) ** have already been populated. ** -** Return SQLITE_OK if successful, or an SQLite error code if an error +** Return SQLITE_OK if successful, or an SQLite error code if an error ** occurs. If a value other than SQLITE_OK is returned, the state the ** pInfo->aMatchinfo[] buffer is left in is undefined. */ @@ -177328,27 +211422,32 @@ static int fts3MatchinfoValues( case FTS3_MATCHINFO_NCOL: if( bGlobal ) pInfo->aMatchinfo[0] = pInfo->nCol; break; - + case FTS3_MATCHINFO_NDOC: if( bGlobal ){ sqlite3_int64 nDoc = 0; - rc = fts3MatchinfoSelectDoctotal(pTab, &pSelect, &nDoc, 0); + rc = fts3MatchinfoSelectDoctotal(pTab, &pSelect, &nDoc, 0, 0); pInfo->aMatchinfo[0] = (u32)nDoc; } break; - case FTS3_MATCHINFO_AVGLENGTH: + case FTS3_MATCHINFO_AVGLENGTH: if( bGlobal ){ sqlite3_int64 nDoc; /* Number of rows in table */ const char *a; /* Aggregate column length array */ + const char *pEnd; /* First byte past end of length array */ - rc = fts3MatchinfoSelectDoctotal(pTab, &pSelect, &nDoc, &a); + rc = fts3MatchinfoSelectDoctotal(pTab, &pSelect, &nDoc, &a, &pEnd); if( rc==SQLITE_OK ){ int iCol; for(iCol=0; iColnCol; iCol++){ u32 iVal; sqlite3_int64 nToken; a += sqlite3Fts3GetVarint(a, &nToken); + if( a>pEnd ){ + rc = SQLITE_CORRUPT_VTAB; + break; + } iVal = (u32)(((u32)(nToken&0xffffffff)+nDoc/2)/nDoc); pInfo->aMatchinfo[iCol] = iVal; } @@ -177362,9 +211461,14 @@ static int fts3MatchinfoValues( if( rc==SQLITE_OK ){ int iCol; const char *a = sqlite3_column_blob(pSelectDocsize, 0); + const char *pEnd = a + sqlite3_column_bytes(pSelectDocsize, 0); for(iCol=0; iColnCol; iCol++){ sqlite3_int64 nToken; - a += sqlite3Fts3GetVarint(a, &nToken); + a += sqlite3Fts3GetVarintBounded(a, pEnd, &nToken); + if( a>pEnd ){ + rc = SQLITE_CORRUPT_VTAB; + break; + } pInfo->aMatchinfo[iCol] = (u32)nToken; } } @@ -177395,14 +211499,14 @@ static int fts3MatchinfoValues( if( rc!=SQLITE_OK ) break; if( bGlobal ){ if( pCsr->pDeferred ){ - rc = fts3MatchinfoSelectDoctotal(pTab, &pSelect, &pInfo->nDoc, 0); + rc = fts3MatchinfoSelectDoctotal(pTab, &pSelect, &pInfo->nDoc,0,0); if( rc!=SQLITE_OK ) break; } - rc = fts3ExprIterate(pExpr, fts3ExprGlobalHitsCb,(void*)pInfo); + rc = sqlite3Fts3ExprIterate(pExpr, fts3ExprGlobalHitsCb,(void*)pInfo); sqlite3Fts3EvalTestDeferred(pCsr, &rc); if( rc!=SQLITE_OK ) break; } - (void)fts3ExprIterate(pExpr, fts3ExprLocalHitsCb,(void*)pInfo); + (void)sqlite3Fts3ExprIterate(pExpr, fts3ExprLocalHitsCb,(void*)pInfo); break; } } @@ -177416,7 +211520,7 @@ static int fts3MatchinfoValues( /* -** Populate pCsr->aMatchinfo[] with data for the current row. The +** Populate pCsr->aMatchinfo[] with data for the current row. The ** 'matchinfo' data is an array of 32-bit unsigned integers (C type u32). */ static void fts3GetMatchinfo( @@ -177436,8 +211540,8 @@ static void fts3GetMatchinfo( sInfo.pCursor = pCsr; sInfo.nCol = pTab->nColumn; - /* If there is cached matchinfo() data, but the format string for the - ** cache does not match the format string for this request, discard + /* If there is cached matchinfo() data, but the format string for the + ** cache does not match the format string for this request, discard ** the cached data. */ if( pCsr->pMIBuffer && strcmp(pCsr->pMIBuffer->zMatchinfo, zArg) ){ sqlite3Fts3MIBufferFree(pCsr->pMIBuffer); @@ -177445,7 +211549,7 @@ static void fts3GetMatchinfo( } /* If Fts3Cursor.pMIBuffer is NULL, then this is the first time the - ** matchinfo function has been called for this query. In this case + ** matchinfo function has been called for this query. In this case ** allocate the array used to accumulate the matchinfo data and ** initialize those elements that are constant for every row. */ @@ -177520,7 +211624,7 @@ SQLITE_PRIVATE void sqlite3Fts3Snippet( /* The returned text includes up to four fragments of text extracted from ** the data in the current row. The first iteration of the for(...) loop - ** below attempts to locate a single fragment of text nToken tokens in + ** below attempts to locate a single fragment of text nToken tokens in ** size that contains at least one instance of all phrases in the query ** expression that appear in the current row. If such a fragment of text ** cannot be found, the second iteration of the loop attempts to locate @@ -177591,7 +211695,7 @@ SQLITE_PRIVATE void sqlite3Fts3Snippet( assert( nFToken>0 ); for(i=0; ipPhrase && pExpr->pPhrase->bIncr ){ + rc = sqlite3Fts3MsrCancel(p->pCsr, pExpr); + pExpr->pPhrase->bIncr = 0; + } + return rc; +} + /* ** Implementation of offsets() function. */ @@ -177681,7 +211801,7 @@ SQLITE_PRIVATE void sqlite3Fts3Offsets( if( rc!=SQLITE_OK ) goto offsets_out; /* Allocate the array of TermOffset iterators. */ - sCtx.aTerm = (TermOffset *)sqlite3_malloc64(sizeof(TermOffset)*nToken); + sCtx.aTerm = (TermOffset *)sqlite3Fts3MallocZero(sizeof(TermOffset)*nToken); if( 0==sCtx.aTerm ){ rc = SQLITE_NOMEM; goto offsets_out; @@ -177689,7 +211809,13 @@ SQLITE_PRIVATE void sqlite3Fts3Offsets( sCtx.iDocid = pCsr->iPrevId; sCtx.pCsr = pCsr; - /* Loop through the table columns, appending offset information to + /* If a query restart will be required, do it here, rather than later of + ** after pointers to poslist buffers that may be invalidated by a restart + ** have been saved. */ + rc = sqlite3Fts3ExprIterate(pCsr->pExpr, fts3ExprRestartIfCb, (void*)&sCtx); + if( rc!=SQLITE_OK ) goto offsets_out; + + /* Loop through the table columns, appending offset information to ** string-buffer res for each column. */ for(iCol=0; iColnColumn; iCol++){ @@ -177702,19 +211828,21 @@ SQLITE_PRIVATE void sqlite3Fts3Offsets( const char *zDoc; int nDoc; - /* Initialize the contents of sCtx.aTerm[] for column iCol. There is - ** no way that this operation can fail, so the return code from - ** fts3ExprIterate() can be discarded. + /* Initialize the contents of sCtx.aTerm[] for column iCol. This + ** operation may fail if the database contains corrupt records. */ sCtx.iCol = iCol; sCtx.iTerm = 0; - (void)fts3ExprIterate(pCsr->pExpr, fts3ExprTermOffsetInit, (void*)&sCtx); + rc = sqlite3Fts3ExprIterate( + pCsr->pExpr, fts3ExprTermOffsetInit, (void*)&sCtx + ); + if( rc!=SQLITE_OK ) goto offsets_out; - /* Retreive the text stored in column iCol. If an SQL NULL is stored + /* Retreive the text stored in column iCol. If an SQL NULL is stored ** in column iCol, jump immediately to the next iteration of the loop. ** If an OOM occurs while retrieving the data (this can happen if SQLite - ** needs to transform the data from utf-16 to utf-8), return SQLITE_NOMEM - ** to the caller. + ** needs to transform the data from utf-16 to utf-8), return SQLITE_NOMEM + ** to the caller. */ zDoc = (const char *)sqlite3_column_text(pCsr->pStmt, iCol+1); nDoc = sqlite3_column_bytes(pCsr->pStmt, iCol+1); @@ -177761,7 +211889,7 @@ SQLITE_PRIVATE void sqlite3Fts3Offsets( } if( rc==SQLITE_OK ){ char aBuffer[64]; - sqlite3_snprintf(sizeof(aBuffer), aBuffer, + sqlite3_snprintf(sizeof(aBuffer), aBuffer, "%d %d %d %d ", iCol, pTerm-sCtx.aTerm, iStart, iEnd-iStart ); rc = fts3StringAppend(&res, aBuffer, -1); @@ -177942,7 +212070,7 @@ static int unicodeDestroy(sqlite3_tokenizer *pTokenizer){ ** ** For each codepoint in the zIn/nIn string, this function checks if the ** sqlite3FtsUnicodeIsalnum() function already returns the desired result. -** If so, no action is taken. Otherwise, the codepoint is added to the +** If so, no action is taken. Otherwise, the codepoint is added to the ** unicode_tokenizer.aiException[] array. For the purposes of tokenization, ** the return value of sqlite3FtsUnicodeIsalnum() is inverted for all ** codepoints in the aiException[] array. @@ -177968,8 +212096,8 @@ static int unicodeAddExceptions( while( zaInput = (const unsigned char *)aInput; if( aInput==0 ){ pCsr->nInput = 0; + pCsr->aInput = (const unsigned char*)""; }else if( nInput<0 ){ pCsr->nInput = (int)strlen(aInput); }else{ @@ -178153,7 +212282,7 @@ static int unicodeNext( const unsigned char *zTerm = &pCsr->aInput[pCsr->nInput]; /* Scan past any delimiter characters before the start of the next token. - ** Return SQLITE_DONE early if this takes us all the way to the end of + ** Return SQLITE_DONE early if this takes us all the way to the end of ** the input. */ while( z=zTerm ) break; READ_UTF8(z, zTerm, iCode); - }while( unicodeIsAlnum(p, (int)iCode) + }while( unicodeIsAlnum(p, (int)iCode) || sqlite3FtsUnicodeIsdiacritic((int)iCode) ); @@ -178200,7 +212329,7 @@ static int unicodeNext( } /* -** Set *ppModule to a pointer to the sqlite3_tokenizer_module +** Set *ppModule to a pointer to the sqlite3_tokenizer_module ** structure for the unicode tokenizer. */ SQLITE_PRIVATE void sqlite3Fts3UnicodeTokenizer(sqlite3_tokenizer_module const **ppModule){ @@ -178255,11 +212384,11 @@ SQLITE_PRIVATE int sqlite3FtsUnicodeIsalnum(int c){ ** range of unicode codepoints that are not either letters or numbers (i.e. ** codepoints for which this function should return 0). ** - ** The most significant 22 bits in each 32-bit value contain the first + ** The most significant 22 bits in each 32-bit value contain the first ** codepoint in the range. The least significant 10 bits are used to store - ** the size of the range (always at least 1). In other words, the value - ** ((C<<22) + N) represents a range of N codepoints starting with codepoint - ** C. It is not possible to represent a range larger than 1023 codepoints + ** the size of the range (always at least 1). In other words, the value + ** ((C<<22) + N) represents a range of N codepoints starting with codepoint + ** C. It is not possible to represent a range larger than 1023 codepoints ** using this format. */ static const unsigned int aEntry[] = { @@ -178384,46 +212513,46 @@ SQLITE_PRIVATE int sqlite3FtsUnicodeIsalnum(int c){ */ static int remove_diacritic(int c, int bComplex){ unsigned short aDia[] = { - 0, 1797, 1848, 1859, 1891, 1928, 1940, 1995, - 2024, 2040, 2060, 2110, 2168, 2206, 2264, 2286, - 2344, 2383, 2472, 2488, 2516, 2596, 2668, 2732, - 2782, 2842, 2894, 2954, 2984, 3000, 3028, 3336, - 3456, 3696, 3712, 3728, 3744, 3766, 3832, 3896, - 3912, 3928, 3944, 3968, 4008, 4040, 4056, 4106, - 4138, 4170, 4202, 4234, 4266, 4296, 4312, 4344, - 4408, 4424, 4442, 4472, 4488, 4504, 6148, 6198, - 6264, 6280, 6360, 6429, 6505, 6529, 61448, 61468, - 61512, 61534, 61592, 61610, 61642, 61672, 61688, 61704, - 61726, 61784, 61800, 61816, 61836, 61880, 61896, 61914, - 61948, 61998, 62062, 62122, 62154, 62184, 62200, 62218, - 62252, 62302, 62364, 62410, 62442, 62478, 62536, 62554, - 62584, 62604, 62640, 62648, 62656, 62664, 62730, 62766, - 62830, 62890, 62924, 62974, 63032, 63050, 63082, 63118, - 63182, 63242, 63274, 63310, 63368, 63390, + 0, 1797, 1848, 1859, 1891, 1928, 1940, 1995, + 2024, 2040, 2060, 2110, 2168, 2206, 2264, 2286, + 2344, 2383, 2472, 2488, 2516, 2596, 2668, 2732, + 2782, 2842, 2894, 2954, 2984, 3000, 3028, 3336, + 3456, 3696, 3712, 3728, 3744, 3766, 3832, 3896, + 3912, 3928, 3944, 3968, 4008, 4040, 4056, 4106, + 4138, 4170, 4202, 4234, 4266, 4296, 4312, 4344, + 4408, 4424, 4442, 4472, 4488, 4504, 6148, 6198, + 6264, 6280, 6360, 6429, 6505, 6529, 61448, 61468, + 61512, 61534, 61592, 61610, 61642, 61672, 61688, 61704, + 61726, 61784, 61800, 61816, 61836, 61880, 61896, 61914, + 61948, 61998, 62062, 62122, 62154, 62184, 62200, 62218, + 62252, 62302, 62364, 62410, 62442, 62478, 62536, 62554, + 62584, 62604, 62640, 62648, 62656, 62664, 62730, 62766, + 62830, 62890, 62924, 62974, 63032, 63050, 63082, 63118, + 63182, 63242, 63274, 63310, 63368, 63390, }; #define HIBIT ((unsigned char)0x80) unsigned char aChar[] = { - '\0', 'a', 'c', 'e', 'i', 'n', - 'o', 'u', 'y', 'y', 'a', 'c', - 'd', 'e', 'e', 'g', 'h', 'i', - 'j', 'k', 'l', 'n', 'o', 'r', - 's', 't', 'u', 'u', 'w', 'y', - 'z', 'o', 'u', 'a', 'i', 'o', - 'u', 'u'|HIBIT, 'a'|HIBIT, 'g', 'k', 'o', - 'o'|HIBIT, 'j', 'g', 'n', 'a'|HIBIT, 'a', - 'e', 'i', 'o', 'r', 'u', 's', - 't', 'h', 'a', 'e', 'o'|HIBIT, 'o', - 'o'|HIBIT, 'y', '\0', '\0', '\0', '\0', - '\0', '\0', '\0', '\0', 'a', 'b', - 'c'|HIBIT, 'd', 'd', 'e'|HIBIT, 'e', 'e'|HIBIT, - 'f', 'g', 'h', 'h', 'i', 'i'|HIBIT, - 'k', 'l', 'l'|HIBIT, 'l', 'm', 'n', - 'o'|HIBIT, 'p', 'r', 'r'|HIBIT, 'r', 's', - 's'|HIBIT, 't', 'u', 'u'|HIBIT, 'v', 'w', - 'w', 'x', 'y', 'z', 'h', 't', - 'w', 'y', 'a', 'a'|HIBIT, 'a'|HIBIT, 'a'|HIBIT, - 'e', 'e'|HIBIT, 'e'|HIBIT, 'i', 'o', 'o'|HIBIT, - 'o'|HIBIT, 'o'|HIBIT, 'u', 'u'|HIBIT, 'u'|HIBIT, 'y', + '\0', 'a', 'c', 'e', 'i', 'n', + 'o', 'u', 'y', 'y', 'a', 'c', + 'd', 'e', 'e', 'g', 'h', 'i', + 'j', 'k', 'l', 'n', 'o', 'r', + 's', 't', 'u', 'u', 'w', 'y', + 'z', 'o', 'u', 'a', 'i', 'o', + 'u', 'u'|HIBIT, 'a'|HIBIT, 'g', 'k', 'o', + 'o'|HIBIT, 'j', 'g', 'n', 'a'|HIBIT, 'a', + 'e', 'i', 'o', 'r', 'u', 's', + 't', 'h', 'a', 'e', 'o'|HIBIT, 'o', + 'o'|HIBIT, 'y', '\0', '\0', '\0', '\0', + '\0', '\0', '\0', '\0', 'a', 'b', + 'c'|HIBIT, 'd', 'd', 'e'|HIBIT, 'e', 'e'|HIBIT, + 'f', 'g', 'h', 'h', 'i', 'i'|HIBIT, + 'k', 'l', 'l'|HIBIT, 'l', 'm', 'n', + 'o'|HIBIT, 'p', 'r', 'r'|HIBIT, 'r', 's', + 's'|HIBIT, 't', 'u', 'u'|HIBIT, 'v', 'w', + 'w', 'x', 'y', 'z', 'h', 't', + 'w', 'y', 'a', 'a'|HIBIT, 'a'|HIBIT, 'a'|HIBIT, + 'e', 'e'|HIBIT, 'e'|HIBIT, 'i', 'o', 'o'|HIBIT, + 'o'|HIBIT, 'o'|HIBIT, 'u', 'u'|HIBIT, 'u'|HIBIT, 'y', }; unsigned int key = (((unsigned int)c)<<3) | 0x00000007; @@ -178545,19 +212674,19 @@ SQLITE_PRIVATE int sqlite3FtsUnicodeFold(int c, int eRemoveDiacritic){ {42802, 1, 62}, {42873, 1, 4}, {42877, 76, 1}, {42878, 1, 10}, {42891, 0, 1}, {42893, 74, 1}, {42896, 1, 4}, {42912, 1, 10}, {42922, 72, 1}, - {65313, 14, 26}, + {65313, 14, 26}, }; static const unsigned short aiOff[] = { - 1, 2, 8, 15, 16, 26, 28, 32, - 37, 38, 40, 48, 63, 64, 69, 71, - 79, 80, 116, 202, 203, 205, 206, 207, - 209, 210, 211, 213, 214, 217, 218, 219, - 775, 7264, 10792, 10795, 23228, 23256, 30204, 54721, - 54753, 54754, 54756, 54787, 54793, 54809, 57153, 57274, - 57921, 58019, 58363, 61722, 65268, 65341, 65373, 65406, - 65408, 65410, 65415, 65424, 65436, 65439, 65450, 65462, - 65472, 65476, 65478, 65480, 65482, 65488, 65506, 65511, - 65514, 65521, 65527, 65528, 65529, + 1, 2, 8, 15, 16, 26, 28, 32, + 37, 38, 40, 48, 63, 64, 69, 71, + 79, 80, 116, 202, 203, 205, 206, 207, + 209, 210, 211, 213, 214, 217, 218, 219, + 775, 7264, 10792, 10795, 23228, 23256, 30204, 54721, + 54753, 54754, 54756, 54787, 54793, 54809, 57153, 57274, + 57921, 58019, 58363, 61722, 65268, 65341, 65373, 65406, + 65408, 65410, 65415, 65424, 65436, 65439, 65450, 65462, + 65472, 65476, 65478, 65480, 65482, 65488, 65506, 65511, + 65514, 65521, 65527, 65528, 65529, }; int ret = c; @@ -178595,7 +212724,7 @@ SQLITE_PRIVATE int sqlite3FtsUnicodeFold(int c, int eRemoveDiacritic){ ret = remove_diacritic(ret, eRemoveDiacritic==2); } } - + else if( c>=66560 && c<66600 ){ ret = c + 40; } @@ -178606,7 +212735,7 @@ SQLITE_PRIVATE int sqlite3FtsUnicodeFold(int c, int eRemoveDiacritic){ #endif /* !defined(SQLITE_DISABLE_FTS3_UNICODE) */ /************** End of fts3_unicode2.c ***************************************/ -/************** Begin file json1.c *******************************************/ +/************** Begin file json.c ********************************************/ /* ** 2015-08-12 ** @@ -178619,98 +212748,295 @@ SQLITE_PRIVATE int sqlite3FtsUnicodeFold(int c, int eRemoveDiacritic){ ** ****************************************************************************** ** -** This SQLite extension implements JSON functions. The interface is -** modeled after MySQL JSON functions: -** -** https://dev.mysql.com/doc/refman/5.7/en/json.html -** -** For the time being, all JSON is stored as pure text. (We might add -** a JSONB type in the future which stores a binary encoding of JSON in -** a BLOB, but there is no support for JSONB in the current implementation. -** This implementation parses JSON text at 250 MB/s, so it is hard to see -** how JSONB might improve on that.) +** SQLite JSON functions. +** +** This file began as an extension in ext/misc/json1.c in 2015. That +** extension proved so useful that it has now been moved into the core. +** +** The original design stored all JSON as pure text, canonical RFC-8259. +** Support for JSON-5 extensions was added with version 3.42.0 (2023-05-16). +** All generated JSON text still conforms strictly to RFC-8259, but text +** with JSON-5 extensions is accepted as input. +** +** Beginning with version 3.45.0 (circa 2024-01-01), these routines also +** accept BLOB values that have JSON encoded using a binary representation +** called "JSONB". The name JSONB comes from PostgreSQL, however the on-disk +** format for SQLite-JSONB is completely different and incompatible with +** PostgreSQL-JSONB. +** +** Decoding and interpreting JSONB is still O(N) where N is the size of +** the input, the same as text JSON. However, the constant of proportionality +** for JSONB is much smaller due to faster parsing. The size of each +** element in JSONB is encoded in its header, so there is no need to search +** for delimiters using persnickety syntax rules. JSONB seems to be about +** 3x faster than text JSON as a result. JSONB is also tends to be slightly +** smaller than text JSON, by 5% or 10%, but there are corner cases where +** JSONB can be slightly larger. So you are not far mistaken to say that +** a JSONB blob is the same size as the equivalent RFC-8259 text. +** +** +** THE JSONB ENCODING: +** +** Every JSON element is encoded in JSONB as a header and a payload. +** The header is between 1 and 9 bytes in size. The payload is zero +** or more bytes. +** +** The lower 4 bits of the first byte of the header determines the +** element type: +** +** 0: NULL +** 1: TRUE +** 2: FALSE +** 3: INT -- RFC-8259 integer literal +** 4: INT5 -- JSON5 integer literal +** 5: FLOAT -- RFC-8259 floating point literal +** 6: FLOAT5 -- JSON5 floating point literal +** 7: TEXT -- Text literal acceptable to both SQL and JSON +** 8: TEXTJ -- Text containing RFC-8259 escapes +** 9: TEXT5 -- Text containing JSON5 and/or RFC-8259 escapes +** 10: TEXTRAW -- Text containing unescaped syntax characters +** 11: ARRAY +** 12: OBJECT +** +** The other three possible values (13-15) are reserved for future +** enhancements. +** +** The upper 4 bits of the first byte determine the size of the header +** and sometimes also the size of the payload. If X is the first byte +** of the element and if X>>4 is between 0 and 11, then the payload +** will be that many bytes in size and the header is exactly one byte +** in size. Other four values for X>>4 (12-15) indicate that the header +** is more than one byte in size and that the payload size is determined +** by the remainder of the header, interpreted as a unsigned big-endian +** integer. +** +** Value of X>>4 Size integer Total header size +** ------------- -------------------- ----------------- +** 12 1 byte (0-255) 2 +** 13 2 byte (0-65535) 3 +** 14 4 byte (0-4294967295) 5 +** 15 8 byte (0-1.8e19) 9 +** +** The payload size need not be expressed in its minimal form. For example, +** if the payload size is 10, the size can be expressed in any of 5 different +** ways: (1) (X>>4)==10, (2) (X>>4)==12 following by one 0x0a byte, +** (3) (X>>4)==13 followed by 0x00 and 0x0a, (4) (X>>4)==14 followed by +** 0x00 0x00 0x00 0x0a, or (5) (X>>4)==15 followed by 7 bytes of 0x00 and +** a single byte of 0x0a. The shorter forms are preferred, of course, but +** sometimes when generating JSONB, the payload size is not known in advance +** and it is convenient to reserve sufficient header space to cover the +** largest possible payload size and then come back later and patch up +** the size when it becomes known, resulting in a non-minimal encoding. +** +** The value (X>>4)==15 is not actually used in the current implementation +** (as SQLite is currently unable to handle BLOBs larger than about 2GB) +** but is included in the design to allow for future enhancements. +** +** The payload follows the header. NULL, TRUE, and FALSE have no payload and +** their payload size must always be zero. The payload for INT, INT5, +** FLOAT, FLOAT5, TEXT, TEXTJ, TEXT5, and TEXTROW is text. Note that the +** "..." or '...' delimiters are omitted from the various text encodings. +** The payload for ARRAY and OBJECT is a list of additional elements that +** are the content for the array or object. The payload for an OBJECT +** must be an even number of elements. The first element of each pair is +** the label and must be of type TEXT, TEXTJ, TEXT5, or TEXTRAW. +** +** A valid JSONB blob consists of a single element, as described above. +** Usually this will be an ARRAY or OBJECT element which has many more +** elements as its content. But the overall blob is just a single element. +** +** Input validation for JSONB blobs simply checks that the element type +** code is between 0 and 12 and that the total size of the element +** (header plus payload) is the same as the size of the BLOB. If those +** checks are true, the BLOB is assumed to be JSONB and processing continues. +** Errors are only raised if some other miscoding is discovered during +** processing. +** +** Additional information can be found in the doc/jsonb.md file of the +** canonical SQLite source tree. +*/ +#ifndef SQLITE_OMIT_JSON +/* #include "sqliteInt.h" */ + +/* JSONB element types +*/ +#define JSONB_NULL 0 /* "null" */ +#define JSONB_TRUE 1 /* "true" */ +#define JSONB_FALSE 2 /* "false" */ +#define JSONB_INT 3 /* integer acceptable to JSON and SQL */ +#define JSONB_INT5 4 /* integer in 0x000 notation */ +#define JSONB_FLOAT 5 /* float acceptable to JSON and SQL */ +#define JSONB_FLOAT5 6 /* float with JSON5 extensions */ +#define JSONB_TEXT 7 /* Text compatible with both JSON and SQL */ +#define JSONB_TEXTJ 8 /* Text with JSON escapes */ +#define JSONB_TEXT5 9 /* Text with JSON-5 escape */ +#define JSONB_TEXTRAW 10 /* SQL text that needs escaping for JSON */ +#define JSONB_ARRAY 11 /* An array */ +#define JSONB_OBJECT 12 /* An object */ + +/* Human-readable names for the JSONB values. The index for each +** string must correspond to the JSONB_* integer above. +*/ +static const char * const jsonbType[] = { + "null", "true", "false", "integer", "integer", + "real", "real", "text", "text", "text", + "text", "array", "object", "", "", "", "" +}; + +/* +** Growing our own isspace() routine this way is twice as fast as +** the library isspace() function, resulting in a 7% overall performance +** increase for the text-JSON parser. (Ubuntu14.10 gcc 4.8.4 x64 with -Os). */ -#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_JSON1) -#if !defined(SQLITEINT_H) -/* #include "sqlite3ext.h" */ +static const char jsonIsSpace[] = { +#ifdef SQLITE_ASCII +/*0 1 2 3 4 5 6 7 8 9 a b c d e f */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, /* 0 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 1 */ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 2 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 3 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 4 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 5 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 6 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 7 */ + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 8 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 9 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* a */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* b */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* c */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* d */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* e */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* f */ #endif -SQLITE_EXTENSION_INIT1 -/* #include */ -/* #include */ -/* #include */ -/* #include */ - -/* Mark a function parameter as unused, to suppress nuisance compiler -** warnings. */ -#ifndef UNUSED_PARAM -# define UNUSED_PARAM(X) (void)(X) +#ifdef SQLITE_EBCDIC +/*0 1 2 3 4 5 6 7 8 9 a b c d e f */ + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, /* 0 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 1 */ + 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 2 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 3 */ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 4 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 5 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 6 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 7 */ + + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 8 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 9 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* a */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* b */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* c */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* d */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* e */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* f */ #endif -#ifndef LARGEST_INT64 -# define LARGEST_INT64 (0xffffffff|(((sqlite3_int64)0x7fffffff)<<32)) -# define SMALLEST_INT64 (((sqlite3_int64)-1) - LARGEST_INT64) -#endif +}; +#define jsonIsspace(x) (jsonIsSpace[(unsigned char)x]) /* -** Versions of isspace(), isalnum() and isdigit() to which it is safe -** to pass signed char values. +** The set of all space characters recognized by jsonIsspace(). +** Useful as the second argument to strspn(). */ -#ifdef sqlite3Isdigit - /* Use the SQLite core versions if this routine is part of the - ** SQLite amalgamation */ -# define safe_isdigit(x) sqlite3Isdigit(x) -# define safe_isalnum(x) sqlite3Isalnum(x) -# define safe_isxdigit(x) sqlite3Isxdigit(x) -#else - /* Use the standard library for separate compilation */ -#include /* amalgamator: keep */ -# define safe_isdigit(x) isdigit((unsigned char)(x)) -# define safe_isalnum(x) isalnum((unsigned char)(x)) -# define safe_isxdigit(x) isxdigit((unsigned char)(x)) +#ifdef SQLITE_ASCII +static const char jsonSpaces[] = "\011\012\015\040"; #endif +#ifdef SQLITE_EBCDIC +static const char jsonSpaces[] = "\005\045\015\100"; +#endif + /* -** Growing our own isspace() routine this way is twice as fast as -** the library isspace() function, resulting in a 7% overall performance -** increase for the parser. (Ubuntu14.10 gcc 4.8.4 x64 with -Os). +** Characters that are special to JSON. Control characters, +** '"' and '\\' and '\''. Actually, '\'' is not special to +** canonical JSON, but it is special in JSON-5, so we include +** it in the set of special characters. */ -static const char jsonIsSpace[] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -}; -#define safe_isspace(x) (jsonIsSpace[(unsigned char)x]) - -#ifndef SQLITE_AMALGAMATION - /* Unsigned integer types. These are already defined in the sqliteInt.h, - ** but the definitions need to be repeated for separate compilation. */ - typedef sqlite3_uint64 u64; - typedef unsigned int u32; - typedef unsigned short int u16; - typedef unsigned char u8; +static const char jsonIsOk[256] = { +#ifdef SQLITE_ASCII +/*0 1 2 3 4 5 6 7 8 9 a b c d e f */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 1 */ + 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, /* 2 */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 3 */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 4 */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, /* 5 */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 6 */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 7 */ + + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 8 */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 9 */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* a */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* b */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* c */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* d */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* e */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 /* f */ +#endif +#ifdef SQLITE_EBCDIC +/*0 1 2 3 4 5 6 7 8 9 a b c d e f */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 1 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 2 */ + 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, /* 3 */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 4 */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 5 */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 6 */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, /* 7 */ + + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 8 */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 9 */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* a */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* b */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* c */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* d */ + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* e */ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 /* f */ #endif +}; /* Objects */ +typedef struct JsonCache JsonCache; typedef struct JsonString JsonString; -typedef struct JsonNode JsonNode; typedef struct JsonParse JsonParse; +/* +** Magic number used for the JSON parse cache in sqlite3_get_auxdata() +*/ +#define JSON_CACHE_ID (-429938) /* Cache entry */ +#define JSON_CACHE_SIZE 4 /* Max number of cache entries */ + +/* +** jsonUnescapeOneChar() returns this invalid code point if it encounters +** a syntax error. +*/ +#define JSON_INVALID_CHAR 0x99999 + +/* A cache mapping JSON text into JSONB blobs. +** +** Each cache entry is a JsonParse object with the following restrictions: +** +** * The bReadOnly flag must be set +** +** * The aBlob[] array must be owned by the JsonParse object. In other +** words, nBlobAlloc must be non-zero. +** +** * eEdit and delta must be zero. +** +** * zJson must be an RCStr. In other words bJsonIsRCStr must be true. +*/ +struct JsonCache { + sqlite3 *db; /* Database connection */ + int nUsed; /* Number of active entries in the cache */ + JsonParse *a[JSON_CACHE_SIZE]; /* One line for each cache entry */ +}; + /* An instance of this object represents a JSON string ** under construction. Really, this is a generic string accumulator ** that can be and is used to create strings other than JSON. +** +** If the generated string is longer than will fit into the zSpace[] buffer, +** then it will be an RCStr string. This aids with caching of large +** JSON strings. */ struct JsonString { sqlite3_context *pCtx; /* Function context - put error messages here */ @@ -178718,88 +213044,232 @@ struct JsonString { u64 nAlloc; /* Bytes of storage available in zBuf[] */ u64 nUsed; /* Bytes of zBuf[] currently used */ u8 bStatic; /* True if zBuf is static space */ - u8 bErr; /* True if an error has been encountered */ + u8 eErr; /* True if an error has been encountered */ char zSpace[100]; /* Initial static space */ }; -/* JSON type values -*/ -#define JSON_NULL 0 -#define JSON_TRUE 1 -#define JSON_FALSE 2 -#define JSON_INT 3 -#define JSON_REAL 4 -#define JSON_STRING 5 -#define JSON_ARRAY 6 -#define JSON_OBJECT 7 +/* Allowed values for JsonString.eErr */ +#define JSTRING_OOM 0x01 /* Out of memory */ +#define JSTRING_MALFORMED 0x02 /* Malformed JSONB */ +#define JSTRING_TOODEEP 0x04 /* JSON nested too deep */ +#define JSTRING_ERR 0x08 /* Error already sent to sqlite3_result */ -/* The "subtype" set for JSON values */ +/* The "subtype" set for text JSON values passed through using +** sqlite3_result_subtype() and sqlite3_value_subtype(). +*/ #define JSON_SUBTYPE 74 /* Ascii for "J" */ /* -** Names of the various JSON types: -*/ -static const char * const jsonType[] = { - "null", "true", "false", "integer", "real", "text", "array", "object" -}; - -/* Bit values for the JsonNode.jnFlag field +** Bit values for the flags passed into various SQL function implementations +** via the sqlite3_user_data() value. */ -#define JNODE_RAW 0x01 /* Content is raw, not JSON encoded */ -#define JNODE_ESCAPE 0x02 /* Content is text with \ escapes */ -#define JNODE_REMOVE 0x04 /* Do not output */ -#define JNODE_REPLACE 0x08 /* Replace with JsonNode.u.iReplace */ -#define JNODE_PATCH 0x10 /* Patch with JsonNode.u.pPatch */ -#define JNODE_APPEND 0x20 /* More ARRAY/OBJECT entries at u.iAppend */ -#define JNODE_LABEL 0x40 /* Is a label of an object */ +#define JSON_JSON 0x01 /* Result is always JSON */ +#define JSON_SQL 0x02 /* Result is always SQL */ +#define JSON_ABPATH 0x03 /* Allow abbreviated JSON path specs */ +#define JSON_ISSET 0x04 /* json_set(), not json_insert() */ +#define JSON_AINS 0x08 /* json_array_insert(), not json_insert() */ +#define JSON_BLOB 0x10 /* Use the BLOB output format */ +#define JSON_INSERT_TYPE(X) (((X)&0xC)>>2) -/* A single node of parsed JSON -*/ -struct JsonNode { - u8 eType; /* One of the JSON_ type values */ - u8 jnFlags; /* JNODE flags */ - u32 n; /* Bytes of content, or number of sub-nodes */ - union { - const char *zJContent; /* Content for INT, REAL, and STRING */ - u32 iAppend; /* More terms for ARRAY and OBJECT */ - u32 iKey; /* Key for ARRAY objects in json_tree() */ - u32 iReplace; /* Replacement content for JNODE_REPLACE */ - JsonNode *pPatch; /* Node chain of patch for JNODE_PATCH */ - } u; -}; -/* A completely parsed JSON string +/* A parsed JSON value. Lifecycle: +** +** 1. JSON comes in and is parsed into a JSONB value in aBlob. The +** original text is stored in zJson. This step is skipped if the +** input is JSONB instead of text JSON. +** +** 2. The aBlob[] array is searched using the JSON path notation, if needed. +** +** 3. Zero or more changes are made to aBlob[] (via json_remove() or +** json_replace() or json_patch() or similar). +** +** 4. New JSON text is generated from the aBlob[] for output. This step +** is skipped if the function is one of the jsonb_* functions that +** returns JSONB instead of text JSON. */ struct JsonParse { - u32 nNode; /* Number of slots of aNode[] used */ - u32 nAlloc; /* Number of slots of aNode[] allocated */ - JsonNode *aNode; /* Array of nodes containing the parse */ - const char *zJson; /* Original JSON string */ - u32 *aUp; /* Index of parent of each node */ - u8 oom; /* Set to true if out of memory */ - u8 nErr; /* Number of errors seen */ - u16 iDepth; /* Nesting depth */ + u8 *aBlob; /* JSONB representation of JSON value */ + u32 nBlob; /* Bytes of aBlob[] actually used */ + u32 nBlobAlloc; /* Bytes allocated to aBlob[]. 0 if aBlob is external */ + char *zJson; /* Json text used for parsing */ + sqlite3 *db; /* The database connection to which this object belongs */ int nJson; /* Length of the zJson string in bytes */ - u32 iHold; /* Replace cache line with the lowest iHold value */ + u32 nJPRef; /* Number of references to this object */ + u32 iErr; /* Error location in zJson[] */ + u16 iDepth; /* Nesting depth */ + u8 nErr; /* Number of errors seen */ + u8 oom; /* Set to true if out of memory */ + u8 bJsonIsRCStr; /* True if zJson is an RCStr */ + u8 hasNonstd; /* True if input uses non-standard features like JSON5 */ + u8 bReadOnly; /* Do not modify. */ + /* Search and edit information. See jsonLookupStep() */ + u8 eEdit; /* Edit operation to apply */ + int delta; /* Size change due to the edit */ + u32 nIns; /* Number of bytes to insert */ + u32 iLabel; /* Location of label if search landed on an object value */ + u8 *aIns; /* Content to be inserted */ }; +/* Allowed values for JsonParse.eEdit */ +#define JEDIT_DEL 1 /* Delete if exists */ +#define JEDIT_REPL 2 /* Overwrite if exists */ +#define JEDIT_INS 3 /* Insert if not exists */ +#define JEDIT_SET 4 /* Insert or overwrite */ +#define JEDIT_AINS 5 /* array_insert() */ + /* ** Maximum nesting depth of JSON for this implementation. ** ** This limit is needed to avoid a stack overflow in the recursive -** descent parser. A depth of 2000 is far deeper than any sane JSON -** should go. +** descent parser. A depth of 1000 is far deeper than any sane JSON +** should go. Historical note: This limit was 2000 prior to version 3.42.0 +*/ +#ifndef SQLITE_JSON_MAX_DEPTH +# define JSON_MAX_DEPTH 1000 +#else +# define JSON_MAX_DEPTH SQLITE_JSON_MAX_DEPTH +#endif + +/* +** Allowed values for the flgs argument to jsonParseFuncArg(); */ -#define JSON_MAX_DEPTH 2000 +#define JSON_EDITABLE 0x01 /* Generate a writable JsonParse object */ +#define JSON_KEEPERROR 0x02 /* Return non-NULL even if there is an error */ + +/************************************************************************** +** Forward references +**************************************************************************/ +static void jsonReturnStringAsBlob(JsonString*); +static int jsonArgIsJsonb(sqlite3_value *pJson, JsonParse *p); +static u32 jsonTranslateBlobToText(JsonParse*,u32,JsonString*); +static void jsonReturnParse(sqlite3_context*,JsonParse*); +static JsonParse *jsonParseFuncArg(sqlite3_context*,sqlite3_value*,u32); +static void jsonParseFree(JsonParse*); +static u32 jsonbPayloadSize(const JsonParse*, u32, u32*); +static u32 jsonUnescapeOneChar(const char*, u32, u32*); + +/************************************************************************** +** Utility routines for dealing with JsonCache objects +**************************************************************************/ + +/* +** Free a JsonCache object. +*/ +static void jsonCacheDelete(JsonCache *p){ + int i; + for(i=0; inUsed; i++){ + jsonParseFree(p->a[i]); + } + sqlite3DbFree(p->db, p); +} +static void jsonCacheDeleteGeneric(void *p){ + jsonCacheDelete((JsonCache*)p); +} + +/* +** Insert a new entry into the cache. If the cache is full, expel +** the least recently used entry. Return SQLITE_OK on success or a +** result code otherwise. +** +** Cache entries are stored in age order, oldest first. +*/ +static int jsonCacheInsert( + sqlite3_context *ctx, /* The SQL statement context holding the cache */ + JsonParse *pParse /* The parse object to be added to the cache */ +){ + JsonCache *p; + + assert( pParse->zJson!=0 ); + assert( pParse->bJsonIsRCStr ); + assert( pParse->delta==0 ); + p = sqlite3_get_auxdata(ctx, JSON_CACHE_ID); + if( p==0 ){ + sqlite3 *db = sqlite3_context_db_handle(ctx); + p = sqlite3DbMallocZero(db, sizeof(*p)); + if( p==0 ) return SQLITE_NOMEM; + p->db = db; + sqlite3_set_auxdata(ctx, JSON_CACHE_ID, p, jsonCacheDeleteGeneric); + p = sqlite3_get_auxdata(ctx, JSON_CACHE_ID); + if( p==0 ) return SQLITE_NOMEM; + } + if( p->nUsed >= JSON_CACHE_SIZE ){ + jsonParseFree(p->a[0]); + memmove(p->a, &p->a[1], (JSON_CACHE_SIZE-1)*sizeof(p->a[0])); + p->nUsed = JSON_CACHE_SIZE-1; + } + assert( pParse->nBlobAlloc>0 ); + pParse->eEdit = 0; + pParse->nJPRef++; + pParse->bReadOnly = 1; + p->a[p->nUsed] = pParse; + p->nUsed++; + return SQLITE_OK; +} + +/* +** Search for a cached translation the json text supplied by pArg. Return +** the JsonParse object if found. Return NULL if not found. +** +** When a match if found, the matching entry is moved to become the +** most-recently used entry if it isn't so already. +** +** The JsonParse object returned still belongs to the Cache and might +** be deleted at any moment. If the caller wants the JsonParse to +** linger, it needs to increment the nPJRef reference counter. +*/ +static JsonParse *jsonCacheSearch( + sqlite3_context *ctx, /* The SQL statement context holding the cache */ + sqlite3_value *pArg /* Function argument containing SQL text */ +){ + JsonCache *p; + int i; + const char *zJson; + int nJson; + + if( sqlite3_value_type(pArg)!=SQLITE_TEXT ){ + return 0; + } + zJson = (const char*)sqlite3_value_text(pArg); + if( zJson==0 ) return 0; + nJson = sqlite3_value_bytes(pArg); + + p = sqlite3_get_auxdata(ctx, JSON_CACHE_ID); + if( p==0 ){ + return 0; + } + for(i=0; inUsed; i++){ + if( p->a[i]->zJson==zJson ) break; + } + if( i>=p->nUsed ){ + for(i=0; inUsed; i++){ + if( p->a[i]->nJson!=nJson ) continue; + if( memcmp(p->a[i]->zJson, zJson, nJson)==0 ) break; + } + } + if( inUsed ){ + if( inUsed-1 ){ + /* Make the matching entry the most recently used entry */ + JsonParse *tmp = p->a[i]; + memmove(&p->a[i], &p->a[i+1], (p->nUsed-i-1)*sizeof(tmp)); + p->a[p->nUsed-1] = tmp; + i = p->nUsed - 1; + } + assert( p->a[i]->delta==0 ); + return p->a[i]; + }else{ + return 0; + } +} /************************************************************************** ** Utility routines for dealing with JsonString objects **************************************************************************/ -/* Set the JsonString object to an empty string +/* Turn uninitialized bulk memory into a valid JsonString object +** holding a zero-length string. */ -static void jsonZero(JsonString *p){ +static void jsonStringZero(JsonString *p){ p->zBuf = p->zSpace; p->nAlloc = sizeof(p->zSpace); p->nUsed = 0; @@ -178808,53 +213278,60 @@ static void jsonZero(JsonString *p){ /* Initialize the JsonString object */ -static void jsonInit(JsonString *p, sqlite3_context *pCtx){ +static void jsonStringInit(JsonString *p, sqlite3_context *pCtx){ p->pCtx = pCtx; - p->bErr = 0; - jsonZero(p); + p->eErr = 0; + jsonStringZero(p); } - /* Free all allocated memory and reset the JsonString object back to its ** initial state. */ -static void jsonReset(JsonString *p){ - if( !p->bStatic ) sqlite3_free(p->zBuf); - jsonZero(p); +static void jsonStringReset(JsonString *p){ + if( !p->bStatic ) sqlite3RCStrUnref(p->zBuf); + jsonStringZero(p); } +/* Report an out-of-memory (OOM) condition +*/ +static void jsonStringOom(JsonString *p){ + p->eErr |= JSTRING_OOM; + if( p->pCtx ) sqlite3_result_error_nomem(p->pCtx); + jsonStringReset(p); +} -/* Report an out-of-memory (OOM) condition +/* Report JSON nested too deep */ -static void jsonOom(JsonString *p){ - p->bErr = 1; - sqlite3_result_error_nomem(p->pCtx); - jsonReset(p); +static void jsonStringTooDeep(JsonString *p){ + p->eErr |= JSTRING_TOODEEP; + assert( p->pCtx!=0 ); + sqlite3_result_error(p->pCtx, "JSON nested too deep", -1); + jsonStringReset(p); } /* Enlarge pJson->zBuf so that it can hold at least N more bytes. ** Return zero on success. Return non-zero on an OOM error */ -static int jsonGrow(JsonString *p, u32 N){ +static int jsonStringGrow(JsonString *p, u32 N){ u64 nTotal = NnAlloc ? p->nAlloc*2 : p->nAlloc+N+10; char *zNew; if( p->bStatic ){ - if( p->bErr ) return 1; - zNew = sqlite3_malloc64(nTotal); + if( p->eErr ) return 1; + zNew = sqlite3RCStrNew(nTotal); if( zNew==0 ){ - jsonOom(p); + jsonStringOom(p); return SQLITE_NOMEM; } memcpy(zNew, p->zBuf, (size_t)p->nUsed); p->zBuf = zNew; p->bStatic = 0; }else{ - zNew = sqlite3_realloc64(p->zBuf, nTotal); - if( zNew==0 ){ - jsonOom(p); + p->zBuf = sqlite3RCStrResize(p->zBuf, nTotal); + if( p->zBuf==0 ){ + p->eErr |= JSTRING_OOM; + jsonStringZero(p); return SQLITE_NOMEM; } - p->zBuf = zNew; } p->nAlloc = nTotal; return SQLITE_OK; @@ -178862,17 +213339,40 @@ static int jsonGrow(JsonString *p, u32 N){ /* Append N bytes from zIn onto the end of the JsonString string. */ -static void jsonAppendRaw(JsonString *p, const char *zIn, u32 N){ - if( (N+p->nUsed >= p->nAlloc) && jsonGrow(p,N)!=0 ) return; +static SQLITE_NOINLINE void jsonStringExpandAndAppend( + JsonString *p, + const char *zIn, + u32 N +){ + assert( N>0 ); + if( jsonStringGrow(p,N) ) return; memcpy(p->zBuf+p->nUsed, zIn, N); p->nUsed += N; } +static void jsonAppendRaw(JsonString *p, const char *zIn, u32 N){ + if( N==0 ) return; + if( N+p->nUsed >= p->nAlloc ){ + jsonStringExpandAndAppend(p,zIn,N); + }else{ + memcpy(p->zBuf+p->nUsed, zIn, N); + p->nUsed += N; + } +} +static void jsonAppendRawNZ(JsonString *p, const char *zIn, u32 N){ + assert( N>0 ); + if( N+p->nUsed >= p->nAlloc ){ + jsonStringExpandAndAppend(p,zIn,N); + }else{ + memcpy(p->zBuf+p->nUsed, zIn, N); + p->nUsed += N; + } +} /* Append formatted text (not to exceed N bytes) to the JsonString. */ static void jsonPrintf(int N, JsonString *p, const char *zFormat, ...){ va_list ap; - if( (p->nUsed + N >= p->nAlloc) && jsonGrow(p, N) ) return; + if( (p->nUsed + N >= p->nAlloc) && jsonStringGrow(p, N) ) return; va_start(ap, zFormat); sqlite3_vsnprintf(N, p->zBuf+p->nUsed, zFormat, ap); va_end(ap); @@ -178881,10 +213381,38 @@ static void jsonPrintf(int N, JsonString *p, const char *zFormat, ...){ /* Append a single character */ -static void jsonAppendChar(JsonString *p, char c){ - if( p->nUsed>=p->nAlloc && jsonGrow(p,1)!=0 ) return; +static SQLITE_NOINLINE void jsonAppendCharExpand(JsonString *p, char c){ + if( jsonStringGrow(p,1) ) return; p->zBuf[p->nUsed++] = c; } +static void jsonAppendChar(JsonString *p, char c){ + if( p->nUsed>=p->nAlloc ){ + jsonAppendCharExpand(p,c); + }else{ + p->zBuf[p->nUsed++] = c; + } +} + +/* Remove a single character from the end of the string +*/ +static void jsonStringTrimOneChar(JsonString *p){ + if( p->eErr==0 ){ + assert( p->nUsed>0 ); + p->nUsed--; + } +} + + +/* Make sure there is a zero terminator on p->zBuf[] +** +** Return true on success. Return false if an OOM prevents this +** from happening. +*/ +static int jsonStringTerminate(JsonString *p){ + jsonAppendChar(p, 0); + jsonStringTrimOneChar(p); + return p->eErr==0; +} /* Append a comma separator to the output buffer, if the previous ** character is not '[' or '{'. @@ -178893,68 +213421,137 @@ static void jsonAppendSeparator(JsonString *p){ char c; if( p->nUsed==0 ) return; c = p->zBuf[p->nUsed-1]; - if( c!='[' && c!='{' ) jsonAppendChar(p, ','); + if( c=='[' || c=='{' ) return; + jsonAppendChar(p, ','); +} + +/* c is a control character. Append the canonical JSON representation +** of that control character to p. +** +** This routine assumes that the output buffer has already been enlarged +** sufficiently to hold the worst-case encoding plus a nul terminator. +*/ +static void jsonAppendControlChar(JsonString *p, u8 c){ + static const char aSpecial[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 'b', 't', 'n', 0, 'f', 'r', 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + assert( sizeof(aSpecial)==32 ); + assert( aSpecial['\b']=='b' ); + assert( aSpecial['\f']=='f' ); + assert( aSpecial['\n']=='n' ); + assert( aSpecial['\r']=='r' ); + assert( aSpecial['\t']=='t' ); + assert( c>=0 && cnUsed+7 <= p->nAlloc ); + if( aSpecial[c] ){ + p->zBuf[p->nUsed] = '\\'; + p->zBuf[p->nUsed+1] = aSpecial[c]; + p->nUsed += 2; + }else{ + p->zBuf[p->nUsed] = '\\'; + p->zBuf[p->nUsed+1] = 'u'; + p->zBuf[p->nUsed+2] = '0'; + p->zBuf[p->nUsed+3] = '0'; + p->zBuf[p->nUsed+4] = "0123456789abcdef"[c>>4]; + p->zBuf[p->nUsed+5] = "0123456789abcdef"[c&0xf]; + p->nUsed += 6; + } } /* Append the N-byte string in zIn to the end of the JsonString string -** under construction. Enclose the string in "..." and escape -** any double-quotes or backslash characters contained within the +** under construction. Enclose the string in double-quotes ("...") and +** escape any double-quotes or backslash characters contained within the ** string. +** +** This routine is a high-runner. There is a measurable performance +** increase associated with unwinding the jsonIsOk[] loop. */ static void jsonAppendString(JsonString *p, const char *zIn, u32 N){ - u32 i; - if( (N+p->nUsed+2 >= p->nAlloc) && jsonGrow(p,N+2)!=0 ) return; + u32 k; + u8 c; + const u8 *z = (const u8*)zIn; + if( z==0 ) return; + if( (N+p->nUsed+2 >= p->nAlloc) && jsonStringGrow(p,N+2)!=0 ) return; p->zBuf[p->nUsed++] = '"'; - for(i=0; i=N ){ + while( k=N ){ + if( k>0 ){ + memcpy(&p->zBuf[p->nUsed], z, k); + p->nUsed += k; + } + break; + } + if( k>0 ){ + memcpy(&p->zBuf[p->nUsed], z, k); + p->nUsed += k; + z += k; + N -= k; + } + c = z[0]; if( c=='"' || c=='\\' ){ - json_simple_escape: - if( (p->nUsed+N+3-i > p->nAlloc) && jsonGrow(p,N+3-i)!=0 ) return; - p->zBuf[p->nUsed++] = '\\'; - }else if( c<=0x1f ){ - static const char aSpecial[] = { - 0, 0, 0, 0, 0, 0, 0, 0, 'b', 't', 'n', 0, 'f', 'r', 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 - }; - assert( sizeof(aSpecial)==32 ); - assert( aSpecial['\b']=='b' ); - assert( aSpecial['\f']=='f' ); - assert( aSpecial['\n']=='n' ); - assert( aSpecial['\r']=='r' ); - assert( aSpecial['\t']=='t' ); - if( aSpecial[c] ){ - c = aSpecial[c]; - goto json_simple_escape; - } - if( (p->nUsed+N+7+i > p->nAlloc) && jsonGrow(p,N+7-i)!=0 ) return; + if( (p->nUsed+N+3 > p->nAlloc) && jsonStringGrow(p,N+3)!=0 ) return; p->zBuf[p->nUsed++] = '\\'; - p->zBuf[p->nUsed++] = 'u'; - p->zBuf[p->nUsed++] = '0'; - p->zBuf[p->nUsed++] = '0'; - p->zBuf[p->nUsed++] = '0' + (c>>4); - c = "0123456789abcdef"[c&0xf]; + p->zBuf[p->nUsed++] = c; + }else if( c=='\'' ){ + p->zBuf[p->nUsed++] = c; + }else{ + if( (p->nUsed+N+7 > p->nAlloc) && jsonStringGrow(p,N+7)!=0 ) return; + jsonAppendControlChar(p, c); } - p->zBuf[p->nUsed++] = c; + z++; + N--; } p->zBuf[p->nUsed++] = '"'; assert( p->nUsednAlloc ); } /* -** Append a function parameter value to the JSON string under -** construction. +** Append an sqlite3_value (such as a function parameter) to the JSON +** string under construction in p. */ -static void jsonAppendValue( +static void jsonAppendSqlValue( JsonString *p, /* Append to this JSON string */ sqlite3_value *pValue /* Value to append */ ){ switch( sqlite3_value_type(pValue) ){ case SQLITE_NULL: { - jsonAppendRaw(p, "null", 4); + jsonAppendRawNZ(p, "null", 4); break; } - case SQLITE_INTEGER: case SQLITE_FLOAT: { + jsonPrintf(100, p, "%!0.17g", sqlite3_value_double(pValue)); + break; + } + case SQLITE_INTEGER: { const char *z = (const char*)sqlite3_value_text(pValue); u32 n = (u32)sqlite3_value_bytes(pValue); jsonAppendRaw(p, z, n); @@ -178971,561 +213568,1253 @@ static void jsonAppendValue( break; } default: { - if( p->bErr==0 ){ + JsonParse px; + memset(&px, 0, sizeof(px)); + if( jsonArgIsJsonb(pValue, &px) ){ + jsonTranslateBlobToText(&px, 0, p); + }else if( p->eErr==0 ){ sqlite3_result_error(p->pCtx, "JSON cannot hold BLOB values", -1); - p->bErr = 2; - jsonReset(p); + p->eErr = JSTRING_ERR; + jsonStringReset(p); } break; } } } - -/* Make the JSON in p the result of the SQL function. +/* Make the text in p (which is probably a generated JSON text string) +** the result of the SQL function. +** +** The JsonString is reset. +** +** If pParse and ctx are both non-NULL, then the SQL string in p is +** loaded into the zJson field of the pParse object as a RCStr and the +** pParse is added to the cache. */ -static void jsonResult(JsonString *p){ - if( p->bErr==0 ){ - sqlite3_result_text64(p->pCtx, p->zBuf, p->nUsed, - p->bStatic ? SQLITE_TRANSIENT : sqlite3_free, - SQLITE_UTF8); - jsonZero(p); +static void jsonReturnString( + JsonString *p, /* String to return */ + JsonParse *pParse, /* JSONB source or NULL */ + sqlite3_context *ctx /* Where to cache */ +){ + assert( (pParse!=0)==(ctx!=0) ); + assert( ctx==0 || ctx==p->pCtx ); + jsonStringTerminate(p); + if( p->eErr==0 ){ + int flags = SQLITE_PTR_TO_INT(sqlite3_user_data(p->pCtx)); + if( flags & JSON_BLOB ){ + jsonReturnStringAsBlob(p); + }else if( p->bStatic ){ + sqlite3_result_text64(p->pCtx, p->zBuf, p->nUsed, + SQLITE_TRANSIENT, SQLITE_UTF8); + }else{ + if( pParse && pParse->bJsonIsRCStr==0 && pParse->nBlobAlloc>0 ){ + int rc; + pParse->zJson = sqlite3RCStrRef(p->zBuf); + pParse->nJson = p->nUsed; + pParse->bJsonIsRCStr = 1; + rc = jsonCacheInsert(ctx, pParse); + if( rc==SQLITE_NOMEM ){ + sqlite3_result_error_nomem(ctx); + jsonStringReset(p); + return; + } + } + sqlite3_result_text64(p->pCtx, sqlite3RCStrRef(p->zBuf), p->nUsed, + sqlite3RCStrUnref, + SQLITE_UTF8); + } + }else if( p->eErr & JSTRING_OOM ){ + sqlite3_result_error_nomem(p->pCtx); + }else if( p->eErr & JSTRING_TOODEEP ){ + /* error already in p->pCtx */ + }else if( p->eErr & JSTRING_MALFORMED ){ + sqlite3_result_error(p->pCtx, "malformed JSON", -1); } - assert( p->bStatic ); + jsonStringReset(p); } /************************************************************************** -** Utility routines for dealing with JsonNode and JsonParse objects +** Utility routines for dealing with JsonParse objects **************************************************************************/ -/* -** Return the number of consecutive JsonNode slots need to represent -** the parsed JSON at pNode. The minimum answer is 1. For ARRAY and -** OBJECT types, the number might be larger. -** -** Appended elements are not counted. The value returned is the number -** by which the JsonNode counter should increment in order to go to the -** next peer value. -*/ -static u32 jsonNodeSize(JsonNode *pNode){ - return pNode->eType>=JSON_ARRAY ? pNode->n+1 : 1; -} - /* ** Reclaim all memory allocated by a JsonParse object. But do not ** delete the JsonParse object itself. */ static void jsonParseReset(JsonParse *pParse){ - sqlite3_free(pParse->aNode); - pParse->aNode = 0; - pParse->nNode = 0; - pParse->nAlloc = 0; - sqlite3_free(pParse->aUp); - pParse->aUp = 0; + assert( pParse->nJPRef<=1 ); + if( pParse->bJsonIsRCStr ){ + sqlite3RCStrUnref(pParse->zJson); + pParse->zJson = 0; + pParse->nJson = 0; + pParse->bJsonIsRCStr = 0; + } + if( pParse->nBlobAlloc ){ + sqlite3DbFree(pParse->db, pParse->aBlob); + pParse->aBlob = 0; + pParse->nBlob = 0; + pParse->nBlobAlloc = 0; + } } /* -** Free a JsonParse object that was obtained from sqlite3_malloc(). +** Decrement the reference count on the JsonParse object. When the +** count reaches zero, free the object. */ static void jsonParseFree(JsonParse *pParse){ - jsonParseReset(pParse); - sqlite3_free(pParse); + if( pParse ){ + if( pParse->nJPRef>1 ){ + pParse->nJPRef--; + }else{ + jsonParseReset(pParse); + sqlite3DbFree(pParse->db, pParse); + } + } } +/************************************************************************** +** Utility routines for the JSON text parser +**************************************************************************/ + /* -** Convert the JsonNode pNode into a pure JSON string and -** append to pOut. Subsubstructure is also included. Return -** the number of JsonNode objects that are encoded. +** Translate a single byte of Hex into an integer. +** This routine only gives a correct answer if h really is a valid hexadecimal +** character: 0..9a..fA..F. But unlike sqlite3HexToInt(), it does not +** assert() if the digit is not hex. */ -static void jsonRenderNode( - JsonNode *pNode, /* The node to render */ - JsonString *pOut, /* Write JSON here */ - sqlite3_value **aReplace /* Replacement values */ -){ - if( pNode->jnFlags & (JNODE_REPLACE|JNODE_PATCH) ){ - if( pNode->jnFlags & JNODE_REPLACE ){ - jsonAppendValue(pOut, aReplace[pNode->u.iReplace]); - return; - } - pNode = pNode->u.pPatch; - } - switch( pNode->eType ){ - default: { - assert( pNode->eType==JSON_NULL ); - jsonAppendRaw(pOut, "null", 4); - break; - } - case JSON_TRUE: { - jsonAppendRaw(pOut, "true", 4); - break; - } - case JSON_FALSE: { - jsonAppendRaw(pOut, "false", 5); - break; - } - case JSON_STRING: { - if( pNode->jnFlags & JNODE_RAW ){ - jsonAppendString(pOut, pNode->u.zJContent, pNode->n); - break; - } - /* Fall through into the next case */ - } - case JSON_REAL: - case JSON_INT: { - jsonAppendRaw(pOut, pNode->u.zJContent, pNode->n); - break; - } - case JSON_ARRAY: { - u32 j = 1; - jsonAppendChar(pOut, '['); - for(;;){ - while( j<=pNode->n ){ - if( (pNode[j].jnFlags & JNODE_REMOVE)==0 ){ - jsonAppendSeparator(pOut); - jsonRenderNode(&pNode[j], pOut, aReplace); - } - j += jsonNodeSize(&pNode[j]); - } - if( (pNode->jnFlags & JNODE_APPEND)==0 ) break; - pNode = &pNode[pNode->u.iAppend]; - j = 1; - } - jsonAppendChar(pOut, ']'); - break; - } - case JSON_OBJECT: { - u32 j = 1; - jsonAppendChar(pOut, '{'); - for(;;){ - while( j<=pNode->n ){ - if( (pNode[j+1].jnFlags & JNODE_REMOVE)==0 ){ - jsonAppendSeparator(pOut); - jsonRenderNode(&pNode[j], pOut, aReplace); - jsonAppendChar(pOut, ':'); - jsonRenderNode(&pNode[j+1], pOut, aReplace); - } - j += 1 + jsonNodeSize(&pNode[j+1]); - } - if( (pNode->jnFlags & JNODE_APPEND)==0 ) break; - pNode = &pNode[pNode->u.iAppend]; - j = 1; - } - jsonAppendChar(pOut, '}'); - break; - } - } +static u8 jsonHexToInt(int h){ +#ifdef SQLITE_ASCII + h += 9*(1&(h>>6)); +#endif +#ifdef SQLITE_EBCDIC + h += 9*(1&~(h>>4)); +#endif + return (u8)(h & 0xf); } /* -** Return a JsonNode and all its descendents as a JSON string. +** Convert a 4-byte hex string into an integer */ -static void jsonReturnJson( - JsonNode *pNode, /* Node to return */ - sqlite3_context *pCtx, /* Return value for this function */ - sqlite3_value **aReplace /* Array of replacement values */ -){ - JsonString s; - jsonInit(&s, pCtx); - jsonRenderNode(pNode, &s, aReplace); - jsonResult(&s); - sqlite3_result_subtype(pCtx, JSON_SUBTYPE); +static u32 jsonHexToInt4(const char *z){ + u32 v; + v = (jsonHexToInt(z[0])<<12) + + (jsonHexToInt(z[1])<<8) + + (jsonHexToInt(z[2])<<4) + + jsonHexToInt(z[3]); + return v; } /* -** Make the JsonNode the return value of the function. +** Return true if z[] begins with 2 (or more) hexadecimal digits */ -static void jsonReturn( - JsonNode *pNode, /* Node to return */ - sqlite3_context *pCtx, /* Return value for this function */ - sqlite3_value **aReplace /* Array of replacement values */ -){ - switch( pNode->eType ){ - default: { - assert( pNode->eType==JSON_NULL ); - sqlite3_result_null(pCtx); - break; - } - case JSON_TRUE: { - sqlite3_result_int(pCtx, 1); - break; - } - case JSON_FALSE: { - sqlite3_result_int(pCtx, 0); - break; - } - case JSON_INT: { - sqlite3_int64 i = 0; - const char *z = pNode->u.zJContent; - if( z[0]=='-' ){ z++; } - while( z[0]>='0' && z[0]<='9' ){ - unsigned v = *(z++) - '0'; - if( i>=LARGEST_INT64/10 ){ - if( i>LARGEST_INT64/10 ) goto int_as_real; - if( z[0]>='0' && z[0]<='9' ) goto int_as_real; - if( v==9 ) goto int_as_real; - if( v==8 ){ - if( pNode->u.zJContent[0]=='-' ){ - sqlite3_result_int64(pCtx, SMALLEST_INT64); - goto int_done; - }else{ - goto int_as_real; +static int jsonIs2Hex(const char *z){ + return sqlite3Isxdigit(z[0]) && sqlite3Isxdigit(z[1]); +} + +/* +** Return true if z[] begins with 4 (or more) hexadecimal digits +*/ +static int jsonIs4Hex(const char *z){ + return jsonIs2Hex(z) && jsonIs2Hex(&z[2]); +} + +/* +** Return the number of bytes of JSON5 whitespace at the beginning of +** the input string z[]. +** +** JSON5 whitespace consists of any of the following characters: +** +** Unicode UTF-8 Name +** U+0009 09 horizontal tab +** U+000a 0a line feed +** U+000b 0b vertical tab +** U+000c 0c form feed +** U+000d 0d carriage return +** U+0020 20 space +** U+00a0 c2 a0 non-breaking space +** U+1680 e1 9a 80 ogham space mark +** U+2000 e2 80 80 en quad +** U+2001 e2 80 81 em quad +** U+2002 e2 80 82 en space +** U+2003 e2 80 83 em space +** U+2004 e2 80 84 three-per-em space +** U+2005 e2 80 85 four-per-em space +** U+2006 e2 80 86 six-per-em space +** U+2007 e2 80 87 figure space +** U+2008 e2 80 88 punctuation space +** U+2009 e2 80 89 thin space +** U+200a e2 80 8a hair space +** U+2028 e2 80 a8 line separator +** U+2029 e2 80 a9 paragraph separator +** U+202f e2 80 af narrow no-break space (NNBSP) +** U+205f e2 81 9f medium mathematical space (MMSP) +** U+3000 e3 80 80 ideographical space +** U+FEFF ef bb bf byte order mark +** +** In addition, comments between '/', '*' and '*', '/' and +** from '/', '/' to end-of-line are also considered to be whitespace. +*/ +static int json5Whitespace(const char *zIn){ + int n = 0; + const u8 *z = (u8*)zIn; + while( 1 /*exit by "goto whitespace_done"*/ ){ + switch( z[n] ){ + case 0x09: + case 0x0a: + case 0x0b: + case 0x0c: + case 0x0d: + case 0x20: { + n++; + break; + } + case '/': { + if( z[n+1]=='*' && z[n+2]!=0 ){ + int j; + for(j=n+3; z[j]!='/' || z[j-1]!='*'; j++){ + if( z[j]==0 ) goto whitespace_done; + } + n = j+1; + break; + }else if( z[n+1]=='/' ){ + int j; + char c; + for(j=n+2; (c = z[j])!=0; j++){ + if( c=='\n' || c=='\r' ) break; + if( 0xe2==(u8)c && 0x80==(u8)z[j+1] + && (0xa8==(u8)z[j+2] || 0xa9==(u8)z[j+2]) + ){ + j += 2; + break; } } + n = j; + if( z[n] ) n++; + break; } - i = i*10 + v; + goto whitespace_done; } - if( pNode->u.zJContent[0]=='-' ){ i = -i; } - sqlite3_result_int64(pCtx, i); - int_done: - break; - int_as_real: /* fall through to real */; - } - case JSON_REAL: { - double r; -#ifdef SQLITE_AMALGAMATION - const char *z = pNode->u.zJContent; - sqlite3AtoF(z, &r, sqlite3Strlen30(z), SQLITE_UTF8); -#else - r = strtod(pNode->u.zJContent, 0); -#endif - sqlite3_result_double(pCtx, r); - break; - } - case JSON_STRING: { -#if 0 /* Never happens because JNODE_RAW is only set by json_set(), - ** json_insert() and json_replace() and those routines do not - ** call jsonReturn() */ - if( pNode->jnFlags & JNODE_RAW ){ - sqlite3_result_text(pCtx, pNode->u.zJContent, pNode->n, - SQLITE_TRANSIENT); - }else -#endif - assert( (pNode->jnFlags & JNODE_RAW)==0 ); - if( (pNode->jnFlags & JNODE_ESCAPE)==0 ){ - /* JSON formatted without any backslash-escapes */ - sqlite3_result_text(pCtx, pNode->u.zJContent+1, pNode->n-2, - SQLITE_TRANSIENT); - }else{ - /* Translate JSON formatted string into raw text */ - u32 i; - u32 n = pNode->n; - const char *z = pNode->u.zJContent; - char *zOut; - u32 j; - zOut = sqlite3_malloc( n+1 ); - if( zOut==0 ){ - sqlite3_result_error_nomem(pCtx); + case 0xc2: { + if( z[n+1]==0xa0 ){ + n += 2; break; } - for(i=1, j=0; i>6)); - zOut[j++] = 0x80 | (v&0x3f); - }else{ - zOut[j++] = (char)(0xe0 | (v>>12)); - zOut[j++] = 0x80 | ((v>>6)&0x3f); - zOut[j++] = 0x80 | (v&0x3f); - } - }else{ - if( c=='b' ){ - c = '\b'; - }else if( c=='f' ){ - c = '\f'; - }else if( c=='n' ){ - c = '\n'; - }else if( c=='r' ){ - c = '\r'; - }else if( c=='t' ){ - c = '\t'; - } - zOut[j++] = c; - } + goto whitespace_done; + } + case 0xe1: { + if( z[n+1]==0x9a && z[n+2]==0x80 ){ + n += 3; + break; + } + goto whitespace_done; + } + case 0xe2: { + if( z[n+1]==0x80 ){ + u8 c = z[n+2]; + if( c<0x80 ) goto whitespace_done; + if( c<=0x8a || c==0xa8 || c==0xa9 || c==0xaf ){ + n += 3; + break; } + }else if( z[n+1]==0x81 && z[n+2]==0x9f ){ + n += 3; + break; } - zOut[j] = 0; - sqlite3_result_text(pCtx, zOut, j, sqlite3_free); + goto whitespace_done; + } + case 0xe3: { + if( z[n+1]==0x80 && z[n+2]==0x80 ){ + n += 3; + break; + } + goto whitespace_done; + } + case 0xef: { + if( z[n+1]==0xbb && z[n+2]==0xbf ){ + n += 3; + break; + } + goto whitespace_done; + } + default: { + goto whitespace_done; } - break; - } - case JSON_ARRAY: - case JSON_OBJECT: { - jsonReturnJson(pNode, pCtx, aReplace); - break; } } + whitespace_done: + return n; } -/* Forward reference */ -static int jsonParseAddNode(JsonParse*,u32,u32,const char*); +/* +** Extra floating-point literals to allow in JSON. +*/ +static const struct NanInfName { + char c1; + char c2; + char n; + char eType; + char nRepl; + char *zMatch; + char *zRepl; +} aNanInfName[] = { + { 'i', 'I', 3, JSONB_FLOAT, 7, "inf", "9.0e999" }, + { 'i', 'I', 8, JSONB_FLOAT, 7, "infinity", "9.0e999" }, + { 'n', 'N', 3, JSONB_NULL, 4, "NaN", "null" }, + { 'q', 'Q', 4, JSONB_NULL, 4, "QNaN", "null" }, + { 's', 'S', 4, JSONB_NULL, 4, "SNaN", "null" }, +}; + /* -** A macro to hint to the compiler that a function should not be -** inlined. +** Report the wrong number of arguments for json_insert(), json_replace() +** or json_set(). */ -#if defined(__GNUC__) -# define JSON_NOINLINE __attribute__((noinline)) -#elif defined(_MSC_VER) && _MSC_VER>=1310 -# define JSON_NOINLINE __declspec(noinline) -#else -# define JSON_NOINLINE -#endif +static void jsonWrongNumArgs( + sqlite3_context *pCtx, + const char *zFuncName +){ + char *zMsg = sqlite3_mprintf("json_%s() needs an odd number of arguments", + zFuncName); + sqlite3_result_error(pCtx, zMsg, -1); + sqlite3_free(zMsg); +} + +/**************************************************************************** +** Utility routines for dealing with the binary BLOB representation of JSON +****************************************************************************/ +/* +** Expand pParse->aBlob so that it holds at least N bytes. +** +** Return the number of errors. +*/ +static int jsonBlobExpand(JsonParse *pParse, u32 N){ + u8 *aNew; + u64 t; + assert( N>pParse->nBlobAlloc ); + if( pParse->nBlobAlloc==0 ){ + t = 100; + }else{ + t = pParse->nBlobAlloc*2; + } + if( tdb, pParse->aBlob, t); + if( aNew==0 ){ pParse->oom = 1; return 1; } + assert( t<0x7fffffff ); + pParse->aBlob = aNew; + pParse->nBlobAlloc = (u32)t; + return 0; +} -static JSON_NOINLINE int jsonParseAddNodeExpand( - JsonParse *pParse, /* Append the node to this object */ - u32 eType, /* Node type */ - u32 n, /* Content size or sub-node count */ - const char *zContent /* Content */ +/* +** If pParse->aBlob is not previously editable (because it is taken +** from sqlite3_value_blob(), as indicated by the fact that +** pParse->nBlobAlloc==0 and pParse->nBlob>0) then make it editable +** by making a copy into space obtained from malloc. +** +** Return true on success. Return false on OOM. +*/ +static int jsonBlobMakeEditable(JsonParse *pParse, u32 nExtra){ + u8 *aOld; + u32 nSize; + assert( !pParse->bReadOnly ); + if( pParse->oom ) return 0; + if( pParse->nBlobAlloc>0 ) return 1; + aOld = pParse->aBlob; + nSize = pParse->nBlob + nExtra; + pParse->aBlob = 0; + if( jsonBlobExpand(pParse, nSize) ){ + return 0; + } + assert( pParse->nBlobAlloc >= pParse->nBlob + nExtra ); + memcpy(pParse->aBlob, aOld, pParse->nBlob); + return 1; +} + +/* Expand pParse->aBlob and append one bytes. +*/ +static SQLITE_NOINLINE void jsonBlobExpandAndAppendOneByte( + JsonParse *pParse, + u8 c ){ - u32 nNew; - JsonNode *pNew; - assert( pParse->nNode>=pParse->nAlloc ); - if( pParse->oom ) return -1; - nNew = pParse->nAlloc*2 + 10; - pNew = sqlite3_realloc64(pParse->aNode, sizeof(JsonNode)*nNew); - if( pNew==0 ){ - pParse->oom = 1; - return -1; + jsonBlobExpand(pParse, pParse->nBlob+1); + if( pParse->oom==0 ){ + assert( pParse->nBlob+1<=pParse->nBlobAlloc ); + pParse->aBlob[pParse->nBlob++] = c; } - pParse->nAlloc = nNew; - pParse->aNode = pNew; - assert( pParse->nNodenAlloc ); - return jsonParseAddNode(pParse, eType, n, zContent); } -/* -** Create a new JsonNode instance based on the arguments and append that -** instance to the JsonParse. Return the index in pParse->aNode[] of the -** new node, or -1 if a memory allocation fails. +/* Append a single character. +*/ +static void jsonBlobAppendOneByte(JsonParse *pParse, u8 c){ + if( pParse->nBlob >= pParse->nBlobAlloc ){ + jsonBlobExpandAndAppendOneByte(pParse, c); + }else{ + pParse->aBlob[pParse->nBlob++] = c; + } +} + +/* Slow version of jsonBlobAppendNode() that first resizes the +** pParse->aBlob structure. +*/ +static void jsonBlobAppendNode(JsonParse*,u8,u64,const void*); +static SQLITE_NOINLINE void jsonBlobExpandAndAppendNode( + JsonParse *pParse, + u8 eType, + u64 szPayload, + const void *aPayload +){ + if( jsonBlobExpand(pParse, pParse->nBlob+szPayload+9) ) return; + jsonBlobAppendNode(pParse, eType, szPayload, aPayload); +} + + +/* Append a node type byte together with the payload size and +** possibly also the payload. +** +** If aPayload is not NULL, then it is a pointer to the payload which +** is also appended. If aPayload is NULL, the pParse->aBlob[] array +** is resized (if necessary) so that it is big enough to hold the +** payload, but the payload is not appended and pParse->nBlob is left +** pointing to where the first byte of payload will eventually be. */ -static int jsonParseAddNode( - JsonParse *pParse, /* Append the node to this object */ - u32 eType, /* Node type */ - u32 n, /* Content size or sub-node count */ - const char *zContent /* Content */ +static void jsonBlobAppendNode( + JsonParse *pParse, /* The JsonParse object under construction */ + u8 eType, /* Node type. One of JSONB_* */ + u64 szPayload, /* Number of bytes of payload */ + const void *aPayload /* The payload. Might be NULL */ ){ - JsonNode *p; - if( pParse->nNode>=pParse->nAlloc ){ - return jsonParseAddNodeExpand(pParse, eType, n, zContent); + u8 *a; + if( pParse->nBlob+szPayload+9 > pParse->nBlobAlloc ){ + jsonBlobExpandAndAppendNode(pParse,eType,szPayload,aPayload); + return; + } + assert( pParse->aBlob!=0 ); + a = &pParse->aBlob[pParse->nBlob]; + if( szPayload<=11 ){ + a[0] = eType | (szPayload<<4); + pParse->nBlob += 1; + }else if( szPayload<=0xff ){ + a[0] = eType | 0xc0; + a[1] = szPayload & 0xff; + pParse->nBlob += 2; + }else if( szPayload<=0xffff ){ + a[0] = eType | 0xd0; + a[1] = (szPayload >> 8) & 0xff; + a[2] = szPayload & 0xff; + pParse->nBlob += 3; + }else{ + a[0] = eType | 0xe0; + a[1] = (szPayload >> 24) & 0xff; + a[2] = (szPayload >> 16) & 0xff; + a[3] = (szPayload >> 8) & 0xff; + a[4] = szPayload & 0xff; + pParse->nBlob += 5; + } + if( aPayload ){ + pParse->nBlob += szPayload; + memcpy(&pParse->aBlob[pParse->nBlob-szPayload], aPayload, szPayload); } - p = &pParse->aNode[pParse->nNode]; - p->eType = (u8)eType; - p->jnFlags = 0; - p->n = n; - p->u.zJContent = zContent; - return pParse->nNode++; +} + +/* Change the payload size for the node at index i to be szPayload. +*/ +static int jsonBlobChangePayloadSize( + JsonParse *pParse, + u32 i, + u32 szPayload +){ + u8 *a; + u8 szType; + u8 nExtra; + u8 nNeeded; + int delta; + if( pParse->oom ) return 0; + a = &pParse->aBlob[i]; + szType = a[0]>>4; + if( szType<=11 ){ + nExtra = 0; + }else if( szType==12 ){ + nExtra = 1; + }else if( szType==13 ){ + nExtra = 2; + }else if( szType==14 ){ + nExtra = 4; + }else{ + nExtra = 8; + } + if( szPayload<=11 ){ + nNeeded = 0; + }else if( szPayload<=0xff ){ + nNeeded = 1; + }else if( szPayload<=0xffff ){ + nNeeded = 2; + }else{ + nNeeded = 4; + } + delta = nNeeded - nExtra; + if( delta ){ + u32 newSize = pParse->nBlob + delta; + if( delta>0 ){ + if( newSize>pParse->nBlobAlloc && jsonBlobExpand(pParse, newSize) ){ + return 0; /* OOM error. Error state recorded in pParse->oom. */ + } + a = &pParse->aBlob[i]; + memmove(&a[1+delta], &a[1], pParse->nBlob - (i+1)); + }else{ + memmove(&a[1], &a[1-delta], pParse->nBlob - (i+1-delta)); + } + pParse->nBlob = newSize; + } + if( nNeeded==0 ){ + a[0] = (a[0] & 0x0f) | (szPayload<<4); + }else if( nNeeded==1 ){ + a[0] = (a[0] & 0x0f) | 0xc0; + a[1] = szPayload & 0xff; + }else if( nNeeded==2 ){ + a[0] = (a[0] & 0x0f) | 0xd0; + a[1] = (szPayload >> 8) & 0xff; + a[2] = szPayload & 0xff; + }else{ + a[0] = (a[0] & 0x0f) | 0xe0; + a[1] = (szPayload >> 24) & 0xff; + a[2] = (szPayload >> 16) & 0xff; + a[3] = (szPayload >> 8) & 0xff; + a[4] = szPayload & 0xff; + } + return delta; } /* -** Return true if z[] begins with 4 (or more) hexadecimal digits +** If z[0] is 'u' and is followed by exactly 4 hexadecimal character, +** then set *pOp to JSONB_TEXTJ and return true. If not, do not make +** any changes to *pOp and return false. */ -static int jsonIs4Hex(const char *z){ - int i; - for(i=0; i<4; i++) if( !safe_isxdigit(z[i]) ) return 0; +static int jsonIs4HexB(const char *z, int *pOp){ + if( z[0]!='u' ) return 0; + if( !jsonIs4Hex(&z[1]) ) return 0; + *pOp = JSONB_TEXTJ; return 1; } /* -** Parse a single JSON value which begins at pParse->zJson[i]. Return the -** index of the first character past the end of the value parsed. +** Check a single element of the JSONB in pParse for validity. +** +** The element to be checked starts at offset i and must end at on the +** last byte before iEnd. +** +** Return 0 if everything is correct. Return the 1-based byte offset of the +** error if a problem is detected. (In other words, if the error is at offset +** 0, return 1). +*/ +static u32 jsonbValidityCheck( + const JsonParse *pParse, /* Input JSONB. Only aBlob and nBlob are used */ + u32 i, /* Start of element as pParse->aBlob[i] */ + u32 iEnd, /* One more than the last byte of the element */ + u32 iDepth /* Current nesting depth */ +){ + u32 n, sz, j, k; + const u8 *z; + u8 x; + if( iDepth>JSON_MAX_DEPTH ) return i+1; + sz = 0; + n = jsonbPayloadSize(pParse, i, &sz); + if( NEVER(n==0) ) return i+1; /* Checked by caller */ + if( NEVER(i+n+sz!=iEnd) ) return i+1; /* Checked by caller */ + z = pParse->aBlob; + x = z[i] & 0x0f; + switch( x ){ + case JSONB_NULL: + case JSONB_TRUE: + case JSONB_FALSE: { + return n+sz==1 ? 0 : i+1; + } + case JSONB_INT: { + if( sz<1 ) return i+1; + j = i+n; + if( z[j]=='-' ){ + j++; + if( sz<2 ) return i+1; + } + k = i+n+sz; + while( jk ) return j+1; + if( z[j+1]!='.' && z[j+1]!='e' && z[j+1]!='E' ) return j+1; + j++; + } + for(; j0 ) return j+1; + if( x==JSONB_FLOAT && (j==k-1 || !sqlite3Isdigit(z[j+1])) ){ + return j+1; + } + seen = 1; + continue; + } + if( z[j]=='e' || z[j]=='E' ){ + if( seen==2 ) return j+1; + if( j==k-1 ) return j+1; + if( z[j+1]=='+' || z[j+1]=='-' ){ + j++; + if( j==k-1 ) return j+1; + } + seen = 2; + continue; + } + return j+1; + } + if( seen==0 ) return i+1; + return 0; + } + case JSONB_TEXT: { + j = i+n; + k = j+sz; + while( j=k ){ + return j+1; + }else if( strchr("\"\\/bfnrt",z[j+1])!=0 ){ + j++; + }else if( z[j+1]=='u' ){ + if( j+5>=k ) return j+1; + if( !jsonIs4Hex((const char*)&z[j+2]) ) return j+1; + j++; + }else if( x!=JSONB_TEXT5 ){ + return j+1; + }else{ + u32 c = 0; + u32 szC = jsonUnescapeOneChar((const char*)&z[j], k-j, &c); + if( c==JSON_INVALID_CHAR ) return j+1; + j += szC - 1; + } + } + j++; + } + return 0; + } + case JSONB_TEXTRAW: { + return 0; + } + case JSONB_ARRAY: { + u32 sub; + j = i+n; + k = j+sz; + while( jk ) return j+1; + sub = jsonbValidityCheck(pParse, j, j+n+sz, iDepth+1); + if( sub ) return sub; + j += n + sz; + } + assert( j==k ); + return 0; + } + case JSONB_OBJECT: { + u32 cnt = 0; + u32 sub; + j = i+n; + k = j+sz; + while( jk ) return j+1; + if( (cnt & 1)==0 ){ + x = z[j] & 0x0f; + if( xJSONB_TEXTRAW ) return j+1; + } + sub = jsonbValidityCheck(pParse, j, j+n+sz, iDepth+1); + if( sub ) return sub; + cnt++; + j += n + sz; + } + assert( j==k ); + if( (cnt & 1)!=0 ) return j+1; + return 0; + } + default: { + return i+1; + } + } +} + +/* +** Translate a single element of JSON text at pParse->zJson[i] into +** its equivalent binary JSONB representation. Append the translation into +** pParse->aBlob[] beginning at pParse->nBlob. The size of +** pParse->aBlob[] is increased as necessary. ** -** Return negative for a syntax error. Special cases: return -2 if the -** first non-whitespace character is '}' and return -3 if the first -** non-whitespace character is ']'. +** Return the index of the first character past the end of the element parsed, +** or one of the following special result codes: +** +** 0 End of input +** -1 Syntax error or OOM +** -2 '}' seen \ +** -3 ']' seen \___ For these returns, pParse->iErr is set to +** -4 ',' seen / the index in zJson[] of the seen character +** -5 ':' seen / */ -static int jsonParseValue(JsonParse *pParse, u32 i){ +static int jsonTranslateTextToBlob(JsonParse *pParse, u32 i){ char c; u32 j; - int iThis; + u32 iThis, iStart; int x; - JsonNode *pNode; + u8 t; const char *z = pParse->zJson; - while( safe_isspace(z[i]) ){ i++; } - if( (c = z[i])=='{' ){ +json_parse_restart: + switch( (u8)z[i] ){ + case '{': { /* Parse object */ - iThis = jsonParseAddNode(pParse, JSON_OBJECT, 0, 0); - if( iThis<0 ) return -1; + iThis = pParse->nBlob; + jsonBlobAppendNode(pParse, JSONB_OBJECT, pParse->nJson-i, 0); + if( ++pParse->iDepth > JSON_MAX_DEPTH ){ + pParse->iErr = i; + return -1; + } + iStart = pParse->nBlob; for(j=i+1;;j++){ - while( safe_isspace(z[j]) ){ j++; } - if( ++pParse->iDepth > JSON_MAX_DEPTH ) return -1; - x = jsonParseValue(pParse, j); - if( x<0 ){ - pParse->iDepth--; - if( x==(-2) && pParse->nNode==(u32)iThis+1 ) return j+1; - return -1; + u32 iBlob = pParse->nBlob; + x = jsonTranslateTextToBlob(pParse, j); + if( x<=0 ){ + int op; + if( x==(-2) ){ + j = pParse->iErr; + if( pParse->nBlob!=(u32)iStart ) pParse->hasNonstd = 1; + break; + } + j += json5Whitespace(&z[j]); + op = JSONB_TEXT; + if( sqlite3JsonId1(z[j]) + || (z[j]=='\\' && jsonIs4HexB(&z[j+1], &op)) + ){ + int k = j+1; + while( (sqlite3JsonId2(z[k]) && json5Whitespace(&z[k])==0) + || (z[k]=='\\' && jsonIs4HexB(&z[k+1], &op)) + ){ + k++; + } + assert( iBlob==pParse->nBlob ); + jsonBlobAppendNode(pParse, op, k-j, &z[j]); + pParse->hasNonstd = 1; + x = k; + }else{ + if( x!=-1 ) pParse->iErr = j; + return -1; + } } if( pParse->oom ) return -1; - pNode = &pParse->aNode[pParse->nNode-1]; - if( pNode->eType!=JSON_STRING ) return -1; - pNode->jnFlags |= JNODE_LABEL; + t = pParse->aBlob[iBlob] & 0x0f; + if( tJSONB_TEXTRAW ){ + pParse->iErr = j; + return -1; + } j = x; - while( safe_isspace(z[j]) ){ j++; } - if( z[j]!=':' ) return -1; - j++; - x = jsonParseValue(pParse, j); - pParse->iDepth--; - if( x<0 ) return -1; + if( z[j]==':' ){ + j++; + }else{ + if( jsonIsspace(z[j]) ){ + /* strspn() is not helpful here */ + do{ j++; }while( jsonIsspace(z[j]) ); + if( z[j]==':' ){ + j++; + goto parse_object_value; + } + } + x = jsonTranslateTextToBlob(pParse, j); + if( x!=(-5) ){ + if( x!=(-1) ) pParse->iErr = j; + return -1; + } + j = pParse->iErr+1; + } + parse_object_value: + x = jsonTranslateTextToBlob(pParse, j); + if( x<=0 ){ + if( x!=(-1) ) pParse->iErr = j; + return -1; + } j = x; - while( safe_isspace(z[j]) ){ j++; } - c = z[j]; - if( c==',' ) continue; - if( c!='}' ) return -1; - break; + if( z[j]==',' ){ + continue; + }else if( z[j]=='}' ){ + break; + }else{ + if( jsonIsspace(z[j]) ){ + j += 1 + (u32)strspn(&z[j+1], jsonSpaces); + if( z[j]==',' ){ + continue; + }else if( z[j]=='}' ){ + break; + } + } + x = jsonTranslateTextToBlob(pParse, j); + if( x==(-4) ){ + j = pParse->iErr; + continue; + } + if( x==(-2) ){ + j = pParse->iErr; + break; + } + } + pParse->iErr = j; + return -1; } - pParse->aNode[iThis].n = pParse->nNode - (u32)iThis - 1; + jsonBlobChangePayloadSize(pParse, iThis, pParse->nBlob - iStart); + pParse->iDepth--; return j+1; - }else if( c=='[' ){ + } + case '[': { /* Parse array */ - iThis = jsonParseAddNode(pParse, JSON_ARRAY, 0, 0); - if( iThis<0 ) return -1; + iThis = pParse->nBlob; + assert( i<=(u32)pParse->nJson ); + jsonBlobAppendNode(pParse, JSONB_ARRAY, pParse->nJson - i, 0); + iStart = pParse->nBlob; + if( pParse->oom ) return -1; + if( ++pParse->iDepth > JSON_MAX_DEPTH ){ + pParse->iErr = i; + return -1; + } for(j=i+1;;j++){ - while( safe_isspace(z[j]) ){ j++; } - if( ++pParse->iDepth > JSON_MAX_DEPTH ) return -1; - x = jsonParseValue(pParse, j); - pParse->iDepth--; - if( x<0 ){ - if( x==(-3) && pParse->nNode==(u32)iThis+1 ) return j+1; + x = jsonTranslateTextToBlob(pParse, j); + if( x<=0 ){ + if( x==(-3) ){ + j = pParse->iErr; + if( pParse->nBlob!=iStart ) pParse->hasNonstd = 1; + break; + } + if( x!=(-1) ) pParse->iErr = j; return -1; } j = x; - while( safe_isspace(z[j]) ){ j++; } - c = z[j]; - if( c==',' ) continue; - if( c!=']' ) return -1; - break; + if( z[j]==',' ){ + continue; + }else if( z[j]==']' ){ + break; + }else{ + if( jsonIsspace(z[j]) ){ + j += 1 + (u32)strspn(&z[j+1], jsonSpaces); + if( z[j]==',' ){ + continue; + }else if( z[j]==']' ){ + break; + } + } + x = jsonTranslateTextToBlob(pParse, j); + if( x==(-4) ){ + j = pParse->iErr; + continue; + } + if( x==(-3) ){ + j = pParse->iErr; + break; + } + } + pParse->iErr = j; + return -1; } - pParse->aNode[iThis].n = pParse->nNode - (u32)iThis - 1; + jsonBlobChangePayloadSize(pParse, iThis, pParse->nBlob - iStart); + pParse->iDepth--; return j+1; - }else if( c=='"' ){ + } + case '\'': { + u8 opcode; + char cDelim; + pParse->hasNonstd = 1; + opcode = JSONB_TEXT; + goto parse_string; + case '"': /* Parse string */ - u8 jnFlags = 0; + opcode = JSONB_TEXT; + parse_string: + cDelim = z[i]; j = i+1; - for(;;){ - c = z[j]; - if( (c & ~0x1f)==0 ){ - /* Control characters are not allowed in strings */ - return -1; + while( 1 /*exit-by-break*/ ){ + if( jsonIsOk[(u8)z[j]] ){ + if( !jsonIsOk[(u8)z[j+1]] ){ + j += 1; + }else if( !jsonIsOk[(u8)z[j+2]] ){ + j += 2; + }else{ + j += 3; + continue; + } } - if( c=='\\' ){ + c = z[j]; + if( c==cDelim ){ + break; + }else if( c=='\\' ){ c = z[++j]; if( c=='"' || c=='\\' || c=='/' || c=='b' || c=='f' || c=='n' || c=='r' || c=='t' - || (c=='u' && jsonIs4Hex(z+j+1)) ){ - jnFlags = JNODE_ESCAPE; + || (c=='u' && jsonIs4Hex(&z[j+1])) ){ + if( opcode==JSONB_TEXT ) opcode = JSONB_TEXTJ; + }else if( c=='\'' || c=='v' || c=='\n' +#ifdef SQLITE_BUG_COMPATIBLE_20250510 + || (c=='0') /* Legacy bug compatible */ +#else + || (c=='0' && !sqlite3Isdigit(z[j+1])) /* Correct implementation */ +#endif + || (0xe2==(u8)c && 0x80==(u8)z[j+1] + && (0xa8==(u8)z[j+2] || 0xa9==(u8)z[j+2])) + || (c=='x' && jsonIs2Hex(&z[j+1])) ){ + opcode = JSONB_TEXT5; + pParse->hasNonstd = 1; + }else if( c=='\r' ){ + if( z[j+1]=='\n' ) j++; + opcode = JSONB_TEXT5; + pParse->hasNonstd = 1; }else{ + pParse->iErr = j; return -1; } + }else if( c<=0x1f ){ + if( c==0 ){ + pParse->iErr = j; + return -1; + } + /* Control characters are not allowed in canonical JSON string + ** literals, but are allowed in JSON5 string literals. */ + opcode = JSONB_TEXT5; + pParse->hasNonstd = 1; }else if( c=='"' ){ - break; + opcode = JSONB_TEXT5; } j++; } - jsonParseAddNode(pParse, JSON_STRING, j+1-i, &z[i]); - if( !pParse->oom ) pParse->aNode[pParse->nNode-1].jnFlags = jnFlags; + jsonBlobAppendNode(pParse, opcode, j-1-i, &z[i+1]); return j+1; - }else if( c=='n' - && strncmp(z+i,"null",4)==0 - && !safe_isalnum(z[i+4]) ){ - jsonParseAddNode(pParse, JSON_NULL, 0, 0); - return i+4; - }else if( c=='t' - && strncmp(z+i,"true",4)==0 - && !safe_isalnum(z[i+4]) ){ - jsonParseAddNode(pParse, JSON_TRUE, 0, 0); - return i+4; - }else if( c=='f' - && strncmp(z+i,"false",5)==0 - && !safe_isalnum(z[i+5]) ){ - jsonParseAddNode(pParse, JSON_FALSE, 0, 0); - return i+5; - }else if( c=='-' || (c>='0' && c<='9') ){ + } + case 't': { + if( strncmp(z+i,"true",4)==0 && !sqlite3Isalnum(z[i+4]) ){ + jsonBlobAppendOneByte(pParse, JSONB_TRUE); + return i+4; + } + pParse->iErr = i; + return -1; + } + case 'f': { + if( strncmp(z+i,"false",5)==0 && !sqlite3Isalnum(z[i+5]) ){ + jsonBlobAppendOneByte(pParse, JSONB_FALSE); + return i+5; + } + pParse->iErr = i; + return -1; + } + case '+': { + u8 seenE; + pParse->hasNonstd = 1; + t = 0x00; /* Bit 0x01: JSON5. Bit 0x02: FLOAT */ + goto parse_number; + case '.': + if( sqlite3Isdigit(z[i+1]) ){ + pParse->hasNonstd = 1; + t = 0x03; /* Bit 0x01: JSON5. Bit 0x02: FLOAT */ + seenE = 0; + goto parse_number_2; + } + pParse->iErr = i; + return -1; + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': /* Parse number */ - u8 seenDP = 0; - u8 seenE = 0; + t = 0x00; /* Bit 0x01: JSON5. Bit 0x02: FLOAT */ + parse_number: + seenE = 0; assert( '-' < '0' ); + assert( '+' < '0' ); + assert( '.' < '0' ); + c = z[i]; + if( c<='0' ){ - j = c=='-' ? i+1 : i; - if( z[j]=='0' && z[j+1]>='0' && z[j+1]<='9' ) return -1; + if( c=='0' ){ + if( (z[i+1]=='x' || z[i+1]=='X') && sqlite3Isxdigit(z[i+2]) ){ + assert( t==0x00 ); + pParse->hasNonstd = 1; + t = 0x01; + for(j=i+3; sqlite3Isxdigit(z[j]); j++){} + goto parse_number_finish; + }else if( sqlite3Isdigit(z[i+1]) ){ + pParse->iErr = i+1; + return -1; + } + }else{ + if( !sqlite3Isdigit(z[i+1]) ){ + /* JSON5 allows for "+Infinity" and "-Infinity" using exactly + ** that case. SQLite also allows these in any case and it allows + ** "+inf" and "-inf". */ + if( (z[i+1]=='I' || z[i+1]=='i') + && sqlite3StrNICmp(&z[i+1], "inf",3)==0 + ){ + pParse->hasNonstd = 1; + if( z[i]=='-' ){ + jsonBlobAppendNode(pParse, JSONB_FLOAT, 6, "-9e999"); + }else{ + jsonBlobAppendNode(pParse, JSONB_FLOAT, 5, "9e999"); + } + return i + (sqlite3StrNICmp(&z[i+4],"inity",5)==0 ? 9 : 4); + } + if( z[i+1]=='.' ){ + pParse->hasNonstd = 1; + t |= 0x01; + goto parse_number_2; + } + pParse->iErr = i; + return -1; + } + if( z[i+1]=='0' ){ + if( sqlite3Isdigit(z[i+2]) ){ + pParse->iErr = i+1; + return -1; + }else if( (z[i+2]=='x' || z[i+2]=='X') && sqlite3Isxdigit(z[i+3]) ){ + pParse->hasNonstd = 1; + t |= 0x01; + for(j=i+4; sqlite3Isxdigit(z[j]); j++){} + goto parse_number_finish; + } + } + } } - j = i+1; - for(;; j++){ + + parse_number_2: + for(j=i+1;; j++){ c = z[j]; - if( c>='0' && c<='9' ) continue; + if( sqlite3Isdigit(c) ) continue; if( c=='.' ){ - if( z[j-1]=='-' ) return -1; - if( seenDP ) return -1; - seenDP = 1; + if( (t & 0x02)!=0 ){ + pParse->iErr = j; + return -1; + } + t |= 0x02; continue; } if( c=='e' || c=='E' ){ - if( z[j-1]<'0' ) return -1; - if( seenE ) return -1; - seenDP = seenE = 1; + if( z[j-1]<'0' ){ + if( ALWAYS(z[j-1]=='.') && ALWAYS(j-2>=i) && sqlite3Isdigit(z[j-2]) ){ + pParse->hasNonstd = 1; + t |= 0x01; + }else{ + pParse->iErr = j; + return -1; + } + } + if( seenE ){ + pParse->iErr = j; + return -1; + } + t |= 0x02; + seenE = 1; c = z[j+1]; if( c=='+' || c=='-' ){ j++; c = z[j+1]; } - if( c<'0' || c>'9' ) return -1; + if( c<'0' || c>'9' ){ + pParse->iErr = j; + return -1; + } continue; } break; } - if( z[j-1]<'0' ) return -1; - jsonParseAddNode(pParse, seenDP ? JSON_REAL : JSON_INT, - j - i, &z[i]); + if( z[j-1]<'0' ){ + if( ALWAYS(z[j-1]=='.') && ALWAYS(j-2>=i) && sqlite3Isdigit(z[j-2]) ){ + pParse->hasNonstd = 1; + t |= 0x01; + }else{ + pParse->iErr = j; + return -1; + } + } + parse_number_finish: + assert( JSONB_INT+0x01==JSONB_INT5 ); + assert( JSONB_FLOAT+0x01==JSONB_FLOAT5 ); + assert( JSONB_INT+0x02==JSONB_FLOAT ); + if( z[i]=='+' ) i++; + jsonBlobAppendNode(pParse, JSONB_INT+t, j-i, &z[i]); return j; - }else if( c=='}' ){ + } + case '}': { + pParse->iErr = i; return -2; /* End of {...} */ - }else if( c==']' ){ + } + case ']': { + pParse->iErr = i; return -3; /* End of [...] */ - }else if( c==0 ){ + } + case ',': { + pParse->iErr = i; + return -4; /* List separator */ + } + case ':': { + pParse->iErr = i; + return -5; /* Object label/value separator */ + } + case 0: { return 0; /* End of file */ - }else{ + } + case 0x09: + case 0x0a: + case 0x0d: + case 0x20: { + i += 1 + (u32)strspn(&z[i+1], jsonSpaces); + goto json_parse_restart; + } + case 0x0b: + case 0x0c: + case '/': + case 0xc2: + case 0xe1: + case 0xe2: + case 0xe3: + case 0xef: { + j = json5Whitespace(&z[i]); + if( j>0 ){ + i += j; + pParse->hasNonstd = 1; + goto json_parse_restart; + } + pParse->iErr = i; + return -1; + } + case 'n': { + if( strncmp(z+i,"null",4)==0 && !sqlite3Isalnum(z[i+4]) ){ + jsonBlobAppendOneByte(pParse, JSONB_NULL); + return i+4; + } + /* fall-through into the default case that checks for NaN */ + /* no break */ deliberate_fall_through + } + default: { + u32 k; + int nn; + c = z[i]; + for(k=0; khasNonstd = 1; + return i + nn; + } + pParse->iErr = i; return -1; /* Syntax error */ } + } /* End switch(z[i]) */ } + /* ** Parse a complete JSON string. Return 0 on success or non-zero if there -** are any errors. If an error occurs, free all memory associated with -** pParse. +** are any errors. If an error occurs, free all memory held by pParse, +** but not pParse itself. ** -** pParse is uninitialized when this routine is called. +** pParse must be initialized to an empty parse object prior to calling +** this routine. */ -static int jsonParse( +static int jsonConvertTextToBlob( JsonParse *pParse, /* Initialize and fill this JsonParse object */ - sqlite3_context *pCtx, /* Report errors here */ - const char *zJson /* Input JSON text to be parsed */ + sqlite3_context *pCtx /* Report errors here */ ){ int i; - memset(pParse, 0, sizeof(*pParse)); - if( zJson==0 ) return 1; - pParse->zJson = zJson; - i = jsonParseValue(pParse, 0); + const char *zJson = pParse->zJson; + i = jsonTranslateTextToBlob(pParse, 0); if( pParse->oom ) i = -1; if( i>0 ){ +#ifdef SQLITE_DEBUG assert( pParse->iDepth==0 ); - while( safe_isspace(zJson[i]) ) i++; - if( zJson[i] ) i = -1; + if( sqlite3Config.bJsonSelfcheck ){ + assert( jsonbValidityCheck(pParse, 0, pParse->nBlob, 0)==0 ); + } +#endif + while( jsonIsspace(zJson[i]) ) i++; + if( zJson[i] ){ + i += json5Whitespace(&zJson[i]); + if( zJson[i] ){ + if( pCtx ) sqlite3_result_error(pCtx, "malformed JSON", -1); + jsonParseReset(pParse); + return 1; + } + pParse->hasNonstd = 1; + } } if( i<=0 ){ if( pCtx!=0 ){ @@ -179541,421 +214830,1860 @@ static int jsonParse( return 0; } -/* Mark node i of pParse as being a child of iParent. Call recursively -** to fill in all the descendants of node i. +/* +** The input string pStr is a well-formed JSON text string. Convert +** this into the JSONB format and make it the return value of the +** SQL function. +*/ +static void jsonReturnStringAsBlob(JsonString *pStr){ + JsonParse px; + assert( pStr->eErr==0 ); + memset(&px, 0, sizeof(px)); + px.zJson = pStr->zBuf; + px.nJson = pStr->nUsed; + px.db = sqlite3_context_db_handle(pStr->pCtx); + (void)jsonTranslateTextToBlob(&px, 0); + if( px.oom ){ + sqlite3DbFree(px.db, px.aBlob); + sqlite3_result_error_nomem(pStr->pCtx); + }else{ + assert( px.nBlobAlloc>0 ); + assert( !px.bReadOnly ); + sqlite3_result_blob(pStr->pCtx, px.aBlob, px.nBlob, SQLITE_DYNAMIC); + } +} + +/* The byte at index i is a node type-code. This routine +** determines the payload size for that node and writes that +** payload size in to *pSz. It returns the offset from i to the +** beginning of the payload. Return 0 on error. */ -static void jsonParseFillInParentage(JsonParse *pParse, u32 i, u32 iParent){ - JsonNode *pNode = &pParse->aNode[i]; - u32 j; - pParse->aUp[i] = iParent; - switch( pNode->eType ){ - case JSON_ARRAY: { - for(j=1; j<=pNode->n; j += jsonNodeSize(pNode+j)){ - jsonParseFillInParentage(pParse, i+j, i); +static u32 jsonbPayloadSize(const JsonParse *pParse, u32 i, u32 *pSz){ + u8 x; + u32 sz; + u32 n; + if( i>=pParse->nBlob ){ + *pSz = 0; + return 0; + }else if( (x = pParse->aBlob[i]>>4)<=11 ){ + sz = x; + n = 1; + }else if( x==12 ){ + if( i+1>=pParse->nBlob ){ + *pSz = 0; + return 0; + } + sz = pParse->aBlob[i+1]; + n = 2; + }else if( x==13 ){ + if( i+2>=pParse->nBlob ){ + *pSz = 0; + return 0; + } + sz = (pParse->aBlob[i+1]<<8) + pParse->aBlob[i+2]; + n = 3; + }else if( x==14 ){ + if( i+4>=pParse->nBlob ){ + *pSz = 0; + return 0; + } + sz = ((u32)pParse->aBlob[i+1]<<24) + (pParse->aBlob[i+2]<<16) + + (pParse->aBlob[i+3]<<8) + pParse->aBlob[i+4]; + n = 5; + }else{ + if( i+8>=pParse->nBlob + || pParse->aBlob[i+1]!=0 + || pParse->aBlob[i+2]!=0 + || pParse->aBlob[i+3]!=0 + || pParse->aBlob[i+4]!=0 + ){ + *pSz = 0; + return 0; + } + sz = ((u32)pParse->aBlob[i+5]<<24) + (pParse->aBlob[i+6]<<16) + + (pParse->aBlob[i+7]<<8) + pParse->aBlob[i+8]; + n = 9; + } + if( (i64)i+sz+n > pParse->nBlob + && (i64)i+sz+n > pParse->nBlob-pParse->delta + ){ + *pSz = 0; + return 0; + } + *pSz = sz; + return n; +} + + +/* +** Translate the binary JSONB representation of JSON beginning at +** pParse->aBlob[i] into a JSON text string. Append the JSON +** text onto the end of pOut. Return the index in pParse->aBlob[] +** of the first byte past the end of the element that is translated. +** +** If an error is detected in the BLOB input, the pOut->eErr flag +** might get set to JSTRING_MALFORMED. But not all BLOB input errors +** are detected. So a malformed JSONB input might either result +** in an error, or in incorrect JSON. +** +** The pOut->eErr JSTRING_OOM flag is set on a OOM. +*/ +static u32 jsonTranslateBlobToText( + JsonParse *pParse, /* the complete parse of the JSON */ + u32 i, /* Start rendering at this index */ + JsonString *pOut /* Write JSON here */ +){ + u32 sz, n, j, iEnd; + + n = jsonbPayloadSize(pParse, i, &sz); + if( n==0 ){ + pOut->eErr |= JSTRING_MALFORMED; + return pParse->nBlob+1; + } + switch( pParse->aBlob[i] & 0x0f ){ + case JSONB_NULL: { + jsonAppendRawNZ(pOut, "null", 4); + return i+1; + } + case JSONB_TRUE: { + jsonAppendRawNZ(pOut, "true", 4); + return i+1; + } + case JSONB_FALSE: { + jsonAppendRawNZ(pOut, "false", 5); + return i+1; + } + case JSONB_INT: + case JSONB_FLOAT: { + if( sz==0 ) goto malformed_jsonb; + jsonAppendRaw(pOut, (const char*)&pParse->aBlob[i+n], sz); + break; + } + case JSONB_INT5: { /* Integer literal in hexadecimal notation */ + u32 k = 2; + sqlite3_uint64 u = 0; + const char *zIn = (const char*)&pParse->aBlob[i+n]; + int bOverflow = 0; + if( sz==0 ) goto malformed_jsonb; + if( zIn[0]=='-' ){ + jsonAppendChar(pOut, '-'); + k++; + }else if( zIn[0]=='+' ){ + k++; + } + for(; keErr |= JSTRING_MALFORMED; + break; + }else if( (u>>60)!=0 ){ + bOverflow = 1; + }else{ + u = u*16 + sqlite3HexToInt(zIn[k]); + } + } + jsonPrintf(100,pOut,bOverflow?"9.0e999":"%llu", u); + break; + } + case JSONB_FLOAT5: { /* Float literal missing digits beside "." */ + u32 k = 0; + const char *zIn = (const char*)&pParse->aBlob[i+n]; + if( sz==0 ) goto malformed_jsonb; + if( zIn[0]=='-' ){ + jsonAppendChar(pOut, '-'); + if( sz<=1 ) goto malformed_jsonb; + k = 1; + } + if( zIn[k]=='.' ){ + jsonAppendChar(pOut, '0'); + } + for(; knUsed+sz+2<=pOut->nAlloc || jsonStringGrow(pOut, sz+2)==0 ){ + pOut->zBuf[pOut->nUsed] = '"'; + memcpy(pOut->zBuf+pOut->nUsed+1,(const char*)&pParse->aBlob[i+n],sz); + pOut->zBuf[pOut->nUsed+sz+1] = '"'; + pOut->nUsed += sz+2; + } + break; + } + case JSONB_TEXT5: { + const char *zIn; + u32 k; + u32 sz2 = sz; + zIn = (const char*)&pParse->aBlob[i+n]; + jsonAppendChar(pOut, '"'); + while( sz2>0 ){ + for(k=0; k0 ){ + jsonAppendRawNZ(pOut, zIn, k); + if( k>=sz2 ){ + break; + } + zIn += k; + sz2 -= k; + } + if( zIn[0]=='"' ){ + jsonAppendRawNZ(pOut, "\\\"", 2); + zIn++; + sz2--; + continue; + } + if( zIn[0]<=0x1f ){ + if( pOut->nUsed+7>pOut->nAlloc && jsonStringGrow(pOut,7) ) break; + jsonAppendControlChar(pOut, zIn[0]); + zIn++; + sz2--; + continue; + } + assert( zIn[0]=='\\' ); + assert( sz2>=1 ); + if( sz2<2 ){ + pOut->eErr |= JSTRING_MALFORMED; + break; + } + switch( (u8)zIn[1] ){ + case '\'': + jsonAppendChar(pOut, '\''); + break; + case 'v': + jsonAppendRawNZ(pOut, "\\u000b", 6); + break; + case 'x': + if( sz2<4 ){ + pOut->eErr |= JSTRING_MALFORMED; + sz2 = 2; + break; + } + jsonAppendRawNZ(pOut, "\\u00", 4); + jsonAppendRawNZ(pOut, &zIn[2], 2); + zIn += 2; + sz2 -= 2; + break; + case '0': + jsonAppendRawNZ(pOut, "\\u0000", 6); + break; + case '\r': + if( sz2>2 && zIn[2]=='\n' ){ + zIn++; + sz2--; + } + break; + case '\n': + break; + case 0xe2: + /* '\' followed by either U+2028 or U+2029 is ignored as + ** whitespace. Not that in UTF8, U+2028 is 0xe2 0x80 0x29. + ** U+2029 is the same except for the last byte */ + if( sz2<4 + || 0x80!=(u8)zIn[2] + || (0xa8!=(u8)zIn[3] && 0xa9!=(u8)zIn[3]) + ){ + pOut->eErr |= JSTRING_MALFORMED; + sz2 = 2; + break; + } + zIn += 2; + sz2 -= 2; + break; + default: + jsonAppendRawNZ(pOut, zIn, 2); + break; + } + assert( sz2>=2 ); + zIn += 2; + sz2 -= 2; + } + jsonAppendChar(pOut, '"'); + break; + } + case JSONB_TEXTRAW: { + jsonAppendString(pOut, (const char*)&pParse->aBlob[i+n], sz); + break; + } + case JSONB_ARRAY: { + jsonAppendChar(pOut, '['); + j = i+n; + iEnd = j+sz; + if( ++pParse->iDepth > JSON_MAX_DEPTH ){ + jsonStringTooDeep(pOut); + } + while( jeErr==0 ){ + j = jsonTranslateBlobToText(pParse, j, pOut); + jsonAppendChar(pOut, ','); + } + pParse->iDepth--; + if( j>iEnd ) pOut->eErr |= JSTRING_MALFORMED; + if( sz>0 ) jsonStringTrimOneChar(pOut); + jsonAppendChar(pOut, ']'); + break; + } + case JSONB_OBJECT: { + int x = 0; + jsonAppendChar(pOut, '{'); + j = i+n; + iEnd = j+sz; + if( ++pParse->iDepth > JSON_MAX_DEPTH ){ + jsonStringTooDeep(pOut); } + while( jeErr==0 ){ + j = jsonTranslateBlobToText(pParse, j, pOut); + jsonAppendChar(pOut, (x++ & 1) ? ',' : ':'); + } + pParse->iDepth--; + if( (x & 1)!=0 || j>iEnd ) pOut->eErr |= JSTRING_MALFORMED; + if( sz>0 ) jsonStringTrimOneChar(pOut); + jsonAppendChar(pOut, '}'); + break; + } + + default: { + malformed_jsonb: + pOut->eErr |= JSTRING_MALFORMED; + break; + } + } + return i+n+sz; +} + +/* Context for recursion of json_pretty() +*/ +typedef struct JsonPretty JsonPretty; +struct JsonPretty { + JsonParse *pParse; /* The BLOB being rendered */ + JsonString *pOut; /* Generate pretty output into this string */ + const char *zIndent; /* Use this text for indentation */ + u32 szIndent; /* Bytes in zIndent[] */ + u32 nIndent; /* Current level of indentation */ +}; + +/* Append indentation to the pretty JSON under construction */ +static void jsonPrettyIndent(JsonPretty *pPretty){ + u32 jj; + for(jj=0; jjnIndent; jj++){ + jsonAppendRaw(pPretty->pOut, pPretty->zIndent, pPretty->szIndent); + } +} + +/* +** Translate the binary JSONB representation of JSON beginning at +** pParse->aBlob[i] into a JSON text string. Append the JSON +** text onto the end of pOut. Return the index in pParse->aBlob[] +** of the first byte past the end of the element that is translated. +** +** This is a variant of jsonTranslateBlobToText() that "pretty-prints" +** the output. Extra whitespace is inserted to make the JSON easier +** for humans to read. +** +** If an error is detected in the BLOB input, the pOut->eErr flag +** might get set to JSTRING_MALFORMED. But not all BLOB input errors +** are detected. So a malformed JSONB input might either result +** in an error, or in incorrect JSON. +** +** The pOut->eErr JSTRING_OOM flag is set on a OOM. +*/ +static u32 jsonTranslateBlobToPrettyText( + JsonPretty *pPretty, /* Pretty-printing context */ + u32 i /* Start rendering at this index */ +){ + u32 sz, n, j, iEnd; + JsonParse *pParse = pPretty->pParse; + JsonString *pOut = pPretty->pOut; + n = jsonbPayloadSize(pParse, i, &sz); + if( n==0 ){ + pOut->eErr |= JSTRING_MALFORMED; + return pParse->nBlob+1; + } + switch( pParse->aBlob[i] & 0x0f ){ + case JSONB_ARRAY: { + j = i+n; + iEnd = j+sz; + jsonAppendChar(pOut, '['); + if( jnIndent++; + if( pPretty->nIndent >= JSON_MAX_DEPTH ){ + jsonStringTooDeep(pOut); + } + while( pOut->eErr==0 ){ + jsonPrettyIndent(pPretty); + j = jsonTranslateBlobToPrettyText(pPretty, j); + if( j>=iEnd ) break; + jsonAppendRawNZ(pOut, ",\n", 2); + } + jsonAppendChar(pOut, '\n'); + pPretty->nIndent--; + jsonPrettyIndent(pPretty); + } + jsonAppendChar(pOut, ']'); + i = iEnd; break; } - case JSON_OBJECT: { - for(j=1; j<=pNode->n; j += jsonNodeSize(pNode+j+1)+1){ - pParse->aUp[i+j] = i; - jsonParseFillInParentage(pParse, i+j+1, i); + case JSONB_OBJECT: { + j = i+n; + iEnd = j+sz; + jsonAppendChar(pOut, '{'); + if( jnIndent++; + if( pPretty->nIndent >= JSON_MAX_DEPTH ){ + jsonStringTooDeep(pOut); + } + pParse->iDepth = pPretty->nIndent; + while( pOut->eErr==0 ){ + jsonPrettyIndent(pPretty); + j = jsonTranslateBlobToText(pParse, j, pOut); + if( j>iEnd ){ + pOut->eErr |= JSTRING_MALFORMED; + break; + } + jsonAppendRawNZ(pOut, ": ", 2); + j = jsonTranslateBlobToPrettyText(pPretty, j); + if( j>=iEnd ) break; + jsonAppendRawNZ(pOut, ",\n", 2); + } + jsonAppendChar(pOut, '\n'); + pPretty->nIndent--; + jsonPrettyIndent(pPretty); } + jsonAppendChar(pOut, '}'); + i = iEnd; break; } default: { + i = jsonTranslateBlobToText(pParse, i, pOut); break; } } + return i; } /* -** Compute the parentage of all nodes in a completed parse. +** Given that a JSONB_ARRAY object starts at offset i, return +** the number of entries in that array. */ -static int jsonParseFindParents(JsonParse *pParse){ - u32 *aUp; - assert( pParse->aUp==0 ); - aUp = pParse->aUp = sqlite3_malloc64( sizeof(u32)*pParse->nNode ); - if( aUp==0 ){ - pParse->oom = 1; - return SQLITE_NOMEM; +static u32 jsonbArrayCount(JsonParse *pParse, u32 iRoot){ + u32 n, sz, i, iEnd; + u32 k = 0; + n = jsonbPayloadSize(pParse, iRoot, &sz); + iEnd = iRoot+n+sz; + for(i=iRoot+n; n>0 && idelta. */ -#define JSON_CACHE_ID (-429938) /* First cache entry */ -#define JSON_CACHE_SZ 4 /* Max number of cache entries */ +static void jsonAfterEditSizeAdjust(JsonParse *pParse, u32 iRoot){ + u32 sz = 0; + u32 nBlob; + assert( pParse->delta!=0 ); + assert( pParse->nBlobAlloc >= pParse->nBlob ); + nBlob = pParse->nBlob; + pParse->nBlob = pParse->nBlobAlloc; + (void)jsonbPayloadSize(pParse, iRoot, &sz); + pParse->nBlob = nBlob; + sz += pParse->delta; + pParse->delta += jsonBlobChangePayloadSize(pParse, iRoot, sz); +} /* -** Obtain a complete parse of the JSON found in the first argument -** of the argv array. Use the sqlite3_get_auxdata() cache for this -** parse if it is available. If the cache is not available or if it -** is no longer valid, parse the JSON again and return the new parse, -** and also register the new parse so that it will be available for -** future sqlite3_get_auxdata() calls. +** If the JSONB at aIns[0..nIns-1] can be expanded (by denormalizing the +** size field) by d bytes, then write the expansion into aOut[] and +** return true. In this way, an overwrite happens without changing the +** size of the JSONB, which reduces memcpy() operations and also make it +** faster and easier to update the B-Tree entry that contains the JSONB +** in the database. +** +** If the expansion of aIns[] by d bytes cannot be (easily) accomplished +** then return false. +** +** The d parameter is guaranteed to be between 1 and 8. +** +** This routine is an optimization. A correct answer is obtained if it +** always leaves the output unchanged and returns false. */ -static JsonParse *jsonParseCached( - sqlite3_context *pCtx, - sqlite3_value **argv, - sqlite3_context *pErrCtx +static int jsonBlobOverwrite( + u8 *aOut, /* Overwrite here */ + const u8 *aIns, /* New content */ + u32 nIns, /* Bytes of new content */ + u32 d /* Need to expand new content by this much */ ){ - const char *zJson = (const char*)sqlite3_value_text(argv[0]); - int nJson = sqlite3_value_bytes(argv[0]); - JsonParse *p; - JsonParse *pMatch = 0; - int iKey; - int iMinKey = 0; - u32 iMinHold = 0xffffffff; - u32 iMaxHold = 0; - if( zJson==0 ) return 0; - for(iKey=0; iKey>4 ){ + default: { /* aIns[] header size 1 */ + if( ((1<nJson==nJson - && memcmp(p->zJson,zJson,nJson)==0 - ){ - p->nErr = 0; - pMatch = p; - }else if( p->iHoldiHold; - iMinKey = iKey; + case 12: { /* aIns[] header size is 2 */ + if( ((1<iHold>iMaxHold ){ - iMaxHold = p->iHold; + case 13: { /* aIns[] header size is 3 */ + if( d!=2 && d!=6 ) return 0; /* d must be 2 or 6 */ + i = d + 3; /* New hdr sz: 5 or 9 */ + szHdr = 3; + break; + } + case 14: { /* aIns[] header size is 5 */ + if( d!=4 ) return 0; /* d must be 4 */ + i = 9; /* New hdr sz: 9 */ + szHdr = 5; + break; + } + case 15: { /* aIns[] header size is 9 */ + return 0; /* No solution */ } } - if( pMatch ){ - pMatch->nErr = 0; - pMatch->iHold = iMaxHold+1; - return pMatch; + assert( i>=2 && i<=9 && aType[i-2]!=0 ); + aOut[0] = (aIns[0] & 0x0f) | aType[i-2]; + memcpy(&aOut[i], &aIns[szHdr], nIns-szHdr); + szPayload = nIns - szHdr; + while( 1/*edit-by-break*/ ){ + i--; + aOut[i] = szPayload & 0xff; + if( i==1 ) break; + szPayload >>= 8; } - p = sqlite3_malloc64( sizeof(*p) + nJson + 1 ); - if( p==0 ){ - sqlite3_result_error_nomem(pCtx); - return 0; + assert( (szPayload>>8)==0 ); + return 1; +} + +/* +** Modify the JSONB blob at pParse->aBlob by removing nDel bytes of +** content beginning at iDel, and replacing them with nIns bytes of +** content given by aIns. +** +** nDel may be zero, in which case no bytes are removed. But iDel is +** still important as new bytes will be insert beginning at iDel. +** +** aIns may be zero, in which case space is created to hold nIns bytes +** beginning at iDel, but that space is uninitialized. +** +** Set pParse->oom if an OOM occurs. +*/ +static void jsonBlobEdit( + JsonParse *pParse, /* The JSONB to be modified is in pParse->aBlob */ + u32 iDel, /* First byte to be removed */ + u32 nDel, /* Number of bytes to remove */ + const u8 *aIns, /* Content to insert */ + u32 nIns /* Bytes of content to insert */ +){ + i64 d = (i64)nIns - (i64)nDel; + assert( pParse->nBlob >= (u64)iDel + (u64)nDel ); + if( d<0 && d>=(-8) && aIns!=0 + && jsonBlobOverwrite(&pParse->aBlob[iDel], aIns, nIns, (int)-d) + ){ + return; } - memset(p, 0, sizeof(*p)); - p->zJson = (char*)&p[1]; - memcpy((char*)p->zJson, zJson, nJson+1); - if( jsonParse(p, pErrCtx, p->zJson) ){ - sqlite3_free(p); - return 0; + if( d!=0 ){ + if( pParse->nBlob + d > pParse->nBlobAlloc ){ + jsonBlobExpand(pParse, pParse->nBlob+d); + if( pParse->oom ) return; + } + memmove(&pParse->aBlob[iDel+nIns], + &pParse->aBlob[iDel+nDel], + pParse->nBlob - (iDel+nDel)); + pParse->nBlob += d; + pParse->delta += d; + } + if( nIns && aIns ){ + memcpy(&pParse->aBlob[iDel], aIns, nIns); + } +} + +/* +** Return the number of escaped newlines to be ignored. +** An escaped newline is a one of the following byte sequences: +** +** 0x5c 0x0a +** 0x5c 0x0d +** 0x5c 0x0d 0x0a +** 0x5c 0xe2 0x80 0xa8 +** 0x5c 0xe2 0x80 0xa9 +*/ +static u32 jsonBytesToBypass(const char *z, u32 n){ + u32 i = 0; + while( i+10 ); + assert( z[0]=='\\' ); + if( n<2 ){ + *piOut = JSON_INVALID_CHAR; + return n; + } + switch( (u8)z[1] ){ + case 'u': { + u32 v, vlo; + if( n<6 ){ + *piOut = JSON_INVALID_CHAR; + return n; + } + v = jsonHexToInt4(&z[2]); + if( (v & 0xfc00)==0xd800 + && n>=12 + && z[6]=='\\' + && z[7]=='u' + && ((vlo = jsonHexToInt4(&z[8]))&0xfc00)==0xdc00 + ){ + *piOut = ((v&0x3ff)<<10) + (vlo&0x3ff) + 0x10000; + return 12; + }else{ + *piOut = v; + return 6; + } + } + case 'b': { *piOut = '\b'; return 2; } + case 'f': { *piOut = '\f'; return 2; } + case 'n': { *piOut = '\n'; return 2; } + case 'r': { *piOut = '\r'; return 2; } + case 't': { *piOut = '\t'; return 2; } + case 'v': { *piOut = '\v'; return 2; } + case '0': { + /* JSON5 requires that the \0 escape not be followed by a digit. + ** But SQLite did not enforce this restriction in versions 3.42.0 + ** through 3.49.2. That was a bug. But some applications might have + ** come to depend on that bug. Use the SQLITE_BUG_COMPATIBLE_20250510 + ** option to restore the old buggy behavior. */ +#ifdef SQLITE_BUG_COMPATIBLE_20250510 + /* Legacy bug-compatible behavior */ + *piOut = 0; +#else + /* Correct behavior */ + *piOut = (n>2 && sqlite3Isdigit(z[2])) ? JSON_INVALID_CHAR : 0; +#endif + return 2; + } + case '\'': + case '"': + case '/': + case '\\':{ *piOut = z[1]; return 2; } + case 'x': { + if( n<4 ){ + *piOut = JSON_INVALID_CHAR; + return n; + } + *piOut = (jsonHexToInt(z[2])<<4) | jsonHexToInt(z[3]); + return 4; + } + case 0xe2: + case '\r': + case '\n': { + u32 nSkip = jsonBytesToBypass(z, n); + if( nSkip==0 ){ + *piOut = JSON_INVALID_CHAR; + return n; + }else if( nSkip==n ){ + *piOut = 0; + return n; + }else if( z[nSkip]=='\\' ){ + return nSkip + jsonUnescapeOneChar(&z[nSkip], n-nSkip, piOut); + }else{ + int sz = sqlite3Utf8ReadLimited((u8*)&z[nSkip], n-nSkip, piOut); + return nSkip + sz; + } + } + default: { + *piOut = JSON_INVALID_CHAR; + return 2; + } + } +} + + +/* +** Compare two object labels. Return 1 if they are equal and +** 0 if they differ. +** +** In this version, we know that one or the other or both of the +** two comparands contains an escape sequence. +*/ +static SQLITE_NOINLINE int jsonLabelCompareEscaped( + const char *zLeft, /* The left label */ + u32 nLeft, /* Size of the left label in bytes */ + int rawLeft, /* True if zLeft contains no escapes */ + const char *zRight, /* The right label */ + u32 nRight, /* Size of the right label in bytes */ + int rawRight /* True if zRight is escape-free */ +){ + u32 cLeft, cRight; + assert( rawLeft==0 || rawRight==0 ); + while( 1 /*exit-by-return*/ ){ + if( nLeft==0 ){ + cLeft = 0; + }else if( rawLeft || zLeft[0]!='\\' ){ + cLeft = ((u8*)zLeft)[0]; + if( cLeft>=0xc0 ){ + int sz = sqlite3Utf8ReadLimited((u8*)zLeft, nLeft, &cLeft); + zLeft += sz; + nLeft -= sz; + }else{ + zLeft++; + nLeft--; + } + }else{ + u32 n = jsonUnescapeOneChar(zLeft, nLeft, &cLeft); + zLeft += n; + assert( n<=nLeft ); + nLeft -= n; + } + if( nRight==0 ){ + cRight = 0; + }else if( rawRight || zRight[0]!='\\' ){ + cRight = ((u8*)zRight)[0]; + if( cRight>=0xc0 ){ + int sz = sqlite3Utf8ReadLimited((u8*)zRight, nRight, &cRight); + zRight += sz; + nRight -= sz; + }else{ + zRight++; + nRight--; + } + }else{ + u32 n = jsonUnescapeOneChar(zRight, nRight, &cRight); + zRight += n; + assert( n<=nRight ); + nRight -= n; + } + if( cLeft!=cRight ) return 0; + if( cLeft==0 ) return 1; } - p->nJson = nJson; - p->iHold = iMaxHold+1; - sqlite3_set_auxdata(pCtx, JSON_CACHE_ID+iMinKey, p, - (void(*)(void*))jsonParseFree); - return (JsonParse*)sqlite3_get_auxdata(pCtx, JSON_CACHE_ID+iMinKey); } /* -** Compare the OBJECT label at pNode against zKey,nKey. Return true on -** a match. +** Compare two object labels. Return 1 if they are equal and +** 0 if they differ. Return -1 if an OOM occurs. */ -static int jsonLabelCompare(JsonNode *pNode, const char *zKey, u32 nKey){ - if( pNode->jnFlags & JNODE_RAW ){ - if( pNode->n!=nKey ) return 0; - return strncmp(pNode->u.zJContent, zKey, nKey)==0; +static int jsonLabelCompare( + const char *zLeft, /* The left label */ + u32 nLeft, /* Size of the left label in bytes */ + int rawLeft, /* True if zLeft contains no escapes */ + const char *zRight, /* The right label */ + u32 nRight, /* Size of the right label in bytes */ + int rawRight /* True if zRight is escape-free */ +){ + if( rawLeft && rawRight ){ + /* Simpliest case: Neither label contains escapes. A simple + ** memcmp() is sufficient. */ + if( nLeft!=nRight ) return 0; + return memcmp(zLeft, zRight, nLeft)==0; }else{ - if( pNode->n!=nKey+2 ) return 0; - return strncmp(pNode->u.zJContent+1, zKey, nKey)==0; + return jsonLabelCompareEscaped(zLeft, nLeft, rawLeft, + zRight, nRight, rawRight); } } -/* forward declaration */ -static JsonNode *jsonLookupAppend(JsonParse*,const char*,int*,const char**); +/* +** Error returns from jsonLookupStep() +*/ +#define JSON_LOOKUP_ERROR 0xffffffff +#define JSON_LOOKUP_NOTFOUND 0xfffffffe +#define JSON_LOOKUP_NOTARRAY 0xfffffffd +#define JSON_LOOKUP_TOODEEP 0xfffffffc +#define JSON_LOOKUP_PATHERROR 0xfffffffb +#define JSON_LOOKUP_ISERROR(x) ((x)>=JSON_LOOKUP_PATHERROR) + +/* Forward declaration */ +static u32 jsonLookupStep(JsonParse*,u32,const char*,u32); + + +/* This helper routine for jsonLookupStep() populates pIns with +** binary data that is to be inserted into pParse. +** +** In the common case, pIns just points to pParse->aIns and pParse->nIns. +** But if the zPath of the original edit operation includes path elements +** that go deeper, additional substructure must be created. +** +** For example: +** +** json_insert('{}', '$.a.b.c', 123); +** +** The search stops at '$.a' But additional substructure must be +** created for the ".b.c" part of the patch so that the final result +** is: {"a":{"b":{"c"::123}}}. This routine populates pIns with +** the binary equivalent of {"b":{"c":123}} so that it can be inserted. +** +** The caller is responsible for resetting pIns when it has finished +** using the substructure. +*/ +static u32 jsonCreateEditSubstructure( + JsonParse *pParse, /* The original JSONB that is being edited */ + JsonParse *pIns, /* Populate this with the blob data to insert */ + const char *zTail /* Tail of the path that determines substructure */ +){ + static const u8 emptyObject[] = { JSONB_ARRAY, JSONB_OBJECT }; + int rc; + memset(pIns, 0, sizeof(*pIns)); + pIns->db = pParse->db; + if( zTail[0]==0 ){ + /* No substructure. Just insert what is given in pParse. */ + pIns->aBlob = pParse->aIns; + pIns->nBlob = pParse->nIns; + rc = 0; + }else{ + /* Construct the binary substructure */ + pIns->nBlob = 1; + pIns->aBlob = (u8*)&emptyObject[zTail[0]=='.']; + pIns->eEdit = pParse->eEdit; + pIns->nIns = pParse->nIns; + pIns->aIns = pParse->aIns; + pIns->iDepth = pParse->iDepth+1; + if( pIns->iDepth >= JSON_MAX_DEPTH ){ + return JSON_LOOKUP_TOODEEP; + } + rc = jsonLookupStep(pIns, 0, zTail, 0); + pParse->iDepth--; + pParse->oom |= pIns->oom; + } + return rc; /* Error code only */ +} /* -** Search along zPath to find the node specified. Return a pointer -** to that node, or NULL if zPath is malformed or if there is no such -** node. +** Search along zPath to find the Json element specified. Return an +** index into pParse->aBlob[] for the start of that element's value. +** +** If the value found by this routine is the value half of label/value pair +** within an object, then set pPath->iLabel to the start of the corresponding +** label, before returning. +** +** Return one of the JSON_LOOKUP error codes if problems are seen. ** -** If pApnd!=0, then try to append new nodes to complete zPath if it is -** possible to do so and if no existing node corresponds to zPath. If -** new nodes are appended *pApnd is set to 1. +** This routine will also modify the blob. If pParse->eEdit is one of +** JEDIT_DEL, JEDIT_REPL, JEDIT_INS, JEDIT_SET, or JEDIT_AINS, then changes +** might be made to the selected value. If an edit is performed, then the +** return value does not necessarily point to the select element. If an edit +** is performed, the return value is only useful for detecting error +** conditions. */ -static JsonNode *jsonLookupStep( +static u32 jsonLookupStep( JsonParse *pParse, /* The JSON to search */ - u32 iRoot, /* Begin the search at this node */ + u32 iRoot, /* Begin the search at this element of aBlob[] */ const char *zPath, /* The path to search */ - int *pApnd, /* Append nodes to complete path if not NULL */ - const char **pzErr /* Make *pzErr point to any syntax error in zPath */ + u32 iLabel /* Label if iRoot is a value of in an object */ ){ - u32 i, j, nKey; + u32 i, j, k, nKey, sz, n, iEnd, rc; const char *zKey; - JsonNode *pRoot = &pParse->aNode[iRoot]; - if( zPath[0]==0 ) return pRoot; + u8 x; + + if( zPath[0]==0 ){ + if( pParse->eEdit && jsonBlobMakeEditable(pParse, pParse->nIns) ){ + n = jsonbPayloadSize(pParse, iRoot, &sz); + sz += n; + if( pParse->eEdit==JEDIT_DEL ){ + if( iLabel>0 ){ + sz += iRoot - iLabel; + iRoot = iLabel; + } + jsonBlobEdit(pParse, iRoot, sz, 0, 0); + }else if( pParse->eEdit==JEDIT_INS ){ + /* Already exists, so json_insert() is a no-op */ + }else if( pParse->eEdit==JEDIT_AINS ){ + /* json_array_insert() */ + if( zPath[-1]!=']' ){ + return JSON_LOOKUP_NOTARRAY; + }else{ + jsonBlobEdit(pParse, iRoot, 0, pParse->aIns, pParse->nIns); + } + }else{ + /* json_set() or json_replace() */ + jsonBlobEdit(pParse, iRoot, sz, pParse->aIns, pParse->nIns); + } + } + pParse->iLabel = iLabel; + return iRoot; + } if( zPath[0]=='.' ){ - if( pRoot->eType!=JSON_OBJECT ) return 0; + int rawKey = 1; + x = pParse->aBlob[iRoot]; zPath++; if( zPath[0]=='"' ){ zKey = zPath + 1; - for(i=1; zPath[i] && zPath[i]!='"'; i++){} + for(i=1; zPath[i] && zPath[i]!='"'; i++){ + if( zPath[i]=='\\' && zPath[i+1]!=0 ) i++; + } nKey = i-1; if( zPath[i] ){ i++; }else{ - *pzErr = zPath; - return 0; + return JSON_LOOKUP_PATHERROR; } + testcase( nKey==0 ); + rawKey = memchr(zKey, '\\', nKey)==0; }else{ zKey = zPath; for(i=0; zPath[i] && zPath[i]!='.' && zPath[i]!='['; i++){} nKey = i; - } - if( nKey==0 ){ - *pzErr = zPath; - return 0; - } - j = 1; - for(;;){ - while( j<=pRoot->n ){ - if( jsonLabelCompare(pRoot+j, zKey, nKey) ){ - return jsonLookupStep(pParse, iRoot+j+1, &zPath[i], pApnd, pzErr); + if( nKey==0 ){ + return JSON_LOOKUP_PATHERROR; + } + } + if( (x & 0x0f)!=JSONB_OBJECT ) return JSON_LOOKUP_NOTFOUND; + n = jsonbPayloadSize(pParse, iRoot, &sz); + j = iRoot + n; /* j is the index of a label */ + iEnd = j+sz; + while( jaBlob[j] & 0x0f; + if( xJSONB_TEXTRAW ) return JSON_LOOKUP_ERROR; + n = jsonbPayloadSize(pParse, j, &sz); + if( n==0 ) return JSON_LOOKUP_ERROR; + k = j+n; /* k is the index of the label text */ + if( k+sz>=iEnd ) return JSON_LOOKUP_ERROR; + zLabel = (const char*)&pParse->aBlob[k]; + rawLabel = x==JSONB_TEXT || x==JSONB_TEXTRAW; + if( jsonLabelCompare(zKey, nKey, rawKey, zLabel, sz, rawLabel) ){ + u32 v = k+sz; /* v is the index of the value */ + if( ((pParse->aBlob[v])&0x0f)>JSONB_OBJECT ) return JSON_LOOKUP_ERROR; + n = jsonbPayloadSize(pParse, v, &sz); + if( n==0 || v+n+sz>iEnd ) return JSON_LOOKUP_ERROR; + assert( j>0 ); + if( ++pParse->iDepth >= JSON_MAX_DEPTH ){ + return JSON_LOOKUP_TOODEEP; } - j++; - j += jsonNodeSize(&pRoot[j]); + rc = jsonLookupStep(pParse, v, &zPath[i], j); + pParse->iDepth--; + if( pParse->delta ) jsonAfterEditSizeAdjust(pParse, iRoot); + return rc; } - if( (pRoot->jnFlags & JNODE_APPEND)==0 ) break; - iRoot += pRoot->u.iAppend; - pRoot = &pParse->aNode[iRoot]; - j = 1; - } - if( pApnd ){ - u32 iStart, iLabel; - JsonNode *pNode; - iStart = jsonParseAddNode(pParse, JSON_OBJECT, 2, 0); - iLabel = jsonParseAddNode(pParse, JSON_STRING, i, zPath); - zPath += i; - pNode = jsonLookupAppend(pParse, zPath, pApnd, pzErr); - if( pParse->oom ) return 0; - if( pNode ){ - pRoot = &pParse->aNode[iRoot]; - pRoot->u.iAppend = iStart - iRoot; - pRoot->jnFlags |= JNODE_APPEND; - pParse->aNode[iLabel].jnFlags |= JNODE_RAW; - } - return pNode; - } - }else if( zPath[0]=='[' && safe_isdigit(zPath[1]) ){ - if( pRoot->eType!=JSON_ARRAY ) return 0; - i = 0; - j = 1; - while( safe_isdigit(zPath[j]) ){ - i = i*10 + zPath[j] - '0'; - j++; + j = k+sz; + if( ((pParse->aBlob[j])&0x0f)>JSONB_OBJECT ) return JSON_LOOKUP_ERROR; + n = jsonbPayloadSize(pParse, j, &sz); + if( n==0 ) return JSON_LOOKUP_ERROR; + j += n+sz; + } + if( j>iEnd ) return JSON_LOOKUP_ERROR; + if( pParse->eEdit>=JEDIT_INS ){ + u32 nIns; /* Total bytes to insert (label+value) */ + JsonParse v; /* BLOB encoding of the value to be inserted */ + JsonParse ix; /* Header of the label to be inserted */ + testcase( pParse->eEdit==JEDIT_INS ); + testcase( pParse->eEdit==JEDIT_SET ); + testcase( pParse->eEdit==JEDIT_AINS ); + if( pParse->eEdit==JEDIT_AINS && sqlite3_strglob("*]",&zPath[i])!=0 ){ + return JSON_LOOKUP_NOTARRAY; + } + memset(&ix, 0, sizeof(ix)); + ix.db = pParse->db; + jsonBlobAppendNode(&ix, rawKey?JSONB_TEXTRAW:JSONB_TEXT5, nKey, 0); + pParse->oom |= ix.oom; + rc = jsonCreateEditSubstructure(pParse, &v, &zPath[i]); + if( !JSON_LOOKUP_ISERROR(rc) + && jsonBlobMakeEditable(pParse, ix.nBlob+nKey+v.nBlob) + ){ + assert( !pParse->oom ); + nIns = ix.nBlob + nKey + v.nBlob; + jsonBlobEdit(pParse, j, 0, 0, nIns); + if( !pParse->oom ){ + assert( pParse->aBlob!=0 ); /* Because pParse->oom!=0 */ + assert( ix.aBlob!=0 ); /* Because pPasre->oom!=0 */ + memcpy(&pParse->aBlob[j], ix.aBlob, ix.nBlob); + k = j + ix.nBlob; + memcpy(&pParse->aBlob[k], zKey, nKey); + k += nKey; + memcpy(&pParse->aBlob[k], v.aBlob, v.nBlob); + if( ALWAYS(pParse->delta) ) jsonAfterEditSizeAdjust(pParse, iRoot); + } + } + jsonParseReset(&v); + jsonParseReset(&ix); + return rc; } - if( zPath[j]!=']' ){ - *pzErr = zPath; - return 0; + }else if( zPath[0]=='[' ){ + u64 kk = 0; + x = pParse->aBlob[iRoot] & 0x0f; + if( x!=JSONB_ARRAY ) return JSON_LOOKUP_NOTFOUND; + n = jsonbPayloadSize(pParse, iRoot, &sz); + i = 1; + while( sqlite3Isdigit(zPath[i]) ){ + if( kk<0xffffffff ) kk = kk*10 + zPath[i] - '0'; + /* ^^^^^^^^^^--- Allow kk to be bigger than any JSON array so that + ** we get NOTFOUND instead of PATHERROR, without overflowing kk. */ + i++; } - zPath += j + 1; - j = 1; - for(;;){ - while( j<=pRoot->n && (i>0 || (pRoot[j].jnFlags & JNODE_REMOVE)!=0) ){ - if( (pRoot[j].jnFlags & JNODE_REMOVE)==0 ) i--; - j += jsonNodeSize(&pRoot[j]); + if( i<2 || zPath[i]!=']' ){ + if( zPath[1]=='#' ){ + kk = jsonbArrayCount(pParse, iRoot); + i = 2; + if( zPath[2]=='-' && sqlite3Isdigit(zPath[3]) ){ + u64 nn = 0; + i = 3; + do{ + if( nn<0xffffffff ) nn = nn*10 + zPath[i] - '0'; + /* ^^^^^^^^^^--- Allow nn to be bigger than any JSON array to + ** get NOTFOUND instead of PATHERROR, without overflowing nn. */ + i++; + }while( sqlite3Isdigit(zPath[i]) ); + if( nn>kk ) return JSON_LOOKUP_NOTFOUND; + kk -= nn; + } + if( zPath[i]!=']' ){ + return JSON_LOOKUP_PATHERROR; + } + }else{ + return JSON_LOOKUP_PATHERROR; } - if( (pRoot->jnFlags & JNODE_APPEND)==0 ) break; - iRoot += pRoot->u.iAppend; - pRoot = &pParse->aNode[iRoot]; - j = 1; - } - if( j<=pRoot->n ){ - return jsonLookupStep(pParse, iRoot+j, zPath, pApnd, pzErr); } - if( i==0 && pApnd ){ - u32 iStart; - JsonNode *pNode; - iStart = jsonParseAddNode(pParse, JSON_ARRAY, 1, 0); - pNode = jsonLookupAppend(pParse, zPath, pApnd, pzErr); - if( pParse->oom ) return 0; - if( pNode ){ - pRoot = &pParse->aNode[iRoot]; - pRoot->u.iAppend = iStart - iRoot; - pRoot->jnFlags |= JNODE_APPEND; + j = iRoot+n; + iEnd = j+sz; + while( jiDepth >= JSON_MAX_DEPTH ){ + return JSON_LOOKUP_TOODEEP; + } + rc = jsonLookupStep(pParse, j, &zPath[i+1], 0); + pParse->iDepth--; + if( pParse->delta ) jsonAfterEditSizeAdjust(pParse, iRoot); + return rc; } - return pNode; + kk--; + n = jsonbPayloadSize(pParse, j, &sz); + if( n==0 ) return JSON_LOOKUP_ERROR; + j += n+sz; + } + if( j>iEnd ) return JSON_LOOKUP_ERROR; + if( kk>0 ) return JSON_LOOKUP_NOTFOUND; + if( pParse->eEdit>=JEDIT_INS ){ + JsonParse v; + testcase( pParse->eEdit==JEDIT_INS ); + testcase( pParse->eEdit==JEDIT_AINS ); + testcase( pParse->eEdit==JEDIT_SET ); + rc = jsonCreateEditSubstructure(pParse, &v, &zPath[i+1]); + if( !JSON_LOOKUP_ISERROR(rc) + && jsonBlobMakeEditable(pParse, v.nBlob) + ){ + assert( !pParse->oom ); + jsonBlobEdit(pParse, j, 0, v.aBlob, v.nBlob); + } + jsonParseReset(&v); + if( pParse->delta ) jsonAfterEditSizeAdjust(pParse, iRoot); + return rc; } }else{ - *pzErr = zPath; + return JSON_LOOKUP_PATHERROR; } - return 0; + return JSON_LOOKUP_NOTFOUND; } /* -** Append content to pParse that will complete zPath. Return a pointer -** to the inserted node, or return NULL if the append fails. +** Convert a JSON BLOB into text and make that text the return value +** of an SQL function. */ -static JsonNode *jsonLookupAppend( - JsonParse *pParse, /* Append content to the JSON parse */ - const char *zPath, /* Description of content to append */ - int *pApnd, /* Set this flag to 1 */ - const char **pzErr /* Make this point to any syntax error */ +static void jsonReturnTextJsonFromBlob( + sqlite3_context *ctx, + const u8 *aBlob, + u32 nBlob ){ - *pApnd = 1; - if( zPath[0]==0 ){ - jsonParseAddNode(pParse, JSON_NULL, 0, 0); - return pParse->oom ? 0 : &pParse->aNode[pParse->nNode-1]; + JsonParse x; + JsonString s; + + if( NEVER(aBlob==0) ) return; + memset(&x, 0, sizeof(x)); + x.aBlob = (u8*)aBlob; + x.nBlob = nBlob; + jsonStringInit(&s, ctx); + jsonTranslateBlobToText(&x, 0, &s); + jsonReturnString(&s, 0, 0); +} + + +/* +** Return the value of the BLOB node at index i. +** +** If the value is a primitive, return it as an SQL value. +** If the value is an array or object, return it as either +** JSON text or the BLOB encoding, depending on the eMode flag +** as follows: +** +** eMode==0 JSONB if the JSON_B flag is set in userdata or +** text if the JSON_B flag is omitted from userdata. +** +** eMode==1 Text +** +** eMode==2 JSONB +*/ +static void jsonReturnFromBlob( + JsonParse *pParse, /* Complete JSON parse tree */ + u32 i, /* Index of the node */ + sqlite3_context *pCtx, /* Return value for this function */ + int eMode /* Format of return: text of JSONB */ +){ + u32 n, sz; + int rc; + sqlite3 *db = sqlite3_context_db_handle(pCtx); + + assert( eMode>=0 && eMode<=2 ); + n = jsonbPayloadSize(pParse, i, &sz); + if( n==0 ){ + sqlite3_result_error(pCtx, "malformed JSON", -1); + return; } - if( zPath[0]=='.' ){ - jsonParseAddNode(pParse, JSON_OBJECT, 0, 0); - }else if( strncmp(zPath,"[0]",3)==0 ){ - jsonParseAddNode(pParse, JSON_ARRAY, 0, 0); - }else{ - return 0; + switch( pParse->aBlob[i] & 0x0f ){ + case JSONB_NULL: { + if( sz ) goto returnfromblob_malformed; + sqlite3_result_null(pCtx); + break; + } + case JSONB_TRUE: { + if( sz ) goto returnfromblob_malformed; + sqlite3_result_int(pCtx, 1); + break; + } + case JSONB_FALSE: { + if( sz ) goto returnfromblob_malformed; + sqlite3_result_int(pCtx, 0); + break; + } + case JSONB_INT5: + case JSONB_INT: { + sqlite3_int64 iRes = 0; + char *z; + int bNeg = 0; + char x; + if( sz==0 ) goto returnfromblob_malformed; + x = (char)pParse->aBlob[i+n]; + if( x=='-' ){ + if( sz<2 ) goto returnfromblob_malformed; + n++; + sz--; + bNeg = 1; + } + z = sqlite3DbStrNDup(db, (const char*)&pParse->aBlob[i+n], (int)sz); + if( z==0 ) goto returnfromblob_oom; + rc = sqlite3DecOrHexToI64(z, &iRes); + sqlite3DbFree(db, z); + if( rc==0 ){ + if( iRes<0 ){ + /* A hexadecimal literal with 16 significant digits and with the + ** high-order bit set is a negative integer in SQLite (and hence + ** iRes comes back as negative) but should be interpreted as a + ** positive value if it occurs within JSON. The value is too + ** large to appear as an SQLite integer so it must be converted + ** into floating point. */ + double r; + r = (double)*(sqlite3_uint64*)&iRes; + sqlite3_result_double(pCtx, bNeg ? -r : r); + }else{ + sqlite3_result_int64(pCtx, bNeg ? -iRes : iRes); + } + }else if( rc==3 && bNeg ){ + sqlite3_result_int64(pCtx, SMALLEST_INT64); + }else if( rc==1 ){ + goto returnfromblob_malformed; + }else{ + if( bNeg ){ n--; sz++; } + goto to_double; + } + break; + } + case JSONB_FLOAT5: + case JSONB_FLOAT: { + double r; + char *z; + if( sz==0 ) goto returnfromblob_malformed; + to_double: + z = sqlite3DbStrNDup(db, (const char*)&pParse->aBlob[i+n], (int)sz); + if( z==0 ) goto returnfromblob_oom; + rc = sqlite3AtoF(z, &r); + sqlite3DbFree(db, z); + if( rc<=0 ) goto returnfromblob_malformed; + sqlite3_result_double(pCtx, r); + break; + } + case JSONB_TEXTRAW: + case JSONB_TEXT: { + sqlite3_result_text(pCtx, (char*)&pParse->aBlob[i+n], sz, + SQLITE_TRANSIENT); + break; + } + case JSONB_TEXT5: + case JSONB_TEXTJ: { + /* Translate JSON formatted string into raw text */ + u32 iIn, iOut; + const char *z; + char *zOut; + u32 nOut = sz; + z = (const char*)&pParse->aBlob[i+n]; + zOut = sqlite3DbMallocRaw(db, ((u64)nOut)+1); + if( zOut==0 ) goto returnfromblob_oom; + for(iIn=iOut=0; iIn=2 ); + zOut[iOut++] = (char)(0xc0 | (v>>6)); + zOut[iOut++] = 0x80 | (v&0x3f); + }else if( v<0x10000 ){ + assert( szEscape>=3 ); + zOut[iOut++] = 0xe0 | (v>>12); + zOut[iOut++] = 0x80 | ((v>>6)&0x3f); + zOut[iOut++] = 0x80 | (v&0x3f); + }else if( v==JSON_INVALID_CHAR ){ + /* Silently ignore illegal unicode */ + }else{ + assert( szEscape>=4 ); + zOut[iOut++] = 0xf0 | (v>>18); + zOut[iOut++] = 0x80 | ((v>>12)&0x3f); + zOut[iOut++] = 0x80 | ((v>>6)&0x3f); + zOut[iOut++] = 0x80 | (v&0x3f); + } + iIn += szEscape - 1; + }else{ + zOut[iOut++] = c; + } + } /* end for() */ + assert( iOut<=nOut ); + zOut[iOut] = 0; + sqlite3_result_text(pCtx, zOut, iOut, SQLITE_DYNAMIC); + break; + } + case JSONB_ARRAY: + case JSONB_OBJECT: { + if( eMode==0 ){ + if( (SQLITE_PTR_TO_INT(sqlite3_user_data(pCtx)) & JSON_BLOB)!=0 ){ + eMode = 2; + }else{ + eMode = 1; + } + } + if( eMode==2 ){ + sqlite3_result_blob(pCtx, &pParse->aBlob[i], sz+n, SQLITE_TRANSIENT); + }else{ + jsonReturnTextJsonFromBlob(pCtx, &pParse->aBlob[i], sz+n); + } + break; + } + default: { + goto returnfromblob_malformed; + } } - if( pParse->oom ) return 0; - return jsonLookupStep(pParse, pParse->nNode-1, zPath, pApnd, pzErr); + return; + +returnfromblob_oom: + sqlite3_result_error_nomem(pCtx); + return; + +returnfromblob_malformed: + sqlite3_result_error(pCtx, "malformed JSON", -1); + return; } /* -** Return the text of a syntax error message on a JSON path. Space is -** obtained from sqlite3_malloc(). +** pArg is a function argument that might be an SQL value or a JSON +** value. Figure out what it is and encode it as a JSONB blob. +** Return the results in pParse. +** +** pParse is uninitialized upon entry. This routine will handle the +** initialization of pParse. The result will be contained in +** pParse->aBlob and pParse->nBlob. pParse->aBlob might be dynamically +** allocated (if pParse->nBlobAlloc is greater than zero) in which case +** the caller is responsible for freeing the space allocated to pParse->aBlob +** when it has finished with it. Or pParse->aBlob might be a static string +** or a value obtained from sqlite3_value_blob(pArg). +** +** If the argument is a BLOB that is clearly not a JSONB, then this +** function might set an error message in ctx and return non-zero. +** It might also set an error message and return non-zero on an OOM error. */ -static char *jsonPathSyntaxError(const char *zErr){ - return sqlite3_mprintf("JSON path error near '%q'", zErr); +static int jsonFunctionArgToBlob( + sqlite3_context *ctx, + sqlite3_value *pArg, + JsonParse *pParse +){ + int eType = sqlite3_value_type(pArg); + static u8 aNull[] = { 0x00 }; + memset(pParse, 0, sizeof(pParse[0])); + pParse->db = sqlite3_context_db_handle(ctx); + switch( eType ){ + default: { + pParse->aBlob = aNull; + pParse->nBlob = 1; + return 0; + } + case SQLITE_BLOB: { + if( !jsonArgIsJsonb(pArg, pParse) ){ + sqlite3_result_error(ctx, "JSON cannot hold BLOB values", -1); + return 1; + } + break; + } + case SQLITE_TEXT: { + const char *zJson = (const char*)sqlite3_value_text(pArg); + int nJson = sqlite3_value_bytes(pArg); + if( zJson==0 ) return 1; + if( sqlite3_value_subtype(pArg)==JSON_SUBTYPE ){ + pParse->zJson = (char*)zJson; + pParse->nJson = nJson; + if( jsonConvertTextToBlob(pParse, ctx) ){ + sqlite3_result_error(ctx, "malformed JSON", -1); + sqlite3DbFree(pParse->db, pParse->aBlob); + memset(pParse, 0, sizeof(pParse[0])); + return 1; + } + }else{ + jsonBlobAppendNode(pParse, JSONB_TEXTRAW, nJson, zJson); + } + break; + } + case SQLITE_FLOAT: { + double r = sqlite3_value_double(pArg); + if( NEVER(sqlite3IsNaN(r)) ){ + jsonBlobAppendNode(pParse, JSONB_NULL, 0, 0); + }else{ + int n = sqlite3_value_bytes(pArg); + const char *z = (const char*)sqlite3_value_text(pArg); + if( z==0 ) return 1; + if( z[0]=='I' ){ + jsonBlobAppendNode(pParse, JSONB_FLOAT, 5, "9e999"); + }else if( z[0]=='-' && z[1]=='I' ){ + jsonBlobAppendNode(pParse, JSONB_FLOAT, 6, "-9e999"); + }else{ + jsonBlobAppendNode(pParse, JSONB_FLOAT, n, z); + } + } + break; + } + case SQLITE_INTEGER: { + int n = sqlite3_value_bytes(pArg); + const char *z = (const char*)sqlite3_value_text(pArg); + if( z==0 ) return 1; + jsonBlobAppendNode(pParse, JSONB_INT, n, z); + break; + } + } + if( pParse->oom ){ + sqlite3_result_error_nomem(ctx); + return 1; + }else{ + return 0; + } } /* -** Do a node lookup using zPath. Return a pointer to the node on success. -** Return NULL if not found or if there is an error. +** Generate a path error. +** +** The specifics of the error are determined by the rc argument. ** -** On an error, write an error message into pCtx and increment the -** pParse->nErr counter. +** rc error +** ----------------- ---------------------- +** JSON_LOOKUP_ARRAY "not an array" +** JSON_LOOKUP_TOODEEP "JSON nested too deep" +** JSON_LOOKUP_ERROR "malformed JSON" +** otherwise... "bad JSON path" ** -** If pApnd!=NULL then try to append missing nodes and set *pApnd = 1 if -** nodes are appended. +** If ctx is not NULL then push the error message into ctx and return NULL. +** If ctx is NULL, then return the text of the error message. */ -static JsonNode *jsonLookup( - JsonParse *pParse, /* The JSON to search */ - const char *zPath, /* The path to search */ - int *pApnd, /* Append nodes to complete path if not NULL */ - sqlite3_context *pCtx /* Report errors here, if not NULL */ +static char *jsonBadPathError( + sqlite3_context *ctx, /* The function call containing the error */ + const char *zPath, /* The path with the problem */ + int rc /* Maybe JSON_LOOKUP_NOTARRAY */ ){ - const char *zErr = 0; - JsonNode *pNode = 0; char *zMsg; - - if( zPath==0 ) return 0; - if( zPath[0]!='$' ){ - zErr = zPath; - goto lookup_err; + if( rc==(int)JSON_LOOKUP_NOTARRAY ){ + zMsg = sqlite3_mprintf("not an array element: %Q", zPath); + }else if( rc==(int)JSON_LOOKUP_ERROR ){ + zMsg = sqlite3_mprintf("malformed JSON"); + }else if( rc==(int)JSON_LOOKUP_TOODEEP ){ + zMsg = sqlite3_mprintf("JSON path too deep"); + }else{ + zMsg = sqlite3_mprintf("bad JSON path: %Q", zPath); } - zPath++; - pNode = jsonLookupStep(pParse, 0, zPath, pApnd, &zErr); - if( zErr==0 ) return pNode; - -lookup_err: - pParse->nErr++; - assert( zErr!=0 && pCtx!=0 ); - zMsg = jsonPathSyntaxError(zErr); + if( ctx==0 ) return zMsg; if( zMsg ){ - sqlite3_result_error(pCtx, zMsg, -1); + sqlite3_result_error(ctx, zMsg, -1); sqlite3_free(zMsg); }else{ - sqlite3_result_error_nomem(pCtx); + sqlite3_result_error_nomem(ctx); } return 0; } +/* argv[0] is a BLOB that seems likely to be a JSONB. Subsequent +** arguments come in pairs where each pair contains a JSON path and +** content to insert or set at that patch. Do the updates +** and return the result. +** +** The specific operation is determined by eEdit, which can be one +** of JEDIT_INS, JEDIT_REPL, JEDIT_SET, or JEDIT_AINS. +*/ +static void jsonInsertIntoBlob( + sqlite3_context *ctx, + int argc, + sqlite3_value **argv, + int eEdit /* JEDIT_INS, JEDIT_REPL, JEDIT_SET, JEDIT_AINS */ +){ + int i; + u32 rc = 0; + const char *zPath = 0; + int flgs; + JsonParse *p; + JsonParse ax; + + assert( (argc&1)==1 ); + flgs = argc==1 ? 0 : JSON_EDITABLE; + p = jsonParseFuncArg(ctx, argv[0], flgs); + if( p==0 ) return; + for(i=1; inBlob, ax.aBlob, ax.nBlob); + } + rc = 0; + }else{ + p->eEdit = eEdit; + p->nIns = ax.nBlob; + p->aIns = ax.aBlob; + p->delta = 0; + p->iDepth = 0; + rc = jsonLookupStep(p, 0, zPath+1, 0); + } + jsonParseReset(&ax); + if( rc==JSON_LOOKUP_NOTFOUND ) continue; + if( JSON_LOOKUP_ISERROR(rc) ) goto jsonInsertIntoBlob_patherror; + } + jsonReturnParse(ctx, p); + jsonParseFree(p); + return; + +jsonInsertIntoBlob_patherror: + jsonParseFree(p); + jsonBadPathError(ctx, zPath, rc); + return; +} /* -** Report the wrong number of arguments for json_insert(), json_replace() -** or json_set(). +** If pArg is a blob that seems like a JSONB blob, then initialize +** p to point to that JSONB and return TRUE. If pArg does not seem like +** a JSONB blob, then return FALSE. +** +** For small BLOBs (having no more than 7 bytes of payload) a full +** validity check is done. So for small BLOBs this routine only returns +** true if the value is guaranteed to be a valid JSONB. For larger BLOBs +** (8 byte or more of payload) only the size of the outermost element is +** checked to verify that the BLOB is superficially valid JSONB. +** +** A full JSONB validation is done on smaller BLOBs because those BLOBs might +** also be text JSON that has been incorrectly cast into a BLOB. +** (See tag-20240123-a and https://sqlite.org/forum/forumpost/012136abd5) +** If the BLOB is 9 bytes are larger, then it is not possible for the +** superficial size check done here to pass if the input is really text +** JSON so we do not need to look deeper in that case. +** +** Why we only need to do full JSONB validation for smaller BLOBs: +** +** The first byte of valid JSON text must be one of: '{', '[', '"', ' ', '\n', +** '\r', '\t', '-', or a digit '0' through '9'. Of these, only a subset +** can also be the first byte of JSONB: '{', '[', and digits '3' +** through '9'. In every one of those cases, the payload size is 7 bytes +** or less. So if we do full JSONB validation for every BLOB where the +** payload is less than 7 bytes, we will never get a false positive for +** JSONB on an input that is really text JSON. +*/ +static int jsonArgIsJsonb(sqlite3_value *pArg, JsonParse *p){ + u32 n, sz = 0; + u8 c; + if( sqlite3_value_type(pArg)!=SQLITE_BLOB ) return 0; + p->aBlob = (u8*)sqlite3_value_blob(pArg); + p->nBlob = (u32)sqlite3_value_bytes(pArg); + if( p->nBlob>0 + && ALWAYS(p->aBlob!=0) + && ((c = p->aBlob[0]) & 0x0f)<=JSONB_OBJECT + && (n = jsonbPayloadSize(p, 0, &sz))>0 + && sz+n==p->nBlob + && ((c & 0x0f)>JSONB_FALSE || sz==0) + && (sz>7 + || (c!=0x7b && c!=0x5b && !sqlite3Isdigit(c)) + || jsonbValidityCheck(p, 0, p->nBlob, 1)==0) + ){ + return 1; + } + p->aBlob = 0; + p->nBlob = 0; + return 0; +} + +/* +** Generate a JsonParse object, containing valid JSONB in aBlob and nBlob, +** from the SQL function argument pArg. Return a pointer to the new +** JsonParse object. +** +** Ownership of the new JsonParse object is passed to the caller. The +** caller should invoke jsonParseFree() on the return value when it +** has finished using it. +** +** If any errors are detected, an appropriate error messages is set +** using sqlite3_result_error() or the equivalent and this routine +** returns NULL. This routine also returns NULL if the pArg argument +** is an SQL NULL value, but no error message is set in that case. This +** is so that SQL functions that are given NULL arguments will return +** a NULL value. */ -static void jsonWrongNumArgs( - sqlite3_context *pCtx, - const char *zFuncName +static JsonParse *jsonParseFuncArg( + sqlite3_context *ctx, + sqlite3_value *pArg, + u32 flgs ){ - char *zMsg = sqlite3_mprintf("json_%s() needs an odd number of arguments", - zFuncName); - sqlite3_result_error(pCtx, zMsg, -1); - sqlite3_free(zMsg); + int eType; /* Datatype of pArg */ + JsonParse *p = 0; /* Value to be returned */ + JsonParse *pFromCache = 0; /* Value taken from cache */ + sqlite3 *db; /* The database connection */ + + assert( ctx!=0 ); + eType = sqlite3_value_type(pArg); + if( eType==SQLITE_NULL ){ + return 0; + } + pFromCache = jsonCacheSearch(ctx, pArg); + if( pFromCache ){ + pFromCache->nJPRef++; + if( (flgs & JSON_EDITABLE)==0 ){ + return pFromCache; + } + } + db = sqlite3_context_db_handle(ctx); +rebuild_from_cache: + p = sqlite3DbMallocZero(db, sizeof(*p)); + if( p==0 ) goto json_pfa_oom; + memset(p, 0, sizeof(*p)); + p->db = db; + p->nJPRef = 1; + if( pFromCache!=0 ){ + u32 nBlob = pFromCache->nBlob; + p->aBlob = sqlite3DbMallocRaw(db, nBlob); + if( p->aBlob==0 ) goto json_pfa_oom; + memcpy(p->aBlob, pFromCache->aBlob, nBlob); + p->nBlobAlloc = p->nBlob = nBlob; + p->hasNonstd = pFromCache->hasNonstd; + jsonParseFree(pFromCache); + return p; + } + if( eType==SQLITE_BLOB ){ + if( jsonArgIsJsonb(pArg,p) ){ + if( (flgs & JSON_EDITABLE)!=0 && jsonBlobMakeEditable(p, 0)==0 ){ + goto json_pfa_oom; + } + return p; + } + /* If the blob is not valid JSONB, fall through into trying to cast + ** the blob into text which is then interpreted as JSON. (tag-20240123-a) + ** + ** This goes against all historical documentation about how the SQLite + ** JSON functions were suppose to work. From the beginning, blob was + ** reserved for expansion and a blob value should have raised an error. + ** But it did not, due to a bug. And many applications came to depend + ** upon this buggy behavior, especially when using the CLI and reading + ** JSON text using readfile(), which returns a blob. For this reason + ** we will continue to support the bug moving forward. + ** See for example https://sqlite.org/forum/forumpost/012136abd5292b8d + */ + } + p->zJson = (char*)sqlite3_value_text(pArg); + p->nJson = sqlite3_value_bytes(pArg); + if( db->mallocFailed ) goto json_pfa_oom; + if( p->nJson==0 ) goto json_pfa_malformed; + assert( p->zJson!=0 ); + if( jsonConvertTextToBlob(p, (flgs & JSON_KEEPERROR) ? 0 : ctx) ){ + if( flgs & JSON_KEEPERROR ){ + p->nErr = 1; + return p; + }else{ + jsonParseFree(p); + return 0; + } + }else{ + int isRCStr = sqlite3ValueIsOfClass(pArg, sqlite3RCStrUnref); + int rc; + if( !isRCStr ){ + char *zNew = sqlite3RCStrNew( p->nJson ); + if( zNew==0 ) goto json_pfa_oom; + memcpy(zNew, p->zJson, p->nJson); + p->zJson = zNew; + p->zJson[p->nJson] = 0; + }else{ + sqlite3RCStrRef(p->zJson); + } + p->bJsonIsRCStr = 1; + rc = jsonCacheInsert(ctx, p); + if( rc==SQLITE_NOMEM ) goto json_pfa_oom; + if( flgs & JSON_EDITABLE ){ + pFromCache = p; + p = 0; + goto rebuild_from_cache; + } + } + return p; + +json_pfa_malformed: + if( flgs & JSON_KEEPERROR ){ + p->nErr = 1; + return p; + }else{ + jsonParseFree(p); + sqlite3_result_error(ctx, "malformed JSON", -1); + return 0; + } + +json_pfa_oom: + jsonParseFree(pFromCache); + jsonParseFree(p); + sqlite3_result_error_nomem(ctx); + return 0; } /* -** Mark all NULL entries in the Object passed in as JNODE_REMOVE. +** Make the return value of a JSON function either the raw JSONB blob +** or make it JSON text, depending on whether the JSON_BLOB flag is +** set on the function. */ -static void jsonRemoveAllNulls(JsonNode *pNode){ - int i, n; - assert( pNode->eType==JSON_OBJECT ); - n = pNode->n; - for(i=2; i<=n; i += jsonNodeSize(&pNode[i])+1){ - switch( pNode[i].eType ){ - case JSON_NULL: - pNode[i].jnFlags |= JNODE_REMOVE; - break; - case JSON_OBJECT: - jsonRemoveAllNulls(&pNode[i]); - break; +static void jsonReturnParse( + sqlite3_context *ctx, + JsonParse *p +){ + int flgs; + if( p->oom ){ + sqlite3_result_error_nomem(ctx); + return; + } + flgs = SQLITE_PTR_TO_INT(sqlite3_user_data(ctx)); + if( flgs & JSON_BLOB ){ + if( p->nBlobAlloc>0 && !p->bReadOnly ){ + sqlite3_result_blob(ctx, p->aBlob, p->nBlob, SQLITE_DYNAMIC); + p->nBlobAlloc = 0; + }else{ + sqlite3_result_blob(ctx, p->aBlob, p->nBlob, SQLITE_TRANSIENT); } + }else{ + JsonString s; + jsonStringInit(&s, ctx); + p->delta = 0; + jsonTranslateBlobToText(p, 0, &s); + jsonReturnString(&s, p, ctx); + sqlite3_result_subtype(ctx, JSON_SUBTYPE); } } - /**************************************************************************** ** SQL functions used for testing and debugging ****************************************************************************/ -#ifdef SQLITE_DEBUG +#if SQLITE_DEBUG /* -** The json_parse(JSON) function returns a string which describes -** a parse of the JSON provided. Or it returns NULL if JSON is not -** well-formed. -*/ -static void jsonParseFunc( - sqlite3_context *ctx, - int argc, - sqlite3_value **argv -){ - JsonString s; /* Output string - not real JSON */ - JsonParse x; /* The parse */ - u32 i; - - assert( argc==1 ); - if( jsonParse(&x, ctx, (const char*)sqlite3_value_text(argv[0])) ) return; - jsonParseFindParents(&x); - jsonInit(&s, ctx); - for(i=0; iaBlob[iStart] & 0x0f; + u32 savedNBlob = pParse->nBlob; + sqlite3_str_appendf(pOut, "%5d:%*s", iStart, nIndent, ""); + if( pParse->nBlobAlloc>pParse->nBlob ){ + pParse->nBlob = pParse->nBlobAlloc; + } + nn = n = jsonbPayloadSize(pParse, iStart, &sz); + if( nn==0 ) nn = 1; + if( sz>0 && xaBlob[iStart+i]); } - jsonPrintf(100, &s,"node %3u: %7s n=%-4d up=%-4d", - i, zType, x.aNode[i].n, x.aUp[i]); - if( x.aNode[i].u.zJContent!=0 ){ - jsonAppendRaw(&s, " ", 1); - jsonAppendRaw(&s, x.aNode[i].u.zJContent, x.aNode[i].n); + if( n==0 ){ + sqlite3_str_appendf(pOut, " ERROR invalid node size\n"); + iStart = n==0 ? iStart+1 : iEnd; + continue; + } + pParse->nBlob = savedNBlob; + if( iStart+n+sz>iEnd ){ + iEnd = iStart+n+sz; + if( iEnd>pParse->nBlob ){ + if( pParse->nBlobAlloc>0 && iEnd>pParse->nBlobAlloc ){ + iEnd = pParse->nBlobAlloc; + }else{ + iEnd = pParse->nBlob; + } + } + } + sqlite3_str_appendall(pOut," <-- "); + switch( x ){ + case JSONB_NULL: sqlite3_str_appendall(pOut,"null"); break; + case JSONB_TRUE: sqlite3_str_appendall(pOut,"true"); break; + case JSONB_FALSE: sqlite3_str_appendall(pOut,"false"); break; + case JSONB_INT: sqlite3_str_appendall(pOut,"int"); break; + case JSONB_INT5: sqlite3_str_appendall(pOut,"int5"); break; + case JSONB_FLOAT: sqlite3_str_appendall(pOut,"float"); break; + case JSONB_FLOAT5: sqlite3_str_appendall(pOut,"float5"); break; + case JSONB_TEXT: sqlite3_str_appendall(pOut,"text"); break; + case JSONB_TEXTJ: sqlite3_str_appendall(pOut,"textj"); break; + case JSONB_TEXT5: sqlite3_str_appendall(pOut,"text5"); break; + case JSONB_TEXTRAW: sqlite3_str_appendall(pOut,"textraw"); break; + case JSONB_ARRAY: { + sqlite3_str_appendf(pOut,"array, %u bytes\n", sz); + jsonDebugPrintBlob(pParse, iStart+n, iStart+n+sz, nIndent+2, pOut); + showContent = 0; + break; + } + case JSONB_OBJECT: { + sqlite3_str_appendf(pOut, "object, %u bytes\n", sz); + jsonDebugPrintBlob(pParse, iStart+n, iStart+n+sz, nIndent+2, pOut); + showContent = 0; + break; + } + default: { + sqlite3_str_appendall(pOut, "ERROR: unknown node type\n"); + showContent = 0; + break; + } + } + if( showContent ){ + if( sz==0 && x<=JSONB_FALSE ){ + sqlite3_str_append(pOut, "\n", 1); + }else{ + u32 j; + sqlite3_str_appendall(pOut, ": \""); + for(j=iStart+n; jaBlob[j]; + if( c<0x20 || c>=0x7f ) c = '.'; + sqlite3_str_append(pOut, (char*)&c, 1); + } + sqlite3_str_append(pOut, "\"\n", 2); + } } - jsonAppendRaw(&s, "\n", 1); + iStart += n + sz; } - jsonParseReset(&x); - jsonResult(&s); } +static void jsonShowParse(JsonParse *pParse){ + sqlite3_str out; + char zBuf[1000]; + if( pParse==0 ){ + printf("NULL pointer\n"); + return; + }else{ + printf("nBlobAlloc = %u\n", pParse->nBlobAlloc); + printf("nBlob = %u\n", pParse->nBlob); + printf("delta = %d\n", pParse->delta); + if( pParse->nBlob==0 ) return; + printf("content (bytes 0..%u):\n", pParse->nBlob-1); + } + sqlite3StrAccumInit(&out, 0, zBuf, sizeof(zBuf), 1000000); + jsonDebugPrintBlob(pParse, 0, pParse->nBlob, 0, &out); + printf("%s", sqlite3_str_value(&out)); + sqlite3_str_reset(&out); +} +#endif /* SQLITE_DEBUG */ +#ifdef SQLITE_DEBUG /* -** The json_test1(JSON) function return true (1) if the input is JSON -** text generated by another json function. It returns (0) if the input -** is not known to be JSON. +** SQL function: json_parse(JSON) +** +** Parse JSON using jsonParseFuncArg(). Return text that is a +** human-readable dump of the binary JSONB for the input parameter. */ -static void jsonTest1Func( +static void jsonParseFunc( sqlite3_context *ctx, int argc, sqlite3_value **argv ){ - UNUSED_PARAM(argc); - sqlite3_result_int(ctx, sqlite3_value_subtype(argv[0])==JSON_SUBTYPE); + JsonParse *p; /* The parse */ + sqlite3_str out; + + assert( argc>=1 ); + sqlite3StrAccumInit(&out, 0, 0, 0, 1000000); + p = jsonParseFuncArg(ctx, argv[0], 0); + if( p==0 ) return; + if( argc==1 ){ + jsonDebugPrintBlob(p, 0, p->nBlob, 0, &out); + sqlite3_result_text64(ctx,out.zText,out.nChar,SQLITE_TRANSIENT,SQLITE_UTF8); + }else{ + jsonShowParse(p); + } + jsonParseFree(p); + sqlite3_str_reset(&out); } #endif /* SQLITE_DEBUG */ @@ -179964,8 +216692,8 @@ static void jsonTest1Func( ****************************************************************************/ /* -** Implementation of the json_QUOTE(VALUE) function. Return a JSON value -** corresponding to the SQL value input. Mostly this means putting +** Implementation of the json_quote(VALUE) function. Return a JSON value +** corresponding to the SQL value input. Mostly this means putting ** double-quotes around strings and returning the unquoted string "null" ** when given a NULL input. */ @@ -179975,11 +216703,11 @@ static void jsonQuoteFunc( sqlite3_value **argv ){ JsonString jx; - UNUSED_PARAM(argc); + UNUSED_PARAMETER(argc); - jsonInit(&jx, ctx); - jsonAppendValue(&jx, argv[0]); - jsonResult(&jx); + jsonStringInit(&jx, ctx); + jsonAppendSqlValue(&jx, argv[0]); + jsonReturnString(&jx, 0, 0); sqlite3_result_subtype(ctx, JSON_SUBTYPE); } @@ -179996,23 +216724,22 @@ static void jsonArrayFunc( int i; JsonString jx; - jsonInit(&jx, ctx); + jsonStringInit(&jx, ctx); jsonAppendChar(&jx, '['); for(i=0; inNode ); if( argc==2 ){ const char *zPath = (const char*)sqlite3_value_text(argv[1]); - pNode = jsonLookup(p, zPath, 0, ctx); + if( zPath==0 ){ + jsonParseFree(p); + return; + } + i = jsonLookupStep(p, 0, zPath[0]=='$' ? zPath+1 : "@", 0); + if( JSON_LOOKUP_ISERROR(i) ){ + if( i==JSON_LOOKUP_NOTFOUND ){ + /* no-op */ + }else{ + jsonBadPathError(ctx, zPath, i); + } + eErr = 1; + i = 0; + } }else{ - pNode = p->aNode; - } - if( pNode==0 ){ - return; + i = 0; } - if( pNode->eType==JSON_ARRAY ){ - assert( (pNode->jnFlags & JNODE_APPEND)==0 ); - for(i=1; i<=pNode->n; n++){ - i += jsonNodeSize(&pNode[i]); - } + if( (p->aBlob[i] & 0x0f)==JSONB_ARRAY ){ + cnt = jsonbArrayCount(p, i); } - sqlite3_result_int64(ctx, n); + if( !eErr ) sqlite3_result_int64(ctx, cnt); + jsonParseFree(p); +} + +/* True if the string is all alphanumerics and underscores */ +static int jsonAllAlphanum(const char *z, int n){ + int i; + for(i=0; i"(JSON,PATH) +** "->>"(JSON,PATH) +** +** Return the element described by PATH. Return NULL if that PATH element +** is not found. +** +** If JSON_JSON is set or if more that one PATH argument is supplied then +** always return a JSON representation of the result. If JSON_SQL is set, +** then always return an SQL representation of the result. If neither flag +** is present and argc==2, then return JSON for objects and arrays and SQL +** for all other values. ** -** Return the element described by PATH. Return NULL if there is no -** PATH element. If there are multiple PATHs, then return a JSON array -** with the result from each path. Throw an error if the JSON or any PATH -** is malformed. +** When multiple PATH arguments are supplied, the result is a JSON array +** containing the result of each PATH. +** +** Abbreviated JSON path expressions are allows if JSON_ABPATH, for +** compatibility with PG. */ static void jsonExtractFunc( sqlite3_context *ctx, int argc, sqlite3_value **argv ){ - JsonParse *p; /* The parse */ - JsonNode *pNode; - const char *zPath; - JsonString jx; - int i; + JsonParse *p = 0; /* The parse */ + int flags; /* Flags associated with the function */ + int i; /* Loop counter */ + JsonString jx; /* String for array result */ if( argc<2 ) return; - p = jsonParseCached(ctx, argv, ctx); + p = jsonParseFuncArg(ctx, argv[0], 0); if( p==0 ) return; - jsonInit(&jx, ctx); - jsonAppendChar(&jx, '['); + flags = SQLITE_PTR_TO_INT(sqlite3_user_data(ctx)); + jsonStringInit(&jx, ctx); + if( argc>2 ){ + jsonAppendChar(&jx, '['); + } for(i=1; inErr ) break; - if( argc>2 ){ - jsonAppendSeparator(&jx); - if( pNode ){ - jsonRenderNode(pNode, &jx, 0); + /* With a single PATH argument */ + const char *zPath = (const char*)sqlite3_value_text(argv[i]); + int nPath; + u32 j; + if( zPath==0 ) goto json_extract_error; + nPath = sqlite3Strlen30(zPath); + if( zPath[0]=='$' ){ + j = jsonLookupStep(p, 0, zPath+1, 0); + }else if( (flags & JSON_ABPATH) ){ + /* The -> and ->> operators accept abbreviated PATH arguments. This + ** is mostly for compatibility with PostgreSQL, but also for + ** convenience. + ** + ** NUMBER ==> $[NUMBER] // PG compatible + ** LABEL ==> $.LABEL // PG compatible + ** [NUMBER] ==> $[NUMBER] // Not PG. Purely for convenience + ** + ** Updated 2024-05-27: If the NUMBER is negative, then PG counts from + ** the right of the array. Hence for negative NUMBER: + ** + ** NUMBER ==> $[#NUMBER] // PG compatible + */ + jsonStringInit(&jx, ctx); + if( sqlite3_value_type(argv[i])==SQLITE_INTEGER ){ + jsonAppendRawNZ(&jx, "[", 1); + if( zPath[0]=='-' ) jsonAppendRawNZ(&jx,"#",1); + jsonAppendRaw(&jx, zPath, nPath); + jsonAppendRawNZ(&jx, "]", 2); + }else if( jsonAllAlphanum(zPath, nPath) ){ + jsonAppendRawNZ(&jx, ".", 1); + jsonAppendRaw(&jx, zPath, nPath); + }else if( zPath[0]=='[' && nPath>=3 && zPath[nPath-1]==']' ){ + jsonAppendRaw(&jx, zPath, nPath); + }else{ + jsonAppendRawNZ(&jx, ".\"", 2); + jsonAppendRaw(&jx, zPath, nPath); + jsonAppendRawNZ(&jx, "\"", 1); + } + jsonStringTerminate(&jx); + j = jsonLookupStep(p, 0, jx.zBuf, 0); + jsonStringReset(&jx); + }else{ + jsonBadPathError(ctx, zPath, 0); + goto json_extract_error; + } + if( jnBlob ){ + if( argc==2 ){ + if( flags & JSON_JSON ){ + jsonStringInit(&jx, ctx); + jsonTranslateBlobToText(p, j, &jx); + jsonReturnString(&jx, 0, 0); + jsonStringReset(&jx); + assert( (flags & JSON_BLOB)==0 ); + sqlite3_result_subtype(ctx, JSON_SUBTYPE); + }else{ + jsonReturnFromBlob(p, j, ctx, 0); + if( (flags & (JSON_SQL|JSON_BLOB))==0 + && (p->aBlob[j]&0x0f)>=JSONB_ARRAY + ){ + sqlite3_result_subtype(ctx, JSON_SUBTYPE); + } + } }else{ - jsonAppendRaw(&jx, "null", 4); + jsonAppendSeparator(&jx); + jsonTranslateBlobToText(p, j, &jx); } - }else if( pNode ){ - jsonReturn(pNode, ctx, 0); + }else if( j==JSON_LOOKUP_NOTFOUND ){ + if( argc==2 ){ + goto json_extract_error; /* Return NULL if not found */ + }else{ + jsonAppendSeparator(&jx); + jsonAppendRawNZ(&jx, "null", 4); + } + }else{ + jsonBadPathError(ctx, zPath, j); + goto json_extract_error; } } - if( argc>2 && i==argc ){ + if( argc>2 ){ jsonAppendChar(&jx, ']'); - jsonResult(&jx); - sqlite3_result_subtype(ctx, JSON_SUBTYPE); + jsonReturnString(&jx, 0, 0); + if( (flags & JSON_BLOB)==0 ){ + sqlite3_result_subtype(ctx, JSON_SUBTYPE); + } } - jsonReset(&jx); +json_extract_error: + jsonStringReset(&jx); + jsonParseFree(p); + return; } -/* This is the RFC 7396 MergePatch algorithm. -*/ -static JsonNode *jsonMergePatch( - JsonParse *pParse, /* The JSON parser that contains the TARGET */ - u32 iTarget, /* Node of the TARGET in pParse */ - JsonNode *pPatch /* The PATCH */ -){ - u32 i, j; - u32 iRoot; - JsonNode *pTarget; - if( pPatch->eType!=JSON_OBJECT ){ - return pPatch; - } - assert( iTarget>=0 && iTargetnNode ); - pTarget = &pParse->aNode[iTarget]; - assert( (pPatch->jnFlags & JNODE_APPEND)==0 ); - if( pTarget->eType!=JSON_OBJECT ){ - jsonRemoveAllNulls(pPatch); - return pPatch; - } - iRoot = iTarget; - for(i=1; in; i += jsonNodeSize(&pPatch[i+1])+1){ - u32 nKey; - const char *zKey; - assert( pPatch[i].eType==JSON_STRING ); - assert( pPatch[i].jnFlags & JNODE_LABEL ); - nKey = pPatch[i].n; - zKey = pPatch[i].u.zJContent; - assert( (pPatch[i].jnFlags & JNODE_RAW)==0 ); - for(j=1; jn; j += jsonNodeSize(&pTarget[j+1])+1 ){ - assert( pTarget[j].eType==JSON_STRING ); - assert( pTarget[j].jnFlags & JNODE_LABEL ); - assert( (pPatch[i].jnFlags & JNODE_RAW)==0 ); - if( pTarget[j].n==nKey && strncmp(pTarget[j].u.zJContent,zKey,nKey)==0 ){ - if( pTarget[j+1].jnFlags & (JNODE_REMOVE|JNODE_PATCH) ) break; - if( pPatch[i+1].eType==JSON_NULL ){ - pTarget[j+1].jnFlags |= JNODE_REMOVE; - }else{ - JsonNode *pNew = jsonMergePatch(pParse, iTarget+j+1, &pPatch[i+1]); - if( pNew==0 ) return 0; - pTarget = &pParse->aNode[iTarget]; - if( pNew!=&pTarget[j+1] ){ - pTarget[j+1].u.pPatch = pNew; - pTarget[j+1].jnFlags |= JNODE_PATCH; - } - } - break; +/* +** Return codes for jsonMergePatch() +*/ +#define JSON_MERGE_OK 0 /* Success */ +#define JSON_MERGE_BADTARGET 1 /* Malformed TARGET blob */ +#define JSON_MERGE_BADPATCH 2 /* Malformed PATCH blob */ +#define JSON_MERGE_OOM 3 /* Out-of-memory condition */ +#define JSON_MERGE_TOODEEP 4 /* Nested too deep */ + +/* +** RFC-7396 MergePatch for two JSONB blobs. +** +** pTarget is the target. pPatch is the patch. The target is updated +** in place. The patch is read-only. +** +** The original RFC-7396 algorithm is this: +** +** define MergePatch(Target, Patch): +** if Patch is an Object: +** if Target is not an Object: +** Target = {} # Ignore the contents and set it to an empty Object +** for each Name/Value pair in Patch: +** if Value is null: +** if Name exists in Target: +** remove the Name/Value pair from Target +** else: +** Target[Name] = MergePatch(Target[Name], Value) +** return Target +** else: +** return Patch +** +** Here is an equivalent algorithm restructured to show the actual +** implementation: +** +** 01 define MergePatch(Target, Patch): +** 02 if Patch is not an Object: +** 03 return Patch +** 04 else: // if Patch is an Object +** 05 if Target is not an Object: +** 06 Target = {} +** 07 for each Name/Value pair in Patch: +** 08 if Name exists in Target: +** 09 if Value is null: +** 10 remove the Name/Value pair from Target +** 11 else +** 12 Target[name] = MergePatch(Target[Name], Value) +** 13 else if Value is not NULL: +** 14 if Value is not an Object: +** 15 Target[name] = Value +** 16 else: +** 17 Target[name] = MergePatch('{}',value) +** 18 return Target +** | +** ^---- Line numbers referenced in comments in the implementation +*/ +static int jsonMergePatch( + JsonParse *pTarget, /* The JSON parser that contains the TARGET */ + u32 iTarget, /* Index of TARGET in pTarget->aBlob[] */ + const JsonParse *pPatch, /* The PATCH */ + u32 iPatch, /* Index of PATCH in pPatch->aBlob[] */ + u32 iDepth /* Nesting depth */ +){ + u8 x; /* Type of a single node */ + u32 n, sz=0; /* Return values from jsonbPayloadSize() */ + u32 iTCursor; /* Cursor position while scanning the target object */ + u32 iTStart; /* First label in the target object */ + u32 iTEndBE; /* Original first byte past end of target, before edit */ + u32 iTEnd; /* Current first byte past end of target */ + u8 eTLabel; /* Node type of the target label */ + u32 iTLabel = 0; /* Index of the label */ + u32 nTLabel = 0; /* Header size in bytes for the target label */ + u32 szTLabel = 0; /* Size of the target label payload */ + u32 iTValue = 0; /* Index of the target value */ + u32 nTValue = 0; /* Header size of the target value */ + u32 szTValue = 0; /* Payload size for the target value */ + + u32 iPCursor; /* Cursor position while scanning the patch */ + u32 iPEnd; /* First byte past the end of the patch */ + u8 ePLabel; /* Node type of the patch label */ + u32 iPLabel; /* Start of patch label */ + u32 nPLabel; /* Size of header on the patch label */ + u32 szPLabel; /* Payload size of the patch label */ + u32 iPValue; /* Start of patch value */ + u32 nPValue; /* Header size for the patch value */ + u32 szPValue; /* Payload size of the patch value */ + + assert( iTarget>=0 && iTargetnBlob ); + assert( iPatch>=0 && iPatchnBlob ); + x = pPatch->aBlob[iPatch] & 0x0f; + if( x!=JSONB_OBJECT ){ /* Algorithm line 02 */ + u32 szPatch; /* Total size of the patch, header+payload */ + u32 szTarget; /* Total size of the target, header+payload */ + n = jsonbPayloadSize(pPatch, iPatch, &sz); + szPatch = n+sz; + sz = 0; + n = jsonbPayloadSize(pTarget, iTarget, &sz); + szTarget = n+sz; + jsonBlobEdit(pTarget, iTarget, szTarget, pPatch->aBlob+iPatch, szPatch); + return pTarget->oom ? JSON_MERGE_OOM : JSON_MERGE_OK; /* Line 03 */ + } + x = pTarget->aBlob[iTarget] & 0x0f; + if( x!=JSONB_OBJECT ){ /* Algorithm line 05 */ + n = jsonbPayloadSize(pTarget, iTarget, &sz); + jsonBlobEdit(pTarget, iTarget+n, sz, 0, 0); + x = pTarget->aBlob[iTarget]; + pTarget->aBlob[iTarget] = (x & 0xf0) | JSONB_OBJECT; + } + n = jsonbPayloadSize(pPatch, iPatch, &sz); + if( NEVER(n==0) ) return JSON_MERGE_BADPATCH; + iPCursor = iPatch+n; + iPEnd = iPCursor+sz; + n = jsonbPayloadSize(pTarget, iTarget, &sz); + if( NEVER(n==0) ) return JSON_MERGE_BADTARGET; + iTStart = iTarget+n; + iTEndBE = iTStart+sz; + + while( iPCursoraBlob[iPCursor] & 0x0f; + if( ePLabelJSONB_TEXTRAW ){ + return JSON_MERGE_BADPATCH; + } + nPLabel = jsonbPayloadSize(pPatch, iPCursor, &szPLabel); + if( nPLabel==0 ) return JSON_MERGE_BADPATCH; + iPValue = iPCursor + nPLabel + szPLabel; + if( iPValue>=iPEnd ) return JSON_MERGE_BADPATCH; + nPValue = jsonbPayloadSize(pPatch, iPValue, &szPValue); + if( nPValue==0 ) return JSON_MERGE_BADPATCH; + iPCursor = iPValue + nPValue + szPValue; + if( iPCursor>iPEnd ) return JSON_MERGE_BADPATCH; + + iTCursor = iTStart; + iTEnd = iTEndBE + pTarget->delta; + while( iTCursoraBlob[iTCursor] & 0x0f; + if( eTLabelJSONB_TEXTRAW ){ + return JSON_MERGE_BADTARGET; + } + nTLabel = jsonbPayloadSize(pTarget, iTCursor, &szTLabel); + if( nTLabel==0 ) return JSON_MERGE_BADTARGET; + iTValue = iTLabel + nTLabel + szTLabel; + if( iTValue>=iTEnd ) return JSON_MERGE_BADTARGET; + nTValue = jsonbPayloadSize(pTarget, iTValue, &szTValue); + if( nTValue==0 ) return JSON_MERGE_BADTARGET; + if( iTValue + nTValue + szTValue > iTEnd ) return JSON_MERGE_BADTARGET; + isEqual = jsonLabelCompare( + (const char*)&pPatch->aBlob[iPLabel+nPLabel], + szPLabel, + (ePLabel==JSONB_TEXT || ePLabel==JSONB_TEXTRAW), + (const char*)&pTarget->aBlob[iTLabel+nTLabel], + szTLabel, + (eTLabel==JSONB_TEXT || eTLabel==JSONB_TEXTRAW)); + if( isEqual ) break; + iTCursor = iTValue + nTValue + szTValue; + } + x = pPatch->aBlob[iPValue] & 0x0f; + if( iTCursoroom) ) return JSON_MERGE_OOM; + }else{ + /* Algorithm line 12 */ + int rc, savedDelta = pTarget->delta; + pTarget->delta = 0; + if( iDepth>=JSON_MAX_DEPTH ) return JSON_MERGE_TOODEEP; + rc = jsonMergePatch(pTarget, iTValue, pPatch, iPValue, iDepth+1); + if( rc ) return rc; + pTarget->delta += savedDelta; + } + }else if( x>0 ){ /* Algorithm line 13 */ + /* No match and patch value is not NULL */ + u32 szNew = szPLabel+nPLabel; + if( (pPatch->aBlob[iPValue] & 0x0f)!=JSONB_OBJECT ){ /* Line 14 */ + jsonBlobEdit(pTarget, iTEnd, 0, 0, szPValue+nPValue+szNew); + if( pTarget->oom ) return JSON_MERGE_OOM; + memcpy(&pTarget->aBlob[iTEnd], &pPatch->aBlob[iPLabel], szNew); + memcpy(&pTarget->aBlob[iTEnd+szNew], + &pPatch->aBlob[iPValue], szPValue+nPValue); + }else{ + int rc, savedDelta; + jsonBlobEdit(pTarget, iTEnd, 0, 0, szNew+1); + if( pTarget->oom ) return JSON_MERGE_OOM; + memcpy(&pTarget->aBlob[iTEnd], &pPatch->aBlob[iPLabel], szNew); + pTarget->aBlob[iTEnd+szNew] = 0x00; + savedDelta = pTarget->delta; + pTarget->delta = 0; + if( iDepth>=JSON_MAX_DEPTH ) return JSON_MERGE_TOODEEP; + rc = jsonMergePatch(pTarget, iTEnd+szNew,pPatch,iPValue,iDepth+1); + if( rc ) return rc; + pTarget->delta += savedDelta; } } - if( j>=pTarget->n && pPatch[i+1].eType!=JSON_NULL ){ - int iStart, iPatch; - iStart = jsonParseAddNode(pParse, JSON_OBJECT, 2, 0); - jsonParseAddNode(pParse, JSON_STRING, nKey, zKey); - iPatch = jsonParseAddNode(pParse, JSON_TRUE, 0, 0); - if( pParse->oom ) return 0; - jsonRemoveAllNulls(pPatch); - pTarget = &pParse->aNode[iTarget]; - pParse->aNode[iRoot].jnFlags |= JNODE_APPEND; - pParse->aNode[iRoot].u.iAppend = iStart - iRoot; - iRoot = iStart; - pParse->aNode[iPatch].jnFlags |= JNODE_PATCH; - pParse->aNode[iPatch].u.pPatch = &pPatch[i+1]; - } } - return pTarget; + if( pTarget->delta ) jsonAfterEditSizeAdjust(pTarget, iTarget); + return pTarget->oom ? JSON_MERGE_OOM : JSON_MERGE_OK; } + /* ** Implementation of the json_mergepatch(JSON1,JSON2) function. Return a JSON ** object that is the result of running the RFC 7396 MergePatch() algorithm @@ -180170,25 +217130,29 @@ static void jsonPatchFunc( int argc, sqlite3_value **argv ){ - JsonParse x; /* The JSON that is being patched */ - JsonParse y; /* The patch */ - JsonNode *pResult; /* The result of the merge */ + JsonParse *pTarget; /* The TARGET */ + JsonParse *pPatch; /* The PATCH */ + int rc; /* Result code */ - UNUSED_PARAM(argc); - if( jsonParse(&x, ctx, (const char*)sqlite3_value_text(argv[0])) ) return; - if( jsonParse(&y, ctx, (const char*)sqlite3_value_text(argv[1])) ){ - jsonParseReset(&x); - return; - } - pResult = jsonMergePatch(&x, 0, y.aNode); - assert( pResult!=0 || x.oom ); - if( pResult ){ - jsonReturnJson(pResult, ctx, 0); - }else{ - sqlite3_result_error_nomem(ctx); + UNUSED_PARAMETER(argc); + assert( argc==2 ); + pTarget = jsonParseFuncArg(ctx, argv[0], JSON_EDITABLE); + if( pTarget==0 ) return; + pPatch = jsonParseFuncArg(ctx, argv[1], 0); + if( pPatch ){ + rc = jsonMergePatch(pTarget, 0, pPatch, 0, 0); + if( rc==JSON_MERGE_OK ){ + jsonReturnParse(ctx, pTarget); + }else if( rc==JSON_MERGE_OOM ){ + sqlite3_result_error_nomem(ctx); + }else if( rc==JSON_MERGE_TOODEEP ){ + sqlite3_result_error(ctx, "JSON nested too deep", -1); + }else{ + sqlite3_result_error(ctx, "malformed JSON", -1); + } + jsonParseFree(pPatch); } - jsonParseReset(&x); - jsonParseReset(&y); + jsonParseFree(pTarget); } @@ -180212,23 +217176,23 @@ static void jsonObjectFunc( "of arguments", -1); return; } - jsonInit(&jx, ctx); + jsonStringInit(&jx, ctx); jsonAppendChar(&jx, '{'); for(i=0; i1 ? JSON_EDITABLE : 0); + if( p==0 ) return; + for(i=1; ijnFlags |= JNODE_REMOVE; - } - if( (x.aNode[0].jnFlags & JNODE_REMOVE)==0 ){ - jsonReturnJson(x.aNode, ctx, 0); + if( zPath==0 ){ + goto json_remove_done; + } + if( zPath[0]!='$' ){ + goto json_remove_patherror; + } + if( zPath[1]==0 ){ + /* json_remove(j,'$') returns NULL */ + goto json_remove_done; + } + p->eEdit = JEDIT_DEL; + p->delta = 0; + rc = jsonLookupStep(p, 0, zPath+1, 0); + if( JSON_LOOKUP_ISERROR(rc) ){ + if( rc==JSON_LOOKUP_NOTFOUND ){ + continue; /* No-op */ + }else{ + jsonBadPathError(ctx, zPath, rc); + } + goto json_remove_done; + } } -remove_done: - jsonParseReset(&x); + jsonReturnParse(ctx, p); + jsonParseFree(p); + return; + +json_remove_patherror: + jsonBadPathError(ctx, zPath, 0); + +json_remove_done: + jsonParseFree(p); + return; } /* @@ -180277,36 +217263,15 @@ static void jsonReplaceFunc( int argc, sqlite3_value **argv ){ - JsonParse x; /* The parse */ - JsonNode *pNode; - const char *zPath; - u32 i; - if( argc<1 ) return; if( (argc&1)==0 ) { jsonWrongNumArgs(ctx, "replace"); return; } - if( jsonParse(&x, ctx, (const char*)sqlite3_value_text(argv[0])) ) return; - assert( x.nNode ); - for(i=1; i<(u32)argc; i+=2){ - zPath = (const char*)sqlite3_value_text(argv[i]); - pNode = jsonLookup(&x, zPath, 0, ctx); - if( x.nErr ) goto replace_err; - if( pNode ){ - pNode->jnFlags |= (u8)JNODE_REPLACE; - pNode->u.iReplace = i + 1; - } - } - if( x.aNode[0].jnFlags & JNODE_REPLACE ){ - sqlite3_result_value(ctx, argv[x.aNode[0].u.iReplace]); - }else{ - jsonReturnJson(x.aNode, ctx, argv); - } -replace_err: - jsonParseReset(&x); + jsonInsertIntoBlob(ctx, argc, argv, JEDIT_REPL); } + /* ** json_set(JSON, PATH, VALUE, ...) ** @@ -180324,49 +217289,26 @@ static void jsonSetFunc( int argc, sqlite3_value **argv ){ - JsonParse x; /* The parse */ - JsonNode *pNode; - const char *zPath; - u32 i; - int bApnd; - int bIsSet = *(int*)sqlite3_user_data(ctx); + int flags = SQLITE_PTR_TO_INT(sqlite3_user_data(ctx)); + int eInsType = JSON_INSERT_TYPE(flags); + static const char *azInsType[] = { "insert", "set", "array_insert" }; + static const u8 aEditType[] = { JEDIT_INS, JEDIT_SET, JEDIT_AINS }; if( argc<1 ) return; + assert( eInsType>=0 && eInsType<=2 ); if( (argc&1)==0 ) { - jsonWrongNumArgs(ctx, bIsSet ? "set" : "insert"); + jsonWrongNumArgs(ctx, azInsType[eInsType]); return; } - if( jsonParse(&x, ctx, (const char*)sqlite3_value_text(argv[0])) ) return; - assert( x.nNode ); - for(i=1; i<(u32)argc; i+=2){ - zPath = (const char*)sqlite3_value_text(argv[i]); - bApnd = 0; - pNode = jsonLookup(&x, zPath, &bApnd, ctx); - if( x.oom ){ - sqlite3_result_error_nomem(ctx); - goto jsonSetDone; - }else if( x.nErr ){ - goto jsonSetDone; - }else if( pNode && (bApnd || bIsSet) ){ - pNode->jnFlags |= (u8)JNODE_REPLACE; - pNode->u.iReplace = i + 1; - } - } - if( x.aNode[0].jnFlags & JNODE_REPLACE ){ - sqlite3_result_value(ctx, argv[x.aNode[0].u.iReplace]); - }else{ - jsonReturnJson(x.aNode, ctx, argv); - } -jsonSetDone: - jsonParseReset(&x); + jsonInsertIntoBlob(ctx, argc, argv, aEditType[eInsType]); } /* ** json_type(JSON) ** json_type(JSON, PATH) ** -** Return the top-level "type" of a JSON string. Throw an error if -** either the JSON or PATH inputs are not well-formed. +** Return the top-level "type" of a JSON string. json_type() raises an +** error if either the JSON or PATH inputs are not well-formed. */ static void jsonTypeFunc( sqlite3_context *ctx, @@ -180374,27 +217316,125 @@ static void jsonTypeFunc( sqlite3_value **argv ){ JsonParse *p; /* The parse */ - const char *zPath; - JsonNode *pNode; + const char *zPath = 0; + u32 i; - p = jsonParseCached(ctx, argv, ctx); + p = jsonParseFuncArg(ctx, argv[0], 0); if( p==0 ) return; if( argc==2 ){ zPath = (const char*)sqlite3_value_text(argv[1]); - pNode = jsonLookup(p, zPath, 0, ctx); + if( zPath==0 ) goto json_type_done; + if( zPath[0]!='$' ){ + jsonBadPathError(ctx, zPath, 0); + goto json_type_done; + } + i = jsonLookupStep(p, 0, zPath+1, 0); + if( JSON_LOOKUP_ISERROR(i) ){ + if( i==JSON_LOOKUP_NOTFOUND ){ + /* no-op */ + }else{ + jsonBadPathError(ctx, zPath, i); + } + goto json_type_done; + } }else{ - pNode = p->aNode; + i = 0; } - if( pNode ){ - sqlite3_result_text(ctx, jsonType[pNode->eType], -1, SQLITE_STATIC); + sqlite3_result_text(ctx, jsonbType[p->aBlob[i]&0x0f], -1, SQLITE_STATIC); +json_type_done: + jsonParseFree(p); +} + +/* +** json_pretty(JSON) +** json_pretty(JSON, INDENT) +** +** Return text that is a pretty-printed rendering of the input JSON. +** If the argument is not valid JSON, return NULL. +** +** The INDENT argument is text that is used for indentation. If omitted, +** it defaults to four spaces (the same as PostgreSQL). +*/ +static void jsonPrettyFunc( + sqlite3_context *ctx, + int argc, + sqlite3_value **argv +){ + JsonString s; /* The output string */ + JsonPretty x; /* Pretty printing context */ + + memset(&x, 0, sizeof(x)); + x.pParse = jsonParseFuncArg(ctx, argv[0], 0); + if( x.pParse==0 ) return; + x.pOut = &s; + jsonStringInit(&s, ctx); + if( argc==1 || (x.zIndent = (const char*)sqlite3_value_text(argv[1]))==0 ){ + x.zIndent = " "; + x.szIndent = 4; + }else{ + x.szIndent = (u32)strlen(x.zIndent); } + jsonTranslateBlobToPrettyText(&x, 0); + jsonReturnString(&s, 0, 0); + jsonParseFree(x.pParse); } /* ** json_valid(JSON) -** -** Return 1 if JSON is a well-formed JSON string according to RFC-7159. -** Return 0 otherwise. +** json_valid(JSON, FLAGS) +** +** Check the JSON argument to see if it is well-formed. The FLAGS argument +** encodes the various constraints on what is meant by "well-formed": +** +** 0x01 Canonical RFC-8259 JSON text +** 0x02 JSON text with optional JSON-5 extensions +** 0x04 Superficially appears to be JSONB +** 0x08 Strictly well-formed JSONB +** +** If the FLAGS argument is omitted, it defaults to 1. Useful values for +** FLAGS include: +** +** 1 Strict canonical JSON text +** 2 JSON text perhaps with JSON-5 extensions +** 4 Superficially appears to be JSONB +** 5 Canonical JSON text or superficial JSONB +** 6 JSON-5 text or superficial JSONB +** 8 Strict JSONB +** 9 Canonical JSON text or strict JSONB +** 10 JSON-5 text or strict JSONB +** +** Other flag combinations are redundant. For example, every canonical +** JSON text is also well-formed JSON-5 text, so FLAG values 2 and 3 +** are the same. Similarly, any input that passes a strict JSONB validation +** will also pass the superficial validation so 12 through 15 are the same +** as 8 through 11 respectively. +** +** This routine runs in linear time to validate text and when doing strict +** JSONB validation. Superficial JSONB validation is constant time, +** assuming the BLOB is already in memory. The performance advantage +** of superficial JSONB validation is why that option is provided. +** Application developers can choose to do fast superficial validation or +** slower strict validation, according to their specific needs. +** +** Only the lower four bits of the FLAGS argument are currently used. +** Higher bits are reserved for future expansion. To facilitate +** compatibility, the current implementation raises an error if any bit +** in FLAGS is set other than the lower four bits. +** +** The original circa 2015 implementation of the JSON routines in +** SQLite only supported canonical RFC-8259 JSON text and the json_valid() +** function only accepted one argument. That is why the default value +** for the FLAGS argument is 1, since FLAGS=1 causes this routine to only +** recognize canonical RFC-8259 JSON text as valid. The extra FLAGS +** argument was added when the JSON routines were extended to support +** JSON5-like extensions and binary JSONB stored in BLOBs. +** +** Return Values: +** +** * Raise an error if FLAGS is outside the range of 1 to 15. +** * Return NULL if the input is NULL +** * Return 1 if the input is well-formed. +** * Return 0 if the input is not well-formed. */ static void jsonValidFunc( sqlite3_context *ctx, @@ -180402,11 +217442,121 @@ static void jsonValidFunc( sqlite3_value **argv ){ JsonParse *p; /* The parse */ - UNUSED_PARAM(argc); - p = jsonParseCached(ctx, argv, 0); - sqlite3_result_int(ctx, p!=0); + u8 flags = 1; + u8 res = 0; + if( argc==2 ){ + i64 f = sqlite3_value_int64(argv[1]); + if( f<1 || f>15 ){ + sqlite3_result_error(ctx, "FLAGS parameter to json_valid() must be" + " between 1 and 15", -1); + return; + } + flags = f & 0x0f; + } + switch( sqlite3_value_type(argv[0]) ){ + case SQLITE_NULL: { +#ifdef SQLITE_LEGACY_JSON_VALID + /* Incorrect legacy behavior was to return FALSE for a NULL input */ + sqlite3_result_int(ctx, 0); +#endif + return; + } + case SQLITE_BLOB: { + JsonParse py; + memset(&py, 0, sizeof(py)); + if( jsonArgIsJsonb(argv[0], &py) ){ + if( flags & 0x04 ){ + /* Superficial checking only - accomplished by the + ** jsonArgIsJsonb() call above. */ + res = 1; + }else if( flags & 0x08 ){ + /* Strict checking. Check by translating BLOB->TEXT->BLOB. If + ** no errors occur, call that a "strict check". */ + res = 0==jsonbValidityCheck(&py, 0, py.nBlob, 1); + } + break; + } + /* Fall through into interpreting the input as text. See note + ** above at tag-20240123-a. */ + /* no break */ deliberate_fall_through + } + default: { + JsonParse px; + if( (flags & 0x3)==0 ) break; + memset(&px, 0, sizeof(px)); + + p = jsonParseFuncArg(ctx, argv[0], JSON_KEEPERROR); + if( p ){ + if( p->oom ){ + sqlite3_result_error_nomem(ctx); + }else if( p->nErr ){ + /* no-op */ + }else if( (flags & 0x02)!=0 || p->hasNonstd==0 ){ + res = 1; + } + jsonParseFree(p); + }else{ + sqlite3_result_error_nomem(ctx); + } + break; + } + } + sqlite3_result_int(ctx, res); } +/* +** json_error_position(JSON) +** +** If the argument is NULL, return NULL +** +** If the argument is BLOB, do a full validity check and return non-zero +** if the check fails. The return value is the approximate 1-based offset +** to the byte of the element that contains the first error. +** +** Otherwise interpret the argument is TEXT (even if it is numeric) and +** return the 1-based character position for where the parser first recognized +** that the input was not valid JSON, or return 0 if the input text looks +** ok. JSON-5 extensions are accepted. +*/ +static void jsonErrorFunc( + sqlite3_context *ctx, + int argc, + sqlite3_value **argv +){ + i64 iErrPos = 0; /* Error position to be returned */ + JsonParse s; + + assert( argc==1 ); + UNUSED_PARAMETER(argc); + memset(&s, 0, sizeof(s)); + s.db = sqlite3_context_db_handle(ctx); + if( jsonArgIsJsonb(argv[0], &s) ){ + iErrPos = (i64)jsonbValidityCheck(&s, 0, s.nBlob, 1); + }else{ + s.zJson = (char*)sqlite3_value_text(argv[0]); + if( s.zJson==0 ) return; /* NULL input or OOM */ + s.nJson = sqlite3_value_bytes(argv[0]); + if( jsonConvertTextToBlob(&s,0) ){ + if( s.oom ){ + iErrPos = -1; + }else{ + /* Convert byte-offset s.iErr into a character offset */ + u32 k; + assert( s.zJson!=0 ); /* Because s.oom is false */ + for(k=0; kzBuf==0 ){ - jsonInit(pStr, ctx); + jsonStringInit(pStr, ctx); jsonAppendChar(pStr, '['); - }else{ + }else if( pStr->nUsed>1 ){ jsonAppendChar(pStr, ','); - pStr->pCtx = ctx; } - jsonAppendValue(pStr, argv[0]); + pStr->pCtx = ctx; + jsonAppendSqlValue(pStr, argv[0]); } } static void jsonArrayCompute(sqlite3_context *ctx, int isFinal){ JsonString *pStr; + int flags = SQLITE_PTR_TO_INT(sqlite3_user_data(ctx)); pStr = (JsonString*)sqlite3_aggregate_context(ctx, 0); if( pStr ){ pStr->pCtx = ctx; - jsonAppendChar(pStr, ']'); - if( pStr->bErr ){ - if( pStr->bErr==1 ) sqlite3_result_error_nomem(ctx); - assert( pStr->bStatic ); + jsonAppendRawNZ(pStr, "]", 2); + jsonStringTrimOneChar(pStr); + if( pStr->eErr ){ + jsonReturnString(pStr, 0, 0); + return; + }else if( flags & JSON_BLOB ){ + jsonReturnStringAsBlob(pStr); + if( isFinal ){ + if( !pStr->bStatic ) sqlite3RCStrUnref(pStr->zBuf); + }else{ + jsonStringTrimOneChar(pStr); + } + return; }else if( isFinal ){ sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed, - pStr->bStatic ? SQLITE_TRANSIENT : sqlite3_free); + pStr->bStatic ? SQLITE_TRANSIENT : + sqlite3RCStrUnref); pStr->bStatic = 1; }else{ sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed, SQLITE_TRANSIENT); - pStr->nUsed--; + jsonStringTrimOneChar(pStr); } + }else if( flags & JSON_BLOB ){ + static const u8 emptyArray = 0x0b; + sqlite3_result_blob(ctx, &emptyArray, 1, SQLITE_STATIC); }else{ sqlite3_result_text(ctx, "[]", 2, SQLITE_STATIC); } @@ -180476,29 +217640,36 @@ static void jsonGroupInverse( int argc, sqlite3_value **argv ){ - int i; + unsigned int i; int inStr = 0; + int nNest = 0; char *z; + char c; JsonString *pStr; - UNUSED_PARAM(argc); - UNUSED_PARAM(argv); + UNUSED_PARAMETER(argc); + UNUSED_PARAMETER(argv); pStr = (JsonString*)sqlite3_aggregate_context(ctx, 0); -#ifdef NEVER /* pStr is always non-NULL since jsonArrayStep() or jsonObjectStep() will - ** always have been called to initalize it */ + ** always have been called to initialize it */ if( NEVER(!pStr) ) return; -#endif z = pStr->zBuf; - for(i=1; z[i]!=',' || inStr; i++){ - assert( inUsed ); - if( z[i]=='"' ){ + for(i=1; inUsed && ((c = z[i])!=',' || inStr || nNest); i++){ + if( c=='"' ){ inStr = !inStr; - }else if( z[i]=='\\' ){ + }else if( c=='\\' ){ i++; + }else if( !inStr ){ + if( c=='{' || c=='[' ) nNest++; + if( c=='}' || c==']' ) nNest--; } } - pStr->nUsed -= i; - memmove(&z[1], &z[i+1], (size_t)pStr->nUsed-1); + if( inUsed ){ + pStr->nUsed -= i; + memmove(&z[1], &z[i+1], (size_t)pStr->nUsed-1); + z[pStr->nUsed] = 0; + }else{ + pStr->nUsed = 1; + } } #else # define jsonGroupInverse 0 @@ -180509,6 +217680,13 @@ static void jsonGroupInverse( ** json_group_obj(NAME,VALUE) ** ** Return a JSON object composed of all names and values in the aggregate. +** +** Rows for which NAME is NULL do not result in a new entry. However, we +** do initially insert a "@" entry into the growing string for each null entry +** and change the first character of the string to "@" to signal that the +** string contains null entries. The "@" markers are needed in order to +** correctly process xInverse() requests. The initial "@" is converted +** back into "{" and the "@" null values are removed by jsonObjectCompute(). */ static void jsonObjectStep( sqlite3_context *ctx, @@ -180518,39 +217696,104 @@ static void jsonObjectStep( JsonString *pStr; const char *z; u32 n; - UNUSED_PARAM(argc); + UNUSED_PARAMETER(argc); pStr = (JsonString*)sqlite3_aggregate_context(ctx, sizeof(*pStr)); if( pStr ){ + z = (const char*)sqlite3_value_text(argv[0]); + n = sqlite3Strlen30(z); if( pStr->zBuf==0 ){ - jsonInit(pStr, ctx); + jsonStringInit(pStr, ctx); jsonAppendChar(pStr, '{'); - }else{ + }else if( pStr->nUsed>1 ){ jsonAppendChar(pStr, ','); - pStr->pCtx = ctx; } - z = (const char*)sqlite3_value_text(argv[0]); - n = (u32)sqlite3_value_bytes(argv[0]); - jsonAppendString(pStr, z, n); - jsonAppendChar(pStr, ':'); - jsonAppendValue(pStr, argv[1]); + pStr->pCtx = ctx; + if( z!=0 ){ + jsonAppendString(pStr, z, n); + jsonAppendChar(pStr, ':'); + jsonAppendSqlValue(pStr, argv[1]); + }else{ + pStr->zBuf[0] = '@'; + jsonAppendRawNZ(pStr, "@", 1); + } } } static void jsonObjectCompute(sqlite3_context *ctx, int isFinal){ JsonString *pStr; + int flags = SQLITE_PTR_TO_INT(sqlite3_user_data(ctx)); pStr = (JsonString*)sqlite3_aggregate_context(ctx, 0); if( pStr ){ - jsonAppendChar(pStr, '}'); - if( pStr->bErr ){ - if( pStr->bErr==1 ) sqlite3_result_error_nomem(ctx); - assert( pStr->bStatic ); + JsonString *pOgStr = pStr; + JsonString tmpStr; + jsonAppendRawNZ(pOgStr, "}", 2); /* Ensure it is zero-terminated */ + jsonStringTrimOneChar(pOgStr); /* Remove the zero terminator */ + pStr->pCtx = ctx; + if( pStr->eErr ){ + jsonReturnString(pStr, 0, 0); + return; + } + if( pStr->zBuf[0]!='{' ){ + /* The string contains null entries that need to be removed */ + u64 i, j; + int inStr = 0; + if( !isFinal ){ + /* Work with a temporary copy of the string if this is not the + ** final result */ + jsonStringInit(&tmpStr, ctx); + jsonAppendRawNZ(&tmpStr, pStr->zBuf, pStr->nUsed+1); + pStr = &tmpStr; + if( pStr->eErr ){ + jsonReturnString(pStr, 0, 0); + return; + } + jsonStringTrimOneChar(pStr); /* Remove zero terminator */ + } + /* Fix up the string by changing the initial "@" flag back to + ** to "{" and removing all subsequence "@" entries, with their + ** associated comma delimeters. */ + pStr->zBuf[0] = '{'; + for(i=j=1; inUsed; i++){ + char c = pStr->zBuf[i]; + if( c=='"' ){ + inStr = !inStr; + pStr->zBuf[j++] = '"'; + }else if( c=='\\' ){ + pStr->zBuf[j++] = '\\'; + pStr->zBuf[j++] = pStr->zBuf[++i]; + }else if( c=='@' && !inStr ){ + assert( i+1nUsed ); + if( pStr->zBuf[i+1]==',' ){ + i++; + }else if( pStr->zBuf[j-1]==',' ){ + j--; + } + }else{ + pStr->zBuf[j++] = c; + } + } + pStr->zBuf[j] = 0; /* Restore zero terminator */ + pStr->nUsed = j; /* Truncate the string */ + } + if( flags & JSON_BLOB ){ + jsonReturnStringAsBlob(pStr); + if( isFinal ){ + if( !pStr->bStatic ) sqlite3RCStrUnref(pStr->zBuf); + }else{ + jsonStringTrimOneChar(pOgStr); + } }else if( isFinal ){ sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed, - pStr->bStatic ? SQLITE_TRANSIENT : sqlite3_free); + pStr->bStatic ? SQLITE_TRANSIENT : + sqlite3RCStrUnref); pStr->bStatic = 1; }else{ sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed, SQLITE_TRANSIENT); - pStr->nUsed--; + jsonStringTrimOneChar(pOgStr); } + if( pStr!=pOgStr ) jsonStringReset(pStr); + }else if( flags & JSON_BLOB ){ + static const unsigned char emptyObject = 0x0c; + sqlite3_result_blob(ctx, &emptyObject, 1, SQLITE_STATIC); }else{ sqlite3_result_text(ctx, "{}", 2, SQLITE_STATIC); } @@ -180569,19 +217812,40 @@ static void jsonObjectFinal(sqlite3_context *ctx){ /**************************************************************************** ** The json_each virtual table ****************************************************************************/ +typedef struct JsonParent JsonParent; +struct JsonParent { + u32 iHead; /* Start of object or array */ + u32 iValue; /* Start of the value */ + u32 iEnd; /* First byte past the end */ + u32 nPath; /* Length of path */ + i64 iKey; /* Key for JSONB_ARRAY */ +}; + typedef struct JsonEachCursor JsonEachCursor; struct JsonEachCursor { sqlite3_vtab_cursor base; /* Base class - must be first */ u32 iRowid; /* The rowid */ - u32 iBegin; /* The first node of the scan */ - u32 i; /* Index in sParse.aNode[] of current row */ + u32 i; /* Index in sParse.aBlob[] of current row */ u32 iEnd; /* EOF when i equals or exceeds this value */ - u8 eType; /* Type of top-level element */ + u32 nRoot; /* Size of the root path in bytes */ + u8 eType; /* Type of the container for element i */ u8 bRecursive; /* True for json_tree(). False for json_each() */ - char *zJson; /* Input JSON */ - char *zRoot; /* Path by which to filter zJson */ + u8 eMode; /* 1 for json_each(). 2 for jsonb_each() */ + u32 nParent; /* Current nesting depth */ + u32 nParentAlloc; /* Space allocated for aParent[] */ + JsonParent *aParent; /* Parent elements of i */ + sqlite3 *db; /* Database connection */ + JsonString path; /* Current path */ JsonParse sParse; /* Parse of the input JSON */ }; +typedef struct JsonEachConnection JsonEachConnection; +struct JsonEachConnection { + sqlite3_vtab base; /* Base class - must be first */ + sqlite3 *db; /* Database connection */ + u8 eMode; /* 1 for json_each(). 2 for jsonb_each() */ + u8 bRecursive; /* True for json_tree(). False for json_each() */ +}; + /* Constructor for the json_each virtual table */ static int jsonEachConnect( @@ -180591,7 +217855,7 @@ static int jsonEachConnect( sqlite3_vtab **ppVtab, char **pzErr ){ - sqlite3_vtab *pNew; + JsonEachConnection *pNew; int rc; /* Column numbers */ @@ -180609,68 +217873,69 @@ static int jsonEachConnect( #define JEACH_JSON 8 #define JEACH_ROOT 9 - UNUSED_PARAM(pzErr); - UNUSED_PARAM(argv); - UNUSED_PARAM(argc); - UNUSED_PARAM(pAux); - rc = sqlite3_declare_vtab(db, + UNUSED_PARAMETER(pzErr); + UNUSED_PARAMETER(argv); + UNUSED_PARAMETER(argc); + UNUSED_PARAMETER(pAux); + rc = sqlite3_declare_vtab(db, "CREATE TABLE x(key,value,type,atom,id,parent,fullkey,path," "json HIDDEN,root HIDDEN)"); if( rc==SQLITE_OK ){ - pNew = *ppVtab = sqlite3_malloc( sizeof(*pNew) ); + pNew = (JsonEachConnection*)sqlite3DbMallocZero(db, sizeof(*pNew)); + *ppVtab = (sqlite3_vtab*)pNew; if( pNew==0 ) return SQLITE_NOMEM; - memset(pNew, 0, sizeof(*pNew)); + sqlite3_vtab_config(db, SQLITE_VTAB_INNOCUOUS); + pNew->db = db; + pNew->eMode = argv[0][4]=='b' ? 2 : 1; + pNew->bRecursive = argv[0][4+pNew->eMode]=='t'; } return rc; } /* destructor for json_each virtual table */ static int jsonEachDisconnect(sqlite3_vtab *pVtab){ - sqlite3_free(pVtab); + JsonEachConnection *p = (JsonEachConnection*)pVtab; + sqlite3DbFree(p->db, pVtab); return SQLITE_OK; } -/* constructor for a JsonEachCursor object for json_each(). */ -static int jsonEachOpenEach(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ +/* constructor for a JsonEachCursor object for json_each()/json_tree(). */ +static int jsonEachOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ + JsonEachConnection *pVtab = (JsonEachConnection*)p; JsonEachCursor *pCur; - UNUSED_PARAM(p); - pCur = sqlite3_malloc( sizeof(*pCur) ); + UNUSED_PARAMETER(p); + pCur = sqlite3DbMallocZero(pVtab->db, sizeof(*pCur)); if( pCur==0 ) return SQLITE_NOMEM; - memset(pCur, 0, sizeof(*pCur)); + pCur->db = pVtab->db; + pCur->eMode = pVtab->eMode; + pCur->bRecursive = pVtab->bRecursive; + jsonStringZero(&pCur->path); *ppCursor = &pCur->base; return SQLITE_OK; } -/* constructor for a JsonEachCursor object for json_tree(). */ -static int jsonEachOpenTree(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ - int rc = jsonEachOpenEach(p, ppCursor); - if( rc==SQLITE_OK ){ - JsonEachCursor *pCur = (JsonEachCursor*)*ppCursor; - pCur->bRecursive = 1; - } - return rc; -} - /* Reset a JsonEachCursor back to its original state. Free any memory ** held. */ static void jsonEachCursorReset(JsonEachCursor *p){ - sqlite3_free(p->zJson); - sqlite3_free(p->zRoot); jsonParseReset(&p->sParse); + jsonStringReset(&p->path); + sqlite3DbFree(p->db, p->aParent); p->iRowid = 0; p->i = 0; + p->aParent = 0; + p->nParent = 0; + p->nParentAlloc = 0; p->iEnd = 0; p->eType = 0; - p->zJson = 0; - p->zRoot = 0; } /* Destructor for a jsonEachCursor object */ static int jsonEachClose(sqlite3_vtab_cursor *cur){ JsonEachCursor *p = (JsonEachCursor*)cur; jsonEachCursorReset(p); - sqlite3_free(cur); + + sqlite3DbFree(p->db, cur); return SQLITE_OK; } @@ -180681,166 +217946,235 @@ static int jsonEachEof(sqlite3_vtab_cursor *cur){ return p->i >= p->iEnd; } +/* +** If the cursor is currently pointing at the label of a object entry, +** then return the index of the value. For all other cases, return the +** current pointer position, which is the value. +*/ +static int jsonSkipLabel(JsonEachCursor *p){ + if( p->eType==JSONB_OBJECT ){ + u32 sz = 0; + u32 n = jsonbPayloadSize(&p->sParse, p->i, &sz); + sz += p->i + n; + if( sz >= p->sParse.nBlob ) sz = p->i; + return sz; + }else{ + return p->i; + } +} + +/* +** Append the path name for the current element. +*/ +static void jsonAppendPathName(JsonEachCursor *p){ + assert( p->nParent>0 ); + assert( p->eType==JSONB_ARRAY || p->eType==JSONB_OBJECT ); + if( p->eType==JSONB_ARRAY ){ + jsonPrintf(30, &p->path, "[%lld]", p->aParent[p->nParent-1].iKey); + }else{ + u32 n, sz = 0, k, i; + const char *z; + int needQuote = 0; + n = jsonbPayloadSize(&p->sParse, p->i, &sz); + k = p->i + n; + z = (const char*)&p->sParse.aBlob[k]; + if( sz==0 || !sqlite3Isalpha(z[0]) ){ + needQuote = 1; + }else{ + for(i=0; ipath,".\"%.*s\"", sz, z); + }else{ + jsonPrintf(sz+2,&p->path,".%.*s", sz, z); + } + } +} + /* Advance the cursor to the next element for json_tree() */ static int jsonEachNext(sqlite3_vtab_cursor *cur){ JsonEachCursor *p = (JsonEachCursor*)cur; + int rc = SQLITE_OK; if( p->bRecursive ){ - if( p->sParse.aNode[p->i].jnFlags & JNODE_LABEL ) p->i++; - p->i++; - p->iRowid++; - if( p->iiEnd ){ - u32 iUp = p->sParse.aUp[p->i]; - JsonNode *pUp = &p->sParse.aNode[iUp]; - p->eType = pUp->eType; - if( pUp->eType==JSON_ARRAY ){ - if( iUp==p->i-1 ){ - pUp->u.iKey = 0; - }else{ - pUp->u.iKey++; - } + u8 x; + u8 levelChange = 0; + u32 n, sz = 0; + u32 i = jsonSkipLabel(p); + x = p->sParse.aBlob[i] & 0x0f; + n = jsonbPayloadSize(&p->sParse, i, &sz); + if( x==JSONB_OBJECT || x==JSONB_ARRAY ){ + JsonParent *pParent; + if( p->nParent>=p->nParentAlloc ){ + JsonParent *pNew; + u64 nNew; + nNew = p->nParentAlloc*2 + 3; + pNew = sqlite3DbRealloc(p->db, p->aParent, sizeof(JsonParent)*nNew); + if( pNew==0 ) return SQLITE_NOMEM; + p->nParentAlloc = (u32)nNew; + p->aParent = pNew; + } + levelChange = 1; + pParent = &p->aParent[p->nParent]; + pParent->iHead = p->i; + pParent->iValue = i; + pParent->iEnd = i + n + sz; + pParent->iKey = -1; + pParent->nPath = (u32)p->path.nUsed; + if( p->eType && p->nParent ){ + jsonAppendPathName(p); + if( p->path.eErr ) rc = SQLITE_NOMEM; + } + p->nParent++; + p->i = i + n; + }else{ + p->i = i + n + sz; + } + while( p->nParent>0 && p->i >= p->aParent[p->nParent-1].iEnd ){ + p->nParent--; + p->path.nUsed = p->aParent[p->nParent].nPath; + levelChange = 1; + } + if( levelChange ){ + if( p->nParent>0 ){ + JsonParent *pParent = &p->aParent[p->nParent-1]; + u32 iVal = pParent->iValue; + p->eType = p->sParse.aBlob[iVal] & 0x0f; + }else{ + p->eType = 0; } } }else{ - switch( p->eType ){ - case JSON_ARRAY: { - p->i += jsonNodeSize(&p->sParse.aNode[p->i]); - p->iRowid++; - break; - } - case JSON_OBJECT: { - p->i += 1 + jsonNodeSize(&p->sParse.aNode[p->i+1]); - p->iRowid++; - break; - } - default: { - p->i = p->iEnd; - break; - } - } + u32 n, sz = 0; + u32 i = jsonSkipLabel(p); + n = jsonbPayloadSize(&p->sParse, i, &sz); + p->i = i + n + sz; } - return SQLITE_OK; + if( p->eType==JSONB_ARRAY && p->nParent ){ + p->aParent[p->nParent-1].iKey++; + } + p->iRowid++; + return rc; } -/* Append the name of the path for element i to pStr +/* Length of the path for rowid==0 in bRecursive mode. */ -static void jsonEachComputePath( - JsonEachCursor *p, /* The cursor */ - JsonString *pStr, /* Write the path here */ - u32 i /* Path to this element */ -){ - JsonNode *pNode, *pUp; - u32 iUp; - if( i==0 ){ - jsonAppendChar(pStr, '$'); - return; - } - iUp = p->sParse.aUp[i]; - jsonEachComputePath(p, pStr, iUp); - pNode = &p->sParse.aNode[i]; - pUp = &p->sParse.aNode[iUp]; - if( pUp->eType==JSON_ARRAY ){ - jsonPrintf(30, pStr, "[%d]", pUp->u.iKey); - }else{ - assert( pUp->eType==JSON_OBJECT ); - if( (pNode->jnFlags & JNODE_LABEL)==0 ) pNode--; - assert( pNode->eType==JSON_STRING ); - assert( pNode->jnFlags & JNODE_LABEL ); - jsonPrintf(pNode->n+1, pStr, ".%.*s", pNode->n-2, pNode->u.zJContent+1); +static int jsonEachPathLength(JsonEachCursor *p){ + u32 n = p->path.nUsed; + char *z = p->path.zBuf; + if( p->iRowid==0 && p->bRecursive && n>=2 ){ + while( n>1 ){ + n--; + if( z[n]=='[' || z[n]=='.' ){ + u32 x, sz = 0; + char cSaved = z[n]; + z[n] = 0; + assert( p->sParse.eEdit==0 ); + x = jsonLookupStep(&p->sParse, 0, z+1, 0); + z[n] = cSaved; + if( JSON_LOOKUP_ISERROR(x) ) continue; + if( x + jsonbPayloadSize(&p->sParse, x, &sz) == p->i ) break; + } + } } + return n; } /* Return the value of a column */ static int jsonEachColumn( sqlite3_vtab_cursor *cur, /* The cursor */ sqlite3_context *ctx, /* First argument to sqlite3_result_...() */ - int i /* Which column to return */ + int iColumn /* Which column to return */ ){ JsonEachCursor *p = (JsonEachCursor*)cur; - JsonNode *pThis = &p->sParse.aNode[p->i]; - switch( i ){ + switch( iColumn ){ case JEACH_KEY: { - if( p->i==0 ) break; - if( p->eType==JSON_OBJECT ){ - jsonReturn(pThis, ctx, 0); - }else if( p->eType==JSON_ARRAY ){ - u32 iKey; - if( p->bRecursive ){ - if( p->iRowid==0 ) break; - iKey = p->sParse.aNode[p->sParse.aUp[p->i]].u.iKey; + if( p->nParent==0 ){ + u32 n, j; + if( p->nRoot==1 ) break; + j = jsonEachPathLength(p); + n = p->nRoot - j; + if( n==0 ){ + break; + }else if( p->path.zBuf[j]=='[' ){ + i64 x; + sqlite3Atoi64(&p->path.zBuf[j+1], &x, n-1, SQLITE_UTF8); + sqlite3_result_int64(ctx, x); + }else if( p->path.zBuf[j+1]=='"' ){ + sqlite3_result_text(ctx, &p->path.zBuf[j+2], n-3, SQLITE_TRANSIENT); }else{ - iKey = p->iRowid; + sqlite3_result_text(ctx, &p->path.zBuf[j+1], n-1, SQLITE_TRANSIENT); } - sqlite3_result_int64(ctx, (sqlite3_int64)iKey); + break; + } + if( p->eType==JSONB_OBJECT ){ + jsonReturnFromBlob(&p->sParse, p->i, ctx, 1); + }else{ + assert( p->eType==JSONB_ARRAY ); + sqlite3_result_int64(ctx, p->aParent[p->nParent-1].iKey); } break; } case JEACH_VALUE: { - if( pThis->jnFlags & JNODE_LABEL ) pThis++; - jsonReturn(pThis, ctx, 0); + u32 i = jsonSkipLabel(p); + jsonReturnFromBlob(&p->sParse, i, ctx, p->eMode); + if( (p->sParse.aBlob[i] & 0x0f)>=JSONB_ARRAY ){ + sqlite3_result_subtype(ctx, JSON_SUBTYPE); + } break; } case JEACH_TYPE: { - if( pThis->jnFlags & JNODE_LABEL ) pThis++; - sqlite3_result_text(ctx, jsonType[pThis->eType], -1, SQLITE_STATIC); + u32 i = jsonSkipLabel(p); + u8 eType = p->sParse.aBlob[i] & 0x0f; + sqlite3_result_text(ctx, jsonbType[eType], -1, SQLITE_STATIC); break; } case JEACH_ATOM: { - if( pThis->jnFlags & JNODE_LABEL ) pThis++; - if( pThis->eType>=JSON_ARRAY ) break; - jsonReturn(pThis, ctx, 0); + u32 i = jsonSkipLabel(p); + if( (p->sParse.aBlob[i] & 0x0f)sParse, i, ctx, 1); + } break; } case JEACH_ID: { - sqlite3_result_int64(ctx, - (sqlite3_int64)p->i + ((pThis->jnFlags & JNODE_LABEL)!=0)); + sqlite3_result_int64(ctx, (sqlite3_int64)p->i); break; } case JEACH_PARENT: { - if( p->i>p->iBegin && p->bRecursive ){ - sqlite3_result_int64(ctx, (sqlite3_int64)p->sParse.aUp[p->i]); + if( p->nParent>0 && p->bRecursive ){ + sqlite3_result_int64(ctx, p->aParent[p->nParent-1].iHead); } break; } case JEACH_FULLKEY: { - JsonString x; - jsonInit(&x, ctx); - if( p->bRecursive ){ - jsonEachComputePath(p, &x, p->i); - }else{ - if( p->zRoot ){ - jsonAppendRaw(&x, p->zRoot, (int)strlen(p->zRoot)); - }else{ - jsonAppendChar(&x, '$'); - } - if( p->eType==JSON_ARRAY ){ - jsonPrintf(30, &x, "[%d]", p->iRowid); - }else if( p->eType==JSON_OBJECT ){ - jsonPrintf(pThis->n, &x, ".%.*s", pThis->n-2, pThis->u.zJContent+1); - } - } - jsonResult(&x); + u64 nBase = p->path.nUsed; + if( p->nParent ) jsonAppendPathName(p); + sqlite3_result_text64(ctx, p->path.zBuf, p->path.nUsed, + SQLITE_TRANSIENT, SQLITE_UTF8); + p->path.nUsed = nBase; break; } case JEACH_PATH: { - if( p->bRecursive ){ - JsonString x; - jsonInit(&x, ctx); - jsonEachComputePath(p, &x, p->sParse.aUp[p->i]); - jsonResult(&x); - break; - } - /* For json_each() path and root are the same so fall through - ** into the root case */ + u32 n = jsonEachPathLength(p); + sqlite3_result_text64(ctx, p->path.zBuf, n, + SQLITE_TRANSIENT, SQLITE_UTF8); + break; } default: { - const char *zRoot = p->zRoot; - if( zRoot==0 ) zRoot = "$"; - sqlite3_result_text(ctx, zRoot, -1, SQLITE_STATIC); + sqlite3_result_text(ctx, p->path.zBuf, p->nRoot, SQLITE_STATIC); break; } case JEACH_JSON: { - assert( i==JEACH_JSON ); - sqlite3_result_text(ctx, p->sParse.zJson, -1, SQLITE_STATIC); + if( p->sParse.zJson==0 ){ + sqlite3_result_blob(ctx, p->sParse.aBlob, p->sParse.nBlob, + SQLITE_TRANSIENT); + }else{ + sqlite3_result_text(ctx, p->sParse.zJson, -1, SQLITE_TRANSIENT); + } break; } } @@ -180872,7 +218206,7 @@ static int jsonEachBestIndex( /* This implementation assumes that JSON and ROOT are the last two ** columns in the table */ assert( JEACH_ROOT == JEACH_JSON+1 ); - UNUSED_PARAM(tab); + UNUSED_PARAMETER(tab); aIdx[0] = aIdx[1] = -1; pConstraint = pIdxInfo->aConstraint; for(i=0; inConstraint; i++, pConstraint++){ @@ -180881,6 +218215,7 @@ static int jsonEachBestIndex( if( pConstraint->iColumn < JEACH_JSON ) continue; iCol = pConstraint->iColumn - JEACH_JSON; assert( iCol==0 || iCol==1 ); + testcase( iCol==0 ); iMask = 1 << iCol; if( pConstraint->usable==0 ){ unusableMask |= iMask; @@ -180889,6 +218224,13 @@ static int jsonEachBestIndex( idxMask |= iMask; } } + if( pIdxInfo->nOrderBy>0 + && pIdxInfo->aOrderBy[0].iColumn<0 + && pIdxInfo->aOrderBy[0].desc==0 + ){ + pIdxInfo->orderByConsumed = 1; + } + if( (unusableMask & ~idxMask)!=0 ){ /* If there are any unusable constraints on JSON or ROOT, then reject ** this entire plan */ @@ -180923,76 +218265,96 @@ static int jsonEachFilter( int argc, sqlite3_value **argv ){ JsonEachCursor *p = (JsonEachCursor*)cur; - const char *z; const char *zRoot = 0; - sqlite3_int64 n; + u32 i, n, sz; - UNUSED_PARAM(idxStr); - UNUSED_PARAM(argc); + UNUSED_PARAMETER(idxStr); + UNUSED_PARAMETER(argc); jsonEachCursorReset(p); if( idxNum==0 ) return SQLITE_OK; - z = (const char*)sqlite3_value_text(argv[0]); - if( z==0 ) return SQLITE_OK; - n = sqlite3_value_bytes(argv[0]); - p->zJson = sqlite3_malloc64( n+1 ); - if( p->zJson==0 ) return SQLITE_NOMEM; - memcpy(p->zJson, z, (size_t)n+1); - if( jsonParse(&p->sParse, 0, p->zJson) ){ - int rc = SQLITE_NOMEM; - if( p->sParse.oom==0 ){ - sqlite3_free(cur->pVtab->zErrMsg); - cur->pVtab->zErrMsg = sqlite3_mprintf("malformed JSON"); - if( cur->pVtab->zErrMsg ) rc = SQLITE_ERROR; - } - jsonEachCursorReset(p); - return rc; - }else if( p->bRecursive && jsonParseFindParents(&p->sParse) ){ - jsonEachCursorReset(p); - return SQLITE_NOMEM; + memset(&p->sParse, 0, sizeof(p->sParse)); + p->sParse.nJPRef = 1; + p->sParse.db = p->db; + if( jsonArgIsJsonb(argv[0], &p->sParse) ){ + /* We have JSONB */ }else{ - JsonNode *pNode = 0; - if( idxNum==3 ){ - const char *zErr = 0; - zRoot = (const char*)sqlite3_value_text(argv[1]); - if( zRoot==0 ) return SQLITE_OK; - n = sqlite3_value_bytes(argv[1]); - p->zRoot = sqlite3_malloc64( n+1 ); - if( p->zRoot==0 ) return SQLITE_NOMEM; - memcpy(p->zRoot, zRoot, (size_t)n+1); - if( zRoot[0]!='$' ){ - zErr = zRoot; - }else{ - pNode = jsonLookupStep(&p->sParse, 0, p->zRoot+1, 0, &zErr); + p->sParse.zJson = (char*)sqlite3_value_text(argv[0]); + p->sParse.nJson = sqlite3_value_bytes(argv[0]); + if( p->sParse.zJson==0 ){ + p->i = p->iEnd = 0; + return SQLITE_OK; + } + if( jsonConvertTextToBlob(&p->sParse, 0) ){ + if( p->sParse.oom ){ + return SQLITE_NOMEM; } - if( zErr ){ + goto json_each_malformed_input; + } + } + if( idxNum==3 ){ + zRoot = (const char*)sqlite3_value_text(argv[1]); + if( zRoot==0 ) return SQLITE_OK; + if( zRoot[0]!='$' ){ + sqlite3_free(cur->pVtab->zErrMsg); + cur->pVtab->zErrMsg = jsonBadPathError(0, zRoot, 0); + jsonEachCursorReset(p); + return cur->pVtab->zErrMsg ? SQLITE_ERROR : SQLITE_NOMEM; + } + p->nRoot = sqlite3Strlen30(zRoot); + if( zRoot[1]==0 ){ + i = p->i = 0; + p->eType = 0; + }else{ + i = jsonLookupStep(&p->sParse, 0, zRoot+1, 0); + if( JSON_LOOKUP_ISERROR(i) ){ + if( i==JSON_LOOKUP_NOTFOUND ){ + p->i = 0; + p->eType = 0; + p->iEnd = 0; + return SQLITE_OK; + } sqlite3_free(cur->pVtab->zErrMsg); - cur->pVtab->zErrMsg = jsonPathSyntaxError(zErr); + cur->pVtab->zErrMsg = jsonBadPathError(0, zRoot, 0); jsonEachCursorReset(p); return cur->pVtab->zErrMsg ? SQLITE_ERROR : SQLITE_NOMEM; - }else if( pNode==0 ){ - return SQLITE_OK; } - }else{ - pNode = p->sParse.aNode; - } - p->iBegin = p->i = (int)(pNode - p->sParse.aNode); - p->eType = pNode->eType; - if( p->eType>=JSON_ARRAY ){ - pNode->u.iKey = 0; - p->iEnd = p->i + pNode->n + 1; - if( p->bRecursive ){ - p->eType = p->sParse.aNode[p->sParse.aUp[p->i]].eType; - if( p->i>0 && (p->sParse.aNode[p->i-1].jnFlags & JNODE_LABEL)!=0 ){ - p->i--; - } + if( p->sParse.iLabel ){ + p->i = p->sParse.iLabel; + p->eType = JSONB_OBJECT; }else{ - p->i++; + p->i = i; + p->eType = JSONB_ARRAY; } - }else{ - p->iEnd = p->i+1; } + jsonAppendRaw(&p->path, zRoot, p->nRoot); + }else{ + i = p->i = 0; + p->eType = 0; + p->nRoot = 1; + jsonAppendRaw(&p->path, "$", 1); + } + p->nParent = 0; + n = jsonbPayloadSize(&p->sParse, i, &sz); + p->iEnd = i+n+sz; + if( (p->sParse.aBlob[i] & 0x0f)>=JSONB_ARRAY && !p->bRecursive ){ + p->i = i + n; + p->eType = p->sParse.aBlob[i] & 0x0f; + p->aParent = sqlite3DbMallocZero(p->db, sizeof(JsonParent)); + if( p->aParent==0 ) return SQLITE_NOMEM; + p->nParent = 1; + p->nParentAlloc = 1; + p->aParent[0].iKey = 0; + p->aParent[0].iEnd = p->iEnd; + p->aParent[0].iHead = p->i; + p->aParent[0].iValue = i; } return SQLITE_OK; + +json_each_malformed_input: + sqlite3_free(cur->pVtab->zErrMsg); + cur->pVtab->zErrMsg = sqlite3_mprintf("malformed JSON"); + jsonEachCursorReset(p); + return cur->pVtab->zErrMsg ? SQLITE_ERROR : SQLITE_NOMEM; } /* The methods of the json_each virtual table */ @@ -181003,35 +218365,7 @@ static sqlite3_module jsonEachModule = { jsonEachBestIndex, /* xBestIndex */ jsonEachDisconnect, /* xDisconnect */ 0, /* xDestroy */ - jsonEachOpenEach, /* xOpen - open a cursor */ - jsonEachClose, /* xClose - close a cursor */ - jsonEachFilter, /* xFilter - configure scan constraints */ - jsonEachNext, /* xNext - advance a cursor */ - jsonEachEof, /* xEof - check for end of scan */ - jsonEachColumn, /* xColumn - read data */ - jsonEachRowid, /* xRowid - read data */ - 0, /* xUpdate */ - 0, /* xBegin */ - 0, /* xSync */ - 0, /* xCommit */ - 0, /* xRollback */ - 0, /* xFindMethod */ - 0, /* xRename */ - 0, /* xSavepoint */ - 0, /* xRelease */ - 0, /* xRollbackTo */ - 0 /* xShadowName */ -}; - -/* The methods of the json_tree virtual table. */ -static sqlite3_module jsonTreeModule = { - 0, /* iVersion */ - 0, /* xCreate */ - jsonEachConnect, /* xConnect */ - jsonEachBestIndex, /* xBestIndex */ - jsonEachDisconnect, /* xDisconnect */ - 0, /* xDestroy */ - jsonEachOpenTree, /* xOpen - open a cursor */ + jsonEachOpen, /* xOpen - open a cursor */ jsonEachClose, /* xClose - close a cursor */ jsonEachFilter, /* xFilter - configure scan constraints */ jsonEachNext, /* xNext - advance a cursor */ @@ -181048,108 +218382,99 @@ static sqlite3_module jsonTreeModule = { 0, /* xSavepoint */ 0, /* xRelease */ 0, /* xRollbackTo */ - 0 /* xShadowName */ + 0, /* xShadowName */ + 0 /* xIntegrity */ }; #endif /* SQLITE_OMIT_VIRTUALTABLE */ - -/**************************************************************************** -** The following routines are the only publically visible identifiers in this -** file. Call the following routines in order to register the various SQL -** functions and the virtual table implemented by this file. -****************************************************************************/ - -SQLITE_PRIVATE int sqlite3Json1Init(sqlite3 *db){ - int rc = SQLITE_OK; - unsigned int i; - static const struct { - const char *zName; - int nArg; - int flag; - void (*xFunc)(sqlite3_context*,int,sqlite3_value**); - } aFunc[] = { - { "json", 1, 0, jsonRemoveFunc }, - { "json_array", -1, 0, jsonArrayFunc }, - { "json_array_length", 1, 0, jsonArrayLengthFunc }, - { "json_array_length", 2, 0, jsonArrayLengthFunc }, - { "json_extract", -1, 0, jsonExtractFunc }, - { "json_insert", -1, 0, jsonSetFunc }, - { "json_object", -1, 0, jsonObjectFunc }, - { "json_patch", 2, 0, jsonPatchFunc }, - { "json_quote", 1, 0, jsonQuoteFunc }, - { "json_remove", -1, 0, jsonRemoveFunc }, - { "json_replace", -1, 0, jsonReplaceFunc }, - { "json_set", -1, 1, jsonSetFunc }, - { "json_type", 1, 0, jsonTypeFunc }, - { "json_type", 2, 0, jsonTypeFunc }, - { "json_valid", 1, 0, jsonValidFunc }, - +#endif /* !defined(SQLITE_OMIT_JSON) */ + +/* +** Register JSON functions. +*/ +SQLITE_PRIVATE void sqlite3RegisterJsonFunctions(void){ +#ifndef SQLITE_OMIT_JSON + static FuncDef aJsonFunc[] = { + /* sqlite3_result_subtype() ----, ,--- sqlite3_value_subtype() */ + /* | | */ + /* Uses cache ------, | | ,---- Returns JSONB */ + /* | | | | */ + /* Number of arguments ---, | | | | ,--- Flags */ + /* | | | | | | */ + JFUNCTION(json, 1,1,1, 0,0,0, jsonRemoveFunc), + JFUNCTION(jsonb, 1,1,0, 0,1,0, jsonRemoveFunc), + JFUNCTION(json_array, -1,0,1, 1,0,0, jsonArrayFunc), + JFUNCTION(jsonb_array, -1,0,1, 1,1,0, jsonArrayFunc), + JFUNCTION(json_array_insert, -1,1,1, 1,0,JSON_AINS, jsonSetFunc), + JFUNCTION(jsonb_array_insert,-1,1,0, 1,1,JSON_AINS, jsonSetFunc), + JFUNCTION(json_array_length, 1,1,0, 0,0,0, jsonArrayLengthFunc), + JFUNCTION(json_array_length, 2,1,0, 0,0,0, jsonArrayLengthFunc), + JFUNCTION(json_error_position,1,1,0, 0,0,0, jsonErrorFunc), + JFUNCTION(json_extract, -1,1,1, 0,0,0, jsonExtractFunc), + JFUNCTION(jsonb_extract, -1,1,0, 0,1,0, jsonExtractFunc), + JFUNCTION(->, 2,1,1, 0,0,JSON_JSON, jsonExtractFunc), + JFUNCTION(->>, 2,1,0, 0,0,JSON_SQL, jsonExtractFunc), + JFUNCTION(json_insert, -1,1,1, 1,0,0, jsonSetFunc), + JFUNCTION(jsonb_insert, -1,1,0, 1,1,0, jsonSetFunc), + JFUNCTION(json_object, -1,0,1, 1,0,0, jsonObjectFunc), + JFUNCTION(jsonb_object, -1,0,1, 1,1,0, jsonObjectFunc), + JFUNCTION(json_patch, 2,1,1, 0,0,0, jsonPatchFunc), + JFUNCTION(jsonb_patch, 2,1,0, 0,1,0, jsonPatchFunc), + JFUNCTION(json_pretty, 1,1,0, 0,0,0, jsonPrettyFunc), + JFUNCTION(json_pretty, 2,1,0, 0,0,0, jsonPrettyFunc), + JFUNCTION(json_quote, 1,0,1, 1,0,0, jsonQuoteFunc), + JFUNCTION(json_remove, -1,1,1, 0,0,0, jsonRemoveFunc), + JFUNCTION(jsonb_remove, -1,1,0, 0,1,0, jsonRemoveFunc), + JFUNCTION(json_replace, -1,1,1, 1,0,0, jsonReplaceFunc), + JFUNCTION(jsonb_replace, -1,1,0, 1,1,0, jsonReplaceFunc), + JFUNCTION(json_set, -1,1,1, 1,0,JSON_ISSET, jsonSetFunc), + JFUNCTION(jsonb_set, -1,1,0, 1,1,JSON_ISSET, jsonSetFunc), + JFUNCTION(json_type, 1,1,0, 0,0,0, jsonTypeFunc), + JFUNCTION(json_type, 2,1,0, 0,0,0, jsonTypeFunc), + JFUNCTION(json_valid, 1,1,0, 0,0,0, jsonValidFunc), + JFUNCTION(json_valid, 2,1,0, 0,0,0, jsonValidFunc), #if SQLITE_DEBUG - /* DEBUG and TESTING functions */ - { "json_parse", 1, 0, jsonParseFunc }, - { "json_test1", 1, 0, jsonTest1Func }, -#endif - }; - static const struct { - const char *zName; - int nArg; - void (*xStep)(sqlite3_context*,int,sqlite3_value**); - void (*xFinal)(sqlite3_context*); - void (*xValue)(sqlite3_context*); - } aAgg[] = { - { "json_group_array", 1, - jsonArrayStep, jsonArrayFinal, jsonArrayValue }, - { "json_group_object", 2, - jsonObjectStep, jsonObjectFinal, jsonObjectValue }, - }; -#ifndef SQLITE_OMIT_VIRTUALTABLE - static const struct { - const char *zName; - sqlite3_module *pModule; - } aMod[] = { - { "json_each", &jsonEachModule }, - { "json_tree", &jsonTreeModule }, + JFUNCTION(json_parse, 1,1,0, 0,0,0, jsonParseFunc), +#endif + WAGGREGATE(json_group_array, 1, 0, 0, + jsonArrayStep, jsonArrayFinal, jsonArrayValue, jsonGroupInverse, + SQLITE_SUBTYPE|SQLITE_RESULT_SUBTYPE|SQLITE_UTF8| + SQLITE_DETERMINISTIC), + WAGGREGATE(jsonb_group_array, 1, JSON_BLOB, 0, + jsonArrayStep, jsonArrayFinal, jsonArrayValue, jsonGroupInverse, + SQLITE_SUBTYPE|SQLITE_RESULT_SUBTYPE|SQLITE_UTF8|SQLITE_DETERMINISTIC), + WAGGREGATE(json_group_object, 2, 0, 0, + jsonObjectStep, jsonObjectFinal, jsonObjectValue, jsonGroupInverse, + SQLITE_SUBTYPE|SQLITE_RESULT_SUBTYPE|SQLITE_UTF8|SQLITE_DETERMINISTIC), + WAGGREGATE(jsonb_group_object,2, JSON_BLOB, 0, + jsonObjectStep, jsonObjectFinal, jsonObjectValue, jsonGroupInverse, + SQLITE_SUBTYPE|SQLITE_RESULT_SUBTYPE|SQLITE_UTF8| + SQLITE_DETERMINISTIC) }; + sqlite3InsertBuiltinFuncs(aJsonFunc, ArraySize(aJsonFunc)); #endif - for(i=0; iaModule, zName)==0 ); + for(i=0; i */ -/* #include */ -/* #include */ +/* #include */ -#ifndef SQLITE_AMALGAMATION +/* +** If building separately, we will need some setup that is normally +** found in sqliteInt.h +*/ +#if !defined(SQLITE_AMALGAMATION) #include "sqlite3rtree.h" typedef sqlite3_int64 i64; typedef sqlite3_uint64 u64; typedef unsigned char u8; typedef unsigned short u16; typedef unsigned int u32; +#if !defined(NDEBUG) && !defined(SQLITE_DEBUG) +# define NDEBUG 1 +#endif +#if defined(NDEBUG) && defined(SQLITE_DEBUG) +# undef NDEBUG +#endif +#if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_MUTATION_TEST) +# define SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS 1 +#endif +#if defined(SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS) +# define ALWAYS(X) (1) +# define NEVER(X) (0) +#elif !defined(NDEBUG) +# define ALWAYS(X) ((X)?1:(assert(0),0)) +# define NEVER(X) ((X)?(assert(0),1):0) +#else +# define ALWAYS(X) (X) +# define NEVER(X) (X) +#endif +#ifndef offsetof +# define offsetof(ST,M) ((size_t)((char*)&((ST*)0)->M - (char*)0)) +#endif +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) +# define FLEXARRAY +#else +# define FLEXARRAY 1 #endif +#endif /* !defined(SQLITE_AMALGAMATION) */ + +/* Macro to check for 4-byte alignment. Only used inside of assert() */ +#ifdef SQLITE_DEBUG +# define FOUR_BYTE_ALIGNED(X) ((((char*)(X) - (char*)0) & 3)==0) +#endif + +/* #include */ +/* #include */ +/* #include */ +/* #include */ /* The following macro is used to suppress compiler warnings. */ @@ -181252,7 +218617,7 @@ typedef struct RtreeSearchPoint RtreeSearchPoint; #define RTREE_MAX_AUX_COLUMN 100 /* Size of hash table Rtree.aHash. This hash table is not expected to -** ever contain very many entries, so a fixed number of buckets is +** ever contain very many entries, so a fixed number of buckets is ** used. */ #define HASHSIZE 97 @@ -181261,13 +218626,13 @@ typedef struct RtreeSearchPoint RtreeSearchPoint; ** the number of rows in the virtual table to calculate the costs of ** various strategies. If possible, this estimate is loaded from the ** sqlite_stat1 table (with RTREE_MIN_ROWEST as a hard-coded minimum). -** Otherwise, if no sqlite_stat1 entry is available, use +** Otherwise, if no sqlite_stat1 entry is available, use ** RTREE_DEFAULT_ROWEST. */ #define RTREE_DEFAULT_ROWEST 1048576 #define RTREE_MIN_ROWEST 100 -/* +/* ** An rtree virtual-table object. */ struct Rtree { @@ -181279,14 +218644,17 @@ struct Rtree { u8 eCoordType; /* RTREE_COORD_REAL32 or RTREE_COORD_INT32 */ u8 nBytesPerCell; /* Bytes consumed per cell */ u8 inWrTrans; /* True if inside write transaction */ - u8 nAux; /* # of auxiliary columns in %_rowid */ + u16 nAux; /* # of auxiliary columns in %_rowid */ +#ifdef SQLITE_ENABLE_GEOPOLY u8 nAuxNotNull; /* Number of initial not-null aux columns */ +#endif #ifdef SQLITE_DEBUG u8 bCorrupt; /* Shadow table corruption detected */ #endif int iDepth; /* Current depth of the r-tree structure */ char *zDb; /* Name of database containing r-tree table */ - char *zName; /* Name of r-tree table */ + char *zName; /* Name of r-tree table */ + char *zNodeName; /* Name of the %_node table */ u32 nBusy; /* Current number of users of this structure */ i64 nRowEst; /* Estimated number of rows in this table */ u32 nCursor; /* Number of open cursors */ @@ -181295,11 +218663,10 @@ struct Rtree { /* List of nodes removed during a CondenseTree operation. List is ** linked together via the pointer normally used for hash chains - - ** RtreeNode.pNext. RtreeNode.iNode stores the depth of the sub-tree + ** RtreeNode.pNext. RtreeNode.iNode stores the depth of the sub-tree ** headed by the node (leaf nodes have RtreeNode.iNode==0). */ RtreeNode *pDeleted; - int iReinsertHeight; /* Height of sub-trees Reinsert() has run on */ /* Blob I/O on xxx_node */ sqlite3_blob *pNodeBlob; @@ -181321,7 +218688,7 @@ struct Rtree { /* Statement for writing to the "aux:" fields, if there are any */ sqlite3_stmt *pWriteAux; - RtreeNode *aHash[HASHSIZE]; /* Hash table of in-memory nodes. */ + RtreeNode *aHash[HASHSIZE]; /* Hash table of in-memory nodes. */ }; /* Possible values for Rtree.eCoordType: */ @@ -181370,7 +218737,7 @@ struct RtreeSearchPoint { }; /* -** The minimum number of cells allowed for a node is a third of the +** The minimum number of cells allowed for a node is a third of the ** maximum. In Gutman's notation: ** ** m = M/3 @@ -181385,7 +218752,7 @@ struct RtreeSearchPoint { /* ** The smallest possible node-size is (512-64)==448 bytes. And the largest ** supported cell size is 48 bytes (8 byte rowid + ten 4 byte coordinates). -** Therefore all non-root nodes must contain at least 3 entries. Since +** Therefore all non-root nodes must contain at least 3 entries. Since ** 3^40 is greater than 2^64, an r-tree structure always has a depth of ** 40 or less. */ @@ -181399,7 +218766,7 @@ struct RtreeSearchPoint { */ #define RTREE_CACHE_SZ 5 -/* +/* ** An rtree cursor object. */ struct RtreeCursor { @@ -181417,7 +218784,7 @@ struct RtreeCursor { sqlite3_stmt *pReadAux; /* Statement to read aux-data */ RtreeSearchPoint sPoint; /* Cached next search point */ RtreeNode *aNode[RTREE_CACHE_SZ]; /* Rtree node cache */ - u32 anQueue[RTREE_MAX_DEPTH+1]; /* Number of queued entries by iLevel */ + u32 anQueue[RTREE_MAX_DEPTH+2]; /* Number of queued entries by iLevel */ }; /* Return the Rtree of a RtreeCursor */ @@ -181472,8 +218839,14 @@ struct RtreeConstraint { #define RTREE_MATCH 0x46 /* F: Old-style sqlite3_rtree_geometry_callback() */ #define RTREE_QUERY 0x47 /* G: New-style sqlite3_rtree_query_callback() */ +/* Special operators available only on cursors. Needs to be consecutive +** with the normal values above, but must be less than RTREE_MATCH. These +** are used in the cursor for contraints such as x=NULL (RTREE_FALSE) or +** x<'xyz' (RTREE_TRUE) */ +#define RTREE_TRUE 0x3f /* ? */ +#define RTREE_FALSE 0x40 /* @ */ -/* +/* ** An rtree structure node. */ struct RtreeNode { @@ -181488,7 +218861,7 @@ struct RtreeNode { /* Return the number of cells in a node */ #define NCELL(pNode) readInt16(&(pNode)->zData[2]) -/* +/* ** A single cell from a node, deserialized */ struct RtreeCell { @@ -181503,11 +218876,11 @@ struct RtreeCell { ** sqlite3_rtree_query_callback() and which appear on the right of MATCH ** operators in order to constrain a search. ** -** xGeom and xQueryFunc are the callback functions. Exactly one of +** xGeom and xQueryFunc are the callback functions. Exactly one of ** xGeom and xQueryFunc fields is non-NULL, depending on whether the ** SQL function was created using sqlite3_rtree_geometry_callback() or ** sqlite3_rtree_query_callback(). -** +** ** This object is deleted automatically by the destructor mechanism in ** sqlite3_create_function_v2(). */ @@ -181529,9 +218902,13 @@ struct RtreeMatchArg { RtreeGeomCallback cb; /* Info about the callback functions */ int nParam; /* Number of parameters to the SQL function */ sqlite3_value **apSqlParam; /* Original SQL parameter values */ - RtreeDValue aParam[1]; /* Values for parameters to the SQL function */ + RtreeDValue aParam[FLEXARRAY]; /* Values for parameters to the SQL function */ }; +/* Size of an RtreeMatchArg object with N parameters */ +#define SZ_RTREEMATCHARG(N) \ + (offsetof(RtreeMatchArg,aParam)+(N)*sizeof(RtreeDValue)) + #ifndef MAX # define MAX(x,y) ((x) < (y) ? (y) : (x)) #endif @@ -181556,7 +218933,29 @@ struct RtreeMatchArg { ** it is not, make it a no-op. */ #ifndef SQLITE_AMALGAMATION -# define testcase(X) +# if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_DEBUG) + unsigned int sqlite3RtreeTestcase = 0; +# define testcase(X) if( X ){ sqlite3RtreeTestcase += __LINE__; } +# else +# define testcase(X) +# endif +#endif + +/* +** Make sure that the compiler intrinsics we desire are enabled when +** compiling with an appropriate version of MSVC unless prevented by +** the SQLITE_DISABLE_INTRINSIC define. +*/ +#if !defined(SQLITE_DISABLE_INTRINSIC) +# if defined(_MSC_VER) && _MSC_VER>=1400 +# if !defined(_WIN32_WCE) +/* # include */ +# pragma intrinsic(_byteswap_ulong) +# pragma intrinsic(_byteswap_uint64) +# else +/* # include */ +# endif +# endif #endif /* @@ -181568,17 +218967,23 @@ struct RtreeMatchArg { ** -DSQLITE_RUNTIME_BYTEORDER=1 is set, then byte-order is determined ** at run-time. */ -#ifndef SQLITE_BYTEORDER -#if defined(i386) || defined(__i386__) || defined(_M_IX86) || \ - defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || \ - defined(_M_AMD64) || defined(_M_ARM) || defined(__x86) || \ - defined(__arm__) -# define SQLITE_BYTEORDER 1234 -#elif defined(sparc) || defined(__ppc__) -# define SQLITE_BYTEORDER 4321 -#else -# define SQLITE_BYTEORDER 0 /* 0 means "unknown at compile-time" */ -#endif +#ifndef SQLITE_BYTEORDER /* Replicate changes at tag-20230904a */ +# if defined(__BYTE_ORDER__) && __BYTE_ORDER__==__ORDER_BIG_ENDIAN__ +# define SQLITE_BYTEORDER 4321 +# elif defined(__BYTE_ORDER__) && __BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__ +# define SQLITE_BYTEORDER 1234 +# elif defined(__BIG_ENDIAN__) && __BIG_ENDIAN__==1 +# define SQLITE_BYTEORDER 4321 +# elif defined(i386) || defined(__i386__) || defined(_M_IX86) || \ + defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || \ + defined(_M_AMD64) || defined(_M_ARM) || defined(__x86) || \ + defined(__ARMEL__) || defined(__AARCH64EL__) || defined(_M_ARM64) +# define SQLITE_BYTEORDER 1234 +# elif defined(sparc) || defined(__ARMEB__) || defined(__AARCH64EB__) +# define SQLITE_BYTEORDER 4321 +# else +# define SQLITE_BYTEORDER 0 +# endif #endif @@ -181599,7 +219004,7 @@ static int readInt16(u8 *p){ return (p[0]<<8) + p[1]; } static void readCoord(u8 *p, RtreeCoord *pCoord){ - assert( ((((char*)p) - (char*)0)&3)==0 ); /* p is always 4-byte aligned */ + assert( FOUR_BYTE_ALIGNED(p) ); #if SQLITE_BYTEORDER==1234 && MSVC_VERSION>=1300 pCoord->u = _byteswap_ulong(*(u32*)p); #elif SQLITE_BYTEORDER==1234 && GCC_VERSION>=4003000 @@ -181608,9 +219013,9 @@ static void readCoord(u8 *p, RtreeCoord *pCoord){ pCoord->u = *(u32*)p; #else pCoord->u = ( - (((u32)p[0]) << 24) + - (((u32)p[1]) << 16) + - (((u32)p[2]) << 8) + + (((u32)p[0]) << 24) + + (((u32)p[1]) << 16) + + (((u32)p[2]) << 8) + (((u32)p[3]) << 0) ); #endif @@ -181630,13 +219035,13 @@ static i64 readInt64(u8 *p){ return x; #else return (i64)( - (((u64)p[0]) << 56) + - (((u64)p[1]) << 48) + - (((u64)p[2]) << 40) + - (((u64)p[3]) << 32) + - (((u64)p[4]) << 24) + - (((u64)p[5]) << 16) + - (((u64)p[6]) << 8) + + (((u64)p[0]) << 56) + + (((u64)p[1]) << 48) + + (((u64)p[2]) << 40) + + (((u64)p[3]) << 32) + + (((u64)p[4]) << 24) + + (((u64)p[5]) << 16) + + (((u64)p[6]) << 8) + (((u64)p[7]) << 0) ); #endif @@ -181653,7 +219058,7 @@ static void writeInt16(u8 *p, int i){ } static int writeCoord(u8 *p, RtreeCoord *pCoord){ u32 i; - assert( ((((char*)p) - (char*)0)&3)==0 ); /* p is always 4-byte aligned */ + assert( FOUR_BYTE_ALIGNED(p) ); assert( sizeof(RtreeCoord)==4 ); assert( sizeof(u32)==4 ); #if SQLITE_BYTEORDER==1234 && GCC_VERSION>=4003000 @@ -181781,23 +219186,9 @@ static RtreeNode *nodeNew(Rtree *pRtree, RtreeNode *pParent){ ** Clear the Rtree.pNodeBlob object */ static void nodeBlobReset(Rtree *pRtree){ - if( pRtree->pNodeBlob && pRtree->inWrTrans==0 && pRtree->nCursor==0 ){ - sqlite3_blob *pBlob = pRtree->pNodeBlob; - pRtree->pNodeBlob = 0; - sqlite3_blob_close(pBlob); - } -} - -/* -** Check to see if pNode is the same as pParent or any of the parents -** of pParent. -*/ -static int nodeInParentChain(const RtreeNode *pNode, const RtreeNode *pParent){ - do{ - if( pNode==pParent ) return 1; - pParent = pParent->pParent; - }while( pParent ); - return 0; + sqlite3_blob *pBlob = pRtree->pNodeBlob; + pRtree->pNodeBlob = 0; + sqlite3_blob_close(pBlob); } /* @@ -181816,14 +219207,9 @@ static int nodeAcquire( ** increase its reference count and return it. */ if( (pNode = nodeHashLookup(pRtree, iNode))!=0 ){ - assert( !pParent || !pNode->pParent || pNode->pParent==pParent ); - if( pParent && !pNode->pParent ){ - if( nodeInParentChain(pNode, pParent) ){ - RTREE_IS_CORRUPT(pRtree); - return SQLITE_CORRUPT_VTAB; - } - pParent->nRef++; - pNode->pParent = pParent; + if( pParent && ALWAYS(pParent!=pNode->pParent) ){ + RTREE_IS_CORRUPT(pRtree); + return SQLITE_CORRUPT_VTAB; } pNode->nRef++; *ppNode = pNode; @@ -181841,14 +219227,11 @@ static int nodeAcquire( } } if( pRtree->pNodeBlob==0 ){ - char *zTab = sqlite3_mprintf("%s_node", pRtree->zName); - if( zTab==0 ) return SQLITE_NOMEM; - rc = sqlite3_blob_open(pRtree->db, pRtree->zDb, zTab, "data", iNode, 0, + rc = sqlite3_blob_open(pRtree->db, pRtree->zDb, pRtree->zNodeName, + "data", iNode, 0, &pRtree->pNodeBlob); - sqlite3_free(zTab); } if( rc ){ - nodeBlobReset(pRtree); *ppNode = 0; /* If unable to open an sqlite3_blob on the desired row, that can only ** be because the shadow tables hold erroneous data. */ @@ -181856,6 +219239,9 @@ static int nodeAcquire( rc = SQLITE_CORRUPT_VTAB; RTREE_IS_CORRUPT(pRtree); } + }else if( iNode<=0 ){ + RTREE_IS_CORRUPT(pRtree); + rc = SQLITE_CORRUPT_VTAB; }else if( pRtree->iNodeSize==sqlite3_blob_bytes(pRtree->pNodeBlob) ){ pNode = (RtreeNode *)sqlite3_malloc64(sizeof(RtreeNode)+pRtree->iNodeSize); if( !pNode ){ @@ -181879,16 +219265,16 @@ static int nodeAcquire( ** are the leaves, and so on. If the depth as specified on the root node ** is greater than RTREE_MAX_DEPTH, the r-tree structure must be corrupt. */ - if( pNode && iNode==1 ){ + if( rc==SQLITE_OK && pNode && iNode==1 ){ pRtree->iDepth = readInt16(pNode->zData); - if( pRtree->iDepth>RTREE_MAX_DEPTH ){ + if( pRtree->iDepth>=RTREE_MAX_DEPTH ){ rc = SQLITE_CORRUPT_VTAB; RTREE_IS_CORRUPT(pRtree); } } /* If no error has occurred so far, check if the "number of entries" - ** field on the node is too large. If so, set the return code to + ** field on the node is too large. If so, set the return code to ** SQLITE_CORRUPT_VTAB. */ if( pNode && rc==SQLITE_OK ){ @@ -181908,6 +219294,7 @@ static int nodeAcquire( } *ppNode = pNode; }else{ + nodeBlobReset(pRtree); if( pNode ){ pRtree->nNodeRef--; sqlite3_free(pNode); @@ -182052,6 +219439,7 @@ static void nodeGetCoord( int iCoord, /* Which coordinate to extract */ RtreeCoord *pCoord /* OUT: Space to write result to */ ){ + assert( iCellzData[12 + pRtree->nBytesPerCell*iCell + 4*iCoord], pCoord); } @@ -182087,7 +219475,7 @@ static int rtreeInit( sqlite3 *, void *, int, const char *const*, sqlite3_vtab **, char **, int ); -/* +/* ** Rtree virtual table module xCreate method. */ static int rtreeCreate( @@ -182100,7 +219488,7 @@ static int rtreeCreate( return rtreeInit(db, pAux, argc, argv, ppVtab, pzErr, 1); } -/* +/* ** Rtree virtual table module xConnect method. */ static int rtreeConnect( @@ -182130,7 +219518,17 @@ static void rtreeRelease(Rtree *pRtree){ pRtree->inWrTrans = 0; assert( pRtree->nCursor==0 ); nodeBlobReset(pRtree); - assert( pRtree->nNodeRef==0 || pRtree->bCorrupt ); + if( pRtree->nNodeRef ){ + int i; + assert( pRtree->bCorrupt ); + for(i=0; iaHash[i] ){ + RtreeNode *pNext = pRtree->aHash[i]->pNext; + sqlite3_free(pRtree->aHash[i]); + pRtree->aHash[i] = pNext; + } + } + } sqlite3_finalize(pRtree->pWriteNode); sqlite3_finalize(pRtree->pDeleteNode); sqlite3_finalize(pRtree->pReadRowid); @@ -182145,7 +219543,7 @@ static void rtreeRelease(Rtree *pRtree){ } } -/* +/* ** Rtree virtual table module xDisconnect method. */ static int rtreeDisconnect(sqlite3_vtab *pVtab){ @@ -182153,7 +219551,7 @@ static int rtreeDisconnect(sqlite3_vtab *pVtab){ return SQLITE_OK; } -/* +/* ** Rtree virtual table module xDestroy method. */ static int rtreeDestroy(sqlite3_vtab *pVtab){ @@ -182163,7 +219561,7 @@ static int rtreeDestroy(sqlite3_vtab *pVtab){ "DROP TABLE '%q'.'%q_node';" "DROP TABLE '%q'.'%q_rowid';" "DROP TABLE '%q'.'%q_parent';", - pRtree->zDb, pRtree->zName, + pRtree->zDb, pRtree->zName, pRtree->zDb, pRtree->zName, pRtree->zDb, pRtree->zName ); @@ -182181,7 +219579,7 @@ static int rtreeDestroy(sqlite3_vtab *pVtab){ return rc; } -/* +/* ** Rtree virtual table module xOpen method. */ static int rtreeOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){ @@ -182203,9 +219601,12 @@ static int rtreeOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){ /* -** Free the RtreeCursor.aConstraint[] array and its contents. +** Reset a cursor back to its initial state. */ -static void freeCursorConstraints(RtreeCursor *pCsr){ +static void resetCursor(RtreeCursor *pCsr){ + Rtree *pRtree = (Rtree *)(pCsr->base.pVtab); + int ii; + sqlite3_stmt *pStmt; if( pCsr->aConstraint ){ int i; /* Used to iterate through constraint array */ for(i=0; inConstraint; i++){ @@ -182218,30 +219619,42 @@ static void freeCursorConstraints(RtreeCursor *pCsr){ sqlite3_free(pCsr->aConstraint); pCsr->aConstraint = 0; } + for(ii=0; iiaNode[ii]); + sqlite3_free(pCsr->aPoint); + pStmt = pCsr->pReadAux; + memset(pCsr, 0, sizeof(RtreeCursor)); + pCsr->base.pVtab = (sqlite3_vtab*)pRtree; + pCsr->pReadAux = pStmt; + + /* The following will only fail if the previous sqlite3_step() call failed, + ** in which case the error has already been caught. This statement never + ** encounters an error within an sqlite3_column_xxx() function, as it + ** calls sqlite3_column_value(), which does not use malloc(). So it is safe + ** to ignore the error code here. */ + sqlite3_reset(pStmt); } -/* +/* ** Rtree virtual table module xClose method. */ static int rtreeClose(sqlite3_vtab_cursor *cur){ Rtree *pRtree = (Rtree *)(cur->pVtab); - int ii; RtreeCursor *pCsr = (RtreeCursor *)cur; assert( pRtree->nCursor>0 ); - freeCursorConstraints(pCsr); + resetCursor(pCsr); sqlite3_finalize(pCsr->pReadAux); - sqlite3_free(pCsr->aPoint); - for(ii=0; iiaNode[ii]); sqlite3_free(pCsr); pRtree->nCursor--; - nodeBlobReset(pRtree); + if( pRtree->nCursor==0 && pRtree->inWrTrans==0 ){ + nodeBlobReset(pRtree); + } return SQLITE_OK; } /* ** Rtree virtual table module xEof method. ** -** Return non-zero if the cursor does not currently point to a valid +** Return non-zero if the cursor does not currently point to a valid ** record (i.e if the scan has finished), or zero otherwise. */ static int rtreeEof(sqlite3_vtab_cursor *cur){ @@ -182297,7 +219710,7 @@ static int rtreeEof(sqlite3_vtab_cursor *cur){ /* ** Check the RTree node or entry given by pCellData and p against the MATCH -** constraint pConstraint. +** constraint pConstraint. */ static int rtreeCallbackConstraint( RtreeConstraint *pConstraint, /* The constraint to test */ @@ -182370,7 +219783,7 @@ static int rtreeCallbackConstraint( return rc; } -/* +/* ** Check the internal RTree node given by pCellData against constraint p. ** If this constraint cannot be satisfied by any child within the node, ** set *peWithin to NOT_WITHIN. @@ -182388,24 +219801,36 @@ static void rtreeNonleafConstraint( */ pCellData += 8 + 4*(p->iCoord&0xfe); - assert(p->op==RTREE_LE || p->op==RTREE_LT || p->op==RTREE_GE - || p->op==RTREE_GT || p->op==RTREE_EQ ); - assert( ((((char*)pCellData) - (char*)0)&3)==0 ); /* 4-byte aligned */ + assert(p->op==RTREE_LE || p->op==RTREE_LT || p->op==RTREE_GE + || p->op==RTREE_GT || p->op==RTREE_EQ || p->op==RTREE_TRUE + || p->op==RTREE_FALSE ); + assert( FOUR_BYTE_ALIGNED(pCellData) ); switch( p->op ){ + case RTREE_TRUE: return; /* Always satisfied */ + case RTREE_FALSE: break; /* Never satisfied */ + case RTREE_EQ: + RTREE_DECODE_COORD(eInt, pCellData, val); + /* val now holds the lower bound of the coordinate pair */ + if( p->u.rValue>=val ){ + pCellData += 4; + RTREE_DECODE_COORD(eInt, pCellData, val); + /* val now holds the upper bound of the coordinate pair */ + if( p->u.rValue<=val ) return; + } + break; case RTREE_LE: case RTREE_LT: - case RTREE_EQ: RTREE_DECODE_COORD(eInt, pCellData, val); /* val now holds the lower bound of the coordinate pair */ if( p->u.rValue>=val ) return; - if( p->op!=RTREE_EQ ) break; /* RTREE_LE and RTREE_LT end here */ - /* Fall through for the RTREE_EQ case */ + break; - default: /* RTREE_GT or RTREE_GE, or fallthrough of RTREE_EQ */ + default: pCellData += 4; RTREE_DECODE_COORD(eInt, pCellData, val); /* val now holds the upper bound of the coordinate pair */ if( p->u.rValue<=val ) return; + break; } *peWithin = NOT_WITHIN; } @@ -182428,34 +219853,37 @@ static void rtreeLeafConstraint( ){ RtreeDValue xN; /* Coordinate value converted to a double */ - assert(p->op==RTREE_LE || p->op==RTREE_LT || p->op==RTREE_GE - || p->op==RTREE_GT || p->op==RTREE_EQ ); + assert(p->op==RTREE_LE || p->op==RTREE_LT || p->op==RTREE_GE + || p->op==RTREE_GT || p->op==RTREE_EQ || p->op==RTREE_TRUE + || p->op==RTREE_FALSE ); pCellData += 8 + p->iCoord*4; - assert( ((((char*)pCellData) - (char*)0)&3)==0 ); /* 4-byte aligned */ + assert( FOUR_BYTE_ALIGNED(pCellData) ); RTREE_DECODE_COORD(eInt, pCellData, xN); switch( p->op ){ - case RTREE_LE: if( xN <= p->u.rValue ) return; break; - case RTREE_LT: if( xN < p->u.rValue ) return; break; - case RTREE_GE: if( xN >= p->u.rValue ) return; break; - case RTREE_GT: if( xN > p->u.rValue ) return; break; - default: if( xN == p->u.rValue ) return; break; + case RTREE_TRUE: return; /* Always satisfied */ + case RTREE_FALSE: break; /* Never satisfied */ + case RTREE_LE: if( xN <= p->u.rValue ) return; break; + case RTREE_LT: if( xN < p->u.rValue ) return; break; + case RTREE_GE: if( xN >= p->u.rValue ) return; break; + case RTREE_GT: if( xN > p->u.rValue ) return; break; + default: if( xN == p->u.rValue ) return; break; } *peWithin = NOT_WITHIN; } /* -** One of the cells in node pNode is guaranteed to have a 64-bit +** One of the cells in node pNode is guaranteed to have a 64-bit ** integer value equal to iRowid. Return the index of this cell. */ static int nodeRowidIndex( - Rtree *pRtree, - RtreeNode *pNode, + Rtree *pRtree, + RtreeNode *pNode, i64 iRowid, int *piIndex ){ int ii; int nCell = NCELL(pNode); - assert( nCell<200 ); + assert( nCell<65536 && nCell>=0 ); for(ii=0; iipParent; - if( pParent ){ + if( ALWAYS(pParent) ){ return nodeRowidIndex(pRtree, pParent, pNode->iNode, piIndex); + }else{ + *piIndex = -1; + return SQLITE_OK; } - *piIndex = -1; - return SQLITE_OK; } /* @@ -182591,7 +220020,7 @@ static RtreeSearchPoint *rtreeSearchPointNew( pFirst = rtreeSearchPointFirst(pCur); pCur->anQueue[iLevel]++; if( pFirst==0 - || pFirst->rScore>rScore + || pFirst->rScore>rScore || (pFirst->rScore==rScore && pFirst->iLevel>iLevel) ){ if( pCur->bPoint ){ @@ -182599,7 +220028,8 @@ static RtreeSearchPoint *rtreeSearchPointNew( pNew = rtreeEnqueue(pCur, rScore, iLevel); if( pNew==0 ) return 0; ii = (int)(pNew - pCur->aPoint) + 1; - if( iiaNode[ii]==0 ); pCur->aNode[ii] = pCur->aNode[0]; }else{ @@ -182660,7 +220090,7 @@ static void rtreeSearchPointPop(RtreeCursor *p){ if( p->bPoint ){ p->anQueue[p->sPoint.iLevel]--; p->bPoint = 0; - }else if( p->nPoint ){ + }else if( ALWAYS(p->nPoint) ){ p->anQueue[p->aPoint[0].iLevel]--; n = --p->nPoint; p->aPoint[0] = p->aPoint[n]; @@ -182711,13 +220141,17 @@ static int rtreeStepToLeaf(RtreeCursor *pCur){ eInt = pRtree->eCoordType==RTREE_COORD_INT32; while( (p = rtreeSearchPointFirst(pCur))!=0 && p->iLevel>0 ){ + u8 *pCellData; pNode = rtreeNodeOfFirstSearchPoint(pCur, &rc); if( rc ) return rc; nCell = NCELL(pNode); - assert( nCell<200 ); + if( nCell>RTREE_MAXCELLS ){ + RTREE_IS_CORRUPT(pRtree); + return SQLITE_CORRUPT_VTAB; + } + pCellData = pNode->zData + (4+pRtree->nBytesPerCell*p->iCell); while( p->iCellzData + (4+pRtree->nBytesPerCell*p->iCell); eWithin = FULLY_WITHIN; for(ii=0; iiaConstraint + ii; @@ -182730,13 +220164,23 @@ static int rtreeStepToLeaf(RtreeCursor *pCur){ }else{ rtreeNonleafConstraint(pConstraint, eInt, pCellData, &eWithin); } - if( eWithin==NOT_WITHIN ) break; + if( eWithin==NOT_WITHIN ){ + p->iCell++; + pCellData += pRtree->nBytesPerCell; + break; + } } - p->iCell++; if( eWithin==NOT_WITHIN ) continue; + p->iCell++; x.iLevel = p->iLevel - 1; if( x.iLevel ){ x.id = readInt64(pCellData); + for(ii=0; iinPoint; ii++){ + if( pCur->aPoint[ii].id==x.id ){ + RTREE_IS_CORRUPT(pRtree); + return SQLITE_CORRUPT_VTAB; + } + } x.iCell = 0; }else{ x.id = p->id; @@ -182764,7 +220208,7 @@ static int rtreeStepToLeaf(RtreeCursor *pCur){ return SQLITE_OK; } -/* +/* ** Rtree virtual table module xNext method. */ static int rtreeNext(sqlite3_vtab_cursor *pVtabCursor){ @@ -182782,7 +220226,7 @@ static int rtreeNext(sqlite3_vtab_cursor *pVtabCursor){ return rc; } -/* +/* ** Rtree virtual table module xRowid method. */ static int rtreeRowid(sqlite3_vtab_cursor *pVtabCursor, sqlite_int64 *pRowid){ @@ -182790,13 +220234,17 @@ static int rtreeRowid(sqlite3_vtab_cursor *pVtabCursor, sqlite_int64 *pRowid){ RtreeSearchPoint *p = rtreeSearchPointFirst(pCsr); int rc = SQLITE_OK; RtreeNode *pNode = rtreeNodeOfFirstSearchPoint(pCsr, &rc); - if( rc==SQLITE_OK && p ){ - *pRowid = nodeGetRowid(RTREE_OF_CURSOR(pCsr), pNode, p->iCell); + if( rc==SQLITE_OK && ALWAYS(p) ){ + if( p->iCell>=NCELL(pNode) ){ + rc = SQLITE_ABORT; + }else{ + *pRowid = nodeGetRowid(RTREE_OF_CURSOR(pCsr), pNode, p->iCell); + } } return rc; } -/* +/* ** Rtree virtual table module xColumn method. */ static int rtreeColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){ @@ -182808,7 +220256,8 @@ static int rtreeColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){ RtreeNode *pNode = rtreeNodeOfFirstSearchPoint(pCsr, &rc); if( rc ) return rc; - if( p==0 ) return SQLITE_OK; + if( NEVER(p==0) ) return SQLITE_OK; + if( p->iCell>=NCELL(pNode) ) return SQLITE_ABORT; if( i==0 ){ sqlite3_result_int64(ctx, nodeGetRowid(pRtree, pNode, p->iCell)); }else if( i<=pRtree->nDim2 ){ @@ -182829,7 +220278,7 @@ static int rtreeColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){ &pCsr->pReadAux, 0); if( rc ) return rc; } - sqlite3_bind_int64(pCsr->pReadAux, 1, + sqlite3_bind_int64(pCsr->pReadAux, 1, nodeGetRowid(pRtree, pNode, p->iCell)); rc = sqlite3_step(pCsr->pReadAux); if( rc==SQLITE_ROW ){ @@ -182842,12 +220291,12 @@ static int rtreeColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){ } sqlite3_result_value(ctx, sqlite3_column_value(pCsr->pReadAux, i - pRtree->nDim2 + 1)); - } + } return SQLITE_OK; } -/* -** Use nodeAcquire() to obtain the leaf node containing the record with +/* +** Use nodeAcquire() to obtain the leaf node containing the record with ** rowid iRowid. If successful, set *ppLeaf to point to the node and ** return SQLITE_OK. If there is no such record in the table, set ** *ppLeaf to 0 and return SQLITE_OK. If an error occurs, set *ppLeaf @@ -182906,11 +220355,13 @@ static int deserializeGeometry(sqlite3_value *pValue, RtreeConstraint *pCons){ return SQLITE_OK; } -/* +SQLITE_PRIVATE int sqlite3IntFloatCompare(i64,double); + +/* ** Rtree virtual table module xFilter method. */ static int rtreeFilter( - sqlite3_vtab_cursor *pVtabCursor, + sqlite3_vtab_cursor *pVtabCursor, int idxNum, const char *idxStr, int argc, sqlite3_value **argv ){ @@ -182920,17 +220371,11 @@ static int rtreeFilter( int ii; int rc = SQLITE_OK; int iCell = 0; - sqlite3_stmt *pStmt; rtreeReference(pRtree); /* Reset the cursor to the same state as rtreeOpen() leaves it in. */ - freeCursorConstraints(pCsr); - sqlite3_free(pCsr->aPoint); - pStmt = pCsr->pReadAux; - memset(pCsr, 0, sizeof(RtreeCursor)); - pCsr->base.pVtab = (sqlite3_vtab*)pRtree; - pCsr->pReadAux = pStmt; + resetCursor(pCsr); pCsr->iStrategy = idxNum; if( idxNum==1 ){ @@ -182939,7 +220384,16 @@ static int rtreeFilter( RtreeSearchPoint *p; /* Search point for the leaf */ i64 iRowid = sqlite3_value_int64(argv[0]); i64 iNode = 0; - rc = findLeafNode(pRtree, iRowid, &pLeaf, &iNode); + int eType = sqlite3_value_numeric_type(argv[0]); + if( eType==SQLITE_INTEGER + || (eType==SQLITE_FLOAT + && 0==sqlite3IntFloatCompare(iRowid,sqlite3_value_double(argv[0]))) + ){ + rc = findLeafNode(pRtree, iRowid, &pLeaf, &iNode); + }else{ + rc = SQLITE_OK; + pLeaf = 0; + } if( rc==SQLITE_OK && pLeaf!=0 ){ p = rtreeSearchPointNew(pCsr, RTREE_ZERO, 0); assert( p!=0 ); /* Always returns pCsr->sPoint */ @@ -182953,8 +220407,8 @@ static int rtreeFilter( pCsr->atEOF = 1; } }else{ - /* Normal case - r-tree scan. Set up the RtreeCursor.aConstraint array - ** with the configured constraints. + /* Normal case - r-tree scan. Set up the RtreeCursor.aConstraint array + ** with the configured constraints. */ rc = nodeAcquire(pRtree, 1, 0, &pRoot); if( rc==SQLITE_OK && argc>0 ){ @@ -182969,6 +220423,7 @@ static int rtreeFilter( || (idxStr && (int)strlen(idxStr)==argc*2) ); for(ii=0; iiaConstraint[ii]; + int eType = sqlite3_value_numeric_type(argv[ii]); p->op = idxStr[ii*2]; p->iCoord = idxStr[ii*2+1]-'0'; if( p->op>=RTREE_MATCH ){ @@ -182983,20 +220438,45 @@ static int rtreeFilter( p->pInfo->nCoord = pRtree->nDim2; p->pInfo->anQueue = pCsr->anQueue; p->pInfo->mxLevel = pRtree->iDepth + 1; - }else{ + }else if( eType==SQLITE_INTEGER ){ + sqlite3_int64 iVal = sqlite3_value_int64(argv[ii]); +#ifdef SQLITE_RTREE_INT_ONLY + p->u.rValue = iVal; +#else + p->u.rValue = (double)iVal; + if( iVal>=((sqlite3_int64)1)<<48 + || iVal<=-(((sqlite3_int64)1)<<48) + ){ + if( p->op==RTREE_LT ) p->op = RTREE_LE; + if( p->op==RTREE_GT ) p->op = RTREE_GE; + } +#endif + }else if( eType==SQLITE_FLOAT ){ #ifdef SQLITE_RTREE_INT_ONLY p->u.rValue = sqlite3_value_int64(argv[ii]); #else p->u.rValue = sqlite3_value_double(argv[ii]); #endif + }else{ + p->u.rValue = RTREE_ZERO; + if( eType==SQLITE_NULL ){ + p->op = RTREE_FALSE; + }else if( p->op==RTREE_LT || p->op==RTREE_LE ){ + p->op = RTREE_TRUE; + }else{ + p->op = RTREE_FALSE; + } } } } } if( rc==SQLITE_OK ){ RtreeSearchPoint *pNew; + assert( pCsr->bPoint==0 ); /* Due to the resetCursor() call above */ pNew = rtreeSearchPointNew(pCsr, RTREE_ZERO, (u8)(pRtree->iDepth+1)); - if( pNew==0 ) return SQLITE_NOMEM; + if( NEVER(pNew==0) ){ /* Because pCsr->bPoint was FALSE */ + return SQLITE_NOMEM; + } pNew->id = 1; pNew->iCell = 0; pNew->eWithin = PARTLY_WITHIN; @@ -183015,7 +220495,7 @@ static int rtreeFilter( /* ** Rtree virtual table module xBestIndex method. There are three -** table scan strategies to choose from (in order from most to +** table scan strategies to choose from (in order from most to ** least desirable): ** ** idxNum idxStr Strategy @@ -183025,8 +220505,8 @@ static int rtreeFilter( ** ------------------------------------------------ ** ** If strategy 1 is used, then idxStr is not meaningful. If strategy -** 2 is used, idxStr is formatted to contain 2 bytes for each -** constraint used. The first two bytes of idxStr correspond to +** 2 is used, idxStr is formatted to contain 2 bytes for each +** constraint used. The first two bytes of idxStr correspond to ** the constraint in sqlite3_index_info.aConstraintUsage[] with ** (argvIndex==1) etc. ** @@ -183072,8 +220552,8 @@ static int rtreeBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ for(ii=0; iinConstraint && iIdx<(int)(sizeof(zIdxStr)-1); ii++){ struct sqlite3_index_constraint *p = &pIdxInfo->aConstraint[ii]; - if( bMatch==0 && p->usable - && p->iColumn==0 && p->op==SQLITE_INDEX_CONSTRAINT_EQ + if( bMatch==0 && p->usable + && p->iColumn<=0 && p->op==SQLITE_INDEX_CONSTRAINT_EQ ){ /* We have an equality constraint on the rowid. Use strategy 1. */ int jj; @@ -183086,11 +220566,11 @@ static int rtreeBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ pIdxInfo->aConstraintUsage[jj].omit = 1; /* This strategy involves a two rowid lookups on an B-Tree structures - ** and then a linear search of an R-Tree node. This should be - ** considered almost as quick as a direct rowid lookup (for which + ** and then a linear search of an R-Tree node. This should be + ** considered almost as quick as a direct rowid lookup (for which ** sqlite uses an internal cost of 0.0). It is expected to return ** a single row. - */ + */ pIdxInfo->estimatedCost = 30.0; pIdxInfo->estimatedRows = 1; pIdxInfo->idxFlags = SQLITE_INDEX_SCAN_UNIQUE; @@ -183102,11 +220582,12 @@ static int rtreeBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ || p->op==SQLITE_INDEX_CONSTRAINT_MATCH) ){ u8 op; + u8 doOmit = 1; switch( p->op ){ - case SQLITE_INDEX_CONSTRAINT_EQ: op = RTREE_EQ; break; - case SQLITE_INDEX_CONSTRAINT_GT: op = RTREE_GT; break; + case SQLITE_INDEX_CONSTRAINT_EQ: op = RTREE_EQ; doOmit = 0; break; + case SQLITE_INDEX_CONSTRAINT_GT: op = RTREE_GT; doOmit = 0; break; case SQLITE_INDEX_CONSTRAINT_LE: op = RTREE_LE; break; - case SQLITE_INDEX_CONSTRAINT_LT: op = RTREE_LT; break; + case SQLITE_INDEX_CONSTRAINT_LT: op = RTREE_LT; doOmit = 0; break; case SQLITE_INDEX_CONSTRAINT_GE: op = RTREE_GE; break; case SQLITE_INDEX_CONSTRAINT_MATCH: op = RTREE_MATCH; break; default: op = 0; break; @@ -183115,15 +220596,19 @@ static int rtreeBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ zIdxStr[iIdx++] = op; zIdxStr[iIdx++] = (char)(p->iColumn - 1 + '0'); pIdxInfo->aConstraintUsage[ii].argvIndex = (iIdx/2); - pIdxInfo->aConstraintUsage[ii].omit = 1; + pIdxInfo->aConstraintUsage[ii].omit = doOmit; } } } pIdxInfo->idxNum = 2; pIdxInfo->needToFreeIdxStr = 1; - if( iIdx>0 && 0==(pIdxInfo->idxStr = sqlite3_mprintf("%s", zIdxStr)) ){ - return SQLITE_NOMEM; + if( iIdx>0 ){ + pIdxInfo->idxStr = sqlite3_malloc( iIdx+1 ); + if( pIdxInfo->idxStr==0 ){ + return SQLITE_NOMEM; + } + memcpy(pIdxInfo->idxStr, zIdxStr, iIdx+1); } nRow = pRtree->nRowEst >> (iIdx/2); @@ -183134,7 +220619,7 @@ static int rtreeBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ } /* -** Return the N-dimensional volumn of the cell stored in *p. +** Return the N-dimensional volume of the cell stored in *p. */ static RtreeDValue cellArea(Rtree *pRtree, RtreeCell *p){ RtreeDValue area = (RtreeDValue)1; @@ -183202,35 +220687,26 @@ static void cellUnion(Rtree *pRtree, RtreeCell *p1, RtreeCell *p2){ */ static int cellContains(Rtree *pRtree, RtreeCell *p1, RtreeCell *p2){ int ii; - int isInt = (pRtree->eCoordType==RTREE_COORD_INT32); - for(ii=0; iinDim2; ii+=2){ - RtreeCoord *a1 = &p1->aCoord[ii]; - RtreeCoord *a2 = &p2->aCoord[ii]; - if( (!isInt && (a2[0].fa1[1].f)) - || ( isInt && (a2[0].ia1[1].i)) - ){ - return 0; + if( pRtree->eCoordType==RTREE_COORD_INT32 ){ + for(ii=0; iinDim2; ii+=2){ + RtreeCoord *a1 = &p1->aCoord[ii]; + RtreeCoord *a2 = &p2->aCoord[ii]; + if( a2[0].ia1[1].i ) return 0; + } + }else{ + for(ii=0; iinDim2; ii+=2){ + RtreeCoord *a1 = &p1->aCoord[ii]; + RtreeCoord *a2 = &p2->aCoord[ii]; + if( a2[0].fa1[1].f ) return 0; } } return 1; } -/* -** Return the amount cell p would grow by if it were unioned with pCell. -*/ -static RtreeDValue cellGrowth(Rtree *pRtree, RtreeCell *p, RtreeCell *pCell){ - RtreeDValue area; - RtreeCell cell; - memcpy(&cell, p, sizeof(RtreeCell)); - area = cellArea(pRtree, &cell); - cellUnion(pRtree, &cell, pCell); - return (cellArea(pRtree, &cell)-area); -} - static RtreeDValue cellOverlap( - Rtree *pRtree, - RtreeCell *p, - RtreeCell *aCell, + Rtree *pRtree, + RtreeCell *p, + RtreeCell *aCell, int nCell ){ int ii; @@ -183273,38 +220749,52 @@ static int ChooseLeaf( for(ii=0; rc==SQLITE_OK && ii<(pRtree->iDepth-iHeight); ii++){ int iCell; sqlite3_int64 iBest = 0; - + int bFound = 0; RtreeDValue fMinGrowth = RTREE_ZERO; RtreeDValue fMinArea = RTREE_ZERO; - int nCell = NCELL(pNode); - RtreeCell cell; - RtreeNode *pChild; - - RtreeCell *aCell = 0; + RtreeNode *pChild = 0; - /* Select the child node which will be enlarged the least if pCell - ** is inserted into it. Resolve ties by choosing the entry with - ** the smallest area. + /* First check to see if there is are any cells in pNode that completely + ** contains pCell. If two or more cells in pNode completely contain pCell + ** then pick the smallest. */ for(iCell=0; iCellpParent ){ RtreeNode *pParent = p->pParent; RtreeCell cell; int iCell; - if( (++cnt)>1000 || nodeParentIndex(pRtree, p, &iCell) ){ + cnt++; + if( cnt>100 ){ + RTREE_IS_CORRUPT(pRtree); + return SQLITE_CORRUPT_VTAB; + } + rc = nodeParentIndex(pRtree, p, &iCell); + if( NEVER(rc!=SQLITE_OK) ){ RTREE_IS_CORRUPT(pRtree); return SQLITE_CORRUPT_VTAB; } @@ -183341,7 +220838,7 @@ static int AdjustTree( cellUnion(pRtree, &cell, pCell); nodeOverwriteCell(pRtree, pParent, &cell, iCell); } - + p = pParent; } return SQLITE_OK; @@ -183370,81 +220867,10 @@ static int parentWrite(Rtree *pRtree, sqlite3_int64 iNode, sqlite3_int64 iPar){ static int rtreeInsertCell(Rtree *, RtreeNode *, RtreeCell *, int); -/* -** Arguments aIdx, aDistance and aSpare all point to arrays of size -** nIdx. The aIdx array contains the set of integers from 0 to -** (nIdx-1) in no particular order. This function sorts the values -** in aIdx according to the indexed values in aDistance. For -** example, assuming the inputs: -** -** aIdx = { 0, 1, 2, 3 } -** aDistance = { 5.0, 2.0, 7.0, 6.0 } -** -** this function sets the aIdx array to contain: -** -** aIdx = { 0, 1, 2, 3 } -** -** The aSpare array is used as temporary working space by the -** sorting algorithm. -*/ -static void SortByDistance( - int *aIdx, - int nIdx, - RtreeDValue *aDistance, - int *aSpare -){ - if( nIdx>1 ){ - int iLeft = 0; - int iRight = 0; - - int nLeft = nIdx/2; - int nRight = nIdx-nLeft; - int *aLeft = aIdx; - int *aRight = &aIdx[nLeft]; - - SortByDistance(aLeft, nLeft, aDistance, aSpare); - SortByDistance(aRight, nRight, aDistance, aSpare); - - memcpy(aSpare, aLeft, sizeof(int)*nLeft); - aLeft = aSpare; - - while( iLeft1 ){ @@ -183555,8 +220981,8 @@ static int splitNodeStartree( int nLeft; for( - nLeft=RTREE_MINCELLS(pRtree); - nLeft<=(nCell-RTREE_MINCELLS(pRtree)); + nLeft=RTREE_MINCELLS(pRtree); + nLeft<=(nCell-RTREE_MINCELLS(pRtree)); nLeft++ ){ RtreeCell left; @@ -183611,21 +221037,26 @@ static int splitNodeStartree( static int updateMapping( - Rtree *pRtree, - i64 iRowid, - RtreeNode *pNode, + Rtree *pRtree, + i64 iRowid, + RtreeNode *pNode, int iHeight ){ int (*xSetMapping)(Rtree *, sqlite3_int64, sqlite3_int64); xSetMapping = ((iHeight==0)?rowidWrite:parentWrite); if( iHeight>0 ){ RtreeNode *pChild = nodeHashLookup(pRtree, iRowid); + RtreeNode *p; + for(p=pNode; p; p=p->pParent){ + if( p==pChild ) return SQLITE_CORRUPT_VTAB; + } if( pChild ){ nodeRelease(pRtree, pChild->pParent); nodeReference(pNode); pChild->pParent = pNode; } } + if( NEVER(pNode==0) ) return SQLITE_ERROR; return xSetMapping(pRtree, iRowid, pNode->iNode); } @@ -183649,7 +221080,7 @@ static int SplitNode( RtreeCell leftbbox; RtreeCell rightbbox; - /* Allocate an array and populate it with a copy of pCell and + /* Allocate an array and populate it with a copy of pCell and ** all cells from node pLeft. Then zero the original node. */ aCell = sqlite3_malloc64((sizeof(RtreeCell)+sizeof(int))*(nCell+1)); @@ -183715,11 +221146,12 @@ static int SplitNode( RtreeNode *pParent = pLeft->pParent; int iCell; rc = nodeParentIndex(pRtree, pLeft, &iCell); - if( rc==SQLITE_OK ){ + if( ALWAYS(rc==SQLITE_OK) ){ nodeOverwriteCell(pRtree, pParent, &leftbbox, iCell); rc = AdjustTree(pRtree, pParent, &leftbbox); + assert( rc==SQLITE_OK ); } - if( rc!=SQLITE_OK ){ + if( NEVER(rc!=SQLITE_OK) ){ goto splitnode_out; } } @@ -183749,15 +221181,6 @@ static int SplitNode( rc = updateMapping(pRtree, pCell->iRowid, pLeft, iHeight); } - if( rc==SQLITE_OK ){ - rc = nodeRelease(pRtree, pRight); - pRight = 0; - } - if( rc==SQLITE_OK ){ - rc = nodeRelease(pRtree, pLeft); - pLeft = 0; - } - splitnode_out: nodeRelease(pRtree, pRight); nodeRelease(pRtree, pLeft); @@ -183766,14 +221189,14 @@ static int SplitNode( } /* -** If node pLeaf is not the root of the r-tree and its pParent pointer is +** If node pLeaf is not the root of the r-tree and its pParent pointer is ** still NULL, load all ancestor nodes of pLeaf into memory and populate ** the pLeaf->pParent chain all the way up to the root node. ** ** This operation is required when a row is deleted (or updated - an update ** is implemented as a delete followed by an insert). SQLite provides the ** rowid of the row to delete, which can be used to find the leaf on which -** the entry resides (argument pLeaf). Once the leaf is located, this +** the entry resides (argument pLeaf). Once the leaf is located, this ** function is called to determine its ancestry. */ static int fixLeafParent(Rtree *pRtree, RtreeNode *pLeaf){ @@ -183794,7 +221217,7 @@ static int fixLeafParent(Rtree *pRtree, RtreeNode *pLeaf){ */ iNode = sqlite3_column_int64(pRtree->pReadParent, 0); for(pTest=pLeaf; pTest && pTest->iNode!=iNode; pTest=pTest->pParent); - if( !pTest ){ + if( pTest==0 ){ rc2 = nodeAcquire(pRtree, iNode, 0, &pChild->pParent); } } @@ -183825,6 +221248,7 @@ static int removeNode(Rtree *pRtree, RtreeNode *pNode, int iHeight){ pParent = pNode->pParent; pNode->pParent = 0; rc = deleteCell(pRtree, pParent, iCell, iHeight+1); + testcase( rc!=SQLITE_OK ); } rc2 = nodeRelease(pRtree, pParent); if( rc==SQLITE_OK ){ @@ -183847,7 +221271,7 @@ static int removeNode(Rtree *pRtree, RtreeNode *pNode, int iHeight){ if( SQLITE_OK!=(rc = sqlite3_reset(pRtree->pDeleteParent)) ){ return rc; } - + /* Remove the node from the in-memory hash table and link it into ** the Rtree.pDeleted list. Its contents will be re-inserted later on. */ @@ -183862,9 +221286,9 @@ static int removeNode(Rtree *pRtree, RtreeNode *pNode, int iHeight){ static int fixBoundingBox(Rtree *pRtree, RtreeNode *pNode){ RtreeNode *pParent = pNode->pParent; - int rc = SQLITE_OK; + int rc = SQLITE_OK; if( pParent ){ - int ii; + int ii; int nCell = NCELL(pNode); RtreeCell box; /* Bounding box for pNode */ nodeGetCell(pRtree, pNode, 0, &box); @@ -183918,109 +221342,8 @@ static int deleteCell(Rtree *pRtree, RtreeNode *pNode, int iCell, int iHeight){ return rc; } -static int Reinsert( - Rtree *pRtree, - RtreeNode *pNode, - RtreeCell *pCell, - int iHeight -){ - int *aOrder; - int *aSpare; - RtreeCell *aCell; - RtreeDValue *aDistance; - int nCell; - RtreeDValue aCenterCoord[RTREE_MAX_DIMENSIONS]; - int iDim; - int ii; - int rc = SQLITE_OK; - int n; - - memset(aCenterCoord, 0, sizeof(RtreeDValue)*RTREE_MAX_DIMENSIONS); - - nCell = NCELL(pNode)+1; - n = (nCell+1)&(~1); - - /* Allocate the buffers used by this operation. The allocation is - ** relinquished before this function returns. - */ - aCell = (RtreeCell *)sqlite3_malloc64(n * ( - sizeof(RtreeCell) + /* aCell array */ - sizeof(int) + /* aOrder array */ - sizeof(int) + /* aSpare array */ - sizeof(RtreeDValue) /* aDistance array */ - )); - if( !aCell ){ - return SQLITE_NOMEM; - } - aOrder = (int *)&aCell[n]; - aSpare = (int *)&aOrder[n]; - aDistance = (RtreeDValue *)&aSpare[n]; - - for(ii=0; iinDim; iDim++){ - aCenterCoord[iDim] += DCOORD(aCell[ii].aCoord[iDim*2]); - aCenterCoord[iDim] += DCOORD(aCell[ii].aCoord[iDim*2+1]); - } - } - for(iDim=0; iDimnDim; iDim++){ - aCenterCoord[iDim] = (aCenterCoord[iDim]/(nCell*(RtreeDValue)2)); - } - - for(ii=0; iinDim; iDim++){ - RtreeDValue coord = (DCOORD(aCell[ii].aCoord[iDim*2+1]) - - DCOORD(aCell[ii].aCoord[iDim*2])); - aDistance[ii] += (coord-aCenterCoord[iDim])*(coord-aCenterCoord[iDim]); - } - } - - SortByDistance(aOrder, nCell, aDistance, aSpare); - nodeZero(pRtree, pNode); - - for(ii=0; rc==SQLITE_OK && ii<(nCell-(RTREE_MINCELLS(pRtree)+1)); ii++){ - RtreeCell *p = &aCell[aOrder[ii]]; - nodeInsertCell(pRtree, pNode, p); - if( p->iRowid==pCell->iRowid ){ - if( iHeight==0 ){ - rc = rowidWrite(pRtree, p->iRowid, pNode->iNode); - }else{ - rc = parentWrite(pRtree, p->iRowid, pNode->iNode); - } - } - } - if( rc==SQLITE_OK ){ - rc = fixBoundingBox(pRtree, pNode); - } - for(; rc==SQLITE_OK && iiiNode currently contains - ** the height of the sub-tree headed by the cell. - */ - RtreeNode *pInsert; - RtreeCell *p = &aCell[aOrder[ii]]; - rc = ChooseLeaf(pRtree, p, iHeight, &pInsert); - if( rc==SQLITE_OK ){ - int rc2; - rc = rtreeInsertCell(pRtree, pInsert, p, iHeight); - rc2 = nodeRelease(pRtree, pInsert); - if( rc==SQLITE_OK ){ - rc = rc2; - } - } - } - - sqlite3_free(aCell); - return rc; -} - /* -** Insert cell pCell into node pNode. Node pNode is the head of a +** Insert cell pCell into node pNode. Node pNode is the head of a ** subtree iHeight high (leaf nodes have iHeight==0). */ static int rtreeInsertCell( @@ -184039,12 +221362,7 @@ static int rtreeInsertCell( } } if( nodeInsertCell(pRtree, pNode, pCell) ){ - if( iHeight<=pRtree->iReinsertHeight || pNode->iNode==1){ - rc = SplitNode(pRtree, pNode, pCell, iHeight); - }else{ - pRtree->iReinsertHeight = iHeight; - rc = Reinsert(pRtree, pNode, pCell, iHeight); - } + rc = SplitNode(pRtree, pNode, pCell, iHeight); }else{ rc = AdjustTree(pRtree, pNode, pCell); if( rc==SQLITE_OK ){ @@ -184110,8 +221428,8 @@ static int rtreeDeleteRowid(Rtree *pRtree, sqlite3_int64 iDelete){ /* Obtain a reference to the root node to initialize Rtree.iDepth */ rc = nodeAcquire(pRtree, 1, 0, &pRoot); - /* Obtain a reference to the leaf node that contains the entry - ** about to be deleted. + /* Obtain a reference to the leaf node that contains the entry + ** about to be deleted. */ if( rc==SQLITE_OK ){ rc = findLeafNode(pRtree, iDelete, &pLeaf, 0); @@ -184142,18 +221460,18 @@ static int rtreeDeleteRowid(Rtree *pRtree, sqlite3_int64 iDelete){ } /* Check if the root node now has exactly one child. If so, remove - ** it, schedule the contents of the child for reinsertion and + ** it, schedule the contents of the child for reinsertion and ** reduce the tree height by one. ** ** This is equivalent to copying the contents of the child into - ** the root node (the operation that Gutman's paper says to perform + ** the root node (the operation that Gutman's paper says to perform ** in this scenario). */ if( rc==SQLITE_OK && pRtree->iDepth>0 && NCELL(pRoot)==1 ){ int rc2; RtreeNode *pChild = 0; i64 iChild = nodeGetRowid(pRtree, pRoot, 0); - rc = nodeAcquire(pRtree, iChild, pRoot, &pChild); + rc = nodeAcquire(pRtree, iChild, pRoot, &pChild); /* tag-20210916a */ if( rc==SQLITE_OK ){ rc = removeNode(pRtree, pChild, pRtree->iDepth-1); } @@ -184216,8 +221534,8 @@ static RtreeValue rtreeValueUp(sqlite3_value *v){ #endif /* !defined(SQLITE_RTREE_INT_ONLY) */ /* -** A constraint has failed while inserting a row into an rtree table. -** Assuming no OOM error occurs, this function sets the error message +** A constraint has failed while inserting a row into an rtree table. +** Assuming no OOM error occurs, this function sets the error message ** (at pRtree->base.zErrMsg) to an appropriate value and returns ** SQLITE_CONSTRAINT. ** @@ -184230,7 +221548,7 @@ static RtreeValue rtreeValueUp(sqlite3_value *v){ */ static int rtreeConstraintError(Rtree *pRtree, int iCol){ sqlite3_stmt *pStmt = 0; - char *zSql; + char *zSql; int rc; assert( iCol==0 || iCol%2 ); @@ -184267,9 +221585,9 @@ static int rtreeConstraintError(Rtree *pRtree, int iCol){ ** The xUpdate method for rtree module virtual tables. */ static int rtreeUpdate( - sqlite3_vtab *pVtab, - int nData, - sqlite3_value **aData, + sqlite3_vtab *pVtab, + int nData, + sqlite3_value **aData, sqlite_int64 *pRowid ){ Rtree *pRtree = (Rtree *)pVtab; @@ -184286,7 +221604,7 @@ static int rtreeUpdate( rtreeReference(pRtree); assert(nData>=1); - cell.iRowid = 0; /* Used only to suppress a compiler warning */ + memset(&cell, 0, sizeof(cell)); /* Constraint handling. A write operation on an r-tree table may return ** SQLITE_CONSTRAINT for two reasons: @@ -184336,7 +221654,7 @@ static int rtreeUpdate( } } - /* If a rowid value was supplied, check if it is already present in + /* If a rowid value was supplied, check if it is already present in ** the table. If so, the constraint has failed. */ if( sqlite3_value_type(aData[2])!=SQLITE_NULL ){ cell.iRowid = sqlite3_value_int64(aData[2]); @@ -184387,7 +221705,6 @@ static int rtreeUpdate( } if( rc==SQLITE_OK ){ int rc2; - pRtree->iReinsertHeight = -1; rc = rtreeInsertCell(pRtree, pLeaf, &cell, 0); rc2 = nodeRelease(pRtree, pLeaf); if( rc==SQLITE_OK ){ @@ -184417,7 +221734,7 @@ static int rtreeUpdate( static int rtreeBeginTransaction(sqlite3_vtab *pVtab){ Rtree *pRtree = (Rtree *)pVtab; assert( pRtree->inWrTrans==0 ); - pRtree->inWrTrans++; + pRtree->inWrTrans = 1; return SQLITE_OK; } @@ -184431,6 +221748,9 @@ static int rtreeEndTransaction(sqlite3_vtab *pVtab){ nodeBlobReset(pRtree); return SQLITE_OK; } +static int rtreeRollback(sqlite3_vtab *pVtab){ + return rtreeEndTransaction(pVtab); +} /* ** The xRename method for rtree module virtual tables. @@ -184442,8 +221762,8 @@ static int rtreeRename(sqlite3_vtab *pVtab, const char *zNewName){ "ALTER TABLE %Q.'%q_node' RENAME TO \"%w_node\";" "ALTER TABLE %Q.'%q_parent' RENAME TO \"%w_parent\";" "ALTER TABLE %Q.'%q_rowid' RENAME TO \"%w_rowid\";" - , pRtree->zDb, pRtree->zName, zNewName - , pRtree->zDb, pRtree->zName, zNewName + , pRtree->zDb, pRtree->zName, zNewName + , pRtree->zDb, pRtree->zName, zNewName , pRtree->zDb, pRtree->zName, zNewName ); if( zSql ){ @@ -184458,8 +221778,8 @@ static int rtreeRename(sqlite3_vtab *pVtab, const char *zNewName){ ** The xSavepoint method. ** ** This module does not need to do anything to support savepoints. However, -** it uses this hook to close any open blob handle. This is done because a -** DROP TABLE command - which fortunately always opens a savepoint - cannot +** it uses this hook to close any open blob handle. This is done because a +** DROP TABLE command - which fortunately always opens a savepoint - cannot ** succeed if there are any open blob handles. i.e. if the blob handle were ** not closed here, the following would fail: ** @@ -184488,7 +221808,7 @@ static int rtreeQueryStat1(sqlite3 *db, Rtree *pRtree){ char *zSql; sqlite3_stmt *p; int rc; - i64 nRow = 0; + i64 nRow = RTREE_MIN_ROWEST; rc = sqlite3_table_column_metadata( db, pRtree->zDb, "sqlite_stat1",0,0,0,0,0,0 @@ -184505,20 +221825,10 @@ static int rtreeQueryStat1(sqlite3 *db, Rtree *pRtree){ if( rc==SQLITE_OK ){ if( sqlite3_step(p)==SQLITE_ROW ) nRow = sqlite3_column_int64(p, 0); rc = sqlite3_finalize(p); - }else if( rc!=SQLITE_NOMEM ){ - rc = SQLITE_OK; - } - - if( rc==SQLITE_OK ){ - if( nRow==0 ){ - pRtree->nRowEst = RTREE_DEFAULT_ROWEST; - }else{ - pRtree->nRowEst = MAX(nRow, RTREE_MIN_ROWEST); - } } sqlite3_free(zSql); } - + pRtree->nRowEst = MAX(nRow, RTREE_MIN_ROWEST); return rc; } @@ -184538,8 +221848,11 @@ static int rtreeShadowName(const char *zName){ return 0; } +/* Forward declaration */ +static int rtreeIntegrity(sqlite3_vtab*, const char*, const char*, int, char**); + static sqlite3_module rtreeModule = { - 3, /* iVersion */ + 4, /* iVersion */ rtreeCreate, /* xCreate - create a table */ rtreeConnect, /* xConnect - connect to an existing table */ rtreeBestIndex, /* xBestIndex - Determine search strategy */ @@ -184556,20 +221869,21 @@ static sqlite3_module rtreeModule = { rtreeBeginTransaction, /* xBegin - begin transaction */ rtreeEndTransaction, /* xSync - sync transaction */ rtreeEndTransaction, /* xCommit - commit transaction */ - rtreeEndTransaction, /* xRollback - rollback transaction */ + rtreeRollback, /* xRollback - rollback transaction */ 0, /* xFindFunction - function overloading */ rtreeRename, /* xRename - rename the table */ rtreeSavepoint, /* xSavepoint */ 0, /* xRelease */ 0, /* xRollbackTo */ - rtreeShadowName /* xShadowName */ + rtreeShadowName, /* xShadowName */ + rtreeIntegrity /* xIntegrity */ }; static int rtreeSqlInit( - Rtree *pRtree, - sqlite3 *db, - const char *zDb, - const char *zPrefix, + Rtree *pRtree, + sqlite3 *db, + const char *zDb, + const char *zPrefix, int isCreate ){ int rc = SQLITE_OK; @@ -184649,13 +221963,13 @@ static int rtreeSqlInit( } zSql = sqlite3_mprintf(zFormat, zDb, zPrefix); if( zSql ){ - rc = sqlite3_prepare_v3(db, zSql, -1, f, appStmt[i], 0); + rc = sqlite3_prepare_v3(db, zSql, -1, f, appStmt[i], 0); }else{ rc = SQLITE_NOMEM; } sqlite3_free(zSql); } - if( pRtree->nAux ){ + if( pRtree->nAux && rc!=SQLITE_NOMEM ){ pRtree->zReadAuxSql = sqlite3_mprintf( "SELECT * FROM \"%w\".\"%w_rowid\" WHERE rowid=?1", zDb, zPrefix); @@ -184668,9 +221982,12 @@ static int rtreeSqlInit( sqlite3_str_appendf(p, "UPDATE \"%w\".\"%w_rowid\"SET ", zDb, zPrefix); for(ii=0; iinAux; ii++){ if( ii ) sqlite3_str_append(p, ",", 1); +#ifdef SQLITE_ENABLE_GEOPOLY if( iinAuxNotNull ){ sqlite3_str_appendf(p,"a%d=coalesce(?%d,a%d)",ii,ii+2,ii); - }else{ + }else +#endif + { sqlite3_str_appendf(p,"a%d=?%d",ii,ii+2); } } @@ -184679,7 +221996,7 @@ static int rtreeSqlInit( if( zSql==0 ){ rc = SQLITE_NOMEM; }else{ - rc = sqlite3_prepare_v3(db, zSql, -1, f, &pRtree->pWriteAux, 0); + rc = sqlite3_prepare_v3(db, zSql, -1, f, &pRtree->pWriteAux, 0); sqlite3_free(zSql); } } @@ -184720,9 +222037,9 @@ static int getIntFromStmt(sqlite3 *db, const char *zSql, int *piVal){ ** table already exists. In this case the node-size is determined by inspecting ** the root node of the tree. ** -** Otherwise, for an xCreate(), use 64 bytes less than the database page-size. -** This ensures that each node is stored on a single database page. If the -** database page-size is so large that more than RTREE_MAXCELLS entries +** Otherwise, for an xCreate(), use 64 bytes less than the database page-size. +** This ensures that each node is stored on a single database page. If the +** database page-size is so large that more than RTREE_MAXCELLS entries ** would fit in a single node, use a smaller node-size. */ static int getNodeSize( @@ -184765,7 +222082,15 @@ static int getNodeSize( return rc; } -/* +/* +** Return the length of a token +*/ +static int rtreeTokenLength(const char *z){ + int dummy = 0; + return sqlite3GetToken((const unsigned char*)z,&dummy); +} + +/* ** This function is the implementation of both the xConnect and xCreate ** methods of the r-tree virtual table. ** @@ -184800,29 +222125,34 @@ static int rtreeInit( "Auxiliary rtree columns must be last" /* 4 */ }; - assert( RTREE_MAX_AUX_COLUMN<256 ); /* Aux columns counted by a u8 */ - if( argc>RTREE_MAX_AUX_COLUMN+3 ){ - *pzErr = sqlite3_mprintf("%s", aErrMsg[3]); + assert( RTREE_MAX_AUX_COLUMN<256 ); + if( argc<6 || argc>RTREE_MAX_AUX_COLUMN+3 ){ + *pzErr = sqlite3_mprintf("%s", aErrMsg[2 + (argc>=6)]); return SQLITE_ERROR; } sqlite3_vtab_config(db, SQLITE_VTAB_CONSTRAINT_SUPPORT, 1); + sqlite3_vtab_config(db, SQLITE_VTAB_INNOCUOUS); + /* Allocate the sqlite3_vtab structure */ nDb = (int)strlen(argv[1]); nName = (int)strlen(argv[2]); - pRtree = (Rtree *)sqlite3_malloc64(sizeof(Rtree)+nDb+nName+2); + pRtree = (Rtree *)sqlite3_malloc64(sizeof(Rtree)+nDb+nName*2+8); if( !pRtree ){ return SQLITE_NOMEM; } - memset(pRtree, 0, sizeof(Rtree)+nDb+nName+2); + memset(pRtree, 0, sizeof(Rtree)+nDb+nName*2+8); pRtree->nBusy = 1; pRtree->base.pModule = &rtreeModule; pRtree->zDb = (char *)&pRtree[1]; pRtree->zName = &pRtree->zDb[nDb+1]; + pRtree->zNodeName = &pRtree->zName[nName+1]; pRtree->eCoordType = (u8)eCoordType; memcpy(pRtree->zDb, argv[1], nDb); memcpy(pRtree->zName, argv[2], nName); + memcpy(pRtree->zNodeName, argv[2], nName); + memcpy(&pRtree->zNodeName[nName], "_node", 6); /* Create/Connect to the underlying relational database schema. If @@ -184830,16 +222160,20 @@ static int rtreeInit( ** the r-tree table schema. */ pSql = sqlite3_str_new(db); - sqlite3_str_appendf(pSql, "CREATE TABLE x(%s", argv[3]); + sqlite3_str_appendf(pSql, "CREATE TABLE x(%.*s INT", + rtreeTokenLength(argv[3]), argv[3]); for(ii=4; iinAux++; - sqlite3_str_appendf(pSql, ",%s", argv[ii]+1); + sqlite3_str_appendf(pSql, ",%.*s", rtreeTokenLength(zArg+1), zArg+1); }else if( pRtree->nAux>0 ){ break; }else{ + static const char *azFormat[] = {",%.*s REAL", ",%.*s INT"}; pRtree->nDim2++; - sqlite3_str_appendf(pSql, ",%s", argv[ii]); + sqlite3_str_appendf(pSql, azFormat[eCoordType], + rtreeTokenLength(zArg), zArg); } } sqlite3_str_appendf(pSql, ");"); @@ -184904,7 +222238,7 @@ static int rtreeInit( ** ** The human readable string takes the form of a Tcl list with one ** entry for each cell in the r-tree node. Each entry is itself a -** list, containing the 8-byte rowid/pageno followed by the +** list, containing the 8-byte rowid/pageno followed by the ** *2 coordinates. */ static void rtreenode(sqlite3_context *ctx, int nArg, sqlite3_value **apArg){ @@ -184923,9 +222257,10 @@ static void rtreenode(sqlite3_context *ctx, int nArg, sqlite3_value **apArg){ tree.nDim2 = tree.nDim*2; tree.nBytesPerCell = 8 + 8 * tree.nDim; node.zData = (u8 *)sqlite3_value_blob(apArg[1]); + if( node.zData==0 ) return; nData = sqlite3_value_bytes(apArg[1]); if( nData<4 ) return; - if( nDatarc = SQLITE_NOMEM; }else{ - pCheck->zReport = sqlite3_mprintf("%z%s%z", + pCheck->zReport = sqlite3_mprintf("%z%s%z", pCheck->zReport, (pCheck->zReport ? "\n" : ""), z ); if( pCheck->zReport==0 ){ @@ -185077,7 +222417,7 @@ static u8 *rtreeCheckGetNode(RtreeCheck *pCheck, i64 iNode, int *pnNode){ if( pCheck->rc==SQLITE_OK && pCheck->pGetNode==0 ){ pCheck->pGetNode = rtreeCheckPrepare(pCheck, - "SELECT data FROM %Q.'%q_node' WHERE nodeno=?", + "SELECT data FROM %Q.'%q_node' WHERE nodeno=?", pCheck->zDb, pCheck->zTab ); } @@ -185147,7 +222487,7 @@ static void rtreeCheckMapping( }else if( rc==SQLITE_ROW ){ i64 ii = sqlite3_column_int64(pStmt, 0); if( ii!=iVal ){ - rtreeCheckAppendMsg(pCheck, + rtreeCheckAppendMsg(pCheck, "Found (%lld -> %lld) in %s table, expected (%lld -> %lld)", iKey, ii, (bLeaf ? "%_rowid" : "%_parent"), iKey, iVal ); @@ -185163,13 +222503,13 @@ static void rtreeCheckMapping( ** if they are not. ** ** Additionally, if pParent is not NULL, then it is assumed to point to -** the array of coordinates on the parent page that bound the page +** the array of coordinates on the parent page that bound the page ** containing pCell. In this case it is also verified that the two ** sets of coordinates are mutually consistent and an error message added ** to the RtreeCheck object if they are not. */ static void rtreeCheckCellCoord( - RtreeCheck *pCheck, + RtreeCheck *pCheck, i64 iNode, /* Node id to use in error messages */ int iCell, /* Cell number to use in error messages */ u8 *pCell, /* Pointer to cell coordinates */ @@ -185185,7 +222525,7 @@ static void rtreeCheckCellCoord( /* printf("%e, %e\n", c1.u.f, c2.u.f); */ if( pCheck->bInt ? c1.i>c2.i : c1.f>c2.f ){ - rtreeCheckAppendMsg(pCheck, + rtreeCheckAppendMsg(pCheck, "Dimension %d of cell %d on node %lld is corrupt", i, iCell, iNode ); } @@ -185194,10 +222534,10 @@ static void rtreeCheckCellCoord( readCoord(&pParent[4*2*i], &p1); readCoord(&pParent[4*(2*i + 1)], &p2); - if( (pCheck->bInt ? c1.ibInt ? c1.ibInt ? c2.i>p2.i : c2.f>p2.f) ){ - rtreeCheckAppendMsg(pCheck, + rtreeCheckAppendMsg(pCheck, "Dimension %d of cell %d on node %lld is corrupt relative to parent" , i, iCell, iNode ); @@ -185229,7 +222569,7 @@ static void rtreeCheckNode( aNode = rtreeCheckGetNode(pCheck, iNode, &nNode); if( aNode ){ if( nNode<4 ){ - rtreeCheckAppendMsg(pCheck, + rtreeCheckAppendMsg(pCheck, "Node %lld is too small (%d bytes)", iNode, nNode ); }else{ @@ -185245,8 +222585,8 @@ static void rtreeCheckNode( } nCell = readInt16(&aNode[2]); if( (4 + nCell*(8 + pCheck->nDim*2*4))>nNode ){ - rtreeCheckAppendMsg(pCheck, - "Node %lld is too small for cell count of %d (%d bytes)", + rtreeCheckAppendMsg(pCheck, + "Node %lld is too small for cell count of %d (%d bytes)", iNode, nCell, nNode ); }else{ @@ -185309,7 +222649,6 @@ static int rtreeCheckTable( ){ RtreeCheck check; /* Common context for various routines */ sqlite3_stmt *pStmt = 0; /* Used to find column count of rtree table */ - int bEnd = 0; /* True if transaction should be closed */ int nAux = 0; /* Number of extra columns. */ /* Initialize the context object */ @@ -185318,21 +222657,13 @@ static int rtreeCheckTable( check.zDb = zDb; check.zTab = zTab; - /* If there is not already an open transaction, open one now. This is - ** to ensure that the queries run as part of this integrity-check operate - ** on a consistent snapshot. */ - if( sqlite3_get_autocommit(db) ){ - check.rc = sqlite3_exec(db, "BEGIN", 0, 0, 0); - bEnd = 1; - } - /* Find the number of auxiliary columns */ - if( check.rc==SQLITE_OK ){ - pStmt = rtreeCheckPrepare(&check, "SELECT * FROM %Q.'%q_rowid'", zDb, zTab); - if( pStmt ){ - nAux = sqlite3_column_count(pStmt) - 2; - sqlite3_finalize(pStmt); - } + pStmt = rtreeCheckPrepare(&check, "SELECT * FROM %Q.'%q_rowid'", zDb, zTab); + if( pStmt ){ + nAux = sqlite3_column_count(pStmt) - 2; + sqlite3_finalize(pStmt); + }else + if( check.rc!=SQLITE_NOMEM ){ check.rc = SQLITE_OK; } @@ -185364,15 +222695,35 @@ static int rtreeCheckTable( sqlite3_finalize(check.aCheckMapping[0]); sqlite3_finalize(check.aCheckMapping[1]); - /* If one was opened, close the transaction */ - if( bEnd ){ - int rc = sqlite3_exec(db, "END", 0, 0, 0); - if( check.rc==SQLITE_OK ) check.rc = rc; - } *pzReport = check.zReport; return check.rc; } +/* +** Implementation of the xIntegrity method for Rtree. +*/ +static int rtreeIntegrity( + sqlite3_vtab *pVtab, /* The virtual table to check */ + const char *zSchema, /* Schema in which the virtual table lives */ + const char *zName, /* Name of the virtual table */ + int isQuick, /* True for a quick_check */ + char **pzErr /* Write results here */ +){ + Rtree *pRtree = (Rtree*)pVtab; + int rc; + assert( pzErr!=0 && *pzErr==0 ); + UNUSED_PARAMETER(zSchema); + UNUSED_PARAMETER(zName); + UNUSED_PARAMETER(isQuick); + rc = rtreeCheckTable(pRtree->db, pRtree->zDb, pRtree->zName, pzErr); + if( rc==SQLITE_OK && *pzErr ){ + *pzErr = sqlite3_mprintf("In RTree %s.%s:\n%z", + pRtree->zDb, pRtree->zName, *pzErr); + if( (*pzErr)==0 ) rc = SQLITE_NOMEM; + } + return rc; +} + /* ** Usage: ** @@ -185389,11 +222740,11 @@ static int rtreeCheckTable( ** b) unless the cell is on the root node, that the cell is bounded ** by the parent cell on the parent node. ** -** c) for leaf nodes, that there is an entry in the %_rowid -** table corresponding to the cell's rowid value that +** c) for leaf nodes, that there is an entry in the %_rowid +** table corresponding to the cell's rowid value that ** points to the correct node. ** -** d) for cells on non-leaf nodes, that there is an entry in the +** d) for cells on non-leaf nodes, that there is an entry in the ** %_parent table mapping from the cell's child node to the ** node that it resides on. ** @@ -185402,17 +222753,17 @@ static int rtreeCheckTable( ** is a leaf cell that corresponds to each entry in the %_rowid table. ** ** 3. That there are the same number of entries in the %_parent table -** as there are non-leaf cells in the r-tree structure, and that -** there is a non-leaf cell that corresponds to each entry in the +** as there are non-leaf cells in the r-tree structure, and that +** there is a non-leaf cell that corresponds to each entry in the ** %_parent table. */ static void rtreecheck( - sqlite3_context *ctx, - int nArg, + sqlite3_context *ctx, + int nArg, sqlite3_value **apArg ){ if( nArg!=1 && nArg!=2 ){ - sqlite3_result_error(ctx, + sqlite3_result_error(ctx, "wrong number of arguments to function rtreecheck()", -1 ); }else{ @@ -185468,11 +222819,7 @@ static void rtreecheck( # define GEODEBUG(X) #endif -#ifndef JSON_NULL /* The following stuff repeats things found in json1 */ -/* -** Versions of isspace(), isalnum() and isdigit() to which it is safe -** to pass signed char values. -*/ +/* Character class routines */ #ifdef sqlite3Isdigit /* Use the SQLite core versions if this routine is part of the ** SQLite amalgamation */ @@ -185487,6 +222834,7 @@ static void rtreecheck( # define safe_isxdigit(x) isxdigit((unsigned char)(x)) #endif +#ifndef JSON_NULL /* The following stuff repeats things found in json1 */ /* ** Growing our own isspace() routine this way is twice as fast as ** the library isspace() function. @@ -185509,7 +222857,7 @@ static const char geopolyIsSpace[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; -#define safe_isspace(x) (geopolyIsSpace[(unsigned char)x]) +#define fast_isspace(x) (geopolyIsSpace[(unsigned char)x]) #endif /* JSON NULL - back to original code */ /* Compiler and version */ @@ -185598,7 +222946,7 @@ static void geopolySwab32(unsigned char *a){ /* Skip whitespace. Return the next non-whitespace character. */ static char geopolySkipSpace(GeoParse *p){ - while( safe_isspace(p->z[0]) ) p->z++; + while( fast_isspace(p->z[0]) ) p->z++; return p->z[0]; } @@ -185645,7 +222993,7 @@ static int geopolyParseNumber(GeoParse *p, GeoCoord *pVal){ /* The sqlite3AtoF() routine is much much faster than atof(), if it ** is available */ double r; - (void)sqlite3AtoF((const char*)p->z, &r, j, SQLITE_UTF8); + (void)sqlite3AtoF((const char*)p->z, &r); *pVal = r; #else *pVal = (GeoCoord)atof((const char*)p->z); @@ -185747,11 +223095,16 @@ static GeoPoly *geopolyFuncParam( ){ GeoPoly *p = 0; int nByte; + testcase( pCtx==0 ); if( sqlite3_value_type(pVal)==SQLITE_BLOB - && (nByte = sqlite3_value_bytes(pVal))>=(4+6*sizeof(GeoCoord)) + && (nByte = sqlite3_value_bytes(pVal))>=(int)(4+6*sizeof(GeoCoord)) ){ const unsigned char *a = sqlite3_value_blob(pVal); int nVertex; + if( a==0 ){ + if( pCtx ) sqlite3_result_error_nomem(pCtx); + return 0; + } nVertex = (a[1]<<16) + (a[2]<<8) + a[3]; if( (a[0]==0 || a[0]==1) && (nVertex*2*sizeof(GeoCoord) + 4)==(unsigned int)nByte @@ -185802,8 +223155,9 @@ static void geopolyBlobFunc( sqlite3_value **argv ){ GeoPoly *p = geopolyFuncParam(context, argv[0], 0); + (void)argc; if( p ){ - sqlite3_result_blob(context, p->hdr, + sqlite3_result_blob(context, p->hdr, 4+8*p->nVertex, SQLITE_TRANSIENT); sqlite3_free(p); } @@ -185821,6 +223175,7 @@ static void geopolyJsonFunc( sqlite3_value **argv ){ GeoPoly *p = geopolyFuncParam(context, argv[0], 0); + (void)argc; if( p ){ sqlite3 *db = sqlite3_context_db_handle(context); sqlite3_str *x = sqlite3_str_new(db); @@ -185902,6 +223257,7 @@ static void geopolyXformFunc( double F = sqlite3_value_double(argv[6]); GeoCoord x1, y1, x0, y0; int ii; + (void)argc; if( p ){ for(ii=0; iinVertex; ii++){ x0 = GeoX(p,ii); @@ -185911,7 +223267,7 @@ static void geopolyXformFunc( GeoX(p,ii) = x1; GeoY(p,ii) = y1; } - sqlite3_result_blob(context, p->hdr, + sqlite3_result_blob(context, p->hdr, 4+8*p->nVertex, SQLITE_TRANSIENT); sqlite3_free(p); } @@ -185952,10 +223308,11 @@ static void geopolyAreaFunc( sqlite3_value **argv ){ GeoPoly *p = geopolyFuncParam(context, argv[0], 0); + (void)argc; if( p ){ sqlite3_result_double(context, geopolyArea(p)); sqlite3_free(p); - } + } } /* @@ -185963,7 +223320,7 @@ static void geopolyAreaFunc( ** ** If the rotation of polygon X is clockwise (incorrect) instead of ** counter-clockwise (the correct winding order according to RFC7946) -** then reverse the order of the vertexes in polygon X. +** then reverse the order of the vertexes in polygon X. ** ** In other words, this routine returns a CCW polygon regardless of the ** winding order of its input. @@ -185977,6 +223334,7 @@ static void geopolyCcwFunc( sqlite3_value **argv ){ GeoPoly *p = geopolyFuncParam(context, argv[0], 0); + (void)argc; if( p ){ if( geopolyArea(p)<0.0 ){ int ii, jj; @@ -185989,10 +223347,10 @@ static void geopolyCcwFunc( GeoY(p,jj) = t; } } - sqlite3_result_blob(context, p->hdr, + sqlite3_result_blob(context, p->hdr, 4+8*p->nVertex, SQLITE_TRANSIENT); sqlite3_free(p); - } + } } #define GEOPOLY_PI 3.1415926535897932385 @@ -186031,6 +223389,7 @@ static void geopolyRegularFunc( int n = sqlite3_value_int(argv[3]); int i; GeoPoly *p; + (void)argc; if( n<3 || r<=0.0 ) return; if( n>1000 ) n = 1000; @@ -186125,6 +223484,8 @@ static GeoPoly *geopolyBBox( aCoord[2].f = mnY; aCoord[3].f = mxY; } + }else if( aCoord ){ + memset(aCoord, 0, sizeof(RtreeCoord)*4); } return pOut; } @@ -186138,8 +223499,9 @@ static void geopolyBBoxFunc( sqlite3_value **argv ){ GeoPoly *p = geopolyBBox(context, argv[0], 0, 0); + (void)argc; if( p ){ - sqlite3_result_blob(context, p->hdr, + sqlite3_result_blob(context, p->hdr, 4+8*p->nVertex, SQLITE_TRANSIENT); sqlite3_free(p); } @@ -186165,6 +223527,7 @@ static void geopolyBBoxStep( ){ RtreeCoord a[4]; int rc = SQLITE_OK; + (void)argc; (void)geopolyBBox(context, argv[0], a, &rc); if( rc==SQLITE_OK ){ GeoBBox *pBBox; @@ -186190,7 +223553,7 @@ static void geopolyBBoxFinal( if( pBBox==0 ) return; p = geopolyBBox(context, 0, pBBox->a, 0); if( p ){ - sqlite3_result_blob(context, p->hdr, + sqlite3_result_blob(context, p->hdr, 4+8*p->nVertex, SQLITE_TRANSIENT); sqlite3_free(p); } @@ -186201,7 +223564,7 @@ static void geopolyBBoxFinal( ** Determine if point (x0,y0) is beneath line segment (x1,y1)->(x2,y2). ** Returns: ** -** +2 x0,y0 is on the line segement +** +2 x0,y0 is on the line segment ** ** +1 x0,y0 is beneath line segment ** @@ -186253,6 +223616,8 @@ static void geopolyContainsPointFunc( int v = 0; int cnt = 0; int ii; + (void)argc; + if( p1==0 ) return; for(ii=0; iinVertex-1; ii++){ v = pointBeneathLine(x0,y0,GeoX(p1,ii), GeoY(p1,ii), @@ -186292,6 +223657,7 @@ static void geopolyWithinFunc( ){ GeoPoly *p1 = geopolyFuncParam(context, argv[0], 0); GeoPoly *p2 = geopolyFuncParam(context, argv[1], 0); + (void)argc; if( p1 && p2 ){ int x = geopolyOverlap(p1, p2); if( x<0 ){ @@ -186304,7 +223670,7 @@ static void geopolyWithinFunc( sqlite3_free(p2); } -/* Objects used by the overlap algorihm. */ +/* Objects used by the overlap algorithm. */ typedef struct GeoEvent GeoEvent; typedef struct GeoSegment GeoSegment; typedef struct GeoOverlap GeoOverlap; @@ -186370,7 +223736,7 @@ static void geopolyAddOneSegment( pEvent->eType = 1; pEvent->pSeg = pSeg; } - + /* @@ -186410,7 +223776,7 @@ static GeoEvent *geopolyEventMerge(GeoEvent *pLeft, GeoEvent *pRight){ } } pLast->pNext = pRight ? pRight : pLeft; - return head.pNext; + return head.pNext; } /* @@ -186459,7 +223825,7 @@ static GeoSegment *geopolySegmentMerge(GeoSegment *pLeft, GeoSegment *pRight){ } } pLast->pNext = pRight ? pRight : pLeft; - return head.pNext; + return head.pNext; } /* @@ -186504,8 +223870,8 @@ static int geopolyOverlap(GeoPoly *p1, GeoPoly *p2){ GeoSegment *pSeg; unsigned char aOverlap[4]; - nByte = sizeof(GeoEvent)*nVertex*2 - + sizeof(GeoSegment)*nVertex + nByte = sizeof(GeoEvent)*nVertex*2 + + sizeof(GeoSegment)*nVertex + sizeof(GeoOverlap); p = sqlite3_malloc64( nByte ); if( p==0 ) return -1; @@ -186515,7 +223881,7 @@ static int geopolyOverlap(GeoPoly *p1, GeoPoly *p2){ geopolyAddSegments(p, p1, 1); geopolyAddSegments(p, p2, 2); pThisEvent = geopolySortEventsByX(p->aEvent, p->nEvent); - rX = pThisEvent->x==0.0 ? -1.0 : 0.0; + rX = pThisEvent && pThisEvent->x==0.0 ? -1.0 : 0.0; memset(aOverlap, 0, sizeof(aOverlap)); while( pThisEvent ){ if( pThisEvent->x!=rX ){ @@ -186574,11 +223940,11 @@ static int geopolyOverlap(GeoPoly *p1, GeoPoly *p2){ }else{ /* Remove a segment */ if( pActive==pThisEvent->pSeg ){ - pActive = pActive->pNext; + pActive = ALWAYS(pActive) ? pActive->pNext : 0; }else{ for(pSeg=pActive; pSeg; pSeg=pSeg->pNext){ if( pSeg->pNext==pThisEvent->pSeg ){ - pSeg->pNext = pSeg->pNext->pNext; + pSeg->pNext = ALWAYS(pSeg->pNext) ? pSeg->pNext->pNext : 0; break; } } @@ -186622,6 +223988,7 @@ static void geopolyOverlapFunc( ){ GeoPoly *p1 = geopolyFuncParam(context, argv[0], 0); GeoPoly *p2 = geopolyFuncParam(context, argv[1], 0); + (void)argc; if( p1 && p2 ){ int x = geopolyOverlap(p1, p2); if( x<0 ){ @@ -186642,12 +224009,16 @@ static void geopolyDebugFunc( int argc, sqlite3_value **argv ){ + (void)context; + (void)argc; #ifdef GEOPOLY_ENABLE_DEBUG geo_debug = sqlite3_value_int(argv[0]); +#else + (void)argv; #endif } -/* +/* ** This function is the implementation of both the xConnect and xCreate ** methods of the geopoly virtual table. ** @@ -186671,26 +224042,36 @@ static int geopolyInit( sqlite3_str *pSql; char *zSql; int ii; + (void)pAux; + + if( argc>=RTREE_MAX_AUX_COLUMN+4 ){ + *pzErr = sqlite3_mprintf("Too many columns for a geopoly table"); + return SQLITE_ERROR; + } sqlite3_vtab_config(db, SQLITE_VTAB_CONSTRAINT_SUPPORT, 1); + sqlite3_vtab_config(db, SQLITE_VTAB_INNOCUOUS); /* Allocate the sqlite3_vtab structure */ nDb = strlen(argv[1]); nName = strlen(argv[2]); - pRtree = (Rtree *)sqlite3_malloc64(sizeof(Rtree)+nDb+nName+2); + pRtree = (Rtree *)sqlite3_malloc64(sizeof(Rtree)+nDb+nName*2+8); if( !pRtree ){ return SQLITE_NOMEM; } - memset(pRtree, 0, sizeof(Rtree)+nDb+nName+2); + memset(pRtree, 0, sizeof(Rtree)+nDb+nName*2+8); pRtree->nBusy = 1; pRtree->base.pModule = &rtreeModule; pRtree->zDb = (char *)&pRtree[1]; pRtree->zName = &pRtree->zDb[nDb+1]; + pRtree->zNodeName = &pRtree->zName[nName+1]; pRtree->eCoordType = RTREE_COORD_REAL32; pRtree->nDim = 2; pRtree->nDim2 = 4; memcpy(pRtree->zDb, argv[1], nDb); memcpy(pRtree->zName, argv[2], nName); + memcpy(pRtree->zNodeName, argv[2], nName); + memcpy(&pRtree->zNodeName[nName], "_node", 6); /* Create/Connect to the underlying relational database schema. If @@ -186737,7 +224118,7 @@ static int geopolyInit( } -/* +/* ** GEOPOLY virtual table module xCreate method. */ static int geopolyCreate( @@ -186750,7 +224131,7 @@ static int geopolyCreate( return geopolyInit(db, pAux, argc, argv, ppVtab, pzErr, 1); } -/* +/* ** GEOPOLY virtual table module xConnect method. */ static int geopolyConnect( @@ -186764,7 +224145,7 @@ static int geopolyConnect( } -/* +/* ** GEOPOLY virtual table module xFilter method. ** ** Query plans: @@ -186787,17 +224168,12 @@ static int geopolyFilter( RtreeNode *pRoot = 0; int rc = SQLITE_OK; int iCell = 0; - sqlite3_stmt *pStmt; + (void)idxStr; rtreeReference(pRtree); /* Reset the cursor to the same state as rtreeOpen() leaves it in. */ - freeCursorConstraints(pCsr); - sqlite3_free(pCsr->aPoint); - pStmt = pCsr->pReadAux; - memset(pCsr, 0, sizeof(RtreeCursor)); - pCsr->base.pVtab = (sqlite3_vtab*)pRtree; - pCsr->pReadAux = pStmt; + resetCursor(pCsr); pCsr->iStrategy = idxNum; if( idxNum==1 ){ @@ -186820,14 +224196,15 @@ static int geopolyFilter( pCsr->atEOF = 1; } }else{ - /* Normal case - r-tree scan. Set up the RtreeCursor.aConstraint array - ** with the configured constraints. + /* Normal case - r-tree scan. Set up the RtreeCursor.aConstraint array + ** with the configured constraints. */ rc = nodeAcquire(pRtree, 1, 0, &pRoot); if( rc==SQLITE_OK && idxNum<=3 ){ RtreeCoord bbox[4]; RtreeConstraint *p; assert( argc==1 ); + assert( argv[0]!=0 ); geopolyBBox(0, argv[0], bbox, &rc); if( rc ){ goto geopoly_filter_end; @@ -186902,7 +224279,7 @@ static int geopolyFilter( /* ** Rtree virtual table module xBestIndex method. There are three -** table scan strategies to choose from (in order from most to +** table scan strategies to choose from (in order from most to ** least desirable): ** ** idxNum idxStr Strategy @@ -186918,6 +224295,7 @@ static int geopolyBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ int iRowidTerm = -1; int iFuncTerm = -1; int idxNum = 0; + (void)tab; for(ii=0; iinConstraint; ii++){ struct sqlite3_index_constraint *p = &pIdxInfo->aConstraint[ii]; @@ -186962,7 +224340,7 @@ static int geopolyBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ } -/* +/* ** GEOPOLY virtual table module xColumn method. */ static int geopolyColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){ @@ -186982,7 +224360,7 @@ static int geopolyColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){ &pCsr->pReadAux, 0); if( rc ) return rc; } - sqlite3_bind_int64(pCsr->pReadAux, 1, + sqlite3_bind_int64(pCsr->pReadAux, 1, nodeGetRowid(pRtree, pNode, p->iCell)); rc = sqlite3_step(pCsr->pReadAux); if( rc==SQLITE_ROW ){ @@ -187021,9 +224399,9 @@ static int geopolyColumn(sqlite3_vtab_cursor *cur, sqlite3_context *ctx, int i){ ** argv[3] = new value for first application-defined column.... */ static int geopolyUpdate( - sqlite3_vtab *pVtab, - int nData, - sqlite3_value **aData, + sqlite3_vtab *pVtab, + int nData, + sqlite3_value **aData, sqlite_int64 *pRowid ){ Rtree *pRtree = (Rtree *)pVtab; @@ -187055,6 +224433,7 @@ static int geopolyUpdate( || !sqlite3_value_nochange(aData[2]) /* UPDATE _shape */ || oldRowid!=newRowid) /* Rowid change */ ){ + assert( aData[2]!=0 ); geopolyBBox(0, aData[2], cell.aCoord, &rc); if( rc ){ if( rc==SQLITE_ERROR ){ @@ -187065,7 +224444,7 @@ static int geopolyUpdate( } coordChange = 1; - /* If a rowid value was supplied, check if it is already present in + /* If a rowid value was supplied, check if it is already present in ** the table. If so, the constraint has failed. */ if( newRowidValid && (!oldRowidValid || oldRowid!=newRowid) ){ int steprc; @@ -187106,7 +224485,6 @@ static int geopolyUpdate( } if( rc==SQLITE_OK ){ int rc2; - pRtree->iReinsertHeight = -1; rc = rtreeInsertCell(pRtree, pLeaf, &cell, 0); rc2 = nodeRelease(pRtree, pLeaf); if( rc==SQLITE_OK ){ @@ -187137,7 +224515,7 @@ static int geopolyUpdate( sqlite3_free(p); nChange = 1; } - for(jj=1; jjnAux; jj++){ + for(jj=1; jjxQueryFunc = 0; pGeomCtx->xDestructor = 0; pGeomCtx->pContext = pContext; - return sqlite3_create_function_v2(db, zGeom, -1, SQLITE_ANY, + return sqlite3_create_function_v2(db, zGeom, -1, SQLITE_ANY, (void *)pGeomCtx, geomCallback, 0, 0, rtreeFreeCallback ); } @@ -187402,17 +224788,20 @@ SQLITE_API int sqlite3_rtree_query_callback( /* Allocate and populate the context object. */ pGeomCtx = (RtreeGeomCallback *)sqlite3_malloc(sizeof(RtreeGeomCallback)); - if( !pGeomCtx ) return SQLITE_NOMEM; + if( !pGeomCtx ){ + if( xDestructor ) xDestructor(pContext); + return SQLITE_NOMEM; + } pGeomCtx->xGeom = 0; pGeomCtx->xQueryFunc = xQueryFunc; pGeomCtx->xDestructor = xDestructor; pGeomCtx->pContext = pContext; - return sqlite3_create_function_v2(db, zQueryFunc, -1, SQLITE_ANY, + return sqlite3_create_function_v2(db, zQueryFunc, -1, SQLITE_ANY, (void *)pGeomCtx, geomCallback, 0, 0, rtreeFreeCallback ); } -#if !SQLITE_CORE +#ifndef SQLITE_CORE #ifdef _WIN32 __declspec(dllexport) #endif @@ -187443,9 +224832,9 @@ SQLITE_API int sqlite3_rtree_init( ************************************************************************* ** $Id: icu.c,v 1.7 2007/12/13 21:54:11 drh Exp $ ** -** This file implements an integration between the ICU library -** ("International Components for Unicode", an open-source library -** for handling unicode data) and SQLite. The integration uses +** This file implements an integration between the ICU library +** ("International Components for Unicode", an open-source library +** for handling unicode data) and SQLite. The integration uses ** ICU to provide the following to SQLite: ** ** * An implementation of the SQL regexp() function (and hence REGEXP @@ -187456,7 +224845,7 @@ SQLITE_API int sqlite3_rtree_init( ** ** * Integration of ICU and SQLite collation sequences. ** -** * An implementation of the LIKE operator that uses ICU to +** * An implementation of the LIKE operator that uses ICU to ** provide case-independent matching. */ @@ -187483,7 +224872,7 @@ SQLITE_API int sqlite3_rtree_init( ** This function is called when an ICU function called from within ** the implementation of an SQL scalar function returns an error. ** -** The scalar function context passed as the first argument is +** The scalar function context passed as the first argument is ** loaded with an error message based on the following two args. */ static void icuFunctionError( @@ -187548,7 +224937,7 @@ static const unsigned char icuUtf8Trans1[] = { /* ** Compare two UTF-8 strings for equality where the first string is -** a "LIKE" expression. Return true (1) if they are the same and +** a "LIKE" expression. Return true (1) if they are the same and ** false (0) if they are different. */ static int icuLikeCompare( @@ -187575,12 +224964,12 @@ static int icuLikeCompare( ** 3. uPattern is an unescaped escape character, or ** 4. uPattern is to be handled as an ordinary character */ - if( !prevEscape && uPattern==MATCH_ALL ){ + if( uPattern==MATCH_ALL && !prevEscape && uPattern!=(uint32_t)uEsc ){ /* Case 1. */ uint8_t c; /* Skip any MATCH_ALL or MATCH_ONE characters that follow a - ** MATCH_ALL. For each MATCH_ONE, skip one character in the + ** MATCH_ALL. For each MATCH_ONE, skip one character in the ** test string. */ while( (c=*zPattern) == MATCH_ALL || c == MATCH_ONE ){ @@ -187601,12 +224990,12 @@ static int icuLikeCompare( } return 0; - }else if( !prevEscape && uPattern==MATCH_ONE ){ + }else if( uPattern==MATCH_ONE && !prevEscape && uPattern!=(uint32_t)uEsc ){ /* Case 2. */ if( *zString==0 ) return 0; SQLITE_ICU_SKIP_UTF8(zString); - }else if( !prevEscape && uPattern==(uint32_t)uEsc){ + }else if( uPattern==(uint32_t)uEsc && !prevEscape ){ /* Case 3. */ prevEscape = 1; @@ -187633,15 +225022,15 @@ static int icuLikeCompare( ** ** A LIKE B ** -** is implemented as like(B, A). If there is an escape character E, +** is implemented as like(B, A). If there is an escape character E, ** ** A LIKE B ESCAPE E ** ** is mapped to like(B, A, E). */ static void icuLikeFunc( - sqlite3_context *context, - int argc, + sqlite3_context *context, + int argc, sqlite3_value **argv ){ const unsigned char *zA = sqlite3_value_text(argv[0]); @@ -187667,7 +225056,7 @@ static void icuLikeFunc( if( zE==0 ) return; U8_NEXT(zE, i, nE, uEsc); if( i!=nE){ - sqlite3_result_error(context, + sqlite3_result_error(context, "ESCAPE expression must be a single character", -1); return; } @@ -187690,7 +225079,7 @@ static void icuRegexpDelete(void *p){ /* ** Implementation of SQLite REGEXP operator. This scalar function takes ** two arguments. The first is a regular expression pattern to compile -** the second is a string to match against that pattern. If either +** the second is a string to match against that pattern. If either ** argument is an SQL NULL, then NULL Is returned. Otherwise, the result ** is 1 if the string matches the pattern, or 0 otherwise. ** @@ -187714,8 +225103,8 @@ static void icuRegexpFunc(sqlite3_context *p, int nArg, sqlite3_value **apArg){ (void)nArg; /* Unused parameter */ - /* If the left hand side of the regexp operator is NULL, - ** then the result is also NULL. + /* If the left hand side of the regexp operator is NULL, + ** then the result is also NULL. */ if( !zString ){ return; @@ -187731,8 +225120,9 @@ static void icuRegexpFunc(sqlite3_context *p, int nArg, sqlite3_value **apArg){ if( U_SUCCESS(status) ){ sqlite3_set_auxdata(p, 0, pExpr, icuRegexpDelete); - }else{ - assert(!pExpr); + pExpr = sqlite3_get_auxdata(p, 0); + } + if( !pExpr ){ icuFunctionError(p, "uregex_open", status); return; } @@ -187753,7 +225143,7 @@ static void icuRegexpFunc(sqlite3_context *p, int nArg, sqlite3_value **apArg){ } /* Set the text that the regular expression operates on to a NULL - ** pointer. This is not really necessary, but it is tidier than + ** pointer. This is not really necessary, but it is tidier than ** leaving the regular expression object configured with an invalid ** pointer after this function returns. */ @@ -187764,7 +225154,7 @@ static void icuRegexpFunc(sqlite3_context *p, int nArg, sqlite3_value **apArg){ } /* -** Implementations of scalar functions for case mapping - upper() and +** Implementations of scalar functions for case mapping - upper() and ** lower(). Function upper() converts its input to upper-case (ABC). ** Function lower() converts to lower-case (abc). ** @@ -187772,7 +225162,7 @@ static void icuRegexpFunc(sqlite3_context *p, int nArg, sqlite3_value **apArg){ ** "language specific". Refer to ICU documentation for the differences ** between the two. ** -** To utilise "general" case mapping, the upper() or lower() scalar +** To utilise "general" case mapping, the upper() or lower() scalar ** functions are invoked with one argument: ** ** upper('ABC') -> 'abc' @@ -187793,7 +225183,7 @@ static void icuCaseFunc16(sqlite3_context *p, int nArg, sqlite3_value **apArg){ const UChar *zInput; /* Pointer to input string */ UChar *zOutput = 0; /* Pointer to output buffer */ int nInput; /* Size of utf-16 input string in bytes */ - int nOut; /* Size of output buffer in bytes */ + sqlite3_int64 nOut; /* Size of output buffer in bytes */ int cnt; int bToUpper; /* True for toupper(), false for tolower() */ UErrorCode status; @@ -187816,7 +225206,7 @@ static void icuCaseFunc16(sqlite3_context *p, int nArg, sqlite3_value **apArg){ } for(cnt=0; cnt<2; cnt++){ - UChar *zNew = sqlite3_realloc(zOutput, nOut); + UChar *zNew = sqlite3_realloc64(zOutput, nOut); if( zNew==0 ){ sqlite3_free(zOutput); sqlite3_result_error_nomem(p); @@ -187825,9 +225215,9 @@ static void icuCaseFunc16(sqlite3_context *p, int nArg, sqlite3_value **apArg){ zOutput = zNew; status = U_ZERO_ERROR; if( bToUpper ){ - nOut = 2*u_strToUpper(zOutput,nOut/2,zInput,nInput/2,zLocale,&status); + nOut = 2LL*u_strToUpper(zOutput,nOut/2,zInput,nInput/2,zLocale,&status); }else{ - nOut = 2*u_strToLower(zOutput,nOut/2,zInput,nInput/2,zLocale,&status); + nOut = 2LL*u_strToLower(zOutput,nOut/2,zInput,nInput/2,zLocale,&status); } if( U_SUCCESS(status) ){ @@ -187880,7 +225270,7 @@ static int icuCollationColl( /* ** Implementation of the scalar function icu_load_collation(). ** -** This scalar function is used to add ICU collation based collation +** This scalar function is used to add ICU collation based collation ** types to an SQLite database connection. It is intended to be called ** as follows: ** @@ -187891,8 +225281,8 @@ static int icuCollationColl( ** collation sequence to create. */ static void icuLoadCollation( - sqlite3_context *p, - int nArg, + sqlite3_context *p, + int nArg, sqlite3_value **apArg ){ sqlite3 *db = (sqlite3 *)sqlite3_user_data(p); @@ -187902,7 +225292,7 @@ static void icuLoadCollation( UCollator *pUCollator; /* ICU library collation object */ int rc; /* Return code from sqlite3_create_collation_x() */ - assert(nArg==2); + assert(nArg==2 || nArg==3); (void)nArg; /* Unused parameter */ zLocale = (const char *)sqlite3_value_text(apArg[0]); zName = (const char *)sqlite3_value_text(apArg[1]); @@ -187917,8 +225307,40 @@ static void icuLoadCollation( return; } assert(p); - - rc = sqlite3_create_collation_v2(db, zName, SQLITE_UTF16, (void *)pUCollator, + if(nArg==3){ + const char *zOption = (const char*)sqlite3_value_text(apArg[2]); + static const struct { + const char *zName; + UColAttributeValue val; + } aStrength[] = { + { "PRIMARY", UCOL_PRIMARY }, + { "SECONDARY", UCOL_SECONDARY }, + { "TERTIARY", UCOL_TERTIARY }, + { "DEFAULT", UCOL_DEFAULT_STRENGTH }, + { "QUARTERNARY", UCOL_QUATERNARY }, + { "IDENTICAL", UCOL_IDENTICAL }, + }; + unsigned int i; + for(i=0; i=sizeof(aStrength)/sizeof(aStrength[0]) ){ + sqlite3_str *pStr = sqlite3_str_new(sqlite3_context_db_handle(p)); + sqlite3_str_appendf(pStr, + "unknown collation strength \"%s\" - should be one of:", + zOption); + for(i=0; izName, p->nArg, p->enc, + db, p->zName, p->nArg, p->enc, p->iContext ? (void*)db : (void*)0, p->xFunc, 0, 0 ); @@ -187968,12 +225392,12 @@ SQLITE_PRIVATE int sqlite3IcuInit(sqlite3 *db){ return rc; } -#if !SQLITE_CORE +#ifndef SQLITE_CORE #ifdef _WIN32 __declspec(dllexport) #endif SQLITE_API int sqlite3_icu_init( - sqlite3 *db, + sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi ){ @@ -188076,7 +225500,7 @@ static int icuDestroy(sqlite3_tokenizer *pTokenizer){ /* ** Prepare to begin tokenizing a particular string. The input ** string to be tokenized is pInput[0..nBytes-1]. A cursor -** used to incrementally tokenize this string is returned in +** used to incrementally tokenize this string is returned in ** *ppCursor. */ static int icuOpen( @@ -188118,7 +225542,7 @@ static int icuOpen( pCsr->aOffset = (int *)&pCsr->aChar[(nChar+3)&~3]; pCsr->aOffset[iOut] = iInput; - U8_NEXT(zInput, iInput, nInput, c); + U8_NEXT(zInput, iInput, nInput, c); while( c>0 ){ int isError = 0; c = u_foldCase(c, opt); @@ -188264,7 +225688,7 @@ SQLITE_PRIVATE void sqlite3Fts3IcuTokenizerModule( ************************************************************************* ** ** -** OVERVIEW +** OVERVIEW ** ** The RBU extension requires that the RBU update be packaged as an ** SQLite database. The tables it expects to find are described in @@ -188272,34 +225696,34 @@ SQLITE_PRIVATE void sqlite3Fts3IcuTokenizerModule( ** that the user wishes to write to, a corresponding data_xyz table is ** created in the RBU database and populated with one row for each row to ** update, insert or delete from the target table. -** +** ** The update proceeds in three stages: -** +** ** 1) The database is updated. The modified database pages are written ** to a *-oal file. A *-oal file is just like a *-wal file, except ** that it is named "-oal" instead of "-wal". ** Because regular SQLite clients do not look for file named ** "-oal", they go on using the original database in ** rollback mode while the *-oal file is being generated. -** +** ** During this stage RBU does not update the database by writing ** directly to the target tables. Instead it creates "imposter" ** tables using the SQLITE_TESTCTRL_IMPOSTER interface that it uses ** to update each b-tree individually. All updates required by each ** b-tree are completed before moving on to the next, and all ** updates are done in sorted key order. -** +** ** 2) The "-oal" file is moved to the equivalent "-wal" ** location using a call to rename(2). Before doing this the RBU ** module takes an EXCLUSIVE lock on the database file, ensuring ** that there are no other active readers. -** +** ** Once the EXCLUSIVE lock is released, any other database readers ** detect the new *-wal file and read the database in wal mode. At ** this point they see the new version of the database - including ** the updates made as part of the RBU update. -** -** 3) The new *-wal file is checkpointed. This proceeds in the same way +** +** 3) The new *-wal file is checkpointed. This proceeds in the same way ** as a regular database checkpoint, except that a single frame is ** checkpointed each time sqlite3rbu_step() is called. If the RBU ** handle is closed before the entire *-wal file is checkpointed, @@ -188308,7 +225732,7 @@ SQLITE_PRIVATE void sqlite3Fts3IcuTokenizerModule( ** the future. ** ** POTENTIAL PROBLEMS -** +** ** The rename() call might not be portable. And RBU is not currently ** syncing the directory after renaming the file. ** @@ -188330,7 +225754,7 @@ SQLITE_PRIVATE void sqlite3Fts3IcuTokenizerModule( ** fields are collected. This means we're probably writing a lot more ** data to disk when saving the state of an ongoing update to the RBU ** update database than is strictly necessary. -** +** */ /* #include */ @@ -188354,46 +225778,46 @@ SQLITE_PRIVATE void sqlite3Fts3IcuTokenizerModule( ** ************************************************************************* ** -** This file contains the public interface for the RBU extension. +** This file contains the public interface for the RBU extension. */ /* ** SUMMARY ** -** Writing a transaction containing a large number of operations on +** Writing a transaction containing a large number of operations on ** b-tree indexes that are collectively larger than the available cache -** memory can be very inefficient. +** memory can be very inefficient. ** ** The problem is that in order to update a b-tree, the leaf page (at least) ** containing the entry being inserted or deleted must be modified. If the -** working set of leaves is larger than the available cache memory, then a -** single leaf that is modified more than once as part of the transaction +** working set of leaves is larger than the available cache memory, then a +** single leaf that is modified more than once as part of the transaction ** may be loaded from or written to the persistent media multiple times. ** Additionally, because the index updates are likely to be applied in -** random order, access to pages within the database is also likely to be in +** random order, access to pages within the database is also likely to be in ** random order, which is itself quite inefficient. ** ** One way to improve the situation is to sort the operations on each index ** by index key before applying them to the b-tree. This leads to an IO ** pattern that resembles a single linear scan through the index b-tree, -** and all but guarantees each modified leaf page is loaded and stored +** and all but guarantees each modified leaf page is loaded and stored ** exactly once. SQLite uses this trick to improve the performance of ** CREATE INDEX commands. This extension allows it to be used to improve ** the performance of large transactions on existing databases. ** -** Additionally, this extension allows the work involved in writing the -** large transaction to be broken down into sub-transactions performed -** sequentially by separate processes. This is useful if the system cannot -** guarantee that a single update process will run for long enough to apply -** the entire update, for example because the update is being applied on a -** mobile device that is frequently rebooted. Even after the writer process +** Additionally, this extension allows the work involved in writing the +** large transaction to be broken down into sub-transactions performed +** sequentially by separate processes. This is useful if the system cannot +** guarantee that a single update process will run for long enough to apply +** the entire update, for example because the update is being applied on a +** mobile device that is frequently rebooted. Even after the writer process ** has committed one or more sub-transactions, other database clients continue -** to read from the original database snapshot. In other words, partially -** applied transactions are not visible to other clients. +** to read from the original database snapshot. In other words, partially +** applied transactions are not visible to other clients. ** ** "RBU" stands for "Resumable Bulk Update". As in a large database update ** transmitted via a wireless network to a mobile device. A transaction -** applied using this extension is hence refered to as an "RBU update". +** applied using this extension is hence referred to as an "RBU update". ** ** ** LIMITATIONS @@ -188405,9 +225829,9 @@ SQLITE_PRIVATE void sqlite3Fts3IcuTokenizerModule( ** ** * INSERT statements may not use any default values. ** -** * UPDATE and DELETE statements must identify their target rows by +** * UPDATE and DELETE statements must identify their target rows by ** non-NULL PRIMARY KEY values. Rows with NULL values stored in PRIMARY -** KEY fields may not be updated or deleted. If the table being written +** KEY fields may not be updated or deleted. If the table being written ** has no PRIMARY KEY, affected rows must be identified by rowid. ** ** * UPDATE statements may not modify PRIMARY KEY columns. @@ -188424,10 +225848,10 @@ SQLITE_PRIVATE void sqlite3Fts3IcuTokenizerModule( ** PREPARATION ** ** An "RBU update" is stored as a separate SQLite database. A database -** containing an RBU update is an "RBU database". For each table in the +** containing an RBU update is an "RBU database". For each table in the ** target database to be updated, the RBU database should contain a table ** named "data_" containing the same set of columns as the -** target table, and one more - "rbu_control". The data_% table should +** target table, and one more - "rbu_control". The data_% table should ** have no PRIMARY KEY or UNIQUE constraints, but each column should have ** the same type as the corresponding column in the target database. ** The "rbu_control" column should have no type at all. For example, if @@ -188442,22 +225866,22 @@ SQLITE_PRIVATE void sqlite3Fts3IcuTokenizerModule( ** The order of the columns in the data_% table does not matter. ** ** Instead of a regular table, the RBU database may also contain virtual -** tables or view named using the data_ naming scheme. +** tables or views named using the data_ naming scheme. ** -** Instead of the plain data_ naming scheme, RBU database tables +** Instead of the plain data_ naming scheme, RBU database tables ** may also be named data_, where is any sequence ** of zero or more numeric characters (0-9). This can be significant because -** tables within the RBU database are always processed in order sorted by +** tables within the RBU database are always processed in order sorted by ** name. By judicious selection of the portion of the names ** of the RBU tables the user can therefore control the order in which they ** are processed. This can be useful, for example, to ensure that "external ** content" FTS4 tables are updated before their underlying content tables. ** ** If the target database table is a virtual table or a table that has no -** PRIMARY KEY declaration, the data_% table must also contain a column -** named "rbu_rowid". This column is mapped to the tables implicit primary -** key column - "rowid". Virtual tables for which the "rowid" column does -** not function like a primary key value cannot be updated using RBU. For +** PRIMARY KEY declaration, the data_% table must also contain a column +** named "rbu_rowid". This column is mapped to the table's implicit primary +** key column - "rowid". Virtual tables for which the "rowid" column does +** not function like a primary key value cannot be updated using RBU. For ** example, if the target db contains either of the following: ** ** CREATE VIRTUAL TABLE x1 USING fts3(a, b); @@ -188480,35 +225904,35 @@ SQLITE_PRIVATE void sqlite3Fts3IcuTokenizerModule( ** CREATE TABLE data_ft1(a, b, langid, rbu_rowid, rbu_control); ** CREATE TABLE data_ft1(a, b, rbu_rowid, rbu_control); ** -** For each row to INSERT into the target database as part of the RBU +** For each row to INSERT into the target database as part of the RBU ** update, the corresponding data_% table should contain a single record ** with the "rbu_control" column set to contain integer value 0. The -** other columns should be set to the values that make up the new record -** to insert. +** other columns should be set to the values that make up the new record +** to insert. ** -** If the target database table has an INTEGER PRIMARY KEY, it is not -** possible to insert a NULL value into the IPK column. Attempting to +** If the target database table has an INTEGER PRIMARY KEY, it is not +** possible to insert a NULL value into the IPK column. Attempting to ** do so results in an SQLITE_MISMATCH error. ** -** For each row to DELETE from the target database as part of the RBU +** For each row to DELETE from the target database as part of the RBU ** update, the corresponding data_% table should contain a single record ** with the "rbu_control" column set to contain integer value 1. The ** real primary key values of the row to delete should be stored in the ** corresponding columns of the data_% table. The values stored in the ** other columns are not used. ** -** For each row to UPDATE from the target database as part of the RBU +** For each row to UPDATE from the target database as part of the RBU ** update, the corresponding data_% table should contain a single record ** with the "rbu_control" column set to contain a value of type text. -** The real primary key values identifying the row to update should be +** The real primary key values identifying the row to update should be ** stored in the corresponding columns of the data_% table row, as should -** the new values of all columns being update. The text value in the +** the new values of all columns being update. The text value in the ** "rbu_control" column must contain the same number of characters as ** there are columns in the target database table, and must consist entirely -** of 'x' and '.' characters (or in some special cases 'd' - see below). For +** of 'x' and '.' characters (or in some special cases 'd' - see below). For ** each column that is being updated, the corresponding character is set to ** 'x'. For those that remain as they are, the corresponding character of the -** rbu_control value should be set to '.'. For example, given the tables +** rbu_control value should be set to '.'. For example, given the tables ** above, the update statement: ** ** UPDATE t1 SET c = 'usa' WHERE a = 4; @@ -188522,30 +225946,30 @@ SQLITE_PRIVATE void sqlite3Fts3IcuTokenizerModule( ** target table with the value stored in the corresponding data_% column, the ** user-defined SQL function "rbu_delta()" is invoked and the result stored in ** the target table column. rbu_delta() is invoked with two arguments - the -** original value currently stored in the target table column and the +** original value currently stored in the target table column and the ** value specified in the data_xxx table. ** ** For example, this row: ** ** INSERT INTO data_t1(a, b, c, rbu_control) VALUES(4, NULL, 'usa', '..d'); ** -** is similar to an UPDATE statement such as: +** is similar to an UPDATE statement such as: ** ** UPDATE t1 SET c = rbu_delta(c, 'usa') WHERE a = 4; ** -** Finally, if an 'f' character appears in place of a 'd' or 's' in an +** Finally, if an 'f' character appears in place of a 'd' or 's' in an ** ota_control string, the contents of the data_xxx table column is assumed ** to be a "fossil delta" - a patch to be applied to a blob value in the ** format used by the fossil source-code management system. In this case -** the existing value within the target database table must be of type BLOB. +** the existing value within the target database table must be of type BLOB. ** It is replaced by the result of applying the specified fossil delta to ** itself. ** ** If the target database table is a virtual table or a table with no PRIMARY -** KEY, the rbu_control value should not include a character corresponding +** KEY, the rbu_control value should not include a character corresponding ** to the rbu_rowid value. For example, this: ** -** INSERT INTO data_ft1(a, b, rbu_rowid, rbu_control) +** INSERT INTO data_ft1(a, b, rbu_rowid, rbu_control) ** VALUES(NULL, 'usa', 12, '.x'); ** ** causes a result similar to: @@ -188555,14 +225979,14 @@ SQLITE_PRIVATE void sqlite3Fts3IcuTokenizerModule( ** The data_xxx tables themselves should have no PRIMARY KEY declarations. ** However, RBU is more efficient if reading the rows in from each data_xxx ** table in "rowid" order is roughly the same as reading them sorted by -** the PRIMARY KEY of the corresponding target database table. In other -** words, rows should be sorted using the destination table PRIMARY KEY +** the PRIMARY KEY of the corresponding target database table. In other +** words, rows should be sorted using the destination table PRIMARY KEY ** fields before they are inserted into the data_xxx tables. ** ** USAGE ** -** The API declared below allows an application to apply an RBU update -** stored on disk to an existing target database. Essentially, the +** The API declared below allows an application to apply an RBU update +** stored on disk to an existing target database. Essentially, the ** application: ** ** 1) Opens an RBU handle using the sqlite3rbu_open() function. @@ -188573,24 +225997,24 @@ SQLITE_PRIVATE void sqlite3Fts3IcuTokenizerModule( ** ** 3) Calls the sqlite3rbu_step() function one or more times on ** the new handle. Each call to sqlite3rbu_step() performs a single -** b-tree operation, so thousands of calls may be required to apply +** b-tree operation, so thousands of calls may be required to apply ** a complete update. ** ** 4) Calls sqlite3rbu_close() to close the RBU update handle. If ** sqlite3rbu_step() has been called enough times to completely ** apply the update to the target database, then the RBU database -** is marked as fully applied. Otherwise, the state of the RBU -** update application is saved in the RBU database for later +** is marked as fully applied. Otherwise, the state of the RBU +** update application is saved in the RBU database for later ** resumption. ** ** See comments below for more detail on APIs. ** ** If an update is only partially applied to the target database by the -** time sqlite3rbu_close() is called, various state information is saved +** time sqlite3rbu_close() is called, various state information is saved ** within the RBU database. This allows subsequent processes to automatically ** resume the RBU update from where it left off. ** -** To remove all RBU extension state information, returning an RBU database +** To remove all RBU extension state information, returning an RBU database ** to its original contents, it is sufficient to drop all tables that begin ** with the prefix "rbu_" ** @@ -188626,21 +226050,21 @@ typedef struct sqlite3rbu sqlite3rbu; ** the path to the RBU database. Each call to this function must be matched ** by a call to sqlite3rbu_close(). When opening the databases, RBU passes ** the SQLITE_CONFIG_URI flag to sqlite3_open_v2(). So if either zTarget -** or zRbu begin with "file:", it will be interpreted as an SQLite +** or zRbu begin with "file:", it will be interpreted as an SQLite ** database URI, not a regular file name. ** -** If the zState argument is passed a NULL value, the RBU extension stores -** the current state of the update (how many rows have been updated, which +** If the zState argument is passed a NULL value, the RBU extension stores +** the current state of the update (how many rows have been updated, which ** indexes are yet to be updated etc.) within the RBU database itself. This ** can be convenient, as it means that the RBU application does not need to -** organize removing a separate state file after the update is concluded. -** Or, if zState is non-NULL, it must be a path to a database file in which +** organize removing a separate state file after the update is concluded. +** Or, if zState is non-NULL, it must be a path to a database file in which ** the RBU extension can store the state of the update. ** ** When resuming an RBU update, the zState argument must be passed the same ** value as when the RBU update was started. ** -** Once the RBU update is finished, the RBU extension does not +** Once the RBU update is finished, the RBU extension does not ** automatically remove any zState database file, even if it created it. ** ** By default, RBU uses the default VFS to access the files on disk. To @@ -188653,7 +226077,7 @@ typedef struct sqlite3rbu sqlite3rbu; ** the zipvfs_create_vfs() API below for details on using RBU with zipvfs. */ SQLITE_API sqlite3rbu *sqlite3rbu_open( - const char *zTarget, + const char *zTarget, const char *zRbu, const char *zState ); @@ -188663,13 +226087,13 @@ SQLITE_API sqlite3rbu *sqlite3rbu_open( ** An RBU vacuum is similar to SQLite's built-in VACUUM command, except ** that it can be suspended and resumed like an RBU update. ** -** The second argument to this function identifies a database in which -** to store the state of the RBU vacuum operation if it is suspended. The +** The second argument to this function identifies a database in which +** to store the state of the RBU vacuum operation if it is suspended. The ** first time sqlite3rbu_vacuum() is called, to start an RBU vacuum ** operation, the state database should either not exist or be empty -** (contain no tables). If an RBU vacuum is suspended by calling +** (contain no tables). If an RBU vacuum is suspended by calling ** sqlite3rbu_close() on the RBU handle before sqlite3rbu_step() has -** returned SQLITE_DONE, the vacuum state is stored in the state database. +** returned SQLITE_DONE, the vacuum state is stored in the state database. ** The vacuum can be resumed by calling this function to open a new RBU ** handle specifying the same target and state databases. ** @@ -188677,26 +226101,26 @@ SQLITE_API sqlite3rbu *sqlite3rbu_open( ** name of the state database is "-vacuum", where ** is the name of the target database file. In this case, on UNIX, if the ** state database is not already present in the file-system, it is created -** with the same permissions as the target db is made. +** with the same permissions as the target db is made. ** -** With an RBU vacuum, it is an SQLITE_MISUSE error if the name of the -** state database ends with "-vactmp". This name is reserved for internal +** With an RBU vacuum, it is an SQLITE_MISUSE error if the name of the +** state database ends with "-vactmp". This name is reserved for internal ** use. ** ** This function does not delete the state database after an RBU vacuum ** is completed, even if it created it. However, if the call to ** sqlite3rbu_close() returns any value other than SQLITE_OK, the contents ** of the state tables within the state database are zeroed. This way, -** the next call to sqlite3rbu_vacuum() opens a handle that starts a +** the next call to sqlite3rbu_vacuum() opens a handle that starts a ** new RBU vacuum operation. ** -** As with sqlite3rbu_open(), Zipvfs users should rever to the comment -** describing the sqlite3rbu_create_vfs() API function below for -** a description of the complications associated with using RBU with +** As with sqlite3rbu_open(), Zipvfs users should refer to the comment +** describing the sqlite3rbu_create_vfs() API function below for +** a description of the complications associated with using RBU with ** zipvfs databases. */ SQLITE_API sqlite3rbu *sqlite3rbu_vacuum( - const char *zTarget, + const char *zTarget, const char *zState ); @@ -188708,7 +226132,7 @@ SQLITE_API sqlite3rbu *sqlite3rbu_vacuum( ** is removed entirely. If the second parameter is negative, the limit is ** not modified (this is useful for querying the current limit). ** -** In all cases the returned value is the current limit in bytes (zero +** In all cases the returned value is the current limit in bytes (zero ** indicates unlimited). ** ** If the temp space limit is exceeded during operation, an SQLITE_FULL @@ -188717,13 +226141,13 @@ SQLITE_API sqlite3rbu *sqlite3rbu_vacuum( SQLITE_API sqlite3_int64 sqlite3rbu_temp_size_limit(sqlite3rbu*, sqlite3_int64); /* -** Return the current amount of temp file space, in bytes, currently used by +** Return the current amount of temp file space, in bytes, currently used by ** the RBU handle passed as the only argument. */ SQLITE_API sqlite3_int64 sqlite3rbu_temp_size(sqlite3rbu*); /* -** Internally, each RBU connection uses a separate SQLite database +** Internally, each RBU connection uses a separate SQLite database ** connection to access the target and rbu update databases. This ** API allows the application direct access to these database handles. ** @@ -188734,10 +226158,10 @@ SQLITE_API sqlite3_int64 sqlite3rbu_temp_size(sqlite3rbu*); ** following scenarios: ** ** * If any target tables are virtual tables, it may be necessary to -** call sqlite3_create_module() on the target database handle to +** call sqlite3_create_module() on the target database handle to ** register the required virtual table implementations. ** -** * If the data_xxx tables in the RBU source database are virtual +** * If the data_xxx tables in the RBU source database are virtual ** tables, the application may need to call sqlite3_create_module() on ** the rbu update db handle to any required virtual table ** implementations. @@ -188756,12 +226180,12 @@ SQLITE_API sqlite3_int64 sqlite3rbu_temp_size(sqlite3rbu*); SQLITE_API sqlite3 *sqlite3rbu_db(sqlite3rbu*, int bRbu); /* -** Do some work towards applying the RBU update to the target db. +** Do some work towards applying the RBU update to the target db. ** -** Return SQLITE_DONE if the update has been completely applied, or +** Return SQLITE_DONE if the update has been completely applied, or ** SQLITE_OK if no error occurs but there remains work to do to apply -** the RBU update. If an error does occur, some other error code is -** returned. +** the RBU update. If an error does occur, some other error code is +** returned. ** ** Once a call to sqlite3rbu_step() has returned a value other than ** SQLITE_OK, all subsequent calls on the same RBU handle are no-ops @@ -188774,7 +226198,7 @@ SQLITE_API int sqlite3rbu_step(sqlite3rbu *pRbu); ** ** If a power failure or application crash occurs during an update, following ** system recovery RBU may resume the update from the point at which the state -** was last saved. In other words, from the most recent successful call to +** was last saved. In other words, from the most recent successful call to ** sqlite3rbu_close() or this function. ** ** SQLITE_OK is returned if successful, or an SQLite error code otherwise. @@ -188782,11 +226206,11 @@ SQLITE_API int sqlite3rbu_step(sqlite3rbu *pRbu); SQLITE_API int sqlite3rbu_savestate(sqlite3rbu *pRbu); /* -** Close an RBU handle. +** Close an RBU handle. ** ** If the RBU update has been completely applied, mark the RBU database ** as fully applied. Otherwise, assuming no error has occurred, save the -** current state of the RBU update appliation to the RBU database. +** current state of the RBU update application to the RBU database. ** ** If an error has already occurred as part of an sqlite3rbu_step() ** or sqlite3rbu_open() call, or if one occurs within this function, an @@ -188796,20 +226220,20 @@ SQLITE_API int sqlite3rbu_savestate(sqlite3rbu *pRbu); ** eventually free any such buffer using sqlite3_free(). ** ** Otherwise, if no error occurs, this function returns SQLITE_OK if the -** update has been partially applied, or SQLITE_DONE if it has been +** update has been partially applied, or SQLITE_DONE if it has been ** completely applied. */ SQLITE_API int sqlite3rbu_close(sqlite3rbu *pRbu, char **pzErrmsg); /* -** Return the total number of key-value operations (inserts, deletes or +** Return the total number of key-value operations (inserts, deletes or ** updates) that have been performed on the target database since the ** current RBU update was started. */ SQLITE_API sqlite3_int64 sqlite3rbu_progress(sqlite3rbu *pRbu); /* -** Obtain permyriadage (permyriadage is to 10000 as percentage is to 100) +** Obtain permyriadage (permyriadage is to 10000 as percentage is to 100) ** progress indications for the two stages of an RBU update. This API may ** be useful for driving GUI progress indicators and similar. ** @@ -188822,16 +226246,16 @@ SQLITE_API sqlite3_int64 sqlite3rbu_progress(sqlite3rbu *pRbu); ** The update is visible to non-RBU clients during stage 2. During stage 1 ** non-RBU reader clients may see the original database. ** -** If this API is called during stage 2 of the update, output variable +** If this API is called during stage 2 of the update, output variable ** (*pnOne) is set to 10000 to indicate that stage 1 has finished and (*pnTwo) ** to a value between 0 and 10000 to indicate the permyriadage progress of -** stage 2. A value of 5000 indicates that stage 2 is half finished, +** stage 2. A value of 5000 indicates that stage 2 is half finished, ** 9000 indicates that it is 90% finished, and so on. ** -** If this API is called during stage 1 of the update, output variable +** If this API is called during stage 1 of the update, output variable ** (*pnTwo) is set to 0 to indicate that stage 2 has not yet started. The -** value to which (*pnOne) is set depends on whether or not the RBU -** database contains an "rbu_count" table. The rbu_count table, if it +** value to which (*pnOne) is set depends on whether or not the RBU +** database contains an "rbu_count" table. The rbu_count table, if it ** exists, must contain the same columns as the following: ** ** CREATE TABLE rbu_count(tbl TEXT PRIMARY KEY, cnt INTEGER) WITHOUT ROWID; @@ -188888,22 +226312,50 @@ SQLITE_API void sqlite3rbu_bp_progress(sqlite3rbu *pRbu, int *pnOne, int*pnTwo); SQLITE_API int sqlite3rbu_state(sqlite3rbu *pRbu); +/* +** As part of applying an RBU update or performing an RBU vacuum operation, +** the system must at one point move the *-oal file to the equivalent *-wal +** path. Normally, it does this by invoking POSIX function rename(2) directly. +** Except on WINCE platforms, where it uses win32 API MoveFileW(). This +** function may be used to register a callback that the RBU module will invoke +** instead of one of these APIs. +** +** If a callback is registered with an RBU handle, it invokes it instead +** of rename(2) when it needs to move a file within the file-system. The +** first argument passed to the xRename() callback is a copy of the second +** argument (pArg) passed to this function. The second is the full path +** to the file to move and the third the full path to which it should be +** moved. The callback function should return SQLITE_OK to indicate +** success. If an error occurs, it should return an SQLite error code. +** In this case the RBU operation will be abandoned and the error returned +** to the RBU user. +** +** Passing a NULL pointer in place of the xRename argument to this function +** restores the default behaviour. +*/ +SQLITE_API void sqlite3rbu_rename_handler( + sqlite3rbu *pRbu, + void *pArg, + int (*xRename)(void *pArg, const char *zOld, const char *zNew) +); + + /* ** Create an RBU VFS named zName that accesses the underlying file-system -** via existing VFS zParent. Or, if the zParent parameter is passed NULL, +** via existing VFS zParent. Or, if the zParent parameter is passed NULL, ** then the new RBU VFS uses the default system VFS to access the file-system. -** The new object is registered as a non-default VFS with SQLite before +** The new object is registered as a non-default VFS with SQLite before ** returning. ** ** Part of the RBU implementation uses a custom VFS object. Usually, this -** object is created and deleted automatically by RBU. +** object is created and deleted automatically by RBU. ** ** The exception is for applications that also use zipvfs. In this case, ** the custom VFS must be explicitly created by the user before the RBU ** handle is opened. The RBU VFS should be installed so that the zipvfs -** VFS uses the RBU VFS, which in turn uses any other VFS layers in use +** VFS uses the RBU VFS, which in turn uses any other VFS layers in use ** (for example multiplexor) to access the file-system. For example, -** to assemble an RBU enabled VFS stack that uses both zipvfs and +** to assemble an RBU enabled VFS stack that uses both zipvfs and ** multiplexor (error checking omitted): ** ** // Create a VFS named "multiplex" (not the default). @@ -188925,9 +226377,9 @@ SQLITE_API int sqlite3rbu_state(sqlite3rbu *pRbu); ** may be used by RBU clients. Attempting to use RBU with a zipvfs VFS stack ** that does not include the RBU layer results in an error. ** -** The overhead of adding the "rbu" VFS to the system is negligible for -** non-RBU users. There is no harm in an application accessing the -** file-system via "rbu" all the time, even if it only uses RBU functionality +** The overhead of adding the "rbu" VFS to the system is negligible for +** non-RBU users. There is no harm in an application accessing the +** file-system via "rbu" all the time, even if it only uses RBU functionality ** occasionally. */ SQLITE_API int sqlite3rbu_create_vfs(const char *zName, const char *zParent); @@ -188972,6 +226424,13 @@ SQLITE_API void sqlite3rbu_destroy_vfs(const char *zName); # define SWAP(TYPE,A,B) {TYPE t=A; A=B; B=t;} #endif +/* +** Name of the URI option that causes RBU to take an exclusive lock as +** part of the incremental checkpoint operation. +*/ +#define RBU_EXCLUSIVE_CHECKPOINT "rbu_exclusive_checkpoint" + + /* ** The rbu_state table is used to save the state of a partially applied ** update so that it can be resumed later. The table consists of integer @@ -188980,17 +226439,17 @@ SQLITE_API void sqlite3rbu_destroy_vfs(const char *zName); ** RBU_STATE_STAGE: ** May be set to integer values 1, 2, 4 or 5. As follows: ** 1: the *-rbu file is currently under construction. -** 2: the *-rbu file has been constructed, but not yet moved +** 2: the *-rbu file has been constructed, but not yet moved ** to the *-wal path. ** 4: the checkpoint is underway. ** 5: the rbu update has been checkpointed. ** ** RBU_STATE_TBL: -** Only valid if STAGE==1. The target database name of the table +** Only valid if STAGE==1. The target database name of the table ** currently being written. ** ** RBU_STATE_IDX: -** Only valid if STAGE==1. The target database name of the index +** Only valid if STAGE==1. The target database name of the index ** currently being written, or NULL if the main table is currently being ** updated. ** @@ -189010,14 +226469,14 @@ SQLITE_API void sqlite3rbu_destroy_vfs(const char *zName); ** be continued if this happens). ** ** RBU_STATE_COOKIE: -** Valid if STAGE==1. The current change-counter cookie value in the +** Valid if STAGE==1. The current change-counter cookie value in the ** target db file. ** ** RBU_STATE_OALSZ: ** Valid if STAGE==1. The size in bytes of the *-oal file. ** ** RBU_STATE_DATATBL: -** Only valid if STAGE==1. The RBU database name of the table +** Only valid if STAGE==1. The RBU database name of the table ** currently being read. */ #define RBU_STATE_STAGE 1 @@ -189044,6 +226503,7 @@ SQLITE_API void sqlite3rbu_destroy_vfs(const char *zName); typedef struct RbuFrame RbuFrame; typedef struct RbuObjIter RbuObjIter; typedef struct RbuState RbuState; +typedef struct RbuSpan RbuSpan; typedef struct rbu_vfs rbu_vfs; typedef struct rbu_file rbu_file; typedef struct RbuUpdateStmt RbuUpdateStmt; @@ -189053,6 +226513,7 @@ typedef unsigned int u32; typedef unsigned short u16; typedef unsigned char u8; typedef sqlite3_int64 i64; +typedef sqlite3_uint64 u64; #endif /* @@ -189088,12 +226549,17 @@ struct RbuUpdateStmt { RbuUpdateStmt *pNext; }; +struct RbuSpan { + const char *zSpan; + int nSpan; +}; + /* ** An iterator of this type is used to iterate through all objects in ** the target database that require updating. For each such table, the ** iterator visits, in order: ** -** * the table itself, +** * the table itself, ** * each index of the table (zero or more points to visit), and ** * a special "cleanup table" state. ** @@ -189107,7 +226573,7 @@ struct RbuUpdateStmt { ** this array set set to 1. This is because in that case, the module has ** no way to tell which fields will be required to add and remove entries ** from the partial indexes. -** +** */ struct RbuObjIter { sqlite3_stmt *pTblIter; /* Iterate through tables */ @@ -189137,6 +226603,9 @@ struct RbuObjIter { sqlite3_stmt *pInsert; /* Statement for INSERT operations */ sqlite3_stmt *pDelete; /* Statement for DELETE ops */ sqlite3_stmt *pTmpInsert; /* Insert into rbu_tmp_$zDataTbl */ + int nIdxCol; + RbuSpan *aIdxCol; + char *zIdxSql; /* Last UPDATE used (for PK b-tree updates only), or NULL. */ RbuUpdateStmt *pRbuUpdate; @@ -189181,12 +226650,33 @@ struct RbuFrame { u32 iWalFrame; }; +#ifndef UNUSED_PARAMETER +/* +** The following macros are used to suppress compiler warnings and to +** make it clear to human readers when a function parameter is deliberately +** left unused within the body of a function. This usually happens when +** a function is called via a function pointer. For example the +** implementation of an SQL aggregate step callback may not use the +** parameter indicating the number of arguments passed to the aggregate, +** if it knows that this is enforced elsewhere. +** +** When a function parameter is not used at all within the body of a function, +** it is generally named "NotUsed" or "NotUsed2" to make things even clearer. +** However, these macros may also be used to suppress warnings related to +** parameters that may or may not be used depending on compilation options. +** For example those parameters only used in assert() statements. In these +** cases the parameters are named as per the usual conventions. +*/ +#define UNUSED_PARAMETER(x) (void)(x) +#define UNUSED_PARAMETER2(x,y) UNUSED_PARAMETER(x),UNUSED_PARAMETER(y) +#endif + /* ** RBU handle. ** ** nPhaseOneStep: ** If the RBU database contains an rbu_count table, this value is set to -** a running estimate of the number of b-tree operations required to +** a running estimate of the number of b-tree operations required to ** finish populating the *-oal file. This allows the sqlite3_bp_progress() ** API to calculate the permyriadage progress of populating the *-oal file ** using the formula: @@ -189206,7 +226696,7 @@ struct RbuFrame { ** ** * the RBU update contains any UPDATE operations. If the PK specified ** for an UPDATE operation does not exist in the target table, then -** no b-tree operations are required on index b-trees. Or if the +** no b-tree operations are required on index b-trees. Or if the ** specified PK does exist, then (nIndex*2) such operations are ** required (one delete and one insert on each index b-tree). ** @@ -189232,13 +226722,15 @@ struct sqlite3rbu { int rc; /* Value returned by last rbu_step() call */ char *zErrmsg; /* Error message if rc!=SQLITE_OK */ int nStep; /* Rows processed for current object */ - int nProgress; /* Rows processed for all objects */ + sqlite3_int64 nProgress; /* Rows processed for all objects */ RbuObjIter objiter; /* Iterator for skipping through tbl/idx */ const char *zVfsName; /* Name of automatically created rbu vfs */ rbu_file *pTargetFd; /* File handle open on target db */ int nPagePerSector; /* Pages per sector for pTargetFd */ i64 iOalSz; i64 nPhaseOneStep; + void *pRenameArg; + int (*xRename)(void*, const char*, const char*); /* The following state variables are used as part of the incremental ** checkpoint stage (eStage==RBU_STAGE_CKPT). See comments surrounding @@ -189338,16 +226830,26 @@ static unsigned int rbuDeltaGetInt(const char **pz, int *pLen){ 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, 36, -1, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, -1, -1, -1, 63, -1, + + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, }; unsigned int v = 0; int c; unsigned char *z = (unsigned char*)*pz; - unsigned char *zStart = z; - while( (c = zValue[0x7f&*(z++)])>=0 ){ - v = (v<<6) + c; + unsigned char *zEnd = z + (*pLen); + while( z=0 ){ + v = (v<<6) + c; + z++; } - z--; - *pLen -= z - zStart; + + *pLen -= (int)(z - (unsigned char*)*pz); *pz = (char*)z; return v; } @@ -189416,21 +226918,22 @@ static int rbuDeltaApply( int lenDelta, /* Length of the delta */ char *zOut /* Write the output into this preallocated buffer */ ){ - unsigned int limit; - unsigned int total = 0; + sqlite3_uint64 limit; + sqlite3_uint64 total = 0; #if RBU_ENABLE_DELTA_CKSUM char *zOrigOut = zOut; #endif limit = rbuDeltaGetInt(&zDelta, &lenDelta); - if( *zDelta!='\n' ){ + if( lenDelta<=0 || *zDelta!='\n' ){ /* ERROR: size integer not terminated by "\n" */ return -1; } - zDelta++; lenDelta--; - while( *zDelta && lenDelta>0 ){ + zDelta++; lenDelta--; /* Skip the \n */ + while( lenDelta>0 && zDelta[0] ){ unsigned int cnt, ofst; cnt = rbuDeltaGetInt(&zDelta, &lenDelta); + if( lenDelta<=0 ) return -1; switch( zDelta[0] ){ case '@': { zDelta++; lenDelta--; @@ -189445,7 +226948,7 @@ static int rbuDeltaApply( /* ERROR: copy exceeds output file size */ return -1; } - if( (int)(ofst+cnt) > lenSrc ){ + if( (u64)ofst+(u64)cnt > (u64)lenSrc ){ /* ERROR: copy extends past end of input */ return -1; } @@ -189460,7 +226963,7 @@ static int rbuDeltaApply( /* ERROR: insert command gives an output larger than predicted */ return -1; } - if( (int)cnt>lenDelta ){ + if( cnt>lenDelta ){ /* ERROR: insert count exceeds size of delta */ return -1; } @@ -189498,7 +227001,7 @@ static int rbuDeltaApply( static int rbuDeltaOutputSize(const char *zDelta, int lenDelta){ int size; size = rbuDeltaGetInt(&zDelta, &lenDelta); - if( *zDelta!='\n' ){ + if( lenDelta<=0 || *zDelta!='\n' ){ /* ERROR: size integer not terminated by "\n" */ return -1; } @@ -189532,6 +227035,7 @@ static void rbuFossilDeltaFunc( char *aOut; assert( argc==2 ); + UNUSED_PARAMETER(argc); nOrig = sqlite3_value_bytes(argv[0]); aOrig = (const char*)sqlite3_value_blob(argv[0]); @@ -189545,7 +227049,7 @@ static void rbuFossilDeltaFunc( return; } - aOut = sqlite3_malloc(nOut+1); + aOut = sqlite3_malloc64((i64)nOut+1); if( aOut==0 ){ sqlite3_result_error_nomem(context); }else{ @@ -189563,7 +227067,7 @@ static void rbuFossilDeltaFunc( /* ** Prepare the SQL statement in buffer zSql against database handle db. ** If successful, set *ppStmt to point to the new statement and return -** SQLITE_OK. +** SQLITE_OK. ** ** Otherwise, if an error does occur, set *ppStmt to NULL and return ** an SQLite error code. Additionally, set output variable *pzErrmsg to @@ -189571,7 +227075,7 @@ static void rbuFossilDeltaFunc( ** of the caller to (eventually) free this buffer using sqlite3_free(). */ static int prepareAndCollectError( - sqlite3 *db, + sqlite3 *db, sqlite3_stmt **ppStmt, char **pzErrmsg, const char *zSql @@ -189603,9 +227107,9 @@ static int resetAndCollectError(sqlite3_stmt *pStmt, char **pzErrmsg){ /* ** Unless it is NULL, argument zSql points to a buffer allocated using ** sqlite3_malloc containing an SQL statement. This function prepares the SQL -** statement against database db and frees the buffer. If statement -** compilation is successful, *ppStmt is set to point to the new statement -** handle and SQLITE_OK is returned. +** statement against database db and frees the buffer. If statement +** compilation is successful, *ppStmt is set to point to the new statement +** handle and SQLITE_OK is returned. ** ** Otherwise, if an error occurs, *ppStmt is set to NULL and an error code ** returned. In this case, *pzErrmsg may also be set to point to an error @@ -189616,7 +227120,7 @@ static int resetAndCollectError(sqlite3_stmt *pStmt, char **pzErrmsg){ ** In this case SQLITE_NOMEM is returned and *ppStmt set to NULL. */ static int prepareFreeAndCollectError( - sqlite3 *db, + sqlite3 *db, sqlite3_stmt **ppStmt, char **pzErrmsg, char *zSql @@ -189671,13 +227175,18 @@ static void rbuObjIterClearStatements(RbuObjIter *pIter){ sqlite3_free(pUp); pUp = pTmp; } - + sqlite3_free(pIter->aIdxCol); + sqlite3_free(pIter->zIdxSql); + pIter->pSelect = 0; pIter->pInsert = 0; pIter->pDelete = 0; pIter->pRbuUpdate = 0; pIter->pTmpInsert = 0; pIter->nCol = 0; + pIter->nIdxCol = 0; + pIter->aIdxCol = 0; + pIter->zIdxSql = 0; } /* @@ -189695,16 +227204,16 @@ static void rbuObjIterFinalize(RbuObjIter *pIter){ /* ** Advance the iterator to the next position. ** -** If no error occurs, SQLITE_OK is returned and the iterator is left -** pointing to the next entry. Otherwise, an error code and message is -** left in the RBU handle passed as the first argument. A copy of the +** If no error occurs, SQLITE_OK is returned and the iterator is left +** pointing to the next entry. Otherwise, an error code and message is +** left in the RBU handle passed as the first argument. A copy of the ** error code is returned. */ static int rbuObjIterNext(sqlite3rbu *p, RbuObjIter *pIter){ int rc = p->rc; if( rc==SQLITE_OK ){ - /* Free any SQLite statements used while processing the previous object */ + /* Free any SQLite statements used while processing the previous object */ rbuObjIterClearStatements(pIter); if( pIter->zIdx==0 ){ rc = sqlite3_exec(p->dbMain, @@ -189724,6 +227233,7 @@ static int rbuObjIterNext(sqlite3rbu *p, RbuObjIter *pIter){ if( rc!=SQLITE_ROW ){ rc = resetAndCollectError(pIter->pTblIter, &p->zErrmsg); pIter->zTbl = 0; + pIter->zDataTbl = 0; }else{ pIter->zTbl = (const char*)sqlite3_column_text(pIter->pTblIter, 0); pIter->zDataTbl = (const char*)sqlite3_column_text(pIter->pTblIter,1); @@ -189763,7 +227273,7 @@ static int rbuObjIterNext(sqlite3rbu *p, RbuObjIter *pIter){ ** The implementation of the rbu_target_name() SQL function. This function ** accepts one or two arguments. The first argument is the name of a table - ** the name of a table in the RBU database. The second, if it is present, is 1 -** for a view or 0 for a table. +** for a view or 0 for a table. ** ** For a non-vacuum RBU handle, if the table name matches the pattern: ** @@ -189792,6 +227302,7 @@ static void rbuTargetNameFunc( zIn = (const char*)sqlite3_value_text(argv[0]); if( zIn ){ if( rbuIsVacuum(p) ){ + assert( argc==2 || argc==1 ); if( argc==1 || 0==sqlite3_value_int(argv[1]) ){ sqlite3_result_text(pCtx, zIn, -1, SQLITE_STATIC); } @@ -189810,19 +227321,19 @@ static void rbuTargetNameFunc( /* ** Initialize the iterator structure passed as the second argument. ** -** If no error occurs, SQLITE_OK is returned and the iterator is left -** pointing to the first entry. Otherwise, an error code and message is -** left in the RBU handle passed as the first argument. A copy of the +** If no error occurs, SQLITE_OK is returned and the iterator is left +** pointing to the first entry. Otherwise, an error code and message is +** left in the RBU handle passed as the first argument. A copy of the ** error code is returned. */ static int rbuObjIterFirst(sqlite3rbu *p, RbuObjIter *pIter){ int rc; memset(pIter, 0, sizeof(RbuObjIter)); - rc = prepareFreeAndCollectError(p->dbRbu, &pIter->pTblIter, &p->zErrmsg, + rc = prepareFreeAndCollectError(p->dbRbu, &pIter->pTblIter, &p->zErrmsg, sqlite3_mprintf( "SELECT rbu_target_name(name, type='view') AS target, name " - "FROM sqlite_master " + "FROM sqlite_schema " "WHERE type IN ('table', 'view') AND target IS NOT NULL " " %s " "ORDER BY name" @@ -189831,7 +227342,7 @@ static int rbuObjIterFirst(sqlite3rbu *p, RbuObjIter *pIter){ if( rc==SQLITE_OK ){ rc = prepareAndCollectError(p->dbMain, &pIter->pIdxIter, &p->zErrmsg, "SELECT name, rootpage, sql IS NULL OR substr(8, 6)=='UNIQUE' " - " FROM main.sqlite_master " + " FROM main.sqlite_schema " " WHERE type='index' AND tbl_name = ?" ); } @@ -189847,7 +227358,7 @@ static int rbuObjIterFirst(sqlite3rbu *p, RbuObjIter *pIter){ ** ** If an error has already occurred (p->rc is already set to something other ** than SQLITE_OK), then this function returns NULL without modifying the -** stored error code. In this case it still calls sqlite3_free() on any +** stored error code. In this case it still calls sqlite3_free() on any ** printf() parameters associated with %z conversions. */ static char *rbuMPrintf(sqlite3rbu *p, const char *zFmt, ...){ @@ -189893,12 +227404,12 @@ static int rbuMPrintfExec(sqlite3rbu *p, sqlite3 *db, const char *zFmt, ...){ } /* -** Attempt to allocate and return a pointer to a zeroed block of nByte -** bytes. +** Attempt to allocate and return a pointer to a zeroed block of nByte +** bytes. ** -** If an error (i.e. an OOM condition) occurs, return NULL and leave an -** error code in the rbu handle passed as the first argument. Or, if an -** error has already occurred when this function is called, return NULL +** If an error (i.e. an OOM condition) occurs, return NULL and leave an +** error code in the rbu handle passed as the first argument. Or, if an +** error has already occurred when this function is called, return NULL ** immediately without attempting the allocation or modifying the stored ** error code. */ @@ -189950,14 +227461,15 @@ static void rbuAllocateIterArrays(sqlite3rbu *p, RbuObjIter *pIter, int nCol){ static char *rbuStrndup(const char *zStr, int *pRc){ char *zRet = 0; - assert( *pRc==SQLITE_OK ); - if( zStr ){ - size_t nCopy = strlen(zStr) + 1; - zRet = (char*)sqlite3_malloc64(nCopy); - if( zRet ){ - memcpy(zRet, zStr, nCopy); - }else{ - *pRc = SQLITE_NOMEM; + if( *pRc==SQLITE_OK ){ + if( zStr ){ + size_t nCopy = strlen(zStr) + 1; + zRet = (char*)sqlite3_malloc64(nCopy); + if( zRet ){ + memcpy(zRet, zStr, nCopy); + }else{ + *pRc = SQLITE_NOMEM; + } } } @@ -189994,7 +227506,7 @@ static void rbuFinalize(sqlite3rbu *p, sqlite3_stmt *pStmt){ ** RBU_PK_VTAB: Table is a virtual table. ** ** Argument *piPk is also of type (int*), and also points to an output -** parameter. Unless the table has an external primary key index +** parameter. Unless the table has an external primary key index ** (i.e. unless *peType is set to 3), then *piPk is set to zero. Or, ** if the table does have an external primary key index, then *piPk ** is set to the root page number of the primary key index before @@ -190002,12 +227514,12 @@ static void rbuFinalize(sqlite3rbu *p, sqlite3_stmt *pStmt){ ** ** ALGORITHM: ** -** if( no entry exists in sqlite_master ){ +** if( no entry exists in sqlite_schema ){ ** return RBU_PK_NOTABLE ** }else if( sql for the entry starts with "CREATE VIRTUAL" ){ ** return RBU_PK_VTAB ** }else if( "PRAGMA index_list()" for the table contains a "pk" index ){ -** if( the index that is the pk exists in sqlite_master ){ +** if( the index that is the pk exists in sqlite_schema ){ ** *piPK = rootpage of that index. ** return RBU_PK_EXTERNAL ** }else{ @@ -190027,9 +227539,9 @@ static void rbuTableType( int *piPk ){ /* - ** 0) SELECT count(*) FROM sqlite_master where name=%Q AND IsVirtual(%Q) + ** 0) SELECT count(*) FROM sqlite_schema where name=%Q AND IsVirtual(%Q) ** 1) PRAGMA index_list = ? - ** 2) SELECT count(*) FROM sqlite_master where name=%Q + ** 2) SELECT count(*) FROM sqlite_schema where name=%Q ** 3) PRAGMA table_info = ? */ sqlite3_stmt *aStmt[4] = {0, 0, 0, 0}; @@ -190038,10 +227550,12 @@ static void rbuTableType( *piPk = 0; assert( p->rc==SQLITE_OK ); - p->rc = prepareFreeAndCollectError(p->dbMain, &aStmt[0], &p->zErrmsg, + p->rc = prepareFreeAndCollectError(p->dbMain, &aStmt[0], &p->zErrmsg, sqlite3_mprintf( - "SELECT (sql LIKE 'create virtual%%'), rootpage" - " FROM sqlite_master" + "SELECT " + " (sql COLLATE nocase BETWEEN 'CREATE VIRTUAL' AND 'CREATE VIRTUAM')," + " rootpage" + " FROM sqlite_schema" " WHERE name=%Q", zTab )); if( p->rc!=SQLITE_OK || sqlite3_step(aStmt[0])!=SQLITE_ROW ){ @@ -190054,7 +227568,7 @@ static void rbuTableType( } *piTnum = sqlite3_column_int(aStmt[0], 1); - p->rc = prepareFreeAndCollectError(p->dbMain, &aStmt[1], &p->zErrmsg, + p->rc = prepareFreeAndCollectError(p->dbMain, &aStmt[1], &p->zErrmsg, sqlite3_mprintf("PRAGMA index_list=%Q",zTab) ); if( p->rc ) goto rbuTableType_end; @@ -190062,9 +227576,9 @@ static void rbuTableType( const u8 *zOrig = sqlite3_column_text(aStmt[1], 3); const u8 *zIdx = sqlite3_column_text(aStmt[1], 1); if( zOrig && zIdx && zOrig[0]=='p' ){ - p->rc = prepareFreeAndCollectError(p->dbMain, &aStmt[2], &p->zErrmsg, + p->rc = prepareFreeAndCollectError(p->dbMain, &aStmt[2], &p->zErrmsg, sqlite3_mprintf( - "SELECT rootpage FROM sqlite_master WHERE name = %Q", zIdx + "SELECT rootpage FROM sqlite_schema WHERE name = %Q", zIdx )); if( p->rc==SQLITE_OK ){ if( sqlite3_step(aStmt[2])==SQLITE_ROW ){ @@ -190078,7 +227592,7 @@ static void rbuTableType( } } - p->rc = prepareFreeAndCollectError(p->dbMain, &aStmt[3], &p->zErrmsg, + p->rc = prepareFreeAndCollectError(p->dbMain, &aStmt[3], &p->zErrmsg, sqlite3_mprintf("PRAGMA table_info=%Q",zTab) ); if( p->rc==SQLITE_OK ){ @@ -190129,6 +227643,9 @@ static void rbuObjIterCacheIndexedCols(sqlite3rbu *p, RbuObjIter *pIter){ while( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pXInfo) ){ int iCid = sqlite3_column_int(pXInfo, 1); if( iCid>=0 ) pIter->abIndexed[iCid] = 1; + if( iCid==-2 ){ + memset(pIter->abIndexed, 0x01, sizeof(u8)*pIter->nTblCol); + } } rbuFinalize(p, pXInfo); bIndex = 1; @@ -190151,7 +227668,7 @@ static void rbuObjIterCacheIndexedCols(sqlite3rbu *p, RbuObjIter *pIter){ ** the table (not index) that the iterator currently points to. ** ** Return SQLITE_OK if successful, or an SQLite error code otherwise. If -** an error does occur, an error code and error message are also left in +** an error does occur, an error code and error message are also left in ** the RBU handle. */ static int rbuObjIterCacheTableInfo(sqlite3rbu *p, RbuObjIter *pIter){ @@ -190173,7 +227690,7 @@ static int rbuObjIterCacheTableInfo(sqlite3rbu *p, RbuObjIter *pIter){ if( p->rc ) return p->rc; if( pIter->zIdx==0 ) pIter->iTnum = iTnum; - assert( pIter->eType==RBU_PK_NONE || pIter->eType==RBU_PK_IPK + assert( pIter->eType==RBU_PK_NONE || pIter->eType==RBU_PK_IPK || pIter->eType==RBU_PK_EXTERNAL || pIter->eType==RBU_PK_WITHOUT_ROWID || pIter->eType==RBU_PK_VTAB ); @@ -190181,7 +227698,7 @@ static int rbuObjIterCacheTableInfo(sqlite3rbu *p, RbuObjIter *pIter){ /* Populate the azTblCol[] and nTblCol variables based on the columns ** of the input table. Ignore any input table columns that begin with ** "rbu_". */ - p->rc = prepareFreeAndCollectError(p->dbRbu, &pStmt, &p->zErrmsg, + p->rc = prepareFreeAndCollectError(p->dbRbu, &pStmt, &p->zErrmsg, sqlite3_mprintf("SELECT * FROM '%q'", pIter->zDataTbl) ); if( p->rc==SQLITE_OK ){ @@ -190217,7 +227734,7 @@ static int rbuObjIterCacheTableInfo(sqlite3rbu *p, RbuObjIter *pIter){ ** present in the input table. Populate the abTblPk[], azTblType[] and ** aiTblOrder[] arrays at the same time. */ if( p->rc==SQLITE_OK ){ - p->rc = prepareFreeAndCollectError(p->dbMain, &pStmt, &p->zErrmsg, + p->rc = prepareFreeAndCollectError(p->dbMain, &pStmt, &p->zErrmsg, sqlite3_mprintf("PRAGMA table_info(%Q)", pIter->zTbl) ); } @@ -190243,7 +227760,8 @@ static int rbuObjIterCacheTableInfo(sqlite3rbu *p, RbuObjIter *pIter){ } pIter->azTblType[iOrder] = rbuStrndup(zType, &p->rc); - pIter->abTblPk[iOrder] = (iPk!=0); + assert( iPk>=0 ); + pIter->abTblPk[iOrder] = (u8)iPk; pIter->abNotNull[iOrder] = (u8)bNotNull || (iPk!=0); iOrder++; } @@ -190259,8 +227777,8 @@ static int rbuObjIterCacheTableInfo(sqlite3rbu *p, RbuObjIter *pIter){ } /* -** This function constructs and returns a pointer to a nul-terminated -** string containing some SQL clause or list based on one or more of the +** This function constructs and returns a pointer to a nul-terminated +** string containing some SQL clause or list based on one or more of the ** column names currently stored in the pIter->azTblCol[] array. */ static char *rbuObjIterGetCollist( @@ -190279,23 +227797,232 @@ static char *rbuObjIterGetCollist( } /* -** This function is used to create a SELECT list (the list of SQL -** expressions that follows a SELECT keyword) for a SELECT statement -** used to read from an data_xxx or rbu_tmp_xxx table while updating the -** index object currently indicated by the iterator object passed as the -** second argument. A "PRAGMA index_xinfo = " statement is used +** Return a comma separated list of the quoted PRIMARY KEY column names, +** in order, for the current table. Before each column name, add the text +** zPre. After each column name, add the zPost text. Use zSeparator as +** the separator text (usually ", "). +*/ +static char *rbuObjIterGetPkList( + sqlite3rbu *p, /* RBU object */ + RbuObjIter *pIter, /* Object iterator for column names */ + const char *zPre, /* Before each quoted column name */ + const char *zSeparator, /* Separator to use between columns */ + const char *zPost /* After each quoted column name */ +){ + int iPk = 1; + char *zRet = 0; + const char *zSep = ""; + while( 1 ){ + int i; + for(i=0; inTblCol; i++){ + if( (int)pIter->abTblPk[i]==iPk ){ + const char *zCol = pIter->azTblCol[i]; + zRet = rbuMPrintf(p, "%z%s%s\"%w\"%s", zRet, zSep, zPre, zCol, zPost); + zSep = zSeparator; + break; + } + } + if( i==pIter->nTblCol ) break; + iPk++; + } + return zRet; +} + +/* +** This function is called as part of restarting an RBU vacuum within +** stage 1 of the process (while the *-oal file is being built) while +** updating a table (not an index). The table may be a rowid table or +** a WITHOUT ROWID table. It queries the target database to find the +** largest key that has already been written to the target table and +** constructs a WHERE clause that can be used to extract the remaining +** rows from the source table. For a rowid table, the WHERE clause +** is of the form: +** +** "WHERE _rowid_ > ?" +** +** and for WITHOUT ROWID tables: +** +** "WHERE (key1, key2) > (?, ?)" +** +** Instead of "?" placeholders, the actual WHERE clauses created by +** this function contain literal SQL values. +*/ +static char *rbuVacuumTableStart( + sqlite3rbu *p, /* RBU handle */ + RbuObjIter *pIter, /* RBU iterator object */ + int bRowid, /* True for a rowid table */ + const char *zWrite /* Target table name prefix */ +){ + sqlite3_stmt *pMax = 0; + char *zRet = 0; + if( bRowid ){ + p->rc = prepareFreeAndCollectError(p->dbMain, &pMax, &p->zErrmsg, + sqlite3_mprintf( + "SELECT max(_rowid_) FROM \"%s%w\"", zWrite, pIter->zTbl + ) + ); + if( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pMax) ){ + sqlite3_int64 iMax = sqlite3_column_int64(pMax, 0); + zRet = rbuMPrintf(p, " WHERE _rowid_ > %lld ", iMax); + } + rbuFinalize(p, pMax); + }else{ + char *zOrder = rbuObjIterGetPkList(p, pIter, "", ", ", " DESC"); + char *zSelect = rbuObjIterGetPkList(p, pIter, "quote(", "||','||", ")"); + char *zList = rbuObjIterGetPkList(p, pIter, "", ", ", ""); + + if( p->rc==SQLITE_OK ){ + p->rc = prepareFreeAndCollectError(p->dbMain, &pMax, &p->zErrmsg, + sqlite3_mprintf( + "SELECT %s FROM \"%s%w\" ORDER BY %s LIMIT 1", + zSelect, zWrite, pIter->zTbl, zOrder + ) + ); + if( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pMax) ){ + const char *zVal = (const char*)sqlite3_column_text(pMax, 0); + zRet = rbuMPrintf(p, " WHERE (%s) > (%s) ", zList, zVal); + } + rbuFinalize(p, pMax); + } + + sqlite3_free(zOrder); + sqlite3_free(zSelect); + sqlite3_free(zList); + } + return zRet; +} + +/* +** This function is called as part of restating an RBU vacuum when the +** current operation is writing content to an index. If possible, it +** queries the target index b-tree for the largest key already written to +** it, then composes and returns an expression that can be used in a WHERE +** clause to select the remaining required rows from the source table. +** It is only possible to return such an expression if: +** +** * The index contains no DESC columns, and +** * The last key written to the index before the operation was +** suspended does not contain any NULL values. +** +** The expression is of the form: +** +** (index-field1, index-field2, ...) > (?, ?, ...) +** +** except that the "?" placeholders are replaced with literal values. +** +** If the expression cannot be created, NULL is returned. In this case, +** the caller has to use an OFFSET clause to extract only the required +** rows from the sourct table, just as it does for an RBU update operation. +*/ +static char *rbuVacuumIndexStart( + sqlite3rbu *p, /* RBU handle */ + RbuObjIter *pIter /* RBU iterator object */ +){ + char *zOrder = 0; + char *zLhs = 0; + char *zSelect = 0; + char *zVector = 0; + char *zRet = 0; + int bFailed = 0; + const char *zSep = ""; + int iCol = 0; + sqlite3_stmt *pXInfo = 0; + + p->rc = prepareFreeAndCollectError(p->dbMain, &pXInfo, &p->zErrmsg, + sqlite3_mprintf("PRAGMA main.index_xinfo = %Q", pIter->zIdx) + ); + while( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pXInfo) ){ + int iCid = sqlite3_column_int(pXInfo, 1); + const char *zCollate = (const char*)sqlite3_column_text(pXInfo, 4); + const char *zCol; + if( sqlite3_column_int(pXInfo, 3) ){ + bFailed = 1; + break; + } + + if( iCid<0 ){ + if( pIter->eType==RBU_PK_IPK ){ + int i; + for(i=0; pIter->abTblPk[i]==0; i++); + assert( inTblCol ); + zCol = pIter->azTblCol[i]; + }else{ + zCol = "_rowid_"; + } + }else{ + zCol = pIter->azTblCol[iCid]; + } + + zLhs = rbuMPrintf(p, "%z%s \"%w\" COLLATE %Q", + zLhs, zSep, zCol, zCollate + ); + zOrder = rbuMPrintf(p, "%z%s \"rbu_imp_%d%w\" COLLATE %Q DESC", + zOrder, zSep, iCol, zCol, zCollate + ); + zSelect = rbuMPrintf(p, "%z%s quote(\"rbu_imp_%d%w\")", + zSelect, zSep, iCol, zCol + ); + zSep = ", "; + iCol++; + } + rbuFinalize(p, pXInfo); + if( bFailed ) goto index_start_out; + + if( p->rc==SQLITE_OK ){ + sqlite3_stmt *pSel = 0; + + p->rc = prepareFreeAndCollectError(p->dbMain, &pSel, &p->zErrmsg, + sqlite3_mprintf("SELECT %s FROM \"rbu_imp_%w\" ORDER BY %s LIMIT 1", + zSelect, pIter->zTbl, zOrder + ) + ); + if( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSel) ){ + zSep = ""; + for(iCol=0; iColnCol; iCol++){ + const char *zQuoted = (const char*)sqlite3_column_text(pSel, iCol); + if( zQuoted==0 ){ + p->rc = SQLITE_NOMEM; + }else if( zQuoted[0]=='N' ){ + bFailed = 1; + break; + } + zVector = rbuMPrintf(p, "%z%s%s", zVector, zSep, zQuoted); + zSep = ", "; + } + + if( !bFailed ){ + zRet = rbuMPrintf(p, "(%s) > (%s)", zLhs, zVector); + } + } + rbuFinalize(p, pSel); + } + + index_start_out: + sqlite3_free(zOrder); + sqlite3_free(zSelect); + sqlite3_free(zVector); + sqlite3_free(zLhs); + return zRet; +} + +/* +** This function is used to create a SELECT list (the list of SQL +** expressions that follows a SELECT keyword) for a SELECT statement +** used to read from an data_xxx or rbu_tmp_xxx table while updating the +** index object currently indicated by the iterator object passed as the +** second argument. A "PRAGMA index_xinfo = " statement is used ** to obtain the required information. ** ** If the index is of the following form: ** ** CREATE INDEX i1 ON t1(c, b COLLATE nocase); ** -** and "t1" is a table with an explicit INTEGER PRIMARY KEY column +** and "t1" is a table with an explicit INTEGER PRIMARY KEY column ** "ipk", the returned string is: ** ** "`c` COLLATE 'BINARY', `b` COLLATE 'NOCASE', `ipk` COLLATE 'BINARY'" ** -** As well as the returned string, three other malloc'd strings are +** As well as the returned string, three other malloc'd strings are ** returned via output parameters. As follows: ** ** pzImposterCols: ... @@ -190332,36 +228059,44 @@ static char *rbuObjIterGetIndexCols( int iCid = sqlite3_column_int(pXInfo, 1); int bDesc = sqlite3_column_int(pXInfo, 3); const char *zCollate = (const char*)sqlite3_column_text(pXInfo, 4); - const char *zCol; + const char *zCol = 0; const char *zType; - if( iCid<0 ){ - /* An integer primary key. If the table has an explicit IPK, use - ** its name. Otherwise, use "rbu_rowid". */ - if( pIter->eType==RBU_PK_IPK ){ - int i; - for(i=0; pIter->abTblPk[i]==0; i++); - assert( inTblCol ); - zCol = pIter->azTblCol[i]; - }else if( rbuIsVacuum(p) ){ - zCol = "_rowid_"; + if( iCid==-2 ){ + int iSeq = sqlite3_column_int(pXInfo, 0); + zRet = sqlite3_mprintf("%z%s(%.*s) COLLATE %Q", zRet, zCom, + pIter->aIdxCol[iSeq].nSpan, pIter->aIdxCol[iSeq].zSpan, zCollate + ); + zType = ""; + }else { + if( iCid<0 ){ + /* An integer primary key. If the table has an explicit IPK, use + ** its name. Otherwise, use "rbu_rowid". */ + if( pIter->eType==RBU_PK_IPK ){ + int i; + for(i=0; pIter->abTblPk[i]==0; i++); + assert( inTblCol ); + zCol = pIter->azTblCol[i]; + }else if( rbuIsVacuum(p) ){ + zCol = "_rowid_"; + }else{ + zCol = "rbu_rowid"; + } + zType = "INTEGER"; }else{ - zCol = "rbu_rowid"; + zCol = pIter->azTblCol[iCid]; + zType = pIter->azTblType[iCid]; } - zType = "INTEGER"; - }else{ - zCol = pIter->azTblCol[iCid]; - zType = pIter->azTblType[iCid]; + zRet = sqlite3_mprintf("%z%s\"%w\" COLLATE %Q", zRet, zCom,zCol,zCollate); } - zRet = sqlite3_mprintf("%z%s\"%w\" COLLATE %Q", zRet, zCom, zCol, zCollate); if( pIter->bUnique==0 || sqlite3_column_int(pXInfo, 5) ){ const char *zOrder = (bDesc ? " DESC" : ""); - zImpPK = sqlite3_mprintf("%z%s\"rbu_imp_%d%w\"%s", + zImpPK = sqlite3_mprintf("%z%s\"rbu_imp_%d%w\"%s", zImpPK, zCom, nBind, zCol, zOrder ); } - zImpCols = sqlite3_mprintf("%z%s\"rbu_imp_%d%w\" %s COLLATE %Q", + zImpCols = sqlite3_mprintf("%z%s\"rbu_imp_%d%w\" %s COLLATE %Q", zImpCols, zCom, nBind, zCol, zType, zCollate ); zWhere = sqlite3_mprintf( @@ -190407,7 +228142,7 @@ static char *rbuObjIterGetIndexCols( ** the text ", old._rowid_" to the returned value. */ static char *rbuObjIterGetOldlist( - sqlite3rbu *p, + sqlite3rbu *p, RbuObjIter *pIter, const char *zObj ){ @@ -190448,7 +228183,7 @@ static char *rbuObjIterGetOldlist( ** "b = ?1 AND c = ?2" */ static char *rbuObjIterGetWhere( - sqlite3rbu *p, + sqlite3rbu *p, RbuObjIter *pIter ){ char *zList = 0; @@ -190463,7 +228198,7 @@ static char *rbuObjIterGetWhere( zSep = " AND "; } } - zList = rbuMPrintf(p, + zList = rbuMPrintf(p, "_rowid_ = (SELECT id FROM rbu_imposter2 WHERE %z)", zList ); @@ -190503,7 +228238,7 @@ static void rbuBadControlError(sqlite3rbu *p){ ** ** The memory for the returned string is obtained from sqlite3_malloc(). ** It is the responsibility of the caller to eventually free it using -** sqlite3_free(). +** sqlite3_free(). ** ** If an OOM error is encountered when allocating space for the new ** string, an error code is left in the rbu handle passed as the first @@ -190527,19 +228262,19 @@ static char *rbuObjIterGetSetlist( for(i=0; inTblCol; i++){ char c = zMask[pIter->aiSrcOrder[i]]; if( c=='x' ){ - zList = rbuMPrintf(p, "%z%s\"%w\"=?%d", + zList = rbuMPrintf(p, "%z%s\"%w\"=?%d", zList, zSep, pIter->azTblCol[i], i+1 ); zSep = ", "; } else if( c=='d' ){ - zList = rbuMPrintf(p, "%z%s\"%w\"=rbu_delta(\"%w\", ?%d)", + zList = rbuMPrintf(p, "%z%s\"%w\"=rbu_delta(\"%w\", ?%d)", zList, zSep, pIter->azTblCol[i], pIter->azTblCol[i], i+1 ); zSep = ", "; } else if( c=='f' ){ - zList = rbuMPrintf(p, "%z%s\"%w\"=rbu_fossil_delta(\"%w\", ?%d)", + zList = rbuMPrintf(p, "%z%s\"%w\"=rbu_fossil_delta(\"%w\", ?%d)", zList, zSep, pIter->azTblCol[i], pIter->azTblCol[i], i+1 ); zSep = ", "; @@ -190557,7 +228292,7 @@ static char *rbuObjIterGetSetlist( ** ** The memory for the returned string is obtained from sqlite3_malloc(). ** It is the responsibility of the caller to eventually free it using -** sqlite3_free(). +** sqlite3_free(). ** ** If an OOM error is encountered when allocating space for the new ** string, an error code is left in the rbu handle passed as the first @@ -190581,8 +228316,8 @@ static char *rbuObjIterGetBindlist(sqlite3rbu *p, int nBind){ } /* -** The iterator currently points to a table (not index) of type -** RBU_PK_WITHOUT_ROWID. This function creates the PRIMARY KEY +** The iterator currently points to a table (not index) of type +** RBU_PK_WITHOUT_ROWID. This function creates the PRIMARY KEY ** declaration for the corresponding imposter table. For example, ** if the iterator points to a table created as: ** @@ -190599,7 +228334,7 @@ static char *rbuWithoutRowidPK(sqlite3rbu *p, RbuObjIter *pIter){ const char *zSep = "PRIMARY KEY("; sqlite3_stmt *pXList = 0; /* PRAGMA index_list = (pIter->zTbl) */ sqlite3_stmt *pXInfo = 0; /* PRAGMA index_xinfo = */ - + p->rc = prepareFreeAndCollectError(p->dbMain, &pXList, &p->zErrmsg, sqlite3_mprintf("PRAGMA main.index_list = %Q", pIter->zTbl) ); @@ -190637,7 +228372,7 @@ static char *rbuWithoutRowidPK(sqlite3rbu *p, RbuObjIter *pIter){ ** a table b-tree where the table has an external primary key. If the ** iterator passed as the second argument does not currently point to ** a table (not index) with an external primary key, this function is a -** no-op. +** no-op. ** ** Assuming the iterator does point to a table with an external PK, this ** function creates a WITHOUT ROWID imposter table named "rbu_imposter2" @@ -190664,8 +228399,8 @@ static void rbuCreateImposterTable2(sqlite3rbu *p, RbuObjIter *pIter){ /* Figure out the name of the primary key index for the current table. ** This is needed for the argument to "PRAGMA index_xinfo". Set ** zIdx to point to a nul-terminated string containing this name. */ - p->rc = prepareAndCollectError(p->dbMain, &pQuery, &p->zErrmsg, - "SELECT name FROM sqlite_master WHERE rootpage = ?" + p->rc = prepareAndCollectError(p->dbMain, &pQuery, &p->zErrmsg, + "SELECT name FROM sqlite_schema WHERE rootpage = ?" ); if( p->rc==SQLITE_OK ){ sqlite3_bind_int(pQuery, 1, tnum); @@ -190686,7 +228421,7 @@ static void rbuCreateImposterTable2(sqlite3rbu *p, RbuObjIter *pIter){ int iCid = sqlite3_column_int(pXInfo, 1); int bDesc = sqlite3_column_int(pXInfo, 3); const char *zCollate = (const char*)sqlite3_column_text(pXInfo, 4); - zCols = rbuMPrintf(p, "%z%sc%d %s COLLATE %Q", zCols, zComma, + zCols = rbuMPrintf(p, "%z%sc%d %s COLLATE %Q", zCols, zComma, iCid, pIter->azTblType[iCid], zCollate ); zPk = rbuMPrintf(p, "%z%sc%d%s", zPk, zComma, iCid, bDesc?" DESC":""); @@ -190698,7 +228433,7 @@ static void rbuCreateImposterTable2(sqlite3rbu *p, RbuObjIter *pIter){ sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->dbMain, "main", 1, tnum); rbuMPrintfExec(p, p->dbMain, - "CREATE TABLE rbu_imposter2(%z, PRIMARY KEY(%z)) WITHOUT ROWID", + "CREATE TABLE rbu_imposter2(%z, PRIMARY KEY(%z)) WITHOUT ROWID", zCols, zPk ); sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->dbMain, "main", 0, 0); @@ -190706,7 +228441,7 @@ static void rbuCreateImposterTable2(sqlite3rbu *p, RbuObjIter *pIter){ } /* -** If an error has already occurred when this function is called, it +** If an error has already occurred when this function is called, it ** immediately returns zero (without doing any work). Or, if an error ** occurs during the execution of this function, it sets the error code ** in the sqlite3rbu object indicated by the first argument and returns @@ -190719,9 +228454,9 @@ static void rbuCreateImposterTable2(sqlite3rbu *p, RbuObjIter *pIter){ ** an imposter table are created, or zero otherwise. ** ** An imposter table is required in all cases except RBU_PK_VTAB. Only -** virtual tables are written to directly. The imposter table has the -** same schema as the actual target table (less any UNIQUE constraints). -** More precisely, the "same schema" means the same columns, types, +** virtual tables are written to directly. The imposter table has the +** same schema as the actual target table (less any UNIQUE constraints). +** More precisely, the "same schema" means the same columns, types, ** collation sequences. For tables that do not have an external PRIMARY ** KEY, it also means the same PRIMARY KEY declaration. */ @@ -190747,7 +228482,7 @@ static void rbuCreateImposterTable(sqlite3rbu *p, RbuObjIter *pIter){ ** "PRIMARY KEY" to the imposter table column declaration. */ zPk = "PRIMARY KEY "; } - zSql = rbuMPrintf(p, "%z%s\"%w\" %s %sCOLLATE %Q%s", + zSql = rbuMPrintf(p, "%z%s\"%w\" %s %sCOLLATE %Q%s", zSql, zComma, zCol, pIter->azTblType[iCol], zPk, zColl, (pIter->abNotNull[iCol] ? " NOT NULL" : "") ); @@ -190762,8 +228497,8 @@ static void rbuCreateImposterTable(sqlite3rbu *p, RbuObjIter *pIter){ } sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->dbMain, "main", 1, tnum); - rbuMPrintfExec(p, p->dbMain, "CREATE TABLE \"rbu_imp_%w\"(%z)%s", - pIter->zTbl, zSql, + rbuMPrintfExec(p, p->dbMain, "CREATE TABLE \"rbu_imp_%w\"(%z)%s", + pIter->zTbl, zSql, (pIter->eType==RBU_PK_WITHOUT_ROWID ? " WITHOUT ROWID" : "") ); sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->dbMain, "main", 0, 0); @@ -190777,12 +228512,12 @@ static void rbuCreateImposterTable(sqlite3rbu *p, RbuObjIter *pIter){ ** INSERT INTO rbu_tmp_xxx VALUES(?, ?, ? ...); ** ** The number of bound variables is equal to the number of columns in -** the target table, plus one (for the rbu_control column), plus one more -** (for the rbu_rowid column) if the target table is an implicit IPK or +** the target table, plus one (for the rbu_control column), plus one more +** (for the rbu_rowid column) if the target table is an implicit IPK or ** virtual table. */ static void rbuObjIterPrepareTmpInsert( - sqlite3rbu *p, + sqlite3rbu *p, RbuObjIter *pIter, const char *zCollist, const char *zRbuRowid @@ -190793,14 +228528,14 @@ static void rbuObjIterPrepareTmpInsert( assert( pIter->pTmpInsert==0 ); p->rc = prepareFreeAndCollectError( p->dbRbu, &pIter->pTmpInsert, &p->zErrmsg, sqlite3_mprintf( - "INSERT INTO %s.'rbu_tmp_%q'(rbu_control,%s%s) VALUES(%z)", + "INSERT INTO %s.'rbu_tmp_%q'(rbu_control,%s%s) VALUES(%z)", p->zStateDb, pIter->zDataTbl, zCollist, zRbuRowid, zBind )); } } static void rbuTmpInsertFunc( - sqlite3_context *pCtx, + sqlite3_context *pCtx, int nVal, sqlite3_value **apVal ){ @@ -190809,8 +228544,8 @@ static void rbuTmpInsertFunc( int i; assert( sqlite3_value_int(apVal[0])!=0 - || p->objiter.eType==RBU_PK_EXTERNAL - || p->objiter.eType==RBU_PK_NONE + || p->objiter.eType==RBU_PK_EXTERNAL + || p->objiter.eType==RBU_PK_NONE ); if( sqlite3_value_int(apVal[0])!=0 ){ p->nPhaseOneStep += p->objiter.nIndex; @@ -190834,30 +228569,61 @@ static char *rbuObjIterGetIndexWhere(sqlite3rbu *p, RbuObjIter *pIter){ int rc = p->rc; char *zRet = 0; + assert( pIter->zIdxSql==0 && pIter->nIdxCol==0 && pIter->aIdxCol==0 ); + if( rc==SQLITE_OK ){ rc = prepareAndCollectError(p->dbMain, &pStmt, &p->zErrmsg, - "SELECT trim(sql) FROM sqlite_master WHERE type='index' AND name=?" + "SELECT trim(sql) FROM sqlite_schema WHERE type='index' AND name=?" ); } if( rc==SQLITE_OK ){ int rc2; rc = sqlite3_bind_text(pStmt, 1, pIter->zIdx, -1, SQLITE_STATIC); if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ - const char *zSql = (const char*)sqlite3_column_text(pStmt, 0); + char *zSql = (char*)sqlite3_column_text(pStmt, 0); + if( zSql ){ + pIter->zIdxSql = zSql = rbuStrndup(zSql, &rc); + } if( zSql ){ int nParen = 0; /* Number of open parenthesis */ int i; + int iIdxCol = 0; + int nIdxAlloc = 0; for(i=0; zSql[i]; i++){ char c = zSql[i]; + + /* If necessary, grow the pIter->aIdxCol[] array */ + if( iIdxCol==nIdxAlloc ){ + RbuSpan *aIdxCol = (RbuSpan*)sqlite3_realloc64( + pIter->aIdxCol, nIdxAlloc*sizeof(RbuSpan) + 16*sizeof(RbuSpan) + ); + if( aIdxCol==0 ){ + rc = SQLITE_NOMEM; + break; + } + pIter->aIdxCol = aIdxCol; + nIdxAlloc += 16; + } + if( c=='(' ){ + if( nParen==0 ){ + assert( iIdxCol==0 ); + pIter->aIdxCol[0].zSpan = &zSql[i+1]; + } nParen++; } else if( c==')' ){ nParen--; if( nParen==0 ){ + int nSpan = (int)(&zSql[i] - pIter->aIdxCol[iIdxCol].zSpan); + pIter->aIdxCol[iIdxCol++].nSpan = nSpan; i++; break; } + }else if( c==',' && nParen==1 ){ + int nSpan = (int)(&zSql[i] - pIter->aIdxCol[iIdxCol].zSpan); + pIter->aIdxCol[iIdxCol++].nSpan = nSpan; + pIter->aIdxCol[iIdxCol].zSpan = &zSql[i+1]; }else if( c=='"' || c=='\'' || c=='`' ){ for(i++; 1; i++){ if( zSql[i]==c ){ @@ -190869,11 +228635,19 @@ static char *rbuObjIterGetIndexWhere(sqlite3rbu *p, RbuObjIter *pIter){ for(i++; 1; i++){ if( zSql[i]==']' ) break; } + }else if( c=='-' && zSql[i+1]=='-' ){ + for(i=i+2; zSql[i] && zSql[i]!='\n'; i++); + if( zSql[i]=='\0' ) break; + }else if( c=='/' && zSql[i+1]=='*' ){ + for(i=i+2; zSql[i] && (zSql[i]!='*' || zSql[i+1]!='/'); i++); + if( zSql[i]=='\0' ) break; + i++; } } if( zSql[i] ){ zRet = rbuStrndup(&zSql[i], &rc); } + pIter->nIdxCol = iIdxCol; } } @@ -190886,12 +228660,12 @@ static char *rbuObjIterGetIndexWhere(sqlite3rbu *p, RbuObjIter *pIter){ } /* -** Ensure that the SQLite statement handles required to update the -** target database object currently indicated by the iterator passed +** Ensure that the SQLite statement handles required to update the +** target database object currently indicated by the iterator passed ** as the second argument are available. */ static int rbuObjIterPrepareAll( - sqlite3rbu *p, + sqlite3rbu *p, RbuObjIter *pIter, int nOffset /* Add "LIMIT -1 OFFSET $nOffset" to SELECT */ ){ @@ -190918,11 +228692,11 @@ static int rbuObjIterPrepareAll( int nBind = 0; assert( pIter->eType!=RBU_PK_VTAB ); + zPart = rbuObjIterGetIndexWhere(p, pIter); zCollist = rbuObjIterGetIndexCols( p, pIter, &zImposterCols, &zImposterPK, &zWhere, &nBind ); zBind = rbuObjIterGetBindlist(p, nBind); - zPart = rbuObjIterGetIndexWhere(p, pIter); /* Create the imposter table used to write to this index. */ sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->dbMain, "main", 0, 1); @@ -190954,12 +228728,24 @@ static int rbuObjIterPrepareAll( if( p->rc==SQLITE_OK ){ char *zSql; if( rbuIsVacuum(p) ){ + char *zStart = 0; + if( nOffset ){ + zStart = rbuVacuumIndexStart(p, pIter); + if( zStart ){ + sqlite3_free(zLimit); + zLimit = 0; + } + } + zSql = sqlite3_mprintf( - "SELECT %s, 0 AS rbu_control FROM '%q' %s ORDER BY %s%s", - zCollist, + "SELECT %s, 0 AS rbu_control FROM '%q' %s %s %s ORDER BY %s%s", + zCollist, pIter->zDataTbl, - zPart, zCollist, zLimit + zPart, + (zStart ? (zPart ? "AND" : "WHERE") : ""), zStart, + zCollist, zLimit ); + sqlite3_free(zStart); }else if( pIter->eType==RBU_PK_EXTERNAL || pIter->eType==RBU_PK_NONE ){ @@ -190976,13 +228762,17 @@ static int rbuObjIterPrepareAll( "%s %s typeof(rbu_control)='integer' AND rbu_control!=1 " "ORDER BY %s%s", zCollist, p->zStateDb, pIter->zDataTbl, zPart, - zCollist, pIter->zDataTbl, + zCollist, pIter->zDataTbl, zPart, (zPart ? "AND" : "WHERE"), zCollist, zLimit ); } - p->rc = prepareFreeAndCollectError(p->dbRbu, &pIter->pSelect, pz, zSql); + if( p->rc==SQLITE_OK ){ + p->rc = prepareFreeAndCollectError(p->dbRbu,&pIter->pSelect,pz,zSql); + }else{ + sqlite3_free(zSql); + } } sqlite3_free(zImposterCols); @@ -191014,7 +228804,7 @@ static int rbuObjIterPrepareAll( if( p->rc==SQLITE_OK ){ p->rc = prepareFreeAndCollectError(p->dbMain, &pIter->pInsert, pz, sqlite3_mprintf( - "INSERT INTO \"%s%w\"(%s%s) VALUES(%s)", + "INSERT INTO \"%s%w\"(%s%s) VALUES(%s)", zWrite, zTbl, zCollist, (bRbuRowid ? ", _rowid_" : ""), zBindings ) ); @@ -191082,18 +228872,42 @@ static int rbuObjIterPrepareAll( /* Create the SELECT statement to read keys from data_xxx */ if( p->rc==SQLITE_OK ){ const char *zRbuRowid = ""; + char *zStart = 0; + char *zOrder = 0; if( bRbuRowid ){ zRbuRowid = rbuIsVacuum(p) ? ",_rowid_ " : ",rbu_rowid"; } - p->rc = prepareFreeAndCollectError(p->dbRbu, &pIter->pSelect, pz, - sqlite3_mprintf( - "SELECT %s,%s rbu_control%s FROM '%q'%s", - zCollist, - (rbuIsVacuum(p) ? "0 AS " : ""), - zRbuRowid, - pIter->zDataTbl, zLimit - ) - ); + + if( rbuIsVacuum(p) ){ + if( nOffset ){ + zStart = rbuVacuumTableStart(p, pIter, bRbuRowid, zWrite); + if( zStart ){ + sqlite3_free(zLimit); + zLimit = 0; + } + } + if( bRbuRowid ){ + zOrder = rbuMPrintf(p, "_rowid_"); + }else{ + zOrder = rbuObjIterGetPkList(p, pIter, "", ", ", ""); + } + } + + if( p->rc==SQLITE_OK ){ + p->rc = prepareFreeAndCollectError(p->dbRbu, &pIter->pSelect, pz, + sqlite3_mprintf( + "SELECT %s,%s rbu_control%s FROM '%q'%s %s %s %s", + zCollist, + (rbuIsVacuum(p) ? "0 AS " : ""), + zRbuRowid, + pIter->zDataTbl, (zStart ? zStart : ""), + (zOrder ? "ORDER BY" : ""), zOrder, + zLimit + ) + ); + } + sqlite3_free(zStart); + sqlite3_free(zOrder); } sqlite3_free(zWhere); @@ -191104,16 +228918,16 @@ static int rbuObjIterPrepareAll( sqlite3_free(zCollist); sqlite3_free(zLimit); } - + return p->rc; } /* ** Set output variable *ppStmt to point to an UPDATE statement that may ** be used to update the imposter table for the main table b-tree of the -** table object that pIter currently points to, assuming that the +** table object that pIter currently points to, assuming that the ** rbu_control column of the data_xyz table contains zMask. -** +** ** If the zMask string does not specify any columns to update, then this ** is not an error. Output variable *ppStmt is set to NULL in this case. */ @@ -191140,7 +228954,7 @@ static int rbuGetUpdateStmt( *pp = pUp->pNext; pUp->pNext = pIter->pRbuUpdate; pIter->pRbuUpdate = pUp; - *ppStmt = pUp->pUpdate; + *ppStmt = pUp->pUpdate; return SQLITE_OK; } nUp++; @@ -191162,15 +228976,15 @@ static int rbuGetUpdateStmt( char *zUpdate = 0; pUp->zMask = (char*)&pUp[1]; - memcpy(pUp->zMask, zMask, pIter->nTblCol); pUp->pNext = pIter->pRbuUpdate; pIter->pRbuUpdate = pUp; if( zSet ){ const char *zPrefix = ""; - + assert( p->rc==SQLITE_OK ); + memcpy(pUp->zMask, zMask, pIter->nTblCol); if( pIter->eType!=RBU_PK_VTAB ) zPrefix = "rbu_imp_"; - zUpdate = sqlite3_mprintf("UPDATE \"%s%w\" SET %s WHERE %s", + zUpdate = sqlite3_mprintf("UPDATE \"%s%w\" SET %s WHERE %s", zPrefix, pIter->zTbl, zSet, zWhere ); p->rc = prepareFreeAndCollectError( @@ -191186,8 +229000,8 @@ static int rbuGetUpdateStmt( } static sqlite3 *rbuOpenDbhandle( - sqlite3rbu *p, - const char *zName, + sqlite3rbu *p, + const char *zName, int bUseVfs ){ sqlite3 *db = 0; @@ -191216,8 +229030,8 @@ static void rbuFreeState(RbuState *p){ } /* -** Allocate an RbuState object and load the contents of the rbu_state -** table into it. Return a pointer to the new object. It is the +** Allocate an RbuState object and load the contents of the rbu_state +** table into it. Return a pointer to the new object. It is the ** responsibility of the caller to eventually free the object using ** sqlite3_free(). ** @@ -191233,7 +229047,7 @@ static RbuState *rbuLoadState(sqlite3rbu *p){ pRet = (RbuState*)rbuMalloc(p, sizeof(RbuState)); if( pRet==0 ) return 0; - rc = prepareFreeAndCollectError(p->dbRbu, &pStmt, &p->zErrmsg, + rc = prepareFreeAndCollectError(p->dbRbu, &pStmt, &p->zErrmsg, sqlite3_mprintf("SELECT k, v FROM %s.rbu_state", p->zStateDb) ); while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ @@ -191258,6 +229072,9 @@ static RbuState *rbuLoadState(sqlite3rbu *p){ case RBU_STATE_ROW: pRet->nRow = sqlite3_column_int(pStmt, 1); + if( pRet->nRow<0 ){ + rc = SQLITE_CORRUPT; + } break; case RBU_STATE_PROGRESS: @@ -191273,7 +229090,7 @@ static RbuState *rbuLoadState(sqlite3rbu *p){ break; case RBU_STATE_OALSZ: - pRet->iOalSz = (u32)sqlite3_column_int64(pStmt, 1); + pRet->iOalSz = sqlite3_column_int64(pStmt, 1); break; case RBU_STATE_PHASEONESTEP: @@ -191300,19 +229117,25 @@ static RbuState *rbuLoadState(sqlite3rbu *p){ /* ** Open the database handle and attach the RBU database as "rbu". If an ** error occurs, leave an error code and message in the RBU handle. +** +** If argument dbMain is not NULL, then it is a database handle already +** open on the target database. Use this handle instead of opening a new +** one. */ -static void rbuOpenDatabase(sqlite3rbu *p, int *pbRetry){ +static void rbuOpenDatabase(sqlite3rbu *p, sqlite3 *dbMain, int *pbRetry){ assert( p->rc || (p->dbMain==0 && p->dbRbu==0) ); assert( p->rc || rbuIsVacuum(p) || p->zTarget!=0 ); + assert( dbMain==0 || rbuIsVacuum(p)==0 ); /* Open the RBU database */ p->dbRbu = rbuOpenDbhandle(p, p->zRbu, 1); + p->dbMain = dbMain; if( p->rc==SQLITE_OK && rbuIsVacuum(p) ){ sqlite3_file_control(p->dbRbu, "main", SQLITE_FCNTL_RBUCNT, (void*)p); if( p->zState==0 ){ const char *zFile = sqlite3_db_filename(p->dbRbu, "main"); - p->zState = rbuMPrintf(p, "file://%s-vacuum?modeof=%s", zFile, zFile); + p->zState = rbuMPrintf(p, "file:///%s-vacuum?modeof=%s", zFile, zFile); } } @@ -191341,9 +229164,9 @@ static void rbuOpenDatabase(sqlite3rbu *p, int *pbRetry){ int bOk = 0; sqlite3_stmt *pCnt = 0; p->rc = prepareAndCollectError(p->dbRbu, &pCnt, &p->zErrmsg, - "SELECT count(*) FROM stat.sqlite_master" + "SELECT count(*) FROM stat.sqlite_schema" ); - if( p->rc==SQLITE_OK + if( p->rc==SQLITE_OK && sqlite3_step(pCnt)==SQLITE_ROW && 1==sqlite3_column_int(pCnt, 0) ){ @@ -191356,7 +229179,7 @@ static void rbuOpenDatabase(sqlite3rbu *p, int *pbRetry){ p->rc = SQLITE_ERROR; p->zErrmsg = sqlite3_mprintf("invalid state database"); } - + if( p->rc==SQLITE_OK ){ p->rc = sqlite3_exec(p->dbRbu, "COMMIT", 0, 0, 0); } @@ -191410,7 +229233,7 @@ static void rbuOpenDatabase(sqlite3rbu *p, int *pbRetry){ if( *zExtra=='\0' ) zExtra = 0; } - zTarget = sqlite3_mprintf("file:%s-vactmp?rbu_memory=1%s%s", + zTarget = sqlite3_mprintf("file:%s-vactmp?rbu_memory=1%s%s", sqlite3_db_filename(p->dbRbu, "main"), (zExtra==0 ? "" : "&"), (zExtra==0 ? "" : zExtra) ); @@ -191425,19 +229248,19 @@ static void rbuOpenDatabase(sqlite3rbu *p, int *pbRetry){ } if( p->rc==SQLITE_OK ){ - p->rc = sqlite3_create_function(p->dbMain, + p->rc = sqlite3_create_function(p->dbMain, "rbu_tmp_insert", -1, SQLITE_UTF8, (void*)p, rbuTmpInsertFunc, 0, 0 ); } if( p->rc==SQLITE_OK ){ - p->rc = sqlite3_create_function(p->dbMain, + p->rc = sqlite3_create_function(p->dbMain, "rbu_fossil_delta", 2, SQLITE_UTF8, 0, rbuFossilDeltaFunc, 0, 0 ); } if( p->rc==SQLITE_OK ){ - p->rc = sqlite3_create_function(p->dbRbu, + p->rc = sqlite3_create_function(p->dbRbu, "rbu_target_name", -1, SQLITE_UTF8, (void*)p, rbuTargetNameFunc, 0, 0 ); } @@ -191445,9 +229268,9 @@ static void rbuOpenDatabase(sqlite3rbu *p, int *pbRetry){ if( p->rc==SQLITE_OK ){ p->rc = sqlite3_file_control(p->dbMain, "main", SQLITE_FCNTL_RBU, (void*)p); } - rbuMPrintfExec(p, p->dbMain, "SELECT * FROM sqlite_master"); + rbuMPrintfExec(p, p->dbMain, "SELECT * FROM sqlite_schema"); - /* Mark the database file just opened as an RBU target database. If + /* Mark the database file just opened as an RBU target database. If ** this call returns SQLITE_NOTFOUND, then the RBU vfs is not in use. ** This is an error. */ if( p->rc==SQLITE_OK ){ @@ -191491,14 +229314,16 @@ static void rbuFileSuffix3(const char *zBase, char *z){ for(i=sz-1; i>0 && z[i]!='/' && z[i]!='.'; i--){} if( z[i]=='.' && sz>i+4 ) memmove(&z[i+1], &z[sz-3], 4); } +#else + UNUSED_PARAMETER2(zBase,z); #endif } /* -** Return the current wal-index header checksum for the target database +** Return the current wal-index header checksum for the target database ** as a 64-bit integer. ** -** The checksum is store in the first page of xShmMap memory as an 8-byte +** The checksum is store in the first page of xShmMap memory as an 8-byte ** blob starting at byte offset 40. */ static i64 rbuShmChecksum(sqlite3rbu *p){ @@ -191508,7 +229333,7 @@ static i64 rbuShmChecksum(sqlite3rbu *p){ u32 volatile *ptr; p->rc = pDb->pMethods->xShmMap(pDb, 0, 32*1024, 0, (void volatile**)&ptr); if( p->rc==SQLITE_OK ){ - iRet = ((i64)ptr[10] << 32) + ptr[11]; + iRet = (i64)(((u64)ptr[10] << 32) + ptr[11]); } } return iRet; @@ -191516,11 +229341,11 @@ static i64 rbuShmChecksum(sqlite3rbu *p){ /* ** This function is called as part of initializing or reinitializing an -** incremental checkpoint. +** incremental checkpoint. ** -** It populates the sqlite3rbu.aFrame[] array with the set of -** (wal frame -> db page) copy operations required to checkpoint the -** current wal file, and obtains the set of shm locks required to safely +** It populates the sqlite3rbu.aFrame[] array with the set of +** (wal frame -> db page) copy operations required to checkpoint the +** current wal file, and obtains the set of shm locks required to safely ** perform the copy operations directly on the file-system. ** ** If argument pState is not NULL, then the incremental checkpoint is @@ -191538,7 +229363,7 @@ static void rbuSetupCheckpoint(sqlite3rbu *p, RbuState *pState){ if( pState==0 ){ p->eStage = 0; if( p->rc==SQLITE_OK ){ - p->rc = sqlite3_exec(p->dbMain, "SELECT * FROM sqlite_master", 0, 0, 0); + p->rc = sqlite3_exec(p->dbMain, "SELECT * FROM sqlite_schema", 0, 0, 0); } } @@ -191555,26 +229380,26 @@ static void rbuSetupCheckpoint(sqlite3rbu *p, RbuState *pState){ ** would be read/written are recorded in the sqlite3rbu.aFrame[] ** array. ** - ** * Calls to xShmLock(UNLOCK) to release the exclusive shm WRITER, + ** * Calls to xShmLock(UNLOCK) to release the exclusive shm WRITER, ** READ0 and CHECKPOINT locks taken as part of the checkpoint are ** no-ops. These locks will not be released until the connection ** is closed. ** - ** * Attempting to xSync() the database file causes an SQLITE_INTERNAL + ** * Attempting to xSync() the database file causes an SQLITE_NOTICE ** error. ** ** As a result, unless an error (i.e. OOM or SQLITE_BUSY) occurs, the - ** checkpoint below fails with SQLITE_INTERNAL, and leaves the aFrame[] - ** array populated with a set of (frame -> page) mappings. Because the - ** WRITER, CHECKPOINT and READ0 locks are still held, it is safe to copy - ** data from the wal file into the database file according to the + ** checkpoint below fails with SQLITE_NOTICE, and leaves the aFrame[] + ** array populated with a set of (frame -> page) mappings. Because the + ** WRITER, CHECKPOINT and READ0 locks are still held, it is safe to copy + ** data from the wal file into the database file according to the ** contents of aFrame[]. */ if( p->rc==SQLITE_OK ){ int rc2; p->eStage = RBU_STAGE_CAPTURE; rc2 = sqlite3_exec(p->dbMain, "PRAGMA main.wal_checkpoint=restart", 0, 0,0); - if( rc2!=SQLITE_INTERNAL ) p->rc = rc2; + if( rc2!=SQLITE_NOTICE ) p->rc = rc2; } if( p->rc==SQLITE_OK && p->nFrame>0 ){ @@ -191600,9 +229425,9 @@ static void rbuSetupCheckpoint(sqlite3rbu *p, RbuState *pState){ p->nPagePerSector = 1; } - /* Call xSync() on the wal file. This causes SQLite to sync the - ** directory in which the target database and the wal file reside, in - ** case it has not been synced since the rename() call in + /* Call xSync() on the wal file. This causes SQLite to sync the + ** directory in which the target database and the wal file reside, in + ** case it has not been synced since the rename() call in ** rbuMoveOalFile(). */ p->rc = pWal->pMethods->xSync(pWal, SQLITE_SYNC_NORMAL); } @@ -191620,7 +229445,7 @@ static int rbuCaptureWalRead(sqlite3rbu *pRbu, i64 iOff, int iAmt){ if( pRbu->mLock!=mReq ){ pRbu->rc = SQLITE_BUSY; - return SQLITE_INTERNAL; + return SQLITE_NOTICE_RBU; } pRbu->pgsz = iAmt; @@ -191643,7 +229468,7 @@ static int rbuCaptureWalRead(sqlite3rbu *pRbu, i64 iOff, int iAmt){ /* ** Called when a page of data is written to offset iOff of the database -** file while the rbu handle is in capture mode. Record the page number +** file while the rbu handle is in capture mode. Record the page number ** of the page being written in the aFrame[] array. */ static int rbuCaptureDbWrite(sqlite3rbu *pRbu, i64 iOff){ @@ -191670,17 +229495,49 @@ static void rbuCheckpointFrame(sqlite3rbu *p, RbuFrame *pFrame){ p->rc = pDb->pMethods->xWrite(pDb, p->aBuf, p->pgsz, iOff); } +/* +** This value is copied from the definition of ZIPVFS_CTRL_FILE_POINTER +** in zipvfs.h. +*/ +#define RBU_ZIPVFS_CTRL_FILE_POINTER 230439 /* -** Take an EXCLUSIVE lock on the database file. +** Take an EXCLUSIVE lock on the database file. Return SQLITE_OK if +** successful, or an SQLite error code otherwise. */ -static void rbuLockDatabase(sqlite3rbu *p){ - sqlite3_file *pReal = p->pTargetFd->pReal; - assert( p->rc==SQLITE_OK ); - p->rc = pReal->pMethods->xLock(pReal, SQLITE_LOCK_SHARED); - if( p->rc==SQLITE_OK ){ - p->rc = pReal->pMethods->xLock(pReal, SQLITE_LOCK_EXCLUSIVE); +static int rbuLockDatabase(sqlite3 *db){ + int rc = SQLITE_OK; + sqlite3_file *fd = 0; + + sqlite3_file_control(db, "main", RBU_ZIPVFS_CTRL_FILE_POINTER, &fd); + if( fd ){ + sqlite3_file_control(db, "main", SQLITE_FCNTL_FILE_POINTER, &fd); + rc = fd->pMethods->xLock(fd, SQLITE_LOCK_SHARED); + if( rc==SQLITE_OK ){ + rc = fd->pMethods->xUnlock(fd, SQLITE_LOCK_NONE); + } + sqlite3_file_control(db, "main", RBU_ZIPVFS_CTRL_FILE_POINTER, &fd); + }else{ + sqlite3_file_control(db, "main", SQLITE_FCNTL_FILE_POINTER, &fd); + } + + if( rc==SQLITE_OK && fd->pMethods ){ + rc = fd->pMethods->xLock(fd, SQLITE_LOCK_SHARED); + if( rc==SQLITE_OK ){ + rc = fd->pMethods->xLock(fd, SQLITE_LOCK_EXCLUSIVE); + } } + return rc; +} + +/* +** Return true if the database handle passed as the only argument +** was opened with the rbu_exclusive_checkpoint=1 URI parameter +** specified. Or false otherwise. +*/ +static int rbuExclusiveCheckpoint(sqlite3 *db){ + const char *zUri = sqlite3_db_filename(db, 0); + return sqlite3_uri_boolean(zUri, RBU_EXCLUSIVE_CHECKPOINT, 0); } #if defined(_WIN32_WCE) @@ -191711,7 +229568,7 @@ static LPWSTR rbuWinUtf8ToUnicode(const char *zFilename){ ** The RBU handle is currently in RBU_STAGE_OAL state, with a SHARED lock ** on the database file. This proc moves the *-oal file to the *-wal path, ** then reopens the database file (this time in vanilla, non-oal, WAL mode). -** If an error occurs, leave an error code and error message in the rbu +** If an error occurs, leave an error code and error message in the rbu ** handle. */ static void rbuMoveOalFile(sqlite3rbu *p){ @@ -191733,54 +229590,43 @@ static void rbuMoveOalFile(sqlite3rbu *p){ }else{ /* Move the *-oal file to *-wal. At this point connection p->db is ** holding a SHARED lock on the target database file (because it is - ** in WAL mode). So no other connection may be writing the db. + ** in WAL mode). So no other connection may be writing the db. ** ** In order to ensure that there are no database readers, an EXCLUSIVE ** lock is obtained here before the *-oal is moved to *-wal. */ - rbuLockDatabase(p); - if( p->rc==SQLITE_OK ){ - rbuFileSuffix3(zBase, zWal); - rbuFileSuffix3(zBase, zOal); + sqlite3 *dbMain = 0; + rbuFileSuffix3(zBase, zWal); + rbuFileSuffix3(zBase, zOal); - /* Re-open the databases. */ - rbuObjIterFinalize(&p->objiter); - sqlite3_close(p->dbRbu); - sqlite3_close(p->dbMain); - p->dbMain = 0; - p->dbRbu = 0; + /* Re-open the databases. */ + rbuObjIterFinalize(&p->objiter); + sqlite3_close(p->dbRbu); + sqlite3_close(p->dbMain); + p->dbMain = 0; + p->dbRbu = 0; -#if defined(_WIN32_WCE) - { - LPWSTR zWideOal; - LPWSTR zWideWal; - - zWideOal = rbuWinUtf8ToUnicode(zOal); - if( zWideOal ){ - zWideWal = rbuWinUtf8ToUnicode(zWal); - if( zWideWal ){ - if( MoveFileW(zWideOal, zWideWal) ){ - p->rc = SQLITE_OK; - }else{ - p->rc = SQLITE_IOERR; - } - sqlite3_free(zWideWal); - }else{ - p->rc = SQLITE_IOERR_NOMEM; - } - sqlite3_free(zWideOal); - }else{ - p->rc = SQLITE_IOERR_NOMEM; - } - } -#else - p->rc = rename(zOal, zWal) ? SQLITE_IOERR : SQLITE_OK; -#endif + dbMain = rbuOpenDbhandle(p, p->zTarget, 1); + if( dbMain ){ + assert( p->rc==SQLITE_OK ); + p->rc = rbuLockDatabase(dbMain); + } - if( p->rc==SQLITE_OK ){ - rbuOpenDatabase(p, 0); - rbuSetupCheckpoint(p, 0); - } + if( p->rc==SQLITE_OK ){ + p->rc = p->xRename(p->pRenameArg, zOal, zWal); + } + + if( p->rc!=SQLITE_OK + || rbuIsVacuum(p) + || rbuExclusiveCheckpoint(dbMain)==0 + ){ + sqlite3_close(dbMain); + dbMain = 0; + } + + if( p->rc==SQLITE_OK ){ + rbuOpenDatabase(p, dbMain, 0); + rbuSetupCheckpoint(p, 0); } } @@ -191891,8 +229737,8 @@ static void rbuStepOneOp(sqlite3rbu *p, int eType){ /* If this is an INSERT into a table b-tree and the table has an ** explicit INTEGER PRIMARY KEY, check that this is not an attempt ** to write a NULL into the IPK column. That is not permitted. */ - if( eType==RBU_INSERT - && pIter->zIdx==0 && pIter->eType==RBU_PK_IPK && pIter->abTblPk[i] + if( eType==RBU_INSERT + && pIter->zIdx==0 && pIter->eType==RBU_PK_IPK && pIter->abTblPk[i] && sqlite3_column_type(pIter->pSelect, i)==SQLITE_NULL ){ p->rc = SQLITE_MISMATCH; @@ -191909,18 +229755,18 @@ static void rbuStepOneOp(sqlite3rbu *p, int eType){ if( p->rc ) return; } if( pIter->zIdx==0 ){ - if( pIter->eType==RBU_PK_VTAB - || pIter->eType==RBU_PK_NONE - || (pIter->eType==RBU_PK_EXTERNAL && rbuIsVacuum(p)) + if( pIter->eType==RBU_PK_VTAB + || pIter->eType==RBU_PK_NONE + || (pIter->eType==RBU_PK_EXTERNAL && rbuIsVacuum(p)) ){ - /* For a virtual table, or a table with no primary key, the + /* For a virtual table, or a table with no primary key, the ** SELECT statement is: ** ** SELECT , rbu_control, rbu_rowid FROM .... ** ** Hence column_value(pIter->nCol+1). */ - assertColumnName(pIter->pSelect, pIter->nCol+1, + assertColumnName(pIter->pSelect, pIter->nCol+1, rbuIsVacuum(p) ? "rowid" : "rbu_rowid" ); pVal = sqlite3_column_value(pIter->pSelect, pIter->nCol+1); @@ -191984,8 +229830,8 @@ static int rbuStep(sqlite3rbu *p){ p->rc = sqlite3_bind_value(pUpdate, i+1, pVal); } } - if( p->rc==SQLITE_OK - && (pIter->eType==RBU_PK_VTAB || pIter->eType==RBU_PK_NONE) + if( p->rc==SQLITE_OK + && (pIter->eType==RBU_PK_VTAB || pIter->eType==RBU_PK_NONE) ){ /* Bind the rbu_rowid value to column _rowid_ */ assertColumnName(pIter->pSelect, pIter->nCol+1, "rbu_rowid"); @@ -192015,7 +229861,7 @@ static void rbuIncrSchemaCookie(sqlite3rbu *p){ int iCookie = 1000000; sqlite3_stmt *pStmt; - p->rc = prepareAndCollectError(dbread, &pStmt, &p->zErrmsg, + p->rc = prepareAndCollectError(dbread, &pStmt, &p->zErrmsg, "PRAGMA schema_version" ); if( p->rc==SQLITE_OK ){ @@ -192047,14 +229893,14 @@ static void rbuSaveState(sqlite3rbu *p, int eStage){ int rc; assert( p->zErrmsg==0 ); - rc = prepareFreeAndCollectError(p->dbRbu, &pInsert, &p->zErrmsg, + rc = prepareFreeAndCollectError(p->dbRbu, &pInsert, &p->zErrmsg, sqlite3_mprintf( "INSERT OR REPLACE INTO %s.rbu_state(k, v) VALUES " "(%d, %d), " "(%d, %Q), " "(%d, %Q), " "(%d, %d), " - "(%d, %d), " + "(%d, %lld), " "(%d, %lld), " "(%d, %lld), " "(%d, %lld), " @@ -192062,9 +229908,9 @@ static void rbuSaveState(sqlite3rbu *p, int eStage){ "(%d, %Q) ", p->zStateDb, RBU_STATE_STAGE, eStage, - RBU_STATE_TBL, p->objiter.zTbl, - RBU_STATE_IDX, p->objiter.zIdx, - RBU_STATE_ROW, p->nStep, + RBU_STATE_TBL, p->objiter.zTbl, + RBU_STATE_IDX, p->objiter.zIdx, + RBU_STATE_ROW, p->nStep, RBU_STATE_PROGRESS, p->nProgress, RBU_STATE_CKPT, p->iWalCksum, RBU_STATE_COOKIE, (i64)pFd->iCookie, @@ -192085,7 +229931,7 @@ static void rbuSaveState(sqlite3rbu *p, int eStage){ /* -** The second argument passed to this function is the name of a PRAGMA +** The second argument passed to this function is the name of a PRAGMA ** setting - "page_size", "auto_vacuum", "user_version" or "application_id". ** This function executes the following on sqlite3rbu.dbRbu: ** @@ -192104,7 +229950,7 @@ static void rbuSaveState(sqlite3rbu *p, int eStage){ static void rbuCopyPragma(sqlite3rbu *p, const char *zPragma){ if( p->rc==SQLITE_OK ){ sqlite3_stmt *pPragma = 0; - p->rc = prepareFreeAndCollectError(p->dbRbu, &pPragma, &p->zErrmsg, + p->rc = prepareFreeAndCollectError(p->dbRbu, &pPragma, &p->zErrmsg, sqlite3_mprintf("PRAGMA main.%s", zPragma) ); if( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pPragma) ){ @@ -192117,7 +229963,7 @@ static void rbuCopyPragma(sqlite3rbu *p, const char *zPragma){ } /* -** The RBU handle passed as the only argument has just been opened and +** The RBU handle passed as the only argument has just been opened and ** the state database is empty. If this RBU handle was opened for an ** RBU vacuum operation, create the schema in the target db. */ @@ -192128,8 +229974,8 @@ static void rbuCreateTargetSchema(sqlite3rbu *p){ assert( rbuIsVacuum(p) ); p->rc = sqlite3_exec(p->dbMain, "PRAGMA writable_schema=1", 0,0, &p->zErrmsg); if( p->rc==SQLITE_OK ){ - p->rc = prepareAndCollectError(p->dbRbu, &pSql, &p->zErrmsg, - "SELECT sql FROM sqlite_master WHERE sql!='' AND rootpage!=0" + p->rc = prepareAndCollectError(p->dbRbu, &pSql, &p->zErrmsg, + "SELECT sql FROM sqlite_schema WHERE sql!='' AND rootpage!=0" " AND name!='sqlite_sequence' " " ORDER BY type DESC" ); @@ -192143,14 +229989,14 @@ static void rbuCreateTargetSchema(sqlite3rbu *p){ if( p->rc!=SQLITE_OK ) return; if( p->rc==SQLITE_OK ){ - p->rc = prepareAndCollectError(p->dbRbu, &pSql, &p->zErrmsg, - "SELECT * FROM sqlite_master WHERE rootpage=0 OR rootpage IS NULL" + p->rc = prepareAndCollectError(p->dbRbu, &pSql, &p->zErrmsg, + "SELECT * FROM sqlite_schema WHERE rootpage=0 OR rootpage IS NULL" ); } if( p->rc==SQLITE_OK ){ - p->rc = prepareAndCollectError(p->dbMain, &pInsert, &p->zErrmsg, - "INSERT INTO sqlite_master VALUES(?,?,?,?,?)" + p->rc = prepareAndCollectError(p->dbMain, &pInsert, &p->zErrmsg, + "INSERT INTO sqlite_schema VALUES(?,?,?,?,?)" ); } @@ -192190,11 +230036,11 @@ SQLITE_API int sqlite3rbu_step(sqlite3rbu *p){ while( p->rc==SQLITE_OK && pIter->zTbl ){ if( pIter->bCleanup ){ - /* Clean up the rbu_tmp_xxx table for the previous table. It + /* Clean up the rbu_tmp_xxx table for the previous table. It ** cannot be dropped as there are currently active SQL statements. ** But the contents can be deleted. */ if( rbuIsVacuum(p)==0 && pIter->abIndexed ){ - rbuMPrintfExec(p, p->dbRbu, + rbuMPrintfExec(p, p->dbRbu, "DELETE FROM %s.'rbu_tmp_%q'", p->zStateDb, pIter->zDataTbl ); } @@ -192244,10 +230090,10 @@ SQLITE_API int sqlite3rbu_step(sqlite3rbu *p){ if( p->rc==SQLITE_OK ){ if( p->nStep>=p->nFrame ){ sqlite3_file *pDb = p->pTargetFd->pReal; - + /* Sync the db file */ p->rc = pDb->pMethods->xSync(pDb, SQLITE_SYNC_NORMAL); - + /* Update nBackfill */ if( p->rc==SQLITE_OK ){ void volatile *ptr; @@ -192256,7 +230102,7 @@ SQLITE_API int sqlite3rbu_step(sqlite3rbu *p){ ((u32 volatile*)ptr)[24] = p->iMaxFrame; } } - + if( p->rc==SQLITE_OK ){ p->eStage = RBU_STAGE_DONE; p->rc = SQLITE_DONE; @@ -192264,7 +230110,7 @@ SQLITE_API int sqlite3rbu_step(sqlite3rbu *p){ }else{ /* At one point the following block copied a single frame from the ** wal file to the database file. So that one call to sqlite3rbu_step() - ** checkpointed a single frame. + ** checkpointed a single frame. ** ** However, if the sector-size is larger than the page-size, and the ** application calls sqlite3rbu_savestate() or close() immediately @@ -192278,7 +230124,7 @@ SQLITE_API int sqlite3rbu_step(sqlite3rbu *p){ iSector = (pFrame->iDbPage-1) / p->nPagePerSector; rbuCheckpointFrame(p, pFrame); p->nStep++; - }while( p->nStepnFrame + }while( p->nStepnFrame && iSector==((p->aFrame[p->nStep].iDbPage-1) / p->nPagePerSector) && p->rc==SQLITE_OK ); @@ -192324,7 +230170,7 @@ static void rbuSetupOal(sqlite3rbu *p, RbuState *pState){ RbuObjIter *pIter = &p->objiter; int rc = SQLITE_OK; - while( rc==SQLITE_OK && pIter->zTbl && (pIter->bCleanup + while( rc==SQLITE_OK && pIter->zTbl && (pIter->bCleanup || rbuStrCompare(pIter->zIdx, pState->zIdx) || (pState->zDataTbl==0 && rbuStrCompare(pIter->zTbl, pState->zTbl)) || (pState->zDataTbl && rbuStrCompare(pIter->zDataTbl, pState->zDataTbl)) @@ -192354,7 +230200,8 @@ static void rbuSetupOal(sqlite3rbu *p, RbuState *pState){ static void rbuDeleteOalFile(sqlite3rbu *p){ char *zOal = rbuMPrintf(p, "%s-oal", p->zTarget); if( zOal ){ - sqlite3_vfs *pVfs = sqlite3_vfs_find(0); + sqlite3_vfs *pVfs = 0; + sqlite3_file_control(p->dbMain, "main", SQLITE_FCNTL_VFS_POINTER, &pVfs); assert( pVfs && p->rc==SQLITE_OK && p->zErrmsg==0 ); pVfs->xDelete(pVfs, zOal, 0); sqlite3_free(zOal); @@ -192400,7 +230247,7 @@ static void rbuDeleteVfs(sqlite3rbu *p){ ** the number of auxilliary indexes on the table. */ static void rbuIndexCntFunc( - sqlite3_context *pCtx, + sqlite3_context *pCtx, int nVal, sqlite3_value **apVal ){ @@ -192408,11 +230255,13 @@ static void rbuIndexCntFunc( sqlite3_stmt *pStmt = 0; char *zErrmsg = 0; int rc; + sqlite3 *db = (rbuIsVacuum(p) ? p->dbRbu : p->dbMain); assert( nVal==1 ); - - rc = prepareFreeAndCollectError(p->dbMain, &pStmt, &zErrmsg, - sqlite3_mprintf("SELECT count(*) FROM sqlite_master " + UNUSED_PARAMETER(nVal); + + rc = prepareFreeAndCollectError(db, &pStmt, &zErrmsg, + sqlite3_mprintf("SELECT count(*) FROM sqlite_schema " "WHERE type='index' AND tbl_name = %Q", sqlite3_value_text(apVal[0])) ); if( rc!=SQLITE_OK ){ @@ -192426,7 +230275,7 @@ static void rbuIndexCntFunc( if( rc==SQLITE_OK ){ sqlite3_result_int(pCtx, nIndex); }else{ - sqlite3_result_error(pCtx, sqlite3_errmsg(p->dbMain), -1); + sqlite3_result_error(pCtx, sqlite3_errmsg(db), -1); } } @@ -192445,7 +230294,7 @@ static void rbuIndexCntFunc( ** and the cnt column the number of rows it contains. ** ** sqlite3rbu.nPhaseOneStep is initialized to the sum of (1 + nIndex) * cnt -** for all rows in the rbu_count table, where nIndex is the number of +** for all rows in the rbu_count table, where nIndex is the number of ** indexes on the corresponding target database table. */ static void rbuInitPhaseOneSteps(sqlite3rbu *p){ @@ -192455,15 +230304,15 @@ static void rbuInitPhaseOneSteps(sqlite3rbu *p){ p->nPhaseOneStep = -1; - p->rc = sqlite3_create_function(p->dbRbu, + p->rc = sqlite3_create_function(p->dbRbu, "rbu_index_cnt", 1, SQLITE_UTF8, (void*)p, rbuIndexCntFunc, 0, 0 ); - + /* Check for the rbu_count table. If it does not exist, or if an error ** occurs, nPhaseOneStep will be left set to -1. */ if( p->rc==SQLITE_OK ){ p->rc = prepareAndCollectError(p->dbRbu, &pStmt, &p->zErrmsg, - "SELECT 1 FROM sqlite_master WHERE tbl_name = 'rbu_count'" + "SELECT 1 FROM sqlite_schema WHERE tbl_name = 'rbu_count'" ); } if( p->rc==SQLITE_OK ){ @@ -192472,7 +230321,7 @@ static void rbuInitPhaseOneSteps(sqlite3rbu *p){ } p->rc = sqlite3_finalize(pStmt); } - + if( p->rc==SQLITE_OK && bExists ){ p->rc = prepareAndCollectError(p->dbRbu, &pStmt, &p->zErrmsg, "SELECT sum(cnt * (1 + rbu_index_cnt(rbu_target_name(tbl))))" @@ -192490,7 +230339,7 @@ static void rbuInitPhaseOneSteps(sqlite3rbu *p){ static sqlite3rbu *openRbuHandle( - const char *zTarget, + const char *zTarget, const char *zRbu, const char *zState ){ @@ -192505,6 +230354,7 @@ static sqlite3rbu *openRbuHandle( /* Create the custom VFS. */ memset(p, 0, sizeof(sqlite3rbu)); + sqlite3rbu_rename_handler(p, 0, 0); rbuCreateVfs(p); /* Open the target, RBU and state databases */ @@ -192528,11 +230378,11 @@ static sqlite3rbu *openRbuHandle( ** to be a wal-mode db. But, this may have happened due to an earlier ** RBU vacuum operation leaving an old wal file in the directory. ** If this is the case, it will have been checkpointed and deleted - ** when the handle was closed and a second attempt to open the + ** when the handle was closed and a second attempt to open the ** database may succeed. */ - rbuOpenDatabase(p, &bRetry); + rbuOpenDatabase(p, 0, &bRetry); if( bRetry ){ - rbuOpenDatabase(p, 0); + rbuOpenDatabase(p, 0, 0); } } @@ -192541,7 +230391,7 @@ static sqlite3rbu *openRbuHandle( assert( pState || p->rc!=SQLITE_OK ); if( p->rc==SQLITE_OK ){ - if( pState->eStage==0 ){ + if( pState->eStage==0 ){ rbuDeleteOalFile(p); rbuInitPhaseOneSteps(p); p->eStage = RBU_STAGE_OAL; @@ -192565,15 +230415,15 @@ static sqlite3rbu *openRbuHandle( } } - if( p->rc==SQLITE_OK + if( p->rc==SQLITE_OK && (p->eStage==RBU_STAGE_OAL || p->eStage==RBU_STAGE_MOVE) && pState->eStage!=0 ){ rbu_file *pFd = (rbuIsVacuum(p) ? p->pRbuFd : p->pTargetFd); - if( pFd->iCookie!=pState->iCookie ){ + if( pFd->iCookie!=pState->iCookie ){ /* At this point (pTargetFd->iCookie) contains the value of the - ** change-counter cookie (the thing that gets incremented when a - ** transaction is committed in rollback mode) currently stored on + ** change-counter cookie (the thing that gets incremented when a + ** transaction is committed in rollback mode) currently stored on ** page 1 of the database file. */ p->rc = SQLITE_BUSY; p->zErrmsg = sqlite3_mprintf("database modified during rbu %s", @@ -192610,7 +230460,7 @@ static sqlite3rbu *openRbuHandle( } /* Check if the main database is a zipvfs db. If it is, set the upper - ** level pager to use "journal_mode=off". This prevents it from + ** level pager to use "journal_mode=off". This prevents it from ** generating a large journal using a temp file. */ if( p->rc==SQLITE_OK ){ int frc = sqlite3_file_control(db, "main", SQLITE_FCNTL_ZIPVFS, 0); @@ -192627,6 +230477,14 @@ static sqlite3rbu *openRbuHandle( }else if( p->eStage==RBU_STAGE_MOVE ){ /* no-op */ }else if( p->eStage==RBU_STAGE_CKPT ){ + if( !rbuIsVacuum(p) && rbuExclusiveCheckpoint(p->dbMain) ){ + /* If the rbu_exclusive_checkpoint=1 URI parameter was specified + ** and an incremental checkpoint is being resumed, attempt an + ** exclusive lock on the db file. If this fails, so be it. */ + p->eStage = RBU_STAGE_DONE; + rbuLockDatabase(p->dbMain); + p->eStage = RBU_STAGE_CKPT; + } rbuSetupCheckpoint(p, pState); }else if( p->eStage==RBU_STAGE_DONE ){ p->rc = SQLITE_DONE; @@ -192656,15 +230514,14 @@ static sqlite3rbu *rbuMisuseError(void){ } /* -** Open and return a new RBU handle. +** Open and return a new RBU handle. */ SQLITE_API sqlite3rbu *sqlite3rbu_open( - const char *zTarget, + const char *zTarget, const char *zRbu, const char *zState ){ if( zTarget==0 || zRbu==0 ){ return rbuMisuseError(); } - /* TODO: Check that zTarget and zRbu are non-NULL */ return openRbuHandle(zTarget, zRbu, zState); } @@ -192672,12 +230529,12 @@ SQLITE_API sqlite3rbu *sqlite3rbu_open( ** Open a handle to begin or resume an RBU VACUUM operation. */ SQLITE_API sqlite3rbu *sqlite3rbu_vacuum( - const char *zTarget, + const char *zTarget, const char *zState ){ if( zTarget==0 ){ return rbuMisuseError(); } if( zState ){ - int n = strlen(zState); + size_t n = strlen(zState); if( n>=7 && 0==memcmp("-vactmp", &zState[n-7], 7) ){ return rbuMisuseError(); } @@ -192746,8 +230603,8 @@ SQLITE_API int sqlite3rbu_close(sqlite3rbu *p, char **pzErrmsg){ rbuObjIterFinalize(&p->objiter); /* If this is an RBU vacuum handle and the vacuum has either finished - ** successfully or encountered an error, delete the contents of the - ** state table. This causes the next call to sqlite3rbu_vacuum() + ** successfully or encountered an error, delete the contents of the + ** state table. This causes the next call to sqlite3rbu_vacuum() ** specifying the current target and state databases to start a new ** vacuum from scratch. */ if( rbuIsVacuum(p) && p->rc!=SQLITE_OK && p->dbRbu ){ @@ -192780,7 +230637,7 @@ SQLITE_API int sqlite3rbu_close(sqlite3rbu *p, char **pzErrmsg){ } /* -** Return the total number of key-value operations (inserts, deletes or +** Return the total number of key-value operations (inserts, deletes or ** updates) that have been performed on the target database since the ** current RBU update was started. */ @@ -192878,7 +230735,7 @@ SQLITE_API int sqlite3rbu_savestate(sqlite3rbu *p){ if( p->eStage==RBU_STAGE_OAL ){ assert( rc!=SQLITE_DONE ); if( rc==SQLITE_OK ) rc = sqlite3_exec(p->dbRbu, "COMMIT", 0, 0, 0); - if( rc==SQLITE_OK ){ + if( rc==SQLITE_OK ){ const char *zBegin = rbuIsVacuum(p) ? "BEGIN" : "BEGIN IMMEDIATE"; rc = sqlite3_exec(p->dbRbu, zBegin, 0, 0, 0); } @@ -192889,11 +230746,60 @@ SQLITE_API int sqlite3rbu_savestate(sqlite3rbu *p){ return rc; } +/* +** Default xRename callback for RBU. +*/ +static int xDefaultRename(void *pArg, const char *zOld, const char *zNew){ + int rc = SQLITE_OK; + UNUSED_PARAMETER(pArg); +#if defined(_WIN32_WCE) + { + LPWSTR zWideOld; + LPWSTR zWideNew; + + zWideOld = rbuWinUtf8ToUnicode(zOld); + if( zWideOld ){ + zWideNew = rbuWinUtf8ToUnicode(zNew); + if( zWideNew ){ + if( MoveFileW(zWideOld, zWideNew) ){ + rc = SQLITE_OK; + }else{ + rc = SQLITE_IOERR; + } + sqlite3_free(zWideNew); + }else{ + rc = SQLITE_IOERR_NOMEM; + } + sqlite3_free(zWideOld); + }else{ + rc = SQLITE_IOERR_NOMEM; + } + } +#else + rc = rename(zOld, zNew) ? SQLITE_IOERR : SQLITE_OK; +#endif + return rc; +} + +SQLITE_API void sqlite3rbu_rename_handler( + sqlite3rbu *pRbu, + void *pArg, + int (*xRename)(void *pArg, const char *zOld, const char *zNew) +){ + if( xRename ){ + pRbu->xRename = xRename; + pRbu->pRenameArg = pArg; + }else{ + pRbu->xRename = xDefaultRename; + pRbu->pRenameArg = 0; + } +} + /************************************************************************** ** Beginning of RBU VFS shim methods. The VFS shim modifies the behaviour ** of a standard VFS in the following ways: ** -** 1. Whenever the first page of a main database file is read or +** 1. Whenever the first page of a main database file is read or ** written, the value of the change-counter cookie is stored in ** rbu_file.iCookie. Similarly, the value of the "write-version" ** database header field is stored in rbu_file.iWriteVer. This ensures @@ -192901,15 +230807,15 @@ SQLITE_API int sqlite3rbu_savestate(sqlite3rbu *p){ ** ** 2. Whenever an SQLITE_OPEN_WAL file is opened, the (rbu_file.pWalFd) ** member variable of the associated database file descriptor is set -** to point to the new file. A mutex protected linked list of all main -** db fds opened using a particular RBU VFS is maintained at +** to point to the new file. A mutex protected linked list of all main +** db fds opened using a particular RBU VFS is maintained at ** rbu_vfs.pMain to facilitate this. ** -** 3. Using a new file-control "SQLITE_FCNTL_RBU", a main db rbu_file +** 3. Using a new file-control "SQLITE_FCNTL_RBU", a main db rbu_file ** object can be marked as the target database of an RBU update. This ** turns on the following extra special behaviour: ** -** 3a. If xAccess() is called to check if there exists a *-wal file +** 3a. If xAccess() is called to check if there exists a *-wal file ** associated with an RBU target database currently in RBU_STAGE_OAL ** stage (preparing the *-oal file), the following special handling ** applies: @@ -192922,30 +230828,30 @@ SQLITE_API int sqlite3rbu_savestate(sqlite3rbu *p){ ** ** Then, when xOpen() is called to open the *-wal file associated with ** the RBU target in RBU_STAGE_OAL stage, instead of opening the *-wal -** file, the rbu vfs opens the corresponding *-oal file instead. +** file, the rbu vfs opens the corresponding *-oal file instead. ** ** 3b. The *-shm pages returned by xShmMap() for a target db file in ** RBU_STAGE_OAL mode are actually stored in heap memory. This is to ** avoid creating a *-shm file on disk. Additionally, xShmLock() calls ** are no-ops on target database files in RBU_STAGE_OAL mode. This is -** because assert() statements in some VFS implementations fail if +** because assert() statements in some VFS implementations fail if ** xShmLock() is called before xShmMap(). ** ** 3c. If an EXCLUSIVE lock is attempted on a target database file in any -** mode except RBU_STAGE_DONE (all work completed and checkpointed), it +** mode except RBU_STAGE_DONE (all work completed and checkpointed), it ** fails with an SQLITE_BUSY error. This is to stop RBU connections ** from automatically checkpointing a *-wal (or *-oal) file from within ** sqlite3_close(). ** ** 3d. In RBU_STAGE_CAPTURE mode, all xRead() calls on the wal file, and -** all xWrite() calls on the target database file perform no IO. +** all xWrite() calls on the target database file perform no IO. ** Instead the frame and page numbers that would be read and written ** are recorded. Additionally, successful attempts to obtain exclusive -** xShmLock() WRITER, CHECKPOINTER and READ0 locks on the target +** xShmLock() WRITER, CHECKPOINTER and READ0 locks on the target ** database file are recorded. xShmLock() calls to unlock the same ** locks are no-ops (so that once obtained, these locks are never ** relinquished). Finally, calls to xSync() on the target database -** file fail with SQLITE_INTERNAL errors. +** file fail with SQLITE_NOTICE errors. */ static void rbuUnlockShm(rbu_file *p){ @@ -193017,7 +230923,7 @@ static void rbuMainlistRemove(rbu_file *p){ } /* -** Given that zWal points to a buffer containing a wal file name passed to +** Given that zWal points to a buffer containing a wal file name passed to ** either the xOpen() or xAccess() VFS method, search the main-db list for ** a file-handle opened by the same database connection on the corresponding ** database file. @@ -193054,9 +230960,12 @@ static int rbuVfsClose(sqlite3_file *pFile){ sqlite3_free(p->zDel); if( p->openFlags & SQLITE_OPEN_MAIN_DB ){ + const sqlite3_io_methods *pMeth = p->pReal->pMethods; rbuMainlistRemove(p); rbuUnlockShm(p); - p->pReal->pMethods->xShmUnmap(p->pReal, 0); + if( pMeth->iVersion>1 && pMeth->xShmUnmap ){ + pMeth->xShmUnmap(p->pReal, 0); + } } else if( (p->openFlags & SQLITE_OPEN_DELETEONCLOSE) && p->pRbu ){ rbuUpdateTempSize(p, 0); @@ -193070,7 +230979,7 @@ static int rbuVfsClose(sqlite3_file *pFile){ /* -** Read and return an unsigned 32-bit big-endian integer from the buffer +** Read and return an unsigned 32-bit big-endian integer from the buffer ** passed as the only argument. */ static u32 rbuGetU32(u8 *aBuf){ @@ -193100,9 +231009,9 @@ static void rbuPutU16(u8 *aBuf, u16 iVal){ ** Read data from an rbuVfs-file. */ static int rbuVfsRead( - sqlite3_file *pFile, - void *zBuf, - int iAmt, + sqlite3_file *pFile, + void *zBuf, + int iAmt, sqlite_int64 iOfst ){ rbu_file *p = (rbu_file*)pFile; @@ -193113,20 +231022,20 @@ static int rbuVfsRead( assert( p->openFlags & SQLITE_OPEN_WAL ); rc = rbuCaptureWalRead(p->pRbu, iOfst, iAmt); }else{ - if( pRbu && pRbu->eStage==RBU_STAGE_OAL - && (p->openFlags & SQLITE_OPEN_WAL) - && iOfst>=pRbu->iOalSz + if( pRbu && pRbu->eStage==RBU_STAGE_OAL + && (p->openFlags & SQLITE_OPEN_WAL) + && iOfst>=pRbu->iOalSz ){ rc = SQLITE_OK; memset(zBuf, 0, iAmt); }else{ rc = p->pReal->pMethods->xRead(p->pReal, zBuf, iAmt, iOfst); #if 1 - /* If this is being called to read the first page of the target - ** database as part of an rbu vacuum operation, synthesize the + /* If this is being called to read the first page of the target + ** database as part of an rbu vacuum operation, synthesize the ** contents of the first page if it does not yet exist. Otherwise, ** SQLite will not check for a *-wal file. */ - if( pRbu && rbuIsVacuum(pRbu) + if( pRbu && rbuIsVacuum(pRbu) && rc==SQLITE_IOERR_SHORT_READ && iOfst==0 && (p->openFlags & SQLITE_OPEN_MAIN_DB) && pRbu->rc==SQLITE_OK @@ -193166,9 +231075,9 @@ static int rbuVfsRead( ** Write data to an rbuVfs-file. */ static int rbuVfsWrite( - sqlite3_file *pFile, - const void *zBuf, - int iAmt, + sqlite3_file *pFile, + const void *zBuf, + int iAmt, sqlite_int64 iOfst ){ rbu_file *p = (rbu_file*)pFile; @@ -193180,8 +231089,8 @@ static int rbuVfsWrite( rc = rbuCaptureDbWrite(p->pRbu, iOfst); }else{ if( pRbu ){ - if( pRbu->eStage==RBU_STAGE_OAL - && (p->openFlags & SQLITE_OPEN_WAL) + if( pRbu->eStage==RBU_STAGE_OAL + && (p->openFlags & SQLITE_OPEN_WAL) && iOfst>=pRbu->iOalSz ){ pRbu->iOalSz = iAmt + iOfst; @@ -193224,7 +231133,7 @@ static int rbuVfsSync(sqlite3_file *pFile, int flags){ rbu_file *p = (rbu_file *)pFile; if( p->pRbu && p->pRbu->eStage==RBU_STAGE_CAPTURE ){ if( p->openFlags & SQLITE_OPEN_MAIN_DB ){ - return SQLITE_INTERNAL; + return SQLITE_NOTICE_RBU; } return SQLITE_OK; } @@ -193241,10 +231150,10 @@ static int rbuVfsFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){ /* If this is an RBU vacuum operation and this is the target database, ** pretend that it has at least one page. Otherwise, SQLite will not - ** check for the existance of a *-wal file. rbuVfsRead() contains + ** check for the existence of a *-wal file. rbuVfsRead() contains ** similar logic. */ - if( rc==SQLITE_OK && *pSize==0 - && p->pRbu && rbuIsVacuum(p->pRbu) + if( rc==SQLITE_OK && *pSize==0 + && p->pRbu && rbuIsVacuum(p->pRbu) && (p->openFlags & SQLITE_OPEN_MAIN_DB) ){ *pSize = 1024; @@ -193261,10 +231170,10 @@ static int rbuVfsLock(sqlite3_file *pFile, int eLock){ int rc = SQLITE_OK; assert( p->openFlags & (SQLITE_OPEN_MAIN_DB|SQLITE_OPEN_TEMP_DB) ); - if( eLock==SQLITE_LOCK_EXCLUSIVE + if( eLock==SQLITE_LOCK_EXCLUSIVE && (p->bNolock || (pRbu && pRbu->eStage!=RBU_STAGE_DONE)) ){ - /* Do not allow EXCLUSIVE locks. Preventing SQLite from taking this + /* Do not allow EXCLUSIVE locks. Preventing SQLite from taking this ** prevents it from checkpointing the database from sqlite3_close(). */ rc = SQLITE_BUSY; }else{ @@ -193320,9 +231229,7 @@ static int rbuVfsFileControl(sqlite3_file *pFile, int op, void *pArg){ }else if( rc==SQLITE_NOTFOUND ){ pRbu->pTargetFd = p; p->pRbu = pRbu; - if( p->openFlags & SQLITE_OPEN_MAIN_DB ){ - rbuMainlistAdd(p); - } + rbuMainlistAdd(p); if( p->pWalFd ) p->pWalFd->pRbu = pRbu; rc = SQLITE_OK; } @@ -193377,25 +231284,24 @@ static int rbuVfsShmLock(sqlite3_file *pFile, int ofst, int n, int flags){ #endif assert( p->openFlags & (SQLITE_OPEN_MAIN_DB|SQLITE_OPEN_TEMP_DB) ); - if( pRbu && (pRbu->eStage==RBU_STAGE_OAL || pRbu->eStage==RBU_STAGE_MOVE) ){ - /* Magic number 1 is the WAL_CKPT_LOCK lock. Preventing SQLite from - ** taking this lock also prevents any checkpoints from occurring. - ** todo: really, it's not clear why this might occur, as - ** wal_autocheckpoint ought to be turned off. */ + if( pRbu && ( + pRbu->eStage==RBU_STAGE_OAL + || pRbu->eStage==RBU_STAGE_MOVE + || pRbu->eStage==RBU_STAGE_DONE + )){ + /* Prevent SQLite from taking a shm-lock on the target file when it + ** is supplying heap memory to the upper layer in place of *-shm + ** segments. */ if( ofst==WAL_LOCK_CKPT && n==1 ) rc = SQLITE_BUSY; }else{ int bCapture = 0; - if( n==1 && (flags & SQLITE_SHM_EXCLUSIVE) - && pRbu && pRbu->eStage==RBU_STAGE_CAPTURE - && (ofst==WAL_LOCK_WRITE || ofst==WAL_LOCK_CKPT || ofst==WAL_LOCK_READ0) - ){ + if( pRbu && pRbu->eStage==RBU_STAGE_CAPTURE ){ bCapture = 1; } - if( bCapture==0 || 0==(flags & SQLITE_SHM_UNLOCK) ){ rc = p->pReal->pMethods->xShmLock(p->pReal, ofst, n, flags); if( bCapture && rc==SQLITE_OK ){ - pRbu->mLock |= (1 << ofst); + pRbu->mLock |= ((1<pRbu ? p->pRbu->eStage : 0); /* If not in RBU_STAGE_OAL, allow this call to pass through. Or, if this - ** rbu is in the RBU_STAGE_OAL state, use heap memory for *-shm space + ** rbu is in the RBU_STAGE_OAL state, use heap memory for *-shm space ** instead of a file on disk. */ assert( p->openFlags & (SQLITE_OPEN_MAIN_DB|SQLITE_OPEN_TEMP_DB) ); - if( eStage==RBU_STAGE_OAL || eStage==RBU_STAGE_MOVE ){ - if( iRegion<=p->nShm ){ - sqlite3_int64 nByte = (iRegion+1) * sizeof(char*); - char **apNew = (char**)sqlite3_realloc64(p->apShm, nByte); - if( apNew==0 ){ - rc = SQLITE_NOMEM; - }else{ - memset(&apNew[p->nShm], 0, sizeof(char*) * (1 + iRegion - p->nShm)); - p->apShm = apNew; - p->nShm = iRegion+1; - } + if( eStage==RBU_STAGE_OAL ){ + sqlite3_int64 nByte = (iRegion+1) * sizeof(char*); + char **apNew = (char**)sqlite3_realloc64(p->apShm, nByte); + + /* This is an RBU connection that uses its own heap memory for the + ** pages of the *-shm file. Since no other process can have run + ** recovery, the connection must request *-shm pages in order + ** from start to finish. */ + assert( iRegion==p->nShm ); + if( apNew==0 ){ + rc = SQLITE_NOMEM; + }else{ + memset(&apNew[p->nShm], 0, sizeof(char*) * (1 + iRegion - p->nShm)); + p->apShm = apNew; + p->nShm = iRegion+1; } - if( rc==SQLITE_OK && p->apShm[iRegion]==0 ){ + if( rc==SQLITE_OK ){ char *pNew = (char*)sqlite3_malloc64(szRegion); if( pNew==0 ){ rc = SQLITE_NOMEM; @@ -193484,33 +231394,6 @@ static int rbuVfsShmUnmap(sqlite3_file *pFile, int delFlag){ return rc; } -/* -** A main database named zName has just been opened. The following -** function returns a pointer to a buffer owned by SQLite that contains -** the name of the *-wal file this db connection will use. SQLite -** happens to pass a pointer to this buffer when using xAccess() -** or xOpen() to operate on the *-wal file. -*/ -static const char *rbuMainToWal(const char *zName, int flags){ - int n = (int)strlen(zName); - const char *z = &zName[n]; - if( flags & SQLITE_OPEN_URI ){ - int odd = 0; - while( 1 ){ - if( z[0]==0 ){ - odd = 1 - odd; - if( odd && z[1]==0 ) break; - } - z++; - } - z += 2; - }else{ - while( *z==0 ) z++; - } - z += (n + 8 + 1); - return z; -} - /* ** Open an rbu file handle. */ @@ -193541,6 +231424,25 @@ static int rbuVfsOpen( rbuVfsShmUnmap, /* xShmUnmap */ 0, 0 /* xFetch, xUnfetch */ }; + static sqlite3_io_methods rbuvfs_io_methods1 = { + 1, /* iVersion */ + rbuVfsClose, /* xClose */ + rbuVfsRead, /* xRead */ + rbuVfsWrite, /* xWrite */ + rbuVfsTruncate, /* xTruncate */ + rbuVfsSync, /* xSync */ + rbuVfsFileSize, /* xFileSize */ + rbuVfsLock, /* xLock */ + rbuVfsUnlock, /* xUnlock */ + rbuVfsCheckReservedLock, /* xCheckReservedLock */ + rbuVfsFileControl, /* xFileControl */ + rbuVfsSectorSize, /* xSectorSize */ + rbuVfsDeviceCharacteristics, /* xDeviceCharacteristics */ + 0, 0, 0, 0, 0, 0 + }; + + + rbu_vfs *pRbuVfs = (rbu_vfs*)pVfs; sqlite3_vfs *pRealVfs = pRbuVfs->pRealVfs; rbu_file *pFd = (rbu_file *)pFile; @@ -193559,34 +231461,20 @@ static int rbuVfsOpen( ** the name of the *-wal file this db connection will use. SQLite ** happens to pass a pointer to this buffer when using xAccess() ** or xOpen() to operate on the *-wal file. */ - pFd->zWal = rbuMainToWal(zName, flags); + pFd->zWal = sqlite3_filename_wal(zName); } else if( flags & SQLITE_OPEN_WAL ){ rbu_file *pDb = rbuFindMaindb(pRbuVfs, zName, 0); if( pDb ){ if( pDb->pRbu && pDb->pRbu->eStage==RBU_STAGE_OAL ){ - /* This call is to open a *-wal file. Intead, open the *-oal. This - ** code ensures that the string passed to xOpen() is terminated by a - ** pair of '\0' bytes in case the VFS attempts to extract a URI - ** parameter from it. */ - const char *zBase = zName; - size_t nCopy; - char *zCopy; + /* This call is to open a *-wal file. Intead, open the *-oal. */ + size_t nOpen; if( rbuIsVacuum(pDb->pRbu) ){ - zBase = sqlite3_db_filename(pDb->pRbu->dbRbu, "main"); - zBase = rbuMainToWal(zBase, SQLITE_OPEN_URI); - } - nCopy = strlen(zBase); - zCopy = sqlite3_malloc64(nCopy+2); - if( zCopy ){ - memcpy(zCopy, zBase, nCopy); - zCopy[nCopy-3] = 'o'; - zCopy[nCopy] = '\0'; - zCopy[nCopy+1] = '\0'; - zOpen = (const char*)(pFd->zDel = zCopy); - }else{ - rc = SQLITE_NOMEM; + zOpen = sqlite3_db_filename(pDb->pRbu->dbRbu, "main"); + zOpen = sqlite3_filename_wal(zOpen); } + nOpen = strlen(zOpen); + ((char*)zOpen)[nOpen-3] = 'o'; pFd->pRbu = pDb->pRbu; } pDb->pWalFd = pFd; @@ -193596,8 +231484,8 @@ static int rbuVfsOpen( pFd->pRbu = pRbuVfs->pRbu; } - if( oflags & SQLITE_OPEN_MAIN_DB - && sqlite3_uri_boolean(zName, "rbu_memory", 0) + if( oflags & SQLITE_OPEN_MAIN_DB + && sqlite3_uri_boolean(zName, "rbu_memory", 0) ){ assert( oflags & SQLITE_OPEN_MAIN_DB ); oflags = SQLITE_OPEN_TEMP_DB | SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | @@ -193609,10 +231497,15 @@ static int rbuVfsOpen( rc = pRealVfs->xOpen(pRealVfs, zOpen, pFd->pReal, oflags, pOutFlags); } if( pFd->pReal->pMethods ){ + const sqlite3_io_methods *pMeth = pFd->pReal->pMethods; /* The xOpen() operation has succeeded. Set the sqlite3_file.pMethods ** pointer and, if the file is a main database file, link it into the ** mutex protected linked list of all such files. */ - pFile->pMethods = &rbuvfs_io_methods; + if( pMeth->iVersion<2 || pMeth->xShmLock==0 ){ + pFile->pMethods = &rbuvfs_io_methods1; + }else{ + pFile->pMethods = &rbuvfs_io_methods; + } if( flags & SQLITE_OPEN_MAIN_DB ){ rbuMainlistAdd(pFd); } @@ -193636,9 +231529,9 @@ static int rbuVfsDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){ ** is available, or false otherwise. */ static int rbuVfsAccess( - sqlite3_vfs *pVfs, - const char *zPath, - int flags, + sqlite3_vfs *pVfs, + const char *zPath, + int flags, int *pResOut ){ rbu_vfs *pRbuVfs = (rbu_vfs*)pVfs; @@ -193654,7 +231547,7 @@ static int rbuVfsAccess( ** a) if the *-wal file does exist, return SQLITE_CANTOPEN. This ** ensures that the RBU extension never tries to update a database ** in wal mode, even if the first page of the database file has - ** been damaged. + ** been damaged. ** ** b) if the *-wal file does not exist, claim that it does anyway, ** causing SQLite to call xOpen() to open it. This call will also @@ -193663,7 +231556,8 @@ static int rbuVfsAccess( */ if( rc==SQLITE_OK && flags==SQLITE_ACCESS_EXISTS ){ rbu_file *pDb = rbuFindMaindb(pRbuVfs, zPath, 1); - if( pDb && pDb->pRbu && pDb->pRbu->eStage==RBU_STAGE_OAL ){ + if( pDb && pDb->pRbu->eStage==RBU_STAGE_OAL ){ + assert( pDb->pRbu ); if( *pResOut ){ rc = SQLITE_CANTOPEN; }else{ @@ -193683,9 +231577,9 @@ static int rbuVfsAccess( ** of at least (DEVSYM_MAX_PATHNAME+1) bytes. */ static int rbuVfsFullPathname( - sqlite3_vfs *pVfs, - const char *zPath, - int nOut, + sqlite3_vfs *pVfs, + const char *zPath, + int nOut, char *zOut ){ sqlite3_vfs *pRealVfs = ((rbu_vfs*)pVfs)->pRealVfs; @@ -193703,7 +231597,7 @@ static void *rbuVfsDlOpen(sqlite3_vfs *pVfs, const char *zPath){ /* ** Populate the buffer zErrMsg (size nByte bytes) with a human readable -** utf-8 string describing the most recent error encountered associated +** utf-8 string describing the most recent error encountered associated ** with dynamic libraries. */ static void rbuVfsDlError(sqlite3_vfs *pVfs, int nByte, char *zErrMsg){ @@ -193715,8 +231609,8 @@ static void rbuVfsDlError(sqlite3_vfs *pVfs, int nByte, char *zErrMsg){ ** Return a pointer to the symbol zSymbol in the dynamic library pHandle. */ static void (*rbuVfsDlSym( - sqlite3_vfs *pVfs, - void *pArg, + sqlite3_vfs *pVfs, + void *pArg, const char *zSym ))(void){ sqlite3_vfs *pRealVfs = ((rbu_vfs*)pVfs)->pRealVfs; @@ -193733,7 +231627,7 @@ static void rbuVfsDlClose(sqlite3_vfs *pVfs, void *pHandle){ #endif /* SQLITE_OMIT_LOAD_EXTENSION */ /* -** Populate the buffer pointed to by zBufOut with nByte bytes of +** Populate the buffer pointed to by zBufOut with nByte bytes of ** random data. */ static int rbuVfsRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){ @@ -193742,7 +231636,7 @@ static int rbuVfsRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){ } /* -** Sleep for nMicro microseconds. Return the number of microseconds +** Sleep for nMicro microseconds. Return the number of microseconds ** actually slept. */ static int rbuVfsSleep(sqlite3_vfs *pVfs, int nMicro){ @@ -193762,6 +231656,9 @@ static int rbuVfsCurrentTime(sqlite3_vfs *pVfs, double *pTimeOut){ ** No-op. */ static int rbuVfsGetLastError(sqlite3_vfs *pVfs, int a, char *b){ + UNUSED_PARAMETER(pVfs); + UNUSED_PARAMETER(a); + UNUSED_PARAMETER(b); return 0; } @@ -193893,7 +231790,7 @@ SQLITE_API sqlite3_int64 sqlite3rbu_temp_size(sqlite3rbu *pRbu){ ** ** This file contains an implementation of the "dbstat" virtual table. ** -** The dbstat virtual table is used to extract low-level formatting +** The dbstat virtual table is used to extract low-level storage ** information from an SQLite database in order to implement the ** "sqlite3_analyzer" utility. See the ../tool/spaceanal.tcl script ** for an example implementation. @@ -193906,24 +231803,33 @@ SQLITE_API sqlite3_int64 sqlite3rbu_temp_size(sqlite3rbu *pRbu){ #if (defined(SQLITE_ENABLE_DBSTAT_VTAB) || defined(SQLITE_TEST)) \ && !defined(SQLITE_OMIT_VIRTUALTABLE) +/* +** The pager and btree modules arrange objects in memory so that there are +** always approximately 200 bytes of addressable memory following each page +** buffer. This way small buffer overreads caused by corrupt database pages +** do not cause undefined behaviour. This module pads each page buffer +** by the following number of bytes for the same purpose. +*/ +#define DBSTAT_PAGE_PADDING_BYTES 256 + /* ** Page paths: -** -** The value of the 'path' column describes the path taken from the -** root-node of the b-tree structure to each page. The value of the +** +** The value of the 'path' column describes the path taken from the +** root-node of the b-tree structure to each page. The value of the ** root-node path is '/'. ** ** The value of the path for the left-most child page of the root of ** a b-tree is '/000/'. (Btrees store content ordered from left to right ** so the pages to the left have smaller keys than the pages to the right.) ** The next to left-most child of the root page is -** '/001', and so on, each sibling page identified by a 3-digit hex +** '/001', and so on, each sibling page identified by a 3-digit hex ** value. The children of the 451st left-most sibling have paths such ** as '/1c2/000/, '/1c2/001/' etc. ** -** Overflow pages are specified by appending a '+' character and a +** Overflow pages are specified by appending a '+' character and a ** six-digit hexadecimal value to the path to the cell they are linked -** from. For example, the three overflow pages in a chain linked from +** from. For example, the three overflow pages in a chain linked from ** the left-most cell of the 450th child of the root page are identified ** by the paths: ** @@ -193937,27 +231843,30 @@ SQLITE_API sqlite3_int64 sqlite3rbu_temp_size(sqlite3rbu *pRbu){ ** ** '/1c2/000/' // Left-most child of 451st child of root */ -#define VTAB_SCHEMA \ - "CREATE TABLE xx( " \ - " name TEXT, /* Name of table or index */" \ - " path TEXT, /* Path to page from root */" \ - " pageno INTEGER, /* Page number */" \ - " pagetype TEXT, /* 'internal', 'leaf' or 'overflow' */" \ - " ncell INTEGER, /* Cells on page (0 for overflow) */" \ - " payload INTEGER, /* Bytes of payload on this page */" \ - " unused INTEGER, /* Bytes of unused space on this page */" \ - " mx_payload INTEGER, /* Largest payload size of all cells */" \ - " pgoffset INTEGER, /* Offset of page in file */" \ - " pgsize INTEGER, /* Size of the page */" \ - " schema TEXT HIDDEN /* Database schema being analyzed */" \ - ");" - - +static const char zDbstatSchema[] = + "CREATE TABLE x(" + " name TEXT," /* 0 Name of table or index */ + " path TEXT," /* 1 Path to page from root (NULL for agg) */ + " pageno INTEGER," /* 2 Page number (page count for aggregates) */ + " pagetype TEXT," /* 3 'internal', 'leaf', 'overflow', or NULL */ + " ncell INTEGER," /* 4 Cells on page (0 for overflow) */ + " payload INTEGER," /* 5 Bytes of payload on this page */ + " unused INTEGER," /* 6 Bytes of unused space on this page */ + " mx_payload INTEGER," /* 7 Largest payload size of all cells */ + " pgoffset INTEGER," /* 8 Offset of page in file (NULL for agg) */ + " pgsize INTEGER," /* 9 Size of the page (sum for aggregate) */ + " schema TEXT HIDDEN," /* 10 Database schema being analyzed */ + " aggregate BOOLEAN HIDDEN" /* 11 aggregate info for each table */ + ")" +; + +/* Forward reference to data structured used in this module */ typedef struct StatTable StatTable; typedef struct StatCursor StatCursor; typedef struct StatPage StatPage; typedef struct StatCell StatCell; +/* Size information for a single cell within a btree page */ struct StatCell { int nLocal; /* Bytes of local payload */ u32 iChildPg; /* Child node (or 0 if this is a leaf) */ @@ -193967,11 +231876,11 @@ struct StatCell { int iOvfl; /* Iterates through aOvfl[] */ }; +/* Size information for a single btree page */ struct StatPage { - u32 iPgno; - DbPage *pPg; - int iCell; - + u32 iPgno; /* Page number */ + u8 *aPg; /* Page buffer from sqlite3_malloc() */ + int iCell; /* Current cell */ char *zPath; /* Path to this page */ /* Variables populated by statDecodePage(): */ @@ -193980,34 +231889,38 @@ struct StatPage { int nUnused; /* Number of unused bytes on page */ StatCell *aCell; /* Array of parsed cells */ u32 iRightChildPg; /* Right-child page number (or 0) */ - int nMxPayload; /* Largest payload of any cell on this page */ + int nMxPayload; /* Largest payload of any cell on the page */ }; +/* The cursor for scanning the dbstat virtual table */ struct StatCursor { - sqlite3_vtab_cursor base; + sqlite3_vtab_cursor base; /* base class. MUST BE FIRST! */ sqlite3_stmt *pStmt; /* Iterates through set of root pages */ - int isEof; /* After pStmt has returned SQLITE_DONE */ + u8 isEof; /* After pStmt has returned SQLITE_DONE */ + u8 isAgg; /* Aggregate results for each table */ int iDb; /* Schema used for this query */ - StatPage aPage[32]; + StatPage aPage[32]; /* Pages in path to current page */ int iPage; /* Current entry in aPage[] */ /* Values to return. */ + u32 iPageno; /* Value of 'pageno' column */ char *zName; /* Value of 'name' column */ char *zPath; /* Value of 'path' column */ - u32 iPageno; /* Value of 'pageno' column */ char *zPagetype; /* Value of 'pagetype' column */ + int nPage; /* Number of pages in current btree */ int nCell; /* Value of 'ncell' column */ - int nPayload; /* Value of 'payload' column */ - int nUnused; /* Value of 'unused' column */ int nMxPayload; /* Value of 'mx_payload' column */ + i64 nUnused; /* Value of 'unused' column */ + i64 nPayload; /* Value of 'payload' column */ i64 iOffset; /* Value of 'pgOffset' column */ - int szPage; /* Value of 'pgSize' column */ + i64 szPage; /* Value of 'pgSize' column */ }; +/* An instance of the DBSTAT virtual table */ struct StatTable { - sqlite3_vtab base; - sqlite3 *db; + sqlite3_vtab base; /* base class. MUST BE FIRST! */ + sqlite3 *db; /* Database connection that owns this vtab */ int iDb; /* Index of database to analyze */ }; @@ -194016,7 +231929,7 @@ struct StatTable { #endif /* -** Connect to or create a statvfs virtual table. +** Connect to or create a new DBSTAT virtual table. */ static int statConnect( sqlite3 *db, @@ -194028,6 +231941,7 @@ static int statConnect( StatTable *pTab = 0; int rc = SQLITE_OK; int iDb; + (void)pAux; if( argc>=4 ){ Token nm; @@ -194040,7 +231954,8 @@ static int statConnect( }else{ iDb = 0; } - rc = sqlite3_declare_vtab(db, VTAB_SCHEMA); + sqlite3_vtab_config(db, SQLITE_VTAB_DIRECTONLY); + rc = sqlite3_declare_vtab(db, zDbstatSchema); if( rc==SQLITE_OK ){ pTab = (StatTable *)sqlite3_malloc64(sizeof(StatTable)); if( pTab==0 ) rc = SQLITE_NOMEM_BKPT; @@ -194058,7 +231973,7 @@ static int statConnect( } /* -** Disconnect from or destroy a statvfs virtual table. +** Disconnect from or destroy the DBSTAT virtual table. */ static int statDisconnect(sqlite3_vtab *pVtab){ sqlite3_free(pVtab); @@ -194066,14 +231981,21 @@ static int statDisconnect(sqlite3_vtab *pVtab){ } /* -** There is no "best-index". This virtual table always does a linear -** scan. However, a schema=? constraint should cause this table to -** operate on a different database schema, so check for it. +** Compute the best query strategy and return the result in idxNum. ** -** idxNum is normally 0, but will be 1 if a schema=? constraint exists. +** idxNum-Bit Meaning +** ---------- ---------------------------------------------- +** 0x01 There is a schema=? term in the WHERE clause +** 0x02 There is a name=? term in the WHERE clause +** 0x04 There is an aggregate=? term in the WHERE clause +** 0x08 Output should be ordered by name and path */ static int statBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ int i; + int iSchema = -1; + int iName = -1; + int iAgg = -1; + (void)tab; /* Look for a valid schema=? constraint. If found, change the idxNum to ** 1 and request the value of that constraint be sent to xFilter. And @@ -194081,19 +232003,44 @@ static int statBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ ** used. */ for(i=0; inConstraint; i++){ - if( pIdxInfo->aConstraint[i].iColumn!=10 ) continue; - if( pIdxInfo->aConstraint[i].usable==0 ) return SQLITE_CONSTRAINT; if( pIdxInfo->aConstraint[i].op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue; - pIdxInfo->idxNum = 1; - pIdxInfo->estimatedCost = 1.0; - pIdxInfo->aConstraintUsage[i].argvIndex = 1; - pIdxInfo->aConstraintUsage[i].omit = 1; - break; + if( pIdxInfo->aConstraint[i].usable==0 ){ + /* Force DBSTAT table should always be the right-most table in a join */ + return SQLITE_CONSTRAINT; + } + switch( pIdxInfo->aConstraint[i].iColumn ){ + case 0: { /* name */ + iName = i; + break; + } + case 10: { /* schema */ + iSchema = i; + break; + } + case 11: { /* aggregate */ + iAgg = i; + break; + } + } } + i = 0; + if( iSchema>=0 ){ + pIdxInfo->aConstraintUsage[iSchema].argvIndex = ++i; + pIdxInfo->aConstraintUsage[iSchema].omit = 1; + pIdxInfo->idxNum |= 0x01; + } + if( iName>=0 ){ + pIdxInfo->aConstraintUsage[iName].argvIndex = ++i; + pIdxInfo->idxNum |= 0x02; + } + if( iAgg>=0 ){ + pIdxInfo->aConstraintUsage[iAgg].argvIndex = ++i; + pIdxInfo->idxNum |= 0x04; + } + pIdxInfo->estimatedCost = 1.0; - - /* Records are always returned in ascending order of (name, path). - ** If this will satisfy the client, set the orderByConsumed flag so that + /* Records are always returned in ascending order of (name, path). + ** If this will satisfy the client, set the orderByConsumed flag so that ** SQLite does not do an external sort. */ if( ( pIdxInfo->nOrderBy==1 @@ -194108,13 +232055,15 @@ static int statBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ ) ){ pIdxInfo->orderByConsumed = 1; + pIdxInfo->idxNum |= 0x08; } + pIdxInfo->idxFlags |= SQLITE_INDEX_SCAN_HEX; return SQLITE_OK; } /* -** Open a new statvfs cursor. +** Open a new DBSTAT cursor. */ static int statOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){ StatTable *pTab = (StatTable *)pVTab; @@ -194146,26 +232095,43 @@ static void statClearCells(StatPage *p){ } static void statClearPage(StatPage *p){ + u8 *aPg = p->aPg; statClearCells(p); - sqlite3PagerUnref(p->pPg); sqlite3_free(p->zPath); memset(p, 0, sizeof(StatPage)); + p->aPg = aPg; } static void statResetCsr(StatCursor *pCsr){ int i; - sqlite3_reset(pCsr->pStmt); + /* In some circumstances, specifically if an OOM has occurred, the call + ** to sqlite3_reset() may cause the pager to be reset (emptied). It is + ** important that statClearPage() is called to free any page refs before + ** this happens. dbsqlfuzz 9ed3e4e3816219d3509d711636c38542bf3f40b1. */ for(i=0; iaPage); i++){ statClearPage(&pCsr->aPage[i]); + sqlite3_free(pCsr->aPage[i].aPg); + pCsr->aPage[i].aPg = 0; } + sqlite3_reset(pCsr->pStmt); pCsr->iPage = 0; sqlite3_free(pCsr->zPath); pCsr->zPath = 0; pCsr->isEof = 0; } +/* Resize the space-used counters inside of the cursor */ +static void statResetCounts(StatCursor *pCsr){ + pCsr->nCell = 0; + pCsr->nMxPayload = 0; + pCsr->nUnused = 0; + pCsr->nPayload = 0; + pCsr->szPage = 0; + pCsr->nPage = 0; +} + /* -** Close a statvfs cursor. +** Close a DBSTAT cursor. */ static int statClose(sqlite3_vtab_cursor *pCursor){ StatCursor *pCsr = (StatCursor *)pCursor; @@ -194175,16 +232141,20 @@ static int statClose(sqlite3_vtab_cursor *pCursor){ return SQLITE_OK; } -static void getLocalPayload( +/* +** For a single cell on a btree page, compute the number of bytes of +** content (payload) stored on that page. That is to say, compute the +** number of bytes of content not found on overflow pages. +*/ +static int getLocalPayload( int nUsable, /* Usable bytes per page */ u8 flags, /* Page flags */ - int nTotal, /* Total record (payload) size */ - int *pnLocal /* OUT: Bytes stored locally */ + int nTotal /* Total record (payload) size */ ){ int nLocal; int nMinLocal; int nMaxLocal; - + if( flags==0x0D ){ /* Table leaf node */ nMinLocal = (nUsable - 12) * 32 / 255 - 23; nMaxLocal = nUsable - 35; @@ -194195,9 +232165,12 @@ static void getLocalPayload( nLocal = nMinLocal + (nTotal - nMinLocal) % (nUsable - 4); if( nLocal>nMaxLocal ) nLocal = nMinLocal; - *pnLocal = nLocal; + return nLocal; } +/* Populate the StatPage object with information about the all +** cells found on the page currently under analysis. +*/ static int statDecodePage(Btree *pBt, StatPage *p){ int nUnused; int iOff; @@ -194205,7 +232178,7 @@ static int statDecodePage(Btree *pBt, StatPage *p){ int isLeaf; int szPage; - u8 *aData = sqlite3PagerGetData(p->pPg); + u8 *aData = p->aPg; u8 *aHdr = &aData[p->iPgno==1 ? 100 : 0]; p->flags = aHdr[0]; @@ -194268,7 +232241,7 @@ static int statDecodePage(Btree *pBt, StatPage *p){ iOff += sqlite3GetVarint(&aData[iOff], &dummy); } if( nPayload>(u32)p->nMxPayload ) p->nMxPayload = nPayload; - getLocalPayload(nUsable, p->flags, nPayload, &nLocal); + nLocal = getLocalPayload(nUsable, p->flags, nPayload); if( nLocal<0 ) goto statPageIsCorrupt; pCell->nLocal = nLocal; assert( nPayload>=(u32)nLocal ); @@ -194276,7 +232249,9 @@ static int statDecodePage(Btree *pBt, StatPage *p){ if( nPayload>(u32)nLocal ){ int j; int nOvfl = ((nPayload - nLocal) + nUsable-4 - 1) / (nUsable - 4); - if( iOff+nLocal>nUsable ) goto statPageIsCorrupt; + if( iOff+nLocal+4>nUsable || nPayload>0x7fffffff ){ + goto statPageIsCorrupt; + } pCell->nLastOvfl = (nPayload-nLocal) - (nOvfl-1) * (nUsable-4); pCell->nOvfl = nOvfl; pCell->aOvfl = sqlite3_malloc64(sizeof(u32)*nOvfl); @@ -194290,7 +232265,7 @@ static int statDecodePage(Btree *pBt, StatPage *p){ if( rc!=SQLITE_OK ){ assert( pPg==0 ); return rc; - } + } pCell->aOvfl[j] = sqlite3Get4byte(sqlite3PagerGetData(pPg)); sqlite3PagerUnref(pPg); } @@ -194318,23 +232293,57 @@ static void statSizeAndOffset(StatCursor *pCsr){ sqlite3_file *fd; sqlite3_int64 x[2]; - /* The default page size and offset */ - pCsr->szPage = sqlite3BtreeGetPageSize(pBt); - pCsr->iOffset = (i64)pCsr->szPage * (pCsr->iPageno - 1); - - /* If connected to a ZIPVFS backend, override the page size and - ** offset with actual values obtained from ZIPVFS. + /* If connected to a ZIPVFS backend, find the page size and + ** offset from ZIPVFS. */ fd = sqlite3PagerFile(pPager); x[0] = pCsr->iPageno; if( sqlite3OsFileControl(fd, 230440, &x)==SQLITE_OK ){ pCsr->iOffset = x[0]; - pCsr->szPage = (int)x[1]; + pCsr->szPage += x[1]; + }else{ + /* Not ZIPVFS: The default page size and offset */ + pCsr->szPage += sqlite3BtreeGetPageSize(pBt); + pCsr->iOffset = (i64)pCsr->szPage * (pCsr->iPageno - 1); + } +} + +/* +** Load a copy of the page data for page iPg into the buffer belonging +** to page object pPg. Allocate the buffer if necessary. Return SQLITE_OK +** if successful, or an SQLite error code otherwise. +*/ +static int statGetPage( + Btree *pBt, /* Load page from this b-tree */ + u32 iPg, /* Page number to load */ + StatPage *pPg /* Load page into this object */ +){ + int pgsz = sqlite3BtreeGetPageSize(pBt); + DbPage *pDbPage = 0; + int rc; + + if( pPg->aPg==0 ){ + pPg->aPg = (u8*)sqlite3_malloc(pgsz + DBSTAT_PAGE_PADDING_BYTES); + if( pPg->aPg==0 ){ + return SQLITE_NOMEM_BKPT; + } + memset(&pPg->aPg[pgsz], 0, DBSTAT_PAGE_PADDING_BYTES); + } + + rc = sqlite3PagerGet(sqlite3BtreePager(pBt), iPg, &pDbPage, 0); + if( rc==SQLITE_OK ){ + const u8 *a = sqlite3PagerGetData(pDbPage); + memcpy(pPg->aPg, a, pgsz); + sqlite3PagerUnref(pDbPage); } + + return rc; } /* -** Move a statvfs cursor to the next entry in the file. +** Move a DBSTAT cursor to the next entry. Normally, the next +** entry will be the next page, but in aggregated mode (pCsr->isAgg!=0), +** the next entry is the next btree. */ static int statNext(sqlite3_vtab_cursor *pCursor){ int rc; @@ -194349,7 +232358,9 @@ static int statNext(sqlite3_vtab_cursor *pCursor){ pCsr->zPath = 0; statNextRestart: - if( pCsr->aPage[0].pPg==0 ){ + if( pCsr->iPage<0 ){ + /* Start measuring space on the next btree */ + statResetCounts(pCsr); rc = sqlite3_step(pCsr->pStmt); if( rc==SQLITE_ROW ){ int nPage; @@ -194359,47 +232370,50 @@ static int statNext(sqlite3_vtab_cursor *pCursor){ pCsr->isEof = 1; return sqlite3_reset(pCsr->pStmt); } - rc = sqlite3PagerGet(pPager, iRoot, &pCsr->aPage[0].pPg, 0); + rc = statGetPage(pBt, iRoot, &pCsr->aPage[0]); pCsr->aPage[0].iPgno = iRoot; pCsr->aPage[0].iCell = 0; - pCsr->aPage[0].zPath = z = sqlite3_mprintf("/"); + if( !pCsr->isAgg ){ + pCsr->aPage[0].zPath = z = sqlite3_mprintf("/"); + if( z==0 ) rc = SQLITE_NOMEM_BKPT; + } pCsr->iPage = 0; - if( z==0 ) rc = SQLITE_NOMEM_BKPT; + pCsr->nPage = 1; }else{ pCsr->isEof = 1; return sqlite3_reset(pCsr->pStmt); } }else{ - - /* Page p itself has already been visited. */ + /* Continue analyzing the btree previously started */ StatPage *p = &pCsr->aPage[pCsr->iPage]; - + if( !pCsr->isAgg ) statResetCounts(pCsr); while( p->iCellnCell ){ StatCell *pCell = &p->aCell[p->iCell]; - if( pCell->iOvflnOvfl ){ - int nUsable; + while( pCell->iOvflnOvfl ){ + int nUsable, iOvfl; sqlite3BtreeEnter(pBt); - nUsable = sqlite3BtreeGetPageSize(pBt) - + nUsable = sqlite3BtreeGetPageSize(pBt) - sqlite3BtreeGetReserveNoMutex(pBt); sqlite3BtreeLeave(pBt); - pCsr->zName = (char *)sqlite3_column_text(pCsr->pStmt, 0); - pCsr->iPageno = pCell->aOvfl[pCell->iOvfl]; - pCsr->zPagetype = "overflow"; - pCsr->nCell = 0; - pCsr->nMxPayload = 0; - pCsr->zPath = z = sqlite3_mprintf( - "%s%.3x+%.6x", p->zPath, p->iCell, pCell->iOvfl - ); + pCsr->nPage++; + statSizeAndOffset(pCsr); if( pCell->iOvflnOvfl-1 ){ - pCsr->nUnused = 0; - pCsr->nPayload = nUsable - 4; + pCsr->nPayload += nUsable - 4; }else{ - pCsr->nPayload = pCell->nLastOvfl; - pCsr->nUnused = nUsable - 4 - pCsr->nPayload; + pCsr->nPayload += pCell->nLastOvfl; + pCsr->nUnused += nUsable - 4 - pCell->nLastOvfl; } + iOvfl = pCell->iOvfl; pCell->iOvfl++; - statSizeAndOffset(pCsr); - return z==0 ? SQLITE_NOMEM_BKPT : SQLITE_OK; + if( !pCsr->isAgg ){ + pCsr->zName = (char *)sqlite3_column_text(pCsr->pStmt, 0); + pCsr->iPageno = pCell->aOvfl[iOvfl]; + pCsr->zPagetype = "overflow"; + pCsr->zPath = z = sqlite3_mprintf( + "%s%.3x+%.6x", p->zPath, p->iCell, iOvfl + ); + return z==0 ? SQLITE_NOMEM_BKPT : SQLITE_OK; + } } if( p->iRightChildPg ) break; p->iCell++; @@ -194407,8 +232421,12 @@ static int statNext(sqlite3_vtab_cursor *pCursor){ if( !p->iRightChildPg || p->iCell>p->nCell ){ statClearPage(p); - if( pCsr->iPage==0 ) return statNext(pCursor); pCsr->iPage--; + if( pCsr->isAgg && pCsr->iPage<0 ){ + /* label-statNext-done: When computing aggregate space usage over + ** an entire btree, this is the exit point from this function */ + return SQLITE_OK; + } goto statNextRestart; /* Tail recursion */ } pCsr->iPage++; @@ -194423,11 +232441,14 @@ static int statNext(sqlite3_vtab_cursor *pCursor){ }else{ p[1].iPgno = p->aCell[p->iCell].iChildPg; } - rc = sqlite3PagerGet(pPager, p[1].iPgno, &p[1].pPg, 0); + rc = statGetPage(pBt, p[1].iPgno, &p[1]); + pCsr->nPage++; p[1].iCell = 0; - p[1].zPath = z = sqlite3_mprintf("%s%.3x/", p->zPath, p->iCell); + if( !pCsr->isAgg ){ + p[1].zPath = z = sqlite3_mprintf("%s%.3x/", p->zPath, p->iCell); + if( z==0 ) rc = SQLITE_NOMEM_BKPT; + } p->iCell++; - if( z==0 ) rc = SQLITE_NOMEM_BKPT; } @@ -194457,16 +232478,23 @@ static int statNext(sqlite3_vtab_cursor *pCursor){ pCsr->zPagetype = "corrupted"; break; } - pCsr->nCell = p->nCell; - pCsr->nUnused = p->nUnused; - pCsr->nMxPayload = p->nMxPayload; - pCsr->zPath = z = sqlite3_mprintf("%s", p->zPath); - if( z==0 ) rc = SQLITE_NOMEM_BKPT; + pCsr->nCell += p->nCell; + pCsr->nUnused += p->nUnused; + if( p->nMxPayload>pCsr->nMxPayload ) pCsr->nMxPayload = p->nMxPayload; + if( !pCsr->isAgg ){ + pCsr->zPath = z = sqlite3_mprintf("%s", p->zPath); + if( z==0 ) rc = SQLITE_NOMEM_BKPT; + } nPayload = 0; for(i=0; inCell; i++){ nPayload += p->aCell[i].nLocal; } - pCsr->nPayload = nPayload; + pCsr->nPayload += nPayload; + + /* If computing aggregate space usage by btree, continue with the + ** next page. The loop will exit via the return at label-statNext-done + */ + if( pCsr->isAgg ) goto statNextRestart; } } @@ -194478,36 +232506,65 @@ static int statEof(sqlite3_vtab_cursor *pCursor){ return pCsr->isEof; } +/* Initialize a cursor according to the query plan idxNum using the +** arguments in argv[0]. See statBestIndex() for a description of the +** meaning of the bits in idxNum. +*/ static int statFilter( - sqlite3_vtab_cursor *pCursor, + sqlite3_vtab_cursor *pCursor, int idxNum, const char *idxStr, int argc, sqlite3_value **argv ){ StatCursor *pCsr = (StatCursor *)pCursor; StatTable *pTab = (StatTable*)(pCursor->pVtab); - char *zSql; - int rc = SQLITE_OK; + sqlite3_str *pSql; /* Query of btrees to analyze */ + char *zSql; /* String value of pSql */ + int iArg = 0; /* Count of argv[] parameters used so far */ + int rc = SQLITE_OK; /* Result of this operation */ + const char *zName = 0; /* Only provide analysis of this table */ + (void)argc; + (void)idxStr; - if( idxNum==1 ){ - const char *zDbase = (const char*)sqlite3_value_text(argv[0]); + statResetCsr(pCsr); + sqlite3_finalize(pCsr->pStmt); + pCsr->pStmt = 0; + if( idxNum & 0x01 ){ + /* schema=? constraint is present. Get its value */ + const char *zDbase = (const char*)sqlite3_value_text(argv[iArg++]); pCsr->iDb = sqlite3FindDbName(pTab->db, zDbase); if( pCsr->iDb<0 ){ - sqlite3_free(pCursor->pVtab->zErrMsg); - pCursor->pVtab->zErrMsg = sqlite3_mprintf("no such schema: %s", zDbase); - return pCursor->pVtab->zErrMsg ? SQLITE_ERROR : SQLITE_NOMEM_BKPT; + pCsr->iDb = 0; + pCsr->isEof = 1; + return SQLITE_OK; } }else{ pCsr->iDb = pTab->iDb; } - statResetCsr(pCsr); - sqlite3_finalize(pCsr->pStmt); - pCsr->pStmt = 0; - zSql = sqlite3_mprintf( - "SELECT 'sqlite_master' AS name, 1 AS rootpage, 'table' AS type" - " UNION ALL " - "SELECT name, rootpage, type" - " FROM \"%w\".sqlite_master WHERE rootpage!=0" - " ORDER BY name", pTab->db->aDb[pCsr->iDb].zDbSName); + if( idxNum & 0x02 ){ + /* name=? constraint is present */ + zName = (const char*)sqlite3_value_text(argv[iArg++]); + } + if( idxNum & 0x04 ){ + /* aggregate=? constraint is present */ + pCsr->isAgg = sqlite3_value_double(argv[iArg++])!=0.0; + }else{ + pCsr->isAgg = 0; + } + pSql = sqlite3_str_new(pTab->db); + sqlite3_str_appendf(pSql, + "SELECT * FROM (" + "SELECT 'sqlite_schema' AS name,1 AS rootpage,'table' AS type" + " UNION ALL " + "SELECT name,rootpage,type" + " FROM \"%w\".sqlite_schema WHERE rootpage!=0)", + pTab->db->aDb[pCsr->iDb].zDbSName); + if( zName ){ + sqlite3_str_appendf(pSql, "WHERE name=%Q", zName); + } + if( idxNum & 0x08 ){ + sqlite3_str_appendf(pSql, " ORDER BY name"); + } + zSql = sqlite3_str_finish(pSql); if( zSql==0 ){ return SQLITE_NOMEM_BKPT; }else{ @@ -194516,14 +232573,15 @@ static int statFilter( } if( rc==SQLITE_OK ){ + pCsr->iPage = -1; rc = statNext(pCursor); } return rc; } static int statColumn( - sqlite3_vtab_cursor *pCursor, - sqlite3_context *ctx, + sqlite3_vtab_cursor *pCursor, + sqlite3_context *ctx, int i ){ StatCursor *pCsr = (StatCursor *)pCursor; @@ -194532,38 +232590,52 @@ static int statColumn( sqlite3_result_text(ctx, pCsr->zName, -1, SQLITE_TRANSIENT); break; case 1: /* path */ - sqlite3_result_text(ctx, pCsr->zPath, -1, SQLITE_TRANSIENT); + if( !pCsr->isAgg ){ + sqlite3_result_text(ctx, pCsr->zPath, -1, SQLITE_TRANSIENT); + } break; case 2: /* pageno */ - sqlite3_result_int64(ctx, pCsr->iPageno); + if( pCsr->isAgg ){ + sqlite3_result_int64(ctx, pCsr->nPage); + }else{ + sqlite3_result_int64(ctx, pCsr->iPageno); + } break; case 3: /* pagetype */ - sqlite3_result_text(ctx, pCsr->zPagetype, -1, SQLITE_STATIC); + if( !pCsr->isAgg ){ + sqlite3_result_text(ctx, pCsr->zPagetype, -1, SQLITE_STATIC); + } break; case 4: /* ncell */ - sqlite3_result_int(ctx, pCsr->nCell); + sqlite3_result_int64(ctx, pCsr->nCell); break; case 5: /* payload */ - sqlite3_result_int(ctx, pCsr->nPayload); + sqlite3_result_int64(ctx, pCsr->nPayload); break; case 6: /* unused */ - sqlite3_result_int(ctx, pCsr->nUnused); + sqlite3_result_int64(ctx, pCsr->nUnused); break; case 7: /* mx_payload */ - sqlite3_result_int(ctx, pCsr->nMxPayload); + sqlite3_result_int64(ctx, pCsr->nMxPayload); break; case 8: /* pgoffset */ - sqlite3_result_int64(ctx, pCsr->iOffset); + if( !pCsr->isAgg ){ + sqlite3_result_int64(ctx, pCsr->iOffset); + } break; case 9: /* pgsize */ - sqlite3_result_int(ctx, pCsr->szPage); + sqlite3_result_int64(ctx, pCsr->szPage); break; - default: { /* schema */ + case 10: { /* schema */ sqlite3 *db = sqlite3_context_db_handle(ctx); int iDb = pCsr->iDb; sqlite3_result_text(ctx, db->aDb[iDb].zDbSName, -1, SQLITE_STATIC); break; } + default: { /* aggregate */ + sqlite3_result_int(ctx, pCsr->isAgg); + break; + } } return SQLITE_OK; } @@ -194602,7 +232674,8 @@ SQLITE_PRIVATE int sqlite3DbstatRegister(sqlite3 *db){ 0, /* xSavepoint */ 0, /* xRelease */ 0, /* xRollbackTo */ - 0 /* xShadowName */ + 0, /* xShadowName */ + 0 /* xIntegrity */ }; return sqlite3_create_module(db, "dbstat", &dbstat_module, 0); } @@ -194627,7 +232700,7 @@ SQLITE_PRIVATE int sqlite3DbstatRegister(sqlite3 *db){ return SQLITE_OK; } ** This file contains an implementation of the "sqlite_dbpage" virtual table. ** ** The sqlite_dbpage virtual table is used to read or write whole raw -** pages of the database file. The pager interface is used so that +** pages of the database file. The pager interface is used so that ** uncommitted changes and changes recorded in the WAL file are correctly ** retrieved. ** @@ -194642,7 +232715,13 @@ SQLITE_PRIVATE int sqlite3DbstatRegister(sqlite3 *db){ return SQLITE_OK; } ** ** The data field of sqlite_dbpage table can be updated. The new ** value must be a BLOB which is the correct page size, otherwise the -** update fails. Rows may not be deleted or inserted. +** update fails. INSERT operations also work, and operate as if they +** where REPLACE. The size of the database can be extended by INSERT-ing +** new pages on the end. +** +** Rows may not be deleted. However, doing an INSERT to page number N +** with NULL page data causes the N-th page and all subsequent pages to be +** deleted and the database to be truncated. */ /* #include "sqliteInt.h" ** Requires access to internal data structures ** */ @@ -194654,8 +232733,8 @@ typedef struct DbpageCursor DbpageCursor; struct DbpageCursor { sqlite3_vtab_cursor base; /* Base class. Must be first */ - int pgno; /* Current page number */ - int mxPgno; /* Last page to visit on this scan */ + Pgno pgno; /* Current page number */ + Pgno mxPgno; /* Last page to visit on this scan */ Pager *pPager; /* Pager being read/written */ DbPage *pPage1; /* Page 1 of the database */ int iDb; /* Index of database to analyze */ @@ -194665,6 +232744,8 @@ struct DbpageCursor { struct DbpageTable { sqlite3_vtab base; /* Base class. Must be first */ sqlite3 *db; /* The database */ + int iDbTrunc; /* Database to truncate */ + Pgno pgnoTrunc; /* Size to truncate to */ }; /* Columns */ @@ -194673,7 +232754,6 @@ struct DbpageTable { #define DBPAGE_COLUMN_SCHEMA 2 - /* ** Connect to or create a dbpagevfs virtual table. */ @@ -194686,8 +232766,14 @@ static int dbpageConnect( ){ DbpageTable *pTab = 0; int rc = SQLITE_OK; + (void)pAux; + (void)argc; + (void)argv; + (void)pzErr; - rc = sqlite3_declare_vtab(db, + sqlite3_vtab_config(db, SQLITE_VTAB_DIRECTONLY); + sqlite3_vtab_config(db, SQLITE_VTAB_USES_ALL_SCHEMAS); + rc = sqlite3_declare_vtab(db, "CREATE TABLE x(pgno INTEGER PRIMARY KEY, data BLOB, schema HIDDEN)"); if( rc==SQLITE_OK ){ pTab = (DbpageTable *)sqlite3_malloc64(sizeof(DbpageTable)); @@ -194723,6 +232809,7 @@ static int dbpageDisconnect(sqlite3_vtab *pVtab){ static int dbpageBestIndex(sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo){ int i; int iPlan = 0; + (void)tab; /* If there is a schema= constraint, it must be honored. Report a ** ridiculously large estimated cost if the schema= constraint is @@ -194784,7 +232871,7 @@ static int dbpageOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){ }else{ memset(pCsr, 0, sizeof(DbpageCursor)); pCsr->base.pVtab = pVTab; - pCsr->pgno = -1; + pCsr->pgno = 0; } *ppCursor = (sqlite3_vtab_cursor *)pCsr; @@ -194827,7 +232914,7 @@ static int dbpageEof(sqlite3_vtab_cursor *pCursor){ ** idxStr is not used */ static int dbpageFilter( - sqlite3_vtab_cursor *pCursor, + sqlite3_vtab_cursor *pCursor, int idxNum, const char *idxStr, int argc, sqlite3_value **argv ){ @@ -194837,8 +232924,11 @@ static int dbpageFilter( sqlite3 *db = pTab->db; Btree *pBt; + UNUSED_PARAMETER(idxStr); + UNUSED_PARAMETER(argc); + /* Default setting is no rows of result */ - pCsr->pgno = 1; + pCsr->pgno = 1; pCsr->mxPgno = 0; if( idxNum & 2 ){ @@ -194851,17 +232941,18 @@ static int dbpageFilter( pCsr->iDb = 0; } pBt = db->aDb[pCsr->iDb].pBt; - if( pBt==0 ) return SQLITE_OK; + if( NEVER(pBt==0) ) return SQLITE_OK; pCsr->pPager = sqlite3BtreePager(pBt); pCsr->szPage = sqlite3BtreeGetPageSize(pBt); pCsr->mxPgno = sqlite3BtreeLastPage(pBt); if( idxNum & 1 ){ + i64 iPg = sqlite3_value_int64(argv[idxNum>>1]); assert( argc>(idxNum>>1) ); - pCsr->pgno = sqlite3_value_int(argv[idxNum>>1]); - if( pCsr->pgno<1 || pCsr->pgno>pCsr->mxPgno ){ + if( iPg<1 || iPg>pCsr->mxPgno ){ pCsr->pgno = 1; pCsr->mxPgno = 0; }else{ + pCsr->pgno = (Pgno)iPg; pCsr->mxPgno = pCsr->pgno; } }else{ @@ -194873,25 +232964,31 @@ static int dbpageFilter( } static int dbpageColumn( - sqlite3_vtab_cursor *pCursor, - sqlite3_context *ctx, + sqlite3_vtab_cursor *pCursor, + sqlite3_context *ctx, int i ){ DbpageCursor *pCsr = (DbpageCursor *)pCursor; int rc = SQLITE_OK; switch( i ){ case 0: { /* pgno */ - sqlite3_result_int(ctx, pCsr->pgno); + sqlite3_result_int64(ctx, (sqlite3_int64)pCsr->pgno); break; } case 1: { /* data */ DbPage *pDbPage = 0; - rc = sqlite3PagerGet(pCsr->pPager, pCsr->pgno, (DbPage**)&pDbPage, 0); - if( rc==SQLITE_OK ){ - sqlite3_result_blob(ctx, sqlite3PagerGetData(pDbPage), pCsr->szPage, - SQLITE_TRANSIENT); + if( pCsr->pgno==(Pgno)((PENDING_BYTE/pCsr->szPage)+1) ){ + /* The pending byte page. Assume it is zeroed out. Attempting to + ** request this page from the page is an SQLITE_CORRUPT error. */ + sqlite3_result_zeroblob(ctx, pCsr->szPage); + }else{ + rc = sqlite3PagerGet(pCsr->pPager, pCsr->pgno, (DbPage**)&pDbPage, 0); + if( rc==SQLITE_OK ){ + sqlite3_result_blob(ctx, sqlite3PagerGetData(pDbPage), pCsr->szPage, + SQLITE_TRANSIENT); + } + sqlite3PagerUnref(pDbPage); } - sqlite3PagerUnref(pDbPage); break; } default: { /* schema */ @@ -194900,7 +232997,7 @@ static int dbpageColumn( break; } } - return SQLITE_OK; + return rc; } static int dbpageRowid(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){ @@ -194909,6 +233006,24 @@ static int dbpageRowid(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){ return SQLITE_OK; } +/* +** Open write transactions. Since we do not know in advance which database +** files will be written by the sqlite_dbpage virtual table, start a write +** transaction on them all. +** +** Return SQLITE_OK if successful, or an SQLite error code otherwise. +*/ +static int dbpageBeginTrans(DbpageTable *pTab){ + sqlite3 *db = pTab->db; + int rc = SQLITE_OK; + int i; + for(i=0; rc==SQLITE_OK && inDb; i++){ + Btree *pBt = db->aDb[i].pBt; + if( pBt ) rc = sqlite3BtreeBeginTrans(pBt, 1, 0); + } + return rc; +} + static int dbpageUpdate( sqlite3_vtab *pVtab, int argc, @@ -194920,12 +233035,13 @@ static int dbpageUpdate( DbPage *pDbPage = 0; int rc = SQLITE_OK; char *zErr = 0; - const char *zSchema; int iDb; Btree *pBt; Pager *pPager; int szPage; + int isInsert; + (void)pRowid; if( pTab->db->flags & SQLITE_Defensive ){ zErr = "read-only"; goto update_fail; @@ -194934,70 +233050,114 @@ static int dbpageUpdate( zErr = "cannot delete"; goto update_fail; } - pgno = sqlite3_value_int(argv[0]); - if( (Pgno)sqlite3_value_int(argv[1])!=pgno ){ - zErr = "cannot insert"; - goto update_fail; + if( sqlite3_value_type(argv[0])==SQLITE_NULL ){ + pgno = (Pgno)sqlite3_value_int64(argv[2]); + isInsert = 1; + }else{ + pgno = (Pgno)sqlite3_value_int64(argv[0]); + if( (Pgno)sqlite3_value_int(argv[1])!=pgno ){ + zErr = "cannot insert"; + goto update_fail; + } + isInsert = 0; } - zSchema = (const char*)sqlite3_value_text(argv[4]); - iDb = zSchema ? sqlite3FindDbName(pTab->db, zSchema) : -1; - if( iDb<0 ){ - zErr = "no such schema"; - goto update_fail; + if( sqlite3_value_type(argv[4])==SQLITE_NULL ){ + iDb = 0; + }else{ + const char *zSchema = (const char*)sqlite3_value_text(argv[4]); + iDb = sqlite3FindDbName(pTab->db, zSchema); + if( iDb<0 ){ + zErr = "no such schema"; + goto update_fail; + } } pBt = pTab->db->aDb[iDb].pBt; - if( pgno<1 || pBt==0 || pgno>(int)sqlite3BtreeLastPage(pBt) ){ + if( pgno<1 || NEVER(pBt==0) ){ zErr = "bad page number"; goto update_fail; } szPage = sqlite3BtreeGetPageSize(pBt); - if( sqlite3_value_type(argv[3])!=SQLITE_BLOB + if( sqlite3_value_type(argv[3])!=SQLITE_BLOB || sqlite3_value_bytes(argv[3])!=szPage ){ - zErr = "bad page value"; + if( sqlite3_value_type(argv[3])==SQLITE_NULL && isInsert && pgno>1 ){ + /* "INSERT INTO dbpage($PGNO,NULL)" causes page number $PGNO and + ** all subsequent pages to be deleted. */ + pTab->iDbTrunc = iDb; + pTab->pgnoTrunc = pgno-1; + pgno = 1; + }else{ + zErr = "bad page value"; + goto update_fail; + } + } + + if( dbpageBeginTrans(pTab)!=SQLITE_OK ){ + zErr = "failed to open transaction"; goto update_fail; } + pPager = sqlite3BtreePager(pBt); rc = sqlite3PagerGet(pPager, pgno, (DbPage**)&pDbPage, 0); if( rc==SQLITE_OK ){ - rc = sqlite3PagerWrite(pDbPage); - if( rc==SQLITE_OK ){ - memcpy(sqlite3PagerGetData(pDbPage), - sqlite3_value_blob(argv[3]), - szPage); + const void *pData = sqlite3_value_blob(argv[3]); + if( (rc = sqlite3PagerWrite(pDbPage))==SQLITE_OK && pData ){ + unsigned char *aPage = sqlite3PagerGetData(pDbPage); + memcpy(aPage, pData, szPage); + pTab->pgnoTrunc = 0; } } + if( rc!=SQLITE_OK ){ + pTab->pgnoTrunc = 0; + } sqlite3PagerUnref(pDbPage); return rc; update_fail: + pTab->pgnoTrunc = 0; sqlite3_free(pVtab->zErrMsg); pVtab->zErrMsg = sqlite3_mprintf("%s", zErr); return SQLITE_ERROR; } -/* Since we do not know in advance which database files will be -** written by the sqlite_dbpage virtual table, start a write transaction -** on them all. -*/ static int dbpageBegin(sqlite3_vtab *pVtab){ DbpageTable *pTab = (DbpageTable *)pVtab; - sqlite3 *db = pTab->db; - int i; - for(i=0; inDb; i++){ - Btree *pBt = db->aDb[i].pBt; - if( pBt ) sqlite3BtreeBeginTrans(pBt, 1, 0); + pTab->pgnoTrunc = 0; + return SQLITE_OK; +} + +/* Invoke sqlite3PagerTruncate() as necessary, just prior to COMMIT +*/ +static int dbpageSync(sqlite3_vtab *pVtab){ + DbpageTable *pTab = (DbpageTable *)pVtab; + if( pTab->pgnoTrunc>0 ){ + Btree *pBt = pTab->db->aDb[pTab->iDbTrunc].pBt; + Pager *pPager = sqlite3BtreePager(pBt); + sqlite3BtreeEnter(pBt); + if( pTab->pgnoTruncpgnoTrunc); + } + sqlite3BtreeLeave(pBt); } + pTab->pgnoTrunc = 0; return SQLITE_OK; } +/* Cancel any pending truncate. +*/ +static int dbpageRollbackTo(sqlite3_vtab *pVtab, int notUsed1){ + DbpageTable *pTab = (DbpageTable *)pVtab; + pTab->pgnoTrunc = 0; + (void)notUsed1; + return SQLITE_OK; +} /* ** Invoke this routine to register the "dbpage" virtual table module */ SQLITE_PRIVATE int sqlite3DbpageRegister(sqlite3 *db){ static sqlite3_module dbpage_module = { - 0, /* iVersion */ + 2, /* iVersion */ dbpageConnect, /* xCreate */ dbpageConnect, /* xConnect */ dbpageBestIndex, /* xBestIndex */ @@ -195012,15 +233172,16 @@ SQLITE_PRIVATE int sqlite3DbpageRegister(sqlite3 *db){ dbpageRowid, /* xRowid - read data */ dbpageUpdate, /* xUpdate */ dbpageBegin, /* xBegin */ - 0, /* xSync */ + dbpageSync, /* xSync */ 0, /* xCommit */ 0, /* xRollback */ 0, /* xFindMethod */ 0, /* xRename */ 0, /* xSavepoint */ 0, /* xRelease */ - 0, /* xRollbackTo */ - 0 /* xShadowName */ + dbpageRollbackTo, /* xRollbackTo */ + 0, /* xShadowName */ + 0 /* xIntegrity */ }; return sqlite3_create_module(db, "sqlite_dbpage", &dbpage_module, 0); } @@ -195029,6 +233190,567 @@ SQLITE_PRIVATE int sqlite3DbpageRegister(sqlite3 *db){ return SQLITE_OK; } #endif /* SQLITE_ENABLE_DBSTAT_VTAB */ /************** End of dbpage.c **********************************************/ +/************** Begin file carray.c ******************************************/ +/* +** 2016-06-29 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file implements a table-valued-function that +** returns the values in a C-language array. +** Examples: +** +** SELECT * FROM carray($ptr,5) +** +** The query above returns 5 integers contained in a C-language array +** at the address $ptr. $ptr is a pointer to the array of integers. +** The pointer value must be assigned to $ptr using the +** sqlite3_bind_pointer() interface with a pointer type of "carray". +** For example: +** +** static int aX[] = { 53, 9, 17, 2231, 4, 99 }; +** int i = sqlite3_bind_parameter_index(pStmt, "$ptr"); +** sqlite3_bind_pointer(pStmt, i, aX, "carray", 0); +** +** There is an optional third parameter to determine the datatype of +** the C-language array. Allowed values of the third parameter are +** 'int32', 'int64', 'double', 'char*', 'struct iovec'. Example: +** +** SELECT * FROM carray($ptr,10,'char*'); +** +** The default value of the third parameter is 'int32'. +** +** HOW IT WORKS +** +** The carray "function" is really a virtual table with the +** following schema: +** +** CREATE TABLE carray( +** value, +** pointer HIDDEN, +** count HIDDEN, +** ctype TEXT HIDDEN +** ); +** +** If the hidden columns "pointer" and "count" are unconstrained, then +** the virtual table has no rows. Otherwise, the virtual table interprets +** the integer value of "pointer" as a pointer to the array and "count" +** as the number of elements in the array. The virtual table steps through +** the array, element by element. +*/ +#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_CARRAY) +/* #include "sqliteInt.h" */ +#if defined(_WIN32) || defined(__RTP__) || defined(_WRS_KERNEL) + struct iovec { + void *iov_base; + size_t iov_len; + }; +#else +# include +#endif + +/* +** Names of allowed datatypes +*/ +static const char *azCarrayType[] = { + "int32", "int64", "double", "char*", "struct iovec" +}; + +/* +** Structure used to hold the sqlite3_carray_bind() information +*/ +typedef struct carray_bind carray_bind; +struct carray_bind { + void *aData; /* The data */ + int nData; /* Number of elements */ + int mFlags; /* Control flags */ + void (*xDel)(void*); /* Destructor for aData */ + void *pDel; /* Alternative argument to xDel() */ +}; + + +/* carray_cursor is a subclass of sqlite3_vtab_cursor which will +** serve as the underlying representation of a cursor that scans +** over rows of the result +*/ +typedef struct carray_cursor carray_cursor; +struct carray_cursor { + sqlite3_vtab_cursor base; /* Base class - must be first */ + sqlite3_int64 iRowid; /* The rowid */ + void *pPtr; /* Pointer to the array of values */ + sqlite3_int64 iCnt; /* Number of integers in the array */ + unsigned char eType; /* One of the CARRAY_type values */ +}; + +/* +** The carrayConnect() method is invoked to create a new +** carray_vtab that describes the carray virtual table. +** +** Think of this routine as the constructor for carray_vtab objects. +** +** All this routine needs to do is: +** +** (1) Allocate the carray_vtab object and initialize all fields. +** +** (2) Tell SQLite (via the sqlite3_declare_vtab() interface) what the +** result set of queries against carray will look like. +*/ +static int carrayConnect( + sqlite3 *db, + void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVtab, + char **pzErr +){ + sqlite3_vtab *pNew; + int rc; + +/* Column numbers */ +#define CARRAY_COLUMN_VALUE 0 +#define CARRAY_COLUMN_POINTER 1 +#define CARRAY_COLUMN_COUNT 2 +#define CARRAY_COLUMN_CTYPE 3 + + rc = sqlite3_declare_vtab(db, + "CREATE TABLE x(value,pointer hidden,count hidden,ctype hidden)"); + if( rc==SQLITE_OK ){ + pNew = *ppVtab = sqlite3_malloc( sizeof(*pNew) ); + if( pNew==0 ) return SQLITE_NOMEM; + memset(pNew, 0, sizeof(*pNew)); + } + return rc; +} + +/* +** This method is the destructor for carray_cursor objects. +*/ +static int carrayDisconnect(sqlite3_vtab *pVtab){ + sqlite3_free(pVtab); + return SQLITE_OK; +} + +/* +** Constructor for a new carray_cursor object. +*/ +static int carrayOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ + carray_cursor *pCur; + pCur = sqlite3_malloc( sizeof(*pCur) ); + if( pCur==0 ) return SQLITE_NOMEM; + memset(pCur, 0, sizeof(*pCur)); + *ppCursor = &pCur->base; + return SQLITE_OK; +} + +/* +** Destructor for a carray_cursor. +*/ +static int carrayClose(sqlite3_vtab_cursor *cur){ + sqlite3_free(cur); + return SQLITE_OK; +} + + +/* +** Advance a carray_cursor to its next row of output. +*/ +static int carrayNext(sqlite3_vtab_cursor *cur){ + carray_cursor *pCur = (carray_cursor*)cur; + pCur->iRowid++; + return SQLITE_OK; +} + +/* +** Return values of columns for the row at which the carray_cursor +** is currently pointing. +*/ +static int carrayColumn( + sqlite3_vtab_cursor *cur, /* The cursor */ + sqlite3_context *ctx, /* First argument to sqlite3_result_...() */ + int i /* Which column to return */ +){ + carray_cursor *pCur = (carray_cursor*)cur; + sqlite3_int64 x = 0; + switch( i ){ + case CARRAY_COLUMN_POINTER: return SQLITE_OK; + case CARRAY_COLUMN_COUNT: x = pCur->iCnt; break; + case CARRAY_COLUMN_CTYPE: { + sqlite3_result_text(ctx, azCarrayType[pCur->eType], -1, SQLITE_STATIC); + return SQLITE_OK; + } + default: { + switch( pCur->eType ){ + case CARRAY_INT32: { + int *p = (int*)pCur->pPtr; + sqlite3_result_int(ctx, p[pCur->iRowid-1]); + return SQLITE_OK; + } + case CARRAY_INT64: { + sqlite3_int64 *p = (sqlite3_int64*)pCur->pPtr; + sqlite3_result_int64(ctx, p[pCur->iRowid-1]); + return SQLITE_OK; + } + case CARRAY_DOUBLE: { + double *p = (double*)pCur->pPtr; + sqlite3_result_double(ctx, p[pCur->iRowid-1]); + return SQLITE_OK; + } + case CARRAY_TEXT: { + const char **p = (const char**)pCur->pPtr; + sqlite3_result_text(ctx, p[pCur->iRowid-1], -1, SQLITE_TRANSIENT); + return SQLITE_OK; + } + default: { + const struct iovec *p = (struct iovec*)pCur->pPtr; + assert( pCur->eType==CARRAY_BLOB ); + sqlite3_result_blob(ctx, p[pCur->iRowid-1].iov_base, + (int)p[pCur->iRowid-1].iov_len, SQLITE_TRANSIENT); + return SQLITE_OK; + } + } + } + } + sqlite3_result_int64(ctx, x); + return SQLITE_OK; +} + +/* +** Return the rowid for the current row. In this implementation, the +** rowid is the same as the output value. +*/ +static int carrayRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ + carray_cursor *pCur = (carray_cursor*)cur; + *pRowid = pCur->iRowid; + return SQLITE_OK; +} + +/* +** Return TRUE if the cursor has been moved off of the last +** row of output. +*/ +static int carrayEof(sqlite3_vtab_cursor *cur){ + carray_cursor *pCur = (carray_cursor*)cur; + return pCur->iRowid>pCur->iCnt; +} + +/* +** This method is called to "rewind" the carray_cursor object back +** to the first row of output. +*/ +static int carrayFilter( + sqlite3_vtab_cursor *pVtabCursor, + int idxNum, const char *idxStr, + int argc, sqlite3_value **argv +){ + carray_cursor *pCur = (carray_cursor *)pVtabCursor; + pCur->pPtr = 0; + pCur->iCnt = 0; + switch( idxNum ){ + case 1: { + carray_bind *pBind = sqlite3_value_pointer(argv[0], "carray-bind"); + if( pBind==0 ) break; + pCur->pPtr = pBind->aData; + pCur->iCnt = pBind->nData; + pCur->eType = pBind->mFlags & 0x07; + break; + } + case 2: + case 3: { + pCur->pPtr = sqlite3_value_pointer(argv[0], "carray"); + pCur->iCnt = pCur->pPtr ? sqlite3_value_int64(argv[1]) : 0; + if( idxNum<3 ){ + pCur->eType = CARRAY_INT32; + }else{ + unsigned char i; + const char *zType = (const char*)sqlite3_value_text(argv[2]); + for(i=0; i=sizeof(azCarrayType)/sizeof(azCarrayType[0]) ){ + pVtabCursor->pVtab->zErrMsg = sqlite3_mprintf( + "unknown datatype: %Q", zType); + return SQLITE_ERROR; + }else{ + pCur->eType = i; + } + } + break; + } + } + pCur->iRowid = 1; + return SQLITE_OK; +} + +/* +** SQLite will invoke this method one or more times while planning a query +** that uses the carray virtual table. This routine needs to create +** a query plan for each invocation and compute an estimated cost for that +** plan. +** +** In this implementation idxNum is used to represent the +** query plan. idxStr is unused. +** +** idxNum is: +** +** 1 If only the pointer= constraint exists. In this case, the +** parameter must be bound using sqlite3_carray_bind(). +** +** 2 if the pointer= and count= constraints exist. +** +** 3 if the ctype= constraint also exists. +** +** idxNum is 0 otherwise and carray becomes an empty table. +*/ +static int carrayBestIndex( + sqlite3_vtab *tab, + sqlite3_index_info *pIdxInfo +){ + int i; /* Loop over constraints */ + int ptrIdx = -1; /* Index of the pointer= constraint, or -1 if none */ + int cntIdx = -1; /* Index of the count= constraint, or -1 if none */ + int ctypeIdx = -1; /* Index of the ctype= constraint, or -1 if none */ + unsigned seen = 0; /* Bitmask of == constrainted columns */ + + const struct sqlite3_index_constraint *pConstraint; + pConstraint = pIdxInfo->aConstraint; + for(i=0; inConstraint; i++, pConstraint++){ + if( pConstraint->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue; + if( pConstraint->iColumn>=0 ) seen |= 1 << pConstraint->iColumn; + if( pConstraint->usable==0 ) continue; + switch( pConstraint->iColumn ){ + case CARRAY_COLUMN_POINTER: + ptrIdx = i; + break; + case CARRAY_COLUMN_COUNT: + cntIdx = i; + break; + case CARRAY_COLUMN_CTYPE: + ctypeIdx = i; + break; + } + } + if( ptrIdx>=0 ){ + pIdxInfo->aConstraintUsage[ptrIdx].argvIndex = 1; + pIdxInfo->aConstraintUsage[ptrIdx].omit = 1; + pIdxInfo->estimatedCost = (double)1; + pIdxInfo->estimatedRows = 100; + pIdxInfo->idxNum = 1; + if( cntIdx>=0 ){ + pIdxInfo->aConstraintUsage[cntIdx].argvIndex = 2; + pIdxInfo->aConstraintUsage[cntIdx].omit = 1; + pIdxInfo->idxNum = 2; + if( ctypeIdx>=0 ){ + pIdxInfo->aConstraintUsage[ctypeIdx].argvIndex = 3; + pIdxInfo->aConstraintUsage[ctypeIdx].omit = 1; + pIdxInfo->idxNum = 3; + }else if( seen & (1<estimatedCost = (double)2147483647; + pIdxInfo->estimatedRows = 2147483647; + pIdxInfo->idxNum = 0; + } + return SQLITE_OK; +} + +/* +** This following structure defines all the methods for the +** carray virtual table. +*/ +static sqlite3_module carrayModule = { + 0, /* iVersion */ + 0, /* xCreate */ + carrayConnect, /* xConnect */ + carrayBestIndex, /* xBestIndex */ + carrayDisconnect, /* xDisconnect */ + 0, /* xDestroy */ + carrayOpen, /* xOpen - open a cursor */ + carrayClose, /* xClose - close a cursor */ + carrayFilter, /* xFilter - configure scan constraints */ + carrayNext, /* xNext - advance a cursor */ + carrayEof, /* xEof - check for end of scan */ + carrayColumn, /* xColumn - read data */ + carrayRowid, /* xRowid - read data */ + 0, /* xUpdate */ + 0, /* xBegin */ + 0, /* xSync */ + 0, /* xCommit */ + 0, /* xRollback */ + 0, /* xFindMethod */ + 0, /* xRename */ + 0, /* xSavepoint */ + 0, /* xRelease */ + 0, /* xRollbackTo */ + 0, /* xShadow */ + 0 /* xIntegrity */ +}; + +/* +** Destructor for the carray_bind object +*/ +static void carrayBindDel(void *pPtr){ + carray_bind *p = (carray_bind*)pPtr; + if( p->xDel!=SQLITE_STATIC ){ + p->xDel(p->pDel); + } + sqlite3_free(p); +} + +/* +** Invoke this interface in order to bind to the single-argument +** version of CARRAY(). +** +** pStmt The prepared statement to which to bind +** idx The index of the parameter of pStmt to which to bind +** aData The data to be bound +** nData The number of elements in aData +** mFlags One of SQLITE_CARRAY_xxxx indicating datatype of aData +** xDestroy Destructor for pDestroy or aData if pDestroy==NULL. +** pDestroy Invoke xDestroy on this pointer if not NULL +** +** The destructor is called pDestroy if pDestroy!=NULL, or against +** aData if pDestroy==NULL. +*/ +SQLITE_API int sqlite3_carray_bind_v2( + sqlite3_stmt *pStmt, + int idx, + void *aData, + int nData, + int mFlags, + void (*xDestroy)(void*), + void *pDestroy +){ + carray_bind *pNew = 0; + int i; + int rc = SQLITE_OK; + + /* Ensure that the mFlags value is acceptable. */ + assert( CARRAY_INT32==0 && CARRAY_INT64==1 && CARRAY_DOUBLE==2 ); + assert( CARRAY_TEXT==3 && CARRAY_BLOB==4 ); + if( mFlagsCARRAY_BLOB ){ + rc = SQLITE_ERROR; + goto carray_bind_error; + } + + pNew = sqlite3_malloc64(sizeof(*pNew)); + if( pNew==0 ){ + rc = SQLITE_NOMEM; + goto carray_bind_error; + } + + pNew->nData = nData; + pNew->mFlags = mFlags; + if( xDestroy==SQLITE_TRANSIENT ){ + sqlite3_int64 sz = nData; + switch( mFlags ){ + case CARRAY_INT32: sz *= 4; break; + case CARRAY_INT64: sz *= 8; break; + case CARRAY_DOUBLE: sz *= 8; break; + case CARRAY_TEXT: sz *= sizeof(char*); break; + default: sz *= sizeof(struct iovec); break; + } + if( mFlags==CARRAY_TEXT ){ + for(i=0; iaData = sqlite3_malloc64( sz ); + if( pNew->aData==0 ){ + rc = SQLITE_NOMEM; + goto carray_bind_error; + } + + if( mFlags==CARRAY_TEXT ){ + char **az = (char**)pNew->aData; + char *z = (char*)&az[nData]; + for(i=0; iaData; + unsigned char *z = (unsigned char*)&p[nData]; + for(i=0; iaData, aData, sz); + } + pNew->xDel = sqlite3_free; + pNew->pDel = pNew->aData; + }else{ + pNew->aData = aData; + pNew->xDel = xDestroy; + pNew->pDel = pDestroy; + } + return sqlite3_bind_pointer(pStmt, idx, pNew, "carray-bind", carrayBindDel); + + carray_bind_error: + if( xDestroy!=SQLITE_STATIC && xDestroy!=SQLITE_TRANSIENT ){ + xDestroy(pDestroy); + } + sqlite3_free(pNew); + return rc; +} + +/* +** Invoke this interface in order to bind to the single-argument +** version of CARRAY(). Same as sqlite3_carray_bind_v2() with the +** pDestroy parameter set to NULL. +*/ +SQLITE_API int sqlite3_carray_bind( + sqlite3_stmt *pStmt, + int idx, + void *aData, + int nData, + int mFlags, + void (*xDestroy)(void*) +){ + return sqlite3_carray_bind_v2(pStmt,idx,aData,nData,mFlags,xDestroy,aData); +} + +/* +** Invoke this routine to register the carray() function. +*/ +SQLITE_PRIVATE Module *sqlite3CarrayRegister(sqlite3 *db){ + return sqlite3VtabCreateModule(db, "carray", &carrayModule, 0, 0); +} + +#endif /* !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_CARRAY) */ + +/************** End of carray.c **********************************************/ /************** Begin file sqlite3session.c **********************************/ #if defined(SQLITE_ENABLE_SESSION) && defined(SQLITE_ENABLE_PREUPDATE_HOOK) @@ -195057,6 +233779,8 @@ typedef struct SessionInput SessionInput; # endif #endif +#define SESSIONS_ROWID "_rowid_" + static int sessions_strm_chunk_size = SESSIONS_STRM_CHUNK_SIZE; typedef struct SessionHook SessionHook; @@ -195074,12 +233798,16 @@ struct SessionHook { struct sqlite3_session { sqlite3 *db; /* Database handle session is attached to */ char *zDb; /* Name of database session is attached to */ + int bEnableSize; /* True if changeset_size() enabled */ int bEnable; /* True if currently recording */ int bIndirect; /* True if all changes are indirect */ int bAutoAttach; /* True to auto-attach tables */ + int bImplicitPK; /* True to handle tables with implicit PK */ int rc; /* Non-zero if an error has occurred */ void *pFilterCtx; /* First argument to pass to xTableFilter */ int (*xTableFilter)(void *pCtx, const char *zTab); + i64 nMalloc; /* Number of bytes of data allocated */ + i64 nMaxChangesetSize; sqlite3_value *pZeroBlob; /* Value containing X'' */ sqlite3_session *pNext; /* Next session object on same db. */ SessionTable *pTable; /* List of attached tables */ @@ -195096,10 +233824,14 @@ struct SessionBuffer { }; /* -** An object of this type is used internally as an abstraction for +** An object of this type is used internally as an abstraction for ** input data. Input data may be supplied either as a single large buffer ** (e.g. sqlite3changeset_start()) or using a stream function (e.g. ** sqlite3changeset_start_strm()). +** +** bNoDiscard: +** If true, then the only time data is discarded is as a result of explicit +** sessionDiscardData() calls. Not within every sessionInputBuffer() call. */ struct SessionInput { int bNoDiscard; /* If true, do not discard in InputBuffer() */ @@ -195122,6 +233854,7 @@ struct sqlite3_changeset_iter { SessionBuffer tblhdr; /* Buffer to hold apValue/zTab/abPK/ */ int bPatchset; /* True if this is a patchset */ int bInvert; /* True to invert changeset */ + int bSkipEmpty; /* Skip noop UPDATE changes */ int rc; /* Iterator error code */ sqlite3_stmt *pConflict; /* Points to conflicting row, if any */ char *zTab; /* Current table */ @@ -195144,24 +233877,41 @@ struct sqlite3_changeset_iter { ** The data associated with each hash-table entry is a structure containing ** a subset of the initial values that the modified row contained at the ** start of the session. Or no initial values if the row was inserted. +** +** pDfltStmt: +** This is only used by the sqlite3changegroup_xxx() APIs, not by +** regular sqlite3_session objects. It is a SELECT statement that +** selects the default value for each table column. For example, +** if the table is +** +** CREATE TABLE xx(a DEFAULT 1, b, c DEFAULT 'abc') +** +** then this variable is the compiled version of: +** +** SELECT 1, NULL, 'abc' */ struct SessionTable { SessionTable *pNext; char *zName; /* Local name of table */ - int nCol; /* Number of columns in table zName */ + int nCol; /* Number of non-hidden columns */ + int nTotalCol; /* Number of columns including hidden */ int bStat1; /* True if this is sqlite_stat1 */ + int bRowid; /* True if this table uses rowid for PK */ const char **azCol; /* Column names */ + const char **azDflt; /* Default value expressions */ + int *aiIdx; /* Index to pass to xNew/xOld */ u8 *abPK; /* Array of primary key flags */ int nEntry; /* Total number of entries in hash table */ int nChange; /* Size of apChange[] array */ SessionChange **apChange; /* Hash table buckets */ + sqlite3_stmt *pDfltStmt; }; -/* +/* ** RECORD FORMAT: ** -** The following record format is similar to (but not compatible with) that -** used in SQLite database files. This format is used as part of the +** The following record format is similar to (but not compatible with) that +** used in SQLite database files. This format is used as part of the ** change-set binary format, and so must be architecture independent. ** ** Unlike the SQLite database record format, each field is self-contained - @@ -195195,7 +233945,7 @@ struct SessionTable { ** Real values: ** An 8-byte big-endian IEEE 754-2008 real value. ** -** Varint values are encoded in the same way as varints in the SQLite +** Varint values are encoded in the same way as varints in the SQLite ** record format. ** ** CHANGESET FORMAT: @@ -195227,7 +233977,7 @@ struct SessionTable { ** ** The new.* record that is part of each INSERT change contains the values ** that make up the new row. Similarly, the old.* record that is part of each -** DELETE change contains the values that made up the row that was deleted +** DELETE change contains the values that made up the row that was deleted ** from the database. In the changeset format, the records that are part ** of INSERT or DELETE changes never contain any undefined (type byte 0x00) ** fields. @@ -195236,8 +233986,8 @@ struct SessionTable { ** associated with table columns that are not PRIMARY KEY columns and are ** not modified by the UPDATE change are set to "undefined". Other fields ** are set to the values that made up the row before the UPDATE that the -** change records took place. Within the new.* record, fields associated -** with table columns modified by the UPDATE change contain the new +** change records took place. Within the new.* record, fields associated +** with table columns modified by the UPDATE change contain the new ** values. Fields associated with table columns that are not modified ** are set to "undefined". ** @@ -195263,7 +234013,7 @@ struct SessionTable { ** ** As in the changeset format, each field of the single record that is part ** of a patchset change is associated with the correspondingly positioned -** table column, counting from left to right within the CREATE TABLE +** table column, counting from left to right within the CREATE TABLE ** statement. ** ** For a DELETE change, all fields within the record except those associated @@ -195281,7 +234031,7 @@ struct SessionTable { ** ** REBASE BLOB FORMAT: ** -** A rebase blob may be output by sqlite3changeset_apply_v2() and its +** A rebase blob may be output by sqlite3changeset_apply_v2() and its ** streaming equivalent for use with the sqlite3_rebaser APIs to rebase ** existing changesets. A rebase blob contains one entry for each conflict ** resolved using either the OMIT or REPLACE strategies within the apply_v2() @@ -195305,7 +234055,7 @@ struct SessionTable { ** ** In a rebase blob, the first field is set to SQLITE_INSERT if the change ** that caused the conflict was an INSERT or UPDATE, or to SQLITE_DELETE if -** it was a DELETE. The second field is set to 0x01 if the conflict +** it was a DELETE. The second field is set to 0x01 if the conflict ** resolution strategy was REPLACE, or 0x00 if it was OMIT. ** ** If the change that caused the conflict was a DELETE, then the single @@ -195321,15 +234071,17 @@ struct SessionTable { ** this structure stored in a SessionTable.aChange[] hash table. */ struct SessionChange { - int op; /* One of UPDATE, DELETE, INSERT */ - int bIndirect; /* True if this change is "indirect" */ + u8 op; /* One of UPDATE, DELETE, INSERT */ + u8 bIndirect; /* True if this change is "indirect" */ + u16 nRecordField; /* Number of fields in aRecord[] */ + int nMaxSize; /* Max size of eventual changeset record */ int nRecord; /* Number of bytes in buffer aRecord[] */ u8 *aRecord; /* Buffer containing old.* record */ SessionChange *pNext; /* For hash-table collisions */ }; /* -** Write a varint with value iVal into the buffer at aBuf. Return the +** Write a varint with value iVal into the buffer at aBuf. Return the ** number of bytes written. */ static int sessionVarintPut(u8 *aBuf, int iVal){ @@ -195344,13 +234096,28 @@ static int sessionVarintLen(int iVal){ } /* -** Read a varint value from aBuf[] into *piVal. Return the number of +** Read a varint value from aBuf[] into *piVal. Return the number of ** bytes read. */ -static int sessionVarintGet(u8 *aBuf, int *piVal){ +static int sessionVarintGet(const u8 *aBuf, int *piVal){ return getVarint32(aBuf, *piVal); } +/* +** Read a varint value from buffer aBuf[], size nBuf bytes, into *piVal. +** Return the number of bytes read. +*/ +static int sessionVarintGetSafe(const u8 *aBuf, int nBuf, int *piVal){ + u8 aCopy[9]; + const u8 *aRead = aBuf; + memset(aCopy, 0, sizeof(aCopy)); + if( nBuf> 0) & 0xFF; } +/* +** Write a double value to the buffer aBuf[]. +*/ +static void sessionPutDouble(u8 *aBuf, double r){ + /* TODO: SQLite does something special to deal with mixed-endian + ** floating point values (e.g. ARM7). This code probably should + ** too. */ + u64 i; + assert( sizeof(double)==8 && sizeof(u64)==8 ); + memcpy(&i, &r, 8); + sessionPutI64(aBuf, i); +} + /* ** This function is used to serialize the contents of value pValue (see ** comment titled "RECORD FORMAT" above). ** -** If it is non-NULL, the serialized form of the value is written to +** If it is non-NULL, the serialized form of the value is written to ** buffer aBuf. *pnWrite is set to the number of bytes written before ** returning. Or, if aBuf is NULL, the only thing this function does is ** set *pnWrite. ** ** If no error occurs, SQLITE_OK is returned. Or, if an OOM error occurs -** within a call to sqlite3_value_text() (may fail if the db is utf-16)) +** within a call to sqlite3_value_text() (may fail if the db is utf-16)) ** SQLITE_NOMEM is returned. */ static int sessionSerializeValue( @@ -195401,40 +234181,37 @@ static int sessionSerializeValue( if( pValue ){ int eType; /* Value type (SQLITE_NULL, TEXT etc.) */ - + eType = sqlite3_value_type(pValue); if( aBuf ) aBuf[0] = eType; - + switch( eType ){ - case SQLITE_NULL: + case SQLITE_NULL: nByte = 1; break; - - case SQLITE_INTEGER: + + case SQLITE_INTEGER: case SQLITE_FLOAT: if( aBuf ){ /* TODO: SQLite does something special to deal with mixed-endian ** floating point values (e.g. ARM7). This code probably should ** too. */ - u64 i; if( eType==SQLITE_INTEGER ){ - i = (u64)sqlite3_value_int64(pValue); + u64 i = (u64)sqlite3_value_int64(pValue); + sessionPutI64(&aBuf[1], i); }else{ - double r; - assert( sizeof(double)==8 && sizeof(u64)==8 ); - r = sqlite3_value_double(pValue); - memcpy(&i, &r, 8); + double r = sqlite3_value_double(pValue); + sessionPutDouble(&aBuf[1], r); } - sessionPutI64(&aBuf[1], i); } - nByte = 9; + nByte = 9; break; - + default: { u8 *z; int n; int nVarint; - + assert( eType==SQLITE_TEXT || eType==SQLITE_BLOB ); if( eType==SQLITE_TEXT ){ z = (u8 *)sqlite3_value_text(pValue); @@ -195444,12 +234221,12 @@ static int sessionSerializeValue( n = sqlite3_value_bytes(pValue); if( z==0 && (eType!=SQLITE_BLOB || n>0) ) return SQLITE_NOMEM; nVarint = sessionVarintLen(n); - + if( aBuf ){ sessionVarintPut(&aBuf[1], n); - if( n ) memcpy(&aBuf[nVarint + 1], z, n); + if( n>0 ) memcpy(&aBuf[nVarint + 1], z, n); } - + nByte = 1 + nVarint + n; break; } @@ -195463,6 +234240,26 @@ static int sessionSerializeValue( return SQLITE_OK; } +/* +** Allocate and return a pointer to a buffer nByte bytes in size. If +** pSession is not NULL, increase the sqlite3_session.nMalloc variable +** by the number of bytes allocated. +*/ +static void *sessionMalloc64(sqlite3_session *pSession, i64 nByte){ + void *pRet = sqlite3_malloc64(nByte); + if( pSession ) pSession->nMalloc += sqlite3_msize(pRet); + return pRet; +} + +/* +** Free buffer pFree, which must have been allocated by an earlier +** call to sessionMalloc64(). If pSession is not NULL, decrease the +** sqlite3_session.nMalloc counter by the number of bytes freed. +*/ +static void sessionFree(sqlite3_session *pSession, void *pFree){ + if( pSession ) pSession->nMalloc -= sqlite3_msize(pFree); + sqlite3_free(pFree); +} /* ** This macro is used to calculate hash key values for data structures. In @@ -195491,7 +234288,7 @@ static unsigned int sessionHashAppendI64(unsigned int h, i64 i){ } /* -** Append the hash of the blob passed via the second and third arguments to +** Append the hash of the blob passed via the second and third arguments to ** the hash-key value passed as the first. Return the new hash-key value. */ static unsigned int sessionHashAppendBlob(unsigned int h, int n, const u8 *z){ @@ -195510,7 +234307,7 @@ static unsigned int sessionHashAppendType(unsigned int h, int eType){ /* ** This function may only be called from within a pre-update callback. -** It calculates a hash based on the primary key values of the old.* or +** It calculates a hash based on the primary key values of the old.* or ** new.* row currently available and, assuming no error occurs, writes it to ** *piHash before returning. If the primary key contains one or more NULL ** values, *pbNullPK is set to true before returning. @@ -195521,6 +234318,7 @@ static unsigned int sessionHashAppendType(unsigned int h, int eType){ */ static int sessionPreupdateHash( sqlite3_session *pSession, /* Session object that owns pTab */ + i64 iRowid, SessionTable *pTab, /* Session table handle */ int bNew, /* True to hash the new.* PK */ int *piHash, /* OUT: Hash value */ @@ -195529,48 +234327,53 @@ static int sessionPreupdateHash( unsigned int h = 0; /* Hash value to return */ int i; /* Used to iterate through columns */ - assert( *pbNullPK==0 ); - assert( pTab->nCol==pSession->hook.xCount(pSession->hook.pCtx) ); - for(i=0; inCol; i++){ - if( pTab->abPK[i] ){ - int rc; - int eType; - sqlite3_value *pVal; - - if( bNew ){ - rc = pSession->hook.xNew(pSession->hook.pCtx, i, &pVal); - }else{ - rc = pSession->hook.xOld(pSession->hook.pCtx, i, &pVal); - } - if( rc!=SQLITE_OK ) return rc; - - eType = sqlite3_value_type(pVal); - h = sessionHashAppendType(h, eType); - if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){ - i64 iVal; - if( eType==SQLITE_INTEGER ){ - iVal = sqlite3_value_int64(pVal); + assert( pTab->nTotalCol==pSession->hook.xCount(pSession->hook.pCtx) ); + if( pTab->bRowid ){ + h = sessionHashAppendI64(h, iRowid); + }else{ + assert( *pbNullPK==0 ); + for(i=0; inCol; i++){ + if( pTab->abPK[i] ){ + int rc; + int eType; + sqlite3_value *pVal; + int iIdx = pTab->aiIdx[i]; + + if( bNew ){ + rc = pSession->hook.xNew(pSession->hook.pCtx, iIdx, &pVal); }else{ - double rVal = sqlite3_value_double(pVal); - assert( sizeof(iVal)==8 && sizeof(rVal)==8 ); - memcpy(&iVal, &rVal, 8); + rc = pSession->hook.xOld(pSession->hook.pCtx, iIdx, &pVal); } - h = sessionHashAppendI64(h, iVal); - }else if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){ - const u8 *z; - int n; - if( eType==SQLITE_TEXT ){ - z = (const u8 *)sqlite3_value_text(pVal); + if( rc!=SQLITE_OK ) return rc; + + eType = sqlite3_value_type(pVal); + h = sessionHashAppendType(h, eType); + if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){ + i64 iVal; + if( eType==SQLITE_INTEGER ){ + iVal = sqlite3_value_int64(pVal); + }else{ + double rVal = sqlite3_value_double(pVal); + assert( sizeof(iVal)==8 && sizeof(rVal)==8 ); + memcpy(&iVal, &rVal, 8); + } + h = sessionHashAppendI64(h, iVal); + }else if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){ + const u8 *z; + int n; + if( eType==SQLITE_TEXT ){ + z = (const u8 *)sqlite3_value_text(pVal); + }else{ + z = (const u8 *)sqlite3_value_blob(pVal); + } + n = sqlite3_value_bytes(pVal); + if( !z && (eType!=SQLITE_BLOB || n>0) ) return SQLITE_NOMEM; + h = sessionHashAppendBlob(h, n, z); }else{ - z = (const u8 *)sqlite3_value_blob(pVal); + assert( eType==SQLITE_NULL ); + assert( pTab->bStat1==0 || i!=1 ); + *pbNullPK = 1; } - n = sqlite3_value_bytes(pVal); - if( !z && (eType!=SQLITE_BLOB || n>0) ) return SQLITE_NOMEM; - h = sessionHashAppendBlob(h, n, z); - }else{ - assert( eType==SQLITE_NULL ); - assert( pTab->bStat1==0 || i!=1 ); - *pbNullPK = 1; } } } @@ -195584,13 +234387,16 @@ static int sessionPreupdateHash( ** Return the number of bytes of space occupied by the value (including ** the type byte). */ -static int sessionSerialLen(u8 *a){ - int e = *a; +static int sessionSerialLen(const u8 *a){ + int e; int n; - if( e==0 || e==0xFF ) return 1; - if( e==SQLITE_NULL ) return 1; + assert( a!=0 ); + e = *a; if( e==SQLITE_INTEGER || e==SQLITE_FLOAT ) return 9; - return sessionVarintGet(&a[1], &n) + 1 + n; + if( e==SQLITE_TEXT || e==SQLITE_BLOB ){ + return sessionVarintGet(&a[1], &n) + 1 + n; + } + return 1; } /* @@ -195613,31 +234419,31 @@ static unsigned int sessionChangeHash( u8 *a = aRecord; /* Used to iterate through change record */ for(i=0; inCol; i++){ - int eType = *a; int isPK = pTab->abPK[i]; if( bPkOnly && isPK==0 ) continue; - /* It is not possible for eType to be SQLITE_NULL here. The session - ** module does not record changes for rows with NULL values stored in - ** primary key columns. */ - assert( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT - || eType==SQLITE_TEXT || eType==SQLITE_BLOB - || eType==SQLITE_NULL || eType==0 - ); - assert( !isPK || (eType!=0 && eType!=SQLITE_NULL) ); - if( isPK ){ - a++; + int eType = *a++; + + assert( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT + || eType==SQLITE_TEXT || eType==SQLITE_BLOB + || eType==SQLITE_NULL || eType==0 + ); + h = sessionHashAppendType(h, eType); if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){ h = sessionHashAppendI64(h, sessionGetI64(a)); a += 8; - }else{ - int n; + }else if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){ + int n; a += sessionVarintGet(a, &n); h = sessionHashAppendBlob(h, n, a); a += n; } + /* It should not be possible for eType to be SQLITE_NULL or 0x00 here, + ** as the session module does not record changes for rows with NULL + ** values stored in primary key columns. But a corrupt changesets + ** may contain such a value. */ }else{ a += sessionSerialLen(a); } @@ -195648,7 +234454,7 @@ static unsigned int sessionChangeHash( /* ** Arguments aLeft and aRight are pointers to change records for table pTab. ** This function returns true if the two records apply to the same row (i.e. -** have the same values stored in the primary key columns), or false +** have the same values stored in the primary key columns), or false ** otherwise. */ static int sessionChangeEqual( @@ -195685,17 +234491,17 @@ static int sessionChangeEqual( ** Arguments aLeft and aRight both point to buffers containing change ** records with nCol columns. This function "merges" the two records into ** a single records which is written to the buffer at *paOut. *paOut is -** then set to point to one byte after the last byte written before +** then set to point to one byte after the last byte written before ** returning. ** -** The merging of records is done as follows: For each column, if the +** The merging of records is done as follows: For each column, if the ** aRight record contains a value for the column, copy the value from ** their. Otherwise, if aLeft contains a value, copy it. If neither ** record contains a value for a given column, then neither does the ** output record. */ static void sessionMergeRecord( - u8 **paOut, + u8 **paOut, int nCol, u8 *aLeft, u8 *aRight @@ -195725,13 +234531,13 @@ static void sessionMergeRecord( /* ** This is a helper function used by sessionMergeUpdate(). ** -** When this function is called, both *paOne and *paTwo point to a value -** within a change record. Before it returns, both have been advanced so +** When this function is called, both *paOne and *paTwo point to a value +** within a change record. Before it returns, both have been advanced so ** as to point to the next value in the record. ** ** If, when this function is called, *paTwo points to a valid value (i.e. ** *paTwo[0] is not 0x00 - the "no value" placeholder), a copy of the *paTwo -** pointer is returned and *pnVal is set to the number of bytes in the +** pointer is returned and *pnVal is set to the number of bytes in the ** serialized value. Otherwise, a copy of *paOne is returned and *pnVal ** set to the number of bytes in the value at *paOne. If *paOne points ** to the "no value" placeholder, *pnVal is set to 1. In other words: @@ -195830,8 +234636,8 @@ static int sessionMergeUpdate( aOld = sessionMergeValue(&aOld1, &aOld2, &nOld); aNew = sessionMergeValue(&aNew1, &aNew2, &nNew); - if( bPatchset==0 - && (pTab->abPK[i] || (nOld==nNew && 0==memcmp(aOld, aNew, nNew))) + if( bPatchset==0 + && (pTab->abPK[i] || (nOld==nNew && 0==memcmp(aOld, aNew, nNew))) ){ *(aOut++) = '\0'; }else{ @@ -195853,6 +234659,7 @@ static int sessionMergeUpdate( */ static int sessionPreupdateEqual( sqlite3_session *pSession, /* Session object that owns SessionTable */ + i64 iRowid, /* Rowid value if pTab->bRowid */ SessionTable *pTab, /* Table associated with change */ SessionChange *pChange, /* Change to compare to */ int op /* Current pre-update operation */ @@ -195860,6 +234667,11 @@ static int sessionPreupdateEqual( int iCol; /* Used to iterate through columns */ u8 *a = pChange->aRecord; /* Cursor used to scan change record */ + if( pTab->bRowid ){ + if( a[0]!=SQLITE_INTEGER ) return 0; + return sessionGetI64(&a[1])==iRowid; + } + assert( op==SQLITE_INSERT || op==SQLITE_UPDATE || op==SQLITE_DELETE ); for(iCol=0; iColnCol; iCol++){ if( !pTab->abPK[iCol] ){ @@ -195868,6 +234680,7 @@ static int sessionPreupdateEqual( sqlite3_value *pVal; /* Value returned by preupdate_new/old */ int rc; /* Error code from preupdate_new/old */ int eType = *a++; /* Type of value from change record */ + int iIdx = pTab->aiIdx[iCol]; /* The following calls to preupdate_new() and preupdate_old() can not ** fail. This is because they cache their return values, and by the @@ -195876,12 +234689,13 @@ static int sessionPreupdateEqual( ** this (that the method has already been called). */ if( op==SQLITE_INSERT ){ /* assert( db->pPreUpdate->pNewUnpacked || db->pPreUpdate->aNew ); */ - rc = pSession->hook.xNew(pSession->hook.pCtx, iCol, &pVal); + rc = pSession->hook.xNew(pSession->hook.pCtx, iIdx, &pVal); }else{ /* assert( db->pPreUpdate->pUnpacked ); */ - rc = pSession->hook.xOld(pSession->hook.pCtx, iCol, &pVal); + rc = pSession->hook.xOld(pSession->hook.pCtx, iIdx, &pVal); } assert( rc==SQLITE_OK ); + (void)rc; /* Suppress warning about unused variable */ if( sqlite3_value_type(pVal)!=eType ) return 0; /* A SessionChange object never has a NULL value in a PK column */ @@ -195920,7 +234734,7 @@ static int sessionPreupdateEqual( } /* -** If required, grow the hash table used to store changes on table pTab +** If required, grow the hash table used to store changes on table pTab ** (part of the session pSession). If a fatal OOM error occurs, set the ** session object to failed and return SQLITE_ERROR. Otherwise, return ** SQLITE_OK. @@ -195930,13 +234744,19 @@ static int sessionPreupdateEqual( ** Growing the hash table in this case is a performance optimization only, ** it is not required for correct operation. */ -static int sessionGrowHash(int bPatchset, SessionTable *pTab){ +static int sessionGrowHash( + sqlite3_session *pSession, /* For memory accounting. May be NULL */ + int bPatchset, + SessionTable *pTab +){ if( pTab->nChange==0 || pTab->nEntry>=(pTab->nChange/2) ){ int i; SessionChange **apNew; sqlite3_int64 nNew = 2*(sqlite3_int64)(pTab->nChange ? pTab->nChange : 128); - apNew = (SessionChange **)sqlite3_malloc64(sizeof(SessionChange *) * nNew); + apNew = (SessionChange**)sessionMalloc64( + pSession, sizeof(SessionChange*) * nNew + ); if( apNew==0 ){ if( pTab->nChange==0 ){ return SQLITE_ERROR; @@ -195957,7 +234777,7 @@ static int sessionGrowHash(int bPatchset, SessionTable *pTab){ } } - sqlite3_free(pTab->apChange); + sessionFree(pSession, pTab->apChange); pTab->nChange = nNew; pTab->apChange = apNew; } @@ -195978,26 +234798,32 @@ static int sessionGrowHash(int bPatchset, SessionTable *pTab){ ** ** For example, if the table is declared as: ** -** CREATE TABLE tbl1(w, x, y, z, PRIMARY KEY(w, z)); +** CREATE TABLE tbl1(w, x DEFAULT 'abc', y, z, PRIMARY KEY(w, z)); ** -** Then the four output variables are populated as follows: +** Then the five output variables are populated as follows: ** ** *pnCol = 4 ** *pzTab = "tbl1" ** *pazCol = {"w", "x", "y", "z"} +** *pazDflt = {NULL, 'abc', NULL, NULL} ** *pabPK = {1, 0, 0, 1} ** ** All returned buffers are part of the same single allocation, which must ** be freed using sqlite3_free() by the caller */ static int sessionTableInfo( + sqlite3_session *pSession, /* For memory accounting. May be NULL */ sqlite3 *db, /* Database connection */ const char *zDb, /* Name of attached database (e.g. "main") */ const char *zThis, /* Table name */ int *pnCol, /* OUT: number of columns */ + int *pnTotalCol, /* OUT: number of hidden columns */ const char **pzTab, /* OUT: Copy of zThis */ const char ***pazCol, /* OUT: Array of column names for table */ - u8 **pabPK /* OUT: Array of booleans - true for PK col */ + const char ***pazDflt, /* OUT: Array of default value expressions */ + int **paiIdx, /* OUT: Array of xNew/xOld indexes */ + u8 **pabPK, /* OUT: Array of booleans - true for PK col */ + int *pbRowid /* OUT: True if only PK is a rowid */ ){ char *zPragma; sqlite3_stmt *pStmt; @@ -196008,19 +234834,30 @@ static int sessionTableInfo( int i; u8 *pAlloc = 0; char **azCol = 0; + char **azDflt = 0; u8 *abPK = 0; + int *aiIdx = 0; + int bRowid = 0; /* Set to true to use rowid as PK */ assert( pazCol && pabPK ); + *pazCol = 0; + *pabPK = 0; + *pnCol = 0; + if( pnTotalCol ) *pnTotalCol = 0; + if( paiIdx ) *paiIdx = 0; + if( pzTab ) *pzTab = 0; + if( pazDflt ) *pazDflt = 0; + nThis = sqlite3Strlen30(zThis); if( nThis==12 && 0==sqlite3_stricmp("sqlite_stat1", zThis) ){ rc = sqlite3_table_column_metadata(db, zDb, zThis, 0, 0, 0, 0, 0, 0); if( rc==SQLITE_OK ){ /* For sqlite_stat1, pretend that (tbl,idx) is the PRIMARY KEY. */ zPragma = sqlite3_mprintf( - "SELECT 0, 'tbl', '', 0, '', 1 UNION ALL " - "SELECT 1, 'idx', '', 0, '', 2 UNION ALL " - "SELECT 2, 'stat', '', 0, '', 0" + "SELECT 0, 'tbl', '', 0, '', 1, 0 UNION ALL " + "SELECT 1, 'idx', '', 0, '', 2, 0 UNION ALL " + "SELECT 2, 'stat', '', 0, '', 0, 0" ); }else if( rc==SQLITE_ERROR ){ zPragma = sqlite3_mprintf(""); @@ -196028,92 +234865,138 @@ static int sessionTableInfo( return rc; } }else{ - zPragma = sqlite3_mprintf("PRAGMA '%q'.table_info('%q')", zDb, zThis); + zPragma = sqlite3_mprintf("PRAGMA '%q'.table_xinfo('%q')", zDb, zThis); + } + if( !zPragma ){ + return SQLITE_NOMEM; } - if( !zPragma ) return SQLITE_NOMEM; rc = sqlite3_prepare_v2(db, zPragma, -1, &pStmt, 0); sqlite3_free(zPragma); - if( rc!=SQLITE_OK ) return rc; + if( rc!=SQLITE_OK ){ + return rc; + } nByte = nThis + 1; + bRowid = (pbRowid!=0); while( SQLITE_ROW==sqlite3_step(pStmt) ){ - nByte += sqlite3_column_bytes(pStmt, 1); - nDbCol++; + nByte += sqlite3_column_bytes(pStmt, 1); /* name */ + nByte += sqlite3_column_bytes(pStmt, 4); /* dflt_value */ + if( sqlite3_column_int(pStmt, 6)==0 ){ /* !hidden */ + nDbCol++; + } + if( sqlite3_column_int(pStmt, 5) ) bRowid = 0; /* pk */ } + if( nDbCol==0 ) bRowid = 0; + nDbCol += bRowid; + nByte += strlen(SESSIONS_ROWID); rc = sqlite3_reset(pStmt); if( rc==SQLITE_OK ){ - nByte += nDbCol * (sizeof(const char *) + sizeof(u8) + 1); - pAlloc = sqlite3_malloc64(nByte); + nByte += nDbCol * (sizeof(const char *)*2 +sizeof(int)+sizeof(u8) + 1 + 1); + pAlloc = sessionMalloc64(pSession, nByte); if( pAlloc==0 ){ rc = SQLITE_NOMEM; + }else{ + memset(pAlloc, 0, nByte); } } if( rc==SQLITE_OK ){ azCol = (char **)pAlloc; - pAlloc = (u8 *)&azCol[nDbCol]; - abPK = (u8 *)pAlloc; + azDflt = (char**)&azCol[nDbCol]; + aiIdx = (int*)&azDflt[nDbCol]; + abPK = (u8 *)&aiIdx[nDbCol]; pAlloc = &abPK[nDbCol]; if( pzTab ){ memcpy(pAlloc, zThis, nThis+1); *pzTab = (char *)pAlloc; pAlloc += nThis+1; } - + i = 0; - while( SQLITE_ROW==sqlite3_step(pStmt) ){ - int nName = sqlite3_column_bytes(pStmt, 1); - const unsigned char *zName = sqlite3_column_text(pStmt, 1); - if( zName==0 ) break; - memcpy(pAlloc, zName, nName+1); - azCol[i] = (char *)pAlloc; + if( bRowid ){ + size_t nName = strlen(SESSIONS_ROWID); + memcpy(pAlloc, SESSIONS_ROWID, nName+1); + azCol[i] = (char*)pAlloc; pAlloc += nName+1; - abPK[i] = sqlite3_column_int(pStmt, 5); + abPK[i] = 1; + aiIdx[i] = -1; i++; } + while( SQLITE_ROW==sqlite3_step(pStmt) ){ + if( sqlite3_column_int(pStmt, 6)==0 ){ /* !hidden */ + int nName = sqlite3_column_bytes(pStmt, 1); + int nDflt = sqlite3_column_bytes(pStmt, 4); + const unsigned char *zName = sqlite3_column_text(pStmt, 1); + const unsigned char *zDflt = sqlite3_column_text(pStmt, 4); + + if( zName==0 ) break; + memcpy(pAlloc, zName, nName+1); + azCol[i] = (char *)pAlloc; + pAlloc += nName+1; + if( zDflt ){ + memcpy(pAlloc, zDflt, nDflt+1); + azDflt[i] = (char *)pAlloc; + pAlloc += nDflt+1; + }else{ + azDflt[i] = 0; + } + abPK[i] = sqlite3_column_int(pStmt, 5); + aiIdx[i] = sqlite3_column_int(pStmt, 0); + i++; + } + if( pnTotalCol ) (*pnTotalCol)++; + } rc = sqlite3_reset(pStmt); - } /* If successful, populate the output variables. Otherwise, zero them and ** free any allocation made. An error code will be returned in this case. */ if( rc==SQLITE_OK ){ - *pazCol = (const char **)azCol; + *pazCol = (const char**)azCol; + if( pazDflt ) *pazDflt = (const char**)azDflt; *pabPK = abPK; *pnCol = nDbCol; + if( paiIdx ) *paiIdx = aiIdx; }else{ - *pazCol = 0; - *pabPK = 0; - *pnCol = 0; - if( pzTab ) *pzTab = 0; - sqlite3_free(azCol); + sessionFree(pSession, azCol); } + if( pbRowid ) *pbRowid = bRowid; sqlite3_finalize(pStmt); return rc; } /* -** This function is only called from within a pre-update handler for a -** write to table pTab, part of session pSession. If this is the first -** write to this table, initalize the SessionTable.nCol, azCol[] and -** abPK[] arrays accordingly. +** This function is called to initialize the SessionTable.nCol, azCol[] +** abPK[] and azDflt[] members of SessionTable object pTab. If these +** fields are already initialized, this function is a no-op. ** ** If an error occurs, an error code is stored in sqlite3_session.rc and ** non-zero returned. Or, if no error occurs but the table has no primary ** key, sqlite3_session.rc is left set to SQLITE_OK and non-zero returned to -** indicate that updates on this table should be ignored. SessionTable.abPK +** indicate that updates on this table should be ignored. SessionTable.abPK ** is set to NULL in this case. */ -static int sessionInitTable(sqlite3_session *pSession, SessionTable *pTab){ +static int sessionInitTable( + sqlite3_session *pSession, /* Optional session handle */ + SessionTable *pTab, /* Table object to initialize */ + sqlite3 *db, /* Database handle to read schema from */ + const char *zDb /* Name of db - "main", "temp" etc. */ +){ + int rc = SQLITE_OK; + if( pTab->nCol==0 ){ u8 *abPK; assert( pTab->azCol==0 || pTab->abPK==0 ); - pSession->rc = sessionTableInfo(pSession->db, pSession->zDb, - pTab->zName, &pTab->nCol, 0, &pTab->azCol, &abPK + sqlite3_free(pTab->azCol); + pTab->abPK = 0; + rc = sessionTableInfo(pSession, db, zDb, + pTab->zName, &pTab->nCol, &pTab->nTotalCol, 0, &pTab->azCol, + &pTab->azDflt, &pTab->aiIdx, &abPK, + ((pSession==0 || pSession->bImplicitPK) ? &pTab->bRowid : 0) ); - if( pSession->rc==SQLITE_OK ){ + if( rc==SQLITE_OK ){ int i; for(i=0; inCol; i++){ if( abPK[i] ){ @@ -196124,9 +235007,333 @@ static int sessionInitTable(sqlite3_session *pSession, SessionTable *pTab){ if( 0==sqlite3_stricmp("sqlite_stat1", pTab->zName) ){ pTab->bStat1 = 1; } + + if( pSession && pSession->bEnableSize ){ + pSession->nMaxChangesetSize += ( + 1 + sessionVarintLen(pTab->nCol) + pTab->nCol + strlen(pTab->zName)+1 + ); + } + } + } + + if( pSession ){ + pSession->rc = rc; + return (rc || pTab->abPK==0); + } + return rc; +} + +/* +** Re-initialize table object pTab. +*/ +static int sessionReinitTable(sqlite3_session *pSession, SessionTable *pTab){ + int nCol = 0; + int nTotalCol = 0; + const char **azCol = 0; + const char **azDflt = 0; + int *aiIdx = 0; + u8 *abPK = 0; + int bRowid = 0; + + assert( pSession->rc==SQLITE_OK ); + + pSession->rc = sessionTableInfo(pSession, pSession->db, pSession->zDb, + pTab->zName, &nCol, &nTotalCol, 0, &azCol, &azDflt, &aiIdx, &abPK, + (pSession->bImplicitPK ? &bRowid : 0) + ); + if( pSession->rc==SQLITE_OK ){ + if( pTab->nCol>nCol || pTab->bRowid!=bRowid ){ + pSession->rc = SQLITE_SCHEMA; + }else{ + int ii; + int nOldCol = pTab->nCol; + for(ii=0; iinCol ){ + if( pTab->abPK[ii]!=abPK[ii] ){ + pSession->rc = SQLITE_SCHEMA; + } + }else if( abPK[ii] ){ + pSession->rc = SQLITE_SCHEMA; + } + } + + if( pSession->rc==SQLITE_OK ){ + const char **a = pTab->azCol; + pTab->azCol = azCol; + pTab->nCol = nCol; + pTab->nTotalCol = nTotalCol; + pTab->azDflt = azDflt; + pTab->abPK = abPK; + pTab->aiIdx = aiIdx; + azCol = a; + } + if( pSession->bEnableSize ){ + pSession->nMaxChangesetSize += (nCol - nOldCol); + pSession->nMaxChangesetSize += sessionVarintLen(nCol); + pSession->nMaxChangesetSize -= sessionVarintLen(nOldCol); + } + } + } + + sqlite3_free((char*)azCol); + return pSession->rc; +} + +/* +** Session-change object (*pp) contains an old.* record with fewer than +** nCol fields. This function updates it with the default values for +** the missing fields. +*/ +static void sessionUpdateOneChange( + sqlite3_session *pSession, /* For memory accounting */ + int *pRc, /* IN/OUT: Error code */ + SessionChange **pp, /* IN/OUT: Change object to update */ + int nCol, /* Number of columns now in table */ + sqlite3_stmt *pDflt /* SELECT */ +){ + SessionChange *pOld = *pp; + + while( pOld->nRecordFieldnRecordField; + int eType = sqlite3_column_type(pDflt, iField); + switch( eType ){ + case SQLITE_NULL: + nIncr = 1; + break; + case SQLITE_INTEGER: + case SQLITE_FLOAT: + nIncr = 9; + break; + default: { + int n = sqlite3_column_bytes(pDflt, iField); + nIncr = 1 + sessionVarintLen(n) + n; + assert( eType==SQLITE_TEXT || eType==SQLITE_BLOB ); + break; + } + } + + nByte = nIncr + (sizeof(SessionChange) + pOld->nRecord); + pNew = sessionMalloc64(pSession, nByte); + if( pNew==0 ){ + *pRc = SQLITE_NOMEM; + return; + }else{ + memcpy(pNew, pOld, sizeof(SessionChange)); + pNew->aRecord = (u8*)&pNew[1]; + memcpy(pNew->aRecord, pOld->aRecord, pOld->nRecord); + pNew->aRecord[pNew->nRecord++] = (u8)eType; + switch( eType ){ + case SQLITE_INTEGER: { + i64 iVal = sqlite3_column_int64(pDflt, iField); + sessionPutI64(&pNew->aRecord[pNew->nRecord], iVal); + pNew->nRecord += 8; + break; + } + + case SQLITE_FLOAT: { + double rVal = sqlite3_column_double(pDflt, iField); + sessionPutDouble(&pNew->aRecord[pNew->nRecord], rVal); + pNew->nRecord += 8; + break; + } + + case SQLITE_TEXT: { + int n = sqlite3_column_bytes(pDflt, iField); + const char *z = (const char*)sqlite3_column_text(pDflt, iField); + pNew->nRecord += sessionVarintPut(&pNew->aRecord[pNew->nRecord], n); + memcpy(&pNew->aRecord[pNew->nRecord], z, n); + pNew->nRecord += n; + break; + } + + case SQLITE_BLOB: { + int n = sqlite3_column_bytes(pDflt, iField); + const u8 *z = (const u8*)sqlite3_column_blob(pDflt, iField); + pNew->nRecord += sessionVarintPut(&pNew->aRecord[pNew->nRecord], n); + memcpy(&pNew->aRecord[pNew->nRecord], z, n); + pNew->nRecord += n; + break; + } + + default: + assert( eType==SQLITE_NULL ); + break; + } + + sessionFree(pSession, pOld); + *pp = pOld = pNew; + pNew->nRecordField++; + pNew->nMaxSize += nIncr; + if( pSession ){ + pSession->nMaxChangesetSize += nIncr; + } + } + } +} + +/* +** Ensure that there is room in the buffer to append nByte bytes of data. +** If not, use sqlite3_realloc() to grow the buffer so that there is. +** +** If successful, return zero. Otherwise, if an OOM condition is encountered, +** set *pRc to SQLITE_NOMEM and return non-zero. +*/ +static int sessionBufferGrow(SessionBuffer *p, i64 nByte, int *pRc){ +#define SESSION_MAX_BUFFER_SZ (0x7FFFFF00 - 1) + i64 nReq = p->nBuf + nByte; + if( *pRc==SQLITE_OK && nReq>p->nAlloc ){ + u8 *aNew; + i64 nNew = p->nAlloc ? p->nAlloc : 128; + + do { + nNew = nNew*2; + }while( nNewSESSION_MAX_BUFFER_SZ ){ + nNew = SESSION_MAX_BUFFER_SZ; + if( nNewaBuf, nNew); + if( 0==aNew ){ + *pRc = SQLITE_NOMEM; + }else{ + p->aBuf = aNew; + p->nAlloc = nNew; + } + } + return (*pRc!=SQLITE_OK); +} + + +/* +** This function is a no-op if *pRc is other than SQLITE_OK when it is +** called. Otherwise, append a string to the buffer. All bytes in the string +** up to (but not including) the nul-terminator are written to the buffer. +** +** If an OOM condition is encountered, set *pRc to SQLITE_NOMEM before +** returning. +*/ +static void sessionAppendStr( + SessionBuffer *p, + const char *zStr, + int *pRc +){ + int nStr = sqlite3Strlen30(zStr); + if( 0==sessionBufferGrow(p, (i64)nStr+1, pRc) ){ + memcpy(&p->aBuf[p->nBuf], zStr, nStr); + p->nBuf += nStr; + p->aBuf[p->nBuf] = 0x00; + } +} + +/* +** Format a string using printf() style formatting and then append it to the +** buffer using sessionAppendString(). +*/ +static void sessionAppendPrintf( + SessionBuffer *p, /* Buffer to append to */ + int *pRc, + const char *zFmt, + ... +){ + if( *pRc==SQLITE_OK ){ + char *zApp = 0; + va_list ap; + va_start(ap, zFmt); + zApp = sqlite3_vmprintf(zFmt, ap); + if( zApp==0 ){ + *pRc = SQLITE_NOMEM; + }else{ + sessionAppendStr(p, zApp, pRc); + } + va_end(ap); + sqlite3_free(zApp); + } +} + +/* +** Prepare a statement against database handle db that SELECTs a single +** row containing the default values for each column in table pTab. For +** example, if pTab is declared as: +** +** CREATE TABLE pTab(a PRIMARY KEY, b DEFAULT 123, c DEFAULT 'abcd'); +** +** Then this function prepares and returns the SQL statement: +** +** SELECT NULL, 123, 'abcd'; +*/ +static int sessionPrepareDfltStmt( + sqlite3 *db, /* Database handle */ + SessionTable *pTab, /* Table to prepare statement for */ + sqlite3_stmt **ppStmt /* OUT: Statement handle */ +){ + SessionBuffer sql = {0,0,0}; + int rc = SQLITE_OK; + const char *zSep = " "; + int ii = 0; + + *ppStmt = 0; + sessionAppendPrintf(&sql, &rc, "SELECT"); + for(ii=0; iinCol; ii++){ + const char *zDflt = pTab->azDflt[ii] ? pTab->azDflt[ii] : "NULL"; + sessionAppendPrintf(&sql, &rc, "%s%s", zSep, zDflt); + zSep = ", "; + } + if( rc==SQLITE_OK ){ + rc = sqlite3_prepare_v2(db, (const char*)sql.aBuf, -1, ppStmt, 0); + } + sqlite3_free(sql.aBuf); + + return rc; +} + +/* +** Finalize statement pStmt. If (*pRc) is SQLITE_OK when this function is +** called, set it to the results of the sqlite3_finalize() call. Or, if +** it is already set to an error code, leave it as is. +*/ +static void sessionFinalizeStmt(sqlite3_stmt *pStmt, int *pRc){ + int rc = sqlite3_finalize(pStmt); + if( *pRc==SQLITE_OK ) *pRc = rc; +} + +/* +** Table pTab has one or more existing change-records with old.* records +** with fewer than pTab->nCol columns. This function updates all such +** change-records with the default values for the missing columns. +*/ +static int sessionUpdateChanges(sqlite3_session *pSession, SessionTable *pTab){ + sqlite3_stmt *pStmt = 0; + int rc = pSession->rc; + + rc = sessionPrepareDfltStmt(pSession->db, pTab, &pStmt); + if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ + int ii = 0; + SessionChange **pp = 0; + for(ii=0; iinChange; ii++){ + for(pp=&pTab->apChange[ii]; *pp; pp=&((*pp)->pNext)){ + if( (*pp)->nRecordField!=pTab->nCol ){ + sessionUpdateOneChange(pSession, &rc, pp, pTab->nCol, pStmt); + } + } } } - return (pSession->rc || pTab->abPK==0); + + sessionFinalizeStmt(pStmt, &rc); + pSession->rc = rc; + return pSession->rc; } /* @@ -196169,9 +235376,112 @@ static int sessionStat1Depth(void *pCtx){ return p->hook.xDepth(p->hook.pCtx); } +static int sessionUpdateMaxSize( + int op, + sqlite3_session *pSession, /* Session object pTab is attached to */ + SessionTable *pTab, /* Table that change applies to */ + SessionChange *pC /* Update pC->nMaxSize */ +){ + i64 nNew = 2; + if( pC->op==SQLITE_INSERT ){ + if( pTab->bRowid ) nNew += 9; + if( op!=SQLITE_DELETE ){ + int ii; + for(ii=0; iinCol; ii++){ + sqlite3_value *p = 0; + pSession->hook.xNew(pSession->hook.pCtx, pTab->aiIdx[ii], &p); + sessionSerializeValue(0, p, &nNew); + } + } + }else if( op==SQLITE_DELETE ){ + nNew += pC->nRecord; + if( sqlite3_preupdate_blobwrite(pSession->db)>=0 ){ + nNew += pC->nRecord; + } + }else{ + int ii; + u8 *pCsr = pC->aRecord; + if( pTab->bRowid ){ + nNew += 9 + 1; + pCsr += 9; + } + for(ii=pTab->bRowid; iinCol; ii++){ + int bChanged = 1; + int nOld = 0; + int eType; + int iIdx = pTab->aiIdx[ii]; + sqlite3_value *p = 0; + pSession->hook.xNew(pSession->hook.pCtx, iIdx, &p); + if( p==0 ){ + return SQLITE_NOMEM; + } + + eType = *pCsr++; + switch( eType ){ + case SQLITE_NULL: + bChanged = sqlite3_value_type(p)!=SQLITE_NULL; + break; + + case SQLITE_FLOAT: + case SQLITE_INTEGER: { + if( eType==sqlite3_value_type(p) ){ + sqlite3_int64 iVal = sessionGetI64(pCsr); + if( eType==SQLITE_INTEGER ){ + bChanged = (iVal!=sqlite3_value_int64(p)); + }else{ + double dVal; + memcpy(&dVal, &iVal, 8); + bChanged = (dVal!=sqlite3_value_double(p)); + } + } + nOld = 8; + pCsr += 8; + break; + } + + default: { + int nByte; + nOld = sessionVarintGet(pCsr, &nByte); + pCsr += nOld; + nOld += nByte; + assert( eType==SQLITE_TEXT || eType==SQLITE_BLOB ); + if( eType==sqlite3_value_type(p) + && nByte==sqlite3_value_bytes(p) + && (nByte==0 || 0==memcmp(pCsr, sqlite3_value_blob(p), nByte)) + ){ + bChanged = 0; + } + pCsr += nByte; + break; + } + } + + if( bChanged && pTab->abPK[ii] ){ + nNew = pC->nRecord + 2; + break; + } + + if( bChanged ){ + nNew += 1 + nOld; + sessionSerializeValue(0, p, &nNew); + }else if( pTab->abPK[ii] ){ + nNew += 2 + nOld; + }else{ + nNew += 2; + } + } + } + + if( nNew>pC->nMaxSize ){ + int nIncr = nNew - pC->nMaxSize; + pC->nMaxSize = nNew; + pSession->nMaxChangesetSize += nIncr; + } + return SQLITE_OK; +} /* -** This function is only called from with a pre-update-hook reporting a +** This function is only called from with a pre-update-hook reporting a ** change on table pTab (attached to session pSession). The type of change ** (UPDATE, INSERT, DELETE) is specified by the first argument. ** @@ -196180,28 +235490,35 @@ static int sessionStat1Depth(void *pCtx){ */ static void sessionPreupdateOneChange( int op, /* One of SQLITE_UPDATE, INSERT, DELETE */ + i64 iRowid, sqlite3_session *pSession, /* Session object pTab is attached to */ SessionTable *pTab /* Table that change applies to */ ){ - int iHash; - int bNull = 0; + int iHash; + int bNull = 0; int rc = SQLITE_OK; + int nExpect = 0; SessionStat1Ctx stat1 = {{0,0,0,0,0},0}; if( pSession->rc ) return; /* Load table details if required */ - if( sessionInitTable(pSession, pTab) ) return; + if( sessionInitTable(pSession, pTab, pSession->db, pSession->zDb) ) return; - /* Check the number of columns in this xPreUpdate call matches the + /* Check the number of columns in this xPreUpdate call matches the ** number of columns in the table. */ - if( pTab->nCol!=pSession->hook.xCount(pSession->hook.pCtx) ){ + nExpect = pSession->hook.xCount(pSession->hook.pCtx); + if( pTab->nTotalColnTotalCol!=nExpect ){ pSession->rc = SQLITE_SCHEMA; return; } /* Grow the hash table if required */ - if( sessionGrowHash(0, pTab) ){ + if( sessionGrowHash(pSession, 0, pTab) ){ pSession->rc = SQLITE_NOMEM; return; } @@ -196228,90 +235545,111 @@ static void sessionPreupdateOneChange( /* Calculate the hash-key for this change. If the primary key of the row ** includes a NULL value, exit early. Such changes are ignored by the ** session module. */ - rc = sessionPreupdateHash(pSession, pTab, op==SQLITE_INSERT, &iHash, &bNull); + rc = sessionPreupdateHash( + pSession, iRowid, pTab, op==SQLITE_INSERT, &iHash, &bNull + ); if( rc!=SQLITE_OK ) goto error_out; if( bNull==0 ){ /* Search the hash table for an existing record for this row. */ SessionChange *pC; for(pC=pTab->apChange[iHash]; pC; pC=pC->pNext){ - if( sessionPreupdateEqual(pSession, pTab, pC, op) ) break; + if( sessionPreupdateEqual(pSession, iRowid, pTab, pC, op) ) break; } if( pC==0 ){ /* Create a new change object containing all the old values (if ** this is an SQLITE_UPDATE or SQLITE_DELETE), or just the PK ** values (if this is an INSERT). */ - SessionChange *pChange; /* New change object */ sqlite3_int64 nByte; /* Number of bytes to allocate */ int i; /* Used to iterate through columns */ - + assert( rc==SQLITE_OK ); pTab->nEntry++; - + /* Figure out how large an allocation is required */ nByte = sizeof(SessionChange); - for(i=0; inCol; i++){ + for(i=pTab->bRowid; inCol; i++){ + int iIdx = pTab->aiIdx[i]; sqlite3_value *p = 0; if( op!=SQLITE_INSERT ){ - TESTONLY(int trc = ) pSession->hook.xOld(pSession->hook.pCtx, i, &p); - assert( trc==SQLITE_OK ); + /* This may fail if the column has a non-NULL default and was added + ** using ALTER TABLE ADD COLUMN after this record was created. */ + rc = pSession->hook.xOld(pSession->hook.pCtx, iIdx, &p); }else if( pTab->abPK[i] ){ - TESTONLY(int trc = ) pSession->hook.xNew(pSession->hook.pCtx, i, &p); + TESTONLY(int trc = ) pSession->hook.xNew(pSession->hook.pCtx,iIdx,&p); assert( trc==SQLITE_OK ); } - /* This may fail if SQLite value p contains a utf-16 string that must - ** be converted to utf-8 and an OOM error occurs while doing so. */ - rc = sessionSerializeValue(0, p, &nByte); + if( rc==SQLITE_OK ){ + /* This may fail if SQLite value p contains a utf-16 string that must + ** be converted to utf-8 and an OOM error occurs while doing so. */ + rc = sessionSerializeValue(0, p, &nByte); + } if( rc!=SQLITE_OK ) goto error_out; } - + if( pTab->bRowid ){ + nByte += 9; /* Size of rowid field - an integer */ + } + /* Allocate the change object */ - pChange = (SessionChange *)sqlite3_malloc64(nByte); - if( !pChange ){ + pC = (SessionChange*)sessionMalloc64(pSession, nByte); + if( !pC ){ rc = SQLITE_NOMEM; goto error_out; }else{ - memset(pChange, 0, sizeof(SessionChange)); - pChange->aRecord = (u8 *)&pChange[1]; + memset(pC, 0, sizeof(SessionChange)); + pC->aRecord = (u8 *)&pC[1]; } - + /* Populate the change object. None of the preupdate_old(), ** preupdate_new() or SerializeValue() calls below may fail as all ** required values and encodings have already been cached in memory. ** It is not possible for an OOM to occur in this block. */ nByte = 0; - for(i=0; inCol; i++){ + if( pTab->bRowid ){ + pC->aRecord[0] = SQLITE_INTEGER; + sessionPutI64(&pC->aRecord[1], iRowid); + nByte = 9; + } + for(i=pTab->bRowid; inCol; i++){ sqlite3_value *p = 0; + int iIdx = pTab->aiIdx[i]; if( op!=SQLITE_INSERT ){ - pSession->hook.xOld(pSession->hook.pCtx, i, &p); + pSession->hook.xOld(pSession->hook.pCtx, iIdx, &p); }else if( pTab->abPK[i] ){ - pSession->hook.xNew(pSession->hook.pCtx, i, &p); + pSession->hook.xNew(pSession->hook.pCtx, iIdx, &p); } - sessionSerializeValue(&pChange->aRecord[nByte], p, &nByte); + sessionSerializeValue(&pC->aRecord[nByte], p, &nByte); } /* Add the change to the hash-table */ if( pSession->bIndirect || pSession->hook.xDepth(pSession->hook.pCtx) ){ - pChange->bIndirect = 1; + pC->bIndirect = 1; } - pChange->nRecord = nByte; - pChange->op = op; - pChange->pNext = pTab->apChange[iHash]; - pTab->apChange[iHash] = pChange; + pC->nRecordField = pTab->nCol; + pC->nRecord = nByte; + pC->op = op; + pC->pNext = pTab->apChange[iHash]; + pTab->apChange[iHash] = pC; }else if( pC->bIndirect ){ /* If the existing change is considered "indirect", but this current ** change is "direct", mark the change object as direct. */ - if( pSession->hook.xDepth(pSession->hook.pCtx)==0 - && pSession->bIndirect==0 + if( pSession->hook.xDepth(pSession->hook.pCtx)==0 + && pSession->bIndirect==0 ){ pC->bIndirect = 0; } } + + assert( rc==SQLITE_OK ); + if( pSession->bEnableSize ){ + rc = sessionUpdateMaxSize(op, pSession, pTab, pC); + } } + /* If an error has occurred, mark the session object as failed. */ error_out: if( pTab->bStat1 ){ @@ -196323,7 +235661,7 @@ static void sessionPreupdateOneChange( } static int sessionFindTable( - sqlite3_session *pSession, + sqlite3_session *pSession, const char *zName, SessionTable **ppTab ){ @@ -196340,11 +235678,15 @@ static int sessionFindTable( /* If there is a table-filter configured, invoke it. If it returns 0, ** do not automatically add the new table. */ if( pSession->xTableFilter==0 - || pSession->xTableFilter(pSession->pFilterCtx, zName) + || pSession->xTableFilter(pSession->pFilterCtx, zName) ){ rc = sqlite3session_attach(pSession, zName); if( rc==SQLITE_OK ){ - for(pRet=pSession->pTable; pRet->pNext; pRet=pRet->pNext); + pRet = pSession->pTable; + while( ALWAYS(pRet) && pRet->pNext ){ + pRet = pRet->pNext; + } + assert( pRet!=0 ); assert( 0==sqlite3_strnicmp(pRet->zName, zName, nName+1) ); } } @@ -196371,12 +235713,14 @@ static void xPreUpdate( int nDb = sqlite3Strlen30(zDb); assert( sqlite3_mutex_held(db->mutex) ); + (void)iKey1; + (void)iKey2; for(pSession=(sqlite3_session *)pCtx; pSession; pSession=pSession->pNext){ SessionTable *pTab; - /* If this session is attached to a different database ("main", "temp" - ** etc.), or if it is not currently enabled, there is nothing to do. Skip + /* If this session is attached to a different database ("main", "temp" + ** etc.), or if it is not currently enabled, there is nothing to do. Skip ** to the next session object attached to this database. */ if( pSession->bEnable==0 ) continue; if( pSession->rc ) continue; @@ -196385,9 +235729,10 @@ static void xPreUpdate( pSession->rc = sessionFindTable(pSession, zName, &pTab); if( pTab ){ assert( pSession->rc==SQLITE_OK ); - sessionPreupdateOneChange(op, pSession, pTab); + assert( op==SQLITE_UPDATE || iKey1==iKey2 ); + sessionPreupdateOneChange(op, iKey1, pSession, pTab); if( op==SQLITE_UPDATE ){ - sessionPreupdateOneChange(SQLITE_INSERT, pSession, pTab); + sessionPreupdateOneChange(SQLITE_INSERT, iKey2, pSession, pTab); } } } @@ -196426,6 +235771,7 @@ static void sessionPreupdateHooks( typedef struct SessionDiffCtx SessionDiffCtx; struct SessionDiffCtx { sqlite3_stmt *pStmt; + int bRowid; int nOldOff; }; @@ -196434,19 +235780,20 @@ struct SessionDiffCtx { */ static int sessionDiffOld(void *pCtx, int iVal, sqlite3_value **ppVal){ SessionDiffCtx *p = (SessionDiffCtx*)pCtx; - *ppVal = sqlite3_column_value(p->pStmt, iVal+p->nOldOff); + *ppVal = sqlite3_column_value(p->pStmt, iVal+p->nOldOff+p->bRowid); return SQLITE_OK; } static int sessionDiffNew(void *pCtx, int iVal, sqlite3_value **ppVal){ SessionDiffCtx *p = (SessionDiffCtx*)pCtx; - *ppVal = sqlite3_column_value(p->pStmt, iVal); + *ppVal = sqlite3_column_value(p->pStmt, iVal+p->bRowid); return SQLITE_OK; } static int sessionDiffCount(void *pCtx){ SessionDiffCtx *p = (SessionDiffCtx*)pCtx; - return p->nOldOff ? p->nOldOff : sqlite3_column_count(p->pStmt); + return (p->nOldOff ? p->nOldOff : sqlite3_column_count(p->pStmt)) - p->bRowid; } static int sessionDiffDepth(void *pCtx){ + (void)pCtx; return 0; } @@ -196467,7 +235814,7 @@ static void sessionDiffHooks( static char *sessionExprComparePK( int nCol, - const char *zDb1, const char *zDb2, + const char *zDb1, const char *zDb2, const char *zTab, const char **azCol, u8 *abPK ){ @@ -196490,7 +235837,7 @@ static char *sessionExprComparePK( static char *sessionExprCompareOther( int nCol, - const char *zDb1, const char *zDb2, + const char *zDb1, const char *zDb2, const char *zTab, const char **azCol, u8 *abPK ){ @@ -196520,17 +235867,18 @@ static char *sessionExprCompareOther( } static char *sessionSelectFindNew( - int nCol, const char *zDb1, /* Pick rows in this db only */ const char *zDb2, /* But not in this one */ + int bRowid, const char *zTbl, /* Table name */ const char *zExpr ){ + const char *zSel = (bRowid ? SESSIONS_ROWID ", *" : "*"); char *zRet = sqlite3_mprintf( - "SELECT * FROM \"%w\".\"%w\" WHERE NOT EXISTS (" + "SELECT %s FROM \"%w\".\"%w\" WHERE NOT EXISTS (" " SELECT 1 FROM \"%w\".\"%w\" WHERE %s" ")", - zDb1, zTbl, zDb2, zTbl, zExpr + zSel, zDb1, zTbl, zDb2, zTbl, zExpr ); return zRet; } @@ -196544,19 +235892,23 @@ static int sessionDiffFindNew( char *zExpr ){ int rc = SQLITE_OK; - char *zStmt = sessionSelectFindNew(pTab->nCol, zDb1, zDb2, pTab->zName,zExpr); + char *zStmt = sessionSelectFindNew( + zDb1, zDb2, pTab->bRowid, pTab->zName, zExpr + ); if( zStmt==0 ){ rc = SQLITE_NOMEM; }else{ sqlite3_stmt *pStmt; - rc = sqlite3_prepare(pSession->db, zStmt, -1, &pStmt, 0); + rc = sqlite3_prepare_v2(pSession->db, zStmt, -1, &pStmt, 0); if( rc==SQLITE_OK ){ SessionDiffCtx *pDiffCtx = (SessionDiffCtx*)pSession->hook.pCtx; pDiffCtx->pStmt = pStmt; pDiffCtx->nOldOff = 0; + pDiffCtx->bRowid = pTab->bRowid; while( SQLITE_ROW==sqlite3_step(pStmt) ){ - sessionPreupdateOneChange(op, pSession, pTab); + i64 iRowid = (pTab->bRowid ? sqlite3_column_int64(pStmt, 0) : 0); + sessionPreupdateOneChange(op, iRowid, pSession, pTab); } rc = sqlite3_finalize(pStmt); } @@ -196566,10 +235918,31 @@ static int sessionDiffFindNew( return rc; } +/* +** Return a comma-separated list of the fully-qualified (with both database +** and table name) column names from table pTab. e.g. +** +** "main"."t1"."a", "main"."t1"."b", "main"."t1"."c" +*/ +static char *sessionAllCols( + const char *zDb, + SessionTable *pTab +){ + int ii; + char *zRet = 0; + for(ii=0; iinCol; ii++){ + zRet = sqlite3_mprintf("%z%s\"%w\".\"%w\".\"%w\"", + zRet, (zRet ? ", " : ""), zDb, pTab->zName, pTab->azCol[ii] + ); + if( !zRet ) break; + } + return zRet; +} + static int sessionDiffFindModified( - sqlite3_session *pSession, - SessionTable *pTab, - const char *zFrom, + sqlite3_session *pSession, + SessionTable *pTab, + const char *zFrom, const char *zExpr ){ int rc = SQLITE_OK; @@ -196580,27 +235953,32 @@ static int sessionDiffFindModified( if( zExpr2==0 ){ rc = SQLITE_NOMEM; }else{ + char *z1 = sessionAllCols(pSession->zDb, pTab); + char *z2 = sessionAllCols(zFrom, pTab); char *zStmt = sqlite3_mprintf( - "SELECT * FROM \"%w\".\"%w\", \"%w\".\"%w\" WHERE %s AND (%z)", - pSession->zDb, pTab->zName, zFrom, pTab->zName, zExpr, zExpr2 + "SELECT %s,%s FROM \"%w\".\"%w\", \"%w\".\"%w\" WHERE %s AND (%z)", + z1, z2, pSession->zDb, pTab->zName, zFrom, pTab->zName, zExpr, zExpr2 ); - if( zStmt==0 ){ + if( zStmt==0 || z1==0 || z2==0 ){ rc = SQLITE_NOMEM; }else{ sqlite3_stmt *pStmt; - rc = sqlite3_prepare(pSession->db, zStmt, -1, &pStmt, 0); + rc = sqlite3_prepare_v2(pSession->db, zStmt, -1, &pStmt, 0); if( rc==SQLITE_OK ){ SessionDiffCtx *pDiffCtx = (SessionDiffCtx*)pSession->hook.pCtx; pDiffCtx->pStmt = pStmt; pDiffCtx->nOldOff = pTab->nCol; while( SQLITE_ROW==sqlite3_step(pStmt) ){ - sessionPreupdateOneChange(SQLITE_UPDATE, pSession, pTab); + i64 iRowid = (pTab->bRowid ? sqlite3_column_int64(pStmt, 0) : 0); + sessionPreupdateOneChange(SQLITE_UPDATE, iRowid, pSession, pTab); } rc = sqlite3_finalize(pStmt); } - sqlite3_free(zStmt); } + sqlite3_free(zStmt); + sqlite3_free(z1); + sqlite3_free(z2); } return rc; @@ -196627,9 +236005,11 @@ SQLITE_API int sqlite3session_diff( SessionTable *pTo; /* Table zTbl */ /* Locate and if necessary initialize the target table object */ + pSession->bAutoAttach++; rc = sessionFindTable(pSession, zTbl, &pTo); + pSession->bAutoAttach--; if( pTo==0 ) goto diff_out; - if( sessionInitTable(pSession, pTo) ){ + if( sessionInitTable(pSession, pTo, pSession->db, pSession->zDb) ){ rc = pSession->rc; goto diff_out; } @@ -196638,13 +236018,43 @@ SQLITE_API int sqlite3session_diff( if( rc==SQLITE_OK ){ int bHasPk = 0; int bMismatch = 0; - int nCol; /* Columns in zFrom.zTbl */ - u8 *abPK; + int nCol = 0; /* Columns in zFrom.zTbl */ + int bRowid = 0; + u8 *abPK = 0; const char **azCol = 0; - rc = sessionTableInfo(db, zFrom, zTbl, &nCol, 0, &azCol, &abPK); + char *zDbExists = 0; + + /* Check that database zFrom is attached. */ + zDbExists = sqlite3_mprintf("SELECT * FROM %Q.sqlite_schema", zFrom); + if( zDbExists==0 ){ + rc = SQLITE_NOMEM; + }else{ + sqlite3_stmt *pDbExists = 0; + rc = sqlite3_prepare_v2(db, zDbExists, -1, &pDbExists, 0); + if( rc==SQLITE_ERROR ){ + rc = SQLITE_OK; + nCol = -1; + } + sqlite3_finalize(pDbExists); + sqlite3_free(zDbExists); + } + + if( rc==SQLITE_OK && nCol==0 ){ + rc = sessionTableInfo(0, db, zFrom, zTbl, + &nCol, 0, 0, &azCol, 0, 0, &abPK, + pSession->bImplicitPK ? &bRowid : 0 + ); + } if( rc==SQLITE_OK ){ if( pTo->nCol!=nCol ){ - bMismatch = 1; + if( nCol<=0 ){ + rc = SQLITE_SCHEMA; + if( pzErrMsg ){ + *pzErrMsg = sqlite3_mprintf("no such table: %s.%s", zFrom, zTbl); + } + }else{ + bMismatch = 1; + } }else{ int i; for(i=0; inCol, + zExpr = sessionExprComparePK(pTo->nCol, zDb, zFrom, pTo->zName, pTo->azCol, pTo->abPK ); } @@ -196721,7 +236133,7 @@ SQLITE_API int sqlite3session_create( memcpy(pNew->zDb, zDb, nDb+1); sessionPreupdateHooks(pNew); - /* Add the new session object to the linked list of session objects + /* Add the new session object to the linked list of session objects ** attached to database handle $db. Do this under the cover of the db ** handle mutex. */ sqlite3_mutex_enter(sqlite3_db_mutex(db)); @@ -196737,7 +236149,7 @@ SQLITE_API int sqlite3session_create( ** Free the list of table objects passed as the first argument. The contents ** of the changed-rows hash tables are also deleted. */ -static void sessionDeleteTable(SessionTable *pList){ +static void sessionDeleteTable(sqlite3_session *pSession, SessionTable *pList){ SessionTable *pNext; SessionTable *pTab; @@ -196749,12 +236161,13 @@ static void sessionDeleteTable(SessionTable *pList){ SessionChange *pNextChange; for(p=pTab->apChange[i]; p; p=pNextChange){ pNextChange = p->pNext; - sqlite3_free(p); + sessionFree(pSession, p); } } - sqlite3_free((char*)pTab->azCol); /* cast works around VC++ bug */ - sqlite3_free(pTab->apChange); - sqlite3_free(pTab); + sqlite3_finalize(pTab->pDfltStmt); + sessionFree(pSession, (char*)pTab->azCol); /* cast works around VC++ bug */ + sessionFree(pSession, pTab->apChange); + sessionFree(pSession, pTab); } } @@ -196780,11 +236193,11 @@ SQLITE_API void sqlite3session_delete(sqlite3_session *pSession){ sqlite3_mutex_leave(sqlite3_db_mutex(db)); sqlite3ValueFree(pSession->pZeroBlob); - /* Delete all attached table objects. And the contents of their + /* Delete all attached table objects. And the contents of their ** associated hash-tables. */ - sessionDeleteTable(pSession->pTable); + sessionDeleteTable(pSession, pSession->pTable); - /* Free the session object itself. */ + /* Free the session object. */ sqlite3_free(pSession); } @@ -196792,7 +236205,7 @@ SQLITE_API void sqlite3session_delete(sqlite3_session *pSession){ ** Set a table filter on a Session Object. */ SQLITE_API void sqlite3session_table_filter( - sqlite3_session *pSession, + sqlite3_session *pSession, int(*xFilter)(void*, const char*), void *pCtx /* First argument passed to xFilter */ ){ @@ -196831,12 +236244,13 @@ SQLITE_API int sqlite3session_attach( if( !pTab ){ /* Allocate new SessionTable object. */ - pTab = (SessionTable *)sqlite3_malloc64(sizeof(SessionTable) + nName + 1); + int nByte = sizeof(SessionTable) + nName + 1; + pTab = (SessionTable*)sessionMalloc64(pSession, nByte); if( !pTab ){ rc = SQLITE_NOMEM; }else{ /* Populate the new SessionTable object and link it into the list. - ** The new object must be linked onto the end of the list, not + ** The new object must be linked onto the end of the list, not ** simply added to the start of it in order to ensure that tables ** appear in the correct order when a changeset or patchset is ** eventually generated. */ @@ -196854,32 +236268,6 @@ SQLITE_API int sqlite3session_attach( return rc; } -/* -** Ensure that there is room in the buffer to append nByte bytes of data. -** If not, use sqlite3_realloc() to grow the buffer so that there is. -** -** If successful, return zero. Otherwise, if an OOM condition is encountered, -** set *pRc to SQLITE_NOMEM and return non-zero. -*/ -static int sessionBufferGrow(SessionBuffer *p, size_t nByte, int *pRc){ - if( *pRc==SQLITE_OK && p->nAlloc-p->nBufnAlloc ? p->nAlloc : 128; - do { - nNew = nNew*2; - }while( (nNew-p->nBuf)aBuf, nNew); - if( 0==aNew ){ - *pRc = SQLITE_NOMEM; - }else{ - p->aBuf = aNew; - p->nAlloc = nNew; - } - } - return (*pRc!=SQLITE_OK); -} - /* ** Append the value passed as the second argument to the buffer passed ** as the first. @@ -196904,8 +236292,8 @@ static void sessionAppendValue(SessionBuffer *p, sqlite3_value *pVal, int *pRc){ } /* -** This function is a no-op if *pRc is other than SQLITE_OK when it is -** called. Otherwise, append a single byte to the buffer. +** This function is a no-op if *pRc is other than SQLITE_OK when it is +** called. Otherwise, append a single byte to the buffer. ** ** If an OOM condition is encountered, set *pRc to SQLITE_NOMEM before ** returning. @@ -196917,8 +236305,8 @@ static void sessionAppendByte(SessionBuffer *p, u8 v, int *pRc){ } /* -** This function is a no-op if *pRc is other than SQLITE_OK when it is -** called. Otherwise, append a single varint to the buffer. +** This function is a no-op if *pRc is other than SQLITE_OK when it is +** called. Otherwise, append a single varint to the buffer. ** ** If an OOM condition is encountered, set *pRc to SQLITE_NOMEM before ** returning. @@ -196930,16 +236318,16 @@ static void sessionAppendVarint(SessionBuffer *p, int v, int *pRc){ } /* -** This function is a no-op if *pRc is other than SQLITE_OK when it is -** called. Otherwise, append a blob of data to the buffer. +** This function is a no-op if *pRc is other than SQLITE_OK when it is +** called. Otherwise, append a blob of data to the buffer. ** ** If an OOM condition is encountered, set *pRc to SQLITE_NOMEM before ** returning. */ static void sessionAppendBlob( - SessionBuffer *p, - const u8 *aBlob, - int nBlob, + SessionBuffer *p, + const u8 *aBlob, + int nBlob, int *pRc ){ if( nBlob>0 && 0==sessionBufferGrow(p, nBlob, pRc) ){ @@ -196949,27 +236337,7 @@ static void sessionAppendBlob( } /* -** This function is a no-op if *pRc is other than SQLITE_OK when it is -** called. Otherwise, append a string to the buffer. All bytes in the string -** up to (but not including) the nul-terminator are written to the buffer. -** -** If an OOM condition is encountered, set *pRc to SQLITE_NOMEM before -** returning. -*/ -static void sessionAppendStr( - SessionBuffer *p, - const char *zStr, - int *pRc -){ - int nStr = sqlite3Strlen30(zStr); - if( 0==sessionBufferGrow(p, nStr, pRc) ){ - memcpy(&p->aBuf[p->nBuf], zStr, nStr); - p->nBuf += nStr; - } -} - -/* -** This function is a no-op if *pRc is other than SQLITE_OK when it is +** This function is a no-op if *pRc is other than SQLITE_OK when it is ** called. Otherwise, append the string representation of integer iVal ** to the buffer. No nul-terminator is written. ** @@ -196987,9 +236355,9 @@ static void sessionAppendInteger( } /* -** This function is a no-op if *pRc is other than SQLITE_OK when it is +** This function is a no-op if *pRc is other than SQLITE_OK when it is ** called. Otherwise, append the string zStr enclosed in quotes (") and -** with any embedded quote characters escaped to the buffer. No +** with any embedded quote characters escaped to the buffer. No ** nul-terminator byte is written. ** ** If an OOM condition is encountered, set *pRc to SQLITE_NOMEM before @@ -197000,17 +236368,20 @@ static void sessionAppendIdent( const char *zStr, /* String to quote, escape and append */ int *pRc /* IN/OUT: Error code */ ){ - int nStr = sqlite3Strlen30(zStr)*2 + 2 + 1; + int nStr = sqlite3Strlen30(zStr)*2 + 2 + 2; if( 0==sessionBufferGrow(p, nStr, pRc) ){ char *zOut = (char *)&p->aBuf[p->nBuf]; const char *zIn = zStr; *zOut++ = '"'; - while( *zIn ){ - if( *zIn=='"' ) *zOut++ = '"'; - *zOut++ = *(zIn++); + if( zIn!=0 ){ + while( *zIn ){ + if( *zIn=='"' ) *zOut++ = '"'; + *zOut++ = *(zIn++); + } } *zOut++ = '"'; p->nBuf = (int)((u8 *)zOut - p->aBuf); + p->aBuf[p->nBuf] = 0x00; } } @@ -197030,15 +236401,14 @@ static void sessionAppendCol( int eType = sqlite3_column_type(pStmt, iCol); sessionAppendByte(p, (u8)eType, pRc); if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){ - sqlite3_int64 i; u8 aBuf[8]; if( eType==SQLITE_INTEGER ){ - i = sqlite3_column_int64(pStmt, iCol); + sqlite3_int64 i = sqlite3_column_int64(pStmt, iCol); + sessionPutI64(aBuf, i); }else{ double r = sqlite3_column_double(pStmt, iCol); - memcpy(&i, &r, 8); + sessionPutDouble(aBuf, r); } - sessionPutI64(aBuf, i); sessionAppendBlob(p, aBuf, 8, pRc); } if( eType==SQLITE_BLOB || eType==SQLITE_TEXT ){ @@ -197062,8 +236432,8 @@ static void sessionAppendCol( /* ** -** This function appends an update change to the buffer (see the comments -** under "CHANGESET FORMAT" at the top of the file). An update change +** This function appends an update change to the buffer (see the comments +** under "CHANGESET FORMAT" at the top of the file). An update change ** consists of: ** ** 1 byte: SQLITE_UPDATE (0x17) @@ -197078,10 +236448,10 @@ static void sessionAppendCol( ** If all of the old.* values are equal to their corresponding new.* value ** (i.e. nothing has changed), then no data at all is appended to the buffer. ** -** Otherwise, the old.* record contains all primary key values and the -** original values of any fields that have been modified. The new.* record +** Otherwise, the old.* record contains all primary key values and the +** original values of any fields that have been modified. The new.* record ** contains the new values of only those fields that have been modified. -*/ +*/ static int sessionAppendUpdate( SessionBuffer *pBuf, /* Buffer to append to */ int bPatchset, /* True for "patchset", 0 for "changeset" */ @@ -197096,6 +236466,7 @@ static int sessionAppendUpdate( int i; /* Used to iterate through columns */ u8 *pCsr = p->aRecord; /* Used to iterate through old.* values */ + assert( abPK!=0 ); sessionAppendByte(pBuf, SQLITE_UPDATE, &rc); sessionAppendByte(pBuf, p->bIndirect, &rc); for(i=0; i FROM zDb.zTab WHERE (pk1, pk2,...) IS (?1, ?2,...) +** +** where is: +** +** 1 AND (?A OR ?1 IS ) AND ... +** +** for each non-pk . */ static int sessionSelectStmt( sqlite3 *db, /* Database handle */ + int bIgnoreNoop, const char *zDb, /* Database name */ const char *zTab, /* Table name */ + int bRowid, int nCol, /* Number of columns in table */ const char **azCol, /* Names of table columns */ u8 *abPK, /* PRIMARY KEY array */ - sqlite3_stmt **ppStmt /* OUT: Prepared SELECT statement */ + sqlite3_stmt **ppStmt, /* OUT: Prepared SELECT statement */ + char **pzErrmsg /* OUT: Error message */ ){ int rc = SQLITE_OK; char *zSql = 0; - int nSql = -1; + const char *zSep = ""; + int i; + + SessionBuffer cols = {0, 0, 0}; + SessionBuffer nooptest = {0, 0, 0}; + SessionBuffer pkfield = {0, 0, 0}; + SessionBuffer pkvar = {0, 0, 0}; + + sessionAppendStr(&nooptest, ", 1", &rc); + if( 0==sqlite3_stricmp("sqlite_stat1", zTab) ){ + sessionAppendStr(&nooptest, " AND (?6 OR ?3 IS stat)", &rc); + sessionAppendStr(&pkfield, "tbl, idx", &rc); + sessionAppendStr(&pkvar, + "?1, (CASE WHEN ?2=X'' THEN NULL ELSE ?2 END)", &rc + ); + sessionAppendStr(&cols, "tbl, ?2, stat", &rc); + }else{ +#if 0 + if( bRowid ){ + sessionAppendStr(&cols, SESSIONS_ROWID, &rc); + } +#endif + for(i=0; idb; /* Source database handle */ SessionTable *pTab; /* Used to iterate through attached tables */ - SessionBuffer buf = {0,0,0}; /* Buffer in which to accumlate changeset */ + SessionBuffer buf = {0,0,0}; /* Buffer in which to accumulate changeset */ int rc; /* Return code */ - assert( xOutput==0 || (pnChangeset==0 && ppChangeset==0 ) ); + assert( xOutput==0 || (pnChangeset==0 && ppChangeset==0) ); + assert( xOutput!=0 || (pnChangeset!=0 && ppChangeset!=0) ); /* Zero the output variables in case an error occurs. If this session ** object is already in the error state (sqlite3_session.rc != SQLITE_OK), ** this call will be a no-op. */ if( xOutput==0 ){ + assert( pnChangeset!=0 && ppChangeset!=0 ); *pnChangeset = 0; *ppChangeset = 0; } if( pSession->rc ) return pSession->rc; - rc = sqlite3_exec(pSession->db, "SAVEPOINT changeset", 0, 0, 0); - if( rc!=SQLITE_OK ) return rc; sqlite3_mutex_enter(sqlite3_db_mutex(db)); + rc = sqlite3_exec(pSession->db, "SAVEPOINT changeset", 0, 0, 0); + if( rc!=SQLITE_OK ){ + sqlite3_mutex_leave(sqlite3_db_mutex(db)); + return rc; + } for(pTab=pSession->pTable; rc==SQLITE_OK && pTab; pTab=pTab->pNext){ if( pTab->nEntry ){ const char *zName = pTab->zName; - int nCol; /* Number of columns in table */ - u8 *abPK; /* Primary key array */ - const char **azCol = 0; /* Table columns */ int i; /* Used to iterate through hash buckets */ sqlite3_stmt *pSel = 0; /* SELECT statement to query table pTab */ int nRewind = buf.nBuf; /* Initial size of write buffer */ int nNoop; /* Size of buffer after writing tbl header */ + int nOldCol = pTab->nCol; /* Check the table schema is still Ok. */ - rc = sessionTableInfo(db, pSession->zDb, zName, &nCol, 0, &azCol, &abPK); - if( !rc && (pTab->nCol!=nCol || memcmp(abPK, pTab->abPK, nCol)) ){ - rc = SQLITE_SCHEMA; + rc = sessionReinitTable(pSession, pTab); + if( rc==SQLITE_OK && pTab->nCol!=nOldCol ){ + rc = sessionUpdateChanges(pSession, pTab); } /* Write a table header */ @@ -197438,8 +236886,9 @@ static int sessionGenerateChangeset( /* Build and compile a statement to execute: */ if( rc==SQLITE_OK ){ - rc = sessionSelectStmt( - db, pSession->zDb, zName, nCol, azCol, abPK, &pSel); + rc = sessionSelectStmt(db, 0, pSession->zDb, + zName, pTab->bRowid, pTab->nCol, pTab->azCol, pTab->abPK, &pSel, 0 + ); } nNoop = buf.nBuf; @@ -197447,21 +236896,22 @@ static int sessionGenerateChangeset( SessionChange *p; /* Used to iterate through changes */ for(p=pTab->apChange[i]; rc==SQLITE_OK && p; p=p->pNext){ - rc = sessionSelectBind(pSel, nCol, abPK, p); + rc = sessionSelectBind(pSel, pTab->nCol, pTab->abPK, p); if( rc!=SQLITE_OK ) continue; if( sqlite3_step(pSel)==SQLITE_ROW ){ if( p->op==SQLITE_INSERT ){ int iCol; sessionAppendByte(&buf, SQLITE_INSERT, &rc); sessionAppendByte(&buf, p->bIndirect, &rc); - for(iCol=0; iColnCol; iCol++){ sessionAppendCol(&buf, pSel, iCol, &rc); } }else{ - rc = sessionAppendUpdate(&buf, bPatchset, pSel, p, abPK); + assert( pTab->abPK!=0 ); + rc = sessionAppendUpdate(&buf, bPatchset, pSel, p, pTab->abPK); } }else if( p->op!=SQLITE_INSERT ){ - rc = sessionAppendDelete(&buf, bPatchset, p, nCol, abPK); + rc = sessionAppendDelete(&buf, bPatchset, p, pTab->nCol,pTab->abPK); } if( rc==SQLITE_OK ){ rc = sqlite3_reset(pSel); @@ -197469,10 +236919,10 @@ static int sessionGenerateChangeset( /* If the buffer is now larger than sessions_strm_chunk_size, pass ** its contents to the xOutput() callback. */ - if( xOutput - && rc==SQLITE_OK - && buf.nBuf>nNoop - && buf.nBuf>sessions_strm_chunk_size + if( xOutput + && rc==SQLITE_OK + && buf.nBuf>nNoop + && buf.nBuf>sessions_strm_chunk_size ){ rc = xOutput(pOut, (void*)buf.aBuf, buf.nBuf); nNoop = -1; @@ -197486,7 +236936,6 @@ static int sessionGenerateChangeset( if( buf.nBuf==nNoop ){ buf.nBuf = nRewind; } - sqlite3_free((char*)azCol); /* cast works around VC++ bug */ } } @@ -197507,10 +236956,10 @@ static int sessionGenerateChangeset( } /* -** Obtain a changeset object containing all changes recorded by the +** Obtain a changeset object containing all changes recorded by the ** session object passed as the first argument. ** -** It is the responsibility of the caller to eventually free the buffer +** It is the responsibility of the caller to eventually free the buffer ** using sqlite3_free(). */ SQLITE_API int sqlite3session_changeset( @@ -197518,7 +236967,14 @@ SQLITE_API int sqlite3session_changeset( int *pnChangeset, /* OUT: Size of buffer at *ppChangeset */ void **ppChangeset /* OUT: Buffer containing changeset */ ){ - return sessionGenerateChangeset(pSession, 0, 0, 0, pnChangeset, ppChangeset); + int rc; + + if( pnChangeset==0 || ppChangeset==0 ) return SQLITE_MISUSE; + rc = sessionGenerateChangeset(pSession, 0, 0, 0, pnChangeset, ppChangeset); + assert( rc || pnChangeset==0 + || pSession->bEnableSize==0 || *pnChangeset<=pSession->nMaxChangesetSize + ); + return rc; } /* @@ -197529,6 +236985,7 @@ SQLITE_API int sqlite3session_changeset_strm( int (*xOutput)(void *pOut, const void *pData, int nData), void *pOut ){ + if( xOutput==0 ) return SQLITE_MISUSE; return sessionGenerateChangeset(pSession, 0, xOutput, pOut, 0, 0); } @@ -197540,14 +236997,15 @@ SQLITE_API int sqlite3session_patchset_strm( int (*xOutput)(void *pOut, const void *pData, int nData), void *pOut ){ + if( xOutput==0 ) return SQLITE_MISUSE; return sessionGenerateChangeset(pSession, 1, xOutput, pOut, 0, 0); } /* -** Obtain a patchset object containing all changes recorded by the +** Obtain a patchset object containing all changes recorded by the ** session object passed as the first argument. ** -** It is the responsibility of the caller to eventually free the buffer +** It is the responsibility of the caller to eventually free the buffer ** using sqlite3_free(). */ SQLITE_API int sqlite3session_patchset( @@ -197555,6 +237013,7 @@ SQLITE_API int sqlite3session_patchset( int *pnPatchset, /* OUT: Size of buffer at *ppChangeset */ void **ppPatchset /* OUT: Buffer containing changeset */ ){ + if( pnPatchset==0 || ppPatchset==0 ) return SQLITE_MISUSE; return sessionGenerateChangeset(pSession, 1, 0, 0, pnPatchset, ppPatchset); } @@ -197603,6 +237062,59 @@ SQLITE_API int sqlite3session_isempty(sqlite3_session *pSession){ return (ret==0); } +/* +** Return the amount of heap memory in use. +*/ +SQLITE_API sqlite3_int64 sqlite3session_memory_used(sqlite3_session *pSession){ + return pSession->nMalloc; +} + +/* +** Configure the session object passed as the first argument. +*/ +SQLITE_API int sqlite3session_object_config(sqlite3_session *pSession, int op, void *pArg){ + int rc = SQLITE_OK; + switch( op ){ + case SQLITE_SESSION_OBJCONFIG_SIZE: { + int iArg = *(int*)pArg; + if( iArg>=0 ){ + if( pSession->pTable ){ + rc = SQLITE_MISUSE; + }else{ + pSession->bEnableSize = (iArg!=0); + } + } + *(int*)pArg = pSession->bEnableSize; + break; + } + + case SQLITE_SESSION_OBJCONFIG_ROWID: { + int iArg = *(int*)pArg; + if( iArg>=0 ){ + if( pSession->pTable ){ + rc = SQLITE_MISUSE; + }else{ + pSession->bImplicitPK = (iArg!=0); + } + } + *(int*)pArg = pSession->bImplicitPK; + break; + } + + default: + rc = SQLITE_MISUSE; + } + + return rc; +} + +/* +** Return the maximum size of sqlite3session_changeset() output. +*/ +SQLITE_API sqlite3_int64 sqlite3session_changeset_size(sqlite3_session *pSession){ + return pSession->nMaxChangesetSize; +} + /* ** Do the work for either sqlite3changeset_start() or start_strm(). */ @@ -197612,7 +237124,8 @@ static int sessionChangesetStart( void *pIn, int nChangeset, /* Size of buffer pChangeset in bytes */ void *pChangeset, /* Pointer to buffer containing changeset */ - int bInvert /* True to invert changeset */ + int bInvert, /* True to invert changeset */ + int bSkipEmpty /* True to skip empty UPDATE changes */ ){ sqlite3_changeset_iter *pRet; /* Iterator to return */ int nByte; /* Number of bytes to allocate for iterator */ @@ -197633,6 +237146,7 @@ static int sessionChangesetStart( pRet->in.pIn = pIn; pRet->in.bEof = (xInput ? 0 : 1); pRet->bInvert = bInvert; + pRet->bSkipEmpty = bSkipEmpty; /* Populate the output variable and return success. */ *pp = pRet; @@ -197647,7 +237161,7 @@ SQLITE_API int sqlite3changeset_start( int nChangeset, /* Size of buffer pChangeset in bytes */ void *pChangeset /* Pointer to buffer containing changeset */ ){ - return sessionChangesetStart(pp, 0, 0, nChangeset, pChangeset, 0); + return sessionChangesetStart(pp, 0, 0, nChangeset, pChangeset, 0, 0); } SQLITE_API int sqlite3changeset_start_v2( sqlite3_changeset_iter **pp, /* OUT: Changeset iterator handle */ @@ -197656,7 +237170,7 @@ SQLITE_API int sqlite3changeset_start_v2( int flags ){ int bInvert = !!(flags & SQLITE_CHANGESETSTART_INVERT); - return sessionChangesetStart(pp, 0, 0, nChangeset, pChangeset, bInvert); + return sessionChangesetStart(pp, 0, 0, nChangeset, pChangeset, bInvert, 0); } /* @@ -197667,7 +237181,7 @@ SQLITE_API int sqlite3changeset_start_strm( int (*xInput)(void *pIn, void *pData, int *pnData), void *pIn ){ - return sessionChangesetStart(pp, xInput, pIn, 0, 0, 0); + return sessionChangesetStart(pp, xInput, pIn, 0, 0, 0, 0); } SQLITE_API int sqlite3changeset_start_v2_strm( sqlite3_changeset_iter **pp, /* OUT: Changeset iterator handle */ @@ -197676,7 +237190,7 @@ SQLITE_API int sqlite3changeset_start_v2_strm( int flags ){ int bInvert = !!(flags & SQLITE_CHANGESETSTART_INVERT); - return sessionChangesetStart(pp, xInput, pIn, 0, 0, bInvert); + return sessionChangesetStart(pp, xInput, pIn, 0, 0, bInvert, 0); } /* @@ -197684,14 +237198,15 @@ SQLITE_API int sqlite3changeset_start_v2_strm( ** object and the buffer is full, discard some data to free up space. */ static void sessionDiscardData(SessionInput *pIn){ - if( pIn->xInput && pIn->iNext>=sessions_strm_chunk_size ){ - int nMove = pIn->buf.nBuf - pIn->iNext; + if( pIn->xInput && pIn->iCurrent>=sessions_strm_chunk_size ){ + int nMove = pIn->buf.nBuf - pIn->iCurrent; assert( nMove>=0 ); if( nMove>0 ){ - memmove(pIn->buf.aBuf, &pIn->buf.aBuf[pIn->iNext], nMove); + memmove(pIn->buf.aBuf, &pIn->buf.aBuf[pIn->iCurrent], nMove); } - pIn->buf.nBuf -= pIn->iNext; - pIn->iNext = 0; + pIn->buf.nBuf -= pIn->iCurrent; + pIn->iNext -= pIn->iCurrent; + pIn->iCurrent = 0; pIn->nData = pIn->buf.nBuf; } } @@ -197753,7 +237268,7 @@ static void sessionSkipRecord( /* ** This function sets the value of the sqlite3_value object passed as the -** first argument to a copy of the string or blob held in the aData[] +** first argument to a copy of the string or blob held in the aData[] ** buffer. SQLITE_OK is returned if successful, or SQLITE_NOMEM if an OOM ** error occurs. */ @@ -197764,7 +237279,7 @@ static int sessionValueSetStr( u8 enc /* String encoding (0 for blobs) */ ){ /* In theory this code could just pass SQLITE_TRANSIENT as the final - ** argument to sqlite3ValueSetStr() and have the copy created + ** argument to sqlite3ValueSetStr() and have the copy created ** automatically. But doing so makes it difficult to detect any OOM ** error. Hence the code to create the copy externally. */ u8 *aCopy = sqlite3_malloc64((sqlite3_int64)nData+1); @@ -197802,11 +237317,14 @@ static int sessionReadRecord( SessionInput *pIn, /* Input data */ int nCol, /* Number of values in record */ u8 *abPK, /* Array of primary key flags, or NULL */ - sqlite3_value **apOut /* Write values to this array */ + sqlite3_value **apOut, /* Write values to this array */ + int *pbEmpty ){ int i; /* Used to iterate through columns */ int rc = SQLITE_OK; + assert( pbEmpty==0 || *pbEmpty==0 ); + if( pbEmpty ) *pbEmpty = 1; for(i=0; iaData[pIn->iNext++]; assert( apOut[i]==0 ); if( eType ){ + if( pbEmpty ) *pbEmpty = 0; apOut[i] = sqlite3ValueNew(0); if( !apOut[i] ) rc = SQLITE_NOMEM; } @@ -197828,7 +237347,8 @@ static int sessionReadRecord( u8 *aVal = &pIn->aData[pIn->iNext]; if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){ int nByte; - pIn->iNext += sessionVarintGet(aVal, &nByte); + int nRem = pIn->nData - pIn->iNext; + pIn->iNext += sessionVarintGetSafe(aVal, nRem, &nByte); rc = sessionInputBuffer(pIn, nByte); if( rc==SQLITE_OK ){ if( nByte<0 || nByte>pIn->nData-pIn->iNext ){ @@ -197841,15 +237361,19 @@ static int sessionReadRecord( } } if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){ - sqlite3_int64 v = sessionGetI64(aVal); - if( eType==SQLITE_INTEGER ){ - sqlite3VdbeMemSetInt64(apOut[i], v); + if( (pIn->nData-pIn->iNext)<8 ){ + rc = SQLITE_CORRUPT_BKPT; }else{ - double d; - memcpy(&d, &v, 8); - sqlite3VdbeMemSetDouble(apOut[i], d); + sqlite3_int64 v = sessionGetI64(aVal); + if( eType==SQLITE_INTEGER ){ + sqlite3VdbeMemSetInt64(apOut[i], v); + }else{ + double d; + memcpy(&d, &v, 8); + sqlite3VdbeMemSetDouble(apOut[i], d); + } + pIn->iNext += 8; } - pIn->iNext += 8; } } } @@ -197865,7 +237389,7 @@ static int sessionReadRecord( ** + array of PK flags (1 byte per column), ** + table name (nul terminated). ** -** This function ensures that all of the above is present in the input +** This function ensures that all of the above is present in the input ** buffer (i.e. that it can be accessed without any calls to xInput()). ** If successful, SQLITE_OK is returned. Otherwise, an SQLite error code. ** The input pointer is not moved. @@ -197877,13 +237401,14 @@ static int sessionChangesetBufferTblhdr(SessionInput *pIn, int *pnByte){ rc = sessionInputBuffer(pIn, 9); if( rc==SQLITE_OK ){ - nRead += sessionVarintGet(&pIn->aData[pIn->iNext + nRead], &nCol); + int nBuf = pIn->nData - pIn->iNext; + nRead += sessionVarintGetSafe(&pIn->aData[pIn->iNext], nBuf, &nCol); /* The hard upper limit for the number of columns in an SQLite - ** database table is, according to sqliteLimit.h, 32676. So - ** consider any table-header that purports to have more than 65536 - ** columns to be corrupt. This is convenient because otherwise, - ** if the (nCol>65536) condition below were omitted, a sufficiently - ** large value for nCol may cause nRead to wrap around and become + ** database table is, according to sqliteLimit.h, 32676. So + ** consider any table-header that purports to have more than 65536 + ** columns to be corrupt. This is convenient because otherwise, + ** if the (nCol>65536) condition below were omitted, a sufficiently + ** large value for nCol may cause nRead to wrap around and become ** negative. Leading to a crash. */ if( nCol<0 || nCol>65536 ){ rc = SQLITE_CORRUPT_BKPT; @@ -197897,8 +237422,15 @@ static int sessionChangesetBufferTblhdr(SessionInput *pIn, int *pnByte){ while( (pIn->iNext + nRead)nData && pIn->aData[pIn->iNext + nRead] ){ nRead++; } + + /* Break out of the loop if if the nul-terminator byte has been found. + ** Otherwise, read some more input data and keep seeking. If there is + ** no more input data, consider the changeset corrupt. */ if( (pIn->iNext + nRead)nData ) break; rc = sessionInputBuffer(pIn, nRead + 100); + if( rc==SQLITE_OK && (pIn->iNext + nRead)>=pIn->nData ){ + rc = SQLITE_CORRUPT_BKPT; + } } *pnByte = nRead+1; return rc; @@ -197919,7 +237451,7 @@ static int sessionChangesetBufferRecord( int *pnByte /* OUT: Size of record in bytes */ ){ int rc = SQLITE_OK; - int nByte = 0; + i64 nByte = 0; int i; for(i=0; rc==SQLITE_OK && iaData[pIn->iNext + nByte++]; if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){ int n; - nByte += sessionVarintGet(&pIn->aData[pIn->iNext+nByte], &n); + int nRem = pIn->nData - (pIn->iNext + nByte); + nByte += sessionVarintGetSafe(&pIn->aData[pIn->iNext+nByte], nRem, &n); nByte += n; rc = sessionInputBuffer(pIn, nByte); }else if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){ nByte += 8; + }else if( eType!=0 && eType!=SQLITE_NULL ){ + rc = SQLITE_CORRUPT_BKPT; } } + if( rc==SQLITE_OK && (pIn->iNext+nByte)>pIn->nData ){ + rc = SQLITE_CORRUPT_BKPT; + } } *pnByte = nByte; return rc; @@ -197948,8 +237486,8 @@ static int sessionChangesetBufferRecord( ** + array of PK flags (1 byte per column), ** + table name (nul terminated). ** -** This function decodes the table-header and populates the p->nCol, -** p->zTab and p->abPK[] variables accordingly. The p->apValue[] array is +** This function decodes the table-header and populates the p->nCol, +** p->zTab and p->abPK[] variables accordingly. The p->apValue[] array is ** also allocated or resized according to the new value of p->nCol. The ** input pointer is left pointing to the byte following the table header. ** @@ -197986,37 +237524,38 @@ static int sessionChangesetReadTblhdr(sqlite3_changeset_iter *p){ } p->apValue = (sqlite3_value**)p->tblhdr.aBuf; - p->abPK = (u8*)&p->apValue[p->nCol*2]; - p->zTab = (char*)&p->abPK[p->nCol]; + if( p->apValue==0 ){ + p->abPK = 0; + p->zTab = 0; + }else{ + p->abPK = (u8*)&p->apValue[p->nCol*2]; + p->zTab = p->abPK ? (char*)&p->abPK[p->nCol] : 0; + } return (p->rc = rc); } /* -** Advance the changeset iterator to the next change. +** Advance the changeset iterator to the next change. The differences between +** this function and sessionChangesetNext() are that ** -** If both paRec and pnRec are NULL, then this function works like the public -** API sqlite3changeset_next(). If SQLITE_ROW is returned, then the -** sqlite3changeset_new() and old() APIs may be used to query for values. +** * If pbEmpty is not NULL and the change is a no-op UPDATE (an UPDATE +** that modifies no columns), this function sets (*pbEmpty) to 1. ** -** Otherwise, if paRec and pnRec are not NULL, then a pointer to the change -** record is written to *paRec before returning and the number of bytes in -** the record to *pnRec. -** -** Either way, this function returns SQLITE_ROW if the iterator is -** successfully advanced to the next change in the changeset, an SQLite -** error code if an error occurs, or SQLITE_DONE if there are no further -** changes in the changeset. +** * If the iterator is configured to skip no-op UPDATEs, +** sessionChangesetNext() does that. This function does not. */ -static int sessionChangesetNext( +static int sessionChangesetNextOne( sqlite3_changeset_iter *p, /* Changeset iterator */ u8 **paRec, /* If non-NULL, store record pointer here */ int *pnRec, /* If non-NULL, store size of record here */ - int *pbNew /* If non-NULL, true if new table */ + int *pbNew, /* If non-NULL, true if new table */ + int *pbEmpty ){ int i; u8 op; assert( (paRec==0 && pnRec==0) || (paRec && pnRec) ); + assert( pbEmpty==0 || *pbEmpty==0 ); /* If the iterator is in the error-state, return immediately. */ if( p->rc!=SQLITE_OK ) return p->rc; @@ -198029,21 +237568,21 @@ static int sessionChangesetNext( memset(p->apValue, 0, sizeof(sqlite3_value*)*p->nCol*2); } - /* Make sure the buffer contains at least 10 bytes of input data, or all - ** remaining data if there are less than 10 bytes available. This is - ** sufficient either for the 'T' or 'P' byte and the varint that follows - ** it, or for the two single byte values otherwise. */ + /* Make sure the buffer contains at least 2 bytes of input data, or all + ** remaining data if there are less than 2 bytes available. This is + ** sufficient either for the 'T' or 'P' byte that begins a new table, + ** or for the "op" and "bIndirect" single bytes otherwise. */ p->rc = sessionInputBuffer(&p->in, 2); if( p->rc!=SQLITE_OK ) return p->rc; + p->in.iCurrent = p->in.iNext; + sessionDiscardData(&p->in); + /* If the iterator is already at the end of the changeset, return DONE. */ if( p->in.iNext>=p->in.nData ){ return SQLITE_DONE; } - sessionDiscardData(&p->in); - p->in.iCurrent = p->in.iNext; - op = p->in.aData[p->in.iNext++]; while( op=='T' || op=='P' ){ if( pbNew ) *pbNew = 1; @@ -198062,13 +237601,15 @@ static int sessionChangesetNext( return (p->rc = SQLITE_CORRUPT_BKPT); } - p->op = op; - p->bIndirect = p->in.aData[p->in.iNext++]; - if( p->op!=SQLITE_UPDATE && p->op!=SQLITE_DELETE && p->op!=SQLITE_INSERT ){ + if( (op!=SQLITE_UPDATE && op!=SQLITE_DELETE && op!=SQLITE_INSERT) + || (p->in.iNext>=p->in.nData) + ){ return (p->rc = SQLITE_CORRUPT_BKPT); } + p->op = op; + p->bIndirect = p->in.aData[p->in.iNext++]; - if( paRec ){ + if( paRec ){ int nVal; /* Number of values to buffer */ if( p->bPatchset==0 && op==SQLITE_UPDATE ){ nVal = p->nCol * 2; @@ -198089,13 +237630,13 @@ static int sessionChangesetNext( /* If this is an UPDATE or DELETE, read the old.* record. */ if( p->op!=SQLITE_INSERT && (p->bPatchset==0 || p->op==SQLITE_DELETE) ){ u8 *abPK = p->bPatchset ? p->abPK : 0; - p->rc = sessionReadRecord(&p->in, p->nCol, abPK, apOld); + p->rc = sessionReadRecord(&p->in, p->nCol, abPK, apOld, 0); if( p->rc!=SQLITE_OK ) return p->rc; } /* If this is an INSERT or UPDATE, read the new.* record. */ if( p->op!=SQLITE_DELETE ){ - p->rc = sessionReadRecord(&p->in, p->nCol, 0, apNew); + p->rc = sessionReadRecord(&p->in, p->nCol, 0, apNew, pbEmpty); if( p->rc!=SQLITE_OK ) return p->rc; } @@ -198117,11 +237658,58 @@ static int sessionChangesetNext( if( p->op==SQLITE_INSERT ) p->op = SQLITE_DELETE; else if( p->op==SQLITE_DELETE ) p->op = SQLITE_INSERT; } + + /* If this is an UPDATE that is part of a changeset, then check that + ** there are no fields in the old.* record that are not (a) PK fields, + ** or (b) also present in the new.* record. + ** + ** Such records are technically corrupt, but the rebaser was at one + ** point generating them. Under most circumstances this is benign, but + ** can cause spurious SQLITE_RANGE errors when applying the changeset. */ + if( p->bPatchset==0 && p->op==SQLITE_UPDATE){ + for(i=0; inCol; i++){ + if( p->abPK[i]==0 && p->apValue[i+p->nCol]==0 ){ + sqlite3ValueFree(p->apValue[i]); + p->apValue[i] = 0; + } + } + } } return SQLITE_ROW; } +/* +** Advance the changeset iterator to the next change. +** +** If both paRec and pnRec are NULL, then this function works like the public +** API sqlite3changeset_next(). If SQLITE_ROW is returned, then the +** sqlite3changeset_new() and old() APIs may be used to query for values. +** +** Otherwise, if paRec and pnRec are not NULL, then a pointer to the change +** record is written to *paRec before returning and the number of bytes in +** the record to *pnRec. +** +** Either way, this function returns SQLITE_ROW if the iterator is +** successfully advanced to the next change in the changeset, an SQLite +** error code if an error occurs, or SQLITE_DONE if there are no further +** changes in the changeset. +*/ +static int sessionChangesetNext( + sqlite3_changeset_iter *p, /* Changeset iterator */ + u8 **paRec, /* If non-NULL, store record pointer here */ + int *pnRec, /* If non-NULL, store size of record here */ + int *pbNew /* If non-NULL, true if new table */ +){ + int bEmpty; + int rc; + do { + bEmpty = 0; + rc = sessionChangesetNextOne(p, paRec, pnRec, pbNew, &bEmpty); + }while( rc==SQLITE_ROW && p->bSkipEmpty && bEmpty); + return rc; +} + /* ** Advance an iterator created by sqlite3changeset_start() to the next ** change in the changeset. This function may return SQLITE_ROW, SQLITE_DONE @@ -198235,7 +237823,7 @@ SQLITE_API int sqlite3changeset_new( /* ** This function may only be called with a changeset iterator that has been -** passed to an SQLITE_CHANGESET_DATA or SQLITE_CHANGESET_CONFLICT +** passed to an SQLITE_CHANGESET_DATA or SQLITE_CHANGESET_CONFLICT ** conflict-handler function. Otherwise, SQLITE_MISUSE is returned. ** ** If successful, *ppValue is set to point to an sqlite3_value structure @@ -198328,7 +237916,13 @@ static int sessionChangesetInvert( /* Test for EOF. */ if( (rc = sessionInputBuffer(pInput, 2)) ) goto finished_invert; - if( pInput->iNext>=pInput->nData ) break; + if( pInput->iNext+1>=pInput->nData ){ + if( pInput->iNext!=pInput->nData ){ + rc = SQLITE_CORRUPT_BKPT; + goto finished_invert; + } + break; + } eType = pInput->aData[pInput->iNext]; switch( eType ){ @@ -198394,9 +237988,9 @@ static int sessionChangesetInvert( /* Read the old.* and new.* records for the update change. */ pInput->iNext += 2; - rc = sessionReadRecord(pInput, nCol, 0, &apVal[0]); + rc = sessionReadRecord(pInput, nCol, 0, &apVal[0], 0); if( rc==SQLITE_OK ){ - rc = sessionReadRecord(pInput, nCol, 0, &apVal[nCol]); + rc = sessionReadRecord(pInput, nCol, 0, &apVal[nCol], 0); } /* Write the new old.* record. Consists of the PK columns from the @@ -198440,11 +238034,11 @@ static int sessionChangesetInvert( } assert( rc==SQLITE_OK ); - if( pnInverted ){ + if( pnInverted && ALWAYS(ppInverted) ){ *pnInverted = sOut.nBuf; *ppInverted = sOut.aBuf; sOut.aBuf = 0; - }else if( sOut.nBuf>0 ){ + }else if( sOut.nBuf>0 && ALWAYS(xOutput!=0) ){ rc = xOutput(pOut, sOut.aBuf, sOut.nBuf); } @@ -198497,24 +238091,199 @@ SQLITE_API int sqlite3changeset_invert_strm( return rc; } + +typedef struct SessionUpdate SessionUpdate; +struct SessionUpdate { + sqlite3_stmt *pStmt; + u32 *aMask; + SessionUpdate *pNext; +}; + typedef struct SessionApplyCtx SessionApplyCtx; struct SessionApplyCtx { sqlite3 *db; sqlite3_stmt *pDelete; /* DELETE statement */ - sqlite3_stmt *pUpdate; /* UPDATE statement */ sqlite3_stmt *pInsert; /* INSERT statement */ sqlite3_stmt *pSelect; /* SELECT statement */ int nCol; /* Size of azCol[] and abPK[] arrays */ const char **azCol; /* Array of column names */ u8 *abPK; /* Boolean array - true if column is in PK */ + u32 *aUpdateMask; /* Used by sessionUpdateFind */ + SessionUpdate *pUp; int bStat1; /* True if table is sqlite_stat1 */ int bDeferConstraints; /* True to defer constraints */ + int bInvertConstraints; /* Invert when iterating constraints buffer */ SessionBuffer constraints; /* Deferred constraints are stored here */ SessionBuffer rebase; /* Rebase information (if any) here */ u8 bRebaseStarted; /* If table header is already in rebase */ u8 bRebase; /* True to collect rebase information */ + u8 bIgnoreNoop; /* True to ignore no-op conflicts */ + u8 bNoUpdateLoop; /* No update-loop processing */ + int bRowid; + char *zErr; /* Error message, if any */ }; +/* Number of prepared UPDATE statements to cache. */ +#define SESSION_UPDATE_CACHE_SZ 12 + +/* +** Find a prepared UPDATE statement suitable for the UPDATE step currently +** being visited by the iterator. The UPDATE is of the form: +** +** UPDATE tbl SET col = ?, col2 = ? WHERE pk1 IS ? AND pk2 IS ? +*/ +static int sessionUpdateFind( + sqlite3_changeset_iter *pIter, + SessionApplyCtx *p, + int bPatchset, + sqlite3_stmt **ppStmt +){ + int rc = SQLITE_OK; + SessionUpdate *pUp = 0; + int nCol = pIter->nCol; + int nU32 = (pIter->nCol+33)/32; + int ii; + + if( p->aUpdateMask==0 ){ + p->aUpdateMask = sqlite3_malloc(nU32*sizeof(u32)); + if( p->aUpdateMask==0 ){ + rc = SQLITE_NOMEM; + } + } + + if( rc==SQLITE_OK ){ + memset(p->aUpdateMask, 0, nU32*sizeof(u32)); + rc = SQLITE_CORRUPT; + for(ii=0; iinCol; ii++){ + if( sessionChangesetNew(pIter, ii) ){ + p->aUpdateMask[ii/32] |= (1<<(ii%32)); + rc = SQLITE_OK; + } + } + } + + if( rc==SQLITE_OK ){ + if( bPatchset ) p->aUpdateMask[nCol/32] |= (1<<(nCol%32)); + + if( p->pUp ){ + int nUp = 0; + SessionUpdate **pp = &p->pUp; + while( 1 ){ + nUp++; + if( 0==memcmp(p->aUpdateMask, (*pp)->aMask, nU32*sizeof(u32)) ){ + pUp = *pp; + *pp = pUp->pNext; + pUp->pNext = p->pUp; + p->pUp = pUp; + break; + } + + if( (*pp)->pNext ){ + pp = &(*pp)->pNext; + }else{ + if( nUp>=SESSION_UPDATE_CACHE_SZ ){ + sqlite3_finalize((*pp)->pStmt); + sqlite3_free(*pp); + *pp = 0; + } + break; + } + } + } + + if( pUp==0 ){ + int nByte = sizeof(SessionUpdate) * nU32*sizeof(u32); + int bStat1 = (sqlite3_stricmp(pIter->zTab, "sqlite_stat1")==0); + pUp = (SessionUpdate*)sqlite3_malloc(nByte); + if( pUp==0 ){ + rc = SQLITE_NOMEM; + }else{ + const char *zSep = ""; + SessionBuffer buf; + + memset(&buf, 0, sizeof(buf)); + pUp->aMask = (u32*)&pUp[1]; + memcpy(pUp->aMask, p->aUpdateMask, nU32*sizeof(u32)); + + sessionAppendStr(&buf, "UPDATE main.", &rc); + sessionAppendIdent(&buf, pIter->zTab, &rc); + sessionAppendStr(&buf, " SET ", &rc); + + /* Create the assignments part of the UPDATE */ + for(ii=0; iinCol; ii++){ + if( p->abPK[ii]==0 && sessionChangesetNew(pIter, ii) ){ + sessionAppendStr(&buf, zSep, &rc); + sessionAppendIdent(&buf, p->azCol[ii], &rc); + sessionAppendStr(&buf, " = ?", &rc); + sessionAppendInteger(&buf, ii*2+1, &rc); + zSep = ", "; + } + } + + /* Create the WHERE clause part of the UPDATE */ + zSep = ""; + sessionAppendStr(&buf, " WHERE ", &rc); + for(ii=0; iinCol; ii++){ + if( p->abPK[ii] || (bPatchset==0 && sessionChangesetOld(pIter, ii)) ){ + sessionAppendStr(&buf, zSep, &rc); + if( bStat1 && ii==1 ){ + assert( sqlite3_stricmp(p->azCol[ii], "idx")==0 ); + sessionAppendStr(&buf, + "idx IS CASE " + "WHEN length(?4)=0 AND typeof(?4)='blob' THEN NULL " + "ELSE ?4 END ", &rc + ); + }else{ + sessionAppendIdent(&buf, p->azCol[ii], &rc); + sessionAppendStr(&buf, " IS ?", &rc); + sessionAppendInteger(&buf, ii*2+2, &rc); + } + zSep = " AND "; + } + } + + if( rc==SQLITE_OK ){ + char *zSql = (char*)buf.aBuf; + rc = sqlite3_prepare_v2(p->db, zSql, buf.nBuf, &pUp->pStmt, 0); + } + + if( rc!=SQLITE_OK ){ + sqlite3_free(pUp); + pUp = 0; + }else{ + pUp->pNext = p->pUp; + p->pUp = pUp; + } + sqlite3_free(buf.aBuf); + } + } + } + + assert( (rc==SQLITE_OK)==(pUp!=0) ); + if( pUp ){ + *ppStmt = pUp->pStmt; + }else{ + *ppStmt = 0; + } + return rc; +} + +/* +** Free all cached UPDATE statements. +*/ +static void sessionUpdateFree(SessionApplyCtx *p){ + SessionUpdate *pUp; + SessionUpdate *pNext; + for(pUp=p->pUp; pUp; pUp=pNext){ + pNext = pUp->pNext; + sqlite3_finalize(pUp->pStmt); + sqlite3_free(pUp); + } + p->pUp = 0; + sqlite3_free(p->aUpdateMask); + p->aUpdateMask = 0; +} + /* ** Formulate a statement to DELETE a row from database db. Assuming a table ** structure like this: @@ -198543,7 +238312,7 @@ static int sessionDeleteRow( SessionBuffer buf = {0, 0, 0}; int nPk = 0; - sessionAppendStr(&buf, "DELETE FROM ", &rc); + sessionAppendStr(&buf, "DELETE FROM main.", &rc); sessionAppendIdent(&buf, zTab, &rc); sessionAppendStr(&buf, " WHERE ", &rc); @@ -198577,110 +238346,13 @@ static int sessionDeleteRow( } if( rc==SQLITE_OK ){ - rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pDelete, 0); - } - sqlite3_free(buf.aBuf); - - return rc; -} - -/* -** Formulate and prepare a statement to UPDATE a row from database db. -** Assuming a table structure like this: -** -** CREATE TABLE x(a, b, c, d, PRIMARY KEY(a, c)); -** -** The UPDATE statement looks like this: -** -** UPDATE x SET -** a = CASE WHEN ?2 THEN ?3 ELSE a END, -** b = CASE WHEN ?5 THEN ?6 ELSE b END, -** c = CASE WHEN ?8 THEN ?9 ELSE c END, -** d = CASE WHEN ?11 THEN ?12 ELSE d END -** WHERE a = ?1 AND c = ?7 AND (?13 OR -** (?5==0 OR b IS ?4) AND (?11==0 OR d IS ?10) AND -** ) -** -** For each column in the table, there are three variables to bind: -** -** ?(i*3+1) The old.* value of the column, if any. -** ?(i*3+2) A boolean flag indicating that the value is being modified. -** ?(i*3+3) The new.* value of the column, if any. -** -** Also, a boolean flag that, if set to true, causes the statement to update -** a row even if the non-PK values do not match. This is required if the -** conflict-handler is invoked with CHANGESET_DATA and returns -** CHANGESET_REPLACE. This is variable "?(nCol*3+1)". -** -** If successful, SQLITE_OK is returned and SessionApplyCtx.pUpdate is left -** pointing to the prepared version of the SQL statement. -*/ -static int sessionUpdateRow( - sqlite3 *db, /* Database handle */ - const char *zTab, /* Table name */ - SessionApplyCtx *p /* Session changeset-apply context */ -){ - int rc = SQLITE_OK; - int i; - const char *zSep = ""; - SessionBuffer buf = {0, 0, 0}; - - /* Append "UPDATE tbl SET " */ - sessionAppendStr(&buf, "UPDATE ", &rc); - sessionAppendIdent(&buf, zTab, &rc); - sessionAppendStr(&buf, " SET ", &rc); - - /* Append the assignments */ - for(i=0; inCol; i++){ - sessionAppendStr(&buf, zSep, &rc); - sessionAppendIdent(&buf, p->azCol[i], &rc); - sessionAppendStr(&buf, " = CASE WHEN ?", &rc); - sessionAppendInteger(&buf, i*3+2, &rc); - sessionAppendStr(&buf, " THEN ?", &rc); - sessionAppendInteger(&buf, i*3+3, &rc); - sessionAppendStr(&buf, " ELSE ", &rc); - sessionAppendIdent(&buf, p->azCol[i], &rc); - sessionAppendStr(&buf, " END", &rc); - zSep = ", "; - } - - /* Append the PK part of the WHERE clause */ - sessionAppendStr(&buf, " WHERE ", &rc); - for(i=0; inCol; i++){ - if( p->abPK[i] ){ - sessionAppendIdent(&buf, p->azCol[i], &rc); - sessionAppendStr(&buf, " = ?", &rc); - sessionAppendInteger(&buf, i*3+1, &rc); - sessionAppendStr(&buf, " AND ", &rc); - } - } - - /* Append the non-PK part of the WHERE clause */ - sessionAppendStr(&buf, " (?", &rc); - sessionAppendInteger(&buf, p->nCol*3+1, &rc); - sessionAppendStr(&buf, " OR 1", &rc); - for(i=0; inCol; i++){ - if( !p->abPK[i] ){ - sessionAppendStr(&buf, " AND (?", &rc); - sessionAppendInteger(&buf, i*3+2, &rc); - sessionAppendStr(&buf, "=0 OR ", &rc); - sessionAppendIdent(&buf, p->azCol[i], &rc); - sessionAppendStr(&buf, " IS ?", &rc); - sessionAppendInteger(&buf, i*3+1, &rc); - sessionAppendStr(&buf, ")", &rc); - } - } - sessionAppendStr(&buf, ")", &rc); - - if( rc==SQLITE_OK ){ - rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pUpdate, 0); + rc = sessionPrepare(db, &p->pDelete, &p->zErr, (char*)buf.aBuf); } sqlite3_free(buf.aBuf); return rc; } - /* ** Formulate and prepare an SQL statement to query table zTab by primary ** key. Assuming the following table structure: @@ -198699,8 +238371,10 @@ static int sessionSelectRow( const char *zTab, /* Table name */ SessionApplyCtx *p /* Session changeset-apply context */ ){ - return sessionSelectStmt( - db, "main", zTab, p->nCol, p->azCol, p->abPK, &p->pSelect); + /* TODO */ + return sessionSelectStmt(db, p->bIgnoreNoop, + "main", zTab, p->bRowid, p->nCol, p->azCol, p->abPK, &p->pSelect, &p->zErr + ); } /* @@ -198736,44 +238410,29 @@ static int sessionInsertRow( sessionAppendStr(&buf, ")", &rc); if( rc==SQLITE_OK ){ - rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pInsert, 0); + rc = sessionPrepare(db, &p->pInsert, &p->zErr, (char*)buf.aBuf); } sqlite3_free(buf.aBuf); return rc; } -static int sessionPrepare(sqlite3 *db, sqlite3_stmt **pp, const char *zSql){ - return sqlite3_prepare_v2(db, zSql, -1, pp, 0); -} - /* ** Prepare statements for applying changes to the sqlite_stat1 table. ** These are similar to those created by sessionSelectRow(), -** sessionInsertRow(), sessionUpdateRow() and sessionDeleteRow() for +** sessionInsertRow(), sessionUpdateRow() and sessionDeleteRow() for ** other tables. */ static int sessionStat1Sql(sqlite3 *db, SessionApplyCtx *p){ int rc = sessionSelectRow(db, "sqlite_stat1", p); if( rc==SQLITE_OK ){ - rc = sessionPrepare(db, &p->pInsert, + rc = sessionPrepare(db, &p->pInsert, 0, "INSERT INTO main.sqlite_stat1 VALUES(?1, " "CASE WHEN length(?2)=0 AND typeof(?2)='blob' THEN NULL ELSE ?2 END, " "?3)" ); } if( rc==SQLITE_OK ){ - rc = sessionPrepare(db, &p->pUpdate, - "UPDATE main.sqlite_stat1 SET " - "tbl = CASE WHEN ?2 THEN ?3 ELSE tbl END, " - "idx = CASE WHEN ?5 THEN ?6 ELSE idx END, " - "stat = CASE WHEN ?8 THEN ?9 ELSE stat END " - "WHERE tbl=?1 AND idx IS " - "CASE WHEN length(?4)=0 AND typeof(?4)='blob' THEN NULL ELSE ?4 END " - "AND (?10 OR ?8=0 OR stat IS ?7)" - ); - } - if( rc==SQLITE_OK ){ - rc = sessionPrepare(db, &p->pDelete, + rc = sessionPrepare(db, &p->pDelete, 0, "DELETE FROM main.sqlite_stat1 WHERE tbl=?1 AND idx IS " "CASE WHEN length(?2)=0 AND typeof(?2)='blob' THEN NULL ELSE ?2 END " "AND (?4 OR stat IS ?3)" @@ -198783,7 +238442,7 @@ static int sessionStat1Sql(sqlite3 *db, SessionApplyCtx *p){ } /* -** A wrapper around sqlite3_bind_value() that detects an extra problem. +** A wrapper around sqlite3_bind_value() that detects an extra problem. ** See comments in the body of this function for details. */ static int sessionBindValue( @@ -198806,15 +238465,15 @@ static int sessionBindValue( } /* -** Iterator pIter must point to an SQLITE_INSERT entry. This function +** Iterator pIter must point to an SQLITE_INSERT entry. This function ** transfers new.* values from the current iterator entry to statement ** pStmt. The table being inserted into has nCol columns. ** -** New.* value $i from the iterator is bound to variable ($i+1) of +** New.* value $i from the iterator is bound to variable ($i+1) of ** statement pStmt. If parameter abPK is NULL, all values from 0 to (nCol-1) ** are transfered to the statement. Otherwise, if abPK is not NULL, it points -** to an array nCol elements in size. In this case only those values for -** which abPK[$i] is true are read from the iterator and bound to the +** to an array nCol elements in size. In this case only those values for +** which abPK[$i] is true are read from the iterator and bound to the ** statement. ** ** An SQLite error code is returned if an error occurs. Otherwise, SQLITE_OK. @@ -198830,14 +238489,14 @@ static int sessionBindRow( int rc = SQLITE_OK; /* Neither sqlite3changeset_old or sqlite3changeset_new can fail if the - ** argument iterator points to a suitable entry. Make sure that xValue - ** is one of these to guarantee that it is safe to ignore the return + ** argument iterator points to a suitable entry. Make sure that xValue + ** is one of these to guarantee that it is safe to ignore the return ** in the code below. */ assert( xValue==sqlite3changeset_old || xValue==sqlite3changeset_new ); for(i=0; rc==SQLITE_OK && ipSelect; int rc; /* Return code */ int nCol; /* Number of columns in table */ int op; /* Changset operation (SQLITE_UPDATE etc.) */ const char *zDummy; /* Unused */ + sqlite3_clear_bindings(pSelect); sqlite3changeset_op(pIter, &zDummy, &nCol, &op, 0); - rc = sessionBindRow(pIter, + rc = sessionBindRow(pIter, op==SQLITE_INSERT ? sqlite3changeset_new : sqlite3changeset_old, - nCol, abPK, pSelect + nCol, p->abPK, pSelect ); + if( op!=SQLITE_DELETE && p->bIgnoreNoop ){ + int ii; + for(ii=0; rc==SQLITE_OK && iiabPK[ii]==0 ){ + sqlite3_value *pVal = 0; + sqlite3changeset_new(pIter, ii, &pVal); + sqlite3_bind_int(pSelect, ii+1+nCol, (pVal==0)); + if( pVal ) rc = sessionBindValue(pSelect, ii+1, pVal); + } + } + } + if( rc==SQLITE_OK ){ rc = sqlite3_step(pSelect); if( rc!=SQLITE_ROW ) rc = sqlite3_reset(pSelect); @@ -198897,7 +238568,7 @@ static int sessionSeekToRow( ** This function is called from within sqlite3changeset_apply_v2() when ** a conflict is encountered and resolved using conflict resolution ** mode eType (either SQLITE_CHANGESET_OMIT or SQLITE_CHANGESET_REPLACE).. -** It adds a conflict resolution record to the buffer in +** It adds a conflict resolution record to the buffer in ** SessionApplyCtx.rebase, which will eventually be returned to the caller ** of apply_v2() as the "rebase" buffer. ** @@ -198925,7 +238596,7 @@ static int sessionRebaseAdd( assert( eType==SQLITE_CHANGESET_REPLACE||eType==SQLITE_CHANGESET_OMIT ); assert( eOp==SQLITE_DELETE || eOp==SQLITE_INSERT || eOp==SQLITE_UPDATE ); - sessionAppendByte(&p->rebase, + sessionAppendByte(&p->rebase, (eOp==SQLITE_DELETE ? SQLITE_DELETE : SQLITE_INSERT), &rc ); sessionAppendByte(&p->rebase, (eType==SQLITE_CHANGESET_REPLACE), &rc); @@ -198973,7 +238644,7 @@ static int sessionRebaseAdd( ** is set to non-zero before returning SQLITE_OK. ** ** If the conflict handler returns SQLITE_CHANGESET_ABORT, SQLITE_ABORT is -** returned. Or, if the conflict handler returns an invalid value, +** returned. Or, if the conflict handler returns an invalid value, ** SQLITE_MISUSE. If the conflict handler returns SQLITE_CHANGESET_OMIT, ** this function returns SQLITE_OK. */ @@ -198985,7 +238656,7 @@ static int sessionConflictHandler( void *pCtx, /* First argument for conflict handler */ int *pbReplace /* OUT: Set to true if PK row is found */ ){ - int res = 0; /* Value returned by conflict handler */ + int res = SQLITE_CHANGESET_OMIT;/* Value returned by conflict handler */ int rc; int nCol; int op; @@ -198999,16 +238670,20 @@ static int sessionConflictHandler( /* Bind the new.* PRIMARY KEY values to the SELECT statement. */ if( pbReplace ){ - rc = sessionSeekToRow(p->db, pIter, p->abPK, p->pSelect); + rc = sessionSeekToRow(pIter, p); }else{ rc = SQLITE_OK; } if( rc==SQLITE_ROW ){ /* There exists another row with the new.* primary key. */ - pIter->pConflict = p->pSelect; - res = xConflict(pCtx, eType, pIter); - pIter->pConflict = 0; + if( 0==p->bIgnoreNoop + || 0==sqlite3_column_int(p->pSelect, sqlite3_column_count(p->pSelect)-1) + ){ + pIter->pConflict = p->pSelect; + res = xConflict(pCtx, eType, pIter); + pIter->pConflict = 0; + } rc = sqlite3_reset(p->pSelect); }else if( rc==SQLITE_OK ){ if( p->bDeferConstraints && eType==SQLITE_CHANGESET_CONFLICT ){ @@ -199017,8 +238692,10 @@ static int sessionConflictHandler( u8 *aBlob = &pIter->in.aData[pIter->in.iCurrent]; int nBlob = pIter->in.iNext - pIter->in.iCurrent; sessionAppendBlob(&p->constraints, aBlob, nBlob, &rc); - return SQLITE_OK; - }else{ + return rc; + }else if( p->bIgnoreNoop==0 || op!=SQLITE_DELETE + || eType==SQLITE_CHANGESET_CONFLICT + ){ /* No other row with the new.* primary key. */ res = xConflict(pCtx, eType+1, pIter); if( res==SQLITE_CHANGESET_REPLACE ) rc = SQLITE_MISUSE; @@ -199063,16 +238740,16 @@ static int sessionConflictHandler( ** to true before returning. In this case the caller will invoke this function ** again, this time with pbRetry set to NULL. ** -** If argument pbReplace is NULL and a CHANGESET_CONFLICT conflict is +** If argument pbReplace is NULL and a CHANGESET_CONFLICT conflict is ** encountered invoke the conflict handler with CHANGESET_CONSTRAINT instead. ** Or, if pbReplace is not NULL, invoke it with CHANGESET_CONFLICT. If such ** an invocation returns SQLITE_CHANGESET_REPLACE, set *pbReplace to true ** before retrying. In this case the caller attempts to remove the conflicting -** row before invoking this function again, this time with pbReplace set +** row before invoking this function again, this time with pbReplace set ** to NULL. ** ** If any conflict handler returns SQLITE_CHANGESET_ABORT, this function -** returns SQLITE_ABORT. Otherwise, if no error occurs, SQLITE_OK is +** returns SQLITE_ABORT. Otherwise, if no error occurs, SQLITE_OK is ** returned. */ static int sessionApplyOneOp( @@ -199088,7 +238765,7 @@ static int sessionApplyOneOp( int nCol; int rc = SQLITE_OK; - assert( p->pDelete && p->pUpdate && p->pInsert && p->pSelect ); + assert( p->pDelete && p->pInsert && p->pSelect ); assert( p->azCol && p->abPK ); assert( !pbReplace || *pbReplace==0 ); @@ -199128,29 +238805,28 @@ static int sessionApplyOneOp( }else if( op==SQLITE_UPDATE ){ int i; + sqlite3_stmt *pUp = 0; + int bPatchset = (pbRetry==0 || pIter->bPatchset); + + rc = sessionUpdateFind(pIter, p, bPatchset, &pUp); /* Bind values to the UPDATE statement. */ for(i=0; rc==SQLITE_OK && ipUpdate, i*3+2, !!pNew); - if( pOld ){ - rc = sessionBindValue(p->pUpdate, i*3+1, pOld); + if( pOld && (p->abPK[i] || bPatchset==0) ){ + rc = sessionBindValue(pUp, i*2+2, pOld); } if( rc==SQLITE_OK && pNew ){ - rc = sessionBindValue(p->pUpdate, i*3+3, pNew); + rc = sessionBindValue(pUp, i*2+1, pNew); } } - if( rc==SQLITE_OK ){ - sqlite3_bind_int(p->pUpdate, nCol*3+1, pbRetry==0 || pIter->bPatchset); - } if( rc!=SQLITE_OK ) return rc; /* Attempt the UPDATE. In the case of a NOTFOUND or DATA conflict, ** the result will be SQLITE_OK with 0 rows modified. */ - sqlite3_step(p->pUpdate); - rc = sqlite3_reset(p->pUpdate); + sqlite3_step(pUp); + rc = sqlite3_reset(pUp); if( rc==SQLITE_OK && sqlite3_changes(p->db)==0 ){ /* A NOTFOUND or DATA error. Search the table to see if it contains @@ -199172,9 +238848,9 @@ static int sessionApplyOneOp( assert( op==SQLITE_INSERT ); if( p->bStat1 ){ /* Check if there is a conflicting row. For sqlite_stat1, this needs - ** to be done using a SELECT, as there is no PRIMARY KEY in the + ** to be done using a SELECT, as there is no PRIMARY KEY in the ** database schema to throw an exception if a duplicate is inserted. */ - rc = sessionSeekToRow(p->db, pIter, p->abPK, p->pSelect); + rc = sessionSeekToRow(pIter, p); if( rc==SQLITE_ROW ){ rc = SQLITE_CONSTRAINT; sqlite3_reset(p->pSelect); @@ -199205,7 +238881,7 @@ static int sessionApplyOneOp( ** the conflict handler callback. ** ** The difference between this function and sessionApplyOne() is that this -** function handles the case where the conflict-handler is invoked and +** function handles the case where the conflict-handler is invoked and ** returns SQLITE_CHANGESET_REPLACE - indicating that the change should be ** retried in some manner. */ @@ -199225,7 +238901,7 @@ static int sessionApplyOneWithRetry( /* If the bRetry flag is set, the change has not been applied due to an ** SQLITE_CHANGESET_DATA problem (i.e. this is an UPDATE or DELETE and ** a row with the correct PK is present in the db, but one or more other - ** fields do not contain the expected values) and the conflict handler + ** fields do not contain the expected values) and the conflict handler ** returned SQLITE_CHANGESET_REPLACE. In this case retry the operation, ** but pass NULL as the final argument so that sessionApplyOneOp() ignores ** the SQLITE_CHANGESET_DATA problem. */ @@ -199243,7 +238919,7 @@ static int sessionApplyOneWithRetry( assert( pIter->op==SQLITE_INSERT ); rc = sqlite3_exec(db, "SAVEPOINT replace_op", 0, 0, 0); if( rc==SQLITE_OK ){ - rc = sessionBindRow(pIter, + rc = sessionBindRow(pIter, sqlite3changeset_new, pApply->nCol, pApply->abPK, pApply->pDelete); sqlite3_bind_int(pApply->pDelete, pApply->nCol+1, 1); } @@ -199264,10 +238940,267 @@ static int sessionApplyOneWithRetry( } /* -** Retry the changes accumulated in the pApply->constraints buffer. +** Create an iterator to iterate through the retry buffer pRetry. +*/ +static int sessionRetryIterInit( + SessionBuffer *pRetry, /* Buffer to iterate through */ + int bPatchset, /* True for patchset, false for changeset */ + const char *zTab, /* Table name */ + SessionApplyCtx *pApply, /* Session apply context */ + sqlite3_changeset_iter **ppIter /* OUT: New iterator */ +){ + sqlite3_changeset_iter *pRet = 0; + int rc = SQLITE_OK; + + rc = sessionChangesetStart( + &pRet, 0, 0, pRetry->nBuf, pRetry->aBuf, pApply->bInvertConstraints, 1 + ); + if( rc==SQLITE_OK ){ + size_t nByte = 2*pApply->nCol*sizeof(sqlite3_value*); + pRet->bPatchset = bPatchset; + pRet->zTab = (char*)zTab; + pRet->nCol = pApply->nCol; + pRet->abPK = pApply->abPK; + sessionBufferGrow(&pRet->tblhdr, nByte, &rc); + pRet->apValue = (sqlite3_value**)pRet->tblhdr.aBuf; + if( rc==SQLITE_OK ){ + memset(pRet->apValue, 0, nByte); + }else{ + sqlite3changeset_finalize(pRet); + pRet = 0; + } + } + + *ppIter = pRet; + return rc; +} + +/* +** Attempt to apply all the changes in retry buffer pRetry to the database. +** Except, if parameter iSkip is greater than or equal to 0, skip change +** iSkip. +*/ +static int sessionApplyRetryBuffer( + SessionBuffer *pRetry, /* Buffer to apply changes from */ + int iSkip, /* If >=0, index of change to omit */ + sqlite3 *db, /* Database handle */ + int bPatchset, /* True for patchset, false for changeset */ + const char *zTab, /* Name of table to write to */ + SessionApplyCtx *pApply, /* Apply context */ + int(*xConflict)(void*, int, sqlite3_changeset_iter*), + void *pCtx /* First argument passed to xConflict */ +){ + int rc = SQLITE_OK; + int rc2 = SQLITE_OK; + int ii = 0; + sqlite3_changeset_iter *pIter = 0; + + assert( pApply->constraints.nBuf==0 ); + + rc = sessionRetryIterInit(pRetry, bPatchset, zTab, pApply, &pIter); + + for(ii=0; rc==SQLITE_OK && SQLITE_ROW==sqlite3changeset_next(pIter); ii++){ + if( ii!=iSkip ){ + rc = sessionApplyOneWithRetry(db, pIter, pApply, xConflict, pCtx); + } + } + + rc2 = sqlite3changeset_finalize(pIter); + if( rc==SQLITE_OK ) rc = rc2; + assert( pApply->bDeferConstraints || pApply->constraints.nBuf==0 ); + + return rc; +} + +/* +** Check if table zTab in the "main" database of db is a WITHOUT ROWID +** table. +** +** If no error occurs, return SQLITE_OK and set output variable (*pbWR) to +** true if zTab is a WITHOUT ROWID table, or false otherwise. Or, if an +** error does occur, return an SQLite error code. The final value of (*pbWR) +** is undefined in this case. +*/ +static int sessionTableIsWithoutRowid(sqlite3 *db, const char *zTab, int *pbWR){ + sqlite3_stmt *pList = 0; + char *zSql = 0; + int rc = SQLITE_OK; + + zSql = sqlite3_mprintf("PRAGMA table_list = %Q", zTab); + if( zSql==0 ){ + rc = SQLITE_NOMEM; + }else{ + rc = sqlite3_prepare_v2(db, zSql, -1, &pList, 0); + sqlite3_free(zSql); + } + + if( rc==SQLITE_OK ){ + sqlite3_step(pList); + *pbWR = sqlite3_column_int(pList, 4); + rc = sqlite3_finalize(pList); + } + + return rc; +} + +/* +** Iterator pUp points to an UPDATE change. This function deletes the +** affected row from the database and creates an INSERT statement that +** may be used to reinsert the row as it is after the UPDATE change +** has been applied. +** +** If successful, SQLITE_OK is returned and output variable (*ppInsert) +** is left pointing to a prepared INSERT statement. It is the responsibility +** of the caller to eventually free this statement using sqlite3_finalize(). +** Or, if an error occurs, an SQLite error code is returned and (*ppInsert) +** set to NULL. pApply->zErr may be set to an error message in this case. +*/ +static int sessionUpdateToDeleteInsert( + sqlite3 *db, /* Database to write to */ + const char *zTab, /* Table name */ + SessionApplyCtx *pApply, /* Apply context */ + sqlite3_changeset_iter *pUp, /* Iterator pointing to UPDATE change */ + sqlite3_stmt **ppInsert /* OUT: INSERT statement */ +){ + sqlite3_stmt *pRet = 0; /* The INSERT statement */ + sqlite3_stmt *pSelect = 0; /* SELECT to read current values of row */ + int rc = SQLITE_OK; + int bWR = 0; + + rc = sessionTableIsWithoutRowid(db, zTab, &bWR); + if( rc==SQLITE_OK ){ + char *zSelect = 0; + char *zInsert = 0; + SessionBuffer cols = {0, 0, 0}; + SessionBuffer insbind = {0, 0, 0}; + SessionBuffer pkcols = {0, 0, 0}; + SessionBuffer selbind = {0, 0, 0}; + + const char *zComma = ""; + const char *zComma2 = ""; + int ii; + for(ii=0; iinCol; ii++){ + sessionAppendStr(&cols, zComma, &rc); + sessionAppendIdent(&cols, pApply->azCol[ii], &rc); + sessionAppendStr(&insbind, zComma, &rc); + sessionAppendStr(&insbind, "?", &rc); + zComma = ", "; + + if( pApply->abPK[ii] ){ + sessionAppendStr(&pkcols, zComma2, &rc); + sessionAppendIdent(&pkcols, pApply->azCol[ii], &rc); + sessionAppendStr(&selbind, zComma2, &rc); + sessionAppendPrintf(&selbind, &rc, "?%d", ii+1); + zComma2 = ", "; + } + } + if( bWR==0 ){ + sessionAppendStr(&cols, zComma, &rc); + sessionAppendStr(&cols, SESSIONS_ROWID, &rc); + sessionAppendStr(&insbind, zComma, &rc); + sessionAppendStr(&insbind, "?", &rc); + } + + if( rc==SQLITE_OK ){ + zSelect = sqlite3_mprintf("SELECT %s FROM %Q WHERE (%s) IS (%s)", + cols.aBuf, zTab, pkcols.aBuf, selbind.aBuf + ); + if( zSelect==0 ) rc = SQLITE_NOMEM; + } + if( rc==SQLITE_OK ){ + zInsert = sqlite3_mprintf("INSERT INTO %Q(%s) VALUES(%s)", + zTab, cols.aBuf, insbind.aBuf + ); + if( zInsert==0 ) rc = SQLITE_NOMEM; + } + + if( rc==SQLITE_OK ){ + rc = sessionPrepare(db, &pSelect, &pApply->zErr, zSelect); + } + if( rc==SQLITE_OK ){ + rc = sessionPrepare(db, &pRet, &pApply->zErr, zInsert); + } + + sqlite3_free(zSelect); + sqlite3_free(zInsert); + sqlite3_free(cols.aBuf); + sqlite3_free(insbind.aBuf); + sqlite3_free(pkcols.aBuf); + sqlite3_free(selbind.aBuf); + } + + if( rc==SQLITE_OK ){ + rc = sessionBindRow( + pUp, sqlite3changeset_old, pApply->nCol, pApply->abPK, pSelect + ); + } + + if( rc==SQLITE_OK && sqlite3_step(pSelect)==SQLITE_ROW ){ + int iCol; + for(iCol=0; iColnCol; iCol++){ + sqlite3_value *pVal = pUp->apValue[iCol+pApply->nCol]; + if( pVal==0 ){ + pVal = sqlite3_column_value(pSelect, iCol); + } + rc = sqlite3_bind_value(pRet, iCol+1, pVal); + } + if( bWR==0 ){ + sqlite3_bind_int64(pRet, iCol+1, sqlite3_column_int64(pSelect, iCol)); + } + } + sessionFinalizeStmt(pSelect, &rc); + + /* Delete the row from the database. */ + if( rc==SQLITE_OK ){ + rc = sessionBindRow( + pUp, sqlite3changeset_old, pApply->nCol, pApply->abPK, pApply->pDelete + ); + sqlite3_bind_int(pApply->pDelete, pApply->nCol+1, 1); + } + if( rc==SQLITE_OK ){ + sqlite3_step(pApply->pDelete); + rc = sqlite3_reset(pApply->pDelete); + } + + if( rc!=SQLITE_OK ){ + sqlite3_finalize(pRet); + pRet = 0; + } + + *ppInsert = pRet; + return rc; +} + +/* +** Retry the changes accumulated in the pApply->constraints buffer. The +** pApply->constraints buffer contains all changes to table zTab that +** could not be applied due to SQLITE_CONSTRAINT errors. This function +** attempts to apply them as follows: +** +** 1) It runs through the buffer and attempts to retry each change, +** removing any that are successfully applied from the buffer. This +** is repeated until no further progress can be made. +** +** 2) For each UPDATE change in the buffer, try the following in a +** savepoint transaction: +** +** a) DELETE the affected row, +** b) Attempt step (1) with remaining changes, +** c) Attempt to INSERT a row equivalent to the one that would be +** created by applying this UPDATE change. +** +** If the INSERT in (c) succeeds, the savepoint is committed and all +** successfully applied changes are removed from the buffer. Step (2) +** is then repeated. +** +** 3) Once step (2) has been attempted for each UPDATE in the change, +** a final attempt is made to apply each remaining change. This time, +** if an SQLITE_CONSTRAINT error is encountered, the conflict handler +** is invoked and the user has to decide whether to omit the change +** or rollback the entire _apply() operation. */ static int sessionRetryConstraints( - sqlite3 *db, + sqlite3 *db, int bPatchset, const char *zTab, SessionApplyCtx *pApply, @@ -199275,39 +239208,101 @@ static int sessionRetryConstraints( void *pCtx /* First argument passed to xConflict */ ){ int rc = SQLITE_OK; + int iUpdate = 0; + /* Step (1) */ while( pApply->constraints.nBuf ){ - sqlite3_changeset_iter *pIter2 = 0; SessionBuffer cons = pApply->constraints; memset(&pApply->constraints, 0, sizeof(SessionBuffer)); - rc = sessionChangesetStart(&pIter2, 0, 0, cons.nBuf, cons.aBuf, 0); + rc = sessionApplyRetryBuffer( + &cons, -1, db, bPatchset, zTab, pApply, xConflict, pCtx + ); + + sqlite3_free(cons.aBuf); + if( rc!=SQLITE_OK ) break; + + /* If no progress has been made this round, break out of the loop. */ + if( pApply->constraints.nBuf>=cons.nBuf ) break; + } + + /* Step (2) */ + while( rc==SQLITE_OK && pApply->constraints.nBuf && !pApply->bNoUpdateLoop ){ + SessionBuffer cons = {0, 0, 0}; + sqlite3_changeset_iter *pUp = 0; + sqlite3_stmt *pInsert = 0; + int iSkip = 0; + + rc = sessionRetryIterInit( + &pApply->constraints, bPatchset, zTab, pApply, &pUp + ); if( rc==SQLITE_OK ){ - size_t nByte = 2*pApply->nCol*sizeof(sqlite3_value*); - int rc2; - pIter2->bPatchset = bPatchset; - pIter2->zTab = (char*)zTab; - pIter2->nCol = pApply->nCol; - pIter2->abPK = pApply->abPK; - sessionBufferGrow(&pIter2->tblhdr, nByte, &rc); - pIter2->apValue = (sqlite3_value**)pIter2->tblhdr.aBuf; - if( rc==SQLITE_OK ) memset(pIter2->apValue, 0, nByte); + int iThis = -1; + while( SQLITE_ROW==sqlite3changeset_next(pUp) ){ + if( pUp->op==SQLITE_UPDATE ) iThis++; + if( iThis==iUpdate ) break; + iSkip++; + } + if( iThis==iUpdate ){ + rc = sqlite3_exec(db, "SAVEPOINT update_op", 0, 0, 0); + if( rc==SQLITE_OK ){ + rc = sessionUpdateToDeleteInsert(db, zTab, pApply, pUp, &pInsert); + } + } + sqlite3changeset_finalize(pUp); + if( iThis!=iUpdate ) break; + } - while( rc==SQLITE_OK && SQLITE_ROW==sqlite3changeset_next(pIter2) ){ - rc = sessionApplyOneWithRetry(db, pIter2, pApply, xConflict, pCtx); + if( rc==SQLITE_OK ){ + cons = pApply->constraints; + + while( rc==SQLITE_OK && pApply->constraints.nBuf>0 ){ + SessionBuffer app = pApply->constraints; + memset(&pApply->constraints, 0, sizeof(SessionBuffer)); + rc = sessionApplyRetryBuffer( + &app, iSkip, db, bPatchset, zTab, pApply, xConflict, pCtx + ); + if( app.aBuf!=cons.aBuf ){ + sqlite3_free(app.aBuf); + } + if( pApply->constraints.nBuf>=app.nBuf ){ + break; + } + iSkip = -1; } + } - rc2 = sqlite3changeset_finalize(pIter2); - if( rc==SQLITE_OK ) rc = rc2; + iUpdate++; + if( rc==SQLITE_OK ){ + sqlite3_step(pInsert); + rc = sqlite3_finalize(pInsert); + if( rc==SQLITE_CONSTRAINT ){ + rc = sqlite3_exec(db, "ROLLBACK TO update_op", 0, 0, 0); + sqlite3_free(pApply->constraints.aBuf); + pApply->constraints = cons; + memset(&cons, 0, sizeof(cons)); + }else if( rc==SQLITE_OK ){ + iUpdate = 0; + } + if( rc==SQLITE_OK ){ + rc = sqlite3_exec(db, "RELEASE update_op", 0, 0, 0); + } + }else{ + sqlite3_finalize(pInsert); } - assert( pApply->bDeferConstraints || pApply->constraints.nBuf==0 ); sqlite3_free(cons.aBuf); - if( rc!=SQLITE_OK ) break; - if( pApply->constraints.nBuf>=cons.nBuf ){ - /* No progress was made on the last round. */ - pApply->bDeferConstraints = 0; - } + } + + /* Step (3) */ + if( rc==SQLITE_OK && pApply->constraints.nBuf ){ + SessionBuffer cons = pApply->constraints; + memset(&pApply->constraints, 0, sizeof(SessionBuffer)); + pApply->bDeferConstraints = 0; + rc = sessionApplyRetryBuffer( + &cons, -1, db, bPatchset, zTab, pApply, xConflict, pCtx + ); + sqlite3_free(cons.aBuf); } return rc; @@ -199315,7 +239310,7 @@ static int sessionRetryConstraints( /* ** Argument pIter is a changeset iterator that has been initialized, but -** not yet passed to sqlite3changeset_next(). This function applies the +** not yet passed to sqlite3changeset_next(). This function applies the ** changeset to the main database attached to handle "db". The supplied ** conflict handler callback is invoked to resolve any conflicts encountered ** while applying the change. @@ -199327,6 +239322,10 @@ static int sessionChangesetApply( void *pCtx, /* Copy of sixth arg to _apply() */ const char *zTab /* Table name */ ), + int(*xFilterIter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + sqlite3_changeset_iter *p + ), int(*xConflict)( void *pCtx, /* Copy of fifth arg to _apply() */ int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ @@ -199342,13 +239341,22 @@ static int sessionChangesetApply( int nTab = 0; /* Result of sqlite3Strlen30(zTab) */ SessionApplyCtx sApply; /* changeset_apply() context object */ int bPatchset; + u64 savedFlag = db->flags & SQLITE_FkNoAction; assert( xConflict!=0 ); + sqlite3_mutex_enter(sqlite3_db_mutex(db)); + if( flags & SQLITE_CHANGESETAPPLY_FKNOACTION ){ + db->flags |= ((u64)SQLITE_FkNoAction); + db->aDb[0].pSchema->schema_cookie -= 32; + } + pIter->in.bNoDiscard = 1; memset(&sApply, 0, sizeof(sApply)); sApply.bRebase = (ppRebase && pnRebase); - sqlite3_mutex_enter(sqlite3_db_mutex(db)); + sApply.bInvertConstraints = !!(flags & SQLITE_CHANGESETAPPLY_INVERT); + sApply.bIgnoreNoop = !!(flags & SQLITE_CHANGESETAPPLY_IGNORENOOP); + sApply.bNoUpdateLoop = !!(flags & SQLITE_CHANGESETAPPLY_NOUPDATELOOP); if( (flags & SQLITE_CHANGESETAPPLY_NOSAVEPOINT)==0 ){ rc = sqlite3_exec(db, "SAVEPOINT changeset_apply", 0, 0, 0); } @@ -199359,7 +239367,7 @@ static int sessionChangesetApply( int nCol; int op; const char *zNew; - + sqlite3changeset_op(pIter, &zNew, &nCol, &op, 0); if( zTab==0 || sqlite3_strnicmp(zNew, zTab, nTab+1) ){ @@ -199370,14 +239378,13 @@ static int sessionChangesetApply( ); if( rc!=SQLITE_OK ) break; + sessionUpdateFree(&sApply); sqlite3_free((char*)sApply.azCol); /* cast works around VC++ bug */ sqlite3_finalize(sApply.pDelete); - sqlite3_finalize(sApply.pUpdate); sqlite3_finalize(sApply.pInsert); sqlite3_finalize(sApply.pSelect); sApply.db = db; sApply.pDelete = 0; - sApply.pUpdate = 0; sApply.pInsert = 0; sApply.pSelect = 0; sApply.nCol = 0; @@ -199386,9 +239393,10 @@ static int sessionChangesetApply( sApply.bStat1 = 0; sApply.bDeferConstraints = 1; sApply.bRebaseStarted = 0; + sApply.bRowid = 0; memset(&sApply.constraints, 0, sizeof(SessionBuffer)); - /* If an xFilter() callback was specified, invoke it now. If the + /* If an xFilter() callback was specified, invoke it now. If the ** xFilter callback returns zero, skip this table. If it returns ** non-zero, proceed. */ schemaMismatch = (xFilter && (0==xFilter(pCtx, zNew))); @@ -199405,25 +239413,26 @@ static int sessionChangesetApply( int i; sqlite3changeset_pk(pIter, &abPK, 0); - rc = sessionTableInfo( - db, "main", zNew, &sApply.nCol, &zTab, &sApply.azCol, &sApply.abPK + rc = sessionTableInfo(0, db, "main", zNew, + &sApply.nCol, 0, &zTab, &sApply.azCol, 0, 0, + &sApply.abPK, &sApply.bRowid ); if( rc!=SQLITE_OK ) break; for(i=0; iflags & SQLITE_FkNoAction ); + db->flags &= ~((u64)SQLITE_FkNoAction); + db->aDb[0].pSchema->schema_cookie -= 32; + } + + assert( rc!=SQLITE_OK || sApply.zErr==0 ); + sqlite3_set_errmsg(db, rc, sApply.zErr); + sqlite3_free(sApply.zErr); + sqlite3_mutex_leave(sqlite3_db_mutex(db)); return rc; } /* -** Apply the changeset passed via pChangeset/nChangeset to the main -** database attached to handle "db". +** This function is called by all six sqlite3changeset_apply() variants: +** +** + sqlite3changeset_apply() +** + sqlite3changeset_apply_v2() +** + sqlite3changeset_apply_v3() +** + sqlite3changeset_apply_strm() +** + sqlite3changeset_apply_strm_v2() +** + sqlite3changeset_apply_strm_v3() +** +** Arguments passed to this function are as follows: +** +** db: +** Database handle to apply changeset to main database of. +** +** nChangeset/pChangeset: +** These are both passed zero for the streaming variants. For the normal +** apply() functions, these are passed the size of and the buffer containing +** the changeset, respectively. +** +** xInput/pIn: +** These are both passed zero for the normal variants. For the streaming +** apply() functions, these are passed the input callback and context +** pointer, respectively. +** +** xFilter: +** The filter function as passed to apply() or apply_v2() (to filter by +** table name), if any. This is always NULL for apply_v3() calls. +** +** xFilterIter: +** The filter function as passed to apply_v3(), if any. +** +** xConflict: +** The conflict handler callback (must not be NULL). +** +** pCtx: +** The context pointer passed to the xFilter and xConflict handler callbacks. +** +** ppRebase, pnRebase: +** Zero for apply(). The rebase changeset output pointers, if any, for +** apply_v2() and apply_v3(). +** +** flags: +** Zero for apply(). The flags parameter for apply_v2() and apply_v3(). */ -SQLITE_API int sqlite3changeset_apply_v2( +static int sessionChangesetApplyV23( sqlite3 *db, /* Apply change to "main" db of this handle */ int nChangeset, /* Size of changeset in bytes */ void *pChangeset, /* Changeset blob */ + int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */ + void *pIn, /* First arg for xInput */ int(*xFilter)( void *pCtx, /* Copy of sixth arg to _apply() */ const char *zTab /* Table name */ ), + int(*xFilterIter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + sqlite3_changeset_iter *p /* Handle describing current change */ + ), int(*xConflict)( void *pCtx, /* Copy of sixth arg to _apply() */ int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ @@ -199536,17 +239611,75 @@ SQLITE_API int sqlite3changeset_apply_v2( void **ppRebase, int *pnRebase, int flags ){ - sqlite3_changeset_iter *pIter; /* Iterator to skip through changeset */ + sqlite3_changeset_iter *pIter; /* Iterator to skip through changeset */ int bInverse = !!(flags & SQLITE_CHANGESETAPPLY_INVERT); - int rc = sessionChangesetStart(&pIter, 0, 0, nChangeset, pChangeset,bInverse); + int rc = sessionChangesetStart( + &pIter, xInput, pIn, nChangeset, pChangeset, bInverse, 1 + ); if( rc==SQLITE_OK ){ - rc = sessionChangesetApply( - db, pIter, xFilter, xConflict, pCtx, ppRebase, pnRebase, flags + rc = sessionChangesetApply(db, pIter, + xFilter, xFilterIter, xConflict, pCtx, ppRebase, pnRebase, flags ); } return rc; } +/* +** Apply the changeset passed via pChangeset/nChangeset to the main +** database attached to handle "db". +*/ +SQLITE_API int sqlite3changeset_apply_v2( + sqlite3 *db, /* Apply change to "main" db of this handle */ + int nChangeset, /* Size of changeset in bytes */ + void *pChangeset, /* Changeset blob */ + int(*xFilter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + const char *zTab /* Table name */ + ), + int(*xConflict)( + void *pCtx, /* Copy of sixth arg to _apply() */ + int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ + sqlite3_changeset_iter *p /* Handle describing change and conflict */ + ), + void *pCtx, /* First argument passed to xConflict */ + void **ppRebase, int *pnRebase, + int flags +){ + return sessionChangesetApplyV23(db, + nChangeset, pChangeset, 0, 0, + xFilter, 0, xConflict, pCtx, + ppRebase, pnRebase, flags + ); +} + +/* +** Apply the changeset passed via pChangeset/nChangeset to the main +** database attached to handle "db". +*/ +SQLITE_API int sqlite3changeset_apply_v3( + sqlite3 *db, /* Apply change to "main" db of this handle */ + int nChangeset, /* Size of changeset in bytes */ + void *pChangeset, /* Changeset blob */ + int(*xFilter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + sqlite3_changeset_iter *p /* Handle describing current change */ + ), + int(*xConflict)( + void *pCtx, /* Copy of sixth arg to _apply() */ + int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ + sqlite3_changeset_iter *p /* Handle describing change and conflict */ + ), + void *pCtx, /* First argument passed to xConflict */ + void **ppRebase, int *pnRebase, + int flags +){ + return sessionChangesetApplyV23(db, + nChangeset, pChangeset, 0, 0, + 0, xFilter, xConflict, pCtx, + ppRebase, pnRebase, flags + ); +} + /* ** Apply the changeset passed via pChangeset/nChangeset to the main database ** attached to handle "db". Invoke the supplied conflict handler callback @@ -199567,8 +239700,10 @@ SQLITE_API int sqlite3changeset_apply( ), void *pCtx /* First argument passed to xConflict */ ){ - return sqlite3changeset_apply_v2( - db, nChangeset, pChangeset, xFilter, xConflict, pCtx, 0, 0, 0 + return sessionChangesetApplyV23(db, + nChangeset, pChangeset, 0, 0, + xFilter, 0, xConflict, pCtx, + 0, 0, 0 ); } @@ -199577,6 +239712,29 @@ SQLITE_API int sqlite3changeset_apply( ** attached to handle "db". Invoke the supplied conflict handler callback ** to resolve any conflicts encountered while applying the change. */ +SQLITE_API int sqlite3changeset_apply_v3_strm( + sqlite3 *db, /* Apply change to "main" db of this handle */ + int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */ + void *pIn, /* First arg for xInput */ + int(*xFilter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + sqlite3_changeset_iter *p + ), + int(*xConflict)( + void *pCtx, /* Copy of sixth arg to _apply() */ + int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ + sqlite3_changeset_iter *p /* Handle describing change and conflict */ + ), + void *pCtx, /* First argument passed to xConflict */ + void **ppRebase, int *pnRebase, + int flags +){ + return sessionChangesetApplyV23(db, + 0, 0, xInput, pIn, + 0, xFilter, xConflict, pCtx, + ppRebase, pnRebase, flags + ); +} SQLITE_API int sqlite3changeset_apply_v2_strm( sqlite3 *db, /* Apply change to "main" db of this handle */ int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */ @@ -199594,15 +239752,11 @@ SQLITE_API int sqlite3changeset_apply_v2_strm( void **ppRebase, int *pnRebase, int flags ){ - sqlite3_changeset_iter *pIter; /* Iterator to skip through changeset */ - int bInverse = !!(flags & SQLITE_CHANGESETAPPLY_INVERT); - int rc = sessionChangesetStart(&pIter, xInput, pIn, 0, 0, bInverse); - if( rc==SQLITE_OK ){ - rc = sessionChangesetApply( - db, pIter, xFilter, xConflict, pCtx, ppRebase, pnRebase, flags - ); - } - return rc; + return sessionChangesetApplyV23(db, + 0, 0, xInput, pIn, + xFilter, 0, xConflict, pCtx, + ppRebase, pnRebase, flags + ); } SQLITE_API int sqlite3changeset_apply_strm( sqlite3 *db, /* Apply change to "main" db of this handle */ @@ -199619,11 +239773,28 @@ SQLITE_API int sqlite3changeset_apply_strm( ), void *pCtx /* First argument passed to xConflict */ ){ - return sqlite3changeset_apply_v2_strm( - db, xInput, pIn, xFilter, xConflict, pCtx, 0, 0, 0 + return sessionChangesetApplyV23(db, + 0, 0, xInput, pIn, + xFilter, 0, xConflict, pCtx, + 0, 0, 0 ); } +/* +** The parts of the sqlite3_changegroup structure used by the +** sqlite3changegroup_change_xxx() APIs. +*/ +typedef struct ChangeData ChangeData; +struct ChangeData { + SessionTable *pTab; + int bIndirect; + int eOp; + + int nBufAlloc; + SessionBuffer *aBuf; + SessionBuffer record; +}; + /* ** sqlite3_changegroup handle. */ @@ -199631,12 +239802,21 @@ struct sqlite3_changegroup { int rc; /* Error code */ int bPatch; /* True to accumulate patchsets */ SessionTable *pList; /* List of tables in current patch */ + SessionBuffer rec; + + sqlite3 *db; /* Configured by changegroup_schema() */ + char *zDb; /* Configured by changegroup_schema() */ + ChangeData cd; /* Used by changegroup_change_xxx() APIs. */ }; /* ** This function is called to merge two changes to the same row together as ** part of an sqlite3changeset_concat() operation. A new change object is ** allocated and a pointer to it stored in *ppNew. +** +** Because they have been vetted by sqlite3changegroup_add() or similar, +** both the aRec[] change and the pExist change are safe to use without +** checking for buffer overflows. */ static int sessionChangeMerge( SessionTable *pTab, /* Table structure */ @@ -199651,6 +239831,7 @@ static int sessionChangeMerge( ){ SessionChange *pNew = 0; int rc = SQLITE_OK; + assert( aRec!=0 ); if( !pExist ){ pNew = (SessionChange *)sqlite3_malloc64(sizeof(SessionChange) + nRec); @@ -199723,7 +239904,7 @@ static int sessionChangeMerge( }else{ int op1 = pExist->op; - /* + /* ** op1=INSERT, op2=INSERT -> Unsupported. Discard op2. ** op1=INSERT, op2=UPDATE -> INSERT. ** op1=INSERT, op2=DELETE -> (none) @@ -199735,7 +239916,7 @@ static int sessionChangeMerge( ** op1=DELETE, op2=INSERT -> UPDATE. ** op1=DELETE, op2=UPDATE -> Unsupported. Discard op2. ** op1=DELETE, op2=DELETE -> Unsupported. Discard op2. - */ + */ if( (op1==SQLITE_INSERT && op2==SQLITE_INSERT) || (op1==SQLITE_UPDATE && op2==SQLITE_INSERT) || (op1==SQLITE_DELETE && op2==SQLITE_UPDATE) @@ -199776,7 +239957,7 @@ static int sessionChangeMerge( memcpy(aCsr, aRec, nRec); aCsr += nRec; }else{ - if( 0==sessionMergeUpdate(&aCsr, pTab, bPatchset, aExist, 0,aRec,0) ){ + if( 0==sessionMergeUpdate(&aCsr, pTab, bPatchset, aExist,0,aRec,0) ){ sqlite3_free(pNew); pNew = 0; } @@ -199817,88 +239998,236 @@ static int sessionChangeMerge( } /* -** Add all changes in the changeset traversed by the iterator passed as -** the first argument to the changegroup hash tables. +** Check if a changeset entry with nCol columns and the PK array passed +** as the final argument to this function is compatible with SessionTable +** pTab. If so, return 1. Otherwise, if they are incompatible in some way, +** return 0. */ -static int sessionChangesetToHash( - sqlite3_changeset_iter *pIter, /* Iterator to read from */ - sqlite3_changegroup *pGrp, /* Changegroup object to add changeset to */ - int bRebase /* True if hash table is for rebasing */ +static int sessionChangesetCheckCompat( + SessionTable *pTab, + int nCol, + u8 *abPK ){ - u8 *aRec; - int nRec; - int rc = SQLITE_OK; - SessionTable *pTab = 0; - - while( SQLITE_ROW==sessionChangesetNext(pIter, &aRec, &nRec, 0) ){ - const char *zNew; - int nCol; - int op; - int iHash; - int bIndirect; - SessionChange *pChange; - SessionChange *pExist = 0; - SessionChange **pp; - - if( pGrp->pList==0 ){ - pGrp->bPatch = pIter->bPatchset; - }else if( pIter->bPatchset!=pGrp->bPatch ){ - rc = SQLITE_ERROR; - break; + if( pTab->azCol && nColnCol ){ + int ii; + for(ii=0; iinCol; ii++){ + u8 bPK = (ii < nCol) ? abPK[ii] : 0; + if( pTab->abPK[ii]!=bPK ) return 0; } + return 1; + } + return (pTab->nCol==nCol && 0==memcmp(abPK, pTab->abPK, nCol)); +} - sqlite3changeset_op(pIter, &zNew, &nCol, &op, &bIndirect); - if( !pTab || sqlite3_stricmp(zNew, pTab->zName) ){ - /* Search the list for a matching table */ - int nNew = (int)strlen(zNew); - u8 *abPK; +static int sessionChangesetExtendRecord( + sqlite3_changegroup *pGrp, + SessionTable *pTab, + int nCol, + int op, + const u8 *aRec, + int nRec, + SessionBuffer *pOut +){ + int rc = SQLITE_OK; + int ii = 0; - sqlite3changeset_pk(pIter, &abPK, 0); - for(pTab = pGrp->pList; pTab; pTab=pTab->pNext){ - if( 0==sqlite3_strnicmp(pTab->zName, zNew, nNew+1) ) break; + assert( pTab->azCol ); + assert( nColnCol ); + + pOut->nBuf = 0; + if( op==SQLITE_INSERT || (op==SQLITE_DELETE && pGrp->bPatch==0) ){ + /* Append the missing default column values to the record. */ + sessionAppendBlob(pOut, aRec, nRec, &rc); + if( rc==SQLITE_OK && pTab->pDfltStmt==0 ){ + rc = sessionPrepareDfltStmt(pGrp->db, pTab, &pTab->pDfltStmt); + if( rc==SQLITE_OK && SQLITE_ROW!=sqlite3_step(pTab->pDfltStmt) ){ + rc = sqlite3_errcode(pGrp->db); } - if( !pTab ){ - SessionTable **ppTab; + } + for(ii=nCol; rc==SQLITE_OK && iinCol; ii++){ + int eType = sqlite3_column_type(pTab->pDfltStmt, ii); + sessionAppendByte(pOut, eType, &rc); + switch( eType ){ + case SQLITE_FLOAT: + case SQLITE_INTEGER: { + if( SQLITE_OK==sessionBufferGrow(pOut, 8, &rc) ){ + if( eType==SQLITE_INTEGER ){ + sqlite3_int64 iVal = sqlite3_column_int64(pTab->pDfltStmt, ii); + sessionPutI64(&pOut->aBuf[pOut->nBuf], iVal); + }else{ + double rVal = sqlite3_column_double(pTab->pDfltStmt, ii); + sessionPutDouble(&pOut->aBuf[pOut->nBuf], rVal); + } + pOut->nBuf += 8; + } + break; + } - pTab = sqlite3_malloc64(sizeof(SessionTable) + nCol + nNew+1); - if( !pTab ){ - rc = SQLITE_NOMEM; + case SQLITE_BLOB: + case SQLITE_TEXT: { + int n = sqlite3_column_bytes(pTab->pDfltStmt, ii); + sessionAppendVarint(pOut, n, &rc); + if( eType==SQLITE_TEXT ){ + const u8 *z = (const u8*)sqlite3_column_text(pTab->pDfltStmt, ii); + sessionAppendBlob(pOut, z, n, &rc); + }else{ + const u8 *z = (const u8*)sqlite3_column_blob(pTab->pDfltStmt, ii); + sessionAppendBlob(pOut, z, n, &rc); + } break; } - memset(pTab, 0, sizeof(SessionTable)); - pTab->nCol = nCol; - pTab->abPK = (u8*)&pTab[1]; - memcpy(pTab->abPK, abPK, nCol); - pTab->zName = (char*)&pTab->abPK[nCol]; - memcpy(pTab->zName, zNew, nNew+1); - - /* The new object must be linked on to the end of the list, not - ** simply added to the start of it. This is to ensure that the - ** tables within the output of sqlite3changegroup_output() are in - ** the right order. */ - for(ppTab=&pGrp->pList; *ppTab; ppTab=&(*ppTab)->pNext); - *ppTab = pTab; - }else if( pTab->nCol!=nCol || memcmp(pTab->abPK, abPK, nCol) ){ - rc = SQLITE_SCHEMA; - break; + + default: + assert( eType==SQLITE_NULL ); + break; + } + } + }else if( op==SQLITE_UPDATE ){ + /* Append missing "undefined" entries to the old.* record. And, if this + ** is an UPDATE, to the new.* record as well. */ + int iOff = 0; + if( pGrp->bPatch==0 ){ + for(ii=0; iinCol-nCol); ii++){ + sessionAppendByte(pOut, 0x00, &rc); } } - if( sessionGrowHash(pIter->bPatchset, pTab) ){ - rc = SQLITE_NOMEM; - break; + sessionAppendBlob(pOut, &aRec[iOff], nRec-iOff, &rc); + for(ii=0; ii<(pTab->nCol-nCol); ii++){ + sessionAppendByte(pOut, 0x00, &rc); + } + }else{ + assert( op==SQLITE_DELETE && pGrp->bPatch ); + sessionAppendBlob(pOut, aRec, nRec, &rc); + } + + return rc; +} + +/* +** Locate or create a SessionTable object that may be used to add the +** change currently pointed to by iterator pIter to changegroup pGrp. +** If successful, set output variable (*ppTab) to point to the table +** object and return SQLITE_OK. Otherwise, if some error occurs, return +** an SQLite error code and leave (*ppTab) set to NULL. +*/ +static int sessionChangesetFindTable( + sqlite3_changegroup *pGrp, + const char *zTab, + sqlite3_changeset_iter *pIter, + SessionTable **ppTab +){ + int rc = SQLITE_OK; + SessionTable *pTab = 0; + int nTab = (int)strlen(zTab); + u8 *abPK = 0; + int nCol = 0; + + *ppTab = 0; + + /* Search the list for an existing table */ + for(pTab = pGrp->pList; pTab; pTab=pTab->pNext){ + if( 0==sqlite3_strnicmp(pTab->zName, zTab, nTab+1) ) break; + } + + + if( pIter ){ + sqlite3changeset_pk(pIter, &abPK, &nCol); + }else if( !pTab && !pGrp->db ){ + return SQLITE_OK; + } + + /* If one was not found above, create a new table now */ + if( !pTab ){ + SessionTable **ppNew; + + pTab = sqlite3_malloc64(sizeof(SessionTable) + nCol + nTab+1); + if( !pTab ){ + return SQLITE_NOMEM; + } + memset(pTab, 0, sizeof(SessionTable)); + pTab->nCol = nCol; + pTab->abPK = (u8*)&pTab[1]; + if( nCol>0 ){ + memcpy(pTab->abPK, abPK, nCol); } + pTab->zName = (char*)&pTab->abPK[nCol]; + memcpy(pTab->zName, zTab, nTab+1); + + if( pGrp->db ){ + pTab->nCol = 0; + rc = sessionInitTable(0, pTab, pGrp->db, pGrp->zDb); + if( rc || pTab->nCol==0 ){ + sqlite3_free(pTab->azCol); + sqlite3_free(pTab); + return rc; + } + } + + /* The new object must be linked on to the end of the list, not + ** simply added to the start of it. This is to ensure that the + ** tables within the output of sqlite3changegroup_output() are in + ** the right order. */ + for(ppNew=&pGrp->pList; *ppNew; ppNew=&(*ppNew)->pNext); + *ppNew = pTab; + } + + /* Check that the table is compatible. */ + if( pIter && !sessionChangesetCheckCompat(pTab, nCol, abPK) ){ + rc = SQLITE_SCHEMA; + } + + *ppTab = pTab; + return rc; +} + +/* +** Add a single change to the changegroup pGrp. +*/ +static int sessionOneChangeToHash( + sqlite3_changegroup *pGrp, /* Changegroup to update */ + SessionTable *pTab, /* Table change pertains to */ + int op, /* One of SQLITE_INSERT, UPDATE, DELETE */ + int bIndirect, /* True to flag change as "indirect" */ + int nCol, /* Number of columns in record(s) */ + u8 *aRec, /* Serialized change record(s) */ + int nRec, /* Size of aRec[] in bytes */ + int bRebase /* True if this is a rebase blob */ +){ + int rc = SQLITE_OK; + int iHash = 0; + SessionChange *pChange = 0; + SessionChange *pExist = 0; + SessionChange **pp = 0; + + assert( nRec>0 ); + + if( nColnCol ){ + SessionBuffer *pBuf = &pGrp->rec; + rc = sessionChangesetExtendRecord(pGrp, pTab, nCol, op, aRec, nRec, pBuf); + aRec = pBuf->aBuf; + nRec = pBuf->nBuf; + assert( pGrp->db ); + } + + if( rc==SQLITE_OK && sessionGrowHash(0, pGrp->bPatch, pTab) ){ + rc = SQLITE_NOMEM; + } + + if( rc==SQLITE_OK ){ + /* Search for existing entry. If found, remove it from the hash table. + ** Code below may link it back in. */ iHash = sessionChangeHash( - pTab, (pIter->bPatchset && op==SQLITE_DELETE), aRec, pTab->nChange + pTab, (pGrp->bPatch && op==SQLITE_DELETE), aRec, pTab->nChange ); - - /* Search for existing entry. If found, remove it from the hash table. - ** Code below may link it back in. - */ for(pp=&pTab->apChange[iHash]; *pp; pp=&(*pp)->pNext){ int bPkOnly1 = 0; int bPkOnly2 = 0; - if( pIter->bPatchset ){ + if( pGrp->bPatch ){ bPkOnly1 = (*pp)->op==SQLITE_DELETE; bPkOnly2 = op==SQLITE_DELETE; } @@ -199909,16 +240238,86 @@ static int sessionChangesetToHash( break; } } + } - rc = sessionChangeMerge(pTab, bRebase, - pIter->bPatchset, pExist, op, bIndirect, aRec, nRec, &pChange + if( rc==SQLITE_OK ){ + rc = sessionChangeMerge(pTab, bRebase, + pGrp->bPatch, pExist, op, bIndirect, aRec, nRec, &pChange ); - if( rc ) break; - if( pChange ){ - pChange->pNext = pTab->apChange[iHash]; - pTab->apChange[iHash] = pChange; - pTab->nEntry++; + } + if( rc==SQLITE_OK && pChange ){ + pChange->pNext = pTab->apChange[iHash]; + pTab->apChange[iHash] = pChange; + pTab->nEntry++; + } + + return rc; +} + +/* +** Add the change currently indicated by iterator pIter to the hash table +** belonging to changegroup pGrp. +*/ +static int sessionOneChangeIterToHash( + sqlite3_changegroup *pGrp, + sqlite3_changeset_iter *pIter, + int bRebase +){ + u8 *aRec = &pIter->in.aData[pIter->in.iCurrent + 2]; + int nRec = (pIter->in.iNext - pIter->in.iCurrent) - 2; + const char *zTab = 0; + int nCol = 0; + int op = 0; + int bIndirect = 0; + int rc = SQLITE_OK; + SessionTable *pTab = 0; + + /* Ensure that only changesets, or only patchsets, but not a mixture + ** of both, are being combined. It is an error to try to combine a + ** changeset and a patchset. */ + if( pGrp->pList==0 ){ + pGrp->bPatch = pIter->bPatchset; + }else if( pIter->bPatchset!=pGrp->bPatch ){ + rc = SQLITE_ERROR; + } + + if( rc==SQLITE_OK ){ + sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect); + rc = sessionChangesetFindTable(pGrp, zTab, pIter, &pTab); + } + + if( rc==SQLITE_OK ){ + rc = sessionOneChangeToHash( + pGrp, pTab, op, bIndirect, nCol, aRec, nRec, bRebase + ); + } + + if( rc==SQLITE_OK ) rc = pIter->rc; + return rc; +} + +/* +** Add all changes in the changeset traversed by the iterator passed as +** the first argument to the changegroup hash tables. +*/ +static int sessionChangesetToHash( + sqlite3_changeset_iter *pIter, /* Iterator to read from */ + sqlite3_changegroup *pGrp, /* Changegroup object to add changeset to */ + int bRebase /* True if hash table is for rebasing */ +){ + u8 *aRec; + int nRec; + int rc = SQLITE_OK; + + pIter->in.bNoDiscard = 1; + while( SQLITE_ROW==(sessionChangesetNext(pIter, &aRec, &nRec, 0)) ){ + if( bRebase && pIter->bPatchset ){ + /* A patchset may not be used as a rebase */ + rc = SQLITE_ERROR; + }else{ + rc = sessionOneChangeIterToHash(pGrp, pIter, bRebase); } + if( rc!=SQLITE_OK ) break; } if( rc==SQLITE_OK ) rc = pIter->rc; @@ -199931,7 +240330,7 @@ static int sessionChangesetToHash( ** ** If xOutput is not NULL, then the changeset/patchset is returned to the ** user via one or more calls to xOutput, as with the other streaming -** interfaces. +** interfaces. ** ** Or, if xOutput is NULL, then (*ppOut) is populated with a pointer to a ** buffer containing the output changeset before this function returns. In @@ -199956,7 +240355,7 @@ static int sessionChangegroupOutput( assert( xOutput==0 || (ppOut==0 && pnOut==0) ); /* Create the serialized output changeset based on the contents of the - ** hash tables attached to the SessionTable objects in list p->pList. + ** hash tables attached to the SessionTable objects in list p->pList. */ for(pTab=pGrp->pList; rc==SQLITE_OK && pTab; pTab=pTab->pNext){ int i; @@ -199980,9 +240379,9 @@ static int sessionChangegroupOutput( if( rc==SQLITE_OK ){ if( xOutput ){ if( buf.nBuf>0 ) rc = xOutput(pOut, buf.aBuf, buf.nBuf); - }else{ + }else if( ppOut ){ *ppOut = buf.aBuf; - *pnOut = buf.nBuf; + if( pnOut ) *pnOut = buf.nBuf; buf.aBuf = 0; } } @@ -200007,6 +240406,58 @@ SQLITE_API int sqlite3changegroup_new(sqlite3_changegroup **pp){ return rc; } +/* +** Configure a changegroup object. +*/ +SQLITE_API int sqlite3changegroup_config( + sqlite3_changegroup *pGrp, + int op, + void *pArg +){ + int rc = SQLITE_OK; + + switch( op ){ + case SQLITE_CHANGEGROUP_CONFIG_PATCHSET: { + int arg = *(int*)pArg; + if( pGrp->pList==0 && arg>=0 ){ + pGrp->bPatch = (arg>0); + } + *(int*)pArg = pGrp->bPatch; + break; + } + default: + rc = SQLITE_MISUSE; + break; + } + + return rc; +} + +/* +** Provide a database schema to the changegroup object. +*/ +SQLITE_API int sqlite3changegroup_schema( + sqlite3_changegroup *pGrp, + sqlite3 *db, + const char *zDb +){ + int rc = SQLITE_OK; + + if( pGrp->pList || pGrp->db ){ + /* Cannot add a schema after one or more calls to sqlite3changegroup_add(), + ** or after sqlite3changegroup_schema() has already been called. */ + rc = SQLITE_MISUSE; + }else{ + pGrp->zDb = sqlite3_mprintf("%s", zDb); + if( pGrp->zDb==0 ){ + rc = SQLITE_NOMEM; + }else{ + pGrp->db = db; + } + } + return rc; +} + /* ** Add the changeset currently stored in buffer pData, size nData bytes, ** to changeset-group p. @@ -200023,6 +240474,28 @@ SQLITE_API int sqlite3changegroup_add(sqlite3_changegroup *pGrp, int nData, void return rc; } +/* +** Add a single change to a changeset-group. +*/ +SQLITE_API int sqlite3changegroup_add_change( + sqlite3_changegroup *pGrp, + sqlite3_changeset_iter *pIter +){ + int rc = SQLITE_OK; + + if( pIter->in.iCurrent==pIter->in.iNext + || pIter->rc!=SQLITE_OK + || pIter->bInvert + ){ + /* Iterator does not point to any valid entry or is an INVERT iterator. */ + rc = SQLITE_ERROR; + }else{ + pIter->in.bNoDiscard = 1; + rc = sessionOneChangeIterToHash(pGrp, pIter, 0); + } + return rc; +} + /* ** Obtain a buffer containing a changeset representing the concatenation ** of all changesets added to the group so far. @@ -200059,7 +240532,7 @@ SQLITE_API int sqlite3changegroup_add_strm( */ SQLITE_API int sqlite3changegroup_output_strm( sqlite3_changegroup *pGrp, - int (*xOutput)(void *pOut, const void *pData, int nData), + int (*xOutput)(void *pOut, const void *pData, int nData), void *pOut ){ return sessionChangegroupOutput(pGrp, xOutput, pOut, 0, 0); @@ -200070,12 +240543,20 @@ SQLITE_API int sqlite3changegroup_output_strm( */ SQLITE_API void sqlite3changegroup_delete(sqlite3_changegroup *pGrp){ if( pGrp ){ - sessionDeleteTable(pGrp->pList); + int ii; + for(ii=0; iicd.nBufAlloc; ii++){ + sqlite3_free(pGrp->cd.aBuf[ii].aBuf); + } + sqlite3_free(pGrp->cd.record.aBuf); + sqlite3_free(pGrp->cd.aBuf); + sqlite3_free(pGrp->zDb); + sessionDeleteTable(0, pGrp->pList); + sqlite3_free(pGrp->rec.aBuf); sqlite3_free(pGrp); } } -/* +/* ** Combine two changesets together. */ SQLITE_API int sqlite3changeset_concat( @@ -200142,7 +240623,7 @@ struct sqlite3_rebaser { /* ** Buffers a1 and a2 must both contain a sessions module record nCol -** fields in size. This function appends an nCol sessions module +** fields in size. This function appends an nCol sessions module ** record to buffer pBuf that is a copy of a1, except that for ** each field that is undefined in a1[], swap in the field from a2[]. */ @@ -200153,14 +240634,17 @@ static void sessionAppendRecordMerge( u8 *a2, int n2, /* Record 2 */ int *pRc /* IN/OUT: error code */ ){ - sessionBufferGrow(pBuf, n1+n2, pRc); + u8 *a1Eof = &a1[n1]; + u8 *a2Eof = &a2[n2]; + + sessionBufferGrow(pBuf, (i64)n1+n2, pRc); if( *pRc==SQLITE_OK ){ int i; u8 *pOut = &pBuf->aBuf[pBuf->nBuf]; for(i=0; i0 && (*a1==0 || *a1==0xFF)) ){ memcpy(pOut, a2, nn2); pOut += nn2; }else{ @@ -200177,20 +240661,20 @@ static void sessionAppendRecordMerge( } /* -** This function is called when rebasing a local UPDATE change against one +** This function is called when rebasing a local UPDATE change against one ** or more remote UPDATE changes. The aRec/nRec buffer contains the current ** old.* and new.* records for the change. The rebase buffer (a single ** record) is in aChange/nChange. The rebased change is appended to buffer ** pBuf. ** -** Rebasing the UPDATE involves: +** Rebasing the UPDATE involves: ** ** * Removing any changes to fields for which the corresponding field ** in the rebase buffer is set to "replaced" (type 0xFF). If this ** means the UPDATE change updates no fields, nothing is appended ** to the output buffer. ** -** * For each field modified by the local change for which the +** * For each field modified by the local change for which the ** corresponding field in the rebase buffer is not "undefined" (0x00) ** or "replaced" (0xFF), the old.* value is replaced by the value ** in the rebase buffer. @@ -200202,24 +240686,25 @@ static void sessionAppendPartialUpdate( u8 *aChange, int nChange, /* Record to rebase against */ int *pRc /* IN/OUT: Return Code */ ){ - sessionBufferGrow(pBuf, 2+nRec+nChange, pRc); + sessionBufferGrow(pBuf, (i64)2+nRec+nChange, pRc); if( *pRc==SQLITE_OK ){ int bData = 0; u8 *pOut = &pBuf->aBuf[pBuf->nBuf]; int i; u8 *a1 = aRec; u8 *a2 = aChange; + u8 *a2Eof = &a2[nChange]; *pOut++ = SQLITE_UPDATE; *pOut++ = pIter->bIndirect; for(i=0; inCol; i++){ int n1 = sessionSerialLen(a1); - int n2 = sessionSerialLen(a2); - if( pIter->abPK[i] || a2[0]==0 ){ - if( !pIter->abPK[i] ) bData = 1; + int n2 = (a2>=a2Eof) ? 0 : sessionSerialLen(a2); + if( n2<=0 || pIter->abPK[i] || a2[0]==0 ){ + if( !pIter->abPK[i] && a1[0] ) bData = 1; memcpy(pOut, a1, n1); pOut += n1; - }else if( a2[0]!=0xFF ){ + }else if( a2[0]!=0xFF && a1[0] ){ bData = 1; memcpy(pOut, a2, n2); pOut += n2; @@ -200249,15 +240734,15 @@ static void sessionAppendPartialUpdate( } /* -** pIter is configured to iterate through a changeset. This function rebases -** that changeset according to the current configuration of the rebaser +** pIter is configured to iterate through a changeset. This function rebases +** that changeset according to the current configuration of the rebaser ** object passed as the first argument. If no error occurs and argument xOutput ** is not NULL, then the changeset is returned to the caller by invoking ** xOutput zero or more times and SQLITE_OK returned. Or, if xOutput is NULL, ** then (*ppOut) is set to point to a buffer containing the rebased changeset ** before this function returns. In this case (*pnOut) is set to the size of ** the buffer in bytes. It is the responsibility of the caller to eventually -** free the (*ppOut) buffer using sqlite3_free(). +** free the (*ppOut) buffer using sqlite3_free(). ** ** If an error occurs, an SQLite error code is returned. If ppOut and ** pnOut are not NULL, then the two output parameters are set to 0 before @@ -200335,7 +240820,7 @@ static int sessionRebase( sessionAppendByte(&sOut, SQLITE_INSERT, &rc); sessionAppendByte(&sOut, pIter->bIndirect, &rc); sessionAppendRecordMerge(&sOut, pIter->nCol, - pCsr, nRec-(pCsr-aRec), + pCsr, nRec-(pCsr-aRec), pChange->aRecord, pChange->nRecord, &rc ); } @@ -200382,7 +240867,7 @@ static int sessionRebase( if( sOut.nBuf>0 ){ rc = xOutput(pOut, sOut.aBuf, sOut.nBuf); } - }else{ + }else if( ppOut ){ *ppOut = (void*)sOut.aBuf; *pnOut = sOut.nBuf; sOut.aBuf = 0; @@ -200392,7 +240877,7 @@ static int sessionRebase( return rc; } -/* +/* ** Create a new rebaser object. */ SQLITE_API int sqlite3rebaser_create(sqlite3_rebaser **ppNew){ @@ -200409,15 +240894,15 @@ SQLITE_API int sqlite3rebaser_create(sqlite3_rebaser **ppNew){ return rc; } -/* +/* ** Call this one or more times to configure a rebaser. */ SQLITE_API int sqlite3rebaser_configure( - sqlite3_rebaser *p, + sqlite3_rebaser *p, int nRebase, const void *pRebase ){ - sqlite3_changeset_iter *pIter = 0; /* Iterator opened on pData/nData */ int rc; /* Return code */ + sqlite3_changeset_iter *pIter = 0; /* Iterator opened on pData/nData */ rc = sqlite3changeset_start(&pIter, nRebase, (void*)pRebase); if( rc==SQLITE_OK ){ rc = sessionChangesetToHash(pIter, &p->grp, 1); @@ -200426,15 +240911,15 @@ SQLITE_API int sqlite3rebaser_configure( return rc; } -/* -** Rebase a changeset according to current rebaser configuration +/* +** Rebase a changeset according to current rebaser configuration */ SQLITE_API int sqlite3rebaser_rebase( sqlite3_rebaser *p, - int nIn, const void *pIn, - int *pnOut, void **ppOut + int nIn, const void *pIn, + int *pnOut, void **ppOut ){ - sqlite3_changeset_iter *pIter = 0; /* Iterator to skip through input */ + sqlite3_changeset_iter *pIter = 0; /* Iterator to skip through input */ int rc = sqlite3changeset_start(&pIter, nIn, (void*)pIn); if( rc==SQLITE_OK ){ @@ -200445,8 +240930,8 @@ SQLITE_API int sqlite3rebaser_rebase( return rc; } -/* -** Rebase a changeset according to current rebaser configuration +/* +** Rebase a changeset according to current rebaser configuration */ SQLITE_API int sqlite3rebaser_rebase_strm( sqlite3_rebaser *p, @@ -200455,7 +240940,7 @@ SQLITE_API int sqlite3rebaser_rebase_strm( int (*xOutput)(void *pOut, const void *pData, int nData), void *pOut ){ - sqlite3_changeset_iter *pIter = 0; /* Iterator to skip through input */ + sqlite3_changeset_iter *pIter = 0; /* Iterator to skip through input */ int rc = sqlite3changeset_start_strm(&pIter, xInput, pIn); if( rc==SQLITE_OK ){ @@ -200466,17 +240951,18 @@ SQLITE_API int sqlite3rebaser_rebase_strm( return rc; } -/* -** Destroy a rebaser object +/* +** Destroy a rebaser object */ SQLITE_API void sqlite3rebaser_delete(sqlite3_rebaser *p){ if( p ){ - sessionDeleteTable(p->grp.pList); + sessionDeleteTable(0, p->grp.pList); + sqlite3_free(p->grp.rec.aBuf); sqlite3_free(p); } } -/* +/* ** Global configuration */ SQLITE_API int sqlite3session_config(int op, void *pArg){ @@ -200497,21 +240983,369 @@ SQLITE_API int sqlite3session_config(int op, void *pArg){ return rc; } +/* +** Begin adding a change to a changegroup object. +*/ +SQLITE_API int sqlite3changegroup_change_begin( + sqlite3_changegroup *pGrp, + int eOp, + const char *zTab, + int bIndirect, + char **pzErr +){ + SessionTable *pTab = 0; + int rc = SQLITE_OK; + + if( pGrp->cd.pTab ){ + rc = SQLITE_MISUSE; + }else if( eOp!=SQLITE_INSERT && eOp!=SQLITE_UPDATE && eOp!=SQLITE_DELETE ){ + rc = SQLITE_ERROR; + }else{ + rc = sessionChangesetFindTable(pGrp, zTab, 0, &pTab); + } + if( rc==SQLITE_OK ){ + if( pTab==0 ){ + if( pzErr ){ + *pzErr = sqlite3_mprintf("no such table: %s", zTab); + } + rc = SQLITE_ERROR; + }else{ + int nReq = pTab->nCol * (eOp==SQLITE_UPDATE ? 2 : 1); + pGrp->cd.pTab = pTab; + pGrp->cd.eOp = eOp; + pGrp->cd.bIndirect = bIndirect; + + if( pGrp->cd.nBufAlloccd.aBuf, nReq * sizeof(SessionBuffer) + ); + if( aBuf==0 ){ + rc = SQLITE_NOMEM; + }else{ + memset(&aBuf[pGrp->cd.nBufAlloc], 0, + sizeof(SessionBuffer) * (nReq - pGrp->cd.nBufAlloc) + ); + pGrp->cd.aBuf = aBuf; + pGrp->cd.nBufAlloc = nReq; + } + } + +#ifdef SQLITE_DEBUG + { + /* Assert that all column values are currently undefined */ + int ii; + for(ii=0; iicd.nBufAlloc; ii++){ + assert( pGrp->cd.aBuf[ii].nBuf==0 ); + } + } +#endif + } + } + + return rc; +} + +/* +** This function does processing common to the _change_int64(), _change_text() +** and other similar APIs. +*/ +static int checkChangeParams( + sqlite3_changegroup *pGrp, + int bNew, + int iCol, + sqlite3_int64 nReq, + SessionBuffer **ppBuf +){ + int rc = SQLITE_OK; + if( pGrp->cd.pTab==0 ){ + rc = SQLITE_MISUSE; + }else if( iCol<0 || iCol>=pGrp->cd.pTab->nCol ){ + rc = SQLITE_RANGE; + }else if( + (bNew && pGrp->cd.eOp==SQLITE_DELETE) + || (!bNew && pGrp->cd.eOp==SQLITE_INSERT) + ){ + rc = SQLITE_ERROR; + }else{ + SessionBuffer *pBuf = &pGrp->cd.aBuf[iCol]; + if( pGrp->cd.eOp==SQLITE_UPDATE && bNew ){ + pBuf += pGrp->cd.pTab->nCol; + } + pBuf->nBuf = 0; + sessionBufferGrow(pBuf, nReq, &rc); + pBuf->nBuf = nReq; + *ppBuf = pBuf; + } + return rc; +} + +/* +** Configure the change currently under construction with an integer value. +*/ +SQLITE_API int sqlite3changegroup_change_int64( + sqlite3_changegroup *pGrp, + int bNew, + int iCol, + sqlite3_int64 iVal +){ + int rc = SQLITE_OK; + SessionBuffer *pBuf = 0; + + if( SQLITE_OK!=(rc = checkChangeParams(pGrp, bNew, iCol, 9, &pBuf)) ){ + return rc; + } + + pBuf->aBuf[0] = SQLITE_INTEGER; + sessionPutI64(&pBuf->aBuf[1], iVal); + return SQLITE_OK; +} + +/* +** Configure the change currently under construction with a null value. +*/ +SQLITE_API int sqlite3changegroup_change_null( + sqlite3_changegroup *pGrp, + int bNew, + int iCol +){ + int rc = SQLITE_OK; + SessionBuffer *pBuf = 0; + + if( SQLITE_OK!=(rc = checkChangeParams(pGrp, bNew, iCol, 1, &pBuf)) ){ + return rc; + } + + pBuf->aBuf[0] = SQLITE_NULL; + return SQLITE_OK; +} + +/* +** Configure the change currently under construction with a real value. +*/ +SQLITE_API int sqlite3changegroup_change_double( + sqlite3_changegroup *pGrp, + int bNew, + int iCol, + double fVal +){ + int rc = SQLITE_OK; + SessionBuffer *pBuf = 0; + + if( SQLITE_OK!=(rc = checkChangeParams(pGrp, bNew, iCol, 9, &pBuf)) ){ + return rc; + } + + pBuf->aBuf[0] = SQLITE_FLOAT; + sessionPutDouble(&pBuf->aBuf[1], fVal); + return SQLITE_OK; +} + +/* +** Configure the change currently under construction with a text value. +*/ +SQLITE_API int sqlite3changegroup_change_text( + sqlite3_changegroup *pGrp, + int bNew, + int iCol, + const char *pVal, + int nVal +){ + int nText = nVal>=0 ? nVal : strlen(pVal); + sqlite3_int64 nByte = 1 + sessionVarintLen(nText) + nText; + int rc = SQLITE_OK; + SessionBuffer *pBuf = 0; + + if( SQLITE_OK!=(rc = checkChangeParams(pGrp, bNew, iCol, nByte, &pBuf)) ){ + return rc; + } + + pBuf->aBuf[0] = SQLITE_TEXT; + pBuf->nBuf = (1 + sessionVarintPut(&pBuf->aBuf[1], nText)); + memcpy(&pBuf->aBuf[pBuf->nBuf], pVal, nText); + pBuf->nBuf += nText; + + return SQLITE_OK; +} + +/* +** Configure the change currently under construction with a blob value. +*/ +SQLITE_API int sqlite3changegroup_change_blob( + sqlite3_changegroup *pGrp, + int bNew, + int iCol, + const void *pVal, + int nVal +){ + sqlite3_int64 nByte = 1 + sessionVarintLen(nVal) + (i64)nVal; + int rc = SQLITE_OK; + SessionBuffer *pBuf = 0; + + if( SQLITE_OK!=(rc = checkChangeParams(pGrp, bNew, iCol, nByte, &pBuf)) ){ + return rc; + } + + pBuf->aBuf[0] = SQLITE_BLOB; + pBuf->nBuf = (1 + sessionVarintPut(&pBuf->aBuf[1], nVal)); + memcpy(&pBuf->aBuf[pBuf->nBuf], pVal, nVal); + pBuf->nBuf += nVal; + + return SQLITE_OK; +} + +/* +** Finish any change currently being constructed by the changegroup object. +*/ +SQLITE_API int sqlite3changegroup_change_finish( + sqlite3_changegroup *pGrp, + int bDiscard, + char **pzErr +){ + int rc = SQLITE_OK; + if( pGrp->cd.pTab ){ + SessionBuffer *aBuf = pGrp->cd.aBuf; + int ii; + + if( bDiscard==0 ){ + int nBuf = pGrp->cd.pTab->nCol; + u8 eUndef = SQLITE_NULL; + if( pGrp->cd.eOp==SQLITE_UPDATE ){ + for(ii=0; iicd.pTab->abPK[ii] ){ + if( aBuf[ii].nBuf<=1 ){ + *pzErr = sqlite3_mprintf( + "invalid change: %s value in PK of old.* record", + aBuf[ii].nBuf==1 ? "null" : "undefined" + ); + rc = SQLITE_ERROR; + break; + }else if( aBuf[ii + nBuf].nBuf>0 ){ + *pzErr = sqlite3_mprintf( + "invalid change: defined value in PK of new.* record" + ); + rc = SQLITE_ERROR; + break; + } + }else + if( pGrp->bPatch==0 && (aBuf[ii].nBuf>0)!=(aBuf[ii+nBuf].nBuf>0) ){ + *pzErr = sqlite3_mprintf( + "invalid change: column %d " + "- old.* value is %sdefined but new.* is %sdefined", + ii, aBuf[ii].nBuf ? "" : "un", aBuf[ii+nBuf].nBuf ? "" : "un" + ); + rc = SQLITE_ERROR; + break; + } + } + eUndef = 0x00; + if( pGrp->bPatch==0 ) nBuf = nBuf * 2; + }else{ + for(ii=0; iicd.pTab->abPK[ii]; + if( (pGrp->cd.eOp==SQLITE_INSERT || pGrp->bPatch==0 || isPK) + && aBuf[ii].nBuf==0 + ){ + *pzErr = sqlite3_mprintf( + "invalid change: column %d is undefined", ii + ); + rc = SQLITE_ERROR; + break; + } + if( aBuf[ii].nBuf==1 && isPK ){ + *pzErr = sqlite3_mprintf( + "invalid change: null value in PK" + ); + rc = SQLITE_ERROR; + break; + } + } + } + + pGrp->cd.record.nBuf = 0; + for(ii=0; iicd.aBuf[ii]; + if( pGrp->bPatch ){ + if( pGrp->cd.pTab->abPK[ii]==0 ){ + if( pGrp->cd.eOp==SQLITE_UPDATE ){ + p += pGrp->cd.pTab->nCol; + }else if( pGrp->cd.eOp==SQLITE_DELETE ){ + continue; + } + } + } + if( 0==sessionBufferGrow(&pGrp->cd.record, p->nBuf?p->nBuf:1, &rc) ){ + if( p->nBuf ){ + memcpy(&pGrp->cd.record.aBuf[pGrp->cd.record.nBuf],p->aBuf,p->nBuf); + pGrp->cd.record.nBuf += p->nBuf; + }else{ + pGrp->cd.record.aBuf[pGrp->cd.record.nBuf++] = eUndef; + } + } + } + if( rc==SQLITE_OK ){ + rc = sessionOneChangeToHash( + pGrp, pGrp->cd.pTab, + pGrp->cd.eOp, pGrp->cd.bIndirect, pGrp->cd.pTab->nCol, + pGrp->cd.record.aBuf, pGrp->cd.record.nBuf, 0 + ); + } + } + + /* Reset all aBuf[] entries to "undefined". */ + { + int nZero = pGrp->cd.pTab->nCol; + if( pGrp->cd.eOp==SQLITE_UPDATE ) nZero += nZero; + for(ii=0; iicd.aBuf[ii].nBuf = 0; + } + } + pGrp->cd.pTab = 0; + } + + return rc; +} + #endif /* SQLITE_ENABLE_SESSION && SQLITE_ENABLE_PREUPDATE_HOOK */ /************** End of sqlite3session.c **************************************/ /************** Begin file fts5.c ********************************************/ +/* +** This, the "fts5.c" source file, is a composite file that is itself +** assembled from the following files: +** +** fts5.h +** fts5Int.h +** fts5parse.h <--- Generated from fts5parse.y by Lemon +** fts5parse.c <--- Generated from fts5parse.y by Lemon +** fts5_aux.c +** fts5_buffer.c +** fts5_config.c +** fts5_expr.c +** fts5_hash.c +** fts5_index.c +** fts5_main.c +** fts5_storage.c +** fts5_tokenize.c +** fts5_unicode2.c +** fts5_varint.c +** fts5_vocab.c +*/ +#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS5) -#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS5) - -#if !defined(NDEBUG) && !defined(SQLITE_DEBUG) +#if !defined(NDEBUG) && !defined(SQLITE_DEBUG) # define NDEBUG 1 #endif #if defined(NDEBUG) && defined(SQLITE_DEBUG) # undef NDEBUG #endif +#ifdef HAVE_STDINT_H +/* #include */ +#endif +#ifdef HAVE_INTTYPES_H +/* #include */ +#endif /* ** 2014 May 31 ** @@ -200524,7 +241358,7 @@ SQLITE_API int sqlite3session_config(int op, void *pArg){ ** ****************************************************************************** ** -** Interfaces to extend FTS5. Using the interfaces defined in this file, +** Interfaces to extend FTS5. Using the interfaces defined in this file, ** FTS5 may be extended with: ** ** * custom tokenizers, and @@ -200569,19 +241403,19 @@ struct Fts5PhraseIter { ** EXTENSION API FUNCTIONS ** ** xUserData(pFts): -** Return a copy of the context pointer the extension function was -** registered with. +** Return a copy of the pUserData pointer passed to the xCreateFunction() +** API when the extension function was registered. ** ** xColumnTotalSize(pFts, iCol, pnToken): ** If parameter iCol is less than zero, set output variable *pnToken ** to the total number of tokens in the FTS5 table. Or, if iCol is ** non-negative but less than the number of columns in the table, return -** the total number of tokens in column iCol, considering all rows in +** the total number of tokens in column iCol, considering all rows in ** the FTS5 table. ** ** If parameter iCol is greater than or equal to the number of columns ** in the table, SQLITE_RANGE is returned. Or, if an error occurs (e.g. -** an OOM condition or IO error), an appropriate SQLite error code is +** an OOM condition or IO error), an appropriate SQLite error code is ** returned. ** ** xColumnCount(pFts): @@ -200595,15 +241429,18 @@ struct Fts5PhraseIter { ** ** If parameter iCol is greater than or equal to the number of columns ** in the table, SQLITE_RANGE is returned. Or, if an error occurs (e.g. -** an OOM condition or IO error), an appropriate SQLite error code is +** an OOM condition or IO error), an appropriate SQLite error code is ** returned. ** ** This function may be quite inefficient if used with an FTS5 table ** created with the "columnsize=0" option. ** ** xColumnText: -** This function attempts to retrieve the text of column iCol of the -** current document. If successful, (*pz) is set to point to a buffer +** If parameter iCol is less than zero, or greater than or equal to the +** number of columns in the table, SQLITE_RANGE is returned. +** +** Otherwise, this function attempts to retrieve the text of column iCol of +** the current document. If successful, (*pz) is set to point to a buffer ** containing the text in utf-8 encoding, (*pn) is set to the size in bytes ** (not characters) of the buffer and SQLITE_OK is returned. Otherwise, ** if an error occurs, an SQLite error code is returned and the final values @@ -200613,8 +241450,10 @@ struct Fts5PhraseIter { ** Returns the number of phrases in the current query expression. ** ** xPhraseSize: -** Returns the number of tokens in phrase iPhrase of the query. Phrases -** are numbered starting from zero. +** If parameter iCol is less than zero, or greater than or equal to the +** number of phrases in the current query, as returned by xPhraseCount, +** 0 is returned. Otherwise, this function returns the number of tokens in +** phrase iPhrase of the query. Phrases are numbered starting from zero. ** ** xInstCount: ** Set *pnInst to the total number of occurrences of all phrases within @@ -200622,23 +241461,24 @@ struct Fts5PhraseIter { ** an error code (i.e. SQLITE_NOMEM) if an error occurs. ** ** This API can be quite slow if used with an FTS5 table created with the -** "detail=none" or "detail=column" option. If the FTS5 table is created -** with either "detail=none" or "detail=column" and "content=" option +** "detail=none" or "detail=column" option. If the FTS5 table is created +** with either "detail=none" or "detail=column" and "content=" option ** (i.e. if it is a contentless table), then this API always returns 0. ** ** xInst: ** Query for the details of phrase match iIdx within the current row. ** Phrase matches are numbered starting from zero, so the iIdx argument ** should be greater than or equal to zero and smaller than the value -** output by xInstCount(). +** output by xInstCount(). If iIdx is less than zero or greater than +** or equal to the value returned by xInstCount(), SQLITE_RANGE is returned. ** -** Usually, output parameter *piPhrase is set to the phrase number, *piCol +** Otherwise, output parameter *piPhrase is set to the phrase number, *piCol ** to the column in which it occurs and *piOff the token offset of the -** first token of the phrase. Returns SQLITE_OK if successful, or an error -** code (i.e. SQLITE_NOMEM) if an error occurs. +** first token of the phrase. SQLITE_OK is returned if successful, or an +** error code (i.e. SQLITE_NOMEM) if an error occurs. ** ** This API can be quite slow if used with an FTS5 table created with the -** "detail=none" or "detail=column" option. +** "detail=none" or "detail=column" option. ** ** xRowid: ** Returns the rowid of the current row. @@ -200654,13 +241494,17 @@ struct Fts5PhraseIter { ** ** with $p set to a phrase equivalent to the phrase iPhrase of the ** current query is executed. Any column filter that applies to -** phrase iPhrase of the current query is included in $p. For each -** row visited, the callback function passed as the fourth argument -** is invoked. The context and API objects passed to the callback +** phrase iPhrase of the current query is included in $p. For each +** row visited, the callback function passed as the fourth argument +** is invoked. The context and API objects passed to the callback ** function may be used to access the properties of each matched row. -** Invoking Api.xUserData() returns a copy of the pointer passed as +** Invoking Api.xUserData() returns a copy of the pointer passed as ** the third argument to pUserData. ** +** If parameter iPhrase is less than zero, or greater than or equal to +** the number of phrases in the query, as returned by xPhraseCount(), +** this function returns SQLITE_RANGE. +** ** If the callback function returns any value other than SQLITE_OK, the ** query is abandoned and the xQueryPhrase function returns immediately. ** If the returned value is SQLITE_DONE, xQueryPhrase returns SQLITE_OK. @@ -200673,14 +241517,14 @@ struct Fts5PhraseIter { ** ** xSetAuxdata(pFts5, pAux, xDelete) ** -** Save the pointer passed as the second argument as the extension functions +** Save the pointer passed as the second argument as the extension function's ** "auxiliary data". The pointer may then be retrieved by the current or any ** future invocation of the same fts5 extension function made as part of ** the same MATCH query using the xGetAuxdata() API. ** ** Each extension function is allocated a single auxiliary data slot for -** each FTS query (MATCH expression). If the extension function is invoked -** more than once for a single FTS query, then all invocations share a +** each FTS query (MATCH expression). If the extension function is invoked +** more than once for a single FTS query, then all invocations share a ** single auxiliary data context. ** ** If there is already an auxiliary data pointer when this function is @@ -200699,7 +241543,7 @@ struct Fts5PhraseIter { ** ** xGetAuxdata(pFts5, bClear) ** -** Returns the current auxiliary data pointer for the fts5 extension +** Returns the current auxiliary data pointer for the fts5 extension ** function. See the xSetAuxdata() method for details. ** ** If the bClear argument is non-zero, then the auxiliary data is cleared @@ -200719,7 +241563,7 @@ struct Fts5PhraseIter { ** method, to iterate through all instances of a single query phrase within ** the current row. This is the same information as is accessible via the ** xInstCount/xInst APIs. While the xInstCount/xInst APIs are more convenient -** to use, this API may be faster under some circumstances. To iterate +** to use, this API may be faster under some circumstances. To iterate ** through instances of phrase iPhrase, use the following code: ** ** Fts5PhraseIter iter; @@ -200737,11 +241581,15 @@ struct Fts5PhraseIter { ** xPhraseFirstColumn() and xPhraseNextColumn() as illustrated below). ** ** This API can be quite slow if used with an FTS5 table created with the -** "detail=none" or "detail=column" option. If the FTS5 table is created -** with either "detail=none" or "detail=column" and "content=" option +** "detail=none" or "detail=column" option. If the FTS5 table is created +** with either "detail=none" or "detail=column" and "content=" option ** (i.e. if it is a contentless table), then this API always iterates ** through an empty set (all calls to xPhraseFirst() set iCol to -1). ** +** In all cases, matches are visited in (column ASC, offset ASC) order. +** i.e. all those in column 0, sorted by offset, followed by those in +** column 1, etc. +** ** xPhraseNext() ** See xPhraseFirst above. ** @@ -200762,22 +241610,93 @@ struct Fts5PhraseIter { ** } ** ** This API can be quite slow if used with an FTS5 table created with the -** "detail=none" option. If the FTS5 table is created with either -** "detail=none" "content=" option (i.e. if it is a contentless table), -** then this API always iterates through an empty set (all calls to +** "detail=none" option. If the FTS5 table is created with either +** "detail=none" "content=" option (i.e. if it is a contentless table), +** then this API always iterates through an empty set (all calls to ** xPhraseFirstColumn() set iCol to -1). ** ** The information accessed using this API and its companion ** xPhraseFirstColumn() may also be obtained using xPhraseFirst/xPhraseNext ** (or xInst/xInstCount). The chief advantage of this API is that it is ** significantly more efficient than those alternatives when used with -** "detail=column" tables. +** "detail=column" tables. ** ** xPhraseNextColumn() ** See xPhraseFirstColumn above. +** +** xQueryToken(pFts5, iPhrase, iToken, ppToken, pnToken) +** This is used to access token iToken of phrase iPhrase of the current +** query. Before returning, output parameter *ppToken is set to point +** to a buffer containing the requested token, and *pnToken to the +** size of this buffer in bytes. +** +** If iPhrase or iToken are less than zero, or if iPhrase is greater than +** or equal to the number of phrases in the query as reported by +** xPhraseCount(), or if iToken is equal to or greater than the number of +** tokens in the phrase, SQLITE_RANGE is returned and *ppToken and *pnToken + are both zeroed. +** +** The output text is not a copy of the query text that specified the +** token. It is the output of the tokenizer module. For tokendata=1 +** tables, this includes any embedded 0x00 and trailing data. +** +** xInstToken(pFts5, iIdx, iToken, ppToken, pnToken) +** This is used to access token iToken of phrase hit iIdx within the +** current row. If iIdx is less than zero or greater than or equal to the +** value returned by xInstCount(), SQLITE_RANGE is returned. Otherwise, +** output variable (*ppToken) is set to point to a buffer containing the +** matching document token, and (*pnToken) to the size of that buffer in +** bytes. +** +** The output text is not a copy of the document text that was tokenized. +** It is the output of the tokenizer module. For tokendata=1 tables, this +** includes any embedded 0x00 and trailing data. +** +** This API may be slow in some cases if the token identified by parameters +** iIdx and iToken matched a prefix token in the query. In most cases, the +** first call to this API for each prefix token in the query is forced +** to scan the portion of the full-text index that matches the prefix +** token to collect the extra data required by this API. If the prefix +** token matches a large number of token instances in the document set, +** this may be a performance problem. +** +** If the user knows in advance that a query may use this API for a +** prefix token, FTS5 may be configured to collect all required data as part +** of the initial querying of the full-text index, avoiding the second scan +** entirely. This also causes prefix queries that do not use this API to +** run more slowly and use more memory. FTS5 may be configured in this way +** either on a per-table basis using the [FTS5 insttoken | 'insttoken'] +** option, or on a per-query basis using the +** [fts5_insttoken | fts5_insttoken()] user function. +** +** This API can be quite slow if used with an FTS5 table created with the +** "detail=none" or "detail=column" option. +** +** xColumnLocale(pFts5, iIdx, pzLocale, pnLocale) +** If parameter iCol is less than zero, or greater than or equal to the +** number of columns in the table, SQLITE_RANGE is returned. +** +** Otherwise, this function attempts to retrieve the locale associated +** with column iCol of the current row. Usually, there is no associated +** locale, and output parameters (*pzLocale) and (*pnLocale) are set +** to NULL and 0, respectively. However, if the fts5_locale() function +** was used to associate a locale with the value when it was inserted +** into the fts5 table, then (*pzLocale) is set to point to a nul-terminated +** buffer containing the name of the locale in utf-8 encoding. (*pnLocale) +** is set to the size in bytes of the buffer, not including the +** nul-terminator. +** +** If successful, SQLITE_OK is returned. Or, if an error occurs, an +** SQLite error code is returned. The final value of the output parameters +** is undefined in this case. +** +** xTokenize_v2: +** Tokenize text using the tokenizer belonging to the FTS5 table. This +** API is the same as the xTokenize() API, except that it allows a tokenizer +** locale to be specified. */ struct Fts5ExtensionApi { - int iVersion; /* Currently always set to 3 */ + int iVersion; /* Currently always set to 4 */ void *(*xUserData)(Fts5Context*); @@ -200785,7 +241704,7 @@ struct Fts5ExtensionApi { int (*xRowCount)(Fts5Context*, sqlite3_int64 *pnRow); int (*xColumnTotalSize)(Fts5Context*, int iCol, sqlite3_int64 *pnToken); - int (*xTokenize)(Fts5Context*, + int (*xTokenize)(Fts5Context*, const char *pText, int nText, /* Text to tokenize */ void *pCtx, /* Context passed to xToken() */ int (*xToken)(void*, int, const char*, int, int, int) /* Callback */ @@ -200812,17 +241731,33 @@ struct Fts5ExtensionApi { int (*xPhraseFirstColumn)(Fts5Context*, int iPhrase, Fts5PhraseIter*, int*); void (*xPhraseNextColumn)(Fts5Context*, Fts5PhraseIter*, int *piCol); + + /* Below this point are iVersion>=3 only */ + int (*xQueryToken)(Fts5Context*, + int iPhrase, int iToken, + const char **ppToken, int *pnToken + ); + int (*xInstToken)(Fts5Context*, int iIdx, int iToken, const char**, int*); + + /* Below this point are iVersion>=4 only */ + int (*xColumnLocale)(Fts5Context*, int iCol, const char **pz, int *pn); + int (*xTokenize_v2)(Fts5Context*, + const char *pText, int nText, /* Text to tokenize */ + const char *pLocale, int nLocale, /* Locale to pass to tokenizer */ + void *pCtx, /* Context passed to xToken() */ + int (*xToken)(void*, int, const char*, int, int, int) /* Callback */ + ); }; -/* +/* ** CUSTOM AUXILIARY FUNCTIONS *************************************************************************/ /************************************************************************* ** CUSTOM TOKENIZERS ** -** Applications may also register custom tokenizer types. A tokenizer -** is registered by providing fts5 with a populated instance of the +** Applications may also register custom tokenizer types. A tokenizer +** is registered by providing fts5 with a populated instance of the ** following structure. All structure methods must be defined, setting ** any member of the fts5_tokenizer struct to NULL leads to undefined ** behaviour. The structure methods are expected to function as follows: @@ -200832,17 +241767,17 @@ struct Fts5ExtensionApi { ** A tokenizer instance is required to actually tokenize text. ** ** The first argument passed to this function is a copy of the (void*) -** pointer provided by the application when the fts5_tokenizer object -** was registered with FTS5 (the third argument to xCreateTokenizer()). +** pointer provided by the application when the fts5_tokenizer_v2 object +** was registered with FTS5 (the third argument to xCreateTokenizer()). ** The second and third arguments are an array of nul-terminated strings ** containing the tokenizer arguments, if any, specified following the ** tokenizer name as part of the CREATE VIRTUAL TABLE statement used ** to create the FTS5 table. ** -** The final argument is an output variable. If successful, (*ppOut) +** The final argument is an output variable. If successful, (*ppOut) ** should be set to point to the new tokenizer handle and SQLITE_OK ** returned. If an error occurs, some value other than SQLITE_OK should -** be returned. In this case, fts5 assumes that the final value of *ppOut +** be returned. In this case, fts5 assumes that the final value of *ppOut ** is undefined. ** ** xDelete: @@ -200851,12 +241786,12 @@ struct Fts5ExtensionApi { ** be invoked exactly once for each successful call to xCreate(). ** ** xTokenize: -** This function is expected to tokenize the nText byte string indicated +** This function is expected to tokenize the nText byte string indicated ** by argument pText. pText may or may not be nul-terminated. The first ** argument passed to this function is a pointer to an Fts5Tokenizer object ** returned by an earlier call to xCreate(). ** -** The second argument indicates the reason that FTS5 is requesting +** The third argument indicates the reason that FTS5 is requesting ** tokenization of the supplied text. This is always one of the following ** four values: ** @@ -200865,8 +241800,8 @@ struct Fts5ExtensionApi { ** determine the set of tokens to add to (or delete from) the ** FTS index. ** -**
                • FTS5_TOKENIZE_QUERY - A MATCH query is being executed -** against the FTS index. The tokenizer is being called to tokenize +**
                • FTS5_TOKENIZE_QUERY - A MATCH query is being executed +** against the FTS index. The tokenizer is being called to tokenize ** a bareword or quoted string specified as part of the query. ** **
                • (FTS5_TOKENIZE_QUERY | FTS5_TOKENIZE_PREFIX) - Same as @@ -200874,12 +241809,19 @@ struct Fts5ExtensionApi { ** followed by a "*" character, indicating that the last token ** returned by the tokenizer will be treated as a token prefix. ** -**
                • FTS5_TOKENIZE_AUX - The tokenizer is being invoked to +**
                • FTS5_TOKENIZE_AUX - The tokenizer is being invoked to ** satisfy an fts5_api.xTokenize() request made by an auxiliary ** function. Or an fts5_api.xColumnSize() request made by the same -** on a columnsize=0 database. +** on a columnsize=0 database. ** ** +** The sixth and seventh arguments passed to xTokenize() - pLocale and +** nLocale - are a pointer to a buffer containing the locale to use for +** tokenization (e.g. "en_US") and its size in bytes, respectively. The +** pLocale buffer is not nul-terminated. pLocale may be passed NULL (in +** which case nLocale is always 0) to indicate that the tokenizer should +** use its default locale. +** ** For each token in the input string, the supplied callback xToken() must ** be invoked. The first argument to it should be a copy of the pointer ** passed as the second argument to xTokenize(). The third and fourth @@ -200889,10 +241831,10 @@ struct Fts5ExtensionApi { ** which the token is derived within the input. ** ** The second argument passed to the xToken() callback ("tflags") should -** normally be set to 0. The exception is if the tokenizer supports +** normally be set to 0. The exception is if the tokenizer supports ** synonyms. In this case see the discussion below for details. ** -** FTS5 assumes the xToken() callback is invoked for each token in the +** FTS5 assumes the xToken() callback is invoked for each token in the ** order that they occur within the input text. ** ** If an xToken() callback returns any value other than SQLITE_OK, then @@ -200903,10 +241845,34 @@ struct Fts5ExtensionApi { ** may abandon the tokenization and return any error code other than ** SQLITE_OK or SQLITE_DONE. ** +** If the tokenizer is registered using an fts5_tokenizer_v2 object, +** then the xTokenize() method has two additional arguments - pLocale +** and nLocale. These specify the locale that the tokenizer should use +** for the current request. If pLocale and nLocale are both 0, then the +** tokenizer should use its default locale. Otherwise, pLocale points to +** an nLocale byte buffer containing the name of the locale to use as utf-8 +** text. pLocale is not nul-terminated. +** +** FTS5_TOKENIZER +** +** There is also an fts5_tokenizer object. This is an older, deprecated, +** version of fts5_tokenizer_v2. It is similar except that: +** +**
                    +**
                  • There is no "iVersion" field, and +**
                  • The xTokenize() method does not take a locale argument. +**
                  +** +** Legacy fts5_tokenizer tokenizers must be registered using the +** legacy xCreateTokenizer() function, instead of xCreateTokenizer_v2(). +** +** Tokenizer implementations registered using either API may be retrieved +** using both xFindTokenizer() and xFindTokenizer_v2(). +** ** SYNONYM SUPPORT ** ** Custom tokenizers may also support synonyms. Consider a case in which a -** user wishes to query for a phrase such as "first place". Using the +** user wishes to query for a phrase such as "first place". Using the ** built-in tokenizers, the FTS5 query 'first + place' will match instances ** of "first place" within the document set, but not alternative forms ** such as "1st place". In some applications, it would be better to match @@ -200915,8 +241881,8 @@ struct Fts5ExtensionApi { ** ** There are several ways to approach this in FTS5: ** -**
                  1. By mapping all synonyms to a single token. In this case, the -** In the above example, this means that the tokenizer returns the +**
                    1. By mapping all synonyms to a single token. In this case, using +** the above example, this means that the tokenizer returns the ** same token for inputs "first" and "1st". Say that token is in ** fact "first", so that when the user inserts the document "I won ** 1st place" entries are added to the index for tokens "i", "won", @@ -200926,34 +241892,34 @@ struct Fts5ExtensionApi { ** **
                    2. By querying the index for all synonyms of each query term ** separately. In this case, when tokenizing query text, the -** tokenizer may provide multiple synonyms for a single term -** within the document. FTS5 then queries the index for each +** tokenizer may provide multiple synonyms for a single term +** within the document. FTS5 then queries the index for each ** synonym individually. For example, faced with the query: ** ** ** ... MATCH 'first place' ** ** the tokenizer offers both "1st" and "first" as synonyms for the -** first token in the MATCH query and FTS5 effectively runs a query +** first token in the MATCH query and FTS5 effectively runs a query ** similar to: ** ** ** ... MATCH '(first OR 1st) place' ** ** except that, for the purposes of auxiliary functions, the query -** still appears to contain just two phrases - "(first OR 1st)" +** still appears to contain just two phrases - "(first OR 1st)" ** being treated as a single phrase. ** **
                    3. By adding multiple synonyms for a single term to the FTS index. ** Using this method, when tokenizing document text, the tokenizer -** provides multiple synonyms for each token. So that when a +** provides multiple synonyms for each token. So that when a ** document such as "I won first place" is tokenized, entries are ** added to the FTS index for "i", "won", "first", "1st" and ** "place". ** ** This way, even if the tokenizer does not provide synonyms ** when tokenizing query text (it should not - to do so would be -** inefficient), it doesn't matter if the user queries for +** inefficient), it doesn't matter if the user queries for ** 'first + place' or '1st + place', as there are entries in the ** FTS index corresponding to both forms of the first token. **
                    @@ -200974,11 +241940,11 @@ struct Fts5ExtensionApi { ** ** It is an error to specify the FTS5_TOKEN_COLOCATED flag the first time ** xToken() is called. Multiple synonyms may be specified for a single token -** by making multiple calls to xToken(FTS5_TOKEN_COLOCATED) in sequence. +** by making multiple calls to xToken(FTS5_TOKEN_COLOCATED) in sequence. ** There is no limit to the number of synonyms that may be provided for a ** single token. ** -** In many cases, method (1) above is the best approach. It does not add +** In many cases, method (1) above is the best approach. It does not add ** extra data to the FTS index or require FTS5 to query for multiple terms, ** so it is efficient in terms of disk space and query speed. However, it ** does not support prefix queries very well. If, as suggested above, the @@ -200990,35 +241956,62 @@ struct Fts5ExtensionApi { ** will not match documents that contain the token "1st" (as the tokenizer ** will probably not map "1s" to any prefix of "first"). ** -** For full prefix support, method (3) may be preferred. In this case, +** For full prefix support, method (3) may be preferred. In this case, ** because the index contains entries for both "first" and "1st", prefix ** queries such as 'fi*' or '1s*' will match correctly. However, because ** extra entries are added to the FTS index, this method uses more space ** within the database. ** ** Method (2) offers a midpoint between (1) and (3). Using this method, -** a query such as '1s*' will match documents that contain the literal +** a query such as '1s*' will match documents that contain the literal ** token "1st", but not "first" (assuming the tokenizer is not able to ** provide synonyms for prefixes). However, a non-prefix query like '1st' ** will match against "1st" and "first". This method does not require -** extra disk space, as no extra entries are added to the FTS index. +** extra disk space, as no extra entries are added to the FTS index. ** On the other hand, it may require more CPU cycles to run MATCH queries, ** as separate queries of the FTS index are required for each synonym. ** ** When using methods (2) or (3), it is important that the tokenizer only -** provide synonyms when tokenizing document text (method (2)) or query -** text (method (3)), not both. Doing so will not cause any errors, but is +** provide synonyms when tokenizing document text (method (3)) or query +** text (method (2)), not both. Doing so will not cause any errors, but is ** inefficient. */ typedef struct Fts5Tokenizer Fts5Tokenizer; +typedef struct fts5_tokenizer_v2 fts5_tokenizer_v2; +struct fts5_tokenizer_v2 { + int iVersion; /* Currently always 2 */ + + int (*xCreate)(void*, const char **azArg, int nArg, Fts5Tokenizer **ppOut); + void (*xDelete)(Fts5Tokenizer*); + int (*xTokenize)(Fts5Tokenizer*, + void *pCtx, + int flags, /* Mask of FTS5_TOKENIZE_* flags */ + const char *pText, int nText, + const char *pLocale, int nLocale, + int (*xToken)( + void *pCtx, /* Copy of 2nd argument to xTokenize() */ + int tflags, /* Mask of FTS5_TOKEN_* flags */ + const char *pToken, /* Pointer to buffer containing token */ + int nToken, /* Size of token in bytes */ + int iStart, /* Byte offset of token within input text */ + int iEnd /* Byte offset of end of token within input text */ + ) + ); +}; + +/* +** New code should use the fts5_tokenizer_v2 type to define tokenizer +** implementations. The following type is included for legacy applications +** that still use it. +*/ typedef struct fts5_tokenizer fts5_tokenizer; struct fts5_tokenizer { int (*xCreate)(void*, const char **azArg, int nArg, Fts5Tokenizer **ppOut); void (*xDelete)(Fts5Tokenizer*); - int (*xTokenize)(Fts5Tokenizer*, + int (*xTokenize)(Fts5Tokenizer*, void *pCtx, int flags, /* Mask of FTS5_TOKENIZE_* flags */ - const char *pText, int nText, + const char *pText, int nText, int (*xToken)( void *pCtx, /* Copy of 2nd argument to xTokenize() */ int tflags, /* Mask of FTS5_TOKEN_* flags */ @@ -201030,6 +242023,7 @@ struct fts5_tokenizer { ); }; + /* Flags that may be passed as the third argument to xTokenize() */ #define FTS5_TOKENIZE_QUERY 0x0001 #define FTS5_TOKENIZE_PREFIX 0x0002 @@ -201049,13 +242043,13 @@ struct fts5_tokenizer { */ typedef struct fts5_api fts5_api; struct fts5_api { - int iVersion; /* Currently always set to 2 */ + int iVersion; /* Currently always set to 3 */ /* Create a new tokenizer */ int (*xCreateTokenizer)( fts5_api *pApi, const char *zName, - void *pContext, + void *pUserData, fts5_tokenizer *pTokenizer, void (*xDestroy)(void*) ); @@ -201064,7 +242058,7 @@ struct fts5_api { int (*xFindTokenizer)( fts5_api *pApi, const char *zName, - void **ppContext, + void **ppUserData, fts5_tokenizer *pTokenizer ); @@ -201072,10 +242066,29 @@ struct fts5_api { int (*xCreateFunction)( fts5_api *pApi, const char *zName, - void *pContext, + void *pUserData, fts5_extension_function xFunction, void (*xDestroy)(void*) ); + + /* APIs below this point are only available if iVersion>=3 */ + + /* Create a new tokenizer */ + int (*xCreateTokenizer_v2)( + fts5_api *pApi, + const char *zName, + void *pUserData, + fts5_tokenizer_v2 *pTokenizer, + void (*xDestroy)(void*) + ); + + /* Find an existing tokenizer */ + int (*xFindTokenizer_v2)( + fts5_api *pApi, + const char *zName, + void **ppUserData, + fts5_tokenizer_v2 **ppTokenizer + ); }; /* @@ -201110,6 +242123,7 @@ SQLITE_EXTENSION_INIT1 /* #include */ /* #include */ +/* #include */ #ifndef SQLITE_AMALGAMATION @@ -201125,8 +242139,20 @@ typedef sqlite3_uint64 u64; #endif #define testcase(x) -#define ALWAYS(x) 1 -#define NEVER(x) 0 + +#if defined(SQLITE_COVERAGE_TEST) || defined(SQLITE_MUTATION_TEST) +# define SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS 1 +#endif +#if defined(SQLITE_OMIT_AUXILIARY_SAFETY_CHECKS) +# define ALWAYS(X) (1) +# define NEVER(X) (0) +#elif !defined(NDEBUG) +# define ALWAYS(X) ((X)?1:(assert(0),0)) +# define NEVER(X) ((X)?(assert(0),1):0) +#else +# define ALWAYS(X) (X) +# define NEVER(X) (X) +#endif #define MIN(x,y) (((x) < (y)) ? (x) : (y)) #define MAX(x,y) (((x) > (y)) ? (x) : (y)) @@ -201137,9 +242163,36 @@ typedef sqlite3_uint64 u64; # define LARGEST_INT64 (0xffffffff|(((i64)0x7fffffff)<<32)) # define SMALLEST_INT64 (((i64)-1) - LARGEST_INT64) +/* +** This macro is used in a single assert() within fts5 to check that an +** allocation is aligned to an 8-byte boundary. But it is a complicated +** macro to get right for multiple platforms without generating warnings. +** So instead of reproducing the entire definition from sqliteInt.h, we +** just do without this assert() for the rare non-amalgamation builds. +*/ +#define EIGHT_BYTE_ALIGNMENT(x) 1 + +/* +** Macros needed to provide flexible arrays in a portable way +*/ +#ifndef offsetof +# define offsetof(ST,M) ((size_t)((char*)&((ST*)0)->M - (char*)0)) #endif +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) +# define FLEXARRAY +#else +# define FLEXARRAY 1 +#endif + +#endif /* SQLITE_AMALGAMATION */ -/* Truncate very long tokens to this many bytes. Hard limit is +/* +** Constants for the largest and smallest possible 32-bit signed integers. +*/ +# define LARGEST_INT32 ((int)(0x7fffffff)) +# define SMALLEST_INT32 ((int)((-1) - LARGEST_INT32)) + +/* Truncate very long tokens to this many bytes. Hard limit is ** (65536-1-1-4-9)==65521 bytes. The limiting factor is the 16-bit offset ** field that occurs at the start of each leaf page (see fts5_index.c). */ #define FTS5_MAX_TOKEN_SIZE 32768 @@ -201151,6 +242204,11 @@ typedef sqlite3_uint64 u64; */ #define FTS5_MAX_PREFIX_INDEXES 31 +/* +** Maximum segments permitted in a single index +*/ +#define FTS5_MAX_SEGMENT 2000 + #define FTS5_DEFAULT_NEARDIST 10 #define FTS5_DEFAULT_RANK "bm25" @@ -201167,7 +242225,7 @@ static int sqlite3Fts5Corrupt(void); /* ** The assert_nc() macro is similar to the assert() macro, except that it -** is used for assert() conditions that are true only if it can be +** is used for assert() conditions that are true only if it can be ** guranteed that the database is not corrupt. */ #ifdef SQLITE_DEBUG @@ -201181,7 +242239,7 @@ SQLITE_API extern int sqlite3_fts5_may_be_corrupt; ** A version of memcmp() that does not cause asan errors if one of the pointer ** parameters is NULL and the number of bytes to compare is zero. */ -#define fts5Memcmp(s1, s2, n) ((n)==0 ? 0 : memcmp((s1), (s2), (n))) +#define fts5Memcmp(s1, s2, n) ((n)<=0 ? 0 : memcmp((s1), (s2), (n))) /* Mark a function parameter as unused, to suppress nuisance compiler ** warnings. */ @@ -201196,7 +242254,7 @@ SQLITE_API extern int sqlite3_fts5_may_be_corrupt; typedef struct Fts5Global Fts5Global; typedef struct Fts5Colset Fts5Colset; -/* If a NEAR() clump or phrase may only match a specific set of columns, +/* If a NEAR() clump or phrase may only match a specific set of columns, ** then an object of the following type is used to record the set of columns. ** Each entry in the aiCol[] array is a column that may be matched. ** @@ -201204,10 +242262,11 @@ typedef struct Fts5Colset Fts5Colset; */ struct Fts5Colset { int nCol; - int aiCol[1]; + int aiCol[FLEXARRAY]; }; - +/* Size (int bytes) of a complete Fts5Colset object with N columns. */ +#define SZ_FTS5COLSET(N) (sizeof(i64)*((N+2)/2)) /************************************************************************** ** Interface to code in fts5_config.c. fts5_config.c contains contains code @@ -201215,6 +242274,18 @@ struct Fts5Colset { */ typedef struct Fts5Config Fts5Config; +typedef struct Fts5TokenizerConfig Fts5TokenizerConfig; + +struct Fts5TokenizerConfig { + Fts5Tokenizer *pTok; + fts5_tokenizer_v2 *pApi2; + fts5_tokenizer *pApi1; + const char **azArg; + int nArg; + int ePattern; /* FTS_PATTERN_XXX constant */ + const char *pLocale; /* Current locale to use */ + int nLocale; /* Size of pLocale in bytes */ +}; /* ** An instance of the following structure encodes all information that can @@ -201224,20 +242295,24 @@ typedef struct Fts5Config Fts5Config; ** ** nAutomerge: ** The minimum number of segments that an auto-merge operation should -** attempt to merge together. A value of 1 sets the object to use the +** attempt to merge together. A value of 1 sets the object to use the ** compile time default. Zero disables auto-merge altogether. ** +** bContentlessDelete: +** True if the contentless_delete option was present in the CREATE +** VIRTUAL TABLE statement. +** ** zContent: ** ** zContentRowid: -** The value of the content_rowid= option, if one was specified. Or +** The value of the content_rowid= option, if one was specified. Or ** the string "rowid" otherwise. This text is not quoted - if it is ** used as part of an SQL statement it needs to be quoted appropriately. ** ** zContentExprlist: ** ** pzErrmsg: -** This exists in order to allow the fts5_index.c module to return a +** This exists in order to allow the fts5_index.c module to return a ** decent error message if it encounters a file-format version it does ** not understand. ** @@ -201250,9 +242325,12 @@ typedef struct Fts5Config Fts5Config; ** ** INSERT INTO tbl(tbl, rank) VALUES('prefix-index', $bPrefixIndex); ** +** bLocale: +** Set to true if locale=1 was specified when the table was created. */ struct Fts5Config { sqlite3 *db; /* Database handle */ + Fts5Global *pGlobal; /* Global fts5 object for handle db */ char *zDb; /* Database holding FTS index (e.g. "main") */ char *zName; /* Name of FTS index */ int nCol; /* Number of columns */ @@ -201261,15 +242339,21 @@ struct Fts5Config { int nPrefix; /* Number of prefix indexes */ int *aPrefix; /* Sizes in bytes of nPrefix prefix indexes */ int eContent; /* An FTS5_CONTENT value */ - char *zContent; /* content table */ - char *zContentRowid; /* "content_rowid=" option value */ + int bContentlessDelete; /* "contentless_delete=" option (dflt==0) */ + int bContentlessUnindexed; /* "contentless_unindexed=" option (dflt=0) */ + char *zContent; /* content table */ + char *zContentRowid; /* "content_rowid=" option value */ int bColumnsize; /* "columnsize=" option value (dflt==1) */ + int bTokendata; /* "tokendata=" option value (dflt==0) */ + int bLocale; /* "locale=" option value (dflt==0) */ int eDetail; /* FTS5_DETAIL_XXX value */ char *zContentExprlist; - Fts5Tokenizer *pTok; - fts5_tokenizer *pTokApi; + Fts5TokenizerConfig t; + int bLock; /* True when table is preparing statement */ + /* Values loaded from the %_config table */ + int iVersion; /* fts5 file format 'version' */ int iCookie; /* Incremented when %_config is modified */ int pgsz; /* Approximate page size used in %_data */ int nAutomerge; /* 'automerge' setting */ @@ -201278,6 +242362,9 @@ struct Fts5Config { int nHashSize; /* Bytes of memory for in-memory hash */ char *zRank; /* Name of rank function */ char *zRankArgs; /* Arguments to rank function */ + int bSecureDelete; /* 'secure-delete' */ + int nDeleteMerge; /* 'deletemerge' */ + int bPrefixInsttoken; /* 'prefix-insttoken' */ /* If non-NULL, points to sqlite3_vtab.base.zErrmsg. Often NULL. */ char **pzErrmsg; @@ -201287,18 +242374,24 @@ struct Fts5Config { #endif }; -/* Current expected value of %_config table 'version' field */ -#define FTS5_CURRENT_VERSION 4 +/* Current expected value of %_config table 'version' field. And +** the expected version if the 'secure-delete' option has ever been +** set on the table. */ +#define FTS5_CURRENT_VERSION 4 +#define FTS5_CURRENT_VERSION_SECUREDELETE 5 -#define FTS5_CONTENT_NORMAL 0 -#define FTS5_CONTENT_NONE 1 -#define FTS5_CONTENT_EXTERNAL 2 - -#define FTS5_DETAIL_FULL 0 -#define FTS5_DETAIL_NONE 1 -#define FTS5_DETAIL_COLUMNS 2 +#define FTS5_CONTENT_NORMAL 0 +#define FTS5_CONTENT_NONE 1 +#define FTS5_CONTENT_EXTERNAL 2 +#define FTS5_CONTENT_UNINDEXED 3 +#define FTS5_DETAIL_FULL 0 +#define FTS5_DETAIL_NONE 1 +#define FTS5_DETAIL_COLUMNS 2 +#define FTS5_PATTERN_NONE 0 +#define FTS5_PATTERN_LIKE 65 /* matches SQLITE_INDEX_CONSTRAINT_LIKE */ +#define FTS5_PATTERN_GLOB 66 /* matches SQLITE_INDEX_CONSTRAINT_GLOB */ static int sqlite3Fts5ConfigParse( Fts5Global*, sqlite3*, int, const char **, Fts5Config**, char** @@ -201325,6 +242418,8 @@ static int sqlite3Fts5ConfigSetValue(Fts5Config*, const char*, sqlite3_value*, i static int sqlite3Fts5ConfigParseRank(const char*, char**, char**); +static void sqlite3Fts5ConfigErrmsg(Fts5Config *pConfig, const char *zFmt, ...); + /* ** End of interface to code in fts5_config.c. **************************************************************************/ @@ -201355,7 +242450,7 @@ static void sqlite3Fts5BufferAppendPrintf(int *, Fts5Buffer*, char *zFmt, ...); static char *sqlite3Fts5Mprintf(int *pRc, const char *zFmt, ...); #define fts5BufferZero(x) sqlite3Fts5BufferZero(x) -#define fts5BufferAppendVarint(a,b,c) sqlite3Fts5BufferAppendVarint(a,b,c) +#define fts5BufferAppendVarint(a,b,c) sqlite3Fts5BufferAppendVarint(a,b,(i64)c) #define fts5BufferFree(a) sqlite3Fts5BufferFree(a) #define fts5BufferAppendBlob(a,b,c,d) sqlite3Fts5BufferAppendBlob(a,b,c,d) #define fts5BufferSet(a,b,c,d) sqlite3Fts5BufferSet(a,b,c,d) @@ -201369,7 +242464,7 @@ static char *sqlite3Fts5Mprintf(int *pRc, const char *zFmt, ...); static void sqlite3Fts5Put32(u8*, int); static int sqlite3Fts5Get32(const u8*); -#define FTS5_POS2COLUMN(iPos) (int)(iPos >> 32) +#define FTS5_POS2COLUMN(iPos) (int)((iPos >> 32) & 0x7FFFFFFF) #define FTS5_POS2OFFSET(iPos) (int)(iPos & 0x7FFFFFFF) typedef struct Fts5PoslistReader Fts5PoslistReader; @@ -201442,16 +242537,19 @@ struct Fts5IndexIter { /* ** Values used as part of the flags argument passed to IndexQuery(). */ -#define FTS5INDEX_QUERY_PREFIX 0x0001 /* Prefix query */ -#define FTS5INDEX_QUERY_DESC 0x0002 /* Docs in descending rowid order */ -#define FTS5INDEX_QUERY_TEST_NOIDX 0x0004 /* Do not use prefix index */ -#define FTS5INDEX_QUERY_SCAN 0x0008 /* Scan query (fts5vocab) */ +#define FTS5INDEX_QUERY_PREFIX 0x0001 /* Prefix query */ +#define FTS5INDEX_QUERY_DESC 0x0002 /* Docs in descending rowid order */ +#define FTS5INDEX_QUERY_TEST_NOIDX 0x0004 /* Do not use prefix index */ +#define FTS5INDEX_QUERY_SCAN 0x0008 /* Scan query (fts5vocab) */ /* The following are used internally by the fts5_index.c module. They are ** defined here only to make it easier to avoid clashes with the flags ** above. */ -#define FTS5INDEX_QUERY_SKIPEMPTY 0x0010 -#define FTS5INDEX_QUERY_NOOUTPUT 0x0020 +#define FTS5INDEX_QUERY_SKIPEMPTY 0x0010 +#define FTS5INDEX_QUERY_NOOUTPUT 0x0020 +#define FTS5INDEX_QUERY_SKIPHASH 0x0040 +#define FTS5INDEX_QUERY_NOTOKENDATA 0x0080 +#define FTS5INDEX_QUERY_SCANONETERM 0x0100 /* ** Create/destroy an Fts5Index object. @@ -201463,27 +242561,27 @@ static int sqlite3Fts5IndexClose(Fts5Index *p); ** Return a simple checksum value based on the arguments. */ static u64 sqlite3Fts5IndexEntryCksum( - i64 iRowid, - int iCol, - int iPos, + i64 iRowid, + int iCol, + int iPos, int iIdx, const char *pTerm, int nTerm ); /* -** Argument p points to a buffer containing utf-8 text that is n bytes in +** Argument p points to a buffer containing utf-8 text that is n bytes in ** size. Return the number of bytes in the nChar character prefix of the ** buffer, or 0 if there are less than nChar characters in total. */ static int sqlite3Fts5IndexCharlenToBytelen( - const char *p, - int nByte, + const char *p, + int nByte, int nChar ); /* -** Open a new iterator to iterate though all rowids that match the +** Open a new iterator to iterate though all rowids that match the ** specified token or token prefix. */ static int sqlite3Fts5IndexQuery( @@ -201506,15 +242604,34 @@ static int sqlite3Fts5IterNextFrom(Fts5IndexIter*, i64 iMatch); */ static void sqlite3Fts5IterClose(Fts5IndexIter*); +/* +** Close the reader blob handle, if it is open. +*/ +static void sqlite3Fts5IndexCloseReader(Fts5Index*); + /* ** This interface is used by the fts5vocab module. */ static const char *sqlite3Fts5IterTerm(Fts5IndexIter*, int*); static int sqlite3Fts5IterNextScan(Fts5IndexIter*); +static void *sqlite3Fts5StructureRef(Fts5Index*); +static void sqlite3Fts5StructureRelease(void*); +static int sqlite3Fts5StructureTest(Fts5Index*, void*); +/* +** Used by xInstToken(): +*/ +static int sqlite3Fts5IterToken( + Fts5IndexIter *pIndexIter, + const char *pToken, int nToken, + i64 iRowid, + int iCol, + int iOff, + const char **ppOut, int *pnOut +); /* -** Insert or remove data to or from the index. Each time a document is +** Insert or remove data to or from the index. Each time a document is ** added to or removed from the index, this function is called one or more ** times. ** @@ -201549,7 +242666,7 @@ static int sqlite3Fts5IndexSync(Fts5Index *p); /* ** Discard any data stored in the in-memory hash tables. Do not write it ** to the database. Additionally, assume that the contents of the %_data -** table may have changed on disk. So any in-memory caches of %_data +** table may have changed on disk. So any in-memory caches of %_data ** records must be invalidated. */ static int sqlite3Fts5IndexRollback(Fts5Index *p); @@ -201563,18 +242680,18 @@ static int sqlite3Fts5IndexSetAverages(Fts5Index *p, const u8*, int); /* ** Functions called by the storage module as part of integrity-check. */ -static int sqlite3Fts5IndexIntegrityCheck(Fts5Index*, u64 cksum); +static int sqlite3Fts5IndexIntegrityCheck(Fts5Index*, u64 cksum, int bUseCksum); -/* -** Called during virtual module initialization to register UDF -** fts5_decode() with SQLite +/* +** Called during virtual module initialization to register UDF +** fts5_decode() with SQLite */ static int sqlite3Fts5IndexInit(sqlite3*); static int sqlite3Fts5IndexSetCookie(Fts5Index*, int); /* -** Return the total number of entries read from the %_data table by +** Return the total number of entries read from the %_data table by ** this connection since it was created. */ static int sqlite3Fts5IndexReads(Fts5Index *p); @@ -201586,19 +242703,29 @@ static int sqlite3Fts5IndexReset(Fts5Index *p); static int sqlite3Fts5IndexLoadConfig(Fts5Index *p); +static int sqlite3Fts5IndexGetOrigin(Fts5Index *p, i64 *piOrigin); +static int sqlite3Fts5IndexContentlessDelete(Fts5Index *p, i64 iOrigin, i64 iRowid); + +static void sqlite3Fts5IndexIterClearTokendata(Fts5IndexIter*); + +/* Used to populate hash tables for xInstToken in detail=none/column mode. */ +static int sqlite3Fts5IndexIterWriteTokendata( + Fts5IndexIter*, const char*, int, i64 iRowid, int iCol, int iOff +); + /* ** End of interface to code in fts5_index.c. **************************************************************************/ /************************************************************************** -** Interface to code in fts5_varint.c. +** Interface to code in fts5_varint.c. */ static int sqlite3Fts5GetVarint32(const unsigned char *p, u32 *v); static int sqlite3Fts5GetVarintLen(u32 iVal); static u8 sqlite3Fts5GetVarint(const unsigned char*, u64*); static int sqlite3Fts5PutVarint(unsigned char *p, u64 v); -#define fts5GetVarint32(a,b) sqlite3Fts5GetVarint32(a,(u32*)&b) +#define fts5GetVarint32(a,b) sqlite3Fts5GetVarint32(a,(u32*)&(b)) #define fts5GetVarint sqlite3Fts5GetVarint #define fts5FastGetVarint32(a, iOff, nVal) { \ @@ -201616,7 +242743,7 @@ static int sqlite3Fts5PutVarint(unsigned char *p, u64 v); /************************************************************************** -** Interface to code in fts5_main.c. +** Interface to code in fts5_main.c. */ /* @@ -201629,25 +242756,26 @@ struct Fts5Table { Fts5Index *pIndex; /* Full-text index */ }; -static int sqlite3Fts5GetTokenizer( - Fts5Global*, - const char **azArg, - int nArg, - Fts5Tokenizer**, - fts5_tokenizer**, - char **pzErr -); +static int sqlite3Fts5LoadTokenizer(Fts5Config *pConfig); static Fts5Table *sqlite3Fts5TableFromCsrid(Fts5Global*, i64); static int sqlite3Fts5FlushToDisk(Fts5Table*); +static void sqlite3Fts5ClearLocale(Fts5Config *pConfig); +static void sqlite3Fts5SetLocale(Fts5Config *pConfig, const char *pLoc, int nLoc); + +static int sqlite3Fts5IsLocaleValue(Fts5Config *pConfig, sqlite3_value *pVal); +static int sqlite3Fts5DecodeLocaleValue(sqlite3_value *pVal, + const char **ppText, int *pnText, const char **ppLoc, int *pnLoc +); + /* ** End of interface to code in fts5.c. **************************************************************************/ /************************************************************************** -** Interface to code in fts5_hash.c. +** Interface to code in fts5_hash.c. */ typedef struct Fts5Hash Fts5Hash; @@ -201671,6 +242799,11 @@ static int sqlite3Fts5HashWrite( */ static void sqlite3Fts5HashClear(Fts5Hash*); +/* +** Return true if the hash is empty, false otherwise. +*/ +static int sqlite3Fts5HashIsEmpty(Fts5Hash*); + static int sqlite3Fts5HashQuery( Fts5Hash*, /* Hash table to query */ int nPre, @@ -201687,17 +242820,19 @@ static void sqlite3Fts5HashScanNext(Fts5Hash*); static int sqlite3Fts5HashScanEof(Fts5Hash*); static void sqlite3Fts5HashScanEntry(Fts5Hash *, const char **pzTerm, /* OUT: term (nul-terminated) */ + int *pnTerm, /* OUT: Size of term in bytes */ const u8 **ppDoclist, /* OUT: pointer to doclist */ int *pnDoclist /* OUT: size of doclist in bytes */ ); + /* ** End of interface to code in fts5_hash.c. **************************************************************************/ /************************************************************************** -** Interface to code in fts5_storage.c. fts5_storage.c contains contains +** Interface to code in fts5_storage.c. fts5_storage.c contains contains ** code to access the data stored in the %_content and %_docsize tables. */ @@ -201714,11 +242849,11 @@ static int sqlite3Fts5StorageRename(Fts5Storage*, const char *zName); static int sqlite3Fts5DropAll(Fts5Config*); static int sqlite3Fts5CreateTable(Fts5Config*, const char*, const char*, int, char **); -static int sqlite3Fts5StorageDelete(Fts5Storage *p, i64, sqlite3_value**); -static int sqlite3Fts5StorageContentInsert(Fts5Storage *p, sqlite3_value**, i64*); +static int sqlite3Fts5StorageDelete(Fts5Storage *p, i64, sqlite3_value**, int); +static int sqlite3Fts5StorageContentInsert(Fts5Storage *p, int, sqlite3_value**, i64*); static int sqlite3Fts5StorageIndexInsert(Fts5Storage *p, sqlite3_value**, i64); -static int sqlite3Fts5StorageIntegrity(Fts5Storage *p); +static int sqlite3Fts5StorageIntegrity(Fts5Storage *p, int iArg); static int sqlite3Fts5StorageStmt(Fts5Storage *p, int eStmt, sqlite3_stmt**, char**); static void sqlite3Fts5StorageStmtRelease(Fts5Storage *p, int eStmt, sqlite3_stmt*); @@ -201740,13 +242875,16 @@ static int sqlite3Fts5StorageOptimize(Fts5Storage *p); static int sqlite3Fts5StorageMerge(Fts5Storage *p, int nMerge); static int sqlite3Fts5StorageReset(Fts5Storage *p); +static void sqlite3Fts5StorageReleaseDeleteRow(Fts5Storage*); +static int sqlite3Fts5StorageFindDeleteRow(Fts5Storage *p, i64 iDel); + /* ** End of interface to code in fts5_storage.c. **************************************************************************/ /************************************************************************** -** Interface to code in fts5_expr.c. +** Interface to code in fts5_expr.c. */ typedef struct Fts5Expr Fts5Expr; typedef struct Fts5ExprNode Fts5ExprNode; @@ -201762,12 +242900,20 @@ struct Fts5Token { /* Parse a MATCH expression. */ static int sqlite3Fts5ExprNew( - Fts5Config *pConfig, + Fts5Config *pConfig, + int bPhraseToAnd, int iCol, /* Column on LHS of MATCH operator */ const char *zExpr, - Fts5Expr **ppNew, + Fts5Expr **ppNew, char **pzErr ); +static int sqlite3Fts5ExprPattern( + Fts5Config *pConfig, + int bGlob, + int iCol, + const char *zText, + Fts5Expr **pp +); /* ** for(rc = sqlite3Fts5ExprFirst(pExpr, pIdx, bDesc); @@ -201778,12 +242924,13 @@ static int sqlite3Fts5ExprNew( ** i64 iRowid = sqlite3Fts5ExprRowid(pExpr); ** } */ -static int sqlite3Fts5ExprFirst(Fts5Expr*, Fts5Index *pIdx, i64 iMin, int bDesc); +static int sqlite3Fts5ExprFirst(Fts5Expr*, Fts5Index *pIdx, i64 iMin, i64, int bDesc); static int sqlite3Fts5ExprNext(Fts5Expr*, i64 iMax); static int sqlite3Fts5ExprEof(Fts5Expr*); static i64 sqlite3Fts5ExprRowid(Fts5Expr*); static void sqlite3Fts5ExprFree(Fts5Expr*); +static int sqlite3Fts5ExprAnd(Fts5Expr **pp1, Fts5Expr *p2); /* Called during startup to register a UDF with SQLite */ static int sqlite3Fts5ExprInit(Fts5Global*, sqlite3*); @@ -201803,6 +242950,10 @@ static int sqlite3Fts5ExprClonePhrase(Fts5Expr*, int, Fts5Expr**); static int sqlite3Fts5ExprPhraseCollist(Fts5Expr *, int, const u8 **, int *); +static int sqlite3Fts5ExprQueryToken(Fts5Expr*, int, int, const char**, int*); +static int sqlite3Fts5ExprInstToken(Fts5Expr*, i64, int, int, int, int, const char**, int*); +static void sqlite3Fts5ExprClearTokens(Fts5Expr*); + /******************************************* ** The fts5_expr.c API above this point is used by the other hand-written ** C code in this module. The interfaces below this point are called by @@ -201825,8 +242976,8 @@ static Fts5ExprNode *sqlite3Fts5ParseImplicitAnd( ); static Fts5ExprPhrase *sqlite3Fts5ParseTerm( - Fts5Parse *pParse, - Fts5ExprPhrase *pPhrase, + Fts5Parse *pParse, + Fts5ExprPhrase *pPhrase, Fts5Token *pToken, int bPrefix ); @@ -201834,14 +242985,14 @@ static Fts5ExprPhrase *sqlite3Fts5ParseTerm( static void sqlite3Fts5ParseSetCaret(Fts5ExprPhrase*); static Fts5ExprNearset *sqlite3Fts5ParseNearset( - Fts5Parse*, + Fts5Parse*, Fts5ExprNearset*, - Fts5ExprPhrase* + Fts5ExprPhrase* ); static Fts5Colset *sqlite3Fts5ParseColset( - Fts5Parse*, - Fts5Colset*, + Fts5Parse*, + Fts5Colset*, Fts5Token * ); @@ -201862,7 +243013,7 @@ static void sqlite3Fts5ParseNear(Fts5Parse *pParse, Fts5Token*); /************************************************************************** -** Interface to code in fts5_aux.c. +** Interface to code in fts5_aux.c. */ static int sqlite3Fts5AuxInit(fts5_api*); @@ -201871,16 +243022,21 @@ static int sqlite3Fts5AuxInit(fts5_api*); **************************************************************************/ /************************************************************************** -** Interface to code in fts5_tokenizer.c. +** Interface to code in fts5_tokenizer.c. */ static int sqlite3Fts5TokenizerInit(fts5_api*); +static int sqlite3Fts5TokenizerPattern( + int (*xCreate)(void*, const char**, int, Fts5Tokenizer**), + Fts5Tokenizer *pTok +); +static int sqlite3Fts5TokenizerPreload(Fts5TokenizerConfig*); /* ** End of interface to code in fts5_tokenizer.c. **************************************************************************/ /************************************************************************** -** Interface to code in fts5_vocab.c. +** Interface to code in fts5_vocab.c. */ static int sqlite3Fts5VocabInit(Fts5Global*, sqlite3*); @@ -201891,7 +243047,7 @@ static int sqlite3Fts5VocabInit(Fts5Global*, sqlite3*); /************************************************************************** -** Interface to automatically generated code in fts5_unicode2.c. +** Interface to automatically generated code in fts5_unicode2.c. */ static int sqlite3Fts5UnicodeIsdiacritic(int c); static int sqlite3Fts5UnicodeFold(int c, int bRemoveDiacritic); @@ -201921,6 +243077,9 @@ static void sqlite3Fts5UnicodeAscii(u8*, u8*); #define FTS5_PLUS 14 #define FTS5_STAR 15 +/* This file is automatically generated by Lemon from input grammar +** source file "fts5parse.y". +*/ /* ** 2000-05-29 ** @@ -201936,7 +243095,7 @@ static void sqlite3Fts5UnicodeAscii(u8*, u8*); ** ** The "lemon" program processes an LALR(1) input grammar file, then uses ** this template to construct a parser. The "lemon" program inserts text -** at each "%%" line. Also, any "P-a-r-s-e" identifer prefix (without the +** at each "%%" line. Also, any "P-a-r-s-e" identifier prefix (without the ** interstitial "-" characters) contained in this template is changed into ** the value of the %name directive from the grammar. Otherwise, the content ** of this template is copied straight through into the generate parser @@ -201945,8 +243104,6 @@ static void sqlite3Fts5UnicodeAscii(u8*, u8*); ** The following is the concatenation of all %include directives from the ** input grammar file: */ -/* #include */ -/* #include */ /************ Begin %include sections from the grammar ************************/ /* #include "fts5Int.h" */ @@ -201976,11 +243133,26 @@ static void sqlite3Fts5UnicodeAscii(u8*, u8*); #define fts5YYMALLOCARGTYPE u64 /**************** End of %include directives **********************************/ -/* These constants specify the various numeric values for terminal symbols -** in a format understandable to "makeheaders". This section is blank unless -** "lemon" is run with the "-m" command-line option. -***************** Begin makeheaders token definitions *************************/ -/**************** End makeheaders token definitions ***************************/ +/* These constants specify the various numeric values for terminal symbols. +***************** Begin token definitions *************************************/ +#ifndef FTS5_OR +#define FTS5_OR 1 +#define FTS5_AND 2 +#define FTS5_NOT 3 +#define FTS5_TERM 4 +#define FTS5_COLON 5 +#define FTS5_MINUS 6 +#define FTS5_LCP 7 +#define FTS5_RCP 8 +#define FTS5_STRING 9 +#define FTS5_LP 10 +#define FTS5_RP 11 +#define FTS5_CARET 12 +#define FTS5_COMMA 13 +#define FTS5_PLUS 14 +#define FTS5_STAR 15 +#endif +/**************** End token definitions ***************************************/ /* The next sections is a series of control #defines. ** various aspects of the generated parser. @@ -202005,7 +243177,7 @@ static void sqlite3Fts5UnicodeAscii(u8*, u8*); ** the minor type might be the name of the identifier. ** Each non-terminal can have a different minor type. ** Terminal symbols all have the same minor type, though. -** This macros defines the minor type for terminal +** This macros defines the minor type for terminal ** symbols. ** fts5YYMINORTYPE is the data type used for all minor types. ** This is typically a union of many types, one of @@ -202019,6 +243191,9 @@ static void sqlite3Fts5UnicodeAscii(u8*, u8*); ** sqlite3Fts5ParserARG_STORE Code to store %extra_argument into fts5yypParser ** sqlite3Fts5ParserARG_FETCH Code to extract %extra_argument from fts5yypParser ** sqlite3Fts5ParserCTX_* As sqlite3Fts5ParserARG_ except for %extra_context +** fts5YYREALLOC Name of the realloc() function to use +** fts5YYFREE Name of the free() function to use +** fts5YYDYNSTACK True if stack space should be extended on heap ** fts5YYERRORSYMBOL is the code number of the error symbol. If not ** defined, then do no error processing. ** fts5YYNSTATE the combined number of states. @@ -202032,6 +243207,8 @@ static void sqlite3Fts5UnicodeAscii(u8*, u8*); ** fts5YY_NO_ACTION The fts5yy_action[] code for no-op ** fts5YY_MIN_REDUCE Minimum value for reduce actions ** fts5YY_MAX_REDUCE Maximum value for reduce actions +** fts5YY_MIN_DSTRCTR Minimum symbol value that has a destructor +** fts5YY_MAX_DSTRCTR Maximum symbol value that has a destructor */ #ifndef INTERFACE # define INTERFACE 1 @@ -202058,13 +243235,25 @@ typedef union { #define sqlite3Fts5ParserARG_PARAM ,pParse #define sqlite3Fts5ParserARG_FETCH Fts5Parse *pParse=fts5yypParser->pParse; #define sqlite3Fts5ParserARG_STORE fts5yypParser->pParse=pParse; +#undef fts5YYREALLOC +#define fts5YYREALLOC realloc +#undef fts5YYFREE +#define fts5YYFREE free +#undef fts5YYDYNSTACK +#define fts5YYDYNSTACK 0 +#undef fts5YYSIZELIMIT +#define sqlite3Fts5ParserCTX(P) 0 #define sqlite3Fts5ParserCTX_SDECL #define sqlite3Fts5ParserCTX_PDECL #define sqlite3Fts5ParserCTX_PARAM #define sqlite3Fts5ParserCTX_FETCH #define sqlite3Fts5ParserCTX_STORE +#undef fts5YYERRORSYMBOL +#undef fts5YYERRSYMDT +#undef fts5YYFALLBACK #define fts5YYNSTATE 35 #define fts5YYNRULE 28 +#define fts5YYNRULE_WITH_ACTION 28 #define fts5YYNFTS5TOKEN 16 #define fts5YY_MAX_SHIFT 34 #define fts5YY_MIN_SHIFTREDUCE 52 @@ -202074,6 +243263,8 @@ typedef union { #define fts5YY_NO_ACTION 82 #define fts5YY_MIN_REDUCE 83 #define fts5YY_MAX_REDUCE 110 +#define fts5YY_MIN_DSTRCTR 16 +#define fts5YY_MAX_DSTRCTR 24 /************* End control #defines *******************************************/ #define fts5YY_NLOOKAHEAD ((int)(sizeof(fts5yy_lookahead)/sizeof(fts5yy_lookahead[0]))) @@ -202089,11 +243280,27 @@ typedef union { # define fts5yytestcase(X) #endif +/* Macro to determine if stack space has the ability to grow using +** heap memory. +*/ +#if fts5YYSTACKDEPTH<=0 || fts5YYDYNSTACK +# define fts5YYGROWABLESTACK 1 +#else +# define fts5YYGROWABLESTACK 0 +#endif + +/* Guarantee a minimum number of initial stack slots. +*/ +#if fts5YYSTACKDEPTH<=0 +# undef fts5YYSTACKDEPTH +# define fts5YYSTACKDEPTH 2 /* Need a minimum stack size */ +#endif + /* Next are the tables used to determine what action to take based on the ** current state and lookahead token. These tables are used to implement ** functions that take a state number and lookahead value and return an -** action integer. +** action integer. ** ** Suppose the action integer is N. Then the action is determined as ** follows @@ -202193,9 +243400,9 @@ static const fts5YYACTIONTYPE fts5yy_default[] = { }; /********** End of lemon-generated parsing tables *****************************/ -/* The next table maps tokens (terminal symbols) into fallback tokens. +/* The next table maps tokens (terminal symbols) into fallback tokens. ** If a construct like the following: -** +** ** %fallback ID X Y Z. ** ** appears in the grammar, then ID becomes a fallback token for X, Y, @@ -202249,17 +243456,13 @@ struct fts5yyParser { #endif sqlite3Fts5ParserARG_SDECL /* A place to hold %extra_argument */ sqlite3Fts5ParserCTX_SDECL /* A place to hold %extra_context */ -#if fts5YYSTACKDEPTH<=0 - int fts5yystksz; /* Current side of the stack */ - fts5yyStackEntry *fts5yystack; /* The parser's stack */ - fts5yyStackEntry fts5yystk0; /* First stack entry */ -#else - fts5yyStackEntry fts5yystack[fts5YYSTACKDEPTH]; /* The parser's stack */ - fts5yyStackEntry *fts5yystackEnd; /* Last entry in the stack */ -#endif + fts5yyStackEntry *fts5yystackEnd; /* Last entry in the stack */ + fts5yyStackEntry *fts5yystack; /* The parser stack */ + fts5yyStackEntry fts5yystk0[fts5YYSTACKDEPTH]; /* Initial stack space */ }; typedef struct fts5yyParser fts5yyParser; +/* #include */ #ifndef NDEBUG /* #include */ static FILE *fts5yyTraceFILE = 0; @@ -202267,10 +243470,10 @@ static char *fts5yyTracePrompt = 0; #endif /* NDEBUG */ #ifndef NDEBUG -/* +/* ** Turn parser tracing on by giving a stream to which to write the trace ** and a prompt to preface each trace message. Tracing is turned off -** by making either argument NULL +** by making either argument NULL ** ** Inputs: **
                      @@ -202295,7 +243498,7 @@ static void sqlite3Fts5ParserTrace(FILE *TraceFILE, char *zTracePrompt){ #if defined(fts5YYCOVERAGE) || !defined(NDEBUG) /* For tracing shifts, the names of all terminals and nonterminals ** are required. The following table supplies these names */ -static const char *const fts5yyTokenName[] = { +static const char *const fts5yyTokenName[] = { /* 0 */ "$", /* 1 */ "OR", /* 2 */ "AND", @@ -202362,37 +243565,54 @@ static const char *const fts5yyRuleName[] = { #endif /* NDEBUG */ -#if fts5YYSTACKDEPTH<=0 +#if fts5YYGROWABLESTACK /* ** Try to increase the size of the parser stack. Return the number ** of errors. Return 0 on success. */ static int fts5yyGrowStack(fts5yyParser *p){ + int oldSize = 1 + (int)(p->fts5yystackEnd - p->fts5yystack); int newSize; int idx; fts5yyStackEntry *pNew; +#ifdef fts5YYSIZELIMIT + int nLimit = fts5YYSIZELIMIT(sqlite3Fts5ParserCTX(p)); +#endif - newSize = p->fts5yystksz*2 + 100; - idx = p->fts5yytos ? (int)(p->fts5yytos - p->fts5yystack) : 0; - if( p->fts5yystack==&p->fts5yystk0 ){ - pNew = malloc(newSize*sizeof(pNew[0])); - if( pNew ) pNew[0] = p->fts5yystk0; + newSize = oldSize*2 + 100; +#ifdef fts5YYSIZELIMIT + if( newSize>nLimit ){ + newSize = nLimit; + if( newSize<=oldSize ) return 1; + } +#endif + idx = (int)(p->fts5yytos - p->fts5yystack); + if( p->fts5yystack==p->fts5yystk0 ){ + pNew = fts5YYREALLOC(0, newSize*sizeof(pNew[0]), sqlite3Fts5ParserCTX(p)); + if( pNew==0 ) return 1; + memcpy(pNew, p->fts5yystack, oldSize*sizeof(pNew[0])); }else{ - pNew = realloc(p->fts5yystack, newSize*sizeof(pNew[0])); + pNew = fts5YYREALLOC(p->fts5yystack, newSize*sizeof(pNew[0]), sqlite3Fts5ParserCTX(p)); + if( pNew==0 ) return 1; } - if( pNew ){ - p->fts5yystack = pNew; - p->fts5yytos = &p->fts5yystack[idx]; + p->fts5yystack = pNew; + p->fts5yytos = &p->fts5yystack[idx]; #ifndef NDEBUG - if( fts5yyTraceFILE ){ - fprintf(fts5yyTraceFILE,"%sStack grows from %d to %d entries.\n", - fts5yyTracePrompt, p->fts5yystksz, newSize); - } -#endif - p->fts5yystksz = newSize; + if( fts5yyTraceFILE ){ + fprintf(fts5yyTraceFILE,"%sStack grows from %d to %d entries.\n", + fts5yyTracePrompt, oldSize, newSize); } - return pNew==0; +#endif + p->fts5yystackEnd = &p->fts5yystack[newSize-1]; + return 0; } +#endif /* fts5YYGROWABLESTACK */ + +#if !fts5YYGROWABLESTACK +/* For builds that do no have a growable stack, fts5yyGrowStack always +** returns an error. +*/ +# define fts5yyGrowStack(X) 1 #endif /* Datatype of the argument to the memory allocated passed as the @@ -202412,28 +243632,18 @@ static void sqlite3Fts5ParserInit(void *fts5yypRawParser sqlite3Fts5ParserCTX_PD #ifdef fts5YYTRACKMAXSTACKDEPTH fts5yypParser->fts5yyhwm = 0; #endif -#if fts5YYSTACKDEPTH<=0 - fts5yypParser->fts5yytos = NULL; - fts5yypParser->fts5yystack = NULL; - fts5yypParser->fts5yystksz = 0; - if( fts5yyGrowStack(fts5yypParser) ){ - fts5yypParser->fts5yystack = &fts5yypParser->fts5yystk0; - fts5yypParser->fts5yystksz = 1; - } -#endif + fts5yypParser->fts5yystack = fts5yypParser->fts5yystk0; + fts5yypParser->fts5yystackEnd = &fts5yypParser->fts5yystack[fts5YYSTACKDEPTH-1]; #ifndef fts5YYNOERRORRECOVERY fts5yypParser->fts5yyerrcnt = -1; #endif fts5yypParser->fts5yytos = fts5yypParser->fts5yystack; fts5yypParser->fts5yystack[0].stateno = 0; fts5yypParser->fts5yystack[0].major = 0; -#if fts5YYSTACKDEPTH>0 - fts5yypParser->fts5yystackEnd = &fts5yypParser->fts5yystack[fts5YYSTACKDEPTH-1]; -#endif } #ifndef sqlite3Fts5Parser_ENGINEALWAYSONSTACK -/* +/* ** This function allocates a new parser. ** The only argument is a pointer to a function which works like ** malloc. @@ -202460,7 +243670,7 @@ static void *sqlite3Fts5ParserAlloc(void *(*mallocProc)(fts5YYMALLOCARGTYPE) sql /* The following function deletes the "minor type" or semantic value ** associated with a symbol. The symbol can be either a terminal ** or nonterminal. "fts5yymajor" is the symbol code, and "fts5yypminor" is -** a pointer to the value to be deleted. The code used to do the +** a pointer to the value to be deleted. The code used to do the ** deletions is derived from the %destructor and/or %token_destructor ** directives of the input grammar. */ @@ -202475,7 +243685,7 @@ static void fts5yy_destructor( /* Here is inserted the actions which take place when a ** terminal or non-terminal is destroyed. This can happen ** when the symbol is popped from the stack during a - ** reduce or during error processing or when a parser is + ** reduce or during error processing or when a parser is ** being destroyed before it is finished parsing. ** ** Note: during a reduce, the only symbols destroyed are those @@ -202485,31 +243695,31 @@ static void fts5yy_destructor( /********* Begin destructor definitions ***************************************/ case 16: /* input */ { - (void)pParse; + (void)pParse; } break; case 17: /* expr */ case 18: /* cnearset */ case 19: /* exprlist */ { - sqlite3Fts5ParseNodeFree((fts5yypminor->fts5yy24)); + sqlite3Fts5ParseNodeFree((fts5yypminor->fts5yy24)); } break; case 20: /* colset */ case 21: /* colsetlist */ { - sqlite3_free((fts5yypminor->fts5yy11)); + sqlite3_free((fts5yypminor->fts5yy11)); } break; case 22: /* nearset */ case 23: /* nearphrases */ { - sqlite3Fts5ParseNearsetFree((fts5yypminor->fts5yy46)); + sqlite3Fts5ParseNearsetFree((fts5yypminor->fts5yy46)); } break; case 24: /* phrase */ { - sqlite3Fts5ParsePhraseFree((fts5yypminor->fts5yy53)); + sqlite3Fts5ParsePhraseFree((fts5yypminor->fts5yy53)); } break; /********* End destructor definitions *****************************************/ @@ -202543,14 +243753,33 @@ static void fts5yy_pop_parser_stack(fts5yyParser *pParser){ */ static void sqlite3Fts5ParserFinalize(void *p){ fts5yyParser *pParser = (fts5yyParser*)p; - while( pParser->fts5yytos>pParser->fts5yystack ) fts5yy_pop_parser_stack(pParser); -#if fts5YYSTACKDEPTH<=0 - if( pParser->fts5yystack!=&pParser->fts5yystk0 ) free(pParser->fts5yystack); + + /* In-lined version of calling fts5yy_pop_parser_stack() for each + ** element left in the stack */ + fts5yyStackEntry *fts5yytos = pParser->fts5yytos; + while( fts5yytos>pParser->fts5yystack ){ +#ifndef NDEBUG + if( fts5yyTraceFILE ){ + fprintf(fts5yyTraceFILE,"%sPopping %s\n", + fts5yyTracePrompt, + fts5yyTokenName[fts5yytos->major]); + } +#endif + if( fts5yytos->major>=fts5YY_MIN_DSTRCTR ){ + fts5yy_destructor(pParser, fts5yytos->major, &fts5yytos->minor); + } + fts5yytos--; + } + +#if fts5YYGROWABLESTACK + if( pParser->fts5yystack!=pParser->fts5yystk0 ){ + fts5YYFREE(pParser->fts5yystack, sqlite3Fts5ParserCTX(pParser)); + } #endif } #ifndef sqlite3Fts5Parser_ENGINEALWAYSONSTACK -/* +/* ** Deallocate and destroy a parser. Destructors are called for ** all stack elements before shutting the parser down. ** @@ -202635,15 +243864,18 @@ static fts5YYACTIONTYPE fts5yy_find_shift_action( do{ i = fts5yy_shift_ofst[stateno]; assert( i>=0 ); - /* assert( i+fts5YYNFTS5TOKEN<=(int)fts5YY_NLOOKAHEAD ); */ + assert( i<=fts5YY_ACTTAB_COUNT ); + assert( i+fts5YYNFTS5TOKEN<=(int)fts5YY_NLOOKAHEAD ); assert( iLookAhead!=fts5YYNOCODE ); assert( iLookAhead < fts5YYNFTS5TOKEN ); i += iLookAhead; - if( i>=fts5YY_NLOOKAHEAD || fts5yy_lookahead[i]!=iLookAhead ){ + assert( i<(int)fts5YY_NLOOKAHEAD ); + if( fts5yy_lookahead[i]!=iLookAhead ){ #ifdef fts5YYFALLBACK fts5YYCODETYPE iFallback; /* Fallback token */ - if( iLookAhead %s\n", @@ -202658,16 +243890,8 @@ static fts5YYACTIONTYPE fts5yy_find_shift_action( #ifdef fts5YYWILDCARD { int j = i - iLookAhead + fts5YYWILDCARD; - if( -#if fts5YY_SHIFT_MIN+fts5YYWILDCARD<0 - j>=0 && -#endif -#if fts5YY_SHIFT_MAX+fts5YYWILDCARD>=fts5YY_ACTTAB_COUNT - j0 - ){ + assert( j<(int)(sizeof(fts5yy_lookahead)/sizeof(fts5yy_lookahead[0])) ); + if( fts5yy_lookahead[j]==fts5YYWILDCARD && iLookAhead>0 ){ #ifndef NDEBUG if( fts5yyTraceFILE ){ fprintf(fts5yyTraceFILE, "%sWILDCARD %s => %s\n", @@ -202681,6 +243905,7 @@ static fts5YYACTIONTYPE fts5yy_find_shift_action( #endif /* fts5YYWILDCARD */ return fts5yy_default[stateno]; }else{ + assert( i>=0 && i<(int)(sizeof(fts5yy_action)/sizeof(fts5yy_action[0])) ); return fts5yy_action[i]; } }while(1); @@ -202776,25 +244001,19 @@ static void fts5yy_shift( assert( fts5yypParser->fts5yyhwm == (int)(fts5yypParser->fts5yytos - fts5yypParser->fts5yystack) ); } #endif -#if fts5YYSTACKDEPTH>0 - if( fts5yypParser->fts5yytos>fts5yypParser->fts5yystackEnd ){ - fts5yypParser->fts5yytos--; - fts5yyStackOverflow(fts5yypParser); - return; - } -#else - if( fts5yypParser->fts5yytos>=&fts5yypParser->fts5yystack[fts5yypParser->fts5yystksz] ){ + fts5yytos = fts5yypParser->fts5yytos; + if( fts5yytos>fts5yypParser->fts5yystackEnd ){ if( fts5yyGrowStack(fts5yypParser) ){ fts5yypParser->fts5yytos--; fts5yyStackOverflow(fts5yypParser); return; } + fts5yytos = fts5yypParser->fts5yytos; + assert( fts5yytos <= fts5yypParser->fts5yystackEnd ); } -#endif if( fts5yyNewState > fts5YY_MAX_SHIFT ){ fts5yyNewState += fts5YY_MIN_REDUCE - fts5YY_MIN_SHIFTREDUCE; } - fts5yytos = fts5yypParser->fts5yytos; fts5yytos->stateno = fts5yyNewState; fts5yytos->major = fts5yyMajor; fts5yytos->minor.fts5yy0 = fts5yyMinor; @@ -202894,51 +244113,6 @@ static fts5YYACTIONTYPE fts5yy_reduce( (void)fts5yyLookahead; (void)fts5yyLookaheadToken; fts5yymsp = fts5yypParser->fts5yytos; -#ifndef NDEBUG - if( fts5yyTraceFILE && fts5yyruleno<(int)(sizeof(fts5yyRuleName)/sizeof(fts5yyRuleName[0])) ){ - fts5yysize = fts5yyRuleInfoNRhs[fts5yyruleno]; - if( fts5yysize ){ - fprintf(fts5yyTraceFILE, "%sReduce %d [%s], go to state %d.\n", - fts5yyTracePrompt, - fts5yyruleno, fts5yyRuleName[fts5yyruleno], fts5yymsp[fts5yysize].stateno); - }else{ - fprintf(fts5yyTraceFILE, "%sReduce %d [%s].\n", - fts5yyTracePrompt, fts5yyruleno, fts5yyRuleName[fts5yyruleno]); - } - } -#endif /* NDEBUG */ - - /* Check that the stack is large enough to grow by a single entry - ** if the RHS of the rule is empty. This ensures that there is room - ** enough on the stack to push the LHS value */ - if( fts5yyRuleInfoNRhs[fts5yyruleno]==0 ){ -#ifdef fts5YYTRACKMAXSTACKDEPTH - if( (int)(fts5yypParser->fts5yytos - fts5yypParser->fts5yystack)>fts5yypParser->fts5yyhwm ){ - fts5yypParser->fts5yyhwm++; - assert( fts5yypParser->fts5yyhwm == (int)(fts5yypParser->fts5yytos - fts5yypParser->fts5yystack)); - } -#endif -#if fts5YYSTACKDEPTH>0 - if( fts5yypParser->fts5yytos>=fts5yypParser->fts5yystackEnd ){ - fts5yyStackOverflow(fts5yypParser); - /* The call to fts5yyStackOverflow() above pops the stack until it is - ** empty, causing the main parser loop to exit. So the return value - ** is never used and does not matter. */ - return 0; - } -#else - if( fts5yypParser->fts5yytos>=&fts5yypParser->fts5yystack[fts5yypParser->fts5yystksz-1] ){ - if( fts5yyGrowStack(fts5yypParser) ){ - fts5yyStackOverflow(fts5yypParser); - /* The call to fts5yyStackOverflow() above pops the stack until it is - ** empty, causing the main parser loop to exit. So the return value - ** is never used and does not matter. */ - return 0; - } - fts5yymsp = fts5yypParser->fts5yytos; - } -#endif - } switch( fts5yyruleno ){ /* Beginning here are the reduction cases. A typical example @@ -202955,7 +244129,7 @@ static fts5YYACTIONTYPE fts5yy_reduce( { sqlite3Fts5ParseFinished(pParse, fts5yymsp[0].minor.fts5yy24); } break; case 1: /* colset ::= MINUS LCP colsetlist RCP */ -{ +{ fts5yymsp[-3].minor.fts5yy11 = sqlite3Fts5ParseColsetInvert(pParse, fts5yymsp[-1].minor.fts5yy11); } break; @@ -202975,13 +244149,13 @@ static fts5YYACTIONTYPE fts5yy_reduce( } break; case 5: /* colsetlist ::= colsetlist STRING */ -{ +{ fts5yylhsminor.fts5yy11 = sqlite3Fts5ParseColset(pParse, fts5yymsp[-1].minor.fts5yy11, &fts5yymsp[0].minor.fts5yy0); } fts5yymsp[-1].minor.fts5yy11 = fts5yylhsminor.fts5yy11; break; case 6: /* colsetlist ::= STRING */ -{ - fts5yylhsminor.fts5yy11 = sqlite3Fts5ParseColset(pParse, 0, &fts5yymsp[0].minor.fts5yy0); +{ + fts5yylhsminor.fts5yy11 = sqlite3Fts5ParseColset(pParse, 0, &fts5yymsp[0].minor.fts5yy0); } fts5yymsp[0].minor.fts5yy11 = fts5yylhsminor.fts5yy11; break; @@ -203025,14 +244199,14 @@ static fts5YYACTIONTYPE fts5yy_reduce( fts5yymsp[-1].minor.fts5yy24 = fts5yylhsminor.fts5yy24; break; case 15: /* cnearset ::= nearset */ -{ - fts5yylhsminor.fts5yy24 = sqlite3Fts5ParseNode(pParse, FTS5_STRING, 0, 0, fts5yymsp[0].minor.fts5yy46); +{ + fts5yylhsminor.fts5yy24 = sqlite3Fts5ParseNode(pParse, FTS5_STRING, 0, 0, fts5yymsp[0].minor.fts5yy46); } fts5yymsp[0].minor.fts5yy24 = fts5yylhsminor.fts5yy24; break; case 16: /* cnearset ::= colset COLON nearset */ -{ - fts5yylhsminor.fts5yy24 = sqlite3Fts5ParseNode(pParse, FTS5_STRING, 0, 0, fts5yymsp[0].minor.fts5yy46); +{ + fts5yylhsminor.fts5yy24 = sqlite3Fts5ParseNode(pParse, FTS5_STRING, 0, 0, fts5yymsp[0].minor.fts5yy46); sqlite3Fts5ParseSetColset(pParse, fts5yylhsminor.fts5yy24, fts5yymsp[-2].minor.fts5yy11); } fts5yymsp[-2].minor.fts5yy24 = fts5yylhsminor.fts5yy24; @@ -203042,9 +244216,9 @@ static fts5YYACTIONTYPE fts5yy_reduce( fts5yymsp[0].minor.fts5yy46 = fts5yylhsminor.fts5yy46; break; case 18: /* nearset ::= CARET phrase */ -{ +{ sqlite3Fts5ParseSetCaret(fts5yymsp[0].minor.fts5yy53); - fts5yymsp[-1].minor.fts5yy46 = sqlite3Fts5ParseNearset(pParse, 0, fts5yymsp[0].minor.fts5yy53); + fts5yymsp[-1].minor.fts5yy46 = sqlite3Fts5ParseNearset(pParse, 0, fts5yymsp[0].minor.fts5yy53); } break; case 19: /* nearset ::= STRING LP nearphrases neardist_opt RP */ @@ -203056,8 +244230,8 @@ static fts5YYACTIONTYPE fts5yy_reduce( fts5yymsp[-4].minor.fts5yy46 = fts5yylhsminor.fts5yy46; break; case 20: /* nearphrases ::= phrase */ -{ - fts5yylhsminor.fts5yy46 = sqlite3Fts5ParseNearset(pParse, 0, fts5yymsp[0].minor.fts5yy53); +{ + fts5yylhsminor.fts5yy46 = sqlite3Fts5ParseNearset(pParse, 0, fts5yymsp[0].minor.fts5yy53); } fts5yymsp[0].minor.fts5yy46 = fts5yylhsminor.fts5yy46; break; @@ -203074,13 +244248,13 @@ static fts5YYACTIONTYPE fts5yy_reduce( { fts5yymsp[-1].minor.fts5yy0 = fts5yymsp[0].minor.fts5yy0; } break; case 24: /* phrase ::= phrase PLUS STRING star_opt */ -{ +{ fts5yylhsminor.fts5yy53 = sqlite3Fts5ParseTerm(pParse, fts5yymsp[-3].minor.fts5yy53, &fts5yymsp[-1].minor.fts5yy0, fts5yymsp[0].minor.fts5yy4); } fts5yymsp[-3].minor.fts5yy53 = fts5yylhsminor.fts5yy53; break; case 25: /* phrase ::= STRING star_opt */ -{ +{ fts5yylhsminor.fts5yy53 = sqlite3Fts5ParseTerm(pParse, 0, &fts5yymsp[-1].minor.fts5yy0, fts5yymsp[0].minor.fts5yy4); } fts5yymsp[-1].minor.fts5yy53 = fts5yylhsminor.fts5yy53; @@ -203241,12 +244415,49 @@ static void sqlite3Fts5Parser( } #endif - do{ + while(1){ /* Exit by "break" */ + assert( fts5yypParser->fts5yytos>=fts5yypParser->fts5yystack ); assert( fts5yyact==fts5yypParser->fts5yytos->stateno ); fts5yyact = fts5yy_find_shift_action((fts5YYCODETYPE)fts5yymajor,fts5yyact); if( fts5yyact >= fts5YY_MIN_REDUCE ){ - fts5yyact = fts5yy_reduce(fts5yypParser,fts5yyact-fts5YY_MIN_REDUCE,fts5yymajor, - fts5yyminor sqlite3Fts5ParserCTX_PARAM); + unsigned int fts5yyruleno = fts5yyact - fts5YY_MIN_REDUCE; /* Reduce by this rule */ +#ifndef NDEBUG + assert( fts5yyruleno<(int)(sizeof(fts5yyRuleName)/sizeof(fts5yyRuleName[0])) ); + if( fts5yyTraceFILE ){ + int fts5yysize = fts5yyRuleInfoNRhs[fts5yyruleno]; + if( fts5yysize ){ + fprintf(fts5yyTraceFILE, "%sReduce %d [%s]%s, pop back to state %d.\n", + fts5yyTracePrompt, + fts5yyruleno, fts5yyRuleName[fts5yyruleno], + fts5yyrulenofts5yytos[fts5yysize].stateno); + }else{ + fprintf(fts5yyTraceFILE, "%sReduce %d [%s]%s.\n", + fts5yyTracePrompt, fts5yyruleno, fts5yyRuleName[fts5yyruleno], + fts5yyrulenofts5yytos - fts5yypParser->fts5yystack)>fts5yypParser->fts5yyhwm ){ + fts5yypParser->fts5yyhwm++; + assert( fts5yypParser->fts5yyhwm == + (int)(fts5yypParser->fts5yytos - fts5yypParser->fts5yystack)); + } +#endif + if( fts5yypParser->fts5yytos>=fts5yypParser->fts5yystackEnd ){ + if( fts5yyGrowStack(fts5yypParser) ){ + fts5yyStackOverflow(fts5yypParser); + break; + } + } + } + fts5yyact = fts5yy_reduce(fts5yypParser,fts5yyruleno,fts5yymajor,fts5yyminor sqlite3Fts5ParserCTX_PARAM); }else if( fts5yyact <= fts5YY_MAX_SHIFTREDUCE ){ fts5yy_shift(fts5yypParser,fts5yyact,(fts5YYCODETYPE)fts5yymajor,fts5yyminor); #ifndef fts5YYNOERRORRECOVERY @@ -203271,7 +244482,7 @@ static void sqlite3Fts5Parser( #ifdef fts5YYERRORSYMBOL /* A syntax error has occurred. ** The response to an error depends upon whether or not the - ** grammar defines an error token "ERROR". + ** grammar defines an error token "ERROR". ** ** This is what we do if the grammar does define ERROR: ** @@ -203302,14 +244513,13 @@ static void sqlite3Fts5Parser( fts5yy_destructor(fts5yypParser, (fts5YYCODETYPE)fts5yymajor, &fts5yyminorunion); fts5yymajor = fts5YYNOCODE; }else{ - while( fts5yypParser->fts5yytos >= fts5yypParser->fts5yystack - && (fts5yyact = fts5yy_find_reduce_action( - fts5yypParser->fts5yytos->stateno, - fts5YYERRORSYMBOL)) > fts5YY_MAX_SHIFTREDUCE - ){ + while( fts5yypParser->fts5yytos > fts5yypParser->fts5yystack ){ + fts5yyact = fts5yy_find_reduce_action(fts5yypParser->fts5yytos->stateno, + fts5YYERRORSYMBOL); + if( fts5yyact<=fts5YY_MAX_SHIFTREDUCE ) break; fts5yy_pop_parser_stack(fts5yypParser); } - if( fts5yypParser->fts5yytos < fts5yypParser->fts5yystack || fts5yymajor==0 ){ + if( fts5yypParser->fts5yytos <= fts5yypParser->fts5yystack || fts5yymajor==0 ){ fts5yy_destructor(fts5yypParser,(fts5YYCODETYPE)fts5yymajor,&fts5yyminorunion); fts5yy_parse_failed(fts5yypParser); #ifndef fts5YYNOERRORRECOVERY @@ -203359,7 +244569,7 @@ static void sqlite3Fts5Parser( break; #endif } - }while( fts5yypParser->fts5yytos>fts5yypParser->fts5yystack ); + } #ifndef NDEBUG if( fts5yyTraceFILE ){ fts5yyStackEntry *i; @@ -203381,13 +244591,12 @@ static void sqlite3Fts5Parser( */ static int sqlite3Fts5ParserFallback(int iToken){ #ifdef fts5YYFALLBACK - if( iToken<(int)(sizeof(fts5yyFallback)/sizeof(fts5yyFallback[0])) ){ - return fts5yyFallback[iToken]; - } + assert( iToken<(int)(sizeof(fts5yyFallback)/sizeof(fts5yyFallback[0])) ); + return fts5yyFallback[iToken]; #else (void)iToken; -#endif return 0; +#endif } /* @@ -203408,7 +244617,7 @@ static int sqlite3Fts5ParserFallback(int iToken){ #include /* amalgamator: keep */ /* -** Object used to iterate through all "coalesced phrase instances" in +** Object used to iterate through all "coalesced phrase instances" in ** a single column of the current row. If the phrase instances in the ** column being considered do not overlap, this object simply iterates ** through them. Or, if they do overlap (share one or more tokens in @@ -203471,7 +244680,7 @@ static int fts5CInstIterNext(CInstIter *pIter){ } /* -** Initialize the iterator object indicated by the final parameter to +** Initialize the iterator object indicated by the final parameter to ** iterate through coalesced phrase instances in column iCol. */ static int fts5CInstIterInit( @@ -203502,30 +244711,34 @@ static int fts5CInstIterInit( */ typedef struct HighlightContext HighlightContext; struct HighlightContext { - CInstIter iter; /* Coalesced Instance Iterator */ - int iPos; /* Current token offset in zIn[] */ + /* Constant parameters to fts5HighlightCb() */ int iRangeStart; /* First token to include */ int iRangeEnd; /* If non-zero, last token to include */ const char *zOpen; /* Opening highlight */ const char *zClose; /* Closing highlight */ const char *zIn; /* Input text */ int nIn; /* Size of input text in bytes */ - int iOff; /* Current offset within zIn[] */ + + /* Variables modified by fts5HighlightCb() */ + CInstIter iter; /* Coalesced Instance Iterator */ + int iPos; /* Current token offset in zIn[] */ + int iOff; /* Have copied up to this offset in zIn[] */ + int bOpen; /* True if highlight is open */ char *zOut; /* Output value */ }; /* ** Append text to the HighlightContext output string - p->zOut. Argument -** z points to a buffer containing n bytes of text to append. If n is +** z points to a buffer containing n bytes of text to append. If n is ** negative, everything up until the first '\0' is appended to the output. ** -** If *pRc is set to any value other than SQLITE_OK when this function is -** called, it is a no-op. If an error (i.e. an OOM condition) is encountered, -** *pRc is set to an error code before returning. +** If *pRc is set to any value other than SQLITE_OK when this function is +** called, it is a no-op. If an error (i.e. an OOM condition) is encountered, +** *pRc is set to an error code before returning. */ static void fts5HighlightAppend( - int *pRc, - HighlightContext *p, + int *pRc, + HighlightContext *p, const char *z, int n ){ if( *pRc==SQLITE_OK && z ){ @@ -203543,8 +244756,8 @@ static int fts5HighlightCb( int tflags, /* Mask of FTS5_TOKEN_* flags */ const char *pToken, /* Buffer containing token */ int nToken, /* Size of token in bytes */ - int iStartOff, /* Start offset of token */ - int iEndOff /* End offset of token */ + int iStartOff, /* Start byte offset of token */ + int iEndOff /* End byte offset of token */ ){ HighlightContext *p = (HighlightContext*)pContext; int rc = SQLITE_OK; @@ -203555,40 +244768,66 @@ static int fts5HighlightCb( if( tflags & FTS5_TOKEN_COLOCATED ) return SQLITE_OK; iPos = p->iPos++; - if( p->iRangeEnd>0 ){ + if( p->iRangeEnd>=0 ){ if( iPosiRangeStart || iPos>p->iRangeEnd ) return SQLITE_OK; if( p->iRangeStart && iPos==p->iRangeStart ) p->iOff = iStartOff; } - if( iPos==p->iter.iStart ){ + /* If the parenthesis is open, and this token is not part of the current + ** phrase, and the starting byte offset of this token is past the point + ** that has currently been copied into the output buffer, close the + ** parenthesis. */ + if( p->bOpen + && (iPos<=p->iter.iStart || p->iter.iStart<0) + && iStartOff>p->iOff + ){ + fts5HighlightAppend(&rc, p, p->zClose, -1); + p->bOpen = 0; + } + + /* If this is the start of a new phrase, and the highlight is not open: + ** + ** * copy text from the input up to the start of the phrase, and + ** * open the highlight. + */ + if( iPos==p->iter.iStart && p->bOpen==0 ){ fts5HighlightAppend(&rc, p, &p->zIn[p->iOff], iStartOff - p->iOff); fts5HighlightAppend(&rc, p, p->zOpen, -1); p->iOff = iStartOff; + p->bOpen = 1; } if( iPos==p->iter.iEnd ){ - if( p->iRangeEnd && p->iter.iStartiRangeStart ){ + if( p->bOpen==0 ){ + assert( p->iRangeEnd>=0 ); fts5HighlightAppend(&rc, p, p->zOpen, -1); + p->bOpen = 1; } fts5HighlightAppend(&rc, p, &p->zIn[p->iOff], iEndOff - p->iOff); - fts5HighlightAppend(&rc, p, p->zClose, -1); p->iOff = iEndOff; + if( rc==SQLITE_OK ){ rc = fts5CInstIterNext(&p->iter); } } - if( p->iRangeEnd>0 && iPos==p->iRangeEnd ){ - fts5HighlightAppend(&rc, p, &p->zIn[p->iOff], iEndOff - p->iOff); - p->iOff = iEndOff; - if( iPos>=p->iter.iStart && iPositer.iEnd ){ + if( iPos==p->iRangeEnd ){ + if( p->bOpen ){ + if( p->iter.iStart>=0 && iPos>=p->iter.iStart ){ + fts5HighlightAppend(&rc, p, &p->zIn[p->iOff], iEndOff - p->iOff); + p->iOff = iEndOff; + } fts5HighlightAppend(&rc, p, p->zClose, -1); + p->bOpen = 0; } + fts5HighlightAppend(&rc, p, &p->zIn[p->iOff], iEndOff - p->iOff); + p->iOff = iEndOff; } return rc; } + /* ** Implementation of highlight() function. */ @@ -203613,15 +244852,28 @@ static void fts5HighlightFunction( memset(&ctx, 0, sizeof(HighlightContext)); ctx.zOpen = (const char*)sqlite3_value_text(apVal[1]); ctx.zClose = (const char*)sqlite3_value_text(apVal[2]); + ctx.iRangeEnd = -1; rc = pApi->xColumnText(pFts, iCol, &ctx.zIn, &ctx.nIn); - - if( ctx.zIn ){ + if( rc==SQLITE_RANGE ){ + sqlite3_result_text(pCtx, "", -1, SQLITE_STATIC); + rc = SQLITE_OK; + }else if( ctx.zIn ){ + const char *pLoc = 0; /* Locale of column iCol */ + int nLoc = 0; /* Size of pLoc in bytes */ if( rc==SQLITE_OK ){ rc = fts5CInstIterInit(pApi, pFts, iCol, &ctx.iter); } if( rc==SQLITE_OK ){ - rc = pApi->xTokenize(pFts, ctx.zIn, ctx.nIn, (void*)&ctx,fts5HighlightCb); + rc = pApi->xColumnLocale(pFts, iCol, &pLoc, &nLoc); + } + if( rc==SQLITE_OK ){ + rc = pApi->xTokenize_v2( + pFts, ctx.zIn, ctx.nIn, pLoc, nLoc, (void*)&ctx, fts5HighlightCb + ); + } + if( ctx.bOpen ){ + fts5HighlightAppend(&rc, &ctx, ctx.zClose, -1); } fts5HighlightAppend(&rc, &ctx, &ctx.zIn[ctx.iOff], ctx.nIn - ctx.iOff); @@ -203752,7 +245004,7 @@ static int fts5SnippetScore( } /* -** Return the value in pVal interpreted as utf-8 text. Except, if pVal +** Return the value in pVal interpreted as utf-8 text. Except, if pVal ** contains a NULL value, return a pointer to a static string zero ** bytes in length instead of a NULL pointer. */ @@ -203775,7 +245027,7 @@ static void fts5SnippetFunction( int rc = SQLITE_OK; /* Return code */ int iCol; /* 1st argument to snippet() */ const char *zEllips; /* 4th argument to snippet() */ - int nToken; /* 5th argument to snippet() */ + i64 nToken; /* 5th argument to snippet() */ int nInst = 0; /* Number of instance matches this row */ int i; /* Used to iterate through instances */ int nPhrase; /* Number of phrases in query */ @@ -203798,12 +245050,13 @@ static void fts5SnippetFunction( iCol = sqlite3_value_int(apVal[0]); ctx.zOpen = fts5ValueToText(apVal[1]); ctx.zClose = fts5ValueToText(apVal[2]); + ctx.iRangeEnd = -1; zEllips = fts5ValueToText(apVal[3]); - nToken = sqlite3_value_int(apVal[4]); + nToken = (int)(MIN( MAX(sqlite3_value_int64(apVal[4]), 0), 64)); iBestCol = (iCol>=0 ? iCol : 0); nPhrase = pApi->xPhraseCount(pFts); - aSeen = sqlite3_malloc(nPhrase); + aSeen = sqlite3_malloc64(nPhrase); if( aSeen==0 ){ rc = SQLITE_NOMEM; } @@ -203814,6 +245067,8 @@ static void fts5SnippetFunction( memset(&sFinder, 0, sizeof(Fts5SFinder)); for(i=0; ixColumnText(pFts, i, &sFinder.zDoc, &nDoc); if( rc!=SQLITE_OK ) break; - rc = pApi->xTokenize(pFts, - sFinder.zDoc, nDoc, (void*)&sFinder,fts5SentenceFinderCb + rc = pApi->xColumnLocale(pFts, i, &pLoc, &nLoc); + if( rc!=SQLITE_OK ) break; + rc = pApi->xTokenize_v2(pFts, + sFinder.zDoc, nDoc, pLoc, nLoc, (void*)&sFinder, fts5SentenceFinderCb ); if( rc!=SQLITE_OK ) break; rc = pApi->xColumnSize(pFts, i, &nDocsize); @@ -203856,7 +245113,7 @@ static void fts5SnippetFunction( if( sFinder.aFirst[jj]xColumnSize(pFts, iBestCol, &nColSize); } if( ctx.zIn ){ + const char *pLoc = 0; /* Locale of column iBestCol */ + int nLoc = 0; /* Bytes in pLoc */ + if( rc==SQLITE_OK ){ rc = fts5CInstIterInit(pApi, pFts, iBestCol, &ctx.iter); } @@ -203898,7 +245158,15 @@ static void fts5SnippetFunction( } if( rc==SQLITE_OK ){ - rc = pApi->xTokenize(pFts, ctx.zIn, ctx.nIn, (void*)&ctx,fts5HighlightCb); + rc = pApi->xColumnLocale(pFts, iBestCol, &pLoc, &nLoc); + } + if( rc==SQLITE_OK ){ + rc = pApi->xTokenize_v2( + pFts, ctx.zIn, ctx.nIn, pLoc, nLoc, (void*)&ctx,fts5HighlightCb + ); + } + if( ctx.bOpen ){ + fts5HighlightAppend(&rc, &ctx, ctx.zClose, -1); } if( ctx.iRangeEnd>=(nColSize-1) ){ fts5HighlightAppend(&rc, &ctx, &ctx.zIn[ctx.iOff], ctx.nIn - ctx.iOff); @@ -203935,7 +245203,7 @@ struct Fts5Bm25Data { ** table matched by each individual phrase within the query. */ static int fts5CountCb( - const Fts5ExtensionApi *pApi, + const Fts5ExtensionApi *pApi, Fts5Context *pFts, void *pUserData /* Pointer to sqlite3_int64 variable */ ){ @@ -203946,19 +245214,19 @@ static int fts5CountCb( } /* -** Set *ppData to point to the Fts5Bm25Data object for the current query. +** Set *ppData to point to the Fts5Bm25Data object for the current query. ** If the object has not already been allocated, allocate and populate it ** now. */ static int fts5Bm25GetData( - const Fts5ExtensionApi *pApi, + const Fts5ExtensionApi *pApi, Fts5Context *pFts, Fts5Bm25Data **ppData /* OUT: bm25-data object for this query */ ){ int rc = SQLITE_OK; /* Return code */ Fts5Bm25Data *p; /* Object to return */ - p = pApi->xGetAuxdata(pFts, 0); + p = (Fts5Bm25Data*)pApi->xGetAuxdata(pFts, 0); if( p==0 ){ int nPhrase; /* Number of phrases in query */ sqlite3_int64 nRow = 0; /* Number of rows in table */ @@ -203999,8 +245267,8 @@ static int fts5Bm25GetData( ** is the number that contain at least one instance of the phrase ** under consideration. ** - ** The problem with this is that if (N < 2*nHit), the IDF is - ** negative. Which is undesirable. So the mimimum allowable IDF is + ** The problem with this is that if (N < 2*nHit), the IDF is + ** negative. Which is undesirable. So the minimum allowable IDF is ** (1e-6) - roughly the same as a term that appears in just over ** half of set of 5,000,000 documents. */ double idf = log( (nRow - nHit + 0.5) / (nHit + 0.5) ); @@ -204032,7 +245300,7 @@ static void fts5Bm25Function( ){ const double k1 = 1.2; /* Constant "k1" from BM25 formula */ const double b = 0.75; /* Constant "b" from BM25 formula */ - int rc = SQLITE_OK; /* Error code */ + int rc; /* Error code */ double score = 0.0; /* SQL function return value */ Fts5Bm25Data *pData; /* Values allocated/calculated once only */ int i; /* Iterator variable */ @@ -204064,23 +245332,68 @@ static void fts5Bm25Function( D = (double)nTok; } - /* Determine the BM25 score for the current row. */ - for(i=0; rc==SQLITE_OK && inPhrase; i++){ - score += pData->aIDF[i] * ( - ( aFreq[i] * (k1 + 1.0) ) / - ( aFreq[i] + k1 * (1 - b + b * D / pData->avgdl) ) - ); - } - - /* If no error has occurred, return the calculated score. Otherwise, - ** throw an SQL exception. */ + /* Determine and return the BM25 score for the current row. Or, if an + ** error has occurred, throw an exception. */ if( rc==SQLITE_OK ){ + for(i=0; inPhrase; i++){ + score += pData->aIDF[i] * ( + ( aFreq[i] * (k1 + 1.0) ) / + ( aFreq[i] + k1 * (1 - b + b * D / pData->avgdl) ) + ); + } sqlite3_result_double(pCtx, -1.0 * score); }else{ sqlite3_result_error_code(pCtx, rc); } } +/* +** Implementation of fts5_get_locale() function. +*/ +static void fts5GetLocaleFunction( + const Fts5ExtensionApi *pApi, /* API offered by current FTS version */ + Fts5Context *pFts, /* First arg to pass to pApi functions */ + sqlite3_context *pCtx, /* Context for returning result/error */ + int nVal, /* Number of values in apVal[] array */ + sqlite3_value **apVal /* Array of trailing arguments */ +){ + int iCol = 0; + int eType = 0; + int rc = SQLITE_OK; + const char *zLocale = 0; + int nLocale = 0; + + /* xColumnLocale() must be available */ + assert( pApi->iVersion>=4 ); + + if( nVal!=1 ){ + const char *z = "wrong number of arguments to function fts5_get_locale()"; + sqlite3_result_error(pCtx, z, -1); + return; + } + + eType = sqlite3_value_numeric_type(apVal[0]); + if( eType!=SQLITE_INTEGER ){ + const char *z = "non-integer argument passed to function fts5_get_locale()"; + sqlite3_result_error(pCtx, z, -1); + return; + } + + iCol = sqlite3_value_int(apVal[0]); + if( iCol<0 || iCol>=pApi->xColumnCount(pFts) ){ + sqlite3_result_error_code(pCtx, SQLITE_RANGE); + return; + } + + rc = pApi->xColumnLocale(pFts, iCol, &zLocale, &nLocale); + if( rc!=SQLITE_OK ){ + sqlite3_result_error_code(pCtx, rc); + return; + } + + sqlite3_result_text(pCtx, zLocale, nLocale, SQLITE_TRANSIENT); +} + static int sqlite3Fts5AuxInit(fts5_api *pApi){ struct Builtin { const char *zFunc; /* Function name (nul-terminated) */ @@ -204088,9 +245401,10 @@ static int sqlite3Fts5AuxInit(fts5_api *pApi){ fts5_extension_function xFunc;/* Callback function */ void (*xDestroy)(void*); /* Destructor function */ } aBuiltin [] = { - { "snippet", 0, fts5SnippetFunction, 0 }, - { "highlight", 0, fts5HighlightFunction, 0 }, - { "bm25", 0, fts5Bm25Function, 0 }, + { "snippet", 0, fts5SnippetFunction, 0 }, + { "highlight", 0, fts5HighlightFunction, 0 }, + { "bm25", 0, fts5Bm25Function, 0 }, + { "fts5_get_locale", 0, fts5GetLocaleFunction, 0 }, }; int rc = SQLITE_OK; /* Return code */ int i; /* To iterate through builtin functions */ @@ -204165,19 +245479,19 @@ static int sqlite3Fts5Get32(const u8 *aBuf){ } /* -** Append buffer nData/pData to buffer pBuf. If an OOM error occurs, set +** Append buffer nData/pData to buffer pBuf. If an OOM error occurs, set ** the error code in p. If an error has already occurred when this function ** is called, it is a no-op. */ static void sqlite3Fts5BufferAppendBlob( int *pRc, - Fts5Buffer *pBuf, - u32 nData, + Fts5Buffer *pBuf, + u32 nData, const u8 *pData ){ - assert_nc( *pRc || nData>=0 ); if( nData ){ if( fts5BufferGrow(pRc, pBuf, nData) ) return; + assert( pBuf->p!=0 ); memcpy(&pBuf->p[pBuf->n], pData, nData); pBuf->n += nData; } @@ -204185,12 +245499,12 @@ static void sqlite3Fts5BufferAppendBlob( /* ** Append the nul-terminated string zStr to the buffer pBuf. This function -** ensures that the byte following the buffer data is set to 0x00, even +** ensures that the byte following the buffer data is set to 0x00, even ** though this byte is not included in the pBuf->n count. */ static void sqlite3Fts5BufferAppendString( int *pRc, - Fts5Buffer *pBuf, + Fts5Buffer *pBuf, const char *zStr ){ int nStr = (int)strlen(zStr); @@ -204202,13 +245516,13 @@ static void sqlite3Fts5BufferAppendString( ** Argument zFmt is a printf() style format string. This function performs ** the printf() style processing, then appends the results to buffer pBuf. ** -** Like sqlite3Fts5BufferAppendString(), this function ensures that the byte +** Like sqlite3Fts5BufferAppendString(), this function ensures that the byte ** following the buffer data is set to 0x00, even though this byte is not ** included in the pBuf->n count. -*/ +*/ static void sqlite3Fts5BufferAppendPrintf( int *pRc, - Fts5Buffer *pBuf, + Fts5Buffer *pBuf, char *zFmt, ... ){ if( *pRc==SQLITE_OK ){ @@ -204235,12 +245549,12 @@ static char *sqlite3Fts5Mprintf(int *pRc, const char *zFmt, ...){ zRet = sqlite3_vmprintf(zFmt, ap); va_end(ap); if( zRet==0 ){ - *pRc = SQLITE_NOMEM; + *pRc = SQLITE_NOMEM; } } return zRet; } - + /* ** Free any buffer allocated by pBuf. Zero the structure before returning. @@ -204251,7 +245565,7 @@ static void sqlite3Fts5BufferFree(Fts5Buffer *pBuf){ } /* -** Zero the contents of the buffer object. But do not free the associated +** Zero the contents of the buffer object. But do not free the associated ** memory allocation. */ static void sqlite3Fts5BufferZero(Fts5Buffer *pBuf){ @@ -204265,8 +245579,8 @@ static void sqlite3Fts5BufferZero(Fts5Buffer *pBuf){ */ static void sqlite3Fts5BufferSet( int *pRc, - Fts5Buffer *pBuf, - int nData, + Fts5Buffer *pBuf, + int nData, const u8 *pData ){ pBuf->n = 0; @@ -204279,21 +245593,36 @@ static int sqlite3Fts5PoslistNext64( i64 *piOff /* IN/OUT: Current offset */ ){ int i = *pi; + assert( a!=0 || i==0 ); if( i>=n ){ /* EOF */ *piOff = -1; - return 1; + return 1; }else{ i64 iOff = *piOff; - int iVal; + u32 iVal; + assert( a!=0 ); fts5FastGetVarint32(a, i, iVal); - if( iVal==1 ){ + if( iVal<=1 ){ + if( iVal==0 ){ + *pi = i; + return 0; + } fts5FastGetVarint32(a, i, iVal); iOff = ((i64)iVal) << 32; + assert( iOff>=0 ); fts5FastGetVarint32(a, i, iVal); + if( iVal<2 ){ + /* This is a corrupt record. So stop parsing it here. */ + *piOff = -1; + return 1; + } + *piOff = iOff + ((iVal-2) & 0x7FFFFFFF); + }else{ + *piOff = (iOff & (i64)0x7FFFFFFF<<32)+((iOff + (iVal-2)) & 0x7FFFFFFF); } - *piOff = iOff + ((iVal-2) & 0x7FFFFFFF); *pi = i; + assert_nc( *piOff>=iOff ); return 0; } } @@ -204328,22 +245657,24 @@ static int sqlite3Fts5PoslistReaderInit( ** to iPos before returning. */ static void sqlite3Fts5PoslistSafeAppend( - Fts5Buffer *pBuf, - i64 *piPrev, + Fts5Buffer *pBuf, + i64 *piPrev, i64 iPos ){ - static const i64 colmask = ((i64)(0x7FFFFFFF)) << 32; - if( (iPos & colmask) != (*piPrev & colmask) ){ - pBuf->p[pBuf->n++] = 1; - pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], (iPos>>32)); - *piPrev = (iPos & colmask); + if( iPos>=*piPrev ){ + static const i64 colmask = ((i64)(0x7FFFFFFF)) << 32; + if( (iPos & colmask) != (*piPrev & colmask) ){ + pBuf->p[pBuf->n++] = 1; + pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], (iPos>>32)); + *piPrev = (iPos & colmask); + } + pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], (iPos-*piPrev)+2); + *piPrev = iPos; } - pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], (iPos-*piPrev)+2); - *piPrev = iPos; } static int sqlite3Fts5PoslistWriterAppend( - Fts5Buffer *pBuf, + Fts5Buffer *pBuf, Fts5PoslistWriter *pWriter, i64 iPos ){ @@ -204372,7 +245703,7 @@ static void *sqlite3Fts5MallocZero(int *pRc, sqlite3_int64 nByte){ ** the length of the string is determined using strlen(). ** ** It is the responsibility of the caller to eventually free the returned -** buffer using sqlite3_free(). If an OOM error occurs, NULL is returned. +** buffer using sqlite3_free(). If an OOM error occurs, NULL is returned. */ static char *sqlite3Fts5Strndup(int *pRc, const char *pIn, int nIn){ char *zRet = 0; @@ -204380,7 +245711,7 @@ static char *sqlite3Fts5Strndup(int *pRc, const char *pIn, int nIn){ if( nIn<0 ){ nIn = (int)strlen(pIn); } - zRet = (char*)sqlite3_malloc(nIn+1); + zRet = (char*)sqlite3_malloc64((i64)nIn+1); if( zRet ){ memcpy(zRet, pIn, nIn); zRet[nIn] = '\0'; @@ -204400,7 +245731,7 @@ static char *sqlite3Fts5Strndup(int *pRc, const char *pIn, int nIn){ ** * The 52 upper and lower case ASCII characters, and ** * The 10 integer ASCII characters. ** * The underscore character "_" (0x5F). -** * The unicode "subsitute" character (0x1A). +** * The unicode "substitute" character (0x1A). */ static int sqlite3Fts5IsBareword(char t){ u8 aBareword[128] = { @@ -204439,9 +245770,9 @@ static int sqlite3Fts5TermsetNew(Fts5Termset **pp){ } static int sqlite3Fts5TermsetAdd( - Fts5Termset *p, + Fts5Termset *p, int iIdx, - const char *pTerm, int nTerm, + const char *pTerm, int nTerm, int *pbPresent ){ int rc = SQLITE_OK; @@ -204462,9 +245793,9 @@ static int sqlite3Fts5TermsetAdd( hash = hash % ArraySize(p->apHash); for(pEntry=p->apHash[hash]; pEntry; pEntry=pEntry->pNext){ - if( pEntry->iIdx==iIdx - && pEntry->nTerm==nTerm - && memcmp(pEntry->pTerm, pTerm, nTerm)==0 + if( pEntry->iIdx==iIdx + && pEntry->nTerm==nTerm + && memcmp(pEntry->pTerm, pTerm, nTerm)==0 ){ *pbPresent = 1; break; @@ -204526,8 +245857,10 @@ static void sqlite3Fts5TermsetFree(Fts5Termset *p){ #define FTS5_DEFAULT_CRISISMERGE 16 #define FTS5_DEFAULT_HASHSIZE (1024*1024) +#define FTS5_DEFAULT_DELETE_AUTOMERGE 10 /* default 10% */ + /* Maximum allowed page size */ -#define FTS5_MAX_PAGE_SIZE (128*1024) +#define FTS5_MAX_PAGE_SIZE (64*1024) static int fts5_iswhitespace(char x){ return (x==' '); @@ -204538,8 +245871,8 @@ static int fts5_isopenquote(char x){ } /* -** Argument pIn points to a character that is part of a nul-terminated -** string. Return a pointer to the first character following *pIn in +** Argument pIn points to a character that is part of a nul-terminated +** string. Return a pointer to the first character following *pIn in ** the string that is not a white-space character. */ static const char *fts5ConfigSkipWhitespace(const char *pIn){ @@ -204551,8 +245884,8 @@ static const char *fts5ConfigSkipWhitespace(const char *pIn){ } /* -** Argument pIn points to a character that is part of a nul-terminated -** string. Return a pointer to the first character following *pIn in +** Argument pIn points to a character that is part of a nul-terminated +** string. Return a pointer to the first character following *pIn in ** the string that is not a "bareword" character. */ static const char *fts5ConfigSkipBareword(const char *pIn){ @@ -204583,9 +245916,9 @@ static const char *fts5ConfigSkipLiteral(const char *pIn){ p++; if( *p=='\'' ){ p++; - while( (*p>='a' && *p<='f') - || (*p>='A' && *p<='F') - || (*p>='0' && *p<='9') + while( (*p>='a' && *p<='f') + || (*p>='A' && *p<='F') + || (*p>='0' && *p<='9') ){ p++; } @@ -204616,7 +245949,7 @@ static const char *fts5ConfigSkipLiteral(const char *pIn){ if( *p=='+' || *p=='-' ) p++; while( fts5_isdigit(*p) ) p++; - /* At this point, if the literal was an integer, the parse is + /* At this point, if the literal was an integer, the parse is ** finished. Or, if it is a floating point value, it may continue ** with either a decimal point or an 'E' character. */ if( *p=='.' && fts5_isdigit(p[1]) ){ @@ -204640,8 +245973,8 @@ static const char *fts5ConfigSkipLiteral(const char *pIn){ ** nul-terminator byte. ** ** If the close-quote is found, the value returned is the byte offset of -** the character immediately following it. Or, if the close-quote is not -** found, -1 is returned. If -1 is returned, the buffer is left in an +** the character immediately following it. Or, if the close-quote is not +** found, -1 is returned. If -1 is returned, the buffer is left in an ** undefined state. */ static int fts5Dequote(char *z){ @@ -204652,9 +245985,9 @@ static int fts5Dequote(char *z){ /* Set stack variable q to the close-quote character */ assert( q=='[' || q=='\'' || q=='"' || q=='`' ); - if( q=='[' ) q = ']'; + if( q=='[' ) q = ']'; - while( ALWAYS(z[iIn]) ){ + while( z[iIn] ){ if( z[iIn]==q ){ if( z[iIn+1]!=q ){ /* Character iIn was the close quote. */ @@ -204662,7 +245995,7 @@ static int fts5Dequote(char *z){ break; }else{ /* Character iIn and iIn+1 form an escaped quote character. Skip - ** the input cursor past both and copy a single quote character + ** the input cursor past both and copy a single quote character ** to the output buffer. */ iIn += 2; z[iOut++] = q; @@ -204707,8 +246040,8 @@ struct Fts5Enum { typedef struct Fts5Enum Fts5Enum; static int fts5ConfigSetEnum( - const Fts5Enum *aEnum, - const char *zEnum, + const Fts5Enum *aEnum, + const char *zEnum, int *peVal ){ int nEnum = (int)strlen(zEnum); @@ -204736,7 +246069,6 @@ static int fts5ConfigSetEnum( ** eventually free any such error message using sqlite3_free(). */ static int fts5ConfigParseSpecial( - Fts5Global *pGlobal, Fts5Config *pConfig, /* Configuration object to update */ const char *zCmd, /* Special command to parse */ const char *zArg, /* Argument to parse */ @@ -204744,6 +246076,7 @@ static int fts5ConfigParseSpecial( ){ int rc = SQLITE_OK; int nCmd = (int)strlen(zCmd); + if( sqlite3_strnicmp("prefix", zCmd, nCmd)==0 ){ const int nByte = sizeof(int) * FTS5_MAX_PREFIX_INDEXES; const char *p; @@ -204800,12 +246133,11 @@ static int fts5ConfigParseSpecial( if( sqlite3_strnicmp("tokenize", zCmd, nCmd)==0 ){ const char *p = (const char*)zArg; sqlite3_int64 nArg = strlen(zArg) + 1; - char **azArg = sqlite3Fts5MallocZero(&rc, sizeof(char*) * nArg); - char *pDel = sqlite3Fts5MallocZero(&rc, nArg * 2); - char *pSpace = pDel; + char **azArg = sqlite3Fts5MallocZero(&rc, (sizeof(char*) + 2) * nArg); - if( azArg && pSpace ){ - if( pConfig->pTok ){ + if( azArg ){ + char *pSpace = (char*)&azArg[nArg]; + if( pConfig->t.azArg ){ *pzErr = sqlite3_mprintf("multiple tokenize=... directives"); rc = SQLITE_ERROR; }else{ @@ -204828,16 +246160,14 @@ static int fts5ConfigParseSpecial( *pzErr = sqlite3_mprintf("parse error in tokenize directive"); rc = SQLITE_ERROR; }else{ - rc = sqlite3Fts5GetTokenizer(pGlobal, - (const char**)azArg, (int)nArg, &pConfig->pTok, &pConfig->pTokApi, - pzErr - ); + pConfig->t.azArg = (const char**)azArg; + pConfig->t.nArg = nArg; + azArg = 0; } } } - sqlite3_free(azArg); - sqlite3_free(pDel); + return rc; } @@ -204856,6 +246186,26 @@ static int fts5ConfigParseSpecial( return rc; } + if( sqlite3_strnicmp("contentless_delete", zCmd, nCmd)==0 ){ + if( (zArg[0]!='0' && zArg[0]!='1') || zArg[1]!='\0' ){ + *pzErr = sqlite3_mprintf("malformed contentless_delete=... directive"); + rc = SQLITE_ERROR; + }else{ + pConfig->bContentlessDelete = (zArg[0]=='1'); + } + return rc; + } + + if( sqlite3_strnicmp("contentless_unindexed", zCmd, nCmd)==0 ){ + if( (zArg[0]!='0' && zArg[0]!='1') || zArg[1]!='\0' ){ + *pzErr = sqlite3_mprintf("malformed contentless_delete=... directive"); + rc = SQLITE_ERROR; + }else{ + pConfig->bContentlessUnindexed = (zArg[0]=='1'); + } + return rc; + } + if( sqlite3_strnicmp("content_rowid", zCmd, nCmd)==0 ){ if( pConfig->zContentRowid ){ *pzErr = sqlite3_mprintf("multiple content_rowid=... directives"); @@ -204876,6 +246226,16 @@ static int fts5ConfigParseSpecial( return rc; } + if( sqlite3_strnicmp("locale", zCmd, nCmd)==0 ){ + if( (zArg[0]!='0' && zArg[0]!='1') || zArg[1]!='\0' ){ + *pzErr = sqlite3_mprintf("malformed locale=... directive"); + rc = SQLITE_ERROR; + }else{ + pConfig->bLocale = (zArg[0]=='1'); + } + return rc; + } + if( sqlite3_strnicmp("detail", zCmd, nCmd)==0 ){ const Fts5Enum aDetail[] = { { "none", FTS5_DETAIL_NONE }, @@ -204890,22 +246250,20 @@ static int fts5ConfigParseSpecial( return rc; } + if( sqlite3_strnicmp("tokendata", zCmd, nCmd)==0 ){ + if( (zArg[0]!='0' && zArg[0]!='1') || zArg[1]!='\0' ){ + *pzErr = sqlite3_mprintf("malformed tokendata=... directive"); + rc = SQLITE_ERROR; + }else{ + pConfig->bTokendata = (zArg[0]=='1'); + } + return rc; + } + *pzErr = sqlite3_mprintf("unrecognized option: \"%.*s\"", nCmd, zCmd); return SQLITE_ERROR; } -/* -** Allocate an instance of the default tokenizer ("simple") at -** Fts5Config.pTokenizer. Return SQLITE_OK if successful, or an SQLite error -** code if an error occurs. -*/ -static int fts5ConfigDefaultTokenizer(Fts5Global *pGlobal, Fts5Config *pConfig){ - assert( pConfig->pTok==0 && pConfig->pTokApi==0 ); - return sqlite3Fts5GetTokenizer( - pGlobal, 0, 0, &pConfig->pTok, &pConfig->pTokApi, 0 - ); -} - /* ** Gobble up the first bareword or quoted word from the input buffer zIn. ** Return a pointer to the character immediately following the last in @@ -204962,20 +246320,22 @@ static const char *fts5ConfigGobbleWord( } static int fts5ConfigParseColumn( - Fts5Config *p, - char *zCol, - char *zArg, - char **pzErr + Fts5Config *p, + char *zCol, + char *zArg, + char **pzErr, + int *pbUnindexed ){ int rc = SQLITE_OK; - if( 0==sqlite3_stricmp(zCol, FTS5_RANK_NAME) - || 0==sqlite3_stricmp(zCol, FTS5_ROWID_NAME) + if( 0==sqlite3_stricmp(zCol, FTS5_RANK_NAME) + || 0==sqlite3_stricmp(zCol, FTS5_ROWID_NAME) ){ *pzErr = sqlite3_mprintf("reserved fts5 column name: %s", zCol); rc = SQLITE_ERROR; }else if( zArg ){ if( 0==sqlite3_stricmp(zArg, "unindexed") ){ p->abUnindexed[p->nCol] = 1; + *pbUnindexed = 1; }else{ *pzErr = sqlite3_mprintf("unrecognized column option: %s", zArg); rc = SQLITE_ERROR; @@ -204996,11 +246356,26 @@ static int fts5ConfigMakeExprlist(Fts5Config *p){ sqlite3Fts5BufferAppendPrintf(&rc, &buf, "T.%Q", p->zContentRowid); if( p->eContent!=FTS5_CONTENT_NONE ){ + assert( p->eContent==FTS5_CONTENT_EXTERNAL + || p->eContent==FTS5_CONTENT_NORMAL + || p->eContent==FTS5_CONTENT_UNINDEXED + ); for(i=0; inCol; i++){ if( p->eContent==FTS5_CONTENT_EXTERNAL ){ sqlite3Fts5BufferAppendPrintf(&rc, &buf, ", T.%Q", p->azCol[i]); - }else{ + }else if( p->eContent==FTS5_CONTENT_NORMAL || p->abUnindexed[i] ){ sqlite3Fts5BufferAppendPrintf(&rc, &buf, ", T.c%d", i); + }else{ + sqlite3Fts5BufferAppendPrintf(&rc, &buf, ", NULL"); + } + } + } + if( p->eContent==FTS5_CONTENT_NORMAL && p->bLocale ){ + for(i=0; inCol; i++){ + if( p->abUnindexed[i]==0 ){ + sqlite3Fts5BufferAppendPrintf(&rc, &buf, ", T.l%d", i); + }else{ + sqlite3Fts5BufferAppendPrintf(&rc, &buf, ", NULL"); } } } @@ -205012,14 +246387,14 @@ static int fts5ConfigMakeExprlist(Fts5Config *p){ /* ** Arguments nArg/azArg contain the string arguments passed to the xCreate -** or xConnect method of the virtual table. This function attempts to +** or xConnect method of the virtual table. This function attempts to ** allocate an instance of Fts5Config containing the results of parsing ** those arguments. ** ** If successful, SQLITE_OK is returned and *ppOut is set to point to the -** new Fts5Config object. If an error occurs, an SQLite error code is +** new Fts5Config object. If an error occurs, an SQLite error code is ** returned, *ppOut is set to NULL and an error message may be left in -** *pzErr. It is the responsibility of the caller to eventually free any +** *pzErr. It is the responsibility of the caller to eventually free any ** such error message using sqlite3_free(). */ static int sqlite3Fts5ConfigParse( @@ -205034,16 +246409,18 @@ static int sqlite3Fts5ConfigParse( Fts5Config *pRet; /* New object to return */ int i; sqlite3_int64 nByte; + int bUnindexed = 0; /* True if there are one or more UNINDEXED */ - *ppOut = pRet = (Fts5Config*)sqlite3_malloc(sizeof(Fts5Config)); + *ppOut = pRet = (Fts5Config*)sqlite3_malloc64(sizeof(Fts5Config)); if( pRet==0 ) return SQLITE_NOMEM; memset(pRet, 0, sizeof(Fts5Config)); + pRet->pGlobal = pGlobal; pRet->db = db; pRet->iCookie = -1; nByte = nArg * (sizeof(char*) + sizeof(u8)); pRet->azCol = (char**)sqlite3Fts5MallocZero(&rc, nByte); - pRet->abUnindexed = (u8*)&pRet->azCol[nArg]; + pRet->abUnindexed = pRet->azCol ? (u8*)&pRet->azCol[nArg] : 0; pRet->zDb = sqlite3Fts5Strndup(&rc, azArg[1], -1); pRet->zName = sqlite3Fts5Strndup(&rc, azArg[2], -1); pRet->bColumnsize = 1; @@ -205056,6 +246433,7 @@ static int sqlite3Fts5ConfigParse( rc = SQLITE_ERROR; } + assert( (pRet->abUnindexed && pRet->azCol) || rc!=SQLITE_OK ); for(i=3; rc==SQLITE_OK && ipTok==0 ){ - rc = fts5ConfigDefaultTokenizer(pGlobal, pRet); + /* We only allow contentless_delete=1 if the table is indeed contentless. */ + if( rc==SQLITE_OK + && pRet->bContentlessDelete + && pRet->eContent!=FTS5_CONTENT_NONE + ){ + *pzErr = sqlite3_mprintf( + "contentless_delete=1 requires a contentless table" + ); + rc = SQLITE_ERROR; + } + + /* We only allow contentless_delete=1 if columnsize=0 is not present. + ** + ** This restriction may be removed at some point. + */ + if( rc==SQLITE_OK && pRet->bContentlessDelete && pRet->bColumnsize==0 ){ + *pzErr = sqlite3_mprintf( + "contentless_delete=1 is incompatible with columnsize=0" + ); + rc = SQLITE_ERROR; + } + + /* We only allow contentless_unindexed=1 if the table is actually a + ** contentless one. + */ + if( rc==SQLITE_OK + && pRet->bContentlessUnindexed + && pRet->eContent!=FTS5_CONTENT_NONE + ){ + *pzErr = sqlite3_mprintf( + "contentless_unindexed=1 requires a contentless table" + ); + rc = SQLITE_ERROR; } /* If no zContent option was specified, fill in the default values. */ if( rc==SQLITE_OK && pRet->zContent==0 ){ const char *zTail = 0; - assert( pRet->eContent==FTS5_CONTENT_NORMAL - || pRet->eContent==FTS5_CONTENT_NONE + assert( pRet->eContent==FTS5_CONTENT_NORMAL + || pRet->eContent==FTS5_CONTENT_NONE ); if( pRet->eContent==FTS5_CONTENT_NORMAL ){ zTail = "content"; + }else if( bUnindexed && pRet->bContentlessUnindexed ){ + pRet->eContent = FTS5_CONTENT_UNINDEXED; + zTail = "content"; }else if( pRet->bColumnsize ){ zTail = "docsize"; } @@ -205144,9 +246558,14 @@ static int sqlite3Fts5ConfigParse( static void sqlite3Fts5ConfigFree(Fts5Config *pConfig){ if( pConfig ){ int i; - if( pConfig->pTok ){ - pConfig->pTokApi->xDelete(pConfig->pTok); + if( pConfig->t.pTok ){ + if( pConfig->t.pApi1 ){ + pConfig->t.pApi1->xDelete(pConfig->t.pTok); + }else{ + pConfig->t.pApi2->xDelete(pConfig->t.pTok); + } } + sqlite3_free((char*)pConfig->t.azArg); sqlite3_free(pConfig->zDb); sqlite3_free(pConfig->zName); for(i=0; inCol; i++){ @@ -205178,7 +246597,7 @@ static int sqlite3Fts5ConfigDeclareVtab(Fts5Config *pConfig){ const char *zSep = (i==0?"":", "); zSql = sqlite3Fts5Mprintf(&rc, "%z%s%Q", zSql, zSep, pConfig->azCol[i]); } - zSql = sqlite3Fts5Mprintf(&rc, "%z, %Q HIDDEN, %s HIDDEN)", + zSql = sqlite3Fts5Mprintf(&rc, "%z, %Q HIDDEN, %s HIDDEN)", zSql, pConfig->zName, FTS5_RANK_NAME ); @@ -205187,7 +246606,7 @@ static int sqlite3Fts5ConfigDeclareVtab(Fts5Config *pConfig){ rc = sqlite3_declare_vtab(pConfig->db, zSql); sqlite3_free(zSql); } - + return rc; } @@ -205205,7 +246624,7 @@ static int sqlite3Fts5ConfigDeclareVtab(Fts5Config *pConfig){ ** int iPos // Position of token in input (first token is 0) ** ** If the callback returns a non-zero value the tokenization is abandoned -** and no further callbacks are issued. +** and no further callbacks are issued. ** ** This function returns SQLITE_OK if successful or an SQLite error code ** if an error occurs. If the tokenization was abandoned early because @@ -205221,10 +246640,24 @@ static int sqlite3Fts5Tokenize( void *pCtx, /* Context passed to xToken() */ int (*xToken)(void*, int, const char*, int, int, int) /* Callback */ ){ - if( pText==0 ) return SQLITE_OK; - return pConfig->pTokApi->xTokenize( - pConfig->pTok, pCtx, flags, pText, nText, xToken - ); + int rc = SQLITE_OK; + if( pText ){ + if( pConfig->t.pTok==0 ){ + rc = sqlite3Fts5LoadTokenizer(pConfig); + } + if( rc==SQLITE_OK ){ + if( pConfig->t.pApi1 ){ + rc = pConfig->t.pApi1->xTokenize( + pConfig->t.pTok, pCtx, flags, pText, nText, xToken + ); + }else{ + rc = pConfig->t.pApi2->xTokenize(pConfig->t.pTok, pCtx, flags, + pText, nText, pConfig->t.pLocale, pConfig->t.nLocale, xToken + ); + } + } + } + return rc; } /* @@ -205235,7 +246668,7 @@ static int sqlite3Fts5Tokenize( */ static const char *fts5ConfigSkipArgs(const char *pIn){ const char *p = pIn; - + while( 1 ){ p = fts5ConfigSkipWhitespace(p); p = fts5ConfigSkipLiteral(p); @@ -205252,7 +246685,7 @@ static const char *fts5ConfigSkipArgs(const char *pIn){ } /* -** Parameter zIn contains a rank() function specification. The format of +** Parameter zIn contains a rank() function specification. The format of ** this is: ** ** + Bareword (function name) @@ -205294,7 +246727,7 @@ static int sqlite3Fts5ConfigParseRank( p++; } if( rc==SQLITE_OK ){ - const char *pArgs; + const char *pArgs; p = fts5ConfigSkipWhitespace(p); pArgs = p; if( *p!=')' ){ @@ -205320,8 +246753,8 @@ static int sqlite3Fts5ConfigParseRank( } static int sqlite3Fts5ConfigSetValue( - Fts5Config *pConfig, - const char *zKey, + Fts5Config *pConfig, + const char *zKey, sqlite3_value *pVal, int *pbBadkey ){ @@ -205332,7 +246765,7 @@ static int sqlite3Fts5ConfigSetValue( if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){ pgsz = sqlite3_value_int(pVal); } - if( pgsz<=0 || pgsz>FTS5_MAX_PAGE_SIZE ){ + if( pgsz<32 || pgsz>FTS5_MAX_PAGE_SIZE ){ *pbBadkey = 1; }else{ pConfig->pgsz = pgsz; @@ -205385,10 +246818,23 @@ static int sqlite3Fts5ConfigSetValue( *pbBadkey = 1; }else{ if( nCrisisMerge<=1 ) nCrisisMerge = FTS5_DEFAULT_CRISISMERGE; + if( nCrisisMerge>=FTS5_MAX_SEGMENT ) nCrisisMerge = FTS5_MAX_SEGMENT-1; pConfig->nCrisisMerge = nCrisisMerge; } } + else if( 0==sqlite3_stricmp(zKey, "deletemerge") ){ + int nVal = -1; + if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){ + nVal = sqlite3_value_int(pVal); + }else{ + *pbBadkey = 1; + } + if( nVal<0 ) nVal = FTS5_DEFAULT_DELETE_AUTOMERGE; + if( nVal>100 ) nVal = 0; + pConfig->nDeleteMerge = nVal; + } + else if( 0==sqlite3_stricmp(zKey, "rank") ){ const char *zIn = (const char*)sqlite3_value_text(pVal); char *zRank; @@ -205403,6 +246849,31 @@ static int sqlite3Fts5ConfigSetValue( rc = SQLITE_OK; *pbBadkey = 1; } + } + + else if( 0==sqlite3_stricmp(zKey, "secure-delete") ){ + int bVal = -1; + if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){ + bVal = sqlite3_value_int(pVal); + } + if( bVal<0 ){ + *pbBadkey = 1; + }else{ + pConfig->bSecureDelete = (bVal ? 1 : 0); + } + } + + else if( 0==sqlite3_stricmp(zKey, "insttoken") ){ + int bVal = -1; + if( SQLITE_INTEGER==sqlite3_value_numeric_type(pVal) ){ + bVal = sqlite3_value_int(pVal); + } + if( bVal<0 ){ + *pbBadkey = 1; + }else{ + pConfig->bPrefixInsttoken = (bVal ? 1 : 0); + } + }else{ *pbBadkey = 1; } @@ -205425,6 +246896,7 @@ static int sqlite3Fts5ConfigLoad(Fts5Config *pConfig, int iCookie){ pConfig->nUsermerge = FTS5_DEFAULT_USERMERGE; pConfig->nCrisisMerge = FTS5_DEFAULT_CRISISMERGE; pConfig->nHashSize = FTS5_DEFAULT_HASHSIZE; + pConfig->nDeleteMerge = FTS5_DEFAULT_DELETE_AUTOMERGE; zSql = sqlite3Fts5Mprintf(&rc, zSelect, pConfig->zDb, pConfig->zName); if( zSql ){ @@ -205446,16 +246918,18 @@ static int sqlite3Fts5ConfigLoad(Fts5Config *pConfig, int iCookie){ } rc = sqlite3_finalize(p); } - - if( rc==SQLITE_OK && iVersion!=FTS5_CURRENT_VERSION ){ + + if( rc==SQLITE_OK + && iVersion!=FTS5_CURRENT_VERSION + && iVersion!=FTS5_CURRENT_VERSION_SECUREDELETE + ){ rc = SQLITE_ERROR; - if( pConfig->pzErrmsg ){ - assert( 0==*pConfig->pzErrmsg ); - *pConfig->pzErrmsg = sqlite3_mprintf( - "invalid fts5 file format (found %d, expected %d) - run 'rebuild'", - iVersion, FTS5_CURRENT_VERSION - ); - } + sqlite3Fts5ConfigErrmsg(pConfig, "invalid fts5 file format " + "(found %d, expected %d or %d) - run 'rebuild'", + iVersion, FTS5_CURRENT_VERSION, FTS5_CURRENT_VERSION_SECUREDELETE + ); + }else{ + pConfig->iVersion = iVersion; } if( rc==SQLITE_OK ){ @@ -205464,6 +246938,27 @@ static int sqlite3Fts5ConfigLoad(Fts5Config *pConfig, int iCookie){ return rc; } +/* +** Set (*pConfig->pzErrmsg) to point to an sqlite3_malloc()ed buffer +** containing the error message created using printf() style formatting +** string zFmt and its trailing arguments. +*/ +static void sqlite3Fts5ConfigErrmsg(Fts5Config *pConfig, const char *zFmt, ...){ + va_list ap; /* ... printf arguments */ + char *zMsg = 0; + + va_start(ap, zFmt); + zMsg = sqlite3_vmprintf(zFmt, ap); + if( pConfig->pzErrmsg ){ + assert( *pConfig->pzErrmsg==0 ); + *pConfig->pzErrmsg = zMsg; + }else{ + sqlite3_free(zMsg); + } + + va_end(ap); +} + /* ** 2014 May 31 ** @@ -205483,6 +246978,10 @@ static int sqlite3Fts5ConfigLoad(Fts5Config *pConfig, int iCookie){ /* #include "fts5Int.h" */ /* #include "fts5parse.h" */ +#ifndef SQLITE_FTS5_MAX_EXPR_DEPTH +# define SQLITE_FTS5_MAX_EXPR_DEPTH 256 +#endif + /* ** All token types in the generated fts5parse.h file are greater than 0. */ @@ -205516,18 +247015,28 @@ struct Fts5Expr { /* ** eType: -** Expression node type. Always one of: +** Expression node type. Usually one of: ** ** FTS5_AND (nChild, apChild valid) ** FTS5_OR (nChild, apChild valid) ** FTS5_NOT (nChild, apChild valid) ** FTS5_STRING (pNear valid) ** FTS5_TERM (pNear valid) +** +** An expression node with eType==0 may also exist. It always matches zero +** rows. This is created when a phrase containing no tokens is parsed. +** e.g. "". +** +** iHeight: +** Distance from this node to furthest leaf. This is always 0 for nodes +** of type FTS5_STRING and FTS5_TERM. For all other nodes it is one +** greater than the largest child value. */ struct Fts5ExprNode { int eType; /* Node type */ int bEof; /* True at EOF */ int bNomatch; /* True if entry is not a match */ + int iHeight; /* Distance to tree leaf nodes */ /* Next method for this node. */ int (*xNext)(Fts5Expr*, Fts5ExprNode*, int, i64); @@ -205535,12 +247044,16 @@ struct Fts5ExprNode { i64 iRowid; /* Current rowid */ Fts5ExprNearset *pNear; /* For FTS5_STRING - cluster of phrases */ - /* Child nodes. For a NOT node, this array always contains 2 entries. For + /* Child nodes. For a NOT node, this array always contains 2 entries. For ** AND or OR nodes, it contains 2 or more entries. */ int nChild; /* Number of child nodes */ - Fts5ExprNode *apChild[1]; /* Array of child nodes */ + Fts5ExprNode *apChild[FLEXARRAY]; /* Array of child nodes */ }; +/* Size (in bytes) of an Fts5ExprNode object that holds up to N children */ +#define SZ_FTS5EXPRNODE(N) \ + (offsetof(Fts5ExprNode,apChild) + (N)*sizeof(Fts5ExprNode*)) + #define Fts5NodeIsString(p) ((p)->eType==FTS5_TERM || (p)->eType==FTS5_STRING) /* @@ -205556,7 +247069,9 @@ struct Fts5ExprNode { struct Fts5ExprTerm { u8 bPrefix; /* True for a prefix term */ u8 bFirst; /* True if token must be first in column */ - char *zTerm; /* nul-terminated term */ + char *pTerm; /* Term data */ + int nQueryTerm; /* Effective size of term in bytes */ + int nFullTerm; /* Size of term in bytes incl. tokendata */ Fts5IndexIter *pIter; /* Iterator for this term */ Fts5ExprTerm *pSynonym; /* Pointer to first in list of synonyms */ }; @@ -205569,9 +247084,13 @@ struct Fts5ExprPhrase { Fts5ExprNode *pNode; /* FTS5_STRING node this phrase is part of */ Fts5Buffer poslist; /* Current position list */ int nTerm; /* Number of entries in aTerm[] */ - Fts5ExprTerm aTerm[1]; /* Terms that make up this phrase */ + Fts5ExprTerm aTerm[FLEXARRAY]; /* Terms that make up this phrase */ }; +/* Size (in bytes) of an Fts5ExprPhrase object that holds up to N terms */ +#define SZ_FTS5EXPRPHRASE(N) \ + (offsetof(Fts5ExprPhrase,aTerm) + (N)*sizeof(Fts5ExprTerm)) + /* ** One or more phrases that must appear within a certain token distance of ** each other within each matching document. @@ -205580,9 +247099,12 @@ struct Fts5ExprNearset { int nNear; /* NEAR parameter */ Fts5Colset *pColset; /* Columns to search (NULL -> all columns) */ int nPhrase; /* Number of entries in aPhrase[] array */ - Fts5ExprPhrase *apPhrase[1]; /* Array of phrase pointers */ + Fts5ExprPhrase *apPhrase[FLEXARRAY]; /* Array of phrase pointers */ }; +/* Size (in bytes) of an Fts5ExprNearset object covering up to N phrases */ +#define SZ_FTS5EXPRNEARSET(N) \ + (offsetof(Fts5ExprNearset,apPhrase)+(N)*sizeof(Fts5ExprPhrase*)) /* ** Parse context. @@ -205594,12 +247116,39 @@ struct Fts5Parse { int nPhrase; /* Size of apPhrase array */ Fts5ExprPhrase **apPhrase; /* Array of all phrases */ Fts5ExprNode *pExpr; /* Result of a successful parse */ + int bPhraseToAnd; /* Convert "a+b" to "a AND b" */ }; +/* +** Check that the Fts5ExprNode.iHeight variables are set correctly in +** the expression tree passed as the only argument. +*/ +#ifndef NDEBUG +static void assert_expr_depth_ok(int rc, Fts5ExprNode *p){ + if( rc==SQLITE_OK ){ + if( p->eType==FTS5_TERM || p->eType==FTS5_STRING || p->eType==0 ){ + assert( p->iHeight==0 ); + }else{ + int ii; + int iMaxChild = 0; + for(ii=0; iinChild; ii++){ + Fts5ExprNode *pChild = p->apChild[ii]; + iMaxChild = MAX(iMaxChild, pChild->iHeight); + assert_expr_depth_ok(SQLITE_OK, pChild); + } + assert( p->iHeight==iMaxChild+1 ); + } + } +} +#else +# define assert_expr_depth_ok(rc, p) +#endif + static void sqlite3Fts5ParseError(Fts5Parse *pParse, const char *zFmt, ...){ va_list ap; va_start(ap, zFmt); if( pParse->rc==SQLITE_OK ){ + assert( pParse->zErr==0 ); pParse->zErr = sqlite3_vmprintf(zFmt, ap); pParse->rc = SQLITE_ERROR; } @@ -205614,7 +247163,7 @@ static int fts5ExprIsspace(char t){ ** Read the first token from the nul-terminated string at *pz. */ static int fts5ExprGetToken( - Fts5Parse *pParse, + Fts5Parse *pParse, const char **pz, /* IN/OUT: Pointer into buffer */ Fts5Token *pToken ){ @@ -205682,9 +247231,10 @@ static void fts5ParseFree(void *p){ sqlite3_free(p); } static int sqlite3Fts5ExprNew( Fts5Config *pConfig, /* FTS5 Configuration */ + int bPhraseToAnd, int iCol, const char *zExpr, /* Expression text */ - Fts5Expr **ppNew, + Fts5Expr **ppNew, char **pzErr ){ Fts5Parse sParse; @@ -205697,6 +247247,7 @@ static int sqlite3Fts5ExprNew( *ppNew = 0; *pzErr = 0; memset(&sParse, 0, sizeof(sParse)); + sParse.bPhraseToAnd = bPhraseToAnd; pEngine = sqlite3Fts5ParserAlloc(fts5ParseAlloc); if( pEngine==0 ){ return SQLITE_NOMEM; } sParse.pConfig = pConfig; @@ -205707,10 +247258,13 @@ static int sqlite3Fts5ExprNew( }while( sParse.rc==SQLITE_OK && t!=FTS5_EOF ); sqlite3Fts5ParserFree(pEngine, fts5ParseFree); + assert( sParse.pExpr || sParse.rc!=SQLITE_OK ); + assert_expr_depth_ok(sParse.rc, sParse.pExpr); + /* If the LHS of the MATCH expression was a user column, apply the ** implicit column-filter. */ - if( iColnCol && sParse.pExpr && sParse.rc==SQLITE_OK ){ - int n = sizeof(Fts5Colset); + if( sParse.rc==SQLITE_OK && iColnCol ){ + int n = SZ_FTS5COLSET(1); Fts5Colset *pColset = (Fts5Colset*)sqlite3Fts5MallocZero(&sParse.rc, n); if( pColset ){ pColset->nCol = 1; @@ -205721,24 +247275,17 @@ static int sqlite3Fts5ExprNew( assert( sParse.rc!=SQLITE_OK || sParse.zErr==0 ); if( sParse.rc==SQLITE_OK ){ - *ppNew = pNew = sqlite3_malloc(sizeof(Fts5Expr)); + *ppNew = pNew = sqlite3_malloc64(sizeof(Fts5Expr)); if( pNew==0 ){ sParse.rc = SQLITE_NOMEM; sqlite3Fts5ParseNodeFree(sParse.pExpr); }else{ - if( !sParse.pExpr ){ - const int nByte = sizeof(Fts5ExprNode); - pNew->pRoot = (Fts5ExprNode*)sqlite3Fts5MallocZero(&sParse.rc, nByte); - if( pNew->pRoot ){ - pNew->pRoot->bEof = 1; - } - }else{ - pNew->pRoot = sParse.pExpr; - } + pNew->pRoot = sParse.pExpr; pNew->pIndex = 0; pNew->pConfig = pConfig; pNew->apExprPhrase = sParse.apPhrase; pNew->nPhrase = sParse.nPhrase; + pNew->bDesc = 0; sParse.apPhrase = 0; } }else{ @@ -205746,10 +247293,103 @@ static int sqlite3Fts5ExprNew( } sqlite3_free(sParse.apPhrase); - *pzErr = sParse.zErr; + if( 0==*pzErr ){ + *pzErr = sParse.zErr; + }else{ + sqlite3_free(sParse.zErr); + } return sParse.rc; } +/* +** Assuming that buffer z is at least nByte bytes in size and contains a +** valid utf-8 string, return the number of characters in the string. +*/ +static int fts5ExprCountChar(const char *z, int nByte){ + int nRet = 0; + int ii; + for(ii=0; ii=3 ){ + int jj; + zExpr[iOut++] = '"'; + for(jj=iFirst; jj0 ){ + int bAnd = 0; + if( pConfig->eDetail!=FTS5_DETAIL_FULL ){ + bAnd = 1; + if( pConfig->eDetail==FTS5_DETAIL_NONE ){ + iCol = pConfig->nCol; + } + } + zExpr[iOut] = '\0'; + rc = sqlite3Fts5ExprNew(pConfig, bAnd, iCol, zExpr, pp,pConfig->pzErrmsg); + }else{ + *pp = 0; + } + sqlite3_free(zExpr); + } + + return rc; +} + /* ** Free the expression node object passed as the only argument. */ @@ -205775,6 +247415,42 @@ static void sqlite3Fts5ExprFree(Fts5Expr *p){ } } +static int sqlite3Fts5ExprAnd(Fts5Expr **pp1, Fts5Expr *p2){ + Fts5Parse sParse; + memset(&sParse, 0, sizeof(sParse)); + + if( *pp1 && p2 ){ + Fts5Expr *p1 = *pp1; + int nPhrase = p1->nPhrase + p2->nPhrase; + + p1->pRoot = sqlite3Fts5ParseNode(&sParse, FTS5_AND, p1->pRoot, p2->pRoot,0); + p2->pRoot = 0; + + if( sParse.rc==SQLITE_OK ){ + Fts5ExprPhrase **ap = (Fts5ExprPhrase**)sqlite3_realloc64( + p1->apExprPhrase, nPhrase * sizeof(Fts5ExprPhrase*) + ); + if( ap==0 ){ + sParse.rc = SQLITE_NOMEM; + }else{ + int i; + memmove(&ap[p2->nPhrase], ap, p1->nPhrase*sizeof(Fts5ExprPhrase*)); + for(i=0; inPhrase; i++){ + ap[i] = p2->apExprPhrase[i]; + } + p1->nPhrase = nPhrase; + p1->apExprPhrase = ap; + } + } + sqlite3_free(p2->apExprPhrase); + sqlite3_free(p2); + }else if( p2 ){ + *pp1 = p2; + } + + return sParse.rc; +} + /* ** Argument pTerm must be a synonym iterator. Return the current rowid ** that it points to. @@ -205784,6 +247460,7 @@ static i64 fts5ExprSynonymRowid(Fts5ExprTerm *pTerm, int bDesc, int *pbEof){ int bRetValid = 0; Fts5ExprTerm *p; + assert( pTerm ); assert( pTerm->pSynonym ); assert( bDesc==0 || bDesc==1 ); for(p=pTerm; p; p=p->pSynonym){ @@ -205804,7 +247481,7 @@ static i64 fts5ExprSynonymRowid(Fts5ExprTerm *pTerm, int bDesc, int *pbEof){ ** Argument pTerm must be a synonym iterator. */ static int fts5ExprSynonymList( - Fts5ExprTerm *pTerm, + Fts5ExprTerm *pTerm, i64 iRowid, Fts5Buffer *pBuf, /* Use this buffer for space if required */ u8 **pa, int *pn @@ -205877,13 +247554,13 @@ static int fts5ExprSynonymList( /* ** All individual term iterators in pPhrase are guaranteed to be valid and -** pointing to the same rowid when this function is called. This function +** pointing to the same rowid when this function is called. This function ** checks if the current rowid really is a match, and if so populates ** the pPhrase->poslist buffer accordingly. Output parameter *pbMatch ** is set to true if this is really a match, or false otherwise. ** -** SQLITE_OK is returned if an error occurs, or an SQLite error code -** otherwise. It is not considered an error code if the current rowid is +** SQLITE_OK is returned if an error occurs, or an SQLite error code +** otherwise. It is not considered an error code if the current rowid is ** not a match. */ static int fts5ExprPhraseIsMatch( @@ -205897,7 +247574,7 @@ static int fts5ExprPhraseIsMatch( int i; int rc = SQLITE_OK; int bFirst = pPhrase->aTerm[0].bFirst; - + fts5BufferZero(&pPhrase->poslist); /* If the aStatic[] array is not large enough, allocate a large array @@ -206019,7 +247696,7 @@ struct Fts5NearTrimmer { ** function is called, it is a no-op. Or, if an error (e.g. SQLITE_NOMEM) ** occurs within this function (*pRc) is set accordingly before returning. ** The return value is undefined in both these cases. -** +** ** If no error occurs and non-zero (a match) is returned, the position-list ** of each phrase object is edited to contain only those entries that ** meet the constraint before returning. @@ -206051,7 +247728,7 @@ static int fts5ExprNearIsMatch(int *pRc, Fts5ExprNearset *pNear){ /* Initialize a lookahead iterator for each phrase. After passing the ** buffer and buffer size to the lookaside-reader init function, zero ** the phrase poslist buffer. The new poslist for the phrase (containing - ** the same entries as the original with some entries removed on account + ** the same entries as the original with some entries removed on account ** of the NEAR constraint) is written over the original even as it is ** being read. This is safe as the entries for the new poslist are a ** subset of the old, so it is not possible for data yet to be read to @@ -206091,7 +247768,7 @@ static int fts5ExprNearIsMatch(int *pRc, Fts5ExprNearset *pNear){ i64 iPos = a[i].reader.iPos; Fts5PoslistWriter *pWriter = &a[i].writer; if( a[i].pOut->n==0 || iPos!=pWriter->iPrev ){ - sqlite3Fts5PoslistWriterAppend(a[i].pOut, pWriter, iPos); + sqlite3Fts5PoslistSafeAppend(a[i].pOut, &pWriter->iPrev, iPos); } } @@ -206208,7 +247885,7 @@ static int fts5ExprNearTest( ** phrase is not a match, break out of the loop early. */ for(i=0; rc==SQLITE_OK && inPhrase; i++){ Fts5ExprPhrase *pPhrase = pNear->apPhrase[i]; - if( pPhrase->nTerm>1 || pPhrase->aTerm[0].pSynonym + if( pPhrase->nTerm>1 || pPhrase->aTerm[0].pSynonym || pNear->pColset || pPhrase->aTerm[0].bFirst ){ int bMatch = 0; @@ -206265,7 +247942,7 @@ static int fts5ExprNearInitAll( p->pIter = 0; } rc = sqlite3Fts5IndexQuery( - pExpr->pIndex, p->zTerm, (int)strlen(p->zTerm), + pExpr->pIndex, p->pTerm, p->nQueryTerm, (pTerm->bPrefix ? FTS5INDEX_QUERY_PREFIX : 0) | (pExpr->bDesc ? FTS5INDEX_QUERY_DESC : 0), pNear->pColset, @@ -206356,7 +248033,7 @@ static void fts5ExprNodeZeroPoslist(Fts5ExprNode *pNode){ */ static int fts5NodeCompare( Fts5Expr *pExpr, - Fts5ExprNode *p1, + Fts5ExprNode *p1, Fts5ExprNode *p2 ){ if( p2->bEof ) return -1; @@ -206371,7 +248048,7 @@ static int fts5NodeCompare( ** If an EOF is reached before this happens, *pbEof is set to true before ** returning. ** -** SQLITE_OK is returned if an error occurs, or an SQLite error code +** SQLITE_OK is returned if an error occurs, or an SQLite error code ** otherwise. It is not considered an error code if an iterator reaches ** EOF. */ @@ -206388,8 +248065,8 @@ static int fts5ExprNodeTest_STRING( const int bDesc = pExpr->bDesc; /* Check that this node should not be FTS5_TERM */ - assert( pNear->nPhrase>1 - || pNear->apPhrase[0]->nTerm>1 + assert( pNear->nPhrase>1 + || pNear->apPhrase[0]->nTerm>1 || pNear->apPhrase[0]->aTerm[0].pSynonym || pNear->apPhrase[0]->aTerm[0].bFirst ); @@ -206421,7 +248098,7 @@ static int fts5ExprNodeTest_STRING( } }else{ Fts5IndexIter *pIter = pPhrase->aTerm[j].pIter; - if( pIter->iRowid==iLast || pIter->bEof ) continue; + if( pIter->iRowid==iLast ) continue; bMatch = 0; if( fts5ExprAdvanceto(pIter, bDesc, &iLast, &rc, &pNode->bEof) ){ return rc; @@ -206449,7 +248126,7 @@ static int fts5ExprNodeNext_STRING( Fts5Expr *pExpr, /* Expression pPhrase belongs to */ Fts5ExprNode *pNode, /* FTS5_STRING or FTS5_TERM node */ int bFromValid, - i64 iFrom + i64 iFrom ){ Fts5ExprTerm *pTerm = &pNode->pNear->apPhrase[0]->aTerm[0]; int rc = SQLITE_OK; @@ -206467,8 +248144,8 @@ static int fts5ExprNodeNext_STRING( for(p=pTerm; p; p=p->pSynonym){ if( sqlite3Fts5IterEof(p->pIter)==0 ){ i64 ii = p->pIter->iRowid; - if( ii==iRowid - || (bFromValid && ii!=iFrom && (ii>iFrom)==pExpr->bDesc) + if( ii==iRowid + || (bFromValid && ii!=iFrom && (ii>iFrom)==pExpr->bDesc) ){ if( bFromValid ){ rc = sqlite3Fts5IterNextFrom(p->pIter, iFrom); @@ -206514,9 +248191,9 @@ static int fts5ExprNodeTest_TERM( Fts5Expr *pExpr, /* Expression that pNear is a part of */ Fts5ExprNode *pNode /* The "NEAR" node (FTS5_TERM) */ ){ - /* As this "NEAR" object is actually a single phrase that consists + /* As this "NEAR" object is actually a single phrase that consists ** of a single term only, grab pointers into the poslist managed by the - ** fts5_index.c iterator object. This is much faster than synthesizing + ** fts5_index.c iterator object. This is much faster than synthesizing ** a new poslist the way we have to for more complicated phrase or NEAR ** expressions. */ Fts5ExprPhrase *pPhrase = pNode->pNear->apPhrase[0]; @@ -206539,7 +248216,7 @@ static int fts5ExprNodeTest_TERM( ** xNext() method for a node of type FTS5_TERM. */ static int fts5ExprNodeNext_TERM( - Fts5Expr *pExpr, + Fts5Expr *pExpr, Fts5ExprNode *pNode, int bFromValid, i64 iFrom @@ -206582,7 +248259,7 @@ static void fts5ExprNodeTest_OR( } static int fts5ExprNodeNext_OR( - Fts5Expr *pExpr, + Fts5Expr *pExpr, Fts5ExprNode *pNode, int bFromValid, i64 iFrom @@ -206594,7 +248271,7 @@ static int fts5ExprNodeNext_OR( Fts5ExprNode *p1 = pNode->apChild[i]; assert( p1->bEof || fts5RowidCmp(pExpr, p1->iRowid, iLast)>=0 ); if( p1->bEof==0 ){ - if( (p1->iRowid==iLast) + if( (p1->iRowid==iLast) || (bFromValid && fts5RowidCmp(pExpr, p1->iRowid, iFrom)<0) ){ int rc = fts5ExprNodeNext(pExpr, p1, bFromValid, iFrom); @@ -206666,7 +248343,7 @@ static int fts5ExprNodeTest_AND( } static int fts5ExprNodeNext_AND( - Fts5Expr *pExpr, + Fts5Expr *pExpr, Fts5ExprNode *pNode, int bFromValid, i64 iFrom @@ -206709,7 +248386,7 @@ static int fts5ExprNodeTest_NOT( } static int fts5ExprNodeNext_NOT( - Fts5Expr *pExpr, + Fts5Expr *pExpr, Fts5ExprNode *pNode, int bFromValid, i64 iFrom @@ -206766,7 +248443,7 @@ static int fts5ExprNodeTest( return rc; } - + /* ** Set node pNode, which is part of expression pExpr, to point to the first ** match. If there are no matches, set the Node.bEof flag to indicate EOF. @@ -206820,8 +248497,8 @@ static int fts5ExprNodeFirst(Fts5Expr *pExpr, Fts5ExprNode *pNode){ /* ** Begin iterating through the set of documents in index pIdx matched by -** the MATCH expression passed as the first argument. If the "bDesc" -** parameter is passed a non-zero value, iteration is in descending rowid +** the MATCH expression passed as the first argument. If the "bDesc" +** parameter is passed a non-zero value, iteration is in descending rowid ** order. Or, if it is zero, in ascending order. ** ** If iterating in ascending rowid order (bDesc==0), the first document @@ -206833,7 +248510,13 @@ static int fts5ExprNodeFirst(Fts5Expr *pExpr, Fts5ExprNode *pNode){ ** Return SQLITE_OK if successful, or an SQLite error code otherwise. It ** is not considered an error if the query does not match any documents. */ -static int sqlite3Fts5ExprFirst(Fts5Expr *p, Fts5Index *pIdx, i64 iFirst, int bDesc){ +static int sqlite3Fts5ExprFirst( + Fts5Expr *p, + Fts5Index *pIdx, + i64 iFirst, + i64 iLast, + int bDesc +){ Fts5ExprNode *pRoot = p->pRoot; int rc; /* Return code */ @@ -206843,23 +248526,26 @@ static int sqlite3Fts5ExprFirst(Fts5Expr *p, Fts5Index *pIdx, i64 iFirst, int bD /* If not at EOF but the current rowid occurs earlier than iFirst in ** the iteration order, move to document iFirst or later. */ - if( rc==SQLITE_OK - && 0==pRoot->bEof - && fts5RowidCmp(p, pRoot->iRowid, iFirst)<0 + if( rc==SQLITE_OK + && 0==pRoot->bEof + && fts5RowidCmp(p, pRoot->iRowid, iFirst)<0 ){ rc = fts5ExprNodeNext(p, pRoot, 1, iFirst); } /* If the iterator is not at a real match, skip forward until it is. */ - while( pRoot->bNomatch ){ - assert( pRoot->bEof==0 && rc==SQLITE_OK ); + while( pRoot->bNomatch && rc==SQLITE_OK ){ + assert( pRoot->bEof==0 ); rc = fts5ExprNodeNext(p, pRoot, 0, 0); } + if( fts5RowidCmp(p, pRoot->iRowid, iLast)>0 ){ + pRoot->bEof = 1; + } return rc; } /* -** Move to the next document +** Move to the next document ** ** Return SQLITE_OK if successful, or an SQLite error code otherwise. It ** is not considered an error if the query does not match any documents. @@ -206902,7 +248588,7 @@ static void fts5ExprPhraseFree(Fts5ExprPhrase *pPhrase){ Fts5ExprTerm *pSyn; Fts5ExprTerm *pNext; Fts5ExprTerm *pTerm = &pPhrase->aTerm[i]; - sqlite3_free(pTerm->zTerm); + sqlite3_free(pTerm->pTerm); sqlite3Fts5IterClose(pTerm->pIter); for(pSyn=pTerm->pSynonym; pSyn; pSyn=pNext){ pNext = pSyn->pSynonym; @@ -206943,12 +248629,9 @@ static Fts5ExprNearset *sqlite3Fts5ParseNearset( Fts5ExprNearset *pRet = 0; if( pParse->rc==SQLITE_OK ){ - if( pPhrase==0 ){ - return pNear; - } if( pNear==0 ){ sqlite3_int64 nByte; - nByte = sizeof(Fts5ExprNearset) + SZALLOC * sizeof(Fts5ExprPhrase*); + nByte = SZ_FTS5EXPRNEARSET(SZALLOC+1); pRet = sqlite3_malloc64(nByte); if( pRet==0 ){ pParse->rc = SQLITE_NOMEM; @@ -206959,7 +248642,7 @@ static Fts5ExprNearset *sqlite3Fts5ParseNearset( int nNew = pNear->nPhrase + SZALLOC; sqlite3_int64 nByte; - nByte = sizeof(Fts5ExprNearset) + nNew * sizeof(Fts5ExprPhrase*); + nByte = SZ_FTS5EXPRNEARSET(nNew+1); pRet = (Fts5ExprNearset*)sqlite3_realloc64(pNear, nByte); if( pRet==0 ){ pParse->rc = SQLITE_NOMEM; @@ -206976,6 +248659,9 @@ static Fts5ExprNearset *sqlite3Fts5ParseNearset( }else{ if( pRet->nPhrase>0 ){ Fts5ExprPhrase *pLast = pRet->apPhrase[pRet->nPhrase-1]; + assert( pParse!=0 ); + assert( pParse->apPhrase!=0 ); + assert( pParse->nPhrase>=2 ); assert( pLast==pParse->apPhrase[pParse->nPhrase-2] ); if( pPhrase->nTerm==0 ){ fts5ExprPhraseFree(pPhrase); @@ -206997,6 +248683,7 @@ static Fts5ExprNearset *sqlite3Fts5ParseNearset( typedef struct TokenCtx TokenCtx; struct TokenCtx { Fts5ExprPhrase *pPhrase; + Fts5Config *pConfig; int rc; }; @@ -207030,8 +248717,12 @@ static int fts5ParseTokenize( rc = SQLITE_NOMEM; }else{ memset(pSyn, 0, (size_t)nByte); - pSyn->zTerm = ((char*)pSyn) + sizeof(Fts5ExprTerm) + sizeof(Fts5Buffer); - memcpy(pSyn->zTerm, pToken, nToken); + pSyn->pTerm = ((char*)pSyn) + sizeof(Fts5ExprTerm) + sizeof(Fts5Buffer); + pSyn->nFullTerm = pSyn->nQueryTerm = nToken; + memcpy(pSyn->pTerm, pToken, nToken); + if( pCtx->pConfig->bTokendata ){ + pSyn->nQueryTerm = (int)strlen(pSyn->pTerm); + } pSyn->pSynonym = pPhrase->aTerm[pPhrase->nTerm-1].pSynonym; pPhrase->aTerm[pPhrase->nTerm-1].pSynonym = pSyn; } @@ -207041,13 +248732,13 @@ static int fts5ParseTokenize( Fts5ExprPhrase *pNew; int nNew = SZALLOC + (pPhrase ? pPhrase->nTerm : 0); - pNew = (Fts5ExprPhrase*)sqlite3_realloc64(pPhrase, - sizeof(Fts5ExprPhrase) + sizeof(Fts5ExprTerm) * nNew + pNew = (Fts5ExprPhrase*)sqlite3_realloc64(pPhrase, + SZ_FTS5EXPRPHRASE(nNew+1) ); if( pNew==0 ){ rc = SQLITE_NOMEM; }else{ - if( pPhrase==0 ) memset(pNew, 0, sizeof(Fts5ExprPhrase)); + if( pPhrase==0 ) memset(pNew, 0, SZ_FTS5EXPRPHRASE(1)); pCtx->pPhrase = pPhrase = pNew; pNew->nTerm = nNew - SZALLOC; } @@ -207056,7 +248747,11 @@ static int fts5ParseTokenize( if( rc==SQLITE_OK ){ pTerm = &pPhrase->aTerm[pPhrase->nTerm++]; memset(pTerm, 0, sizeof(Fts5ExprTerm)); - pTerm->zTerm = sqlite3Fts5Strndup(&rc, pToken, nToken); + pTerm->pTerm = sqlite3Fts5Strndup(&rc, pToken, nToken); + pTerm->nFullTerm = pTerm->nQueryTerm = nToken; + if( pCtx->pConfig->bTokendata && rc==SQLITE_OK ){ + pTerm->nQueryTerm = (int)strlen(pTerm->pTerm); + } } } @@ -207091,6 +248786,20 @@ static void sqlite3Fts5ParseFinished(Fts5Parse *pParse, Fts5ExprNode *p){ pParse->pExpr = p; } +static int parseGrowPhraseArray(Fts5Parse *pParse){ + if( (pParse->nPhrase % 8)==0 ){ + sqlite3_int64 nByte = sizeof(Fts5ExprPhrase*) * (pParse->nPhrase + 8); + Fts5ExprPhrase **apNew; + apNew = (Fts5ExprPhrase**)sqlite3_realloc64(pParse->apPhrase, nByte); + if( apNew==0 ){ + pParse->rc = SQLITE_NOMEM; + return SQLITE_NOMEM; + } + pParse->apPhrase = apNew; + } + return SQLITE_OK; +} + /* ** This function is called by the parser to process a string token. The ** string may or may not be quoted. In any case it is tokenized and a @@ -207109,6 +248818,7 @@ static Fts5ExprPhrase *sqlite3Fts5ParseTerm( memset(&sCtx, 0, sizeof(TokenCtx)); sCtx.pPhrase = pAppend; + sCtx.pConfig = pConfig; rc = fts5ParseStringFromToken(pToken, &z); if( rc==SQLITE_OK ){ @@ -207126,16 +248836,9 @@ static Fts5ExprPhrase *sqlite3Fts5ParseTerm( }else{ if( pAppend==0 ){ - if( (pParse->nPhrase % 8)==0 ){ - sqlite3_int64 nByte = sizeof(Fts5ExprPhrase*) * (pParse->nPhrase + 8); - Fts5ExprPhrase **apNew; - apNew = (Fts5ExprPhrase**)sqlite3_realloc64(pParse->apPhrase, nByte); - if( apNew==0 ){ - pParse->rc = SQLITE_NOMEM; - fts5ExprPhraseFree(sCtx.pPhrase); - return 0; - } - pParse->apPhrase = apNew; + if( parseGrowPhraseArray(pParse) ){ + fts5ExprPhraseFree(sCtx.pPhrase); + return 0; } pParse->nPhrase++; } @@ -207143,10 +248846,11 @@ static Fts5ExprPhrase *sqlite3Fts5ParseTerm( if( sCtx.pPhrase==0 ){ /* This happens when parsing a token or quoted phrase that contains ** no token characters at all. (e.g ... MATCH '""'). */ - sCtx.pPhrase = sqlite3Fts5MallocZero(&pParse->rc, sizeof(Fts5ExprPhrase)); + sCtx.pPhrase = sqlite3Fts5MallocZero(&pParse->rc, SZ_FTS5EXPRPHRASE(1)); }else if( sCtx.pPhrase->nTerm ){ sCtx.pPhrase->aTerm[sCtx.pPhrase->nTerm-1].bPrefix = (u8)bPrefix; } + assert( pParse->apPhrase!=0 ); pParse->apPhrase[pParse->nPhrase-1] = sCtx.pPhrase; } @@ -207158,66 +248862,69 @@ static Fts5ExprPhrase *sqlite3Fts5ParseTerm( ** expression passed as the second argument. */ static int sqlite3Fts5ExprClonePhrase( - Fts5Expr *pExpr, - int iPhrase, + Fts5Expr *pExpr, + int iPhrase, Fts5Expr **ppNew ){ int rc = SQLITE_OK; /* Return code */ - Fts5ExprPhrase *pOrig; /* The phrase extracted from pExpr */ + Fts5ExprPhrase *pOrig = 0; /* The phrase extracted from pExpr */ Fts5Expr *pNew = 0; /* Expression to return via *ppNew */ - TokenCtx sCtx = {0,0}; /* Context object for fts5ParseTokenize */ - - pOrig = pExpr->apExprPhrase[iPhrase]; - pNew = (Fts5Expr*)sqlite3Fts5MallocZero(&rc, sizeof(Fts5Expr)); + TokenCtx sCtx = {0,0,0}; /* Context object for fts5ParseTokenize */ + if( !pExpr || iPhrase<0 || iPhrase>=pExpr->nPhrase ){ + rc = SQLITE_RANGE; + }else{ + pOrig = pExpr->apExprPhrase[iPhrase]; + pNew = (Fts5Expr*)sqlite3Fts5MallocZero(&rc, sizeof(Fts5Expr)); + } if( rc==SQLITE_OK ){ - pNew->apExprPhrase = (Fts5ExprPhrase**)sqlite3Fts5MallocZero(&rc, + pNew->apExprPhrase = (Fts5ExprPhrase**)sqlite3Fts5MallocZero(&rc, sizeof(Fts5ExprPhrase*)); } if( rc==SQLITE_OK ){ - pNew->pRoot = (Fts5ExprNode*)sqlite3Fts5MallocZero(&rc, - sizeof(Fts5ExprNode)); + pNew->pRoot = (Fts5ExprNode*)sqlite3Fts5MallocZero(&rc, SZ_FTS5EXPRNODE(1)); } if( rc==SQLITE_OK ){ - pNew->pRoot->pNear = (Fts5ExprNearset*)sqlite3Fts5MallocZero(&rc, - sizeof(Fts5ExprNearset) + sizeof(Fts5ExprPhrase*)); + pNew->pRoot->pNear = (Fts5ExprNearset*)sqlite3Fts5MallocZero(&rc, + SZ_FTS5EXPRNEARSET(2)); } - if( rc==SQLITE_OK ){ + if( rc==SQLITE_OK && ALWAYS(pOrig!=0) ){ Fts5Colset *pColsetOrig = pOrig->pNode->pNear->pColset; if( pColsetOrig ){ sqlite3_int64 nByte; Fts5Colset *pColset; - nByte = sizeof(Fts5Colset) + (pColsetOrig->nCol-1) * sizeof(int); + nByte = SZ_FTS5COLSET(pColsetOrig->nCol); pColset = (Fts5Colset*)sqlite3Fts5MallocZero(&rc, nByte); - if( pColset ){ + if( pColset ){ memcpy(pColset, pColsetOrig, (size_t)nByte); } pNew->pRoot->pNear->pColset = pColset; } } - if( pOrig->nTerm ){ - int i; /* Used to iterate through phrase terms */ - for(i=0; rc==SQLITE_OK && inTerm; i++){ - int tflags = 0; - Fts5ExprTerm *p; - for(p=&pOrig->aTerm[i]; p && rc==SQLITE_OK; p=p->pSynonym){ - const char *zTerm = p->zTerm; - rc = fts5ParseTokenize((void*)&sCtx, tflags, zTerm, (int)strlen(zTerm), - 0, 0); - tflags = FTS5_TOKEN_COLOCATED; - } - if( rc==SQLITE_OK ){ - sCtx.pPhrase->aTerm[i].bPrefix = pOrig->aTerm[i].bPrefix; - sCtx.pPhrase->aTerm[i].bFirst = pOrig->aTerm[i].bFirst; + if( rc==SQLITE_OK ){ + if( pOrig->nTerm ){ + int i; /* Used to iterate through phrase terms */ + sCtx.pConfig = pExpr->pConfig; + for(i=0; rc==SQLITE_OK && inTerm; i++){ + int tflags = 0; + Fts5ExprTerm *p; + for(p=&pOrig->aTerm[i]; p && rc==SQLITE_OK; p=p->pSynonym){ + rc = fts5ParseTokenize((void*)&sCtx,tflags,p->pTerm,p->nFullTerm,0,0); + tflags = FTS5_TOKEN_COLOCATED; + } + if( rc==SQLITE_OK ){ + sCtx.pPhrase->aTerm[i].bPrefix = pOrig->aTerm[i].bPrefix; + sCtx.pPhrase->aTerm[i].bFirst = pOrig->aTerm[i].bFirst; + } } + }else{ + /* This happens when parsing a token or quoted phrase that contains + ** no token characters at all. (e.g ... MATCH '""'). */ + sCtx.pPhrase = sqlite3Fts5MallocZero(&rc, SZ_FTS5EXPRPHRASE(1)); } - }else{ - /* This happens when parsing a token or quoted phrase that contains - ** no token characters at all. (e.g ... MATCH '""'). */ - sCtx.pPhrase = sqlite3Fts5MallocZero(&rc, sizeof(Fts5ExprPhrase)); } - if( rc==SQLITE_OK ){ + if( rc==SQLITE_OK && ALWAYS(sCtx.pPhrase) ){ /* All the allocations succeeded. Put the expression object together. */ pNew->pIndex = pExpr->pIndex; pNew->pConfig = pExpr->pConfig; @@ -207227,9 +248934,9 @@ static int sqlite3Fts5ExprClonePhrase( pNew->pRoot->pNear->nPhrase = 1; sCtx.pPhrase->pNode = pNew->pRoot; - if( pOrig->nTerm==1 - && pOrig->aTerm[0].pSynonym==0 - && pOrig->aTerm[0].bFirst==0 + if( pOrig->nTerm==1 + && pOrig->aTerm[0].pSynonym==0 + && pOrig->aTerm[0].bFirst==0 ){ pNew->pRoot->eType = FTS5_TERM; pNew->pRoot->xNext = fts5ExprNodeNext_TERM; @@ -207262,7 +248969,7 @@ static void sqlite3Fts5ParseNear(Fts5Parse *pParse, Fts5Token *pTok){ } static void sqlite3Fts5ParseSetDistance( - Fts5Parse *pParse, + Fts5Parse *pParse, Fts5ExprNearset *pNear, Fts5Token *p ){ @@ -207278,7 +248985,8 @@ static void sqlite3Fts5ParseSetDistance( ); return; } - nNear = nNear * 10 + (p->p[i] - '0'); + if( nNear<214748363 ) nNear = nNear * 10 + (p->p[i] - '0'); + /* ^^^^^^^^^^^^^^^--- Prevent integer overflow */ } }else{ nNear = FTS5_DEFAULT_NEARDIST; @@ -207291,7 +248999,7 @@ static void sqlite3Fts5ParseSetDistance( ** The second argument passed to this function may be NULL, or it may be ** an existing Fts5Colset object. This function returns a pointer to ** a new colset object containing the contents of (p) with new value column -** number iCol appended. +** number iCol appended. ** ** If an OOM error occurs, store an error code in pParse and return NULL. ** The old colset object (if any) is not freed in this case. @@ -207307,7 +249015,7 @@ static Fts5Colset *fts5ParseColset( assert( pParse->rc==SQLITE_OK ); assert( iCol>=0 && iColpConfig->nCol ); - pNew = sqlite3_realloc64(p, sizeof(Fts5Colset) + sizeof(int)*nCol); + pNew = sqlite3_realloc64(p, SZ_FTS5COLSET(nCol+1)); if( pNew==0 ){ pParse->rc = SQLITE_NOMEM; }else{ @@ -207341,8 +249049,8 @@ static Fts5Colset *sqlite3Fts5ParseColsetInvert(Fts5Parse *pParse, Fts5Colset *p Fts5Colset *pRet; int nCol = pParse->pConfig->nCol; - pRet = (Fts5Colset*)sqlite3Fts5MallocZero(&pParse->rc, - sizeof(Fts5Colset) + sizeof(int)*nCol + pRet = (Fts5Colset*)sqlite3Fts5MallocZero(&pParse->rc, + SZ_FTS5COLSET(nCol+1) ); if( pRet ){ int i; @@ -207394,7 +249102,7 @@ static Fts5Colset *sqlite3Fts5ParseColset( /* ** If argument pOrig is NULL, or if (*pRc) is set to anything other than -** SQLITE_OK when this function is called, NULL is returned. +** SQLITE_OK when this function is called, NULL is returned. ** ** Otherwise, a copy of (*pOrig) is made into memory obtained from ** sqlite3Fts5MallocZero() and a pointer to it returned. If the allocation @@ -207403,9 +249111,9 @@ static Fts5Colset *sqlite3Fts5ParseColset( static Fts5Colset *fts5CloneColset(int *pRc, Fts5Colset *pOrig){ Fts5Colset *pRet; if( pOrig ){ - sqlite3_int64 nByte = sizeof(Fts5Colset) + (pOrig->nCol-1) * sizeof(int); + sqlite3_int64 nByte = SZ_FTS5COLSET(pOrig->nCol); pRet = (Fts5Colset*)sqlite3Fts5MallocZero(pRc, nByte); - if( pRet ){ + if( pRet ){ memcpy(pRet, pOrig, (size_t)nByte); } }else{ @@ -207444,13 +249152,13 @@ static void fts5MergeColset(Fts5Colset *pColset, Fts5Colset *pMerge){ ** zero, or it may create copies of pColset using fts5CloneColset(). */ static void fts5ParseSetColset( - Fts5Parse *pParse, - Fts5ExprNode *pNode, + Fts5Parse *pParse, + Fts5ExprNode *pNode, Fts5Colset *pColset, Fts5Colset **ppFree ){ if( pParse->rc==SQLITE_OK ){ - assert( pNode->eType==FTS5_TERM || pNode->eType==FTS5_STRING + assert( pNode->eType==FTS5_TERM || pNode->eType==FTS5_STRING || pNode->eType==FTS5_AND || pNode->eType==FTS5_OR || pNode->eType==FTS5_NOT || pNode->eType==FTS5_EOF ); @@ -207482,15 +249190,14 @@ static void fts5ParseSetColset( ** Apply colset pColset to expression node pExpr and all of its descendents. */ static void sqlite3Fts5ParseSetColset( - Fts5Parse *pParse, - Fts5ExprNode *pExpr, - Fts5Colset *pColset + Fts5Parse *pParse, + Fts5ExprNode *pExpr, + Fts5Colset *pColset ){ Fts5Colset *pFree = pColset; if( pParse->pConfig->eDetail==FTS5_DETAIL_NONE ){ - pParse->rc = SQLITE_ERROR; - pParse->zErr = sqlite3_mprintf( - "fts5: column queries are not supported (detail=none)" + sqlite3Fts5ParseError(pParse, + "fts5: column queries are not supported (detail=none)" ); }else{ fts5ParseSetColset(pParse, pExpr, pColset, &pFree); @@ -207502,7 +249209,7 @@ static void fts5ExprAssignXNext(Fts5ExprNode *pNode){ switch( pNode->eType ){ case FTS5_STRING: { Fts5ExprNearset *pNear = pNode->pNear; - if( pNear->nPhrase==1 && pNear->apPhrase[0]->nTerm==1 + if( pNear->nPhrase==1 && pNear->apPhrase[0]->nTerm==1 && pNear->apPhrase[0]->aTerm[0].pSynonym==0 && pNear->apPhrase[0]->aTerm[0].bFirst==0 ){ @@ -207531,7 +249238,11 @@ static void fts5ExprAssignXNext(Fts5ExprNode *pNode){ } } +/* +** Add pSub as a child of p. +*/ static void fts5ExprAddChildren(Fts5ExprNode *p, Fts5ExprNode *pSub){ + int ii = p->nChild; if( p->eType!=FTS5_NOT && pSub->eType==p->eType ){ int nByte = sizeof(Fts5ExprNode*) * pSub->nChild; memcpy(&p->apChild[p->nChild], pSub->apChild, nByte); @@ -207540,6 +249251,73 @@ static void fts5ExprAddChildren(Fts5ExprNode *p, Fts5ExprNode *pSub){ }else{ p->apChild[p->nChild++] = pSub; } + for( ; iinChild; ii++){ + p->iHeight = MAX(p->iHeight, p->apChild[ii]->iHeight + 1); + } +} + +/* +** This function is used when parsing LIKE or GLOB patterns against +** trigram indexes that specify either detail=column or detail=none. +** It converts a phrase: +** +** abc + def + ghi +** +** into an AND tree: +** +** abc AND def AND ghi +*/ +static Fts5ExprNode *fts5ParsePhraseToAnd( + Fts5Parse *pParse, + Fts5ExprNearset *pNear +){ + int nTerm = pNear->apPhrase[0]->nTerm; + int ii; + int nByte; + Fts5ExprNode *pRet; + + assert( pNear->nPhrase==1 ); + assert( pParse->bPhraseToAnd ); + + nByte = SZ_FTS5EXPRNODE(nTerm+1); + pRet = (Fts5ExprNode*)sqlite3Fts5MallocZero(&pParse->rc, nByte); + if( pRet ){ + pRet->eType = FTS5_AND; + pRet->nChild = nTerm; + pRet->iHeight = 1; + fts5ExprAssignXNext(pRet); + pParse->nPhrase--; + for(ii=0; iirc, SZ_FTS5EXPRPHRASE(1) + ); + if( pPhrase ){ + if( parseGrowPhraseArray(pParse) ){ + fts5ExprPhraseFree(pPhrase); + }else{ + Fts5ExprTerm *p = &pNear->apPhrase[0]->aTerm[ii]; + Fts5ExprTerm *pTo = &pPhrase->aTerm[0]; + pParse->apPhrase[pParse->nPhrase++] = pPhrase; + pPhrase->nTerm = 1; + pTo->pTerm = sqlite3Fts5Strndup(&pParse->rc, p->pTerm, p->nFullTerm); + pTo->nQueryTerm = p->nQueryTerm; + pTo->nFullTerm = p->nFullTerm; + pRet->apChild[ii] = sqlite3Fts5ParseNode(pParse, FTS5_STRING, + 0, 0, sqlite3Fts5ParseNearset(pParse, 0, pPhrase) + ); + } + } + } + + if( pParse->rc ){ + sqlite3Fts5ParseNodeFree(pRet); + pRet = 0; + }else{ + sqlite3Fts5ParseNearsetFree(pNear); + } + } + + return pRet; } /* @@ -207558,7 +249336,7 @@ static Fts5ExprNode *sqlite3Fts5ParseNode( if( pParse->rc==SQLITE_OK ){ int nChild = 0; /* Number of children of returned node */ sqlite3_int64 nByte; /* Bytes of space to allocate for this node */ - + assert( (eType!=FTS5_STRING && !pNear) || (eType==FTS5_STRING && !pLeft && !pRight) ); @@ -207566,51 +249344,67 @@ static Fts5ExprNode *sqlite3Fts5ParseNode( if( eType!=FTS5_STRING && pLeft==0 ) return pRight; if( eType!=FTS5_STRING && pRight==0 ) return pLeft; - if( eType==FTS5_NOT ){ - nChild = 2; - }else if( eType==FTS5_AND || eType==FTS5_OR ){ - nChild = 2; - if( pLeft->eType==eType ) nChild += pLeft->nChild-1; - if( pRight->eType==eType ) nChild += pRight->nChild-1; - } + if( eType==FTS5_STRING + && pParse->bPhraseToAnd + && pNear->apPhrase[0]->nTerm>1 + ){ + pRet = fts5ParsePhraseToAnd(pParse, pNear); + }else{ + if( eType==FTS5_NOT ){ + nChild = 2; + }else if( eType==FTS5_AND || eType==FTS5_OR ){ + nChild = 2; + if( pLeft->eType==eType ) nChild += pLeft->nChild-1; + if( pRight->eType==eType ) nChild += pRight->nChild-1; + } - nByte = sizeof(Fts5ExprNode) + sizeof(Fts5ExprNode*)*(nChild-1); - pRet = (Fts5ExprNode*)sqlite3Fts5MallocZero(&pParse->rc, nByte); + nByte = SZ_FTS5EXPRNODE(nChild); + pRet = (Fts5ExprNode*)sqlite3Fts5MallocZero(&pParse->rc, nByte); - if( pRet ){ - pRet->eType = eType; - pRet->pNear = pNear; - fts5ExprAssignXNext(pRet); - if( eType==FTS5_STRING ){ - int iPhrase; - for(iPhrase=0; iPhrasenPhrase; iPhrase++){ - pNear->apPhrase[iPhrase]->pNode = pRet; - if( pNear->apPhrase[iPhrase]->nTerm==0 ){ - pRet->xNext = 0; - pRet->eType = FTS5_EOF; - } - } - - if( pParse->pConfig->eDetail!=FTS5_DETAIL_FULL ){ - Fts5ExprPhrase *pPhrase = pNear->apPhrase[0]; - if( pNear->nPhrase!=1 - || pPhrase->nTerm>1 - || (pPhrase->nTerm>0 && pPhrase->aTerm[0].bFirst) - ){ - assert( pParse->rc==SQLITE_OK ); - pParse->rc = SQLITE_ERROR; - assert( pParse->zErr==0 ); - pParse->zErr = sqlite3_mprintf( - "fts5: %s queries are not supported (detail!=full)", - pNear->nPhrase==1 ? "phrase": "NEAR" - ); - sqlite3_free(pRet); + if( pRet ){ + pRet->eType = eType; + pRet->pNear = pNear; + fts5ExprAssignXNext(pRet); + if( eType==FTS5_STRING ){ + int iPhrase; + for(iPhrase=0; iPhrasenPhrase; iPhrase++){ + pNear->apPhrase[iPhrase]->pNode = pRet; + if( pNear->apPhrase[iPhrase]->nTerm==0 ){ + pRet->xNext = 0; + pRet->eType = FTS5_EOF; + } + } + + if( pParse->pConfig->eDetail!=FTS5_DETAIL_FULL ){ + Fts5ExprPhrase *pPhrase = pNear->apPhrase[0]; + if( pNear->nPhrase!=1 + || pPhrase->nTerm>1 + || (pPhrase->nTerm>0 && pPhrase->aTerm[0].bFirst) + ){ + sqlite3Fts5ParseError(pParse, + "fts5: %s queries are not supported (detail!=full)", + pNear->nPhrase==1 ? "phrase": "NEAR" + ); + sqlite3Fts5ParseNodeFree(pRet); + pRet = 0; + pNear = 0; + assert( pLeft==0 && pRight==0 ); + } + } + }else{ + assert( pNear==0 ); + fts5ExprAddChildren(pRet, pLeft); + fts5ExprAddChildren(pRet, pRight); + pLeft = pRight = 0; + if( pRet->iHeight>SQLITE_FTS5_MAX_EXPR_DEPTH ){ + sqlite3Fts5ParseError(pParse, + "fts5 expression tree is too large (maximum depth %d)", + SQLITE_FTS5_MAX_EXPR_DEPTH + ); + sqlite3Fts5ParseNodeFree(pRet); pRet = 0; } } - }else{ - fts5ExprAddChildren(pRet, pLeft); - fts5ExprAddChildren(pRet, pRight); } } } @@ -207637,14 +249431,15 @@ static Fts5ExprNode *sqlite3Fts5ParseImplicitAnd( sqlite3Fts5ParseNodeFree(pRight); }else{ - assert( pLeft->eType==FTS5_STRING + assert( pLeft->eType==FTS5_STRING || pLeft->eType==FTS5_TERM || pLeft->eType==FTS5_EOF || pLeft->eType==FTS5_AND ); - assert( pRight->eType==FTS5_STRING - || pRight->eType==FTS5_TERM - || pRight->eType==FTS5_EOF + assert( pRight->eType==FTS5_STRING + || pRight->eType==FTS5_TERM + || pRight->eType==FTS5_EOF + || (pRight->eType==FTS5_AND && pParse->bPhraseToAnd) ); if( pLeft->eType==FTS5_AND ){ @@ -207652,12 +249447,14 @@ static Fts5ExprNode *sqlite3Fts5ParseImplicitAnd( }else{ pPrev = pLeft; } - assert( pPrev->eType==FTS5_STRING - || pPrev->eType==FTS5_TERM - || pPrev->eType==FTS5_EOF + assert( pPrev->eType==FTS5_STRING + || pPrev->eType==FTS5_TERM + || pPrev->eType==FTS5_EOF ); if( pRight->eType==FTS5_EOF ){ + assert( pParse->apPhrase!=0 ); + assert( pParse->nPhrase>0 ); assert( pParse->apPhrase[pParse->nPhrase-1]==pRight->pNear->apPhrase[0] ); sqlite3Fts5ParseNodeFree(pRight); pRet = pLeft; @@ -207688,6 +249485,7 @@ static Fts5ExprNode *sqlite3Fts5ParseImplicitAnd( return pRet; } +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) static char *fts5ExprTermPrint(Fts5ExprTerm *pTerm){ sqlite3_int64 nByte = 0; Fts5ExprTerm *p; @@ -207695,16 +249493,17 @@ static char *fts5ExprTermPrint(Fts5ExprTerm *pTerm){ /* Determine the maximum amount of space required. */ for(p=pTerm; p; p=p->pSynonym){ - nByte += (int)strlen(pTerm->zTerm) * 2 + 3 + 2; + nByte += pTerm->nQueryTerm * 2 + 3 + 2; } zQuoted = sqlite3_malloc64(nByte); if( zQuoted ){ int i = 0; for(p=pTerm; p; p=p->pSynonym){ - char *zIn = p->zTerm; + char *zIn = p->pTerm; + char *zEnd = &zIn[p->nQueryTerm]; zQuoted[i++] = '"'; - while( *zIn ){ + while( zIneType==FTS5_STRING || pExpr->eType==FTS5_TERM ){ Fts5ExprNearset *pNear = pExpr->pNear; - int i; + int i; int iTerm; zRet = fts5PrintfAppend(zRet, "%s ", zNearsetCmd); @@ -207782,8 +249581,10 @@ static char *fts5ExprPrintTcl( zRet = fts5PrintfAppend(zRet, " {"); for(iTerm=0; zRet && iTermnTerm; iTerm++){ - char *zTerm = pPhrase->aTerm[iTerm].zTerm; - zRet = fts5PrintfAppend(zRet, "%s%s", iTerm==0?"":" ", zTerm); + Fts5ExprTerm *p = &pPhrase->aTerm[iTerm]; + zRet = fts5PrintfAppend(zRet, "%s%.*s", iTerm==0?"":" ", + p->nQueryTerm, p->pTerm + ); if( pPhrase->aTerm[iTerm].bPrefix ){ zRet = fts5PrintfAppend(zRet, "*"); } @@ -207793,15 +249594,17 @@ static char *fts5ExprPrintTcl( if( zRet==0 ) return 0; } + }else if( pExpr->eType==0 ){ + zRet = sqlite3_mprintf("{}"); }else{ char const *zOp = 0; int i; switch( pExpr->eType ){ case FTS5_AND: zOp = "AND"; break; case FTS5_NOT: zOp = "NOT"; break; - default: + default: assert( pExpr->eType==FTS5_OR ); - zOp = "OR"; + zOp = "OR"; break; } @@ -207827,12 +249630,21 @@ static char *fts5ExprPrint(Fts5Config *pConfig, Fts5ExprNode *pExpr){ }else if( pExpr->eType==FTS5_STRING || pExpr->eType==FTS5_TERM ){ Fts5ExprNearset *pNear = pExpr->pNear; - int i; + int i; int iTerm; if( pNear->pColset ){ - int iCol = pNear->pColset->aiCol[0]; - zRet = fts5PrintfAppend(zRet, "%s : ", pConfig->azCol[iCol]); + int ii; + Fts5Colset *pColset = pNear->pColset; + if( pColset->nCol>1 ) zRet = fts5PrintfAppend(zRet, "{"); + for(ii=0; iinCol; ii++){ + zRet = fts5PrintfAppend(zRet, "%s%s", + pConfig->azCol[pColset->aiCol[ii]], ii==pColset->nCol-1 ? "" : " " + ); + } + if( zRet ){ + zRet = fts5PrintfAppend(zRet, "%s : ", pColset->nCol>1 ? "}" : ""); + } if( zRet==0 ) return 0; } @@ -207872,9 +249684,9 @@ static char *fts5ExprPrint(Fts5Config *pConfig, Fts5ExprNode *pExpr){ switch( pExpr->eType ){ case FTS5_AND: zOp = " AND "; break; case FTS5_NOT: zOp = " NOT "; break; - default: + default: assert( pExpr->eType==FTS5_OR ); - zOp = " OR "; + zOp = " OR "; break; } @@ -207886,7 +249698,7 @@ static char *fts5ExprPrint(Fts5Config *pConfig, Fts5ExprNode *pExpr){ }else{ int e = pExpr->apChild[i]->eType; int b = (e!=FTS5_STRING && e!=FTS5_TERM && e!=FTS5_EOF); - zRet = fts5PrintfAppend(zRet, "%s%s%z%s", + zRet = fts5PrintfAppend(zRet, "%s%s%z%s", (i==0 ? "" : zOp), (b?"(":""), z, (b?")":"") ); @@ -207946,14 +249758,16 @@ static void fts5ExprFunction( azConfig[1] = "main"; azConfig[2] = "tbl"; for(i=3; iArgnCol, zExpr, &pExpr, &zErr); + rc = sqlite3Fts5ExprNew(pConfig, 0, pConfig->nCol, zExpr, &pExpr, &zErr); } if( rc==SQLITE_OK ){ char *zText; @@ -208002,7 +249816,7 @@ static void fts5ExprFunctionTcl( /* ** The implementation of an SQLite user-defined-function that accepts a -** single integer as an argument. If the integer is an alpha-numeric +** single integer as an argument. If the integer is an alpha-numeric ** unicode code point, 1 is returned. Otherwise 0. */ static void fts5ExprIsAlnum( @@ -208013,7 +249827,7 @@ static void fts5ExprIsAlnum( int iCode; u8 aArr[32]; if( nArg!=1 ){ - sqlite3_result_error(pCtx, + sqlite3_result_error(pCtx, "wrong number of arguments to function fts5_isalnum", -1 ); return; @@ -208032,7 +249846,7 @@ static void fts5ExprFold( sqlite3_value **apVal /* Function arguments */ ){ if( nArg!=1 && nArg!=2 ){ - sqlite3_result_error(pCtx, + sqlite3_result_error(pCtx, "wrong number of arguments to function fts5_fold", -1 ); }else{ @@ -208043,12 +249857,14 @@ static void fts5ExprFold( sqlite3_result_int(pCtx, sqlite3Fts5UnicodeFold(iCode, bRemoveDiacritics)); } } +#endif /* if SQLITE_TEST || SQLITE_FTS5_DEBUG */ /* ** This is called during initialization to register the fts5_expr() scalar ** UDF with the SQLite handle passed as the only argument. */ static int sqlite3Fts5ExprInit(Fts5Global *pGlobal, sqlite3 *db){ +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) struct Fts5ExprFunc { const char *z; void (*x)(sqlite3_context*,int,sqlite3_value**); @@ -208066,6 +249882,10 @@ static int sqlite3Fts5ExprInit(Fts5Global *pGlobal, sqlite3 *db){ struct Fts5ExprFunc *p = &aFunc[i]; rc = sqlite3_create_function(db, p->z, -1, SQLITE_UTF8, pCtx, p->x, 0, 0); } +#else + int rc = SQLITE_OK; + UNUSED_PARAM2(pGlobal,db); +#endif /* Avoid warnings indicating that sqlite3Fts5ParserTrace() and ** sqlite3Fts5ParserFallback() are unused */ @@ -208116,6 +249936,15 @@ struct Fts5PoslistPopulator { int bMiss; }; +/* +** Clear the position lists associated with all phrases in the expression +** passed as the first argument. Argument bLive is true if the expression +** might be pointing to a real entry, otherwise it has just been reset. +** +** At present this function is only used for detail=col and detail=none +** fts5 tables. This implies that all phrases must be at most 1 token +** in size, as phrase matches are not supported without detail=full. +*/ static Fts5PoslistPopulator *sqlite3Fts5ExprClearPoslists(Fts5Expr *pExpr, int bLive){ Fts5PoslistPopulator *pRet; pRet = sqlite3_malloc64(sizeof(Fts5PoslistPopulator)*pExpr->nPhrase); @@ -208125,8 +249954,8 @@ static Fts5PoslistPopulator *sqlite3Fts5ExprClearPoslists(Fts5Expr *pExpr, int b for(i=0; inPhrase; i++){ Fts5Buffer *pBuf = &pExpr->apExprPhrase[i]->poslist; Fts5ExprNode *pNode = pExpr->apExprPhrase[i]->pNode; - assert( pExpr->apExprPhrase[i]->nTerm==1 ); - if( bLive && + assert( pExpr->apExprPhrase[i]->nTerm<=1 ); + if( bLive && (pBuf->n==0 || pNode->iRowid!=pExpr->pRoot->iRowid || pNode->bEof) ){ pRet[i].bMiss = 1; @@ -208156,6 +249985,17 @@ static int fts5ExprColsetTest(Fts5Colset *pColset, int iCol){ return 0; } +/* +** pToken is a buffer nToken bytes in size that may or may not contain +** an embedded 0x00 byte. If it does, return the number of bytes in +** the buffer before the 0x00. If it does not, return nToken. +*/ +static int fts5QueryTerm(const char *pToken, int nToken){ + int ii; + for(ii=0; iipExpr; int i; + int nQuery = nToken; + i64 iRowid = pExpr->pRoot->iRowid; UNUSED_PARAM2(iUnused1, iUnused2); - if( nToken>FTS5_MAX_TOKEN_SIZE ) nToken = FTS5_MAX_TOKEN_SIZE; + if( nQuery>FTS5_MAX_TOKEN_SIZE ) nQuery = FTS5_MAX_TOKEN_SIZE; + if( pExpr->pConfig->bTokendata ){ + nQuery = fts5QueryTerm(pToken, nQuery); + } if( (tflags & FTS5_TOKEN_COLOCATED)==0 ) p->iOff++; for(i=0; inPhrase; i++){ - Fts5ExprTerm *pTerm; + Fts5ExprTerm *pT; if( p->aPopulator[i].bOk==0 ) continue; - for(pTerm=&pExpr->apExprPhrase[i]->aTerm[0]; pTerm; pTerm=pTerm->pSynonym){ - int nTerm = (int)strlen(pTerm->zTerm); - if( (nTerm==nToken || (nTermbPrefix)) - && memcmp(pTerm->zTerm, pToken, nTerm)==0 + for(pT=&pExpr->apExprPhrase[i]->aTerm[0]; pT; pT=pT->pSynonym){ + if( (pT->nQueryTerm==nQuery || (pT->nQueryTermbPrefix)) + && memcmp(pT->pTerm, pToken, pT->nQueryTerm)==0 ){ int rc = sqlite3Fts5PoslistWriterAppend( &pExpr->apExprPhrase[i]->poslist, &p->aPopulator[i].writer, p->iOff ); + if( rc==SQLITE_OK && (pExpr->pConfig->bTokendata || pT->bPrefix) ){ + int iCol = p->iOff>>32; + int iTokOff = p->iOff & 0x7FFFFFFF; + rc = sqlite3Fts5IndexIterWriteTokendata( + pT->pIter, pToken, nToken, iRowid, iCol, iTokOff + ); + } if( rc ) return rc; break; } @@ -208193,9 +250044,9 @@ static int fts5ExprPopulatePoslistsCb( static int sqlite3Fts5ExprPopulatePoslists( Fts5Config *pConfig, - Fts5Expr *pExpr, + Fts5Expr *pExpr, Fts5PoslistPopulator *aPopulator, - int iCol, + int iCol, const char *z, int n ){ int i; @@ -208207,7 +250058,7 @@ static int sqlite3Fts5ExprPopulatePoslists( for(i=0; inPhrase; i++){ Fts5ExprNode *pNode = pExpr->apExprPhrase[i]->pNode; Fts5Colset *pColset = pNode->pNear->pColset; - if( (pColset && 0==fts5ExprColsetTest(pColset, iCol)) + if( (pColset && 0==fts5ExprColsetTest(pColset, iCol)) || aPopulator[i].bMiss ){ aPopulator[i].bOk = 0; @@ -208216,7 +250067,7 @@ static int sqlite3Fts5ExprPopulatePoslists( } } - return sqlite3Fts5Tokenize(pConfig, + return sqlite3Fts5Tokenize(pConfig, FTS5_TOKENIZE_DOCUMENT, z, n, (void*)&sCtx, fts5ExprPopulatePoslistsCb ); } @@ -208236,6 +250087,7 @@ static int fts5ExprCheckPoslists(Fts5ExprNode *pNode, i64 iRowid){ pNode->iRowid = iRowid; pNode->bEof = 0; switch( pNode->eType ){ + case 0: case FTS5_TERM: case FTS5_STRING: return (pNode->pNear->apPhrase[0]->poslist.n>0); @@ -208281,12 +250133,12 @@ static void sqlite3Fts5ExprCheckPoslists(Fts5Expr *pExpr, i64 iRowid){ } /* -** This function is only called for detail=columns tables. +** This function is only called for detail=columns tables. */ static int sqlite3Fts5ExprPhraseCollist( - Fts5Expr *pExpr, - int iPhrase, - const u8 **ppCollist, + Fts5Expr *pExpr, + int iPhrase, + const u8 **ppCollist, int *pnCollist ){ Fts5ExprPhrase *pPhrase = pExpr->apExprPhrase[iPhrase]; @@ -208296,8 +250148,8 @@ static int sqlite3Fts5ExprPhraseCollist( assert( iPhrase>=0 && iPhrasenPhrase ); assert( pExpr->pConfig->eDetail==FTS5_DETAIL_COLUMNS ); - if( pNode->bEof==0 - && pNode->iRowid==pExpr->pRoot->iRowid + if( pNode->bEof==0 + && pNode->iRowid==pExpr->pRoot->iRowid && pPhrase->poslist.n>0 ){ Fts5ExprTerm *pTerm = &pPhrase->aTerm[0]; @@ -208318,6 +250170,82 @@ static int sqlite3Fts5ExprPhraseCollist( return rc; } +/* +** Does the work of the fts5_api.xQueryToken() API method. +*/ +static int sqlite3Fts5ExprQueryToken( + Fts5Expr *pExpr, + int iPhrase, + int iToken, + const char **ppOut, + int *pnOut +){ + Fts5ExprPhrase *pPhrase = 0; + + if( iPhrase<0 || iPhrase>=pExpr->nPhrase ){ + return SQLITE_RANGE; + } + pPhrase = pExpr->apExprPhrase[iPhrase]; + if( iToken<0 || iToken>=pPhrase->nTerm ){ + return SQLITE_RANGE; + } + + *ppOut = pPhrase->aTerm[iToken].pTerm; + *pnOut = pPhrase->aTerm[iToken].nFullTerm; + return SQLITE_OK; +} + +/* +** Does the work of the fts5_api.xInstToken() API method. +*/ +static int sqlite3Fts5ExprInstToken( + Fts5Expr *pExpr, + i64 iRowid, + int iPhrase, + int iCol, + int iOff, + int iToken, + const char **ppOut, + int *pnOut +){ + Fts5ExprPhrase *pPhrase = 0; + Fts5ExprTerm *pTerm = 0; + int rc = SQLITE_OK; + + if( iPhrase<0 || iPhrase>=pExpr->nPhrase ){ + return SQLITE_RANGE; + } + pPhrase = pExpr->apExprPhrase[iPhrase]; + if( iToken<0 || iToken>=pPhrase->nTerm ){ + return SQLITE_RANGE; + } + pTerm = &pPhrase->aTerm[iToken]; + if( pExpr->pConfig->bTokendata || pTerm->bPrefix ){ + rc = sqlite3Fts5IterToken( + pTerm->pIter, pTerm->pTerm, pTerm->nQueryTerm, + iRowid, iCol, iOff+iToken, ppOut, pnOut + ); + }else{ + *ppOut = pTerm->pTerm; + *pnOut = pTerm->nFullTerm; + } + return rc; +} + +/* +** Clear the token mappings for all Fts5IndexIter objects managed by +** the expression passed as the only argument. +*/ +static void sqlite3Fts5ExprClearTokens(Fts5Expr *pExpr){ + int ii; + for(ii=0; iinPhrase; ii++){ + Fts5ExprTerm *pT; + for(pT=&pExpr->apExprPhrase[ii]->aTerm[0]; pT; pT=pT->pSynonym){ + sqlite3Fts5IndexIterClearTokendata(pT->pIter); + } + } +} + /* ** 2014 August 11 ** @@ -208340,7 +250268,7 @@ typedef struct Fts5HashEntry Fts5HashEntry; /* ** This file contains the implementation of an in-memory hash table used -** to accumuluate "term -> doclist" content before it is flused to a level-0 +** to accumulate "term -> doclist" content before it is flushed to a level-0 ** segment. */ @@ -208355,11 +250283,16 @@ struct Fts5Hash { }; /* -** Each entry in the hash table is represented by an object of the -** following type. Each object, its key (a nul-terminated string) and -** its current data are stored in a single memory allocation. The -** key immediately follows the object in memory. The position list -** data immediately follows the key data in memory. +** Each entry in the hash table is represented by an object of the +** following type. Each object, its key, and its current data are stored +** in a single memory allocation. The key immediately follows the object +** in memory. The position list data immediately follows the key data +** in memory. +** +** The key is Fts5HashEntry.nKey bytes in size. It consists of a single +** byte identifying the index (either the main term index or a prefix-index), +** followed by the term data. For example: "0token". There is no +** nul-terminator - in this case nKey=6. ** ** The data that follows the key is in a similar, but not identical format ** to the doclist data stored in the database. It is: @@ -208379,7 +250312,7 @@ struct Fts5Hash { struct Fts5HashEntry { Fts5HashEntry *pHashNext; /* Next hash entry with same hash-key */ Fts5HashEntry *pScanNext; /* Next entry in sorted order */ - + int nAlloc; /* Total size of allocation */ int iSzPoslist; /* Offset of space for 4-byte poslist size */ int nData; /* Total bytes of data (incl. structure) */ @@ -208392,7 +250325,7 @@ struct Fts5HashEntry { }; /* -** Eqivalent to: +** Equivalent to: ** ** char *fts5EntryKey(Fts5HashEntry *pEntry){ return zKey; } */ @@ -208406,7 +250339,7 @@ static int sqlite3Fts5HashNew(Fts5Config *pConfig, Fts5Hash **ppNew, int *pnByte int rc = SQLITE_OK; Fts5Hash *pNew; - *ppNew = pNew = (Fts5Hash*)sqlite3_malloc(sizeof(Fts5Hash)); + *ppNew = pNew = (Fts5Hash*)sqlite3_malloc64(sizeof(Fts5Hash)); if( pNew==0 ){ rc = SQLITE_NOMEM; }else{ @@ -208494,8 +250427,7 @@ static int fts5HashResize(Fts5Hash *pHash){ unsigned int iHash; Fts5HashEntry *p = apOld[i]; apOld[i] = p->pHashNext; - iHash = fts5HashKey(nNew, (u8*)fts5EntryKey(p), - (int)strlen(fts5EntryKey(p))); + iHash = fts5HashKey(nNew, (u8*)fts5EntryKey(p), p->nKey); p->pHashNext = apNew[iHash]; apNew[iHash] = p; } @@ -208508,7 +250440,7 @@ static int fts5HashResize(Fts5Hash *pHash){ } static int fts5HashAddPoslistSize( - Fts5Hash *pHash, + Fts5Hash *pHash, Fts5HashEntry *p, Fts5HashEntry *p2 ){ @@ -208571,16 +250503,16 @@ static int sqlite3Fts5HashWrite( u8 *pPtr; int nIncr = 0; /* Amount to increment (*pHash->pnByte) by */ int bNew; /* If non-delete entry should be written */ - + bNew = (pHash->eDetail==FTS5_DETAIL_FULL); /* Attempt to locate an existing hash entry */ iHash = fts5HashKey2(pHash->nSlot, (u8)bByte, (const u8*)pToken, nToken); for(p=pHash->aSlot[iHash]; p; p=p->pHashNext){ char *zKey = fts5EntryKey(p); - if( zKey[0]==bByte - && p->nKey==nToken - && memcmp(&zKey[1], pToken, nToken)==0 + if( zKey[0]==bByte + && p->nKey==nToken+1 + && memcmp(&zKey[1], pToken, nToken)==0 ){ break; } @@ -208609,9 +250541,9 @@ static int sqlite3Fts5HashWrite( zKey[0] = bByte; memcpy(&zKey[1], pToken, nToken); assert( iHash==fts5HashKey(pHash->nSlot, (u8*)zKey, nToken+1) ); - p->nKey = nToken; + p->nKey = nToken+1; zKey[nToken+1] = '\0'; - p->nData = nToken+1 + 1 + sizeof(Fts5HashEntry); + p->nData = nToken+1 + sizeof(Fts5HashEntry); p->pHashNext = pHash->aSlot[iHash]; pHash->aSlot[iHash] = p; pHash->nEntry++; @@ -208626,11 +250558,10 @@ static int sqlite3Fts5HashWrite( p->iCol = (pHash->eDetail==FTS5_DETAIL_FULL ? 0 : -1); } - nIncr += p->nData; }else{ - /* Appending to an existing hash-entry. Check that there is enough - ** space to append the largest possible new entry. Worst case scenario + /* Appending to an existing hash-entry. Check that there is enough + ** space to append the largest possible new entry. Worst case scenario ** is: ** ** + 9 bytes for a new rowid, @@ -208659,8 +250590,9 @@ static int sqlite3Fts5HashWrite( /* If this is a new rowid, append the 4-byte size field for the previous ** entry, and the new rowid for this entry. */ if( iRowid!=p->iRowid ){ + u64 iDiff = (u64)iRowid - (u64)p->iRowid; fts5HashAddPoslistSize(pHash, p, 0); - p->nData += sqlite3Fts5PutVarint(&pPtr[p->nData], iRowid - p->iRowid); + p->nData += sqlite3Fts5PutVarint(&pPtr[p->nData], iDiff); p->iRowid = iRowid; bNew = 1; p->iSzPoslist = p->nData; @@ -208676,7 +250608,7 @@ static int sqlite3Fts5HashWrite( p->bContent = 1; }else{ /* Append a new column value, if necessary */ - assert( iCol>=p->iCol ); + assert_nc( iCol>=p->iCol ); if( iCol!=p->iCol ){ if( pHash->eDetail==FTS5_DETAIL_FULL ){ pPtr[p->nData++] = 0x01; @@ -208728,12 +250660,17 @@ static Fts5HashEntry *fts5HashEntryMerge( *ppOut = p1; p1 = 0; }else{ - int i = 0; char *zKey1 = fts5EntryKey(p1); char *zKey2 = fts5EntryKey(p2); - while( zKey1[i]==zKey2[i] ) i++; + int nMin = MIN(p1->nKey, p2->nKey); - if( ((u8)zKey1[i])>((u8)zKey2[i]) ){ + int cmp = memcmp(zKey1, zKey2, nMin); + if( cmp==0 ){ + cmp = p1->nKey - p2->nKey; + } + assert( cmp!=0 ); + + if( cmp>0 ){ /* p2 is smaller */ *ppOut = p2; ppOut = &p2->pScanNext; @@ -208752,13 +250689,11 @@ static Fts5HashEntry *fts5HashEntryMerge( } /* -** Extract all tokens from hash table iHash and link them into a list -** in sorted order. The hash table is cleared before returning. It is -** the responsibility of the caller to free the elements of the returned -** list. +** Link all tokens from hash table iHash into a list in sorted order. The +** tokens are not removed from the hash table. */ static int fts5HashEntrySort( - Fts5Hash *pHash, + Fts5Hash *pHash, const char *pTerm, int nTerm, /* Query prefix, if any */ Fts5HashEntry **ppSorted ){ @@ -208776,8 +250711,8 @@ static int fts5HashEntrySort( for(iSlot=0; iSlotnSlot; iSlot++){ Fts5HashEntry *pIter; for(pIter=pHash->aSlot[iSlot]; pIter; pIter=pIter->pHashNext){ - if( pTerm==0 - || (pIter->nKey+1>=nTerm && 0==memcmp(fts5EntryKey(pIter), pTerm, nTerm)) + if( pTerm==0 + || (pIter->nKey>=nTerm && 0==memcmp(fts5EntryKey(pIter), pTerm, nTerm)) ){ Fts5HashEntry *pEntry = pIter; pEntry->pScanNext = 0; @@ -208795,7 +250730,6 @@ static int fts5HashEntrySort( pList = fts5HashEntryMerge(pList, ap[i]); } - pHash->nEntry = 0; sqlite3_free(ap); *ppSorted = pList; return SQLITE_OK; @@ -208817,12 +250751,11 @@ static int sqlite3Fts5HashQuery( for(p=pHash->aSlot[iHash]; p; p=p->pHashNext){ zKey = fts5EntryKey(p); - assert( p->nKey+1==(int)strlen(zKey) ); - if( nTerm==p->nKey+1 && memcmp(zKey, pTerm, nTerm)==0 ) break; + if( nTerm==p->nKey && memcmp(zKey, pTerm, nTerm)==0 ) break; } if( p ){ - int nHashPre = sizeof(Fts5HashEntry) + nTerm + 1; + int nHashPre = sizeof(Fts5HashEntry) + nTerm; int nList = p->nData - nHashPre; u8 *pRet = (u8*)(*ppOut = sqlite3_malloc64(nPre + nList + 10)); if( pRet ){ @@ -208849,6 +250782,28 @@ static int sqlite3Fts5HashScanInit( return fts5HashEntrySort(p, pTerm, nTerm, &p->pScan); } +#ifdef SQLITE_DEBUG +static int fts5HashCount(Fts5Hash *pHash){ + int nEntry = 0; + int ii; + for(ii=0; iinSlot; ii++){ + Fts5HashEntry *p = 0; + for(p=pHash->aSlot[ii]; p; p=p->pHashNext){ + nEntry++; + } + } + return nEntry; +} +#endif + +/* +** Return true if the hash table is empty, false otherwise. +*/ +static int sqlite3Fts5HashIsEmpty(Fts5Hash *pHash){ + assert( pHash->nEntry==fts5HashCount(pHash) ); + return pHash->nEntry==0; +} + static void sqlite3Fts5HashScanNext(Fts5Hash *p){ assert( !sqlite3Fts5HashScanEof(p) ); p->pScan = p->pScan->pScanNext; @@ -208861,19 +250816,22 @@ static int sqlite3Fts5HashScanEof(Fts5Hash *p){ static void sqlite3Fts5HashScanEntry( Fts5Hash *pHash, const char **pzTerm, /* OUT: term (nul-terminated) */ + int *pnTerm, /* OUT: Size of term in bytes */ const u8 **ppDoclist, /* OUT: pointer to doclist */ int *pnDoclist /* OUT: size of doclist in bytes */ ){ Fts5HashEntry *p; if( (p = pHash->pScan) ){ char *zKey = fts5EntryKey(p); - int nTerm = (int)strlen(zKey); + int nTerm = p->nKey; fts5HashAddPoslistSize(pHash, p, 0); *pzTerm = zKey; - *ppDoclist = (const u8*)&zKey[nTerm+1]; - *pnDoclist = p->nData - (sizeof(Fts5HashEntry) + nTerm + 1); + *pnTerm = nTerm; + *ppDoclist = (const u8*)&zKey[nTerm]; + *pnDoclist = p->nData - (sizeof(Fts5HashEntry) + nTerm); }else{ *pzTerm = 0; + *pnTerm = 0; *ppDoclist = 0; *pnDoclist = 0; } @@ -208891,7 +250849,7 @@ static void sqlite3Fts5HashScanEntry( ** ****************************************************************************** ** -** Low level access to the FTS index stored in the database file. The +** Low level access to the FTS index stored in the database file. The ** routines in this file file implement all read and write access to the ** %_data table. Other parts of the system access this functionality via ** the interface defined in fts5Int.h. @@ -208907,10 +250865,10 @@ static void sqlite3Fts5HashScanEntry( ** As well as the main term index, there may be up to 31 prefix indexes. ** The format is similar to FTS3/4, except that: ** -** * all segment b-tree leaf data is stored in fixed size page records -** (e.g. 1000 bytes). A single doclist may span multiple pages. Care is -** taken to ensure it is possible to iterate in either direction through -** the entries in a doclist, or to seek to a specific entry within a +** * all segment b-tree leaf data is stored in fixed size page records +** (e.g. 1000 bytes). A single doclist may span multiple pages. Care is +** taken to ensure it is possible to iterate in either direction through +** the entries in a doclist, or to seek to a specific entry within a ** doclist, without loading it into memory. ** ** * large doclists that span many pages have associated "doclist index" @@ -208935,6 +250893,26 @@ static void sqlite3Fts5HashScanEntry( # error "FTS5_MAX_PREFIX_INDEXES is too large" #endif +#define FTS5_MAX_LEVEL 64 + +/* +** There are two versions of the format used for the structure record: +** +** 1. the legacy format, that may be read by all fts5 versions, and +** +** 2. the V2 format, which is used by contentless_delete=1 databases. +** +** Both begin with a 4-byte "configuration cookie" value. Then, a legacy +** format structure record contains a varint - the number of levels in +** the structure. Whereas a V2 structure record contains the constant +** 4 bytes [0xff 0x00 0x00 0x01]. This is unambiguous as the value of a +** varint has to be at least 16256 to begin with "0xFF". And the default +** maximum number of levels is 64. +** +** See below for more on structure record formats. +*/ +#define FTS5_STRUCTURE_V2 "\xFF\x00\x00\x01" + /* ** Details: ** @@ -208942,21 +250920,21 @@ static void sqlite3Fts5HashScanEntry( ** ** CREATE TABLE %_data(id INTEGER PRIMARY KEY, block BLOB); ** -** , contains the following 5 types of records. See the comments surrounding -** the FTS5_*_ROWID macros below for a description of how %_data rowids are +** , contains the following 6 types of records. See the comments surrounding +** the FTS5_*_ROWID macros below for a description of how %_data rowids are ** assigned to each fo them. ** ** 1. Structure Records: ** ** The set of segments that make up an index - the index structure - are ** recorded in a single record within the %_data table. The record consists -** of a single 32-bit configuration cookie value followed by a list of -** SQLite varints. If the FTS table features more than one index (because -** there are one or more prefix indexes), it is guaranteed that all share -** the same cookie value. +** of a single 32-bit configuration cookie value followed by a list of +** SQLite varints. ** -** Immediately following the configuration cookie, the record begins with -** three varints: +** If the structure record is a V2 record, the configuration cookie is +** followed by the following 4 bytes: [0xFF 0x00 0x00 0x01]. +** +** Next, the record continues with three varints: ** ** + number of levels, ** + total number of segments on all levels, @@ -208971,6 +250949,12 @@ static void sqlite3Fts5HashScanEntry( ** + first leaf page number (often 1, always greater than 0) ** + final leaf page number ** +** Then, for V2 structures only: +** +** + lower origin counter value, +** + upper origin counter value, +** + the number of tombstone hash pages. +** ** 2. The Averages Record: ** ** A single record within the %_data table. The data is a list of varints. @@ -208982,7 +250966,7 @@ static void sqlite3Fts5HashScanEntry( ** ** TERM/DOCLIST FORMAT: ** -** Most of each segment leaf is taken up by term/doclist data. The +** Most of each segment leaf is taken up by term/doclist data. The ** general format of term/doclist, starting with the first term ** on the leaf page, is: ** @@ -209025,7 +251009,7 @@ static void sqlite3Fts5HashScanEntry( ** ** PAGE FORMAT ** -** Each leaf page begins with a 4-byte header containing 2 16-bit +** Each leaf page begins with a 4-byte header containing 2 16-bit ** unsigned integer fields in big-endian format. They are: ** ** * The byte offset of the first rowid on the page, if it exists @@ -209060,7 +251044,7 @@ static void sqlite3Fts5HashScanEntry( ** 5. Segment doclist indexes: ** ** Doclist indexes are themselves b-trees, however they usually consist of -** a single leaf record only. The format of each doclist index leaf page +** a single leaf record only. The format of each doclist index leaf page ** is: ** ** * Flags byte. Bits are: @@ -209070,8 +251054,8 @@ static void sqlite3Fts5HashScanEntry( ** ** * First rowid on page indicated by previous field. As a varint. ** -** * A list of varints, one for each subsequent termless page. A -** positive delta if the termless page contains at least one rowid, +** * A list of varints, one for each subsequent termless page. A +** positive delta if the termless page contains at least one rowid, ** or an 0x00 byte otherwise. ** ** Internal doclist index nodes are: @@ -209084,8 +251068,40 @@ static void sqlite3Fts5HashScanEntry( ** * Copy of first rowid on page indicated by previous field. As a varint. ** ** * A list of delta-encoded varints - the first rowid on each subsequent -** child page. +** child page. +** +** 6. Tombstone Hash Page ** +** These records are only ever present in contentless_delete=1 tables. +** There are zero or more of these associated with each segment. They +** are used to store the tombstone rowids for rows contained in the +** associated segments. +** +** The set of nHashPg tombstone hash pages associated with a single +** segment together form a single hash table containing tombstone rowids. +** To find the page of the hash on which a key might be stored: +** +** iPg = (rowid % nHashPg) +** +** Then, within page iPg, which has nSlot slots: +** +** iSlot = (rowid / nHashPg) % nSlot +** +** Each tombstone hash page begins with an 8 byte header: +** +** 1-byte: Key-size (the size in bytes of each slot). Either 4 or 8. +** 1-byte: rowid-0-tombstone flag. This flag is only valid on the +** first tombstone hash page for each segment (iPg=0). If set, +** the hash table contains rowid 0. If clear, it does not. +** Rowid 0 is handled specially. +** 2-bytes: unused. +** 4-bytes: Big-endian integer containing number of entries on page. +** +** Following this are nSlot 4 or 8 byte slots (depending on the key-size +** in the first byte of the page header). The number of slots may be +** determined based on the size of the page record and the key-size: +** +** nSlot = (nByte - 8) / key-size */ /* @@ -209101,7 +251117,7 @@ static void sqlite3Fts5HashScanEntry( ** ** Each segment has a unique non-zero 16-bit id. ** -** The rowid for each segment leaf is found by passing the segment id and +** The rowid for each segment leaf is found by passing the segment id and ** the leaf page number to the FTS5_SEGMENT_ROWID macro. Leaves are numbered ** sequentially starting from 1. */ @@ -209119,11 +251135,7 @@ static void sqlite3Fts5HashScanEntry( #define FTS5_SEGMENT_ROWID(segid, pgno) fts5_dri(segid, 0, 0, pgno) #define FTS5_DLIDX_ROWID(segid, height, pgno) fts5_dri(segid, 1, height, pgno) - -/* -** Maximum segments permitted in a single index -*/ -#define FTS5_MAX_SEGMENT 2000 +#define FTS5_TOMBSTONE_ROWID(segid,ipg) fts5_dri(segid+(1<<16), 0, 0, ipg) #ifdef SQLITE_DEBUG static int sqlite3Fts5Corrupt() { return SQLITE_CORRUPT_VTAB; } @@ -209150,6 +251162,9 @@ typedef struct Fts5SegWriter Fts5SegWriter; typedef struct Fts5Structure Fts5Structure; typedef struct Fts5StructureLevel Fts5StructureLevel; typedef struct Fts5StructureSegment Fts5StructureSegment; +typedef struct Fts5TokenDataIter Fts5TokenDataIter; +typedef struct Fts5TokenDataMap Fts5TokenDataMap; +typedef struct Fts5TombstoneArray Fts5TombstoneArray; struct Fts5Data { u8 *p; /* Pointer to buffer containing record */ @@ -209159,6 +251174,12 @@ struct Fts5Data { /* ** One object per %_data table. +** +** nContentlessDelete: +** The number of contentless delete operations since the most recent +** call to fts5IndexFlush() or fts5IndexDiscardData(). This is tracked +** so that extra auto-merge work can be done by fts5IndexFlush() to +** account for the delete operations. */ struct Fts5Index { Fts5Config *pConfig; /* Virtual table configuration */ @@ -209173,19 +251194,25 @@ struct Fts5Index { int nPendingData; /* Current bytes of pending data */ i64 iWriteRowid; /* Rowid for current doc being written */ int bDelete; /* Current write is a delete */ + int nContentlessDelete; /* Number of contentless delete ops */ + int nPendingRow; /* Number of INSERT in hash table */ /* Error state. */ int rc; /* Current error code */ + int flushRc; /* State used by the fts5DataXXX() functions. */ sqlite3_blob *pReader; /* RO incr-blob open on %_data table */ sqlite3_stmt *pWriter; /* "INSERT ... %_data VALUES(?,?)" */ sqlite3_stmt *pDeleter; /* "DELETE FROM %_data ... id>=? AND id<=?" */ sqlite3_stmt *pIdxWriter; /* "INSERT ... %_idx VALUES(?,?,?,?)" */ - sqlite3_stmt *pIdxDeleter; /* "DELETE FROM %_idx WHERE segid=? */ + sqlite3_stmt *pIdxDeleter; /* "DELETE FROM %_idx WHERE segid=?" */ sqlite3_stmt *pIdxSelect; + sqlite3_stmt *pIdxNextSelect; int nRead; /* Total number of blocks read */ + sqlite3_stmt *pDeleteFromIdx; + sqlite3_stmt *pDataVersion; i64 iStructVersion; /* data_version when pStruct read */ Fts5Structure *pStruct; /* Current db structure (or NULL) */ @@ -209203,13 +251230,25 @@ struct Fts5DoclistIter { /* ** The contents of the "structure" record for each index are represented -** using an Fts5Structure record in memory. Which uses instances of the +** using an Fts5Structure record in memory. Which uses instances of the ** other Fts5StructureXXX types as components. +** +** nOriginCntr: +** This value is set to non-zero for structure records created for +** contentlessdelete=1 tables only. In that case it represents the +** origin value to apply to the next top-level segment created. */ struct Fts5StructureSegment { int iSegid; /* Segment id */ int pgnoFirst; /* First leaf page number in segment */ int pgnoLast; /* Last leaf page number in segment */ + + /* contentlessdelete=1 tables only: */ + u64 iOrigin1; + u64 iOrigin2; + int nPgTombstone; /* Number of tombstone hash table pages */ + u64 nEntryTombstone; /* Number of tombstone entries that "count" */ + u64 nEntry; /* Number of rows in this segment */ }; struct Fts5StructureLevel { int nMerge; /* Number of segments in incr-merge */ @@ -209219,11 +251258,16 @@ struct Fts5StructureLevel { struct Fts5Structure { int nRef; /* Object reference count */ u64 nWriteCounter; /* Total leaves written to level 0 */ + u64 nOriginCntr; /* Origin value for next top-level segment */ int nSegment; /* Total segments in this structure */ int nLevel; /* Number of levels in this index */ - Fts5StructureLevel aLevel[1]; /* Array of nLevel level objects */ + Fts5StructureLevel aLevel[FLEXARRAY]; /* Array of nLevel level objects */ }; +/* Size (in bytes) of an Fts5Structure object holding up to N levels */ +#define SZ_FTS5STRUCTURE(N) \ + (offsetof(Fts5Structure,aLevel) + (N)*sizeof(Fts5StructureLevel)) + /* ** An object of type Fts5SegWriter is used to write to segments. */ @@ -209276,11 +251320,8 @@ struct Fts5CResult { ** Current leaf page number within segment. ** ** iLeafOffset: -** Byte offset within the current leaf that is the first byte of the +** Byte offset within the current leaf that is the first byte of the ** position list data (one byte passed the position-list size field). -** rowid field of the current entry. Usually this is the size field of the -** position list data. The exception is if the rowid for the current entry -** is the last thing on the leaf page. ** ** pLeaf: ** Buffer containing current leaf page data. Set to NULL at EOF. @@ -209293,7 +251334,7 @@ struct Fts5CResult { ** Mask of FTS5_SEGITER_XXX values. Interpreted as follows: ** ** FTS5_SEGITER_ONETERM: -** If set, set the iterator to point to EOF after the current doclist +** If set, set the iterator to point to EOF after the current doclist ** has been exhausted. Do not proceed to the next term in the segment. ** ** FTS5_SEGITER_REVERSE: @@ -209310,6 +251351,13 @@ struct Fts5CResult { ** ** iTermIdx: ** Index of current term on iTermLeafPgno. +** +** apTombstone/nTombstone: +** These are used for contentless_delete=1 tables only. When the cursor +** is first allocated, the apTombstone[] array is allocated so that it +** is large enough for all tombstones hash pages associated with the +** segment. The pages themselves are loaded lazily from the database as +** they are required. */ struct Fts5SegIter { Fts5StructureSegment *pSeg; /* Segment to iterate through */ @@ -209317,12 +251365,13 @@ struct Fts5SegIter { int iLeafPgno; /* Current leaf page number */ Fts5Data *pLeaf; /* Current leaf data */ Fts5Data *pNextLeaf; /* Leaf page (iLeafPgno+1) */ - int iLeafOffset; /* Byte offset within current leaf */ + i64 iLeafOffset; /* Byte offset within current leaf */ + Fts5TombstoneArray *pTombArray; /* Array of tombstone pages */ /* Next method */ void (*xNext)(Fts5Index*, Fts5SegIter*, int*); - /* The page and offset from which the current term was read. The offset + /* The page and offset from which the current term was read. The offset ** is the offset of the first rowid in the current doclist. */ int iTermLeafPgno; int iTermLeafOffset; @@ -209344,8 +251393,51 @@ struct Fts5SegIter { u8 bDel; /* True if the delete flag is set */ }; +static int fts5IndexCorruptRowid(Fts5Index *pIdx, i64 iRowid){ + pIdx->rc = FTS5_CORRUPT; + sqlite3Fts5ConfigErrmsg(pIdx->pConfig, + "fts5: corruption found reading blob %lld from table \"%s\"", + iRowid, pIdx->pConfig->zName + ); + return SQLITE_CORRUPT_VTAB; +} +#define FTS5_CORRUPT_ROWID(pIdx, iRowid) fts5IndexCorruptRowid(pIdx, iRowid) + +static int fts5IndexCorruptIter(Fts5Index *pIdx, Fts5SegIter *pIter){ + pIdx->rc = FTS5_CORRUPT; + sqlite3Fts5ConfigErrmsg(pIdx->pConfig, + "fts5: corruption on page %d, segment %d, table \"%s\"", + pIter->iLeafPgno, pIter->pSeg->iSegid, pIdx->pConfig->zName + ); + return SQLITE_CORRUPT_VTAB; +} +#define FTS5_CORRUPT_ITER(pIdx, pIter) fts5IndexCorruptIter(pIdx, pIter) + +static int fts5IndexCorruptIdx(Fts5Index *pIdx){ + pIdx->rc = FTS5_CORRUPT; + sqlite3Fts5ConfigErrmsg(pIdx->pConfig, + "fts5: corruption in table \"%s\"", pIdx->pConfig->zName + ); + return SQLITE_CORRUPT_VTAB; +} +#define FTS5_CORRUPT_IDX(pIdx) fts5IndexCorruptIdx(pIdx) + + +/* +** Array of tombstone pages. Reference counted. +*/ +struct Fts5TombstoneArray { + int nRef; /* Number of pointers to this object */ + int nTombstone; + Fts5Data *apTombstone[FLEXARRAY]; /* Array of tombstone pages */ +}; + +/* Size (in bytes) of an Fts5TombstoneArray holding up to N tombstones */ +#define SZ_FTS5TOMBSTONEARRAY(N) \ + (offsetof(Fts5TombstoneArray,apTombstone)+(N)*sizeof(Fts5Data*)) + /* -** Argument is a pointer to an Fts5Data structure that contains a +** Argument is a pointer to an Fts5Data structure that contains a ** leaf page. */ #define ASSERT_SZLEAF_OK(x) assert( \ @@ -209355,7 +251447,7 @@ struct Fts5SegIter { #define FTS5_SEGITER_ONETERM 0x01 #define FTS5_SEGITER_REVERSE 0x02 -/* +/* ** Argument is a pointer to an Fts5Data structure that contains a leaf ** page. This macro evaluates to true if the leaf contains no terms, or ** false if it contains at least one term. @@ -209377,20 +251469,27 @@ struct Fts5SegIter { ** on empty segments. ** ** The results of comparing segments aSeg[N] and aSeg[N+1], where N is an -** even number, is stored in aFirst[(nSeg+N)/2]. The "result" of the +** even number, is stored in aFirst[(nSeg+N)/2]. The "result" of the ** comparison in this context is the index of the iterator that currently ** points to the smaller term/rowid combination. Iterators at EOF are ** considered to be greater than all other iterators. ** ** aFirst[1] contains the index in aSeg[] of the iterator that points to -** the smallest key overall. aFirst[0] is unused. +** the smallest key overall. aFirst[0] is unused. ** ** poslist: ** Used by sqlite3Fts5IterPoslist() when the poslist needs to be buffered. ** There is no way to tell if this is populated or not. +** +** pColset: +** If not NULL, points to an object containing a set of column indices. +** Only matches that occur in one of these columns will be returned. +** The Fts5Iter does not own the Fts5Colset object, and so it is not +** freed when the iterator is closed - it is owned by the upper layer. */ struct Fts5Iter { Fts5IndexIter base; /* Base class containing output vars */ + Fts5TokenDataIter *pTokenDataIter; Fts5Index *pIndex; /* Index that owns this iterator */ Fts5Buffer poslist; /* Buffer containing current poslist */ @@ -209405,9 +251504,11 @@ struct Fts5Iter { i64 iSwitchRowid; /* Firstest rowid of other than aFirst[1] */ Fts5CResult *aFirst; /* Current merge state (see above) */ - Fts5SegIter aSeg[1]; /* Array of segment iterators */ + Fts5SegIter aSeg[FLEXARRAY]; /* Array of segment iterators */ }; +/* Size (in bytes) of an Fts5Iter object holding up to N segment iterators */ +#define SZ_FTS5ITER(N) (offsetof(Fts5Iter,aSeg)+(N)*sizeof(Fts5SegIter)) /* ** An instance of the following type is used to iterate through the contents @@ -209435,9 +251536,13 @@ struct Fts5DlidxLvl { struct Fts5DlidxIter { int nLvl; int iSegid; - Fts5DlidxLvl aLvl[1]; + Fts5DlidxLvl aLvl[FLEXARRAY]; }; +/* Size (in bytes) of an Fts5DlidxIter object with up to N levels */ +#define SZ_FTS5DLIDXITER(N) \ + (offsetof(Fts5DlidxIter,aLvl)+(N)*sizeof(Fts5DlidxLvl)) + static void fts5PutU16(u8 *aOut, u16 iVal){ aOut[0] = (iVal>>8); aOut[1] = (iVal&0xFF); @@ -209445,7 +251550,61 @@ static void fts5PutU16(u8 *aOut, u16 iVal){ static u16 fts5GetU16(const u8 *aIn){ return ((u16)aIn[0] << 8) + aIn[1]; -} +} + +/* +** The only argument points to a buffer at least 8 bytes in size. This +** function interprets the first 8 bytes of the buffer as a 64-bit big-endian +** unsigned integer and returns the result. +*/ +static u64 fts5GetU64(u8 *a){ + return ((u64)a[0] << 56) + + ((u64)a[1] << 48) + + ((u64)a[2] << 40) + + ((u64)a[3] << 32) + + ((u64)a[4] << 24) + + ((u64)a[5] << 16) + + ((u64)a[6] << 8) + + ((u64)a[7] << 0); +} + +/* +** The only argument points to a buffer at least 4 bytes in size. This +** function interprets the first 4 bytes of the buffer as a 32-bit big-endian +** unsigned integer and returns the result. +*/ +static u32 fts5GetU32(const u8 *a){ + return ((u32)a[0] << 24) + + ((u32)a[1] << 16) + + ((u32)a[2] << 8) + + ((u32)a[3] << 0); +} + +/* +** Write iVal, formated as a 64-bit big-endian unsigned integer, to the +** buffer indicated by the first argument. +*/ +static void fts5PutU64(u8 *a, u64 iVal){ + a[0] = ((iVal >> 56) & 0xFF); + a[1] = ((iVal >> 48) & 0xFF); + a[2] = ((iVal >> 40) & 0xFF); + a[3] = ((iVal >> 32) & 0xFF); + a[4] = ((iVal >> 24) & 0xFF); + a[5] = ((iVal >> 16) & 0xFF); + a[6] = ((iVal >> 8) & 0xFF); + a[7] = ((iVal >> 0) & 0xFF); +} + +/* +** Write iVal, formated as a 32-bit big-endian unsigned integer, to the +** buffer indicated by the first argument. +*/ +static void fts5PutU32(u8 *a, u32 iVal){ + a[0] = ((iVal >> 24) & 0xFF); + a[1] = ((iVal >> 16) & 0xFF); + a[2] = ((iVal >> 8) & 0xFF); + a[3] = ((iVal >> 0) & 0xFF); +} /* ** Allocate and return a buffer at least nByte bytes in size. @@ -209486,8 +251645,11 @@ static int fts5BufferCompareBlob( ** res = *pLeft - *pRight */ static int fts5BufferCompare(Fts5Buffer *pLeft, Fts5Buffer *pRight){ - int nCmp = MIN(pLeft->n, pRight->n); - int res = fts5Memcmp(pLeft->p, pRight->p, nCmp); + int nCmp, res; + nCmp = MIN(pLeft->n, pRight->n); + assert( nCmp<=0 || pLeft->p!=0 ); + assert( nCmp<=0 || pRight->p!=0 ); + res = fts5Memcmp(pLeft->p, pRight->p, nCmp); return (res==0 ? (pLeft->n - pRight->n) : res); } @@ -209500,18 +251662,20 @@ static int fts5LeafFirstTermOff(Fts5Data *pLeaf){ /* ** Close the read-only blob handle, if it is open. */ -static void fts5CloseReader(Fts5Index *p){ +static void fts5IndexCloseReader(Fts5Index *p){ if( p->pReader ){ + int rc; sqlite3_blob *pReader = p->pReader; p->pReader = 0; - sqlite3_blob_close(pReader); + rc = sqlite3_blob_close(pReader); + if( p->rc==SQLITE_OK ) p->rc = rc; } } /* ** Retrieve a record from the %_data table. ** -** If an error occurs, NULL is returned and an error left in the +** If an error occurs, NULL is returned and an error left in the ** Fts5Index object. */ static Fts5Data *fts5DataRead(Fts5Index *p, i64 iRowid){ @@ -209529,16 +251693,16 @@ static Fts5Data *fts5DataRead(Fts5Index *p, i64 iRowid){ assert( p->pReader==0 ); p->pReader = pBlob; if( rc!=SQLITE_OK ){ - fts5CloseReader(p); + fts5IndexCloseReader(p); } if( rc==SQLITE_ABORT ) rc = SQLITE_OK; } - /* If the blob handle is not open at this point, open it and seek + /* If the blob handle is not open at this point, open it and seek ** to the requested entry. */ if( p->pReader==0 && rc==SQLITE_OK ){ Fts5Config *pConfig = p->pConfig; - rc = sqlite3_blob_open(pConfig->db, + rc = sqlite3_blob_open(pConfig->db, pConfig->zDb, p->zDataTbl, "block", iRowid, 0, &p->pReader ); } @@ -209546,18 +251710,20 @@ static Fts5Data *fts5DataRead(Fts5Index *p, i64 iRowid){ /* If either of the sqlite3_blob_open() or sqlite3_blob_reopen() calls ** above returned SQLITE_ERROR, return SQLITE_CORRUPT_VTAB instead. ** All the reasons those functions might return SQLITE_ERROR - missing - ** table, missing row, non-blob/text in block column - indicate + ** table, missing row, non-blob/text in block column - indicate ** backing store corruption. */ - if( rc==SQLITE_ERROR ) rc = FTS5_CORRUPT; + if( rc==SQLITE_ERROR ) rc = FTS5_CORRUPT_ROWID(p, iRowid); if( rc==SQLITE_OK ){ u8 *aOut = 0; /* Read blob data into this buffer */ - int nByte = sqlite3_blob_bytes(p->pReader); - sqlite3_int64 nAlloc = sizeof(Fts5Data) + nByte + FTS5_DATA_PADDING; + i64 nByte = sqlite3_blob_bytes(p->pReader); + i64 szData = (sizeof(Fts5Data) + 7) & ~7; + i64 nAlloc = szData + nByte + FTS5_DATA_PADDING; pRet = (Fts5Data*)sqlite3_malloc64(nAlloc); if( pRet ){ pRet->nn = nByte; - aOut = pRet->p = (u8*)&pRet[1]; + pRet->szLeaf = 0; + aOut = pRet->p = (u8*)pRet + szData; }else{ rc = SQLITE_NOMEM; } @@ -209569,9 +251735,8 @@ static Fts5Data *fts5DataRead(Fts5Index *p, i64 iRowid){ sqlite3_free(pRet); pRet = 0; }else{ - /* TODO1: Fix this */ pRet->p[nByte] = 0x00; - pRet->szLeaf = fts5GetU16(&pRet->p[2]); + pRet->p[nByte+1] = 0x00; } } p->rc = rc; @@ -209579,9 +251744,11 @@ static Fts5Data *fts5DataRead(Fts5Index *p, i64 iRowid){ } assert( (pRet==0)==(p->rc!=SQLITE_OK) ); + assert( pRet==0 || EIGHT_BYTE_ALIGNMENT( pRet->p ) ); return pRet; } + /* ** Release a reference to data record returned by an earlier call to ** fts5DataRead(). @@ -209590,11 +251757,19 @@ static void fts5DataRelease(Fts5Data *pData){ sqlite3_free(pData); } +/* +** Read a leaf-page record. This is similar to fts5DataRead(), except that +** it fills in the Fts5Data.szLeaf value before returning. +*/ static Fts5Data *fts5LeafRead(Fts5Index *p, i64 iRowid){ Fts5Data *pRet = fts5DataRead(p, iRowid); if( pRet ){ - if( pRet->szLeaf>pRet->nn ){ - p->rc = FTS5_CORRUPT; + assert( pRet->szLeaf==0 ); + if( pRet->nn>=4 ){ + pRet->szLeaf = fts5GetU16(&pRet->p[2]); + } + if( pRet->szLeaf<4 || pRet->szLeaf>pRet->nn ){ + FTS5_CORRUPT_ROWID(p, iRowid); fts5DataRelease(pRet); pRet = 0; } @@ -209609,9 +251784,13 @@ static int fts5IndexPrepareStmt( ){ if( p->rc==SQLITE_OK ){ if( zSql ){ - p->rc = sqlite3_prepare_v3(p->pConfig->db, zSql, -1, + int rc = sqlite3_prepare_v3(p->pConfig->db, zSql, -1, SQLITE_PREPARE_PERSISTENT|SQLITE_PREPARE_NO_VTAB, ppStmt, 0); + /* If this prepare() call fails with SQLITE_ERROR, then one of the + ** %_idx or %_data tables has been removed or modified. Call this + ** corruption. */ + p->rc = (rc==SQLITE_ERROR ? SQLITE_CORRUPT : rc); }else{ p->rc = SQLITE_NOMEM; } @@ -209630,7 +251809,7 @@ static void fts5DataWrite(Fts5Index *p, i64 iRowid, const u8 *pData, int nData){ if( p->pWriter==0 ){ Fts5Config *pConfig = p->pConfig; fts5IndexPrepareStmt(p, &p->pWriter, sqlite3_mprintf( - "REPLACE INTO '%q'.'%q_data'(id, block) VALUES(?,?)", + "REPLACE INTO '%q'.'%q_data'(id, block) VALUES(?,?)", pConfig->zDb, pConfig->zName )); if( p->rc ) return; @@ -209654,7 +251833,7 @@ static void fts5DataDelete(Fts5Index *p, i64 iFirst, i64 iLast){ if( p->pDeleter==0 ){ Fts5Config *pConfig = p->pConfig; char *zSql = sqlite3_mprintf( - "DELETE FROM '%q'.'%q_data' WHERE id>=? AND id<=?", + "DELETE FROM '%q'.'%q_data' WHERE id>=? AND id<=?", pConfig->zDb, pConfig->zName ); if( fts5IndexPrepareStmt(p, &p->pDeleter, zSql) ) return; @@ -209669,10 +251848,17 @@ static void fts5DataDelete(Fts5Index *p, i64 iFirst, i64 iLast){ /* ** Remove all records associated with segment iSegid. */ -static void fts5DataRemoveSegment(Fts5Index *p, int iSegid){ +static void fts5DataRemoveSegment(Fts5Index *p, Fts5StructureSegment *pSeg){ + int iSegid = pSeg->iSegid; i64 iFirst = FTS5_SEGMENT_ROWID(iSegid, 0); i64 iLast = FTS5_SEGMENT_ROWID(iSegid+1, 0)-1; fts5DataDelete(p, iFirst, iLast); + + if( pSeg->nPgTombstone ){ + i64 iTomb1 = FTS5_TOMBSTONE_ROWID(iSegid, 0); + i64 iTomb2 = FTS5_TOMBSTONE_ROWID(iSegid, pSeg->nPgTombstone-1); + fts5DataDelete(p, iTomb1, iTomb2); + } if( p->pIdxDeleter==0 ){ Fts5Config *pConfig = p->pConfig; fts5IndexPrepareStmt(p, &p->pIdxDeleter, sqlite3_mprintf( @@ -209688,7 +251874,7 @@ static void fts5DataRemoveSegment(Fts5Index *p, int iSegid){ } /* -** Release a reference to an Fts5Structure object returned by an earlier +** Release a reference to an Fts5Structure object returned by an earlier ** call to fts5StructureRead() or fts5StructureDecode(). */ static void fts5StructureRelease(Fts5Structure *pStruct){ @@ -209706,6 +251892,58 @@ static void fts5StructureRef(Fts5Structure *pStruct){ pStruct->nRef++; } +static void *sqlite3Fts5StructureRef(Fts5Index *p){ + fts5StructureRef(p->pStruct); + return (void*)p->pStruct; +} +static void sqlite3Fts5StructureRelease(void *p){ + if( p ){ + fts5StructureRelease((Fts5Structure*)p); + } +} +static int sqlite3Fts5StructureTest(Fts5Index *p, void *pStruct){ + if( p->pStruct!=(Fts5Structure*)pStruct ){ + return SQLITE_ABORT; + } + return SQLITE_OK; +} + +/* +** Ensure that structure object (*pp) is writable. +** +** This function is a no-op if (*pRc) is not SQLITE_OK when it is called. If +** an error occurs, (*pRc) is set to an SQLite error code before returning. +*/ +static void fts5StructureMakeWritable(int *pRc, Fts5Structure **pp){ + Fts5Structure *p = *pp; + if( *pRc==SQLITE_OK && p->nRef>1 ){ + i64 nByte = SZ_FTS5STRUCTURE(p->nLevel); + Fts5Structure *pNew; + pNew = (Fts5Structure*)sqlite3Fts5MallocZero(pRc, nByte); + if( pNew ){ + int i; + memcpy(pNew, p, nByte); + for(i=0; inLevel; i++) pNew->aLevel[i].aSeg = 0; + for(i=0; inLevel; i++){ + Fts5StructureLevel *pLvl = &pNew->aLevel[i]; + nByte = sizeof(Fts5StructureSegment) * pNew->aLevel[i].nSeg; + pLvl->aSeg = (Fts5StructureSegment*)sqlite3Fts5MallocZero(pRc, nByte); + if( pLvl->aSeg==0 ){ + for(i=0; inLevel; i++){ + sqlite3_free(pNew->aLevel[i].aSeg); + } + sqlite3_free(pNew); + return; + } + memcpy(pLvl->aSeg, p->aLevel[i].aSeg, nByte); + } + p->nRef--; + pNew->nRef = 1; + } + *pp = pNew; + } +} + /* ** Deserialize and return the structure record currently stored in serialized ** form within buffer pData/nData. @@ -209731,11 +251969,19 @@ static int fts5StructureDecode( int nSegment = 0; sqlite3_int64 nByte; /* Bytes of space to allocate at pRet */ Fts5Structure *pRet = 0; /* Structure object to return */ + int bStructureV2 = 0; /* True for FTS5_STRUCTURE_V2 */ + u64 nOriginCntr = 0; /* Largest origin value seen so far */ /* Grab the cookie value */ if( piCookie ) *piCookie = sqlite3Fts5Get32(pData); i = 4; + /* Check if this is a V2 structure record. Set bStructureV2 if it is. */ + if( 0==memcmp(&pData[i], FTS5_STRUCTURE_V2, 4) ){ + i += 4; + bStructureV2 = 1; + } + /* Read the total number of levels and segments from the start of the ** structure record. */ i += fts5GetVarint32(&pData[i], nLevel); @@ -209745,10 +251991,7 @@ static int fts5StructureDecode( ){ return FTS5_CORRUPT; } - nByte = ( - sizeof(Fts5Structure) + /* Main structure */ - sizeof(Fts5StructureLevel) * (nLevel-1) /* aLevel[] array */ - ); + nByte = SZ_FTS5STRUCTURE(nLevel); pRet = (Fts5Structure*)sqlite3Fts5MallocZero(&rc, nByte); if( pRet ){ @@ -209768,8 +252011,8 @@ static int fts5StructureDecode( i += fts5GetVarint32(&pData[i], pLvl->nMerge); i += fts5GetVarint32(&pData[i], nTotal); if( nTotalnMerge ) rc = FTS5_CORRUPT; - pLvl->aSeg = (Fts5StructureSegment*)sqlite3Fts5MallocZero(&rc, - nTotal * sizeof(Fts5StructureSegment) + pLvl->aSeg = (Fts5StructureSegment*)sqlite3Fts5MallocZero(&rc, + (i64)nTotal * sizeof(Fts5StructureSegment) ); nSegment -= nTotal; } @@ -209782,9 +252025,18 @@ static int fts5StructureDecode( rc = FTS5_CORRUPT; break; } + assert( pSeg!=0 ); i += fts5GetVarint32(&pData[i], pSeg->iSegid); i += fts5GetVarint32(&pData[i], pSeg->pgnoFirst); i += fts5GetVarint32(&pData[i], pSeg->pgnoLast); + if( bStructureV2 ){ + i += fts5GetVarint(&pData[i], &pSeg->iOrigin1); + i += fts5GetVarint(&pData[i], &pSeg->iOrigin2); + i += fts5GetVarint32(&pData[i], pSeg->nPgTombstone); + i += fts5GetVarint(&pData[i], &pSeg->nEntryTombstone); + i += fts5GetVarint(&pData[i], &pSeg->nEntry); + nOriginCntr = MAX(nOriginCntr, pSeg->iOrigin2); + } if( pSeg->pgnoLastpgnoFirst ){ rc = FTS5_CORRUPT; break; @@ -209795,6 +252047,9 @@ static int fts5StructureDecode( } } if( nSegment!=0 && rc==SQLITE_OK ) rc = FTS5_CORRUPT; + if( bStructureV2 ){ + pRet->nOriginCntr = nOriginCntr+1; + } if( rc!=SQLITE_OK ){ fts5StructureRelease(pRet); @@ -209807,16 +252062,16 @@ static int fts5StructureDecode( } /* -** +** Add a level to the Fts5Structure.aLevel[] array of structure object +** (*ppStruct). */ static void fts5StructureAddLevel(int *pRc, Fts5Structure **ppStruct){ + fts5StructureMakeWritable(pRc, ppStruct); + assert( (ppStruct!=0 && (*ppStruct)!=0) || (*pRc)!=SQLITE_OK ); if( *pRc==SQLITE_OK ){ Fts5Structure *pStruct = *ppStruct; int nLevel = pStruct->nLevel; - sqlite3_int64 nByte = ( - sizeof(Fts5Structure) + /* Main structure */ - sizeof(Fts5StructureLevel) * (nLevel+1) /* aLevel[] array */ - ); + sqlite3_int64 nByte = SZ_FTS5STRUCTURE(nLevel+2); pStruct = sqlite3_realloc64(pStruct, nByte); if( pStruct ){ @@ -209834,10 +252089,10 @@ static void fts5StructureAddLevel(int *pRc, Fts5Structure **ppStruct){ ** segments. */ static void fts5StructureExtendLevel( - int *pRc, - Fts5Structure *pStruct, - int iLvl, - int nExtra, + int *pRc, + Fts5Structure *pStruct, + int iLvl, + int nExtra, int bInsert ){ if( *pRc==SQLITE_OK ){ @@ -209873,8 +252128,14 @@ static Fts5Structure *fts5StructureReadUncached(Fts5Index *p){ /* TODO: Do we need this if the leaf-index is appended? Probably... */ memset(&pData->p[pData->nn], 0, FTS5_DATA_PADDING); p->rc = fts5StructureDecode(pData->p, pData->nn, &iCookie, &pRet); - if( p->rc==SQLITE_OK && pConfig->iCookie!=iCookie ){ - p->rc = sqlite3Fts5ConfigLoad(pConfig, iCookie); + if( p->rc==SQLITE_OK ){ + if( (pConfig->pgsz==0 || pConfig->iCookie!=iCookie) ){ + p->rc = sqlite3Fts5ConfigLoad(pConfig, iCookie); + } + }else if( p->rc==SQLITE_CORRUPT_VTAB ){ + sqlite3Fts5ConfigErrmsg(p->pConfig, + "fts5: corrupt structure record for table \"%s\"", p->pConfig->zName + ); } fts5DataRelease(pData); if( p->rc!=SQLITE_OK ){ @@ -209891,7 +252152,7 @@ static i64 fts5IndexDataVersion(Fts5Index *p){ if( p->rc==SQLITE_OK ){ if( p->pDataVersion==0 ){ - p->rc = fts5IndexPrepareStmt(p, &p->pDataVersion, + p->rc = fts5IndexPrepareStmt(p, &p->pDataVersion, sqlite3_mprintf("PRAGMA %Q.data_version", p->pConfig->zDb) ); if( p->rc ) return 0; @@ -209910,7 +252171,7 @@ static i64 fts5IndexDataVersion(Fts5Index *p){ ** Read, deserialize and return the structure record. ** ** The Fts5Structure.aLevel[] and each Fts5StructureLevel.aSeg[] array -** are over-allocated as described for function fts5StructureDecode() +** are over-allocated as described for function fts5StructureDecode() ** above. ** ** If an error occurs, NULL is returned and an error code left in the @@ -210004,6 +252265,7 @@ static void fts5StructureWrite(Fts5Index *p, Fts5Structure *pStruct){ Fts5Buffer buf; /* Buffer to serialize record into */ int iLvl; /* Used to iterate through levels */ int iCookie; /* Cookie value to store */ + int nHdr = (pStruct->nOriginCntr>0 ? (4+4+9+9+9) : (4+9+9)); assert( pStruct->nSegment==fts5StructureCountSegments(pStruct) ); memset(&buf, 0, sizeof(Fts5Buffer)); @@ -210012,9 +252274,12 @@ static void fts5StructureWrite(Fts5Index *p, Fts5Structure *pStruct){ iCookie = p->pConfig->iCookie; if( iCookie<0 ) iCookie = 0; - if( 0==sqlite3Fts5BufferSize(&p->rc, &buf, 4+9+9+9) ){ + if( 0==sqlite3Fts5BufferSize(&p->rc, &buf, nHdr) ){ sqlite3Fts5Put32(buf.p, iCookie); buf.n = 4; + if( pStruct->nOriginCntr>0 ){ + fts5BufferSafeAppendBlob(&buf, FTS5_STRUCTURE_V2, 4); + } fts5BufferSafeAppendVarint(&buf, pStruct->nLevel); fts5BufferSafeAppendVarint(&buf, pStruct->nSegment); fts5BufferSafeAppendVarint(&buf, (i64)pStruct->nWriteCounter); @@ -210028,9 +252293,17 @@ static void fts5StructureWrite(Fts5Index *p, Fts5Structure *pStruct){ assert( pLvl->nMerge<=pLvl->nSeg ); for(iSeg=0; iSegnSeg; iSeg++){ - fts5BufferAppendVarint(&p->rc, &buf, pLvl->aSeg[iSeg].iSegid); - fts5BufferAppendVarint(&p->rc, &buf, pLvl->aSeg[iSeg].pgnoFirst); - fts5BufferAppendVarint(&p->rc, &buf, pLvl->aSeg[iSeg].pgnoLast); + Fts5StructureSegment *pSeg = &pLvl->aSeg[iSeg]; + fts5BufferAppendVarint(&p->rc, &buf, pSeg->iSegid); + fts5BufferAppendVarint(&p->rc, &buf, pSeg->pgnoFirst); + fts5BufferAppendVarint(&p->rc, &buf, pSeg->pgnoLast); + if( pStruct->nOriginCntr>0 ){ + fts5BufferAppendVarint(&p->rc, &buf, pSeg->iOrigin1); + fts5BufferAppendVarint(&p->rc, &buf, pSeg->iOrigin2); + fts5BufferAppendVarint(&p->rc, &buf, pSeg->nPgTombstone); + fts5BufferAppendVarint(&p->rc, &buf, pSeg->nEntryTombstone); + fts5BufferAppendVarint(&p->rc, &buf, pSeg->nEntry); + } } } @@ -210059,8 +252332,8 @@ static int fts5SegmentSize(Fts5StructureSegment *pSeg){ } /* -** Return a copy of index structure pStruct. Except, promote as many -** segments as possible to level iPromote. If an OOM occurs, NULL is +** Return a copy of index structure pStruct. Except, promote as many +** segments as possible to level iPromote. If an OOM occurs, NULL is ** returned. */ static void fts5StructurePromoteTo( @@ -210100,8 +252373,8 @@ static void fts5StructurePromoteTo( ** ** b) If the segment just written is larger than the newest segment on ** the next populated level, then that segment, and any other adjacent -** segments that are also smaller than the one just written, are -** promoted. +** segments that are also smaller than the one just written, are +** promoted. ** ** If one or more segments are promoted, the structure object is updated ** to reflect this. @@ -210135,7 +252408,7 @@ static void fts5StructurePromote( if( sz>szMax ) szMax = sz; } if( szMax>=szSeg ){ - /* Condition (a) is true. Promote the newest segment on level + /* Condition (a) is true. Promote the newest segment on level ** iLvl to level iTst. */ iPromote = iTst; szPromote = szMax; @@ -210154,7 +252427,7 @@ static void fts5StructurePromote( /* -** Advance the iterator passed as the only argument. If the end of the +** Advance the iterator passed as the only argument. If the end of the ** doclist-index page is reached, return non-zero. */ static int fts5DlidxLvlNext(Fts5DlidxLvl *pLvl){ @@ -210169,13 +252442,13 @@ static int fts5DlidxLvlNext(Fts5DlidxLvl *pLvl){ }else{ int iOff; for(iOff=pLvl->iOff; iOffnn; iOff++){ - if( pData->p[iOff] ) break; + if( pData->p[iOff] ) break; } if( iOffnn ){ - i64 iVal; + u64 iVal; pLvl->iLeafPgno += (iOff - pLvl->iOff) + 1; - iOff += fts5GetVarint(&pData->p[iOff], (u64*)&iVal); + iOff += fts5GetVarint(&pData->p[iOff], &iVal); pLvl->iRowid += iVal; pLvl->iOff = iOff; }else{ @@ -210199,7 +252472,7 @@ static int fts5DlidxIterNextR(Fts5Index *p, Fts5DlidxIter *pIter, int iLvl){ if( pLvl[1].bEof==0 ){ fts5DataRelease(pLvl->pData); memset(pLvl, 0, sizeof(Fts5DlidxLvl)); - pLvl->pData = fts5DataRead(p, + pLvl->pData = fts5DataRead(p, FTS5_DLIDX_ROWID(pIter->iSegid, iLvl, pLvl[1].iLeafPgno) ); if( pLvl->pData ) fts5DlidxLvlNext(pLvl); @@ -210219,7 +252492,7 @@ static int fts5DlidxIterNext(Fts5Index *p, Fts5DlidxIter *pIter){ ** points to the first rowid in the doclist-index. ** ** pData: -** pointer to doclist-index record, +** pointer to doclist-index record, ** ** When this function is called pIter->iLeafPgno is the page number the ** doclist is associated with (the one featuring the term). @@ -210250,7 +252523,7 @@ static void fts5DlidxIterLast(Fts5Index *p, Fts5DlidxIter *pIter){ Fts5DlidxLvl *pChild = &pLvl[-1]; fts5DataRelease(pChild->pData); memset(pChild, 0, sizeof(Fts5DlidxLvl)); - pChild->pData = fts5DataRead(p, + pChild->pData = fts5DataRead(p, FTS5_DLIDX_ROWID(pIter->iSegid, i-1, pLvl->iLeafPgno) ); } @@ -210268,42 +252541,25 @@ static int fts5DlidxLvlPrev(Fts5DlidxLvl *pLvl){ pLvl->bEof = 1; }else{ u8 *a = pLvl->pData->p; - i64 iVal; - int iLimit; - int ii; - int nZero = 0; - - /* Currently iOff points to the first byte of a varint. This block - ** decrements iOff until it points to the first byte of the previous - ** varint. Taking care not to read any memory locations that occur - ** before the buffer in memory. */ - iLimit = (iOff>9 ? iOff-9 : 0); - for(iOff--; iOff>iLimit; iOff--){ - if( (a[iOff-1] & 0x80)==0 ) break; - } - - fts5GetVarint(&a[iOff], (u64*)&iVal); - pLvl->iRowid -= iVal; - pLvl->iLeafPgno--; - - /* Skip backwards past any 0x00 varints. */ - for(ii=iOff-1; ii>=pLvl->iFirstOff && a[ii]==0x00; ii--){ - nZero++; - } - if( ii>=pLvl->iFirstOff && (a[ii] & 0x80) ){ - /* The byte immediately before the last 0x00 byte has the 0x80 bit - ** set. So the last 0x00 is only a varint 0 if there are 8 more 0x80 - ** bytes before a[ii]. */ - int bZero = 0; /* True if last 0x00 counts */ - if( (ii-8)>=pLvl->iFirstOff ){ - int j; - for(j=1; j<=8 && (a[ii-j] & 0x80); j++); - bZero = (j>8); + + pLvl->iOff = 0; + fts5DlidxLvlNext(pLvl); + while( 1 ){ + int nZero = 0; + int ii = pLvl->iOff; + u64 delta = 0; + + while( a[ii]==0 ){ + nZero++; + ii++; } - if( bZero==0 ) nZero--; + ii += sqlite3Fts5GetVarint(&a[ii], &delta); + + if( ii>=iOff ) break; + pLvl->iLeafPgno += nZero+1; + pLvl->iRowid += delta; + pLvl->iOff = ii; } - pLvl->iLeafPgno -= nZero; - pLvl->iOff = iOff - nZero; } return pLvl->bEof; @@ -210319,7 +252575,7 @@ static int fts5DlidxIterPrevR(Fts5Index *p, Fts5DlidxIter *pIter, int iLvl){ if( pLvl[1].bEof==0 ){ fts5DataRelease(pLvl->pData); memset(pLvl, 0, sizeof(Fts5DlidxLvl)); - pLvl->pData = fts5DataRead(p, + pLvl->pData = fts5DataRead(p, FTS5_DLIDX_ROWID(pIter->iSegid, iLvl, pLvl[1].iLeafPgno) ); if( pLvl->pData ){ @@ -210360,7 +252616,7 @@ static Fts5DlidxIter *fts5DlidxIterInit( int bDone = 0; for(i=0; p->rc==SQLITE_OK && bDone==0; i++){ - sqlite3_int64 nByte = sizeof(Fts5DlidxIter) + i * sizeof(Fts5DlidxLvl); + sqlite3_int64 nByte = SZ_FTS5DLIDXITER(i+1); Fts5DlidxIter *pNew; pNew = (Fts5DlidxIter*)sqlite3_realloc64(pIter, nByte); @@ -210418,7 +252674,7 @@ static void fts5SegIterNextPage( pIter->pLeaf = pIter->pNextLeaf; pIter->pNextLeaf = 0; }else if( pIter->iLeafPgno<=pSeg->pgnoLast ){ - pIter->pLeaf = fts5LeafRead(p, + pIter->pLeaf = fts5LeafRead(p, FTS5_SEGMENT_ROWID(pSeg->iSegid, pIter->iLeafPgno) ); }else{ @@ -210462,7 +252718,7 @@ static int fts5GetPoslistSize(const u8 *p, int *pnSz, int *pbDel){ ** Fts5SegIter.nPos ** Fts5SegIter.bDel ** -** Leave Fts5SegIter.iLeafOffset pointing to the first byte of the +** Leave Fts5SegIter.iLeafOffset pointing to the first byte of the ** position list content (if any). */ static void fts5SegIterLoadNPos(Fts5Index *p, Fts5SegIter *pIter){ @@ -210496,13 +252752,13 @@ static void fts5SegIterLoadNPos(Fts5Index *p, Fts5SegIter *pIter){ static void fts5SegIterLoadRowid(Fts5Index *p, Fts5SegIter *pIter){ u8 *a = pIter->pLeaf->p; /* Buffer to read data from */ - int iOff = pIter->iLeafOffset; + i64 iOff = pIter->iLeafOffset; ASSERT_SZLEAF_OK(pIter->pLeaf); - if( iOff>=pIter->pLeaf->szLeaf ){ + while( iOff>=pIter->pLeaf->szLeaf ){ fts5SegIterNextPage(p, pIter); if( pIter->pLeaf==0 ){ - if( p->rc==SQLITE_OK ) p->rc = FTS5_CORRUPT; + if( p->rc==SQLITE_OK ) FTS5_CORRUPT_ITER(p, pIter); return; } iOff = 4; @@ -210513,7 +252769,7 @@ static void fts5SegIterLoadRowid(Fts5Index *p, Fts5SegIter *pIter){ } /* -** Fts5SegIter.iLeafOffset currently points to the first byte of the +** Fts5SegIter.iLeafOffset currently points to the first byte of the ** "nSuffix" field of a term. Function parameter nKeep contains the value ** of the "nPrefix" field (if there was one - it is passed 0 if this is ** the first term in the segment). @@ -210524,17 +252780,17 @@ static void fts5SegIterLoadRowid(Fts5Index *p, Fts5SegIter *pIter){ ** Fts5SegIter.rowid ** ** accordingly and leaves (Fts5SegIter.iLeafOffset) set to the content of -** the first position list. The position list belonging to document +** the first position list. The position list belonging to document ** (Fts5SegIter.iRowid). */ static void fts5SegIterLoadTerm(Fts5Index *p, Fts5SegIter *pIter, int nKeep){ u8 *a = pIter->pLeaf->p; /* Buffer to read data from */ - int iOff = pIter->iLeafOffset; /* Offset to read at */ + i64 iOff = pIter->iLeafOffset; /* Offset to read at */ int nNew; /* Bytes of new data */ iOff += fts5GetVarint32(&a[iOff], nNew); if( iOff+nNew>pIter->pLeaf->szLeaf || nKeep>pIter->term.n || nNew==0 ){ - p->rc = FTS5_CORRUPT; + FTS5_CORRUPT_ITER(p, pIter); return; } pIter->term.n = nKeep; @@ -210570,12 +252826,31 @@ static void fts5SegIterSetNext(Fts5Index *p, Fts5SegIter *pIter){ } } +/* +** Allocate a tombstone hash page array object (pIter->pTombArray) for +** the iterator passed as the second argument. If an OOM error occurs, +** leave an error in the Fts5Index object. +*/ +static void fts5SegIterAllocTombstone(Fts5Index *p, Fts5SegIter *pIter){ + const i64 nTomb = (i64)pIter->pSeg->nPgTombstone; + if( nTomb>0 ){ + i64 nByte = SZ_FTS5TOMBSTONEARRAY(nTomb+1); + Fts5TombstoneArray *pNew; + pNew = (Fts5TombstoneArray*)sqlite3Fts5MallocZero(&p->rc, nByte); + if( pNew ){ + pNew->nTombstone = nTomb; + pNew->nRef = 1; + pIter->pTombArray = pNew; + } + } +} + /* ** Initialize the iterator object pIter to iterate through the entries in -** segment pSeg. The iterator is left pointing to the first entry when +** segment pSeg. The iterator is left pointing to the first entry when ** this function returns. ** -** If an error occurs, Fts5Index.rc is set to an appropriate error code. If +** If an error occurs, Fts5Index.rc is set to an appropriate error code. If ** an error has already occurred when this function is called, it is a no-op. */ static void fts5SegIterInit( @@ -210598,16 +252873,20 @@ static void fts5SegIterInit( fts5SegIterSetNext(p, pIter); pIter->pSeg = pSeg; pIter->iLeafPgno = pSeg->pgnoFirst-1; - fts5SegIterNextPage(p, pIter); + do { + fts5SegIterNextPage(p, pIter); + }while( p->rc==SQLITE_OK && pIter->pLeaf && pIter->pLeaf->nn==4 ); } - if( p->rc==SQLITE_OK ){ + if( p->rc==SQLITE_OK && pIter->pLeaf ){ pIter->iLeafOffset = 4; + assert( pIter->pLeaf!=0 ); assert_nc( pIter->pLeaf->nn>4 ); assert_nc( fts5LeafFirstTermOff(pIter->pLeaf)==4 ); pIter->iPgidxOff = pIter->pLeaf->szLeaf+1; fts5SegIterLoadTerm(p, pIter, 0); fts5SegIterLoadNPos(p, pIter); + fts5SegIterAllocTombstone(p, pIter); } } @@ -210620,8 +252899,8 @@ static void fts5SegIterInit( ** the position-list size field for the first relevant rowid on the page. ** Fts5SegIter.rowid is set, but nPos and bDel are not. ** -** This function advances the iterator so that it points to the last -** relevant rowid on the page and, if necessary, initializes the +** This function advances the iterator so that it points to the last +** relevant rowid on the page and, if necessary, initializes the ** aRowidOffset[] and iRowidOffset variables. At this point the iterator ** is in its regular state - Fts5SegIter.iLeafOffset points to the first ** byte of the position list content associated with said rowid. @@ -210639,8 +252918,9 @@ static void fts5SegIterReverseInitPage(Fts5Index *p, Fts5SegIter *pIter){ ASSERT_SZLEAF_OK(pIter->pLeaf); while( 1 ){ - i64 iDelta = 0; + u64 iDelta = 0; + if( i>=n ) break; if( eDetail==FTS5_DETAIL_NONE ){ /* todo */ if( i=n ) break; - i += fts5GetVarint(&a[i], (u64*)&iDelta); + i += fts5GetVarint(&a[i], &iDelta); pIter->iRowid += iDelta; /* If necessary, grow the pIter->aRowidOffset[] array. */ if( iRowidOffset>=pIter->nRowidOffset ){ - int nNew = pIter->nRowidOffset + 8; + i64 nNew = pIter->nRowidOffset + 8; int *aNew = (int*)sqlite3_realloc64(pIter->aRowidOffset,nNew*sizeof(int)); if( aNew==0 ){ p->rc = SQLITE_NOMEM; @@ -210688,7 +252968,7 @@ static void fts5SegIterReverseNewPage(Fts5Index *p, Fts5SegIter *pIter){ while( p->rc==SQLITE_OK && pIter->iLeafPgno>pIter->iTermLeafPgno ){ Fts5Data *pNew; pIter->iLeafPgno--; - pNew = fts5DataRead(p, FTS5_SEGMENT_ROWID( + pNew = fts5LeafRead(p, FTS5_SEGMENT_ROWID( pIter->pSeg->iSegid, pIter->iLeafPgno )); if( pNew ){ @@ -210705,8 +252985,12 @@ static void fts5SegIterReverseNewPage(Fts5Index *p, Fts5SegIter *pIter){ int iRowidOff; iRowidOff = fts5LeafFirstRowidOff(pNew); if( iRowidOff ){ - pIter->pLeaf = pNew; - pIter->iLeafOffset = iRowidOff; + if( iRowidOff>=pNew->szLeaf ){ + FTS5_CORRUPT_ITER(p, pIter); + }else{ + pIter->pLeaf = pNew; + pIter->iLeafOffset = iRowidOff; + } } } @@ -210753,7 +253037,7 @@ static void fts5SegIterNext_Reverse( if( pIter->iRowidOffset>0 ){ u8 *a = pIter->pLeaf->p; int iOff; - i64 iDelta; + u64 iDelta; pIter->iRowidOffset--; pIter->iLeafOffset = pIter->aRowidOffset[pIter->iRowidOffset]; @@ -210762,7 +253046,7 @@ static void fts5SegIterNext_Reverse( if( p->pConfig->eDetail!=FTS5_DETAIL_NONE ){ iOff += pIter->nPos; } - fts5GetVarint(&a[iOff], (u64*)&iDelta); + fts5GetVarint(&a[iOff], &iDelta); pIter->iRowid -= iDelta; }else{ fts5SegIterReverseNewPage(p, pIter); @@ -210790,7 +253074,7 @@ static void fts5SegIterNext_None( iOff = pIter->iLeafOffset; /* Next entry is on the next page */ - if( pIter->pSeg && iOff>=pIter->pLeaf->szLeaf ){ + while( pIter->pSeg && iOff>=pIter->pLeaf->szLeaf ){ fts5SegIterNextPage(p, pIter); if( p->rc || pIter->pLeaf==0 ) return; pIter->iRowid = 0; @@ -210799,7 +253083,7 @@ static void fts5SegIterNext_None( if( iOffiEndofDoclist ){ /* Next entry is on the current page */ - i64 iDelta; + u64 iDelta; iOff += sqlite3Fts5GetVarint(&pIter->pLeaf->p[iOff], (u64*)&iDelta); pIter->iLeafOffset = iOff; pIter->iRowid += iDelta; @@ -210814,15 +253098,16 @@ static void fts5SegIterNext_None( }else{ const u8 *pList = 0; const char *zTerm = 0; + int nTerm = 0; int nList; sqlite3Fts5HashScanNext(p->pHash); - sqlite3Fts5HashScanEntry(p->pHash, &zTerm, &pList, &nList); + sqlite3Fts5HashScanEntry(p->pHash, &zTerm, &nTerm, &pList, &nList); if( pList==0 ) goto next_none_eof; pIter->pLeaf->p = (u8*)pList; pIter->pLeaf->nn = nList; pIter->pLeaf->szLeaf = nList; pIter->iEndofDoclist = nList; - sqlite3Fts5BufferSet(&p->rc,&pIter->term, (int)strlen(zTerm), (u8*)zTerm); + sqlite3Fts5BufferSet(&p->rc,&pIter->term, nTerm, (u8*)zTerm); pIter->iLeafOffset = fts5GetVarint(pList, (u64*)&pIter->iRowid); } @@ -210841,10 +253126,10 @@ static void fts5SegIterNext_None( /* -** Advance iterator pIter to the next entry. +** Advance iterator pIter to the next entry. ** -** If an error occurs, Fts5Index.rc is set to an appropriate error code. It -** is not considered an error if the iterator reaches EOF. If an error has +** If an error occurs, Fts5Index.rc is set to an appropriate error code. It +** is not considered an error if the iterator reaches EOF. If an error has ** already occurred when this function is called, it is a no-op. */ static void fts5SegIterNext( @@ -210888,11 +253173,12 @@ static void fts5SegIterNext( }else if( pIter->pSeg==0 ){ const u8 *pList = 0; const char *zTerm = 0; + int nTerm = 0; int nList = 0; assert( (pIter->flags & FTS5_SEGITER_ONETERM) || pbNewTerm ); if( 0==(pIter->flags & FTS5_SEGITER_ONETERM) ){ sqlite3Fts5HashScanNext(p->pHash); - sqlite3Fts5HashScanEntry(p->pHash, &zTerm, &pList, &nList); + sqlite3Fts5HashScanEntry(p->pHash, &zTerm, &nTerm, &pList, &nList); } if( pList==0 ){ fts5DataRelease(pIter->pLeaf); @@ -210902,8 +253188,7 @@ static void fts5SegIterNext( pIter->pLeaf->nn = nList; pIter->pLeaf->szLeaf = nList; pIter->iEndofDoclist = nList+1; - sqlite3Fts5BufferSet(&p->rc, &pIter->term, (int)strlen(zTerm), - (u8*)zTerm); + sqlite3Fts5BufferSet(&p->rc, &pIter->term, nTerm, (u8*)zTerm); pIter->iLeafOffset = fts5GetVarint(pList, (u64*)&pIter->iRowid); *pbNewTerm = 1; } @@ -210935,7 +253220,7 @@ static void fts5SegIterNext( } assert_nc( iOffszLeaf ); if( iOff>pLeaf->szLeaf ){ - p->rc = FTS5_CORRUPT; + FTS5_CORRUPT_ITER(p, pIter); return; } } @@ -210955,14 +253240,9 @@ static void fts5SegIterNext( }else{ /* The following could be done by calling fts5SegIterLoadNPos(). But ** this block is particularly performance critical, so equivalent - ** code is inlined. - ** - ** Later: Switched back to fts5SegIterLoadNPos() because it supports - ** detail=none mode. Not ideal. - */ + ** code is inlined. */ int nSz; - assert( p->rc==SQLITE_OK ); - assert( pIter->iLeafOffset<=pIter->pLeaf->nn ); + assert_nc( pIter->iLeafOffset<=pIter->pLeaf->nn ); fts5FastGetVarint32(pIter->pLeaf->p, pIter->iLeafOffset, nSz); pIter->bDel = (nSz & 0x0001); pIter->nPos = nSz>>1; @@ -210988,10 +253268,10 @@ static void fts5SegIterReverse(Fts5Index *p, Fts5SegIter *pIter){ Fts5Data *pLast = 0; int pgnoLast = 0; - if( pDlidx ){ + if( pDlidx && p->pConfig->iVersion==FTS5_CURRENT_VERSION ){ int iSegid = pIter->pSeg->iSegid; pgnoLast = fts5DlidxIterPgno(pDlidx); - pLast = fts5DataRead(p, FTS5_SEGMENT_ROWID(iSegid, pgnoLast)); + pLast = fts5LeafRead(p, FTS5_SEGMENT_ROWID(iSegid, pgnoLast)); }else{ Fts5Data *pLeaf = pIter->pLeaf; /* Current leaf data */ @@ -211018,7 +253298,7 @@ static void fts5SegIterReverse(Fts5Index *p, Fts5SegIter *pIter){ ** forward to find the page containing the last rowid. */ for(pgno=pIter->iLeafPgno+1; !p->rc && pgno<=pSeg->pgnoLast; pgno++){ i64 iAbs = FTS5_SEGMENT_ROWID(pSeg->iSegid, pgno); - Fts5Data *pNew = fts5DataRead(p, iAbs); + Fts5Data *pNew = fts5LeafRead(p, iAbs); if( pNew ){ int iRowid, bTermless; iRowid = fts5LeafFirstRowidOff(pNew); @@ -211035,7 +253315,7 @@ static void fts5SegIterReverse(Fts5Index *p, Fts5SegIter *pIter){ } /* If pLast is NULL at this point, then the last rowid for this doclist - ** lies on the page currently indicated by the iterator. In this case + ** lies on the page currently indicated by the iterator. In this case ** pIter->iLeafOffset is already set to point to the position-list size ** field associated with the first relevant rowid on the page. ** @@ -211048,16 +253328,21 @@ static void fts5SegIterReverse(Fts5Index *p, Fts5SegIter *pIter){ fts5DataRelease(pIter->pLeaf); pIter->pLeaf = pLast; pIter->iLeafPgno = pgnoLast; - iOff = fts5LeafFirstRowidOff(pLast); - iOff += fts5GetVarint(&pLast->p[iOff], (u64*)&pIter->iRowid); - pIter->iLeafOffset = iOff; + if( p->rc==SQLITE_OK ){ + iOff = fts5LeafFirstRowidOff(pLast); + if( iOff>pLast->szLeaf ){ + FTS5_CORRUPT_ITER(p, pIter); + return; + } + iOff += fts5GetVarint(&pLast->p[iOff], (u64*)&pIter->iRowid); + pIter->iLeafOffset = iOff; - if( fts5LeafIsTermless(pLast) ){ - pIter->iEndofDoclist = pLast->nn+1; - }else{ - pIter->iEndofDoclist = fts5LeafFirstTermOff(pLast); + if( fts5LeafIsTermless(pLast) ){ + pIter->iEndofDoclist = pLast->nn+1; + }else{ + pIter->iEndofDoclist = fts5LeafFirstTermOff(pLast); + } } - } fts5SegIterReverseInitPage(p, pIter); @@ -211065,8 +253350,8 @@ static void fts5SegIterReverse(Fts5Index *p, Fts5SegIter *pIter){ /* ** Iterator pIter currently points to the first rowid of a doclist. -** There is a doclist-index associated with the final term on the current -** page. If the current term is the last term on the page, load the +** There is a doclist-index associated with the final term on the current +** page. If the current term is the last term on the page, load the ** doclist-index from disk and initialize an iterator at (pIter->pDlidx). */ static void fts5SegIterLoadDlidx(Fts5Index *p, Fts5SegIter *pIter){ @@ -211080,8 +253365,8 @@ static void fts5SegIterLoadDlidx(Fts5Index *p, Fts5SegIter *pIter){ /* Check if the current doclist ends on this page. If it does, return ** early without loading the doclist-index (as it belongs to a different ** term. */ - if( pIter->iTermLeafPgno==pIter->iLeafPgno - && pIter->iEndofDoclistszLeaf + if( pIter->iTermLeafPgno==pIter->iLeafPgno + && pIter->iEndofDoclistszLeaf ){ return; } @@ -211109,25 +253394,24 @@ static void fts5LeafSeek( Fts5SegIter *pIter, /* Iterator to seek */ const u8 *pTerm, int nTerm /* Term to search for */ ){ - int iOff; + u32 iOff; const u8 *a = pIter->pLeaf->p; - int szLeaf = pIter->pLeaf->szLeaf; - int n = pIter->pLeaf->nn; + u32 n = (u32)pIter->pLeaf->nn; u32 nMatch = 0; u32 nKeep = 0; u32 nNew = 0; u32 iTermOff; - int iPgidx; /* Current offset in pgidx */ + u32 iPgidx; /* Current offset in pgidx */ int bEndOfPage = 0; assert( p->rc==SQLITE_OK ); - iPgidx = szLeaf; + iPgidx = (u32)pIter->pLeaf->szLeaf; iPgidx += fts5GetVarint32(&a[iPgidx], iTermOff); iOff = iTermOff; if( iOff>n ){ - p->rc = FTS5_CORRUPT; + FTS5_CORRUPT_ITER(p, pIter); return; } @@ -211138,6 +253422,10 @@ static void fts5LeafSeek( if( nKeepn ){ + FTS5_CORRUPT_ITER(p, pIter); + return; + } assert( nKeep>=nMatch ); if( nKeep==nMatch ){ @@ -211170,7 +253458,7 @@ static void fts5LeafSeek( iOff = iTermOff; if( iOff>=n ){ - p->rc = FTS5_CORRUPT; + FTS5_CORRUPT_ITER(p, pIter); return; } @@ -211189,15 +253477,15 @@ static void fts5LeafSeek( if( pIter->pLeaf==0 ) return; a = pIter->pLeaf->p; if( fts5LeafIsTermless(pIter->pLeaf)==0 ){ - iPgidx = pIter->pLeaf->szLeaf; + iPgidx = (u32)pIter->pLeaf->szLeaf; iPgidx += fts5GetVarint32(&pIter->pLeaf->p[iPgidx], iOff); - if( iOff<4 || iOff>=pIter->pLeaf->szLeaf ){ - p->rc = FTS5_CORRUPT; + if( iOff<4 || (i64)iOff>=pIter->pLeaf->szLeaf ){ + FTS5_CORRUPT_ITER(p, pIter); return; }else{ nKeep = 0; iTermOff = iOff; - n = pIter->pLeaf->nn; + n = (u32)pIter->pLeaf->nn; iOff += fts5GetVarint32(&a[iOff], nNew); break; } @@ -211206,11 +253494,11 @@ static void fts5LeafSeek( } search_success: - pIter->iLeafOffset = iOff + nNew; - if( pIter->iLeafOffset>n || nNew<1 ){ - p->rc = FTS5_CORRUPT; + if( (i64)iOff+nNew>n || nNew<1 ){ + FTS5_CORRUPT_ITER(p, pIter); return; } + pIter->iLeafOffset = iOff + nNew; pIter->iTermLeafOffset = pIter->iLeafOffset; pIter->iTermLeafPgno = pIter->iLeafPgno; @@ -211246,7 +253534,7 @@ static sqlite3_stmt *fts5IdxSelectStmt(Fts5Index *p){ ** Initialize the object pIter to point to term pTerm/nTerm within segment ** pSeg. If there is no such term in the index, the iterator is set to EOF. ** -** If an error occurs, Fts5Index.rc is set to an appropriate error code. If +** If an error occurs, Fts5Index.rc is set to an appropriate error code. If ** an error has already occurred when this function is called, it is a no-op. */ static void fts5SegIterSeekInit( @@ -211292,7 +253580,7 @@ static void fts5SegIterSeekInit( fts5LeafSeek(p, bGe, pIter, pTerm, nTerm); } - if( p->rc==SQLITE_OK && bGe==0 ){ + if( p->rc==SQLITE_OK && (bGe==0 || (flags & FTS5INDEX_QUERY_SCANONETERM)) ){ pIter->flags |= FTS5_SEGITER_ONETERM; if( pIter->pLeaf ){ if( flags & FTS5INDEX_QUERY_DESC ){ @@ -211308,6 +253596,9 @@ static void fts5SegIterSeekInit( } fts5SegIterSetNext(p, pIter); + if( 0==(flags & FTS5INDEX_QUERY_SCANONETERM) ){ + fts5SegIterAllocTombstone(p, pIter); + } /* Either: ** @@ -211324,12 +253615,89 @@ static void fts5SegIterSeekInit( ); } + +/* +** SQL used by fts5SegIterNextInit() to find the page to open. +*/ +static sqlite3_stmt *fts5IdxNextStmt(Fts5Index *p){ + if( p->pIdxNextSelect==0 ){ + Fts5Config *pConfig = p->pConfig; + fts5IndexPrepareStmt(p, &p->pIdxNextSelect, sqlite3_mprintf( + "SELECT pgno FROM '%q'.'%q_idx' WHERE " + "segid=? AND term>? ORDER BY term ASC LIMIT 1", + pConfig->zDb, pConfig->zName + )); + + } + return p->pIdxNextSelect; +} + +/* +** This is similar to fts5SegIterSeekInit(), except that it initializes +** the segment iterator to point to the first term following the page +** with pToken/nToken on it. +*/ +static void fts5SegIterNextInit( + Fts5Index *p, + const char *pTerm, int nTerm, + Fts5StructureSegment *pSeg, /* Description of segment */ + Fts5SegIter *pIter /* Object to populate */ +){ + int iPg = -1; /* Page of segment to open */ + int bDlidx = 0; + sqlite3_stmt *pSel = 0; /* SELECT to find iPg */ + + pSel = fts5IdxNextStmt(p); + if( pSel ){ + assert( p->rc==SQLITE_OK ); + sqlite3_bind_int(pSel, 1, pSeg->iSegid); + sqlite3_bind_blob(pSel, 2, pTerm, nTerm, SQLITE_STATIC); + + if( sqlite3_step(pSel)==SQLITE_ROW ){ + i64 val = sqlite3_column_int64(pSel, 0); + iPg = (int)(val>>1); + bDlidx = (val & 0x0001); + } + p->rc = sqlite3_reset(pSel); + sqlite3_bind_null(pSel, 2); + if( p->rc ) return; + } + + memset(pIter, 0, sizeof(*pIter)); + pIter->pSeg = pSeg; + pIter->flags |= FTS5_SEGITER_ONETERM; + if( iPg>=0 ){ + pIter->iLeafPgno = iPg - 1; + fts5SegIterNextPage(p, pIter); + fts5SegIterSetNext(p, pIter); + } + if( pIter->pLeaf ){ + const u8 *a = pIter->pLeaf->p; + int iTermOff = 0; + + pIter->iPgidxOff = pIter->pLeaf->szLeaf; + pIter->iPgidxOff += fts5GetVarint32(&a[pIter->iPgidxOff], iTermOff); + if( iTermOff > pIter->pLeaf->szLeaf ){ + p->rc = FTS5_CORRUPT; + return; + } + pIter->iLeafOffset = iTermOff; + fts5SegIterLoadTerm(p, pIter, 0); + fts5SegIterLoadNPos(p, pIter); + if( bDlidx ) fts5SegIterLoadDlidx(p, pIter); + + assert( p->rc!=SQLITE_OK || + fts5BufferCompareBlob(&pIter->term, (const u8*)pTerm, nTerm)>0 + ); + } +} + /* ** Initialize the object pIter to point to term pTerm/nTerm within the -** in-memory hash table. If there is no such term in the hash-table, the +** in-memory hash table. If there is no such term in the hash-table, the ** iterator is set to EOF. ** -** If an error occurs, Fts5Index.rc is set to an appropriate error code. If +** If an error occurs, Fts5Index.rc is set to an appropriate error code. If ** an error has already occurred when this function is called, it is a no-op. */ static void fts5SegIterHashInit( @@ -211350,16 +253718,23 @@ static void fts5SegIterHashInit( const u8 *pList = 0; p->rc = sqlite3Fts5HashScanInit(p->pHash, (const char*)pTerm, nTerm); - sqlite3Fts5HashScanEntry(p->pHash, (const char**)&z, &pList, &nList); - n = (z ? (int)strlen((const char*)z) : 0); + sqlite3Fts5HashScanEntry(p->pHash, (const char**)&z, &n, &pList, &nList); if( pList ){ pLeaf = fts5IdxMalloc(p, sizeof(Fts5Data)); if( pLeaf ){ pLeaf->p = (u8*)pList; } } + + /* The call to sqlite3Fts5HashScanInit() causes the hash table to + ** fill the size field of all existing position lists. This means they + ** can no longer be appended to. Since the only scenario in which they + ** can be appended to is if the previous operation on this table was + ** a DELETE, by clearing the Fts5Index.bDelete flag we can avoid this + ** possibility altogether. */ + p->bDelete = 0; }else{ - p->rc = sqlite3Fts5HashQuery(p->pHash, sizeof(Fts5Data), + p->rc = sqlite3Fts5HashQuery(p->pHash, sizeof(Fts5Data), (const char*)pTerm, nTerm, (void**)&pLeaf, &nList ); if( pLeaf ){ @@ -211388,6 +253763,37 @@ static void fts5SegIterHashInit( fts5SegIterSetNext(p, pIter); } +/* +** Array ap[] contains n elements. Release each of these elements using +** fts5DataRelease(). Then free the array itself using sqlite3_free(). +*/ +static void fts5IndexFreeArray(Fts5Data **ap, int n){ + if( ap ){ + int ii; + for(ii=0; iinRef--; + if( p->nRef<=0 ){ + int ii; + for(ii=0; iinTombstone; ii++){ + fts5DataRelease(p->apTombstone[ii]); + } + sqlite3_free(p); + } + } +} + /* ** Zero the iterator passed as the only argument. */ @@ -211395,6 +253801,7 @@ static void fts5SegIterClear(Fts5SegIter *pIter){ fts5BufferFree(&pIter->term); fts5DataRelease(pIter->pLeaf); fts5DataRelease(pIter->pNextLeaf); + fts5TombstoneArrayDelete(pIter->pTombArray); fts5DlidxIterFree(pIter->pDlidx); sqlite3_free(pIter->aRowidOffset); memset(pIter, 0, sizeof(Fts5SegIter)); @@ -211409,7 +253816,7 @@ static void fts5SegIterClear(Fts5SegIter *pIter){ ** two iterators. */ static void fts5AssertComparisonResult( - Fts5Iter *pIter, + Fts5Iter *pIter, Fts5SegIter *p1, Fts5SegIter *p2, Fts5CResult *pRes @@ -211446,7 +253853,7 @@ static void fts5AssertComparisonResult( /* ** This function is a no-op unless SQLITE_DEBUG is defined when this module -** is compiled. In that case, this function is essentially an assert() +** is compiled. In that case, this function is essentially an assert() ** statement used to verify that the contents of the pIter->aFirst[] array ** are correct. */ @@ -211460,9 +253867,9 @@ static void fts5AssertMultiIterSetup(Fts5Index *p, Fts5Iter *pIter){ /* Check that pIter->iSwitchRowid is set correctly. */ for(i=0; inSeg; i++){ Fts5SegIter *p1 = &pIter->aSeg[i]; - assert( p1==pFirst - || p1->pLeaf==0 - || fts5BufferCompare(&pFirst->term, &p1->term) + assert( p1==pFirst + || p1->pLeaf==0 + || fts5BufferCompare(&pFirst->term, &p1->term) || p1->iRowid==pIter->iSwitchRowid || (p1->iRowidiSwitchRowid)==pIter->bRev ); @@ -211492,7 +253899,7 @@ static void fts5AssertMultiIterSetup(Fts5Index *p, Fts5Iter *pIter){ ** ** If the returned value is non-zero, then it is the index of an entry ** in the pIter->aSeg[] array that is (a) not at EOF, and (b) pointing -** to a key that is a duplicate of another, higher priority, +** to a key that is a duplicate of another, higher priority, ** segment-iterator in the pSeg->aSeg[] array. */ static int fts5MultiIterDoCompare(Fts5Iter *pIter, int iOut){ @@ -211528,7 +253935,6 @@ static int fts5MultiIterDoCompare(Fts5Iter *pIter, int iOut){ assert_nc( i2!=0 ); pRes->bTermEq = 1; if( p1->iRowid==p2->iRowid ){ - p1->bDel = p2->bDel; return i2; } res = ((p1->iRowid > p2->iRowid)==pIter->bRev) ? -1 : +1; @@ -211547,7 +253953,8 @@ static int fts5MultiIterDoCompare(Fts5Iter *pIter, int iOut){ /* ** Move the seg-iter so that it points to the first rowid on page iLeafPgno. -** It is an error if leaf iLeafPgno does not exist or contains no rowids. +** It is an error if leaf iLeafPgno does not exist. Unless the db is +** a 'secure-delete' db, if it contains no rowids then this is also an error. */ static void fts5SegIterGotoPage( Fts5Index *p, /* FTS5 backend object */ @@ -211557,33 +253964,35 @@ static void fts5SegIterGotoPage( assert( iLeafPgno>pIter->iLeafPgno ); if( iLeafPgno>pIter->pSeg->pgnoLast ){ - p->rc = FTS5_CORRUPT; + FTS5_CORRUPT_IDX(p); }else{ fts5DataRelease(pIter->pNextLeaf); pIter->pNextLeaf = 0; pIter->iLeafPgno = iLeafPgno-1; - fts5SegIterNextPage(p, pIter); - assert( p->rc!=SQLITE_OK || pIter->iLeafPgno==iLeafPgno ); - if( p->rc==SQLITE_OK ){ + while( p->rc==SQLITE_OK ){ int iOff; - u8 *a = pIter->pLeaf->p; - int n = pIter->pLeaf->szLeaf; - + fts5SegIterNextPage(p, pIter); + if( pIter->pLeaf==0 ) break; iOff = fts5LeafFirstRowidOff(pIter->pLeaf); - if( iOff<4 || iOff>=n ){ - p->rc = FTS5_CORRUPT; - }else{ - iOff += fts5GetVarint(&a[iOff], (u64*)&pIter->iRowid); - pIter->iLeafOffset = iOff; - fts5SegIterLoadNPos(p, pIter); + if( iOff>0 ){ + u8 *a = pIter->pLeaf->p; + int n = pIter->pLeaf->szLeaf; + if( iOff<4 || iOff>=n ){ + FTS5_CORRUPT_IDX(p); + }else{ + iOff += fts5GetVarint(&a[iOff], (u64*)&pIter->iRowid); + pIter->iLeafOffset = iOff; + fts5SegIterLoadNPos(p, pIter); + } + break; } } } } /* -** Advance the iterator passed as the second argument until it is at or +** Advance the iterator passed as the second argument until it is at or ** past rowid iFrom. Regardless of the value of iFrom, the iterator is ** always advanced at least once. */ @@ -211637,7 +254046,6 @@ static void fts5SegIterNextFrom( }while( p->rc==SQLITE_OK ); } - /* ** Free the iterator object passed as the second argument. */ @@ -211679,7 +254087,7 @@ static void fts5MultiIterAdvanced( ** If non-zero is returned, the caller should call fts5MultiIterAdvanced() ** on the iterator instead. That function does the same as this one, except ** that it deals with more complicated cases as well. -*/ +*/ static int fts5MultiIterAdvanceRowid( Fts5Iter *pIter, /* Iterator to update aFirst[] array for */ int iChanged, /* Index of sub-iterator just advanced */ @@ -211730,14 +254138,93 @@ static void fts5MultiIterSetEof(Fts5Iter *pIter){ } /* -** Move the iterator to the next entry. +** The argument to this macro must be an Fts5Data structure containing a +** tombstone hash page. This macro returns the key-size of the hash-page. +*/ +#define TOMBSTONE_KEYSIZE(pPg) (pPg->p[0]==4 ? 4 : 8) + +#define TOMBSTONE_NSLOT(pPg) \ + ((pPg->nn > 16) ? ((pPg->nn-8) / TOMBSTONE_KEYSIZE(pPg)) : 1) + +/* +** Query a single tombstone hash table for rowid iRowid. Return true if +** it is found or false otherwise. The tombstone hash table is one of +** nHashTable tables. +*/ +static int fts5IndexTombstoneQuery( + Fts5Data *pHash, /* Hash table page to query */ + int nHashTable, /* Number of pages attached to segment */ + u64 iRowid /* Rowid to query hash for */ +){ + const int szKey = TOMBSTONE_KEYSIZE(pHash); + const int nSlot = TOMBSTONE_NSLOT(pHash); + int iSlot = (iRowid / nHashTable) % nSlot; + int nCollide = nSlot; + + if( iRowid==0 ){ + return pHash->p[1]; + }else if( szKey==4 ){ + u32 *aSlot = (u32*)&pHash->p[8]; + while( aSlot[iSlot] ){ + if( fts5GetU32((u8*)&aSlot[iSlot])==iRowid ) return 1; + if( nCollide--==0 ) break; + iSlot = (iSlot+1)%nSlot; + } + }else{ + u64 *aSlot = (u64*)&pHash->p[8]; + while( aSlot[iSlot] ){ + if( fts5GetU64((u8*)&aSlot[iSlot])==iRowid ) return 1; + if( nCollide--==0 ) break; + iSlot = (iSlot+1)%nSlot; + } + } + + return 0; +} + +/* +** Return true if the iterator passed as the only argument points +** to an segment entry for which there is a tombstone. Return false +** if there is no tombstone or if the iterator is already at EOF. +*/ +static int fts5MultiIterIsDeleted(Fts5Iter *pIter){ + int iFirst = pIter->aFirst[1].iFirst; + Fts5SegIter *pSeg = &pIter->aSeg[iFirst]; + Fts5TombstoneArray *pArray = pSeg->pTombArray; + + if( pSeg->pLeaf && pArray ){ + /* Figure out which page the rowid might be present on. */ + int iPg = ((u64)pSeg->iRowid) % pArray->nTombstone; + assert( iPg>=0 ); + + /* If tombstone hash page iPg has not yet been loaded from the + ** database, load it now. */ + if( pArray->apTombstone[iPg]==0 ){ + pArray->apTombstone[iPg] = fts5DataRead(pIter->pIndex, + FTS5_TOMBSTONE_ROWID(pSeg->pSeg->iSegid, iPg) + ); + if( pArray->apTombstone[iPg]==0 ) return 0; + } + + return fts5IndexTombstoneQuery( + pArray->apTombstone[iPg], + pArray->nTombstone, + pSeg->iRowid + ); + } + + return 0; +} + +/* +** Move the iterator to the next entry. ** -** If an error occurs, an error code is left in Fts5Index.rc. It is not -** considered an error if the iterator reaches EOF, or if it is already at +** If an error occurs, an error code is left in Fts5Index.rc. It is not +** considered an error if the iterator reaches EOF, or if it is already at ** EOF when this function is called. */ static void fts5MultiIterNext( - Fts5Index *p, + Fts5Index *p, Fts5Iter *pIter, int bFrom, /* True if argument iFrom is valid */ i64 iFrom /* Advance at least as far as this */ @@ -211755,7 +254242,7 @@ static void fts5MultiIterNext( pSeg->xNext(p, pSeg, &bNewTerm); } - if( pSeg->pLeaf==0 || bNewTerm + if( pSeg->pLeaf==0 || bNewTerm || fts5MultiIterAdvanceRowid(pIter, iFirst, &pSeg) ){ fts5MultiIterAdvanced(p, pIter, iFirst, 1); @@ -211766,7 +254253,9 @@ static void fts5MultiIterNext( fts5AssertMultiIterSetup(p, pIter); assert( pSeg==&pIter->aSeg[pIter->aFirst[1].iFirst] && pSeg->pLeaf ); - if( pIter->bSkipEmpty==0 || pSeg->nPos ){ + if( (pIter->bSkipEmpty==0 || pSeg->nPos) + && 0==fts5MultiIterIsDeleted(pIter) + ){ pIter->xSetOutputs(pIter, pSeg); return; } @@ -211775,7 +254264,7 @@ static void fts5MultiIterNext( } static void fts5MultiIterNext2( - Fts5Index *p, + Fts5Index *p, Fts5Iter *pIter, int *pbNewTerm /* OUT: True if *might* be new term */ ){ @@ -211789,7 +254278,7 @@ static void fts5MultiIterNext2( assert( p->rc==SQLITE_OK ); pSeg->xNext(p, pSeg, &bNewTerm); - if( pSeg->pLeaf==0 || bNewTerm + if( pSeg->pLeaf==0 || bNewTerm || fts5MultiIterAdvanceRowid(pIter, iFirst, &pSeg) ){ fts5MultiIterAdvanced(p, pIter, iFirst, 1); @@ -211798,7 +254287,9 @@ static void fts5MultiIterNext2( } fts5AssertMultiIterSetup(p, pIter); - }while( fts5MultiIterIsEmpty(p, pIter) ); + }while( (fts5MultiIterIsEmpty(p, pIter) || fts5MultiIterIsDeleted(pIter)) + && (p->rc==SQLITE_OK) + ); } } @@ -211811,12 +254302,11 @@ static Fts5Iter *fts5MultiIterAlloc( int nSeg ){ Fts5Iter *pNew; - int nSlot; /* Power of two >= nSeg */ + i64 nSlot; /* Power of two >= nSeg */ for(nSlot=2; nSlotaSeg[] */ + pNew = fts5IdxMalloc(p, + SZ_FTS5ITER(nSlot) + /* pNew + pNew->aSeg[] */ sizeof(Fts5CResult) * nSlot /* pNew->aFirst[] */ ); if( pNew ){ @@ -211829,8 +254319,8 @@ static Fts5Iter *fts5MultiIterAlloc( } static void fts5PoslistCallback( - Fts5Index *pUnused, - void *pContext, + Fts5Index *pUnused, + void *pContext, const u8 *pChunk, int nChunk ){ UNUSED_PARAM(pUnused); @@ -211867,8 +254357,8 @@ static int fts5IndexColsetTest(Fts5Colset *pColset, int iCol){ } static void fts5PoslistOffsetsCallback( - Fts5Index *pUnused, - void *pContext, + Fts5Index *pUnused, + void *pContext, const u8 *pChunk, int nChunk ){ PoslistOffsetsCtx *pCtx = (PoslistOffsetsCtx*)pContext; @@ -211891,7 +254381,7 @@ static void fts5PoslistOffsetsCallback( static void fts5PoslistFilterCallback( Fts5Index *pUnused, - void *pContext, + void *pContext, const u8 *pChunk, int nChunk ){ PoslistCallbackCtx *pCtx = (PoslistCallbackCtx*)pContext; @@ -211916,8 +254406,7 @@ static void fts5PoslistFilterCallback( do { while( ieState ){ fts5BufferSafeAppendBlob(pCtx->pBuf, &pChunk[iStart], i-iStart); @@ -211954,7 +254443,7 @@ static void fts5ChunkIterate( int pgno = pSeg->iLeafPgno; int pgnoSave = 0; - /* This function does notmwork with detail=none databases. */ + /* This function does not work with detail=none databases. */ assert( p->pConfig->eDetail!=FTS5_DETAIL_NONE ); if( (pSeg->flags & FTS5_SEGITER_REVERSE)==0 ){ @@ -211967,6 +254456,9 @@ static void fts5ChunkIterate( fts5DataRelease(pData); if( nRem<=0 ){ break; + }else if( pSeg->pSeg==0 ){ + FTS5_CORRUPT_IDX(p); + return; }else{ pgno++; pData = fts5LeafRead(p, FTS5_SEGMENT_ROWID(pSeg->pSeg->iSegid, pgno)); @@ -211994,7 +254486,11 @@ static void fts5SegiterPoslist( Fts5Colset *pColset, Fts5Buffer *pBuf ){ + assert( pBuf!=0 ); + assert( pSeg!=0 ); if( 0==fts5BufferGrow(&p->rc, pBuf, pSeg->nPos+FTS5_DATA_ZERO_PADDING) ){ + assert( pBuf->p!=0 ); + assert( pBuf->nSpace >= pBuf->n+pSeg->nPos+FTS5_DATA_ZERO_PADDING ); memset(&pBuf->p[pBuf->n+pSeg->nPos], 0, FTS5_DATA_ZERO_PADDING); if( pColset==0 ){ fts5ChunkIterate(p, pSeg, (void*)pBuf, fts5PoslistCallback); @@ -212018,66 +254514,72 @@ static void fts5SegiterPoslist( } /* -** IN/OUT parameter (*pa) points to a position list n bytes in size. If -** the position list contains entries for column iCol, then (*pa) is set -** to point to the sub-position-list for that column and the number of -** bytes in it returned. Or, if the argument position list does not -** contain any entries for column iCol, return 0. +** Parameter pPos points to a buffer containing a position list, size nPos. +** This function filters it according to pColset (which must be non-NULL) +** and sets pIter->base.pData/nData to point to the new position list. +** If memory is required for the new position list, use buffer pIter->poslist. +** Or, if the new position list is a contiguous subset of the input, set +** pIter->base.pData/nData to point directly to it. +** +** This function is a no-op if *pRc is other than SQLITE_OK when it is +** called. If an OOM error is encountered, *pRc is set to SQLITE_NOMEM +** before returning. */ -static int fts5IndexExtractCol( - const u8 **pa, /* IN/OUT: Pointer to poslist */ - int n, /* IN: Size of poslist in bytes */ - int iCol /* Column to extract from poslist */ -){ - int iCurrent = 0; /* Anything before the first 0x01 is col 0 */ - const u8 *p = *pa; - const u8 *pEnd = &p[n]; /* One byte past end of position list */ - - while( iCol>iCurrent ){ - /* Advance pointer p until it points to pEnd or an 0x01 byte that is - ** not part of a varint. Note that it is not possible for a negative - ** or extremely large varint to occur within an uncorrupted position - ** list. So the last byte of each varint may be assumed to have a clear - ** 0x80 bit. */ - while( *p!=0x01 ){ - while( *p++ & 0x80 ); - if( p>=pEnd ) return 0; - } - *pa = p++; - iCurrent = *p++; - if( iCurrent & 0x80 ){ - p--; - p += fts5GetVarint32(p, iCurrent); - } - } - if( iCol!=iCurrent ) return 0; - - /* Advance pointer p until it points to pEnd or an 0x01 byte that is - ** not part of a varint */ - while( pnCol; i++){ - const u8 *pSub = pPos; - int nSub = fts5IndexExtractCol(&pSub, nPos, pColset->aiCol[i]); - if( nSub ){ - fts5BufferAppendBlob(pRc, pBuf, nSub, pSub); + const u8 *p = pPos; + const u8 *aCopy = p; + const u8 *pEnd = &p[nPos]; /* One byte past end of position list */ + int i = 0; + int iCurrent = 0; + + if( pColset->nCol>1 && sqlite3Fts5BufferSize(pRc, &pIter->poslist, nPos) ){ + return; + } + + while( 1 ){ + while( pColset->aiCol[i]nCol ){ + pIter->base.pData = pIter->poslist.p; + pIter->base.nData = pIter->poslist.n; + return; + } + } + + /* Advance pointer p until it points to pEnd or an 0x01 byte that is + ** not part of a varint */ + while( paiCol[i]==iCurrent ){ + if( pColset->nCol==1 ){ + pIter->base.pData = aCopy; + pIter->base.nData = p-aCopy; + return; + } + fts5BufferSafeAppendBlob(&pIter->poslist, aCopy, p-aCopy); + } + if( p>=pEnd ){ + pIter->base.pData = pIter->poslist.p; + pIter->base.nData = pIter->poslist.n; + return; + } + aCopy = p++; + iCurrent = *p++; + if( iCurrent & 0x80 ){ + p--; + p += fts5GetVarint32(p, iCurrent); } } } + } /* @@ -212101,7 +254603,7 @@ static void fts5IterSetOutputs_Nocolset(Fts5Iter *pIter, Fts5SegIter *pSeg){ assert( pIter->pColset==0 ); if( pSeg->iLeafOffset+pSeg->nPos<=pSeg->pLeaf->szLeaf ){ - /* All data is stored on the current page. Populate the output + /* All data is stored on the current page. Populate the output ** variables to point into the body of the page object. */ pIter->base.pData = &pSeg->pLeaf->p[pSeg->iLeafOffset]; }else{ @@ -212137,25 +254639,28 @@ static void fts5IterSetOutputs_Col(Fts5Iter *pIter, Fts5SegIter *pSeg){ } /* -** xSetOutputs callback used when: +** xSetOutputs callback used when: ** ** * detail=col, ** * there is a column filter, and -** * the table contains 100 or fewer columns. +** * the table contains 100 or fewer columns. ** -** The last point is to ensure all column numbers are stored as +** The last point is to ensure all column numbers are stored as ** single-byte varints. */ static void fts5IterSetOutputs_Col100(Fts5Iter *pIter, Fts5SegIter *pSeg){ assert( pIter->pIndex->pConfig->eDetail==FTS5_DETAIL_COLUMNS ); assert( pIter->pColset ); + assert( pIter->poslist.nSpace>=pIter->pIndex->pConfig->nCol ); - if( pSeg->iLeafOffset+pSeg->nPos>pSeg->pLeaf->szLeaf ){ + if( pSeg->iLeafOffset+pSeg->nPos>pSeg->pLeaf->szLeaf + || pSeg->nPos>pIter->pIndex->pConfig->nCol + ){ fts5IterSetOutputs_Col(pIter, pSeg); }else{ u8 *a = (u8*)&pSeg->pLeaf->p[pSeg->iLeafOffset]; - u8 *pEnd = (u8*)&a[pSeg->nPos]; + u8 *pEnd = (u8*)&a[pSeg->nPos]; int iPrev = 0; int *aiCol = pIter->pColset->aiCol; int *aiColEnd = &aiCol[pIter->pColset->nCol]; @@ -212194,19 +254699,12 @@ static void fts5IterSetOutputs_Full(Fts5Iter *pIter, Fts5SegIter *pSeg){ assert( pColset ); if( pSeg->iLeafOffset+pSeg->nPos<=pSeg->pLeaf->szLeaf ){ - /* All data is stored on the current page. Populate the output + /* All data is stored on the current page. Populate the output ** variables to point into the body of the page object. */ const u8 *a = &pSeg->pLeaf->p[pSeg->iLeafOffset]; - if( pColset->nCol==1 ){ - pIter->base.nData = fts5IndexExtractCol(&a, pSeg->nPos,pColset->aiCol[0]); - pIter->base.pData = a; - }else{ - int *pRc = &pIter->pIndex->rc; - fts5BufferZero(&pIter->poslist); - fts5IndexExtractColset(pRc, pColset, a, pSeg->nPos, &pIter->poslist); - pIter->base.pData = pIter->poslist.p; - pIter->base.nData = pIter->poslist.n; - } + int *pRc = &pIter->pIndex->rc; + fts5BufferZero(&pIter->poslist); + fts5IndexExtractColset(pRc, pColset, a, pSeg->nPos, pIter); }else{ /* The data is distributed over two or more pages. Copy it into the ** Fts5Iter.poslist buffer and then set the output pointer to point @@ -212219,6 +254717,7 @@ static void fts5IterSetOutputs_Full(Fts5Iter *pIter, Fts5SegIter *pSeg){ } static void fts5IterSetOutputCb(int *pRc, Fts5Iter *pIter){ + assert( pIter!=0 || (*pRc)!=SQLITE_OK ); if( *pRc==SQLITE_OK ){ Fts5Config *pConfig = pIter->pIndex->pConfig; if( pConfig->eDetail==FTS5_DETAIL_NONE ){ @@ -212249,6 +254748,32 @@ static void fts5IterSetOutputCb(int *pRc, Fts5Iter *pIter){ } } +/* +** All the component segment-iterators of pIter have been set up. This +** functions finishes setup for iterator pIter itself. +*/ +static void fts5MultiIterFinishSetup(Fts5Index *p, Fts5Iter *pIter){ + int iIter; + for(iIter=pIter->nSeg-1; iIter>0; iIter--){ + int iEq; + if( (iEq = fts5MultiIterDoCompare(pIter, iIter)) ){ + Fts5SegIter *pSeg = &pIter->aSeg[iEq]; + if( p->rc==SQLITE_OK ) pSeg->xNext(p, pSeg, 0); + fts5MultiIterAdvanced(p, pIter, iEq, iIter); + } + } + fts5MultiIterSetEof(pIter); + fts5AssertMultiIterSetup(p, pIter); + + if( (pIter->bSkipEmpty && fts5MultiIterIsEmpty(p, pIter)) + || fts5MultiIterIsDeleted(pIter) + ){ + fts5MultiIterNext(p, pIter, 0, 0); + }else if( pIter->base.bEof==0 ){ + Fts5SegIter *pSeg = &pIter->aSeg[pIter->aFirst[1].iFirst]; + pIter->xSetOutputs(pIter, pSeg); + } +} /* ** Allocate a new Fts5Iter object. @@ -212258,7 +254783,7 @@ static void fts5IterSetOutputCb(int *pRc, Fts5Iter *pIter){ ** is zero or greater, data from the first nSegment segments on level iLevel ** is merged. ** -** The iterator initially points to the first term/rowid entry in the +** The iterator initially points to the first term/rowid entry in the ** iterated data. */ static void fts5MultiIterNew( @@ -212284,13 +254809,16 @@ static void fts5MultiIterNew( if( iLevel<0 ){ assert( pStruct->nSegment==fts5StructureCountSegments(pStruct) ); nSeg = pStruct->nSegment; - nSeg += (p->pHash ? 1 : 0); + nSeg += (p->pHash && 0==(flags & FTS5INDEX_QUERY_SKIPHASH)); }else{ nSeg = MIN(pStruct->aLevel[iLevel].nSeg, nSegment); } } *ppOut = pNew = fts5MultiIterAlloc(p, nSeg); - if( pNew==0 ) return; + if( pNew==0 ){ + assert( p->rc!=SQLITE_OK ); + goto fts5MultiIterNew_post_check; + } pNew->bRev = (0!=(flags & FTS5INDEX_QUERY_DESC)); pNew->bSkipEmpty = (0!=(flags & FTS5INDEX_QUERY_SKIPEMPTY)); pNew->pColset = pColset; @@ -212302,7 +254830,7 @@ static void fts5MultiIterNew( if( p->rc==SQLITE_OK ){ if( iLevel<0 ){ Fts5StructureLevel *pEnd = &pStruct->aLevel[pStruct->nLevel]; - if( p->pHash ){ + if( p->pHash && 0==(flags & FTS5INDEX_QUERY_SKIPHASH) ){ /* Add a segment iterator for the current contents of the hash table. */ Fts5SegIter *pIter = &pNew->aSeg[iIter++]; fts5SegIterHashInit(p, pTerm, nTerm, flags, pIter); @@ -212327,33 +254855,20 @@ static void fts5MultiIterNew( assert( iIter==nSeg ); } - /* If the above was successful, each component iterators now points - ** to the first entry in its segment. In this case initialize the + /* If the above was successful, each component iterator now points + ** to the first entry in its segment. In this case initialize the ** aFirst[] array. Or, if an error has occurred, free the iterator ** object and set the output variable to NULL. */ if( p->rc==SQLITE_OK ){ - for(iIter=pNew->nSeg-1; iIter>0; iIter--){ - int iEq; - if( (iEq = fts5MultiIterDoCompare(pNew, iIter)) ){ - Fts5SegIter *pSeg = &pNew->aSeg[iEq]; - if( p->rc==SQLITE_OK ) pSeg->xNext(p, pSeg, 0); - fts5MultiIterAdvanced(p, pNew, iEq, iIter); - } - } - fts5MultiIterSetEof(pNew); - fts5AssertMultiIterSetup(p, pNew); - - if( pNew->bSkipEmpty && fts5MultiIterIsEmpty(p, pNew) ){ - fts5MultiIterNext(p, pNew, 0, 0); - }else if( pNew->base.bEof==0 ){ - Fts5SegIter *pSeg = &pNew->aSeg[pNew->aFirst[1].iFirst]; - pNew->xSetOutputs(pNew, pSeg); - } - + fts5MultiIterFinishSetup(p, pNew); }else{ fts5MultiIterFree(pNew); *ppOut = 0; } + +fts5MultiIterNew_post_check: + assert( (*ppOut)!=0 || p->rc!=SQLITE_OK ); + return; } /* @@ -212370,7 +254885,6 @@ static void fts5MultiIterNew2( pNew = fts5MultiIterAlloc(p, 2); if( pNew ){ Fts5SegIter *pIter = &pNew->aSeg[1]; - pIter->flags = FTS5_SEGITER_ONETERM; if( pData->szLeaf>0 ){ pIter->pLeaf = pData; @@ -212397,12 +254911,13 @@ static void fts5MultiIterNew2( } /* -** Return true if the iterator is at EOF or if an error has occurred. +** Return true if the iterator is at EOF or if an error has occurred. ** False otherwise. */ static int fts5MultiIterEof(Fts5Index *p, Fts5Iter *pIter){ - assert( p->rc - || (pIter->aSeg[ pIter->aFirst[1].iFirst ].pLeaf==0)==pIter->base.bEof + assert( pIter!=0 || p->rc!=SQLITE_OK ); + assert( p->rc!=SQLITE_OK + || (pIter->aSeg[ pIter->aFirst[1].iFirst ].pLeaf==0)==pIter->base.bEof ); return (p->rc || pIter->base.bEof); } @@ -212421,8 +254936,8 @@ static i64 fts5MultiIterRowid(Fts5Iter *pIter){ ** Move the iterator to the next entry at or following iMatch. */ static void fts5MultiIterNextFrom( - Fts5Index *p, - Fts5Iter *pIter, + Fts5Index *p, + Fts5Iter *pIter, i64 iMatch ){ while( 1 ){ @@ -212436,7 +254951,7 @@ static void fts5MultiIterNextFrom( } /* -** Return a pointer to a buffer containing the term associated with the +** Return a pointer to a buffer containing the term associated with the ** entry that the iterator currently points to. */ static const u8 *fts5MultiIterTerm(Fts5Iter *pIter, int *pn){ @@ -212447,11 +254962,11 @@ static const u8 *fts5MultiIterTerm(Fts5Iter *pIter, int *pn){ /* ** Allocate a new segment-id for the structure pStruct. The new segment -** id must be between 1 and 65335 inclusive, and must not be used by +** id must be between 1 and 65335 inclusive, and must not be used by ** any currently existing segment. If a free segment id cannot be found, ** SQLITE_FULL is returned. ** -** If an error has already occurred, this function is a no-op. 0 is +** If an error has already occurred, this function is a no-op. 0 is ** returned in this case. */ static int fts5AllocateSegid(Fts5Index *p, Fts5Structure *pStruct){ @@ -212516,14 +255031,17 @@ static void fts5IndexDiscardData(Fts5Index *p){ if( p->pHash ){ sqlite3Fts5HashClear(p->pHash); p->nPendingData = 0; + p->nPendingRow = 0; + p->flushRc = SQLITE_OK; } + p->nContentlessDelete = 0; } /* -** Return the size of the prefix, in bytes, that buffer +** Return the size of the prefix, in bytes, that buffer ** (pNew/) shares with buffer (pOld/nOld). ** -** Buffer (pNew/) is guaranteed to be greater +** Buffer (pNew/) is guaranteed to be greater ** than buffer (pOld/nOld). */ static int fts5PrefixCompress(int nOld, const u8 *pOld, const u8 *pNew){ @@ -212535,7 +255053,7 @@ static int fts5PrefixCompress(int nOld, const u8 *pOld, const u8 *pNew){ } static void fts5WriteDlidxClear( - Fts5Index *p, + Fts5Index *p, Fts5SegWriter *pWriter, int bFlush /* If true, write dlidx to disk */ ){ @@ -212546,7 +255064,7 @@ static void fts5WriteDlidxClear( if( pDlidx->buf.n==0 ) break; if( bFlush ){ assert( pDlidx->pgno!=0 ); - fts5DataWrite(p, + fts5DataWrite(p, FTS5_DLIDX_ROWID(pWriter->iSegid, i, pDlidx->pgno), pDlidx->buf.p, pDlidx->buf.n ); @@ -212600,8 +255118,8 @@ static int fts5WriteFlushDlidx(Fts5Index *p, Fts5SegWriter *pWriter){ } /* -** This function is called whenever processing of the doclist for the -** last term on leaf page (pWriter->iBtPage) is completed. +** This function is called whenever processing of the doclist for the +** last term on leaf page (pWriter->iBtPage) is completed. ** ** The doclist-index for that term is currently stored in-memory within the ** Fts5SegWriter.aDlidx[] array. If it is large enough, this function @@ -212686,8 +255204,8 @@ static i64 fts5DlidxExtractFirstRowid(Fts5Buffer *pBuf){ ** doclist-index. */ static void fts5WriteDlidxAppend( - Fts5Index *p, - Fts5SegWriter *pWriter, + Fts5Index *p, + Fts5SegWriter *pWriter, i64 iRowid ){ int i; @@ -212700,11 +255218,11 @@ static void fts5WriteDlidxAppend( if( pDlidx->buf.n>=p->pConfig->pgsz ){ /* The current doclist-index page is full. Write it to disk and push ** a copy of iRowid (which will become the first rowid on the next - ** doclist-index leaf page) up into the next level of the b-tree + ** doclist-index leaf page) up into the next level of the b-tree ** hierarchy. If the node being flushed is currently the root node, ** also push its first rowid upwards. */ pDlidx->buf.p[0] = 0x01; /* Not the root node */ - fts5DataWrite(p, + fts5DataWrite(p, FTS5_DLIDX_ROWID(pWriter->iSegid, i, pDlidx->pgno), pDlidx->buf.p, pDlidx->buf.n ); @@ -212730,7 +255248,7 @@ static void fts5WriteDlidxAppend( } if( pDlidx->bPrevValid ){ - iVal = iRowid - pDlidx->iPrev; + iVal = (u64)iRowid - (u64)pDlidx->iPrev; }else{ i64 iPgno = (i==0 ? pWriter->writer.pgno : pDlidx[-1].pgno); assert( pDlidx->buf.n==0 ); @@ -212788,13 +255306,13 @@ static void fts5WriteFlushLeaf(Fts5Index *p, Fts5SegWriter *pWriter){ ** Append term pTerm/nTerm to the segment being written by the writer passed ** as the second argument. ** -** If an error occurs, set the Fts5Index.rc error code. If an error has +** If an error occurs, set the Fts5Index.rc error code. If an error has ** already occurred, this function is a no-op. */ static void fts5WriteAppendTerm( - Fts5Index *p, + Fts5Index *p, Fts5SegWriter *pWriter, - int nTerm, const u8 *pTerm + int nTerm, const u8 *pTerm ){ int nPrefix; /* Bytes of prefix compression for term */ Fts5PageWriter *pPage = &pWriter->writer; @@ -212813,7 +255331,7 @@ static void fts5WriteAppendTerm( } fts5BufferGrow(&p->rc, &pPage->buf, nTerm+FTS5_DATA_PADDING); } - + /* TODO1: Updating pgidx here. */ pPgidx->n += sqlite3Fts5PutVarint( &pPgidx->p[pPgidx->n], pPage->buf.n - pPage->iPrevPgidx @@ -212829,11 +255347,11 @@ static void fts5WriteAppendTerm( if( pPage->pgno!=1 ){ /* This is the first term on a leaf that is not the leftmost leaf in ** the segment b-tree. In this case it is necessary to add a term to - ** the b-tree hierarchy that is (a) larger than the largest term + ** the b-tree hierarchy that is (a) larger than the largest term ** already written to the segment and (b) smaller than or equal to ** this term. In other words, a prefix of (pTerm/nTerm) that is one ** byte longer than the longest prefix (pTerm/nTerm) shares with the - ** previous term. + ** previous term. ** ** Usually, the previous term is available in pPage->term. The exception ** is if this is the first term written in an incremental-merge step. @@ -212870,10 +255388,10 @@ static void fts5WriteAppendTerm( } /* -** Append a rowid and position-list size field to the writers output. +** Append a rowid and position-list size field to the writers output. */ static void fts5WriteAppendRowid( - Fts5Index *p, + Fts5Index *p, Fts5SegWriter *pWriter, i64 iRowid ){ @@ -212884,7 +255402,7 @@ static void fts5WriteAppendRowid( fts5WriteFlushLeaf(p, pWriter); } - /* If this is to be the first rowid written to the page, set the + /* If this is to be the first rowid written to the page, set the ** rowid-pointer in the page-header. Also append a value to the dlidx ** buffer, in case a doclist-index is required. */ if( pWriter->bFirstRowidInPage ){ @@ -212897,7 +255415,9 @@ static void fts5WriteAppendRowid( fts5BufferAppendVarint(&p->rc, &pPage->buf, iRowid); }else{ assert_nc( p->rc || iRowid>pWriter->iPrevRowid ); - fts5BufferAppendVarint(&p->rc, &pPage->buf, iRowid - pWriter->iPrevRowid); + fts5BufferAppendVarint(&p->rc, &pPage->buf, + (u64)iRowid - (u64)pWriter->iPrevRowid + ); } pWriter->iPrevRowid = iRowid; pWriter->bFirstRowidInDoclist = 0; @@ -212906,18 +255426,18 @@ static void fts5WriteAppendRowid( } static void fts5WriteAppendPoslistData( - Fts5Index *p, - Fts5SegWriter *pWriter, - const u8 *aData, + Fts5Index *p, + Fts5SegWriter *pWriter, + const u8 *aData, int nData ){ Fts5PageWriter *pPage = &pWriter->writer; const u8 *a = aData; int n = nData; - - assert( p->pConfig->pgsz>0 ); - while( p->rc==SQLITE_OK - && (pPage->buf.n + pPage->pgidx.n + n)>=p->pConfig->pgsz + + assert( p->pConfig->pgsz>0 || p->rc!=SQLITE_OK ); + while( p->rc==SQLITE_OK + && (pPage->buf.n + pPage->pgidx.n + n)>=p->pConfig->pgsz ){ int nReq = p->pConfig->pgsz - pPage->buf.n - pPage->pgidx.n; int nCopy = 0; @@ -212940,7 +255460,7 @@ static void fts5WriteAppendPoslistData( ** allocations associated with the writer. */ static void fts5WriteFinish( - Fts5Index *p, + Fts5Index *p, Fts5SegWriter *pWriter, /* Writer object */ int *pnLeaf /* OUT: Number of leaf pages in b-tree */ ){ @@ -212968,8 +255488,8 @@ static void fts5WriteFinish( } static void fts5WriteInit( - Fts5Index *p, - Fts5SegWriter *pWriter, + Fts5Index *p, + Fts5SegWriter *pWriter, int iSegid ){ const int nBuffer = p->pConfig->pgsz + FTS5_DATA_PADDING; @@ -212992,7 +255512,7 @@ static void fts5WriteInit( if( p->pIdxWriter==0 ){ Fts5Config *pConfig = p->pConfig; fts5IndexPrepareStmt(p, &p->pIdxWriter, sqlite3_mprintf( - "INSERT INTO '%q'.'%q_idx'(segid,term,pgno) VALUES(?,?,?)", + "INSERT INTO '%q'.'%q_idx'(segid,term,pgno) VALUES(?,?,?)", pConfig->zDb, pConfig->zName )); } @@ -213043,14 +255563,14 @@ static void fts5TrimSegments(Fts5Index *p, Fts5Iter *pIter){ ** a single page has been assigned to more than one segment. In ** this case a prior iteration of this loop may have corrupted the ** segment currently being trimmed. */ - p->rc = FTS5_CORRUPT; + FTS5_CORRUPT_ROWID(p, iLeafRowid); }else{ fts5BufferZero(&buf); fts5BufferGrow(&p->rc, &buf, pData->nn); fts5BufferAppendBlob(&p->rc, &buf, sizeof(aHdr), aHdr); fts5BufferAppendVarint(&p->rc, &buf, pSeg->term.n); fts5BufferAppendBlob(&p->rc, &buf, pSeg->term.n, pSeg->term.p); - fts5BufferAppendBlob(&p->rc, &buf, pData->szLeaf-iOff,&pData->p[iOff]); + fts5BufferAppendBlob(&p->rc, &buf,pData->szLeaf-iOff,&pData->p[iOff]); if( p->rc==SQLITE_OK ){ /* Set the szLeaf field */ fts5PutU16(&buf.p[2], (u16)buf.n); @@ -213058,13 +255578,13 @@ static void fts5TrimSegments(Fts5Index *p, Fts5Iter *pIter){ /* Set up the new page-index array */ fts5BufferAppendVarint(&p->rc, &buf, 4); - if( pSeg->iLeafPgno==pSeg->iTermLeafPgno + if( pSeg->iLeafPgno==pSeg->iTermLeafPgno && pSeg->iEndofDoclistszLeaf && pSeg->iPgidxOff<=pData->nn ){ int nDiff = pData->szLeaf - pSeg->iEndofDoclist; fts5BufferAppendVarint(&p->rc, &buf, buf.n - 1 - nDiff - 4); - fts5BufferAppendBlob(&p->rc, &buf, + fts5BufferAppendBlob(&p->rc, &buf, pData->nn - pSeg->iPgidxOff, &pData->p[pSeg->iPgidxOff] ); } @@ -213081,8 +255601,8 @@ static void fts5TrimSegments(Fts5Index *p, Fts5Iter *pIter){ } static void fts5MergeChunkCallback( - Fts5Index *p, - void *pCtx, + Fts5Index *p, + void *pCtx, const u8 *pChunk, int nChunk ){ Fts5SegWriter *pWriter = (Fts5SegWriter*)pCtx; @@ -213151,6 +255671,12 @@ static void fts5IndexMergeLevel( /* Read input from all segments in the input level */ nInput = pLvl->nSeg; + + /* Set the range of origins that will go into the output segment. */ + if( pStruct->nOriginCntr>0 ){ + pSeg->iOrigin1 = pLvl->aSeg[0].iOrigin1; + pSeg->iOrigin2 = pLvl->aSeg[pLvl->nSeg-1].iOrigin2; + } } bOldest = (pLvlOut->nSeg==1 && pStruct->nLevel==iLvl+2); @@ -213205,12 +255731,16 @@ static void fts5IndexMergeLevel( ** and last leaf page number at the same time. */ fts5WriteFinish(p, &writer, &pSeg->pgnoLast); + assert( pIter!=0 || p->rc!=SQLITE_OK ); if( fts5MultiIterEof(p, pIter) ){ int i; /* Remove the redundant segments from the %_data table */ + assert( pSeg->nEntry==0 ); for(i=0; iaSeg[i].iSegid); + Fts5StructureSegment *pOld = &pLvl->aSeg[i]; + pSeg->nEntry += (pOld->nEntry - pOld->nEntryTombstone); + fts5DataRemoveSegment(p, pOld); } /* Remove the redundant segments from the input level */ @@ -213236,6 +255766,48 @@ static void fts5IndexMergeLevel( if( pnRem ) *pnRem -= writer.nLeafWritten; } +/* +** If this is not a contentless_delete=1 table, or if the 'deletemerge' +** configuration option is set to 0, then this function always returns -1. +** Otherwise, it searches the structure object passed as the second argument +** for a level suitable for merging due to having a large number of +** tombstones in the tombstone hash. If one is found, its index is returned. +** Otherwise, if there is no suitable level, -1. +*/ +static int fts5IndexFindDeleteMerge(Fts5Index *p, Fts5Structure *pStruct){ + Fts5Config *pConfig = p->pConfig; + int iRet = -1; + if( pConfig->bContentlessDelete && pConfig->nDeleteMerge>0 ){ + int ii; + int nBest = 0; + + for(ii=0; iinLevel; ii++){ + Fts5StructureLevel *pLvl = &pStruct->aLevel[ii]; + i64 nEntry = 0; + i64 nTomb = 0; + int iSeg; + for(iSeg=0; iSegnSeg; iSeg++){ + nEntry += pLvl->aSeg[iSeg].nEntry; + nTomb += pLvl->aSeg[iSeg].nEntryTombstone; + } + assert_nc( nEntry>0 || pLvl->nSeg==0 ); + if( nEntry>0 ){ + int nPercent = (nTomb * 100) / nEntry; + if( nPercent>=pConfig->nDeleteMerge && nPercent>nBest ){ + iRet = ii; + nBest = nPercent; + } + } + + /* If pLvl is already the input level to an ongoing merge, look no + ** further for a merge candidate. The caller should be allowed to + ** continue merging from pLvl first. */ + if( pLvl->nMerge ) break; + } + } + return iRet; +} + /* ** Do up to nPg pages of automerge work on the index. ** @@ -213255,14 +255827,15 @@ static int fts5IndexMerge( int iBestLvl = 0; /* Level offering the most input segments */ int nBest = 0; /* Number of input segments on best level */ - /* Set iBestLvl to the level to read input segments from. */ + /* Set iBestLvl to the level to read input segments from. Or to -1 if + ** there is no level suitable to merge segments from. */ assert( pStruct->nLevel>0 ); for(iLvl=0; iLvlnLevel; iLvl++){ Fts5StructureLevel *pLvl = &pStruct->aLevel[iLvl]; if( pLvl->nMerge ){ if( pLvl->nMerge>nBest ){ iBestLvl = iLvl; - nBest = pLvl->nMerge; + nBest = nMin; } break; } @@ -213271,22 +255844,18 @@ static int fts5IndexMerge( iBestLvl = iLvl; } } - - /* If nBest is still 0, then the index must be empty. */ -#ifdef SQLITE_DEBUG - for(iLvl=0; nBest==0 && iLvlnLevel; iLvl++){ - assert( pStruct->aLevel[iLvl].nSeg==0 ); + if( nBestaLevel[iBestLvl].nMerge==0 ){ - break; - } + if( iBestLvl<0 ) break; bRet = 1; fts5IndexMergeLevel(p, &pStruct, iBestLvl, &nRem); if( p->rc==SQLITE_OK && pStruct->aLevel[iBestLvl].nMerge==0 ){ fts5StructurePromote(p, iBestLvl+1, pStruct); } + + if( nMin==1 ) nMin = 2; } *ppStruct = pStruct; return bRet; @@ -213297,7 +255866,7 @@ static int fts5IndexMerge( ** segment. This function updates the write-counter accordingly and, if ** necessary, performs incremental merge work. ** -** If an error occurs, set the Fts5Index.rc error code. If an error has +** If an error occurs, set the Fts5Index.rc error code. If an error has ** already occurred, this function is a no-op. */ static void fts5IndexAutomerge( @@ -213305,7 +255874,7 @@ static void fts5IndexAutomerge( Fts5Structure **ppStruct, /* IN/OUT: Current structure of index */ int nLeaf /* Number of output leaves just written */ ){ - if( p->rc==SQLITE_OK && p->pConfig->nAutomerge>0 ){ + if( p->rc==SQLITE_OK && p->pConfig->nAutomerge>0 && ALWAYS((*ppStruct)!=0) ){ Fts5Structure *pStruct = *ppStruct; u64 nWrite; /* Initial value of write-counter */ int nWork; /* Number of work-quanta to perform */ @@ -213327,16 +255896,16 @@ static void fts5IndexCrisismerge( ){ const int nCrisis = p->pConfig->nCrisisMerge; Fts5Structure *pStruct = *ppStruct; - int iLvl = 0; - - assert( p->rc!=SQLITE_OK || pStruct->nLevel>0 ); - while( p->rc==SQLITE_OK && pStruct->aLevel[iLvl].nSeg>=nCrisis ){ - fts5IndexMergeLevel(p, &pStruct, iLvl, 0); - assert( p->rc!=SQLITE_OK || pStruct->nLevel>(iLvl+1) ); - fts5StructurePromote(p, iLvl+1, pStruct); - iLvl++; + if( pStruct && pStruct->nLevel>0 ){ + int iLvl = 0; + while( p->rc==SQLITE_OK && pStruct->aLevel[iLvl].nSeg>=nCrisis ){ + fts5IndexMergeLevel(p, &pStruct, iLvl, 0); + assert( p->rc!=SQLITE_OK || pStruct->nLevel>(iLvl+1) ); + fts5StructurePromote(p, iLvl+1, pStruct); + iLvl++; + } + *ppStruct = pStruct; } - *ppStruct = pStruct; } static int fts5IndexReturn(Fts5Index *p){ @@ -213345,15 +255914,23 @@ static int fts5IndexReturn(Fts5Index *p){ return rc; } +/* +** Close the read-only blob handle, if it is open. +*/ +static void sqlite3Fts5IndexCloseReader(Fts5Index *p){ + fts5IndexCloseReader(p); + fts5IndexReturn(p); +} + typedef struct Fts5FlushCtx Fts5FlushCtx; struct Fts5FlushCtx { Fts5Index *pIdx; - Fts5SegWriter writer; + Fts5SegWriter writer; }; /* ** Buffer aBuf[] contains a list of varints, all small enough to fit -** in a 32-bit integer. Return the size of the largest prefix of this +** in a 32-bit integer. Return the size of the largest prefix of this ** list nMax bytes or less in size. */ static int fts5PoslistPrefix(const u8 *aBuf, int nMax){ @@ -213371,10 +255948,505 @@ static int fts5PoslistPrefix(const u8 *aBuf, int nMax){ } /* -** Flush the contents of in-memory hash table iHash to a new level-0 +** Execute the SQL statement: +** +** DELETE FROM %_idx WHERE (segid, (pgno/2)) = ($iSegid, $iPgno); +** +** This is used when a secure-delete operation removes the last term +** from a segment leaf page. In that case the %_idx entry is removed +** too. This is done to ensure that if all instances of a token are +** removed from an fts5 database in secure-delete mode, no trace of +** the token itself remains in the database. +*/ +static void fts5SecureDeleteIdxEntry( + Fts5Index *p, /* FTS5 backend object */ + int iSegid, /* Id of segment to delete entry for */ + int iPgno /* Page number within segment */ +){ + if( iPgno!=1 ){ + assert( p->pConfig->iVersion==FTS5_CURRENT_VERSION_SECUREDELETE ); + if( p->pDeleteFromIdx==0 ){ + fts5IndexPrepareStmt(p, &p->pDeleteFromIdx, sqlite3_mprintf( + "DELETE FROM '%q'.'%q_idx' WHERE (segid, (pgno/2)) = (?1, ?2)", + p->pConfig->zDb, p->pConfig->zName + )); + } + if( p->rc==SQLITE_OK ){ + sqlite3_bind_int(p->pDeleteFromIdx, 1, iSegid); + sqlite3_bind_int(p->pDeleteFromIdx, 2, iPgno); + sqlite3_step(p->pDeleteFromIdx); + p->rc = sqlite3_reset(p->pDeleteFromIdx); + } + } +} + +/* +** This is called when a secure-delete operation removes a position-list +** that overflows onto segment page iPgno of segment pSeg. This function +** rewrites node iPgno, and possibly one or more of its right-hand peers, +** to remove this portion of the position list. +** +** Output variable (*pbLastInDoclist) is set to true if the position-list +** removed is followed by a new term or the end-of-segment, or false if +** it is followed by another rowid/position list. +*/ +static void fts5SecureDeleteOverflow( + Fts5Index *p, + Fts5StructureSegment *pSeg, + int iPgno, + int *pbLastInDoclist +){ + const int bDetailNone = (p->pConfig->eDetail==FTS5_DETAIL_NONE); + int pgno; + Fts5Data *pLeaf = 0; + assert( iPgno!=1 ); + + *pbLastInDoclist = 1; + for(pgno=iPgno; p->rc==SQLITE_OK && pgno<=pSeg->pgnoLast; pgno++){ + i64 iRowid = FTS5_SEGMENT_ROWID(pSeg->iSegid, pgno); + int iNext = 0; + u8 *aPg = 0; + + pLeaf = fts5LeafRead(p, iRowid); + if( pLeaf==0 ) break; + aPg = pLeaf->p; + + iNext = fts5GetU16(&aPg[0]); + if( iNext!=0 ){ + *pbLastInDoclist = 0; + } + if( iNext==0 && pLeaf->szLeafnn ){ + fts5GetVarint32(&aPg[pLeaf->szLeaf], iNext); + } + + if( iNext==0 ){ + /* The page contains no terms or rowids. Replace it with an empty + ** page and move on to the right-hand peer. */ + const u8 aEmpty[] = {0x00, 0x00, 0x00, 0x04}; + assert_nc( bDetailNone==0 || pLeaf->nn==4 ); + if( bDetailNone==0 ) fts5DataWrite(p, iRowid, aEmpty, sizeof(aEmpty)); + fts5DataRelease(pLeaf); + pLeaf = 0; + }else if( bDetailNone ){ + break; + }else if( iNext>=pLeaf->szLeaf || pLeaf->nnszLeaf || iNext<4 ){ + FTS5_CORRUPT_ROWID(p, iRowid); + break; + }else{ + int nShift = iNext - 4; + int nPg; + + int nIdx = 0; + u8 *aIdx = 0; + + /* Unless the current page footer is 0 bytes in size (in which case + ** the new page footer will be as well), allocate and populate a + ** buffer containing the new page footer. Set stack variables aIdx + ** and nIdx accordingly. */ + if( pLeaf->nn>pLeaf->szLeaf ){ + int iFirst = 0; + int i1 = pLeaf->szLeaf; + int i2 = 0; + + i1 += fts5GetVarint32(&aPg[i1], iFirst); + if( iFirstrc, (pLeaf->nn-pLeaf->szLeaf)+2); + if( aIdx==0 ) break; + i2 = sqlite3Fts5PutVarint(aIdx, iFirst-nShift); + if( i1nn ){ + memcpy(&aIdx[i2], &aPg[i1], pLeaf->nn-i1); + i2 += (pLeaf->nn-i1); + } + nIdx = i2; + } + + /* Modify the contents of buffer aPg[]. Set nPg to the new size + ** in bytes. The new page is always smaller than the old. */ + nPg = pLeaf->szLeaf - nShift; + memmove(&aPg[4], &aPg[4+nShift], nPg-4); + fts5PutU16(&aPg[2], nPg); + if( fts5GetU16(&aPg[0]) ) fts5PutU16(&aPg[0], 4); + if( nIdx>0 ){ + memcpy(&aPg[nPg], aIdx, nIdx); + nPg += nIdx; + } + sqlite3_free(aIdx); + + /* Write the new page to disk and exit the loop */ + assert( nPg>4 || fts5GetU16(aPg)==0 ); + fts5DataWrite(p, iRowid, aPg, nPg); + break; + } + } + fts5DataRelease(pLeaf); +} + +/* +** Completely remove the entry that pSeg currently points to from +** the database. +*/ +static void fts5DoSecureDelete( + Fts5Index *p, + Fts5SegIter *pSeg +){ + const int bDetailNone = (p->pConfig->eDetail==FTS5_DETAIL_NONE); + int iSegid = pSeg->pSeg->iSegid; + u8 *aPg = pSeg->pLeaf->p; + int nPg = pSeg->pLeaf->nn; + int iPgIdx = pSeg->pLeaf->szLeaf; /* Offset of page footer */ + + u64 iDelta = 0; + int iNextOff = 0; + int iOff = 0; + int nIdx = 0; + u8 *aIdx = 0; + int bLastInDoclist = 0; + int iIdx = 0; + int iStart = 0; + int iDelKeyOff = 0; /* Offset of deleted key, if any */ + + nIdx = nPg-iPgIdx; + aIdx = sqlite3Fts5MallocZero(&p->rc, ((i64)nIdx)+16); + if( p->rc ) return; + memcpy(aIdx, &aPg[iPgIdx], nIdx); + + /* At this point segment iterator pSeg points to the entry + ** this function should remove from the b-tree segment. + ** + ** In detail=full or detail=column mode, pSeg->iLeafOffset is the + ** offset of the first byte in the position-list for the entry to + ** remove. Immediately before this comes two varints that will also + ** need to be removed: + ** + ** + the rowid or delta rowid value for the entry, and + ** + the size of the position list in bytes. + ** + ** Or, in detail=none mode, there is a single varint prior to + ** pSeg->iLeafOffset - the rowid or delta rowid value. + ** + ** This block sets the following variables: + ** + ** iStart: + ** The offset of the first byte of the rowid or delta-rowid + ** value for the doclist entry being removed. + ** + ** iDelta: + ** The value of the rowid or delta-rowid value for the doclist + ** entry being removed. + ** + ** iNextOff: + ** The offset of the next entry following the position list + ** for the one being removed. If the position list for this + ** entry overflows onto the next leaf page, this value will be + ** greater than pLeaf->szLeaf. + */ + { + int iSOP; /* Start-Of-Position-list */ + if( pSeg->iLeafPgno==pSeg->iTermLeafPgno ){ + iStart = pSeg->iTermLeafOffset; + }else{ + iStart = fts5GetU16(&aPg[0]); + } + if( iStart>nPg ){ + FTS5_CORRUPT_IDX(p); + sqlite3_free(aIdx); + return; + } + + iSOP = iStart + fts5GetVarint(&aPg[iStart], &iDelta); + assert_nc( iSOP<=pSeg->iLeafOffset ); + + if( bDetailNone ){ + while( iSOPiLeafOffset ){ + if( aPg[iSOP]==0x00 ) iSOP++; + if( aPg[iSOP]==0x00 ) iSOP++; + iStart = iSOP; + iSOP = iStart + fts5GetVarint(&aPg[iStart], &iDelta); + } + + iNextOff = iSOP; + if( iNextOffiEndofDoclist && aPg[iNextOff]==0x00 ) iNextOff++; + if( iNextOffiEndofDoclist && aPg[iNextOff]==0x00 ) iNextOff++; + + }else{ + int nPos = 0; + iSOP += fts5GetVarint32(&aPg[iSOP], nPos); + while( iSOPiLeafOffset ){ + iStart = iSOP + (nPos/2); + iSOP = iStart + fts5GetVarint(&aPg[iStart], &iDelta); + iSOP += fts5GetVarint32(&aPg[iSOP], nPos); + } + assert_nc( iSOP==pSeg->iLeafOffset ); + iNextOff = iSOP + pSeg->nPos; + } + } + + iOff = iStart; + + /* If the position-list for the entry being removed flows over past + ** the end of this page, delete the portion of the position-list on the + ** next page and beyond. + ** + ** Set variable bLastInDoclist to true if this entry happens + ** to be the last rowid in the doclist for its term. */ + if( iNextOff>=iPgIdx ){ + int pgno = pSeg->iLeafPgno+1; + fts5SecureDeleteOverflow(p, pSeg->pSeg, pgno, &bLastInDoclist); + iNextOff = iPgIdx; + } + + if( pSeg->bDel==0 ){ + if( iNextOff!=iPgIdx ){ + /* Loop through the page-footer. If iNextOff (offset of the + ** entry following the one we are removing) is equal to the + ** offset of a key on this page, then the entry is the last + ** in its doclist. */ + int iKeyOff = 0; + for(iIdx=0; iIdxbDel ){ + iOff += sqlite3Fts5PutVarint(&aPg[iOff], iDelta); + aPg[iOff++] = 0x01; + }else if( bLastInDoclist==0 ){ + if( iNextOff!=iPgIdx ){ + u64 iNextDelta = 0; + iNextOff += fts5GetVarint(&aPg[iNextOff], &iNextDelta); + iOff += sqlite3Fts5PutVarint(&aPg[iOff], iDelta + iNextDelta); + } + }else if( + pSeg->iLeafPgno==pSeg->iTermLeafPgno + && iStart==pSeg->iTermLeafOffset + ){ + /* The entry being removed was the only position list in its + ** doclist. Therefore the term needs to be removed as well. */ + int iKey = 0; + int iKeyOff = 0; + + /* Set iKeyOff to the offset of the term that will be removed - the + ** last offset in the footer that is not greater than iStart. */ + for(iIdx=0; iIdx(u32)iStart ) break; + iKeyOff += iVal; + } + assert_nc( iKey>=1 ); + + /* Set iDelKeyOff to the value of the footer entry to remove from + ** the page. */ + iDelKeyOff = iOff = iKeyOff; + + if( iNextOff!=iPgIdx ){ + /* This is the only position-list associated with the term, and there + ** is another term following it on this page. So the subsequent term + ** needs to be moved to replace the term associated with the entry + ** being removed. */ + u64 nPrefix = 0; + u64 nSuffix = 0; + u64 nPrefix2 = 0; + u64 nSuffix2 = 0; + + iDelKeyOff = iNextOff; + iNextOff += fts5GetVarint(&aPg[iNextOff], &nPrefix2); + iNextOff += fts5GetVarint(&aPg[iNextOff], &nSuffix2); + + if( iKey!=1 ){ + iKeyOff += fts5GetVarint(&aPg[iKeyOff], &nPrefix); + } + iKeyOff += fts5GetVarint(&aPg[iKeyOff], &nSuffix); + + nPrefix = MIN(nPrefix, nPrefix2); + nSuffix = (nPrefix2 + nSuffix2) - nPrefix; + + if( (iKeyOff+nSuffix)>(u64)iPgIdx || (iNextOff+nSuffix2)>(u64)iPgIdx ){ + FTS5_CORRUPT_IDX(p); + }else{ + if( iKey!=1 ){ + iOff += sqlite3Fts5PutVarint(&aPg[iOff], nPrefix); + } + iOff += sqlite3Fts5PutVarint(&aPg[iOff], nSuffix); + if( nPrefix2>(u64)pSeg->term.n ){ + FTS5_CORRUPT_IDX(p); + }else if( nPrefix2>nPrefix ){ + memcpy(&aPg[iOff], &pSeg->term.p[nPrefix], nPrefix2-nPrefix); + iOff += (nPrefix2-nPrefix); + } + memmove(&aPg[iOff], &aPg[iNextOff], nSuffix2); + iOff += nSuffix2; + iNextOff += nSuffix2; + } + } + }else if( iStart==4 ){ + int iPgno; + + assert_nc( pSeg->iLeafPgno>pSeg->iTermLeafPgno ); + /* The entry being removed may be the only position list in + ** its doclist. */ + for(iPgno=pSeg->iLeafPgno-1; iPgno>pSeg->iTermLeafPgno; iPgno-- ){ + Fts5Data *pPg = fts5LeafRead(p, FTS5_SEGMENT_ROWID(iSegid, iPgno)); + int bEmpty = (pPg && pPg->nn==4); + fts5DataRelease(pPg); + if( bEmpty==0 ) break; + } + + if( iPgno==pSeg->iTermLeafPgno ){ + i64 iId = FTS5_SEGMENT_ROWID(iSegid, pSeg->iTermLeafPgno); + Fts5Data *pTerm = fts5LeafRead(p, iId); + if( pTerm && pTerm->szLeaf==pSeg->iTermLeafOffset ){ + u8 *aTermIdx = &pTerm->p[pTerm->szLeaf]; + int nTermIdx = pTerm->nn - pTerm->szLeaf; + int iTermIdx = 0; + i64 iTermOff = 0; + + while( 1 ){ + u32 iVal = 0; + int nByte = fts5GetVarint32(&aTermIdx[iTermIdx], iVal); + iTermOff += iVal; + if( (iTermIdx+nByte)>=nTermIdx ) break; + iTermIdx += nByte; + } + nTermIdx = iTermIdx; + + if( iTermOff>pTerm->szLeaf ){ + FTS5_CORRUPT_IDX(p); + }else{ + memmove(&pTerm->p[iTermOff], &pTerm->p[pTerm->szLeaf], nTermIdx); + fts5PutU16(&pTerm->p[2], iTermOff); + fts5DataWrite(p, iId, pTerm->p, iTermOff+nTermIdx); + if( nTermIdx==0 ){ + fts5SecureDeleteIdxEntry(p, iSegid, pSeg->iTermLeafPgno); + } + } + } + fts5DataRelease(pTerm); + } + } + + /* Assuming no error has occurred, this block does final edits to the + ** leaf page before writing it back to disk. Input variables are: + ** + ** nPg: Total initial size of leaf page. + ** iPgIdx: Initial offset of page footer. + ** + ** iOff: Offset to move data to + ** iNextOff: Offset to move data from + */ + if( p->rc==SQLITE_OK ){ + const int nMove = nPg - iNextOff; /* Number of bytes to move */ + int nShift = iNextOff - iOff; /* Distance to move them */ + + int iPrevKeyOut = 0; + int iKeyIn = 0; + + if( nMove>0 ){ + memmove(&aPg[iOff], &aPg[iNextOff], nMove); + } + iPgIdx -= nShift; + nPg = iPgIdx; + fts5PutU16(&aPg[2], iPgIdx); + + for(iIdx=0; iIdxiOff ? nShift : 0)); + nPg += sqlite3Fts5PutVarint(&aPg[nPg], iKeyOut - iPrevKeyOut); + iPrevKeyOut = iKeyOut; + } + } + + if( iPgIdx==nPg && nIdx>0 && pSeg->iLeafPgno!=1 ){ + fts5SecureDeleteIdxEntry(p, iSegid, pSeg->iLeafPgno); + } + + assert_nc( nPg>4 || fts5GetU16(aPg)==0 ); + fts5DataWrite(p, FTS5_SEGMENT_ROWID(iSegid,pSeg->iLeafPgno), aPg, nPg); + } + sqlite3_free(aIdx); +} + +/* +** This is called as part of flushing a delete to disk in 'secure-delete' +** mode. It edits the segments within the database described by argument +** pStruct to remove the entries for term zTerm, rowid iRowid. +** +** Return SQLITE_OK if successful, or an SQLite error code if an error +** has occurred. Any error code is also stored in the Fts5Index handle. +*/ +static int fts5FlushSecureDelete( + Fts5Index *p, + Fts5Structure *pStruct, + const char *zTerm, + int nTerm, + i64 iRowid +){ + const int f = FTS5INDEX_QUERY_SKIPHASH; + Fts5Iter *pIter = 0; /* Used to find term instance */ + + /* If the version number has not been set to SECUREDELETE, do so now. */ + if( p->pConfig->iVersion!=FTS5_CURRENT_VERSION_SECUREDELETE ){ + Fts5Config *pConfig = p->pConfig; + sqlite3_stmt *pStmt = 0; + fts5IndexPrepareStmt(p, &pStmt, sqlite3_mprintf( + "REPLACE INTO %Q.'%q_config' VALUES ('version', %d)", + pConfig->zDb, pConfig->zName, FTS5_CURRENT_VERSION_SECUREDELETE + )); + if( p->rc==SQLITE_OK ){ + int rc; + sqlite3_step(pStmt); + rc = sqlite3_finalize(pStmt); + if( p->rc==SQLITE_OK ) p->rc = rc; + pConfig->iCookie++; + pConfig->iVersion = FTS5_CURRENT_VERSION_SECUREDELETE; + } + } + + fts5MultiIterNew(p, pStruct, f, 0, (const u8*)zTerm, nTerm, -1, 0, &pIter); + if( fts5MultiIterEof(p, pIter)==0 ){ + i64 iThis = fts5MultiIterRowid(pIter); + if( iThisrc==SQLITE_OK + && fts5MultiIterEof(p, pIter)==0 + && iRowid==fts5MultiIterRowid(pIter) + ){ + Fts5SegIter *pSeg = &pIter->aSeg[pIter->aFirst[1].iFirst]; + fts5DoSecureDelete(p, pSeg); + } + } + + fts5MultiIterFree(pIter); + return p->rc; +} + + +/* +** Flush the contents of in-memory hash table iHash to a new level-0 ** segment on disk. Also update the corresponding structure record. ** -** If an error occurs, set the Fts5Index.rc error code. If an error has +** If an error occurs, set the Fts5Index.rc error code. If an error has ** already occurred, this function is a no-op. */ static void fts5FlushOneHash(Fts5Index *p){ @@ -213386,143 +256458,199 @@ static void fts5FlushOneHash(Fts5Index *p){ /* Obtain a reference to the index structure and allocate a new segment-id ** for the new level-0 segment. */ pStruct = fts5StructureRead(p); - iSegid = fts5AllocateSegid(p, pStruct); fts5StructureInvalidate(p); - if( iSegid ){ - const int pgsz = p->pConfig->pgsz; - int eDetail = p->pConfig->eDetail; - Fts5StructureSegment *pSeg; /* New segment within pStruct */ - Fts5Buffer *pBuf; /* Buffer in which to assemble leaf page */ - Fts5Buffer *pPgidx; /* Buffer in which to assemble pgidx */ - - Fts5SegWriter writer; - fts5WriteInit(p, &writer, iSegid); + if( sqlite3Fts5HashIsEmpty(pHash)==0 ){ + iSegid = fts5AllocateSegid(p, pStruct); + if( iSegid ){ + const int pgsz = p->pConfig->pgsz; + int eDetail = p->pConfig->eDetail; + int bSecureDelete = p->pConfig->bSecureDelete; + Fts5StructureSegment *pSeg; /* New segment within pStruct */ + Fts5Buffer *pBuf; /* Buffer in which to assemble leaf page */ + Fts5Buffer *pPgidx; /* Buffer in which to assemble pgidx */ + + Fts5SegWriter writer; + fts5WriteInit(p, &writer, iSegid); + + pBuf = &writer.writer.buf; + pPgidx = &writer.writer.pgidx; + + /* fts5WriteInit() should have initialized the buffers to (most likely) + ** the maximum space required. */ + assert( p->rc || pBuf->nSpace>=(pgsz + FTS5_DATA_PADDING) ); + assert( p->rc || pPgidx->nSpace>=(pgsz + FTS5_DATA_PADDING) ); + + /* Begin scanning through hash table entries. This loop runs once for each + ** term/doclist currently stored within the hash table. */ + if( p->rc==SQLITE_OK ){ + p->rc = sqlite3Fts5HashScanInit(pHash, 0, 0); + } + while( p->rc==SQLITE_OK && 0==sqlite3Fts5HashScanEof(pHash) ){ + const char *zTerm; /* Buffer containing term */ + int nTerm; /* Size of zTerm in bytes */ + const u8 *pDoclist; /* Pointer to doclist for this term */ + int nDoclist; /* Size of doclist in bytes */ + + /* Get the term and doclist for this entry. */ + sqlite3Fts5HashScanEntry(pHash, &zTerm, &nTerm, &pDoclist, &nDoclist); + if( bSecureDelete==0 ){ + fts5WriteAppendTerm(p, &writer, nTerm, (const u8*)zTerm); + if( p->rc!=SQLITE_OK ) break; + assert( writer.bFirstRowidInPage==0 ); + } - pBuf = &writer.writer.buf; - pPgidx = &writer.writer.pgidx; + if( !bSecureDelete && pgsz>=(pBuf->n + pPgidx->n + nDoclist + 1) ){ + /* The entire doclist will fit on the current leaf. */ + fts5BufferSafeAppendBlob(pBuf, pDoclist, nDoclist); + }else{ + int bTermWritten = !bSecureDelete; + i64 iRowid = 0; + i64 iPrev = 0; + int iOff = 0; + + /* The entire doclist will not fit on this leaf. The following + ** loop iterates through the poslists that make up the current + ** doclist. */ + while( p->rc==SQLITE_OK && iOffrc!=SQLITE_OK || pDoclist[iOff]==0x01 ){ + iOff++; + continue; + } + } + } - /* fts5WriteInit() should have initialized the buffers to (most likely) - ** the maximum space required. */ - assert( p->rc || pBuf->nSpace>=(pgsz + FTS5_DATA_PADDING) ); - assert( p->rc || pPgidx->nSpace>=(pgsz + FTS5_DATA_PADDING) ); + if( p->rc==SQLITE_OK && bTermWritten==0 ){ + fts5WriteAppendTerm(p, &writer, nTerm, (const u8*)zTerm); + bTermWritten = 1; + assert( p->rc!=SQLITE_OK || writer.bFirstRowidInPage==0 ); + } - /* Begin scanning through hash table entries. This loop runs once for each - ** term/doclist currently stored within the hash table. */ - if( p->rc==SQLITE_OK ){ - p->rc = sqlite3Fts5HashScanInit(pHash, 0, 0); - } - while( p->rc==SQLITE_OK && 0==sqlite3Fts5HashScanEof(pHash) ){ - const char *zTerm; /* Buffer containing term */ - const u8 *pDoclist; /* Pointer to doclist for this term */ - int nDoclist; /* Size of doclist in bytes */ - - /* Write the term for this entry to disk. */ - sqlite3Fts5HashScanEntry(pHash, &zTerm, &pDoclist, &nDoclist); - fts5WriteAppendTerm(p, &writer, (int)strlen(zTerm), (const u8*)zTerm); - if( p->rc!=SQLITE_OK ) break; - - assert( writer.bFirstRowidInPage==0 ); - if( pgsz>=(pBuf->n + pPgidx->n + nDoclist + 1) ){ - /* The entire doclist will fit on the current leaf. */ - fts5BufferSafeAppendBlob(pBuf, pDoclist, nDoclist); - }else{ - i64 iRowid = 0; - i64 iDelta = 0; - int iOff = 0; - - /* The entire doclist will not fit on this leaf. The following - ** loop iterates through the poslists that make up the current - ** doclist. */ - while( p->rc==SQLITE_OK && iOffp[0], (u16)pBuf->n); /* first rowid on page */ - pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], iRowid); - writer.bFirstRowidInPage = 0; - fts5WriteDlidxAppend(p, &writer, iRowid); + if( writer.bFirstRowidInPage ){ + fts5PutU16(&pBuf->p[0], (u16)pBuf->n); /* first rowid on page */ + pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], iRowid); + writer.bFirstRowidInPage = 0; + fts5WriteDlidxAppend(p, &writer, iRowid); + }else{ + u64 iRowidDelta = (u64)iRowid - (u64)iPrev; + pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], iRowidDelta); + } if( p->rc!=SQLITE_OK ) break; - }else{ - pBuf->n += sqlite3Fts5PutVarint(&pBuf->p[pBuf->n], iDelta); - } - assert( pBuf->n<=pBuf->nSpace ); + assert( pBuf->n<=pBuf->nSpace ); + iPrev = iRowid; - if( eDetail==FTS5_DETAIL_NONE ){ - if( iOffp[pBuf->n++] = 0; - iOff++; + if( eDetail==FTS5_DETAIL_NONE ){ if( iOffp[pBuf->n++] = 0; iOff++; + if( iOffp[pBuf->n++] = 0; + iOff++; + } + } + if( (pBuf->n + pPgidx->n)>=pgsz ){ + fts5WriteFlushLeaf(p, &writer); } - } - if( (pBuf->n + pPgidx->n)>=pgsz ){ - fts5WriteFlushLeaf(p, &writer); - } - }else{ - int bDummy; - int nPos; - int nCopy = fts5GetPoslistSize(&pDoclist[iOff], &nPos, &bDummy); - nCopy += nPos; - if( (pBuf->n + pPgidx->n + nCopy) <= pgsz ){ - /* The entire poslist will fit on the current leaf. So copy - ** it in one go. */ - fts5BufferSafeAppendBlob(pBuf, &pDoclist[iOff], nCopy); }else{ - /* The entire poslist will not fit on this leaf. So it needs - ** to be broken into sections. The only qualification being - ** that each varint must be stored contiguously. */ - const u8 *pPoslist = &pDoclist[iOff]; - int iPos = 0; - while( p->rc==SQLITE_OK ){ - int nSpace = pgsz - pBuf->n - pPgidx->n; - int n = 0; - if( (nCopy - iPos)<=nSpace ){ - n = nCopy - iPos; - }else{ - n = fts5PoslistPrefix(&pPoslist[iPos], nSpace); - } - assert( n>0 ); - fts5BufferSafeAppendBlob(pBuf, &pPoslist[iPos], n); - iPos += n; - if( (pBuf->n + pPgidx->n)>=pgsz ){ - fts5WriteFlushLeaf(p, &writer); + int bDel = 0; + int nPos = 0; + int nCopy = fts5GetPoslistSize(&pDoclist[iOff], &nPos, &bDel); + if( bDel && bSecureDelete ){ + fts5BufferAppendVarint(&p->rc, pBuf, nPos*2); + iOff += nCopy; + nCopy = nPos; + }else{ + nCopy += nPos; + } + if( (pBuf->n + pPgidx->n + nCopy) <= pgsz ){ + /* The entire poslist will fit on the current leaf. So copy + ** it in one go. */ + fts5BufferSafeAppendBlob(pBuf, &pDoclist[iOff], nCopy); + }else{ + /* The entire poslist will not fit on this leaf. So it needs + ** to be broken into sections. The only qualification being + ** that each varint must be stored contiguously. */ + const u8 *pPoslist = &pDoclist[iOff]; + int iPos = 0; + while( p->rc==SQLITE_OK ){ + int nSpace = pgsz - pBuf->n - pPgidx->n; + int n = 0; + if( (nCopy - iPos)<=nSpace ){ + n = nCopy - iPos; + }else{ + n = fts5PoslistPrefix(&pPoslist[iPos], nSpace); + } + assert( n>0 ); + fts5BufferSafeAppendBlob(pBuf, &pPoslist[iPos], n); + iPos += n; + if( (pBuf->n + pPgidx->n)>=pgsz ){ + fts5WriteFlushLeaf(p, &writer); + } + if( iPos>=nCopy ) break; } - if( iPos>=nCopy ) break; } + iOff += nCopy; } - iOff += nCopy; } } - } - /* TODO2: Doclist terminator written here. */ - /* pBuf->p[pBuf->n++] = '\0'; */ - assert( pBuf->n<=pBuf->nSpace ); - if( p->rc==SQLITE_OK ) sqlite3Fts5HashScanNext(pHash); - } - sqlite3Fts5HashClear(pHash); - fts5WriteFinish(p, &writer, &pgnoLast); + /* TODO2: Doclist terminator written here. */ + /* pBuf->p[pBuf->n++] = '\0'; */ + assert( pBuf->n<=pBuf->nSpace ); + if( p->rc==SQLITE_OK ) sqlite3Fts5HashScanNext(pHash); + } + fts5WriteFinish(p, &writer, &pgnoLast); - /* Update the Fts5Structure. It is written back to the database by the - ** fts5StructureRelease() call below. */ - if( pStruct->nLevel==0 ){ - fts5StructureAddLevel(&p->rc, &pStruct); - } - fts5StructureExtendLevel(&p->rc, pStruct, 0, 1, 0); - if( p->rc==SQLITE_OK ){ - pSeg = &pStruct->aLevel[0].aSeg[ pStruct->aLevel[0].nSeg++ ]; - pSeg->iSegid = iSegid; - pSeg->pgnoFirst = 1; - pSeg->pgnoLast = pgnoLast; - pStruct->nSegment++; + assert( p->rc!=SQLITE_OK || bSecureDelete || pgnoLast>0 ); + if( pgnoLast>0 ){ + /* Update the Fts5Structure. It is written back to the database by the + ** fts5StructureRelease() call below. */ + if( pStruct->nLevel==0 ){ + fts5StructureAddLevel(&p->rc, &pStruct); + } + fts5StructureExtendLevel(&p->rc, pStruct, 0, 1, 0); + if( p->rc==SQLITE_OK ){ + pSeg = &pStruct->aLevel[0].aSeg[ pStruct->aLevel[0].nSeg++ ]; + pSeg->iSegid = iSegid; + pSeg->pgnoFirst = 1; + pSeg->pgnoLast = pgnoLast; + if( pStruct->nOriginCntr>0 ){ + pSeg->iOrigin1 = pStruct->nOriginCntr; + pSeg->iOrigin2 = pStruct->nOriginCntr; + pSeg->nEntry = p->nPendingRow; + pStruct->nOriginCntr++; + } + pStruct->nSegment++; + } + fts5StructurePromote(p, 0, pStruct); + } } - fts5StructurePromote(p, 0, pStruct); } - fts5IndexAutomerge(p, &pStruct, pgnoLast); + fts5IndexAutomerge(p, &pStruct, pgnoLast + p->nContentlessDelete); fts5IndexCrisismerge(p, &pStruct); fts5StructureWrite(p, pStruct); fts5StructureRelease(pStruct); @@ -213533,52 +256661,70 @@ static void fts5FlushOneHash(Fts5Index *p){ */ static void fts5IndexFlush(Fts5Index *p){ /* Unless it is empty, flush the hash table to disk */ - if( p->nPendingData ){ + if( p->flushRc ){ + p->rc = p->flushRc; + return; + } + if( p->nPendingData || p->nContentlessDelete ){ assert( p->pHash ); - p->nPendingData = 0; fts5FlushOneHash(p); + if( p->rc==SQLITE_OK ){ + sqlite3Fts5HashClear(p->pHash); + p->nPendingData = 0; + p->nPendingRow = 0; + p->nContentlessDelete = 0; + }else if( p->nPendingData || p->nContentlessDelete ){ + p->flushRc = p->rc; + } } } static Fts5Structure *fts5IndexOptimizeStruct( - Fts5Index *p, + Fts5Index *p, Fts5Structure *pStruct ){ Fts5Structure *pNew = 0; - sqlite3_int64 nByte = sizeof(Fts5Structure); + sqlite3_int64 nByte = SZ_FTS5STRUCTURE(1); int nSeg = pStruct->nSegment; int i; /* Figure out if this structure requires optimization. A structure does ** not require optimization if either: ** - ** + it consists of fewer than two segments, or - ** + all segments are on the same level, or - ** + all segments except one are currently inputs to a merge operation. + ** 1. it consists of fewer than two segments, or + ** 2. all segments are on the same level, or + ** 3. all segments except one are currently inputs to a merge operation. ** - ** In the first case, return NULL. In the second, increment the ref-count - ** on *pStruct and return a copy of the pointer to it. + ** In the first case, if there are no tombstone hash pages, return NULL. In + ** the second, increment the ref-count on *pStruct and return a copy of the + ** pointer to it. */ - if( nSeg<2 ) return 0; + if( nSeg==0 ) return 0; for(i=0; inLevel; i++){ int nThis = pStruct->aLevel[i].nSeg; - if( nThis==nSeg || (nThis==nSeg-1 && pStruct->aLevel[i].nMerge==nThis) ){ + int nMerge = pStruct->aLevel[i].nMerge; + if( nThis>0 && (nThis==nSeg || (nThis==nSeg-1 && nMerge==nThis)) ){ + if( nSeg==1 && nThis==1 && pStruct->aLevel[i].aSeg[0].nPgTombstone==0 ){ + return 0; + } fts5StructureRef(pStruct); return pStruct; } assert( pStruct->aLevel[i].nMerge<=nThis ); } - nByte += (pStruct->nLevel+1) * sizeof(Fts5StructureLevel); + nByte += (((i64)pStruct->nLevel)+1) * sizeof(Fts5StructureLevel); + assert( nByte==(i64)SZ_FTS5STRUCTURE(pStruct->nLevel+2) ); pNew = (Fts5Structure*)sqlite3Fts5MallocZero(&p->rc, nByte); if( pNew ){ Fts5StructureLevel *pLvl; nByte = nSeg * sizeof(Fts5StructureSegment); - pNew->nLevel = pStruct->nLevel+1; + pNew->nLevel = MIN(pStruct->nLevel+1, FTS5_MAX_LEVEL); pNew->nRef = 1; pNew->nWriteCounter = pStruct->nWriteCounter; - pLvl = &pNew->aLevel[pStruct->nLevel]; + pNew->nOriginCntr = pStruct->nOriginCntr; + pLvl = &pNew->aLevel[pNew->nLevel-1]; pLvl->aSeg = (Fts5StructureSegment*)sqlite3Fts5MallocZero(&p->rc, nByte); if( pLvl->aSeg ){ int iLvl, iSeg; @@ -213608,7 +256754,9 @@ static int sqlite3Fts5IndexOptimize(Fts5Index *p){ assert( p->rc==SQLITE_OK ); fts5IndexFlush(p); + assert( p->rc!=SQLITE_OK || p->nContentlessDelete==0 ); pStruct = fts5StructureRead(p); + assert( p->rc!=SQLITE_OK || pStruct!=0 ); fts5StructureInvalidate(p); if( pStruct ){ @@ -213629,7 +256777,7 @@ static int sqlite3Fts5IndexOptimize(Fts5Index *p){ fts5StructureRelease(pNew); } - return fts5IndexReturn(p); + return fts5IndexReturn(p); } /* @@ -213637,7 +256785,10 @@ static int sqlite3Fts5IndexOptimize(Fts5Index *p){ ** INSERT command. */ static int sqlite3Fts5IndexMerge(Fts5Index *p, int nMerge){ - Fts5Structure *pStruct = fts5StructureRead(p); + Fts5Structure *pStruct = 0; + + fts5IndexFlush(p); + pStruct = fts5StructureRead(p); if( pStruct ){ int nMin = p->pConfig->nUsermerge; fts5StructureInvalidate(p); @@ -213645,8 +256796,8 @@ static int sqlite3Fts5IndexMerge(Fts5Index *p, int nMerge){ Fts5Structure *pNew = fts5IndexOptimizeStruct(p, pStruct); fts5StructureRelease(pStruct); pStruct = pNew; - nMin = 2; - nMerge = nMerge*-1; + nMin = 1; + nMerge = (nMerge==SMALLEST_INT32 ? LARGEST_INT32 : (nMerge*-1)); } if( pStruct && pStruct->nLevel ){ if( fts5IndexMerge(p, &pStruct, nMerge, nMin) ){ @@ -213660,7 +256811,7 @@ static int sqlite3Fts5IndexMerge(Fts5Index *p, int nMerge){ static void fts5AppendRowid( Fts5Index *p, - i64 iDelta, + u64 iDelta, Fts5Iter *pUnused, Fts5Buffer *pBuf ){ @@ -213670,7 +256821,7 @@ static void fts5AppendRowid( static void fts5AppendPoslist( Fts5Index *p, - i64 iDelta, + u64 iDelta, Fts5Iter *pMulti, Fts5Buffer *pBuf ){ @@ -213689,7 +256840,7 @@ static void fts5AppendPoslist( static void fts5DoclistIterNext(Fts5DoclistIter *pIter){ u8 *p = pIter->aPoslist + pIter->nSize + pIter->nPoslist; - assert( pIter->aPoslist ); + assert( pIter->aPoslist || (p==0 && pIter->aPoslist==0) ); if( p>=pIter->aEof ){ pIter->aPoslist = 0; }else{ @@ -213709,17 +256860,22 @@ static void fts5DoclistIterNext(Fts5DoclistIter *pIter){ } pIter->aPoslist = p; + if( &pIter->aPoslist[pIter->nPoslist]>pIter->aEof ){ + pIter->aPoslist = 0; + } } } static void fts5DoclistIterInit( - Fts5Buffer *pBuf, + Fts5Buffer *pBuf, Fts5DoclistIter *pIter ){ memset(pIter, 0, sizeof(*pIter)); - pIter->aPoslist = pBuf->p; - pIter->aEof = &pBuf->p[pBuf->n]; - fts5DoclistIterNext(pIter); + if( pBuf->n>0 ){ + pIter->aPoslist = pBuf->p; + pIter->aEof = &pBuf->p[pBuf->n]; + fts5DoclistIterNext(pIter); + } } #if 0 @@ -213740,10 +256896,10 @@ static void fts5MergeAppendDocid( } #endif -#define fts5MergeAppendDocid(pBuf, iLastRowid, iRowid) { \ - assert( (pBuf)->n!=0 || (iLastRowid)==0 ); \ - fts5BufferSafeAppendVarint((pBuf), (iRowid) - (iLastRowid)); \ - (iLastRowid) = (iRowid); \ +#define fts5MergeAppendDocid(pBuf, iLastRowid, iRowid) { \ + assert( (pBuf)->n!=0 || (iLastRowid)==0 ); \ + fts5BufferSafeAppendVarint((pBuf), (u64)(iRowid) - (u64)(iLastRowid)); \ + (iLastRowid) = (iRowid); \ } /* @@ -213773,16 +256929,20 @@ static void fts5NextRowid(Fts5Buffer *pBuf, int *piOff, i64 *piRowid){ static void fts5MergeRowidLists( Fts5Index *p, /* FTS5 backend object */ Fts5Buffer *p1, /* First list to merge */ - Fts5Buffer *p2 /* Second list to merge */ + int nBuf, /* Number of entries in apBuf[] */ + Fts5Buffer *aBuf /* Array of other lists to merge into p1 */ ){ int i1 = 0; int i2 = 0; i64 iRowid1 = 0; i64 iRowid2 = 0; i64 iOut = 0; - + Fts5Buffer *p2 = &aBuf[0]; Fts5Buffer out; + + (void)nBuf; memset(&out, 0, sizeof(out)); + assert( nBuf==1 ); sqlite3Fts5BufferSize(&p->rc, &out, p1->n + p2->n); if( p->rc ) return; @@ -213809,235 +256969,665 @@ static void fts5MergeRowidLists( fts5BufferFree(&out); } +typedef struct PrefixMerger PrefixMerger; +struct PrefixMerger { + Fts5DoclistIter iter; /* Doclist iterator */ + i64 iPos; /* For iterating through a position list */ + int iOff; + u8 *aPos; + PrefixMerger *pNext; /* Next in docid/poslist order */ +}; + +static void fts5PrefixMergerInsertByRowid( + PrefixMerger **ppHead, + PrefixMerger *p +){ + if( p->iter.aPoslist ){ + PrefixMerger **pp = ppHead; + while( *pp && p->iter.iRowid>(*pp)->iter.iRowid ){ + pp = &(*pp)->pNext; + } + p->pNext = *pp; + *pp = p; + } +} + +static void fts5PrefixMergerInsertByPosition( + PrefixMerger **ppHead, + PrefixMerger *p +){ + if( p->iPos>=0 ){ + PrefixMerger **pp = ppHead; + while( *pp && p->iPos>(*pp)->iPos ){ + pp = &(*pp)->pNext; + } + p->pNext = *pp; + *pp = p; + } +} + + /* -** Buffers p1 and p2 contain doclists. This function merges the content -** of the two doclists together and sets buffer p1 to the result before -** returning. -** -** If an error occurs, an error code is left in p->rc. If an error has -** already occurred, this function is a no-op. +** Array aBuf[] contains nBuf doclists. These are all merged in with the +** doclist in buffer p1. */ static void fts5MergePrefixLists( Fts5Index *p, /* FTS5 backend object */ Fts5Buffer *p1, /* First list to merge */ - Fts5Buffer *p2 /* Second list to merge */ -){ - if( p2->n ){ - i64 iLastRowid = 0; - Fts5DoclistIter i1; - Fts5DoclistIter i2; - Fts5Buffer out = {0, 0, 0}; - Fts5Buffer tmp = {0, 0, 0}; - - /* The maximum size of the output is equal to the sum of the two - ** input sizes + 1 varint (9 bytes). The extra varint is because if the - ** first rowid in one input is a large negative number, and the first in - ** the other a non-negative number, the delta for the non-negative - ** number will be larger on disk than the literal integer value - ** was. */ - if( sqlite3Fts5BufferSize(&p->rc, &out, p1->n + p2->n + 9) ) return; - fts5DoclistIterInit(p1, &i1); - fts5DoclistIterInit(p2, &i2); + int nBuf, /* Number of buffers in array aBuf[] */ + Fts5Buffer *aBuf /* Other lists to merge in */ +){ +#define fts5PrefixMergerNextPosition(p) \ + sqlite3Fts5PoslistNext64((p)->aPos,(p)->iter.nPoslist,&(p)->iOff,&(p)->iPos) +#define FTS5_MERGE_NLIST 16 + PrefixMerger aMerger[FTS5_MERGE_NLIST]; + PrefixMerger *pHead = 0; + int i; + int nOut = 0; + Fts5Buffer out = {0, 0, 0}; + Fts5Buffer tmp = {0, 0, 0}; + i64 iLastRowid = 0; + + /* Initialize a doclist-iterator for each input buffer. Arrange them in + ** a linked-list starting at pHead in ascending order of rowid. Avoid + ** linking any iterators already at EOF into the linked list at all. */ + assert( nBuf+1<=(int)(sizeof(aMerger)/sizeof(aMerger[0])) ); + memset(aMerger, 0, sizeof(PrefixMerger)*(nBuf+1)); + pHead = &aMerger[nBuf]; + fts5DoclistIterInit(p1, &pHead->iter); + for(i=0; in + 9 + 10*nBuf; + + /* The maximum size of the output is equal to the sum of the + ** input sizes + 1 varint (9 bytes). The extra varint is because if the + ** first rowid in one input is a large negative number, and the first in + ** the other a non-negative number, the delta for the non-negative + ** number will be larger on disk than the literal integer value + ** was. + ** + ** Or, if the input position-lists are corrupt, then the output might + ** include up to (nBuf+1) extra 10-byte positions created by interpreting -1 + ** (the value PoslistNext64() uses for EOF) as a position and appending + ** it to the output. This can happen at most once for each input + ** position-list, hence (nBuf+1) 10 byte paddings. */ + if( sqlite3Fts5BufferSize(&p->rc, &out, nOut) ) return; + + while( pHead ){ + fts5MergeAppendDocid(&out, iLastRowid, pHead->iter.iRowid); + + if( pHead->pNext && iLastRowid==pHead->pNext->iter.iRowid ){ + /* Merge data from two or more poslists */ + i64 iPrev = 0; + int nTmp = FTS5_DATA_ZERO_PADDING; + int nMerge = 0; + PrefixMerger *pSave = pHead; + PrefixMerger *pThis = 0; + int nTail = 0; + + pHead = 0; + while( pSave && pSave->iter.iRowid==iLastRowid ){ + PrefixMerger *pNext = pSave->pNext; + pSave->iOff = 0; + pSave->iPos = 0; + pSave->aPos = &pSave->iter.aPoslist[pSave->iter.nSize]; + fts5PrefixMergerNextPosition(pSave); + nTmp += pSave->iter.nPoslist + 10; + nMerge++; + fts5PrefixMergerInsertByPosition(&pHead, pSave); + pSave = pNext; + } + + if( pHead==0 || pHead->pNext==0 ){ + FTS5_CORRUPT_IDX(p); + break; + } - while( 1 ){ - if( i1.iRowidrc, &tmp, nTmp+nMerge*10) ){ + break; } - else{ - /* Merge the two position lists. */ - i64 iPos1 = 0; - i64 iPos2 = 0; - int iOff1 = 0; - int iOff2 = 0; - u8 *a1 = &i1.aPoslist[i1.nSize]; - u8 *a2 = &i2.aPoslist[i2.nSize]; - int nCopy; - u8 *aCopy; - - i64 iPrev = 0; - Fts5PoslistWriter writer; - memset(&writer, 0, sizeof(writer)); - - fts5MergeAppendDocid(&out, iLastRowid, i2.iRowid); - fts5BufferZero(&tmp); - sqlite3Fts5BufferSize(&p->rc, &tmp, i1.nPoslist + i2.nPoslist); - if( p->rc ) break; + fts5BufferZero(&tmp); - sqlite3Fts5PoslistNext64(a1, i1.nPoslist, &iOff1, &iPos1); - sqlite3Fts5PoslistNext64(a2, i2.nPoslist, &iOff2, &iPos2); - assert( iPos1>=0 && iPos2>=0 ); + pThis = pHead; + pHead = pThis->pNext; + sqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, pThis->iPos); + fts5PrefixMergerNextPosition(pThis); + fts5PrefixMergerInsertByPosition(&pHead, pThis); - if( iPos1pNext ){ + pThis = pHead; + if( pThis->iPos!=iPrev ){ + sqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, pThis->iPos); } + fts5PrefixMergerNextPosition(pThis); + pHead = pThis->pNext; + fts5PrefixMergerInsertByPosition(&pHead, pThis); + } - if( iPos1>=0 && iPos2>=0 ){ - while( 1 ){ - if( iPos1iPos!=iPrev ){ + sqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, pHead->iPos); + } + nTail = pHead->iter.nPoslist - pHead->iOff; - if( iPos1>=0 ){ - if( iPos1!=iPrev ){ - sqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, iPos1); - } - aCopy = &a1[iOff1]; - nCopy = i1.nPoslist - iOff1; - }else{ - assert( iPos2>=0 && iPos2!=iPrev ); - sqlite3Fts5PoslistSafeAppend(&tmp, &iPrev, iPos2); - aCopy = &a2[iOff2]; - nCopy = i2.nPoslist - iOff2; - } - if( nCopy>0 ){ - fts5BufferSafeAppendBlob(&tmp, aCopy, nCopy); + /* WRITEPOSLISTSIZE */ + assert_nc( tmp.n+nTail<=nTmp ); + assert( tmp.n+nTail<=nTmp+nMerge*10 ); + if( tmp.n+nTail>nTmp-FTS5_DATA_ZERO_PADDING ){ + if( p->rc==SQLITE_OK ) FTS5_CORRUPT_IDX(p); + break; + } + fts5BufferSafeAppendVarint(&out, (tmp.n+nTail) * 2); + fts5BufferSafeAppendBlob(&out, tmp.p, tmp.n); + if( nTail>0 ){ + fts5BufferSafeAppendBlob(&out, &pHead->aPos[pHead->iOff], nTail); + } + + pHead = pSave; + for(i=0; iiter.aPoslist && pX->iter.iRowid==iLastRowid ){ + fts5DoclistIterNext(&pX->iter); + fts5PrefixMergerInsertByRowid(&pHead, pX); } + } + + }else{ + /* Copy poslist from pHead to output */ + PrefixMerger *pThis = pHead; + Fts5DoclistIter *pI = &pThis->iter; + fts5BufferSafeAppendBlob(&out, pI->aPoslist, pI->nPoslist+pI->nSize); + fts5DoclistIterNext(pI); + pHead = pThis->pNext; + fts5PrefixMergerInsertByRowid(&pHead, pThis); + } + } + + fts5BufferFree(p1); + fts5BufferFree(&tmp); + memset(&out.p[out.n], 0, FTS5_DATA_ZERO_PADDING); + *p1 = out; +} + + +/* +** Iterate through a range of entries in the FTS index, invoking the xVisit +** callback for each of them. +** +** Parameter pToken points to an nToken buffer containing an FTS index term +** (i.e. a document term with the preceding 1 byte index identifier - +** FTS5_MAIN_PREFIX or similar). If bPrefix is true, then the call visits +** all entries for terms that have pToken/nToken as a prefix. If bPrefix +** is false, then only entries with pToken/nToken as the entire key are +** visited. +** +** If the current table is a tokendata=1 table, then if bPrefix is true then +** each index term is treated separately. However, if bPrefix is false, then +** all index terms corresponding to pToken/nToken are collapsed into a single +** term before the callback is invoked. +** +** The callback invoked for each entry visited is specified by paramter xVisit. +** Each time it is invoked, it is passed a pointer to the Fts5Index object, +** a copy of the 7th paramter to this function (pCtx) and a pointer to the +** iterator that indicates the current entry. If the current entry is the +** first with a new term (i.e. different from that of the previous entry, +** including the very first term), then the final two parameters are passed +** a pointer to the term and its size in bytes, respectively. If the current +** entry is not the first associated with its term, these two parameters +** are passed 0. +** +** If parameter pColset is not NULL, then it is used to filter entries before +** the callback is invoked. +*/ +static int fts5VisitEntries( + Fts5Index *p, /* Fts5 index object */ + Fts5Colset *pColset, /* Columns filter to apply, or NULL */ + u8 *pToken, /* Buffer containing token */ + int nToken, /* Size of buffer pToken in bytes */ + int bPrefix, /* True for a prefix scan */ + void (*xVisit)(Fts5Index*, void *pCtx, Fts5Iter *pIter, const u8*, int), + void *pCtx /* Passed as second argument to xVisit() */ +){ + const int flags = (bPrefix ? FTS5INDEX_QUERY_SCAN : 0) + | FTS5INDEX_QUERY_SKIPEMPTY + | FTS5INDEX_QUERY_NOOUTPUT; + Fts5Iter *p1 = 0; /* Iterator used to gather data from index */ + int bNewTerm = 1; + Fts5Structure *pStruct = fts5StructureRead(p); + + fts5MultiIterNew(p, pStruct, flags, pColset, pToken, nToken, -1, 0, &p1); + fts5IterSetOutputCb(&p->rc, p1); + for( /* no-op */ ; + fts5MultiIterEof(p, p1)==0; + fts5MultiIterNext2(p, p1, &bNewTerm) + ){ + Fts5SegIter *pSeg = &p1->aSeg[ p1->aFirst[1].iFirst ]; + int nNew = 0; + const u8 *pNew = 0; + + p1->xSetOutputs(p1, pSeg); + if( p->rc ) break; - /* WRITEPOSLISTSIZE */ - fts5BufferSafeAppendVarint(&out, tmp.n * 2); - fts5BufferSafeAppendBlob(&out, tmp.p, tmp.n); - fts5DoclistIterNext(&i1); - fts5DoclistIterNext(&i2); - assert( out.n<=(p1->n+p2->n+9) ); - if( i1.aPoslist==0 || i2.aPoslist==0 ) break; + if( bNewTerm ){ + nNew = pSeg->term.n; + pNew = pSeg->term.p; + if( nNewrc; +} + + +/* +** Usually, a tokendata=1 iterator (struct Fts5TokenDataIter) accumulates an +** array of these for each row it visits (so all iRowid fields are the same). +** Or, for an iterator used by an "ORDER BY rank" query, it accumulates an +** array of these for the entire query (in which case iRowid fields may take +** a variety of values). +** +** Each instance in the array indicates the iterator (and therefore term) +** associated with position iPos of rowid iRowid. This is used by the +** xInstToken() API. +** +** iRowid: +** Rowid for the current entry. +** +** iPos: +** Position of current entry within row. In the usual ((iCol<<32)+iOff) +** format (e.g. see macros FTS5_POS2COLUMN() and FTS5_POS2OFFSET()). +** +** iIter: +** If the Fts5TokenDataIter iterator that the entry is part of is +** actually an iterator (i.e. with nIter>0, not just a container for +** Fts5TokenDataMap structures), then this variable is an index into +** the apIter[] array. The corresponding term is that which the iterator +** at apIter[iIter] currently points to. +** +** Or, if the Fts5TokenDataIter iterator is just a container object +** (nIter==0), then iIter is an index into the term.p[] buffer where +** the term is stored. +** +** nByte: +** In the case where iIter is an index into term.p[], this variable +** is the size of the term in bytes. If iIter is an index into apIter[], +** this variable is unused. +*/ +struct Fts5TokenDataMap { + i64 iRowid; /* Row this token is located in */ + i64 iPos; /* Position of token */ + int iIter; /* Iterator token was read from */ + int nByte; /* Length of token in bytes (or 0) */ +}; + +/* +** An object used to supplement Fts5Iter for tokendata=1 iterators. +** +** This object serves two purposes. The first is as a container for an array +** of Fts5TokenDataMap structures, which are used to find the token required +** when the xInstToken() API is used. This is done by the nMapAlloc, nMap and +** aMap[] variables. +*/ +struct Fts5TokenDataIter { + i64 nMapAlloc; /* Allocated size of aMap[] in entries */ + i64 nMap; /* Number of valid entries in aMap[] */ + Fts5TokenDataMap *aMap; /* Array of (rowid+pos -> token) mappings */ + + /* The following are used for prefix-queries only. */ + Fts5Buffer terms; + + /* The following are used for other full-token tokendata queries only. */ + i64 nIter; + i64 nIterAlloc; + Fts5PoslistReader *aPoslistReader; + int *aPoslistToIter; + Fts5Iter *apIter[FLEXARRAY]; +}; + +/* Size in bytes of an Fts5TokenDataIter object holding up to N iterators */ +#define SZ_FTS5TOKENDATAITER(N) \ + (offsetof(Fts5TokenDataIter,apIter) + (N)*sizeof(Fts5Iter)) + +/* +** The two input arrays - a1[] and a2[] - are in sorted order. This function +** merges the two arrays together and writes the result to output array +** aOut[]. aOut[] is guaranteed to be large enough to hold the result. +** +** Duplicate entries are copied into the output. So the size of the output +** array is always (n1+n2) entries. +*/ +static void fts5TokendataMerge( + Fts5TokenDataMap *a1, int n1, /* Input array 1 */ + Fts5TokenDataMap *a2, int n2, /* Input array 2 */ + Fts5TokenDataMap *aOut /* Output array */ +){ + int i1 = 0; + int i2 = 0; + + assert( n1>=0 && n2>=0 ); + while( i1=n2 || (i1rc==SQLITE_OK ){ + if( pT->nMap==pT->nMapAlloc ){ + i64 nNew = pT->nMapAlloc ? pT->nMapAlloc*2 : 64; + i64 nAlloc = nNew * sizeof(Fts5TokenDataMap); + Fts5TokenDataMap *aNew; + + aNew = (Fts5TokenDataMap*)sqlite3_realloc64(pT->aMap, nAlloc); + if( aNew==0 ){ + p->rc = SQLITE_NOMEM; + return; } + + pT->aMap = aNew; + pT->nMapAlloc = nNew; } - if( i1.aPoslist ){ - fts5MergeAppendDocid(&out, iLastRowid, i1.iRowid); - fts5BufferSafeAppendBlob(&out, i1.aPoslist, i1.aEof - i1.aPoslist); + pT->aMap[pT->nMap].iRowid = iRowid; + pT->aMap[pT->nMap].iPos = iPos; + pT->aMap[pT->nMap].iIter = iIter; + pT->aMap[pT->nMap].nByte = nByte; + pT->nMap++; + } +} + +/* +** Sort the contents of the pT->aMap[] array. +** +** The sorting algorithm requires a malloc(). If this fails, an error code +** is left in Fts5Index.rc before returning. +*/ +static void fts5TokendataIterSortMap(Fts5Index *p, Fts5TokenDataIter *pT){ + Fts5TokenDataMap *aTmp = 0; + i64 nByte = pT->nMap * sizeof(Fts5TokenDataMap); + + aTmp = (Fts5TokenDataMap*)sqlite3Fts5MallocZero(&p->rc, nByte); + if( aTmp ){ + Fts5TokenDataMap *a1 = pT->aMap; + Fts5TokenDataMap *a2 = aTmp; + i64 nHalf; + + for(nHalf=1; nHalfnMap; nHalf=nHalf*2){ + int i1; + for(i1=0; i1nMap; i1+=(nHalf*2)){ + int n1 = MIN(nHalf, pT->nMap-i1); + int n2 = MIN(nHalf, pT->nMap-i1-n1); + fts5TokendataMerge(&a1[i1], n1, &a1[i1+n1], n2, &a2[i1]); + } + SWAPVAL(Fts5TokenDataMap*, a1, a2); } - else if( i2.aPoslist ){ - fts5MergeAppendDocid(&out, iLastRowid, i2.iRowid); - fts5BufferSafeAppendBlob(&out, i2.aPoslist, i2.aEof - i2.aPoslist); + + if( a1!=pT->aMap ){ + memcpy(pT->aMap, a1, pT->nMap*sizeof(Fts5TokenDataMap)); } - assert( out.n<=(p1->n+p2->n+9) ); + sqlite3_free(aTmp); - fts5BufferSet(&p->rc, p1, out.n, out.p); - fts5BufferFree(&tmp); - fts5BufferFree(&out); +#ifdef SQLITE_DEBUG + { + int ii; + for(ii=1; iinMap; ii++){ + Fts5TokenDataMap *p1 = &pT->aMap[ii-1]; + Fts5TokenDataMap *p2 = &pT->aMap[ii]; + assert( p1->iRowidiRowid + || (p1->iRowid==p2->iRowid && p1->iPos<=p2->iPos) + ); + } + } +#endif + } +} + +/* +** Delete an Fts5TokenDataIter structure and its contents. +*/ +static void fts5TokendataIterDelete(Fts5TokenDataIter *pSet){ + if( pSet ){ + int ii; + for(ii=0; iinIter; ii++){ + fts5MultiIterFree(pSet->apIter[ii]); + } + fts5BufferFree(&pSet->terms); + sqlite3_free(pSet->aPoslistReader); + sqlite3_free(pSet->aMap); + sqlite3_free(pSet); + } +} + + +/* +** fts5VisitEntries() context object used by fts5SetupPrefixIterTokendata() +** to pass data to prefixIterSetupTokendataCb(). +*/ +typedef struct TokendataSetupCtx TokendataSetupCtx; +struct TokendataSetupCtx { + Fts5TokenDataIter *pT; /* Object being populated with mappings */ + int iTermOff; /* Offset of current term in terms.p[] */ + int nTermByte; /* Size of current term in bytes */ +}; + +/* +** fts5VisitEntries() callback used by fts5SetupPrefixIterTokendata(). This +** callback adds an entry to the Fts5TokenDataIter.aMap[] array for each +** position in the current position-list. It doesn't matter that some of +** these may be out of order - they will be sorted later. +*/ +static void prefixIterSetupTokendataCb( + Fts5Index *p, + void *pCtx, + Fts5Iter *p1, + const u8 *pNew, + int nNew +){ + TokendataSetupCtx *pSetup = (TokendataSetupCtx*)pCtx; + int iPosOff = 0; + i64 iPos = 0; + + if( pNew ){ + pSetup->nTermByte = nNew-1; + pSetup->iTermOff = pSetup->pT->terms.n; + fts5BufferAppendBlob(&p->rc, &pSetup->pT->terms, nNew-1, pNew+1); + } + + while( 0==sqlite3Fts5PoslistNext64( + p1->base.pData, p1->base.nData, &iPosOff, &iPos + ) ){ + fts5TokendataIterAppendMap(p, + pSetup->pT, pSetup->iTermOff, pSetup->nTermByte, p1->base.iRowid, iPos + ); + } +} + + +/* +** Context object passed by fts5SetupPrefixIter() to fts5VisitEntries(). +*/ +typedef struct PrefixSetupCtx PrefixSetupCtx; +struct PrefixSetupCtx { + void (*xMerge)(Fts5Index*, Fts5Buffer*, int, Fts5Buffer*); + void (*xAppend)(Fts5Index*, u64, Fts5Iter*, Fts5Buffer*); + i64 iLastRowid; + int nMerge; + Fts5Buffer *aBuf; + int nBuf; + Fts5Buffer doclist; + TokendataSetupCtx *pTokendata; +}; + +/* +** fts5VisitEntries() callback used by fts5SetupPrefixIter() +*/ +static void prefixIterSetupCb( + Fts5Index *p, + void *pCtx, + Fts5Iter *p1, + const u8 *pNew, + int nNew +){ + PrefixSetupCtx *pSetup = (PrefixSetupCtx*)pCtx; + const int nMerge = pSetup->nMerge; + + if( p1->base.nData>0 ){ + if( p1->base.iRowid<=pSetup->iLastRowid && pSetup->doclist.n>0 ){ + int i; + for(i=0; p->rc==SQLITE_OK && pSetup->doclist.n; i++){ + int i1 = i*nMerge; + int iStore; + assert( i1+nMerge<=pSetup->nBuf ); + for(iStore=i1; iStoreaBuf[iStore].n==0 ){ + fts5BufferSwap(&pSetup->doclist, &pSetup->aBuf[iStore]); + fts5BufferZero(&pSetup->doclist); + break; + } + } + if( iStore==i1+nMerge ){ + pSetup->xMerge(p, &pSetup->doclist, nMerge, &pSetup->aBuf[i1]); + for(iStore=i1; iStoreaBuf[iStore]); + } + } + } + pSetup->iLastRowid = 0; + } + + pSetup->xAppend( + p, (u64)p1->base.iRowid-(u64)pSetup->iLastRowid, p1, &pSetup->doclist + ); + pSetup->iLastRowid = p1->base.iRowid; + } + + if( pSetup->pTokendata ){ + prefixIterSetupTokendataCb(p, (void*)pSetup->pTokendata, p1, pNew, nNew); } } static void fts5SetupPrefixIter( Fts5Index *p, /* Index to read from */ int bDesc, /* True for "ORDER BY rowid DESC" */ - const u8 *pToken, /* Buffer containing prefix to match */ + int iIdx, /* Index to scan for data */ + u8 *pToken, /* Buffer containing prefix to match */ int nToken, /* Size of buffer pToken in bytes */ Fts5Colset *pColset, /* Restrict matches to these columns */ - Fts5Iter **ppIter /* OUT: New iterator */ + Fts5Iter **ppIter /* OUT: New iterator */ ){ Fts5Structure *pStruct; - Fts5Buffer *aBuf; - const int nBuf = 32; + PrefixSetupCtx s; + TokendataSetupCtx s2; + + memset(&s, 0, sizeof(s)); + memset(&s2, 0, sizeof(s2)); + + s.nMerge = 1; + s.iLastRowid = 0; + s.nBuf = 32; + if( iIdx==0 + && p->pConfig->eDetail==FTS5_DETAIL_FULL + && p->pConfig->bPrefixInsttoken + ){ + s.pTokendata = &s2; + s2.pT = (Fts5TokenDataIter*)fts5IdxMalloc(p, SZ_FTS5TOKENDATAITER(1)); + } - void (*xMerge)(Fts5Index*, Fts5Buffer*, Fts5Buffer*); - void (*xAppend)(Fts5Index*, i64, Fts5Iter*, Fts5Buffer*); if( p->pConfig->eDetail==FTS5_DETAIL_NONE ){ - xMerge = fts5MergeRowidLists; - xAppend = fts5AppendRowid; + s.xMerge = fts5MergeRowidLists; + s.xAppend = fts5AppendRowid; }else{ - xMerge = fts5MergePrefixLists; - xAppend = fts5AppendPoslist; + s.nMerge = FTS5_MERGE_NLIST-1; + s.nBuf = s.nMerge*8; /* Sufficient to merge (16^8)==(2^32) lists */ + s.xMerge = fts5MergePrefixLists; + s.xAppend = fts5AppendPoslist; } - aBuf = (Fts5Buffer*)fts5IdxMalloc(p, sizeof(Fts5Buffer)*nBuf); + s.aBuf = (Fts5Buffer*)fts5IdxMalloc(p, sizeof(Fts5Buffer)*s.nBuf); pStruct = fts5StructureRead(p); + assert( p->rc!=SQLITE_OK || (s.aBuf && pStruct) ); - if( aBuf && pStruct ){ - const int flags = FTS5INDEX_QUERY_SCAN - | FTS5INDEX_QUERY_SKIPEMPTY - | FTS5INDEX_QUERY_NOOUTPUT; + if( p->rc==SQLITE_OK ){ + void *pCtx = (void*)&s; int i; - i64 iLastRowid = 0; - Fts5Iter *p1 = 0; /* Iterator used to gather data from index */ Fts5Data *pData; - Fts5Buffer doclist; - int bNewTerm = 1; - - memset(&doclist, 0, sizeof(doclist)); - fts5MultiIterNew(p, pStruct, flags, pColset, pToken, nToken, -1, 0, &p1); - fts5IterSetOutputCb(&p->rc, p1); - for( /* no-op */ ; - fts5MultiIterEof(p, p1)==0; - fts5MultiIterNext2(p, p1, &bNewTerm) - ){ - Fts5SegIter *pSeg = &p1->aSeg[ p1->aFirst[1].iFirst ]; - int nTerm = pSeg->term.n; - const u8 *pTerm = pSeg->term.p; - p1->xSetOutputs(p1, pSeg); - - assert_nc( memcmp(pToken, pTerm, MIN(nToken, nTerm))<=0 ); - if( bNewTerm ){ - if( nTermbase.nData==0 ) continue; - - if( p1->base.iRowid<=iLastRowid && doclist.n>0 ){ - for(i=0; p->rc==SQLITE_OK && doclist.n; i++){ - assert( ibase.iRowid-iLastRowid, p1, &doclist); - iLastRowid = p1->base.iRowid; + /* If iIdx is non-zero, then it is the number of a prefix-index for + ** prefixes 1 character longer than the prefix being queried for. That + ** index contains all the doclists required, except for the one + ** corresponding to the prefix itself. That one is extracted from the + ** main term index here. */ + if( iIdx!=0 ){ + pToken[0] = FTS5_MAIN_PREFIX; + fts5VisitEntries(p, pColset, pToken, nToken, 0, prefixIterSetupCb, pCtx); } - for(i=0; irc==SQLITE_OK ){ - xMerge(p, &doclist, &aBuf[i]); + s.xMerge(p, &s.doclist, s.nMerge, &s.aBuf[i]); + } + for(iFree=i; iFreerc!=SQLITE_OK ); if( pData ){ pData->p = (u8*)&pData[1]; - pData->nn = pData->szLeaf = doclist.n; - if( doclist.n ) memcpy(pData->p, doclist.p, doclist.n); + pData->nn = pData->szLeaf = s.doclist.n; + if( s.doclist.n ) memcpy(pData->p, s.doclist.p, s.doclist.n); fts5MultiIterNew2(p, pData, bDesc, ppIter); } - fts5BufferFree(&doclist); + + assert( (*ppIter)!=0 || p->rc!=SQLITE_OK ); + if( p->rc==SQLITE_OK && s.pTokendata ){ + fts5TokendataIterSortMap(p, s2.pT); + (*ppIter)->pTokenDataIter = s2.pT; + s2.pT = 0; + } } + fts5TokendataIterDelete(s2.pT); + fts5BufferFree(&s.doclist); fts5StructureRelease(pStruct); - sqlite3_free(aBuf); + sqlite3_free(s.aBuf); } @@ -214054,15 +257644,18 @@ static int sqlite3Fts5IndexBeginWrite(Fts5Index *p, int bDelete, i64 iRowid){ } /* Flush the hash table to disk if required */ - if( iRowidiWriteRowid + if( iRowidiWriteRowid || (iRowid==p->iWriteRowid && p->bDelete==0) - || (p->nPendingData > p->pConfig->nHashSize) + || (p->nPendingData > p->pConfig->nHashSize) ){ fts5IndexFlush(p); } p->iWriteRowid = iRowid; p->bDelete = bDelete; + if( bDelete==0 ){ + p->nPendingRow++; + } return fts5IndexReturn(p); } @@ -214072,22 +257665,21 @@ static int sqlite3Fts5IndexBeginWrite(Fts5Index *p, int bDelete, i64 iRowid){ static int sqlite3Fts5IndexSync(Fts5Index *p){ assert( p->rc==SQLITE_OK ); fts5IndexFlush(p); - fts5CloseReader(p); + fts5IndexCloseReader(p); return fts5IndexReturn(p); } /* ** Discard any data stored in the in-memory hash tables. Do not write it ** to the database. Additionally, assume that the contents of the %_data -** table may have changed on disk. So any in-memory caches of %_data +** table may have changed on disk. So any in-memory caches of %_data ** records must be invalidated. */ static int sqlite3Fts5IndexRollback(Fts5Index *p){ - fts5CloseReader(p); + fts5IndexCloseReader(p); fts5IndexDiscardData(p); fts5StructureInvalidate(p); - /* assert( p->rc==SQLITE_OK ); */ - return SQLITE_OK; + return fts5IndexReturn(p); } /* @@ -214096,11 +257688,20 @@ static int sqlite3Fts5IndexRollback(Fts5Index *p){ ** and the initial version of the "averages" record (a zero-byte blob). */ static int sqlite3Fts5IndexReinit(Fts5Index *p){ - Fts5Structure s; + Fts5Structure *pTmp; + union { + Fts5Structure sFts; + u8 tmpSpace[SZ_FTS5STRUCTURE(1)]; + } uFts; fts5StructureInvalidate(p); - memset(&s, 0, sizeof(Fts5Structure)); + fts5IndexDiscardData(p); + pTmp = &uFts.sFts; + memset(uFts.tmpSpace, 0, sizeof(uFts.tmpSpace)); + if( p->pConfig->bContentlessDelete ){ + pTmp->nOriginCntr = 1; + } fts5DataWrite(p, FTS5_AVERAGES_ROWID, (const u8*)"", 0); - fts5StructureWrite(p, &s); + fts5StructureWrite(p, pTmp); return fts5IndexReturn(p); } @@ -214112,8 +257713,8 @@ static int sqlite3Fts5IndexReinit(Fts5Index *p){ ** Otherwise, set *pp to NULL and return an SQLite error code. */ static int sqlite3Fts5IndexOpen( - Fts5Config *pConfig, - int bCreate, + Fts5Config *pConfig, + int bCreate, Fts5Index **pp, char **pzErr ){ @@ -214130,8 +257731,8 @@ static int sqlite3Fts5IndexOpen( pConfig, "data", "id INTEGER PRIMARY KEY, block BLOB", 0, pzErr ); if( rc==SQLITE_OK ){ - rc = sqlite3Fts5CreateTable(pConfig, "idx", - "segid, term, pgno, PRIMARY KEY(segid, term)", + rc = sqlite3Fts5CreateTable(pConfig, "idx", + "segid, term, pgno, PRIMARY KEY(segid, term)", 1, pzErr ); } @@ -214162,7 +257763,9 @@ static int sqlite3Fts5IndexClose(Fts5Index *p){ sqlite3_finalize(p->pIdxWriter); sqlite3_finalize(p->pIdxDeleter); sqlite3_finalize(p->pIdxSelect); + sqlite3_finalize(p->pIdxNextSelect); sqlite3_finalize(p->pDataVersion); + sqlite3_finalize(p->pDeleteFromIdx); sqlite3Fts5HashFree(p->pHash); sqlite3_free(p->zDataTbl); sqlite3_free(p); @@ -214171,13 +257774,13 @@ static int sqlite3Fts5IndexClose(Fts5Index *p){ } /* -** Argument p points to a buffer containing utf-8 text that is n bytes in +** Argument p points to a buffer containing utf-8 text that is n bytes in ** size. Return the number of bytes in the nChar character prefix of the ** buffer, or 0 if there are less than nChar characters in total. */ static int sqlite3Fts5IndexCharlenToBytelen( - const char *p, - int nByte, + const char *p, + int nByte, int nChar ){ int n = 0; @@ -214185,9 +257788,13 @@ static int sqlite3Fts5IndexCharlenToBytelen( for(i=0; i=nByte ) return 0; /* Input contains fewer than nChar chars */ if( (unsigned char)p[n++]>=0xc0 ){ + if( n>=nByte ) return 0; while( (p[n] & 0xc0)==0x80 ){ n++; - if( n>=nByte ) break; + if( n>=nByte ){ + if( i+1==nChar ) break; + return 0; + } } } } @@ -214199,7 +257806,7 @@ static int sqlite3Fts5IndexCharlenToBytelen( ** unicode characters in the string. */ static int fts5IndexCharlen(const char *pIn, int nIn){ - int nChar = 0; + int nChar = 0; int i = 0; while( i=0xc0 ){ @@ -214211,7 +257818,7 @@ static int fts5IndexCharlen(const char *pIn, int nIn){ } /* -** Insert or remove data to or from the index. Each time a document is +** Insert or remove data to or from the index. Each time a document is ** added to or removed from the index, this function is called one or more ** times. ** @@ -214242,7 +257849,7 @@ static int sqlite3Fts5IndexWrite( const int nChar = pConfig->aPrefix[i]; int nByte = sqlite3Fts5IndexCharlenToBytelen(pToken, nToken, nChar); if( nByte ){ - rc = sqlite3Fts5HashWrite(p->pHash, + rc = sqlite3Fts5HashWrite(p->pHash, p->iWriteRowid, iCol, iPos, (char)(FTS5_MAIN_PREFIX+i+1), pToken, nByte ); @@ -214253,7 +257860,389 @@ static int sqlite3Fts5IndexWrite( } /* -** Open a new iterator to iterate though all rowid that match the +** pToken points to a buffer of size nToken bytes containing a search +** term, including the index number at the start, used on a tokendata=1 +** table. This function returns true if the term in buffer pBuf matches +** token pToken/nToken. +*/ +static int fts5IsTokendataPrefix( + Fts5Buffer *pBuf, + const u8 *pToken, + int nToken +){ + return ( + pBuf->n>=nToken + && 0==memcmp(pBuf->p, pToken, nToken) + && (pBuf->n==nToken || pBuf->p[nToken]==0x00) + ); +} + +/* +** Ensure the segment-iterator passed as the only argument points to EOF. +*/ +static void fts5SegIterSetEOF(Fts5SegIter *pSeg){ + fts5DataRelease(pSeg->pLeaf); + pSeg->pLeaf = 0; +} + +static void fts5IterClose(Fts5IndexIter *pIndexIter){ + if( pIndexIter ){ + Fts5Iter *pIter = (Fts5Iter*)pIndexIter; + Fts5Index *pIndex = pIter->pIndex; + fts5TokendataIterDelete(pIter->pTokenDataIter); + fts5MultiIterFree(pIter); + fts5IndexCloseReader(pIndex); + } +} + +/* +** This function appends iterator pAppend to Fts5TokenDataIter pIn and +** returns the result. +*/ +static Fts5TokenDataIter *fts5AppendTokendataIter( + Fts5Index *p, /* Index object (for error code) */ + Fts5TokenDataIter *pIn, /* Current Fts5TokenDataIter struct */ + Fts5Iter *pAppend /* Append this iterator */ +){ + Fts5TokenDataIter *pRet = pIn; + + if( p->rc==SQLITE_OK ){ + if( pIn==0 || pIn->nIter==pIn->nIterAlloc ){ + i64 nAlloc = pIn ? pIn->nIterAlloc*2 : 16; + i64 nByte = SZ_FTS5TOKENDATAITER(nAlloc+1); + Fts5TokenDataIter *pNew; + pNew = (Fts5TokenDataIter*)sqlite3_realloc64(pIn, nByte); + + if( pNew==0 ){ + p->rc = SQLITE_NOMEM; + }else{ + if( pIn==0 ) memset(pNew, 0, nByte); + pRet = pNew; + pNew->nIterAlloc = nAlloc; + } + } + } + if( p->rc ){ + fts5IterClose((Fts5IndexIter*)pAppend); + }else{ + pRet->apIter[pRet->nIter++] = pAppend; + } + assert( pRet==0 || pRet->nIter<=pRet->nIterAlloc ); + + return pRet; +} + +/* +** The iterator passed as the only argument must be a tokendata=1 iterator +** (pIter->pTokenDataIter!=0). This function sets the iterator output +** variables (pIter->base.*) according to the contents of the current +** row. +*/ +static void fts5IterSetOutputsTokendata(Fts5Iter *pIter){ + int ii; + int nHit = 0; + i64 iRowid = SMALLEST_INT64; + int iMin = 0; + + Fts5TokenDataIter *pT = pIter->pTokenDataIter; + + pIter->base.nData = 0; + pIter->base.pData = 0; + + for(ii=0; iinIter; ii++){ + Fts5Iter *p = pT->apIter[ii]; + if( p->base.bEof==0 ){ + if( nHit==0 || p->base.iRowidbase.iRowid; + nHit = 1; + pIter->base.pData = p->base.pData; + pIter->base.nData = p->base.nData; + iMin = ii; + }else if( p->base.iRowid==iRowid ){ + nHit++; + } + } + } + + if( nHit==0 ){ + pIter->base.bEof = 1; + }else{ + int eDetail = pIter->pIndex->pConfig->eDetail; + pIter->base.bEof = 0; + pIter->base.iRowid = iRowid; + + if( nHit==1 && eDetail==FTS5_DETAIL_FULL ){ + fts5TokendataIterAppendMap(pIter->pIndex, pT, iMin, 0, iRowid, -1); + }else + if( nHit>1 && eDetail!=FTS5_DETAIL_NONE ){ + int nReader = 0; + int nByte = 0; + i64 iPrev = 0; + + /* Allocate array of iterators if they are not already allocated. */ + if( pT->aPoslistReader==0 ){ + pT->aPoslistReader = (Fts5PoslistReader*)sqlite3Fts5MallocZero( + &pIter->pIndex->rc, + pT->nIter * (sizeof(Fts5PoslistReader) + sizeof(int)) + ); + if( pT->aPoslistReader==0 ) return; + pT->aPoslistToIter = (int*)&pT->aPoslistReader[pT->nIter]; + } + + /* Populate an iterator for each poslist that will be merged */ + for(ii=0; iinIter; ii++){ + Fts5Iter *p = pT->apIter[ii]; + if( iRowid==p->base.iRowid ){ + pT->aPoslistToIter[nReader] = ii; + sqlite3Fts5PoslistReaderInit( + p->base.pData, p->base.nData, &pT->aPoslistReader[nReader++] + ); + nByte += p->base.nData; + } + } + + /* Ensure the output buffer is large enough */ + if( fts5BufferGrow(&pIter->pIndex->rc, &pIter->poslist, nByte+nHit*10) ){ + return; + } + + /* Ensure the token-mapping is large enough */ + if( eDetail==FTS5_DETAIL_FULL && pT->nMapAlloc<(pT->nMap + nByte) ){ + i64 nNew = (pT->nMapAlloc + nByte) * 2; + Fts5TokenDataMap *aNew = (Fts5TokenDataMap*)sqlite3_realloc64( + pT->aMap, nNew*sizeof(Fts5TokenDataMap) + ); + if( aNew==0 ){ + pIter->pIndex->rc = SQLITE_NOMEM; + return; + } + pT->aMap = aNew; + pT->nMapAlloc = nNew; + } + + pIter->poslist.n = 0; + + while( 1 ){ + i64 iMinPos = LARGEST_INT64; + + /* Find smallest position */ + iMin = 0; + for(ii=0; iiaPoslistReader[ii]; + if( pReader->bEof==0 ){ + if( pReader->iPosiPos; + iMin = ii; + } + } + } + + /* If all readers were at EOF, break out of the loop. */ + if( iMinPos==LARGEST_INT64 ) break; + + sqlite3Fts5PoslistSafeAppend(&pIter->poslist, &iPrev, iMinPos); + sqlite3Fts5PoslistReaderNext(&pT->aPoslistReader[iMin]); + + if( eDetail==FTS5_DETAIL_FULL ){ + pT->aMap[pT->nMap].iPos = iMinPos; + pT->aMap[pT->nMap].iIter = pT->aPoslistToIter[iMin]; + pT->aMap[pT->nMap].iRowid = iRowid; + pT->nMap++; + } + } + + pIter->base.pData = pIter->poslist.p; + pIter->base.nData = pIter->poslist.n; + } + } +} + +/* +** The iterator passed as the only argument must be a tokendata=1 iterator +** (pIter->pTokenDataIter!=0). This function advances the iterator. If +** argument bFrom is false, then the iterator is advanced to the next +** entry. Or, if bFrom is true, it is advanced to the first entry with +** a rowid of iFrom or greater. +*/ +static void fts5TokendataIterNext(Fts5Iter *pIter, int bFrom, i64 iFrom){ + int ii; + Fts5TokenDataIter *pT = pIter->pTokenDataIter; + Fts5Index *pIndex = pIter->pIndex; + + for(ii=0; iinIter; ii++){ + Fts5Iter *p = pT->apIter[ii]; + if( p->base.bEof==0 + && (p->base.iRowid==pIter->base.iRowid || (bFrom && p->base.iRowidbase.bEof==0 + && p->base.iRowidrc==SQLITE_OK + ){ + fts5MultiIterNext(pIndex, p, 0, 0); + } + } + } + + if( pIndex->rc==SQLITE_OK ){ + fts5IterSetOutputsTokendata(pIter); + } +} + +/* +** If the segment-iterator passed as the first argument is at EOF, then +** set pIter->term to a copy of buffer pTerm. +*/ +static void fts5TokendataSetTermIfEof(Fts5Iter *pIter, Fts5Buffer *pTerm){ + if( pIter && pIter->aSeg[0].pLeaf==0 ){ + fts5BufferSet(&pIter->pIndex->rc, &pIter->aSeg[0].term, pTerm->n, pTerm->p); + } +} + +/* +** This function sets up an iterator to use for a non-prefix query on a +** tokendata=1 table. +*/ +static Fts5Iter *fts5SetupTokendataIter( + Fts5Index *p, /* FTS index to query */ + const u8 *pToken, /* Buffer containing query term */ + int nToken, /* Size of buffer pToken in bytes */ + Fts5Colset *pColset /* Colset to filter on */ +){ + Fts5Iter *pRet = 0; + Fts5TokenDataIter *pSet = 0; + Fts5Structure *pStruct = 0; + const int flags = FTS5INDEX_QUERY_SCANONETERM | FTS5INDEX_QUERY_SCAN; + + Fts5Buffer bSeek = {0, 0, 0}; + Fts5Buffer *pSmall = 0; + + fts5IndexFlush(p); + pStruct = fts5StructureRead(p); + + while( p->rc==SQLITE_OK ){ + Fts5Iter *pPrev = pSet ? pSet->apIter[pSet->nIter-1] : 0; + Fts5Iter *pNew = 0; + Fts5SegIter *pNewIter = 0; + Fts5SegIter *pPrevIter = 0; + + int iLvl, iSeg, ii; + + pNew = fts5MultiIterAlloc(p, pStruct->nSegment); + if( pSmall ){ + fts5BufferSet(&p->rc, &bSeek, pSmall->n, pSmall->p); + fts5BufferAppendBlob(&p->rc, &bSeek, 1, (const u8*)"\0"); + }else{ + fts5BufferSet(&p->rc, &bSeek, nToken, pToken); + } + if( p->rc ){ + fts5IterClose((Fts5IndexIter*)pNew); + break; + } + + pNewIter = &pNew->aSeg[0]; + pPrevIter = (pPrev ? &pPrev->aSeg[0] : 0); + for(iLvl=0; iLvlnLevel; iLvl++){ + for(iSeg=pStruct->aLevel[iLvl].nSeg-1; iSeg>=0; iSeg--){ + Fts5StructureSegment *pSeg = &pStruct->aLevel[iLvl].aSeg[iSeg]; + int bDone = 0; + + if( pPrevIter ){ + if( fts5BufferCompare(pSmall, &pPrevIter->term) ){ + memcpy(pNewIter, pPrevIter, sizeof(Fts5SegIter)); + memset(pPrevIter, 0, sizeof(Fts5SegIter)); + bDone = 1; + }else if( pPrevIter->iEndofDoclist>pPrevIter->pLeaf->szLeaf ){ + fts5SegIterNextInit(p,(const char*)bSeek.p,bSeek.n-1,pSeg,pNewIter); + bDone = 1; + } + } + + if( bDone==0 ){ + fts5SegIterSeekInit(p, bSeek.p, bSeek.n, flags, pSeg, pNewIter); + } + + if( pPrevIter ){ + if( pPrevIter->pTombArray ){ + pNewIter->pTombArray = pPrevIter->pTombArray; + pNewIter->pTombArray->nRef++; + } + }else{ + fts5SegIterAllocTombstone(p, pNewIter); + } + + pNewIter++; + if( pPrevIter ) pPrevIter++; + if( p->rc ) break; + } + } + fts5TokendataSetTermIfEof(pPrev, pSmall); + + pNew->bSkipEmpty = 1; + pNew->pColset = pColset; + fts5IterSetOutputCb(&p->rc, pNew); + + /* Loop through all segments in the new iterator. Find the smallest + ** term that any segment-iterator points to. Iterator pNew will be + ** used for this term. Also, set any iterator that points to a term that + ** does not match pToken/nToken to point to EOF */ + pSmall = 0; + for(ii=0; iinSeg; ii++){ + Fts5SegIter *pII = &pNew->aSeg[ii]; + if( 0==fts5IsTokendataPrefix(&pII->term, pToken, nToken) ){ + fts5SegIterSetEOF(pII); + } + if( pII->pLeaf && (!pSmall || fts5BufferCompare(pSmall, &pII->term)>0) ){ + pSmall = &pII->term; + } + } + + /* If pSmall is still NULL at this point, then the new iterator does + ** not point to any terms that match the query. So delete it and break + ** out of the loop - all required iterators have been collected. */ + if( pSmall==0 ){ + fts5IterClose((Fts5IndexIter*)pNew); + break; + } + + /* Append this iterator to the set and continue. */ + pSet = fts5AppendTokendataIter(p, pSet, pNew); + } + + if( p->rc==SQLITE_OK && pSet ){ + int ii; + for(ii=0; iinIter; ii++){ + Fts5Iter *pIter = pSet->apIter[ii]; + int iSeg; + for(iSeg=0; iSegnSeg; iSeg++){ + pIter->aSeg[iSeg].flags |= FTS5_SEGITER_ONETERM; + } + fts5MultiIterFinishSetup(p, pIter); + } + } + + if( p->rc==SQLITE_OK ){ + pRet = fts5MultiIterAlloc(p, 0); + } + if( pRet ){ + pRet->nSeg = 0; + pRet->pTokenDataIter = pSet; + if( pSet ){ + fts5IterSetOutputsTokendata(pRet); + }else{ + pRet->base.bEof = 1; + } + }else{ + fts5TokendataIterDelete(pSet); + } + + fts5StructureRelease(pStruct); + fts5BufferFree(&bSeek); + return pRet; +} + +/* +** Open a new iterator to iterate though all rowid that match the ** specified token or token prefix. */ static int sqlite3Fts5IndexQuery( @@ -214272,7 +258261,19 @@ static int sqlite3Fts5IndexQuery( if( sqlite3Fts5BufferSize(&p->rc, &buf, nToken+1)==0 ){ int iIdx = 0; /* Index to search */ - if( nToken ) memcpy(&buf.p[1], pToken, nToken); + int iPrefixIdx = 0; /* +1 prefix index */ + int bTokendata = pConfig->bTokendata; + assert( buf.p!=0 ); + if( nToken>0 ) memcpy(&buf.p[1], pToken, nToken); + + /* The NOTOKENDATA flag is set when each token in a tokendata=1 table + ** should be treated individually, instead of merging all those with + ** a common prefix into a single entry. This is used, for example, by + ** queries performed as part of an integrity-check, or by the fts5vocab + ** module. */ + if( flags & (FTS5INDEX_QUERY_NOTOKENDATA|FTS5INDEX_QUERY_SCAN) ){ + bTokendata = 0; + } /* Figure out which index to search and set iIdx accordingly. If this ** is a prefix query for which there is no prefix index, set iIdx to @@ -214280,9 +258281,9 @@ static int sqlite3Fts5IndexQuery( ** satisfied by scanning multiple terms in the main index. ** ** If the QUERY_TEST_NOIDX flag was specified, then this must be a - ** prefix-query. Instead of using a prefix-index (if one exists), + ** prefix-query. Instead of using a prefix-index (if one exists), ** evaluate the prefix query using the main FTS index. This is used - ** for internal sanity checking by the integrity-check in debug + ** for internal sanity checking by the integrity-check in debug ** mode only. */ #ifdef SQLITE_DEBUG if( pConfig->bPrefixIndex==0 || (flags & FTS5INDEX_QUERY_TEST_NOIDX) ){ @@ -214293,37 +258294,45 @@ static int sqlite3Fts5IndexQuery( if( flags & FTS5INDEX_QUERY_PREFIX ){ int nChar = fts5IndexCharlen(pToken, nToken); for(iIdx=1; iIdx<=pConfig->nPrefix; iIdx++){ - if( pConfig->aPrefix[iIdx-1]==nChar ) break; + int nIdxChar = pConfig->aPrefix[iIdx-1]; + if( nIdxChar==nChar ) break; + if( nIdxChar==nChar+1 ) iPrefixIdx = iIdx; } } - if( iIdx<=pConfig->nPrefix ){ + if( bTokendata && iIdx==0 ){ + buf.p[0] = FTS5_MAIN_PREFIX; + pRet = fts5SetupTokendataIter(p, buf.p, nToken+1, pColset); + }else if( iIdx<=pConfig->nPrefix ){ /* Straight index lookup */ Fts5Structure *pStruct = fts5StructureRead(p); buf.p[0] = (u8)(FTS5_MAIN_PREFIX + iIdx); if( pStruct ){ - fts5MultiIterNew(p, pStruct, flags | FTS5INDEX_QUERY_SKIPEMPTY, + fts5MultiIterNew(p, pStruct, flags | FTS5INDEX_QUERY_SKIPEMPTY, pColset, buf.p, nToken+1, -1, 0, &pRet ); fts5StructureRelease(pStruct); } }else{ - /* Scan multiple terms in the main index */ + /* Scan multiple terms in the main index for a prefix query. */ int bDesc = (flags & FTS5INDEX_QUERY_DESC)!=0; - buf.p[0] = FTS5_MAIN_PREFIX; - fts5SetupPrefixIter(p, bDesc, buf.p, nToken+1, pColset, &pRet); - assert( p->rc!=SQLITE_OK || pRet->pColset==0 ); - fts5IterSetOutputCb(&p->rc, pRet); - if( p->rc==SQLITE_OK ){ - Fts5SegIter *pSeg = &pRet->aSeg[pRet->aFirst[1].iFirst]; - if( pSeg->pLeaf ) pRet->xSetOutputs(pRet, pSeg); + fts5SetupPrefixIter(p, bDesc, iPrefixIdx, buf.p, nToken+1, pColset,&pRet); + if( pRet==0 ){ + assert( p->rc!=SQLITE_OK ); + }else{ + assert( pRet->pColset==0 ); + fts5IterSetOutputCb(&p->rc, pRet); + if( p->rc==SQLITE_OK ){ + Fts5SegIter *pSeg = &pRet->aSeg[pRet->aFirst[1].iFirst]; + if( pSeg->pLeaf ) pRet->xSetOutputs(pRet, pSeg); + } } } if( p->rc ){ - sqlite3Fts5IterClose((Fts5IndexIter*)pRet); + fts5IterClose((Fts5IndexIter*)pRet); pRet = 0; - fts5CloseReader(p); + fts5IndexCloseReader(p); } *ppIter = (Fts5IndexIter*)pRet; @@ -214336,12 +258345,17 @@ static int sqlite3Fts5IndexQuery( ** Return true if the iterator passed as the only argument is at EOF. */ /* -** Move to the next matching rowid. +** Move to the next matching rowid. */ static int sqlite3Fts5IterNext(Fts5IndexIter *pIndexIter){ Fts5Iter *pIter = (Fts5Iter*)pIndexIter; assert( pIter->pIndex->rc==SQLITE_OK ); - fts5MultiIterNext(pIter->pIndex, pIter, 0, 0); + if( pIter->nSeg==0 ){ + assert( pIter->pTokenDataIter ); + fts5TokendataIterNext(pIter, 0, 0); + }else{ + fts5MultiIterNext(pIter->pIndex, pIter, 0, 0); + } return fts5IndexReturn(pIter->pIndex); } @@ -214374,7 +258388,12 @@ static int sqlite3Fts5IterNextScan(Fts5IndexIter *pIndexIter){ */ static int sqlite3Fts5IterNextFrom(Fts5IndexIter *pIndexIter, i64 iMatch){ Fts5Iter *pIter = (Fts5Iter*)pIndexIter; - fts5MultiIterNextFrom(pIter->pIndex, pIter, iMatch); + if( pIter->nSeg==0 ){ + assert( pIter->pTokenDataIter ); + fts5TokendataIterNext(pIter, 1, iMatch); + }else{ + fts5MultiIterNextFrom(pIter->pIndex, pIter, iMatch); + } return fts5IndexReturn(pIter->pIndex); } @@ -214384,8 +258403,180 @@ static int sqlite3Fts5IterNextFrom(Fts5IndexIter *pIndexIter, i64 iMatch){ static const char *sqlite3Fts5IterTerm(Fts5IndexIter *pIndexIter, int *pn){ int n; const char *z = (const char*)fts5MultiIterTerm((Fts5Iter*)pIndexIter, &n); + assert_nc( z || n<=1 ); *pn = n-1; - return &z[1]; + return (z ? &z[1] : 0); +} + +/* +** pIter is a prefix query. This function populates pIter->pTokenDataIter +** with an Fts5TokenDataIter object containing mappings for all rows +** matched by the query. +*/ +static int fts5SetupPrefixIterTokendata( + Fts5Iter *pIter, + const char *pToken, /* Token prefix to search for */ + int nToken /* Size of pToken in bytes */ +){ + Fts5Index *p = pIter->pIndex; + Fts5Buffer token = {0, 0, 0}; + TokendataSetupCtx ctx; + + memset(&ctx, 0, sizeof(ctx)); + + fts5BufferGrow(&p->rc, &token, nToken+1); + assert( token.p!=0 || p->rc!=SQLITE_OK ); + ctx.pT = (Fts5TokenDataIter*)sqlite3Fts5MallocZero(&p->rc, + SZ_FTS5TOKENDATAITER(1)); + + if( p->rc==SQLITE_OK ){ + + /* Fill in the token prefix to search for */ + token.p[0] = FTS5_MAIN_PREFIX; + memcpy(&token.p[1], pToken, nToken); + token.n = nToken+1; + + fts5VisitEntries( + p, 0, token.p, token.n, 1, prefixIterSetupTokendataCb, (void*)&ctx + ); + + fts5TokendataIterSortMap(p, ctx.pT); + } + + if( p->rc==SQLITE_OK ){ + pIter->pTokenDataIter = ctx.pT; + }else{ + fts5TokendataIterDelete(ctx.pT); + } + fts5BufferFree(&token); + + return fts5IndexReturn(p); +} + +/* +** This is used by xInstToken() to access the token at offset iOff, column +** iCol of row iRowid. The token is returned via output variables *ppOut +** and *pnOut. The iterator passed as the first argument must be a tokendata=1 +** iterator (pIter->pTokenDataIter!=0). +** +** pToken/nToken: +*/ +static int sqlite3Fts5IterToken( + Fts5IndexIter *pIndexIter, + const char *pToken, int nToken, + i64 iRowid, + int iCol, + int iOff, + const char **ppOut, int *pnOut +){ + Fts5Iter *pIter = (Fts5Iter*)pIndexIter; + Fts5TokenDataIter *pT = pIter->pTokenDataIter; + i64 iPos = (((i64)iCol)<<32) + iOff; + Fts5TokenDataMap *aMap = 0; + int i1 = 0; + int i2 = 0; + int iTest = 0; + + assert( pT || (pToken && pIter->nSeg>0) ); + if( pT==0 ){ + int rc = fts5SetupPrefixIterTokendata(pIter, pToken, nToken); + if( rc!=SQLITE_OK ) return rc; + pT = pIter->pTokenDataIter; + } + + i2 = pT->nMap; + aMap = pT->aMap; + + while( i2>i1 ){ + iTest = (i1 + i2) / 2; + + if( aMap[iTest].iRowidiRowid ){ + i2 = iTest; + }else{ + if( aMap[iTest].iPosiPos ){ + i2 = iTest; + }else{ + break; + } + } + } + + if( i2>i1 ){ + if( pIter->nSeg==0 ){ + Fts5Iter *pMap = pT->apIter[aMap[iTest].iIter]; + *ppOut = (const char*)pMap->aSeg[0].term.p+1; + *pnOut = pMap->aSeg[0].term.n-1; + }else{ + Fts5TokenDataMap *p = &aMap[iTest]; + *ppOut = (const char*)&pT->terms.p[p->iIter]; + *pnOut = aMap[iTest].nByte; + } + } + + return SQLITE_OK; +} + +/* +** Clear any existing entries from the token-map associated with the +** iterator passed as the only argument. +*/ +static void sqlite3Fts5IndexIterClearTokendata(Fts5IndexIter *pIndexIter){ + Fts5Iter *pIter = (Fts5Iter*)pIndexIter; + if( pIter && pIter->pTokenDataIter + && (pIter->nSeg==0 || pIter->pIndex->pConfig->eDetail!=FTS5_DETAIL_FULL) + ){ + pIter->pTokenDataIter->nMap = 0; + } +} + +/* +** Set a token-mapping for the iterator passed as the first argument. This +** is used in detail=column or detail=none mode when a token is requested +** using the xInstToken() API. In this case the caller tokenizers the +** current row and configures the token-mapping via multiple calls to this +** function. +*/ +static int sqlite3Fts5IndexIterWriteTokendata( + Fts5IndexIter *pIndexIter, + const char *pToken, int nToken, + i64 iRowid, int iCol, int iOff +){ + Fts5Iter *pIter = (Fts5Iter*)pIndexIter; + Fts5TokenDataIter *pT = pIter->pTokenDataIter; + Fts5Index *p = pIter->pIndex; + i64 iPos = (((i64)iCol)<<32) + iOff; + + assert( p->pConfig->eDetail!=FTS5_DETAIL_FULL ); + assert( pIter->pTokenDataIter || pIter->nSeg>0 ); + if( pIter->nSeg>0 ){ + /* This is a prefix term iterator. */ + if( pT==0 ){ + pT = (Fts5TokenDataIter*)sqlite3Fts5MallocZero(&p->rc, + SZ_FTS5TOKENDATAITER(1)); + pIter->pTokenDataIter = pT; + } + if( pT ){ + fts5TokendataIterAppendMap(p, pT, pT->terms.n, nToken, iRowid, iPos); + fts5BufferAppendBlob(&p->rc, &pT->terms, nToken, (const u8*)pToken); + } + }else{ + int ii; + for(ii=0; iinIter; ii++){ + Fts5Buffer *pTerm = &pT->apIter[ii]->aSeg[0].term; + if( nToken==pTerm->n-1 && memcmp(pToken, pTerm->p+1, nToken)==0 ) break; + } + if( iinIter ){ + fts5TokendataIterAppendMap(p, pT, ii, 0, iRowid, iPos); + } + } + return fts5IndexReturn(p); } /* @@ -214393,15 +258584,14 @@ static const char *sqlite3Fts5IterTerm(Fts5IndexIter *pIndexIter, int *pn){ */ static void sqlite3Fts5IterClose(Fts5IndexIter *pIndexIter){ if( pIndexIter ){ - Fts5Iter *pIter = (Fts5Iter*)pIndexIter; - Fts5Index *pIndex = pIter->pIndex; - fts5MultiIterFree(pIter); - fts5CloseReader(pIndex); + Fts5Index *pIndex = ((Fts5Iter*)pIndexIter)->pIndex; + fts5IterClose(pIndexIter); + fts5IndexReturn(pIndex); } } /* -** Read and decode the "averages" record from the database. +** Read and decode the "averages" record from the database. ** ** Parameter anSize must point to an array of size nCol, where nCol is ** the number of user defined columns in the FTS table. @@ -214427,7 +258617,7 @@ static int sqlite3Fts5IndexGetAverages(Fts5Index *p, i64 *pnRow, i64 *anSize){ } /* -** Replace the current "averages" record with the contents of the buffer +** Replace the current "averages" record with the contents of the buffer ** supplied as the second argument. */ static int sqlite3Fts5IndexSetAverages(Fts5Index *p, const u8 *pData, int nData){ @@ -214445,7 +258635,7 @@ static int sqlite3Fts5IndexReads(Fts5Index *p){ } /* -** Set the 32-bit cookie value stored at the start of all structure +** Set the 32-bit cookie value stored at the start of all structure ** records to the value passed as the second argument. ** ** Return SQLITE_OK if successful, or an SQLite error code if an error @@ -214460,7 +258650,7 @@ static int sqlite3Fts5IndexSetCookie(Fts5Index *p, int iNew){ assert( p->rc==SQLITE_OK ); sqlite3Fts5Put32(aCookie, iNew); - rc = sqlite3_blob_open(pConfig->db, pConfig->zDb, p->zDataTbl, + rc = sqlite3_blob_open(pConfig->db, pConfig->zDb, p->zDataTbl, "block", FTS5_STRUCTURE_ROWID, 1, &pBlob ); if( rc==SQLITE_OK ){ @@ -214478,10 +258668,354 @@ static int sqlite3Fts5IndexLoadConfig(Fts5Index *p){ return fts5IndexReturn(p); } +/* +** Retrieve the origin value that will be used for the segment currently +** being accumulated in the in-memory hash table when it is flushed to +** disk. If successful, SQLITE_OK is returned and (*piOrigin) set to +** the queried value. Or, if an error occurs, an error code is returned +** and the final value of (*piOrigin) is undefined. +*/ +static int sqlite3Fts5IndexGetOrigin(Fts5Index *p, i64 *piOrigin){ + Fts5Structure *pStruct; + pStruct = fts5StructureRead(p); + if( pStruct ){ + *piOrigin = pStruct->nOriginCntr; + fts5StructureRelease(pStruct); + } + return fts5IndexReturn(p); +} + +/* +** Buffer pPg contains a page of a tombstone hash table - one of nPg pages +** associated with the same segment. This function adds rowid iRowid to +** the hash table. The caller is required to guarantee that there is at +** least one free slot on the page. +** +** If parameter bForce is false and the hash table is deemed to be full +** (more than half of the slots are occupied), then non-zero is returned +** and iRowid not inserted. Or, if bForce is true or if the hash table page +** is not full, iRowid is inserted and zero returned. +*/ +static int fts5IndexTombstoneAddToPage( + Fts5Data *pPg, + int bForce, + int nPg, + u64 iRowid +){ + const int szKey = TOMBSTONE_KEYSIZE(pPg); + const int nSlot = TOMBSTONE_NSLOT(pPg); + const int nElem = fts5GetU32(&pPg->p[4]); + int iSlot = (iRowid / nPg) % nSlot; + int nCollide = nSlot; + + if( szKey==4 && iRowid>0xFFFFFFFF ) return 2; + if( iRowid==0 ){ + pPg->p[1] = 0x01; + return 0; + } + + if( bForce==0 && nElem>=(nSlot/2) ){ + return 1; + } + + fts5PutU32(&pPg->p[4], nElem+1); + if( szKey==4 ){ + u32 *aSlot = (u32*)&pPg->p[8]; + while( aSlot[iSlot] ){ + iSlot = (iSlot + 1) % nSlot; + if( nCollide--==0 ) return 0; + } + fts5PutU32((u8*)&aSlot[iSlot], (u32)iRowid); + }else{ + u64 *aSlot = (u64*)&pPg->p[8]; + while( aSlot[iSlot] ){ + iSlot = (iSlot + 1) % nSlot; + if( nCollide--==0 ) return 0; + } + fts5PutU64((u8*)&aSlot[iSlot], iRowid); + } + + return 0; +} + +/* +** This function attempts to build a new hash containing all the keys +** currently in the tombstone hash table for segment pSeg. The new +** hash will be stored in the nOut buffers passed in array apOut[]. +** All pages of the new hash use key-size szKey (4 or 8). +** +** Return 0 if the hash is successfully rebuilt into the nOut pages. +** Or non-zero if it is not (because one page became overfull). In this +** case the caller should retry with a larger nOut parameter. +** +** Parameter pData1 is page iPg1 of the hash table being rebuilt. +*/ +static int fts5IndexTombstoneRehash( + Fts5Index *p, + Fts5StructureSegment *pSeg, /* Segment to rebuild hash of */ + Fts5Data *pData1, /* One page of current hash - or NULL */ + int iPg1, /* Which page of the current hash is pData1 */ + int szKey, /* 4 or 8, the keysize */ + int nOut, /* Number of output pages */ + Fts5Data **apOut /* Array of output hash pages */ +){ + int ii; + int res = 0; + + /* Initialize the headers of all the output pages */ + for(ii=0; iip[0] = szKey; + fts5PutU32(&apOut[ii]->p[4], 0); + } + + /* Loop through the current pages of the hash table. */ + for(ii=0; res==0 && iinPgTombstone; ii++){ + Fts5Data *pData = 0; /* Page ii of the current hash table */ + Fts5Data *pFree = 0; /* Free this at the end of the loop */ + + if( iPg1==ii ){ + pData = pData1; + }else{ + pFree = pData = fts5DataRead(p, FTS5_TOMBSTONE_ROWID(pSeg->iSegid, ii)); + } + + if( pData ){ + int szKeyIn = TOMBSTONE_KEYSIZE(pData); + int nSlotIn = (pData->nn - 8) / szKeyIn; + int iIn; + for(iIn=0; iInp[8]; + if( aSlot[iIn] ) iVal = fts5GetU32((u8*)&aSlot[iIn]); + }else{ + u64 *aSlot = (u64*)&pData->p[8]; + if( aSlot[iIn] ) iVal = fts5GetU64((u8*)&aSlot[iIn]); + } + + /* If iVal is not 0 at this point, insert it into the new hash table */ + if( iVal ){ + Fts5Data *pPg = apOut[(iVal % nOut)]; + res = fts5IndexTombstoneAddToPage(pPg, 0, nOut, iVal); + if( res ) break; + } + } + + /* If this is page 0 of the old hash, copy the rowid-0-flag from the + ** old hash to the new. */ + if( ii==0 ){ + apOut[0]->p[1] = pData->p[1]; + } + } + fts5DataRelease(pFree); + } + + return res; +} + +/* +** This is called to rebuild the hash table belonging to segment pSeg. +** If parameter pData1 is not NULL, then one page of the existing hash table +** has already been loaded - pData1, which is page iPg1. The key-size for +** the new hash table is szKey (4 or 8). +** +** If successful, the new hash table is not written to disk. Instead, +** output parameter (*pnOut) is set to the number of pages in the new +** hash table, and (*papOut) to point to an array of buffers containing +** the new page data. +** +** If an error occurs, an error code is left in the Fts5Index object and +** both output parameters set to 0 before returning. +*/ +static void fts5IndexTombstoneRebuild( + Fts5Index *p, + Fts5StructureSegment *pSeg, /* Segment to rebuild hash of */ + Fts5Data *pData1, /* One page of current hash - or NULL */ + int iPg1, /* Which page of the current hash is pData1 */ + int szKey, /* 4 or 8, the keysize */ + int *pnOut, /* OUT: Number of output pages */ + Fts5Data ***papOut /* OUT: Output hash pages */ +){ + const int MINSLOT = 32; + int nSlotPerPage = MAX(MINSLOT, (p->pConfig->pgsz - 8) / szKey); + i64 nSlot = 0; /* Number of slots in each output page */ + i64 nOut = 0; + + /* Figure out how many output pages (nOut) and how many slots per + ** page (nSlot). There are three possibilities: + ** + ** 1. The hash table does not yet exist. In this case the new hash + ** table will consist of a single page with MINSLOT slots. + ** + ** 2. The hash table exists but is currently a single page. In this + ** case an attempt is made to grow the page to accommodate the new + ** entry. The page is allowed to grow up to nSlotPerPage (see above) + ** slots. + ** + ** 3. The hash table already consists of more than one page, or of + ** a single page already so large that it cannot be grown. In this + ** case the new hash consists of (nPg*2+1) pages of nSlotPerPage + ** slots each, where nPg is the current number of pages in the + ** hash table. + */ + if( pSeg->nPgTombstone==0 ){ + /* Case 1. */ + nOut = 1; + nSlot = MINSLOT; + }else if( pSeg->nPgTombstone==1 ){ + /* Case 2. */ + u32 nElem = fts5GetU32(&pData1->p[4]); + assert( pData1 && iPg1==0 ); + if( nElem>((u32)nSlotPerPage/4) ){ + nOut = 0; + }else{ + nOut = 1; + nSlot = MAX((i64)nElem*4, MINSLOT); + } + } + if( nOut==0 ){ + /* Case 3. */ + nOut = ((i64)pSeg->nPgTombstone * 2 + 1); + nSlot = nSlotPerPage; + } + + /* Allocate the required array and output pages */ + while( 1 ){ + int res = 0; + i64 ii = 0; + i64 szPage = 0; + Fts5Data **apOut = 0; + + /* Allocate space for the new hash table */ + assert( nSlot>=MINSLOT ); + apOut = (Fts5Data**)sqlite3Fts5MallocZero(&p->rc, sizeof(Fts5Data*) * nOut); + szPage = 8 + nSlot*szKey; + for(ii=0; iirc, + sizeof(Fts5Data)+szPage + ); + if( pNew ){ + pNew->nn = szPage; + pNew->p = (u8*)&pNew[1]; + apOut[ii] = pNew; + } + } + + /* Rebuild the hash table. */ + if( p->rc==SQLITE_OK ){ + res = fts5IndexTombstoneRehash(p, pSeg, pData1, iPg1, szKey, nOut, apOut); + } + if( res==0 ){ + if( p->rc ){ + fts5IndexFreeArray(apOut, nOut); + apOut = 0; + nOut = 0; + } + *pnOut = nOut; + *papOut = apOut; + break; + } + + /* If control flows to here, it was not possible to rebuild the hash + ** table. Free all buffers and then try again with more pages. */ + assert( p->rc==SQLITE_OK ); + fts5IndexFreeArray(apOut, nOut); + nSlot = nSlotPerPage; + nOut = nOut*2 + 1; + } +} + + +/* +** Add a tombstone for rowid iRowid to segment pSeg. +*/ +static void fts5IndexTombstoneAdd( + Fts5Index *p, + Fts5StructureSegment *pSeg, + u64 iRowid +){ + Fts5Data *pPg = 0; + int iPg = -1; + int szKey = 0; + int nHash = 0; + Fts5Data **apHash = 0; + + p->nContentlessDelete++; + + if( pSeg->nPgTombstone>0 ){ + iPg = iRowid % pSeg->nPgTombstone; + pPg = fts5DataRead(p, FTS5_TOMBSTONE_ROWID(pSeg->iSegid,iPg)); + if( pPg==0 ){ + assert( p->rc!=SQLITE_OK ); + return; + } + + if( 0==fts5IndexTombstoneAddToPage(pPg, 0, pSeg->nPgTombstone, iRowid) ){ + fts5DataWrite(p, FTS5_TOMBSTONE_ROWID(pSeg->iSegid,iPg), pPg->p, pPg->nn); + fts5DataRelease(pPg); + return; + } + } + + /* Have to rebuild the hash table. First figure out the key-size (4 or 8). */ + szKey = pPg ? TOMBSTONE_KEYSIZE(pPg) : 4; + if( iRowid>0xFFFFFFFF ) szKey = 8; + + /* Rebuild the hash table */ + fts5IndexTombstoneRebuild(p, pSeg, pPg, iPg, szKey, &nHash, &apHash); + assert( p->rc==SQLITE_OK || (nHash==0 && apHash==0) ); + + /* If all has succeeded, write the new rowid into one of the new hash + ** table pages, then write them all out to disk. */ + if( nHash ){ + int ii = 0; + fts5IndexTombstoneAddToPage(apHash[iRowid % nHash], 1, nHash, iRowid); + for(ii=0; iiiSegid, ii); + fts5DataWrite(p, iTombstoneRowid, apHash[ii]->p, apHash[ii]->nn); + } + pSeg->nPgTombstone = nHash; + fts5StructureWrite(p, p->pStruct); + } + + fts5DataRelease(pPg); + fts5IndexFreeArray(apHash, nHash); +} + +/* +** Add iRowid to the tombstone list of the segment or segments that contain +** rows from origin iOrigin. Return SQLITE_OK if successful, or an SQLite +** error code otherwise. +*/ +static int sqlite3Fts5IndexContentlessDelete(Fts5Index *p, i64 iOrigin, i64 iRowid){ + Fts5Structure *pStruct; + pStruct = fts5StructureRead(p); + if( pStruct ){ + int bFound = 0; /* True after pSeg->nEntryTombstone incr. */ + int iLvl; + for(iLvl=pStruct->nLevel-1; iLvl>=0; iLvl--){ + int iSeg; + for(iSeg=pStruct->aLevel[iLvl].nSeg-1; iSeg>=0; iSeg--){ + Fts5StructureSegment *pSeg = &pStruct->aLevel[iLvl].aSeg[iSeg]; + if( pSeg->iOrigin1<=(u64)iOrigin && pSeg->iOrigin2>=(u64)iOrigin ){ + if( bFound==0 ){ + pSeg->nEntryTombstone++; + bFound = 1; + } + fts5IndexTombstoneAdd(p, pSeg, iRowid); + } + } + } + fts5StructureRelease(pStruct); + } + return fts5IndexReturn(p); +} /************************************************************************* ************************************************************************** -** Below this point is the implementation of the integrity-check +** Below this point is the implementation of the integrity-check ** functionality. */ @@ -214489,9 +259023,9 @@ static int sqlite3Fts5IndexLoadConfig(Fts5Index *p){ ** Return a simple checksum value based on the arguments. */ static u64 sqlite3Fts5IndexEntryCksum( - i64 iRowid, - int iCol, - int iPos, + i64 iRowid, + int iCol, + int iPos, int iIdx, const char *pTerm, int nTerm @@ -214507,15 +259041,15 @@ static u64 sqlite3Fts5IndexEntryCksum( #ifdef SQLITE_DEBUG /* -** This function is purely an internal test. It does not contribute to +** This function is purely an internal test. It does not contribute to ** FTS functionality, or even the integrity-check, in any way. ** -** Instead, it tests that the same set of pgno/rowid combinations are +** Instead, it tests that the same set of pgno/rowid combinations are ** visited regardless of whether the doclist-index identified by parameters ** iSegid/iLeaf is iterated in forwards or reverse order. */ static void fts5TestDlidxReverse( - Fts5Index *p, + Fts5Index *p, int iSegid, /* Segment id to load from */ int iLeaf /* Load doclist-index for this leaf */ ){ @@ -214561,9 +259095,11 @@ static int fts5QueryCksum( int eDetail = p->pConfig->eDetail; u64 cksum = *pCksum; Fts5IndexIter *pIter = 0; - int rc = sqlite3Fts5IndexQuery(p, z, n, flags, 0, &pIter); + int rc = sqlite3Fts5IndexQuery( + p, z, n, (flags | FTS5INDEX_QUERY_NOTOKENDATA), 0, &pIter + ); - while( rc==SQLITE_OK && 0==sqlite3Fts5IterEof(pIter) ){ + while( rc==SQLITE_OK && ALWAYS(pIter!=0) && 0==sqlite3Fts5IterEof(pIter) ){ i64 rowid = pIter->iRowid; if( eDetail==FTS5_DETAIL_NONE ){ @@ -214583,29 +259119,68 @@ static int fts5QueryCksum( rc = sqlite3Fts5IterNext(pIter); } } - sqlite3Fts5IterClose(pIter); + fts5IterClose(pIter); *pCksum = cksum; return rc; } +/* +** Check if buffer z[], size n bytes, contains as series of valid utf-8 +** encoded codepoints. If so, return 0. Otherwise, if the buffer does not +** contain valid utf-8, return non-zero. +*/ +static int fts5TestUtf8(const char *z, int n){ + int i = 0; + assert_nc( n>0 ); + while( i=n || (z[i+1] & 0xC0)!=0x80 ) return 1; + i += 2; + }else + if( (z[i] & 0xF0)==0xE0 ){ + if( i+2>=n || (z[i+1] & 0xC0)!=0x80 || (z[i+2] & 0xC0)!=0x80 ) return 1; + i += 3; + }else + if( (z[i] & 0xF8)==0xF0 ){ + if( i+3>=n || (z[i+1] & 0xC0)!=0x80 || (z[i+2] & 0xC0)!=0x80 ) return 1; + if( (z[i+2] & 0xC0)!=0x80 ) return 1; + i += 3; + }else{ + return 1; + } + } + + return 0; +} /* -** This function is also purely an internal test. It does not contribute to +** This function is also purely an internal test. It does not contribute to ** FTS functionality, or even the integrity-check, in any way. +** +** This function sets output variable (*pbFail) to true if the test fails. Or +** leaves it unchanged if the test succeeds. */ static void fts5TestTerm( - Fts5Index *p, + Fts5Index *p, Fts5Buffer *pPrev, /* Previous term */ const char *z, int n, /* Possibly new term to test */ u64 expected, - u64 *pCksum + u64 *pCksum, + int *pbFail ){ int rc = p->rc; if( pPrev->n==0 ){ fts5BufferSet(&rc, pPrev, n, (const u8*)z); }else - if( rc==SQLITE_OK && (pPrev->n!=n || memcmp(pPrev->p, z, n)) ){ + if( *pbFail==0 + && rc==SQLITE_OK + && (pPrev->n!=n || memcmp(pPrev->p, z, n)) + && (p->pHash==0 || p->pHash->nEntry==0) + ){ u64 cksum3 = *pCksum; const char *zTerm = (const char*)&pPrev->p[1]; /* term sans prefix-byte */ int nTerm = pPrev->n-1; /* Size of zTerm in bytes */ @@ -214624,13 +259199,19 @@ static void fts5TestTerm( if( rc==SQLITE_OK && ck1!=ck2 ) rc = FTS5_CORRUPT; /* If this is a prefix query, check that the results returned if the - ** the index is disabled are the same. In both ASC and DESC order. + ** the index is disabled are the same. In both ASC and DESC order. ** ** This check may only be performed if the hash table is empty. This ** is because the hash table only supports a single scan query at ** a time, and the multi-iter loop from which this function is called - ** is already performing such a scan. */ - if( p->nPendingData==0 ){ + ** is already performing such a scan. + ** + ** Also only do this if buffer zTerm contains nTerm bytes of valid + ** utf-8. Otherwise, the last part of the buffer contents might contain + ** a non-utf-8 sequence that happens to be a prefix of a valid utf-8 + ** character stored in the main fts index, which will cause the + ** test to fail. */ + if( p->nPendingData==0 && 0==fts5TestUtf8(zTerm, nTerm) ){ if( iIdx>0 && rc==SQLITE_OK ){ int f = flags|FTS5INDEX_QUERY_TEST_NOIDX; ck2 = 0; @@ -214649,16 +259230,16 @@ static void fts5TestTerm( fts5BufferSet(&rc, pPrev, n, (const u8*)z); if( rc==SQLITE_OK && cksum3!=expected ){ - rc = FTS5_CORRUPT; + *pbFail = 1; } *pCksum = cksum3; } p->rc = rc; } - + #else # define fts5TestDlidxReverse(x,y,z) -# define fts5TestTerm(u,v,w,x,y,z) +# define fts5TestTerm(t,u,v,w,x,y,z) #endif /* @@ -214681,17 +259262,20 @@ static void fts5IndexIntegrityCheckEmpty( /* Now check that the iter.nEmpty leaves following the current leaf ** (a) exist and (b) contain no terms. */ for(i=iFirst; p->rc==SQLITE_OK && i<=iLast; i++){ - Fts5Data *pLeaf = fts5DataRead(p, FTS5_SEGMENT_ROWID(pSeg->iSegid, i)); + Fts5Data *pLeaf = fts5LeafRead(p, FTS5_SEGMENT_ROWID(pSeg->iSegid, i)); if( pLeaf ){ - if( !fts5LeafIsTermless(pLeaf) ) p->rc = FTS5_CORRUPT; - if( i>=iNoRowid && 0!=fts5LeafFirstRowidOff(pLeaf) ) p->rc = FTS5_CORRUPT; + if( !fts5LeafIsTermless(pLeaf) + || (i>=iNoRowid && 0!=fts5LeafFirstRowidOff(pLeaf)) + ){ + FTS5_CORRUPT_ROWID(p, FTS5_SEGMENT_ROWID(pSeg->iSegid, i)); + } } fts5DataRelease(pLeaf); } } -static void fts5IntegrityCheckPgidx(Fts5Index *p, Fts5Data *pLeaf){ - int iTermOff = 0; +static void fts5IntegrityCheckPgidx(Fts5Index *p, i64 iRowid, Fts5Data *pLeaf){ + i64 iTermOff = 0; int ii; Fts5Buffer buf1 = {0,0,0}; @@ -214700,7 +259284,7 @@ static void fts5IntegrityCheckPgidx(Fts5Index *p, Fts5Data *pLeaf){ ii = pLeaf->szLeaf; while( iinn && p->rc==SQLITE_OK ){ int res; - int iOff; + i64 iOff; int nIncr; ii += fts5GetVarint32(&pLeaf->p[ii], nIncr); @@ -214708,12 +259292,12 @@ static void fts5IntegrityCheckPgidx(Fts5Index *p, Fts5Data *pLeaf){ iOff = iTermOff; if( iOff>=pLeaf->szLeaf ){ - p->rc = FTS5_CORRUPT; + FTS5_CORRUPT_ROWID(p, iRowid); }else if( iTermOff==nIncr ){ int nByte; iOff += fts5GetVarint32(&pLeaf->p[iOff], nByte); if( (iOff+nByte)>pLeaf->szLeaf ){ - p->rc = FTS5_CORRUPT; + FTS5_CORRUPT_ROWID(p, iRowid); }else{ fts5BufferSet(&p->rc, &buf1, nByte, &pLeaf->p[iOff]); } @@ -214722,7 +259306,7 @@ static void fts5IntegrityCheckPgidx(Fts5Index *p, Fts5Data *pLeaf){ iOff += fts5GetVarint32(&pLeaf->p[iOff], nKeep); iOff += fts5GetVarint32(&pLeaf->p[iOff], nByte); if( nKeep>buf1.n || (iOff+nByte)>pLeaf->szLeaf ){ - p->rc = FTS5_CORRUPT; + FTS5_CORRUPT_ROWID(p, iRowid); }else{ buf1.n = nKeep; fts5BufferAppendBlob(&p->rc, &buf1, nByte, &pLeaf->p[iOff]); @@ -214730,7 +259314,7 @@ static void fts5IntegrityCheckPgidx(Fts5Index *p, Fts5Data *pLeaf){ if( p->rc==SQLITE_OK ){ res = fts5BufferCompare(&buf1, &buf2); - if( res<=0 ) p->rc = FTS5_CORRUPT; + if( res<=0 ) FTS5_CORRUPT_ROWID(p, iRowid); } } fts5BufferSet(&p->rc, &buf2, buf1.n, buf1.p); @@ -214745,6 +259329,7 @@ static void fts5IndexIntegrityCheckSegment( Fts5StructureSegment *pSeg /* Segment to check internal consistency */ ){ Fts5Config *pConfig = p->pConfig; + int bSecureDelete = (pConfig->iVersion==FTS5_CURRENT_VERSION_SECUREDELETE); sqlite3_stmt *pStmt = 0; int rc2; int iIdxPrevLeaf = pSeg->pgnoFirst-1; @@ -214753,7 +259338,8 @@ static void fts5IndexIntegrityCheckSegment( if( pSeg->pgnoFirst==0 ) return; fts5IndexPrepareStmt(p, &pStmt, sqlite3_mprintf( - "SELECT segid, term, (pgno>>1), (pgno&1) FROM %Q.'%q_idx' WHERE segid=%d", + "SELECT segid, term, (pgno>>1), (pgno&1) FROM %Q.'%q_idx' WHERE segid=%d " + "ORDER BY 1, 2", pConfig->zDb, pConfig->zName, pSeg->iSegid )); @@ -214762,12 +259348,12 @@ static void fts5IndexIntegrityCheckSegment( i64 iRow; /* Rowid for this leaf */ Fts5Data *pLeaf; /* Data for this leaf */ + const char *zIdxTerm = (const char*)sqlite3_column_blob(pStmt, 1); int nIdxTerm = sqlite3_column_bytes(pStmt, 1); - const char *zIdxTerm = (const char*)sqlite3_column_text(pStmt, 1); int iIdxLeaf = sqlite3_column_int(pStmt, 2); int bIdxDlidx = sqlite3_column_int(pStmt, 3); - /* If the leaf in question has already been trimmed from the segment, + /* If the leaf in question has already been trimmed from the segment, ** ignore this b-tree entry. Otherwise, load it into memory. */ if( iIdxLeafpgnoFirst ) continue; iRow = FTS5_SEGMENT_ROWID(pSeg->iSegid, iIdxLeaf); @@ -214779,7 +259365,19 @@ static void fts5IndexIntegrityCheckSegment( ** is also a rowid pointer within the leaf page header, it points to a ** location before the term. */ if( pLeaf->nn<=pLeaf->szLeaf ){ - p->rc = FTS5_CORRUPT; + + if( nIdxTerm==0 + && pConfig->iVersion==FTS5_CURRENT_VERSION_SECUREDELETE + && pLeaf->nn==pLeaf->szLeaf + && pLeaf->nn==4 + ){ + /* special case - the very first page in a segment keeps its %_idx + ** entry even if all the terms are removed from it by secure-delete + ** operations. */ + }else{ + FTS5_CORRUPT_ROWID(p, iRow); + } + }else{ int iOff; /* Offset of first term on leaf */ int iRowidOff; /* Offset of first rowid on leaf */ @@ -214789,15 +259387,19 @@ static void fts5IndexIntegrityCheckSegment( iOff = fts5LeafFirstTermOff(pLeaf); iRowidOff = fts5LeafFirstRowidOff(pLeaf); if( iRowidOff>=iOff || iOff>=pLeaf->szLeaf ){ - p->rc = FTS5_CORRUPT; + FTS5_CORRUPT_ROWID(p, iRow); }else{ iOff += fts5GetVarint32(&pLeaf->p[iOff], nTerm); - res = fts5Memcmp(&pLeaf->p[iOff], zIdxTerm, MIN(nTerm, nIdxTerm)); - if( res==0 ) res = nTerm - nIdxTerm; - if( res<0 ) p->rc = FTS5_CORRUPT; + if( (i64)iOff+(i64)nTerm>(i64)pLeaf->szLeaf ){ + FTS5_CORRUPT_ROWID(p, iRow); + }else{ + res = fts5Memcmp(&pLeaf->p[iOff], zIdxTerm, MIN(nTerm, nIdxTerm)); + if( res==0 ) res = nTerm - nIdxTerm; + if( res<0 ) FTS5_CORRUPT_ROWID(p, iRow); + } } - fts5IntegrityCheckPgidx(p, pLeaf); + fts5IntegrityCheckPgidx(p, iRow, pLeaf); } fts5DataRelease(pLeaf); if( p->rc ) break; @@ -214825,9 +259427,9 @@ static void fts5IndexIntegrityCheckSegment( /* Check any rowid-less pages that occur before the current leaf. */ for(iPg=iPrevLeaf+1; iPgrc = FTS5_CORRUPT; + if( fts5LeafFirstRowidOff(pLeaf)!=0 ) FTS5_CORRUPT_ROWID(p, iKey); fts5DataRelease(pLeaf); } } @@ -214836,16 +259438,19 @@ static void fts5IndexIntegrityCheckSegment( /* Check that the leaf page indicated by the iterator really does ** contain the rowid suggested by the same. */ iKey = FTS5_SEGMENT_ROWID(iSegid, iPrevLeaf); - pLeaf = fts5DataRead(p, iKey); + pLeaf = fts5LeafRead(p, iKey); if( pLeaf ){ i64 iRowid; int iRowidOff = fts5LeafFirstRowidOff(pLeaf); ASSERT_SZLEAF_OK(pLeaf); if( iRowidOff>=pLeaf->szLeaf ){ - p->rc = FTS5_CORRUPT; - }else{ + FTS5_CORRUPT_ROWID(p, iKey); + }else if( bSecureDelete==0 || iRowidOff>0 ){ + i64 iDlRowid = fts5DlidxIterRowid(pDlidx); fts5GetVarint(&pLeaf->p[iRowidOff], (u64*)&iRowid); - if( iRowid!=fts5DlidxIterRowid(pDlidx) ) p->rc = FTS5_CORRUPT; + if( iRowidpConfig->eDetail; u64 cksum2 = 0; /* Checksum based on contents of indexes */ Fts5Buffer poslist = {0,0,0}; /* Buffer used to hold a poslist */ Fts5Iter *pIter; /* Used to iterate through entire index */ Fts5Structure *pStruct; /* Index structure */ + int iLvl, iSeg; #ifdef SQLITE_DEBUG /* Used by extra internal tests only run if NDEBUG is not defined */ u64 cksum3 = 0; /* Checksum based on contents of indexes */ Fts5Buffer term = {0,0,0}; /* Buffer used to hold most recent term */ + int bTestFail = 0; #endif const int flags = FTS5INDEX_QUERY_NOOUTPUT; - + /* Load the FTS index structure */ pStruct = fts5StructureRead(p); + if( pStruct==0 ){ + assert( p->rc!=SQLITE_OK ); + return fts5IndexReturn(p); + } /* Check that the internal nodes of each segment match the leaves */ - if( pStruct ){ - int iLvl, iSeg; - for(iLvl=0; iLvlnLevel; iLvl++){ - for(iSeg=0; iSegaLevel[iLvl].nSeg; iSeg++){ - Fts5StructureSegment *pSeg = &pStruct->aLevel[iLvl].aSeg[iSeg]; - fts5IndexIntegrityCheckSegment(p, pSeg); - } + for(iLvl=0; iLvlnLevel; iLvl++){ + for(iSeg=0; iSegaLevel[iLvl].nSeg; iSeg++){ + Fts5StructureSegment *pSeg = &pStruct->aLevel[iLvl].aSeg[iSeg]; + fts5IndexIntegrityCheckSegment(p, pSeg); } } @@ -214919,7 +259527,7 @@ static int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum){ ** ** Two versions of the same checksum are calculated. The first (stack ** variable cksum2) based on entries extracted from the full-text index - ** while doing a linear scan of each individual index in turn. + ** while doing a linear scan of each individual index in turn. ** ** As each term visited by the linear scans, a separate query for the ** same term is performed. cksum3 is calculated based on the entries @@ -214936,7 +259544,8 @@ static int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum){ char *z = (char*)fts5MultiIterTerm(pIter, &n); /* If this is a new term, query for it. Update cksum3 with the results. */ - fts5TestTerm(p, &term, z, n, cksum2, &cksum3); + fts5TestTerm(p, &term, z, n, cksum2, &cksum3, &bTestFail); + if( p->rc ) break; if( eDetail==FTS5_DETAIL_NONE ){ if( 0==fts5MultiIterIsEmpty(p, pIter) ){ @@ -214945,6 +259554,7 @@ static int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum){ }else{ poslist.n = 0; fts5SegiterPoslist(p, &pIter->aSeg[pIter->aFirst[1].iFirst], 0, &poslist); + fts5BufferAppendBlob(&p->rc, &poslist, 4, (const u8*)"\0\0\0\0"); while( 0==sqlite3Fts5PoslistNext64(poslist.p, poslist.n, &iOff, &iPos) ){ int iCol = FTS5_POS2COLUMN(iPos); int iTokOff = FTS5_POS2OFFSET(iPos); @@ -214952,15 +259562,26 @@ static int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum){ } } } - fts5TestTerm(p, &term, 0, 0, cksum2, &cksum3); + fts5TestTerm(p, &term, 0, 0, cksum2, &cksum3, &bTestFail); fts5MultiIterFree(pIter); - if( p->rc==SQLITE_OK && cksum!=cksum2 ) p->rc = FTS5_CORRUPT; - - fts5StructureRelease(pStruct); + if( p->rc==SQLITE_OK && bUseCksum && cksum!=cksum2 ){ + p->rc = FTS5_CORRUPT; + sqlite3Fts5ConfigErrmsg(p->pConfig, + "fts5: checksum mismatch for table \"%s\"", p->pConfig->zName + ); + } #ifdef SQLITE_DEBUG + /* In SQLITE_DEBUG builds, expensive extra checks were run as part of + ** the integrity-check above. If no other errors were detected, but one + ** of these tests failed, set the result to SQLITE_CORRUPT_VTAB here. */ + if( p->rc==SQLITE_OK && bTestFail ){ + p->rc = FTS5_CORRUPT; + } fts5BufferFree(&term); #endif + + fts5StructureRelease(pStruct); fts5BufferFree(&poslist); return fts5IndexReturn(p); } @@ -214971,12 +259592,14 @@ static int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum){ ** function only. */ +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) /* ** Decode a segment-data rowid from the %_data table. This function is ** the opposite of macro FTS5_SEGMENT_ROWID(). */ static void fts5DecodeRowid( i64 iRowid, /* Rowid from %_data table */ + int *pbTombstone, /* OUT: Tombstone hash flag */ int *piSegid, /* OUT: Segment id */ int *pbDlidx, /* OUT: Dlidx flag */ int *piHeight, /* OUT: Height */ @@ -214992,11 +259615,16 @@ static void fts5DecodeRowid( iRowid >>= FTS5_DATA_DLI_B; *piSegid = (int)(iRowid & (((i64)1 << FTS5_DATA_ID_B) - 1)); + iRowid >>= FTS5_DATA_ID_B; + + *pbTombstone = (int)(iRowid & 0x0001); } +#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) static void fts5DebugRowid(int *pRc, Fts5Buffer *pBuf, i64 iKey){ - int iSegid, iHeight, iPgno, bDlidx; /* Rowid compenents */ - fts5DecodeRowid(iKey, &iSegid, &bDlidx, &iHeight, &iPgno); + int iSegid, iHeight, iPgno, bDlidx, bTomb; /* Rowid components */ + fts5DecodeRowid(iKey, &bTomb, &iSegid, &bDlidx, &iHeight, &iPgno); if( iSegid==0 ){ if( iKey==FTS5_AVERAGES_ROWID ){ @@ -215006,12 +259634,16 @@ static void fts5DebugRowid(int *pRc, Fts5Buffer *pBuf, i64 iKey){ } } else{ - sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "{%ssegid=%d h=%d pgno=%d}", - bDlidx ? "dlidx " : "", iSegid, iHeight, iPgno + sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "{%s%ssegid=%d h=%d pgno=%d}", + bDlidx ? "dlidx " : "", + bTomb ? "tombstone " : "", + iSegid, iHeight, iPgno ); } } +#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) static void fts5DebugStructure( int *pRc, /* IN/OUT: error code */ Fts5Buffer *pBuf, @@ -215021,25 +259653,33 @@ static void fts5DebugStructure( for(iLvl=0; iLvlnLevel; iLvl++){ Fts5StructureLevel *pLvl = &p->aLevel[iLvl]; - sqlite3Fts5BufferAppendPrintf(pRc, pBuf, + sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " {lvl=%d nMerge=%d nSeg=%d", iLvl, pLvl->nMerge, pLvl->nSeg ); for(iSeg=0; iSegnSeg; iSeg++){ Fts5StructureSegment *pSeg = &pLvl->aSeg[iSeg]; - sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " {id=%d leaves=%d..%d}", + sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " {id=%d leaves=%d..%d", pSeg->iSegid, pSeg->pgnoFirst, pSeg->pgnoLast ); + if( pSeg->iOrigin1>0 ){ + sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " origin=%lld..%lld", + pSeg->iOrigin1, pSeg->iOrigin2 + ); + } + sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "}"); } sqlite3Fts5BufferAppendPrintf(pRc, pBuf, "}"); } } +#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) /* ** This is part of the fts5_decode() debugging aid. ** ** Arguments pBlob/nBlob contain a serialized Fts5Structure object. This ** function appends a human-readable representation of the same object -** to the buffer passed as the second argument. +** to the buffer passed as the second argument. */ static void fts5DecodeStructure( int *pRc, /* IN/OUT: error code */ @@ -215058,13 +259698,15 @@ static void fts5DecodeStructure( fts5DebugStructure(pRc, pBuf, p); fts5StructureRelease(p); } +#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) /* ** This is part of the fts5_decode() debugging aid. ** -** Arguments pBlob/nBlob contain an "averages" record. This function -** appends a human-readable representation of record to the buffer passed -** as the second argument. +** Arguments pBlob/nBlob contain an "averages" record. This function +** appends a human-readable representation of record to the buffer passed +** as the second argument. */ static void fts5DecodeAverages( int *pRc, /* IN/OUT: error code */ @@ -215081,7 +259723,9 @@ static void fts5DecodeAverages( zSpace = " "; } } +#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) /* ** Buffer (a/n) is assumed to contain a list of serialized varints. Read ** each varint and append its string representation to buffer pBuf. Return @@ -215098,12 +259742,14 @@ static int fts5DecodePoslist(int *pRc, Fts5Buffer *pBuf, const u8 *a, int n){ } return iOff; } +#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) /* ** The start of buffer (a/n) contains the start of a doclist. The doclist ** may or may not finish within the buffer. This function appends a text ** representation of the part of the doclist that is present to buffer -** pBuf. +** pBuf. ** ** The return value is the number of bytes read from the input buffer. */ @@ -215131,9 +259777,11 @@ static int fts5DecodeDoclist(int *pRc, Fts5Buffer *pBuf, const u8 *a, int n){ return iOff; } +#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) /* -** This function is part of the fts5_decode() debugging function. It is +** This function is part of the fts5_decode() debugging function. It is ** only ever used with detail=none tables. ** ** Buffer (pData/nData) contains a doclist in the format used by detail=none @@ -215172,7 +259820,27 @@ static void fts5DecodeRowidList( sqlite3Fts5BufferAppendPrintf(pRc, pBuf, " %lld%s", iRowid, zApp); } } +#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ + +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) +static void fts5BufferAppendTerm(int *pRc, Fts5Buffer *pBuf, Fts5Buffer *pTerm){ + int ii; + fts5BufferGrow(pRc, pBuf, pTerm->n*2 + 1); + if( *pRc==SQLITE_OK ){ + for(ii=0; iin; ii++){ + if( pTerm->p[ii]==0x00 ){ + pBuf->p[pBuf->n++] = '\\'; + pBuf->p[pBuf->n++] = '0'; + }else{ + pBuf->p[pBuf->n++] = pTerm->p[ii]; + } + } + pBuf->p[pBuf->n] = 0x00; + } +} +#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) /* ** The implementation of user-defined scalar function fts5_decode(). */ @@ -215183,6 +259851,7 @@ static void fts5DecodeFunction( ){ i64 iRowid; /* Rowid for record being decoded */ int iSegid,iHeight,iPgno,bDlidx;/* Rowid components */ + int bTomb; const u8 *aBlob; int n; /* Record to decode */ u8 *a = 0; Fts5Buffer s; /* Build up text to return here */ @@ -215200,12 +259869,12 @@ static void fts5DecodeFunction( ** buffer overreads even if the record is corrupt. */ n = sqlite3_value_bytes(apVal[1]); aBlob = sqlite3_value_blob(apVal[1]); - nSpace = n + FTS5_DATA_ZERO_PADDING; + nSpace = ((i64)n) + FTS5_DATA_ZERO_PADDING; a = (u8*)sqlite3Fts5MallocZero(&rc, nSpace); if( a==0 ) goto decode_out; if( n>0 ) memcpy(a, aBlob, n); - fts5DecodeRowid(iRowid, &iSegid, &bDlidx, &iHeight, &iPgno); + fts5DecodeRowid(iRowid, &bTomb, &iSegid, &bDlidx, &iHeight, &iPgno); fts5DebugRowid(&rc, &s, iRowid); if( bDlidx ){ @@ -215220,10 +259889,32 @@ static void fts5DecodeFunction( lvl.iLeafPgno = iPgno; for(fts5DlidxLvlNext(&lvl); lvl.bEof==0; fts5DlidxLvlNext(&lvl)){ - sqlite3Fts5BufferAppendPrintf(&rc, &s, + sqlite3Fts5BufferAppendPrintf(&rc, &s, " %d(%lld)", lvl.iLeafPgno, lvl.iRowid ); } + }else if( bTomb ){ + u32 nElem = fts5GetU32(&a[4]); + int szKey = (aBlob[0]==4 || aBlob[0]==8) ? aBlob[0] : 8; + int nSlot = (n - 8) / szKey; + int ii; + sqlite3Fts5BufferAppendPrintf(&rc, &s, " nElem=%d", (int)nElem); + if( aBlob[1] ){ + sqlite3Fts5BufferAppendPrintf(&rc, &s, " 0"); + } + for(ii=0; iiestimatedCost = (double)100; + pIdxInfo->estimatedRows = 100; + pIdxInfo->idxNum = 0; + for(i=0, p=pIdxInfo->aConstraint; inConstraint; i++, p++){ + if( p->usable==0 ) continue; + if( p->op==SQLITE_INDEX_CONSTRAINT_EQ && p->iColumn==11 ){ + rc = SQLITE_OK; + pIdxInfo->aConstraintUsage[i].omit = 1; + pIdxInfo->aConstraintUsage[i].argvIndex = 1; + break; + } + } + return rc; +} + +/* +** This method is the destructor for bytecodevtab objects. +*/ +static int fts5structDisconnectMethod(sqlite3_vtab *pVtab){ + Fts5StructVtab *p = (Fts5StructVtab*)pVtab; + sqlite3_free(p); + return SQLITE_OK; +} + +/* +** Constructor for a new bytecodevtab_cursor object. +*/ +static int fts5structOpenMethod(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCsr){ + int rc = SQLITE_OK; + Fts5StructVcsr *pNew = 0; + + pNew = sqlite3Fts5MallocZero(&rc, sizeof(*pNew)); + *ppCsr = (sqlite3_vtab_cursor*)pNew; + + return SQLITE_OK; +} + +/* +** Destructor for a bytecodevtab_cursor. +*/ +static int fts5structCloseMethod(sqlite3_vtab_cursor *cur){ + Fts5StructVcsr *pCsr = (Fts5StructVcsr*)cur; + fts5StructureRelease(pCsr->pStruct); + sqlite3_free(pCsr); + return SQLITE_OK; +} + + +/* +** Advance a bytecodevtab_cursor to its next row of output. +*/ +static int fts5structNextMethod(sqlite3_vtab_cursor *cur){ + Fts5StructVcsr *pCsr = (Fts5StructVcsr*)cur; + Fts5Structure *p = pCsr->pStruct; + + assert( pCsr->pStruct ); + pCsr->iSeg++; + pCsr->iRowid++; + while( pCsr->iLevelnLevel && pCsr->iSeg>=p->aLevel[pCsr->iLevel].nSeg ){ + pCsr->iLevel++; + pCsr->iSeg = 0; + } + if( pCsr->iLevel>=p->nLevel ){ + fts5StructureRelease(pCsr->pStruct); + pCsr->pStruct = 0; + } + return SQLITE_OK; +} + +/* +** Return TRUE if the cursor has been moved off of the last +** row of output. +*/ +static int fts5structEofMethod(sqlite3_vtab_cursor *cur){ + Fts5StructVcsr *pCsr = (Fts5StructVcsr*)cur; + return pCsr->pStruct==0; +} + +static int fts5structRowidMethod( + sqlite3_vtab_cursor *cur, + sqlite_int64 *piRowid +){ + Fts5StructVcsr *pCsr = (Fts5StructVcsr*)cur; + *piRowid = pCsr->iRowid; + return SQLITE_OK; +} + +/* +** Return values of columns for the row at which the bytecodevtab_cursor +** is currently pointing. +*/ +static int fts5structColumnMethod( + sqlite3_vtab_cursor *cur, /* The cursor */ + sqlite3_context *ctx, /* First argument to sqlite3_result_...() */ + int i /* Which column to return */ +){ + Fts5StructVcsr *pCsr = (Fts5StructVcsr*)cur; + Fts5Structure *p = pCsr->pStruct; + Fts5StructureSegment *pSeg = &p->aLevel[pCsr->iLevel].aSeg[pCsr->iSeg]; + + switch( i ){ + case 0: /* level */ + sqlite3_result_int(ctx, pCsr->iLevel); + break; + case 1: /* segment */ + sqlite3_result_int(ctx, pCsr->iSeg); + break; + case 2: /* merge */ + sqlite3_result_int(ctx, pCsr->iSeg < p->aLevel[pCsr->iLevel].nMerge); + break; + case 3: /* segid */ + sqlite3_result_int(ctx, pSeg->iSegid); + break; + case 4: /* leaf1 */ + sqlite3_result_int(ctx, pSeg->pgnoFirst); + break; + case 5: /* leaf2 */ + sqlite3_result_int(ctx, pSeg->pgnoLast); + break; + case 6: /* origin1 */ + sqlite3_result_int64(ctx, pSeg->iOrigin1); + break; + case 7: /* origin2 */ + sqlite3_result_int64(ctx, pSeg->iOrigin2); + break; + case 8: /* npgtombstone */ + sqlite3_result_int(ctx, pSeg->nPgTombstone); + break; + case 9: /* nentrytombstone */ + sqlite3_result_int64(ctx, pSeg->nEntryTombstone); + break; + case 10: /* nentry */ + sqlite3_result_int64(ctx, pSeg->nEntry); + break; + } + return SQLITE_OK; +} + +/* +** Initialize a cursor. +** +** idxNum==0 means show all subprograms +** idxNum==1 means show only the main bytecode and omit subprograms. +*/ +static int fts5structFilterMethod( + sqlite3_vtab_cursor *pVtabCursor, + int idxNum, const char *idxStr, + int argc, sqlite3_value **argv +){ + Fts5StructVcsr *pCsr = (Fts5StructVcsr *)pVtabCursor; + int rc = SQLITE_OK; + + const u8 *aBlob = 0; + int nBlob = 0; + + assert( argc==1 ); + fts5StructureRelease(pCsr->pStruct); + pCsr->pStruct = 0; + + nBlob = sqlite3_value_bytes(argv[0]); + aBlob = (const u8*)sqlite3_value_blob(argv[0]); + rc = fts5StructureDecode(aBlob, nBlob, 0, &pCsr->pStruct); + if( rc==SQLITE_OK ){ + pCsr->iLevel = 0; + pCsr->iRowid = 0; + pCsr->iSeg = -1; + rc = fts5structNextMethod(pVtabCursor); + } + + return rc; +} + +#endif /* SQLITE_TEST || SQLITE_FTS5_DEBUG */ /* ** This is called as part of registering the FTS5 module with database @@ -215425,13 +260348,14 @@ static void fts5RowidFunction( ** SQLite error code is returned instead. */ static int sqlite3Fts5IndexInit(sqlite3 *db){ +#if defined(SQLITE_TEST) || defined(SQLITE_FTS5_DEBUG) int rc = sqlite3_create_function( db, "fts5_decode", 2, SQLITE_UTF8, 0, fts5DecodeFunction, 0, 0 ); if( rc==SQLITE_OK ){ rc = sqlite3_create_function( - db, "fts5_decode_none", 2, + db, "fts5_decode_none", 2, SQLITE_UTF8, (void*)db, fts5DecodeFunction, 0, 0 ); } @@ -215441,7 +260365,42 @@ static int sqlite3Fts5IndexInit(sqlite3 *db){ db, "fts5_rowid", -1, SQLITE_UTF8, 0, fts5RowidFunction, 0, 0 ); } + + if( rc==SQLITE_OK ){ + static const sqlite3_module fts5structure_module = { + 0, /* iVersion */ + 0, /* xCreate */ + fts5structConnectMethod, /* xConnect */ + fts5structBestIndexMethod, /* xBestIndex */ + fts5structDisconnectMethod, /* xDisconnect */ + 0, /* xDestroy */ + fts5structOpenMethod, /* xOpen */ + fts5structCloseMethod, /* xClose */ + fts5structFilterMethod, /* xFilter */ + fts5structNextMethod, /* xNext */ + fts5structEofMethod, /* xEof */ + fts5structColumnMethod, /* xColumn */ + fts5structRowidMethod, /* xRowid */ + 0, /* xUpdate */ + 0, /* xBegin */ + 0, /* xSync */ + 0, /* xCommit */ + 0, /* xRollback */ + 0, /* xFindFunction */ + 0, /* xRename */ + 0, /* xSavepoint */ + 0, /* xRelease */ + 0, /* xRollbackTo */ + 0, /* xShadowName */ + 0 /* xIntegrity */ + }; + rc = sqlite3_create_module(db, "fts5_structure", &fts5structure_module, 0); + } return rc; +#else + return SQLITE_OK; + UNUSED_PARAM(db); +#endif } @@ -215477,7 +260436,9 @@ static int sqlite3Fts5IndexReset(Fts5Index *p){ ** assert() conditions in the fts5 code are activated - conditions that are ** only true if it is guaranteed that the fts5 database is not corrupt. */ +#ifdef SQLITE_DEBUG SQLITE_API int sqlite3_fts5_may_be_corrupt = 1; +#endif typedef struct Fts5Auxdata Fts5Auxdata; @@ -215488,9 +260449,9 @@ typedef struct Fts5Sorter Fts5Sorter; typedef struct Fts5TokenizerModule Fts5TokenizerModule; /* -** NOTES ON TRANSACTIONS: +** NOTES ON TRANSACTIONS: ** -** SQLite invokes the following virtual table methods as transactions are +** SQLite invokes the following virtual table methods as transactions are ** opened and closed by the user: ** ** xBegin(): Start of a new transaction. @@ -215499,7 +260460,7 @@ typedef struct Fts5TokenizerModule Fts5TokenizerModule; ** xRollback(): Rollback the transaction. ** ** Anything that is required as part of a commit that may fail is performed -** in the xSync() callback. Current versions of SQLite ignore any errors +** in the xSync() callback. Current versions of SQLite ignore any errors ** returned by xCommit(). ** ** And as sub-transactions are opened/closed: @@ -215508,9 +260469,9 @@ typedef struct Fts5TokenizerModule Fts5TokenizerModule; ** xRelease(int S): Commit and close savepoint S. ** xRollbackTo(int S): Rollback to start of savepoint S. ** -** During a write-transaction the fts5_index.c module may cache some data +** During a write-transaction the fts5_index.c module may cache some data ** in-memory. It is flushed to disk whenever xSync(), xRelease() or -** xSavepoint() is called. And discarded whenever xRollback() or xRollbackTo() +** xSavepoint() is called. And discarded whenever xRollback() or xRollbackTo() ** is called. ** ** Additionally, if SQLITE_DEBUG is defined, an instance of the following @@ -215524,20 +260485,30 @@ struct Fts5TransactionState { }; /* -** A single object of this type is allocated when the FTS5 module is +** A single object of this type is allocated when the FTS5 module is ** registered with a database handle. It is used to store pointers to ** all registered FTS5 extensions - tokenizers and auxiliary functions. */ struct Fts5Global { fts5_api api; /* User visible part of object (see fts5.h) */ - sqlite3 *db; /* Associated database connection */ + sqlite3 *db; /* Associated database connection */ i64 iNextId; /* Used to allocate unique cursor ids */ Fts5Auxiliary *pAux; /* First in list of all aux. functions */ Fts5TokenizerModule *pTok; /* First in list of all tokenizer modules */ Fts5TokenizerModule *pDfltTok; /* Default tokenizer module */ Fts5Cursor *pCsr; /* First in list of all open cursors */ + u32 aLocaleHdr[4]; }; +/* +** Size of header on fts5_locale() values. And macro to access a buffer +** containing a copy of the header from an Fts5Config pointer. +*/ +#define FTS5_LOCALE_HDR_SIZE ((int)sizeof( ((Fts5Global*)0)->aLocaleHdr )) +#define FTS5_LOCALE_HDR(pConfig) ((const u8*)(pConfig->pGlobal->aLocaleHdr)) + +#define FTS5_INSTTOKEN_SUBTYPE 73 + /* ** Each auxiliary function registered with the FTS5 module is represented ** by an object of the following type. All such objects are stored as part @@ -215556,11 +260527,28 @@ struct Fts5Auxiliary { ** Each tokenizer module registered with the FTS5 module is represented ** by an object of the following type. All such objects are stored as part ** of the Fts5Global.pTok list. +** +** bV2Native: +** True if the tokenizer was registered using xCreateTokenizer_v2(), false +** for xCreateTokenizer(). If this variable is true, then x2 is populated +** with the routines as supplied by the caller and x1 contains synthesized +** wrapper routines. In this case the user-data pointer passed to +** x1.xCreate should be a pointer to the Fts5TokenizerModule structure, +** not a copy of pUserData. +** +** Of course, if bV2Native is false, then x1 contains the real routines and +** x2 the synthesized ones. In this case a pointer to the Fts5TokenizerModule +** object should be passed to x2.xCreate. +** +** The synthesized wrapper routines are necessary for xFindTokenizer(_v2) +** calls. */ struct Fts5TokenizerModule { char *zName; /* Name of tokenizer */ void *pUserData; /* User pointer passed to xCreate() */ - fts5_tokenizer x; /* Tokenizer functions */ + int bV2Native; /* True if v2 native tokenizer */ + fts5_tokenizer x1; /* Tokenizer functions */ + fts5_tokenizer_v2 x2; /* V2 tokenizer functions */ void (*xDestroy)(void*); /* Destructor function */ Fts5TokenizerModule *pNext; /* Next registered tokenizer module */ }; @@ -215570,6 +260558,8 @@ struct Fts5FullTable { Fts5Storage *pStorage; /* Document store */ Fts5Global *pGlobal; /* Global (connection wide) data */ Fts5Cursor *pSortCsr; /* Sort data from this cursor */ + int iSavepoint; /* Successful xSavepoint()+1 */ + #ifdef SQLITE_DEBUG struct Fts5TransactionState ts; #endif @@ -215586,7 +260576,7 @@ struct Fts5MatchPhrase { ** ** aIdx[]: ** There is one entry in the aIdx[] array for each phrase in the query, -** the value of which is the offset within aPoslist[] following the last +** the value of which is the offset within aPoslist[] following the last ** byte of the position list for the corresponding phrase. */ struct Fts5Sorter { @@ -215594,16 +260584,18 @@ struct Fts5Sorter { i64 iRowid; /* Current rowid */ const u8 *aPoslist; /* Position lists for current row */ int nIdx; /* Number of entries in aIdx[] */ - int aIdx[1]; /* Offsets into aPoslist for current row */ + int aIdx[FLEXARRAY]; /* Offsets into aPoslist for current row */ }; +/* Size (int bytes) of an Fts5Sorter object with N indexes */ +#define SZ_FTS5SORTER(N) (offsetof(Fts5Sorter,nIdx)+((N+2)/2)*sizeof(i64)) /* ** Virtual-table cursor object. ** ** iSpecial: -** If this is a 'special' query (refer to function fts5SpecialMatch()), -** then this variable contains the result of the query. +** If this is a 'special' query (refer to function fts5SpecialMatch()), +** then this variable contains the result of the query. ** ** iFirstRowid, iLastRowid: ** These variables are only used for FTS5_PLAN_MATCH cursors. Assuming the @@ -215646,7 +260638,7 @@ struct Fts5Cursor { Fts5Auxiliary *pAux; /* Currently executing extension function */ Fts5Auxdata *pAuxdata; /* First in linked list of saved aux-data */ - /* Cache used by auxiliary functions xInst() and xInstCount() */ + /* Cache used by auxiliary API functions xInst() and xInstCount() */ Fts5PoslistReader *aInstIter; /* One for each phrase */ int nInstAlloc; /* Size of aInst[] array (entries / 3) */ int nInstCount; /* Number of phrase instances */ @@ -215654,7 +260646,7 @@ struct Fts5Cursor { }; /* -** Bits that make up the "idxNum" parameter passed indirectly by +** Bits that make up the "idxNum" parameter passed indirectly by ** xBestIndex() to xFilter(). */ #define FTS5_BI_MATCH 0x0001 /* MATCH ? */ @@ -215713,7 +260705,7 @@ static void fts5CheckTransactionState(Fts5FullTable *p, int op, int iSavepoint){ break; case FTS5_SYNC: - assert( p->ts.eState==1 ); + assert( p->ts.eState==1 || p->ts.eState==2 ); p->ts.eState = 2; break; @@ -215728,23 +260720,26 @@ static void fts5CheckTransactionState(Fts5FullTable *p, int op, int iSavepoint){ break; case FTS5_SAVEPOINT: - assert( p->ts.eState==1 ); + assert( p->ts.eState>=1 ); assert( iSavepoint>=0 ); assert( iSavepoint>=p->ts.iSavepoint ); p->ts.iSavepoint = iSavepoint; break; - + case FTS5_RELEASE: - assert( p->ts.eState==1 ); + assert( p->ts.eState>=1 ); assert( iSavepoint>=0 ); assert( iSavepoint<=p->ts.iSavepoint ); p->ts.iSavepoint = iSavepoint-1; break; case FTS5_ROLLBACKTO: - assert( p->ts.eState==1 ); + assert( p->ts.eState>=1 ); assert( iSavepoint>=-1 ); - assert( iSavepoint<=p->ts.iSavepoint ); + /* The following assert() can fail if another vtab strikes an error + ** within an xSavepoint() call then SQLite calls xRollbackTo() - without + ** having called xSavepoint() on this vtab. */ + /* assert( iSavepoint<=p->ts.iSavepoint ); */ p->ts.iSavepoint = iSavepoint; break; } @@ -215754,14 +260749,20 @@ static void fts5CheckTransactionState(Fts5FullTable *p, int op, int iSavepoint){ #endif /* -** Return true if pTab is a contentless table. +** Return true if pTab is a contentless table. If parameter bIncludeUnindexed +** is true, this includes contentless tables that store UNINDEXED columns +** only. */ -static int fts5IsContentless(Fts5FullTable *pTab){ - return pTab->p.pConfig->eContent==FTS5_CONTENT_NONE; +static int fts5IsContentless(Fts5FullTable *pTab, int bIncludeUnindexed){ + int eContent = pTab->p.pConfig->eContent; + return ( + eContent==FTS5_CONTENT_NONE + || (bIncludeUnindexed && eContent==FTS5_CONTENT_UNINDEXED) + ); } /* -** Delete a virtual table handle allocated by fts5InitVtab(). +** Delete a virtual table handle allocated by fts5InitVtab(). */ static void fts5FreeVtab(Fts5FullTable *pTab){ if( pTab ){ @@ -215825,8 +260826,12 @@ static int fts5InitVtab( assert( (rc==SQLITE_OK && *pzErr==0) || pConfig==0 ); } if( rc==SQLITE_OK ){ + pConfig->pzErrmsg = pzErr; pTab->p.pConfig = pConfig; pTab->pGlobal = pGlobal; + if( bCreate || sqlite3Fts5TokenizerPreload(&pConfig->t) ){ + rc = sqlite3Fts5LoadTokenizer(pConfig); + } } /* Open the index sub-system */ @@ -215848,13 +260853,17 @@ static int fts5InitVtab( /* Load the initial configuration */ if( rc==SQLITE_OK ){ - assert( pConfig->pzErrmsg==0 ); - pConfig->pzErrmsg = pzErr; - rc = sqlite3Fts5IndexLoadConfig(pTab->p.pIndex); - sqlite3Fts5IndexRollback(pTab->p.pIndex); - pConfig->pzErrmsg = 0; + rc = sqlite3Fts5ConfigLoad(pTab->p.pConfig, pTab->p.pConfig->iCookie-1); + } + + if( rc==SQLITE_OK && pConfig->eContent==FTS5_CONTENT_NORMAL ){ + rc = sqlite3_vtab_config(db, SQLITE_VTAB_CONSTRAINT_SUPPORT, (int)1); + } + if( rc==SQLITE_OK ){ + rc = sqlite3_vtab_config(db, SQLITE_VTAB_INNOCUOUS); } + if( pConfig ) pConfig->pzErrmsg = 0; if( rc!=SQLITE_OK ){ fts5FreeVtab(pTab); pTab = 0; @@ -215916,74 +260925,123 @@ static void fts5SetUniqueFlag(sqlite3_index_info *pIdxInfo){ #endif } +static void fts5SetEstimatedRows(sqlite3_index_info *pIdxInfo, i64 nRow){ +#if SQLITE_VERSION_NUMBER>=3008002 +#ifndef SQLITE_CORE + if( sqlite3_libversion_number()>=3008002 ) +#endif + { + pIdxInfo->estimatedRows = MAX(1, nRow); + } +#endif +} + +static int fts5UsePatternMatch( + Fts5Config *pConfig, + struct sqlite3_index_constraint *p +){ + assert( FTS5_PATTERN_GLOB==SQLITE_INDEX_CONSTRAINT_GLOB ); + assert( FTS5_PATTERN_LIKE==SQLITE_INDEX_CONSTRAINT_LIKE ); + if( pConfig->t.ePattern==FTS5_PATTERN_GLOB && p->op==FTS5_PATTERN_GLOB ){ + return 1; + } + if( pConfig->t.ePattern==FTS5_PATTERN_LIKE + && (p->op==FTS5_PATTERN_LIKE || p->op==FTS5_PATTERN_GLOB) + ){ + return 1; + } + return 0; +} + /* -** Implementation of the xBestIndex method for FTS5 tables. Within the +** Implementation of the xBestIndex method for FTS5 tables. Within the ** WHERE constraint, it searches for the following: ** -** 1. A MATCH constraint against the special column. +** 1. A MATCH constraint against the table column. ** 2. A MATCH constraint against the "rank" column. -** 3. An == constraint against the rowid column. -** 4. A < or <= constraint against the rowid column. -** 5. A > or >= constraint against the rowid column. +** 3. A MATCH constraint against some other column. +** 4. An == constraint against the rowid column. +** 5. A < or <= constraint against the rowid column. +** 6. A > or >= constraint against the rowid column. ** -** Within the ORDER BY, either: +** Within the ORDER BY, the following are supported: ** ** 5. ORDER BY rank [ASC|DESC] ** 6. ORDER BY rowid [ASC|DESC] ** -** Costs are assigned as follows: +** Information for the xFilter call is passed via both the idxNum and +** idxStr variables. Specifically, idxNum is a bitmask of the following +** flags used to encode the ORDER BY clause: +** +** FTS5_BI_ORDER_RANK +** FTS5_BI_ORDER_ROWID +** FTS5_BI_ORDER_DESC +** +** idxStr is used to encode data from the WHERE clause. For each argument +** passed to the xFilter method, the following is appended to idxStr: ** -** a) If an unusable MATCH operator is present in the WHERE clause, the -** cost is unconditionally set to 1e50 (a really big number). +** Match against table column: "m" +** Match against rank column: "r" +** Match against other column: "M" +** LIKE against other column: "L" +** GLOB against other column: "G" +** Equality constraint against the rowid: "=" +** A < or <= against the rowid: "<" +** A > or >= against the rowid: ">" +** +** This function ensures that there is at most one "r" or "=". And that if +** there exists an "=" then there is no "<" or ">". +** +** If an unusable MATCH operator is present in the WHERE clause, then +** SQLITE_CONSTRAINT is returned. +** +** Costs are assigned as follows: ** ** a) If a MATCH operator is present, the cost depends on the other ** constraints also present. As follows: ** -** * No other constraints: cost=1000.0 -** * One rowid range constraint: cost=750.0 -** * Both rowid range constraints: cost=500.0 -** * An == rowid constraint: cost=100.0 +** * No other constraints: cost=50000.0 +** * One rowid range constraint: cost=37500.0 +** * Both rowid range constraints: cost=30000.0 +** * An == rowid constraint: cost=25000.0 ** ** b) Otherwise, if there is no MATCH: ** -** * No other constraints: cost=1000000.0 -** * One rowid range constraint: cost=750000.0 -** * Both rowid range constraints: cost=250000.0 -** * An == rowid constraint: cost=10.0 +** * No other constraints: cost=3000000.0 +** * One rowid range constraints: cost=2250000.0 +** * Both rowid range constraint: cost=750000.0 +** * An == rowid constraint: cost=25.0 ** ** Costs are not modified by the ORDER BY clause. +** +** The ratios used in case (a) are based on informal results obtained from +** the tool/fts5cost.tcl script. The "MATCH and ==" combination has the +** cost set quite high because the query may be a prefix query. Unless +** there is a prefix index, prefix queries with rowid constraints are much +** more expensive than non-prefix queries with rowid constraints. +** +** The estimated rows returned is set to the cost/40. For simple queries, +** experimental results show that cost/4 might be about right. But for +** more complex queries that use multiple terms the number of rows might +** be far fewer than this. So we compromise and use cost/40. */ static int fts5BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){ Fts5Table *pTab = (Fts5Table*)pVTab; Fts5Config *pConfig = pTab->pConfig; const int nCol = pConfig->nCol; int idxFlags = 0; /* Parameter passed through to xFilter() */ - int bHasMatch; - int iNext; int i; - struct Constraint { - int op; /* Mask against sqlite3_index_constraint.op */ - int fts5op; /* FTS5 mask for idxFlags */ - int iCol; /* 0==rowid, 1==tbl, 2==rank */ - int omit; /* True to omit this if found */ - int iConsIndex; /* Index in pInfo->aConstraint[] */ - } aConstraint[] = { - {SQLITE_INDEX_CONSTRAINT_MATCH|SQLITE_INDEX_CONSTRAINT_EQ, - FTS5_BI_MATCH, 1, 1, -1}, - {SQLITE_INDEX_CONSTRAINT_MATCH|SQLITE_INDEX_CONSTRAINT_EQ, - FTS5_BI_RANK, 2, 1, -1}, - {SQLITE_INDEX_CONSTRAINT_EQ, FTS5_BI_ROWID_EQ, 0, 0, -1}, - {SQLITE_INDEX_CONSTRAINT_LT|SQLITE_INDEX_CONSTRAINT_LE, - FTS5_BI_ROWID_LE, 0, 0, -1}, - {SQLITE_INDEX_CONSTRAINT_GT|SQLITE_INDEX_CONSTRAINT_GE, - FTS5_BI_ROWID_GE, 0, 0, -1}, - }; + char *idxStr; + int iIdxStr = 0; + int iCons = 0; + + int bSeenEq = 0; + int bSeenGt = 0; + int bSeenLt = 0; + int nSeenMatch = 0; + int bSeenRank = 0; - int aColMap[3]; - aColMap[0] = -1; - aColMap[1] = nCol; - aColMap[2] = nCol+1; assert( SQLITE_INDEX_CONSTRAINT_EQbLock ){ + pTab->base.zErrMsg = sqlite3_mprintf( + "recursively defined fts5 content table" + ); + return SQLITE_ERROR; + } + + idxStr = (char*)sqlite3_malloc64((i64)pInfo->nConstraint * 8 + 1); + if( idxStr==0 ) return SQLITE_NOMEM; + pInfo->idxStr = idxStr; + pInfo->needToFreeIdxStr = 1; + for(i=0; inConstraint; i++){ struct sqlite3_index_constraint *p = &pInfo->aConstraint[i]; int iCol = p->iColumn; - - if( (p->op==SQLITE_INDEX_CONSTRAINT_MATCH && iCol>=0 && iCol<=nCol) - || (p->op==SQLITE_INDEX_CONSTRAINT_EQ && iCol==nCol) + if( p->op==SQLITE_INDEX_CONSTRAINT_MATCH + || (p->op==SQLITE_INDEX_CONSTRAINT_EQ && iCol>=nCol) ){ /* A MATCH operator or equivalent */ - if( p->usable ){ - idxFlags = (idxFlags & 0xFFFF) | FTS5_BI_MATCH | (iCol << 16); - aConstraint[0].iConsIndex = i; + if( p->usable==0 || iCol<0 ){ + /* As there exists an unusable MATCH constraint this is an + ** unusable plan. Return SQLITE_CONSTRAINT. */ + idxStr[iIdxStr] = 0; + return SQLITE_CONSTRAINT; }else{ - /* As there exists an unusable MATCH constraint this is an - ** unusable plan. Set a prohibitively high cost. */ - pInfo->estimatedCost = 1e50; - return SQLITE_OK; - } - }else if( p->op<=SQLITE_INDEX_CONSTRAINT_MATCH ){ - int j; - for(j=1; jiCol] && (p->op & pC->op) && p->usable ){ - pC->iConsIndex = i; - idxFlags |= pC->fts5op; + if( iCol==nCol+1 ){ + if( bSeenRank ) continue; + idxStr[iIdxStr++] = 'r'; + bSeenRank = 1; + }else{ + nSeenMatch++; + idxStr[iIdxStr++] = 'M'; + sqlite3_snprintf(6, &idxStr[iIdxStr], "%d", iCol); + iIdxStr += (int)strlen(&idxStr[iIdxStr]); + assert( idxStr[iIdxStr]=='\0' ); + } + pInfo->aConstraintUsage[i].argvIndex = ++iCons; + pInfo->aConstraintUsage[i].omit = 1; + } + }else if( p->usable ){ + if( iCol>=0 && iColop==FTS5_PATTERN_LIKE || p->op==FTS5_PATTERN_GLOB ); + idxStr[iIdxStr++] = p->op==FTS5_PATTERN_LIKE ? 'L' : 'G'; + sqlite3_snprintf(6, &idxStr[iIdxStr], "%d", iCol); + idxStr += strlen(&idxStr[iIdxStr]); + pInfo->aConstraintUsage[i].argvIndex = ++iCons; + assert( idxStr[iIdxStr]=='\0' ); + nSeenMatch++; + }else if( bSeenEq==0 && p->op==SQLITE_INDEX_CONSTRAINT_EQ && iCol<0 ){ + idxStr[iIdxStr++] = '='; + bSeenEq = 1; + pInfo->aConstraintUsage[i].argvIndex = ++iCons; + pInfo->aConstraintUsage[i].omit = 1; + } + } + } + + if( bSeenEq==0 ){ + for(i=0; inConstraint; i++){ + struct sqlite3_index_constraint *p = &pInfo->aConstraint[i]; + if( p->iColumn<0 && p->usable ){ + int op = p->op; + if( op==SQLITE_INDEX_CONSTRAINT_LT || op==SQLITE_INDEX_CONSTRAINT_LE ){ + if( bSeenLt ) continue; + idxStr[iIdxStr++] = '<'; + pInfo->aConstraintUsage[i].argvIndex = ++iCons; + bSeenLt = 1; + }else + if( op==SQLITE_INDEX_CONSTRAINT_GT || op==SQLITE_INDEX_CONSTRAINT_GE ){ + if( bSeenGt ) continue; + idxStr[iIdxStr++] = '>'; + pInfo->aConstraintUsage[i].argvIndex = ++iCons; + bSeenGt = 1; } } } } + idxStr[iIdxStr] = '\0'; - /* Set idxFlags flags for the ORDER BY clause */ + /* Set idxFlags flags for the ORDER BY clause + ** + ** Note that tokendata=1 tables cannot currently handle "ORDER BY rowid DESC". + */ if( pInfo->nOrderBy==1 ){ int iSort = pInfo->aOrderBy[0].iColumn; - if( iSort==(pConfig->nCol+1) && BitFlagTest(idxFlags, FTS5_BI_MATCH) ){ + if( iSort==(pConfig->nCol+1) && nSeenMatch>0 ){ idxFlags |= FTS5_BI_ORDER_RANK; - }else if( iSort==-1 ){ + }else if( iSort==-1 && (!pInfo->aOrderBy[0].desc || !pConfig->bTokendata) ){ idxFlags |= FTS5_BI_ORDER_ROWID; } if( BitFlagTest(idxFlags, FTS5_BI_ORDER_RANK|FTS5_BI_ORDER_ROWID) ){ @@ -216038,26 +261148,36 @@ static int fts5BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){ } /* Calculate the estimated cost based on the flags set in idxFlags. */ - bHasMatch = BitFlagTest(idxFlags, FTS5_BI_MATCH); - if( BitFlagTest(idxFlags, FTS5_BI_ROWID_EQ) ){ - pInfo->estimatedCost = bHasMatch ? 100.0 : 10.0; - if( bHasMatch==0 ) fts5SetUniqueFlag(pInfo); - }else if( BitFlagAllTest(idxFlags, FTS5_BI_ROWID_LE|FTS5_BI_ROWID_GE) ){ - pInfo->estimatedCost = bHasMatch ? 500.0 : 250000.0; - }else if( BitFlagTest(idxFlags, FTS5_BI_ROWID_LE|FTS5_BI_ROWID_GE) ){ - pInfo->estimatedCost = bHasMatch ? 750.0 : 750000.0; + if( bSeenEq ){ + pInfo->estimatedCost = nSeenMatch ? 25000.0 : 25.0; + fts5SetEstimatedRows(pInfo, 1); + fts5SetUniqueFlag(pInfo); }else{ - pInfo->estimatedCost = bHasMatch ? 1000.0 : 1000000.0; - } - - /* Assign argvIndex values to each constraint in use. */ - iNext = 1; - for(i=0; iiConsIndex>=0 ){ - pInfo->aConstraintUsage[pC->iConsIndex].argvIndex = iNext++; - pInfo->aConstraintUsage[pC->iConsIndex].omit = (unsigned char)pC->omit; + i64 nEstRows; + if( nSeenMatch ){ + if( bSeenLt && bSeenGt ){ + pInfo->estimatedCost = 50000.0; + }else if( bSeenLt || bSeenGt ){ + pInfo->estimatedCost = 37500.0; + }else{ + pInfo->estimatedCost = 50000.0; + } + nEstRows = (i64)(pInfo->estimatedCost / 40.0); + for(i=1; iestimatedCost *= 2.5; + nEstRows = nEstRows / 2; + } + }else{ + if( bSeenLt && bSeenGt ){ + pInfo->estimatedCost = 750000.0; + }else if( bSeenLt || bSeenGt ){ + pInfo->estimatedCost = 2250000.0; + }else{ + pInfo->estimatedCost = 3000000.0; + } + nEstRows = (i64)(pInfo->estimatedCost / 4.0); } + fts5SetEstimatedRows(pInfo, nEstRows); } pInfo->idxNum = idxFlags; @@ -216110,15 +261230,15 @@ static int fts5StmtType(Fts5Cursor *pCsr){ /* ** This function is called after the cursor passed as the only argument -** is moved to point at a different row. It clears all cached data +** is moved to point at a different row. It clears all cached data ** specific to the previous row stored by the cursor object. */ static void fts5CsrNewrow(Fts5Cursor *pCsr){ - CsrFlagSet(pCsr, - FTS5CSR_REQUIRE_CONTENT - | FTS5CSR_REQUIRE_DOCSIZE - | FTS5CSR_REQUIRE_INST - | FTS5CSR_REQUIRE_POSLIST + CsrFlagSet(pCsr, + FTS5CSR_REQUIRE_CONTENT + | FTS5CSR_REQUIRE_DOCSIZE + | FTS5CSR_REQUIRE_INST + | FTS5CSR_REQUIRE_POSLIST ); } @@ -216157,6 +261277,7 @@ static void fts5FreeCursorComponents(Fts5Cursor *pCsr){ sqlite3_free(pCsr->zRankArgs); } + sqlite3Fts5IndexCloseReader(pTab->p.pIndex); memset(&pCsr->ePlan, 0, sizeof(Fts5Cursor) - ((u8*)&pCsr->ePlan - (u8*)pCsr)); } @@ -216188,7 +261309,7 @@ static int fts5SorterNext(Fts5Cursor *pCsr){ rc = sqlite3_step(pSorter->pStmt); if( rc==SQLITE_DONE ){ rc = SQLITE_OK; - CsrFlagSet(pCsr, FTS5CSR_EOF); + CsrFlagSet(pCsr, FTS5CSR_EOF|FTS5CSR_REQUIRE_CONTENT); }else if( rc==SQLITE_ROW ){ const u8 *a; const u8 *aBlob; @@ -216221,14 +261342,14 @@ static int fts5SorterNext(Fts5Cursor *pCsr){ /* -** Set the FTS5CSR_REQUIRE_RESEEK flag on all FTS5_PLAN_MATCH cursors +** Set the FTS5CSR_REQUIRE_RESEEK flag on all FTS5_PLAN_MATCH cursors ** open on table pTab. */ static void fts5TripCursors(Fts5FullTable *pTab){ Fts5Cursor *pCsr; for(pCsr=pTab->pGlobal->pCsr; pCsr; pCsr=pCsr->pNext){ if( pCsr->ePlan==FTS5_PLAN_MATCH - && pCsr->base.pVtab==(sqlite3_vtab*)pTab + && pCsr->base.pVtab==(sqlite3_vtab*)pTab ){ CsrFlagSet(pCsr, FTS5CSR_REQUIRE_RESEEK); } @@ -216237,14 +261358,14 @@ static void fts5TripCursors(Fts5FullTable *pTab){ /* ** If the REQUIRE_RESEEK flag is set on the cursor passed as the first -** argument, close and reopen all Fts5IndexIter iterators that the cursor +** argument, close and reopen all Fts5IndexIter iterators that the cursor ** is using. Then attempt to move the cursor to a rowid equal to or laster -** (in the cursors sort order - ASC or DESC) than the current rowid. +** (in the cursors sort order - ASC or DESC) than the current rowid. ** ** If the new rowid is not equal to the old, set output parameter *pbSkip ** to 1 before returning. Otherwise, leave it unchanged. ** -** Return SQLITE_OK if successful or if no reseek was required, or an +** Return SQLITE_OK if successful or if no reseek was required, or an ** error code if an error occurred. */ static int fts5CursorReseek(Fts5Cursor *pCsr, int *pbSkip){ @@ -216255,7 +261376,9 @@ static int fts5CursorReseek(Fts5Cursor *pCsr, int *pbSkip){ int bDesc = pCsr->bDesc; i64 iRowid = sqlite3Fts5ExprRowid(pCsr->pExpr); - rc = sqlite3Fts5ExprFirst(pCsr->pExpr, pTab->p.pIndex, iRowid, bDesc); + rc = sqlite3Fts5ExprFirst( + pCsr->pExpr, pTab->p.pIndex, iRowid, pCsr->iLastRowid, bDesc + ); if( rc==SQLITE_OK && iRowid!=sqlite3Fts5ExprRowid(pCsr->pExpr) ){ *pbSkip = 1; } @@ -216272,7 +261395,7 @@ static int fts5CursorReseek(Fts5Cursor *pCsr, int *pbSkip){ /* -** Advance the cursor to the next row in the table that matches the +** Advance the cursor to the next row in the table that matches the ** search criteria. ** ** Return SQLITE_OK if nothing goes wrong. SQLITE_OK is returned @@ -216284,10 +261407,20 @@ static int fts5NextMethod(sqlite3_vtab_cursor *pCursor){ int rc; assert( (pCsr->ePlan<3)== - (pCsr->ePlan==FTS5_PLAN_MATCH || pCsr->ePlan==FTS5_PLAN_SOURCE) + (pCsr->ePlan==FTS5_PLAN_MATCH || pCsr->ePlan==FTS5_PLAN_SOURCE) ); assert( !CsrFlagTest(pCsr, FTS5CSR_EOF) ); + /* If this cursor uses FTS5_PLAN_MATCH and this is a tokendata=1 table, + ** clear any token mappings accumulated at the fts5_index.c level. In + ** other cases, specifically FTS5_PLAN_SOURCE and FTS5_PLAN_SORTED_MATCH, + ** we need to retain the mappings for the entire query. */ + if( pCsr->ePlan==FTS5_PLAN_MATCH + && ((Fts5Table*)pCursor->pVtab)->pConfig->bTokendata + ){ + sqlite3Fts5ExprClearTokens(pCsr->pExpr); + } + if( pCsr->ePlan<3 ){ int bSkip = 0; if( (rc = fts5CursorReseek(pCsr, &bSkip)) || bSkip ) return rc; @@ -216301,31 +261434,41 @@ static int fts5NextMethod(sqlite3_vtab_cursor *pCursor){ rc = SQLITE_OK; break; } - + case FTS5_PLAN_SORTED_MATCH: { rc = fts5SorterNext(pCsr); break; } - - default: + + default: { + Fts5Config *pConfig = ((Fts5Table*)pCursor->pVtab)->pConfig; + pConfig->bLock++; rc = sqlite3_step(pCsr->pStmt); + pConfig->bLock--; if( rc!=SQLITE_ROW ){ CsrFlagSet(pCsr, FTS5CSR_EOF); rc = sqlite3_reset(pCsr->pStmt); + if( rc!=SQLITE_OK ){ + pCursor->pVtab->zErrMsg = sqlite3_mprintf( + "%s", sqlite3_errmsg(pConfig->db) + ); + } }else{ rc = SQLITE_OK; + CsrFlagSet(pCsr, FTS5CSR_REQUIRE_DOCSIZE); } break; + } } } - + return rc; } static int fts5PrepareStatement( sqlite3_stmt **ppStmt, - Fts5Config *pConfig, + Fts5Config *pConfig, const char *zFmt, ... ){ @@ -216337,12 +261480,12 @@ static int fts5PrepareStatement( va_start(ap, zFmt); zSql = sqlite3_vmprintf(zFmt, ap); if( zSql==0 ){ - rc = SQLITE_NOMEM; + rc = SQLITE_NOMEM; }else{ - rc = sqlite3_prepare_v3(pConfig->db, zSql, -1, + rc = sqlite3_prepare_v3(pConfig->db, zSql, -1, SQLITE_PREPARE_PERSISTENT, &pRet, 0); if( rc!=SQLITE_OK ){ - *pConfig->pzErrmsg = sqlite3_mprintf("%s", sqlite3_errmsg(pConfig->db)); + sqlite3Fts5ConfigErrmsg(pConfig, "%s", sqlite3_errmsg(pConfig->db)); } sqlite3_free(zSql); } @@ -216350,11 +261493,11 @@ static int fts5PrepareStatement( va_end(ap); *ppStmt = pRet; return rc; -} +} static int fts5CursorFirstSorted( - Fts5FullTable *pTab, - Fts5Cursor *pCsr, + Fts5FullTable *pTab, + Fts5Cursor *pCsr, int bDesc ){ Fts5Config *pConfig = pTab->p.pConfig; @@ -216364,9 +261507,9 @@ static int fts5CursorFirstSorted( int rc; const char *zRank = pCsr->zRank; const char *zRankArgs = pCsr->zRankArgs; - + nPhrase = sqlite3Fts5ExprPhraseCount(pCsr->pExpr); - nByte = sizeof(Fts5Sorter) + sizeof(int) * (nPhrase-1); + nByte = SZ_FTS5SORTER(nPhrase); pSorter = (Fts5Sorter*)sqlite3_malloc64(nByte); if( pSorter==0 ) return SQLITE_NOMEM; memset(pSorter, 0, (size_t)nByte); @@ -216375,12 +261518,12 @@ static int fts5CursorFirstSorted( /* TODO: It would be better to have some system for reusing statement ** handles here, rather than preparing a new one for each query. But that ** is not possible as SQLite reference counts the virtual table objects. - ** And since the statement required here reads from this very virtual + ** And since the statement required here reads from this very virtual ** table, saving it creates a circular reference. ** ** If SQLite a built-in statement cache, this wouldn't be a problem. */ rc = fts5PrepareStatement(&pSorter->pStmt, pConfig, - "SELECT rowid, rank FROM %Q.%Q ORDER BY %s(%s%s%s) %s", + "SELECT rowid, rank FROM %Q.%Q ORDER BY %s(\"%w\"%s%s) %s", pConfig->zDb, pConfig->zName, zRank, pConfig->zName, (zRankArgs ? ", " : ""), (zRankArgs ? zRankArgs : ""), @@ -216407,7 +261550,9 @@ static int fts5CursorFirstSorted( static int fts5CursorFirst(Fts5FullTable *pTab, Fts5Cursor *pCsr, int bDesc){ int rc; Fts5Expr *pExpr = pCsr->pExpr; - rc = sqlite3Fts5ExprFirst(pExpr, pTab->p.pIndex, pCsr->iFirstRowid, bDesc); + rc = sqlite3Fts5ExprFirst( + pExpr, pTab->p.pIndex, pCsr->iFirstRowid, pCsr->iLastRowid, bDesc + ); if( sqlite3Fts5ExprEof(pExpr) ){ CsrFlagSet(pCsr, FTS5CSR_EOF); } @@ -216422,8 +261567,8 @@ static int fts5CursorFirst(Fts5FullTable *pTab, Fts5Cursor *pCsr, int bDesc){ ** parameters. */ static int fts5SpecialMatch( - Fts5FullTable *pTab, - Fts5Cursor *pCsr, + Fts5FullTable *pTab, + Fts5Cursor *pCsr, const char *zQuery ){ int rc = SQLITE_OK; /* Return code */ @@ -216436,10 +261581,10 @@ static int fts5SpecialMatch( assert( pTab->p.base.zErrMsg==0 ); pCsr->ePlan = FTS5_PLAN_SPECIAL; - if( 0==sqlite3_strnicmp("reads", z, n) ){ + if( n==5 && 0==sqlite3_strnicmp("reads", z, n) ){ pCsr->iSpecial = sqlite3Fts5IndexReads(pTab->p.pIndex); } - else if( 0==sqlite3_strnicmp("id", z, n) ){ + else if( n==2 && 0==sqlite3_strnicmp("id", z, n) ){ pCsr->iSpecial = pCsr->iCsrId; } else{ @@ -216521,7 +261666,7 @@ static int fts5FindRankFunction(Fts5Cursor *pCsr){ static int fts5CursorParseRank( Fts5Config *pConfig, - Fts5Cursor *pCsr, + Fts5Cursor *pCsr, sqlite3_value *pRank ){ int rc = SQLITE_OK; @@ -216566,11 +261711,150 @@ static i64 fts5GetRowidLimit(sqlite3_value *pVal, i64 iDefault){ return iDefault; } +/* +** Set the error message on the virtual table passed as the first argument. +*/ +static void fts5SetVtabError(Fts5FullTable *p, const char *zFormat, ...){ + va_list ap; /* ... printf arguments */ + va_start(ap, zFormat); + sqlite3_free(p->p.base.zErrMsg); + p->p.base.zErrMsg = sqlite3_vmprintf(zFormat, ap); + va_end(ap); +} + +/* +** Arrange for subsequent calls to sqlite3Fts5Tokenize() to use the locale +** specified by pLocale/nLocale. The buffer indicated by pLocale must remain +** valid until after the final call to sqlite3Fts5Tokenize() that will use +** the locale. +*/ +static void sqlite3Fts5SetLocale( + Fts5Config *pConfig, + const char *zLocale, + int nLocale +){ + Fts5TokenizerConfig *pT = &pConfig->t; + pT->pLocale = zLocale; + pT->nLocale = nLocale; +} + +/* +** Clear any locale configured by an earlier call to sqlite3Fts5SetLocale(). +*/ +static void sqlite3Fts5ClearLocale(Fts5Config *pConfig){ + sqlite3Fts5SetLocale(pConfig, 0, 0); +} + +/* +** Return true if the value passed as the only argument is an +** fts5_locale() value. +*/ +static int sqlite3Fts5IsLocaleValue(Fts5Config *pConfig, sqlite3_value *pVal){ + int ret = 0; + if( sqlite3_value_type(pVal)==SQLITE_BLOB ){ + /* Call sqlite3_value_bytes() after sqlite3_value_blob() in this case. + ** If the blob was created using zeroblob(), then sqlite3_value_blob() + ** may call malloc(). If this malloc() fails, then the values returned + ** by both value_blob() and value_bytes() will be 0. If value_bytes() were + ** called first, then the NULL pointer returned by value_blob() might + ** be dereferenced. */ + const u8 *pBlob = sqlite3_value_blob(pVal); + int nBlob = sqlite3_value_bytes(pVal); + if( nBlob>FTS5_LOCALE_HDR_SIZE + && 0==memcmp(pBlob, FTS5_LOCALE_HDR(pConfig), FTS5_LOCALE_HDR_SIZE) + ){ + ret = 1; + } + } + return ret; +} + +/* +** Value pVal is guaranteed to be an fts5_locale() value, according to +** sqlite3Fts5IsLocaleValue(). This function extracts the text and locale +** from the value and returns them separately. +** +** If successful, SQLITE_OK is returned and (*ppText) and (*ppLoc) set +** to point to buffers containing the text and locale, as utf-8, +** respectively. In this case output parameters (*pnText) and (*pnLoc) are +** set to the sizes in bytes of these two buffers. +** +** Or, if an error occurs, then an SQLite error code is returned. The final +** value of the four output parameters is undefined in this case. +*/ +static int sqlite3Fts5DecodeLocaleValue( + sqlite3_value *pVal, + const char **ppText, + int *pnText, + const char **ppLoc, + int *pnLoc +){ + const char *p = sqlite3_value_blob(pVal); + int n = sqlite3_value_bytes(pVal); + int nLoc = 0; + + assert( sqlite3_value_type(pVal)==SQLITE_BLOB ); + assert( n>FTS5_LOCALE_HDR_SIZE ); + + for(nLoc=FTS5_LOCALE_HDR_SIZE; p[nLoc]; nLoc++){ + if( nLoc==(n-1) ){ + return SQLITE_MISMATCH; + } + } + *ppLoc = &p[FTS5_LOCALE_HDR_SIZE]; + *pnLoc = nLoc - FTS5_LOCALE_HDR_SIZE; + + *ppText = &p[nLoc+1]; + *pnText = n - nLoc - 1; + return SQLITE_OK; +} + +/* +** Argument pVal is the text of a full-text search expression. It may or +** may not have been wrapped by fts5_locale(). This function extracts +** the text of the expression, and sets output variable (*pzText) to +** point to a nul-terminated buffer containing the expression. +** +** If pVal was an fts5_locale() value, then sqlite3Fts5SetLocale() is called +** to set the tokenizer to use the specified locale. +** +** If output variable (*pbFreeAndReset) is set to true, then the caller +** is required to (a) call sqlite3Fts5ClearLocale() to reset the tokenizer +** locale, and (b) call sqlite3_free() to free (*pzText). +*/ +static int fts5ExtractExprText( + Fts5Config *pConfig, /* Fts5 configuration */ + sqlite3_value *pVal, /* Value to extract expression text from */ + char **pzText, /* OUT: nul-terminated buffer of text */ + int *pbFreeAndReset /* OUT: Free (*pzText) and clear locale */ +){ + int rc = SQLITE_OK; + + if( sqlite3Fts5IsLocaleValue(pConfig, pVal) ){ + const char *pText = 0; + int nText = 0; + const char *pLoc = 0; + int nLoc = 0; + rc = sqlite3Fts5DecodeLocaleValue(pVal, &pText, &nText, &pLoc, &nLoc); + *pzText = sqlite3Fts5Mprintf(&rc, "%.*s", nText, pText); + if( rc==SQLITE_OK ){ + sqlite3Fts5SetLocale(pConfig, pLoc, nLoc); + } + *pbFreeAndReset = 1; + }else{ + *pzText = (char*)sqlite3_value_text(pVal); + *pbFreeAndReset = 0; + } + + return rc; +} + + /* ** This is the xFilter interface for the virtual table. See ** the virtual table xFilter method documentation for additional ** information. -** +** ** There are three possible query strategies: ** ** 1. Full-text search using a MATCH operator. @@ -216580,7 +261864,7 @@ static i64 fts5GetRowidLimit(sqlite3_value *pVal, i64 iDefault){ static int fts5FilterMethod( sqlite3_vtab_cursor *pCursor, /* The cursor used for this query */ int idxNum, /* Strategy index */ - const char *zUnused, /* Unused */ + const char *idxStr, /* Unused */ int nVal, /* Number of elements in apVal */ sqlite3_value **apVal /* Arguments for the indexing scheme */ ){ @@ -216588,20 +261872,20 @@ static int fts5FilterMethod( Fts5Config *pConfig = pTab->p.pConfig; Fts5Cursor *pCsr = (Fts5Cursor*)pCursor; int rc = SQLITE_OK; /* Error code */ - int iVal = 0; /* Counter for apVal[] */ int bDesc; /* True if ORDER BY [rank|rowid] DESC */ int bOrderByRank; /* True if ORDER BY rank */ - sqlite3_value *pMatch = 0; /* MATCH ? expression (or NULL) */ sqlite3_value *pRank = 0; /* rank MATCH ? expression (or NULL) */ sqlite3_value *pRowidEq = 0; /* rowid = ? expression (or NULL) */ sqlite3_value *pRowidLe = 0; /* rowid <= ? expression (or NULL) */ sqlite3_value *pRowidGe = 0; /* rowid >= ? expression (or NULL) */ int iCol; /* Column on LHS of MATCH operator */ char **pzErrmsg = pConfig->pzErrmsg; + int bPrefixInsttoken = pConfig->bPrefixInsttoken; + int i; + int iIdxStr = 0; + Fts5Expr *pExpr = 0; - UNUSED_PARAM(zUnused); - UNUSED_PARAM(nVal); - + assert( pConfig->bLock==0 ); if( pCsr->ePlan ){ fts5FreeCursorComponents(pCsr); memset(&pCsr->ePlan, 0, sizeof(Fts5Cursor) - ((u8*)&pCsr->ePlan-(u8*)pCsr)); @@ -216613,27 +261897,93 @@ static int fts5FilterMethod( assert( pCsr->pRank==0 ); assert( pCsr->zRank==0 ); assert( pCsr->zRankArgs==0 ); + assert( pTab->pSortCsr==0 || nVal==0 ); assert( pzErrmsg==0 || pzErrmsg==&pTab->p.base.zErrMsg ); pConfig->pzErrmsg = &pTab->p.base.zErrMsg; - /* Decode the arguments passed through to this function. - ** - ** Note: The following set of if(...) statements must be in the same - ** order as the corresponding entries in the struct at the top of - ** fts5BestIndexMethod(). */ - if( BitFlagTest(idxNum, FTS5_BI_MATCH) ) pMatch = apVal[iVal++]; - if( BitFlagTest(idxNum, FTS5_BI_RANK) ) pRank = apVal[iVal++]; - if( BitFlagTest(idxNum, FTS5_BI_ROWID_EQ) ) pRowidEq = apVal[iVal++]; - if( BitFlagTest(idxNum, FTS5_BI_ROWID_LE) ) pRowidLe = apVal[iVal++]; - if( BitFlagTest(idxNum, FTS5_BI_ROWID_GE) ) pRowidGe = apVal[iVal++]; - iCol = (idxNum>>16); - assert( iCol>=0 && iCol<=pConfig->nCol ); - assert( iVal==nVal ); + /* Decode the arguments passed through to this function. */ + for(i=0; ibPrefixInsttoken = 1; + } + + iCol = 0; + do{ + iCol = iCol*10 + (idxStr[iIdxStr]-'0'); + iIdxStr++; + }while( idxStr[iIdxStr]>='0' && idxStr[iIdxStr]<='9' ); + + if( zText[0]=='*' ){ + /* The user has issued a query of the form "MATCH '*...'". This + ** indicates that the MATCH expression is not a full text query, + ** but a request for an internal parameter. */ + rc = fts5SpecialMatch(pTab, pCsr, &zText[1]); + bInternal = 1; + }else{ + char **pzErr = &pTab->p.base.zErrMsg; + rc = sqlite3Fts5ExprNew(pConfig, 0, iCol, zText, &pExpr, pzErr); + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5ExprAnd(&pCsr->pExpr, pExpr); + pExpr = 0; + } + } + + if( bFreeAndReset ){ + sqlite3_free(zText); + sqlite3Fts5ClearLocale(pConfig); + } + + if( bInternal || rc!=SQLITE_OK ) goto filter_out; + + break; + } + case 'L': + case 'G': { + int bGlob = (idxStr[iIdxStr-1]=='G'); + const char *zText = (const char*)sqlite3_value_text(apVal[i]); + iCol = 0; + do{ + iCol = iCol*10 + (idxStr[iIdxStr]-'0'); + iIdxStr++; + }while( idxStr[iIdxStr]>='0' && idxStr[iIdxStr]<='9' ); + if( zText ){ + rc = sqlite3Fts5ExprPattern(pConfig, bGlob, iCol, zText, &pExpr); + } + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5ExprAnd(&pCsr->pExpr, pExpr); + pExpr = 0; + } + if( rc!=SQLITE_OK ) goto filter_out; + break; + } + case '=': + pRowidEq = apVal[i]; + break; + case '<': + pRowidLe = apVal[i]; + break; + default: assert( idxStr[iIdxStr-1]=='>' ); + pRowidGe = apVal[i]; + break; + } + } bOrderByRank = ((idxNum & FTS5_BI_ORDER_RANK) ? 1 : 0); pCsr->bDesc = bDesc = ((idxNum & FTS5_BI_ORDER_DESC) ? 1 : 0); - /* Set the cursor upper and lower rowid limits. Only some strategies + /* Set the cursor upper and lower rowid limits. Only some strategies ** actually use them. This is ok, as the xBestIndex() method leaves the ** sqlite3_index_constraint.omit flag clear for range constraints ** on the rowid field. */ @@ -216648,15 +261998,18 @@ static int fts5FilterMethod( pCsr->iFirstRowid = fts5GetRowidLimit(pRowidGe, SMALLEST_INT64); } + rc = sqlite3Fts5IndexLoadConfig(pTab->p.pIndex); + if( rc!=SQLITE_OK ) goto filter_out; + if( pTab->pSortCsr ){ - /* If pSortCsr is non-NULL, then this call is being made as part of + /* If pSortCsr is non-NULL, then this call is being made as part of ** processing for a "... MATCH ORDER BY rank" query (ePlan is ** set to FTS5_PLAN_SORTED_MATCH). pSortCsr is the cursor that will - ** return results to the user for this query. The current cursor - ** (pCursor) is used to execute the query issued by function + ** return results to the user for this query. The current cursor + ** (pCursor) is used to execute the query issued by function ** fts5CursorFirstSorted() above. */ assert( pRowidEq==0 && pRowidLe==0 && pRowidGe==0 && pRank==0 ); - assert( nVal==0 && pMatch==0 && bOrderByRank==0 && bDesc==0 ); + assert( nVal==0 && bOrderByRank==0 && bDesc==0 ); assert( pCsr->iLastRowid==LARGEST_INT64 ); assert( pCsr->iFirstRowid==SMALLEST_INT64 ); if( pTab->pSortCsr->bDesc ){ @@ -216669,35 +262022,20 @@ static int fts5FilterMethod( pCsr->ePlan = FTS5_PLAN_SOURCE; pCsr->pExpr = pTab->pSortCsr->pExpr; rc = fts5CursorFirst(pTab, pCsr, bDesc); - }else if( pMatch ){ - const char *zExpr = (const char*)sqlite3_value_text(apVal[0]); - if( zExpr==0 ) zExpr = ""; - + }else if( pCsr->pExpr ){ + assert( rc==SQLITE_OK ); rc = fts5CursorParseRank(pConfig, pCsr, pRank); if( rc==SQLITE_OK ){ - if( zExpr[0]=='*' ){ - /* The user has issued a query of the form "MATCH '*...'". This - ** indicates that the MATCH expression is not a full text query, - ** but a request for an internal parameter. */ - rc = fts5SpecialMatch(pTab, pCsr, &zExpr[1]); - }else{ - char **pzErr = &pTab->p.base.zErrMsg; - rc = sqlite3Fts5ExprNew(pConfig, iCol, zExpr, &pCsr->pExpr, pzErr); - if( rc==SQLITE_OK ){ - if( bOrderByRank ){ - pCsr->ePlan = FTS5_PLAN_SORTED_MATCH; - rc = fts5CursorFirstSorted(pTab, pCsr, bDesc); - }else{ - pCsr->ePlan = FTS5_PLAN_MATCH; - rc = fts5CursorFirst(pTab, pCsr, bDesc); - } - } + if( bOrderByRank ){ + pCsr->ePlan = FTS5_PLAN_SORTED_MATCH; + rc = fts5CursorFirstSorted(pTab, pCsr, bDesc); + }else{ + pCsr->ePlan = FTS5_PLAN_MATCH; + rc = fts5CursorFirst(pTab, pCsr, bDesc); } } }else if( pConfig->zContent==0 ){ - *pConfig->pzErrmsg = sqlite3_mprintf( - "%s: table does not support scanning", pConfig->zName - ); + fts5SetVtabError(pTab,"%s: table does not support scanning",pConfig->zName); rc = SQLITE_ERROR; }else{ /* This is either a full-table scan (ePlan==FTS5_PLAN_SCAN) or a lookup @@ -216707,8 +262045,9 @@ static int fts5FilterMethod( pTab->pStorage, fts5StmtType(pCsr), &pCsr->pStmt, &pTab->p.base.zErrMsg ); if( rc==SQLITE_OK ){ - if( pCsr->ePlan==FTS5_PLAN_ROWID ){ - sqlite3_bind_value(pCsr->pStmt, 1, apVal[0]); + if( pRowidEq!=0 ){ + assert( pCsr->ePlan==FTS5_PLAN_ROWID ); + sqlite3_bind_value(pCsr->pStmt, 1, pRowidEq); }else{ sqlite3_bind_int64(pCsr->pStmt, 1, pCsr->iFirstRowid); sqlite3_bind_int64(pCsr->pStmt, 2, pCsr->iLastRowid); @@ -216717,12 +262056,15 @@ static int fts5FilterMethod( } } + filter_out: + sqlite3Fts5ExprFree(pExpr); pConfig->pzErrmsg = pzErrmsg; + pConfig->bPrefixInsttoken = bPrefixInsttoken; return rc; } -/* -** This is the xEof method of the virtual table. SQLite calls this +/* +** This is the xEof method of the virtual table. SQLite calls this ** routine to find out if it has reached the end of a result set. */ static int fts5EofMethod(sqlite3_vtab_cursor *pCursor){ @@ -216734,18 +262076,22 @@ static int fts5EofMethod(sqlite3_vtab_cursor *pCursor){ ** Return the rowid that the cursor currently points to. */ static i64 fts5CursorRowid(Fts5Cursor *pCsr){ - assert( pCsr->ePlan==FTS5_PLAN_MATCH - || pCsr->ePlan==FTS5_PLAN_SORTED_MATCH - || pCsr->ePlan==FTS5_PLAN_SOURCE + assert( pCsr->ePlan==FTS5_PLAN_MATCH + || pCsr->ePlan==FTS5_PLAN_SORTED_MATCH + || pCsr->ePlan==FTS5_PLAN_SOURCE + || pCsr->ePlan==FTS5_PLAN_SCAN + || pCsr->ePlan==FTS5_PLAN_ROWID ); if( pCsr->pSorter ){ return pCsr->pSorter->iRowid; + }else if( pCsr->ePlan>=FTS5_PLAN_SCAN ){ + return sqlite3_column_int64(pCsr->pStmt, 0); }else{ return sqlite3Fts5ExprRowid(pCsr->pExpr); } } -/* +/* ** This is the xRowid method. The SQLite core calls this routine to ** retrieve the rowid for the current row of the result set. fts5 ** exposes %_content.rowid as the rowid for the virtual table. The @@ -216754,27 +262100,18 @@ static i64 fts5CursorRowid(Fts5Cursor *pCsr){ static int fts5RowidMethod(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){ Fts5Cursor *pCsr = (Fts5Cursor*)pCursor; int ePlan = pCsr->ePlan; - - assert( CsrFlagTest(pCsr, FTS5CSR_EOF)==0 ); - switch( ePlan ){ - case FTS5_PLAN_SPECIAL: - *pRowid = 0; - break; - - case FTS5_PLAN_SOURCE: - case FTS5_PLAN_MATCH: - case FTS5_PLAN_SORTED_MATCH: - *pRowid = fts5CursorRowid(pCsr); - break; - default: - *pRowid = sqlite3_column_int64(pCsr->pStmt, 0); - break; + assert( CsrFlagTest(pCsr, FTS5CSR_EOF)==0 ); + if( ePlan==FTS5_PLAN_SPECIAL ){ + *pRowid = 0; + }else{ + *pRowid = fts5CursorRowid(pCsr); } return SQLITE_OK; } + /* ** If the cursor requires seeking (bSeekRequired flag is set), seek it. ** Return SQLITE_OK if no error occurs, or an SQLite error code otherwise. @@ -216785,7 +262122,7 @@ static int fts5RowidMethod(sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid){ static int fts5SeekCursor(Fts5Cursor *pCsr, int bErrormsg){ int rc = SQLITE_OK; - /* If the cursor does not yet have a statement handle, obtain one now. */ + /* If the cursor does not yet have a statement handle, obtain one now. */ if( pCsr->pStmt==0 ){ Fts5FullTable *pTab = (Fts5FullTable*)(pCsr->base.pVtab); int eStmt = fts5StmtType(pCsr); @@ -216797,10 +262134,13 @@ static int fts5SeekCursor(Fts5Cursor *pCsr, int bErrormsg){ } if( rc==SQLITE_OK && CsrFlagTest(pCsr, FTS5CSR_REQUIRE_CONTENT) ){ + Fts5Table *pTab = (Fts5Table*)(pCsr->base.pVtab); assert( pCsr->pExpr ); sqlite3_reset(pCsr->pStmt); sqlite3_bind_int64(pCsr->pStmt, 1, fts5CursorRowid(pCsr)); + pTab->pConfig->bLock++; rc = sqlite3_step(pCsr->pStmt); + pTab->pConfig->bLock--; if( rc==SQLITE_ROW ){ rc = SQLITE_OK; CsrFlagClear(pCsr, FTS5CSR_REQUIRE_CONTENT); @@ -216808,20 +262148,21 @@ static int fts5SeekCursor(Fts5Cursor *pCsr, int bErrormsg){ rc = sqlite3_reset(pCsr->pStmt); if( rc==SQLITE_OK ){ rc = FTS5_CORRUPT; + fts5SetVtabError((Fts5FullTable*)pTab, + "fts5: missing row %lld from content table %s", + fts5CursorRowid(pCsr), + pTab->pConfig->zContent + ); + }else if( pTab->pConfig->pzErrmsg ){ + fts5SetVtabError((Fts5FullTable*)pTab, + "%s", sqlite3_errmsg(pTab->pConfig->db) + ); } } } return rc; } -static void fts5SetVtabError(Fts5FullTable *p, const char *zFormat, ...){ - va_list ap; /* ... printf arguments */ - va_start(ap, zFormat); - assert( p->p.base.zErrMsg==0 ); - p->p.base.zErrMsg = sqlite3_vmprintf(zFormat, ap); - va_end(ap); -} - /* ** This function is called to handle an FTS INSERT command. In other words, ** an INSERT statement of the form: @@ -216829,7 +262170,7 @@ static void fts5SetVtabError(Fts5FullTable *p, const char *zFormat, ...){ ** INSERT INTO fts(fts) VALUES($pCmd) ** INSERT INTO fts(fts, rank) VALUES($pCmd, $pVal) ** -** Argument pVal is the value assigned to column "fts" by the INSERT +** Argument pVal is the value assigned to column "fts" by the INSERT ** statement. This function returns SQLITE_OK if successful, or an SQLite ** error code if an error occurs. ** @@ -216845,10 +262186,11 @@ static int fts5SpecialInsert( Fts5Config *pConfig = pTab->p.pConfig; int rc = SQLITE_OK; int bError = 0; + int bLoadConfig = 0; if( 0==sqlite3_stricmp("delete-all", zCmd) ){ if( pConfig->eContent==FTS5_CONTENT_NORMAL ){ - fts5SetVtabError(pTab, + fts5SetVtabError(pTab, "'delete-all' may only be used with a " "contentless or external content fts5 table" ); @@ -216856,28 +262198,36 @@ static int fts5SpecialInsert( }else{ rc = sqlite3Fts5StorageDeleteAll(pTab->pStorage); } + bLoadConfig = 1; }else if( 0==sqlite3_stricmp("rebuild", zCmd) ){ - if( pConfig->eContent==FTS5_CONTENT_NONE ){ - fts5SetVtabError(pTab, + if( fts5IsContentless(pTab, 1) ){ + fts5SetVtabError(pTab, "'rebuild' may not be used with a contentless fts5 table" ); rc = SQLITE_ERROR; }else{ rc = sqlite3Fts5StorageRebuild(pTab->pStorage); } + bLoadConfig = 1; }else if( 0==sqlite3_stricmp("optimize", zCmd) ){ rc = sqlite3Fts5StorageOptimize(pTab->pStorage); }else if( 0==sqlite3_stricmp("merge", zCmd) ){ int nMerge = sqlite3_value_int(pVal); rc = sqlite3Fts5StorageMerge(pTab->pStorage, nMerge); }else if( 0==sqlite3_stricmp("integrity-check", zCmd) ){ - rc = sqlite3Fts5StorageIntegrity(pTab->pStorage); + int iArg = sqlite3_value_int(pVal); + rc = sqlite3Fts5StorageIntegrity(pTab->pStorage, iArg); #ifdef SQLITE_DEBUG }else if( 0==sqlite3_stricmp("prefix-index", zCmd) ){ pConfig->bPrefixIndex = sqlite3_value_int(pVal); #endif + }else if( 0==sqlite3_stricmp("flush", zCmd) ){ + rc = sqlite3Fts5FlushToDisk(&pTab->p); }else{ - rc = sqlite3Fts5IndexLoadConfig(pTab->p.pIndex); + rc = sqlite3Fts5FlushToDisk(&pTab->p); + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5IndexLoadConfig(pTab->p.pIndex); + } if( rc==SQLITE_OK ){ rc = sqlite3Fts5ConfigSetValue(pTab->p.pConfig, zCmd, pVal, &bError); } @@ -216889,31 +262239,37 @@ static int fts5SpecialInsert( } } } + + if( rc==SQLITE_OK && bLoadConfig ){ + pTab->p.pConfig->iCookie--; + rc = sqlite3Fts5IndexLoadConfig(pTab->p.pIndex); + } + return rc; } static int fts5SpecialDelete( - Fts5FullTable *pTab, + Fts5FullTable *pTab, sqlite3_value **apVal ){ int rc = SQLITE_OK; int eType1 = sqlite3_value_type(apVal[1]); if( eType1==SQLITE_INTEGER ){ sqlite3_int64 iDel = sqlite3_value_int64(apVal[1]); - rc = sqlite3Fts5StorageDelete(pTab->pStorage, iDel, &apVal[2]); + rc = sqlite3Fts5StorageDelete(pTab->pStorage, iDel, &apVal[2], 0); } return rc; } static void fts5StorageInsert( - int *pRc, - Fts5FullTable *pTab, - sqlite3_value **apVal, + int *pRc, + Fts5FullTable *pTab, + sqlite3_value **apVal, i64 *piRowid ){ int rc = *pRc; if( rc==SQLITE_OK ){ - rc = sqlite3Fts5StorageContentInsert(pTab->pStorage, apVal, piRowid); + rc = sqlite3Fts5StorageContentInsert(pTab->pStorage, 0, apVal, piRowid); } if( rc==SQLITE_OK ){ rc = sqlite3Fts5StorageIndexInsert(pTab->pStorage, apVal, *piRowid); @@ -216921,13 +262277,74 @@ static void fts5StorageInsert( *pRc = rc; } -/* -** This function is the implementation of the xUpdate callback used by +/* +** +** This function is called when the user attempts an UPDATE on a contentless +** table. Parameter bRowidModified is true if the UPDATE statement modifies +** the rowid value. Parameter apVal[] contains the new values for each user +** defined column of the fts5 table. pConfig is the configuration object of the +** table being updated (guaranteed to be contentless). The contentless_delete=1 +** and contentless_unindexed=1 options may or may not be set. +** +** This function returns SQLITE_OK if the UPDATE can go ahead, or an SQLite +** error code if it cannot. In this case an error message is also loaded into +** pConfig. Output parameter (*pbContent) is set to true if the caller should +** update the %_content table only - not the FTS index or any other shadow +** table. This occurs when an UPDATE modifies only UNINDEXED columns of the +** table. +** +** An UPDATE may proceed if: +** +** * The only columns modified are UNINDEXED columns, or +** +** * The contentless_delete=1 option was specified and all of the indexed +** columns (not a subset) have been modified. +*/ +static int fts5ContentlessUpdate( + Fts5Config *pConfig, + sqlite3_value **apVal, + int bRowidModified, + int *pbContent +){ + int ii; + int bSeenIndex = 0; /* Have seen modified indexed column */ + int bSeenIndexNC = 0; /* Have seen unmodified indexed column */ + int rc = SQLITE_OK; + + for(ii=0; iinCol; ii++){ + if( pConfig->abUnindexed[ii]==0 ){ + if( sqlite3_value_nochange(apVal[ii]) ){ + bSeenIndexNC++; + }else{ + bSeenIndex++; + } + } + } + + if( bSeenIndex==0 && bRowidModified==0 ){ + *pbContent = 1; + }else{ + if( bSeenIndexNC || pConfig->bContentlessDelete==0 ){ + rc = SQLITE_ERROR; + sqlite3Fts5ConfigErrmsg(pConfig, + (pConfig->bContentlessDelete ? + "%s a subset of columns on fts5 contentless-delete table: %s" : + "%s contentless fts5 table: %s") + , "cannot UPDATE", pConfig->zName + ); + } + } + + return rc; +} + +/* +** This function is the implementation of the xUpdate callback used by ** FTS3 virtual tables. It is invoked by SQLite each time a row is to be ** inserted, updated or deleted. ** ** A delete specifies a single argument - the rowid of the row to remove. -** +** ** Update and insert operations pass: ** ** 1. The "old" rowid, or NULL. @@ -216947,35 +262364,47 @@ static int fts5UpdateMethod( int rc = SQLITE_OK; /* Return code */ /* A transaction must be open when this is called. */ - assert( pTab->ts.eState==1 ); + assert( pTab->ts.eState==1 || pTab->ts.eState==2 ); assert( pVtab->zErrMsg==0 ); assert( nArg==1 || nArg==(2+pConfig->nCol+2) ); - assert( sqlite3_value_type(apVal[0])==SQLITE_INTEGER - || sqlite3_value_type(apVal[0])==SQLITE_NULL + assert( sqlite3_value_type(apVal[0])==SQLITE_INTEGER + || sqlite3_value_type(apVal[0])==SQLITE_NULL ); assert( pTab->p.pConfig->pzErrmsg==0 ); + if( pConfig->pgsz==0 ){ + rc = sqlite3Fts5ConfigLoad(pTab->p.pConfig, pTab->p.pConfig->iCookie); + if( rc!=SQLITE_OK ) return rc; + } + pTab->p.pConfig->pzErrmsg = &pTab->p.base.zErrMsg; /* Put any active cursors into REQUIRE_SEEK state. */ fts5TripCursors(pTab); eType0 = sqlite3_value_type(apVal[0]); - if( eType0==SQLITE_NULL - && sqlite3_value_type(apVal[2+pConfig->nCol])!=SQLITE_NULL + if( eType0==SQLITE_NULL + && sqlite3_value_type(apVal[2+pConfig->nCol])!=SQLITE_NULL ){ /* A "special" INSERT op. These are handled separately. */ const char *z = (const char*)sqlite3_value_text(apVal[2+pConfig->nCol]); - if( pConfig->eContent!=FTS5_CONTENT_NORMAL - && 0==sqlite3_stricmp("delete", z) + if( pConfig->eContent!=FTS5_CONTENT_NORMAL + && 0==sqlite3_stricmp("delete", z) ){ - rc = fts5SpecialDelete(pTab, apVal); + if( pConfig->bContentlessDelete ){ + fts5SetVtabError(pTab, + "'delete' may not be used with a contentless_delete=1 table" + ); + rc = SQLITE_ERROR; + }else{ + rc = fts5SpecialDelete(pTab, apVal); + } }else{ rc = fts5SpecialInsert(pTab, z, apVal[2 + pConfig->nCol + 1]); } }else{ /* A regular INSERT, UPDATE or DELETE statement. The trick here is that - ** any conflict on the rowid value must be detected before any + ** any conflict on the rowid value must be detected before any ** modifications are made to the database file. There are 4 cases: ** ** 1) DELETE @@ -216986,99 +262415,138 @@ static int fts5UpdateMethod( ** Cases 3 and 4 may violate the rowid constraint. */ int eConflict = SQLITE_ABORT; - if( pConfig->eContent==FTS5_CONTENT_NORMAL ){ + if( pConfig->eContent==FTS5_CONTENT_NORMAL || pConfig->bContentlessDelete ){ eConflict = sqlite3_vtab_on_conflict(pConfig->db); } assert( eType0==SQLITE_INTEGER || eType0==SQLITE_NULL ); assert( nArg!=1 || eType0==SQLITE_INTEGER ); - /* Filter out attempts to run UPDATE or DELETE on contentless tables. - ** This is not suported. */ - if( eType0==SQLITE_INTEGER && fts5IsContentless(pTab) ){ - pTab->p.base.zErrMsg = sqlite3_mprintf( - "cannot %s contentless fts5 table: %s", - (nArg>1 ? "UPDATE" : "DELETE from"), pConfig->zName - ); - rc = SQLITE_ERROR; - } - /* DELETE */ - else if( nArg==1 ){ - i64 iDel = sqlite3_value_int64(apVal[0]); /* Rowid to delete */ - rc = sqlite3Fts5StorageDelete(pTab->pStorage, iDel, 0); + if( nArg==1 ){ + /* It is only possible to DELETE from a contentless table if the + ** contentless_delete=1 flag is set. */ + if( fts5IsContentless(pTab, 1) && pConfig->bContentlessDelete==0 ){ + fts5SetVtabError(pTab, + "cannot DELETE from contentless fts5 table: %s", pConfig->zName + ); + rc = SQLITE_ERROR; + }else{ + i64 iDel = sqlite3_value_int64(apVal[0]); /* Rowid to delete */ + rc = sqlite3Fts5StorageDelete(pTab->pStorage, iDel, 0, 0); + } } /* INSERT or UPDATE */ else{ int eType1 = sqlite3_value_numeric_type(apVal[1]); - if( eType1!=SQLITE_INTEGER && eType1!=SQLITE_NULL ){ - rc = SQLITE_MISMATCH; + /* It is an error to write an fts5_locale() value to a table without + ** the locale=1 option. */ + if( pConfig->bLocale==0 ){ + int ii; + for(ii=0; iinCol; ii++){ + sqlite3_value *pVal = apVal[ii+2]; + if( sqlite3Fts5IsLocaleValue(pConfig, pVal) ){ + fts5SetVtabError(pTab, "fts5_locale() requires locale=1"); + rc = SQLITE_MISMATCH; + goto update_out; + } + } } - else if( eType0!=SQLITE_INTEGER ){ - /* If this is a REPLACE, first remove the current entry (if any) */ + if( eType0!=SQLITE_INTEGER ){ + /* An INSERT statement. If the conflict-mode is REPLACE, first remove + ** the current entry (if any). */ if( eConflict==SQLITE_REPLACE && eType1==SQLITE_INTEGER ){ i64 iNew = sqlite3_value_int64(apVal[1]); /* Rowid to delete */ - rc = sqlite3Fts5StorageDelete(pTab->pStorage, iNew, 0); + rc = sqlite3Fts5StorageDelete(pTab->pStorage, iNew, 0, 0); } fts5StorageInsert(&rc, pTab, apVal, pRowid); } /* UPDATE */ else{ + Fts5Storage *pStorage = pTab->pStorage; i64 iOld = sqlite3_value_int64(apVal[0]); /* Old rowid */ i64 iNew = sqlite3_value_int64(apVal[1]); /* New rowid */ - if( eType1==SQLITE_INTEGER && iOld!=iNew ){ + int bContent = 0; /* Content only update */ + + /* If this is a contentless table (including contentless_unindexed=1 + ** tables), check if the UPDATE may proceed. */ + if( fts5IsContentless(pTab, 1) ){ + rc = fts5ContentlessUpdate(pConfig, &apVal[2], iOld!=iNew, &bContent); + if( rc!=SQLITE_OK ) goto update_out; + } + + if( eType1!=SQLITE_INTEGER ){ + rc = SQLITE_MISMATCH; + }else if( iOld!=iNew ){ + assert( bContent==0 ); if( eConflict==SQLITE_REPLACE ){ - rc = sqlite3Fts5StorageDelete(pTab->pStorage, iOld, 0); + rc = sqlite3Fts5StorageDelete(pStorage, iOld, 0, 1); if( rc==SQLITE_OK ){ - rc = sqlite3Fts5StorageDelete(pTab->pStorage, iNew, 0); + rc = sqlite3Fts5StorageDelete(pStorage, iNew, 0, 0); } fts5StorageInsert(&rc, pTab, apVal, pRowid); }else{ - rc = sqlite3Fts5StorageContentInsert(pTab->pStorage, apVal, pRowid); + rc = sqlite3Fts5StorageFindDeleteRow(pStorage, iOld); + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5StorageContentInsert(pStorage, 0, apVal, pRowid); + } if( rc==SQLITE_OK ){ - rc = sqlite3Fts5StorageDelete(pTab->pStorage, iOld, 0); + rc = sqlite3Fts5StorageDelete(pStorage, iOld, 0, 0); } if( rc==SQLITE_OK ){ - rc = sqlite3Fts5StorageIndexInsert(pTab->pStorage, apVal,*pRowid); + rc = sqlite3Fts5StorageIndexInsert(pStorage, apVal, *pRowid); } } + }else if( bContent ){ + /* This occurs when an UPDATE on a contentless table affects *only* + ** UNINDEXED columns. This is a no-op for contentless_unindexed=0 + ** tables, or a write to the %_content table only for =1 tables. */ + assert( fts5IsContentless(pTab, 1) ); + rc = sqlite3Fts5StorageFindDeleteRow(pStorage, iOld); + if( rc==SQLITE_OK ){ + rc = sqlite3Fts5StorageContentInsert(pStorage, 1, apVal, pRowid); + } }else{ - rc = sqlite3Fts5StorageDelete(pTab->pStorage, iOld, 0); + rc = sqlite3Fts5StorageDelete(pStorage, iOld, 0, 1); fts5StorageInsert(&rc, pTab, apVal, pRowid); } + sqlite3Fts5StorageReleaseDeleteRow(pStorage); } } } + update_out: + sqlite3Fts5IndexCloseReader(pTab->p.pIndex); pTab->p.pConfig->pzErrmsg = 0; return rc; } /* -** Implementation of xSync() method. +** Implementation of xSync() method. */ static int fts5SyncMethod(sqlite3_vtab *pVtab){ int rc; Fts5FullTable *pTab = (Fts5FullTable*)pVtab; fts5CheckTransactionState(pTab, FTS5_SYNC, 0); pTab->p.pConfig->pzErrmsg = &pTab->p.base.zErrMsg; - fts5TripCursors(pTab); - rc = sqlite3Fts5StorageSync(pTab->pStorage); + rc = sqlite3Fts5FlushToDisk(&pTab->p); pTab->p.pConfig->pzErrmsg = 0; return rc; } /* -** Implementation of xBegin() method. +** Implementation of xBegin() method. */ static int fts5BeginMethod(sqlite3_vtab *pVtab){ - fts5CheckTransactionState((Fts5FullTable*)pVtab, FTS5_BEGIN, 0); - fts5NewTransaction((Fts5FullTable*)pVtab); - return SQLITE_OK; + int rc = fts5NewTransaction((Fts5FullTable*)pVtab); + if( rc==SQLITE_OK ){ + fts5CheckTransactionState((Fts5FullTable*)pVtab, FTS5_BEGIN, 0); + } + return rc; } /* @@ -217101,6 +262569,7 @@ static int fts5RollbackMethod(sqlite3_vtab *pVtab){ Fts5FullTable *pTab = (Fts5FullTable*)pVtab; fts5CheckTransactionState(pTab, FTS5_ROLLBACK, 0); rc = sqlite3Fts5StorageRollback(pTab->pStorage); + pTab->p.pConfig->pgsz = 0; return rc; } @@ -217117,8 +262586,8 @@ static int fts5ApiColumnCount(Fts5Context *pCtx){ } static int fts5ApiColumnTotalSize( - Fts5Context *pCtx, - int iCol, + Fts5Context *pCtx, + int iCol, sqlite3_int64 *pnToken ){ Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; @@ -217132,17 +262601,40 @@ static int fts5ApiRowCount(Fts5Context *pCtx, i64 *pnRow){ return sqlite3Fts5StorageRowCount(pTab->pStorage, pnRow); } -static int fts5ApiTokenize( - Fts5Context *pCtx, - const char *pText, int nText, +/* +** Implementation of xTokenize_v2() API. +*/ +static int fts5ApiTokenize_v2( + Fts5Context *pCtx, + const char *pText, int nText, + const char *pLoc, int nLoc, void *pUserData, int (*xToken)(void*, int, const char*, int, int, int) ){ Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; Fts5Table *pTab = (Fts5Table*)(pCsr->base.pVtab); - return sqlite3Fts5Tokenize( - pTab->pConfig, FTS5_TOKENIZE_AUX, pText, nText, pUserData, xToken + int rc = SQLITE_OK; + + sqlite3Fts5SetLocale(pTab->pConfig, pLoc, nLoc); + rc = sqlite3Fts5Tokenize(pTab->pConfig, + FTS5_TOKENIZE_AUX, pText, nText, pUserData, xToken ); + sqlite3Fts5SetLocale(pTab->pConfig, 0, 0); + + return rc; +} + +/* +** Implementation of xTokenize() API. This is just xTokenize_v2() with NULL/0 +** passed as the locale. +*/ +static int fts5ApiTokenize( + Fts5Context *pCtx, + const char *pText, int nText, + void *pUserData, + int (*xToken)(void*, int, const char*, int, int, int) +){ + return fts5ApiTokenize_v2(pCtx, pText, nText, 0, 0, pUserData, xToken); } static int fts5ApiPhraseCount(Fts5Context *pCtx){ @@ -217155,54 +262647,120 @@ static int fts5ApiPhraseSize(Fts5Context *pCtx, int iPhrase){ return sqlite3Fts5ExprPhraseSize(pCsr->pExpr, iPhrase); } +/* +** Argument pStmt is an SQL statement of the type used by Fts5Cursor. This +** function extracts the text value of column iCol of the current row. +** Additionally, if there is an associated locale, it invokes +** sqlite3Fts5SetLocale() to configure the tokenizer. In all cases the caller +** should invoke sqlite3Fts5ClearLocale() to clear the locale at some point +** after this function returns. +** +** If successful, (*ppText) is set to point to a buffer containing the text +** value as utf-8 and SQLITE_OK returned. (*pnText) is set to the size of that +** buffer in bytes. It is not guaranteed to be nul-terminated. If an error +** occurs, an SQLite error code is returned. The final values of the two +** output parameters are undefined in this case. +*/ +static int fts5TextFromStmt( + Fts5Config *pConfig, + sqlite3_stmt *pStmt, + int iCol, + const char **ppText, + int *pnText +){ + sqlite3_value *pVal = sqlite3_column_value(pStmt, iCol+1); + const char *pLoc = 0; + int nLoc = 0; + int rc = SQLITE_OK; + + if( pConfig->bLocale + && pConfig->eContent==FTS5_CONTENT_EXTERNAL + && sqlite3Fts5IsLocaleValue(pConfig, pVal) + ){ + rc = sqlite3Fts5DecodeLocaleValue(pVal, ppText, pnText, &pLoc, &nLoc); + }else{ + *ppText = (const char*)sqlite3_value_text(pVal); + *pnText = sqlite3_value_bytes(pVal); + if( pConfig->bLocale && pConfig->eContent==FTS5_CONTENT_NORMAL ){ + pLoc = (const char*)sqlite3_column_text(pStmt, iCol+1+pConfig->nCol); + nLoc = sqlite3_column_bytes(pStmt, iCol+1+pConfig->nCol); + } + } + sqlite3Fts5SetLocale(pConfig, pLoc, nLoc); + return rc; +} + static int fts5ApiColumnText( - Fts5Context *pCtx, - int iCol, - const char **pz, + Fts5Context *pCtx, + int iCol, + const char **pz, int *pn ){ int rc = SQLITE_OK; Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; - if( fts5IsContentless((Fts5FullTable*)(pCsr->base.pVtab)) - || pCsr->ePlan==FTS5_PLAN_SPECIAL - ){ + Fts5Table *pTab = (Fts5Table*)(pCsr->base.pVtab); + + assert( pCsr->ePlan!=FTS5_PLAN_SPECIAL ); + if( iCol<0 || iCol>=pTab->pConfig->nCol ){ + rc = SQLITE_RANGE; + }else if( fts5IsContentless((Fts5FullTable*)(pCsr->base.pVtab), 0) ){ *pz = 0; *pn = 0; }else{ rc = fts5SeekCursor(pCsr, 0); if( rc==SQLITE_OK ){ - *pz = (const char*)sqlite3_column_text(pCsr->pStmt, iCol+1); - *pn = sqlite3_column_bytes(pCsr->pStmt, iCol+1); + rc = fts5TextFromStmt(pTab->pConfig, pCsr->pStmt, iCol, pz, pn); + sqlite3Fts5ClearLocale(pTab->pConfig); } } return rc; } +/* +** This is called by various API functions - xInst, xPhraseFirst, +** xPhraseFirstColumn etc. - to obtain the position list for phrase iPhrase +** of the current row. This function works for both detail=full tables (in +** which case the position-list was read from the fts index) or for other +** detail= modes if the row content is available. +*/ static int fts5CsrPoslist( - Fts5Cursor *pCsr, - int iPhrase, - const u8 **pa, - int *pn + Fts5Cursor *pCsr, /* Fts5 cursor object */ + int iPhrase, /* Phrase to find position list for */ + const u8 **pa, /* OUT: Pointer to position list buffer */ + int *pn /* OUT: Size of (*pa) in bytes */ ){ Fts5Config *pConfig = ((Fts5Table*)(pCsr->base.pVtab))->pConfig; int rc = SQLITE_OK; int bLive = (pCsr->pSorter==0); - if( CsrFlagTest(pCsr, FTS5CSR_REQUIRE_POSLIST) ){ - + if( iPhrase<0 || iPhrase>=sqlite3Fts5ExprPhraseCount(pCsr->pExpr) ){ + rc = SQLITE_RANGE; + }else if( pConfig->eDetail!=FTS5_DETAIL_FULL + && fts5IsContentless((Fts5FullTable*)pCsr->base.pVtab, 1) + ){ + *pa = 0; + *pn = 0; + return SQLITE_OK; + }else if( CsrFlagTest(pCsr, FTS5CSR_REQUIRE_POSLIST) ){ if( pConfig->eDetail!=FTS5_DETAIL_FULL ){ Fts5PoslistPopulator *aPopulator; int i; + aPopulator = sqlite3Fts5ExprClearPoslists(pCsr->pExpr, bLive); if( aPopulator==0 ) rc = SQLITE_NOMEM; + if( rc==SQLITE_OK ){ + rc = fts5SeekCursor(pCsr, 0); + } for(i=0; inCol && rc==SQLITE_OK; i++){ - int n; const char *z; - rc = fts5ApiColumnText((Fts5Context*)pCsr, i, &z, &n); + const char *z = 0; + int n = 0; + rc = fts5TextFromStmt(pConfig, pCsr->pStmt, i, &z, &n); if( rc==SQLITE_OK ){ rc = sqlite3Fts5ExprPopulatePoslists( pConfig, pCsr->pExpr, aPopulator, i, z, n ); } + sqlite3Fts5ClearLocale(pConfig); } sqlite3_free(aPopulator); @@ -217213,13 +262771,18 @@ static int fts5CsrPoslist( CsrFlagClear(pCsr, FTS5CSR_REQUIRE_POSLIST); } - if( pCsr->pSorter && pConfig->eDetail==FTS5_DETAIL_FULL ){ - Fts5Sorter *pSorter = pCsr->pSorter; - int i1 = (iPhrase==0 ? 0 : pSorter->aIdx[iPhrase-1]); - *pn = pSorter->aIdx[iPhrase] - i1; - *pa = &pSorter->aPoslist[i1]; + if( rc==SQLITE_OK ){ + if( pCsr->pSorter && pConfig->eDetail==FTS5_DETAIL_FULL ){ + Fts5Sorter *pSorter = pCsr->pSorter; + int i1 = (iPhrase==0 ? 0 : pSorter->aIdx[iPhrase-1]); + *pn = pSorter->aIdx[iPhrase] - i1; + *pa = &pSorter->aPoslist[i1]; + }else{ + *pn = sqlite3Fts5ExprPoslist(pCsr->pExpr, iPhrase, pa); + } }else{ - *pn = sqlite3Fts5ExprPoslist(pCsr->pExpr, iPhrase, pa); + *pa = 0; + *pn = 0; } return rc; @@ -217235,7 +262798,7 @@ static int fts5CacheInstArray(Fts5Cursor *pCsr){ Fts5PoslistReader *aIter; /* One iterator for each phrase */ int nIter; /* Number of iterators/phrases */ int nCol = ((Fts5Table*)pCsr->base.pVtab)->pConfig->nCol; - + nIter = sqlite3Fts5ExprPhraseCount(pCsr->pExpr); if( pCsr->aInstIter==0 ){ sqlite3_int64 nByte = sizeof(Fts5PoslistReader) * nIter; @@ -217250,7 +262813,7 @@ static int fts5CacheInstArray(Fts5Cursor *pCsr){ /* Initialize all iterators */ for(i=0; i=pCsr->nInstAlloc ){ - pCsr->nInstAlloc = pCsr->nInstAlloc ? pCsr->nInstAlloc*2 : 32; + int nNewSize = pCsr->nInstAlloc ? pCsr->nInstAlloc*2 : 32; aInst = (int*)sqlite3_realloc64( - pCsr->aInst, pCsr->nInstAlloc*sizeof(int)*3 + pCsr->aInst, nNewSize*sizeof(int)*3 ); if( aInst ){ pCsr->aInst = aInst; + pCsr->nInstAlloc = nNewSize; }else{ + nInst--; rc = SQLITE_NOMEM; break; } @@ -217288,7 +262853,8 @@ static int fts5CacheInstArray(Fts5Cursor *pCsr){ aInst[0] = iBest; aInst[1] = FTS5_POS2COLUMN(aIter[iBest].iPos); aInst[2] = FTS5_POS2OFFSET(aIter[iBest].iPos); - if( aInst[1]<0 || aInst[1]>=nCol ){ + assert( aInst[1]>=0 ); + if( aInst[1]>=nCol ){ rc = FTS5_CORRUPT; break; } @@ -217305,7 +262871,7 @@ static int fts5CacheInstArray(Fts5Cursor *pCsr){ static int fts5ApiInstCount(Fts5Context *pCtx, int *pnInst){ Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; int rc = SQLITE_OK; - if( CsrFlagTest(pCsr, FTS5CSR_REQUIRE_INST)==0 + if( CsrFlagTest(pCsr, FTS5CSR_REQUIRE_INST)==0 || SQLITE_OK==(rc = fts5CacheInstArray(pCsr)) ){ *pnInst = pCsr->nInstCount; } @@ -217313,25 +262879,19 @@ static int fts5ApiInstCount(Fts5Context *pCtx, int *pnInst){ } static int fts5ApiInst( - Fts5Context *pCtx, - int iIdx, - int *piPhrase, - int *piCol, + Fts5Context *pCtx, + int iIdx, + int *piPhrase, + int *piCol, int *piOff ){ Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; int rc = SQLITE_OK; - if( CsrFlagTest(pCsr, FTS5CSR_REQUIRE_INST)==0 - || SQLITE_OK==(rc = fts5CacheInstArray(pCsr)) + if( CsrFlagTest(pCsr, FTS5CSR_REQUIRE_INST)==0 + || SQLITE_OK==(rc = fts5CacheInstArray(pCsr)) ){ if( iIdx<0 || iIdx>=pCsr->nInstCount ){ rc = SQLITE_RANGE; -#if 0 - }else if( fts5IsOffsetless((Fts5Table*)pCsr->base.pVtab) ){ - *piPhrase = pCsr->aInst[iIdx*3]; - *piCol = pCsr->aInst[iIdx*3 + 2]; - *piOff = -1; -#endif }else{ *piPhrase = pCsr->aInst[iIdx*3]; *piCol = pCsr->aInst[iIdx*3 + 1]; @@ -217372,7 +262932,7 @@ static int fts5ApiColumnSize(Fts5Context *pCtx, int iCol, int *pnToken){ if( pConfig->bColumnsize ){ i64 iRowid = fts5CursorRowid(pCsr); rc = sqlite3Fts5StorageDocsize(pTab->pStorage, iRowid, pCsr->aColumnSize); - }else if( pConfig->zContent==0 ){ + }else if( !pConfig->zContent || pConfig->eContent==FTS5_CONTENT_UNINDEXED ){ int i; for(i=0; inCol; i++){ if( pConfig->abUnindexed[i]==0 ){ @@ -217381,17 +262941,19 @@ static int fts5ApiColumnSize(Fts5Context *pCtx, int iCol, int *pnToken){ } }else{ int i; + rc = fts5SeekCursor(pCsr, 0); for(i=0; rc==SQLITE_OK && inCol; i++){ if( pConfig->abUnindexed[i]==0 ){ - const char *z; int n; - void *p = (void*)(&pCsr->aColumnSize[i]); + const char *z = 0; + int n = 0; pCsr->aColumnSize[i] = 0; - rc = fts5ApiColumnText(pCtx, i, &z, &n); + rc = fts5TextFromStmt(pConfig, pCsr->pStmt, i, &z, &n); if( rc==SQLITE_OK ){ - rc = sqlite3Fts5Tokenize( - pConfig, FTS5_TOKENIZE_AUX, z, n, p, fts5ColumnSizeCb + rc = sqlite3Fts5Tokenize(pConfig, FTS5_TOKENIZE_AUX, + z, n, (void*)&pCsr->aColumnSize[i], fts5ColumnSizeCb ); } + sqlite3Fts5ClearLocale(pConfig); } } } @@ -217471,11 +263033,10 @@ static void *fts5ApiGetAuxdata(Fts5Context *pCtx, int bClear){ } static void fts5ApiPhraseNext( - Fts5Context *pUnused, - Fts5PhraseIter *pIter, + Fts5Context *pCtx, + Fts5PhraseIter *pIter, int *piCol, int *piOff ){ - UNUSED_PARAM(pUnused); if( pIter->a>=pIter->b ){ *piCol = -1; *piOff = -1; @@ -217483,8 +263044,12 @@ static void fts5ApiPhraseNext( int iVal; pIter->a += fts5GetVarint32(pIter->a, iVal); if( iVal==1 ){ + /* Avoid returning a (*piCol) value that is too large for the table, + ** even if the position-list is corrupt. The caller might not be + ** expecting it. */ + int nCol = ((Fts5Table*)(((Fts5Cursor*)pCtx)->base.pVtab))->pConfig->nCol; pIter->a += fts5GetVarint32(pIter->a, iVal); - *piCol = iVal; + *piCol = (iVal>=nCol ? nCol-1 : iVal); *piOff = 0; pIter->a += fts5GetVarint32(pIter->a, iVal); } @@ -217493,16 +263058,17 @@ static void fts5ApiPhraseNext( } static int fts5ApiPhraseFirst( - Fts5Context *pCtx, - int iPhrase, - Fts5PhraseIter *pIter, + Fts5Context *pCtx, + int iPhrase, + Fts5PhraseIter *pIter, int *piCol, int *piOff ){ Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; int n; int rc = fts5CsrPoslist(pCsr, iPhrase, &pIter->a, &n); if( rc==SQLITE_OK ){ - pIter->b = &pIter->a[n]; + assert( pIter->a || n==0 ); + pIter->b = (pIter->a ? &pIter->a[n] : 0); *piCol = 0; *piOff = 0; fts5ApiPhraseNext(pCtx, pIter, piCol, piOff); @@ -217511,8 +263077,8 @@ static int fts5ApiPhraseFirst( } static void fts5ApiPhraseNextColumn( - Fts5Context *pCtx, - Fts5PhraseIter *pIter, + Fts5Context *pCtx, + Fts5PhraseIter *pIter, int *piCol ){ Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; @@ -217541,9 +263107,9 @@ static void fts5ApiPhraseNextColumn( } static int fts5ApiPhraseFirstColumn( - Fts5Context *pCtx, - int iPhrase, - Fts5PhraseIter *pIter, + Fts5Context *pCtx, + int iPhrase, + Fts5PhraseIter *pIter, int *piCol ){ int rc = SQLITE_OK; @@ -217552,24 +263118,30 @@ static int fts5ApiPhraseFirstColumn( if( pConfig->eDetail==FTS5_DETAIL_COLUMNS ){ Fts5Sorter *pSorter = pCsr->pSorter; - int n; - if( pSorter ){ - int i1 = (iPhrase==0 ? 0 : pSorter->aIdx[iPhrase-1]); - n = pSorter->aIdx[iPhrase] - i1; - pIter->a = &pSorter->aPoslist[i1]; + if( iPhrase<0 || iPhrase>=sqlite3Fts5ExprPhraseCount(pCsr->pExpr) ){ + rc = SQLITE_RANGE; }else{ - rc = sqlite3Fts5ExprPhraseCollist(pCsr->pExpr, iPhrase, &pIter->a, &n); - } - if( rc==SQLITE_OK ){ - pIter->b = &pIter->a[n]; - *piCol = 0; - fts5ApiPhraseNextColumn(pCtx, pIter, piCol); + int n; + if( pSorter ){ + int i1 = (iPhrase==0 ? 0 : pSorter->aIdx[iPhrase-1]); + n = pSorter->aIdx[iPhrase] - i1; + pIter->a = &pSorter->aPoslist[i1]; + }else{ + rc = sqlite3Fts5ExprPhraseCollist(pCsr->pExpr, iPhrase, &pIter->a, &n); + } + if( rc==SQLITE_OK ){ + assert( pIter->a || n==0 ); + pIter->b = (pIter->a ? &pIter->a[n] : 0); + *piCol = 0; + fts5ApiPhraseNextColumn(pCtx, pIter, piCol); + } } }else{ int n; rc = fts5CsrPoslist(pCsr, iPhrase, &pIter->a, &n); if( rc==SQLITE_OK ){ - pIter->b = &pIter->a[n]; + assert( pIter->a || n==0 ); + pIter->b = (pIter->a ? &pIter->a[n] : 0); if( n<=0 ){ *piCol = -1; }else if( pIter->a[0]==0x01 ){ @@ -217583,13 +263155,96 @@ static int fts5ApiPhraseFirstColumn( return rc; } +/* +** xQueryToken() API implemenetation. +*/ +static int fts5ApiQueryToken( + Fts5Context* pCtx, + int iPhrase, + int iToken, + const char **ppOut, + int *pnOut +){ + Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; + return sqlite3Fts5ExprQueryToken(pCsr->pExpr, iPhrase, iToken, ppOut, pnOut); +} + +/* +** xInstToken() API implemenetation. +*/ +static int fts5ApiInstToken( + Fts5Context *pCtx, + int iIdx, + int iToken, + const char **ppOut, int *pnOut +){ + Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; + int rc = SQLITE_OK; + if( CsrFlagTest(pCsr, FTS5CSR_REQUIRE_INST)==0 + || SQLITE_OK==(rc = fts5CacheInstArray(pCsr)) + ){ + if( iIdx<0 || iIdx>=pCsr->nInstCount ){ + rc = SQLITE_RANGE; + }else{ + int iPhrase = pCsr->aInst[iIdx*3]; + int iCol = pCsr->aInst[iIdx*3 + 1]; + int iOff = pCsr->aInst[iIdx*3 + 2]; + i64 iRowid = fts5CursorRowid(pCsr); + rc = sqlite3Fts5ExprInstToken( + pCsr->pExpr, iRowid, iPhrase, iCol, iOff, iToken, ppOut, pnOut + ); + } + } + return rc; +} + -static int fts5ApiQueryPhrase(Fts5Context*, int, void*, +static int fts5ApiQueryPhrase(Fts5Context*, int, void*, int(*)(const Fts5ExtensionApi*, Fts5Context*, void*) ); +/* +** The xColumnLocale() API. +*/ +static int fts5ApiColumnLocale( + Fts5Context *pCtx, + int iCol, + const char **pzLocale, + int *pnLocale +){ + int rc = SQLITE_OK; + Fts5Cursor *pCsr = (Fts5Cursor*)pCtx; + Fts5Config *pConfig = ((Fts5Table*)(pCsr->base.pVtab))->pConfig; + + *pzLocale = 0; + *pnLocale = 0; + + assert( pCsr->ePlan!=FTS5_PLAN_SPECIAL ); + if( iCol<0 || iCol>=pConfig->nCol ){ + rc = SQLITE_RANGE; + }else if( + pConfig->abUnindexed[iCol]==0 + && 0==fts5IsContentless((Fts5FullTable*)pCsr->base.pVtab, 1) + && pConfig->bLocale + ){ + rc = fts5SeekCursor(pCsr, 0); + if( rc==SQLITE_OK ){ + const char *zDummy = 0; + int nDummy = 0; + rc = fts5TextFromStmt(pConfig, pCsr->pStmt, iCol, &zDummy, &nDummy); + if( rc==SQLITE_OK ){ + *pzLocale = pConfig->t.pLocale; + *pnLocale = pConfig->t.nLocale; + } + sqlite3Fts5ClearLocale(pConfig); + } + } + + return rc; +} + static const Fts5ExtensionApi sFts5Api = { - 2, /* iVersion */ + 4, /* iVersion */ fts5ApiUserData, fts5ApiColumnCount, fts5ApiRowCount, @@ -217609,14 +263264,18 @@ static const Fts5ExtensionApi sFts5Api = { fts5ApiPhraseNext, fts5ApiPhraseFirstColumn, fts5ApiPhraseNextColumn, + fts5ApiQueryToken, + fts5ApiInstToken, + fts5ApiColumnLocale, + fts5ApiTokenize_v2 }; /* ** Implementation of API function xQueryPhrase(). */ static int fts5ApiQueryPhrase( - Fts5Context *pCtx, - int iPhrase, + Fts5Context *pCtx, + int iPhrase, void *pUserData, int(*xCallback)(const Fts5ExtensionApi*, Fts5Context*, void*) ){ @@ -217659,6 +263318,7 @@ static void fts5ApiInvoke( sqlite3_value **argv ){ assert( pCsr->pAux==0 ); + assert( pCsr->ePlan!=FTS5_PLAN_SPECIAL ); pCsr->pAux = pAux; pAux->xFunc(&sFts5Api, (Fts5Context*)pCsr, context, argc, argv); pCsr->pAux = 0; @@ -217672,6 +263332,21 @@ static Fts5Cursor *fts5CursorFromCsrid(Fts5Global *pGlobal, i64 iCsrId){ return pCsr; } +/* +** Parameter zFmt is a printf() style formatting string. This function +** formats it using the trailing arguments and returns the result as +** an error message to the context passed as the first argument. +*/ +static void fts5ResultError(sqlite3_context *pCtx, const char *zFmt, ...){ + char *zErr = 0; + va_list ap; + va_start(ap, zFmt); + zErr = sqlite3_vmprintf(zFmt, ap); + sqlite3_result_error(pCtx, zErr, -1); + sqlite3_free(zErr); + va_end(ap); +} + static void fts5ApiCallback( sqlite3_context *context, int argc, @@ -217687,18 +263362,19 @@ static void fts5ApiCallback( iCsrId = sqlite3_value_int64(argv[0]); pCsr = fts5CursorFromCsrid(pAux->pGlobal, iCsrId); - if( pCsr==0 ){ - char *zErr = sqlite3_mprintf("no such cursor: %lld", iCsrId); - sqlite3_result_error(context, zErr, -1); - sqlite3_free(zErr); + if( pCsr==0 || (pCsr->ePlan==0 || pCsr->ePlan==FTS5_PLAN_SPECIAL) ){ + fts5ResultError(context, "no such cursor: %lld", iCsrId); }else{ + sqlite3_vtab *pTab = pCsr->base.pVtab; fts5ApiInvoke(pAux, pCsr, context, argc-1, &argv[1]); + sqlite3_free(pTab->zErrMsg); + pTab->zErrMsg = 0; } } /* -** Given cursor id iId, return a pointer to the corresponding Fts5Table +** Given cursor id iId, return a pointer to the corresponding Fts5Table ** object. Or NULL If the cursor id does not exist. */ static Fts5Table *sqlite3Fts5TableFromCsrid( @@ -217781,7 +263457,7 @@ static int fts5PoslistBlob(sqlite3_context *pCtx, Fts5Cursor *pCsr){ return rc; } -/* +/* ** This is the xColumn method, called by SQLite to request a value from ** the row that the supplied cursor currently points to. */ @@ -217794,7 +263470,7 @@ static int fts5ColumnMethod( Fts5Config *pConfig = pTab->p.pConfig; Fts5Cursor *pCsr = (Fts5Cursor*)pCursor; int rc = SQLITE_OK; - + assert( CsrFlagTest(pCsr, FTS5CSR_EOF)==0 ); if( pCsr->ePlan==FTS5_PLAN_SPECIAL ){ @@ -217810,11 +263486,11 @@ static int fts5ColumnMethod( ** auxiliary function. */ sqlite3_result_int64(pCtx, pCsr->iCsrId); }else if( iCol==pConfig->nCol+1 ){ - /* The value of the "rank" column. */ + if( pCsr->ePlan==FTS5_PLAN_SOURCE ){ fts5PoslistBlob(pCtx, pCsr); - }else if( + }else if( pCsr->ePlan==FTS5_PLAN_MATCH || pCsr->ePlan==FTS5_PLAN_SORTED_MATCH ){ @@ -217822,12 +263498,32 @@ static int fts5ColumnMethod( fts5ApiInvoke(pCsr->pRank, pCsr, pCtx, pCsr->nRankArg, pCsr->apRankArg); } } - }else if( !fts5IsContentless(pTab) ){ - rc = fts5SeekCursor(pCsr, 1); - if( rc==SQLITE_OK ){ - sqlite3_result_value(pCtx, sqlite3_column_value(pCsr->pStmt, iCol+1)); + }else{ + if( !sqlite3_vtab_nochange(pCtx) && pConfig->eContent!=FTS5_CONTENT_NONE ){ + pConfig->pzErrmsg = &pTab->p.base.zErrMsg; + rc = fts5SeekCursor(pCsr, 1); + if( rc==SQLITE_OK ){ + sqlite3_value *pVal = sqlite3_column_value(pCsr->pStmt, iCol+1); + if( pConfig->bLocale + && pConfig->eContent==FTS5_CONTENT_EXTERNAL + && sqlite3Fts5IsLocaleValue(pConfig, pVal) + ){ + const char *z = 0; + int n = 0; + rc = fts5TextFromStmt(pConfig, pCsr->pStmt, iCol, &z, &n); + if( rc==SQLITE_OK ){ + sqlite3_result_text(pCtx, z, n, SQLITE_TRANSIENT); + } + sqlite3Fts5ClearLocale(pConfig); + }else{ + sqlite3_result_value(pCtx, pVal); + } + } + + pConfig->pzErrmsg = 0; } } + return rc; } @@ -217865,8 +263561,10 @@ static int fts5RenameMethod( sqlite3_vtab *pVtab, /* Virtual table handle */ const char *zName /* New name of table */ ){ + int rc; Fts5FullTable *pTab = (Fts5FullTable*)pVtab; - return sqlite3Fts5StorageRename(pTab->pStorage, zName); + rc = sqlite3Fts5StorageRename(pTab->pStorage, zName); + return rc; } static int sqlite3Fts5FlushToDisk(Fts5Table *pTab){ @@ -217880,9 +263578,15 @@ static int sqlite3Fts5FlushToDisk(Fts5Table *pTab){ ** Flush the contents of the pending-terms table to disk. */ static int fts5SavepointMethod(sqlite3_vtab *pVtab, int iSavepoint){ - UNUSED_PARAM(iSavepoint); /* Call below is a no-op for NDEBUG builds */ - fts5CheckTransactionState((Fts5FullTable*)pVtab, FTS5_SAVEPOINT, iSavepoint); - return sqlite3Fts5FlushToDisk((Fts5Table*)pVtab); + Fts5FullTable *pTab = (Fts5FullTable*)pVtab; + int rc = SQLITE_OK; + + fts5CheckTransactionState(pTab, FTS5_SAVEPOINT, iSavepoint); + rc = sqlite3Fts5FlushToDisk((Fts5Table*)pVtab); + if( rc==SQLITE_OK ){ + pTab->iSavepoint = iSavepoint+1; + } + return rc; } /* @@ -217891,9 +263595,16 @@ static int fts5SavepointMethod(sqlite3_vtab *pVtab, int iSavepoint){ ** This is a no-op. */ static int fts5ReleaseMethod(sqlite3_vtab *pVtab, int iSavepoint){ - UNUSED_PARAM(iSavepoint); /* Call below is a no-op for NDEBUG builds */ - fts5CheckTransactionState((Fts5FullTable*)pVtab, FTS5_RELEASE, iSavepoint); - return sqlite3Fts5FlushToDisk((Fts5Table*)pVtab); + Fts5FullTable *pTab = (Fts5FullTable*)pVtab; + int rc = SQLITE_OK; + fts5CheckTransactionState(pTab, FTS5_RELEASE, iSavepoint); + if( (iSavepoint+1)iSavepoint ){ + rc = sqlite3Fts5FlushToDisk(&pTab->p); + if( rc==SQLITE_OK ){ + pTab->iSavepoint = iSavepoint; + } + } + return rc; } /* @@ -217903,10 +263614,14 @@ static int fts5ReleaseMethod(sqlite3_vtab *pVtab, int iSavepoint){ */ static int fts5RollbackToMethod(sqlite3_vtab *pVtab, int iSavepoint){ Fts5FullTable *pTab = (Fts5FullTable*)pVtab; - UNUSED_PARAM(iSavepoint); /* Call below is a no-op for NDEBUG builds */ + int rc = SQLITE_OK; fts5CheckTransactionState(pTab, FTS5_ROLLBACKTO, iSavepoint); fts5TripCursors(pTab); - return sqlite3Fts5StorageRollback(pTab->pStorage); + if( (iSavepoint+1)<=pTab->iSavepoint ){ + pTab->p.pConfig->pgsz = 0; + rc = sqlite3Fts5StorageRollback(pTab->pStorage); + } + return rc; } /* @@ -217948,47 +263663,210 @@ static int fts5CreateAux( } /* -** Register a new tokenizer. This is the implementation of the -** fts5_api.xCreateTokenizer() method. +** This function is used by xCreateTokenizer_v2() and xCreateTokenizer(). +** It allocates and partially populates a new Fts5TokenizerModule object. +** The new object is already linked into the Fts5Global context before +** returning. +** +** If successful, SQLITE_OK is returned and a pointer to the new +** Fts5TokenizerModule object returned via output parameter (*ppNew). All +** that is required is for the caller to fill in the methods in +** Fts5TokenizerModule.x1 and x2, and to set Fts5TokenizerModule.bV2Native +** as appropriate. +** +** If an error occurs, an SQLite error code is returned and the final value +** of (*ppNew) undefined. */ -static int fts5CreateTokenizer( - fts5_api *pApi, /* Global context (one per db handle) */ +static int fts5NewTokenizerModule( + Fts5Global *pGlobal, /* Global context (one per db handle) */ const char *zName, /* Name of new function */ void *pUserData, /* User data for aux. function */ - fts5_tokenizer *pTokenizer, /* Tokenizer implementation */ - void(*xDestroy)(void*) /* Destructor for pUserData */ + void(*xDestroy)(void*), /* Destructor for pUserData */ + Fts5TokenizerModule **ppNew ){ - Fts5Global *pGlobal = (Fts5Global*)pApi; - Fts5TokenizerModule *pNew; - sqlite3_int64 nName; /* Size of zName and its \0 terminator */ - sqlite3_int64 nByte; /* Bytes of space to allocate */ int rc = SQLITE_OK; + Fts5TokenizerModule *pNew; + sqlite3_int64 nName; /* Size of zName and its \0 terminator */ + sqlite3_int64 nByte; /* Bytes of space to allocate */ nName = strlen(zName) + 1; nByte = sizeof(Fts5TokenizerModule) + nName; - pNew = (Fts5TokenizerModule*)sqlite3_malloc64(nByte); + *ppNew = pNew = (Fts5TokenizerModule*)sqlite3Fts5MallocZero(&rc, nByte); if( pNew ){ - memset(pNew, 0, (size_t)nByte); pNew->zName = (char*)&pNew[1]; memcpy(pNew->zName, zName, nName); pNew->pUserData = pUserData; - pNew->x = *pTokenizer; pNew->xDestroy = xDestroy; pNew->pNext = pGlobal->pTok; pGlobal->pTok = pNew; if( pNew->pNext==0 ){ pGlobal->pDfltTok = pNew; } + } + + return rc; +} + +/* +** An instance of this type is used as the Fts5Tokenizer object for +** wrapper tokenizers - those that provide access to a v1 tokenizer via +** the fts5_tokenizer_v2 API, and those that provide access to a v2 tokenizer +** via the fts5_tokenizer API. +*/ +typedef struct Fts5VtoVTokenizer Fts5VtoVTokenizer; +struct Fts5VtoVTokenizer { + int bV2Native; /* True if v2 native tokenizer */ + fts5_tokenizer x1; /* Tokenizer functions */ + fts5_tokenizer_v2 x2; /* V2 tokenizer functions */ + Fts5Tokenizer *pReal; +}; + +/* +** Create a wrapper tokenizer. The context argument pCtx points to the +** Fts5TokenizerModule object. +*/ +static int fts5VtoVCreate( + void *pCtx, + const char **azArg, + int nArg, + Fts5Tokenizer **ppOut +){ + Fts5TokenizerModule *pMod = (Fts5TokenizerModule*)pCtx; + Fts5VtoVTokenizer *pNew = 0; + int rc = SQLITE_OK; + + pNew = (Fts5VtoVTokenizer*)sqlite3Fts5MallocZero(&rc, sizeof(*pNew)); + if( rc==SQLITE_OK ){ + pNew->x1 = pMod->x1; + pNew->x2 = pMod->x2; + pNew->bV2Native = pMod->bV2Native; + if( pMod->bV2Native ){ + rc = pMod->x2.xCreate(pMod->pUserData, azArg, nArg, &pNew->pReal); + }else{ + rc = pMod->x1.xCreate(pMod->pUserData, azArg, nArg, &pNew->pReal); + } + if( rc!=SQLITE_OK ){ + sqlite3_free(pNew); + pNew = 0; + } + } + + *ppOut = (Fts5Tokenizer*)pNew; + return rc; +} + +/* +** Delete an Fts5VtoVTokenizer wrapper tokenizer. +*/ +static void fts5VtoVDelete(Fts5Tokenizer *pTok){ + Fts5VtoVTokenizer *p = (Fts5VtoVTokenizer*)pTok; + if( p ){ + if( p->bV2Native ){ + p->x2.xDelete(p->pReal); + }else{ + p->x1.xDelete(p->pReal); + } + sqlite3_free(p); + } +} + + +/* +** xTokenizer method for a wrapper tokenizer that offers the v1 interface +** (no support for locales). +*/ +static int fts5V1toV2Tokenize( + Fts5Tokenizer *pTok, + void *pCtx, int flags, + const char *pText, int nText, + int (*xToken)(void*, int, const char*, int, int, int) +){ + Fts5VtoVTokenizer *p = (Fts5VtoVTokenizer*)pTok; + assert( p->bV2Native ); + return p->x2.xTokenize(p->pReal, pCtx, flags, pText, nText, 0, 0, xToken); +} + +/* +** xTokenizer method for a wrapper tokenizer that offers the v2 interface +** (with locale support). +*/ +static int fts5V2toV1Tokenize( + Fts5Tokenizer *pTok, + void *pCtx, int flags, + const char *pText, int nText, + const char *pLocale, int nLocale, + int (*xToken)(void*, int, const char*, int, int, int) +){ + Fts5VtoVTokenizer *p = (Fts5VtoVTokenizer*)pTok; + assert( p->bV2Native==0 ); + UNUSED_PARAM2(pLocale,nLocale); + return p->x1.xTokenize(p->pReal, pCtx, flags, pText, nText, xToken); +} + +/* +** Register a new tokenizer. This is the implementation of the +** fts5_api.xCreateTokenizer_v2() method. +*/ +static int fts5CreateTokenizer_v2( + fts5_api *pApi, /* Global context (one per db handle) */ + const char *zName, /* Name of new function */ + void *pUserData, /* User data for aux. function */ + fts5_tokenizer_v2 *pTokenizer, /* Tokenizer implementation */ + void(*xDestroy)(void*) /* Destructor for pUserData */ +){ + Fts5Global *pGlobal = (Fts5Global*)pApi; + int rc = SQLITE_OK; + + if( pTokenizer->iVersion>2 ){ + rc = SQLITE_ERROR; }else{ - rc = SQLITE_NOMEM; + Fts5TokenizerModule *pNew = 0; + rc = fts5NewTokenizerModule(pGlobal, zName, pUserData, xDestroy, &pNew); + if( pNew ){ + pNew->x2 = *pTokenizer; + pNew->bV2Native = 1; + pNew->x1.xCreate = fts5VtoVCreate; + pNew->x1.xTokenize = fts5V1toV2Tokenize; + pNew->x1.xDelete = fts5VtoVDelete; + } } return rc; } +/* +** The fts5_api.xCreateTokenizer() method. +*/ +static int fts5CreateTokenizer( + fts5_api *pApi, /* Global context (one per db handle) */ + const char *zName, /* Name of new function */ + void *pUserData, /* User data for aux. function */ + fts5_tokenizer *pTokenizer, /* Tokenizer implementation */ + void(*xDestroy)(void*) /* Destructor for pUserData */ +){ + Fts5TokenizerModule *pNew = 0; + int rc = SQLITE_OK; + + rc = fts5NewTokenizerModule( + (Fts5Global*)pApi, zName, pUserData, xDestroy, &pNew + ); + if( pNew ){ + pNew->x1 = *pTokenizer; + pNew->x2.xCreate = fts5VtoVCreate; + pNew->x2.xTokenize = fts5V2toV1Tokenize; + pNew->x2.xDelete = fts5VtoVDelete; + } + return rc; +} + +/* +** Search the global context passed as the first argument for a tokenizer +** module named zName. If found, return a pointer to the Fts5TokenizerModule +** object. Otherwise, return NULL. +*/ static Fts5TokenizerModule *fts5LocateTokenizer( - Fts5Global *pGlobal, - const char *zName + Fts5Global *pGlobal, /* Global (one per db handle) object */ + const char *zName /* Name of tokenizer module to find */ ){ Fts5TokenizerModule *pMod = 0; @@ -218004,7 +263882,37 @@ static Fts5TokenizerModule *fts5LocateTokenizer( } /* -** Find a tokenizer. This is the implementation of the +** Find a tokenizer. This is the implementation of the +** fts5_api.xFindTokenizer_v2() method. +*/ +static int fts5FindTokenizer_v2( + fts5_api *pApi, /* Global context (one per db handle) */ + const char *zName, /* Name of tokenizer */ + void **ppUserData, + fts5_tokenizer_v2 **ppTokenizer /* Populate this object */ +){ + int rc = SQLITE_OK; + Fts5TokenizerModule *pMod; + + pMod = fts5LocateTokenizer((Fts5Global*)pApi, zName); + if( pMod ){ + if( pMod->bV2Native ){ + *ppUserData = pMod->pUserData; + }else{ + *ppUserData = (void*)pMod; + } + *ppTokenizer = &pMod->x2; + }else{ + *ppTokenizer = 0; + *ppUserData = 0; + rc = SQLITE_ERROR; + } + + return rc; +} + +/* +** Find a tokenizer. This is the implementation of the ** fts5_api.xFindTokenizer() method. */ static int fts5FindTokenizer( @@ -218018,48 +263926,75 @@ static int fts5FindTokenizer( pMod = fts5LocateTokenizer((Fts5Global*)pApi, zName); if( pMod ){ - *pTokenizer = pMod->x; - *ppUserData = pMod->pUserData; + if( pMod->bV2Native==0 ){ + *ppUserData = pMod->pUserData; + }else{ + *ppUserData = (void*)pMod; + } + *pTokenizer = pMod->x1; }else{ - memset(pTokenizer, 0, sizeof(fts5_tokenizer)); + memset(pTokenizer, 0, sizeof(*pTokenizer)); + *ppUserData = 0; rc = SQLITE_ERROR; } return rc; } -static int sqlite3Fts5GetTokenizer( - Fts5Global *pGlobal, - const char **azArg, - int nArg, - Fts5Tokenizer **ppTok, - fts5_tokenizer **ppTokApi, - char **pzErr -){ - Fts5TokenizerModule *pMod; +/* +** Attempt to instantiate the tokenizer. +*/ +static int sqlite3Fts5LoadTokenizer(Fts5Config *pConfig){ + const char **azArg = pConfig->t.azArg; + const int nArg = pConfig->t.nArg; + Fts5TokenizerModule *pMod = 0; int rc = SQLITE_OK; - pMod = fts5LocateTokenizer(pGlobal, nArg==0 ? 0 : azArg[0]); + pMod = fts5LocateTokenizer(pConfig->pGlobal, nArg==0 ? 0 : azArg[0]); if( pMod==0 ){ assert( nArg>0 ); rc = SQLITE_ERROR; - *pzErr = sqlite3_mprintf("no such tokenizer: %s", azArg[0]); + sqlite3Fts5ConfigErrmsg(pConfig, "no such tokenizer: %s", azArg[0]); }else{ - rc = pMod->x.xCreate(pMod->pUserData, &azArg[1], (nArg?nArg-1:0), ppTok); - *ppTokApi = &pMod->x; - if( rc!=SQLITE_OK && pzErr ){ - *pzErr = sqlite3_mprintf("error in tokenizer constructor"); + int (*xCreate)(void*, const char**, int, Fts5Tokenizer**) = 0; + if( pMod->bV2Native ){ + xCreate = pMod->x2.xCreate; + pConfig->t.pApi2 = &pMod->x2; + }else{ + pConfig->t.pApi1 = &pMod->x1; + xCreate = pMod->x1.xCreate; + } + + rc = xCreate(pMod->pUserData, + (azArg?&azArg[1]:0), (nArg?nArg-1:0), &pConfig->t.pTok + ); + + if( rc!=SQLITE_OK ){ + if( rc!=SQLITE_NOMEM ){ + sqlite3Fts5ConfigErrmsg(pConfig, "error in tokenizer constructor"); + } + }else if( pMod->bV2Native==0 ){ + pConfig->t.ePattern = sqlite3Fts5TokenizerPattern( + pMod->x1.xCreate, pConfig->t.pTok + ); } } if( rc!=SQLITE_OK ){ - *ppTokApi = 0; - *ppTok = 0; + pConfig->t.pApi1 = 0; + pConfig->t.pApi2 = 0; + pConfig->t.pTok = 0; } return rc; } + +/* +** xDestroy callback passed to sqlite3_create_module(). This is invoked +** when the db handle is being closed. Free memory associated with +** tokenizers and aux functions registered with this db handle. +*/ static void fts5ModuleDestroy(void *pCtx){ Fts5TokenizerModule *pTok, *pNextTok; Fts5Auxiliary *pAux, *pNextAux; @@ -218080,6 +264015,10 @@ static void fts5ModuleDestroy(void *pCtx){ sqlite3_free(pGlobal); } +/* +** Implementation of the fts5() function used by clients to obtain the +** API pointer. +*/ static void fts5Fts5Func( sqlite3_context *pCtx, /* Function call context */ int nArg, /* Number of args */ @@ -218103,7 +264042,82 @@ static void fts5SourceIdFunc( ){ assert( nArg==0 ); UNUSED_PARAM2(nArg, apUnused); - sqlite3_result_text(pCtx, "fts5: 2019-04-16 19:49:53 884b4b7e502b4e991677b53971277adfaf0a04a284f8e483e2553d0f83156b50", -1, SQLITE_TRANSIENT); + sqlite3_result_text(pCtx, "fts5: 2026-07-24 19:02:57 bf7c7f30031888f4e796e429ab3978879485813aaca6f641c7b33e4e09459bcc", -1, SQLITE_TRANSIENT); +} + +/* +** Implementation of fts5_locale(LOCALE, TEXT) function. +** +** If parameter LOCALE is NULL, or a zero-length string, then a copy of +** TEXT is returned. Otherwise, both LOCALE and TEXT are interpreted as +** text, and the value returned is a blob consisting of: +** +** * The 4 bytes 0x00, 0xE0, 0xB2, 0xEb (FTS5_LOCALE_HEADER). +** * The LOCALE, as utf-8 text, followed by +** * 0x00, followed by +** * The TEXT, as utf-8 text. +** +** There is no final nul-terminator following the TEXT value. +*/ +static void fts5LocaleFunc( + sqlite3_context *pCtx, /* Function call context */ + int nArg, /* Number of args */ + sqlite3_value **apArg /* Function arguments */ +){ + const char *zLocale = 0; + i64 nLocale = 0; + const char *zText = 0; + i64 nText = 0; + + assert( nArg==2 ); + UNUSED_PARAM(nArg); + + zLocale = (const char*)sqlite3_value_text(apArg[0]); + nLocale = sqlite3_value_bytes(apArg[0]); + + zText = (const char*)sqlite3_value_text(apArg[1]); + nText = sqlite3_value_bytes(apArg[1]); + + if( zLocale==0 || zLocale[0]=='\0' ){ + sqlite3_result_text(pCtx, zText, nText, SQLITE_TRANSIENT); + }else{ + Fts5Global *p = (Fts5Global*)sqlite3_user_data(pCtx); + u8 *pBlob = 0; + u8 *pCsr = 0; + i64 nBlob = 0; + + nBlob = FTS5_LOCALE_HDR_SIZE + nLocale + 1 + nText; + pBlob = (u8*)sqlite3_malloc64(nBlob); + if( pBlob==0 ){ + sqlite3_result_error_nomem(pCtx); + return; + } + + pCsr = pBlob; + memcpy(pCsr, (const u8*)p->aLocaleHdr, FTS5_LOCALE_HDR_SIZE); + pCsr += FTS5_LOCALE_HDR_SIZE; + memcpy(pCsr, zLocale, nLocale); + pCsr += nLocale; + (*pCsr++) = 0x00; + if( zText ) memcpy(pCsr, zText, nText); + assert( &pCsr[nText]==&pBlob[nBlob] ); + + sqlite3_result_blob(pCtx, pBlob, nBlob, sqlite3_free); + } +} + +/* +** Implementation of fts5_insttoken() function. +*/ +static void fts5InsttokenFunc( + sqlite3_context *pCtx, /* Function call context */ + int nArg, /* Number of args */ + sqlite3_value **apArg /* Function arguments */ +){ + assert( nArg==1 ); + (void)nArg; + sqlite3_result_value(pCtx, apArg[0]); + sqlite3_result_subtype(pCtx, FTS5_INSTTOKEN_SUBTYPE); } /* @@ -218121,9 +264135,48 @@ static int fts5ShadowName(const char *zName){ return 0; } +/* +** Run an integrity check on the FTS5 data structures. Return a string +** if anything is found amiss. Return a NULL pointer if everything is +** OK. +*/ +static int fts5IntegrityMethod( + sqlite3_vtab *pVtab, /* the FTS5 virtual table to check */ + const char *zSchema, /* Name of schema in which this table lives */ + const char *zTabname, /* Name of the table itself */ + int isQuick, /* True if this is a quick-check */ + char **pzErr /* Write error message here */ +){ + Fts5FullTable *pTab = (Fts5FullTable*)pVtab; + int rc; + + assert( pzErr!=0 && *pzErr==0 ); + UNUSED_PARAM(isQuick); + assert( pTab->p.pConfig->pzErrmsg==0 ); + pTab->p.pConfig->pzErrmsg = pzErr; + rc = sqlite3Fts5StorageIntegrity(pTab->pStorage, 0); + if( *pzErr==0 && rc!=SQLITE_OK ){ + if( (rc&0xff)==SQLITE_CORRUPT ){ + *pzErr = sqlite3_mprintf("malformed inverted index for FTS5 table %s.%s", + zSchema, zTabname); + rc = (*pzErr) ? SQLITE_OK : SQLITE_NOMEM; + }else{ + *pzErr = sqlite3_mprintf("unable to validate the inverted index for" + " FTS5 table %s.%s: %s", + zSchema, zTabname, sqlite3_errstr(rc)); + } + }else if( (rc&0xff)==SQLITE_CORRUPT ){ + rc = SQLITE_OK; + } + sqlite3Fts5IndexCloseReader(pTab->p.pIndex); + pTab->p.pConfig->pzErrmsg = 0; + + return rc; +} + static int fts5Init(sqlite3 *db){ static const sqlite3_module fts5Mod = { - /* iVersion */ 3, + /* iVersion */ 4, /* xCreate */ fts5CreateMethod, /* xConnect */ fts5ConnectMethod, /* xBestIndex */ fts5BestIndexMethod, @@ -218146,23 +264199,36 @@ static int fts5Init(sqlite3 *db){ /* xSavepoint */ fts5SavepointMethod, /* xRelease */ fts5ReleaseMethod, /* xRollbackTo */ fts5RollbackToMethod, - /* xShadowName */ fts5ShadowName + /* xShadowName */ fts5ShadowName, + /* xIntegrity */ fts5IntegrityMethod }; int rc; Fts5Global *pGlobal = 0; - pGlobal = (Fts5Global*)sqlite3_malloc(sizeof(Fts5Global)); + pGlobal = (Fts5Global*)sqlite3_malloc64(sizeof(Fts5Global)); if( pGlobal==0 ){ rc = SQLITE_NOMEM; }else{ void *p = (void*)pGlobal; memset(pGlobal, 0, sizeof(Fts5Global)); pGlobal->db = db; - pGlobal->api.iVersion = 2; + pGlobal->api.iVersion = 3; pGlobal->api.xCreateFunction = fts5CreateAux; pGlobal->api.xCreateTokenizer = fts5CreateTokenizer; pGlobal->api.xFindTokenizer = fts5FindTokenizer; + pGlobal->api.xCreateTokenizer_v2 = fts5CreateTokenizer_v2; + pGlobal->api.xFindTokenizer_v2 = fts5FindTokenizer_v2; + + /* Initialize pGlobal->aLocaleHdr[] to a 128-bit pseudo-random vector. + ** The constants below were generated randomly. */ + sqlite3_randomness(sizeof(pGlobal->aLocaleHdr), pGlobal->aLocaleHdr); + pGlobal->aLocaleHdr[0] ^= 0xF924976D; + pGlobal->aLocaleHdr[1] ^= 0x16596E13; + pGlobal->aLocaleHdr[2] ^= 0x7C80BEAA; + pGlobal->aLocaleHdr[3] ^= 0x9B03A67F; + assert( sizeof(pGlobal->aLocaleHdr)==16 ); + rc = sqlite3_create_module_v2(db, "fts5", &fts5Mod, p, fts5ModuleDestroy); if( rc==SQLITE_OK ) rc = sqlite3Fts5IndexInit(db); if( rc==SQLITE_OK ) rc = sqlite3Fts5ExprInit(pGlobal, db); @@ -218176,7 +264242,23 @@ static int fts5Init(sqlite3 *db){ } if( rc==SQLITE_OK ){ rc = sqlite3_create_function( - db, "fts5_source_id", 0, SQLITE_UTF8, p, fts5SourceIdFunc, 0, 0 + db, "fts5_source_id", 0, + SQLITE_UTF8|SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS, + p, fts5SourceIdFunc, 0, 0 + ); + } + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function( + db, "fts5_locale", 2, + SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_RESULT_SUBTYPE|SQLITE_SUBTYPE, + p, fts5LocaleFunc, 0, 0 + ); + } + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function( + db, "fts5_insttoken", 1, + SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_RESULT_SUBTYPE, + p, fts5InsttokenFunc, 0, 0 ); } } @@ -218186,8 +264268,8 @@ static int fts5Init(sqlite3 *db){ ** its entry point to enable the matchinfo() demo. */ #ifdef SQLITE_FTS5_ENABLE_TEST_MI if( rc==SQLITE_OK ){ - extern int sqlite3Fts5TestRegisterMatchinfo(sqlite3*); - rc = sqlite3Fts5TestRegisterMatchinfo(db); + extern int sqlite3Fts5TestRegisterMatchinfoAPI(fts5_api*); + rc = sqlite3Fts5TestRegisterMatchinfoAPI(&pGlobal->api); } #endif @@ -218199,7 +264281,7 @@ static int fts5Init(sqlite3 *db){ ** this module is being built as part of the SQLite core (SQLITE_CORE is ** defined), then sqlite3_open() will call sqlite3Fts5Init() directly. ** -** Or, if this module is being built as a loadable extension, +** Or, if this module is being built as a loadable extension, ** sqlite3Fts5Init() is omitted and the two standard entry points ** sqlite3_fts_init() and sqlite3_fts5_init() defined instead. */ @@ -218253,34 +264335,62 @@ SQLITE_PRIVATE int sqlite3Fts5Init(sqlite3 *db){ /* #include "fts5Int.h" */ +/* +** pSavedRow: +** SQL statement FTS5_STMT_LOOKUP2 is a copy of FTS5_STMT_LOOKUP, it +** does a by-rowid lookup to retrieve a single row from the %_content +** table or equivalent external-content table/view. +** +** However, FTS5_STMT_LOOKUP2 is only used when retrieving the original +** values for a row being UPDATEd. In that case, the SQL statement is +** not reset and pSavedRow is set to point at it. This is so that the +** insert operation that follows the delete may access the original +** row values for any new values for which sqlite3_value_nochange() returns +** true. i.e. if the user executes: +** +** CREATE VIRTUAL TABLE ft USING fts5(a, b, c, locale=1); +** ... +** UPDATE fts SET a=?, b=? WHERE rowid=?; +** +** then the value passed to the xUpdate() method of this table as the +** new.c value is an sqlite3_value_nochange() value. So in this case it +** must be read from the saved row stored in Fts5Storage.pSavedRow. +** +** This is necessary - using sqlite3_value_nochange() instead of just having +** SQLite pass the original value back via xUpdate() - so as not to discard +** any locale information associated with such values. +** +*/ struct Fts5Storage { Fts5Config *pConfig; Fts5Index *pIndex; int bTotalsValid; /* True if nTotalRow/aTotalSize[] are valid */ i64 nTotalRow; /* Total number of rows in FTS table */ - i64 *aTotalSize; /* Total sizes of each column */ - sqlite3_stmt *aStmt[11]; + i64 *aTotalSize; /* Total sizes of each column */ + sqlite3_stmt *pSavedRow; + sqlite3_stmt *aStmt[12]; }; -#if FTS5_STMT_SCAN_ASC!=0 -# error "FTS5_STMT_SCAN_ASC mismatch" +#if FTS5_STMT_SCAN_ASC!=0 +# error "FTS5_STMT_SCAN_ASC mismatch" #endif -#if FTS5_STMT_SCAN_DESC!=1 -# error "FTS5_STMT_SCAN_DESC mismatch" +#if FTS5_STMT_SCAN_DESC!=1 +# error "FTS5_STMT_SCAN_DESC mismatch" #endif #if FTS5_STMT_LOOKUP!=2 -# error "FTS5_STMT_LOOKUP mismatch" +# error "FTS5_STMT_LOOKUP mismatch" #endif -#define FTS5_STMT_INSERT_CONTENT 3 -#define FTS5_STMT_REPLACE_CONTENT 4 -#define FTS5_STMT_DELETE_CONTENT 5 -#define FTS5_STMT_REPLACE_DOCSIZE 6 -#define FTS5_STMT_DELETE_DOCSIZE 7 -#define FTS5_STMT_LOOKUP_DOCSIZE 8 -#define FTS5_STMT_REPLACE_CONFIG 9 -#define FTS5_STMT_SCAN 10 +#define FTS5_STMT_LOOKUP2 3 +#define FTS5_STMT_INSERT_CONTENT 4 +#define FTS5_STMT_REPLACE_CONTENT 5 +#define FTS5_STMT_DELETE_CONTENT 6 +#define FTS5_STMT_REPLACE_DOCSIZE 7 +#define FTS5_STMT_DELETE_DOCSIZE 8 +#define FTS5_STMT_LOOKUP_DOCSIZE 9 +#define FTS5_STMT_REPLACE_CONFIG 10 +#define FTS5_STMT_SCAN 11 /* ** Prepare the two insert statements - Fts5Storage.pInsertContent and @@ -218296,12 +264406,12 @@ static int fts5StorageGetStmt( ){ int rc = SQLITE_OK; - /* If there is no %_docsize table, there should be no requests for + /* If there is no %_docsize table, there should be no requests for ** statements to operate on it. */ assert( p->pConfig->bColumnsize || ( - eStmt!=FTS5_STMT_REPLACE_DOCSIZE - && eStmt!=FTS5_STMT_DELETE_DOCSIZE - && eStmt!=FTS5_STMT_LOOKUP_DOCSIZE + eStmt!=FTS5_STMT_REPLACE_DOCSIZE + && eStmt!=FTS5_STMT_DELETE_DOCSIZE + && eStmt!=FTS5_STMT_LOOKUP_DOCSIZE )); assert( eStmt>=0 && eStmtaStmt) ); @@ -218310,14 +264420,15 @@ static int fts5StorageGetStmt( "SELECT %s FROM %s T WHERE T.%Q >= ? AND T.%Q <= ? ORDER BY T.%Q ASC", "SELECT %s FROM %s T WHERE T.%Q <= ? AND T.%Q >= ? ORDER BY T.%Q DESC", "SELECT %s FROM %s T WHERE T.%Q=?", /* LOOKUP */ + "SELECT %s FROM %s T WHERE T.%Q=?", /* LOOKUP2 */ "INSERT INTO %Q.'%q_content' VALUES(%s)", /* INSERT_CONTENT */ "REPLACE INTO %Q.'%q_content' VALUES(%s)", /* REPLACE_CONTENT */ "DELETE FROM %Q.'%q_content' WHERE id=?", /* DELETE_CONTENT */ - "REPLACE INTO %Q.'%q_docsize' VALUES(?,?)", /* REPLACE_DOCSIZE */ + "REPLACE INTO %Q.'%q_docsize' VALUES(?,?%s)", /* REPLACE_DOCSIZE */ "DELETE FROM %Q.'%q_docsize' WHERE id=?", /* DELETE_DOCSIZE */ - "SELECT sz FROM %Q.'%q_docsize' WHERE id=?", /* LOOKUP_DOCSIZE */ + "SELECT sz%s FROM %Q.'%q_docsize' WHERE id=?", /* LOOKUP_DOCSIZE */ "REPLACE INTO %Q.'%q_config' VALUES(?,?)", /* REPLACE_CONFIG */ "SELECT %s FROM %s AS T", /* SCAN */ @@ -218325,46 +264436,77 @@ static int fts5StorageGetStmt( Fts5Config *pC = p->pConfig; char *zSql = 0; + assert( ArraySize(azStmt)==ArraySize(p->aStmt) ); + switch( eStmt ){ case FTS5_STMT_SCAN: - zSql = sqlite3_mprintf(azStmt[eStmt], + zSql = sqlite3_mprintf(azStmt[eStmt], pC->zContentExprlist, pC->zContent ); break; case FTS5_STMT_SCAN_ASC: case FTS5_STMT_SCAN_DESC: - zSql = sqlite3_mprintf(azStmt[eStmt], pC->zContentExprlist, + zSql = sqlite3_mprintf(azStmt[eStmt], pC->zContentExprlist, pC->zContent, pC->zContentRowid, pC->zContentRowid, pC->zContentRowid ); break; case FTS5_STMT_LOOKUP: - zSql = sqlite3_mprintf(azStmt[eStmt], + case FTS5_STMT_LOOKUP2: + zSql = sqlite3_mprintf(azStmt[eStmt], pC->zContentExprlist, pC->zContent, pC->zContentRowid ); break; - case FTS5_STMT_INSERT_CONTENT: + case FTS5_STMT_INSERT_CONTENT: case FTS5_STMT_REPLACE_CONTENT: { - int nCol = pC->nCol + 1; - char *zBind; + char *zBind = 0; int i; - zBind = sqlite3_malloc64(1 + nCol*2); - if( zBind ){ - for(i=0; ieContent==FTS5_CONTENT_NORMAL + || pC->eContent==FTS5_CONTENT_UNINDEXED + ); + + /* Add bindings for the "c*" columns - those that store the actual + ** table content. If eContent==NORMAL, then there is one binding + ** for each column. Or, if eContent==UNINDEXED, then there are only + ** bindings for the UNINDEXED columns. */ + for(i=0; rc==SQLITE_OK && i<(pC->nCol+1); i++){ + if( !i || pC->eContent==FTS5_CONTENT_NORMAL || pC->abUnindexed[i-1] ){ + zBind = sqlite3Fts5Mprintf(&rc, "%z%s?%d", zBind, zBind?",":"",i+1); + } + } + + /* Add bindings for any "l*" columns. Only non-UNINDEXED columns + ** require these. */ + if( pC->bLocale && pC->eContent==FTS5_CONTENT_NORMAL ){ + for(i=0; rc==SQLITE_OK && inCol; i++){ + if( pC->abUnindexed[i]==0 ){ + zBind = sqlite3Fts5Mprintf(&rc, "%z,?%d", zBind, pC->nCol+i+2); + } } - zBind[i*2-1] = '\0'; - zSql = sqlite3_mprintf(azStmt[eStmt], pC->zDb, pC->zName, zBind); - sqlite3_free(zBind); } + + zSql = sqlite3Fts5Mprintf(&rc, azStmt[eStmt], pC->zDb, pC->zName,zBind); + sqlite3_free(zBind); break; } + case FTS5_STMT_REPLACE_DOCSIZE: + zSql = sqlite3_mprintf(azStmt[eStmt], pC->zDb, pC->zName, + (pC->bContentlessDelete ? ",?" : "") + ); + break; + + case FTS5_STMT_LOOKUP_DOCSIZE: + zSql = sqlite3_mprintf(azStmt[eStmt], + (pC->bContentlessDelete ? ",origin" : ""), + pC->zDb, pC->zName + ); + break; + default: zSql = sqlite3_mprintf(azStmt[eStmt], pC->zDb, pC->zName); break; @@ -218374,12 +264516,19 @@ static int fts5StorageGetStmt( rc = SQLITE_NOMEM; }else{ int f = SQLITE_PREPARE_PERSISTENT; - if( eStmt>FTS5_STMT_LOOKUP ) f |= SQLITE_PREPARE_NO_VTAB; + if( eStmt>FTS5_STMT_LOOKUP2 ) f |= SQLITE_PREPARE_NO_VTAB; + p->pConfig->bLock++; rc = sqlite3_prepare_v3(pC->db, zSql, -1, f, &p->aStmt[eStmt], 0); + p->pConfig->bLock--; sqlite3_free(zSql); if( rc!=SQLITE_OK && pzErrMsg ){ *pzErrMsg = sqlite3_mprintf("%s", sqlite3_errmsg(pC->db)); } + if( rc==SQLITE_ERROR && eStmt>FTS5_STMT_LOOKUP2 && eStmtdb, 0, + int rc = fts5ExecPrintf(pConfig->db, 0, "DROP TABLE IF EXISTS %Q.'%q_data';" "DROP TABLE IF EXISTS %Q.'%q_idx';" "DROP TABLE IF EXISTS %Q.'%q_config';", @@ -218427,13 +264576,13 @@ static int sqlite3Fts5DropAll(Fts5Config *pConfig){ pConfig->zDb, pConfig->zName ); if( rc==SQLITE_OK && pConfig->bColumnsize ){ - rc = fts5ExecPrintf(pConfig->db, 0, + rc = fts5ExecPrintf(pConfig->db, 0, "DROP TABLE IF EXISTS %Q.'%q_docsize';", pConfig->zDb, pConfig->zName ); } if( rc==SQLITE_OK && pConfig->eContent==FTS5_CONTENT_NORMAL ){ - rc = fts5ExecPrintf(pConfig->db, 0, + rc = fts5ExecPrintf(pConfig->db, 0, "DROP TABLE IF EXISTS %Q.'%q_content';", pConfig->zDb, pConfig->zName ); @@ -218448,7 +264597,7 @@ static void fts5StorageRenameOne( const char *zName /* New name of FTS5 table */ ){ if( *pRc==SQLITE_OK ){ - *pRc = fts5ExecPrintf(pConfig->db, 0, + *pRc = fts5ExecPrintf(pConfig->db, 0, "ALTER TABLE %Q.'%q_%s' RENAME TO '%q_%s';", pConfig->zDb, pConfig->zName, zTail, zName, zTail ); @@ -218486,7 +264635,7 @@ static int sqlite3Fts5CreateTable( char *zErr = 0; rc = fts5ExecPrintf(pConfig->db, &zErr, "CREATE TABLE %Q.'%q_%q'(%s)%s", - pConfig->zDb, pConfig->zName, zPost, zDefn, + pConfig->zDb, pConfig->zName, zPost, zDefn, #ifndef SQLITE_FTS5_NO_WITHOUT_ROWID bWithout?" WITHOUT ROWID": #endif @@ -218494,7 +264643,7 @@ static int sqlite3Fts5CreateTable( ); if( zErr ){ *pzErr = sqlite3_mprintf( - "fts5: error creating shadow table %q_%s: %s", + "fts5: error creating shadow table %q_%s: %s", pConfig->zName, zPost, zErr ); sqlite3_free(zErr); @@ -218505,15 +264654,15 @@ static int sqlite3Fts5CreateTable( /* ** Open a new Fts5Index handle. If the bCreate argument is true, create -** and initialize the underlying tables +** and initialize the underlying tables ** ** If successful, set *pp to point to the new object and return SQLITE_OK. ** Otherwise, set *pp to NULL and return an SQLite error code. */ static int sqlite3Fts5StorageOpen( - Fts5Config *pConfig, - Fts5Index *pIndex, - int bCreate, + Fts5Config *pConfig, + Fts5Index *pIndex, + int bCreate, Fts5Storage **pp, char **pzErr /* OUT: Error message */ ){ @@ -218532,29 +264681,42 @@ static int sqlite3Fts5StorageOpen( p->pIndex = pIndex; if( bCreate ){ - if( pConfig->eContent==FTS5_CONTENT_NORMAL ){ - int nDefn = 32 + pConfig->nCol*10; - char *zDefn = sqlite3_malloc64(32 + (sqlite3_int64)pConfig->nCol * 10); - if( zDefn==0 ){ - rc = SQLITE_NOMEM; - }else{ - int i; - int iOff; - sqlite3_snprintf(nDefn, zDefn, "id INTEGER PRIMARY KEY"); - iOff = (int)strlen(zDefn); + if( pConfig->eContent==FTS5_CONTENT_NORMAL + || pConfig->eContent==FTS5_CONTENT_UNINDEXED + ){ + int i = 0; + char *zDefn = 0; + sqlite3_str *pDefn = sqlite3_str_new(pConfig->db); + + sqlite3_str_appendf(pDefn, "id INTEGER PRIMARY KEY"); + for(i=0; inCol; i++){ + if( pConfig->eContent==FTS5_CONTENT_NORMAL || pConfig->abUnindexed[i] ){ + sqlite3_str_appendf(pDefn, ", c%d", i); + } + } + if( pConfig->bLocale ){ for(i=0; inCol; i++){ - sqlite3_snprintf(nDefn-iOff, &zDefn[iOff], ", c%d", i); - iOff += (int)strlen(&zDefn[iOff]); + if( pConfig->abUnindexed[i]==0 ){ + sqlite3_str_appendf(pDefn, ", l%d", i); + } } + } + zDefn = sqlite3_str_finish(pDefn); + + if( zDefn ){ rc = sqlite3Fts5CreateTable(pConfig, "content", zDefn, 0, pzErr); + sqlite3_free(zDefn); + }else{ + rc = SQLITE_NOMEM; } - sqlite3_free(zDefn); } if( rc==SQLITE_OK && pConfig->bColumnsize ){ - rc = sqlite3Fts5CreateTable( - pConfig, "docsize", "id INTEGER PRIMARY KEY, sz BLOB", 0, pzErr - ); + const char *zCols = "id INTEGER PRIMARY KEY, sz BLOB"; + if( pConfig->bContentlessDelete ){ + zCols = "id INTEGER PRIMARY KEY, sz BLOB, origin INTEGER"; + } + rc = sqlite3Fts5CreateTable(pConfig, "docsize", zCols, 0, pzErr); } if( rc==SQLITE_OK ){ rc = sqlite3Fts5CreateTable( @@ -218619,60 +264781,193 @@ static int fts5StorageInsertCallback( return sqlite3Fts5IndexWrite(pIdx, pCtx->iCol, pCtx->szCol-1, pToken, nToken); } +/* +** This function is used as part of an UPDATE statement that modifies the +** rowid of a row. In that case, this function is called first to set +** Fts5Storage.pSavedRow to point to a statement that may be used to +** access the original values of the row being deleted - iDel. +** +** SQLITE_OK is returned if successful, or an SQLite error code otherwise. +** It is not considered an error if row iDel does not exist. In this case +** pSavedRow is not set and SQLITE_OK returned. +*/ +static int sqlite3Fts5StorageFindDeleteRow(Fts5Storage *p, i64 iDel){ + int rc = SQLITE_OK; + sqlite3_stmt *pSeek = 0; + + assert( p->pSavedRow==0 ); + rc = fts5StorageGetStmt(p, FTS5_STMT_LOOKUP+1, &pSeek, 0); + if( rc==SQLITE_OK ){ + sqlite3_bind_int64(pSeek, 1, iDel); + if( sqlite3_step(pSeek)!=SQLITE_ROW ){ + rc = sqlite3_reset(pSeek); + }else{ + p->pSavedRow = pSeek; + } + } + + return rc; +} + /* ** If a row with rowid iDel is present in the %_content table, add the ** delete-markers to the FTS index necessary to delete it. Do not actually ** remove the %_content row at this time though. +** +** If parameter bSaveRow is true, then Fts5Storage.pSavedRow is left +** pointing to a statement (FTS5_STMT_LOOKUP2) that may be used to access +** the original values of the row being deleted. This is used by UPDATE +** statements. */ static int fts5StorageDeleteFromIndex( - Fts5Storage *p, - i64 iDel, - sqlite3_value **apVal + Fts5Storage *p, + i64 iDel, + sqlite3_value **apVal, + int bSaveRow /* True to set pSavedRow */ ){ Fts5Config *pConfig = p->pConfig; sqlite3_stmt *pSeek = 0; /* SELECT to read row iDel from %_data */ - int rc; /* Return code */ + int rc = SQLITE_OK; /* Return code */ int rc2; /* sqlite3_reset() return code */ int iCol; Fts5InsertCtx ctx; + assert( bSaveRow==0 || apVal==0 ); + assert( bSaveRow==0 || bSaveRow==1 ); + assert( FTS5_STMT_LOOKUP2==FTS5_STMT_LOOKUP+1 ); + if( apVal==0 ){ - rc = fts5StorageGetStmt(p, FTS5_STMT_LOOKUP, &pSeek, 0); - if( rc!=SQLITE_OK ) return rc; - sqlite3_bind_int64(pSeek, 1, iDel); - if( sqlite3_step(pSeek)!=SQLITE_ROW ){ - return sqlite3_reset(pSeek); + if( p->pSavedRow && bSaveRow ){ + pSeek = p->pSavedRow; + p->pSavedRow = 0; + }else{ + rc = fts5StorageGetStmt(p, FTS5_STMT_LOOKUP+bSaveRow, &pSeek, 0); + if( rc!=SQLITE_OK ) return rc; + sqlite3_bind_int64(pSeek, 1, iDel); + if( sqlite3_step(pSeek)!=SQLITE_ROW ){ + return sqlite3_reset(pSeek); + } } } ctx.pStorage = p; ctx.iCol = -1; - rc = sqlite3Fts5IndexBeginWrite(p->pIndex, 1, iDel); for(iCol=1; rc==SQLITE_OK && iCol<=pConfig->nCol; iCol++){ if( pConfig->abUnindexed[iCol-1]==0 ){ - const char *zText; - int nText; + sqlite3_value *pVal = 0; + sqlite3_value *pFree = 0; + const char *pText = 0; + int nText = 0; + const char *pLoc = 0; + int nLoc = 0; + + assert( pSeek==0 || apVal==0 ); + assert( pSeek!=0 || apVal!=0 ); if( pSeek ){ - zText = (const char*)sqlite3_column_text(pSeek, iCol); - nText = sqlite3_column_bytes(pSeek, iCol); + pVal = sqlite3_column_value(pSeek, iCol); }else{ - zText = (const char*)sqlite3_value_text(apVal[iCol-1]); - nText = sqlite3_value_bytes(apVal[iCol-1]); + pVal = apVal[iCol-1]; } - ctx.szCol = 0; - rc = sqlite3Fts5Tokenize(pConfig, FTS5_TOKENIZE_DOCUMENT, - zText, nText, (void*)&ctx, fts5StorageInsertCallback - ); - p->aTotalSize[iCol-1] -= (i64)ctx.szCol; + + if( pConfig->bLocale && sqlite3Fts5IsLocaleValue(pConfig, pVal) ){ + rc = sqlite3Fts5DecodeLocaleValue(pVal, &pText, &nText, &pLoc, &nLoc); + }else{ + if( sqlite3_value_type(pVal)!=SQLITE_TEXT ){ + /* Make a copy of the value to work with. This is because the call + ** to sqlite3_value_text() below forces the type of the value to + ** SQLITE_TEXT, and we may need to use it again later. */ + pFree = pVal = sqlite3_value_dup(pVal); + if( pVal==0 ){ + rc = SQLITE_NOMEM; + } + } + if( rc==SQLITE_OK ){ + pText = (const char*)sqlite3_value_text(pVal); + nText = sqlite3_value_bytes(pVal); + if( pConfig->bLocale && pSeek ){ + pLoc = (const char*)sqlite3_column_text(pSeek, iCol+pConfig->nCol); + nLoc = sqlite3_column_bytes(pSeek, iCol + pConfig->nCol); + } + } + } + + if( rc==SQLITE_OK ){ + sqlite3Fts5SetLocale(pConfig, pLoc, nLoc); + ctx.szCol = 0; + rc = sqlite3Fts5Tokenize(pConfig, FTS5_TOKENIZE_DOCUMENT, + pText, nText, (void*)&ctx, fts5StorageInsertCallback + ); + p->aTotalSize[iCol-1] -= (i64)ctx.szCol; + if( rc==SQLITE_OK && p->aTotalSize[iCol-1]<0 ){ + rc = FTS5_CORRUPT; + } + sqlite3Fts5ClearLocale(pConfig); + } + sqlite3_value_free(pFree); } } - p->nTotalRow--; + if( rc==SQLITE_OK && p->nTotalRow<1 ){ + rc = FTS5_CORRUPT; + }else{ + p->nTotalRow--; + } - rc2 = sqlite3_reset(pSeek); - if( rc==SQLITE_OK ) rc = rc2; + if( rc==SQLITE_OK && bSaveRow ){ + assert( p->pSavedRow==0 ); + p->pSavedRow = pSeek; + }else{ + rc2 = sqlite3_reset(pSeek); + if( rc==SQLITE_OK ) rc = rc2; + } return rc; } +/* +** Reset any saved statement pSavedRow. Zero pSavedRow as well. This +** should be called by the xUpdate() method of the fts5 table before +** returning from any operation that may have set Fts5Storage.pSavedRow. +*/ +static void sqlite3Fts5StorageReleaseDeleteRow(Fts5Storage *pStorage){ + assert( pStorage->pSavedRow==0 + || pStorage->pSavedRow==pStorage->aStmt[FTS5_STMT_LOOKUP2] + ); + sqlite3_reset(pStorage->pSavedRow); + pStorage->pSavedRow = 0; +} + +/* +** This function is called to process a DELETE on a contentless_delete=1 +** table. It adds the tombstone required to delete the entry with rowid +** iDel. If successful, SQLITE_OK is returned. Or, if an error occurs, +** an SQLite error code. +*/ +static int fts5StorageContentlessDelete(Fts5Storage *p, i64 iDel){ + i64 iOrigin = 0; + sqlite3_stmt *pLookup = 0; + int rc = SQLITE_OK; + + assert( p->pConfig->bContentlessDelete ); + assert( p->pConfig->eContent==FTS5_CONTENT_NONE + || p->pConfig->eContent==FTS5_CONTENT_UNINDEXED + ); + + /* Look up the origin of the document in the %_docsize table. Store + ** this in stack variable iOrigin. */ + rc = fts5StorageGetStmt(p, FTS5_STMT_LOOKUP_DOCSIZE, &pLookup, 0); + if( rc==SQLITE_OK ){ + sqlite3_bind_int64(pLookup, 1, iDel); + if( SQLITE_ROW==sqlite3_step(pLookup) ){ + iOrigin = sqlite3_column_int64(pLookup, 1); + } + rc = sqlite3_reset(pLookup); + } + + if( rc==SQLITE_OK && iOrigin!=0 ){ + rc = sqlite3Fts5IndexContentlessDelete(p->pIndex, iOrigin, iDel); + } + + return rc; +} /* ** Insert a record into the %_docsize table. Specifically, do: @@ -218693,6 +264988,13 @@ static int fts5StorageInsertDocsize( rc = fts5StorageGetStmt(p, FTS5_STMT_REPLACE_DOCSIZE, &pReplace, 0); if( rc==SQLITE_OK ){ sqlite3_bind_int64(pReplace, 1, iRowid); + if( p->pConfig->bContentlessDelete ){ + i64 iOrigin = 0; + rc = sqlite3Fts5IndexGetOrigin(p->pIndex, &iOrigin); + sqlite3_bind_int64(pReplace, 3, iOrigin); + } + } + if( rc==SQLITE_OK ){ sqlite3_bind_blob(pReplace, 2, pBuf->p, pBuf->n, SQLITE_STATIC); sqlite3_step(pReplace); rc = sqlite3_reset(pReplace); @@ -218703,7 +265005,7 @@ static int fts5StorageInsertDocsize( } /* -** Load the contents of the "averages" record from disk into the +** Load the contents of the "averages" record from disk into the ** p->nTotalRow and p->aTotalSize[] variables. If successful, and if ** argument bCache is true, set the p->bTotalsValid flag to indicate ** that the contents of aTotalSize[] and nTotalRow are valid until @@ -218722,7 +265024,7 @@ static int fts5StorageLoadTotals(Fts5Storage *p, int bCache){ } /* -** Store the current contents of the p->nTotalRow and p->aTotalSize[] +** Store the current contents of the p->nTotalRow and p->aTotalSize[] ** variables in the "averages" record on disk. ** ** Return SQLITE_OK if successful, or an SQLite error code if an error @@ -218750,7 +265052,12 @@ static int fts5StorageSaveTotals(Fts5Storage *p){ /* ** Remove a row from the FTS table. */ -static int sqlite3Fts5StorageDelete(Fts5Storage *p, i64 iDel, sqlite3_value **apVal){ +static int sqlite3Fts5StorageDelete( + Fts5Storage *p, /* Storage object */ + i64 iDel, /* Rowid to delete from table */ + sqlite3_value **apVal, /* Optional - values to remove from index */ + int bSaveRow /* If true, set pSavedRow for deleted row */ +){ Fts5Config *pConfig = p->pConfig; int rc; sqlite3_stmt *pDel = 0; @@ -218760,7 +265067,21 @@ static int sqlite3Fts5StorageDelete(Fts5Storage *p, i64 iDel, sqlite3_value **ap /* Delete the index records */ if( rc==SQLITE_OK ){ - rc = fts5StorageDeleteFromIndex(p, iDel, apVal); + rc = sqlite3Fts5IndexBeginWrite(p->pIndex, 1, iDel); + } + + if( rc==SQLITE_OK ){ + if( p->pConfig->bContentlessDelete ){ + rc = fts5StorageContentlessDelete(p, iDel); + if( rc==SQLITE_OK + && bSaveRow + && p->pConfig->eContent==FTS5_CONTENT_UNINDEXED + ){ + rc = sqlite3Fts5StorageFindDeleteRow(p, iDel); + } + }else{ + rc = fts5StorageDeleteFromIndex(p, iDel, apVal, bSaveRow); + } } /* Delete the %_docsize record */ @@ -218774,7 +265095,9 @@ static int sqlite3Fts5StorageDelete(Fts5Storage *p, i64 iDel, sqlite3_value **ap } /* Delete the %_content record */ - if( pConfig->eContent==FTS5_CONTENT_NORMAL ){ + if( pConfig->eContent==FTS5_CONTENT_NORMAL + || pConfig->eContent==FTS5_CONTENT_UNINDEXED + ){ if( rc==SQLITE_OK ){ rc = fts5StorageGetStmt(p, FTS5_STMT_DELETE_CONTENT, &pDel, 0); } @@ -218795,17 +265118,24 @@ static int sqlite3Fts5StorageDeleteAll(Fts5Storage *p){ Fts5Config *pConfig = p->pConfig; int rc; + p->bTotalsValid = 0; + /* Delete the contents of the %_data and %_docsize tables. */ rc = fts5ExecPrintf(pConfig->db, 0, - "DELETE FROM %Q.'%q_data';" + "DELETE FROM %Q.'%q_data';" "DELETE FROM %Q.'%q_idx';", pConfig->zDb, pConfig->zName, pConfig->zDb, pConfig->zName ); if( rc==SQLITE_OK && pConfig->bColumnsize ){ rc = fts5ExecPrintf(pConfig->db, 0, - "DELETE FROM %Q.'%q_docsize';", - pConfig->zDb, pConfig->zName + "DELETE FROM %Q.'%q_docsize';", pConfig->zDb, pConfig->zName + ); + } + + if( rc==SQLITE_OK && pConfig->eContent==FTS5_CONTENT_UNINDEXED ){ + rc = fts5ExecPrintf(pConfig->db, 0, + "DELETE FROM %Q.'%q_content';", pConfig->zDb, pConfig->zName ); } @@ -218835,7 +265165,7 @@ static int sqlite3Fts5StorageRebuild(Fts5Storage *p){ } if( rc==SQLITE_OK ){ - rc = fts5StorageGetStmt(p, FTS5_STMT_SCAN, &pScan, 0); + rc = fts5StorageGetStmt(p, FTS5_STMT_SCAN, &pScan, pConfig->pzErrmsg); } while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pScan) ){ @@ -218846,13 +265176,36 @@ static int sqlite3Fts5StorageRebuild(Fts5Storage *p){ for(ctx.iCol=0; rc==SQLITE_OK && ctx.iColnCol; ctx.iCol++){ ctx.szCol = 0; if( pConfig->abUnindexed[ctx.iCol]==0 ){ - rc = sqlite3Fts5Tokenize(pConfig, - FTS5_TOKENIZE_DOCUMENT, - (const char*)sqlite3_column_text(pScan, ctx.iCol+1), - sqlite3_column_bytes(pScan, ctx.iCol+1), - (void*)&ctx, - fts5StorageInsertCallback - ); + int nText = 0; /* Size of pText in bytes */ + const char *pText = 0; /* Pointer to buffer containing text value */ + int nLoc = 0; /* Size of pLoc in bytes */ + const char *pLoc = 0; /* Pointer to buffer containing text value */ + + sqlite3_value *pVal = sqlite3_column_value(pScan, ctx.iCol+1); + if( pConfig->eContent==FTS5_CONTENT_EXTERNAL + && sqlite3Fts5IsLocaleValue(pConfig, pVal) + ){ + rc = sqlite3Fts5DecodeLocaleValue(pVal, &pText, &nText, &pLoc, &nLoc); + }else{ + pText = (const char*)sqlite3_value_text(pVal); + nText = sqlite3_value_bytes(pVal); + if( pConfig->bLocale ){ + int iCol = ctx.iCol + 1 + pConfig->nCol; + pLoc = (const char*)sqlite3_column_text(pScan, iCol); + nLoc = sqlite3_column_bytes(pScan, iCol); + } + } + + if( rc==SQLITE_OK ){ + sqlite3Fts5SetLocale(pConfig, pLoc, nLoc); + rc = sqlite3Fts5Tokenize(pConfig, + FTS5_TOKENIZE_DOCUMENT, + pText, nText, + (void*)&ctx, + fts5StorageInsertCallback + ); + sqlite3Fts5ClearLocale(pConfig); + } } sqlite3Fts5BufferAppendVarint(&rc, &buf, ctx.szCol); p->aTotalSize[ctx.iCol] += (i64)ctx.szCol; @@ -218917,15 +265270,18 @@ static int fts5StorageNewRowid(Fts5Storage *p, i64 *piRowid){ ** Insert a new row into the FTS content table. */ static int sqlite3Fts5StorageContentInsert( - Fts5Storage *p, - sqlite3_value **apVal, + Fts5Storage *p, + int bReplace, /* True to use REPLACE instead of INSERT */ + sqlite3_value **apVal, i64 *piRowid ){ Fts5Config *pConfig = p->pConfig; int rc = SQLITE_OK; /* Insert the new row into the %_content table. */ - if( pConfig->eContent!=FTS5_CONTENT_NORMAL ){ + if( pConfig->eContent!=FTS5_CONTENT_NORMAL + && pConfig->eContent!=FTS5_CONTENT_UNINDEXED + ){ if( sqlite3_value_type(apVal[1])==SQLITE_INTEGER ){ *piRowid = sqlite3_value_int64(apVal[1]); }else{ @@ -218934,9 +265290,52 @@ static int sqlite3Fts5StorageContentInsert( }else{ sqlite3_stmt *pInsert = 0; /* Statement to write %_content table */ int i; /* Counter variable */ - rc = fts5StorageGetStmt(p, FTS5_STMT_INSERT_CONTENT, &pInsert, 0); - for(i=1; rc==SQLITE_OK && i<=pConfig->nCol+1; i++){ - rc = sqlite3_bind_value(pInsert, i, apVal[i]); + + assert( FTS5_STMT_INSERT_CONTENT+1==FTS5_STMT_REPLACE_CONTENT ); + assert( bReplace==0 || bReplace==1 ); + rc = fts5StorageGetStmt(p, FTS5_STMT_INSERT_CONTENT+bReplace, &pInsert, 0); + if( pInsert ) sqlite3_clear_bindings(pInsert); + + /* Bind the rowid value */ + sqlite3_bind_value(pInsert, 1, apVal[1]); + + /* Loop through values for user-defined columns. i=2 is the leftmost + ** user-defined column. As is column 1 of pSavedRow. */ + for(i=2; rc==SQLITE_OK && i<=pConfig->nCol+1; i++){ + int bUnindexed = pConfig->abUnindexed[i-2]; + if( pConfig->eContent==FTS5_CONTENT_NORMAL || bUnindexed ){ + sqlite3_value *pVal = apVal[i]; + + if( sqlite3_value_nochange(pVal) && p->pSavedRow ){ + /* This is an UPDATE statement, and user-defined column (i-2) was not + ** modified. Retrieve the value from Fts5Storage.pSavedRow. */ + pVal = sqlite3_column_value(p->pSavedRow, i-1); + if( pConfig->bLocale && bUnindexed==0 ){ + sqlite3_bind_value(pInsert, pConfig->nCol + i, + sqlite3_column_value(p->pSavedRow, pConfig->nCol + i - 1) + ); + } + }else if( sqlite3Fts5IsLocaleValue(pConfig, pVal) ){ + const char *pText = 0; + const char *pLoc = 0; + int nText = 0; + int nLoc = 0; + assert( pConfig->bLocale ); + + rc = sqlite3Fts5DecodeLocaleValue(pVal, &pText, &nText, &pLoc, &nLoc); + if( rc==SQLITE_OK ){ + sqlite3_bind_text(pInsert, i, pText, nText, SQLITE_TRANSIENT); + if( bUnindexed==0 ){ + int iLoc = pConfig->nCol + i; + sqlite3_bind_text(pInsert, iLoc, pLoc, nLoc, SQLITE_TRANSIENT); + } + } + + continue; + } + + rc = sqlite3_bind_value(pInsert, i, pVal); + } } if( rc==SQLITE_OK ){ sqlite3_step(pInsert); @@ -218952,8 +265351,8 @@ static int sqlite3Fts5StorageContentInsert( ** Insert new entries into the FTS index and %_docsize table. */ static int sqlite3Fts5StorageIndexInsert( - Fts5Storage *p, - sqlite3_value **apVal, + Fts5Storage *p, + sqlite3_value **apVal, i64 iRowid ){ Fts5Config *pConfig = p->pConfig; @@ -218971,13 +265370,38 @@ static int sqlite3Fts5StorageIndexInsert( for(ctx.iCol=0; rc==SQLITE_OK && ctx.iColnCol; ctx.iCol++){ ctx.szCol = 0; if( pConfig->abUnindexed[ctx.iCol]==0 ){ - rc = sqlite3Fts5Tokenize(pConfig, - FTS5_TOKENIZE_DOCUMENT, - (const char*)sqlite3_value_text(apVal[ctx.iCol+2]), - sqlite3_value_bytes(apVal[ctx.iCol+2]), - (void*)&ctx, - fts5StorageInsertCallback - ); + int nText = 0; /* Size of pText in bytes */ + const char *pText = 0; /* Pointer to buffer containing text value */ + int nLoc = 0; /* Size of pText in bytes */ + const char *pLoc = 0; /* Pointer to buffer containing text value */ + + sqlite3_value *pVal = apVal[ctx.iCol+2]; + if( p->pSavedRow && sqlite3_value_nochange(pVal) ){ + pVal = sqlite3_column_value(p->pSavedRow, ctx.iCol+1); + if( pConfig->eContent==FTS5_CONTENT_NORMAL && pConfig->bLocale ){ + int iCol = ctx.iCol + 1 + pConfig->nCol; + pLoc = (const char*)sqlite3_column_text(p->pSavedRow, iCol); + nLoc = sqlite3_column_bytes(p->pSavedRow, iCol); + } + }else{ + pVal = apVal[ctx.iCol+2]; + } + + if( pConfig->bLocale && sqlite3Fts5IsLocaleValue(pConfig, pVal) ){ + rc = sqlite3Fts5DecodeLocaleValue(pVal, &pText, &nText, &pLoc, &nLoc); + }else{ + pText = (const char*)sqlite3_value_text(pVal); + nText = sqlite3_value_bytes(pVal); + } + + if( rc==SQLITE_OK ){ + sqlite3Fts5SetLocale(pConfig, pLoc, nLoc); + rc = sqlite3Fts5Tokenize(pConfig, + FTS5_TOKENIZE_DOCUMENT, pText, nText, (void*)&ctx, + fts5StorageInsertCallback + ); + sqlite3Fts5ClearLocale(pConfig); + } } sqlite3Fts5BufferAppendVarint(&rc, &buf, ctx.szCol); p->aTotalSize[ctx.iCol] += (i64)ctx.szCol; @@ -218998,7 +265422,7 @@ static int fts5StorageCount(Fts5Storage *p, const char *zSuffix, i64 *pnRow){ char *zSql; int rc; - zSql = sqlite3_mprintf("SELECT count(*) FROM %Q.'%q_%s'", + zSql = sqlite3_mprintf("SELECT count(*) FROM %Q.'%q_%s'", pConfig->zDb, pConfig->zName, zSuffix ); if( zSql==0 ){ @@ -219105,13 +265529,14 @@ static int fts5StorageIntegrityCallback( ** some other SQLite error code if an error occurs while attempting to ** determine this. */ -static int sqlite3Fts5StorageIntegrity(Fts5Storage *p){ +static int sqlite3Fts5StorageIntegrity(Fts5Storage *p, int iArg){ Fts5Config *pConfig = p->pConfig; - int rc; /* Return code */ + int rc = SQLITE_OK; /* Return code */ int *aColSize; /* Array of size pConfig->nCol */ i64 *aTotalSize; /* Array of size pConfig->nCol */ Fts5IntegrityCtx ctx; sqlite3_stmt *pScan; + int bUseCksum; memset(&ctx, 0, sizeof(Fts5IntegrityCtx)); ctx.pConfig = p->pConfig; @@ -219120,82 +265545,120 @@ static int sqlite3Fts5StorageIntegrity(Fts5Storage *p){ aColSize = (int*)&aTotalSize[pConfig->nCol]; memset(aTotalSize, 0, sizeof(i64) * pConfig->nCol); - /* Generate the expected index checksum based on the contents of the - ** %_content table. This block stores the checksum in ctx.cksum. */ - rc = fts5StorageGetStmt(p, FTS5_STMT_SCAN, &pScan, 0); - if( rc==SQLITE_OK ){ - int rc2; - while( SQLITE_ROW==sqlite3_step(pScan) ){ - int i; - ctx.iRowid = sqlite3_column_int64(pScan, 0); - ctx.szCol = 0; - if( pConfig->bColumnsize ){ - rc = sqlite3Fts5StorageDocsize(p, ctx.iRowid, aColSize); - } - if( rc==SQLITE_OK && pConfig->eDetail==FTS5_DETAIL_NONE ){ - rc = sqlite3Fts5TermsetNew(&ctx.pTermset); - } - for(i=0; rc==SQLITE_OK && inCol; i++){ - if( pConfig->abUnindexed[i] ) continue; - ctx.iCol = i; + bUseCksum = (pConfig->eContent==FTS5_CONTENT_NORMAL + || (pConfig->eContent==FTS5_CONTENT_EXTERNAL && iArg) + ); + if( bUseCksum ){ + /* Generate the expected index checksum based on the contents of the + ** %_content table. This block stores the checksum in ctx.cksum. */ + rc = fts5StorageGetStmt(p, FTS5_STMT_SCAN, &pScan, 0); + if( rc==SQLITE_OK ){ + int rc2; + while( SQLITE_ROW==sqlite3_step(pScan) ){ + int i; + ctx.iRowid = sqlite3_column_int64(pScan, 0); ctx.szCol = 0; - if( pConfig->eDetail==FTS5_DETAIL_COLUMNS ){ - rc = sqlite3Fts5TermsetNew(&ctx.pTermset); - } - if( rc==SQLITE_OK ){ - rc = sqlite3Fts5Tokenize(pConfig, - FTS5_TOKENIZE_DOCUMENT, - (const char*)sqlite3_column_text(pScan, i+1), - sqlite3_column_bytes(pScan, i+1), - (void*)&ctx, - fts5StorageIntegrityCallback - ); + if( pConfig->bColumnsize ){ + rc = sqlite3Fts5StorageDocsize(p, ctx.iRowid, aColSize); } - if( rc==SQLITE_OK && pConfig->bColumnsize && ctx.szCol!=aColSize[i] ){ - rc = FTS5_CORRUPT; + if( rc==SQLITE_OK && pConfig->eDetail==FTS5_DETAIL_NONE ){ + rc = sqlite3Fts5TermsetNew(&ctx.pTermset); } - aTotalSize[i] += ctx.szCol; - if( pConfig->eDetail==FTS5_DETAIL_COLUMNS ){ - sqlite3Fts5TermsetFree(ctx.pTermset); - ctx.pTermset = 0; + for(i=0; rc==SQLITE_OK && inCol; i++){ + if( pConfig->abUnindexed[i]==0 ){ + const char *pText = 0; + int nText = 0; + const char *pLoc = 0; + int nLoc = 0; + sqlite3_value *pVal = sqlite3_column_value(pScan, i+1); + + if( pConfig->eContent==FTS5_CONTENT_EXTERNAL + && sqlite3Fts5IsLocaleValue(pConfig, pVal) + ){ + rc = sqlite3Fts5DecodeLocaleValue( + pVal, &pText, &nText, &pLoc, &nLoc + ); + }else{ + if( pConfig->eContent==FTS5_CONTENT_NORMAL && pConfig->bLocale ){ + int iCol = i + 1 + pConfig->nCol; + pLoc = (const char*)sqlite3_column_text(pScan, iCol); + nLoc = sqlite3_column_bytes(pScan, iCol); + } + pText = (const char*)sqlite3_value_text(pVal); + nText = sqlite3_value_bytes(pVal); + } + + ctx.iCol = i; + ctx.szCol = 0; + + if( rc==SQLITE_OK && pConfig->eDetail==FTS5_DETAIL_COLUMNS ){ + rc = sqlite3Fts5TermsetNew(&ctx.pTermset); + } + + if( rc==SQLITE_OK ){ + sqlite3Fts5SetLocale(pConfig, pLoc, nLoc); + rc = sqlite3Fts5Tokenize(pConfig, + FTS5_TOKENIZE_DOCUMENT, + pText, nText, + (void*)&ctx, + fts5StorageIntegrityCallback + ); + sqlite3Fts5ClearLocale(pConfig); + } + + /* If this is not a columnsize=0 database, check that the number + ** of tokens in the value matches the aColSize[] value read from + ** the %_docsize table. */ + if( rc==SQLITE_OK + && pConfig->bColumnsize + && ctx.szCol!=aColSize[i] + ){ + rc = FTS5_CORRUPT; + } + aTotalSize[i] += ctx.szCol; + if( pConfig->eDetail==FTS5_DETAIL_COLUMNS ){ + sqlite3Fts5TermsetFree(ctx.pTermset); + ctx.pTermset = 0; + } + } } - } - sqlite3Fts5TermsetFree(ctx.pTermset); - ctx.pTermset = 0; + sqlite3Fts5TermsetFree(ctx.pTermset); + ctx.pTermset = 0; - if( rc!=SQLITE_OK ) break; + if( rc!=SQLITE_OK ) break; + } + rc2 = sqlite3_reset(pScan); + if( rc==SQLITE_OK ) rc = rc2; } - rc2 = sqlite3_reset(pScan); - if( rc==SQLITE_OK ) rc = rc2; - } - /* Test that the "totals" (sometimes called "averages") record looks Ok */ - if( rc==SQLITE_OK ){ - int i; - rc = fts5StorageLoadTotals(p, 0); - for(i=0; rc==SQLITE_OK && inCol; i++){ - if( p->aTotalSize[i]!=aTotalSize[i] ) rc = FTS5_CORRUPT; + /* Test that the "totals" (sometimes called "averages") record looks Ok */ + if( rc==SQLITE_OK ){ + int i; + rc = fts5StorageLoadTotals(p, 0); + for(i=0; rc==SQLITE_OK && inCol; i++){ + if( p->aTotalSize[i]!=aTotalSize[i] ) rc = FTS5_CORRUPT; + } } - } - /* Check that the %_docsize and %_content tables contain the expected - ** number of rows. */ - if( rc==SQLITE_OK && pConfig->eContent==FTS5_CONTENT_NORMAL ){ - i64 nRow = 0; - rc = fts5StorageCount(p, "content", &nRow); - if( rc==SQLITE_OK && nRow!=p->nTotalRow ) rc = FTS5_CORRUPT; - } - if( rc==SQLITE_OK && pConfig->bColumnsize ){ - i64 nRow = 0; - rc = fts5StorageCount(p, "docsize", &nRow); - if( rc==SQLITE_OK && nRow!=p->nTotalRow ) rc = FTS5_CORRUPT; + /* Check that the %_docsize and %_content tables contain the expected + ** number of rows. */ + if( rc==SQLITE_OK && pConfig->eContent==FTS5_CONTENT_NORMAL ){ + i64 nRow = 0; + rc = fts5StorageCount(p, "content", &nRow); + if( rc==SQLITE_OK && nRow!=p->nTotalRow ) rc = FTS5_CORRUPT; + } + if( rc==SQLITE_OK && pConfig->bColumnsize ){ + i64 nRow = 0; + rc = fts5StorageCount(p, "docsize", &nRow); + if( rc==SQLITE_OK && nRow!=p->nTotalRow ) rc = FTS5_CORRUPT; + } } /* Pass the expected checksum down to the FTS index module. It will ** verify, amongst other things, that it matches the checksum generated by ** inspecting the index itself. */ if( rc==SQLITE_OK ){ - rc = sqlite3Fts5IndexIntegrityCheck(p->pIndex, ctx.cksum); + rc = sqlite3Fts5IndexIntegrityCheck(p->pIndex, ctx.cksum, bUseCksum); } sqlite3_free(aTotalSize); @@ -219207,13 +265670,13 @@ static int sqlite3Fts5StorageIntegrity(Fts5Storage *p){ ** %_content table. */ static int sqlite3Fts5StorageStmt( - Fts5Storage *p, - int eStmt, - sqlite3_stmt **pp, + Fts5Storage *p, + int eStmt, + sqlite3_stmt **pp, char **pzErrMsg ){ int rc; - assert( eStmt==FTS5_STMT_SCAN_ASC + assert( eStmt==FTS5_STMT_SCAN_ASC || eStmt==FTS5_STMT_SCAN_DESC || eStmt==FTS5_STMT_LOOKUP ); @@ -219231,8 +265694,8 @@ static int sqlite3Fts5StorageStmt( ** must match that passed to the sqlite3Fts5StorageStmt() call. */ static void sqlite3Fts5StorageStmtRelease( - Fts5Storage *p, - int eStmt, + Fts5Storage *p, + int eStmt, sqlite3_stmt *pStmt ){ assert( eStmt==FTS5_STMT_SCAN_ASC @@ -219275,8 +265738,9 @@ static int sqlite3Fts5StorageDocsize(Fts5Storage *p, i64 iRowid, int *aCol){ assert( p->pConfig->bColumnsize ); rc = fts5StorageGetStmt(p, FTS5_STMT_LOOKUP_DOCSIZE, &pLookup, 0); - if( rc==SQLITE_OK ){ + if( pLookup ){ int bCorrupt = 1; + assert( rc==SQLITE_OK ); sqlite3_bind_int64(pLookup, 1, iRowid); if( SQLITE_ROW==sqlite3_step(pLookup) ){ const u8 *aBlob = sqlite3_column_blob(pLookup, 0); @@ -219289,6 +265753,8 @@ static int sqlite3Fts5StorageDocsize(Fts5Storage *p, i64 iRowid, int *aCol){ if( bCorrupt && rc==SQLITE_OK ){ rc = FTS5_CORRUPT; } + }else{ + assert( rc!=SQLITE_OK ); } return rc; @@ -219315,7 +265781,7 @@ static int sqlite3Fts5StorageSize(Fts5Storage *p, int iCol, i64 *pnToken){ static int sqlite3Fts5StorageRowCount(Fts5Storage *p, i64 *pnRow){ int rc = fts5StorageLoadTotals(p, 0); if( rc==SQLITE_OK ){ - /* nTotalRow being zero does not necessarily indicate a corrupt + /* nTotalRow being zero does not necessarily indicate a corrupt ** database - it might be that the FTS5 table really does contain zero ** rows. However this function is only called from the xRowCount() API, ** and there is no way for that API to be invoked if the table contains @@ -219334,7 +265800,9 @@ static int sqlite3Fts5StorageSync(Fts5Storage *p){ i64 iLastRowid = sqlite3_last_insert_rowid(p->pConfig->db); if( p->bTotalsValid ){ rc = fts5StorageSaveTotals(p); - p->bTotalsValid = 0; + if( rc==SQLITE_OK ){ + p->bTotalsValid = 0; + } } if( rc==SQLITE_OK ){ rc = sqlite3Fts5IndexSync(p->pIndex); @@ -219349,7 +265817,7 @@ static int sqlite3Fts5StorageRollback(Fts5Storage *p){ } static int sqlite3Fts5StorageConfigValue( - Fts5Storage *p, + Fts5Storage *p, const char *z, sqlite3_value *pVal, int iVal @@ -219399,7 +265867,7 @@ static int sqlite3Fts5StorageConfigValue( /* ** For tokenizers with no "unicode" modifier, the set of token characters -** is the same as the set of ASCII range alphanumeric characters. +** is the same as the set of ASCII range alphanumeric characters. */ static unsigned char aAsciiTokenChar[128] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x00..0x0F */ @@ -219418,8 +265886,8 @@ struct AsciiTokenizer { }; static void fts5AsciiAddExceptions( - AsciiTokenizer *p, - const char *zArg, + AsciiTokenizer *p, + const char *zArg, int bTokenChars ){ int i; @@ -219441,7 +265909,7 @@ static void fts5AsciiDelete(Fts5Tokenizer *p){ ** Create an "ascii" tokenizer. */ static int fts5AsciiCreate( - void *pUnused, + void *pUnused, const char **azArg, int nArg, Fts5Tokenizer **ppOut ){ @@ -219451,7 +265919,7 @@ static int fts5AsciiCreate( if( nArg%2 ){ rc = SQLITE_ERROR; }else{ - p = sqlite3_malloc(sizeof(AsciiTokenizer)); + p = sqlite3_malloc64(sizeof(AsciiTokenizer)); if( p==0 ){ rc = SQLITE_NOMEM; }else{ @@ -219544,7 +266012,7 @@ static int fts5AsciiTokenize( rc = xToken(pCtx, 0, pFold, nByte, is, ie); is = ie+1; } - + if( pFold!=aFold ) sqlite3_free(pFold); if( rc==SQLITE_DONE ) rc = SQLITE_OK; return rc; @@ -219577,7 +266045,7 @@ static const unsigned char sqlite3Utf8Trans1[] = { c = *(zIn++); \ if( c>=0xc0 ){ \ c = sqlite3Utf8Trans1[c-0xc0]; \ - while( zIn!=zTerm && (*zIn & 0xc0)==0x80 ){ \ + while( zIn=0xc0 ){ \ + while( (((unsigned char)*zIn) & 0xc0)==0x80 ){ zIn++; } \ + } \ +} + typedef struct Unicode61Tokenizer Unicode61Tokenizer; struct Unicode61Tokenizer { unsigned char aTokenChar[128]; /* ASCII range token characters */ @@ -219649,7 +266123,7 @@ static int fts5UnicodeAddExceptions( p->aTokenChar[iCode] = (unsigned char)bTokenChars; }else{ bToken = p->aCategory[sqlite3Fts5UnicodeCategory(iCode)]; - assert( (bToken==0 || bToken==1) ); + assert( (bToken==0 || bToken==1) ); assert( (bTokenChars==0 || bTokenChars==1) ); if( bToken!=bTokenChars && sqlite3Fts5UnicodeIsdiacritic(iCode)==0 ){ int i; @@ -219728,19 +266202,19 @@ static int unicodeSetCategories(Unicode61Tokenizer *p, const char *zCat){ ** Create a "unicode61" tokenizer. */ static int fts5UnicodeCreate( - void *pUnused, + void *pUnused, const char **azArg, int nArg, Fts5Tokenizer **ppOut ){ int rc = SQLITE_OK; /* Return code */ - Unicode61Tokenizer *p = 0; /* New tokenizer object */ + Unicode61Tokenizer *p = 0; /* New tokenizer object */ UNUSED_PARAM(pUnused); if( nArg%2 ){ rc = SQLITE_ERROR; }else{ - p = (Unicode61Tokenizer*)sqlite3_malloc(sizeof(Unicode61Tokenizer)); + p = (Unicode61Tokenizer*)sqlite3_malloc64(sizeof(Unicode61Tokenizer)); if( p ){ const char *zCat = "L* N* Co"; int i; @@ -219759,7 +266233,6 @@ static int fts5UnicodeCreate( zCat = azArg[i+1]; } } - if( rc==SQLITE_OK ){ rc = unicodeSetCategories(p, zCat); } @@ -219789,7 +266262,6 @@ static int fts5UnicodeCreate( rc = SQLITE_ERROR; } } - }else{ rc = SQLITE_NOMEM; } @@ -219804,7 +266276,7 @@ static int fts5UnicodeCreate( /* ** Return true if, for the purposes of tokenizing with the tokenizer -** passed as the first argument, codepoint iCode is considered a token +** passed as the first argument, codepoint iCode is considered a token ** character (not a separator). */ static int fts5UnicodeIsAlnum(Unicode61Tokenizer *p, int iCode){ @@ -219896,7 +266368,7 @@ static int fts5UnicodeTokenize( } }else if( a[*zCsr]==0 ){ /* An ascii-range separator character. End of token. */ - break; + break; }else{ ascii_tokenchar: if( *zCsr>='A' && *zCsr<='Z' ){ @@ -219910,9 +266382,9 @@ static int fts5UnicodeTokenize( } /* Invoke the token callback */ - rc = xToken(pCtx, 0, aFold, zOut-aFold, is, ie); + rc = xToken(pCtx, 0, aFold, zOut-aFold, is, ie); } - + tokenize_done: if( rc==SQLITE_DONE ) rc = SQLITE_OK; return rc; @@ -219928,7 +266400,7 @@ static int fts5UnicodeTokenize( typedef struct PorterTokenizer PorterTokenizer; struct PorterTokenizer { - fts5_tokenizer tokenizer; /* Parent tokenizer module */ + fts5_tokenizer_v2 tokenizer_v2; /* Parent tokenizer module */ Fts5Tokenizer *pTokenizer; /* Parent tokenizer instance */ char aBuf[FTS5_PORTER_MAX_TOKEN + 64]; }; @@ -219940,7 +266412,7 @@ static void fts5PorterDelete(Fts5Tokenizer *pTok){ if( pTok ){ PorterTokenizer *p = (PorterTokenizer*)pTok; if( p->pTokenizer ){ - p->tokenizer.xDelete(p->pTokenizer); + p->tokenizer_v2.xDelete(p->pTokenizer); } sqlite3_free(p); } @@ -219950,7 +266422,7 @@ static void fts5PorterDelete(Fts5Tokenizer *pTok){ ** Create a "porter" tokenizer. */ static int fts5PorterCreate( - void *pCtx, + void *pCtx, const char **azArg, int nArg, Fts5Tokenizer **ppOut ){ @@ -219959,22 +266431,30 @@ static int fts5PorterCreate( PorterTokenizer *pRet; void *pUserdata = 0; const char *zBase = "unicode61"; + fts5_tokenizer_v2 *pV2 = 0; - if( nArg>0 ){ - zBase = azArg[0]; + while( nArg>0 ){ + if( sqlite3_stricmp(azArg[0],"porter")==0 ){ + nArg--; + azArg++; + }else{ + zBase = azArg[0]; + break; + } } - pRet = (PorterTokenizer*)sqlite3_malloc(sizeof(PorterTokenizer)); + pRet = (PorterTokenizer*)sqlite3_malloc64(sizeof(PorterTokenizer)); if( pRet ){ memset(pRet, 0, sizeof(PorterTokenizer)); - rc = pApi->xFindTokenizer(pApi, zBase, &pUserdata, &pRet->tokenizer); + rc = pApi->xFindTokenizer_v2(pApi, zBase, &pUserdata, &pV2); }else{ rc = SQLITE_NOMEM; } if( rc==SQLITE_OK ){ int nArg2 = (nArg>0 ? nArg-1 : 0); - const char **azArg2 = (nArg2 ? &azArg[1] : 0); - rc = pRet->tokenizer.xCreate(pUserdata, azArg2, nArg2, &pRet->pTokenizer); + const char **az2 = (nArg2 ? &azArg[1] : 0); + memcpy(&pRet->tokenizer_v2, pV2, sizeof(fts5_tokenizer_v2)); + rc = pRet->tokenizer_v2.xCreate(pUserdata, az2, nArg2, &pRet->pTokenizer); } if( rc!=SQLITE_OK ){ @@ -220094,7 +266574,7 @@ static int fts5Porter_Ostar(char *zStem, int nStem){ /* porter rule condition: (m > 1 and (*S or *T)) */ static int fts5Porter_MGt1_and_S_or_T(char *zStem, int nStem){ assert( nStem>0 ); - return (zStem[nStem-1]=='s' || zStem[nStem-1]=='t') + return (zStem[nStem-1]=='s' || zStem[nStem-1]=='t') && fts5Porter_MGt1(zStem, nStem); } @@ -220119,16 +266599,16 @@ static int fts5PorterStep4(char *aBuf, int *pnBuf){ int ret = 0; int nBuf = *pnBuf; switch( aBuf[nBuf-2] ){ - - case 'a': + + case 'a': if( nBuf>2 && 0==memcmp("al", &aBuf[nBuf-2], 2) ){ if( fts5Porter_MGt1(aBuf, nBuf-2) ){ *pnBuf = nBuf - 2; } } break; - - case 'c': + + case 'c': if( nBuf>4 && 0==memcmp("ance", &aBuf[nBuf-4], 4) ){ if( fts5Porter_MGt1(aBuf, nBuf-4) ){ *pnBuf = nBuf - 4; @@ -220139,24 +266619,24 @@ static int fts5PorterStep4(char *aBuf, int *pnBuf){ } } break; - - case 'e': + + case 'e': if( nBuf>2 && 0==memcmp("er", &aBuf[nBuf-2], 2) ){ if( fts5Porter_MGt1(aBuf, nBuf-2) ){ *pnBuf = nBuf - 2; } } break; - - case 'i': + + case 'i': if( nBuf>2 && 0==memcmp("ic", &aBuf[nBuf-2], 2) ){ if( fts5Porter_MGt1(aBuf, nBuf-2) ){ *pnBuf = nBuf - 2; } } break; - - case 'l': + + case 'l': if( nBuf>4 && 0==memcmp("able", &aBuf[nBuf-4], 4) ){ if( fts5Porter_MGt1(aBuf, nBuf-4) ){ *pnBuf = nBuf - 4; @@ -220167,8 +266647,8 @@ static int fts5PorterStep4(char *aBuf, int *pnBuf){ } } break; - - case 'n': + + case 'n': if( nBuf>3 && 0==memcmp("ant", &aBuf[nBuf-3], 3) ){ if( fts5Porter_MGt1(aBuf, nBuf-3) ){ *pnBuf = nBuf - 3; @@ -220187,8 +266667,8 @@ static int fts5PorterStep4(char *aBuf, int *pnBuf){ } } break; - - case 'o': + + case 'o': if( nBuf>3 && 0==memcmp("ion", &aBuf[nBuf-3], 3) ){ if( fts5Porter_MGt1_and_S_or_T(aBuf, nBuf-3) ){ *pnBuf = nBuf - 3; @@ -220199,16 +266679,16 @@ static int fts5PorterStep4(char *aBuf, int *pnBuf){ } } break; - - case 's': + + case 's': if( nBuf>3 && 0==memcmp("ism", &aBuf[nBuf-3], 3) ){ if( fts5Porter_MGt1(aBuf, nBuf-3) ){ *pnBuf = nBuf - 3; } } break; - - case 't': + + case 't': if( nBuf>3 && 0==memcmp("ate", &aBuf[nBuf-3], 3) ){ if( fts5Porter_MGt1(aBuf, nBuf-3) ){ *pnBuf = nBuf - 3; @@ -220219,76 +266699,76 @@ static int fts5PorterStep4(char *aBuf, int *pnBuf){ } } break; - - case 'u': + + case 'u': if( nBuf>3 && 0==memcmp("ous", &aBuf[nBuf-3], 3) ){ if( fts5Porter_MGt1(aBuf, nBuf-3) ){ *pnBuf = nBuf - 3; } } break; - - case 'v': + + case 'v': if( nBuf>3 && 0==memcmp("ive", &aBuf[nBuf-3], 3) ){ if( fts5Porter_MGt1(aBuf, nBuf-3) ){ *pnBuf = nBuf - 3; } } break; - - case 'z': + + case 'z': if( nBuf>3 && 0==memcmp("ize", &aBuf[nBuf-3], 3) ){ if( fts5Porter_MGt1(aBuf, nBuf-3) ){ *pnBuf = nBuf - 3; } } break; - + } return ret; } - + static int fts5PorterStep1B2(char *aBuf, int *pnBuf){ int ret = 0; int nBuf = *pnBuf; switch( aBuf[nBuf-2] ){ - - case 'a': + + case 'a': if( nBuf>2 && 0==memcmp("at", &aBuf[nBuf-2], 2) ){ memcpy(&aBuf[nBuf-2], "ate", 3); *pnBuf = nBuf - 2 + 3; ret = 1; } break; - - case 'b': + + case 'b': if( nBuf>2 && 0==memcmp("bl", &aBuf[nBuf-2], 2) ){ memcpy(&aBuf[nBuf-2], "ble", 3); *pnBuf = nBuf - 2 + 3; ret = 1; } break; - - case 'i': + + case 'i': if( nBuf>2 && 0==memcmp("iz", &aBuf[nBuf-2], 2) ){ memcpy(&aBuf[nBuf-2], "ize", 3); *pnBuf = nBuf - 2 + 3; ret = 1; } break; - + } return ret; } - + static int fts5PorterStep2(char *aBuf, int *pnBuf){ int ret = 0; int nBuf = *pnBuf; switch( aBuf[nBuf-2] ){ - - case 'a': + + case 'a': if( nBuf>7 && 0==memcmp("ational", &aBuf[nBuf-7], 7) ){ if( fts5Porter_MGt0(aBuf, nBuf-7) ){ memcpy(&aBuf[nBuf-7], "ate", 3); @@ -220301,8 +266781,8 @@ static int fts5PorterStep2(char *aBuf, int *pnBuf){ } } break; - - case 'c': + + case 'c': if( nBuf>4 && 0==memcmp("enci", &aBuf[nBuf-4], 4) ){ if( fts5Porter_MGt0(aBuf, nBuf-4) ){ memcpy(&aBuf[nBuf-4], "ence", 4); @@ -220315,8 +266795,8 @@ static int fts5PorterStep2(char *aBuf, int *pnBuf){ } } break; - - case 'e': + + case 'e': if( nBuf>4 && 0==memcmp("izer", &aBuf[nBuf-4], 4) ){ if( fts5Porter_MGt0(aBuf, nBuf-4) ){ memcpy(&aBuf[nBuf-4], "ize", 3); @@ -220324,8 +266804,8 @@ static int fts5PorterStep2(char *aBuf, int *pnBuf){ } } break; - - case 'g': + + case 'g': if( nBuf>4 && 0==memcmp("logi", &aBuf[nBuf-4], 4) ){ if( fts5Porter_MGt0(aBuf, nBuf-4) ){ memcpy(&aBuf[nBuf-4], "log", 3); @@ -220333,8 +266813,8 @@ static int fts5PorterStep2(char *aBuf, int *pnBuf){ } } break; - - case 'l': + + case 'l': if( nBuf>3 && 0==memcmp("bli", &aBuf[nBuf-3], 3) ){ if( fts5Porter_MGt0(aBuf, nBuf-3) ){ memcpy(&aBuf[nBuf-3], "ble", 3); @@ -220362,8 +266842,8 @@ static int fts5PorterStep2(char *aBuf, int *pnBuf){ } } break; - - case 'o': + + case 'o': if( nBuf>7 && 0==memcmp("ization", &aBuf[nBuf-7], 7) ){ if( fts5Porter_MGt0(aBuf, nBuf-7) ){ memcpy(&aBuf[nBuf-7], "ize", 3); @@ -220381,8 +266861,8 @@ static int fts5PorterStep2(char *aBuf, int *pnBuf){ } } break; - - case 's': + + case 's': if( nBuf>5 && 0==memcmp("alism", &aBuf[nBuf-5], 5) ){ if( fts5Porter_MGt0(aBuf, nBuf-5) ){ memcpy(&aBuf[nBuf-5], "al", 2); @@ -220405,8 +266885,8 @@ static int fts5PorterStep2(char *aBuf, int *pnBuf){ } } break; - - case 't': + + case 't': if( nBuf>5 && 0==memcmp("aliti", &aBuf[nBuf-5], 5) ){ if( fts5Porter_MGt0(aBuf, nBuf-5) ){ memcpy(&aBuf[nBuf-5], "al", 2); @@ -220424,18 +266904,18 @@ static int fts5PorterStep2(char *aBuf, int *pnBuf){ } } break; - + } return ret; } - + static int fts5PorterStep3(char *aBuf, int *pnBuf){ int ret = 0; int nBuf = *pnBuf; switch( aBuf[nBuf-2] ){ - - case 'a': + + case 'a': if( nBuf>4 && 0==memcmp("ical", &aBuf[nBuf-4], 4) ){ if( fts5Porter_MGt0(aBuf, nBuf-4) ){ memcpy(&aBuf[nBuf-4], "ic", 2); @@ -220443,16 +266923,16 @@ static int fts5PorterStep3(char *aBuf, int *pnBuf){ } } break; - - case 's': + + case 's': if( nBuf>4 && 0==memcmp("ness", &aBuf[nBuf-4], 4) ){ if( fts5Porter_MGt0(aBuf, nBuf-4) ){ *pnBuf = nBuf - 4; } } break; - - case 't': + + case 't': if( nBuf>5 && 0==memcmp("icate", &aBuf[nBuf-5], 5) ){ if( fts5Porter_MGt0(aBuf, nBuf-5) ){ memcpy(&aBuf[nBuf-5], "ic", 2); @@ -220465,24 +266945,24 @@ static int fts5PorterStep3(char *aBuf, int *pnBuf){ } } break; - - case 'u': + + case 'u': if( nBuf>3 && 0==memcmp("ful", &aBuf[nBuf-3], 3) ){ if( fts5Porter_MGt0(aBuf, nBuf-3) ){ *pnBuf = nBuf - 3; } } break; - - case 'v': + + case 'v': if( nBuf>5 && 0==memcmp("ative", &aBuf[nBuf-5], 5) ){ if( fts5Porter_MGt0(aBuf, nBuf-5) ){ *pnBuf = nBuf - 5; } } break; - - case 'z': + + case 'z': if( nBuf>5 && 0==memcmp("alize", &aBuf[nBuf-5], 5) ){ if( fts5Porter_MGt0(aBuf, nBuf-5) ){ memcpy(&aBuf[nBuf-5], "al", 2); @@ -220490,18 +266970,18 @@ static int fts5PorterStep3(char *aBuf, int *pnBuf){ } } break; - + } return ret; } - + static int fts5PorterStep1B(char *aBuf, int *pnBuf){ int ret = 0; int nBuf = *pnBuf; switch( aBuf[nBuf-2] ){ - - case 'e': + + case 'e': if( nBuf>3 && 0==memcmp("eed", &aBuf[nBuf-3], 3) ){ if( fts5Porter_MGt0(aBuf, nBuf-3) ){ memcpy(&aBuf[nBuf-3], "ee", 2); @@ -220514,8 +266994,8 @@ static int fts5PorterStep1B(char *aBuf, int *pnBuf){ } } break; - - case 'n': + + case 'n': if( nBuf>3 && 0==memcmp("ing", &aBuf[nBuf-3], 3) ){ if( fts5Porter_Vowel(aBuf, nBuf-3) ){ *pnBuf = nBuf - 3; @@ -220523,12 +267003,12 @@ static int fts5PorterStep1B(char *aBuf, int *pnBuf){ } } break; - + } return ret; } - -/* + +/* ** GENERATED CODE ENDS HERE (mkportersteps.tcl) *************************************************************************** **************************************************************************/ @@ -220537,7 +267017,7 @@ static void fts5PorterStep1A(char *aBuf, int *pnBuf){ int nBuf = *pnBuf; if( aBuf[nBuf-1]=='s' ){ if( aBuf[nBuf-2]=='e' ){ - if( (nBuf>4 && aBuf[nBuf-4]=='s' && aBuf[nBuf-3]=='s') + if( (nBuf>4 && aBuf[nBuf-4]=='s' && aBuf[nBuf-3]=='s') || (nBuf>3 && aBuf[nBuf-3]=='i' ) ){ *pnBuf = nBuf-2; @@ -220552,11 +267032,11 @@ static void fts5PorterStep1A(char *aBuf, int *pnBuf){ } static int fts5PorterCb( - void *pCtx, + void *pCtx, int tflags, - const char *pToken, - int nToken, - int iStart, + const char *pToken, + int nToken, + int iStart, int iEnd ){ PorterContext *p = (PorterContext*)pCtx; @@ -220574,8 +267054,8 @@ static int fts5PorterCb( if( fts5PorterStep1B(aBuf, &nBuf) ){ if( fts5PorterStep1B2(aBuf, &nBuf)==0 ){ char c = aBuf[nBuf-1]; - if( fts5PorterIsVowel(c, 0)==0 - && c!='l' && c!='s' && c!='z' && c==aBuf[nBuf-2] + if( fts5PorterIsVowel(c, 0)==0 + && c!='l' && c!='s' && c!='z' && c==aBuf[nBuf-2] ){ nBuf--; }else if( fts5Porter_MEq1(aBuf, nBuf) && fts5Porter_Ostar(aBuf, nBuf) ){ @@ -220597,7 +267077,7 @@ static int fts5PorterCb( /* Step 5a. */ assert( nBuf>0 ); if( aBuf[nBuf-1]=='e' ){ - if( fts5Porter_MGt1(aBuf, nBuf-1) + if( fts5Porter_MGt1(aBuf, nBuf-1) || (fts5Porter_MEq1(aBuf, nBuf-1) && !fts5Porter_Ostar(aBuf, nBuf-1)) ){ nBuf--; @@ -220605,8 +267085,8 @@ static int fts5PorterCb( } /* Step 5b. */ - if( nBuf>1 && aBuf[nBuf-1]=='l' - && aBuf[nBuf-2]=='l' && fts5Porter_MGt1(aBuf, nBuf-1) + if( nBuf>1 && aBuf[nBuf-1]=='l' + && aBuf[nBuf-2]=='l' && fts5Porter_MGt1(aBuf, nBuf-1) ){ nBuf--; } @@ -220625,6 +267105,7 @@ static int fts5PorterTokenize( void *pCtx, int flags, const char *pText, int nText, + const char *pLoc, int nLoc, int (*xToken)(void*, int, const char*, int nToken, int iStart, int iEnd) ){ PorterTokenizer *p = (PorterTokenizer*)pTokenizer; @@ -220632,11 +267113,194 @@ static int fts5PorterTokenize( sCtx.xToken = xToken; sCtx.pCtx = pCtx; sCtx.aBuf = p->aBuf; - return p->tokenizer.xTokenize( - p->pTokenizer, (void*)&sCtx, flags, pText, nText, fts5PorterCb + return p->tokenizer_v2.xTokenize( + p->pTokenizer, (void*)&sCtx, flags, pText, nText, pLoc, nLoc, fts5PorterCb ); } +/************************************************************************** +** Start of trigram implementation. +*/ +typedef struct TrigramTokenizer TrigramTokenizer; +struct TrigramTokenizer { + int bFold; /* True to fold to lower-case */ + int iFoldParam; /* Parameter to pass to Fts5UnicodeFold() */ +}; + +/* +** Free a trigram tokenizer. +*/ +static void fts5TriDelete(Fts5Tokenizer *p){ + sqlite3_free(p); +} + +/* +** Allocate a trigram tokenizer. +*/ +static int fts5TriCreate( + void *pUnused, + const char **azArg, + int nArg, + Fts5Tokenizer **ppOut +){ + int rc = SQLITE_OK; + TrigramTokenizer *pNew = 0; + UNUSED_PARAM(pUnused); + if( nArg%2 ){ + rc = SQLITE_ERROR; + }else{ + int i; + pNew = (TrigramTokenizer*)sqlite3_malloc64(sizeof(*pNew)); + if( pNew==0 ){ + rc = SQLITE_NOMEM; + }else{ + pNew->bFold = 1; + pNew->iFoldParam = 0; + + for(i=0; rc==SQLITE_OK && ibFold = (zArg[0]=='0'); + } + }else if( 0==sqlite3_stricmp(azArg[i], "remove_diacritics") ){ + if( (zArg[0]!='0' && zArg[0]!='1' && zArg[0]!='2') || zArg[1] ){ + rc = SQLITE_ERROR; + }else{ + pNew->iFoldParam = (zArg[0]!='0') ? 2 : 0; + } + }else{ + rc = SQLITE_ERROR; + } + } + + if( pNew->iFoldParam!=0 && pNew->bFold==0 ){ + rc = SQLITE_ERROR; + } + + if( rc!=SQLITE_OK ){ + fts5TriDelete((Fts5Tokenizer*)pNew); + pNew = 0; + } + } + } + *ppOut = (Fts5Tokenizer*)pNew; + return rc; +} + +/* +** Trigram tokenizer tokenize routine. +*/ +static int fts5TriTokenize( + Fts5Tokenizer *pTok, + void *pCtx, + int unusedFlags, + const char *pText, int nText, + int (*xToken)(void*, int, const char*, int, int, int) +){ + TrigramTokenizer *p = (TrigramTokenizer*)pTok; + int rc = SQLITE_OK; + char aBuf[32]; + char *zOut = aBuf; + int ii; + const unsigned char *zIn = (const unsigned char*)pText; + const unsigned char *zEof = (zIn ? &zIn[nText] : 0); + u32 iCode = 0; + int aStart[3]; /* Input offset of each character in aBuf[] */ + + UNUSED_PARAM(unusedFlags); + + /* Populate aBuf[] with the characters for the first trigram. */ + for(ii=0; ii<3; ii++){ + do { + aStart[ii] = zIn - (const unsigned char*)pText; + if( zIn>=zEof ) return SQLITE_OK; + READ_UTF8(zIn, zEof, iCode); + if( p->bFold ) iCode = sqlite3Fts5UnicodeFold(iCode, p->iFoldParam); + }while( iCode==0 ); + WRITE_UTF8(zOut, iCode); + } + + /* At the start of each iteration of this loop: + ** + ** aBuf: Contains 3 characters. The 3 characters of the next trigram. + ** zOut: Points to the byte following the last character in aBuf. + ** aStart[3]: Contains the byte offset in the input text corresponding + ** to the start of each of the three characters in the buffer. + */ + assert( zIn<=zEof ); + while( 1 ){ + int iNext; /* Start of character following current tri */ + const char *z1; + + /* Read characters from the input up until the first non-diacritic */ + do { + iNext = zIn - (const unsigned char*)pText; + if( zIn>=zEof ){ + iCode = 0; + break; + } + READ_UTF8(zIn, zEof, iCode); + if( p->bFold ) iCode = sqlite3Fts5UnicodeFold(iCode, p->iFoldParam); + }while( iCode==0 ); + + /* Pass the current trigram back to fts5 */ + rc = xToken(pCtx, 0, aBuf, zOut-aBuf, aStart[0], iNext); + if( iCode==0 || rc!=SQLITE_OK ) break; + + /* Remove the first character from buffer aBuf[]. Append the character + ** with codepoint iCode. */ + z1 = aBuf; + FTS5_SKIP_UTF8(z1); + memmove(aBuf, z1, zOut - z1); + zOut -= (z1 - aBuf); + WRITE_UTF8(zOut, iCode); + + /* Update the aStart[] array */ + aStart[0] = aStart[1]; + aStart[1] = aStart[2]; + aStart[2] = iNext; + } + + return rc; +} + +/* +** Argument xCreate is a pointer to a constructor function for a tokenizer. +** pTok is a tokenizer previously created using the same method. This function +** returns one of FTS5_PATTERN_NONE, FTS5_PATTERN_LIKE or FTS5_PATTERN_GLOB +** indicating the style of pattern matching that the tokenizer can support. +** In practice, this is: +** +** "trigram" tokenizer, case_sensitive=1 - FTS5_PATTERN_GLOB +** "trigram" tokenizer, case_sensitive=0 (the default) - FTS5_PATTERN_LIKE +** all other tokenizers - FTS5_PATTERN_NONE +*/ +static int sqlite3Fts5TokenizerPattern( + int (*xCreate)(void*, const char**, int, Fts5Tokenizer**), + Fts5Tokenizer *pTok +){ + if( xCreate==fts5TriCreate ){ + TrigramTokenizer *p = (TrigramTokenizer*)pTok; + if( p->iFoldParam==0 ){ + return p->bFold ? FTS5_PATTERN_LIKE : FTS5_PATTERN_GLOB; + } + } + return FTS5_PATTERN_NONE; +} + +/* +** Return true if the tokenizer described by p->azArg[] is the trigram +** tokenizer. This tokenizer needs to be loaded before xBestIndex is +** called for the first time in order to correctly handle LIKE/GLOB. +*/ +static int sqlite3Fts5TokenizerPreload(Fts5TokenizerConfig *p){ + return (p->nArg>=1 && 0==sqlite3_stricmp(p->azArg[0], "trigram")); +} + + /* ** Register all built-in tokenizers with FTS5. */ @@ -220647,9 +267311,9 @@ static int sqlite3Fts5TokenizerInit(fts5_api *pApi){ } aBuiltin[] = { { "unicode61", {fts5UnicodeCreate, fts5UnicodeDelete, fts5UnicodeTokenize}}, { "ascii", {fts5AsciiCreate, fts5AsciiDelete, fts5AsciiTokenize }}, - { "porter", {fts5PorterCreate, fts5PorterDelete, fts5PorterTokenize }}, + { "trigram", {fts5TriCreate, fts5TriDelete, fts5TriTokenize}}, }; - + int rc = SQLITE_OK; /* Return code */ int i; /* To iterate through builtin functions */ @@ -220661,7 +267325,20 @@ static int sqlite3Fts5TokenizerInit(fts5_api *pApi){ 0 ); } - + if( rc==SQLITE_OK ){ + fts5_tokenizer_v2 sPorter = { + 2, + fts5PorterCreate, + fts5PorterDelete, + fts5PorterTokenize + }; + rc = pApi->xCreateTokenizer_v2(pApi, + "porter", + (void*)pApi, + &sPorter, + 0 + ); + } return rc; } @@ -220697,46 +267374,46 @@ static int sqlite3Fts5TokenizerInit(fts5_api *pApi){ */ static int fts5_remove_diacritic(int c, int bComplex){ unsigned short aDia[] = { - 0, 1797, 1848, 1859, 1891, 1928, 1940, 1995, - 2024, 2040, 2060, 2110, 2168, 2206, 2264, 2286, - 2344, 2383, 2472, 2488, 2516, 2596, 2668, 2732, - 2782, 2842, 2894, 2954, 2984, 3000, 3028, 3336, - 3456, 3696, 3712, 3728, 3744, 3766, 3832, 3896, - 3912, 3928, 3944, 3968, 4008, 4040, 4056, 4106, - 4138, 4170, 4202, 4234, 4266, 4296, 4312, 4344, - 4408, 4424, 4442, 4472, 4488, 4504, 6148, 6198, - 6264, 6280, 6360, 6429, 6505, 6529, 61448, 61468, - 61512, 61534, 61592, 61610, 61642, 61672, 61688, 61704, - 61726, 61784, 61800, 61816, 61836, 61880, 61896, 61914, - 61948, 61998, 62062, 62122, 62154, 62184, 62200, 62218, - 62252, 62302, 62364, 62410, 62442, 62478, 62536, 62554, - 62584, 62604, 62640, 62648, 62656, 62664, 62730, 62766, - 62830, 62890, 62924, 62974, 63032, 63050, 63082, 63118, - 63182, 63242, 63274, 63310, 63368, 63390, + 0, 1797, 1848, 1859, 1891, 1928, 1940, 1995, + 2024, 2040, 2060, 2110, 2168, 2206, 2264, 2286, + 2344, 2383, 2472, 2488, 2516, 2596, 2668, 2732, + 2782, 2842, 2894, 2954, 2984, 3000, 3028, 3336, + 3456, 3696, 3712, 3728, 3744, 3766, 3832, 3896, + 3912, 3928, 3944, 3968, 4008, 4040, 4056, 4106, + 4138, 4170, 4202, 4234, 4266, 4296, 4312, 4344, + 4408, 4424, 4442, 4472, 4488, 4504, 6148, 6198, + 6264, 6280, 6360, 6429, 6505, 6529, 61448, 61468, + 61512, 61534, 61592, 61610, 61642, 61672, 61688, 61704, + 61726, 61784, 61800, 61816, 61836, 61880, 61896, 61914, + 61948, 61998, 62062, 62122, 62154, 62184, 62200, 62218, + 62252, 62302, 62364, 62410, 62442, 62478, 62536, 62554, + 62584, 62604, 62640, 62648, 62656, 62664, 62730, 62766, + 62830, 62890, 62924, 62974, 63032, 63050, 63082, 63118, + 63182, 63242, 63274, 63310, 63368, 63390, }; #define HIBIT ((unsigned char)0x80) unsigned char aChar[] = { - '\0', 'a', 'c', 'e', 'i', 'n', - 'o', 'u', 'y', 'y', 'a', 'c', - 'd', 'e', 'e', 'g', 'h', 'i', - 'j', 'k', 'l', 'n', 'o', 'r', - 's', 't', 'u', 'u', 'w', 'y', - 'z', 'o', 'u', 'a', 'i', 'o', - 'u', 'u'|HIBIT, 'a'|HIBIT, 'g', 'k', 'o', - 'o'|HIBIT, 'j', 'g', 'n', 'a'|HIBIT, 'a', - 'e', 'i', 'o', 'r', 'u', 's', - 't', 'h', 'a', 'e', 'o'|HIBIT, 'o', - 'o'|HIBIT, 'y', '\0', '\0', '\0', '\0', - '\0', '\0', '\0', '\0', 'a', 'b', - 'c'|HIBIT, 'd', 'd', 'e'|HIBIT, 'e', 'e'|HIBIT, - 'f', 'g', 'h', 'h', 'i', 'i'|HIBIT, - 'k', 'l', 'l'|HIBIT, 'l', 'm', 'n', - 'o'|HIBIT, 'p', 'r', 'r'|HIBIT, 'r', 's', - 's'|HIBIT, 't', 'u', 'u'|HIBIT, 'v', 'w', - 'w', 'x', 'y', 'z', 'h', 't', - 'w', 'y', 'a', 'a'|HIBIT, 'a'|HIBIT, 'a'|HIBIT, - 'e', 'e'|HIBIT, 'e'|HIBIT, 'i', 'o', 'o'|HIBIT, - 'o'|HIBIT, 'o'|HIBIT, 'u', 'u'|HIBIT, 'u'|HIBIT, 'y', + '\0', 'a', 'c', 'e', 'i', 'n', + 'o', 'u', 'y', 'y', 'a', 'c', + 'd', 'e', 'e', 'g', 'h', 'i', + 'j', 'k', 'l', 'n', 'o', 'r', + 's', 't', 'u', 'u', 'w', 'y', + 'z', 'o', 'u', 'a', 'i', 'o', + 'u', 'u'|HIBIT, 'a'|HIBIT, 'g', 'k', 'o', + 'o'|HIBIT, 'j', 'g', 'n', 'a'|HIBIT, 'a', + 'e', 'i', 'o', 'r', 'u', 's', + 't', 'h', 'a', 'e', 'o'|HIBIT, 'o', + 'o'|HIBIT, 'y', '\0', '\0', '\0', '\0', + '\0', '\0', '\0', '\0', 'a', 'b', + 'c'|HIBIT, 'd', 'd', 'e'|HIBIT, 'e', 'e'|HIBIT, + 'f', 'g', 'h', 'h', 'i', 'i'|HIBIT, + 'k', 'l', 'l'|HIBIT, 'l', 'm', 'n', + 'o'|HIBIT, 'p', 'r', 'r'|HIBIT, 'r', 's', + 's'|HIBIT, 't', 'u', 'u'|HIBIT, 'v', 'w', + 'w', 'x', 'y', 'z', 'h', 't', + 'w', 'y', 'a', 'a'|HIBIT, 'a'|HIBIT, 'a'|HIBIT, + 'e', 'e'|HIBIT, 'e'|HIBIT, 'i', 'o', 'o'|HIBIT, + 'o'|HIBIT, 'o'|HIBIT, 'u', 'u'|HIBIT, 'u'|HIBIT, 'y', }; unsigned int key = (((unsigned int)c)<<3) | 0x00000007; @@ -220858,19 +267535,19 @@ static int sqlite3Fts5UnicodeFold(int c, int eRemoveDiacritic){ {42802, 1, 62}, {42873, 1, 4}, {42877, 76, 1}, {42878, 1, 10}, {42891, 0, 1}, {42893, 74, 1}, {42896, 1, 4}, {42912, 1, 10}, {42922, 72, 1}, - {65313, 14, 26}, + {65313, 14, 26}, }; static const unsigned short aiOff[] = { - 1, 2, 8, 15, 16, 26, 28, 32, - 37, 38, 40, 48, 63, 64, 69, 71, - 79, 80, 116, 202, 203, 205, 206, 207, - 209, 210, 211, 213, 214, 217, 218, 219, - 775, 7264, 10792, 10795, 23228, 23256, 30204, 54721, - 54753, 54754, 54756, 54787, 54793, 54809, 57153, 57274, - 57921, 58019, 58363, 61722, 65268, 65341, 65373, 65406, - 65408, 65410, 65415, 65424, 65436, 65439, 65450, 65462, - 65472, 65476, 65478, 65480, 65482, 65488, 65506, 65511, - 65514, 65521, 65527, 65528, 65529, + 1, 2, 8, 15, 16, 26, 28, 32, + 37, 38, 40, 48, 63, 64, 69, 71, + 79, 80, 116, 202, 203, 205, 206, 207, + 209, 210, 211, 213, 214, 217, 218, 219, + 775, 7264, 10792, 10795, 23228, 23256, 30204, 54721, + 54753, 54754, 54756, 54787, 54793, 54809, 57153, 57274, + 57921, 58019, 58363, 61722, 65268, 65341, 65373, 65406, + 65408, 65410, 65415, 65424, 65436, 65439, 65450, 65462, + 65472, 65476, 65478, 65480, 65482, 65488, 65506, 65511, + 65514, 65521, 65527, 65528, 65529, }; int ret = c; @@ -220908,7 +267585,7 @@ static int sqlite3Fts5UnicodeFold(int c, int eRemoveDiacritic){ ret = fts5_remove_diacritic(ret, eRemoveDiacritic==2); } } - + else if( c>=66560 && c<66600 ){ ret = c + 40; } @@ -220917,7 +267594,7 @@ static int sqlite3Fts5UnicodeFold(int c, int eRemoveDiacritic){ } -static int sqlite3Fts5UnicodeCatParse(const char *zCat, u8 *aArray){ +static int sqlite3Fts5UnicodeCatParse(const char *zCat, u8 *aArray){ aArray[0] = 1; switch( zCat[0] ){ case 'C': @@ -220927,7 +267604,7 @@ static int sqlite3Fts5UnicodeCatParse(const char *zCat, u8 *aArray){ case 'n': aArray[3] = 1; break; case 's': aArray[4] = 1; break; case 'o': aArray[31] = 1; break; - case '*': + case '*': aArray[1] = 1; aArray[2] = 1; aArray[3] = 1; @@ -220945,7 +267622,7 @@ static int sqlite3Fts5UnicodeCatParse(const char *zCat, u8 *aArray){ case 't': aArray[8] = 1; break; case 'u': aArray[9] = 1; break; case 'C': aArray[30] = 1; break; - case '*': + case '*': aArray[5] = 1; aArray[6] = 1; aArray[7] = 1; @@ -220961,7 +267638,7 @@ static int sqlite3Fts5UnicodeCatParse(const char *zCat, u8 *aArray){ case 'c': aArray[10] = 1; break; case 'e': aArray[11] = 1; break; case 'n': aArray[12] = 1; break; - case '*': + case '*': aArray[10] = 1; aArray[11] = 1; aArray[12] = 1; @@ -220974,7 +267651,7 @@ static int sqlite3Fts5UnicodeCatParse(const char *zCat, u8 *aArray){ case 'd': aArray[13] = 1; break; case 'l': aArray[14] = 1; break; case 'o': aArray[15] = 1; break; - case '*': + case '*': aArray[13] = 1; aArray[14] = 1; aArray[15] = 1; @@ -220991,7 +267668,7 @@ static int sqlite3Fts5UnicodeCatParse(const char *zCat, u8 *aArray){ case 'i': aArray[20] = 1; break; case 'o': aArray[21] = 1; break; case 's': aArray[22] = 1; break; - case '*': + case '*': aArray[16] = 1; aArray[17] = 1; aArray[18] = 1; @@ -221009,7 +267686,7 @@ static int sqlite3Fts5UnicodeCatParse(const char *zCat, u8 *aArray){ case 'k': aArray[24] = 1; break; case 'm': aArray[25] = 1; break; case 'o': aArray[26] = 1; break; - case '*': + case '*': aArray[23] = 1; aArray[24] = 1; aArray[25] = 1; @@ -221023,7 +267700,7 @@ static int sqlite3Fts5UnicodeCatParse(const char *zCat, u8 *aArray){ case 'l': aArray[27] = 1; break; case 'p': aArray[28] = 1; break; case 's': aArray[29] = 1; break; - case '*': + case '*': aArray[27] = 1; aArray[28] = 1; aArray[29] = 1; @@ -221031,374 +267708,377 @@ static int sqlite3Fts5UnicodeCatParse(const char *zCat, u8 *aArray){ default: return 1; } break; + + default: + return 1; } return 0; } static u16 aFts5UnicodeBlock[] = { - 0, 1471, 1753, 1760, 1760, 1760, 1760, 1760, 1760, 1760, - 1760, 1760, 1760, 1760, 1760, 1763, 1765, + 0, 1471, 1753, 1760, 1760, 1760, 1760, 1760, 1760, 1760, + 1760, 1760, 1760, 1760, 1760, 1763, 1765, }; static u16 aFts5UnicodeMap[] = { - 0, 32, 33, 36, 37, 40, 41, 42, 43, 44, - 45, 46, 48, 58, 60, 63, 65, 91, 92, 93, - 94, 95, 96, 97, 123, 124, 125, 126, 127, 160, - 161, 162, 166, 167, 168, 169, 170, 171, 172, 173, - 174, 175, 176, 177, 178, 180, 181, 182, 184, 185, - 186, 187, 188, 191, 192, 215, 216, 223, 247, 248, - 256, 312, 313, 329, 330, 377, 383, 385, 387, 388, - 391, 394, 396, 398, 402, 403, 405, 406, 409, 412, - 414, 415, 417, 418, 423, 427, 428, 431, 434, 436, - 437, 440, 442, 443, 444, 446, 448, 452, 453, 454, - 455, 456, 457, 458, 459, 460, 461, 477, 478, 496, - 497, 498, 499, 500, 503, 505, 506, 564, 570, 572, - 573, 575, 577, 580, 583, 584, 592, 660, 661, 688, - 706, 710, 722, 736, 741, 748, 749, 750, 751, 768, - 880, 884, 885, 886, 890, 891, 894, 900, 902, 903, - 904, 908, 910, 912, 913, 931, 940, 975, 977, 978, - 981, 984, 1008, 1012, 1014, 1015, 1018, 1020, 1021, 1072, - 1120, 1154, 1155, 1160, 1162, 1217, 1231, 1232, 1329, 1369, - 1370, 1377, 1417, 1418, 1423, 1425, 1470, 1471, 1472, 1473, - 1475, 1476, 1478, 1479, 1488, 1520, 1523, 1536, 1542, 1545, - 1547, 1548, 1550, 1552, 1563, 1566, 1568, 1600, 1601, 1611, - 1632, 1642, 1646, 1648, 1649, 1748, 1749, 1750, 1757, 1758, - 1759, 1765, 1767, 1769, 1770, 1774, 1776, 1786, 1789, 1791, - 1792, 1807, 1808, 1809, 1810, 1840, 1869, 1958, 1969, 1984, - 1994, 2027, 2036, 2038, 2039, 2042, 2048, 2070, 2074, 2075, - 2084, 2085, 2088, 2089, 2096, 2112, 2137, 2142, 2208, 2210, - 2276, 2304, 2307, 2308, 2362, 2363, 2364, 2365, 2366, 2369, - 2377, 2381, 2382, 2384, 2385, 2392, 2402, 2404, 2406, 2416, - 2417, 2418, 2425, 2433, 2434, 2437, 2447, 2451, 2474, 2482, - 2486, 2492, 2493, 2494, 2497, 2503, 2507, 2509, 2510, 2519, - 2524, 2527, 2530, 2534, 2544, 2546, 2548, 2554, 2555, 2561, - 2563, 2565, 2575, 2579, 2602, 2610, 2613, 2616, 2620, 2622, - 2625, 2631, 2635, 2641, 2649, 2654, 2662, 2672, 2674, 2677, - 2689, 2691, 2693, 2703, 2707, 2730, 2738, 2741, 2748, 2749, - 2750, 2753, 2759, 2761, 2763, 2765, 2768, 2784, 2786, 2790, - 2800, 2801, 2817, 2818, 2821, 2831, 2835, 2858, 2866, 2869, - 2876, 2877, 2878, 2879, 2880, 2881, 2887, 2891, 2893, 2902, - 2903, 2908, 2911, 2914, 2918, 2928, 2929, 2930, 2946, 2947, - 2949, 2958, 2962, 2969, 2972, 2974, 2979, 2984, 2990, 3006, - 3008, 3009, 3014, 3018, 3021, 3024, 3031, 3046, 3056, 3059, - 3065, 3066, 3073, 3077, 3086, 3090, 3114, 3125, 3133, 3134, - 3137, 3142, 3146, 3157, 3160, 3168, 3170, 3174, 3192, 3199, - 3202, 3205, 3214, 3218, 3242, 3253, 3260, 3261, 3262, 3263, - 3264, 3270, 3271, 3274, 3276, 3285, 3294, 3296, 3298, 3302, - 3313, 3330, 3333, 3342, 3346, 3389, 3390, 3393, 3398, 3402, - 3405, 3406, 3415, 3424, 3426, 3430, 3440, 3449, 3450, 3458, - 3461, 3482, 3507, 3517, 3520, 3530, 3535, 3538, 3542, 3544, - 3570, 3572, 3585, 3633, 3634, 3636, 3647, 3648, 3654, 3655, - 3663, 3664, 3674, 3713, 3716, 3719, 3722, 3725, 3732, 3737, - 3745, 3749, 3751, 3754, 3757, 3761, 3762, 3764, 3771, 3773, - 3776, 3782, 3784, 3792, 3804, 3840, 3841, 3844, 3859, 3860, - 3861, 3864, 3866, 3872, 3882, 3892, 3893, 3894, 3895, 3896, - 3897, 3898, 3899, 3900, 3901, 3902, 3904, 3913, 3953, 3967, - 3968, 3973, 3974, 3976, 3981, 3993, 4030, 4038, 4039, 4046, - 4048, 4053, 4057, 4096, 4139, 4141, 4145, 4146, 4152, 4153, - 4155, 4157, 4159, 4160, 4170, 4176, 4182, 4184, 4186, 4190, - 4193, 4194, 4197, 4199, 4206, 4209, 4213, 4226, 4227, 4229, - 4231, 4237, 4238, 4239, 4240, 4250, 4253, 4254, 4256, 4295, - 4301, 4304, 4347, 4348, 4349, 4682, 4688, 4696, 4698, 4704, - 4746, 4752, 4786, 4792, 4800, 4802, 4808, 4824, 4882, 4888, - 4957, 4960, 4969, 4992, 5008, 5024, 5120, 5121, 5741, 5743, - 5760, 5761, 5787, 5788, 5792, 5867, 5870, 5888, 5902, 5906, - 5920, 5938, 5941, 5952, 5970, 5984, 5998, 6002, 6016, 6068, - 6070, 6071, 6078, 6086, 6087, 6089, 6100, 6103, 6104, 6107, - 6108, 6109, 6112, 6128, 6144, 6150, 6151, 6155, 6158, 6160, - 6176, 6211, 6212, 6272, 6313, 6314, 6320, 6400, 6432, 6435, - 6439, 6441, 6448, 6450, 6451, 6457, 6464, 6468, 6470, 6480, - 6512, 6528, 6576, 6593, 6600, 6608, 6618, 6622, 6656, 6679, - 6681, 6686, 6688, 6741, 6742, 6743, 6744, 6752, 6753, 6754, - 6755, 6757, 6765, 6771, 6783, 6784, 6800, 6816, 6823, 6824, - 6912, 6916, 6917, 6964, 6965, 6966, 6971, 6972, 6973, 6978, - 6979, 6981, 6992, 7002, 7009, 7019, 7028, 7040, 7042, 7043, - 7073, 7074, 7078, 7080, 7082, 7083, 7084, 7086, 7088, 7098, - 7142, 7143, 7144, 7146, 7149, 7150, 7151, 7154, 7164, 7168, - 7204, 7212, 7220, 7222, 7227, 7232, 7245, 7248, 7258, 7288, - 7294, 7360, 7376, 7379, 7380, 7393, 7394, 7401, 7405, 7406, - 7410, 7412, 7413, 7424, 7468, 7531, 7544, 7545, 7579, 7616, - 7676, 7680, 7830, 7838, 7936, 7944, 7952, 7960, 7968, 7976, - 7984, 7992, 8000, 8008, 8016, 8025, 8027, 8029, 8031, 8033, - 8040, 8048, 8064, 8072, 8080, 8088, 8096, 8104, 8112, 8118, - 8120, 8124, 8125, 8126, 8127, 8130, 8134, 8136, 8140, 8141, - 8144, 8150, 8152, 8157, 8160, 8168, 8173, 8178, 8182, 8184, - 8188, 8189, 8192, 8203, 8208, 8214, 8216, 8217, 8218, 8219, - 8221, 8222, 8223, 8224, 8232, 8233, 8234, 8239, 8240, 8249, - 8250, 8251, 8255, 8257, 8260, 8261, 8262, 8263, 8274, 8275, - 8276, 8277, 8287, 8288, 8298, 8304, 8305, 8308, 8314, 8317, - 8318, 8319, 8320, 8330, 8333, 8334, 8336, 8352, 8400, 8413, - 8417, 8418, 8421, 8448, 8450, 8451, 8455, 8456, 8458, 8459, - 8462, 8464, 8467, 8468, 8469, 8470, 8472, 8473, 8478, 8484, - 8485, 8486, 8487, 8488, 8489, 8490, 8494, 8495, 8496, 8500, - 8501, 8505, 8506, 8508, 8510, 8512, 8517, 8519, 8522, 8523, - 8524, 8526, 8527, 8528, 8544, 8579, 8581, 8585, 8592, 8597, - 8602, 8604, 8608, 8609, 8611, 8612, 8614, 8615, 8622, 8623, - 8654, 8656, 8658, 8659, 8660, 8661, 8692, 8960, 8968, 8972, - 8992, 8994, 9001, 9002, 9003, 9084, 9085, 9115, 9140, 9180, - 9186, 9216, 9280, 9312, 9372, 9450, 9472, 9655, 9656, 9665, - 9666, 9720, 9728, 9839, 9840, 9985, 10088, 10089, 10090, 10091, - 10092, 10093, 10094, 10095, 10096, 10097, 10098, 10099, 10100, 10101, - 10102, 10132, 10176, 10181, 10182, 10183, 10214, 10215, 10216, 10217, - 10218, 10219, 10220, 10221, 10222, 10223, 10224, 10240, 10496, 10627, - 10628, 10629, 10630, 10631, 10632, 10633, 10634, 10635, 10636, 10637, - 10638, 10639, 10640, 10641, 10642, 10643, 10644, 10645, 10646, 10647, - 10648, 10649, 10712, 10713, 10714, 10715, 10716, 10748, 10749, 10750, - 11008, 11056, 11077, 11079, 11088, 11264, 11312, 11360, 11363, 11365, - 11367, 11374, 11377, 11378, 11380, 11381, 11383, 11388, 11390, 11393, - 11394, 11492, 11493, 11499, 11503, 11506, 11513, 11517, 11518, 11520, - 11559, 11565, 11568, 11631, 11632, 11647, 11648, 11680, 11688, 11696, - 11704, 11712, 11720, 11728, 11736, 11744, 11776, 11778, 11779, 11780, - 11781, 11782, 11785, 11786, 11787, 11788, 11789, 11790, 11799, 11800, - 11802, 11803, 11804, 11805, 11806, 11808, 11809, 11810, 11811, 11812, - 11813, 11814, 11815, 11816, 11817, 11818, 11823, 11824, 11834, 11904, - 11931, 12032, 12272, 12288, 12289, 12292, 12293, 12294, 12295, 12296, - 12297, 12298, 12299, 12300, 12301, 12302, 12303, 12304, 12305, 12306, - 12308, 12309, 12310, 12311, 12312, 12313, 12314, 12315, 12316, 12317, - 12318, 12320, 12321, 12330, 12334, 12336, 12337, 12342, 12344, 12347, - 12348, 12349, 12350, 12353, 12441, 12443, 12445, 12447, 12448, 12449, - 12539, 12540, 12543, 12549, 12593, 12688, 12690, 12694, 12704, 12736, - 12784, 12800, 12832, 12842, 12872, 12880, 12881, 12896, 12928, 12938, - 12977, 12992, 13056, 13312, 19893, 19904, 19968, 40908, 40960, 40981, - 40982, 42128, 42192, 42232, 42238, 42240, 42508, 42509, 42512, 42528, - 42538, 42560, 42606, 42607, 42608, 42611, 42612, 42622, 42623, 42624, - 42655, 42656, 42726, 42736, 42738, 42752, 42775, 42784, 42786, 42800, - 42802, 42864, 42865, 42873, 42878, 42888, 42889, 42891, 42896, 42912, - 43000, 43002, 43003, 43010, 43011, 43014, 43015, 43019, 43020, 43043, - 43045, 43047, 43048, 43056, 43062, 43064, 43065, 43072, 43124, 43136, - 43138, 43188, 43204, 43214, 43216, 43232, 43250, 43256, 43259, 43264, - 43274, 43302, 43310, 43312, 43335, 43346, 43359, 43360, 43392, 43395, - 43396, 43443, 43444, 43446, 43450, 43452, 43453, 43457, 43471, 43472, - 43486, 43520, 43561, 43567, 43569, 43571, 43573, 43584, 43587, 43588, - 43596, 43597, 43600, 43612, 43616, 43632, 43633, 43639, 43642, 43643, - 43648, 43696, 43697, 43698, 43701, 43703, 43705, 43710, 43712, 43713, - 43714, 43739, 43741, 43742, 43744, 43755, 43756, 43758, 43760, 43762, - 43763, 43765, 43766, 43777, 43785, 43793, 43808, 43816, 43968, 44003, - 44005, 44006, 44008, 44009, 44011, 44012, 44013, 44016, 44032, 55203, - 55216, 55243, 55296, 56191, 56319, 57343, 57344, 63743, 63744, 64112, - 64256, 64275, 64285, 64286, 64287, 64297, 64298, 64312, 64318, 64320, - 64323, 64326, 64434, 64467, 64830, 64831, 64848, 64914, 65008, 65020, - 65021, 65024, 65040, 65047, 65048, 65049, 65056, 65072, 65073, 65075, - 65077, 65078, 65079, 65080, 65081, 65082, 65083, 65084, 65085, 65086, - 65087, 65088, 65089, 65090, 65091, 65092, 65093, 65095, 65096, 65097, - 65101, 65104, 65108, 65112, 65113, 65114, 65115, 65116, 65117, 65118, - 65119, 65122, 65123, 65124, 65128, 65129, 65130, 65136, 65142, 65279, - 65281, 65284, 65285, 65288, 65289, 65290, 65291, 65292, 65293, 65294, - 65296, 65306, 65308, 65311, 65313, 65339, 65340, 65341, 65342, 65343, - 65344, 65345, 65371, 65372, 65373, 65374, 65375, 65376, 65377, 65378, - 65379, 65380, 65382, 65392, 65393, 65438, 65440, 65474, 65482, 65490, - 65498, 65504, 65506, 65507, 65508, 65509, 65512, 65513, 65517, 65529, - 65532, 0, 13, 40, 60, 63, 80, 128, 256, 263, - 311, 320, 373, 377, 394, 400, 464, 509, 640, 672, - 768, 800, 816, 833, 834, 842, 896, 927, 928, 968, - 976, 977, 1024, 1064, 1104, 1184, 2048, 2056, 2058, 2103, - 2108, 2111, 2135, 2136, 2304, 2326, 2335, 2336, 2367, 2432, - 2494, 2560, 2561, 2565, 2572, 2576, 2581, 2585, 2616, 2623, - 2624, 2640, 2656, 2685, 2687, 2816, 2873, 2880, 2904, 2912, - 2936, 3072, 3680, 4096, 4097, 4098, 4099, 4152, 4167, 4178, - 4198, 4224, 4226, 4227, 4272, 4275, 4279, 4281, 4283, 4285, - 4286, 4304, 4336, 4352, 4355, 4391, 4396, 4397, 4406, 4416, - 4480, 4482, 4483, 4531, 4534, 4543, 4545, 4549, 4560, 5760, - 5803, 5804, 5805, 5806, 5808, 5814, 5815, 5824, 8192, 9216, - 9328, 12288, 26624, 28416, 28496, 28497, 28559, 28563, 45056, 53248, - 53504, 53545, 53605, 53607, 53610, 53613, 53619, 53627, 53635, 53637, - 53644, 53674, 53678, 53760, 53826, 53829, 54016, 54112, 54272, 54298, - 54324, 54350, 54358, 54376, 54402, 54428, 54430, 54434, 54437, 54441, - 54446, 54454, 54459, 54461, 54469, 54480, 54506, 54532, 54535, 54541, - 54550, 54558, 54584, 54587, 54592, 54598, 54602, 54610, 54636, 54662, - 54688, 54714, 54740, 54766, 54792, 54818, 54844, 54870, 54896, 54922, - 54952, 54977, 54978, 55003, 55004, 55010, 55035, 55036, 55061, 55062, - 55068, 55093, 55094, 55119, 55120, 55126, 55151, 55152, 55177, 55178, - 55184, 55209, 55210, 55235, 55236, 55242, 55246, 60928, 60933, 60961, - 60964, 60967, 60969, 60980, 60985, 60987, 60994, 60999, 61001, 61003, - 61005, 61009, 61012, 61015, 61017, 61019, 61021, 61023, 61025, 61028, - 61031, 61036, 61044, 61049, 61054, 61056, 61067, 61089, 61093, 61099, - 61168, 61440, 61488, 61600, 61617, 61633, 61649, 61696, 61712, 61744, - 61808, 61926, 61968, 62016, 62032, 62208, 62256, 62263, 62336, 62368, - 62406, 62432, 62464, 62528, 62530, 62713, 62720, 62784, 62800, 62971, - 63045, 63104, 63232, 0, 42710, 42752, 46900, 46912, 47133, 63488, - 1, 32, 256, 0, 65533, + 0, 32, 33, 36, 37, 40, 41, 42, 43, 44, + 45, 46, 48, 58, 60, 63, 65, 91, 92, 93, + 94, 95, 96, 97, 123, 124, 125, 126, 127, 160, + 161, 162, 166, 167, 168, 169, 170, 171, 172, 173, + 174, 175, 176, 177, 178, 180, 181, 182, 184, 185, + 186, 187, 188, 191, 192, 215, 216, 223, 247, 248, + 256, 312, 313, 329, 330, 377, 383, 385, 387, 388, + 391, 394, 396, 398, 402, 403, 405, 406, 409, 412, + 414, 415, 417, 418, 423, 427, 428, 431, 434, 436, + 437, 440, 442, 443, 444, 446, 448, 452, 453, 454, + 455, 456, 457, 458, 459, 460, 461, 477, 478, 496, + 497, 498, 499, 500, 503, 505, 506, 564, 570, 572, + 573, 575, 577, 580, 583, 584, 592, 660, 661, 688, + 706, 710, 722, 736, 741, 748, 749, 750, 751, 768, + 880, 884, 885, 886, 890, 891, 894, 900, 902, 903, + 904, 908, 910, 912, 913, 931, 940, 975, 977, 978, + 981, 984, 1008, 1012, 1014, 1015, 1018, 1020, 1021, 1072, + 1120, 1154, 1155, 1160, 1162, 1217, 1231, 1232, 1329, 1369, + 1370, 1377, 1417, 1418, 1423, 1425, 1470, 1471, 1472, 1473, + 1475, 1476, 1478, 1479, 1488, 1520, 1523, 1536, 1542, 1545, + 1547, 1548, 1550, 1552, 1563, 1566, 1568, 1600, 1601, 1611, + 1632, 1642, 1646, 1648, 1649, 1748, 1749, 1750, 1757, 1758, + 1759, 1765, 1767, 1769, 1770, 1774, 1776, 1786, 1789, 1791, + 1792, 1807, 1808, 1809, 1810, 1840, 1869, 1958, 1969, 1984, + 1994, 2027, 2036, 2038, 2039, 2042, 2048, 2070, 2074, 2075, + 2084, 2085, 2088, 2089, 2096, 2112, 2137, 2142, 2208, 2210, + 2276, 2304, 2307, 2308, 2362, 2363, 2364, 2365, 2366, 2369, + 2377, 2381, 2382, 2384, 2385, 2392, 2402, 2404, 2406, 2416, + 2417, 2418, 2425, 2433, 2434, 2437, 2447, 2451, 2474, 2482, + 2486, 2492, 2493, 2494, 2497, 2503, 2507, 2509, 2510, 2519, + 2524, 2527, 2530, 2534, 2544, 2546, 2548, 2554, 2555, 2561, + 2563, 2565, 2575, 2579, 2602, 2610, 2613, 2616, 2620, 2622, + 2625, 2631, 2635, 2641, 2649, 2654, 2662, 2672, 2674, 2677, + 2689, 2691, 2693, 2703, 2707, 2730, 2738, 2741, 2748, 2749, + 2750, 2753, 2759, 2761, 2763, 2765, 2768, 2784, 2786, 2790, + 2800, 2801, 2817, 2818, 2821, 2831, 2835, 2858, 2866, 2869, + 2876, 2877, 2878, 2879, 2880, 2881, 2887, 2891, 2893, 2902, + 2903, 2908, 2911, 2914, 2918, 2928, 2929, 2930, 2946, 2947, + 2949, 2958, 2962, 2969, 2972, 2974, 2979, 2984, 2990, 3006, + 3008, 3009, 3014, 3018, 3021, 3024, 3031, 3046, 3056, 3059, + 3065, 3066, 3073, 3077, 3086, 3090, 3114, 3125, 3133, 3134, + 3137, 3142, 3146, 3157, 3160, 3168, 3170, 3174, 3192, 3199, + 3202, 3205, 3214, 3218, 3242, 3253, 3260, 3261, 3262, 3263, + 3264, 3270, 3271, 3274, 3276, 3285, 3294, 3296, 3298, 3302, + 3313, 3330, 3333, 3342, 3346, 3389, 3390, 3393, 3398, 3402, + 3405, 3406, 3415, 3424, 3426, 3430, 3440, 3449, 3450, 3458, + 3461, 3482, 3507, 3517, 3520, 3530, 3535, 3538, 3542, 3544, + 3570, 3572, 3585, 3633, 3634, 3636, 3647, 3648, 3654, 3655, + 3663, 3664, 3674, 3713, 3716, 3719, 3722, 3725, 3732, 3737, + 3745, 3749, 3751, 3754, 3757, 3761, 3762, 3764, 3771, 3773, + 3776, 3782, 3784, 3792, 3804, 3840, 3841, 3844, 3859, 3860, + 3861, 3864, 3866, 3872, 3882, 3892, 3893, 3894, 3895, 3896, + 3897, 3898, 3899, 3900, 3901, 3902, 3904, 3913, 3953, 3967, + 3968, 3973, 3974, 3976, 3981, 3993, 4030, 4038, 4039, 4046, + 4048, 4053, 4057, 4096, 4139, 4141, 4145, 4146, 4152, 4153, + 4155, 4157, 4159, 4160, 4170, 4176, 4182, 4184, 4186, 4190, + 4193, 4194, 4197, 4199, 4206, 4209, 4213, 4226, 4227, 4229, + 4231, 4237, 4238, 4239, 4240, 4250, 4253, 4254, 4256, 4295, + 4301, 4304, 4347, 4348, 4349, 4682, 4688, 4696, 4698, 4704, + 4746, 4752, 4786, 4792, 4800, 4802, 4808, 4824, 4882, 4888, + 4957, 4960, 4969, 4992, 5008, 5024, 5120, 5121, 5741, 5743, + 5760, 5761, 5787, 5788, 5792, 5867, 5870, 5888, 5902, 5906, + 5920, 5938, 5941, 5952, 5970, 5984, 5998, 6002, 6016, 6068, + 6070, 6071, 6078, 6086, 6087, 6089, 6100, 6103, 6104, 6107, + 6108, 6109, 6112, 6128, 6144, 6150, 6151, 6155, 6158, 6160, + 6176, 6211, 6212, 6272, 6313, 6314, 6320, 6400, 6432, 6435, + 6439, 6441, 6448, 6450, 6451, 6457, 6464, 6468, 6470, 6480, + 6512, 6528, 6576, 6593, 6600, 6608, 6618, 6622, 6656, 6679, + 6681, 6686, 6688, 6741, 6742, 6743, 6744, 6752, 6753, 6754, + 6755, 6757, 6765, 6771, 6783, 6784, 6800, 6816, 6823, 6824, + 6912, 6916, 6917, 6964, 6965, 6966, 6971, 6972, 6973, 6978, + 6979, 6981, 6992, 7002, 7009, 7019, 7028, 7040, 7042, 7043, + 7073, 7074, 7078, 7080, 7082, 7083, 7084, 7086, 7088, 7098, + 7142, 7143, 7144, 7146, 7149, 7150, 7151, 7154, 7164, 7168, + 7204, 7212, 7220, 7222, 7227, 7232, 7245, 7248, 7258, 7288, + 7294, 7360, 7376, 7379, 7380, 7393, 7394, 7401, 7405, 7406, + 7410, 7412, 7413, 7424, 7468, 7531, 7544, 7545, 7579, 7616, + 7676, 7680, 7830, 7838, 7936, 7944, 7952, 7960, 7968, 7976, + 7984, 7992, 8000, 8008, 8016, 8025, 8027, 8029, 8031, 8033, + 8040, 8048, 8064, 8072, 8080, 8088, 8096, 8104, 8112, 8118, + 8120, 8124, 8125, 8126, 8127, 8130, 8134, 8136, 8140, 8141, + 8144, 8150, 8152, 8157, 8160, 8168, 8173, 8178, 8182, 8184, + 8188, 8189, 8192, 8203, 8208, 8214, 8216, 8217, 8218, 8219, + 8221, 8222, 8223, 8224, 8232, 8233, 8234, 8239, 8240, 8249, + 8250, 8251, 8255, 8257, 8260, 8261, 8262, 8263, 8274, 8275, + 8276, 8277, 8287, 8288, 8298, 8304, 8305, 8308, 8314, 8317, + 8318, 8319, 8320, 8330, 8333, 8334, 8336, 8352, 8400, 8413, + 8417, 8418, 8421, 8448, 8450, 8451, 8455, 8456, 8458, 8459, + 8462, 8464, 8467, 8468, 8469, 8470, 8472, 8473, 8478, 8484, + 8485, 8486, 8487, 8488, 8489, 8490, 8494, 8495, 8496, 8500, + 8501, 8505, 8506, 8508, 8510, 8512, 8517, 8519, 8522, 8523, + 8524, 8526, 8527, 8528, 8544, 8579, 8581, 8585, 8592, 8597, + 8602, 8604, 8608, 8609, 8611, 8612, 8614, 8615, 8622, 8623, + 8654, 8656, 8658, 8659, 8660, 8661, 8692, 8960, 8968, 8972, + 8992, 8994, 9001, 9002, 9003, 9084, 9085, 9115, 9140, 9180, + 9186, 9216, 9280, 9312, 9372, 9450, 9472, 9655, 9656, 9665, + 9666, 9720, 9728, 9839, 9840, 9985, 10088, 10089, 10090, 10091, + 10092, 10093, 10094, 10095, 10096, 10097, 10098, 10099, 10100, 10101, + 10102, 10132, 10176, 10181, 10182, 10183, 10214, 10215, 10216, 10217, + 10218, 10219, 10220, 10221, 10222, 10223, 10224, 10240, 10496, 10627, + 10628, 10629, 10630, 10631, 10632, 10633, 10634, 10635, 10636, 10637, + 10638, 10639, 10640, 10641, 10642, 10643, 10644, 10645, 10646, 10647, + 10648, 10649, 10712, 10713, 10714, 10715, 10716, 10748, 10749, 10750, + 11008, 11056, 11077, 11079, 11088, 11264, 11312, 11360, 11363, 11365, + 11367, 11374, 11377, 11378, 11380, 11381, 11383, 11388, 11390, 11393, + 11394, 11492, 11493, 11499, 11503, 11506, 11513, 11517, 11518, 11520, + 11559, 11565, 11568, 11631, 11632, 11647, 11648, 11680, 11688, 11696, + 11704, 11712, 11720, 11728, 11736, 11744, 11776, 11778, 11779, 11780, + 11781, 11782, 11785, 11786, 11787, 11788, 11789, 11790, 11799, 11800, + 11802, 11803, 11804, 11805, 11806, 11808, 11809, 11810, 11811, 11812, + 11813, 11814, 11815, 11816, 11817, 11818, 11823, 11824, 11834, 11904, + 11931, 12032, 12272, 12288, 12289, 12292, 12293, 12294, 12295, 12296, + 12297, 12298, 12299, 12300, 12301, 12302, 12303, 12304, 12305, 12306, + 12308, 12309, 12310, 12311, 12312, 12313, 12314, 12315, 12316, 12317, + 12318, 12320, 12321, 12330, 12334, 12336, 12337, 12342, 12344, 12347, + 12348, 12349, 12350, 12353, 12441, 12443, 12445, 12447, 12448, 12449, + 12539, 12540, 12543, 12549, 12593, 12688, 12690, 12694, 12704, 12736, + 12784, 12800, 12832, 12842, 12872, 12880, 12881, 12896, 12928, 12938, + 12977, 12992, 13056, 13312, 19893, 19904, 19968, 40908, 40960, 40981, + 40982, 42128, 42192, 42232, 42238, 42240, 42508, 42509, 42512, 42528, + 42538, 42560, 42606, 42607, 42608, 42611, 42612, 42622, 42623, 42624, + 42655, 42656, 42726, 42736, 42738, 42752, 42775, 42784, 42786, 42800, + 42802, 42864, 42865, 42873, 42878, 42888, 42889, 42891, 42896, 42912, + 43000, 43002, 43003, 43010, 43011, 43014, 43015, 43019, 43020, 43043, + 43045, 43047, 43048, 43056, 43062, 43064, 43065, 43072, 43124, 43136, + 43138, 43188, 43204, 43214, 43216, 43232, 43250, 43256, 43259, 43264, + 43274, 43302, 43310, 43312, 43335, 43346, 43359, 43360, 43392, 43395, + 43396, 43443, 43444, 43446, 43450, 43452, 43453, 43457, 43471, 43472, + 43486, 43520, 43561, 43567, 43569, 43571, 43573, 43584, 43587, 43588, + 43596, 43597, 43600, 43612, 43616, 43632, 43633, 43639, 43642, 43643, + 43648, 43696, 43697, 43698, 43701, 43703, 43705, 43710, 43712, 43713, + 43714, 43739, 43741, 43742, 43744, 43755, 43756, 43758, 43760, 43762, + 43763, 43765, 43766, 43777, 43785, 43793, 43808, 43816, 43968, 44003, + 44005, 44006, 44008, 44009, 44011, 44012, 44013, 44016, 44032, 55203, + 55216, 55243, 55296, 56191, 56319, 57343, 57344, 63743, 63744, 64112, + 64256, 64275, 64285, 64286, 64287, 64297, 64298, 64312, 64318, 64320, + 64323, 64326, 64434, 64467, 64830, 64831, 64848, 64914, 65008, 65020, + 65021, 65024, 65040, 65047, 65048, 65049, 65056, 65072, 65073, 65075, + 65077, 65078, 65079, 65080, 65081, 65082, 65083, 65084, 65085, 65086, + 65087, 65088, 65089, 65090, 65091, 65092, 65093, 65095, 65096, 65097, + 65101, 65104, 65108, 65112, 65113, 65114, 65115, 65116, 65117, 65118, + 65119, 65122, 65123, 65124, 65128, 65129, 65130, 65136, 65142, 65279, + 65281, 65284, 65285, 65288, 65289, 65290, 65291, 65292, 65293, 65294, + 65296, 65306, 65308, 65311, 65313, 65339, 65340, 65341, 65342, 65343, + 65344, 65345, 65371, 65372, 65373, 65374, 65375, 65376, 65377, 65378, + 65379, 65380, 65382, 65392, 65393, 65438, 65440, 65474, 65482, 65490, + 65498, 65504, 65506, 65507, 65508, 65509, 65512, 65513, 65517, 65529, + 65532, 0, 13, 40, 60, 63, 80, 128, 256, 263, + 311, 320, 373, 377, 394, 400, 464, 509, 640, 672, + 768, 800, 816, 833, 834, 842, 896, 927, 928, 968, + 976, 977, 1024, 1064, 1104, 1184, 2048, 2056, 2058, 2103, + 2108, 2111, 2135, 2136, 2304, 2326, 2335, 2336, 2367, 2432, + 2494, 2560, 2561, 2565, 2572, 2576, 2581, 2585, 2616, 2623, + 2624, 2640, 2656, 2685, 2687, 2816, 2873, 2880, 2904, 2912, + 2936, 3072, 3680, 4096, 4097, 4098, 4099, 4152, 4167, 4178, + 4198, 4224, 4226, 4227, 4272, 4275, 4279, 4281, 4283, 4285, + 4286, 4304, 4336, 4352, 4355, 4391, 4396, 4397, 4406, 4416, + 4480, 4482, 4483, 4531, 4534, 4543, 4545, 4549, 4560, 5760, + 5803, 5804, 5805, 5806, 5808, 5814, 5815, 5824, 8192, 9216, + 9328, 12288, 26624, 28416, 28496, 28497, 28559, 28563, 45056, 53248, + 53504, 53545, 53605, 53607, 53610, 53613, 53619, 53627, 53635, 53637, + 53644, 53674, 53678, 53760, 53826, 53829, 54016, 54112, 54272, 54298, + 54324, 54350, 54358, 54376, 54402, 54428, 54430, 54434, 54437, 54441, + 54446, 54454, 54459, 54461, 54469, 54480, 54506, 54532, 54535, 54541, + 54550, 54558, 54584, 54587, 54592, 54598, 54602, 54610, 54636, 54662, + 54688, 54714, 54740, 54766, 54792, 54818, 54844, 54870, 54896, 54922, + 54952, 54977, 54978, 55003, 55004, 55010, 55035, 55036, 55061, 55062, + 55068, 55093, 55094, 55119, 55120, 55126, 55151, 55152, 55177, 55178, + 55184, 55209, 55210, 55235, 55236, 55242, 55246, 60928, 60933, 60961, + 60964, 60967, 60969, 60980, 60985, 60987, 60994, 60999, 61001, 61003, + 61005, 61009, 61012, 61015, 61017, 61019, 61021, 61023, 61025, 61028, + 61031, 61036, 61044, 61049, 61054, 61056, 61067, 61089, 61093, 61099, + 61168, 61440, 61488, 61600, 61617, 61633, 61649, 61696, 61712, 61744, + 61808, 61926, 61968, 62016, 62032, 62208, 62256, 62263, 62336, 62368, + 62406, 62432, 62464, 62528, 62530, 62713, 62720, 62784, 62800, 62971, + 63045, 63104, 63232, 0, 42710, 42752, 46900, 46912, 47133, 63488, + 1, 32, 256, 0, 65533, }; static u16 aFts5UnicodeData[] = { - 1025, 61, 117, 55, 117, 54, 50, 53, 57, 53, - 49, 85, 333, 85, 121, 85, 841, 54, 53, 50, - 56, 48, 56, 837, 54, 57, 50, 57, 1057, 61, - 53, 151, 58, 53, 56, 58, 39, 52, 57, 34, - 58, 56, 58, 57, 79, 56, 37, 85, 56, 47, - 39, 51, 111, 53, 745, 57, 233, 773, 57, 261, - 1822, 37, 542, 37, 1534, 222, 69, 73, 37, 126, - 126, 73, 69, 137, 37, 73, 37, 105, 101, 73, - 37, 73, 37, 190, 158, 37, 126, 126, 73, 37, - 126, 94, 37, 39, 94, 69, 135, 41, 40, 37, - 41, 40, 37, 41, 40, 37, 542, 37, 606, 37, - 41, 40, 37, 126, 73, 37, 1886, 197, 73, 37, - 73, 69, 126, 105, 37, 286, 2181, 39, 869, 582, - 152, 390, 472, 166, 248, 38, 56, 38, 568, 3596, - 158, 38, 56, 94, 38, 101, 53, 88, 41, 53, - 105, 41, 73, 37, 553, 297, 1125, 94, 37, 105, - 101, 798, 133, 94, 57, 126, 94, 37, 1641, 1541, - 1118, 58, 172, 75, 1790, 478, 37, 2846, 1225, 38, - 213, 1253, 53, 49, 55, 1452, 49, 44, 53, 76, - 53, 76, 53, 44, 871, 103, 85, 162, 121, 85, - 55, 85, 90, 364, 53, 85, 1031, 38, 327, 684, - 333, 149, 71, 44, 3175, 53, 39, 236, 34, 58, - 204, 70, 76, 58, 140, 71, 333, 103, 90, 39, - 469, 34, 39, 44, 967, 876, 2855, 364, 39, 333, - 1063, 300, 70, 58, 117, 38, 711, 140, 38, 300, - 38, 108, 38, 172, 501, 807, 108, 53, 39, 359, - 876, 108, 42, 1735, 44, 42, 44, 39, 106, 268, - 138, 44, 74, 39, 236, 327, 76, 85, 333, 53, - 38, 199, 231, 44, 74, 263, 71, 711, 231, 39, - 135, 44, 39, 106, 140, 74, 74, 44, 39, 42, - 71, 103, 76, 333, 71, 87, 207, 58, 55, 76, - 42, 199, 71, 711, 231, 71, 71, 71, 44, 106, - 76, 76, 108, 44, 135, 39, 333, 76, 103, 44, - 76, 42, 295, 103, 711, 231, 71, 167, 44, 39, - 106, 172, 76, 42, 74, 44, 39, 71, 76, 333, - 53, 55, 44, 74, 263, 71, 711, 231, 71, 167, - 44, 39, 42, 44, 42, 140, 74, 74, 44, 44, - 42, 71, 103, 76, 333, 58, 39, 207, 44, 39, - 199, 103, 135, 71, 39, 71, 71, 103, 391, 74, - 44, 74, 106, 106, 44, 39, 42, 333, 111, 218, - 55, 58, 106, 263, 103, 743, 327, 167, 39, 108, - 138, 108, 140, 76, 71, 71, 76, 333, 239, 58, - 74, 263, 103, 743, 327, 167, 44, 39, 42, 44, - 170, 44, 74, 74, 76, 74, 39, 71, 76, 333, - 71, 74, 263, 103, 1319, 39, 106, 140, 106, 106, - 44, 39, 42, 71, 76, 333, 207, 58, 199, 74, - 583, 775, 295, 39, 231, 44, 106, 108, 44, 266, - 74, 53, 1543, 44, 71, 236, 55, 199, 38, 268, - 53, 333, 85, 71, 39, 71, 39, 39, 135, 231, - 103, 39, 39, 71, 135, 44, 71, 204, 76, 39, - 167, 38, 204, 333, 135, 39, 122, 501, 58, 53, - 122, 76, 218, 333, 335, 58, 44, 58, 44, 58, - 44, 54, 50, 54, 50, 74, 263, 1159, 460, 42, - 172, 53, 76, 167, 364, 1164, 282, 44, 218, 90, - 181, 154, 85, 1383, 74, 140, 42, 204, 42, 76, - 74, 76, 39, 333, 213, 199, 74, 76, 135, 108, - 39, 106, 71, 234, 103, 140, 423, 44, 74, 76, - 202, 44, 39, 42, 333, 106, 44, 90, 1225, 41, - 41, 1383, 53, 38, 10631, 135, 231, 39, 135, 1319, - 135, 1063, 135, 231, 39, 135, 487, 1831, 135, 2151, - 108, 309, 655, 519, 346, 2727, 49, 19847, 85, 551, - 61, 839, 54, 50, 2407, 117, 110, 423, 135, 108, - 583, 108, 85, 583, 76, 423, 103, 76, 1671, 76, - 42, 236, 266, 44, 74, 364, 117, 38, 117, 55, - 39, 44, 333, 335, 213, 49, 149, 108, 61, 333, - 1127, 38, 1671, 1319, 44, 39, 2247, 935, 108, 138, - 76, 106, 74, 44, 202, 108, 58, 85, 333, 967, - 167, 1415, 554, 231, 74, 333, 47, 1114, 743, 76, - 106, 85, 1703, 42, 44, 42, 236, 44, 42, 44, - 74, 268, 202, 332, 44, 333, 333, 245, 38, 213, - 140, 42, 1511, 44, 42, 172, 42, 44, 170, 44, - 74, 231, 333, 245, 346, 300, 314, 76, 42, 967, - 42, 140, 74, 76, 42, 44, 74, 71, 333, 1415, - 44, 42, 76, 106, 44, 42, 108, 74, 149, 1159, - 266, 268, 74, 76, 181, 333, 103, 333, 967, 198, - 85, 277, 108, 53, 428, 42, 236, 135, 44, 135, - 74, 44, 71, 1413, 2022, 421, 38, 1093, 1190, 1260, - 140, 4830, 261, 3166, 261, 265, 197, 201, 261, 265, - 261, 265, 197, 201, 261, 41, 41, 41, 94, 229, - 265, 453, 261, 264, 261, 264, 261, 264, 165, 69, - 137, 40, 56, 37, 120, 101, 69, 137, 40, 120, - 133, 69, 137, 120, 261, 169, 120, 101, 69, 137, - 40, 88, 381, 162, 209, 85, 52, 51, 54, 84, - 51, 54, 52, 277, 59, 60, 162, 61, 309, 52, - 51, 149, 80, 117, 57, 54, 50, 373, 57, 53, - 48, 341, 61, 162, 194, 47, 38, 207, 121, 54, - 50, 38, 335, 121, 54, 50, 422, 855, 428, 139, - 44, 107, 396, 90, 41, 154, 41, 90, 37, 105, - 69, 105, 37, 58, 41, 90, 57, 169, 218, 41, - 58, 41, 58, 41, 58, 137, 58, 37, 137, 37, - 135, 37, 90, 69, 73, 185, 94, 101, 58, 57, - 90, 37, 58, 527, 1134, 94, 142, 47, 185, 186, - 89, 154, 57, 90, 57, 90, 57, 250, 57, 1018, - 89, 90, 57, 58, 57, 1018, 8601, 282, 153, 666, - 89, 250, 54, 50, 2618, 57, 986, 825, 1306, 217, - 602, 1274, 378, 1935, 2522, 719, 5882, 57, 314, 57, - 1754, 281, 3578, 57, 4634, 3322, 54, 50, 54, 50, - 54, 50, 54, 50, 54, 50, 54, 50, 54, 50, - 975, 1434, 185, 54, 50, 1017, 54, 50, 54, 50, - 54, 50, 54, 50, 54, 50, 537, 8218, 4217, 54, - 50, 54, 50, 54, 50, 54, 50, 54, 50, 54, - 50, 54, 50, 54, 50, 54, 50, 54, 50, 54, - 50, 2041, 54, 50, 54, 50, 1049, 54, 50, 8281, - 1562, 697, 90, 217, 346, 1513, 1509, 126, 73, 69, - 254, 105, 37, 94, 37, 94, 165, 70, 105, 37, - 3166, 37, 218, 158, 108, 94, 149, 47, 85, 1221, - 37, 37, 1799, 38, 53, 44, 743, 231, 231, 231, - 231, 231, 231, 231, 231, 1036, 85, 52, 51, 52, - 51, 117, 52, 51, 53, 52, 51, 309, 49, 85, - 49, 53, 52, 51, 85, 52, 51, 54, 50, 54, - 50, 54, 50, 54, 50, 181, 38, 341, 81, 858, - 2874, 6874, 410, 61, 117, 58, 38, 39, 46, 54, - 50, 54, 50, 54, 50, 54, 50, 54, 50, 90, - 54, 50, 54, 50, 54, 50, 54, 50, 49, 54, - 82, 58, 302, 140, 74, 49, 166, 90, 110, 38, - 39, 53, 90, 2759, 76, 88, 70, 39, 49, 2887, - 53, 102, 39, 1319, 3015, 90, 143, 346, 871, 1178, - 519, 1018, 335, 986, 271, 58, 495, 1050, 335, 1274, - 495, 2042, 8218, 39, 39, 2074, 39, 39, 679, 38, - 36583, 1786, 1287, 198, 85, 8583, 38, 117, 519, 333, - 71, 1502, 39, 44, 107, 53, 332, 53, 38, 798, - 44, 2247, 334, 76, 213, 760, 294, 88, 478, 69, - 2014, 38, 261, 190, 350, 38, 88, 158, 158, 382, - 70, 37, 231, 44, 103, 44, 135, 44, 743, 74, - 76, 42, 154, 207, 90, 55, 58, 1671, 149, 74, - 1607, 522, 44, 85, 333, 588, 199, 117, 39, 333, - 903, 268, 85, 743, 364, 74, 53, 935, 108, 42, - 1511, 44, 74, 140, 74, 44, 138, 437, 38, 333, - 85, 1319, 204, 74, 76, 74, 76, 103, 44, 263, - 44, 42, 333, 149, 519, 38, 199, 122, 39, 42, - 1543, 44, 39, 108, 71, 76, 167, 76, 39, 44, - 39, 71, 38, 85, 359, 42, 76, 74, 85, 39, - 70, 42, 44, 199, 199, 199, 231, 231, 1127, 74, - 44, 74, 44, 74, 53, 42, 44, 333, 39, 39, - 743, 1575, 36, 68, 68, 36, 63, 63, 11719, 3399, - 229, 165, 39, 44, 327, 57, 423, 167, 39, 71, - 71, 3463, 536, 11623, 54, 50, 2055, 1735, 391, 55, - 58, 524, 245, 54, 50, 53, 236, 53, 81, 80, - 54, 50, 54, 50, 54, 50, 54, 50, 54, 50, - 54, 50, 54, 50, 54, 50, 85, 54, 50, 149, - 112, 117, 149, 49, 54, 50, 54, 50, 54, 50, - 117, 57, 49, 121, 53, 55, 85, 167, 4327, 34, - 117, 55, 117, 54, 50, 53, 57, 53, 49, 85, - 333, 85, 121, 85, 841, 54, 53, 50, 56, 48, - 56, 837, 54, 57, 50, 57, 54, 50, 53, 54, - 50, 85, 327, 38, 1447, 70, 999, 199, 199, 199, - 103, 87, 57, 56, 58, 87, 58, 153, 90, 98, - 90, 391, 839, 615, 71, 487, 455, 3943, 117, 1455, - 314, 1710, 143, 570, 47, 410, 1466, 44, 935, 1575, - 999, 143, 551, 46, 263, 46, 967, 53, 1159, 263, - 53, 174, 1289, 1285, 2503, 333, 199, 39, 1415, 71, - 39, 743, 53, 271, 711, 207, 53, 839, 53, 1799, - 71, 39, 108, 76, 140, 135, 103, 871, 108, 44, - 271, 309, 935, 79, 53, 1735, 245, 711, 271, 615, - 271, 2343, 1007, 42, 44, 42, 1703, 492, 245, 655, - 333, 76, 42, 1447, 106, 140, 74, 76, 85, 34, - 149, 807, 333, 108, 1159, 172, 42, 268, 333, 149, - 76, 42, 1543, 106, 300, 74, 135, 149, 333, 1383, - 44, 42, 44, 74, 204, 42, 44, 333, 28135, 3182, - 149, 34279, 18215, 2215, 39, 1482, 140, 422, 71, 7898, - 1274, 1946, 74, 108, 122, 202, 258, 268, 90, 236, - 986, 140, 1562, 2138, 108, 58, 2810, 591, 841, 837, - 841, 229, 581, 841, 837, 41, 73, 41, 73, 137, - 265, 133, 37, 229, 357, 841, 837, 73, 137, 265, - 233, 837, 73, 137, 169, 41, 233, 837, 841, 837, - 841, 837, 841, 837, 841, 837, 841, 837, 841, 901, - 809, 57, 805, 57, 197, 809, 57, 805, 57, 197, - 809, 57, 805, 57, 197, 809, 57, 805, 57, 197, - 809, 57, 805, 57, 197, 94, 1613, 135, 871, 71, - 39, 39, 327, 135, 39, 39, 39, 39, 39, 39, - 103, 71, 39, 39, 39, 39, 39, 39, 71, 39, - 135, 231, 135, 135, 39, 327, 551, 103, 167, 551, - 89, 1434, 3226, 506, 474, 506, 506, 367, 1018, 1946, - 1402, 954, 1402, 314, 90, 1082, 218, 2266, 666, 1210, - 186, 570, 2042, 58, 5850, 154, 2010, 154, 794, 2266, - 378, 2266, 3738, 39, 39, 39, 39, 39, 39, 17351, - 34, 3074, 7692, 63, 63, + 1025, 61, 117, 55, 117, 54, 50, 53, 57, 53, + 49, 85, 333, 85, 121, 85, 841, 54, 53, 50, + 56, 48, 56, 837, 54, 57, 50, 57, 1057, 61, + 53, 151, 58, 53, 56, 58, 39, 52, 57, 34, + 58, 56, 58, 57, 79, 56, 37, 85, 56, 47, + 39, 51, 111, 53, 745, 57, 233, 773, 57, 261, + 1822, 37, 542, 37, 1534, 222, 69, 73, 37, 126, + 126, 73, 69, 137, 37, 73, 37, 105, 101, 73, + 37, 73, 37, 190, 158, 37, 126, 126, 73, 37, + 126, 94, 37, 39, 94, 69, 135, 41, 40, 37, + 41, 40, 37, 41, 40, 37, 542, 37, 606, 37, + 41, 40, 37, 126, 73, 37, 1886, 197, 73, 37, + 73, 69, 126, 105, 37, 286, 2181, 39, 869, 582, + 152, 390, 472, 166, 248, 38, 56, 38, 568, 3596, + 158, 38, 56, 94, 38, 101, 53, 88, 41, 53, + 105, 41, 73, 37, 553, 297, 1125, 94, 37, 105, + 101, 798, 133, 94, 57, 126, 94, 37, 1641, 1541, + 1118, 58, 172, 75, 1790, 478, 37, 2846, 1225, 38, + 213, 1253, 53, 49, 55, 1452, 49, 44, 53, 76, + 53, 76, 53, 44, 871, 103, 85, 162, 121, 85, + 55, 85, 90, 364, 53, 85, 1031, 38, 327, 684, + 333, 149, 71, 44, 3175, 53, 39, 236, 34, 58, + 204, 70, 76, 58, 140, 71, 333, 103, 90, 39, + 469, 34, 39, 44, 967, 876, 2855, 364, 39, 333, + 1063, 300, 70, 58, 117, 38, 711, 140, 38, 300, + 38, 108, 38, 172, 501, 807, 108, 53, 39, 359, + 876, 108, 42, 1735, 44, 42, 44, 39, 106, 268, + 138, 44, 74, 39, 236, 327, 76, 85, 333, 53, + 38, 199, 231, 44, 74, 263, 71, 711, 231, 39, + 135, 44, 39, 106, 140, 74, 74, 44, 39, 42, + 71, 103, 76, 333, 71, 87, 207, 58, 55, 76, + 42, 199, 71, 711, 231, 71, 71, 71, 44, 106, + 76, 76, 108, 44, 135, 39, 333, 76, 103, 44, + 76, 42, 295, 103, 711, 231, 71, 167, 44, 39, + 106, 172, 76, 42, 74, 44, 39, 71, 76, 333, + 53, 55, 44, 74, 263, 71, 711, 231, 71, 167, + 44, 39, 42, 44, 42, 140, 74, 74, 44, 44, + 42, 71, 103, 76, 333, 58, 39, 207, 44, 39, + 199, 103, 135, 71, 39, 71, 71, 103, 391, 74, + 44, 74, 106, 106, 44, 39, 42, 333, 111, 218, + 55, 58, 106, 263, 103, 743, 327, 167, 39, 108, + 138, 108, 140, 76, 71, 71, 76, 333, 239, 58, + 74, 263, 103, 743, 327, 167, 44, 39, 42, 44, + 170, 44, 74, 74, 76, 74, 39, 71, 76, 333, + 71, 74, 263, 103, 1319, 39, 106, 140, 106, 106, + 44, 39, 42, 71, 76, 333, 207, 58, 199, 74, + 583, 775, 295, 39, 231, 44, 106, 108, 44, 266, + 74, 53, 1543, 44, 71, 236, 55, 199, 38, 268, + 53, 333, 85, 71, 39, 71, 39, 39, 135, 231, + 103, 39, 39, 71, 135, 44, 71, 204, 76, 39, + 167, 38, 204, 333, 135, 39, 122, 501, 58, 53, + 122, 76, 218, 333, 335, 58, 44, 58, 44, 58, + 44, 54, 50, 54, 50, 74, 263, 1159, 460, 42, + 172, 53, 76, 167, 364, 1164, 282, 44, 218, 90, + 181, 154, 85, 1383, 74, 140, 42, 204, 42, 76, + 74, 76, 39, 333, 213, 199, 74, 76, 135, 108, + 39, 106, 71, 234, 103, 140, 423, 44, 74, 76, + 202, 44, 39, 42, 333, 106, 44, 90, 1225, 41, + 41, 1383, 53, 38, 10631, 135, 231, 39, 135, 1319, + 135, 1063, 135, 231, 39, 135, 487, 1831, 135, 2151, + 108, 309, 655, 519, 346, 2727, 49, 19847, 85, 551, + 61, 839, 54, 50, 2407, 117, 110, 423, 135, 108, + 583, 108, 85, 583, 76, 423, 103, 76, 1671, 76, + 42, 236, 266, 44, 74, 364, 117, 38, 117, 55, + 39, 44, 333, 335, 213, 49, 149, 108, 61, 333, + 1127, 38, 1671, 1319, 44, 39, 2247, 935, 108, 138, + 76, 106, 74, 44, 202, 108, 58, 85, 333, 967, + 167, 1415, 554, 231, 74, 333, 47, 1114, 743, 76, + 106, 85, 1703, 42, 44, 42, 236, 44, 42, 44, + 74, 268, 202, 332, 44, 333, 333, 245, 38, 213, + 140, 42, 1511, 44, 42, 172, 42, 44, 170, 44, + 74, 231, 333, 245, 346, 300, 314, 76, 42, 967, + 42, 140, 74, 76, 42, 44, 74, 71, 333, 1415, + 44, 42, 76, 106, 44, 42, 108, 74, 149, 1159, + 266, 268, 74, 76, 181, 333, 103, 333, 967, 198, + 85, 277, 108, 53, 428, 42, 236, 135, 44, 135, + 74, 44, 71, 1413, 2022, 421, 38, 1093, 1190, 1260, + 140, 4830, 261, 3166, 261, 265, 197, 201, 261, 265, + 261, 265, 197, 201, 261, 41, 41, 41, 94, 229, + 265, 453, 261, 264, 261, 264, 261, 264, 165, 69, + 137, 40, 56, 37, 120, 101, 69, 137, 40, 120, + 133, 69, 137, 120, 261, 169, 120, 101, 69, 137, + 40, 88, 381, 162, 209, 85, 52, 51, 54, 84, + 51, 54, 52, 277, 59, 60, 162, 61, 309, 52, + 51, 149, 80, 117, 57, 54, 50, 373, 57, 53, + 48, 341, 61, 162, 194, 47, 38, 207, 121, 54, + 50, 38, 335, 121, 54, 50, 422, 855, 428, 139, + 44, 107, 396, 90, 41, 154, 41, 90, 37, 105, + 69, 105, 37, 58, 41, 90, 57, 169, 218, 41, + 58, 41, 58, 41, 58, 137, 58, 37, 137, 37, + 135, 37, 90, 69, 73, 185, 94, 101, 58, 57, + 90, 37, 58, 527, 1134, 94, 142, 47, 185, 186, + 89, 154, 57, 90, 57, 90, 57, 250, 57, 1018, + 89, 90, 57, 58, 57, 1018, 8601, 282, 153, 666, + 89, 250, 54, 50, 2618, 57, 986, 825, 1306, 217, + 602, 1274, 378, 1935, 2522, 719, 5882, 57, 314, 57, + 1754, 281, 3578, 57, 4634, 3322, 54, 50, 54, 50, + 54, 50, 54, 50, 54, 50, 54, 50, 54, 50, + 975, 1434, 185, 54, 50, 1017, 54, 50, 54, 50, + 54, 50, 54, 50, 54, 50, 537, 8218, 4217, 54, + 50, 54, 50, 54, 50, 54, 50, 54, 50, 54, + 50, 54, 50, 54, 50, 54, 50, 54, 50, 54, + 50, 2041, 54, 50, 54, 50, 1049, 54, 50, 8281, + 1562, 697, 90, 217, 346, 1513, 1509, 126, 73, 69, + 254, 105, 37, 94, 37, 94, 165, 70, 105, 37, + 3166, 37, 218, 158, 108, 94, 149, 47, 85, 1221, + 37, 37, 1799, 38, 53, 44, 743, 231, 231, 231, + 231, 231, 231, 231, 231, 1036, 85, 52, 51, 52, + 51, 117, 52, 51, 53, 52, 51, 309, 49, 85, + 49, 53, 52, 51, 85, 52, 51, 54, 50, 54, + 50, 54, 50, 54, 50, 181, 38, 341, 81, 858, + 2874, 6874, 410, 61, 117, 58, 38, 39, 46, 54, + 50, 54, 50, 54, 50, 54, 50, 54, 50, 90, + 54, 50, 54, 50, 54, 50, 54, 50, 49, 54, + 82, 58, 302, 140, 74, 49, 166, 90, 110, 38, + 39, 53, 90, 2759, 76, 88, 70, 39, 49, 2887, + 53, 102, 39, 1319, 3015, 90, 143, 346, 871, 1178, + 519, 1018, 335, 986, 271, 58, 495, 1050, 335, 1274, + 495, 2042, 8218, 39, 39, 2074, 39, 39, 679, 38, + 36583, 1786, 1287, 198, 85, 8583, 38, 117, 519, 333, + 71, 1502, 39, 44, 107, 53, 332, 53, 38, 798, + 44, 2247, 334, 76, 213, 760, 294, 88, 478, 69, + 2014, 38, 261, 190, 350, 38, 88, 158, 158, 382, + 70, 37, 231, 44, 103, 44, 135, 44, 743, 74, + 76, 42, 154, 207, 90, 55, 58, 1671, 149, 74, + 1607, 522, 44, 85, 333, 588, 199, 117, 39, 333, + 903, 268, 85, 743, 364, 74, 53, 935, 108, 42, + 1511, 44, 74, 140, 74, 44, 138, 437, 38, 333, + 85, 1319, 204, 74, 76, 74, 76, 103, 44, 263, + 44, 42, 333, 149, 519, 38, 199, 122, 39, 42, + 1543, 44, 39, 108, 71, 76, 167, 76, 39, 44, + 39, 71, 38, 85, 359, 42, 76, 74, 85, 39, + 70, 42, 44, 199, 199, 199, 231, 231, 1127, 74, + 44, 74, 44, 74, 53, 42, 44, 333, 39, 39, + 743, 1575, 36, 68, 68, 36, 63, 63, 11719, 3399, + 229, 165, 39, 44, 327, 57, 423, 167, 39, 71, + 71, 3463, 536, 11623, 54, 50, 2055, 1735, 391, 55, + 58, 524, 245, 54, 50, 53, 236, 53, 81, 80, + 54, 50, 54, 50, 54, 50, 54, 50, 54, 50, + 54, 50, 54, 50, 54, 50, 85, 54, 50, 149, + 112, 117, 149, 49, 54, 50, 54, 50, 54, 50, + 117, 57, 49, 121, 53, 55, 85, 167, 4327, 34, + 117, 55, 117, 54, 50, 53, 57, 53, 49, 85, + 333, 85, 121, 85, 841, 54, 53, 50, 56, 48, + 56, 837, 54, 57, 50, 57, 54, 50, 53, 54, + 50, 85, 327, 38, 1447, 70, 999, 199, 199, 199, + 103, 87, 57, 56, 58, 87, 58, 153, 90, 98, + 90, 391, 839, 615, 71, 487, 455, 3943, 117, 1455, + 314, 1710, 143, 570, 47, 410, 1466, 44, 935, 1575, + 999, 143, 551, 46, 263, 46, 967, 53, 1159, 263, + 53, 174, 1289, 1285, 2503, 333, 199, 39, 1415, 71, + 39, 743, 53, 271, 711, 207, 53, 839, 53, 1799, + 71, 39, 108, 76, 140, 135, 103, 871, 108, 44, + 271, 309, 935, 79, 53, 1735, 245, 711, 271, 615, + 271, 2343, 1007, 42, 44, 42, 1703, 492, 245, 655, + 333, 76, 42, 1447, 106, 140, 74, 76, 85, 34, + 149, 807, 333, 108, 1159, 172, 42, 268, 333, 149, + 76, 42, 1543, 106, 300, 74, 135, 149, 333, 1383, + 44, 42, 44, 74, 204, 42, 44, 333, 28135, 3182, + 149, 34279, 18215, 2215, 39, 1482, 140, 422, 71, 7898, + 1274, 1946, 74, 108, 122, 202, 258, 268, 90, 236, + 986, 140, 1562, 2138, 108, 58, 2810, 591, 841, 837, + 841, 229, 581, 841, 837, 41, 73, 41, 73, 137, + 265, 133, 37, 229, 357, 841, 837, 73, 137, 265, + 233, 837, 73, 137, 169, 41, 233, 837, 841, 837, + 841, 837, 841, 837, 841, 837, 841, 837, 841, 901, + 809, 57, 805, 57, 197, 809, 57, 805, 57, 197, + 809, 57, 805, 57, 197, 809, 57, 805, 57, 197, + 809, 57, 805, 57, 197, 94, 1613, 135, 871, 71, + 39, 39, 327, 135, 39, 39, 39, 39, 39, 39, + 103, 71, 39, 39, 39, 39, 39, 39, 71, 39, + 135, 231, 135, 135, 39, 327, 551, 103, 167, 551, + 89, 1434, 3226, 506, 474, 506, 506, 367, 1018, 1946, + 1402, 954, 1402, 314, 90, 1082, 218, 2266, 666, 1210, + 186, 570, 2042, 58, 5850, 154, 2010, 154, 794, 2266, + 378, 2266, 3738, 39, 39, 39, 39, 39, 39, 17351, + 34, 3074, 7692, 63, 63, }; -static int sqlite3Fts5UnicodeCategory(u32 iCode) { +static int sqlite3Fts5UnicodeCategory(u32 iCode) { int iRes = -1; int iHi; int iLo; @@ -221440,6 +268120,7 @@ static void sqlite3Fts5UnicodeAscii(u8 *aArray, u8 *aAscii){ } iTbl++; } + aAscii[0] = 0; /* 0x00 is never a token character */ } /* @@ -221748,7 +268429,7 @@ static int FTS5_NOINLINE fts5PutVarint64(unsigned char *p, u64 v){ v >>= 7; } return 9; - } + } n = 0; do{ buf[n++] = (u8)((v & 0x7f) | 0x80); @@ -221800,7 +268481,7 @@ static int sqlite3Fts5GetVarintLen(u32 iVal){ ****************************************************************************** ** ** This is an SQLite virtual table module implementing direct access to an -** existing FTS5 index. The module may create several different types of +** existing FTS5 index. The module may create several different types of ** tables: ** ** col: @@ -221808,21 +268489,21 @@ static int sqlite3Fts5GetVarintLen(u32 iVal){ ** ** One row for each term/column combination. The value of $doc is set to ** the number of fts5 rows that contain at least one instance of term -** $term within column $col. Field $cnt is set to the total number of -** instances of term $term in column $col (in any row of the fts5 table). +** $term within column $col. Field $cnt is set to the total number of +** instances of term $term in column $col (in any row of the fts5 table). ** ** row: ** CREATE TABLE vocab(term, doc, cnt, PRIMARY KEY(term)); ** ** One row for each term in the database. The value of $doc is set to ** the number of fts5 rows that contain at least one instance of term -** $term. Field $cnt is set to the total number of instances of term +** $term. Field $cnt is set to the total number of instances of term ** $term in the database. ** ** instance: ** CREATE TABLE vocab(term, doc, col, offset, PRIMARY KEY()); ** -** One row for each term instance in the database. +** One row for each term instance in the database. */ @@ -221839,6 +268520,7 @@ struct Fts5VocabTable { sqlite3 *db; /* Database handle */ Fts5Global *pGlobal; /* FTS5 global object for this database */ int eType; /* FTS5_VOCAB_COL, ROW or INSTANCE */ + unsigned bBusy; /* True if busy */ }; struct Fts5VocabCursor { @@ -221848,9 +268530,11 @@ struct Fts5VocabCursor { int bEof; /* True if this cursor is at EOF */ Fts5IndexIter *pIter; /* Term/rowid iterator object */ + void *pStruct; /* From sqlite3Fts5StructureRef() */ int nLeTerm; /* Size of zLeTerm in bytes */ char *zLeTerm; /* (term <= $zLeTerm) paramater, or NULL */ + int colUsed; /* Copy of sqlite3_index_info.colUsed */ /* These are used by 'col' tables only */ int iCol; @@ -221877,13 +268561,15 @@ struct Fts5VocabCursor { /* ** Bits for the mask used as the idxNum value by xBestIndex/xFilter. */ -#define FTS5_VOCAB_TERM_EQ 0x01 -#define FTS5_VOCAB_TERM_GE 0x02 -#define FTS5_VOCAB_TERM_LE 0x04 +#define FTS5_VOCAB_TERM_EQ 0x0100 +#define FTS5_VOCAB_TERM_GE 0x0200 +#define FTS5_VOCAB_TERM_LE 0x0400 + +#define FTS5_VOCAB_COLUSED_MASK 0xFF /* -** Translate a string containing an fts5vocab table type to an +** Translate a string containing an fts5vocab table type to an ** FTS5_VOCAB_XXX constant. If successful, set *peType to the output ** value and return SQLITE_OK. Otherwise, set *pzErr to an error message ** and return SQLITE_ERROR. @@ -221961,8 +268647,8 @@ static int fts5VocabInitVtab( sqlite3_vtab **ppVTab, /* Write the resulting vtab structure here */ char **pzErr /* Write any error message here */ ){ - const char *azSchema[] = { - "CREATE TABlE vocab(" FTS5_VOCAB_COL_SCHEMA ")", + const char *azSchema[] = { + "CREATE TABlE vocab(" FTS5_VOCAB_COL_SCHEMA ")", "CREATE TABlE vocab(" FTS5_VOCAB_ROW_SCHEMA ")", "CREATE TABlE vocab(" FTS5_VOCAB_INST_SCHEMA ")" }; @@ -221977,14 +268663,14 @@ static int fts5VocabInitVtab( *pzErr = sqlite3_mprintf("wrong number of vtable arguments"); rc = SQLITE_ERROR; }else{ - int nByte; /* Bytes of space to allocate */ + i64 nByte; /* Bytes of space to allocate */ const char *zDb = bDb ? argv[3] : argv[1]; const char *zTab = bDb ? argv[4] : argv[3]; const char *zType = bDb ? argv[5] : argv[4]; - int nDb = (int)strlen(zDb)+1; - int nTab = (int)strlen(zTab)+1; + i64 nDb = strlen(zDb)+1; + i64 nTab = strlen(zTab)+1; int eType = 0; - + rc = fts5VocabTableType(zType, pzErr, &eType); if( rc==SQLITE_OK ){ assert( eType>=0 && eType= ? ** -** are interpreted. Less-than and less-than-or-equal are treated +** are interpreted. Less-than and less-than-or-equal are treated ** identically, as are greater-than and greater-than-or-equal. */ static int fts5VocabBestIndexMethod( @@ -222056,11 +268742,13 @@ static int fts5VocabBestIndexMethod( int iTermEq = -1; int iTermGe = -1; int iTermLe = -1; - int idxNum = 0; + int idxNum = (int)pInfo->colUsed; int nArg = 0; UNUSED_PARAM(pUnused); + assert( (pInfo->colUsed & FTS5_VOCAB_COLUSED_MASK)==pInfo->colUsed ); + for(i=0; inConstraint; i++){ struct sqlite3_index_constraint *p = &pInfo->aConstraint[i]; if( p->usable==0 ) continue; @@ -222096,8 +268784,8 @@ static int fts5VocabBestIndexMethod( ** specifically - "ORDER BY term" or "ORDER BY term ASC" - set the ** sqlite3_index_info.orderByConsumed flag to tell the core the results ** are already in sorted order. */ - if( pInfo->nOrderBy==1 - && pInfo->aOrderBy[0].iColumn==0 + if( pInfo->nOrderBy==1 + && pInfo->aOrderBy[0].iColumn==0 && pInfo->aOrderBy[0].desc==0 ){ pInfo->orderByConsumed = 1; @@ -222111,7 +268799,7 @@ static int fts5VocabBestIndexMethod( ** Implementation of xOpen method. */ static int fts5VocabOpenMethod( - sqlite3_vtab *pVTab, + sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCsr ){ Fts5VocabTable *pTab = (Fts5VocabTable*)pVTab; @@ -222121,6 +268809,12 @@ static int fts5VocabOpenMethod( sqlite3_stmt *pStmt = 0; char *zSql = 0; + if( pTab->bBusy ){ + pVTab->zErrMsg = sqlite3_mprintf( + "recursive definition for %s.%s", pTab->zFts5Db, pTab->zFts5Tbl + ); + return SQLITE_ERROR; + } zSql = sqlite3Fts5Mprintf(&rc, "SELECT t.%Q FROM %Q.%Q AS t WHERE t.%Q MATCH '*id'", pTab->zFts5Tbl, pTab->zFts5Db, pTab->zFts5Tbl, pTab->zFts5Tbl @@ -222132,10 +268826,12 @@ static int fts5VocabOpenMethod( assert( rc==SQLITE_OK || pStmt==0 ); if( rc==SQLITE_ERROR ) rc = SQLITE_OK; + pTab->bBusy = 1; if( pStmt && sqlite3_step(pStmt)==SQLITE_ROW ){ i64 iId = sqlite3_column_int64(pStmt, 0); pFts5 = sqlite3Fts5TableFromCsrid(pTab->pGlobal, iId); } + pTab->bBusy = 0; if( rc==SQLITE_OK ){ if( pFts5==0 ){ @@ -222144,7 +268840,7 @@ static int fts5VocabOpenMethod( if( rc==SQLITE_OK ){ pVTab->zErrMsg = sqlite3_mprintf( "no such fts5 table: %s.%s", pTab->zFts5Db, pTab->zFts5Tbl - ); + ); rc = SQLITE_ERROR; } }else{ @@ -222153,7 +268849,7 @@ static int fts5VocabOpenMethod( } if( rc==SQLITE_OK ){ - int nByte = pFts5->pConfig->nCol * sizeof(i64)*2 + sizeof(Fts5VocabCursor); + i64 nByte = pFts5->pConfig->nCol * sizeof(i64)*2 + sizeof(Fts5VocabCursor); pCsr = (Fts5VocabCursor*)sqlite3Fts5MallocZero(&rc, nByte); } @@ -222170,14 +268866,27 @@ static int fts5VocabOpenMethod( return rc; } +/* +** Restore cursor pCsr to the state it was in immediately after being +** created by the xOpen() method. +*/ static void fts5VocabResetCursor(Fts5VocabCursor *pCsr){ + int nCol = pCsr->pFts5->pConfig->nCol; pCsr->rowid = 0; sqlite3Fts5IterClose(pCsr->pIter); + sqlite3Fts5StructureRelease(pCsr->pStruct); + pCsr->pStruct = 0; pCsr->pIter = 0; sqlite3_free(pCsr->zLeTerm); pCsr->nLeTerm = -1; pCsr->zLeTerm = 0; pCsr->bEof = 0; + pCsr->iCol = 0; + pCsr->iInstPos = 0; + pCsr->iInstOff = 0; + pCsr->colUsed = 0; + memset(pCsr->aCnt, 0, sizeof(i64)*nCol); + memset(pCsr->aDoc, 0, sizeof(i64)*nCol); } /* @@ -222195,7 +268904,7 @@ static int fts5VocabCloseMethod(sqlite3_vtab_cursor *pCursor){ static int fts5VocabInstanceNewTerm(Fts5VocabCursor *pCsr){ int rc = SQLITE_OK; - + if( sqlite3Fts5IterEof(pCsr->pIter) ){ pCsr->bEof = 1; }else{ @@ -222221,11 +268930,11 @@ static int fts5VocabInstanceNext(Fts5VocabCursor *pCsr){ Fts5IndexIter *pIter = pCsr->pIter; i64 *pp = &pCsr->iInstPos; int *po = &pCsr->iInstOff; - + assert( sqlite3Fts5IterEof(pIter)==0 ); assert( pCsr->bEof==0 ); while( eDetail==FTS5_DETAIL_NONE - || sqlite3Fts5PoslistNext64(pIter->pData, pIter->nData, po, pp) + || sqlite3Fts5PoslistNext64(pIter->pData, pIter->nData, po, pp) ){ pCsr->iInstPos = 0; pCsr->iInstOff = 0; @@ -222250,9 +268959,11 @@ static int fts5VocabInstanceNext(Fts5VocabCursor *pCsr){ static int fts5VocabNextMethod(sqlite3_vtab_cursor *pCursor){ Fts5VocabCursor *pCsr = (Fts5VocabCursor*)pCursor; Fts5VocabTable *pTab = (Fts5VocabTable*)pCursor->pVtab; - int rc = SQLITE_OK; int nCol = pCsr->pFts5->pConfig->nCol; + int rc; + rc = sqlite3Fts5StructureTest(pCsr->pFts5->pIndex, pCsr->pStruct); + if( rc!=SQLITE_OK ) return rc; pCsr->rowid++; if( pTab->eType==FTS5_VOCAB_INSTANCE ){ @@ -222300,9 +269011,19 @@ static int fts5VocabNextMethod(sqlite3_vtab_cursor *pCursor){ switch( pTab->eType ){ case FTS5_VOCAB_ROW: - if( eDetail==FTS5_DETAIL_FULL ){ - while( 0==sqlite3Fts5PoslistNext64(pPos, nPos, &iOff, &iPos) ){ - pCsr->aCnt[0]++; + /* Do not bother counting the number of instances if the "cnt" + ** column is not being read (according to colUsed). */ + if( eDetail==FTS5_DETAIL_FULL && (pCsr->colUsed & 0x04) ){ + while( iPosaCnt[] */ + pCsr->aCnt[0]++; + } } } pCsr->aDoc[0]++; @@ -222350,8 +269071,8 @@ static int fts5VocabNextMethod(sqlite3_vtab_cursor *pCursor){ if( rc==SQLITE_OK ){ zTerm = sqlite3Fts5IterTerm(pCsr->pIter, &nTerm); - if( nTerm!=pCsr->term.n - || (nTerm>0 && memcmp(zTerm, pCsr->term.p, nTerm)) + if( nTerm!=pCsr->term.n + || (nTerm>0 && memcmp(zTerm, pCsr->term.p, nTerm)) ){ break; } @@ -222362,8 +269083,10 @@ static int fts5VocabNextMethod(sqlite3_vtab_cursor *pCursor){ } if( rc==SQLITE_OK && pCsr->bEof==0 && pTab->eType==FTS5_VOCAB_COL ){ - while( pCsr->aDoc[pCsr->iCol]==0 ) pCsr->iCol++; - assert( pCsr->iColpFts5->pConfig->nCol ); + for(/* noop */; pCsr->iColaDoc[pCsr->iCol]==0; pCsr->iCol++); + if( pCsr->iCol==nCol ){ + rc = FTS5_CORRUPT; + } } return rc; } @@ -222398,11 +269121,12 @@ static int fts5VocabFilterMethod( if( idxNum & FTS5_VOCAB_TERM_EQ ) pEq = apVal[iVal++]; if( idxNum & FTS5_VOCAB_TERM_GE ) pGe = apVal[iVal++]; if( idxNum & FTS5_VOCAB_TERM_LE ) pLe = apVal[iVal++]; + pCsr->colUsed = (idxNum & FTS5_VOCAB_COLUSED_MASK); if( pEq ){ zTerm = (const char *)sqlite3_value_text(pEq); nTerm = sqlite3_value_bytes(pEq); - f = 0; + f = FTS5INDEX_QUERY_NOTOKENDATA; }else{ if( pGe ){ zTerm = (const char *)sqlite3_value_text(pGe); @@ -222412,7 +269136,7 @@ static int fts5VocabFilterMethod( const char *zCopy = (const char *)sqlite3_value_text(pLe); if( zCopy==0 ) zCopy = ""; pCsr->nLeTerm = sqlite3_value_bytes(pLe); - pCsr->zLeTerm = sqlite3_malloc(pCsr->nLeTerm+1); + pCsr->zLeTerm = sqlite3_malloc64((i64)pCsr->nLeTerm+1); if( pCsr->zLeTerm==0 ){ rc = SQLITE_NOMEM; }else{ @@ -222424,12 +269148,15 @@ static int fts5VocabFilterMethod( if( rc==SQLITE_OK ){ Fts5Index *pIndex = pCsr->pFts5->pIndex; rc = sqlite3Fts5IndexQuery(pIndex, zTerm, nTerm, f, 0, &pCsr->pIter); + if( rc==SQLITE_OK ){ + pCsr->pStruct = sqlite3Fts5StructureRef(pIndex); + } } if( rc==SQLITE_OK && eType==FTS5_VOCAB_INSTANCE ){ rc = fts5VocabInstanceNewTerm(pCsr); } - if( rc==SQLITE_OK && !pCsr->bEof - && (eType!=FTS5_VOCAB_INSTANCE + if( rc==SQLITE_OK && !pCsr->bEof + && (eType!=FTS5_VOCAB_INSTANCE || pCsr->pFts5->pConfig->eDetail!=FTS5_DETAIL_NONE) ){ rc = fts5VocabNextMethod(pCursor); @@ -222438,8 +269165,8 @@ static int fts5VocabFilterMethod( return rc; } -/* -** This is the xEof method of the virtual table. SQLite calls this +/* +** This is the xEof method of the virtual table. SQLite calls this ** routine to find out if it has reached the end of a result set. */ static int fts5VocabEofMethod(sqlite3_vtab_cursor *pCursor){ @@ -222514,13 +269241,13 @@ static int fts5VocabColumnMethod( return SQLITE_OK; } -/* +/* ** This is the xRowid method. The SQLite core calls this routine to ** retrieve the rowid for the current row of the result set. The ** rowid should be written to *pRowid. */ static int fts5VocabRowidMethod( - sqlite3_vtab_cursor *pCursor, + sqlite3_vtab_cursor *pCursor, sqlite_int64 *pRowid ){ Fts5VocabCursor *pCsr = (Fts5VocabCursor*)pCursor; @@ -222553,7 +269280,8 @@ static int sqlite3Fts5VocabInit(Fts5Global *pGlobal, sqlite3 *db){ /* xSavepoint */ 0, /* xRelease */ 0, /* xRollbackTo */ 0, - /* xShadowName */ 0 + /* xShadowName */ 0, + /* xIntegrity */ 0 }; void *p = (void*)pGlobal; @@ -222561,7 +269289,7 @@ static int sqlite3Fts5VocabInit(Fts5Global *pGlobal, sqlite3 *db){ } - +/* Here ends the fts5.c composite file. */ #endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_FTS5) */ /************** End of fts5.c ************************************************/ @@ -222598,6 +269326,16 @@ SQLITE_EXTENSION_INIT1 #ifndef SQLITE_OMIT_VIRTUALTABLE + +#define STMT_NUM_INTEGER_COLUMN 10 +typedef struct StmtRow StmtRow; +struct StmtRow { + sqlite3_int64 iRowid; /* Rowid value */ + char *zSql; /* column "sql" */ + int aCol[STMT_NUM_INTEGER_COLUMN+1]; /* all other column values */ + StmtRow *pNext; /* Next row to return */ +}; + /* stmt_vtab is a subclass of sqlite3_vtab which will ** serve as the underlying representation of a stmt virtual table */ @@ -222615,8 +269353,7 @@ typedef struct stmt_cursor stmt_cursor; struct stmt_cursor { sqlite3_vtab_cursor base; /* Base class - must be first */ sqlite3 *db; /* Database connection for this cursor */ - sqlite3_stmt *pStmt; /* Statement cursor is currently pointing at */ - sqlite3_int64 iRowid; /* The rowid */ + StmtRow *pRow; /* Current row */ }; /* @@ -222656,11 +269393,15 @@ static int stmtConnect( #define STMT_COLUMN_MEM 10 /* SQLITE_STMTSTATUS_MEMUSED */ + (void)pAux; + (void)argc; + (void)argv; + (void)pzErr; rc = sqlite3_declare_vtab(db, "CREATE TABLE x(sql,ncol,ro,busy,nscan,nsort,naidx,nstep," "reprep,run,mem)"); if( rc==SQLITE_OK ){ - pNew = sqlite3_malloc( sizeof(*pNew) ); + pNew = sqlite3_malloc64( sizeof(*pNew) ); *ppVtab = (sqlite3_vtab*)pNew; if( pNew==0 ) return SQLITE_NOMEM; memset(pNew, 0, sizeof(*pNew)); @@ -222682,7 +269423,7 @@ static int stmtDisconnect(sqlite3_vtab *pVtab){ */ static int stmtOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ stmt_cursor *pCur; - pCur = sqlite3_malloc( sizeof(*pCur) ); + pCur = sqlite3_malloc64( sizeof(*pCur) ); if( pCur==0 ) return SQLITE_NOMEM; memset(pCur, 0, sizeof(*pCur)); pCur->db = ((stmt_vtab*)p)->db; @@ -222690,10 +269431,21 @@ static int stmtOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ return SQLITE_OK; } +static void stmtCsrReset(stmt_cursor *pCur){ + StmtRow *pRow = 0; + StmtRow *pNext = 0; + for(pRow=pCur->pRow; pRow; pRow=pNext){ + pNext = pRow->pNext; + sqlite3_free(pRow); + } + pCur->pRow = 0; +} + /* ** Destructor for a stmt_cursor. */ static int stmtClose(sqlite3_vtab_cursor *cur){ + stmtCsrReset((stmt_cursor*)cur); sqlite3_free(cur); return SQLITE_OK; } @@ -222704,8 +269456,9 @@ static int stmtClose(sqlite3_vtab_cursor *cur){ */ static int stmtNext(sqlite3_vtab_cursor *cur){ stmt_cursor *pCur = (stmt_cursor*)cur; - pCur->iRowid++; - pCur->pStmt = sqlite3_next_stmt(pCur->db, pCur->pStmt); + StmtRow *pNext = pCur->pRow->pNext; + sqlite3_free(pCur->pRow); + pCur->pRow = pNext; return SQLITE_OK; } @@ -222719,38 +269472,11 @@ static int stmtColumn( int i /* Which column to return */ ){ stmt_cursor *pCur = (stmt_cursor*)cur; - switch( i ){ - case STMT_COLUMN_SQL: { - sqlite3_result_text(ctx, sqlite3_sql(pCur->pStmt), -1, SQLITE_TRANSIENT); - break; - } - case STMT_COLUMN_NCOL: { - sqlite3_result_int(ctx, sqlite3_column_count(pCur->pStmt)); - break; - } - case STMT_COLUMN_RO: { - sqlite3_result_int(ctx, sqlite3_stmt_readonly(pCur->pStmt)); - break; - } - case STMT_COLUMN_BUSY: { - sqlite3_result_int(ctx, sqlite3_stmt_busy(pCur->pStmt)); - break; - } - case STMT_COLUMN_MEM: { - i = SQLITE_STMTSTATUS_MEMUSED + - STMT_COLUMN_NSCAN - SQLITE_STMTSTATUS_FULLSCAN_STEP; - /* Fall thru */ - } - case STMT_COLUMN_NSCAN: - case STMT_COLUMN_NSORT: - case STMT_COLUMN_NAIDX: - case STMT_COLUMN_NSTEP: - case STMT_COLUMN_REPREP: - case STMT_COLUMN_RUN: { - sqlite3_result_int(ctx, sqlite3_stmt_status(pCur->pStmt, - i-STMT_COLUMN_NSCAN+SQLITE_STMTSTATUS_FULLSCAN_STEP, 0)); - break; - } + StmtRow *pRow = pCur->pRow; + if( i==STMT_COLUMN_SQL ){ + sqlite3_result_text(ctx, pRow->zSql, -1, SQLITE_TRANSIENT); + }else{ + sqlite3_result_int(ctx, pRow->aCol[i]); } return SQLITE_OK; } @@ -222761,7 +269487,7 @@ static int stmtColumn( */ static int stmtRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ stmt_cursor *pCur = (stmt_cursor*)cur; - *pRowid = pCur->iRowid; + *pRowid = pCur->pRow->iRowid; return SQLITE_OK; } @@ -222771,24 +269497,72 @@ static int stmtRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ */ static int stmtEof(sqlite3_vtab_cursor *cur){ stmt_cursor *pCur = (stmt_cursor*)cur; - return pCur->pStmt==0; + return pCur->pRow==0; } /* ** This method is called to "rewind" the stmt_cursor object back ** to the first row of output. This method is always called at least -** once prior to any call to stmtColumn() or stmtRowid() or +** once prior to any call to stmtColumn() or stmtRowid() or ** stmtEof(). */ static int stmtFilter( - sqlite3_vtab_cursor *pVtabCursor, + sqlite3_vtab_cursor *pVtabCursor, int idxNum, const char *idxStr, int argc, sqlite3_value **argv ){ stmt_cursor *pCur = (stmt_cursor *)pVtabCursor; - pCur->pStmt = 0; - pCur->iRowid = 0; - return stmtNext(pVtabCursor); + sqlite3_stmt *p = 0; + sqlite3_int64 iRowid = 1; + StmtRow **ppRow = 0; + + (void)idxNum; + (void)idxStr; + (void)argc; + (void)argv; + stmtCsrReset(pCur); + ppRow = &pCur->pRow; + for(p=sqlite3_next_stmt(pCur->db, 0); p; p=sqlite3_next_stmt(pCur->db, p)){ + const char *zSql = sqlite3_sql(p); + sqlite3_int64 nSql = zSql ? strlen(zSql)+1 : 0; + StmtRow *pNew = (StmtRow*)sqlite3_malloc64(sizeof(StmtRow) + nSql); + + if( pNew==0 ) return SQLITE_NOMEM; + memset(pNew, 0, sizeof(StmtRow)); + if( zSql ){ + pNew->zSql = (char*)&pNew[1]; + memcpy(pNew->zSql, zSql, nSql); + } + pNew->aCol[STMT_COLUMN_NCOL] = sqlite3_column_count(p); + pNew->aCol[STMT_COLUMN_RO] = sqlite3_stmt_readonly(p); + pNew->aCol[STMT_COLUMN_BUSY] = sqlite3_stmt_busy(p); + pNew->aCol[STMT_COLUMN_NSCAN] = sqlite3_stmt_status( + p, SQLITE_STMTSTATUS_FULLSCAN_STEP, 0 + ); + pNew->aCol[STMT_COLUMN_NSORT] = sqlite3_stmt_status( + p, SQLITE_STMTSTATUS_SORT, 0 + ); + pNew->aCol[STMT_COLUMN_NAIDX] = sqlite3_stmt_status( + p, SQLITE_STMTSTATUS_AUTOINDEX, 0 + ); + pNew->aCol[STMT_COLUMN_NSTEP] = sqlite3_stmt_status( + p, SQLITE_STMTSTATUS_VM_STEP, 0 + ); + pNew->aCol[STMT_COLUMN_REPREP] = sqlite3_stmt_status( + p, SQLITE_STMTSTATUS_REPREPARE, 0 + ); + pNew->aCol[STMT_COLUMN_RUN] = sqlite3_stmt_status( + p, SQLITE_STMTSTATUS_RUN, 0 + ); + pNew->aCol[STMT_COLUMN_MEM] = sqlite3_stmt_status( + p, SQLITE_STMTSTATUS_MEMUSED, 0 + ); + pNew->iRowid = iRowid++; + *ppRow = pNew; + ppRow = &pNew->pNext; + } + + return SQLITE_OK; } /* @@ -222801,13 +269575,14 @@ static int stmtBestIndex( sqlite3_vtab *tab, sqlite3_index_info *pIdxInfo ){ + (void)tab; pIdxInfo->estimatedCost = (double)500; pIdxInfo->estimatedRows = 500; return SQLITE_OK; } /* -** This following structure defines all the methods for the +** This following structure defines all the methods for the ** stmt virtual table. */ static sqlite3_module stmtModule = { @@ -222835,6 +269610,7 @@ static sqlite3_module stmtModule = { 0, /* xRelease */ 0, /* xRollbackTo */ 0, /* xShadowName */ + 0 /* xIntegrity */ }; #endif /* SQLITE_OMIT_VIRTUALTABLE */ @@ -222852,8 +269628,8 @@ SQLITE_PRIVATE int sqlite3StmtVtabInit(sqlite3 *db){ __declspec(dllexport) #endif SQLITE_API int sqlite3_stmt_init( - sqlite3 *db, - char **pzErrMsg, + sqlite3 *db, + char **pzErrMsg, const sqlite3_api_routines *pApi ){ int rc = SQLITE_OK; @@ -222867,10 +269643,7 @@ SQLITE_API int sqlite3_stmt_init( #endif /* !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_STMTVTAB) */ /************** End of stmt.c ************************************************/ -#if __LINE__!=222870 -#undef SQLITE_SOURCE_ID -#define SQLITE_SOURCE_ID "2019-04-16 19:49:53 884b4b7e502b4e991677b53971277adfaf0a04a284f8e483e2553d0f8315alt2" -#endif /* Return the source-id for this library */ SQLITE_API const char *sqlite3_sourceid(void){ return SQLITE_SOURCE_ID; } +#endif /* SQLITE_AMALGAMATION */ /************************** End of sqlite3.c ******************************/ diff --git a/src/common/dep/zlib/README b/src/common/dep/zlib/README index 51106de4..2b1e6f36 100644 --- a/src/common/dep/zlib/README +++ b/src/common/dep/zlib/README @@ -1,10 +1,10 @@ ZLIB DATA COMPRESSION LIBRARY -zlib 1.2.11 is a general purpose data compression library. All the code is -thread safe. The data format used by the zlib library is described by RFCs -(Request for Comments) 1950 to 1952 in the files -http://tools.ietf.org/html/rfc1950 (zlib format), rfc1951 (deflate format) and -rfc1952 (gzip format). +zlib 1.3.2 is a general purpose data compression library. All the code is +thread safe (though see the FAQ for caveats). The data format used by the zlib +library is described by RFCs (Request for Comments) 1950 to 1952 at +https://datatracker.ietf.org/doc/html/rfc1950 (zlib format), rfc1951 (deflate +format) and rfc1952 (gzip format). All functions of the compression library are documented in the file zlib.h (volunteer to write man pages welcome, contact zlib@gzip.org). A usage example @@ -21,32 +21,31 @@ make_vms.com. Questions about zlib should be sent to , or to Gilles Vollant for the Windows DLL version. The zlib home page is -http://zlib.net/ . Before reporting a problem, please check this site to +https://zlib.net/ . Before reporting a problem, please check this site to verify that you have the latest version of zlib; otherwise get the latest version and check whether the problem still exists or not. -PLEASE read the zlib FAQ http://zlib.net/zlib_faq.html before asking for help. +PLEASE read the zlib FAQ https://zlib.net/zlib_faq.html before asking for help. Mark Nelson wrote an article about zlib for the Jan. 1997 issue of Dr. Dobb's Journal; a copy of the article is available at -http://marknelson.us/1997/01/01/zlib-engine/ . +https://zlib.net/nelson/ . -The changes made in version 1.2.11 are documented in the file ChangeLog. +The changes made in version 1.3.2 are documented in the file ChangeLog. Unsupported third party contributions are provided in directory contrib/ . -zlib is available in Java using the java.util.zip package, documented at -http://java.sun.com/developer/technicalArticles/Programming/compression/ . +zlib is available in Java using the java.util.zip package. Follow the API +Documentation link at: https://docs.oracle.com/search/?q=java.util.zip . -A Perl interface to zlib written by Paul Marquess is available -at CPAN (Comprehensive Perl Archive Network) sites, including -http://search.cpan.org/~pmqs/IO-Compress-Zlib/ . +A Perl interface to zlib and bzip2 written by Paul Marquess +can be found at https://github.com/pmqs/IO-Compress . A Python interface to zlib written by A.M. Kuchling is available in Python 1.5 and later versions, see -http://docs.python.org/library/zlib.html . +https://docs.python.org/3/library/zlib.html . -zlib is built into tcl: http://wiki.tcl.tk/4610 . +zlib is built into tcl: https://wiki.tcl-lang.org/page/zlib . An experimental package to read and write files in .zip format, written on top of zlib by Gilles Vollant , is available in the @@ -64,15 +63,13 @@ Notes for some targets: - zlib doesn't work with gcc 2.6.3 on a DEC 3000/300LX under OSF/1 2.1 it works when compiled with cc. -- On Digital Unix 4.0D (formely OSF/1) on AlphaServer, the cc option -std1 is +- On Digital Unix 4.0D (formerly OSF/1) on AlphaServer, the cc option -std1 is necessary to get gzprintf working correctly. This is done by configure. - zlib doesn't work on HP-UX 9.05 with some versions of /bin/cc. It works with other compilers. Use "make test" to check your compiler. -- gzdopen is not supported on RISCOS or BEOS. - -- For PalmOs, see http://palmzlib.sourceforge.net/ +- For PalmOs, see https://palmzlib.sourceforge.net/ Acknowledgments: @@ -84,7 +81,7 @@ Acknowledgments: Copyright notice: - (C) 1995-2017 Jean-loup Gailly and Mark Adler + (C) 1995-2026 Jean-loup Gailly and Mark Adler This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -108,7 +105,10 @@ Copyright notice: If you use the zlib library in a product, we would appreciate *not* receiving lengthy legal documents to sign. The sources are provided for free but without warranty of any kind. The library has been entirely written by Jean-loup -Gailly and Mark Adler; it does not include third-party code. +Gailly and Mark Adler; it does not include third-party code. We make all +contributions to and distributions of this project solely in our personal +capacity, and are not conveying any rights to any intellectual property of +any third parties. If you redistribute modified sources, we would appreciate that you include in the file ChangeLog history information documenting your changes. Please read diff --git a/src/common/dep/zlib/VENDOR.md b/src/common/dep/zlib/VENDOR.md new file mode 100644 index 00000000..977de79a --- /dev/null +++ b/src/common/dep/zlib/VENDOR.md @@ -0,0 +1,26 @@ +# zlib vendor record + +The embedded zlib source is **1.3.2**, imported without local patches from the +official release archive: + +- Source: `https://zlib.net/zlib132.zip` +- Archive SHA-256: `e8bf55f3017aa181690990cb58a994e77885da140609fc8f94abe9b65d2cae28` + +The native `salamand` project compiles the embedded sources directly; it does +not load a system zlib DLL. Keep the complete in-memory API source set in the +project, including `infback.c` and `uncompr.c`, so headers and implementation +remain one verified release. + +## Compatibility and update cadence + +`tools/test-zlib-compatibility.ps1` builds the exact vendored C sources with +the Visual Studio developer environment. It verifies the 1.2.11 compression +vector in `tests/zlib-vectors/legacy-zlib-1.2.11.hex`, its decompression, and +rejection of the retained malformed streams in the same directory. The pull +request workflow runs this probe on x64. + +Review zlib in the first maintenance window of every calendar quarter and +immediately after an upstream security advisory or release. Each upgrade must +record its source URL and SHA-256 here, refresh only intentional compatibility +vectors, retain existing corrupt-input regression files, run the probe, and +build the native project on every supported platform. diff --git a/src/common/dep/zlib/adler32.c b/src/common/dep/zlib/adler32.c index d0be4380..04b81d29 100644 --- a/src/common/dep/zlib/adler32.c +++ b/src/common/dep/zlib/adler32.c @@ -7,8 +7,6 @@ #include "zutil.h" -local uLong adler32_combine_ OF((uLong adler1, uLong adler2, z_off64_t len2)); - #define BASE 65521U /* largest prime smaller than 65536 */ #define NMAX 5552 /* NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 */ @@ -60,11 +58,7 @@ local uLong adler32_combine_ OF((uLong adler1, uLong adler2, z_off64_t len2)); #endif /* ========================================================================= */ -uLong ZEXPORT adler32_z(adler, buf, len) - uLong adler; - const Bytef *buf; - z_size_t len; -{ +uLong ZEXPORT adler32_z(uLong adler, const Bytef *buf, z_size_t len) { unsigned long sum2; unsigned n; @@ -131,20 +125,12 @@ uLong ZEXPORT adler32_z(adler, buf, len) } /* ========================================================================= */ -uLong ZEXPORT adler32(adler, buf, len) - uLong adler; - const Bytef *buf; - uInt len; -{ +uLong ZEXPORT adler32(uLong adler, const Bytef *buf, uInt len) { return adler32_z(adler, buf, len); } /* ========================================================================= */ -local uLong adler32_combine_(adler1, adler2, len2) - uLong adler1; - uLong adler2; - z_off64_t len2; -{ +local uLong adler32_combine_(uLong adler1, uLong adler2, z_off64_t len2) { unsigned long sum1; unsigned long sum2; unsigned rem; @@ -169,18 +155,10 @@ local uLong adler32_combine_(adler1, adler2, len2) } /* ========================================================================= */ -uLong ZEXPORT adler32_combine(adler1, adler2, len2) - uLong adler1; - uLong adler2; - z_off_t len2; -{ +uLong ZEXPORT adler32_combine(uLong adler1, uLong adler2, z_off_t len2) { return adler32_combine_(adler1, adler2, len2); } -uLong ZEXPORT adler32_combine64(adler1, adler2, len2) - uLong adler1; - uLong adler2; - z_off64_t len2; -{ +uLong ZEXPORT adler32_combine64(uLong adler1, uLong adler2, z_off64_t len2) { return adler32_combine_(adler1, adler2, len2); } diff --git a/src/common/dep/zlib/compress.c b/src/common/dep/zlib/compress.c index e2db404a..bd74b948 100644 --- a/src/common/dep/zlib/compress.c +++ b/src/common/dep/zlib/compress.c @@ -1,5 +1,5 @@ /* compress.c -- compress a memory buffer - * Copyright (C) 1995-2005, 2014, 2016 Jean-loup Gailly, Mark Adler + * Copyright (C) 1995-2026 Jean-loup Gailly, Mark Adler * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -18,18 +18,19 @@ compress2 returns Z_OK if success, Z_MEM_ERROR if there was not enough memory, Z_BUF_ERROR if there was not enough room in the output buffer, Z_STREAM_ERROR if the level parameter is invalid. + + The _z versions of the functions take size_t length arguments. */ -int ZEXPORT compress2 (dest, destLen, source, sourceLen, level) - Bytef *dest; - uLongf *destLen; - const Bytef *source; - uLong sourceLen; - int level; -{ +int ZEXPORT compress2_z(Bytef *dest, z_size_t *destLen, const Bytef *source, + z_size_t sourceLen, int level) { z_stream stream; int err; const uInt max = (uInt)-1; - uLong left; + z_size_t left; + + if ((sourceLen > 0 && source == NULL) || + destLen == NULL || (*destLen > 0 && dest == NULL)) + return Z_STREAM_ERROR; left = *destLen; *destLen = 0; @@ -48,29 +49,38 @@ int ZEXPORT compress2 (dest, destLen, source, sourceLen, level) do { if (stream.avail_out == 0) { - stream.avail_out = left > (uLong)max ? max : (uInt)left; + stream.avail_out = left > (z_size_t)max ? max : (uInt)left; left -= stream.avail_out; } if (stream.avail_in == 0) { - stream.avail_in = sourceLen > (uLong)max ? max : (uInt)sourceLen; + stream.avail_in = sourceLen > (z_size_t)max ? max : + (uInt)sourceLen; sourceLen -= stream.avail_in; } err = deflate(&stream, sourceLen ? Z_NO_FLUSH : Z_FINISH); } while (err == Z_OK); - *destLen = stream.total_out; + *destLen = (z_size_t)(stream.next_out - dest); deflateEnd(&stream); return err == Z_STREAM_END ? Z_OK : err; } - +int ZEXPORT compress2(Bytef *dest, uLongf *destLen, const Bytef *source, + uLong sourceLen, int level) { + int ret; + z_size_t got = *destLen; + ret = compress2_z(dest, &got, source, sourceLen, level); + *destLen = (uLong)got; + return ret; +} /* =========================================================================== */ -int ZEXPORT compress (dest, destLen, source, sourceLen) - Bytef *dest; - uLongf *destLen; - const Bytef *source; - uLong sourceLen; -{ +int ZEXPORT compress_z(Bytef *dest, z_size_t *destLen, const Bytef *source, + z_size_t sourceLen) { + return compress2_z(dest, destLen, source, sourceLen, + Z_DEFAULT_COMPRESSION); +} +int ZEXPORT compress(Bytef *dest, uLongf *destLen, const Bytef *source, + uLong sourceLen) { return compress2(dest, destLen, source, sourceLen, Z_DEFAULT_COMPRESSION); } @@ -78,9 +88,12 @@ int ZEXPORT compress (dest, destLen, source, sourceLen) If the default memLevel or windowBits for deflateInit() is changed, then this function needs to be updated. */ -uLong ZEXPORT compressBound (sourceLen) - uLong sourceLen; -{ - return sourceLen + (sourceLen >> 12) + (sourceLen >> 14) + - (sourceLen >> 25) + 13; +z_size_t ZEXPORT compressBound_z(z_size_t sourceLen) { + z_size_t bound = sourceLen + (sourceLen >> 12) + (sourceLen >> 14) + + (sourceLen >> 25) + 13; + return bound < sourceLen ? (z_size_t)-1 : bound; +} +uLong ZEXPORT compressBound(uLong sourceLen) { + z_size_t bound = compressBound_z(sourceLen); + return (uLong)bound != bound ? (uLong)-1 : (uLong)bound; } diff --git a/src/common/dep/zlib/crc32.c b/src/common/dep/zlib/crc32.c index 9580440c..d9ade515 100644 --- a/src/common/dep/zlib/crc32.c +++ b/src/common/dep/zlib/crc32.c @@ -1,12 +1,10 @@ /* crc32.c -- compute the CRC-32 of a data stream - * Copyright (C) 1995-2006, 2010, 2011, 2012, 2016 Mark Adler + * Copyright (C) 1995-2026 Mark Adler * For conditions of distribution and use, see copyright notice in zlib.h * - * Thanks to Rodney Brown for his contribution of faster - * CRC methods: exclusive-oring 32 bits of data at a time, and pre-computing - * tables for updating the shift register in one step with three exclusive-ors - * instead of four steps with four exclusive-ors. This results in about a - * factor of two increase in speed on a Power PC G4 (PPC7455) using gcc -O3. + * This interleaved implementation of a CRC makes use of pipelined multiple + * arithmetic-logic units, commonly found in modern CPU cores. It is due to + * Kadatch and Jenkins (2010). See doc/crc-doc.1.0.pdf in this distribution. */ /* @(#) $Id$ */ @@ -14,429 +12,972 @@ /* Note on the use of DYNAMIC_CRC_TABLE: there is no mutex or semaphore protection on the static variables used to control the first-use generation - of the crc tables. Therefore, if you #define DYNAMIC_CRC_TABLE, you should + of the crc tables. Therefore, if you #define DYNAMIC_CRC_TABLE, you should first call get_crc_table() to initialize the tables before allowing more than one thread to use crc32(). - DYNAMIC_CRC_TABLE and MAKECRCH can be #defined to write out crc32.h. + MAKECRCH can be #defined to write out crc32.h. A main() routine is also + produced, so that this one source file can be compiled to an executable. */ #ifdef MAKECRCH # include # ifndef DYNAMIC_CRC_TABLE # define DYNAMIC_CRC_TABLE -# endif /* !DYNAMIC_CRC_TABLE */ -#endif /* MAKECRCH */ +# endif +#endif +#ifdef DYNAMIC_CRC_TABLE +# define Z_ONCE +#endif + +#include "zutil.h" /* for Z_U4, Z_U8, z_crc_t, and FAR definitions */ + +#ifdef HAVE_S390X_VX +# include "contrib/crc32vx/crc32_vx_hooks.h" +#endif -#include "zutil.h" /* for STDC and FAR definitions */ + /* + A CRC of a message is computed on N braids of words in the message, where + each word consists of W bytes (4 or 8). If N is 3, for example, then three + running sparse CRCs are calculated respectively on each braid, at these + indices in the array of words: 0, 3, 6, ..., 1, 4, 7, ..., and 2, 5, 8, ... + This is done starting at a word boundary, and continues until as many blocks + of N * W bytes as are available have been processed. The results are combined + into a single CRC at the end. For this code, N must be in the range 1..6 and + W must be 4 or 8. The upper limit on N can be increased if desired by adding + more #if blocks, extending the patterns apparent in the code. In addition, + crc32.h would need to be regenerated, if the maximum N value is increased. + + N and W are chosen empirically by benchmarking the execution time on a given + processor. The choices for N and W below were based on testing on Intel Kaby + Lake i7, AMD Ryzen 7, ARM Cortex-A57, Sparc64-VII, PowerPC POWER9, and MIPS64 + Octeon II processors. The Intel, AMD, and ARM processors were all fastest + with N=5, W=8. The Sparc, PowerPC, and MIPS64 were all fastest at N=5, W=4. + They were all tested with either gcc or clang, all using the -O3 optimization + level. Your mileage may vary. + */ -/* Definitions for doing the crc four data bytes at a time. */ -#if !defined(NOBYFOUR) && defined(Z_U4) -# define BYFOUR +/* Define N */ +#ifdef Z_TESTN +# define N Z_TESTN +#else +# define N 5 #endif -#ifdef BYFOUR - local unsigned long crc32_little OF((unsigned long, - const unsigned char FAR *, z_size_t)); - local unsigned long crc32_big OF((unsigned long, - const unsigned char FAR *, z_size_t)); -# define TBLS 8 +#if N < 1 || N > 6 +# error N must be in 1..6 +#endif + +/* + z_crc_t must be at least 32 bits. z_word_t must be at least as long as + z_crc_t. It is assumed here that z_word_t is either 32 bits or 64 bits, and + that bytes are eight bits. + */ + +/* + Define W and the associated z_word_t type. If W is not defined, then a + braided calculation is not used, and the associated tables and code are not + compiled. + */ +#ifdef Z_TESTW +# if Z_TESTW-1 != -1 +# define W Z_TESTW +# endif #else -# define TBLS 1 -#endif /* BYFOUR */ +# ifdef MAKECRCH +# define W 8 /* required for MAKECRCH */ +# else +# if defined(__x86_64__) || defined(__aarch64__) +# define W 8 +# else +# define W 4 +# endif +# endif +#endif +#ifdef W +# if W == 8 && defined(Z_U8) + typedef Z_U8 z_word_t; +# elif defined(Z_U4) +# undef W +# define W 4 + typedef Z_U4 z_word_t; +# else +# undef W +# endif +#endif -/* Local functions for crc concatenation */ -local unsigned long gf2_matrix_times OF((unsigned long *mat, - unsigned long vec)); -local void gf2_matrix_square OF((unsigned long *square, unsigned long *mat)); -local uLong crc32_combine_ OF((uLong crc1, uLong crc2, z_off64_t len2)); +/* If available, use the ARM processor CRC32 instruction. */ +#if defined(__aarch64__) && defined(__ARM_FEATURE_CRC32) && \ + defined(W) && W == 8 +# define ARMCRC32 +#endif +#if defined(W) && (!defined(ARMCRC32) || defined(DYNAMIC_CRC_TABLE)) +/* + Swap the bytes in a z_word_t to convert between little and big endian. Any + self-respecting compiler will optimize this to a single machine byte-swap + instruction, if one is available. This assumes that word_t is either 32 bits + or 64 bits. + */ +local z_word_t byte_swap(z_word_t word) { +# if W == 8 + return + (word & 0xff00000000000000) >> 56 | + (word & 0xff000000000000) >> 40 | + (word & 0xff0000000000) >> 24 | + (word & 0xff00000000) >> 8 | + (word & 0xff000000) << 8 | + (word & 0xff0000) << 24 | + (word & 0xff00) << 40 | + (word & 0xff) << 56; +# else /* W == 4 */ + return + (word & 0xff000000) >> 24 | + (word & 0xff0000) >> 8 | + (word & 0xff00) << 8 | + (word & 0xff) << 24; +# endif +} +#endif #ifdef DYNAMIC_CRC_TABLE +/* ========================================================================= + * Table of powers of x for combining CRC-32s, filled in by make_crc_table() + * below. + */ + local z_crc_t FAR x2n_table[32]; +#else +/* ========================================================================= + * Tables for byte-wise and braided CRC-32 calculations, and a table of powers + * of x for combining CRC-32s, all made by make_crc_table(). + */ +# include "crc32.h" +#endif + +/* CRC polynomial. */ +#define POLY 0xedb88320 /* p(x) reflected, with x^32 implied */ + +/* + Return a(x) multiplied by b(x) modulo p(x), where p(x) is the CRC polynomial, + reflected. For speed, this requires that a not be zero. + */ +local uLong multmodp(uLong a, uLong b) { + uLong m, p; + + m = (uLong)1 << 31; + p = 0; + for (;;) { + if (a & m) { + p ^= b; + if ((a & (m - 1)) == 0) + break; + } + m >>= 1; + b = b & 1 ? (b >> 1) ^ POLY : b >> 1; + } + return p; +} -local volatile int crc_table_empty = 1; -local z_crc_t FAR crc_table[TBLS][256]; -local void make_crc_table OF((void)); +/* + Return x^(n * 2^k) modulo p(x). Requires that x2n_table[] has been + initialized. n must not be negative. + */ +local uLong x2nmodp(z_off64_t n, unsigned k) { + uLong p; + + p = (uLong)1 << 31; /* x^0 == 1 */ + while (n) { + if (n & 1) + p = multmodp(x2n_table[k & 31], p); + n >>= 1; + k++; + } + return p; +} + +#ifdef DYNAMIC_CRC_TABLE +/* ========================================================================= + * Build the tables for byte-wise and braided CRC-32 calculations, and a table + * of powers of x for combining CRC-32s. + */ +local z_crc_t FAR crc_table[256]; +#ifdef W + local z_word_t FAR crc_big_table[256]; + local z_crc_t FAR crc_braid_table[W][256]; + local z_word_t FAR crc_braid_big_table[W][256]; + local void braid(z_crc_t [][256], z_word_t [][256], int, int); +#endif #ifdef MAKECRCH - local void write_table OF((FILE *, const z_crc_t FAR *)); + local void write_table(FILE *, const z_crc_t FAR *, int); + local void write_table32hi(FILE *, const z_word_t FAR *, int); + local void write_table64(FILE *, const z_word_t FAR *, int); #endif /* MAKECRCH */ + +/* State for once(). */ +local z_once_t made = Z_ONCE_INIT; + /* Generate tables for a byte-wise 32-bit CRC calculation on the polynomial: 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. Polynomials over GF(2) are represented in binary, one bit per coefficient, - with the lowest powers in the most significant bit. Then adding polynomials + with the lowest powers in the most significant bit. Then adding polynomials is just exclusive-or, and multiplying a polynomial by x is a right shift by - one. If we call the above polynomial p, and represent a byte as the + one. If we call the above polynomial p, and represent a byte as the polynomial q, also with the lowest power in the most significant bit (so the - byte 0xb1 is the polynomial x^7+x^3+x+1), then the CRC is (q*x^32) mod p, + byte 0xb1 is the polynomial x^7+x^3+x^2+1), then the CRC is (q*x^32) mod p, where a mod b means the remainder after dividing a by b. This calculation is done using the shift-register method of multiplying and - taking the remainder. The register is initialized to zero, and for each + taking the remainder. The register is initialized to zero, and for each incoming bit, x^32 is added mod p to the register if the bit is a one (where - x^32 mod p is p+x^32 = x^26+...+1), and the register is multiplied mod p by - x (which is shifting right by one and adding x^32 mod p if the bit shifted - out is a one). We start with the highest power (least significant bit) of - q and repeat for all eight bits of q. - - The first table is simply the CRC of all possible eight bit values. This is - all the information needed to generate CRCs on data a byte at a time for all - combinations of CRC register values and incoming bytes. The remaining tables - allow for word-at-a-time CRC calculation for both big-endian and little- - endian machines, where a word is four bytes. -*/ -local void make_crc_table() -{ - z_crc_t c; - int n, k; - z_crc_t poly; /* polynomial exclusive-or pattern */ - /* terms of polynomial defining this crc (except x^32): */ - static volatile int first = 1; /* flag to limit concurrent making */ - static const unsigned char p[] = {0,1,2,4,5,7,8,10,11,12,16,22,23,26}; - - /* See if another task is already doing this (not thread-safe, but better - than nothing -- significantly reduces duration of vulnerability in - case the advice about DYNAMIC_CRC_TABLE is ignored) */ - if (first) { - first = 0; - - /* make exclusive-or pattern from polynomial (0xedb88320UL) */ - poly = 0; - for (n = 0; n < (int)(sizeof(p)/sizeof(unsigned char)); n++) - poly |= (z_crc_t)1 << (31 - p[n]); - - /* generate a crc for every 8-bit value */ - for (n = 0; n < 256; n++) { - c = (z_crc_t)n; - for (k = 0; k < 8; k++) - c = c & 1 ? poly ^ (c >> 1) : c >> 1; - crc_table[0][n] = c; - } - -#ifdef BYFOUR - /* generate crc for each value followed by one, two, and three zeros, - and then the byte reversal of those as well as the first table */ - for (n = 0; n < 256; n++) { - c = crc_table[0][n]; - crc_table[4][n] = ZSWAP32(c); - for (k = 1; k < 4; k++) { - c = crc_table[0][c & 0xff] ^ (c >> 8); - crc_table[k][n] = c; - crc_table[k + 4][n] = ZSWAP32(c); - } - } -#endif /* BYFOUR */ + x^32 mod p is p+x^32 = x^26+...+1), and the register is multiplied mod p by x + (which is shifting right by one and adding x^32 mod p if the bit shifted out + is a one). We start with the highest power (least significant bit) of q and + repeat for all eight bits of q. + + The table is simply the CRC of all possible eight bit values. This is all the + information needed to generate CRCs on data a byte at a time for all + combinations of CRC register values and incoming bytes. + */ - crc_table_empty = 0; - } - else { /* not first */ - /* wait for the other guy to finish (not efficient, but rare) */ - while (crc_table_empty) - ; +local void make_crc_table(void) { + unsigned i, j, n; + z_crc_t p; + + /* initialize the CRC of bytes tables */ + for (i = 0; i < 256; i++) { + p = i; + for (j = 0; j < 8; j++) + p = p & 1 ? (p >> 1) ^ POLY : p >> 1; + crc_table[i] = p; +#ifdef W + crc_big_table[i] = byte_swap(p); +#endif } + /* initialize the x^2^n mod p(x) table */ + p = (z_crc_t)1 << 30; /* x^1 */ + x2n_table[0] = p; + for (n = 1; n < 32; n++) + x2n_table[n] = p = (z_crc_t)multmodp(p, p); + +#ifdef W + /* initialize the braiding tables -- needs x2n_table[] */ + braid(crc_braid_table, crc_braid_big_table, N, W); +#endif + #ifdef MAKECRCH - /* write out CRC tables to crc32.h */ { + /* + The crc32.h header file contains tables for both 32-bit and 64-bit + z_word_t's, and so requires a 64-bit type be available. In that case, + z_word_t must be defined to be 64-bits. This code then also generates + and writes out the tables for the case that z_word_t is 32 bits. + */ +#if !defined(W) || W != 8 +# error Need a 64-bit integer type in order to generate crc32.h. +#endif FILE *out; + int k, n; + z_crc_t ltl[8][256]; + z_word_t big[8][256]; out = fopen("crc32.h", "w"); if (out == NULL) return; - fprintf(out, "/* crc32.h -- tables for rapid CRC calculation\n"); - fprintf(out, " * Generated automatically by crc32.c\n */\n\n"); - fprintf(out, "local const z_crc_t FAR "); - fprintf(out, "crc_table[TBLS][256] =\n{\n {\n"); - write_table(out, crc_table[0]); -# ifdef BYFOUR - fprintf(out, "#ifdef BYFOUR\n"); - for (k = 1; k < 8; k++) { - fprintf(out, " },\n {\n"); - write_table(out, crc_table[k]); + + /* write out little-endian CRC table to crc32.h */ + fprintf(out, + "/* crc32.h -- tables for rapid CRC calculation\n" + " * Generated automatically by crc32.c\n */\n" + "\n" + "local const z_crc_t FAR crc_table[] = {\n" + " "); + write_table(out, crc_table, 256); + fprintf(out, + "};\n"); + + /* write out big-endian CRC table for 64-bit z_word_t to crc32.h */ + fprintf(out, + "\n" + "#ifdef W\n" + "\n" + "#if W == 8\n" + "\n" + "local const z_word_t FAR crc_big_table[] = {\n" + " "); + write_table64(out, crc_big_table, 256); + fprintf(out, + "};\n"); + + /* write out big-endian CRC table for 32-bit z_word_t to crc32.h */ + fprintf(out, + "\n" + "#else /* W == 4 */\n" + "\n" + "local const z_word_t FAR crc_big_table[] = {\n" + " "); + write_table32hi(out, crc_big_table, 256); + fprintf(out, + "};\n" + "\n" + "#endif\n"); + + /* write out braid tables for each value of N */ + for (n = 1; n <= 6; n++) { + fprintf(out, + "\n" + "#if N == %d\n", n); + + /* compute braid tables for this N and 64-bit word_t */ + braid(ltl, big, n, 8); + + /* write out braid tables for 64-bit z_word_t to crc32.h */ + fprintf(out, + "\n" + "#if W == 8\n" + "\n" + "local const z_crc_t FAR crc_braid_table[][256] = {\n"); + for (k = 0; k < 8; k++) { + fprintf(out, " {"); + write_table(out, ltl[k], 256); + fprintf(out, "}%s", k < 7 ? ",\n" : ""); + } + fprintf(out, + "};\n" + "\n" + "local const z_word_t FAR crc_braid_big_table[][256] = {\n"); + for (k = 0; k < 8; k++) { + fprintf(out, " {"); + write_table64(out, big[k], 256); + fprintf(out, "}%s", k < 7 ? ",\n" : ""); + } + fprintf(out, + "};\n"); + + /* compute braid tables for this N and 32-bit word_t */ + braid(ltl, big, n, 4); + + /* write out braid tables for 32-bit z_word_t to crc32.h */ + fprintf(out, + "\n" + "#else /* W == 4 */\n" + "\n" + "local const z_crc_t FAR crc_braid_table[][256] = {\n"); + for (k = 0; k < 4; k++) { + fprintf(out, " {"); + write_table(out, ltl[k], 256); + fprintf(out, "}%s", k < 3 ? ",\n" : ""); + } + fprintf(out, + "};\n" + "\n" + "local const z_word_t FAR crc_braid_big_table[][256] = {\n"); + for (k = 0; k < 4; k++) { + fprintf(out, " {"); + write_table32hi(out, big[k], 256); + fprintf(out, "}%s", k < 3 ? ",\n" : ""); + } + fprintf(out, + "};\n" + "\n" + "#endif\n" + "\n" + "#endif\n"); } - fprintf(out, "#endif\n"); -# endif /* BYFOUR */ - fprintf(out, " }\n};\n"); + fprintf(out, + "\n" + "#endif\n"); + + /* write out zeros operator table to crc32.h */ + fprintf(out, + "\n" + "local const z_crc_t FAR x2n_table[] = {\n" + " "); + write_table(out, x2n_table, 32); + fprintf(out, + "};\n"); fclose(out); } #endif /* MAKECRCH */ } #ifdef MAKECRCH -local void write_table(out, table) - FILE *out; - const z_crc_t FAR *table; -{ + +/* + Write the 32-bit values in table[0..k-1] to out, five per line in + hexadecimal separated by commas. + */ +local void write_table(FILE *out, const z_crc_t FAR *table, int k) { int n; - for (n = 0; n < 256; n++) - fprintf(out, "%s0x%08lxUL%s", n % 5 ? "" : " ", + for (n = 0; n < k; n++) + fprintf(out, "%s0x%08lx%s", n == 0 || n % 5 ? "" : " ", (unsigned long)(table[n]), - n == 255 ? "\n" : (n % 5 == 4 ? ",\n" : ", ")); + n == k - 1 ? "" : (n % 5 == 4 ? ",\n" : ", ")); +} + +/* + Write the high 32-bits of each value in table[0..k-1] to out, five per line + in hexadecimal separated by commas. + */ +local void write_table32hi(FILE *out, const z_word_t FAR *table, int k) { + int n; + + for (n = 0; n < k; n++) + fprintf(out, "%s0x%08lx%s", n == 0 || n % 5 ? "" : " ", + (unsigned long)(table[n] >> 32), + n == k - 1 ? "" : (n % 5 == 4 ? ",\n" : ", ")); +} + +/* + Write the 64-bit values in table[0..k-1] to out, three per line in + hexadecimal separated by commas. This assumes that if there is a 64-bit + type, then there is also a long long integer type, and it is at least 64 + bits. If not, then the type cast and format string can be adjusted + accordingly. + */ +local void write_table64(FILE *out, const z_word_t FAR *table, int k) { + int n; + + for (n = 0; n < k; n++) + fprintf(out, "%s0x%016llx%s", n == 0 || n % 3 ? "" : " ", + (unsigned long long)(table[n]), + n == k - 1 ? "" : (n % 3 == 2 ? ",\n" : ", ")); +} + +/* Actually do the deed. */ +int main(void) { + make_crc_table(); + return 0; } + #endif /* MAKECRCH */ -#else /* !DYNAMIC_CRC_TABLE */ -/* ======================================================================== - * Tables of CRC-32s of all single-byte values, made by make_crc_table(). +#ifdef W +/* + Generate the little and big-endian braid tables for the given n and z_word_t + size w. Each array must have room for w blocks of 256 elements. */ -#include "crc32.h" +local void braid(z_crc_t ltl[][256], z_word_t big[][256], int n, int w) { + int k; + z_crc_t i, p, q; + for (k = 0; k < w; k++) { + p = (z_crc_t)x2nmodp((n * w + 3 - k) << 3, 0); + ltl[k][0] = 0; + big[w - 1 - k][0] = 0; + for (i = 1; i < 256; i++) { + ltl[k][i] = q = (z_crc_t)multmodp(i << 24, p); + big[w - 1 - k][i] = byte_swap(q); + } + } +} +#endif + #endif /* DYNAMIC_CRC_TABLE */ /* ========================================================================= - * This function can be used by asm versions of crc32() + * This function can be used by asm versions of crc32(), and to force the + * generation of the CRC tables in a threaded application. */ -const z_crc_t FAR * ZEXPORT get_crc_table() -{ +const z_crc_t FAR * ZEXPORT get_crc_table(void) { #ifdef DYNAMIC_CRC_TABLE - if (crc_table_empty) - make_crc_table(); + z_once(&made, make_crc_table); #endif /* DYNAMIC_CRC_TABLE */ return (const z_crc_t FAR *)crc_table; } -/* ========================================================================= */ -#define DO1 crc = crc_table[0][((int)crc ^ (*buf++)) & 0xff] ^ (crc >> 8) -#define DO8 DO1; DO1; DO1; DO1; DO1; DO1; DO1; DO1 +/* ========================================================================= + * Use ARM machine instructions if available. This will compute the CRC about + * ten times faster than the braided calculation. This code does not check for + * the presence of the CRC instruction at run time. __ARM_FEATURE_CRC32 will + * only be defined if the compilation specifies an ARM processor architecture + * that has the instructions. For example, compiling with -march=armv8.1-a or + * -march=armv8-a+crc, or -march=native if the compile machine has the crc32 + * instructions. + */ +#ifdef ARMCRC32 -/* ========================================================================= */ -unsigned long ZEXPORT crc32_z(crc, buf, len) - unsigned long crc; - const unsigned char FAR *buf; - z_size_t len; -{ - if (buf == Z_NULL) return 0UL; +/* + Constants empirically determined to maximize speed. These values are from + measurements on a Cortex-A57. Your mileage may vary. + */ +#define Z_BATCH 3990 /* number of words in a batch */ +#define Z_BATCH_ZEROS 0xa10d3d0c /* computed from Z_BATCH = 3990 */ +#define Z_BATCH_MIN 800 /* fewest words in a final batch */ + +uLong ZEXPORT crc32_z(uLong crc, const unsigned char FAR *buf, z_size_t len) { + uLong val; + z_word_t crc1, crc2; + const z_word_t *word; + z_word_t val0, val1, val2; + z_size_t last, last2, i; + z_size_t num; + + /* Return initial CRC, if requested. */ + if (buf == Z_NULL) return 0; #ifdef DYNAMIC_CRC_TABLE - if (crc_table_empty) - make_crc_table(); + z_once(&made, make_crc_table); #endif /* DYNAMIC_CRC_TABLE */ -#ifdef BYFOUR - if (sizeof(void *) == sizeof(ptrdiff_t)) { - z_crc_t endian; + /* Pre-condition the CRC */ + crc = (~crc) & 0xffffffff; - endian = 1; - if (*((unsigned char *)(&endian))) - return crc32_little(crc, buf, len); - else - return crc32_big(crc, buf, len); + /* Compute the CRC up to a word boundary. */ + while (len && ((z_size_t)buf & 7) != 0) { + len--; + val = *buf++; + __asm__ volatile("crc32b %w0, %w0, %w1" : "+r"(crc) : "r"(val)); } -#endif /* BYFOUR */ - crc = crc ^ 0xffffffffUL; - while (len >= 8) { - DO8; - len -= 8; + + /* Prepare to compute the CRC on full 64-bit words word[0..num-1]. */ + word = (z_word_t const *)buf; + num = len >> 3; + len &= 7; + + /* Do three interleaved CRCs to realize the throughput of one crc32x + instruction per cycle. Each CRC is calculated on Z_BATCH words. The + three CRCs are combined into a single CRC after each set of batches. */ + while (num >= 3 * Z_BATCH) { + crc1 = 0; + crc2 = 0; + for (i = 0; i < Z_BATCH; i++) { + val0 = word[i]; + val1 = word[i + Z_BATCH]; + val2 = word[i + 2 * Z_BATCH]; + __asm__ volatile("crc32x %w0, %w0, %x1" : "+r"(crc) : "r"(val0)); + __asm__ volatile("crc32x %w0, %w0, %x1" : "+r"(crc1) : "r"(val1)); + __asm__ volatile("crc32x %w0, %w0, %x1" : "+r"(crc2) : "r"(val2)); + } + word += 3 * Z_BATCH; + num -= 3 * Z_BATCH; + crc = multmodp(Z_BATCH_ZEROS, crc) ^ crc1; + crc = multmodp(Z_BATCH_ZEROS, crc) ^ crc2; } - if (len) do { - DO1; - } while (--len); - return crc ^ 0xffffffffUL; -} -/* ========================================================================= */ -unsigned long ZEXPORT crc32(crc, buf, len) - unsigned long crc; - const unsigned char FAR *buf; - uInt len; -{ - return crc32_z(crc, buf, len); + /* Do one last smaller batch with the remaining words, if there are enough + to pay for the combination of CRCs. */ + last = num / 3; + if (last >= Z_BATCH_MIN) { + last2 = last << 1; + crc1 = 0; + crc2 = 0; + for (i = 0; i < last; i++) { + val0 = word[i]; + val1 = word[i + last]; + val2 = word[i + last2]; + __asm__ volatile("crc32x %w0, %w0, %x1" : "+r"(crc) : "r"(val0)); + __asm__ volatile("crc32x %w0, %w0, %x1" : "+r"(crc1) : "r"(val1)); + __asm__ volatile("crc32x %w0, %w0, %x1" : "+r"(crc2) : "r"(val2)); + } + word += 3 * last; + num -= 3 * last; + val = x2nmodp((int)last, 6); + crc = multmodp(val, crc) ^ crc1; + crc = multmodp(val, crc) ^ crc2; + } + + /* Compute the CRC on any remaining words. */ + for (i = 0; i < num; i++) { + val0 = word[i]; + __asm__ volatile("crc32x %w0, %w0, %x1" : "+r"(crc) : "r"(val0)); + } + word += num; + + /* Complete the CRC on any remaining bytes. */ + buf = (const unsigned char FAR *)word; + while (len) { + len--; + val = *buf++; + __asm__ volatile("crc32b %w0, %w0, %w1" : "+r"(crc) : "r"(val)); + } + + /* Return the CRC, post-conditioned. */ + return crc ^ 0xffffffff; } -#ifdef BYFOUR +#else + +#ifdef W /* - This BYFOUR code accesses the passed unsigned char * buffer with a 32-bit - integer pointer type. This violates the strict aliasing rule, where a - compiler can assume, for optimization purposes, that two pointers to - fundamentally different types won't ever point to the same memory. This can - manifest as a problem only if one of the pointers is written to. This code - only reads from those pointers. So long as this code remains isolated in - this compilation unit, there won't be a problem. For this reason, this code - should not be copied and pasted into a compilation unit in which other code - writes to the buffer that is passed to these routines. + Return the CRC of the W bytes in the word_t data, taking the + least-significant byte of the word as the first byte of data, without any pre + or post conditioning. This is used to combine the CRCs of each braid. */ +local z_crc_t crc_word(z_word_t data) { + int k; + for (k = 0; k < W; k++) + data = (data >> 8) ^ crc_table[data & 0xff]; + return (z_crc_t)data; +} -/* ========================================================================= */ -#define DOLIT4 c ^= *buf4++; \ - c = crc_table[3][c & 0xff] ^ crc_table[2][(c >> 8) & 0xff] ^ \ - crc_table[1][(c >> 16) & 0xff] ^ crc_table[0][c >> 24] -#define DOLIT32 DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4 +local z_word_t crc_word_big(z_word_t data) { + int k; + for (k = 0; k < W; k++) + data = (data << 8) ^ + crc_big_table[(data >> ((W - 1) << 3)) & 0xff]; + return data; +} + +#endif /* ========================================================================= */ -local unsigned long crc32_little(crc, buf, len) - unsigned long crc; - const unsigned char FAR *buf; - z_size_t len; -{ - register z_crc_t c; - register const z_crc_t FAR *buf4; - - c = (z_crc_t)crc; - c = ~c; - while (len && ((ptrdiff_t)buf & 3)) { - c = crc_table[0][(c ^ *buf++) & 0xff] ^ (c >> 8); - len--; - } +uLong ZEXPORT crc32_z(uLong crc, const unsigned char FAR *buf, z_size_t len) { + /* Return initial CRC, if requested. */ + if (buf == Z_NULL) return 0; - buf4 = (const z_crc_t FAR *)(const void FAR *)buf; - while (len >= 32) { - DOLIT32; - len -= 32; - } - while (len >= 4) { - DOLIT4; - len -= 4; - } - buf = (const unsigned char FAR *)buf4; +#ifdef DYNAMIC_CRC_TABLE + z_once(&made, make_crc_table); +#endif /* DYNAMIC_CRC_TABLE */ - if (len) do { - c = crc_table[0][(c ^ *buf++) & 0xff] ^ (c >> 8); - } while (--len); - c = ~c; - return (unsigned long)c; -} + /* Pre-condition the CRC */ + crc = (~crc) & 0xffffffff; -/* ========================================================================= */ -#define DOBIG4 c ^= *buf4++; \ - c = crc_table[4][c & 0xff] ^ crc_table[5][(c >> 8) & 0xff] ^ \ - crc_table[6][(c >> 16) & 0xff] ^ crc_table[7][c >> 24] -#define DOBIG32 DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4 +#ifdef W -/* ========================================================================= */ -local unsigned long crc32_big(crc, buf, len) - unsigned long crc; - const unsigned char FAR *buf; - z_size_t len; -{ - register z_crc_t c; - register const z_crc_t FAR *buf4; - - c = ZSWAP32((z_crc_t)crc); - c = ~c; - while (len && ((ptrdiff_t)buf & 3)) { - c = crc_table[4][(c >> 24) ^ *buf++] ^ (c << 8); - len--; + /* If provided enough bytes, do a braided CRC calculation. */ + if (len >= N * W + W - 1) { + z_size_t blks; + z_word_t const *words; + unsigned endian; + int k; + + /* Compute the CRC up to a z_word_t boundary. */ + while (len && ((z_size_t)buf & (W - 1)) != 0) { + len--; + crc = (crc >> 8) ^ crc_table[(crc ^ *buf++) & 0xff]; + } + + /* Compute the CRC on as many N z_word_t blocks as are available. */ + blks = len / (N * W); + len -= blks * N * W; + words = (z_word_t const *)buf; + + /* Do endian check at execution time instead of compile time, since ARM + processors can change the endianness at execution time. If the + compiler knows what the endianness will be, it can optimize out the + check and the unused branch. */ + endian = 1; + if (*(unsigned char *)&endian) { + /* Little endian. */ + + z_crc_t crc0; + z_word_t word0; +#if N > 1 + z_crc_t crc1; + z_word_t word1; +#if N > 2 + z_crc_t crc2; + z_word_t word2; +#if N > 3 + z_crc_t crc3; + z_word_t word3; +#if N > 4 + z_crc_t crc4; + z_word_t word4; +#if N > 5 + z_crc_t crc5; + z_word_t word5; +#endif +#endif +#endif +#endif +#endif + + /* Initialize the CRC for each braid. */ + crc0 = crc; +#if N > 1 + crc1 = 0; +#if N > 2 + crc2 = 0; +#if N > 3 + crc3 = 0; +#if N > 4 + crc4 = 0; +#if N > 5 + crc5 = 0; +#endif +#endif +#endif +#endif +#endif + + /* + Process the first blks-1 blocks, computing the CRCs on each braid + independently. + */ + while (--blks) { + /* Load the word for each braid into registers. */ + word0 = crc0 ^ words[0]; +#if N > 1 + word1 = crc1 ^ words[1]; +#if N > 2 + word2 = crc2 ^ words[2]; +#if N > 3 + word3 = crc3 ^ words[3]; +#if N > 4 + word4 = crc4 ^ words[4]; +#if N > 5 + word5 = crc5 ^ words[5]; +#endif +#endif +#endif +#endif +#endif + words += N; + + /* Compute and update the CRC for each word. The loop should + get unrolled. */ + crc0 = crc_braid_table[0][word0 & 0xff]; +#if N > 1 + crc1 = crc_braid_table[0][word1 & 0xff]; +#if N > 2 + crc2 = crc_braid_table[0][word2 & 0xff]; +#if N > 3 + crc3 = crc_braid_table[0][word3 & 0xff]; +#if N > 4 + crc4 = crc_braid_table[0][word4 & 0xff]; +#if N > 5 + crc5 = crc_braid_table[0][word5 & 0xff]; +#endif +#endif +#endif +#endif +#endif + for (k = 1; k < W; k++) { + crc0 ^= crc_braid_table[k][(word0 >> (k << 3)) & 0xff]; +#if N > 1 + crc1 ^= crc_braid_table[k][(word1 >> (k << 3)) & 0xff]; +#if N > 2 + crc2 ^= crc_braid_table[k][(word2 >> (k << 3)) & 0xff]; +#if N > 3 + crc3 ^= crc_braid_table[k][(word3 >> (k << 3)) & 0xff]; +#if N > 4 + crc4 ^= crc_braid_table[k][(word4 >> (k << 3)) & 0xff]; +#if N > 5 + crc5 ^= crc_braid_table[k][(word5 >> (k << 3)) & 0xff]; +#endif +#endif +#endif +#endif +#endif + } + } + + /* + Process the last block, combining the CRCs of the N braids at the + same time. + */ + crc = crc_word(crc0 ^ words[0]); +#if N > 1 + crc = crc_word(crc1 ^ words[1] ^ crc); +#if N > 2 + crc = crc_word(crc2 ^ words[2] ^ crc); +#if N > 3 + crc = crc_word(crc3 ^ words[3] ^ crc); +#if N > 4 + crc = crc_word(crc4 ^ words[4] ^ crc); +#if N > 5 + crc = crc_word(crc5 ^ words[5] ^ crc); +#endif +#endif +#endif +#endif +#endif + words += N; + } + else { + /* Big endian. */ + + z_word_t crc0, word0, comb; +#if N > 1 + z_word_t crc1, word1; +#if N > 2 + z_word_t crc2, word2; +#if N > 3 + z_word_t crc3, word3; +#if N > 4 + z_word_t crc4, word4; +#if N > 5 + z_word_t crc5, word5; +#endif +#endif +#endif +#endif +#endif + + /* Initialize the CRC for each braid. */ + crc0 = byte_swap(crc); +#if N > 1 + crc1 = 0; +#if N > 2 + crc2 = 0; +#if N > 3 + crc3 = 0; +#if N > 4 + crc4 = 0; +#if N > 5 + crc5 = 0; +#endif +#endif +#endif +#endif +#endif + + /* + Process the first blks-1 blocks, computing the CRCs on each braid + independently. + */ + while (--blks) { + /* Load the word for each braid into registers. */ + word0 = crc0 ^ words[0]; +#if N > 1 + word1 = crc1 ^ words[1]; +#if N > 2 + word2 = crc2 ^ words[2]; +#if N > 3 + word3 = crc3 ^ words[3]; +#if N > 4 + word4 = crc4 ^ words[4]; +#if N > 5 + word5 = crc5 ^ words[5]; +#endif +#endif +#endif +#endif +#endif + words += N; + + /* Compute and update the CRC for each word. The loop should + get unrolled. */ + crc0 = crc_braid_big_table[0][word0 & 0xff]; +#if N > 1 + crc1 = crc_braid_big_table[0][word1 & 0xff]; +#if N > 2 + crc2 = crc_braid_big_table[0][word2 & 0xff]; +#if N > 3 + crc3 = crc_braid_big_table[0][word3 & 0xff]; +#if N > 4 + crc4 = crc_braid_big_table[0][word4 & 0xff]; +#if N > 5 + crc5 = crc_braid_big_table[0][word5 & 0xff]; +#endif +#endif +#endif +#endif +#endif + for (k = 1; k < W; k++) { + crc0 ^= crc_braid_big_table[k][(word0 >> (k << 3)) & 0xff]; +#if N > 1 + crc1 ^= crc_braid_big_table[k][(word1 >> (k << 3)) & 0xff]; +#if N > 2 + crc2 ^= crc_braid_big_table[k][(word2 >> (k << 3)) & 0xff]; +#if N > 3 + crc3 ^= crc_braid_big_table[k][(word3 >> (k << 3)) & 0xff]; +#if N > 4 + crc4 ^= crc_braid_big_table[k][(word4 >> (k << 3)) & 0xff]; +#if N > 5 + crc5 ^= crc_braid_big_table[k][(word5 >> (k << 3)) & 0xff]; +#endif +#endif +#endif +#endif +#endif + } + } + + /* + Process the last block, combining the CRCs of the N braids at the + same time. + */ + comb = crc_word_big(crc0 ^ words[0]); +#if N > 1 + comb = crc_word_big(crc1 ^ words[1] ^ comb); +#if N > 2 + comb = crc_word_big(crc2 ^ words[2] ^ comb); +#if N > 3 + comb = crc_word_big(crc3 ^ words[3] ^ comb); +#if N > 4 + comb = crc_word_big(crc4 ^ words[4] ^ comb); +#if N > 5 + comb = crc_word_big(crc5 ^ words[5] ^ comb); +#endif +#endif +#endif +#endif +#endif + words += N; + crc = byte_swap(comb); + } + + /* + Update the pointer to the remaining bytes to process. + */ + buf = (unsigned char const *)words; } - buf4 = (const z_crc_t FAR *)(const void FAR *)buf; - while (len >= 32) { - DOBIG32; - len -= 32; +#endif /* W */ + + /* Complete the computation of the CRC on any remaining bytes. */ + while (len >= 8) { + len -= 8; + crc = (crc >> 8) ^ crc_table[(crc ^ *buf++) & 0xff]; + crc = (crc >> 8) ^ crc_table[(crc ^ *buf++) & 0xff]; + crc = (crc >> 8) ^ crc_table[(crc ^ *buf++) & 0xff]; + crc = (crc >> 8) ^ crc_table[(crc ^ *buf++) & 0xff]; + crc = (crc >> 8) ^ crc_table[(crc ^ *buf++) & 0xff]; + crc = (crc >> 8) ^ crc_table[(crc ^ *buf++) & 0xff]; + crc = (crc >> 8) ^ crc_table[(crc ^ *buf++) & 0xff]; + crc = (crc >> 8) ^ crc_table[(crc ^ *buf++) & 0xff]; } - while (len >= 4) { - DOBIG4; - len -= 4; + while (len) { + len--; + crc = (crc >> 8) ^ crc_table[(crc ^ *buf++) & 0xff]; } - buf = (const unsigned char FAR *)buf4; - if (len) do { - c = crc_table[4][(c >> 24) ^ *buf++] ^ (c << 8); - } while (--len); - c = ~c; - return (unsigned long)(ZSWAP32(c)); + /* Return the CRC, post-conditioned. */ + return crc ^ 0xffffffff; } -#endif /* BYFOUR */ - -#define GF2_DIM 32 /* dimension of GF(2) vectors (length of CRC) */ +#endif /* ========================================================================= */ -local unsigned long gf2_matrix_times(mat, vec) - unsigned long *mat; - unsigned long vec; -{ - unsigned long sum; - - sum = 0; - while (vec) { - if (vec & 1) - sum ^= *mat; - vec >>= 1; - mat++; - } - return sum; +uLong ZEXPORT crc32(uLong crc, const unsigned char FAR *buf, uInt len) { + #ifdef HAVE_S390X_VX + return crc32_z_hook(crc, buf, len); + #endif + return crc32_z(crc, buf, len); } /* ========================================================================= */ -local void gf2_matrix_square(square, mat) - unsigned long *square; - unsigned long *mat; -{ - int n; - - for (n = 0; n < GF2_DIM; n++) - square[n] = gf2_matrix_times(mat, mat[n]); +uLong ZEXPORT crc32_combine_gen64(z_off64_t len2) { + if (len2 < 0) + return 0; +#ifdef DYNAMIC_CRC_TABLE + z_once(&made, make_crc_table); +#endif /* DYNAMIC_CRC_TABLE */ + return x2nmodp(len2, 3); } /* ========================================================================= */ -local uLong crc32_combine_(crc1, crc2, len2) - uLong crc1; - uLong crc2; - z_off64_t len2; -{ - int n; - unsigned long row; - unsigned long even[GF2_DIM]; /* even-power-of-two zeros operator */ - unsigned long odd[GF2_DIM]; /* odd-power-of-two zeros operator */ - - /* degenerate case (also disallow negative lengths) */ - if (len2 <= 0) - return crc1; - - /* put operator for one zero bit in odd */ - odd[0] = 0xedb88320UL; /* CRC-32 polynomial */ - row = 1; - for (n = 1; n < GF2_DIM; n++) { - odd[n] = row; - row <<= 1; - } +uLong ZEXPORT crc32_combine_gen(z_off_t len2) { + return crc32_combine_gen64((z_off64_t)len2); +} - /* put operator for two zero bits in even */ - gf2_matrix_square(even, odd); - - /* put operator for four zero bits in odd */ - gf2_matrix_square(odd, even); - - /* apply len2 zeros to crc1 (first square will put the operator for one - zero byte, eight zero bits, in even) */ - do { - /* apply zeros operator for this bit of len2 */ - gf2_matrix_square(even, odd); - if (len2 & 1) - crc1 = gf2_matrix_times(even, crc1); - len2 >>= 1; - - /* if no more bits set, then done */ - if (len2 == 0) - break; - - /* another iteration of the loop with odd and even swapped */ - gf2_matrix_square(odd, even); - if (len2 & 1) - crc1 = gf2_matrix_times(odd, crc1); - len2 >>= 1; - - /* if no more bits set, then done */ - } while (len2 != 0); - - /* return combined crc */ - crc1 ^= crc2; - return crc1; +/* ========================================================================= */ +uLong ZEXPORT crc32_combine_op(uLong crc1, uLong crc2, uLong op) { + if (op == 0) + return 0; + return multmodp(op, crc1 & 0xffffffff) ^ (crc2 & 0xffffffff); } /* ========================================================================= */ -uLong ZEXPORT crc32_combine(crc1, crc2, len2) - uLong crc1; - uLong crc2; - z_off_t len2; -{ - return crc32_combine_(crc1, crc2, len2); +uLong ZEXPORT crc32_combine64(uLong crc1, uLong crc2, z_off64_t len2) { + return crc32_combine_op(crc1, crc2, crc32_combine_gen64(len2)); } -uLong ZEXPORT crc32_combine64(crc1, crc2, len2) - uLong crc1; - uLong crc2; - z_off64_t len2; -{ - return crc32_combine_(crc1, crc2, len2); +/* ========================================================================= */ +uLong ZEXPORT crc32_combine(uLong crc1, uLong crc2, z_off_t len2) { + return crc32_combine64(crc1, crc2, (z_off64_t)len2); } diff --git a/src/common/dep/zlib/crc32.h b/src/common/dep/zlib/crc32.h index 9e0c7781..137df68d 100644 --- a/src/common/dep/zlib/crc32.h +++ b/src/common/dep/zlib/crc32.h @@ -2,440 +2,9445 @@ * Generated automatically by crc32.c */ -local const z_crc_t FAR crc_table[TBLS][256] = -{ - { - 0x00000000UL, 0x77073096UL, 0xee0e612cUL, 0x990951baUL, 0x076dc419UL, - 0x706af48fUL, 0xe963a535UL, 0x9e6495a3UL, 0x0edb8832UL, 0x79dcb8a4UL, - 0xe0d5e91eUL, 0x97d2d988UL, 0x09b64c2bUL, 0x7eb17cbdUL, 0xe7b82d07UL, - 0x90bf1d91UL, 0x1db71064UL, 0x6ab020f2UL, 0xf3b97148UL, 0x84be41deUL, - 0x1adad47dUL, 0x6ddde4ebUL, 0xf4d4b551UL, 0x83d385c7UL, 0x136c9856UL, - 0x646ba8c0UL, 0xfd62f97aUL, 0x8a65c9ecUL, 0x14015c4fUL, 0x63066cd9UL, - 0xfa0f3d63UL, 0x8d080df5UL, 0x3b6e20c8UL, 0x4c69105eUL, 0xd56041e4UL, - 0xa2677172UL, 0x3c03e4d1UL, 0x4b04d447UL, 0xd20d85fdUL, 0xa50ab56bUL, - 0x35b5a8faUL, 0x42b2986cUL, 0xdbbbc9d6UL, 0xacbcf940UL, 0x32d86ce3UL, - 0x45df5c75UL, 0xdcd60dcfUL, 0xabd13d59UL, 0x26d930acUL, 0x51de003aUL, - 0xc8d75180UL, 0xbfd06116UL, 0x21b4f4b5UL, 0x56b3c423UL, 0xcfba9599UL, - 0xb8bda50fUL, 0x2802b89eUL, 0x5f058808UL, 0xc60cd9b2UL, 0xb10be924UL, - 0x2f6f7c87UL, 0x58684c11UL, 0xc1611dabUL, 0xb6662d3dUL, 0x76dc4190UL, - 0x01db7106UL, 0x98d220bcUL, 0xefd5102aUL, 0x71b18589UL, 0x06b6b51fUL, - 0x9fbfe4a5UL, 0xe8b8d433UL, 0x7807c9a2UL, 0x0f00f934UL, 0x9609a88eUL, - 0xe10e9818UL, 0x7f6a0dbbUL, 0x086d3d2dUL, 0x91646c97UL, 0xe6635c01UL, - 0x6b6b51f4UL, 0x1c6c6162UL, 0x856530d8UL, 0xf262004eUL, 0x6c0695edUL, - 0x1b01a57bUL, 0x8208f4c1UL, 0xf50fc457UL, 0x65b0d9c6UL, 0x12b7e950UL, - 0x8bbeb8eaUL, 0xfcb9887cUL, 0x62dd1ddfUL, 0x15da2d49UL, 0x8cd37cf3UL, - 0xfbd44c65UL, 0x4db26158UL, 0x3ab551ceUL, 0xa3bc0074UL, 0xd4bb30e2UL, - 0x4adfa541UL, 0x3dd895d7UL, 0xa4d1c46dUL, 0xd3d6f4fbUL, 0x4369e96aUL, - 0x346ed9fcUL, 0xad678846UL, 0xda60b8d0UL, 0x44042d73UL, 0x33031de5UL, - 0xaa0a4c5fUL, 0xdd0d7cc9UL, 0x5005713cUL, 0x270241aaUL, 0xbe0b1010UL, - 0xc90c2086UL, 0x5768b525UL, 0x206f85b3UL, 0xb966d409UL, 0xce61e49fUL, - 0x5edef90eUL, 0x29d9c998UL, 0xb0d09822UL, 0xc7d7a8b4UL, 0x59b33d17UL, - 0x2eb40d81UL, 0xb7bd5c3bUL, 0xc0ba6cadUL, 0xedb88320UL, 0x9abfb3b6UL, - 0x03b6e20cUL, 0x74b1d29aUL, 0xead54739UL, 0x9dd277afUL, 0x04db2615UL, - 0x73dc1683UL, 0xe3630b12UL, 0x94643b84UL, 0x0d6d6a3eUL, 0x7a6a5aa8UL, - 0xe40ecf0bUL, 0x9309ff9dUL, 0x0a00ae27UL, 0x7d079eb1UL, 0xf00f9344UL, - 0x8708a3d2UL, 0x1e01f268UL, 0x6906c2feUL, 0xf762575dUL, 0x806567cbUL, - 0x196c3671UL, 0x6e6b06e7UL, 0xfed41b76UL, 0x89d32be0UL, 0x10da7a5aUL, - 0x67dd4accUL, 0xf9b9df6fUL, 0x8ebeeff9UL, 0x17b7be43UL, 0x60b08ed5UL, - 0xd6d6a3e8UL, 0xa1d1937eUL, 0x38d8c2c4UL, 0x4fdff252UL, 0xd1bb67f1UL, - 0xa6bc5767UL, 0x3fb506ddUL, 0x48b2364bUL, 0xd80d2bdaUL, 0xaf0a1b4cUL, - 0x36034af6UL, 0x41047a60UL, 0xdf60efc3UL, 0xa867df55UL, 0x316e8eefUL, - 0x4669be79UL, 0xcb61b38cUL, 0xbc66831aUL, 0x256fd2a0UL, 0x5268e236UL, - 0xcc0c7795UL, 0xbb0b4703UL, 0x220216b9UL, 0x5505262fUL, 0xc5ba3bbeUL, - 0xb2bd0b28UL, 0x2bb45a92UL, 0x5cb36a04UL, 0xc2d7ffa7UL, 0xb5d0cf31UL, - 0x2cd99e8bUL, 0x5bdeae1dUL, 0x9b64c2b0UL, 0xec63f226UL, 0x756aa39cUL, - 0x026d930aUL, 0x9c0906a9UL, 0xeb0e363fUL, 0x72076785UL, 0x05005713UL, - 0x95bf4a82UL, 0xe2b87a14UL, 0x7bb12baeUL, 0x0cb61b38UL, 0x92d28e9bUL, - 0xe5d5be0dUL, 0x7cdcefb7UL, 0x0bdbdf21UL, 0x86d3d2d4UL, 0xf1d4e242UL, - 0x68ddb3f8UL, 0x1fda836eUL, 0x81be16cdUL, 0xf6b9265bUL, 0x6fb077e1UL, - 0x18b74777UL, 0x88085ae6UL, 0xff0f6a70UL, 0x66063bcaUL, 0x11010b5cUL, - 0x8f659effUL, 0xf862ae69UL, 0x616bffd3UL, 0x166ccf45UL, 0xa00ae278UL, - 0xd70dd2eeUL, 0x4e048354UL, 0x3903b3c2UL, 0xa7672661UL, 0xd06016f7UL, - 0x4969474dUL, 0x3e6e77dbUL, 0xaed16a4aUL, 0xd9d65adcUL, 0x40df0b66UL, - 0x37d83bf0UL, 0xa9bcae53UL, 0xdebb9ec5UL, 0x47b2cf7fUL, 0x30b5ffe9UL, - 0xbdbdf21cUL, 0xcabac28aUL, 0x53b39330UL, 0x24b4a3a6UL, 0xbad03605UL, - 0xcdd70693UL, 0x54de5729UL, 0x23d967bfUL, 0xb3667a2eUL, 0xc4614ab8UL, - 0x5d681b02UL, 0x2a6f2b94UL, 0xb40bbe37UL, 0xc30c8ea1UL, 0x5a05df1bUL, - 0x2d02ef8dUL -#ifdef BYFOUR - }, - { - 0x00000000UL, 0x191b3141UL, 0x32366282UL, 0x2b2d53c3UL, 0x646cc504UL, - 0x7d77f445UL, 0x565aa786UL, 0x4f4196c7UL, 0xc8d98a08UL, 0xd1c2bb49UL, - 0xfaefe88aUL, 0xe3f4d9cbUL, 0xacb54f0cUL, 0xb5ae7e4dUL, 0x9e832d8eUL, - 0x87981ccfUL, 0x4ac21251UL, 0x53d92310UL, 0x78f470d3UL, 0x61ef4192UL, - 0x2eaed755UL, 0x37b5e614UL, 0x1c98b5d7UL, 0x05838496UL, 0x821b9859UL, - 0x9b00a918UL, 0xb02dfadbUL, 0xa936cb9aUL, 0xe6775d5dUL, 0xff6c6c1cUL, - 0xd4413fdfUL, 0xcd5a0e9eUL, 0x958424a2UL, 0x8c9f15e3UL, 0xa7b24620UL, - 0xbea97761UL, 0xf1e8e1a6UL, 0xe8f3d0e7UL, 0xc3de8324UL, 0xdac5b265UL, - 0x5d5daeaaUL, 0x44469febUL, 0x6f6bcc28UL, 0x7670fd69UL, 0x39316baeUL, - 0x202a5aefUL, 0x0b07092cUL, 0x121c386dUL, 0xdf4636f3UL, 0xc65d07b2UL, - 0xed705471UL, 0xf46b6530UL, 0xbb2af3f7UL, 0xa231c2b6UL, 0x891c9175UL, - 0x9007a034UL, 0x179fbcfbUL, 0x0e848dbaUL, 0x25a9de79UL, 0x3cb2ef38UL, - 0x73f379ffUL, 0x6ae848beUL, 0x41c51b7dUL, 0x58de2a3cUL, 0xf0794f05UL, - 0xe9627e44UL, 0xc24f2d87UL, 0xdb541cc6UL, 0x94158a01UL, 0x8d0ebb40UL, - 0xa623e883UL, 0xbf38d9c2UL, 0x38a0c50dUL, 0x21bbf44cUL, 0x0a96a78fUL, - 0x138d96ceUL, 0x5ccc0009UL, 0x45d73148UL, 0x6efa628bUL, 0x77e153caUL, - 0xbabb5d54UL, 0xa3a06c15UL, 0x888d3fd6UL, 0x91960e97UL, 0xded79850UL, - 0xc7cca911UL, 0xece1fad2UL, 0xf5facb93UL, 0x7262d75cUL, 0x6b79e61dUL, - 0x4054b5deUL, 0x594f849fUL, 0x160e1258UL, 0x0f152319UL, 0x243870daUL, - 0x3d23419bUL, 0x65fd6ba7UL, 0x7ce65ae6UL, 0x57cb0925UL, 0x4ed03864UL, - 0x0191aea3UL, 0x188a9fe2UL, 0x33a7cc21UL, 0x2abcfd60UL, 0xad24e1afUL, - 0xb43fd0eeUL, 0x9f12832dUL, 0x8609b26cUL, 0xc94824abUL, 0xd05315eaUL, - 0xfb7e4629UL, 0xe2657768UL, 0x2f3f79f6UL, 0x362448b7UL, 0x1d091b74UL, - 0x04122a35UL, 0x4b53bcf2UL, 0x52488db3UL, 0x7965de70UL, 0x607eef31UL, - 0xe7e6f3feUL, 0xfefdc2bfUL, 0xd5d0917cUL, 0xcccba03dUL, 0x838a36faUL, - 0x9a9107bbUL, 0xb1bc5478UL, 0xa8a76539UL, 0x3b83984bUL, 0x2298a90aUL, - 0x09b5fac9UL, 0x10aecb88UL, 0x5fef5d4fUL, 0x46f46c0eUL, 0x6dd93fcdUL, - 0x74c20e8cUL, 0xf35a1243UL, 0xea412302UL, 0xc16c70c1UL, 0xd8774180UL, - 0x9736d747UL, 0x8e2de606UL, 0xa500b5c5UL, 0xbc1b8484UL, 0x71418a1aUL, - 0x685abb5bUL, 0x4377e898UL, 0x5a6cd9d9UL, 0x152d4f1eUL, 0x0c367e5fUL, - 0x271b2d9cUL, 0x3e001cddUL, 0xb9980012UL, 0xa0833153UL, 0x8bae6290UL, - 0x92b553d1UL, 0xddf4c516UL, 0xc4eff457UL, 0xefc2a794UL, 0xf6d996d5UL, - 0xae07bce9UL, 0xb71c8da8UL, 0x9c31de6bUL, 0x852aef2aUL, 0xca6b79edUL, - 0xd37048acUL, 0xf85d1b6fUL, 0xe1462a2eUL, 0x66de36e1UL, 0x7fc507a0UL, - 0x54e85463UL, 0x4df36522UL, 0x02b2f3e5UL, 0x1ba9c2a4UL, 0x30849167UL, - 0x299fa026UL, 0xe4c5aeb8UL, 0xfdde9ff9UL, 0xd6f3cc3aUL, 0xcfe8fd7bUL, - 0x80a96bbcUL, 0x99b25afdUL, 0xb29f093eUL, 0xab84387fUL, 0x2c1c24b0UL, - 0x350715f1UL, 0x1e2a4632UL, 0x07317773UL, 0x4870e1b4UL, 0x516bd0f5UL, - 0x7a468336UL, 0x635db277UL, 0xcbfad74eUL, 0xd2e1e60fUL, 0xf9ccb5ccUL, - 0xe0d7848dUL, 0xaf96124aUL, 0xb68d230bUL, 0x9da070c8UL, 0x84bb4189UL, - 0x03235d46UL, 0x1a386c07UL, 0x31153fc4UL, 0x280e0e85UL, 0x674f9842UL, - 0x7e54a903UL, 0x5579fac0UL, 0x4c62cb81UL, 0x8138c51fUL, 0x9823f45eUL, - 0xb30ea79dUL, 0xaa1596dcUL, 0xe554001bUL, 0xfc4f315aUL, 0xd7626299UL, - 0xce7953d8UL, 0x49e14f17UL, 0x50fa7e56UL, 0x7bd72d95UL, 0x62cc1cd4UL, - 0x2d8d8a13UL, 0x3496bb52UL, 0x1fbbe891UL, 0x06a0d9d0UL, 0x5e7ef3ecUL, - 0x4765c2adUL, 0x6c48916eUL, 0x7553a02fUL, 0x3a1236e8UL, 0x230907a9UL, - 0x0824546aUL, 0x113f652bUL, 0x96a779e4UL, 0x8fbc48a5UL, 0xa4911b66UL, - 0xbd8a2a27UL, 0xf2cbbce0UL, 0xebd08da1UL, 0xc0fdde62UL, 0xd9e6ef23UL, - 0x14bce1bdUL, 0x0da7d0fcUL, 0x268a833fUL, 0x3f91b27eUL, 0x70d024b9UL, - 0x69cb15f8UL, 0x42e6463bUL, 0x5bfd777aUL, 0xdc656bb5UL, 0xc57e5af4UL, - 0xee530937UL, 0xf7483876UL, 0xb809aeb1UL, 0xa1129ff0UL, 0x8a3fcc33UL, - 0x9324fd72UL - }, - { - 0x00000000UL, 0x01c26a37UL, 0x0384d46eUL, 0x0246be59UL, 0x0709a8dcUL, - 0x06cbc2ebUL, 0x048d7cb2UL, 0x054f1685UL, 0x0e1351b8UL, 0x0fd13b8fUL, - 0x0d9785d6UL, 0x0c55efe1UL, 0x091af964UL, 0x08d89353UL, 0x0a9e2d0aUL, - 0x0b5c473dUL, 0x1c26a370UL, 0x1de4c947UL, 0x1fa2771eUL, 0x1e601d29UL, - 0x1b2f0bacUL, 0x1aed619bUL, 0x18abdfc2UL, 0x1969b5f5UL, 0x1235f2c8UL, - 0x13f798ffUL, 0x11b126a6UL, 0x10734c91UL, 0x153c5a14UL, 0x14fe3023UL, - 0x16b88e7aUL, 0x177ae44dUL, 0x384d46e0UL, 0x398f2cd7UL, 0x3bc9928eUL, - 0x3a0bf8b9UL, 0x3f44ee3cUL, 0x3e86840bUL, 0x3cc03a52UL, 0x3d025065UL, - 0x365e1758UL, 0x379c7d6fUL, 0x35dac336UL, 0x3418a901UL, 0x3157bf84UL, - 0x3095d5b3UL, 0x32d36beaUL, 0x331101ddUL, 0x246be590UL, 0x25a98fa7UL, - 0x27ef31feUL, 0x262d5bc9UL, 0x23624d4cUL, 0x22a0277bUL, 0x20e69922UL, - 0x2124f315UL, 0x2a78b428UL, 0x2bbade1fUL, 0x29fc6046UL, 0x283e0a71UL, - 0x2d711cf4UL, 0x2cb376c3UL, 0x2ef5c89aUL, 0x2f37a2adUL, 0x709a8dc0UL, - 0x7158e7f7UL, 0x731e59aeUL, 0x72dc3399UL, 0x7793251cUL, 0x76514f2bUL, - 0x7417f172UL, 0x75d59b45UL, 0x7e89dc78UL, 0x7f4bb64fUL, 0x7d0d0816UL, - 0x7ccf6221UL, 0x798074a4UL, 0x78421e93UL, 0x7a04a0caUL, 0x7bc6cafdUL, - 0x6cbc2eb0UL, 0x6d7e4487UL, 0x6f38fadeUL, 0x6efa90e9UL, 0x6bb5866cUL, - 0x6a77ec5bUL, 0x68315202UL, 0x69f33835UL, 0x62af7f08UL, 0x636d153fUL, - 0x612bab66UL, 0x60e9c151UL, 0x65a6d7d4UL, 0x6464bde3UL, 0x662203baUL, - 0x67e0698dUL, 0x48d7cb20UL, 0x4915a117UL, 0x4b531f4eUL, 0x4a917579UL, - 0x4fde63fcUL, 0x4e1c09cbUL, 0x4c5ab792UL, 0x4d98dda5UL, 0x46c49a98UL, - 0x4706f0afUL, 0x45404ef6UL, 0x448224c1UL, 0x41cd3244UL, 0x400f5873UL, - 0x4249e62aUL, 0x438b8c1dUL, 0x54f16850UL, 0x55330267UL, 0x5775bc3eUL, - 0x56b7d609UL, 0x53f8c08cUL, 0x523aaabbUL, 0x507c14e2UL, 0x51be7ed5UL, - 0x5ae239e8UL, 0x5b2053dfUL, 0x5966ed86UL, 0x58a487b1UL, 0x5deb9134UL, - 0x5c29fb03UL, 0x5e6f455aUL, 0x5fad2f6dUL, 0xe1351b80UL, 0xe0f771b7UL, - 0xe2b1cfeeUL, 0xe373a5d9UL, 0xe63cb35cUL, 0xe7fed96bUL, 0xe5b86732UL, - 0xe47a0d05UL, 0xef264a38UL, 0xeee4200fUL, 0xeca29e56UL, 0xed60f461UL, - 0xe82fe2e4UL, 0xe9ed88d3UL, 0xebab368aUL, 0xea695cbdUL, 0xfd13b8f0UL, - 0xfcd1d2c7UL, 0xfe976c9eUL, 0xff5506a9UL, 0xfa1a102cUL, 0xfbd87a1bUL, - 0xf99ec442UL, 0xf85cae75UL, 0xf300e948UL, 0xf2c2837fUL, 0xf0843d26UL, - 0xf1465711UL, 0xf4094194UL, 0xf5cb2ba3UL, 0xf78d95faUL, 0xf64fffcdUL, - 0xd9785d60UL, 0xd8ba3757UL, 0xdafc890eUL, 0xdb3ee339UL, 0xde71f5bcUL, - 0xdfb39f8bUL, 0xddf521d2UL, 0xdc374be5UL, 0xd76b0cd8UL, 0xd6a966efUL, - 0xd4efd8b6UL, 0xd52db281UL, 0xd062a404UL, 0xd1a0ce33UL, 0xd3e6706aUL, - 0xd2241a5dUL, 0xc55efe10UL, 0xc49c9427UL, 0xc6da2a7eUL, 0xc7184049UL, - 0xc25756ccUL, 0xc3953cfbUL, 0xc1d382a2UL, 0xc011e895UL, 0xcb4dafa8UL, - 0xca8fc59fUL, 0xc8c97bc6UL, 0xc90b11f1UL, 0xcc440774UL, 0xcd866d43UL, - 0xcfc0d31aUL, 0xce02b92dUL, 0x91af9640UL, 0x906dfc77UL, 0x922b422eUL, - 0x93e92819UL, 0x96a63e9cUL, 0x976454abUL, 0x9522eaf2UL, 0x94e080c5UL, - 0x9fbcc7f8UL, 0x9e7eadcfUL, 0x9c381396UL, 0x9dfa79a1UL, 0x98b56f24UL, - 0x99770513UL, 0x9b31bb4aUL, 0x9af3d17dUL, 0x8d893530UL, 0x8c4b5f07UL, - 0x8e0de15eUL, 0x8fcf8b69UL, 0x8a809decUL, 0x8b42f7dbUL, 0x89044982UL, - 0x88c623b5UL, 0x839a6488UL, 0x82580ebfUL, 0x801eb0e6UL, 0x81dcdad1UL, - 0x8493cc54UL, 0x8551a663UL, 0x8717183aUL, 0x86d5720dUL, 0xa9e2d0a0UL, - 0xa820ba97UL, 0xaa6604ceUL, 0xaba46ef9UL, 0xaeeb787cUL, 0xaf29124bUL, - 0xad6fac12UL, 0xacadc625UL, 0xa7f18118UL, 0xa633eb2fUL, 0xa4755576UL, - 0xa5b73f41UL, 0xa0f829c4UL, 0xa13a43f3UL, 0xa37cfdaaUL, 0xa2be979dUL, - 0xb5c473d0UL, 0xb40619e7UL, 0xb640a7beUL, 0xb782cd89UL, 0xb2cddb0cUL, - 0xb30fb13bUL, 0xb1490f62UL, 0xb08b6555UL, 0xbbd72268UL, 0xba15485fUL, - 0xb853f606UL, 0xb9919c31UL, 0xbcde8ab4UL, 0xbd1ce083UL, 0xbf5a5edaUL, - 0xbe9834edUL - }, - { - 0x00000000UL, 0xb8bc6765UL, 0xaa09c88bUL, 0x12b5afeeUL, 0x8f629757UL, - 0x37def032UL, 0x256b5fdcUL, 0x9dd738b9UL, 0xc5b428efUL, 0x7d084f8aUL, - 0x6fbde064UL, 0xd7018701UL, 0x4ad6bfb8UL, 0xf26ad8ddUL, 0xe0df7733UL, - 0x58631056UL, 0x5019579fUL, 0xe8a530faUL, 0xfa109f14UL, 0x42acf871UL, - 0xdf7bc0c8UL, 0x67c7a7adUL, 0x75720843UL, 0xcdce6f26UL, 0x95ad7f70UL, - 0x2d111815UL, 0x3fa4b7fbUL, 0x8718d09eUL, 0x1acfe827UL, 0xa2738f42UL, - 0xb0c620acUL, 0x087a47c9UL, 0xa032af3eUL, 0x188ec85bUL, 0x0a3b67b5UL, - 0xb28700d0UL, 0x2f503869UL, 0x97ec5f0cUL, 0x8559f0e2UL, 0x3de59787UL, - 0x658687d1UL, 0xdd3ae0b4UL, 0xcf8f4f5aUL, 0x7733283fUL, 0xeae41086UL, - 0x525877e3UL, 0x40edd80dUL, 0xf851bf68UL, 0xf02bf8a1UL, 0x48979fc4UL, - 0x5a22302aUL, 0xe29e574fUL, 0x7f496ff6UL, 0xc7f50893UL, 0xd540a77dUL, - 0x6dfcc018UL, 0x359fd04eUL, 0x8d23b72bUL, 0x9f9618c5UL, 0x272a7fa0UL, - 0xbafd4719UL, 0x0241207cUL, 0x10f48f92UL, 0xa848e8f7UL, 0x9b14583dUL, - 0x23a83f58UL, 0x311d90b6UL, 0x89a1f7d3UL, 0x1476cf6aUL, 0xaccaa80fUL, - 0xbe7f07e1UL, 0x06c36084UL, 0x5ea070d2UL, 0xe61c17b7UL, 0xf4a9b859UL, - 0x4c15df3cUL, 0xd1c2e785UL, 0x697e80e0UL, 0x7bcb2f0eUL, 0xc377486bUL, - 0xcb0d0fa2UL, 0x73b168c7UL, 0x6104c729UL, 0xd9b8a04cUL, 0x446f98f5UL, - 0xfcd3ff90UL, 0xee66507eUL, 0x56da371bUL, 0x0eb9274dUL, 0xb6054028UL, - 0xa4b0efc6UL, 0x1c0c88a3UL, 0x81dbb01aUL, 0x3967d77fUL, 0x2bd27891UL, - 0x936e1ff4UL, 0x3b26f703UL, 0x839a9066UL, 0x912f3f88UL, 0x299358edUL, - 0xb4446054UL, 0x0cf80731UL, 0x1e4da8dfUL, 0xa6f1cfbaUL, 0xfe92dfecUL, - 0x462eb889UL, 0x549b1767UL, 0xec277002UL, 0x71f048bbUL, 0xc94c2fdeUL, - 0xdbf98030UL, 0x6345e755UL, 0x6b3fa09cUL, 0xd383c7f9UL, 0xc1366817UL, - 0x798a0f72UL, 0xe45d37cbUL, 0x5ce150aeUL, 0x4e54ff40UL, 0xf6e89825UL, - 0xae8b8873UL, 0x1637ef16UL, 0x048240f8UL, 0xbc3e279dUL, 0x21e91f24UL, - 0x99557841UL, 0x8be0d7afUL, 0x335cb0caUL, 0xed59b63bUL, 0x55e5d15eUL, - 0x47507eb0UL, 0xffec19d5UL, 0x623b216cUL, 0xda874609UL, 0xc832e9e7UL, - 0x708e8e82UL, 0x28ed9ed4UL, 0x9051f9b1UL, 0x82e4565fUL, 0x3a58313aUL, - 0xa78f0983UL, 0x1f336ee6UL, 0x0d86c108UL, 0xb53aa66dUL, 0xbd40e1a4UL, - 0x05fc86c1UL, 0x1749292fUL, 0xaff54e4aUL, 0x322276f3UL, 0x8a9e1196UL, - 0x982bbe78UL, 0x2097d91dUL, 0x78f4c94bUL, 0xc048ae2eUL, 0xd2fd01c0UL, - 0x6a4166a5UL, 0xf7965e1cUL, 0x4f2a3979UL, 0x5d9f9697UL, 0xe523f1f2UL, - 0x4d6b1905UL, 0xf5d77e60UL, 0xe762d18eUL, 0x5fdeb6ebUL, 0xc2098e52UL, - 0x7ab5e937UL, 0x680046d9UL, 0xd0bc21bcUL, 0x88df31eaUL, 0x3063568fUL, - 0x22d6f961UL, 0x9a6a9e04UL, 0x07bda6bdUL, 0xbf01c1d8UL, 0xadb46e36UL, - 0x15080953UL, 0x1d724e9aUL, 0xa5ce29ffUL, 0xb77b8611UL, 0x0fc7e174UL, - 0x9210d9cdUL, 0x2aacbea8UL, 0x38191146UL, 0x80a57623UL, 0xd8c66675UL, - 0x607a0110UL, 0x72cfaefeUL, 0xca73c99bUL, 0x57a4f122UL, 0xef189647UL, - 0xfdad39a9UL, 0x45115eccUL, 0x764dee06UL, 0xcef18963UL, 0xdc44268dUL, - 0x64f841e8UL, 0xf92f7951UL, 0x41931e34UL, 0x5326b1daUL, 0xeb9ad6bfUL, - 0xb3f9c6e9UL, 0x0b45a18cUL, 0x19f00e62UL, 0xa14c6907UL, 0x3c9b51beUL, - 0x842736dbUL, 0x96929935UL, 0x2e2efe50UL, 0x2654b999UL, 0x9ee8defcUL, - 0x8c5d7112UL, 0x34e11677UL, 0xa9362eceUL, 0x118a49abUL, 0x033fe645UL, - 0xbb838120UL, 0xe3e09176UL, 0x5b5cf613UL, 0x49e959fdUL, 0xf1553e98UL, - 0x6c820621UL, 0xd43e6144UL, 0xc68bceaaUL, 0x7e37a9cfUL, 0xd67f4138UL, - 0x6ec3265dUL, 0x7c7689b3UL, 0xc4caeed6UL, 0x591dd66fUL, 0xe1a1b10aUL, - 0xf3141ee4UL, 0x4ba87981UL, 0x13cb69d7UL, 0xab770eb2UL, 0xb9c2a15cUL, - 0x017ec639UL, 0x9ca9fe80UL, 0x241599e5UL, 0x36a0360bUL, 0x8e1c516eUL, - 0x866616a7UL, 0x3eda71c2UL, 0x2c6fde2cUL, 0x94d3b949UL, 0x090481f0UL, - 0xb1b8e695UL, 0xa30d497bUL, 0x1bb12e1eUL, 0x43d23e48UL, 0xfb6e592dUL, - 0xe9dbf6c3UL, 0x516791a6UL, 0xccb0a91fUL, 0x740cce7aUL, 0x66b96194UL, - 0xde0506f1UL - }, - { - 0x00000000UL, 0x96300777UL, 0x2c610eeeUL, 0xba510999UL, 0x19c46d07UL, - 0x8ff46a70UL, 0x35a563e9UL, 0xa395649eUL, 0x3288db0eUL, 0xa4b8dc79UL, - 0x1ee9d5e0UL, 0x88d9d297UL, 0x2b4cb609UL, 0xbd7cb17eUL, 0x072db8e7UL, - 0x911dbf90UL, 0x6410b71dUL, 0xf220b06aUL, 0x4871b9f3UL, 0xde41be84UL, - 0x7dd4da1aUL, 0xebe4dd6dUL, 0x51b5d4f4UL, 0xc785d383UL, 0x56986c13UL, - 0xc0a86b64UL, 0x7af962fdUL, 0xecc9658aUL, 0x4f5c0114UL, 0xd96c0663UL, - 0x633d0ffaUL, 0xf50d088dUL, 0xc8206e3bUL, 0x5e10694cUL, 0xe44160d5UL, - 0x727167a2UL, 0xd1e4033cUL, 0x47d4044bUL, 0xfd850dd2UL, 0x6bb50aa5UL, - 0xfaa8b535UL, 0x6c98b242UL, 0xd6c9bbdbUL, 0x40f9bcacUL, 0xe36cd832UL, - 0x755cdf45UL, 0xcf0dd6dcUL, 0x593dd1abUL, 0xac30d926UL, 0x3a00de51UL, - 0x8051d7c8UL, 0x1661d0bfUL, 0xb5f4b421UL, 0x23c4b356UL, 0x9995bacfUL, - 0x0fa5bdb8UL, 0x9eb80228UL, 0x0888055fUL, 0xb2d90cc6UL, 0x24e90bb1UL, - 0x877c6f2fUL, 0x114c6858UL, 0xab1d61c1UL, 0x3d2d66b6UL, 0x9041dc76UL, - 0x0671db01UL, 0xbc20d298UL, 0x2a10d5efUL, 0x8985b171UL, 0x1fb5b606UL, - 0xa5e4bf9fUL, 0x33d4b8e8UL, 0xa2c90778UL, 0x34f9000fUL, 0x8ea80996UL, - 0x18980ee1UL, 0xbb0d6a7fUL, 0x2d3d6d08UL, 0x976c6491UL, 0x015c63e6UL, - 0xf4516b6bUL, 0x62616c1cUL, 0xd8306585UL, 0x4e0062f2UL, 0xed95066cUL, - 0x7ba5011bUL, 0xc1f40882UL, 0x57c40ff5UL, 0xc6d9b065UL, 0x50e9b712UL, - 0xeab8be8bUL, 0x7c88b9fcUL, 0xdf1ddd62UL, 0x492dda15UL, 0xf37cd38cUL, - 0x654cd4fbUL, 0x5861b24dUL, 0xce51b53aUL, 0x7400bca3UL, 0xe230bbd4UL, - 0x41a5df4aUL, 0xd795d83dUL, 0x6dc4d1a4UL, 0xfbf4d6d3UL, 0x6ae96943UL, - 0xfcd96e34UL, 0x468867adUL, 0xd0b860daUL, 0x732d0444UL, 0xe51d0333UL, - 0x5f4c0aaaUL, 0xc97c0dddUL, 0x3c710550UL, 0xaa410227UL, 0x10100bbeUL, - 0x86200cc9UL, 0x25b56857UL, 0xb3856f20UL, 0x09d466b9UL, 0x9fe461ceUL, - 0x0ef9de5eUL, 0x98c9d929UL, 0x2298d0b0UL, 0xb4a8d7c7UL, 0x173db359UL, - 0x810db42eUL, 0x3b5cbdb7UL, 0xad6cbac0UL, 0x2083b8edUL, 0xb6b3bf9aUL, - 0x0ce2b603UL, 0x9ad2b174UL, 0x3947d5eaUL, 0xaf77d29dUL, 0x1526db04UL, - 0x8316dc73UL, 0x120b63e3UL, 0x843b6494UL, 0x3e6a6d0dUL, 0xa85a6a7aUL, - 0x0bcf0ee4UL, 0x9dff0993UL, 0x27ae000aUL, 0xb19e077dUL, 0x44930ff0UL, - 0xd2a30887UL, 0x68f2011eUL, 0xfec20669UL, 0x5d5762f7UL, 0xcb676580UL, - 0x71366c19UL, 0xe7066b6eUL, 0x761bd4feUL, 0xe02bd389UL, 0x5a7ada10UL, - 0xcc4add67UL, 0x6fdfb9f9UL, 0xf9efbe8eUL, 0x43beb717UL, 0xd58eb060UL, - 0xe8a3d6d6UL, 0x7e93d1a1UL, 0xc4c2d838UL, 0x52f2df4fUL, 0xf167bbd1UL, - 0x6757bca6UL, 0xdd06b53fUL, 0x4b36b248UL, 0xda2b0dd8UL, 0x4c1b0aafUL, - 0xf64a0336UL, 0x607a0441UL, 0xc3ef60dfUL, 0x55df67a8UL, 0xef8e6e31UL, - 0x79be6946UL, 0x8cb361cbUL, 0x1a8366bcUL, 0xa0d26f25UL, 0x36e26852UL, - 0x95770cccUL, 0x03470bbbUL, 0xb9160222UL, 0x2f260555UL, 0xbe3bbac5UL, - 0x280bbdb2UL, 0x925ab42bUL, 0x046ab35cUL, 0xa7ffd7c2UL, 0x31cfd0b5UL, - 0x8b9ed92cUL, 0x1daede5bUL, 0xb0c2649bUL, 0x26f263ecUL, 0x9ca36a75UL, - 0x0a936d02UL, 0xa906099cUL, 0x3f360eebUL, 0x85670772UL, 0x13570005UL, - 0x824abf95UL, 0x147ab8e2UL, 0xae2bb17bUL, 0x381bb60cUL, 0x9b8ed292UL, - 0x0dbed5e5UL, 0xb7efdc7cUL, 0x21dfdb0bUL, 0xd4d2d386UL, 0x42e2d4f1UL, - 0xf8b3dd68UL, 0x6e83da1fUL, 0xcd16be81UL, 0x5b26b9f6UL, 0xe177b06fUL, - 0x7747b718UL, 0xe65a0888UL, 0x706a0fffUL, 0xca3b0666UL, 0x5c0b0111UL, - 0xff9e658fUL, 0x69ae62f8UL, 0xd3ff6b61UL, 0x45cf6c16UL, 0x78e20aa0UL, - 0xeed20dd7UL, 0x5483044eUL, 0xc2b30339UL, 0x612667a7UL, 0xf71660d0UL, - 0x4d476949UL, 0xdb776e3eUL, 0x4a6ad1aeUL, 0xdc5ad6d9UL, 0x660bdf40UL, - 0xf03bd837UL, 0x53aebca9UL, 0xc59ebbdeUL, 0x7fcfb247UL, 0xe9ffb530UL, - 0x1cf2bdbdUL, 0x8ac2bacaUL, 0x3093b353UL, 0xa6a3b424UL, 0x0536d0baUL, - 0x9306d7cdUL, 0x2957de54UL, 0xbf67d923UL, 0x2e7a66b3UL, 0xb84a61c4UL, - 0x021b685dUL, 0x942b6f2aUL, 0x37be0bb4UL, 0xa18e0cc3UL, 0x1bdf055aUL, - 0x8def022dUL - }, - { - 0x00000000UL, 0x41311b19UL, 0x82623632UL, 0xc3532d2bUL, 0x04c56c64UL, - 0x45f4777dUL, 0x86a75a56UL, 0xc796414fUL, 0x088ad9c8UL, 0x49bbc2d1UL, - 0x8ae8effaUL, 0xcbd9f4e3UL, 0x0c4fb5acUL, 0x4d7eaeb5UL, 0x8e2d839eUL, - 0xcf1c9887UL, 0x5112c24aUL, 0x1023d953UL, 0xd370f478UL, 0x9241ef61UL, - 0x55d7ae2eUL, 0x14e6b537UL, 0xd7b5981cUL, 0x96848305UL, 0x59981b82UL, - 0x18a9009bUL, 0xdbfa2db0UL, 0x9acb36a9UL, 0x5d5d77e6UL, 0x1c6c6cffUL, - 0xdf3f41d4UL, 0x9e0e5acdUL, 0xa2248495UL, 0xe3159f8cUL, 0x2046b2a7UL, - 0x6177a9beUL, 0xa6e1e8f1UL, 0xe7d0f3e8UL, 0x2483dec3UL, 0x65b2c5daUL, - 0xaaae5d5dUL, 0xeb9f4644UL, 0x28cc6b6fUL, 0x69fd7076UL, 0xae6b3139UL, - 0xef5a2a20UL, 0x2c09070bUL, 0x6d381c12UL, 0xf33646dfUL, 0xb2075dc6UL, - 0x715470edUL, 0x30656bf4UL, 0xf7f32abbUL, 0xb6c231a2UL, 0x75911c89UL, - 0x34a00790UL, 0xfbbc9f17UL, 0xba8d840eUL, 0x79dea925UL, 0x38efb23cUL, - 0xff79f373UL, 0xbe48e86aUL, 0x7d1bc541UL, 0x3c2ade58UL, 0x054f79f0UL, - 0x447e62e9UL, 0x872d4fc2UL, 0xc61c54dbUL, 0x018a1594UL, 0x40bb0e8dUL, - 0x83e823a6UL, 0xc2d938bfUL, 0x0dc5a038UL, 0x4cf4bb21UL, 0x8fa7960aUL, - 0xce968d13UL, 0x0900cc5cUL, 0x4831d745UL, 0x8b62fa6eUL, 0xca53e177UL, - 0x545dbbbaUL, 0x156ca0a3UL, 0xd63f8d88UL, 0x970e9691UL, 0x5098d7deUL, - 0x11a9ccc7UL, 0xd2fae1ecUL, 0x93cbfaf5UL, 0x5cd76272UL, 0x1de6796bUL, - 0xdeb55440UL, 0x9f844f59UL, 0x58120e16UL, 0x1923150fUL, 0xda703824UL, - 0x9b41233dUL, 0xa76bfd65UL, 0xe65ae67cUL, 0x2509cb57UL, 0x6438d04eUL, - 0xa3ae9101UL, 0xe29f8a18UL, 0x21cca733UL, 0x60fdbc2aUL, 0xafe124adUL, - 0xeed03fb4UL, 0x2d83129fUL, 0x6cb20986UL, 0xab2448c9UL, 0xea1553d0UL, - 0x29467efbUL, 0x687765e2UL, 0xf6793f2fUL, 0xb7482436UL, 0x741b091dUL, - 0x352a1204UL, 0xf2bc534bUL, 0xb38d4852UL, 0x70de6579UL, 0x31ef7e60UL, - 0xfef3e6e7UL, 0xbfc2fdfeUL, 0x7c91d0d5UL, 0x3da0cbccUL, 0xfa368a83UL, - 0xbb07919aUL, 0x7854bcb1UL, 0x3965a7a8UL, 0x4b98833bUL, 0x0aa99822UL, - 0xc9fab509UL, 0x88cbae10UL, 0x4f5def5fUL, 0x0e6cf446UL, 0xcd3fd96dUL, - 0x8c0ec274UL, 0x43125af3UL, 0x022341eaUL, 0xc1706cc1UL, 0x804177d8UL, - 0x47d73697UL, 0x06e62d8eUL, 0xc5b500a5UL, 0x84841bbcUL, 0x1a8a4171UL, - 0x5bbb5a68UL, 0x98e87743UL, 0xd9d96c5aUL, 0x1e4f2d15UL, 0x5f7e360cUL, - 0x9c2d1b27UL, 0xdd1c003eUL, 0x120098b9UL, 0x533183a0UL, 0x9062ae8bUL, - 0xd153b592UL, 0x16c5f4ddUL, 0x57f4efc4UL, 0x94a7c2efUL, 0xd596d9f6UL, - 0xe9bc07aeUL, 0xa88d1cb7UL, 0x6bde319cUL, 0x2aef2a85UL, 0xed796bcaUL, - 0xac4870d3UL, 0x6f1b5df8UL, 0x2e2a46e1UL, 0xe136de66UL, 0xa007c57fUL, - 0x6354e854UL, 0x2265f34dUL, 0xe5f3b202UL, 0xa4c2a91bUL, 0x67918430UL, - 0x26a09f29UL, 0xb8aec5e4UL, 0xf99fdefdUL, 0x3accf3d6UL, 0x7bfde8cfUL, - 0xbc6ba980UL, 0xfd5ab299UL, 0x3e099fb2UL, 0x7f3884abUL, 0xb0241c2cUL, - 0xf1150735UL, 0x32462a1eUL, 0x73773107UL, 0xb4e17048UL, 0xf5d06b51UL, - 0x3683467aUL, 0x77b25d63UL, 0x4ed7facbUL, 0x0fe6e1d2UL, 0xccb5ccf9UL, - 0x8d84d7e0UL, 0x4a1296afUL, 0x0b238db6UL, 0xc870a09dUL, 0x8941bb84UL, - 0x465d2303UL, 0x076c381aUL, 0xc43f1531UL, 0x850e0e28UL, 0x42984f67UL, - 0x03a9547eUL, 0xc0fa7955UL, 0x81cb624cUL, 0x1fc53881UL, 0x5ef42398UL, - 0x9da70eb3UL, 0xdc9615aaUL, 0x1b0054e5UL, 0x5a314ffcUL, 0x996262d7UL, - 0xd85379ceUL, 0x174fe149UL, 0x567efa50UL, 0x952dd77bUL, 0xd41ccc62UL, - 0x138a8d2dUL, 0x52bb9634UL, 0x91e8bb1fUL, 0xd0d9a006UL, 0xecf37e5eUL, - 0xadc26547UL, 0x6e91486cUL, 0x2fa05375UL, 0xe836123aUL, 0xa9070923UL, - 0x6a542408UL, 0x2b653f11UL, 0xe479a796UL, 0xa548bc8fUL, 0x661b91a4UL, - 0x272a8abdUL, 0xe0bccbf2UL, 0xa18dd0ebUL, 0x62defdc0UL, 0x23efe6d9UL, - 0xbde1bc14UL, 0xfcd0a70dUL, 0x3f838a26UL, 0x7eb2913fUL, 0xb924d070UL, - 0xf815cb69UL, 0x3b46e642UL, 0x7a77fd5bUL, 0xb56b65dcUL, 0xf45a7ec5UL, - 0x370953eeUL, 0x763848f7UL, 0xb1ae09b8UL, 0xf09f12a1UL, 0x33cc3f8aUL, - 0x72fd2493UL - }, - { - 0x00000000UL, 0x376ac201UL, 0x6ed48403UL, 0x59be4602UL, 0xdca80907UL, - 0xebc2cb06UL, 0xb27c8d04UL, 0x85164f05UL, 0xb851130eUL, 0x8f3bd10fUL, - 0xd685970dUL, 0xe1ef550cUL, 0x64f91a09UL, 0x5393d808UL, 0x0a2d9e0aUL, - 0x3d475c0bUL, 0x70a3261cUL, 0x47c9e41dUL, 0x1e77a21fUL, 0x291d601eUL, - 0xac0b2f1bUL, 0x9b61ed1aUL, 0xc2dfab18UL, 0xf5b56919UL, 0xc8f23512UL, - 0xff98f713UL, 0xa626b111UL, 0x914c7310UL, 0x145a3c15UL, 0x2330fe14UL, - 0x7a8eb816UL, 0x4de47a17UL, 0xe0464d38UL, 0xd72c8f39UL, 0x8e92c93bUL, - 0xb9f80b3aUL, 0x3cee443fUL, 0x0b84863eUL, 0x523ac03cUL, 0x6550023dUL, - 0x58175e36UL, 0x6f7d9c37UL, 0x36c3da35UL, 0x01a91834UL, 0x84bf5731UL, - 0xb3d59530UL, 0xea6bd332UL, 0xdd011133UL, 0x90e56b24UL, 0xa78fa925UL, - 0xfe31ef27UL, 0xc95b2d26UL, 0x4c4d6223UL, 0x7b27a022UL, 0x2299e620UL, - 0x15f32421UL, 0x28b4782aUL, 0x1fdeba2bUL, 0x4660fc29UL, 0x710a3e28UL, - 0xf41c712dUL, 0xc376b32cUL, 0x9ac8f52eUL, 0xada2372fUL, 0xc08d9a70UL, - 0xf7e75871UL, 0xae591e73UL, 0x9933dc72UL, 0x1c259377UL, 0x2b4f5176UL, - 0x72f11774UL, 0x459bd575UL, 0x78dc897eUL, 0x4fb64b7fUL, 0x16080d7dUL, - 0x2162cf7cUL, 0xa4748079UL, 0x931e4278UL, 0xcaa0047aUL, 0xfdcac67bUL, - 0xb02ebc6cUL, 0x87447e6dUL, 0xdefa386fUL, 0xe990fa6eUL, 0x6c86b56bUL, - 0x5bec776aUL, 0x02523168UL, 0x3538f369UL, 0x087faf62UL, 0x3f156d63UL, - 0x66ab2b61UL, 0x51c1e960UL, 0xd4d7a665UL, 0xe3bd6464UL, 0xba032266UL, - 0x8d69e067UL, 0x20cbd748UL, 0x17a11549UL, 0x4e1f534bUL, 0x7975914aUL, - 0xfc63de4fUL, 0xcb091c4eUL, 0x92b75a4cUL, 0xa5dd984dUL, 0x989ac446UL, - 0xaff00647UL, 0xf64e4045UL, 0xc1248244UL, 0x4432cd41UL, 0x73580f40UL, - 0x2ae64942UL, 0x1d8c8b43UL, 0x5068f154UL, 0x67023355UL, 0x3ebc7557UL, - 0x09d6b756UL, 0x8cc0f853UL, 0xbbaa3a52UL, 0xe2147c50UL, 0xd57ebe51UL, - 0xe839e25aUL, 0xdf53205bUL, 0x86ed6659UL, 0xb187a458UL, 0x3491eb5dUL, - 0x03fb295cUL, 0x5a456f5eUL, 0x6d2fad5fUL, 0x801b35e1UL, 0xb771f7e0UL, - 0xeecfb1e2UL, 0xd9a573e3UL, 0x5cb33ce6UL, 0x6bd9fee7UL, 0x3267b8e5UL, - 0x050d7ae4UL, 0x384a26efUL, 0x0f20e4eeUL, 0x569ea2ecUL, 0x61f460edUL, - 0xe4e22fe8UL, 0xd388ede9UL, 0x8a36abebUL, 0xbd5c69eaUL, 0xf0b813fdUL, - 0xc7d2d1fcUL, 0x9e6c97feUL, 0xa90655ffUL, 0x2c101afaUL, 0x1b7ad8fbUL, - 0x42c49ef9UL, 0x75ae5cf8UL, 0x48e900f3UL, 0x7f83c2f2UL, 0x263d84f0UL, - 0x115746f1UL, 0x944109f4UL, 0xa32bcbf5UL, 0xfa958df7UL, 0xcdff4ff6UL, - 0x605d78d9UL, 0x5737bad8UL, 0x0e89fcdaUL, 0x39e33edbUL, 0xbcf571deUL, - 0x8b9fb3dfUL, 0xd221f5ddUL, 0xe54b37dcUL, 0xd80c6bd7UL, 0xef66a9d6UL, - 0xb6d8efd4UL, 0x81b22dd5UL, 0x04a462d0UL, 0x33cea0d1UL, 0x6a70e6d3UL, - 0x5d1a24d2UL, 0x10fe5ec5UL, 0x27949cc4UL, 0x7e2adac6UL, 0x494018c7UL, - 0xcc5657c2UL, 0xfb3c95c3UL, 0xa282d3c1UL, 0x95e811c0UL, 0xa8af4dcbUL, - 0x9fc58fcaUL, 0xc67bc9c8UL, 0xf1110bc9UL, 0x740744ccUL, 0x436d86cdUL, - 0x1ad3c0cfUL, 0x2db902ceUL, 0x4096af91UL, 0x77fc6d90UL, 0x2e422b92UL, - 0x1928e993UL, 0x9c3ea696UL, 0xab546497UL, 0xf2ea2295UL, 0xc580e094UL, - 0xf8c7bc9fUL, 0xcfad7e9eUL, 0x9613389cUL, 0xa179fa9dUL, 0x246fb598UL, - 0x13057799UL, 0x4abb319bUL, 0x7dd1f39aUL, 0x3035898dUL, 0x075f4b8cUL, - 0x5ee10d8eUL, 0x698bcf8fUL, 0xec9d808aUL, 0xdbf7428bUL, 0x82490489UL, - 0xb523c688UL, 0x88649a83UL, 0xbf0e5882UL, 0xe6b01e80UL, 0xd1dadc81UL, - 0x54cc9384UL, 0x63a65185UL, 0x3a181787UL, 0x0d72d586UL, 0xa0d0e2a9UL, - 0x97ba20a8UL, 0xce0466aaUL, 0xf96ea4abUL, 0x7c78ebaeUL, 0x4b1229afUL, - 0x12ac6fadUL, 0x25c6adacUL, 0x1881f1a7UL, 0x2feb33a6UL, 0x765575a4UL, - 0x413fb7a5UL, 0xc429f8a0UL, 0xf3433aa1UL, 0xaafd7ca3UL, 0x9d97bea2UL, - 0xd073c4b5UL, 0xe71906b4UL, 0xbea740b6UL, 0x89cd82b7UL, 0x0cdbcdb2UL, - 0x3bb10fb3UL, 0x620f49b1UL, 0x55658bb0UL, 0x6822d7bbUL, 0x5f4815baUL, - 0x06f653b8UL, 0x319c91b9UL, 0xb48adebcUL, 0x83e01cbdUL, 0xda5e5abfUL, - 0xed3498beUL - }, - { - 0x00000000UL, 0x6567bcb8UL, 0x8bc809aaUL, 0xeeafb512UL, 0x5797628fUL, - 0x32f0de37UL, 0xdc5f6b25UL, 0xb938d79dUL, 0xef28b4c5UL, 0x8a4f087dUL, - 0x64e0bd6fUL, 0x018701d7UL, 0xb8bfd64aUL, 0xddd86af2UL, 0x3377dfe0UL, - 0x56106358UL, 0x9f571950UL, 0xfa30a5e8UL, 0x149f10faUL, 0x71f8ac42UL, - 0xc8c07bdfUL, 0xada7c767UL, 0x43087275UL, 0x266fcecdUL, 0x707fad95UL, - 0x1518112dUL, 0xfbb7a43fUL, 0x9ed01887UL, 0x27e8cf1aUL, 0x428f73a2UL, - 0xac20c6b0UL, 0xc9477a08UL, 0x3eaf32a0UL, 0x5bc88e18UL, 0xb5673b0aUL, - 0xd00087b2UL, 0x6938502fUL, 0x0c5fec97UL, 0xe2f05985UL, 0x8797e53dUL, - 0xd1878665UL, 0xb4e03addUL, 0x5a4f8fcfUL, 0x3f283377UL, 0x8610e4eaUL, - 0xe3775852UL, 0x0dd8ed40UL, 0x68bf51f8UL, 0xa1f82bf0UL, 0xc49f9748UL, - 0x2a30225aUL, 0x4f579ee2UL, 0xf66f497fUL, 0x9308f5c7UL, 0x7da740d5UL, - 0x18c0fc6dUL, 0x4ed09f35UL, 0x2bb7238dUL, 0xc518969fUL, 0xa07f2a27UL, - 0x1947fdbaUL, 0x7c204102UL, 0x928ff410UL, 0xf7e848a8UL, 0x3d58149bUL, - 0x583fa823UL, 0xb6901d31UL, 0xd3f7a189UL, 0x6acf7614UL, 0x0fa8caacUL, - 0xe1077fbeUL, 0x8460c306UL, 0xd270a05eUL, 0xb7171ce6UL, 0x59b8a9f4UL, - 0x3cdf154cUL, 0x85e7c2d1UL, 0xe0807e69UL, 0x0e2fcb7bUL, 0x6b4877c3UL, - 0xa20f0dcbUL, 0xc768b173UL, 0x29c70461UL, 0x4ca0b8d9UL, 0xf5986f44UL, - 0x90ffd3fcUL, 0x7e5066eeUL, 0x1b37da56UL, 0x4d27b90eUL, 0x284005b6UL, - 0xc6efb0a4UL, 0xa3880c1cUL, 0x1ab0db81UL, 0x7fd76739UL, 0x9178d22bUL, - 0xf41f6e93UL, 0x03f7263bUL, 0x66909a83UL, 0x883f2f91UL, 0xed589329UL, - 0x546044b4UL, 0x3107f80cUL, 0xdfa84d1eUL, 0xbacff1a6UL, 0xecdf92feUL, - 0x89b82e46UL, 0x67179b54UL, 0x027027ecUL, 0xbb48f071UL, 0xde2f4cc9UL, - 0x3080f9dbUL, 0x55e74563UL, 0x9ca03f6bUL, 0xf9c783d3UL, 0x176836c1UL, - 0x720f8a79UL, 0xcb375de4UL, 0xae50e15cUL, 0x40ff544eUL, 0x2598e8f6UL, - 0x73888baeUL, 0x16ef3716UL, 0xf8408204UL, 0x9d273ebcUL, 0x241fe921UL, - 0x41785599UL, 0xafd7e08bUL, 0xcab05c33UL, 0x3bb659edUL, 0x5ed1e555UL, - 0xb07e5047UL, 0xd519ecffUL, 0x6c213b62UL, 0x094687daUL, 0xe7e932c8UL, - 0x828e8e70UL, 0xd49eed28UL, 0xb1f95190UL, 0x5f56e482UL, 0x3a31583aUL, - 0x83098fa7UL, 0xe66e331fUL, 0x08c1860dUL, 0x6da63ab5UL, 0xa4e140bdUL, - 0xc186fc05UL, 0x2f294917UL, 0x4a4ef5afUL, 0xf3762232UL, 0x96119e8aUL, - 0x78be2b98UL, 0x1dd99720UL, 0x4bc9f478UL, 0x2eae48c0UL, 0xc001fdd2UL, - 0xa566416aUL, 0x1c5e96f7UL, 0x79392a4fUL, 0x97969f5dUL, 0xf2f123e5UL, - 0x05196b4dUL, 0x607ed7f5UL, 0x8ed162e7UL, 0xebb6de5fUL, 0x528e09c2UL, - 0x37e9b57aUL, 0xd9460068UL, 0xbc21bcd0UL, 0xea31df88UL, 0x8f566330UL, - 0x61f9d622UL, 0x049e6a9aUL, 0xbda6bd07UL, 0xd8c101bfUL, 0x366eb4adUL, - 0x53090815UL, 0x9a4e721dUL, 0xff29cea5UL, 0x11867bb7UL, 0x74e1c70fUL, - 0xcdd91092UL, 0xa8beac2aUL, 0x46111938UL, 0x2376a580UL, 0x7566c6d8UL, - 0x10017a60UL, 0xfeaecf72UL, 0x9bc973caUL, 0x22f1a457UL, 0x479618efUL, - 0xa939adfdUL, 0xcc5e1145UL, 0x06ee4d76UL, 0x6389f1ceUL, 0x8d2644dcUL, - 0xe841f864UL, 0x51792ff9UL, 0x341e9341UL, 0xdab12653UL, 0xbfd69aebUL, - 0xe9c6f9b3UL, 0x8ca1450bUL, 0x620ef019UL, 0x07694ca1UL, 0xbe519b3cUL, - 0xdb362784UL, 0x35999296UL, 0x50fe2e2eUL, 0x99b95426UL, 0xfcdee89eUL, - 0x12715d8cUL, 0x7716e134UL, 0xce2e36a9UL, 0xab498a11UL, 0x45e63f03UL, - 0x208183bbUL, 0x7691e0e3UL, 0x13f65c5bUL, 0xfd59e949UL, 0x983e55f1UL, - 0x2106826cUL, 0x44613ed4UL, 0xaace8bc6UL, 0xcfa9377eUL, 0x38417fd6UL, - 0x5d26c36eUL, 0xb389767cUL, 0xd6eecac4UL, 0x6fd61d59UL, 0x0ab1a1e1UL, - 0xe41e14f3UL, 0x8179a84bUL, 0xd769cb13UL, 0xb20e77abUL, 0x5ca1c2b9UL, - 0x39c67e01UL, 0x80fea99cUL, 0xe5991524UL, 0x0b36a036UL, 0x6e511c8eUL, - 0xa7166686UL, 0xc271da3eUL, 0x2cde6f2cUL, 0x49b9d394UL, 0xf0810409UL, - 0x95e6b8b1UL, 0x7b490da3UL, 0x1e2eb11bUL, 0x483ed243UL, 0x2d596efbUL, - 0xc3f6dbe9UL, 0xa6916751UL, 0x1fa9b0ccUL, 0x7ace0c74UL, 0x9461b966UL, - 0xf10605deUL +local const z_crc_t FAR crc_table[] = { + 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}; + +#ifdef W + +#if W == 8 + +local const z_word_t FAR crc_big_table[] = { + 0x0000000000000000, 0x9630077700000000, 0x2c610eee00000000, + 0xba51099900000000, 0x19c46d0700000000, 0x8ff46a7000000000, + 0x35a563e900000000, 0xa395649e00000000, 0x3288db0e00000000, + 0xa4b8dc7900000000, 0x1ee9d5e000000000, 0x88d9d29700000000, + 0x2b4cb60900000000, 0xbd7cb17e00000000, 0x072db8e700000000, + 0x911dbf9000000000, 0x6410b71d00000000, 0xf220b06a00000000, + 0x4871b9f300000000, 0xde41be8400000000, 0x7dd4da1a00000000, + 0xebe4dd6d00000000, 0x51b5d4f400000000, 0xc785d38300000000, + 0x56986c1300000000, 0xc0a86b6400000000, 0x7af962fd00000000, + 0xecc9658a00000000, 0x4f5c011400000000, 0xd96c066300000000, + 0x633d0ffa00000000, 0xf50d088d00000000, 0xc8206e3b00000000, + 0x5e10694c00000000, 0xe44160d500000000, 0x727167a200000000, + 0xd1e4033c00000000, 0x47d4044b00000000, 0xfd850dd200000000, + 0x6bb50aa500000000, 0xfaa8b53500000000, 0x6c98b24200000000, + 0xd6c9bbdb00000000, 0x40f9bcac00000000, 0xe36cd83200000000, + 0x755cdf4500000000, 0xcf0dd6dc00000000, 0x593dd1ab00000000, + 0xac30d92600000000, 0x3a00de5100000000, 0x8051d7c800000000, + 0x1661d0bf00000000, 0xb5f4b42100000000, 0x23c4b35600000000, + 0x9995bacf00000000, 0x0fa5bdb800000000, 0x9eb8022800000000, + 0x0888055f00000000, 0xb2d90cc600000000, 0x24e90bb100000000, + 0x877c6f2f00000000, 0x114c685800000000, 0xab1d61c100000000, + 0x3d2d66b600000000, 0x9041dc7600000000, 0x0671db0100000000, + 0xbc20d29800000000, 0x2a10d5ef00000000, 0x8985b17100000000, + 0x1fb5b60600000000, 0xa5e4bf9f00000000, 0x33d4b8e800000000, + 0xa2c9077800000000, 0x34f9000f00000000, 0x8ea8099600000000, + 0x18980ee100000000, 0xbb0d6a7f00000000, 0x2d3d6d0800000000, + 0x976c649100000000, 0x015c63e600000000, 0xf4516b6b00000000, + 0x62616c1c00000000, 0xd830658500000000, 0x4e0062f200000000, + 0xed95066c00000000, 0x7ba5011b00000000, 0xc1f4088200000000, + 0x57c40ff500000000, 0xc6d9b06500000000, 0x50e9b71200000000, + 0xeab8be8b00000000, 0x7c88b9fc00000000, 0xdf1ddd6200000000, + 0x492dda1500000000, 0xf37cd38c00000000, 0x654cd4fb00000000, + 0x5861b24d00000000, 0xce51b53a00000000, 0x7400bca300000000, + 0xe230bbd400000000, 0x41a5df4a00000000, 0xd795d83d00000000, + 0x6dc4d1a400000000, 0xfbf4d6d300000000, 0x6ae9694300000000, + 0xfcd96e3400000000, 0x468867ad00000000, 0xd0b860da00000000, + 0x732d044400000000, 0xe51d033300000000, 0x5f4c0aaa00000000, + 0xc97c0ddd00000000, 0x3c71055000000000, 0xaa41022700000000, + 0x10100bbe00000000, 0x86200cc900000000, 0x25b5685700000000, + 0xb3856f2000000000, 0x09d466b900000000, 0x9fe461ce00000000, + 0x0ef9de5e00000000, 0x98c9d92900000000, 0x2298d0b000000000, + 0xb4a8d7c700000000, 0x173db35900000000, 0x810db42e00000000, + 0x3b5cbdb700000000, 0xad6cbac000000000, 0x2083b8ed00000000, + 0xb6b3bf9a00000000, 0x0ce2b60300000000, 0x9ad2b17400000000, + 0x3947d5ea00000000, 0xaf77d29d00000000, 0x1526db0400000000, + 0x8316dc7300000000, 0x120b63e300000000, 0x843b649400000000, + 0x3e6a6d0d00000000, 0xa85a6a7a00000000, 0x0bcf0ee400000000, + 0x9dff099300000000, 0x27ae000a00000000, 0xb19e077d00000000, + 0x44930ff000000000, 0xd2a3088700000000, 0x68f2011e00000000, + 0xfec2066900000000, 0x5d5762f700000000, 0xcb67658000000000, + 0x71366c1900000000, 0xe7066b6e00000000, 0x761bd4fe00000000, + 0xe02bd38900000000, 0x5a7ada1000000000, 0xcc4add6700000000, + 0x6fdfb9f900000000, 0xf9efbe8e00000000, 0x43beb71700000000, + 0xd58eb06000000000, 0xe8a3d6d600000000, 0x7e93d1a100000000, + 0xc4c2d83800000000, 0x52f2df4f00000000, 0xf167bbd100000000, + 0x6757bca600000000, 0xdd06b53f00000000, 0x4b36b24800000000, + 0xda2b0dd800000000, 0x4c1b0aaf00000000, 0xf64a033600000000, + 0x607a044100000000, 0xc3ef60df00000000, 0x55df67a800000000, + 0xef8e6e3100000000, 0x79be694600000000, 0x8cb361cb00000000, + 0x1a8366bc00000000, 0xa0d26f2500000000, 0x36e2685200000000, + 0x95770ccc00000000, 0x03470bbb00000000, 0xb916022200000000, + 0x2f26055500000000, 0xbe3bbac500000000, 0x280bbdb200000000, + 0x925ab42b00000000, 0x046ab35c00000000, 0xa7ffd7c200000000, + 0x31cfd0b500000000, 0x8b9ed92c00000000, 0x1daede5b00000000, + 0xb0c2649b00000000, 0x26f263ec00000000, 0x9ca36a7500000000, + 0x0a936d0200000000, 0xa906099c00000000, 0x3f360eeb00000000, + 0x8567077200000000, 0x1357000500000000, 0x824abf9500000000, + 0x147ab8e200000000, 0xae2bb17b00000000, 0x381bb60c00000000, + 0x9b8ed29200000000, 0x0dbed5e500000000, 0xb7efdc7c00000000, + 0x21dfdb0b00000000, 0xd4d2d38600000000, 0x42e2d4f100000000, + 0xf8b3dd6800000000, 0x6e83da1f00000000, 0xcd16be8100000000, + 0x5b26b9f600000000, 0xe177b06f00000000, 0x7747b71800000000, + 0xe65a088800000000, 0x706a0fff00000000, 0xca3b066600000000, + 0x5c0b011100000000, 0xff9e658f00000000, 0x69ae62f800000000, + 0xd3ff6b6100000000, 0x45cf6c1600000000, 0x78e20aa000000000, + 0xeed20dd700000000, 0x5483044e00000000, 0xc2b3033900000000, + 0x612667a700000000, 0xf71660d000000000, 0x4d47694900000000, + 0xdb776e3e00000000, 0x4a6ad1ae00000000, 0xdc5ad6d900000000, + 0x660bdf4000000000, 0xf03bd83700000000, 0x53aebca900000000, + 0xc59ebbde00000000, 0x7fcfb24700000000, 0xe9ffb53000000000, + 0x1cf2bdbd00000000, 0x8ac2baca00000000, 0x3093b35300000000, + 0xa6a3b42400000000, 0x0536d0ba00000000, 0x9306d7cd00000000, + 0x2957de5400000000, 0xbf67d92300000000, 0x2e7a66b300000000, + 0xb84a61c400000000, 0x021b685d00000000, 0x942b6f2a00000000, + 0x37be0bb400000000, 0xa18e0cc300000000, 0x1bdf055a00000000, + 0x8def022d00000000}; + +#else /* W == 4 */ + +local const z_word_t FAR crc_big_table[] = { + 0x00000000, 0x96300777, 0x2c610eee, 0xba510999, 0x19c46d07, + 0x8ff46a70, 0x35a563e9, 0xa395649e, 0x3288db0e, 0xa4b8dc79, + 0x1ee9d5e0, 0x88d9d297, 0x2b4cb609, 0xbd7cb17e, 0x072db8e7, + 0x911dbf90, 0x6410b71d, 0xf220b06a, 0x4871b9f3, 0xde41be84, + 0x7dd4da1a, 0xebe4dd6d, 0x51b5d4f4, 0xc785d383, 0x56986c13, + 0xc0a86b64, 0x7af962fd, 0xecc9658a, 0x4f5c0114, 0xd96c0663, + 0x633d0ffa, 0xf50d088d, 0xc8206e3b, 0x5e10694c, 0xe44160d5, + 0x727167a2, 0xd1e4033c, 0x47d4044b, 0xfd850dd2, 0x6bb50aa5, + 0xfaa8b535, 0x6c98b242, 0xd6c9bbdb, 0x40f9bcac, 0xe36cd832, + 0x755cdf45, 0xcf0dd6dc, 0x593dd1ab, 0xac30d926, 0x3a00de51, + 0x8051d7c8, 0x1661d0bf, 0xb5f4b421, 0x23c4b356, 0x9995bacf, + 0x0fa5bdb8, 0x9eb80228, 0x0888055f, 0xb2d90cc6, 0x24e90bb1, + 0x877c6f2f, 0x114c6858, 0xab1d61c1, 0x3d2d66b6, 0x9041dc76, + 0x0671db01, 0xbc20d298, 0x2a10d5ef, 0x8985b171, 0x1fb5b606, + 0xa5e4bf9f, 0x33d4b8e8, 0xa2c90778, 0x34f9000f, 0x8ea80996, + 0x18980ee1, 0xbb0d6a7f, 0x2d3d6d08, 0x976c6491, 0x015c63e6, + 0xf4516b6b, 0x62616c1c, 0xd8306585, 0x4e0062f2, 0xed95066c, + 0x7ba5011b, 0xc1f40882, 0x57c40ff5, 0xc6d9b065, 0x50e9b712, + 0xeab8be8b, 0x7c88b9fc, 0xdf1ddd62, 0x492dda15, 0xf37cd38c, + 0x654cd4fb, 0x5861b24d, 0xce51b53a, 0x7400bca3, 0xe230bbd4, + 0x41a5df4a, 0xd795d83d, 0x6dc4d1a4, 0xfbf4d6d3, 0x6ae96943, + 0xfcd96e34, 0x468867ad, 0xd0b860da, 0x732d0444, 0xe51d0333, + 0x5f4c0aaa, 0xc97c0ddd, 0x3c710550, 0xaa410227, 0x10100bbe, + 0x86200cc9, 0x25b56857, 0xb3856f20, 0x09d466b9, 0x9fe461ce, + 0x0ef9de5e, 0x98c9d929, 0x2298d0b0, 0xb4a8d7c7, 0x173db359, + 0x810db42e, 0x3b5cbdb7, 0xad6cbac0, 0x2083b8ed, 0xb6b3bf9a, + 0x0ce2b603, 0x9ad2b174, 0x3947d5ea, 0xaf77d29d, 0x1526db04, + 0x8316dc73, 0x120b63e3, 0x843b6494, 0x3e6a6d0d, 0xa85a6a7a, + 0x0bcf0ee4, 0x9dff0993, 0x27ae000a, 0xb19e077d, 0x44930ff0, + 0xd2a30887, 0x68f2011e, 0xfec20669, 0x5d5762f7, 0xcb676580, + 0x71366c19, 0xe7066b6e, 0x761bd4fe, 0xe02bd389, 0x5a7ada10, + 0xcc4add67, 0x6fdfb9f9, 0xf9efbe8e, 0x43beb717, 0xd58eb060, + 0xe8a3d6d6, 0x7e93d1a1, 0xc4c2d838, 0x52f2df4f, 0xf167bbd1, + 0x6757bca6, 0xdd06b53f, 0x4b36b248, 0xda2b0dd8, 0x4c1b0aaf, + 0xf64a0336, 0x607a0441, 0xc3ef60df, 0x55df67a8, 0xef8e6e31, + 0x79be6946, 0x8cb361cb, 0x1a8366bc, 0xa0d26f25, 0x36e26852, + 0x95770ccc, 0x03470bbb, 0xb9160222, 0x2f260555, 0xbe3bbac5, + 0x280bbdb2, 0x925ab42b, 0x046ab35c, 0xa7ffd7c2, 0x31cfd0b5, + 0x8b9ed92c, 0x1daede5b, 0xb0c2649b, 0x26f263ec, 0x9ca36a75, + 0x0a936d02, 0xa906099c, 0x3f360eeb, 0x85670772, 0x13570005, + 0x824abf95, 0x147ab8e2, 0xae2bb17b, 0x381bb60c, 0x9b8ed292, + 0x0dbed5e5, 0xb7efdc7c, 0x21dfdb0b, 0xd4d2d386, 0x42e2d4f1, + 0xf8b3dd68, 0x6e83da1f, 0xcd16be81, 0x5b26b9f6, 0xe177b06f, + 0x7747b718, 0xe65a0888, 0x706a0fff, 0xca3b0666, 0x5c0b0111, + 0xff9e658f, 0x69ae62f8, 0xd3ff6b61, 0x45cf6c16, 0x78e20aa0, + 0xeed20dd7, 0x5483044e, 0xc2b30339, 0x612667a7, 0xf71660d0, + 0x4d476949, 0xdb776e3e, 0x4a6ad1ae, 0xdc5ad6d9, 0x660bdf40, + 0xf03bd837, 0x53aebca9, 0xc59ebbde, 0x7fcfb247, 0xe9ffb530, + 0x1cf2bdbd, 0x8ac2baca, 0x3093b353, 0xa6a3b424, 0x0536d0ba, + 0x9306d7cd, 0x2957de54, 0xbf67d923, 0x2e7a66b3, 0xb84a61c4, + 0x021b685d, 0x942b6f2a, 0x37be0bb4, 0xa18e0cc3, 0x1bdf055a, + 0x8def022d}; + +#endif + +#if N == 1 + +#if W == 8 + +local const z_crc_t FAR crc_braid_table[][256] = { + {0x00000000, 0xccaa009e, 0x4225077d, 0x8e8f07e3, 0x844a0efa, + 0x48e00e64, 0xc66f0987, 0x0ac50919, 0xd3e51bb5, 0x1f4f1b2b, + 0x91c01cc8, 0x5d6a1c56, 0x57af154f, 0x9b0515d1, 0x158a1232, + 0xd92012ac, 0x7cbb312b, 0xb01131b5, 0x3e9e3656, 0xf23436c8, + 0xf8f13fd1, 0x345b3f4f, 0xbad438ac, 0x767e3832, 0xaf5e2a9e, + 0x63f42a00, 0xed7b2de3, 0x21d12d7d, 0x2b142464, 0xe7be24fa, + 0x69312319, 0xa59b2387, 0xf9766256, 0x35dc62c8, 0xbb53652b, + 0x77f965b5, 0x7d3c6cac, 0xb1966c32, 0x3f196bd1, 0xf3b36b4f, + 0x2a9379e3, 0xe639797d, 0x68b67e9e, 0xa41c7e00, 0xaed97719, + 0x62737787, 0xecfc7064, 0x205670fa, 0x85cd537d, 0x496753e3, + 0xc7e85400, 0x0b42549e, 0x01875d87, 0xcd2d5d19, 0x43a25afa, + 0x8f085a64, 0x562848c8, 0x9a824856, 0x140d4fb5, 0xd8a74f2b, + 0xd2624632, 0x1ec846ac, 0x9047414f, 0x5ced41d1, 0x299dc2ed, + 0xe537c273, 0x6bb8c590, 0xa712c50e, 0xadd7cc17, 0x617dcc89, + 0xeff2cb6a, 0x2358cbf4, 0xfa78d958, 0x36d2d9c6, 0xb85dde25, + 0x74f7debb, 0x7e32d7a2, 0xb298d73c, 0x3c17d0df, 0xf0bdd041, + 0x5526f3c6, 0x998cf358, 0x1703f4bb, 0xdba9f425, 0xd16cfd3c, + 0x1dc6fda2, 0x9349fa41, 0x5fe3fadf, 0x86c3e873, 0x4a69e8ed, + 0xc4e6ef0e, 0x084cef90, 0x0289e689, 0xce23e617, 0x40ace1f4, + 0x8c06e16a, 0xd0eba0bb, 0x1c41a025, 0x92cea7c6, 0x5e64a758, + 0x54a1ae41, 0x980baedf, 0x1684a93c, 0xda2ea9a2, 0x030ebb0e, + 0xcfa4bb90, 0x412bbc73, 0x8d81bced, 0x8744b5f4, 0x4beeb56a, + 0xc561b289, 0x09cbb217, 0xac509190, 0x60fa910e, 0xee7596ed, + 0x22df9673, 0x281a9f6a, 0xe4b09ff4, 0x6a3f9817, 0xa6959889, + 0x7fb58a25, 0xb31f8abb, 0x3d908d58, 0xf13a8dc6, 0xfbff84df, + 0x37558441, 0xb9da83a2, 0x7570833c, 0x533b85da, 0x9f918544, + 0x111e82a7, 0xddb48239, 0xd7718b20, 0x1bdb8bbe, 0x95548c5d, + 0x59fe8cc3, 0x80de9e6f, 0x4c749ef1, 0xc2fb9912, 0x0e51998c, + 0x04949095, 0xc83e900b, 0x46b197e8, 0x8a1b9776, 0x2f80b4f1, + 0xe32ab46f, 0x6da5b38c, 0xa10fb312, 0xabcaba0b, 0x6760ba95, + 0xe9efbd76, 0x2545bde8, 0xfc65af44, 0x30cfafda, 0xbe40a839, + 0x72eaa8a7, 0x782fa1be, 0xb485a120, 0x3a0aa6c3, 0xf6a0a65d, + 0xaa4de78c, 0x66e7e712, 0xe868e0f1, 0x24c2e06f, 0x2e07e976, + 0xe2ade9e8, 0x6c22ee0b, 0xa088ee95, 0x79a8fc39, 0xb502fca7, + 0x3b8dfb44, 0xf727fbda, 0xfde2f2c3, 0x3148f25d, 0xbfc7f5be, + 0x736df520, 0xd6f6d6a7, 0x1a5cd639, 0x94d3d1da, 0x5879d144, + 0x52bcd85d, 0x9e16d8c3, 0x1099df20, 0xdc33dfbe, 0x0513cd12, + 0xc9b9cd8c, 0x4736ca6f, 0x8b9ccaf1, 0x8159c3e8, 0x4df3c376, + 0xc37cc495, 0x0fd6c40b, 0x7aa64737, 0xb60c47a9, 0x3883404a, + 0xf42940d4, 0xfeec49cd, 0x32464953, 0xbcc94eb0, 0x70634e2e, + 0xa9435c82, 0x65e95c1c, 0xeb665bff, 0x27cc5b61, 0x2d095278, + 0xe1a352e6, 0x6f2c5505, 0xa386559b, 0x061d761c, 0xcab77682, + 0x44387161, 0x889271ff, 0x825778e6, 0x4efd7878, 0xc0727f9b, + 0x0cd87f05, 0xd5f86da9, 0x19526d37, 0x97dd6ad4, 0x5b776a4a, + 0x51b26353, 0x9d1863cd, 0x1397642e, 0xdf3d64b0, 0x83d02561, + 0x4f7a25ff, 0xc1f5221c, 0x0d5f2282, 0x079a2b9b, 0xcb302b05, + 0x45bf2ce6, 0x89152c78, 0x50353ed4, 0x9c9f3e4a, 0x121039a9, + 0xdeba3937, 0xd47f302e, 0x18d530b0, 0x965a3753, 0x5af037cd, + 0xff6b144a, 0x33c114d4, 0xbd4e1337, 0x71e413a9, 0x7b211ab0, + 0xb78b1a2e, 0x39041dcd, 0xf5ae1d53, 0x2c8e0fff, 0xe0240f61, + 0x6eab0882, 0xa201081c, 0xa8c40105, 0x646e019b, 0xeae10678, + 0x264b06e6}, + {0x00000000, 0xa6770bb4, 0x979f1129, 0x31e81a9d, 0xf44f2413, + 0x52382fa7, 0x63d0353a, 0xc5a73e8e, 0x33ef4e67, 0x959845d3, + 0xa4705f4e, 0x020754fa, 0xc7a06a74, 0x61d761c0, 0x503f7b5d, + 0xf64870e9, 0x67de9cce, 0xc1a9977a, 0xf0418de7, 0x56368653, + 0x9391b8dd, 0x35e6b369, 0x040ea9f4, 0xa279a240, 0x5431d2a9, + 0xf246d91d, 0xc3aec380, 0x65d9c834, 0xa07ef6ba, 0x0609fd0e, + 0x37e1e793, 0x9196ec27, 0xcfbd399c, 0x69ca3228, 0x582228b5, + 0xfe552301, 0x3bf21d8f, 0x9d85163b, 0xac6d0ca6, 0x0a1a0712, + 0xfc5277fb, 0x5a257c4f, 0x6bcd66d2, 0xcdba6d66, 0x081d53e8, + 0xae6a585c, 0x9f8242c1, 0x39f54975, 0xa863a552, 0x0e14aee6, + 0x3ffcb47b, 0x998bbfcf, 0x5c2c8141, 0xfa5b8af5, 0xcbb39068, + 0x6dc49bdc, 0x9b8ceb35, 0x3dfbe081, 0x0c13fa1c, 0xaa64f1a8, + 0x6fc3cf26, 0xc9b4c492, 0xf85cde0f, 0x5e2bd5bb, 0x440b7579, + 0xe27c7ecd, 0xd3946450, 0x75e36fe4, 0xb044516a, 0x16335ade, + 0x27db4043, 0x81ac4bf7, 0x77e43b1e, 0xd19330aa, 0xe07b2a37, + 0x460c2183, 0x83ab1f0d, 0x25dc14b9, 0x14340e24, 0xb2430590, + 0x23d5e9b7, 0x85a2e203, 0xb44af89e, 0x123df32a, 0xd79acda4, + 0x71edc610, 0x4005dc8d, 0xe672d739, 0x103aa7d0, 0xb64dac64, + 0x87a5b6f9, 0x21d2bd4d, 0xe47583c3, 0x42028877, 0x73ea92ea, + 0xd59d995e, 0x8bb64ce5, 0x2dc14751, 0x1c295dcc, 0xba5e5678, + 0x7ff968f6, 0xd98e6342, 0xe86679df, 0x4e11726b, 0xb8590282, + 0x1e2e0936, 0x2fc613ab, 0x89b1181f, 0x4c162691, 0xea612d25, + 0xdb8937b8, 0x7dfe3c0c, 0xec68d02b, 0x4a1fdb9f, 0x7bf7c102, + 0xdd80cab6, 0x1827f438, 0xbe50ff8c, 0x8fb8e511, 0x29cfeea5, + 0xdf879e4c, 0x79f095f8, 0x48188f65, 0xee6f84d1, 0x2bc8ba5f, + 0x8dbfb1eb, 0xbc57ab76, 0x1a20a0c2, 0x8816eaf2, 0x2e61e146, + 0x1f89fbdb, 0xb9fef06f, 0x7c59cee1, 0xda2ec555, 0xebc6dfc8, + 0x4db1d47c, 0xbbf9a495, 0x1d8eaf21, 0x2c66b5bc, 0x8a11be08, + 0x4fb68086, 0xe9c18b32, 0xd82991af, 0x7e5e9a1b, 0xefc8763c, + 0x49bf7d88, 0x78576715, 0xde206ca1, 0x1b87522f, 0xbdf0599b, + 0x8c184306, 0x2a6f48b2, 0xdc27385b, 0x7a5033ef, 0x4bb82972, + 0xedcf22c6, 0x28681c48, 0x8e1f17fc, 0xbff70d61, 0x198006d5, + 0x47abd36e, 0xe1dcd8da, 0xd034c247, 0x7643c9f3, 0xb3e4f77d, + 0x1593fcc9, 0x247be654, 0x820cede0, 0x74449d09, 0xd23396bd, + 0xe3db8c20, 0x45ac8794, 0x800bb91a, 0x267cb2ae, 0x1794a833, + 0xb1e3a387, 0x20754fa0, 0x86024414, 0xb7ea5e89, 0x119d553d, + 0xd43a6bb3, 0x724d6007, 0x43a57a9a, 0xe5d2712e, 0x139a01c7, + 0xb5ed0a73, 0x840510ee, 0x22721b5a, 0xe7d525d4, 0x41a22e60, + 0x704a34fd, 0xd63d3f49, 0xcc1d9f8b, 0x6a6a943f, 0x5b828ea2, + 0xfdf58516, 0x3852bb98, 0x9e25b02c, 0xafcdaab1, 0x09baa105, + 0xfff2d1ec, 0x5985da58, 0x686dc0c5, 0xce1acb71, 0x0bbdf5ff, + 0xadcafe4b, 0x9c22e4d6, 0x3a55ef62, 0xabc30345, 0x0db408f1, + 0x3c5c126c, 0x9a2b19d8, 0x5f8c2756, 0xf9fb2ce2, 0xc813367f, + 0x6e643dcb, 0x982c4d22, 0x3e5b4696, 0x0fb35c0b, 0xa9c457bf, + 0x6c636931, 0xca146285, 0xfbfc7818, 0x5d8b73ac, 0x03a0a617, + 0xa5d7ada3, 0x943fb73e, 0x3248bc8a, 0xf7ef8204, 0x519889b0, + 0x6070932d, 0xc6079899, 0x304fe870, 0x9638e3c4, 0xa7d0f959, + 0x01a7f2ed, 0xc400cc63, 0x6277c7d7, 0x539fdd4a, 0xf5e8d6fe, + 0x647e3ad9, 0xc209316d, 0xf3e12bf0, 0x55962044, 0x90311eca, + 0x3646157e, 0x07ae0fe3, 0xa1d90457, 0x579174be, 0xf1e67f0a, + 0xc00e6597, 0x66796e23, 0xa3de50ad, 0x05a95b19, 0x34414184, + 0x92364a30}, + {0x00000000, 0xcb5cd3a5, 0x4dc8a10b, 0x869472ae, 0x9b914216, + 0x50cd91b3, 0xd659e31d, 0x1d0530b8, 0xec53826d, 0x270f51c8, + 0xa19b2366, 0x6ac7f0c3, 0x77c2c07b, 0xbc9e13de, 0x3a0a6170, + 0xf156b2d5, 0x03d6029b, 0xc88ad13e, 0x4e1ea390, 0x85427035, + 0x9847408d, 0x531b9328, 0xd58fe186, 0x1ed33223, 0xef8580f6, + 0x24d95353, 0xa24d21fd, 0x6911f258, 0x7414c2e0, 0xbf481145, + 0x39dc63eb, 0xf280b04e, 0x07ac0536, 0xccf0d693, 0x4a64a43d, + 0x81387798, 0x9c3d4720, 0x57619485, 0xd1f5e62b, 0x1aa9358e, + 0xebff875b, 0x20a354fe, 0xa6372650, 0x6d6bf5f5, 0x706ec54d, + 0xbb3216e8, 0x3da66446, 0xf6fab7e3, 0x047a07ad, 0xcf26d408, + 0x49b2a6a6, 0x82ee7503, 0x9feb45bb, 0x54b7961e, 0xd223e4b0, + 0x197f3715, 0xe82985c0, 0x23755665, 0xa5e124cb, 0x6ebdf76e, + 0x73b8c7d6, 0xb8e41473, 0x3e7066dd, 0xf52cb578, 0x0f580a6c, + 0xc404d9c9, 0x4290ab67, 0x89cc78c2, 0x94c9487a, 0x5f959bdf, + 0xd901e971, 0x125d3ad4, 0xe30b8801, 0x28575ba4, 0xaec3290a, + 0x659ffaaf, 0x789aca17, 0xb3c619b2, 0x35526b1c, 0xfe0eb8b9, + 0x0c8e08f7, 0xc7d2db52, 0x4146a9fc, 0x8a1a7a59, 0x971f4ae1, + 0x5c439944, 0xdad7ebea, 0x118b384f, 0xe0dd8a9a, 0x2b81593f, + 0xad152b91, 0x6649f834, 0x7b4cc88c, 0xb0101b29, 0x36846987, + 0xfdd8ba22, 0x08f40f5a, 0xc3a8dcff, 0x453cae51, 0x8e607df4, + 0x93654d4c, 0x58399ee9, 0xdeadec47, 0x15f13fe2, 0xe4a78d37, + 0x2ffb5e92, 0xa96f2c3c, 0x6233ff99, 0x7f36cf21, 0xb46a1c84, + 0x32fe6e2a, 0xf9a2bd8f, 0x0b220dc1, 0xc07ede64, 0x46eaacca, + 0x8db67f6f, 0x90b34fd7, 0x5bef9c72, 0xdd7beedc, 0x16273d79, + 0xe7718fac, 0x2c2d5c09, 0xaab92ea7, 0x61e5fd02, 0x7ce0cdba, + 0xb7bc1e1f, 0x31286cb1, 0xfa74bf14, 0x1eb014d8, 0xd5ecc77d, + 0x5378b5d3, 0x98246676, 0x852156ce, 0x4e7d856b, 0xc8e9f7c5, + 0x03b52460, 0xf2e396b5, 0x39bf4510, 0xbf2b37be, 0x7477e41b, + 0x6972d4a3, 0xa22e0706, 0x24ba75a8, 0xefe6a60d, 0x1d661643, + 0xd63ac5e6, 0x50aeb748, 0x9bf264ed, 0x86f75455, 0x4dab87f0, + 0xcb3ff55e, 0x006326fb, 0xf135942e, 0x3a69478b, 0xbcfd3525, + 0x77a1e680, 0x6aa4d638, 0xa1f8059d, 0x276c7733, 0xec30a496, + 0x191c11ee, 0xd240c24b, 0x54d4b0e5, 0x9f886340, 0x828d53f8, + 0x49d1805d, 0xcf45f2f3, 0x04192156, 0xf54f9383, 0x3e134026, + 0xb8873288, 0x73dbe12d, 0x6eded195, 0xa5820230, 0x2316709e, + 0xe84aa33b, 0x1aca1375, 0xd196c0d0, 0x5702b27e, 0x9c5e61db, + 0x815b5163, 0x4a0782c6, 0xcc93f068, 0x07cf23cd, 0xf6999118, + 0x3dc542bd, 0xbb513013, 0x700de3b6, 0x6d08d30e, 0xa65400ab, + 0x20c07205, 0xeb9ca1a0, 0x11e81eb4, 0xdab4cd11, 0x5c20bfbf, + 0x977c6c1a, 0x8a795ca2, 0x41258f07, 0xc7b1fda9, 0x0ced2e0c, + 0xfdbb9cd9, 0x36e74f7c, 0xb0733dd2, 0x7b2fee77, 0x662adecf, + 0xad760d6a, 0x2be27fc4, 0xe0beac61, 0x123e1c2f, 0xd962cf8a, + 0x5ff6bd24, 0x94aa6e81, 0x89af5e39, 0x42f38d9c, 0xc467ff32, + 0x0f3b2c97, 0xfe6d9e42, 0x35314de7, 0xb3a53f49, 0x78f9ecec, + 0x65fcdc54, 0xaea00ff1, 0x28347d5f, 0xe368aefa, 0x16441b82, + 0xdd18c827, 0x5b8cba89, 0x90d0692c, 0x8dd55994, 0x46898a31, + 0xc01df89f, 0x0b412b3a, 0xfa1799ef, 0x314b4a4a, 0xb7df38e4, + 0x7c83eb41, 0x6186dbf9, 0xaada085c, 0x2c4e7af2, 0xe712a957, + 0x15921919, 0xdececabc, 0x585ab812, 0x93066bb7, 0x8e035b0f, + 0x455f88aa, 0xc3cbfa04, 0x089729a1, 0xf9c19b74, 0x329d48d1, + 0xb4093a7f, 0x7f55e9da, 0x6250d962, 0xa90c0ac7, 0x2f987869, + 0xe4c4abcc}, + {0x00000000, 0x3d6029b0, 0x7ac05360, 0x47a07ad0, 0xf580a6c0, + 0xc8e08f70, 0x8f40f5a0, 0xb220dc10, 0x30704bc1, 0x0d106271, + 0x4ab018a1, 0x77d03111, 0xc5f0ed01, 0xf890c4b1, 0xbf30be61, + 0x825097d1, 0x60e09782, 0x5d80be32, 0x1a20c4e2, 0x2740ed52, + 0x95603142, 0xa80018f2, 0xefa06222, 0xd2c04b92, 0x5090dc43, + 0x6df0f5f3, 0x2a508f23, 0x1730a693, 0xa5107a83, 0x98705333, + 0xdfd029e3, 0xe2b00053, 0xc1c12f04, 0xfca106b4, 0xbb017c64, + 0x866155d4, 0x344189c4, 0x0921a074, 0x4e81daa4, 0x73e1f314, + 0xf1b164c5, 0xccd14d75, 0x8b7137a5, 0xb6111e15, 0x0431c205, + 0x3951ebb5, 0x7ef19165, 0x4391b8d5, 0xa121b886, 0x9c419136, + 0xdbe1ebe6, 0xe681c256, 0x54a11e46, 0x69c137f6, 0x2e614d26, + 0x13016496, 0x9151f347, 0xac31daf7, 0xeb91a027, 0xd6f18997, + 0x64d15587, 0x59b17c37, 0x1e1106e7, 0x23712f57, 0x58f35849, + 0x659371f9, 0x22330b29, 0x1f532299, 0xad73fe89, 0x9013d739, + 0xd7b3ade9, 0xead38459, 0x68831388, 0x55e33a38, 0x124340e8, + 0x2f236958, 0x9d03b548, 0xa0639cf8, 0xe7c3e628, 0xdaa3cf98, + 0x3813cfcb, 0x0573e67b, 0x42d39cab, 0x7fb3b51b, 0xcd93690b, + 0xf0f340bb, 0xb7533a6b, 0x8a3313db, 0x0863840a, 0x3503adba, + 0x72a3d76a, 0x4fc3feda, 0xfde322ca, 0xc0830b7a, 0x872371aa, + 0xba43581a, 0x9932774d, 0xa4525efd, 0xe3f2242d, 0xde920d9d, + 0x6cb2d18d, 0x51d2f83d, 0x167282ed, 0x2b12ab5d, 0xa9423c8c, + 0x9422153c, 0xd3826fec, 0xeee2465c, 0x5cc29a4c, 0x61a2b3fc, + 0x2602c92c, 0x1b62e09c, 0xf9d2e0cf, 0xc4b2c97f, 0x8312b3af, + 0xbe729a1f, 0x0c52460f, 0x31326fbf, 0x7692156f, 0x4bf23cdf, + 0xc9a2ab0e, 0xf4c282be, 0xb362f86e, 0x8e02d1de, 0x3c220dce, + 0x0142247e, 0x46e25eae, 0x7b82771e, 0xb1e6b092, 0x8c869922, + 0xcb26e3f2, 0xf646ca42, 0x44661652, 0x79063fe2, 0x3ea64532, + 0x03c66c82, 0x8196fb53, 0xbcf6d2e3, 0xfb56a833, 0xc6368183, + 0x74165d93, 0x49767423, 0x0ed60ef3, 0x33b62743, 0xd1062710, + 0xec660ea0, 0xabc67470, 0x96a65dc0, 0x248681d0, 0x19e6a860, + 0x5e46d2b0, 0x6326fb00, 0xe1766cd1, 0xdc164561, 0x9bb63fb1, + 0xa6d61601, 0x14f6ca11, 0x2996e3a1, 0x6e369971, 0x5356b0c1, + 0x70279f96, 0x4d47b626, 0x0ae7ccf6, 0x3787e546, 0x85a73956, + 0xb8c710e6, 0xff676a36, 0xc2074386, 0x4057d457, 0x7d37fde7, + 0x3a978737, 0x07f7ae87, 0xb5d77297, 0x88b75b27, 0xcf1721f7, + 0xf2770847, 0x10c70814, 0x2da721a4, 0x6a075b74, 0x576772c4, + 0xe547aed4, 0xd8278764, 0x9f87fdb4, 0xa2e7d404, 0x20b743d5, + 0x1dd76a65, 0x5a7710b5, 0x67173905, 0xd537e515, 0xe857cca5, + 0xaff7b675, 0x92979fc5, 0xe915e8db, 0xd475c16b, 0x93d5bbbb, + 0xaeb5920b, 0x1c954e1b, 0x21f567ab, 0x66551d7b, 0x5b3534cb, + 0xd965a31a, 0xe4058aaa, 0xa3a5f07a, 0x9ec5d9ca, 0x2ce505da, + 0x11852c6a, 0x562556ba, 0x6b457f0a, 0x89f57f59, 0xb49556e9, + 0xf3352c39, 0xce550589, 0x7c75d999, 0x4115f029, 0x06b58af9, + 0x3bd5a349, 0xb9853498, 0x84e51d28, 0xc34567f8, 0xfe254e48, + 0x4c059258, 0x7165bbe8, 0x36c5c138, 0x0ba5e888, 0x28d4c7df, + 0x15b4ee6f, 0x521494bf, 0x6f74bd0f, 0xdd54611f, 0xe03448af, + 0xa794327f, 0x9af41bcf, 0x18a48c1e, 0x25c4a5ae, 0x6264df7e, + 0x5f04f6ce, 0xed242ade, 0xd044036e, 0x97e479be, 0xaa84500e, + 0x4834505d, 0x755479ed, 0x32f4033d, 0x0f942a8d, 0xbdb4f69d, + 0x80d4df2d, 0xc774a5fd, 0xfa148c4d, 0x78441b9c, 0x4524322c, + 0x028448fc, 0x3fe4614c, 0x8dc4bd5c, 0xb0a494ec, 0xf704ee3c, + 0xca64c78c}, + {0x00000000, 0xb8bc6765, 0xaa09c88b, 0x12b5afee, 0x8f629757, + 0x37def032, 0x256b5fdc, 0x9dd738b9, 0xc5b428ef, 0x7d084f8a, + 0x6fbde064, 0xd7018701, 0x4ad6bfb8, 0xf26ad8dd, 0xe0df7733, + 0x58631056, 0x5019579f, 0xe8a530fa, 0xfa109f14, 0x42acf871, + 0xdf7bc0c8, 0x67c7a7ad, 0x75720843, 0xcdce6f26, 0x95ad7f70, + 0x2d111815, 0x3fa4b7fb, 0x8718d09e, 0x1acfe827, 0xa2738f42, + 0xb0c620ac, 0x087a47c9, 0xa032af3e, 0x188ec85b, 0x0a3b67b5, + 0xb28700d0, 0x2f503869, 0x97ec5f0c, 0x8559f0e2, 0x3de59787, + 0x658687d1, 0xdd3ae0b4, 0xcf8f4f5a, 0x7733283f, 0xeae41086, + 0x525877e3, 0x40edd80d, 0xf851bf68, 0xf02bf8a1, 0x48979fc4, + 0x5a22302a, 0xe29e574f, 0x7f496ff6, 0xc7f50893, 0xd540a77d, + 0x6dfcc018, 0x359fd04e, 0x8d23b72b, 0x9f9618c5, 0x272a7fa0, + 0xbafd4719, 0x0241207c, 0x10f48f92, 0xa848e8f7, 0x9b14583d, + 0x23a83f58, 0x311d90b6, 0x89a1f7d3, 0x1476cf6a, 0xaccaa80f, + 0xbe7f07e1, 0x06c36084, 0x5ea070d2, 0xe61c17b7, 0xf4a9b859, + 0x4c15df3c, 0xd1c2e785, 0x697e80e0, 0x7bcb2f0e, 0xc377486b, + 0xcb0d0fa2, 0x73b168c7, 0x6104c729, 0xd9b8a04c, 0x446f98f5, + 0xfcd3ff90, 0xee66507e, 0x56da371b, 0x0eb9274d, 0xb6054028, + 0xa4b0efc6, 0x1c0c88a3, 0x81dbb01a, 0x3967d77f, 0x2bd27891, + 0x936e1ff4, 0x3b26f703, 0x839a9066, 0x912f3f88, 0x299358ed, + 0xb4446054, 0x0cf80731, 0x1e4da8df, 0xa6f1cfba, 0xfe92dfec, + 0x462eb889, 0x549b1767, 0xec277002, 0x71f048bb, 0xc94c2fde, + 0xdbf98030, 0x6345e755, 0x6b3fa09c, 0xd383c7f9, 0xc1366817, + 0x798a0f72, 0xe45d37cb, 0x5ce150ae, 0x4e54ff40, 0xf6e89825, + 0xae8b8873, 0x1637ef16, 0x048240f8, 0xbc3e279d, 0x21e91f24, + 0x99557841, 0x8be0d7af, 0x335cb0ca, 0xed59b63b, 0x55e5d15e, + 0x47507eb0, 0xffec19d5, 0x623b216c, 0xda874609, 0xc832e9e7, + 0x708e8e82, 0x28ed9ed4, 0x9051f9b1, 0x82e4565f, 0x3a58313a, + 0xa78f0983, 0x1f336ee6, 0x0d86c108, 0xb53aa66d, 0xbd40e1a4, + 0x05fc86c1, 0x1749292f, 0xaff54e4a, 0x322276f3, 0x8a9e1196, + 0x982bbe78, 0x2097d91d, 0x78f4c94b, 0xc048ae2e, 0xd2fd01c0, + 0x6a4166a5, 0xf7965e1c, 0x4f2a3979, 0x5d9f9697, 0xe523f1f2, + 0x4d6b1905, 0xf5d77e60, 0xe762d18e, 0x5fdeb6eb, 0xc2098e52, + 0x7ab5e937, 0x680046d9, 0xd0bc21bc, 0x88df31ea, 0x3063568f, + 0x22d6f961, 0x9a6a9e04, 0x07bda6bd, 0xbf01c1d8, 0xadb46e36, + 0x15080953, 0x1d724e9a, 0xa5ce29ff, 0xb77b8611, 0x0fc7e174, + 0x9210d9cd, 0x2aacbea8, 0x38191146, 0x80a57623, 0xd8c66675, + 0x607a0110, 0x72cfaefe, 0xca73c99b, 0x57a4f122, 0xef189647, + 0xfdad39a9, 0x45115ecc, 0x764dee06, 0xcef18963, 0xdc44268d, + 0x64f841e8, 0xf92f7951, 0x41931e34, 0x5326b1da, 0xeb9ad6bf, + 0xb3f9c6e9, 0x0b45a18c, 0x19f00e62, 0xa14c6907, 0x3c9b51be, + 0x842736db, 0x96929935, 0x2e2efe50, 0x2654b999, 0x9ee8defc, + 0x8c5d7112, 0x34e11677, 0xa9362ece, 0x118a49ab, 0x033fe645, + 0xbb838120, 0xe3e09176, 0x5b5cf613, 0x49e959fd, 0xf1553e98, + 0x6c820621, 0xd43e6144, 0xc68bceaa, 0x7e37a9cf, 0xd67f4138, + 0x6ec3265d, 0x7c7689b3, 0xc4caeed6, 0x591dd66f, 0xe1a1b10a, + 0xf3141ee4, 0x4ba87981, 0x13cb69d7, 0xab770eb2, 0xb9c2a15c, + 0x017ec639, 0x9ca9fe80, 0x241599e5, 0x36a0360b, 0x8e1c516e, + 0x866616a7, 0x3eda71c2, 0x2c6fde2c, 0x94d3b949, 0x090481f0, + 0xb1b8e695, 0xa30d497b, 0x1bb12e1e, 0x43d23e48, 0xfb6e592d, + 0xe9dbf6c3, 0x516791a6, 0xccb0a91f, 0x740cce7a, 0x66b96194, + 0xde0506f1}, + {0x00000000, 0x01c26a37, 0x0384d46e, 0x0246be59, 0x0709a8dc, + 0x06cbc2eb, 0x048d7cb2, 0x054f1685, 0x0e1351b8, 0x0fd13b8f, + 0x0d9785d6, 0x0c55efe1, 0x091af964, 0x08d89353, 0x0a9e2d0a, + 0x0b5c473d, 0x1c26a370, 0x1de4c947, 0x1fa2771e, 0x1e601d29, + 0x1b2f0bac, 0x1aed619b, 0x18abdfc2, 0x1969b5f5, 0x1235f2c8, + 0x13f798ff, 0x11b126a6, 0x10734c91, 0x153c5a14, 0x14fe3023, + 0x16b88e7a, 0x177ae44d, 0x384d46e0, 0x398f2cd7, 0x3bc9928e, + 0x3a0bf8b9, 0x3f44ee3c, 0x3e86840b, 0x3cc03a52, 0x3d025065, + 0x365e1758, 0x379c7d6f, 0x35dac336, 0x3418a901, 0x3157bf84, + 0x3095d5b3, 0x32d36bea, 0x331101dd, 0x246be590, 0x25a98fa7, + 0x27ef31fe, 0x262d5bc9, 0x23624d4c, 0x22a0277b, 0x20e69922, + 0x2124f315, 0x2a78b428, 0x2bbade1f, 0x29fc6046, 0x283e0a71, + 0x2d711cf4, 0x2cb376c3, 0x2ef5c89a, 0x2f37a2ad, 0x709a8dc0, + 0x7158e7f7, 0x731e59ae, 0x72dc3399, 0x7793251c, 0x76514f2b, + 0x7417f172, 0x75d59b45, 0x7e89dc78, 0x7f4bb64f, 0x7d0d0816, + 0x7ccf6221, 0x798074a4, 0x78421e93, 0x7a04a0ca, 0x7bc6cafd, + 0x6cbc2eb0, 0x6d7e4487, 0x6f38fade, 0x6efa90e9, 0x6bb5866c, + 0x6a77ec5b, 0x68315202, 0x69f33835, 0x62af7f08, 0x636d153f, + 0x612bab66, 0x60e9c151, 0x65a6d7d4, 0x6464bde3, 0x662203ba, + 0x67e0698d, 0x48d7cb20, 0x4915a117, 0x4b531f4e, 0x4a917579, + 0x4fde63fc, 0x4e1c09cb, 0x4c5ab792, 0x4d98dda5, 0x46c49a98, + 0x4706f0af, 0x45404ef6, 0x448224c1, 0x41cd3244, 0x400f5873, + 0x4249e62a, 0x438b8c1d, 0x54f16850, 0x55330267, 0x5775bc3e, + 0x56b7d609, 0x53f8c08c, 0x523aaabb, 0x507c14e2, 0x51be7ed5, + 0x5ae239e8, 0x5b2053df, 0x5966ed86, 0x58a487b1, 0x5deb9134, + 0x5c29fb03, 0x5e6f455a, 0x5fad2f6d, 0xe1351b80, 0xe0f771b7, + 0xe2b1cfee, 0xe373a5d9, 0xe63cb35c, 0xe7fed96b, 0xe5b86732, + 0xe47a0d05, 0xef264a38, 0xeee4200f, 0xeca29e56, 0xed60f461, + 0xe82fe2e4, 0xe9ed88d3, 0xebab368a, 0xea695cbd, 0xfd13b8f0, + 0xfcd1d2c7, 0xfe976c9e, 0xff5506a9, 0xfa1a102c, 0xfbd87a1b, + 0xf99ec442, 0xf85cae75, 0xf300e948, 0xf2c2837f, 0xf0843d26, + 0xf1465711, 0xf4094194, 0xf5cb2ba3, 0xf78d95fa, 0xf64fffcd, + 0xd9785d60, 0xd8ba3757, 0xdafc890e, 0xdb3ee339, 0xde71f5bc, + 0xdfb39f8b, 0xddf521d2, 0xdc374be5, 0xd76b0cd8, 0xd6a966ef, + 0xd4efd8b6, 0xd52db281, 0xd062a404, 0xd1a0ce33, 0xd3e6706a, + 0xd2241a5d, 0xc55efe10, 0xc49c9427, 0xc6da2a7e, 0xc7184049, + 0xc25756cc, 0xc3953cfb, 0xc1d382a2, 0xc011e895, 0xcb4dafa8, + 0xca8fc59f, 0xc8c97bc6, 0xc90b11f1, 0xcc440774, 0xcd866d43, + 0xcfc0d31a, 0xce02b92d, 0x91af9640, 0x906dfc77, 0x922b422e, + 0x93e92819, 0x96a63e9c, 0x976454ab, 0x9522eaf2, 0x94e080c5, + 0x9fbcc7f8, 0x9e7eadcf, 0x9c381396, 0x9dfa79a1, 0x98b56f24, + 0x99770513, 0x9b31bb4a, 0x9af3d17d, 0x8d893530, 0x8c4b5f07, + 0x8e0de15e, 0x8fcf8b69, 0x8a809dec, 0x8b42f7db, 0x89044982, + 0x88c623b5, 0x839a6488, 0x82580ebf, 0x801eb0e6, 0x81dcdad1, + 0x8493cc54, 0x8551a663, 0x8717183a, 0x86d5720d, 0xa9e2d0a0, + 0xa820ba97, 0xaa6604ce, 0xaba46ef9, 0xaeeb787c, 0xaf29124b, + 0xad6fac12, 0xacadc625, 0xa7f18118, 0xa633eb2f, 0xa4755576, + 0xa5b73f41, 0xa0f829c4, 0xa13a43f3, 0xa37cfdaa, 0xa2be979d, + 0xb5c473d0, 0xb40619e7, 0xb640a7be, 0xb782cd89, 0xb2cddb0c, + 0xb30fb13b, 0xb1490f62, 0xb08b6555, 0xbbd72268, 0xba15485f, + 0xb853f606, 0xb9919c31, 0xbcde8ab4, 0xbd1ce083, 0xbf5a5eda, + 0xbe9834ed}, + {0x00000000, 0x191b3141, 0x32366282, 0x2b2d53c3, 0x646cc504, + 0x7d77f445, 0x565aa786, 0x4f4196c7, 0xc8d98a08, 0xd1c2bb49, + 0xfaefe88a, 0xe3f4d9cb, 0xacb54f0c, 0xb5ae7e4d, 0x9e832d8e, + 0x87981ccf, 0x4ac21251, 0x53d92310, 0x78f470d3, 0x61ef4192, + 0x2eaed755, 0x37b5e614, 0x1c98b5d7, 0x05838496, 0x821b9859, + 0x9b00a918, 0xb02dfadb, 0xa936cb9a, 0xe6775d5d, 0xff6c6c1c, + 0xd4413fdf, 0xcd5a0e9e, 0x958424a2, 0x8c9f15e3, 0xa7b24620, + 0xbea97761, 0xf1e8e1a6, 0xe8f3d0e7, 0xc3de8324, 0xdac5b265, + 0x5d5daeaa, 0x44469feb, 0x6f6bcc28, 0x7670fd69, 0x39316bae, + 0x202a5aef, 0x0b07092c, 0x121c386d, 0xdf4636f3, 0xc65d07b2, + 0xed705471, 0xf46b6530, 0xbb2af3f7, 0xa231c2b6, 0x891c9175, + 0x9007a034, 0x179fbcfb, 0x0e848dba, 0x25a9de79, 0x3cb2ef38, + 0x73f379ff, 0x6ae848be, 0x41c51b7d, 0x58de2a3c, 0xf0794f05, + 0xe9627e44, 0xc24f2d87, 0xdb541cc6, 0x94158a01, 0x8d0ebb40, + 0xa623e883, 0xbf38d9c2, 0x38a0c50d, 0x21bbf44c, 0x0a96a78f, + 0x138d96ce, 0x5ccc0009, 0x45d73148, 0x6efa628b, 0x77e153ca, + 0xbabb5d54, 0xa3a06c15, 0x888d3fd6, 0x91960e97, 0xded79850, + 0xc7cca911, 0xece1fad2, 0xf5facb93, 0x7262d75c, 0x6b79e61d, + 0x4054b5de, 0x594f849f, 0x160e1258, 0x0f152319, 0x243870da, + 0x3d23419b, 0x65fd6ba7, 0x7ce65ae6, 0x57cb0925, 0x4ed03864, + 0x0191aea3, 0x188a9fe2, 0x33a7cc21, 0x2abcfd60, 0xad24e1af, + 0xb43fd0ee, 0x9f12832d, 0x8609b26c, 0xc94824ab, 0xd05315ea, + 0xfb7e4629, 0xe2657768, 0x2f3f79f6, 0x362448b7, 0x1d091b74, + 0x04122a35, 0x4b53bcf2, 0x52488db3, 0x7965de70, 0x607eef31, + 0xe7e6f3fe, 0xfefdc2bf, 0xd5d0917c, 0xcccba03d, 0x838a36fa, + 0x9a9107bb, 0xb1bc5478, 0xa8a76539, 0x3b83984b, 0x2298a90a, + 0x09b5fac9, 0x10aecb88, 0x5fef5d4f, 0x46f46c0e, 0x6dd93fcd, + 0x74c20e8c, 0xf35a1243, 0xea412302, 0xc16c70c1, 0xd8774180, + 0x9736d747, 0x8e2de606, 0xa500b5c5, 0xbc1b8484, 0x71418a1a, + 0x685abb5b, 0x4377e898, 0x5a6cd9d9, 0x152d4f1e, 0x0c367e5f, + 0x271b2d9c, 0x3e001cdd, 0xb9980012, 0xa0833153, 0x8bae6290, + 0x92b553d1, 0xddf4c516, 0xc4eff457, 0xefc2a794, 0xf6d996d5, + 0xae07bce9, 0xb71c8da8, 0x9c31de6b, 0x852aef2a, 0xca6b79ed, + 0xd37048ac, 0xf85d1b6f, 0xe1462a2e, 0x66de36e1, 0x7fc507a0, + 0x54e85463, 0x4df36522, 0x02b2f3e5, 0x1ba9c2a4, 0x30849167, + 0x299fa026, 0xe4c5aeb8, 0xfdde9ff9, 0xd6f3cc3a, 0xcfe8fd7b, + 0x80a96bbc, 0x99b25afd, 0xb29f093e, 0xab84387f, 0x2c1c24b0, + 0x350715f1, 0x1e2a4632, 0x07317773, 0x4870e1b4, 0x516bd0f5, + 0x7a468336, 0x635db277, 0xcbfad74e, 0xd2e1e60f, 0xf9ccb5cc, + 0xe0d7848d, 0xaf96124a, 0xb68d230b, 0x9da070c8, 0x84bb4189, + 0x03235d46, 0x1a386c07, 0x31153fc4, 0x280e0e85, 0x674f9842, + 0x7e54a903, 0x5579fac0, 0x4c62cb81, 0x8138c51f, 0x9823f45e, + 0xb30ea79d, 0xaa1596dc, 0xe554001b, 0xfc4f315a, 0xd7626299, + 0xce7953d8, 0x49e14f17, 0x50fa7e56, 0x7bd72d95, 0x62cc1cd4, + 0x2d8d8a13, 0x3496bb52, 0x1fbbe891, 0x06a0d9d0, 0x5e7ef3ec, + 0x4765c2ad, 0x6c48916e, 0x7553a02f, 0x3a1236e8, 0x230907a9, + 0x0824546a, 0x113f652b, 0x96a779e4, 0x8fbc48a5, 0xa4911b66, + 0xbd8a2a27, 0xf2cbbce0, 0xebd08da1, 0xc0fdde62, 0xd9e6ef23, + 0x14bce1bd, 0x0da7d0fc, 0x268a833f, 0x3f91b27e, 0x70d024b9, + 0x69cb15f8, 0x42e6463b, 0x5bfd777a, 0xdc656bb5, 0xc57e5af4, + 0xee530937, 0xf7483876, 0xb809aeb1, 0xa1129ff0, 0x8a3fcc33, + 0x9324fd72}, + {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}}; + +local const z_word_t FAR crc_braid_big_table[][256] = { + {0x0000000000000000, 0x9630077700000000, 0x2c610eee00000000, + 0xba51099900000000, 0x19c46d0700000000, 0x8ff46a7000000000, + 0x35a563e900000000, 0xa395649e00000000, 0x3288db0e00000000, + 0xa4b8dc7900000000, 0x1ee9d5e000000000, 0x88d9d29700000000, + 0x2b4cb60900000000, 0xbd7cb17e00000000, 0x072db8e700000000, + 0x911dbf9000000000, 0x6410b71d00000000, 0xf220b06a00000000, + 0x4871b9f300000000, 0xde41be8400000000, 0x7dd4da1a00000000, + 0xebe4dd6d00000000, 0x51b5d4f400000000, 0xc785d38300000000, + 0x56986c1300000000, 0xc0a86b6400000000, 0x7af962fd00000000, + 0xecc9658a00000000, 0x4f5c011400000000, 0xd96c066300000000, + 0x633d0ffa00000000, 0xf50d088d00000000, 0xc8206e3b00000000, + 0x5e10694c00000000, 0xe44160d500000000, 0x727167a200000000, + 0xd1e4033c00000000, 0x47d4044b00000000, 0xfd850dd200000000, + 0x6bb50aa500000000, 0xfaa8b53500000000, 0x6c98b24200000000, + 0xd6c9bbdb00000000, 0x40f9bcac00000000, 0xe36cd83200000000, + 0x755cdf4500000000, 0xcf0dd6dc00000000, 0x593dd1ab00000000, + 0xac30d92600000000, 0x3a00de5100000000, 0x8051d7c800000000, + 0x1661d0bf00000000, 0xb5f4b42100000000, 0x23c4b35600000000, + 0x9995bacf00000000, 0x0fa5bdb800000000, 0x9eb8022800000000, + 0x0888055f00000000, 0xb2d90cc600000000, 0x24e90bb100000000, + 0x877c6f2f00000000, 0x114c685800000000, 0xab1d61c100000000, + 0x3d2d66b600000000, 0x9041dc7600000000, 0x0671db0100000000, + 0xbc20d29800000000, 0x2a10d5ef00000000, 0x8985b17100000000, + 0x1fb5b60600000000, 0xa5e4bf9f00000000, 0x33d4b8e800000000, + 0xa2c9077800000000, 0x34f9000f00000000, 0x8ea8099600000000, + 0x18980ee100000000, 0xbb0d6a7f00000000, 0x2d3d6d0800000000, + 0x976c649100000000, 0x015c63e600000000, 0xf4516b6b00000000, + 0x62616c1c00000000, 0xd830658500000000, 0x4e0062f200000000, + 0xed95066c00000000, 0x7ba5011b00000000, 0xc1f4088200000000, + 0x57c40ff500000000, 0xc6d9b06500000000, 0x50e9b71200000000, + 0xeab8be8b00000000, 0x7c88b9fc00000000, 0xdf1ddd6200000000, + 0x492dda1500000000, 0xf37cd38c00000000, 0x654cd4fb00000000, + 0x5861b24d00000000, 0xce51b53a00000000, 0x7400bca300000000, + 0xe230bbd400000000, 0x41a5df4a00000000, 0xd795d83d00000000, + 0x6dc4d1a400000000, 0xfbf4d6d300000000, 0x6ae9694300000000, + 0xfcd96e3400000000, 0x468867ad00000000, 0xd0b860da00000000, + 0x732d044400000000, 0xe51d033300000000, 0x5f4c0aaa00000000, + 0xc97c0ddd00000000, 0x3c71055000000000, 0xaa41022700000000, + 0x10100bbe00000000, 0x86200cc900000000, 0x25b5685700000000, + 0xb3856f2000000000, 0x09d466b900000000, 0x9fe461ce00000000, + 0x0ef9de5e00000000, 0x98c9d92900000000, 0x2298d0b000000000, + 0xb4a8d7c700000000, 0x173db35900000000, 0x810db42e00000000, + 0x3b5cbdb700000000, 0xad6cbac000000000, 0x2083b8ed00000000, + 0xb6b3bf9a00000000, 0x0ce2b60300000000, 0x9ad2b17400000000, + 0x3947d5ea00000000, 0xaf77d29d00000000, 0x1526db0400000000, + 0x8316dc7300000000, 0x120b63e300000000, 0x843b649400000000, + 0x3e6a6d0d00000000, 0xa85a6a7a00000000, 0x0bcf0ee400000000, + 0x9dff099300000000, 0x27ae000a00000000, 0xb19e077d00000000, + 0x44930ff000000000, 0xd2a3088700000000, 0x68f2011e00000000, + 0xfec2066900000000, 0x5d5762f700000000, 0xcb67658000000000, + 0x71366c1900000000, 0xe7066b6e00000000, 0x761bd4fe00000000, + 0xe02bd38900000000, 0x5a7ada1000000000, 0xcc4add6700000000, + 0x6fdfb9f900000000, 0xf9efbe8e00000000, 0x43beb71700000000, + 0xd58eb06000000000, 0xe8a3d6d600000000, 0x7e93d1a100000000, + 0xc4c2d83800000000, 0x52f2df4f00000000, 0xf167bbd100000000, + 0x6757bca600000000, 0xdd06b53f00000000, 0x4b36b24800000000, + 0xda2b0dd800000000, 0x4c1b0aaf00000000, 0xf64a033600000000, + 0x607a044100000000, 0xc3ef60df00000000, 0x55df67a800000000, + 0xef8e6e3100000000, 0x79be694600000000, 0x8cb361cb00000000, + 0x1a8366bc00000000, 0xa0d26f2500000000, 0x36e2685200000000, + 0x95770ccc00000000, 0x03470bbb00000000, 0xb916022200000000, + 0x2f26055500000000, 0xbe3bbac500000000, 0x280bbdb200000000, + 0x925ab42b00000000, 0x046ab35c00000000, 0xa7ffd7c200000000, + 0x31cfd0b500000000, 0x8b9ed92c00000000, 0x1daede5b00000000, + 0xb0c2649b00000000, 0x26f263ec00000000, 0x9ca36a7500000000, + 0x0a936d0200000000, 0xa906099c00000000, 0x3f360eeb00000000, + 0x8567077200000000, 0x1357000500000000, 0x824abf9500000000, + 0x147ab8e200000000, 0xae2bb17b00000000, 0x381bb60c00000000, + 0x9b8ed29200000000, 0x0dbed5e500000000, 0xb7efdc7c00000000, + 0x21dfdb0b00000000, 0xd4d2d38600000000, 0x42e2d4f100000000, + 0xf8b3dd6800000000, 0x6e83da1f00000000, 0xcd16be8100000000, + 0x5b26b9f600000000, 0xe177b06f00000000, 0x7747b71800000000, + 0xe65a088800000000, 0x706a0fff00000000, 0xca3b066600000000, + 0x5c0b011100000000, 0xff9e658f00000000, 0x69ae62f800000000, + 0xd3ff6b6100000000, 0x45cf6c1600000000, 0x78e20aa000000000, + 0xeed20dd700000000, 0x5483044e00000000, 0xc2b3033900000000, + 0x612667a700000000, 0xf71660d000000000, 0x4d47694900000000, + 0xdb776e3e00000000, 0x4a6ad1ae00000000, 0xdc5ad6d900000000, + 0x660bdf4000000000, 0xf03bd83700000000, 0x53aebca900000000, + 0xc59ebbde00000000, 0x7fcfb24700000000, 0xe9ffb53000000000, + 0x1cf2bdbd00000000, 0x8ac2baca00000000, 0x3093b35300000000, + 0xa6a3b42400000000, 0x0536d0ba00000000, 0x9306d7cd00000000, + 0x2957de5400000000, 0xbf67d92300000000, 0x2e7a66b300000000, + 0xb84a61c400000000, 0x021b685d00000000, 0x942b6f2a00000000, + 0x37be0bb400000000, 0xa18e0cc300000000, 0x1bdf055a00000000, + 0x8def022d00000000}, + {0x0000000000000000, 0x41311b1900000000, 0x8262363200000000, + 0xc3532d2b00000000, 0x04c56c6400000000, 0x45f4777d00000000, + 0x86a75a5600000000, 0xc796414f00000000, 0x088ad9c800000000, + 0x49bbc2d100000000, 0x8ae8effa00000000, 0xcbd9f4e300000000, + 0x0c4fb5ac00000000, 0x4d7eaeb500000000, 0x8e2d839e00000000, + 0xcf1c988700000000, 0x5112c24a00000000, 0x1023d95300000000, + 0xd370f47800000000, 0x9241ef6100000000, 0x55d7ae2e00000000, + 0x14e6b53700000000, 0xd7b5981c00000000, 0x9684830500000000, + 0x59981b8200000000, 0x18a9009b00000000, 0xdbfa2db000000000, + 0x9acb36a900000000, 0x5d5d77e600000000, 0x1c6c6cff00000000, + 0xdf3f41d400000000, 0x9e0e5acd00000000, 0xa224849500000000, + 0xe3159f8c00000000, 0x2046b2a700000000, 0x6177a9be00000000, + 0xa6e1e8f100000000, 0xe7d0f3e800000000, 0x2483dec300000000, + 0x65b2c5da00000000, 0xaaae5d5d00000000, 0xeb9f464400000000, + 0x28cc6b6f00000000, 0x69fd707600000000, 0xae6b313900000000, + 0xef5a2a2000000000, 0x2c09070b00000000, 0x6d381c1200000000, + 0xf33646df00000000, 0xb2075dc600000000, 0x715470ed00000000, + 0x30656bf400000000, 0xf7f32abb00000000, 0xb6c231a200000000, + 0x75911c8900000000, 0x34a0079000000000, 0xfbbc9f1700000000, + 0xba8d840e00000000, 0x79dea92500000000, 0x38efb23c00000000, + 0xff79f37300000000, 0xbe48e86a00000000, 0x7d1bc54100000000, + 0x3c2ade5800000000, 0x054f79f000000000, 0x447e62e900000000, + 0x872d4fc200000000, 0xc61c54db00000000, 0x018a159400000000, + 0x40bb0e8d00000000, 0x83e823a600000000, 0xc2d938bf00000000, + 0x0dc5a03800000000, 0x4cf4bb2100000000, 0x8fa7960a00000000, + 0xce968d1300000000, 0x0900cc5c00000000, 0x4831d74500000000, + 0x8b62fa6e00000000, 0xca53e17700000000, 0x545dbbba00000000, + 0x156ca0a300000000, 0xd63f8d8800000000, 0x970e969100000000, + 0x5098d7de00000000, 0x11a9ccc700000000, 0xd2fae1ec00000000, + 0x93cbfaf500000000, 0x5cd7627200000000, 0x1de6796b00000000, + 0xdeb5544000000000, 0x9f844f5900000000, 0x58120e1600000000, + 0x1923150f00000000, 0xda70382400000000, 0x9b41233d00000000, + 0xa76bfd6500000000, 0xe65ae67c00000000, 0x2509cb5700000000, + 0x6438d04e00000000, 0xa3ae910100000000, 0xe29f8a1800000000, + 0x21cca73300000000, 0x60fdbc2a00000000, 0xafe124ad00000000, + 0xeed03fb400000000, 0x2d83129f00000000, 0x6cb2098600000000, + 0xab2448c900000000, 0xea1553d000000000, 0x29467efb00000000, + 0x687765e200000000, 0xf6793f2f00000000, 0xb748243600000000, + 0x741b091d00000000, 0x352a120400000000, 0xf2bc534b00000000, + 0xb38d485200000000, 0x70de657900000000, 0x31ef7e6000000000, + 0xfef3e6e700000000, 0xbfc2fdfe00000000, 0x7c91d0d500000000, + 0x3da0cbcc00000000, 0xfa368a8300000000, 0xbb07919a00000000, + 0x7854bcb100000000, 0x3965a7a800000000, 0x4b98833b00000000, + 0x0aa9982200000000, 0xc9fab50900000000, 0x88cbae1000000000, + 0x4f5def5f00000000, 0x0e6cf44600000000, 0xcd3fd96d00000000, + 0x8c0ec27400000000, 0x43125af300000000, 0x022341ea00000000, + 0xc1706cc100000000, 0x804177d800000000, 0x47d7369700000000, + 0x06e62d8e00000000, 0xc5b500a500000000, 0x84841bbc00000000, + 0x1a8a417100000000, 0x5bbb5a6800000000, 0x98e8774300000000, + 0xd9d96c5a00000000, 0x1e4f2d1500000000, 0x5f7e360c00000000, + 0x9c2d1b2700000000, 0xdd1c003e00000000, 0x120098b900000000, + 0x533183a000000000, 0x9062ae8b00000000, 0xd153b59200000000, + 0x16c5f4dd00000000, 0x57f4efc400000000, 0x94a7c2ef00000000, + 0xd596d9f600000000, 0xe9bc07ae00000000, 0xa88d1cb700000000, + 0x6bde319c00000000, 0x2aef2a8500000000, 0xed796bca00000000, + 0xac4870d300000000, 0x6f1b5df800000000, 0x2e2a46e100000000, + 0xe136de6600000000, 0xa007c57f00000000, 0x6354e85400000000, + 0x2265f34d00000000, 0xe5f3b20200000000, 0xa4c2a91b00000000, + 0x6791843000000000, 0x26a09f2900000000, 0xb8aec5e400000000, + 0xf99fdefd00000000, 0x3accf3d600000000, 0x7bfde8cf00000000, + 0xbc6ba98000000000, 0xfd5ab29900000000, 0x3e099fb200000000, + 0x7f3884ab00000000, 0xb0241c2c00000000, 0xf115073500000000, + 0x32462a1e00000000, 0x7377310700000000, 0xb4e1704800000000, + 0xf5d06b5100000000, 0x3683467a00000000, 0x77b25d6300000000, + 0x4ed7facb00000000, 0x0fe6e1d200000000, 0xccb5ccf900000000, + 0x8d84d7e000000000, 0x4a1296af00000000, 0x0b238db600000000, + 0xc870a09d00000000, 0x8941bb8400000000, 0x465d230300000000, + 0x076c381a00000000, 0xc43f153100000000, 0x850e0e2800000000, + 0x42984f6700000000, 0x03a9547e00000000, 0xc0fa795500000000, + 0x81cb624c00000000, 0x1fc5388100000000, 0x5ef4239800000000, + 0x9da70eb300000000, 0xdc9615aa00000000, 0x1b0054e500000000, + 0x5a314ffc00000000, 0x996262d700000000, 0xd85379ce00000000, + 0x174fe14900000000, 0x567efa5000000000, 0x952dd77b00000000, + 0xd41ccc6200000000, 0x138a8d2d00000000, 0x52bb963400000000, + 0x91e8bb1f00000000, 0xd0d9a00600000000, 0xecf37e5e00000000, + 0xadc2654700000000, 0x6e91486c00000000, 0x2fa0537500000000, + 0xe836123a00000000, 0xa907092300000000, 0x6a54240800000000, + 0x2b653f1100000000, 0xe479a79600000000, 0xa548bc8f00000000, + 0x661b91a400000000, 0x272a8abd00000000, 0xe0bccbf200000000, + 0xa18dd0eb00000000, 0x62defdc000000000, 0x23efe6d900000000, + 0xbde1bc1400000000, 0xfcd0a70d00000000, 0x3f838a2600000000, + 0x7eb2913f00000000, 0xb924d07000000000, 0xf815cb6900000000, + 0x3b46e64200000000, 0x7a77fd5b00000000, 0xb56b65dc00000000, + 0xf45a7ec500000000, 0x370953ee00000000, 0x763848f700000000, + 0xb1ae09b800000000, 0xf09f12a100000000, 0x33cc3f8a00000000, + 0x72fd249300000000}, + {0x0000000000000000, 0x376ac20100000000, 0x6ed4840300000000, + 0x59be460200000000, 0xdca8090700000000, 0xebc2cb0600000000, + 0xb27c8d0400000000, 0x85164f0500000000, 0xb851130e00000000, + 0x8f3bd10f00000000, 0xd685970d00000000, 0xe1ef550c00000000, + 0x64f91a0900000000, 0x5393d80800000000, 0x0a2d9e0a00000000, + 0x3d475c0b00000000, 0x70a3261c00000000, 0x47c9e41d00000000, + 0x1e77a21f00000000, 0x291d601e00000000, 0xac0b2f1b00000000, + 0x9b61ed1a00000000, 0xc2dfab1800000000, 0xf5b5691900000000, + 0xc8f2351200000000, 0xff98f71300000000, 0xa626b11100000000, + 0x914c731000000000, 0x145a3c1500000000, 0x2330fe1400000000, + 0x7a8eb81600000000, 0x4de47a1700000000, 0xe0464d3800000000, + 0xd72c8f3900000000, 0x8e92c93b00000000, 0xb9f80b3a00000000, + 0x3cee443f00000000, 0x0b84863e00000000, 0x523ac03c00000000, + 0x6550023d00000000, 0x58175e3600000000, 0x6f7d9c3700000000, + 0x36c3da3500000000, 0x01a9183400000000, 0x84bf573100000000, + 0xb3d5953000000000, 0xea6bd33200000000, 0xdd01113300000000, + 0x90e56b2400000000, 0xa78fa92500000000, 0xfe31ef2700000000, + 0xc95b2d2600000000, 0x4c4d622300000000, 0x7b27a02200000000, + 0x2299e62000000000, 0x15f3242100000000, 0x28b4782a00000000, + 0x1fdeba2b00000000, 0x4660fc2900000000, 0x710a3e2800000000, + 0xf41c712d00000000, 0xc376b32c00000000, 0x9ac8f52e00000000, + 0xada2372f00000000, 0xc08d9a7000000000, 0xf7e7587100000000, + 0xae591e7300000000, 0x9933dc7200000000, 0x1c25937700000000, + 0x2b4f517600000000, 0x72f1177400000000, 0x459bd57500000000, + 0x78dc897e00000000, 0x4fb64b7f00000000, 0x16080d7d00000000, + 0x2162cf7c00000000, 0xa474807900000000, 0x931e427800000000, + 0xcaa0047a00000000, 0xfdcac67b00000000, 0xb02ebc6c00000000, + 0x87447e6d00000000, 0xdefa386f00000000, 0xe990fa6e00000000, + 0x6c86b56b00000000, 0x5bec776a00000000, 0x0252316800000000, + 0x3538f36900000000, 0x087faf6200000000, 0x3f156d6300000000, + 0x66ab2b6100000000, 0x51c1e96000000000, 0xd4d7a66500000000, + 0xe3bd646400000000, 0xba03226600000000, 0x8d69e06700000000, + 0x20cbd74800000000, 0x17a1154900000000, 0x4e1f534b00000000, + 0x7975914a00000000, 0xfc63de4f00000000, 0xcb091c4e00000000, + 0x92b75a4c00000000, 0xa5dd984d00000000, 0x989ac44600000000, + 0xaff0064700000000, 0xf64e404500000000, 0xc124824400000000, + 0x4432cd4100000000, 0x73580f4000000000, 0x2ae6494200000000, + 0x1d8c8b4300000000, 0x5068f15400000000, 0x6702335500000000, + 0x3ebc755700000000, 0x09d6b75600000000, 0x8cc0f85300000000, + 0xbbaa3a5200000000, 0xe2147c5000000000, 0xd57ebe5100000000, + 0xe839e25a00000000, 0xdf53205b00000000, 0x86ed665900000000, + 0xb187a45800000000, 0x3491eb5d00000000, 0x03fb295c00000000, + 0x5a456f5e00000000, 0x6d2fad5f00000000, 0x801b35e100000000, + 0xb771f7e000000000, 0xeecfb1e200000000, 0xd9a573e300000000, + 0x5cb33ce600000000, 0x6bd9fee700000000, 0x3267b8e500000000, + 0x050d7ae400000000, 0x384a26ef00000000, 0x0f20e4ee00000000, + 0x569ea2ec00000000, 0x61f460ed00000000, 0xe4e22fe800000000, + 0xd388ede900000000, 0x8a36abeb00000000, 0xbd5c69ea00000000, + 0xf0b813fd00000000, 0xc7d2d1fc00000000, 0x9e6c97fe00000000, + 0xa90655ff00000000, 0x2c101afa00000000, 0x1b7ad8fb00000000, + 0x42c49ef900000000, 0x75ae5cf800000000, 0x48e900f300000000, + 0x7f83c2f200000000, 0x263d84f000000000, 0x115746f100000000, + 0x944109f400000000, 0xa32bcbf500000000, 0xfa958df700000000, + 0xcdff4ff600000000, 0x605d78d900000000, 0x5737bad800000000, + 0x0e89fcda00000000, 0x39e33edb00000000, 0xbcf571de00000000, + 0x8b9fb3df00000000, 0xd221f5dd00000000, 0xe54b37dc00000000, + 0xd80c6bd700000000, 0xef66a9d600000000, 0xb6d8efd400000000, + 0x81b22dd500000000, 0x04a462d000000000, 0x33cea0d100000000, + 0x6a70e6d300000000, 0x5d1a24d200000000, 0x10fe5ec500000000, + 0x27949cc400000000, 0x7e2adac600000000, 0x494018c700000000, + 0xcc5657c200000000, 0xfb3c95c300000000, 0xa282d3c100000000, + 0x95e811c000000000, 0xa8af4dcb00000000, 0x9fc58fca00000000, + 0xc67bc9c800000000, 0xf1110bc900000000, 0x740744cc00000000, + 0x436d86cd00000000, 0x1ad3c0cf00000000, 0x2db902ce00000000, + 0x4096af9100000000, 0x77fc6d9000000000, 0x2e422b9200000000, + 0x1928e99300000000, 0x9c3ea69600000000, 0xab54649700000000, + 0xf2ea229500000000, 0xc580e09400000000, 0xf8c7bc9f00000000, + 0xcfad7e9e00000000, 0x9613389c00000000, 0xa179fa9d00000000, + 0x246fb59800000000, 0x1305779900000000, 0x4abb319b00000000, + 0x7dd1f39a00000000, 0x3035898d00000000, 0x075f4b8c00000000, + 0x5ee10d8e00000000, 0x698bcf8f00000000, 0xec9d808a00000000, + 0xdbf7428b00000000, 0x8249048900000000, 0xb523c68800000000, + 0x88649a8300000000, 0xbf0e588200000000, 0xe6b01e8000000000, + 0xd1dadc8100000000, 0x54cc938400000000, 0x63a6518500000000, + 0x3a18178700000000, 0x0d72d58600000000, 0xa0d0e2a900000000, + 0x97ba20a800000000, 0xce0466aa00000000, 0xf96ea4ab00000000, + 0x7c78ebae00000000, 0x4b1229af00000000, 0x12ac6fad00000000, + 0x25c6adac00000000, 0x1881f1a700000000, 0x2feb33a600000000, + 0x765575a400000000, 0x413fb7a500000000, 0xc429f8a000000000, + 0xf3433aa100000000, 0xaafd7ca300000000, 0x9d97bea200000000, + 0xd073c4b500000000, 0xe71906b400000000, 0xbea740b600000000, + 0x89cd82b700000000, 0x0cdbcdb200000000, 0x3bb10fb300000000, + 0x620f49b100000000, 0x55658bb000000000, 0x6822d7bb00000000, + 0x5f4815ba00000000, 0x06f653b800000000, 0x319c91b900000000, + 0xb48adebc00000000, 0x83e01cbd00000000, 0xda5e5abf00000000, + 0xed3498be00000000}, + {0x0000000000000000, 0x6567bcb800000000, 0x8bc809aa00000000, + 0xeeafb51200000000, 0x5797628f00000000, 0x32f0de3700000000, + 0xdc5f6b2500000000, 0xb938d79d00000000, 0xef28b4c500000000, + 0x8a4f087d00000000, 0x64e0bd6f00000000, 0x018701d700000000, + 0xb8bfd64a00000000, 0xddd86af200000000, 0x3377dfe000000000, + 0x5610635800000000, 0x9f57195000000000, 0xfa30a5e800000000, + 0x149f10fa00000000, 0x71f8ac4200000000, 0xc8c07bdf00000000, + 0xada7c76700000000, 0x4308727500000000, 0x266fcecd00000000, + 0x707fad9500000000, 0x1518112d00000000, 0xfbb7a43f00000000, + 0x9ed0188700000000, 0x27e8cf1a00000000, 0x428f73a200000000, + 0xac20c6b000000000, 0xc9477a0800000000, 0x3eaf32a000000000, + 0x5bc88e1800000000, 0xb5673b0a00000000, 0xd00087b200000000, + 0x6938502f00000000, 0x0c5fec9700000000, 0xe2f0598500000000, + 0x8797e53d00000000, 0xd187866500000000, 0xb4e03add00000000, + 0x5a4f8fcf00000000, 0x3f28337700000000, 0x8610e4ea00000000, + 0xe377585200000000, 0x0dd8ed4000000000, 0x68bf51f800000000, + 0xa1f82bf000000000, 0xc49f974800000000, 0x2a30225a00000000, + 0x4f579ee200000000, 0xf66f497f00000000, 0x9308f5c700000000, + 0x7da740d500000000, 0x18c0fc6d00000000, 0x4ed09f3500000000, + 0x2bb7238d00000000, 0xc518969f00000000, 0xa07f2a2700000000, + 0x1947fdba00000000, 0x7c20410200000000, 0x928ff41000000000, + 0xf7e848a800000000, 0x3d58149b00000000, 0x583fa82300000000, + 0xb6901d3100000000, 0xd3f7a18900000000, 0x6acf761400000000, + 0x0fa8caac00000000, 0xe1077fbe00000000, 0x8460c30600000000, + 0xd270a05e00000000, 0xb7171ce600000000, 0x59b8a9f400000000, + 0x3cdf154c00000000, 0x85e7c2d100000000, 0xe0807e6900000000, + 0x0e2fcb7b00000000, 0x6b4877c300000000, 0xa20f0dcb00000000, + 0xc768b17300000000, 0x29c7046100000000, 0x4ca0b8d900000000, + 0xf5986f4400000000, 0x90ffd3fc00000000, 0x7e5066ee00000000, + 0x1b37da5600000000, 0x4d27b90e00000000, 0x284005b600000000, + 0xc6efb0a400000000, 0xa3880c1c00000000, 0x1ab0db8100000000, + 0x7fd7673900000000, 0x9178d22b00000000, 0xf41f6e9300000000, + 0x03f7263b00000000, 0x66909a8300000000, 0x883f2f9100000000, + 0xed58932900000000, 0x546044b400000000, 0x3107f80c00000000, + 0xdfa84d1e00000000, 0xbacff1a600000000, 0xecdf92fe00000000, + 0x89b82e4600000000, 0x67179b5400000000, 0x027027ec00000000, + 0xbb48f07100000000, 0xde2f4cc900000000, 0x3080f9db00000000, + 0x55e7456300000000, 0x9ca03f6b00000000, 0xf9c783d300000000, + 0x176836c100000000, 0x720f8a7900000000, 0xcb375de400000000, + 0xae50e15c00000000, 0x40ff544e00000000, 0x2598e8f600000000, + 0x73888bae00000000, 0x16ef371600000000, 0xf840820400000000, + 0x9d273ebc00000000, 0x241fe92100000000, 0x4178559900000000, + 0xafd7e08b00000000, 0xcab05c3300000000, 0x3bb659ed00000000, + 0x5ed1e55500000000, 0xb07e504700000000, 0xd519ecff00000000, + 0x6c213b6200000000, 0x094687da00000000, 0xe7e932c800000000, + 0x828e8e7000000000, 0xd49eed2800000000, 0xb1f9519000000000, + 0x5f56e48200000000, 0x3a31583a00000000, 0x83098fa700000000, + 0xe66e331f00000000, 0x08c1860d00000000, 0x6da63ab500000000, + 0xa4e140bd00000000, 0xc186fc0500000000, 0x2f29491700000000, + 0x4a4ef5af00000000, 0xf376223200000000, 0x96119e8a00000000, + 0x78be2b9800000000, 0x1dd9972000000000, 0x4bc9f47800000000, + 0x2eae48c000000000, 0xc001fdd200000000, 0xa566416a00000000, + 0x1c5e96f700000000, 0x79392a4f00000000, 0x97969f5d00000000, + 0xf2f123e500000000, 0x05196b4d00000000, 0x607ed7f500000000, + 0x8ed162e700000000, 0xebb6de5f00000000, 0x528e09c200000000, + 0x37e9b57a00000000, 0xd946006800000000, 0xbc21bcd000000000, + 0xea31df8800000000, 0x8f56633000000000, 0x61f9d62200000000, + 0x049e6a9a00000000, 0xbda6bd0700000000, 0xd8c101bf00000000, + 0x366eb4ad00000000, 0x5309081500000000, 0x9a4e721d00000000, + 0xff29cea500000000, 0x11867bb700000000, 0x74e1c70f00000000, + 0xcdd9109200000000, 0xa8beac2a00000000, 0x4611193800000000, + 0x2376a58000000000, 0x7566c6d800000000, 0x10017a6000000000, + 0xfeaecf7200000000, 0x9bc973ca00000000, 0x22f1a45700000000, + 0x479618ef00000000, 0xa939adfd00000000, 0xcc5e114500000000, + 0x06ee4d7600000000, 0x6389f1ce00000000, 0x8d2644dc00000000, + 0xe841f86400000000, 0x51792ff900000000, 0x341e934100000000, + 0xdab1265300000000, 0xbfd69aeb00000000, 0xe9c6f9b300000000, + 0x8ca1450b00000000, 0x620ef01900000000, 0x07694ca100000000, + 0xbe519b3c00000000, 0xdb36278400000000, 0x3599929600000000, + 0x50fe2e2e00000000, 0x99b9542600000000, 0xfcdee89e00000000, + 0x12715d8c00000000, 0x7716e13400000000, 0xce2e36a900000000, + 0xab498a1100000000, 0x45e63f0300000000, 0x208183bb00000000, + 0x7691e0e300000000, 0x13f65c5b00000000, 0xfd59e94900000000, + 0x983e55f100000000, 0x2106826c00000000, 0x44613ed400000000, + 0xaace8bc600000000, 0xcfa9377e00000000, 0x38417fd600000000, + 0x5d26c36e00000000, 0xb389767c00000000, 0xd6eecac400000000, + 0x6fd61d5900000000, 0x0ab1a1e100000000, 0xe41e14f300000000, + 0x8179a84b00000000, 0xd769cb1300000000, 0xb20e77ab00000000, + 0x5ca1c2b900000000, 0x39c67e0100000000, 0x80fea99c00000000, + 0xe599152400000000, 0x0b36a03600000000, 0x6e511c8e00000000, + 0xa716668600000000, 0xc271da3e00000000, 0x2cde6f2c00000000, + 0x49b9d39400000000, 0xf081040900000000, 0x95e6b8b100000000, + 0x7b490da300000000, 0x1e2eb11b00000000, 0x483ed24300000000, + 0x2d596efb00000000, 0xc3f6dbe900000000, 0xa691675100000000, + 0x1fa9b0cc00000000, 0x7ace0c7400000000, 0x9461b96600000000, + 0xf10605de00000000}, + {0x0000000000000000, 0xb029603d00000000, 0x6053c07a00000000, + 0xd07aa04700000000, 0xc0a680f500000000, 0x708fe0c800000000, + 0xa0f5408f00000000, 0x10dc20b200000000, 0xc14b703000000000, + 0x7162100d00000000, 0xa118b04a00000000, 0x1131d07700000000, + 0x01edf0c500000000, 0xb1c490f800000000, 0x61be30bf00000000, + 0xd197508200000000, 0x8297e06000000000, 0x32be805d00000000, + 0xe2c4201a00000000, 0x52ed402700000000, 0x4231609500000000, + 0xf21800a800000000, 0x2262a0ef00000000, 0x924bc0d200000000, + 0x43dc905000000000, 0xf3f5f06d00000000, 0x238f502a00000000, + 0x93a6301700000000, 0x837a10a500000000, 0x3353709800000000, + 0xe329d0df00000000, 0x5300b0e200000000, 0x042fc1c100000000, + 0xb406a1fc00000000, 0x647c01bb00000000, 0xd455618600000000, + 0xc489413400000000, 0x74a0210900000000, 0xa4da814e00000000, + 0x14f3e17300000000, 0xc564b1f100000000, 0x754dd1cc00000000, + 0xa537718b00000000, 0x151e11b600000000, 0x05c2310400000000, + 0xb5eb513900000000, 0x6591f17e00000000, 0xd5b8914300000000, + 0x86b821a100000000, 0x3691419c00000000, 0xe6ebe1db00000000, + 0x56c281e600000000, 0x461ea15400000000, 0xf637c16900000000, + 0x264d612e00000000, 0x9664011300000000, 0x47f3519100000000, + 0xf7da31ac00000000, 0x27a091eb00000000, 0x9789f1d600000000, + 0x8755d16400000000, 0x377cb15900000000, 0xe706111e00000000, + 0x572f712300000000, 0x4958f35800000000, 0xf971936500000000, + 0x290b332200000000, 0x9922531f00000000, 0x89fe73ad00000000, + 0x39d7139000000000, 0xe9adb3d700000000, 0x5984d3ea00000000, + 0x8813836800000000, 0x383ae35500000000, 0xe840431200000000, + 0x5869232f00000000, 0x48b5039d00000000, 0xf89c63a000000000, + 0x28e6c3e700000000, 0x98cfa3da00000000, 0xcbcf133800000000, + 0x7be6730500000000, 0xab9cd34200000000, 0x1bb5b37f00000000, + 0x0b6993cd00000000, 0xbb40f3f000000000, 0x6b3a53b700000000, + 0xdb13338a00000000, 0x0a84630800000000, 0xbaad033500000000, + 0x6ad7a37200000000, 0xdafec34f00000000, 0xca22e3fd00000000, + 0x7a0b83c000000000, 0xaa71238700000000, 0x1a5843ba00000000, + 0x4d77329900000000, 0xfd5e52a400000000, 0x2d24f2e300000000, + 0x9d0d92de00000000, 0x8dd1b26c00000000, 0x3df8d25100000000, + 0xed82721600000000, 0x5dab122b00000000, 0x8c3c42a900000000, + 0x3c15229400000000, 0xec6f82d300000000, 0x5c46e2ee00000000, + 0x4c9ac25c00000000, 0xfcb3a26100000000, 0x2cc9022600000000, + 0x9ce0621b00000000, 0xcfe0d2f900000000, 0x7fc9b2c400000000, + 0xafb3128300000000, 0x1f9a72be00000000, 0x0f46520c00000000, + 0xbf6f323100000000, 0x6f15927600000000, 0xdf3cf24b00000000, + 0x0eaba2c900000000, 0xbe82c2f400000000, 0x6ef862b300000000, + 0xded1028e00000000, 0xce0d223c00000000, 0x7e24420100000000, + 0xae5ee24600000000, 0x1e77827b00000000, 0x92b0e6b100000000, + 0x2299868c00000000, 0xf2e326cb00000000, 0x42ca46f600000000, + 0x5216664400000000, 0xe23f067900000000, 0x3245a63e00000000, + 0x826cc60300000000, 0x53fb968100000000, 0xe3d2f6bc00000000, + 0x33a856fb00000000, 0x838136c600000000, 0x935d167400000000, + 0x2374764900000000, 0xf30ed60e00000000, 0x4327b63300000000, + 0x102706d100000000, 0xa00e66ec00000000, 0x7074c6ab00000000, + 0xc05da69600000000, 0xd081862400000000, 0x60a8e61900000000, + 0xb0d2465e00000000, 0x00fb266300000000, 0xd16c76e100000000, + 0x614516dc00000000, 0xb13fb69b00000000, 0x0116d6a600000000, + 0x11caf61400000000, 0xa1e3962900000000, 0x7199366e00000000, + 0xc1b0565300000000, 0x969f277000000000, 0x26b6474d00000000, + 0xf6cce70a00000000, 0x46e5873700000000, 0x5639a78500000000, + 0xe610c7b800000000, 0x366a67ff00000000, 0x864307c200000000, + 0x57d4574000000000, 0xe7fd377d00000000, 0x3787973a00000000, + 0x87aef70700000000, 0x9772d7b500000000, 0x275bb78800000000, + 0xf72117cf00000000, 0x470877f200000000, 0x1408c71000000000, + 0xa421a72d00000000, 0x745b076a00000000, 0xc472675700000000, + 0xd4ae47e500000000, 0x648727d800000000, 0xb4fd879f00000000, + 0x04d4e7a200000000, 0xd543b72000000000, 0x656ad71d00000000, + 0xb510775a00000000, 0x0539176700000000, 0x15e537d500000000, + 0xa5cc57e800000000, 0x75b6f7af00000000, 0xc59f979200000000, + 0xdbe815e900000000, 0x6bc175d400000000, 0xbbbbd59300000000, + 0x0b92b5ae00000000, 0x1b4e951c00000000, 0xab67f52100000000, + 0x7b1d556600000000, 0xcb34355b00000000, 0x1aa365d900000000, + 0xaa8a05e400000000, 0x7af0a5a300000000, 0xcad9c59e00000000, + 0xda05e52c00000000, 0x6a2c851100000000, 0xba56255600000000, + 0x0a7f456b00000000, 0x597ff58900000000, 0xe95695b400000000, + 0x392c35f300000000, 0x890555ce00000000, 0x99d9757c00000000, + 0x29f0154100000000, 0xf98ab50600000000, 0x49a3d53b00000000, + 0x983485b900000000, 0x281de58400000000, 0xf86745c300000000, + 0x484e25fe00000000, 0x5892054c00000000, 0xe8bb657100000000, + 0x38c1c53600000000, 0x88e8a50b00000000, 0xdfc7d42800000000, + 0x6feeb41500000000, 0xbf94145200000000, 0x0fbd746f00000000, + 0x1f6154dd00000000, 0xaf4834e000000000, 0x7f3294a700000000, + 0xcf1bf49a00000000, 0x1e8ca41800000000, 0xaea5c42500000000, + 0x7edf646200000000, 0xcef6045f00000000, 0xde2a24ed00000000, + 0x6e0344d000000000, 0xbe79e49700000000, 0x0e5084aa00000000, + 0x5d50344800000000, 0xed79547500000000, 0x3d03f43200000000, + 0x8d2a940f00000000, 0x9df6b4bd00000000, 0x2ddfd48000000000, + 0xfda574c700000000, 0x4d8c14fa00000000, 0x9c1b447800000000, + 0x2c32244500000000, 0xfc48840200000000, 0x4c61e43f00000000, + 0x5cbdc48d00000000, 0xec94a4b000000000, 0x3cee04f700000000, + 0x8cc764ca00000000}, + {0x0000000000000000, 0xa5d35ccb00000000, 0x0ba1c84d00000000, + 0xae72948600000000, 0x1642919b00000000, 0xb391cd5000000000, + 0x1de359d600000000, 0xb830051d00000000, 0x6d8253ec00000000, + 0xc8510f2700000000, 0x66239ba100000000, 0xc3f0c76a00000000, + 0x7bc0c27700000000, 0xde139ebc00000000, 0x70610a3a00000000, + 0xd5b256f100000000, 0x9b02d60300000000, 0x3ed18ac800000000, + 0x90a31e4e00000000, 0x3570428500000000, 0x8d40479800000000, + 0x28931b5300000000, 0x86e18fd500000000, 0x2332d31e00000000, + 0xf68085ef00000000, 0x5353d92400000000, 0xfd214da200000000, + 0x58f2116900000000, 0xe0c2147400000000, 0x451148bf00000000, + 0xeb63dc3900000000, 0x4eb080f200000000, 0x3605ac0700000000, + 0x93d6f0cc00000000, 0x3da4644a00000000, 0x9877388100000000, + 0x20473d9c00000000, 0x8594615700000000, 0x2be6f5d100000000, + 0x8e35a91a00000000, 0x5b87ffeb00000000, 0xfe54a32000000000, + 0x502637a600000000, 0xf5f56b6d00000000, 0x4dc56e7000000000, + 0xe81632bb00000000, 0x4664a63d00000000, 0xe3b7faf600000000, + 0xad077a0400000000, 0x08d426cf00000000, 0xa6a6b24900000000, + 0x0375ee8200000000, 0xbb45eb9f00000000, 0x1e96b75400000000, + 0xb0e423d200000000, 0x15377f1900000000, 0xc08529e800000000, + 0x6556752300000000, 0xcb24e1a500000000, 0x6ef7bd6e00000000, + 0xd6c7b87300000000, 0x7314e4b800000000, 0xdd66703e00000000, + 0x78b52cf500000000, 0x6c0a580f00000000, 0xc9d904c400000000, + 0x67ab904200000000, 0xc278cc8900000000, 0x7a48c99400000000, + 0xdf9b955f00000000, 0x71e901d900000000, 0xd43a5d1200000000, + 0x01880be300000000, 0xa45b572800000000, 0x0a29c3ae00000000, + 0xaffa9f6500000000, 0x17ca9a7800000000, 0xb219c6b300000000, + 0x1c6b523500000000, 0xb9b80efe00000000, 0xf7088e0c00000000, + 0x52dbd2c700000000, 0xfca9464100000000, 0x597a1a8a00000000, + 0xe14a1f9700000000, 0x4499435c00000000, 0xeaebd7da00000000, + 0x4f388b1100000000, 0x9a8adde000000000, 0x3f59812b00000000, + 0x912b15ad00000000, 0x34f8496600000000, 0x8cc84c7b00000000, + 0x291b10b000000000, 0x8769843600000000, 0x22bad8fd00000000, + 0x5a0ff40800000000, 0xffdca8c300000000, 0x51ae3c4500000000, + 0xf47d608e00000000, 0x4c4d659300000000, 0xe99e395800000000, + 0x47ecadde00000000, 0xe23ff11500000000, 0x378da7e400000000, + 0x925efb2f00000000, 0x3c2c6fa900000000, 0x99ff336200000000, + 0x21cf367f00000000, 0x841c6ab400000000, 0x2a6efe3200000000, + 0x8fbda2f900000000, 0xc10d220b00000000, 0x64de7ec000000000, + 0xcaacea4600000000, 0x6f7fb68d00000000, 0xd74fb39000000000, + 0x729cef5b00000000, 0xdcee7bdd00000000, 0x793d271600000000, + 0xac8f71e700000000, 0x095c2d2c00000000, 0xa72eb9aa00000000, + 0x02fde56100000000, 0xbacde07c00000000, 0x1f1ebcb700000000, + 0xb16c283100000000, 0x14bf74fa00000000, 0xd814b01e00000000, + 0x7dc7ecd500000000, 0xd3b5785300000000, 0x7666249800000000, + 0xce56218500000000, 0x6b857d4e00000000, 0xc5f7e9c800000000, + 0x6024b50300000000, 0xb596e3f200000000, 0x1045bf3900000000, + 0xbe372bbf00000000, 0x1be4777400000000, 0xa3d4726900000000, + 0x06072ea200000000, 0xa875ba2400000000, 0x0da6e6ef00000000, + 0x4316661d00000000, 0xe6c53ad600000000, 0x48b7ae5000000000, + 0xed64f29b00000000, 0x5554f78600000000, 0xf087ab4d00000000, + 0x5ef53fcb00000000, 0xfb26630000000000, 0x2e9435f100000000, + 0x8b47693a00000000, 0x2535fdbc00000000, 0x80e6a17700000000, + 0x38d6a46a00000000, 0x9d05f8a100000000, 0x33776c2700000000, + 0x96a430ec00000000, 0xee111c1900000000, 0x4bc240d200000000, + 0xe5b0d45400000000, 0x4063889f00000000, 0xf8538d8200000000, + 0x5d80d14900000000, 0xf3f245cf00000000, 0x5621190400000000, + 0x83934ff500000000, 0x2640133e00000000, 0x883287b800000000, + 0x2de1db7300000000, 0x95d1de6e00000000, 0x300282a500000000, + 0x9e70162300000000, 0x3ba34ae800000000, 0x7513ca1a00000000, + 0xd0c096d100000000, 0x7eb2025700000000, 0xdb615e9c00000000, + 0x63515b8100000000, 0xc682074a00000000, 0x68f093cc00000000, + 0xcd23cf0700000000, 0x189199f600000000, 0xbd42c53d00000000, + 0x133051bb00000000, 0xb6e30d7000000000, 0x0ed3086d00000000, + 0xab0054a600000000, 0x0572c02000000000, 0xa0a19ceb00000000, + 0xb41ee81100000000, 0x11cdb4da00000000, 0xbfbf205c00000000, + 0x1a6c7c9700000000, 0xa25c798a00000000, 0x078f254100000000, + 0xa9fdb1c700000000, 0x0c2eed0c00000000, 0xd99cbbfd00000000, + 0x7c4fe73600000000, 0xd23d73b000000000, 0x77ee2f7b00000000, + 0xcfde2a6600000000, 0x6a0d76ad00000000, 0xc47fe22b00000000, + 0x61acbee000000000, 0x2f1c3e1200000000, 0x8acf62d900000000, + 0x24bdf65f00000000, 0x816eaa9400000000, 0x395eaf8900000000, + 0x9c8df34200000000, 0x32ff67c400000000, 0x972c3b0f00000000, + 0x429e6dfe00000000, 0xe74d313500000000, 0x493fa5b300000000, + 0xececf97800000000, 0x54dcfc6500000000, 0xf10fa0ae00000000, + 0x5f7d342800000000, 0xfaae68e300000000, 0x821b441600000000, + 0x27c818dd00000000, 0x89ba8c5b00000000, 0x2c69d09000000000, + 0x9459d58d00000000, 0x318a894600000000, 0x9ff81dc000000000, + 0x3a2b410b00000000, 0xef9917fa00000000, 0x4a4a4b3100000000, + 0xe438dfb700000000, 0x41eb837c00000000, 0xf9db866100000000, + 0x5c08daaa00000000, 0xf27a4e2c00000000, 0x57a912e700000000, + 0x1919921500000000, 0xbccacede00000000, 0x12b85a5800000000, + 0xb76b069300000000, 0x0f5b038e00000000, 0xaa885f4500000000, + 0x04facbc300000000, 0xa129970800000000, 0x749bc1f900000000, + 0xd1489d3200000000, 0x7f3a09b400000000, 0xdae9557f00000000, + 0x62d9506200000000, 0xc70a0ca900000000, 0x6978982f00000000, + 0xccabc4e400000000}, + {0x0000000000000000, 0xb40b77a600000000, 0x29119f9700000000, + 0x9d1ae83100000000, 0x13244ff400000000, 0xa72f385200000000, + 0x3a35d06300000000, 0x8e3ea7c500000000, 0x674eef3300000000, + 0xd345989500000000, 0x4e5f70a400000000, 0xfa54070200000000, + 0x746aa0c700000000, 0xc061d76100000000, 0x5d7b3f5000000000, + 0xe97048f600000000, 0xce9cde6700000000, 0x7a97a9c100000000, + 0xe78d41f000000000, 0x5386365600000000, 0xddb8919300000000, + 0x69b3e63500000000, 0xf4a90e0400000000, 0x40a279a200000000, + 0xa9d2315400000000, 0x1dd946f200000000, 0x80c3aec300000000, + 0x34c8d96500000000, 0xbaf67ea000000000, 0x0efd090600000000, + 0x93e7e13700000000, 0x27ec969100000000, 0x9c39bdcf00000000, + 0x2832ca6900000000, 0xb528225800000000, 0x012355fe00000000, + 0x8f1df23b00000000, 0x3b16859d00000000, 0xa60c6dac00000000, + 0x12071a0a00000000, 0xfb7752fc00000000, 0x4f7c255a00000000, + 0xd266cd6b00000000, 0x666dbacd00000000, 0xe8531d0800000000, + 0x5c586aae00000000, 0xc142829f00000000, 0x7549f53900000000, + 0x52a563a800000000, 0xe6ae140e00000000, 0x7bb4fc3f00000000, + 0xcfbf8b9900000000, 0x41812c5c00000000, 0xf58a5bfa00000000, + 0x6890b3cb00000000, 0xdc9bc46d00000000, 0x35eb8c9b00000000, + 0x81e0fb3d00000000, 0x1cfa130c00000000, 0xa8f164aa00000000, + 0x26cfc36f00000000, 0x92c4b4c900000000, 0x0fde5cf800000000, + 0xbbd52b5e00000000, 0x79750b4400000000, 0xcd7e7ce200000000, + 0x506494d300000000, 0xe46fe37500000000, 0x6a5144b000000000, + 0xde5a331600000000, 0x4340db2700000000, 0xf74bac8100000000, + 0x1e3be47700000000, 0xaa3093d100000000, 0x372a7be000000000, + 0x83210c4600000000, 0x0d1fab8300000000, 0xb914dc2500000000, + 0x240e341400000000, 0x900543b200000000, 0xb7e9d52300000000, + 0x03e2a28500000000, 0x9ef84ab400000000, 0x2af33d1200000000, + 0xa4cd9ad700000000, 0x10c6ed7100000000, 0x8ddc054000000000, + 0x39d772e600000000, 0xd0a73a1000000000, 0x64ac4db600000000, + 0xf9b6a58700000000, 0x4dbdd22100000000, 0xc38375e400000000, + 0x7788024200000000, 0xea92ea7300000000, 0x5e999dd500000000, + 0xe54cb68b00000000, 0x5147c12d00000000, 0xcc5d291c00000000, + 0x78565eba00000000, 0xf668f97f00000000, 0x42638ed900000000, + 0xdf7966e800000000, 0x6b72114e00000000, 0x820259b800000000, + 0x36092e1e00000000, 0xab13c62f00000000, 0x1f18b18900000000, + 0x9126164c00000000, 0x252d61ea00000000, 0xb83789db00000000, + 0x0c3cfe7d00000000, 0x2bd068ec00000000, 0x9fdb1f4a00000000, + 0x02c1f77b00000000, 0xb6ca80dd00000000, 0x38f4271800000000, + 0x8cff50be00000000, 0x11e5b88f00000000, 0xa5eecf2900000000, + 0x4c9e87df00000000, 0xf895f07900000000, 0x658f184800000000, + 0xd1846fee00000000, 0x5fbac82b00000000, 0xebb1bf8d00000000, + 0x76ab57bc00000000, 0xc2a0201a00000000, 0xf2ea168800000000, + 0x46e1612e00000000, 0xdbfb891f00000000, 0x6ff0feb900000000, + 0xe1ce597c00000000, 0x55c52eda00000000, 0xc8dfc6eb00000000, + 0x7cd4b14d00000000, 0x95a4f9bb00000000, 0x21af8e1d00000000, + 0xbcb5662c00000000, 0x08be118a00000000, 0x8680b64f00000000, + 0x328bc1e900000000, 0xaf9129d800000000, 0x1b9a5e7e00000000, + 0x3c76c8ef00000000, 0x887dbf4900000000, 0x1567577800000000, + 0xa16c20de00000000, 0x2f52871b00000000, 0x9b59f0bd00000000, + 0x0643188c00000000, 0xb2486f2a00000000, 0x5b3827dc00000000, + 0xef33507a00000000, 0x7229b84b00000000, 0xc622cfed00000000, + 0x481c682800000000, 0xfc171f8e00000000, 0x610df7bf00000000, + 0xd506801900000000, 0x6ed3ab4700000000, 0xdad8dce100000000, + 0x47c234d000000000, 0xf3c9437600000000, 0x7df7e4b300000000, + 0xc9fc931500000000, 0x54e67b2400000000, 0xe0ed0c8200000000, + 0x099d447400000000, 0xbd9633d200000000, 0x208cdbe300000000, + 0x9487ac4500000000, 0x1ab90b8000000000, 0xaeb27c2600000000, + 0x33a8941700000000, 0x87a3e3b100000000, 0xa04f752000000000, + 0x1444028600000000, 0x895eeab700000000, 0x3d559d1100000000, + 0xb36b3ad400000000, 0x07604d7200000000, 0x9a7aa54300000000, + 0x2e71d2e500000000, 0xc7019a1300000000, 0x730aedb500000000, + 0xee10058400000000, 0x5a1b722200000000, 0xd425d5e700000000, + 0x602ea24100000000, 0xfd344a7000000000, 0x493f3dd600000000, + 0x8b9f1dcc00000000, 0x3f946a6a00000000, 0xa28e825b00000000, + 0x1685f5fd00000000, 0x98bb523800000000, 0x2cb0259e00000000, + 0xb1aacdaf00000000, 0x05a1ba0900000000, 0xecd1f2ff00000000, + 0x58da855900000000, 0xc5c06d6800000000, 0x71cb1ace00000000, + 0xfff5bd0b00000000, 0x4bfecaad00000000, 0xd6e4229c00000000, + 0x62ef553a00000000, 0x4503c3ab00000000, 0xf108b40d00000000, + 0x6c125c3c00000000, 0xd8192b9a00000000, 0x56278c5f00000000, + 0xe22cfbf900000000, 0x7f3613c800000000, 0xcb3d646e00000000, + 0x224d2c9800000000, 0x96465b3e00000000, 0x0b5cb30f00000000, + 0xbf57c4a900000000, 0x3169636c00000000, 0x856214ca00000000, + 0x1878fcfb00000000, 0xac738b5d00000000, 0x17a6a00300000000, + 0xa3add7a500000000, 0x3eb73f9400000000, 0x8abc483200000000, + 0x0482eff700000000, 0xb089985100000000, 0x2d93706000000000, + 0x999807c600000000, 0x70e84f3000000000, 0xc4e3389600000000, + 0x59f9d0a700000000, 0xedf2a70100000000, 0x63cc00c400000000, + 0xd7c7776200000000, 0x4add9f5300000000, 0xfed6e8f500000000, + 0xd93a7e6400000000, 0x6d3109c200000000, 0xf02be1f300000000, + 0x4420965500000000, 0xca1e319000000000, 0x7e15463600000000, + 0xe30fae0700000000, 0x5704d9a100000000, 0xbe74915700000000, + 0x0a7fe6f100000000, 0x97650ec000000000, 0x236e796600000000, + 0xad50dea300000000, 0x195ba90500000000, 0x8441413400000000, + 0x304a369200000000}, + {0x0000000000000000, 0x9e00aacc00000000, 0x7d07254200000000, + 0xe3078f8e00000000, 0xfa0e4a8400000000, 0x640ee04800000000, + 0x87096fc600000000, 0x1909c50a00000000, 0xb51be5d300000000, + 0x2b1b4f1f00000000, 0xc81cc09100000000, 0x561c6a5d00000000, + 0x4f15af5700000000, 0xd115059b00000000, 0x32128a1500000000, + 0xac1220d900000000, 0x2b31bb7c00000000, 0xb53111b000000000, + 0x56369e3e00000000, 0xc83634f200000000, 0xd13ff1f800000000, + 0x4f3f5b3400000000, 0xac38d4ba00000000, 0x32387e7600000000, + 0x9e2a5eaf00000000, 0x002af46300000000, 0xe32d7bed00000000, + 0x7d2dd12100000000, 0x6424142b00000000, 0xfa24bee700000000, + 0x1923316900000000, 0x87239ba500000000, 0x566276f900000000, + 0xc862dc3500000000, 0x2b6553bb00000000, 0xb565f97700000000, + 0xac6c3c7d00000000, 0x326c96b100000000, 0xd16b193f00000000, + 0x4f6bb3f300000000, 0xe379932a00000000, 0x7d7939e600000000, + 0x9e7eb66800000000, 0x007e1ca400000000, 0x1977d9ae00000000, + 0x8777736200000000, 0x6470fcec00000000, 0xfa70562000000000, + 0x7d53cd8500000000, 0xe353674900000000, 0x0054e8c700000000, + 0x9e54420b00000000, 0x875d870100000000, 0x195d2dcd00000000, + 0xfa5aa24300000000, 0x645a088f00000000, 0xc848285600000000, + 0x5648829a00000000, 0xb54f0d1400000000, 0x2b4fa7d800000000, + 0x324662d200000000, 0xac46c81e00000000, 0x4f41479000000000, + 0xd141ed5c00000000, 0xedc29d2900000000, 0x73c237e500000000, + 0x90c5b86b00000000, 0x0ec512a700000000, 0x17ccd7ad00000000, + 0x89cc7d6100000000, 0x6acbf2ef00000000, 0xf4cb582300000000, + 0x58d978fa00000000, 0xc6d9d23600000000, 0x25de5db800000000, + 0xbbdef77400000000, 0xa2d7327e00000000, 0x3cd798b200000000, + 0xdfd0173c00000000, 0x41d0bdf000000000, 0xc6f3265500000000, + 0x58f38c9900000000, 0xbbf4031700000000, 0x25f4a9db00000000, + 0x3cfd6cd100000000, 0xa2fdc61d00000000, 0x41fa499300000000, + 0xdffae35f00000000, 0x73e8c38600000000, 0xede8694a00000000, + 0x0eefe6c400000000, 0x90ef4c0800000000, 0x89e6890200000000, + 0x17e623ce00000000, 0xf4e1ac4000000000, 0x6ae1068c00000000, + 0xbba0ebd000000000, 0x25a0411c00000000, 0xc6a7ce9200000000, + 0x58a7645e00000000, 0x41aea15400000000, 0xdfae0b9800000000, + 0x3ca9841600000000, 0xa2a92eda00000000, 0x0ebb0e0300000000, + 0x90bba4cf00000000, 0x73bc2b4100000000, 0xedbc818d00000000, + 0xf4b5448700000000, 0x6ab5ee4b00000000, 0x89b261c500000000, + 0x17b2cb0900000000, 0x909150ac00000000, 0x0e91fa6000000000, + 0xed9675ee00000000, 0x7396df2200000000, 0x6a9f1a2800000000, + 0xf49fb0e400000000, 0x17983f6a00000000, 0x899895a600000000, + 0x258ab57f00000000, 0xbb8a1fb300000000, 0x588d903d00000000, + 0xc68d3af100000000, 0xdf84fffb00000000, 0x4184553700000000, + 0xa283dab900000000, 0x3c83707500000000, 0xda853b5300000000, + 0x4485919f00000000, 0xa7821e1100000000, 0x3982b4dd00000000, + 0x208b71d700000000, 0xbe8bdb1b00000000, 0x5d8c549500000000, + 0xc38cfe5900000000, 0x6f9ede8000000000, 0xf19e744c00000000, + 0x1299fbc200000000, 0x8c99510e00000000, 0x9590940400000000, + 0x0b903ec800000000, 0xe897b14600000000, 0x76971b8a00000000, + 0xf1b4802f00000000, 0x6fb42ae300000000, 0x8cb3a56d00000000, + 0x12b30fa100000000, 0x0bbacaab00000000, 0x95ba606700000000, + 0x76bdefe900000000, 0xe8bd452500000000, 0x44af65fc00000000, + 0xdaafcf3000000000, 0x39a840be00000000, 0xa7a8ea7200000000, + 0xbea12f7800000000, 0x20a185b400000000, 0xc3a60a3a00000000, + 0x5da6a0f600000000, 0x8ce74daa00000000, 0x12e7e76600000000, + 0xf1e068e800000000, 0x6fe0c22400000000, 0x76e9072e00000000, + 0xe8e9ade200000000, 0x0bee226c00000000, 0x95ee88a000000000, + 0x39fca87900000000, 0xa7fc02b500000000, 0x44fb8d3b00000000, + 0xdafb27f700000000, 0xc3f2e2fd00000000, 0x5df2483100000000, + 0xbef5c7bf00000000, 0x20f56d7300000000, 0xa7d6f6d600000000, + 0x39d65c1a00000000, 0xdad1d39400000000, 0x44d1795800000000, + 0x5dd8bc5200000000, 0xc3d8169e00000000, 0x20df991000000000, + 0xbedf33dc00000000, 0x12cd130500000000, 0x8ccdb9c900000000, + 0x6fca364700000000, 0xf1ca9c8b00000000, 0xe8c3598100000000, + 0x76c3f34d00000000, 0x95c47cc300000000, 0x0bc4d60f00000000, + 0x3747a67a00000000, 0xa9470cb600000000, 0x4a40833800000000, + 0xd44029f400000000, 0xcd49ecfe00000000, 0x5349463200000000, + 0xb04ec9bc00000000, 0x2e4e637000000000, 0x825c43a900000000, + 0x1c5ce96500000000, 0xff5b66eb00000000, 0x615bcc2700000000, + 0x7852092d00000000, 0xe652a3e100000000, 0x05552c6f00000000, + 0x9b5586a300000000, 0x1c761d0600000000, 0x8276b7ca00000000, + 0x6171384400000000, 0xff71928800000000, 0xe678578200000000, + 0x7878fd4e00000000, 0x9b7f72c000000000, 0x057fd80c00000000, + 0xa96df8d500000000, 0x376d521900000000, 0xd46add9700000000, + 0x4a6a775b00000000, 0x5363b25100000000, 0xcd63189d00000000, + 0x2e64971300000000, 0xb0643ddf00000000, 0x6125d08300000000, + 0xff257a4f00000000, 0x1c22f5c100000000, 0x82225f0d00000000, + 0x9b2b9a0700000000, 0x052b30cb00000000, 0xe62cbf4500000000, + 0x782c158900000000, 0xd43e355000000000, 0x4a3e9f9c00000000, + 0xa939101200000000, 0x3739bade00000000, 0x2e307fd400000000, + 0xb030d51800000000, 0x53375a9600000000, 0xcd37f05a00000000, + 0x4a146bff00000000, 0xd414c13300000000, 0x37134ebd00000000, + 0xa913e47100000000, 0xb01a217b00000000, 0x2e1a8bb700000000, + 0xcd1d043900000000, 0x531daef500000000, 0xff0f8e2c00000000, + 0x610f24e000000000, 0x8208ab6e00000000, 0x1c0801a200000000, + 0x0501c4a800000000, 0x9b016e6400000000, 0x7806e1ea00000000, + 0xe6064b2600000000}}; + +#else /* W == 4 */ + +local const z_crc_t FAR crc_braid_table[][256] = { + {0x00000000, 0xb8bc6765, 0xaa09c88b, 0x12b5afee, 0x8f629757, + 0x37def032, 0x256b5fdc, 0x9dd738b9, 0xc5b428ef, 0x7d084f8a, + 0x6fbde064, 0xd7018701, 0x4ad6bfb8, 0xf26ad8dd, 0xe0df7733, + 0x58631056, 0x5019579f, 0xe8a530fa, 0xfa109f14, 0x42acf871, + 0xdf7bc0c8, 0x67c7a7ad, 0x75720843, 0xcdce6f26, 0x95ad7f70, + 0x2d111815, 0x3fa4b7fb, 0x8718d09e, 0x1acfe827, 0xa2738f42, + 0xb0c620ac, 0x087a47c9, 0xa032af3e, 0x188ec85b, 0x0a3b67b5, + 0xb28700d0, 0x2f503869, 0x97ec5f0c, 0x8559f0e2, 0x3de59787, + 0x658687d1, 0xdd3ae0b4, 0xcf8f4f5a, 0x7733283f, 0xeae41086, + 0x525877e3, 0x40edd80d, 0xf851bf68, 0xf02bf8a1, 0x48979fc4, + 0x5a22302a, 0xe29e574f, 0x7f496ff6, 0xc7f50893, 0xd540a77d, + 0x6dfcc018, 0x359fd04e, 0x8d23b72b, 0x9f9618c5, 0x272a7fa0, + 0xbafd4719, 0x0241207c, 0x10f48f92, 0xa848e8f7, 0x9b14583d, + 0x23a83f58, 0x311d90b6, 0x89a1f7d3, 0x1476cf6a, 0xaccaa80f, + 0xbe7f07e1, 0x06c36084, 0x5ea070d2, 0xe61c17b7, 0xf4a9b859, + 0x4c15df3c, 0xd1c2e785, 0x697e80e0, 0x7bcb2f0e, 0xc377486b, + 0xcb0d0fa2, 0x73b168c7, 0x6104c729, 0xd9b8a04c, 0x446f98f5, + 0xfcd3ff90, 0xee66507e, 0x56da371b, 0x0eb9274d, 0xb6054028, + 0xa4b0efc6, 0x1c0c88a3, 0x81dbb01a, 0x3967d77f, 0x2bd27891, + 0x936e1ff4, 0x3b26f703, 0x839a9066, 0x912f3f88, 0x299358ed, + 0xb4446054, 0x0cf80731, 0x1e4da8df, 0xa6f1cfba, 0xfe92dfec, + 0x462eb889, 0x549b1767, 0xec277002, 0x71f048bb, 0xc94c2fde, + 0xdbf98030, 0x6345e755, 0x6b3fa09c, 0xd383c7f9, 0xc1366817, + 0x798a0f72, 0xe45d37cb, 0x5ce150ae, 0x4e54ff40, 0xf6e89825, + 0xae8b8873, 0x1637ef16, 0x048240f8, 0xbc3e279d, 0x21e91f24, + 0x99557841, 0x8be0d7af, 0x335cb0ca, 0xed59b63b, 0x55e5d15e, + 0x47507eb0, 0xffec19d5, 0x623b216c, 0xda874609, 0xc832e9e7, + 0x708e8e82, 0x28ed9ed4, 0x9051f9b1, 0x82e4565f, 0x3a58313a, + 0xa78f0983, 0x1f336ee6, 0x0d86c108, 0xb53aa66d, 0xbd40e1a4, + 0x05fc86c1, 0x1749292f, 0xaff54e4a, 0x322276f3, 0x8a9e1196, + 0x982bbe78, 0x2097d91d, 0x78f4c94b, 0xc048ae2e, 0xd2fd01c0, + 0x6a4166a5, 0xf7965e1c, 0x4f2a3979, 0x5d9f9697, 0xe523f1f2, + 0x4d6b1905, 0xf5d77e60, 0xe762d18e, 0x5fdeb6eb, 0xc2098e52, + 0x7ab5e937, 0x680046d9, 0xd0bc21bc, 0x88df31ea, 0x3063568f, + 0x22d6f961, 0x9a6a9e04, 0x07bda6bd, 0xbf01c1d8, 0xadb46e36, + 0x15080953, 0x1d724e9a, 0xa5ce29ff, 0xb77b8611, 0x0fc7e174, + 0x9210d9cd, 0x2aacbea8, 0x38191146, 0x80a57623, 0xd8c66675, + 0x607a0110, 0x72cfaefe, 0xca73c99b, 0x57a4f122, 0xef189647, + 0xfdad39a9, 0x45115ecc, 0x764dee06, 0xcef18963, 0xdc44268d, + 0x64f841e8, 0xf92f7951, 0x41931e34, 0x5326b1da, 0xeb9ad6bf, + 0xb3f9c6e9, 0x0b45a18c, 0x19f00e62, 0xa14c6907, 0x3c9b51be, + 0x842736db, 0x96929935, 0x2e2efe50, 0x2654b999, 0x9ee8defc, + 0x8c5d7112, 0x34e11677, 0xa9362ece, 0x118a49ab, 0x033fe645, + 0xbb838120, 0xe3e09176, 0x5b5cf613, 0x49e959fd, 0xf1553e98, + 0x6c820621, 0xd43e6144, 0xc68bceaa, 0x7e37a9cf, 0xd67f4138, + 0x6ec3265d, 0x7c7689b3, 0xc4caeed6, 0x591dd66f, 0xe1a1b10a, + 0xf3141ee4, 0x4ba87981, 0x13cb69d7, 0xab770eb2, 0xb9c2a15c, + 0x017ec639, 0x9ca9fe80, 0x241599e5, 0x36a0360b, 0x8e1c516e, + 0x866616a7, 0x3eda71c2, 0x2c6fde2c, 0x94d3b949, 0x090481f0, + 0xb1b8e695, 0xa30d497b, 0x1bb12e1e, 0x43d23e48, 0xfb6e592d, + 0xe9dbf6c3, 0x516791a6, 0xccb0a91f, 0x740cce7a, 0x66b96194, + 0xde0506f1}, + {0x00000000, 0x01c26a37, 0x0384d46e, 0x0246be59, 0x0709a8dc, + 0x06cbc2eb, 0x048d7cb2, 0x054f1685, 0x0e1351b8, 0x0fd13b8f, + 0x0d9785d6, 0x0c55efe1, 0x091af964, 0x08d89353, 0x0a9e2d0a, + 0x0b5c473d, 0x1c26a370, 0x1de4c947, 0x1fa2771e, 0x1e601d29, + 0x1b2f0bac, 0x1aed619b, 0x18abdfc2, 0x1969b5f5, 0x1235f2c8, + 0x13f798ff, 0x11b126a6, 0x10734c91, 0x153c5a14, 0x14fe3023, + 0x16b88e7a, 0x177ae44d, 0x384d46e0, 0x398f2cd7, 0x3bc9928e, + 0x3a0bf8b9, 0x3f44ee3c, 0x3e86840b, 0x3cc03a52, 0x3d025065, + 0x365e1758, 0x379c7d6f, 0x35dac336, 0x3418a901, 0x3157bf84, + 0x3095d5b3, 0x32d36bea, 0x331101dd, 0x246be590, 0x25a98fa7, + 0x27ef31fe, 0x262d5bc9, 0x23624d4c, 0x22a0277b, 0x20e69922, + 0x2124f315, 0x2a78b428, 0x2bbade1f, 0x29fc6046, 0x283e0a71, + 0x2d711cf4, 0x2cb376c3, 0x2ef5c89a, 0x2f37a2ad, 0x709a8dc0, + 0x7158e7f7, 0x731e59ae, 0x72dc3399, 0x7793251c, 0x76514f2b, + 0x7417f172, 0x75d59b45, 0x7e89dc78, 0x7f4bb64f, 0x7d0d0816, + 0x7ccf6221, 0x798074a4, 0x78421e93, 0x7a04a0ca, 0x7bc6cafd, + 0x6cbc2eb0, 0x6d7e4487, 0x6f38fade, 0x6efa90e9, 0x6bb5866c, + 0x6a77ec5b, 0x68315202, 0x69f33835, 0x62af7f08, 0x636d153f, + 0x612bab66, 0x60e9c151, 0x65a6d7d4, 0x6464bde3, 0x662203ba, + 0x67e0698d, 0x48d7cb20, 0x4915a117, 0x4b531f4e, 0x4a917579, + 0x4fde63fc, 0x4e1c09cb, 0x4c5ab792, 0x4d98dda5, 0x46c49a98, + 0x4706f0af, 0x45404ef6, 0x448224c1, 0x41cd3244, 0x400f5873, + 0x4249e62a, 0x438b8c1d, 0x54f16850, 0x55330267, 0x5775bc3e, + 0x56b7d609, 0x53f8c08c, 0x523aaabb, 0x507c14e2, 0x51be7ed5, + 0x5ae239e8, 0x5b2053df, 0x5966ed86, 0x58a487b1, 0x5deb9134, + 0x5c29fb03, 0x5e6f455a, 0x5fad2f6d, 0xe1351b80, 0xe0f771b7, + 0xe2b1cfee, 0xe373a5d9, 0xe63cb35c, 0xe7fed96b, 0xe5b86732, + 0xe47a0d05, 0xef264a38, 0xeee4200f, 0xeca29e56, 0xed60f461, + 0xe82fe2e4, 0xe9ed88d3, 0xebab368a, 0xea695cbd, 0xfd13b8f0, + 0xfcd1d2c7, 0xfe976c9e, 0xff5506a9, 0xfa1a102c, 0xfbd87a1b, + 0xf99ec442, 0xf85cae75, 0xf300e948, 0xf2c2837f, 0xf0843d26, + 0xf1465711, 0xf4094194, 0xf5cb2ba3, 0xf78d95fa, 0xf64fffcd, + 0xd9785d60, 0xd8ba3757, 0xdafc890e, 0xdb3ee339, 0xde71f5bc, + 0xdfb39f8b, 0xddf521d2, 0xdc374be5, 0xd76b0cd8, 0xd6a966ef, + 0xd4efd8b6, 0xd52db281, 0xd062a404, 0xd1a0ce33, 0xd3e6706a, + 0xd2241a5d, 0xc55efe10, 0xc49c9427, 0xc6da2a7e, 0xc7184049, + 0xc25756cc, 0xc3953cfb, 0xc1d382a2, 0xc011e895, 0xcb4dafa8, + 0xca8fc59f, 0xc8c97bc6, 0xc90b11f1, 0xcc440774, 0xcd866d43, + 0xcfc0d31a, 0xce02b92d, 0x91af9640, 0x906dfc77, 0x922b422e, + 0x93e92819, 0x96a63e9c, 0x976454ab, 0x9522eaf2, 0x94e080c5, + 0x9fbcc7f8, 0x9e7eadcf, 0x9c381396, 0x9dfa79a1, 0x98b56f24, + 0x99770513, 0x9b31bb4a, 0x9af3d17d, 0x8d893530, 0x8c4b5f07, + 0x8e0de15e, 0x8fcf8b69, 0x8a809dec, 0x8b42f7db, 0x89044982, + 0x88c623b5, 0x839a6488, 0x82580ebf, 0x801eb0e6, 0x81dcdad1, + 0x8493cc54, 0x8551a663, 0x8717183a, 0x86d5720d, 0xa9e2d0a0, + 0xa820ba97, 0xaa6604ce, 0xaba46ef9, 0xaeeb787c, 0xaf29124b, + 0xad6fac12, 0xacadc625, 0xa7f18118, 0xa633eb2f, 0xa4755576, + 0xa5b73f41, 0xa0f829c4, 0xa13a43f3, 0xa37cfdaa, 0xa2be979d, + 0xb5c473d0, 0xb40619e7, 0xb640a7be, 0xb782cd89, 0xb2cddb0c, + 0xb30fb13b, 0xb1490f62, 0xb08b6555, 0xbbd72268, 0xba15485f, + 0xb853f606, 0xb9919c31, 0xbcde8ab4, 0xbd1ce083, 0xbf5a5eda, + 0xbe9834ed}, + {0x00000000, 0x191b3141, 0x32366282, 0x2b2d53c3, 0x646cc504, + 0x7d77f445, 0x565aa786, 0x4f4196c7, 0xc8d98a08, 0xd1c2bb49, + 0xfaefe88a, 0xe3f4d9cb, 0xacb54f0c, 0xb5ae7e4d, 0x9e832d8e, + 0x87981ccf, 0x4ac21251, 0x53d92310, 0x78f470d3, 0x61ef4192, + 0x2eaed755, 0x37b5e614, 0x1c98b5d7, 0x05838496, 0x821b9859, + 0x9b00a918, 0xb02dfadb, 0xa936cb9a, 0xe6775d5d, 0xff6c6c1c, + 0xd4413fdf, 0xcd5a0e9e, 0x958424a2, 0x8c9f15e3, 0xa7b24620, + 0xbea97761, 0xf1e8e1a6, 0xe8f3d0e7, 0xc3de8324, 0xdac5b265, + 0x5d5daeaa, 0x44469feb, 0x6f6bcc28, 0x7670fd69, 0x39316bae, + 0x202a5aef, 0x0b07092c, 0x121c386d, 0xdf4636f3, 0xc65d07b2, + 0xed705471, 0xf46b6530, 0xbb2af3f7, 0xa231c2b6, 0x891c9175, + 0x9007a034, 0x179fbcfb, 0x0e848dba, 0x25a9de79, 0x3cb2ef38, + 0x73f379ff, 0x6ae848be, 0x41c51b7d, 0x58de2a3c, 0xf0794f05, + 0xe9627e44, 0xc24f2d87, 0xdb541cc6, 0x94158a01, 0x8d0ebb40, + 0xa623e883, 0xbf38d9c2, 0x38a0c50d, 0x21bbf44c, 0x0a96a78f, + 0x138d96ce, 0x5ccc0009, 0x45d73148, 0x6efa628b, 0x77e153ca, + 0xbabb5d54, 0xa3a06c15, 0x888d3fd6, 0x91960e97, 0xded79850, + 0xc7cca911, 0xece1fad2, 0xf5facb93, 0x7262d75c, 0x6b79e61d, + 0x4054b5de, 0x594f849f, 0x160e1258, 0x0f152319, 0x243870da, + 0x3d23419b, 0x65fd6ba7, 0x7ce65ae6, 0x57cb0925, 0x4ed03864, + 0x0191aea3, 0x188a9fe2, 0x33a7cc21, 0x2abcfd60, 0xad24e1af, + 0xb43fd0ee, 0x9f12832d, 0x8609b26c, 0xc94824ab, 0xd05315ea, + 0xfb7e4629, 0xe2657768, 0x2f3f79f6, 0x362448b7, 0x1d091b74, + 0x04122a35, 0x4b53bcf2, 0x52488db3, 0x7965de70, 0x607eef31, + 0xe7e6f3fe, 0xfefdc2bf, 0xd5d0917c, 0xcccba03d, 0x838a36fa, + 0x9a9107bb, 0xb1bc5478, 0xa8a76539, 0x3b83984b, 0x2298a90a, + 0x09b5fac9, 0x10aecb88, 0x5fef5d4f, 0x46f46c0e, 0x6dd93fcd, + 0x74c20e8c, 0xf35a1243, 0xea412302, 0xc16c70c1, 0xd8774180, + 0x9736d747, 0x8e2de606, 0xa500b5c5, 0xbc1b8484, 0x71418a1a, + 0x685abb5b, 0x4377e898, 0x5a6cd9d9, 0x152d4f1e, 0x0c367e5f, + 0x271b2d9c, 0x3e001cdd, 0xb9980012, 0xa0833153, 0x8bae6290, + 0x92b553d1, 0xddf4c516, 0xc4eff457, 0xefc2a794, 0xf6d996d5, + 0xae07bce9, 0xb71c8da8, 0x9c31de6b, 0x852aef2a, 0xca6b79ed, + 0xd37048ac, 0xf85d1b6f, 0xe1462a2e, 0x66de36e1, 0x7fc507a0, + 0x54e85463, 0x4df36522, 0x02b2f3e5, 0x1ba9c2a4, 0x30849167, + 0x299fa026, 0xe4c5aeb8, 0xfdde9ff9, 0xd6f3cc3a, 0xcfe8fd7b, + 0x80a96bbc, 0x99b25afd, 0xb29f093e, 0xab84387f, 0x2c1c24b0, + 0x350715f1, 0x1e2a4632, 0x07317773, 0x4870e1b4, 0x516bd0f5, + 0x7a468336, 0x635db277, 0xcbfad74e, 0xd2e1e60f, 0xf9ccb5cc, + 0xe0d7848d, 0xaf96124a, 0xb68d230b, 0x9da070c8, 0x84bb4189, + 0x03235d46, 0x1a386c07, 0x31153fc4, 0x280e0e85, 0x674f9842, + 0x7e54a903, 0x5579fac0, 0x4c62cb81, 0x8138c51f, 0x9823f45e, + 0xb30ea79d, 0xaa1596dc, 0xe554001b, 0xfc4f315a, 0xd7626299, + 0xce7953d8, 0x49e14f17, 0x50fa7e56, 0x7bd72d95, 0x62cc1cd4, + 0x2d8d8a13, 0x3496bb52, 0x1fbbe891, 0x06a0d9d0, 0x5e7ef3ec, + 0x4765c2ad, 0x6c48916e, 0x7553a02f, 0x3a1236e8, 0x230907a9, + 0x0824546a, 0x113f652b, 0x96a779e4, 0x8fbc48a5, 0xa4911b66, + 0xbd8a2a27, 0xf2cbbce0, 0xebd08da1, 0xc0fdde62, 0xd9e6ef23, + 0x14bce1bd, 0x0da7d0fc, 0x268a833f, 0x3f91b27e, 0x70d024b9, + 0x69cb15f8, 0x42e6463b, 0x5bfd777a, 0xdc656bb5, 0xc57e5af4, + 0xee530937, 0xf7483876, 0xb809aeb1, 0xa1129ff0, 0x8a3fcc33, + 0x9324fd72}, + {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}}; + +local const z_word_t FAR crc_braid_big_table[][256] = { + {0x00000000, 0x96300777, 0x2c610eee, 0xba510999, 0x19c46d07, + 0x8ff46a70, 0x35a563e9, 0xa395649e, 0x3288db0e, 0xa4b8dc79, + 0x1ee9d5e0, 0x88d9d297, 0x2b4cb609, 0xbd7cb17e, 0x072db8e7, + 0x911dbf90, 0x6410b71d, 0xf220b06a, 0x4871b9f3, 0xde41be84, + 0x7dd4da1a, 0xebe4dd6d, 0x51b5d4f4, 0xc785d383, 0x56986c13, + 0xc0a86b64, 0x7af962fd, 0xecc9658a, 0x4f5c0114, 0xd96c0663, + 0x633d0ffa, 0xf50d088d, 0xc8206e3b, 0x5e10694c, 0xe44160d5, + 0x727167a2, 0xd1e4033c, 0x47d4044b, 0xfd850dd2, 0x6bb50aa5, + 0xfaa8b535, 0x6c98b242, 0xd6c9bbdb, 0x40f9bcac, 0xe36cd832, + 0x755cdf45, 0xcf0dd6dc, 0x593dd1ab, 0xac30d926, 0x3a00de51, + 0x8051d7c8, 0x1661d0bf, 0xb5f4b421, 0x23c4b356, 0x9995bacf, + 0x0fa5bdb8, 0x9eb80228, 0x0888055f, 0xb2d90cc6, 0x24e90bb1, + 0x877c6f2f, 0x114c6858, 0xab1d61c1, 0x3d2d66b6, 0x9041dc76, + 0x0671db01, 0xbc20d298, 0x2a10d5ef, 0x8985b171, 0x1fb5b606, + 0xa5e4bf9f, 0x33d4b8e8, 0xa2c90778, 0x34f9000f, 0x8ea80996, + 0x18980ee1, 0xbb0d6a7f, 0x2d3d6d08, 0x976c6491, 0x015c63e6, + 0xf4516b6b, 0x62616c1c, 0xd8306585, 0x4e0062f2, 0xed95066c, + 0x7ba5011b, 0xc1f40882, 0x57c40ff5, 0xc6d9b065, 0x50e9b712, + 0xeab8be8b, 0x7c88b9fc, 0xdf1ddd62, 0x492dda15, 0xf37cd38c, + 0x654cd4fb, 0x5861b24d, 0xce51b53a, 0x7400bca3, 0xe230bbd4, + 0x41a5df4a, 0xd795d83d, 0x6dc4d1a4, 0xfbf4d6d3, 0x6ae96943, + 0xfcd96e34, 0x468867ad, 0xd0b860da, 0x732d0444, 0xe51d0333, + 0x5f4c0aaa, 0xc97c0ddd, 0x3c710550, 0xaa410227, 0x10100bbe, + 0x86200cc9, 0x25b56857, 0xb3856f20, 0x09d466b9, 0x9fe461ce, + 0x0ef9de5e, 0x98c9d929, 0x2298d0b0, 0xb4a8d7c7, 0x173db359, + 0x810db42e, 0x3b5cbdb7, 0xad6cbac0, 0x2083b8ed, 0xb6b3bf9a, + 0x0ce2b603, 0x9ad2b174, 0x3947d5ea, 0xaf77d29d, 0x1526db04, + 0x8316dc73, 0x120b63e3, 0x843b6494, 0x3e6a6d0d, 0xa85a6a7a, + 0x0bcf0ee4, 0x9dff0993, 0x27ae000a, 0xb19e077d, 0x44930ff0, + 0xd2a30887, 0x68f2011e, 0xfec20669, 0x5d5762f7, 0xcb676580, + 0x71366c19, 0xe7066b6e, 0x761bd4fe, 0xe02bd389, 0x5a7ada10, + 0xcc4add67, 0x6fdfb9f9, 0xf9efbe8e, 0x43beb717, 0xd58eb060, + 0xe8a3d6d6, 0x7e93d1a1, 0xc4c2d838, 0x52f2df4f, 0xf167bbd1, + 0x6757bca6, 0xdd06b53f, 0x4b36b248, 0xda2b0dd8, 0x4c1b0aaf, + 0xf64a0336, 0x607a0441, 0xc3ef60df, 0x55df67a8, 0xef8e6e31, + 0x79be6946, 0x8cb361cb, 0x1a8366bc, 0xa0d26f25, 0x36e26852, + 0x95770ccc, 0x03470bbb, 0xb9160222, 0x2f260555, 0xbe3bbac5, + 0x280bbdb2, 0x925ab42b, 0x046ab35c, 0xa7ffd7c2, 0x31cfd0b5, + 0x8b9ed92c, 0x1daede5b, 0xb0c2649b, 0x26f263ec, 0x9ca36a75, + 0x0a936d02, 0xa906099c, 0x3f360eeb, 0x85670772, 0x13570005, + 0x824abf95, 0x147ab8e2, 0xae2bb17b, 0x381bb60c, 0x9b8ed292, + 0x0dbed5e5, 0xb7efdc7c, 0x21dfdb0b, 0xd4d2d386, 0x42e2d4f1, + 0xf8b3dd68, 0x6e83da1f, 0xcd16be81, 0x5b26b9f6, 0xe177b06f, + 0x7747b718, 0xe65a0888, 0x706a0fff, 0xca3b0666, 0x5c0b0111, + 0xff9e658f, 0x69ae62f8, 0xd3ff6b61, 0x45cf6c16, 0x78e20aa0, + 0xeed20dd7, 0x5483044e, 0xc2b30339, 0x612667a7, 0xf71660d0, + 0x4d476949, 0xdb776e3e, 0x4a6ad1ae, 0xdc5ad6d9, 0x660bdf40, + 0xf03bd837, 0x53aebca9, 0xc59ebbde, 0x7fcfb247, 0xe9ffb530, + 0x1cf2bdbd, 0x8ac2baca, 0x3093b353, 0xa6a3b424, 0x0536d0ba, + 0x9306d7cd, 0x2957de54, 0xbf67d923, 0x2e7a66b3, 0xb84a61c4, + 0x021b685d, 0x942b6f2a, 0x37be0bb4, 0xa18e0cc3, 0x1bdf055a, + 0x8def022d}, + {0x00000000, 0x41311b19, 0x82623632, 0xc3532d2b, 0x04c56c64, + 0x45f4777d, 0x86a75a56, 0xc796414f, 0x088ad9c8, 0x49bbc2d1, + 0x8ae8effa, 0xcbd9f4e3, 0x0c4fb5ac, 0x4d7eaeb5, 0x8e2d839e, + 0xcf1c9887, 0x5112c24a, 0x1023d953, 0xd370f478, 0x9241ef61, + 0x55d7ae2e, 0x14e6b537, 0xd7b5981c, 0x96848305, 0x59981b82, + 0x18a9009b, 0xdbfa2db0, 0x9acb36a9, 0x5d5d77e6, 0x1c6c6cff, + 0xdf3f41d4, 0x9e0e5acd, 0xa2248495, 0xe3159f8c, 0x2046b2a7, + 0x6177a9be, 0xa6e1e8f1, 0xe7d0f3e8, 0x2483dec3, 0x65b2c5da, + 0xaaae5d5d, 0xeb9f4644, 0x28cc6b6f, 0x69fd7076, 0xae6b3139, + 0xef5a2a20, 0x2c09070b, 0x6d381c12, 0xf33646df, 0xb2075dc6, + 0x715470ed, 0x30656bf4, 0xf7f32abb, 0xb6c231a2, 0x75911c89, + 0x34a00790, 0xfbbc9f17, 0xba8d840e, 0x79dea925, 0x38efb23c, + 0xff79f373, 0xbe48e86a, 0x7d1bc541, 0x3c2ade58, 0x054f79f0, + 0x447e62e9, 0x872d4fc2, 0xc61c54db, 0x018a1594, 0x40bb0e8d, + 0x83e823a6, 0xc2d938bf, 0x0dc5a038, 0x4cf4bb21, 0x8fa7960a, + 0xce968d13, 0x0900cc5c, 0x4831d745, 0x8b62fa6e, 0xca53e177, + 0x545dbbba, 0x156ca0a3, 0xd63f8d88, 0x970e9691, 0x5098d7de, + 0x11a9ccc7, 0xd2fae1ec, 0x93cbfaf5, 0x5cd76272, 0x1de6796b, + 0xdeb55440, 0x9f844f59, 0x58120e16, 0x1923150f, 0xda703824, + 0x9b41233d, 0xa76bfd65, 0xe65ae67c, 0x2509cb57, 0x6438d04e, + 0xa3ae9101, 0xe29f8a18, 0x21cca733, 0x60fdbc2a, 0xafe124ad, + 0xeed03fb4, 0x2d83129f, 0x6cb20986, 0xab2448c9, 0xea1553d0, + 0x29467efb, 0x687765e2, 0xf6793f2f, 0xb7482436, 0x741b091d, + 0x352a1204, 0xf2bc534b, 0xb38d4852, 0x70de6579, 0x31ef7e60, + 0xfef3e6e7, 0xbfc2fdfe, 0x7c91d0d5, 0x3da0cbcc, 0xfa368a83, + 0xbb07919a, 0x7854bcb1, 0x3965a7a8, 0x4b98833b, 0x0aa99822, + 0xc9fab509, 0x88cbae10, 0x4f5def5f, 0x0e6cf446, 0xcd3fd96d, + 0x8c0ec274, 0x43125af3, 0x022341ea, 0xc1706cc1, 0x804177d8, + 0x47d73697, 0x06e62d8e, 0xc5b500a5, 0x84841bbc, 0x1a8a4171, + 0x5bbb5a68, 0x98e87743, 0xd9d96c5a, 0x1e4f2d15, 0x5f7e360c, + 0x9c2d1b27, 0xdd1c003e, 0x120098b9, 0x533183a0, 0x9062ae8b, + 0xd153b592, 0x16c5f4dd, 0x57f4efc4, 0x94a7c2ef, 0xd596d9f6, + 0xe9bc07ae, 0xa88d1cb7, 0x6bde319c, 0x2aef2a85, 0xed796bca, + 0xac4870d3, 0x6f1b5df8, 0x2e2a46e1, 0xe136de66, 0xa007c57f, + 0x6354e854, 0x2265f34d, 0xe5f3b202, 0xa4c2a91b, 0x67918430, + 0x26a09f29, 0xb8aec5e4, 0xf99fdefd, 0x3accf3d6, 0x7bfde8cf, + 0xbc6ba980, 0xfd5ab299, 0x3e099fb2, 0x7f3884ab, 0xb0241c2c, + 0xf1150735, 0x32462a1e, 0x73773107, 0xb4e17048, 0xf5d06b51, + 0x3683467a, 0x77b25d63, 0x4ed7facb, 0x0fe6e1d2, 0xccb5ccf9, + 0x8d84d7e0, 0x4a1296af, 0x0b238db6, 0xc870a09d, 0x8941bb84, + 0x465d2303, 0x076c381a, 0xc43f1531, 0x850e0e28, 0x42984f67, + 0x03a9547e, 0xc0fa7955, 0x81cb624c, 0x1fc53881, 0x5ef42398, + 0x9da70eb3, 0xdc9615aa, 0x1b0054e5, 0x5a314ffc, 0x996262d7, + 0xd85379ce, 0x174fe149, 0x567efa50, 0x952dd77b, 0xd41ccc62, + 0x138a8d2d, 0x52bb9634, 0x91e8bb1f, 0xd0d9a006, 0xecf37e5e, + 0xadc26547, 0x6e91486c, 0x2fa05375, 0xe836123a, 0xa9070923, + 0x6a542408, 0x2b653f11, 0xe479a796, 0xa548bc8f, 0x661b91a4, + 0x272a8abd, 0xe0bccbf2, 0xa18dd0eb, 0x62defdc0, 0x23efe6d9, + 0xbde1bc14, 0xfcd0a70d, 0x3f838a26, 0x7eb2913f, 0xb924d070, + 0xf815cb69, 0x3b46e642, 0x7a77fd5b, 0xb56b65dc, 0xf45a7ec5, + 0x370953ee, 0x763848f7, 0xb1ae09b8, 0xf09f12a1, 0x33cc3f8a, + 0x72fd2493}, + {0x00000000, 0x376ac201, 0x6ed48403, 0x59be4602, 0xdca80907, + 0xebc2cb06, 0xb27c8d04, 0x85164f05, 0xb851130e, 0x8f3bd10f, + 0xd685970d, 0xe1ef550c, 0x64f91a09, 0x5393d808, 0x0a2d9e0a, + 0x3d475c0b, 0x70a3261c, 0x47c9e41d, 0x1e77a21f, 0x291d601e, + 0xac0b2f1b, 0x9b61ed1a, 0xc2dfab18, 0xf5b56919, 0xc8f23512, + 0xff98f713, 0xa626b111, 0x914c7310, 0x145a3c15, 0x2330fe14, + 0x7a8eb816, 0x4de47a17, 0xe0464d38, 0xd72c8f39, 0x8e92c93b, + 0xb9f80b3a, 0x3cee443f, 0x0b84863e, 0x523ac03c, 0x6550023d, + 0x58175e36, 0x6f7d9c37, 0x36c3da35, 0x01a91834, 0x84bf5731, + 0xb3d59530, 0xea6bd332, 0xdd011133, 0x90e56b24, 0xa78fa925, + 0xfe31ef27, 0xc95b2d26, 0x4c4d6223, 0x7b27a022, 0x2299e620, + 0x15f32421, 0x28b4782a, 0x1fdeba2b, 0x4660fc29, 0x710a3e28, + 0xf41c712d, 0xc376b32c, 0x9ac8f52e, 0xada2372f, 0xc08d9a70, + 0xf7e75871, 0xae591e73, 0x9933dc72, 0x1c259377, 0x2b4f5176, + 0x72f11774, 0x459bd575, 0x78dc897e, 0x4fb64b7f, 0x16080d7d, + 0x2162cf7c, 0xa4748079, 0x931e4278, 0xcaa0047a, 0xfdcac67b, + 0xb02ebc6c, 0x87447e6d, 0xdefa386f, 0xe990fa6e, 0x6c86b56b, + 0x5bec776a, 0x02523168, 0x3538f369, 0x087faf62, 0x3f156d63, + 0x66ab2b61, 0x51c1e960, 0xd4d7a665, 0xe3bd6464, 0xba032266, + 0x8d69e067, 0x20cbd748, 0x17a11549, 0x4e1f534b, 0x7975914a, + 0xfc63de4f, 0xcb091c4e, 0x92b75a4c, 0xa5dd984d, 0x989ac446, + 0xaff00647, 0xf64e4045, 0xc1248244, 0x4432cd41, 0x73580f40, + 0x2ae64942, 0x1d8c8b43, 0x5068f154, 0x67023355, 0x3ebc7557, + 0x09d6b756, 0x8cc0f853, 0xbbaa3a52, 0xe2147c50, 0xd57ebe51, + 0xe839e25a, 0xdf53205b, 0x86ed6659, 0xb187a458, 0x3491eb5d, + 0x03fb295c, 0x5a456f5e, 0x6d2fad5f, 0x801b35e1, 0xb771f7e0, + 0xeecfb1e2, 0xd9a573e3, 0x5cb33ce6, 0x6bd9fee7, 0x3267b8e5, + 0x050d7ae4, 0x384a26ef, 0x0f20e4ee, 0x569ea2ec, 0x61f460ed, + 0xe4e22fe8, 0xd388ede9, 0x8a36abeb, 0xbd5c69ea, 0xf0b813fd, + 0xc7d2d1fc, 0x9e6c97fe, 0xa90655ff, 0x2c101afa, 0x1b7ad8fb, + 0x42c49ef9, 0x75ae5cf8, 0x48e900f3, 0x7f83c2f2, 0x263d84f0, + 0x115746f1, 0x944109f4, 0xa32bcbf5, 0xfa958df7, 0xcdff4ff6, + 0x605d78d9, 0x5737bad8, 0x0e89fcda, 0x39e33edb, 0xbcf571de, + 0x8b9fb3df, 0xd221f5dd, 0xe54b37dc, 0xd80c6bd7, 0xef66a9d6, + 0xb6d8efd4, 0x81b22dd5, 0x04a462d0, 0x33cea0d1, 0x6a70e6d3, + 0x5d1a24d2, 0x10fe5ec5, 0x27949cc4, 0x7e2adac6, 0x494018c7, + 0xcc5657c2, 0xfb3c95c3, 0xa282d3c1, 0x95e811c0, 0xa8af4dcb, + 0x9fc58fca, 0xc67bc9c8, 0xf1110bc9, 0x740744cc, 0x436d86cd, + 0x1ad3c0cf, 0x2db902ce, 0x4096af91, 0x77fc6d90, 0x2e422b92, + 0x1928e993, 0x9c3ea696, 0xab546497, 0xf2ea2295, 0xc580e094, + 0xf8c7bc9f, 0xcfad7e9e, 0x9613389c, 0xa179fa9d, 0x246fb598, + 0x13057799, 0x4abb319b, 0x7dd1f39a, 0x3035898d, 0x075f4b8c, + 0x5ee10d8e, 0x698bcf8f, 0xec9d808a, 0xdbf7428b, 0x82490489, + 0xb523c688, 0x88649a83, 0xbf0e5882, 0xe6b01e80, 0xd1dadc81, + 0x54cc9384, 0x63a65185, 0x3a181787, 0x0d72d586, 0xa0d0e2a9, + 0x97ba20a8, 0xce0466aa, 0xf96ea4ab, 0x7c78ebae, 0x4b1229af, + 0x12ac6fad, 0x25c6adac, 0x1881f1a7, 0x2feb33a6, 0x765575a4, + 0x413fb7a5, 0xc429f8a0, 0xf3433aa1, 0xaafd7ca3, 0x9d97bea2, + 0xd073c4b5, 0xe71906b4, 0xbea740b6, 0x89cd82b7, 0x0cdbcdb2, + 0x3bb10fb3, 0x620f49b1, 0x55658bb0, 0x6822d7bb, 0x5f4815ba, + 0x06f653b8, 0x319c91b9, 0xb48adebc, 0x83e01cbd, 0xda5e5abf, + 0xed3498be}, + {0x00000000, 0x6567bcb8, 0x8bc809aa, 0xeeafb512, 0x5797628f, + 0x32f0de37, 0xdc5f6b25, 0xb938d79d, 0xef28b4c5, 0x8a4f087d, + 0x64e0bd6f, 0x018701d7, 0xb8bfd64a, 0xddd86af2, 0x3377dfe0, + 0x56106358, 0x9f571950, 0xfa30a5e8, 0x149f10fa, 0x71f8ac42, + 0xc8c07bdf, 0xada7c767, 0x43087275, 0x266fcecd, 0x707fad95, + 0x1518112d, 0xfbb7a43f, 0x9ed01887, 0x27e8cf1a, 0x428f73a2, + 0xac20c6b0, 0xc9477a08, 0x3eaf32a0, 0x5bc88e18, 0xb5673b0a, + 0xd00087b2, 0x6938502f, 0x0c5fec97, 0xe2f05985, 0x8797e53d, + 0xd1878665, 0xb4e03add, 0x5a4f8fcf, 0x3f283377, 0x8610e4ea, + 0xe3775852, 0x0dd8ed40, 0x68bf51f8, 0xa1f82bf0, 0xc49f9748, + 0x2a30225a, 0x4f579ee2, 0xf66f497f, 0x9308f5c7, 0x7da740d5, + 0x18c0fc6d, 0x4ed09f35, 0x2bb7238d, 0xc518969f, 0xa07f2a27, + 0x1947fdba, 0x7c204102, 0x928ff410, 0xf7e848a8, 0x3d58149b, + 0x583fa823, 0xb6901d31, 0xd3f7a189, 0x6acf7614, 0x0fa8caac, + 0xe1077fbe, 0x8460c306, 0xd270a05e, 0xb7171ce6, 0x59b8a9f4, + 0x3cdf154c, 0x85e7c2d1, 0xe0807e69, 0x0e2fcb7b, 0x6b4877c3, + 0xa20f0dcb, 0xc768b173, 0x29c70461, 0x4ca0b8d9, 0xf5986f44, + 0x90ffd3fc, 0x7e5066ee, 0x1b37da56, 0x4d27b90e, 0x284005b6, + 0xc6efb0a4, 0xa3880c1c, 0x1ab0db81, 0x7fd76739, 0x9178d22b, + 0xf41f6e93, 0x03f7263b, 0x66909a83, 0x883f2f91, 0xed589329, + 0x546044b4, 0x3107f80c, 0xdfa84d1e, 0xbacff1a6, 0xecdf92fe, + 0x89b82e46, 0x67179b54, 0x027027ec, 0xbb48f071, 0xde2f4cc9, + 0x3080f9db, 0x55e74563, 0x9ca03f6b, 0xf9c783d3, 0x176836c1, + 0x720f8a79, 0xcb375de4, 0xae50e15c, 0x40ff544e, 0x2598e8f6, + 0x73888bae, 0x16ef3716, 0xf8408204, 0x9d273ebc, 0x241fe921, + 0x41785599, 0xafd7e08b, 0xcab05c33, 0x3bb659ed, 0x5ed1e555, + 0xb07e5047, 0xd519ecff, 0x6c213b62, 0x094687da, 0xe7e932c8, + 0x828e8e70, 0xd49eed28, 0xb1f95190, 0x5f56e482, 0x3a31583a, + 0x83098fa7, 0xe66e331f, 0x08c1860d, 0x6da63ab5, 0xa4e140bd, + 0xc186fc05, 0x2f294917, 0x4a4ef5af, 0xf3762232, 0x96119e8a, + 0x78be2b98, 0x1dd99720, 0x4bc9f478, 0x2eae48c0, 0xc001fdd2, + 0xa566416a, 0x1c5e96f7, 0x79392a4f, 0x97969f5d, 0xf2f123e5, + 0x05196b4d, 0x607ed7f5, 0x8ed162e7, 0xebb6de5f, 0x528e09c2, + 0x37e9b57a, 0xd9460068, 0xbc21bcd0, 0xea31df88, 0x8f566330, + 0x61f9d622, 0x049e6a9a, 0xbda6bd07, 0xd8c101bf, 0x366eb4ad, + 0x53090815, 0x9a4e721d, 0xff29cea5, 0x11867bb7, 0x74e1c70f, + 0xcdd91092, 0xa8beac2a, 0x46111938, 0x2376a580, 0x7566c6d8, + 0x10017a60, 0xfeaecf72, 0x9bc973ca, 0x22f1a457, 0x479618ef, + 0xa939adfd, 0xcc5e1145, 0x06ee4d76, 0x6389f1ce, 0x8d2644dc, + 0xe841f864, 0x51792ff9, 0x341e9341, 0xdab12653, 0xbfd69aeb, + 0xe9c6f9b3, 0x8ca1450b, 0x620ef019, 0x07694ca1, 0xbe519b3c, + 0xdb362784, 0x35999296, 0x50fe2e2e, 0x99b95426, 0xfcdee89e, + 0x12715d8c, 0x7716e134, 0xce2e36a9, 0xab498a11, 0x45e63f03, + 0x208183bb, 0x7691e0e3, 0x13f65c5b, 0xfd59e949, 0x983e55f1, + 0x2106826c, 0x44613ed4, 0xaace8bc6, 0xcfa9377e, 0x38417fd6, + 0x5d26c36e, 0xb389767c, 0xd6eecac4, 0x6fd61d59, 0x0ab1a1e1, + 0xe41e14f3, 0x8179a84b, 0xd769cb13, 0xb20e77ab, 0x5ca1c2b9, + 0x39c67e01, 0x80fea99c, 0xe5991524, 0x0b36a036, 0x6e511c8e, + 0xa7166686, 0xc271da3e, 0x2cde6f2c, 0x49b9d394, 0xf0810409, + 0x95e6b8b1, 0x7b490da3, 0x1e2eb11b, 0x483ed243, 0x2d596efb, + 0xc3f6dbe9, 0xa6916751, 0x1fa9b0cc, 0x7ace0c74, 0x9461b966, + 0xf10605de}}; + +#endif + +#endif + +#if N == 2 + +#if W == 8 + +local const z_crc_t FAR crc_braid_table[][256] = { + {0x00000000, 0xae689191, 0x87a02563, 0x29c8b4f2, 0xd4314c87, + 0x7a59dd16, 0x539169e4, 0xfdf9f875, 0x73139f4f, 0xdd7b0ede, + 0xf4b3ba2c, 0x5adb2bbd, 0xa722d3c8, 0x094a4259, 0x2082f6ab, + 0x8eea673a, 0xe6273e9e, 0x484faf0f, 0x61871bfd, 0xcfef8a6c, + 0x32167219, 0x9c7ee388, 0xb5b6577a, 0x1bdec6eb, 0x9534a1d1, + 0x3b5c3040, 0x129484b2, 0xbcfc1523, 0x4105ed56, 0xef6d7cc7, + 0xc6a5c835, 0x68cd59a4, 0x173f7b7d, 0xb957eaec, 0x909f5e1e, + 0x3ef7cf8f, 0xc30e37fa, 0x6d66a66b, 0x44ae1299, 0xeac68308, + 0x642ce432, 0xca4475a3, 0xe38cc151, 0x4de450c0, 0xb01da8b5, + 0x1e753924, 0x37bd8dd6, 0x99d51c47, 0xf11845e3, 0x5f70d472, + 0x76b86080, 0xd8d0f111, 0x25290964, 0x8b4198f5, 0xa2892c07, + 0x0ce1bd96, 0x820bdaac, 0x2c634b3d, 0x05abffcf, 0xabc36e5e, + 0x563a962b, 0xf85207ba, 0xd19ab348, 0x7ff222d9, 0x2e7ef6fa, + 0x8016676b, 0xa9ded399, 0x07b64208, 0xfa4fba7d, 0x54272bec, + 0x7def9f1e, 0xd3870e8f, 0x5d6d69b5, 0xf305f824, 0xdacd4cd6, + 0x74a5dd47, 0x895c2532, 0x2734b4a3, 0x0efc0051, 0xa09491c0, + 0xc859c864, 0x663159f5, 0x4ff9ed07, 0xe1917c96, 0x1c6884e3, + 0xb2001572, 0x9bc8a180, 0x35a03011, 0xbb4a572b, 0x1522c6ba, + 0x3cea7248, 0x9282e3d9, 0x6f7b1bac, 0xc1138a3d, 0xe8db3ecf, + 0x46b3af5e, 0x39418d87, 0x97291c16, 0xbee1a8e4, 0x10893975, + 0xed70c100, 0x43185091, 0x6ad0e463, 0xc4b875f2, 0x4a5212c8, + 0xe43a8359, 0xcdf237ab, 0x639aa63a, 0x9e635e4f, 0x300bcfde, + 0x19c37b2c, 0xb7abeabd, 0xdf66b319, 0x710e2288, 0x58c6967a, + 0xf6ae07eb, 0x0b57ff9e, 0xa53f6e0f, 0x8cf7dafd, 0x229f4b6c, + 0xac752c56, 0x021dbdc7, 0x2bd50935, 0x85bd98a4, 0x784460d1, + 0xd62cf140, 0xffe445b2, 0x518cd423, 0x5cfdedf4, 0xf2957c65, + 0xdb5dc897, 0x75355906, 0x88cca173, 0x26a430e2, 0x0f6c8410, + 0xa1041581, 0x2fee72bb, 0x8186e32a, 0xa84e57d8, 0x0626c649, + 0xfbdf3e3c, 0x55b7afad, 0x7c7f1b5f, 0xd2178ace, 0xbadad36a, + 0x14b242fb, 0x3d7af609, 0x93126798, 0x6eeb9fed, 0xc0830e7c, + 0xe94bba8e, 0x47232b1f, 0xc9c94c25, 0x67a1ddb4, 0x4e696946, + 0xe001f8d7, 0x1df800a2, 0xb3909133, 0x9a5825c1, 0x3430b450, + 0x4bc29689, 0xe5aa0718, 0xcc62b3ea, 0x620a227b, 0x9ff3da0e, + 0x319b4b9f, 0x1853ff6d, 0xb63b6efc, 0x38d109c6, 0x96b99857, + 0xbf712ca5, 0x1119bd34, 0xece04541, 0x4288d4d0, 0x6b406022, + 0xc528f1b3, 0xade5a817, 0x038d3986, 0x2a458d74, 0x842d1ce5, + 0x79d4e490, 0xd7bc7501, 0xfe74c1f3, 0x501c5062, 0xdef63758, + 0x709ea6c9, 0x5956123b, 0xf73e83aa, 0x0ac77bdf, 0xa4afea4e, + 0x8d675ebc, 0x230fcf2d, 0x72831b0e, 0xdceb8a9f, 0xf5233e6d, + 0x5b4baffc, 0xa6b25789, 0x08dac618, 0x211272ea, 0x8f7ae37b, + 0x01908441, 0xaff815d0, 0x8630a122, 0x285830b3, 0xd5a1c8c6, + 0x7bc95957, 0x5201eda5, 0xfc697c34, 0x94a42590, 0x3accb401, + 0x130400f3, 0xbd6c9162, 0x40956917, 0xeefdf886, 0xc7354c74, + 0x695ddde5, 0xe7b7badf, 0x49df2b4e, 0x60179fbc, 0xce7f0e2d, + 0x3386f658, 0x9dee67c9, 0xb426d33b, 0x1a4e42aa, 0x65bc6073, + 0xcbd4f1e2, 0xe21c4510, 0x4c74d481, 0xb18d2cf4, 0x1fe5bd65, + 0x362d0997, 0x98459806, 0x16afff3c, 0xb8c76ead, 0x910fda5f, + 0x3f674bce, 0xc29eb3bb, 0x6cf6222a, 0x453e96d8, 0xeb560749, + 0x839b5eed, 0x2df3cf7c, 0x043b7b8e, 0xaa53ea1f, 0x57aa126a, + 0xf9c283fb, 0xd00a3709, 0x7e62a698, 0xf088c1a2, 0x5ee05033, + 0x7728e4c1, 0xd9407550, 0x24b98d25, 0x8ad11cb4, 0xa319a846, + 0x0d7139d7}, + {0x00000000, 0xb9fbdbe8, 0xa886b191, 0x117d6a79, 0x8a7c6563, + 0x3387be8b, 0x22fad4f2, 0x9b010f1a, 0xcf89cc87, 0x7672176f, + 0x670f7d16, 0xdef4a6fe, 0x45f5a9e4, 0xfc0e720c, 0xed731875, + 0x5488c39d, 0x44629f4f, 0xfd9944a7, 0xece42ede, 0x551ff536, + 0xce1efa2c, 0x77e521c4, 0x66984bbd, 0xdf639055, 0x8beb53c8, + 0x32108820, 0x236de259, 0x9a9639b1, 0x019736ab, 0xb86ced43, + 0xa911873a, 0x10ea5cd2, 0x88c53e9e, 0x313ee576, 0x20438f0f, + 0x99b854e7, 0x02b95bfd, 0xbb428015, 0xaa3fea6c, 0x13c43184, + 0x474cf219, 0xfeb729f1, 0xefca4388, 0x56319860, 0xcd30977a, + 0x74cb4c92, 0x65b626eb, 0xdc4dfd03, 0xcca7a1d1, 0x755c7a39, + 0x64211040, 0xdddacba8, 0x46dbc4b2, 0xff201f5a, 0xee5d7523, + 0x57a6aecb, 0x032e6d56, 0xbad5b6be, 0xaba8dcc7, 0x1253072f, + 0x89520835, 0x30a9d3dd, 0x21d4b9a4, 0x982f624c, 0xcafb7b7d, + 0x7300a095, 0x627dcaec, 0xdb861104, 0x40871e1e, 0xf97cc5f6, + 0xe801af8f, 0x51fa7467, 0x0572b7fa, 0xbc896c12, 0xadf4066b, + 0x140fdd83, 0x8f0ed299, 0x36f50971, 0x27886308, 0x9e73b8e0, + 0x8e99e432, 0x37623fda, 0x261f55a3, 0x9fe48e4b, 0x04e58151, + 0xbd1e5ab9, 0xac6330c0, 0x1598eb28, 0x411028b5, 0xf8ebf35d, + 0xe9969924, 0x506d42cc, 0xcb6c4dd6, 0x7297963e, 0x63eafc47, + 0xda1127af, 0x423e45e3, 0xfbc59e0b, 0xeab8f472, 0x53432f9a, + 0xc8422080, 0x71b9fb68, 0x60c49111, 0xd93f4af9, 0x8db78964, + 0x344c528c, 0x253138f5, 0x9ccae31d, 0x07cbec07, 0xbe3037ef, + 0xaf4d5d96, 0x16b6867e, 0x065cdaac, 0xbfa70144, 0xaeda6b3d, + 0x1721b0d5, 0x8c20bfcf, 0x35db6427, 0x24a60e5e, 0x9d5dd5b6, + 0xc9d5162b, 0x702ecdc3, 0x6153a7ba, 0xd8a87c52, 0x43a97348, + 0xfa52a8a0, 0xeb2fc2d9, 0x52d41931, 0x4e87f0bb, 0xf77c2b53, + 0xe601412a, 0x5ffa9ac2, 0xc4fb95d8, 0x7d004e30, 0x6c7d2449, + 0xd586ffa1, 0x810e3c3c, 0x38f5e7d4, 0x29888dad, 0x90735645, + 0x0b72595f, 0xb28982b7, 0xa3f4e8ce, 0x1a0f3326, 0x0ae56ff4, + 0xb31eb41c, 0xa263de65, 0x1b98058d, 0x80990a97, 0x3962d17f, + 0x281fbb06, 0x91e460ee, 0xc56ca373, 0x7c97789b, 0x6dea12e2, + 0xd411c90a, 0x4f10c610, 0xf6eb1df8, 0xe7967781, 0x5e6dac69, + 0xc642ce25, 0x7fb915cd, 0x6ec47fb4, 0xd73fa45c, 0x4c3eab46, + 0xf5c570ae, 0xe4b81ad7, 0x5d43c13f, 0x09cb02a2, 0xb030d94a, + 0xa14db333, 0x18b668db, 0x83b767c1, 0x3a4cbc29, 0x2b31d650, + 0x92ca0db8, 0x8220516a, 0x3bdb8a82, 0x2aa6e0fb, 0x935d3b13, + 0x085c3409, 0xb1a7efe1, 0xa0da8598, 0x19215e70, 0x4da99ded, + 0xf4524605, 0xe52f2c7c, 0x5cd4f794, 0xc7d5f88e, 0x7e2e2366, + 0x6f53491f, 0xd6a892f7, 0x847c8bc6, 0x3d87502e, 0x2cfa3a57, + 0x9501e1bf, 0x0e00eea5, 0xb7fb354d, 0xa6865f34, 0x1f7d84dc, + 0x4bf54741, 0xf20e9ca9, 0xe373f6d0, 0x5a882d38, 0xc1892222, + 0x7872f9ca, 0x690f93b3, 0xd0f4485b, 0xc01e1489, 0x79e5cf61, + 0x6898a518, 0xd1637ef0, 0x4a6271ea, 0xf399aa02, 0xe2e4c07b, + 0x5b1f1b93, 0x0f97d80e, 0xb66c03e6, 0xa711699f, 0x1eeab277, + 0x85ebbd6d, 0x3c106685, 0x2d6d0cfc, 0x9496d714, 0x0cb9b558, + 0xb5426eb0, 0xa43f04c9, 0x1dc4df21, 0x86c5d03b, 0x3f3e0bd3, + 0x2e4361aa, 0x97b8ba42, 0xc33079df, 0x7acba237, 0x6bb6c84e, + 0xd24d13a6, 0x494c1cbc, 0xf0b7c754, 0xe1caad2d, 0x583176c5, + 0x48db2a17, 0xf120f1ff, 0xe05d9b86, 0x59a6406e, 0xc2a74f74, + 0x7b5c949c, 0x6a21fee5, 0xd3da250d, 0x8752e690, 0x3ea93d78, + 0x2fd45701, 0x962f8ce9, 0x0d2e83f3, 0xb4d5581b, 0xa5a83262, + 0x1c53e98a}, + {0x00000000, 0x9d0fe176, 0xe16ec4ad, 0x7c6125db, 0x19ac8f1b, + 0x84a36e6d, 0xf8c24bb6, 0x65cdaac0, 0x33591e36, 0xae56ff40, + 0xd237da9b, 0x4f383bed, 0x2af5912d, 0xb7fa705b, 0xcb9b5580, + 0x5694b4f6, 0x66b23c6c, 0xfbbddd1a, 0x87dcf8c1, 0x1ad319b7, + 0x7f1eb377, 0xe2115201, 0x9e7077da, 0x037f96ac, 0x55eb225a, + 0xc8e4c32c, 0xb485e6f7, 0x298a0781, 0x4c47ad41, 0xd1484c37, + 0xad2969ec, 0x3026889a, 0xcd6478d8, 0x506b99ae, 0x2c0abc75, + 0xb1055d03, 0xd4c8f7c3, 0x49c716b5, 0x35a6336e, 0xa8a9d218, + 0xfe3d66ee, 0x63328798, 0x1f53a243, 0x825c4335, 0xe791e9f5, + 0x7a9e0883, 0x06ff2d58, 0x9bf0cc2e, 0xabd644b4, 0x36d9a5c2, + 0x4ab88019, 0xd7b7616f, 0xb27acbaf, 0x2f752ad9, 0x53140f02, + 0xce1bee74, 0x988f5a82, 0x0580bbf4, 0x79e19e2f, 0xe4ee7f59, + 0x8123d599, 0x1c2c34ef, 0x604d1134, 0xfd42f042, 0x41b9f7f1, + 0xdcb61687, 0xa0d7335c, 0x3dd8d22a, 0x581578ea, 0xc51a999c, + 0xb97bbc47, 0x24745d31, 0x72e0e9c7, 0xefef08b1, 0x938e2d6a, + 0x0e81cc1c, 0x6b4c66dc, 0xf64387aa, 0x8a22a271, 0x172d4307, + 0x270bcb9d, 0xba042aeb, 0xc6650f30, 0x5b6aee46, 0x3ea74486, + 0xa3a8a5f0, 0xdfc9802b, 0x42c6615d, 0x1452d5ab, 0x895d34dd, + 0xf53c1106, 0x6833f070, 0x0dfe5ab0, 0x90f1bbc6, 0xec909e1d, + 0x719f7f6b, 0x8cdd8f29, 0x11d26e5f, 0x6db34b84, 0xf0bcaaf2, + 0x95710032, 0x087ee144, 0x741fc49f, 0xe91025e9, 0xbf84911f, + 0x228b7069, 0x5eea55b2, 0xc3e5b4c4, 0xa6281e04, 0x3b27ff72, + 0x4746daa9, 0xda493bdf, 0xea6fb345, 0x77605233, 0x0b0177e8, + 0x960e969e, 0xf3c33c5e, 0x6eccdd28, 0x12adf8f3, 0x8fa21985, + 0xd936ad73, 0x44394c05, 0x385869de, 0xa55788a8, 0xc09a2268, + 0x5d95c31e, 0x21f4e6c5, 0xbcfb07b3, 0x8373efe2, 0x1e7c0e94, + 0x621d2b4f, 0xff12ca39, 0x9adf60f9, 0x07d0818f, 0x7bb1a454, + 0xe6be4522, 0xb02af1d4, 0x2d2510a2, 0x51443579, 0xcc4bd40f, + 0xa9867ecf, 0x34899fb9, 0x48e8ba62, 0xd5e75b14, 0xe5c1d38e, + 0x78ce32f8, 0x04af1723, 0x99a0f655, 0xfc6d5c95, 0x6162bde3, + 0x1d039838, 0x800c794e, 0xd698cdb8, 0x4b972cce, 0x37f60915, + 0xaaf9e863, 0xcf3442a3, 0x523ba3d5, 0x2e5a860e, 0xb3556778, + 0x4e17973a, 0xd318764c, 0xaf795397, 0x3276b2e1, 0x57bb1821, + 0xcab4f957, 0xb6d5dc8c, 0x2bda3dfa, 0x7d4e890c, 0xe041687a, + 0x9c204da1, 0x012facd7, 0x64e20617, 0xf9ede761, 0x858cc2ba, + 0x188323cc, 0x28a5ab56, 0xb5aa4a20, 0xc9cb6ffb, 0x54c48e8d, + 0x3109244d, 0xac06c53b, 0xd067e0e0, 0x4d680196, 0x1bfcb560, + 0x86f35416, 0xfa9271cd, 0x679d90bb, 0x02503a7b, 0x9f5fdb0d, + 0xe33efed6, 0x7e311fa0, 0xc2ca1813, 0x5fc5f965, 0x23a4dcbe, + 0xbeab3dc8, 0xdb669708, 0x4669767e, 0x3a0853a5, 0xa707b2d3, + 0xf1930625, 0x6c9ce753, 0x10fdc288, 0x8df223fe, 0xe83f893e, + 0x75306848, 0x09514d93, 0x945eace5, 0xa478247f, 0x3977c509, + 0x4516e0d2, 0xd81901a4, 0xbdd4ab64, 0x20db4a12, 0x5cba6fc9, + 0xc1b58ebf, 0x97213a49, 0x0a2edb3f, 0x764ffee4, 0xeb401f92, + 0x8e8db552, 0x13825424, 0x6fe371ff, 0xf2ec9089, 0x0fae60cb, + 0x92a181bd, 0xeec0a466, 0x73cf4510, 0x1602efd0, 0x8b0d0ea6, + 0xf76c2b7d, 0x6a63ca0b, 0x3cf77efd, 0xa1f89f8b, 0xdd99ba50, + 0x40965b26, 0x255bf1e6, 0xb8541090, 0xc435354b, 0x593ad43d, + 0x691c5ca7, 0xf413bdd1, 0x8872980a, 0x157d797c, 0x70b0d3bc, + 0xedbf32ca, 0x91de1711, 0x0cd1f667, 0x5a454291, 0xc74aa3e7, + 0xbb2b863c, 0x2624674a, 0x43e9cd8a, 0xdee62cfc, 0xa2870927, + 0x3f88e851}, + {0x00000000, 0xdd96d985, 0x605cb54b, 0xbdca6cce, 0xc0b96a96, + 0x1d2fb313, 0xa0e5dfdd, 0x7d730658, 0x5a03d36d, 0x87950ae8, + 0x3a5f6626, 0xe7c9bfa3, 0x9abab9fb, 0x472c607e, 0xfae60cb0, + 0x2770d535, 0xb407a6da, 0x69917f5f, 0xd45b1391, 0x09cdca14, + 0x74becc4c, 0xa92815c9, 0x14e27907, 0xc974a082, 0xee0475b7, + 0x3392ac32, 0x8e58c0fc, 0x53ce1979, 0x2ebd1f21, 0xf32bc6a4, + 0x4ee1aa6a, 0x937773ef, 0xb37e4bf5, 0x6ee89270, 0xd322febe, + 0x0eb4273b, 0x73c72163, 0xae51f8e6, 0x139b9428, 0xce0d4dad, + 0xe97d9898, 0x34eb411d, 0x89212dd3, 0x54b7f456, 0x29c4f20e, + 0xf4522b8b, 0x49984745, 0x940e9ec0, 0x0779ed2f, 0xdaef34aa, + 0x67255864, 0xbab381e1, 0xc7c087b9, 0x1a565e3c, 0xa79c32f2, + 0x7a0aeb77, 0x5d7a3e42, 0x80ece7c7, 0x3d268b09, 0xe0b0528c, + 0x9dc354d4, 0x40558d51, 0xfd9fe19f, 0x2009381a, 0xbd8d91ab, + 0x601b482e, 0xddd124e0, 0x0047fd65, 0x7d34fb3d, 0xa0a222b8, + 0x1d684e76, 0xc0fe97f3, 0xe78e42c6, 0x3a189b43, 0x87d2f78d, + 0x5a442e08, 0x27372850, 0xfaa1f1d5, 0x476b9d1b, 0x9afd449e, + 0x098a3771, 0xd41ceef4, 0x69d6823a, 0xb4405bbf, 0xc9335de7, + 0x14a58462, 0xa96fe8ac, 0x74f93129, 0x5389e41c, 0x8e1f3d99, + 0x33d55157, 0xee4388d2, 0x93308e8a, 0x4ea6570f, 0xf36c3bc1, + 0x2efae244, 0x0ef3da5e, 0xd36503db, 0x6eaf6f15, 0xb339b690, + 0xce4ab0c8, 0x13dc694d, 0xae160583, 0x7380dc06, 0x54f00933, + 0x8966d0b6, 0x34acbc78, 0xe93a65fd, 0x944963a5, 0x49dfba20, + 0xf415d6ee, 0x29830f6b, 0xbaf47c84, 0x6762a501, 0xdaa8c9cf, + 0x073e104a, 0x7a4d1612, 0xa7dbcf97, 0x1a11a359, 0xc7877adc, + 0xe0f7afe9, 0x3d61766c, 0x80ab1aa2, 0x5d3dc327, 0x204ec57f, + 0xfdd81cfa, 0x40127034, 0x9d84a9b1, 0xa06a2517, 0x7dfcfc92, + 0xc036905c, 0x1da049d9, 0x60d34f81, 0xbd459604, 0x008ffaca, + 0xdd19234f, 0xfa69f67a, 0x27ff2fff, 0x9a354331, 0x47a39ab4, + 0x3ad09cec, 0xe7464569, 0x5a8c29a7, 0x871af022, 0x146d83cd, + 0xc9fb5a48, 0x74313686, 0xa9a7ef03, 0xd4d4e95b, 0x094230de, + 0xb4885c10, 0x691e8595, 0x4e6e50a0, 0x93f88925, 0x2e32e5eb, + 0xf3a43c6e, 0x8ed73a36, 0x5341e3b3, 0xee8b8f7d, 0x331d56f8, + 0x13146ee2, 0xce82b767, 0x7348dba9, 0xaede022c, 0xd3ad0474, + 0x0e3bddf1, 0xb3f1b13f, 0x6e6768ba, 0x4917bd8f, 0x9481640a, + 0x294b08c4, 0xf4ddd141, 0x89aed719, 0x54380e9c, 0xe9f26252, + 0x3464bbd7, 0xa713c838, 0x7a8511bd, 0xc74f7d73, 0x1ad9a4f6, + 0x67aaa2ae, 0xba3c7b2b, 0x07f617e5, 0xda60ce60, 0xfd101b55, + 0x2086c2d0, 0x9d4cae1e, 0x40da779b, 0x3da971c3, 0xe03fa846, + 0x5df5c488, 0x80631d0d, 0x1de7b4bc, 0xc0716d39, 0x7dbb01f7, + 0xa02dd872, 0xdd5ede2a, 0x00c807af, 0xbd026b61, 0x6094b2e4, + 0x47e467d1, 0x9a72be54, 0x27b8d29a, 0xfa2e0b1f, 0x875d0d47, + 0x5acbd4c2, 0xe701b80c, 0x3a976189, 0xa9e01266, 0x7476cbe3, + 0xc9bca72d, 0x142a7ea8, 0x695978f0, 0xb4cfa175, 0x0905cdbb, + 0xd493143e, 0xf3e3c10b, 0x2e75188e, 0x93bf7440, 0x4e29adc5, + 0x335aab9d, 0xeecc7218, 0x53061ed6, 0x8e90c753, 0xae99ff49, + 0x730f26cc, 0xcec54a02, 0x13539387, 0x6e2095df, 0xb3b64c5a, + 0x0e7c2094, 0xd3eaf911, 0xf49a2c24, 0x290cf5a1, 0x94c6996f, + 0x495040ea, 0x342346b2, 0xe9b59f37, 0x547ff3f9, 0x89e92a7c, + 0x1a9e5993, 0xc7088016, 0x7ac2ecd8, 0xa754355d, 0xda273305, + 0x07b1ea80, 0xba7b864e, 0x67ed5fcb, 0x409d8afe, 0x9d0b537b, + 0x20c13fb5, 0xfd57e630, 0x8024e068, 0x5db239ed, 0xe0785523, + 0x3dee8ca6}, + {0x00000000, 0x9ba54c6f, 0xec3b9e9f, 0x779ed2f0, 0x03063b7f, + 0x98a37710, 0xef3da5e0, 0x7498e98f, 0x060c76fe, 0x9da93a91, + 0xea37e861, 0x7192a40e, 0x050a4d81, 0x9eaf01ee, 0xe931d31e, + 0x72949f71, 0x0c18edfc, 0x97bda193, 0xe0237363, 0x7b863f0c, + 0x0f1ed683, 0x94bb9aec, 0xe325481c, 0x78800473, 0x0a149b02, + 0x91b1d76d, 0xe62f059d, 0x7d8a49f2, 0x0912a07d, 0x92b7ec12, + 0xe5293ee2, 0x7e8c728d, 0x1831dbf8, 0x83949797, 0xf40a4567, + 0x6faf0908, 0x1b37e087, 0x8092ace8, 0xf70c7e18, 0x6ca93277, + 0x1e3dad06, 0x8598e169, 0xf2063399, 0x69a37ff6, 0x1d3b9679, + 0x869eda16, 0xf10008e6, 0x6aa54489, 0x14293604, 0x8f8c7a6b, + 0xf812a89b, 0x63b7e4f4, 0x172f0d7b, 0x8c8a4114, 0xfb1493e4, + 0x60b1df8b, 0x122540fa, 0x89800c95, 0xfe1ede65, 0x65bb920a, + 0x11237b85, 0x8a8637ea, 0xfd18e51a, 0x66bda975, 0x3063b7f0, + 0xabc6fb9f, 0xdc58296f, 0x47fd6500, 0x33658c8f, 0xa8c0c0e0, + 0xdf5e1210, 0x44fb5e7f, 0x366fc10e, 0xadca8d61, 0xda545f91, + 0x41f113fe, 0x3569fa71, 0xaeccb61e, 0xd95264ee, 0x42f72881, + 0x3c7b5a0c, 0xa7de1663, 0xd040c493, 0x4be588fc, 0x3f7d6173, + 0xa4d82d1c, 0xd346ffec, 0x48e3b383, 0x3a772cf2, 0xa1d2609d, + 0xd64cb26d, 0x4de9fe02, 0x3971178d, 0xa2d45be2, 0xd54a8912, + 0x4eefc57d, 0x28526c08, 0xb3f72067, 0xc469f297, 0x5fccbef8, + 0x2b545777, 0xb0f11b18, 0xc76fc9e8, 0x5cca8587, 0x2e5e1af6, + 0xb5fb5699, 0xc2658469, 0x59c0c806, 0x2d582189, 0xb6fd6de6, + 0xc163bf16, 0x5ac6f379, 0x244a81f4, 0xbfefcd9b, 0xc8711f6b, + 0x53d45304, 0x274cba8b, 0xbce9f6e4, 0xcb772414, 0x50d2687b, + 0x2246f70a, 0xb9e3bb65, 0xce7d6995, 0x55d825fa, 0x2140cc75, + 0xbae5801a, 0xcd7b52ea, 0x56de1e85, 0x60c76fe0, 0xfb62238f, + 0x8cfcf17f, 0x1759bd10, 0x63c1549f, 0xf86418f0, 0x8ffaca00, + 0x145f866f, 0x66cb191e, 0xfd6e5571, 0x8af08781, 0x1155cbee, + 0x65cd2261, 0xfe686e0e, 0x89f6bcfe, 0x1253f091, 0x6cdf821c, + 0xf77ace73, 0x80e41c83, 0x1b4150ec, 0x6fd9b963, 0xf47cf50c, + 0x83e227fc, 0x18476b93, 0x6ad3f4e2, 0xf176b88d, 0x86e86a7d, + 0x1d4d2612, 0x69d5cf9d, 0xf27083f2, 0x85ee5102, 0x1e4b1d6d, + 0x78f6b418, 0xe353f877, 0x94cd2a87, 0x0f6866e8, 0x7bf08f67, + 0xe055c308, 0x97cb11f8, 0x0c6e5d97, 0x7efac2e6, 0xe55f8e89, + 0x92c15c79, 0x09641016, 0x7dfcf999, 0xe659b5f6, 0x91c76706, + 0x0a622b69, 0x74ee59e4, 0xef4b158b, 0x98d5c77b, 0x03708b14, + 0x77e8629b, 0xec4d2ef4, 0x9bd3fc04, 0x0076b06b, 0x72e22f1a, + 0xe9476375, 0x9ed9b185, 0x057cfdea, 0x71e41465, 0xea41580a, + 0x9ddf8afa, 0x067ac695, 0x50a4d810, 0xcb01947f, 0xbc9f468f, + 0x273a0ae0, 0x53a2e36f, 0xc807af00, 0xbf997df0, 0x243c319f, + 0x56a8aeee, 0xcd0de281, 0xba933071, 0x21367c1e, 0x55ae9591, + 0xce0bd9fe, 0xb9950b0e, 0x22304761, 0x5cbc35ec, 0xc7197983, + 0xb087ab73, 0x2b22e71c, 0x5fba0e93, 0xc41f42fc, 0xb381900c, + 0x2824dc63, 0x5ab04312, 0xc1150f7d, 0xb68bdd8d, 0x2d2e91e2, + 0x59b6786d, 0xc2133402, 0xb58de6f2, 0x2e28aa9d, 0x489503e8, + 0xd3304f87, 0xa4ae9d77, 0x3f0bd118, 0x4b933897, 0xd03674f8, + 0xa7a8a608, 0x3c0dea67, 0x4e997516, 0xd53c3979, 0xa2a2eb89, + 0x3907a7e6, 0x4d9f4e69, 0xd63a0206, 0xa1a4d0f6, 0x3a019c99, + 0x448dee14, 0xdf28a27b, 0xa8b6708b, 0x33133ce4, 0x478bd56b, + 0xdc2e9904, 0xabb04bf4, 0x3015079b, 0x428198ea, 0xd924d485, + 0xaeba0675, 0x351f4a1a, 0x4187a395, 0xda22effa, 0xadbc3d0a, + 0x36197165}, + {0x00000000, 0xc18edfc0, 0x586cb9c1, 0x99e26601, 0xb0d97382, + 0x7157ac42, 0xe8b5ca43, 0x293b1583, 0xbac3e145, 0x7b4d3e85, + 0xe2af5884, 0x23218744, 0x0a1a92c7, 0xcb944d07, 0x52762b06, + 0x93f8f4c6, 0xaef6c4cb, 0x6f781b0b, 0xf69a7d0a, 0x3714a2ca, + 0x1e2fb749, 0xdfa16889, 0x46430e88, 0x87cdd148, 0x1435258e, + 0xd5bbfa4e, 0x4c599c4f, 0x8dd7438f, 0xa4ec560c, 0x656289cc, + 0xfc80efcd, 0x3d0e300d, 0x869c8fd7, 0x47125017, 0xdef03616, + 0x1f7ee9d6, 0x3645fc55, 0xf7cb2395, 0x6e294594, 0xafa79a54, + 0x3c5f6e92, 0xfdd1b152, 0x6433d753, 0xa5bd0893, 0x8c861d10, + 0x4d08c2d0, 0xd4eaa4d1, 0x15647b11, 0x286a4b1c, 0xe9e494dc, + 0x7006f2dd, 0xb1882d1d, 0x98b3389e, 0x593de75e, 0xc0df815f, + 0x01515e9f, 0x92a9aa59, 0x53277599, 0xcac51398, 0x0b4bcc58, + 0x2270d9db, 0xe3fe061b, 0x7a1c601a, 0xbb92bfda, 0xd64819ef, + 0x17c6c62f, 0x8e24a02e, 0x4faa7fee, 0x66916a6d, 0xa71fb5ad, + 0x3efdd3ac, 0xff730c6c, 0x6c8bf8aa, 0xad05276a, 0x34e7416b, + 0xf5699eab, 0xdc528b28, 0x1ddc54e8, 0x843e32e9, 0x45b0ed29, + 0x78bedd24, 0xb93002e4, 0x20d264e5, 0xe15cbb25, 0xc867aea6, + 0x09e97166, 0x900b1767, 0x5185c8a7, 0xc27d3c61, 0x03f3e3a1, + 0x9a1185a0, 0x5b9f5a60, 0x72a44fe3, 0xb32a9023, 0x2ac8f622, + 0xeb4629e2, 0x50d49638, 0x915a49f8, 0x08b82ff9, 0xc936f039, + 0xe00de5ba, 0x21833a7a, 0xb8615c7b, 0x79ef83bb, 0xea17777d, + 0x2b99a8bd, 0xb27bcebc, 0x73f5117c, 0x5ace04ff, 0x9b40db3f, + 0x02a2bd3e, 0xc32c62fe, 0xfe2252f3, 0x3fac8d33, 0xa64eeb32, + 0x67c034f2, 0x4efb2171, 0x8f75feb1, 0x169798b0, 0xd7194770, + 0x44e1b3b6, 0x856f6c76, 0x1c8d0a77, 0xdd03d5b7, 0xf438c034, + 0x35b61ff4, 0xac5479f5, 0x6ddaa635, 0x77e1359f, 0xb66fea5f, + 0x2f8d8c5e, 0xee03539e, 0xc738461d, 0x06b699dd, 0x9f54ffdc, + 0x5eda201c, 0xcd22d4da, 0x0cac0b1a, 0x954e6d1b, 0x54c0b2db, + 0x7dfba758, 0xbc757898, 0x25971e99, 0xe419c159, 0xd917f154, + 0x18992e94, 0x817b4895, 0x40f59755, 0x69ce82d6, 0xa8405d16, + 0x31a23b17, 0xf02ce4d7, 0x63d41011, 0xa25acfd1, 0x3bb8a9d0, + 0xfa367610, 0xd30d6393, 0x1283bc53, 0x8b61da52, 0x4aef0592, + 0xf17dba48, 0x30f36588, 0xa9110389, 0x689fdc49, 0x41a4c9ca, + 0x802a160a, 0x19c8700b, 0xd846afcb, 0x4bbe5b0d, 0x8a3084cd, + 0x13d2e2cc, 0xd25c3d0c, 0xfb67288f, 0x3ae9f74f, 0xa30b914e, + 0x62854e8e, 0x5f8b7e83, 0x9e05a143, 0x07e7c742, 0xc6691882, + 0xef520d01, 0x2edcd2c1, 0xb73eb4c0, 0x76b06b00, 0xe5489fc6, + 0x24c64006, 0xbd242607, 0x7caaf9c7, 0x5591ec44, 0x941f3384, + 0x0dfd5585, 0xcc738a45, 0xa1a92c70, 0x6027f3b0, 0xf9c595b1, + 0x384b4a71, 0x11705ff2, 0xd0fe8032, 0x491ce633, 0x889239f3, + 0x1b6acd35, 0xdae412f5, 0x430674f4, 0x8288ab34, 0xabb3beb7, + 0x6a3d6177, 0xf3df0776, 0x3251d8b6, 0x0f5fe8bb, 0xced1377b, + 0x5733517a, 0x96bd8eba, 0xbf869b39, 0x7e0844f9, 0xe7ea22f8, + 0x2664fd38, 0xb59c09fe, 0x7412d63e, 0xedf0b03f, 0x2c7e6fff, + 0x05457a7c, 0xc4cba5bc, 0x5d29c3bd, 0x9ca71c7d, 0x2735a3a7, + 0xe6bb7c67, 0x7f591a66, 0xbed7c5a6, 0x97ecd025, 0x56620fe5, + 0xcf8069e4, 0x0e0eb624, 0x9df642e2, 0x5c789d22, 0xc59afb23, + 0x041424e3, 0x2d2f3160, 0xeca1eea0, 0x754388a1, 0xb4cd5761, + 0x89c3676c, 0x484db8ac, 0xd1afdead, 0x1021016d, 0x391a14ee, + 0xf894cb2e, 0x6176ad2f, 0xa0f872ef, 0x33008629, 0xf28e59e9, + 0x6b6c3fe8, 0xaae2e028, 0x83d9f5ab, 0x42572a6b, 0xdbb54c6a, + 0x1a3b93aa}, + {0x00000000, 0xefc26b3e, 0x04f5d03d, 0xeb37bb03, 0x09eba07a, + 0xe629cb44, 0x0d1e7047, 0xe2dc1b79, 0x13d740f4, 0xfc152bca, + 0x172290c9, 0xf8e0fbf7, 0x1a3ce08e, 0xf5fe8bb0, 0x1ec930b3, + 0xf10b5b8d, 0x27ae81e8, 0xc86cead6, 0x235b51d5, 0xcc993aeb, + 0x2e452192, 0xc1874aac, 0x2ab0f1af, 0xc5729a91, 0x3479c11c, + 0xdbbbaa22, 0x308c1121, 0xdf4e7a1f, 0x3d926166, 0xd2500a58, + 0x3967b15b, 0xd6a5da65, 0x4f5d03d0, 0xa09f68ee, 0x4ba8d3ed, + 0xa46ab8d3, 0x46b6a3aa, 0xa974c894, 0x42437397, 0xad8118a9, + 0x5c8a4324, 0xb348281a, 0x587f9319, 0xb7bdf827, 0x5561e35e, + 0xbaa38860, 0x51943363, 0xbe56585d, 0x68f38238, 0x8731e906, + 0x6c065205, 0x83c4393b, 0x61182242, 0x8eda497c, 0x65edf27f, + 0x8a2f9941, 0x7b24c2cc, 0x94e6a9f2, 0x7fd112f1, 0x901379cf, + 0x72cf62b6, 0x9d0d0988, 0x763ab28b, 0x99f8d9b5, 0x9eba07a0, + 0x71786c9e, 0x9a4fd79d, 0x758dbca3, 0x9751a7da, 0x7893cce4, + 0x93a477e7, 0x7c661cd9, 0x8d6d4754, 0x62af2c6a, 0x89989769, + 0x665afc57, 0x8486e72e, 0x6b448c10, 0x80733713, 0x6fb15c2d, + 0xb9148648, 0x56d6ed76, 0xbde15675, 0x52233d4b, 0xb0ff2632, + 0x5f3d4d0c, 0xb40af60f, 0x5bc89d31, 0xaac3c6bc, 0x4501ad82, + 0xae361681, 0x41f47dbf, 0xa32866c6, 0x4cea0df8, 0xa7ddb6fb, + 0x481fddc5, 0xd1e70470, 0x3e256f4e, 0xd512d44d, 0x3ad0bf73, + 0xd80ca40a, 0x37cecf34, 0xdcf97437, 0x333b1f09, 0xc2304484, + 0x2df22fba, 0xc6c594b9, 0x2907ff87, 0xcbdbe4fe, 0x24198fc0, + 0xcf2e34c3, 0x20ec5ffd, 0xf6498598, 0x198beea6, 0xf2bc55a5, + 0x1d7e3e9b, 0xffa225e2, 0x10604edc, 0xfb57f5df, 0x14959ee1, + 0xe59ec56c, 0x0a5cae52, 0xe16b1551, 0x0ea97e6f, 0xec756516, + 0x03b70e28, 0xe880b52b, 0x0742de15, 0xe6050901, 0x09c7623f, + 0xe2f0d93c, 0x0d32b202, 0xefeea97b, 0x002cc245, 0xeb1b7946, + 0x04d91278, 0xf5d249f5, 0x1a1022cb, 0xf12799c8, 0x1ee5f2f6, + 0xfc39e98f, 0x13fb82b1, 0xf8cc39b2, 0x170e528c, 0xc1ab88e9, + 0x2e69e3d7, 0xc55e58d4, 0x2a9c33ea, 0xc8402893, 0x278243ad, + 0xccb5f8ae, 0x23779390, 0xd27cc81d, 0x3dbea323, 0xd6891820, + 0x394b731e, 0xdb976867, 0x34550359, 0xdf62b85a, 0x30a0d364, + 0xa9580ad1, 0x469a61ef, 0xadaddaec, 0x426fb1d2, 0xa0b3aaab, + 0x4f71c195, 0xa4467a96, 0x4b8411a8, 0xba8f4a25, 0x554d211b, + 0xbe7a9a18, 0x51b8f126, 0xb364ea5f, 0x5ca68161, 0xb7913a62, + 0x5853515c, 0x8ef68b39, 0x6134e007, 0x8a035b04, 0x65c1303a, + 0x871d2b43, 0x68df407d, 0x83e8fb7e, 0x6c2a9040, 0x9d21cbcd, + 0x72e3a0f3, 0x99d41bf0, 0x761670ce, 0x94ca6bb7, 0x7b080089, + 0x903fbb8a, 0x7ffdd0b4, 0x78bf0ea1, 0x977d659f, 0x7c4ade9c, + 0x9388b5a2, 0x7154aedb, 0x9e96c5e5, 0x75a17ee6, 0x9a6315d8, + 0x6b684e55, 0x84aa256b, 0x6f9d9e68, 0x805ff556, 0x6283ee2f, + 0x8d418511, 0x66763e12, 0x89b4552c, 0x5f118f49, 0xb0d3e477, + 0x5be45f74, 0xb426344a, 0x56fa2f33, 0xb938440d, 0x520fff0e, + 0xbdcd9430, 0x4cc6cfbd, 0xa304a483, 0x48331f80, 0xa7f174be, + 0x452d6fc7, 0xaaef04f9, 0x41d8bffa, 0xae1ad4c4, 0x37e20d71, + 0xd820664f, 0x3317dd4c, 0xdcd5b672, 0x3e09ad0b, 0xd1cbc635, + 0x3afc7d36, 0xd53e1608, 0x24354d85, 0xcbf726bb, 0x20c09db8, + 0xcf02f686, 0x2ddeedff, 0xc21c86c1, 0x292b3dc2, 0xc6e956fc, + 0x104c8c99, 0xff8ee7a7, 0x14b95ca4, 0xfb7b379a, 0x19a72ce3, + 0xf66547dd, 0x1d52fcde, 0xf29097e0, 0x039bcc6d, 0xec59a753, + 0x076e1c50, 0xe8ac776e, 0x0a706c17, 0xe5b20729, 0x0e85bc2a, + 0xe147d714}, + {0x00000000, 0x177b1443, 0x2ef62886, 0x398d3cc5, 0x5dec510c, + 0x4a97454f, 0x731a798a, 0x64616dc9, 0xbbd8a218, 0xaca3b65b, + 0x952e8a9e, 0x82559edd, 0xe634f314, 0xf14fe757, 0xc8c2db92, + 0xdfb9cfd1, 0xacc04271, 0xbbbb5632, 0x82366af7, 0x954d7eb4, + 0xf12c137d, 0xe657073e, 0xdfda3bfb, 0xc8a12fb8, 0x1718e069, + 0x0063f42a, 0x39eec8ef, 0x2e95dcac, 0x4af4b165, 0x5d8fa526, + 0x640299e3, 0x73798da0, 0x82f182a3, 0x958a96e0, 0xac07aa25, + 0xbb7cbe66, 0xdf1dd3af, 0xc866c7ec, 0xf1ebfb29, 0xe690ef6a, + 0x392920bb, 0x2e5234f8, 0x17df083d, 0x00a41c7e, 0x64c571b7, + 0x73be65f4, 0x4a335931, 0x5d484d72, 0x2e31c0d2, 0x394ad491, + 0x00c7e854, 0x17bcfc17, 0x73dd91de, 0x64a6859d, 0x5d2bb958, + 0x4a50ad1b, 0x95e962ca, 0x82927689, 0xbb1f4a4c, 0xac645e0f, + 0xc80533c6, 0xdf7e2785, 0xe6f31b40, 0xf1880f03, 0xde920307, + 0xc9e91744, 0xf0642b81, 0xe71f3fc2, 0x837e520b, 0x94054648, + 0xad887a8d, 0xbaf36ece, 0x654aa11f, 0x7231b55c, 0x4bbc8999, + 0x5cc79dda, 0x38a6f013, 0x2fdde450, 0x1650d895, 0x012bccd6, + 0x72524176, 0x65295535, 0x5ca469f0, 0x4bdf7db3, 0x2fbe107a, + 0x38c50439, 0x014838fc, 0x16332cbf, 0xc98ae36e, 0xdef1f72d, + 0xe77ccbe8, 0xf007dfab, 0x9466b262, 0x831da621, 0xba909ae4, + 0xadeb8ea7, 0x5c6381a4, 0x4b1895e7, 0x7295a922, 0x65eebd61, + 0x018fd0a8, 0x16f4c4eb, 0x2f79f82e, 0x3802ec6d, 0xe7bb23bc, + 0xf0c037ff, 0xc94d0b3a, 0xde361f79, 0xba5772b0, 0xad2c66f3, + 0x94a15a36, 0x83da4e75, 0xf0a3c3d5, 0xe7d8d796, 0xde55eb53, + 0xc92eff10, 0xad4f92d9, 0xba34869a, 0x83b9ba5f, 0x94c2ae1c, + 0x4b7b61cd, 0x5c00758e, 0x658d494b, 0x72f65d08, 0x169730c1, + 0x01ec2482, 0x38611847, 0x2f1a0c04, 0x6655004f, 0x712e140c, + 0x48a328c9, 0x5fd83c8a, 0x3bb95143, 0x2cc24500, 0x154f79c5, + 0x02346d86, 0xdd8da257, 0xcaf6b614, 0xf37b8ad1, 0xe4009e92, + 0x8061f35b, 0x971ae718, 0xae97dbdd, 0xb9eccf9e, 0xca95423e, + 0xddee567d, 0xe4636ab8, 0xf3187efb, 0x97791332, 0x80020771, + 0xb98f3bb4, 0xaef42ff7, 0x714de026, 0x6636f465, 0x5fbbc8a0, + 0x48c0dce3, 0x2ca1b12a, 0x3bdaa569, 0x025799ac, 0x152c8def, + 0xe4a482ec, 0xf3df96af, 0xca52aa6a, 0xdd29be29, 0xb948d3e0, + 0xae33c7a3, 0x97befb66, 0x80c5ef25, 0x5f7c20f4, 0x480734b7, + 0x718a0872, 0x66f11c31, 0x029071f8, 0x15eb65bb, 0x2c66597e, + 0x3b1d4d3d, 0x4864c09d, 0x5f1fd4de, 0x6692e81b, 0x71e9fc58, + 0x15889191, 0x02f385d2, 0x3b7eb917, 0x2c05ad54, 0xf3bc6285, + 0xe4c776c6, 0xdd4a4a03, 0xca315e40, 0xae503389, 0xb92b27ca, + 0x80a61b0f, 0x97dd0f4c, 0xb8c70348, 0xafbc170b, 0x96312bce, + 0x814a3f8d, 0xe52b5244, 0xf2504607, 0xcbdd7ac2, 0xdca66e81, + 0x031fa150, 0x1464b513, 0x2de989d6, 0x3a929d95, 0x5ef3f05c, + 0x4988e41f, 0x7005d8da, 0x677ecc99, 0x14074139, 0x037c557a, + 0x3af169bf, 0x2d8a7dfc, 0x49eb1035, 0x5e900476, 0x671d38b3, + 0x70662cf0, 0xafdfe321, 0xb8a4f762, 0x8129cba7, 0x9652dfe4, + 0xf233b22d, 0xe548a66e, 0xdcc59aab, 0xcbbe8ee8, 0x3a3681eb, + 0x2d4d95a8, 0x14c0a96d, 0x03bbbd2e, 0x67dad0e7, 0x70a1c4a4, + 0x492cf861, 0x5e57ec22, 0x81ee23f3, 0x969537b0, 0xaf180b75, + 0xb8631f36, 0xdc0272ff, 0xcb7966bc, 0xf2f45a79, 0xe58f4e3a, + 0x96f6c39a, 0x818dd7d9, 0xb800eb1c, 0xaf7bff5f, 0xcb1a9296, + 0xdc6186d5, 0xe5ecba10, 0xf297ae53, 0x2d2e6182, 0x3a5575c1, + 0x03d84904, 0x14a35d47, 0x70c2308e, 0x67b924cd, 0x5e341808, + 0x494f0c4b}}; + +local const z_word_t FAR crc_braid_big_table[][256] = { + {0x0000000000000000, 0x43147b1700000000, 0x8628f62e00000000, + 0xc53c8d3900000000, 0x0c51ec5d00000000, 0x4f45974a00000000, + 0x8a791a7300000000, 0xc96d616400000000, 0x18a2d8bb00000000, + 0x5bb6a3ac00000000, 0x9e8a2e9500000000, 0xdd9e558200000000, + 0x14f334e600000000, 0x57e74ff100000000, 0x92dbc2c800000000, + 0xd1cfb9df00000000, 0x7142c0ac00000000, 0x3256bbbb00000000, + 0xf76a368200000000, 0xb47e4d9500000000, 0x7d132cf100000000, + 0x3e0757e600000000, 0xfb3bdadf00000000, 0xb82fa1c800000000, + 0x69e0181700000000, 0x2af4630000000000, 0xefc8ee3900000000, + 0xacdc952e00000000, 0x65b1f44a00000000, 0x26a58f5d00000000, + 0xe399026400000000, 0xa08d797300000000, 0xa382f18200000000, + 0xe0968a9500000000, 0x25aa07ac00000000, 0x66be7cbb00000000, + 0xafd31ddf00000000, 0xecc766c800000000, 0x29fbebf100000000, + 0x6aef90e600000000, 0xbb20293900000000, 0xf834522e00000000, + 0x3d08df1700000000, 0x7e1ca40000000000, 0xb771c56400000000, + 0xf465be7300000000, 0x3159334a00000000, 0x724d485d00000000, + 0xd2c0312e00000000, 0x91d44a3900000000, 0x54e8c70000000000, + 0x17fcbc1700000000, 0xde91dd7300000000, 0x9d85a66400000000, + 0x58b92b5d00000000, 0x1bad504a00000000, 0xca62e99500000000, + 0x8976928200000000, 0x4c4a1fbb00000000, 0x0f5e64ac00000000, + 0xc63305c800000000, 0x85277edf00000000, 0x401bf3e600000000, + 0x030f88f100000000, 0x070392de00000000, 0x4417e9c900000000, + 0x812b64f000000000, 0xc23f1fe700000000, 0x0b527e8300000000, + 0x4846059400000000, 0x8d7a88ad00000000, 0xce6ef3ba00000000, + 0x1fa14a6500000000, 0x5cb5317200000000, 0x9989bc4b00000000, + 0xda9dc75c00000000, 0x13f0a63800000000, 0x50e4dd2f00000000, + 0x95d8501600000000, 0xd6cc2b0100000000, 0x7641527200000000, + 0x3555296500000000, 0xf069a45c00000000, 0xb37ddf4b00000000, + 0x7a10be2f00000000, 0x3904c53800000000, 0xfc38480100000000, + 0xbf2c331600000000, 0x6ee38ac900000000, 0x2df7f1de00000000, + 0xe8cb7ce700000000, 0xabdf07f000000000, 0x62b2669400000000, + 0x21a61d8300000000, 0xe49a90ba00000000, 0xa78eebad00000000, + 0xa481635c00000000, 0xe795184b00000000, 0x22a9957200000000, + 0x61bdee6500000000, 0xa8d08f0100000000, 0xebc4f41600000000, + 0x2ef8792f00000000, 0x6dec023800000000, 0xbc23bbe700000000, + 0xff37c0f000000000, 0x3a0b4dc900000000, 0x791f36de00000000, + 0xb07257ba00000000, 0xf3662cad00000000, 0x365aa19400000000, + 0x754eda8300000000, 0xd5c3a3f000000000, 0x96d7d8e700000000, + 0x53eb55de00000000, 0x10ff2ec900000000, 0xd9924fad00000000, + 0x9a8634ba00000000, 0x5fbab98300000000, 0x1caec29400000000, + 0xcd617b4b00000000, 0x8e75005c00000000, 0x4b498d6500000000, + 0x085df67200000000, 0xc130971600000000, 0x8224ec0100000000, + 0x4718613800000000, 0x040c1a2f00000000, 0x4f00556600000000, + 0x0c142e7100000000, 0xc928a34800000000, 0x8a3cd85f00000000, + 0x4351b93b00000000, 0x0045c22c00000000, 0xc5794f1500000000, + 0x866d340200000000, 0x57a28ddd00000000, 0x14b6f6ca00000000, + 0xd18a7bf300000000, 0x929e00e400000000, 0x5bf3618000000000, + 0x18e71a9700000000, 0xdddb97ae00000000, 0x9ecfecb900000000, + 0x3e4295ca00000000, 0x7d56eedd00000000, 0xb86a63e400000000, + 0xfb7e18f300000000, 0x3213799700000000, 0x7107028000000000, + 0xb43b8fb900000000, 0xf72ff4ae00000000, 0x26e04d7100000000, + 0x65f4366600000000, 0xa0c8bb5f00000000, 0xe3dcc04800000000, + 0x2ab1a12c00000000, 0x69a5da3b00000000, 0xac99570200000000, + 0xef8d2c1500000000, 0xec82a4e400000000, 0xaf96dff300000000, + 0x6aaa52ca00000000, 0x29be29dd00000000, 0xe0d348b900000000, + 0xa3c733ae00000000, 0x66fbbe9700000000, 0x25efc58000000000, + 0xf4207c5f00000000, 0xb734074800000000, 0x72088a7100000000, + 0x311cf16600000000, 0xf871900200000000, 0xbb65eb1500000000, + 0x7e59662c00000000, 0x3d4d1d3b00000000, 0x9dc0644800000000, + 0xded41f5f00000000, 0x1be8926600000000, 0x58fce97100000000, + 0x9191881500000000, 0xd285f30200000000, 0x17b97e3b00000000, + 0x54ad052c00000000, 0x8562bcf300000000, 0xc676c7e400000000, + 0x034a4add00000000, 0x405e31ca00000000, 0x893350ae00000000, + 0xca272bb900000000, 0x0f1ba68000000000, 0x4c0fdd9700000000, + 0x4803c7b800000000, 0x0b17bcaf00000000, 0xce2b319600000000, + 0x8d3f4a8100000000, 0x44522be500000000, 0x074650f200000000, + 0xc27addcb00000000, 0x816ea6dc00000000, 0x50a11f0300000000, + 0x13b5641400000000, 0xd689e92d00000000, 0x959d923a00000000, + 0x5cf0f35e00000000, 0x1fe4884900000000, 0xdad8057000000000, + 0x99cc7e6700000000, 0x3941071400000000, 0x7a557c0300000000, + 0xbf69f13a00000000, 0xfc7d8a2d00000000, 0x3510eb4900000000, + 0x7604905e00000000, 0xb3381d6700000000, 0xf02c667000000000, + 0x21e3dfaf00000000, 0x62f7a4b800000000, 0xa7cb298100000000, + 0xe4df529600000000, 0x2db233f200000000, 0x6ea648e500000000, + 0xab9ac5dc00000000, 0xe88ebecb00000000, 0xeb81363a00000000, + 0xa8954d2d00000000, 0x6da9c01400000000, 0x2ebdbb0300000000, + 0xe7d0da6700000000, 0xa4c4a17000000000, 0x61f82c4900000000, + 0x22ec575e00000000, 0xf323ee8100000000, 0xb037959600000000, + 0x750b18af00000000, 0x361f63b800000000, 0xff7202dc00000000, + 0xbc6679cb00000000, 0x795af4f200000000, 0x3a4e8fe500000000, + 0x9ac3f69600000000, 0xd9d78d8100000000, 0x1ceb00b800000000, + 0x5fff7baf00000000, 0x96921acb00000000, 0xd58661dc00000000, + 0x10baece500000000, 0x53ae97f200000000, 0x82612e2d00000000, + 0xc175553a00000000, 0x0449d80300000000, 0x475da31400000000, + 0x8e30c27000000000, 0xcd24b96700000000, 0x0818345e00000000, + 0x4b0c4f4900000000}, + {0x0000000000000000, 0x3e6bc2ef00000000, 0x3dd0f50400000000, + 0x03bb37eb00000000, 0x7aa0eb0900000000, 0x44cb29e600000000, + 0x47701e0d00000000, 0x791bdce200000000, 0xf440d71300000000, + 0xca2b15fc00000000, 0xc990221700000000, 0xf7fbe0f800000000, + 0x8ee03c1a00000000, 0xb08bfef500000000, 0xb330c91e00000000, + 0x8d5b0bf100000000, 0xe881ae2700000000, 0xd6ea6cc800000000, + 0xd5515b2300000000, 0xeb3a99cc00000000, 0x9221452e00000000, + 0xac4a87c100000000, 0xaff1b02a00000000, 0x919a72c500000000, + 0x1cc1793400000000, 0x22aabbdb00000000, 0x21118c3000000000, + 0x1f7a4edf00000000, 0x6661923d00000000, 0x580a50d200000000, + 0x5bb1673900000000, 0x65daa5d600000000, 0xd0035d4f00000000, + 0xee689fa000000000, 0xedd3a84b00000000, 0xd3b86aa400000000, + 0xaaa3b64600000000, 0x94c874a900000000, 0x9773434200000000, + 0xa91881ad00000000, 0x24438a5c00000000, 0x1a2848b300000000, + 0x19937f5800000000, 0x27f8bdb700000000, 0x5ee3615500000000, + 0x6088a3ba00000000, 0x6333945100000000, 0x5d5856be00000000, + 0x3882f36800000000, 0x06e9318700000000, 0x0552066c00000000, + 0x3b39c48300000000, 0x4222186100000000, 0x7c49da8e00000000, + 0x7ff2ed6500000000, 0x41992f8a00000000, 0xccc2247b00000000, + 0xf2a9e69400000000, 0xf112d17f00000000, 0xcf79139000000000, + 0xb662cf7200000000, 0x88090d9d00000000, 0x8bb23a7600000000, + 0xb5d9f89900000000, 0xa007ba9e00000000, 0x9e6c787100000000, + 0x9dd74f9a00000000, 0xa3bc8d7500000000, 0xdaa7519700000000, + 0xe4cc937800000000, 0xe777a49300000000, 0xd91c667c00000000, + 0x54476d8d00000000, 0x6a2caf6200000000, 0x6997988900000000, + 0x57fc5a6600000000, 0x2ee7868400000000, 0x108c446b00000000, + 0x1337738000000000, 0x2d5cb16f00000000, 0x488614b900000000, + 0x76edd65600000000, 0x7556e1bd00000000, 0x4b3d235200000000, + 0x3226ffb000000000, 0x0c4d3d5f00000000, 0x0ff60ab400000000, + 0x319dc85b00000000, 0xbcc6c3aa00000000, 0x82ad014500000000, + 0x811636ae00000000, 0xbf7df44100000000, 0xc66628a300000000, + 0xf80dea4c00000000, 0xfbb6dda700000000, 0xc5dd1f4800000000, + 0x7004e7d100000000, 0x4e6f253e00000000, 0x4dd412d500000000, + 0x73bfd03a00000000, 0x0aa40cd800000000, 0x34cfce3700000000, + 0x3774f9dc00000000, 0x091f3b3300000000, 0x844430c200000000, + 0xba2ff22d00000000, 0xb994c5c600000000, 0x87ff072900000000, + 0xfee4dbcb00000000, 0xc08f192400000000, 0xc3342ecf00000000, + 0xfd5fec2000000000, 0x988549f600000000, 0xa6ee8b1900000000, + 0xa555bcf200000000, 0x9b3e7e1d00000000, 0xe225a2ff00000000, + 0xdc4e601000000000, 0xdff557fb00000000, 0xe19e951400000000, + 0x6cc59ee500000000, 0x52ae5c0a00000000, 0x51156be100000000, + 0x6f7ea90e00000000, 0x166575ec00000000, 0x280eb70300000000, + 0x2bb580e800000000, 0x15de420700000000, 0x010905e600000000, + 0x3f62c70900000000, 0x3cd9f0e200000000, 0x02b2320d00000000, + 0x7ba9eeef00000000, 0x45c22c0000000000, 0x46791beb00000000, + 0x7812d90400000000, 0xf549d2f500000000, 0xcb22101a00000000, + 0xc89927f100000000, 0xf6f2e51e00000000, 0x8fe939fc00000000, + 0xb182fb1300000000, 0xb239ccf800000000, 0x8c520e1700000000, + 0xe988abc100000000, 0xd7e3692e00000000, 0xd4585ec500000000, + 0xea339c2a00000000, 0x932840c800000000, 0xad43822700000000, + 0xaef8b5cc00000000, 0x9093772300000000, 0x1dc87cd200000000, + 0x23a3be3d00000000, 0x201889d600000000, 0x1e734b3900000000, + 0x676897db00000000, 0x5903553400000000, 0x5ab862df00000000, + 0x64d3a03000000000, 0xd10a58a900000000, 0xef619a4600000000, + 0xecdaadad00000000, 0xd2b16f4200000000, 0xabaab3a000000000, + 0x95c1714f00000000, 0x967a46a400000000, 0xa811844b00000000, + 0x254a8fba00000000, 0x1b214d5500000000, 0x189a7abe00000000, + 0x26f1b85100000000, 0x5fea64b300000000, 0x6181a65c00000000, + 0x623a91b700000000, 0x5c51535800000000, 0x398bf68e00000000, + 0x07e0346100000000, 0x045b038a00000000, 0x3a30c16500000000, + 0x432b1d8700000000, 0x7d40df6800000000, 0x7efbe88300000000, + 0x40902a6c00000000, 0xcdcb219d00000000, 0xf3a0e37200000000, + 0xf01bd49900000000, 0xce70167600000000, 0xb76bca9400000000, + 0x8900087b00000000, 0x8abb3f9000000000, 0xb4d0fd7f00000000, + 0xa10ebf7800000000, 0x9f657d9700000000, 0x9cde4a7c00000000, + 0xa2b5889300000000, 0xdbae547100000000, 0xe5c5969e00000000, + 0xe67ea17500000000, 0xd815639a00000000, 0x554e686b00000000, + 0x6b25aa8400000000, 0x689e9d6f00000000, 0x56f55f8000000000, + 0x2fee836200000000, 0x1185418d00000000, 0x123e766600000000, + 0x2c55b48900000000, 0x498f115f00000000, 0x77e4d3b000000000, + 0x745fe45b00000000, 0x4a3426b400000000, 0x332ffa5600000000, + 0x0d4438b900000000, 0x0eff0f5200000000, 0x3094cdbd00000000, + 0xbdcfc64c00000000, 0x83a404a300000000, 0x801f334800000000, + 0xbe74f1a700000000, 0xc76f2d4500000000, 0xf904efaa00000000, + 0xfabfd84100000000, 0xc4d41aae00000000, 0x710de23700000000, + 0x4f6620d800000000, 0x4cdd173300000000, 0x72b6d5dc00000000, + 0x0bad093e00000000, 0x35c6cbd100000000, 0x367dfc3a00000000, + 0x08163ed500000000, 0x854d352400000000, 0xbb26f7cb00000000, + 0xb89dc02000000000, 0x86f602cf00000000, 0xffedde2d00000000, + 0xc1861cc200000000, 0xc23d2b2900000000, 0xfc56e9c600000000, + 0x998c4c1000000000, 0xa7e78eff00000000, 0xa45cb91400000000, + 0x9a377bfb00000000, 0xe32ca71900000000, 0xdd4765f600000000, + 0xdefc521d00000000, 0xe09790f200000000, 0x6dcc9b0300000000, + 0x53a759ec00000000, 0x501c6e0700000000, 0x6e77ace800000000, + 0x176c700a00000000, 0x2907b2e500000000, 0x2abc850e00000000, + 0x14d747e100000000}, + {0x0000000000000000, 0xc0df8ec100000000, 0xc1b96c5800000000, + 0x0166e29900000000, 0x8273d9b000000000, 0x42ac577100000000, + 0x43cab5e800000000, 0x83153b2900000000, 0x45e1c3ba00000000, + 0x853e4d7b00000000, 0x8458afe200000000, 0x4487212300000000, + 0xc7921a0a00000000, 0x074d94cb00000000, 0x062b765200000000, + 0xc6f4f89300000000, 0xcbc4f6ae00000000, 0x0b1b786f00000000, + 0x0a7d9af600000000, 0xcaa2143700000000, 0x49b72f1e00000000, + 0x8968a1df00000000, 0x880e434600000000, 0x48d1cd8700000000, + 0x8e25351400000000, 0x4efabbd500000000, 0x4f9c594c00000000, + 0x8f43d78d00000000, 0x0c56eca400000000, 0xcc89626500000000, + 0xcdef80fc00000000, 0x0d300e3d00000000, 0xd78f9c8600000000, + 0x1750124700000000, 0x1636f0de00000000, 0xd6e97e1f00000000, + 0x55fc453600000000, 0x9523cbf700000000, 0x9445296e00000000, + 0x549aa7af00000000, 0x926e5f3c00000000, 0x52b1d1fd00000000, + 0x53d7336400000000, 0x9308bda500000000, 0x101d868c00000000, + 0xd0c2084d00000000, 0xd1a4ead400000000, 0x117b641500000000, + 0x1c4b6a2800000000, 0xdc94e4e900000000, 0xddf2067000000000, + 0x1d2d88b100000000, 0x9e38b39800000000, 0x5ee73d5900000000, + 0x5f81dfc000000000, 0x9f5e510100000000, 0x59aaa99200000000, + 0x9975275300000000, 0x9813c5ca00000000, 0x58cc4b0b00000000, + 0xdbd9702200000000, 0x1b06fee300000000, 0x1a601c7a00000000, + 0xdabf92bb00000000, 0xef1948d600000000, 0x2fc6c61700000000, + 0x2ea0248e00000000, 0xee7faa4f00000000, 0x6d6a916600000000, + 0xadb51fa700000000, 0xacd3fd3e00000000, 0x6c0c73ff00000000, + 0xaaf88b6c00000000, 0x6a2705ad00000000, 0x6b41e73400000000, + 0xab9e69f500000000, 0x288b52dc00000000, 0xe854dc1d00000000, + 0xe9323e8400000000, 0x29edb04500000000, 0x24ddbe7800000000, + 0xe40230b900000000, 0xe564d22000000000, 0x25bb5ce100000000, + 0xa6ae67c800000000, 0x6671e90900000000, 0x67170b9000000000, + 0xa7c8855100000000, 0x613c7dc200000000, 0xa1e3f30300000000, + 0xa085119a00000000, 0x605a9f5b00000000, 0xe34fa47200000000, + 0x23902ab300000000, 0x22f6c82a00000000, 0xe22946eb00000000, + 0x3896d45000000000, 0xf8495a9100000000, 0xf92fb80800000000, + 0x39f036c900000000, 0xbae50de000000000, 0x7a3a832100000000, + 0x7b5c61b800000000, 0xbb83ef7900000000, 0x7d7717ea00000000, + 0xbda8992b00000000, 0xbcce7bb200000000, 0x7c11f57300000000, + 0xff04ce5a00000000, 0x3fdb409b00000000, 0x3ebda20200000000, + 0xfe622cc300000000, 0xf35222fe00000000, 0x338dac3f00000000, + 0x32eb4ea600000000, 0xf234c06700000000, 0x7121fb4e00000000, + 0xb1fe758f00000000, 0xb098971600000000, 0x704719d700000000, + 0xb6b3e14400000000, 0x766c6f8500000000, 0x770a8d1c00000000, + 0xb7d503dd00000000, 0x34c038f400000000, 0xf41fb63500000000, + 0xf57954ac00000000, 0x35a6da6d00000000, 0x9f35e17700000000, + 0x5fea6fb600000000, 0x5e8c8d2f00000000, 0x9e5303ee00000000, + 0x1d4638c700000000, 0xdd99b60600000000, 0xdcff549f00000000, + 0x1c20da5e00000000, 0xdad422cd00000000, 0x1a0bac0c00000000, + 0x1b6d4e9500000000, 0xdbb2c05400000000, 0x58a7fb7d00000000, + 0x987875bc00000000, 0x991e972500000000, 0x59c119e400000000, + 0x54f117d900000000, 0x942e991800000000, 0x95487b8100000000, + 0x5597f54000000000, 0xd682ce6900000000, 0x165d40a800000000, + 0x173ba23100000000, 0xd7e42cf000000000, 0x1110d46300000000, + 0xd1cf5aa200000000, 0xd0a9b83b00000000, 0x107636fa00000000, + 0x93630dd300000000, 0x53bc831200000000, 0x52da618b00000000, + 0x9205ef4a00000000, 0x48ba7df100000000, 0x8865f33000000000, + 0x890311a900000000, 0x49dc9f6800000000, 0xcac9a44100000000, + 0x0a162a8000000000, 0x0b70c81900000000, 0xcbaf46d800000000, + 0x0d5bbe4b00000000, 0xcd84308a00000000, 0xcce2d21300000000, + 0x0c3d5cd200000000, 0x8f2867fb00000000, 0x4ff7e93a00000000, + 0x4e910ba300000000, 0x8e4e856200000000, 0x837e8b5f00000000, + 0x43a1059e00000000, 0x42c7e70700000000, 0x821869c600000000, + 0x010d52ef00000000, 0xc1d2dc2e00000000, 0xc0b43eb700000000, + 0x006bb07600000000, 0xc69f48e500000000, 0x0640c62400000000, + 0x072624bd00000000, 0xc7f9aa7c00000000, 0x44ec915500000000, + 0x84331f9400000000, 0x8555fd0d00000000, 0x458a73cc00000000, + 0x702ca9a100000000, 0xb0f3276000000000, 0xb195c5f900000000, + 0x714a4b3800000000, 0xf25f701100000000, 0x3280fed000000000, + 0x33e61c4900000000, 0xf339928800000000, 0x35cd6a1b00000000, + 0xf512e4da00000000, 0xf474064300000000, 0x34ab888200000000, + 0xb7beb3ab00000000, 0x77613d6a00000000, 0x7607dff300000000, + 0xb6d8513200000000, 0xbbe85f0f00000000, 0x7b37d1ce00000000, + 0x7a51335700000000, 0xba8ebd9600000000, 0x399b86bf00000000, + 0xf944087e00000000, 0xf822eae700000000, 0x38fd642600000000, + 0xfe099cb500000000, 0x3ed6127400000000, 0x3fb0f0ed00000000, + 0xff6f7e2c00000000, 0x7c7a450500000000, 0xbca5cbc400000000, + 0xbdc3295d00000000, 0x7d1ca79c00000000, 0xa7a3352700000000, + 0x677cbbe600000000, 0x661a597f00000000, 0xa6c5d7be00000000, + 0x25d0ec9700000000, 0xe50f625600000000, 0xe46980cf00000000, + 0x24b60e0e00000000, 0xe242f69d00000000, 0x229d785c00000000, + 0x23fb9ac500000000, 0xe324140400000000, 0x60312f2d00000000, + 0xa0eea1ec00000000, 0xa188437500000000, 0x6157cdb400000000, + 0x6c67c38900000000, 0xacb84d4800000000, 0xaddeafd100000000, + 0x6d01211000000000, 0xee141a3900000000, 0x2ecb94f800000000, + 0x2fad766100000000, 0xef72f8a000000000, 0x2986003300000000, + 0xe9598ef200000000, 0xe83f6c6b00000000, 0x28e0e2aa00000000, + 0xabf5d98300000000, 0x6b2a574200000000, 0x6a4cb5db00000000, + 0xaa933b1a00000000}, + {0x0000000000000000, 0x6f4ca59b00000000, 0x9f9e3bec00000000, + 0xf0d29e7700000000, 0x7f3b060300000000, 0x1077a39800000000, + 0xe0a53def00000000, 0x8fe9987400000000, 0xfe760c0600000000, + 0x913aa99d00000000, 0x61e837ea00000000, 0x0ea4927100000000, + 0x814d0a0500000000, 0xee01af9e00000000, 0x1ed331e900000000, + 0x719f947200000000, 0xfced180c00000000, 0x93a1bd9700000000, + 0x637323e000000000, 0x0c3f867b00000000, 0x83d61e0f00000000, + 0xec9abb9400000000, 0x1c4825e300000000, 0x7304807800000000, + 0x029b140a00000000, 0x6dd7b19100000000, 0x9d052fe600000000, + 0xf2498a7d00000000, 0x7da0120900000000, 0x12ecb79200000000, + 0xe23e29e500000000, 0x8d728c7e00000000, 0xf8db311800000000, + 0x9797948300000000, 0x67450af400000000, 0x0809af6f00000000, + 0x87e0371b00000000, 0xe8ac928000000000, 0x187e0cf700000000, + 0x7732a96c00000000, 0x06ad3d1e00000000, 0x69e1988500000000, + 0x993306f200000000, 0xf67fa36900000000, 0x79963b1d00000000, + 0x16da9e8600000000, 0xe60800f100000000, 0x8944a56a00000000, + 0x0436291400000000, 0x6b7a8c8f00000000, 0x9ba812f800000000, + 0xf4e4b76300000000, 0x7b0d2f1700000000, 0x14418a8c00000000, + 0xe49314fb00000000, 0x8bdfb16000000000, 0xfa40251200000000, + 0x950c808900000000, 0x65de1efe00000000, 0x0a92bb6500000000, + 0x857b231100000000, 0xea37868a00000000, 0x1ae518fd00000000, + 0x75a9bd6600000000, 0xf0b7633000000000, 0x9ffbc6ab00000000, + 0x6f2958dc00000000, 0x0065fd4700000000, 0x8f8c653300000000, + 0xe0c0c0a800000000, 0x10125edf00000000, 0x7f5efb4400000000, + 0x0ec16f3600000000, 0x618dcaad00000000, 0x915f54da00000000, + 0xfe13f14100000000, 0x71fa693500000000, 0x1eb6ccae00000000, + 0xee6452d900000000, 0x8128f74200000000, 0x0c5a7b3c00000000, + 0x6316dea700000000, 0x93c440d000000000, 0xfc88e54b00000000, + 0x73617d3f00000000, 0x1c2dd8a400000000, 0xecff46d300000000, + 0x83b3e34800000000, 0xf22c773a00000000, 0x9d60d2a100000000, + 0x6db24cd600000000, 0x02fee94d00000000, 0x8d17713900000000, + 0xe25bd4a200000000, 0x12894ad500000000, 0x7dc5ef4e00000000, + 0x086c522800000000, 0x6720f7b300000000, 0x97f269c400000000, + 0xf8becc5f00000000, 0x7757542b00000000, 0x181bf1b000000000, + 0xe8c96fc700000000, 0x8785ca5c00000000, 0xf61a5e2e00000000, + 0x9956fbb500000000, 0x698465c200000000, 0x06c8c05900000000, + 0x8921582d00000000, 0xe66dfdb600000000, 0x16bf63c100000000, + 0x79f3c65a00000000, 0xf4814a2400000000, 0x9bcdefbf00000000, + 0x6b1f71c800000000, 0x0453d45300000000, 0x8bba4c2700000000, + 0xe4f6e9bc00000000, 0x142477cb00000000, 0x7b68d25000000000, + 0x0af7462200000000, 0x65bbe3b900000000, 0x95697dce00000000, + 0xfa25d85500000000, 0x75cc402100000000, 0x1a80e5ba00000000, + 0xea527bcd00000000, 0x851ede5600000000, 0xe06fc76000000000, + 0x8f2362fb00000000, 0x7ff1fc8c00000000, 0x10bd591700000000, + 0x9f54c16300000000, 0xf01864f800000000, 0x00cafa8f00000000, + 0x6f865f1400000000, 0x1e19cb6600000000, 0x71556efd00000000, + 0x8187f08a00000000, 0xeecb551100000000, 0x6122cd6500000000, + 0x0e6e68fe00000000, 0xfebcf68900000000, 0x91f0531200000000, + 0x1c82df6c00000000, 0x73ce7af700000000, 0x831ce48000000000, + 0xec50411b00000000, 0x63b9d96f00000000, 0x0cf57cf400000000, + 0xfc27e28300000000, 0x936b471800000000, 0xe2f4d36a00000000, + 0x8db876f100000000, 0x7d6ae88600000000, 0x12264d1d00000000, + 0x9dcfd56900000000, 0xf28370f200000000, 0x0251ee8500000000, + 0x6d1d4b1e00000000, 0x18b4f67800000000, 0x77f853e300000000, + 0x872acd9400000000, 0xe866680f00000000, 0x678ff07b00000000, + 0x08c355e000000000, 0xf811cb9700000000, 0x975d6e0c00000000, + 0xe6c2fa7e00000000, 0x898e5fe500000000, 0x795cc19200000000, + 0x1610640900000000, 0x99f9fc7d00000000, 0xf6b559e600000000, + 0x0667c79100000000, 0x692b620a00000000, 0xe459ee7400000000, + 0x8b154bef00000000, 0x7bc7d59800000000, 0x148b700300000000, + 0x9b62e87700000000, 0xf42e4dec00000000, 0x04fcd39b00000000, + 0x6bb0760000000000, 0x1a2fe27200000000, 0x756347e900000000, + 0x85b1d99e00000000, 0xeafd7c0500000000, 0x6514e47100000000, + 0x0a5841ea00000000, 0xfa8adf9d00000000, 0x95c67a0600000000, + 0x10d8a45000000000, 0x7f9401cb00000000, 0x8f469fbc00000000, + 0xe00a3a2700000000, 0x6fe3a25300000000, 0x00af07c800000000, + 0xf07d99bf00000000, 0x9f313c2400000000, 0xeeaea85600000000, + 0x81e20dcd00000000, 0x713093ba00000000, 0x1e7c362100000000, + 0x9195ae5500000000, 0xfed90bce00000000, 0x0e0b95b900000000, + 0x6147302200000000, 0xec35bc5c00000000, 0x837919c700000000, + 0x73ab87b000000000, 0x1ce7222b00000000, 0x930eba5f00000000, + 0xfc421fc400000000, 0x0c9081b300000000, 0x63dc242800000000, + 0x1243b05a00000000, 0x7d0f15c100000000, 0x8ddd8bb600000000, + 0xe2912e2d00000000, 0x6d78b65900000000, 0x023413c200000000, + 0xf2e68db500000000, 0x9daa282e00000000, 0xe803954800000000, + 0x874f30d300000000, 0x779daea400000000, 0x18d10b3f00000000, + 0x9738934b00000000, 0xf87436d000000000, 0x08a6a8a700000000, + 0x67ea0d3c00000000, 0x1675994e00000000, 0x79393cd500000000, + 0x89eba2a200000000, 0xe6a7073900000000, 0x694e9f4d00000000, + 0x06023ad600000000, 0xf6d0a4a100000000, 0x999c013a00000000, + 0x14ee8d4400000000, 0x7ba228df00000000, 0x8b70b6a800000000, + 0xe43c133300000000, 0x6bd58b4700000000, 0x04992edc00000000, + 0xf44bb0ab00000000, 0x9b07153000000000, 0xea98814200000000, + 0x85d424d900000000, 0x7506baae00000000, 0x1a4a1f3500000000, + 0x95a3874100000000, 0xfaef22da00000000, 0x0a3dbcad00000000, + 0x6571193600000000}, + {0x0000000000000000, 0x85d996dd00000000, 0x4bb55c6000000000, + 0xce6ccabd00000000, 0x966ab9c000000000, 0x13b32f1d00000000, + 0xdddfe5a000000000, 0x5806737d00000000, 0x6dd3035a00000000, + 0xe80a958700000000, 0x26665f3a00000000, 0xa3bfc9e700000000, + 0xfbb9ba9a00000000, 0x7e602c4700000000, 0xb00ce6fa00000000, + 0x35d5702700000000, 0xdaa607b400000000, 0x5f7f916900000000, + 0x91135bd400000000, 0x14cacd0900000000, 0x4cccbe7400000000, + 0xc91528a900000000, 0x0779e21400000000, 0x82a074c900000000, + 0xb77504ee00000000, 0x32ac923300000000, 0xfcc0588e00000000, + 0x7919ce5300000000, 0x211fbd2e00000000, 0xa4c62bf300000000, + 0x6aaae14e00000000, 0xef73779300000000, 0xf54b7eb300000000, + 0x7092e86e00000000, 0xbefe22d300000000, 0x3b27b40e00000000, + 0x6321c77300000000, 0xe6f851ae00000000, 0x28949b1300000000, + 0xad4d0dce00000000, 0x98987de900000000, 0x1d41eb3400000000, + 0xd32d218900000000, 0x56f4b75400000000, 0x0ef2c42900000000, + 0x8b2b52f400000000, 0x4547984900000000, 0xc09e0e9400000000, + 0x2fed790700000000, 0xaa34efda00000000, 0x6458256700000000, + 0xe181b3ba00000000, 0xb987c0c700000000, 0x3c5e561a00000000, + 0xf2329ca700000000, 0x77eb0a7a00000000, 0x423e7a5d00000000, + 0xc7e7ec8000000000, 0x098b263d00000000, 0x8c52b0e000000000, + 0xd454c39d00000000, 0x518d554000000000, 0x9fe19ffd00000000, + 0x1a38092000000000, 0xab918dbd00000000, 0x2e481b6000000000, + 0xe024d1dd00000000, 0x65fd470000000000, 0x3dfb347d00000000, + 0xb822a2a000000000, 0x764e681d00000000, 0xf397fec000000000, + 0xc6428ee700000000, 0x439b183a00000000, 0x8df7d28700000000, + 0x082e445a00000000, 0x5028372700000000, 0xd5f1a1fa00000000, + 0x1b9d6b4700000000, 0x9e44fd9a00000000, 0x71378a0900000000, + 0xf4ee1cd400000000, 0x3a82d66900000000, 0xbf5b40b400000000, + 0xe75d33c900000000, 0x6284a51400000000, 0xace86fa900000000, + 0x2931f97400000000, 0x1ce4895300000000, 0x993d1f8e00000000, + 0x5751d53300000000, 0xd28843ee00000000, 0x8a8e309300000000, + 0x0f57a64e00000000, 0xc13b6cf300000000, 0x44e2fa2e00000000, + 0x5edaf30e00000000, 0xdb0365d300000000, 0x156faf6e00000000, + 0x90b639b300000000, 0xc8b04ace00000000, 0x4d69dc1300000000, + 0x830516ae00000000, 0x06dc807300000000, 0x3309f05400000000, + 0xb6d0668900000000, 0x78bcac3400000000, 0xfd653ae900000000, + 0xa563499400000000, 0x20badf4900000000, 0xeed615f400000000, + 0x6b0f832900000000, 0x847cf4ba00000000, 0x01a5626700000000, + 0xcfc9a8da00000000, 0x4a103e0700000000, 0x12164d7a00000000, + 0x97cfdba700000000, 0x59a3111a00000000, 0xdc7a87c700000000, + 0xe9aff7e000000000, 0x6c76613d00000000, 0xa21aab8000000000, + 0x27c33d5d00000000, 0x7fc54e2000000000, 0xfa1cd8fd00000000, + 0x3470124000000000, 0xb1a9849d00000000, 0x17256aa000000000, + 0x92fcfc7d00000000, 0x5c9036c000000000, 0xd949a01d00000000, + 0x814fd36000000000, 0x049645bd00000000, 0xcafa8f0000000000, + 0x4f2319dd00000000, 0x7af669fa00000000, 0xff2fff2700000000, + 0x3143359a00000000, 0xb49aa34700000000, 0xec9cd03a00000000, + 0x694546e700000000, 0xa7298c5a00000000, 0x22f01a8700000000, + 0xcd836d1400000000, 0x485afbc900000000, 0x8636317400000000, + 0x03efa7a900000000, 0x5be9d4d400000000, 0xde30420900000000, + 0x105c88b400000000, 0x95851e6900000000, 0xa0506e4e00000000, + 0x2589f89300000000, 0xebe5322e00000000, 0x6e3ca4f300000000, + 0x363ad78e00000000, 0xb3e3415300000000, 0x7d8f8bee00000000, + 0xf8561d3300000000, 0xe26e141300000000, 0x67b782ce00000000, + 0xa9db487300000000, 0x2c02deae00000000, 0x7404add300000000, + 0xf1dd3b0e00000000, 0x3fb1f1b300000000, 0xba68676e00000000, + 0x8fbd174900000000, 0x0a64819400000000, 0xc4084b2900000000, + 0x41d1ddf400000000, 0x19d7ae8900000000, 0x9c0e385400000000, + 0x5262f2e900000000, 0xd7bb643400000000, 0x38c813a700000000, + 0xbd11857a00000000, 0x737d4fc700000000, 0xf6a4d91a00000000, + 0xaea2aa6700000000, 0x2b7b3cba00000000, 0xe517f60700000000, + 0x60ce60da00000000, 0x551b10fd00000000, 0xd0c2862000000000, + 0x1eae4c9d00000000, 0x9b77da4000000000, 0xc371a93d00000000, + 0x46a83fe000000000, 0x88c4f55d00000000, 0x0d1d638000000000, + 0xbcb4e71d00000000, 0x396d71c000000000, 0xf701bb7d00000000, + 0x72d82da000000000, 0x2ade5edd00000000, 0xaf07c80000000000, + 0x616b02bd00000000, 0xe4b2946000000000, 0xd167e44700000000, + 0x54be729a00000000, 0x9ad2b82700000000, 0x1f0b2efa00000000, + 0x470d5d8700000000, 0xc2d4cb5a00000000, 0x0cb801e700000000, + 0x8961973a00000000, 0x6612e0a900000000, 0xe3cb767400000000, + 0x2da7bcc900000000, 0xa87e2a1400000000, 0xf078596900000000, + 0x75a1cfb400000000, 0xbbcd050900000000, 0x3e1493d400000000, + 0x0bc1e3f300000000, 0x8e18752e00000000, 0x4074bf9300000000, + 0xc5ad294e00000000, 0x9dab5a3300000000, 0x1872ccee00000000, + 0xd61e065300000000, 0x53c7908e00000000, 0x49ff99ae00000000, + 0xcc260f7300000000, 0x024ac5ce00000000, 0x8793531300000000, + 0xdf95206e00000000, 0x5a4cb6b300000000, 0x94207c0e00000000, + 0x11f9ead300000000, 0x242c9af400000000, 0xa1f50c2900000000, + 0x6f99c69400000000, 0xea40504900000000, 0xb246233400000000, + 0x379fb5e900000000, 0xf9f37f5400000000, 0x7c2ae98900000000, + 0x93599e1a00000000, 0x168008c700000000, 0xd8ecc27a00000000, + 0x5d3554a700000000, 0x053327da00000000, 0x80eab10700000000, + 0x4e867bba00000000, 0xcb5fed6700000000, 0xfe8a9d4000000000, + 0x7b530b9d00000000, 0xb53fc12000000000, 0x30e657fd00000000, + 0x68e0248000000000, 0xed39b25d00000000, 0x235578e000000000, + 0xa68cee3d00000000}, + {0x0000000000000000, 0x76e10f9d00000000, 0xadc46ee100000000, + 0xdb25617c00000000, 0x1b8fac1900000000, 0x6d6ea38400000000, + 0xb64bc2f800000000, 0xc0aacd6500000000, 0x361e593300000000, + 0x40ff56ae00000000, 0x9bda37d200000000, 0xed3b384f00000000, + 0x2d91f52a00000000, 0x5b70fab700000000, 0x80559bcb00000000, + 0xf6b4945600000000, 0x6c3cb26600000000, 0x1addbdfb00000000, + 0xc1f8dc8700000000, 0xb719d31a00000000, 0x77b31e7f00000000, + 0x015211e200000000, 0xda77709e00000000, 0xac967f0300000000, + 0x5a22eb5500000000, 0x2cc3e4c800000000, 0xf7e685b400000000, + 0x81078a2900000000, 0x41ad474c00000000, 0x374c48d100000000, + 0xec6929ad00000000, 0x9a88263000000000, 0xd87864cd00000000, + 0xae996b5000000000, 0x75bc0a2c00000000, 0x035d05b100000000, + 0xc3f7c8d400000000, 0xb516c74900000000, 0x6e33a63500000000, + 0x18d2a9a800000000, 0xee663dfe00000000, 0x9887326300000000, + 0x43a2531f00000000, 0x35435c8200000000, 0xf5e991e700000000, + 0x83089e7a00000000, 0x582dff0600000000, 0x2eccf09b00000000, + 0xb444d6ab00000000, 0xc2a5d93600000000, 0x1980b84a00000000, + 0x6f61b7d700000000, 0xafcb7ab200000000, 0xd92a752f00000000, + 0x020f145300000000, 0x74ee1bce00000000, 0x825a8f9800000000, + 0xf4bb800500000000, 0x2f9ee17900000000, 0x597feee400000000, + 0x99d5238100000000, 0xef342c1c00000000, 0x34114d6000000000, + 0x42f042fd00000000, 0xf1f7b94100000000, 0x8716b6dc00000000, + 0x5c33d7a000000000, 0x2ad2d83d00000000, 0xea78155800000000, + 0x9c991ac500000000, 0x47bc7bb900000000, 0x315d742400000000, + 0xc7e9e07200000000, 0xb108efef00000000, 0x6a2d8e9300000000, + 0x1ccc810e00000000, 0xdc664c6b00000000, 0xaa8743f600000000, + 0x71a2228a00000000, 0x07432d1700000000, 0x9dcb0b2700000000, + 0xeb2a04ba00000000, 0x300f65c600000000, 0x46ee6a5b00000000, + 0x8644a73e00000000, 0xf0a5a8a300000000, 0x2b80c9df00000000, + 0x5d61c64200000000, 0xabd5521400000000, 0xdd345d8900000000, + 0x06113cf500000000, 0x70f0336800000000, 0xb05afe0d00000000, + 0xc6bbf19000000000, 0x1d9e90ec00000000, 0x6b7f9f7100000000, + 0x298fdd8c00000000, 0x5f6ed21100000000, 0x844bb36d00000000, + 0xf2aabcf000000000, 0x3200719500000000, 0x44e17e0800000000, + 0x9fc41f7400000000, 0xe92510e900000000, 0x1f9184bf00000000, + 0x69708b2200000000, 0xb255ea5e00000000, 0xc4b4e5c300000000, + 0x041e28a600000000, 0x72ff273b00000000, 0xa9da464700000000, + 0xdf3b49da00000000, 0x45b36fea00000000, 0x3352607700000000, + 0xe877010b00000000, 0x9e960e9600000000, 0x5e3cc3f300000000, + 0x28ddcc6e00000000, 0xf3f8ad1200000000, 0x8519a28f00000000, + 0x73ad36d900000000, 0x054c394400000000, 0xde69583800000000, + 0xa88857a500000000, 0x68229ac000000000, 0x1ec3955d00000000, + 0xc5e6f42100000000, 0xb307fbbc00000000, 0xe2ef738300000000, + 0x940e7c1e00000000, 0x4f2b1d6200000000, 0x39ca12ff00000000, + 0xf960df9a00000000, 0x8f81d00700000000, 0x54a4b17b00000000, + 0x2245bee600000000, 0xd4f12ab000000000, 0xa210252d00000000, + 0x7935445100000000, 0x0fd44bcc00000000, 0xcf7e86a900000000, + 0xb99f893400000000, 0x62bae84800000000, 0x145be7d500000000, + 0x8ed3c1e500000000, 0xf832ce7800000000, 0x2317af0400000000, + 0x55f6a09900000000, 0x955c6dfc00000000, 0xe3bd626100000000, + 0x3898031d00000000, 0x4e790c8000000000, 0xb8cd98d600000000, + 0xce2c974b00000000, 0x1509f63700000000, 0x63e8f9aa00000000, + 0xa34234cf00000000, 0xd5a33b5200000000, 0x0e865a2e00000000, + 0x786755b300000000, 0x3a97174e00000000, 0x4c7618d300000000, + 0x975379af00000000, 0xe1b2763200000000, 0x2118bb5700000000, + 0x57f9b4ca00000000, 0x8cdcd5b600000000, 0xfa3dda2b00000000, + 0x0c894e7d00000000, 0x7a6841e000000000, 0xa14d209c00000000, + 0xd7ac2f0100000000, 0x1706e26400000000, 0x61e7edf900000000, + 0xbac28c8500000000, 0xcc23831800000000, 0x56aba52800000000, + 0x204aaab500000000, 0xfb6fcbc900000000, 0x8d8ec45400000000, + 0x4d24093100000000, 0x3bc506ac00000000, 0xe0e067d000000000, + 0x9601684d00000000, 0x60b5fc1b00000000, 0x1654f38600000000, + 0xcd7192fa00000000, 0xbb909d6700000000, 0x7b3a500200000000, + 0x0ddb5f9f00000000, 0xd6fe3ee300000000, 0xa01f317e00000000, + 0x1318cac200000000, 0x65f9c55f00000000, 0xbedca42300000000, + 0xc83dabbe00000000, 0x089766db00000000, 0x7e76694600000000, + 0xa553083a00000000, 0xd3b207a700000000, 0x250693f100000000, + 0x53e79c6c00000000, 0x88c2fd1000000000, 0xfe23f28d00000000, + 0x3e893fe800000000, 0x4868307500000000, 0x934d510900000000, + 0xe5ac5e9400000000, 0x7f2478a400000000, 0x09c5773900000000, + 0xd2e0164500000000, 0xa40119d800000000, 0x64abd4bd00000000, + 0x124adb2000000000, 0xc96fba5c00000000, 0xbf8eb5c100000000, + 0x493a219700000000, 0x3fdb2e0a00000000, 0xe4fe4f7600000000, + 0x921f40eb00000000, 0x52b58d8e00000000, 0x2454821300000000, + 0xff71e36f00000000, 0x8990ecf200000000, 0xcb60ae0f00000000, + 0xbd81a19200000000, 0x66a4c0ee00000000, 0x1045cf7300000000, + 0xd0ef021600000000, 0xa60e0d8b00000000, 0x7d2b6cf700000000, + 0x0bca636a00000000, 0xfd7ef73c00000000, 0x8b9ff8a100000000, + 0x50ba99dd00000000, 0x265b964000000000, 0xe6f15b2500000000, + 0x901054b800000000, 0x4b3535c400000000, 0x3dd43a5900000000, + 0xa75c1c6900000000, 0xd1bd13f400000000, 0x0a98728800000000, + 0x7c797d1500000000, 0xbcd3b07000000000, 0xca32bfed00000000, + 0x1117de9100000000, 0x67f6d10c00000000, 0x9142455a00000000, + 0xe7a34ac700000000, 0x3c862bbb00000000, 0x4a67242600000000, + 0x8acde94300000000, 0xfc2ce6de00000000, 0x270987a200000000, + 0x51e8883f00000000}, + {0x0000000000000000, 0xe8dbfbb900000000, 0x91b186a800000000, + 0x796a7d1100000000, 0x63657c8a00000000, 0x8bbe873300000000, + 0xf2d4fa2200000000, 0x1a0f019b00000000, 0x87cc89cf00000000, + 0x6f17727600000000, 0x167d0f6700000000, 0xfea6f4de00000000, + 0xe4a9f54500000000, 0x0c720efc00000000, 0x751873ed00000000, + 0x9dc3885400000000, 0x4f9f624400000000, 0xa74499fd00000000, + 0xde2ee4ec00000000, 0x36f51f5500000000, 0x2cfa1ece00000000, + 0xc421e57700000000, 0xbd4b986600000000, 0x559063df00000000, + 0xc853eb8b00000000, 0x2088103200000000, 0x59e26d2300000000, + 0xb139969a00000000, 0xab36970100000000, 0x43ed6cb800000000, + 0x3a8711a900000000, 0xd25cea1000000000, 0x9e3ec58800000000, + 0x76e53e3100000000, 0x0f8f432000000000, 0xe754b89900000000, + 0xfd5bb90200000000, 0x158042bb00000000, 0x6cea3faa00000000, + 0x8431c41300000000, 0x19f24c4700000000, 0xf129b7fe00000000, + 0x8843caef00000000, 0x6098315600000000, 0x7a9730cd00000000, + 0x924ccb7400000000, 0xeb26b66500000000, 0x03fd4ddc00000000, + 0xd1a1a7cc00000000, 0x397a5c7500000000, 0x4010216400000000, + 0xa8cbdadd00000000, 0xb2c4db4600000000, 0x5a1f20ff00000000, + 0x23755dee00000000, 0xcbaea65700000000, 0x566d2e0300000000, + 0xbeb6d5ba00000000, 0xc7dca8ab00000000, 0x2f07531200000000, + 0x3508528900000000, 0xddd3a93000000000, 0xa4b9d42100000000, + 0x4c622f9800000000, 0x7d7bfbca00000000, 0x95a0007300000000, + 0xecca7d6200000000, 0x041186db00000000, 0x1e1e874000000000, + 0xf6c57cf900000000, 0x8faf01e800000000, 0x6774fa5100000000, + 0xfab7720500000000, 0x126c89bc00000000, 0x6b06f4ad00000000, + 0x83dd0f1400000000, 0x99d20e8f00000000, 0x7109f53600000000, + 0x0863882700000000, 0xe0b8739e00000000, 0x32e4998e00000000, + 0xda3f623700000000, 0xa3551f2600000000, 0x4b8ee49f00000000, + 0x5181e50400000000, 0xb95a1ebd00000000, 0xc03063ac00000000, + 0x28eb981500000000, 0xb528104100000000, 0x5df3ebf800000000, + 0x249996e900000000, 0xcc426d5000000000, 0xd64d6ccb00000000, + 0x3e96977200000000, 0x47fcea6300000000, 0xaf2711da00000000, + 0xe3453e4200000000, 0x0b9ec5fb00000000, 0x72f4b8ea00000000, + 0x9a2f435300000000, 0x802042c800000000, 0x68fbb97100000000, + 0x1191c46000000000, 0xf94a3fd900000000, 0x6489b78d00000000, + 0x8c524c3400000000, 0xf538312500000000, 0x1de3ca9c00000000, + 0x07eccb0700000000, 0xef3730be00000000, 0x965d4daf00000000, + 0x7e86b61600000000, 0xacda5c0600000000, 0x4401a7bf00000000, + 0x3d6bdaae00000000, 0xd5b0211700000000, 0xcfbf208c00000000, + 0x2764db3500000000, 0x5e0ea62400000000, 0xb6d55d9d00000000, + 0x2b16d5c900000000, 0xc3cd2e7000000000, 0xbaa7536100000000, + 0x527ca8d800000000, 0x4873a94300000000, 0xa0a852fa00000000, + 0xd9c22feb00000000, 0x3119d45200000000, 0xbbf0874e00000000, + 0x532b7cf700000000, 0x2a4101e600000000, 0xc29afa5f00000000, + 0xd895fbc400000000, 0x304e007d00000000, 0x49247d6c00000000, + 0xa1ff86d500000000, 0x3c3c0e8100000000, 0xd4e7f53800000000, + 0xad8d882900000000, 0x4556739000000000, 0x5f59720b00000000, + 0xb78289b200000000, 0xcee8f4a300000000, 0x26330f1a00000000, + 0xf46fe50a00000000, 0x1cb41eb300000000, 0x65de63a200000000, + 0x8d05981b00000000, 0x970a998000000000, 0x7fd1623900000000, + 0x06bb1f2800000000, 0xee60e49100000000, 0x73a36cc500000000, + 0x9b78977c00000000, 0xe212ea6d00000000, 0x0ac911d400000000, + 0x10c6104f00000000, 0xf81debf600000000, 0x817796e700000000, + 0x69ac6d5e00000000, 0x25ce42c600000000, 0xcd15b97f00000000, + 0xb47fc46e00000000, 0x5ca43fd700000000, 0x46ab3e4c00000000, + 0xae70c5f500000000, 0xd71ab8e400000000, 0x3fc1435d00000000, + 0xa202cb0900000000, 0x4ad930b000000000, 0x33b34da100000000, + 0xdb68b61800000000, 0xc167b78300000000, 0x29bc4c3a00000000, + 0x50d6312b00000000, 0xb80dca9200000000, 0x6a51208200000000, + 0x828adb3b00000000, 0xfbe0a62a00000000, 0x133b5d9300000000, + 0x09345c0800000000, 0xe1efa7b100000000, 0x9885daa000000000, + 0x705e211900000000, 0xed9da94d00000000, 0x054652f400000000, + 0x7c2c2fe500000000, 0x94f7d45c00000000, 0x8ef8d5c700000000, + 0x66232e7e00000000, 0x1f49536f00000000, 0xf792a8d600000000, + 0xc68b7c8400000000, 0x2e50873d00000000, 0x573afa2c00000000, + 0xbfe1019500000000, 0xa5ee000e00000000, 0x4d35fbb700000000, + 0x345f86a600000000, 0xdc847d1f00000000, 0x4147f54b00000000, + 0xa99c0ef200000000, 0xd0f673e300000000, 0x382d885a00000000, + 0x222289c100000000, 0xcaf9727800000000, 0xb3930f6900000000, + 0x5b48f4d000000000, 0x89141ec000000000, 0x61cfe57900000000, + 0x18a5986800000000, 0xf07e63d100000000, 0xea71624a00000000, + 0x02aa99f300000000, 0x7bc0e4e200000000, 0x931b1f5b00000000, + 0x0ed8970f00000000, 0xe6036cb600000000, 0x9f6911a700000000, + 0x77b2ea1e00000000, 0x6dbdeb8500000000, 0x8566103c00000000, + 0xfc0c6d2d00000000, 0x14d7969400000000, 0x58b5b90c00000000, + 0xb06e42b500000000, 0xc9043fa400000000, 0x21dfc41d00000000, + 0x3bd0c58600000000, 0xd30b3e3f00000000, 0xaa61432e00000000, + 0x42bab89700000000, 0xdf7930c300000000, 0x37a2cb7a00000000, + 0x4ec8b66b00000000, 0xa6134dd200000000, 0xbc1c4c4900000000, + 0x54c7b7f000000000, 0x2dadcae100000000, 0xc576315800000000, + 0x172adb4800000000, 0xfff120f100000000, 0x869b5de000000000, + 0x6e40a65900000000, 0x744fa7c200000000, 0x9c945c7b00000000, + 0xe5fe216a00000000, 0x0d25dad300000000, 0x90e6528700000000, + 0x783da93e00000000, 0x0157d42f00000000, 0xe98c2f9600000000, + 0xf3832e0d00000000, 0x1b58d5b400000000, 0x6232a8a500000000, + 0x8ae9531c00000000}, + {0x0000000000000000, 0x919168ae00000000, 0x6325a08700000000, + 0xf2b4c82900000000, 0x874c31d400000000, 0x16dd597a00000000, + 0xe469915300000000, 0x75f8f9fd00000000, 0x4f9f137300000000, + 0xde0e7bdd00000000, 0x2cbab3f400000000, 0xbd2bdb5a00000000, + 0xc8d322a700000000, 0x59424a0900000000, 0xabf6822000000000, + 0x3a67ea8e00000000, 0x9e3e27e600000000, 0x0faf4f4800000000, + 0xfd1b876100000000, 0x6c8aefcf00000000, 0x1972163200000000, + 0x88e37e9c00000000, 0x7a57b6b500000000, 0xebc6de1b00000000, + 0xd1a1349500000000, 0x40305c3b00000000, 0xb284941200000000, + 0x2315fcbc00000000, 0x56ed054100000000, 0xc77c6def00000000, + 0x35c8a5c600000000, 0xa459cd6800000000, 0x7d7b3f1700000000, + 0xecea57b900000000, 0x1e5e9f9000000000, 0x8fcff73e00000000, + 0xfa370ec300000000, 0x6ba6666d00000000, 0x9912ae4400000000, + 0x0883c6ea00000000, 0x32e42c6400000000, 0xa37544ca00000000, + 0x51c18ce300000000, 0xc050e44d00000000, 0xb5a81db000000000, + 0x2439751e00000000, 0xd68dbd3700000000, 0x471cd59900000000, + 0xe34518f100000000, 0x72d4705f00000000, 0x8060b87600000000, + 0x11f1d0d800000000, 0x6409292500000000, 0xf598418b00000000, + 0x072c89a200000000, 0x96bde10c00000000, 0xacda0b8200000000, + 0x3d4b632c00000000, 0xcfffab0500000000, 0x5e6ec3ab00000000, + 0x2b963a5600000000, 0xba0752f800000000, 0x48b39ad100000000, + 0xd922f27f00000000, 0xfaf67e2e00000000, 0x6b67168000000000, + 0x99d3dea900000000, 0x0842b60700000000, 0x7dba4ffa00000000, + 0xec2b275400000000, 0x1e9fef7d00000000, 0x8f0e87d300000000, + 0xb5696d5d00000000, 0x24f805f300000000, 0xd64ccdda00000000, + 0x47dda57400000000, 0x32255c8900000000, 0xa3b4342700000000, + 0x5100fc0e00000000, 0xc09194a000000000, 0x64c859c800000000, + 0xf559316600000000, 0x07edf94f00000000, 0x967c91e100000000, + 0xe384681c00000000, 0x721500b200000000, 0x80a1c89b00000000, + 0x1130a03500000000, 0x2b574abb00000000, 0xbac6221500000000, + 0x4872ea3c00000000, 0xd9e3829200000000, 0xac1b7b6f00000000, + 0x3d8a13c100000000, 0xcf3edbe800000000, 0x5eafb34600000000, + 0x878d413900000000, 0x161c299700000000, 0xe4a8e1be00000000, + 0x7539891000000000, 0x00c170ed00000000, 0x9150184300000000, + 0x63e4d06a00000000, 0xf275b8c400000000, 0xc812524a00000000, + 0x59833ae400000000, 0xab37f2cd00000000, 0x3aa69a6300000000, + 0x4f5e639e00000000, 0xdecf0b3000000000, 0x2c7bc31900000000, + 0xbdeaabb700000000, 0x19b366df00000000, 0x88220e7100000000, + 0x7a96c65800000000, 0xeb07aef600000000, 0x9eff570b00000000, + 0x0f6e3fa500000000, 0xfddaf78c00000000, 0x6c4b9f2200000000, + 0x562c75ac00000000, 0xc7bd1d0200000000, 0x3509d52b00000000, + 0xa498bd8500000000, 0xd160447800000000, 0x40f12cd600000000, + 0xb245e4ff00000000, 0x23d48c5100000000, 0xf4edfd5c00000000, + 0x657c95f200000000, 0x97c85ddb00000000, 0x0659357500000000, + 0x73a1cc8800000000, 0xe230a42600000000, 0x10846c0f00000000, + 0x811504a100000000, 0xbb72ee2f00000000, 0x2ae3868100000000, + 0xd8574ea800000000, 0x49c6260600000000, 0x3c3edffb00000000, + 0xadafb75500000000, 0x5f1b7f7c00000000, 0xce8a17d200000000, + 0x6ad3daba00000000, 0xfb42b21400000000, 0x09f67a3d00000000, + 0x9867129300000000, 0xed9feb6e00000000, 0x7c0e83c000000000, + 0x8eba4be900000000, 0x1f2b234700000000, 0x254cc9c900000000, + 0xb4dda16700000000, 0x4669694e00000000, 0xd7f801e000000000, + 0xa200f81d00000000, 0x339190b300000000, 0xc125589a00000000, + 0x50b4303400000000, 0x8996c24b00000000, 0x1807aae500000000, + 0xeab362cc00000000, 0x7b220a6200000000, 0x0edaf39f00000000, + 0x9f4b9b3100000000, 0x6dff531800000000, 0xfc6e3bb600000000, + 0xc609d13800000000, 0x5798b99600000000, 0xa52c71bf00000000, + 0x34bd191100000000, 0x4145e0ec00000000, 0xd0d4884200000000, + 0x2260406b00000000, 0xb3f128c500000000, 0x17a8e5ad00000000, + 0x86398d0300000000, 0x748d452a00000000, 0xe51c2d8400000000, + 0x90e4d47900000000, 0x0175bcd700000000, 0xf3c174fe00000000, + 0x62501c5000000000, 0x5837f6de00000000, 0xc9a69e7000000000, + 0x3b12565900000000, 0xaa833ef700000000, 0xdf7bc70a00000000, + 0x4eeaafa400000000, 0xbc5e678d00000000, 0x2dcf0f2300000000, + 0x0e1b837200000000, 0x9f8aebdc00000000, 0x6d3e23f500000000, + 0xfcaf4b5b00000000, 0x8957b2a600000000, 0x18c6da0800000000, + 0xea72122100000000, 0x7be37a8f00000000, 0x4184900100000000, + 0xd015f8af00000000, 0x22a1308600000000, 0xb330582800000000, + 0xc6c8a1d500000000, 0x5759c97b00000000, 0xa5ed015200000000, + 0x347c69fc00000000, 0x9025a49400000000, 0x01b4cc3a00000000, + 0xf300041300000000, 0x62916cbd00000000, 0x1769954000000000, + 0x86f8fdee00000000, 0x744c35c700000000, 0xe5dd5d6900000000, + 0xdfbab7e700000000, 0x4e2bdf4900000000, 0xbc9f176000000000, + 0x2d0e7fce00000000, 0x58f6863300000000, 0xc967ee9d00000000, + 0x3bd326b400000000, 0xaa424e1a00000000, 0x7360bc6500000000, + 0xe2f1d4cb00000000, 0x10451ce200000000, 0x81d4744c00000000, + 0xf42c8db100000000, 0x65bde51f00000000, 0x97092d3600000000, + 0x0698459800000000, 0x3cffaf1600000000, 0xad6ec7b800000000, + 0x5fda0f9100000000, 0xce4b673f00000000, 0xbbb39ec200000000, + 0x2a22f66c00000000, 0xd8963e4500000000, 0x490756eb00000000, + 0xed5e9b8300000000, 0x7ccff32d00000000, 0x8e7b3b0400000000, + 0x1fea53aa00000000, 0x6a12aa5700000000, 0xfb83c2f900000000, + 0x09370ad000000000, 0x98a6627e00000000, 0xa2c188f000000000, + 0x3350e05e00000000, 0xc1e4287700000000, 0x507540d900000000, + 0x258db92400000000, 0xb41cd18a00000000, 0x46a819a300000000, + 0xd739710d00000000}}; + +#else /* W == 4 */ + +local const z_crc_t FAR crc_braid_table[][256] = { + {0x00000000, 0xccaa009e, 0x4225077d, 0x8e8f07e3, 0x844a0efa, + 0x48e00e64, 0xc66f0987, 0x0ac50919, 0xd3e51bb5, 0x1f4f1b2b, + 0x91c01cc8, 0x5d6a1c56, 0x57af154f, 0x9b0515d1, 0x158a1232, + 0xd92012ac, 0x7cbb312b, 0xb01131b5, 0x3e9e3656, 0xf23436c8, + 0xf8f13fd1, 0x345b3f4f, 0xbad438ac, 0x767e3832, 0xaf5e2a9e, + 0x63f42a00, 0xed7b2de3, 0x21d12d7d, 0x2b142464, 0xe7be24fa, + 0x69312319, 0xa59b2387, 0xf9766256, 0x35dc62c8, 0xbb53652b, + 0x77f965b5, 0x7d3c6cac, 0xb1966c32, 0x3f196bd1, 0xf3b36b4f, + 0x2a9379e3, 0xe639797d, 0x68b67e9e, 0xa41c7e00, 0xaed97719, + 0x62737787, 0xecfc7064, 0x205670fa, 0x85cd537d, 0x496753e3, + 0xc7e85400, 0x0b42549e, 0x01875d87, 0xcd2d5d19, 0x43a25afa, + 0x8f085a64, 0x562848c8, 0x9a824856, 0x140d4fb5, 0xd8a74f2b, + 0xd2624632, 0x1ec846ac, 0x9047414f, 0x5ced41d1, 0x299dc2ed, + 0xe537c273, 0x6bb8c590, 0xa712c50e, 0xadd7cc17, 0x617dcc89, + 0xeff2cb6a, 0x2358cbf4, 0xfa78d958, 0x36d2d9c6, 0xb85dde25, + 0x74f7debb, 0x7e32d7a2, 0xb298d73c, 0x3c17d0df, 0xf0bdd041, + 0x5526f3c6, 0x998cf358, 0x1703f4bb, 0xdba9f425, 0xd16cfd3c, + 0x1dc6fda2, 0x9349fa41, 0x5fe3fadf, 0x86c3e873, 0x4a69e8ed, + 0xc4e6ef0e, 0x084cef90, 0x0289e689, 0xce23e617, 0x40ace1f4, + 0x8c06e16a, 0xd0eba0bb, 0x1c41a025, 0x92cea7c6, 0x5e64a758, + 0x54a1ae41, 0x980baedf, 0x1684a93c, 0xda2ea9a2, 0x030ebb0e, + 0xcfa4bb90, 0x412bbc73, 0x8d81bced, 0x8744b5f4, 0x4beeb56a, + 0xc561b289, 0x09cbb217, 0xac509190, 0x60fa910e, 0xee7596ed, + 0x22df9673, 0x281a9f6a, 0xe4b09ff4, 0x6a3f9817, 0xa6959889, + 0x7fb58a25, 0xb31f8abb, 0x3d908d58, 0xf13a8dc6, 0xfbff84df, + 0x37558441, 0xb9da83a2, 0x7570833c, 0x533b85da, 0x9f918544, + 0x111e82a7, 0xddb48239, 0xd7718b20, 0x1bdb8bbe, 0x95548c5d, + 0x59fe8cc3, 0x80de9e6f, 0x4c749ef1, 0xc2fb9912, 0x0e51998c, + 0x04949095, 0xc83e900b, 0x46b197e8, 0x8a1b9776, 0x2f80b4f1, + 0xe32ab46f, 0x6da5b38c, 0xa10fb312, 0xabcaba0b, 0x6760ba95, + 0xe9efbd76, 0x2545bde8, 0xfc65af44, 0x30cfafda, 0xbe40a839, + 0x72eaa8a7, 0x782fa1be, 0xb485a120, 0x3a0aa6c3, 0xf6a0a65d, + 0xaa4de78c, 0x66e7e712, 0xe868e0f1, 0x24c2e06f, 0x2e07e976, + 0xe2ade9e8, 0x6c22ee0b, 0xa088ee95, 0x79a8fc39, 0xb502fca7, + 0x3b8dfb44, 0xf727fbda, 0xfde2f2c3, 0x3148f25d, 0xbfc7f5be, + 0x736df520, 0xd6f6d6a7, 0x1a5cd639, 0x94d3d1da, 0x5879d144, + 0x52bcd85d, 0x9e16d8c3, 0x1099df20, 0xdc33dfbe, 0x0513cd12, + 0xc9b9cd8c, 0x4736ca6f, 0x8b9ccaf1, 0x8159c3e8, 0x4df3c376, + 0xc37cc495, 0x0fd6c40b, 0x7aa64737, 0xb60c47a9, 0x3883404a, + 0xf42940d4, 0xfeec49cd, 0x32464953, 0xbcc94eb0, 0x70634e2e, + 0xa9435c82, 0x65e95c1c, 0xeb665bff, 0x27cc5b61, 0x2d095278, + 0xe1a352e6, 0x6f2c5505, 0xa386559b, 0x061d761c, 0xcab77682, + 0x44387161, 0x889271ff, 0x825778e6, 0x4efd7878, 0xc0727f9b, + 0x0cd87f05, 0xd5f86da9, 0x19526d37, 0x97dd6ad4, 0x5b776a4a, + 0x51b26353, 0x9d1863cd, 0x1397642e, 0xdf3d64b0, 0x83d02561, + 0x4f7a25ff, 0xc1f5221c, 0x0d5f2282, 0x079a2b9b, 0xcb302b05, + 0x45bf2ce6, 0x89152c78, 0x50353ed4, 0x9c9f3e4a, 0x121039a9, + 0xdeba3937, 0xd47f302e, 0x18d530b0, 0x965a3753, 0x5af037cd, + 0xff6b144a, 0x33c114d4, 0xbd4e1337, 0x71e413a9, 0x7b211ab0, + 0xb78b1a2e, 0x39041dcd, 0xf5ae1d53, 0x2c8e0fff, 0xe0240f61, + 0x6eab0882, 0xa201081c, 0xa8c40105, 0x646e019b, 0xeae10678, + 0x264b06e6}, + {0x00000000, 0xa6770bb4, 0x979f1129, 0x31e81a9d, 0xf44f2413, + 0x52382fa7, 0x63d0353a, 0xc5a73e8e, 0x33ef4e67, 0x959845d3, + 0xa4705f4e, 0x020754fa, 0xc7a06a74, 0x61d761c0, 0x503f7b5d, + 0xf64870e9, 0x67de9cce, 0xc1a9977a, 0xf0418de7, 0x56368653, + 0x9391b8dd, 0x35e6b369, 0x040ea9f4, 0xa279a240, 0x5431d2a9, + 0xf246d91d, 0xc3aec380, 0x65d9c834, 0xa07ef6ba, 0x0609fd0e, + 0x37e1e793, 0x9196ec27, 0xcfbd399c, 0x69ca3228, 0x582228b5, + 0xfe552301, 0x3bf21d8f, 0x9d85163b, 0xac6d0ca6, 0x0a1a0712, + 0xfc5277fb, 0x5a257c4f, 0x6bcd66d2, 0xcdba6d66, 0x081d53e8, + 0xae6a585c, 0x9f8242c1, 0x39f54975, 0xa863a552, 0x0e14aee6, + 0x3ffcb47b, 0x998bbfcf, 0x5c2c8141, 0xfa5b8af5, 0xcbb39068, + 0x6dc49bdc, 0x9b8ceb35, 0x3dfbe081, 0x0c13fa1c, 0xaa64f1a8, + 0x6fc3cf26, 0xc9b4c492, 0xf85cde0f, 0x5e2bd5bb, 0x440b7579, + 0xe27c7ecd, 0xd3946450, 0x75e36fe4, 0xb044516a, 0x16335ade, + 0x27db4043, 0x81ac4bf7, 0x77e43b1e, 0xd19330aa, 0xe07b2a37, + 0x460c2183, 0x83ab1f0d, 0x25dc14b9, 0x14340e24, 0xb2430590, + 0x23d5e9b7, 0x85a2e203, 0xb44af89e, 0x123df32a, 0xd79acda4, + 0x71edc610, 0x4005dc8d, 0xe672d739, 0x103aa7d0, 0xb64dac64, + 0x87a5b6f9, 0x21d2bd4d, 0xe47583c3, 0x42028877, 0x73ea92ea, + 0xd59d995e, 0x8bb64ce5, 0x2dc14751, 0x1c295dcc, 0xba5e5678, + 0x7ff968f6, 0xd98e6342, 0xe86679df, 0x4e11726b, 0xb8590282, + 0x1e2e0936, 0x2fc613ab, 0x89b1181f, 0x4c162691, 0xea612d25, + 0xdb8937b8, 0x7dfe3c0c, 0xec68d02b, 0x4a1fdb9f, 0x7bf7c102, + 0xdd80cab6, 0x1827f438, 0xbe50ff8c, 0x8fb8e511, 0x29cfeea5, + 0xdf879e4c, 0x79f095f8, 0x48188f65, 0xee6f84d1, 0x2bc8ba5f, + 0x8dbfb1eb, 0xbc57ab76, 0x1a20a0c2, 0x8816eaf2, 0x2e61e146, + 0x1f89fbdb, 0xb9fef06f, 0x7c59cee1, 0xda2ec555, 0xebc6dfc8, + 0x4db1d47c, 0xbbf9a495, 0x1d8eaf21, 0x2c66b5bc, 0x8a11be08, + 0x4fb68086, 0xe9c18b32, 0xd82991af, 0x7e5e9a1b, 0xefc8763c, + 0x49bf7d88, 0x78576715, 0xde206ca1, 0x1b87522f, 0xbdf0599b, + 0x8c184306, 0x2a6f48b2, 0xdc27385b, 0x7a5033ef, 0x4bb82972, + 0xedcf22c6, 0x28681c48, 0x8e1f17fc, 0xbff70d61, 0x198006d5, + 0x47abd36e, 0xe1dcd8da, 0xd034c247, 0x7643c9f3, 0xb3e4f77d, + 0x1593fcc9, 0x247be654, 0x820cede0, 0x74449d09, 0xd23396bd, + 0xe3db8c20, 0x45ac8794, 0x800bb91a, 0x267cb2ae, 0x1794a833, + 0xb1e3a387, 0x20754fa0, 0x86024414, 0xb7ea5e89, 0x119d553d, + 0xd43a6bb3, 0x724d6007, 0x43a57a9a, 0xe5d2712e, 0x139a01c7, + 0xb5ed0a73, 0x840510ee, 0x22721b5a, 0xe7d525d4, 0x41a22e60, + 0x704a34fd, 0xd63d3f49, 0xcc1d9f8b, 0x6a6a943f, 0x5b828ea2, + 0xfdf58516, 0x3852bb98, 0x9e25b02c, 0xafcdaab1, 0x09baa105, + 0xfff2d1ec, 0x5985da58, 0x686dc0c5, 0xce1acb71, 0x0bbdf5ff, + 0xadcafe4b, 0x9c22e4d6, 0x3a55ef62, 0xabc30345, 0x0db408f1, + 0x3c5c126c, 0x9a2b19d8, 0x5f8c2756, 0xf9fb2ce2, 0xc813367f, + 0x6e643dcb, 0x982c4d22, 0x3e5b4696, 0x0fb35c0b, 0xa9c457bf, + 0x6c636931, 0xca146285, 0xfbfc7818, 0x5d8b73ac, 0x03a0a617, + 0xa5d7ada3, 0x943fb73e, 0x3248bc8a, 0xf7ef8204, 0x519889b0, + 0x6070932d, 0xc6079899, 0x304fe870, 0x9638e3c4, 0xa7d0f959, + 0x01a7f2ed, 0xc400cc63, 0x6277c7d7, 0x539fdd4a, 0xf5e8d6fe, + 0x647e3ad9, 0xc209316d, 0xf3e12bf0, 0x55962044, 0x90311eca, + 0x3646157e, 0x07ae0fe3, 0xa1d90457, 0x579174be, 0xf1e67f0a, + 0xc00e6597, 0x66796e23, 0xa3de50ad, 0x05a95b19, 0x34414184, + 0x92364a30}, + {0x00000000, 0xcb5cd3a5, 0x4dc8a10b, 0x869472ae, 0x9b914216, + 0x50cd91b3, 0xd659e31d, 0x1d0530b8, 0xec53826d, 0x270f51c8, + 0xa19b2366, 0x6ac7f0c3, 0x77c2c07b, 0xbc9e13de, 0x3a0a6170, + 0xf156b2d5, 0x03d6029b, 0xc88ad13e, 0x4e1ea390, 0x85427035, + 0x9847408d, 0x531b9328, 0xd58fe186, 0x1ed33223, 0xef8580f6, + 0x24d95353, 0xa24d21fd, 0x6911f258, 0x7414c2e0, 0xbf481145, + 0x39dc63eb, 0xf280b04e, 0x07ac0536, 0xccf0d693, 0x4a64a43d, + 0x81387798, 0x9c3d4720, 0x57619485, 0xd1f5e62b, 0x1aa9358e, + 0xebff875b, 0x20a354fe, 0xa6372650, 0x6d6bf5f5, 0x706ec54d, + 0xbb3216e8, 0x3da66446, 0xf6fab7e3, 0x047a07ad, 0xcf26d408, + 0x49b2a6a6, 0x82ee7503, 0x9feb45bb, 0x54b7961e, 0xd223e4b0, + 0x197f3715, 0xe82985c0, 0x23755665, 0xa5e124cb, 0x6ebdf76e, + 0x73b8c7d6, 0xb8e41473, 0x3e7066dd, 0xf52cb578, 0x0f580a6c, + 0xc404d9c9, 0x4290ab67, 0x89cc78c2, 0x94c9487a, 0x5f959bdf, + 0xd901e971, 0x125d3ad4, 0xe30b8801, 0x28575ba4, 0xaec3290a, + 0x659ffaaf, 0x789aca17, 0xb3c619b2, 0x35526b1c, 0xfe0eb8b9, + 0x0c8e08f7, 0xc7d2db52, 0x4146a9fc, 0x8a1a7a59, 0x971f4ae1, + 0x5c439944, 0xdad7ebea, 0x118b384f, 0xe0dd8a9a, 0x2b81593f, + 0xad152b91, 0x6649f834, 0x7b4cc88c, 0xb0101b29, 0x36846987, + 0xfdd8ba22, 0x08f40f5a, 0xc3a8dcff, 0x453cae51, 0x8e607df4, + 0x93654d4c, 0x58399ee9, 0xdeadec47, 0x15f13fe2, 0xe4a78d37, + 0x2ffb5e92, 0xa96f2c3c, 0x6233ff99, 0x7f36cf21, 0xb46a1c84, + 0x32fe6e2a, 0xf9a2bd8f, 0x0b220dc1, 0xc07ede64, 0x46eaacca, + 0x8db67f6f, 0x90b34fd7, 0x5bef9c72, 0xdd7beedc, 0x16273d79, + 0xe7718fac, 0x2c2d5c09, 0xaab92ea7, 0x61e5fd02, 0x7ce0cdba, + 0xb7bc1e1f, 0x31286cb1, 0xfa74bf14, 0x1eb014d8, 0xd5ecc77d, + 0x5378b5d3, 0x98246676, 0x852156ce, 0x4e7d856b, 0xc8e9f7c5, + 0x03b52460, 0xf2e396b5, 0x39bf4510, 0xbf2b37be, 0x7477e41b, + 0x6972d4a3, 0xa22e0706, 0x24ba75a8, 0xefe6a60d, 0x1d661643, + 0xd63ac5e6, 0x50aeb748, 0x9bf264ed, 0x86f75455, 0x4dab87f0, + 0xcb3ff55e, 0x006326fb, 0xf135942e, 0x3a69478b, 0xbcfd3525, + 0x77a1e680, 0x6aa4d638, 0xa1f8059d, 0x276c7733, 0xec30a496, + 0x191c11ee, 0xd240c24b, 0x54d4b0e5, 0x9f886340, 0x828d53f8, + 0x49d1805d, 0xcf45f2f3, 0x04192156, 0xf54f9383, 0x3e134026, + 0xb8873288, 0x73dbe12d, 0x6eded195, 0xa5820230, 0x2316709e, + 0xe84aa33b, 0x1aca1375, 0xd196c0d0, 0x5702b27e, 0x9c5e61db, + 0x815b5163, 0x4a0782c6, 0xcc93f068, 0x07cf23cd, 0xf6999118, + 0x3dc542bd, 0xbb513013, 0x700de3b6, 0x6d08d30e, 0xa65400ab, + 0x20c07205, 0xeb9ca1a0, 0x11e81eb4, 0xdab4cd11, 0x5c20bfbf, + 0x977c6c1a, 0x8a795ca2, 0x41258f07, 0xc7b1fda9, 0x0ced2e0c, + 0xfdbb9cd9, 0x36e74f7c, 0xb0733dd2, 0x7b2fee77, 0x662adecf, + 0xad760d6a, 0x2be27fc4, 0xe0beac61, 0x123e1c2f, 0xd962cf8a, + 0x5ff6bd24, 0x94aa6e81, 0x89af5e39, 0x42f38d9c, 0xc467ff32, + 0x0f3b2c97, 0xfe6d9e42, 0x35314de7, 0xb3a53f49, 0x78f9ecec, + 0x65fcdc54, 0xaea00ff1, 0x28347d5f, 0xe368aefa, 0x16441b82, + 0xdd18c827, 0x5b8cba89, 0x90d0692c, 0x8dd55994, 0x46898a31, + 0xc01df89f, 0x0b412b3a, 0xfa1799ef, 0x314b4a4a, 0xb7df38e4, + 0x7c83eb41, 0x6186dbf9, 0xaada085c, 0x2c4e7af2, 0xe712a957, + 0x15921919, 0xdececabc, 0x585ab812, 0x93066bb7, 0x8e035b0f, + 0x455f88aa, 0xc3cbfa04, 0x089729a1, 0xf9c19b74, 0x329d48d1, + 0xb4093a7f, 0x7f55e9da, 0x6250d962, 0xa90c0ac7, 0x2f987869, + 0xe4c4abcc}, + {0x00000000, 0x3d6029b0, 0x7ac05360, 0x47a07ad0, 0xf580a6c0, + 0xc8e08f70, 0x8f40f5a0, 0xb220dc10, 0x30704bc1, 0x0d106271, + 0x4ab018a1, 0x77d03111, 0xc5f0ed01, 0xf890c4b1, 0xbf30be61, + 0x825097d1, 0x60e09782, 0x5d80be32, 0x1a20c4e2, 0x2740ed52, + 0x95603142, 0xa80018f2, 0xefa06222, 0xd2c04b92, 0x5090dc43, + 0x6df0f5f3, 0x2a508f23, 0x1730a693, 0xa5107a83, 0x98705333, + 0xdfd029e3, 0xe2b00053, 0xc1c12f04, 0xfca106b4, 0xbb017c64, + 0x866155d4, 0x344189c4, 0x0921a074, 0x4e81daa4, 0x73e1f314, + 0xf1b164c5, 0xccd14d75, 0x8b7137a5, 0xb6111e15, 0x0431c205, + 0x3951ebb5, 0x7ef19165, 0x4391b8d5, 0xa121b886, 0x9c419136, + 0xdbe1ebe6, 0xe681c256, 0x54a11e46, 0x69c137f6, 0x2e614d26, + 0x13016496, 0x9151f347, 0xac31daf7, 0xeb91a027, 0xd6f18997, + 0x64d15587, 0x59b17c37, 0x1e1106e7, 0x23712f57, 0x58f35849, + 0x659371f9, 0x22330b29, 0x1f532299, 0xad73fe89, 0x9013d739, + 0xd7b3ade9, 0xead38459, 0x68831388, 0x55e33a38, 0x124340e8, + 0x2f236958, 0x9d03b548, 0xa0639cf8, 0xe7c3e628, 0xdaa3cf98, + 0x3813cfcb, 0x0573e67b, 0x42d39cab, 0x7fb3b51b, 0xcd93690b, + 0xf0f340bb, 0xb7533a6b, 0x8a3313db, 0x0863840a, 0x3503adba, + 0x72a3d76a, 0x4fc3feda, 0xfde322ca, 0xc0830b7a, 0x872371aa, + 0xba43581a, 0x9932774d, 0xa4525efd, 0xe3f2242d, 0xde920d9d, + 0x6cb2d18d, 0x51d2f83d, 0x167282ed, 0x2b12ab5d, 0xa9423c8c, + 0x9422153c, 0xd3826fec, 0xeee2465c, 0x5cc29a4c, 0x61a2b3fc, + 0x2602c92c, 0x1b62e09c, 0xf9d2e0cf, 0xc4b2c97f, 0x8312b3af, + 0xbe729a1f, 0x0c52460f, 0x31326fbf, 0x7692156f, 0x4bf23cdf, + 0xc9a2ab0e, 0xf4c282be, 0xb362f86e, 0x8e02d1de, 0x3c220dce, + 0x0142247e, 0x46e25eae, 0x7b82771e, 0xb1e6b092, 0x8c869922, + 0xcb26e3f2, 0xf646ca42, 0x44661652, 0x79063fe2, 0x3ea64532, + 0x03c66c82, 0x8196fb53, 0xbcf6d2e3, 0xfb56a833, 0xc6368183, + 0x74165d93, 0x49767423, 0x0ed60ef3, 0x33b62743, 0xd1062710, + 0xec660ea0, 0xabc67470, 0x96a65dc0, 0x248681d0, 0x19e6a860, + 0x5e46d2b0, 0x6326fb00, 0xe1766cd1, 0xdc164561, 0x9bb63fb1, + 0xa6d61601, 0x14f6ca11, 0x2996e3a1, 0x6e369971, 0x5356b0c1, + 0x70279f96, 0x4d47b626, 0x0ae7ccf6, 0x3787e546, 0x85a73956, + 0xb8c710e6, 0xff676a36, 0xc2074386, 0x4057d457, 0x7d37fde7, + 0x3a978737, 0x07f7ae87, 0xb5d77297, 0x88b75b27, 0xcf1721f7, + 0xf2770847, 0x10c70814, 0x2da721a4, 0x6a075b74, 0x576772c4, + 0xe547aed4, 0xd8278764, 0x9f87fdb4, 0xa2e7d404, 0x20b743d5, + 0x1dd76a65, 0x5a7710b5, 0x67173905, 0xd537e515, 0xe857cca5, + 0xaff7b675, 0x92979fc5, 0xe915e8db, 0xd475c16b, 0x93d5bbbb, + 0xaeb5920b, 0x1c954e1b, 0x21f567ab, 0x66551d7b, 0x5b3534cb, + 0xd965a31a, 0xe4058aaa, 0xa3a5f07a, 0x9ec5d9ca, 0x2ce505da, + 0x11852c6a, 0x562556ba, 0x6b457f0a, 0x89f57f59, 0xb49556e9, + 0xf3352c39, 0xce550589, 0x7c75d999, 0x4115f029, 0x06b58af9, + 0x3bd5a349, 0xb9853498, 0x84e51d28, 0xc34567f8, 0xfe254e48, + 0x4c059258, 0x7165bbe8, 0x36c5c138, 0x0ba5e888, 0x28d4c7df, + 0x15b4ee6f, 0x521494bf, 0x6f74bd0f, 0xdd54611f, 0xe03448af, + 0xa794327f, 0x9af41bcf, 0x18a48c1e, 0x25c4a5ae, 0x6264df7e, + 0x5f04f6ce, 0xed242ade, 0xd044036e, 0x97e479be, 0xaa84500e, + 0x4834505d, 0x755479ed, 0x32f4033d, 0x0f942a8d, 0xbdb4f69d, + 0x80d4df2d, 0xc774a5fd, 0xfa148c4d, 0x78441b9c, 0x4524322c, + 0x028448fc, 0x3fe4614c, 0x8dc4bd5c, 0xb0a494ec, 0xf704ee3c, + 0xca64c78c}}; + +local const z_word_t FAR crc_braid_big_table[][256] = { + {0x00000000, 0xb029603d, 0x6053c07a, 0xd07aa047, 0xc0a680f5, + 0x708fe0c8, 0xa0f5408f, 0x10dc20b2, 0xc14b7030, 0x7162100d, + 0xa118b04a, 0x1131d077, 0x01edf0c5, 0xb1c490f8, 0x61be30bf, + 0xd1975082, 0x8297e060, 0x32be805d, 0xe2c4201a, 0x52ed4027, + 0x42316095, 0xf21800a8, 0x2262a0ef, 0x924bc0d2, 0x43dc9050, + 0xf3f5f06d, 0x238f502a, 0x93a63017, 0x837a10a5, 0x33537098, + 0xe329d0df, 0x5300b0e2, 0x042fc1c1, 0xb406a1fc, 0x647c01bb, + 0xd4556186, 0xc4894134, 0x74a02109, 0xa4da814e, 0x14f3e173, + 0xc564b1f1, 0x754dd1cc, 0xa537718b, 0x151e11b6, 0x05c23104, + 0xb5eb5139, 0x6591f17e, 0xd5b89143, 0x86b821a1, 0x3691419c, + 0xe6ebe1db, 0x56c281e6, 0x461ea154, 0xf637c169, 0x264d612e, + 0x96640113, 0x47f35191, 0xf7da31ac, 0x27a091eb, 0x9789f1d6, + 0x8755d164, 0x377cb159, 0xe706111e, 0x572f7123, 0x4958f358, + 0xf9719365, 0x290b3322, 0x9922531f, 0x89fe73ad, 0x39d71390, + 0xe9adb3d7, 0x5984d3ea, 0x88138368, 0x383ae355, 0xe8404312, + 0x5869232f, 0x48b5039d, 0xf89c63a0, 0x28e6c3e7, 0x98cfa3da, + 0xcbcf1338, 0x7be67305, 0xab9cd342, 0x1bb5b37f, 0x0b6993cd, + 0xbb40f3f0, 0x6b3a53b7, 0xdb13338a, 0x0a846308, 0xbaad0335, + 0x6ad7a372, 0xdafec34f, 0xca22e3fd, 0x7a0b83c0, 0xaa712387, + 0x1a5843ba, 0x4d773299, 0xfd5e52a4, 0x2d24f2e3, 0x9d0d92de, + 0x8dd1b26c, 0x3df8d251, 0xed827216, 0x5dab122b, 0x8c3c42a9, + 0x3c152294, 0xec6f82d3, 0x5c46e2ee, 0x4c9ac25c, 0xfcb3a261, + 0x2cc90226, 0x9ce0621b, 0xcfe0d2f9, 0x7fc9b2c4, 0xafb31283, + 0x1f9a72be, 0x0f46520c, 0xbf6f3231, 0x6f159276, 0xdf3cf24b, + 0x0eaba2c9, 0xbe82c2f4, 0x6ef862b3, 0xded1028e, 0xce0d223c, + 0x7e244201, 0xae5ee246, 0x1e77827b, 0x92b0e6b1, 0x2299868c, + 0xf2e326cb, 0x42ca46f6, 0x52166644, 0xe23f0679, 0x3245a63e, + 0x826cc603, 0x53fb9681, 0xe3d2f6bc, 0x33a856fb, 0x838136c6, + 0x935d1674, 0x23747649, 0xf30ed60e, 0x4327b633, 0x102706d1, + 0xa00e66ec, 0x7074c6ab, 0xc05da696, 0xd0818624, 0x60a8e619, + 0xb0d2465e, 0x00fb2663, 0xd16c76e1, 0x614516dc, 0xb13fb69b, + 0x0116d6a6, 0x11caf614, 0xa1e39629, 0x7199366e, 0xc1b05653, + 0x969f2770, 0x26b6474d, 0xf6cce70a, 0x46e58737, 0x5639a785, + 0xe610c7b8, 0x366a67ff, 0x864307c2, 0x57d45740, 0xe7fd377d, + 0x3787973a, 0x87aef707, 0x9772d7b5, 0x275bb788, 0xf72117cf, + 0x470877f2, 0x1408c710, 0xa421a72d, 0x745b076a, 0xc4726757, + 0xd4ae47e5, 0x648727d8, 0xb4fd879f, 0x04d4e7a2, 0xd543b720, + 0x656ad71d, 0xb510775a, 0x05391767, 0x15e537d5, 0xa5cc57e8, + 0x75b6f7af, 0xc59f9792, 0xdbe815e9, 0x6bc175d4, 0xbbbbd593, + 0x0b92b5ae, 0x1b4e951c, 0xab67f521, 0x7b1d5566, 0xcb34355b, + 0x1aa365d9, 0xaa8a05e4, 0x7af0a5a3, 0xcad9c59e, 0xda05e52c, + 0x6a2c8511, 0xba562556, 0x0a7f456b, 0x597ff589, 0xe95695b4, + 0x392c35f3, 0x890555ce, 0x99d9757c, 0x29f01541, 0xf98ab506, + 0x49a3d53b, 0x983485b9, 0x281de584, 0xf86745c3, 0x484e25fe, + 0x5892054c, 0xe8bb6571, 0x38c1c536, 0x88e8a50b, 0xdfc7d428, + 0x6feeb415, 0xbf941452, 0x0fbd746f, 0x1f6154dd, 0xaf4834e0, + 0x7f3294a7, 0xcf1bf49a, 0x1e8ca418, 0xaea5c425, 0x7edf6462, + 0xcef6045f, 0xde2a24ed, 0x6e0344d0, 0xbe79e497, 0x0e5084aa, + 0x5d503448, 0xed795475, 0x3d03f432, 0x8d2a940f, 0x9df6b4bd, + 0x2ddfd480, 0xfda574c7, 0x4d8c14fa, 0x9c1b4478, 0x2c322445, + 0xfc488402, 0x4c61e43f, 0x5cbdc48d, 0xec94a4b0, 0x3cee04f7, + 0x8cc764ca}, + {0x00000000, 0xa5d35ccb, 0x0ba1c84d, 0xae729486, 0x1642919b, + 0xb391cd50, 0x1de359d6, 0xb830051d, 0x6d8253ec, 0xc8510f27, + 0x66239ba1, 0xc3f0c76a, 0x7bc0c277, 0xde139ebc, 0x70610a3a, + 0xd5b256f1, 0x9b02d603, 0x3ed18ac8, 0x90a31e4e, 0x35704285, + 0x8d404798, 0x28931b53, 0x86e18fd5, 0x2332d31e, 0xf68085ef, + 0x5353d924, 0xfd214da2, 0x58f21169, 0xe0c21474, 0x451148bf, + 0xeb63dc39, 0x4eb080f2, 0x3605ac07, 0x93d6f0cc, 0x3da4644a, + 0x98773881, 0x20473d9c, 0x85946157, 0x2be6f5d1, 0x8e35a91a, + 0x5b87ffeb, 0xfe54a320, 0x502637a6, 0xf5f56b6d, 0x4dc56e70, + 0xe81632bb, 0x4664a63d, 0xe3b7faf6, 0xad077a04, 0x08d426cf, + 0xa6a6b249, 0x0375ee82, 0xbb45eb9f, 0x1e96b754, 0xb0e423d2, + 0x15377f19, 0xc08529e8, 0x65567523, 0xcb24e1a5, 0x6ef7bd6e, + 0xd6c7b873, 0x7314e4b8, 0xdd66703e, 0x78b52cf5, 0x6c0a580f, + 0xc9d904c4, 0x67ab9042, 0xc278cc89, 0x7a48c994, 0xdf9b955f, + 0x71e901d9, 0xd43a5d12, 0x01880be3, 0xa45b5728, 0x0a29c3ae, + 0xaffa9f65, 0x17ca9a78, 0xb219c6b3, 0x1c6b5235, 0xb9b80efe, + 0xf7088e0c, 0x52dbd2c7, 0xfca94641, 0x597a1a8a, 0xe14a1f97, + 0x4499435c, 0xeaebd7da, 0x4f388b11, 0x9a8adde0, 0x3f59812b, + 0x912b15ad, 0x34f84966, 0x8cc84c7b, 0x291b10b0, 0x87698436, + 0x22bad8fd, 0x5a0ff408, 0xffdca8c3, 0x51ae3c45, 0xf47d608e, + 0x4c4d6593, 0xe99e3958, 0x47ecadde, 0xe23ff115, 0x378da7e4, + 0x925efb2f, 0x3c2c6fa9, 0x99ff3362, 0x21cf367f, 0x841c6ab4, + 0x2a6efe32, 0x8fbda2f9, 0xc10d220b, 0x64de7ec0, 0xcaacea46, + 0x6f7fb68d, 0xd74fb390, 0x729cef5b, 0xdcee7bdd, 0x793d2716, + 0xac8f71e7, 0x095c2d2c, 0xa72eb9aa, 0x02fde561, 0xbacde07c, + 0x1f1ebcb7, 0xb16c2831, 0x14bf74fa, 0xd814b01e, 0x7dc7ecd5, + 0xd3b57853, 0x76662498, 0xce562185, 0x6b857d4e, 0xc5f7e9c8, + 0x6024b503, 0xb596e3f2, 0x1045bf39, 0xbe372bbf, 0x1be47774, + 0xa3d47269, 0x06072ea2, 0xa875ba24, 0x0da6e6ef, 0x4316661d, + 0xe6c53ad6, 0x48b7ae50, 0xed64f29b, 0x5554f786, 0xf087ab4d, + 0x5ef53fcb, 0xfb266300, 0x2e9435f1, 0x8b47693a, 0x2535fdbc, + 0x80e6a177, 0x38d6a46a, 0x9d05f8a1, 0x33776c27, 0x96a430ec, + 0xee111c19, 0x4bc240d2, 0xe5b0d454, 0x4063889f, 0xf8538d82, + 0x5d80d149, 0xf3f245cf, 0x56211904, 0x83934ff5, 0x2640133e, + 0x883287b8, 0x2de1db73, 0x95d1de6e, 0x300282a5, 0x9e701623, + 0x3ba34ae8, 0x7513ca1a, 0xd0c096d1, 0x7eb20257, 0xdb615e9c, + 0x63515b81, 0xc682074a, 0x68f093cc, 0xcd23cf07, 0x189199f6, + 0xbd42c53d, 0x133051bb, 0xb6e30d70, 0x0ed3086d, 0xab0054a6, + 0x0572c020, 0xa0a19ceb, 0xb41ee811, 0x11cdb4da, 0xbfbf205c, + 0x1a6c7c97, 0xa25c798a, 0x078f2541, 0xa9fdb1c7, 0x0c2eed0c, + 0xd99cbbfd, 0x7c4fe736, 0xd23d73b0, 0x77ee2f7b, 0xcfde2a66, + 0x6a0d76ad, 0xc47fe22b, 0x61acbee0, 0x2f1c3e12, 0x8acf62d9, + 0x24bdf65f, 0x816eaa94, 0x395eaf89, 0x9c8df342, 0x32ff67c4, + 0x972c3b0f, 0x429e6dfe, 0xe74d3135, 0x493fa5b3, 0xececf978, + 0x54dcfc65, 0xf10fa0ae, 0x5f7d3428, 0xfaae68e3, 0x821b4416, + 0x27c818dd, 0x89ba8c5b, 0x2c69d090, 0x9459d58d, 0x318a8946, + 0x9ff81dc0, 0x3a2b410b, 0xef9917fa, 0x4a4a4b31, 0xe438dfb7, + 0x41eb837c, 0xf9db8661, 0x5c08daaa, 0xf27a4e2c, 0x57a912e7, + 0x19199215, 0xbccacede, 0x12b85a58, 0xb76b0693, 0x0f5b038e, + 0xaa885f45, 0x04facbc3, 0xa1299708, 0x749bc1f9, 0xd1489d32, + 0x7f3a09b4, 0xdae9557f, 0x62d95062, 0xc70a0ca9, 0x6978982f, + 0xccabc4e4}, + {0x00000000, 0xb40b77a6, 0x29119f97, 0x9d1ae831, 0x13244ff4, + 0xa72f3852, 0x3a35d063, 0x8e3ea7c5, 0x674eef33, 0xd3459895, + 0x4e5f70a4, 0xfa540702, 0x746aa0c7, 0xc061d761, 0x5d7b3f50, + 0xe97048f6, 0xce9cde67, 0x7a97a9c1, 0xe78d41f0, 0x53863656, + 0xddb89193, 0x69b3e635, 0xf4a90e04, 0x40a279a2, 0xa9d23154, + 0x1dd946f2, 0x80c3aec3, 0x34c8d965, 0xbaf67ea0, 0x0efd0906, + 0x93e7e137, 0x27ec9691, 0x9c39bdcf, 0x2832ca69, 0xb5282258, + 0x012355fe, 0x8f1df23b, 0x3b16859d, 0xa60c6dac, 0x12071a0a, + 0xfb7752fc, 0x4f7c255a, 0xd266cd6b, 0x666dbacd, 0xe8531d08, + 0x5c586aae, 0xc142829f, 0x7549f539, 0x52a563a8, 0xe6ae140e, + 0x7bb4fc3f, 0xcfbf8b99, 0x41812c5c, 0xf58a5bfa, 0x6890b3cb, + 0xdc9bc46d, 0x35eb8c9b, 0x81e0fb3d, 0x1cfa130c, 0xa8f164aa, + 0x26cfc36f, 0x92c4b4c9, 0x0fde5cf8, 0xbbd52b5e, 0x79750b44, + 0xcd7e7ce2, 0x506494d3, 0xe46fe375, 0x6a5144b0, 0xde5a3316, + 0x4340db27, 0xf74bac81, 0x1e3be477, 0xaa3093d1, 0x372a7be0, + 0x83210c46, 0x0d1fab83, 0xb914dc25, 0x240e3414, 0x900543b2, + 0xb7e9d523, 0x03e2a285, 0x9ef84ab4, 0x2af33d12, 0xa4cd9ad7, + 0x10c6ed71, 0x8ddc0540, 0x39d772e6, 0xd0a73a10, 0x64ac4db6, + 0xf9b6a587, 0x4dbdd221, 0xc38375e4, 0x77880242, 0xea92ea73, + 0x5e999dd5, 0xe54cb68b, 0x5147c12d, 0xcc5d291c, 0x78565eba, + 0xf668f97f, 0x42638ed9, 0xdf7966e8, 0x6b72114e, 0x820259b8, + 0x36092e1e, 0xab13c62f, 0x1f18b189, 0x9126164c, 0x252d61ea, + 0xb83789db, 0x0c3cfe7d, 0x2bd068ec, 0x9fdb1f4a, 0x02c1f77b, + 0xb6ca80dd, 0x38f42718, 0x8cff50be, 0x11e5b88f, 0xa5eecf29, + 0x4c9e87df, 0xf895f079, 0x658f1848, 0xd1846fee, 0x5fbac82b, + 0xebb1bf8d, 0x76ab57bc, 0xc2a0201a, 0xf2ea1688, 0x46e1612e, + 0xdbfb891f, 0x6ff0feb9, 0xe1ce597c, 0x55c52eda, 0xc8dfc6eb, + 0x7cd4b14d, 0x95a4f9bb, 0x21af8e1d, 0xbcb5662c, 0x08be118a, + 0x8680b64f, 0x328bc1e9, 0xaf9129d8, 0x1b9a5e7e, 0x3c76c8ef, + 0x887dbf49, 0x15675778, 0xa16c20de, 0x2f52871b, 0x9b59f0bd, + 0x0643188c, 0xb2486f2a, 0x5b3827dc, 0xef33507a, 0x7229b84b, + 0xc622cfed, 0x481c6828, 0xfc171f8e, 0x610df7bf, 0xd5068019, + 0x6ed3ab47, 0xdad8dce1, 0x47c234d0, 0xf3c94376, 0x7df7e4b3, + 0xc9fc9315, 0x54e67b24, 0xe0ed0c82, 0x099d4474, 0xbd9633d2, + 0x208cdbe3, 0x9487ac45, 0x1ab90b80, 0xaeb27c26, 0x33a89417, + 0x87a3e3b1, 0xa04f7520, 0x14440286, 0x895eeab7, 0x3d559d11, + 0xb36b3ad4, 0x07604d72, 0x9a7aa543, 0x2e71d2e5, 0xc7019a13, + 0x730aedb5, 0xee100584, 0x5a1b7222, 0xd425d5e7, 0x602ea241, + 0xfd344a70, 0x493f3dd6, 0x8b9f1dcc, 0x3f946a6a, 0xa28e825b, + 0x1685f5fd, 0x98bb5238, 0x2cb0259e, 0xb1aacdaf, 0x05a1ba09, + 0xecd1f2ff, 0x58da8559, 0xc5c06d68, 0x71cb1ace, 0xfff5bd0b, + 0x4bfecaad, 0xd6e4229c, 0x62ef553a, 0x4503c3ab, 0xf108b40d, + 0x6c125c3c, 0xd8192b9a, 0x56278c5f, 0xe22cfbf9, 0x7f3613c8, + 0xcb3d646e, 0x224d2c98, 0x96465b3e, 0x0b5cb30f, 0xbf57c4a9, + 0x3169636c, 0x856214ca, 0x1878fcfb, 0xac738b5d, 0x17a6a003, + 0xa3add7a5, 0x3eb73f94, 0x8abc4832, 0x0482eff7, 0xb0899851, + 0x2d937060, 0x999807c6, 0x70e84f30, 0xc4e33896, 0x59f9d0a7, + 0xedf2a701, 0x63cc00c4, 0xd7c77762, 0x4add9f53, 0xfed6e8f5, + 0xd93a7e64, 0x6d3109c2, 0xf02be1f3, 0x44209655, 0xca1e3190, + 0x7e154636, 0xe30fae07, 0x5704d9a1, 0xbe749157, 0x0a7fe6f1, + 0x97650ec0, 0x236e7966, 0xad50dea3, 0x195ba905, 0x84414134, + 0x304a3692}, + {0x00000000, 0x9e00aacc, 0x7d072542, 0xe3078f8e, 0xfa0e4a84, + 0x640ee048, 0x87096fc6, 0x1909c50a, 0xb51be5d3, 0x2b1b4f1f, + 0xc81cc091, 0x561c6a5d, 0x4f15af57, 0xd115059b, 0x32128a15, + 0xac1220d9, 0x2b31bb7c, 0xb53111b0, 0x56369e3e, 0xc83634f2, + 0xd13ff1f8, 0x4f3f5b34, 0xac38d4ba, 0x32387e76, 0x9e2a5eaf, + 0x002af463, 0xe32d7bed, 0x7d2dd121, 0x6424142b, 0xfa24bee7, + 0x19233169, 0x87239ba5, 0x566276f9, 0xc862dc35, 0x2b6553bb, + 0xb565f977, 0xac6c3c7d, 0x326c96b1, 0xd16b193f, 0x4f6bb3f3, + 0xe379932a, 0x7d7939e6, 0x9e7eb668, 0x007e1ca4, 0x1977d9ae, + 0x87777362, 0x6470fcec, 0xfa705620, 0x7d53cd85, 0xe3536749, + 0x0054e8c7, 0x9e54420b, 0x875d8701, 0x195d2dcd, 0xfa5aa243, + 0x645a088f, 0xc8482856, 0x5648829a, 0xb54f0d14, 0x2b4fa7d8, + 0x324662d2, 0xac46c81e, 0x4f414790, 0xd141ed5c, 0xedc29d29, + 0x73c237e5, 0x90c5b86b, 0x0ec512a7, 0x17ccd7ad, 0x89cc7d61, + 0x6acbf2ef, 0xf4cb5823, 0x58d978fa, 0xc6d9d236, 0x25de5db8, + 0xbbdef774, 0xa2d7327e, 0x3cd798b2, 0xdfd0173c, 0x41d0bdf0, + 0xc6f32655, 0x58f38c99, 0xbbf40317, 0x25f4a9db, 0x3cfd6cd1, + 0xa2fdc61d, 0x41fa4993, 0xdffae35f, 0x73e8c386, 0xede8694a, + 0x0eefe6c4, 0x90ef4c08, 0x89e68902, 0x17e623ce, 0xf4e1ac40, + 0x6ae1068c, 0xbba0ebd0, 0x25a0411c, 0xc6a7ce92, 0x58a7645e, + 0x41aea154, 0xdfae0b98, 0x3ca98416, 0xa2a92eda, 0x0ebb0e03, + 0x90bba4cf, 0x73bc2b41, 0xedbc818d, 0xf4b54487, 0x6ab5ee4b, + 0x89b261c5, 0x17b2cb09, 0x909150ac, 0x0e91fa60, 0xed9675ee, + 0x7396df22, 0x6a9f1a28, 0xf49fb0e4, 0x17983f6a, 0x899895a6, + 0x258ab57f, 0xbb8a1fb3, 0x588d903d, 0xc68d3af1, 0xdf84fffb, + 0x41845537, 0xa283dab9, 0x3c837075, 0xda853b53, 0x4485919f, + 0xa7821e11, 0x3982b4dd, 0x208b71d7, 0xbe8bdb1b, 0x5d8c5495, + 0xc38cfe59, 0x6f9ede80, 0xf19e744c, 0x1299fbc2, 0x8c99510e, + 0x95909404, 0x0b903ec8, 0xe897b146, 0x76971b8a, 0xf1b4802f, + 0x6fb42ae3, 0x8cb3a56d, 0x12b30fa1, 0x0bbacaab, 0x95ba6067, + 0x76bdefe9, 0xe8bd4525, 0x44af65fc, 0xdaafcf30, 0x39a840be, + 0xa7a8ea72, 0xbea12f78, 0x20a185b4, 0xc3a60a3a, 0x5da6a0f6, + 0x8ce74daa, 0x12e7e766, 0xf1e068e8, 0x6fe0c224, 0x76e9072e, + 0xe8e9ade2, 0x0bee226c, 0x95ee88a0, 0x39fca879, 0xa7fc02b5, + 0x44fb8d3b, 0xdafb27f7, 0xc3f2e2fd, 0x5df24831, 0xbef5c7bf, + 0x20f56d73, 0xa7d6f6d6, 0x39d65c1a, 0xdad1d394, 0x44d17958, + 0x5dd8bc52, 0xc3d8169e, 0x20df9910, 0xbedf33dc, 0x12cd1305, + 0x8ccdb9c9, 0x6fca3647, 0xf1ca9c8b, 0xe8c35981, 0x76c3f34d, + 0x95c47cc3, 0x0bc4d60f, 0x3747a67a, 0xa9470cb6, 0x4a408338, + 0xd44029f4, 0xcd49ecfe, 0x53494632, 0xb04ec9bc, 0x2e4e6370, + 0x825c43a9, 0x1c5ce965, 0xff5b66eb, 0x615bcc27, 0x7852092d, + 0xe652a3e1, 0x05552c6f, 0x9b5586a3, 0x1c761d06, 0x8276b7ca, + 0x61713844, 0xff719288, 0xe6785782, 0x7878fd4e, 0x9b7f72c0, + 0x057fd80c, 0xa96df8d5, 0x376d5219, 0xd46add97, 0x4a6a775b, + 0x5363b251, 0xcd63189d, 0x2e649713, 0xb0643ddf, 0x6125d083, + 0xff257a4f, 0x1c22f5c1, 0x82225f0d, 0x9b2b9a07, 0x052b30cb, + 0xe62cbf45, 0x782c1589, 0xd43e3550, 0x4a3e9f9c, 0xa9391012, + 0x3739bade, 0x2e307fd4, 0xb030d518, 0x53375a96, 0xcd37f05a, + 0x4a146bff, 0xd414c133, 0x37134ebd, 0xa913e471, 0xb01a217b, + 0x2e1a8bb7, 0xcd1d0439, 0x531daef5, 0xff0f8e2c, 0x610f24e0, + 0x8208ab6e, 0x1c0801a2, 0x0501c4a8, 0x9b016e64, 0x7806e1ea, + 0xe6064b26}}; + #endif - } -}; + +#endif + +#if N == 3 + +#if W == 8 + +local const z_crc_t FAR crc_braid_table[][256] = { + {0x00000000, 0x81256527, 0xd93bcc0f, 0x581ea928, 0x69069e5f, + 0xe823fb78, 0xb03d5250, 0x31183777, 0xd20d3cbe, 0x53285999, + 0x0b36f0b1, 0x8a139596, 0xbb0ba2e1, 0x3a2ec7c6, 0x62306eee, + 0xe3150bc9, 0x7f6b7f3d, 0xfe4e1a1a, 0xa650b332, 0x2775d615, + 0x166de162, 0x97488445, 0xcf562d6d, 0x4e73484a, 0xad664383, + 0x2c4326a4, 0x745d8f8c, 0xf578eaab, 0xc460dddc, 0x4545b8fb, + 0x1d5b11d3, 0x9c7e74f4, 0xfed6fe7a, 0x7ff39b5d, 0x27ed3275, + 0xa6c85752, 0x97d06025, 0x16f50502, 0x4eebac2a, 0xcfcec90d, + 0x2cdbc2c4, 0xadfea7e3, 0xf5e00ecb, 0x74c56bec, 0x45dd5c9b, + 0xc4f839bc, 0x9ce69094, 0x1dc3f5b3, 0x81bd8147, 0x0098e460, + 0x58864d48, 0xd9a3286f, 0xe8bb1f18, 0x699e7a3f, 0x3180d317, + 0xb0a5b630, 0x53b0bdf9, 0xd295d8de, 0x8a8b71f6, 0x0bae14d1, + 0x3ab623a6, 0xbb934681, 0xe38defa9, 0x62a88a8e, 0x26dcfab5, + 0xa7f99f92, 0xffe736ba, 0x7ec2539d, 0x4fda64ea, 0xceff01cd, + 0x96e1a8e5, 0x17c4cdc2, 0xf4d1c60b, 0x75f4a32c, 0x2dea0a04, + 0xaccf6f23, 0x9dd75854, 0x1cf23d73, 0x44ec945b, 0xc5c9f17c, + 0x59b78588, 0xd892e0af, 0x808c4987, 0x01a92ca0, 0x30b11bd7, + 0xb1947ef0, 0xe98ad7d8, 0x68afb2ff, 0x8bbab936, 0x0a9fdc11, + 0x52817539, 0xd3a4101e, 0xe2bc2769, 0x6399424e, 0x3b87eb66, + 0xbaa28e41, 0xd80a04cf, 0x592f61e8, 0x0131c8c0, 0x8014ade7, + 0xb10c9a90, 0x3029ffb7, 0x6837569f, 0xe91233b8, 0x0a073871, + 0x8b225d56, 0xd33cf47e, 0x52199159, 0x6301a62e, 0xe224c309, + 0xba3a6a21, 0x3b1f0f06, 0xa7617bf2, 0x26441ed5, 0x7e5ab7fd, + 0xff7fd2da, 0xce67e5ad, 0x4f42808a, 0x175c29a2, 0x96794c85, + 0x756c474c, 0xf449226b, 0xac578b43, 0x2d72ee64, 0x1c6ad913, + 0x9d4fbc34, 0xc551151c, 0x4474703b, 0x4db9f56a, 0xcc9c904d, + 0x94823965, 0x15a75c42, 0x24bf6b35, 0xa59a0e12, 0xfd84a73a, + 0x7ca1c21d, 0x9fb4c9d4, 0x1e91acf3, 0x468f05db, 0xc7aa60fc, + 0xf6b2578b, 0x779732ac, 0x2f899b84, 0xaeacfea3, 0x32d28a57, + 0xb3f7ef70, 0xebe94658, 0x6acc237f, 0x5bd41408, 0xdaf1712f, + 0x82efd807, 0x03cabd20, 0xe0dfb6e9, 0x61fad3ce, 0x39e47ae6, + 0xb8c11fc1, 0x89d928b6, 0x08fc4d91, 0x50e2e4b9, 0xd1c7819e, + 0xb36f0b10, 0x324a6e37, 0x6a54c71f, 0xeb71a238, 0xda69954f, + 0x5b4cf068, 0x03525940, 0x82773c67, 0x616237ae, 0xe0475289, + 0xb859fba1, 0x397c9e86, 0x0864a9f1, 0x8941ccd6, 0xd15f65fe, + 0x507a00d9, 0xcc04742d, 0x4d21110a, 0x153fb822, 0x941add05, + 0xa502ea72, 0x24278f55, 0x7c39267d, 0xfd1c435a, 0x1e094893, + 0x9f2c2db4, 0xc732849c, 0x4617e1bb, 0x770fd6cc, 0xf62ab3eb, + 0xae341ac3, 0x2f117fe4, 0x6b650fdf, 0xea406af8, 0xb25ec3d0, + 0x337ba6f7, 0x02639180, 0x8346f4a7, 0xdb585d8f, 0x5a7d38a8, + 0xb9683361, 0x384d5646, 0x6053ff6e, 0xe1769a49, 0xd06ead3e, + 0x514bc819, 0x09556131, 0x88700416, 0x140e70e2, 0x952b15c5, + 0xcd35bced, 0x4c10d9ca, 0x7d08eebd, 0xfc2d8b9a, 0xa43322b2, + 0x25164795, 0xc6034c5c, 0x4726297b, 0x1f388053, 0x9e1de574, + 0xaf05d203, 0x2e20b724, 0x763e1e0c, 0xf71b7b2b, 0x95b3f1a5, + 0x14969482, 0x4c883daa, 0xcdad588d, 0xfcb56ffa, 0x7d900add, + 0x258ea3f5, 0xa4abc6d2, 0x47becd1b, 0xc69ba83c, 0x9e850114, + 0x1fa06433, 0x2eb85344, 0xaf9d3663, 0xf7839f4b, 0x76a6fa6c, + 0xead88e98, 0x6bfdebbf, 0x33e34297, 0xb2c627b0, 0x83de10c7, + 0x02fb75e0, 0x5ae5dcc8, 0xdbc0b9ef, 0x38d5b226, 0xb9f0d701, + 0xe1ee7e29, 0x60cb1b0e, 0x51d32c79, 0xd0f6495e, 0x88e8e076, + 0x09cd8551}, + {0x00000000, 0x9b73ead4, 0xed96d3e9, 0x76e5393d, 0x005ca193, + 0x9b2f4b47, 0xedca727a, 0x76b998ae, 0x00b94326, 0x9bcaa9f2, + 0xed2f90cf, 0x765c7a1b, 0x00e5e2b5, 0x9b960861, 0xed73315c, + 0x7600db88, 0x0172864c, 0x9a016c98, 0xece455a5, 0x7797bf71, + 0x012e27df, 0x9a5dcd0b, 0xecb8f436, 0x77cb1ee2, 0x01cbc56a, + 0x9ab82fbe, 0xec5d1683, 0x772efc57, 0x019764f9, 0x9ae48e2d, + 0xec01b710, 0x77725dc4, 0x02e50c98, 0x9996e64c, 0xef73df71, + 0x740035a5, 0x02b9ad0b, 0x99ca47df, 0xef2f7ee2, 0x745c9436, + 0x025c4fbe, 0x992fa56a, 0xefca9c57, 0x74b97683, 0x0200ee2d, + 0x997304f9, 0xef963dc4, 0x74e5d710, 0x03978ad4, 0x98e46000, + 0xee01593d, 0x7572b3e9, 0x03cb2b47, 0x98b8c193, 0xee5df8ae, + 0x752e127a, 0x032ec9f2, 0x985d2326, 0xeeb81a1b, 0x75cbf0cf, + 0x03726861, 0x980182b5, 0xeee4bb88, 0x7597515c, 0x05ca1930, + 0x9eb9f3e4, 0xe85ccad9, 0x732f200d, 0x0596b8a3, 0x9ee55277, + 0xe8006b4a, 0x7373819e, 0x05735a16, 0x9e00b0c2, 0xe8e589ff, + 0x7396632b, 0x052ffb85, 0x9e5c1151, 0xe8b9286c, 0x73cac2b8, + 0x04b89f7c, 0x9fcb75a8, 0xe92e4c95, 0x725da641, 0x04e43eef, + 0x9f97d43b, 0xe972ed06, 0x720107d2, 0x0401dc5a, 0x9f72368e, + 0xe9970fb3, 0x72e4e567, 0x045d7dc9, 0x9f2e971d, 0xe9cbae20, + 0x72b844f4, 0x072f15a8, 0x9c5cff7c, 0xeab9c641, 0x71ca2c95, + 0x0773b43b, 0x9c005eef, 0xeae567d2, 0x71968d06, 0x0796568e, + 0x9ce5bc5a, 0xea008567, 0x71736fb3, 0x07caf71d, 0x9cb91dc9, + 0xea5c24f4, 0x712fce20, 0x065d93e4, 0x9d2e7930, 0xebcb400d, + 0x70b8aad9, 0x06013277, 0x9d72d8a3, 0xeb97e19e, 0x70e40b4a, + 0x06e4d0c2, 0x9d973a16, 0xeb72032b, 0x7001e9ff, 0x06b87151, + 0x9dcb9b85, 0xeb2ea2b8, 0x705d486c, 0x0b943260, 0x90e7d8b4, + 0xe602e189, 0x7d710b5d, 0x0bc893f3, 0x90bb7927, 0xe65e401a, + 0x7d2daace, 0x0b2d7146, 0x905e9b92, 0xe6bba2af, 0x7dc8487b, + 0x0b71d0d5, 0x90023a01, 0xe6e7033c, 0x7d94e9e8, 0x0ae6b42c, + 0x91955ef8, 0xe77067c5, 0x7c038d11, 0x0aba15bf, 0x91c9ff6b, + 0xe72cc656, 0x7c5f2c82, 0x0a5ff70a, 0x912c1dde, 0xe7c924e3, + 0x7cbace37, 0x0a035699, 0x9170bc4d, 0xe7958570, 0x7ce66fa4, + 0x09713ef8, 0x9202d42c, 0xe4e7ed11, 0x7f9407c5, 0x092d9f6b, + 0x925e75bf, 0xe4bb4c82, 0x7fc8a656, 0x09c87dde, 0x92bb970a, + 0xe45eae37, 0x7f2d44e3, 0x0994dc4d, 0x92e73699, 0xe4020fa4, + 0x7f71e570, 0x0803b8b4, 0x93705260, 0xe5956b5d, 0x7ee68189, + 0x085f1927, 0x932cf3f3, 0xe5c9cace, 0x7eba201a, 0x08bafb92, + 0x93c91146, 0xe52c287b, 0x7e5fc2af, 0x08e65a01, 0x9395b0d5, + 0xe57089e8, 0x7e03633c, 0x0e5e2b50, 0x952dc184, 0xe3c8f8b9, + 0x78bb126d, 0x0e028ac3, 0x95716017, 0xe394592a, 0x78e7b3fe, + 0x0ee76876, 0x959482a2, 0xe371bb9f, 0x7802514b, 0x0ebbc9e5, + 0x95c82331, 0xe32d1a0c, 0x785ef0d8, 0x0f2cad1c, 0x945f47c8, + 0xe2ba7ef5, 0x79c99421, 0x0f700c8f, 0x9403e65b, 0xe2e6df66, + 0x799535b2, 0x0f95ee3a, 0x94e604ee, 0xe2033dd3, 0x7970d707, + 0x0fc94fa9, 0x94baa57d, 0xe25f9c40, 0x792c7694, 0x0cbb27c8, + 0x97c8cd1c, 0xe12df421, 0x7a5e1ef5, 0x0ce7865b, 0x97946c8f, + 0xe17155b2, 0x7a02bf66, 0x0c0264ee, 0x97718e3a, 0xe194b707, + 0x7ae75dd3, 0x0c5ec57d, 0x972d2fa9, 0xe1c81694, 0x7abbfc40, + 0x0dc9a184, 0x96ba4b50, 0xe05f726d, 0x7b2c98b9, 0x0d950017, + 0x96e6eac3, 0xe003d3fe, 0x7b70392a, 0x0d70e2a2, 0x96030876, + 0xe0e6314b, 0x7b95db9f, 0x0d2c4331, 0x965fa9e5, 0xe0ba90d8, + 0x7bc97a0c}, + {0x00000000, 0x172864c0, 0x2e50c980, 0x3978ad40, 0x5ca19300, + 0x4b89f7c0, 0x72f15a80, 0x65d93e40, 0xb9432600, 0xae6b42c0, + 0x9713ef80, 0x803b8b40, 0xe5e2b500, 0xf2cad1c0, 0xcbb27c80, + 0xdc9a1840, 0xa9f74a41, 0xbedf2e81, 0x87a783c1, 0x908fe701, + 0xf556d941, 0xe27ebd81, 0xdb0610c1, 0xcc2e7401, 0x10b46c41, + 0x079c0881, 0x3ee4a5c1, 0x29ccc101, 0x4c15ff41, 0x5b3d9b81, + 0x624536c1, 0x756d5201, 0x889f92c3, 0x9fb7f603, 0xa6cf5b43, + 0xb1e73f83, 0xd43e01c3, 0xc3166503, 0xfa6ec843, 0xed46ac83, + 0x31dcb4c3, 0x26f4d003, 0x1f8c7d43, 0x08a41983, 0x6d7d27c3, + 0x7a554303, 0x432dee43, 0x54058a83, 0x2168d882, 0x3640bc42, + 0x0f381102, 0x181075c2, 0x7dc94b82, 0x6ae12f42, 0x53998202, + 0x44b1e6c2, 0x982bfe82, 0x8f039a42, 0xb67b3702, 0xa15353c2, + 0xc48a6d82, 0xd3a20942, 0xeadaa402, 0xfdf2c0c2, 0xca4e23c7, + 0xdd664707, 0xe41eea47, 0xf3368e87, 0x96efb0c7, 0x81c7d407, + 0xb8bf7947, 0xaf971d87, 0x730d05c7, 0x64256107, 0x5d5dcc47, + 0x4a75a887, 0x2fac96c7, 0x3884f207, 0x01fc5f47, 0x16d43b87, + 0x63b96986, 0x74910d46, 0x4de9a006, 0x5ac1c4c6, 0x3f18fa86, + 0x28309e46, 0x11483306, 0x066057c6, 0xdafa4f86, 0xcdd22b46, + 0xf4aa8606, 0xe382e2c6, 0x865bdc86, 0x9173b846, 0xa80b1506, + 0xbf2371c6, 0x42d1b104, 0x55f9d5c4, 0x6c817884, 0x7ba91c44, + 0x1e702204, 0x095846c4, 0x3020eb84, 0x27088f44, 0xfb929704, + 0xecbaf3c4, 0xd5c25e84, 0xc2ea3a44, 0xa7330404, 0xb01b60c4, + 0x8963cd84, 0x9e4ba944, 0xeb26fb45, 0xfc0e9f85, 0xc57632c5, + 0xd25e5605, 0xb7876845, 0xa0af0c85, 0x99d7a1c5, 0x8effc505, + 0x5265dd45, 0x454db985, 0x7c3514c5, 0x6b1d7005, 0x0ec44e45, + 0x19ec2a85, 0x209487c5, 0x37bce305, 0x4fed41cf, 0x58c5250f, + 0x61bd884f, 0x7695ec8f, 0x134cd2cf, 0x0464b60f, 0x3d1c1b4f, + 0x2a347f8f, 0xf6ae67cf, 0xe186030f, 0xd8feae4f, 0xcfd6ca8f, + 0xaa0ff4cf, 0xbd27900f, 0x845f3d4f, 0x9377598f, 0xe61a0b8e, + 0xf1326f4e, 0xc84ac20e, 0xdf62a6ce, 0xbabb988e, 0xad93fc4e, + 0x94eb510e, 0x83c335ce, 0x5f592d8e, 0x4871494e, 0x7109e40e, + 0x662180ce, 0x03f8be8e, 0x14d0da4e, 0x2da8770e, 0x3a8013ce, + 0xc772d30c, 0xd05ab7cc, 0xe9221a8c, 0xfe0a7e4c, 0x9bd3400c, + 0x8cfb24cc, 0xb583898c, 0xa2abed4c, 0x7e31f50c, 0x691991cc, + 0x50613c8c, 0x4749584c, 0x2290660c, 0x35b802cc, 0x0cc0af8c, + 0x1be8cb4c, 0x6e85994d, 0x79adfd8d, 0x40d550cd, 0x57fd340d, + 0x32240a4d, 0x250c6e8d, 0x1c74c3cd, 0x0b5ca70d, 0xd7c6bf4d, + 0xc0eedb8d, 0xf99676cd, 0xeebe120d, 0x8b672c4d, 0x9c4f488d, + 0xa537e5cd, 0xb21f810d, 0x85a36208, 0x928b06c8, 0xabf3ab88, + 0xbcdbcf48, 0xd902f108, 0xce2a95c8, 0xf7523888, 0xe07a5c48, + 0x3ce04408, 0x2bc820c8, 0x12b08d88, 0x0598e948, 0x6041d708, + 0x7769b3c8, 0x4e111e88, 0x59397a48, 0x2c542849, 0x3b7c4c89, + 0x0204e1c9, 0x152c8509, 0x70f5bb49, 0x67dddf89, 0x5ea572c9, + 0x498d1609, 0x95170e49, 0x823f6a89, 0xbb47c7c9, 0xac6fa309, + 0xc9b69d49, 0xde9ef989, 0xe7e654c9, 0xf0ce3009, 0x0d3cf0cb, + 0x1a14940b, 0x236c394b, 0x34445d8b, 0x519d63cb, 0x46b5070b, + 0x7fcdaa4b, 0x68e5ce8b, 0xb47fd6cb, 0xa357b20b, 0x9a2f1f4b, + 0x8d077b8b, 0xe8de45cb, 0xfff6210b, 0xc68e8c4b, 0xd1a6e88b, + 0xa4cbba8a, 0xb3e3de4a, 0x8a9b730a, 0x9db317ca, 0xf86a298a, + 0xef424d4a, 0xd63ae00a, 0xc11284ca, 0x1d889c8a, 0x0aa0f84a, + 0x33d8550a, 0x24f031ca, 0x41290f8a, 0x56016b4a, 0x6f79c60a, + 0x7851a2ca}, + {0x00000000, 0x9fda839e, 0xe4c4017d, 0x7b1e82e3, 0x12f904bb, + 0x8d238725, 0xf63d05c6, 0x69e78658, 0x25f20976, 0xba288ae8, + 0xc136080b, 0x5eec8b95, 0x370b0dcd, 0xa8d18e53, 0xd3cf0cb0, + 0x4c158f2e, 0x4be412ec, 0xd43e9172, 0xaf201391, 0x30fa900f, + 0x591d1657, 0xc6c795c9, 0xbdd9172a, 0x220394b4, 0x6e161b9a, + 0xf1cc9804, 0x8ad21ae7, 0x15089979, 0x7cef1f21, 0xe3359cbf, + 0x982b1e5c, 0x07f19dc2, 0x97c825d8, 0x0812a646, 0x730c24a5, + 0xecd6a73b, 0x85312163, 0x1aeba2fd, 0x61f5201e, 0xfe2fa380, + 0xb23a2cae, 0x2de0af30, 0x56fe2dd3, 0xc924ae4d, 0xa0c32815, + 0x3f19ab8b, 0x44072968, 0xdbddaaf6, 0xdc2c3734, 0x43f6b4aa, + 0x38e83649, 0xa732b5d7, 0xced5338f, 0x510fb011, 0x2a1132f2, + 0xb5cbb16c, 0xf9de3e42, 0x6604bddc, 0x1d1a3f3f, 0x82c0bca1, + 0xeb273af9, 0x74fdb967, 0x0fe33b84, 0x9039b81a, 0xf4e14df1, + 0x6b3bce6f, 0x10254c8c, 0x8fffcf12, 0xe618494a, 0x79c2cad4, + 0x02dc4837, 0x9d06cba9, 0xd1134487, 0x4ec9c719, 0x35d745fa, + 0xaa0dc664, 0xc3ea403c, 0x5c30c3a2, 0x272e4141, 0xb8f4c2df, + 0xbf055f1d, 0x20dfdc83, 0x5bc15e60, 0xc41bddfe, 0xadfc5ba6, + 0x3226d838, 0x49385adb, 0xd6e2d945, 0x9af7566b, 0x052dd5f5, + 0x7e335716, 0xe1e9d488, 0x880e52d0, 0x17d4d14e, 0x6cca53ad, + 0xf310d033, 0x63296829, 0xfcf3ebb7, 0x87ed6954, 0x1837eaca, + 0x71d06c92, 0xee0aef0c, 0x95146def, 0x0aceee71, 0x46db615f, + 0xd901e2c1, 0xa21f6022, 0x3dc5e3bc, 0x542265e4, 0xcbf8e67a, + 0xb0e66499, 0x2f3ce707, 0x28cd7ac5, 0xb717f95b, 0xcc097bb8, + 0x53d3f826, 0x3a347e7e, 0xa5eefde0, 0xdef07f03, 0x412afc9d, + 0x0d3f73b3, 0x92e5f02d, 0xe9fb72ce, 0x7621f150, 0x1fc67708, + 0x801cf496, 0xfb027675, 0x64d8f5eb, 0x32b39da3, 0xad691e3d, + 0xd6779cde, 0x49ad1f40, 0x204a9918, 0xbf901a86, 0xc48e9865, + 0x5b541bfb, 0x174194d5, 0x889b174b, 0xf38595a8, 0x6c5f1636, + 0x05b8906e, 0x9a6213f0, 0xe17c9113, 0x7ea6128d, 0x79578f4f, + 0xe68d0cd1, 0x9d938e32, 0x02490dac, 0x6bae8bf4, 0xf474086a, + 0x8f6a8a89, 0x10b00917, 0x5ca58639, 0xc37f05a7, 0xb8618744, + 0x27bb04da, 0x4e5c8282, 0xd186011c, 0xaa9883ff, 0x35420061, + 0xa57bb87b, 0x3aa13be5, 0x41bfb906, 0xde653a98, 0xb782bcc0, + 0x28583f5e, 0x5346bdbd, 0xcc9c3e23, 0x8089b10d, 0x1f533293, + 0x644db070, 0xfb9733ee, 0x9270b5b6, 0x0daa3628, 0x76b4b4cb, + 0xe96e3755, 0xee9faa97, 0x71452909, 0x0a5babea, 0x95812874, + 0xfc66ae2c, 0x63bc2db2, 0x18a2af51, 0x87782ccf, 0xcb6da3e1, + 0x54b7207f, 0x2fa9a29c, 0xb0732102, 0xd994a75a, 0x464e24c4, + 0x3d50a627, 0xa28a25b9, 0xc652d052, 0x598853cc, 0x2296d12f, + 0xbd4c52b1, 0xd4abd4e9, 0x4b715777, 0x306fd594, 0xafb5560a, + 0xe3a0d924, 0x7c7a5aba, 0x0764d859, 0x98be5bc7, 0xf159dd9f, + 0x6e835e01, 0x159ddce2, 0x8a475f7c, 0x8db6c2be, 0x126c4120, + 0x6972c3c3, 0xf6a8405d, 0x9f4fc605, 0x0095459b, 0x7b8bc778, + 0xe45144e6, 0xa844cbc8, 0x379e4856, 0x4c80cab5, 0xd35a492b, + 0xbabdcf73, 0x25674ced, 0x5e79ce0e, 0xc1a34d90, 0x519af58a, + 0xce407614, 0xb55ef4f7, 0x2a847769, 0x4363f131, 0xdcb972af, + 0xa7a7f04c, 0x387d73d2, 0x7468fcfc, 0xebb27f62, 0x90acfd81, + 0x0f767e1f, 0x6691f847, 0xf94b7bd9, 0x8255f93a, 0x1d8f7aa4, + 0x1a7ee766, 0x85a464f8, 0xfebae61b, 0x61606585, 0x0887e3dd, + 0x975d6043, 0xec43e2a0, 0x7399613e, 0x3f8cee10, 0xa0566d8e, + 0xdb48ef6d, 0x44926cf3, 0x2d75eaab, 0xb2af6935, 0xc9b1ebd6, + 0x566b6848}, + {0x00000000, 0x65673b46, 0xcace768c, 0xafa94dca, 0x4eedeb59, + 0x2b8ad01f, 0x84239dd5, 0xe144a693, 0x9ddbd6b2, 0xf8bcedf4, + 0x5715a03e, 0x32729b78, 0xd3363deb, 0xb65106ad, 0x19f84b67, + 0x7c9f7021, 0xe0c6ab25, 0x85a19063, 0x2a08dda9, 0x4f6fe6ef, + 0xae2b407c, 0xcb4c7b3a, 0x64e536f0, 0x01820db6, 0x7d1d7d97, + 0x187a46d1, 0xb7d30b1b, 0xd2b4305d, 0x33f096ce, 0x5697ad88, + 0xf93ee042, 0x9c59db04, 0x1afc500b, 0x7f9b6b4d, 0xd0322687, + 0xb5551dc1, 0x5411bb52, 0x31768014, 0x9edfcdde, 0xfbb8f698, + 0x872786b9, 0xe240bdff, 0x4de9f035, 0x288ecb73, 0xc9ca6de0, + 0xacad56a6, 0x03041b6c, 0x6663202a, 0xfa3afb2e, 0x9f5dc068, + 0x30f48da2, 0x5593b6e4, 0xb4d71077, 0xd1b02b31, 0x7e1966fb, + 0x1b7e5dbd, 0x67e12d9c, 0x028616da, 0xad2f5b10, 0xc8486056, + 0x290cc6c5, 0x4c6bfd83, 0xe3c2b049, 0x86a58b0f, 0x35f8a016, + 0x509f9b50, 0xff36d69a, 0x9a51eddc, 0x7b154b4f, 0x1e727009, + 0xb1db3dc3, 0xd4bc0685, 0xa82376a4, 0xcd444de2, 0x62ed0028, + 0x078a3b6e, 0xe6ce9dfd, 0x83a9a6bb, 0x2c00eb71, 0x4967d037, + 0xd53e0b33, 0xb0593075, 0x1ff07dbf, 0x7a9746f9, 0x9bd3e06a, + 0xfeb4db2c, 0x511d96e6, 0x347aada0, 0x48e5dd81, 0x2d82e6c7, + 0x822bab0d, 0xe74c904b, 0x060836d8, 0x636f0d9e, 0xccc64054, + 0xa9a17b12, 0x2f04f01d, 0x4a63cb5b, 0xe5ca8691, 0x80adbdd7, + 0x61e91b44, 0x048e2002, 0xab276dc8, 0xce40568e, 0xb2df26af, + 0xd7b81de9, 0x78115023, 0x1d766b65, 0xfc32cdf6, 0x9955f6b0, + 0x36fcbb7a, 0x539b803c, 0xcfc25b38, 0xaaa5607e, 0x050c2db4, + 0x606b16f2, 0x812fb061, 0xe4488b27, 0x4be1c6ed, 0x2e86fdab, + 0x52198d8a, 0x377eb6cc, 0x98d7fb06, 0xfdb0c040, 0x1cf466d3, + 0x79935d95, 0xd63a105f, 0xb35d2b19, 0x6bf1402c, 0x0e967b6a, + 0xa13f36a0, 0xc4580de6, 0x251cab75, 0x407b9033, 0xefd2ddf9, + 0x8ab5e6bf, 0xf62a969e, 0x934dadd8, 0x3ce4e012, 0x5983db54, + 0xb8c77dc7, 0xdda04681, 0x72090b4b, 0x176e300d, 0x8b37eb09, + 0xee50d04f, 0x41f99d85, 0x249ea6c3, 0xc5da0050, 0xa0bd3b16, + 0x0f1476dc, 0x6a734d9a, 0x16ec3dbb, 0x738b06fd, 0xdc224b37, + 0xb9457071, 0x5801d6e2, 0x3d66eda4, 0x92cfa06e, 0xf7a89b28, + 0x710d1027, 0x146a2b61, 0xbbc366ab, 0xdea45ded, 0x3fe0fb7e, + 0x5a87c038, 0xf52e8df2, 0x9049b6b4, 0xecd6c695, 0x89b1fdd3, + 0x2618b019, 0x437f8b5f, 0xa23b2dcc, 0xc75c168a, 0x68f55b40, + 0x0d926006, 0x91cbbb02, 0xf4ac8044, 0x5b05cd8e, 0x3e62f6c8, + 0xdf26505b, 0xba416b1d, 0x15e826d7, 0x708f1d91, 0x0c106db0, + 0x697756f6, 0xc6de1b3c, 0xa3b9207a, 0x42fd86e9, 0x279abdaf, + 0x8833f065, 0xed54cb23, 0x5e09e03a, 0x3b6edb7c, 0x94c796b6, + 0xf1a0adf0, 0x10e40b63, 0x75833025, 0xda2a7def, 0xbf4d46a9, + 0xc3d23688, 0xa6b50dce, 0x091c4004, 0x6c7b7b42, 0x8d3fddd1, + 0xe858e697, 0x47f1ab5d, 0x2296901b, 0xbecf4b1f, 0xdba87059, + 0x74013d93, 0x116606d5, 0xf022a046, 0x95459b00, 0x3aecd6ca, + 0x5f8bed8c, 0x23149dad, 0x4673a6eb, 0xe9daeb21, 0x8cbdd067, + 0x6df976f4, 0x089e4db2, 0xa7370078, 0xc2503b3e, 0x44f5b031, + 0x21928b77, 0x8e3bc6bd, 0xeb5cfdfb, 0x0a185b68, 0x6f7f602e, + 0xc0d62de4, 0xa5b116a2, 0xd92e6683, 0xbc495dc5, 0x13e0100f, + 0x76872b49, 0x97c38dda, 0xf2a4b69c, 0x5d0dfb56, 0x386ac010, + 0xa4331b14, 0xc1542052, 0x6efd6d98, 0x0b9a56de, 0xeadef04d, + 0x8fb9cb0b, 0x201086c1, 0x4577bd87, 0x39e8cda6, 0x5c8ff6e0, + 0xf326bb2a, 0x9641806c, 0x770526ff, 0x12621db9, 0xbdcb5073, + 0xd8ac6b35}, + {0x00000000, 0xd7e28058, 0x74b406f1, 0xa35686a9, 0xe9680de2, + 0x3e8a8dba, 0x9ddc0b13, 0x4a3e8b4b, 0x09a11d85, 0xde439ddd, + 0x7d151b74, 0xaaf79b2c, 0xe0c91067, 0x372b903f, 0x947d1696, + 0x439f96ce, 0x13423b0a, 0xc4a0bb52, 0x67f63dfb, 0xb014bda3, + 0xfa2a36e8, 0x2dc8b6b0, 0x8e9e3019, 0x597cb041, 0x1ae3268f, + 0xcd01a6d7, 0x6e57207e, 0xb9b5a026, 0xf38b2b6d, 0x2469ab35, + 0x873f2d9c, 0x50ddadc4, 0x26847614, 0xf166f64c, 0x523070e5, + 0x85d2f0bd, 0xcfec7bf6, 0x180efbae, 0xbb587d07, 0x6cbafd5f, + 0x2f256b91, 0xf8c7ebc9, 0x5b916d60, 0x8c73ed38, 0xc64d6673, + 0x11afe62b, 0xb2f96082, 0x651be0da, 0x35c64d1e, 0xe224cd46, + 0x41724bef, 0x9690cbb7, 0xdcae40fc, 0x0b4cc0a4, 0xa81a460d, + 0x7ff8c655, 0x3c67509b, 0xeb85d0c3, 0x48d3566a, 0x9f31d632, + 0xd50f5d79, 0x02eddd21, 0xa1bb5b88, 0x7659dbd0, 0x4d08ec28, + 0x9aea6c70, 0x39bcead9, 0xee5e6a81, 0xa460e1ca, 0x73826192, + 0xd0d4e73b, 0x07366763, 0x44a9f1ad, 0x934b71f5, 0x301df75c, + 0xe7ff7704, 0xadc1fc4f, 0x7a237c17, 0xd975fabe, 0x0e977ae6, + 0x5e4ad722, 0x89a8577a, 0x2afed1d3, 0xfd1c518b, 0xb722dac0, + 0x60c05a98, 0xc396dc31, 0x14745c69, 0x57ebcaa7, 0x80094aff, + 0x235fcc56, 0xf4bd4c0e, 0xbe83c745, 0x6961471d, 0xca37c1b4, + 0x1dd541ec, 0x6b8c9a3c, 0xbc6e1a64, 0x1f389ccd, 0xc8da1c95, + 0x82e497de, 0x55061786, 0xf650912f, 0x21b21177, 0x622d87b9, + 0xb5cf07e1, 0x16998148, 0xc17b0110, 0x8b458a5b, 0x5ca70a03, + 0xfff18caa, 0x28130cf2, 0x78cea136, 0xaf2c216e, 0x0c7aa7c7, + 0xdb98279f, 0x91a6acd4, 0x46442c8c, 0xe512aa25, 0x32f02a7d, + 0x716fbcb3, 0xa68d3ceb, 0x05dbba42, 0xd2393a1a, 0x9807b151, + 0x4fe53109, 0xecb3b7a0, 0x3b5137f8, 0x9a11d850, 0x4df35808, + 0xeea5dea1, 0x39475ef9, 0x7379d5b2, 0xa49b55ea, 0x07cdd343, + 0xd02f531b, 0x93b0c5d5, 0x4452458d, 0xe704c324, 0x30e6437c, + 0x7ad8c837, 0xad3a486f, 0x0e6ccec6, 0xd98e4e9e, 0x8953e35a, + 0x5eb16302, 0xfde7e5ab, 0x2a0565f3, 0x603beeb8, 0xb7d96ee0, + 0x148fe849, 0xc36d6811, 0x80f2fedf, 0x57107e87, 0xf446f82e, + 0x23a47876, 0x699af33d, 0xbe787365, 0x1d2ef5cc, 0xcacc7594, + 0xbc95ae44, 0x6b772e1c, 0xc821a8b5, 0x1fc328ed, 0x55fda3a6, + 0x821f23fe, 0x2149a557, 0xf6ab250f, 0xb534b3c1, 0x62d63399, + 0xc180b530, 0x16623568, 0x5c5cbe23, 0x8bbe3e7b, 0x28e8b8d2, + 0xff0a388a, 0xafd7954e, 0x78351516, 0xdb6393bf, 0x0c8113e7, + 0x46bf98ac, 0x915d18f4, 0x320b9e5d, 0xe5e91e05, 0xa67688cb, + 0x71940893, 0xd2c28e3a, 0x05200e62, 0x4f1e8529, 0x98fc0571, + 0x3baa83d8, 0xec480380, 0xd7193478, 0x00fbb420, 0xa3ad3289, + 0x744fb2d1, 0x3e71399a, 0xe993b9c2, 0x4ac53f6b, 0x9d27bf33, + 0xdeb829fd, 0x095aa9a5, 0xaa0c2f0c, 0x7deeaf54, 0x37d0241f, + 0xe032a447, 0x436422ee, 0x9486a2b6, 0xc45b0f72, 0x13b98f2a, + 0xb0ef0983, 0x670d89db, 0x2d330290, 0xfad182c8, 0x59870461, + 0x8e658439, 0xcdfa12f7, 0x1a1892af, 0xb94e1406, 0x6eac945e, + 0x24921f15, 0xf3709f4d, 0x502619e4, 0x87c499bc, 0xf19d426c, + 0x267fc234, 0x8529449d, 0x52cbc4c5, 0x18f54f8e, 0xcf17cfd6, + 0x6c41497f, 0xbba3c927, 0xf83c5fe9, 0x2fdedfb1, 0x8c885918, + 0x5b6ad940, 0x1154520b, 0xc6b6d253, 0x65e054fa, 0xb202d4a2, + 0xe2df7966, 0x353df93e, 0x966b7f97, 0x4189ffcf, 0x0bb77484, + 0xdc55f4dc, 0x7f037275, 0xa8e1f22d, 0xeb7e64e3, 0x3c9ce4bb, + 0x9fca6212, 0x4828e24a, 0x02166901, 0xd5f4e959, 0x76a26ff0, + 0xa140efa8}, + {0x00000000, 0xef52b6e1, 0x05d46b83, 0xea86dd62, 0x0ba8d706, + 0xe4fa61e7, 0x0e7cbc85, 0xe12e0a64, 0x1751ae0c, 0xf80318ed, + 0x1285c58f, 0xfdd7736e, 0x1cf9790a, 0xf3abcfeb, 0x192d1289, + 0xf67fa468, 0x2ea35c18, 0xc1f1eaf9, 0x2b77379b, 0xc425817a, + 0x250b8b1e, 0xca593dff, 0x20dfe09d, 0xcf8d567c, 0x39f2f214, + 0xd6a044f5, 0x3c269997, 0xd3742f76, 0x325a2512, 0xdd0893f3, + 0x378e4e91, 0xd8dcf870, 0x5d46b830, 0xb2140ed1, 0x5892d3b3, + 0xb7c06552, 0x56ee6f36, 0xb9bcd9d7, 0x533a04b5, 0xbc68b254, + 0x4a17163c, 0xa545a0dd, 0x4fc37dbf, 0xa091cb5e, 0x41bfc13a, + 0xaeed77db, 0x446baab9, 0xab391c58, 0x73e5e428, 0x9cb752c9, + 0x76318fab, 0x9963394a, 0x784d332e, 0x971f85cf, 0x7d9958ad, + 0x92cbee4c, 0x64b44a24, 0x8be6fcc5, 0x616021a7, 0x8e329746, + 0x6f1c9d22, 0x804e2bc3, 0x6ac8f6a1, 0x859a4040, 0xba8d7060, + 0x55dfc681, 0xbf591be3, 0x500bad02, 0xb125a766, 0x5e771187, + 0xb4f1cce5, 0x5ba37a04, 0xaddcde6c, 0x428e688d, 0xa808b5ef, + 0x475a030e, 0xa674096a, 0x4926bf8b, 0xa3a062e9, 0x4cf2d408, + 0x942e2c78, 0x7b7c9a99, 0x91fa47fb, 0x7ea8f11a, 0x9f86fb7e, + 0x70d44d9f, 0x9a5290fd, 0x7500261c, 0x837f8274, 0x6c2d3495, + 0x86abe9f7, 0x69f95f16, 0x88d75572, 0x6785e393, 0x8d033ef1, + 0x62518810, 0xe7cbc850, 0x08997eb1, 0xe21fa3d3, 0x0d4d1532, + 0xec631f56, 0x0331a9b7, 0xe9b774d5, 0x06e5c234, 0xf09a665c, + 0x1fc8d0bd, 0xf54e0ddf, 0x1a1cbb3e, 0xfb32b15a, 0x146007bb, + 0xfee6dad9, 0x11b46c38, 0xc9689448, 0x263a22a9, 0xccbcffcb, + 0x23ee492a, 0xc2c0434e, 0x2d92f5af, 0xc71428cd, 0x28469e2c, + 0xde393a44, 0x316b8ca5, 0xdbed51c7, 0x34bfe726, 0xd591ed42, + 0x3ac35ba3, 0xd04586c1, 0x3f173020, 0xae6be681, 0x41395060, + 0xabbf8d02, 0x44ed3be3, 0xa5c33187, 0x4a918766, 0xa0175a04, + 0x4f45ece5, 0xb93a488d, 0x5668fe6c, 0xbcee230e, 0x53bc95ef, + 0xb2929f8b, 0x5dc0296a, 0xb746f408, 0x581442e9, 0x80c8ba99, + 0x6f9a0c78, 0x851cd11a, 0x6a4e67fb, 0x8b606d9f, 0x6432db7e, + 0x8eb4061c, 0x61e6b0fd, 0x97991495, 0x78cba274, 0x924d7f16, + 0x7d1fc9f7, 0x9c31c393, 0x73637572, 0x99e5a810, 0x76b71ef1, + 0xf32d5eb1, 0x1c7fe850, 0xf6f93532, 0x19ab83d3, 0xf88589b7, + 0x17d73f56, 0xfd51e234, 0x120354d5, 0xe47cf0bd, 0x0b2e465c, + 0xe1a89b3e, 0x0efa2ddf, 0xefd427bb, 0x0086915a, 0xea004c38, + 0x0552fad9, 0xdd8e02a9, 0x32dcb448, 0xd85a692a, 0x3708dfcb, + 0xd626d5af, 0x3974634e, 0xd3f2be2c, 0x3ca008cd, 0xcadfaca5, + 0x258d1a44, 0xcf0bc726, 0x205971c7, 0xc1777ba3, 0x2e25cd42, + 0xc4a31020, 0x2bf1a6c1, 0x14e696e1, 0xfbb42000, 0x1132fd62, + 0xfe604b83, 0x1f4e41e7, 0xf01cf706, 0x1a9a2a64, 0xf5c89c85, + 0x03b738ed, 0xece58e0c, 0x0663536e, 0xe931e58f, 0x081fefeb, + 0xe74d590a, 0x0dcb8468, 0xe2993289, 0x3a45caf9, 0xd5177c18, + 0x3f91a17a, 0xd0c3179b, 0x31ed1dff, 0xdebfab1e, 0x3439767c, + 0xdb6bc09d, 0x2d1464f5, 0xc246d214, 0x28c00f76, 0xc792b997, + 0x26bcb3f3, 0xc9ee0512, 0x2368d870, 0xcc3a6e91, 0x49a02ed1, + 0xa6f29830, 0x4c744552, 0xa326f3b3, 0x4208f9d7, 0xad5a4f36, + 0x47dc9254, 0xa88e24b5, 0x5ef180dd, 0xb1a3363c, 0x5b25eb5e, + 0xb4775dbf, 0x555957db, 0xba0be13a, 0x508d3c58, 0xbfdf8ab9, + 0x670372c9, 0x8851c428, 0x62d7194a, 0x8d85afab, 0x6caba5cf, + 0x83f9132e, 0x697fce4c, 0x862d78ad, 0x7052dcc5, 0x9f006a24, + 0x7586b746, 0x9ad401a7, 0x7bfa0bc3, 0x94a8bd22, 0x7e2e6040, + 0x917cd6a1}, + {0x00000000, 0x87a6cb43, 0xd43c90c7, 0x539a5b84, 0x730827cf, + 0xf4aeec8c, 0xa734b708, 0x20927c4b, 0xe6104f9e, 0x61b684dd, + 0x322cdf59, 0xb58a141a, 0x95186851, 0x12bea312, 0x4124f896, + 0xc68233d5, 0x1751997d, 0x90f7523e, 0xc36d09ba, 0x44cbc2f9, + 0x6459beb2, 0xe3ff75f1, 0xb0652e75, 0x37c3e536, 0xf141d6e3, + 0x76e71da0, 0x257d4624, 0xa2db8d67, 0x8249f12c, 0x05ef3a6f, + 0x567561eb, 0xd1d3aaa8, 0x2ea332fa, 0xa905f9b9, 0xfa9fa23d, + 0x7d39697e, 0x5dab1535, 0xda0dde76, 0x899785f2, 0x0e314eb1, + 0xc8b37d64, 0x4f15b627, 0x1c8feda3, 0x9b2926e0, 0xbbbb5aab, + 0x3c1d91e8, 0x6f87ca6c, 0xe821012f, 0x39f2ab87, 0xbe5460c4, + 0xedce3b40, 0x6a68f003, 0x4afa8c48, 0xcd5c470b, 0x9ec61c8f, + 0x1960d7cc, 0xdfe2e419, 0x58442f5a, 0x0bde74de, 0x8c78bf9d, + 0xaceac3d6, 0x2b4c0895, 0x78d65311, 0xff709852, 0x5d4665f4, + 0xdae0aeb7, 0x897af533, 0x0edc3e70, 0x2e4e423b, 0xa9e88978, + 0xfa72d2fc, 0x7dd419bf, 0xbb562a6a, 0x3cf0e129, 0x6f6abaad, + 0xe8cc71ee, 0xc85e0da5, 0x4ff8c6e6, 0x1c629d62, 0x9bc45621, + 0x4a17fc89, 0xcdb137ca, 0x9e2b6c4e, 0x198da70d, 0x391fdb46, + 0xbeb91005, 0xed234b81, 0x6a8580c2, 0xac07b317, 0x2ba17854, + 0x783b23d0, 0xff9de893, 0xdf0f94d8, 0x58a95f9b, 0x0b33041f, + 0x8c95cf5c, 0x73e5570e, 0xf4439c4d, 0xa7d9c7c9, 0x207f0c8a, + 0x00ed70c1, 0x874bbb82, 0xd4d1e006, 0x53772b45, 0x95f51890, + 0x1253d3d3, 0x41c98857, 0xc66f4314, 0xe6fd3f5f, 0x615bf41c, + 0x32c1af98, 0xb56764db, 0x64b4ce73, 0xe3120530, 0xb0885eb4, + 0x372e95f7, 0x17bce9bc, 0x901a22ff, 0xc380797b, 0x4426b238, + 0x82a481ed, 0x05024aae, 0x5698112a, 0xd13eda69, 0xf1aca622, + 0x760a6d61, 0x259036e5, 0xa236fda6, 0xba8ccbe8, 0x3d2a00ab, + 0x6eb05b2f, 0xe916906c, 0xc984ec27, 0x4e222764, 0x1db87ce0, + 0x9a1eb7a3, 0x5c9c8476, 0xdb3a4f35, 0x88a014b1, 0x0f06dff2, + 0x2f94a3b9, 0xa83268fa, 0xfba8337e, 0x7c0ef83d, 0xaddd5295, + 0x2a7b99d6, 0x79e1c252, 0xfe470911, 0xded5755a, 0x5973be19, + 0x0ae9e59d, 0x8d4f2ede, 0x4bcd1d0b, 0xcc6bd648, 0x9ff18dcc, + 0x1857468f, 0x38c53ac4, 0xbf63f187, 0xecf9aa03, 0x6b5f6140, + 0x942ff912, 0x13893251, 0x401369d5, 0xc7b5a296, 0xe727dedd, + 0x6081159e, 0x331b4e1a, 0xb4bd8559, 0x723fb68c, 0xf5997dcf, + 0xa603264b, 0x21a5ed08, 0x01379143, 0x86915a00, 0xd50b0184, + 0x52adcac7, 0x837e606f, 0x04d8ab2c, 0x5742f0a8, 0xd0e43beb, + 0xf07647a0, 0x77d08ce3, 0x244ad767, 0xa3ec1c24, 0x656e2ff1, + 0xe2c8e4b2, 0xb152bf36, 0x36f47475, 0x1666083e, 0x91c0c37d, + 0xc25a98f9, 0x45fc53ba, 0xe7caae1c, 0x606c655f, 0x33f63edb, + 0xb450f598, 0x94c289d3, 0x13644290, 0x40fe1914, 0xc758d257, + 0x01dae182, 0x867c2ac1, 0xd5e67145, 0x5240ba06, 0x72d2c64d, + 0xf5740d0e, 0xa6ee568a, 0x21489dc9, 0xf09b3761, 0x773dfc22, + 0x24a7a7a6, 0xa3016ce5, 0x839310ae, 0x0435dbed, 0x57af8069, + 0xd0094b2a, 0x168b78ff, 0x912db3bc, 0xc2b7e838, 0x4511237b, + 0x65835f30, 0xe2259473, 0xb1bfcff7, 0x361904b4, 0xc9699ce6, + 0x4ecf57a5, 0x1d550c21, 0x9af3c762, 0xba61bb29, 0x3dc7706a, + 0x6e5d2bee, 0xe9fbe0ad, 0x2f79d378, 0xa8df183b, 0xfb4543bf, + 0x7ce388fc, 0x5c71f4b7, 0xdbd73ff4, 0x884d6470, 0x0febaf33, + 0xde38059b, 0x599eced8, 0x0a04955c, 0x8da25e1f, 0xad302254, + 0x2a96e917, 0x790cb293, 0xfeaa79d0, 0x38284a05, 0xbf8e8146, + 0xec14dac2, 0x6bb21181, 0x4b206dca, 0xcc86a689, 0x9f1cfd0d, + 0x18ba364e}}; + +local const z_word_t FAR crc_braid_big_table[][256] = { + {0x0000000000000000, 0x43cba68700000000, 0xc7903cd400000000, + 0x845b9a5300000000, 0xcf27087300000000, 0x8cecaef400000000, + 0x08b734a700000000, 0x4b7c922000000000, 0x9e4f10e600000000, + 0xdd84b66100000000, 0x59df2c3200000000, 0x1a148ab500000000, + 0x5168189500000000, 0x12a3be1200000000, 0x96f8244100000000, + 0xd53382c600000000, 0x7d99511700000000, 0x3e52f79000000000, + 0xba096dc300000000, 0xf9c2cb4400000000, 0xb2be596400000000, + 0xf175ffe300000000, 0x752e65b000000000, 0x36e5c33700000000, + 0xe3d641f100000000, 0xa01de77600000000, 0x24467d2500000000, + 0x678ddba200000000, 0x2cf1498200000000, 0x6f3aef0500000000, + 0xeb61755600000000, 0xa8aad3d100000000, 0xfa32a32e00000000, + 0xb9f905a900000000, 0x3da29ffa00000000, 0x7e69397d00000000, + 0x3515ab5d00000000, 0x76de0dda00000000, 0xf285978900000000, + 0xb14e310e00000000, 0x647db3c800000000, 0x27b6154f00000000, + 0xa3ed8f1c00000000, 0xe026299b00000000, 0xab5abbbb00000000, + 0xe8911d3c00000000, 0x6cca876f00000000, 0x2f0121e800000000, + 0x87abf23900000000, 0xc46054be00000000, 0x403bceed00000000, + 0x03f0686a00000000, 0x488cfa4a00000000, 0x0b475ccd00000000, + 0x8f1cc69e00000000, 0xccd7601900000000, 0x19e4e2df00000000, + 0x5a2f445800000000, 0xde74de0b00000000, 0x9dbf788c00000000, + 0xd6c3eaac00000000, 0x95084c2b00000000, 0x1153d67800000000, + 0x529870ff00000000, 0xf465465d00000000, 0xb7aee0da00000000, + 0x33f57a8900000000, 0x703edc0e00000000, 0x3b424e2e00000000, + 0x7889e8a900000000, 0xfcd272fa00000000, 0xbf19d47d00000000, + 0x6a2a56bb00000000, 0x29e1f03c00000000, 0xadba6a6f00000000, + 0xee71cce800000000, 0xa50d5ec800000000, 0xe6c6f84f00000000, + 0x629d621c00000000, 0x2156c49b00000000, 0x89fc174a00000000, + 0xca37b1cd00000000, 0x4e6c2b9e00000000, 0x0da78d1900000000, + 0x46db1f3900000000, 0x0510b9be00000000, 0x814b23ed00000000, + 0xc280856a00000000, 0x17b307ac00000000, 0x5478a12b00000000, + 0xd0233b7800000000, 0x93e89dff00000000, 0xd8940fdf00000000, + 0x9b5fa95800000000, 0x1f04330b00000000, 0x5ccf958c00000000, + 0x0e57e57300000000, 0x4d9c43f400000000, 0xc9c7d9a700000000, + 0x8a0c7f2000000000, 0xc170ed0000000000, 0x82bb4b8700000000, + 0x06e0d1d400000000, 0x452b775300000000, 0x9018f59500000000, + 0xd3d3531200000000, 0x5788c94100000000, 0x14436fc600000000, + 0x5f3ffde600000000, 0x1cf45b6100000000, 0x98afc13200000000, + 0xdb6467b500000000, 0x73ceb46400000000, 0x300512e300000000, + 0xb45e88b000000000, 0xf7952e3700000000, 0xbce9bc1700000000, + 0xff221a9000000000, 0x7b7980c300000000, 0x38b2264400000000, + 0xed81a48200000000, 0xae4a020500000000, 0x2a11985600000000, + 0x69da3ed100000000, 0x22a6acf100000000, 0x616d0a7600000000, + 0xe536902500000000, 0xa6fd36a200000000, 0xe8cb8cba00000000, + 0xab002a3d00000000, 0x2f5bb06e00000000, 0x6c9016e900000000, + 0x27ec84c900000000, 0x6427224e00000000, 0xe07cb81d00000000, + 0xa3b71e9a00000000, 0x76849c5c00000000, 0x354f3adb00000000, + 0xb114a08800000000, 0xf2df060f00000000, 0xb9a3942f00000000, + 0xfa6832a800000000, 0x7e33a8fb00000000, 0x3df80e7c00000000, + 0x9552ddad00000000, 0xd6997b2a00000000, 0x52c2e17900000000, + 0x110947fe00000000, 0x5a75d5de00000000, 0x19be735900000000, + 0x9de5e90a00000000, 0xde2e4f8d00000000, 0x0b1dcd4b00000000, + 0x48d66bcc00000000, 0xcc8df19f00000000, 0x8f46571800000000, + 0xc43ac53800000000, 0x87f163bf00000000, 0x03aaf9ec00000000, + 0x40615f6b00000000, 0x12f92f9400000000, 0x5132891300000000, + 0xd569134000000000, 0x96a2b5c700000000, 0xddde27e700000000, + 0x9e15816000000000, 0x1a4e1b3300000000, 0x5985bdb400000000, + 0x8cb63f7200000000, 0xcf7d99f500000000, 0x4b2603a600000000, + 0x08eda52100000000, 0x4391370100000000, 0x005a918600000000, + 0x84010bd500000000, 0xc7caad5200000000, 0x6f607e8300000000, + 0x2cabd80400000000, 0xa8f0425700000000, 0xeb3be4d000000000, + 0xa04776f000000000, 0xe38cd07700000000, 0x67d74a2400000000, + 0x241ceca300000000, 0xf12f6e6500000000, 0xb2e4c8e200000000, + 0x36bf52b100000000, 0x7574f43600000000, 0x3e08661600000000, + 0x7dc3c09100000000, 0xf9985ac200000000, 0xba53fc4500000000, + 0x1caecae700000000, 0x5f656c6000000000, 0xdb3ef63300000000, + 0x98f550b400000000, 0xd389c29400000000, 0x9042641300000000, + 0x1419fe4000000000, 0x57d258c700000000, 0x82e1da0100000000, + 0xc12a7c8600000000, 0x4571e6d500000000, 0x06ba405200000000, + 0x4dc6d27200000000, 0x0e0d74f500000000, 0x8a56eea600000000, + 0xc99d482100000000, 0x61379bf000000000, 0x22fc3d7700000000, + 0xa6a7a72400000000, 0xe56c01a300000000, 0xae10938300000000, + 0xeddb350400000000, 0x6980af5700000000, 0x2a4b09d000000000, + 0xff788b1600000000, 0xbcb32d9100000000, 0x38e8b7c200000000, + 0x7b23114500000000, 0x305f836500000000, 0x739425e200000000, + 0xf7cfbfb100000000, 0xb404193600000000, 0xe69c69c900000000, + 0xa557cf4e00000000, 0x210c551d00000000, 0x62c7f39a00000000, + 0x29bb61ba00000000, 0x6a70c73d00000000, 0xee2b5d6e00000000, + 0xade0fbe900000000, 0x78d3792f00000000, 0x3b18dfa800000000, + 0xbf4345fb00000000, 0xfc88e37c00000000, 0xb7f4715c00000000, + 0xf43fd7db00000000, 0x70644d8800000000, 0x33afeb0f00000000, + 0x9b0538de00000000, 0xd8ce9e5900000000, 0x5c95040a00000000, + 0x1f5ea28d00000000, 0x542230ad00000000, 0x17e9962a00000000, + 0x93b20c7900000000, 0xd079aafe00000000, 0x054a283800000000, + 0x46818ebf00000000, 0xc2da14ec00000000, 0x8111b26b00000000, + 0xca6d204b00000000, 0x89a686cc00000000, 0x0dfd1c9f00000000, + 0x4e36ba1800000000}, + {0x0000000000000000, 0xe1b652ef00000000, 0x836bd40500000000, + 0x62dd86ea00000000, 0x06d7a80b00000000, 0xe761fae400000000, + 0x85bc7c0e00000000, 0x640a2ee100000000, 0x0cae511700000000, + 0xed1803f800000000, 0x8fc5851200000000, 0x6e73d7fd00000000, + 0x0a79f91c00000000, 0xebcfabf300000000, 0x89122d1900000000, + 0x68a47ff600000000, 0x185ca32e00000000, 0xf9eaf1c100000000, + 0x9b37772b00000000, 0x7a8125c400000000, 0x1e8b0b2500000000, + 0xff3d59ca00000000, 0x9de0df2000000000, 0x7c568dcf00000000, + 0x14f2f23900000000, 0xf544a0d600000000, 0x9799263c00000000, + 0x762f74d300000000, 0x12255a3200000000, 0xf39308dd00000000, + 0x914e8e3700000000, 0x70f8dcd800000000, 0x30b8465d00000000, + 0xd10e14b200000000, 0xb3d3925800000000, 0x5265c0b700000000, + 0x366fee5600000000, 0xd7d9bcb900000000, 0xb5043a5300000000, + 0x54b268bc00000000, 0x3c16174a00000000, 0xdda045a500000000, + 0xbf7dc34f00000000, 0x5ecb91a000000000, 0x3ac1bf4100000000, + 0xdb77edae00000000, 0xb9aa6b4400000000, 0x581c39ab00000000, + 0x28e4e57300000000, 0xc952b79c00000000, 0xab8f317600000000, + 0x4a39639900000000, 0x2e334d7800000000, 0xcf851f9700000000, + 0xad58997d00000000, 0x4ceecb9200000000, 0x244ab46400000000, + 0xc5fce68b00000000, 0xa721606100000000, 0x4697328e00000000, + 0x229d1c6f00000000, 0xc32b4e8000000000, 0xa1f6c86a00000000, + 0x40409a8500000000, 0x60708dba00000000, 0x81c6df5500000000, + 0xe31b59bf00000000, 0x02ad0b5000000000, 0x66a725b100000000, + 0x8711775e00000000, 0xe5ccf1b400000000, 0x047aa35b00000000, + 0x6cdedcad00000000, 0x8d688e4200000000, 0xefb508a800000000, + 0x0e035a4700000000, 0x6a0974a600000000, 0x8bbf264900000000, + 0xe962a0a300000000, 0x08d4f24c00000000, 0x782c2e9400000000, + 0x999a7c7b00000000, 0xfb47fa9100000000, 0x1af1a87e00000000, + 0x7efb869f00000000, 0x9f4dd47000000000, 0xfd90529a00000000, + 0x1c26007500000000, 0x74827f8300000000, 0x95342d6c00000000, + 0xf7e9ab8600000000, 0x165ff96900000000, 0x7255d78800000000, + 0x93e3856700000000, 0xf13e038d00000000, 0x1088516200000000, + 0x50c8cbe700000000, 0xb17e990800000000, 0xd3a31fe200000000, + 0x32154d0d00000000, 0x561f63ec00000000, 0xb7a9310300000000, + 0xd574b7e900000000, 0x34c2e50600000000, 0x5c669af000000000, + 0xbdd0c81f00000000, 0xdf0d4ef500000000, 0x3ebb1c1a00000000, + 0x5ab132fb00000000, 0xbb07601400000000, 0xd9dae6fe00000000, + 0x386cb41100000000, 0x489468c900000000, 0xa9223a2600000000, + 0xcbffbccc00000000, 0x2a49ee2300000000, 0x4e43c0c200000000, + 0xaff5922d00000000, 0xcd2814c700000000, 0x2c9e462800000000, + 0x443a39de00000000, 0xa58c6b3100000000, 0xc751eddb00000000, + 0x26e7bf3400000000, 0x42ed91d500000000, 0xa35bc33a00000000, + 0xc18645d000000000, 0x2030173f00000000, 0x81e66bae00000000, + 0x6050394100000000, 0x028dbfab00000000, 0xe33bed4400000000, + 0x8731c3a500000000, 0x6687914a00000000, 0x045a17a000000000, + 0xe5ec454f00000000, 0x8d483ab900000000, 0x6cfe685600000000, + 0x0e23eebc00000000, 0xef95bc5300000000, 0x8b9f92b200000000, + 0x6a29c05d00000000, 0x08f446b700000000, 0xe942145800000000, + 0x99bac88000000000, 0x780c9a6f00000000, 0x1ad11c8500000000, + 0xfb674e6a00000000, 0x9f6d608b00000000, 0x7edb326400000000, + 0x1c06b48e00000000, 0xfdb0e66100000000, 0x9514999700000000, + 0x74a2cb7800000000, 0x167f4d9200000000, 0xf7c91f7d00000000, + 0x93c3319c00000000, 0x7275637300000000, 0x10a8e59900000000, + 0xf11eb77600000000, 0xb15e2df300000000, 0x50e87f1c00000000, + 0x3235f9f600000000, 0xd383ab1900000000, 0xb78985f800000000, + 0x563fd71700000000, 0x34e251fd00000000, 0xd554031200000000, + 0xbdf07ce400000000, 0x5c462e0b00000000, 0x3e9ba8e100000000, + 0xdf2dfa0e00000000, 0xbb27d4ef00000000, 0x5a91860000000000, + 0x384c00ea00000000, 0xd9fa520500000000, 0xa9028edd00000000, + 0x48b4dc3200000000, 0x2a695ad800000000, 0xcbdf083700000000, + 0xafd526d600000000, 0x4e63743900000000, 0x2cbef2d300000000, + 0xcd08a03c00000000, 0xa5acdfca00000000, 0x441a8d2500000000, + 0x26c70bcf00000000, 0xc771592000000000, 0xa37b77c100000000, + 0x42cd252e00000000, 0x2010a3c400000000, 0xc1a6f12b00000000, + 0xe196e61400000000, 0x0020b4fb00000000, 0x62fd321100000000, + 0x834b60fe00000000, 0xe7414e1f00000000, 0x06f71cf000000000, + 0x642a9a1a00000000, 0x859cc8f500000000, 0xed38b70300000000, + 0x0c8ee5ec00000000, 0x6e53630600000000, 0x8fe531e900000000, + 0xebef1f0800000000, 0x0a594de700000000, 0x6884cb0d00000000, + 0x893299e200000000, 0xf9ca453a00000000, 0x187c17d500000000, + 0x7aa1913f00000000, 0x9b17c3d000000000, 0xff1ded3100000000, + 0x1eabbfde00000000, 0x7c76393400000000, 0x9dc06bdb00000000, + 0xf564142d00000000, 0x14d246c200000000, 0x760fc02800000000, + 0x97b992c700000000, 0xf3b3bc2600000000, 0x1205eec900000000, + 0x70d8682300000000, 0x916e3acc00000000, 0xd12ea04900000000, + 0x3098f2a600000000, 0x5245744c00000000, 0xb3f326a300000000, + 0xd7f9084200000000, 0x364f5aad00000000, 0x5492dc4700000000, + 0xb5248ea800000000, 0xdd80f15e00000000, 0x3c36a3b100000000, + 0x5eeb255b00000000, 0xbf5d77b400000000, 0xdb57595500000000, + 0x3ae10bba00000000, 0x583c8d5000000000, 0xb98adfbf00000000, + 0xc972036700000000, 0x28c4518800000000, 0x4a19d76200000000, + 0xabaf858d00000000, 0xcfa5ab6c00000000, 0x2e13f98300000000, + 0x4cce7f6900000000, 0xad782d8600000000, 0xc5dc527000000000, + 0x246a009f00000000, 0x46b7867500000000, 0xa701d49a00000000, + 0xc30bfa7b00000000, 0x22bda89400000000, 0x40602e7e00000000, + 0xa1d67c9100000000}, + {0x0000000000000000, 0x5880e2d700000000, 0xf106b47400000000, + 0xa98656a300000000, 0xe20d68e900000000, 0xba8d8a3e00000000, + 0x130bdc9d00000000, 0x4b8b3e4a00000000, 0x851da10900000000, + 0xdd9d43de00000000, 0x741b157d00000000, 0x2c9bf7aa00000000, + 0x6710c9e000000000, 0x3f902b3700000000, 0x96167d9400000000, + 0xce969f4300000000, 0x0a3b421300000000, 0x52bba0c400000000, + 0xfb3df66700000000, 0xa3bd14b000000000, 0xe8362afa00000000, + 0xb0b6c82d00000000, 0x19309e8e00000000, 0x41b07c5900000000, + 0x8f26e31a00000000, 0xd7a601cd00000000, 0x7e20576e00000000, + 0x26a0b5b900000000, 0x6d2b8bf300000000, 0x35ab692400000000, + 0x9c2d3f8700000000, 0xc4addd5000000000, 0x1476842600000000, + 0x4cf666f100000000, 0xe570305200000000, 0xbdf0d28500000000, + 0xf67beccf00000000, 0xaefb0e1800000000, 0x077d58bb00000000, + 0x5ffdba6c00000000, 0x916b252f00000000, 0xc9ebc7f800000000, + 0x606d915b00000000, 0x38ed738c00000000, 0x73664dc600000000, + 0x2be6af1100000000, 0x8260f9b200000000, 0xdae01b6500000000, + 0x1e4dc63500000000, 0x46cd24e200000000, 0xef4b724100000000, + 0xb7cb909600000000, 0xfc40aedc00000000, 0xa4c04c0b00000000, + 0x0d461aa800000000, 0x55c6f87f00000000, 0x9b50673c00000000, + 0xc3d085eb00000000, 0x6a56d34800000000, 0x32d6319f00000000, + 0x795d0fd500000000, 0x21dded0200000000, 0x885bbba100000000, + 0xd0db597600000000, 0x28ec084d00000000, 0x706cea9a00000000, + 0xd9eabc3900000000, 0x816a5eee00000000, 0xcae160a400000000, + 0x9261827300000000, 0x3be7d4d000000000, 0x6367360700000000, + 0xadf1a94400000000, 0xf5714b9300000000, 0x5cf71d3000000000, + 0x0477ffe700000000, 0x4ffcc1ad00000000, 0x177c237a00000000, + 0xbefa75d900000000, 0xe67a970e00000000, 0x22d74a5e00000000, + 0x7a57a88900000000, 0xd3d1fe2a00000000, 0x8b511cfd00000000, + 0xc0da22b700000000, 0x985ac06000000000, 0x31dc96c300000000, + 0x695c741400000000, 0xa7caeb5700000000, 0xff4a098000000000, + 0x56cc5f2300000000, 0x0e4cbdf400000000, 0x45c783be00000000, + 0x1d47616900000000, 0xb4c137ca00000000, 0xec41d51d00000000, + 0x3c9a8c6b00000000, 0x641a6ebc00000000, 0xcd9c381f00000000, + 0x951cdac800000000, 0xde97e48200000000, 0x8617065500000000, + 0x2f9150f600000000, 0x7711b22100000000, 0xb9872d6200000000, + 0xe107cfb500000000, 0x4881991600000000, 0x10017bc100000000, + 0x5b8a458b00000000, 0x030aa75c00000000, 0xaa8cf1ff00000000, + 0xf20c132800000000, 0x36a1ce7800000000, 0x6e212caf00000000, + 0xc7a77a0c00000000, 0x9f2798db00000000, 0xd4aca69100000000, + 0x8c2c444600000000, 0x25aa12e500000000, 0x7d2af03200000000, + 0xb3bc6f7100000000, 0xeb3c8da600000000, 0x42badb0500000000, + 0x1a3a39d200000000, 0x51b1079800000000, 0x0931e54f00000000, + 0xa0b7b3ec00000000, 0xf837513b00000000, 0x50d8119a00000000, + 0x0858f34d00000000, 0xa1dea5ee00000000, 0xf95e473900000000, + 0xb2d5797300000000, 0xea559ba400000000, 0x43d3cd0700000000, + 0x1b532fd000000000, 0xd5c5b09300000000, 0x8d45524400000000, + 0x24c304e700000000, 0x7c43e63000000000, 0x37c8d87a00000000, + 0x6f483aad00000000, 0xc6ce6c0e00000000, 0x9e4e8ed900000000, + 0x5ae3538900000000, 0x0263b15e00000000, 0xabe5e7fd00000000, + 0xf365052a00000000, 0xb8ee3b6000000000, 0xe06ed9b700000000, + 0x49e88f1400000000, 0x11686dc300000000, 0xdffef28000000000, + 0x877e105700000000, 0x2ef846f400000000, 0x7678a42300000000, + 0x3df39a6900000000, 0x657378be00000000, 0xccf52e1d00000000, + 0x9475ccca00000000, 0x44ae95bc00000000, 0x1c2e776b00000000, + 0xb5a821c800000000, 0xed28c31f00000000, 0xa6a3fd5500000000, + 0xfe231f8200000000, 0x57a5492100000000, 0x0f25abf600000000, + 0xc1b334b500000000, 0x9933d66200000000, 0x30b580c100000000, + 0x6835621600000000, 0x23be5c5c00000000, 0x7b3ebe8b00000000, + 0xd2b8e82800000000, 0x8a380aff00000000, 0x4e95d7af00000000, + 0x1615357800000000, 0xbf9363db00000000, 0xe713810c00000000, + 0xac98bf4600000000, 0xf4185d9100000000, 0x5d9e0b3200000000, + 0x051ee9e500000000, 0xcb8876a600000000, 0x9308947100000000, + 0x3a8ec2d200000000, 0x620e200500000000, 0x29851e4f00000000, + 0x7105fc9800000000, 0xd883aa3b00000000, 0x800348ec00000000, + 0x783419d700000000, 0x20b4fb0000000000, 0x8932ada300000000, + 0xd1b24f7400000000, 0x9a39713e00000000, 0xc2b993e900000000, + 0x6b3fc54a00000000, 0x33bf279d00000000, 0xfd29b8de00000000, + 0xa5a95a0900000000, 0x0c2f0caa00000000, 0x54afee7d00000000, + 0x1f24d03700000000, 0x47a432e000000000, 0xee22644300000000, + 0xb6a2869400000000, 0x720f5bc400000000, 0x2a8fb91300000000, + 0x8309efb000000000, 0xdb890d6700000000, 0x9002332d00000000, + 0xc882d1fa00000000, 0x6104875900000000, 0x3984658e00000000, + 0xf712facd00000000, 0xaf92181a00000000, 0x06144eb900000000, + 0x5e94ac6e00000000, 0x151f922400000000, 0x4d9f70f300000000, + 0xe419265000000000, 0xbc99c48700000000, 0x6c429df100000000, + 0x34c27f2600000000, 0x9d44298500000000, 0xc5c4cb5200000000, + 0x8e4ff51800000000, 0xd6cf17cf00000000, 0x7f49416c00000000, + 0x27c9a3bb00000000, 0xe95f3cf800000000, 0xb1dfde2f00000000, + 0x1859888c00000000, 0x40d96a5b00000000, 0x0b52541100000000, + 0x53d2b6c600000000, 0xfa54e06500000000, 0xa2d402b200000000, + 0x6679dfe200000000, 0x3ef93d3500000000, 0x977f6b9600000000, + 0xcfff894100000000, 0x8474b70b00000000, 0xdcf455dc00000000, + 0x7572037f00000000, 0x2df2e1a800000000, 0xe3647eeb00000000, + 0xbbe49c3c00000000, 0x1262ca9f00000000, 0x4ae2284800000000, + 0x0169160200000000, 0x59e9f4d500000000, 0xf06fa27600000000, + 0xa8ef40a100000000}, + {0x0000000000000000, 0x463b676500000000, 0x8c76ceca00000000, + 0xca4da9af00000000, 0x59ebed4e00000000, 0x1fd08a2b00000000, + 0xd59d238400000000, 0x93a644e100000000, 0xb2d6db9d00000000, + 0xf4edbcf800000000, 0x3ea0155700000000, 0x789b723200000000, + 0xeb3d36d300000000, 0xad0651b600000000, 0x674bf81900000000, + 0x21709f7c00000000, 0x25abc6e000000000, 0x6390a18500000000, + 0xa9dd082a00000000, 0xefe66f4f00000000, 0x7c402bae00000000, + 0x3a7b4ccb00000000, 0xf036e56400000000, 0xb60d820100000000, + 0x977d1d7d00000000, 0xd1467a1800000000, 0x1b0bd3b700000000, + 0x5d30b4d200000000, 0xce96f03300000000, 0x88ad975600000000, + 0x42e03ef900000000, 0x04db599c00000000, 0x0b50fc1a00000000, + 0x4d6b9b7f00000000, 0x872632d000000000, 0xc11d55b500000000, + 0x52bb115400000000, 0x1480763100000000, 0xdecddf9e00000000, + 0x98f6b8fb00000000, 0xb986278700000000, 0xffbd40e200000000, + 0x35f0e94d00000000, 0x73cb8e2800000000, 0xe06dcac900000000, + 0xa656adac00000000, 0x6c1b040300000000, 0x2a20636600000000, + 0x2efb3afa00000000, 0x68c05d9f00000000, 0xa28df43000000000, + 0xe4b6935500000000, 0x7710d7b400000000, 0x312bb0d100000000, + 0xfb66197e00000000, 0xbd5d7e1b00000000, 0x9c2de16700000000, + 0xda16860200000000, 0x105b2fad00000000, 0x566048c800000000, + 0xc5c60c2900000000, 0x83fd6b4c00000000, 0x49b0c2e300000000, + 0x0f8ba58600000000, 0x16a0f83500000000, 0x509b9f5000000000, + 0x9ad636ff00000000, 0xdced519a00000000, 0x4f4b157b00000000, + 0x0970721e00000000, 0xc33ddbb100000000, 0x8506bcd400000000, + 0xa47623a800000000, 0xe24d44cd00000000, 0x2800ed6200000000, + 0x6e3b8a0700000000, 0xfd9dcee600000000, 0xbba6a98300000000, + 0x71eb002c00000000, 0x37d0674900000000, 0x330b3ed500000000, + 0x753059b000000000, 0xbf7df01f00000000, 0xf946977a00000000, + 0x6ae0d39b00000000, 0x2cdbb4fe00000000, 0xe6961d5100000000, + 0xa0ad7a3400000000, 0x81dde54800000000, 0xc7e6822d00000000, + 0x0dab2b8200000000, 0x4b904ce700000000, 0xd836080600000000, + 0x9e0d6f6300000000, 0x5440c6cc00000000, 0x127ba1a900000000, + 0x1df0042f00000000, 0x5bcb634a00000000, 0x9186cae500000000, + 0xd7bdad8000000000, 0x441be96100000000, 0x02208e0400000000, + 0xc86d27ab00000000, 0x8e5640ce00000000, 0xaf26dfb200000000, + 0xe91db8d700000000, 0x2350117800000000, 0x656b761d00000000, + 0xf6cd32fc00000000, 0xb0f6559900000000, 0x7abbfc3600000000, + 0x3c809b5300000000, 0x385bc2cf00000000, 0x7e60a5aa00000000, + 0xb42d0c0500000000, 0xf2166b6000000000, 0x61b02f8100000000, + 0x278b48e400000000, 0xedc6e14b00000000, 0xabfd862e00000000, + 0x8a8d195200000000, 0xccb67e3700000000, 0x06fbd79800000000, + 0x40c0b0fd00000000, 0xd366f41c00000000, 0x955d937900000000, + 0x5f103ad600000000, 0x192b5db300000000, 0x2c40f16b00000000, + 0x6a7b960e00000000, 0xa0363fa100000000, 0xe60d58c400000000, + 0x75ab1c2500000000, 0x33907b4000000000, 0xf9ddd2ef00000000, + 0xbfe6b58a00000000, 0x9e962af600000000, 0xd8ad4d9300000000, + 0x12e0e43c00000000, 0x54db835900000000, 0xc77dc7b800000000, + 0x8146a0dd00000000, 0x4b0b097200000000, 0x0d306e1700000000, + 0x09eb378b00000000, 0x4fd050ee00000000, 0x859df94100000000, + 0xc3a69e2400000000, 0x5000dac500000000, 0x163bbda000000000, + 0xdc76140f00000000, 0x9a4d736a00000000, 0xbb3dec1600000000, + 0xfd068b7300000000, 0x374b22dc00000000, 0x717045b900000000, + 0xe2d6015800000000, 0xa4ed663d00000000, 0x6ea0cf9200000000, + 0x289ba8f700000000, 0x27100d7100000000, 0x612b6a1400000000, + 0xab66c3bb00000000, 0xed5da4de00000000, 0x7efbe03f00000000, + 0x38c0875a00000000, 0xf28d2ef500000000, 0xb4b6499000000000, + 0x95c6d6ec00000000, 0xd3fdb18900000000, 0x19b0182600000000, + 0x5f8b7f4300000000, 0xcc2d3ba200000000, 0x8a165cc700000000, + 0x405bf56800000000, 0x0660920d00000000, 0x02bbcb9100000000, + 0x4480acf400000000, 0x8ecd055b00000000, 0xc8f6623e00000000, + 0x5b5026df00000000, 0x1d6b41ba00000000, 0xd726e81500000000, + 0x911d8f7000000000, 0xb06d100c00000000, 0xf656776900000000, + 0x3c1bdec600000000, 0x7a20b9a300000000, 0xe986fd4200000000, + 0xafbd9a2700000000, 0x65f0338800000000, 0x23cb54ed00000000, + 0x3ae0095e00000000, 0x7cdb6e3b00000000, 0xb696c79400000000, + 0xf0ada0f100000000, 0x630be41000000000, 0x2530837500000000, + 0xef7d2ada00000000, 0xa9464dbf00000000, 0x8836d2c300000000, + 0xce0db5a600000000, 0x04401c0900000000, 0x427b7b6c00000000, + 0xd1dd3f8d00000000, 0x97e658e800000000, 0x5dabf14700000000, + 0x1b90962200000000, 0x1f4bcfbe00000000, 0x5970a8db00000000, + 0x933d017400000000, 0xd506661100000000, 0x46a022f000000000, + 0x009b459500000000, 0xcad6ec3a00000000, 0x8ced8b5f00000000, + 0xad9d142300000000, 0xeba6734600000000, 0x21ebdae900000000, + 0x67d0bd8c00000000, 0xf476f96d00000000, 0xb24d9e0800000000, + 0x780037a700000000, 0x3e3b50c200000000, 0x31b0f54400000000, + 0x778b922100000000, 0xbdc63b8e00000000, 0xfbfd5ceb00000000, + 0x685b180a00000000, 0x2e607f6f00000000, 0xe42dd6c000000000, + 0xa216b1a500000000, 0x83662ed900000000, 0xc55d49bc00000000, + 0x0f10e01300000000, 0x492b877600000000, 0xda8dc39700000000, + 0x9cb6a4f200000000, 0x56fb0d5d00000000, 0x10c06a3800000000, + 0x141b33a400000000, 0x522054c100000000, 0x986dfd6e00000000, + 0xde569a0b00000000, 0x4df0deea00000000, 0x0bcbb98f00000000, + 0xc186102000000000, 0x87bd774500000000, 0xa6cde83900000000, + 0xe0f68f5c00000000, 0x2abb26f300000000, 0x6c80419600000000, + 0xff26057700000000, 0xb91d621200000000, 0x7350cbbd00000000, + 0x356bacd800000000}, + {0x0000000000000000, 0x9e83da9f00000000, 0x7d01c4e400000000, + 0xe3821e7b00000000, 0xbb04f91200000000, 0x2587238d00000000, + 0xc6053df600000000, 0x5886e76900000000, 0x7609f22500000000, + 0xe88a28ba00000000, 0x0b0836c100000000, 0x958bec5e00000000, + 0xcd0d0b3700000000, 0x538ed1a800000000, 0xb00ccfd300000000, + 0x2e8f154c00000000, 0xec12e44b00000000, 0x72913ed400000000, + 0x911320af00000000, 0x0f90fa3000000000, 0x57161d5900000000, + 0xc995c7c600000000, 0x2a17d9bd00000000, 0xb494032200000000, + 0x9a1b166e00000000, 0x0498ccf100000000, 0xe71ad28a00000000, + 0x7999081500000000, 0x211fef7c00000000, 0xbf9c35e300000000, + 0x5c1e2b9800000000, 0xc29df10700000000, 0xd825c89700000000, + 0x46a6120800000000, 0xa5240c7300000000, 0x3ba7d6ec00000000, + 0x6321318500000000, 0xfda2eb1a00000000, 0x1e20f56100000000, + 0x80a32ffe00000000, 0xae2c3ab200000000, 0x30afe02d00000000, + 0xd32dfe5600000000, 0x4dae24c900000000, 0x1528c3a000000000, + 0x8bab193f00000000, 0x6829074400000000, 0xf6aadddb00000000, + 0x34372cdc00000000, 0xaab4f64300000000, 0x4936e83800000000, + 0xd7b532a700000000, 0x8f33d5ce00000000, 0x11b00f5100000000, + 0xf232112a00000000, 0x6cb1cbb500000000, 0x423edef900000000, + 0xdcbd046600000000, 0x3f3f1a1d00000000, 0xa1bcc08200000000, + 0xf93a27eb00000000, 0x67b9fd7400000000, 0x843be30f00000000, + 0x1ab8399000000000, 0xf14de1f400000000, 0x6fce3b6b00000000, + 0x8c4c251000000000, 0x12cfff8f00000000, 0x4a4918e600000000, + 0xd4cac27900000000, 0x3748dc0200000000, 0xa9cb069d00000000, + 0x874413d100000000, 0x19c7c94e00000000, 0xfa45d73500000000, + 0x64c60daa00000000, 0x3c40eac300000000, 0xa2c3305c00000000, + 0x41412e2700000000, 0xdfc2f4b800000000, 0x1d5f05bf00000000, + 0x83dcdf2000000000, 0x605ec15b00000000, 0xfedd1bc400000000, + 0xa65bfcad00000000, 0x38d8263200000000, 0xdb5a384900000000, + 0x45d9e2d600000000, 0x6b56f79a00000000, 0xf5d52d0500000000, + 0x1657337e00000000, 0x88d4e9e100000000, 0xd0520e8800000000, + 0x4ed1d41700000000, 0xad53ca6c00000000, 0x33d010f300000000, + 0x2968296300000000, 0xb7ebf3fc00000000, 0x5469ed8700000000, + 0xcaea371800000000, 0x926cd07100000000, 0x0cef0aee00000000, + 0xef6d149500000000, 0x71eece0a00000000, 0x5f61db4600000000, + 0xc1e201d900000000, 0x22601fa200000000, 0xbce3c53d00000000, + 0xe465225400000000, 0x7ae6f8cb00000000, 0x9964e6b000000000, + 0x07e73c2f00000000, 0xc57acd2800000000, 0x5bf917b700000000, + 0xb87b09cc00000000, 0x26f8d35300000000, 0x7e7e343a00000000, + 0xe0fdeea500000000, 0x037ff0de00000000, 0x9dfc2a4100000000, + 0xb3733f0d00000000, 0x2df0e59200000000, 0xce72fbe900000000, + 0x50f1217600000000, 0x0877c61f00000000, 0x96f41c8000000000, + 0x757602fb00000000, 0xebf5d86400000000, 0xa39db33200000000, + 0x3d1e69ad00000000, 0xde9c77d600000000, 0x401fad4900000000, + 0x18994a2000000000, 0x861a90bf00000000, 0x65988ec400000000, + 0xfb1b545b00000000, 0xd594411700000000, 0x4b179b8800000000, + 0xa89585f300000000, 0x36165f6c00000000, 0x6e90b80500000000, + 0xf013629a00000000, 0x13917ce100000000, 0x8d12a67e00000000, + 0x4f8f577900000000, 0xd10c8de600000000, 0x328e939d00000000, + 0xac0d490200000000, 0xf48bae6b00000000, 0x6a0874f400000000, + 0x898a6a8f00000000, 0x1709b01000000000, 0x3986a55c00000000, + 0xa7057fc300000000, 0x448761b800000000, 0xda04bb2700000000, + 0x82825c4e00000000, 0x1c0186d100000000, 0xff8398aa00000000, + 0x6100423500000000, 0x7bb87ba500000000, 0xe53ba13a00000000, + 0x06b9bf4100000000, 0x983a65de00000000, 0xc0bc82b700000000, + 0x5e3f582800000000, 0xbdbd465300000000, 0x233e9ccc00000000, + 0x0db1898000000000, 0x9332531f00000000, 0x70b04d6400000000, + 0xee3397fb00000000, 0xb6b5709200000000, 0x2836aa0d00000000, + 0xcbb4b47600000000, 0x55376ee900000000, 0x97aa9fee00000000, + 0x0929457100000000, 0xeaab5b0a00000000, 0x7428819500000000, + 0x2cae66fc00000000, 0xb22dbc6300000000, 0x51afa21800000000, + 0xcf2c788700000000, 0xe1a36dcb00000000, 0x7f20b75400000000, + 0x9ca2a92f00000000, 0x022173b000000000, 0x5aa794d900000000, + 0xc4244e4600000000, 0x27a6503d00000000, 0xb9258aa200000000, + 0x52d052c600000000, 0xcc53885900000000, 0x2fd1962200000000, + 0xb1524cbd00000000, 0xe9d4abd400000000, 0x7757714b00000000, + 0x94d56f3000000000, 0x0a56b5af00000000, 0x24d9a0e300000000, + 0xba5a7a7c00000000, 0x59d8640700000000, 0xc75bbe9800000000, + 0x9fdd59f100000000, 0x015e836e00000000, 0xe2dc9d1500000000, + 0x7c5f478a00000000, 0xbec2b68d00000000, 0x20416c1200000000, + 0xc3c3726900000000, 0x5d40a8f600000000, 0x05c64f9f00000000, + 0x9b45950000000000, 0x78c78b7b00000000, 0xe64451e400000000, + 0xc8cb44a800000000, 0x56489e3700000000, 0xb5ca804c00000000, + 0x2b495ad300000000, 0x73cfbdba00000000, 0xed4c672500000000, + 0x0ece795e00000000, 0x904da3c100000000, 0x8af59a5100000000, + 0x147640ce00000000, 0xf7f45eb500000000, 0x6977842a00000000, + 0x31f1634300000000, 0xaf72b9dc00000000, 0x4cf0a7a700000000, + 0xd2737d3800000000, 0xfcfc687400000000, 0x627fb2eb00000000, + 0x81fdac9000000000, 0x1f7e760f00000000, 0x47f8916600000000, + 0xd97b4bf900000000, 0x3af9558200000000, 0xa47a8f1d00000000, + 0x66e77e1a00000000, 0xf864a48500000000, 0x1be6bafe00000000, + 0x8565606100000000, 0xdde3870800000000, 0x43605d9700000000, + 0xa0e243ec00000000, 0x3e61997300000000, 0x10ee8c3f00000000, + 0x8e6d56a000000000, 0x6def48db00000000, 0xf36c924400000000, + 0xabea752d00000000, 0x3569afb200000000, 0xd6ebb1c900000000, + 0x48686b5600000000}, + {0x0000000000000000, 0xc064281700000000, 0x80c9502e00000000, + 0x40ad783900000000, 0x0093a15c00000000, 0xc0f7894b00000000, + 0x805af17200000000, 0x403ed96500000000, 0x002643b900000000, + 0xc0426bae00000000, 0x80ef139700000000, 0x408b3b8000000000, + 0x00b5e2e500000000, 0xc0d1caf200000000, 0x807cb2cb00000000, + 0x40189adc00000000, 0x414af7a900000000, 0x812edfbe00000000, + 0xc183a78700000000, 0x01e78f9000000000, 0x41d956f500000000, + 0x81bd7ee200000000, 0xc11006db00000000, 0x01742ecc00000000, + 0x416cb41000000000, 0x81089c0700000000, 0xc1a5e43e00000000, + 0x01c1cc2900000000, 0x41ff154c00000000, 0x819b3d5b00000000, + 0xc136456200000000, 0x01526d7500000000, 0xc3929f8800000000, + 0x03f6b79f00000000, 0x435bcfa600000000, 0x833fe7b100000000, + 0xc3013ed400000000, 0x036516c300000000, 0x43c86efa00000000, + 0x83ac46ed00000000, 0xc3b4dc3100000000, 0x03d0f42600000000, + 0x437d8c1f00000000, 0x8319a40800000000, 0xc3277d6d00000000, + 0x0343557a00000000, 0x43ee2d4300000000, 0x838a055400000000, + 0x82d8682100000000, 0x42bc403600000000, 0x0211380f00000000, + 0xc275101800000000, 0x824bc97d00000000, 0x422fe16a00000000, + 0x0282995300000000, 0xc2e6b14400000000, 0x82fe2b9800000000, + 0x429a038f00000000, 0x02377bb600000000, 0xc25353a100000000, + 0x826d8ac400000000, 0x4209a2d300000000, 0x02a4daea00000000, + 0xc2c0f2fd00000000, 0xc7234eca00000000, 0x074766dd00000000, + 0x47ea1ee400000000, 0x878e36f300000000, 0xc7b0ef9600000000, + 0x07d4c78100000000, 0x4779bfb800000000, 0x871d97af00000000, + 0xc7050d7300000000, 0x0761256400000000, 0x47cc5d5d00000000, + 0x87a8754a00000000, 0xc796ac2f00000000, 0x07f2843800000000, + 0x475ffc0100000000, 0x873bd41600000000, 0x8669b96300000000, + 0x460d917400000000, 0x06a0e94d00000000, 0xc6c4c15a00000000, + 0x86fa183f00000000, 0x469e302800000000, 0x0633481100000000, + 0xc657600600000000, 0x864ffada00000000, 0x462bd2cd00000000, + 0x0686aaf400000000, 0xc6e282e300000000, 0x86dc5b8600000000, + 0x46b8739100000000, 0x06150ba800000000, 0xc67123bf00000000, + 0x04b1d14200000000, 0xc4d5f95500000000, 0x8478816c00000000, + 0x441ca97b00000000, 0x0422701e00000000, 0xc446580900000000, + 0x84eb203000000000, 0x448f082700000000, 0x049792fb00000000, + 0xc4f3baec00000000, 0x845ec2d500000000, 0x443aeac200000000, + 0x040433a700000000, 0xc4601bb000000000, 0x84cd638900000000, + 0x44a94b9e00000000, 0x45fb26eb00000000, 0x859f0efc00000000, + 0xc53276c500000000, 0x05565ed200000000, 0x456887b700000000, + 0x850cafa000000000, 0xc5a1d79900000000, 0x05c5ff8e00000000, + 0x45dd655200000000, 0x85b94d4500000000, 0xc514357c00000000, + 0x05701d6b00000000, 0x454ec40e00000000, 0x852aec1900000000, + 0xc587942000000000, 0x05e3bc3700000000, 0xcf41ed4f00000000, + 0x0f25c55800000000, 0x4f88bd6100000000, 0x8fec957600000000, + 0xcfd24c1300000000, 0x0fb6640400000000, 0x4f1b1c3d00000000, + 0x8f7f342a00000000, 0xcf67aef600000000, 0x0f0386e100000000, + 0x4faefed800000000, 0x8fcad6cf00000000, 0xcff40faa00000000, + 0x0f9027bd00000000, 0x4f3d5f8400000000, 0x8f59779300000000, + 0x8e0b1ae600000000, 0x4e6f32f100000000, 0x0ec24ac800000000, + 0xcea662df00000000, 0x8e98bbba00000000, 0x4efc93ad00000000, + 0x0e51eb9400000000, 0xce35c38300000000, 0x8e2d595f00000000, + 0x4e49714800000000, 0x0ee4097100000000, 0xce80216600000000, + 0x8ebef80300000000, 0x4edad01400000000, 0x0e77a82d00000000, + 0xce13803a00000000, 0x0cd372c700000000, 0xccb75ad000000000, + 0x8c1a22e900000000, 0x4c7e0afe00000000, 0x0c40d39b00000000, + 0xcc24fb8c00000000, 0x8c8983b500000000, 0x4cedaba200000000, + 0x0cf5317e00000000, 0xcc91196900000000, 0x8c3c615000000000, + 0x4c58494700000000, 0x0c66902200000000, 0xcc02b83500000000, + 0x8cafc00c00000000, 0x4ccbe81b00000000, 0x4d99856e00000000, + 0x8dfdad7900000000, 0xcd50d54000000000, 0x0d34fd5700000000, + 0x4d0a243200000000, 0x8d6e0c2500000000, 0xcdc3741c00000000, + 0x0da75c0b00000000, 0x4dbfc6d700000000, 0x8ddbeec000000000, + 0xcd7696f900000000, 0x0d12beee00000000, 0x4d2c678b00000000, + 0x8d484f9c00000000, 0xcde537a500000000, 0x0d811fb200000000, + 0x0862a38500000000, 0xc8068b9200000000, 0x88abf3ab00000000, + 0x48cfdbbc00000000, 0x08f102d900000000, 0xc8952ace00000000, + 0x883852f700000000, 0x485c7ae000000000, 0x0844e03c00000000, + 0xc820c82b00000000, 0x888db01200000000, 0x48e9980500000000, + 0x08d7416000000000, 0xc8b3697700000000, 0x881e114e00000000, + 0x487a395900000000, 0x4928542c00000000, 0x894c7c3b00000000, + 0xc9e1040200000000, 0x09852c1500000000, 0x49bbf57000000000, + 0x89dfdd6700000000, 0xc972a55e00000000, 0x09168d4900000000, + 0x490e179500000000, 0x896a3f8200000000, 0xc9c747bb00000000, + 0x09a36fac00000000, 0x499db6c900000000, 0x89f99ede00000000, + 0xc954e6e700000000, 0x0930cef000000000, 0xcbf03c0d00000000, + 0x0b94141a00000000, 0x4b396c2300000000, 0x8b5d443400000000, + 0xcb639d5100000000, 0x0b07b54600000000, 0x4baacd7f00000000, + 0x8bcee56800000000, 0xcbd67fb400000000, 0x0bb257a300000000, + 0x4b1f2f9a00000000, 0x8b7b078d00000000, 0xcb45dee800000000, + 0x0b21f6ff00000000, 0x4b8c8ec600000000, 0x8be8a6d100000000, + 0x8abacba400000000, 0x4adee3b300000000, 0x0a739b8a00000000, + 0xca17b39d00000000, 0x8a296af800000000, 0x4a4d42ef00000000, + 0x0ae03ad600000000, 0xca8412c100000000, 0x8a9c881d00000000, + 0x4af8a00a00000000, 0x0a55d83300000000, 0xca31f02400000000, + 0x8a0f294100000000, 0x4a6b015600000000, 0x0ac6796f00000000, + 0xcaa2517800000000}, + {0x0000000000000000, 0xd4ea739b00000000, 0xe9d396ed00000000, + 0x3d39e57600000000, 0x93a15c0000000000, 0x474b2f9b00000000, + 0x7a72caed00000000, 0xae98b97600000000, 0x2643b90000000000, + 0xf2a9ca9b00000000, 0xcf902fed00000000, 0x1b7a5c7600000000, + 0xb5e2e50000000000, 0x6108969b00000000, 0x5c3173ed00000000, + 0x88db007600000000, 0x4c86720100000000, 0x986c019a00000000, + 0xa555e4ec00000000, 0x71bf977700000000, 0xdf272e0100000000, + 0x0bcd5d9a00000000, 0x36f4b8ec00000000, 0xe21ecb7700000000, + 0x6ac5cb0100000000, 0xbe2fb89a00000000, 0x83165dec00000000, + 0x57fc2e7700000000, 0xf964970100000000, 0x2d8ee49a00000000, + 0x10b701ec00000000, 0xc45d727700000000, 0x980ce50200000000, + 0x4ce6969900000000, 0x71df73ef00000000, 0xa535007400000000, + 0x0badb90200000000, 0xdf47ca9900000000, 0xe27e2fef00000000, + 0x36945c7400000000, 0xbe4f5c0200000000, 0x6aa52f9900000000, + 0x579ccaef00000000, 0x8376b97400000000, 0x2dee000200000000, + 0xf904739900000000, 0xc43d96ef00000000, 0x10d7e57400000000, + 0xd48a970300000000, 0x0060e49800000000, 0x3d5901ee00000000, + 0xe9b3727500000000, 0x472bcb0300000000, 0x93c1b89800000000, + 0xaef85dee00000000, 0x7a122e7500000000, 0xf2c92e0300000000, + 0x26235d9800000000, 0x1b1ab8ee00000000, 0xcff0cb7500000000, + 0x6168720300000000, 0xb582019800000000, 0x88bbe4ee00000000, + 0x5c51977500000000, 0x3019ca0500000000, 0xe4f3b99e00000000, + 0xd9ca5ce800000000, 0x0d202f7300000000, 0xa3b8960500000000, + 0x7752e59e00000000, 0x4a6b00e800000000, 0x9e81737300000000, + 0x165a730500000000, 0xc2b0009e00000000, 0xff89e5e800000000, + 0x2b63967300000000, 0x85fb2f0500000000, 0x51115c9e00000000, + 0x6c28b9e800000000, 0xb8c2ca7300000000, 0x7c9fb80400000000, + 0xa875cb9f00000000, 0x954c2ee900000000, 0x41a65d7200000000, + 0xef3ee40400000000, 0x3bd4979f00000000, 0x06ed72e900000000, + 0xd207017200000000, 0x5adc010400000000, 0x8e36729f00000000, + 0xb30f97e900000000, 0x67e5e47200000000, 0xc97d5d0400000000, + 0x1d972e9f00000000, 0x20aecbe900000000, 0xf444b87200000000, + 0xa8152f0700000000, 0x7cff5c9c00000000, 0x41c6b9ea00000000, + 0x952cca7100000000, 0x3bb4730700000000, 0xef5e009c00000000, + 0xd267e5ea00000000, 0x068d967100000000, 0x8e56960700000000, + 0x5abce59c00000000, 0x678500ea00000000, 0xb36f737100000000, + 0x1df7ca0700000000, 0xc91db99c00000000, 0xf4245cea00000000, + 0x20ce2f7100000000, 0xe4935d0600000000, 0x30792e9d00000000, + 0x0d40cbeb00000000, 0xd9aab87000000000, 0x7732010600000000, + 0xa3d8729d00000000, 0x9ee197eb00000000, 0x4a0be47000000000, + 0xc2d0e40600000000, 0x163a979d00000000, 0x2b0372eb00000000, + 0xffe9017000000000, 0x5171b80600000000, 0x859bcb9d00000000, + 0xb8a22eeb00000000, 0x6c485d7000000000, 0x6032940b00000000, + 0xb4d8e79000000000, 0x89e102e600000000, 0x5d0b717d00000000, + 0xf393c80b00000000, 0x2779bb9000000000, 0x1a405ee600000000, + 0xceaa2d7d00000000, 0x46712d0b00000000, 0x929b5e9000000000, + 0xafa2bbe600000000, 0x7b48c87d00000000, 0xd5d0710b00000000, + 0x013a029000000000, 0x3c03e7e600000000, 0xe8e9947d00000000, + 0x2cb4e60a00000000, 0xf85e959100000000, 0xc56770e700000000, + 0x118d037c00000000, 0xbf15ba0a00000000, 0x6bffc99100000000, + 0x56c62ce700000000, 0x822c5f7c00000000, 0x0af75f0a00000000, + 0xde1d2c9100000000, 0xe324c9e700000000, 0x37ceba7c00000000, + 0x9956030a00000000, 0x4dbc709100000000, 0x708595e700000000, + 0xa46fe67c00000000, 0xf83e710900000000, 0x2cd4029200000000, + 0x11ede7e400000000, 0xc507947f00000000, 0x6b9f2d0900000000, + 0xbf755e9200000000, 0x824cbbe400000000, 0x56a6c87f00000000, + 0xde7dc80900000000, 0x0a97bb9200000000, 0x37ae5ee400000000, + 0xe3442d7f00000000, 0x4ddc940900000000, 0x9936e79200000000, + 0xa40f02e400000000, 0x70e5717f00000000, 0xb4b8030800000000, + 0x6052709300000000, 0x5d6b95e500000000, 0x8981e67e00000000, + 0x27195f0800000000, 0xf3f32c9300000000, 0xcecac9e500000000, + 0x1a20ba7e00000000, 0x92fbba0800000000, 0x4611c99300000000, + 0x7b282ce500000000, 0xafc25f7e00000000, 0x015ae60800000000, + 0xd5b0959300000000, 0xe88970e500000000, 0x3c63037e00000000, + 0x502b5e0e00000000, 0x84c12d9500000000, 0xb9f8c8e300000000, + 0x6d12bb7800000000, 0xc38a020e00000000, 0x1760719500000000, + 0x2a5994e300000000, 0xfeb3e77800000000, 0x7668e70e00000000, + 0xa282949500000000, 0x9fbb71e300000000, 0x4b51027800000000, + 0xe5c9bb0e00000000, 0x3123c89500000000, 0x0c1a2de300000000, + 0xd8f05e7800000000, 0x1cad2c0f00000000, 0xc8475f9400000000, + 0xf57ebae200000000, 0x2194c97900000000, 0x8f0c700f00000000, + 0x5be6039400000000, 0x66dfe6e200000000, 0xb235957900000000, + 0x3aee950f00000000, 0xee04e69400000000, 0xd33d03e200000000, + 0x07d7707900000000, 0xa94fc90f00000000, 0x7da5ba9400000000, + 0x409c5fe200000000, 0x94762c7900000000, 0xc827bb0c00000000, + 0x1ccdc89700000000, 0x21f42de100000000, 0xf51e5e7a00000000, + 0x5b86e70c00000000, 0x8f6c949700000000, 0xb25571e100000000, + 0x66bf027a00000000, 0xee64020c00000000, 0x3a8e719700000000, + 0x07b794e100000000, 0xd35de77a00000000, 0x7dc55e0c00000000, + 0xa92f2d9700000000, 0x9416c8e100000000, 0x40fcbb7a00000000, + 0x84a1c90d00000000, 0x504bba9600000000, 0x6d725fe000000000, + 0xb9982c7b00000000, 0x1700950d00000000, 0xc3eae69600000000, + 0xfed303e000000000, 0x2a39707b00000000, 0xa2e2700d00000000, + 0x7608039600000000, 0x4b31e6e000000000, 0x9fdb957b00000000, + 0x31432c0d00000000, 0xe5a95f9600000000, 0xd890bae000000000, + 0x0c7ac97b00000000}, + {0x0000000000000000, 0x2765258100000000, 0x0fcc3bd900000000, + 0x28a91e5800000000, 0x5f9e066900000000, 0x78fb23e800000000, + 0x50523db000000000, 0x7737183100000000, 0xbe3c0dd200000000, + 0x9959285300000000, 0xb1f0360b00000000, 0x9695138a00000000, + 0xe1a20bbb00000000, 0xc6c72e3a00000000, 0xee6e306200000000, + 0xc90b15e300000000, 0x3d7f6b7f00000000, 0x1a1a4efe00000000, + 0x32b350a600000000, 0x15d6752700000000, 0x62e16d1600000000, + 0x4584489700000000, 0x6d2d56cf00000000, 0x4a48734e00000000, + 0x834366ad00000000, 0xa426432c00000000, 0x8c8f5d7400000000, + 0xabea78f500000000, 0xdcdd60c400000000, 0xfbb8454500000000, + 0xd3115b1d00000000, 0xf4747e9c00000000, 0x7afed6fe00000000, + 0x5d9bf37f00000000, 0x7532ed2700000000, 0x5257c8a600000000, + 0x2560d09700000000, 0x0205f51600000000, 0x2aaceb4e00000000, + 0x0dc9cecf00000000, 0xc4c2db2c00000000, 0xe3a7fead00000000, + 0xcb0ee0f500000000, 0xec6bc57400000000, 0x9b5cdd4500000000, + 0xbc39f8c400000000, 0x9490e69c00000000, 0xb3f5c31d00000000, + 0x4781bd8100000000, 0x60e4980000000000, 0x484d865800000000, + 0x6f28a3d900000000, 0x181fbbe800000000, 0x3f7a9e6900000000, + 0x17d3803100000000, 0x30b6a5b000000000, 0xf9bdb05300000000, + 0xded895d200000000, 0xf6718b8a00000000, 0xd114ae0b00000000, + 0xa623b63a00000000, 0x814693bb00000000, 0xa9ef8de300000000, + 0x8e8aa86200000000, 0xb5fadc2600000000, 0x929ff9a700000000, + 0xba36e7ff00000000, 0x9d53c27e00000000, 0xea64da4f00000000, + 0xcd01ffce00000000, 0xe5a8e19600000000, 0xc2cdc41700000000, + 0x0bc6d1f400000000, 0x2ca3f47500000000, 0x040aea2d00000000, + 0x236fcfac00000000, 0x5458d79d00000000, 0x733df21c00000000, + 0x5b94ec4400000000, 0x7cf1c9c500000000, 0x8885b75900000000, + 0xafe092d800000000, 0x87498c8000000000, 0xa02ca90100000000, + 0xd71bb13000000000, 0xf07e94b100000000, 0xd8d78ae900000000, + 0xffb2af6800000000, 0x36b9ba8b00000000, 0x11dc9f0a00000000, + 0x3975815200000000, 0x1e10a4d300000000, 0x6927bce200000000, + 0x4e42996300000000, 0x66eb873b00000000, 0x418ea2ba00000000, + 0xcf040ad800000000, 0xe8612f5900000000, 0xc0c8310100000000, + 0xe7ad148000000000, 0x909a0cb100000000, 0xb7ff293000000000, + 0x9f56376800000000, 0xb83312e900000000, 0x7138070a00000000, + 0x565d228b00000000, 0x7ef43cd300000000, 0x5991195200000000, + 0x2ea6016300000000, 0x09c324e200000000, 0x216a3aba00000000, + 0x060f1f3b00000000, 0xf27b61a700000000, 0xd51e442600000000, + 0xfdb75a7e00000000, 0xdad27fff00000000, 0xade567ce00000000, + 0x8a80424f00000000, 0xa2295c1700000000, 0x854c799600000000, + 0x4c476c7500000000, 0x6b2249f400000000, 0x438b57ac00000000, + 0x64ee722d00000000, 0x13d96a1c00000000, 0x34bc4f9d00000000, + 0x1c1551c500000000, 0x3b70744400000000, 0x6af5b94d00000000, + 0x4d909ccc00000000, 0x6539829400000000, 0x425ca71500000000, + 0x356bbf2400000000, 0x120e9aa500000000, 0x3aa784fd00000000, + 0x1dc2a17c00000000, 0xd4c9b49f00000000, 0xf3ac911e00000000, + 0xdb058f4600000000, 0xfc60aac700000000, 0x8b57b2f600000000, + 0xac32977700000000, 0x849b892f00000000, 0xa3feacae00000000, + 0x578ad23200000000, 0x70eff7b300000000, 0x5846e9eb00000000, + 0x7f23cc6a00000000, 0x0814d45b00000000, 0x2f71f1da00000000, + 0x07d8ef8200000000, 0x20bdca0300000000, 0xe9b6dfe000000000, + 0xced3fa6100000000, 0xe67ae43900000000, 0xc11fc1b800000000, + 0xb628d98900000000, 0x914dfc0800000000, 0xb9e4e25000000000, + 0x9e81c7d100000000, 0x100b6fb300000000, 0x376e4a3200000000, + 0x1fc7546a00000000, 0x38a271eb00000000, 0x4f9569da00000000, + 0x68f04c5b00000000, 0x4059520300000000, 0x673c778200000000, + 0xae37626100000000, 0x895247e000000000, 0xa1fb59b800000000, + 0x869e7c3900000000, 0xf1a9640800000000, 0xd6cc418900000000, + 0xfe655fd100000000, 0xd9007a5000000000, 0x2d7404cc00000000, + 0x0a11214d00000000, 0x22b83f1500000000, 0x05dd1a9400000000, + 0x72ea02a500000000, 0x558f272400000000, 0x7d26397c00000000, + 0x5a431cfd00000000, 0x9348091e00000000, 0xb42d2c9f00000000, + 0x9c8432c700000000, 0xbbe1174600000000, 0xccd60f7700000000, + 0xebb32af600000000, 0xc31a34ae00000000, 0xe47f112f00000000, + 0xdf0f656b00000000, 0xf86a40ea00000000, 0xd0c35eb200000000, + 0xf7a67b3300000000, 0x8091630200000000, 0xa7f4468300000000, + 0x8f5d58db00000000, 0xa8387d5a00000000, 0x613368b900000000, + 0x46564d3800000000, 0x6eff536000000000, 0x499a76e100000000, + 0x3ead6ed000000000, 0x19c84b5100000000, 0x3161550900000000, + 0x1604708800000000, 0xe2700e1400000000, 0xc5152b9500000000, + 0xedbc35cd00000000, 0xcad9104c00000000, 0xbdee087d00000000, + 0x9a8b2dfc00000000, 0xb22233a400000000, 0x9547162500000000, + 0x5c4c03c600000000, 0x7b29264700000000, 0x5380381f00000000, + 0x74e51d9e00000000, 0x03d205af00000000, 0x24b7202e00000000, + 0x0c1e3e7600000000, 0x2b7b1bf700000000, 0xa5f1b39500000000, + 0x8294961400000000, 0xaa3d884c00000000, 0x8d58adcd00000000, + 0xfa6fb5fc00000000, 0xdd0a907d00000000, 0xf5a38e2500000000, + 0xd2c6aba400000000, 0x1bcdbe4700000000, 0x3ca89bc600000000, + 0x1401859e00000000, 0x3364a01f00000000, 0x4453b82e00000000, + 0x63369daf00000000, 0x4b9f83f700000000, 0x6cfaa67600000000, + 0x988ed8ea00000000, 0xbfebfd6b00000000, 0x9742e33300000000, + 0xb027c6b200000000, 0xc710de8300000000, 0xe075fb0200000000, + 0xc8dce55a00000000, 0xefb9c0db00000000, 0x26b2d53800000000, + 0x01d7f0b900000000, 0x297eeee100000000, 0x0e1bcb6000000000, + 0x792cd35100000000, 0x5e49f6d000000000, 0x76e0e88800000000, + 0x5185cd0900000000}}; + +#else /* W == 4 */ + +local const z_crc_t FAR crc_braid_table[][256] = { + {0x00000000, 0x9ba54c6f, 0xec3b9e9f, 0x779ed2f0, 0x03063b7f, + 0x98a37710, 0xef3da5e0, 0x7498e98f, 0x060c76fe, 0x9da93a91, + 0xea37e861, 0x7192a40e, 0x050a4d81, 0x9eaf01ee, 0xe931d31e, + 0x72949f71, 0x0c18edfc, 0x97bda193, 0xe0237363, 0x7b863f0c, + 0x0f1ed683, 0x94bb9aec, 0xe325481c, 0x78800473, 0x0a149b02, + 0x91b1d76d, 0xe62f059d, 0x7d8a49f2, 0x0912a07d, 0x92b7ec12, + 0xe5293ee2, 0x7e8c728d, 0x1831dbf8, 0x83949797, 0xf40a4567, + 0x6faf0908, 0x1b37e087, 0x8092ace8, 0xf70c7e18, 0x6ca93277, + 0x1e3dad06, 0x8598e169, 0xf2063399, 0x69a37ff6, 0x1d3b9679, + 0x869eda16, 0xf10008e6, 0x6aa54489, 0x14293604, 0x8f8c7a6b, + 0xf812a89b, 0x63b7e4f4, 0x172f0d7b, 0x8c8a4114, 0xfb1493e4, + 0x60b1df8b, 0x122540fa, 0x89800c95, 0xfe1ede65, 0x65bb920a, + 0x11237b85, 0x8a8637ea, 0xfd18e51a, 0x66bda975, 0x3063b7f0, + 0xabc6fb9f, 0xdc58296f, 0x47fd6500, 0x33658c8f, 0xa8c0c0e0, + 0xdf5e1210, 0x44fb5e7f, 0x366fc10e, 0xadca8d61, 0xda545f91, + 0x41f113fe, 0x3569fa71, 0xaeccb61e, 0xd95264ee, 0x42f72881, + 0x3c7b5a0c, 0xa7de1663, 0xd040c493, 0x4be588fc, 0x3f7d6173, + 0xa4d82d1c, 0xd346ffec, 0x48e3b383, 0x3a772cf2, 0xa1d2609d, + 0xd64cb26d, 0x4de9fe02, 0x3971178d, 0xa2d45be2, 0xd54a8912, + 0x4eefc57d, 0x28526c08, 0xb3f72067, 0xc469f297, 0x5fccbef8, + 0x2b545777, 0xb0f11b18, 0xc76fc9e8, 0x5cca8587, 0x2e5e1af6, + 0xb5fb5699, 0xc2658469, 0x59c0c806, 0x2d582189, 0xb6fd6de6, + 0xc163bf16, 0x5ac6f379, 0x244a81f4, 0xbfefcd9b, 0xc8711f6b, + 0x53d45304, 0x274cba8b, 0xbce9f6e4, 0xcb772414, 0x50d2687b, + 0x2246f70a, 0xb9e3bb65, 0xce7d6995, 0x55d825fa, 0x2140cc75, + 0xbae5801a, 0xcd7b52ea, 0x56de1e85, 0x60c76fe0, 0xfb62238f, + 0x8cfcf17f, 0x1759bd10, 0x63c1549f, 0xf86418f0, 0x8ffaca00, + 0x145f866f, 0x66cb191e, 0xfd6e5571, 0x8af08781, 0x1155cbee, + 0x65cd2261, 0xfe686e0e, 0x89f6bcfe, 0x1253f091, 0x6cdf821c, + 0xf77ace73, 0x80e41c83, 0x1b4150ec, 0x6fd9b963, 0xf47cf50c, + 0x83e227fc, 0x18476b93, 0x6ad3f4e2, 0xf176b88d, 0x86e86a7d, + 0x1d4d2612, 0x69d5cf9d, 0xf27083f2, 0x85ee5102, 0x1e4b1d6d, + 0x78f6b418, 0xe353f877, 0x94cd2a87, 0x0f6866e8, 0x7bf08f67, + 0xe055c308, 0x97cb11f8, 0x0c6e5d97, 0x7efac2e6, 0xe55f8e89, + 0x92c15c79, 0x09641016, 0x7dfcf999, 0xe659b5f6, 0x91c76706, + 0x0a622b69, 0x74ee59e4, 0xef4b158b, 0x98d5c77b, 0x03708b14, + 0x77e8629b, 0xec4d2ef4, 0x9bd3fc04, 0x0076b06b, 0x72e22f1a, + 0xe9476375, 0x9ed9b185, 0x057cfdea, 0x71e41465, 0xea41580a, + 0x9ddf8afa, 0x067ac695, 0x50a4d810, 0xcb01947f, 0xbc9f468f, + 0x273a0ae0, 0x53a2e36f, 0xc807af00, 0xbf997df0, 0x243c319f, + 0x56a8aeee, 0xcd0de281, 0xba933071, 0x21367c1e, 0x55ae9591, + 0xce0bd9fe, 0xb9950b0e, 0x22304761, 0x5cbc35ec, 0xc7197983, + 0xb087ab73, 0x2b22e71c, 0x5fba0e93, 0xc41f42fc, 0xb381900c, + 0x2824dc63, 0x5ab04312, 0xc1150f7d, 0xb68bdd8d, 0x2d2e91e2, + 0x59b6786d, 0xc2133402, 0xb58de6f2, 0x2e28aa9d, 0x489503e8, + 0xd3304f87, 0xa4ae9d77, 0x3f0bd118, 0x4b933897, 0xd03674f8, + 0xa7a8a608, 0x3c0dea67, 0x4e997516, 0xd53c3979, 0xa2a2eb89, + 0x3907a7e6, 0x4d9f4e69, 0xd63a0206, 0xa1a4d0f6, 0x3a019c99, + 0x448dee14, 0xdf28a27b, 0xa8b6708b, 0x33133ce4, 0x478bd56b, + 0xdc2e9904, 0xabb04bf4, 0x3015079b, 0x428198ea, 0xd924d485, + 0xaeba0675, 0x351f4a1a, 0x4187a395, 0xda22effa, 0xadbc3d0a, + 0x36197165}, + {0x00000000, 0xc18edfc0, 0x586cb9c1, 0x99e26601, 0xb0d97382, + 0x7157ac42, 0xe8b5ca43, 0x293b1583, 0xbac3e145, 0x7b4d3e85, + 0xe2af5884, 0x23218744, 0x0a1a92c7, 0xcb944d07, 0x52762b06, + 0x93f8f4c6, 0xaef6c4cb, 0x6f781b0b, 0xf69a7d0a, 0x3714a2ca, + 0x1e2fb749, 0xdfa16889, 0x46430e88, 0x87cdd148, 0x1435258e, + 0xd5bbfa4e, 0x4c599c4f, 0x8dd7438f, 0xa4ec560c, 0x656289cc, + 0xfc80efcd, 0x3d0e300d, 0x869c8fd7, 0x47125017, 0xdef03616, + 0x1f7ee9d6, 0x3645fc55, 0xf7cb2395, 0x6e294594, 0xafa79a54, + 0x3c5f6e92, 0xfdd1b152, 0x6433d753, 0xa5bd0893, 0x8c861d10, + 0x4d08c2d0, 0xd4eaa4d1, 0x15647b11, 0x286a4b1c, 0xe9e494dc, + 0x7006f2dd, 0xb1882d1d, 0x98b3389e, 0x593de75e, 0xc0df815f, + 0x01515e9f, 0x92a9aa59, 0x53277599, 0xcac51398, 0x0b4bcc58, + 0x2270d9db, 0xe3fe061b, 0x7a1c601a, 0xbb92bfda, 0xd64819ef, + 0x17c6c62f, 0x8e24a02e, 0x4faa7fee, 0x66916a6d, 0xa71fb5ad, + 0x3efdd3ac, 0xff730c6c, 0x6c8bf8aa, 0xad05276a, 0x34e7416b, + 0xf5699eab, 0xdc528b28, 0x1ddc54e8, 0x843e32e9, 0x45b0ed29, + 0x78bedd24, 0xb93002e4, 0x20d264e5, 0xe15cbb25, 0xc867aea6, + 0x09e97166, 0x900b1767, 0x5185c8a7, 0xc27d3c61, 0x03f3e3a1, + 0x9a1185a0, 0x5b9f5a60, 0x72a44fe3, 0xb32a9023, 0x2ac8f622, + 0xeb4629e2, 0x50d49638, 0x915a49f8, 0x08b82ff9, 0xc936f039, + 0xe00de5ba, 0x21833a7a, 0xb8615c7b, 0x79ef83bb, 0xea17777d, + 0x2b99a8bd, 0xb27bcebc, 0x73f5117c, 0x5ace04ff, 0x9b40db3f, + 0x02a2bd3e, 0xc32c62fe, 0xfe2252f3, 0x3fac8d33, 0xa64eeb32, + 0x67c034f2, 0x4efb2171, 0x8f75feb1, 0x169798b0, 0xd7194770, + 0x44e1b3b6, 0x856f6c76, 0x1c8d0a77, 0xdd03d5b7, 0xf438c034, + 0x35b61ff4, 0xac5479f5, 0x6ddaa635, 0x77e1359f, 0xb66fea5f, + 0x2f8d8c5e, 0xee03539e, 0xc738461d, 0x06b699dd, 0x9f54ffdc, + 0x5eda201c, 0xcd22d4da, 0x0cac0b1a, 0x954e6d1b, 0x54c0b2db, + 0x7dfba758, 0xbc757898, 0x25971e99, 0xe419c159, 0xd917f154, + 0x18992e94, 0x817b4895, 0x40f59755, 0x69ce82d6, 0xa8405d16, + 0x31a23b17, 0xf02ce4d7, 0x63d41011, 0xa25acfd1, 0x3bb8a9d0, + 0xfa367610, 0xd30d6393, 0x1283bc53, 0x8b61da52, 0x4aef0592, + 0xf17dba48, 0x30f36588, 0xa9110389, 0x689fdc49, 0x41a4c9ca, + 0x802a160a, 0x19c8700b, 0xd846afcb, 0x4bbe5b0d, 0x8a3084cd, + 0x13d2e2cc, 0xd25c3d0c, 0xfb67288f, 0x3ae9f74f, 0xa30b914e, + 0x62854e8e, 0x5f8b7e83, 0x9e05a143, 0x07e7c742, 0xc6691882, + 0xef520d01, 0x2edcd2c1, 0xb73eb4c0, 0x76b06b00, 0xe5489fc6, + 0x24c64006, 0xbd242607, 0x7caaf9c7, 0x5591ec44, 0x941f3384, + 0x0dfd5585, 0xcc738a45, 0xa1a92c70, 0x6027f3b0, 0xf9c595b1, + 0x384b4a71, 0x11705ff2, 0xd0fe8032, 0x491ce633, 0x889239f3, + 0x1b6acd35, 0xdae412f5, 0x430674f4, 0x8288ab34, 0xabb3beb7, + 0x6a3d6177, 0xf3df0776, 0x3251d8b6, 0x0f5fe8bb, 0xced1377b, + 0x5733517a, 0x96bd8eba, 0xbf869b39, 0x7e0844f9, 0xe7ea22f8, + 0x2664fd38, 0xb59c09fe, 0x7412d63e, 0xedf0b03f, 0x2c7e6fff, + 0x05457a7c, 0xc4cba5bc, 0x5d29c3bd, 0x9ca71c7d, 0x2735a3a7, + 0xe6bb7c67, 0x7f591a66, 0xbed7c5a6, 0x97ecd025, 0x56620fe5, + 0xcf8069e4, 0x0e0eb624, 0x9df642e2, 0x5c789d22, 0xc59afb23, + 0x041424e3, 0x2d2f3160, 0xeca1eea0, 0x754388a1, 0xb4cd5761, + 0x89c3676c, 0x484db8ac, 0xd1afdead, 0x1021016d, 0x391a14ee, + 0xf894cb2e, 0x6176ad2f, 0xa0f872ef, 0x33008629, 0xf28e59e9, + 0x6b6c3fe8, 0xaae2e028, 0x83d9f5ab, 0x42572a6b, 0xdbb54c6a, + 0x1a3b93aa}, + {0x00000000, 0xefc26b3e, 0x04f5d03d, 0xeb37bb03, 0x09eba07a, + 0xe629cb44, 0x0d1e7047, 0xe2dc1b79, 0x13d740f4, 0xfc152bca, + 0x172290c9, 0xf8e0fbf7, 0x1a3ce08e, 0xf5fe8bb0, 0x1ec930b3, + 0xf10b5b8d, 0x27ae81e8, 0xc86cead6, 0x235b51d5, 0xcc993aeb, + 0x2e452192, 0xc1874aac, 0x2ab0f1af, 0xc5729a91, 0x3479c11c, + 0xdbbbaa22, 0x308c1121, 0xdf4e7a1f, 0x3d926166, 0xd2500a58, + 0x3967b15b, 0xd6a5da65, 0x4f5d03d0, 0xa09f68ee, 0x4ba8d3ed, + 0xa46ab8d3, 0x46b6a3aa, 0xa974c894, 0x42437397, 0xad8118a9, + 0x5c8a4324, 0xb348281a, 0x587f9319, 0xb7bdf827, 0x5561e35e, + 0xbaa38860, 0x51943363, 0xbe56585d, 0x68f38238, 0x8731e906, + 0x6c065205, 0x83c4393b, 0x61182242, 0x8eda497c, 0x65edf27f, + 0x8a2f9941, 0x7b24c2cc, 0x94e6a9f2, 0x7fd112f1, 0x901379cf, + 0x72cf62b6, 0x9d0d0988, 0x763ab28b, 0x99f8d9b5, 0x9eba07a0, + 0x71786c9e, 0x9a4fd79d, 0x758dbca3, 0x9751a7da, 0x7893cce4, + 0x93a477e7, 0x7c661cd9, 0x8d6d4754, 0x62af2c6a, 0x89989769, + 0x665afc57, 0x8486e72e, 0x6b448c10, 0x80733713, 0x6fb15c2d, + 0xb9148648, 0x56d6ed76, 0xbde15675, 0x52233d4b, 0xb0ff2632, + 0x5f3d4d0c, 0xb40af60f, 0x5bc89d31, 0xaac3c6bc, 0x4501ad82, + 0xae361681, 0x41f47dbf, 0xa32866c6, 0x4cea0df8, 0xa7ddb6fb, + 0x481fddc5, 0xd1e70470, 0x3e256f4e, 0xd512d44d, 0x3ad0bf73, + 0xd80ca40a, 0x37cecf34, 0xdcf97437, 0x333b1f09, 0xc2304484, + 0x2df22fba, 0xc6c594b9, 0x2907ff87, 0xcbdbe4fe, 0x24198fc0, + 0xcf2e34c3, 0x20ec5ffd, 0xf6498598, 0x198beea6, 0xf2bc55a5, + 0x1d7e3e9b, 0xffa225e2, 0x10604edc, 0xfb57f5df, 0x14959ee1, + 0xe59ec56c, 0x0a5cae52, 0xe16b1551, 0x0ea97e6f, 0xec756516, + 0x03b70e28, 0xe880b52b, 0x0742de15, 0xe6050901, 0x09c7623f, + 0xe2f0d93c, 0x0d32b202, 0xefeea97b, 0x002cc245, 0xeb1b7946, + 0x04d91278, 0xf5d249f5, 0x1a1022cb, 0xf12799c8, 0x1ee5f2f6, + 0xfc39e98f, 0x13fb82b1, 0xf8cc39b2, 0x170e528c, 0xc1ab88e9, + 0x2e69e3d7, 0xc55e58d4, 0x2a9c33ea, 0xc8402893, 0x278243ad, + 0xccb5f8ae, 0x23779390, 0xd27cc81d, 0x3dbea323, 0xd6891820, + 0x394b731e, 0xdb976867, 0x34550359, 0xdf62b85a, 0x30a0d364, + 0xa9580ad1, 0x469a61ef, 0xadaddaec, 0x426fb1d2, 0xa0b3aaab, + 0x4f71c195, 0xa4467a96, 0x4b8411a8, 0xba8f4a25, 0x554d211b, + 0xbe7a9a18, 0x51b8f126, 0xb364ea5f, 0x5ca68161, 0xb7913a62, + 0x5853515c, 0x8ef68b39, 0x6134e007, 0x8a035b04, 0x65c1303a, + 0x871d2b43, 0x68df407d, 0x83e8fb7e, 0x6c2a9040, 0x9d21cbcd, + 0x72e3a0f3, 0x99d41bf0, 0x761670ce, 0x94ca6bb7, 0x7b080089, + 0x903fbb8a, 0x7ffdd0b4, 0x78bf0ea1, 0x977d659f, 0x7c4ade9c, + 0x9388b5a2, 0x7154aedb, 0x9e96c5e5, 0x75a17ee6, 0x9a6315d8, + 0x6b684e55, 0x84aa256b, 0x6f9d9e68, 0x805ff556, 0x6283ee2f, + 0x8d418511, 0x66763e12, 0x89b4552c, 0x5f118f49, 0xb0d3e477, + 0x5be45f74, 0xb426344a, 0x56fa2f33, 0xb938440d, 0x520fff0e, + 0xbdcd9430, 0x4cc6cfbd, 0xa304a483, 0x48331f80, 0xa7f174be, + 0x452d6fc7, 0xaaef04f9, 0x41d8bffa, 0xae1ad4c4, 0x37e20d71, + 0xd820664f, 0x3317dd4c, 0xdcd5b672, 0x3e09ad0b, 0xd1cbc635, + 0x3afc7d36, 0xd53e1608, 0x24354d85, 0xcbf726bb, 0x20c09db8, + 0xcf02f686, 0x2ddeedff, 0xc21c86c1, 0x292b3dc2, 0xc6e956fc, + 0x104c8c99, 0xff8ee7a7, 0x14b95ca4, 0xfb7b379a, 0x19a72ce3, + 0xf66547dd, 0x1d52fcde, 0xf29097e0, 0x039bcc6d, 0xec59a753, + 0x076e1c50, 0xe8ac776e, 0x0a706c17, 0xe5b20729, 0x0e85bc2a, + 0xe147d714}, + {0x00000000, 0x177b1443, 0x2ef62886, 0x398d3cc5, 0x5dec510c, + 0x4a97454f, 0x731a798a, 0x64616dc9, 0xbbd8a218, 0xaca3b65b, + 0x952e8a9e, 0x82559edd, 0xe634f314, 0xf14fe757, 0xc8c2db92, + 0xdfb9cfd1, 0xacc04271, 0xbbbb5632, 0x82366af7, 0x954d7eb4, + 0xf12c137d, 0xe657073e, 0xdfda3bfb, 0xc8a12fb8, 0x1718e069, + 0x0063f42a, 0x39eec8ef, 0x2e95dcac, 0x4af4b165, 0x5d8fa526, + 0x640299e3, 0x73798da0, 0x82f182a3, 0x958a96e0, 0xac07aa25, + 0xbb7cbe66, 0xdf1dd3af, 0xc866c7ec, 0xf1ebfb29, 0xe690ef6a, + 0x392920bb, 0x2e5234f8, 0x17df083d, 0x00a41c7e, 0x64c571b7, + 0x73be65f4, 0x4a335931, 0x5d484d72, 0x2e31c0d2, 0x394ad491, + 0x00c7e854, 0x17bcfc17, 0x73dd91de, 0x64a6859d, 0x5d2bb958, + 0x4a50ad1b, 0x95e962ca, 0x82927689, 0xbb1f4a4c, 0xac645e0f, + 0xc80533c6, 0xdf7e2785, 0xe6f31b40, 0xf1880f03, 0xde920307, + 0xc9e91744, 0xf0642b81, 0xe71f3fc2, 0x837e520b, 0x94054648, + 0xad887a8d, 0xbaf36ece, 0x654aa11f, 0x7231b55c, 0x4bbc8999, + 0x5cc79dda, 0x38a6f013, 0x2fdde450, 0x1650d895, 0x012bccd6, + 0x72524176, 0x65295535, 0x5ca469f0, 0x4bdf7db3, 0x2fbe107a, + 0x38c50439, 0x014838fc, 0x16332cbf, 0xc98ae36e, 0xdef1f72d, + 0xe77ccbe8, 0xf007dfab, 0x9466b262, 0x831da621, 0xba909ae4, + 0xadeb8ea7, 0x5c6381a4, 0x4b1895e7, 0x7295a922, 0x65eebd61, + 0x018fd0a8, 0x16f4c4eb, 0x2f79f82e, 0x3802ec6d, 0xe7bb23bc, + 0xf0c037ff, 0xc94d0b3a, 0xde361f79, 0xba5772b0, 0xad2c66f3, + 0x94a15a36, 0x83da4e75, 0xf0a3c3d5, 0xe7d8d796, 0xde55eb53, + 0xc92eff10, 0xad4f92d9, 0xba34869a, 0x83b9ba5f, 0x94c2ae1c, + 0x4b7b61cd, 0x5c00758e, 0x658d494b, 0x72f65d08, 0x169730c1, + 0x01ec2482, 0x38611847, 0x2f1a0c04, 0x6655004f, 0x712e140c, + 0x48a328c9, 0x5fd83c8a, 0x3bb95143, 0x2cc24500, 0x154f79c5, + 0x02346d86, 0xdd8da257, 0xcaf6b614, 0xf37b8ad1, 0xe4009e92, + 0x8061f35b, 0x971ae718, 0xae97dbdd, 0xb9eccf9e, 0xca95423e, + 0xddee567d, 0xe4636ab8, 0xf3187efb, 0x97791332, 0x80020771, + 0xb98f3bb4, 0xaef42ff7, 0x714de026, 0x6636f465, 0x5fbbc8a0, + 0x48c0dce3, 0x2ca1b12a, 0x3bdaa569, 0x025799ac, 0x152c8def, + 0xe4a482ec, 0xf3df96af, 0xca52aa6a, 0xdd29be29, 0xb948d3e0, + 0xae33c7a3, 0x97befb66, 0x80c5ef25, 0x5f7c20f4, 0x480734b7, + 0x718a0872, 0x66f11c31, 0x029071f8, 0x15eb65bb, 0x2c66597e, + 0x3b1d4d3d, 0x4864c09d, 0x5f1fd4de, 0x6692e81b, 0x71e9fc58, + 0x15889191, 0x02f385d2, 0x3b7eb917, 0x2c05ad54, 0xf3bc6285, + 0xe4c776c6, 0xdd4a4a03, 0xca315e40, 0xae503389, 0xb92b27ca, + 0x80a61b0f, 0x97dd0f4c, 0xb8c70348, 0xafbc170b, 0x96312bce, + 0x814a3f8d, 0xe52b5244, 0xf2504607, 0xcbdd7ac2, 0xdca66e81, + 0x031fa150, 0x1464b513, 0x2de989d6, 0x3a929d95, 0x5ef3f05c, + 0x4988e41f, 0x7005d8da, 0x677ecc99, 0x14074139, 0x037c557a, + 0x3af169bf, 0x2d8a7dfc, 0x49eb1035, 0x5e900476, 0x671d38b3, + 0x70662cf0, 0xafdfe321, 0xb8a4f762, 0x8129cba7, 0x9652dfe4, + 0xf233b22d, 0xe548a66e, 0xdcc59aab, 0xcbbe8ee8, 0x3a3681eb, + 0x2d4d95a8, 0x14c0a96d, 0x03bbbd2e, 0x67dad0e7, 0x70a1c4a4, + 0x492cf861, 0x5e57ec22, 0x81ee23f3, 0x969537b0, 0xaf180b75, + 0xb8631f36, 0xdc0272ff, 0xcb7966bc, 0xf2f45a79, 0xe58f4e3a, + 0x96f6c39a, 0x818dd7d9, 0xb800eb1c, 0xaf7bff5f, 0xcb1a9296, + 0xdc6186d5, 0xe5ecba10, 0xf297ae53, 0x2d2e6182, 0x3a5575c1, + 0x03d84904, 0x14a35d47, 0x70c2308e, 0x67b924cd, 0x5e341808, + 0x494f0c4b}}; + +local const z_word_t FAR crc_braid_big_table[][256] = { + {0x00000000, 0x43147b17, 0x8628f62e, 0xc53c8d39, 0x0c51ec5d, + 0x4f45974a, 0x8a791a73, 0xc96d6164, 0x18a2d8bb, 0x5bb6a3ac, + 0x9e8a2e95, 0xdd9e5582, 0x14f334e6, 0x57e74ff1, 0x92dbc2c8, + 0xd1cfb9df, 0x7142c0ac, 0x3256bbbb, 0xf76a3682, 0xb47e4d95, + 0x7d132cf1, 0x3e0757e6, 0xfb3bdadf, 0xb82fa1c8, 0x69e01817, + 0x2af46300, 0xefc8ee39, 0xacdc952e, 0x65b1f44a, 0x26a58f5d, + 0xe3990264, 0xa08d7973, 0xa382f182, 0xe0968a95, 0x25aa07ac, + 0x66be7cbb, 0xafd31ddf, 0xecc766c8, 0x29fbebf1, 0x6aef90e6, + 0xbb202939, 0xf834522e, 0x3d08df17, 0x7e1ca400, 0xb771c564, + 0xf465be73, 0x3159334a, 0x724d485d, 0xd2c0312e, 0x91d44a39, + 0x54e8c700, 0x17fcbc17, 0xde91dd73, 0x9d85a664, 0x58b92b5d, + 0x1bad504a, 0xca62e995, 0x89769282, 0x4c4a1fbb, 0x0f5e64ac, + 0xc63305c8, 0x85277edf, 0x401bf3e6, 0x030f88f1, 0x070392de, + 0x4417e9c9, 0x812b64f0, 0xc23f1fe7, 0x0b527e83, 0x48460594, + 0x8d7a88ad, 0xce6ef3ba, 0x1fa14a65, 0x5cb53172, 0x9989bc4b, + 0xda9dc75c, 0x13f0a638, 0x50e4dd2f, 0x95d85016, 0xd6cc2b01, + 0x76415272, 0x35552965, 0xf069a45c, 0xb37ddf4b, 0x7a10be2f, + 0x3904c538, 0xfc384801, 0xbf2c3316, 0x6ee38ac9, 0x2df7f1de, + 0xe8cb7ce7, 0xabdf07f0, 0x62b26694, 0x21a61d83, 0xe49a90ba, + 0xa78eebad, 0xa481635c, 0xe795184b, 0x22a99572, 0x61bdee65, + 0xa8d08f01, 0xebc4f416, 0x2ef8792f, 0x6dec0238, 0xbc23bbe7, + 0xff37c0f0, 0x3a0b4dc9, 0x791f36de, 0xb07257ba, 0xf3662cad, + 0x365aa194, 0x754eda83, 0xd5c3a3f0, 0x96d7d8e7, 0x53eb55de, + 0x10ff2ec9, 0xd9924fad, 0x9a8634ba, 0x5fbab983, 0x1caec294, + 0xcd617b4b, 0x8e75005c, 0x4b498d65, 0x085df672, 0xc1309716, + 0x8224ec01, 0x47186138, 0x040c1a2f, 0x4f005566, 0x0c142e71, + 0xc928a348, 0x8a3cd85f, 0x4351b93b, 0x0045c22c, 0xc5794f15, + 0x866d3402, 0x57a28ddd, 0x14b6f6ca, 0xd18a7bf3, 0x929e00e4, + 0x5bf36180, 0x18e71a97, 0xdddb97ae, 0x9ecfecb9, 0x3e4295ca, + 0x7d56eedd, 0xb86a63e4, 0xfb7e18f3, 0x32137997, 0x71070280, + 0xb43b8fb9, 0xf72ff4ae, 0x26e04d71, 0x65f43666, 0xa0c8bb5f, + 0xe3dcc048, 0x2ab1a12c, 0x69a5da3b, 0xac995702, 0xef8d2c15, + 0xec82a4e4, 0xaf96dff3, 0x6aaa52ca, 0x29be29dd, 0xe0d348b9, + 0xa3c733ae, 0x66fbbe97, 0x25efc580, 0xf4207c5f, 0xb7340748, + 0x72088a71, 0x311cf166, 0xf8719002, 0xbb65eb15, 0x7e59662c, + 0x3d4d1d3b, 0x9dc06448, 0xded41f5f, 0x1be89266, 0x58fce971, + 0x91918815, 0xd285f302, 0x17b97e3b, 0x54ad052c, 0x8562bcf3, + 0xc676c7e4, 0x034a4add, 0x405e31ca, 0x893350ae, 0xca272bb9, + 0x0f1ba680, 0x4c0fdd97, 0x4803c7b8, 0x0b17bcaf, 0xce2b3196, + 0x8d3f4a81, 0x44522be5, 0x074650f2, 0xc27addcb, 0x816ea6dc, + 0x50a11f03, 0x13b56414, 0xd689e92d, 0x959d923a, 0x5cf0f35e, + 0x1fe48849, 0xdad80570, 0x99cc7e67, 0x39410714, 0x7a557c03, + 0xbf69f13a, 0xfc7d8a2d, 0x3510eb49, 0x7604905e, 0xb3381d67, + 0xf02c6670, 0x21e3dfaf, 0x62f7a4b8, 0xa7cb2981, 0xe4df5296, + 0x2db233f2, 0x6ea648e5, 0xab9ac5dc, 0xe88ebecb, 0xeb81363a, + 0xa8954d2d, 0x6da9c014, 0x2ebdbb03, 0xe7d0da67, 0xa4c4a170, + 0x61f82c49, 0x22ec575e, 0xf323ee81, 0xb0379596, 0x750b18af, + 0x361f63b8, 0xff7202dc, 0xbc6679cb, 0x795af4f2, 0x3a4e8fe5, + 0x9ac3f696, 0xd9d78d81, 0x1ceb00b8, 0x5fff7baf, 0x96921acb, + 0xd58661dc, 0x10baece5, 0x53ae97f2, 0x82612e2d, 0xc175553a, + 0x0449d803, 0x475da314, 0x8e30c270, 0xcd24b967, 0x0818345e, + 0x4b0c4f49}, + {0x00000000, 0x3e6bc2ef, 0x3dd0f504, 0x03bb37eb, 0x7aa0eb09, + 0x44cb29e6, 0x47701e0d, 0x791bdce2, 0xf440d713, 0xca2b15fc, + 0xc9902217, 0xf7fbe0f8, 0x8ee03c1a, 0xb08bfef5, 0xb330c91e, + 0x8d5b0bf1, 0xe881ae27, 0xd6ea6cc8, 0xd5515b23, 0xeb3a99cc, + 0x9221452e, 0xac4a87c1, 0xaff1b02a, 0x919a72c5, 0x1cc17934, + 0x22aabbdb, 0x21118c30, 0x1f7a4edf, 0x6661923d, 0x580a50d2, + 0x5bb16739, 0x65daa5d6, 0xd0035d4f, 0xee689fa0, 0xedd3a84b, + 0xd3b86aa4, 0xaaa3b646, 0x94c874a9, 0x97734342, 0xa91881ad, + 0x24438a5c, 0x1a2848b3, 0x19937f58, 0x27f8bdb7, 0x5ee36155, + 0x6088a3ba, 0x63339451, 0x5d5856be, 0x3882f368, 0x06e93187, + 0x0552066c, 0x3b39c483, 0x42221861, 0x7c49da8e, 0x7ff2ed65, + 0x41992f8a, 0xccc2247b, 0xf2a9e694, 0xf112d17f, 0xcf791390, + 0xb662cf72, 0x88090d9d, 0x8bb23a76, 0xb5d9f899, 0xa007ba9e, + 0x9e6c7871, 0x9dd74f9a, 0xa3bc8d75, 0xdaa75197, 0xe4cc9378, + 0xe777a493, 0xd91c667c, 0x54476d8d, 0x6a2caf62, 0x69979889, + 0x57fc5a66, 0x2ee78684, 0x108c446b, 0x13377380, 0x2d5cb16f, + 0x488614b9, 0x76edd656, 0x7556e1bd, 0x4b3d2352, 0x3226ffb0, + 0x0c4d3d5f, 0x0ff60ab4, 0x319dc85b, 0xbcc6c3aa, 0x82ad0145, + 0x811636ae, 0xbf7df441, 0xc66628a3, 0xf80dea4c, 0xfbb6dda7, + 0xc5dd1f48, 0x7004e7d1, 0x4e6f253e, 0x4dd412d5, 0x73bfd03a, + 0x0aa40cd8, 0x34cfce37, 0x3774f9dc, 0x091f3b33, 0x844430c2, + 0xba2ff22d, 0xb994c5c6, 0x87ff0729, 0xfee4dbcb, 0xc08f1924, + 0xc3342ecf, 0xfd5fec20, 0x988549f6, 0xa6ee8b19, 0xa555bcf2, + 0x9b3e7e1d, 0xe225a2ff, 0xdc4e6010, 0xdff557fb, 0xe19e9514, + 0x6cc59ee5, 0x52ae5c0a, 0x51156be1, 0x6f7ea90e, 0x166575ec, + 0x280eb703, 0x2bb580e8, 0x15de4207, 0x010905e6, 0x3f62c709, + 0x3cd9f0e2, 0x02b2320d, 0x7ba9eeef, 0x45c22c00, 0x46791beb, + 0x7812d904, 0xf549d2f5, 0xcb22101a, 0xc89927f1, 0xf6f2e51e, + 0x8fe939fc, 0xb182fb13, 0xb239ccf8, 0x8c520e17, 0xe988abc1, + 0xd7e3692e, 0xd4585ec5, 0xea339c2a, 0x932840c8, 0xad438227, + 0xaef8b5cc, 0x90937723, 0x1dc87cd2, 0x23a3be3d, 0x201889d6, + 0x1e734b39, 0x676897db, 0x59035534, 0x5ab862df, 0x64d3a030, + 0xd10a58a9, 0xef619a46, 0xecdaadad, 0xd2b16f42, 0xabaab3a0, + 0x95c1714f, 0x967a46a4, 0xa811844b, 0x254a8fba, 0x1b214d55, + 0x189a7abe, 0x26f1b851, 0x5fea64b3, 0x6181a65c, 0x623a91b7, + 0x5c515358, 0x398bf68e, 0x07e03461, 0x045b038a, 0x3a30c165, + 0x432b1d87, 0x7d40df68, 0x7efbe883, 0x40902a6c, 0xcdcb219d, + 0xf3a0e372, 0xf01bd499, 0xce701676, 0xb76bca94, 0x8900087b, + 0x8abb3f90, 0xb4d0fd7f, 0xa10ebf78, 0x9f657d97, 0x9cde4a7c, + 0xa2b58893, 0xdbae5471, 0xe5c5969e, 0xe67ea175, 0xd815639a, + 0x554e686b, 0x6b25aa84, 0x689e9d6f, 0x56f55f80, 0x2fee8362, + 0x1185418d, 0x123e7666, 0x2c55b489, 0x498f115f, 0x77e4d3b0, + 0x745fe45b, 0x4a3426b4, 0x332ffa56, 0x0d4438b9, 0x0eff0f52, + 0x3094cdbd, 0xbdcfc64c, 0x83a404a3, 0x801f3348, 0xbe74f1a7, + 0xc76f2d45, 0xf904efaa, 0xfabfd841, 0xc4d41aae, 0x710de237, + 0x4f6620d8, 0x4cdd1733, 0x72b6d5dc, 0x0bad093e, 0x35c6cbd1, + 0x367dfc3a, 0x08163ed5, 0x854d3524, 0xbb26f7cb, 0xb89dc020, + 0x86f602cf, 0xffedde2d, 0xc1861cc2, 0xc23d2b29, 0xfc56e9c6, + 0x998c4c10, 0xa7e78eff, 0xa45cb914, 0x9a377bfb, 0xe32ca719, + 0xdd4765f6, 0xdefc521d, 0xe09790f2, 0x6dcc9b03, 0x53a759ec, + 0x501c6e07, 0x6e77ace8, 0x176c700a, 0x2907b2e5, 0x2abc850e, + 0x14d747e1}, + {0x00000000, 0xc0df8ec1, 0xc1b96c58, 0x0166e299, 0x8273d9b0, + 0x42ac5771, 0x43cab5e8, 0x83153b29, 0x45e1c3ba, 0x853e4d7b, + 0x8458afe2, 0x44872123, 0xc7921a0a, 0x074d94cb, 0x062b7652, + 0xc6f4f893, 0xcbc4f6ae, 0x0b1b786f, 0x0a7d9af6, 0xcaa21437, + 0x49b72f1e, 0x8968a1df, 0x880e4346, 0x48d1cd87, 0x8e253514, + 0x4efabbd5, 0x4f9c594c, 0x8f43d78d, 0x0c56eca4, 0xcc896265, + 0xcdef80fc, 0x0d300e3d, 0xd78f9c86, 0x17501247, 0x1636f0de, + 0xd6e97e1f, 0x55fc4536, 0x9523cbf7, 0x9445296e, 0x549aa7af, + 0x926e5f3c, 0x52b1d1fd, 0x53d73364, 0x9308bda5, 0x101d868c, + 0xd0c2084d, 0xd1a4ead4, 0x117b6415, 0x1c4b6a28, 0xdc94e4e9, + 0xddf20670, 0x1d2d88b1, 0x9e38b398, 0x5ee73d59, 0x5f81dfc0, + 0x9f5e5101, 0x59aaa992, 0x99752753, 0x9813c5ca, 0x58cc4b0b, + 0xdbd97022, 0x1b06fee3, 0x1a601c7a, 0xdabf92bb, 0xef1948d6, + 0x2fc6c617, 0x2ea0248e, 0xee7faa4f, 0x6d6a9166, 0xadb51fa7, + 0xacd3fd3e, 0x6c0c73ff, 0xaaf88b6c, 0x6a2705ad, 0x6b41e734, + 0xab9e69f5, 0x288b52dc, 0xe854dc1d, 0xe9323e84, 0x29edb045, + 0x24ddbe78, 0xe40230b9, 0xe564d220, 0x25bb5ce1, 0xa6ae67c8, + 0x6671e909, 0x67170b90, 0xa7c88551, 0x613c7dc2, 0xa1e3f303, + 0xa085119a, 0x605a9f5b, 0xe34fa472, 0x23902ab3, 0x22f6c82a, + 0xe22946eb, 0x3896d450, 0xf8495a91, 0xf92fb808, 0x39f036c9, + 0xbae50de0, 0x7a3a8321, 0x7b5c61b8, 0xbb83ef79, 0x7d7717ea, + 0xbda8992b, 0xbcce7bb2, 0x7c11f573, 0xff04ce5a, 0x3fdb409b, + 0x3ebda202, 0xfe622cc3, 0xf35222fe, 0x338dac3f, 0x32eb4ea6, + 0xf234c067, 0x7121fb4e, 0xb1fe758f, 0xb0989716, 0x704719d7, + 0xb6b3e144, 0x766c6f85, 0x770a8d1c, 0xb7d503dd, 0x34c038f4, + 0xf41fb635, 0xf57954ac, 0x35a6da6d, 0x9f35e177, 0x5fea6fb6, + 0x5e8c8d2f, 0x9e5303ee, 0x1d4638c7, 0xdd99b606, 0xdcff549f, + 0x1c20da5e, 0xdad422cd, 0x1a0bac0c, 0x1b6d4e95, 0xdbb2c054, + 0x58a7fb7d, 0x987875bc, 0x991e9725, 0x59c119e4, 0x54f117d9, + 0x942e9918, 0x95487b81, 0x5597f540, 0xd682ce69, 0x165d40a8, + 0x173ba231, 0xd7e42cf0, 0x1110d463, 0xd1cf5aa2, 0xd0a9b83b, + 0x107636fa, 0x93630dd3, 0x53bc8312, 0x52da618b, 0x9205ef4a, + 0x48ba7df1, 0x8865f330, 0x890311a9, 0x49dc9f68, 0xcac9a441, + 0x0a162a80, 0x0b70c819, 0xcbaf46d8, 0x0d5bbe4b, 0xcd84308a, + 0xcce2d213, 0x0c3d5cd2, 0x8f2867fb, 0x4ff7e93a, 0x4e910ba3, + 0x8e4e8562, 0x837e8b5f, 0x43a1059e, 0x42c7e707, 0x821869c6, + 0x010d52ef, 0xc1d2dc2e, 0xc0b43eb7, 0x006bb076, 0xc69f48e5, + 0x0640c624, 0x072624bd, 0xc7f9aa7c, 0x44ec9155, 0x84331f94, + 0x8555fd0d, 0x458a73cc, 0x702ca9a1, 0xb0f32760, 0xb195c5f9, + 0x714a4b38, 0xf25f7011, 0x3280fed0, 0x33e61c49, 0xf3399288, + 0x35cd6a1b, 0xf512e4da, 0xf4740643, 0x34ab8882, 0xb7beb3ab, + 0x77613d6a, 0x7607dff3, 0xb6d85132, 0xbbe85f0f, 0x7b37d1ce, + 0x7a513357, 0xba8ebd96, 0x399b86bf, 0xf944087e, 0xf822eae7, + 0x38fd6426, 0xfe099cb5, 0x3ed61274, 0x3fb0f0ed, 0xff6f7e2c, + 0x7c7a4505, 0xbca5cbc4, 0xbdc3295d, 0x7d1ca79c, 0xa7a33527, + 0x677cbbe6, 0x661a597f, 0xa6c5d7be, 0x25d0ec97, 0xe50f6256, + 0xe46980cf, 0x24b60e0e, 0xe242f69d, 0x229d785c, 0x23fb9ac5, + 0xe3241404, 0x60312f2d, 0xa0eea1ec, 0xa1884375, 0x6157cdb4, + 0x6c67c389, 0xacb84d48, 0xaddeafd1, 0x6d012110, 0xee141a39, + 0x2ecb94f8, 0x2fad7661, 0xef72f8a0, 0x29860033, 0xe9598ef2, + 0xe83f6c6b, 0x28e0e2aa, 0xabf5d983, 0x6b2a5742, 0x6a4cb5db, + 0xaa933b1a}, + {0x00000000, 0x6f4ca59b, 0x9f9e3bec, 0xf0d29e77, 0x7f3b0603, + 0x1077a398, 0xe0a53def, 0x8fe99874, 0xfe760c06, 0x913aa99d, + 0x61e837ea, 0x0ea49271, 0x814d0a05, 0xee01af9e, 0x1ed331e9, + 0x719f9472, 0xfced180c, 0x93a1bd97, 0x637323e0, 0x0c3f867b, + 0x83d61e0f, 0xec9abb94, 0x1c4825e3, 0x73048078, 0x029b140a, + 0x6dd7b191, 0x9d052fe6, 0xf2498a7d, 0x7da01209, 0x12ecb792, + 0xe23e29e5, 0x8d728c7e, 0xf8db3118, 0x97979483, 0x67450af4, + 0x0809af6f, 0x87e0371b, 0xe8ac9280, 0x187e0cf7, 0x7732a96c, + 0x06ad3d1e, 0x69e19885, 0x993306f2, 0xf67fa369, 0x79963b1d, + 0x16da9e86, 0xe60800f1, 0x8944a56a, 0x04362914, 0x6b7a8c8f, + 0x9ba812f8, 0xf4e4b763, 0x7b0d2f17, 0x14418a8c, 0xe49314fb, + 0x8bdfb160, 0xfa402512, 0x950c8089, 0x65de1efe, 0x0a92bb65, + 0x857b2311, 0xea37868a, 0x1ae518fd, 0x75a9bd66, 0xf0b76330, + 0x9ffbc6ab, 0x6f2958dc, 0x0065fd47, 0x8f8c6533, 0xe0c0c0a8, + 0x10125edf, 0x7f5efb44, 0x0ec16f36, 0x618dcaad, 0x915f54da, + 0xfe13f141, 0x71fa6935, 0x1eb6ccae, 0xee6452d9, 0x8128f742, + 0x0c5a7b3c, 0x6316dea7, 0x93c440d0, 0xfc88e54b, 0x73617d3f, + 0x1c2dd8a4, 0xecff46d3, 0x83b3e348, 0xf22c773a, 0x9d60d2a1, + 0x6db24cd6, 0x02fee94d, 0x8d177139, 0xe25bd4a2, 0x12894ad5, + 0x7dc5ef4e, 0x086c5228, 0x6720f7b3, 0x97f269c4, 0xf8becc5f, + 0x7757542b, 0x181bf1b0, 0xe8c96fc7, 0x8785ca5c, 0xf61a5e2e, + 0x9956fbb5, 0x698465c2, 0x06c8c059, 0x8921582d, 0xe66dfdb6, + 0x16bf63c1, 0x79f3c65a, 0xf4814a24, 0x9bcdefbf, 0x6b1f71c8, + 0x0453d453, 0x8bba4c27, 0xe4f6e9bc, 0x142477cb, 0x7b68d250, + 0x0af74622, 0x65bbe3b9, 0x95697dce, 0xfa25d855, 0x75cc4021, + 0x1a80e5ba, 0xea527bcd, 0x851ede56, 0xe06fc760, 0x8f2362fb, + 0x7ff1fc8c, 0x10bd5917, 0x9f54c163, 0xf01864f8, 0x00cafa8f, + 0x6f865f14, 0x1e19cb66, 0x71556efd, 0x8187f08a, 0xeecb5511, + 0x6122cd65, 0x0e6e68fe, 0xfebcf689, 0x91f05312, 0x1c82df6c, + 0x73ce7af7, 0x831ce480, 0xec50411b, 0x63b9d96f, 0x0cf57cf4, + 0xfc27e283, 0x936b4718, 0xe2f4d36a, 0x8db876f1, 0x7d6ae886, + 0x12264d1d, 0x9dcfd569, 0xf28370f2, 0x0251ee85, 0x6d1d4b1e, + 0x18b4f678, 0x77f853e3, 0x872acd94, 0xe866680f, 0x678ff07b, + 0x08c355e0, 0xf811cb97, 0x975d6e0c, 0xe6c2fa7e, 0x898e5fe5, + 0x795cc192, 0x16106409, 0x99f9fc7d, 0xf6b559e6, 0x0667c791, + 0x692b620a, 0xe459ee74, 0x8b154bef, 0x7bc7d598, 0x148b7003, + 0x9b62e877, 0xf42e4dec, 0x04fcd39b, 0x6bb07600, 0x1a2fe272, + 0x756347e9, 0x85b1d99e, 0xeafd7c05, 0x6514e471, 0x0a5841ea, + 0xfa8adf9d, 0x95c67a06, 0x10d8a450, 0x7f9401cb, 0x8f469fbc, + 0xe00a3a27, 0x6fe3a253, 0x00af07c8, 0xf07d99bf, 0x9f313c24, + 0xeeaea856, 0x81e20dcd, 0x713093ba, 0x1e7c3621, 0x9195ae55, + 0xfed90bce, 0x0e0b95b9, 0x61473022, 0xec35bc5c, 0x837919c7, + 0x73ab87b0, 0x1ce7222b, 0x930eba5f, 0xfc421fc4, 0x0c9081b3, + 0x63dc2428, 0x1243b05a, 0x7d0f15c1, 0x8ddd8bb6, 0xe2912e2d, + 0x6d78b659, 0x023413c2, 0xf2e68db5, 0x9daa282e, 0xe8039548, + 0x874f30d3, 0x779daea4, 0x18d10b3f, 0x9738934b, 0xf87436d0, + 0x08a6a8a7, 0x67ea0d3c, 0x1675994e, 0x79393cd5, 0x89eba2a2, + 0xe6a70739, 0x694e9f4d, 0x06023ad6, 0xf6d0a4a1, 0x999c013a, + 0x14ee8d44, 0x7ba228df, 0x8b70b6a8, 0xe43c1333, 0x6bd58b47, + 0x04992edc, 0xf44bb0ab, 0x9b071530, 0xea988142, 0x85d424d9, + 0x7506baae, 0x1a4a1f35, 0x95a38741, 0xfaef22da, 0x0a3dbcad, + 0x65711936}}; + +#endif + +#endif + +#if N == 4 + +#if W == 8 + +local const z_crc_t FAR crc_braid_table[][256] = { + {0x00000000, 0xf1da05aa, 0x38c50d15, 0xc91f08bf, 0x718a1a2a, + 0x80501f80, 0x494f173f, 0xb8951295, 0xe3143454, 0x12ce31fe, + 0xdbd13941, 0x2a0b3ceb, 0x929e2e7e, 0x63442bd4, 0xaa5b236b, + 0x5b8126c1, 0x1d596ee9, 0xec836b43, 0x259c63fc, 0xd4466656, + 0x6cd374c3, 0x9d097169, 0x541679d6, 0xa5cc7c7c, 0xfe4d5abd, + 0x0f975f17, 0xc68857a8, 0x37525202, 0x8fc74097, 0x7e1d453d, + 0xb7024d82, 0x46d84828, 0x3ab2ddd2, 0xcb68d878, 0x0277d0c7, + 0xf3add56d, 0x4b38c7f8, 0xbae2c252, 0x73fdcaed, 0x8227cf47, + 0xd9a6e986, 0x287cec2c, 0xe163e493, 0x10b9e139, 0xa82cf3ac, + 0x59f6f606, 0x90e9feb9, 0x6133fb13, 0x27ebb33b, 0xd631b691, + 0x1f2ebe2e, 0xeef4bb84, 0x5661a911, 0xa7bbacbb, 0x6ea4a404, + 0x9f7ea1ae, 0xc4ff876f, 0x352582c5, 0xfc3a8a7a, 0x0de08fd0, + 0xb5759d45, 0x44af98ef, 0x8db09050, 0x7c6a95fa, 0x7565bba4, + 0x84bfbe0e, 0x4da0b6b1, 0xbc7ab31b, 0x04efa18e, 0xf535a424, + 0x3c2aac9b, 0xcdf0a931, 0x96718ff0, 0x67ab8a5a, 0xaeb482e5, + 0x5f6e874f, 0xe7fb95da, 0x16219070, 0xdf3e98cf, 0x2ee49d65, + 0x683cd54d, 0x99e6d0e7, 0x50f9d858, 0xa123ddf2, 0x19b6cf67, + 0xe86ccacd, 0x2173c272, 0xd0a9c7d8, 0x8b28e119, 0x7af2e4b3, + 0xb3edec0c, 0x4237e9a6, 0xfaa2fb33, 0x0b78fe99, 0xc267f626, + 0x33bdf38c, 0x4fd76676, 0xbe0d63dc, 0x77126b63, 0x86c86ec9, + 0x3e5d7c5c, 0xcf8779f6, 0x06987149, 0xf74274e3, 0xacc35222, + 0x5d195788, 0x94065f37, 0x65dc5a9d, 0xdd494808, 0x2c934da2, + 0xe58c451d, 0x145640b7, 0x528e089f, 0xa3540d35, 0x6a4b058a, + 0x9b910020, 0x230412b5, 0xd2de171f, 0x1bc11fa0, 0xea1b1a0a, + 0xb19a3ccb, 0x40403961, 0x895f31de, 0x78853474, 0xc01026e1, + 0x31ca234b, 0xf8d52bf4, 0x090f2e5e, 0xeacb7748, 0x1b1172e2, + 0xd20e7a5d, 0x23d47ff7, 0x9b416d62, 0x6a9b68c8, 0xa3846077, + 0x525e65dd, 0x09df431c, 0xf80546b6, 0x311a4e09, 0xc0c04ba3, + 0x78555936, 0x898f5c9c, 0x40905423, 0xb14a5189, 0xf79219a1, + 0x06481c0b, 0xcf5714b4, 0x3e8d111e, 0x8618038b, 0x77c20621, + 0xbedd0e9e, 0x4f070b34, 0x14862df5, 0xe55c285f, 0x2c4320e0, + 0xdd99254a, 0x650c37df, 0x94d63275, 0x5dc93aca, 0xac133f60, + 0xd079aa9a, 0x21a3af30, 0xe8bca78f, 0x1966a225, 0xa1f3b0b0, + 0x5029b51a, 0x9936bda5, 0x68ecb80f, 0x336d9ece, 0xc2b79b64, + 0x0ba893db, 0xfa729671, 0x42e784e4, 0xb33d814e, 0x7a2289f1, + 0x8bf88c5b, 0xcd20c473, 0x3cfac1d9, 0xf5e5c966, 0x043fcccc, + 0xbcaade59, 0x4d70dbf3, 0x846fd34c, 0x75b5d6e6, 0x2e34f027, + 0xdfeef58d, 0x16f1fd32, 0xe72bf898, 0x5fbeea0d, 0xae64efa7, + 0x677be718, 0x96a1e2b2, 0x9faeccec, 0x6e74c946, 0xa76bc1f9, + 0x56b1c453, 0xee24d6c6, 0x1ffed36c, 0xd6e1dbd3, 0x273bde79, + 0x7cbaf8b8, 0x8d60fd12, 0x447ff5ad, 0xb5a5f007, 0x0d30e292, + 0xfceae738, 0x35f5ef87, 0xc42fea2d, 0x82f7a205, 0x732da7af, + 0xba32af10, 0x4be8aaba, 0xf37db82f, 0x02a7bd85, 0xcbb8b53a, + 0x3a62b090, 0x61e39651, 0x903993fb, 0x59269b44, 0xa8fc9eee, + 0x10698c7b, 0xe1b389d1, 0x28ac816e, 0xd97684c4, 0xa51c113e, + 0x54c61494, 0x9dd91c2b, 0x6c031981, 0xd4960b14, 0x254c0ebe, + 0xec530601, 0x1d8903ab, 0x4608256a, 0xb7d220c0, 0x7ecd287f, + 0x8f172dd5, 0x37823f40, 0xc6583aea, 0x0f473255, 0xfe9d37ff, + 0xb8457fd7, 0x499f7a7d, 0x808072c2, 0x715a7768, 0xc9cf65fd, + 0x38156057, 0xf10a68e8, 0x00d06d42, 0x5b514b83, 0xaa8b4e29, + 0x63944696, 0x924e433c, 0x2adb51a9, 0xdb015403, 0x121e5cbc, + 0xe3c45916}, + {0x00000000, 0x0ee7e8d1, 0x1dcfd1a2, 0x13283973, 0x3b9fa344, + 0x35784b95, 0x265072e6, 0x28b79a37, 0x773f4688, 0x79d8ae59, + 0x6af0972a, 0x64177ffb, 0x4ca0e5cc, 0x42470d1d, 0x516f346e, + 0x5f88dcbf, 0xee7e8d10, 0xe09965c1, 0xf3b15cb2, 0xfd56b463, + 0xd5e12e54, 0xdb06c685, 0xc82efff6, 0xc6c91727, 0x9941cb98, + 0x97a62349, 0x848e1a3a, 0x8a69f2eb, 0xa2de68dc, 0xac39800d, + 0xbf11b97e, 0xb1f651af, 0x078c1c61, 0x096bf4b0, 0x1a43cdc3, + 0x14a42512, 0x3c13bf25, 0x32f457f4, 0x21dc6e87, 0x2f3b8656, + 0x70b35ae9, 0x7e54b238, 0x6d7c8b4b, 0x639b639a, 0x4b2cf9ad, + 0x45cb117c, 0x56e3280f, 0x5804c0de, 0xe9f29171, 0xe71579a0, + 0xf43d40d3, 0xfadaa802, 0xd26d3235, 0xdc8adae4, 0xcfa2e397, + 0xc1450b46, 0x9ecdd7f9, 0x902a3f28, 0x8302065b, 0x8de5ee8a, + 0xa55274bd, 0xabb59c6c, 0xb89da51f, 0xb67a4dce, 0x0f1838c2, + 0x01ffd013, 0x12d7e960, 0x1c3001b1, 0x34879b86, 0x3a607357, + 0x29484a24, 0x27afa2f5, 0x78277e4a, 0x76c0969b, 0x65e8afe8, + 0x6b0f4739, 0x43b8dd0e, 0x4d5f35df, 0x5e770cac, 0x5090e47d, + 0xe166b5d2, 0xef815d03, 0xfca96470, 0xf24e8ca1, 0xdaf91696, + 0xd41efe47, 0xc736c734, 0xc9d12fe5, 0x9659f35a, 0x98be1b8b, + 0x8b9622f8, 0x8571ca29, 0xadc6501e, 0xa321b8cf, 0xb00981bc, + 0xbeee696d, 0x089424a3, 0x0673cc72, 0x155bf501, 0x1bbc1dd0, + 0x330b87e7, 0x3dec6f36, 0x2ec45645, 0x2023be94, 0x7fab622b, + 0x714c8afa, 0x6264b389, 0x6c835b58, 0x4434c16f, 0x4ad329be, + 0x59fb10cd, 0x571cf81c, 0xe6eaa9b3, 0xe80d4162, 0xfb257811, + 0xf5c290c0, 0xdd750af7, 0xd392e226, 0xc0badb55, 0xce5d3384, + 0x91d5ef3b, 0x9f3207ea, 0x8c1a3e99, 0x82fdd648, 0xaa4a4c7f, + 0xa4ada4ae, 0xb7859ddd, 0xb962750c, 0x1e307184, 0x10d79955, + 0x03ffa026, 0x0d1848f7, 0x25afd2c0, 0x2b483a11, 0x38600362, + 0x3687ebb3, 0x690f370c, 0x67e8dfdd, 0x74c0e6ae, 0x7a270e7f, + 0x52909448, 0x5c777c99, 0x4f5f45ea, 0x41b8ad3b, 0xf04efc94, + 0xfea91445, 0xed812d36, 0xe366c5e7, 0xcbd15fd0, 0xc536b701, + 0xd61e8e72, 0xd8f966a3, 0x8771ba1c, 0x899652cd, 0x9abe6bbe, + 0x9459836f, 0xbcee1958, 0xb209f189, 0xa121c8fa, 0xafc6202b, + 0x19bc6de5, 0x175b8534, 0x0473bc47, 0x0a945496, 0x2223cea1, + 0x2cc42670, 0x3fec1f03, 0x310bf7d2, 0x6e832b6d, 0x6064c3bc, + 0x734cfacf, 0x7dab121e, 0x551c8829, 0x5bfb60f8, 0x48d3598b, + 0x4634b15a, 0xf7c2e0f5, 0xf9250824, 0xea0d3157, 0xe4ead986, + 0xcc5d43b1, 0xc2baab60, 0xd1929213, 0xdf757ac2, 0x80fda67d, + 0x8e1a4eac, 0x9d3277df, 0x93d59f0e, 0xbb620539, 0xb585ede8, + 0xa6add49b, 0xa84a3c4a, 0x11284946, 0x1fcfa197, 0x0ce798e4, + 0x02007035, 0x2ab7ea02, 0x245002d3, 0x37783ba0, 0x399fd371, + 0x66170fce, 0x68f0e71f, 0x7bd8de6c, 0x753f36bd, 0x5d88ac8a, + 0x536f445b, 0x40477d28, 0x4ea095f9, 0xff56c456, 0xf1b12c87, + 0xe29915f4, 0xec7efd25, 0xc4c96712, 0xca2e8fc3, 0xd906b6b0, + 0xd7e15e61, 0x886982de, 0x868e6a0f, 0x95a6537c, 0x9b41bbad, + 0xb3f6219a, 0xbd11c94b, 0xae39f038, 0xa0de18e9, 0x16a45527, + 0x1843bdf6, 0x0b6b8485, 0x058c6c54, 0x2d3bf663, 0x23dc1eb2, + 0x30f427c1, 0x3e13cf10, 0x619b13af, 0x6f7cfb7e, 0x7c54c20d, + 0x72b32adc, 0x5a04b0eb, 0x54e3583a, 0x47cb6149, 0x492c8998, + 0xf8dad837, 0xf63d30e6, 0xe5150995, 0xebf2e144, 0xc3457b73, + 0xcda293a2, 0xde8aaad1, 0xd06d4200, 0x8fe59ebf, 0x8102766e, + 0x922a4f1d, 0x9ccda7cc, 0xb47a3dfb, 0xba9dd52a, 0xa9b5ec59, + 0xa7520488}, + {0x00000000, 0x3c60e308, 0x78c1c610, 0x44a12518, 0xf1838c20, + 0xcde36f28, 0x89424a30, 0xb522a938, 0x38761e01, 0x0416fd09, + 0x40b7d811, 0x7cd73b19, 0xc9f59221, 0xf5957129, 0xb1345431, + 0x8d54b739, 0x70ec3c02, 0x4c8cdf0a, 0x082dfa12, 0x344d191a, + 0x816fb022, 0xbd0f532a, 0xf9ae7632, 0xc5ce953a, 0x489a2203, + 0x74fac10b, 0x305be413, 0x0c3b071b, 0xb919ae23, 0x85794d2b, + 0xc1d86833, 0xfdb88b3b, 0xe1d87804, 0xddb89b0c, 0x9919be14, + 0xa5795d1c, 0x105bf424, 0x2c3b172c, 0x689a3234, 0x54fad13c, + 0xd9ae6605, 0xe5ce850d, 0xa16fa015, 0x9d0f431d, 0x282dea25, + 0x144d092d, 0x50ec2c35, 0x6c8ccf3d, 0x91344406, 0xad54a70e, + 0xe9f58216, 0xd595611e, 0x60b7c826, 0x5cd72b2e, 0x18760e36, + 0x2416ed3e, 0xa9425a07, 0x9522b90f, 0xd1839c17, 0xede37f1f, + 0x58c1d627, 0x64a1352f, 0x20001037, 0x1c60f33f, 0x18c1f649, + 0x24a11541, 0x60003059, 0x5c60d351, 0xe9427a69, 0xd5229961, + 0x9183bc79, 0xade35f71, 0x20b7e848, 0x1cd70b40, 0x58762e58, + 0x6416cd50, 0xd1346468, 0xed548760, 0xa9f5a278, 0x95954170, + 0x682dca4b, 0x544d2943, 0x10ec0c5b, 0x2c8cef53, 0x99ae466b, + 0xa5cea563, 0xe16f807b, 0xdd0f6373, 0x505bd44a, 0x6c3b3742, + 0x289a125a, 0x14faf152, 0xa1d8586a, 0x9db8bb62, 0xd9199e7a, + 0xe5797d72, 0xf9198e4d, 0xc5796d45, 0x81d8485d, 0xbdb8ab55, + 0x089a026d, 0x34fae165, 0x705bc47d, 0x4c3b2775, 0xc16f904c, + 0xfd0f7344, 0xb9ae565c, 0x85ceb554, 0x30ec1c6c, 0x0c8cff64, + 0x482dda7c, 0x744d3974, 0x89f5b24f, 0xb5955147, 0xf134745f, + 0xcd549757, 0x78763e6f, 0x4416dd67, 0x00b7f87f, 0x3cd71b77, + 0xb183ac4e, 0x8de34f46, 0xc9426a5e, 0xf5228956, 0x4000206e, + 0x7c60c366, 0x38c1e67e, 0x04a10576, 0x3183ec92, 0x0de30f9a, + 0x49422a82, 0x7522c98a, 0xc00060b2, 0xfc6083ba, 0xb8c1a6a2, + 0x84a145aa, 0x09f5f293, 0x3595119b, 0x71343483, 0x4d54d78b, + 0xf8767eb3, 0xc4169dbb, 0x80b7b8a3, 0xbcd75bab, 0x416fd090, + 0x7d0f3398, 0x39ae1680, 0x05cef588, 0xb0ec5cb0, 0x8c8cbfb8, + 0xc82d9aa0, 0xf44d79a8, 0x7919ce91, 0x45792d99, 0x01d80881, + 0x3db8eb89, 0x889a42b1, 0xb4faa1b9, 0xf05b84a1, 0xcc3b67a9, + 0xd05b9496, 0xec3b779e, 0xa89a5286, 0x94fab18e, 0x21d818b6, + 0x1db8fbbe, 0x5919dea6, 0x65793dae, 0xe82d8a97, 0xd44d699f, + 0x90ec4c87, 0xac8caf8f, 0x19ae06b7, 0x25cee5bf, 0x616fc0a7, + 0x5d0f23af, 0xa0b7a894, 0x9cd74b9c, 0xd8766e84, 0xe4168d8c, + 0x513424b4, 0x6d54c7bc, 0x29f5e2a4, 0x159501ac, 0x98c1b695, + 0xa4a1559d, 0xe0007085, 0xdc60938d, 0x69423ab5, 0x5522d9bd, + 0x1183fca5, 0x2de31fad, 0x29421adb, 0x1522f9d3, 0x5183dccb, + 0x6de33fc3, 0xd8c196fb, 0xe4a175f3, 0xa00050eb, 0x9c60b3e3, + 0x113404da, 0x2d54e7d2, 0x69f5c2ca, 0x559521c2, 0xe0b788fa, + 0xdcd76bf2, 0x98764eea, 0xa416ade2, 0x59ae26d9, 0x65cec5d1, + 0x216fe0c9, 0x1d0f03c1, 0xa82daaf9, 0x944d49f1, 0xd0ec6ce9, + 0xec8c8fe1, 0x61d838d8, 0x5db8dbd0, 0x1919fec8, 0x25791dc0, + 0x905bb4f8, 0xac3b57f0, 0xe89a72e8, 0xd4fa91e0, 0xc89a62df, + 0xf4fa81d7, 0xb05ba4cf, 0x8c3b47c7, 0x3919eeff, 0x05790df7, + 0x41d828ef, 0x7db8cbe7, 0xf0ec7cde, 0xcc8c9fd6, 0x882dbace, + 0xb44d59c6, 0x016ff0fe, 0x3d0f13f6, 0x79ae36ee, 0x45ced5e6, + 0xb8765edd, 0x8416bdd5, 0xc0b798cd, 0xfcd77bc5, 0x49f5d2fd, + 0x759531f5, 0x313414ed, 0x0d54f7e5, 0x800040dc, 0xbc60a3d4, + 0xf8c186cc, 0xc4a165c4, 0x7183ccfc, 0x4de32ff4, 0x09420aec, + 0x3522e9e4}, + {0x00000000, 0x6307d924, 0xc60fb248, 0xa5086b6c, 0x576e62d1, + 0x3469bbf5, 0x9161d099, 0xf26609bd, 0xaedcc5a2, 0xcddb1c86, + 0x68d377ea, 0x0bd4aece, 0xf9b2a773, 0x9ab57e57, 0x3fbd153b, + 0x5cbacc1f, 0x86c88d05, 0xe5cf5421, 0x40c73f4d, 0x23c0e669, + 0xd1a6efd4, 0xb2a136f0, 0x17a95d9c, 0x74ae84b8, 0x281448a7, + 0x4b139183, 0xee1bfaef, 0x8d1c23cb, 0x7f7a2a76, 0x1c7df352, + 0xb975983e, 0xda72411a, 0xd6e01c4b, 0xb5e7c56f, 0x10efae03, + 0x73e87727, 0x818e7e9a, 0xe289a7be, 0x4781ccd2, 0x248615f6, + 0x783cd9e9, 0x1b3b00cd, 0xbe336ba1, 0xdd34b285, 0x2f52bb38, + 0x4c55621c, 0xe95d0970, 0x8a5ad054, 0x5028914e, 0x332f486a, + 0x96272306, 0xf520fa22, 0x0746f39f, 0x64412abb, 0xc14941d7, + 0xa24e98f3, 0xfef454ec, 0x9df38dc8, 0x38fbe6a4, 0x5bfc3f80, + 0xa99a363d, 0xca9def19, 0x6f958475, 0x0c925d51, 0x76b13ed7, + 0x15b6e7f3, 0xb0be8c9f, 0xd3b955bb, 0x21df5c06, 0x42d88522, + 0xe7d0ee4e, 0x84d7376a, 0xd86dfb75, 0xbb6a2251, 0x1e62493d, + 0x7d659019, 0x8f0399a4, 0xec044080, 0x490c2bec, 0x2a0bf2c8, + 0xf079b3d2, 0x937e6af6, 0x3676019a, 0x5571d8be, 0xa717d103, + 0xc4100827, 0x6118634b, 0x021fba6f, 0x5ea57670, 0x3da2af54, + 0x98aac438, 0xfbad1d1c, 0x09cb14a1, 0x6acccd85, 0xcfc4a6e9, + 0xacc37fcd, 0xa051229c, 0xc356fbb8, 0x665e90d4, 0x055949f0, + 0xf73f404d, 0x94389969, 0x3130f205, 0x52372b21, 0x0e8de73e, + 0x6d8a3e1a, 0xc8825576, 0xab858c52, 0x59e385ef, 0x3ae45ccb, + 0x9fec37a7, 0xfcebee83, 0x2699af99, 0x459e76bd, 0xe0961dd1, + 0x8391c4f5, 0x71f7cd48, 0x12f0146c, 0xb7f87f00, 0xd4ffa624, + 0x88456a3b, 0xeb42b31f, 0x4e4ad873, 0x2d4d0157, 0xdf2b08ea, + 0xbc2cd1ce, 0x1924baa2, 0x7a236386, 0xed627dae, 0x8e65a48a, + 0x2b6dcfe6, 0x486a16c2, 0xba0c1f7f, 0xd90bc65b, 0x7c03ad37, + 0x1f047413, 0x43beb80c, 0x20b96128, 0x85b10a44, 0xe6b6d360, + 0x14d0dadd, 0x77d703f9, 0xd2df6895, 0xb1d8b1b1, 0x6baaf0ab, + 0x08ad298f, 0xada542e3, 0xcea29bc7, 0x3cc4927a, 0x5fc34b5e, + 0xfacb2032, 0x99ccf916, 0xc5763509, 0xa671ec2d, 0x03798741, + 0x607e5e65, 0x921857d8, 0xf11f8efc, 0x5417e590, 0x37103cb4, + 0x3b8261e5, 0x5885b8c1, 0xfd8dd3ad, 0x9e8a0a89, 0x6cec0334, + 0x0febda10, 0xaae3b17c, 0xc9e46858, 0x955ea447, 0xf6597d63, + 0x5351160f, 0x3056cf2b, 0xc230c696, 0xa1371fb2, 0x043f74de, + 0x6738adfa, 0xbd4aece0, 0xde4d35c4, 0x7b455ea8, 0x1842878c, + 0xea248e31, 0x89235715, 0x2c2b3c79, 0x4f2ce55d, 0x13962942, + 0x7091f066, 0xd5999b0a, 0xb69e422e, 0x44f84b93, 0x27ff92b7, + 0x82f7f9db, 0xe1f020ff, 0x9bd34379, 0xf8d49a5d, 0x5ddcf131, + 0x3edb2815, 0xccbd21a8, 0xafbaf88c, 0x0ab293e0, 0x69b54ac4, + 0x350f86db, 0x56085fff, 0xf3003493, 0x9007edb7, 0x6261e40a, + 0x01663d2e, 0xa46e5642, 0xc7698f66, 0x1d1bce7c, 0x7e1c1758, + 0xdb147c34, 0xb813a510, 0x4a75acad, 0x29727589, 0x8c7a1ee5, + 0xef7dc7c1, 0xb3c70bde, 0xd0c0d2fa, 0x75c8b996, 0x16cf60b2, + 0xe4a9690f, 0x87aeb02b, 0x22a6db47, 0x41a10263, 0x4d335f32, + 0x2e348616, 0x8b3ced7a, 0xe83b345e, 0x1a5d3de3, 0x795ae4c7, + 0xdc528fab, 0xbf55568f, 0xe3ef9a90, 0x80e843b4, 0x25e028d8, + 0x46e7f1fc, 0xb481f841, 0xd7862165, 0x728e4a09, 0x1189932d, + 0xcbfbd237, 0xa8fc0b13, 0x0df4607f, 0x6ef3b95b, 0x9c95b0e6, + 0xff9269c2, 0x5a9a02ae, 0x399ddb8a, 0x65271795, 0x0620ceb1, + 0xa328a5dd, 0xc02f7cf9, 0x32497544, 0x514eac60, 0xf446c70c, + 0x97411e28}, + {0x00000000, 0x01b5fd1d, 0x036bfa3a, 0x02de0727, 0x06d7f474, + 0x07620969, 0x05bc0e4e, 0x0409f353, 0x0dafe8e8, 0x0c1a15f5, + 0x0ec412d2, 0x0f71efcf, 0x0b781c9c, 0x0acde181, 0x0813e6a6, + 0x09a61bbb, 0x1b5fd1d0, 0x1aea2ccd, 0x18342bea, 0x1981d6f7, + 0x1d8825a4, 0x1c3dd8b9, 0x1ee3df9e, 0x1f562283, 0x16f03938, + 0x1745c425, 0x159bc302, 0x142e3e1f, 0x1027cd4c, 0x11923051, + 0x134c3776, 0x12f9ca6b, 0x36bfa3a0, 0x370a5ebd, 0x35d4599a, + 0x3461a487, 0x306857d4, 0x31ddaac9, 0x3303adee, 0x32b650f3, + 0x3b104b48, 0x3aa5b655, 0x387bb172, 0x39ce4c6f, 0x3dc7bf3c, + 0x3c724221, 0x3eac4506, 0x3f19b81b, 0x2de07270, 0x2c558f6d, + 0x2e8b884a, 0x2f3e7557, 0x2b378604, 0x2a827b19, 0x285c7c3e, + 0x29e98123, 0x204f9a98, 0x21fa6785, 0x232460a2, 0x22919dbf, + 0x26986eec, 0x272d93f1, 0x25f394d6, 0x244669cb, 0x6d7f4740, + 0x6ccaba5d, 0x6e14bd7a, 0x6fa14067, 0x6ba8b334, 0x6a1d4e29, + 0x68c3490e, 0x6976b413, 0x60d0afa8, 0x616552b5, 0x63bb5592, + 0x620ea88f, 0x66075bdc, 0x67b2a6c1, 0x656ca1e6, 0x64d95cfb, + 0x76209690, 0x77956b8d, 0x754b6caa, 0x74fe91b7, 0x70f762e4, + 0x71429ff9, 0x739c98de, 0x722965c3, 0x7b8f7e78, 0x7a3a8365, + 0x78e48442, 0x7951795f, 0x7d588a0c, 0x7ced7711, 0x7e337036, + 0x7f868d2b, 0x5bc0e4e0, 0x5a7519fd, 0x58ab1eda, 0x591ee3c7, + 0x5d171094, 0x5ca2ed89, 0x5e7ceaae, 0x5fc917b3, 0x566f0c08, + 0x57daf115, 0x5504f632, 0x54b10b2f, 0x50b8f87c, 0x510d0561, + 0x53d30246, 0x5266ff5b, 0x409f3530, 0x412ac82d, 0x43f4cf0a, + 0x42413217, 0x4648c144, 0x47fd3c59, 0x45233b7e, 0x4496c663, + 0x4d30ddd8, 0x4c8520c5, 0x4e5b27e2, 0x4feedaff, 0x4be729ac, + 0x4a52d4b1, 0x488cd396, 0x49392e8b, 0xdafe8e80, 0xdb4b739d, + 0xd99574ba, 0xd82089a7, 0xdc297af4, 0xdd9c87e9, 0xdf4280ce, + 0xdef77dd3, 0xd7516668, 0xd6e49b75, 0xd43a9c52, 0xd58f614f, + 0xd186921c, 0xd0336f01, 0xd2ed6826, 0xd358953b, 0xc1a15f50, + 0xc014a24d, 0xc2caa56a, 0xc37f5877, 0xc776ab24, 0xc6c35639, + 0xc41d511e, 0xc5a8ac03, 0xcc0eb7b8, 0xcdbb4aa5, 0xcf654d82, + 0xced0b09f, 0xcad943cc, 0xcb6cbed1, 0xc9b2b9f6, 0xc80744eb, + 0xec412d20, 0xedf4d03d, 0xef2ad71a, 0xee9f2a07, 0xea96d954, + 0xeb232449, 0xe9fd236e, 0xe848de73, 0xe1eec5c8, 0xe05b38d5, + 0xe2853ff2, 0xe330c2ef, 0xe73931bc, 0xe68ccca1, 0xe452cb86, + 0xe5e7369b, 0xf71efcf0, 0xf6ab01ed, 0xf47506ca, 0xf5c0fbd7, + 0xf1c90884, 0xf07cf599, 0xf2a2f2be, 0xf3170fa3, 0xfab11418, + 0xfb04e905, 0xf9daee22, 0xf86f133f, 0xfc66e06c, 0xfdd31d71, + 0xff0d1a56, 0xfeb8e74b, 0xb781c9c0, 0xb63434dd, 0xb4ea33fa, + 0xb55fcee7, 0xb1563db4, 0xb0e3c0a9, 0xb23dc78e, 0xb3883a93, + 0xba2e2128, 0xbb9bdc35, 0xb945db12, 0xb8f0260f, 0xbcf9d55c, + 0xbd4c2841, 0xbf922f66, 0xbe27d27b, 0xacde1810, 0xad6be50d, + 0xafb5e22a, 0xae001f37, 0xaa09ec64, 0xabbc1179, 0xa962165e, + 0xa8d7eb43, 0xa171f0f8, 0xa0c40de5, 0xa21a0ac2, 0xa3aff7df, + 0xa7a6048c, 0xa613f991, 0xa4cdfeb6, 0xa57803ab, 0x813e6a60, + 0x808b977d, 0x8255905a, 0x83e06d47, 0x87e99e14, 0x865c6309, + 0x8482642e, 0x85379933, 0x8c918288, 0x8d247f95, 0x8ffa78b2, + 0x8e4f85af, 0x8a4676fc, 0x8bf38be1, 0x892d8cc6, 0x889871db, + 0x9a61bbb0, 0x9bd446ad, 0x990a418a, 0x98bfbc97, 0x9cb64fc4, + 0x9d03b2d9, 0x9fddb5fe, 0x9e6848e3, 0x97ce5358, 0x967bae45, + 0x94a5a962, 0x9510547f, 0x9119a72c, 0x90ac5a31, 0x92725d16, + 0x93c7a00b}, + {0x00000000, 0x6e8c1b41, 0xdd183682, 0xb3942dc3, 0x61416b45, + 0x0fcd7004, 0xbc595dc7, 0xd2d54686, 0xc282d68a, 0xac0ecdcb, + 0x1f9ae008, 0x7116fb49, 0xa3c3bdcf, 0xcd4fa68e, 0x7edb8b4d, + 0x1057900c, 0x5e74ab55, 0x30f8b014, 0x836c9dd7, 0xede08696, + 0x3f35c010, 0x51b9db51, 0xe22df692, 0x8ca1edd3, 0x9cf67ddf, + 0xf27a669e, 0x41ee4b5d, 0x2f62501c, 0xfdb7169a, 0x933b0ddb, + 0x20af2018, 0x4e233b59, 0xbce956aa, 0xd2654deb, 0x61f16028, + 0x0f7d7b69, 0xdda83def, 0xb32426ae, 0x00b00b6d, 0x6e3c102c, + 0x7e6b8020, 0x10e79b61, 0xa373b6a2, 0xcdffade3, 0x1f2aeb65, + 0x71a6f024, 0xc232dde7, 0xacbec6a6, 0xe29dfdff, 0x8c11e6be, + 0x3f85cb7d, 0x5109d03c, 0x83dc96ba, 0xed508dfb, 0x5ec4a038, + 0x3048bb79, 0x201f2b75, 0x4e933034, 0xfd071df7, 0x938b06b6, + 0x415e4030, 0x2fd25b71, 0x9c4676b2, 0xf2ca6df3, 0xa2a3ab15, + 0xcc2fb054, 0x7fbb9d97, 0x113786d6, 0xc3e2c050, 0xad6edb11, + 0x1efaf6d2, 0x7076ed93, 0x60217d9f, 0x0ead66de, 0xbd394b1d, + 0xd3b5505c, 0x016016da, 0x6fec0d9b, 0xdc782058, 0xb2f43b19, + 0xfcd70040, 0x925b1b01, 0x21cf36c2, 0x4f432d83, 0x9d966b05, + 0xf31a7044, 0x408e5d87, 0x2e0246c6, 0x3e55d6ca, 0x50d9cd8b, + 0xe34de048, 0x8dc1fb09, 0x5f14bd8f, 0x3198a6ce, 0x820c8b0d, + 0xec80904c, 0x1e4afdbf, 0x70c6e6fe, 0xc352cb3d, 0xadded07c, + 0x7f0b96fa, 0x11878dbb, 0xa213a078, 0xcc9fbb39, 0xdcc82b35, + 0xb2443074, 0x01d01db7, 0x6f5c06f6, 0xbd894070, 0xd3055b31, + 0x609176f2, 0x0e1d6db3, 0x403e56ea, 0x2eb24dab, 0x9d266068, + 0xf3aa7b29, 0x217f3daf, 0x4ff326ee, 0xfc670b2d, 0x92eb106c, + 0x82bc8060, 0xec309b21, 0x5fa4b6e2, 0x3128ada3, 0xe3fdeb25, + 0x8d71f064, 0x3ee5dda7, 0x5069c6e6, 0x9e36506b, 0xf0ba4b2a, + 0x432e66e9, 0x2da27da8, 0xff773b2e, 0x91fb206f, 0x226f0dac, + 0x4ce316ed, 0x5cb486e1, 0x32389da0, 0x81acb063, 0xef20ab22, + 0x3df5eda4, 0x5379f6e5, 0xe0eddb26, 0x8e61c067, 0xc042fb3e, + 0xaecee07f, 0x1d5acdbc, 0x73d6d6fd, 0xa103907b, 0xcf8f8b3a, + 0x7c1ba6f9, 0x1297bdb8, 0x02c02db4, 0x6c4c36f5, 0xdfd81b36, + 0xb1540077, 0x638146f1, 0x0d0d5db0, 0xbe997073, 0xd0156b32, + 0x22df06c1, 0x4c531d80, 0xffc73043, 0x914b2b02, 0x439e6d84, + 0x2d1276c5, 0x9e865b06, 0xf00a4047, 0xe05dd04b, 0x8ed1cb0a, + 0x3d45e6c9, 0x53c9fd88, 0x811cbb0e, 0xef90a04f, 0x5c048d8c, + 0x328896cd, 0x7cabad94, 0x1227b6d5, 0xa1b39b16, 0xcf3f8057, + 0x1deac6d1, 0x7366dd90, 0xc0f2f053, 0xae7eeb12, 0xbe297b1e, + 0xd0a5605f, 0x63314d9c, 0x0dbd56dd, 0xdf68105b, 0xb1e40b1a, + 0x027026d9, 0x6cfc3d98, 0x3c95fb7e, 0x5219e03f, 0xe18dcdfc, + 0x8f01d6bd, 0x5dd4903b, 0x33588b7a, 0x80cca6b9, 0xee40bdf8, + 0xfe172df4, 0x909b36b5, 0x230f1b76, 0x4d830037, 0x9f5646b1, + 0xf1da5df0, 0x424e7033, 0x2cc26b72, 0x62e1502b, 0x0c6d4b6a, + 0xbff966a9, 0xd1757de8, 0x03a03b6e, 0x6d2c202f, 0xdeb80dec, + 0xb03416ad, 0xa06386a1, 0xceef9de0, 0x7d7bb023, 0x13f7ab62, + 0xc122ede4, 0xafaef6a5, 0x1c3adb66, 0x72b6c027, 0x807cadd4, + 0xeef0b695, 0x5d649b56, 0x33e88017, 0xe13dc691, 0x8fb1ddd0, + 0x3c25f013, 0x52a9eb52, 0x42fe7b5e, 0x2c72601f, 0x9fe64ddc, + 0xf16a569d, 0x23bf101b, 0x4d330b5a, 0xfea72699, 0x902b3dd8, + 0xde080681, 0xb0841dc0, 0x03103003, 0x6d9c2b42, 0xbf496dc4, + 0xd1c57685, 0x62515b46, 0x0cdd4007, 0x1c8ad00b, 0x7206cb4a, + 0xc192e689, 0xaf1efdc8, 0x7dcbbb4e, 0x1347a00f, 0xa0d38dcc, + 0xce5f968d}, + {0x00000000, 0xe71da697, 0x154a4b6f, 0xf257edf8, 0x2a9496de, + 0xcd893049, 0x3fdeddb1, 0xd8c37b26, 0x55292dbc, 0xb2348b2b, + 0x406366d3, 0xa77ec044, 0x7fbdbb62, 0x98a01df5, 0x6af7f00d, + 0x8dea569a, 0xaa525b78, 0x4d4ffdef, 0xbf181017, 0x5805b680, + 0x80c6cda6, 0x67db6b31, 0x958c86c9, 0x7291205e, 0xff7b76c4, + 0x1866d053, 0xea313dab, 0x0d2c9b3c, 0xd5efe01a, 0x32f2468d, + 0xc0a5ab75, 0x27b80de2, 0x8fd5b0b1, 0x68c81626, 0x9a9ffbde, + 0x7d825d49, 0xa541266f, 0x425c80f8, 0xb00b6d00, 0x5716cb97, + 0xdafc9d0d, 0x3de13b9a, 0xcfb6d662, 0x28ab70f5, 0xf0680bd3, + 0x1775ad44, 0xe52240bc, 0x023fe62b, 0x2587ebc9, 0xc29a4d5e, + 0x30cda0a6, 0xd7d00631, 0x0f137d17, 0xe80edb80, 0x1a593678, + 0xfd4490ef, 0x70aec675, 0x97b360e2, 0x65e48d1a, 0x82f92b8d, + 0x5a3a50ab, 0xbd27f63c, 0x4f701bc4, 0xa86dbd53, 0xc4da6723, + 0x23c7c1b4, 0xd1902c4c, 0x368d8adb, 0xee4ef1fd, 0x0953576a, + 0xfb04ba92, 0x1c191c05, 0x91f34a9f, 0x76eeec08, 0x84b901f0, + 0x63a4a767, 0xbb67dc41, 0x5c7a7ad6, 0xae2d972e, 0x493031b9, + 0x6e883c5b, 0x89959acc, 0x7bc27734, 0x9cdfd1a3, 0x441caa85, + 0xa3010c12, 0x5156e1ea, 0xb64b477d, 0x3ba111e7, 0xdcbcb770, + 0x2eeb5a88, 0xc9f6fc1f, 0x11358739, 0xf62821ae, 0x047fcc56, + 0xe3626ac1, 0x4b0fd792, 0xac127105, 0x5e459cfd, 0xb9583a6a, + 0x619b414c, 0x8686e7db, 0x74d10a23, 0x93ccacb4, 0x1e26fa2e, + 0xf93b5cb9, 0x0b6cb141, 0xec7117d6, 0x34b26cf0, 0xd3afca67, + 0x21f8279f, 0xc6e58108, 0xe15d8cea, 0x06402a7d, 0xf417c785, + 0x130a6112, 0xcbc91a34, 0x2cd4bca3, 0xde83515b, 0x399ef7cc, + 0xb474a156, 0x536907c1, 0xa13eea39, 0x46234cae, 0x9ee03788, + 0x79fd911f, 0x8baa7ce7, 0x6cb7da70, 0x52c5c807, 0xb5d86e90, + 0x478f8368, 0xa09225ff, 0x78515ed9, 0x9f4cf84e, 0x6d1b15b6, + 0x8a06b321, 0x07ece5bb, 0xe0f1432c, 0x12a6aed4, 0xf5bb0843, + 0x2d787365, 0xca65d5f2, 0x3832380a, 0xdf2f9e9d, 0xf897937f, + 0x1f8a35e8, 0xedddd810, 0x0ac07e87, 0xd20305a1, 0x351ea336, + 0xc7494ece, 0x2054e859, 0xadbebec3, 0x4aa31854, 0xb8f4f5ac, + 0x5fe9533b, 0x872a281d, 0x60378e8a, 0x92606372, 0x757dc5e5, + 0xdd1078b6, 0x3a0dde21, 0xc85a33d9, 0x2f47954e, 0xf784ee68, + 0x109948ff, 0xe2cea507, 0x05d30390, 0x8839550a, 0x6f24f39d, + 0x9d731e65, 0x7a6eb8f2, 0xa2adc3d4, 0x45b06543, 0xb7e788bb, + 0x50fa2e2c, 0x774223ce, 0x905f8559, 0x620868a1, 0x8515ce36, + 0x5dd6b510, 0xbacb1387, 0x489cfe7f, 0xaf8158e8, 0x226b0e72, + 0xc576a8e5, 0x3721451d, 0xd03ce38a, 0x08ff98ac, 0xefe23e3b, + 0x1db5d3c3, 0xfaa87554, 0x961faf24, 0x710209b3, 0x8355e44b, + 0x644842dc, 0xbc8b39fa, 0x5b969f6d, 0xa9c17295, 0x4edcd402, + 0xc3368298, 0x242b240f, 0xd67cc9f7, 0x31616f60, 0xe9a21446, + 0x0ebfb2d1, 0xfce85f29, 0x1bf5f9be, 0x3c4df45c, 0xdb5052cb, + 0x2907bf33, 0xce1a19a4, 0x16d96282, 0xf1c4c415, 0x039329ed, + 0xe48e8f7a, 0x6964d9e0, 0x8e797f77, 0x7c2e928f, 0x9b333418, + 0x43f04f3e, 0xa4ede9a9, 0x56ba0451, 0xb1a7a2c6, 0x19ca1f95, + 0xfed7b902, 0x0c8054fa, 0xeb9df26d, 0x335e894b, 0xd4432fdc, + 0x2614c224, 0xc10964b3, 0x4ce33229, 0xabfe94be, 0x59a97946, + 0xbeb4dfd1, 0x6677a4f7, 0x816a0260, 0x733def98, 0x9420490f, + 0xb39844ed, 0x5485e27a, 0xa6d20f82, 0x41cfa915, 0x990cd233, + 0x7e1174a4, 0x8c46995c, 0x6b5b3fcb, 0xe6b16951, 0x01accfc6, + 0xf3fb223e, 0x14e684a9, 0xcc25ff8f, 0x2b385918, 0xd96fb4e0, + 0x3e721277}, + {0x00000000, 0xa58b900e, 0x9066265d, 0x35edb653, 0xfbbd4afb, + 0x5e36daf5, 0x6bdb6ca6, 0xce50fca8, 0x2c0b93b7, 0x898003b9, + 0xbc6db5ea, 0x19e625e4, 0xd7b6d94c, 0x723d4942, 0x47d0ff11, + 0xe25b6f1f, 0x5817276e, 0xfd9cb760, 0xc8710133, 0x6dfa913d, + 0xa3aa6d95, 0x0621fd9b, 0x33cc4bc8, 0x9647dbc6, 0x741cb4d9, + 0xd19724d7, 0xe47a9284, 0x41f1028a, 0x8fa1fe22, 0x2a2a6e2c, + 0x1fc7d87f, 0xba4c4871, 0xb02e4edc, 0x15a5ded2, 0x20486881, + 0x85c3f88f, 0x4b930427, 0xee189429, 0xdbf5227a, 0x7e7eb274, + 0x9c25dd6b, 0x39ae4d65, 0x0c43fb36, 0xa9c86b38, 0x67989790, + 0xc213079e, 0xf7feb1cd, 0x527521c3, 0xe83969b2, 0x4db2f9bc, + 0x785f4fef, 0xddd4dfe1, 0x13842349, 0xb60fb347, 0x83e20514, + 0x2669951a, 0xc432fa05, 0x61b96a0b, 0x5454dc58, 0xf1df4c56, + 0x3f8fb0fe, 0x9a0420f0, 0xafe996a3, 0x0a6206ad, 0xbb2d9bf9, + 0x1ea60bf7, 0x2b4bbda4, 0x8ec02daa, 0x4090d102, 0xe51b410c, + 0xd0f6f75f, 0x757d6751, 0x9726084e, 0x32ad9840, 0x07402e13, + 0xa2cbbe1d, 0x6c9b42b5, 0xc910d2bb, 0xfcfd64e8, 0x5976f4e6, + 0xe33abc97, 0x46b12c99, 0x735c9aca, 0xd6d70ac4, 0x1887f66c, + 0xbd0c6662, 0x88e1d031, 0x2d6a403f, 0xcf312f20, 0x6ababf2e, + 0x5f57097d, 0xfadc9973, 0x348c65db, 0x9107f5d5, 0xa4ea4386, + 0x0161d388, 0x0b03d525, 0xae88452b, 0x9b65f378, 0x3eee6376, + 0xf0be9fde, 0x55350fd0, 0x60d8b983, 0xc553298d, 0x27084692, + 0x8283d69c, 0xb76e60cf, 0x12e5f0c1, 0xdcb50c69, 0x793e9c67, + 0x4cd32a34, 0xe958ba3a, 0x5314f24b, 0xf69f6245, 0xc372d416, + 0x66f94418, 0xa8a9b8b0, 0x0d2228be, 0x38cf9eed, 0x9d440ee3, + 0x7f1f61fc, 0xda94f1f2, 0xef7947a1, 0x4af2d7af, 0x84a22b07, + 0x2129bb09, 0x14c40d5a, 0xb14f9d54, 0xad2a31b3, 0x08a1a1bd, + 0x3d4c17ee, 0x98c787e0, 0x56977b48, 0xf31ceb46, 0xc6f15d15, + 0x637acd1b, 0x8121a204, 0x24aa320a, 0x11478459, 0xb4cc1457, + 0x7a9ce8ff, 0xdf1778f1, 0xeafacea2, 0x4f715eac, 0xf53d16dd, + 0x50b686d3, 0x655b3080, 0xc0d0a08e, 0x0e805c26, 0xab0bcc28, + 0x9ee67a7b, 0x3b6dea75, 0xd936856a, 0x7cbd1564, 0x4950a337, + 0xecdb3339, 0x228bcf91, 0x87005f9f, 0xb2ede9cc, 0x176679c2, + 0x1d047f6f, 0xb88fef61, 0x8d625932, 0x28e9c93c, 0xe6b93594, + 0x4332a59a, 0x76df13c9, 0xd35483c7, 0x310fecd8, 0x94847cd6, + 0xa169ca85, 0x04e25a8b, 0xcab2a623, 0x6f39362d, 0x5ad4807e, + 0xff5f1070, 0x45135801, 0xe098c80f, 0xd5757e5c, 0x70feee52, + 0xbeae12fa, 0x1b2582f4, 0x2ec834a7, 0x8b43a4a9, 0x6918cbb6, + 0xcc935bb8, 0xf97eedeb, 0x5cf57de5, 0x92a5814d, 0x372e1143, + 0x02c3a710, 0xa748371e, 0x1607aa4a, 0xb38c3a44, 0x86618c17, + 0x23ea1c19, 0xedbae0b1, 0x483170bf, 0x7ddcc6ec, 0xd85756e2, + 0x3a0c39fd, 0x9f87a9f3, 0xaa6a1fa0, 0x0fe18fae, 0xc1b17306, + 0x643ae308, 0x51d7555b, 0xf45cc555, 0x4e108d24, 0xeb9b1d2a, + 0xde76ab79, 0x7bfd3b77, 0xb5adc7df, 0x102657d1, 0x25cbe182, + 0x8040718c, 0x621b1e93, 0xc7908e9d, 0xf27d38ce, 0x57f6a8c0, + 0x99a65468, 0x3c2dc466, 0x09c07235, 0xac4be23b, 0xa629e496, + 0x03a27498, 0x364fc2cb, 0x93c452c5, 0x5d94ae6d, 0xf81f3e63, + 0xcdf28830, 0x6879183e, 0x8a227721, 0x2fa9e72f, 0x1a44517c, + 0xbfcfc172, 0x719f3dda, 0xd414add4, 0xe1f91b87, 0x44728b89, + 0xfe3ec3f8, 0x5bb553f6, 0x6e58e5a5, 0xcbd375ab, 0x05838903, + 0xa008190d, 0x95e5af5e, 0x306e3f50, 0xd235504f, 0x77bec041, + 0x42537612, 0xe7d8e61c, 0x29881ab4, 0x8c038aba, 0xb9ee3ce9, + 0x1c65ace7}}; + +local const z_word_t FAR crc_braid_big_table[][256] = { + {0x0000000000000000, 0x0e908ba500000000, 0x5d26669000000000, + 0x53b6ed3500000000, 0xfb4abdfb00000000, 0xf5da365e00000000, + 0xa66cdb6b00000000, 0xa8fc50ce00000000, 0xb7930b2c00000000, + 0xb903808900000000, 0xeab56dbc00000000, 0xe425e61900000000, + 0x4cd9b6d700000000, 0x42493d7200000000, 0x11ffd04700000000, + 0x1f6f5be200000000, 0x6e27175800000000, 0x60b79cfd00000000, + 0x330171c800000000, 0x3d91fa6d00000000, 0x956daaa300000000, + 0x9bfd210600000000, 0xc84bcc3300000000, 0xc6db479600000000, + 0xd9b41c7400000000, 0xd72497d100000000, 0x84927ae400000000, + 0x8a02f14100000000, 0x22fea18f00000000, 0x2c6e2a2a00000000, + 0x7fd8c71f00000000, 0x71484cba00000000, 0xdc4e2eb000000000, + 0xd2dea51500000000, 0x8168482000000000, 0x8ff8c38500000000, + 0x2704934b00000000, 0x299418ee00000000, 0x7a22f5db00000000, + 0x74b27e7e00000000, 0x6bdd259c00000000, 0x654dae3900000000, + 0x36fb430c00000000, 0x386bc8a900000000, 0x9097986700000000, + 0x9e0713c200000000, 0xcdb1fef700000000, 0xc321755200000000, + 0xb26939e800000000, 0xbcf9b24d00000000, 0xef4f5f7800000000, + 0xe1dfd4dd00000000, 0x4923841300000000, 0x47b30fb600000000, + 0x1405e28300000000, 0x1a95692600000000, 0x05fa32c400000000, + 0x0b6ab96100000000, 0x58dc545400000000, 0x564cdff100000000, + 0xfeb08f3f00000000, 0xf020049a00000000, 0xa396e9af00000000, + 0xad06620a00000000, 0xf99b2dbb00000000, 0xf70ba61e00000000, + 0xa4bd4b2b00000000, 0xaa2dc08e00000000, 0x02d1904000000000, + 0x0c411be500000000, 0x5ff7f6d000000000, 0x51677d7500000000, + 0x4e08269700000000, 0x4098ad3200000000, 0x132e400700000000, + 0x1dbecba200000000, 0xb5429b6c00000000, 0xbbd210c900000000, + 0xe864fdfc00000000, 0xe6f4765900000000, 0x97bc3ae300000000, + 0x992cb14600000000, 0xca9a5c7300000000, 0xc40ad7d600000000, + 0x6cf6871800000000, 0x62660cbd00000000, 0x31d0e18800000000, + 0x3f406a2d00000000, 0x202f31cf00000000, 0x2ebfba6a00000000, + 0x7d09575f00000000, 0x7399dcfa00000000, 0xdb658c3400000000, + 0xd5f5079100000000, 0x8643eaa400000000, 0x88d3610100000000, + 0x25d5030b00000000, 0x2b4588ae00000000, 0x78f3659b00000000, + 0x7663ee3e00000000, 0xde9fbef000000000, 0xd00f355500000000, + 0x83b9d86000000000, 0x8d2953c500000000, 0x9246082700000000, + 0x9cd6838200000000, 0xcf606eb700000000, 0xc1f0e51200000000, + 0x690cb5dc00000000, 0x679c3e7900000000, 0x342ad34c00000000, + 0x3aba58e900000000, 0x4bf2145300000000, 0x45629ff600000000, + 0x16d472c300000000, 0x1844f96600000000, 0xb0b8a9a800000000, + 0xbe28220d00000000, 0xed9ecf3800000000, 0xe30e449d00000000, + 0xfc611f7f00000000, 0xf2f194da00000000, 0xa14779ef00000000, + 0xafd7f24a00000000, 0x072ba28400000000, 0x09bb292100000000, + 0x5a0dc41400000000, 0x549d4fb100000000, 0xb3312aad00000000, + 0xbda1a10800000000, 0xee174c3d00000000, 0xe087c79800000000, + 0x487b975600000000, 0x46eb1cf300000000, 0x155df1c600000000, + 0x1bcd7a6300000000, 0x04a2218100000000, 0x0a32aa2400000000, + 0x5984471100000000, 0x5714ccb400000000, 0xffe89c7a00000000, + 0xf17817df00000000, 0xa2cefaea00000000, 0xac5e714f00000000, + 0xdd163df500000000, 0xd386b65000000000, 0x80305b6500000000, + 0x8ea0d0c000000000, 0x265c800e00000000, 0x28cc0bab00000000, + 0x7b7ae69e00000000, 0x75ea6d3b00000000, 0x6a8536d900000000, + 0x6415bd7c00000000, 0x37a3504900000000, 0x3933dbec00000000, + 0x91cf8b2200000000, 0x9f5f008700000000, 0xcce9edb200000000, + 0xc279661700000000, 0x6f7f041d00000000, 0x61ef8fb800000000, + 0x3259628d00000000, 0x3cc9e92800000000, 0x9435b9e600000000, + 0x9aa5324300000000, 0xc913df7600000000, 0xc78354d300000000, + 0xd8ec0f3100000000, 0xd67c849400000000, 0x85ca69a100000000, + 0x8b5ae20400000000, 0x23a6b2ca00000000, 0x2d36396f00000000, + 0x7e80d45a00000000, 0x70105fff00000000, 0x0158134500000000, + 0x0fc898e000000000, 0x5c7e75d500000000, 0x52eefe7000000000, + 0xfa12aebe00000000, 0xf482251b00000000, 0xa734c82e00000000, + 0xa9a4438b00000000, 0xb6cb186900000000, 0xb85b93cc00000000, + 0xebed7ef900000000, 0xe57df55c00000000, 0x4d81a59200000000, + 0x43112e3700000000, 0x10a7c30200000000, 0x1e3748a700000000, + 0x4aaa071600000000, 0x443a8cb300000000, 0x178c618600000000, + 0x191cea2300000000, 0xb1e0baed00000000, 0xbf70314800000000, + 0xecc6dc7d00000000, 0xe25657d800000000, 0xfd390c3a00000000, + 0xf3a9879f00000000, 0xa01f6aaa00000000, 0xae8fe10f00000000, + 0x0673b1c100000000, 0x08e33a6400000000, 0x5b55d75100000000, + 0x55c55cf400000000, 0x248d104e00000000, 0x2a1d9beb00000000, + 0x79ab76de00000000, 0x773bfd7b00000000, 0xdfc7adb500000000, + 0xd157261000000000, 0x82e1cb2500000000, 0x8c71408000000000, + 0x931e1b6200000000, 0x9d8e90c700000000, 0xce387df200000000, + 0xc0a8f65700000000, 0x6854a69900000000, 0x66c42d3c00000000, + 0x3572c00900000000, 0x3be24bac00000000, 0x96e429a600000000, + 0x9874a20300000000, 0xcbc24f3600000000, 0xc552c49300000000, + 0x6dae945d00000000, 0x633e1ff800000000, 0x3088f2cd00000000, + 0x3e18796800000000, 0x2177228a00000000, 0x2fe7a92f00000000, + 0x7c51441a00000000, 0x72c1cfbf00000000, 0xda3d9f7100000000, + 0xd4ad14d400000000, 0x871bf9e100000000, 0x898b724400000000, + 0xf8c33efe00000000, 0xf653b55b00000000, 0xa5e5586e00000000, + 0xab75d3cb00000000, 0x0389830500000000, 0x0d1908a000000000, + 0x5eafe59500000000, 0x503f6e3000000000, 0x4f5035d200000000, + 0x41c0be7700000000, 0x1276534200000000, 0x1ce6d8e700000000, + 0xb41a882900000000, 0xba8a038c00000000, 0xe93ceeb900000000, + 0xe7ac651c00000000}, + {0x0000000000000000, 0x97a61de700000000, 0x6f4b4a1500000000, + 0xf8ed57f200000000, 0xde96942a00000000, 0x493089cd00000000, + 0xb1ddde3f00000000, 0x267bc3d800000000, 0xbc2d295500000000, + 0x2b8b34b200000000, 0xd366634000000000, 0x44c07ea700000000, + 0x62bbbd7f00000000, 0xf51da09800000000, 0x0df0f76a00000000, + 0x9a56ea8d00000000, 0x785b52aa00000000, 0xeffd4f4d00000000, + 0x171018bf00000000, 0x80b6055800000000, 0xa6cdc68000000000, + 0x316bdb6700000000, 0xc9868c9500000000, 0x5e20917200000000, + 0xc4767bff00000000, 0x53d0661800000000, 0xab3d31ea00000000, + 0x3c9b2c0d00000000, 0x1ae0efd500000000, 0x8d46f23200000000, + 0x75aba5c000000000, 0xe20db82700000000, 0xb1b0d58f00000000, + 0x2616c86800000000, 0xdefb9f9a00000000, 0x495d827d00000000, + 0x6f2641a500000000, 0xf8805c4200000000, 0x006d0bb000000000, + 0x97cb165700000000, 0x0d9dfcda00000000, 0x9a3be13d00000000, + 0x62d6b6cf00000000, 0xf570ab2800000000, 0xd30b68f000000000, + 0x44ad751700000000, 0xbc4022e500000000, 0x2be63f0200000000, + 0xc9eb872500000000, 0x5e4d9ac200000000, 0xa6a0cd3000000000, + 0x3106d0d700000000, 0x177d130f00000000, 0x80db0ee800000000, + 0x7836591a00000000, 0xef9044fd00000000, 0x75c6ae7000000000, + 0xe260b39700000000, 0x1a8de46500000000, 0x8d2bf98200000000, + 0xab503a5a00000000, 0x3cf627bd00000000, 0xc41b704f00000000, + 0x53bd6da800000000, 0x2367dac400000000, 0xb4c1c72300000000, + 0x4c2c90d100000000, 0xdb8a8d3600000000, 0xfdf14eee00000000, + 0x6a57530900000000, 0x92ba04fb00000000, 0x051c191c00000000, + 0x9f4af39100000000, 0x08ecee7600000000, 0xf001b98400000000, + 0x67a7a46300000000, 0x41dc67bb00000000, 0xd67a7a5c00000000, + 0x2e972dae00000000, 0xb931304900000000, 0x5b3c886e00000000, + 0xcc9a958900000000, 0x3477c27b00000000, 0xa3d1df9c00000000, + 0x85aa1c4400000000, 0x120c01a300000000, 0xeae1565100000000, + 0x7d474bb600000000, 0xe711a13b00000000, 0x70b7bcdc00000000, + 0x885aeb2e00000000, 0x1ffcf6c900000000, 0x3987351100000000, + 0xae2128f600000000, 0x56cc7f0400000000, 0xc16a62e300000000, + 0x92d70f4b00000000, 0x057112ac00000000, 0xfd9c455e00000000, + 0x6a3a58b900000000, 0x4c419b6100000000, 0xdbe7868600000000, + 0x230ad17400000000, 0xb4accc9300000000, 0x2efa261e00000000, + 0xb95c3bf900000000, 0x41b16c0b00000000, 0xd61771ec00000000, + 0xf06cb23400000000, 0x67caafd300000000, 0x9f27f82100000000, + 0x0881e5c600000000, 0xea8c5de100000000, 0x7d2a400600000000, + 0x85c717f400000000, 0x12610a1300000000, 0x341ac9cb00000000, + 0xa3bcd42c00000000, 0x5b5183de00000000, 0xccf79e3900000000, + 0x56a174b400000000, 0xc107695300000000, 0x39ea3ea100000000, + 0xae4c234600000000, 0x8837e09e00000000, 0x1f91fd7900000000, + 0xe77caa8b00000000, 0x70dab76c00000000, 0x07c8c55200000000, + 0x906ed8b500000000, 0x68838f4700000000, 0xff2592a000000000, + 0xd95e517800000000, 0x4ef84c9f00000000, 0xb6151b6d00000000, + 0x21b3068a00000000, 0xbbe5ec0700000000, 0x2c43f1e000000000, + 0xd4aea61200000000, 0x4308bbf500000000, 0x6573782d00000000, + 0xf2d565ca00000000, 0x0a38323800000000, 0x9d9e2fdf00000000, + 0x7f9397f800000000, 0xe8358a1f00000000, 0x10d8dded00000000, + 0x877ec00a00000000, 0xa10503d200000000, 0x36a31e3500000000, + 0xce4e49c700000000, 0x59e8542000000000, 0xc3bebead00000000, + 0x5418a34a00000000, 0xacf5f4b800000000, 0x3b53e95f00000000, + 0x1d282a8700000000, 0x8a8e376000000000, 0x7263609200000000, + 0xe5c57d7500000000, 0xb67810dd00000000, 0x21de0d3a00000000, + 0xd9335ac800000000, 0x4e95472f00000000, 0x68ee84f700000000, + 0xff48991000000000, 0x07a5cee200000000, 0x9003d30500000000, + 0x0a55398800000000, 0x9df3246f00000000, 0x651e739d00000000, + 0xf2b86e7a00000000, 0xd4c3ada200000000, 0x4365b04500000000, + 0xbb88e7b700000000, 0x2c2efa5000000000, 0xce23427700000000, + 0x59855f9000000000, 0xa168086200000000, 0x36ce158500000000, + 0x10b5d65d00000000, 0x8713cbba00000000, 0x7ffe9c4800000000, + 0xe85881af00000000, 0x720e6b2200000000, 0xe5a876c500000000, + 0x1d45213700000000, 0x8ae33cd000000000, 0xac98ff0800000000, + 0x3b3ee2ef00000000, 0xc3d3b51d00000000, 0x5475a8fa00000000, + 0x24af1f9600000000, 0xb309027100000000, 0x4be4558300000000, + 0xdc42486400000000, 0xfa398bbc00000000, 0x6d9f965b00000000, + 0x9572c1a900000000, 0x02d4dc4e00000000, 0x988236c300000000, + 0x0f242b2400000000, 0xf7c97cd600000000, 0x606f613100000000, + 0x4614a2e900000000, 0xd1b2bf0e00000000, 0x295fe8fc00000000, + 0xbef9f51b00000000, 0x5cf44d3c00000000, 0xcb5250db00000000, + 0x33bf072900000000, 0xa4191ace00000000, 0x8262d91600000000, + 0x15c4c4f100000000, 0xed29930300000000, 0x7a8f8ee400000000, + 0xe0d9646900000000, 0x777f798e00000000, 0x8f922e7c00000000, + 0x1834339b00000000, 0x3e4ff04300000000, 0xa9e9eda400000000, + 0x5104ba5600000000, 0xc6a2a7b100000000, 0x951fca1900000000, + 0x02b9d7fe00000000, 0xfa54800c00000000, 0x6df29deb00000000, + 0x4b895e3300000000, 0xdc2f43d400000000, 0x24c2142600000000, + 0xb36409c100000000, 0x2932e34c00000000, 0xbe94feab00000000, + 0x4679a95900000000, 0xd1dfb4be00000000, 0xf7a4776600000000, + 0x60026a8100000000, 0x98ef3d7300000000, 0x0f49209400000000, + 0xed4498b300000000, 0x7ae2855400000000, 0x820fd2a600000000, + 0x15a9cf4100000000, 0x33d20c9900000000, 0xa474117e00000000, + 0x5c99468c00000000, 0xcb3f5b6b00000000, 0x5169b1e600000000, + 0xc6cfac0100000000, 0x3e22fbf300000000, 0xa984e61400000000, + 0x8fff25cc00000000, 0x1859382b00000000, 0xe0b46fd900000000, + 0x7712723e00000000}, + {0x0000000000000000, 0x411b8c6e00000000, 0x823618dd00000000, + 0xc32d94b300000000, 0x456b416100000000, 0x0470cd0f00000000, + 0xc75d59bc00000000, 0x8646d5d200000000, 0x8ad682c200000000, + 0xcbcd0eac00000000, 0x08e09a1f00000000, 0x49fb167100000000, + 0xcfbdc3a300000000, 0x8ea64fcd00000000, 0x4d8bdb7e00000000, + 0x0c90571000000000, 0x55ab745e00000000, 0x14b0f83000000000, + 0xd79d6c8300000000, 0x9686e0ed00000000, 0x10c0353f00000000, + 0x51dbb95100000000, 0x92f62de200000000, 0xd3eda18c00000000, + 0xdf7df69c00000000, 0x9e667af200000000, 0x5d4bee4100000000, + 0x1c50622f00000000, 0x9a16b7fd00000000, 0xdb0d3b9300000000, + 0x1820af2000000000, 0x593b234e00000000, 0xaa56e9bc00000000, + 0xeb4d65d200000000, 0x2860f16100000000, 0x697b7d0f00000000, + 0xef3da8dd00000000, 0xae2624b300000000, 0x6d0bb00000000000, + 0x2c103c6e00000000, 0x20806b7e00000000, 0x619be71000000000, + 0xa2b673a300000000, 0xe3adffcd00000000, 0x65eb2a1f00000000, + 0x24f0a67100000000, 0xe7dd32c200000000, 0xa6c6beac00000000, + 0xfffd9de200000000, 0xbee6118c00000000, 0x7dcb853f00000000, + 0x3cd0095100000000, 0xba96dc8300000000, 0xfb8d50ed00000000, + 0x38a0c45e00000000, 0x79bb483000000000, 0x752b1f2000000000, + 0x3430934e00000000, 0xf71d07fd00000000, 0xb6068b9300000000, + 0x30405e4100000000, 0x715bd22f00000000, 0xb276469c00000000, + 0xf36dcaf200000000, 0x15aba3a200000000, 0x54b02fcc00000000, + 0x979dbb7f00000000, 0xd686371100000000, 0x50c0e2c300000000, + 0x11db6ead00000000, 0xd2f6fa1e00000000, 0x93ed767000000000, + 0x9f7d216000000000, 0xde66ad0e00000000, 0x1d4b39bd00000000, + 0x5c50b5d300000000, 0xda16600100000000, 0x9b0dec6f00000000, + 0x582078dc00000000, 0x193bf4b200000000, 0x4000d7fc00000000, + 0x011b5b9200000000, 0xc236cf2100000000, 0x832d434f00000000, + 0x056b969d00000000, 0x44701af300000000, 0x875d8e4000000000, + 0xc646022e00000000, 0xcad6553e00000000, 0x8bcdd95000000000, + 0x48e04de300000000, 0x09fbc18d00000000, 0x8fbd145f00000000, + 0xcea6983100000000, 0x0d8b0c8200000000, 0x4c9080ec00000000, + 0xbffd4a1e00000000, 0xfee6c67000000000, 0x3dcb52c300000000, + 0x7cd0dead00000000, 0xfa960b7f00000000, 0xbb8d871100000000, + 0x78a013a200000000, 0x39bb9fcc00000000, 0x352bc8dc00000000, + 0x743044b200000000, 0xb71dd00100000000, 0xf6065c6f00000000, + 0x704089bd00000000, 0x315b05d300000000, 0xf276916000000000, + 0xb36d1d0e00000000, 0xea563e4000000000, 0xab4db22e00000000, + 0x6860269d00000000, 0x297baaf300000000, 0xaf3d7f2100000000, + 0xee26f34f00000000, 0x2d0b67fc00000000, 0x6c10eb9200000000, + 0x6080bc8200000000, 0x219b30ec00000000, 0xe2b6a45f00000000, + 0xa3ad283100000000, 0x25ebfde300000000, 0x64f0718d00000000, + 0xa7dde53e00000000, 0xe6c6695000000000, 0x6b50369e00000000, + 0x2a4bbaf000000000, 0xe9662e4300000000, 0xa87da22d00000000, + 0x2e3b77ff00000000, 0x6f20fb9100000000, 0xac0d6f2200000000, + 0xed16e34c00000000, 0xe186b45c00000000, 0xa09d383200000000, + 0x63b0ac8100000000, 0x22ab20ef00000000, 0xa4edf53d00000000, + 0xe5f6795300000000, 0x26dbede000000000, 0x67c0618e00000000, + 0x3efb42c000000000, 0x7fe0ceae00000000, 0xbccd5a1d00000000, + 0xfdd6d67300000000, 0x7b9003a100000000, 0x3a8b8fcf00000000, + 0xf9a61b7c00000000, 0xb8bd971200000000, 0xb42dc00200000000, + 0xf5364c6c00000000, 0x361bd8df00000000, 0x770054b100000000, + 0xf146816300000000, 0xb05d0d0d00000000, 0x737099be00000000, + 0x326b15d000000000, 0xc106df2200000000, 0x801d534c00000000, + 0x4330c7ff00000000, 0x022b4b9100000000, 0x846d9e4300000000, + 0xc576122d00000000, 0x065b869e00000000, 0x47400af000000000, + 0x4bd05de000000000, 0x0acbd18e00000000, 0xc9e6453d00000000, + 0x88fdc95300000000, 0x0ebb1c8100000000, 0x4fa090ef00000000, + 0x8c8d045c00000000, 0xcd96883200000000, 0x94adab7c00000000, + 0xd5b6271200000000, 0x169bb3a100000000, 0x57803fcf00000000, + 0xd1c6ea1d00000000, 0x90dd667300000000, 0x53f0f2c000000000, + 0x12eb7eae00000000, 0x1e7b29be00000000, 0x5f60a5d000000000, + 0x9c4d316300000000, 0xdd56bd0d00000000, 0x5b1068df00000000, + 0x1a0be4b100000000, 0xd926700200000000, 0x983dfc6c00000000, + 0x7efb953c00000000, 0x3fe0195200000000, 0xfccd8de100000000, + 0xbdd6018f00000000, 0x3b90d45d00000000, 0x7a8b583300000000, + 0xb9a6cc8000000000, 0xf8bd40ee00000000, 0xf42d17fe00000000, + 0xb5369b9000000000, 0x761b0f2300000000, 0x3700834d00000000, + 0xb146569f00000000, 0xf05ddaf100000000, 0x33704e4200000000, + 0x726bc22c00000000, 0x2b50e16200000000, 0x6a4b6d0c00000000, + 0xa966f9bf00000000, 0xe87d75d100000000, 0x6e3ba00300000000, + 0x2f202c6d00000000, 0xec0db8de00000000, 0xad1634b000000000, + 0xa18663a000000000, 0xe09defce00000000, 0x23b07b7d00000000, + 0x62abf71300000000, 0xe4ed22c100000000, 0xa5f6aeaf00000000, + 0x66db3a1c00000000, 0x27c0b67200000000, 0xd4ad7c8000000000, + 0x95b6f0ee00000000, 0x569b645d00000000, 0x1780e83300000000, + 0x91c63de100000000, 0xd0ddb18f00000000, 0x13f0253c00000000, + 0x52eba95200000000, 0x5e7bfe4200000000, 0x1f60722c00000000, + 0xdc4de69f00000000, 0x9d566af100000000, 0x1b10bf2300000000, + 0x5a0b334d00000000, 0x9926a7fe00000000, 0xd83d2b9000000000, + 0x810608de00000000, 0xc01d84b000000000, 0x0330100300000000, + 0x422b9c6d00000000, 0xc46d49bf00000000, 0x8576c5d100000000, + 0x465b516200000000, 0x0740dd0c00000000, 0x0bd08a1c00000000, + 0x4acb067200000000, 0x89e692c100000000, 0xc8fd1eaf00000000, + 0x4ebbcb7d00000000, 0x0fa0471300000000, 0xcc8dd3a000000000, + 0x8d965fce00000000}, + {0x0000000000000000, 0x1dfdb50100000000, 0x3afa6b0300000000, + 0x2707de0200000000, 0x74f4d70600000000, 0x6909620700000000, + 0x4e0ebc0500000000, 0x53f3090400000000, 0xe8e8af0d00000000, + 0xf5151a0c00000000, 0xd212c40e00000000, 0xcfef710f00000000, + 0x9c1c780b00000000, 0x81e1cd0a00000000, 0xa6e6130800000000, + 0xbb1ba60900000000, 0xd0d15f1b00000000, 0xcd2cea1a00000000, + 0xea2b341800000000, 0xf7d6811900000000, 0xa425881d00000000, + 0xb9d83d1c00000000, 0x9edfe31e00000000, 0x8322561f00000000, + 0x3839f01600000000, 0x25c4451700000000, 0x02c39b1500000000, + 0x1f3e2e1400000000, 0x4ccd271000000000, 0x5130921100000000, + 0x76374c1300000000, 0x6bcaf91200000000, 0xa0a3bf3600000000, + 0xbd5e0a3700000000, 0x9a59d43500000000, 0x87a4613400000000, + 0xd457683000000000, 0xc9aadd3100000000, 0xeead033300000000, + 0xf350b63200000000, 0x484b103b00000000, 0x55b6a53a00000000, + 0x72b17b3800000000, 0x6f4cce3900000000, 0x3cbfc73d00000000, + 0x2142723c00000000, 0x0645ac3e00000000, 0x1bb8193f00000000, + 0x7072e02d00000000, 0x6d8f552c00000000, 0x4a888b2e00000000, + 0x57753e2f00000000, 0x0486372b00000000, 0x197b822a00000000, + 0x3e7c5c2800000000, 0x2381e92900000000, 0x989a4f2000000000, + 0x8567fa2100000000, 0xa260242300000000, 0xbf9d912200000000, + 0xec6e982600000000, 0xf1932d2700000000, 0xd694f32500000000, + 0xcb69462400000000, 0x40477f6d00000000, 0x5dbaca6c00000000, + 0x7abd146e00000000, 0x6740a16f00000000, 0x34b3a86b00000000, + 0x294e1d6a00000000, 0x0e49c36800000000, 0x13b4766900000000, + 0xa8afd06000000000, 0xb552656100000000, 0x9255bb6300000000, + 0x8fa80e6200000000, 0xdc5b076600000000, 0xc1a6b26700000000, + 0xe6a16c6500000000, 0xfb5cd96400000000, 0x9096207600000000, + 0x8d6b957700000000, 0xaa6c4b7500000000, 0xb791fe7400000000, + 0xe462f77000000000, 0xf99f427100000000, 0xde989c7300000000, + 0xc365297200000000, 0x787e8f7b00000000, 0x65833a7a00000000, + 0x4284e47800000000, 0x5f79517900000000, 0x0c8a587d00000000, + 0x1177ed7c00000000, 0x3670337e00000000, 0x2b8d867f00000000, + 0xe0e4c05b00000000, 0xfd19755a00000000, 0xda1eab5800000000, + 0xc7e31e5900000000, 0x9410175d00000000, 0x89eda25c00000000, + 0xaeea7c5e00000000, 0xb317c95f00000000, 0x080c6f5600000000, + 0x15f1da5700000000, 0x32f6045500000000, 0x2f0bb15400000000, + 0x7cf8b85000000000, 0x61050d5100000000, 0x4602d35300000000, + 0x5bff665200000000, 0x30359f4000000000, 0x2dc82a4100000000, + 0x0acff44300000000, 0x1732414200000000, 0x44c1484600000000, + 0x593cfd4700000000, 0x7e3b234500000000, 0x63c6964400000000, + 0xd8dd304d00000000, 0xc520854c00000000, 0xe2275b4e00000000, + 0xffdaee4f00000000, 0xac29e74b00000000, 0xb1d4524a00000000, + 0x96d38c4800000000, 0x8b2e394900000000, 0x808efeda00000000, + 0x9d734bdb00000000, 0xba7495d900000000, 0xa78920d800000000, + 0xf47a29dc00000000, 0xe9879cdd00000000, 0xce8042df00000000, + 0xd37df7de00000000, 0x686651d700000000, 0x759be4d600000000, + 0x529c3ad400000000, 0x4f618fd500000000, 0x1c9286d100000000, + 0x016f33d000000000, 0x2668edd200000000, 0x3b9558d300000000, + 0x505fa1c100000000, 0x4da214c000000000, 0x6aa5cac200000000, + 0x77587fc300000000, 0x24ab76c700000000, 0x3956c3c600000000, + 0x1e511dc400000000, 0x03aca8c500000000, 0xb8b70ecc00000000, + 0xa54abbcd00000000, 0x824d65cf00000000, 0x9fb0d0ce00000000, + 0xcc43d9ca00000000, 0xd1be6ccb00000000, 0xf6b9b2c900000000, + 0xeb4407c800000000, 0x202d41ec00000000, 0x3dd0f4ed00000000, + 0x1ad72aef00000000, 0x072a9fee00000000, 0x54d996ea00000000, + 0x492423eb00000000, 0x6e23fde900000000, 0x73de48e800000000, + 0xc8c5eee100000000, 0xd5385be000000000, 0xf23f85e200000000, + 0xefc230e300000000, 0xbc3139e700000000, 0xa1cc8ce600000000, + 0x86cb52e400000000, 0x9b36e7e500000000, 0xf0fc1ef700000000, + 0xed01abf600000000, 0xca0675f400000000, 0xd7fbc0f500000000, + 0x8408c9f100000000, 0x99f57cf000000000, 0xbef2a2f200000000, + 0xa30f17f300000000, 0x1814b1fa00000000, 0x05e904fb00000000, + 0x22eedaf900000000, 0x3f136ff800000000, 0x6ce066fc00000000, + 0x711dd3fd00000000, 0x561a0dff00000000, 0x4be7b8fe00000000, + 0xc0c981b700000000, 0xdd3434b600000000, 0xfa33eab400000000, + 0xe7ce5fb500000000, 0xb43d56b100000000, 0xa9c0e3b000000000, + 0x8ec73db200000000, 0x933a88b300000000, 0x28212eba00000000, + 0x35dc9bbb00000000, 0x12db45b900000000, 0x0f26f0b800000000, + 0x5cd5f9bc00000000, 0x41284cbd00000000, 0x662f92bf00000000, + 0x7bd227be00000000, 0x1018deac00000000, 0x0de56bad00000000, + 0x2ae2b5af00000000, 0x371f00ae00000000, 0x64ec09aa00000000, + 0x7911bcab00000000, 0x5e1662a900000000, 0x43ebd7a800000000, + 0xf8f071a100000000, 0xe50dc4a000000000, 0xc20a1aa200000000, + 0xdff7afa300000000, 0x8c04a6a700000000, 0x91f913a600000000, + 0xb6fecda400000000, 0xab0378a500000000, 0x606a3e8100000000, + 0x7d978b8000000000, 0x5a90558200000000, 0x476de08300000000, + 0x149ee98700000000, 0x09635c8600000000, 0x2e64828400000000, + 0x3399378500000000, 0x8882918c00000000, 0x957f248d00000000, + 0xb278fa8f00000000, 0xaf854f8e00000000, 0xfc76468a00000000, + 0xe18bf38b00000000, 0xc68c2d8900000000, 0xdb71988800000000, + 0xb0bb619a00000000, 0xad46d49b00000000, 0x8a410a9900000000, + 0x97bcbf9800000000, 0xc44fb69c00000000, 0xd9b2039d00000000, + 0xfeb5dd9f00000000, 0xe348689e00000000, 0x5853ce9700000000, + 0x45ae7b9600000000, 0x62a9a59400000000, 0x7f54109500000000, + 0x2ca7199100000000, 0x315aac9000000000, 0x165d729200000000, + 0x0ba0c79300000000}, + {0x0000000000000000, 0x24d9076300000000, 0x48b20fc600000000, + 0x6c6b08a500000000, 0xd1626e5700000000, 0xf5bb693400000000, + 0x99d0619100000000, 0xbd0966f200000000, 0xa2c5dcae00000000, + 0x861cdbcd00000000, 0xea77d36800000000, 0xceaed40b00000000, + 0x73a7b2f900000000, 0x577eb59a00000000, 0x3b15bd3f00000000, + 0x1fccba5c00000000, 0x058dc88600000000, 0x2154cfe500000000, + 0x4d3fc74000000000, 0x69e6c02300000000, 0xd4efa6d100000000, + 0xf036a1b200000000, 0x9c5da91700000000, 0xb884ae7400000000, + 0xa748142800000000, 0x8391134b00000000, 0xeffa1bee00000000, + 0xcb231c8d00000000, 0x762a7a7f00000000, 0x52f37d1c00000000, + 0x3e9875b900000000, 0x1a4172da00000000, 0x4b1ce0d600000000, + 0x6fc5e7b500000000, 0x03aeef1000000000, 0x2777e87300000000, + 0x9a7e8e8100000000, 0xbea789e200000000, 0xd2cc814700000000, + 0xf615862400000000, 0xe9d93c7800000000, 0xcd003b1b00000000, + 0xa16b33be00000000, 0x85b234dd00000000, 0x38bb522f00000000, + 0x1c62554c00000000, 0x70095de900000000, 0x54d05a8a00000000, + 0x4e91285000000000, 0x6a482f3300000000, 0x0623279600000000, + 0x22fa20f500000000, 0x9ff3460700000000, 0xbb2a416400000000, + 0xd74149c100000000, 0xf3984ea200000000, 0xec54f4fe00000000, + 0xc88df39d00000000, 0xa4e6fb3800000000, 0x803ffc5b00000000, + 0x3d369aa900000000, 0x19ef9dca00000000, 0x7584956f00000000, + 0x515d920c00000000, 0xd73eb17600000000, 0xf3e7b61500000000, + 0x9f8cbeb000000000, 0xbb55b9d300000000, 0x065cdf2100000000, + 0x2285d84200000000, 0x4eeed0e700000000, 0x6a37d78400000000, + 0x75fb6dd800000000, 0x51226abb00000000, 0x3d49621e00000000, + 0x1990657d00000000, 0xa499038f00000000, 0x804004ec00000000, + 0xec2b0c4900000000, 0xc8f20b2a00000000, 0xd2b379f000000000, + 0xf66a7e9300000000, 0x9a01763600000000, 0xbed8715500000000, + 0x03d117a700000000, 0x270810c400000000, 0x4b63186100000000, + 0x6fba1f0200000000, 0x7076a55e00000000, 0x54afa23d00000000, + 0x38c4aa9800000000, 0x1c1dadfb00000000, 0xa114cb0900000000, + 0x85cdcc6a00000000, 0xe9a6c4cf00000000, 0xcd7fc3ac00000000, + 0x9c2251a000000000, 0xb8fb56c300000000, 0xd4905e6600000000, + 0xf049590500000000, 0x4d403ff700000000, 0x6999389400000000, + 0x05f2303100000000, 0x212b375200000000, 0x3ee78d0e00000000, + 0x1a3e8a6d00000000, 0x765582c800000000, 0x528c85ab00000000, + 0xef85e35900000000, 0xcb5ce43a00000000, 0xa737ec9f00000000, + 0x83eeebfc00000000, 0x99af992600000000, 0xbd769e4500000000, + 0xd11d96e000000000, 0xf5c4918300000000, 0x48cdf77100000000, + 0x6c14f01200000000, 0x007ff8b700000000, 0x24a6ffd400000000, + 0x3b6a458800000000, 0x1fb342eb00000000, 0x73d84a4e00000000, + 0x57014d2d00000000, 0xea082bdf00000000, 0xced12cbc00000000, + 0xa2ba241900000000, 0x8663237a00000000, 0xae7d62ed00000000, + 0x8aa4658e00000000, 0xe6cf6d2b00000000, 0xc2166a4800000000, + 0x7f1f0cba00000000, 0x5bc60bd900000000, 0x37ad037c00000000, + 0x1374041f00000000, 0x0cb8be4300000000, 0x2861b92000000000, + 0x440ab18500000000, 0x60d3b6e600000000, 0xdddad01400000000, + 0xf903d77700000000, 0x9568dfd200000000, 0xb1b1d8b100000000, + 0xabf0aa6b00000000, 0x8f29ad0800000000, 0xe342a5ad00000000, + 0xc79ba2ce00000000, 0x7a92c43c00000000, 0x5e4bc35f00000000, + 0x3220cbfa00000000, 0x16f9cc9900000000, 0x093576c500000000, + 0x2dec71a600000000, 0x4187790300000000, 0x655e7e6000000000, + 0xd857189200000000, 0xfc8e1ff100000000, 0x90e5175400000000, + 0xb43c103700000000, 0xe561823b00000000, 0xc1b8855800000000, + 0xadd38dfd00000000, 0x890a8a9e00000000, 0x3403ec6c00000000, + 0x10daeb0f00000000, 0x7cb1e3aa00000000, 0x5868e4c900000000, + 0x47a45e9500000000, 0x637d59f600000000, 0x0f16515300000000, + 0x2bcf563000000000, 0x96c630c200000000, 0xb21f37a100000000, + 0xde743f0400000000, 0xfaad386700000000, 0xe0ec4abd00000000, + 0xc4354dde00000000, 0xa85e457b00000000, 0x8c87421800000000, + 0x318e24ea00000000, 0x1557238900000000, 0x793c2b2c00000000, + 0x5de52c4f00000000, 0x4229961300000000, 0x66f0917000000000, + 0x0a9b99d500000000, 0x2e429eb600000000, 0x934bf84400000000, + 0xb792ff2700000000, 0xdbf9f78200000000, 0xff20f0e100000000, + 0x7943d39b00000000, 0x5d9ad4f800000000, 0x31f1dc5d00000000, + 0x1528db3e00000000, 0xa821bdcc00000000, 0x8cf8baaf00000000, + 0xe093b20a00000000, 0xc44ab56900000000, 0xdb860f3500000000, + 0xff5f085600000000, 0x933400f300000000, 0xb7ed079000000000, + 0x0ae4616200000000, 0x2e3d660100000000, 0x42566ea400000000, + 0x668f69c700000000, 0x7cce1b1d00000000, 0x58171c7e00000000, + 0x347c14db00000000, 0x10a513b800000000, 0xadac754a00000000, + 0x8975722900000000, 0xe51e7a8c00000000, 0xc1c77def00000000, + 0xde0bc7b300000000, 0xfad2c0d000000000, 0x96b9c87500000000, + 0xb260cf1600000000, 0x0f69a9e400000000, 0x2bb0ae8700000000, + 0x47dba62200000000, 0x6302a14100000000, 0x325f334d00000000, + 0x1686342e00000000, 0x7aed3c8b00000000, 0x5e343be800000000, + 0xe33d5d1a00000000, 0xc7e45a7900000000, 0xab8f52dc00000000, + 0x8f5655bf00000000, 0x909aefe300000000, 0xb443e88000000000, + 0xd828e02500000000, 0xfcf1e74600000000, 0x41f881b400000000, + 0x652186d700000000, 0x094a8e7200000000, 0x2d93891100000000, + 0x37d2fbcb00000000, 0x130bfca800000000, 0x7f60f40d00000000, + 0x5bb9f36e00000000, 0xe6b0959c00000000, 0xc26992ff00000000, + 0xae029a5a00000000, 0x8adb9d3900000000, 0x9517276500000000, + 0xb1ce200600000000, 0xdda528a300000000, 0xf97c2fc000000000, + 0x4475493200000000, 0x60ac4e5100000000, 0x0cc746f400000000, + 0x281e419700000000}, + {0x0000000000000000, 0x08e3603c00000000, 0x10c6c17800000000, + 0x1825a14400000000, 0x208c83f100000000, 0x286fe3cd00000000, + 0x304a428900000000, 0x38a922b500000000, 0x011e763800000000, + 0x09fd160400000000, 0x11d8b74000000000, 0x193bd77c00000000, + 0x2192f5c900000000, 0x297195f500000000, 0x315434b100000000, + 0x39b7548d00000000, 0x023cec7000000000, 0x0adf8c4c00000000, + 0x12fa2d0800000000, 0x1a194d3400000000, 0x22b06f8100000000, + 0x2a530fbd00000000, 0x3276aef900000000, 0x3a95cec500000000, + 0x03229a4800000000, 0x0bc1fa7400000000, 0x13e45b3000000000, + 0x1b073b0c00000000, 0x23ae19b900000000, 0x2b4d798500000000, + 0x3368d8c100000000, 0x3b8bb8fd00000000, 0x0478d8e100000000, + 0x0c9bb8dd00000000, 0x14be199900000000, 0x1c5d79a500000000, + 0x24f45b1000000000, 0x2c173b2c00000000, 0x34329a6800000000, + 0x3cd1fa5400000000, 0x0566aed900000000, 0x0d85cee500000000, + 0x15a06fa100000000, 0x1d430f9d00000000, 0x25ea2d2800000000, + 0x2d094d1400000000, 0x352cec5000000000, 0x3dcf8c6c00000000, + 0x0644349100000000, 0x0ea754ad00000000, 0x1682f5e900000000, + 0x1e6195d500000000, 0x26c8b76000000000, 0x2e2bd75c00000000, + 0x360e761800000000, 0x3eed162400000000, 0x075a42a900000000, + 0x0fb9229500000000, 0x179c83d100000000, 0x1f7fe3ed00000000, + 0x27d6c15800000000, 0x2f35a16400000000, 0x3710002000000000, + 0x3ff3601c00000000, 0x49f6c11800000000, 0x4115a12400000000, + 0x5930006000000000, 0x51d3605c00000000, 0x697a42e900000000, + 0x619922d500000000, 0x79bc839100000000, 0x715fe3ad00000000, + 0x48e8b72000000000, 0x400bd71c00000000, 0x582e765800000000, + 0x50cd166400000000, 0x686434d100000000, 0x608754ed00000000, + 0x78a2f5a900000000, 0x7041959500000000, 0x4bca2d6800000000, + 0x43294d5400000000, 0x5b0cec1000000000, 0x53ef8c2c00000000, + 0x6b46ae9900000000, 0x63a5cea500000000, 0x7b806fe100000000, + 0x73630fdd00000000, 0x4ad45b5000000000, 0x42373b6c00000000, + 0x5a129a2800000000, 0x52f1fa1400000000, 0x6a58d8a100000000, + 0x62bbb89d00000000, 0x7a9e19d900000000, 0x727d79e500000000, + 0x4d8e19f900000000, 0x456d79c500000000, 0x5d48d88100000000, + 0x55abb8bd00000000, 0x6d029a0800000000, 0x65e1fa3400000000, + 0x7dc45b7000000000, 0x75273b4c00000000, 0x4c906fc100000000, + 0x44730ffd00000000, 0x5c56aeb900000000, 0x54b5ce8500000000, + 0x6c1cec3000000000, 0x64ff8c0c00000000, 0x7cda2d4800000000, + 0x74394d7400000000, 0x4fb2f58900000000, 0x475195b500000000, + 0x5f7434f100000000, 0x579754cd00000000, 0x6f3e767800000000, + 0x67dd164400000000, 0x7ff8b70000000000, 0x771bd73c00000000, + 0x4eac83b100000000, 0x464fe38d00000000, 0x5e6a42c900000000, + 0x568922f500000000, 0x6e20004000000000, 0x66c3607c00000000, + 0x7ee6c13800000000, 0x7605a10400000000, 0x92ec833100000000, + 0x9a0fe30d00000000, 0x822a424900000000, 0x8ac9227500000000, + 0xb26000c000000000, 0xba8360fc00000000, 0xa2a6c1b800000000, + 0xaa45a18400000000, 0x93f2f50900000000, 0x9b11953500000000, + 0x8334347100000000, 0x8bd7544d00000000, 0xb37e76f800000000, + 0xbb9d16c400000000, 0xa3b8b78000000000, 0xab5bd7bc00000000, + 0x90d06f4100000000, 0x98330f7d00000000, 0x8016ae3900000000, + 0x88f5ce0500000000, 0xb05cecb000000000, 0xb8bf8c8c00000000, + 0xa09a2dc800000000, 0xa8794df400000000, 0x91ce197900000000, + 0x992d794500000000, 0x8108d80100000000, 0x89ebb83d00000000, + 0xb1429a8800000000, 0xb9a1fab400000000, 0xa1845bf000000000, + 0xa9673bcc00000000, 0x96945bd000000000, 0x9e773bec00000000, + 0x86529aa800000000, 0x8eb1fa9400000000, 0xb618d82100000000, + 0xbefbb81d00000000, 0xa6de195900000000, 0xae3d796500000000, + 0x978a2de800000000, 0x9f694dd400000000, 0x874cec9000000000, + 0x8faf8cac00000000, 0xb706ae1900000000, 0xbfe5ce2500000000, + 0xa7c06f6100000000, 0xaf230f5d00000000, 0x94a8b7a000000000, + 0x9c4bd79c00000000, 0x846e76d800000000, 0x8c8d16e400000000, + 0xb424345100000000, 0xbcc7546d00000000, 0xa4e2f52900000000, + 0xac01951500000000, 0x95b6c19800000000, 0x9d55a1a400000000, + 0x857000e000000000, 0x8d9360dc00000000, 0xb53a426900000000, + 0xbdd9225500000000, 0xa5fc831100000000, 0xad1fe32d00000000, + 0xdb1a422900000000, 0xd3f9221500000000, 0xcbdc835100000000, + 0xc33fe36d00000000, 0xfb96c1d800000000, 0xf375a1e400000000, + 0xeb5000a000000000, 0xe3b3609c00000000, 0xda04341100000000, + 0xd2e7542d00000000, 0xcac2f56900000000, 0xc221955500000000, + 0xfa88b7e000000000, 0xf26bd7dc00000000, 0xea4e769800000000, + 0xe2ad16a400000000, 0xd926ae5900000000, 0xd1c5ce6500000000, + 0xc9e06f2100000000, 0xc1030f1d00000000, 0xf9aa2da800000000, + 0xf1494d9400000000, 0xe96cecd000000000, 0xe18f8cec00000000, + 0xd838d86100000000, 0xd0dbb85d00000000, 0xc8fe191900000000, + 0xc01d792500000000, 0xf8b45b9000000000, 0xf0573bac00000000, + 0xe8729ae800000000, 0xe091fad400000000, 0xdf629ac800000000, + 0xd781faf400000000, 0xcfa45bb000000000, 0xc7473b8c00000000, + 0xffee193900000000, 0xf70d790500000000, 0xef28d84100000000, + 0xe7cbb87d00000000, 0xde7cecf000000000, 0xd69f8ccc00000000, + 0xceba2d8800000000, 0xc6594db400000000, 0xfef06f0100000000, + 0xf6130f3d00000000, 0xee36ae7900000000, 0xe6d5ce4500000000, + 0xdd5e76b800000000, 0xd5bd168400000000, 0xcd98b7c000000000, + 0xc57bd7fc00000000, 0xfdd2f54900000000, 0xf531957500000000, + 0xed14343100000000, 0xe5f7540d00000000, 0xdc40008000000000, + 0xd4a360bc00000000, 0xcc86c1f800000000, 0xc465a1c400000000, + 0xfccc837100000000, 0xf42fe34d00000000, 0xec0a420900000000, + 0xe4e9223500000000}, + {0x0000000000000000, 0xd1e8e70e00000000, 0xa2d1cf1d00000000, + 0x7339281300000000, 0x44a39f3b00000000, 0x954b783500000000, + 0xe672502600000000, 0x379ab72800000000, 0x88463f7700000000, + 0x59aed87900000000, 0x2a97f06a00000000, 0xfb7f176400000000, + 0xcce5a04c00000000, 0x1d0d474200000000, 0x6e346f5100000000, + 0xbfdc885f00000000, 0x108d7eee00000000, 0xc16599e000000000, + 0xb25cb1f300000000, 0x63b456fd00000000, 0x542ee1d500000000, + 0x85c606db00000000, 0xf6ff2ec800000000, 0x2717c9c600000000, + 0x98cb419900000000, 0x4923a69700000000, 0x3a1a8e8400000000, + 0xebf2698a00000000, 0xdc68dea200000000, 0x0d8039ac00000000, + 0x7eb911bf00000000, 0xaf51f6b100000000, 0x611c8c0700000000, + 0xb0f46b0900000000, 0xc3cd431a00000000, 0x1225a41400000000, + 0x25bf133c00000000, 0xf457f43200000000, 0x876edc2100000000, + 0x56863b2f00000000, 0xe95ab37000000000, 0x38b2547e00000000, + 0x4b8b7c6d00000000, 0x9a639b6300000000, 0xadf92c4b00000000, + 0x7c11cb4500000000, 0x0f28e35600000000, 0xdec0045800000000, + 0x7191f2e900000000, 0xa07915e700000000, 0xd3403df400000000, + 0x02a8dafa00000000, 0x35326dd200000000, 0xe4da8adc00000000, + 0x97e3a2cf00000000, 0x460b45c100000000, 0xf9d7cd9e00000000, + 0x283f2a9000000000, 0x5b06028300000000, 0x8aeee58d00000000, + 0xbd7452a500000000, 0x6c9cb5ab00000000, 0x1fa59db800000000, + 0xce4d7ab600000000, 0xc238180f00000000, 0x13d0ff0100000000, + 0x60e9d71200000000, 0xb101301c00000000, 0x869b873400000000, + 0x5773603a00000000, 0x244a482900000000, 0xf5a2af2700000000, + 0x4a7e277800000000, 0x9b96c07600000000, 0xe8afe86500000000, + 0x39470f6b00000000, 0x0eddb84300000000, 0xdf355f4d00000000, + 0xac0c775e00000000, 0x7de4905000000000, 0xd2b566e100000000, + 0x035d81ef00000000, 0x7064a9fc00000000, 0xa18c4ef200000000, + 0x9616f9da00000000, 0x47fe1ed400000000, 0x34c736c700000000, + 0xe52fd1c900000000, 0x5af3599600000000, 0x8b1bbe9800000000, + 0xf822968b00000000, 0x29ca718500000000, 0x1e50c6ad00000000, + 0xcfb821a300000000, 0xbc8109b000000000, 0x6d69eebe00000000, + 0xa324940800000000, 0x72cc730600000000, 0x01f55b1500000000, + 0xd01dbc1b00000000, 0xe7870b3300000000, 0x366fec3d00000000, + 0x4556c42e00000000, 0x94be232000000000, 0x2b62ab7f00000000, + 0xfa8a4c7100000000, 0x89b3646200000000, 0x585b836c00000000, + 0x6fc1344400000000, 0xbe29d34a00000000, 0xcd10fb5900000000, + 0x1cf81c5700000000, 0xb3a9eae600000000, 0x62410de800000000, + 0x117825fb00000000, 0xc090c2f500000000, 0xf70a75dd00000000, + 0x26e292d300000000, 0x55dbbac000000000, 0x84335dce00000000, + 0x3befd59100000000, 0xea07329f00000000, 0x993e1a8c00000000, + 0x48d6fd8200000000, 0x7f4c4aaa00000000, 0xaea4ada400000000, + 0xdd9d85b700000000, 0x0c7562b900000000, 0x8471301e00000000, + 0x5599d71000000000, 0x26a0ff0300000000, 0xf748180d00000000, + 0xc0d2af2500000000, 0x113a482b00000000, 0x6203603800000000, + 0xb3eb873600000000, 0x0c370f6900000000, 0xdddfe86700000000, + 0xaee6c07400000000, 0x7f0e277a00000000, 0x4894905200000000, + 0x997c775c00000000, 0xea455f4f00000000, 0x3badb84100000000, + 0x94fc4ef000000000, 0x4514a9fe00000000, 0x362d81ed00000000, + 0xe7c566e300000000, 0xd05fd1cb00000000, 0x01b736c500000000, + 0x728e1ed600000000, 0xa366f9d800000000, 0x1cba718700000000, + 0xcd52968900000000, 0xbe6bbe9a00000000, 0x6f83599400000000, + 0x5819eebc00000000, 0x89f109b200000000, 0xfac821a100000000, + 0x2b20c6af00000000, 0xe56dbc1900000000, 0x34855b1700000000, + 0x47bc730400000000, 0x9654940a00000000, 0xa1ce232200000000, + 0x7026c42c00000000, 0x031fec3f00000000, 0xd2f70b3100000000, + 0x6d2b836e00000000, 0xbcc3646000000000, 0xcffa4c7300000000, + 0x1e12ab7d00000000, 0x29881c5500000000, 0xf860fb5b00000000, + 0x8b59d34800000000, 0x5ab1344600000000, 0xf5e0c2f700000000, + 0x240825f900000000, 0x57310dea00000000, 0x86d9eae400000000, + 0xb1435dcc00000000, 0x60abbac200000000, 0x139292d100000000, + 0xc27a75df00000000, 0x7da6fd8000000000, 0xac4e1a8e00000000, + 0xdf77329d00000000, 0x0e9fd59300000000, 0x390562bb00000000, + 0xe8ed85b500000000, 0x9bd4ada600000000, 0x4a3c4aa800000000, + 0x4649281100000000, 0x97a1cf1f00000000, 0xe498e70c00000000, + 0x3570000200000000, 0x02eab72a00000000, 0xd302502400000000, + 0xa03b783700000000, 0x71d39f3900000000, 0xce0f176600000000, + 0x1fe7f06800000000, 0x6cded87b00000000, 0xbd363f7500000000, + 0x8aac885d00000000, 0x5b446f5300000000, 0x287d474000000000, + 0xf995a04e00000000, 0x56c456ff00000000, 0x872cb1f100000000, + 0xf41599e200000000, 0x25fd7eec00000000, 0x1267c9c400000000, + 0xc38f2eca00000000, 0xb0b606d900000000, 0x615ee1d700000000, + 0xde82698800000000, 0x0f6a8e8600000000, 0x7c53a69500000000, + 0xadbb419b00000000, 0x9a21f6b300000000, 0x4bc911bd00000000, + 0x38f039ae00000000, 0xe918dea000000000, 0x2755a41600000000, + 0xf6bd431800000000, 0x85846b0b00000000, 0x546c8c0500000000, + 0x63f63b2d00000000, 0xb21edc2300000000, 0xc127f43000000000, + 0x10cf133e00000000, 0xaf139b6100000000, 0x7efb7c6f00000000, + 0x0dc2547c00000000, 0xdc2ab37200000000, 0xebb0045a00000000, + 0x3a58e35400000000, 0x4961cb4700000000, 0x98892c4900000000, + 0x37d8daf800000000, 0xe6303df600000000, 0x950915e500000000, + 0x44e1f2eb00000000, 0x737b45c300000000, 0xa293a2cd00000000, + 0xd1aa8ade00000000, 0x00426dd000000000, 0xbf9ee58f00000000, + 0x6e76028100000000, 0x1d4f2a9200000000, 0xcca7cd9c00000000, + 0xfb3d7ab400000000, 0x2ad59dba00000000, 0x59ecb5a900000000, + 0x880452a700000000}, + {0x0000000000000000, 0xaa05daf100000000, 0x150dc53800000000, + 0xbf081fc900000000, 0x2a1a8a7100000000, 0x801f508000000000, + 0x3f174f4900000000, 0x951295b800000000, 0x543414e300000000, + 0xfe31ce1200000000, 0x4139d1db00000000, 0xeb3c0b2a00000000, + 0x7e2e9e9200000000, 0xd42b446300000000, 0x6b235baa00000000, + 0xc126815b00000000, 0xe96e591d00000000, 0x436b83ec00000000, + 0xfc639c2500000000, 0x566646d400000000, 0xc374d36c00000000, + 0x6971099d00000000, 0xd679165400000000, 0x7c7ccca500000000, + 0xbd5a4dfe00000000, 0x175f970f00000000, 0xa85788c600000000, + 0x0252523700000000, 0x9740c78f00000000, 0x3d451d7e00000000, + 0x824d02b700000000, 0x2848d84600000000, 0xd2ddb23a00000000, + 0x78d868cb00000000, 0xc7d0770200000000, 0x6dd5adf300000000, + 0xf8c7384b00000000, 0x52c2e2ba00000000, 0xedcafd7300000000, + 0x47cf278200000000, 0x86e9a6d900000000, 0x2cec7c2800000000, + 0x93e463e100000000, 0x39e1b91000000000, 0xacf32ca800000000, + 0x06f6f65900000000, 0xb9fee99000000000, 0x13fb336100000000, + 0x3bb3eb2700000000, 0x91b631d600000000, 0x2ebe2e1f00000000, + 0x84bbf4ee00000000, 0x11a9615600000000, 0xbbacbba700000000, + 0x04a4a46e00000000, 0xaea17e9f00000000, 0x6f87ffc400000000, + 0xc582253500000000, 0x7a8a3afc00000000, 0xd08fe00d00000000, + 0x459d75b500000000, 0xef98af4400000000, 0x5090b08d00000000, + 0xfa956a7c00000000, 0xa4bb657500000000, 0x0ebebf8400000000, + 0xb1b6a04d00000000, 0x1bb37abc00000000, 0x8ea1ef0400000000, + 0x24a435f500000000, 0x9bac2a3c00000000, 0x31a9f0cd00000000, + 0xf08f719600000000, 0x5a8aab6700000000, 0xe582b4ae00000000, + 0x4f876e5f00000000, 0xda95fbe700000000, 0x7090211600000000, + 0xcf983edf00000000, 0x659de42e00000000, 0x4dd53c6800000000, + 0xe7d0e69900000000, 0x58d8f95000000000, 0xf2dd23a100000000, + 0x67cfb61900000000, 0xcdca6ce800000000, 0x72c2732100000000, + 0xd8c7a9d000000000, 0x19e1288b00000000, 0xb3e4f27a00000000, + 0x0cecedb300000000, 0xa6e9374200000000, 0x33fba2fa00000000, + 0x99fe780b00000000, 0x26f667c200000000, 0x8cf3bd3300000000, + 0x7666d74f00000000, 0xdc630dbe00000000, 0x636b127700000000, + 0xc96ec88600000000, 0x5c7c5d3e00000000, 0xf67987cf00000000, + 0x4971980600000000, 0xe37442f700000000, 0x2252c3ac00000000, + 0x8857195d00000000, 0x375f069400000000, 0x9d5adc6500000000, + 0x084849dd00000000, 0xa24d932c00000000, 0x1d458ce500000000, + 0xb740561400000000, 0x9f088e5200000000, 0x350d54a300000000, + 0x8a054b6a00000000, 0x2000919b00000000, 0xb512042300000000, + 0x1f17ded200000000, 0xa01fc11b00000000, 0x0a1a1bea00000000, + 0xcb3c9ab100000000, 0x6139404000000000, 0xde315f8900000000, + 0x7434857800000000, 0xe12610c000000000, 0x4b23ca3100000000, + 0xf42bd5f800000000, 0x5e2e0f0900000000, 0x4877cbea00000000, + 0xe272111b00000000, 0x5d7a0ed200000000, 0xf77fd42300000000, + 0x626d419b00000000, 0xc8689b6a00000000, 0x776084a300000000, + 0xdd655e5200000000, 0x1c43df0900000000, 0xb64605f800000000, + 0x094e1a3100000000, 0xa34bc0c000000000, 0x3659557800000000, + 0x9c5c8f8900000000, 0x2354904000000000, 0x89514ab100000000, + 0xa11992f700000000, 0x0b1c480600000000, 0xb41457cf00000000, + 0x1e118d3e00000000, 0x8b03188600000000, 0x2106c27700000000, + 0x9e0eddbe00000000, 0x340b074f00000000, 0xf52d861400000000, + 0x5f285ce500000000, 0xe020432c00000000, 0x4a2599dd00000000, + 0xdf370c6500000000, 0x7532d69400000000, 0xca3ac95d00000000, + 0x603f13ac00000000, 0x9aaa79d000000000, 0x30afa32100000000, + 0x8fa7bce800000000, 0x25a2661900000000, 0xb0b0f3a100000000, + 0x1ab5295000000000, 0xa5bd369900000000, 0x0fb8ec6800000000, + 0xce9e6d3300000000, 0x649bb7c200000000, 0xdb93a80b00000000, + 0x719672fa00000000, 0xe484e74200000000, 0x4e813db300000000, + 0xf189227a00000000, 0x5b8cf88b00000000, 0x73c420cd00000000, + 0xd9c1fa3c00000000, 0x66c9e5f500000000, 0xcccc3f0400000000, + 0x59deaabc00000000, 0xf3db704d00000000, 0x4cd36f8400000000, + 0xe6d6b57500000000, 0x27f0342e00000000, 0x8df5eedf00000000, + 0x32fdf11600000000, 0x98f82be700000000, 0x0deabe5f00000000, + 0xa7ef64ae00000000, 0x18e77b6700000000, 0xb2e2a19600000000, + 0xecccae9f00000000, 0x46c9746e00000000, 0xf9c16ba700000000, + 0x53c4b15600000000, 0xc6d624ee00000000, 0x6cd3fe1f00000000, + 0xd3dbe1d600000000, 0x79de3b2700000000, 0xb8f8ba7c00000000, + 0x12fd608d00000000, 0xadf57f4400000000, 0x07f0a5b500000000, + 0x92e2300d00000000, 0x38e7eafc00000000, 0x87eff53500000000, + 0x2dea2fc400000000, 0x05a2f78200000000, 0xafa72d7300000000, + 0x10af32ba00000000, 0xbaaae84b00000000, 0x2fb87df300000000, + 0x85bda70200000000, 0x3ab5b8cb00000000, 0x90b0623a00000000, + 0x5196e36100000000, 0xfb93399000000000, 0x449b265900000000, + 0xee9efca800000000, 0x7b8c691000000000, 0xd189b3e100000000, + 0x6e81ac2800000000, 0xc48476d900000000, 0x3e111ca500000000, + 0x9414c65400000000, 0x2b1cd99d00000000, 0x8119036c00000000, + 0x140b96d400000000, 0xbe0e4c2500000000, 0x010653ec00000000, + 0xab03891d00000000, 0x6a25084600000000, 0xc020d2b700000000, + 0x7f28cd7e00000000, 0xd52d178f00000000, 0x403f823700000000, + 0xea3a58c600000000, 0x5532470f00000000, 0xff379dfe00000000, + 0xd77f45b800000000, 0x7d7a9f4900000000, 0xc272808000000000, + 0x68775a7100000000, 0xfd65cfc900000000, 0x5760153800000000, + 0xe8680af100000000, 0x426dd00000000000, 0x834b515b00000000, + 0x294e8baa00000000, 0x9646946300000000, 0x3c434e9200000000, + 0xa951db2a00000000, 0x035401db00000000, 0xbc5c1e1200000000, + 0x1659c4e300000000}}; + +#else /* W == 4 */ + +local const z_crc_t FAR crc_braid_table[][256] = { + {0x00000000, 0xae689191, 0x87a02563, 0x29c8b4f2, 0xd4314c87, + 0x7a59dd16, 0x539169e4, 0xfdf9f875, 0x73139f4f, 0xdd7b0ede, + 0xf4b3ba2c, 0x5adb2bbd, 0xa722d3c8, 0x094a4259, 0x2082f6ab, + 0x8eea673a, 0xe6273e9e, 0x484faf0f, 0x61871bfd, 0xcfef8a6c, + 0x32167219, 0x9c7ee388, 0xb5b6577a, 0x1bdec6eb, 0x9534a1d1, + 0x3b5c3040, 0x129484b2, 0xbcfc1523, 0x4105ed56, 0xef6d7cc7, + 0xc6a5c835, 0x68cd59a4, 0x173f7b7d, 0xb957eaec, 0x909f5e1e, + 0x3ef7cf8f, 0xc30e37fa, 0x6d66a66b, 0x44ae1299, 0xeac68308, + 0x642ce432, 0xca4475a3, 0xe38cc151, 0x4de450c0, 0xb01da8b5, + 0x1e753924, 0x37bd8dd6, 0x99d51c47, 0xf11845e3, 0x5f70d472, + 0x76b86080, 0xd8d0f111, 0x25290964, 0x8b4198f5, 0xa2892c07, + 0x0ce1bd96, 0x820bdaac, 0x2c634b3d, 0x05abffcf, 0xabc36e5e, + 0x563a962b, 0xf85207ba, 0xd19ab348, 0x7ff222d9, 0x2e7ef6fa, + 0x8016676b, 0xa9ded399, 0x07b64208, 0xfa4fba7d, 0x54272bec, + 0x7def9f1e, 0xd3870e8f, 0x5d6d69b5, 0xf305f824, 0xdacd4cd6, + 0x74a5dd47, 0x895c2532, 0x2734b4a3, 0x0efc0051, 0xa09491c0, + 0xc859c864, 0x663159f5, 0x4ff9ed07, 0xe1917c96, 0x1c6884e3, + 0xb2001572, 0x9bc8a180, 0x35a03011, 0xbb4a572b, 0x1522c6ba, + 0x3cea7248, 0x9282e3d9, 0x6f7b1bac, 0xc1138a3d, 0xe8db3ecf, + 0x46b3af5e, 0x39418d87, 0x97291c16, 0xbee1a8e4, 0x10893975, + 0xed70c100, 0x43185091, 0x6ad0e463, 0xc4b875f2, 0x4a5212c8, + 0xe43a8359, 0xcdf237ab, 0x639aa63a, 0x9e635e4f, 0x300bcfde, + 0x19c37b2c, 0xb7abeabd, 0xdf66b319, 0x710e2288, 0x58c6967a, + 0xf6ae07eb, 0x0b57ff9e, 0xa53f6e0f, 0x8cf7dafd, 0x229f4b6c, + 0xac752c56, 0x021dbdc7, 0x2bd50935, 0x85bd98a4, 0x784460d1, + 0xd62cf140, 0xffe445b2, 0x518cd423, 0x5cfdedf4, 0xf2957c65, + 0xdb5dc897, 0x75355906, 0x88cca173, 0x26a430e2, 0x0f6c8410, + 0xa1041581, 0x2fee72bb, 0x8186e32a, 0xa84e57d8, 0x0626c649, + 0xfbdf3e3c, 0x55b7afad, 0x7c7f1b5f, 0xd2178ace, 0xbadad36a, + 0x14b242fb, 0x3d7af609, 0x93126798, 0x6eeb9fed, 0xc0830e7c, + 0xe94bba8e, 0x47232b1f, 0xc9c94c25, 0x67a1ddb4, 0x4e696946, + 0xe001f8d7, 0x1df800a2, 0xb3909133, 0x9a5825c1, 0x3430b450, + 0x4bc29689, 0xe5aa0718, 0xcc62b3ea, 0x620a227b, 0x9ff3da0e, + 0x319b4b9f, 0x1853ff6d, 0xb63b6efc, 0x38d109c6, 0x96b99857, + 0xbf712ca5, 0x1119bd34, 0xece04541, 0x4288d4d0, 0x6b406022, + 0xc528f1b3, 0xade5a817, 0x038d3986, 0x2a458d74, 0x842d1ce5, + 0x79d4e490, 0xd7bc7501, 0xfe74c1f3, 0x501c5062, 0xdef63758, + 0x709ea6c9, 0x5956123b, 0xf73e83aa, 0x0ac77bdf, 0xa4afea4e, + 0x8d675ebc, 0x230fcf2d, 0x72831b0e, 0xdceb8a9f, 0xf5233e6d, + 0x5b4baffc, 0xa6b25789, 0x08dac618, 0x211272ea, 0x8f7ae37b, + 0x01908441, 0xaff815d0, 0x8630a122, 0x285830b3, 0xd5a1c8c6, + 0x7bc95957, 0x5201eda5, 0xfc697c34, 0x94a42590, 0x3accb401, + 0x130400f3, 0xbd6c9162, 0x40956917, 0xeefdf886, 0xc7354c74, + 0x695ddde5, 0xe7b7badf, 0x49df2b4e, 0x60179fbc, 0xce7f0e2d, + 0x3386f658, 0x9dee67c9, 0xb426d33b, 0x1a4e42aa, 0x65bc6073, + 0xcbd4f1e2, 0xe21c4510, 0x4c74d481, 0xb18d2cf4, 0x1fe5bd65, + 0x362d0997, 0x98459806, 0x16afff3c, 0xb8c76ead, 0x910fda5f, + 0x3f674bce, 0xc29eb3bb, 0x6cf6222a, 0x453e96d8, 0xeb560749, + 0x839b5eed, 0x2df3cf7c, 0x043b7b8e, 0xaa53ea1f, 0x57aa126a, + 0xf9c283fb, 0xd00a3709, 0x7e62a698, 0xf088c1a2, 0x5ee05033, + 0x7728e4c1, 0xd9407550, 0x24b98d25, 0x8ad11cb4, 0xa319a846, + 0x0d7139d7}, + {0x00000000, 0xb9fbdbe8, 0xa886b191, 0x117d6a79, 0x8a7c6563, + 0x3387be8b, 0x22fad4f2, 0x9b010f1a, 0xcf89cc87, 0x7672176f, + 0x670f7d16, 0xdef4a6fe, 0x45f5a9e4, 0xfc0e720c, 0xed731875, + 0x5488c39d, 0x44629f4f, 0xfd9944a7, 0xece42ede, 0x551ff536, + 0xce1efa2c, 0x77e521c4, 0x66984bbd, 0xdf639055, 0x8beb53c8, + 0x32108820, 0x236de259, 0x9a9639b1, 0x019736ab, 0xb86ced43, + 0xa911873a, 0x10ea5cd2, 0x88c53e9e, 0x313ee576, 0x20438f0f, + 0x99b854e7, 0x02b95bfd, 0xbb428015, 0xaa3fea6c, 0x13c43184, + 0x474cf219, 0xfeb729f1, 0xefca4388, 0x56319860, 0xcd30977a, + 0x74cb4c92, 0x65b626eb, 0xdc4dfd03, 0xcca7a1d1, 0x755c7a39, + 0x64211040, 0xdddacba8, 0x46dbc4b2, 0xff201f5a, 0xee5d7523, + 0x57a6aecb, 0x032e6d56, 0xbad5b6be, 0xaba8dcc7, 0x1253072f, + 0x89520835, 0x30a9d3dd, 0x21d4b9a4, 0x982f624c, 0xcafb7b7d, + 0x7300a095, 0x627dcaec, 0xdb861104, 0x40871e1e, 0xf97cc5f6, + 0xe801af8f, 0x51fa7467, 0x0572b7fa, 0xbc896c12, 0xadf4066b, + 0x140fdd83, 0x8f0ed299, 0x36f50971, 0x27886308, 0x9e73b8e0, + 0x8e99e432, 0x37623fda, 0x261f55a3, 0x9fe48e4b, 0x04e58151, + 0xbd1e5ab9, 0xac6330c0, 0x1598eb28, 0x411028b5, 0xf8ebf35d, + 0xe9969924, 0x506d42cc, 0xcb6c4dd6, 0x7297963e, 0x63eafc47, + 0xda1127af, 0x423e45e3, 0xfbc59e0b, 0xeab8f472, 0x53432f9a, + 0xc8422080, 0x71b9fb68, 0x60c49111, 0xd93f4af9, 0x8db78964, + 0x344c528c, 0x253138f5, 0x9ccae31d, 0x07cbec07, 0xbe3037ef, + 0xaf4d5d96, 0x16b6867e, 0x065cdaac, 0xbfa70144, 0xaeda6b3d, + 0x1721b0d5, 0x8c20bfcf, 0x35db6427, 0x24a60e5e, 0x9d5dd5b6, + 0xc9d5162b, 0x702ecdc3, 0x6153a7ba, 0xd8a87c52, 0x43a97348, + 0xfa52a8a0, 0xeb2fc2d9, 0x52d41931, 0x4e87f0bb, 0xf77c2b53, + 0xe601412a, 0x5ffa9ac2, 0xc4fb95d8, 0x7d004e30, 0x6c7d2449, + 0xd586ffa1, 0x810e3c3c, 0x38f5e7d4, 0x29888dad, 0x90735645, + 0x0b72595f, 0xb28982b7, 0xa3f4e8ce, 0x1a0f3326, 0x0ae56ff4, + 0xb31eb41c, 0xa263de65, 0x1b98058d, 0x80990a97, 0x3962d17f, + 0x281fbb06, 0x91e460ee, 0xc56ca373, 0x7c97789b, 0x6dea12e2, + 0xd411c90a, 0x4f10c610, 0xf6eb1df8, 0xe7967781, 0x5e6dac69, + 0xc642ce25, 0x7fb915cd, 0x6ec47fb4, 0xd73fa45c, 0x4c3eab46, + 0xf5c570ae, 0xe4b81ad7, 0x5d43c13f, 0x09cb02a2, 0xb030d94a, + 0xa14db333, 0x18b668db, 0x83b767c1, 0x3a4cbc29, 0x2b31d650, + 0x92ca0db8, 0x8220516a, 0x3bdb8a82, 0x2aa6e0fb, 0x935d3b13, + 0x085c3409, 0xb1a7efe1, 0xa0da8598, 0x19215e70, 0x4da99ded, + 0xf4524605, 0xe52f2c7c, 0x5cd4f794, 0xc7d5f88e, 0x7e2e2366, + 0x6f53491f, 0xd6a892f7, 0x847c8bc6, 0x3d87502e, 0x2cfa3a57, + 0x9501e1bf, 0x0e00eea5, 0xb7fb354d, 0xa6865f34, 0x1f7d84dc, + 0x4bf54741, 0xf20e9ca9, 0xe373f6d0, 0x5a882d38, 0xc1892222, + 0x7872f9ca, 0x690f93b3, 0xd0f4485b, 0xc01e1489, 0x79e5cf61, + 0x6898a518, 0xd1637ef0, 0x4a6271ea, 0xf399aa02, 0xe2e4c07b, + 0x5b1f1b93, 0x0f97d80e, 0xb66c03e6, 0xa711699f, 0x1eeab277, + 0x85ebbd6d, 0x3c106685, 0x2d6d0cfc, 0x9496d714, 0x0cb9b558, + 0xb5426eb0, 0xa43f04c9, 0x1dc4df21, 0x86c5d03b, 0x3f3e0bd3, + 0x2e4361aa, 0x97b8ba42, 0xc33079df, 0x7acba237, 0x6bb6c84e, + 0xd24d13a6, 0x494c1cbc, 0xf0b7c754, 0xe1caad2d, 0x583176c5, + 0x48db2a17, 0xf120f1ff, 0xe05d9b86, 0x59a6406e, 0xc2a74f74, + 0x7b5c949c, 0x6a21fee5, 0xd3da250d, 0x8752e690, 0x3ea93d78, + 0x2fd45701, 0x962f8ce9, 0x0d2e83f3, 0xb4d5581b, 0xa5a83262, + 0x1c53e98a}, + {0x00000000, 0x9d0fe176, 0xe16ec4ad, 0x7c6125db, 0x19ac8f1b, + 0x84a36e6d, 0xf8c24bb6, 0x65cdaac0, 0x33591e36, 0xae56ff40, + 0xd237da9b, 0x4f383bed, 0x2af5912d, 0xb7fa705b, 0xcb9b5580, + 0x5694b4f6, 0x66b23c6c, 0xfbbddd1a, 0x87dcf8c1, 0x1ad319b7, + 0x7f1eb377, 0xe2115201, 0x9e7077da, 0x037f96ac, 0x55eb225a, + 0xc8e4c32c, 0xb485e6f7, 0x298a0781, 0x4c47ad41, 0xd1484c37, + 0xad2969ec, 0x3026889a, 0xcd6478d8, 0x506b99ae, 0x2c0abc75, + 0xb1055d03, 0xd4c8f7c3, 0x49c716b5, 0x35a6336e, 0xa8a9d218, + 0xfe3d66ee, 0x63328798, 0x1f53a243, 0x825c4335, 0xe791e9f5, + 0x7a9e0883, 0x06ff2d58, 0x9bf0cc2e, 0xabd644b4, 0x36d9a5c2, + 0x4ab88019, 0xd7b7616f, 0xb27acbaf, 0x2f752ad9, 0x53140f02, + 0xce1bee74, 0x988f5a82, 0x0580bbf4, 0x79e19e2f, 0xe4ee7f59, + 0x8123d599, 0x1c2c34ef, 0x604d1134, 0xfd42f042, 0x41b9f7f1, + 0xdcb61687, 0xa0d7335c, 0x3dd8d22a, 0x581578ea, 0xc51a999c, + 0xb97bbc47, 0x24745d31, 0x72e0e9c7, 0xefef08b1, 0x938e2d6a, + 0x0e81cc1c, 0x6b4c66dc, 0xf64387aa, 0x8a22a271, 0x172d4307, + 0x270bcb9d, 0xba042aeb, 0xc6650f30, 0x5b6aee46, 0x3ea74486, + 0xa3a8a5f0, 0xdfc9802b, 0x42c6615d, 0x1452d5ab, 0x895d34dd, + 0xf53c1106, 0x6833f070, 0x0dfe5ab0, 0x90f1bbc6, 0xec909e1d, + 0x719f7f6b, 0x8cdd8f29, 0x11d26e5f, 0x6db34b84, 0xf0bcaaf2, + 0x95710032, 0x087ee144, 0x741fc49f, 0xe91025e9, 0xbf84911f, + 0x228b7069, 0x5eea55b2, 0xc3e5b4c4, 0xa6281e04, 0x3b27ff72, + 0x4746daa9, 0xda493bdf, 0xea6fb345, 0x77605233, 0x0b0177e8, + 0x960e969e, 0xf3c33c5e, 0x6eccdd28, 0x12adf8f3, 0x8fa21985, + 0xd936ad73, 0x44394c05, 0x385869de, 0xa55788a8, 0xc09a2268, + 0x5d95c31e, 0x21f4e6c5, 0xbcfb07b3, 0x8373efe2, 0x1e7c0e94, + 0x621d2b4f, 0xff12ca39, 0x9adf60f9, 0x07d0818f, 0x7bb1a454, + 0xe6be4522, 0xb02af1d4, 0x2d2510a2, 0x51443579, 0xcc4bd40f, + 0xa9867ecf, 0x34899fb9, 0x48e8ba62, 0xd5e75b14, 0xe5c1d38e, + 0x78ce32f8, 0x04af1723, 0x99a0f655, 0xfc6d5c95, 0x6162bde3, + 0x1d039838, 0x800c794e, 0xd698cdb8, 0x4b972cce, 0x37f60915, + 0xaaf9e863, 0xcf3442a3, 0x523ba3d5, 0x2e5a860e, 0xb3556778, + 0x4e17973a, 0xd318764c, 0xaf795397, 0x3276b2e1, 0x57bb1821, + 0xcab4f957, 0xb6d5dc8c, 0x2bda3dfa, 0x7d4e890c, 0xe041687a, + 0x9c204da1, 0x012facd7, 0x64e20617, 0xf9ede761, 0x858cc2ba, + 0x188323cc, 0x28a5ab56, 0xb5aa4a20, 0xc9cb6ffb, 0x54c48e8d, + 0x3109244d, 0xac06c53b, 0xd067e0e0, 0x4d680196, 0x1bfcb560, + 0x86f35416, 0xfa9271cd, 0x679d90bb, 0x02503a7b, 0x9f5fdb0d, + 0xe33efed6, 0x7e311fa0, 0xc2ca1813, 0x5fc5f965, 0x23a4dcbe, + 0xbeab3dc8, 0xdb669708, 0x4669767e, 0x3a0853a5, 0xa707b2d3, + 0xf1930625, 0x6c9ce753, 0x10fdc288, 0x8df223fe, 0xe83f893e, + 0x75306848, 0x09514d93, 0x945eace5, 0xa478247f, 0x3977c509, + 0x4516e0d2, 0xd81901a4, 0xbdd4ab64, 0x20db4a12, 0x5cba6fc9, + 0xc1b58ebf, 0x97213a49, 0x0a2edb3f, 0x764ffee4, 0xeb401f92, + 0x8e8db552, 0x13825424, 0x6fe371ff, 0xf2ec9089, 0x0fae60cb, + 0x92a181bd, 0xeec0a466, 0x73cf4510, 0x1602efd0, 0x8b0d0ea6, + 0xf76c2b7d, 0x6a63ca0b, 0x3cf77efd, 0xa1f89f8b, 0xdd99ba50, + 0x40965b26, 0x255bf1e6, 0xb8541090, 0xc435354b, 0x593ad43d, + 0x691c5ca7, 0xf413bdd1, 0x8872980a, 0x157d797c, 0x70b0d3bc, + 0xedbf32ca, 0x91de1711, 0x0cd1f667, 0x5a454291, 0xc74aa3e7, + 0xbb2b863c, 0x2624674a, 0x43e9cd8a, 0xdee62cfc, 0xa2870927, + 0x3f88e851}, + {0x00000000, 0xdd96d985, 0x605cb54b, 0xbdca6cce, 0xc0b96a96, + 0x1d2fb313, 0xa0e5dfdd, 0x7d730658, 0x5a03d36d, 0x87950ae8, + 0x3a5f6626, 0xe7c9bfa3, 0x9abab9fb, 0x472c607e, 0xfae60cb0, + 0x2770d535, 0xb407a6da, 0x69917f5f, 0xd45b1391, 0x09cdca14, + 0x74becc4c, 0xa92815c9, 0x14e27907, 0xc974a082, 0xee0475b7, + 0x3392ac32, 0x8e58c0fc, 0x53ce1979, 0x2ebd1f21, 0xf32bc6a4, + 0x4ee1aa6a, 0x937773ef, 0xb37e4bf5, 0x6ee89270, 0xd322febe, + 0x0eb4273b, 0x73c72163, 0xae51f8e6, 0x139b9428, 0xce0d4dad, + 0xe97d9898, 0x34eb411d, 0x89212dd3, 0x54b7f456, 0x29c4f20e, + 0xf4522b8b, 0x49984745, 0x940e9ec0, 0x0779ed2f, 0xdaef34aa, + 0x67255864, 0xbab381e1, 0xc7c087b9, 0x1a565e3c, 0xa79c32f2, + 0x7a0aeb77, 0x5d7a3e42, 0x80ece7c7, 0x3d268b09, 0xe0b0528c, + 0x9dc354d4, 0x40558d51, 0xfd9fe19f, 0x2009381a, 0xbd8d91ab, + 0x601b482e, 0xddd124e0, 0x0047fd65, 0x7d34fb3d, 0xa0a222b8, + 0x1d684e76, 0xc0fe97f3, 0xe78e42c6, 0x3a189b43, 0x87d2f78d, + 0x5a442e08, 0x27372850, 0xfaa1f1d5, 0x476b9d1b, 0x9afd449e, + 0x098a3771, 0xd41ceef4, 0x69d6823a, 0xb4405bbf, 0xc9335de7, + 0x14a58462, 0xa96fe8ac, 0x74f93129, 0x5389e41c, 0x8e1f3d99, + 0x33d55157, 0xee4388d2, 0x93308e8a, 0x4ea6570f, 0xf36c3bc1, + 0x2efae244, 0x0ef3da5e, 0xd36503db, 0x6eaf6f15, 0xb339b690, + 0xce4ab0c8, 0x13dc694d, 0xae160583, 0x7380dc06, 0x54f00933, + 0x8966d0b6, 0x34acbc78, 0xe93a65fd, 0x944963a5, 0x49dfba20, + 0xf415d6ee, 0x29830f6b, 0xbaf47c84, 0x6762a501, 0xdaa8c9cf, + 0x073e104a, 0x7a4d1612, 0xa7dbcf97, 0x1a11a359, 0xc7877adc, + 0xe0f7afe9, 0x3d61766c, 0x80ab1aa2, 0x5d3dc327, 0x204ec57f, + 0xfdd81cfa, 0x40127034, 0x9d84a9b1, 0xa06a2517, 0x7dfcfc92, + 0xc036905c, 0x1da049d9, 0x60d34f81, 0xbd459604, 0x008ffaca, + 0xdd19234f, 0xfa69f67a, 0x27ff2fff, 0x9a354331, 0x47a39ab4, + 0x3ad09cec, 0xe7464569, 0x5a8c29a7, 0x871af022, 0x146d83cd, + 0xc9fb5a48, 0x74313686, 0xa9a7ef03, 0xd4d4e95b, 0x094230de, + 0xb4885c10, 0x691e8595, 0x4e6e50a0, 0x93f88925, 0x2e32e5eb, + 0xf3a43c6e, 0x8ed73a36, 0x5341e3b3, 0xee8b8f7d, 0x331d56f8, + 0x13146ee2, 0xce82b767, 0x7348dba9, 0xaede022c, 0xd3ad0474, + 0x0e3bddf1, 0xb3f1b13f, 0x6e6768ba, 0x4917bd8f, 0x9481640a, + 0x294b08c4, 0xf4ddd141, 0x89aed719, 0x54380e9c, 0xe9f26252, + 0x3464bbd7, 0xa713c838, 0x7a8511bd, 0xc74f7d73, 0x1ad9a4f6, + 0x67aaa2ae, 0xba3c7b2b, 0x07f617e5, 0xda60ce60, 0xfd101b55, + 0x2086c2d0, 0x9d4cae1e, 0x40da779b, 0x3da971c3, 0xe03fa846, + 0x5df5c488, 0x80631d0d, 0x1de7b4bc, 0xc0716d39, 0x7dbb01f7, + 0xa02dd872, 0xdd5ede2a, 0x00c807af, 0xbd026b61, 0x6094b2e4, + 0x47e467d1, 0x9a72be54, 0x27b8d29a, 0xfa2e0b1f, 0x875d0d47, + 0x5acbd4c2, 0xe701b80c, 0x3a976189, 0xa9e01266, 0x7476cbe3, + 0xc9bca72d, 0x142a7ea8, 0x695978f0, 0xb4cfa175, 0x0905cdbb, + 0xd493143e, 0xf3e3c10b, 0x2e75188e, 0x93bf7440, 0x4e29adc5, + 0x335aab9d, 0xeecc7218, 0x53061ed6, 0x8e90c753, 0xae99ff49, + 0x730f26cc, 0xcec54a02, 0x13539387, 0x6e2095df, 0xb3b64c5a, + 0x0e7c2094, 0xd3eaf911, 0xf49a2c24, 0x290cf5a1, 0x94c6996f, + 0x495040ea, 0x342346b2, 0xe9b59f37, 0x547ff3f9, 0x89e92a7c, + 0x1a9e5993, 0xc7088016, 0x7ac2ecd8, 0xa754355d, 0xda273305, + 0x07b1ea80, 0xba7b864e, 0x67ed5fcb, 0x409d8afe, 0x9d0b537b, + 0x20c13fb5, 0xfd57e630, 0x8024e068, 0x5db239ed, 0xe0785523, + 0x3dee8ca6}}; + +local const z_word_t FAR crc_braid_big_table[][256] = { + {0x00000000, 0x85d996dd, 0x4bb55c60, 0xce6ccabd, 0x966ab9c0, + 0x13b32f1d, 0xdddfe5a0, 0x5806737d, 0x6dd3035a, 0xe80a9587, + 0x26665f3a, 0xa3bfc9e7, 0xfbb9ba9a, 0x7e602c47, 0xb00ce6fa, + 0x35d57027, 0xdaa607b4, 0x5f7f9169, 0x91135bd4, 0x14cacd09, + 0x4cccbe74, 0xc91528a9, 0x0779e214, 0x82a074c9, 0xb77504ee, + 0x32ac9233, 0xfcc0588e, 0x7919ce53, 0x211fbd2e, 0xa4c62bf3, + 0x6aaae14e, 0xef737793, 0xf54b7eb3, 0x7092e86e, 0xbefe22d3, + 0x3b27b40e, 0x6321c773, 0xe6f851ae, 0x28949b13, 0xad4d0dce, + 0x98987de9, 0x1d41eb34, 0xd32d2189, 0x56f4b754, 0x0ef2c429, + 0x8b2b52f4, 0x45479849, 0xc09e0e94, 0x2fed7907, 0xaa34efda, + 0x64582567, 0xe181b3ba, 0xb987c0c7, 0x3c5e561a, 0xf2329ca7, + 0x77eb0a7a, 0x423e7a5d, 0xc7e7ec80, 0x098b263d, 0x8c52b0e0, + 0xd454c39d, 0x518d5540, 0x9fe19ffd, 0x1a380920, 0xab918dbd, + 0x2e481b60, 0xe024d1dd, 0x65fd4700, 0x3dfb347d, 0xb822a2a0, + 0x764e681d, 0xf397fec0, 0xc6428ee7, 0x439b183a, 0x8df7d287, + 0x082e445a, 0x50283727, 0xd5f1a1fa, 0x1b9d6b47, 0x9e44fd9a, + 0x71378a09, 0xf4ee1cd4, 0x3a82d669, 0xbf5b40b4, 0xe75d33c9, + 0x6284a514, 0xace86fa9, 0x2931f974, 0x1ce48953, 0x993d1f8e, + 0x5751d533, 0xd28843ee, 0x8a8e3093, 0x0f57a64e, 0xc13b6cf3, + 0x44e2fa2e, 0x5edaf30e, 0xdb0365d3, 0x156faf6e, 0x90b639b3, + 0xc8b04ace, 0x4d69dc13, 0x830516ae, 0x06dc8073, 0x3309f054, + 0xb6d06689, 0x78bcac34, 0xfd653ae9, 0xa5634994, 0x20badf49, + 0xeed615f4, 0x6b0f8329, 0x847cf4ba, 0x01a56267, 0xcfc9a8da, + 0x4a103e07, 0x12164d7a, 0x97cfdba7, 0x59a3111a, 0xdc7a87c7, + 0xe9aff7e0, 0x6c76613d, 0xa21aab80, 0x27c33d5d, 0x7fc54e20, + 0xfa1cd8fd, 0x34701240, 0xb1a9849d, 0x17256aa0, 0x92fcfc7d, + 0x5c9036c0, 0xd949a01d, 0x814fd360, 0x049645bd, 0xcafa8f00, + 0x4f2319dd, 0x7af669fa, 0xff2fff27, 0x3143359a, 0xb49aa347, + 0xec9cd03a, 0x694546e7, 0xa7298c5a, 0x22f01a87, 0xcd836d14, + 0x485afbc9, 0x86363174, 0x03efa7a9, 0x5be9d4d4, 0xde304209, + 0x105c88b4, 0x95851e69, 0xa0506e4e, 0x2589f893, 0xebe5322e, + 0x6e3ca4f3, 0x363ad78e, 0xb3e34153, 0x7d8f8bee, 0xf8561d33, + 0xe26e1413, 0x67b782ce, 0xa9db4873, 0x2c02deae, 0x7404add3, + 0xf1dd3b0e, 0x3fb1f1b3, 0xba68676e, 0x8fbd1749, 0x0a648194, + 0xc4084b29, 0x41d1ddf4, 0x19d7ae89, 0x9c0e3854, 0x5262f2e9, + 0xd7bb6434, 0x38c813a7, 0xbd11857a, 0x737d4fc7, 0xf6a4d91a, + 0xaea2aa67, 0x2b7b3cba, 0xe517f607, 0x60ce60da, 0x551b10fd, + 0xd0c28620, 0x1eae4c9d, 0x9b77da40, 0xc371a93d, 0x46a83fe0, + 0x88c4f55d, 0x0d1d6380, 0xbcb4e71d, 0x396d71c0, 0xf701bb7d, + 0x72d82da0, 0x2ade5edd, 0xaf07c800, 0x616b02bd, 0xe4b29460, + 0xd167e447, 0x54be729a, 0x9ad2b827, 0x1f0b2efa, 0x470d5d87, + 0xc2d4cb5a, 0x0cb801e7, 0x8961973a, 0x6612e0a9, 0xe3cb7674, + 0x2da7bcc9, 0xa87e2a14, 0xf0785969, 0x75a1cfb4, 0xbbcd0509, + 0x3e1493d4, 0x0bc1e3f3, 0x8e18752e, 0x4074bf93, 0xc5ad294e, + 0x9dab5a33, 0x1872ccee, 0xd61e0653, 0x53c7908e, 0x49ff99ae, + 0xcc260f73, 0x024ac5ce, 0x87935313, 0xdf95206e, 0x5a4cb6b3, + 0x94207c0e, 0x11f9ead3, 0x242c9af4, 0xa1f50c29, 0x6f99c694, + 0xea405049, 0xb2462334, 0x379fb5e9, 0xf9f37f54, 0x7c2ae989, + 0x93599e1a, 0x168008c7, 0xd8ecc27a, 0x5d3554a7, 0x053327da, + 0x80eab107, 0x4e867bba, 0xcb5fed67, 0xfe8a9d40, 0x7b530b9d, + 0xb53fc120, 0x30e657fd, 0x68e02480, 0xed39b25d, 0x235578e0, + 0xa68cee3d}, + {0x00000000, 0x76e10f9d, 0xadc46ee1, 0xdb25617c, 0x1b8fac19, + 0x6d6ea384, 0xb64bc2f8, 0xc0aacd65, 0x361e5933, 0x40ff56ae, + 0x9bda37d2, 0xed3b384f, 0x2d91f52a, 0x5b70fab7, 0x80559bcb, + 0xf6b49456, 0x6c3cb266, 0x1addbdfb, 0xc1f8dc87, 0xb719d31a, + 0x77b31e7f, 0x015211e2, 0xda77709e, 0xac967f03, 0x5a22eb55, + 0x2cc3e4c8, 0xf7e685b4, 0x81078a29, 0x41ad474c, 0x374c48d1, + 0xec6929ad, 0x9a882630, 0xd87864cd, 0xae996b50, 0x75bc0a2c, + 0x035d05b1, 0xc3f7c8d4, 0xb516c749, 0x6e33a635, 0x18d2a9a8, + 0xee663dfe, 0x98873263, 0x43a2531f, 0x35435c82, 0xf5e991e7, + 0x83089e7a, 0x582dff06, 0x2eccf09b, 0xb444d6ab, 0xc2a5d936, + 0x1980b84a, 0x6f61b7d7, 0xafcb7ab2, 0xd92a752f, 0x020f1453, + 0x74ee1bce, 0x825a8f98, 0xf4bb8005, 0x2f9ee179, 0x597feee4, + 0x99d52381, 0xef342c1c, 0x34114d60, 0x42f042fd, 0xf1f7b941, + 0x8716b6dc, 0x5c33d7a0, 0x2ad2d83d, 0xea781558, 0x9c991ac5, + 0x47bc7bb9, 0x315d7424, 0xc7e9e072, 0xb108efef, 0x6a2d8e93, + 0x1ccc810e, 0xdc664c6b, 0xaa8743f6, 0x71a2228a, 0x07432d17, + 0x9dcb0b27, 0xeb2a04ba, 0x300f65c6, 0x46ee6a5b, 0x8644a73e, + 0xf0a5a8a3, 0x2b80c9df, 0x5d61c642, 0xabd55214, 0xdd345d89, + 0x06113cf5, 0x70f03368, 0xb05afe0d, 0xc6bbf190, 0x1d9e90ec, + 0x6b7f9f71, 0x298fdd8c, 0x5f6ed211, 0x844bb36d, 0xf2aabcf0, + 0x32007195, 0x44e17e08, 0x9fc41f74, 0xe92510e9, 0x1f9184bf, + 0x69708b22, 0xb255ea5e, 0xc4b4e5c3, 0x041e28a6, 0x72ff273b, + 0xa9da4647, 0xdf3b49da, 0x45b36fea, 0x33526077, 0xe877010b, + 0x9e960e96, 0x5e3cc3f3, 0x28ddcc6e, 0xf3f8ad12, 0x8519a28f, + 0x73ad36d9, 0x054c3944, 0xde695838, 0xa88857a5, 0x68229ac0, + 0x1ec3955d, 0xc5e6f421, 0xb307fbbc, 0xe2ef7383, 0x940e7c1e, + 0x4f2b1d62, 0x39ca12ff, 0xf960df9a, 0x8f81d007, 0x54a4b17b, + 0x2245bee6, 0xd4f12ab0, 0xa210252d, 0x79354451, 0x0fd44bcc, + 0xcf7e86a9, 0xb99f8934, 0x62bae848, 0x145be7d5, 0x8ed3c1e5, + 0xf832ce78, 0x2317af04, 0x55f6a099, 0x955c6dfc, 0xe3bd6261, + 0x3898031d, 0x4e790c80, 0xb8cd98d6, 0xce2c974b, 0x1509f637, + 0x63e8f9aa, 0xa34234cf, 0xd5a33b52, 0x0e865a2e, 0x786755b3, + 0x3a97174e, 0x4c7618d3, 0x975379af, 0xe1b27632, 0x2118bb57, + 0x57f9b4ca, 0x8cdcd5b6, 0xfa3dda2b, 0x0c894e7d, 0x7a6841e0, + 0xa14d209c, 0xd7ac2f01, 0x1706e264, 0x61e7edf9, 0xbac28c85, + 0xcc238318, 0x56aba528, 0x204aaab5, 0xfb6fcbc9, 0x8d8ec454, + 0x4d240931, 0x3bc506ac, 0xe0e067d0, 0x9601684d, 0x60b5fc1b, + 0x1654f386, 0xcd7192fa, 0xbb909d67, 0x7b3a5002, 0x0ddb5f9f, + 0xd6fe3ee3, 0xa01f317e, 0x1318cac2, 0x65f9c55f, 0xbedca423, + 0xc83dabbe, 0x089766db, 0x7e766946, 0xa553083a, 0xd3b207a7, + 0x250693f1, 0x53e79c6c, 0x88c2fd10, 0xfe23f28d, 0x3e893fe8, + 0x48683075, 0x934d5109, 0xe5ac5e94, 0x7f2478a4, 0x09c57739, + 0xd2e01645, 0xa40119d8, 0x64abd4bd, 0x124adb20, 0xc96fba5c, + 0xbf8eb5c1, 0x493a2197, 0x3fdb2e0a, 0xe4fe4f76, 0x921f40eb, + 0x52b58d8e, 0x24548213, 0xff71e36f, 0x8990ecf2, 0xcb60ae0f, + 0xbd81a192, 0x66a4c0ee, 0x1045cf73, 0xd0ef0216, 0xa60e0d8b, + 0x7d2b6cf7, 0x0bca636a, 0xfd7ef73c, 0x8b9ff8a1, 0x50ba99dd, + 0x265b9640, 0xe6f15b25, 0x901054b8, 0x4b3535c4, 0x3dd43a59, + 0xa75c1c69, 0xd1bd13f4, 0x0a987288, 0x7c797d15, 0xbcd3b070, + 0xca32bfed, 0x1117de91, 0x67f6d10c, 0x9142455a, 0xe7a34ac7, + 0x3c862bbb, 0x4a672426, 0x8acde943, 0xfc2ce6de, 0x270987a2, + 0x51e8883f}, + {0x00000000, 0xe8dbfbb9, 0x91b186a8, 0x796a7d11, 0x63657c8a, + 0x8bbe8733, 0xf2d4fa22, 0x1a0f019b, 0x87cc89cf, 0x6f177276, + 0x167d0f67, 0xfea6f4de, 0xe4a9f545, 0x0c720efc, 0x751873ed, + 0x9dc38854, 0x4f9f6244, 0xa74499fd, 0xde2ee4ec, 0x36f51f55, + 0x2cfa1ece, 0xc421e577, 0xbd4b9866, 0x559063df, 0xc853eb8b, + 0x20881032, 0x59e26d23, 0xb139969a, 0xab369701, 0x43ed6cb8, + 0x3a8711a9, 0xd25cea10, 0x9e3ec588, 0x76e53e31, 0x0f8f4320, + 0xe754b899, 0xfd5bb902, 0x158042bb, 0x6cea3faa, 0x8431c413, + 0x19f24c47, 0xf129b7fe, 0x8843caef, 0x60983156, 0x7a9730cd, + 0x924ccb74, 0xeb26b665, 0x03fd4ddc, 0xd1a1a7cc, 0x397a5c75, + 0x40102164, 0xa8cbdadd, 0xb2c4db46, 0x5a1f20ff, 0x23755dee, + 0xcbaea657, 0x566d2e03, 0xbeb6d5ba, 0xc7dca8ab, 0x2f075312, + 0x35085289, 0xddd3a930, 0xa4b9d421, 0x4c622f98, 0x7d7bfbca, + 0x95a00073, 0xecca7d62, 0x041186db, 0x1e1e8740, 0xf6c57cf9, + 0x8faf01e8, 0x6774fa51, 0xfab77205, 0x126c89bc, 0x6b06f4ad, + 0x83dd0f14, 0x99d20e8f, 0x7109f536, 0x08638827, 0xe0b8739e, + 0x32e4998e, 0xda3f6237, 0xa3551f26, 0x4b8ee49f, 0x5181e504, + 0xb95a1ebd, 0xc03063ac, 0x28eb9815, 0xb5281041, 0x5df3ebf8, + 0x249996e9, 0xcc426d50, 0xd64d6ccb, 0x3e969772, 0x47fcea63, + 0xaf2711da, 0xe3453e42, 0x0b9ec5fb, 0x72f4b8ea, 0x9a2f4353, + 0x802042c8, 0x68fbb971, 0x1191c460, 0xf94a3fd9, 0x6489b78d, + 0x8c524c34, 0xf5383125, 0x1de3ca9c, 0x07eccb07, 0xef3730be, + 0x965d4daf, 0x7e86b616, 0xacda5c06, 0x4401a7bf, 0x3d6bdaae, + 0xd5b02117, 0xcfbf208c, 0x2764db35, 0x5e0ea624, 0xb6d55d9d, + 0x2b16d5c9, 0xc3cd2e70, 0xbaa75361, 0x527ca8d8, 0x4873a943, + 0xa0a852fa, 0xd9c22feb, 0x3119d452, 0xbbf0874e, 0x532b7cf7, + 0x2a4101e6, 0xc29afa5f, 0xd895fbc4, 0x304e007d, 0x49247d6c, + 0xa1ff86d5, 0x3c3c0e81, 0xd4e7f538, 0xad8d8829, 0x45567390, + 0x5f59720b, 0xb78289b2, 0xcee8f4a3, 0x26330f1a, 0xf46fe50a, + 0x1cb41eb3, 0x65de63a2, 0x8d05981b, 0x970a9980, 0x7fd16239, + 0x06bb1f28, 0xee60e491, 0x73a36cc5, 0x9b78977c, 0xe212ea6d, + 0x0ac911d4, 0x10c6104f, 0xf81debf6, 0x817796e7, 0x69ac6d5e, + 0x25ce42c6, 0xcd15b97f, 0xb47fc46e, 0x5ca43fd7, 0x46ab3e4c, + 0xae70c5f5, 0xd71ab8e4, 0x3fc1435d, 0xa202cb09, 0x4ad930b0, + 0x33b34da1, 0xdb68b618, 0xc167b783, 0x29bc4c3a, 0x50d6312b, + 0xb80dca92, 0x6a512082, 0x828adb3b, 0xfbe0a62a, 0x133b5d93, + 0x09345c08, 0xe1efa7b1, 0x9885daa0, 0x705e2119, 0xed9da94d, + 0x054652f4, 0x7c2c2fe5, 0x94f7d45c, 0x8ef8d5c7, 0x66232e7e, + 0x1f49536f, 0xf792a8d6, 0xc68b7c84, 0x2e50873d, 0x573afa2c, + 0xbfe10195, 0xa5ee000e, 0x4d35fbb7, 0x345f86a6, 0xdc847d1f, + 0x4147f54b, 0xa99c0ef2, 0xd0f673e3, 0x382d885a, 0x222289c1, + 0xcaf97278, 0xb3930f69, 0x5b48f4d0, 0x89141ec0, 0x61cfe579, + 0x18a59868, 0xf07e63d1, 0xea71624a, 0x02aa99f3, 0x7bc0e4e2, + 0x931b1f5b, 0x0ed8970f, 0xe6036cb6, 0x9f6911a7, 0x77b2ea1e, + 0x6dbdeb85, 0x8566103c, 0xfc0c6d2d, 0x14d79694, 0x58b5b90c, + 0xb06e42b5, 0xc9043fa4, 0x21dfc41d, 0x3bd0c586, 0xd30b3e3f, + 0xaa61432e, 0x42bab897, 0xdf7930c3, 0x37a2cb7a, 0x4ec8b66b, + 0xa6134dd2, 0xbc1c4c49, 0x54c7b7f0, 0x2dadcae1, 0xc5763158, + 0x172adb48, 0xfff120f1, 0x869b5de0, 0x6e40a659, 0x744fa7c2, + 0x9c945c7b, 0xe5fe216a, 0x0d25dad3, 0x90e65287, 0x783da93e, + 0x0157d42f, 0xe98c2f96, 0xf3832e0d, 0x1b58d5b4, 0x6232a8a5, + 0x8ae9531c}, + {0x00000000, 0x919168ae, 0x6325a087, 0xf2b4c829, 0x874c31d4, + 0x16dd597a, 0xe4699153, 0x75f8f9fd, 0x4f9f1373, 0xde0e7bdd, + 0x2cbab3f4, 0xbd2bdb5a, 0xc8d322a7, 0x59424a09, 0xabf68220, + 0x3a67ea8e, 0x9e3e27e6, 0x0faf4f48, 0xfd1b8761, 0x6c8aefcf, + 0x19721632, 0x88e37e9c, 0x7a57b6b5, 0xebc6de1b, 0xd1a13495, + 0x40305c3b, 0xb2849412, 0x2315fcbc, 0x56ed0541, 0xc77c6def, + 0x35c8a5c6, 0xa459cd68, 0x7d7b3f17, 0xecea57b9, 0x1e5e9f90, + 0x8fcff73e, 0xfa370ec3, 0x6ba6666d, 0x9912ae44, 0x0883c6ea, + 0x32e42c64, 0xa37544ca, 0x51c18ce3, 0xc050e44d, 0xb5a81db0, + 0x2439751e, 0xd68dbd37, 0x471cd599, 0xe34518f1, 0x72d4705f, + 0x8060b876, 0x11f1d0d8, 0x64092925, 0xf598418b, 0x072c89a2, + 0x96bde10c, 0xacda0b82, 0x3d4b632c, 0xcfffab05, 0x5e6ec3ab, + 0x2b963a56, 0xba0752f8, 0x48b39ad1, 0xd922f27f, 0xfaf67e2e, + 0x6b671680, 0x99d3dea9, 0x0842b607, 0x7dba4ffa, 0xec2b2754, + 0x1e9fef7d, 0x8f0e87d3, 0xb5696d5d, 0x24f805f3, 0xd64ccdda, + 0x47dda574, 0x32255c89, 0xa3b43427, 0x5100fc0e, 0xc09194a0, + 0x64c859c8, 0xf5593166, 0x07edf94f, 0x967c91e1, 0xe384681c, + 0x721500b2, 0x80a1c89b, 0x1130a035, 0x2b574abb, 0xbac62215, + 0x4872ea3c, 0xd9e38292, 0xac1b7b6f, 0x3d8a13c1, 0xcf3edbe8, + 0x5eafb346, 0x878d4139, 0x161c2997, 0xe4a8e1be, 0x75398910, + 0x00c170ed, 0x91501843, 0x63e4d06a, 0xf275b8c4, 0xc812524a, + 0x59833ae4, 0xab37f2cd, 0x3aa69a63, 0x4f5e639e, 0xdecf0b30, + 0x2c7bc319, 0xbdeaabb7, 0x19b366df, 0x88220e71, 0x7a96c658, + 0xeb07aef6, 0x9eff570b, 0x0f6e3fa5, 0xfddaf78c, 0x6c4b9f22, + 0x562c75ac, 0xc7bd1d02, 0x3509d52b, 0xa498bd85, 0xd1604478, + 0x40f12cd6, 0xb245e4ff, 0x23d48c51, 0xf4edfd5c, 0x657c95f2, + 0x97c85ddb, 0x06593575, 0x73a1cc88, 0xe230a426, 0x10846c0f, + 0x811504a1, 0xbb72ee2f, 0x2ae38681, 0xd8574ea8, 0x49c62606, + 0x3c3edffb, 0xadafb755, 0x5f1b7f7c, 0xce8a17d2, 0x6ad3daba, + 0xfb42b214, 0x09f67a3d, 0x98671293, 0xed9feb6e, 0x7c0e83c0, + 0x8eba4be9, 0x1f2b2347, 0x254cc9c9, 0xb4dda167, 0x4669694e, + 0xd7f801e0, 0xa200f81d, 0x339190b3, 0xc125589a, 0x50b43034, + 0x8996c24b, 0x1807aae5, 0xeab362cc, 0x7b220a62, 0x0edaf39f, + 0x9f4b9b31, 0x6dff5318, 0xfc6e3bb6, 0xc609d138, 0x5798b996, + 0xa52c71bf, 0x34bd1911, 0x4145e0ec, 0xd0d48842, 0x2260406b, + 0xb3f128c5, 0x17a8e5ad, 0x86398d03, 0x748d452a, 0xe51c2d84, + 0x90e4d479, 0x0175bcd7, 0xf3c174fe, 0x62501c50, 0x5837f6de, + 0xc9a69e70, 0x3b125659, 0xaa833ef7, 0xdf7bc70a, 0x4eeaafa4, + 0xbc5e678d, 0x2dcf0f23, 0x0e1b8372, 0x9f8aebdc, 0x6d3e23f5, + 0xfcaf4b5b, 0x8957b2a6, 0x18c6da08, 0xea721221, 0x7be37a8f, + 0x41849001, 0xd015f8af, 0x22a13086, 0xb3305828, 0xc6c8a1d5, + 0x5759c97b, 0xa5ed0152, 0x347c69fc, 0x9025a494, 0x01b4cc3a, + 0xf3000413, 0x62916cbd, 0x17699540, 0x86f8fdee, 0x744c35c7, + 0xe5dd5d69, 0xdfbab7e7, 0x4e2bdf49, 0xbc9f1760, 0x2d0e7fce, + 0x58f68633, 0xc967ee9d, 0x3bd326b4, 0xaa424e1a, 0x7360bc65, + 0xe2f1d4cb, 0x10451ce2, 0x81d4744c, 0xf42c8db1, 0x65bde51f, + 0x97092d36, 0x06984598, 0x3cffaf16, 0xad6ec7b8, 0x5fda0f91, + 0xce4b673f, 0xbbb39ec2, 0x2a22f66c, 0xd8963e45, 0x490756eb, + 0xed5e9b83, 0x7ccff32d, 0x8e7b3b04, 0x1fea53aa, 0x6a12aa57, + 0xfb83c2f9, 0x09370ad0, 0x98a6627e, 0xa2c188f0, 0x3350e05e, + 0xc1e42877, 0x507540d9, 0x258db924, 0xb41cd18a, 0x46a819a3, + 0xd739710d}}; + +#endif + +#endif + +#if N == 5 + +#if W == 8 + +local const z_crc_t FAR crc_braid_table[][256] = { + {0x00000000, 0xaf449247, 0x85f822cf, 0x2abcb088, 0xd08143df, + 0x7fc5d198, 0x55796110, 0xfa3df357, 0x7a7381ff, 0xd53713b8, + 0xff8ba330, 0x50cf3177, 0xaaf2c220, 0x05b65067, 0x2f0ae0ef, + 0x804e72a8, 0xf4e703fe, 0x5ba391b9, 0x711f2131, 0xde5bb376, + 0x24664021, 0x8b22d266, 0xa19e62ee, 0x0edaf0a9, 0x8e948201, + 0x21d01046, 0x0b6ca0ce, 0xa4283289, 0x5e15c1de, 0xf1515399, + 0xdbede311, 0x74a97156, 0x32bf01bd, 0x9dfb93fa, 0xb7472372, + 0x1803b135, 0xe23e4262, 0x4d7ad025, 0x67c660ad, 0xc882f2ea, + 0x48cc8042, 0xe7881205, 0xcd34a28d, 0x627030ca, 0x984dc39d, + 0x370951da, 0x1db5e152, 0xb2f17315, 0xc6580243, 0x691c9004, + 0x43a0208c, 0xece4b2cb, 0x16d9419c, 0xb99dd3db, 0x93216353, + 0x3c65f114, 0xbc2b83bc, 0x136f11fb, 0x39d3a173, 0x96973334, + 0x6caac063, 0xc3ee5224, 0xe952e2ac, 0x461670eb, 0x657e037a, + 0xca3a913d, 0xe08621b5, 0x4fc2b3f2, 0xb5ff40a5, 0x1abbd2e2, + 0x3007626a, 0x9f43f02d, 0x1f0d8285, 0xb04910c2, 0x9af5a04a, + 0x35b1320d, 0xcf8cc15a, 0x60c8531d, 0x4a74e395, 0xe53071d2, + 0x91990084, 0x3edd92c3, 0x1461224b, 0xbb25b00c, 0x4118435b, + 0xee5cd11c, 0xc4e06194, 0x6ba4f3d3, 0xebea817b, 0x44ae133c, + 0x6e12a3b4, 0xc15631f3, 0x3b6bc2a4, 0x942f50e3, 0xbe93e06b, + 0x11d7722c, 0x57c102c7, 0xf8859080, 0xd2392008, 0x7d7db24f, + 0x87404118, 0x2804d35f, 0x02b863d7, 0xadfcf190, 0x2db28338, + 0x82f6117f, 0xa84aa1f7, 0x070e33b0, 0xfd33c0e7, 0x527752a0, + 0x78cbe228, 0xd78f706f, 0xa3260139, 0x0c62937e, 0x26de23f6, + 0x899ab1b1, 0x73a742e6, 0xdce3d0a1, 0xf65f6029, 0x591bf26e, + 0xd95580c6, 0x76111281, 0x5cada209, 0xf3e9304e, 0x09d4c319, + 0xa690515e, 0x8c2ce1d6, 0x23687391, 0xcafc06f4, 0x65b894b3, + 0x4f04243b, 0xe040b67c, 0x1a7d452b, 0xb539d76c, 0x9f8567e4, + 0x30c1f5a3, 0xb08f870b, 0x1fcb154c, 0x3577a5c4, 0x9a333783, + 0x600ec4d4, 0xcf4a5693, 0xe5f6e61b, 0x4ab2745c, 0x3e1b050a, + 0x915f974d, 0xbbe327c5, 0x14a7b582, 0xee9a46d5, 0x41ded492, + 0x6b62641a, 0xc426f65d, 0x446884f5, 0xeb2c16b2, 0xc190a63a, + 0x6ed4347d, 0x94e9c72a, 0x3bad556d, 0x1111e5e5, 0xbe5577a2, + 0xf8430749, 0x5707950e, 0x7dbb2586, 0xd2ffb7c1, 0x28c24496, + 0x8786d6d1, 0xad3a6659, 0x027ef41e, 0x823086b6, 0x2d7414f1, + 0x07c8a479, 0xa88c363e, 0x52b1c569, 0xfdf5572e, 0xd749e7a6, + 0x780d75e1, 0x0ca404b7, 0xa3e096f0, 0x895c2678, 0x2618b43f, + 0xdc254768, 0x7361d52f, 0x59dd65a7, 0xf699f7e0, 0x76d78548, + 0xd993170f, 0xf32fa787, 0x5c6b35c0, 0xa656c697, 0x091254d0, + 0x23aee458, 0x8cea761f, 0xaf82058e, 0x00c697c9, 0x2a7a2741, + 0x853eb506, 0x7f034651, 0xd047d416, 0xfafb649e, 0x55bff6d9, + 0xd5f18471, 0x7ab51636, 0x5009a6be, 0xff4d34f9, 0x0570c7ae, + 0xaa3455e9, 0x8088e561, 0x2fcc7726, 0x5b650670, 0xf4219437, + 0xde9d24bf, 0x71d9b6f8, 0x8be445af, 0x24a0d7e8, 0x0e1c6760, + 0xa158f527, 0x2116878f, 0x8e5215c8, 0xa4eea540, 0x0baa3707, + 0xf197c450, 0x5ed35617, 0x746fe69f, 0xdb2b74d8, 0x9d3d0433, + 0x32799674, 0x18c526fc, 0xb781b4bb, 0x4dbc47ec, 0xe2f8d5ab, + 0xc8446523, 0x6700f764, 0xe74e85cc, 0x480a178b, 0x62b6a703, + 0xcdf23544, 0x37cfc613, 0x988b5454, 0xb237e4dc, 0x1d73769b, + 0x69da07cd, 0xc69e958a, 0xec222502, 0x4366b745, 0xb95b4412, + 0x161fd655, 0x3ca366dd, 0x93e7f49a, 0x13a98632, 0xbced1475, + 0x9651a4fd, 0x391536ba, 0xc328c5ed, 0x6c6c57aa, 0x46d0e722, + 0xe9947565}, + {0x00000000, 0x4e890ba9, 0x9d121752, 0xd39b1cfb, 0xe15528e5, + 0xafdc234c, 0x7c473fb7, 0x32ce341e, 0x19db578b, 0x57525c22, + 0x84c940d9, 0xca404b70, 0xf88e7f6e, 0xb60774c7, 0x659c683c, + 0x2b156395, 0x33b6af16, 0x7d3fa4bf, 0xaea4b844, 0xe02db3ed, + 0xd2e387f3, 0x9c6a8c5a, 0x4ff190a1, 0x01789b08, 0x2a6df89d, + 0x64e4f334, 0xb77fefcf, 0xf9f6e466, 0xcb38d078, 0x85b1dbd1, + 0x562ac72a, 0x18a3cc83, 0x676d5e2c, 0x29e45585, 0xfa7f497e, + 0xb4f642d7, 0x863876c9, 0xc8b17d60, 0x1b2a619b, 0x55a36a32, + 0x7eb609a7, 0x303f020e, 0xe3a41ef5, 0xad2d155c, 0x9fe32142, + 0xd16a2aeb, 0x02f13610, 0x4c783db9, 0x54dbf13a, 0x1a52fa93, + 0xc9c9e668, 0x8740edc1, 0xb58ed9df, 0xfb07d276, 0x289cce8d, + 0x6615c524, 0x4d00a6b1, 0x0389ad18, 0xd012b1e3, 0x9e9bba4a, + 0xac558e54, 0xe2dc85fd, 0x31479906, 0x7fce92af, 0xcedabc58, + 0x8053b7f1, 0x53c8ab0a, 0x1d41a0a3, 0x2f8f94bd, 0x61069f14, + 0xb29d83ef, 0xfc148846, 0xd701ebd3, 0x9988e07a, 0x4a13fc81, + 0x049af728, 0x3654c336, 0x78ddc89f, 0xab46d464, 0xe5cfdfcd, + 0xfd6c134e, 0xb3e518e7, 0x607e041c, 0x2ef70fb5, 0x1c393bab, + 0x52b03002, 0x812b2cf9, 0xcfa22750, 0xe4b744c5, 0xaa3e4f6c, + 0x79a55397, 0x372c583e, 0x05e26c20, 0x4b6b6789, 0x98f07b72, + 0xd67970db, 0xa9b7e274, 0xe73ee9dd, 0x34a5f526, 0x7a2cfe8f, + 0x48e2ca91, 0x066bc138, 0xd5f0ddc3, 0x9b79d66a, 0xb06cb5ff, + 0xfee5be56, 0x2d7ea2ad, 0x63f7a904, 0x51399d1a, 0x1fb096b3, + 0xcc2b8a48, 0x82a281e1, 0x9a014d62, 0xd48846cb, 0x07135a30, + 0x499a5199, 0x7b546587, 0x35dd6e2e, 0xe64672d5, 0xa8cf797c, + 0x83da1ae9, 0xcd531140, 0x1ec80dbb, 0x50410612, 0x628f320c, + 0x2c0639a5, 0xff9d255e, 0xb1142ef7, 0x46c47ef1, 0x084d7558, + 0xdbd669a3, 0x955f620a, 0xa7915614, 0xe9185dbd, 0x3a834146, + 0x740a4aef, 0x5f1f297a, 0x119622d3, 0xc20d3e28, 0x8c843581, + 0xbe4a019f, 0xf0c30a36, 0x235816cd, 0x6dd11d64, 0x7572d1e7, + 0x3bfbda4e, 0xe860c6b5, 0xa6e9cd1c, 0x9427f902, 0xdaaef2ab, + 0x0935ee50, 0x47bce5f9, 0x6ca9866c, 0x22208dc5, 0xf1bb913e, + 0xbf329a97, 0x8dfcae89, 0xc375a520, 0x10eeb9db, 0x5e67b272, + 0x21a920dd, 0x6f202b74, 0xbcbb378f, 0xf2323c26, 0xc0fc0838, + 0x8e750391, 0x5dee1f6a, 0x136714c3, 0x38727756, 0x76fb7cff, + 0xa5606004, 0xebe96bad, 0xd9275fb3, 0x97ae541a, 0x443548e1, + 0x0abc4348, 0x121f8fcb, 0x5c968462, 0x8f0d9899, 0xc1849330, + 0xf34aa72e, 0xbdc3ac87, 0x6e58b07c, 0x20d1bbd5, 0x0bc4d840, + 0x454dd3e9, 0x96d6cf12, 0xd85fc4bb, 0xea91f0a5, 0xa418fb0c, + 0x7783e7f7, 0x390aec5e, 0x881ec2a9, 0xc697c900, 0x150cd5fb, + 0x5b85de52, 0x694bea4c, 0x27c2e1e5, 0xf459fd1e, 0xbad0f6b7, + 0x91c59522, 0xdf4c9e8b, 0x0cd78270, 0x425e89d9, 0x7090bdc7, + 0x3e19b66e, 0xed82aa95, 0xa30ba13c, 0xbba86dbf, 0xf5216616, + 0x26ba7aed, 0x68337144, 0x5afd455a, 0x14744ef3, 0xc7ef5208, + 0x896659a1, 0xa2733a34, 0xecfa319d, 0x3f612d66, 0x71e826cf, + 0x432612d1, 0x0daf1978, 0xde340583, 0x90bd0e2a, 0xef739c85, + 0xa1fa972c, 0x72618bd7, 0x3ce8807e, 0x0e26b460, 0x40afbfc9, + 0x9334a332, 0xddbda89b, 0xf6a8cb0e, 0xb821c0a7, 0x6bbadc5c, + 0x2533d7f5, 0x17fde3eb, 0x5974e842, 0x8aeff4b9, 0xc466ff10, + 0xdcc53393, 0x924c383a, 0x41d724c1, 0x0f5e2f68, 0x3d901b76, + 0x731910df, 0xa0820c24, 0xee0b078d, 0xc51e6418, 0x8b976fb1, + 0x580c734a, 0x168578e3, 0x244b4cfd, 0x6ac24754, 0xb9595baf, + 0xf7d05006}, + {0x00000000, 0x8d88fde2, 0xc060fd85, 0x4de80067, 0x5bb0fd4b, + 0xd63800a9, 0x9bd000ce, 0x1658fd2c, 0xb761fa96, 0x3ae90774, + 0x77010713, 0xfa89faf1, 0xecd107dd, 0x6159fa3f, 0x2cb1fa58, + 0xa13907ba, 0xb5b2f36d, 0x383a0e8f, 0x75d20ee8, 0xf85af30a, + 0xee020e26, 0x638af3c4, 0x2e62f3a3, 0xa3ea0e41, 0x02d309fb, + 0x8f5bf419, 0xc2b3f47e, 0x4f3b099c, 0x5963f4b0, 0xd4eb0952, + 0x99030935, 0x148bf4d7, 0xb014e09b, 0x3d9c1d79, 0x70741d1e, + 0xfdfce0fc, 0xeba41dd0, 0x662ce032, 0x2bc4e055, 0xa64c1db7, + 0x07751a0d, 0x8afde7ef, 0xc715e788, 0x4a9d1a6a, 0x5cc5e746, + 0xd14d1aa4, 0x9ca51ac3, 0x112de721, 0x05a613f6, 0x882eee14, + 0xc5c6ee73, 0x484e1391, 0x5e16eebd, 0xd39e135f, 0x9e761338, + 0x13feeeda, 0xb2c7e960, 0x3f4f1482, 0x72a714e5, 0xff2fe907, + 0xe977142b, 0x64ffe9c9, 0x2917e9ae, 0xa49f144c, 0xbb58c777, + 0x36d03a95, 0x7b383af2, 0xf6b0c710, 0xe0e83a3c, 0x6d60c7de, + 0x2088c7b9, 0xad003a5b, 0x0c393de1, 0x81b1c003, 0xcc59c064, + 0x41d13d86, 0x5789c0aa, 0xda013d48, 0x97e93d2f, 0x1a61c0cd, + 0x0eea341a, 0x8362c9f8, 0xce8ac99f, 0x4302347d, 0x555ac951, + 0xd8d234b3, 0x953a34d4, 0x18b2c936, 0xb98bce8c, 0x3403336e, + 0x79eb3309, 0xf463ceeb, 0xe23b33c7, 0x6fb3ce25, 0x225bce42, + 0xafd333a0, 0x0b4c27ec, 0x86c4da0e, 0xcb2cda69, 0x46a4278b, + 0x50fcdaa7, 0xdd742745, 0x909c2722, 0x1d14dac0, 0xbc2ddd7a, + 0x31a52098, 0x7c4d20ff, 0xf1c5dd1d, 0xe79d2031, 0x6a15ddd3, + 0x27fdddb4, 0xaa752056, 0xbefed481, 0x33762963, 0x7e9e2904, + 0xf316d4e6, 0xe54e29ca, 0x68c6d428, 0x252ed44f, 0xa8a629ad, + 0x099f2e17, 0x8417d3f5, 0xc9ffd392, 0x44772e70, 0x522fd35c, + 0xdfa72ebe, 0x924f2ed9, 0x1fc7d33b, 0xadc088af, 0x2048754d, + 0x6da0752a, 0xe02888c8, 0xf67075e4, 0x7bf88806, 0x36108861, + 0xbb987583, 0x1aa17239, 0x97298fdb, 0xdac18fbc, 0x5749725e, + 0x41118f72, 0xcc997290, 0x817172f7, 0x0cf98f15, 0x18727bc2, + 0x95fa8620, 0xd8128647, 0x559a7ba5, 0x43c28689, 0xce4a7b6b, + 0x83a27b0c, 0x0e2a86ee, 0xaf138154, 0x229b7cb6, 0x6f737cd1, + 0xe2fb8133, 0xf4a37c1f, 0x792b81fd, 0x34c3819a, 0xb94b7c78, + 0x1dd46834, 0x905c95d6, 0xddb495b1, 0x503c6853, 0x4664957f, + 0xcbec689d, 0x860468fa, 0x0b8c9518, 0xaab592a2, 0x273d6f40, + 0x6ad56f27, 0xe75d92c5, 0xf1056fe9, 0x7c8d920b, 0x3165926c, + 0xbced6f8e, 0xa8669b59, 0x25ee66bb, 0x680666dc, 0xe58e9b3e, + 0xf3d66612, 0x7e5e9bf0, 0x33b69b97, 0xbe3e6675, 0x1f0761cf, + 0x928f9c2d, 0xdf679c4a, 0x52ef61a8, 0x44b79c84, 0xc93f6166, + 0x84d76101, 0x095f9ce3, 0x16984fd8, 0x9b10b23a, 0xd6f8b25d, + 0x5b704fbf, 0x4d28b293, 0xc0a04f71, 0x8d484f16, 0x00c0b2f4, + 0xa1f9b54e, 0x2c7148ac, 0x619948cb, 0xec11b529, 0xfa494805, + 0x77c1b5e7, 0x3a29b580, 0xb7a14862, 0xa32abcb5, 0x2ea24157, + 0x634a4130, 0xeec2bcd2, 0xf89a41fe, 0x7512bc1c, 0x38fabc7b, + 0xb5724199, 0x144b4623, 0x99c3bbc1, 0xd42bbba6, 0x59a34644, + 0x4ffbbb68, 0xc273468a, 0x8f9b46ed, 0x0213bb0f, 0xa68caf43, + 0x2b0452a1, 0x66ec52c6, 0xeb64af24, 0xfd3c5208, 0x70b4afea, + 0x3d5caf8d, 0xb0d4526f, 0x11ed55d5, 0x9c65a837, 0xd18da850, + 0x5c0555b2, 0x4a5da89e, 0xc7d5557c, 0x8a3d551b, 0x07b5a8f9, + 0x133e5c2e, 0x9eb6a1cc, 0xd35ea1ab, 0x5ed65c49, 0x488ea165, + 0xc5065c87, 0x88ee5ce0, 0x0566a102, 0xa45fa6b8, 0x29d75b5a, + 0x643f5b3d, 0xe9b7a6df, 0xffef5bf3, 0x7267a611, 0x3f8fa676, + 0xb2075b94}, + {0x00000000, 0x80f0171f, 0xda91287f, 0x5a613f60, 0x6e5356bf, + 0xeea341a0, 0xb4c27ec0, 0x343269df, 0xdca6ad7e, 0x5c56ba61, + 0x06378501, 0x86c7921e, 0xb2f5fbc1, 0x3205ecde, 0x6864d3be, + 0xe894c4a1, 0x623c5cbd, 0xe2cc4ba2, 0xb8ad74c2, 0x385d63dd, + 0x0c6f0a02, 0x8c9f1d1d, 0xd6fe227d, 0x560e3562, 0xbe9af1c3, + 0x3e6ae6dc, 0x640bd9bc, 0xe4fbcea3, 0xd0c9a77c, 0x5039b063, + 0x0a588f03, 0x8aa8981c, 0xc478b97a, 0x4488ae65, 0x1ee99105, + 0x9e19861a, 0xaa2befc5, 0x2adbf8da, 0x70bac7ba, 0xf04ad0a5, + 0x18de1404, 0x982e031b, 0xc24f3c7b, 0x42bf2b64, 0x768d42bb, + 0xf67d55a4, 0xac1c6ac4, 0x2cec7ddb, 0xa644e5c7, 0x26b4f2d8, + 0x7cd5cdb8, 0xfc25daa7, 0xc817b378, 0x48e7a467, 0x12869b07, + 0x92768c18, 0x7ae248b9, 0xfa125fa6, 0xa07360c6, 0x208377d9, + 0x14b11e06, 0x94410919, 0xce203679, 0x4ed02166, 0x538074b5, + 0xd37063aa, 0x89115cca, 0x09e14bd5, 0x3dd3220a, 0xbd233515, + 0xe7420a75, 0x67b21d6a, 0x8f26d9cb, 0x0fd6ced4, 0x55b7f1b4, + 0xd547e6ab, 0xe1758f74, 0x6185986b, 0x3be4a70b, 0xbb14b014, + 0x31bc2808, 0xb14c3f17, 0xeb2d0077, 0x6bdd1768, 0x5fef7eb7, + 0xdf1f69a8, 0x857e56c8, 0x058e41d7, 0xed1a8576, 0x6dea9269, + 0x378bad09, 0xb77bba16, 0x8349d3c9, 0x03b9c4d6, 0x59d8fbb6, + 0xd928eca9, 0x97f8cdcf, 0x1708dad0, 0x4d69e5b0, 0xcd99f2af, + 0xf9ab9b70, 0x795b8c6f, 0x233ab30f, 0xa3caa410, 0x4b5e60b1, + 0xcbae77ae, 0x91cf48ce, 0x113f5fd1, 0x250d360e, 0xa5fd2111, + 0xff9c1e71, 0x7f6c096e, 0xf5c49172, 0x7534866d, 0x2f55b90d, + 0xafa5ae12, 0x9b97c7cd, 0x1b67d0d2, 0x4106efb2, 0xc1f6f8ad, + 0x29623c0c, 0xa9922b13, 0xf3f31473, 0x7303036c, 0x47316ab3, + 0xc7c17dac, 0x9da042cc, 0x1d5055d3, 0xa700e96a, 0x27f0fe75, + 0x7d91c115, 0xfd61d60a, 0xc953bfd5, 0x49a3a8ca, 0x13c297aa, + 0x933280b5, 0x7ba64414, 0xfb56530b, 0xa1376c6b, 0x21c77b74, + 0x15f512ab, 0x950505b4, 0xcf643ad4, 0x4f942dcb, 0xc53cb5d7, + 0x45cca2c8, 0x1fad9da8, 0x9f5d8ab7, 0xab6fe368, 0x2b9ff477, + 0x71fecb17, 0xf10edc08, 0x199a18a9, 0x996a0fb6, 0xc30b30d6, + 0x43fb27c9, 0x77c94e16, 0xf7395909, 0xad586669, 0x2da87176, + 0x63785010, 0xe388470f, 0xb9e9786f, 0x39196f70, 0x0d2b06af, + 0x8ddb11b0, 0xd7ba2ed0, 0x574a39cf, 0xbfdefd6e, 0x3f2eea71, + 0x654fd511, 0xe5bfc20e, 0xd18dabd1, 0x517dbcce, 0x0b1c83ae, + 0x8bec94b1, 0x01440cad, 0x81b41bb2, 0xdbd524d2, 0x5b2533cd, + 0x6f175a12, 0xefe74d0d, 0xb586726d, 0x35766572, 0xdde2a1d3, + 0x5d12b6cc, 0x077389ac, 0x87839eb3, 0xb3b1f76c, 0x3341e073, + 0x6920df13, 0xe9d0c80c, 0xf4809ddf, 0x74708ac0, 0x2e11b5a0, + 0xaee1a2bf, 0x9ad3cb60, 0x1a23dc7f, 0x4042e31f, 0xc0b2f400, + 0x282630a1, 0xa8d627be, 0xf2b718de, 0x72470fc1, 0x4675661e, + 0xc6857101, 0x9ce44e61, 0x1c14597e, 0x96bcc162, 0x164cd67d, + 0x4c2de91d, 0xccddfe02, 0xf8ef97dd, 0x781f80c2, 0x227ebfa2, + 0xa28ea8bd, 0x4a1a6c1c, 0xcaea7b03, 0x908b4463, 0x107b537c, + 0x24493aa3, 0xa4b92dbc, 0xfed812dc, 0x7e2805c3, 0x30f824a5, + 0xb00833ba, 0xea690cda, 0x6a991bc5, 0x5eab721a, 0xde5b6505, + 0x843a5a65, 0x04ca4d7a, 0xec5e89db, 0x6cae9ec4, 0x36cfa1a4, + 0xb63fb6bb, 0x820ddf64, 0x02fdc87b, 0x589cf71b, 0xd86ce004, + 0x52c47818, 0xd2346f07, 0x88555067, 0x08a54778, 0x3c972ea7, + 0xbc6739b8, 0xe60606d8, 0x66f611c7, 0x8e62d566, 0x0e92c279, + 0x54f3fd19, 0xd403ea06, 0xe03183d9, 0x60c194c6, 0x3aa0aba6, + 0xba50bcb9}, + {0x00000000, 0x9570d495, 0xf190af6b, 0x64e07bfe, 0x38505897, + 0xad208c02, 0xc9c0f7fc, 0x5cb02369, 0x70a0b12e, 0xe5d065bb, + 0x81301e45, 0x1440cad0, 0x48f0e9b9, 0xdd803d2c, 0xb96046d2, + 0x2c109247, 0xe141625c, 0x7431b6c9, 0x10d1cd37, 0x85a119a2, + 0xd9113acb, 0x4c61ee5e, 0x288195a0, 0xbdf14135, 0x91e1d372, + 0x049107e7, 0x60717c19, 0xf501a88c, 0xa9b18be5, 0x3cc15f70, + 0x5821248e, 0xcd51f01b, 0x19f3c2f9, 0x8c83166c, 0xe8636d92, + 0x7d13b907, 0x21a39a6e, 0xb4d34efb, 0xd0333505, 0x4543e190, + 0x695373d7, 0xfc23a742, 0x98c3dcbc, 0x0db30829, 0x51032b40, + 0xc473ffd5, 0xa093842b, 0x35e350be, 0xf8b2a0a5, 0x6dc27430, + 0x09220fce, 0x9c52db5b, 0xc0e2f832, 0x55922ca7, 0x31725759, + 0xa40283cc, 0x8812118b, 0x1d62c51e, 0x7982bee0, 0xecf26a75, + 0xb042491c, 0x25329d89, 0x41d2e677, 0xd4a232e2, 0x33e785f2, + 0xa6975167, 0xc2772a99, 0x5707fe0c, 0x0bb7dd65, 0x9ec709f0, + 0xfa27720e, 0x6f57a69b, 0x434734dc, 0xd637e049, 0xb2d79bb7, + 0x27a74f22, 0x7b176c4b, 0xee67b8de, 0x8a87c320, 0x1ff717b5, + 0xd2a6e7ae, 0x47d6333b, 0x233648c5, 0xb6469c50, 0xeaf6bf39, + 0x7f866bac, 0x1b661052, 0x8e16c4c7, 0xa2065680, 0x37768215, + 0x5396f9eb, 0xc6e62d7e, 0x9a560e17, 0x0f26da82, 0x6bc6a17c, + 0xfeb675e9, 0x2a14470b, 0xbf64939e, 0xdb84e860, 0x4ef43cf5, + 0x12441f9c, 0x8734cb09, 0xe3d4b0f7, 0x76a46462, 0x5ab4f625, + 0xcfc422b0, 0xab24594e, 0x3e548ddb, 0x62e4aeb2, 0xf7947a27, + 0x937401d9, 0x0604d54c, 0xcb552557, 0x5e25f1c2, 0x3ac58a3c, + 0xafb55ea9, 0xf3057dc0, 0x6675a955, 0x0295d2ab, 0x97e5063e, + 0xbbf59479, 0x2e8540ec, 0x4a653b12, 0xdf15ef87, 0x83a5ccee, + 0x16d5187b, 0x72356385, 0xe745b710, 0x67cf0be4, 0xf2bfdf71, + 0x965fa48f, 0x032f701a, 0x5f9f5373, 0xcaef87e6, 0xae0ffc18, + 0x3b7f288d, 0x176fbaca, 0x821f6e5f, 0xe6ff15a1, 0x738fc134, + 0x2f3fe25d, 0xba4f36c8, 0xdeaf4d36, 0x4bdf99a3, 0x868e69b8, + 0x13febd2d, 0x771ec6d3, 0xe26e1246, 0xbede312f, 0x2baee5ba, + 0x4f4e9e44, 0xda3e4ad1, 0xf62ed896, 0x635e0c03, 0x07be77fd, + 0x92cea368, 0xce7e8001, 0x5b0e5494, 0x3fee2f6a, 0xaa9efbff, + 0x7e3cc91d, 0xeb4c1d88, 0x8fac6676, 0x1adcb2e3, 0x466c918a, + 0xd31c451f, 0xb7fc3ee1, 0x228cea74, 0x0e9c7833, 0x9becaca6, + 0xff0cd758, 0x6a7c03cd, 0x36cc20a4, 0xa3bcf431, 0xc75c8fcf, + 0x522c5b5a, 0x9f7dab41, 0x0a0d7fd4, 0x6eed042a, 0xfb9dd0bf, + 0xa72df3d6, 0x325d2743, 0x56bd5cbd, 0xc3cd8828, 0xefdd1a6f, + 0x7aadcefa, 0x1e4db504, 0x8b3d6191, 0xd78d42f8, 0x42fd966d, + 0x261ded93, 0xb36d3906, 0x54288e16, 0xc1585a83, 0xa5b8217d, + 0x30c8f5e8, 0x6c78d681, 0xf9080214, 0x9de879ea, 0x0898ad7f, + 0x24883f38, 0xb1f8ebad, 0xd5189053, 0x406844c6, 0x1cd867af, + 0x89a8b33a, 0xed48c8c4, 0x78381c51, 0xb569ec4a, 0x201938df, + 0x44f94321, 0xd18997b4, 0x8d39b4dd, 0x18496048, 0x7ca91bb6, + 0xe9d9cf23, 0xc5c95d64, 0x50b989f1, 0x3459f20f, 0xa129269a, + 0xfd9905f3, 0x68e9d166, 0x0c09aa98, 0x99797e0d, 0x4ddb4cef, + 0xd8ab987a, 0xbc4be384, 0x293b3711, 0x758b1478, 0xe0fbc0ed, + 0x841bbb13, 0x116b6f86, 0x3d7bfdc1, 0xa80b2954, 0xcceb52aa, + 0x599b863f, 0x052ba556, 0x905b71c3, 0xf4bb0a3d, 0x61cbdea8, + 0xac9a2eb3, 0x39eafa26, 0x5d0a81d8, 0xc87a554d, 0x94ca7624, + 0x01baa2b1, 0x655ad94f, 0xf02a0dda, 0xdc3a9f9d, 0x494a4b08, + 0x2daa30f6, 0xb8dae463, 0xe46ac70a, 0x711a139f, 0x15fa6861, + 0x808abcf4}, + {0x00000000, 0xcf9e17c8, 0x444d29d1, 0x8bd33e19, 0x889a53a2, + 0x4704446a, 0xccd77a73, 0x03496dbb, 0xca45a105, 0x05dbb6cd, + 0x8e0888d4, 0x41969f1c, 0x42dff2a7, 0x8d41e56f, 0x0692db76, + 0xc90cccbe, 0x4ffa444b, 0x80645383, 0x0bb76d9a, 0xc4297a52, + 0xc76017e9, 0x08fe0021, 0x832d3e38, 0x4cb329f0, 0x85bfe54e, + 0x4a21f286, 0xc1f2cc9f, 0x0e6cdb57, 0x0d25b6ec, 0xc2bba124, + 0x49689f3d, 0x86f688f5, 0x9ff48896, 0x506a9f5e, 0xdbb9a147, + 0x1427b68f, 0x176edb34, 0xd8f0ccfc, 0x5323f2e5, 0x9cbde52d, + 0x55b12993, 0x9a2f3e5b, 0x11fc0042, 0xde62178a, 0xdd2b7a31, + 0x12b56df9, 0x996653e0, 0x56f84428, 0xd00eccdd, 0x1f90db15, + 0x9443e50c, 0x5bddf2c4, 0x58949f7f, 0x970a88b7, 0x1cd9b6ae, + 0xd347a166, 0x1a4b6dd8, 0xd5d57a10, 0x5e064409, 0x919853c1, + 0x92d13e7a, 0x5d4f29b2, 0xd69c17ab, 0x19020063, 0xe498176d, + 0x2b0600a5, 0xa0d53ebc, 0x6f4b2974, 0x6c0244cf, 0xa39c5307, + 0x284f6d1e, 0xe7d17ad6, 0x2eddb668, 0xe143a1a0, 0x6a909fb9, + 0xa50e8871, 0xa647e5ca, 0x69d9f202, 0xe20acc1b, 0x2d94dbd3, + 0xab625326, 0x64fc44ee, 0xef2f7af7, 0x20b16d3f, 0x23f80084, + 0xec66174c, 0x67b52955, 0xa82b3e9d, 0x6127f223, 0xaeb9e5eb, + 0x256adbf2, 0xeaf4cc3a, 0xe9bda181, 0x2623b649, 0xadf08850, + 0x626e9f98, 0x7b6c9ffb, 0xb4f28833, 0x3f21b62a, 0xf0bfa1e2, + 0xf3f6cc59, 0x3c68db91, 0xb7bbe588, 0x7825f240, 0xb1293efe, + 0x7eb72936, 0xf564172f, 0x3afa00e7, 0x39b36d5c, 0xf62d7a94, + 0x7dfe448d, 0xb2605345, 0x3496dbb0, 0xfb08cc78, 0x70dbf261, + 0xbf45e5a9, 0xbc0c8812, 0x73929fda, 0xf841a1c3, 0x37dfb60b, + 0xfed37ab5, 0x314d6d7d, 0xba9e5364, 0x750044ac, 0x76492917, + 0xb9d73edf, 0x320400c6, 0xfd9a170e, 0x1241289b, 0xdddf3f53, + 0x560c014a, 0x99921682, 0x9adb7b39, 0x55456cf1, 0xde9652e8, + 0x11084520, 0xd804899e, 0x179a9e56, 0x9c49a04f, 0x53d7b787, + 0x509eda3c, 0x9f00cdf4, 0x14d3f3ed, 0xdb4de425, 0x5dbb6cd0, + 0x92257b18, 0x19f64501, 0xd66852c9, 0xd5213f72, 0x1abf28ba, + 0x916c16a3, 0x5ef2016b, 0x97fecdd5, 0x5860da1d, 0xd3b3e404, + 0x1c2df3cc, 0x1f649e77, 0xd0fa89bf, 0x5b29b7a6, 0x94b7a06e, + 0x8db5a00d, 0x422bb7c5, 0xc9f889dc, 0x06669e14, 0x052ff3af, + 0xcab1e467, 0x4162da7e, 0x8efccdb6, 0x47f00108, 0x886e16c0, + 0x03bd28d9, 0xcc233f11, 0xcf6a52aa, 0x00f44562, 0x8b277b7b, + 0x44b96cb3, 0xc24fe446, 0x0dd1f38e, 0x8602cd97, 0x499cda5f, + 0x4ad5b7e4, 0x854ba02c, 0x0e989e35, 0xc10689fd, 0x080a4543, + 0xc794528b, 0x4c476c92, 0x83d97b5a, 0x809016e1, 0x4f0e0129, + 0xc4dd3f30, 0x0b4328f8, 0xf6d93ff6, 0x3947283e, 0xb2941627, + 0x7d0a01ef, 0x7e436c54, 0xb1dd7b9c, 0x3a0e4585, 0xf590524d, + 0x3c9c9ef3, 0xf302893b, 0x78d1b722, 0xb74fa0ea, 0xb406cd51, + 0x7b98da99, 0xf04be480, 0x3fd5f348, 0xb9237bbd, 0x76bd6c75, + 0xfd6e526c, 0x32f045a4, 0x31b9281f, 0xfe273fd7, 0x75f401ce, + 0xba6a1606, 0x7366dab8, 0xbcf8cd70, 0x372bf369, 0xf8b5e4a1, + 0xfbfc891a, 0x34629ed2, 0xbfb1a0cb, 0x702fb703, 0x692db760, + 0xa6b3a0a8, 0x2d609eb1, 0xe2fe8979, 0xe1b7e4c2, 0x2e29f30a, + 0xa5facd13, 0x6a64dadb, 0xa3681665, 0x6cf601ad, 0xe7253fb4, + 0x28bb287c, 0x2bf245c7, 0xe46c520f, 0x6fbf6c16, 0xa0217bde, + 0x26d7f32b, 0xe949e4e3, 0x629adafa, 0xad04cd32, 0xae4da089, + 0x61d3b741, 0xea008958, 0x259e9e90, 0xec92522e, 0x230c45e6, + 0xa8df7bff, 0x67416c37, 0x6408018c, 0xab961644, 0x2045285d, + 0xefdb3f95}, + {0x00000000, 0x24825136, 0x4904a26c, 0x6d86f35a, 0x920944d8, + 0xb68b15ee, 0xdb0de6b4, 0xff8fb782, 0xff638ff1, 0xdbe1dec7, + 0xb6672d9d, 0x92e57cab, 0x6d6acb29, 0x49e89a1f, 0x246e6945, + 0x00ec3873, 0x25b619a3, 0x01344895, 0x6cb2bbcf, 0x4830eaf9, + 0xb7bf5d7b, 0x933d0c4d, 0xfebbff17, 0xda39ae21, 0xdad59652, + 0xfe57c764, 0x93d1343e, 0xb7536508, 0x48dcd28a, 0x6c5e83bc, + 0x01d870e6, 0x255a21d0, 0x4b6c3346, 0x6fee6270, 0x0268912a, + 0x26eac01c, 0xd965779e, 0xfde726a8, 0x9061d5f2, 0xb4e384c4, + 0xb40fbcb7, 0x908ded81, 0xfd0b1edb, 0xd9894fed, 0x2606f86f, + 0x0284a959, 0x6f025a03, 0x4b800b35, 0x6eda2ae5, 0x4a587bd3, + 0x27de8889, 0x035cd9bf, 0xfcd36e3d, 0xd8513f0b, 0xb5d7cc51, + 0x91559d67, 0x91b9a514, 0xb53bf422, 0xd8bd0778, 0xfc3f564e, + 0x03b0e1cc, 0x2732b0fa, 0x4ab443a0, 0x6e361296, 0x96d8668c, + 0xb25a37ba, 0xdfdcc4e0, 0xfb5e95d6, 0x04d12254, 0x20537362, + 0x4dd58038, 0x6957d10e, 0x69bbe97d, 0x4d39b84b, 0x20bf4b11, + 0x043d1a27, 0xfbb2ada5, 0xdf30fc93, 0xb2b60fc9, 0x96345eff, + 0xb36e7f2f, 0x97ec2e19, 0xfa6add43, 0xdee88c75, 0x21673bf7, + 0x05e56ac1, 0x6863999b, 0x4ce1c8ad, 0x4c0df0de, 0x688fa1e8, + 0x050952b2, 0x218b0384, 0xde04b406, 0xfa86e530, 0x9700166a, + 0xb382475c, 0xddb455ca, 0xf93604fc, 0x94b0f7a6, 0xb032a690, + 0x4fbd1112, 0x6b3f4024, 0x06b9b37e, 0x223be248, 0x22d7da3b, + 0x06558b0d, 0x6bd37857, 0x4f512961, 0xb0de9ee3, 0x945ccfd5, + 0xf9da3c8f, 0xdd586db9, 0xf8024c69, 0xdc801d5f, 0xb106ee05, + 0x9584bf33, 0x6a0b08b1, 0x4e895987, 0x230faadd, 0x078dfbeb, + 0x0761c398, 0x23e392ae, 0x4e6561f4, 0x6ae730c2, 0x95688740, + 0xb1ead676, 0xdc6c252c, 0xf8ee741a, 0xf6c1cb59, 0xd2439a6f, + 0xbfc56935, 0x9b473803, 0x64c88f81, 0x404adeb7, 0x2dcc2ded, + 0x094e7cdb, 0x09a244a8, 0x2d20159e, 0x40a6e6c4, 0x6424b7f2, + 0x9bab0070, 0xbf295146, 0xd2afa21c, 0xf62df32a, 0xd377d2fa, + 0xf7f583cc, 0x9a737096, 0xbef121a0, 0x417e9622, 0x65fcc714, + 0x087a344e, 0x2cf86578, 0x2c145d0b, 0x08960c3d, 0x6510ff67, + 0x4192ae51, 0xbe1d19d3, 0x9a9f48e5, 0xf719bbbf, 0xd39bea89, + 0xbdadf81f, 0x992fa929, 0xf4a95a73, 0xd02b0b45, 0x2fa4bcc7, + 0x0b26edf1, 0x66a01eab, 0x42224f9d, 0x42ce77ee, 0x664c26d8, + 0x0bcad582, 0x2f4884b4, 0xd0c73336, 0xf4456200, 0x99c3915a, + 0xbd41c06c, 0x981be1bc, 0xbc99b08a, 0xd11f43d0, 0xf59d12e6, + 0x0a12a564, 0x2e90f452, 0x43160708, 0x6794563e, 0x67786e4d, + 0x43fa3f7b, 0x2e7ccc21, 0x0afe9d17, 0xf5712a95, 0xd1f37ba3, + 0xbc7588f9, 0x98f7d9cf, 0x6019add5, 0x449bfce3, 0x291d0fb9, + 0x0d9f5e8f, 0xf210e90d, 0xd692b83b, 0xbb144b61, 0x9f961a57, + 0x9f7a2224, 0xbbf87312, 0xd67e8048, 0xf2fcd17e, 0x0d7366fc, + 0x29f137ca, 0x4477c490, 0x60f595a6, 0x45afb476, 0x612de540, + 0x0cab161a, 0x2829472c, 0xd7a6f0ae, 0xf324a198, 0x9ea252c2, + 0xba2003f4, 0xbacc3b87, 0x9e4e6ab1, 0xf3c899eb, 0xd74ac8dd, + 0x28c57f5f, 0x0c472e69, 0x61c1dd33, 0x45438c05, 0x2b759e93, + 0x0ff7cfa5, 0x62713cff, 0x46f36dc9, 0xb97cda4b, 0x9dfe8b7d, + 0xf0787827, 0xd4fa2911, 0xd4161162, 0xf0944054, 0x9d12b30e, + 0xb990e238, 0x461f55ba, 0x629d048c, 0x0f1bf7d6, 0x2b99a6e0, + 0x0ec38730, 0x2a41d606, 0x47c7255c, 0x6345746a, 0x9ccac3e8, + 0xb84892de, 0xd5ce6184, 0xf14c30b2, 0xf1a008c1, 0xd52259f7, + 0xb8a4aaad, 0x9c26fb9b, 0x63a94c19, 0x472b1d2f, 0x2aadee75, + 0x0e2fbf43}, + {0x00000000, 0x36f290f3, 0x6de521e6, 0x5b17b115, 0xdbca43cc, + 0xed38d33f, 0xb62f622a, 0x80ddf2d9, 0x6ce581d9, 0x5a17112a, + 0x0100a03f, 0x37f230cc, 0xb72fc215, 0x81dd52e6, 0xdacae3f3, + 0xec387300, 0xd9cb03b2, 0xef399341, 0xb42e2254, 0x82dcb2a7, + 0x0201407e, 0x34f3d08d, 0x6fe46198, 0x5916f16b, 0xb52e826b, + 0x83dc1298, 0xd8cba38d, 0xee39337e, 0x6ee4c1a7, 0x58165154, + 0x0301e041, 0x35f370b2, 0x68e70125, 0x5e1591d6, 0x050220c3, + 0x33f0b030, 0xb32d42e9, 0x85dfd21a, 0xdec8630f, 0xe83af3fc, + 0x040280fc, 0x32f0100f, 0x69e7a11a, 0x5f1531e9, 0xdfc8c330, + 0xe93a53c3, 0xb22de2d6, 0x84df7225, 0xb12c0297, 0x87de9264, + 0xdcc92371, 0xea3bb382, 0x6ae6415b, 0x5c14d1a8, 0x070360bd, + 0x31f1f04e, 0xddc9834e, 0xeb3b13bd, 0xb02ca2a8, 0x86de325b, + 0x0603c082, 0x30f15071, 0x6be6e164, 0x5d147197, 0xd1ce024a, + 0xe73c92b9, 0xbc2b23ac, 0x8ad9b35f, 0x0a044186, 0x3cf6d175, + 0x67e16060, 0x5113f093, 0xbd2b8393, 0x8bd91360, 0xd0cea275, + 0xe63c3286, 0x66e1c05f, 0x501350ac, 0x0b04e1b9, 0x3df6714a, + 0x080501f8, 0x3ef7910b, 0x65e0201e, 0x5312b0ed, 0xd3cf4234, + 0xe53dd2c7, 0xbe2a63d2, 0x88d8f321, 0x64e08021, 0x521210d2, + 0x0905a1c7, 0x3ff73134, 0xbf2ac3ed, 0x89d8531e, 0xd2cfe20b, + 0xe43d72f8, 0xb929036f, 0x8fdb939c, 0xd4cc2289, 0xe23eb27a, + 0x62e340a3, 0x5411d050, 0x0f066145, 0x39f4f1b6, 0xd5cc82b6, + 0xe33e1245, 0xb829a350, 0x8edb33a3, 0x0e06c17a, 0x38f45189, + 0x63e3e09c, 0x5511706f, 0x60e200dd, 0x5610902e, 0x0d07213b, + 0x3bf5b1c8, 0xbb284311, 0x8ddad3e2, 0xd6cd62f7, 0xe03ff204, + 0x0c078104, 0x3af511f7, 0x61e2a0e2, 0x57103011, 0xd7cdc2c8, + 0xe13f523b, 0xba28e32e, 0x8cda73dd, 0x78ed02d5, 0x4e1f9226, + 0x15082333, 0x23fab3c0, 0xa3274119, 0x95d5d1ea, 0xcec260ff, + 0xf830f00c, 0x1408830c, 0x22fa13ff, 0x79eda2ea, 0x4f1f3219, + 0xcfc2c0c0, 0xf9305033, 0xa227e126, 0x94d571d5, 0xa1260167, + 0x97d49194, 0xccc32081, 0xfa31b072, 0x7aec42ab, 0x4c1ed258, + 0x1709634d, 0x21fbf3be, 0xcdc380be, 0xfb31104d, 0xa026a158, + 0x96d431ab, 0x1609c372, 0x20fb5381, 0x7bece294, 0x4d1e7267, + 0x100a03f0, 0x26f89303, 0x7def2216, 0x4b1db2e5, 0xcbc0403c, + 0xfd32d0cf, 0xa62561da, 0x90d7f129, 0x7cef8229, 0x4a1d12da, + 0x110aa3cf, 0x27f8333c, 0xa725c1e5, 0x91d75116, 0xcac0e003, + 0xfc3270f0, 0xc9c10042, 0xff3390b1, 0xa42421a4, 0x92d6b157, + 0x120b438e, 0x24f9d37d, 0x7fee6268, 0x491cf29b, 0xa524819b, + 0x93d61168, 0xc8c1a07d, 0xfe33308e, 0x7eeec257, 0x481c52a4, + 0x130be3b1, 0x25f97342, 0xa923009f, 0x9fd1906c, 0xc4c62179, + 0xf234b18a, 0x72e94353, 0x441bd3a0, 0x1f0c62b5, 0x29fef246, + 0xc5c68146, 0xf33411b5, 0xa823a0a0, 0x9ed13053, 0x1e0cc28a, + 0x28fe5279, 0x73e9e36c, 0x451b739f, 0x70e8032d, 0x461a93de, + 0x1d0d22cb, 0x2bffb238, 0xab2240e1, 0x9dd0d012, 0xc6c76107, + 0xf035f1f4, 0x1c0d82f4, 0x2aff1207, 0x71e8a312, 0x471a33e1, + 0xc7c7c138, 0xf13551cb, 0xaa22e0de, 0x9cd0702d, 0xc1c401ba, + 0xf7369149, 0xac21205c, 0x9ad3b0af, 0x1a0e4276, 0x2cfcd285, + 0x77eb6390, 0x4119f363, 0xad218063, 0x9bd31090, 0xc0c4a185, + 0xf6363176, 0x76ebc3af, 0x4019535c, 0x1b0ee249, 0x2dfc72ba, + 0x180f0208, 0x2efd92fb, 0x75ea23ee, 0x4318b31d, 0xc3c541c4, + 0xf537d137, 0xae206022, 0x98d2f0d1, 0x74ea83d1, 0x42181322, + 0x190fa237, 0x2ffd32c4, 0xaf20c01d, 0x99d250ee, 0xc2c5e1fb, + 0xf4377108}}; + +local const z_word_t FAR crc_braid_big_table[][256] = { + {0x0000000000000000, 0xf390f23600000000, 0xe621e56d00000000, + 0x15b1175b00000000, 0xcc43cadb00000000, 0x3fd338ed00000000, + 0x2a622fb600000000, 0xd9f2dd8000000000, 0xd981e56c00000000, + 0x2a11175a00000000, 0x3fa0000100000000, 0xcc30f23700000000, + 0x15c22fb700000000, 0xe652dd8100000000, 0xf3e3cada00000000, + 0x007338ec00000000, 0xb203cbd900000000, 0x419339ef00000000, + 0x54222eb400000000, 0xa7b2dc8200000000, 0x7e40010200000000, + 0x8dd0f33400000000, 0x9861e46f00000000, 0x6bf1165900000000, + 0x6b822eb500000000, 0x9812dc8300000000, 0x8da3cbd800000000, + 0x7e3339ee00000000, 0xa7c1e46e00000000, 0x5451165800000000, + 0x41e0010300000000, 0xb270f33500000000, 0x2501e76800000000, + 0xd691155e00000000, 0xc320020500000000, 0x30b0f03300000000, + 0xe9422db300000000, 0x1ad2df8500000000, 0x0f63c8de00000000, + 0xfcf33ae800000000, 0xfc80020400000000, 0x0f10f03200000000, + 0x1aa1e76900000000, 0xe931155f00000000, 0x30c3c8df00000000, + 0xc3533ae900000000, 0xd6e22db200000000, 0x2572df8400000000, + 0x97022cb100000000, 0x6492de8700000000, 0x7123c9dc00000000, + 0x82b33bea00000000, 0x5b41e66a00000000, 0xa8d1145c00000000, + 0xbd60030700000000, 0x4ef0f13100000000, 0x4e83c9dd00000000, + 0xbd133beb00000000, 0xa8a22cb000000000, 0x5b32de8600000000, + 0x82c0030600000000, 0x7150f13000000000, 0x64e1e66b00000000, + 0x9771145d00000000, 0x4a02ced100000000, 0xb9923ce700000000, + 0xac232bbc00000000, 0x5fb3d98a00000000, 0x8641040a00000000, + 0x75d1f63c00000000, 0x6060e16700000000, 0x93f0135100000000, + 0x93832bbd00000000, 0x6013d98b00000000, 0x75a2ced000000000, + 0x86323ce600000000, 0x5fc0e16600000000, 0xac50135000000000, + 0xb9e1040b00000000, 0x4a71f63d00000000, 0xf801050800000000, + 0x0b91f73e00000000, 0x1e20e06500000000, 0xedb0125300000000, + 0x3442cfd300000000, 0xc7d23de500000000, 0xd2632abe00000000, + 0x21f3d88800000000, 0x2180e06400000000, 0xd210125200000000, + 0xc7a1050900000000, 0x3431f73f00000000, 0xedc32abf00000000, + 0x1e53d88900000000, 0x0be2cfd200000000, 0xf8723de400000000, + 0x6f0329b900000000, 0x9c93db8f00000000, 0x8922ccd400000000, + 0x7ab23ee200000000, 0xa340e36200000000, 0x50d0115400000000, + 0x4561060f00000000, 0xb6f1f43900000000, 0xb682ccd500000000, + 0x45123ee300000000, 0x50a329b800000000, 0xa333db8e00000000, + 0x7ac1060e00000000, 0x8951f43800000000, 0x9ce0e36300000000, + 0x6f70115500000000, 0xdd00e26000000000, 0x2e90105600000000, + 0x3b21070d00000000, 0xc8b1f53b00000000, 0x114328bb00000000, + 0xe2d3da8d00000000, 0xf762cdd600000000, 0x04f23fe000000000, + 0x0481070c00000000, 0xf711f53a00000000, 0xe2a0e26100000000, + 0x1130105700000000, 0xc8c2cdd700000000, 0x3b523fe100000000, + 0x2ee328ba00000000, 0xdd73da8c00000000, 0xd502ed7800000000, + 0x26921f4e00000000, 0x3323081500000000, 0xc0b3fa2300000000, + 0x194127a300000000, 0xead1d59500000000, 0xff60c2ce00000000, + 0x0cf030f800000000, 0x0c83081400000000, 0xff13fa2200000000, + 0xeaa2ed7900000000, 0x19321f4f00000000, 0xc0c0c2cf00000000, + 0x335030f900000000, 0x26e127a200000000, 0xd571d59400000000, + 0x670126a100000000, 0x9491d49700000000, 0x8120c3cc00000000, + 0x72b031fa00000000, 0xab42ec7a00000000, 0x58d21e4c00000000, + 0x4d63091700000000, 0xbef3fb2100000000, 0xbe80c3cd00000000, + 0x4d1031fb00000000, 0x58a126a000000000, 0xab31d49600000000, + 0x72c3091600000000, 0x8153fb2000000000, 0x94e2ec7b00000000, + 0x67721e4d00000000, 0xf0030a1000000000, 0x0393f82600000000, + 0x1622ef7d00000000, 0xe5b21d4b00000000, 0x3c40c0cb00000000, + 0xcfd032fd00000000, 0xda6125a600000000, 0x29f1d79000000000, + 0x2982ef7c00000000, 0xda121d4a00000000, 0xcfa30a1100000000, + 0x3c33f82700000000, 0xe5c125a700000000, 0x1651d79100000000, + 0x03e0c0ca00000000, 0xf07032fc00000000, 0x4200c1c900000000, + 0xb19033ff00000000, 0xa42124a400000000, 0x57b1d69200000000, + 0x8e430b1200000000, 0x7dd3f92400000000, 0x6862ee7f00000000, + 0x9bf21c4900000000, 0x9b8124a500000000, 0x6811d69300000000, + 0x7da0c1c800000000, 0x8e3033fe00000000, 0x57c2ee7e00000000, + 0xa4521c4800000000, 0xb1e30b1300000000, 0x4273f92500000000, + 0x9f0023a900000000, 0x6c90d19f00000000, 0x7921c6c400000000, + 0x8ab134f200000000, 0x5343e97200000000, 0xa0d31b4400000000, + 0xb5620c1f00000000, 0x46f2fe2900000000, 0x4681c6c500000000, + 0xb51134f300000000, 0xa0a023a800000000, 0x5330d19e00000000, + 0x8ac20c1e00000000, 0x7952fe2800000000, 0x6ce3e97300000000, + 0x9f731b4500000000, 0x2d03e87000000000, 0xde931a4600000000, + 0xcb220d1d00000000, 0x38b2ff2b00000000, 0xe14022ab00000000, + 0x12d0d09d00000000, 0x0761c7c600000000, 0xf4f135f000000000, + 0xf4820d1c00000000, 0x0712ff2a00000000, 0x12a3e87100000000, + 0xe1331a4700000000, 0x38c1c7c700000000, 0xcb5135f100000000, + 0xdee022aa00000000, 0x2d70d09c00000000, 0xba01c4c100000000, + 0x499136f700000000, 0x5c2021ac00000000, 0xafb0d39a00000000, + 0x76420e1a00000000, 0x85d2fc2c00000000, 0x9063eb7700000000, + 0x63f3194100000000, 0x638021ad00000000, 0x9010d39b00000000, + 0x85a1c4c000000000, 0x763136f600000000, 0xafc3eb7600000000, + 0x5c53194000000000, 0x49e20e1b00000000, 0xba72fc2d00000000, + 0x08020f1800000000, 0xfb92fd2e00000000, 0xee23ea7500000000, + 0x1db3184300000000, 0xc441c5c300000000, 0x37d137f500000000, + 0x226020ae00000000, 0xd1f0d29800000000, 0xd183ea7400000000, + 0x2213184200000000, 0x37a20f1900000000, 0xc432fd2f00000000, + 0x1dc020af00000000, 0xee50d29900000000, 0xfbe1c5c200000000, + 0x087137f400000000}, + {0x0000000000000000, 0x3651822400000000, 0x6ca2044900000000, + 0x5af3866d00000000, 0xd844099200000000, 0xee158bb600000000, + 0xb4e60ddb00000000, 0x82b78fff00000000, 0xf18f63ff00000000, + 0xc7dee1db00000000, 0x9d2d67b600000000, 0xab7ce59200000000, + 0x29cb6a6d00000000, 0x1f9ae84900000000, 0x45696e2400000000, + 0x7338ec0000000000, 0xa319b62500000000, 0x9548340100000000, + 0xcfbbb26c00000000, 0xf9ea304800000000, 0x7b5dbfb700000000, + 0x4d0c3d9300000000, 0x17ffbbfe00000000, 0x21ae39da00000000, + 0x5296d5da00000000, 0x64c757fe00000000, 0x3e34d19300000000, + 0x086553b700000000, 0x8ad2dc4800000000, 0xbc835e6c00000000, + 0xe670d80100000000, 0xd0215a2500000000, 0x46336c4b00000000, + 0x7062ee6f00000000, 0x2a91680200000000, 0x1cc0ea2600000000, + 0x9e7765d900000000, 0xa826e7fd00000000, 0xf2d5619000000000, + 0xc484e3b400000000, 0xb7bc0fb400000000, 0x81ed8d9000000000, + 0xdb1e0bfd00000000, 0xed4f89d900000000, 0x6ff8062600000000, + 0x59a9840200000000, 0x035a026f00000000, 0x350b804b00000000, + 0xe52ada6e00000000, 0xd37b584a00000000, 0x8988de2700000000, + 0xbfd95c0300000000, 0x3d6ed3fc00000000, 0x0b3f51d800000000, + 0x51ccd7b500000000, 0x679d559100000000, 0x14a5b99100000000, + 0x22f43bb500000000, 0x7807bdd800000000, 0x4e563ffc00000000, + 0xcce1b00300000000, 0xfab0322700000000, 0xa043b44a00000000, + 0x9612366e00000000, 0x8c66d89600000000, 0xba375ab200000000, + 0xe0c4dcdf00000000, 0xd6955efb00000000, 0x5422d10400000000, + 0x6273532000000000, 0x3880d54d00000000, 0x0ed1576900000000, + 0x7de9bb6900000000, 0x4bb8394d00000000, 0x114bbf2000000000, + 0x271a3d0400000000, 0xa5adb2fb00000000, 0x93fc30df00000000, + 0xc90fb6b200000000, 0xff5e349600000000, 0x2f7f6eb300000000, + 0x192eec9700000000, 0x43dd6afa00000000, 0x758ce8de00000000, + 0xf73b672100000000, 0xc16ae50500000000, 0x9b99636800000000, + 0xadc8e14c00000000, 0xdef00d4c00000000, 0xe8a18f6800000000, + 0xb252090500000000, 0x84038b2100000000, 0x06b404de00000000, + 0x30e586fa00000000, 0x6a16009700000000, 0x5c4782b300000000, + 0xca55b4dd00000000, 0xfc0436f900000000, 0xa6f7b09400000000, + 0x90a632b000000000, 0x1211bd4f00000000, 0x24403f6b00000000, + 0x7eb3b90600000000, 0x48e23b2200000000, 0x3bdad72200000000, + 0x0d8b550600000000, 0x5778d36b00000000, 0x6129514f00000000, + 0xe39edeb000000000, 0xd5cf5c9400000000, 0x8f3cdaf900000000, + 0xb96d58dd00000000, 0x694c02f800000000, 0x5f1d80dc00000000, + 0x05ee06b100000000, 0x33bf849500000000, 0xb1080b6a00000000, + 0x8759894e00000000, 0xddaa0f2300000000, 0xebfb8d0700000000, + 0x98c3610700000000, 0xae92e32300000000, 0xf461654e00000000, + 0xc230e76a00000000, 0x4087689500000000, 0x76d6eab100000000, + 0x2c256cdc00000000, 0x1a74eef800000000, 0x59cbc1f600000000, + 0x6f9a43d200000000, 0x3569c5bf00000000, 0x0338479b00000000, + 0x818fc86400000000, 0xb7de4a4000000000, 0xed2dcc2d00000000, + 0xdb7c4e0900000000, 0xa844a20900000000, 0x9e15202d00000000, + 0xc4e6a64000000000, 0xf2b7246400000000, 0x7000ab9b00000000, + 0x465129bf00000000, 0x1ca2afd200000000, 0x2af32df600000000, + 0xfad277d300000000, 0xcc83f5f700000000, 0x9670739a00000000, + 0xa021f1be00000000, 0x22967e4100000000, 0x14c7fc6500000000, + 0x4e347a0800000000, 0x7865f82c00000000, 0x0b5d142c00000000, + 0x3d0c960800000000, 0x67ff106500000000, 0x51ae924100000000, + 0xd3191dbe00000000, 0xe5489f9a00000000, 0xbfbb19f700000000, + 0x89ea9bd300000000, 0x1ff8adbd00000000, 0x29a92f9900000000, + 0x735aa9f400000000, 0x450b2bd000000000, 0xc7bca42f00000000, + 0xf1ed260b00000000, 0xab1ea06600000000, 0x9d4f224200000000, + 0xee77ce4200000000, 0xd8264c6600000000, 0x82d5ca0b00000000, + 0xb484482f00000000, 0x3633c7d000000000, 0x006245f400000000, + 0x5a91c39900000000, 0x6cc041bd00000000, 0xbce11b9800000000, + 0x8ab099bc00000000, 0xd0431fd100000000, 0xe6129df500000000, + 0x64a5120a00000000, 0x52f4902e00000000, 0x0807164300000000, + 0x3e56946700000000, 0x4d6e786700000000, 0x7b3ffa4300000000, + 0x21cc7c2e00000000, 0x179dfe0a00000000, 0x952a71f500000000, + 0xa37bf3d100000000, 0xf98875bc00000000, 0xcfd9f79800000000, + 0xd5ad196000000000, 0xe3fc9b4400000000, 0xb90f1d2900000000, + 0x8f5e9f0d00000000, 0x0de910f200000000, 0x3bb892d600000000, + 0x614b14bb00000000, 0x571a969f00000000, 0x24227a9f00000000, + 0x1273f8bb00000000, 0x48807ed600000000, 0x7ed1fcf200000000, + 0xfc66730d00000000, 0xca37f12900000000, 0x90c4774400000000, + 0xa695f56000000000, 0x76b4af4500000000, 0x40e52d6100000000, + 0x1a16ab0c00000000, 0x2c47292800000000, 0xaef0a6d700000000, + 0x98a124f300000000, 0xc252a29e00000000, 0xf40320ba00000000, + 0x873bccba00000000, 0xb16a4e9e00000000, 0xeb99c8f300000000, + 0xddc84ad700000000, 0x5f7fc52800000000, 0x692e470c00000000, + 0x33ddc16100000000, 0x058c434500000000, 0x939e752b00000000, + 0xa5cff70f00000000, 0xff3c716200000000, 0xc96df34600000000, + 0x4bda7cb900000000, 0x7d8bfe9d00000000, 0x277878f000000000, + 0x1129fad400000000, 0x621116d400000000, 0x544094f000000000, + 0x0eb3129d00000000, 0x38e290b900000000, 0xba551f4600000000, + 0x8c049d6200000000, 0xd6f71b0f00000000, 0xe0a6992b00000000, + 0x3087c30e00000000, 0x06d6412a00000000, 0x5c25c74700000000, + 0x6a74456300000000, 0xe8c3ca9c00000000, 0xde9248b800000000, + 0x8461ced500000000, 0xb2304cf100000000, 0xc108a0f100000000, + 0xf75922d500000000, 0xadaaa4b800000000, 0x9bfb269c00000000, + 0x194ca96300000000, 0x2f1d2b4700000000, 0x75eead2a00000000, + 0x43bf2f0e00000000}, + {0x0000000000000000, 0xc8179ecf00000000, 0xd1294d4400000000, + 0x193ed38b00000000, 0xa2539a8800000000, 0x6a44044700000000, + 0x737ad7cc00000000, 0xbb6d490300000000, 0x05a145ca00000000, + 0xcdb6db0500000000, 0xd488088e00000000, 0x1c9f964100000000, + 0xa7f2df4200000000, 0x6fe5418d00000000, 0x76db920600000000, + 0xbecc0cc900000000, 0x4b44fa4f00000000, 0x8353648000000000, + 0x9a6db70b00000000, 0x527a29c400000000, 0xe91760c700000000, + 0x2100fe0800000000, 0x383e2d8300000000, 0xf029b34c00000000, + 0x4ee5bf8500000000, 0x86f2214a00000000, 0x9fccf2c100000000, + 0x57db6c0e00000000, 0xecb6250d00000000, 0x24a1bbc200000000, + 0x3d9f684900000000, 0xf588f68600000000, 0x9688f49f00000000, + 0x5e9f6a5000000000, 0x47a1b9db00000000, 0x8fb6271400000000, + 0x34db6e1700000000, 0xfcccf0d800000000, 0xe5f2235300000000, + 0x2de5bd9c00000000, 0x9329b15500000000, 0x5b3e2f9a00000000, + 0x4200fc1100000000, 0x8a1762de00000000, 0x317a2bdd00000000, + 0xf96db51200000000, 0xe053669900000000, 0x2844f85600000000, + 0xddcc0ed000000000, 0x15db901f00000000, 0x0ce5439400000000, + 0xc4f2dd5b00000000, 0x7f9f945800000000, 0xb7880a9700000000, + 0xaeb6d91c00000000, 0x66a147d300000000, 0xd86d4b1a00000000, + 0x107ad5d500000000, 0x0944065e00000000, 0xc153989100000000, + 0x7a3ed19200000000, 0xb2294f5d00000000, 0xab179cd600000000, + 0x6300021900000000, 0x6d1798e400000000, 0xa500062b00000000, + 0xbc3ed5a000000000, 0x74294b6f00000000, 0xcf44026c00000000, + 0x07539ca300000000, 0x1e6d4f2800000000, 0xd67ad1e700000000, + 0x68b6dd2e00000000, 0xa0a143e100000000, 0xb99f906a00000000, + 0x71880ea500000000, 0xcae547a600000000, 0x02f2d96900000000, + 0x1bcc0ae200000000, 0xd3db942d00000000, 0x265362ab00000000, + 0xee44fc6400000000, 0xf77a2fef00000000, 0x3f6db12000000000, + 0x8400f82300000000, 0x4c1766ec00000000, 0x5529b56700000000, + 0x9d3e2ba800000000, 0x23f2276100000000, 0xebe5b9ae00000000, + 0xf2db6a2500000000, 0x3accf4ea00000000, 0x81a1bde900000000, + 0x49b6232600000000, 0x5088f0ad00000000, 0x989f6e6200000000, + 0xfb9f6c7b00000000, 0x3388f2b400000000, 0x2ab6213f00000000, + 0xe2a1bff000000000, 0x59ccf6f300000000, 0x91db683c00000000, + 0x88e5bbb700000000, 0x40f2257800000000, 0xfe3e29b100000000, + 0x3629b77e00000000, 0x2f1764f500000000, 0xe700fa3a00000000, + 0x5c6db33900000000, 0x947a2df600000000, 0x8d44fe7d00000000, + 0x455360b200000000, 0xb0db963400000000, 0x78cc08fb00000000, + 0x61f2db7000000000, 0xa9e545bf00000000, 0x12880cbc00000000, + 0xda9f927300000000, 0xc3a141f800000000, 0x0bb6df3700000000, + 0xb57ad3fe00000000, 0x7d6d4d3100000000, 0x64539eba00000000, + 0xac44007500000000, 0x1729497600000000, 0xdf3ed7b900000000, + 0xc600043200000000, 0x0e179afd00000000, 0x9b28411200000000, + 0x533fdfdd00000000, 0x4a010c5600000000, 0x8216929900000000, + 0x397bdb9a00000000, 0xf16c455500000000, 0xe85296de00000000, + 0x2045081100000000, 0x9e8904d800000000, 0x569e9a1700000000, + 0x4fa0499c00000000, 0x87b7d75300000000, 0x3cda9e5000000000, + 0xf4cd009f00000000, 0xedf3d31400000000, 0x25e44ddb00000000, + 0xd06cbb5d00000000, 0x187b259200000000, 0x0145f61900000000, + 0xc95268d600000000, 0x723f21d500000000, 0xba28bf1a00000000, + 0xa3166c9100000000, 0x6b01f25e00000000, 0xd5cdfe9700000000, + 0x1dda605800000000, 0x04e4b3d300000000, 0xccf32d1c00000000, + 0x779e641f00000000, 0xbf89fad000000000, 0xa6b7295b00000000, + 0x6ea0b79400000000, 0x0da0b58d00000000, 0xc5b72b4200000000, + 0xdc89f8c900000000, 0x149e660600000000, 0xaff32f0500000000, + 0x67e4b1ca00000000, 0x7eda624100000000, 0xb6cdfc8e00000000, + 0x0801f04700000000, 0xc0166e8800000000, 0xd928bd0300000000, + 0x113f23cc00000000, 0xaa526acf00000000, 0x6245f40000000000, + 0x7b7b278b00000000, 0xb36cb94400000000, 0x46e44fc200000000, + 0x8ef3d10d00000000, 0x97cd028600000000, 0x5fda9c4900000000, + 0xe4b7d54a00000000, 0x2ca04b8500000000, 0x359e980e00000000, + 0xfd8906c100000000, 0x43450a0800000000, 0x8b5294c700000000, + 0x926c474c00000000, 0x5a7bd98300000000, 0xe116908000000000, + 0x29010e4f00000000, 0x303fddc400000000, 0xf828430b00000000, + 0xf63fd9f600000000, 0x3e28473900000000, 0x271694b200000000, + 0xef010a7d00000000, 0x546c437e00000000, 0x9c7bddb100000000, + 0x85450e3a00000000, 0x4d5290f500000000, 0xf39e9c3c00000000, + 0x3b8902f300000000, 0x22b7d17800000000, 0xeaa04fb700000000, + 0x51cd06b400000000, 0x99da987b00000000, 0x80e44bf000000000, + 0x48f3d53f00000000, 0xbd7b23b900000000, 0x756cbd7600000000, + 0x6c526efd00000000, 0xa445f03200000000, 0x1f28b93100000000, + 0xd73f27fe00000000, 0xce01f47500000000, 0x06166aba00000000, + 0xb8da667300000000, 0x70cdf8bc00000000, 0x69f32b3700000000, + 0xa1e4b5f800000000, 0x1a89fcfb00000000, 0xd29e623400000000, + 0xcba0b1bf00000000, 0x03b72f7000000000, 0x60b72d6900000000, + 0xa8a0b3a600000000, 0xb19e602d00000000, 0x7989fee200000000, + 0xc2e4b7e100000000, 0x0af3292e00000000, 0x13cdfaa500000000, + 0xdbda646a00000000, 0x651668a300000000, 0xad01f66c00000000, + 0xb43f25e700000000, 0x7c28bb2800000000, 0xc745f22b00000000, + 0x0f526ce400000000, 0x166cbf6f00000000, 0xde7b21a000000000, + 0x2bf3d72600000000, 0xe3e449e900000000, 0xfada9a6200000000, + 0x32cd04ad00000000, 0x89a04dae00000000, 0x41b7d36100000000, + 0x588900ea00000000, 0x909e9e2500000000, 0x2e5292ec00000000, + 0xe6450c2300000000, 0xff7bdfa800000000, 0x376c416700000000, + 0x8c01086400000000, 0x441696ab00000000, 0x5d28452000000000, + 0x953fdbef00000000}, + {0x0000000000000000, 0x95d4709500000000, 0x6baf90f100000000, + 0xfe7be06400000000, 0x9758503800000000, 0x028c20ad00000000, + 0xfcf7c0c900000000, 0x6923b05c00000000, 0x2eb1a07000000000, + 0xbb65d0e500000000, 0x451e308100000000, 0xd0ca401400000000, + 0xb9e9f04800000000, 0x2c3d80dd00000000, 0xd24660b900000000, + 0x4792102c00000000, 0x5c6241e100000000, 0xc9b6317400000000, + 0x37cdd11000000000, 0xa219a18500000000, 0xcb3a11d900000000, + 0x5eee614c00000000, 0xa095812800000000, 0x3541f1bd00000000, + 0x72d3e19100000000, 0xe707910400000000, 0x197c716000000000, + 0x8ca801f500000000, 0xe58bb1a900000000, 0x705fc13c00000000, + 0x8e24215800000000, 0x1bf051cd00000000, 0xf9c2f31900000000, + 0x6c16838c00000000, 0x926d63e800000000, 0x07b9137d00000000, + 0x6e9aa32100000000, 0xfb4ed3b400000000, 0x053533d000000000, + 0x90e1434500000000, 0xd773536900000000, 0x42a723fc00000000, + 0xbcdcc39800000000, 0x2908b30d00000000, 0x402b035100000000, + 0xd5ff73c400000000, 0x2b8493a000000000, 0xbe50e33500000000, + 0xa5a0b2f800000000, 0x3074c26d00000000, 0xce0f220900000000, + 0x5bdb529c00000000, 0x32f8e2c000000000, 0xa72c925500000000, + 0x5957723100000000, 0xcc8302a400000000, 0x8b11128800000000, + 0x1ec5621d00000000, 0xe0be827900000000, 0x756af2ec00000000, + 0x1c4942b000000000, 0x899d322500000000, 0x77e6d24100000000, + 0xe232a2d400000000, 0xf285e73300000000, 0x675197a600000000, + 0x992a77c200000000, 0x0cfe075700000000, 0x65ddb70b00000000, + 0xf009c79e00000000, 0x0e7227fa00000000, 0x9ba6576f00000000, + 0xdc34474300000000, 0x49e037d600000000, 0xb79bd7b200000000, + 0x224fa72700000000, 0x4b6c177b00000000, 0xdeb867ee00000000, + 0x20c3878a00000000, 0xb517f71f00000000, 0xaee7a6d200000000, + 0x3b33d64700000000, 0xc548362300000000, 0x509c46b600000000, + 0x39bff6ea00000000, 0xac6b867f00000000, 0x5210661b00000000, + 0xc7c4168e00000000, 0x805606a200000000, 0x1582763700000000, + 0xebf9965300000000, 0x7e2de6c600000000, 0x170e569a00000000, + 0x82da260f00000000, 0x7ca1c66b00000000, 0xe975b6fe00000000, + 0x0b47142a00000000, 0x9e9364bf00000000, 0x60e884db00000000, + 0xf53cf44e00000000, 0x9c1f441200000000, 0x09cb348700000000, + 0xf7b0d4e300000000, 0x6264a47600000000, 0x25f6b45a00000000, + 0xb022c4cf00000000, 0x4e5924ab00000000, 0xdb8d543e00000000, + 0xb2aee46200000000, 0x277a94f700000000, 0xd901749300000000, + 0x4cd5040600000000, 0x572555cb00000000, 0xc2f1255e00000000, + 0x3c8ac53a00000000, 0xa95eb5af00000000, 0xc07d05f300000000, + 0x55a9756600000000, 0xabd2950200000000, 0x3e06e59700000000, + 0x7994f5bb00000000, 0xec40852e00000000, 0x123b654a00000000, + 0x87ef15df00000000, 0xeecca58300000000, 0x7b18d51600000000, + 0x8563357200000000, 0x10b745e700000000, 0xe40bcf6700000000, + 0x71dfbff200000000, 0x8fa45f9600000000, 0x1a702f0300000000, + 0x73539f5f00000000, 0xe687efca00000000, 0x18fc0fae00000000, + 0x8d287f3b00000000, 0xcaba6f1700000000, 0x5f6e1f8200000000, + 0xa115ffe600000000, 0x34c18f7300000000, 0x5de23f2f00000000, + 0xc8364fba00000000, 0x364dafde00000000, 0xa399df4b00000000, + 0xb8698e8600000000, 0x2dbdfe1300000000, 0xd3c61e7700000000, + 0x46126ee200000000, 0x2f31debe00000000, 0xbae5ae2b00000000, + 0x449e4e4f00000000, 0xd14a3eda00000000, 0x96d82ef600000000, + 0x030c5e6300000000, 0xfd77be0700000000, 0x68a3ce9200000000, + 0x01807ece00000000, 0x94540e5b00000000, 0x6a2fee3f00000000, + 0xfffb9eaa00000000, 0x1dc93c7e00000000, 0x881d4ceb00000000, + 0x7666ac8f00000000, 0xe3b2dc1a00000000, 0x8a916c4600000000, + 0x1f451cd300000000, 0xe13efcb700000000, 0x74ea8c2200000000, + 0x33789c0e00000000, 0xa6acec9b00000000, 0x58d70cff00000000, + 0xcd037c6a00000000, 0xa420cc3600000000, 0x31f4bca300000000, + 0xcf8f5cc700000000, 0x5a5b2c5200000000, 0x41ab7d9f00000000, + 0xd47f0d0a00000000, 0x2a04ed6e00000000, 0xbfd09dfb00000000, + 0xd6f32da700000000, 0x43275d3200000000, 0xbd5cbd5600000000, + 0x2888cdc300000000, 0x6f1addef00000000, 0xfacead7a00000000, + 0x04b54d1e00000000, 0x91613d8b00000000, 0xf8428dd700000000, + 0x6d96fd4200000000, 0x93ed1d2600000000, 0x06396db300000000, + 0x168e285400000000, 0x835a58c100000000, 0x7d21b8a500000000, + 0xe8f5c83000000000, 0x81d6786c00000000, 0x140208f900000000, + 0xea79e89d00000000, 0x7fad980800000000, 0x383f882400000000, + 0xadebf8b100000000, 0x539018d500000000, 0xc644684000000000, + 0xaf67d81c00000000, 0x3ab3a88900000000, 0xc4c848ed00000000, + 0x511c387800000000, 0x4aec69b500000000, 0xdf38192000000000, + 0x2143f94400000000, 0xb49789d100000000, 0xddb4398d00000000, + 0x4860491800000000, 0xb61ba97c00000000, 0x23cfd9e900000000, + 0x645dc9c500000000, 0xf189b95000000000, 0x0ff2593400000000, + 0x9a2629a100000000, 0xf30599fd00000000, 0x66d1e96800000000, + 0x98aa090c00000000, 0x0d7e799900000000, 0xef4cdb4d00000000, + 0x7a98abd800000000, 0x84e34bbc00000000, 0x11373b2900000000, + 0x78148b7500000000, 0xedc0fbe000000000, 0x13bb1b8400000000, + 0x866f6b1100000000, 0xc1fd7b3d00000000, 0x54290ba800000000, + 0xaa52ebcc00000000, 0x3f869b5900000000, 0x56a52b0500000000, + 0xc3715b9000000000, 0x3d0abbf400000000, 0xa8decb6100000000, + 0xb32e9aac00000000, 0x26faea3900000000, 0xd8810a5d00000000, + 0x4d557ac800000000, 0x2476ca9400000000, 0xb1a2ba0100000000, + 0x4fd95a6500000000, 0xda0d2af000000000, 0x9d9f3adc00000000, + 0x084b4a4900000000, 0xf630aa2d00000000, 0x63e4dab800000000, + 0x0ac76ae400000000, 0x9f131a7100000000, 0x6168fa1500000000, + 0xf4bc8a8000000000}, + {0x0000000000000000, 0x1f17f08000000000, 0x7f2891da00000000, + 0x603f615a00000000, 0xbf56536e00000000, 0xa041a3ee00000000, + 0xc07ec2b400000000, 0xdf69323400000000, 0x7eada6dc00000000, + 0x61ba565c00000000, 0x0185370600000000, 0x1e92c78600000000, + 0xc1fbf5b200000000, 0xdeec053200000000, 0xbed3646800000000, + 0xa1c494e800000000, 0xbd5c3c6200000000, 0xa24bcce200000000, + 0xc274adb800000000, 0xdd635d3800000000, 0x020a6f0c00000000, + 0x1d1d9f8c00000000, 0x7d22fed600000000, 0x62350e5600000000, + 0xc3f19abe00000000, 0xdce66a3e00000000, 0xbcd90b6400000000, + 0xa3cefbe400000000, 0x7ca7c9d000000000, 0x63b0395000000000, + 0x038f580a00000000, 0x1c98a88a00000000, 0x7ab978c400000000, + 0x65ae884400000000, 0x0591e91e00000000, 0x1a86199e00000000, + 0xc5ef2baa00000000, 0xdaf8db2a00000000, 0xbac7ba7000000000, + 0xa5d04af000000000, 0x0414de1800000000, 0x1b032e9800000000, + 0x7b3c4fc200000000, 0x642bbf4200000000, 0xbb428d7600000000, + 0xa4557df600000000, 0xc46a1cac00000000, 0xdb7dec2c00000000, + 0xc7e544a600000000, 0xd8f2b42600000000, 0xb8cdd57c00000000, + 0xa7da25fc00000000, 0x78b317c800000000, 0x67a4e74800000000, + 0x079b861200000000, 0x188c769200000000, 0xb948e27a00000000, + 0xa65f12fa00000000, 0xc66073a000000000, 0xd977832000000000, + 0x061eb11400000000, 0x1909419400000000, 0x793620ce00000000, + 0x6621d04e00000000, 0xb574805300000000, 0xaa6370d300000000, + 0xca5c118900000000, 0xd54be10900000000, 0x0a22d33d00000000, + 0x153523bd00000000, 0x750a42e700000000, 0x6a1db26700000000, + 0xcbd9268f00000000, 0xd4ced60f00000000, 0xb4f1b75500000000, + 0xabe647d500000000, 0x748f75e100000000, 0x6b98856100000000, + 0x0ba7e43b00000000, 0x14b014bb00000000, 0x0828bc3100000000, + 0x173f4cb100000000, 0x77002deb00000000, 0x6817dd6b00000000, + 0xb77eef5f00000000, 0xa8691fdf00000000, 0xc8567e8500000000, + 0xd7418e0500000000, 0x76851aed00000000, 0x6992ea6d00000000, + 0x09ad8b3700000000, 0x16ba7bb700000000, 0xc9d3498300000000, + 0xd6c4b90300000000, 0xb6fbd85900000000, 0xa9ec28d900000000, + 0xcfcdf89700000000, 0xd0da081700000000, 0xb0e5694d00000000, + 0xaff299cd00000000, 0x709babf900000000, 0x6f8c5b7900000000, + 0x0fb33a2300000000, 0x10a4caa300000000, 0xb1605e4b00000000, + 0xae77aecb00000000, 0xce48cf9100000000, 0xd15f3f1100000000, + 0x0e360d2500000000, 0x1121fda500000000, 0x711e9cff00000000, + 0x6e096c7f00000000, 0x7291c4f500000000, 0x6d86347500000000, + 0x0db9552f00000000, 0x12aea5af00000000, 0xcdc7979b00000000, + 0xd2d0671b00000000, 0xb2ef064100000000, 0xadf8f6c100000000, + 0x0c3c622900000000, 0x132b92a900000000, 0x7314f3f300000000, + 0x6c03037300000000, 0xb36a314700000000, 0xac7dc1c700000000, + 0xcc42a09d00000000, 0xd355501d00000000, 0x6ae900a700000000, + 0x75fef02700000000, 0x15c1917d00000000, 0x0ad661fd00000000, + 0xd5bf53c900000000, 0xcaa8a34900000000, 0xaa97c21300000000, + 0xb580329300000000, 0x1444a67b00000000, 0x0b5356fb00000000, + 0x6b6c37a100000000, 0x747bc72100000000, 0xab12f51500000000, + 0xb405059500000000, 0xd43a64cf00000000, 0xcb2d944f00000000, + 0xd7b53cc500000000, 0xc8a2cc4500000000, 0xa89dad1f00000000, + 0xb78a5d9f00000000, 0x68e36fab00000000, 0x77f49f2b00000000, + 0x17cbfe7100000000, 0x08dc0ef100000000, 0xa9189a1900000000, + 0xb60f6a9900000000, 0xd6300bc300000000, 0xc927fb4300000000, + 0x164ec97700000000, 0x095939f700000000, 0x696658ad00000000, + 0x7671a82d00000000, 0x1050786300000000, 0x0f4788e300000000, + 0x6f78e9b900000000, 0x706f193900000000, 0xaf062b0d00000000, + 0xb011db8d00000000, 0xd02ebad700000000, 0xcf394a5700000000, + 0x6efddebf00000000, 0x71ea2e3f00000000, 0x11d54f6500000000, + 0x0ec2bfe500000000, 0xd1ab8dd100000000, 0xcebc7d5100000000, + 0xae831c0b00000000, 0xb194ec8b00000000, 0xad0c440100000000, + 0xb21bb48100000000, 0xd224d5db00000000, 0xcd33255b00000000, + 0x125a176f00000000, 0x0d4de7ef00000000, 0x6d7286b500000000, + 0x7265763500000000, 0xd3a1e2dd00000000, 0xccb6125d00000000, + 0xac89730700000000, 0xb39e838700000000, 0x6cf7b1b300000000, + 0x73e0413300000000, 0x13df206900000000, 0x0cc8d0e900000000, + 0xdf9d80f400000000, 0xc08a707400000000, 0xa0b5112e00000000, + 0xbfa2e1ae00000000, 0x60cbd39a00000000, 0x7fdc231a00000000, + 0x1fe3424000000000, 0x00f4b2c000000000, 0xa130262800000000, + 0xbe27d6a800000000, 0xde18b7f200000000, 0xc10f477200000000, + 0x1e66754600000000, 0x017185c600000000, 0x614ee49c00000000, + 0x7e59141c00000000, 0x62c1bc9600000000, 0x7dd64c1600000000, + 0x1de92d4c00000000, 0x02feddcc00000000, 0xdd97eff800000000, + 0xc2801f7800000000, 0xa2bf7e2200000000, 0xbda88ea200000000, + 0x1c6c1a4a00000000, 0x037beaca00000000, 0x63448b9000000000, + 0x7c537b1000000000, 0xa33a492400000000, 0xbc2db9a400000000, + 0xdc12d8fe00000000, 0xc305287e00000000, 0xa524f83000000000, + 0xba3308b000000000, 0xda0c69ea00000000, 0xc51b996a00000000, + 0x1a72ab5e00000000, 0x05655bde00000000, 0x655a3a8400000000, + 0x7a4dca0400000000, 0xdb895eec00000000, 0xc49eae6c00000000, + 0xa4a1cf3600000000, 0xbbb63fb600000000, 0x64df0d8200000000, + 0x7bc8fd0200000000, 0x1bf79c5800000000, 0x04e06cd800000000, + 0x1878c45200000000, 0x076f34d200000000, 0x6750558800000000, + 0x7847a50800000000, 0xa72e973c00000000, 0xb83967bc00000000, + 0xd80606e600000000, 0xc711f66600000000, 0x66d5628e00000000, + 0x79c2920e00000000, 0x19fdf35400000000, 0x06ea03d400000000, + 0xd98331e000000000, 0xc694c16000000000, 0xa6aba03a00000000, + 0xb9bc50ba00000000}, + {0x0000000000000000, 0xe2fd888d00000000, 0x85fd60c000000000, + 0x6700e84d00000000, 0x4bfdb05b00000000, 0xa90038d600000000, + 0xce00d09b00000000, 0x2cfd581600000000, 0x96fa61b700000000, + 0x7407e93a00000000, 0x1307017700000000, 0xf1fa89fa00000000, + 0xdd07d1ec00000000, 0x3ffa596100000000, 0x58fab12c00000000, + 0xba0739a100000000, 0x6df3b2b500000000, 0x8f0e3a3800000000, + 0xe80ed27500000000, 0x0af35af800000000, 0x260e02ee00000000, + 0xc4f38a6300000000, 0xa3f3622e00000000, 0x410eeaa300000000, + 0xfb09d30200000000, 0x19f45b8f00000000, 0x7ef4b3c200000000, + 0x9c093b4f00000000, 0xb0f4635900000000, 0x5209ebd400000000, + 0x3509039900000000, 0xd7f48b1400000000, 0x9be014b000000000, + 0x791d9c3d00000000, 0x1e1d747000000000, 0xfce0fcfd00000000, + 0xd01da4eb00000000, 0x32e02c6600000000, 0x55e0c42b00000000, + 0xb71d4ca600000000, 0x0d1a750700000000, 0xefe7fd8a00000000, + 0x88e715c700000000, 0x6a1a9d4a00000000, 0x46e7c55c00000000, + 0xa41a4dd100000000, 0xc31aa59c00000000, 0x21e72d1100000000, + 0xf613a60500000000, 0x14ee2e8800000000, 0x73eec6c500000000, + 0x91134e4800000000, 0xbdee165e00000000, 0x5f139ed300000000, + 0x3813769e00000000, 0xdaeefe1300000000, 0x60e9c7b200000000, + 0x82144f3f00000000, 0xe514a77200000000, 0x07e92fff00000000, + 0x2b1477e900000000, 0xc9e9ff6400000000, 0xaee9172900000000, + 0x4c149fa400000000, 0x77c758bb00000000, 0x953ad03600000000, + 0xf23a387b00000000, 0x10c7b0f600000000, 0x3c3ae8e000000000, + 0xdec7606d00000000, 0xb9c7882000000000, 0x5b3a00ad00000000, + 0xe13d390c00000000, 0x03c0b18100000000, 0x64c059cc00000000, + 0x863dd14100000000, 0xaac0895700000000, 0x483d01da00000000, + 0x2f3de99700000000, 0xcdc0611a00000000, 0x1a34ea0e00000000, + 0xf8c9628300000000, 0x9fc98ace00000000, 0x7d34024300000000, + 0x51c95a5500000000, 0xb334d2d800000000, 0xd4343a9500000000, + 0x36c9b21800000000, 0x8cce8bb900000000, 0x6e33033400000000, + 0x0933eb7900000000, 0xebce63f400000000, 0xc7333be200000000, + 0x25ceb36f00000000, 0x42ce5b2200000000, 0xa033d3af00000000, + 0xec274c0b00000000, 0x0edac48600000000, 0x69da2ccb00000000, + 0x8b27a44600000000, 0xa7dafc5000000000, 0x452774dd00000000, + 0x22279c9000000000, 0xc0da141d00000000, 0x7add2dbc00000000, + 0x9820a53100000000, 0xff204d7c00000000, 0x1dddc5f100000000, + 0x31209de700000000, 0xd3dd156a00000000, 0xb4ddfd2700000000, + 0x562075aa00000000, 0x81d4febe00000000, 0x6329763300000000, + 0x04299e7e00000000, 0xe6d416f300000000, 0xca294ee500000000, + 0x28d4c66800000000, 0x4fd42e2500000000, 0xad29a6a800000000, + 0x172e9f0900000000, 0xf5d3178400000000, 0x92d3ffc900000000, + 0x702e774400000000, 0x5cd32f5200000000, 0xbe2ea7df00000000, + 0xd92e4f9200000000, 0x3bd3c71f00000000, 0xaf88c0ad00000000, + 0x4d75482000000000, 0x2a75a06d00000000, 0xc88828e000000000, + 0xe47570f600000000, 0x0688f87b00000000, 0x6188103600000000, + 0x837598bb00000000, 0x3972a11a00000000, 0xdb8f299700000000, + 0xbc8fc1da00000000, 0x5e72495700000000, 0x728f114100000000, + 0x907299cc00000000, 0xf772718100000000, 0x158ff90c00000000, + 0xc27b721800000000, 0x2086fa9500000000, 0x478612d800000000, + 0xa57b9a5500000000, 0x8986c24300000000, 0x6b7b4ace00000000, + 0x0c7ba28300000000, 0xee862a0e00000000, 0x548113af00000000, + 0xb67c9b2200000000, 0xd17c736f00000000, 0x3381fbe200000000, + 0x1f7ca3f400000000, 0xfd812b7900000000, 0x9a81c33400000000, + 0x787c4bb900000000, 0x3468d41d00000000, 0xd6955c9000000000, + 0xb195b4dd00000000, 0x53683c5000000000, 0x7f95644600000000, + 0x9d68eccb00000000, 0xfa68048600000000, 0x18958c0b00000000, + 0xa292b5aa00000000, 0x406f3d2700000000, 0x276fd56a00000000, + 0xc5925de700000000, 0xe96f05f100000000, 0x0b928d7c00000000, + 0x6c92653100000000, 0x8e6fedbc00000000, 0x599b66a800000000, + 0xbb66ee2500000000, 0xdc66066800000000, 0x3e9b8ee500000000, + 0x1266d6f300000000, 0xf09b5e7e00000000, 0x979bb63300000000, + 0x75663ebe00000000, 0xcf61071f00000000, 0x2d9c8f9200000000, + 0x4a9c67df00000000, 0xa861ef5200000000, 0x849cb74400000000, + 0x66613fc900000000, 0x0161d78400000000, 0xe39c5f0900000000, + 0xd84f981600000000, 0x3ab2109b00000000, 0x5db2f8d600000000, + 0xbf4f705b00000000, 0x93b2284d00000000, 0x714fa0c000000000, + 0x164f488d00000000, 0xf4b2c00000000000, 0x4eb5f9a100000000, + 0xac48712c00000000, 0xcb48996100000000, 0x29b511ec00000000, + 0x054849fa00000000, 0xe7b5c17700000000, 0x80b5293a00000000, + 0x6248a1b700000000, 0xb5bc2aa300000000, 0x5741a22e00000000, + 0x30414a6300000000, 0xd2bcc2ee00000000, 0xfe419af800000000, + 0x1cbc127500000000, 0x7bbcfa3800000000, 0x994172b500000000, + 0x23464b1400000000, 0xc1bbc39900000000, 0xa6bb2bd400000000, + 0x4446a35900000000, 0x68bbfb4f00000000, 0x8a4673c200000000, + 0xed469b8f00000000, 0x0fbb130200000000, 0x43af8ca600000000, + 0xa152042b00000000, 0xc652ec6600000000, 0x24af64eb00000000, + 0x08523cfd00000000, 0xeaafb47000000000, 0x8daf5c3d00000000, + 0x6f52d4b000000000, 0xd555ed1100000000, 0x37a8659c00000000, + 0x50a88dd100000000, 0xb255055c00000000, 0x9ea85d4a00000000, + 0x7c55d5c700000000, 0x1b553d8a00000000, 0xf9a8b50700000000, + 0x2e5c3e1300000000, 0xcca1b69e00000000, 0xaba15ed300000000, + 0x495cd65e00000000, 0x65a18e4800000000, 0x875c06c500000000, + 0xe05cee8800000000, 0x02a1660500000000, 0xb8a65fa400000000, + 0x5a5bd72900000000, 0x3d5b3f6400000000, 0xdfa6b7e900000000, + 0xf35befff00000000, 0x11a6677200000000, 0x76a68f3f00000000, + 0x945b07b200000000}, + {0x0000000000000000, 0xa90b894e00000000, 0x5217129d00000000, + 0xfb1c9bd300000000, 0xe52855e100000000, 0x4c23dcaf00000000, + 0xb73f477c00000000, 0x1e34ce3200000000, 0x8b57db1900000000, + 0x225c525700000000, 0xd940c98400000000, 0x704b40ca00000000, + 0x6e7f8ef800000000, 0xc77407b600000000, 0x3c689c6500000000, + 0x9563152b00000000, 0x16afb63300000000, 0xbfa43f7d00000000, + 0x44b8a4ae00000000, 0xedb32de000000000, 0xf387e3d200000000, + 0x5a8c6a9c00000000, 0xa190f14f00000000, 0x089b780100000000, + 0x9df86d2a00000000, 0x34f3e46400000000, 0xcfef7fb700000000, + 0x66e4f6f900000000, 0x78d038cb00000000, 0xd1dbb18500000000, + 0x2ac72a5600000000, 0x83cca31800000000, 0x2c5e6d6700000000, + 0x8555e42900000000, 0x7e497ffa00000000, 0xd742f6b400000000, + 0xc976388600000000, 0x607db1c800000000, 0x9b612a1b00000000, + 0x326aa35500000000, 0xa709b67e00000000, 0x0e023f3000000000, + 0xf51ea4e300000000, 0x5c152dad00000000, 0x4221e39f00000000, + 0xeb2a6ad100000000, 0x1036f10200000000, 0xb93d784c00000000, + 0x3af1db5400000000, 0x93fa521a00000000, 0x68e6c9c900000000, + 0xc1ed408700000000, 0xdfd98eb500000000, 0x76d207fb00000000, + 0x8dce9c2800000000, 0x24c5156600000000, 0xb1a6004d00000000, + 0x18ad890300000000, 0xe3b112d000000000, 0x4aba9b9e00000000, + 0x548e55ac00000000, 0xfd85dce200000000, 0x0699473100000000, + 0xaf92ce7f00000000, 0x58bcdace00000000, 0xf1b7538000000000, + 0x0aabc85300000000, 0xa3a0411d00000000, 0xbd948f2f00000000, + 0x149f066100000000, 0xef839db200000000, 0x468814fc00000000, + 0xd3eb01d700000000, 0x7ae0889900000000, 0x81fc134a00000000, + 0x28f79a0400000000, 0x36c3543600000000, 0x9fc8dd7800000000, + 0x64d446ab00000000, 0xcddfcfe500000000, 0x4e136cfd00000000, + 0xe718e5b300000000, 0x1c047e6000000000, 0xb50ff72e00000000, + 0xab3b391c00000000, 0x0230b05200000000, 0xf92c2b8100000000, + 0x5027a2cf00000000, 0xc544b7e400000000, 0x6c4f3eaa00000000, + 0x9753a57900000000, 0x3e582c3700000000, 0x206ce20500000000, + 0x89676b4b00000000, 0x727bf09800000000, 0xdb7079d600000000, + 0x74e2b7a900000000, 0xdde93ee700000000, 0x26f5a53400000000, + 0x8ffe2c7a00000000, 0x91cae24800000000, 0x38c16b0600000000, + 0xc3ddf0d500000000, 0x6ad6799b00000000, 0xffb56cb000000000, + 0x56bee5fe00000000, 0xada27e2d00000000, 0x04a9f76300000000, + 0x1a9d395100000000, 0xb396b01f00000000, 0x488a2bcc00000000, + 0xe181a28200000000, 0x624d019a00000000, 0xcb4688d400000000, + 0x305a130700000000, 0x99519a4900000000, 0x8765547b00000000, + 0x2e6edd3500000000, 0xd57246e600000000, 0x7c79cfa800000000, + 0xe91ada8300000000, 0x401153cd00000000, 0xbb0dc81e00000000, + 0x1206415000000000, 0x0c328f6200000000, 0xa539062c00000000, + 0x5e259dff00000000, 0xf72e14b100000000, 0xf17ec44600000000, + 0x58754d0800000000, 0xa369d6db00000000, 0x0a625f9500000000, + 0x145691a700000000, 0xbd5d18e900000000, 0x4641833a00000000, + 0xef4a0a7400000000, 0x7a291f5f00000000, 0xd322961100000000, + 0x283e0dc200000000, 0x8135848c00000000, 0x9f014abe00000000, + 0x360ac3f000000000, 0xcd16582300000000, 0x641dd16d00000000, + 0xe7d1727500000000, 0x4edafb3b00000000, 0xb5c660e800000000, + 0x1ccde9a600000000, 0x02f9279400000000, 0xabf2aeda00000000, + 0x50ee350900000000, 0xf9e5bc4700000000, 0x6c86a96c00000000, + 0xc58d202200000000, 0x3e91bbf100000000, 0x979a32bf00000000, + 0x89aefc8d00000000, 0x20a575c300000000, 0xdbb9ee1000000000, + 0x72b2675e00000000, 0xdd20a92100000000, 0x742b206f00000000, + 0x8f37bbbc00000000, 0x263c32f200000000, 0x3808fcc000000000, + 0x9103758e00000000, 0x6a1fee5d00000000, 0xc314671300000000, + 0x5677723800000000, 0xff7cfb7600000000, 0x046060a500000000, + 0xad6be9eb00000000, 0xb35f27d900000000, 0x1a54ae9700000000, + 0xe148354400000000, 0x4843bc0a00000000, 0xcb8f1f1200000000, + 0x6284965c00000000, 0x99980d8f00000000, 0x309384c100000000, + 0x2ea74af300000000, 0x87acc3bd00000000, 0x7cb0586e00000000, + 0xd5bbd12000000000, 0x40d8c40b00000000, 0xe9d34d4500000000, + 0x12cfd69600000000, 0xbbc45fd800000000, 0xa5f091ea00000000, + 0x0cfb18a400000000, 0xf7e7837700000000, 0x5eec0a3900000000, + 0xa9c21e8800000000, 0x00c997c600000000, 0xfbd50c1500000000, + 0x52de855b00000000, 0x4cea4b6900000000, 0xe5e1c22700000000, + 0x1efd59f400000000, 0xb7f6d0ba00000000, 0x2295c59100000000, + 0x8b9e4cdf00000000, 0x7082d70c00000000, 0xd9895e4200000000, + 0xc7bd907000000000, 0x6eb6193e00000000, 0x95aa82ed00000000, + 0x3ca10ba300000000, 0xbf6da8bb00000000, 0x166621f500000000, + 0xed7aba2600000000, 0x4471336800000000, 0x5a45fd5a00000000, + 0xf34e741400000000, 0x0852efc700000000, 0xa159668900000000, + 0x343a73a200000000, 0x9d31faec00000000, 0x662d613f00000000, + 0xcf26e87100000000, 0xd112264300000000, 0x7819af0d00000000, + 0x830534de00000000, 0x2a0ebd9000000000, 0x859c73ef00000000, + 0x2c97faa100000000, 0xd78b617200000000, 0x7e80e83c00000000, + 0x60b4260e00000000, 0xc9bfaf4000000000, 0x32a3349300000000, + 0x9ba8bddd00000000, 0x0ecba8f600000000, 0xa7c021b800000000, + 0x5cdcba6b00000000, 0xf5d7332500000000, 0xebe3fd1700000000, + 0x42e8745900000000, 0xb9f4ef8a00000000, 0x10ff66c400000000, + 0x9333c5dc00000000, 0x3a384c9200000000, 0xc124d74100000000, + 0x682f5e0f00000000, 0x761b903d00000000, 0xdf10197300000000, + 0x240c82a000000000, 0x8d070bee00000000, 0x18641ec500000000, + 0xb16f978b00000000, 0x4a730c5800000000, 0xe378851600000000, + 0xfd4c4b2400000000, 0x5447c26a00000000, 0xaf5b59b900000000, + 0x0650d0f700000000}, + {0x0000000000000000, 0x479244af00000000, 0xcf22f88500000000, + 0x88b0bc2a00000000, 0xdf4381d000000000, 0x98d1c57f00000000, + 0x1061795500000000, 0x57f33dfa00000000, 0xff81737a00000000, + 0xb81337d500000000, 0x30a38bff00000000, 0x7731cf5000000000, + 0x20c2f2aa00000000, 0x6750b60500000000, 0xefe00a2f00000000, + 0xa8724e8000000000, 0xfe03e7f400000000, 0xb991a35b00000000, + 0x31211f7100000000, 0x76b35bde00000000, 0x2140662400000000, + 0x66d2228b00000000, 0xee629ea100000000, 0xa9f0da0e00000000, + 0x0182948e00000000, 0x4610d02100000000, 0xcea06c0b00000000, + 0x893228a400000000, 0xdec1155e00000000, 0x995351f100000000, + 0x11e3eddb00000000, 0x5671a97400000000, 0xbd01bf3200000000, + 0xfa93fb9d00000000, 0x722347b700000000, 0x35b1031800000000, + 0x62423ee200000000, 0x25d07a4d00000000, 0xad60c66700000000, + 0xeaf282c800000000, 0x4280cc4800000000, 0x051288e700000000, + 0x8da234cd00000000, 0xca30706200000000, 0x9dc34d9800000000, + 0xda51093700000000, 0x52e1b51d00000000, 0x1573f1b200000000, + 0x430258c600000000, 0x04901c6900000000, 0x8c20a04300000000, + 0xcbb2e4ec00000000, 0x9c41d91600000000, 0xdbd39db900000000, + 0x5363219300000000, 0x14f1653c00000000, 0xbc832bbc00000000, + 0xfb116f1300000000, 0x73a1d33900000000, 0x3433979600000000, + 0x63c0aa6c00000000, 0x2452eec300000000, 0xace252e900000000, + 0xeb70164600000000, 0x7a037e6500000000, 0x3d913aca00000000, + 0xb52186e000000000, 0xf2b3c24f00000000, 0xa540ffb500000000, + 0xe2d2bb1a00000000, 0x6a62073000000000, 0x2df0439f00000000, + 0x85820d1f00000000, 0xc21049b000000000, 0x4aa0f59a00000000, + 0x0d32b13500000000, 0x5ac18ccf00000000, 0x1d53c86000000000, + 0x95e3744a00000000, 0xd27130e500000000, 0x8400999100000000, + 0xc392dd3e00000000, 0x4b22611400000000, 0x0cb025bb00000000, + 0x5b43184100000000, 0x1cd15cee00000000, 0x9461e0c400000000, + 0xd3f3a46b00000000, 0x7b81eaeb00000000, 0x3c13ae4400000000, + 0xb4a3126e00000000, 0xf33156c100000000, 0xa4c26b3b00000000, + 0xe3502f9400000000, 0x6be093be00000000, 0x2c72d71100000000, + 0xc702c15700000000, 0x809085f800000000, 0x082039d200000000, + 0x4fb27d7d00000000, 0x1841408700000000, 0x5fd3042800000000, + 0xd763b80200000000, 0x90f1fcad00000000, 0x3883b22d00000000, + 0x7f11f68200000000, 0xf7a14aa800000000, 0xb0330e0700000000, + 0xe7c033fd00000000, 0xa052775200000000, 0x28e2cb7800000000, + 0x6f708fd700000000, 0x390126a300000000, 0x7e93620c00000000, + 0xf623de2600000000, 0xb1b19a8900000000, 0xe642a77300000000, + 0xa1d0e3dc00000000, 0x29605ff600000000, 0x6ef21b5900000000, + 0xc68055d900000000, 0x8112117600000000, 0x09a2ad5c00000000, + 0x4e30e9f300000000, 0x19c3d40900000000, 0x5e5190a600000000, + 0xd6e12c8c00000000, 0x9173682300000000, 0xf406fcca00000000, + 0xb394b86500000000, 0x3b24044f00000000, 0x7cb640e000000000, + 0x2b457d1a00000000, 0x6cd739b500000000, 0xe467859f00000000, + 0xa3f5c13000000000, 0x0b878fb000000000, 0x4c15cb1f00000000, + 0xc4a5773500000000, 0x8337339a00000000, 0xd4c40e6000000000, + 0x93564acf00000000, 0x1be6f6e500000000, 0x5c74b24a00000000, + 0x0a051b3e00000000, 0x4d975f9100000000, 0xc527e3bb00000000, + 0x82b5a71400000000, 0xd5469aee00000000, 0x92d4de4100000000, + 0x1a64626b00000000, 0x5df626c400000000, 0xf584684400000000, + 0xb2162ceb00000000, 0x3aa690c100000000, 0x7d34d46e00000000, + 0x2ac7e99400000000, 0x6d55ad3b00000000, 0xe5e5111100000000, + 0xa27755be00000000, 0x490743f800000000, 0x0e95075700000000, + 0x8625bb7d00000000, 0xc1b7ffd200000000, 0x9644c22800000000, + 0xd1d6868700000000, 0x59663aad00000000, 0x1ef47e0200000000, + 0xb686308200000000, 0xf114742d00000000, 0x79a4c80700000000, + 0x3e368ca800000000, 0x69c5b15200000000, 0x2e57f5fd00000000, + 0xa6e749d700000000, 0xe1750d7800000000, 0xb704a40c00000000, + 0xf096e0a300000000, 0x78265c8900000000, 0x3fb4182600000000, + 0x684725dc00000000, 0x2fd5617300000000, 0xa765dd5900000000, + 0xe0f799f600000000, 0x4885d77600000000, 0x0f1793d900000000, + 0x87a72ff300000000, 0xc0356b5c00000000, 0x97c656a600000000, + 0xd054120900000000, 0x58e4ae2300000000, 0x1f76ea8c00000000, + 0x8e0582af00000000, 0xc997c60000000000, 0x41277a2a00000000, + 0x06b53e8500000000, 0x5146037f00000000, 0x16d447d000000000, + 0x9e64fbfa00000000, 0xd9f6bf5500000000, 0x7184f1d500000000, + 0x3616b57a00000000, 0xbea6095000000000, 0xf9344dff00000000, + 0xaec7700500000000, 0xe95534aa00000000, 0x61e5888000000000, + 0x2677cc2f00000000, 0x7006655b00000000, 0x379421f400000000, + 0xbf249dde00000000, 0xf8b6d97100000000, 0xaf45e48b00000000, + 0xe8d7a02400000000, 0x60671c0e00000000, 0x27f558a100000000, + 0x8f87162100000000, 0xc815528e00000000, 0x40a5eea400000000, + 0x0737aa0b00000000, 0x50c497f100000000, 0x1756d35e00000000, + 0x9fe66f7400000000, 0xd8742bdb00000000, 0x33043d9d00000000, + 0x7496793200000000, 0xfc26c51800000000, 0xbbb481b700000000, + 0xec47bc4d00000000, 0xabd5f8e200000000, 0x236544c800000000, + 0x64f7006700000000, 0xcc854ee700000000, 0x8b170a4800000000, + 0x03a7b66200000000, 0x4435f2cd00000000, 0x13c6cf3700000000, + 0x54548b9800000000, 0xdce437b200000000, 0x9b76731d00000000, + 0xcd07da6900000000, 0x8a959ec600000000, 0x022522ec00000000, + 0x45b7664300000000, 0x12445bb900000000, 0x55d61f1600000000, + 0xdd66a33c00000000, 0x9af4e79300000000, 0x3286a91300000000, + 0x7514edbc00000000, 0xfda4519600000000, 0xba36153900000000, + 0xedc528c300000000, 0xaa576c6c00000000, 0x22e7d04600000000, + 0x657594e900000000}}; + +#else /* W == 4 */ + +local const z_crc_t FAR crc_braid_table[][256] = { + {0x00000000, 0x65673b46, 0xcace768c, 0xafa94dca, 0x4eedeb59, + 0x2b8ad01f, 0x84239dd5, 0xe144a693, 0x9ddbd6b2, 0xf8bcedf4, + 0x5715a03e, 0x32729b78, 0xd3363deb, 0xb65106ad, 0x19f84b67, + 0x7c9f7021, 0xe0c6ab25, 0x85a19063, 0x2a08dda9, 0x4f6fe6ef, + 0xae2b407c, 0xcb4c7b3a, 0x64e536f0, 0x01820db6, 0x7d1d7d97, + 0x187a46d1, 0xb7d30b1b, 0xd2b4305d, 0x33f096ce, 0x5697ad88, + 0xf93ee042, 0x9c59db04, 0x1afc500b, 0x7f9b6b4d, 0xd0322687, + 0xb5551dc1, 0x5411bb52, 0x31768014, 0x9edfcdde, 0xfbb8f698, + 0x872786b9, 0xe240bdff, 0x4de9f035, 0x288ecb73, 0xc9ca6de0, + 0xacad56a6, 0x03041b6c, 0x6663202a, 0xfa3afb2e, 0x9f5dc068, + 0x30f48da2, 0x5593b6e4, 0xb4d71077, 0xd1b02b31, 0x7e1966fb, + 0x1b7e5dbd, 0x67e12d9c, 0x028616da, 0xad2f5b10, 0xc8486056, + 0x290cc6c5, 0x4c6bfd83, 0xe3c2b049, 0x86a58b0f, 0x35f8a016, + 0x509f9b50, 0xff36d69a, 0x9a51eddc, 0x7b154b4f, 0x1e727009, + 0xb1db3dc3, 0xd4bc0685, 0xa82376a4, 0xcd444de2, 0x62ed0028, + 0x078a3b6e, 0xe6ce9dfd, 0x83a9a6bb, 0x2c00eb71, 0x4967d037, + 0xd53e0b33, 0xb0593075, 0x1ff07dbf, 0x7a9746f9, 0x9bd3e06a, + 0xfeb4db2c, 0x511d96e6, 0x347aada0, 0x48e5dd81, 0x2d82e6c7, + 0x822bab0d, 0xe74c904b, 0x060836d8, 0x636f0d9e, 0xccc64054, + 0xa9a17b12, 0x2f04f01d, 0x4a63cb5b, 0xe5ca8691, 0x80adbdd7, + 0x61e91b44, 0x048e2002, 0xab276dc8, 0xce40568e, 0xb2df26af, + 0xd7b81de9, 0x78115023, 0x1d766b65, 0xfc32cdf6, 0x9955f6b0, + 0x36fcbb7a, 0x539b803c, 0xcfc25b38, 0xaaa5607e, 0x050c2db4, + 0x606b16f2, 0x812fb061, 0xe4488b27, 0x4be1c6ed, 0x2e86fdab, + 0x52198d8a, 0x377eb6cc, 0x98d7fb06, 0xfdb0c040, 0x1cf466d3, + 0x79935d95, 0xd63a105f, 0xb35d2b19, 0x6bf1402c, 0x0e967b6a, + 0xa13f36a0, 0xc4580de6, 0x251cab75, 0x407b9033, 0xefd2ddf9, + 0x8ab5e6bf, 0xf62a969e, 0x934dadd8, 0x3ce4e012, 0x5983db54, + 0xb8c77dc7, 0xdda04681, 0x72090b4b, 0x176e300d, 0x8b37eb09, + 0xee50d04f, 0x41f99d85, 0x249ea6c3, 0xc5da0050, 0xa0bd3b16, + 0x0f1476dc, 0x6a734d9a, 0x16ec3dbb, 0x738b06fd, 0xdc224b37, + 0xb9457071, 0x5801d6e2, 0x3d66eda4, 0x92cfa06e, 0xf7a89b28, + 0x710d1027, 0x146a2b61, 0xbbc366ab, 0xdea45ded, 0x3fe0fb7e, + 0x5a87c038, 0xf52e8df2, 0x9049b6b4, 0xecd6c695, 0x89b1fdd3, + 0x2618b019, 0x437f8b5f, 0xa23b2dcc, 0xc75c168a, 0x68f55b40, + 0x0d926006, 0x91cbbb02, 0xf4ac8044, 0x5b05cd8e, 0x3e62f6c8, + 0xdf26505b, 0xba416b1d, 0x15e826d7, 0x708f1d91, 0x0c106db0, + 0x697756f6, 0xc6de1b3c, 0xa3b9207a, 0x42fd86e9, 0x279abdaf, + 0x8833f065, 0xed54cb23, 0x5e09e03a, 0x3b6edb7c, 0x94c796b6, + 0xf1a0adf0, 0x10e40b63, 0x75833025, 0xda2a7def, 0xbf4d46a9, + 0xc3d23688, 0xa6b50dce, 0x091c4004, 0x6c7b7b42, 0x8d3fddd1, + 0xe858e697, 0x47f1ab5d, 0x2296901b, 0xbecf4b1f, 0xdba87059, + 0x74013d93, 0x116606d5, 0xf022a046, 0x95459b00, 0x3aecd6ca, + 0x5f8bed8c, 0x23149dad, 0x4673a6eb, 0xe9daeb21, 0x8cbdd067, + 0x6df976f4, 0x089e4db2, 0xa7370078, 0xc2503b3e, 0x44f5b031, + 0x21928b77, 0x8e3bc6bd, 0xeb5cfdfb, 0x0a185b68, 0x6f7f602e, + 0xc0d62de4, 0xa5b116a2, 0xd92e6683, 0xbc495dc5, 0x13e0100f, + 0x76872b49, 0x97c38dda, 0xf2a4b69c, 0x5d0dfb56, 0x386ac010, + 0xa4331b14, 0xc1542052, 0x6efd6d98, 0x0b9a56de, 0xeadef04d, + 0x8fb9cb0b, 0x201086c1, 0x4577bd87, 0x39e8cda6, 0x5c8ff6e0, + 0xf326bb2a, 0x9641806c, 0x770526ff, 0x12621db9, 0xbdcb5073, + 0xd8ac6b35}, + {0x00000000, 0xd7e28058, 0x74b406f1, 0xa35686a9, 0xe9680de2, + 0x3e8a8dba, 0x9ddc0b13, 0x4a3e8b4b, 0x09a11d85, 0xde439ddd, + 0x7d151b74, 0xaaf79b2c, 0xe0c91067, 0x372b903f, 0x947d1696, + 0x439f96ce, 0x13423b0a, 0xc4a0bb52, 0x67f63dfb, 0xb014bda3, + 0xfa2a36e8, 0x2dc8b6b0, 0x8e9e3019, 0x597cb041, 0x1ae3268f, + 0xcd01a6d7, 0x6e57207e, 0xb9b5a026, 0xf38b2b6d, 0x2469ab35, + 0x873f2d9c, 0x50ddadc4, 0x26847614, 0xf166f64c, 0x523070e5, + 0x85d2f0bd, 0xcfec7bf6, 0x180efbae, 0xbb587d07, 0x6cbafd5f, + 0x2f256b91, 0xf8c7ebc9, 0x5b916d60, 0x8c73ed38, 0xc64d6673, + 0x11afe62b, 0xb2f96082, 0x651be0da, 0x35c64d1e, 0xe224cd46, + 0x41724bef, 0x9690cbb7, 0xdcae40fc, 0x0b4cc0a4, 0xa81a460d, + 0x7ff8c655, 0x3c67509b, 0xeb85d0c3, 0x48d3566a, 0x9f31d632, + 0xd50f5d79, 0x02eddd21, 0xa1bb5b88, 0x7659dbd0, 0x4d08ec28, + 0x9aea6c70, 0x39bcead9, 0xee5e6a81, 0xa460e1ca, 0x73826192, + 0xd0d4e73b, 0x07366763, 0x44a9f1ad, 0x934b71f5, 0x301df75c, + 0xe7ff7704, 0xadc1fc4f, 0x7a237c17, 0xd975fabe, 0x0e977ae6, + 0x5e4ad722, 0x89a8577a, 0x2afed1d3, 0xfd1c518b, 0xb722dac0, + 0x60c05a98, 0xc396dc31, 0x14745c69, 0x57ebcaa7, 0x80094aff, + 0x235fcc56, 0xf4bd4c0e, 0xbe83c745, 0x6961471d, 0xca37c1b4, + 0x1dd541ec, 0x6b8c9a3c, 0xbc6e1a64, 0x1f389ccd, 0xc8da1c95, + 0x82e497de, 0x55061786, 0xf650912f, 0x21b21177, 0x622d87b9, + 0xb5cf07e1, 0x16998148, 0xc17b0110, 0x8b458a5b, 0x5ca70a03, + 0xfff18caa, 0x28130cf2, 0x78cea136, 0xaf2c216e, 0x0c7aa7c7, + 0xdb98279f, 0x91a6acd4, 0x46442c8c, 0xe512aa25, 0x32f02a7d, + 0x716fbcb3, 0xa68d3ceb, 0x05dbba42, 0xd2393a1a, 0x9807b151, + 0x4fe53109, 0xecb3b7a0, 0x3b5137f8, 0x9a11d850, 0x4df35808, + 0xeea5dea1, 0x39475ef9, 0x7379d5b2, 0xa49b55ea, 0x07cdd343, + 0xd02f531b, 0x93b0c5d5, 0x4452458d, 0xe704c324, 0x30e6437c, + 0x7ad8c837, 0xad3a486f, 0x0e6ccec6, 0xd98e4e9e, 0x8953e35a, + 0x5eb16302, 0xfde7e5ab, 0x2a0565f3, 0x603beeb8, 0xb7d96ee0, + 0x148fe849, 0xc36d6811, 0x80f2fedf, 0x57107e87, 0xf446f82e, + 0x23a47876, 0x699af33d, 0xbe787365, 0x1d2ef5cc, 0xcacc7594, + 0xbc95ae44, 0x6b772e1c, 0xc821a8b5, 0x1fc328ed, 0x55fda3a6, + 0x821f23fe, 0x2149a557, 0xf6ab250f, 0xb534b3c1, 0x62d63399, + 0xc180b530, 0x16623568, 0x5c5cbe23, 0x8bbe3e7b, 0x28e8b8d2, + 0xff0a388a, 0xafd7954e, 0x78351516, 0xdb6393bf, 0x0c8113e7, + 0x46bf98ac, 0x915d18f4, 0x320b9e5d, 0xe5e91e05, 0xa67688cb, + 0x71940893, 0xd2c28e3a, 0x05200e62, 0x4f1e8529, 0x98fc0571, + 0x3baa83d8, 0xec480380, 0xd7193478, 0x00fbb420, 0xa3ad3289, + 0x744fb2d1, 0x3e71399a, 0xe993b9c2, 0x4ac53f6b, 0x9d27bf33, + 0xdeb829fd, 0x095aa9a5, 0xaa0c2f0c, 0x7deeaf54, 0x37d0241f, + 0xe032a447, 0x436422ee, 0x9486a2b6, 0xc45b0f72, 0x13b98f2a, + 0xb0ef0983, 0x670d89db, 0x2d330290, 0xfad182c8, 0x59870461, + 0x8e658439, 0xcdfa12f7, 0x1a1892af, 0xb94e1406, 0x6eac945e, + 0x24921f15, 0xf3709f4d, 0x502619e4, 0x87c499bc, 0xf19d426c, + 0x267fc234, 0x8529449d, 0x52cbc4c5, 0x18f54f8e, 0xcf17cfd6, + 0x6c41497f, 0xbba3c927, 0xf83c5fe9, 0x2fdedfb1, 0x8c885918, + 0x5b6ad940, 0x1154520b, 0xc6b6d253, 0x65e054fa, 0xb202d4a2, + 0xe2df7966, 0x353df93e, 0x966b7f97, 0x4189ffcf, 0x0bb77484, + 0xdc55f4dc, 0x7f037275, 0xa8e1f22d, 0xeb7e64e3, 0x3c9ce4bb, + 0x9fca6212, 0x4828e24a, 0x02166901, 0xd5f4e959, 0x76a26ff0, + 0xa140efa8}, + {0x00000000, 0xef52b6e1, 0x05d46b83, 0xea86dd62, 0x0ba8d706, + 0xe4fa61e7, 0x0e7cbc85, 0xe12e0a64, 0x1751ae0c, 0xf80318ed, + 0x1285c58f, 0xfdd7736e, 0x1cf9790a, 0xf3abcfeb, 0x192d1289, + 0xf67fa468, 0x2ea35c18, 0xc1f1eaf9, 0x2b77379b, 0xc425817a, + 0x250b8b1e, 0xca593dff, 0x20dfe09d, 0xcf8d567c, 0x39f2f214, + 0xd6a044f5, 0x3c269997, 0xd3742f76, 0x325a2512, 0xdd0893f3, + 0x378e4e91, 0xd8dcf870, 0x5d46b830, 0xb2140ed1, 0x5892d3b3, + 0xb7c06552, 0x56ee6f36, 0xb9bcd9d7, 0x533a04b5, 0xbc68b254, + 0x4a17163c, 0xa545a0dd, 0x4fc37dbf, 0xa091cb5e, 0x41bfc13a, + 0xaeed77db, 0x446baab9, 0xab391c58, 0x73e5e428, 0x9cb752c9, + 0x76318fab, 0x9963394a, 0x784d332e, 0x971f85cf, 0x7d9958ad, + 0x92cbee4c, 0x64b44a24, 0x8be6fcc5, 0x616021a7, 0x8e329746, + 0x6f1c9d22, 0x804e2bc3, 0x6ac8f6a1, 0x859a4040, 0xba8d7060, + 0x55dfc681, 0xbf591be3, 0x500bad02, 0xb125a766, 0x5e771187, + 0xb4f1cce5, 0x5ba37a04, 0xaddcde6c, 0x428e688d, 0xa808b5ef, + 0x475a030e, 0xa674096a, 0x4926bf8b, 0xa3a062e9, 0x4cf2d408, + 0x942e2c78, 0x7b7c9a99, 0x91fa47fb, 0x7ea8f11a, 0x9f86fb7e, + 0x70d44d9f, 0x9a5290fd, 0x7500261c, 0x837f8274, 0x6c2d3495, + 0x86abe9f7, 0x69f95f16, 0x88d75572, 0x6785e393, 0x8d033ef1, + 0x62518810, 0xe7cbc850, 0x08997eb1, 0xe21fa3d3, 0x0d4d1532, + 0xec631f56, 0x0331a9b7, 0xe9b774d5, 0x06e5c234, 0xf09a665c, + 0x1fc8d0bd, 0xf54e0ddf, 0x1a1cbb3e, 0xfb32b15a, 0x146007bb, + 0xfee6dad9, 0x11b46c38, 0xc9689448, 0x263a22a9, 0xccbcffcb, + 0x23ee492a, 0xc2c0434e, 0x2d92f5af, 0xc71428cd, 0x28469e2c, + 0xde393a44, 0x316b8ca5, 0xdbed51c7, 0x34bfe726, 0xd591ed42, + 0x3ac35ba3, 0xd04586c1, 0x3f173020, 0xae6be681, 0x41395060, + 0xabbf8d02, 0x44ed3be3, 0xa5c33187, 0x4a918766, 0xa0175a04, + 0x4f45ece5, 0xb93a488d, 0x5668fe6c, 0xbcee230e, 0x53bc95ef, + 0xb2929f8b, 0x5dc0296a, 0xb746f408, 0x581442e9, 0x80c8ba99, + 0x6f9a0c78, 0x851cd11a, 0x6a4e67fb, 0x8b606d9f, 0x6432db7e, + 0x8eb4061c, 0x61e6b0fd, 0x97991495, 0x78cba274, 0x924d7f16, + 0x7d1fc9f7, 0x9c31c393, 0x73637572, 0x99e5a810, 0x76b71ef1, + 0xf32d5eb1, 0x1c7fe850, 0xf6f93532, 0x19ab83d3, 0xf88589b7, + 0x17d73f56, 0xfd51e234, 0x120354d5, 0xe47cf0bd, 0x0b2e465c, + 0xe1a89b3e, 0x0efa2ddf, 0xefd427bb, 0x0086915a, 0xea004c38, + 0x0552fad9, 0xdd8e02a9, 0x32dcb448, 0xd85a692a, 0x3708dfcb, + 0xd626d5af, 0x3974634e, 0xd3f2be2c, 0x3ca008cd, 0xcadfaca5, + 0x258d1a44, 0xcf0bc726, 0x205971c7, 0xc1777ba3, 0x2e25cd42, + 0xc4a31020, 0x2bf1a6c1, 0x14e696e1, 0xfbb42000, 0x1132fd62, + 0xfe604b83, 0x1f4e41e7, 0xf01cf706, 0x1a9a2a64, 0xf5c89c85, + 0x03b738ed, 0xece58e0c, 0x0663536e, 0xe931e58f, 0x081fefeb, + 0xe74d590a, 0x0dcb8468, 0xe2993289, 0x3a45caf9, 0xd5177c18, + 0x3f91a17a, 0xd0c3179b, 0x31ed1dff, 0xdebfab1e, 0x3439767c, + 0xdb6bc09d, 0x2d1464f5, 0xc246d214, 0x28c00f76, 0xc792b997, + 0x26bcb3f3, 0xc9ee0512, 0x2368d870, 0xcc3a6e91, 0x49a02ed1, + 0xa6f29830, 0x4c744552, 0xa326f3b3, 0x4208f9d7, 0xad5a4f36, + 0x47dc9254, 0xa88e24b5, 0x5ef180dd, 0xb1a3363c, 0x5b25eb5e, + 0xb4775dbf, 0x555957db, 0xba0be13a, 0x508d3c58, 0xbfdf8ab9, + 0x670372c9, 0x8851c428, 0x62d7194a, 0x8d85afab, 0x6caba5cf, + 0x83f9132e, 0x697fce4c, 0x862d78ad, 0x7052dcc5, 0x9f006a24, + 0x7586b746, 0x9ad401a7, 0x7bfa0bc3, 0x94a8bd22, 0x7e2e6040, + 0x917cd6a1}, + {0x00000000, 0x87a6cb43, 0xd43c90c7, 0x539a5b84, 0x730827cf, + 0xf4aeec8c, 0xa734b708, 0x20927c4b, 0xe6104f9e, 0x61b684dd, + 0x322cdf59, 0xb58a141a, 0x95186851, 0x12bea312, 0x4124f896, + 0xc68233d5, 0x1751997d, 0x90f7523e, 0xc36d09ba, 0x44cbc2f9, + 0x6459beb2, 0xe3ff75f1, 0xb0652e75, 0x37c3e536, 0xf141d6e3, + 0x76e71da0, 0x257d4624, 0xa2db8d67, 0x8249f12c, 0x05ef3a6f, + 0x567561eb, 0xd1d3aaa8, 0x2ea332fa, 0xa905f9b9, 0xfa9fa23d, + 0x7d39697e, 0x5dab1535, 0xda0dde76, 0x899785f2, 0x0e314eb1, + 0xc8b37d64, 0x4f15b627, 0x1c8feda3, 0x9b2926e0, 0xbbbb5aab, + 0x3c1d91e8, 0x6f87ca6c, 0xe821012f, 0x39f2ab87, 0xbe5460c4, + 0xedce3b40, 0x6a68f003, 0x4afa8c48, 0xcd5c470b, 0x9ec61c8f, + 0x1960d7cc, 0xdfe2e419, 0x58442f5a, 0x0bde74de, 0x8c78bf9d, + 0xaceac3d6, 0x2b4c0895, 0x78d65311, 0xff709852, 0x5d4665f4, + 0xdae0aeb7, 0x897af533, 0x0edc3e70, 0x2e4e423b, 0xa9e88978, + 0xfa72d2fc, 0x7dd419bf, 0xbb562a6a, 0x3cf0e129, 0x6f6abaad, + 0xe8cc71ee, 0xc85e0da5, 0x4ff8c6e6, 0x1c629d62, 0x9bc45621, + 0x4a17fc89, 0xcdb137ca, 0x9e2b6c4e, 0x198da70d, 0x391fdb46, + 0xbeb91005, 0xed234b81, 0x6a8580c2, 0xac07b317, 0x2ba17854, + 0x783b23d0, 0xff9de893, 0xdf0f94d8, 0x58a95f9b, 0x0b33041f, + 0x8c95cf5c, 0x73e5570e, 0xf4439c4d, 0xa7d9c7c9, 0x207f0c8a, + 0x00ed70c1, 0x874bbb82, 0xd4d1e006, 0x53772b45, 0x95f51890, + 0x1253d3d3, 0x41c98857, 0xc66f4314, 0xe6fd3f5f, 0x615bf41c, + 0x32c1af98, 0xb56764db, 0x64b4ce73, 0xe3120530, 0xb0885eb4, + 0x372e95f7, 0x17bce9bc, 0x901a22ff, 0xc380797b, 0x4426b238, + 0x82a481ed, 0x05024aae, 0x5698112a, 0xd13eda69, 0xf1aca622, + 0x760a6d61, 0x259036e5, 0xa236fda6, 0xba8ccbe8, 0x3d2a00ab, + 0x6eb05b2f, 0xe916906c, 0xc984ec27, 0x4e222764, 0x1db87ce0, + 0x9a1eb7a3, 0x5c9c8476, 0xdb3a4f35, 0x88a014b1, 0x0f06dff2, + 0x2f94a3b9, 0xa83268fa, 0xfba8337e, 0x7c0ef83d, 0xaddd5295, + 0x2a7b99d6, 0x79e1c252, 0xfe470911, 0xded5755a, 0x5973be19, + 0x0ae9e59d, 0x8d4f2ede, 0x4bcd1d0b, 0xcc6bd648, 0x9ff18dcc, + 0x1857468f, 0x38c53ac4, 0xbf63f187, 0xecf9aa03, 0x6b5f6140, + 0x942ff912, 0x13893251, 0x401369d5, 0xc7b5a296, 0xe727dedd, + 0x6081159e, 0x331b4e1a, 0xb4bd8559, 0x723fb68c, 0xf5997dcf, + 0xa603264b, 0x21a5ed08, 0x01379143, 0x86915a00, 0xd50b0184, + 0x52adcac7, 0x837e606f, 0x04d8ab2c, 0x5742f0a8, 0xd0e43beb, + 0xf07647a0, 0x77d08ce3, 0x244ad767, 0xa3ec1c24, 0x656e2ff1, + 0xe2c8e4b2, 0xb152bf36, 0x36f47475, 0x1666083e, 0x91c0c37d, + 0xc25a98f9, 0x45fc53ba, 0xe7caae1c, 0x606c655f, 0x33f63edb, + 0xb450f598, 0x94c289d3, 0x13644290, 0x40fe1914, 0xc758d257, + 0x01dae182, 0x867c2ac1, 0xd5e67145, 0x5240ba06, 0x72d2c64d, + 0xf5740d0e, 0xa6ee568a, 0x21489dc9, 0xf09b3761, 0x773dfc22, + 0x24a7a7a6, 0xa3016ce5, 0x839310ae, 0x0435dbed, 0x57af8069, + 0xd0094b2a, 0x168b78ff, 0x912db3bc, 0xc2b7e838, 0x4511237b, + 0x65835f30, 0xe2259473, 0xb1bfcff7, 0x361904b4, 0xc9699ce6, + 0x4ecf57a5, 0x1d550c21, 0x9af3c762, 0xba61bb29, 0x3dc7706a, + 0x6e5d2bee, 0xe9fbe0ad, 0x2f79d378, 0xa8df183b, 0xfb4543bf, + 0x7ce388fc, 0x5c71f4b7, 0xdbd73ff4, 0x884d6470, 0x0febaf33, + 0xde38059b, 0x599eced8, 0x0a04955c, 0x8da25e1f, 0xad302254, + 0x2a96e917, 0x790cb293, 0xfeaa79d0, 0x38284a05, 0xbf8e8146, + 0xec14dac2, 0x6bb21181, 0x4b206dca, 0xcc86a689, 0x9f1cfd0d, + 0x18ba364e}}; + +local const z_word_t FAR crc_braid_big_table[][256] = { + {0x00000000, 0x43cba687, 0xc7903cd4, 0x845b9a53, 0xcf270873, + 0x8cecaef4, 0x08b734a7, 0x4b7c9220, 0x9e4f10e6, 0xdd84b661, + 0x59df2c32, 0x1a148ab5, 0x51681895, 0x12a3be12, 0x96f82441, + 0xd53382c6, 0x7d995117, 0x3e52f790, 0xba096dc3, 0xf9c2cb44, + 0xb2be5964, 0xf175ffe3, 0x752e65b0, 0x36e5c337, 0xe3d641f1, + 0xa01de776, 0x24467d25, 0x678ddba2, 0x2cf14982, 0x6f3aef05, + 0xeb617556, 0xa8aad3d1, 0xfa32a32e, 0xb9f905a9, 0x3da29ffa, + 0x7e69397d, 0x3515ab5d, 0x76de0dda, 0xf2859789, 0xb14e310e, + 0x647db3c8, 0x27b6154f, 0xa3ed8f1c, 0xe026299b, 0xab5abbbb, + 0xe8911d3c, 0x6cca876f, 0x2f0121e8, 0x87abf239, 0xc46054be, + 0x403bceed, 0x03f0686a, 0x488cfa4a, 0x0b475ccd, 0x8f1cc69e, + 0xccd76019, 0x19e4e2df, 0x5a2f4458, 0xde74de0b, 0x9dbf788c, + 0xd6c3eaac, 0x95084c2b, 0x1153d678, 0x529870ff, 0xf465465d, + 0xb7aee0da, 0x33f57a89, 0x703edc0e, 0x3b424e2e, 0x7889e8a9, + 0xfcd272fa, 0xbf19d47d, 0x6a2a56bb, 0x29e1f03c, 0xadba6a6f, + 0xee71cce8, 0xa50d5ec8, 0xe6c6f84f, 0x629d621c, 0x2156c49b, + 0x89fc174a, 0xca37b1cd, 0x4e6c2b9e, 0x0da78d19, 0x46db1f39, + 0x0510b9be, 0x814b23ed, 0xc280856a, 0x17b307ac, 0x5478a12b, + 0xd0233b78, 0x93e89dff, 0xd8940fdf, 0x9b5fa958, 0x1f04330b, + 0x5ccf958c, 0x0e57e573, 0x4d9c43f4, 0xc9c7d9a7, 0x8a0c7f20, + 0xc170ed00, 0x82bb4b87, 0x06e0d1d4, 0x452b7753, 0x9018f595, + 0xd3d35312, 0x5788c941, 0x14436fc6, 0x5f3ffde6, 0x1cf45b61, + 0x98afc132, 0xdb6467b5, 0x73ceb464, 0x300512e3, 0xb45e88b0, + 0xf7952e37, 0xbce9bc17, 0xff221a90, 0x7b7980c3, 0x38b22644, + 0xed81a482, 0xae4a0205, 0x2a119856, 0x69da3ed1, 0x22a6acf1, + 0x616d0a76, 0xe5369025, 0xa6fd36a2, 0xe8cb8cba, 0xab002a3d, + 0x2f5bb06e, 0x6c9016e9, 0x27ec84c9, 0x6427224e, 0xe07cb81d, + 0xa3b71e9a, 0x76849c5c, 0x354f3adb, 0xb114a088, 0xf2df060f, + 0xb9a3942f, 0xfa6832a8, 0x7e33a8fb, 0x3df80e7c, 0x9552ddad, + 0xd6997b2a, 0x52c2e179, 0x110947fe, 0x5a75d5de, 0x19be7359, + 0x9de5e90a, 0xde2e4f8d, 0x0b1dcd4b, 0x48d66bcc, 0xcc8df19f, + 0x8f465718, 0xc43ac538, 0x87f163bf, 0x03aaf9ec, 0x40615f6b, + 0x12f92f94, 0x51328913, 0xd5691340, 0x96a2b5c7, 0xddde27e7, + 0x9e158160, 0x1a4e1b33, 0x5985bdb4, 0x8cb63f72, 0xcf7d99f5, + 0x4b2603a6, 0x08eda521, 0x43913701, 0x005a9186, 0x84010bd5, + 0xc7caad52, 0x6f607e83, 0x2cabd804, 0xa8f04257, 0xeb3be4d0, + 0xa04776f0, 0xe38cd077, 0x67d74a24, 0x241ceca3, 0xf12f6e65, + 0xb2e4c8e2, 0x36bf52b1, 0x7574f436, 0x3e086616, 0x7dc3c091, + 0xf9985ac2, 0xba53fc45, 0x1caecae7, 0x5f656c60, 0xdb3ef633, + 0x98f550b4, 0xd389c294, 0x90426413, 0x1419fe40, 0x57d258c7, + 0x82e1da01, 0xc12a7c86, 0x4571e6d5, 0x06ba4052, 0x4dc6d272, + 0x0e0d74f5, 0x8a56eea6, 0xc99d4821, 0x61379bf0, 0x22fc3d77, + 0xa6a7a724, 0xe56c01a3, 0xae109383, 0xeddb3504, 0x6980af57, + 0x2a4b09d0, 0xff788b16, 0xbcb32d91, 0x38e8b7c2, 0x7b231145, + 0x305f8365, 0x739425e2, 0xf7cfbfb1, 0xb4041936, 0xe69c69c9, + 0xa557cf4e, 0x210c551d, 0x62c7f39a, 0x29bb61ba, 0x6a70c73d, + 0xee2b5d6e, 0xade0fbe9, 0x78d3792f, 0x3b18dfa8, 0xbf4345fb, + 0xfc88e37c, 0xb7f4715c, 0xf43fd7db, 0x70644d88, 0x33afeb0f, + 0x9b0538de, 0xd8ce9e59, 0x5c95040a, 0x1f5ea28d, 0x542230ad, + 0x17e9962a, 0x93b20c79, 0xd079aafe, 0x054a2838, 0x46818ebf, + 0xc2da14ec, 0x8111b26b, 0xca6d204b, 0x89a686cc, 0x0dfd1c9f, + 0x4e36ba18}, + {0x00000000, 0xe1b652ef, 0x836bd405, 0x62dd86ea, 0x06d7a80b, + 0xe761fae4, 0x85bc7c0e, 0x640a2ee1, 0x0cae5117, 0xed1803f8, + 0x8fc58512, 0x6e73d7fd, 0x0a79f91c, 0xebcfabf3, 0x89122d19, + 0x68a47ff6, 0x185ca32e, 0xf9eaf1c1, 0x9b37772b, 0x7a8125c4, + 0x1e8b0b25, 0xff3d59ca, 0x9de0df20, 0x7c568dcf, 0x14f2f239, + 0xf544a0d6, 0x9799263c, 0x762f74d3, 0x12255a32, 0xf39308dd, + 0x914e8e37, 0x70f8dcd8, 0x30b8465d, 0xd10e14b2, 0xb3d39258, + 0x5265c0b7, 0x366fee56, 0xd7d9bcb9, 0xb5043a53, 0x54b268bc, + 0x3c16174a, 0xdda045a5, 0xbf7dc34f, 0x5ecb91a0, 0x3ac1bf41, + 0xdb77edae, 0xb9aa6b44, 0x581c39ab, 0x28e4e573, 0xc952b79c, + 0xab8f3176, 0x4a396399, 0x2e334d78, 0xcf851f97, 0xad58997d, + 0x4ceecb92, 0x244ab464, 0xc5fce68b, 0xa7216061, 0x4697328e, + 0x229d1c6f, 0xc32b4e80, 0xa1f6c86a, 0x40409a85, 0x60708dba, + 0x81c6df55, 0xe31b59bf, 0x02ad0b50, 0x66a725b1, 0x8711775e, + 0xe5ccf1b4, 0x047aa35b, 0x6cdedcad, 0x8d688e42, 0xefb508a8, + 0x0e035a47, 0x6a0974a6, 0x8bbf2649, 0xe962a0a3, 0x08d4f24c, + 0x782c2e94, 0x999a7c7b, 0xfb47fa91, 0x1af1a87e, 0x7efb869f, + 0x9f4dd470, 0xfd90529a, 0x1c260075, 0x74827f83, 0x95342d6c, + 0xf7e9ab86, 0x165ff969, 0x7255d788, 0x93e38567, 0xf13e038d, + 0x10885162, 0x50c8cbe7, 0xb17e9908, 0xd3a31fe2, 0x32154d0d, + 0x561f63ec, 0xb7a93103, 0xd574b7e9, 0x34c2e506, 0x5c669af0, + 0xbdd0c81f, 0xdf0d4ef5, 0x3ebb1c1a, 0x5ab132fb, 0xbb076014, + 0xd9dae6fe, 0x386cb411, 0x489468c9, 0xa9223a26, 0xcbffbccc, + 0x2a49ee23, 0x4e43c0c2, 0xaff5922d, 0xcd2814c7, 0x2c9e4628, + 0x443a39de, 0xa58c6b31, 0xc751eddb, 0x26e7bf34, 0x42ed91d5, + 0xa35bc33a, 0xc18645d0, 0x2030173f, 0x81e66bae, 0x60503941, + 0x028dbfab, 0xe33bed44, 0x8731c3a5, 0x6687914a, 0x045a17a0, + 0xe5ec454f, 0x8d483ab9, 0x6cfe6856, 0x0e23eebc, 0xef95bc53, + 0x8b9f92b2, 0x6a29c05d, 0x08f446b7, 0xe9421458, 0x99bac880, + 0x780c9a6f, 0x1ad11c85, 0xfb674e6a, 0x9f6d608b, 0x7edb3264, + 0x1c06b48e, 0xfdb0e661, 0x95149997, 0x74a2cb78, 0x167f4d92, + 0xf7c91f7d, 0x93c3319c, 0x72756373, 0x10a8e599, 0xf11eb776, + 0xb15e2df3, 0x50e87f1c, 0x3235f9f6, 0xd383ab19, 0xb78985f8, + 0x563fd717, 0x34e251fd, 0xd5540312, 0xbdf07ce4, 0x5c462e0b, + 0x3e9ba8e1, 0xdf2dfa0e, 0xbb27d4ef, 0x5a918600, 0x384c00ea, + 0xd9fa5205, 0xa9028edd, 0x48b4dc32, 0x2a695ad8, 0xcbdf0837, + 0xafd526d6, 0x4e637439, 0x2cbef2d3, 0xcd08a03c, 0xa5acdfca, + 0x441a8d25, 0x26c70bcf, 0xc7715920, 0xa37b77c1, 0x42cd252e, + 0x2010a3c4, 0xc1a6f12b, 0xe196e614, 0x0020b4fb, 0x62fd3211, + 0x834b60fe, 0xe7414e1f, 0x06f71cf0, 0x642a9a1a, 0x859cc8f5, + 0xed38b703, 0x0c8ee5ec, 0x6e536306, 0x8fe531e9, 0xebef1f08, + 0x0a594de7, 0x6884cb0d, 0x893299e2, 0xf9ca453a, 0x187c17d5, + 0x7aa1913f, 0x9b17c3d0, 0xff1ded31, 0x1eabbfde, 0x7c763934, + 0x9dc06bdb, 0xf564142d, 0x14d246c2, 0x760fc028, 0x97b992c7, + 0xf3b3bc26, 0x1205eec9, 0x70d86823, 0x916e3acc, 0xd12ea049, + 0x3098f2a6, 0x5245744c, 0xb3f326a3, 0xd7f90842, 0x364f5aad, + 0x5492dc47, 0xb5248ea8, 0xdd80f15e, 0x3c36a3b1, 0x5eeb255b, + 0xbf5d77b4, 0xdb575955, 0x3ae10bba, 0x583c8d50, 0xb98adfbf, + 0xc9720367, 0x28c45188, 0x4a19d762, 0xabaf858d, 0xcfa5ab6c, + 0x2e13f983, 0x4cce7f69, 0xad782d86, 0xc5dc5270, 0x246a009f, + 0x46b78675, 0xa701d49a, 0xc30bfa7b, 0x22bda894, 0x40602e7e, + 0xa1d67c91}, + {0x00000000, 0x5880e2d7, 0xf106b474, 0xa98656a3, 0xe20d68e9, + 0xba8d8a3e, 0x130bdc9d, 0x4b8b3e4a, 0x851da109, 0xdd9d43de, + 0x741b157d, 0x2c9bf7aa, 0x6710c9e0, 0x3f902b37, 0x96167d94, + 0xce969f43, 0x0a3b4213, 0x52bba0c4, 0xfb3df667, 0xa3bd14b0, + 0xe8362afa, 0xb0b6c82d, 0x19309e8e, 0x41b07c59, 0x8f26e31a, + 0xd7a601cd, 0x7e20576e, 0x26a0b5b9, 0x6d2b8bf3, 0x35ab6924, + 0x9c2d3f87, 0xc4addd50, 0x14768426, 0x4cf666f1, 0xe5703052, + 0xbdf0d285, 0xf67beccf, 0xaefb0e18, 0x077d58bb, 0x5ffdba6c, + 0x916b252f, 0xc9ebc7f8, 0x606d915b, 0x38ed738c, 0x73664dc6, + 0x2be6af11, 0x8260f9b2, 0xdae01b65, 0x1e4dc635, 0x46cd24e2, + 0xef4b7241, 0xb7cb9096, 0xfc40aedc, 0xa4c04c0b, 0x0d461aa8, + 0x55c6f87f, 0x9b50673c, 0xc3d085eb, 0x6a56d348, 0x32d6319f, + 0x795d0fd5, 0x21dded02, 0x885bbba1, 0xd0db5976, 0x28ec084d, + 0x706cea9a, 0xd9eabc39, 0x816a5eee, 0xcae160a4, 0x92618273, + 0x3be7d4d0, 0x63673607, 0xadf1a944, 0xf5714b93, 0x5cf71d30, + 0x0477ffe7, 0x4ffcc1ad, 0x177c237a, 0xbefa75d9, 0xe67a970e, + 0x22d74a5e, 0x7a57a889, 0xd3d1fe2a, 0x8b511cfd, 0xc0da22b7, + 0x985ac060, 0x31dc96c3, 0x695c7414, 0xa7caeb57, 0xff4a0980, + 0x56cc5f23, 0x0e4cbdf4, 0x45c783be, 0x1d476169, 0xb4c137ca, + 0xec41d51d, 0x3c9a8c6b, 0x641a6ebc, 0xcd9c381f, 0x951cdac8, + 0xde97e482, 0x86170655, 0x2f9150f6, 0x7711b221, 0xb9872d62, + 0xe107cfb5, 0x48819916, 0x10017bc1, 0x5b8a458b, 0x030aa75c, + 0xaa8cf1ff, 0xf20c1328, 0x36a1ce78, 0x6e212caf, 0xc7a77a0c, + 0x9f2798db, 0xd4aca691, 0x8c2c4446, 0x25aa12e5, 0x7d2af032, + 0xb3bc6f71, 0xeb3c8da6, 0x42badb05, 0x1a3a39d2, 0x51b10798, + 0x0931e54f, 0xa0b7b3ec, 0xf837513b, 0x50d8119a, 0x0858f34d, + 0xa1dea5ee, 0xf95e4739, 0xb2d57973, 0xea559ba4, 0x43d3cd07, + 0x1b532fd0, 0xd5c5b093, 0x8d455244, 0x24c304e7, 0x7c43e630, + 0x37c8d87a, 0x6f483aad, 0xc6ce6c0e, 0x9e4e8ed9, 0x5ae35389, + 0x0263b15e, 0xabe5e7fd, 0xf365052a, 0xb8ee3b60, 0xe06ed9b7, + 0x49e88f14, 0x11686dc3, 0xdffef280, 0x877e1057, 0x2ef846f4, + 0x7678a423, 0x3df39a69, 0x657378be, 0xccf52e1d, 0x9475ccca, + 0x44ae95bc, 0x1c2e776b, 0xb5a821c8, 0xed28c31f, 0xa6a3fd55, + 0xfe231f82, 0x57a54921, 0x0f25abf6, 0xc1b334b5, 0x9933d662, + 0x30b580c1, 0x68356216, 0x23be5c5c, 0x7b3ebe8b, 0xd2b8e828, + 0x8a380aff, 0x4e95d7af, 0x16153578, 0xbf9363db, 0xe713810c, + 0xac98bf46, 0xf4185d91, 0x5d9e0b32, 0x051ee9e5, 0xcb8876a6, + 0x93089471, 0x3a8ec2d2, 0x620e2005, 0x29851e4f, 0x7105fc98, + 0xd883aa3b, 0x800348ec, 0x783419d7, 0x20b4fb00, 0x8932ada3, + 0xd1b24f74, 0x9a39713e, 0xc2b993e9, 0x6b3fc54a, 0x33bf279d, + 0xfd29b8de, 0xa5a95a09, 0x0c2f0caa, 0x54afee7d, 0x1f24d037, + 0x47a432e0, 0xee226443, 0xb6a28694, 0x720f5bc4, 0x2a8fb913, + 0x8309efb0, 0xdb890d67, 0x9002332d, 0xc882d1fa, 0x61048759, + 0x3984658e, 0xf712facd, 0xaf92181a, 0x06144eb9, 0x5e94ac6e, + 0x151f9224, 0x4d9f70f3, 0xe4192650, 0xbc99c487, 0x6c429df1, + 0x34c27f26, 0x9d442985, 0xc5c4cb52, 0x8e4ff518, 0xd6cf17cf, + 0x7f49416c, 0x27c9a3bb, 0xe95f3cf8, 0xb1dfde2f, 0x1859888c, + 0x40d96a5b, 0x0b525411, 0x53d2b6c6, 0xfa54e065, 0xa2d402b2, + 0x6679dfe2, 0x3ef93d35, 0x977f6b96, 0xcfff8941, 0x8474b70b, + 0xdcf455dc, 0x7572037f, 0x2df2e1a8, 0xe3647eeb, 0xbbe49c3c, + 0x1262ca9f, 0x4ae22848, 0x01691602, 0x59e9f4d5, 0xf06fa276, + 0xa8ef40a1}, + {0x00000000, 0x463b6765, 0x8c76ceca, 0xca4da9af, 0x59ebed4e, + 0x1fd08a2b, 0xd59d2384, 0x93a644e1, 0xb2d6db9d, 0xf4edbcf8, + 0x3ea01557, 0x789b7232, 0xeb3d36d3, 0xad0651b6, 0x674bf819, + 0x21709f7c, 0x25abc6e0, 0x6390a185, 0xa9dd082a, 0xefe66f4f, + 0x7c402bae, 0x3a7b4ccb, 0xf036e564, 0xb60d8201, 0x977d1d7d, + 0xd1467a18, 0x1b0bd3b7, 0x5d30b4d2, 0xce96f033, 0x88ad9756, + 0x42e03ef9, 0x04db599c, 0x0b50fc1a, 0x4d6b9b7f, 0x872632d0, + 0xc11d55b5, 0x52bb1154, 0x14807631, 0xdecddf9e, 0x98f6b8fb, + 0xb9862787, 0xffbd40e2, 0x35f0e94d, 0x73cb8e28, 0xe06dcac9, + 0xa656adac, 0x6c1b0403, 0x2a206366, 0x2efb3afa, 0x68c05d9f, + 0xa28df430, 0xe4b69355, 0x7710d7b4, 0x312bb0d1, 0xfb66197e, + 0xbd5d7e1b, 0x9c2de167, 0xda168602, 0x105b2fad, 0x566048c8, + 0xc5c60c29, 0x83fd6b4c, 0x49b0c2e3, 0x0f8ba586, 0x16a0f835, + 0x509b9f50, 0x9ad636ff, 0xdced519a, 0x4f4b157b, 0x0970721e, + 0xc33ddbb1, 0x8506bcd4, 0xa47623a8, 0xe24d44cd, 0x2800ed62, + 0x6e3b8a07, 0xfd9dcee6, 0xbba6a983, 0x71eb002c, 0x37d06749, + 0x330b3ed5, 0x753059b0, 0xbf7df01f, 0xf946977a, 0x6ae0d39b, + 0x2cdbb4fe, 0xe6961d51, 0xa0ad7a34, 0x81dde548, 0xc7e6822d, + 0x0dab2b82, 0x4b904ce7, 0xd8360806, 0x9e0d6f63, 0x5440c6cc, + 0x127ba1a9, 0x1df0042f, 0x5bcb634a, 0x9186cae5, 0xd7bdad80, + 0x441be961, 0x02208e04, 0xc86d27ab, 0x8e5640ce, 0xaf26dfb2, + 0xe91db8d7, 0x23501178, 0x656b761d, 0xf6cd32fc, 0xb0f65599, + 0x7abbfc36, 0x3c809b53, 0x385bc2cf, 0x7e60a5aa, 0xb42d0c05, + 0xf2166b60, 0x61b02f81, 0x278b48e4, 0xedc6e14b, 0xabfd862e, + 0x8a8d1952, 0xccb67e37, 0x06fbd798, 0x40c0b0fd, 0xd366f41c, + 0x955d9379, 0x5f103ad6, 0x192b5db3, 0x2c40f16b, 0x6a7b960e, + 0xa0363fa1, 0xe60d58c4, 0x75ab1c25, 0x33907b40, 0xf9ddd2ef, + 0xbfe6b58a, 0x9e962af6, 0xd8ad4d93, 0x12e0e43c, 0x54db8359, + 0xc77dc7b8, 0x8146a0dd, 0x4b0b0972, 0x0d306e17, 0x09eb378b, + 0x4fd050ee, 0x859df941, 0xc3a69e24, 0x5000dac5, 0x163bbda0, + 0xdc76140f, 0x9a4d736a, 0xbb3dec16, 0xfd068b73, 0x374b22dc, + 0x717045b9, 0xe2d60158, 0xa4ed663d, 0x6ea0cf92, 0x289ba8f7, + 0x27100d71, 0x612b6a14, 0xab66c3bb, 0xed5da4de, 0x7efbe03f, + 0x38c0875a, 0xf28d2ef5, 0xb4b64990, 0x95c6d6ec, 0xd3fdb189, + 0x19b01826, 0x5f8b7f43, 0xcc2d3ba2, 0x8a165cc7, 0x405bf568, + 0x0660920d, 0x02bbcb91, 0x4480acf4, 0x8ecd055b, 0xc8f6623e, + 0x5b5026df, 0x1d6b41ba, 0xd726e815, 0x911d8f70, 0xb06d100c, + 0xf6567769, 0x3c1bdec6, 0x7a20b9a3, 0xe986fd42, 0xafbd9a27, + 0x65f03388, 0x23cb54ed, 0x3ae0095e, 0x7cdb6e3b, 0xb696c794, + 0xf0ada0f1, 0x630be410, 0x25308375, 0xef7d2ada, 0xa9464dbf, + 0x8836d2c3, 0xce0db5a6, 0x04401c09, 0x427b7b6c, 0xd1dd3f8d, + 0x97e658e8, 0x5dabf147, 0x1b909622, 0x1f4bcfbe, 0x5970a8db, + 0x933d0174, 0xd5066611, 0x46a022f0, 0x009b4595, 0xcad6ec3a, + 0x8ced8b5f, 0xad9d1423, 0xeba67346, 0x21ebdae9, 0x67d0bd8c, + 0xf476f96d, 0xb24d9e08, 0x780037a7, 0x3e3b50c2, 0x31b0f544, + 0x778b9221, 0xbdc63b8e, 0xfbfd5ceb, 0x685b180a, 0x2e607f6f, + 0xe42dd6c0, 0xa216b1a5, 0x83662ed9, 0xc55d49bc, 0x0f10e013, + 0x492b8776, 0xda8dc397, 0x9cb6a4f2, 0x56fb0d5d, 0x10c06a38, + 0x141b33a4, 0x522054c1, 0x986dfd6e, 0xde569a0b, 0x4df0deea, + 0x0bcbb98f, 0xc1861020, 0x87bd7745, 0xa6cde839, 0xe0f68f5c, + 0x2abb26f3, 0x6c804196, 0xff260577, 0xb91d6212, 0x7350cbbd, + 0x356bacd8}}; + +#endif + +#endif + +#if N == 6 + +#if W == 8 + +local const z_crc_t FAR crc_braid_table[][256] = { + {0x00000000, 0x3db1ecdc, 0x7b63d9b8, 0x46d23564, 0xf6c7b370, + 0xcb765fac, 0x8da46ac8, 0xb0158614, 0x36fe60a1, 0x0b4f8c7d, + 0x4d9db919, 0x702c55c5, 0xc039d3d1, 0xfd883f0d, 0xbb5a0a69, + 0x86ebe6b5, 0x6dfcc142, 0x504d2d9e, 0x169f18fa, 0x2b2ef426, + 0x9b3b7232, 0xa68a9eee, 0xe058ab8a, 0xdde94756, 0x5b02a1e3, + 0x66b34d3f, 0x2061785b, 0x1dd09487, 0xadc51293, 0x9074fe4f, + 0xd6a6cb2b, 0xeb1727f7, 0xdbf98284, 0xe6486e58, 0xa09a5b3c, + 0x9d2bb7e0, 0x2d3e31f4, 0x108fdd28, 0x565de84c, 0x6bec0490, + 0xed07e225, 0xd0b60ef9, 0x96643b9d, 0xabd5d741, 0x1bc05155, + 0x2671bd89, 0x60a388ed, 0x5d126431, 0xb60543c6, 0x8bb4af1a, + 0xcd669a7e, 0xf0d776a2, 0x40c2f0b6, 0x7d731c6a, 0x3ba1290e, + 0x0610c5d2, 0x80fb2367, 0xbd4acfbb, 0xfb98fadf, 0xc6291603, + 0x763c9017, 0x4b8d7ccb, 0x0d5f49af, 0x30eea573, 0x6c820349, + 0x5133ef95, 0x17e1daf1, 0x2a50362d, 0x9a45b039, 0xa7f45ce5, + 0xe1266981, 0xdc97855d, 0x5a7c63e8, 0x67cd8f34, 0x211fba50, + 0x1cae568c, 0xacbbd098, 0x910a3c44, 0xd7d80920, 0xea69e5fc, + 0x017ec20b, 0x3ccf2ed7, 0x7a1d1bb3, 0x47acf76f, 0xf7b9717b, + 0xca089da7, 0x8cdaa8c3, 0xb16b441f, 0x3780a2aa, 0x0a314e76, + 0x4ce37b12, 0x715297ce, 0xc14711da, 0xfcf6fd06, 0xba24c862, + 0x879524be, 0xb77b81cd, 0x8aca6d11, 0xcc185875, 0xf1a9b4a9, + 0x41bc32bd, 0x7c0dde61, 0x3adfeb05, 0x076e07d9, 0x8185e16c, + 0xbc340db0, 0xfae638d4, 0xc757d408, 0x7742521c, 0x4af3bec0, + 0x0c218ba4, 0x31906778, 0xda87408f, 0xe736ac53, 0xa1e49937, + 0x9c5575eb, 0x2c40f3ff, 0x11f11f23, 0x57232a47, 0x6a92c69b, + 0xec79202e, 0xd1c8ccf2, 0x971af996, 0xaaab154a, 0x1abe935e, + 0x270f7f82, 0x61dd4ae6, 0x5c6ca63a, 0xd9040692, 0xe4b5ea4e, + 0xa267df2a, 0x9fd633f6, 0x2fc3b5e2, 0x1272593e, 0x54a06c5a, + 0x69118086, 0xeffa6633, 0xd24b8aef, 0x9499bf8b, 0xa9285357, + 0x193dd543, 0x248c399f, 0x625e0cfb, 0x5fefe027, 0xb4f8c7d0, + 0x89492b0c, 0xcf9b1e68, 0xf22af2b4, 0x423f74a0, 0x7f8e987c, + 0x395cad18, 0x04ed41c4, 0x8206a771, 0xbfb74bad, 0xf9657ec9, + 0xc4d49215, 0x74c11401, 0x4970f8dd, 0x0fa2cdb9, 0x32132165, + 0x02fd8416, 0x3f4c68ca, 0x799e5dae, 0x442fb172, 0xf43a3766, + 0xc98bdbba, 0x8f59eede, 0xb2e80202, 0x3403e4b7, 0x09b2086b, + 0x4f603d0f, 0x72d1d1d3, 0xc2c457c7, 0xff75bb1b, 0xb9a78e7f, + 0x841662a3, 0x6f014554, 0x52b0a988, 0x14629cec, 0x29d37030, + 0x99c6f624, 0xa4771af8, 0xe2a52f9c, 0xdf14c340, 0x59ff25f5, + 0x644ec929, 0x229cfc4d, 0x1f2d1091, 0xaf389685, 0x92897a59, + 0xd45b4f3d, 0xe9eaa3e1, 0xb58605db, 0x8837e907, 0xcee5dc63, + 0xf35430bf, 0x4341b6ab, 0x7ef05a77, 0x38226f13, 0x059383cf, + 0x8378657a, 0xbec989a6, 0xf81bbcc2, 0xc5aa501e, 0x75bfd60a, + 0x480e3ad6, 0x0edc0fb2, 0x336de36e, 0xd87ac499, 0xe5cb2845, + 0xa3191d21, 0x9ea8f1fd, 0x2ebd77e9, 0x130c9b35, 0x55deae51, + 0x686f428d, 0xee84a438, 0xd33548e4, 0x95e77d80, 0xa856915c, + 0x18431748, 0x25f2fb94, 0x6320cef0, 0x5e91222c, 0x6e7f875f, + 0x53ce6b83, 0x151c5ee7, 0x28adb23b, 0x98b8342f, 0xa509d8f3, + 0xe3dbed97, 0xde6a014b, 0x5881e7fe, 0x65300b22, 0x23e23e46, + 0x1e53d29a, 0xae46548e, 0x93f7b852, 0xd5258d36, 0xe89461ea, + 0x0383461d, 0x3e32aac1, 0x78e09fa5, 0x45517379, 0xf544f56d, + 0xc8f519b1, 0x8e272cd5, 0xb396c009, 0x357d26bc, 0x08ccca60, + 0x4e1eff04, 0x73af13d8, 0xc3ba95cc, 0xfe0b7910, 0xb8d94c74, + 0x8568a0a8}, + {0x00000000, 0x69790b65, 0xd2f216ca, 0xbb8b1daf, 0x7e952bd5, + 0x17ec20b0, 0xac673d1f, 0xc51e367a, 0xfd2a57aa, 0x94535ccf, + 0x2fd84160, 0x46a14a05, 0x83bf7c7f, 0xeac6771a, 0x514d6ab5, + 0x383461d0, 0x2125a915, 0x485ca270, 0xf3d7bfdf, 0x9aaeb4ba, + 0x5fb082c0, 0x36c989a5, 0x8d42940a, 0xe43b9f6f, 0xdc0ffebf, + 0xb576f5da, 0x0efde875, 0x6784e310, 0xa29ad56a, 0xcbe3de0f, + 0x7068c3a0, 0x1911c8c5, 0x424b522a, 0x2b32594f, 0x90b944e0, + 0xf9c04f85, 0x3cde79ff, 0x55a7729a, 0xee2c6f35, 0x87556450, + 0xbf610580, 0xd6180ee5, 0x6d93134a, 0x04ea182f, 0xc1f42e55, + 0xa88d2530, 0x1306389f, 0x7a7f33fa, 0x636efb3f, 0x0a17f05a, + 0xb19cedf5, 0xd8e5e690, 0x1dfbd0ea, 0x7482db8f, 0xcf09c620, + 0xa670cd45, 0x9e44ac95, 0xf73da7f0, 0x4cb6ba5f, 0x25cfb13a, + 0xe0d18740, 0x89a88c25, 0x3223918a, 0x5b5a9aef, 0x8496a454, + 0xedefaf31, 0x5664b29e, 0x3f1db9fb, 0xfa038f81, 0x937a84e4, + 0x28f1994b, 0x4188922e, 0x79bcf3fe, 0x10c5f89b, 0xab4ee534, + 0xc237ee51, 0x0729d82b, 0x6e50d34e, 0xd5dbcee1, 0xbca2c584, + 0xa5b30d41, 0xccca0624, 0x77411b8b, 0x1e3810ee, 0xdb262694, + 0xb25f2df1, 0x09d4305e, 0x60ad3b3b, 0x58995aeb, 0x31e0518e, + 0x8a6b4c21, 0xe3124744, 0x260c713e, 0x4f757a5b, 0xf4fe67f4, + 0x9d876c91, 0xc6ddf67e, 0xafa4fd1b, 0x142fe0b4, 0x7d56ebd1, + 0xb848ddab, 0xd131d6ce, 0x6abacb61, 0x03c3c004, 0x3bf7a1d4, + 0x528eaab1, 0xe905b71e, 0x807cbc7b, 0x45628a01, 0x2c1b8164, + 0x97909ccb, 0xfee997ae, 0xe7f85f6b, 0x8e81540e, 0x350a49a1, + 0x5c7342c4, 0x996d74be, 0xf0147fdb, 0x4b9f6274, 0x22e66911, + 0x1ad208c1, 0x73ab03a4, 0xc8201e0b, 0xa159156e, 0x64472314, + 0x0d3e2871, 0xb6b535de, 0xdfcc3ebb, 0xd25c4ee9, 0xbb25458c, + 0x00ae5823, 0x69d75346, 0xacc9653c, 0xc5b06e59, 0x7e3b73f6, + 0x17427893, 0x2f761943, 0x460f1226, 0xfd840f89, 0x94fd04ec, + 0x51e33296, 0x389a39f3, 0x8311245c, 0xea682f39, 0xf379e7fc, + 0x9a00ec99, 0x218bf136, 0x48f2fa53, 0x8deccc29, 0xe495c74c, + 0x5f1edae3, 0x3667d186, 0x0e53b056, 0x672abb33, 0xdca1a69c, + 0xb5d8adf9, 0x70c69b83, 0x19bf90e6, 0xa2348d49, 0xcb4d862c, + 0x90171cc3, 0xf96e17a6, 0x42e50a09, 0x2b9c016c, 0xee823716, + 0x87fb3c73, 0x3c7021dc, 0x55092ab9, 0x6d3d4b69, 0x0444400c, + 0xbfcf5da3, 0xd6b656c6, 0x13a860bc, 0x7ad16bd9, 0xc15a7676, + 0xa8237d13, 0xb132b5d6, 0xd84bbeb3, 0x63c0a31c, 0x0ab9a879, + 0xcfa79e03, 0xa6de9566, 0x1d5588c9, 0x742c83ac, 0x4c18e27c, + 0x2561e919, 0x9eeaf4b6, 0xf793ffd3, 0x328dc9a9, 0x5bf4c2cc, + 0xe07fdf63, 0x8906d406, 0x56caeabd, 0x3fb3e1d8, 0x8438fc77, + 0xed41f712, 0x285fc168, 0x4126ca0d, 0xfaadd7a2, 0x93d4dcc7, + 0xabe0bd17, 0xc299b672, 0x7912abdd, 0x106ba0b8, 0xd57596c2, + 0xbc0c9da7, 0x07878008, 0x6efe8b6d, 0x77ef43a8, 0x1e9648cd, + 0xa51d5562, 0xcc645e07, 0x097a687d, 0x60036318, 0xdb887eb7, + 0xb2f175d2, 0x8ac51402, 0xe3bc1f67, 0x583702c8, 0x314e09ad, + 0xf4503fd7, 0x9d2934b2, 0x26a2291d, 0x4fdb2278, 0x1481b897, + 0x7df8b3f2, 0xc673ae5d, 0xaf0aa538, 0x6a149342, 0x036d9827, + 0xb8e68588, 0xd19f8eed, 0xe9abef3d, 0x80d2e458, 0x3b59f9f7, + 0x5220f292, 0x973ec4e8, 0xfe47cf8d, 0x45ccd222, 0x2cb5d947, + 0x35a41182, 0x5cdd1ae7, 0xe7560748, 0x8e2f0c2d, 0x4b313a57, + 0x22483132, 0x99c32c9d, 0xf0ba27f8, 0xc88e4628, 0xa1f74d4d, + 0x1a7c50e2, 0x73055b87, 0xb61b6dfd, 0xdf626698, 0x64e97b37, + 0x0d907052}, + {0x00000000, 0x7fc99b93, 0xff933726, 0x805aacb5, 0x2457680d, + 0x5b9ef39e, 0xdbc45f2b, 0xa40dc4b8, 0x48aed01a, 0x37674b89, + 0xb73de73c, 0xc8f47caf, 0x6cf9b817, 0x13302384, 0x936a8f31, + 0xeca314a2, 0x915da034, 0xee943ba7, 0x6ece9712, 0x11070c81, + 0xb50ac839, 0xcac353aa, 0x4a99ff1f, 0x3550648c, 0xd9f3702e, + 0xa63aebbd, 0x26604708, 0x59a9dc9b, 0xfda41823, 0x826d83b0, + 0x02372f05, 0x7dfeb496, 0xf9ca4629, 0x8603ddba, 0x0659710f, + 0x7990ea9c, 0xdd9d2e24, 0xa254b5b7, 0x220e1902, 0x5dc78291, + 0xb1649633, 0xcead0da0, 0x4ef7a115, 0x313e3a86, 0x9533fe3e, + 0xeafa65ad, 0x6aa0c918, 0x1569528b, 0x6897e61d, 0x175e7d8e, + 0x9704d13b, 0xe8cd4aa8, 0x4cc08e10, 0x33091583, 0xb353b936, + 0xcc9a22a5, 0x20393607, 0x5ff0ad94, 0xdfaa0121, 0xa0639ab2, + 0x046e5e0a, 0x7ba7c599, 0xfbfd692c, 0x8434f2bf, 0x28e58a13, + 0x572c1180, 0xd776bd35, 0xa8bf26a6, 0x0cb2e21e, 0x737b798d, + 0xf321d538, 0x8ce84eab, 0x604b5a09, 0x1f82c19a, 0x9fd86d2f, + 0xe011f6bc, 0x441c3204, 0x3bd5a997, 0xbb8f0522, 0xc4469eb1, + 0xb9b82a27, 0xc671b1b4, 0x462b1d01, 0x39e28692, 0x9def422a, + 0xe226d9b9, 0x627c750c, 0x1db5ee9f, 0xf116fa3d, 0x8edf61ae, + 0x0e85cd1b, 0x714c5688, 0xd5419230, 0xaa8809a3, 0x2ad2a516, + 0x551b3e85, 0xd12fcc3a, 0xaee657a9, 0x2ebcfb1c, 0x5175608f, + 0xf578a437, 0x8ab13fa4, 0x0aeb9311, 0x75220882, 0x99811c20, + 0xe64887b3, 0x66122b06, 0x19dbb095, 0xbdd6742d, 0xc21fefbe, + 0x4245430b, 0x3d8cd898, 0x40726c0e, 0x3fbbf79d, 0xbfe15b28, + 0xc028c0bb, 0x64250403, 0x1bec9f90, 0x9bb63325, 0xe47fa8b6, + 0x08dcbc14, 0x77152787, 0xf74f8b32, 0x888610a1, 0x2c8bd419, + 0x53424f8a, 0xd318e33f, 0xacd178ac, 0x51cb1426, 0x2e028fb5, + 0xae582300, 0xd191b893, 0x759c7c2b, 0x0a55e7b8, 0x8a0f4b0d, + 0xf5c6d09e, 0x1965c43c, 0x66ac5faf, 0xe6f6f31a, 0x993f6889, + 0x3d32ac31, 0x42fb37a2, 0xc2a19b17, 0xbd680084, 0xc096b412, + 0xbf5f2f81, 0x3f058334, 0x40cc18a7, 0xe4c1dc1f, 0x9b08478c, + 0x1b52eb39, 0x649b70aa, 0x88386408, 0xf7f1ff9b, 0x77ab532e, + 0x0862c8bd, 0xac6f0c05, 0xd3a69796, 0x53fc3b23, 0x2c35a0b0, + 0xa801520f, 0xd7c8c99c, 0x57926529, 0x285bfeba, 0x8c563a02, + 0xf39fa191, 0x73c50d24, 0x0c0c96b7, 0xe0af8215, 0x9f661986, + 0x1f3cb533, 0x60f52ea0, 0xc4f8ea18, 0xbb31718b, 0x3b6bdd3e, + 0x44a246ad, 0x395cf23b, 0x469569a8, 0xc6cfc51d, 0xb9065e8e, + 0x1d0b9a36, 0x62c201a5, 0xe298ad10, 0x9d513683, 0x71f22221, + 0x0e3bb9b2, 0x8e611507, 0xf1a88e94, 0x55a54a2c, 0x2a6cd1bf, + 0xaa367d0a, 0xd5ffe699, 0x792e9e35, 0x06e705a6, 0x86bda913, + 0xf9743280, 0x5d79f638, 0x22b06dab, 0xa2eac11e, 0xdd235a8d, + 0x31804e2f, 0x4e49d5bc, 0xce137909, 0xb1dae29a, 0x15d72622, + 0x6a1ebdb1, 0xea441104, 0x958d8a97, 0xe8733e01, 0x97baa592, + 0x17e00927, 0x682992b4, 0xcc24560c, 0xb3edcd9f, 0x33b7612a, + 0x4c7efab9, 0xa0ddee1b, 0xdf147588, 0x5f4ed93d, 0x208742ae, + 0x848a8616, 0xfb431d85, 0x7b19b130, 0x04d02aa3, 0x80e4d81c, + 0xff2d438f, 0x7f77ef3a, 0x00be74a9, 0xa4b3b011, 0xdb7a2b82, + 0x5b208737, 0x24e91ca4, 0xc84a0806, 0xb7839395, 0x37d93f20, + 0x4810a4b3, 0xec1d600b, 0x93d4fb98, 0x138e572d, 0x6c47ccbe, + 0x11b97828, 0x6e70e3bb, 0xee2a4f0e, 0x91e3d49d, 0x35ee1025, + 0x4a278bb6, 0xca7d2703, 0xb5b4bc90, 0x5917a832, 0x26de33a1, + 0xa6849f14, 0xd94d0487, 0x7d40c03f, 0x02895bac, 0x82d3f719, + 0xfd1a6c8a}, + {0x00000000, 0xa396284c, 0x9c5d56d9, 0x3fcb7e95, 0xe3cbabf3, + 0x405d83bf, 0x7f96fd2a, 0xdc00d566, 0x1ce651a7, 0xbf7079eb, + 0x80bb077e, 0x232d2f32, 0xff2dfa54, 0x5cbbd218, 0x6370ac8d, + 0xc0e684c1, 0x39cca34e, 0x9a5a8b02, 0xa591f597, 0x0607dddb, + 0xda0708bd, 0x799120f1, 0x465a5e64, 0xe5cc7628, 0x252af2e9, + 0x86bcdaa5, 0xb977a430, 0x1ae18c7c, 0xc6e1591a, 0x65777156, + 0x5abc0fc3, 0xf92a278f, 0x7399469c, 0xd00f6ed0, 0xefc41045, + 0x4c523809, 0x9052ed6f, 0x33c4c523, 0x0c0fbbb6, 0xaf9993fa, + 0x6f7f173b, 0xcce93f77, 0xf32241e2, 0x50b469ae, 0x8cb4bcc8, + 0x2f229484, 0x10e9ea11, 0xb37fc25d, 0x4a55e5d2, 0xe9c3cd9e, + 0xd608b30b, 0x759e9b47, 0xa99e4e21, 0x0a08666d, 0x35c318f8, + 0x965530b4, 0x56b3b475, 0xf5259c39, 0xcaeee2ac, 0x6978cae0, + 0xb5781f86, 0x16ee37ca, 0x2925495f, 0x8ab36113, 0xe7328d38, + 0x44a4a574, 0x7b6fdbe1, 0xd8f9f3ad, 0x04f926cb, 0xa76f0e87, + 0x98a47012, 0x3b32585e, 0xfbd4dc9f, 0x5842f4d3, 0x67898a46, + 0xc41fa20a, 0x181f776c, 0xbb895f20, 0x844221b5, 0x27d409f9, + 0xdefe2e76, 0x7d68063a, 0x42a378af, 0xe13550e3, 0x3d358585, + 0x9ea3adc9, 0xa168d35c, 0x02fefb10, 0xc2187fd1, 0x618e579d, + 0x5e452908, 0xfdd30144, 0x21d3d422, 0x8245fc6e, 0xbd8e82fb, + 0x1e18aab7, 0x94abcba4, 0x373de3e8, 0x08f69d7d, 0xab60b531, + 0x77606057, 0xd4f6481b, 0xeb3d368e, 0x48ab1ec2, 0x884d9a03, + 0x2bdbb24f, 0x1410ccda, 0xb786e496, 0x6b8631f0, 0xc81019bc, + 0xf7db6729, 0x544d4f65, 0xad6768ea, 0x0ef140a6, 0x313a3e33, + 0x92ac167f, 0x4eacc319, 0xed3aeb55, 0xd2f195c0, 0x7167bd8c, + 0xb181394d, 0x12171101, 0x2ddc6f94, 0x8e4a47d8, 0x524a92be, + 0xf1dcbaf2, 0xce17c467, 0x6d81ec2b, 0x15141c31, 0xb682347d, + 0x89494ae8, 0x2adf62a4, 0xf6dfb7c2, 0x55499f8e, 0x6a82e11b, + 0xc914c957, 0x09f24d96, 0xaa6465da, 0x95af1b4f, 0x36393303, + 0xea39e665, 0x49afce29, 0x7664b0bc, 0xd5f298f0, 0x2cd8bf7f, + 0x8f4e9733, 0xb085e9a6, 0x1313c1ea, 0xcf13148c, 0x6c853cc0, + 0x534e4255, 0xf0d86a19, 0x303eeed8, 0x93a8c694, 0xac63b801, + 0x0ff5904d, 0xd3f5452b, 0x70636d67, 0x4fa813f2, 0xec3e3bbe, + 0x668d5aad, 0xc51b72e1, 0xfad00c74, 0x59462438, 0x8546f15e, + 0x26d0d912, 0x191ba787, 0xba8d8fcb, 0x7a6b0b0a, 0xd9fd2346, + 0xe6365dd3, 0x45a0759f, 0x99a0a0f9, 0x3a3688b5, 0x05fdf620, + 0xa66bde6c, 0x5f41f9e3, 0xfcd7d1af, 0xc31caf3a, 0x608a8776, + 0xbc8a5210, 0x1f1c7a5c, 0x20d704c9, 0x83412c85, 0x43a7a844, + 0xe0318008, 0xdffafe9d, 0x7c6cd6d1, 0xa06c03b7, 0x03fa2bfb, + 0x3c31556e, 0x9fa77d22, 0xf2269109, 0x51b0b945, 0x6e7bc7d0, + 0xcdedef9c, 0x11ed3afa, 0xb27b12b6, 0x8db06c23, 0x2e26446f, + 0xeec0c0ae, 0x4d56e8e2, 0x729d9677, 0xd10bbe3b, 0x0d0b6b5d, + 0xae9d4311, 0x91563d84, 0x32c015c8, 0xcbea3247, 0x687c1a0b, + 0x57b7649e, 0xf4214cd2, 0x282199b4, 0x8bb7b1f8, 0xb47ccf6d, + 0x17eae721, 0xd70c63e0, 0x749a4bac, 0x4b513539, 0xe8c71d75, + 0x34c7c813, 0x9751e05f, 0xa89a9eca, 0x0b0cb686, 0x81bfd795, + 0x2229ffd9, 0x1de2814c, 0xbe74a900, 0x62747c66, 0xc1e2542a, + 0xfe292abf, 0x5dbf02f3, 0x9d598632, 0x3ecfae7e, 0x0104d0eb, + 0xa292f8a7, 0x7e922dc1, 0xdd04058d, 0xe2cf7b18, 0x41595354, + 0xb87374db, 0x1be55c97, 0x242e2202, 0x87b80a4e, 0x5bb8df28, + 0xf82ef764, 0xc7e589f1, 0x6473a1bd, 0xa495257c, 0x07030d30, + 0x38c873a5, 0x9b5e5be9, 0x475e8e8f, 0xe4c8a6c3, 0xdb03d856, + 0x7895f01a}, + {0x00000000, 0x2a283862, 0x545070c4, 0x7e7848a6, 0xa8a0e188, + 0x8288d9ea, 0xfcf0914c, 0xd6d8a92e, 0x8a30c551, 0xa018fd33, + 0xde60b595, 0xf4488df7, 0x229024d9, 0x08b81cbb, 0x76c0541d, + 0x5ce86c7f, 0xcf108ce3, 0xe538b481, 0x9b40fc27, 0xb168c445, + 0x67b06d6b, 0x4d985509, 0x33e01daf, 0x19c825cd, 0x452049b2, + 0x6f0871d0, 0x11703976, 0x3b580114, 0xed80a83a, 0xc7a89058, + 0xb9d0d8fe, 0x93f8e09c, 0x45501f87, 0x6f7827e5, 0x11006f43, + 0x3b285721, 0xedf0fe0f, 0xc7d8c66d, 0xb9a08ecb, 0x9388b6a9, + 0xcf60dad6, 0xe548e2b4, 0x9b30aa12, 0xb1189270, 0x67c03b5e, + 0x4de8033c, 0x33904b9a, 0x19b873f8, 0x8a409364, 0xa068ab06, + 0xde10e3a0, 0xf438dbc2, 0x22e072ec, 0x08c84a8e, 0x76b00228, + 0x5c983a4a, 0x00705635, 0x2a586e57, 0x542026f1, 0x7e081e93, + 0xa8d0b7bd, 0x82f88fdf, 0xfc80c779, 0xd6a8ff1b, 0x8aa03f0e, + 0xa088076c, 0xdef04fca, 0xf4d877a8, 0x2200de86, 0x0828e6e4, + 0x7650ae42, 0x5c789620, 0x0090fa5f, 0x2ab8c23d, 0x54c08a9b, + 0x7ee8b2f9, 0xa8301bd7, 0x821823b5, 0xfc606b13, 0xd6485371, + 0x45b0b3ed, 0x6f988b8f, 0x11e0c329, 0x3bc8fb4b, 0xed105265, + 0xc7386a07, 0xb94022a1, 0x93681ac3, 0xcf8076bc, 0xe5a84ede, + 0x9bd00678, 0xb1f83e1a, 0x67209734, 0x4d08af56, 0x3370e7f0, + 0x1958df92, 0xcff02089, 0xe5d818eb, 0x9ba0504d, 0xb188682f, + 0x6750c101, 0x4d78f963, 0x3300b1c5, 0x192889a7, 0x45c0e5d8, + 0x6fe8ddba, 0x1190951c, 0x3bb8ad7e, 0xed600450, 0xc7483c32, + 0xb9307494, 0x93184cf6, 0x00e0ac6a, 0x2ac89408, 0x54b0dcae, + 0x7e98e4cc, 0xa8404de2, 0x82687580, 0xfc103d26, 0xd6380544, + 0x8ad0693b, 0xa0f85159, 0xde8019ff, 0xf4a8219d, 0x227088b3, + 0x0858b0d1, 0x7620f877, 0x5c08c015, 0xce31785d, 0xe419403f, + 0x9a610899, 0xb04930fb, 0x669199d5, 0x4cb9a1b7, 0x32c1e911, + 0x18e9d173, 0x4401bd0c, 0x6e29856e, 0x1051cdc8, 0x3a79f5aa, + 0xeca15c84, 0xc68964e6, 0xb8f12c40, 0x92d91422, 0x0121f4be, + 0x2b09ccdc, 0x5571847a, 0x7f59bc18, 0xa9811536, 0x83a92d54, + 0xfdd165f2, 0xd7f95d90, 0x8b1131ef, 0xa139098d, 0xdf41412b, + 0xf5697949, 0x23b1d067, 0x0999e805, 0x77e1a0a3, 0x5dc998c1, + 0x8b6167da, 0xa1495fb8, 0xdf31171e, 0xf5192f7c, 0x23c18652, + 0x09e9be30, 0x7791f696, 0x5db9cef4, 0x0151a28b, 0x2b799ae9, + 0x5501d24f, 0x7f29ea2d, 0xa9f14303, 0x83d97b61, 0xfda133c7, + 0xd7890ba5, 0x4471eb39, 0x6e59d35b, 0x10219bfd, 0x3a09a39f, + 0xecd10ab1, 0xc6f932d3, 0xb8817a75, 0x92a94217, 0xce412e68, + 0xe469160a, 0x9a115eac, 0xb03966ce, 0x66e1cfe0, 0x4cc9f782, + 0x32b1bf24, 0x18998746, 0x44914753, 0x6eb97f31, 0x10c13797, + 0x3ae90ff5, 0xec31a6db, 0xc6199eb9, 0xb861d61f, 0x9249ee7d, + 0xcea18202, 0xe489ba60, 0x9af1f2c6, 0xb0d9caa4, 0x6601638a, + 0x4c295be8, 0x3251134e, 0x18792b2c, 0x8b81cbb0, 0xa1a9f3d2, + 0xdfd1bb74, 0xf5f98316, 0x23212a38, 0x0909125a, 0x77715afc, + 0x5d59629e, 0x01b10ee1, 0x2b993683, 0x55e17e25, 0x7fc94647, + 0xa911ef69, 0x8339d70b, 0xfd419fad, 0xd769a7cf, 0x01c158d4, + 0x2be960b6, 0x55912810, 0x7fb91072, 0xa961b95c, 0x8349813e, + 0xfd31c998, 0xd719f1fa, 0x8bf19d85, 0xa1d9a5e7, 0xdfa1ed41, + 0xf589d523, 0x23517c0d, 0x0979446f, 0x77010cc9, 0x5d2934ab, + 0xced1d437, 0xe4f9ec55, 0x9a81a4f3, 0xb0a99c91, 0x667135bf, + 0x4c590ddd, 0x3221457b, 0x18097d19, 0x44e11166, 0x6ec92904, + 0x10b161a2, 0x3a9959c0, 0xec41f0ee, 0xc669c88c, 0xb811802a, + 0x9239b848}, + {0x00000000, 0x4713f6fb, 0x8e27edf6, 0xc9341b0d, 0xc73eddad, + 0x802d2b56, 0x4919305b, 0x0e0ac6a0, 0x550cbd1b, 0x121f4be0, + 0xdb2b50ed, 0x9c38a616, 0x923260b6, 0xd521964d, 0x1c158d40, + 0x5b067bbb, 0xaa197a36, 0xed0a8ccd, 0x243e97c0, 0x632d613b, + 0x6d27a79b, 0x2a345160, 0xe3004a6d, 0xa413bc96, 0xff15c72d, + 0xb80631d6, 0x71322adb, 0x3621dc20, 0x382b1a80, 0x7f38ec7b, + 0xb60cf776, 0xf11f018d, 0x8f43f22d, 0xc85004d6, 0x01641fdb, + 0x4677e920, 0x487d2f80, 0x0f6ed97b, 0xc65ac276, 0x8149348d, + 0xda4f4f36, 0x9d5cb9cd, 0x5468a2c0, 0x137b543b, 0x1d71929b, + 0x5a626460, 0x93567f6d, 0xd4458996, 0x255a881b, 0x62497ee0, + 0xab7d65ed, 0xec6e9316, 0xe26455b6, 0xa577a34d, 0x6c43b840, + 0x2b504ebb, 0x70563500, 0x3745c3fb, 0xfe71d8f6, 0xb9622e0d, + 0xb768e8ad, 0xf07b1e56, 0x394f055b, 0x7e5cf3a0, 0xc5f6e21b, + 0x82e514e0, 0x4bd10fed, 0x0cc2f916, 0x02c83fb6, 0x45dbc94d, + 0x8cefd240, 0xcbfc24bb, 0x90fa5f00, 0xd7e9a9fb, 0x1eddb2f6, + 0x59ce440d, 0x57c482ad, 0x10d77456, 0xd9e36f5b, 0x9ef099a0, + 0x6fef982d, 0x28fc6ed6, 0xe1c875db, 0xa6db8320, 0xa8d14580, + 0xefc2b37b, 0x26f6a876, 0x61e55e8d, 0x3ae32536, 0x7df0d3cd, + 0xb4c4c8c0, 0xf3d73e3b, 0xfdddf89b, 0xbace0e60, 0x73fa156d, + 0x34e9e396, 0x4ab51036, 0x0da6e6cd, 0xc492fdc0, 0x83810b3b, + 0x8d8bcd9b, 0xca983b60, 0x03ac206d, 0x44bfd696, 0x1fb9ad2d, + 0x58aa5bd6, 0x919e40db, 0xd68db620, 0xd8877080, 0x9f94867b, + 0x56a09d76, 0x11b36b8d, 0xe0ac6a00, 0xa7bf9cfb, 0x6e8b87f6, + 0x2998710d, 0x2792b7ad, 0x60814156, 0xa9b55a5b, 0xeea6aca0, + 0xb5a0d71b, 0xf2b321e0, 0x3b873aed, 0x7c94cc16, 0x729e0ab6, + 0x358dfc4d, 0xfcb9e740, 0xbbaa11bb, 0x509cc277, 0x178f348c, + 0xdebb2f81, 0x99a8d97a, 0x97a21fda, 0xd0b1e921, 0x1985f22c, + 0x5e9604d7, 0x05907f6c, 0x42838997, 0x8bb7929a, 0xcca46461, + 0xc2aea2c1, 0x85bd543a, 0x4c894f37, 0x0b9ab9cc, 0xfa85b841, + 0xbd964eba, 0x74a255b7, 0x33b1a34c, 0x3dbb65ec, 0x7aa89317, + 0xb39c881a, 0xf48f7ee1, 0xaf89055a, 0xe89af3a1, 0x21aee8ac, + 0x66bd1e57, 0x68b7d8f7, 0x2fa42e0c, 0xe6903501, 0xa183c3fa, + 0xdfdf305a, 0x98ccc6a1, 0x51f8ddac, 0x16eb2b57, 0x18e1edf7, + 0x5ff21b0c, 0x96c60001, 0xd1d5f6fa, 0x8ad38d41, 0xcdc07bba, + 0x04f460b7, 0x43e7964c, 0x4ded50ec, 0x0afea617, 0xc3cabd1a, + 0x84d94be1, 0x75c64a6c, 0x32d5bc97, 0xfbe1a79a, 0xbcf25161, + 0xb2f897c1, 0xf5eb613a, 0x3cdf7a37, 0x7bcc8ccc, 0x20caf777, + 0x67d9018c, 0xaeed1a81, 0xe9feec7a, 0xe7f42ada, 0xa0e7dc21, + 0x69d3c72c, 0x2ec031d7, 0x956a206c, 0xd279d697, 0x1b4dcd9a, + 0x5c5e3b61, 0x5254fdc1, 0x15470b3a, 0xdc731037, 0x9b60e6cc, + 0xc0669d77, 0x87756b8c, 0x4e417081, 0x0952867a, 0x075840da, + 0x404bb621, 0x897fad2c, 0xce6c5bd7, 0x3f735a5a, 0x7860aca1, + 0xb154b7ac, 0xf6474157, 0xf84d87f7, 0xbf5e710c, 0x766a6a01, + 0x31799cfa, 0x6a7fe741, 0x2d6c11ba, 0xe4580ab7, 0xa34bfc4c, + 0xad413aec, 0xea52cc17, 0x2366d71a, 0x647521e1, 0x1a29d241, + 0x5d3a24ba, 0x940e3fb7, 0xd31dc94c, 0xdd170fec, 0x9a04f917, + 0x5330e21a, 0x142314e1, 0x4f256f5a, 0x083699a1, 0xc10282ac, + 0x86117457, 0x881bb2f7, 0xcf08440c, 0x063c5f01, 0x412fa9fa, + 0xb030a877, 0xf7235e8c, 0x3e174581, 0x7904b37a, 0x770e75da, + 0x301d8321, 0xf929982c, 0xbe3a6ed7, 0xe53c156c, 0xa22fe397, + 0x6b1bf89a, 0x2c080e61, 0x2202c8c1, 0x65113e3a, 0xac252537, + 0xeb36d3cc}, + {0x00000000, 0xa13984ee, 0x99020f9d, 0x383b8b73, 0xe975197b, + 0x484c9d95, 0x707716e6, 0xd14e9208, 0x099b34b7, 0xa8a2b059, + 0x90993b2a, 0x31a0bfc4, 0xe0ee2dcc, 0x41d7a922, 0x79ec2251, + 0xd8d5a6bf, 0x1336696e, 0xb20fed80, 0x8a3466f3, 0x2b0de21d, + 0xfa437015, 0x5b7af4fb, 0x63417f88, 0xc278fb66, 0x1aad5dd9, + 0xbb94d937, 0x83af5244, 0x2296d6aa, 0xf3d844a2, 0x52e1c04c, + 0x6ada4b3f, 0xcbe3cfd1, 0x266cd2dc, 0x87555632, 0xbf6edd41, + 0x1e5759af, 0xcf19cba7, 0x6e204f49, 0x561bc43a, 0xf72240d4, + 0x2ff7e66b, 0x8ece6285, 0xb6f5e9f6, 0x17cc6d18, 0xc682ff10, + 0x67bb7bfe, 0x5f80f08d, 0xfeb97463, 0x355abbb2, 0x94633f5c, + 0xac58b42f, 0x0d6130c1, 0xdc2fa2c9, 0x7d162627, 0x452dad54, + 0xe41429ba, 0x3cc18f05, 0x9df80beb, 0xa5c38098, 0x04fa0476, + 0xd5b4967e, 0x748d1290, 0x4cb699e3, 0xed8f1d0d, 0x4cd9a5b8, + 0xede02156, 0xd5dbaa25, 0x74e22ecb, 0xa5acbcc3, 0x0495382d, + 0x3caeb35e, 0x9d9737b0, 0x4542910f, 0xe47b15e1, 0xdc409e92, + 0x7d791a7c, 0xac378874, 0x0d0e0c9a, 0x353587e9, 0x940c0307, + 0x5fefccd6, 0xfed64838, 0xc6edc34b, 0x67d447a5, 0xb69ad5ad, + 0x17a35143, 0x2f98da30, 0x8ea15ede, 0x5674f861, 0xf74d7c8f, + 0xcf76f7fc, 0x6e4f7312, 0xbf01e11a, 0x1e3865f4, 0x2603ee87, + 0x873a6a69, 0x6ab57764, 0xcb8cf38a, 0xf3b778f9, 0x528efc17, + 0x83c06e1f, 0x22f9eaf1, 0x1ac26182, 0xbbfbe56c, 0x632e43d3, + 0xc217c73d, 0xfa2c4c4e, 0x5b15c8a0, 0x8a5b5aa8, 0x2b62de46, + 0x13595535, 0xb260d1db, 0x79831e0a, 0xd8ba9ae4, 0xe0811197, + 0x41b89579, 0x90f60771, 0x31cf839f, 0x09f408ec, 0xa8cd8c02, + 0x70182abd, 0xd121ae53, 0xe91a2520, 0x4823a1ce, 0x996d33c6, + 0x3854b728, 0x006f3c5b, 0xa156b8b5, 0x99b34b70, 0x388acf9e, + 0x00b144ed, 0xa188c003, 0x70c6520b, 0xd1ffd6e5, 0xe9c45d96, + 0x48fdd978, 0x90287fc7, 0x3111fb29, 0x092a705a, 0xa813f4b4, + 0x795d66bc, 0xd864e252, 0xe05f6921, 0x4166edcf, 0x8a85221e, + 0x2bbca6f0, 0x13872d83, 0xb2bea96d, 0x63f03b65, 0xc2c9bf8b, + 0xfaf234f8, 0x5bcbb016, 0x831e16a9, 0x22279247, 0x1a1c1934, + 0xbb259dda, 0x6a6b0fd2, 0xcb528b3c, 0xf369004f, 0x525084a1, + 0xbfdf99ac, 0x1ee61d42, 0x26dd9631, 0x87e412df, 0x56aa80d7, + 0xf7930439, 0xcfa88f4a, 0x6e910ba4, 0xb644ad1b, 0x177d29f5, + 0x2f46a286, 0x8e7f2668, 0x5f31b460, 0xfe08308e, 0xc633bbfd, + 0x670a3f13, 0xace9f0c2, 0x0dd0742c, 0x35ebff5f, 0x94d27bb1, + 0x459ce9b9, 0xe4a56d57, 0xdc9ee624, 0x7da762ca, 0xa572c475, + 0x044b409b, 0x3c70cbe8, 0x9d494f06, 0x4c07dd0e, 0xed3e59e0, + 0xd505d293, 0x743c567d, 0xd56aeec8, 0x74536a26, 0x4c68e155, + 0xed5165bb, 0x3c1ff7b3, 0x9d26735d, 0xa51df82e, 0x04247cc0, + 0xdcf1da7f, 0x7dc85e91, 0x45f3d5e2, 0xe4ca510c, 0x3584c304, + 0x94bd47ea, 0xac86cc99, 0x0dbf4877, 0xc65c87a6, 0x67650348, + 0x5f5e883b, 0xfe670cd5, 0x2f299edd, 0x8e101a33, 0xb62b9140, + 0x171215ae, 0xcfc7b311, 0x6efe37ff, 0x56c5bc8c, 0xf7fc3862, + 0x26b2aa6a, 0x878b2e84, 0xbfb0a5f7, 0x1e892119, 0xf3063c14, + 0x523fb8fa, 0x6a043389, 0xcb3db767, 0x1a73256f, 0xbb4aa181, + 0x83712af2, 0x2248ae1c, 0xfa9d08a3, 0x5ba48c4d, 0x639f073e, + 0xc2a683d0, 0x13e811d8, 0xb2d19536, 0x8aea1e45, 0x2bd39aab, + 0xe030557a, 0x4109d194, 0x79325ae7, 0xd80bde09, 0x09454c01, + 0xa87cc8ef, 0x9047439c, 0x317ec772, 0xe9ab61cd, 0x4892e523, + 0x70a96e50, 0xd190eabe, 0x00de78b6, 0xa1e7fc58, 0x99dc772b, + 0x38e5f3c5}, + {0x00000000, 0xe81790a1, 0x0b5e2703, 0xe349b7a2, 0x16bc4e06, + 0xfeabdea7, 0x1de26905, 0xf5f5f9a4, 0x2d789c0c, 0xc56f0cad, + 0x2626bb0f, 0xce312bae, 0x3bc4d20a, 0xd3d342ab, 0x309af509, + 0xd88d65a8, 0x5af13818, 0xb2e6a8b9, 0x51af1f1b, 0xb9b88fba, + 0x4c4d761e, 0xa45ae6bf, 0x4713511d, 0xaf04c1bc, 0x7789a414, + 0x9f9e34b5, 0x7cd78317, 0x94c013b6, 0x6135ea12, 0x89227ab3, + 0x6a6bcd11, 0x827c5db0, 0xb5e27030, 0x5df5e091, 0xbebc5733, + 0x56abc792, 0xa35e3e36, 0x4b49ae97, 0xa8001935, 0x40178994, + 0x989aec3c, 0x708d7c9d, 0x93c4cb3f, 0x7bd35b9e, 0x8e26a23a, + 0x6631329b, 0x85788539, 0x6d6f1598, 0xef134828, 0x0704d889, + 0xe44d6f2b, 0x0c5aff8a, 0xf9af062e, 0x11b8968f, 0xf2f1212d, + 0x1ae6b18c, 0xc26bd424, 0x2a7c4485, 0xc935f327, 0x21226386, + 0xd4d79a22, 0x3cc00a83, 0xdf89bd21, 0x379e2d80, 0xb0b5e621, + 0x58a27680, 0xbbebc122, 0x53fc5183, 0xa609a827, 0x4e1e3886, + 0xad578f24, 0x45401f85, 0x9dcd7a2d, 0x75daea8c, 0x96935d2e, + 0x7e84cd8f, 0x8b71342b, 0x6366a48a, 0x802f1328, 0x68388389, + 0xea44de39, 0x02534e98, 0xe11af93a, 0x090d699b, 0xfcf8903f, + 0x14ef009e, 0xf7a6b73c, 0x1fb1279d, 0xc73c4235, 0x2f2bd294, + 0xcc626536, 0x2475f597, 0xd1800c33, 0x39979c92, 0xdade2b30, + 0x32c9bb91, 0x05579611, 0xed4006b0, 0x0e09b112, 0xe61e21b3, + 0x13ebd817, 0xfbfc48b6, 0x18b5ff14, 0xf0a26fb5, 0x282f0a1d, + 0xc0389abc, 0x23712d1e, 0xcb66bdbf, 0x3e93441b, 0xd684d4ba, + 0x35cd6318, 0xdddaf3b9, 0x5fa6ae09, 0xb7b13ea8, 0x54f8890a, + 0xbcef19ab, 0x491ae00f, 0xa10d70ae, 0x4244c70c, 0xaa5357ad, + 0x72de3205, 0x9ac9a2a4, 0x79801506, 0x919785a7, 0x64627c03, + 0x8c75eca2, 0x6f3c5b00, 0x872bcba1, 0xba1aca03, 0x520d5aa2, + 0xb144ed00, 0x59537da1, 0xaca68405, 0x44b114a4, 0xa7f8a306, + 0x4fef33a7, 0x9762560f, 0x7f75c6ae, 0x9c3c710c, 0x742be1ad, + 0x81de1809, 0x69c988a8, 0x8a803f0a, 0x6297afab, 0xe0ebf21b, + 0x08fc62ba, 0xebb5d518, 0x03a245b9, 0xf657bc1d, 0x1e402cbc, + 0xfd099b1e, 0x151e0bbf, 0xcd936e17, 0x2584feb6, 0xc6cd4914, + 0x2edad9b5, 0xdb2f2011, 0x3338b0b0, 0xd0710712, 0x386697b3, + 0x0ff8ba33, 0xe7ef2a92, 0x04a69d30, 0xecb10d91, 0x1944f435, + 0xf1536494, 0x121ad336, 0xfa0d4397, 0x2280263f, 0xca97b69e, + 0x29de013c, 0xc1c9919d, 0x343c6839, 0xdc2bf898, 0x3f624f3a, + 0xd775df9b, 0x5509822b, 0xbd1e128a, 0x5e57a528, 0xb6403589, + 0x43b5cc2d, 0xaba25c8c, 0x48ebeb2e, 0xa0fc7b8f, 0x78711e27, + 0x90668e86, 0x732f3924, 0x9b38a985, 0x6ecd5021, 0x86dac080, + 0x65937722, 0x8d84e783, 0x0aaf2c22, 0xe2b8bc83, 0x01f10b21, + 0xe9e69b80, 0x1c136224, 0xf404f285, 0x174d4527, 0xff5ad586, + 0x27d7b02e, 0xcfc0208f, 0x2c89972d, 0xc49e078c, 0x316bfe28, + 0xd97c6e89, 0x3a35d92b, 0xd222498a, 0x505e143a, 0xb849849b, + 0x5b003339, 0xb317a398, 0x46e25a3c, 0xaef5ca9d, 0x4dbc7d3f, + 0xa5abed9e, 0x7d268836, 0x95311897, 0x7678af35, 0x9e6f3f94, + 0x6b9ac630, 0x838d5691, 0x60c4e133, 0x88d37192, 0xbf4d5c12, + 0x575accb3, 0xb4137b11, 0x5c04ebb0, 0xa9f11214, 0x41e682b5, + 0xa2af3517, 0x4ab8a5b6, 0x9235c01e, 0x7a2250bf, 0x996be71d, + 0x717c77bc, 0x84898e18, 0x6c9e1eb9, 0x8fd7a91b, 0x67c039ba, + 0xe5bc640a, 0x0dabf4ab, 0xeee24309, 0x06f5d3a8, 0xf3002a0c, + 0x1b17baad, 0xf85e0d0f, 0x10499dae, 0xc8c4f806, 0x20d368a7, + 0xc39adf05, 0x2b8d4fa4, 0xde78b600, 0x366f26a1, 0xd5269103, + 0x3d3101a2}}; + +local const z_word_t FAR crc_braid_big_table[][256] = { + {0x0000000000000000, 0xa19017e800000000, 0x03275e0b00000000, + 0xa2b749e300000000, 0x064ebc1600000000, 0xa7deabfe00000000, + 0x0569e21d00000000, 0xa4f9f5f500000000, 0x0c9c782d00000000, + 0xad0c6fc500000000, 0x0fbb262600000000, 0xae2b31ce00000000, + 0x0ad2c43b00000000, 0xab42d3d300000000, 0x09f59a3000000000, + 0xa8658dd800000000, 0x1838f15a00000000, 0xb9a8e6b200000000, + 0x1b1faf5100000000, 0xba8fb8b900000000, 0x1e764d4c00000000, + 0xbfe65aa400000000, 0x1d51134700000000, 0xbcc104af00000000, + 0x14a4897700000000, 0xb5349e9f00000000, 0x1783d77c00000000, + 0xb613c09400000000, 0x12ea356100000000, 0xb37a228900000000, + 0x11cd6b6a00000000, 0xb05d7c8200000000, 0x3070e2b500000000, + 0x91e0f55d00000000, 0x3357bcbe00000000, 0x92c7ab5600000000, + 0x363e5ea300000000, 0x97ae494b00000000, 0x351900a800000000, + 0x9489174000000000, 0x3cec9a9800000000, 0x9d7c8d7000000000, + 0x3fcbc49300000000, 0x9e5bd37b00000000, 0x3aa2268e00000000, + 0x9b32316600000000, 0x3985788500000000, 0x98156f6d00000000, + 0x284813ef00000000, 0x89d8040700000000, 0x2b6f4de400000000, + 0x8aff5a0c00000000, 0x2e06aff900000000, 0x8f96b81100000000, + 0x2d21f1f200000000, 0x8cb1e61a00000000, 0x24d46bc200000000, + 0x85447c2a00000000, 0x27f335c900000000, 0x8663222100000000, + 0x229ad7d400000000, 0x830ac03c00000000, 0x21bd89df00000000, + 0x802d9e3700000000, 0x21e6b5b000000000, 0x8076a25800000000, + 0x22c1ebbb00000000, 0x8351fc5300000000, 0x27a809a600000000, + 0x86381e4e00000000, 0x248f57ad00000000, 0x851f404500000000, + 0x2d7acd9d00000000, 0x8ceada7500000000, 0x2e5d939600000000, + 0x8fcd847e00000000, 0x2b34718b00000000, 0x8aa4666300000000, + 0x28132f8000000000, 0x8983386800000000, 0x39de44ea00000000, + 0x984e530200000000, 0x3af91ae100000000, 0x9b690d0900000000, + 0x3f90f8fc00000000, 0x9e00ef1400000000, 0x3cb7a6f700000000, + 0x9d27b11f00000000, 0x35423cc700000000, 0x94d22b2f00000000, + 0x366562cc00000000, 0x97f5752400000000, 0x330c80d100000000, + 0x929c973900000000, 0x302bdeda00000000, 0x91bbc93200000000, + 0x1196570500000000, 0xb00640ed00000000, 0x12b1090e00000000, + 0xb3211ee600000000, 0x17d8eb1300000000, 0xb648fcfb00000000, + 0x14ffb51800000000, 0xb56fa2f000000000, 0x1d0a2f2800000000, + 0xbc9a38c000000000, 0x1e2d712300000000, 0xbfbd66cb00000000, + 0x1b44933e00000000, 0xbad484d600000000, 0x1863cd3500000000, + 0xb9f3dadd00000000, 0x09aea65f00000000, 0xa83eb1b700000000, + 0x0a89f85400000000, 0xab19efbc00000000, 0x0fe01a4900000000, + 0xae700da100000000, 0x0cc7444200000000, 0xad5753aa00000000, + 0x0532de7200000000, 0xa4a2c99a00000000, 0x0615807900000000, + 0xa785979100000000, 0x037c626400000000, 0xa2ec758c00000000, + 0x005b3c6f00000000, 0xa1cb2b8700000000, 0x03ca1aba00000000, + 0xa25a0d5200000000, 0x00ed44b100000000, 0xa17d535900000000, + 0x0584a6ac00000000, 0xa414b14400000000, 0x06a3f8a700000000, + 0xa733ef4f00000000, 0x0f56629700000000, 0xaec6757f00000000, + 0x0c713c9c00000000, 0xade12b7400000000, 0x0918de8100000000, + 0xa888c96900000000, 0x0a3f808a00000000, 0xabaf976200000000, + 0x1bf2ebe000000000, 0xba62fc0800000000, 0x18d5b5eb00000000, + 0xb945a20300000000, 0x1dbc57f600000000, 0xbc2c401e00000000, + 0x1e9b09fd00000000, 0xbf0b1e1500000000, 0x176e93cd00000000, + 0xb6fe842500000000, 0x1449cdc600000000, 0xb5d9da2e00000000, + 0x11202fdb00000000, 0xb0b0383300000000, 0x120771d000000000, + 0xb397663800000000, 0x33baf80f00000000, 0x922aefe700000000, + 0x309da60400000000, 0x910db1ec00000000, 0x35f4441900000000, + 0x946453f100000000, 0x36d31a1200000000, 0x97430dfa00000000, + 0x3f26802200000000, 0x9eb697ca00000000, 0x3c01de2900000000, + 0x9d91c9c100000000, 0x39683c3400000000, 0x98f82bdc00000000, + 0x3a4f623f00000000, 0x9bdf75d700000000, 0x2b82095500000000, + 0x8a121ebd00000000, 0x28a5575e00000000, 0x893540b600000000, + 0x2dccb54300000000, 0x8c5ca2ab00000000, 0x2eebeb4800000000, + 0x8f7bfca000000000, 0x271e717800000000, 0x868e669000000000, + 0x24392f7300000000, 0x85a9389b00000000, 0x2150cd6e00000000, + 0x80c0da8600000000, 0x2277936500000000, 0x83e7848d00000000, + 0x222caf0a00000000, 0x83bcb8e200000000, 0x210bf10100000000, + 0x809be6e900000000, 0x2462131c00000000, 0x85f204f400000000, + 0x27454d1700000000, 0x86d55aff00000000, 0x2eb0d72700000000, + 0x8f20c0cf00000000, 0x2d97892c00000000, 0x8c079ec400000000, + 0x28fe6b3100000000, 0x896e7cd900000000, 0x2bd9353a00000000, + 0x8a4922d200000000, 0x3a145e5000000000, 0x9b8449b800000000, + 0x3933005b00000000, 0x98a317b300000000, 0x3c5ae24600000000, + 0x9dcaf5ae00000000, 0x3f7dbc4d00000000, 0x9eedaba500000000, + 0x3688267d00000000, 0x9718319500000000, 0x35af787600000000, + 0x943f6f9e00000000, 0x30c69a6b00000000, 0x91568d8300000000, + 0x33e1c46000000000, 0x9271d38800000000, 0x125c4dbf00000000, + 0xb3cc5a5700000000, 0x117b13b400000000, 0xb0eb045c00000000, + 0x1412f1a900000000, 0xb582e64100000000, 0x1735afa200000000, + 0xb6a5b84a00000000, 0x1ec0359200000000, 0xbf50227a00000000, + 0x1de76b9900000000, 0xbc777c7100000000, 0x188e898400000000, + 0xb91e9e6c00000000, 0x1ba9d78f00000000, 0xba39c06700000000, + 0x0a64bce500000000, 0xabf4ab0d00000000, 0x0943e2ee00000000, + 0xa8d3f50600000000, 0x0c2a00f300000000, 0xadba171b00000000, + 0x0f0d5ef800000000, 0xae9d491000000000, 0x06f8c4c800000000, + 0xa768d32000000000, 0x05df9ac300000000, 0xa44f8d2b00000000, + 0x00b678de00000000, 0xa1266f3600000000, 0x039126d500000000, + 0xa201313d00000000}, + {0x0000000000000000, 0xee8439a100000000, 0x9d0f029900000000, + 0x738b3b3800000000, 0x7b1975e900000000, 0x959d4c4800000000, + 0xe616777000000000, 0x08924ed100000000, 0xb7349b0900000000, + 0x59b0a2a800000000, 0x2a3b999000000000, 0xc4bfa03100000000, + 0xcc2deee000000000, 0x22a9d74100000000, 0x5122ec7900000000, + 0xbfa6d5d800000000, 0x6e69361300000000, 0x80ed0fb200000000, + 0xf366348a00000000, 0x1de20d2b00000000, 0x157043fa00000000, + 0xfbf47a5b00000000, 0x887f416300000000, 0x66fb78c200000000, + 0xd95dad1a00000000, 0x37d994bb00000000, 0x4452af8300000000, + 0xaad6962200000000, 0xa244d8f300000000, 0x4cc0e15200000000, + 0x3f4bda6a00000000, 0xd1cfe3cb00000000, 0xdcd26c2600000000, + 0x3256558700000000, 0x41dd6ebf00000000, 0xaf59571e00000000, + 0xa7cb19cf00000000, 0x494f206e00000000, 0x3ac41b5600000000, + 0xd44022f700000000, 0x6be6f72f00000000, 0x8562ce8e00000000, + 0xf6e9f5b600000000, 0x186dcc1700000000, 0x10ff82c600000000, + 0xfe7bbb6700000000, 0x8df0805f00000000, 0x6374b9fe00000000, + 0xb2bb5a3500000000, 0x5c3f639400000000, 0x2fb458ac00000000, + 0xc130610d00000000, 0xc9a22fdc00000000, 0x2726167d00000000, + 0x54ad2d4500000000, 0xba2914e400000000, 0x058fc13c00000000, + 0xeb0bf89d00000000, 0x9880c3a500000000, 0x7604fa0400000000, + 0x7e96b4d500000000, 0x90128d7400000000, 0xe399b64c00000000, + 0x0d1d8fed00000000, 0xb8a5d94c00000000, 0x5621e0ed00000000, + 0x25aadbd500000000, 0xcb2ee27400000000, 0xc3bcaca500000000, + 0x2d38950400000000, 0x5eb3ae3c00000000, 0xb037979d00000000, + 0x0f91424500000000, 0xe1157be400000000, 0x929e40dc00000000, + 0x7c1a797d00000000, 0x748837ac00000000, 0x9a0c0e0d00000000, + 0xe987353500000000, 0x07030c9400000000, 0xd6ccef5f00000000, + 0x3848d6fe00000000, 0x4bc3edc600000000, 0xa547d46700000000, + 0xadd59ab600000000, 0x4351a31700000000, 0x30da982f00000000, + 0xde5ea18e00000000, 0x61f8745600000000, 0x8f7c4df700000000, + 0xfcf776cf00000000, 0x12734f6e00000000, 0x1ae101bf00000000, + 0xf465381e00000000, 0x87ee032600000000, 0x696a3a8700000000, + 0x6477b56a00000000, 0x8af38ccb00000000, 0xf978b7f300000000, + 0x17fc8e5200000000, 0x1f6ec08300000000, 0xf1eaf92200000000, + 0x8261c21a00000000, 0x6ce5fbbb00000000, 0xd3432e6300000000, + 0x3dc717c200000000, 0x4e4c2cfa00000000, 0xa0c8155b00000000, + 0xa85a5b8a00000000, 0x46de622b00000000, 0x3555591300000000, + 0xdbd160b200000000, 0x0a1e837900000000, 0xe49abad800000000, + 0x971181e000000000, 0x7995b84100000000, 0x7107f69000000000, + 0x9f83cf3100000000, 0xec08f40900000000, 0x028ccda800000000, + 0xbd2a187000000000, 0x53ae21d100000000, 0x20251ae900000000, + 0xcea1234800000000, 0xc6336d9900000000, 0x28b7543800000000, + 0x5b3c6f0000000000, 0xb5b856a100000000, 0x704bb39900000000, + 0x9ecf8a3800000000, 0xed44b10000000000, 0x03c088a100000000, + 0x0b52c67000000000, 0xe5d6ffd100000000, 0x965dc4e900000000, + 0x78d9fd4800000000, 0xc77f289000000000, 0x29fb113100000000, + 0x5a702a0900000000, 0xb4f413a800000000, 0xbc665d7900000000, + 0x52e264d800000000, 0x21695fe000000000, 0xcfed664100000000, + 0x1e22858a00000000, 0xf0a6bc2b00000000, 0x832d871300000000, + 0x6da9beb200000000, 0x653bf06300000000, 0x8bbfc9c200000000, + 0xf834f2fa00000000, 0x16b0cb5b00000000, 0xa9161e8300000000, + 0x4792272200000000, 0x34191c1a00000000, 0xda9d25bb00000000, + 0xd20f6b6a00000000, 0x3c8b52cb00000000, 0x4f0069f300000000, + 0xa184505200000000, 0xac99dfbf00000000, 0x421de61e00000000, + 0x3196dd2600000000, 0xdf12e48700000000, 0xd780aa5600000000, + 0x390493f700000000, 0x4a8fa8cf00000000, 0xa40b916e00000000, + 0x1bad44b600000000, 0xf5297d1700000000, 0x86a2462f00000000, + 0x68267f8e00000000, 0x60b4315f00000000, 0x8e3008fe00000000, + 0xfdbb33c600000000, 0x133f0a6700000000, 0xc2f0e9ac00000000, + 0x2c74d00d00000000, 0x5fffeb3500000000, 0xb17bd29400000000, + 0xb9e99c4500000000, 0x576da5e400000000, 0x24e69edc00000000, + 0xca62a77d00000000, 0x75c472a500000000, 0x9b404b0400000000, + 0xe8cb703c00000000, 0x064f499d00000000, 0x0edd074c00000000, + 0xe0593eed00000000, 0x93d205d500000000, 0x7d563c7400000000, + 0xc8ee6ad500000000, 0x266a537400000000, 0x55e1684c00000000, + 0xbb6551ed00000000, 0xb3f71f3c00000000, 0x5d73269d00000000, + 0x2ef81da500000000, 0xc07c240400000000, 0x7fdaf1dc00000000, + 0x915ec87d00000000, 0xe2d5f34500000000, 0x0c51cae400000000, + 0x04c3843500000000, 0xea47bd9400000000, 0x99cc86ac00000000, + 0x7748bf0d00000000, 0xa6875cc600000000, 0x4803656700000000, + 0x3b885e5f00000000, 0xd50c67fe00000000, 0xdd9e292f00000000, + 0x331a108e00000000, 0x40912bb600000000, 0xae15121700000000, + 0x11b3c7cf00000000, 0xff37fe6e00000000, 0x8cbcc55600000000, + 0x6238fcf700000000, 0x6aaab22600000000, 0x842e8b8700000000, + 0xf7a5b0bf00000000, 0x1921891e00000000, 0x143c06f300000000, + 0xfab83f5200000000, 0x8933046a00000000, 0x67b73dcb00000000, + 0x6f25731a00000000, 0x81a14abb00000000, 0xf22a718300000000, + 0x1cae482200000000, 0xa3089dfa00000000, 0x4d8ca45b00000000, + 0x3e079f6300000000, 0xd083a6c200000000, 0xd811e81300000000, + 0x3695d1b200000000, 0x451eea8a00000000, 0xab9ad32b00000000, + 0x7a5530e000000000, 0x94d1094100000000, 0xe75a327900000000, + 0x09de0bd800000000, 0x014c450900000000, 0xefc87ca800000000, + 0x9c43479000000000, 0x72c77e3100000000, 0xcd61abe900000000, + 0x23e5924800000000, 0x506ea97000000000, 0xbeea90d100000000, + 0xb678de0000000000, 0x58fce7a100000000, 0x2b77dc9900000000, + 0xc5f3e53800000000}, + {0x0000000000000000, 0xfbf6134700000000, 0xf6ed278e00000000, + 0x0d1b34c900000000, 0xaddd3ec700000000, 0x562b2d8000000000, + 0x5b30194900000000, 0xa0c60a0e00000000, 0x1bbd0c5500000000, + 0xe04b1f1200000000, 0xed502bdb00000000, 0x16a6389c00000000, + 0xb660329200000000, 0x4d9621d500000000, 0x408d151c00000000, + 0xbb7b065b00000000, 0x367a19aa00000000, 0xcd8c0aed00000000, + 0xc0973e2400000000, 0x3b612d6300000000, 0x9ba7276d00000000, + 0x6051342a00000000, 0x6d4a00e300000000, 0x96bc13a400000000, + 0x2dc715ff00000000, 0xd63106b800000000, 0xdb2a327100000000, + 0x20dc213600000000, 0x801a2b3800000000, 0x7bec387f00000000, + 0x76f70cb600000000, 0x8d011ff100000000, 0x2df2438f00000000, + 0xd60450c800000000, 0xdb1f640100000000, 0x20e9774600000000, + 0x802f7d4800000000, 0x7bd96e0f00000000, 0x76c25ac600000000, + 0x8d34498100000000, 0x364f4fda00000000, 0xcdb95c9d00000000, + 0xc0a2685400000000, 0x3b547b1300000000, 0x9b92711d00000000, + 0x6064625a00000000, 0x6d7f569300000000, 0x968945d400000000, + 0x1b885a2500000000, 0xe07e496200000000, 0xed657dab00000000, + 0x16936eec00000000, 0xb65564e200000000, 0x4da377a500000000, + 0x40b8436c00000000, 0xbb4e502b00000000, 0x0035567000000000, + 0xfbc3453700000000, 0xf6d871fe00000000, 0x0d2e62b900000000, + 0xade868b700000000, 0x561e7bf000000000, 0x5b054f3900000000, + 0xa0f35c7e00000000, 0x1be2f6c500000000, 0xe014e58200000000, + 0xed0fd14b00000000, 0x16f9c20c00000000, 0xb63fc80200000000, + 0x4dc9db4500000000, 0x40d2ef8c00000000, 0xbb24fccb00000000, + 0x005ffa9000000000, 0xfba9e9d700000000, 0xf6b2dd1e00000000, + 0x0d44ce5900000000, 0xad82c45700000000, 0x5674d71000000000, + 0x5b6fe3d900000000, 0xa099f09e00000000, 0x2d98ef6f00000000, + 0xd66efc2800000000, 0xdb75c8e100000000, 0x2083dba600000000, + 0x8045d1a800000000, 0x7bb3c2ef00000000, 0x76a8f62600000000, + 0x8d5ee56100000000, 0x3625e33a00000000, 0xcdd3f07d00000000, + 0xc0c8c4b400000000, 0x3b3ed7f300000000, 0x9bf8ddfd00000000, + 0x600eceba00000000, 0x6d15fa7300000000, 0x96e3e93400000000, + 0x3610b54a00000000, 0xcde6a60d00000000, 0xc0fd92c400000000, + 0x3b0b818300000000, 0x9bcd8b8d00000000, 0x603b98ca00000000, + 0x6d20ac0300000000, 0x96d6bf4400000000, 0x2dadb91f00000000, + 0xd65baa5800000000, 0xdb409e9100000000, 0x20b68dd600000000, + 0x807087d800000000, 0x7b86949f00000000, 0x769da05600000000, + 0x8d6bb31100000000, 0x006aace000000000, 0xfb9cbfa700000000, + 0xf6878b6e00000000, 0x0d71982900000000, 0xadb7922700000000, + 0x5641816000000000, 0x5b5ab5a900000000, 0xa0aca6ee00000000, + 0x1bd7a0b500000000, 0xe021b3f200000000, 0xed3a873b00000000, + 0x16cc947c00000000, 0xb60a9e7200000000, 0x4dfc8d3500000000, + 0x40e7b9fc00000000, 0xbb11aabb00000000, 0x77c29c5000000000, + 0x8c348f1700000000, 0x812fbbde00000000, 0x7ad9a89900000000, + 0xda1fa29700000000, 0x21e9b1d000000000, 0x2cf2851900000000, + 0xd704965e00000000, 0x6c7f900500000000, 0x9789834200000000, + 0x9a92b78b00000000, 0x6164a4cc00000000, 0xc1a2aec200000000, + 0x3a54bd8500000000, 0x374f894c00000000, 0xccb99a0b00000000, + 0x41b885fa00000000, 0xba4e96bd00000000, 0xb755a27400000000, + 0x4ca3b13300000000, 0xec65bb3d00000000, 0x1793a87a00000000, + 0x1a889cb300000000, 0xe17e8ff400000000, 0x5a0589af00000000, + 0xa1f39ae800000000, 0xace8ae2100000000, 0x571ebd6600000000, + 0xf7d8b76800000000, 0x0c2ea42f00000000, 0x013590e600000000, + 0xfac383a100000000, 0x5a30dfdf00000000, 0xa1c6cc9800000000, + 0xacddf85100000000, 0x572beb1600000000, 0xf7ede11800000000, + 0x0c1bf25f00000000, 0x0100c69600000000, 0xfaf6d5d100000000, + 0x418dd38a00000000, 0xba7bc0cd00000000, 0xb760f40400000000, + 0x4c96e74300000000, 0xec50ed4d00000000, 0x17a6fe0a00000000, + 0x1abdcac300000000, 0xe14bd98400000000, 0x6c4ac67500000000, + 0x97bcd53200000000, 0x9aa7e1fb00000000, 0x6151f2bc00000000, + 0xc197f8b200000000, 0x3a61ebf500000000, 0x377adf3c00000000, + 0xcc8ccc7b00000000, 0x77f7ca2000000000, 0x8c01d96700000000, + 0x811aedae00000000, 0x7aecfee900000000, 0xda2af4e700000000, + 0x21dce7a000000000, 0x2cc7d36900000000, 0xd731c02e00000000, + 0x6c206a9500000000, 0x97d679d200000000, 0x9acd4d1b00000000, + 0x613b5e5c00000000, 0xc1fd545200000000, 0x3a0b471500000000, + 0x371073dc00000000, 0xcce6609b00000000, 0x779d66c000000000, + 0x8c6b758700000000, 0x8170414e00000000, 0x7a86520900000000, + 0xda40580700000000, 0x21b64b4000000000, 0x2cad7f8900000000, + 0xd75b6cce00000000, 0x5a5a733f00000000, 0xa1ac607800000000, + 0xacb754b100000000, 0x574147f600000000, 0xf7874df800000000, + 0x0c715ebf00000000, 0x016a6a7600000000, 0xfa9c793100000000, + 0x41e77f6a00000000, 0xba116c2d00000000, 0xb70a58e400000000, + 0x4cfc4ba300000000, 0xec3a41ad00000000, 0x17cc52ea00000000, + 0x1ad7662300000000, 0xe121756400000000, 0x41d2291a00000000, + 0xba243a5d00000000, 0xb73f0e9400000000, 0x4cc91dd300000000, + 0xec0f17dd00000000, 0x17f9049a00000000, 0x1ae2305300000000, + 0xe114231400000000, 0x5a6f254f00000000, 0xa199360800000000, + 0xac8202c100000000, 0x5774118600000000, 0xf7b21b8800000000, + 0x0c4408cf00000000, 0x015f3c0600000000, 0xfaa92f4100000000, + 0x77a830b000000000, 0x8c5e23f700000000, 0x8145173e00000000, + 0x7ab3047900000000, 0xda750e7700000000, 0x21831d3000000000, + 0x2c9829f900000000, 0xd76e3abe00000000, 0x6c153ce500000000, + 0x97e32fa200000000, 0x9af81b6b00000000, 0x610e082c00000000, + 0xc1c8022200000000, 0x3a3e116500000000, 0x372525ac00000000, + 0xccd336eb00000000}, + {0x0000000000000000, 0x6238282a00000000, 0xc470505400000000, + 0xa648787e00000000, 0x88e1a0a800000000, 0xead9888200000000, + 0x4c91f0fc00000000, 0x2ea9d8d600000000, 0x51c5308a00000000, + 0x33fd18a000000000, 0x95b560de00000000, 0xf78d48f400000000, + 0xd924902200000000, 0xbb1cb80800000000, 0x1d54c07600000000, + 0x7f6ce85c00000000, 0xe38c10cf00000000, 0x81b438e500000000, + 0x27fc409b00000000, 0x45c468b100000000, 0x6b6db06700000000, + 0x0955984d00000000, 0xaf1de03300000000, 0xcd25c81900000000, + 0xb249204500000000, 0xd071086f00000000, 0x7639701100000000, + 0x1401583b00000000, 0x3aa880ed00000000, 0x5890a8c700000000, + 0xfed8d0b900000000, 0x9ce0f89300000000, 0x871f504500000000, + 0xe527786f00000000, 0x436f001100000000, 0x2157283b00000000, + 0x0ffef0ed00000000, 0x6dc6d8c700000000, 0xcb8ea0b900000000, + 0xa9b6889300000000, 0xd6da60cf00000000, 0xb4e248e500000000, + 0x12aa309b00000000, 0x709218b100000000, 0x5e3bc06700000000, + 0x3c03e84d00000000, 0x9a4b903300000000, 0xf873b81900000000, + 0x6493408a00000000, 0x06ab68a000000000, 0xa0e310de00000000, + 0xc2db38f400000000, 0xec72e02200000000, 0x8e4ac80800000000, + 0x2802b07600000000, 0x4a3a985c00000000, 0x3556700000000000, + 0x576e582a00000000, 0xf126205400000000, 0x931e087e00000000, + 0xbdb7d0a800000000, 0xdf8ff88200000000, 0x79c780fc00000000, + 0x1bffa8d600000000, 0x0e3fa08a00000000, 0x6c0788a000000000, + 0xca4ff0de00000000, 0xa877d8f400000000, 0x86de002200000000, + 0xe4e6280800000000, 0x42ae507600000000, 0x2096785c00000000, + 0x5ffa900000000000, 0x3dc2b82a00000000, 0x9b8ac05400000000, + 0xf9b2e87e00000000, 0xd71b30a800000000, 0xb523188200000000, + 0x136b60fc00000000, 0x715348d600000000, 0xedb3b04500000000, + 0x8f8b986f00000000, 0x29c3e01100000000, 0x4bfbc83b00000000, + 0x655210ed00000000, 0x076a38c700000000, 0xa12240b900000000, + 0xc31a689300000000, 0xbc7680cf00000000, 0xde4ea8e500000000, + 0x7806d09b00000000, 0x1a3ef8b100000000, 0x3497206700000000, + 0x56af084d00000000, 0xf0e7703300000000, 0x92df581900000000, + 0x8920f0cf00000000, 0xeb18d8e500000000, 0x4d50a09b00000000, + 0x2f6888b100000000, 0x01c1506700000000, 0x63f9784d00000000, + 0xc5b1003300000000, 0xa789281900000000, 0xd8e5c04500000000, + 0xbadde86f00000000, 0x1c95901100000000, 0x7eadb83b00000000, + 0x500460ed00000000, 0x323c48c700000000, 0x947430b900000000, + 0xf64c189300000000, 0x6aace00000000000, 0x0894c82a00000000, + 0xaedcb05400000000, 0xcce4987e00000000, 0xe24d40a800000000, + 0x8075688200000000, 0x263d10fc00000000, 0x440538d600000000, + 0x3b69d08a00000000, 0x5951f8a000000000, 0xff1980de00000000, + 0x9d21a8f400000000, 0xb388702200000000, 0xd1b0580800000000, + 0x77f8207600000000, 0x15c0085c00000000, 0x5d7831ce00000000, + 0x3f4019e400000000, 0x9908619a00000000, 0xfb3049b000000000, + 0xd599916600000000, 0xb7a1b94c00000000, 0x11e9c13200000000, + 0x73d1e91800000000, 0x0cbd014400000000, 0x6e85296e00000000, + 0xc8cd511000000000, 0xaaf5793a00000000, 0x845ca1ec00000000, + 0xe66489c600000000, 0x402cf1b800000000, 0x2214d99200000000, + 0xbef4210100000000, 0xdccc092b00000000, 0x7a84715500000000, + 0x18bc597f00000000, 0x361581a900000000, 0x542da98300000000, + 0xf265d1fd00000000, 0x905df9d700000000, 0xef31118b00000000, + 0x8d0939a100000000, 0x2b4141df00000000, 0x497969f500000000, + 0x67d0b12300000000, 0x05e8990900000000, 0xa3a0e17700000000, + 0xc198c95d00000000, 0xda67618b00000000, 0xb85f49a100000000, + 0x1e1731df00000000, 0x7c2f19f500000000, 0x5286c12300000000, + 0x30bee90900000000, 0x96f6917700000000, 0xf4ceb95d00000000, + 0x8ba2510100000000, 0xe99a792b00000000, 0x4fd2015500000000, + 0x2dea297f00000000, 0x0343f1a900000000, 0x617bd98300000000, + 0xc733a1fd00000000, 0xa50b89d700000000, 0x39eb714400000000, + 0x5bd3596e00000000, 0xfd9b211000000000, 0x9fa3093a00000000, + 0xb10ad1ec00000000, 0xd332f9c600000000, 0x757a81b800000000, + 0x1742a99200000000, 0x682e41ce00000000, 0x0a1669e400000000, + 0xac5e119a00000000, 0xce6639b000000000, 0xe0cfe16600000000, + 0x82f7c94c00000000, 0x24bfb13200000000, 0x4687991800000000, + 0x5347914400000000, 0x317fb96e00000000, 0x9737c11000000000, + 0xf50fe93a00000000, 0xdba631ec00000000, 0xb99e19c600000000, + 0x1fd661b800000000, 0x7dee499200000000, 0x0282a1ce00000000, + 0x60ba89e400000000, 0xc6f2f19a00000000, 0xa4cad9b000000000, + 0x8a63016600000000, 0xe85b294c00000000, 0x4e13513200000000, + 0x2c2b791800000000, 0xb0cb818b00000000, 0xd2f3a9a100000000, + 0x74bbd1df00000000, 0x1683f9f500000000, 0x382a212300000000, + 0x5a12090900000000, 0xfc5a717700000000, 0x9e62595d00000000, + 0xe10eb10100000000, 0x8336992b00000000, 0x257ee15500000000, + 0x4746c97f00000000, 0x69ef11a900000000, 0x0bd7398300000000, + 0xad9f41fd00000000, 0xcfa769d700000000, 0xd458c10100000000, + 0xb660e92b00000000, 0x1028915500000000, 0x7210b97f00000000, + 0x5cb961a900000000, 0x3e81498300000000, 0x98c931fd00000000, + 0xfaf119d700000000, 0x859df18b00000000, 0xe7a5d9a100000000, + 0x41eda1df00000000, 0x23d589f500000000, 0x0d7c512300000000, + 0x6f44790900000000, 0xc90c017700000000, 0xab34295d00000000, + 0x37d4d1ce00000000, 0x55ecf9e400000000, 0xf3a4819a00000000, + 0x919ca9b000000000, 0xbf35716600000000, 0xdd0d594c00000000, + 0x7b45213200000000, 0x197d091800000000, 0x6611e14400000000, + 0x0429c96e00000000, 0xa261b11000000000, 0xc059993a00000000, + 0xeef041ec00000000, 0x8cc869c600000000, 0x2a8011b800000000, + 0x48b8399200000000}, + {0x0000000000000000, 0x4c2896a300000000, 0xd9565d9c00000000, + 0x957ecb3f00000000, 0xf3abcbe300000000, 0xbf835d4000000000, + 0x2afd967f00000000, 0x66d500dc00000000, 0xa751e61c00000000, + 0xeb7970bf00000000, 0x7e07bb8000000000, 0x322f2d2300000000, + 0x54fa2dff00000000, 0x18d2bb5c00000000, 0x8dac706300000000, + 0xc184e6c000000000, 0x4ea3cc3900000000, 0x028b5a9a00000000, + 0x97f591a500000000, 0xdbdd070600000000, 0xbd0807da00000000, + 0xf120917900000000, 0x645e5a4600000000, 0x2876cce500000000, + 0xe9f22a2500000000, 0xa5dabc8600000000, 0x30a477b900000000, + 0x7c8ce11a00000000, 0x1a59e1c600000000, 0x5671776500000000, + 0xc30fbc5a00000000, 0x8f272af900000000, 0x9c46997300000000, + 0xd06e0fd000000000, 0x4510c4ef00000000, 0x0938524c00000000, + 0x6fed529000000000, 0x23c5c43300000000, 0xb6bb0f0c00000000, + 0xfa9399af00000000, 0x3b177f6f00000000, 0x773fe9cc00000000, + 0xe24122f300000000, 0xae69b45000000000, 0xc8bcb48c00000000, + 0x8494222f00000000, 0x11eae91000000000, 0x5dc27fb300000000, + 0xd2e5554a00000000, 0x9ecdc3e900000000, 0x0bb308d600000000, + 0x479b9e7500000000, 0x214e9ea900000000, 0x6d66080a00000000, + 0xf818c33500000000, 0xb430559600000000, 0x75b4b35600000000, + 0x399c25f500000000, 0xace2eeca00000000, 0xe0ca786900000000, + 0x861f78b500000000, 0xca37ee1600000000, 0x5f49252900000000, + 0x1361b38a00000000, 0x388d32e700000000, 0x74a5a44400000000, + 0xe1db6f7b00000000, 0xadf3f9d800000000, 0xcb26f90400000000, + 0x870e6fa700000000, 0x1270a49800000000, 0x5e58323b00000000, + 0x9fdcd4fb00000000, 0xd3f4425800000000, 0x468a896700000000, + 0x0aa21fc400000000, 0x6c771f1800000000, 0x205f89bb00000000, + 0xb521428400000000, 0xf909d42700000000, 0x762efede00000000, + 0x3a06687d00000000, 0xaf78a34200000000, 0xe35035e100000000, + 0x8585353d00000000, 0xc9ada39e00000000, 0x5cd368a100000000, + 0x10fbfe0200000000, 0xd17f18c200000000, 0x9d578e6100000000, + 0x0829455e00000000, 0x4401d3fd00000000, 0x22d4d32100000000, + 0x6efc458200000000, 0xfb828ebd00000000, 0xb7aa181e00000000, + 0xa4cbab9400000000, 0xe8e33d3700000000, 0x7d9df60800000000, + 0x31b560ab00000000, 0x5760607700000000, 0x1b48f6d400000000, + 0x8e363deb00000000, 0xc21eab4800000000, 0x039a4d8800000000, + 0x4fb2db2b00000000, 0xdacc101400000000, 0x96e486b700000000, + 0xf031866b00000000, 0xbc1910c800000000, 0x2967dbf700000000, + 0x654f4d5400000000, 0xea6867ad00000000, 0xa640f10e00000000, + 0x333e3a3100000000, 0x7f16ac9200000000, 0x19c3ac4e00000000, + 0x55eb3aed00000000, 0xc095f1d200000000, 0x8cbd677100000000, + 0x4d3981b100000000, 0x0111171200000000, 0x946fdc2d00000000, + 0xd8474a8e00000000, 0xbe924a5200000000, 0xf2badcf100000000, + 0x67c417ce00000000, 0x2bec816d00000000, 0x311c141500000000, + 0x7d3482b600000000, 0xe84a498900000000, 0xa462df2a00000000, + 0xc2b7dff600000000, 0x8e9f495500000000, 0x1be1826a00000000, + 0x57c914c900000000, 0x964df20900000000, 0xda6564aa00000000, + 0x4f1baf9500000000, 0x0333393600000000, 0x65e639ea00000000, + 0x29ceaf4900000000, 0xbcb0647600000000, 0xf098f2d500000000, + 0x7fbfd82c00000000, 0x33974e8f00000000, 0xa6e985b000000000, + 0xeac1131300000000, 0x8c1413cf00000000, 0xc03c856c00000000, + 0x55424e5300000000, 0x196ad8f000000000, 0xd8ee3e3000000000, + 0x94c6a89300000000, 0x01b863ac00000000, 0x4d90f50f00000000, + 0x2b45f5d300000000, 0x676d637000000000, 0xf213a84f00000000, + 0xbe3b3eec00000000, 0xad5a8d6600000000, 0xe1721bc500000000, + 0x740cd0fa00000000, 0x3824465900000000, 0x5ef1468500000000, + 0x12d9d02600000000, 0x87a71b1900000000, 0xcb8f8dba00000000, + 0x0a0b6b7a00000000, 0x4623fdd900000000, 0xd35d36e600000000, + 0x9f75a04500000000, 0xf9a0a09900000000, 0xb588363a00000000, + 0x20f6fd0500000000, 0x6cde6ba600000000, 0xe3f9415f00000000, + 0xafd1d7fc00000000, 0x3aaf1cc300000000, 0x76878a6000000000, + 0x10528abc00000000, 0x5c7a1c1f00000000, 0xc904d72000000000, + 0x852c418300000000, 0x44a8a74300000000, 0x088031e000000000, + 0x9dfefadf00000000, 0xd1d66c7c00000000, 0xb7036ca000000000, + 0xfb2bfa0300000000, 0x6e55313c00000000, 0x227da79f00000000, + 0x099126f200000000, 0x45b9b05100000000, 0xd0c77b6e00000000, + 0x9cefedcd00000000, 0xfa3aed1100000000, 0xb6127bb200000000, + 0x236cb08d00000000, 0x6f44262e00000000, 0xaec0c0ee00000000, + 0xe2e8564d00000000, 0x77969d7200000000, 0x3bbe0bd100000000, + 0x5d6b0b0d00000000, 0x11439dae00000000, 0x843d569100000000, + 0xc815c03200000000, 0x4732eacb00000000, 0x0b1a7c6800000000, + 0x9e64b75700000000, 0xd24c21f400000000, 0xb499212800000000, + 0xf8b1b78b00000000, 0x6dcf7cb400000000, 0x21e7ea1700000000, + 0xe0630cd700000000, 0xac4b9a7400000000, 0x3935514b00000000, + 0x751dc7e800000000, 0x13c8c73400000000, 0x5fe0519700000000, + 0xca9e9aa800000000, 0x86b60c0b00000000, 0x95d7bf8100000000, + 0xd9ff292200000000, 0x4c81e21d00000000, 0x00a974be00000000, + 0x667c746200000000, 0x2a54e2c100000000, 0xbf2a29fe00000000, + 0xf302bf5d00000000, 0x3286599d00000000, 0x7eaecf3e00000000, + 0xebd0040100000000, 0xa7f892a200000000, 0xc12d927e00000000, + 0x8d0504dd00000000, 0x187bcfe200000000, 0x5453594100000000, + 0xdb7473b800000000, 0x975ce51b00000000, 0x02222e2400000000, + 0x4e0ab88700000000, 0x28dfb85b00000000, 0x64f72ef800000000, + 0xf189e5c700000000, 0xbda1736400000000, 0x7c2595a400000000, + 0x300d030700000000, 0xa573c83800000000, 0xe95b5e9b00000000, + 0x8f8e5e4700000000, 0xc3a6c8e400000000, 0x56d803db00000000, + 0x1af0957800000000}, + {0x0000000000000000, 0x939bc97f00000000, 0x263793ff00000000, + 0xb5ac5a8000000000, 0x0d68572400000000, 0x9ef39e5b00000000, + 0x2b5fc4db00000000, 0xb8c40da400000000, 0x1ad0ae4800000000, + 0x894b673700000000, 0x3ce73db700000000, 0xaf7cf4c800000000, + 0x17b8f96c00000000, 0x8423301300000000, 0x318f6a9300000000, + 0xa214a3ec00000000, 0x34a05d9100000000, 0xa73b94ee00000000, + 0x1297ce6e00000000, 0x810c071100000000, 0x39c80ab500000000, + 0xaa53c3ca00000000, 0x1fff994a00000000, 0x8c64503500000000, + 0x2e70f3d900000000, 0xbdeb3aa600000000, 0x0847602600000000, + 0x9bdca95900000000, 0x2318a4fd00000000, 0xb0836d8200000000, + 0x052f370200000000, 0x96b4fe7d00000000, 0x2946caf900000000, + 0xbadd038600000000, 0x0f71590600000000, 0x9cea907900000000, + 0x242e9ddd00000000, 0xb7b554a200000000, 0x02190e2200000000, + 0x9182c75d00000000, 0x339664b100000000, 0xa00dadce00000000, + 0x15a1f74e00000000, 0x863a3e3100000000, 0x3efe339500000000, + 0xad65faea00000000, 0x18c9a06a00000000, 0x8b52691500000000, + 0x1de6976800000000, 0x8e7d5e1700000000, 0x3bd1049700000000, + 0xa84acde800000000, 0x108ec04c00000000, 0x8315093300000000, + 0x36b953b300000000, 0xa5229acc00000000, 0x0736392000000000, + 0x94adf05f00000000, 0x2101aadf00000000, 0xb29a63a000000000, + 0x0a5e6e0400000000, 0x99c5a77b00000000, 0x2c69fdfb00000000, + 0xbff2348400000000, 0x138ae52800000000, 0x80112c5700000000, + 0x35bd76d700000000, 0xa626bfa800000000, 0x1ee2b20c00000000, + 0x8d797b7300000000, 0x38d521f300000000, 0xab4ee88c00000000, + 0x095a4b6000000000, 0x9ac1821f00000000, 0x2f6dd89f00000000, + 0xbcf611e000000000, 0x04321c4400000000, 0x97a9d53b00000000, + 0x22058fbb00000000, 0xb19e46c400000000, 0x272ab8b900000000, + 0xb4b171c600000000, 0x011d2b4600000000, 0x9286e23900000000, + 0x2a42ef9d00000000, 0xb9d926e200000000, 0x0c757c6200000000, + 0x9feeb51d00000000, 0x3dfa16f100000000, 0xae61df8e00000000, + 0x1bcd850e00000000, 0x88564c7100000000, 0x309241d500000000, + 0xa30988aa00000000, 0x16a5d22a00000000, 0x853e1b5500000000, + 0x3acc2fd100000000, 0xa957e6ae00000000, 0x1cfbbc2e00000000, + 0x8f60755100000000, 0x37a478f500000000, 0xa43fb18a00000000, + 0x1193eb0a00000000, 0x8208227500000000, 0x201c819900000000, + 0xb38748e600000000, 0x062b126600000000, 0x95b0db1900000000, + 0x2d74d6bd00000000, 0xbeef1fc200000000, 0x0b43454200000000, + 0x98d88c3d00000000, 0x0e6c724000000000, 0x9df7bb3f00000000, + 0x285be1bf00000000, 0xbbc028c000000000, 0x0304256400000000, + 0x909fec1b00000000, 0x2533b69b00000000, 0xb6a87fe400000000, + 0x14bcdc0800000000, 0x8727157700000000, 0x328b4ff700000000, + 0xa110868800000000, 0x19d48b2c00000000, 0x8a4f425300000000, + 0x3fe318d300000000, 0xac78d1ac00000000, 0x2614cb5100000000, + 0xb58f022e00000000, 0x002358ae00000000, 0x93b891d100000000, + 0x2b7c9c7500000000, 0xb8e7550a00000000, 0x0d4b0f8a00000000, + 0x9ed0c6f500000000, 0x3cc4651900000000, 0xaf5fac6600000000, + 0x1af3f6e600000000, 0x89683f9900000000, 0x31ac323d00000000, + 0xa237fb4200000000, 0x179ba1c200000000, 0x840068bd00000000, + 0x12b496c000000000, 0x812f5fbf00000000, 0x3483053f00000000, + 0xa718cc4000000000, 0x1fdcc1e400000000, 0x8c47089b00000000, + 0x39eb521b00000000, 0xaa709b6400000000, 0x0864388800000000, + 0x9bfff1f700000000, 0x2e53ab7700000000, 0xbdc8620800000000, + 0x050c6fac00000000, 0x9697a6d300000000, 0x233bfc5300000000, + 0xb0a0352c00000000, 0x0f5201a800000000, 0x9cc9c8d700000000, + 0x2965925700000000, 0xbafe5b2800000000, 0x023a568c00000000, + 0x91a19ff300000000, 0x240dc57300000000, 0xb7960c0c00000000, + 0x1582afe000000000, 0x8619669f00000000, 0x33b53c1f00000000, + 0xa02ef56000000000, 0x18eaf8c400000000, 0x8b7131bb00000000, + 0x3edd6b3b00000000, 0xad46a24400000000, 0x3bf25c3900000000, + 0xa869954600000000, 0x1dc5cfc600000000, 0x8e5e06b900000000, + 0x369a0b1d00000000, 0xa501c26200000000, 0x10ad98e200000000, + 0x8336519d00000000, 0x2122f27100000000, 0xb2b93b0e00000000, + 0x0715618e00000000, 0x948ea8f100000000, 0x2c4aa55500000000, + 0xbfd16c2a00000000, 0x0a7d36aa00000000, 0x99e6ffd500000000, + 0x359e2e7900000000, 0xa605e70600000000, 0x13a9bd8600000000, + 0x803274f900000000, 0x38f6795d00000000, 0xab6db02200000000, + 0x1ec1eaa200000000, 0x8d5a23dd00000000, 0x2f4e803100000000, + 0xbcd5494e00000000, 0x097913ce00000000, 0x9ae2dab100000000, + 0x2226d71500000000, 0xb1bd1e6a00000000, 0x041144ea00000000, + 0x978a8d9500000000, 0x013e73e800000000, 0x92a5ba9700000000, + 0x2709e01700000000, 0xb492296800000000, 0x0c5624cc00000000, + 0x9fcdedb300000000, 0x2a61b73300000000, 0xb9fa7e4c00000000, + 0x1beedda000000000, 0x887514df00000000, 0x3dd94e5f00000000, + 0xae42872000000000, 0x16868a8400000000, 0x851d43fb00000000, + 0x30b1197b00000000, 0xa32ad00400000000, 0x1cd8e48000000000, + 0x8f432dff00000000, 0x3aef777f00000000, 0xa974be0000000000, + 0x11b0b3a400000000, 0x822b7adb00000000, 0x3787205b00000000, + 0xa41ce92400000000, 0x06084ac800000000, 0x959383b700000000, + 0x203fd93700000000, 0xb3a4104800000000, 0x0b601dec00000000, + 0x98fbd49300000000, 0x2d578e1300000000, 0xbecc476c00000000, + 0x2878b91100000000, 0xbbe3706e00000000, 0x0e4f2aee00000000, + 0x9dd4e39100000000, 0x2510ee3500000000, 0xb68b274a00000000, + 0x03277dca00000000, 0x90bcb4b500000000, 0x32a8175900000000, + 0xa133de2600000000, 0x149f84a600000000, 0x87044dd900000000, + 0x3fc0407d00000000, 0xac5b890200000000, 0x19f7d38200000000, + 0x8a6c1afd00000000}, + {0x0000000000000000, 0x650b796900000000, 0xca16f2d200000000, + 0xaf1d8bbb00000000, 0xd52b957e00000000, 0xb020ec1700000000, + 0x1f3d67ac00000000, 0x7a361ec500000000, 0xaa572afd00000000, + 0xcf5c539400000000, 0x6041d82f00000000, 0x054aa14600000000, + 0x7f7cbf8300000000, 0x1a77c6ea00000000, 0xb56a4d5100000000, + 0xd061343800000000, 0x15a9252100000000, 0x70a25c4800000000, + 0xdfbfd7f300000000, 0xbab4ae9a00000000, 0xc082b05f00000000, + 0xa589c93600000000, 0x0a94428d00000000, 0x6f9f3be400000000, + 0xbffe0fdc00000000, 0xdaf576b500000000, 0x75e8fd0e00000000, + 0x10e3846700000000, 0x6ad59aa200000000, 0x0fdee3cb00000000, + 0xa0c3687000000000, 0xc5c8111900000000, 0x2a524b4200000000, + 0x4f59322b00000000, 0xe044b99000000000, 0x854fc0f900000000, + 0xff79de3c00000000, 0x9a72a75500000000, 0x356f2cee00000000, + 0x5064558700000000, 0x800561bf00000000, 0xe50e18d600000000, + 0x4a13936d00000000, 0x2f18ea0400000000, 0x552ef4c100000000, + 0x30258da800000000, 0x9f38061300000000, 0xfa337f7a00000000, + 0x3ffb6e6300000000, 0x5af0170a00000000, 0xf5ed9cb100000000, + 0x90e6e5d800000000, 0xead0fb1d00000000, 0x8fdb827400000000, + 0x20c609cf00000000, 0x45cd70a600000000, 0x95ac449e00000000, + 0xf0a73df700000000, 0x5fbab64c00000000, 0x3ab1cf2500000000, + 0x4087d1e000000000, 0x258ca88900000000, 0x8a91233200000000, + 0xef9a5a5b00000000, 0x54a4968400000000, 0x31afefed00000000, + 0x9eb2645600000000, 0xfbb91d3f00000000, 0x818f03fa00000000, + 0xe4847a9300000000, 0x4b99f12800000000, 0x2e92884100000000, + 0xfef3bc7900000000, 0x9bf8c51000000000, 0x34e54eab00000000, + 0x51ee37c200000000, 0x2bd8290700000000, 0x4ed3506e00000000, + 0xe1cedbd500000000, 0x84c5a2bc00000000, 0x410db3a500000000, + 0x2406cacc00000000, 0x8b1b417700000000, 0xee10381e00000000, + 0x942626db00000000, 0xf12d5fb200000000, 0x5e30d40900000000, + 0x3b3bad6000000000, 0xeb5a995800000000, 0x8e51e03100000000, + 0x214c6b8a00000000, 0x444712e300000000, 0x3e710c2600000000, + 0x5b7a754f00000000, 0xf467fef400000000, 0x916c879d00000000, + 0x7ef6ddc600000000, 0x1bfda4af00000000, 0xb4e02f1400000000, + 0xd1eb567d00000000, 0xabdd48b800000000, 0xced631d100000000, + 0x61cbba6a00000000, 0x04c0c30300000000, 0xd4a1f73b00000000, + 0xb1aa8e5200000000, 0x1eb705e900000000, 0x7bbc7c8000000000, + 0x018a624500000000, 0x64811b2c00000000, 0xcb9c909700000000, + 0xae97e9fe00000000, 0x6b5ff8e700000000, 0x0e54818e00000000, + 0xa1490a3500000000, 0xc442735c00000000, 0xbe746d9900000000, + 0xdb7f14f000000000, 0x74629f4b00000000, 0x1169e62200000000, + 0xc108d21a00000000, 0xa403ab7300000000, 0x0b1e20c800000000, + 0x6e1559a100000000, 0x1423476400000000, 0x71283e0d00000000, + 0xde35b5b600000000, 0xbb3eccdf00000000, 0xe94e5cd200000000, + 0x8c4525bb00000000, 0x2358ae0000000000, 0x4653d76900000000, + 0x3c65c9ac00000000, 0x596eb0c500000000, 0xf6733b7e00000000, + 0x9378421700000000, 0x4319762f00000000, 0x26120f4600000000, + 0x890f84fd00000000, 0xec04fd9400000000, 0x9632e35100000000, + 0xf3399a3800000000, 0x5c24118300000000, 0x392f68ea00000000, + 0xfce779f300000000, 0x99ec009a00000000, 0x36f18b2100000000, + 0x53faf24800000000, 0x29ccec8d00000000, 0x4cc795e400000000, + 0xe3da1e5f00000000, 0x86d1673600000000, 0x56b0530e00000000, + 0x33bb2a6700000000, 0x9ca6a1dc00000000, 0xf9add8b500000000, + 0x839bc67000000000, 0xe690bf1900000000, 0x498d34a200000000, + 0x2c864dcb00000000, 0xc31c179000000000, 0xa6176ef900000000, + 0x090ae54200000000, 0x6c019c2b00000000, 0x163782ee00000000, + 0x733cfb8700000000, 0xdc21703c00000000, 0xb92a095500000000, + 0x694b3d6d00000000, 0x0c40440400000000, 0xa35dcfbf00000000, + 0xc656b6d600000000, 0xbc60a81300000000, 0xd96bd17a00000000, + 0x76765ac100000000, 0x137d23a800000000, 0xd6b532b100000000, + 0xb3be4bd800000000, 0x1ca3c06300000000, 0x79a8b90a00000000, + 0x039ea7cf00000000, 0x6695dea600000000, 0xc988551d00000000, + 0xac832c7400000000, 0x7ce2184c00000000, 0x19e9612500000000, + 0xb6f4ea9e00000000, 0xd3ff93f700000000, 0xa9c98d3200000000, + 0xccc2f45b00000000, 0x63df7fe000000000, 0x06d4068900000000, + 0xbdeaca5600000000, 0xd8e1b33f00000000, 0x77fc388400000000, + 0x12f741ed00000000, 0x68c15f2800000000, 0x0dca264100000000, + 0xa2d7adfa00000000, 0xc7dcd49300000000, 0x17bde0ab00000000, + 0x72b699c200000000, 0xddab127900000000, 0xb8a06b1000000000, + 0xc29675d500000000, 0xa79d0cbc00000000, 0x0880870700000000, + 0x6d8bfe6e00000000, 0xa843ef7700000000, 0xcd48961e00000000, + 0x62551da500000000, 0x075e64cc00000000, 0x7d687a0900000000, + 0x1863036000000000, 0xb77e88db00000000, 0xd275f1b200000000, + 0x0214c58a00000000, 0x671fbce300000000, 0xc802375800000000, + 0xad094e3100000000, 0xd73f50f400000000, 0xb234299d00000000, + 0x1d29a22600000000, 0x7822db4f00000000, 0x97b8811400000000, + 0xf2b3f87d00000000, 0x5dae73c600000000, 0x38a50aaf00000000, + 0x4293146a00000000, 0x27986d0300000000, 0x8885e6b800000000, + 0xed8e9fd100000000, 0x3defabe900000000, 0x58e4d28000000000, + 0xf7f9593b00000000, 0x92f2205200000000, 0xe8c43e9700000000, + 0x8dcf47fe00000000, 0x22d2cc4500000000, 0x47d9b52c00000000, + 0x8211a43500000000, 0xe71add5c00000000, 0x480756e700000000, + 0x2d0c2f8e00000000, 0x573a314b00000000, 0x3231482200000000, + 0x9d2cc39900000000, 0xf827baf000000000, 0x28468ec800000000, + 0x4d4df7a100000000, 0xe2507c1a00000000, 0x875b057300000000, + 0xfd6d1bb600000000, 0x986662df00000000, 0x377be96400000000, + 0x5270900d00000000}, + {0x0000000000000000, 0xdcecb13d00000000, 0xb8d9637b00000000, + 0x6435d24600000000, 0x70b3c7f600000000, 0xac5f76cb00000000, + 0xc86aa48d00000000, 0x148615b000000000, 0xa160fe3600000000, + 0x7d8c4f0b00000000, 0x19b99d4d00000000, 0xc5552c7000000000, + 0xd1d339c000000000, 0x0d3f88fd00000000, 0x690a5abb00000000, + 0xb5e6eb8600000000, 0x42c1fc6d00000000, 0x9e2d4d5000000000, + 0xfa189f1600000000, 0x26f42e2b00000000, 0x32723b9b00000000, + 0xee9e8aa600000000, 0x8aab58e000000000, 0x5647e9dd00000000, + 0xe3a1025b00000000, 0x3f4db36600000000, 0x5b78612000000000, + 0x8794d01d00000000, 0x9312c5ad00000000, 0x4ffe749000000000, + 0x2bcba6d600000000, 0xf72717eb00000000, 0x8482f9db00000000, + 0x586e48e600000000, 0x3c5b9aa000000000, 0xe0b72b9d00000000, + 0xf4313e2d00000000, 0x28dd8f1000000000, 0x4ce85d5600000000, + 0x9004ec6b00000000, 0x25e207ed00000000, 0xf90eb6d000000000, + 0x9d3b649600000000, 0x41d7d5ab00000000, 0x5551c01b00000000, + 0x89bd712600000000, 0xed88a36000000000, 0x3164125d00000000, + 0xc64305b600000000, 0x1aafb48b00000000, 0x7e9a66cd00000000, + 0xa276d7f000000000, 0xb6f0c24000000000, 0x6a1c737d00000000, + 0x0e29a13b00000000, 0xd2c5100600000000, 0x6723fb8000000000, + 0xbbcf4abd00000000, 0xdffa98fb00000000, 0x031629c600000000, + 0x17903c7600000000, 0xcb7c8d4b00000000, 0xaf495f0d00000000, + 0x73a5ee3000000000, 0x4903826c00000000, 0x95ef335100000000, + 0xf1dae11700000000, 0x2d36502a00000000, 0x39b0459a00000000, + 0xe55cf4a700000000, 0x816926e100000000, 0x5d8597dc00000000, + 0xe8637c5a00000000, 0x348fcd6700000000, 0x50ba1f2100000000, + 0x8c56ae1c00000000, 0x98d0bbac00000000, 0x443c0a9100000000, + 0x2009d8d700000000, 0xfce569ea00000000, 0x0bc27e0100000000, + 0xd72ecf3c00000000, 0xb31b1d7a00000000, 0x6ff7ac4700000000, + 0x7b71b9f700000000, 0xa79d08ca00000000, 0xc3a8da8c00000000, + 0x1f446bb100000000, 0xaaa2803700000000, 0x764e310a00000000, + 0x127be34c00000000, 0xce97527100000000, 0xda1147c100000000, + 0x06fdf6fc00000000, 0x62c824ba00000000, 0xbe24958700000000, + 0xcd817bb700000000, 0x116dca8a00000000, 0x755818cc00000000, + 0xa9b4a9f100000000, 0xbd32bc4100000000, 0x61de0d7c00000000, + 0x05ebdf3a00000000, 0xd9076e0700000000, 0x6ce1858100000000, + 0xb00d34bc00000000, 0xd438e6fa00000000, 0x08d457c700000000, + 0x1c52427700000000, 0xc0bef34a00000000, 0xa48b210c00000000, + 0x7867903100000000, 0x8f4087da00000000, 0x53ac36e700000000, + 0x3799e4a100000000, 0xeb75559c00000000, 0xfff3402c00000000, + 0x231ff11100000000, 0x472a235700000000, 0x9bc6926a00000000, + 0x2e2079ec00000000, 0xf2ccc8d100000000, 0x96f91a9700000000, + 0x4a15abaa00000000, 0x5e93be1a00000000, 0x827f0f2700000000, + 0xe64add6100000000, 0x3aa66c5c00000000, 0x920604d900000000, + 0x4eeab5e400000000, 0x2adf67a200000000, 0xf633d69f00000000, + 0xe2b5c32f00000000, 0x3e59721200000000, 0x5a6ca05400000000, + 0x8680116900000000, 0x3366faef00000000, 0xef8a4bd200000000, + 0x8bbf999400000000, 0x575328a900000000, 0x43d53d1900000000, + 0x9f398c2400000000, 0xfb0c5e6200000000, 0x27e0ef5f00000000, + 0xd0c7f8b400000000, 0x0c2b498900000000, 0x681e9bcf00000000, + 0xb4f22af200000000, 0xa0743f4200000000, 0x7c988e7f00000000, + 0x18ad5c3900000000, 0xc441ed0400000000, 0x71a7068200000000, + 0xad4bb7bf00000000, 0xc97e65f900000000, 0x1592d4c400000000, + 0x0114c17400000000, 0xddf8704900000000, 0xb9cda20f00000000, + 0x6521133200000000, 0x1684fd0200000000, 0xca684c3f00000000, + 0xae5d9e7900000000, 0x72b12f4400000000, 0x66373af400000000, + 0xbadb8bc900000000, 0xdeee598f00000000, 0x0202e8b200000000, + 0xb7e4033400000000, 0x6b08b20900000000, 0x0f3d604f00000000, + 0xd3d1d17200000000, 0xc757c4c200000000, 0x1bbb75ff00000000, + 0x7f8ea7b900000000, 0xa362168400000000, 0x5445016f00000000, + 0x88a9b05200000000, 0xec9c621400000000, 0x3070d32900000000, + 0x24f6c69900000000, 0xf81a77a400000000, 0x9c2fa5e200000000, + 0x40c314df00000000, 0xf525ff5900000000, 0x29c94e6400000000, + 0x4dfc9c2200000000, 0x91102d1f00000000, 0x859638af00000000, + 0x597a899200000000, 0x3d4f5bd400000000, 0xe1a3eae900000000, + 0xdb0586b500000000, 0x07e9378800000000, 0x63dce5ce00000000, + 0xbf3054f300000000, 0xabb6414300000000, 0x775af07e00000000, + 0x136f223800000000, 0xcf83930500000000, 0x7a65788300000000, + 0xa689c9be00000000, 0xc2bc1bf800000000, 0x1e50aac500000000, + 0x0ad6bf7500000000, 0xd63a0e4800000000, 0xb20fdc0e00000000, + 0x6ee36d3300000000, 0x99c47ad800000000, 0x4528cbe500000000, + 0x211d19a300000000, 0xfdf1a89e00000000, 0xe977bd2e00000000, + 0x359b0c1300000000, 0x51aede5500000000, 0x8d426f6800000000, + 0x38a484ee00000000, 0xe44835d300000000, 0x807de79500000000, + 0x5c9156a800000000, 0x4817431800000000, 0x94fbf22500000000, + 0xf0ce206300000000, 0x2c22915e00000000, 0x5f877f6e00000000, + 0x836bce5300000000, 0xe75e1c1500000000, 0x3bb2ad2800000000, + 0x2f34b89800000000, 0xf3d809a500000000, 0x97eddbe300000000, + 0x4b016ade00000000, 0xfee7815800000000, 0x220b306500000000, + 0x463ee22300000000, 0x9ad2531e00000000, 0x8e5446ae00000000, + 0x52b8f79300000000, 0x368d25d500000000, 0xea6194e800000000, + 0x1d46830300000000, 0xc1aa323e00000000, 0xa59fe07800000000, + 0x7973514500000000, 0x6df544f500000000, 0xb119f5c800000000, + 0xd52c278e00000000, 0x09c096b300000000, 0xbc267d3500000000, + 0x60cacc0800000000, 0x04ff1e4e00000000, 0xd813af7300000000, + 0xcc95bac300000000, 0x10790bfe00000000, 0x744cd9b800000000, + 0xa8a0688500000000}}; + +#else /* W == 4 */ + +local const z_crc_t FAR crc_braid_table[][256] = { + {0x00000000, 0x81256527, 0xd93bcc0f, 0x581ea928, 0x69069e5f, + 0xe823fb78, 0xb03d5250, 0x31183777, 0xd20d3cbe, 0x53285999, + 0x0b36f0b1, 0x8a139596, 0xbb0ba2e1, 0x3a2ec7c6, 0x62306eee, + 0xe3150bc9, 0x7f6b7f3d, 0xfe4e1a1a, 0xa650b332, 0x2775d615, + 0x166de162, 0x97488445, 0xcf562d6d, 0x4e73484a, 0xad664383, + 0x2c4326a4, 0x745d8f8c, 0xf578eaab, 0xc460dddc, 0x4545b8fb, + 0x1d5b11d3, 0x9c7e74f4, 0xfed6fe7a, 0x7ff39b5d, 0x27ed3275, + 0xa6c85752, 0x97d06025, 0x16f50502, 0x4eebac2a, 0xcfcec90d, + 0x2cdbc2c4, 0xadfea7e3, 0xf5e00ecb, 0x74c56bec, 0x45dd5c9b, + 0xc4f839bc, 0x9ce69094, 0x1dc3f5b3, 0x81bd8147, 0x0098e460, + 0x58864d48, 0xd9a3286f, 0xe8bb1f18, 0x699e7a3f, 0x3180d317, + 0xb0a5b630, 0x53b0bdf9, 0xd295d8de, 0x8a8b71f6, 0x0bae14d1, + 0x3ab623a6, 0xbb934681, 0xe38defa9, 0x62a88a8e, 0x26dcfab5, + 0xa7f99f92, 0xffe736ba, 0x7ec2539d, 0x4fda64ea, 0xceff01cd, + 0x96e1a8e5, 0x17c4cdc2, 0xf4d1c60b, 0x75f4a32c, 0x2dea0a04, + 0xaccf6f23, 0x9dd75854, 0x1cf23d73, 0x44ec945b, 0xc5c9f17c, + 0x59b78588, 0xd892e0af, 0x808c4987, 0x01a92ca0, 0x30b11bd7, + 0xb1947ef0, 0xe98ad7d8, 0x68afb2ff, 0x8bbab936, 0x0a9fdc11, + 0x52817539, 0xd3a4101e, 0xe2bc2769, 0x6399424e, 0x3b87eb66, + 0xbaa28e41, 0xd80a04cf, 0x592f61e8, 0x0131c8c0, 0x8014ade7, + 0xb10c9a90, 0x3029ffb7, 0x6837569f, 0xe91233b8, 0x0a073871, + 0x8b225d56, 0xd33cf47e, 0x52199159, 0x6301a62e, 0xe224c309, + 0xba3a6a21, 0x3b1f0f06, 0xa7617bf2, 0x26441ed5, 0x7e5ab7fd, + 0xff7fd2da, 0xce67e5ad, 0x4f42808a, 0x175c29a2, 0x96794c85, + 0x756c474c, 0xf449226b, 0xac578b43, 0x2d72ee64, 0x1c6ad913, + 0x9d4fbc34, 0xc551151c, 0x4474703b, 0x4db9f56a, 0xcc9c904d, + 0x94823965, 0x15a75c42, 0x24bf6b35, 0xa59a0e12, 0xfd84a73a, + 0x7ca1c21d, 0x9fb4c9d4, 0x1e91acf3, 0x468f05db, 0xc7aa60fc, + 0xf6b2578b, 0x779732ac, 0x2f899b84, 0xaeacfea3, 0x32d28a57, + 0xb3f7ef70, 0xebe94658, 0x6acc237f, 0x5bd41408, 0xdaf1712f, + 0x82efd807, 0x03cabd20, 0xe0dfb6e9, 0x61fad3ce, 0x39e47ae6, + 0xb8c11fc1, 0x89d928b6, 0x08fc4d91, 0x50e2e4b9, 0xd1c7819e, + 0xb36f0b10, 0x324a6e37, 0x6a54c71f, 0xeb71a238, 0xda69954f, + 0x5b4cf068, 0x03525940, 0x82773c67, 0x616237ae, 0xe0475289, + 0xb859fba1, 0x397c9e86, 0x0864a9f1, 0x8941ccd6, 0xd15f65fe, + 0x507a00d9, 0xcc04742d, 0x4d21110a, 0x153fb822, 0x941add05, + 0xa502ea72, 0x24278f55, 0x7c39267d, 0xfd1c435a, 0x1e094893, + 0x9f2c2db4, 0xc732849c, 0x4617e1bb, 0x770fd6cc, 0xf62ab3eb, + 0xae341ac3, 0x2f117fe4, 0x6b650fdf, 0xea406af8, 0xb25ec3d0, + 0x337ba6f7, 0x02639180, 0x8346f4a7, 0xdb585d8f, 0x5a7d38a8, + 0xb9683361, 0x384d5646, 0x6053ff6e, 0xe1769a49, 0xd06ead3e, + 0x514bc819, 0x09556131, 0x88700416, 0x140e70e2, 0x952b15c5, + 0xcd35bced, 0x4c10d9ca, 0x7d08eebd, 0xfc2d8b9a, 0xa43322b2, + 0x25164795, 0xc6034c5c, 0x4726297b, 0x1f388053, 0x9e1de574, + 0xaf05d203, 0x2e20b724, 0x763e1e0c, 0xf71b7b2b, 0x95b3f1a5, + 0x14969482, 0x4c883daa, 0xcdad588d, 0xfcb56ffa, 0x7d900add, + 0x258ea3f5, 0xa4abc6d2, 0x47becd1b, 0xc69ba83c, 0x9e850114, + 0x1fa06433, 0x2eb85344, 0xaf9d3663, 0xf7839f4b, 0x76a6fa6c, + 0xead88e98, 0x6bfdebbf, 0x33e34297, 0xb2c627b0, 0x83de10c7, + 0x02fb75e0, 0x5ae5dcc8, 0xdbc0b9ef, 0x38d5b226, 0xb9f0d701, + 0xe1ee7e29, 0x60cb1b0e, 0x51d32c79, 0xd0f6495e, 0x88e8e076, + 0x09cd8551}, + {0x00000000, 0x9b73ead4, 0xed96d3e9, 0x76e5393d, 0x005ca193, + 0x9b2f4b47, 0xedca727a, 0x76b998ae, 0x00b94326, 0x9bcaa9f2, + 0xed2f90cf, 0x765c7a1b, 0x00e5e2b5, 0x9b960861, 0xed73315c, + 0x7600db88, 0x0172864c, 0x9a016c98, 0xece455a5, 0x7797bf71, + 0x012e27df, 0x9a5dcd0b, 0xecb8f436, 0x77cb1ee2, 0x01cbc56a, + 0x9ab82fbe, 0xec5d1683, 0x772efc57, 0x019764f9, 0x9ae48e2d, + 0xec01b710, 0x77725dc4, 0x02e50c98, 0x9996e64c, 0xef73df71, + 0x740035a5, 0x02b9ad0b, 0x99ca47df, 0xef2f7ee2, 0x745c9436, + 0x025c4fbe, 0x992fa56a, 0xefca9c57, 0x74b97683, 0x0200ee2d, + 0x997304f9, 0xef963dc4, 0x74e5d710, 0x03978ad4, 0x98e46000, + 0xee01593d, 0x7572b3e9, 0x03cb2b47, 0x98b8c193, 0xee5df8ae, + 0x752e127a, 0x032ec9f2, 0x985d2326, 0xeeb81a1b, 0x75cbf0cf, + 0x03726861, 0x980182b5, 0xeee4bb88, 0x7597515c, 0x05ca1930, + 0x9eb9f3e4, 0xe85ccad9, 0x732f200d, 0x0596b8a3, 0x9ee55277, + 0xe8006b4a, 0x7373819e, 0x05735a16, 0x9e00b0c2, 0xe8e589ff, + 0x7396632b, 0x052ffb85, 0x9e5c1151, 0xe8b9286c, 0x73cac2b8, + 0x04b89f7c, 0x9fcb75a8, 0xe92e4c95, 0x725da641, 0x04e43eef, + 0x9f97d43b, 0xe972ed06, 0x720107d2, 0x0401dc5a, 0x9f72368e, + 0xe9970fb3, 0x72e4e567, 0x045d7dc9, 0x9f2e971d, 0xe9cbae20, + 0x72b844f4, 0x072f15a8, 0x9c5cff7c, 0xeab9c641, 0x71ca2c95, + 0x0773b43b, 0x9c005eef, 0xeae567d2, 0x71968d06, 0x0796568e, + 0x9ce5bc5a, 0xea008567, 0x71736fb3, 0x07caf71d, 0x9cb91dc9, + 0xea5c24f4, 0x712fce20, 0x065d93e4, 0x9d2e7930, 0xebcb400d, + 0x70b8aad9, 0x06013277, 0x9d72d8a3, 0xeb97e19e, 0x70e40b4a, + 0x06e4d0c2, 0x9d973a16, 0xeb72032b, 0x7001e9ff, 0x06b87151, + 0x9dcb9b85, 0xeb2ea2b8, 0x705d486c, 0x0b943260, 0x90e7d8b4, + 0xe602e189, 0x7d710b5d, 0x0bc893f3, 0x90bb7927, 0xe65e401a, + 0x7d2daace, 0x0b2d7146, 0x905e9b92, 0xe6bba2af, 0x7dc8487b, + 0x0b71d0d5, 0x90023a01, 0xe6e7033c, 0x7d94e9e8, 0x0ae6b42c, + 0x91955ef8, 0xe77067c5, 0x7c038d11, 0x0aba15bf, 0x91c9ff6b, + 0xe72cc656, 0x7c5f2c82, 0x0a5ff70a, 0x912c1dde, 0xe7c924e3, + 0x7cbace37, 0x0a035699, 0x9170bc4d, 0xe7958570, 0x7ce66fa4, + 0x09713ef8, 0x9202d42c, 0xe4e7ed11, 0x7f9407c5, 0x092d9f6b, + 0x925e75bf, 0xe4bb4c82, 0x7fc8a656, 0x09c87dde, 0x92bb970a, + 0xe45eae37, 0x7f2d44e3, 0x0994dc4d, 0x92e73699, 0xe4020fa4, + 0x7f71e570, 0x0803b8b4, 0x93705260, 0xe5956b5d, 0x7ee68189, + 0x085f1927, 0x932cf3f3, 0xe5c9cace, 0x7eba201a, 0x08bafb92, + 0x93c91146, 0xe52c287b, 0x7e5fc2af, 0x08e65a01, 0x9395b0d5, + 0xe57089e8, 0x7e03633c, 0x0e5e2b50, 0x952dc184, 0xe3c8f8b9, + 0x78bb126d, 0x0e028ac3, 0x95716017, 0xe394592a, 0x78e7b3fe, + 0x0ee76876, 0x959482a2, 0xe371bb9f, 0x7802514b, 0x0ebbc9e5, + 0x95c82331, 0xe32d1a0c, 0x785ef0d8, 0x0f2cad1c, 0x945f47c8, + 0xe2ba7ef5, 0x79c99421, 0x0f700c8f, 0x9403e65b, 0xe2e6df66, + 0x799535b2, 0x0f95ee3a, 0x94e604ee, 0xe2033dd3, 0x7970d707, + 0x0fc94fa9, 0x94baa57d, 0xe25f9c40, 0x792c7694, 0x0cbb27c8, + 0x97c8cd1c, 0xe12df421, 0x7a5e1ef5, 0x0ce7865b, 0x97946c8f, + 0xe17155b2, 0x7a02bf66, 0x0c0264ee, 0x97718e3a, 0xe194b707, + 0x7ae75dd3, 0x0c5ec57d, 0x972d2fa9, 0xe1c81694, 0x7abbfc40, + 0x0dc9a184, 0x96ba4b50, 0xe05f726d, 0x7b2c98b9, 0x0d950017, + 0x96e6eac3, 0xe003d3fe, 0x7b70392a, 0x0d70e2a2, 0x96030876, + 0xe0e6314b, 0x7b95db9f, 0x0d2c4331, 0x965fa9e5, 0xe0ba90d8, + 0x7bc97a0c}, + {0x00000000, 0x172864c0, 0x2e50c980, 0x3978ad40, 0x5ca19300, + 0x4b89f7c0, 0x72f15a80, 0x65d93e40, 0xb9432600, 0xae6b42c0, + 0x9713ef80, 0x803b8b40, 0xe5e2b500, 0xf2cad1c0, 0xcbb27c80, + 0xdc9a1840, 0xa9f74a41, 0xbedf2e81, 0x87a783c1, 0x908fe701, + 0xf556d941, 0xe27ebd81, 0xdb0610c1, 0xcc2e7401, 0x10b46c41, + 0x079c0881, 0x3ee4a5c1, 0x29ccc101, 0x4c15ff41, 0x5b3d9b81, + 0x624536c1, 0x756d5201, 0x889f92c3, 0x9fb7f603, 0xa6cf5b43, + 0xb1e73f83, 0xd43e01c3, 0xc3166503, 0xfa6ec843, 0xed46ac83, + 0x31dcb4c3, 0x26f4d003, 0x1f8c7d43, 0x08a41983, 0x6d7d27c3, + 0x7a554303, 0x432dee43, 0x54058a83, 0x2168d882, 0x3640bc42, + 0x0f381102, 0x181075c2, 0x7dc94b82, 0x6ae12f42, 0x53998202, + 0x44b1e6c2, 0x982bfe82, 0x8f039a42, 0xb67b3702, 0xa15353c2, + 0xc48a6d82, 0xd3a20942, 0xeadaa402, 0xfdf2c0c2, 0xca4e23c7, + 0xdd664707, 0xe41eea47, 0xf3368e87, 0x96efb0c7, 0x81c7d407, + 0xb8bf7947, 0xaf971d87, 0x730d05c7, 0x64256107, 0x5d5dcc47, + 0x4a75a887, 0x2fac96c7, 0x3884f207, 0x01fc5f47, 0x16d43b87, + 0x63b96986, 0x74910d46, 0x4de9a006, 0x5ac1c4c6, 0x3f18fa86, + 0x28309e46, 0x11483306, 0x066057c6, 0xdafa4f86, 0xcdd22b46, + 0xf4aa8606, 0xe382e2c6, 0x865bdc86, 0x9173b846, 0xa80b1506, + 0xbf2371c6, 0x42d1b104, 0x55f9d5c4, 0x6c817884, 0x7ba91c44, + 0x1e702204, 0x095846c4, 0x3020eb84, 0x27088f44, 0xfb929704, + 0xecbaf3c4, 0xd5c25e84, 0xc2ea3a44, 0xa7330404, 0xb01b60c4, + 0x8963cd84, 0x9e4ba944, 0xeb26fb45, 0xfc0e9f85, 0xc57632c5, + 0xd25e5605, 0xb7876845, 0xa0af0c85, 0x99d7a1c5, 0x8effc505, + 0x5265dd45, 0x454db985, 0x7c3514c5, 0x6b1d7005, 0x0ec44e45, + 0x19ec2a85, 0x209487c5, 0x37bce305, 0x4fed41cf, 0x58c5250f, + 0x61bd884f, 0x7695ec8f, 0x134cd2cf, 0x0464b60f, 0x3d1c1b4f, + 0x2a347f8f, 0xf6ae67cf, 0xe186030f, 0xd8feae4f, 0xcfd6ca8f, + 0xaa0ff4cf, 0xbd27900f, 0x845f3d4f, 0x9377598f, 0xe61a0b8e, + 0xf1326f4e, 0xc84ac20e, 0xdf62a6ce, 0xbabb988e, 0xad93fc4e, + 0x94eb510e, 0x83c335ce, 0x5f592d8e, 0x4871494e, 0x7109e40e, + 0x662180ce, 0x03f8be8e, 0x14d0da4e, 0x2da8770e, 0x3a8013ce, + 0xc772d30c, 0xd05ab7cc, 0xe9221a8c, 0xfe0a7e4c, 0x9bd3400c, + 0x8cfb24cc, 0xb583898c, 0xa2abed4c, 0x7e31f50c, 0x691991cc, + 0x50613c8c, 0x4749584c, 0x2290660c, 0x35b802cc, 0x0cc0af8c, + 0x1be8cb4c, 0x6e85994d, 0x79adfd8d, 0x40d550cd, 0x57fd340d, + 0x32240a4d, 0x250c6e8d, 0x1c74c3cd, 0x0b5ca70d, 0xd7c6bf4d, + 0xc0eedb8d, 0xf99676cd, 0xeebe120d, 0x8b672c4d, 0x9c4f488d, + 0xa537e5cd, 0xb21f810d, 0x85a36208, 0x928b06c8, 0xabf3ab88, + 0xbcdbcf48, 0xd902f108, 0xce2a95c8, 0xf7523888, 0xe07a5c48, + 0x3ce04408, 0x2bc820c8, 0x12b08d88, 0x0598e948, 0x6041d708, + 0x7769b3c8, 0x4e111e88, 0x59397a48, 0x2c542849, 0x3b7c4c89, + 0x0204e1c9, 0x152c8509, 0x70f5bb49, 0x67dddf89, 0x5ea572c9, + 0x498d1609, 0x95170e49, 0x823f6a89, 0xbb47c7c9, 0xac6fa309, + 0xc9b69d49, 0xde9ef989, 0xe7e654c9, 0xf0ce3009, 0x0d3cf0cb, + 0x1a14940b, 0x236c394b, 0x34445d8b, 0x519d63cb, 0x46b5070b, + 0x7fcdaa4b, 0x68e5ce8b, 0xb47fd6cb, 0xa357b20b, 0x9a2f1f4b, + 0x8d077b8b, 0xe8de45cb, 0xfff6210b, 0xc68e8c4b, 0xd1a6e88b, + 0xa4cbba8a, 0xb3e3de4a, 0x8a9b730a, 0x9db317ca, 0xf86a298a, + 0xef424d4a, 0xd63ae00a, 0xc11284ca, 0x1d889c8a, 0x0aa0f84a, + 0x33d8550a, 0x24f031ca, 0x41290f8a, 0x56016b4a, 0x6f79c60a, + 0x7851a2ca}, + {0x00000000, 0x9fda839e, 0xe4c4017d, 0x7b1e82e3, 0x12f904bb, + 0x8d238725, 0xf63d05c6, 0x69e78658, 0x25f20976, 0xba288ae8, + 0xc136080b, 0x5eec8b95, 0x370b0dcd, 0xa8d18e53, 0xd3cf0cb0, + 0x4c158f2e, 0x4be412ec, 0xd43e9172, 0xaf201391, 0x30fa900f, + 0x591d1657, 0xc6c795c9, 0xbdd9172a, 0x220394b4, 0x6e161b9a, + 0xf1cc9804, 0x8ad21ae7, 0x15089979, 0x7cef1f21, 0xe3359cbf, + 0x982b1e5c, 0x07f19dc2, 0x97c825d8, 0x0812a646, 0x730c24a5, + 0xecd6a73b, 0x85312163, 0x1aeba2fd, 0x61f5201e, 0xfe2fa380, + 0xb23a2cae, 0x2de0af30, 0x56fe2dd3, 0xc924ae4d, 0xa0c32815, + 0x3f19ab8b, 0x44072968, 0xdbddaaf6, 0xdc2c3734, 0x43f6b4aa, + 0x38e83649, 0xa732b5d7, 0xced5338f, 0x510fb011, 0x2a1132f2, + 0xb5cbb16c, 0xf9de3e42, 0x6604bddc, 0x1d1a3f3f, 0x82c0bca1, + 0xeb273af9, 0x74fdb967, 0x0fe33b84, 0x9039b81a, 0xf4e14df1, + 0x6b3bce6f, 0x10254c8c, 0x8fffcf12, 0xe618494a, 0x79c2cad4, + 0x02dc4837, 0x9d06cba9, 0xd1134487, 0x4ec9c719, 0x35d745fa, + 0xaa0dc664, 0xc3ea403c, 0x5c30c3a2, 0x272e4141, 0xb8f4c2df, + 0xbf055f1d, 0x20dfdc83, 0x5bc15e60, 0xc41bddfe, 0xadfc5ba6, + 0x3226d838, 0x49385adb, 0xd6e2d945, 0x9af7566b, 0x052dd5f5, + 0x7e335716, 0xe1e9d488, 0x880e52d0, 0x17d4d14e, 0x6cca53ad, + 0xf310d033, 0x63296829, 0xfcf3ebb7, 0x87ed6954, 0x1837eaca, + 0x71d06c92, 0xee0aef0c, 0x95146def, 0x0aceee71, 0x46db615f, + 0xd901e2c1, 0xa21f6022, 0x3dc5e3bc, 0x542265e4, 0xcbf8e67a, + 0xb0e66499, 0x2f3ce707, 0x28cd7ac5, 0xb717f95b, 0xcc097bb8, + 0x53d3f826, 0x3a347e7e, 0xa5eefde0, 0xdef07f03, 0x412afc9d, + 0x0d3f73b3, 0x92e5f02d, 0xe9fb72ce, 0x7621f150, 0x1fc67708, + 0x801cf496, 0xfb027675, 0x64d8f5eb, 0x32b39da3, 0xad691e3d, + 0xd6779cde, 0x49ad1f40, 0x204a9918, 0xbf901a86, 0xc48e9865, + 0x5b541bfb, 0x174194d5, 0x889b174b, 0xf38595a8, 0x6c5f1636, + 0x05b8906e, 0x9a6213f0, 0xe17c9113, 0x7ea6128d, 0x79578f4f, + 0xe68d0cd1, 0x9d938e32, 0x02490dac, 0x6bae8bf4, 0xf474086a, + 0x8f6a8a89, 0x10b00917, 0x5ca58639, 0xc37f05a7, 0xb8618744, + 0x27bb04da, 0x4e5c8282, 0xd186011c, 0xaa9883ff, 0x35420061, + 0xa57bb87b, 0x3aa13be5, 0x41bfb906, 0xde653a98, 0xb782bcc0, + 0x28583f5e, 0x5346bdbd, 0xcc9c3e23, 0x8089b10d, 0x1f533293, + 0x644db070, 0xfb9733ee, 0x9270b5b6, 0x0daa3628, 0x76b4b4cb, + 0xe96e3755, 0xee9faa97, 0x71452909, 0x0a5babea, 0x95812874, + 0xfc66ae2c, 0x63bc2db2, 0x18a2af51, 0x87782ccf, 0xcb6da3e1, + 0x54b7207f, 0x2fa9a29c, 0xb0732102, 0xd994a75a, 0x464e24c4, + 0x3d50a627, 0xa28a25b9, 0xc652d052, 0x598853cc, 0x2296d12f, + 0xbd4c52b1, 0xd4abd4e9, 0x4b715777, 0x306fd594, 0xafb5560a, + 0xe3a0d924, 0x7c7a5aba, 0x0764d859, 0x98be5bc7, 0xf159dd9f, + 0x6e835e01, 0x159ddce2, 0x8a475f7c, 0x8db6c2be, 0x126c4120, + 0x6972c3c3, 0xf6a8405d, 0x9f4fc605, 0x0095459b, 0x7b8bc778, + 0xe45144e6, 0xa844cbc8, 0x379e4856, 0x4c80cab5, 0xd35a492b, + 0xbabdcf73, 0x25674ced, 0x5e79ce0e, 0xc1a34d90, 0x519af58a, + 0xce407614, 0xb55ef4f7, 0x2a847769, 0x4363f131, 0xdcb972af, + 0xa7a7f04c, 0x387d73d2, 0x7468fcfc, 0xebb27f62, 0x90acfd81, + 0x0f767e1f, 0x6691f847, 0xf94b7bd9, 0x8255f93a, 0x1d8f7aa4, + 0x1a7ee766, 0x85a464f8, 0xfebae61b, 0x61606585, 0x0887e3dd, + 0x975d6043, 0xec43e2a0, 0x7399613e, 0x3f8cee10, 0xa0566d8e, + 0xdb48ef6d, 0x44926cf3, 0x2d75eaab, 0xb2af6935, 0xc9b1ebd6, + 0x566b6848}}; + +local const z_word_t FAR crc_braid_big_table[][256] = { + {0x00000000, 0x9e83da9f, 0x7d01c4e4, 0xe3821e7b, 0xbb04f912, + 0x2587238d, 0xc6053df6, 0x5886e769, 0x7609f225, 0xe88a28ba, + 0x0b0836c1, 0x958bec5e, 0xcd0d0b37, 0x538ed1a8, 0xb00ccfd3, + 0x2e8f154c, 0xec12e44b, 0x72913ed4, 0x911320af, 0x0f90fa30, + 0x57161d59, 0xc995c7c6, 0x2a17d9bd, 0xb4940322, 0x9a1b166e, + 0x0498ccf1, 0xe71ad28a, 0x79990815, 0x211fef7c, 0xbf9c35e3, + 0x5c1e2b98, 0xc29df107, 0xd825c897, 0x46a61208, 0xa5240c73, + 0x3ba7d6ec, 0x63213185, 0xfda2eb1a, 0x1e20f561, 0x80a32ffe, + 0xae2c3ab2, 0x30afe02d, 0xd32dfe56, 0x4dae24c9, 0x1528c3a0, + 0x8bab193f, 0x68290744, 0xf6aadddb, 0x34372cdc, 0xaab4f643, + 0x4936e838, 0xd7b532a7, 0x8f33d5ce, 0x11b00f51, 0xf232112a, + 0x6cb1cbb5, 0x423edef9, 0xdcbd0466, 0x3f3f1a1d, 0xa1bcc082, + 0xf93a27eb, 0x67b9fd74, 0x843be30f, 0x1ab83990, 0xf14de1f4, + 0x6fce3b6b, 0x8c4c2510, 0x12cfff8f, 0x4a4918e6, 0xd4cac279, + 0x3748dc02, 0xa9cb069d, 0x874413d1, 0x19c7c94e, 0xfa45d735, + 0x64c60daa, 0x3c40eac3, 0xa2c3305c, 0x41412e27, 0xdfc2f4b8, + 0x1d5f05bf, 0x83dcdf20, 0x605ec15b, 0xfedd1bc4, 0xa65bfcad, + 0x38d82632, 0xdb5a3849, 0x45d9e2d6, 0x6b56f79a, 0xf5d52d05, + 0x1657337e, 0x88d4e9e1, 0xd0520e88, 0x4ed1d417, 0xad53ca6c, + 0x33d010f3, 0x29682963, 0xb7ebf3fc, 0x5469ed87, 0xcaea3718, + 0x926cd071, 0x0cef0aee, 0xef6d1495, 0x71eece0a, 0x5f61db46, + 0xc1e201d9, 0x22601fa2, 0xbce3c53d, 0xe4652254, 0x7ae6f8cb, + 0x9964e6b0, 0x07e73c2f, 0xc57acd28, 0x5bf917b7, 0xb87b09cc, + 0x26f8d353, 0x7e7e343a, 0xe0fdeea5, 0x037ff0de, 0x9dfc2a41, + 0xb3733f0d, 0x2df0e592, 0xce72fbe9, 0x50f12176, 0x0877c61f, + 0x96f41c80, 0x757602fb, 0xebf5d864, 0xa39db332, 0x3d1e69ad, + 0xde9c77d6, 0x401fad49, 0x18994a20, 0x861a90bf, 0x65988ec4, + 0xfb1b545b, 0xd5944117, 0x4b179b88, 0xa89585f3, 0x36165f6c, + 0x6e90b805, 0xf013629a, 0x13917ce1, 0x8d12a67e, 0x4f8f5779, + 0xd10c8de6, 0x328e939d, 0xac0d4902, 0xf48bae6b, 0x6a0874f4, + 0x898a6a8f, 0x1709b010, 0x3986a55c, 0xa7057fc3, 0x448761b8, + 0xda04bb27, 0x82825c4e, 0x1c0186d1, 0xff8398aa, 0x61004235, + 0x7bb87ba5, 0xe53ba13a, 0x06b9bf41, 0x983a65de, 0xc0bc82b7, + 0x5e3f5828, 0xbdbd4653, 0x233e9ccc, 0x0db18980, 0x9332531f, + 0x70b04d64, 0xee3397fb, 0xb6b57092, 0x2836aa0d, 0xcbb4b476, + 0x55376ee9, 0x97aa9fee, 0x09294571, 0xeaab5b0a, 0x74288195, + 0x2cae66fc, 0xb22dbc63, 0x51afa218, 0xcf2c7887, 0xe1a36dcb, + 0x7f20b754, 0x9ca2a92f, 0x022173b0, 0x5aa794d9, 0xc4244e46, + 0x27a6503d, 0xb9258aa2, 0x52d052c6, 0xcc538859, 0x2fd19622, + 0xb1524cbd, 0xe9d4abd4, 0x7757714b, 0x94d56f30, 0x0a56b5af, + 0x24d9a0e3, 0xba5a7a7c, 0x59d86407, 0xc75bbe98, 0x9fdd59f1, + 0x015e836e, 0xe2dc9d15, 0x7c5f478a, 0xbec2b68d, 0x20416c12, + 0xc3c37269, 0x5d40a8f6, 0x05c64f9f, 0x9b459500, 0x78c78b7b, + 0xe64451e4, 0xc8cb44a8, 0x56489e37, 0xb5ca804c, 0x2b495ad3, + 0x73cfbdba, 0xed4c6725, 0x0ece795e, 0x904da3c1, 0x8af59a51, + 0x147640ce, 0xf7f45eb5, 0x6977842a, 0x31f16343, 0xaf72b9dc, + 0x4cf0a7a7, 0xd2737d38, 0xfcfc6874, 0x627fb2eb, 0x81fdac90, + 0x1f7e760f, 0x47f89166, 0xd97b4bf9, 0x3af95582, 0xa47a8f1d, + 0x66e77e1a, 0xf864a485, 0x1be6bafe, 0x85656061, 0xdde38708, + 0x43605d97, 0xa0e243ec, 0x3e619973, 0x10ee8c3f, 0x8e6d56a0, + 0x6def48db, 0xf36c9244, 0xabea752d, 0x3569afb2, 0xd6ebb1c9, + 0x48686b56}, + {0x00000000, 0xc0642817, 0x80c9502e, 0x40ad7839, 0x0093a15c, + 0xc0f7894b, 0x805af172, 0x403ed965, 0x002643b9, 0xc0426bae, + 0x80ef1397, 0x408b3b80, 0x00b5e2e5, 0xc0d1caf2, 0x807cb2cb, + 0x40189adc, 0x414af7a9, 0x812edfbe, 0xc183a787, 0x01e78f90, + 0x41d956f5, 0x81bd7ee2, 0xc11006db, 0x01742ecc, 0x416cb410, + 0x81089c07, 0xc1a5e43e, 0x01c1cc29, 0x41ff154c, 0x819b3d5b, + 0xc1364562, 0x01526d75, 0xc3929f88, 0x03f6b79f, 0x435bcfa6, + 0x833fe7b1, 0xc3013ed4, 0x036516c3, 0x43c86efa, 0x83ac46ed, + 0xc3b4dc31, 0x03d0f426, 0x437d8c1f, 0x8319a408, 0xc3277d6d, + 0x0343557a, 0x43ee2d43, 0x838a0554, 0x82d86821, 0x42bc4036, + 0x0211380f, 0xc2751018, 0x824bc97d, 0x422fe16a, 0x02829953, + 0xc2e6b144, 0x82fe2b98, 0x429a038f, 0x02377bb6, 0xc25353a1, + 0x826d8ac4, 0x4209a2d3, 0x02a4daea, 0xc2c0f2fd, 0xc7234eca, + 0x074766dd, 0x47ea1ee4, 0x878e36f3, 0xc7b0ef96, 0x07d4c781, + 0x4779bfb8, 0x871d97af, 0xc7050d73, 0x07612564, 0x47cc5d5d, + 0x87a8754a, 0xc796ac2f, 0x07f28438, 0x475ffc01, 0x873bd416, + 0x8669b963, 0x460d9174, 0x06a0e94d, 0xc6c4c15a, 0x86fa183f, + 0x469e3028, 0x06334811, 0xc6576006, 0x864ffada, 0x462bd2cd, + 0x0686aaf4, 0xc6e282e3, 0x86dc5b86, 0x46b87391, 0x06150ba8, + 0xc67123bf, 0x04b1d142, 0xc4d5f955, 0x8478816c, 0x441ca97b, + 0x0422701e, 0xc4465809, 0x84eb2030, 0x448f0827, 0x049792fb, + 0xc4f3baec, 0x845ec2d5, 0x443aeac2, 0x040433a7, 0xc4601bb0, + 0x84cd6389, 0x44a94b9e, 0x45fb26eb, 0x859f0efc, 0xc53276c5, + 0x05565ed2, 0x456887b7, 0x850cafa0, 0xc5a1d799, 0x05c5ff8e, + 0x45dd6552, 0x85b94d45, 0xc514357c, 0x05701d6b, 0x454ec40e, + 0x852aec19, 0xc5879420, 0x05e3bc37, 0xcf41ed4f, 0x0f25c558, + 0x4f88bd61, 0x8fec9576, 0xcfd24c13, 0x0fb66404, 0x4f1b1c3d, + 0x8f7f342a, 0xcf67aef6, 0x0f0386e1, 0x4faefed8, 0x8fcad6cf, + 0xcff40faa, 0x0f9027bd, 0x4f3d5f84, 0x8f597793, 0x8e0b1ae6, + 0x4e6f32f1, 0x0ec24ac8, 0xcea662df, 0x8e98bbba, 0x4efc93ad, + 0x0e51eb94, 0xce35c383, 0x8e2d595f, 0x4e497148, 0x0ee40971, + 0xce802166, 0x8ebef803, 0x4edad014, 0x0e77a82d, 0xce13803a, + 0x0cd372c7, 0xccb75ad0, 0x8c1a22e9, 0x4c7e0afe, 0x0c40d39b, + 0xcc24fb8c, 0x8c8983b5, 0x4cedaba2, 0x0cf5317e, 0xcc911969, + 0x8c3c6150, 0x4c584947, 0x0c669022, 0xcc02b835, 0x8cafc00c, + 0x4ccbe81b, 0x4d99856e, 0x8dfdad79, 0xcd50d540, 0x0d34fd57, + 0x4d0a2432, 0x8d6e0c25, 0xcdc3741c, 0x0da75c0b, 0x4dbfc6d7, + 0x8ddbeec0, 0xcd7696f9, 0x0d12beee, 0x4d2c678b, 0x8d484f9c, + 0xcde537a5, 0x0d811fb2, 0x0862a385, 0xc8068b92, 0x88abf3ab, + 0x48cfdbbc, 0x08f102d9, 0xc8952ace, 0x883852f7, 0x485c7ae0, + 0x0844e03c, 0xc820c82b, 0x888db012, 0x48e99805, 0x08d74160, + 0xc8b36977, 0x881e114e, 0x487a3959, 0x4928542c, 0x894c7c3b, + 0xc9e10402, 0x09852c15, 0x49bbf570, 0x89dfdd67, 0xc972a55e, + 0x09168d49, 0x490e1795, 0x896a3f82, 0xc9c747bb, 0x09a36fac, + 0x499db6c9, 0x89f99ede, 0xc954e6e7, 0x0930cef0, 0xcbf03c0d, + 0x0b94141a, 0x4b396c23, 0x8b5d4434, 0xcb639d51, 0x0b07b546, + 0x4baacd7f, 0x8bcee568, 0xcbd67fb4, 0x0bb257a3, 0x4b1f2f9a, + 0x8b7b078d, 0xcb45dee8, 0x0b21f6ff, 0x4b8c8ec6, 0x8be8a6d1, + 0x8abacba4, 0x4adee3b3, 0x0a739b8a, 0xca17b39d, 0x8a296af8, + 0x4a4d42ef, 0x0ae03ad6, 0xca8412c1, 0x8a9c881d, 0x4af8a00a, + 0x0a55d833, 0xca31f024, 0x8a0f2941, 0x4a6b0156, 0x0ac6796f, + 0xcaa25178}, + {0x00000000, 0xd4ea739b, 0xe9d396ed, 0x3d39e576, 0x93a15c00, + 0x474b2f9b, 0x7a72caed, 0xae98b976, 0x2643b900, 0xf2a9ca9b, + 0xcf902fed, 0x1b7a5c76, 0xb5e2e500, 0x6108969b, 0x5c3173ed, + 0x88db0076, 0x4c867201, 0x986c019a, 0xa555e4ec, 0x71bf9777, + 0xdf272e01, 0x0bcd5d9a, 0x36f4b8ec, 0xe21ecb77, 0x6ac5cb01, + 0xbe2fb89a, 0x83165dec, 0x57fc2e77, 0xf9649701, 0x2d8ee49a, + 0x10b701ec, 0xc45d7277, 0x980ce502, 0x4ce69699, 0x71df73ef, + 0xa5350074, 0x0badb902, 0xdf47ca99, 0xe27e2fef, 0x36945c74, + 0xbe4f5c02, 0x6aa52f99, 0x579ccaef, 0x8376b974, 0x2dee0002, + 0xf9047399, 0xc43d96ef, 0x10d7e574, 0xd48a9703, 0x0060e498, + 0x3d5901ee, 0xe9b37275, 0x472bcb03, 0x93c1b898, 0xaef85dee, + 0x7a122e75, 0xf2c92e03, 0x26235d98, 0x1b1ab8ee, 0xcff0cb75, + 0x61687203, 0xb5820198, 0x88bbe4ee, 0x5c519775, 0x3019ca05, + 0xe4f3b99e, 0xd9ca5ce8, 0x0d202f73, 0xa3b89605, 0x7752e59e, + 0x4a6b00e8, 0x9e817373, 0x165a7305, 0xc2b0009e, 0xff89e5e8, + 0x2b639673, 0x85fb2f05, 0x51115c9e, 0x6c28b9e8, 0xb8c2ca73, + 0x7c9fb804, 0xa875cb9f, 0x954c2ee9, 0x41a65d72, 0xef3ee404, + 0x3bd4979f, 0x06ed72e9, 0xd2070172, 0x5adc0104, 0x8e36729f, + 0xb30f97e9, 0x67e5e472, 0xc97d5d04, 0x1d972e9f, 0x20aecbe9, + 0xf444b872, 0xa8152f07, 0x7cff5c9c, 0x41c6b9ea, 0x952cca71, + 0x3bb47307, 0xef5e009c, 0xd267e5ea, 0x068d9671, 0x8e569607, + 0x5abce59c, 0x678500ea, 0xb36f7371, 0x1df7ca07, 0xc91db99c, + 0xf4245cea, 0x20ce2f71, 0xe4935d06, 0x30792e9d, 0x0d40cbeb, + 0xd9aab870, 0x77320106, 0xa3d8729d, 0x9ee197eb, 0x4a0be470, + 0xc2d0e406, 0x163a979d, 0x2b0372eb, 0xffe90170, 0x5171b806, + 0x859bcb9d, 0xb8a22eeb, 0x6c485d70, 0x6032940b, 0xb4d8e790, + 0x89e102e6, 0x5d0b717d, 0xf393c80b, 0x2779bb90, 0x1a405ee6, + 0xceaa2d7d, 0x46712d0b, 0x929b5e90, 0xafa2bbe6, 0x7b48c87d, + 0xd5d0710b, 0x013a0290, 0x3c03e7e6, 0xe8e9947d, 0x2cb4e60a, + 0xf85e9591, 0xc56770e7, 0x118d037c, 0xbf15ba0a, 0x6bffc991, + 0x56c62ce7, 0x822c5f7c, 0x0af75f0a, 0xde1d2c91, 0xe324c9e7, + 0x37ceba7c, 0x9956030a, 0x4dbc7091, 0x708595e7, 0xa46fe67c, + 0xf83e7109, 0x2cd40292, 0x11ede7e4, 0xc507947f, 0x6b9f2d09, + 0xbf755e92, 0x824cbbe4, 0x56a6c87f, 0xde7dc809, 0x0a97bb92, + 0x37ae5ee4, 0xe3442d7f, 0x4ddc9409, 0x9936e792, 0xa40f02e4, + 0x70e5717f, 0xb4b80308, 0x60527093, 0x5d6b95e5, 0x8981e67e, + 0x27195f08, 0xf3f32c93, 0xcecac9e5, 0x1a20ba7e, 0x92fbba08, + 0x4611c993, 0x7b282ce5, 0xafc25f7e, 0x015ae608, 0xd5b09593, + 0xe88970e5, 0x3c63037e, 0x502b5e0e, 0x84c12d95, 0xb9f8c8e3, + 0x6d12bb78, 0xc38a020e, 0x17607195, 0x2a5994e3, 0xfeb3e778, + 0x7668e70e, 0xa2829495, 0x9fbb71e3, 0x4b510278, 0xe5c9bb0e, + 0x3123c895, 0x0c1a2de3, 0xd8f05e78, 0x1cad2c0f, 0xc8475f94, + 0xf57ebae2, 0x2194c979, 0x8f0c700f, 0x5be60394, 0x66dfe6e2, + 0xb2359579, 0x3aee950f, 0xee04e694, 0xd33d03e2, 0x07d77079, + 0xa94fc90f, 0x7da5ba94, 0x409c5fe2, 0x94762c79, 0xc827bb0c, + 0x1ccdc897, 0x21f42de1, 0xf51e5e7a, 0x5b86e70c, 0x8f6c9497, + 0xb25571e1, 0x66bf027a, 0xee64020c, 0x3a8e7197, 0x07b794e1, + 0xd35de77a, 0x7dc55e0c, 0xa92f2d97, 0x9416c8e1, 0x40fcbb7a, + 0x84a1c90d, 0x504bba96, 0x6d725fe0, 0xb9982c7b, 0x1700950d, + 0xc3eae696, 0xfed303e0, 0x2a39707b, 0xa2e2700d, 0x76080396, + 0x4b31e6e0, 0x9fdb957b, 0x31432c0d, 0xe5a95f96, 0xd890bae0, + 0x0c7ac97b}, + {0x00000000, 0x27652581, 0x0fcc3bd9, 0x28a91e58, 0x5f9e0669, + 0x78fb23e8, 0x50523db0, 0x77371831, 0xbe3c0dd2, 0x99592853, + 0xb1f0360b, 0x9695138a, 0xe1a20bbb, 0xc6c72e3a, 0xee6e3062, + 0xc90b15e3, 0x3d7f6b7f, 0x1a1a4efe, 0x32b350a6, 0x15d67527, + 0x62e16d16, 0x45844897, 0x6d2d56cf, 0x4a48734e, 0x834366ad, + 0xa426432c, 0x8c8f5d74, 0xabea78f5, 0xdcdd60c4, 0xfbb84545, + 0xd3115b1d, 0xf4747e9c, 0x7afed6fe, 0x5d9bf37f, 0x7532ed27, + 0x5257c8a6, 0x2560d097, 0x0205f516, 0x2aaceb4e, 0x0dc9cecf, + 0xc4c2db2c, 0xe3a7fead, 0xcb0ee0f5, 0xec6bc574, 0x9b5cdd45, + 0xbc39f8c4, 0x9490e69c, 0xb3f5c31d, 0x4781bd81, 0x60e49800, + 0x484d8658, 0x6f28a3d9, 0x181fbbe8, 0x3f7a9e69, 0x17d38031, + 0x30b6a5b0, 0xf9bdb053, 0xded895d2, 0xf6718b8a, 0xd114ae0b, + 0xa623b63a, 0x814693bb, 0xa9ef8de3, 0x8e8aa862, 0xb5fadc26, + 0x929ff9a7, 0xba36e7ff, 0x9d53c27e, 0xea64da4f, 0xcd01ffce, + 0xe5a8e196, 0xc2cdc417, 0x0bc6d1f4, 0x2ca3f475, 0x040aea2d, + 0x236fcfac, 0x5458d79d, 0x733df21c, 0x5b94ec44, 0x7cf1c9c5, + 0x8885b759, 0xafe092d8, 0x87498c80, 0xa02ca901, 0xd71bb130, + 0xf07e94b1, 0xd8d78ae9, 0xffb2af68, 0x36b9ba8b, 0x11dc9f0a, + 0x39758152, 0x1e10a4d3, 0x6927bce2, 0x4e429963, 0x66eb873b, + 0x418ea2ba, 0xcf040ad8, 0xe8612f59, 0xc0c83101, 0xe7ad1480, + 0x909a0cb1, 0xb7ff2930, 0x9f563768, 0xb83312e9, 0x7138070a, + 0x565d228b, 0x7ef43cd3, 0x59911952, 0x2ea60163, 0x09c324e2, + 0x216a3aba, 0x060f1f3b, 0xf27b61a7, 0xd51e4426, 0xfdb75a7e, + 0xdad27fff, 0xade567ce, 0x8a80424f, 0xa2295c17, 0x854c7996, + 0x4c476c75, 0x6b2249f4, 0x438b57ac, 0x64ee722d, 0x13d96a1c, + 0x34bc4f9d, 0x1c1551c5, 0x3b707444, 0x6af5b94d, 0x4d909ccc, + 0x65398294, 0x425ca715, 0x356bbf24, 0x120e9aa5, 0x3aa784fd, + 0x1dc2a17c, 0xd4c9b49f, 0xf3ac911e, 0xdb058f46, 0xfc60aac7, + 0x8b57b2f6, 0xac329777, 0x849b892f, 0xa3feacae, 0x578ad232, + 0x70eff7b3, 0x5846e9eb, 0x7f23cc6a, 0x0814d45b, 0x2f71f1da, + 0x07d8ef82, 0x20bdca03, 0xe9b6dfe0, 0xced3fa61, 0xe67ae439, + 0xc11fc1b8, 0xb628d989, 0x914dfc08, 0xb9e4e250, 0x9e81c7d1, + 0x100b6fb3, 0x376e4a32, 0x1fc7546a, 0x38a271eb, 0x4f9569da, + 0x68f04c5b, 0x40595203, 0x673c7782, 0xae376261, 0x895247e0, + 0xa1fb59b8, 0x869e7c39, 0xf1a96408, 0xd6cc4189, 0xfe655fd1, + 0xd9007a50, 0x2d7404cc, 0x0a11214d, 0x22b83f15, 0x05dd1a94, + 0x72ea02a5, 0x558f2724, 0x7d26397c, 0x5a431cfd, 0x9348091e, + 0xb42d2c9f, 0x9c8432c7, 0xbbe11746, 0xccd60f77, 0xebb32af6, + 0xc31a34ae, 0xe47f112f, 0xdf0f656b, 0xf86a40ea, 0xd0c35eb2, + 0xf7a67b33, 0x80916302, 0xa7f44683, 0x8f5d58db, 0xa8387d5a, + 0x613368b9, 0x46564d38, 0x6eff5360, 0x499a76e1, 0x3ead6ed0, + 0x19c84b51, 0x31615509, 0x16047088, 0xe2700e14, 0xc5152b95, + 0xedbc35cd, 0xcad9104c, 0xbdee087d, 0x9a8b2dfc, 0xb22233a4, + 0x95471625, 0x5c4c03c6, 0x7b292647, 0x5380381f, 0x74e51d9e, + 0x03d205af, 0x24b7202e, 0x0c1e3e76, 0x2b7b1bf7, 0xa5f1b395, + 0x82949614, 0xaa3d884c, 0x8d58adcd, 0xfa6fb5fc, 0xdd0a907d, + 0xf5a38e25, 0xd2c6aba4, 0x1bcdbe47, 0x3ca89bc6, 0x1401859e, + 0x3364a01f, 0x4453b82e, 0x63369daf, 0x4b9f83f7, 0x6cfaa676, + 0x988ed8ea, 0xbfebfd6b, 0x9742e333, 0xb027c6b2, 0xc710de83, + 0xe075fb02, 0xc8dce55a, 0xefb9c0db, 0x26b2d538, 0x01d7f0b9, + 0x297eeee1, 0x0e1bcb60, 0x792cd351, 0x5e49f6d0, 0x76e0e888, + 0x5185cd09}}; + +#endif + +#endif + +#endif + +local const z_crc_t FAR x2n_table[] = { + 0x40000000, 0x20000000, 0x08000000, 0x00800000, 0x00008000, + 0xedb88320, 0xb1e6b092, 0xa06a2517, 0xed627dae, 0x88d14467, + 0xd7bbfe6a, 0xec447f11, 0x8e7ea170, 0x6427800e, 0x4d47bae0, + 0x09fe548f, 0x83852d0f, 0x30362f1a, 0x7b5a9cc3, 0x31fec169, + 0x9fec022a, 0x6c8dedc4, 0x15d6874d, 0x5fde7a4e, 0xbad90e37, + 0x2e4e5eef, 0x4eaba214, 0xa8a472c0, 0x429a969e, 0x148d302a, + 0xc40ba6d0, 0xc4e22c3c}; diff --git a/src/common/dep/zlib/deflate.c b/src/common/dep/zlib/deflate.c index 1ec76144..d7d2c7c1 100644 --- a/src/common/dep/zlib/deflate.c +++ b/src/common/dep/zlib/deflate.c @@ -1,5 +1,5 @@ /* deflate.c -- compress data using the deflation algorithm - * Copyright (C) 1995-2017 Jean-loup Gailly and Mark Adler + * Copyright (C) 1995-2026 Jean-loup Gailly and Mark Adler * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -37,7 +37,7 @@ * REFERENCES * * Deutsch, L.P.,"DEFLATE Compressed Data Format Specification". - * Available in http://tools.ietf.org/html/rfc1951 + * Available at https://datatracker.ietf.org/doc/html/rfc1951 * * A description of the Rabin and Karp algorithm is given in the book * "Algorithms" by R. Sedgewick, Addison-Wesley, p252. @@ -52,7 +52,7 @@ #include "deflate.h" const char deflate_copyright[] = - " deflate 1.2.11 Copyright 1995-2017 Jean-loup Gailly and Mark Adler "; + " deflate 1.3.2 Copyright 1995-2026 Jean-loup Gailly and Mark Adler "; /* If you use the zlib library in a product, an acknowledgment is welcome in the documentation of your product. If for some reason you cannot @@ -60,9 +60,6 @@ const char deflate_copyright[] = copyright string in the executable of your product. */ -/* =========================================================================== - * Function prototypes. - */ typedef enum { need_more, /* block not completed, need more input or more output */ block_done, /* block flush performed */ @@ -70,35 +67,16 @@ typedef enum { finish_done /* finish done, accept no more input or output */ } block_state; -typedef block_state (*compress_func) OF((deflate_state *s, int flush)); +typedef block_state (*compress_func)(deflate_state *s, int flush); /* Compression function. Returns the block state after the call. */ -local int deflateStateCheck OF((z_streamp strm)); -local void slide_hash OF((deflate_state *s)); -local void fill_window OF((deflate_state *s)); -local block_state deflate_stored OF((deflate_state *s, int flush)); -local block_state deflate_fast OF((deflate_state *s, int flush)); +local block_state deflate_stored(deflate_state *s, int flush); +local block_state deflate_fast(deflate_state *s, int flush); #ifndef FASTEST -local block_state deflate_slow OF((deflate_state *s, int flush)); -#endif -local block_state deflate_rle OF((deflate_state *s, int flush)); -local block_state deflate_huff OF((deflate_state *s, int flush)); -local void lm_init OF((deflate_state *s)); -local void putShortMSB OF((deflate_state *s, uInt b)); -local void flush_pending OF((z_streamp strm)); -local unsigned read_buf OF((z_streamp strm, Bytef *buf, unsigned size)); -#ifdef ASMV -# pragma message("Assembler code may have bugs -- use at your own risk") - void match_init OF((void)); /* asm code initialization */ - uInt longest_match OF((deflate_state *s, IPos cur_match)); -#else -local uInt longest_match OF((deflate_state *s, IPos cur_match)); -#endif - -#ifdef ZLIB_DEBUG -local void check_match OF((deflate_state *s, IPos start, IPos match, - int length)); +local block_state deflate_slow(deflate_state *s, int flush); #endif +local block_state deflate_rle(deflate_state *s, int flush); +local block_state deflate_huff(deflate_state *s, int flush); /* =========================================================================== * Local data @@ -160,7 +138,7 @@ local const config configuration_table[10] = { * characters, so that a running hash key can be computed from the previous * key instead of complete recalculation each time. */ -#define UPDATE_HASH(s,h,c) (h = (((h)<hash_shift) ^ (c)) & s->hash_mask) +#define UPDATE_HASH(s,h,c) (h = (((h) << s->hash_shift) ^ (c)) & s->hash_mask) /* =========================================================================== @@ -190,17 +168,23 @@ local const config configuration_table[10] = { * prev[] will be initialized on the fly. */ #define CLEAR_HASH(s) \ - s->head[s->hash_size-1] = NIL; \ - zmemzero((Bytef *)s->head, (unsigned)(s->hash_size-1)*sizeof(*s->head)); + do { \ + s->head[s->hash_size - 1] = NIL; \ + zmemzero(s->head, (unsigned)(s->hash_size - 1)*sizeof(*s->head)); \ + s->slid = 0; \ + } while (0) /* =========================================================================== * Slide the hash table when sliding the window down (could be avoided with 32 * bit values at the expense of memory usage). We slide even when level == 0 to * keep the hash table consistent if we switch back to level > 0 later. */ -local void slide_hash(s) - deflate_state *s; -{ +#if defined(__has_feature) +# if __has_feature(memory_sanitizer) + __attribute__((no_sanitize("memory"))) +# endif +#endif +local void slide_hash(deflate_state *s) { unsigned n, m; Posf *p; uInt wsize = s->w_size; @@ -211,8 +195,8 @@ local void slide_hash(s) m = *--p; *p = (Pos)(m >= wsize ? m - wsize : NIL); } while (--n); - n = wsize; #ifndef FASTEST + n = wsize; p = &s->prev[n]; do { m = *--p; @@ -222,41 +206,191 @@ local void slide_hash(s) */ } while (--n); #endif + s->slid = 1; +} + +/* =========================================================================== + * Read a new buffer from the current input stream, update the adler32 + * and total number of bytes read. All deflate() input goes through + * this function so some applications may wish to modify it to avoid + * allocating a large strm->next_in buffer and copying from it. + * (See also flush_pending()). + */ +local unsigned read_buf(z_streamp strm, Bytef *buf, unsigned size) { + unsigned len = strm->avail_in; + + if (len > size) len = size; + if (len == 0) return 0; + + strm->avail_in -= len; + + zmemcpy(buf, strm->next_in, len); + if (strm->state->wrap == 1) { + strm->adler = adler32(strm->adler, buf, len); + } +#ifdef GZIP + else if (strm->state->wrap == 2) { + strm->adler = crc32(strm->adler, buf, len); + } +#endif + strm->next_in += len; + strm->total_in += len; + + return len; +} + +/* =========================================================================== + * Fill the window when the lookahead becomes insufficient. + * Updates strstart and lookahead. + * + * IN assertion: lookahead < MIN_LOOKAHEAD + * OUT assertions: strstart <= window_size-MIN_LOOKAHEAD + * At least one byte has been read, or avail_in == 0; reads are + * performed for at least two bytes (required for the zip translate_eol + * option -- not supported here). + */ +local void fill_window(deflate_state *s) { + unsigned n; + unsigned more; /* Amount of free space at the end of the window. */ + uInt wsize = s->w_size; + + Assert(s->lookahead < MIN_LOOKAHEAD, "already enough lookahead"); + + do { + more = (unsigned)(s->window_size -(ulg)s->lookahead -(ulg)s->strstart); + + /* Deal with !@#$% 64K limit: */ +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4127) +#endif + if (sizeof(int) <= 2) { +#ifdef _MSC_VER +#pragma warning(pop) +#endif + if (more == 0 && s->strstart == 0 && s->lookahead == 0) { + more = wsize; + + } else if (more == (unsigned)(-1)) { + /* Very unlikely, but possible on 16 bit machine if + * strstart == 0 && lookahead == 1 (input done a byte at time) + */ + more--; + } + } + + /* If the window is almost full and there is insufficient lookahead, + * move the upper half to the lower one to make room in the upper half. + */ + if (s->strstart >= wsize + MAX_DIST(s)) { + + zmemcpy(s->window, s->window + wsize, (unsigned)wsize - more); + s->match_start -= wsize; + s->strstart -= wsize; /* we now have strstart >= MAX_DIST */ + s->block_start -= (long) wsize; + if (s->insert > s->strstart) + s->insert = s->strstart; + slide_hash(s); + more += wsize; + } + if (s->strm->avail_in == 0) break; + + /* If there was no sliding: + * strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 && + * more == window_size - lookahead - strstart + * => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1) + * => more >= window_size - 2*WSIZE + 2 + * In the BIG_MEM or MMAP case (not yet supported), + * window_size == input_size + MIN_LOOKAHEAD && + * strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD. + * Otherwise, window_size == 2*WSIZE so more >= 2. + * If there was sliding, more >= WSIZE. So in all cases, more >= 2. + */ + Assert(more >= 2, "more < 2"); + + n = read_buf(s->strm, s->window + s->strstart + s->lookahead, more); + s->lookahead += n; + + /* Initialize the hash value now that we have some input: */ + if (s->lookahead + s->insert >= MIN_MATCH) { + uInt str = s->strstart - s->insert; + s->ins_h = s->window[str]; + UPDATE_HASH(s, s->ins_h, s->window[str + 1]); +#if MIN_MATCH != 3 + Call UPDATE_HASH() MIN_MATCH-3 more times +#endif + while (s->insert) { + UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); +#ifndef FASTEST + s->prev[str & s->w_mask] = s->head[s->ins_h]; +#endif + s->head[s->ins_h] = (Pos)str; + str++; + s->insert--; + if (s->lookahead + s->insert < MIN_MATCH) + break; + } + } + /* If the whole input has less than MIN_MATCH bytes, ins_h is garbage, + * but this is not important since only literal bytes will be emitted. + */ + + } while (s->lookahead < MIN_LOOKAHEAD && s->strm->avail_in != 0); + + /* If the WIN_INIT bytes after the end of the current data have never been + * written, then zero those bytes in order to avoid memory check reports of + * the use of uninitialized (or uninitialised as Julian writes) bytes by + * the longest match routines. Update the high water mark for the next + * time through here. WIN_INIT is set to MAX_MATCH since the longest match + * routines allow scanning to strstart + MAX_MATCH, ignoring lookahead. + */ + if (s->high_water < s->window_size) { + ulg curr = s->strstart + (ulg)(s->lookahead); + ulg init; + + if (s->high_water < curr) { + /* Previous high water mark below current data -- zero WIN_INIT + * bytes or up to end of window, whichever is less. + */ + init = s->window_size - curr; + if (init > WIN_INIT) + init = WIN_INIT; + zmemzero(s->window + curr, (unsigned)init); + s->high_water = curr + init; + } + else if (s->high_water < (ulg)curr + WIN_INIT) { + /* High water mark at or above current data, but below current data + * plus WIN_INIT -- zero out to current data plus WIN_INIT, or up + * to end of window, whichever is less. + */ + init = (ulg)curr + WIN_INIT - s->high_water; + if (init > s->window_size - s->high_water) + init = s->window_size - s->high_water; + zmemzero(s->window + s->high_water, (unsigned)init); + s->high_water += init; + } + } + + Assert((ulg)s->strstart <= s->window_size - MIN_LOOKAHEAD, + "not enough room for search"); } /* ========================================================================= */ -int ZEXPORT deflateInit_(strm, level, version, stream_size) - z_streamp strm; - int level; - const char *version; - int stream_size; -{ +int ZEXPORT deflateInit_(z_streamp strm, int level, const char *version, + int stream_size) { return deflateInit2_(strm, level, Z_DEFLATED, MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, version, stream_size); /* To do: ignore strm->next_in if we use it as window */ } /* ========================================================================= */ -int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy, - version, stream_size) - z_streamp strm; - int level; - int method; - int windowBits; - int memLevel; - int strategy; - const char *version; - int stream_size; -{ +int ZEXPORT deflateInit2_(z_streamp strm, int level, int method, + int windowBits, int memLevel, int strategy, + const char *version, int stream_size) { deflate_state *s; int wrap = 1; static const char my_version[] = ZLIB_VERSION; - ushf *overlay; - /* We overlay pending_buf and d_buf+l_buf. This works since the average - * output size for (length,distance) codes is <= 24 bits. - */ - if (version == Z_NULL || version[0] != my_version[0] || stream_size != sizeof(z_stream)) { return Z_VERSION_ERROR; @@ -287,6 +421,8 @@ int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy, if (windowBits < 0) { /* suppress zlib wrapper */ wrap = 0; + if (windowBits < -15) + return Z_STREAM_ERROR; windowBits = -windowBits; } #ifdef GZIP @@ -303,6 +439,7 @@ int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy, if (windowBits == 8) windowBits = 9; /* until 256-byte window bug fixed */ s = (deflate_state *) ZALLOC(strm, 1, sizeof(deflate_state)); if (s == Z_NULL) return Z_MEM_ERROR; + zmemzero(s, sizeof(deflate_state)); strm->state = (struct internal_state FAR *)s; s->strm = strm; s->status = INIT_STATE; /* to pass state test in deflateReset() */ @@ -316,7 +453,7 @@ int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy, s->hash_bits = (uInt)memLevel + 7; s->hash_size = 1 << s->hash_bits; s->hash_mask = s->hash_size - 1; - s->hash_shift = ((s->hash_bits+MIN_MATCH-1)/MIN_MATCH); + s->hash_shift = ((s->hash_bits + MIN_MATCH-1) / MIN_MATCH); s->window = (Bytef *) ZALLOC(strm, s->w_size, 2*sizeof(Byte)); s->prev = (Posf *) ZALLOC(strm, s->w_size, sizeof(Pos)); @@ -326,9 +463,47 @@ int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy, s->lit_bufsize = 1 << (memLevel + 6); /* 16K elements by default */ - overlay = (ushf *) ZALLOC(strm, s->lit_bufsize, sizeof(ush)+2); - s->pending_buf = (uchf *) overlay; - s->pending_buf_size = (ulg)s->lit_bufsize * (sizeof(ush)+2L); + /* We overlay pending_buf and sym_buf. This works since the average size + * for length/distance pairs over any compressed block is assured to be 31 + * bits or less. + * + * Analysis: The longest fixed codes are a length code of 8 bits plus 5 + * extra bits, for lengths 131 to 257. The longest fixed distance codes are + * 5 bits plus 13 extra bits, for distances 16385 to 32768. The longest + * possible fixed-codes length/distance pair is then 31 bits total. + * + * sym_buf starts one-fourth of the way into pending_buf. So there are + * three bytes in sym_buf for every four bytes in pending_buf. Each symbol + * in sym_buf is three bytes -- two for the distance and one for the + * literal/length. As each symbol is consumed, the pointer to the next + * sym_buf value to read moves forward three bytes. From that symbol, up to + * 31 bits are written to pending_buf. The closest the written pending_buf + * bits gets to the next sym_buf symbol to read is just before the last + * code is written. At that time, 31*(n - 2) bits have been written, just + * after 24*(n - 2) bits have been consumed from sym_buf. sym_buf starts at + * 8*n bits into pending_buf. (Note that the symbol buffer fills when n - 1 + * symbols are written.) The closest the writing gets to what is unread is + * then n + 14 bits. Here n is lit_bufsize, which is 16384 by default, and + * can range from 128 to 32768. + * + * Therefore, at a minimum, there are 142 bits of space between what is + * written and what is read in the overlain buffers, so the symbols cannot + * be overwritten by the compressed data. That space is actually 139 bits, + * due to the three-bit fixed-code block header. + * + * That covers the case where either Z_FIXED is specified, forcing fixed + * codes, or when the use of fixed codes is chosen, because that choice + * results in a smaller compressed block than dynamic codes. That latter + * condition then assures that the above analysis also covers all dynamic + * blocks. A dynamic-code block will only be chosen to be emitted if it has + * fewer bits than a fixed-code block would for the same set of symbols. + * Therefore its average symbol length is assured to be less than 31. So + * the compressed data for a dynamic block also cannot overwrite the + * symbols from which it is being constructed. + */ + + s->pending_buf = (uchf *) ZALLOC(strm, s->lit_bufsize, LIT_BUFS); + s->pending_buf_size = (ulg)s->lit_bufsize * 4; if (s->window == Z_NULL || s->prev == Z_NULL || s->head == Z_NULL || s->pending_buf == Z_NULL) { @@ -337,8 +512,18 @@ int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy, deflateEnd (strm); return Z_MEM_ERROR; } - s->d_buf = overlay + s->lit_bufsize/sizeof(ush); - s->l_buf = s->pending_buf + (1+sizeof(ush))*s->lit_bufsize; +#ifdef LIT_MEM + s->d_buf = (ushf *)(s->pending_buf + (s->lit_bufsize << 1)); + s->l_buf = s->pending_buf + (s->lit_bufsize << 2); + s->sym_end = s->lit_bufsize - 1; +#else + s->sym_buf = s->pending_buf + s->lit_bufsize; + s->sym_end = (s->lit_bufsize - 1) * 3; +#endif + /* We avoid equality with lit_bufsize*3 because of wraparound at 64K + * on 16 bit machines and because stored blocks are restricted to + * 64K-1 bytes. + */ s->level = level; s->strategy = strategy; @@ -350,9 +535,7 @@ int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy, /* ========================================================================= * Check for a valid deflate stream state. Return 0 if ok, 1 if not. */ -local int deflateStateCheck (strm) - z_streamp strm; -{ +local int deflateStateCheck(z_streamp strm) { deflate_state *s; if (strm == Z_NULL || strm->zalloc == (alloc_func)0 || strm->zfree == (free_func)0) @@ -373,11 +556,8 @@ local int deflateStateCheck (strm) } /* ========================================================================= */ -int ZEXPORT deflateSetDictionary (strm, dictionary, dictLength) - z_streamp strm; - const Bytef *dictionary; - uInt dictLength; -{ +int ZEXPORT deflateSetDictionary(z_streamp strm, const Bytef *dictionary, + uInt dictLength) { deflate_state *s; uInt str, n; int wrap; @@ -442,11 +622,8 @@ int ZEXPORT deflateSetDictionary (strm, dictionary, dictLength) } /* ========================================================================= */ -int ZEXPORT deflateGetDictionary (strm, dictionary, dictLength) - z_streamp strm; - Bytef *dictionary; - uInt *dictLength; -{ +int ZEXPORT deflateGetDictionary(z_streamp strm, Bytef *dictionary, + uInt *dictLength) { deflate_state *s; uInt len; @@ -464,9 +641,7 @@ int ZEXPORT deflateGetDictionary (strm, dictionary, dictLength) } /* ========================================================================= */ -int ZEXPORT deflateResetKeep (strm) - z_streamp strm; -{ +int ZEXPORT deflateResetKeep(z_streamp strm) { deflate_state *s; if (deflateStateCheck(strm)) { @@ -488,23 +663,45 @@ int ZEXPORT deflateResetKeep (strm) #ifdef GZIP s->wrap == 2 ? GZIP_STATE : #endif - s->wrap ? INIT_STATE : BUSY_STATE; + INIT_STATE; strm->adler = #ifdef GZIP s->wrap == 2 ? crc32(0L, Z_NULL, 0) : #endif adler32(0L, Z_NULL, 0); - s->last_flush = Z_NO_FLUSH; + s->last_flush = -2; _tr_init(s); return Z_OK; } +/* =========================================================================== + * Initialize the "longest match" routines for a new zlib stream + */ +local void lm_init(deflate_state *s) { + s->window_size = (ulg)2L*s->w_size; + + CLEAR_HASH(s); + + /* Set the default configuration parameters: + */ + s->max_lazy_match = configuration_table[s->level].max_lazy; + s->good_match = configuration_table[s->level].good_length; + s->nice_match = configuration_table[s->level].nice_length; + s->max_chain_length = configuration_table[s->level].max_chain; + + s->strstart = 0; + s->block_start = 0L; + s->lookahead = 0; + s->insert = 0; + s->match_length = s->prev_length = MIN_MATCH-1; + s->match_available = 0; + s->ins_h = 0; +} + /* ========================================================================= */ -int ZEXPORT deflateReset (strm) - z_streamp strm; -{ +int ZEXPORT deflateReset(z_streamp strm) { int ret; ret = deflateResetKeep(strm); @@ -514,10 +711,7 @@ int ZEXPORT deflateReset (strm) } /* ========================================================================= */ -int ZEXPORT deflateSetHeader (strm, head) - z_streamp strm; - gz_headerp head; -{ +int ZEXPORT deflateSetHeader(z_streamp strm, gz_headerp head) { if (deflateStateCheck(strm) || strm->state->wrap != 2) return Z_STREAM_ERROR; strm->state->gzhead = head; @@ -525,32 +719,44 @@ int ZEXPORT deflateSetHeader (strm, head) } /* ========================================================================= */ -int ZEXPORT deflatePending (strm, pending, bits) - unsigned *pending; - int *bits; - z_streamp strm; -{ +int ZEXPORT deflatePending(z_streamp strm, unsigned *pending, int *bits) { if (deflateStateCheck(strm)) return Z_STREAM_ERROR; - if (pending != Z_NULL) - *pending = strm->state->pending; if (bits != Z_NULL) *bits = strm->state->bi_valid; + if (pending != Z_NULL) { + *pending = (unsigned)strm->state->pending; + if (*pending != strm->state->pending) { + *pending = (unsigned)-1; + return Z_BUF_ERROR; + } + } return Z_OK; } /* ========================================================================= */ -int ZEXPORT deflatePrime (strm, bits, value) - z_streamp strm; - int bits; - int value; -{ +int ZEXPORT deflateUsed(z_streamp strm, int *bits) { + if (deflateStateCheck(strm)) return Z_STREAM_ERROR; + if (bits != Z_NULL) + *bits = strm->state->bi_used; + return Z_OK; +} + +/* ========================================================================= */ +int ZEXPORT deflatePrime(z_streamp strm, int bits, int value) { deflate_state *s; int put; if (deflateStateCheck(strm)) return Z_STREAM_ERROR; s = strm->state; - if ((Bytef *)(s->d_buf) < s->pending_out + ((Buf_size + 7) >> 3)) +#ifdef LIT_MEM + if (bits < 0 || bits > 16 || + (uchf *)s->d_buf < s->pending_out + ((Buf_size + 7) >> 3)) return Z_BUF_ERROR; +#else + if (bits < 0 || bits > 16 || + s->sym_buf < s->pending_out + ((Buf_size + 7) >> 3)) + return Z_BUF_ERROR; +#endif do { put = Buf_size - s->bi_valid; if (put > bits) @@ -565,11 +771,7 @@ int ZEXPORT deflatePrime (strm, bits, value) } /* ========================================================================= */ -int ZEXPORT deflateParams(strm, level, strategy) - z_streamp strm; - int level; - int strategy; -{ +int ZEXPORT deflateParams(z_streamp strm, int level, int strategy) { deflate_state *s; compress_func func; @@ -587,12 +789,12 @@ int ZEXPORT deflateParams(strm, level, strategy) func = configuration_table[s->level].func; if ((strategy != s->strategy || func != configuration_table[level].func) && - s->high_water) { + s->last_flush != -2) { /* Flush the last buffer: */ int err = deflate(strm, Z_BLOCK); if (err == Z_STREAM_ERROR) return err; - if (strm->avail_out == 0) + if (strm->avail_in || (s->strstart - s->block_start) + s->lookahead) return Z_BUF_ERROR; } if (s->level != level) { @@ -614,13 +816,8 @@ int ZEXPORT deflateParams(strm, level, strategy) } /* ========================================================================= */ -int ZEXPORT deflateTune(strm, good_length, max_lazy, nice_length, max_chain) - z_streamp strm; - int good_length; - int max_lazy; - int nice_length; - int max_chain; -{ +int ZEXPORT deflateTune(z_streamp strm, int good_length, int max_lazy, + int nice_length, int max_chain) { deflate_state *s; if (deflateStateCheck(strm)) return Z_STREAM_ERROR; @@ -633,40 +830,57 @@ int ZEXPORT deflateTune(strm, good_length, max_lazy, nice_length, max_chain) } /* ========================================================================= - * For the default windowBits of 15 and memLevel of 8, this function returns - * a close to exact, as well as small, upper bound on the compressed size. - * They are coded as constants here for a reason--if the #define's are - * changed, then this function needs to be changed as well. The return - * value for 15 and 8 only works for those exact settings. + * For the default windowBits of 15 and memLevel of 8, this function returns a + * close to exact, as well as small, upper bound on the compressed size. This + * is an expansion of ~0.03%, plus a small constant. * - * For any setting other than those defaults for windowBits and memLevel, - * the value returned is a conservative worst case for the maximum expansion - * resulting from using fixed blocks instead of stored blocks, which deflate - * can emit on compressed data for some combinations of the parameters. + * For any setting other than those defaults for windowBits and memLevel, one + * of two worst case bounds is returned. This is at most an expansion of ~4% or + * ~13%, plus a small constant. * - * This function could be more sophisticated to provide closer upper bounds for - * every combination of windowBits and memLevel. But even the conservative - * upper bound of about 14% expansion does not seem onerous for output buffer - * allocation. + * Both the 0.03% and 4% derive from the overhead of stored blocks. The first + * one is for stored blocks of 16383 bytes (memLevel == 8), whereas the second + * is for stored blocks of 127 bytes (the worst case memLevel == 1). The + * expansion results from five bytes of header for each stored block. + * + * The larger expansion of 13% results from a window size less than or equal to + * the symbols buffer size (windowBits <= memLevel + 7). In that case some of + * the data being compressed may have slid out of the sliding window, impeding + * a stored block from being emitted. Then the only choice is a fixed or + * dynamic block, where a fixed block limits the maximum expansion to 9 bits + * per 8-bit byte, plus 10 bits for every block. The smallest block size for + * which this can occur is 255 (memLevel == 2). + * + * Shifts are used to approximate divisions, for speed. */ -uLong ZEXPORT deflateBound(strm, sourceLen) - z_streamp strm; - uLong sourceLen; -{ +z_size_t ZEXPORT deflateBound_z(z_streamp strm, z_size_t sourceLen) { deflate_state *s; - uLong complen, wraplen; - - /* conservative upper bound for compressed data */ - complen = sourceLen + - ((sourceLen + 7) >> 3) + ((sourceLen + 63) >> 6) + 5; - - /* if can't get parameters, return conservative bound plus zlib wrapper */ - if (deflateStateCheck(strm)) - return complen + 6; + z_size_t fixedlen, storelen, wraplen, bound; + + /* upper bound for fixed blocks with 9-bit literals and length 255 + (memLevel == 2, which is the lowest that may not use stored blocks) -- + ~13% overhead plus a small constant */ + fixedlen = sourceLen + (sourceLen >> 3) + (sourceLen >> 8) + + (sourceLen >> 9) + 4; + if (fixedlen < sourceLen) + fixedlen = (z_size_t)-1; + + /* upper bound for stored blocks with length 127 (memLevel == 1) -- + ~4% overhead plus a small constant */ + storelen = sourceLen + (sourceLen >> 5) + (sourceLen >> 7) + + (sourceLen >> 11) + 7; + if (storelen < sourceLen) + storelen = (z_size_t)-1; + + /* if can't get parameters, return larger bound plus a wrapper */ + if (deflateStateCheck(strm)) { + bound = fixedlen > storelen ? fixedlen : storelen; + return bound + 18 < bound ? (z_size_t)-1 : bound + 18; + } /* compute wrapper length */ s = strm->state; - switch (s->wrap) { + switch (s->wrap < 0 ? -s->wrap : s->wrap) { case 0: /* raw deflate */ wraplen = 0; break; @@ -696,16 +910,25 @@ uLong ZEXPORT deflateBound(strm, sourceLen) break; #endif default: /* for compiler happiness */ - wraplen = 6; + wraplen = 18; } - /* if not default parameters, return conservative bound */ - if (s->w_bits != 15 || s->hash_bits != 8 + 7) - return complen + wraplen; + /* if not default parameters, return one of the conservative bounds */ + if (s->w_bits != 15 || s->hash_bits != 8 + 7) { + bound = s->w_bits <= s->hash_bits && s->level ? fixedlen : + storelen; + return bound + wraplen < bound ? (z_size_t)-1 : bound + wraplen; + } - /* default settings: return tight bound for that case */ - return sourceLen + (sourceLen >> 12) + (sourceLen >> 14) + - (sourceLen >> 25) + 13 - 6 + wraplen; + /* default settings: return tight bound for that case -- ~0.03% overhead + plus a small constant */ + bound = sourceLen + (sourceLen >> 12) + (sourceLen >> 14) + + (sourceLen >> 25) + 13 - 6 + wraplen; + return bound < sourceLen ? (z_size_t)-1 : bound; +} +uLong ZEXPORT deflateBound(z_streamp strm, uLong sourceLen) { + z_size_t bound = deflateBound_z(strm, sourceLen); + return (uLong)bound != bound ? (uLong)-1 : (uLong)bound; } /* ========================================================================= @@ -713,10 +936,7 @@ uLong ZEXPORT deflateBound(strm, sourceLen) * IN assertion: the stream state is correct and there is enough room in * pending_buf. */ -local void putShortMSB (s, b) - deflate_state *s; - uInt b; -{ +local void putShortMSB(deflate_state *s, uInt b) { put_byte(s, (Byte)(b >> 8)); put_byte(s, (Byte)(b & 0xff)); } @@ -727,15 +947,13 @@ local void putShortMSB (s, b) * applications may wish to modify it to avoid allocating a large * strm->next_out buffer and copying into it. (See also read_buf()). */ -local void flush_pending(strm) - z_streamp strm; -{ +local void flush_pending(z_streamp strm) { unsigned len; deflate_state *s = strm->state; _tr_flush_bits(s); - len = s->pending; - if (len > strm->avail_out) len = strm->avail_out; + len = s->pending > strm->avail_out ? strm->avail_out : + (unsigned)s->pending; if (len == 0) return; zmemcpy(strm->next_out, s->pending_out, len); @@ -755,15 +973,12 @@ local void flush_pending(strm) #define HCRC_UPDATE(beg) \ do { \ if (s->gzhead->hcrc && s->pending > (beg)) \ - strm->adler = crc32(strm->adler, s->pending_buf + (beg), \ - s->pending - (beg)); \ + strm->adler = crc32_z(strm->adler, s->pending_buf + (beg), \ + s->pending - (beg)); \ } while (0) /* ========================================================================= */ -int ZEXPORT deflate (strm, flush) - z_streamp strm; - int flush; -{ +int ZEXPORT deflate(z_streamp strm, int flush) { int old_flush; /* value of flush param for previous deflate call */ deflate_state *s; @@ -811,9 +1026,11 @@ int ZEXPORT deflate (strm, flush) } /* Write the header */ + if (s->status == INIT_STATE && s->wrap == 0) + s->status = BUSY_STATE; if (s->status == INIT_STATE) { /* zlib header */ - uInt header = (Z_DEFLATED + ((s->w_bits-8)<<4)) << 8; + uInt header = (Z_DEFLATED + ((s->w_bits - 8) << 4)) << 8; uInt level_flags; if (s->strategy >= Z_HUFFMAN_ONLY || s->level < 2) @@ -891,8 +1108,8 @@ int ZEXPORT deflate (strm, flush) put_byte(s, (s->gzhead->extra_len >> 8) & 0xff); } if (s->gzhead->hcrc) - strm->adler = crc32(strm->adler, s->pending_buf, - s->pending); + strm->adler = crc32_z(strm->adler, s->pending_buf, + s->pending); s->gzindex = 0; s->status = EXTRA_STATE; } @@ -900,9 +1117,9 @@ int ZEXPORT deflate (strm, flush) if (s->status == EXTRA_STATE) { if (s->gzhead->extra != Z_NULL) { ulg beg = s->pending; /* start of bytes to update crc */ - uInt left = (s->gzhead->extra_len & 0xffff) - s->gzindex; + ulg left = (s->gzhead->extra_len & 0xffff) - s->gzindex; while (s->pending + left > s->pending_buf_size) { - uInt copy = s->pending_buf_size - s->pending; + ulg copy = s->pending_buf_size - s->pending; zmemcpy(s->pending_buf + s->pending, s->gzhead->extra + s->gzindex, copy); s->pending = s->pending_buf_size; @@ -1073,9 +1290,7 @@ int ZEXPORT deflate (strm, flush) } /* ========================================================================= */ -int ZEXPORT deflateEnd (strm) - z_streamp strm; -{ +int ZEXPORT deflateEnd(z_streamp strm) { int status; if (deflateStateCheck(strm)) return Z_STREAM_ERROR; @@ -1099,16 +1314,14 @@ int ZEXPORT deflateEnd (strm) * To simplify the source, this is not supported for 16-bit MSDOS (which * doesn't have enough memory anyway to duplicate compression states). */ -int ZEXPORT deflateCopy (dest, source) - z_streamp dest; - z_streamp source; -{ +int ZEXPORT deflateCopy(z_streamp dest, z_streamp source) { #ifdef MAXSEG_64K + (void)dest; + (void)source; return Z_STREAM_ERROR; #else deflate_state *ds; deflate_state *ss; - ushf *overlay; if (deflateStateCheck(source) || dest == Z_NULL) { @@ -1117,34 +1330,43 @@ int ZEXPORT deflateCopy (dest, source) ss = source->state; - zmemcpy((voidpf)dest, (voidpf)source, sizeof(z_stream)); + zmemcpy(dest, source, sizeof(z_stream)); ds = (deflate_state *) ZALLOC(dest, 1, sizeof(deflate_state)); if (ds == Z_NULL) return Z_MEM_ERROR; + zmemzero(ds, sizeof(deflate_state)); dest->state = (struct internal_state FAR *) ds; - zmemcpy((voidpf)ds, (voidpf)ss, sizeof(deflate_state)); + zmemcpy(ds, ss, sizeof(deflate_state)); ds->strm = dest; ds->window = (Bytef *) ZALLOC(dest, ds->w_size, 2*sizeof(Byte)); ds->prev = (Posf *) ZALLOC(dest, ds->w_size, sizeof(Pos)); ds->head = (Posf *) ZALLOC(dest, ds->hash_size, sizeof(Pos)); - overlay = (ushf *) ZALLOC(dest, ds->lit_bufsize, sizeof(ush)+2); - ds->pending_buf = (uchf *) overlay; + ds->pending_buf = (uchf *) ZALLOC(dest, ds->lit_bufsize, LIT_BUFS); if (ds->window == Z_NULL || ds->prev == Z_NULL || ds->head == Z_NULL || ds->pending_buf == Z_NULL) { deflateEnd (dest); return Z_MEM_ERROR; } - /* following zmemcpy do not work for 16-bit MSDOS */ - zmemcpy(ds->window, ss->window, ds->w_size * 2 * sizeof(Byte)); - zmemcpy((voidpf)ds->prev, (voidpf)ss->prev, ds->w_size * sizeof(Pos)); - zmemcpy((voidpf)ds->head, (voidpf)ss->head, ds->hash_size * sizeof(Pos)); - zmemcpy(ds->pending_buf, ss->pending_buf, (uInt)ds->pending_buf_size); + /* following zmemcpy's do not work for 16-bit MSDOS */ + zmemcpy(ds->window, ss->window, ss->high_water); + zmemcpy(ds->prev, ss->prev, + (ss->slid || ss->strstart - ss->insert > ds->w_size ? ds->w_size : + ss->strstart - ss->insert) * sizeof(Pos)); + zmemcpy(ds->head, ss->head, ds->hash_size * sizeof(Pos)); ds->pending_out = ds->pending_buf + (ss->pending_out - ss->pending_buf); - ds->d_buf = overlay + ds->lit_bufsize/sizeof(ush); - ds->l_buf = ds->pending_buf + (1+sizeof(ush))*ds->lit_bufsize; + zmemcpy(ds->pending_out, ss->pending_out, ss->pending); +#ifdef LIT_MEM + ds->d_buf = (ushf *)(ds->pending_buf + (ds->lit_bufsize << 1)); + ds->l_buf = ds->pending_buf + (ds->lit_bufsize << 2); + zmemcpy(ds->d_buf, ss->d_buf, ss->sym_next * sizeof(ush)); + zmemcpy(ds->l_buf, ss->l_buf, ss->sym_next); +#else + ds->sym_buf = ds->pending_buf + ds->lit_bufsize; + zmemcpy(ds->sym_buf, ss->sym_buf, ss->sym_next); +#endif ds->l_desc.dyn_tree = ds->dyn_ltree; ds->d_desc.dyn_tree = ds->dyn_dtree; @@ -1154,71 +1376,6 @@ int ZEXPORT deflateCopy (dest, source) #endif /* MAXSEG_64K */ } -/* =========================================================================== - * Read a new buffer from the current input stream, update the adler32 - * and total number of bytes read. All deflate() input goes through - * this function so some applications may wish to modify it to avoid - * allocating a large strm->next_in buffer and copying from it. - * (See also flush_pending()). - */ -local unsigned read_buf(strm, buf, size) - z_streamp strm; - Bytef *buf; - unsigned size; -{ - unsigned len = strm->avail_in; - - if (len > size) len = size; - if (len == 0) return 0; - - strm->avail_in -= len; - - zmemcpy(buf, strm->next_in, len); - if (strm->state->wrap == 1) { - strm->adler = adler32(strm->adler, buf, len); - } -#ifdef GZIP - else if (strm->state->wrap == 2) { - strm->adler = crc32(strm->adler, buf, len); - } -#endif - strm->next_in += len; - strm->total_in += len; - - return len; -} - -/* =========================================================================== - * Initialize the "longest match" routines for a new zlib stream - */ -local void lm_init (s) - deflate_state *s; -{ - s->window_size = (ulg)2L*s->w_size; - - CLEAR_HASH(s); - - /* Set the default configuration parameters: - */ - s->max_lazy_match = configuration_table[s->level].max_lazy; - s->good_match = configuration_table[s->level].good_length; - s->nice_match = configuration_table[s->level].nice_length; - s->max_chain_length = configuration_table[s->level].max_chain; - - s->strstart = 0; - s->block_start = 0L; - s->lookahead = 0; - s->insert = 0; - s->match_length = s->prev_length = MIN_MATCH-1; - s->match_available = 0; - s->ins_h = 0; -#ifndef FASTEST -#ifdef ASMV - match_init(); /* initialize the asm code */ -#endif -#endif -} - #ifndef FASTEST /* =========================================================================== * Set match_start to the longest match starting at the given string and @@ -1229,18 +1386,11 @@ local void lm_init (s) * string (strstart) and its distance is <= MAX_DIST, and prev_length >= 1 * OUT assertion: the match length is not greater than s->lookahead. */ -#ifndef ASMV -/* For 80x86 and 680x0, an optimized version will be provided in match.asm or - * match.S. The code will be functionally equivalent. - */ -local uInt longest_match(s, cur_match) - deflate_state *s; - IPos cur_match; /* current match */ -{ +local uInt longest_match(deflate_state *s, IPos cur_match) { unsigned chain_length = s->max_chain_length;/* max hash chain length */ - register Bytef *scan = s->window + s->strstart; /* current string */ - register Bytef *match; /* matched string */ - register int len; /* length of current match */ + Bytef *scan = s->window + s->strstart; /* current string */ + Bytef *match; /* matched string */ + int len; /* length of current match */ int best_len = (int)s->prev_length; /* best match length so far */ int nice_match = s->nice_match; /* stop if match long enough */ IPos limit = s->strstart > (IPos)MAX_DIST(s) ? @@ -1255,13 +1405,13 @@ local uInt longest_match(s, cur_match) /* Compare two bytes at a time. Note: this is not always beneficial. * Try with and without -DUNALIGNED_OK to check. */ - register Bytef *strend = s->window + s->strstart + MAX_MATCH - 1; - register ush scan_start = *(ushf*)scan; - register ush scan_end = *(ushf*)(scan+best_len-1); + Bytef *strend = s->window + s->strstart + MAX_MATCH - 1; + ush scan_start = *(ushf*)scan; + ush scan_end = *(ushf*)(scan + best_len - 1); #else - register Bytef *strend = s->window + s->strstart + MAX_MATCH; - register Byte scan_end1 = scan[best_len-1]; - register Byte scan_end = scan[best_len]; + Bytef *strend = s->window + s->strstart + MAX_MATCH; + Byte scan_end1 = scan[best_len - 1]; + Byte scan_end = scan[best_len]; #endif /* The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16. @@ -1278,7 +1428,8 @@ local uInt longest_match(s, cur_match) */ if ((uInt)nice_match > s->lookahead) nice_match = (int)s->lookahead; - Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead"); + Assert((ulg)s->strstart <= s->window_size - MIN_LOOKAHEAD, + "need lookahead"); do { Assert(cur_match < s->strstart, "no future"); @@ -1296,43 +1447,44 @@ local uInt longest_match(s, cur_match) /* This code assumes sizeof(unsigned short) == 2. Do not use * UNALIGNED_OK if your compiler uses a different size. */ - if (*(ushf*)(match+best_len-1) != scan_end || + if (*(ushf*)(match + best_len - 1) != scan_end || *(ushf*)match != scan_start) continue; /* It is not necessary to compare scan[2] and match[2] since they are * always equal when the other bytes match, given that the hash keys * are equal and that HASH_BITS >= 8. Compare 2 bytes at a time at - * strstart+3, +5, ... up to strstart+257. We check for insufficient + * strstart + 3, + 5, up to strstart + 257. We check for insufficient * lookahead only every 4th comparison; the 128th check will be made - * at strstart+257. If MAX_MATCH-2 is not a multiple of 8, it is + * at strstart + 257. If MAX_MATCH-2 is not a multiple of 8, it is * necessary to put more guard bytes at the end of the window, or * to check more often for insufficient lookahead. */ Assert(scan[2] == match[2], "scan[2]?"); scan++, match++; do { - } while (*(ushf*)(scan+=2) == *(ushf*)(match+=2) && - *(ushf*)(scan+=2) == *(ushf*)(match+=2) && - *(ushf*)(scan+=2) == *(ushf*)(match+=2) && - *(ushf*)(scan+=2) == *(ushf*)(match+=2) && + } while (*(ushf*)(scan += 2) == *(ushf*)(match += 2) && + *(ushf*)(scan += 2) == *(ushf*)(match += 2) && + *(ushf*)(scan += 2) == *(ushf*)(match += 2) && + *(ushf*)(scan += 2) == *(ushf*)(match += 2) && scan < strend); /* The funny "do {}" generates better code on most compilers */ - /* Here, scan <= window+strstart+257 */ - Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan"); + /* Here, scan <= window + strstart + 257 */ + Assert(scan <= s->window + (unsigned)(s->window_size - 1), + "wild scan"); if (*scan == *match) scan++; - len = (MAX_MATCH - 1) - (int)(strend-scan); + len = (MAX_MATCH - 1) - (int)(strend - scan); scan = strend - (MAX_MATCH-1); #else /* UNALIGNED_OK */ - if (match[best_len] != scan_end || - match[best_len-1] != scan_end1 || - *match != *scan || - *++match != scan[1]) continue; + if (match[best_len] != scan_end || + match[best_len - 1] != scan_end1 || + *match != *scan || + *++match != scan[1]) continue; - /* The check at best_len-1 can be removed because it will be made + /* The check at best_len - 1 can be removed because it will be made * again later. (This heuristic is not always a win.) * It is not necessary to compare scan[2] and match[2] since they * are always equal when the other bytes match, given that @@ -1342,7 +1494,7 @@ local uInt longest_match(s, cur_match) Assert(*scan == *match, "match[2]?"); /* We check for insufficient lookahead only every 8th comparison; - * the 256th check will be made at strstart+258. + * the 256th check will be made at strstart + 258. */ do { } while (*++scan == *++match && *++scan == *++match && @@ -1351,7 +1503,8 @@ local uInt longest_match(s, cur_match) *++scan == *++match && *++scan == *++match && scan < strend); - Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan"); + Assert(scan <= s->window + (unsigned)(s->window_size - 1), + "wild scan"); len = MAX_MATCH - (int)(strend - scan); scan = strend - MAX_MATCH; @@ -1363,9 +1516,9 @@ local uInt longest_match(s, cur_match) best_len = len; if (len >= nice_match) break; #ifdef UNALIGNED_OK - scan_end = *(ushf*)(scan+best_len-1); + scan_end = *(ushf*)(scan + best_len - 1); #else - scan_end1 = scan[best_len-1]; + scan_end1 = scan[best_len - 1]; scan_end = scan[best_len]; #endif } @@ -1375,28 +1528,25 @@ local uInt longest_match(s, cur_match) if ((uInt)best_len <= s->lookahead) return (uInt)best_len; return s->lookahead; } -#endif /* ASMV */ #else /* FASTEST */ /* --------------------------------------------------------------------------- * Optimized version for FASTEST only */ -local uInt longest_match(s, cur_match) - deflate_state *s; - IPos cur_match; /* current match */ -{ - register Bytef *scan = s->window + s->strstart; /* current string */ - register Bytef *match; /* matched string */ - register int len; /* length of current match */ - register Bytef *strend = s->window + s->strstart + MAX_MATCH; +local uInt longest_match(deflate_state *s, IPos cur_match) { + Bytef *scan = s->window + s->strstart; /* current string */ + Bytef *match; /* matched string */ + int len; /* length of current match */ + Bytef *strend = s->window + s->strstart + MAX_MATCH; /* The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16. * It is easy to get rid of this optimization if necessary. */ Assert(s->hash_bits >= 8 && MAX_MATCH == 258, "Code too clever"); - Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead"); + Assert((ulg)s->strstart <= s->window_size - MIN_LOOKAHEAD, + "need lookahead"); Assert(cur_match < s->strstart, "no future"); @@ -1406,7 +1556,7 @@ local uInt longest_match(s, cur_match) */ if (match[0] != scan[0] || match[1] != scan[1]) return MIN_MATCH-1; - /* The check at best_len-1 can be removed because it will be made + /* The check at best_len - 1 can be removed because it will be made * again later. (This heuristic is not always a win.) * It is not necessary to compare scan[2] and match[2] since they * are always equal when the other bytes match, given that @@ -1416,7 +1566,7 @@ local uInt longest_match(s, cur_match) Assert(*scan == *match, "match[2]?"); /* We check for insufficient lookahead only every 8th comparison; - * the 256th check will be made at strstart+258. + * the 256th check will be made at strstart + 258. */ do { } while (*++scan == *++match && *++scan == *++match && @@ -1425,7 +1575,7 @@ local uInt longest_match(s, cur_match) *++scan == *++match && *++scan == *++match && scan < strend); - Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan"); + Assert(scan <= s->window + (unsigned)(s->window_size - 1), "wild scan"); len = MAX_MATCH - (int)(strend - scan); @@ -1445,23 +1595,27 @@ local uInt longest_match(s, cur_match) /* =========================================================================== * Check that the match at match_start is indeed a match. */ -local void check_match(s, start, match, length) - deflate_state *s; - IPos start, match; - int length; -{ +local void check_match(deflate_state *s, IPos start, IPos match, int length) { /* check that the match is indeed a match */ - if (zmemcmp(s->window + match, - s->window + start, length) != EQUAL) { - fprintf(stderr, " start %u, match %u, length %d\n", - start, match, length); + Bytef *back = s->window + (int)match, *here = s->window + start; + IPos len = (IPos)length; + if (match == (IPos)-1) { + /* match starts one byte before the current window -- just compare the + subsequent length-1 bytes */ + back++; + here++; + len--; + } + if (zmemcmp(back, here, len) != EQUAL) { + fprintf(stderr, " start %u, match %d, length %d\n", + start, (int)match, length); do { - fprintf(stderr, "%c%c", s->window[match++], s->window[start++]); - } while (--length != 0); + fprintf(stderr, "(%02x %02x)", *back++, *here++); + } while (--len != 0); z_error("invalid match"); } if (z_verbose > 1) { - fprintf(stderr,"\\[%d,%d]", start-match, length); + fprintf(stderr,"\\[%d,%d]", start - match, length); do { putc(s->window[start++], stderr); } while (--length != 0); } } @@ -1469,135 +1623,6 @@ local void check_match(s, start, match, length) # define check_match(s, start, match, length) #endif /* ZLIB_DEBUG */ -/* =========================================================================== - * Fill the window when the lookahead becomes insufficient. - * Updates strstart and lookahead. - * - * IN assertion: lookahead < MIN_LOOKAHEAD - * OUT assertions: strstart <= window_size-MIN_LOOKAHEAD - * At least one byte has been read, or avail_in == 0; reads are - * performed for at least two bytes (required for the zip translate_eol - * option -- not supported here). - */ -local void fill_window(s) - deflate_state *s; -{ - unsigned n; - unsigned more; /* Amount of free space at the end of the window. */ - uInt wsize = s->w_size; - - Assert(s->lookahead < MIN_LOOKAHEAD, "already enough lookahead"); - - do { - more = (unsigned)(s->window_size -(ulg)s->lookahead -(ulg)s->strstart); - - /* Deal with !@#$% 64K limit: */ - if (sizeof(int) <= 2) { - if (more == 0 && s->strstart == 0 && s->lookahead == 0) { - more = wsize; - - } else if (more == (unsigned)(-1)) { - /* Very unlikely, but possible on 16 bit machine if - * strstart == 0 && lookahead == 1 (input done a byte at time) - */ - more--; - } - } - - /* If the window is almost full and there is insufficient lookahead, - * move the upper half to the lower one to make room in the upper half. - */ - if (s->strstart >= wsize+MAX_DIST(s)) { - - zmemcpy(s->window, s->window+wsize, (unsigned)wsize - more); - s->match_start -= wsize; - s->strstart -= wsize; /* we now have strstart >= MAX_DIST */ - s->block_start -= (long) wsize; - slide_hash(s); - more += wsize; - } - if (s->strm->avail_in == 0) break; - - /* If there was no sliding: - * strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 && - * more == window_size - lookahead - strstart - * => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1) - * => more >= window_size - 2*WSIZE + 2 - * In the BIG_MEM or MMAP case (not yet supported), - * window_size == input_size + MIN_LOOKAHEAD && - * strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD. - * Otherwise, window_size == 2*WSIZE so more >= 2. - * If there was sliding, more >= WSIZE. So in all cases, more >= 2. - */ - Assert(more >= 2, "more < 2"); - - n = read_buf(s->strm, s->window + s->strstart + s->lookahead, more); - s->lookahead += n; - - /* Initialize the hash value now that we have some input: */ - if (s->lookahead + s->insert >= MIN_MATCH) { - uInt str = s->strstart - s->insert; - s->ins_h = s->window[str]; - UPDATE_HASH(s, s->ins_h, s->window[str + 1]); -#if MIN_MATCH != 3 - Call UPDATE_HASH() MIN_MATCH-3 more times -#endif - while (s->insert) { - UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); -#ifndef FASTEST - s->prev[str & s->w_mask] = s->head[s->ins_h]; -#endif - s->head[s->ins_h] = (Pos)str; - str++; - s->insert--; - if (s->lookahead + s->insert < MIN_MATCH) - break; - } - } - /* If the whole input has less than MIN_MATCH bytes, ins_h is garbage, - * but this is not important since only literal bytes will be emitted. - */ - - } while (s->lookahead < MIN_LOOKAHEAD && s->strm->avail_in != 0); - - /* If the WIN_INIT bytes after the end of the current data have never been - * written, then zero those bytes in order to avoid memory check reports of - * the use of uninitialized (or uninitialised as Julian writes) bytes by - * the longest match routines. Update the high water mark for the next - * time through here. WIN_INIT is set to MAX_MATCH since the longest match - * routines allow scanning to strstart + MAX_MATCH, ignoring lookahead. - */ - if (s->high_water < s->window_size) { - ulg curr = s->strstart + (ulg)(s->lookahead); - ulg init; - - if (s->high_water < curr) { - /* Previous high water mark below current data -- zero WIN_INIT - * bytes or up to end of window, whichever is less. - */ - init = s->window_size - curr; - if (init > WIN_INIT) - init = WIN_INIT; - zmemzero(s->window + curr, (unsigned)init); - s->high_water = curr + init; - } - else if (s->high_water < (ulg)curr + WIN_INIT) { - /* High water mark at or above current data, but below current data - * plus WIN_INIT -- zero out to current data plus WIN_INIT, or up - * to end of window, whichever is less. - */ - init = (ulg)curr + WIN_INIT - s->high_water; - if (init > s->window_size - s->high_water) - init = s->window_size - s->high_water; - zmemzero(s->window + s->high_water, (unsigned)init); - s->high_water += init; - } - } - - Assert((ulg)s->strstart <= s->window_size - MIN_LOOKAHEAD, - "not enough room for search"); -} - /* =========================================================================== * Flush the current block, with given end-of-file flag. * IN assertion: strstart is set to the end of the current match. @@ -1638,23 +1663,21 @@ local void fill_window(s) * * deflate_stored() is written to minimize the number of times an input byte is * copied. It is most efficient with large input and output buffers, which - * maximizes the opportunites to have a single copy from next_in to next_out. + * maximizes the opportunities to have a single copy from next_in to next_out. */ -local block_state deflate_stored(s, flush) - deflate_state *s; - int flush; -{ +local block_state deflate_stored(deflate_state *s, int flush) { /* Smallest worthy block size when not flushing or finishing. By default * this is 32K. This can be as small as 507 bytes for memLevel == 1. For * large input and output buffers, the stored block size will be larger. */ - unsigned min_block = MIN(s->pending_buf_size - 5, s->w_size); + unsigned min_block = (unsigned)(MIN(s->pending_buf_size - 5, s->w_size)); /* Copy as many min_block or larger stored blocks directly to next_out as * possible. If flushing, copy the remaining available input to next_out as * stored blocks, if there is enough space. */ - unsigned len, left, have, last = 0; + int last = 0; + unsigned len, left, have; unsigned used = s->strm->avail_in; do { /* Set len to the maximum size block that we can copy directly with the @@ -1662,12 +1685,12 @@ local block_state deflate_stored(s, flush) * would be copied from what's left in the window. */ len = MAX_STORED; /* maximum deflate stored block length */ - have = (s->bi_valid + 42) >> 3; /* number of header bytes */ + have = ((unsigned)s->bi_valid + 42) >> 3; /* bytes in header */ if (s->strm->avail_out < have) /* need room for header */ break; /* maximum stored block length that will fit in avail_out: */ have = s->strm->avail_out - have; - left = s->strstart - s->block_start; /* bytes left in window */ + left = (unsigned)(s->strstart - s->block_start); /* window bytes */ if (len > (ulg)left + s->strm->avail_in) len = left + s->strm->avail_in; /* limit len to the input */ if (len > have) @@ -1690,10 +1713,10 @@ local block_state deflate_stored(s, flush) _tr_stored_block(s, (char *)0, 0L, last); /* Replace the lengths in the dummy stored block with len. */ - s->pending_buf[s->pending - 4] = len; - s->pending_buf[s->pending - 3] = len >> 8; - s->pending_buf[s->pending - 2] = ~len; - s->pending_buf[s->pending - 1] = ~len >> 8; + s->pending_buf[s->pending - 4] = (Bytef)len; + s->pending_buf[s->pending - 3] = (Bytef)(len >> 8); + s->pending_buf[s->pending - 2] = (Bytef)~len; + s->pending_buf[s->pending - 1] = (Bytef)(~len >> 8); /* Write the stored block header bytes. */ flush_pending(s->strm); @@ -1742,6 +1765,7 @@ local block_state deflate_stored(s, flush) s->matches = 2; /* clear hash */ zmemcpy(s->window, s->strm->next_in - s->w_size, s->w_size); s->strstart = s->w_size; + s->insert = s->strstart; } else { if (s->window_size - s->strstart <= used) { @@ -1750,19 +1774,23 @@ local block_state deflate_stored(s, flush) zmemcpy(s->window, s->window + s->w_size, s->strstart); if (s->matches < 2) s->matches++; /* add a pending slide_hash() */ + if (s->insert > s->strstart) + s->insert = s->strstart; } zmemcpy(s->window + s->strstart, s->strm->next_in - used, used); s->strstart += used; + s->insert += MIN(used, s->w_size - s->insert); } s->block_start = s->strstart; - s->insert += MIN(used, s->w_size - s->insert); } if (s->high_water < s->strstart) s->high_water = s->strstart; /* If the last block was written to next_out, then done. */ - if (last) + if (last) { + s->bi_used = 8; return finish_done; + } /* If flushing and all input has been consumed, then done. */ if (flush != Z_NO_FLUSH && flush != Z_FINISH && @@ -1770,7 +1798,7 @@ local block_state deflate_stored(s, flush) return block_done; /* Fill the window with any remaining input. */ - have = s->window_size - s->strstart - 1; + have = (unsigned)(s->window_size - s->strstart); if (s->strm->avail_in > have && s->block_start >= (long)s->w_size) { /* Slide the window down. */ s->block_start -= s->w_size; @@ -1779,12 +1807,15 @@ local block_state deflate_stored(s, flush) if (s->matches < 2) s->matches++; /* add a pending slide_hash() */ have += s->w_size; /* more space now */ + if (s->insert > s->strstart) + s->insert = s->strstart; } if (have > s->strm->avail_in) have = s->strm->avail_in; if (have) { read_buf(s->strm, s->window + s->strstart, have); s->strstart += have; + s->insert += MIN(have, s->w_size - s->insert); } if (s->high_water < s->strstart) s->high_water = s->strstart; @@ -1794,11 +1825,11 @@ local block_state deflate_stored(s, flush) * have enough input for a worthy block, or if flushing and there is enough * room for the remaining input as a stored block in the pending buffer. */ - have = (s->bi_valid + 42) >> 3; /* number of header bytes */ + have = ((unsigned)s->bi_valid + 42) >> 3; /* bytes in header */ /* maximum stored block length that will fit in pending: */ - have = MIN(s->pending_buf_size - have, MAX_STORED); + have = (unsigned)MIN(s->pending_buf_size - have, MAX_STORED); min_block = MIN(have, s->w_size); - left = s->strstart - s->block_start; + left = (unsigned)(s->strstart - s->block_start); if (left >= min_block || ((left || flush == Z_FINISH) && flush != Z_NO_FLUSH && s->strm->avail_in == 0 && left <= have)) { @@ -1811,6 +1842,8 @@ local block_state deflate_stored(s, flush) } /* We've done all we can with the available input and output. */ + if (last) + s->bi_used = 8; return last ? finish_started : need_more; } @@ -1821,10 +1854,7 @@ local block_state deflate_stored(s, flush) * new strings in the dictionary only for unmatched strings or for short * matches. It is used only for the fast compression options. */ -local block_state deflate_fast(s, flush) - deflate_state *s; - int flush; -{ +local block_state deflate_fast(deflate_state *s, int flush) { IPos hash_head; /* head of the hash chain */ int bflush; /* set if current block must be flushed */ @@ -1842,7 +1872,7 @@ local block_state deflate_fast(s, flush) if (s->lookahead == 0) break; /* flush the current block */ } - /* Insert the string window[strstart .. strstart+2] in the + /* Insert the string window[strstart .. strstart + 2] in the * dictionary, and set hash_head to the head of the hash chain: */ hash_head = NIL; @@ -1862,7 +1892,7 @@ local block_state deflate_fast(s, flush) /* longest_match() sets match_start */ } if (s->match_length >= MIN_MATCH) { - check_match(s, s->strstart, s->match_start, s->match_length); + check_match(s, s->strstart, s->match_start, (int)s->match_length); _tr_tally_dist(s, s->strstart - s->match_start, s->match_length - MIN_MATCH, bflush); @@ -1890,7 +1920,7 @@ local block_state deflate_fast(s, flush) s->strstart += s->match_length; s->match_length = 0; s->ins_h = s->window[s->strstart]; - UPDATE_HASH(s, s->ins_h, s->window[s->strstart+1]); + UPDATE_HASH(s, s->ins_h, s->window[s->strstart + 1]); #if MIN_MATCH != 3 Call UPDATE_HASH() MIN_MATCH-3 more times #endif @@ -1901,7 +1931,7 @@ local block_state deflate_fast(s, flush) } else { /* No match, output a literal byte */ Tracevv((stderr,"%c", s->window[s->strstart])); - _tr_tally_lit (s, s->window[s->strstart], bflush); + _tr_tally_lit(s, s->window[s->strstart], bflush); s->lookahead--; s->strstart++; } @@ -1912,7 +1942,7 @@ local block_state deflate_fast(s, flush) FLUSH_BLOCK(s, 1); return finish_done; } - if (s->last_lit) + if (s->sym_next) FLUSH_BLOCK(s, 0); return block_done; } @@ -1923,10 +1953,7 @@ local block_state deflate_fast(s, flush) * evaluation for matches: a match is finally adopted only if there is * no better match at the next window position. */ -local block_state deflate_slow(s, flush) - deflate_state *s; - int flush; -{ +local block_state deflate_slow(deflate_state *s, int flush) { IPos hash_head; /* head of hash chain */ int bflush; /* set if current block must be flushed */ @@ -1945,7 +1972,7 @@ local block_state deflate_slow(s, flush) if (s->lookahead == 0) break; /* flush the current block */ } - /* Insert the string window[strstart .. strstart+2] in the + /* Insert the string window[strstart .. strstart + 2] in the * dictionary, and set hash_head to the head of the hash chain: */ hash_head = NIL; @@ -1987,17 +2014,17 @@ local block_state deflate_slow(s, flush) uInt max_insert = s->strstart + s->lookahead - MIN_MATCH; /* Do not insert strings in hash table beyond this. */ - check_match(s, s->strstart-1, s->prev_match, s->prev_length); + check_match(s, s->strstart - 1, s->prev_match, (int)s->prev_length); - _tr_tally_dist(s, s->strstart -1 - s->prev_match, + _tr_tally_dist(s, s->strstart - 1 - s->prev_match, s->prev_length - MIN_MATCH, bflush); /* Insert in hash table all strings up to the end of the match. - * strstart-1 and strstart are already inserted. If there is not + * strstart - 1 and strstart are already inserted. If there is not * enough lookahead, the last two strings are not inserted in * the hash table. */ - s->lookahead -= s->prev_length-1; + s->lookahead -= s->prev_length - 1; s->prev_length -= 2; do { if (++s->strstart <= max_insert) { @@ -2015,8 +2042,8 @@ local block_state deflate_slow(s, flush) * single literal. If there was a match but the current match * is longer, truncate the previous match to a single literal. */ - Tracevv((stderr,"%c", s->window[s->strstart-1])); - _tr_tally_lit(s, s->window[s->strstart-1], bflush); + Tracevv((stderr,"%c", s->window[s->strstart - 1])); + _tr_tally_lit(s, s->window[s->strstart - 1], bflush); if (bflush) { FLUSH_BLOCK_ONLY(s, 0); } @@ -2034,8 +2061,8 @@ local block_state deflate_slow(s, flush) } Assert (flush != Z_NO_FLUSH, "no flush?"); if (s->match_available) { - Tracevv((stderr,"%c", s->window[s->strstart-1])); - _tr_tally_lit(s, s->window[s->strstart-1], bflush); + Tracevv((stderr,"%c", s->window[s->strstart - 1])); + _tr_tally_lit(s, s->window[s->strstart - 1], bflush); s->match_available = 0; } s->insert = s->strstart < MIN_MATCH-1 ? s->strstart : MIN_MATCH-1; @@ -2043,7 +2070,7 @@ local block_state deflate_slow(s, flush) FLUSH_BLOCK(s, 1); return finish_done; } - if (s->last_lit) + if (s->sym_next) FLUSH_BLOCK(s, 0); return block_done; } @@ -2054,10 +2081,7 @@ local block_state deflate_slow(s, flush) * one. Do not maintain a hash table. (It will be regenerated if this run of * deflate switches away from Z_RLE.) */ -local block_state deflate_rle(s, flush) - deflate_state *s; - int flush; -{ +local block_state deflate_rle(deflate_state *s, int flush) { int bflush; /* set if current block must be flushed */ uInt prev; /* byte at distance one to match */ Bytef *scan, *strend; /* scan goes up to strend for length of run */ @@ -2092,12 +2116,13 @@ local block_state deflate_rle(s, flush) if (s->match_length > s->lookahead) s->match_length = s->lookahead; } - Assert(scan <= s->window+(uInt)(s->window_size-1), "wild scan"); + Assert(scan <= s->window + (uInt)(s->window_size - 1), + "wild scan"); } /* Emit match if have run of MIN_MATCH or longer, else emit literal */ if (s->match_length >= MIN_MATCH) { - check_match(s, s->strstart, s->strstart - 1, s->match_length); + check_match(s, s->strstart, s->strstart - 1, (int)s->match_length); _tr_tally_dist(s, 1, s->match_length - MIN_MATCH, bflush); @@ -2107,7 +2132,7 @@ local block_state deflate_rle(s, flush) } else { /* No match, output a literal byte */ Tracevv((stderr,"%c", s->window[s->strstart])); - _tr_tally_lit (s, s->window[s->strstart], bflush); + _tr_tally_lit(s, s->window[s->strstart], bflush); s->lookahead--; s->strstart++; } @@ -2118,7 +2143,7 @@ local block_state deflate_rle(s, flush) FLUSH_BLOCK(s, 1); return finish_done; } - if (s->last_lit) + if (s->sym_next) FLUSH_BLOCK(s, 0); return block_done; } @@ -2127,10 +2152,7 @@ local block_state deflate_rle(s, flush) * For Z_HUFFMAN_ONLY, do not look for matches. Do not maintain a hash table. * (It will be regenerated if this run of deflate switches away from Huffman.) */ -local block_state deflate_huff(s, flush) - deflate_state *s; - int flush; -{ +local block_state deflate_huff(deflate_state *s, int flush) { int bflush; /* set if current block must be flushed */ for (;;) { @@ -2147,7 +2169,7 @@ local block_state deflate_huff(s, flush) /* Output a literal byte */ s->match_length = 0; Tracevv((stderr,"%c", s->window[s->strstart])); - _tr_tally_lit (s, s->window[s->strstart], bflush); + _tr_tally_lit(s, s->window[s->strstart], bflush); s->lookahead--; s->strstart++; if (bflush) FLUSH_BLOCK(s, 0); @@ -2157,7 +2179,7 @@ local block_state deflate_huff(s, flush) FLUSH_BLOCK(s, 1); return finish_done; } - if (s->last_lit) + if (s->sym_next) FLUSH_BLOCK(s, 0); return block_done; } diff --git a/src/common/dep/zlib/deflate.h b/src/common/dep/zlib/deflate.h index 23ecdd31..0732ba83 100644 --- a/src/common/dep/zlib/deflate.h +++ b/src/common/dep/zlib/deflate.h @@ -1,5 +1,5 @@ /* deflate.h -- internal compression state - * Copyright (C) 1995-2016 Jean-loup Gailly + * Copyright (C) 1995-2026 Jean-loup Gailly * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -23,6 +23,10 @@ # define GZIP #endif +/* define LIT_MEM to slightly increase the speed of deflate (order 1% to 2%) at + the cost of a larger memory footprint */ +/* #define LIT_MEM */ + /* =========================================================================== * Internal compression state. */ @@ -217,7 +221,14 @@ typedef struct internal_state { /* Depth of each subtree used as tie breaker for trees of equal frequency */ - uchf *l_buf; /* buffer for literals or lengths */ +#ifdef LIT_MEM +# define LIT_BUFS 5 + ushf *d_buf; /* buffer for distances */ + uchf *l_buf; /* buffer for literals/lengths */ +#else +# define LIT_BUFS 4 + uchf *sym_buf; /* buffer for distances and literals/lengths */ +#endif uInt lit_bufsize; /* Size of match buffer for literals/lengths. There are 4 reasons for @@ -239,13 +250,8 @@ typedef struct internal_state { * - I can't count above 4 */ - uInt last_lit; /* running index in l_buf */ - - ushf *d_buf; - /* Buffer for distances. To simplify the code, d_buf and l_buf have - * the same number of elements. To use different lengths, an extra flag - * array would be necessary. - */ + uInt sym_next; /* running index in symbol buffer */ + uInt sym_end; /* symbol table full when sym_next reaches this */ ulg opt_len; /* bit length of current block with optimal trees */ ulg static_len; /* bit length of current block with static trees */ @@ -265,6 +271,9 @@ typedef struct internal_state { /* Number of valid bits in bi_buf. All bits above the last valid bit * are always zero. */ + int bi_used; + /* Last number of used bits when going to a byte boundary. + */ ulg high_water; /* High water mark offset in window for initialized bytes -- bytes above @@ -273,6 +282,9 @@ typedef struct internal_state { * updated to the new high water mark. */ + int slid; + /* True if the hash table has been slid since it was cleared. */ + } FAR deflate_state; /* Output a byte on the stream. @@ -296,14 +308,14 @@ typedef struct internal_state { memory checker errors from longest match routines */ /* in trees.c */ -void ZLIB_INTERNAL _tr_init OF((deflate_state *s)); -int ZLIB_INTERNAL _tr_tally OF((deflate_state *s, unsigned dist, unsigned lc)); -void ZLIB_INTERNAL _tr_flush_block OF((deflate_state *s, charf *buf, - ulg stored_len, int last)); -void ZLIB_INTERNAL _tr_flush_bits OF((deflate_state *s)); -void ZLIB_INTERNAL _tr_align OF((deflate_state *s)); -void ZLIB_INTERNAL _tr_stored_block OF((deflate_state *s, charf *buf, - ulg stored_len, int last)); +void ZLIB_INTERNAL _tr_init(deflate_state *s); +int ZLIB_INTERNAL _tr_tally(deflate_state *s, unsigned dist, unsigned lc); +void ZLIB_INTERNAL _tr_flush_block(deflate_state *s, charf *buf, + ulg stored_len, int last); +void ZLIB_INTERNAL _tr_flush_bits(deflate_state *s); +void ZLIB_INTERNAL _tr_align(deflate_state *s); +void ZLIB_INTERNAL _tr_stored_block(deflate_state *s, charf *buf, + ulg stored_len, int last); #define d_code(dist) \ ((dist) < 256 ? _dist_code[dist] : _dist_code[256+((dist)>>7)]) @@ -323,23 +335,45 @@ void ZLIB_INTERNAL _tr_stored_block OF((deflate_state *s, charf *buf, extern const uch ZLIB_INTERNAL _dist_code[]; #endif +#ifdef LIT_MEM +# define _tr_tally_lit(s, c, flush) \ + { uch cc = (c); \ + s->d_buf[s->sym_next] = 0; \ + s->l_buf[s->sym_next++] = cc; \ + s->dyn_ltree[cc].Freq++; \ + flush = (s->sym_next == s->sym_end); \ + } +# define _tr_tally_dist(s, distance, length, flush) \ + { uch len = (uch)(length); \ + ush dist = (ush)(distance); \ + s->d_buf[s->sym_next] = dist; \ + s->l_buf[s->sym_next++] = len; \ + dist--; \ + s->dyn_ltree[_length_code[len]+LITERALS+1].Freq++; \ + s->dyn_dtree[d_code(dist)].Freq++; \ + flush = (s->sym_next == s->sym_end); \ + } +#else # define _tr_tally_lit(s, c, flush) \ { uch cc = (c); \ - s->d_buf[s->last_lit] = 0; \ - s->l_buf[s->last_lit++] = cc; \ + s->sym_buf[s->sym_next++] = 0; \ + s->sym_buf[s->sym_next++] = 0; \ + s->sym_buf[s->sym_next++] = cc; \ s->dyn_ltree[cc].Freq++; \ - flush = (s->last_lit == s->lit_bufsize-1); \ + flush = (s->sym_next == s->sym_end); \ } # define _tr_tally_dist(s, distance, length, flush) \ { uch len = (uch)(length); \ ush dist = (ush)(distance); \ - s->d_buf[s->last_lit] = dist; \ - s->l_buf[s->last_lit++] = len; \ + s->sym_buf[s->sym_next++] = (uch)dist; \ + s->sym_buf[s->sym_next++] = (uch)(dist >> 8); \ + s->sym_buf[s->sym_next++] = len; \ dist--; \ s->dyn_ltree[_length_code[len]+LITERALS+1].Freq++; \ s->dyn_dtree[d_code(dist)].Freq++; \ - flush = (s->last_lit == s->lit_bufsize-1); \ + flush = (s->sym_next == s->sym_end); \ } +#endif #else # define _tr_tally_lit(s, c, flush) flush = _tr_tally(s, 0, c) # define _tr_tally_dist(s, distance, length, flush) \ diff --git a/src/common/dep/zlib/gzguts.h b/src/common/dep/zlib/gzguts.h index 990a4d25..266305de 100644 --- a/src/common/dep/zlib/gzguts.h +++ b/src/common/dep/zlib/gzguts.h @@ -1,5 +1,5 @@ /* gzguts.h -- zlib internal header definitions for gz* operations - * Copyright (C) 2004, 2005, 2010, 2011, 2012, 2013, 2016 Mark Adler + * Copyright (C) 2004-2026 Mark Adler * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -7,9 +7,8 @@ # ifndef _LARGEFILE_SOURCE # define _LARGEFILE_SOURCE 1 # endif -# ifdef _FILE_OFFSET_BITS -# undef _FILE_OFFSET_BITS -# endif +# undef _FILE_OFFSET_BITS +# undef _TIME_BITS #endif #ifdef HAVE_HIDDEN @@ -18,6 +17,18 @@ # define ZLIB_INTERNAL #endif +#if defined(_WIN32) +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif +# ifndef _CRT_SECURE_NO_WARNINGS +# define _CRT_SECURE_NO_WARNINGS +# endif +# ifndef _CRT_NONSTDC_NO_DEPRECATE +# define _CRT_NONSTDC_NO_DEPRECATE +# endif +#endif + #include #include "zlib.h" #ifdef STDC @@ -26,8 +37,8 @@ # include #endif -#ifndef _POSIX_SOURCE -# define _POSIX_SOURCE +#ifndef _POSIX_C_SOURCE +# define _POSIX_C_SOURCE 200112L #endif #include @@ -37,19 +48,13 @@ #if defined(__TURBOC__) || defined(_MSC_VER) || defined(_WIN32) # include +# include #endif -#if defined(_WIN32) || defined(__CYGWIN__) +#if defined(_WIN32) && !defined(WIDECHAR) # define WIDECHAR #endif -#ifdef WINAPI_FAMILY -# define open _open -# define read _read -# define write _write -# define close _close -#endif - #ifdef NO_DEFLATE /* for compatibility with old definition */ # define NO_GZCOMPRESS #endif @@ -73,33 +78,28 @@ #endif #ifndef HAVE_VSNPRINTF -# ifdef MSDOS +# if !defined(NO_vsnprintf) && \ + (defined(MSDOS) || defined(__TURBOC__) || defined(__SASC) || \ + defined(VMS) || defined(__OS400) || defined(__MVS__)) /* vsnprintf may exist on some MS-DOS compilers (DJGPP?), but for now we just assume it doesn't. */ # define NO_vsnprintf # endif -# ifdef __TURBOC__ -# define NO_vsnprintf -# endif # ifdef WIN32 /* In Win32, vsnprintf is available as the "non-ANSI" _vsnprintf. */ -# if !defined(vsnprintf) && !defined(NO_vsnprintf) -# if !defined(_MSC_VER) || ( defined(_MSC_VER) && _MSC_VER < 1500 ) -# define vsnprintf _vsnprintf +# if !defined(_MSC_VER) || ( defined(_MSC_VER) && _MSC_VER < 1500 ) +# ifndef vsnprintf +# define vsnprintf _vsnprintf # endif # endif -# endif -# ifdef __SASC -# define NO_vsnprintf -# endif -# ifdef VMS -# define NO_vsnprintf -# endif -# ifdef __OS400__ -# define NO_vsnprintf -# endif -# ifdef __MVS__ -# define NO_vsnprintf +# elif !defined(__STDC_VERSION__) || __STDC_VERSION__-0 < 199901L +/* Otherwise if C89/90, assume no C99 snprintf() or vsnprintf() */ +# ifndef NO_snprintf +# define NO_snprintf +# endif +# ifndef NO_vsnprintf +# define NO_vsnprintf +# endif # endif #endif @@ -119,8 +119,8 @@ /* gz* functions always use library allocation functions */ #ifndef STDC - extern voidp malloc OF((uInt size)); - extern void free OF((voidpf ptr)); + extern voidp malloc(uInt size); + extern void free(voidpf ptr); #endif /* get errno and strerror definition */ @@ -138,10 +138,10 @@ /* provide prototypes for these when building zlib without LFS */ #if !defined(_LARGEFILE64_SOURCE) || _LFS64_LARGEFILE-0 == 0 - ZEXTERN gzFile ZEXPORT gzopen64 OF((const char *, const char *)); - ZEXTERN z_off64_t ZEXPORT gzseek64 OF((gzFile, z_off64_t, int)); - ZEXTERN z_off64_t ZEXPORT gztell64 OF((gzFile)); - ZEXTERN z_off64_t ZEXPORT gzoffset64 OF((gzFile)); + ZEXTERN gzFile ZEXPORT gzopen64(const char *, const char *); + ZEXTERN z_off64_t ZEXPORT gzseek64(gzFile, z_off64_t, int); + ZEXTERN z_off64_t ZEXPORT gztell64(gzFile); + ZEXTERN z_off64_t ZEXPORT gzoffset64(gzFile); #endif /* default memLevel */ @@ -183,16 +183,18 @@ typedef struct { unsigned char *out; /* output buffer (double-sized when reading) */ int direct; /* 0 if processing gzip, 1 if transparent */ /* just for reading */ + int junk; /* -1 = start, 1 = junk candidate, 0 = in gzip */ int how; /* 0: get header, 1: copy, 2: decompress */ + int again; /* true if EAGAIN or EWOULDBLOCK on last i/o */ z_off64_t start; /* where the gzip data started, for rewinding */ int eof; /* true if end of input file reached */ int past; /* true if read requested past end */ /* just for writing */ int level; /* compression level */ int strategy; /* compression strategy */ + int reset; /* true if a reset is pending after a Z_FINISH */ /* seek request */ z_off64_t skip; /* amount to skip (already rewound if backwards) */ - int seek; /* true if seek request pending */ /* error information */ int err; /* error code */ char *msg; /* error message */ @@ -202,17 +204,13 @@ typedef struct { typedef gz_state FAR *gz_statep; /* shared functions */ -void ZLIB_INTERNAL gz_error OF((gz_statep, int, const char *)); +void ZLIB_INTERNAL gz_error(gz_statep, int, const char *); #if defined UNDER_CE -char ZLIB_INTERNAL *gz_strwinerror OF((DWORD error)); +char ZLIB_INTERNAL *gz_strwinerror(DWORD error); #endif /* GT_OFF(x), where x is an unsigned value, is true if x > maximum z_off64_t value -- needed when comparing unsigned to z_off64_t, which is signed (possible z_off64_t types off_t, off64_t, and long are all signed) */ -#ifdef INT_MAX -# define GT_OFF(x) (sizeof(int) == sizeof(z_off64_t) && (x) > INT_MAX) -#else -unsigned ZLIB_INTERNAL gz_intmax OF((void)); -# define GT_OFF(x) (sizeof(int) == sizeof(z_off64_t) && (x) > gz_intmax()) -#endif +unsigned ZLIB_INTERNAL gz_intmax(void); +#define GT_OFF(x) (sizeof(int) == sizeof(z_off64_t) && (x) > gz_intmax()) diff --git a/src/common/dep/zlib/infback.c b/src/common/dep/zlib/infback.c new file mode 100644 index 00000000..e6443feb --- /dev/null +++ b/src/common/dep/zlib/infback.c @@ -0,0 +1,581 @@ +/* infback.c -- inflate using a call-back interface + * Copyright (C) 1995-2026 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* + This code is largely copied from inflate.c. Normally either infback.o or + inflate.o would be linked into an application--not both. The interface + with inffast.c is retained so that optimized assembler-coded versions of + inflate_fast() can be used with either inflate.c or infback.c. + */ + +#include "zutil.h" +#include "inftrees.h" +#include "inflate.h" +#include "inffast.h" + +/* + strm provides memory allocation functions in zalloc and zfree, or + Z_NULL to use the library memory allocation functions. + + windowBits is in the range 8..15, and window is a user-supplied + window and output buffer that is 2**windowBits bytes. + */ +int ZEXPORT inflateBackInit_(z_streamp strm, int windowBits, + unsigned char FAR *window, const char *version, + int stream_size) { + struct inflate_state FAR *state; + + if (version == Z_NULL || version[0] != ZLIB_VERSION[0] || + stream_size != (int)(sizeof(z_stream))) + return Z_VERSION_ERROR; + if (strm == Z_NULL || window == Z_NULL || + windowBits < 8 || windowBits > 15) + return Z_STREAM_ERROR; + strm->msg = Z_NULL; /* in case we return an error */ + if (strm->zalloc == (alloc_func)0) { +#ifdef Z_SOLO + return Z_STREAM_ERROR; +#else + strm->zalloc = zcalloc; + strm->opaque = (voidpf)0; +#endif + } + if (strm->zfree == (free_func)0) +#ifdef Z_SOLO + return Z_STREAM_ERROR; +#else + strm->zfree = zcfree; +#endif + state = (struct inflate_state FAR *)ZALLOC(strm, 1, + sizeof(struct inflate_state)); + if (state == Z_NULL) return Z_MEM_ERROR; + Tracev((stderr, "inflate: allocated\n")); + strm->state = (struct internal_state FAR *)state; + state->dmax = 32768U; + state->wbits = (uInt)windowBits; + state->wsize = 1U << windowBits; + state->window = window; + state->wnext = 0; + state->whave = 0; + state->sane = 1; + return Z_OK; +} + +/* Macros for inflateBack(): */ + +/* Load returned state from inflate_fast() */ +#define LOAD() \ + do { \ + put = strm->next_out; \ + left = strm->avail_out; \ + next = strm->next_in; \ + have = strm->avail_in; \ + hold = state->hold; \ + bits = state->bits; \ + } while (0) + +/* Set state from registers for inflate_fast() */ +#define RESTORE() \ + do { \ + strm->next_out = put; \ + strm->avail_out = left; \ + strm->next_in = next; \ + strm->avail_in = have; \ + state->hold = hold; \ + state->bits = bits; \ + } while (0) + +/* Clear the input bit accumulator */ +#define INITBITS() \ + do { \ + hold = 0; \ + bits = 0; \ + } while (0) + +/* Assure that some input is available. If input is requested, but denied, + then return a Z_BUF_ERROR from inflateBack(). */ +#define PULL() \ + do { \ + if (have == 0) { \ + have = in(in_desc, &next); \ + if (have == 0) { \ + next = Z_NULL; \ + ret = Z_BUF_ERROR; \ + goto inf_leave; \ + } \ + } \ + } while (0) + +/* Get a byte of input into the bit accumulator, or return from inflateBack() + with an error if there is no input available. */ +#define PULLBYTE() \ + do { \ + PULL(); \ + have--; \ + hold += (unsigned long)(*next++) << bits; \ + bits += 8; \ + } while (0) + +/* Assure that there are at least n bits in the bit accumulator. If there is + not enough available input to do that, then return from inflateBack() with + an error. */ +#define NEEDBITS(n) \ + do { \ + while (bits < (unsigned)(n)) \ + PULLBYTE(); \ + } while (0) + +/* Return the low n bits of the bit accumulator (n < 16) */ +#define BITS(n) \ + ((unsigned)hold & ((1U << (n)) - 1)) + +/* Remove n bits from the bit accumulator */ +#define DROPBITS(n) \ + do { \ + hold >>= (n); \ + bits -= (unsigned)(n); \ + } while (0) + +/* Remove zero to seven bits as needed to go to a byte boundary */ +#define BYTEBITS() \ + do { \ + hold >>= bits & 7; \ + bits -= bits & 7; \ + } while (0) + +/* Assure that some output space is available, by writing out the window + if it's full. If the write fails, return from inflateBack() with a + Z_BUF_ERROR. */ +#define ROOM() \ + do { \ + if (left == 0) { \ + put = state->window; \ + left = state->wsize; \ + state->whave = left; \ + if (out(out_desc, put, left)) { \ + ret = Z_BUF_ERROR; \ + goto inf_leave; \ + } \ + } \ + } while (0) + +/* + strm provides the memory allocation functions and window buffer on input, + and provides information on the unused input on return. For Z_DATA_ERROR + returns, strm will also provide an error message. + + in() and out() are the call-back input and output functions. When + inflateBack() needs more input, it calls in(). When inflateBack() has + filled the window with output, or when it completes with data in the + window, it calls out() to write out the data. The application must not + change the provided input until in() is called again or inflateBack() + returns. The application must not change the window/output buffer until + inflateBack() returns. + + in() and out() are called with a descriptor parameter provided in the + inflateBack() call. This parameter can be a structure that provides the + information required to do the read or write, as well as accumulated + information on the input and output such as totals and check values. + + in() should return zero on failure. out() should return non-zero on + failure. If either in() or out() fails, than inflateBack() returns a + Z_BUF_ERROR. strm->next_in can be checked for Z_NULL to see whether it + was in() or out() that caused in the error. Otherwise, inflateBack() + returns Z_STREAM_END on success, Z_DATA_ERROR for an deflate format + error, or Z_MEM_ERROR if it could not allocate memory for the state. + inflateBack() can also return Z_STREAM_ERROR if the input parameters + are not correct, i.e. strm is Z_NULL or the state was not initialized. + */ +int ZEXPORT inflateBack(z_streamp strm, in_func in, void FAR *in_desc, + out_func out, void FAR *out_desc) { + struct inflate_state FAR *state; + z_const unsigned char FAR *next; /* next input */ + unsigned char FAR *put; /* next output */ + unsigned have, left; /* available input and output */ + unsigned long hold; /* bit buffer */ + unsigned bits; /* bits in bit buffer */ + unsigned copy; /* number of stored or match bytes to copy */ + unsigned char FAR *from; /* where to copy match bytes from */ + code here; /* current decoding table entry */ + code last; /* parent table entry */ + unsigned len; /* length to copy for repeats, bits to drop */ + int ret; /* return code */ + static const unsigned short order[19] = /* permutation of code lengths */ + {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; + + /* Check that the strm exists and that the state was initialized */ + if (strm == Z_NULL || strm->state == Z_NULL) + return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + + /* Reset the state */ + strm->msg = Z_NULL; + state->mode = TYPE; + state->last = 0; + state->whave = 0; + next = strm->next_in; + have = next != Z_NULL ? strm->avail_in : 0; + hold = 0; + bits = 0; + put = state->window; + left = state->wsize; + + /* Inflate until end of block marked as last */ + for (;;) + switch (state->mode) { + case TYPE: + /* determine and dispatch block type */ + if (state->last) { + BYTEBITS(); + state->mode = DONE; + break; + } + NEEDBITS(3); + state->last = BITS(1); + DROPBITS(1); + switch (BITS(2)) { + case 0: /* stored block */ + Tracev((stderr, "inflate: stored block%s\n", + state->last ? " (last)" : "")); + state->mode = STORED; + break; + case 1: /* fixed block */ + inflate_fixed(state); + Tracev((stderr, "inflate: fixed codes block%s\n", + state->last ? " (last)" : "")); + state->mode = LEN; /* decode codes */ + break; + case 2: /* dynamic block */ + Tracev((stderr, "inflate: dynamic codes block%s\n", + state->last ? " (last)" : "")); + state->mode = TABLE; + break; + default: + strm->msg = (z_const char *)"invalid block type"; + state->mode = BAD; + } + DROPBITS(2); + break; + + case STORED: + /* get and verify stored block length */ + BYTEBITS(); /* go to byte boundary */ + NEEDBITS(32); + if ((hold & 0xffff) != ((hold >> 16) ^ 0xffff)) { + strm->msg = (z_const char *)"invalid stored block lengths"; + state->mode = BAD; + break; + } + state->length = (unsigned)hold & 0xffff; + Tracev((stderr, "inflate: stored length %u\n", + state->length)); + INITBITS(); + + /* copy stored block from input to output */ + while (state->length != 0) { + copy = state->length; + PULL(); + ROOM(); + if (copy > have) copy = have; + if (copy > left) copy = left; + zmemcpy(put, next, copy); + have -= copy; + next += copy; + left -= copy; + put += copy; + state->length -= copy; + } + Tracev((stderr, "inflate: stored end\n")); + state->mode = TYPE; + break; + + case TABLE: + /* get dynamic table entries descriptor */ + NEEDBITS(14); + state->nlen = BITS(5) + 257; + DROPBITS(5); + state->ndist = BITS(5) + 1; + DROPBITS(5); + state->ncode = BITS(4) + 4; + DROPBITS(4); +#ifndef PKZIP_BUG_WORKAROUND + if (state->nlen > 286 || state->ndist > 30) { + strm->msg = (z_const char *) + "too many length or distance symbols"; + state->mode = BAD; + break; + } +#endif + Tracev((stderr, "inflate: table sizes ok\n")); + + /* get code length code lengths (not a typo) */ + state->have = 0; + while (state->have < state->ncode) { + NEEDBITS(3); + state->lens[order[state->have++]] = (unsigned short)BITS(3); + DROPBITS(3); + } + while (state->have < 19) + state->lens[order[state->have++]] = 0; + state->next = state->codes; + state->lencode = (code const FAR *)(state->next); + state->lenbits = 7; + ret = inflate_table(CODES, state->lens, 19, &(state->next), + &(state->lenbits), state->work); + if (ret) { + strm->msg = (z_const char *)"invalid code lengths set"; + state->mode = BAD; + break; + } + Tracev((stderr, "inflate: code lengths ok\n")); + + /* get length and distance code code lengths */ + state->have = 0; + while (state->have < state->nlen + state->ndist) { + for (;;) { + here = state->lencode[BITS(state->lenbits)]; + if ((unsigned)(here.bits) <= bits) break; + PULLBYTE(); + } + if (here.val < 16) { + DROPBITS(here.bits); + state->lens[state->have++] = here.val; + } + else { + if (here.val == 16) { + NEEDBITS(here.bits + 2); + DROPBITS(here.bits); + if (state->have == 0) { + strm->msg = (z_const char *) + "invalid bit length repeat"; + state->mode = BAD; + break; + } + len = (unsigned)(state->lens[state->have - 1]); + copy = 3 + BITS(2); + DROPBITS(2); + } + else if (here.val == 17) { + NEEDBITS(here.bits + 3); + DROPBITS(here.bits); + len = 0; + copy = 3 + BITS(3); + DROPBITS(3); + } + else { + NEEDBITS(here.bits + 7); + DROPBITS(here.bits); + len = 0; + copy = 11 + BITS(7); + DROPBITS(7); + } + if (state->have + copy > state->nlen + state->ndist) { + strm->msg = (z_const char *) + "invalid bit length repeat"; + state->mode = BAD; + break; + } + while (copy--) + state->lens[state->have++] = (unsigned short)len; + } + } + + /* handle error breaks in while */ + if (state->mode == BAD) break; + + /* check for end-of-block code (better have one) */ + if (state->lens[256] == 0) { + strm->msg = (z_const char *) + "invalid code -- missing end-of-block"; + state->mode = BAD; + break; + } + + /* build code tables -- note: do not change the lenbits or distbits + values here (9 and 6) without reading the comments in inftrees.h + concerning the ENOUGH constants, which depend on those values */ + state->next = state->codes; + state->lencode = (code const FAR *)(state->next); + state->lenbits = 9; + ret = inflate_table(LENS, state->lens, state->nlen, &(state->next), + &(state->lenbits), state->work); + if (ret) { + strm->msg = (z_const char *)"invalid literal/lengths set"; + state->mode = BAD; + break; + } + state->distcode = (code const FAR *)(state->next); + state->distbits = 6; + ret = inflate_table(DISTS, state->lens + state->nlen, state->ndist, + &(state->next), &(state->distbits), state->work); + if (ret) { + strm->msg = (z_const char *)"invalid distances set"; + state->mode = BAD; + break; + } + Tracev((stderr, "inflate: codes ok\n")); + state->mode = LEN; + /* fallthrough */ + + case LEN: + /* use inflate_fast() if we have enough input and output */ + if (have >= 6 && left >= 258) { + RESTORE(); + if (state->whave < state->wsize) + state->whave = state->wsize - left; + inflate_fast(strm, state->wsize); + LOAD(); + break; + } + + /* get a literal, length, or end-of-block code */ + for (;;) { + here = state->lencode[BITS(state->lenbits)]; + if ((unsigned)(here.bits) <= bits) break; + PULLBYTE(); + } + if (here.op && (here.op & 0xf0) == 0) { + last = here; + for (;;) { + here = state->lencode[last.val + + (BITS(last.bits + last.op) >> last.bits)]; + if ((unsigned)(last.bits + here.bits) <= bits) break; + PULLBYTE(); + } + DROPBITS(last.bits); + } + DROPBITS(here.bits); + state->length = (unsigned)here.val; + + /* process literal */ + if (here.op == 0) { + Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ? + "inflate: literal '%c'\n" : + "inflate: literal 0x%02x\n", here.val)); + ROOM(); + *put++ = (unsigned char)(state->length); + left--; + state->mode = LEN; + break; + } + + /* process end of block */ + if (here.op & 32) { + Tracevv((stderr, "inflate: end of block\n")); + state->mode = TYPE; + break; + } + + /* invalid code */ + if (here.op & 64) { + strm->msg = (z_const char *)"invalid literal/length code"; + state->mode = BAD; + break; + } + + /* length code -- get extra bits, if any */ + state->extra = (unsigned)(here.op) & 15; + if (state->extra != 0) { + NEEDBITS(state->extra); + state->length += BITS(state->extra); + DROPBITS(state->extra); + } + Tracevv((stderr, "inflate: length %u\n", state->length)); + + /* get distance code */ + for (;;) { + here = state->distcode[BITS(state->distbits)]; + if ((unsigned)(here.bits) <= bits) break; + PULLBYTE(); + } + if ((here.op & 0xf0) == 0) { + last = here; + for (;;) { + here = state->distcode[last.val + + (BITS(last.bits + last.op) >> last.bits)]; + if ((unsigned)(last.bits + here.bits) <= bits) break; + PULLBYTE(); + } + DROPBITS(last.bits); + } + DROPBITS(here.bits); + if (here.op & 64) { + strm->msg = (z_const char *)"invalid distance code"; + state->mode = BAD; + break; + } + state->offset = (unsigned)here.val; + + /* get distance extra bits, if any */ + state->extra = (unsigned)(here.op) & 15; + if (state->extra != 0) { + NEEDBITS(state->extra); + state->offset += BITS(state->extra); + DROPBITS(state->extra); + } + if (state->offset > state->wsize - (state->whave < state->wsize ? + left : 0)) { + strm->msg = (z_const char *)"invalid distance too far back"; + state->mode = BAD; + break; + } + Tracevv((stderr, "inflate: distance %u\n", state->offset)); + + /* copy match from window to output */ + do { + ROOM(); + copy = state->wsize - state->offset; + if (copy < left) { + from = put + copy; + copy = left - copy; + } + else { + from = put - state->offset; + copy = left; + } + if (copy > state->length) copy = state->length; + state->length -= copy; + left -= copy; + do { + *put++ = *from++; + } while (--copy); + } while (state->length != 0); + break; + + case DONE: + /* inflate stream terminated properly */ + ret = Z_STREAM_END; + goto inf_leave; + + case BAD: + ret = Z_DATA_ERROR; + goto inf_leave; + + default: + /* can't happen, but makes compilers happy */ + ret = Z_STREAM_ERROR; + goto inf_leave; + } + + /* Write leftover output and return unused input */ + inf_leave: + if (left < state->wsize) { + if (out(out_desc, state->window, state->wsize - left) && + ret == Z_STREAM_END) + ret = Z_BUF_ERROR; + } + strm->next_in = next; + strm->avail_in = have; + return ret; +} + +int ZEXPORT inflateBackEnd(z_streamp strm) { + if (strm == Z_NULL || strm->state == Z_NULL || strm->zfree == (free_func)0) + return Z_STREAM_ERROR; + ZFREE(strm, strm->state); + strm->state = Z_NULL; + Tracev((stderr, "inflate: end\n")); + return Z_OK; +} diff --git a/src/common/dep/zlib/inffast.c b/src/common/dep/zlib/inffast.c index 0dbd1dbc..d1657f3f 100644 --- a/src/common/dep/zlib/inffast.c +++ b/src/common/dep/zlib/inffast.c @@ -1,5 +1,5 @@ /* inffast.c -- fast decoding - * Copyright (C) 1995-2017 Mark Adler + * Copyright (C) 1995-2026 Mark Adler * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -47,10 +47,7 @@ requires strm->avail_out >= 258 for each loop to avoid checking for output space. */ -void ZLIB_INTERNAL inflate_fast(strm, start) -z_streamp strm; -unsigned start; /* inflate()'s starting value for strm->avail_out */ -{ +void ZLIB_INTERNAL inflate_fast(z_streamp strm, unsigned start) { struct inflate_state FAR *state; z_const unsigned char FAR *in; /* local strm->next_in */ z_const unsigned char FAR *last; /* have enough input while in < last */ @@ -70,7 +67,7 @@ unsigned start; /* inflate()'s starting value for strm->avail_out */ code const FAR *dcode; /* local strm->distcode */ unsigned lmask; /* mask for first level of length codes */ unsigned dmask; /* mask for first level of distance codes */ - code here; /* retrieved table entry */ + code const *here; /* retrieved table entry */ unsigned op; /* code bits, operation, extra bits, or */ /* window position, window bytes to copy */ unsigned len; /* match length, unused bytes */ @@ -107,20 +104,20 @@ unsigned start; /* inflate()'s starting value for strm->avail_out */ hold += (unsigned long)(*in++) << bits; bits += 8; } - here = lcode[hold & lmask]; + here = lcode + (hold & lmask); dolen: - op = (unsigned)(here.bits); + op = (unsigned)(here->bits); hold >>= op; bits -= op; - op = (unsigned)(here.op); + op = (unsigned)(here->op); if (op == 0) { /* literal */ - Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ? + Tracevv((stderr, here->val >= 0x20 && here->val < 0x7f ? "inflate: literal '%c'\n" : - "inflate: literal 0x%02x\n", here.val)); - *out++ = (unsigned char)(here.val); + "inflate: literal 0x%02x\n", here->val)); + *out++ = (unsigned char)(here->val); } else if (op & 16) { /* length base */ - len = (unsigned)(here.val); + len = (unsigned)(here->val); op &= 15; /* number of extra bits */ if (op) { if (bits < op) { @@ -138,14 +135,14 @@ unsigned start; /* inflate()'s starting value for strm->avail_out */ hold += (unsigned long)(*in++) << bits; bits += 8; } - here = dcode[hold & dmask]; + here = dcode + (hold & dmask); dodist: - op = (unsigned)(here.bits); + op = (unsigned)(here->bits); hold >>= op; bits -= op; - op = (unsigned)(here.op); + op = (unsigned)(here->op); if (op & 16) { /* distance base */ - dist = (unsigned)(here.val); + dist = (unsigned)(here->val); op &= 15; /* number of extra bits */ if (bits < op) { hold += (unsigned long)(*in++) << bits; @@ -158,7 +155,8 @@ unsigned start; /* inflate()'s starting value for strm->avail_out */ dist += (unsigned)hold & ((1U << op) - 1); #ifdef INFLATE_STRICT if (dist > dmax) { - strm->msg = (char *)"invalid distance too far back"; + strm->msg = (z_const char *) + "invalid distance too far back"; state->mode = BAD; break; } @@ -171,8 +169,8 @@ unsigned start; /* inflate()'s starting value for strm->avail_out */ op = dist - op; /* distance back in window */ if (op > whave) { if (state->sane) { - strm->msg = - (char *)"invalid distance too far back"; + strm->msg = (z_const char *) + "invalid distance too far back"; state->mode = BAD; break; } @@ -264,17 +262,17 @@ unsigned start; /* inflate()'s starting value for strm->avail_out */ } } else if ((op & 64) == 0) { /* 2nd level distance code */ - here = dcode[here.val + (hold & ((1U << op) - 1))]; + here = dcode + here->val + (hold & ((1U << op) - 1)); goto dodist; } else { - strm->msg = (char *)"invalid distance code"; + strm->msg = (z_const char *)"invalid distance code"; state->mode = BAD; break; } } else if ((op & 64) == 0) { /* 2nd level length code */ - here = lcode[here.val + (hold & ((1U << op) - 1))]; + here = lcode + here->val + (hold & ((1U << op) - 1)); goto dolen; } else if (op & 32) { /* end-of-block */ @@ -283,7 +281,7 @@ unsigned start; /* inflate()'s starting value for strm->avail_out */ break; } else { - strm->msg = (char *)"invalid literal/length code"; + strm->msg = (z_const char *)"invalid literal/length code"; state->mode = BAD; break; } diff --git a/src/common/dep/zlib/inffast.h b/src/common/dep/zlib/inffast.h index e5c1aa4c..49c6d156 100644 --- a/src/common/dep/zlib/inffast.h +++ b/src/common/dep/zlib/inffast.h @@ -8,4 +8,4 @@ subject to change. Applications should only use zlib.h. */ -void ZLIB_INTERNAL inflate_fast OF((z_streamp strm, unsigned start)); +void ZLIB_INTERNAL inflate_fast(z_streamp strm, unsigned start); diff --git a/src/common/dep/zlib/inffixed.h b/src/common/dep/zlib/inffixed.h index d6283277..05ce88e4 100644 --- a/src/common/dep/zlib/inffixed.h +++ b/src/common/dep/zlib/inffixed.h @@ -1,94 +1,94 @@ - /* inffixed.h -- table for decoding fixed codes - * Generated automatically by makefixed(). - */ +/* inffixed.h -- table for decoding fixed codes + * Generated automatically by makefixed(). + */ - /* WARNING: this file should *not* be used by applications. - It is part of the implementation of this library and is - subject to change. Applications should only use zlib.h. - */ +/* WARNING: this file should *not* be used by applications. + It is part of the implementation of this library and is + subject to change. Applications should only use zlib.h. + */ - static const code lenfix[512] = { - {96,7,0},{0,8,80},{0,8,16},{20,8,115},{18,7,31},{0,8,112},{0,8,48}, - {0,9,192},{16,7,10},{0,8,96},{0,8,32},{0,9,160},{0,8,0},{0,8,128}, - {0,8,64},{0,9,224},{16,7,6},{0,8,88},{0,8,24},{0,9,144},{19,7,59}, - {0,8,120},{0,8,56},{0,9,208},{17,7,17},{0,8,104},{0,8,40},{0,9,176}, - {0,8,8},{0,8,136},{0,8,72},{0,9,240},{16,7,4},{0,8,84},{0,8,20}, - {21,8,227},{19,7,43},{0,8,116},{0,8,52},{0,9,200},{17,7,13},{0,8,100}, - {0,8,36},{0,9,168},{0,8,4},{0,8,132},{0,8,68},{0,9,232},{16,7,8}, - {0,8,92},{0,8,28},{0,9,152},{20,7,83},{0,8,124},{0,8,60},{0,9,216}, - {18,7,23},{0,8,108},{0,8,44},{0,9,184},{0,8,12},{0,8,140},{0,8,76}, - {0,9,248},{16,7,3},{0,8,82},{0,8,18},{21,8,163},{19,7,35},{0,8,114}, - {0,8,50},{0,9,196},{17,7,11},{0,8,98},{0,8,34},{0,9,164},{0,8,2}, - {0,8,130},{0,8,66},{0,9,228},{16,7,7},{0,8,90},{0,8,26},{0,9,148}, - {20,7,67},{0,8,122},{0,8,58},{0,9,212},{18,7,19},{0,8,106},{0,8,42}, - {0,9,180},{0,8,10},{0,8,138},{0,8,74},{0,9,244},{16,7,5},{0,8,86}, - {0,8,22},{64,8,0},{19,7,51},{0,8,118},{0,8,54},{0,9,204},{17,7,15}, - {0,8,102},{0,8,38},{0,9,172},{0,8,6},{0,8,134},{0,8,70},{0,9,236}, - {16,7,9},{0,8,94},{0,8,30},{0,9,156},{20,7,99},{0,8,126},{0,8,62}, - {0,9,220},{18,7,27},{0,8,110},{0,8,46},{0,9,188},{0,8,14},{0,8,142}, - {0,8,78},{0,9,252},{96,7,0},{0,8,81},{0,8,17},{21,8,131},{18,7,31}, - {0,8,113},{0,8,49},{0,9,194},{16,7,10},{0,8,97},{0,8,33},{0,9,162}, - {0,8,1},{0,8,129},{0,8,65},{0,9,226},{16,7,6},{0,8,89},{0,8,25}, - {0,9,146},{19,7,59},{0,8,121},{0,8,57},{0,9,210},{17,7,17},{0,8,105}, - {0,8,41},{0,9,178},{0,8,9},{0,8,137},{0,8,73},{0,9,242},{16,7,4}, - {0,8,85},{0,8,21},{16,8,258},{19,7,43},{0,8,117},{0,8,53},{0,9,202}, - {17,7,13},{0,8,101},{0,8,37},{0,9,170},{0,8,5},{0,8,133},{0,8,69}, - {0,9,234},{16,7,8},{0,8,93},{0,8,29},{0,9,154},{20,7,83},{0,8,125}, - {0,8,61},{0,9,218},{18,7,23},{0,8,109},{0,8,45},{0,9,186},{0,8,13}, - {0,8,141},{0,8,77},{0,9,250},{16,7,3},{0,8,83},{0,8,19},{21,8,195}, - {19,7,35},{0,8,115},{0,8,51},{0,9,198},{17,7,11},{0,8,99},{0,8,35}, - {0,9,166},{0,8,3},{0,8,131},{0,8,67},{0,9,230},{16,7,7},{0,8,91}, - {0,8,27},{0,9,150},{20,7,67},{0,8,123},{0,8,59},{0,9,214},{18,7,19}, - {0,8,107},{0,8,43},{0,9,182},{0,8,11},{0,8,139},{0,8,75},{0,9,246}, - {16,7,5},{0,8,87},{0,8,23},{64,8,0},{19,7,51},{0,8,119},{0,8,55}, - {0,9,206},{17,7,15},{0,8,103},{0,8,39},{0,9,174},{0,8,7},{0,8,135}, - {0,8,71},{0,9,238},{16,7,9},{0,8,95},{0,8,31},{0,9,158},{20,7,99}, - {0,8,127},{0,8,63},{0,9,222},{18,7,27},{0,8,111},{0,8,47},{0,9,190}, - {0,8,15},{0,8,143},{0,8,79},{0,9,254},{96,7,0},{0,8,80},{0,8,16}, - {20,8,115},{18,7,31},{0,8,112},{0,8,48},{0,9,193},{16,7,10},{0,8,96}, - {0,8,32},{0,9,161},{0,8,0},{0,8,128},{0,8,64},{0,9,225},{16,7,6}, - {0,8,88},{0,8,24},{0,9,145},{19,7,59},{0,8,120},{0,8,56},{0,9,209}, - {17,7,17},{0,8,104},{0,8,40},{0,9,177},{0,8,8},{0,8,136},{0,8,72}, - {0,9,241},{16,7,4},{0,8,84},{0,8,20},{21,8,227},{19,7,43},{0,8,116}, - {0,8,52},{0,9,201},{17,7,13},{0,8,100},{0,8,36},{0,9,169},{0,8,4}, - {0,8,132},{0,8,68},{0,9,233},{16,7,8},{0,8,92},{0,8,28},{0,9,153}, - {20,7,83},{0,8,124},{0,8,60},{0,9,217},{18,7,23},{0,8,108},{0,8,44}, - {0,9,185},{0,8,12},{0,8,140},{0,8,76},{0,9,249},{16,7,3},{0,8,82}, - {0,8,18},{21,8,163},{19,7,35},{0,8,114},{0,8,50},{0,9,197},{17,7,11}, - {0,8,98},{0,8,34},{0,9,165},{0,8,2},{0,8,130},{0,8,66},{0,9,229}, - {16,7,7},{0,8,90},{0,8,26},{0,9,149},{20,7,67},{0,8,122},{0,8,58}, - {0,9,213},{18,7,19},{0,8,106},{0,8,42},{0,9,181},{0,8,10},{0,8,138}, - {0,8,74},{0,9,245},{16,7,5},{0,8,86},{0,8,22},{64,8,0},{19,7,51}, - {0,8,118},{0,8,54},{0,9,205},{17,7,15},{0,8,102},{0,8,38},{0,9,173}, - {0,8,6},{0,8,134},{0,8,70},{0,9,237},{16,7,9},{0,8,94},{0,8,30}, - {0,9,157},{20,7,99},{0,8,126},{0,8,62},{0,9,221},{18,7,27},{0,8,110}, - {0,8,46},{0,9,189},{0,8,14},{0,8,142},{0,8,78},{0,9,253},{96,7,0}, - {0,8,81},{0,8,17},{21,8,131},{18,7,31},{0,8,113},{0,8,49},{0,9,195}, - {16,7,10},{0,8,97},{0,8,33},{0,9,163},{0,8,1},{0,8,129},{0,8,65}, - {0,9,227},{16,7,6},{0,8,89},{0,8,25},{0,9,147},{19,7,59},{0,8,121}, - {0,8,57},{0,9,211},{17,7,17},{0,8,105},{0,8,41},{0,9,179},{0,8,9}, - {0,8,137},{0,8,73},{0,9,243},{16,7,4},{0,8,85},{0,8,21},{16,8,258}, - {19,7,43},{0,8,117},{0,8,53},{0,9,203},{17,7,13},{0,8,101},{0,8,37}, - {0,9,171},{0,8,5},{0,8,133},{0,8,69},{0,9,235},{16,7,8},{0,8,93}, - {0,8,29},{0,9,155},{20,7,83},{0,8,125},{0,8,61},{0,9,219},{18,7,23}, - {0,8,109},{0,8,45},{0,9,187},{0,8,13},{0,8,141},{0,8,77},{0,9,251}, - {16,7,3},{0,8,83},{0,8,19},{21,8,195},{19,7,35},{0,8,115},{0,8,51}, - {0,9,199},{17,7,11},{0,8,99},{0,8,35},{0,9,167},{0,8,3},{0,8,131}, - {0,8,67},{0,9,231},{16,7,7},{0,8,91},{0,8,27},{0,9,151},{20,7,67}, - {0,8,123},{0,8,59},{0,9,215},{18,7,19},{0,8,107},{0,8,43},{0,9,183}, - {0,8,11},{0,8,139},{0,8,75},{0,9,247},{16,7,5},{0,8,87},{0,8,23}, - {64,8,0},{19,7,51},{0,8,119},{0,8,55},{0,9,207},{17,7,15},{0,8,103}, - {0,8,39},{0,9,175},{0,8,7},{0,8,135},{0,8,71},{0,9,239},{16,7,9}, - {0,8,95},{0,8,31},{0,9,159},{20,7,99},{0,8,127},{0,8,63},{0,9,223}, - {18,7,27},{0,8,111},{0,8,47},{0,9,191},{0,8,15},{0,8,143},{0,8,79}, - {0,9,255} - }; +static const code lenfix[512] = { + {96,7,0},{0,8,80},{0,8,16},{20,8,115},{18,7,31},{0,8,112},{0,8,48}, + {0,9,192},{16,7,10},{0,8,96},{0,8,32},{0,9,160},{0,8,0},{0,8,128}, + {0,8,64},{0,9,224},{16,7,6},{0,8,88},{0,8,24},{0,9,144},{19,7,59}, + {0,8,120},{0,8,56},{0,9,208},{17,7,17},{0,8,104},{0,8,40},{0,9,176}, + {0,8,8},{0,8,136},{0,8,72},{0,9,240},{16,7,4},{0,8,84},{0,8,20}, + {21,8,227},{19,7,43},{0,8,116},{0,8,52},{0,9,200},{17,7,13},{0,8,100}, + {0,8,36},{0,9,168},{0,8,4},{0,8,132},{0,8,68},{0,9,232},{16,7,8}, + {0,8,92},{0,8,28},{0,9,152},{20,7,83},{0,8,124},{0,8,60},{0,9,216}, + {18,7,23},{0,8,108},{0,8,44},{0,9,184},{0,8,12},{0,8,140},{0,8,76}, + {0,9,248},{16,7,3},{0,8,82},{0,8,18},{21,8,163},{19,7,35},{0,8,114}, + {0,8,50},{0,9,196},{17,7,11},{0,8,98},{0,8,34},{0,9,164},{0,8,2}, + {0,8,130},{0,8,66},{0,9,228},{16,7,7},{0,8,90},{0,8,26},{0,9,148}, + {20,7,67},{0,8,122},{0,8,58},{0,9,212},{18,7,19},{0,8,106},{0,8,42}, + {0,9,180},{0,8,10},{0,8,138},{0,8,74},{0,9,244},{16,7,5},{0,8,86}, + {0,8,22},{64,8,0},{19,7,51},{0,8,118},{0,8,54},{0,9,204},{17,7,15}, + {0,8,102},{0,8,38},{0,9,172},{0,8,6},{0,8,134},{0,8,70},{0,9,236}, + {16,7,9},{0,8,94},{0,8,30},{0,9,156},{20,7,99},{0,8,126},{0,8,62}, + {0,9,220},{18,7,27},{0,8,110},{0,8,46},{0,9,188},{0,8,14},{0,8,142}, + {0,8,78},{0,9,252},{96,7,0},{0,8,81},{0,8,17},{21,8,131},{18,7,31}, + {0,8,113},{0,8,49},{0,9,194},{16,7,10},{0,8,97},{0,8,33},{0,9,162}, + {0,8,1},{0,8,129},{0,8,65},{0,9,226},{16,7,6},{0,8,89},{0,8,25}, + {0,9,146},{19,7,59},{0,8,121},{0,8,57},{0,9,210},{17,7,17},{0,8,105}, + {0,8,41},{0,9,178},{0,8,9},{0,8,137},{0,8,73},{0,9,242},{16,7,4}, + {0,8,85},{0,8,21},{16,8,258},{19,7,43},{0,8,117},{0,8,53},{0,9,202}, + {17,7,13},{0,8,101},{0,8,37},{0,9,170},{0,8,5},{0,8,133},{0,8,69}, + {0,9,234},{16,7,8},{0,8,93},{0,8,29},{0,9,154},{20,7,83},{0,8,125}, + {0,8,61},{0,9,218},{18,7,23},{0,8,109},{0,8,45},{0,9,186},{0,8,13}, + {0,8,141},{0,8,77},{0,9,250},{16,7,3},{0,8,83},{0,8,19},{21,8,195}, + {19,7,35},{0,8,115},{0,8,51},{0,9,198},{17,7,11},{0,8,99},{0,8,35}, + {0,9,166},{0,8,3},{0,8,131},{0,8,67},{0,9,230},{16,7,7},{0,8,91}, + {0,8,27},{0,9,150},{20,7,67},{0,8,123},{0,8,59},{0,9,214},{18,7,19}, + {0,8,107},{0,8,43},{0,9,182},{0,8,11},{0,8,139},{0,8,75},{0,9,246}, + {16,7,5},{0,8,87},{0,8,23},{64,8,0},{19,7,51},{0,8,119},{0,8,55}, + {0,9,206},{17,7,15},{0,8,103},{0,8,39},{0,9,174},{0,8,7},{0,8,135}, + {0,8,71},{0,9,238},{16,7,9},{0,8,95},{0,8,31},{0,9,158},{20,7,99}, + {0,8,127},{0,8,63},{0,9,222},{18,7,27},{0,8,111},{0,8,47},{0,9,190}, + {0,8,15},{0,8,143},{0,8,79},{0,9,254},{96,7,0},{0,8,80},{0,8,16}, + {20,8,115},{18,7,31},{0,8,112},{0,8,48},{0,9,193},{16,7,10},{0,8,96}, + {0,8,32},{0,9,161},{0,8,0},{0,8,128},{0,8,64},{0,9,225},{16,7,6}, + {0,8,88},{0,8,24},{0,9,145},{19,7,59},{0,8,120},{0,8,56},{0,9,209}, + {17,7,17},{0,8,104},{0,8,40},{0,9,177},{0,8,8},{0,8,136},{0,8,72}, + {0,9,241},{16,7,4},{0,8,84},{0,8,20},{21,8,227},{19,7,43},{0,8,116}, + {0,8,52},{0,9,201},{17,7,13},{0,8,100},{0,8,36},{0,9,169},{0,8,4}, + {0,8,132},{0,8,68},{0,9,233},{16,7,8},{0,8,92},{0,8,28},{0,9,153}, + {20,7,83},{0,8,124},{0,8,60},{0,9,217},{18,7,23},{0,8,108},{0,8,44}, + {0,9,185},{0,8,12},{0,8,140},{0,8,76},{0,9,249},{16,7,3},{0,8,82}, + {0,8,18},{21,8,163},{19,7,35},{0,8,114},{0,8,50},{0,9,197},{17,7,11}, + {0,8,98},{0,8,34},{0,9,165},{0,8,2},{0,8,130},{0,8,66},{0,9,229}, + {16,7,7},{0,8,90},{0,8,26},{0,9,149},{20,7,67},{0,8,122},{0,8,58}, + {0,9,213},{18,7,19},{0,8,106},{0,8,42},{0,9,181},{0,8,10},{0,8,138}, + {0,8,74},{0,9,245},{16,7,5},{0,8,86},{0,8,22},{64,8,0},{19,7,51}, + {0,8,118},{0,8,54},{0,9,205},{17,7,15},{0,8,102},{0,8,38},{0,9,173}, + {0,8,6},{0,8,134},{0,8,70},{0,9,237},{16,7,9},{0,8,94},{0,8,30}, + {0,9,157},{20,7,99},{0,8,126},{0,8,62},{0,9,221},{18,7,27},{0,8,110}, + {0,8,46},{0,9,189},{0,8,14},{0,8,142},{0,8,78},{0,9,253},{96,7,0}, + {0,8,81},{0,8,17},{21,8,131},{18,7,31},{0,8,113},{0,8,49},{0,9,195}, + {16,7,10},{0,8,97},{0,8,33},{0,9,163},{0,8,1},{0,8,129},{0,8,65}, + {0,9,227},{16,7,6},{0,8,89},{0,8,25},{0,9,147},{19,7,59},{0,8,121}, + {0,8,57},{0,9,211},{17,7,17},{0,8,105},{0,8,41},{0,9,179},{0,8,9}, + {0,8,137},{0,8,73},{0,9,243},{16,7,4},{0,8,85},{0,8,21},{16,8,258}, + {19,7,43},{0,8,117},{0,8,53},{0,9,203},{17,7,13},{0,8,101},{0,8,37}, + {0,9,171},{0,8,5},{0,8,133},{0,8,69},{0,9,235},{16,7,8},{0,8,93}, + {0,8,29},{0,9,155},{20,7,83},{0,8,125},{0,8,61},{0,9,219},{18,7,23}, + {0,8,109},{0,8,45},{0,9,187},{0,8,13},{0,8,141},{0,8,77},{0,9,251}, + {16,7,3},{0,8,83},{0,8,19},{21,8,195},{19,7,35},{0,8,115},{0,8,51}, + {0,9,199},{17,7,11},{0,8,99},{0,8,35},{0,9,167},{0,8,3},{0,8,131}, + {0,8,67},{0,9,231},{16,7,7},{0,8,91},{0,8,27},{0,9,151},{20,7,67}, + {0,8,123},{0,8,59},{0,9,215},{18,7,19},{0,8,107},{0,8,43},{0,9,183}, + {0,8,11},{0,8,139},{0,8,75},{0,9,247},{16,7,5},{0,8,87},{0,8,23}, + {64,8,0},{19,7,51},{0,8,119},{0,8,55},{0,9,207},{17,7,15},{0,8,103}, + {0,8,39},{0,9,175},{0,8,7},{0,8,135},{0,8,71},{0,9,239},{16,7,9}, + {0,8,95},{0,8,31},{0,9,159},{20,7,99},{0,8,127},{0,8,63},{0,9,223}, + {18,7,27},{0,8,111},{0,8,47},{0,9,191},{0,8,15},{0,8,143},{0,8,79}, + {0,9,255} +}; - static const code distfix[32] = { - {16,5,1},{23,5,257},{19,5,17},{27,5,4097},{17,5,5},{25,5,1025}, - {21,5,65},{29,5,16385},{16,5,3},{24,5,513},{20,5,33},{28,5,8193}, - {18,5,9},{26,5,2049},{22,5,129},{64,5,0},{16,5,2},{23,5,385}, - {19,5,25},{27,5,6145},{17,5,7},{25,5,1537},{21,5,97},{29,5,24577}, - {16,5,4},{24,5,769},{20,5,49},{28,5,12289},{18,5,13},{26,5,3073}, - {22,5,193},{64,5,0} - }; +static const code distfix[32] = { + {16,5,1},{23,5,257},{19,5,17},{27,5,4097},{17,5,5},{25,5,1025}, + {21,5,65},{29,5,16385},{16,5,3},{24,5,513},{20,5,33},{28,5,8193}, + {18,5,9},{26,5,2049},{22,5,129},{64,5,0},{16,5,2},{23,5,385}, + {19,5,25},{27,5,6145},{17,5,7},{25,5,1537},{21,5,97},{29,5,24577}, + {16,5,4},{24,5,769},{20,5,49},{28,5,12289},{18,5,13},{26,5,3073}, + {22,5,193},{64,5,0} +}; diff --git a/src/common/dep/zlib/inflate.c b/src/common/dep/zlib/inflate.c index ac333e8c..5f5d4922 100644 --- a/src/common/dep/zlib/inflate.c +++ b/src/common/dep/zlib/inflate.c @@ -1,5 +1,5 @@ /* inflate.c -- zlib decompression - * Copyright (C) 1995-2016 Mark Adler + * Copyright (C) 1995-2026 Mark Adler * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -85,26 +85,7 @@ #include "inflate.h" #include "inffast.h" -#ifdef MAKEFIXED -# ifndef BUILDFIXED -# define BUILDFIXED -# endif -#endif - -/* function prototypes */ -local int inflateStateCheck OF((z_streamp strm)); -local void fixedtables OF((struct inflate_state FAR *state)); -local int updatewindow OF((z_streamp strm, const unsigned char FAR *end, - unsigned copy)); -#ifdef BUILDFIXED - void makefixed OF((void)); -#endif -local unsigned syncsearch OF((unsigned FAR *have, const unsigned char FAR *buf, - unsigned len)); - -local int inflateStateCheck(strm) -z_streamp strm; -{ +local int inflateStateCheck(z_streamp strm) { struct inflate_state FAR *state; if (strm == Z_NULL || strm->zalloc == (alloc_func)0 || strm->zfree == (free_func)0) @@ -116,20 +97,20 @@ z_streamp strm; return 0; } -int ZEXPORT inflateResetKeep(strm) -z_streamp strm; -{ +int ZEXPORT inflateResetKeep(z_streamp strm) { struct inflate_state FAR *state; if (inflateStateCheck(strm)) return Z_STREAM_ERROR; state = (struct inflate_state FAR *)strm->state; strm->total_in = strm->total_out = state->total = 0; strm->msg = Z_NULL; + strm->data_type = 0; if (state->wrap) /* to support ill-conceived Java test suite */ strm->adler = state->wrap & 1; state->mode = HEAD; state->last = 0; state->havedict = 0; + state->flags = -1; state->dmax = 32768U; state->head = Z_NULL; state->hold = 0; @@ -141,9 +122,7 @@ z_streamp strm; return Z_OK; } -int ZEXPORT inflateReset(strm) -z_streamp strm; -{ +int ZEXPORT inflateReset(z_streamp strm) { struct inflate_state FAR *state; if (inflateStateCheck(strm)) return Z_STREAM_ERROR; @@ -154,10 +133,7 @@ z_streamp strm; return inflateResetKeep(strm); } -int ZEXPORT inflateReset2(strm, windowBits) -z_streamp strm; -int windowBits; -{ +int ZEXPORT inflateReset2(z_streamp strm, int windowBits) { int wrap; struct inflate_state FAR *state; @@ -167,6 +143,8 @@ int windowBits; /* extract wrap request from windowBits parameter */ if (windowBits < 0) { + if (windowBits < -15) + return Z_STREAM_ERROR; wrap = 0; windowBits = -windowBits; } @@ -192,12 +170,8 @@ int windowBits; return inflateReset(strm); } -int ZEXPORT inflateInit2_(strm, windowBits, version, stream_size) -z_streamp strm; -int windowBits; -const char *version; -int stream_size; -{ +int ZEXPORT inflateInit2_(z_streamp strm, int windowBits, + const char *version, int stream_size) { int ret; struct inflate_state FAR *state; @@ -223,6 +197,7 @@ int stream_size; state = (struct inflate_state FAR *) ZALLOC(strm, 1, sizeof(struct inflate_state)); if (state == Z_NULL) return Z_MEM_ERROR; + zmemzero(state, sizeof(struct inflate_state)); Tracev((stderr, "inflate: allocated\n")); strm->state = (struct internal_state FAR *)state; state->strm = strm; @@ -236,22 +211,17 @@ int stream_size; return ret; } -int ZEXPORT inflateInit_(strm, version, stream_size) -z_streamp strm; -const char *version; -int stream_size; -{ +int ZEXPORT inflateInit_(z_streamp strm, const char *version, + int stream_size) { return inflateInit2_(strm, DEF_WBITS, version, stream_size); } -int ZEXPORT inflatePrime(strm, bits, value) -z_streamp strm; -int bits; -int value; -{ +int ZEXPORT inflatePrime(z_streamp strm, int bits, int value) { struct inflate_state FAR *state; if (inflateStateCheck(strm)) return Z_STREAM_ERROR; + if (bits == 0) + return Z_OK; state = (struct inflate_state FAR *)strm->state; if (bits < 0) { state->hold = 0; @@ -260,125 +230,11 @@ int value; } if (bits > 16 || state->bits + (uInt)bits > 32) return Z_STREAM_ERROR; value &= (1L << bits) - 1; - state->hold += (unsigned)value << state->bits; + state->hold += (unsigned long)value << state->bits; state->bits += (uInt)bits; return Z_OK; } -/* - Return state with length and distance decoding tables and index sizes set to - fixed code decoding. Normally this returns fixed tables from inffixed.h. - If BUILDFIXED is defined, then instead this routine builds the tables the - first time it's called, and returns those tables the first time and - thereafter. This reduces the size of the code by about 2K bytes, in - exchange for a little execution time. However, BUILDFIXED should not be - used for threaded applications, since the rewriting of the tables and virgin - may not be thread-safe. - */ -local void fixedtables(state) -struct inflate_state FAR *state; -{ -#ifdef BUILDFIXED - static int virgin = 1; - static code *lenfix, *distfix; - static code fixed[544]; - - /* build fixed huffman tables if first call (may not be thread safe) */ - if (virgin) { - unsigned sym, bits; - static code *next; - - /* literal/length table */ - sym = 0; - while (sym < 144) state->lens[sym++] = 8; - while (sym < 256) state->lens[sym++] = 9; - while (sym < 280) state->lens[sym++] = 7; - while (sym < 288) state->lens[sym++] = 8; - next = fixed; - lenfix = next; - bits = 9; - inflate_table(LENS, state->lens, 288, &(next), &(bits), state->work); - - /* distance table */ - sym = 0; - while (sym < 32) state->lens[sym++] = 5; - distfix = next; - bits = 5; - inflate_table(DISTS, state->lens, 32, &(next), &(bits), state->work); - - /* do this just once */ - virgin = 0; - } -#else /* !BUILDFIXED */ -# include "inffixed.h" -#endif /* BUILDFIXED */ - state->lencode = lenfix; - state->lenbits = 9; - state->distcode = distfix; - state->distbits = 5; -} - -#ifdef MAKEFIXED -#include - -/* - Write out the inffixed.h that is #include'd above. Defining MAKEFIXED also - defines BUILDFIXED, so the tables are built on the fly. makefixed() writes - those tables to stdout, which would be piped to inffixed.h. A small program - can simply call makefixed to do this: - - void makefixed(void); - - int main(void) - { - makefixed(); - return 0; - } - - Then that can be linked with zlib built with MAKEFIXED defined and run: - - a.out > inffixed.h - */ -void makefixed() -{ - unsigned low, size; - struct inflate_state state; - - fixedtables(&state); - puts(" /* inffixed.h -- table for decoding fixed codes"); - puts(" * Generated automatically by makefixed()."); - puts(" */"); - puts(""); - puts(" /* WARNING: this file should *not* be used by applications."); - puts(" It is part of the implementation of this library and is"); - puts(" subject to change. Applications should only use zlib.h."); - puts(" */"); - puts(""); - size = 1U << 9; - printf(" static const code lenfix[%u] = {", size); - low = 0; - for (;;) { - if ((low % 7) == 0) printf("\n "); - printf("{%u,%u,%d}", (low & 127) == 99 ? 64 : state.lencode[low].op, - state.lencode[low].bits, state.lencode[low].val); - if (++low == size) break; - putchar(','); - } - puts("\n };"); - size = 1U << 5; - printf("\n static const code distfix[%u] = {", size); - low = 0; - for (;;) { - if ((low % 6) == 0) printf("\n "); - printf("{%u,%u,%d}", state.distcode[low].op, state.distcode[low].bits, - state.distcode[low].val); - if (++low == size) break; - putchar(','); - } - puts("\n };"); -} -#endif /* MAKEFIXED */ - /* Update the window with the last wsize (normally 32K) bytes written before returning. If window does not exist yet, create it. This is only called @@ -393,11 +249,7 @@ void makefixed() output will fall in the output data, making match copies simpler and faster. The advantage may be dependent on the size of the processor's data caches. */ -local int updatewindow(strm, end, copy) -z_streamp strm; -const Bytef *end; -unsigned copy; -{ +local int updatewindow(z_streamp strm, const Bytef *end, unsigned copy) { struct inflate_state FAR *state; unsigned dist; @@ -447,10 +299,10 @@ unsigned copy; /* check function to use adler32() for zlib or crc32() for gzip */ #ifdef GUNZIP -# define UPDATE(check, buf, len) \ +# define UPDATE_CHECK(check, buf, len) \ (state->flags ? crc32(check, buf, len) : adler32(check, buf, len)) #else -# define UPDATE(check, buf, len) adler32(check, buf, len) +# define UPDATE_CHECK(check, buf, len) adler32(check, buf, len) #endif /* check macros for header crc */ @@ -619,10 +471,7 @@ unsigned copy; will return Z_BUF_ERROR if it has not reached the end of the stream. */ -int ZEXPORT inflate(strm, flush) -z_streamp strm; -int flush; -{ +int ZEXPORT inflate(z_streamp strm, int flush) { struct inflate_state FAR *state; z_const unsigned char FAR *next; /* next input */ unsigned char FAR *put; /* next output */ @@ -670,7 +519,6 @@ int flush; state->mode = FLAGS; break; } - state->flags = 0; /* expect zlib header */ if (state->head != Z_NULL) state->head->done = -1; if (!(state->wrap & 1) || /* check if zlib header allowed */ @@ -678,12 +526,12 @@ int flush; if ( #endif ((BITS(8) << 8) + (hold >> 8)) % 31) { - strm->msg = (char *)"incorrect header check"; + strm->msg = (z_const char *)"incorrect header check"; state->mode = BAD; break; } if (BITS(4) != Z_DEFLATED) { - strm->msg = (char *)"unknown compression method"; + strm->msg = (z_const char *)"unknown compression method"; state->mode = BAD; break; } @@ -692,11 +540,12 @@ int flush; if (state->wbits == 0) state->wbits = len; if (len > 15 || len > state->wbits) { - strm->msg = (char *)"invalid window size"; + strm->msg = (z_const char *)"invalid window size"; state->mode = BAD; break; } state->dmax = 1U << len; + state->flags = 0; /* indicate zlib header */ Tracev((stderr, "inflate: zlib header ok\n")); strm->adler = state->check = adler32(0L, Z_NULL, 0); state->mode = hold & 0x200 ? DICTID : TYPE; @@ -707,12 +556,12 @@ int flush; NEEDBITS(16); state->flags = (int)(hold); if ((state->flags & 0xff) != Z_DEFLATED) { - strm->msg = (char *)"unknown compression method"; + strm->msg = (z_const char *)"unknown compression method"; state->mode = BAD; break; } if (state->flags & 0xe000) { - strm->msg = (char *)"unknown header flags set"; + strm->msg = (z_const char *)"unknown header flags set"; state->mode = BAD; break; } @@ -722,6 +571,7 @@ int flush; CRC2(state->check, hold); INITBITS(); state->mode = TIME; + /* fallthrough */ case TIME: NEEDBITS(32); if (state->head != Z_NULL) @@ -730,6 +580,7 @@ int flush; CRC4(state->check, hold); INITBITS(); state->mode = OS; + /* fallthrough */ case OS: NEEDBITS(16); if (state->head != Z_NULL) { @@ -740,6 +591,7 @@ int flush; CRC2(state->check, hold); INITBITS(); state->mode = EXLEN; + /* fallthrough */ case EXLEN: if (state->flags & 0x0400) { NEEDBITS(16); @@ -753,14 +605,16 @@ int flush; else if (state->head != Z_NULL) state->head->extra = Z_NULL; state->mode = EXTRA; + /* fallthrough */ case EXTRA: if (state->flags & 0x0400) { copy = state->length; if (copy > have) copy = have; if (copy) { if (state->head != Z_NULL && - state->head->extra != Z_NULL) { - len = state->head->extra_len - state->length; + state->head->extra != Z_NULL && + (len = state->head->extra_len - state->length) < + state->head->extra_max) { zmemcpy(state->head->extra + len, next, len + copy > state->head->extra_max ? state->head->extra_max - len : copy); @@ -775,6 +629,7 @@ int flush; } state->length = 0; state->mode = NAME; + /* fallthrough */ case NAME: if (state->flags & 0x0800) { if (have == 0) goto inf_leave; @@ -796,6 +651,7 @@ int flush; state->head->name = Z_NULL; state->length = 0; state->mode = COMMENT; + /* fallthrough */ case COMMENT: if (state->flags & 0x1000) { if (have == 0) goto inf_leave; @@ -816,11 +672,12 @@ int flush; else if (state->head != Z_NULL) state->head->comment = Z_NULL; state->mode = HCRC; + /* fallthrough */ case HCRC: if (state->flags & 0x0200) { NEEDBITS(16); if ((state->wrap & 4) && hold != (state->check & 0xffff)) { - strm->msg = (char *)"header crc mismatch"; + strm->msg = (z_const char *)"header crc mismatch"; state->mode = BAD; break; } @@ -839,6 +696,7 @@ int flush; strm->adler = state->check = ZSWAP32(hold); INITBITS(); state->mode = DICT; + /* fallthrough */ case DICT: if (state->havedict == 0) { RESTORE(); @@ -846,8 +704,10 @@ int flush; } strm->adler = state->check = adler32(0L, Z_NULL, 0); state->mode = TYPE; + /* fallthrough */ case TYPE: if (flush == Z_BLOCK || flush == Z_TREES) goto inf_leave; + /* fallthrough */ case TYPEDO: if (state->last) { BYTEBITS(); @@ -864,7 +724,7 @@ int flush; state->mode = STORED; break; case 1: /* fixed block */ - fixedtables(state); + inflate_fixed(state); Tracev((stderr, "inflate: fixed codes block%s\n", state->last ? " (last)" : "")); state->mode = LEN_; /* decode codes */ @@ -878,8 +738,8 @@ int flush; state->last ? " (last)" : "")); state->mode = TABLE; break; - case 3: - strm->msg = (char *)"invalid block type"; + default: + strm->msg = (z_const char *)"invalid block type"; state->mode = BAD; } DROPBITS(2); @@ -888,7 +748,7 @@ int flush; BYTEBITS(); /* go to byte boundary */ NEEDBITS(32); if ((hold & 0xffff) != ((hold >> 16) ^ 0xffff)) { - strm->msg = (char *)"invalid stored block lengths"; + strm->msg = (z_const char *)"invalid stored block lengths"; state->mode = BAD; break; } @@ -898,8 +758,10 @@ int flush; INITBITS(); state->mode = COPY_; if (flush == Z_TREES) goto inf_leave; + /* fallthrough */ case COPY_: state->mode = COPY; + /* fallthrough */ case COPY: copy = state->length; if (copy) { @@ -927,7 +789,8 @@ int flush; DROPBITS(4); #ifndef PKZIP_BUG_WORKAROUND if (state->nlen > 286 || state->ndist > 30) { - strm->msg = (char *)"too many length or distance symbols"; + strm->msg = (z_const char *) + "too many length or distance symbols"; state->mode = BAD; break; } @@ -935,6 +798,7 @@ int flush; Tracev((stderr, "inflate: table sizes ok\n")); state->have = 0; state->mode = LENLENS; + /* fallthrough */ case LENLENS: while (state->have < state->ncode) { NEEDBITS(3); @@ -944,18 +808,19 @@ int flush; while (state->have < 19) state->lens[order[state->have++]] = 0; state->next = state->codes; - state->lencode = (const code FAR *)(state->next); + state->lencode = state->distcode = (const code FAR *)(state->next); state->lenbits = 7; ret = inflate_table(CODES, state->lens, 19, &(state->next), &(state->lenbits), state->work); if (ret) { - strm->msg = (char *)"invalid code lengths set"; + strm->msg = (z_const char *)"invalid code lengths set"; state->mode = BAD; break; } Tracev((stderr, "inflate: code lengths ok\n")); state->have = 0; state->mode = CODELENS; + /* fallthrough */ case CODELENS: while (state->have < state->nlen + state->ndist) { for (;;) { @@ -972,7 +837,8 @@ int flush; NEEDBITS(here.bits + 2); DROPBITS(here.bits); if (state->have == 0) { - strm->msg = (char *)"invalid bit length repeat"; + strm->msg = (z_const char *) + "invalid bit length repeat"; state->mode = BAD; break; } @@ -995,7 +861,8 @@ int flush; DROPBITS(7); } if (state->have + copy > state->nlen + state->ndist) { - strm->msg = (char *)"invalid bit length repeat"; + strm->msg = (z_const char *) + "invalid bit length repeat"; state->mode = BAD; break; } @@ -1009,7 +876,8 @@ int flush; /* check for end-of-block code (better have one) */ if (state->lens[256] == 0) { - strm->msg = (char *)"invalid code -- missing end-of-block"; + strm->msg = (z_const char *) + "invalid code -- missing end-of-block"; state->mode = BAD; break; } @@ -1023,7 +891,7 @@ int flush; ret = inflate_table(LENS, state->lens, state->nlen, &(state->next), &(state->lenbits), state->work); if (ret) { - strm->msg = (char *)"invalid literal/lengths set"; + strm->msg = (z_const char *)"invalid literal/lengths set"; state->mode = BAD; break; } @@ -1032,15 +900,17 @@ int flush; ret = inflate_table(DISTS, state->lens + state->nlen, state->ndist, &(state->next), &(state->distbits), state->work); if (ret) { - strm->msg = (char *)"invalid distances set"; + strm->msg = (z_const char *)"invalid distances set"; state->mode = BAD; break; } Tracev((stderr, "inflate: codes ok\n")); state->mode = LEN_; if (flush == Z_TREES) goto inf_leave; + /* fallthrough */ case LEN_: state->mode = LEN; + /* fallthrough */ case LEN: if (have >= 6 && left >= 258) { RESTORE(); @@ -1084,12 +954,13 @@ int flush; break; } if (here.op & 64) { - strm->msg = (char *)"invalid literal/length code"; + strm->msg = (z_const char *)"invalid literal/length code"; state->mode = BAD; break; } state->extra = (unsigned)(here.op) & 15; state->mode = LENEXT; + /* fallthrough */ case LENEXT: if (state->extra) { NEEDBITS(state->extra); @@ -1100,6 +971,7 @@ int flush; Tracevv((stderr, "inflate: length %u\n", state->length)); state->was = state->length; state->mode = DIST; + /* fallthrough */ case DIST: for (;;) { here = state->distcode[BITS(state->distbits)]; @@ -1120,13 +992,14 @@ int flush; DROPBITS(here.bits); state->back += here.bits; if (here.op & 64) { - strm->msg = (char *)"invalid distance code"; + strm->msg = (z_const char *)"invalid distance code"; state->mode = BAD; break; } state->offset = (unsigned)here.val; state->extra = (unsigned)(here.op) & 15; state->mode = DISTEXT; + /* fallthrough */ case DISTEXT: if (state->extra) { NEEDBITS(state->extra); @@ -1136,13 +1009,14 @@ int flush; } #ifdef INFLATE_STRICT if (state->offset > state->dmax) { - strm->msg = (char *)"invalid distance too far back"; + strm->msg = (z_const char *)"invalid distance too far back"; state->mode = BAD; break; } #endif Tracevv((stderr, "inflate: distance %u\n", state->offset)); state->mode = MATCH; + /* fallthrough */ case MATCH: if (left == 0) goto inf_leave; copy = out - left; @@ -1150,7 +1024,8 @@ int flush; copy = state->offset - copy; if (copy > state->whave) { if (state->sane) { - strm->msg = (char *)"invalid distance too far back"; + strm->msg = (z_const char *) + "invalid distance too far back"; state->mode = BAD; break; } @@ -1202,14 +1077,14 @@ int flush; state->total += out; if ((state->wrap & 4) && out) strm->adler = state->check = - UPDATE(state->check, put - out, out); + UPDATE_CHECK(state->check, put - out, out); out = left; if ((state->wrap & 4) && ( #ifdef GUNZIP state->flags ? hold : #endif ZSWAP32(hold)) != state->check) { - strm->msg = (char *)"incorrect data check"; + strm->msg = (z_const char *)"incorrect data check"; state->mode = BAD; break; } @@ -1218,11 +1093,12 @@ int flush; } #ifdef GUNZIP state->mode = LENGTH; + /* fallthrough */ case LENGTH: if (state->wrap && state->flags) { NEEDBITS(32); - if (hold != (state->total & 0xffffffffUL)) { - strm->msg = (char *)"incorrect length check"; + if ((state->wrap & 4) && hold != (state->total & 0xffffffff)) { + strm->msg = (z_const char *)"incorrect length check"; state->mode = BAD; break; } @@ -1231,6 +1107,7 @@ int flush; } #endif state->mode = DONE; + /* fallthrough */ case DONE: ret = Z_STREAM_END; goto inf_leave; @@ -1240,6 +1117,7 @@ int flush; case MEM: return Z_MEM_ERROR; case SYNC: + /* fallthrough */ default: return Z_STREAM_ERROR; } @@ -1265,7 +1143,7 @@ int flush; state->total += out; if ((state->wrap & 4) && out) strm->adler = state->check = - UPDATE(state->check, strm->next_out - out, out); + UPDATE_CHECK(state->check, strm->next_out - out, out); strm->data_type = (int)state->bits + (state->last ? 64 : 0) + (state->mode == TYPE ? 128 : 0) + (state->mode == LEN_ || state->mode == COPY_ ? 256 : 0); @@ -1274,9 +1152,7 @@ int flush; return ret; } -int ZEXPORT inflateEnd(strm) -z_streamp strm; -{ +int ZEXPORT inflateEnd(z_streamp strm) { struct inflate_state FAR *state; if (inflateStateCheck(strm)) return Z_STREAM_ERROR; @@ -1288,11 +1164,8 @@ z_streamp strm; return Z_OK; } -int ZEXPORT inflateGetDictionary(strm, dictionary, dictLength) -z_streamp strm; -Bytef *dictionary; -uInt *dictLength; -{ +int ZEXPORT inflateGetDictionary(z_streamp strm, Bytef *dictionary, + uInt *dictLength) { struct inflate_state FAR *state; /* check state */ @@ -1311,11 +1184,8 @@ uInt *dictLength; return Z_OK; } -int ZEXPORT inflateSetDictionary(strm, dictionary, dictLength) -z_streamp strm; -const Bytef *dictionary; -uInt dictLength; -{ +int ZEXPORT inflateSetDictionary(z_streamp strm, const Bytef *dictionary, + uInt dictLength) { struct inflate_state FAR *state; unsigned long dictid; int ret; @@ -1346,10 +1216,7 @@ uInt dictLength; return Z_OK; } -int ZEXPORT inflateGetHeader(strm, head) -z_streamp strm; -gz_headerp head; -{ +int ZEXPORT inflateGetHeader(z_streamp strm, gz_headerp head) { struct inflate_state FAR *state; /* check state */ @@ -1374,11 +1241,8 @@ gz_headerp head; called again with more data and the *have state. *have is initialized to zero for the first call. */ -local unsigned syncsearch(have, buf, len) -unsigned FAR *have; -const unsigned char FAR *buf; -unsigned len; -{ +local unsigned syncsearch(unsigned FAR *have, const unsigned char FAR *buf, + unsigned len) { unsigned got; unsigned next; @@ -1397,10 +1261,9 @@ unsigned len; return next; } -int ZEXPORT inflateSync(strm) -z_streamp strm; -{ +int ZEXPORT inflateSync(z_streamp strm) { unsigned len; /* number of bytes to look at or looked at */ + int flags; /* temporary to save header status */ unsigned long in, out; /* temporary to save total_in and total_out */ unsigned char buf[4]; /* to restore bit buffer to byte string */ struct inflate_state FAR *state; @@ -1413,7 +1276,7 @@ z_streamp strm; /* if first time, start search in bit buffer */ if (state->mode != SYNC) { state->mode = SYNC; - state->hold <<= state->bits & 7; + state->hold >>= state->bits & 7; state->bits -= state->bits & 7; len = 0; while (state->bits >= 8) { @@ -1433,9 +1296,15 @@ z_streamp strm; /* return no joy or set up to restart inflate() on a new block */ if (state->have != 4) return Z_DATA_ERROR; + if (state->flags == -1) + state->wrap = 0; /* if no header yet, treat as raw */ + else + state->wrap &= ~4; /* no point in computing a check value now */ + flags = state->flags; in = strm->total_in; out = strm->total_out; inflateReset(strm); strm->total_in = in; strm->total_out = out; + state->flags = flags; state->mode = TYPE; return Z_OK; } @@ -1448,9 +1317,7 @@ z_streamp strm; block. When decompressing, PPP checks that at the end of input packet, inflate is waiting for these length bytes. */ -int ZEXPORT inflateSyncPoint(strm) -z_streamp strm; -{ +int ZEXPORT inflateSyncPoint(z_streamp strm) { struct inflate_state FAR *state; if (inflateStateCheck(strm)) return Z_STREAM_ERROR; @@ -1458,14 +1325,10 @@ z_streamp strm; return state->mode == STORED && state->bits == 0; } -int ZEXPORT inflateCopy(dest, source) -z_streamp dest; -z_streamp source; -{ +int ZEXPORT inflateCopy(z_streamp dest, z_streamp source) { struct inflate_state FAR *state; struct inflate_state FAR *copy; unsigned char FAR *window; - unsigned wsize; /* check input */ if (inflateStateCheck(source) || dest == Z_NULL) @@ -1476,6 +1339,7 @@ z_streamp source; copy = (struct inflate_state FAR *) ZALLOC(source, 1, sizeof(struct inflate_state)); if (copy == Z_NULL) return Z_MEM_ERROR; + zmemzero(copy, sizeof(struct inflate_state)); window = Z_NULL; if (state->window != Z_NULL) { window = (unsigned char FAR *) @@ -1487,8 +1351,8 @@ z_streamp source; } /* copy state */ - zmemcpy((voidpf)dest, (voidpf)source, sizeof(z_stream)); - zmemcpy((voidpf)copy, (voidpf)state, sizeof(struct inflate_state)); + zmemcpy(dest, source, sizeof(z_stream)); + zmemcpy(copy, state, sizeof(struct inflate_state)); copy->strm = dest; if (state->lencode >= state->codes && state->lencode <= state->codes + ENOUGH - 1) { @@ -1496,19 +1360,14 @@ z_streamp source; copy->distcode = copy->codes + (state->distcode - state->codes); } copy->next = copy->codes + (state->next - state->codes); - if (window != Z_NULL) { - wsize = 1U << state->wbits; - zmemcpy(window, state->window, wsize); - } + if (window != Z_NULL) + zmemcpy(window, state->window, state->whave); copy->window = window; dest->state = (struct internal_state FAR *)copy; return Z_OK; } -int ZEXPORT inflateUndermine(strm, subvert) -z_streamp strm; -int subvert; -{ +int ZEXPORT inflateUndermine(z_streamp strm, int subvert) { struct inflate_state FAR *state; if (inflateStateCheck(strm)) return Z_STREAM_ERROR; @@ -1523,24 +1382,19 @@ int subvert; #endif } -int ZEXPORT inflateValidate(strm, check) -z_streamp strm; -int check; -{ +int ZEXPORT inflateValidate(z_streamp strm, int check) { struct inflate_state FAR *state; if (inflateStateCheck(strm)) return Z_STREAM_ERROR; state = (struct inflate_state FAR *)strm->state; - if (check) + if (check && state->wrap) state->wrap |= 4; else state->wrap &= ~4; return Z_OK; } -long ZEXPORT inflateMark(strm) -z_streamp strm; -{ +long ZEXPORT inflateMark(z_streamp strm) { struct inflate_state FAR *state; if (inflateStateCheck(strm)) @@ -1551,9 +1405,7 @@ z_streamp strm; (state->mode == MATCH ? state->was - state->length : 0)); } -unsigned long ZEXPORT inflateCodesUsed(strm) -z_streamp strm; -{ +unsigned long ZEXPORT inflateCodesUsed(z_streamp strm) { struct inflate_state FAR *state; if (inflateStateCheck(strm)) return (unsigned long)-1; state = (struct inflate_state FAR *)strm->state; diff --git a/src/common/dep/zlib/inflate.h b/src/common/dep/zlib/inflate.h index a46cce6b..f758e0dc 100644 --- a/src/common/dep/zlib/inflate.h +++ b/src/common/dep/zlib/inflate.h @@ -1,5 +1,5 @@ /* inflate.h -- internal inflate state definition - * Copyright (C) 1995-2016 Mark Adler + * Copyright (C) 1995-2019 Mark Adler * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -86,7 +86,8 @@ struct inflate_state { int wrap; /* bit 0 true for zlib, bit 1 true for gzip, bit 2 true to validate check value */ int havedict; /* true if dictionary provided */ - int flags; /* gzip header method and flags (0 if zlib) */ + int flags; /* gzip header method and flags, 0 if zlib, or + -1 if raw or no header yet */ unsigned dmax; /* zlib header max distance (INFLATE_STRICT) */ unsigned long check; /* protected copy of check value */ unsigned long total; /* protected copy of output count */ @@ -99,7 +100,7 @@ struct inflate_state { unsigned char FAR *window; /* allocated sliding window, if needed */ /* bit accumulator */ unsigned long hold; /* input bit accumulator */ - unsigned bits; /* number of bits in "in" */ + unsigned bits; /* number of bits in hold */ /* for string and stored block copying */ unsigned length; /* literal or length of data to copy */ unsigned offset; /* distance back to copy string from */ diff --git a/src/common/dep/zlib/inftrees.c b/src/common/dep/zlib/inftrees.c index 2ea08fc1..dcbc64e0 100644 --- a/src/common/dep/zlib/inftrees.c +++ b/src/common/dep/zlib/inftrees.c @@ -1,15 +1,29 @@ /* inftrees.c -- generate Huffman trees for efficient decoding - * Copyright (C) 1995-2017 Mark Adler + * Copyright (C) 1995-2026 Mark Adler * For conditions of distribution and use, see copyright notice in zlib.h */ +#ifdef MAKEFIXED +# ifndef BUILDFIXED +# define BUILDFIXED +# endif +#endif +#ifdef BUILDFIXED +# define Z_ONCE +#endif + #include "zutil.h" #include "inftrees.h" +#include "inflate.h" + +#ifndef NULL +# define NULL 0 +#endif #define MAXBITS 15 const char inflate_copyright[] = - " inflate 1.2.11 Copyright 1995-2017 Mark Adler "; + " inflate 1.3.2 Copyright 1995-2026 Mark Adler "; /* If you use the zlib library in a product, an acknowledgment is welcome in the documentation of your product. If for some reason you cannot @@ -29,14 +43,9 @@ const char inflate_copyright[] = table index bits. It will differ if the request is greater than the longest code or if it is less than the shortest code. */ -int ZLIB_INTERNAL inflate_table(type, lens, codes, table, bits, work) -codetype type; -unsigned short FAR *lens; -unsigned codes; -code FAR * FAR *table; -unsigned FAR *bits; -unsigned short FAR *work; -{ +int ZLIB_INTERNAL inflate_table(codetype type, unsigned short FAR *lens, + unsigned codes, code FAR * FAR *table, + unsigned FAR *bits, unsigned short FAR *work) { unsigned len; /* a code's length in bits */ unsigned sym; /* index of code symbols */ unsigned min, max; /* minimum and maximum code lengths */ @@ -52,9 +61,9 @@ unsigned short FAR *work; unsigned mask; /* mask for low root bits */ code here; /* table entry for duplication */ code FAR *next; /* next available space in table */ - const unsigned short FAR *base; /* base value table to use */ - const unsigned short FAR *extra; /* extra bits table to use */ - unsigned match; /* use base and extra for symbol >= match */ + const unsigned short FAR *base = NULL; /* base value table to use */ + const unsigned short FAR *extra = NULL; /* extra bits table to use */ + unsigned match = 0; /* use base and extra for symbol >= match */ unsigned short count[MAXBITS+1]; /* number of codes of each length */ unsigned short offs[MAXBITS+1]; /* offsets in table for each length */ static const unsigned short lbase[31] = { /* Length codes 257..285 base */ @@ -62,7 +71,7 @@ unsigned short FAR *work; 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0}; static const unsigned short lext[31] = { /* Length codes 257..285 extra */ 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, - 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 77, 202}; + 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 199, 75}; static const unsigned short dbase[32] = { /* Distance codes 0..29 base */ 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, @@ -180,7 +189,6 @@ unsigned short FAR *work; /* set up for code type */ switch (type) { case CODES: - base = extra = work; /* dummy value--not used */ match = 20; break; case LENS: @@ -188,10 +196,9 @@ unsigned short FAR *work; extra = lext; match = 257; break; - default: /* DISTS */ + case DISTS: base = dbase; extra = dext; - match = 0; } /* initialize state for loop */ @@ -302,3 +309,116 @@ unsigned short FAR *work; *bits = root; return 0; } + +#ifdef BUILDFIXED +/* + If this is compiled with BUILDFIXED defined, and if inflate will be used in + multiple threads, and if atomics are not available, then inflate() must be + called with a fixed block (e.g. 0x03 0x00) to initialize the tables and must + return before any other threads are allowed to call inflate. + */ + +static code *lenfix, *distfix; +static code fixed[544]; + +/* State for z_once(). */ +local z_once_t built = Z_ONCE_INIT; + +local void buildtables(void) { + unsigned sym, bits; + static code *next; + unsigned short lens[288], work[288]; + + /* literal/length table */ + sym = 0; + while (sym < 144) lens[sym++] = 8; + while (sym < 256) lens[sym++] = 9; + while (sym < 280) lens[sym++] = 7; + while (sym < 288) lens[sym++] = 8; + next = fixed; + lenfix = next; + bits = 9; + inflate_table(LENS, lens, 288, &(next), &(bits), work); + + /* distance table */ + sym = 0; + while (sym < 32) lens[sym++] = 5; + distfix = next; + bits = 5; + inflate_table(DISTS, lens, 32, &(next), &(bits), work); +} +#else /* !BUILDFIXED */ +# include "inffixed.h" +#endif /* BUILDFIXED */ + +/* + Return state with length and distance decoding tables and index sizes set to + fixed code decoding. Normally this returns fixed tables from inffixed.h. + If BUILDFIXED is defined, then instead this routine builds the tables the + first time it's called, and returns those tables the first time and + thereafter. This reduces the size of the code by about 2K bytes, in + exchange for a little execution time. However, BUILDFIXED should not be + used for threaded applications if atomics are not available, as it will + not be thread-safe. + */ +void inflate_fixed(struct inflate_state FAR *state) { +#ifdef BUILDFIXED + z_once(&built, buildtables); +#endif /* BUILDFIXED */ + state->lencode = lenfix; + state->lenbits = 9; + state->distcode = distfix; + state->distbits = 5; +} + +#ifdef MAKEFIXED +#include + +/* + Write out the inffixed.h that will be #include'd above. Defining MAKEFIXED + also defines BUILDFIXED, so the tables are built on the fly. main() writes + those tables to stdout, which would directed to inffixed.h. Compile this + along with zutil.c: + + cc -DMAKEFIXED -o fix inftrees.c zutil.c + ./fix > inffixed.h + */ +int main(void) { + unsigned low, size; + struct inflate_state state; + + inflate_fixed(&state); + puts("/* inffixed.h -- table for decoding fixed codes"); + puts(" * Generated automatically by makefixed()."); + puts(" */"); + puts(""); + puts("/* WARNING: this file should *not* be used by applications."); + puts(" It is part of the implementation of this library and is"); + puts(" subject to change. Applications should only use zlib.h."); + puts(" */"); + puts(""); + size = 1U << 9; + printf("static const code lenfix[%u] = {", size); + low = 0; + for (;;) { + if ((low % 7) == 0) printf("\n "); + printf("{%u,%u,%d}", (low & 127) == 99 ? 64 : state.lencode[low].op, + state.lencode[low].bits, state.lencode[low].val); + if (++low == size) break; + putchar(','); + } + puts("\n};"); + size = 1U << 5; + printf("\nstatic const code distfix[%u] = {", size); + low = 0; + for (;;) { + if ((low % 6) == 0) printf("\n "); + printf("{%u,%u,%d}", state.distcode[low].op, state.distcode[low].bits, + state.distcode[low].val); + if (++low == size) break; + putchar(','); + } + puts("\n};"); + return 0; +} +#endif /* MAKEFIXED */ diff --git a/src/common/dep/zlib/inftrees.h b/src/common/dep/zlib/inftrees.h index baa53a0b..84d05369 100644 --- a/src/common/dep/zlib/inftrees.h +++ b/src/common/dep/zlib/inftrees.h @@ -1,5 +1,5 @@ /* inftrees.h -- header to use inftrees.c - * Copyright (C) 1995-2005, 2010 Mark Adler + * Copyright (C) 1995-2026 Mark Adler * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -38,11 +38,11 @@ typedef struct { /* Maximum size of the dynamic table. The maximum number of code structures is 1444, which is the sum of 852 for literal/length codes and 592 for distance codes. These values were found by exhaustive searches using the program - examples/enough.c found in the zlib distribtution. The arguments to that + examples/enough.c found in the zlib distribution. The arguments to that program are the number of symbols, the initial root table size, and the maximum bit length of a code. "enough 286 9 15" for literal/length codes - returns returns 852, and "enough 30 6 15" for distance codes returns 592. - The initial root table size (9 or 6) is found in the fifth argument of the + returns 852, and "enough 30 6 15" for distance codes returns 592. The + initial root table size (9 or 6) is found in the fifth argument of the inflate_table() calls in inflate.c and infback.c. If the root table size is changed, then these maximum sizes would be need to be recalculated and updated. */ @@ -57,6 +57,8 @@ typedef enum { DISTS } codetype; -int ZLIB_INTERNAL inflate_table OF((codetype type, unsigned short FAR *lens, - unsigned codes, code FAR * FAR *table, - unsigned FAR *bits, unsigned short FAR *work)); +int ZLIB_INTERNAL inflate_table(codetype type, unsigned short FAR *lens, + unsigned codes, code FAR * FAR *table, + unsigned FAR *bits, unsigned short FAR *work); +struct inflate_state; +void ZLIB_INTERNAL inflate_fixed(struct inflate_state FAR *state); diff --git a/src/common/dep/zlib/trees.c b/src/common/dep/zlib/trees.c index 50cf4b45..8e4da01e 100644 --- a/src/common/dep/zlib/trees.c +++ b/src/common/dep/zlib/trees.c @@ -1,5 +1,5 @@ /* trees.c -- output deflated data using Huffman coding - * Copyright (C) 1995-2017 Jean-loup Gailly + * Copyright (C) 1995-2026 Jean-loup Gailly * detect_data_type() function provided freely by Cosmin Truta, 2006 * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -112,7 +112,7 @@ local int base_dist[D_CODES]; #else # include "trees.h" -#endif /* GEN_TREES_H */ +#endif /* defined(GEN_TREES_H) || !defined(STDC) */ struct static_tree_desc_s { const ct_data *static_tree; /* static tree or NULL */ @@ -122,39 +122,117 @@ struct static_tree_desc_s { int max_length; /* max bit length for the codes */ }; -local const static_tree_desc static_l_desc = +#ifdef NO_INIT_GLOBAL_POINTERS +# define TCONST +#else +# define TCONST const +#endif + +local TCONST static_tree_desc static_l_desc = {static_ltree, extra_lbits, LITERALS+1, L_CODES, MAX_BITS}; -local const static_tree_desc static_d_desc = +local TCONST static_tree_desc static_d_desc = {static_dtree, extra_dbits, 0, D_CODES, MAX_BITS}; -local const static_tree_desc static_bl_desc = +local TCONST static_tree_desc static_bl_desc = {(const ct_data *)0, extra_blbits, 0, BL_CODES, MAX_BL_BITS}; /* =========================================================================== - * Local (static) routines in this file. + * Output a short LSB first on the stream. + * IN assertion: there is enough room in pendingBuf. + */ +#define put_short(s, w) { \ + put_byte(s, (uch)((w) & 0xff)); \ + put_byte(s, (uch)((ush)(w) >> 8)); \ +} + +/* =========================================================================== + * Reverse the first len bits of a code, using straightforward code (a faster + * method would use a table) + * IN assertion: 1 <= len <= 15 + */ +local unsigned bi_reverse(unsigned code, int len) { + unsigned res = 0; + do { + res |= code & 1; + code >>= 1, res <<= 1; + } while (--len > 0); + return res >> 1; +} + +/* =========================================================================== + * Flush the bit buffer, keeping at most 7 bits in it. + */ +local void bi_flush(deflate_state *s) { + if (s->bi_valid == 16) { + put_short(s, s->bi_buf); + s->bi_buf = 0; + s->bi_valid = 0; + } else if (s->bi_valid >= 8) { + put_byte(s, (Byte)s->bi_buf); + s->bi_buf >>= 8; + s->bi_valid -= 8; + } +} + +/* =========================================================================== + * Flush the bit buffer and align the output on a byte boundary + */ +local void bi_windup(deflate_state *s) { + if (s->bi_valid > 8) { + put_short(s, s->bi_buf); + } else if (s->bi_valid > 0) { + put_byte(s, (Byte)s->bi_buf); + } + s->bi_used = ((s->bi_valid - 1) & 7) + 1; + s->bi_buf = 0; + s->bi_valid = 0; +#ifdef ZLIB_DEBUG + s->bits_sent = (s->bits_sent + 7) & ~(ulg)7; +#endif +} + +/* =========================================================================== + * Generate the codes for a given tree and bit counts (which need not be + * optimal). + * IN assertion: the array bl_count contains the bit length statistics for + * the given tree and the field len is set for all tree elements. + * OUT assertion: the field code is set for all tree elements of non + * zero code length. */ +local void gen_codes(ct_data *tree, int max_code, ushf *bl_count) { + ush next_code[MAX_BITS+1]; /* next code value for each bit length */ + unsigned code = 0; /* running code value */ + int bits; /* bit index */ + int n; /* code index */ -local void tr_static_init OF((void)); -local void init_block OF((deflate_state *s)); -local void pqdownheap OF((deflate_state *s, ct_data *tree, int k)); -local void gen_bitlen OF((deflate_state *s, tree_desc *desc)); -local void gen_codes OF((ct_data *tree, int max_code, ushf *bl_count)); -local void build_tree OF((deflate_state *s, tree_desc *desc)); -local void scan_tree OF((deflate_state *s, ct_data *tree, int max_code)); -local void send_tree OF((deflate_state *s, ct_data *tree, int max_code)); -local int build_bl_tree OF((deflate_state *s)); -local void send_all_trees OF((deflate_state *s, int lcodes, int dcodes, - int blcodes)); -local void compress_block OF((deflate_state *s, const ct_data *ltree, - const ct_data *dtree)); -local int detect_data_type OF((deflate_state *s)); -local unsigned bi_reverse OF((unsigned value, int length)); -local void bi_windup OF((deflate_state *s)); -local void bi_flush OF((deflate_state *s)); + /* The distribution counts are first used to generate the code values + * without bit reversal. + */ + for (bits = 1; bits <= MAX_BITS; bits++) { + code = (code + bl_count[bits - 1]) << 1; + next_code[bits] = (ush)code; + } + /* Check that the bit counts in bl_count are consistent. The last code + * must be all ones. + */ + Assert (code + bl_count[MAX_BITS] - 1 == (1 << MAX_BITS) - 1, + "inconsistent bit counts"); + Tracev((stderr,"\ngen_codes: max_code %d ", max_code)); + + for (n = 0; n <= max_code; n++) { + int len = tree[n].Len; + if (len == 0) continue; + /* Now reverse the bits */ + tree[n].Code = (ush)bi_reverse(next_code[len]++, len); + + Tracecv(tree != static_ltree, (stderr,"\nn %3d %c l %2d c %4x (%x) ", + n, (isgraph(n) ? n : ' '), len, tree[n].Code, next_code[len] - 1)); + } +} #ifdef GEN_TREES_H -local void gen_trees_header OF((void)); +local void gen_trees_header(void); #endif #ifndef ZLIB_DEBUG @@ -167,33 +245,18 @@ local void gen_trees_header OF((void)); send_bits(s, tree[c].Code, tree[c].Len); } #endif -/* =========================================================================== - * Output a short LSB first on the stream. - * IN assertion: there is enough room in pendingBuf. - */ -#define put_short(s, w) { \ - put_byte(s, (uch)((w) & 0xff)); \ - put_byte(s, (uch)((ush)(w) >> 8)); \ -} - /* =========================================================================== * Send a value on a given number of bits. * IN assertion: length <= 16 and value fits in length bits. */ #ifdef ZLIB_DEBUG -local void send_bits OF((deflate_state *s, int value, int length)); - -local void send_bits(s, value, length) - deflate_state *s; - int value; /* value to send */ - int length; /* number of bits */ -{ +local void send_bits(deflate_state *s, int value, int length) { Tracevv((stderr," l %2d v %4x ", length, value)); Assert(length > 0 && length <= 15, "invalid length"); s->bits_sent += (ulg)length; /* If not enough room in bi_buf, use (valid) bits from bi_buf and - * (16 - bi_valid) bits from value, leaving (width - (16-bi_valid)) + * (16 - bi_valid) bits from value, leaving (width - (16 - bi_valid)) * unused bits in value. */ if (s->bi_valid > (int)Buf_size - length) { @@ -229,8 +292,7 @@ local void send_bits(s, value, length) /* =========================================================================== * Initialize the various 'constant' tables. */ -local void tr_static_init() -{ +local void tr_static_init(void) { #if defined(GEN_TREES_H) || !defined(STDC) static int static_init_done = 0; int n; /* iterates over tree elements */ @@ -256,7 +318,7 @@ local void tr_static_init() length = 0; for (code = 0; code < LENGTH_CODES-1; code++) { base_length[code] = length; - for (n = 0; n < (1< dist code (0..29) */ dist = 0; for (code = 0 ; code < 16; code++) { base_dist[code] = dist; - for (n = 0; n < (1<>= 7; /* from now on, all distances are divided by 128 */ for ( ; code < D_CODES; code++) { base_dist[code] = dist << 7; - for (n = 0; n < (1<<(extra_dbits[code]-7)); n++) { + for (n = 0; n < (1 << (extra_dbits[code] - 7)); n++) { _dist_code[256 + dist++] = (uch)code; } } - Assert (dist == 256, "tr_static_init: 256+dist != 512"); + Assert (dist == 256, "tr_static_init: 256 + dist != 512"); /* Construct the codes of the static literal tree */ for (bits = 0; bits <= MAX_BITS; bits++) bl_count[bits] = 0; @@ -312,7 +374,7 @@ local void tr_static_init() } /* =========================================================================== - * Genererate the file trees.h describing the static trees. + * Generate the file trees.h describing the static trees. */ #ifdef GEN_TREES_H # ifndef ZLIB_DEBUG @@ -321,10 +383,9 @@ local void tr_static_init() # define SEPARATOR(i, last, width) \ ((i) == (last)? "\n};\n\n" : \ - ((i) % (width) == (width)-1 ? ",\n" : ", ")) + ((i) % (width) == (width) - 1 ? ",\n" : ", ")) -void gen_trees_header() -{ +void gen_trees_header(void) { FILE *header = fopen("trees.h", "w"); int i; @@ -373,12 +434,26 @@ void gen_trees_header() } #endif /* GEN_TREES_H */ +/* =========================================================================== + * Initialize a new block. + */ +local void init_block(deflate_state *s) { + int n; /* iterates over tree elements */ + + /* Initialize the trees. */ + for (n = 0; n < L_CODES; n++) s->dyn_ltree[n].Freq = 0; + for (n = 0; n < D_CODES; n++) s->dyn_dtree[n].Freq = 0; + for (n = 0; n < BL_CODES; n++) s->bl_tree[n].Freq = 0; + + s->dyn_ltree[END_BLOCK].Freq = 1; + s->opt_len = s->static_len = 0L; + s->sym_next = s->matches = 0; +} + /* =========================================================================== * Initialize the tree data structures for a new zlib stream. */ -void ZLIB_INTERNAL _tr_init(s) - deflate_state *s; -{ +void ZLIB_INTERNAL _tr_init(deflate_state *s) { tr_static_init(); s->l_desc.dyn_tree = s->dyn_ltree; @@ -392,6 +467,7 @@ void ZLIB_INTERNAL _tr_init(s) s->bi_buf = 0; s->bi_valid = 0; + s->bi_used = 0; #ifdef ZLIB_DEBUG s->compressed_len = 0L; s->bits_sent = 0L; @@ -401,24 +477,6 @@ void ZLIB_INTERNAL _tr_init(s) init_block(s); } -/* =========================================================================== - * Initialize a new block. - */ -local void init_block(s) - deflate_state *s; -{ - int n; /* iterates over tree elements */ - - /* Initialize the trees. */ - for (n = 0; n < L_CODES; n++) s->dyn_ltree[n].Freq = 0; - for (n = 0; n < D_CODES; n++) s->dyn_dtree[n].Freq = 0; - for (n = 0; n < BL_CODES; n++) s->bl_tree[n].Freq = 0; - - s->dyn_ltree[END_BLOCK].Freq = 1; - s->opt_len = s->static_len = 0L; - s->last_lit = s->matches = 0; -} - #define SMALLEST 1 /* Index within the heap array of least frequent node in the Huffman tree */ @@ -448,17 +506,13 @@ local void init_block(s) * when the heap property is re-established (each father smaller than its * two sons). */ -local void pqdownheap(s, tree, k) - deflate_state *s; - ct_data *tree; /* the tree to restore */ - int k; /* node to move down */ -{ +local void pqdownheap(deflate_state *s, ct_data *tree, int k) { int v = s->heap[k]; int j = k << 1; /* left son of k */ while (j <= s->heap_len) { /* Set j to the smallest of the two sons: */ if (j < s->heap_len && - smaller(tree, s->heap[j+1], s->heap[j], s->depth)) { + smaller(tree, s->heap[j + 1], s->heap[j], s->depth)) { j++; } /* Exit if v is smaller than both sons */ @@ -483,10 +537,7 @@ local void pqdownheap(s, tree, k) * The length opt_len is updated; static_len is also updated if stree is * not null. */ -local void gen_bitlen(s, desc) - deflate_state *s; - tree_desc *desc; /* the tree descriptor */ -{ +local void gen_bitlen(deflate_state *s, tree_desc *desc) { ct_data *tree = desc->dyn_tree; int max_code = desc->max_code; const ct_data *stree = desc->stat_desc->static_tree; @@ -507,7 +558,7 @@ local void gen_bitlen(s, desc) */ tree[s->heap[s->heap_max]].Len = 0; /* root of the heap */ - for (h = s->heap_max+1; h < HEAP_SIZE; h++) { + for (h = s->heap_max + 1; h < HEAP_SIZE; h++) { n = s->heap[h]; bits = tree[tree[n].Dad].Len + 1; if (bits > max_length) bits = max_length, overflow++; @@ -518,7 +569,7 @@ local void gen_bitlen(s, desc) s->bl_count[bits]++; xbits = 0; - if (n >= base) xbits = extra[n-base]; + if (n >= base) xbits = extra[n - base]; f = tree[n].Freq; s->opt_len += (ulg)f * (unsigned)(bits + xbits); if (stree) s->static_len += (ulg)f * (unsigned)(stree[n].Len + xbits); @@ -530,10 +581,10 @@ local void gen_bitlen(s, desc) /* Find the first bit length which could increase: */ do { - bits = max_length-1; + bits = max_length - 1; while (s->bl_count[bits] == 0) bits--; - s->bl_count[bits]--; /* move one leaf down the tree */ - s->bl_count[bits+1] += 2; /* move one overflow item as its brother */ + s->bl_count[bits]--; /* move one leaf down the tree */ + s->bl_count[bits + 1] += 2; /* move one overflow item as its brother */ s->bl_count[max_length]--; /* The brother of the overflow item also moves one step up, * but this does not affect bl_count[max_length] @@ -561,48 +612,9 @@ local void gen_bitlen(s, desc) } } -/* =========================================================================== - * Generate the codes for a given tree and bit counts (which need not be - * optimal). - * IN assertion: the array bl_count contains the bit length statistics for - * the given tree and the field len is set for all tree elements. - * OUT assertion: the field code is set for all tree elements of non - * zero code length. - */ -local void gen_codes (tree, max_code, bl_count) - ct_data *tree; /* the tree to decorate */ - int max_code; /* largest code with non zero frequency */ - ushf *bl_count; /* number of codes at each bit length */ -{ - ush next_code[MAX_BITS+1]; /* next code value for each bit length */ - unsigned code = 0; /* running code value */ - int bits; /* bit index */ - int n; /* code index */ - - /* The distribution counts are first used to generate the code values - * without bit reversal. - */ - for (bits = 1; bits <= MAX_BITS; bits++) { - code = (code + bl_count[bits-1]) << 1; - next_code[bits] = (ush)code; - } - /* Check that the bit counts in bl_count are consistent. The last code - * must be all ones. - */ - Assert (code + bl_count[MAX_BITS]-1 == (1< +#endif /* =========================================================================== * Construct one Huffman tree and assigns the code bit strings and lengths. @@ -612,10 +624,7 @@ local void gen_codes (tree, max_code, bl_count) * and corresponding code. The length opt_len is updated; static_len is * also updated if stree is not null. The field max_code is set. */ -local void build_tree(s, desc) - deflate_state *s; - tree_desc *desc; /* the tree descriptor */ -{ +local void build_tree(deflate_state *s, tree_desc *desc) { ct_data *tree = desc->dyn_tree; const ct_data *stree = desc->stat_desc->static_tree; int elems = desc->stat_desc->elems; @@ -624,7 +633,7 @@ local void build_tree(s, desc) int node; /* new node being created */ /* Construct the initial heap, with least frequent element in - * heap[SMALLEST]. The sons of heap[n] are heap[2*n] and heap[2*n+1]. + * heap[SMALLEST]. The sons of heap[n] are heap[2*n] and heap[2*n + 1]. * heap[0] is not used. */ s->heap_len = 0, s->heap_max = HEAP_SIZE; @@ -652,7 +661,7 @@ local void build_tree(s, desc) } desc->max_code = max_code; - /* The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree, + /* The elements heap[heap_len/2 + 1 .. heap_len] are leaves of the tree, * establish sub-heaps of increasing lengths: */ for (n = s->heap_len/2; n >= 1; n--) pqdownheap(s, tree, n); @@ -700,11 +709,7 @@ local void build_tree(s, desc) * Scan a literal or distance tree to determine the frequencies of the codes * in the bit length tree. */ -local void scan_tree (s, tree, max_code) - deflate_state *s; - ct_data *tree; /* the tree to be scanned */ - int max_code; /* and its largest code of non zero frequency */ -{ +local void scan_tree(deflate_state *s, ct_data *tree, int max_code) { int n; /* iterates over all tree elements */ int prevlen = -1; /* last emitted length */ int curlen; /* length of current code */ @@ -714,14 +719,14 @@ local void scan_tree (s, tree, max_code) int min_count = 4; /* min repeat count */ if (nextlen == 0) max_count = 138, min_count = 3; - tree[max_code+1].Len = (ush)0xffff; /* guard */ + tree[max_code + 1].Len = (ush)0xffff; /* guard */ for (n = 0; n <= max_code; n++) { - curlen = nextlen; nextlen = tree[n+1].Len; + curlen = nextlen; nextlen = tree[n + 1].Len; if (++count < max_count && curlen == nextlen) { continue; } else if (count < min_count) { - s->bl_tree[curlen].Freq += count; + s->bl_tree[curlen].Freq += (ush)count; } else if (curlen != 0) { if (curlen != prevlen) s->bl_tree[curlen].Freq++; s->bl_tree[REP_3_6].Freq++; @@ -745,11 +750,7 @@ local void scan_tree (s, tree, max_code) * Send a literal or distance tree in compressed form, using the codes in * bl_tree. */ -local void send_tree (s, tree, max_code) - deflate_state *s; - ct_data *tree; /* the tree to be scanned */ - int max_code; /* and its largest code of non zero frequency */ -{ +local void send_tree(deflate_state *s, ct_data *tree, int max_code) { int n; /* iterates over all tree elements */ int prevlen = -1; /* last emitted length */ int curlen; /* length of current code */ @@ -758,11 +759,11 @@ local void send_tree (s, tree, max_code) int max_count = 7; /* max repeat count */ int min_count = 4; /* min repeat count */ - /* tree[max_code+1].Len = -1; */ /* guard already set */ + /* tree[max_code + 1].Len = -1; */ /* guard already set */ if (nextlen == 0) max_count = 138, min_count = 3; for (n = 0; n <= max_code; n++) { - curlen = nextlen; nextlen = tree[n+1].Len; + curlen = nextlen; nextlen = tree[n + 1].Len; if (++count < max_count && curlen == nextlen) { continue; } else if (count < min_count) { @@ -773,13 +774,13 @@ local void send_tree (s, tree, max_code) send_code(s, curlen, s->bl_tree); count--; } Assert(count >= 3 && count <= 6, " 3_6?"); - send_code(s, REP_3_6, s->bl_tree); send_bits(s, count-3, 2); + send_code(s, REP_3_6, s->bl_tree); send_bits(s, count - 3, 2); } else if (count <= 10) { - send_code(s, REPZ_3_10, s->bl_tree); send_bits(s, count-3, 3); + send_code(s, REPZ_3_10, s->bl_tree); send_bits(s, count - 3, 3); } else { - send_code(s, REPZ_11_138, s->bl_tree); send_bits(s, count-11, 7); + send_code(s, REPZ_11_138, s->bl_tree); send_bits(s, count - 11, 7); } count = 0; prevlen = curlen; if (nextlen == 0) { @@ -796,9 +797,7 @@ local void send_tree (s, tree, max_code) * Construct the Huffman tree for the bit lengths and return the index in * bl_order of the last bit length code to send. */ -local int build_bl_tree(s) - deflate_state *s; -{ +local int build_bl_tree(deflate_state *s) { int max_blindex; /* index of last bit length code of non zero freq */ /* Determine the bit length frequencies for literal and distance trees */ @@ -807,8 +806,8 @@ local int build_bl_tree(s) /* Build the bit length tree: */ build_tree(s, (tree_desc *)(&(s->bl_desc))); - /* opt_len now includes the length of the tree representations, except - * the lengths of the bit lengths codes and the 5+5+4 bits for the counts. + /* opt_len now includes the length of the tree representations, except the + * lengths of the bit lengths codes and the 5 + 5 + 4 bits for the counts. */ /* Determine the number of bit length codes to send. The pkzip format @@ -819,8 +818,8 @@ local int build_bl_tree(s) if (s->bl_tree[bl_order[max_blindex]].Len != 0) break; } /* Update opt_len to include the bit length tree and counts */ - s->opt_len += 3*((ulg)max_blindex+1) + 5+5+4; - Tracev((stderr, "\ndyn trees: dyn %ld, stat %ld", + s->opt_len += 3*((ulg)max_blindex + 1) + 5 + 5 + 4; + Tracev((stderr, "\ndyn trees: dyn %lu, stat %lu", s->opt_len, s->static_len)); return max_blindex; @@ -831,61 +830,54 @@ local int build_bl_tree(s) * lengths of the bit length codes, the literal tree and the distance tree. * IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4. */ -local void send_all_trees(s, lcodes, dcodes, blcodes) - deflate_state *s; - int lcodes, dcodes, blcodes; /* number of codes for each tree */ -{ +local void send_all_trees(deflate_state *s, int lcodes, int dcodes, + int blcodes) { int rank; /* index in bl_order */ Assert (lcodes >= 257 && dcodes >= 1 && blcodes >= 4, "not enough codes"); Assert (lcodes <= L_CODES && dcodes <= D_CODES && blcodes <= BL_CODES, "too many codes"); Tracev((stderr, "\nbl counts: ")); - send_bits(s, lcodes-257, 5); /* not +255 as stated in appnote.txt */ - send_bits(s, dcodes-1, 5); - send_bits(s, blcodes-4, 4); /* not -3 as stated in appnote.txt */ + send_bits(s, lcodes - 257, 5); /* not +255 as stated in appnote.txt */ + send_bits(s, dcodes - 1, 5); + send_bits(s, blcodes - 4, 4); /* not -3 as stated in appnote.txt */ for (rank = 0; rank < blcodes; rank++) { Tracev((stderr, "\nbl code %2d ", bl_order[rank])); send_bits(s, s->bl_tree[bl_order[rank]].Len, 3); } - Tracev((stderr, "\nbl tree: sent %ld", s->bits_sent)); + Tracev((stderr, "\nbl tree: sent %lu", s->bits_sent)); - send_tree(s, (ct_data *)s->dyn_ltree, lcodes-1); /* literal tree */ - Tracev((stderr, "\nlit tree: sent %ld", s->bits_sent)); + send_tree(s, (ct_data *)s->dyn_ltree, lcodes - 1); /* literal tree */ + Tracev((stderr, "\nlit tree: sent %lu", s->bits_sent)); - send_tree(s, (ct_data *)s->dyn_dtree, dcodes-1); /* distance tree */ - Tracev((stderr, "\ndist tree: sent %ld", s->bits_sent)); + send_tree(s, (ct_data *)s->dyn_dtree, dcodes - 1); /* distance tree */ + Tracev((stderr, "\ndist tree: sent %lu", s->bits_sent)); } /* =========================================================================== * Send a stored block */ -void ZLIB_INTERNAL _tr_stored_block(s, buf, stored_len, last) - deflate_state *s; - charf *buf; /* input block */ - ulg stored_len; /* length of input block */ - int last; /* one if this is the last block for a file */ -{ - send_bits(s, (STORED_BLOCK<<1)+last, 3); /* send block type */ +void ZLIB_INTERNAL _tr_stored_block(deflate_state *s, charf *buf, + ulg stored_len, int last) { + send_bits(s, (STORED_BLOCK<<1) + last, 3); /* send block type */ bi_windup(s); /* align on byte boundary */ put_short(s, (ush)stored_len); put_short(s, (ush)~stored_len); - zmemcpy(s->pending_buf + s->pending, (Bytef *)buf, stored_len); + if (stored_len) + zmemcpy(s->pending_buf + s->pending, (Bytef *)buf, stored_len); s->pending += stored_len; #ifdef ZLIB_DEBUG s->compressed_len = (s->compressed_len + 3 + 7) & (ulg)~7L; s->compressed_len += (stored_len + 4) << 3; s->bits_sent += 2*16; - s->bits_sent += stored_len<<3; + s->bits_sent += stored_len << 3; #endif } /* =========================================================================== * Flush the bits in the bit buffer to pending output (leaves at most 7 bits) */ -void ZLIB_INTERNAL _tr_flush_bits(s) - deflate_state *s; -{ +void ZLIB_INTERNAL _tr_flush_bits(deflate_state *s) { bi_flush(s); } @@ -893,9 +885,7 @@ void ZLIB_INTERNAL _tr_flush_bits(s) * Send one empty static block to give enough lookahead for inflate. * This takes 10 bits, of which 7 may remain in the bit buffer. */ -void ZLIB_INTERNAL _tr_align(s) - deflate_state *s; -{ +void ZLIB_INTERNAL _tr_align(deflate_state *s) { send_bits(s, STATIC_TREES<<1, 3); send_code(s, END_BLOCK, static_ltree); #ifdef ZLIB_DEBUG @@ -904,16 +894,108 @@ void ZLIB_INTERNAL _tr_align(s) bi_flush(s); } +/* =========================================================================== + * Send the block data compressed using the given Huffman trees + */ +local void compress_block(deflate_state *s, const ct_data *ltree, + const ct_data *dtree) { + unsigned dist; /* distance of matched string */ + int lc; /* match length or unmatched char (if dist == 0) */ + unsigned sx = 0; /* running index in symbol buffers */ + unsigned code; /* the code to send */ + int extra; /* number of extra bits to send */ + + if (s->sym_next != 0) do { +#ifdef LIT_MEM + dist = s->d_buf[sx]; + lc = s->l_buf[sx++]; +#else + dist = s->sym_buf[sx++] & 0xff; + dist += (unsigned)(s->sym_buf[sx++] & 0xff) << 8; + lc = s->sym_buf[sx++]; +#endif + if (dist == 0) { + send_code(s, lc, ltree); /* send a literal byte */ + Tracecv(isgraph(lc), (stderr," '%c' ", lc)); + } else { + /* Here, lc is the match length - MIN_MATCH */ + code = _length_code[lc]; + send_code(s, code + LITERALS + 1, ltree); /* send length code */ + extra = extra_lbits[code]; + if (extra != 0) { + lc -= base_length[code]; + send_bits(s, lc, extra); /* send the extra length bits */ + } + dist--; /* dist is now the match distance - 1 */ + code = d_code(dist); + Assert (code < D_CODES, "bad d_code"); + + send_code(s, code, dtree); /* send the distance code */ + extra = extra_dbits[code]; + if (extra != 0) { + dist -= (unsigned)base_dist[code]; + send_bits(s, (int)dist, extra); /* send the extra bits */ + } + } /* literal or match pair ? */ + + /* Check for no overlay of pending_buf on needed symbols */ +#ifdef LIT_MEM + Assert(s->pending < 2 * (s->lit_bufsize + sx), "pendingBuf overflow"); +#else + Assert(s->pending < s->lit_bufsize + sx, "pendingBuf overflow"); +#endif + + } while (sx < s->sym_next); + + send_code(s, END_BLOCK, ltree); +} + +/* =========================================================================== + * Check if the data type is TEXT or BINARY, using the following algorithm: + * - TEXT if the two conditions below are satisfied: + * a) There are no non-portable control characters belonging to the + * "block list" (0..6, 14..25, 28..31). + * b) There is at least one printable character belonging to the + * "allow list" (9 {TAB}, 10 {LF}, 13 {CR}, 32..255). + * - BINARY otherwise. + * - The following partially-portable control characters form a + * "gray list" that is ignored in this detection algorithm: + * (7 {BEL}, 8 {BS}, 11 {VT}, 12 {FF}, 26 {SUB}, 27 {ESC}). + * IN assertion: the fields Freq of dyn_ltree are set. + */ +local int detect_data_type(deflate_state *s) { + /* block_mask is the bit mask of block-listed bytes + * set bits 0..6, 14..25, and 28..31 + * 0xf3ffc07f = binary 11110011111111111100000001111111 + */ + unsigned long block_mask = 0xf3ffc07fUL; + int n; + + /* Check for non-textual ("block-listed") bytes. */ + for (n = 0; n <= 31; n++, block_mask >>= 1) + if ((block_mask & 1) && (s->dyn_ltree[n].Freq != 0)) + return Z_BINARY; + + /* Check for textual ("allow-listed") bytes. */ + if (s->dyn_ltree[9].Freq != 0 || s->dyn_ltree[10].Freq != 0 + || s->dyn_ltree[13].Freq != 0) + return Z_TEXT; + for (n = 32; n < LITERALS; n++) + if (s->dyn_ltree[n].Freq != 0) + return Z_TEXT; + + /* There are no "block-listed" or "allow-listed" bytes: + * this stream either is empty or has tolerated ("gray-listed") bytes only. + */ + return Z_BINARY; +} + /* =========================================================================== * Determine the best encoding for the current block: dynamic trees, static * trees or store, and write out the encoded block. */ -void ZLIB_INTERNAL _tr_flush_block(s, buf, stored_len, last) - deflate_state *s; - charf *buf; /* input block, or NULL if too old */ - ulg stored_len; /* length of input block */ - int last; /* one if this is the last block for a file */ -{ +void ZLIB_INTERNAL _tr_flush_block(deflate_state *s, charf *buf, + ulg stored_len, int last) { ulg opt_lenb, static_lenb; /* opt_len and static_len in bytes */ int max_blindex = 0; /* index of last bit length code of non zero freq */ @@ -926,11 +1008,11 @@ void ZLIB_INTERNAL _tr_flush_block(s, buf, stored_len, last) /* Construct the literal and distance trees */ build_tree(s, (tree_desc *)(&(s->l_desc))); - Tracev((stderr, "\nlit data: dyn %ld, stat %ld", s->opt_len, + Tracev((stderr, "\nlit data: dyn %lu, stat %lu", s->opt_len, s->static_len)); build_tree(s, (tree_desc *)(&(s->d_desc))); - Tracev((stderr, "\ndist data: dyn %ld, stat %ld", s->opt_len, + Tracev((stderr, "\ndist data: dyn %lu, stat %lu", s->opt_len, s->static_len)); /* At this point, opt_len and static_len are the total bit lengths of * the compressed block data, excluding the tree representations. @@ -942,14 +1024,17 @@ void ZLIB_INTERNAL _tr_flush_block(s, buf, stored_len, last) max_blindex = build_bl_tree(s); /* Determine the best encoding. Compute the block lengths in bytes. */ - opt_lenb = (s->opt_len+3+7)>>3; - static_lenb = (s->static_len+3+7)>>3; + opt_lenb = (s->opt_len + 3 + 7) >> 3; + static_lenb = (s->static_len + 3 + 7) >> 3; Tracev((stderr, "\nopt %lu(%lu) stat %lu(%lu) stored %lu lit %u ", opt_lenb, s->opt_len, static_lenb, s->static_len, stored_len, - s->last_lit)); + s->sym_next / 3)); - if (static_lenb <= opt_lenb) opt_lenb = static_lenb; +#ifndef FORCE_STATIC + if (static_lenb <= opt_lenb || s->strategy == Z_FIXED) +#endif + opt_lenb = static_lenb; } else { Assert(buf != (char*)0, "lost buf"); @@ -959,7 +1044,7 @@ void ZLIB_INTERNAL _tr_flush_block(s, buf, stored_len, last) #ifdef FORCE_STORED if (buf != (char*)0) { /* force stored block */ #else - if (stored_len+4 <= opt_lenb && buf != (char*)0) { + if (stored_len + 4 <= opt_lenb && buf != (char*)0) { /* 4: two words for the lengths */ #endif /* The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE. @@ -970,21 +1055,17 @@ void ZLIB_INTERNAL _tr_flush_block(s, buf, stored_len, last) */ _tr_stored_block(s, buf, stored_len, last); -#ifdef FORCE_STATIC - } else if (static_lenb >= 0) { /* force static trees */ -#else - } else if (s->strategy == Z_FIXED || static_lenb == opt_lenb) { -#endif - send_bits(s, (STATIC_TREES<<1)+last, 3); + } else if (static_lenb == opt_lenb) { + send_bits(s, (STATIC_TREES<<1) + last, 3); compress_block(s, (const ct_data *)static_ltree, (const ct_data *)static_dtree); #ifdef ZLIB_DEBUG s->compressed_len += 3 + s->static_len; #endif } else { - send_bits(s, (DYN_TREES<<1)+last, 3); - send_all_trees(s, s->l_desc.max_code+1, s->d_desc.max_code+1, - max_blindex+1); + send_bits(s, (DYN_TREES<<1) + last, 3); + send_all_trees(s, s->l_desc.max_code + 1, s->d_desc.max_code + 1, + max_blindex + 1); compress_block(s, (const ct_data *)s->dyn_ltree, (const ct_data *)s->dyn_dtree); #ifdef ZLIB_DEBUG @@ -1003,21 +1084,23 @@ void ZLIB_INTERNAL _tr_flush_block(s, buf, stored_len, last) s->compressed_len += 7; /* align on byte boundary */ #endif } - Tracev((stderr,"\ncomprlen %lu(%lu) ", s->compressed_len>>3, - s->compressed_len-7*last)); + Tracev((stderr,"\ncomprlen %lu(%lu) ", s->compressed_len >> 3, + s->compressed_len - 7*(ulg)last)); } /* =========================================================================== * Save the match info and tally the frequency counts. Return true if * the current block must be flushed. */ -int ZLIB_INTERNAL _tr_tally (s, dist, lc) - deflate_state *s; - unsigned dist; /* distance of matched string */ - unsigned lc; /* match length-MIN_MATCH or unmatched char (if dist==0) */ -{ - s->d_buf[s->last_lit] = (ush)dist; - s->l_buf[s->last_lit++] = (uch)lc; +int ZLIB_INTERNAL _tr_tally(deflate_state *s, unsigned dist, unsigned lc) { +#ifdef LIT_MEM + s->d_buf[s->sym_next] = (ush)dist; + s->l_buf[s->sym_next++] = (uch)lc; +#else + s->sym_buf[s->sym_next++] = (uch)dist; + s->sym_buf[s->sym_next++] = (uch)(dist >> 8); + s->sym_buf[s->sym_next++] = (uch)lc; +#endif if (dist == 0) { /* lc is the unmatched char */ s->dyn_ltree[lc].Freq++; @@ -1029,175 +1112,8 @@ int ZLIB_INTERNAL _tr_tally (s, dist, lc) (ush)lc <= (ush)(MAX_MATCH-MIN_MATCH) && (ush)d_code(dist) < (ush)D_CODES, "_tr_tally: bad match"); - s->dyn_ltree[_length_code[lc]+LITERALS+1].Freq++; + s->dyn_ltree[_length_code[lc] + LITERALS + 1].Freq++; s->dyn_dtree[d_code(dist)].Freq++; } - -#ifdef TRUNCATE_BLOCK - /* Try to guess if it is profitable to stop the current block here */ - if ((s->last_lit & 0x1fff) == 0 && s->level > 2) { - /* Compute an upper bound for the compressed length */ - ulg out_length = (ulg)s->last_lit*8L; - ulg in_length = (ulg)((long)s->strstart - s->block_start); - int dcode; - for (dcode = 0; dcode < D_CODES; dcode++) { - out_length += (ulg)s->dyn_dtree[dcode].Freq * - (5L+extra_dbits[dcode]); - } - out_length >>= 3; - Tracev((stderr,"\nlast_lit %u, in %ld, out ~%ld(%ld%%) ", - s->last_lit, in_length, out_length, - 100L - out_length*100L/in_length)); - if (s->matches < s->last_lit/2 && out_length < in_length/2) return 1; - } -#endif - return (s->last_lit == s->lit_bufsize-1); - /* We avoid equality with lit_bufsize because of wraparound at 64K - * on 16 bit machines and because stored blocks are restricted to - * 64K-1 bytes. - */ -} - -/* =========================================================================== - * Send the block data compressed using the given Huffman trees - */ -local void compress_block(s, ltree, dtree) - deflate_state *s; - const ct_data *ltree; /* literal tree */ - const ct_data *dtree; /* distance tree */ -{ - unsigned dist; /* distance of matched string */ - int lc; /* match length or unmatched char (if dist == 0) */ - unsigned lx = 0; /* running index in l_buf */ - unsigned code; /* the code to send */ - int extra; /* number of extra bits to send */ - - if (s->last_lit != 0) do { - dist = s->d_buf[lx]; - lc = s->l_buf[lx++]; - if (dist == 0) { - send_code(s, lc, ltree); /* send a literal byte */ - Tracecv(isgraph(lc), (stderr," '%c' ", lc)); - } else { - /* Here, lc is the match length - MIN_MATCH */ - code = _length_code[lc]; - send_code(s, code+LITERALS+1, ltree); /* send the length code */ - extra = extra_lbits[code]; - if (extra != 0) { - lc -= base_length[code]; - send_bits(s, lc, extra); /* send the extra length bits */ - } - dist--; /* dist is now the match distance - 1 */ - code = d_code(dist); - Assert (code < D_CODES, "bad d_code"); - - send_code(s, code, dtree); /* send the distance code */ - extra = extra_dbits[code]; - if (extra != 0) { - dist -= (unsigned)base_dist[code]; - send_bits(s, dist, extra); /* send the extra distance bits */ - } - } /* literal or match pair ? */ - - /* Check that the overlay between pending_buf and d_buf+l_buf is ok: */ - Assert((uInt)(s->pending) < s->lit_bufsize + 2*lx, - "pendingBuf overflow"); - - } while (lx < s->last_lit); - - send_code(s, END_BLOCK, ltree); -} - -/* =========================================================================== - * Check if the data type is TEXT or BINARY, using the following algorithm: - * - TEXT if the two conditions below are satisfied: - * a) There are no non-portable control characters belonging to the - * "black list" (0..6, 14..25, 28..31). - * b) There is at least one printable character belonging to the - * "white list" (9 {TAB}, 10 {LF}, 13 {CR}, 32..255). - * - BINARY otherwise. - * - The following partially-portable control characters form a - * "gray list" that is ignored in this detection algorithm: - * (7 {BEL}, 8 {BS}, 11 {VT}, 12 {FF}, 26 {SUB}, 27 {ESC}). - * IN assertion: the fields Freq of dyn_ltree are set. - */ -local int detect_data_type(s) - deflate_state *s; -{ - /* black_mask is the bit mask of black-listed bytes - * set bits 0..6, 14..25, and 28..31 - * 0xf3ffc07f = binary 11110011111111111100000001111111 - */ - unsigned long black_mask = 0xf3ffc07fUL; - int n; - - /* Check for non-textual ("black-listed") bytes. */ - for (n = 0; n <= 31; n++, black_mask >>= 1) - if ((black_mask & 1) && (s->dyn_ltree[n].Freq != 0)) - return Z_BINARY; - - /* Check for textual ("white-listed") bytes. */ - if (s->dyn_ltree[9].Freq != 0 || s->dyn_ltree[10].Freq != 0 - || s->dyn_ltree[13].Freq != 0) - return Z_TEXT; - for (n = 32; n < LITERALS; n++) - if (s->dyn_ltree[n].Freq != 0) - return Z_TEXT; - - /* There are no "black-listed" or "white-listed" bytes: - * this stream either is empty or has tolerated ("gray-listed") bytes only. - */ - return Z_BINARY; -} - -/* =========================================================================== - * Reverse the first len bits of a code, using straightforward code (a faster - * method would use a table) - * IN assertion: 1 <= len <= 15 - */ -local unsigned bi_reverse(code, len) - unsigned code; /* the value to invert */ - int len; /* its bit length */ -{ - register unsigned res = 0; - do { - res |= code & 1; - code >>= 1, res <<= 1; - } while (--len > 0); - return res >> 1; -} - -/* =========================================================================== - * Flush the bit buffer, keeping at most 7 bits in it. - */ -local void bi_flush(s) - deflate_state *s; -{ - if (s->bi_valid == 16) { - put_short(s, s->bi_buf); - s->bi_buf = 0; - s->bi_valid = 0; - } else if (s->bi_valid >= 8) { - put_byte(s, (Byte)s->bi_buf); - s->bi_buf >>= 8; - s->bi_valid -= 8; - } -} - -/* =========================================================================== - * Flush the bit buffer and align the output on a byte boundary - */ -local void bi_windup(s) - deflate_state *s; -{ - if (s->bi_valid > 8) { - put_short(s, s->bi_buf); - } else if (s->bi_valid > 0) { - put_byte(s, (Byte)s->bi_buf); - } - s->bi_buf = 0; - s->bi_valid = 0; -#ifdef ZLIB_DEBUG - s->bits_sent = (s->bits_sent+7) & ~7; -#endif + return (s->sym_next == s->sym_end); } diff --git a/src/common/dep/zlib/uncompr.c b/src/common/dep/zlib/uncompr.c new file mode 100644 index 00000000..2195e785 --- /dev/null +++ b/src/common/dep/zlib/uncompr.c @@ -0,0 +1,101 @@ +/* uncompr.c -- decompress a memory buffer + * Copyright (C) 1995-2026 Jean-loup Gailly, Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id$ */ + +#define ZLIB_INTERNAL +#include "zlib.h" + +/* =========================================================================== + Decompresses the source buffer into the destination buffer. *sourceLen is + the byte length of the source buffer. Upon entry, *destLen is the total size + of the destination buffer, which must be large enough to hold the entire + uncompressed data. (The size of the uncompressed data must have been saved + previously by the compressor and transmitted to the decompressor by some + mechanism outside the scope of this compression library.) Upon exit, + *destLen is the size of the decompressed data and *sourceLen is the number + of source bytes consumed. Upon return, source + *sourceLen points to the + first unused input byte. + + uncompress returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_BUF_ERROR if there was not enough room in the output buffer, or + Z_DATA_ERROR if the input data was corrupted, including if the input data is + an incomplete zlib stream. + + The _z versions of the functions take size_t length arguments. +*/ +int ZEXPORT uncompress2_z(Bytef *dest, z_size_t *destLen, const Bytef *source, + z_size_t *sourceLen) { + z_stream stream; + int err; + const uInt max = (uInt)-1; + z_size_t len, left; + + if (sourceLen == NULL || (*sourceLen > 0 && source == NULL) || + destLen == NULL || (*destLen > 0 && dest == NULL)) + return Z_STREAM_ERROR; + + len = *sourceLen; + left = *destLen; + if (left == 0 && dest == Z_NULL) + dest = (Bytef *)&stream.reserved; /* next_out cannot be NULL */ + + stream.next_in = (z_const Bytef *)source; + stream.avail_in = 0; + stream.zalloc = (alloc_func)0; + stream.zfree = (free_func)0; + stream.opaque = (voidpf)0; + + err = inflateInit(&stream); + if (err != Z_OK) return err; + + stream.next_out = dest; + stream.avail_out = 0; + + do { + if (stream.avail_out == 0) { + stream.avail_out = left > (z_size_t)max ? max : (uInt)left; + left -= stream.avail_out; + } + if (stream.avail_in == 0) { + stream.avail_in = len > (z_size_t)max ? max : (uInt)len; + len -= stream.avail_in; + } + err = inflate(&stream, Z_NO_FLUSH); + } while (err == Z_OK); + + /* Set len and left to the unused input data and unused output space. Set + *sourceLen to the amount of input consumed. Set *destLen to the amount + of data produced. */ + len += stream.avail_in; + left += stream.avail_out; + *sourceLen -= len; + *destLen -= left; + + inflateEnd(&stream); + return err == Z_STREAM_END ? Z_OK : + err == Z_NEED_DICT ? Z_DATA_ERROR : + err == Z_BUF_ERROR && len == 0 ? Z_DATA_ERROR : + err; +} +int ZEXPORT uncompress2(Bytef *dest, uLongf *destLen, const Bytef *source, + uLong *sourceLen) { + int ret; + z_size_t got = *destLen, used = *sourceLen; + ret = uncompress2_z(dest, &got, source, &used); + *sourceLen = (uLong)used; + *destLen = (uLong)got; + return ret; +} +int ZEXPORT uncompress_z(Bytef *dest, z_size_t *destLen, const Bytef *source, + z_size_t sourceLen) { + z_size_t used = sourceLen; + return uncompress2_z(dest, destLen, source, &used); +} +int ZEXPORT uncompress(Bytef *dest, uLongf *destLen, const Bytef *source, + uLong sourceLen) { + uLong used = sourceLen; + return uncompress2(dest, destLen, source, &used); +} diff --git a/src/common/dep/zlib/zconf.h b/src/common/dep/zlib/zconf.h index 5e1d68a0..828ca617 100644 --- a/src/common/dep/zlib/zconf.h +++ b/src/common/dep/zlib/zconf.h @@ -1,5 +1,5 @@ /* zconf.h -- configuration of the zlib compression library - * Copyright (C) 1995-2016 Jean-loup Gailly, Mark Adler + * Copyright (C) 1995-2026 Jean-loup Gailly, Mark Adler * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -33,14 +33,21 @@ # ifndef Z_SOLO # define compress z_compress # define compress2 z_compress2 +# define compress_z z_compress_z +# define compress2_z z_compress2_z # define compressBound z_compressBound +# define compressBound_z z_compressBound_z # endif # define crc32 z_crc32 # define crc32_combine z_crc32_combine # define crc32_combine64 z_crc32_combine64 +# define crc32_combine_gen z_crc32_combine_gen +# define crc32_combine_gen64 z_crc32_combine_gen64 +# define crc32_combine_op z_crc32_combine_op # define crc32_z z_crc32_z # define deflate z_deflate # define deflateBound z_deflateBound +# define deflateBound_z z_deflateBound_z # define deflateCopy z_deflateCopy # define deflateEnd z_deflateEnd # define deflateGetDictionary z_deflateGetDictionary @@ -56,6 +63,7 @@ # define deflateSetDictionary z_deflateSetDictionary # define deflateSetHeader z_deflateSetHeader # define deflateTune z_deflateTune +# define deflateUsed z_deflateUsed # define deflate_copyright z_deflate_copyright # define get_crc_table z_get_crc_table # ifndef Z_SOLO @@ -125,9 +133,12 @@ # define inflate_copyright z_inflate_copyright # define inflate_fast z_inflate_fast # define inflate_table z_inflate_table +# define inflate_fixed z_inflate_fixed # ifndef Z_SOLO # define uncompress z_uncompress # define uncompress2 z_uncompress2 +# define uncompress_z z_uncompress_z +# define uncompress2_z z_uncompress2_z # endif # define zError z_zError # ifndef Z_SOLO @@ -231,14 +242,20 @@ # endif #endif -#if defined(ZLIB_CONST) && !defined(z_const) -# define z_const const -#else -# define z_const +#ifndef z_const +# ifdef ZLIB_CONST +# define z_const const +# else +# define z_const +# endif #endif #ifdef Z_SOLO - typedef unsigned long z_size_t; +# ifdef _WIN64 + typedef unsigned long long z_size_t; +# else + typedef unsigned long z_size_t; +# endif #else # define z_longlong long long # if defined(NO_SIZE_T) @@ -293,14 +310,6 @@ # endif #endif -#ifndef Z_ARG /* function prototypes for stdarg */ -# if defined(STDC) || defined(Z_HAVE_STDARG_H) -# define Z_ARG(args) args -# else -# define Z_ARG(args) () -# endif -#endif - /* The following definitions for FAR are needed only for MSDOS mixed * model programming (small or medium model with some far allocations). * This was tested only with MSC; for other MSDOS compilers you may have @@ -349,6 +358,9 @@ # ifdef FAR # undef FAR # endif +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif # include /* No need for _export, use ZLIB.DEF instead. */ /* For complete Windows compatibility, use WINAPI, not __stdcall. */ @@ -431,11 +443,11 @@ typedef uLong FAR uLongf; typedef unsigned long z_crc_t; #endif -#ifdef HAVE_UNISTD_H /* may be set to #if 1 by ./configure */ +#if HAVE_UNISTD_H-0 /* may be set to #if 1 by ./configure */ # define Z_HAVE_UNISTD_H #endif -#ifdef HAVE_STDARG_H /* may be set to #if 1 by ./configure */ +#if HAVE_STDARG_H-0 /* may be set to #if 1 by ./configure */ # define Z_HAVE_STDARG_H #endif @@ -467,11 +479,14 @@ typedef uLong FAR uLongf; # undef _LARGEFILE64_SOURCE #endif -#if defined(__WATCOMC__) && !defined(Z_HAVE_UNISTD_H) -# define Z_HAVE_UNISTD_H +#ifndef Z_HAVE_UNISTD_H +# if defined(__WATCOMC__) || defined(__GO32__) || \ + (defined(_LARGEFILE64_SOURCE) && !defined(_WIN32)) +# define Z_HAVE_UNISTD_H +# endif #endif #ifndef Z_SOLO -# if defined(Z_HAVE_UNISTD_H) || defined(_LARGEFILE64_SOURCE) +# if defined(Z_HAVE_UNISTD_H) # include /* for SEEK_*, off_t, and _LFS64_LARGEFILE */ # ifdef VMS # include /* for off_t */ @@ -501,17 +516,19 @@ typedef uLong FAR uLongf; #endif #ifndef z_off_t -# define z_off_t long +# define z_off_t long long #endif #if !defined(_WIN32) && defined(Z_LARGE64) # define z_off64_t off64_t +#elif defined(__MINGW32__) +# define z_off64_t long long +#elif defined(_WIN32) && !defined(__GNUC__) +# define z_off64_t __int64 +#elif defined(__GO32__) +# define z_off64_t offset_t #else -# if defined(_WIN32) && !defined(__GNUC__) && !defined(Z_SOLO) -# define z_off64_t __int64 -# else -# define z_off64_t z_off_t -# endif +# define z_off64_t z_off_t #endif /* MVS linker does not support external names larger than 8 bytes */ diff --git a/src/common/dep/zlib/zlib.h b/src/common/dep/zlib/zlib.h index f09cdaf1..a57d3361 100644 --- a/src/common/dep/zlib/zlib.h +++ b/src/common/dep/zlib/zlib.h @@ -1,7 +1,7 @@ /* zlib.h -- interface of the 'zlib' general purpose compression library - version 1.2.11, January 15th, 2017 + version 1.3.2, February 17th, 2026 - Copyright (C) 1995-2017 Jean-loup Gailly and Mark Adler + Copyright (C) 1995-2026 Jean-loup Gailly and Mark Adler This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -24,24 +24,28 @@ The data format used by the zlib library is described by RFCs (Request for - Comments) 1950 to 1952 in the files http://tools.ietf.org/html/rfc1950 + Comments) 1950 to 1952 at https://datatracker.ietf.org/doc/html/rfc1950 (zlib format), rfc1951 (deflate format) and rfc1952 (gzip format). */ #ifndef ZLIB_H #define ZLIB_H -#include "zconf.h" +#ifdef ZLIB_BUILD +# include +#else +# include "zconf.h" +#endif #ifdef __cplusplus extern "C" { #endif -#define ZLIB_VERSION "1.2.11" -#define ZLIB_VERNUM 0x12b0 +#define ZLIB_VERSION "1.3.2" +#define ZLIB_VERNUM 0x1320 #define ZLIB_VER_MAJOR 1 -#define ZLIB_VER_MINOR 2 -#define ZLIB_VER_REVISION 11 +#define ZLIB_VER_MINOR 3 +#define ZLIB_VER_REVISION 2 #define ZLIB_VER_SUBREVISION 0 /* @@ -78,8 +82,8 @@ extern "C" { even in the case of corrupted input. */ -typedef voidpf (*alloc_func) OF((voidpf opaque, uInt items, uInt size)); -typedef void (*free_func) OF((voidpf opaque, voidpf address)); +typedef voidpf (*alloc_func)(voidpf opaque, uInt items, uInt size); +typedef void (*free_func)(voidpf opaque, voidpf address); struct internal_state; @@ -217,7 +221,7 @@ typedef gz_header FAR *gz_headerp; /* basic functions */ -ZEXTERN const char * ZEXPORT zlibVersion OF((void)); +ZEXTERN const char * ZEXPORT zlibVersion(void); /* The application can compare zlibVersion and ZLIB_VERSION for consistency. If the first character differs, the library code actually used is not compatible with the zlib.h header file used by the application. This check @@ -225,12 +229,12 @@ ZEXTERN const char * ZEXPORT zlibVersion OF((void)); */ /* -ZEXTERN int ZEXPORT deflateInit OF((z_streamp strm, int level)); +ZEXTERN int ZEXPORT deflateInit(z_streamp strm, int level); Initializes the internal stream state for compression. The fields zalloc, zfree and opaque must be initialized before by the caller. If zalloc and zfree are set to Z_NULL, deflateInit updates them to use default - allocation functions. + allocation functions. total_in, total_out, adler, and msg are initialized. The compression level must be Z_DEFAULT_COMPRESSION, or between 0 and 9: 1 gives best speed, 9 gives best compression, 0 gives no compression at all @@ -247,7 +251,7 @@ ZEXTERN int ZEXPORT deflateInit OF((z_streamp strm, int level)); */ -ZEXTERN int ZEXPORT deflate OF((z_streamp strm, int flush)); +ZEXTERN int ZEXPORT deflate(z_streamp strm, int flush); /* deflate compresses as much data as possible, and stops when the input buffer becomes empty or the output buffer becomes full. It may introduce @@ -276,7 +280,7 @@ ZEXTERN int ZEXPORT deflate OF((z_streamp strm, int flush)); == 0), or after each call of deflate(). If deflate returns Z_OK and with zero avail_out, it must be called again after making room in the output buffer because there might be more output pending. See deflatePending(), - which can be used if desired to determine whether or not there is more ouput + which can be used if desired to determine whether or not there is more output in that case. Normally the parameter flush is set to Z_NO_FLUSH, which allows deflate to @@ -320,8 +324,8 @@ ZEXTERN int ZEXPORT deflate OF((z_streamp strm, int flush)); with the same value of the flush parameter and more output space (updated avail_out), until the flush is complete (deflate returns with non-zero avail_out). In the case of a Z_FULL_FLUSH or Z_SYNC_FLUSH, make sure that - avail_out is greater than six to avoid repeated flush markers due to - avail_out == 0 on return. + avail_out is greater than six when the flush marker begins, in order to avoid + repeated flush markers upon calling deflate() again when avail_out == 0. If the parameter flush is set to Z_FINISH, pending input is processed, pending output is flushed and deflate returns with Z_STREAM_END if there was @@ -360,7 +364,7 @@ ZEXTERN int ZEXPORT deflate OF((z_streamp strm, int flush)); */ -ZEXTERN int ZEXPORT deflateEnd OF((z_streamp strm)); +ZEXTERN int ZEXPORT deflateEnd(z_streamp strm); /* All dynamically allocated data structures for this stream are freed. This function discards any unprocessed input and does not flush any pending @@ -375,7 +379,7 @@ ZEXTERN int ZEXPORT deflateEnd OF((z_streamp strm)); /* -ZEXTERN int ZEXPORT inflateInit OF((z_streamp strm)); +ZEXTERN int ZEXPORT inflateInit(z_streamp strm); Initializes the internal stream state for decompression. The fields next_in, avail_in, zalloc, zfree and opaque must be initialized before by @@ -383,7 +387,8 @@ ZEXTERN int ZEXPORT inflateInit OF((z_streamp strm)); read or consumed. The allocation of a sliding window will be deferred to the first call of inflate (if the decompression does not complete on the first call). If zalloc and zfree are set to Z_NULL, inflateInit updates - them to use default allocation functions. + them to use default allocation functions. total_in, total_out, adler, and + msg are initialized. inflateInit returns Z_OK if success, Z_MEM_ERROR if there was not enough memory, Z_VERSION_ERROR if the zlib library version is incompatible with the @@ -397,7 +402,7 @@ ZEXTERN int ZEXPORT inflateInit OF((z_streamp strm)); */ -ZEXTERN int ZEXPORT inflate OF((z_streamp strm, int flush)); +ZEXTERN int ZEXPORT inflate(z_streamp strm, int flush); /* inflate decompresses as much data as possible, and stops when the input buffer becomes empty or the output buffer becomes full. It may introduce @@ -440,7 +445,7 @@ ZEXTERN int ZEXPORT inflate OF((z_streamp strm, int flush)); The Z_BLOCK option assists in appending to or combining deflate streams. To assist in this, on return inflate() always sets strm->data_type to the - number of unused bits in the last byte taken from strm->next_in, plus 64 if + number of unused bits in the input taken from strm->next_in, plus 64 if inflate() is currently decoding the last block in the deflate stream, plus 128 if inflate() returned immediately after decoding an end-of-block code or decoding the complete header up to just before the first byte of the deflate @@ -517,7 +522,7 @@ ZEXTERN int ZEXPORT inflate OF((z_streamp strm, int flush)); */ -ZEXTERN int ZEXPORT inflateEnd OF((z_streamp strm)); +ZEXTERN int ZEXPORT inflateEnd(z_streamp strm); /* All dynamically allocated data structures for this stream are freed. This function discards any unprocessed input and does not flush any pending @@ -535,16 +540,15 @@ ZEXTERN int ZEXPORT inflateEnd OF((z_streamp strm)); */ /* -ZEXTERN int ZEXPORT deflateInit2 OF((z_streamp strm, - int level, - int method, - int windowBits, - int memLevel, - int strategy)); +ZEXTERN int ZEXPORT deflateInit2(z_streamp strm, + int level, + int method, + int windowBits, + int memLevel, + int strategy); This is another version of deflateInit with more compression options. The - fields next_in, zalloc, zfree and opaque must be initialized before by the - caller. + fields zalloc, zfree and opaque must be initialized before by the caller. The method parameter is the compression method. It must be Z_DEFLATED in this version of the library. @@ -587,18 +591,21 @@ ZEXTERN int ZEXPORT deflateInit2 OF((z_streamp strm, The strategy parameter is used to tune the compression algorithm. Use the value Z_DEFAULT_STRATEGY for normal data, Z_FILTERED for data produced by a - filter (or predictor), Z_HUFFMAN_ONLY to force Huffman encoding only (no - string match), or Z_RLE to limit match distances to one (run-length - encoding). Filtered data consists mostly of small values with a somewhat - random distribution. In this case, the compression algorithm is tuned to - compress them better. The effect of Z_FILTERED is to force more Huffman - coding and less string matching; it is somewhat intermediate between - Z_DEFAULT_STRATEGY and Z_HUFFMAN_ONLY. Z_RLE is designed to be almost as - fast as Z_HUFFMAN_ONLY, but give better compression for PNG image data. The - strategy parameter only affects the compression ratio but not the - correctness of the compressed output even if it is not set appropriately. - Z_FIXED prevents the use of dynamic Huffman codes, allowing for a simpler - decoder for special applications. + filter (or predictor), Z_RLE to limit match distances to one (run-length + encoding), or Z_HUFFMAN_ONLY to force Huffman encoding only (no string + matching). Filtered data consists mostly of small values with a somewhat + random distribution, as produced by the PNG filters. In this case, the + compression algorithm is tuned to compress them better. The effect of + Z_FILTERED is to force more Huffman coding and less string matching than the + default; it is intermediate between Z_DEFAULT_STRATEGY and Z_HUFFMAN_ONLY. + Z_RLE is almost as fast as Z_HUFFMAN_ONLY, but should give better + compression for PNG image data than Huffman only. The degree of string + matching from most to none is: Z_DEFAULT_STRATEGY, Z_FILTERED, Z_RLE, then + Z_HUFFMAN_ONLY. The strategy parameter affects the compression ratio but + never the correctness of the compressed output, even if it is not set + optimally for the given data. Z_FIXED uses the default string matching, but + prevents the use of dynamic Huffman codes, allowing for a simpler decoder + for special applications. deflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough memory, Z_STREAM_ERROR if any parameter is invalid (such as an invalid @@ -608,9 +615,9 @@ ZEXTERN int ZEXPORT deflateInit2 OF((z_streamp strm, compression: this will be done by deflate(). */ -ZEXTERN int ZEXPORT deflateSetDictionary OF((z_streamp strm, - const Bytef *dictionary, - uInt dictLength)); +ZEXTERN int ZEXPORT deflateSetDictionary(z_streamp strm, + const Bytef *dictionary, + uInt dictLength); /* Initializes the compression dictionary from the given byte sequence without producing any compressed output. When using the zlib format, this @@ -652,16 +659,16 @@ ZEXTERN int ZEXPORT deflateSetDictionary OF((z_streamp strm, not perform any compression: this will be done by deflate(). */ -ZEXTERN int ZEXPORT deflateGetDictionary OF((z_streamp strm, - Bytef *dictionary, - uInt *dictLength)); +ZEXTERN int ZEXPORT deflateGetDictionary(z_streamp strm, + Bytef *dictionary, + uInt *dictLength); /* Returns the sliding dictionary being maintained by deflate. dictLength is set to the number of bytes in the dictionary, and that many bytes are copied to dictionary. dictionary must have enough space, where 32768 bytes is always enough. If deflateGetDictionary() is called with dictionary equal to Z_NULL, then only the dictionary length is returned, and nothing is copied. - Similary, if dictLength is Z_NULL, then it is not set. + Similarly, if dictLength is Z_NULL, then it is not set. deflateGetDictionary() may return a length less than the window size, even when more than the window size in input has been provided. It may return up @@ -674,8 +681,8 @@ ZEXTERN int ZEXPORT deflateGetDictionary OF((z_streamp strm, stream state is inconsistent. */ -ZEXTERN int ZEXPORT deflateCopy OF((z_streamp dest, - z_streamp source)); +ZEXTERN int ZEXPORT deflateCopy(z_streamp dest, + z_streamp source); /* Sets the destination stream as a complete copy of the source stream. @@ -692,31 +699,32 @@ ZEXTERN int ZEXPORT deflateCopy OF((z_streamp dest, destination. */ -ZEXTERN int ZEXPORT deflateReset OF((z_streamp strm)); +ZEXTERN int ZEXPORT deflateReset(z_streamp strm); /* This function is equivalent to deflateEnd followed by deflateInit, but does not free and reallocate the internal compression state. The stream will leave the compression level and any other attributes that may have been - set unchanged. + set unchanged. total_in, total_out, adler, and msg are initialized. deflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source stream state was inconsistent (such as zalloc or state being Z_NULL). */ -ZEXTERN int ZEXPORT deflateParams OF((z_streamp strm, - int level, - int strategy)); +ZEXTERN int ZEXPORT deflateParams(z_streamp strm, + int level, + int strategy); /* Dynamically update the compression level and compression strategy. The interpretation of level and strategy is as in deflateInit2(). This can be used to switch between compression and straight copy of the input data, or to switch to a different kind of input data requiring a different strategy. If the compression approach (which is a function of the level) or the - strategy is changed, and if any input has been consumed in a previous - deflate() call, then the input available so far is compressed with the old - level and strategy using deflate(strm, Z_BLOCK). There are three approaches - for the compression levels 0, 1..3, and 4..9 respectively. The new level - and strategy will take effect at the next call of deflate(). + strategy is changed, and if there have been any deflate() calls since the + state was initialized or reset, then the input available so far is + compressed with the old level and strategy using deflate(strm, Z_BLOCK). + There are three approaches for the compression levels 0, 1..3, and 4..9 + respectively. The new level and strategy will take effect at the next call + of deflate(). If a deflate(strm, Z_BLOCK) is performed by deflateParams(), and it does not have enough output space to complete, then the parameter change will not @@ -729,7 +737,7 @@ ZEXTERN int ZEXPORT deflateParams OF((z_streamp strm, Then no more input data should be provided before the deflateParams() call. If this is done, the old level and strategy will be applied to the data compressed before deflateParams(), and the new level and strategy will be - applied to the the data compressed after deflateParams(). + applied to the data compressed after deflateParams(). deflateParams returns Z_OK on success, Z_STREAM_ERROR if the source stream state was inconsistent or if a parameter was invalid, or Z_BUF_ERROR if @@ -740,11 +748,11 @@ ZEXTERN int ZEXPORT deflateParams OF((z_streamp strm, retried with more output space. */ -ZEXTERN int ZEXPORT deflateTune OF((z_streamp strm, - int good_length, - int max_lazy, - int nice_length, - int max_chain)); +ZEXTERN int ZEXPORT deflateTune(z_streamp strm, + int good_length, + int max_lazy, + int nice_length, + int max_chain); /* Fine tune deflate's internal compression parameters. This should only be used by someone who understands the algorithm used by zlib's deflate for @@ -757,8 +765,8 @@ ZEXTERN int ZEXPORT deflateTune OF((z_streamp strm, returns Z_OK on success, or Z_STREAM_ERROR for an invalid deflate stream. */ -ZEXTERN uLong ZEXPORT deflateBound OF((z_streamp strm, - uLong sourceLen)); +ZEXTERN uLong ZEXPORT deflateBound(z_streamp strm, uLong sourceLen); +ZEXTERN z_size_t ZEXPORT deflateBound_z(z_streamp strm, z_size_t sourceLen); /* deflateBound() returns an upper bound on the compressed size after deflation of sourceLen bytes. It must be called after deflateInit() or @@ -770,11 +778,14 @@ ZEXTERN uLong ZEXPORT deflateBound OF((z_streamp strm, to return Z_STREAM_END. Note that it is possible for the compressed size to be larger than the value returned by deflateBound() if flush options other than Z_FINISH or Z_NO_FLUSH are used. + + delfateBound_z() is the same, but takes and returns a size_t length. Note + that a long is 32 bits on Windows. */ -ZEXTERN int ZEXPORT deflatePending OF((z_streamp strm, - unsigned *pending, - int *bits)); +ZEXTERN int ZEXPORT deflatePending(z_streamp strm, + unsigned *pending, + int *bits); /* deflatePending() returns the number of bytes and bits of output that have been generated, but not yet provided in the available output. The bytes not @@ -784,12 +795,27 @@ ZEXTERN int ZEXPORT deflatePending OF((z_streamp strm, or bits are Z_NULL, then those values are not set. deflatePending returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. If an int is 16 bits and memLevel is 9, then + it is possible for the number of pending bytes to not fit in an unsigned. In + that case Z_BUF_ERROR is returned and *pending is set to the maximum value + of an unsigned. + */ + +ZEXTERN int ZEXPORT deflateUsed(z_streamp strm, + int *bits); +/* + deflateUsed() returns in *bits the most recent number of deflate bits used + in the last byte when flushing to a byte boundary. The result is in 1..8, or + 0 if there has not yet been a flush. This helps determine the location of + the last bit of a deflate stream. + + deflateUsed returns Z_OK if success, or Z_STREAM_ERROR if the source stream state was inconsistent. */ -ZEXTERN int ZEXPORT deflatePrime OF((z_streamp strm, - int bits, - int value)); +ZEXTERN int ZEXPORT deflatePrime(z_streamp strm, + int bits, + int value); /* deflatePrime() inserts bits in the deflate output stream. The intent is that this function is used to start off the deflate output with the bits @@ -804,8 +830,8 @@ ZEXTERN int ZEXPORT deflatePrime OF((z_streamp strm, source stream state was inconsistent. */ -ZEXTERN int ZEXPORT deflateSetHeader OF((z_streamp strm, - gz_headerp head)); +ZEXTERN int ZEXPORT deflateSetHeader(z_streamp strm, + gz_headerp head); /* deflateSetHeader() provides gzip header information for when a gzip stream is requested by deflateInit2(). deflateSetHeader() may be called @@ -821,16 +847,17 @@ ZEXTERN int ZEXPORT deflateSetHeader OF((z_streamp strm, gzip file" and give up. If deflateSetHeader is not used, the default gzip header has text false, - the time set to zero, and os set to 255, with no extra, name, or comment - fields. The gzip header is returned to the default state by deflateReset(). + the time set to zero, and os set to the current operating system, with no + extra, name, or comment fields. The gzip header is returned to the default + state by deflateReset(). deflateSetHeader returns Z_OK if success, or Z_STREAM_ERROR if the source stream state was inconsistent. */ /* -ZEXTERN int ZEXPORT inflateInit2 OF((z_streamp strm, - int windowBits)); +ZEXTERN int ZEXPORT inflateInit2(z_streamp strm, + int windowBits); This is another version of inflateInit with an extra parameter. The fields next_in, avail_in, zalloc, zfree and opaque must be initialized @@ -865,9 +892,11 @@ ZEXTERN int ZEXPORT inflateInit2 OF((z_streamp strm, detection, or add 16 to decode only the gzip format (the zlib format will return a Z_DATA_ERROR). If a gzip stream is being decoded, strm->adler is a CRC-32 instead of an Adler-32. Unlike the gunzip utility and gzread() (see - below), inflate() will not automatically decode concatenated gzip streams. - inflate() will return Z_STREAM_END at the end of the gzip stream. The state - would need to be reset to continue decoding a subsequent gzip stream. + below), inflate() will *not* automatically decode concatenated gzip members. + inflate() will return Z_STREAM_END at the end of the gzip member. The state + would need to be reset to continue decoding a subsequent gzip member. This + *must* be done if there is more data after a gzip member, in order for the + decompression to be compliant with the gzip standard (RFC 1952). inflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough memory, Z_VERSION_ERROR if the zlib library version is incompatible with the @@ -881,9 +910,9 @@ ZEXTERN int ZEXPORT inflateInit2 OF((z_streamp strm, deferred until inflate() is called. */ -ZEXTERN int ZEXPORT inflateSetDictionary OF((z_streamp strm, - const Bytef *dictionary, - uInt dictLength)); +ZEXTERN int ZEXPORT inflateSetDictionary(z_streamp strm, + const Bytef *dictionary, + uInt dictLength); /* Initializes the decompression dictionary from the given uncompressed byte sequence. This function must be called immediately after a call of inflate, @@ -904,22 +933,22 @@ ZEXTERN int ZEXPORT inflateSetDictionary OF((z_streamp strm, inflate(). */ -ZEXTERN int ZEXPORT inflateGetDictionary OF((z_streamp strm, - Bytef *dictionary, - uInt *dictLength)); +ZEXTERN int ZEXPORT inflateGetDictionary(z_streamp strm, + Bytef *dictionary, + uInt *dictLength); /* Returns the sliding dictionary being maintained by inflate. dictLength is set to the number of bytes in the dictionary, and that many bytes are copied to dictionary. dictionary must have enough space, where 32768 bytes is always enough. If inflateGetDictionary() is called with dictionary equal to Z_NULL, then only the dictionary length is returned, and nothing is copied. - Similary, if dictLength is Z_NULL, then it is not set. + Similarly, if dictLength is Z_NULL, then it is not set. inflateGetDictionary returns Z_OK on success, or Z_STREAM_ERROR if the stream state is inconsistent. */ -ZEXTERN int ZEXPORT inflateSync OF((z_streamp strm)); +ZEXTERN int ZEXPORT inflateSync(z_streamp strm); /* Skips invalid compressed data until a possible full flush point (see above for the description of deflate with Z_FULL_FLUSH) can be found, or until all @@ -932,14 +961,14 @@ ZEXTERN int ZEXPORT inflateSync OF((z_streamp strm)); inflateSync returns Z_OK if a possible full flush point has been found, Z_BUF_ERROR if no more input was provided, Z_DATA_ERROR if no flush point has been found, or Z_STREAM_ERROR if the stream structure was inconsistent. - In the success case, the application may save the current current value of - total_in which indicates where valid compressed data was found. In the - error case, the application may repeatedly call inflateSync, providing more - input each time, until success or end of the input data. + In the success case, the application may save the current value of total_in + which indicates where valid compressed data was found. In the error case, + the application may repeatedly call inflateSync, providing more input each + time, until success or end of the input data. */ -ZEXTERN int ZEXPORT inflateCopy OF((z_streamp dest, - z_streamp source)); +ZEXTERN int ZEXPORT inflateCopy(z_streamp dest, + z_streamp source); /* Sets the destination stream as a complete copy of the source stream. @@ -954,18 +983,19 @@ ZEXTERN int ZEXPORT inflateCopy OF((z_streamp dest, destination. */ -ZEXTERN int ZEXPORT inflateReset OF((z_streamp strm)); +ZEXTERN int ZEXPORT inflateReset(z_streamp strm); /* This function is equivalent to inflateEnd followed by inflateInit, but does not free and reallocate the internal decompression state. The stream will keep attributes that may have been set by inflateInit2. + total_in, total_out, adler, and msg are initialized. inflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source stream state was inconsistent (such as zalloc or state being Z_NULL). */ -ZEXTERN int ZEXPORT inflateReset2 OF((z_streamp strm, - int windowBits)); +ZEXTERN int ZEXPORT inflateReset2(z_streamp strm, + int windowBits); /* This function is the same as inflateReset, but it also permits changing the wrap and window size requests. The windowBits parameter is interpreted @@ -978,17 +1008,19 @@ ZEXTERN int ZEXPORT inflateReset2 OF((z_streamp strm, the windowBits parameter is invalid. */ -ZEXTERN int ZEXPORT inflatePrime OF((z_streamp strm, - int bits, - int value)); +ZEXTERN int ZEXPORT inflatePrime(z_streamp strm, + int bits, + int value); /* - This function inserts bits in the inflate input stream. The intent is - that this function is used to start inflating at a bit position in the - middle of a byte. The provided bits will be used before any bytes are used - from next_in. This function should only be used with raw inflate, and - should be used before the first inflate() call after inflateInit2() or - inflateReset(). bits must be less than or equal to 16, and that many of the - least significant bits of value will be inserted in the input. + This function inserts bits in the inflate input stream. The intent is to + use inflatePrime() to start inflating at a bit position in the middle of a + byte. The provided bits will be used before any bytes are used from + next_in. This function should be used with raw inflate, before the first + inflate() call, after inflateInit2() or inflateReset(). It can also be used + after an inflate() return indicates the end of a deflate block or header + when using Z_BLOCK. bits must be less than or equal to 16, and that many of + the least significant bits of value will be inserted in the input. The + other bits in value can be non-zero, and will be ignored. If bits is negative, then the input stream bit buffer is emptied. Then inflatePrime() can be called again to put bits in the buffer. This is used @@ -996,10 +1028,18 @@ ZEXTERN int ZEXPORT inflatePrime OF((z_streamp strm, to feeding inflate codes. inflatePrime returns Z_OK if success, or Z_STREAM_ERROR if the source - stream state was inconsistent. + stream state was inconsistent, or if bits is out of range. If inflate was + in the middle of processing a header, trailer, or stored block lengths, then + it is possible for there to be only eight bits available in the bit buffer. + In that case, bits > 8 is considered out of range. However, when used as + outlined above, there will always be 16 bits available in the buffer for + insertion. As noted in its documentation above, inflate records the number + of bits in the bit buffer on return in data_type. 32 minus that is the + number of bits available for insertion. inflatePrime does not update + data_type with the new number of bits in buffer. */ -ZEXTERN long ZEXPORT inflateMark OF((z_streamp strm)); +ZEXTERN long ZEXPORT inflateMark(z_streamp strm); /* This function returns two values, one in the lower 16 bits of the return value, and the other in the remaining upper bits, obtained by shifting the @@ -1027,8 +1067,8 @@ ZEXTERN long ZEXPORT inflateMark OF((z_streamp strm)); source stream state was inconsistent. */ -ZEXTERN int ZEXPORT inflateGetHeader OF((z_streamp strm, - gz_headerp head)); +ZEXTERN int ZEXPORT inflateGetHeader(z_streamp strm, + gz_headerp head); /* inflateGetHeader() requests that gzip header information be stored in the provided gz_header structure. inflateGetHeader() may be called after @@ -1042,20 +1082,22 @@ ZEXTERN int ZEXPORT inflateGetHeader OF((z_streamp strm, The text, time, xflags, and os fields are filled in with the gzip header contents. hcrc is set to true if there is a header CRC. (The header CRC - was valid if done is set to one.) If extra is not Z_NULL, then extra_max - contains the maximum number of bytes to write to extra. Once done is true, - extra_len contains the actual extra field length, and extra contains the - extra field, or that field truncated if extra_max is less than extra_len. - If name is not Z_NULL, then up to name_max characters are written there, - terminated with a zero unless the length is greater than name_max. If - comment is not Z_NULL, then up to comm_max characters are written there, - terminated with a zero unless the length is greater than comm_max. When any - of extra, name, or comment are not Z_NULL and the respective field is not - present in the header, then that field is set to Z_NULL to signal its - absence. This allows the use of deflateSetHeader() with the returned - structure to duplicate the header. However if those fields are set to - allocated memory, then the application will need to save those pointers - elsewhere so that they can be eventually freed. + was valid if done is set to one.) The extra, name, and comment pointers + much each be either Z_NULL or point to space to store that information from + the header. If extra is not Z_NULL, then extra_max contains the maximum + number of bytes that can be written to extra. Once done is true, extra_len + contains the actual extra field length, and extra contains the extra field, + or that field truncated if extra_max is less than extra_len. If name is not + Z_NULL, then up to name_max characters, including the terminating zero, are + written there. If comment is not Z_NULL, then up to comm_max characters, + including the terminating zero, are written there. The application can tell + that the name or comment did not fit in the provided space by the absence of + a terminating zero. If any of extra, name, or comment are not present in + the header, then that field's pointer is set to Z_NULL. This allows the use + of deflateSetHeader() with the returned structure to duplicate the header. + Note that if those fields initially pointed to allocated memory, then the + application will need to save them elsewhere so that they can be eventually + freed. If inflateGetHeader is not used, then the header information is simply discarded. The header is always checked for validity, including the header @@ -1068,8 +1110,8 @@ ZEXTERN int ZEXPORT inflateGetHeader OF((z_streamp strm, */ /* -ZEXTERN int ZEXPORT inflateBackInit OF((z_streamp strm, int windowBits, - unsigned char FAR *window)); +ZEXTERN int ZEXPORT inflateBackInit(z_streamp strm, int windowBits, + unsigned char FAR *window); Initialize the internal stream state for decompression using inflateBack() calls. The fields zalloc, zfree and opaque in strm must be initialized @@ -1089,13 +1131,13 @@ ZEXTERN int ZEXPORT inflateBackInit OF((z_streamp strm, int windowBits, the version of the header file. */ -typedef unsigned (*in_func) OF((void FAR *, - z_const unsigned char FAR * FAR *)); -typedef int (*out_func) OF((void FAR *, unsigned char FAR *, unsigned)); +typedef unsigned (*in_func)(void FAR *, + z_const unsigned char FAR * FAR *); +typedef int (*out_func)(void FAR *, unsigned char FAR *, unsigned); -ZEXTERN int ZEXPORT inflateBack OF((z_streamp strm, - in_func in, void FAR *in_desc, - out_func out, void FAR *out_desc)); +ZEXTERN int ZEXPORT inflateBack(z_streamp strm, + in_func in, void FAR *in_desc, + out_func out, void FAR *out_desc); /* inflateBack() does a raw inflate with a single call using a call-back interface for input and output. This is potentially more efficient than @@ -1163,7 +1205,7 @@ ZEXTERN int ZEXPORT inflateBack OF((z_streamp strm, cannot return Z_OK. */ -ZEXTERN int ZEXPORT inflateBackEnd OF((z_streamp strm)); +ZEXTERN int ZEXPORT inflateBackEnd(z_streamp strm); /* All memory allocated by inflateBackInit() is freed. @@ -1171,7 +1213,7 @@ ZEXTERN int ZEXPORT inflateBackEnd OF((z_streamp strm)); state was inconsistent. */ -ZEXTERN uLong ZEXPORT zlibCompileFlags OF((void)); +ZEXTERN uLong ZEXPORT zlibCompileFlags(void); /* Return flags indicating compile-time options. Type sizes, two bits each, 00 = 16 bits, 01 = 32, 10 = 64, 11 = other: @@ -1203,13 +1245,14 @@ ZEXTERN uLong ZEXPORT zlibCompileFlags OF((void)); 21: FASTEST -- deflate algorithm with only one, lowest compression level 22,23: 0 (reserved) - The sprintf variant used by gzprintf (zero is best): + The sprintf variant used by gzprintf (all zeros is best): 24: 0 = vs*, 1 = s* -- 1 means limited to 20 arguments after the format - 25: 0 = *nprintf, 1 = *printf -- 1 means gzprintf() not secure! + 25: 0 = *nprintf, 1 = *printf -- 1 means gzprintf() is not secure! 26: 0 = returns value, 1 = void -- 1 means inferred string length returned + 27: 0 = gzprintf() present, 1 = not -- 1 means gzprintf() returns an error Remainder: - 27-31: 0 (reserved) + 28-31: 0 (reserved) */ #ifndef Z_SOLO @@ -1221,11 +1264,14 @@ ZEXTERN uLong ZEXPORT zlibCompileFlags OF((void)); stream-oriented functions. To simplify the interface, some default options are assumed (compression level and memory usage, standard memory allocation functions). The source code of these utility functions can be modified if - you need special options. + you need special options. The _z versions of the functions use the size_t + type for lengths. Note that a long is 32 bits on Windows. */ -ZEXTERN int ZEXPORT compress OF((Bytef *dest, uLongf *destLen, - const Bytef *source, uLong sourceLen)); +ZEXTERN int ZEXPORT compress(Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen); +ZEXTERN int ZEXPORT compress_z(Bytef *dest, z_size_t *destLen, + const Bytef *source, z_size_t sourceLen); /* Compresses the source buffer into the destination buffer. sourceLen is the byte length of the source buffer. Upon entry, destLen is the total size @@ -1239,9 +1285,12 @@ ZEXTERN int ZEXPORT compress OF((Bytef *dest, uLongf *destLen, buffer. */ -ZEXTERN int ZEXPORT compress2 OF((Bytef *dest, uLongf *destLen, - const Bytef *source, uLong sourceLen, - int level)); +ZEXTERN int ZEXPORT compress2(Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen, + int level); +ZEXTERN int ZEXPORT compress2_z(Bytef *dest, z_size_t *destLen, + const Bytef *source, z_size_t sourceLen, + int level); /* Compresses the source buffer into the destination buffer. The level parameter has the same meaning as in deflateInit. sourceLen is the byte @@ -1255,22 +1304,25 @@ ZEXTERN int ZEXPORT compress2 OF((Bytef *dest, uLongf *destLen, Z_STREAM_ERROR if the level parameter is invalid. */ -ZEXTERN uLong ZEXPORT compressBound OF((uLong sourceLen)); +ZEXTERN uLong ZEXPORT compressBound(uLong sourceLen); +ZEXTERN z_size_t ZEXPORT compressBound_z(z_size_t sourceLen); /* compressBound() returns an upper bound on the compressed size after compress() or compress2() on sourceLen bytes. It would be used before a compress() or compress2() call to allocate the destination buffer. */ -ZEXTERN int ZEXPORT uncompress OF((Bytef *dest, uLongf *destLen, - const Bytef *source, uLong sourceLen)); +ZEXTERN int ZEXPORT uncompress(Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen); +ZEXTERN int ZEXPORT uncompress_z(Bytef *dest, z_size_t *destLen, + const Bytef *source, z_size_t sourceLen); /* Decompresses the source buffer into the destination buffer. sourceLen is - the byte length of the source buffer. Upon entry, destLen is the total size + the byte length of the source buffer. On entry, *destLen is the total size of the destination buffer, which must be large enough to hold the entire uncompressed data. (The size of the uncompressed data must have been saved previously by the compressor and transmitted to the decompressor by some - mechanism outside the scope of this compression library.) Upon exit, destLen + mechanism outside the scope of this compression library.) On exit, *destLen is the actual size of the uncompressed data. uncompress returns Z_OK if success, Z_MEM_ERROR if there was not @@ -1280,8 +1332,10 @@ ZEXTERN int ZEXPORT uncompress OF((Bytef *dest, uLongf *destLen, buffer with the uncompressed data up to that point. */ -ZEXTERN int ZEXPORT uncompress2 OF((Bytef *dest, uLongf *destLen, - const Bytef *source, uLong *sourceLen)); +ZEXTERN int ZEXPORT uncompress2(Bytef *dest, uLongf *destLen, + const Bytef *source, uLong *sourceLen); +ZEXTERN int ZEXPORT uncompress2_z(Bytef *dest, z_size_t *destLen, + const Bytef *source, z_size_t *sourceLen); /* Same as uncompress, except that sourceLen is a pointer, where the length of the source is *sourceLen. On return, *sourceLen is the number of @@ -1300,22 +1354,26 @@ ZEXTERN int ZEXPORT uncompress2 OF((Bytef *dest, uLongf *destLen, typedef struct gzFile_s *gzFile; /* semi-opaque gzip file descriptor */ /* -ZEXTERN gzFile ZEXPORT gzopen OF((const char *path, const char *mode)); +ZEXTERN gzFile ZEXPORT gzopen(const char *path, const char *mode); - Opens a gzip (.gz) file for reading or writing. The mode parameter is as - in fopen ("rb" or "wb") but can also include a compression level ("wb9") or - a strategy: 'f' for filtered data as in "wb6f", 'h' for Huffman-only - compression as in "wb1h", 'R' for run-length encoding as in "wb1R", or 'F' - for fixed code compression as in "wb9F". (See the description of - deflateInit2 for more information about the strategy parameter.) 'T' will - request transparent writing or appending with no compression and not using - the gzip format. + Open the gzip (.gz) file at path for reading and decompressing, or + compressing and writing. The mode parameter is as in fopen ("rb" or "wb") + but can also include a compression level ("wb9") or a strategy: 'f' for + filtered data as in "wb6f", 'h' for Huffman-only compression as in "wb1h", + 'R' for run-length encoding as in "wb1R", or 'F' for fixed code compression + as in "wb9F". (See the description of deflateInit2 for more information + about the strategy parameter.) 'T' will request transparent writing or + appending with no compression and not using the gzip format. 'T' cannot be + used to force transparent reading. Transparent reading is automatically + performed if there is no gzip header at the start. Transparent reading can + be disabled with the 'G' option, which will instead return an error if there + is no gzip header. 'N' will open the file in non-blocking mode. - "a" can be used instead of "w" to request that the gzip stream that will - be written be appended to the file. "+" will result in an error, since + 'a' can be used instead of 'w' to request that the gzip stream that will + be written be appended to the file. '+' will result in an error, since reading and writing to the same gzip file is not supported. The addition of - "x" when writing will create the file exclusively, which fails if the file - already exists. On systems that support it, the addition of "e" when + 'x' when writing will create the file exclusively, which fails if the file + already exists. On systems that support it, the addition of 'e' when reading or writing will set the flag to close the file on an execve() call. These functions, as well as gzip, will read and decode a sequence of gzip @@ -1334,14 +1392,22 @@ ZEXTERN gzFile ZEXPORT gzopen OF((const char *path, const char *mode)); insufficient memory to allocate the gzFile state, or if an invalid mode was specified (an 'r', 'w', or 'a' was not provided, or '+' was provided). errno can be checked to determine if the reason gzopen failed was that the - file could not be opened. + file could not be opened. Note that if 'N' is in mode for non-blocking, the + open() itself can fail in order to not block. In that case gzopen() will + return NULL and errno will be EAGAIN or ENONBLOCK. The call to gzopen() can + then be re-tried. If the application would like to block on opening the + file, then it can use open() without O_NONBLOCK, and then gzdopen() with the + resulting file descriptor and 'N' in the mode, which will set it to non- + blocking. */ -ZEXTERN gzFile ZEXPORT gzdopen OF((int fd, const char *mode)); +ZEXTERN gzFile ZEXPORT gzdopen(int fd, const char *mode); /* - gzdopen associates a gzFile with the file descriptor fd. File descriptors - are obtained from calls like open, dup, creat, pipe or fileno (if the file - has been previously opened with fopen). The mode parameter is as in gzopen. + Associate a gzFile with the file descriptor fd. File descriptors are + obtained from calls like open, dup, creat, pipe or fileno (if the file has + been previously opened with fopen). The mode parameter is as in gzopen. An + 'e' in mode will set fd's flag to close the file on an execve() call. An 'N' + in mode will set fd's non-blocking flag. The next call of gzclose on the returned gzFile will also close the file descriptor fd, just like fclose(fdopen(fd, mode)) closes the file descriptor @@ -1360,15 +1426,15 @@ ZEXTERN gzFile ZEXPORT gzdopen OF((int fd, const char *mode)); will not detect if fd is invalid (unless fd is -1). */ -ZEXTERN int ZEXPORT gzbuffer OF((gzFile file, unsigned size)); +ZEXTERN int ZEXPORT gzbuffer(gzFile file, unsigned size); /* - Set the internal buffer size used by this library's functions. The - default buffer size is 8192 bytes. This function must be called after - gzopen() or gzdopen(), and before any other calls that read or write the - file. The buffer memory allocation is always deferred to the first read or - write. Three times that size in buffer space is allocated. A larger buffer - size of, for example, 64K or 128K bytes will noticeably increase the speed - of decompression (reading). + Set the internal buffer size used by this library's functions for file to + size. The default buffer size is 8192 bytes. This function must be called + after gzopen() or gzdopen(), and before any other calls that read or write + the file. The buffer memory allocation is always deferred to the first read + or write. Three times that size in buffer space is allocated. A larger + buffer size of, for example, 64K or 128K bytes will noticeably increase the + speed of decompression (reading). The new buffer size also affects the maximum length for gzprintf(). @@ -1376,20 +1442,20 @@ ZEXTERN int ZEXPORT gzbuffer OF((gzFile file, unsigned size)); too late. */ -ZEXTERN int ZEXPORT gzsetparams OF((gzFile file, int level, int strategy)); +ZEXTERN int ZEXPORT gzsetparams(gzFile file, int level, int strategy); /* - Dynamically update the compression level or strategy. See the description - of deflateInit2 for the meaning of these parameters. Previously provided - data is flushed before the parameter change. + Dynamically update the compression level and strategy for file. See the + description of deflateInit2 for the meaning of these parameters. Previously + provided data is flushed before applying the parameter changes. gzsetparams returns Z_OK if success, Z_STREAM_ERROR if the file was not opened for writing, Z_ERRNO if there is an error writing the flushed data, or Z_MEM_ERROR if there is a memory allocation error. */ -ZEXTERN int ZEXPORT gzread OF((gzFile file, voidp buf, unsigned len)); +ZEXTERN int ZEXPORT gzread(gzFile file, voidp buf, unsigned len); /* - Reads the given number of uncompressed bytes from the compressed file. If + Read and decompress up to len uncompressed bytes from file into buf. If the input file is not in gzip format, gzread copies the given number of bytes into the buffer directly from the file. @@ -1411,20 +1477,26 @@ ZEXTERN int ZEXPORT gzread OF((gzFile file, voidp buf, unsigned len)); stream. Alternatively, gzerror can be used before gzclose to detect this case. + gzread can be used to read a gzip file on a non-blocking device. If the + input stalls and there is no uncompressed data to return, then gzread() will + return -1, and errno will be EAGAIN or EWOULDBLOCK. gzread() can then be + called again. + gzread returns the number of uncompressed bytes actually read, less than len for end of file, or -1 for error. If len is too large to fit in an int, then nothing is read, -1 is returned, and the error state is set to - Z_STREAM_ERROR. + Z_STREAM_ERROR. If some data was read before an error, then that data is + returned until exhausted, after which the next call will signal the error. */ -ZEXTERN z_size_t ZEXPORT gzfread OF((voidp buf, z_size_t size, z_size_t nitems, - gzFile file)); +ZEXTERN z_size_t ZEXPORT gzfread(voidp buf, z_size_t size, z_size_t nitems, + gzFile file); /* - Read up to nitems items of size size from file to buf, otherwise operating - as gzread() does. This duplicates the interface of stdio's fread(), with - size_t request and return types. If the library defines size_t, then - z_size_t is identical to size_t. If not, then z_size_t is an unsigned - integer type that can contain a pointer. + Read and decompress up to nitems items of size size from file into buf, + otherwise operating as gzread() does. This duplicates the interface of + stdio's fread(), with size_t request and return types. If the library + defines size_t, then z_size_t is identical to size_t. If not, then z_size_t + is an unsigned integer type that can contain a pointer. gzfread() returns the number of full items read of size size, or zero if the end of the file was reached and a full item could not be read, or if @@ -1435,26 +1507,29 @@ ZEXTERN z_size_t ZEXPORT gzfread OF((voidp buf, z_size_t size, z_size_t nitems, In the event that the end of file is reached and only a partial item is available at the end, i.e. the remaining uncompressed data length is not a - multiple of size, then the final partial item is nevetheless read into buf + multiple of size, then the final partial item is nevertheless read into buf and the end-of-file flag is set. The length of the partial item read is not provided, but could be inferred from the result of gztell(). This behavior - is the same as the behavior of fread() implementations in common libraries, - but it prevents the direct use of gzfread() to read a concurrently written - file, reseting and retrying on end-of-file, when size is not 1. + is the same as that of fread() implementations in common libraries. This + could result in data loss if used with size != 1 when reading a concurrently + written file or a non-blocking file. In that case, use size == 1 or gzread() + instead. */ -ZEXTERN int ZEXPORT gzwrite OF((gzFile file, - voidpc buf, unsigned len)); +ZEXTERN int ZEXPORT gzwrite(gzFile file, voidpc buf, unsigned len); /* - Writes the given number of uncompressed bytes into the compressed file. - gzwrite returns the number of uncompressed bytes written or 0 in case of - error. + Compress and write the len uncompressed bytes at buf to file. gzwrite + returns the number of uncompressed bytes written, or 0 in case of error or + if len is 0. If the write destination is non-blocking, then gzwrite() may + return a number of bytes written that is not 0 and less than len. + + If len does not fit in an int, then 0 is returned and nothing is written. */ -ZEXTERN z_size_t ZEXPORT gzfwrite OF((voidpc buf, z_size_t size, - z_size_t nitems, gzFile file)); +ZEXTERN z_size_t ZEXPORT gzfwrite(voidpc buf, z_size_t size, + z_size_t nitems, gzFile file); /* - gzfwrite() writes nitems items of size size from buf to file, duplicating + Compress and write nitems items of size size from buf to file, duplicating the interface of stdio's fwrite(), with size_t request and return types. If the library defines size_t, then z_size_t is identical to size_t. If not, then z_size_t is an unsigned integer type that can contain a pointer. @@ -1463,76 +1538,117 @@ ZEXTERN z_size_t ZEXPORT gzfwrite OF((voidpc buf, z_size_t size, if there was an error. If the multiplication of size and nitems overflows, i.e. the product does not fit in a z_size_t, then nothing is written, zero is returned, and the error state is set to Z_STREAM_ERROR. + + If writing a concurrently read file or a non-blocking file with size != 1, + a partial item could be written, with no way of knowing how much of it was + not written, resulting in data loss. In that case, use size == 1 or + gzwrite() instead. */ -ZEXTERN int ZEXPORTVA gzprintf Z_ARG((gzFile file, const char *format, ...)); +#if defined(STDC) || defined(Z_HAVE_STDARG_H) +ZEXTERN int ZEXPORTVA gzprintf(gzFile file, const char *format, ...); +#else +ZEXTERN int ZEXPORTVA gzprintf(); +#endif /* - Converts, formats, and writes the arguments to the compressed file under - control of the format string, as in fprintf. gzprintf returns the number of + Convert, format, compress, and write the arguments (...) to file under + control of the string format, as in fprintf. gzprintf returns the number of uncompressed bytes actually written, or a negative zlib error code in case of error. The number of uncompressed bytes written is limited to 8191, or one less than the buffer size given to gzbuffer(). The caller should assure that this limit is not exceeded. If it is exceeded, then gzprintf() will - return an error (0) with nothing written. In this case, there may also be a - buffer overflow with unpredictable consequences, which is possible only if - zlib was compiled with the insecure functions sprintf() or vsprintf() - because the secure snprintf() or vsnprintf() functions were not available. - This can be determined using zlibCompileFlags(). + return an error (0) with nothing written. + + In that last case, there may also be a buffer overflow with unpredictable + consequences, which is possible only if zlib was compiled with the insecure + functions sprintf() or vsprintf(), because the secure snprintf() and + vsnprintf() functions were not available. That would only be the case for + a non-ANSI C compiler. zlib may have been built without gzprintf() because + secure functions were not available and having gzprintf() be insecure was + not an option, in which case, gzprintf() returns Z_STREAM_ERROR. All of + these possibilities can be determined using zlibCompileFlags(). + + If a Z_BUF_ERROR is returned, then nothing was written due to a stall on + the non-blocking write destination. */ -ZEXTERN int ZEXPORT gzputs OF((gzFile file, const char *s)); +ZEXTERN int ZEXPORT gzputs(gzFile file, const char *s); /* - Writes the given null-terminated string to the compressed file, excluding + Compress and write the given null-terminated string s to file, excluding the terminating null character. gzputs returns the number of characters written, or -1 in case of error. + The number of characters written may be less than the length of the string + if the write destination is non-blocking. + + If the length of the string does not fit in an int, then -1 is returned + and nothing is written. */ -ZEXTERN char * ZEXPORT gzgets OF((gzFile file, char *buf, int len)); +ZEXTERN char * ZEXPORT gzgets(gzFile file, char *buf, int len); /* - Reads bytes from the compressed file until len-1 characters are read, or a - newline character is read and transferred to buf, or an end-of-file - condition is encountered. If any characters are read or if len == 1, the - string is terminated with a null character. If no characters are read due - to an end-of-file or len < 1, then the buffer is left untouched. + Read and decompress bytes from file into buf, until len-1 characters are + read, or until a newline character is read and transferred to buf, or an + end-of-file condition is encountered. If any characters are read or if len + is one, the string is terminated with a null character. If no characters + are read due to an end-of-file or len is less than one, then the buffer is + left untouched. gzgets returns buf which is a null-terminated string, or it returns NULL - for end-of-file or in case of error. If there was an error, the contents at - buf are indeterminate. + for end-of-file or in case of error. If some data was read before an error, + then that data is returned until exhausted, after which the next call will + return NULL to signal the error. + + gzgets can be used on a file being concurrently written, and on a non- + blocking device, both as for gzread(). However lines may be broken in the + middle, leaving it up to the application to reassemble them as needed. */ -ZEXTERN int ZEXPORT gzputc OF((gzFile file, int c)); +ZEXTERN int ZEXPORT gzputc(gzFile file, int c); /* - Writes c, converted to an unsigned char, into the compressed file. gzputc + Compress and write c, converted to an unsigned char, into file. gzputc returns the value that was written, or -1 in case of error. */ -ZEXTERN int ZEXPORT gzgetc OF((gzFile file)); +ZEXTERN int ZEXPORT gzgetc(gzFile file); /* - Reads one byte from the compressed file. gzgetc returns this byte or -1 - in case of end of file or error. This is implemented as a macro for speed. - As such, it does not do all of the checking the other functions do. I.e. - it does not check to see if file is NULL, nor whether the structure file - points to has been clobbered or not. + Read and decompress one byte from file. gzgetc returns this byte or -1 in + case of end of file or error. If some data was read before an error, then + that data is returned until exhausted, after which the next call will return + -1 to signal the error. + + This is implemented as a macro for speed. As such, it does not do all of + the checking the other functions do. I.e. it does not check to see if file + is NULL, nor whether the structure file points to has been clobbered or not. + + gzgetc can be used to read a gzip file on a non-blocking device. If the + input stalls and there is no uncompressed data to return, then gzgetc() will + return -1, and errno will be EAGAIN or EWOULDBLOCK. gzread() can then be + called again. */ -ZEXTERN int ZEXPORT gzungetc OF((int c, gzFile file)); +ZEXTERN int ZEXPORT gzungetc(int c, gzFile file); /* - Push one character back onto the stream to be read as the first character - on the next read. At least one character of push-back is allowed. + Push c back onto the stream for file to be read as the first character on + the next read. At least one character of push-back is always allowed. gzungetc() returns the character pushed, or -1 on failure. gzungetc() will fail if c is -1, and may fail if a character has been pushed but not read yet. If gzungetc is used immediately after gzopen or gzdopen, at least the output buffer size of pushed characters is allowed. (See gzbuffer above.) The pushed character will be discarded if the stream is repositioned with gzseek() or gzrewind(). + + gzungetc(-1, file) will force any pending seek to execute. Then gztell() + will report the position, even if the requested seek reached end of file. + This can be used to determine the number of uncompressed bytes in a gzip + file without having to read it into a buffer. */ -ZEXTERN int ZEXPORT gzflush OF((gzFile file, int flush)); +ZEXTERN int ZEXPORT gzflush(gzFile file, int flush); /* - Flushes all pending output into the compressed file. The parameter flush - is as in the deflate() function. The return value is the zlib error number - (see function gzerror below). gzflush is only permitted when writing. + Flush all pending output to file. The parameter flush is as in the + deflate() function. The return value is the zlib error number (see function + gzerror below). gzflush is only permitted when writing. If the flush parameter is Z_FINISH, the remaining data is written and the gzip stream is completed in the output. If gzwrite() is called again, a new @@ -1544,18 +1660,19 @@ ZEXTERN int ZEXPORT gzflush OF((gzFile file, int flush)); */ /* -ZEXTERN z_off_t ZEXPORT gzseek OF((gzFile file, - z_off_t offset, int whence)); +ZEXTERN z_off_t ZEXPORT gzseek(gzFile file, + z_off_t offset, int whence); - Sets the starting position for the next gzread or gzwrite on the given - compressed file. The offset represents a number of bytes in the + Set the starting position to offset relative to whence for the next gzread + or gzwrite on file. The offset represents a number of bytes in the uncompressed data stream. The whence parameter is defined as in lseek(2); the value SEEK_END is not supported. If the file is opened for reading, this function is emulated but can be extremely slow. If the file is opened for writing, only forward seeks are supported; gzseek then compresses a sequence of zeroes up to the new - starting position. + starting position. For reading or writing, any actual seeking is deferred + until the next read or write operation, or close operation when writing. gzseek returns the resulting offset location as measured in bytes from the beginning of the uncompressed stream, or -1 in case of error, in @@ -1563,52 +1680,52 @@ ZEXTERN z_off_t ZEXPORT gzseek OF((gzFile file, would be before the current position. */ -ZEXTERN int ZEXPORT gzrewind OF((gzFile file)); +ZEXTERN int ZEXPORT gzrewind(gzFile file); /* - Rewinds the given file. This function is supported only for reading. + Rewind file. This function is supported only for reading. - gzrewind(file) is equivalent to (int)gzseek(file, 0L, SEEK_SET) + gzrewind(file) is equivalent to (int)gzseek(file, 0L, SEEK_SET). */ /* -ZEXTERN z_off_t ZEXPORT gztell OF((gzFile file)); +ZEXTERN z_off_t ZEXPORT gztell(gzFile file); - Returns the starting position for the next gzread or gzwrite on the given - compressed file. This position represents a number of bytes in the - uncompressed data stream, and is zero when starting, even if appending or - reading a gzip stream from the middle of a file using gzdopen(). + Return the starting position for the next gzread or gzwrite on file. + This position represents a number of bytes in the uncompressed data stream, + and is zero when starting, even if appending or reading a gzip stream from + the middle of a file using gzdopen(). gztell(file) is equivalent to gzseek(file, 0L, SEEK_CUR) */ /* -ZEXTERN z_off_t ZEXPORT gzoffset OF((gzFile file)); +ZEXTERN z_off_t ZEXPORT gzoffset(gzFile file); - Returns the current offset in the file being read or written. This offset - includes the count of bytes that precede the gzip stream, for example when - appending or when using gzdopen() for reading. When reading, the offset - does not include as yet unused buffered input. This information can be used - for a progress indicator. On error, gzoffset() returns -1. + Return the current compressed (actual) read or write offset of file. This + offset includes the count of bytes that precede the gzip stream, for example + when appending or when using gzdopen() for reading. When reading, the + offset does not include as yet unused buffered input. This information can + be used for a progress indicator. On error, gzoffset() returns -1. */ -ZEXTERN int ZEXPORT gzeof OF((gzFile file)); +ZEXTERN int ZEXPORT gzeof(gzFile file); /* - Returns true (1) if the end-of-file indicator has been set while reading, - false (0) otherwise. Note that the end-of-file indicator is set only if the - read tried to go past the end of the input, but came up short. Therefore, - just like feof(), gzeof() may return false even if there is no more data to - read, in the event that the last read request was for the exact number of - bytes remaining in the input file. This will happen if the input file size - is an exact multiple of the buffer size. + Return true (1) if the end-of-file indicator for file has been set while + reading, false (0) otherwise. Note that the end-of-file indicator is set + only if the read tried to go past the end of the input, but came up short. + Therefore, just like feof(), gzeof() may return false even if there is no + more data to read, in the event that the last read request was for the exact + number of bytes remaining in the input file. This will happen if the input + file size is an exact multiple of the buffer size. If gzeof() returns true, then the read functions will return no more data, unless the end-of-file indicator is reset by gzclearerr() and the input file has grown since the previous end of file was detected. */ -ZEXTERN int ZEXPORT gzdirect OF((gzFile file)); +ZEXTERN int ZEXPORT gzdirect(gzFile file); /* - Returns true (1) if file is being copied directly while reading, or false + Return true (1) if file is being copied directly while reading, or false (0) if file is a gzip stream being decompressed. If the input file is empty, gzdirect() will return true, since the input @@ -1616,8 +1733,11 @@ ZEXTERN int ZEXPORT gzdirect OF((gzFile file)); If gzdirect() is used immediately after gzopen() or gzdopen() it will cause buffers to be allocated to allow reading the file to determine if it - is a gzip file. Therefore if gzbuffer() is used, it should be called before - gzdirect(). + is a gzip file. Therefore if gzbuffer() is used, it should be called before + gzdirect(). If the input is being written concurrently or the device is non- + blocking, then gzdirect() may give a different answer once four bytes of + input have been accumulated, which is what is needed to confirm or deny a + gzip header. Before this, gzdirect() will return true (1). When writing, gzdirect() returns true (1) if transparent writing was requested ("wT" for the gzopen() mode), or false (0) otherwise. (Note: @@ -1627,10 +1747,10 @@ ZEXTERN int ZEXPORT gzdirect OF((gzFile file)); gzip file reading and decompression, which may not be desired.) */ -ZEXTERN int ZEXPORT gzclose OF((gzFile file)); +ZEXTERN int ZEXPORT gzclose(gzFile file); /* - Flushes all pending output if necessary, closes the compressed file and - deallocates the (de)compression state. Note that once file is closed, you + Flush all pending output for file, if necessary, close file and + deallocate the (de)compression state. Note that once file is closed, you cannot call gzerror with file, since its structures have been deallocated. gzclose must not be called more than once on the same file, just as free must not be called more than once on the same allocation. @@ -1640,8 +1760,8 @@ ZEXTERN int ZEXPORT gzclose OF((gzFile file)); last read ended in the middle of a gzip stream, or Z_OK on success. */ -ZEXTERN int ZEXPORT gzclose_r OF((gzFile file)); -ZEXTERN int ZEXPORT gzclose_w OF((gzFile file)); +ZEXTERN int ZEXPORT gzclose_r(gzFile file); +ZEXTERN int ZEXPORT gzclose_w(gzFile file); /* Same as gzclose(), but gzclose_r() is only for use when reading, and gzclose_w() is only for use when writing or appending. The advantage to @@ -1652,12 +1772,13 @@ ZEXTERN int ZEXPORT gzclose_w OF((gzFile file)); zlib library. */ -ZEXTERN const char * ZEXPORT gzerror OF((gzFile file, int *errnum)); +ZEXTERN const char * ZEXPORT gzerror(gzFile file, int *errnum); /* - Returns the error message for the last error which occurred on the given - compressed file. errnum is set to zlib error number. If an error occurred - in the file system and not in the compression library, errnum is set to - Z_ERRNO and the application may consult errno to get the exact error code. + Return the error message for the last error which occurred on file. + If errnum is not NULL, *errnum is set to zlib error number. If an error + occurred in the file system and not in the compression library, *errnum is + set to Z_ERRNO and the application may consult errno to get the exact error + code. The application must not modify the returned string. Future calls to this function may invalidate the previously returned string. If file is @@ -1668,9 +1789,9 @@ ZEXTERN const char * ZEXPORT gzerror OF((gzFile file, int *errnum)); functions above that do not distinguish those cases in their return values. */ -ZEXTERN void ZEXPORT gzclearerr OF((gzFile file)); +ZEXTERN void ZEXPORT gzclearerr(gzFile file); /* - Clears the error and end-of-file flags for file. This is analogous to the + Clear the error and end-of-file flags for file. This is analogous to the clearerr() function in stdio. This is useful for continuing to read a gzip file that is being written concurrently. */ @@ -1685,11 +1806,12 @@ ZEXTERN void ZEXPORT gzclearerr OF((gzFile file)); library. */ -ZEXTERN uLong ZEXPORT adler32 OF((uLong adler, const Bytef *buf, uInt len)); +ZEXTERN uLong ZEXPORT adler32(uLong adler, const Bytef *buf, uInt len); /* Update a running Adler-32 checksum with the bytes buf[0..len-1] and - return the updated checksum. If buf is Z_NULL, this function returns the - required initial value for the checksum. + return the updated checksum. An Adler-32 value is in the range of a 32-bit + unsigned integer. If buf is Z_NULL, this function returns the required + initial value for the checksum. An Adler-32 checksum is almost as reliable as a CRC-32 but can be computed much faster. @@ -1704,15 +1826,16 @@ ZEXTERN uLong ZEXPORT adler32 OF((uLong adler, const Bytef *buf, uInt len)); if (adler != original_adler) error(); */ -ZEXTERN uLong ZEXPORT adler32_z OF((uLong adler, const Bytef *buf, - z_size_t len)); +ZEXTERN uLong ZEXPORT adler32_z(uLong adler, const Bytef *buf, + z_size_t len); /* - Same as adler32(), but with a size_t length. + Same as adler32(), but with a size_t length. Note that a long is 32 bits + on Windows. */ /* -ZEXTERN uLong ZEXPORT adler32_combine OF((uLong adler1, uLong adler2, - z_off_t len2)); +ZEXTERN uLong ZEXPORT adler32_combine(uLong adler1, uLong adler2, + z_off_t len2); Combine two Adler-32 checksums into one. For two sequences of bytes, seq1 and seq2 with lengths len1 and len2, Adler-32 checksums were calculated for @@ -1722,12 +1845,13 @@ ZEXTERN uLong ZEXPORT adler32_combine OF((uLong adler1, uLong adler2, negative, the result has no meaning or utility. */ -ZEXTERN uLong ZEXPORT crc32 OF((uLong crc, const Bytef *buf, uInt len)); +ZEXTERN uLong ZEXPORT crc32(uLong crc, const Bytef *buf, uInt len); /* Update a running CRC-32 with the bytes buf[0..len-1] and return the - updated CRC-32. If buf is Z_NULL, this function returns the required - initial value for the crc. Pre- and post-conditioning (one's complement) is - performed within this function so it shouldn't be done by the application. + updated CRC-32. A CRC-32 value is in the range of a 32-bit unsigned integer. + If buf is Z_NULL, this function returns the required initial value for the + crc. Pre- and post-conditioning (one's complement) is performed within this + function so it shouldn't be done by the application. Usage example: @@ -1739,20 +1863,35 @@ ZEXTERN uLong ZEXPORT crc32 OF((uLong crc, const Bytef *buf, uInt len)); if (crc != original_crc) error(); */ -ZEXTERN uLong ZEXPORT crc32_z OF((uLong adler, const Bytef *buf, - z_size_t len)); +ZEXTERN uLong ZEXPORT crc32_z(uLong crc, const Bytef *buf, + z_size_t len); /* - Same as crc32(), but with a size_t length. + Same as crc32(), but with a size_t length. Note that a long is 32 bits on + Windows. */ /* -ZEXTERN uLong ZEXPORT crc32_combine OF((uLong crc1, uLong crc2, z_off_t len2)); +ZEXTERN uLong ZEXPORT crc32_combine(uLong crc1, uLong crc2, z_off_t len2); Combine two CRC-32 check values into one. For two sequences of bytes, seq1 and seq2 with lengths len1 and len2, CRC-32 check values were calculated for each, crc1 and crc2. crc32_combine() returns the CRC-32 check value of seq1 and seq2 concatenated, requiring only crc1, crc2, and - len2. + len2. len2 must be non-negative, otherwise zero is returned. +*/ + +/* +ZEXTERN uLong ZEXPORT crc32_combine_gen(z_off_t len2); + + Return the operator corresponding to length len2, to be used with + crc32_combine_op(). len2 must be non-negative, otherwise zero is returned. +*/ + +ZEXTERN uLong ZEXPORT crc32_combine_op(uLong crc1, uLong crc2, uLong op); +/* + Give the same result as crc32_combine(), using op in place of len2. op is + is generated from len2 by crc32_combine_gen(). This will be faster than + crc32_combine() if the generated op is used more than once. */ @@ -1761,20 +1900,20 @@ ZEXTERN uLong ZEXPORT crc32_combine OF((uLong crc1, uLong crc2, z_off_t len2)); /* deflateInit and inflateInit are macros to allow checking the zlib version * and the compiler's view of z_stream: */ -ZEXTERN int ZEXPORT deflateInit_ OF((z_streamp strm, int level, - const char *version, int stream_size)); -ZEXTERN int ZEXPORT inflateInit_ OF((z_streamp strm, - const char *version, int stream_size)); -ZEXTERN int ZEXPORT deflateInit2_ OF((z_streamp strm, int level, int method, - int windowBits, int memLevel, - int strategy, const char *version, - int stream_size)); -ZEXTERN int ZEXPORT inflateInit2_ OF((z_streamp strm, int windowBits, - const char *version, int stream_size)); -ZEXTERN int ZEXPORT inflateBackInit_ OF((z_streamp strm, int windowBits, - unsigned char FAR *window, - const char *version, - int stream_size)); +ZEXTERN int ZEXPORT deflateInit_(z_streamp strm, int level, + const char *version, int stream_size); +ZEXTERN int ZEXPORT inflateInit_(z_streamp strm, + const char *version, int stream_size); +ZEXTERN int ZEXPORT deflateInit2_(z_streamp strm, int level, int method, + int windowBits, int memLevel, + int strategy, const char *version, + int stream_size); +ZEXTERN int ZEXPORT inflateInit2_(z_streamp strm, int windowBits, + const char *version, int stream_size); +ZEXTERN int ZEXPORT inflateBackInit_(z_streamp strm, int windowBits, + unsigned char FAR *window, + const char *version, + int stream_size); #ifdef Z_PREFIX_SET # define z_deflateInit(strm, level) \ deflateInit_((strm), (level), ZLIB_VERSION, (int)sizeof(z_stream)) @@ -1819,7 +1958,7 @@ struct gzFile_s { unsigned char *next; z_off64_t pos; }; -ZEXTERN int ZEXPORT gzgetc_ OF((gzFile file)); /* backward compatibility */ +ZEXTERN int ZEXPORT gzgetc_(gzFile file); /* backward compatibility */ #ifdef Z_PREFIX_SET # undef z_gzgetc # define z_gzgetc(g) \ @@ -1836,12 +1975,13 @@ ZEXTERN int ZEXPORT gzgetc_ OF((gzFile file)); /* backward compatibility */ * without large file support, _LFS64_LARGEFILE must also be true */ #ifdef Z_LARGE64 - ZEXTERN gzFile ZEXPORT gzopen64 OF((const char *, const char *)); - ZEXTERN z_off64_t ZEXPORT gzseek64 OF((gzFile, z_off64_t, int)); - ZEXTERN z_off64_t ZEXPORT gztell64 OF((gzFile)); - ZEXTERN z_off64_t ZEXPORT gzoffset64 OF((gzFile)); - ZEXTERN uLong ZEXPORT adler32_combine64 OF((uLong, uLong, z_off64_t)); - ZEXTERN uLong ZEXPORT crc32_combine64 OF((uLong, uLong, z_off64_t)); + ZEXTERN gzFile ZEXPORT gzopen64(const char *, const char *); + ZEXTERN z_off64_t ZEXPORT gzseek64(gzFile, z_off64_t, int); + ZEXTERN z_off64_t ZEXPORT gztell64(gzFile); + ZEXTERN z_off64_t ZEXPORT gzoffset64(gzFile); + ZEXTERN uLong ZEXPORT adler32_combine64(uLong, uLong, z_off64_t); + ZEXTERN uLong ZEXPORT crc32_combine64(uLong, uLong, z_off64_t); + ZEXTERN uLong ZEXPORT crc32_combine_gen64(z_off64_t); #endif #if !defined(ZLIB_INTERNAL) && defined(Z_WANT64) @@ -1852,6 +1992,7 @@ ZEXTERN int ZEXPORT gzgetc_ OF((gzFile file)); /* backward compatibility */ # define z_gzoffset z_gzoffset64 # define z_adler32_combine z_adler32_combine64 # define z_crc32_combine z_crc32_combine64 +# define z_crc32_combine_gen z_crc32_combine_gen64 # else # define gzopen gzopen64 # define gzseek gzseek64 @@ -1859,49 +2000,53 @@ ZEXTERN int ZEXPORT gzgetc_ OF((gzFile file)); /* backward compatibility */ # define gzoffset gzoffset64 # define adler32_combine adler32_combine64 # define crc32_combine crc32_combine64 +# define crc32_combine_gen crc32_combine_gen64 # endif # ifndef Z_LARGE64 - ZEXTERN gzFile ZEXPORT gzopen64 OF((const char *, const char *)); - ZEXTERN z_off_t ZEXPORT gzseek64 OF((gzFile, z_off_t, int)); - ZEXTERN z_off_t ZEXPORT gztell64 OF((gzFile)); - ZEXTERN z_off_t ZEXPORT gzoffset64 OF((gzFile)); - ZEXTERN uLong ZEXPORT adler32_combine64 OF((uLong, uLong, z_off_t)); - ZEXTERN uLong ZEXPORT crc32_combine64 OF((uLong, uLong, z_off_t)); + ZEXTERN gzFile ZEXPORT gzopen64(const char *, const char *); + ZEXTERN z_off_t ZEXPORT gzseek64(gzFile, z_off_t, int); + ZEXTERN z_off_t ZEXPORT gztell64(gzFile); + ZEXTERN z_off_t ZEXPORT gzoffset64(gzFile); + ZEXTERN uLong ZEXPORT adler32_combine64(uLong, uLong, z_off64_t); + ZEXTERN uLong ZEXPORT crc32_combine64(uLong, uLong, z_off64_t); + ZEXTERN uLong ZEXPORT crc32_combine_gen64(z_off64_t); # endif #else - ZEXTERN gzFile ZEXPORT gzopen OF((const char *, const char *)); - ZEXTERN z_off_t ZEXPORT gzseek OF((gzFile, z_off_t, int)); - ZEXTERN z_off_t ZEXPORT gztell OF((gzFile)); - ZEXTERN z_off_t ZEXPORT gzoffset OF((gzFile)); - ZEXTERN uLong ZEXPORT adler32_combine OF((uLong, uLong, z_off_t)); - ZEXTERN uLong ZEXPORT crc32_combine OF((uLong, uLong, z_off_t)); + ZEXTERN gzFile ZEXPORT gzopen(const char *, const char *); + ZEXTERN z_off_t ZEXPORT gzseek(gzFile, z_off_t, int); + ZEXTERN z_off_t ZEXPORT gztell(gzFile); + ZEXTERN z_off_t ZEXPORT gzoffset(gzFile); + ZEXTERN uLong ZEXPORT adler32_combine(uLong, uLong, z_off_t); + ZEXTERN uLong ZEXPORT crc32_combine(uLong, uLong, z_off_t); + ZEXTERN uLong ZEXPORT crc32_combine_gen(z_off_t); #endif #else /* Z_SOLO */ - ZEXTERN uLong ZEXPORT adler32_combine OF((uLong, uLong, z_off_t)); - ZEXTERN uLong ZEXPORT crc32_combine OF((uLong, uLong, z_off_t)); + ZEXTERN uLong ZEXPORT adler32_combine(uLong, uLong, z_off_t); + ZEXTERN uLong ZEXPORT crc32_combine(uLong, uLong, z_off_t); + ZEXTERN uLong ZEXPORT crc32_combine_gen(z_off_t); #endif /* !Z_SOLO */ /* undocumented functions */ -ZEXTERN const char * ZEXPORT zError OF((int)); -ZEXTERN int ZEXPORT inflateSyncPoint OF((z_streamp)); -ZEXTERN const z_crc_t FAR * ZEXPORT get_crc_table OF((void)); -ZEXTERN int ZEXPORT inflateUndermine OF((z_streamp, int)); -ZEXTERN int ZEXPORT inflateValidate OF((z_streamp, int)); -ZEXTERN unsigned long ZEXPORT inflateCodesUsed OF ((z_streamp)); -ZEXTERN int ZEXPORT inflateResetKeep OF((z_streamp)); -ZEXTERN int ZEXPORT deflateResetKeep OF((z_streamp)); -#if (defined(_WIN32) || defined(__CYGWIN__)) && !defined(Z_SOLO) -ZEXTERN gzFile ZEXPORT gzopen_w OF((const wchar_t *path, - const char *mode)); +ZEXTERN const char * ZEXPORT zError(int); +ZEXTERN int ZEXPORT inflateSyncPoint(z_streamp); +ZEXTERN const z_crc_t FAR * ZEXPORT get_crc_table(void); +ZEXTERN int ZEXPORT inflateUndermine(z_streamp, int); +ZEXTERN int ZEXPORT inflateValidate(z_streamp, int); +ZEXTERN unsigned long ZEXPORT inflateCodesUsed(z_streamp); +ZEXTERN int ZEXPORT inflateResetKeep(z_streamp); +ZEXTERN int ZEXPORT deflateResetKeep(z_streamp); +#if defined(_WIN32) && !defined(Z_SOLO) +ZEXTERN gzFile ZEXPORT gzopen_w(const wchar_t *path, + const char *mode); #endif #if defined(STDC) || defined(Z_HAVE_STDARG_H) # ifndef Z_SOLO -ZEXTERN int ZEXPORTVA gzvprintf Z_ARG((gzFile file, - const char *format, - va_list va)); +ZEXTERN int ZEXPORTVA gzvprintf(gzFile file, + const char *format, + va_list va); # endif #endif diff --git a/src/common/dep/zlib/zutil.c b/src/common/dep/zlib/zutil.c index a76c6b0c..4ea02a9c 100644 --- a/src/common/dep/zlib/zutil.c +++ b/src/common/dep/zlib/zutil.c @@ -1,5 +1,5 @@ /* zutil.c -- target dependent utility functions for the compression library - * Copyright (C) 1995-2017 Jean-loup Gailly + * Copyright (C) 1995-2026 Jean-loup Gailly * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -24,13 +24,11 @@ z_const char * const z_errmsg[10] = { }; -const char * ZEXPORT zlibVersion() -{ +const char * ZEXPORT zlibVersion(void) { return ZLIB_VERSION; } -uLong ZEXPORT zlibCompileFlags() -{ +uLong ZEXPORT zlibCompileFlags(void) { uLong flags; flags = 0; @@ -61,9 +59,11 @@ uLong ZEXPORT zlibCompileFlags() #ifdef ZLIB_DEBUG flags += 1 << 8; #endif + /* #if defined(ASMV) || defined(ASMINF) flags += 1 << 9; #endif + */ #ifdef ZLIB_WINAPI flags += 1 << 10; #endif @@ -86,28 +86,36 @@ uLong ZEXPORT zlibCompileFlags() flags += 1L << 21; #endif #if defined(STDC) || defined(Z_HAVE_STDARG_H) -# ifdef NO_vsnprintf - flags += 1L << 25; -# ifdef HAS_vsprintf_void - flags += 1L << 26; -# endif -# else -# ifdef HAS_vsnprintf_void - flags += 1L << 26; -# endif -# endif +# ifdef NO_vsnprintf +# ifdef ZLIB_INSECURE + flags += 1L << 25; +# else + flags += 1L << 27; +# endif +# ifdef HAS_vsprintf_void + flags += 1L << 26; +# endif +# else +# ifdef HAS_vsnprintf_void + flags += 1L << 26; +# endif +# endif #else flags += 1L << 24; -# ifdef NO_snprintf - flags += 1L << 25; -# ifdef HAS_sprintf_void - flags += 1L << 26; -# endif -# else -# ifdef HAS_snprintf_void - flags += 1L << 26; -# endif -# endif +# ifdef NO_snprintf +# ifdef ZLIB_INSECURE + flags += 1L << 25; +# else + flags += 1L << 27; +# endif +# ifdef HAS_sprintf_void + flags += 1L << 26; +# endif +# else +# ifdef HAS_snprintf_void + flags += 1L << 26; +# endif +# endif #endif return flags; } @@ -119,9 +127,7 @@ uLong ZEXPORT zlibCompileFlags() # endif int ZLIB_INTERNAL z_verbose = verbose; -void ZLIB_INTERNAL z_error (m) - char *m; -{ +void ZLIB_INTERNAL z_error(char *m) { fprintf(stderr, "%s\n", m); exit(1); } @@ -130,14 +136,12 @@ void ZLIB_INTERNAL z_error (m) /* exported to allow conversion of error code to string for compress() and * uncompress() */ -const char * ZEXPORT zError(err) - int err; -{ +const char * ZEXPORT zError(int err) { return ERR_MSG(err); } -#if defined(_WIN32_WCE) - /* The Microsoft C Run-Time Library for Windows CE doesn't have +#if defined(_WIN32_WCE) && _WIN32_WCE < 0x800 + /* The older Microsoft C Run-Time Library for Windows CE doesn't have * errno. We define it as a global variable to simplify porting. * Its value is always 0 and should not be used. */ @@ -146,39 +150,34 @@ const char * ZEXPORT zError(err) #ifndef HAVE_MEMCPY -void ZLIB_INTERNAL zmemcpy(dest, source, len) - Bytef* dest; - const Bytef* source; - uInt len; -{ - if (len == 0) return; - do { - *dest++ = *source++; /* ??? to be unrolled */ - } while (--len != 0); +void ZLIB_INTERNAL zmemcpy(void FAR *dst, const void FAR *src, z_size_t n) { + uchf *p = dst; + const uchf *q = src; + while (n) { + *p++ = *q++; + n--; + } } -int ZLIB_INTERNAL zmemcmp(s1, s2, len) - const Bytef* s1; - const Bytef* s2; - uInt len; -{ - uInt j; - - for (j = 0; j < len; j++) { - if (s1[j] != s2[j]) return 2*(s1[j] > s2[j])-1; +int ZLIB_INTERNAL zmemcmp(const void FAR *s1, const void FAR *s2, z_size_t n) { + const uchf *p = s1, *q = s2; + while (n) { + if (*p++ != *q++) + return (int)p[-1] - (int)q[-1]; + n--; } return 0; } -void ZLIB_INTERNAL zmemzero(dest, len) - Bytef* dest; - uInt len; -{ +void ZLIB_INTERNAL zmemzero(void FAR *b, z_size_t len) { + uchf *p = b; if (len == 0) return; - do { - *dest++ = 0; /* ??? to be unrolled */ - } while (--len != 0); + while (len) { + *p++ = 0; + len--; + } } + #endif #ifndef Z_SOLO @@ -214,8 +213,7 @@ local ptr_table table[MAX_PTR]; * a protected system like OS/2. Use Microsoft C instead. */ -voidpf ZLIB_INTERNAL zcalloc (voidpf opaque, unsigned items, unsigned size) -{ +voidpf ZLIB_INTERNAL zcalloc(voidpf opaque, unsigned items, unsigned size) { voidpf buf; ulg bsize = (ulg)items*size; @@ -240,8 +238,7 @@ voidpf ZLIB_INTERNAL zcalloc (voidpf opaque, unsigned items, unsigned size) return buf; } -void ZLIB_INTERNAL zcfree (voidpf opaque, voidpf ptr) -{ +void ZLIB_INTERNAL zcfree(voidpf opaque, voidpf ptr) { int n; (void)opaque; @@ -277,14 +274,12 @@ void ZLIB_INTERNAL zcfree (voidpf opaque, voidpf ptr) # define _hfree hfree #endif -voidpf ZLIB_INTERNAL zcalloc (voidpf opaque, uInt items, uInt size) -{ +voidpf ZLIB_INTERNAL zcalloc(voidpf opaque, uInt items, uInt size) { (void)opaque; return _halloc((long)items, size); } -void ZLIB_INTERNAL zcfree (voidpf opaque, voidpf ptr) -{ +void ZLIB_INTERNAL zcfree(voidpf opaque, voidpf ptr) { (void)opaque; _hfree(ptr); } @@ -297,25 +292,18 @@ void ZLIB_INTERNAL zcfree (voidpf opaque, voidpf ptr) #ifndef MY_ZCALLOC /* Any system without a special alloc function */ #ifndef STDC -extern voidp malloc OF((uInt size)); -extern voidp calloc OF((uInt items, uInt size)); -extern void free OF((voidpf ptr)); +extern voidp malloc(uInt size); +extern voidp calloc(uInt items, uInt size); +extern void free(voidpf ptr); #endif -voidpf ZLIB_INTERNAL zcalloc (opaque, items, size) - voidpf opaque; - unsigned items; - unsigned size; -{ +voidpf ZLIB_INTERNAL zcalloc(voidpf opaque, unsigned items, unsigned size) { (void)opaque; return sizeof(uInt) > 2 ? (voidpf)malloc(items * size) : (voidpf)calloc(items, size); } -void ZLIB_INTERNAL zcfree (opaque, ptr) - voidpf opaque; - voidpf ptr; -{ +void ZLIB_INTERNAL zcfree(voidpf opaque, voidpf ptr) { (void)opaque; free(ptr); } diff --git a/src/common/dep/zlib/zutil.h b/src/common/dep/zlib/zutil.h index b079ea6a..a9bc23ca 100644 --- a/src/common/dep/zlib/zutil.h +++ b/src/common/dep/zlib/zutil.h @@ -1,5 +1,5 @@ /* zutil.h -- internal interface and configuration of the compression library - * Copyright (C) 1995-2016 Jean-loup Gailly, Mark Adler + * Copyright (C) 1995-2026 Jean-loup Gailly, Mark Adler * For conditions of distribution and use, see copyright notice in zlib.h */ @@ -29,10 +29,6 @@ # include #endif -#ifdef Z_SOLO - typedef long ptrdiff_t; /* guess -- will be caught if guess is wrong */ -#endif - #ifndef local # define local static #endif @@ -40,23 +36,42 @@ define "local" for the non-static meaning of "static", for readability (compile with -Dlocal if your debugger can't find static symbols) */ +extern const char deflate_copyright[]; +extern const char inflate_copyright[]; +extern const char inflate9_copyright[]; + typedef unsigned char uch; typedef uch FAR uchf; typedef unsigned short ush; typedef ush FAR ushf; typedef unsigned long ulg; +#if !defined(Z_U8) && !defined(Z_SOLO) && defined(STDC) +# include +# if (ULONG_MAX == 0xffffffffffffffff) +# define Z_U8 unsigned long +# elif (ULLONG_MAX == 0xffffffffffffffff) +# define Z_U8 unsigned long long +# elif (ULONG_LONG_MAX == 0xffffffffffffffff) +# define Z_U8 unsigned long long +# elif (UINT_MAX == 0xffffffffffffffff) +# define Z_U8 unsigned +# endif +#endif + extern z_const char * const z_errmsg[10]; /* indexed by 2-zlib_error */ /* (size given to avoid silly warnings with Visual C++) */ -#define ERR_MSG(err) z_errmsg[Z_NEED_DICT-(err)] +#define ERR_MSG(err) z_errmsg[(err) < -6 || (err) > 2 ? 9 : 2 - (err)] #define ERR_RETURN(strm,err) \ return (strm->msg = ERR_MSG(err), (err)) /* To be used only when the state is known to be valid */ /* common constants */ - +#if MAX_WBITS < 9 || MAX_WBITS > 15 +# error MAX_WBITS must be in 9..15 +#endif #ifndef DEF_WBITS # define DEF_WBITS MAX_WBITS #endif @@ -130,20 +145,11 @@ extern z_const char * const z_errmsg[10]; /* indexed by 2-zlib_error */ # endif #endif -#if defined(MACOS) || defined(TARGET_OS_MAC) +#if defined(MACOS) # define OS_CODE 7 -# ifndef Z_SOLO -# if defined(__MWERKS__) && __dest_os != __be_os && __dest_os != __win32_os -# include /* for fdopen */ -# else -# ifndef fdopen -# define fdopen(fd,mode) NULL /* No fdopen() */ -# endif -# endif -# endif #endif -#ifdef __acorn +#if defined(__acorn) || defined(__riscos) # define OS_CODE 13 #endif @@ -163,22 +169,6 @@ extern z_const char * const z_errmsg[10]; /* indexed by 2-zlib_error */ # define OS_CODE 19 #endif -#if defined(_BEOS_) || defined(RISCOS) -# define fdopen(fd,mode) NULL /* No fdopen() */ -#endif - -#if (defined(_MSC_VER) && (_MSC_VER > 600)) && !defined __INTERIX -# if defined(_WIN32_WCE) -# define fdopen(fd,mode) NULL /* No fdopen() */ -# ifndef _PTRDIFF_T_DEFINED - typedef int ptrdiff_t; -# define _PTRDIFF_T_DEFINED -# endif -# else -# define fdopen(fd,type) _fdopen(fd,type) -# endif -#endif - #if defined(__BORLANDC__) && !defined(MSDOS) #pragma warn -8004 #pragma warn -8008 @@ -186,10 +176,10 @@ extern z_const char * const z_errmsg[10]; /* indexed by 2-zlib_error */ #endif /* provide prototypes for these when building zlib without LFS */ -#if !defined(_WIN32) && \ - (!defined(_LARGEFILE64_SOURCE) || _LFS64_LARGEFILE-0 == 0) - ZEXTERN uLong ZEXPORT adler32_combine64 OF((uLong, uLong, z_off_t)); - ZEXTERN uLong ZEXPORT crc32_combine64 OF((uLong, uLong, z_off_t)); +#ifndef Z_LARGE64 + ZEXTERN uLong ZEXPORT adler32_combine64(uLong, uLong, z_off64_t); + ZEXTERN uLong ZEXPORT crc32_combine64(uLong, uLong, z_off64_t); + ZEXTERN uLong ZEXPORT crc32_combine_gen64(z_off64_t); #endif /* common defaults */ @@ -228,16 +218,16 @@ extern z_const char * const z_errmsg[10]; /* indexed by 2-zlib_error */ # define zmemzero(dest, len) memset(dest, 0, len) # endif #else - void ZLIB_INTERNAL zmemcpy OF((Bytef* dest, const Bytef* source, uInt len)); - int ZLIB_INTERNAL zmemcmp OF((const Bytef* s1, const Bytef* s2, uInt len)); - void ZLIB_INTERNAL zmemzero OF((Bytef* dest, uInt len)); + void ZLIB_INTERNAL zmemcpy(void FAR *, const void FAR *, z_size_t); + int ZLIB_INTERNAL zmemcmp(const void FAR *, const void FAR *, z_size_t); + void ZLIB_INTERNAL zmemzero(void FAR *, z_size_t); #endif /* Diagnostic functions */ #ifdef ZLIB_DEBUG # include extern int ZLIB_INTERNAL z_verbose; - extern void ZLIB_INTERNAL z_error OF((char *m)); + extern void ZLIB_INTERNAL z_error(char *m); # define Assert(cond,msg) {if(!(cond)) z_error(msg);} # define Trace(x) {if (z_verbose>=0) fprintf x ;} # define Tracev(x) {if (z_verbose>0) fprintf x ;} @@ -254,9 +244,9 @@ extern z_const char * const z_errmsg[10]; /* indexed by 2-zlib_error */ #endif #ifndef Z_SOLO - voidpf ZLIB_INTERNAL zcalloc OF((voidpf opaque, unsigned items, - unsigned size)); - void ZLIB_INTERNAL zcfree OF((voidpf opaque, voidpf ptr)); + voidpf ZLIB_INTERNAL zcalloc(voidpf opaque, unsigned items, + unsigned size); + void ZLIB_INTERNAL zcfree(voidpf opaque, voidpf ptr); #endif #define ZALLOC(strm, items, size) \ @@ -268,4 +258,74 @@ extern z_const char * const z_errmsg[10]; /* indexed by 2-zlib_error */ #define ZSWAP32(q) ((((q) >> 24) & 0xff) + (((q) >> 8) & 0xff00) + \ (((q) & 0xff00) << 8) + (((q) & 0xff) << 24)) +#ifdef Z_ONCE +/* + Create a local z_once() function depending on the availability of atomics. + */ + +/* Check for the availability of atomics. */ +#if defined(__STDC__) && __STDC_VERSION__ >= 201112L && \ + !defined(__STDC_NO_ATOMICS__) + +#include +typedef struct { + atomic_flag begun; + atomic_int done; +} z_once_t; +#define Z_ONCE_INIT {ATOMIC_FLAG_INIT, 0} + +/* + Run the provided init() function exactly once, even if multiple threads + invoke once() at the same time. The state must be a once_t initialized with + Z_ONCE_INIT. + */ +local void z_once(z_once_t *state, void (*init)(void)) { + if (!atomic_load(&state->done)) { + if (atomic_flag_test_and_set(&state->begun)) + while (!atomic_load(&state->done)) + ; + else { + init(); + atomic_store(&state->done, 1); + } + } +} + +#else /* no atomics */ + +#warning zlib not thread-safe + +typedef struct z_once_s { + volatile int begun; + volatile int done; +} z_once_t; +#define Z_ONCE_INIT {0, 0} + +/* Test and set. Alas, not atomic, but tries to limit the period of + vulnerability. */ +local int test_and_set(int volatile *flag) { + int was; + + was = *flag; + *flag = 1; + return was; +} + +/* Run the provided init() function once. This is not thread-safe. */ +local void z_once(z_once_t *state, void (*init)(void)) { + if (!state->done) { + if (test_and_set(&state->begun)) + while (!state->done) + ; + else { + init(); + state->done = 1; + } + } +} + +#endif /* ?atomics */ + +#endif /* Z_ONCE */ + #endif /* ZUTIL_H */ diff --git a/src/common/handles.cpp b/src/common/handles.cpp index 7b158109..2954fef5 100644 --- a/src/common/handles.cpp +++ b/src/common/handles.cpp @@ -16,24 +16,6 @@ #ifdef HANDLES_ENABLE -static WCHAR* Utf8AllocWideHandles(const char* src) -{ - if (src == NULL) - return NULL; - int len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, NULL, 0); - if (len <= 0) - return NULL; - WCHAR* buf = (WCHAR*)malloc(len * sizeof(WCHAR)); - if (buf == NULL) - return NULL; - if (MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, src, -1, buf, len) == 0) - { - free(buf); - return NULL; - } - return buf; -} - static BOOL ConvertFindDataWToUtf8Local(const WIN32_FIND_DATAW* src, WIN32_FIND_DATAA* dst) { if (dst == NULL || src == NULL) @@ -1005,14 +987,15 @@ C__Handles::CreateFileUtf8(LPCSTR lpFileName, DWORD dwDesiredAccess, HANDLE hTemplateFile) { HANDLE ret = INVALID_HANDLE_VALUE; - WCHAR* fileNameW = Utf8AllocWideHandles(lpFileName); - if (fileNameW == NULL) + CWidePath fileNameW(lpFileName); + const WCHAR* apiPath = fileNameW.GetPathForWin32Api(); + if (apiPath == NULL) { SetLastError(ERROR_NO_UNICODE_TRANSLATION); } else { - ret = ::CreateFileW(fileNameW, dwDesiredAccess, dwShareMode, + ret = ::CreateFileW(apiPath, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); } @@ -1023,11 +1006,11 @@ C__Handles::CreateFileUtf8(LPCSTR lpFileName, DWORD dwDesiredAccess, if (ret == INVALID_HANDLE_VALUE) { DWORD err = GetLastError(); - if (fileNameW != NULL) + if (fileNameW.IsValid()) { _snwprintf_s(paramsWBuf, _TRUNCATE, L"dwDesiredAccess=0x%X,\ndwShareMode=0x%X,\ndwCreationDisposition=0x%X,\ndwFlagsAndAttributes=0x%X,\nlpFileName=%s", - dwDesiredAccess, dwShareMode, dwCreationDisposition, dwFlagsAndAttributes, fileNameW); + dwDesiredAccess, dwShareMode, dwCreationDisposition, dwFlagsAndAttributes, fileNameW.GetDisplayPath()); paramsW = paramsWBuf; } else @@ -1040,8 +1023,6 @@ C__Handles::CreateFileUtf8(LPCSTR lpFileName, DWORD dwDesiredAccess, } SetLastError(err); } - if (fileNameW != NULL) - free(fileNameW); CheckCreate(ret != INVALID_HANDLE_VALUE, __htFile, __hoCreateFile, ret, GetLastError(), TRUE, NULL, paramsA, paramsW); return ret; } @@ -2140,20 +2121,19 @@ HANDLE C__Handles::FindFirstFileUtf8(LPCSTR lpFileName, LPWIN32_FIND_DATAA lpFindFileData) { HANDLE ret = INVALID_HANDLE_VALUE; - WCHAR* fileNameW = Utf8AllocWideHandles(lpFileName); - if (fileNameW == NULL) + CWidePath fileNameW(lpFileName); + const WCHAR* apiPath = fileNameW.GetPathForWin32Api(); + if (apiPath == NULL) { SetLastError(ERROR_NO_UNICODE_TRANSLATION); } else { WIN32_FIND_DATAW dataW; - ret = ::FindFirstFileW(fileNameW, &dataW); + ret = ::FindFirstFileW(apiPath, &dataW); if (ret != INVALID_HANDLE_VALUE && lpFindFileData != NULL) ConvertFindDataWToUtf8Local(&dataW, lpFindFileData); } - if (fileNameW != NULL) - free(fileNameW); CheckCreate(ret != INVALID_HANDLE_VALUE, __htFindFile, __hoFindFirstFile, ret, GetLastError(), TRUE, NULL, lpFileName); return ret; @@ -2211,20 +2191,22 @@ HINSTANCE C__Handles::LoadLibraryUtf8(LPCSTR lpLibFileName) { HINSTANCE ret = NULL; - WCHAR* fileNameW = Utf8AllocWideHandles(lpLibFileName); - if (fileNameW == NULL) + CWidePath fileNameW(lpLibFileName); + const WCHAR* fullPath = fileNameW.GetFullPathForWin32Api(); + if (fullPath == NULL) { SetLastError(ERROR_NO_UNICODE_TRANSLATION); } else { - ret = ::LoadLibraryW(fileNameW); + const DWORD loadFlags = LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | + LOAD_LIBRARY_SEARCH_SYSTEM32 | + LOAD_LIBRARY_SEARCH_USER_DIRS; + ret = ::LoadLibraryExW(fullPath, NULL, loadFlags); } DWORD err = GetLastError(); - CheckCreate(ret != NULL, __htLibrary, __hoLoadLibrary, ret, err, TRUE, NULL, lpLibFileName, fileNameW); + CheckCreate(ret != NULL, __htLibrary, __hoLoadLibrary, ret, err, TRUE, NULL, lpLibFileName, fileNameW.GetDisplayPath()); SetLastError(err); - if (fileNameW != NULL) - free(fileNameW); return ret; } diff --git a/src/common/handles.h b/src/common/handles.h index 41c9bc98..5b124247 100644 --- a/src/common/handles.h +++ b/src/common/handles.h @@ -4,6 +4,17 @@ #pragma once +#include "wide_path.h" + +// These flags were made available to Windows 7 through KB2533623. Keep the +// definitions here because older SDKs omit them for the Windows 7 target. +#ifndef LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR +#define LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR 0x00000100 +#define LOAD_LIBRARY_SEARCH_APPLICATION_DIR 0x00000200 +#define LOAD_LIBRARY_SEARCH_USER_DIRS 0x00000400 +#define LOAD_LIBRARY_SEARCH_SYSTEM32 0x00000800 +#endif + // HANDLES_ENABLE macro - enables handle monitoring // _DEBUG or __HANDLES_DEBUG macro - outputs debug messages to TRACE // MULTITHREADED_HANDLES_ENABLE macro - prepares handles for multi-threaded applications @@ -15,33 +26,22 @@ // to avoid problems with semicolons in the macros defined below inline void __HandlesEmptyFunction() {} -// UTF-8 to wide string conversion helper for Release mode -inline WCHAR* Utf8AllocWideHandlesRelease(const char* src) -{ - if (src == NULL) - return NULL; - int len = MultiByteToWideChar(CP_UTF8, 0, src, -1, NULL, 0); - if (len <= 0) - return NULL; - WCHAR* dst = (WCHAR*)malloc(len * sizeof(WCHAR)); - if (dst != NULL) - MultiByteToWideChar(CP_UTF8, 0, src, -1, dst, len); - return dst; -} - // LoadLibraryUtf8 for Release mode (when HANDLES_ENABLE is not defined) inline HINSTANCE LoadLibraryUtf8(LPCSTR lpLibFileName) { HINSTANCE ret = NULL; - WCHAR* fileNameW = Utf8AllocWideHandlesRelease(lpLibFileName); - if (fileNameW == NULL) + CWidePath fileNameW(lpLibFileName); + const WCHAR* fullPath = fileNameW.GetFullPathForWin32Api(); + if (fullPath == NULL) { SetLastError(ERROR_NO_UNICODE_TRANSLATION); } else { - ret = ::LoadLibraryW(fileNameW); - free(fileNameW); + const DWORD loadFlags = LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | + LOAD_LIBRARY_SEARCH_SYSTEM32 | + LOAD_LIBRARY_SEARCH_USER_DIRS; + ret = ::LoadLibraryExW(fullPath, NULL, loadFlags); } return ret; } diff --git a/src/common/lock_ordering.cpp b/src/common/lock_ordering.cpp new file mode 100644 index 00000000..a500585c --- /dev/null +++ b/src/common/lock_ordering.cpp @@ -0,0 +1,108 @@ +// SPDX-FileCopyrightText: 2026 Taskscape Ltd +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "precomp.h" +#include "lock_ordering.h" + +#ifdef _DEBUG + +struct CLockOrderEntry +{ + CRITICAL_SECTION* CriticalSection; + CLockRank Rank; + const char* Name; +}; + +// The rank stack is per thread so unrelated workers never influence each +// other's ordering assertions or timeout diagnostics. +static __declspec(thread) CLockOrderEntry LockOrderStack[32]; +static __declspec(thread) unsigned int LockOrderStackCount = 0; + +static void ReportLockWaitTimeout(CRITICAL_SECTION* criticalSection, CLockRank rank, const char* lockName, + DWORD timeoutMilliseconds) +{ + const DWORD waiterThreadId = GetCurrentThreadId(); + const DWORD ownerThreadId = (DWORD)(ULONG_PTR)criticalSection->OwningThread; + const LONG recursionCount = criticalSection->RecursionCount; + const CLockRank heldRank = LockOrderStackCount == 0 ? lkrNone : LockOrderStack[LockOrderStackCount - 1].Rank; + char message[512]; + + // Avoid trace infrastructure here: a diagnostic must not acquire another application lock while one is stalled. + _snprintf_s(message, _countof(message), _TRUNCATE, + "Open Salamander lock wait timeout: waiter=%lu lock=%s rank=%d owner=%lu recursion=%ld held-rank=%d timeout-ms=%lu\n", + waiterThreadId, lockName != NULL ? lockName : "unnamed", (int)rank, ownerThreadId, recursionCount, + (int)heldRank, timeoutMilliseconds); + OutputDebugStringA(message); +} + +static BOOL IsRecursiveAcquisition(CRITICAL_SECTION* criticalSection, CLockRank rank) +{ + return LockOrderStackCount != 0 && + LockOrderStack[LockOrderStackCount - 1].CriticalSection == criticalSection && + LockOrderStack[LockOrderStackCount - 1].Rank == rank; +} + +#endif + +void LockOrderEnter(CRITICAL_SECTION* criticalSection, CLockRank rank, const char* lockName, DWORD timeoutMilliseconds) +{ +#ifdef _DEBUG + _ASSERTE(criticalSection != NULL); + _ASSERTE(rank != lkrNone); + + const BOOL recursive = IsRecursiveAcquisition(criticalSection, rank); + if (!recursive && LockOrderStackCount != 0) + { + // Equal ranks are reserved for recursive acquisition of the same lock; peers need an explicit order. + _ASSERTE(rank > LockOrderStack[LockOrderStackCount - 1].Rank); + } + + if (!TryEnterCriticalSection(criticalSection)) + { + const ULONGLONG waitStarted = GetTickCount64(); + while (!TryEnterCriticalSection(criticalSection)) + { + if (GetTickCount64() - waitStarted >= timeoutMilliseconds) + { + ReportLockWaitTimeout(criticalSection, rank, lockName, timeoutMilliseconds); + // Preserve the legacy contract after recording enough state to diagnose the blocking owner. + EnterCriticalSection(criticalSection); + break; + } + SwitchToThread(); + } + } + + _ASSERTE(LockOrderStackCount < _countof(LockOrderStack)); + if (LockOrderStackCount < _countof(LockOrderStack)) + { + LockOrderStack[LockOrderStackCount].CriticalSection = criticalSection; + LockOrderStack[LockOrderStackCount].Rank = rank; + LockOrderStack[LockOrderStackCount].Name = lockName; + ++LockOrderStackCount; + } +#else + UNREFERENCED_PARAMETER(rank); + UNREFERENCED_PARAMETER(lockName); + UNREFERENCED_PARAMETER(timeoutMilliseconds); + EnterCriticalSection(criticalSection); +#endif +} + +void LockOrderLeave(CRITICAL_SECTION* criticalSection, CLockRank rank, const char* lockName) +{ +#ifdef _DEBUG + _ASSERTE(LockOrderStackCount != 0); + if (LockOrderStackCount != 0) + { + const CLockOrderEntry& last = LockOrderStack[LockOrderStackCount - 1]; + // LIFO release keeps the stack faithful to the actual lock ownership used by later rank checks. + _ASSERTE(last.CriticalSection == criticalSection && last.Rank == rank && last.Name == lockName); + --LockOrderStackCount; + } +#else + UNREFERENCED_PARAMETER(rank); + UNREFERENCED_PARAMETER(lockName); +#endif + LeaveCriticalSection(criticalSection); +} diff --git a/src/common/lock_ordering.h b/src/common/lock_ordering.h new file mode 100644 index 00000000..f279c7cd --- /dev/null +++ b/src/common/lock_ordering.h @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2026 Taskscape Ltd +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +// Shared critical sections are acquired from lower to higher ranks so nested +// calls have one documented direction instead of an implicit call-order rule. +enum CLockRank +{ + lkrNone = 0, + lkrProcessLifetime = 10, + lkrConfiguration = 20, + lkrPluginRegistry = 30, + lkrWorkerQueue = 40, + lkrOperationState = 50, + lkrUiState = 60, + lkrExternalBroker = 70, +}; + +// These functions retain CRITICAL_SECTION blocking semantics while Debug +// builds diagnose rank inversions and long waits before entering the lock. +void LockOrderEnter(CRITICAL_SECTION* criticalSection, CLockRank rank, const char* lockName, + DWORD timeoutMilliseconds = 10000); +void LockOrderLeave(CRITICAL_SECTION* criticalSection, CLockRank rank, const char* lockName); diff --git a/src/common/monotonic_time.h b/src/common/monotonic_time.h new file mode 100644 index 00000000..f49abc46 --- /dev/null +++ b/src/common/monotonic_time.h @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: 2026 Taskscape Ltd +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +// Time points and durations are deliberately named at call sites. Their +// 64-bit representation keeps long-running timers correct after GetTickCount +// would have wrapped. +typedef ULONGLONG CMonotonicTimePoint; +typedef ULONGLONG CMonotonicDuration; + +class CMonotonicClock +{ +public: + // GetTickCount64 is monotonic and is not affected by wall-clock changes. + static CMonotonicTimePoint Now() + { + return GetTickCount64(); + } + + static CMonotonicTimePoint DeadlineAfter(CMonotonicDuration duration) + { + return Now() + duration; + } + + static CMonotonicDuration Elapsed(CMonotonicTimePoint start, CMonotonicTimePoint now) + { + // A defensive zero also makes injected/synthetic backward samples safe. + return now >= start ? now - start : 0; + } + + static BOOL HasElapsed(CMonotonicTimePoint start, CMonotonicDuration duration, + CMonotonicTimePoint now) + { + return Elapsed(start, now) >= duration; + } + + static BOOL HasReached(CMonotonicTimePoint deadline, CMonotonicTimePoint now) + { + return now >= deadline; + } + + // Win32 timers retain DWORD delays, so saturate only at that API boundary. + static DWORD RemainingWin32TimerDelay(CMonotonicTimePoint deadline, + CMonotonicTimePoint now) + { + if (HasReached(deadline, now)) + return 0; + + const CMonotonicDuration remaining = deadline - now; + return remaining > MAXDWORD ? MAXDWORD : (DWORD)remaining; + } +}; diff --git a/src/common/scoped_kernel_handle.h b/src/common/scoped_kernel_handle.h new file mode 100644 index 00000000..a2f7d2fe --- /dev/null +++ b/src/common/scoped_kernel_handle.h @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: 2026 Taskscape Ltd +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "handles.h" + +// Keeps the existing debug handle accounting while making local kernel-handle +// ownership survive every return path in newly touched code. +class CScopedKernelHandle +{ +public: + CScopedKernelHandle() : Handle(INVALID_HANDLE_VALUE) + { + } + + explicit CScopedKernelHandle(HANDLE handle) : Handle(handle) + { + } + + ~CScopedKernelHandle() + { + // Destruction must not replace the failure code a caller is returning. + const DWORD error = GetLastError(); + Close(NULL); + SetLastError(error); + } + +private: + CScopedKernelHandle(const CScopedKernelHandle&); + CScopedKernelHandle& operator=(const CScopedKernelHandle&); + +public: + BOOL IsValid() const + { + return Handle != INVALID_HANDLE_VALUE && Handle != NULL; + } + + HANDLE Get() const + { + return Handle; + } + + // Reset makes replacement ownership explicit and closes any prior owner. + void Reset(HANDLE handle = INVALID_HANDLE_VALUE) + { + Close(NULL); + Handle = handle; + } + + // Release is the only way to pass ownership back to a legacy raw-HANDLE API. + HANDLE Release() + { + HANDLE handle = Handle; + Handle = INVALID_HANDLE_VALUE; + return handle; + } + + // Some legacy result contracts report a close failure; callers can preserve + // that behaviour without reinstating a raw cleanup ladder. + BOOL Close(DWORD* error) + { + if (!IsValid()) + return TRUE; + + HANDLE handle = Handle; + Handle = INVALID_HANDLE_VALUE; + if (!HANDLES(CloseHandle(handle))) + { + if (error != NULL) + *error = GetLastError(); + return FALSE; + } + return TRUE; + } + +private: + HANDLE Handle; +}; diff --git a/src/common/scoped_native_resources.h b/src/common/scoped_native_resources.h new file mode 100644 index 00000000..1bd5479e --- /dev/null +++ b/src/common/scoped_native_resources.h @@ -0,0 +1,131 @@ +// SPDX-FileCopyrightText: 2026 Taskscape Ltd +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "lock_ordering.h" + +// Keeps malloc-owned scratch storage on the stack so callback failures and +// early returns cannot bypass the allocator paired with the legacy ABI. +class CScopedHeapBuffer +{ +public: + explicit CScopedHeapBuffer(void* buffer = NULL) : Buffer(buffer) + { + } + + ~CScopedHeapBuffer() + { + // Cleanup must not replace the Win32 error a caller is returning. + const DWORD error = GetLastError(); + Reset(); + SetLastError(error); + } + +private: + CScopedHeapBuffer(const CScopedHeapBuffer&); + CScopedHeapBuffer& operator=(const CScopedHeapBuffer&); + +public: + void* Get() const + { + return Buffer; + } + + void Reset(void* buffer = NULL) + { + if (Buffer != NULL) + free(Buffer); + Buffer = buffer; + } + + void* Release() + { + void* buffer = Buffer; + Buffer = NULL; + return buffer; + } + +private: + void* Buffer; +}; + +// Owns a MapViewOfFile result without imposing a C++ object layout on either +// side of a plug-in boundary. +class CScopedMappingView +{ +public: + explicit CScopedMappingView(void* view = NULL) : View(view) + { + } + + ~CScopedMappingView() + { + // Unmapping is best-effort cleanup and must retain the caller's error. + const DWORD error = GetLastError(); + Reset(); + SetLastError(error); + } + +private: + CScopedMappingView(const CScopedMappingView&); + CScopedMappingView& operator=(const CScopedMappingView&); + +public: + void* Get() const + { + return View; + } + + void Reset(void* view = NULL) + { + if (View != NULL) + UnmapViewOfFile(View); + View = view; + } + + void* Release() + { + void* view = View; + View = NULL; + return view; + } + +private: + void* View; +}; + +// Pairs a ranked CRITICAL_SECTION acquisition at callback boundaries so future +// returns or unwinding cannot strand callers or bypass the lock-order checks. +class CScopedCriticalSection +{ +public: + CScopedCriticalSection(CRITICAL_SECTION* criticalSection, CLockRank rank = lkrNone, const char* lockName = NULL) + : CriticalSection(criticalSection), Rank(rank), LockName(lockName) + { + if (Rank != lkrNone) + LockOrderEnter(CriticalSection, Rank, LockName); + else + EnterCriticalSection(CriticalSection); + } + + ~CScopedCriticalSection() + { + if (Rank != lkrNone) + LockOrderLeave(CriticalSection, Rank, LockName); + else + LeaveCriticalSection(CriticalSection); + } + +private: + CScopedCriticalSection(const CScopedCriticalSection&); + CScopedCriticalSection& operator=(const CScopedCriticalSection&); + +private: + CRITICAL_SECTION* CriticalSection; + CLockRank Rank; + const char* LockName; +}; diff --git a/src/common/strutils.cpp b/src/common/strutils.cpp index 25f7cb0a..cbdd6ee8 100644 --- a/src/common/strutils.cpp +++ b/src/common/strutils.cpp @@ -4,11 +4,111 @@ #include "precomp.h" #include +#include #pragma warning(3 : 4706) // warning C4706: assignment within conditional expression #include "strutils.h" +EBoundedStringResult CopyStringChecked(char* destination, size_t destinationCapacity, const char* source) +{ + if (destination == NULL || destinationCapacity == 0 || source == NULL) + { + if (destination != NULL && destinationCapacity != 0) + destination[0] = 0; + SetLastError(ERROR_INVALID_PARAMETER); + return bsrInvalidParameter; + } + + size_t sourceLength = strlen(source); + if (sourceLength >= destinationCapacity) + { + destination[0] = 0; + SetLastError(ERROR_INSUFFICIENT_BUFFER); + return bsrTruncated; + } + + memcpy(destination, source, sourceLength + 1); + return bsrSuccess; +} + +EBoundedStringResult FormatStringCheckedV(char* destination, size_t destinationCapacity, + const char* format, va_list arguments) +{ + if (destination == NULL || destinationCapacity == 0 || format == NULL) + { + if (destination != NULL && destinationCapacity != 0) + destination[0] = 0; + SetLastError(ERROR_INVALID_PARAMETER); + return bsrInvalidParameter; + } + + va_list measuredArguments; + va_copy(measuredArguments, arguments); + int required = _vscprintf(format, measuredArguments); + va_end(measuredArguments); + if (required < 0) + { + destination[0] = 0; + SetLastError(ERROR_NO_UNICODE_TRANSLATION); + return bsrEncodingError; + } + if ((size_t)required >= destinationCapacity) + { + destination[0] = 0; + SetLastError(ERROR_INSUFFICIENT_BUFFER); + return bsrTruncated; + } + + int written = vsnprintf_s(destination, destinationCapacity, _TRUNCATE, format, arguments); + if (written != required) + { + destination[0] = 0; + SetLastError(ERROR_NO_UNICODE_TRANSLATION); + return bsrEncodingError; + } + return bsrSuccess; +} + +EBoundedStringResult FormatStringChecked(char* destination, size_t destinationCapacity, + const char* format, ...) +{ + va_list arguments; + va_start(arguments, format); + EBoundedStringResult result = FormatStringCheckedV(destination, destinationCapacity, format, arguments); + va_end(arguments); + return result; +} + +EBoundedStringResult ConvertWideToUtf8Checked(const WCHAR* source, char* destination, + size_t destinationCapacity) +{ + if (destination == NULL || destinationCapacity == 0 || source == NULL) + { + if (destination != NULL && destinationCapacity != 0) + destination[0] = 0; + SetLastError(ERROR_INVALID_PARAMETER); + return bsrInvalidParameter; + } + + destination[0] = 0; + int required = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, source, -1, NULL, 0, NULL, NULL); + if (required == 0) + return bsrEncodingError; + if ((size_t)required > destinationCapacity) + { + SetLastError(ERROR_INSUFFICIENT_BUFFER); + return bsrTruncated; + } + if (WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, source, -1, destination, + (int)destinationCapacity, NULL, NULL) != required) + { + destination[0] = 0; + return bsrEncodingError; + } + return bsrSuccess; +} + int ConvertU2A(const WCHAR* src, int srcLen, char* buf, int bufSize, BOOL compositeCheck, UINT codepage) { if (buf == NULL || bufSize <= 0) @@ -431,10 +531,10 @@ BOOL ConvertFindDataWToUtf8(const WIN32_FIND_DATAW& src, WIN32_FIND_DATAA* dst) dst->nFileSizeLow = src.nFileSizeLow; dst->dwReserved0 = src.dwReserved0; dst->dwReserved1 = src.dwReserved1; - if (ConvertWideToUtf8(src.cFileName, -1, dst->cFileName, _countof(dst->cFileName)) == 0) - dst->cFileName[0] = 0; - if (ConvertWideToUtf8(src.cAlternateFileName, -1, dst->cAlternateFileName, _countof(dst->cAlternateFileName)) == 0) - dst->cAlternateFileName[0] = 0; + if (ConvertWideToUtf8Checked(src.cFileName, dst->cFileName, _countof(dst->cFileName)) != bsrSuccess || + ConvertWideToUtf8Checked(src.cAlternateFileName, dst->cAlternateFileName, + _countof(dst->cAlternateFileName)) != bsrSuccess) + return FALSE; return TRUE; } @@ -442,81 +542,75 @@ HANDLE CreateFileUtf8(const char* fileName, DWORD desiredAccess, DWORD shareMode LPSECURITY_ATTRIBUTES securityAttributes, DWORD creationDisposition, DWORD flagsAndAttributes, HANDLE templateFile) { - // Use stack buffer for common case (paths under MAX_PATH), heap for long paths - WCHAR stackBuf[MAX_PATH]; - CStrStackOrHeap fileNameW(fileName, stackBuf, MAX_PATH); - if (fileNameW == NULL) + CWidePath fileNameW(fileName); + const WCHAR* apiPath = fileNameW.GetPathForWin32Api(); + if (apiPath == NULL) { SetLastError(ERROR_INVALID_PARAMETER); return INVALID_HANDLE_VALUE; } - return CreateFileW(fileNameW, desiredAccess, shareMode, securityAttributes, + return CreateFileW(apiPath, desiredAccess, shareMode, securityAttributes, creationDisposition, flagsAndAttributes, templateFile); } BOOL DeleteFileUtf8(const char* fileName) { - // Use stack buffer for common case (paths under MAX_PATH), heap for long paths - WCHAR stackBuf[MAX_PATH]; - CStrStackOrHeap fileNameW(fileName, stackBuf, MAX_PATH); - if (fileNameW == NULL) + CWidePath fileNameW(fileName); + const WCHAR* apiPath = fileNameW.GetPathForWin32Api(); + if (apiPath == NULL) { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } - return DeleteFileW(fileNameW); + return DeleteFileW(apiPath); } BOOL CreateDirectoryUtf8(const char* dirName, LPSECURITY_ATTRIBUTES securityAttributes) { - // Use stack buffer for common case (paths under MAX_PATH), heap for long paths - WCHAR stackBuf[MAX_PATH]; - CStrStackOrHeap dirNameW(dirName, stackBuf, MAX_PATH); - if (dirNameW == NULL) + CWidePath dirNameW(dirName); + const WCHAR* apiPath = dirNameW.GetPathForWin32Api(); + if (apiPath == NULL) { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } - return CreateDirectoryW(dirNameW, securityAttributes); + return CreateDirectoryW(apiPath, securityAttributes); } BOOL RemoveDirectoryUtf8(const char* dirName) { - // Use stack buffer for common case (paths under MAX_PATH), heap for long paths - WCHAR stackBuf[MAX_PATH]; - CStrStackOrHeap dirNameW(dirName, stackBuf, MAX_PATH); - if (dirNameW == NULL) + CWidePath dirNameW(dirName); + const WCHAR* apiPath = dirNameW.GetPathForWin32Api(); + if (apiPath == NULL) { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } - return RemoveDirectoryW(dirNameW); + return RemoveDirectoryW(apiPath); } BOOL SetFileAttributesUtf8(const char* fileName, DWORD attrs) { - // Use stack buffer for common case (paths under MAX_PATH), heap for long paths - WCHAR stackBuf[MAX_PATH]; - CStrStackOrHeap fileNameW(fileName, stackBuf, MAX_PATH); - if (fileNameW == NULL) + CWidePath fileNameW(fileName); + const WCHAR* apiPath = fileNameW.GetPathForWin32Api(); + if (apiPath == NULL) { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } - return SetFileAttributesW(fileNameW, attrs); + return SetFileAttributesW(apiPath, attrs); } DWORD GetFileAttributesUtf8(const char* fileName) { - // Use stack buffer for common case (paths under MAX_PATH), heap for long paths - WCHAR stackBuf[MAX_PATH]; - CStrStackOrHeap fileNameW(fileName, stackBuf, MAX_PATH); - if (fileNameW == NULL) + CWidePath fileNameW(fileName); + const WCHAR* apiPath = fileNameW.GetPathForWin32Api(); + if (apiPath == NULL) { SetLastError(ERROR_INVALID_PARAMETER); return INVALID_FILE_ATTRIBUTES; } - return GetFileAttributesW(fileNameW); + return GetFileAttributesW(apiPath); } WCHAR* DupStr(const WCHAR* txt) diff --git a/src/common/strutils.h b/src/common/strutils.h index 0b42c047..7d960a7a 100644 --- a/src/common/strutils.h +++ b/src/common/strutils.h @@ -3,6 +3,32 @@ #pragma once +#include "wide_path.h" +#include + +// Result for bounded string operations. Callers must handle truncation and +// conversion failures instead of continuing with an implicitly shortened value. +enum EBoundedStringResult +{ + bsrSuccess, + bsrTruncated, + bsrInvalidParameter, + bsrEncodingError +}; + +// Copy or format into a caller-owned buffer. On every non-success result the +// destination is an empty, null-terminated string when a destination exists. +EBoundedStringResult CopyStringChecked(char* destination, size_t destinationCapacity, const char* source); +EBoundedStringResult FormatStringCheckedV(char* destination, size_t destinationCapacity, + const char* format, va_list arguments); +EBoundedStringResult FormatStringChecked(char* destination, size_t destinationCapacity, + const char* format, ...); + +// Strict UTF-16 to UTF-8 conversion for external-input boundaries. The result +// distinguishes a destination that is too small from malformed UTF-16. +EBoundedStringResult ConvertWideToUtf8Checked(const WCHAR* source, char* destination, + size_t destinationCapacity); + // SAFE_ALLOC macro removes code that tests if memory allocation succeeded (see allochan.*) // convert Unicode string (UTF-16) to ANSI multibyte string; 'src' is Unicode string; @@ -55,82 +81,6 @@ WCHAR* ConvertAllocUtf8ToWide(const char* src, int srcLen); // convert UTF-16 to allocated UTF-8 string (caller frees) char* ConvertAllocWideToUtf8(const WCHAR* src, int srcLen); -// Convert UTF-8 to wide string using stack buffer with heap fallback for long paths. -// Tries stack buffer first, falls back to heap allocation only if needed. -// Returns pointer to the converted string (either stackBuf or newly allocated). -// Sets *heapAllocated to TRUE if heap allocation was used (caller must free). -// Returns NULL on error. -// Usage example: -// WCHAR stackBuf[MAX_PATH]; -// BOOL heapAlloc; -// WCHAR* fileNameW = ConvertUtf8ToWideStackOrHeap(fileName, stackBuf, MAX_PATH, &heapAlloc); -// if (fileNameW != NULL) { ... use fileNameW ... } -// if (heapAlloc) free(fileNameW); -inline WCHAR* ConvertUtf8ToWideStackOrHeap(const char* src, WCHAR* stackBuf, int stackBufSizeInChars, BOOL* heapAllocated) -{ - *heapAllocated = FALSE; - if (src == NULL) - { - SetLastError(ERROR_INVALID_PARAMETER); - return NULL; - } - - // Get required length first - int len = MultiByteToWideChar(CP_UTF8, 0, src, -1, NULL, 0); - if (len <= 0) - { - SetLastError(ERROR_NO_UNICODE_TRANSLATION); - return NULL; - } - - if (len <= stackBufSizeInChars) - { - // Fits in stack buffer - use it directly (no heap allocation) - MultiByteToWideChar(CP_UTF8, 0, src, -1, stackBuf, stackBufSizeInChars); - return stackBuf; - } - - // Path too long for stack buffer - fall back to heap allocation - WCHAR* heapBuf = (WCHAR*)malloc(len * sizeof(WCHAR)); - if (heapBuf == NULL) - { - SetLastError(ERROR_NOT_ENOUGH_MEMORY); - return NULL; - } - MultiByteToWideChar(CP_UTF8, 0, src, -1, heapBuf, len); - *heapAllocated = TRUE; - return heapBuf; -} - -// Helper class for automatic cleanup of stack-or-heap wide string buffers. -// If the string was heap-allocated, it frees the memory on destruction. -// Usage: -// WCHAR stackBuf[MAX_PATH]; -// CStrStackOrHeap fileNameW(fileName, stackBuf, MAX_PATH); -// if (fileNameW != NULL) { ... use fileNameW ... } -class CStrStackOrHeap -{ -public: - WCHAR* Ptr; - BOOL HeapAllocated; - -public: - // Convert UTF-8 string using stack buffer with heap fallback - CStrStackOrHeap(const char* src, WCHAR* stackBuf, int stackBufSizeInChars) - { - Ptr = ConvertUtf8ToWideStackOrHeap(src, stackBuf, stackBufSizeInChars, &HeapAllocated); - } - - ~CStrStackOrHeap() - { - if (HeapAllocated && Ptr != NULL) - free(Ptr); - } - - operator WCHAR*() { return Ptr; } - BOOL IsValid() const { return Ptr != NULL; } -}; - // convert WIN32_FIND_DATAW to WIN32_FIND_DATAA with UTF-8 names BOOL ConvertFindDataWToUtf8(const WIN32_FIND_DATAW& src, WIN32_FIND_DATAA* dst); diff --git a/src/common/thread_owner.h b/src/common/thread_owner.h new file mode 100644 index 00000000..67012bb7 --- /dev/null +++ b/src/common/thread_owner.h @@ -0,0 +1,298 @@ +// SPDX-FileCopyrightText: 2026 Taskscape Ltd +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "handles.h" + +#include +#include + +// precomp.h declares this later through consts.h; keep the worker seam usable +// by focused sources that include it before the application declarations. +void SetThreadNameInVCAndTrace(const char* name); + +// Centralizes the lifetime contract for newly touched CRT-backed workers: the +// owner retains their handle, stop request, launch data, and completion signal +// until the callback has returned and the thread has been joined. +typedef DWORD(WINAPI* CThreadOwnerEntry)(void* parameter, HANDLE stopEvent); + +// Shutdown has two observable phases: a worker first gets time to observe its +// cancellation request, then a longer recovery window to persist/close work. +// A breach is never permission to terminate the thread because its callback +// can still hold application-owned state. +class CThreadShutdownDeadline +{ +public: + CThreadShutdownDeadline(LPCSTR workerName, + DWORD cancellationDeadline = 5000, + DWORD recoveryDeadline = 30000) + : WorkerName(workerName != NULL ? workerName : "unnamed worker"), + CancellationDeadline(cancellationDeadline), + RecoveryDeadline(recoveryDeadline) + { + } + + // Returns WAIT_TIMEOUT when either diagnostic deadline was breached, even + // though the final safe join below has completed. + DWORD WaitForSafeJoin(HANDLE worker) const + { + DWORD waitResult = WaitForSingleObject(worker, CancellationDeadline); + if (waitResult != WAIT_TIMEOUT) + return waitResult; + + TraceDeadlineBreach("cancellation", CancellationDeadline, worker); + waitResult = WaitForSingleObject(worker, RecoveryDeadline); + if (waitResult != WAIT_TIMEOUT) + return WAIT_TIMEOUT; + + TraceDeadlineBreach("operation recovery", RecoveryDeadline, worker); + // This worker has not been proven independent of process state, so it + // must finish before shutdown frees the state it can still reference. + WaitForSingleObject(worker, INFINITE); + return WAIT_TIMEOUT; + } + +private: + void TraceDeadlineBreach(LPCSTR phase, DWORD deadline, HANDLE worker) const + { + DWORD exitCode = STILL_ACTIVE; + if (!GetExitCodeThread(worker, &exitCode)) + exitCode = GetLastError(); + TRACE_E("Shutdown " << phase << " deadline breached for " << WorkerName + << " after " << deadline << " ms; thread state=" << exitCode + << ". Keeping the process alive for a safe join."); + } + +private: + LPCSTR WorkerName; + DWORD CancellationDeadline; + DWORD RecoveryDeadline; +}; + +class CThreadOwner +{ +public: + CThreadOwner() + : Thread(NULL), StopEvent(NULL), CompletionEvent(NULL) + { + } + + ~CThreadOwner() + { + // Owners may be destroyed only after their callback has stopped, so a + // forgotten shutdown path cannot leave launch data pointing at dead state. + StopAndJoin(INFINITE); + } + +private: + CThreadOwner(const CThreadOwner&); + CThreadOwner& operator=(const CThreadOwner&); + +public: + // The caller keeps parameter alive through WaitForCompletion or StopAndJoin; + // the wrapper itself owns the copied launch record for the whole callback. + BOOL Start(CThreadOwnerEntry entry, void* parameter, LPCSTR name, + BOOL initializeCOM = FALSE, DWORD coInit = COINIT_APARTMENTTHREADED) + { + if (entry == NULL) + { + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + + if (Thread != NULL) + { + if (WaitForCompletion(0) != WAIT_OBJECT_0) + { + SetLastError(ERROR_BUSY); + return FALSE; + } + Join(); + CloseOwnedHandles(); + } + + StopEvent = HANDLES(CreateEvent(NULL, TRUE, FALSE, NULL)); + CompletionEvent = HANDLES(CreateEvent(NULL, TRUE, FALSE, NULL)); + if (StopEvent == NULL || CompletionEvent == NULL) + { + CloseOwnedHandles(); + return FALSE; + } + + CThreadLaunchData* launch = (CThreadLaunchData*)malloc(sizeof(CThreadLaunchData)); + if (launch == NULL) + { + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + CloseOwnedHandles(); + return FALSE; + } + + launch->Entry = entry; + launch->Parameter = parameter; + launch->StopEvent = StopEvent; + launch->CompletionEvent = CompletionEvent; + launch->InitializeCOM = initializeCOM; + launch->CoInit = coInit; + lstrcpynA(launch->Name, name != NULL ? name : "OpenSalamanderWorker", sizeof(launch->Name)); + + unsigned threadID = 0; + Thread = (HANDLE)HANDLES(_beginthreadex(NULL, 0, ThreadMain, launch, 0, &threadID)); + if (Thread == NULL) + { + free(launch); + CloseOwnedHandles(); + return FALSE; + } + return TRUE; + } + + BOOL HasThread() const + { + return Thread != NULL; + } + + HANDLE GetStopEvent() const + { + return StopEvent; + } + + // Exposes the owner's completion signal so a coordinator can wait for + // work without repeatedly probing the thread exit code. + HANDLE GetCompletionEvent() const + { + return CompletionEvent; + } + + DWORD WaitForCompletion(DWORD timeout) const + { + return CompletionEvent != NULL ? WaitForSingleObject(CompletionEvent, timeout) : WAIT_OBJECT_0; + } + + BOOL GetExitCode(DWORD* exitCode) const + { + if (Thread == NULL || exitCode == NULL) + { + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + return GetExitCodeThread(Thread, exitCode); + } + + void RequestStop() + { + if (StopEvent != NULL) + SetEvent(StopEvent); + } + + // A timeout is diagnostic only: the owner still joins before releasing its + // state, preserving the no-use-after-free shutdown invariant. + DWORD StopAndJoin(DWORD timeout) + { + if (Thread == NULL) + return WAIT_OBJECT_0; + + RequestStop(); + const DWORD completion = WaitForCompletion(timeout); + WaitForSingleObject(Thread, INFINITE); + CloseOwnedHandles(); + return completion; + } + + // The declared shutdown policy makes cancellation and recovery delays + // visible while retaining the owner until the callback has returned. + DWORD StopAndJoin(const CThreadShutdownDeadline& deadline) + { + if (Thread == NULL) + return WAIT_OBJECT_0; + + RequestStop(); + const DWORD completion = deadline.WaitForSafeJoin(Thread); + CloseOwnedHandles(); + return completion; + } + +private: + struct CThreadLaunchData + { + CThreadOwnerEntry Entry; + void* Parameter; + HANDLE StopEvent; + HANDLE CompletionEvent; + BOOL InitializeCOM; + DWORD CoInit; + char Name[64]; + }; + + static unsigned __stdcall ThreadMain(void* parameter) + { + CThreadLaunchData* launch = (CThreadLaunchData*)parameter; + DWORD result = ERROR_UNHANDLED_EXCEPTION; + HRESULT comResult = S_OK; + BOOL comInitialized = FALSE; + + if (launch->Name[0] != 0) + SetThreadNameInVCAndTrace(launch->Name); + + if (launch->InitializeCOM) + { + comResult = CoInitializeEx(NULL, launch->CoInit); + comInitialized = SUCCEEDED(comResult); + } + + if (!launch->InitializeCOM || comInitialized) + { + // Contain C++ exceptions at the common worker boundary so every + // normal failure path reaches the completion event below. + try + { + result = launch->Entry(launch->Parameter, launch->StopEvent); + } + catch (...) + { + result = ERROR_UNHANDLED_EXCEPTION; + } + } + else + result = (DWORD)comResult; + + if (comInitialized) + CoUninitialize(); + + // Completion is signaled before freeing the launch record, letting the + // owner safely join and release its events without polling the handle. + SetEvent(launch->CompletionEvent); + free(launch); + return result; + } + + void Join() + { + if (Thread != NULL) + WaitForSingleObject(Thread, INFINITE); + } + + void CloseOwnedHandles() + { + if (Thread != NULL) + { + HANDLES(CloseHandle(Thread)); + Thread = NULL; + } + if (StopEvent != NULL) + { + HANDLES(CloseHandle(StopEvent)); + StopEvent = NULL; + } + if (CompletionEvent != NULL) + { + HANDLES(CloseHandle(CompletionEvent)); + CompletionEvent = NULL; + } + } + +private: + HANDLE Thread; + HANDLE StopEvent; + HANDLE CompletionEvent; +}; diff --git a/src/common/wide_path.h b/src/common/wide_path.h new file mode 100644 index 00000000..2a0a961a --- /dev/null +++ b/src/common/wide_path.h @@ -0,0 +1,189 @@ +// SPDX-FileCopyrightText: 2026 Taskscape Ltd +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +// Owns the two representations of a path at a Win32 boundary. The original +// UTF-8 display value remains owned by the caller; this type keeps its UTF-16 +// display spelling separate from the API spelling so adding \\?\ never leaks +// into UI, logs, histories, or plug-in data. +class CWidePath +{ +public: + explicit CWidePath(const char* utf8Path) + : DisplayPath(NULL), ApiPath(NULL) + { + if (utf8Path == NULL) + { + SetLastError(ERROR_INVALID_PARAMETER); + return; + } + + int length = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8Path, -1, NULL, 0); + if (length == 0 && GetLastError() == ERROR_NO_UNICODE_TRANSLATION) + length = MultiByteToWideChar(CP_UTF8, 0, utf8Path, -1, NULL, 0); + if (length == 0) + return; + + DisplayPath = (WCHAR*)malloc(length * sizeof(WCHAR)); + if (DisplayPath == NULL) + { + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + return; + } + + int converted = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, utf8Path, -1, DisplayPath, length); + if (converted == 0 && GetLastError() == ERROR_NO_UNICODE_TRANSLATION) + converted = MultiByteToWideChar(CP_UTF8, 0, utf8Path, -1, DisplayPath, length); + if (converted == 0) + { + DWORD error = GetLastError(); + free(DisplayPath); + DisplayPath = NULL; + SetLastError(error); + } + } + + explicit CWidePath(const WCHAR* widePath) + : DisplayPath(Duplicate(widePath)), ApiPath(NULL) + { + } + + ~CWidePath() + { + free(DisplayPath); + free(ApiPath); + } + + BOOL IsValid() const { return DisplayPath != NULL; } + const WCHAR* GetDisplayPath() const { return DisplayPath; } + + // Supplies a UTF-16 path suitable for ordinary filesystem APIs. Only + // paths that exceed the legacy limit are made absolute and prefixed. + const WCHAR* GetPathForWin32Api() + { + if (DisplayPath == NULL) + return NULL; + if (ApiPath != NULL) + return ApiPath; + if (IsExtendedLengthPath(DisplayPath) || lstrlenW(DisplayPath) < MAX_PATH) + return DisplayPath; + return BuildAbsoluteApiPath(); + } + + // Some APIs, notably secure DLL loading, require an absolute path even + // when it is short. This is the only normalization performed here. + const WCHAR* GetFullPathForWin32Api() + { + if (DisplayPath == NULL) + return NULL; + if (ApiPath != NULL) + return ApiPath; + if (IsExtendedLengthPath(DisplayPath)) + return DisplayPath; + return BuildAbsoluteApiPath(); + } + +private: + WCHAR* DisplayPath; + WCHAR* ApiPath; + + CWidePath(const CWidePath&); + CWidePath& operator=(const CWidePath&); + + static WCHAR* Duplicate(const WCHAR* path) + { + if (path == NULL) + { + SetLastError(ERROR_INVALID_PARAMETER); + return NULL; + } + size_t length = lstrlenW(path) + 1; + WCHAR* copy = (WCHAR*)malloc(length * sizeof(WCHAR)); + if (copy == NULL) + { + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + return NULL; + } + memcpy(copy, path, length * sizeof(WCHAR)); + return copy; + } + + static BOOL IsExtendedLengthPath(const WCHAR* path) + { + return path[0] == L'\\' && path[1] == L'\\' && path[2] == L'?' && path[3] == L'\\'; + } + + static BOOL IsUncPath(const WCHAR* path) + { + return path[0] == L'\\' && path[1] == L'\\' && !IsExtendedLengthPath(path); + } + + const WCHAR* BuildAbsoluteApiPath() + { + DWORD required = GetFullPathNameW(DisplayPath, 0, NULL, NULL); + if (required == 0) + return NULL; + + // The current directory can change between the size probe and copy. + // Retry with the exact size returned by Win32 rather than truncating. + for (int attempt = 0; attempt != 4; ++attempt) + { + WCHAR* fullPath = (WCHAR*)malloc(required * sizeof(WCHAR)); + if (fullPath == NULL) + { + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + return NULL; + } + + DWORD copied = GetFullPathNameW(DisplayPath, required, fullPath, NULL); + if (copied != 0 && copied < required) + { + ApiPath = PrefixExtendedLengthPathIfNeeded(fullPath); + if (ApiPath == NULL) + free(fullPath); + return ApiPath; + } + + DWORD error = GetLastError(); + free(fullPath); + if (copied == 0) + { + SetLastError(error); + return NULL; + } + required = copied + 1; + } + + SetLastError(ERROR_INSUFFICIENT_BUFFER); + return NULL; + } + + static WCHAR* PrefixExtendedLengthPathIfNeeded(WCHAR* fullPath) + { + size_t length = lstrlenW(fullPath); + if (length < MAX_PATH || IsExtendedLengthPath(fullPath)) + return fullPath; + + static const WCHAR extendedPrefix[] = L"\\\\?\\"; + static const WCHAR extendedUncPrefix[] = L"\\\\?\\UNC\\"; + const WCHAR* prefix = IsUncPath(fullPath) ? extendedUncPrefix : extendedPrefix; + size_t prefixLength = lstrlenW(prefix); + const WCHAR* suffix = IsUncPath(fullPath) ? fullPath + 2 : fullPath; + size_t suffixLength = lstrlenW(suffix); + WCHAR* prefixedPath = (WCHAR*)malloc((prefixLength + suffixLength + 1) * sizeof(WCHAR)); + if (prefixedPath == NULL) + { + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + return NULL; + } + memcpy(prefixedPath, prefix, prefixLength * sizeof(WCHAR)); + memcpy(prefixedPath + prefixLength, suffix, (suffixLength + 1) * sizeof(WCHAR)); + free(fullPath); + return prefixedPath; + } +}; diff --git a/src/consts.h b/src/consts.h index 5bb2ab2c..d3caa583 100644 --- a/src/consts.h +++ b/src/consts.h @@ -106,9 +106,15 @@ BOOL SalGetTempFileName(const char* path, const char* prefix, char* tmpName, BOO // and then set it back) BOOL SalMoveFile(const char* srcName, const char* destName); -// variant of Windows GetFileSize (has simpler error handling); 'file' is opened -// file for GetFileSize() call; returns obtained file size in 'size'; returns success, -// on FALSE (error) 'err' contains Windows error code and 'size' is zero +// Unambiguous 64-bit wrappers for file size and position. They never require +// callers to inspect GetLastError after receiving a sentinel value. +CFileOffsetResult SalGetFileSizeEx(HANDLE file); +CFileOffsetResult SalSetFilePointerEx(HANDLE file, const CQuadWord& distance, DWORD moveMethod); + +// Legacy compatibility adapter for existing callers. Prefer SalGetFileSizeEx +// in new operation code. +// 'file' is opened file for the size query; on FALSE (error) 'err' contains +// the captured Windows error code and 'size' is zero. BOOL SalGetFileSize(HANDLE file, CQuadWord& size, DWORD& err); BOOL SalGetFileSize2(const char* fileName, CQuadWord& size, DWORD* err); // 'err' can be NULL if we don't care @@ -609,6 +615,12 @@ void ShowSafeWaitWindow(BOOL show); BOOL GetSafeWaitWindowClosePressed(); // returns TRUE if user presses ESC or clicked mouse on Close button BOOL UserWantsToCancelSafeWaitWindow(); +// Returns a caller-owned duplicate that is signaled when its safe-wait Close +// button is pressed; it remains valid until the caller closes the duplicate. +HANDLE GetSafeWaitWindowCancelEvent(); +// The wait-window UI signals cancellation through this helper to avoid racing +// its event lifetime with the owner that is waiting on a duplicate. +void SignalSafeWaitWindowCancellation(); // serves for additional text change in window // WARNING: no new window layout occurs and if text expands significantly // it will be truncated; use for example for countdown: 60s, 55s, 50s, ... @@ -1005,10 +1017,10 @@ BOOL CheckAndConnectUNCNetworkPath(HWND parent, const char* UNCPath, BOOL& pathI // we unsuccessfully tried to establish connection (e.g., "credentials conflict") BOOL CheckAndRestoreNetworkConnection(HWND parent, const char drive, BOOL& pathInvalid); -// functions for thread management, when terminating the process these threads should close, -// if they don't do it themselves, they must be terminated -void AddAuxThread(HANDLE view, BOOL testIfFinished = FALSE); -void TerminateAuxThreads(); +// Auxiliary workers are tracked until their named shutdown deadline and final +// safe join; they are never force-terminated while they may use host state. +void AddAuxThread(HANDLE view, BOOL testIfFinished = FALSE, LPCSTR description = "legacy auxiliary worker"); +void ShutdownAuxThreads(); // returns TRUE if the specified file exists; otherwise returns FALSE extern "C" BOOL FileExists(const char* fileName); @@ -1183,6 +1195,7 @@ struct COpenViewerData //#define WM_USER_RENAME_NEXT_ITEM WM_APP + 407 // [next, 0] - posted after pressing (Shift)Tab in inplace QuickRename to move to (previous) next item; inspired by Vista; 'next' is TRUE for next and FALSE for previous #define WM_USER_PROGRDLG_UPDATEICON WM_APP + 408 // [0, 0] - CProgressDlgArray: after delivery of this message to CProgressDialog new dialog icon setting occurs +#define WM_USER_PROGRDLG_WORKERCOMPLETE WM_APP + 413 // [0, CWorkerCompletion*] - CProgressDialog: worker-owned completion result; the dialog consumes it asynchronously #define WM_USER_FORCECLOSE_MAINWND WM_APP + 409 // [0, 0] - forced closing of Salamander main window @@ -1195,6 +1208,13 @@ struct COpenViewerData #define WM_USER_SLGINCOMPLETE WM_APP + 414 // [0, 0] - warning that SLG is not completely translated, motivational text to get involved #define WM_USER_USERMENUICONS_READY WM_APP + 415 // [bkgndReaderData, threadID] - notification for main window that icon reading for User Menu in thread with ID 'threadID' has completed +#define WM_USER_ALLOCATION_EMERGENCY WM_APP + 416 // [0, 0] - pre-registered allocation-failure notification; recovery runs on the UI thread +// The isolated UI runner uses these private messages to avoid racing startup or blocking on modal command handlers. +#define WM_USER_UI_TEST_READY WM_APP + 417 // [0, 0] returns TRUE only after startup reaches the running state +#define WM_USER_UI_TEST_COMMAND WM_APP + 418 // [command ID, 0] queues WM_COMMAND only for an isolated ready process +#define WM_USER_UI_TEST_CONFIG_GENERATION WM_APP + 419 // [0, 0] returns completed Configuration-handler count +#define WM_USER_UI_TEST_CONFIG_FAULT WM_APP + 420 // [write boundary, 0] arms only the next isolated configuration save +#define WM_USER_UI_TEST_FTP_ORGANIZE_COMMAND WM_APP + 421 // [0, 0] resolves the loaded FTP plug-in's host command ID // states for Shift+F1 help mode #define HELP_INACTIVE 0 // not in Shift+F1 help mode (must be 0) @@ -1470,8 +1490,11 @@ enum CSymbolsImageListIndexes extern HIMAGELIST HFindSymbolsImageList; // symbols for find extern HIMAGELIST HMenuMarkImageList; // check marks for menu -extern HIMAGELIST HGrayToolBarImageList; // toolbar and menu in gray version (calculated from color) -extern HIMAGELIST HHotToolBarImageList; // toolbar and menu in color version +// Keep command menus on the established small size while toolbar image lists follow the user preference. +extern HIMAGELIST HGrayMenuImageList; +extern HIMAGELIST HHotMenuImageList; +extern HIMAGELIST HGrayToolBarImageList; // toolbar in gray version (calculated from color) +extern HIMAGELIST HHotToolBarImageList; // toolbar in color version extern HIMAGELIST HBottomTBImageList; // bottom toolbar (F1 - F12) extern HIMAGELIST HHotBottomTBImageList; // bottom toolbar (F1 - F12) @@ -1685,6 +1708,7 @@ extern int RelExceptionHasOccured; // has any call of IUnknown method Releas extern BOOL SalamanderBusy; // is Salamander busy? extern DWORD LastSalamanderIdleTime; // GetTickCount() from the moment when SalamanderBusy last transitioned to TRUE +extern BOOL FileManagerUiStartupReady; // TRUE only after all synchronous startup and plug-in initialization is complete extern int PasteLinkIsRunning; // if greater than zero, Past Shortcuts command is running in one of the panels @@ -1839,9 +1863,20 @@ struct CSVGIcon const char* SVGName; }; +// Logical toolbar sizes are persisted as pixels so the values remain stable across DPI changes. +enum CToolbarIconSize +{ + TOOLBAR_ICON_SIZE_SMALL = 16, + TOOLBAR_ICON_SIZE_MEDIUM = 24, + TOOLBAR_ICON_SIZE_LARGE = 32, +}; + +BOOL IsValidToolbarIconSize(int iconSize); +int GetToolbarIconSizeForSystemDPI(); + BOOL CreateToolbarBitmaps(HINSTANCE hInstance, int resID, COLORREF transparent, COLORREF bkColorForAlpha, HBITMAP& hMaskBitmap, HBITMAP& hGrayBitmap, HBITMAP& hColorBitmap, BOOL appendIcons, - const CSVGIcon* svgIcons, int svgIconsCount); + const CSVGIcon* svgIcons, int svgIconsCount, int iconSize); //**************************************************************************** // @@ -2087,7 +2122,7 @@ int GetWMCommandFromSalCmd(int salCmd); //****************************************************************************** // number of items in SalamanderConfigurationRoots array -#define SALCFG_ROOTS_COUNT 83 +#define SALCFG_ROOTS_COUNT 84 // main thread id (valid only after entering WinMain()) extern DWORD MainThreadID; diff --git a/src/dialogs.h b/src/dialogs.h index c34f70c8..de3b44b0 100644 --- a/src/dialogs.h +++ b/src/dialogs.h @@ -330,7 +330,6 @@ class CProgressDialog : public CCommonDialog HANDLE Worker; // worker thread associated with this dialog (NULL if it doesn't exist yet/any more) HANDLE WContinue; // multi-purpose event HANDLE WorkerNotSuspended; // non-signaled == the worker should enter suspend mode - BOOL CancelWorker; // if TRUE, the worker thread will terminate int OperationProgress; // progress value shared with the worker thread int SummaryProgress; // progress value shared with the worker thread BOOL ShowPause; // the "pause" button text: TRUE = pause, FALSE = resume @@ -348,7 +347,7 @@ class CProgressDialog : public CCommonDialog *Target, *Status; COperations* Script; - char Caption[50]; + char Caption[100]; // includes the dispatch ID shown in concurrent operation dialogs CChangeAttrsData* AttrsData; CConvertData* ConvertData; BOOL AcceptCommands; diff --git a/src/dialogs_attributes.cpp b/src/dialogs_attributes.cpp index 24890303..32fb25e9 100644 --- a/src/dialogs_attributes.cpp +++ b/src/dialogs_attributes.cpp @@ -2749,7 +2749,12 @@ CWaitWindow::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam) { // if the user clicked the Close button, set the global variable if (CWindow::WindowProc(WM_NCHITTEST, NULL, GetMessagePos()) == HTCLOSE) + { SafeWaitWindowClosePressed = TRUE; + // Wake operations waiting on the close action without making + // them depend on a periodic timeout to observe cancellation. + SignalSafeWaitWindowCancellation(); + } } return 0; } diff --git a/src/dialogs_config_general.cpp b/src/dialogs_config_general.cpp index 3fb6350c..1f7f264d 100644 --- a/src/dialogs_config_general.cpp +++ b/src/dialogs_config_general.cpp @@ -411,6 +411,8 @@ CConfiguration::CConfiguration() strcpy(MiddleToolBar, DefMiddleToolBar); strcpy(LeftToolBar, DefLeftToolBar); strcpy(RightToolBar, DefRightToolBar); + // Preserve the historical 16-pixel appearance for existing and fresh profiles. + ToolbarIconSize = TOOLBAR_ICON_SIZE_SMALL; SkillLevel = SKILL_LEVEL_ADVANCED; // try to present as many options as possible diff --git a/src/dialogs_file_ops.cpp b/src/dialogs_file_ops.cpp index 234f16fd..4d3989bb 100644 --- a/src/dialogs_file_ops.cpp +++ b/src/dialogs_file_ops.cpp @@ -16,6 +16,7 @@ #include "menu.h" #include "consts.h" #include "utf8gui.h" +#include "release_diagnostics.h" CConfiguration Configuration; @@ -384,6 +385,8 @@ static DWORD WaitForProgressDialogStartup(HANDLE readyEvent, HANDLE failedEvent) DWORD wait = MsgWaitForMultipleObjects(2, events, FALSE, PROGRESS_DIALOG_STARTUP_TIMEOUT - elapsed, QS_PAINT); + // Startup waits are retained as a compact sequence for post-failure diagnosis. + RecordReleaseDiagnosticWait("progress_dialog_startup", wait); if (wait == WAIT_OBJECT_0 || wait == WAIT_OBJECT_0 + 1 || wait == WAIT_TIMEOUT || wait == WAIT_FAILED) return wait; @@ -527,11 +530,11 @@ CProgressDialog::CProgressDialog(HWND parent, COperations* script, const char* c Worker = NULL; WContinue = NULL; WorkerNotSuspended = NULL; - CancelWorker = FALSE; OperationProgress = 0; SummaryProgress = 0; - strcpy(Caption, caption); Script = script; + // Keep the operation ID visible at the UI boundary where users resolve retries and cancellations. + _snprintf_s(Caption, _countof(Caption), _TRUNCATE, "%s [#%s]", caption, script->GetCorrelationId()); AttrsData = attrsData; AcceptCommands = TRUE; CanClose = TRUE; @@ -817,7 +820,7 @@ CProgressDialog::DialogProc(UINT uMsg, WPARAM wParam, LPARAM lParam) } Worker = StartWorker(Script, HWindow, AttrsData, ConvertData, WContinue, - WorkerNotSuspended, &CancelWorker, &OperationProgress, + WorkerNotSuspended, &OperationProgress, &SummaryProgress); if (Worker == NULL) { @@ -842,7 +845,7 @@ CProgressDialog::DialogProc(UINT uMsg, WPARAM wParam, LPARAM lParam) { // The UI reached its deadline after handing us the script. The // worker exists now, so cancel it through the normal dialog path. - CancelWorker = TRUE; + Script->RequestCancellation(); SetEvent(WorkerNotSuspended); if (!startupReady) ProgrDlgData->ReportStartupFailed(); @@ -910,7 +913,7 @@ CProgressDialog::DialogProc(UINT uMsg, WPARAM wParam, LPARAM lParam) //--- worker request to show a dialog case WM_USER_DIALOG: { - if (CancelWorker) + if (Script != NULL && Script->IsCancellationRequested()) return TRUE; // should terminate, it should not request anything BOOL canFlash = RunningInOwnThread; @@ -1047,8 +1050,21 @@ CProgressDialog::DialogProc(UINT uMsg, WPARAM wParam, LPARAM lParam) *(int*)data[0] = (int)dlg.Execute(); break; } + + case 13: + { + *(int*)data[0] = SalMessageBox(HWindow, data[1], LoadStr(IDS_QUESTION), + MB_YESNO | MB_DEFBUTTON2 | MB_ICONEXCLAMATION | + MSGBOXEX_ESCAPEENABLED); + break; + } } + // The response is synchronous with the worker, making this the single + // place to count every user-approved retry across operation dialogs. + if (wParam != 5 && Script != NULL && *(int*)data[0] == IDRETRY) + Script->RecordItemRetry(); + StatusPaused = FALSE; // now time-left and speed can be displayed again if (Status != NULL && ShowPause) @@ -1215,7 +1231,7 @@ CProgressDialog::DialogProc(UINT uMsg, WPARAM wParam, LPARAM lParam) case WM_USER_BUTTONDROPDOWN: { - if (!AcceptCommands || !CanClose || CancelWorker || Script == NULL) + if (!AcceptCommands || !CanClose || Script == NULL || Script->IsCancellationRequested()) return 0; Script->ChangeSpeedLimit = TRUE; // the speed limit may change, let the worker stop at an "appropriate" point @@ -1360,7 +1376,8 @@ MENU_TEMPLATE_ITEM ProgressDialogMenu2[] = { if (!IsWindowEnabled(HWindow)) // there is a modal dialog above this dialog (a message box asking about operation canceling or reporting an error) CloseAllOwnedEnabledDialogs(HWindow); - CancelWorker = TRUE; // set worker cancel + if (Script != NULL) + Script->RequestCancellation(); EnableWindow(GetDlgItem(HWindow, IDB_PAUSERESUME), FALSE); if (WorkerNotSuspended != NULL) SetEvent(WorkerNotSuspended); // so that Cancel proceeds even after Pause is pressed @@ -1369,6 +1386,44 @@ MENU_TEMPLATE_ITEM ProgressDialogMenu2[] = return TRUE; // message processed } + case WM_USER_PROGRDLG_WORKERCOMPLETE: + { + // The worker has already released Script and is not waiting for this + // handler. This message is therefore safe to process after a modal + // path, a user cancel, or shutdown cancellation has delayed the UI. + CWorkerCompletion* completion = (CWorkerCompletion*)lParam; + if (!IsWindowEnabled(HWindow)) // if a modal dialog is open we must locate and close it + CloseAllOwnedEnabledDialogs(HWindow); + + Script = NULL; // it was freed by the worker before posting this message + BOOL cancelled = completion != NULL ? completion->CancellationRequested : (BOOL)wParam; + BOOL failed = completion != NULL ? completion->State == opsFailed : (BOOL)wParam; + if (completion == NULL) + TRACE_E("CProgressDialog: worker completion result could not be allocated."); + delete completion; // ownership was transferred by PostMessage + + // The worker no longer depends on the UI. Joining it here is only + // handle cleanup and cannot recreate the old worker-to-UI circular wait. + if (Worker != NULL) + { + WaitForSingleObject(Worker, INFINITE); + HANDLES(CloseHandle(Worker)); + Worker = NULL; + } + HANDLES(CloseHandle(WorkerNotSuspended)); + WorkerNotSuspended = NULL; + HANDLES(CloseHandle(WContinue)); + WContinue = NULL; + if (RunningInOwnThread) + ProgressDlgArray.ClearDlgWindow(HWindow); + wParam = cancelled || failed ? IDCANCEL : IDOK; + + if (!DoNotBeepOnClose && Configuration.MinBeepWhenDone && GetForegroundWindow() != HWindow) + MessageBeep(0); + PostMessage(HWindow, WM_USER_PROGRDLGEND, wParam, 0); // delay ending the dialog a bit + return TRUE; + } + case WM_USER_PROGRDLG_UPDATEICON: { SetWindowIcon(); @@ -1458,35 +1513,9 @@ MENU_TEMPLATE_ITEM ProgressDialogMenu2[] = switch (LOWORD(wParam)) { - case IDOK: // operation finished, called only by the worker, must not come from the dialog - { - if (!IsWindowEnabled(HWindow)) // if a modal dialog is open we must locate and close it - CloseAllOwnedEnabledDialogs(HWindow); - if (InSendMessage()) - ReplyMessage(0); // let the worker continue - Script = NULL; // script is freed in the worker thread, so we must not use it here anymore - SetEvent(WContinue); // worker may start deleting the script - WaitForSingleObject(Worker, INFINITE); // wait until it finishes and exits - HANDLES(CloseHandle(Worker)); - Worker = NULL; - HANDLES(CloseHandle(WorkerNotSuspended)); - WorkerNotSuspended = NULL; - HANDLES(CloseHandle(WContinue)); - WContinue = NULL; - if (RunningInOwnThread) - ProgressDlgArray.ClearDlgWindow(HWindow); - if (CancelWorker) - wParam = IDCANCEL; - - if (!DoNotBeepOnClose && Configuration.MinBeepWhenDone && GetForegroundWindow() != HWindow) - MessageBeep(0); - PostMessage(HWindow, WM_USER_PROGRDLGEND, wParam, lParam); // probably needless on W2K+: delay ending the dialog a bit - return TRUE; - } - case IDCANCEL: // abort { - if (CanClose && !CancelWorker) + if (CanClose && Script != NULL && !Script->IsCancellationRequested()) { ResetEvent(WorkerNotSuspended); @@ -1516,7 +1545,7 @@ MENU_TEMPLATE_ITEM ProgressDialogMenu2[] = if (ret == IDYES) { - CancelWorker = TRUE; // set cancel of the worker + Script->RequestCancellation(); EnableWindow(GetDlgItem(HWindow, IDB_PAUSERESUME), FALSE); } else @@ -1529,7 +1558,7 @@ MENU_TEMPLATE_ITEM ProgressDialogMenu2[] = Script->InitSpeedMeters(TRUE); } } - if ((CancelWorker || ShowPause) && // only if it's Cancel or the operation isn't paused + if ((Script->IsCancellationRequested() || ShowPause) && // only if it's Cancel or the operation isn't paused WorkerNotSuspended != NULL) { SetEvent(WorkerNotSuspended); // may be NULL if the message box was closed from IDOK via WM_CLOSE @@ -1541,7 +1570,7 @@ MENU_TEMPLATE_ITEM ProgressDialogMenu2[] = case CM_RESUMEOPER: // resume posted from the operations queue case IDB_PAUSERESUME: // pause/resume { - if ((LOWORD(wParam) == CM_RESUMEOPER || AcceptCommands) && CanClose && !CancelWorker) + if ((LOWORD(wParam) == CM_RESUMEOPER || AcceptCommands) && CanClose && Script != NULL && !Script->IsCancellationRequested()) { AutoPaused = FALSE; // this may be a manual pause/resume or an automatic resume BOOL speedMetersInitCalled = FALSE; diff --git a/src/drivelst.cpp b/src/drivelst.cpp index 5c09a798..e4a1dcc7 100644 --- a/src/drivelst.cpp +++ b/src/drivelst.cpp @@ -1798,7 +1798,8 @@ BOOL CDrivesList::BuildData(BOOL noTimeout, TDirectArray* copyDrives else volumeName[0] = 0; if (thread != NULL) - AddAuxThread(thread, TRUE); // if the thread is not finished, we will kill it before closing the software + // The probe reads shared volume-name storage during its safe shutdown join. + AddAuxThread(thread, TRUE, "removable-drive volume probe"); if (volumeName[0] == 0) strcpy(volumeName, LoadStr(IDS_COMPACT_DISK)); else @@ -2650,7 +2651,8 @@ BOOL CDrivesList::GetDriveBarToolTip(int index, char* text) else volumeName[0] = 0; if (thread != NULL) - AddAuxThread(thread, TRUE); // if the thread is still running, we will kill it before closing the program + // The probe reads shared volume-name storage during its safe shutdown join. + AddAuxThread(thread, TRUE, "removable-drive volume probe"); if (volumeName[0] == 0) strcpy(volumeName, LoadStr(IDS_COMPACT_DISK)); diff --git a/src/drivelst.h b/src/drivelst.h index 1782fc25..cba8d09e 100644 --- a/src/drivelst.h +++ b/src/drivelst.h @@ -223,7 +223,8 @@ struct CNBWNetAC3Thread { if (Thread != NULL) { - AddAuxThread(Thread, TRUE); // thread can run only during shutdown, we will kill it by this + // Network browsing can access its owner while it unwinds, so retain it for a safe join. + AddAuxThread(Thread, TRUE, "network-browser worker"); Thread = NULL; } if (shutdown) diff --git a/src/execlog.cpp b/src/execlog.cpp index d41036e0..335bfdd7 100644 --- a/src/execlog.cpp +++ b/src/execlog.cpp @@ -90,25 +90,38 @@ void ExecLogFileListingResult(const char* path, BOOL success, int fileCount, int ExecLogWrite(success, message); } -void ExecLogFileOperationStart(const char* operation, const char* source, const char* target) +void ExecLogFileOperationStart(const char* operationId, int itemSequence, int attempt, + const char* operation, const char* source, const char* target) { char message[512]; + // Every operation log line carries dispatch and retry context for interleaved workers. _snprintf_s(message, _TRUNCATE, - "op start: op=%s, source=%s, target=%s", - ExecLogSafeStr(operation), ExecLogSafeStr(source), ExecLogSafeStr(target)); + "op start: operation=%s, item=%d, attempt=%d, op=%s, source=%s, target=%s", + ExecLogSafeStr(operationId), itemSequence, attempt, ExecLogSafeStr(operation), + ExecLogSafeStr(source), ExecLogSafeStr(target)); ExecLogWrite(TRUE, message); } -void ExecLogFileOperationResult(const char* operation, const char* source, const char* target, BOOL success) +void ExecLogFileOperationResult(const char* operationId, int itemSequence, int attempt, + const char* operation, const char* source, const char* target, BOOL success) { char message[512]; _snprintf_s(message, _TRUNCATE, - "op result: op=%s, source=%s, target=%s, success=%d", - ExecLogSafeStr(operation), ExecLogSafeStr(source), ExecLogSafeStr(target), - success ? 1 : 0); + "op result: operation=%s, item=%d, attempt=%d, op=%s, source=%s, target=%s, success=%d", + ExecLogSafeStr(operationId), itemSequence, attempt, ExecLogSafeStr(operation), + ExecLogSafeStr(source), ExecLogSafeStr(target), success ? 1 : 0); ExecLogWrite(success, message); } +void ExecLogFileOperationRetry(const char* operationId, int itemSequence, int attempt) +{ + char message[256]; + _snprintf_s(message, _TRUNCATE, + "op retry: operation=%s, item=%d, attempt=%d", + ExecLogSafeStr(operationId), itemSequence, attempt); + ExecLogWrite(TRUE, message); +} + void ExecLogFeatureStart(const char* feature, const char* detail) { char message[512]; diff --git a/src/execlog.h b/src/execlog.h index 9ebcf8cb..6a809ef4 100644 --- a/src/execlog.h +++ b/src/execlog.h @@ -12,8 +12,11 @@ void ExecLogFileListingStart(const char* path, BOOL isPlugin, const char* fsName void ExecLogFileListingResult(const char* path, BOOL success, int fileCount, int dirCount, BOOL isPlugin, const char* fsName); -void ExecLogFileOperationStart(const char* operation, const char* source, const char* target); -void ExecLogFileOperationResult(const char* operation, const char* source, const char* target, BOOL success); +void ExecLogFileOperationStart(const char* operationId, int itemSequence, int attempt, + const char* operation, const char* source, const char* target); +void ExecLogFileOperationResult(const char* operationId, int itemSequence, int attempt, + const char* operation, const char* source, const char* target, BOOL success); +void ExecLogFileOperationRetry(const char* operationId, int itemSequence, int attempt); void ExecLogFeatureStart(const char* feature, const char* detail); void ExecLogFeatureResult(const char* feature, const char* detail, BOOL success); @@ -51,19 +54,33 @@ inline void ExecLogFileListingResult(const char* path, BOOL success, int fileCou (void)fsName; } -inline void ExecLogFileOperationStart(const char* operation, const char* source, const char* target) +inline void ExecLogFileOperationStart(const char* operationId, int itemSequence, int attempt, + const char* operation, const char* source, const char* target) { + (void)operationId; + (void)itemSequence; + (void)attempt; (void)operation; (void)source; (void)target; } -inline void ExecLogFileOperationResult(const char* operation, const char* source, const char* target, BOOL success) +inline void ExecLogFileOperationResult(const char* operationId, int itemSequence, int attempt, + const char* operation, const char* source, const char* target, BOOL success) { + (void)operationId; + (void)itemSequence; + (void)attempt; (void)operation; (void)source; (void)target; (void)success; } +inline void ExecLogFileOperationRetry(const char* operationId, int itemSequence, int attempt) +{ + (void)operationId; + (void)itemSequence; + (void)attempt; +} inline void ExecLogFeatureStart(const char* feature, const char* detail) { diff --git a/src/file_identity.cpp b/src/file_identity.cpp new file mode 100644 index 00000000..a9db5d95 --- /dev/null +++ b/src/file_identity.cpp @@ -0,0 +1,238 @@ +// SPDX-FileCopyrightText: 2026 Taskscape Ltd +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "precomp.h" + +#include "common/scoped_kernel_handle.h" +#include "file_operation_filesystem.h" +#include "worker.h" + +namespace +{ +const DWORD FileIdentityNotCaptured = 0; +const DWORD FileIdentityAbsent = 1; +const DWORD FileIdentityPresent = 2; + +unsigned __int64 HashFinalPath(HANDLE handle, DWORD* error) +{ + DWORD length = GetFinalPathNameByHandleW(handle, NULL, 0, FILE_NAME_NORMALIZED | VOLUME_NAME_DOS); + if (length == 0) + { + *error = GetLastError(); + return 0; + } + + WCHAR* path = (WCHAR*)malloc((length + 1) * sizeof(WCHAR)); + if (path == NULL) + { + *error = ERROR_NOT_ENOUGH_MEMORY; + return 0; + } + DWORD copied = GetFinalPathNameByHandleW(handle, path, length + 1, FILE_NAME_NORMALIZED | VOLUME_NAME_DOS); + if (copied == 0 || copied > length) + { + *error = GetLastError(); + free(path); + return 0; + } + + // FNV-1a gives a compact, case-preserving record of the path which the + // handle actually opened. The file ID remains the primary identity. + unsigned __int64 hash = 1469598103934665603ULL; + DWORD i; + for (i = 0; i < copied; i++) + { + hash ^= (unsigned __int64)path[i]; + hash *= 1099511628211ULL; + } + free(path); + return hash; +} + +BOOL ReadFileIdentity(HANDLE handle, COperation::CFileIdentity* identity, DWORD* error) +{ + BY_HANDLE_FILE_INFORMATION information; + if (!GetFileInformationByHandle(handle, &information)) + { + *error = GetLastError(); + return FALSE; + } + + unsigned __int64 finalPathHash = HashFinalPath(handle, error); + if (finalPathHash == 0) + return FALSE; + + identity->State = FileIdentityPresent; + identity->VolumeSerialNumber = information.dwVolumeSerialNumber; + identity->FileIndexHigh = information.nFileIndexHigh; + identity->FileIndexLow = information.nFileIndexLow; + identity->FinalPathHash = finalPathHash; + return TRUE; +} + +BOOL CaptureFileIdentity(const char* path, COperation::CFileIdentity* identity, BOOL allowAbsent, DWORD* error) +{ + memset(identity, 0, sizeof(*identity)); + identity->State = FileIdentityNotCaptured; + // Identity capture often exits through validation failures, so keep its + // tracked file handle owned until the complete record has been produced. + CScopedKernelHandle handle(HANDLES_Q(CreateFileUtf8(path, FILE_READ_ATTRIBUTES, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, + NULL))); + if (!handle.IsValid()) + { + *error = GetLastError(); + if (allowAbsent && (*error == ERROR_FILE_NOT_FOUND || *error == ERROR_PATH_NOT_FOUND)) + { + identity->State = FileIdentityAbsent; + *error = ERROR_SUCCESS; + return TRUE; + } + return FALSE; + } + + BOOL result = ReadFileIdentity(handle.Get(), identity, error); + DWORD closeError; + if (!handle.Close(&closeError) && result) + { + *error = closeError; + result = FALSE; + } + return result; +} + +BOOL SameFileIdentity(const COperation::CFileIdentity& left, const COperation::CFileIdentity& right) +{ + return left.State == right.State && + (left.State == FileIdentityAbsent || + (left.State == FileIdentityPresent && + left.VolumeSerialNumber == right.VolumeSerialNumber && + left.FileIndexHigh == right.FileIndexHigh && + left.FileIndexLow == right.FileIndexLow && + left.FinalPathHash == right.FinalPathHash)); +} + +BOOL OpenVerifiedFileForDelete(const char* path, const COperation::CFileIdentity& expected, + CScopedKernelHandle* handle, DWORD* error) +{ + handle->Reset(); + if (expected.State != FileIdentityPresent) + { + *error = ERROR_INVALID_DATA; + return FALSE; + } + + // The output wrapper makes the verified handle's ownership transfer clear + // until deletion has either committed or returned a recoverable error. + handle->Reset(HANDLES_Q(CreateFileUtf8(path, DELETE | FILE_READ_ATTRIBUTES, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, + NULL))); + if (!handle->IsValid()) + { + *error = GetLastError(); + return FALSE; + } + + COperation::CFileIdentity actual; + if (!ReadFileIdentity(handle->Get(), &actual, error) || !SameFileIdentity(expected, actual)) + { + if (*error == ERROR_SUCCESS) + *error = ERROR_INVALID_DATA; + return FALSE; + } + return TRUE; +} +} // namespace + +BOOL CaptureOperationFileIdentities(COperation* operation, DWORD* error) +{ + memset(&operation->SourceIdentity, 0, sizeof(operation->SourceIdentity)); + memset(&operation->TargetIdentity, 0, sizeof(operation->TargetIdentity)); + + BOOL needsSourceIdentity = operation->Opcode == ocMoveFile || operation->Opcode == ocMoveDir || + operation->Opcode == ocDeleteFile || operation->Opcode == ocDeleteDir || + operation->Opcode == ocDeleteDirLink; + BOOL needsTargetIdentity = operation->Opcode == ocCopyFile || operation->Opcode == ocMoveFile || + operation->Opcode == ocMoveDir; + if (needsSourceIdentity && !CaptureFileIdentity(operation->SourceName, &operation->SourceIdentity, FALSE, error)) + return FALSE; + if (needsTargetIdentity && !CaptureFileIdentity(operation->TargetName, &operation->TargetIdentity, TRUE, error)) + return FALSE; + *error = ERROR_SUCCESS; + return TRUE; +} + +BOOL VerifyFileIdentity(const char* path, const COperation::CFileIdentity& expected, DWORD* error) +{ + if (expected.State == FileIdentityNotCaptured) + { + *error = ERROR_INVALID_DATA; + return FALSE; + } + COperation::CFileIdentity actual; + if (!CaptureFileIdentity(path, &actual, expected.State == FileIdentityAbsent, error)) + return FALSE; + if (!SameFileIdentity(expected, actual)) + { + *error = ERROR_INVALID_DATA; + return FALSE; + } + *error = ERROR_SUCCESS; + return TRUE; +} + +BOOL VerifyFileHandleIdentity(HANDLE handle, const COperation::CFileIdentity& expected, DWORD* error) +{ + if (expected.State != FileIdentityPresent) + { + *error = ERROR_INVALID_DATA; + return FALSE; + } + COperation::CFileIdentity actual; + if (!ReadFileIdentity(handle, &actual, error)) + return FALSE; + if (!SameFileIdentity(expected, actual)) + { + *error = ERROR_INVALID_DATA; + return FALSE; + } + *error = ERROR_SUCCESS; + return TRUE; +} + +BOOL DeleteFileWithVerifiedIdentity(const char* path, const COperation::CFileIdentity& expected, DWORD* error) +{ + CScopedKernelHandle handle; + if (!OpenVerifiedFileForDelete(path, expected, &handle, error)) + return FALSE; + + FILE_DISPOSITION_INFO disposition; + disposition.DeleteFile = TRUE; + BOOL result = OperationExecutionFileSystem().SetFileInformationByHandle(handle.Get(), FileDispositionInfo, + &disposition, sizeof(disposition)); + if (!result && GetLastError() == ERROR_ACCESS_DENIED) + { + FILE_BASIC_INFO basicInfo; + if (GetFileInformationByHandleEx(handle.Get(), FileBasicInfo, &basicInfo, sizeof(basicInfo))) + { + basicInfo.FileAttributes &= ~FILE_ATTRIBUTE_READONLY; + if (OperationExecutionFileSystem().SetFileInformationByHandle(handle.Get(), FileBasicInfo, + &basicInfo, sizeof(basicInfo))) + result = OperationExecutionFileSystem().SetFileInformationByHandle(handle.Get(), FileDispositionInfo, + &disposition, sizeof(disposition)); + } + } + if (!result) + *error = GetLastError(); + DWORD closeError; + if (!handle.Close(&closeError) && result) + { + *error = closeError; + result = FALSE; + } + return result; +} diff --git a/src/file_operation_filesystem.cpp b/src/file_operation_filesystem.cpp new file mode 100644 index 00000000..909e9476 --- /dev/null +++ b/src/file_operation_filesystem.cpp @@ -0,0 +1,116 @@ +// SPDX-FileCopyrightText: 2026 Taskscape Ltd +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "precomp.h" + +#include "file_operation_filesystem.h" + +namespace +{ +class CWin32FileOperationFileSystem : public CFileOperationFileSystem +{ +public: + DWORD GetAttributes(const char* path) override + { + return SalGetFileAttributes(path); + } + + BOOL GetDiskFreeSpace(const char* path, DWORD* sectorsPerCluster, + DWORD* bytesPerSector, DWORD* freeClusters, + DWORD* totalClusters) override + { + return MyGetDiskFreeSpace(path, sectorsPerCluster, bytesPerSector, freeClusters, totalClusters); + } + + CQuadWord QueryFreeSpace(const char* path) override + { + return MyGetDiskFreeSpace(path); + } +}; + +CWin32FileOperationFileSystem Win32FileOperationFileSystem; +CFileOperationFileSystem* TestFileOperationFileSystem = NULL; + +class CWin32OperationExecutionFileSystem : public COperationExecutionFileSystem +{ +public: + HANDLE CreateFile(const char* path, DWORD desiredAccess, DWORD shareMode, + DWORD creationDisposition, DWORD flagsAndAttributes) override + { + return CreateFileUtf8(path, desiredAccess, shareMode, NULL, creationDisposition, + flagsAndAttributes, NULL); + } + + BOOL WriteFile(HANDLE file, const void* buffer, DWORD bytesToWrite, + DWORD* bytesWritten, LPOVERLAPPED overlapped) override + { + return ::WriteFile(file, buffer, bytesToWrite, bytesWritten, overlapped); + } + + BOOL SetFileTime(HANDLE file, const FILETIME* creationTime, + const FILETIME* lastAccessTime, const FILETIME* lastWriteTime) override + { + return ::SetFileTime(file, creationTime, lastAccessTime, lastWriteTime); + } + + BOOL FlushFileBuffers(HANDLE file) override + { + return ::FlushFileBuffers(file); + } + + BOOL ReplaceFile(const char* replacedFileName, const char* replacementFileName) override + { + CStrP replacedFileNameW(ConvertAllocUtf8ToWide(replacedFileName, -1)); + CStrP replacementFileNameW(ConvertAllocUtf8ToWide(replacementFileName, -1)); + if (replacedFileNameW == NULL || replacementFileNameW == NULL) + { + SetLastError(ERROR_NO_UNICODE_TRANSLATION); + return FALSE; + } + return ReplaceFileW(replacedFileNameW, replacementFileNameW, NULL, + REPLACEFILE_WRITE_THROUGH, NULL, NULL); + } + + BOOL MoveFile(const char* existingFileName, const char* newFileName) override + { + CStrP existingFileNameW(ConvertAllocUtf8ToWide(existingFileName, -1)); + CStrP newFileNameW(ConvertAllocUtf8ToWide(newFileName, -1)); + if (existingFileNameW == NULL || newFileNameW == NULL) + { + SetLastError(ERROR_NO_UNICODE_TRANSLATION); + return FALSE; + } + return MoveFileExW(existingFileNameW, newFileNameW, + MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH); + } + + BOOL SetFileInformationByHandle(HANDLE file, FILE_INFO_BY_HANDLE_CLASS informationClass, + void* information, DWORD informationSize) override + { + return ::SetFileInformationByHandle(file, informationClass, information, informationSize); + } +}; + +CWin32OperationExecutionFileSystem Win32OperationExecutionFileSystem; +COperationExecutionFileSystem* TestOperationExecutionFileSystem = NULL; +} + +CFileOperationFileSystem& FileOperationFileSystem() +{ + return TestFileOperationFileSystem != NULL ? *TestFileOperationFileSystem : Win32FileOperationFileSystem; +} + +void SetFileOperationFileSystemForTests(CFileOperationFileSystem* replacement) +{ + TestFileOperationFileSystem = replacement; +} + +COperationExecutionFileSystem& OperationExecutionFileSystem() +{ + return TestOperationExecutionFileSystem != NULL ? *TestOperationExecutionFileSystem : Win32OperationExecutionFileSystem; +} + +void SetOperationExecutionFileSystemForTests(COperationExecutionFileSystem* replacement) +{ + TestOperationExecutionFileSystem = replacement; +} diff --git a/src/file_operation_filesystem.h b/src/file_operation_filesystem.h new file mode 100644 index 00000000..de1efa5e --- /dev/null +++ b/src/file_operation_filesystem.h @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: 2026 Taskscape Ltd +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +// Planning needs only filesystem facts. Keep this deliberately narrow: the +// adapter does not create, delete, copy, or mutate anything, and test code can +// replace it while characterizing the generated COperationPlan. +class CFileOperationFileSystem +{ +public: + virtual ~CFileOperationFileSystem() {} + + virtual DWORD GetAttributes(const char* path) = 0; + virtual BOOL GetDiskFreeSpace(const char* path, DWORD* sectorsPerCluster, + DWORD* bytesPerSector, DWORD* freeClusters, + DWORD* totalClusters) = 0; + virtual CQuadWord QueryFreeSpace(const char* path) = 0; +}; + +CFileOperationFileSystem& FileOperationFileSystem(); + +// Intended for a single-threaded characterization test during planning. The +// caller owns the replacement and must restore NULL before it is destroyed. +void SetFileOperationFileSystemForTests(CFileOperationFileSystem* replacement); + +// Execution has a separate seam from planning. It is intentionally limited to +// the durable copy/move boundaries so a native test can make one Win32 call +// fail without changing worker, dialog, or operation-script behaviour. +class COperationExecutionFileSystem +{ +public: + virtual ~COperationExecutionFileSystem() {} + + virtual HANDLE CreateFile(const char* path, DWORD desiredAccess, DWORD shareMode, + DWORD creationDisposition, DWORD flagsAndAttributes) = 0; + virtual BOOL WriteFile(HANDLE file, const void* buffer, DWORD bytesToWrite, + DWORD* bytesWritten, LPOVERLAPPED overlapped) = 0; + virtual BOOL SetFileTime(HANDLE file, const FILETIME* creationTime, + const FILETIME* lastAccessTime, const FILETIME* lastWriteTime) = 0; + virtual BOOL FlushFileBuffers(HANDLE file) = 0; + virtual BOOL ReplaceFile(const char* replacedFileName, const char* replacementFileName) = 0; + virtual BOOL MoveFile(const char* existingFileName, const char* newFileName) = 0; + virtual BOOL SetFileInformationByHandle(HANDLE file, FILE_INFO_BY_HANDLE_CLASS informationClass, + void* information, DWORD informationSize) = 0; +}; + +COperationExecutionFileSystem& OperationExecutionFileSystem(); + +// Intended for a single-threaded native fault-injection test. A fake can fail +// any selected call deterministically and must be restored before destruction. +void SetOperationExecutionFileSystemForTests(COperationExecutionFileSystem* replacement); diff --git a/src/fileswindow_init.cpp b/src/fileswindow_init.cpp index 53445bdd..9c2be0ca 100644 --- a/src/fileswindow_init.cpp +++ b/src/fileswindow_init.cpp @@ -16,6 +16,7 @@ #include "shellib.h" #include "pack.h" #include "thumbnl.h" +#include "parserbroker.h" #include "geticon.h" #include "shiconov.h" @@ -896,31 +897,24 @@ unsigned IconThreadThreadFBody(void* parameter) shi.hIcon = NULL; // precaution against incorrect icon deallocation (none is created here) char* s = iconData->NameAndData; - int len = (int)strlen(s); - int size = len + 4; - size -= (size & 0x3); // size % 4 (alignment to four bytes) if (strlen(s) + (name - path) < MAX_PATH) { strcpy(name, s); // TRACE_I("Load thumbnail for: " << name << "..."); - CPluginInterfaceForThumbLoaderEncapsulation** loader; - loader = (CPluginInterfaceForThumbLoaderEncapsulation**)(s + size + sizeof(CQuadWord) + sizeof(FILETIME)); - while (*loader != NULL) + int thumbnailSize = window->GetThumbnailSize(); + thumbMaker.Clear(thumbnailSize); + CALL_STACK_MESSAGE3("IconThreadThreadFBody::LoadThumbnailInBroker(%s, %d)", path, wanted == 4); + // Thumbnail providers parse attacker-controlled bytes. Do not fall + // back to their in-process SDK callback when the broker is unavailable: + // a missing thumbnail is recoverable, a corrupted host is not. + if (ParserBroker.LoadThumbnail(path, thumbnailSize, thumbnailSize, wanted == 4, &thumbMaker)) { - int thumbnailSize = window->GetThumbnailSize(); - thumbMaker.Clear(thumbnailSize); - CALL_STACK_MESSAGE3("IconThreadThreadFBody::LoadThumbnail(%s, %d)", path, wanted == 4); - if ((*loader)->LoadThumbnail(path, thumbnailSize, thumbnailSize, &thumbMaker, wanted == 4)) - { - thumbnailFlag = wanted == 4 /* first thumbnail loading round */ ? (thumbMaker.IsOnlyPreview() ? 6 /* low-quality/smaller */ : 5 /* quality */) : 5 /* in the second round all obtained thumbnails are quality */; - thumbMaker.HandleIncompleteImages(); - break; // the thumbnail may be loaded; do not try another plug-in - } - loader++; // try the next plug-in in line, it might load the thumbnail + thumbnailFlag = wanted == 4 /* first thumbnail loading round */ ? (thumbMaker.IsOnlyPreview() ? 6 /* low-quality/smaller */ : 5 /* quality */) : 5 /* in the second round all obtained thumbnails are quality */; + thumbMaker.HandleIncompleteImages(); } - if (*loader == NULL) - thumbMaker.Clear(); // failed thumbnail -> clean it up + else + thumbMaker.Clear(); // failed broker job -> clean it up // TRACE_I("Load thumbnail is done."); } else @@ -1497,14 +1491,9 @@ CFilesWindow::~CFilesWindow() if (IconCacheThread != NULL) { SetEvent(ICEventTerminate); // icon reader, terminate yourself! - if (WaitForSingleObject(IconCacheThread, 1000) == WAIT_TIMEOUT) - { - // Shell icon handlers can be slow or uncooperative. Keep this panel alive - // until its worker leaves rather than corrupting process-wide state with - // forced termination. - TRACE_E("Icon reader did not stop within the shutdown deadline; waiting for a safe join."); - WaitForSingleObject(IconCacheThread, INFINITE); - } + // Shell handlers can take time to return; panel state stays alive until + // the worker has completed its bounded recovery phase and safe join. + CThreadShutdownDeadline("panel icon reader").WaitForSafeJoin(IconCacheThread); HANDLES(CloseHandle(IconCacheThread)); } diff --git a/src/fileswindow_navigation.cpp b/src/fileswindow_navigation.cpp index 1995bbcf..237dd305 100644 --- a/src/fileswindow_navigation.cpp +++ b/src/fileswindow_navigation.cpp @@ -34,6 +34,21 @@ int DeltaForTotalCount(int total) return delta; } +// A panel owns this many retained names and metadata records at most. The +// list box is count-based and paints only its visible range, so retaining more +// records would consume memory without making a large directory more usable. +#define DIRECTORY_ENUMERATION_MAX_CACHED_ITEMS 100000 + +// Check cancellation and give the safe-wait window's thread a scheduling +// opportunity at this cadence; a slow share must not defer cancellation until +// the next time-based ESC probe happens to run. +#define DIRECTORY_ENUMERATION_BATCH_SIZE 256 + +static BOOL IsDirectoryEnumerationBatchBoundary(int enumeratedItems) +{ + return enumeratedItems > 0 && enumeratedItems % DIRECTORY_ENUMERATION_BATCH_SIZE == 0; +} + static BOOL ConvertFindDataWToA(const WIN32_FIND_DATAW& src, WIN32_FIND_DATAA& dst) { ZeroMemory(&dst, sizeof(dst)); @@ -57,16 +72,15 @@ static BOOL ConvertFindDataWToA(const WIN32_FIND_DATAW& src, WIN32_FIND_DATAA& d static HANDLE FindFirstFileUtf8(const char* path, WIN32_FIND_DATAA& data) { - // Use stack buffer for common case (paths under MAX_PATH), heap for long paths - WCHAR stackBuf[MAX_PATH]; - CStrStackOrHeap pathW(path, stackBuf, MAX_PATH); - if (pathW == NULL) + CWidePath pathW(path); + const WCHAR* apiPath = pathW.GetPathForWin32Api(); + if (apiPath == NULL) { SetLastError(ERROR_NO_UNICODE_TRANSLATION); return INVALID_HANDLE_VALUE; } WIN32_FIND_DATAW dataW; - HANDLE h = HANDLES_Q(FindFirstFileW(pathW, &dataW)); + HANDLE h = HANDLES_Q(FindFirstFileW(apiPath, &dataW)); if (h != INVALID_HANDLE_VALUE) { if (!ConvertFindDataWToA(dataW, data)) @@ -363,6 +377,8 @@ BOOL CFilesWindow::ReadDirectory(HWND parent, BOOL isRefresh) file.IconOverlayIndex = ICONOVERLAYINDEX_NOTUSED; file.IconOverlayDone = 0; int len; + // This spans the shared ADD_ITEM label used by synthesized parent entries. + BOOL atBatchBoundary = FALSE; #ifndef _WIN64 int foundWin64RedirectedDirs = 0; BOOL isWin64RedirectedDir = FALSE; @@ -388,11 +404,11 @@ BOOL CFilesWindow::ReadDirectory(HWND parent, BOOL isRefresh) WIN32_FIND_DATAW fileDataW; WIN32_FIND_DATAA fileData; HANDLE search; + BOOL directoryEnumerationLimitReached = FALSE; - // Use stack buffer for common case (paths under MAX_PATH), heap for long paths - WCHAR fileNameStackBuf[MAX_PATH]; - CStrStackOrHeap fileNameW(fileName, fileNameStackBuf, MAX_PATH); - search = fileNameW != NULL ? HANDLES_Q(FindFirstFileW(fileNameW, &fileDataW)) : INVALID_HANDLE_VALUE; + CWidePath fileNameW(fileName); + const WCHAR* fileNameApiPath = fileNameW.GetPathForWin32Api(); + search = fileNameApiPath != NULL ? HANDLES_Q(FindFirstFileW(fileNameApiPath, &fileDataW)) : INVALID_HANDLE_VALUE; if (search == INVALID_HANDLE_VALUE) { @@ -500,8 +516,17 @@ BOOL CFilesWindow::ReadDirectory(HWND parent, BOOL isRefresh) { NumberOfItemsInCurDir++; - // test ESC - doesn't user want to interrupt reading? - if (GetTickCount() - lastEscCheckTime >= 200) // 5 times per second + atBatchBoundary = IsDirectoryEnumerationBatchBoundary(NumberOfItemsInCurDir); + if (atBatchBoundary) + { + // Yield only between bounded batches, preserving the synchronous + // panel ownership model while allowing the cancellation UI to run. + Sleep(0); + } + + // Test ESC at least once per batch; retain the time-based probe for + // sparse, high-latency enumeration calls that do not reach a batch. + if (atBatchBoundary || GetTickCount() - lastEscCheckTime >= 200) { if (UserWantsToCancelSafeWaitWindow()) { @@ -598,6 +623,17 @@ BOOL CFilesWindow::ReadDirectory(HWND parent, BOOL isRefresh) ADD_ITEM: // to add ".." + // Reserve one record for a synthesized parent entry so that a + // capped listing never traps the user in the displayed directory. + if (!isUpDir && Files->Count + Dirs->Count >= DIRECTORY_ENUMERATION_MAX_CACHED_ITEMS - 1) + { + // Stop at the metadata budget instead of retaining an + // unbounded directory-sized cache on a slow or huge share. + directoryEnumerationLimitReached = TRUE; + testFindNextErr = FALSE; + break; + } + //--- name file.Name = FileNamesArena.AllocString(st, len); // allocation from arena if (file.Name == NULL) @@ -988,7 +1024,9 @@ BOOL CFilesWindow::ReadDirectory(HWND parent, BOOL isRefresh) *(fileNameEnd - 1) = 0; // it's not logical, but times ".." are from current directory if (!UNCRootUpDir) { - search = fileNameW != NULL ? HANDLES_Q(FindFirstFileW(fileNameW, &fileDataW)) : INVALID_HANDLE_VALUE; + CWidePath parentFileNameW(fileName); + const WCHAR* parentFileNameApiPath = parentFileNameW.GetPathForWin32Api(); + search = parentFileNameApiPath != NULL ? HANDLES_Q(FindFirstFileW(parentFileNameApiPath, &fileDataW)) : INVALID_HANDLE_VALUE; if (search != INVALID_HANDLE_VALUE) ConvertFindDataWToA(fileDataW, fileData); } @@ -1075,7 +1113,9 @@ BOOL CFilesWindow::ReadDirectory(HWND parent, BOOL isRefresh) SetCurrentDirectoryToSystem(); - if (Files->Count + Dirs->Count == 0) + if (directoryEnumerationLimitReached) + StatusLine->SetText(LoadStr(IDS_DIRECTORYENUMERATIONLIMIT)); + else if (Files->Count + Dirs->Count == 0) StatusLine->SetText(LoadStr(IDS_NOFILESFOUND)); // sorting of Files and Dirs according to the current sorting method @@ -1945,10 +1985,9 @@ BOOL AddWin64RedirectedDirAux(const char* path, const char* subDir, const char* { HANDLE h; WIN32_FIND_DATAW dataW; - // Use stack buffer - findPath is MAX_PATH so it always fits - WCHAR findPathStackBuf[MAX_PATH]; - CStrStackOrHeap findPathW(findPath, findPathStackBuf, MAX_PATH); - h = findPathW != NULL ? HANDLES_Q(FindFirstFileW(findPathW, &dataW)) : INVALID_HANDLE_VALUE; // find-data for redirected-dir can be obtained from the "." directory in the listing of redirected-dir + CWidePath findPathW(findPath); + const WCHAR* findPathApiPath = findPathW.GetPathForWin32Api(); + h = findPathApiPath != NULL ? HANDLES_Q(FindFirstFileW(findPathApiPath, &dataW)) : INVALID_HANDLE_VALUE; // find-data for redirected-dir can be obtained from the "." directory in the listing of redirected-dir if (h != INVALID_HANDLE_VALUE) { BOOL found = FALSE; @@ -1975,10 +2014,9 @@ BOOL AddWin64RedirectedDirAux(const char* path, const char* subDir, const char* { WIN32_FIND_DATAW fdW; WIN32_FIND_DATAA fdA; - // Use stack buffer - findPath is MAX_PATH so it always fits - WCHAR findPath2StackBuf[MAX_PATH]; - CStrStackOrHeap findPath2W(findPath, findPath2StackBuf, MAX_PATH); - h = findPath2W != NULL ? HANDLES_Q(FindFirstFileW(findPath2W, &fdW)) : INVALID_HANDLE_VALUE; + CWidePath findPath2W(findPath); + const WCHAR* findPath2ApiPath = findPath2W.GetPathForWin32Api(); + h = findPath2ApiPath != NULL ? HANDLES_Q(FindFirstFileW(findPath2ApiPath, &fdW)) : INVALID_HANDLE_VALUE; if (h != INVALID_HANDLE_VALUE) { HANDLES(FindClose(h)); diff --git a/src/fileswindow_operations.cpp b/src/fileswindow_operations.cpp index 234b2f5f..14123b60 100644 --- a/src/fileswindow_operations.cpp +++ b/src/fileswindow_operations.cpp @@ -12,6 +12,7 @@ #include "fileswnd.h" #include "dialogs.h" #include "worker.h" +#include "file_operation_filesystem.h" #include "cache.h" #include "pack.h" #include "shellib.h" @@ -353,14 +354,15 @@ BOOL CFilesWindow::MoveFiles(const char* source, const char* target, const char* GetPathFlagsForCopyOp(targetDir, OPFL_TGTPATH_IS_NET, OPFL_TGTPATH_IS_FAST); script->TargetPathSupADS = targetSupADS; + script->TargetMetadataFileSystem = GetMetadataTargetFileSystem(targetDir); // script->TargetPathSupEFS = targetSupEFS; DWORD d1, d2, d3, d4; - if (MyGetDiskFreeSpace(targetDir, &d1, &d2, &d3, &d4)) + if (FileOperationFileSystem().GetDiskFreeSpace(targetDir, &d1, &d2, &d3, &d4)) { script->BytesPerCluster = d1 * d2; // W2K and later: the product d1 * d2 * d3 didn't work on DFS trees, reported by Ludek.Vydra@k2atmitec.cz - script->FreeSpace = MyGetDiskFreeSpace(targetDir); + script->FreeSpace = FileOperationFileSystem().QueryFreeSpace(targetDir); } BOOL scriptOK = TRUE; // result of script creation, success? @@ -621,6 +623,7 @@ BOOL CFilesWindow::BuildScriptMain2(COperations* script, BOOL copy, char* target BOOL targetIsFAT32 /*, targetSupEFS*/; BOOL targetSupADS = IsPathOnVolumeSupADS(targetPath, &targetIsFAT32); script->TargetPathSupADS = targetSupADS; + script->TargetMetadataFileSystem = GetMetadataTargetFileSystem(targetPath); DWORD srcAndTgtPathsFlags = GetPathFlagsForCopyOp(targetPath, OPFL_TGTPATH_IS_NET, OPFL_TGTPATH_IS_FAST); // script->TargetPathSupEFS = targetSupEFS; CTargetPathState targetPathState = GetTargetPathState(tpsUnknown, targetPath); @@ -631,11 +634,11 @@ BOOL CFilesWindow::BuildScriptMain2(COperations* script, BOOL copy, char* target usedNames = new TIndirectArray(100, 50); DWORD d1, d2, d3, d4; - if (MyGetDiskFreeSpace(targetPath, &d1, &d2, &d3, &d4)) + if (FileOperationFileSystem().GetDiskFreeSpace(targetPath, &d1, &d2, &d3, &d4)) { script->BytesPerCluster = d1 * d2; // W2K and later: the product d1 * d2 * d3 did not work on DFS trees, reported by Ludek.Vydra@k2atmitec.cz - script->FreeSpace = MyGetDiskFreeSpace(targetPath); + script->FreeSpace = FileOperationFileSystem().QueryFreeSpace(targetPath); } int i; @@ -644,7 +647,7 @@ BOOL CFilesWindow::BuildScriptMain2(COperations* script, BOOL copy, char* target char* fileName = data->At(i)->FileName; char* mapName = data->At(i)->MapName; - DWORD attrs = SalGetFileAttributes(fileName); + DWORD attrs = FileOperationFileSystem().GetAttributes(fileName); if (attrs != 0xFFFFFFFF) { char* s = fileName + strlen(fileName); @@ -670,7 +673,7 @@ BOOL CFilesWindow::BuildScriptMain2(COperations* script, BOOL copy, char* target BOOL isKnown; // mapName must be NULL here, otherwise data->MakeCopyOfName could not be TRUE if ((isKnown = ContainsString(usedNames, targetName)) != 0 || - SalGetFileAttributes(targetPath) != 0xFFFFFFFF) + FileOperationFileSystem().GetAttributes(targetPath) != 0xFFFFFFFF) { // name already exists, we must generate a new one if (!isKnown) AddStringToNames(usedNames, targetName); @@ -765,7 +768,7 @@ BOOL CFilesWindow::BuildScriptMain2(COperations* script, BOOL copy, char* target if (strlen(targetName) < MAX_PATH) // name assembly succeeded, otherwise we ignore the result { if ((isKnown = ContainsString(usedNames, targetName)) != 0 || - SalGetFileAttributes(targetPath) != 0xFFFFFFFF) + FileOperationFileSystem().GetAttributes(targetPath) != 0xFFFFFFFF) { if (!isKnown) AddStringToNames(usedNames, targetName); @@ -798,7 +801,7 @@ BOOL CFilesWindow::BuildScriptMain2(COperations* script, BOOL copy, char* target if (strlen(targetName) < MAX_PATH) // name assembly succeeded, otherwise we ignore the result { if ((isKnown = ContainsString(usedNames, targetName)) != 0 || - SalGetFileAttributes(targetPath) != 0xFFFFFFFF) + FileOperationFileSystem().GetAttributes(targetPath) != 0xFFFFFFFF) { if (!isKnown) AddStringToNames(usedNames, targetName); @@ -853,7 +856,7 @@ BOOL CFilesWindow::BuildScriptMain2(COperations* script, BOOL copy, char* target if (strlen(targetName) < MAX_PATH) // name assembly succeeded, otherwise we ignore the result { if ((isKnown = ContainsString(usedNames, targetName)) != 0 || - SalGetFileAttributes(targetPath) != 0xFFFFFFFF) + FileOperationFileSystem().GetAttributes(targetPath) != 0xFFFFFFFF) { if (!isKnown) AddStringToNames(usedNames, targetName); @@ -887,7 +890,7 @@ BOOL CFilesWindow::BuildScriptMain2(COperations* script, BOOL copy, char* target if (strlen(targetName) < MAX_PATH) // name assembly succeeded, otherwise we ignore the result { if ((isKnown = ContainsString(usedNames, targetName)) != 0 || - SalGetFileAttributes(targetPath) != 0xFFFFFFFF) + FileOperationFileSystem().GetAttributes(targetPath) != 0xFFFFFFFF) { if (!isKnown) AddStringToNames(usedNames, targetName); @@ -1253,6 +1256,7 @@ BOOL CFilesWindow::BuildScriptMain(COperations* script, CActionType type, targetSupADS = IsPathOnVolumeSupADS(targetPath, &targetIsFAT32); targetPathState = GetTargetPathState(targetPathState, targetPath); script->TargetPathSupADS = targetSupADS; + script->TargetMetadataFileSystem = GetMetadataTargetFileSystem(targetPath); // script->TargetPathSupEFS = targetSupEFS; srcAndTgtPathsFlags |= GetPathFlagsForCopyOp(sourcePath, OPFL_SRCPATH_IS_NET, OPFL_SRCPATH_IS_FAST) | GetPathFlagsForCopyOp(targetPath, OPFL_TGTPATH_IS_NET, OPFL_TGTPATH_IS_FAST); @@ -1278,6 +1282,7 @@ BOOL CFilesWindow::BuildScriptMain(COperations* script, CActionType type, UpdateWindow(MainWindow->HWindow); if (res == IDNO || res == IDCANCEL) // user chose CANCEL or NO -> abort return FALSE; + script->PlannedMetadataLosses |= mmlSecurity; } } } @@ -1298,13 +1303,13 @@ BOOL CFilesWindow::BuildScriptMain(COperations* script, CActionType type, strcpy(s, oneFile->Name); // try whether the file name is valid; if not, try its DOS name // (handles files accessible only via Unicode or DOS names) - if (SalGetFileAttributes(sourcePath) == 0xffffffff) + if (FileOperationFileSystem().GetAttributes(sourcePath) == 0xffffffff) { DWORD err = GetLastError(); if (err == ERROR_FILE_NOT_FOUND || err == ERROR_INVALID_NAME) { strcpy(s, oneFile->DosName); - if (SalGetFileAttributes(sourcePath) != 0xffffffff) + if (FileOperationFileSystem().GetAttributes(sourcePath) != 0xffffffff) { useName = oneFile->DosName; useDOSName = NULL; @@ -1319,11 +1324,11 @@ BOOL CFilesWindow::BuildScriptMain(COperations* script, CActionType type, if (type == atMove || type == atCopy) // outside Copy and Move it makes no sense to check { DWORD d1, d2, d3, d4; - if (MyGetDiskFreeSpace(targetPath, &d1, &d2, &d3, &d4)) + if (FileOperationFileSystem().GetDiskFreeSpace(targetPath, &d1, &d2, &d3, &d4)) { script->BytesPerCluster = d1 * d2; // W2K and later: the product d1 * d2 * d3 did not work on DFS trees, reported by Ludek.Vydra@k2atmitec.cz - script->FreeSpace = MyGetDiskFreeSpace(targetPath); + script->FreeSpace = FileOperationFileSystem().QueryFreeSpace(targetPath); } } @@ -1681,6 +1686,21 @@ MENU_TEMPLATE_ITEM MsgBoxButtons[] = else return TRUE; } + if (sourceDirAttr & FILE_ATTRIBUTE_REPARSE_POINT) + { + // REPARSE_POINT_POLICY: Directory reparse points are operation + // boundaries. Do not enumerate them for copy, move, count, convert, + // or attribute operations: their target can be outside the selected + // operation root, can form a cycle, or can change after planning. + // Deletes took the dedicated link-only path above. Unknown tags are + // therefore never followed and cloud directories are never hydrated + // merely because the planner inspected their parent tree. + TRACE_I("BuildScriptDir(): skipping directory reparse point without traversal: " << sourcePath); + *sourceEnd = 0; // restoring sourcePath + if (targetEnd != NULL) + *targetEnd = 0; // restoring targetPath + return TRUE; + } //--- if (type == atDelete && Configuration.CnfrmSHDirDel && (sourceDirAttr & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM))) @@ -1852,6 +1872,7 @@ MENU_TEMPLATE_ITEM MsgBoxButtons[] = case IDB_ALL: ConfirmADSLossAll = TRUE; // intentional fallthrough case IDYES: + script->PlannedMetadataLosses |= mmlAlternateDataStreams; break; // we will ignore ADS, so they won't be copied/moved (and will be completely lost) case IDB_SKIPALL: @@ -2022,71 +2043,10 @@ MENU_TEMPLATE_ITEM MsgBoxButtons[] = } } - BOOL copyMoveDirIsLink = FALSE; - BOOL copyMoveSkipLinkContent = FALSE; - if ((type == atCopy || type == atMove) && // if it's a link, determine whether to skip or copy its content - (sourceDirAttr & FILE_ATTRIBUTE_REPARSE_POINT)) - { - copyMoveDirIsLink = TRUE; - int res; - if (ConfirmCopyLinkContentAll) - res = IDYES; - else - { - if (ConfirmCopyLinkContentSkipAll) - res = IDB_SKIP; - else - { - char detailsTxt[MAX_PATH + 200]; - char junctionOrSymlinkTgt[MAX_PATH]; - int repPointType; - if (GetReparsePointDestination(sourcePath, junctionOrSymlinkTgt, MAX_PATH, &repPointType, FALSE)) - { - if (repPointType == 1 /* MOUNT POINT */) - strcpy_s(detailsTxt, LoadStr(IDS_VOLMOUNTPOINT)); - else - { - sprintf_s(detailsTxt, LoadStr(repPointType == 2 /* JUNCTION POINT */ ? IDS_INFODLGTYPE9 : IDS_INFODLGTYPE10), - junctionOrSymlinkTgt); - int len = (int)strlen(detailsTxt); - if (detailsTxt[0] == '(') - memmove(detailsTxt, detailsTxt + 1, --len + 1); - if (len > 0 && detailsTxt[len - 1] == ')') - detailsTxt[--len] = 0; - } - } - else - strcpy_s(detailsTxt, LoadStr(IDS_UNABLETORESOLVELINK)); - - res = (int)CConfirmLinkTgtCopyDlg(HWindow, sourcePath, detailsTxt).Execute(); - } - } - switch (res) - { - case IDB_ALL: - ConfirmCopyLinkContentAll = TRUE; // intentional fallthrough - case IDYES: - break; // copy the link content to the target - - case IDB_SKIPALL: - ConfirmCopyLinkContentSkipAll = TRUE; // intentionalfallthrough - case IDB_SKIP: - copyMoveSkipLinkContent = TRUE; - break; // skip the link content (do not copy) - - case IDCANCEL: - { - *sourceEnd = 0; // restoring sourcePath - *targetEnd = 0; // restoring targetPath - return FALSE; - } - } - } //--- build the path to sourceDirName and start searching for contained files BOOL delDirectory = TRUE; // delete a non-empty directory? BOOL delDirectoryReturn = TRUE; // return value when a non-empty directory isn't removed BOOL canDelDirAfterMove = TRUE; // Move only: FALSE if not everything is moved (filter skipped something), source directory can't be removed (won't be empty) - if (!copyMoveDirIsLink || !copyMoveSkipLinkContent) { WIN32_FIND_DATAW fW; WIN32_FIND_DATA f; @@ -2213,9 +2173,17 @@ MENU_TEMPLATE_ITEM MsgBoxButtons[] = } //--- build a directory or file + if (type == atMove && (f.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) + { + // The reparse entry is deliberately skipped below, so the + // containing source directory cannot be removed as part of + // this move. This keeps the skipped link intact rather + // than turning a safe no-traversal decision into data loss. + canDelDirAfterMove = FALSE; + } if (f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { - if (!BuildScriptDir(script, copyMoveDirIsLink ? atCopy : type, sourcePath, sourcePathSupADS, targetPath, + if (!BuildScriptDir(script, type, sourcePath, sourcePathSupADS, targetPath, targetPathState, targetPathSupADS, targetPathIsFAT32, NULL, f.cFileName, f.cAlternateFileName[0] != 0 ? f.cAlternateFileName : NULL, @@ -2235,7 +2203,7 @@ MENU_TEMPLATE_ITEM MsgBoxButtons[] = { if (filterCriteria == NULL || filterCriteria->AgreeMasksAndAdvanced(&f)) { - if (!BuildScriptFile(script, copyMoveDirIsLink ? atCopy : type, sourcePath, sourcePathSupADS, targetPath, + if (!BuildScriptFile(script, type, sourcePath, sourcePathSupADS, targetPath, targetPathState, targetPathSupADS, targetPathIsFAT32, NULL, f.cFileName, f.cAlternateFileName[0] != 0 ? f.cAlternateFileName : NULL, @@ -2294,12 +2262,6 @@ MENU_TEMPLATE_ITEM MsgBoxButtons[] = } } } - else - { - *sourceEnd = 0; // restoring sourcePath - if (targetEnd != NULL) - *targetEnd = 0; // restoring targetPath - } //--- change-case: rename only after operations inside are complete if (type == atChangeCase) { @@ -2336,13 +2298,11 @@ MENU_TEMPLATE_ITEM MsgBoxButtons[] = } } // if this directory contains a skipped item, the parent directory cannot be deleted - // if it's just a link to a directory, delete it regardless of remaining content - if (!copyMoveDirIsLink && canDelUpperDirAfterMove != NULL && !canDelDirAfterMove) + if (canDelUpperDirAfterMove != NULL && !canDelDirAfterMove) *canDelUpperDirAfterMove = FALSE; // if nothing inside the directory was copied or moved and we are transferring only files, - // cancel creation of the directory (an unnecessary empty directory). If it was a link to a directory, - // this rule does not apply (it's a link, not a real directory) - if (!copyMoveDirIsLink && (type == atCopy || type == atMove) && filterCriteria != NULL && + // cancel creation of the directory (an unnecessary empty directory) + if ((type == atCopy || type == atMove) && filterCriteria != NULL && filterCriteria->SkipEmptyDirs && createDirIndex >= 0 && createDirIndex == script->Count - 1) { @@ -2381,15 +2341,14 @@ MENU_TEMPLATE_ITEM MsgBoxButtons[] = } // if we need to delete the directory or a link to it at sourcePath + dirName (delete and move) - if (copyMoveDirIsLink && type == atMove || // for a link canDelDirAfterMove is irrelevant (a link can always be removed) - !copyMoveDirIsLink && type == atMove && canDelDirAfterMove || type == atDelete) // delete the source directory or the link to the directory + if (type == atMove && canDelDirAfterMove || type == atDelete) // delete the source directory { if (type == atDelete && !delDirectory) return delDirectoryReturn; // CANCEL / NO - op.Opcode = copyMoveDirIsLink && type == atMove ? ocDeleteDirLink : ocDeleteDir; + op.Opcode = ocDeleteDir; op.OpFlags = 0; - op.Size = copyMoveDirIsLink && type == atMove ? DELETE_DIRLINK_SIZE : DELETE_DIR_SIZE; + op.Size = DELETE_DIR_SIZE; op.Attr = sourceDirAttr; BOOL skip; BOOL skipTooLongSrcNameErr = FALSE; @@ -2506,6 +2465,17 @@ BOOL CFilesWindow::BuildScriptFile(COperations* script, CActionType type, char* CQuadWord fileSizeLoc = fileSize; char message[2 * MAX_PATH + 200]; COperation op; + + if (type != atDelete && (sourceFileAttr & FILE_ATTRIBUTE_REPARSE_POINT)) + { + // REPARSE_POINT_POLICY: File reparse points are not opened by planning + // copy/move/count/convert/attribute work. In particular, this keeps a + // cloud placeholder offline; opening it without FILE_FLAG_OPEN_REPARSE_POINT + // could hydrate provider data as an unintended side effect. Delete has + // its own handle-first identity-checked path and targets the link. + TRACE_I("BuildScriptFile(): skipping file reparse point without hydration: " << sourcePath << "\\" << fileName); + return TRUE; + } switch (type) { case atCopy: @@ -2736,6 +2706,7 @@ MENU_TEMPLATE_ITEM MsgBoxButtons[] = case IDB_ALL: ConfirmADSLossAll = TRUE; // intentional fallthrough case IDYES: + script->PlannedMetadataLosses |= mmlAlternateDataStreams; break; // we will ignore ADS, so they won't be copied/moved (and will be completely lost) case IDB_SKIPALL: @@ -2903,7 +2874,7 @@ MENU_TEMPLATE_ITEM MsgBoxButtons[] = if (script->BytesPerCluster == 0) // no space-estimate risk { DWORD d1, d2, d3, d4; - if (MyGetDiskFreeSpace(sourcePath, &d1, &d2, &d3, &d4)) + if (FileOperationFileSystem().GetDiskFreeSpace(sourcePath, &d1, &d2, &d3, &d4)) script->BytesPerCluster = d1 * d2; } @@ -3015,7 +2986,7 @@ MENU_TEMPLATE_ITEM MsgBoxButtons[] = { return skip; } - op.TargetName = (char*)(DWORD_PTR)((SalGetFileAttributes(op.SourceName) & attrsData->AttrAnd) | attrsData->AttrOr); + op.TargetName = (char*)(DWORD_PTR)((FileOperationFileSystem().GetAttributes(op.SourceName) & attrsData->AttrAnd) | attrsData->AttrOr); script->Add(op); if (!script->IsGood()) { diff --git a/src/find.cpp b/src/find.cpp index e2028cf2..89dffbcf 100644 --- a/src/find.cpp +++ b/src/find.cpp @@ -34,6 +34,11 @@ const char* FINDOPTIONSITEM_GREP_REG = "Grep"; const char* FINDIGNOREITEM_PATH_REG = "Path"; const char* FINDIGNOREITEM_ENABLED_REG = "Enabled"; +// Search results and deferred directories are background work, not a mandate +// to retain every discovered entry when a giant tree exceeds interactive memory. +#define FIND_MAX_RESULTS 100000 +#define FIND_MAX_DEFERRED_DIRECTORIES 4096 + // following variable was used up to Open Salamander 2.5, // where we switched to CFilterCriteria with its Save/Load const char* OLD_FINDOPTIONSITEM_EXCLUDEMASK_REG = "ExcludeMask"; @@ -1448,6 +1453,23 @@ BOOL AddFoundItem(const char* path, const char* name, DWORD sizeLow, DWORD sizeH if (duplicateCandidates != NULL && isDir) // directories are irrelevant to us when searching for duplicates return TRUE; + if (duplicateCandidates == NULL && data->FoundFilesListView->GetCount() >= FIND_MAX_RESULTS) + { + // Stop rather than silently dropping matches: a partial result set must be explicit to the user. + if (!data->ResultLimitReached) + { + FIND_LOG_ITEM log; + log.Flags = FLI_ERROR; + log.Text = LoadStr(IDS_CANTSHOWRESULTS); + log.Path = NULL; + SendMessage(data->HWindow, WM_USER_ADDLOG, (WPARAM)&log, 0); + TRACE_I("Find result budget reached: " << FIND_MAX_RESULTS << " items"); + data->ResultLimitReached = TRUE; + } + data->StopSearch = TRUE; + return FALSE; + } + CFoundFilesData* foundData = new CFoundFilesData; if (foundData != NULL) { @@ -1650,7 +1672,7 @@ void SearchDirectory(char (&path)[MAX_PATH], char* end, int startPathLen, { BOOL searchNow = TRUE; - if (dirStack != NULL) + if (dirStack != NULL && dirStackCount < FIND_MAX_DEFERRED_DIRECTORIES) { // just store for later search char* newFileName = new char[l + 1]; @@ -1662,6 +1684,8 @@ void SearchDirectory(char (&path)[MAX_PATH], char* end, int startPathLen, // no need to assign an item - we have space dirStack->At(dirStackCount) = newFileName; dirStackCount++; + if (dirStackCount > data->DirectoryStackHighWaterMark) + data->DirectoryStackHighWaterMark = dirStackCount; searchNow = FALSE; } else @@ -1671,6 +1695,8 @@ void SearchDirectory(char (&path)[MAX_PATH], char* end, int startPathLen, if (dirStack->IsGood()) { dirStackCount++; + if (dirStackCount > data->DirectoryStackHighWaterMark) + data->DirectoryStackHighWaterMark = dirStackCount; searchNow = FALSE; } else @@ -1684,7 +1710,9 @@ void SearchDirectory(char (&path)[MAX_PATH], char* end, int startPathLen, if (searchNow) { - // out of memory - we will not use dirStack + // The deferred queue is full, so retain no more names and continue depth-first. + if (dirStack != NULL) + data->DirectoryStackFallbacks++; strcpy_s(end, _countof(path) - (end - path), file.cFileName); strcat_s(end, _countof(path) - (end - path), "\\"); l++; @@ -2213,7 +2241,8 @@ BOOL OpenFindDialog(HWND hCenterAgainst, const char* initPath) HANDLES(CloseHandle(loop)); goto ERROR_TFD_CREATE; } - AddAuxThread(loop); // add the thread among existing viewers (killed on exit) + // The modeless loop receives its close request before global shutdown joins it. + AddAuxThread(loop, FALSE, "Find dialog message loop"); SetCursor(hOldCur); return TRUE; } diff --git a/src/find.h b/src/find.h index e95b2e17..d501d847 100644 --- a/src/find.h +++ b/src/find.h @@ -167,6 +167,9 @@ struct CGrepData int FoundVisibleCount; // number of items displayed in the list view DWORD FoundVisibleTick; // when it was last displayed BOOL NeedRefresh; // need to refresh the display (an item was added without being shown) + BOOL ResultLimitReached; // prevents a huge match set from retaining every result indefinitely + LONG DirectoryStackHighWaterMark; // records bounded deferred-directory pressure for diagnostics + LONG DirectoryStackFallbacks; // directories searched depth-first after the deferred budget fills CSearchingString* SearchingText; // synchronized "Searching" text in the Find status bar CSearchingString* SearchingText2; // [optional] second text on the right; used for "Total: 35%" diff --git a/src/find_dialog_ui.cpp b/src/find_dialog_ui.cpp index a1affc21..a1465368 100644 --- a/src/find_dialog_ui.cpp +++ b/src/find_dialog_ui.cpp @@ -929,6 +929,10 @@ void CFindDialog::StartSearch(WORD command) GrepData.FoundFilesListView = FoundFilesListView; GrepData.FoundVisibleCount = 0; GrepData.FoundVisibleTick = GetTickCount(); + // Each search starts a fresh bounded-work generation for results and deferred directories. + GrepData.ResultLimitReached = FALSE; + GrepData.DirectoryStackHighWaterMark = 0; + GrepData.DirectoryStackFallbacks = 0; GrepData.SearchingText = &SearchingText; GrepData.SearchingText2 = &SearchingText2; diff --git a/src/finddlg2.cpp b/src/finddlg2.cpp index 9c90b59b..4c75bda8 100644 --- a/src/finddlg2.cpp +++ b/src/finddlg2.cpp @@ -267,8 +267,9 @@ void CFindDialog::OnColorsChange() { if (MainMenu != NULL) { - MainMenu->SetImageList(HGrayToolBarImageList, TRUE); - MainMenu->SetHotImageList(HHotToolBarImageList, TRUE); + // Find menus retain compact rows while the command toolbar follows the shared preference. + MainMenu->SetImageList(HGrayMenuImageList, TRUE); + MainMenu->SetHotImageList(HHotMenuImageList, TRUE); } if (TBHeader != NULL) TBHeader->OnColorsChange(); diff --git a/src/gui_controls.cpp b/src/gui_controls.cpp index 944ac377..abe48f38 100644 --- a/src/gui_controls.cpp +++ b/src/gui_controls.cpp @@ -1458,7 +1458,8 @@ CToolbarHeader::CToolbarHeader(HWND hDlg, int ctrlID, HWND hAlignWindow, DWORD b CSVGIcon svgIcons[TLBHDR_COUNT] = { {0, "Modify"}, - {1, "New_Insert"}, + // Reuse the shared Fluent New glyph so dialog toolbars cannot drift visually. + {1, "New"}, {2, "Delete"}, {3, "SortByName"}, {4, "MoveItemUp"}, @@ -1473,7 +1474,8 @@ CToolbarHeader::CToolbarHeader(HWND hDlg, int ctrlID, HWND hAlignWindow, DWORD b IDB_EDTLBTB, RGB(255, 0, 255), GetSysColor(COLOR_BTNFACE), hTmpMaskBitmap, hTmpGrayBitmap, hTmpColorBitmap, - FALSE, svgIcons, TLBHDR_COUNT); + FALSE, svgIcons, TLBHDR_COUNT, + iconSize); // Embedded edit controls intentionally remain compact. HHotImageList = ImageList_Create(iconSize, iconSize, ILC_MASK | ILC_COLORDDB, TLBHDR_COUNT, 1); HGrayImageList = ImageList_Create(iconSize, iconSize, ILC_MASK | ILC_COLORDDB, TLBHDR_COUNT, 1); ImageList_Add(HHotImageList, hTmpColorBitmap, hTmpMaskBitmap); @@ -1556,7 +1558,8 @@ void CToolbarHeader::CreateImageLists(HIMAGELIST* enabled, HIMAGELIST* disabled) NSVGrasterizer* rast = nsvgCreateRasterizer(); // JRYFIXME: temporarily reading from a file, switch to a shared storage with toolbars - const char* svgNames[] = {"Modify", "New_Insert", "Delete", "SortByName", "MoveItemUp", "MoveItemDown"}; + // Reuse the shared Fluent New glyph so dialog toolbars cannot drift visually. + const char* svgNames[] = {"Modify", "New", "Delete", "SortByName", "MoveItemUp", "MoveItemDown"}; for (int j = 0; j < 2; j++) { DWORD* p = (DWORD*)lpBits; @@ -2605,4 +2608,4 @@ HICON SalLoadIcon(HINSTANCE hInst, LPCTSTR iconName, CIconSizeEnum iconSize) HANDLES_ADD(__htIcon, __hoLoadIcon, hIcon); } return hIcon; -} \ No newline at end of file +} diff --git a/src/iconpool.cpp b/src/iconpool.cpp index 8c52684c..cd50df89 100644 --- a/src/iconpool.cpp +++ b/src/iconpool.cpp @@ -9,6 +9,7 @@ #include "fileswnd.h" #include "geticon.h" #include "iconpool.h" +#include "common/thread_owner.h" // Global icon thread pool instance CIconThreadPool IconPool; @@ -26,15 +27,23 @@ CIconThreadPool::CIconThreadPool() { memset(Workers, 0, sizeof(Workers)); WorkerCount = 0; - QueueHead = 0; - QueueTail = 0; QueueCount = 0; + ActiveCount = 0; + CurrentGeneration = 1; memset(WorkQueue, 0, sizeof(WorkQueue)); WorkAvailableEvent = NULL; TerminateEvent = NULL; + AllWorkCompletedEvent = NULL; ResultCallback = NULL; CallbackContext = NULL; NextRequestId = 1; + SubmittedCount = 0; + CompletedCount = 0; + DeduplicatedCount = 0; + CancelledCount = 0; + BackpressureRejectedCount = 0; + VisiblePreemptionCount = 0; + HighWaterMark = 0; Initialized = FALSE; } @@ -55,7 +64,8 @@ BOOL CIconThreadPool::Initialize(int numWorkers) HANDLES(InitializeCriticalSection(&QueueLock)); - WorkAvailableEvent = HANDLES(CreateEvent(NULL, FALSE, FALSE, NULL)); // auto-reset + // A manual-reset event wakes enough workers to drain every ready fixed slot. + WorkAvailableEvent = HANDLES(CreateEvent(NULL, TRUE, FALSE, NULL)); if (WorkAvailableEvent == NULL) { HANDLES(DeleteCriticalSection(&QueueLock)); @@ -70,6 +80,18 @@ BOOL CIconThreadPool::Initialize(int numWorkers) HANDLES(DeleteCriticalSection(&QueueLock)); return FALSE; } + + // Waiting on this event replaces polling and includes in-flight work. + AllWorkCompletedEvent = HANDLES(CreateEvent(NULL, TRUE, TRUE, NULL)); + if (AllWorkCompletedEvent == NULL) + { + HANDLES(CloseHandle(TerminateEvent)); + TerminateEvent = NULL; + HANDLES(CloseHandle(WorkAvailableEvent)); + WorkAvailableEvent = NULL; + HANDLES(DeleteCriticalSection(&QueueLock)); + return FALSE; + } // Create worker threads WorkerCount = 0; @@ -95,6 +117,8 @@ BOOL CIconThreadPool::Initialize(int numWorkers) TerminateEvent = NULL; HANDLES(CloseHandle(WorkAvailableEvent)); WorkAvailableEvent = NULL; + HANDLES(CloseHandle(AllWorkCompletedEvent)); + AllWorkCompletedEvent = NULL; HANDLES(DeleteCriticalSection(&QueueLock)); return FALSE; } @@ -109,6 +133,9 @@ void CIconThreadPool::Shutdown() if (!Initialized) return; + // Obsolete requests must not extend shutdown after the owner starts teardown. + CancelAllPending(); + // Signal all workers to terminate SetEvent(TerminateEvent); @@ -116,11 +143,9 @@ void CIconThreadPool::Shutdown() for (int i = 0; i < WorkerCount; i++) SetEvent(WorkAvailableEvent); - // Wait for all workers to finish - if (WorkerCount > 0) - { - WaitForMultipleObjects(WorkerCount, Workers, TRUE, 5000); - } + // A diagnostic deadline is not permission to free queue state a worker may still use. + for (int i = 0; i < WorkerCount; i++) + CThreadShutdownDeadline("icon work pool").WaitForSafeJoin(Workers[i]); // Close worker handles for (int i = 0; i < WorkerCount; i++) @@ -144,19 +169,18 @@ void CIconThreadPool::Shutdown() HANDLES(CloseHandle(WorkAvailableEvent)); WorkAvailableEvent = NULL; } - - HANDLES(DeleteCriticalSection(&QueueLock)); - - // Destroy any remaining icons in the queue - for (int i = 0; i < ICON_POOL_QUEUE_SIZE; i++) + if (AllWorkCompletedEvent != NULL) { - if (WorkQueue[i].ResultIcon != NULL) - { - DestroyIcon(WorkQueue[i].ResultIcon); - WorkQueue[i].ResultIcon = NULL; - } + HANDLES(CloseHandle(AllWorkCompletedEvent)); + AllWorkCompletedEvent = NULL; } + // Every slot owns its result until a callback consumes it synchronously. + for (int i = 0; i < ICON_POOL_QUEUE_SIZE; i++) + ClearWorkItemLocked(WorkQueue[i]); + + HANDLES(DeleteCriticalSection(&QueueLock)); + Initialized = FALSE; TRACE_I("CIconThreadPool::Shutdown(): Thread pool shut down"); } @@ -169,18 +193,61 @@ void CIconThreadPool::SetResultCallback(IconPoolResultCallback callback, void* c DWORD CIconThreadPool::SubmitWorkItem(CIconWorkItem* item) { - if (!Initialized) + if (!Initialized || item == NULL) return 0; - + HANDLES(EnterCriticalSection(&QueueLock)); - + + item->Generation = item->Generation != 0 ? item->Generation : CurrentGeneration; + item->Priority = item->Priority == iwpVisible ? iwpVisible : iwpBackground; + + // Coalescing identical requests prevents repeated paints from multiplying work. + for (int i = 0; i < ICON_POOL_QUEUE_SIZE; i++) + { + CIconWorkItem& queued = WorkQueue[i]; + if (queued.RequestId != 0 && !queued.Cancelled && IsSameWorkItem(queued, *item)) + { + if (item->Priority > queued.Priority) + queued.Priority = item->Priority; + InterlockedIncrement64(&DeduplicatedCount); + DWORD requestId = queued.RequestId; + HANDLES(LeaveCriticalSection(&QueueLock)); + return requestId; + } + } + if (QueueCount >= ICON_POOL_QUEUE_SIZE) { - HANDLES(LeaveCriticalSection(&QueueLock)); - TRACE_I("CIconThreadPool::SubmitWorkItem(): Queue full"); - return 0; // Queue full + int preemptedSlot = -1; + if (item->Priority == iwpVisible) + { + // Visible work may reclaim only dormant background slots; active I/O stays cooperative. + for (int i = 0; i < ICON_POOL_QUEUE_SIZE; i++) + { + if (WorkQueue[i].RequestId != 0 && !WorkQueue[i].InProgress && + !WorkQueue[i].Cancelled && WorkQueue[i].Priority == iwpBackground) + { + preemptedSlot = i; + break; + } + } + } + + if (preemptedSlot >= 0) + { + CancelWorkItemLocked(WorkQueue[preemptedSlot]); + DiscardCancelledWorkItemsLocked(); + InterlockedIncrement64(&VisiblePreemptionCount); + } + else + { + InterlockedIncrement64(&BackpressureRejectedCount); + HANDLES(LeaveCriticalSection(&QueueLock)); + TRACE_I("CIconThreadPool::SubmitWorkItem(): bounded queue rejected work"); + return 0; + } } - + DWORD requestId = InterlockedIncrement(&NextRequestId); if (requestId == 0) requestId = InterlockedIncrement(&NextRequestId); // Skip 0 @@ -189,48 +256,77 @@ DWORD CIconThreadPool::SubmitWorkItem(CIconWorkItem* item) item->ResultIcon = NULL; item->Completed = 0; item->Cancelled = 0; - - // Copy to queue - int slot = QueueHead; + item->InProgress = 0; + + int slot = -1; + for (int i = 0; i < ICON_POOL_QUEUE_SIZE; i++) + { + if (WorkQueue[i].RequestId == 0) + { + slot = i; + break; + } + } + if (slot < 0) + { + // This should be unreachable after the capacity/preemption branch, but remains bounded if corrupted. + InterlockedIncrement64(&BackpressureRejectedCount); + HANDLES(LeaveCriticalSection(&QueueLock)); + return 0; + } + memcpy(&WorkQueue[slot], item, sizeof(CIconWorkItem)); - QueueHead = (QueueHead + 1) % ICON_POOL_QUEUE_SIZE; InterlockedIncrement(&QueueCount); - + InterlockedIncrement64(&SubmittedCount); + LONG highWater = InterlockedCompareExchange(&QueueCount, 0, 0); + for (LONGLONG observed = InterlockedCompareExchange64(&HighWaterMark, 0, 0); + highWater > observed && InterlockedCompareExchange64(&HighWaterMark, highWater, observed) != observed; + observed = InterlockedCompareExchange64(&HighWaterMark, 0, 0)) + { + } + ResetEvent(AllWorkCompletedEvent); + UpdateWorkAvailableSignalLocked(); HANDLES(LeaveCriticalSection(&QueueLock)); - - // Signal that work is available - SetEvent(WorkAvailableEvent); - + return requestId; } -DWORD CIconThreadPool::SubmitGetFileIcon(const char* path, CIconSizeEnum iconSize) +DWORD CIconThreadPool::SubmitGetFileIcon(const char* path, CIconSizeEnum iconSize, DWORD generation, BOOL visible) { CIconWorkItem item; + memset(&item, 0, sizeof(item)); item.Type = iwtGetFileIcon; lstrcpynA(item.Path, path, MAX_PATH); item.Index = 0; item.IconSize = iconSize; + item.Generation = generation; + item.Priority = visible ? iwpVisible : iwpBackground; return SubmitWorkItem(&item); } -DWORD CIconThreadPool::SubmitExtractIcon(const char* path, int index, CIconSizeEnum iconSize) +DWORD CIconThreadPool::SubmitExtractIcon(const char* path, int index, CIconSizeEnum iconSize, DWORD generation, BOOL visible) { CIconWorkItem item; + memset(&item, 0, sizeof(item)); item.Type = iwtExtractIcon; lstrcpynA(item.Path, path, MAX_PATH); item.Index = index; item.IconSize = iconSize; + item.Generation = generation; + item.Priority = visible ? iwpVisible : iwpBackground; return SubmitWorkItem(&item); } -DWORD CIconThreadPool::SubmitLoadImageIcon(const char* path, CIconSizeEnum iconSize) +DWORD CIconThreadPool::SubmitLoadImageIcon(const char* path, CIconSizeEnum iconSize, DWORD generation, BOOL visible) { CIconWorkItem item; + memset(&item, 0, sizeof(item)); item.Type = iwtLoadImageIcon; lstrcpynA(item.Path, path, MAX_PATH); item.Index = 0; item.IconSize = iconSize; + item.Generation = generation; + item.Priority = visible ? iwpVisible : iwpBackground; return SubmitWorkItem(&item); } @@ -244,34 +340,88 @@ int CIconThreadPool::GetPendingCount() return QueueCount; } +CIconQueueMetrics CIconThreadPool::GetMetrics() +{ + CIconQueueMetrics metrics; + memset(&metrics, 0, sizeof(metrics)); + metrics.Capacity = ICON_POOL_QUEUE_SIZE; + + if (Initialized) + { + HANDLES(EnterCriticalSection(&QueueLock)); + metrics.Queued = QueueCount - ActiveCount; + metrics.Active = ActiveCount; + HANDLES(LeaveCriticalSection(&QueueLock)); + } + + metrics.Submitted = InterlockedCompareExchange64(&SubmittedCount, 0, 0); + metrics.Completed = InterlockedCompareExchange64(&CompletedCount, 0, 0); + metrics.Deduplicated = InterlockedCompareExchange64(&DeduplicatedCount, 0, 0); + metrics.Cancelled = InterlockedCompareExchange64(&CancelledCount, 0, 0); + metrics.BackpressureRejected = InterlockedCompareExchange64(&BackpressureRejectedCount, 0, 0); + metrics.VisiblePreemptions = InterlockedCompareExchange64(&VisiblePreemptionCount, 0, 0); + metrics.HighWaterMark = InterlockedCompareExchange64(&HighWaterMark, 0, 0); + return metrics; +} + void CIconThreadPool::CancelAllPending() { HANDLES(EnterCriticalSection(&QueueLock)); - - // Mark all pending items as cancelled + + // Cancellation frees dormant slots immediately while active providers observe it cooperatively. + for (int i = 0; i < ICON_POOL_QUEUE_SIZE; i++) + CancelWorkItemLocked(WorkQueue[i]); + DiscardCancelledWorkItemsLocked(); + UpdateWorkAvailableSignalLocked(); + UpdateCompletionSignalLocked(); + HANDLES(LeaveCriticalSection(&QueueLock)); +} + +void CIconThreadPool::CancelObsoleteGenerations(DWORD currentGeneration) +{ + if (!Initialized || currentGeneration == 0) + return; + + HANDLES(EnterCriticalSection(&QueueLock)); + if (currentGeneration > CurrentGeneration) + CurrentGeneration = currentGeneration; for (int i = 0; i < ICON_POOL_QUEUE_SIZE; i++) { - if (WorkQueue[i].RequestId != 0 && WorkQueue[i].Completed == 0) - { - InterlockedExchange(&WorkQueue[i].Cancelled, 1); - } + if (WorkQueue[i].RequestId != 0 && WorkQueue[i].Generation < CurrentGeneration) + CancelWorkItemLocked(WorkQueue[i]); } - + DiscardCancelledWorkItemsLocked(); + UpdateWorkAvailableSignalLocked(); + UpdateCompletionSignalLocked(); HANDLES(LeaveCriticalSection(&QueueLock)); } -BOOL CIconThreadPool::WaitForCompletion(DWORD timeoutMs) +DWORD CIconThreadPool::BeginGeneration() { - DWORD startTime = GetTickCount(); - while (QueueCount > 0) + if (!Initialized) + return 0; + + HANDLES(EnterCriticalSection(&QueueLock)); + CurrentGeneration++; + if (CurrentGeneration == 0) + CurrentGeneration = 1; + DWORD generation = CurrentGeneration; + for (int i = 0; i < ICON_POOL_QUEUE_SIZE; i++) { - DWORD elapsed = GetTickCount() - startTime; - if (timeoutMs != INFINITE && elapsed >= timeoutMs) - return FALSE; - - Sleep(1); + if (WorkQueue[i].RequestId != 0 && WorkQueue[i].Generation < generation) + CancelWorkItemLocked(WorkQueue[i]); } - return TRUE; + DiscardCancelledWorkItemsLocked(); + UpdateWorkAvailableSignalLocked(); + UpdateCompletionSignalLocked(); + HANDLES(LeaveCriticalSection(&QueueLock)); + return generation; +} + +BOOL CIconThreadPool::WaitForCompletion(DWORD timeoutMs) +{ + return AllWorkCompletedEvent != NULL && + WaitForSingleObject(AllWorkCompletedEvent, timeoutMs) == WAIT_OBJECT_0; } DWORD WINAPI CIconThreadPool::WorkerThreadProc(LPVOID param) @@ -294,52 +444,53 @@ DWORD WINAPI CIconThreadPool::WorkerThreadProc(LPVOID param) while (TRUE) { - DWORD wait = WaitForMultipleObjects(2, handles, FALSE, 100); // 100ms timeout for periodic check + DWORD wait = WaitForMultipleObjects(2, handles, FALSE, INFINITE); if (wait == WAIT_OBJECT_0) // Terminate event break; + + if (wait != WAIT_OBJECT_0 + 1) + continue; - // Try to get a work item + // Select a ready fixed slot under the lock so no two workers process it. CIconWorkItem* workItem = NULL; HANDLES(EnterCriticalSection(&pool->QueueLock)); - - if (pool->QueueCount > 0) + + int slot = pool->FindReadyWorkItemLocked(); + if (slot >= 0) { - workItem = &pool->WorkQueue[pool->QueueTail]; - - // Check if already processed or cancelled - if (workItem->Completed || workItem->Cancelled) - { - // Clear the slot and move on - workItem->RequestId = 0; - pool->QueueTail = (pool->QueueTail + 1) % ICON_POOL_QUEUE_SIZE; - InterlockedDecrement(&pool->QueueCount); - workItem = NULL; - } + workItem = &pool->WorkQueue[slot]; + InterlockedExchange(&workItem->InProgress, 1); + InterlockedIncrement(&pool->ActiveCount); } - + pool->UpdateWorkAvailableSignalLocked(); HANDLES(LeaveCriticalSection(&pool->QueueLock)); - if (workItem != NULL && !workItem->Cancelled) + if (workItem != NULL) { - // Process the work item - pool->ProcessWorkItem(workItem); - - // Mark as completed - InterlockedExchange(&workItem->Completed, 1); - - // Call result callback if set - if (pool->ResultCallback != NULL) + if (!workItem->Cancelled) { - pool->ResultCallback(workItem, pool->CallbackContext); + // Process the work item + pool->ProcessWorkItem(workItem); + + // Mark as completed + InterlockedExchange(&workItem->Completed, 1); + + // The callback runs before slot reuse; it must copy the icon to retain it. + if (!workItem->Cancelled && pool->ResultCallback != NULL) + pool->ResultCallback(workItem, pool->CallbackContext); } - // Remove from queue + // Release this fixed slot only after the worker and synchronous callback are done. HANDLES(EnterCriticalSection(&pool->QueueLock)); - workItem->RequestId = 0; - pool->QueueTail = (pool->QueueTail + 1) % ICON_POOL_QUEUE_SIZE; + if (!workItem->Cancelled) + InterlockedIncrement64(&pool->CompletedCount); + pool->ClearWorkItemLocked(*workItem); InterlockedDecrement(&pool->QueueCount); + InterlockedDecrement(&pool->ActiveCount); + pool->UpdateWorkAvailableSignalLocked(); + pool->UpdateCompletionSignalLocked(); HANDLES(LeaveCriticalSection(&pool->QueueLock)); } } @@ -403,6 +554,73 @@ void CIconThreadPool::ProcessWorkItem(CIconWorkItem* item) } } +BOOL CIconThreadPool::IsSameWorkItem(const CIconWorkItem& left, const CIconWorkItem& right) const +{ + return left.Type == right.Type && left.Index == right.Index && + left.IconSize == right.IconSize && left.Generation == right.Generation && + lstrcmpiA(left.Path, right.Path) == 0; +} + +int CIconThreadPool::FindReadyWorkItemLocked() const +{ + int selected = -1; + for (int i = 0; i < ICON_POOL_QUEUE_SIZE; i++) + { + const CIconWorkItem& candidate = WorkQueue[i]; + if (candidate.RequestId == 0 || candidate.Cancelled || candidate.InProgress) + continue; + if (selected < 0 || candidate.Priority > WorkQueue[selected].Priority || + (candidate.Priority == WorkQueue[selected].Priority && candidate.RequestId < WorkQueue[selected].RequestId)) + selected = i; + } + return selected; +} + +void CIconThreadPool::CancelWorkItemLocked(CIconWorkItem& item) +{ + if (item.RequestId != 0 && !item.Completed && !item.Cancelled) + { + InterlockedExchange(&item.Cancelled, 1); + InterlockedIncrement64(&CancelledCount); + } +} + +void CIconThreadPool::DiscardCancelledWorkItemsLocked() +{ + for (int i = 0; i < ICON_POOL_QUEUE_SIZE; i++) + { + CIconWorkItem& item = WorkQueue[i]; + if (item.RequestId != 0 && item.Cancelled && !item.InProgress) + { + ClearWorkItemLocked(item); + InterlockedDecrement(&QueueCount); + } + } +} + +void CIconThreadPool::ClearWorkItemLocked(CIconWorkItem& item) +{ + if (item.ResultIcon != NULL) + DestroyIcon(item.ResultIcon); + memset(&item, 0, sizeof(item)); +} + +void CIconThreadPool::UpdateWorkAvailableSignalLocked() +{ + if (FindReadyWorkItemLocked() >= 0) + SetEvent(WorkAvailableEvent); + else + ResetEvent(WorkAvailableEvent); +} + +void CIconThreadPool::UpdateCompletionSignalLocked() +{ + if (QueueCount == 0) + SetEvent(AllWorkCompletedEvent); + else + ResetEvent(AllWorkCompletedEvent); +} + // // **************************************************************************** // Helper functions for the icon reader thread diff --git a/src/iconpool.h b/src/iconpool.h index 60590551..79e2cb25 100644 --- a/src/iconpool.h +++ b/src/iconpool.h @@ -20,6 +20,13 @@ // Maximum number of pending work items in the queue #define ICON_POOL_QUEUE_SIZE 64 +// Visible panel entries must not wait behind speculative background warming. +enum EIconWorkPriority +{ + iwpBackground = 0, + iwpVisible = 1, +}; + // Work item types enum EIconWorkType { @@ -39,10 +46,29 @@ struct CIconWorkItem volatile LONG Completed; // 1 when work is done, 0 while pending volatile LONG Cancelled; // 1 if work was cancelled DWORD RequestId; // Unique ID for this request + DWORD Generation; // Panel/listing generation that owns this request + EIconWorkPriority Priority; // Visible work is selected before background warming + volatile LONG InProgress; // Keeps a fixed queue slot stable while a worker uses it +}; + +// Backpressure is observable so callers can distinguish a slow consumer from +// an icon-provider failure without making the queue grow beyond its fixed budget. +struct CIconQueueMetrics +{ + LONG Capacity; + LONG Queued; + LONG Active; + LONGLONG Submitted; + LONGLONG Completed; + LONGLONG Deduplicated; + LONGLONG Cancelled; + LONGLONG BackpressureRejected; + LONGLONG VisiblePreemptions; + LONGLONG HighWaterMark; }; -// Result callback - called when icon extraction completes -// Note: This is called from a worker thread, synchronization is the caller's responsibility +// Result callback - called when icon extraction completes. The callback must +// copy ResultIcon if it needs to retain it because the queue owns and destroys it. typedef void (*IconPoolResultCallback)(CIconWorkItem* item, void* context); class CIconThreadPool @@ -52,16 +78,17 @@ class CIconThreadPool HANDLE Workers[ICON_POOL_MAX_WORKERS]; int WorkerCount; - // Work queue (circular buffer) + // Fixed work slots bound memory while workers keep their selected slot stable. CIconWorkItem WorkQueue[ICON_POOL_QUEUE_SIZE]; - volatile LONG QueueHead; // Next slot to write to - volatile LONG QueueTail; // Next slot to read from - volatile LONG QueueCount; // Number of items in queue + volatile LONG QueueCount; // Number of queued or active work items + volatile LONG ActiveCount; // Number of items currently held by workers + DWORD CurrentGeneration; // Newer panel generations invalidate stale work // Synchronization CRITICAL_SECTION QueueLock; HANDLE WorkAvailableEvent; // Signaled when work is available HANDLE TerminateEvent; // Signaled to terminate workers + HANDLE AllWorkCompletedEvent; // Signaled only when no queued or active work remains // Callback for results IconPoolResultCallback ResultCallback; @@ -69,6 +96,15 @@ class CIconThreadPool // Request ID counter volatile LONG NextRequestId; + + // Monotonic counters expose loss/coalescing without retaining completed items. + volatile LONGLONG SubmittedCount; + volatile LONGLONG CompletedCount; + volatile LONGLONG DeduplicatedCount; + volatile LONGLONG CancelledCount; + volatile LONGLONG BackpressureRejectedCount; + volatile LONGLONG VisiblePreemptionCount; + volatile LONGLONG HighWaterMark; // Pool state BOOL Initialized; @@ -87,20 +123,33 @@ class CIconThreadPool // Set the callback for completed work items void SetResultCallback(IconPoolResultCallback callback, void* context); - // Submit a work item to the pool - // Returns request ID on success, 0 on failure (queue full) - DWORD SubmitGetFileIcon(const char* path, CIconSizeEnum iconSize); - DWORD SubmitExtractIcon(const char* path, int index, CIconSizeEnum iconSize); - DWORD SubmitLoadImageIcon(const char* path, CIconSizeEnum iconSize); + // Begin a newer panel/listing generation and cooperatively discard older work. + DWORD BeginGeneration(); + + // Submit a work item to the pool. A visible item may preempt queued + // background warming, but the fixed-capacity queue never allocates more slots. + // Returns request ID on success, 0 when bounded backpressure rejects the request. + DWORD SubmitGetFileIcon(const char* path, CIconSizeEnum iconSize, + DWORD generation = 0, BOOL visible = FALSE); + DWORD SubmitExtractIcon(const char* path, int index, CIconSizeEnum iconSize, + DWORD generation = 0, BOOL visible = FALSE); + DWORD SubmitLoadImageIcon(const char* path, CIconSizeEnum iconSize, + DWORD generation = 0, BOOL visible = FALSE); // Check if there are pending work items BOOL HasPendingWork(); // Get count of pending work items int GetPendingCount(); + + // Snapshot bounded-queue pressure, cancellations, and coalescing for diagnostics. + CIconQueueMetrics GetMetrics(); // Cancel all pending work items void CancelAllPending(); + + // Cancel queued work older than the supplied visible panel/listing generation. + void CancelObsoleteGenerations(DWORD currentGeneration); // Wait for all pending work to complete (with timeout in ms) // Returns TRUE if all work completed, FALSE on timeout @@ -118,6 +167,14 @@ class CIconThreadPool // Submit a work item (internal) DWORD SubmitWorkItem(CIconWorkItem* item); + + BOOL IsSameWorkItem(const CIconWorkItem& left, const CIconWorkItem& right) const; + int FindReadyWorkItemLocked() const; + void CancelWorkItemLocked(CIconWorkItem& item); + void DiscardCancelledWorkItemsLocked(); + void ClearWorkItemLocked(CIconWorkItem& item); + void UpdateWorkAvailableSignalLocked(); + void UpdateCompletionSignalLocked(); }; // Global icon thread pool instance diff --git a/src/lang/lang.rc b/src/lang/lang.rc index 1693f0af..0c6ba03b 100644 --- a/src/lang/lang.rc +++ b/src/lang/lang.rc @@ -1452,7 +1452,8 @@ BEGIN PUSHBUTTON "&Ignore Selected",IDB_IGNORESEL,218,129,77,14 END -IDD_CUSTOMIZETOOLBAR DIALOGEX 17, 65, 419, 153 +// The wider action column accommodates explicit localized icon dimensions. +IDD_CUSTOMIZETOOLBAR DIALOGEX 17, 65, 470, 153 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU CAPTION "Customize Toolbar" FONT 8, "MS Shell Dlg", 400, 0, 0x1 @@ -1463,11 +1464,13 @@ BEGIN PUSHBUTTON "<- &Remove",IDB_CTB_REMOVE,155,85,50,14 LTEXT "Current &toolbar buttons:",IDC_STATIC_2,213,6,105,8 LISTBOX IDL_CTB_CURRENT_ITEMS,213,16,142,133,LBS_OWNERDRAWFIXED | LBS_DISABLENOSCROLL | WS_VSCROLL | WS_GROUP | WS_TABSTOP - DEFPUSHBUTTON "&Close",IDOK,364,16,50,14,WS_GROUP - PUSHBUTTON "R&eset",IDB_CTB_RESET,364,35,50,14 - PUSHBUTTON "&Help",IDHELP,364,54,50,14 - PUSHBUTTON "Move &Up",IDB_CTB_UP,364,95,50,14,WS_GROUP - PUSHBUTTON "Move &Down",IDB_CTB_DOWN,364,115,50,14,WS_GROUP + DEFPUSHBUTTON "&Close",IDOK,364,16,101,14,WS_GROUP + PUSHBUTTON "R&eset",IDB_CTB_RESET,364,35,101,14 + PUSHBUTTON "&Help",IDHELP,364,54,101,14 + LTEXT "&Icon size:",IDC_STATIC_3,364,76,101,8 + COMBOBOX IDC_CTB_ICON_SIZE,364,86,101,60,CBS_DROPDOWNLIST | WS_TABSTOP + PUSHBUTTON "Move &Up",IDB_CTB_UP,364,111,101,14,WS_GROUP + PUSHBUTTON "Move &Down",IDB_CTB_DOWN,364,130,101,14,WS_GROUP END IDD_IMPORTCONFIG DIALOGEX 75, 79, 265, 136 @@ -2063,23 +2066,23 @@ BEGIN PUSHBUTTON "Help",IDHELP,139,122,50,14 END -IDD_SALMON_MAIN DIALOGEX 81, 47, 351, 199 +IDD_SALMON_MAIN DIALOGEX 81, 47, 351, 215 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU CAPTION "Open Salamander Bug Reporter" FONT 8, "MS Shell Dlg", 400, 0, 0x1 BEGIN LTEXT "A problem has occured, forcing Open Salamander to close. To help us avoid similar problems in the future, please send an error report below.",IDC_SALMON_INTRO,8,9,336,16 - LTEXT "Privacy Policy: we don't give out any of your information to any third party.",IDC_SALMON_PRIVACY,8,33,259,8 - LTEXT "...",IDC_SALMON_UPLOADING,8,42,84,8 - LTEXT "Error description (optional)",IDC_SALMON_DESCRIPTION,8,51,169,8 - PUSHBUTTON "&View Report",IDC_SALMON_VIEW,254,48,90,14 - LTEXT "&Last action:",IDC_SALMON_ACTION_LABEL,8,69,40,8 - EDITTEXT IDC_SALMON_ACTION,8,79,336,54,ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_WANTRETURN | WS_VSCROLL - LTEXT "&Contact email:",IDC_SALMON_EMAIL_LABEL,8,144,50,8 - EDITTEXT IDC_SALMON_EMAIL,8,154,336,14,ES_AUTOHSCROLL - CONTROL "&Restart Open Salamander",IDC_SALMON_RESTART,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,8,178,98,10 - DEFPUSHBUTTON "&Send Report",IDOK,158,176,90,14 - PUSHBUTTON "&Do Not Send Report",IDCANCEL,254,176,90,14 + LTEXT "Consent: Send Report uploads this crash archive (dump, optional description and optional email) to reports.taskscape.com over HTTPS. View Report lets you inspect it. Do Not Send Report prevents upload; you can then confirm deletion.",IDC_SALMON_PRIVACY,8,31,336,24 + LTEXT "...",IDC_SALMON_UPLOADING,8,57,84,8 + LTEXT "Error description (optional)",IDC_SALMON_DESCRIPTION,8,66,169,8 + PUSHBUTTON "&View Report",IDC_SALMON_VIEW,254,63,90,14 + LTEXT "&Last action:",IDC_SALMON_ACTION_LABEL,8,84,40,8 + EDITTEXT IDC_SALMON_ACTION,8,94,336,54,ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_WANTRETURN | WS_VSCROLL + LTEXT "&Contact email:",IDC_SALMON_EMAIL_LABEL,8,159,50,8 + EDITTEXT IDC_SALMON_EMAIL,8,169,336,14,ES_AUTOHSCROLL + CONTROL "&Restart Open Salamander",IDC_SALMON_RESTART,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,8,193,98,10 + DEFPUSHBUTTON "&Send Report",IDOK,158,191,90,14 + PUSHBUTTON "&Do Not Send Report",IDCANCEL,254,191,90,14 END IDD_CONFIRMLINKTGTCOPY DIALOGEX 15, 34, 352, 96 diff --git a/src/lang/lang.rh b/src/lang/lang.rh index ee032beb..34104113 100644 --- a/src/lang/lang.rh +++ b/src/lang/lang.rh @@ -609,6 +609,8 @@ #define IDB_CTB_RESET 2745 #define IDB_CTB_UP 2746 #define IDB_CTB_DOWN 2747 +// Stable automation identity for the toolbar-size preference control. +#define IDC_CTB_ICON_SIZE 2748 #define IDD_FIND_DUPLICATE 2750 #define IDC_FD_SAME_NAME 2751 #define IDC_FD_SAME_SIZE 2752 @@ -683,6 +685,13 @@ #define IDD_CONFIRMLINKTGTCOPY 6208 #define IDS_DETAILS 6209 #define IDC_DELETEARCHIVEFILES 6210 +#define IDS_METADATALOSS_BEFORESOURCEDELETE 8200 +#define IDS_METADATALOSS_LASTWRITETIME 8201 +#define IDS_METADATALOSS_CREATIONANDACCESSTIMES 8202 +#define IDS_METADATALOSS_ATTRIBUTES 8203 +#define IDS_METADATALOSS_SECURITY 8204 +#define IDS_METADATALOSS_ADS 8205 +#define IDS_METADATALOSS_COMPRESSIONANDENCRYPTION 8206 #define IDD_VIEWERGOTOOFFSET 6220 #define IDE_VGTO_OFFSET 6221 #define IDC_VGTO_HEX 6222 @@ -691,7 +700,7 @@ // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 8200 +#define _APS_NEXT_RESOURCE_VALUE 8207 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 6223 #define _APS_NEXT_SYMED_VALUE 101 diff --git a/src/lang/texts.rc2 b/src/lang/texts.rc2 index 2a5171fd..39a78104 100644 --- a/src/lang/texts.rc2 +++ b/src/lang/texts.rc2 @@ -684,6 +684,10 @@ STRINGTABLE IDS_GRIPSINTOOLBAR, "&Lock the Toolbars" IDS_MIDDLETOOLBAR, "&Middle Toolbar" IDS_CHANGEDIRECTORY, "Change &Directory...\tShift+F7" + // Keep the logical pixel dimensions visible when choosing a toolbar size. + IDS_TOOLBAR_ICON_SIZE_SMALL, "Small (16x16 pixels)" + IDS_TOOLBAR_ICON_SIZE_MEDIUM, "Medium (24x24 pixels)" + IDS_TOOLBAR_ICON_SIZE_LARGE, "Large (32x32 pixels)" IDS_HDR_ELASTIC, "Automatic Column Width" IDS_HDR_SMARTMODE, "Smart Column Mode\tCtrl+N" @@ -1104,6 +1108,7 @@ STRINGTABLE IDS_READDIRTERMINATED, "If you want to cancel listing (the list of files and directories will be incomplete), press the Yes button. If you want to continue with listing and temporarily suppress use of automatic refresh for current path, press the No button. If you want to continue with listing, press the Cancel button." IDS_READINGPATHESC, "Listing path ""%s"", press the ESC key to cancel..." + IDS_DIRECTORYENUMERATIONLIMIT, "Directory listing was limited to 100,000 items to keep the panel responsive. Refine the path or filter to see additional items." // ViewWith, EditWith: IDS_VIEWWITH_EXTERNAL, "External Viewer [%s]" @@ -1615,6 +1620,14 @@ STRINGTABLE IDS_ERRORGETTINGFILETIME, "Error Getting File Time" IDS_ERRORSETTINGFILETIME, "Error Setting File Time" + IDS_METADATALOSS_BEFORESOURCEDELETE, "The cross-volume move cannot preserve all metadata on %s.\n\nSource: %s\n\nThe following metadata is lost or could not be verified:\n%s\n\nYes deletes the source and completes the move. No keeps the source and continues copying." + IDS_METADATALOSS_LASTWRITETIME, "Last-write timestamp" + IDS_METADATALOSS_CREATIONANDACCESSTIMES, "Creation and last-access timestamps" + IDS_METADATALOSS_ATTRIBUTES, "File attributes" + IDS_METADATALOSS_SECURITY, "Owner, group, and access-control list" + IDS_METADATALOSS_ADS, "Alternate data streams" + IDS_METADATALOSS_COMPRESSIONANDENCRYPTION, "NTFS compression or EFS encryption" + IDS_USERMENUERROR, "User Menu Error" IDS_TOOLONGLISTOFSELNAMES, "List of selected names is too long. Please unselect some names. Maximal length is 32771 characters." IDS_EMPTYLISTOFSELNAMES, "List of selected names is empty. Please select some names or focus some name." @@ -1925,6 +1938,8 @@ STRINGTABLE IDS_SALMON_BUGREPORT_PROBLEM "Some problem has occured during the bug report creation:\n%s" IDS_SALMON_MINIDUMP_CREATE "Cannot create minidump file %s. Code: %d." IDS_SALMON_MINIDUMP_CALL "Error during minidump creation. Code: %d." + IDS_SALMON_HTTP_ERROR "Cannot upload the report securely. Code: %d." + IDS_SALMON_HTTP_STATUS "The secure crash-report service returned HTTP status %d." // IDS_SALMON_FAILED, "Starting of Bug Reporter failed. Please pack following bug report using 7-Zip packer and send it to our email address support@taskscape.com.\n\nThank you for helping us improve Open Salamander." IDS_SALMON_NOT_RUNNING, "Open Salamander Bug Reporter (salmon.exe) is not running. Open Salamander will not be able to handle potential crashes. Please restart Open Salamander." diff --git a/src/mainwnd_config.cpp b/src/mainwnd_config.cpp index d11a48ca..c0dbe677 100644 --- a/src/mainwnd_config.cpp +++ b/src/mainwnd_config.cpp @@ -22,6 +22,7 @@ #include "logo.h" #include "tasklist.h" #include "pwdmngr.h" +#include "regwork.h" // // ConfigVersion - version number of the loaded configuration @@ -133,6 +134,7 @@ // 102 = 4.00b1 (DB177) only to transfer plug-in configuration from version 4.00b1 (DB171) // 103 = 4.00 only to transfer plug-in configuration from version 4.00b1 (DB177) // 104 = 5.00 only to transfer plug-in configuration from version 4.00, first Open Salamander release +// 105 = 6.00 only to transfer configuration from version 5.00 into the new major-version root // // When increasing configuration version, add one to THIS_CONFIG_VERSION // @@ -140,7 +142,7 @@ // so that new plug-ins are auto-installed and the plugins.ver counter resets. // -const DWORD THIS_CONFIG_VERSION = 104; +const DWORD THIS_CONFIG_VERSION = 105; // Configuration roots for individual Open Salamander versions. // The root of the current (youngest) configuration is at index 0. @@ -152,6 +154,7 @@ const DWORD THIS_CONFIG_VERSION = 104; // !!! Keep the corresponding lines in SalamanderConfigurationVersions up to date const char* SalamanderConfigurationRoots[SALCFG_ROOTS_COUNT + 1] = { + "Software\\Open Salamander\\6.0", "Software\\Open Salamander\\5.0", "Software\\Taskscape Ltd\\Open Salamander 4.0", "Software\\Taskscape Ltd\\Open Salamander 4.0 beta 1 (DB177)", @@ -238,6 +241,7 @@ const char* SalamanderConfigurationRoots[SALCFG_ROOTS_COUNT + 1] = }; const char* SalamanderConfigurationVersions[SALCFG_ROOTS_COUNT] = { + "6.0", "5.0", "4.0", "4.0 beta 1 (DB177)", @@ -324,6 +328,435 @@ const char* SalamanderConfigurationVersions[SALCFG_ROOTS_COUNT] = const char* SALAMANDER_ROOT_REG = NULL; // will be set in salamdr1.cpp +// The public root remains the path that existing host and plug-in code opens. Once a +// transactional configuration has been committed it points at the selected generation; +// the immutable store root is used only to create and switch generations. +static char ConfigurationStoreRoot[512] = {}; +static char ActiveConfigurationRoot[512] = {}; +static const char* CONFIGURATION_GENERATIONS_REG = "Configuration Generations"; +static const char* CONFIGURATION_ACTIVE_GENERATION_REG = "Active Generation"; +static const char* CONFIGURATION_TRANSACTION_COMPLETE_REG = "Transaction Complete"; +static const char* CONFIGURATION_TRANSACTION_CHECKSUM_REG = "Transaction Checksum"; +static const char* CONFIGURATION_SCHEMA_VERSION_REG = "Configuration Schema Version"; +static const DWORD CONFIGURATION_SCHEMA_VERSION = 1; +static char ConfigurationSchemaDiagnostic[256] = {}; + +static const char* GetConfigurationGenerationName(DWORD generation) +{ + return generation == 0 ? "Generation 0" : "Generation 1"; +} + +static void HashConfigurationBytes(DWORD& checksum, const void* data, DWORD size) +{ + const BYTE* bytes = (const BYTE*)data; + for (DWORD i = 0; i < size; ++i) + { + checksum ^= bytes[i]; + checksum *= 16777619u; + } +} + +// The checksum deliberately excludes its own value and the completion marker. Registry +// enumeration is stable for a fixed key, so this catches partial trees and changed values +// before an active-generation switch is allowed. +static BOOL CalculateConfigurationChecksum(HKEY key, DWORD& checksum) +{ + char name[513]; + DWORD nameSize; + DWORD type; + DWORD dataSize; + LONG result; + DWORD index = 0; + + for (;;) + { + nameSize = sizeof(name); + result = RegEnumValue(key, index, name, &nameSize, NULL, &type, NULL, NULL); + if (result == ERROR_NO_MORE_ITEMS) + break; + if (result != ERROR_SUCCESS) + return FALSE; + name[nameSize] = 0; + + if (strcmp(name, CONFIGURATION_TRANSACTION_COMPLETE_REG) != 0 && + strcmp(name, CONFIGURATION_TRANSACTION_CHECKSUM_REG) != 0 && + strcmp(name, CONFIGURATION_SCHEMA_VERSION_REG) != 0) + { + result = RegQueryValueEx(key, name, NULL, &type, NULL, &dataSize); + if (result != ERROR_SUCCESS) + return FALSE; + HashConfigurationBytes(checksum, name, nameSize + 1); + HashConfigurationBytes(checksum, &type, sizeof(type)); + HashConfigurationBytes(checksum, &dataSize, sizeof(dataSize)); + BYTE* data = dataSize == 0 ? NULL : (BYTE*)malloc(dataSize); + if (dataSize != 0 && data == NULL) + return FALSE; + DWORD readSize = dataSize; + result = RegQueryValueEx(key, name, NULL, &type, data, &readSize); + if (result == ERROR_SUCCESS && readSize == dataSize) + HashConfigurationBytes(checksum, data, dataSize); + if (data != NULL) + free(data); + if (result != ERROR_SUCCESS || readSize != dataSize) + return FALSE; + } + ++index; + } + + index = 0; + for (;;) + { + nameSize = sizeof(name); + result = RegEnumKeyEx(key, index, name, &nameSize, NULL, NULL, NULL, NULL); + if (result == ERROR_NO_MORE_ITEMS) + break; + if (result != ERROR_SUCCESS) + return FALSE; + name[nameSize] = 0; + HashConfigurationBytes(checksum, name, nameSize + 1); + HKEY child; + if (RegOpenKeyEx(key, name, 0, KEY_READ, &child) != ERROR_SUCCESS) + return FALSE; + BOOL childOK = CalculateConfigurationChecksum(child, checksum); + RegCloseKey(child); + if (!childOK) + return FALSE; + ++index; + } + return TRUE; +} + +static BOOL GetRequiredDword(HKEY key, const char* valueName, DWORD& value) +{ + return GetValueAux(NULL, key, valueName, REG_DWORD, &value, sizeof(value)); +} + +static BOOL GetRequiredInt(HKEY key, const char* valueName, int& value) +{ + DWORD rawValue; + if (!GetRequiredDword(key, valueName, rawValue)) + return FALSE; + value = (int)rawValue; + return TRUE; +} + +static BOOL ValidateRequiredDwordRange(HKEY key, const char* valueName, DWORD minimum, DWORD maximum) +{ + DWORD value; + return GetRequiredDword(key, valueName, value) && value >= minimum && value <= maximum; +} + +static BOOL ValidateRequiredPercentage(HKEY key, const char* valueName) +{ + char value[20]; + char extra; + double percentage; + return GetValueAux(NULL, key, valueName, REG_SZ, value, sizeof(value)) && + sscanf(value, "%lf%c", &percentage, &extra) == 1 && + percentage >= 0.0 && percentage <= 100.0; +} + +static BOOL ValidateWindowConfigurationSchema(HKEY generationKey) +{ + HKEY windowKey = NULL; + int left, right, top, bottom; + BOOL valid = OpenKeyAux(NULL, generationKey, "Window", windowKey) && + GetRequiredInt(windowKey, "Left", left) && + GetRequiredInt(windowKey, "Right", right) && + GetRequiredInt(windowKey, "Top", top) && + GetRequiredInt(windowKey, "Bottom", bottom) && + left < right && top < bottom && + ValidateRequiredDwordRange(windowKey, "Show", 0, 11) && + ValidateRequiredPercentage(windowKey, "Split Position") && + ValidateRequiredPercentage(windowKey, "Before Zoom Split Position"); + if (windowKey != NULL) + CloseKeyAux(windowKey); + return valid; +} + +// This is the schema gate for a complete saved host profile. The transaction checksum +// protects every persisted value; the checks below reject values that are individually +// well-formed but unsafe together before LoadConfig exposes the snapshot to global state. +static BOOL ValidateCompleteConfigurationSchema(HKEY generationKey) +{ + DWORD schemaVersion; + DWORD configVersion; + DWORD visibleDrives; + DWORD separatedDrives; + HKEY versionKey = NULL; + HKEY configKey = NULL; + HKEY viewerKey = NULL; + BOOL valid = GetRequiredDword(generationKey, CONFIGURATION_SCHEMA_VERSION_REG, schemaVersion) && + schemaVersion == CONFIGURATION_SCHEMA_VERSION && + OpenKeyAux(NULL, generationKey, "Version", versionKey) && + GetRequiredDword(versionKey, "Config Version", configVersion) && + configVersion == THIS_CONFIG_VERSION && + OpenKeyAux(NULL, generationKey, "Configuration", configKey) && + ValidateRequiredDwordRange(configKey, "Size Format", SIZE_FORMAT_BYTES, SIZE_FORMAT_MIXED) && + ValidateRequiredDwordRange(configKey, "Thumbnail Size", THUMBNAIL_SIZE_MIN, THUMBNAIL_SIZE_MAX) && + ValidateRequiredDwordRange(configKey, "Title bar mode", TITLE_BAR_MODE_DIRECTORY, TITLE_BAR_MODE_FULLPATH) && + ValidateRequiredDwordRange(configKey, "Make File List Destination", 0, 2) && + ValidateRequiredDwordRange(configKey, "Time Resolution", 0, 3600) && + ValidateRequiredDwordRange(configKey, "DragDrop Min Time", 0, 60000) && + GetRequiredDword(configKey, "Visible Drives", visibleDrives) && + GetRequiredDword(configKey, "Separated Drives", separatedDrives) && + (visibleDrives & ~DRIVES_MASK) == 0 && + (separatedDrives & ~DRIVES_MASK) == 0 && + (separatedDrives & ~visibleDrives) == 0 && + OpenKeyAux(NULL, generationKey, "Viewer", viewerKey) && + ValidateRequiredDwordRange(viewerKey, "Tabelator Size", 1, 30) && + ValidateRequiredDwordRange(viewerKey, "Default Mode", 0, 2) && + ValidateWindowConfigurationSchema(generationKey); + if (viewerKey != NULL) + CloseKeyAux(viewerKey); + if (configKey != NULL) + CloseKeyAux(configKey); + if (versionKey != NULL) + CloseKeyAux(versionKey); + return valid; +} + +// Schema migrations are explicit and idempotent. Schema version 0 is the original +// transactional snapshot format; adding its version marker is safe because that marker +// is intentionally excluded from the content checksum above. +static BOOL MigrateConfigurationSchema(HKEY generationKey) +{ + DWORD schemaVersion = 0; + DWORD valueType = 0; + DWORD valueSize = sizeof(schemaVersion); + LONG readResult = RegQueryValueEx(generationKey, CONFIGURATION_SCHEMA_VERSION_REG, NULL, + &valueType, (BYTE*)&schemaVersion, &valueSize); + if (readResult == ERROR_FILE_NOT_FOUND) + schemaVersion = 0; + else if (readResult != ERROR_SUCCESS || valueType != REG_DWORD || valueSize != sizeof(schemaVersion)) + return FALSE; + if (schemaVersion > CONFIGURATION_SCHEMA_VERSION) + return FALSE; + + while (schemaVersion < CONFIGURATION_SCHEMA_VERSION) + { + switch (schemaVersion) + { + case 0: + schemaVersion = 1; + if (!SetValue(generationKey, CONFIGURATION_SCHEMA_VERSION_REG, REG_DWORD, + &schemaVersion, sizeof(schemaVersion))) + return FALSE; + break; + + default: + return FALSE; + } + } + return TRUE; +} + +static BOOL IsCommittedConfigurationGeneration(HKEY generationKey) +{ + DWORD complete = 0; + DWORD expectedChecksum = 0; + DWORD checksum = 2166136261u; + HKEY configKey = NULL; + HKEY versionKey = NULL; + if (!GetValueAux(NULL, generationKey, CONFIGURATION_TRANSACTION_COMPLETE_REG, REG_DWORD, + &complete, sizeof(complete)) || + complete != 1 || + !GetValueAux(NULL, generationKey, CONFIGURATION_TRANSACTION_CHECKSUM_REG, REG_DWORD, + &expectedChecksum, sizeof(expectedChecksum)) || + !OpenKeyAux(NULL, generationKey, "Configuration", configKey) || + !OpenKeyAux(NULL, generationKey, "Version", versionKey)) + { + if (configKey != NULL) + CloseKeyAux(configKey); + if (versionKey != NULL) + CloseKeyAux(versionKey); + return FALSE; + } + CloseKeyAux(configKey); + CloseKeyAux(versionKey); + return CalculateConfigurationChecksum(generationKey, checksum) && checksum == expectedChecksum; +} + +static BOOL OpenCommittedConfigurationGeneration(HKEY storeKey, DWORD generation, HKEY& generationKey) +{ + HKEY generationsKey = NULL; + generationKey = NULL; + if (!OpenKeyAux(NULL, storeKey, CONFIGURATION_GENERATIONS_REG, generationsKey)) + { + return FALSE; + } + if (!OpenKeyAux(NULL, generationsKey, GetConfigurationGenerationName(generation), generationKey)) + { + CloseKeyAux(generationsKey); + return FALSE; + } + CloseKeyAux(generationsKey); + if (IsCommittedConfigurationGeneration(generationKey) && + MigrateConfigurationSchema(generationKey) && + ValidateCompleteConfigurationSchema(generationKey)) + return TRUE; + CloseKeyAux(generationKey); + return FALSE; +} + +const char* GetConfigurationSchemaDiagnostic() +{ + return ConfigurationSchemaDiagnostic[0] == 0 ? NULL : ConfigurationSchemaDiagnostic; +} + +void SetConfigurationStoreRoot(const char* root) +{ + ConfigurationStoreRoot[0] = 0; + ActiveConfigurationRoot[0] = 0; + SALAMANDER_ROOT_REG = root; + if (root != NULL) + lstrcpyn(ConfigurationStoreRoot, root, sizeof(ConfigurationStoreRoot)); +} + +BOOL SelectCommittedConfigurationGeneration() +{ + if (ConfigurationStoreRoot[0] == 0) + return FALSE; + + HKEY storeKey; + if (!OpenKeyAux(NULL, HKEY_CURRENT_USER, ConfigurationStoreRoot, storeKey)) + return FALSE; + + ConfigurationSchemaDiagnostic[0] = 0; + DWORD activeGeneration; + BOOL selected = FALSE; + if (GetValueAux(NULL, storeKey, CONFIGURATION_ACTIVE_GENERATION_REG, REG_DWORD, + &activeGeneration, sizeof(activeGeneration)) && activeGeneration <= 1) + { + HKEY generationKey; + if (OpenCommittedConfigurationGeneration(storeKey, activeGeneration, generationKey)) + { + CloseKeyAux(generationKey); + selected = TRUE; + } + else + { + DWORD fallbackGeneration = 1 - activeGeneration; + if (OpenCommittedConfigurationGeneration(storeKey, fallbackGeneration, generationKey)) + { + // The interrupted/corrupt active slot is never exposed to normal readers. + SetValueAux(NULL, storeKey, CONFIGURATION_ACTIVE_GENERATION_REG, REG_DWORD, + &fallbackGeneration, sizeof(fallbackGeneration)); + RegFlushKey(storeKey); + CloseKeyAux(generationKey); + activeGeneration = fallbackGeneration; + selected = TRUE; + lstrcpyn(ConfigurationSchemaDiagnostic, + "Open Salamander ignored an invalid configuration profile and restored the last verified profile.", + sizeof(ConfigurationSchemaDiagnostic)); + } + else + lstrcpyn(ConfigurationSchemaDiagnostic, + "Open Salamander could not validate the saved configuration. The default profile will be used.", + sizeof(ConfigurationSchemaDiagnostic)); + } + } + CloseKeyAux(storeKey); + + if (selected) + { + _snprintf_s(ActiveConfigurationRoot, _TRUNCATE, "%s\\%s\\%s", ConfigurationStoreRoot, + CONFIGURATION_GENERATIONS_REG, GetConfigurationGenerationName(activeGeneration)); + SALAMANDER_ROOT_REG = ActiveConfigurationRoot; + } + else + { + // Legacy and imported configurations remain readable; the next save migrates them. + SALAMANDER_ROOT_REG = ConfigurationStoreRoot; + } + return selected; +} + +BOOL UsesTransactionalConfigurationStore() +{ + return ConfigurationStoreRoot[0] != 0 && SALAMANDER_ROOT_REG == ActiveConfigurationRoot; +} + +static BOOL BeginConfigurationTransaction(HKEY& storeKey, HKEY& generationKey, DWORD& generation) +{ + storeKey = NULL; + generationKey = NULL; + if (ConfigurationStoreRoot[0] == 0 || + !CreateKey(HKEY_CURRENT_USER, ConfigurationStoreRoot, storeKey)) + return FALSE; + + DWORD activeGeneration = 1; + GetValueAux(NULL, storeKey, CONFIGURATION_ACTIVE_GENERATION_REG, REG_DWORD, + &activeGeneration, sizeof(activeGeneration)); + generation = activeGeneration <= 1 ? 1 - activeGeneration : 0; + + HKEY generationsKey = NULL; + if (!CreateKey(storeKey, CONFIGURATION_GENERATIONS_REG, generationsKey) || + !CreateKey(generationsKey, GetConfigurationGenerationName(generation), generationKey)) + { + if (generationsKey != NULL) + CloseKey(generationsKey); + CloseKey(storeKey); + return FALSE; + } + CloseKey(generationsKey); + if (!ClearKey(generationKey)) + { + CloseKey(generationKey); + CloseKey(storeKey); + return FALSE; + } + return TRUE; +} + +static BOOL CommitConfigurationTransaction(HKEY storeKey, HKEY generationKey, DWORD generation) +{ + DWORD checksum = 2166136261u; + DWORD complete = 1; + BOOL committed = MigrateConfigurationSchema(generationKey) && + ValidateCompleteConfigurationSchema(generationKey) && + CalculateConfigurationChecksum(generationKey, checksum) && + SetValue(generationKey, CONFIGURATION_TRANSACTION_CHECKSUM_REG, REG_DWORD, + &checksum, sizeof(checksum)) && + SetValue(generationKey, CONFIGURATION_TRANSACTION_COMPLETE_REG, REG_DWORD, + &complete, sizeof(complete)) && + FlushConfigurationRegistryKey(generationKey) == ERROR_SUCCESS && + IsCommittedConfigurationGeneration(generationKey) && + SetValue(storeKey, CONFIGURATION_ACTIVE_GENERATION_REG, REG_DWORD, + &generation, sizeof(generation)) && + FlushConfigurationRegistryKey(storeKey) == ERROR_SUCCESS; + if (committed) + { + _snprintf_s(ActiveConfigurationRoot, _TRUNCATE, "%s\\%s\\%s", ConfigurationStoreRoot, + CONFIGURATION_GENERATIONS_REG, GetConfigurationGenerationName(generation)); + SALAMANDER_ROOT_REG = ActiveConfigurationRoot; // the selector write above is the atomic commit point + } + return committed; +} + +static void RetirePreviousConfigurationGenerationAfterSuccessfulStartup() +{ + // Do not discard the fallback until this process has successfully restored the chosen generation. + if (ConfigurationStoreRoot[0] == 0 || SALAMANDER_ROOT_REG != ActiveConfigurationRoot) + return; + HKEY storeKey; + if (OpenKeyAux(NULL, HKEY_CURRENT_USER, ConfigurationStoreRoot, storeKey)) + { + DWORD activeGeneration; + if (GetValueAux(NULL, storeKey, CONFIGURATION_ACTIVE_GENERATION_REG, REG_DWORD, + &activeGeneration, sizeof(activeGeneration)) && activeGeneration <= 1) + { + HKEY generationsKey; + if (OpenKeyAux(NULL, storeKey, CONFIGURATION_GENERATIONS_REG, generationsKey)) + { + SHDeleteKey(generationsKey, GetConfigurationGenerationName(1 - activeGeneration)); + CloseKeyAux(generationsKey); + } + } + CloseKeyAux(storeKey); + } +} + const char* SALAMANDER_SAVE_IN_PROGRESS = "Save In Progress"; // value exists only during configuration save (detects interrupted saves -> corrupted configuration) BOOL IsSetSALAMANDER_SAVE_IN_PROGRESS = FALSE; // TRUE = the registry contains SALAMANDER_SAVE_IN_PROGRESS (detect interrupted configuration saving) @@ -430,6 +863,8 @@ const char* CONFIG_TOPTOOLBAR_REG = "Top ToolBar"; const char* CONFIG_MIDDLETOOLBAR_REG = "Middle ToolBar"; const char* CONFIG_LEFTTOOLBAR_REG = "Left ToolBar"; const char* CONFIG_RIGHTTOOLBAR_REG = "Right ToolBar"; +// Store the logical size rather than the DPI-scaled bitmap dimension. +const char* CONFIG_TOOLBARICONSIZE_REG = "ToolBar Icon Size"; const char* CONFIG_TOPTOOLBARVISIBLE_REG = "Show Top ToolBar"; const char* CONFIG_PLGTOOLBARVISIBLE_REG = "Show Plugins Bar"; const char* CONFIG_MIDDLETOOLBARVISIBLE_REG = "Show Middle ToolBar"; @@ -1200,7 +1635,27 @@ MENU_TEMPLATE_ITEM MsgBoxButtons[] = } } } - BOOL cfgFound = rootFound && OpenKeyAux(NULL, hRootKey, SALAMANDER_CONFIG_REG, hCfgKey); + // A version root can contain either the legacy direct tree or a committed + // generation. Never offer a staging/incomplete generation as importable. + BOOL cfgFound = FALSE; + if (rootFound) + { + DWORD activeGeneration; + HKEY generationKey; + BOOL hasActiveGeneration = GetValueAux(NULL, hRootKey, CONFIGURATION_ACTIVE_GENERATION_REG, REG_DWORD, + &activeGeneration, sizeof(activeGeneration)) && activeGeneration <= 1; + if (hasActiveGeneration && + (OpenCommittedConfigurationGeneration(hRootKey, activeGeneration, generationKey) || + OpenCommittedConfigurationGeneration(hRootKey, 1 - activeGeneration, generationKey))) + { + cfgFound = OpenKeyAux(NULL, generationKey, SALAMANDER_CONFIG_REG, hCfgKey); + CloseKeyAux(generationKey); + } + else + { + cfgFound = OpenKeyAux(NULL, hRootKey, SALAMANDER_CONFIG_REG, hCfgKey); + } + } if (rootFound) CloseKeyAux(hRootKey); @@ -1409,34 +1864,21 @@ void CMainWindow::SaveConfig(HWND parent) } ConfigSaveInProgress = TRUE; + BeginConfigurationWriteFaultInjection(); // Normal saves use the existing worker so registry I/O yields to the UI message loop without a modal wait window. const BOOL registryWorkerStarted = GlobalSaveWaitWindow == NULL && !CriticalShutdown && RegistryWorkerThread.StartThread(); LoadSaveToRegistryMutex.Enter(); + HKEY storeKey; HKEY salamander; - if (CreateKey(HKEY_CURRENT_USER, SALAMANDER_ROOT_REG, salamander)) + DWORD transactionGeneration; + if (BeginConfigurationTransaction(storeKey, salamander, transactionGeneration)) { HKEY actKey; BOOL cfgIsOK = TRUE; - BOOL deleteSALAMANDER_SAVE_IN_PROGRESS = !IsSetSALAMANDER_SAVE_IN_PROGRESS; - if (deleteSALAMANDER_SAVE_IN_PROGRESS) - { - DWORD saveInProgress = 1; - if (GetValueAux(NULL, salamander, SALAMANDER_SAVE_IN_PROGRESS, REG_DWORD, &saveInProgress, sizeof(DWORD))) - { // use GetValueAux so we don't show the "Load Configuration" message - cfgIsOK = FALSE; // the configuration is corrupted; saving won't fix it (it wasn't stored completely) - TRACE_E("CMainWindow::SaveConfig(): unable to save configuration, configuration key in registry is corrupted"); - } - else - { - saveInProgress = 1; - SetValue(salamander, SALAMANDER_SAVE_IN_PROGRESS, REG_DWORD, &saveInProgress, sizeof(DWORD)); - IsSetSALAMANDER_SAVE_IN_PROGRESS = TRUE; - } - } if (cfgIsOK) { @@ -1999,6 +2441,9 @@ void CMainWindow::SaveConfig(HWND parent) SetValue(actKey, CONFIG_LEFTTOOLBAR_REG, REG_SZ, Configuration.LeftToolBar, -1); SetValue(actKey, CONFIG_RIGHTTOOLBAR_REG, REG_SZ, Configuration.RightToolBar, -1); + // Persist one shared size for all configurable command toolbars. + SetValue(actKey, CONFIG_TOOLBARICONSIZE_REG, REG_DWORD, + &Configuration.ToolbarIconSize, sizeof(DWORD)); SetValue(actKey, CONFIG_TOPTOOLBARVISIBLE_REG, REG_DWORD, &Configuration.TopToolBarVisible, sizeof(DWORD)); @@ -2356,13 +2801,11 @@ void CMainWindow::SaveConfig(HWND parent) if (GlobalSaveWaitWindow != NULL) GlobalSaveWaitWindow->SetProgressPos(++GlobalSaveWaitWindowProgress); // 8 - if (deleteSALAMANDER_SAVE_IN_PROGRESS) - { - DeleteValue(salamander, SALAMANDER_SAVE_IN_PROGRESS); - IsSetSALAMANDER_SAVE_IN_PROGRESS = FALSE; - } + if (!CommitConfigurationTransaction(storeKey, salamander, transactionGeneration)) + TRACE_E("CMainWindow::SaveConfig(): staged configuration failed validation; active generation was not changed"); } CloseKey(salamander); + CloseKey(storeKey); } LoadSaveToRegistryMutex.Leave(); @@ -2372,6 +2815,7 @@ void CMainWindow::SaveConfig(HWND parent) RegistryWorkerThread.StopThread(); ConfigSaveInProgress = FALSE; + EndConfigurationWriteFaultInjection(); if (ConfigSaveQueued && !CriticalShutdown) { // A later commit arrived while this save yielded to the UI, so debounce one follow-up snapshot. @@ -3533,6 +3977,11 @@ BOOL CMainWindow::LoadConfig(BOOL importingOldConfig, const CCommandLineParams* Configuration.LeftToolBar, 100); GetValue(actKey, CONFIG_RIGHTTOOLBAR_REG, REG_SZ, Configuration.RightToolBar, 100); + GetValue(actKey, CONFIG_TOOLBARICONSIZE_REG, REG_DWORD, + &Configuration.ToolbarIconSize, sizeof(DWORD)); + // Missing or damaged values fall back to the historical 16-pixel toolbar. + if (!IsValidToolbarIconSize(Configuration.ToolbarIconSize)) + Configuration.ToolbarIconSize = TOOLBAR_ICON_SIZE_SMALL; // there used to be only one change drive button - now we introduce two buttons // and merge all bitmaps into one @@ -4030,6 +4479,8 @@ BOOL CMainWindow::LoadConfig(BOOL importingOldConfig, const CCommandLineParams* // restore DefaultDir MainWindow->UpdateDefaultDir(TRUE); + if (ret) + RetirePreviousConfigurationGenerationAfterSuccessfulStartup(); return ret; } diff --git a/src/mainwnd_init.cpp b/src/mainwnd_init.cpp index 2e6de5f5..87d22019 100644 --- a/src/mainwnd_init.cpp +++ b/src/mainwnd_init.cpp @@ -2174,8 +2174,9 @@ void CMainWindow::OnWmContextMenu(HWND hWnd, int xPos, int yPos) // create the menu CMenuPopup menu; - menu.SetImageList(HGrayToolBarImageList, TRUE); - menu.SetHotImageList(HHotToolBarImageList, TRUE); + // Context menus stay compact regardless of the selected toolbar icon size. + menu.SetImageList(HGrayMenuImageList, TRUE); + menu.SetHotImageList(HHotMenuImageList, TRUE); // prepare a separator item MENU_ITEM_INFO miiSep; @@ -3166,6 +3167,8 @@ void CMainWindow::OnColorsChanged(BOOL reloadUMIcons) { TopToolBar->SetImageList(HGrayToolBarImageList); TopToolBar->SetHotImageList(HHotToolBarImageList); + // Recompute proportional spacing when Customize Toolbar replaces the image-list size. + TopToolBar->ApplyConfiguredIconSpacing(); TopToolBar->OnColorsChanged(); } @@ -3174,6 +3177,8 @@ void CMainWindow::OnColorsChanged(BOOL reloadUMIcons) { MiddleToolBar->SetImageList(HGrayToolBarImageList); MiddleToolBar->SetHotImageList(HHotToolBarImageList); + // Keep the vertical toolbar synchronized with the same live icon-size change. + MiddleToolBar->ApplyConfiguredIconSpacing(); MiddleToolBar->OnColorsChanged(); } @@ -3230,15 +3235,15 @@ void CMainWindow::OnColorsChanged(BOOL reloadUMIcons) BottomToolBar->OnColorsChanged(); } - // main menu - MainMenu.SetImageList(HGrayToolBarImageList, TRUE); - MainMenu.SetHotImageList(HHotToolBarImageList, TRUE); + // Main and archive menu row sizes remain independent from toolbar preferences. + MainMenu.SetImageList(HGrayMenuImageList, TRUE); + MainMenu.SetHotImageList(HHotMenuImageList, TRUE); // archive menu - ArchiveMenu.SetImageList(HGrayToolBarImageList, TRUE); - ArchiveMenu.SetHotImageList(HHotToolBarImageList, TRUE); - ArchivePanelMenu.SetImageList(HGrayToolBarImageList, TRUE); - ArchivePanelMenu.SetHotImageList(HHotToolBarImageList, TRUE); + ArchiveMenu.SetImageList(HGrayMenuImageList, TRUE); + ArchiveMenu.SetHotImageList(HHotMenuImageList, TRUE); + ArchivePanelMenu.SetImageList(HGrayMenuImageList, TRUE); + ArchivePanelMenu.SetHotImageList(HHotMenuImageList, TRUE); // left/right panel if (LeftPanel != NULL) @@ -3250,6 +3255,24 @@ void CMainWindow::OnColorsChanged(BOOL reloadUMIcons) { RightPanel->OnColorsChanged(); } + + // Rebar child metrics and panel geometry must follow a newly selected toolbar image height immediately. + if (HTopRebar != NULL && TopToolBar != NULL && TopToolBar->HWindow != NULL) + { + int index = (int)SendMessage(HTopRebar, RB_IDTOINDEX, BANDID_TOPTOOLBAR, 0); + if (index != -1) + { + REBARBANDINFO rbbi; + ZeroMemory(&rbbi, sizeof(rbbi)); + rbbi.cbSize = sizeof(rbbi); + rbbi.fMask = RBBIM_CHILDSIZE; + rbbi.cxMinChild = 10; + rbbi.cyMinChild = TopToolBar->GetNeededHeight(); + SendMessage(HTopRebar, RB_SETBANDINFO, index, (LPARAM)&rbbi); + } + } + if (HWindow != NULL) + LayoutWindows(); } void CMainWindow::StartAnimate() diff --git a/src/mainwnd_messages.cpp b/src/mainwnd_messages.cpp index 488c9364..188e9b64 100644 --- a/src/mainwnd_messages.cpp +++ b/src/mainwnd_messages.cpp @@ -38,6 +38,7 @@ extern "C" #include "worker.h" #include "find.h" #include "viewer.h" +#include "operation_journal.h" // variables used when saving configuration during shutdown, log-off or restart // we must pump messages so the system does not kill us as "not responding" @@ -71,6 +72,16 @@ const int MIN_WIN_WIDTH = 2; // minimal panel width extern BOOL CacheNextSetFocus; BOOL MainFrameIsActive = FALSE; +// Isolated UI tests use this generation to wait through persistence after Configuration dialog teardown. +static LONG FileManagerUiConfigurationGeneration = 0; + +static BOOL IsFileManagerUiTestControlEnabled() +{ + // Keep the private automation protocol inert for ordinary application launches. + char isolated[2]; + DWORD length = GetEnvironmentVariableA("FILEMANAGER_UI_ISOLATED", isolated, _countof(isolated)); + return length == 1 && isolated[0] == '1'; +} // code for testing time losses /* @@ -718,6 +729,8 @@ CMainWindow::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam) { case WM_CREATE: { + // Register before creating worker-facing services so their notifications carry this window generation. + DeleteManager.RegisterCallbackWindow(HWindow); SHChangeNotifyInitialize(); // request receiving Shell Notifications ExecLogStartupPhase("main window create"); @@ -848,12 +861,8 @@ MENU_TEMPLATE_ITEM AddToSystemMenu[] = TopToolBar->SetImageList(HGrayToolBarImageList); TopToolBar->SetHotImageList(HHotToolBarImageList); TopToolBar->SetStyle(TLB_STYLE_IMAGE | TLB_STYLE_ADJUSTABLE); - TOOLBAR_PADDING padding; - TopToolBar->GetPadding(&padding); - padding.ToolBarVertical = 1; - padding.IconLeft = 2; - padding.IconRight = 3; - TopToolBar->SetPadding(&padding); + // Initialize the main command bar with the Fluent inset for the configured icon size. + TopToolBar->ApplyConfiguredIconSpacing(); MiddleToolBar = new CMainToolBar(HWindow, mtbtMiddle); if (MiddleToolBar == NULL) @@ -864,11 +873,8 @@ MENU_TEMPLATE_ITEM AddToSystemMenu[] = MiddleToolBar->SetImageList(HGrayToolBarImageList); MiddleToolBar->SetHotImageList(HHotToolBarImageList); MiddleToolBar->SetStyle(TLB_STYLE_IMAGE | TLB_STYLE_ADJUSTABLE | TLB_STYLE_VERTICAL); - MiddleToolBar->GetPadding(&padding); - padding.ToolBarVertical = 1; - padding.IconLeft = 2; - padding.IconRight = 3; - MiddleToolBar->SetPadding(&padding); + // Match the vertical main toolbar to the same size-dependent Fluent spacing. + MiddleToolBar->ApplyConfiguredIconSpacing(); PluginsBar = new CPluginsBar(HWindow); if (PluginsBar == NULL) @@ -894,6 +900,7 @@ MENU_TEMPLATE_ITEM AddToSystemMenu[] = TRACE_E(LOW_MEMORY); return -1; } + TOOLBAR_PADDING padding; UMToolBar->GetPadding(&padding); padding.IconLeft = 2; padding.IconRight = 3; @@ -1204,6 +1211,9 @@ MENU_TEMPLATE_ITEM AddToSystemMenu[] = case WM_USER_PROCESSDELETEMAN: { + // A queued worker notification is valid only for the still-registered main-window generation. + if (!DeleteManager.IsCurrentCallbackWindow(HWindow, (DWORD)wParam)) + return 0; // delay data processing due to the main window activation after ESC from the viewer on WinXP; // without this hack, it somehow did not catch up - the main window stayed inactive and the safe-wait window never appeared if (!SetTimer(HWindow, IDT_DELETEMNGR_PROCESS, 200, NULL)) @@ -1659,6 +1669,8 @@ MENU_TEMPLATE_ITEM AddToSystemMenu[] = } EndStopRefresh(); // snooper starts again now + // Publish only after accepted changes have completed their synchronous SaveConfig call. + InterlockedIncrement(&FileManagerUiConfigurationGeneration); return 0; } @@ -1754,12 +1766,49 @@ MENU_TEMPLATE_ITEM AddToSystemMenu[] = // Command dispatch extracted to HandleWmCommand() in mainwnd_commands.cpp. return HandleWmCommand(wParam, lParam); + case WM_USER_UI_TEST_READY: + // The synchronous query acknowledges the exact native startup boundary to the isolated runner. + return IsFileManagerUiTestControlEnabled() && FileManagerUiStartupReady; + + case WM_USER_UI_TEST_COMMAND: + // Queue modal commands on the UI thread after acknowledging them, avoiding a cross-process SendMessage deadlock. + if (IsFileManagerUiTestControlEnabled() && FileManagerUiStartupReady) + { + // A left-panel refresh is non-modal and must complete before tests quick-search for newly created files. + if (lParam == 1 && wParam == CM_LEFTREFRESH) + { + SendMessage(HWindow, WM_COMMAND, wParam, 0); + return TRUE; // WM_COMMAND returns zero even when the synchronous refresh succeeds. + } + return PostMessage(HWindow, WM_COMMAND, wParam, lParam); + } + return FALSE; + + case WM_USER_UI_TEST_CONFIG_GENERATION: + // Expose completion only to the isolated runner; ordinary processes retain no automation surface. + return IsFileManagerUiTestControlEnabled() ? FileManagerUiConfigurationGeneration : 0; + + case WM_USER_UI_TEST_CONFIG_FAULT: + // Arm after startup so only the explicit recovery-test commit consumes the requested write boundary. + if (IsFileManagerUiTestControlEnabled() && FileManagerUiStartupReady && wParam > 0 && wParam <= LONG_MAX) + { + ArmNextConfigurationWriteFault((LONG)wParam); + return TRUE; + } + return FALSE; + + case WM_USER_UI_TEST_FTP_ORGANIZE_COMMAND: + // Resolve FTP's private command 7 through the host allocator only for an isolated, fully started test process. + if (IsFileManagerUiTestControlEnabled() && FileManagerUiStartupReady) + return Plugins.ResolveMenuItemCommandForTests(HWindow, "ftp\\ftp.spl", 7); + return 0; + case WM_USER_DISPACHCHANGENOTIF: { if (LastDispachChangeNotifTime < lParam) // not an outdated message { - if (AlreadyInPlugin || StopRefresh > 0) + if (IsInPlugin() || StopRefresh > 0) NeedToResentDispachChangeNotif = TRUE; else { @@ -1922,6 +1971,8 @@ MENU_TEMPLATE_ITEM AddToSystemMenu[] = // some toolbar was configured - force an update IdleForceRefresh = TRUE; IdleRefreshStates = TRUE; + // Toolbar contents and icon size are user preferences, so persist them through the shared debounce. + ScheduleConfigSave(); return 0; } @@ -3291,6 +3342,13 @@ MENU_TEMPLATE_ITEM AddToSystemMenu[] = // Shutdown handling extracted to HandleShutdown() in mainwnd_shutdown.cpp. return HandleShutdown(uMsg, wParam, lParam); + case WM_USER_ALLOCATION_EMERGENCY: + // The allocator only posted this pre-registered message; now that the + // UI thread owns execution it may write recovery state and close safely. + COperationJournal::PersistEmergencyShutdownState(); + PostMessage(HWindow, WM_USER_FORCECLOSE_MAINWND, 0, 0); + return 0; + case WM_ERASEBKGND: { @@ -3369,6 +3427,8 @@ MENU_TEMPLATE_ITEM AddToSystemMenu[] = case WM_DESTROY: { + // Invalidate before child teardown so concurrent workers cannot target this closing HWND. + DeleteManager.InvalidateCallbackWindow(HWindow); if (!CanDestroyMainWindow) { // some crazy shell extension has just called DestroyWindow on Salamander's main window diff --git a/src/mainwnd_shutdown.cpp b/src/mainwnd_shutdown.cpp index 7d673f8d..c0a47020 100644 --- a/src/mainwnd_shutdown.cpp +++ b/src/mainwnd_shutdown.cpp @@ -307,7 +307,7 @@ LRESULT CMainWindow::HandleShutdown(UINT uMsg, WPARAM wParam, LPARAM lParam) } } - if (!endAfterCleanup && AlreadyInPlugin > 0) + if (!endAfterCleanup && IsInPlugin()) { TRACE_E("WM_USER_CLOSE_MAINWND: AlreadyInPlugin > 0!"); if (uMsg == WM_QUERYENDSESSION) @@ -558,8 +558,9 @@ LRESULT CMainWindow::HandleShutdown(UINT uMsg, WPARAM wParam, LPARAM lParam) if (uMsg == WM_QUERYENDSESSION && (lParam & ENDSESSION_CRITICAL) != 0) // this applies to Vista+ { - BOOL cfgOK = FALSE; - if (SALAMANDER_ROOT_REG != NULL) + const BOOL transactionalConfig = UsesTransactionalConfigurationStore(); + BOOL cfgOK = transactionalConfig; + if (SALAMANDER_ROOT_REG != NULL && !transactionalConfig) { // ensure exclusive access to the configuration in the registry LoadSaveToRegistryMutex.Enter(); @@ -579,8 +580,8 @@ LRESULT CMainWindow::HandleShutdown(UINT uMsg, WPARAM wParam, LPARAM lParam) // NOTE: LoadSaveToRegistryMutex.Leave() is called again in WM_ENDSESSION after saving the config (see below) } - BOOL backupOK = FALSE; - if (cfgOK) // old configuration seems OK; back it up in case saving the new configuration fails + BOOL backupOK = transactionalConfig; + if (cfgOK && !transactionalConfig) // old configuration seems OK; back it up in case saving the new configuration fails { char backup[200]; sprintf_s(backup, "%s.backup.63A7CD13", SALAMANDER_ROOT_REG); // "63A7CD13" prevents the key name from matching a user key diff --git a/src/manifest.xml b/src/manifest.xml index 432a92f4..193a9358 100644 --- a/src/manifest.xml +++ b/src/manifest.xml @@ -1,10 +1,11 @@  + + version="6.0.0.0"> Open Salamander File Manager @@ -43,4 +44,4 @@ true - \ No newline at end of file + diff --git a/src/mapi.cpp b/src/mapi.cpp index 516cd5b4..645689e6 100644 --- a/src/mapi.cpp +++ b/src/mapi.cpp @@ -257,7 +257,8 @@ BOOL SimpleMAPISendMail(CSimpleMAPI* mapi) return FALSE; } - AddAuxThread(thread); // add the thread among existing viewers (killed on exit) + // MAPI may be inside provider code, so shutdown waits rather than ending it unsafely. + AddAuxThread(thread, FALSE, "Simple MAPI sender"); SetCursor(hOldCur); return TRUE; } diff --git a/src/menu_templates.cpp b/src/menu_templates.cpp index 5a8aa9da..6725baff 100644 --- a/src/menu_templates.cpp +++ b/src/menu_templates.cpp @@ -370,18 +370,19 @@ MENU_TEMPLATE_ITEM ArchivePanelMenuTemplate[] = BOOL BuildSalamanderMenus() { CALL_STACK_MESSAGE1("BuildSalamanderMenus()"); - // main window`s menu - MainMenu.LoadFromTemplate(HLanguage, MainMenuTemplate, NULL, HGrayToolBarImageList, HHotToolBarImageList); + // Menus deliberately use compact command lists even when toolbar icons are enlarged. + MainMenu.LoadFromTemplate(HLanguage, MainMenuTemplate, NULL, HGrayMenuImageList, HHotMenuImageList); // context menu in archives - ArchiveMenu.LoadFromTemplate(HLanguage, ArchiveMenuTemplate, NULL, HGrayToolBarImageList, HHotToolBarImageList); - ArchivePanelMenu.LoadFromTemplate(HLanguage, ArchivePanelMenuTemplate, NULL, HGrayToolBarImageList, HHotToolBarImageList); + ArchiveMenu.LoadFromTemplate(HLanguage, ArchiveMenuTemplate, NULL, HGrayMenuImageList, HHotMenuImageList); + ArchivePanelMenu.LoadFromTemplate(HLanguage, ArchivePanelMenuTemplate, NULL, HGrayMenuImageList, HHotMenuImageList); return TRUE; } BOOL BuildFindMenu(CMenuPopup* popup) { CALL_STACK_MESSAGE1("BuildFindMenu()"); - return popup->LoadFromTemplate(HLanguage, FindMenuTemplate, NULL, HGrayToolBarImageList, HHotToolBarImageList); + // Find menus follow the same compact menu sizing invariant as the main window. + return popup->LoadFromTemplate(HLanguage, FindMenuTemplate, NULL, HGrayMenuImageList, HHotMenuImageList); } DWORD diff --git a/src/operation_journal.cpp b/src/operation_journal.cpp new file mode 100644 index 00000000..99f61d0d --- /dev/null +++ b/src/operation_journal.cpp @@ -0,0 +1,503 @@ +// SPDX-FileCopyrightText: 2026 Taskscape Ltd +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "precomp.h" + +#include "operation_journal.h" +#include "worker.h" + +namespace +{ +const char* const JournalDirectoryName = "operation-journals"; + +BOOL GetJournalDirectory(char* directory, int directoryLen, BOOL create) +{ + BOOL gotPath = create ? CreateOurPathInRoamingAPPDATA(directory, directoryLen) + : GetOurPathInRoamingAPPDATA(directory, directoryLen); + if (!gotPath || !SalPathAppend(directory, JournalDirectoryName, directoryLen)) + return FALSE; + if (create) + CreateDirectoryUtf8(directory, NULL); // an existing directory is expected + return TRUE; +} + +BOOL WriteAll(HANDLE file, const char* text) +{ + DWORD length = (DWORD)strlen(text); + DWORD written = 0; + return WriteFile(file, text, length, &written, NULL) && written == length; +} + +BOOL GetPathIdentity(const char* path, char* identity, int identityLen) +{ + lstrcpyn(identity, "unavailable", identityLen); + if (path == NULL || path[0] == 0) + return FALSE; + HANDLE handle = HANDLES_Q(CreateFileUtf8(path, 0, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, + NULL)); + if (handle == INVALID_HANDLE_VALUE) + return FALSE; + BY_HANDLE_FILE_INFORMATION info; + BOOL result = GetFileInformationByHandle(handle, &info); + HANDLES(CloseHandle(handle)); + if (!result) + return FALSE; + _snprintf_s(identity, identityLen, _TRUNCATE, "%08lX:%08lX%08lX", + info.dwVolumeSerialNumber, info.nFileIndexHigh, info.nFileIndexLow); + return TRUE; +} + +const char* OpcodeName(COperationCode opcode) +{ + switch (opcode) + { + case ocCopyFile: return "copy-file"; + case ocMoveFile: return "move-file"; + case ocDeleteFile: return "delete-file"; + case ocCreateDir: return "create-dir"; + case ocMoveDir: return "move-dir"; + case ocDeleteDir: return "delete-dir"; + case ocDeleteDirLink: return "delete-dir-link"; + default: return NULL; + } +} + +BOOL IsTerminalJournal(const char* content) +{ + return strstr(content, "OPERATION|completed") != NULL || + strstr(content, "OPERATION|cancelled") != NULL || + strstr(content, "OPERATION|failed") != NULL || + strstr(content, "OPERATION|reconciled") != NULL; +} + +char* ReadJournal(const char* path) +{ + HANDLE file = HANDLES_Q(CreateFileUtf8(path, GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)); + if (file == INVALID_HANDLE_VALUE) + return NULL; + DWORD size = GetFileSize(file, NULL); + if (size == INVALID_FILE_SIZE || size > 16 * 1024 * 1024) + { + HANDLES(CloseHandle(file)); + return NULL; + } + char* content = (char*)malloc(size + 1); + DWORD read = 0; + BOOL ok = content != NULL && ReadFile(file, content, size, &read, NULL) && read == size; + HANDLES(CloseHandle(file)); + if (!ok) + { + if (content != NULL) + free(content); + return NULL; + } + content[size] = 0; + return content; +} + +void AppendRecoveryRecord(const char* path, const char* record) +{ + HANDLE file = HANDLES_Q(CreateFileUtf8(path, FILE_APPEND_DATA, FILE_SHARE_READ, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH, NULL)); + if (file != INVALID_HANDLE_VALUE) + { + WriteAll(file, record); + FlushFileBuffers(file); + HANDLES(CloseHandle(file)); + } +} + +struct CRecoveryItem +{ + int Index; + char* Target; + char* Temporary; + BOOL TemporaryReady; + CRecoveryItem() : Index(-1), Target(NULL), Temporary(NULL), TemporaryReady(FALSE) {} + ~CRecoveryItem() + { + if (Target != NULL) free(Target); + if (Temporary != NULL) free(Temporary); + } +}; + +CRecoveryItem* FindRecoveryItem(TDirectArray& items, int index) +{ + int i; + for (i = 0; i < items.Count; i++) + if (items[i]->Index == index) return items[i]; + return NULL; +} + +void SplitFields(char* line, char** fields, int fieldCount) +{ + int i; + fields[0] = line; + for (i = 1; i < fieldCount; i++) + { + char* separator = strchr(fields[i - 1], '|'); + if (separator == NULL) + fields[i] = (char*)""; + else + { + *separator = 0; + fields[i] = separator + 1; + } + } +} + +void ParseRecoveryItems(char* content, TDirectArray& items) +{ + char* line = content; + while (line != NULL && *line != 0) + { + char* next = strchr(line, '\n'); + if (next != NULL) *next++ = 0; + int length = (int)strlen(line); + if (length > 0 && line[length - 1] == '\r') line[length - 1] = 0; + char* fields[6]; + SplitFields(line, fields, _countof(fields)); + if (strcmp(fields[0], "ITEM") == 0) + { + CRecoveryItem* item = new CRecoveryItem; + if (item != NULL) + { + item->Index = atoi(fields[1]); + item->Target = _strdup(fields[4]); + if (!items.Add(item)) delete item; + } + } + else if (strcmp(fields[0], "TEMP") == 0) + { + CRecoveryItem* item = FindRecoveryItem(items, atoi(fields[1])); + if (item != NULL) + { + if (item->Temporary != NULL) free(item->Temporary); + item->Temporary = _strdup(fields[2]); + } + } + else if (strcmp(fields[0], "STATE") == 0 && strcmp(fields[2], "temporary-ready") == 0) + { + CRecoveryItem* item = FindRecoveryItem(items, atoi(fields[1])); + if (item != NULL) item->TemporaryReady = TRUE; + } + line = next; + } +} + +BOOL IsSiblingTransactionalTemporary(const char* temporaryPath, const char* targetPath) +{ + if (temporaryPath == NULL || targetPath == NULL) return FALSE; + char temporaryDirectory[3 * MAX_PATH]; + char targetDirectory[3 * MAX_PATH]; + lstrcpyn(temporaryDirectory, temporaryPath, _countof(temporaryDirectory)); + lstrcpyn(targetDirectory, targetPath, _countof(targetDirectory)); + if (!CutDirectory(temporaryDirectory) || !CutDirectory(targetDirectory) || + StrICmp(temporaryDirectory, targetDirectory) != 0) return FALSE; + const char* name = strrchr(temporaryPath, '\\'); + name = name == NULL ? temporaryPath : name + 1; + return _strnicmp(name, "SALCP", 5) == 0; +} + +BOOL MoveTemporaryToTarget(const char* temporaryPath, const char* targetPath) +{ + CStrP temporaryPathW(ConvertAllocUtf8ToWide(temporaryPath, -1)); + CStrP targetPathW(ConvertAllocUtf8ToWide(targetPath, -1)); + return temporaryPathW != NULL && targetPathW != NULL && + MoveFileExW(temporaryPathW, targetPathW, MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH); +} + +void ReconcileJournal(const char* path, int action, int& resumed, int& rolledBack, int& unresolved) +{ + char* content = ReadJournal(path); + if (content == NULL) { unresolved++; return; } + TDirectArray items(8, 8); + ParseRecoveryItems(content, items); + int i; + for (i = 0; i < items.Count; i++) + { + CRecoveryItem* item = items[i]; + if (item->Temporary == NULL || item->Target == NULL || + !IsSiblingTransactionalTemporary(item->Temporary, item->Target) || !FileExists(item->Temporary)) + continue; + if (action == IDYES && item->TemporaryReady) + { + if (MoveTemporaryToTarget(item->Temporary, item->Target)) resumed++; else unresolved++; + } + else if (action == IDNO) + { + if (DeleteFileUtf8(item->Temporary)) rolledBack++; else unresolved++; + } + else unresolved++; + } + for (i = 0; i < items.Count; i++) delete items[i]; + free(content); + AppendRecoveryRecord(path, "OPERATION|reconciled\r\n"); +} + +BOOL WriteReconciliationReport(char* reportPath, int reportPathLen, TDirectArray& journals, + int resumed, int rolledBack, int unresolved) +{ + char directory[MAX_PATH]; + if (!GetJournalDirectory(directory, _countof(directory), TRUE)) return FALSE; + _snprintf_s(reportPath, reportPathLen, _TRUNCATE, "%s\\reconciliation-%08lX.txt", directory, GetTickCount()); + HANDLE report = HANDLES_Q(CreateFileUtf8(reportPath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH, NULL)); + if (report == INVALID_HANDLE_VALUE) return FALSE; + char summary[256]; + _snprintf_s(summary, _countof(summary), _TRUNCATE, + "Open Salamander operation recovery report\r\nResumed commits: %d\r\nRolled back temporary files: %d\r\nUnresolved items: %d\r\n\r\n", + resumed, rolledBack, unresolved); + BOOL ok = WriteAll(report, summary); + int i; + for (i = 0; ok && i < journals.Count; i++) + { + char heading[3 * MAX_PATH + 32]; + _snprintf_s(heading, _countof(heading), _TRUNCATE, "Journal: %s\r\n", journals.At(i)); + char* content = ReadJournal(journals.At(i)); + ok = WriteAll(report, heading) && content != NULL; + if (content != NULL) + { + ok = ok && WriteAll(report, content) && WriteAll(report, "\r\n"); + free(content); + } + } + if (ok) ok = FlushFileBuffers(report); + HANDLES(CloseHandle(report)); + return ok; +} +} // namespace + +COperationJournal::COperationJournal() : File(INVALID_HANDLE_VALUE), CurrentItem(-1), CurrentAttempt(0) { Path[0] = 0; } + +COperationJournal::~COperationJournal() +{ + if (File != INVALID_HANDLE_VALUE) HANDLES(CloseHandle(File)); +} + +BOOL COperationJournal::Append(const char* text) +{ + return File != INVALID_HANDLE_VALUE && WriteAll(File, text) && FlushFileBuffers(File); +} + +BOOL COperationJournal::AppendPlanOperand(EOperationPlanOperandKind kind, const char* path, DWORD value) +{ + switch (kind) + { + case opokNone: + return Append(""); + case opokPath: + return Append(path == NULL ? "" : path); + case opokDWORD: + { + char text[16]; + _snprintf_s(text, _countof(text), _TRUNCATE, "0x%08lX", value); + return Append(text); + } + } + return FALSE; +} + +BOOL COperationJournal::AppendGoldenMasterPlan(COperations& operations) +{ + COperationPlan plan; + if (!plan.Capture(operations)) + return FALSE; + + char header[128]; + _snprintf_s(header, _countof(header), _TRUNCATE, "PLAN|1|operation=%s|items=%d\r\n", + plan.GetOperationId(), plan.GetCount()); + if (!Append(header)) + return FALSE; + + for (int index = 0; index < plan.GetCount(); ++index) + { + const COperationPlanItem& item = plan.At(index); + char prefix[192]; + _snprintf_s(prefix, _countof(prefix), _TRUNCATE, + "PLANITEM|%d|%s|size=%08lX:%08lX|file-size=%08lX:%08lX|attr=0x%08lX|flags=0x%08lX|source=", + index, COperationPlan::GetOpcodeName(item.Opcode), + item.Size.HiDWord, item.Size.LoDWord, + item.FileSize.HiDWord, item.FileSize.LoDWord, + item.Attr, item.OpFlags); + if (!Append(prefix) || + !AppendPlanOperand(item.SourceKind, item.SourcePath, item.SourceValue) || + !Append("|target=") || + !AppendPlanOperand(item.TargetKind, item.TargetPath, item.TargetValue) || + !Append("\r\n")) + return FALSE; + } + return TRUE; +} + +BOOL COperationJournal::Begin(COperations& operations) +{ + char directory[MAX_PATH]; + if (!GetJournalDirectory(directory, _countof(directory), TRUE)) return FALSE; + _snprintf_s(Path, _countof(Path), _TRUNCATE, "%s\\operation-%08lX-%08lX.opj", + directory, GetCurrentProcessId(), GetTickCount()); + File = HANDLES_Q(CreateFileUtf8(Path, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_NEW, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH, NULL)); + if (File == INVALID_HANDLE_VALUE) return FALSE; + char line[160]; + // Persist the ID independently of filenames so recovery matches an unfinished journal to diagnostics. + _snprintf_s(line, _countof(line), _TRUNCATE, + "FORMAT|1\r\nCORRELATION|operation=%s\r\nOPERATION|planned|items=%d\r\n", + operations.GetCorrelationId(), operations.Count); + if (!Append(line) || !AppendGoldenMasterPlan(operations)) return FALSE; + int i; + for (i = 0; i < operations.Count; i++) + { + const COperation* operation = &operations.At(i); + const char* opcode = OpcodeName(operation->Opcode); + if (opcode == NULL) continue; + const char* source = operation->Opcode == ocCreateDir ? operation->TargetName : operation->SourceName; + const char* target = operation->TargetName; + char identity[80]; + GetPathIdentity(source, identity, _countof(identity)); + char prefix[160]; + char sequence[16]; + _snprintf_s(prefix, _countof(prefix), _TRUNCATE, "ITEM|%d|%s|", i, opcode); + _snprintf_s(sequence, _countof(sequence), _TRUNCATE, "%d", i); + if (!Append(prefix) || !Append(source == NULL ? "" : source) || !Append("|") || + !Append(target == NULL ? "" : target) || !Append("|") || !Append(identity) || + !Append("|operation=") || !Append(operations.GetCorrelationId()) || + !Append("|sequence=") || !Append(sequence) || !Append("|attempt=1\r\n")) + return FALSE; + } + return TRUE; +} + +BOOL COperationJournal::BeginItem(int itemIndex, const COperation* operation, int attempt) +{ + if (OpcodeName(operation->Opcode) == NULL) return TRUE; + CurrentItem = itemIndex; + CurrentAttempt = attempt; + char line[96]; + _snprintf_s(line, _countof(line), _TRUNCATE, "STATE|%d|prepared|attempt=%d\r\n", CurrentItem, CurrentAttempt); + return Append(line); +} + +void COperationJournal::RecordRetry(int attempt) +{ + if (CurrentItem >= 0) + { + CurrentAttempt = attempt; + char line[80]; + // Retry history distinguishes another attempt from a duplicate callback after cancellation. + _snprintf_s(line, _countof(line), _TRUNCATE, "RETRY|%d|attempt=%d\r\n", CurrentItem, CurrentAttempt); + Append(line); + } +} + +BOOL COperationJournal::SetTemporaryPath(const char* temporaryPath) +{ + if (CurrentItem < 0 || temporaryPath == NULL) return FALSE; + char prefix[64]; + _snprintf_s(prefix, _countof(prefix), _TRUNCATE, "TEMP|%d|", CurrentItem); + return Append(prefix) && Append(temporaryPath) && Append("\r\n"); +} + +BOOL COperationJournal::MarkTemporaryReady() +{ + if (CurrentItem < 0) return FALSE; + char line[80]; + _snprintf_s(line, _countof(line), _TRUNCATE, "STATE|%d|temporary-ready\r\n", CurrentItem); + return Append(line); +} + +void COperationJournal::CompleteItem(BOOL succeeded) +{ + if (CurrentItem >= 0) + { + char line[96]; + _snprintf_s(line, _countof(line), _TRUNCATE, "STATE|%d|%s|attempt=%d\r\n", CurrentItem, + succeeded ? "committed" : "failed", CurrentAttempt); + Append(line); + CurrentItem = -1; + CurrentAttempt = 0; + } +} + +void COperationJournal::Finish(BOOL failed, BOOL cancelled) +{ + if (File != INVALID_HANDLE_VALUE) + { + Append(cancelled ? "OPERATION|cancelled\r\n" : failed ? "OPERATION|failed\r\n" : "OPERATION|completed\r\n"); + HANDLES(CloseHandle(File)); + File = INVALID_HANDLE_VALUE; + } +} + +void COperationJournal::PersistEmergencyShutdownState() +{ + // The normal journal remains authoritative for individual items; this + // small marker explains why a later recovery scan found it unfinished. + char directory[MAX_PATH]; + if (!GetJournalDirectory(directory, _countof(directory), TRUE)) return; + char path[3 * MAX_PATH]; + _snprintf_s(path, _countof(path), _TRUNCATE, "%s\\memory-pressure-%08lX-%08lX.opj", + directory, GetCurrentProcessId(), GetTickCount()); + HANDLE file = HANDLES_Q(CreateFileUtf8(path, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_NEW, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH, NULL)); + if (file == INVALID_HANDLE_VALUE) return; + WriteAll(file, "FORMAT|1\r\nOPERATION|memory-pressure\r\n"); + FlushFileBuffers(file); + HANDLES(CloseHandle(file)); +} + +void COperationJournal::OfferRecovery(HWND parent) +{ + char directory[MAX_PATH]; + if (!GetJournalDirectory(directory, _countof(directory), FALSE)) return; + char pattern[3 * MAX_PATH]; + _snprintf_s(pattern, _countof(pattern), _TRUNCATE, "%s\\*.opj", directory); + WIN32_FIND_DATA findData; + HANDLE find = HANDLES_Q(FindFirstFileUtf8(pattern, &findData)); + if (find == INVALID_HANDLE_VALUE) return; + TDirectArray journals(4, 4); + do + { + if ((findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) + { + char path[3 * MAX_PATH]; + _snprintf_s(path, _countof(path), _TRUNCATE, "%s\\%s", directory, findData.cFileName); + char* content = ReadJournal(path); + if (content != NULL) + { + if (!IsTerminalJournal(content)) + { + char* copy = _strdup(path); + if (copy != NULL && !journals.Add(copy)) free(copy); + } + free(content); + } + } + } while (FindNextFileA(find, &findData)); + HANDLES(FindClose(find)); + if (journals.Count == 0) return; + int action = SalMessageBox(parent, + "Open Salamander found incomplete file operations.\r\n\r\n" + "Yes resumes only fully written transactional targets.\r\n" + "No rolls back known uncommitted transactional targets.\r\n" + "Cancel leaves files unchanged and writes a reconciliation report.", + "Recover file operations", MB_YESNOCANCEL | MB_ICONEXCLAMATION | MB_DEFBUTTON3); + int resumed = 0, rolledBack = 0, unresolved = 0; + int i; + for (i = 0; i < journals.Count; i++) ReconcileJournal(journals[i], action, resumed, rolledBack, unresolved); + char reportPath[3 * MAX_PATH]; + if (WriteReconciliationReport(reportPath, _countof(reportPath), journals, resumed, rolledBack, unresolved)) + { + char message[3 * MAX_PATH + 160]; + _snprintf_s(message, _countof(message), _TRUNCATE, + "Recovery reconciliation completed.\r\nResumed: %d\r\nRolled back: %d\r\nUnresolved: %d\r\n\r\nReport: %s", + resumed, rolledBack, unresolved, reportPath); + SalMessageBox(parent, message, "File operation recovery", MB_OK | MB_ICONINFORMATION); + } + for (i = 0; i < journals.Count; i++) free(journals[i]); +} diff --git a/src/operation_journal.h b/src/operation_journal.h new file mode 100644 index 00000000..0f6766a1 --- /dev/null +++ b/src/operation_journal.h @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: 2026 Taskscape Ltd +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "operation_plan.h" + +class COperations; +struct COperation; + +// Durable, append-only facts for a native disk-operation script. Recovery +// never replays ordinary destructive work: it can only commit a fully-written +// transactional sibling target, remove an uncommitted sibling target, or +// produce a reconciliation report. +class COperationJournal +{ +private: + HANDLE File; + char Path[3 * MAX_PATH]; + int CurrentItem; + int CurrentAttempt; + + BOOL Append(const char* text); + BOOL AppendPlanOperand(EOperationPlanOperandKind kind, const char* path, DWORD value); + BOOL AppendGoldenMasterPlan(COperations& operations); + +public: + COperationJournal(); + ~COperationJournal(); + + BOOL Begin(COperations& operations); + BOOL BeginItem(int itemIndex, const COperation* operation, int attempt); + void RecordRetry(int attempt); + BOOL SetTemporaryPath(const char* temporaryPath); + BOOL MarkTemporaryReady(); + void CompleteItem(BOOL succeeded); + void Finish(BOOL failed, BOOL cancelled); + + // Runs after the allocator's posted UI notification, keeping filesystem I/O + // out of the failing allocation thread while retaining an auditable marker. + static void PersistEmergencyShutdownState(); + static void OfferRecovery(HWND parent); +}; diff --git a/src/operation_plan.cpp b/src/operation_plan.cpp new file mode 100644 index 00000000..a13b82ed --- /dev/null +++ b/src/operation_plan.cpp @@ -0,0 +1,147 @@ +// SPDX-FileCopyrightText: 2026 Taskscape Ltd +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "precomp.h" + +#include "operation_plan.h" +#include "worker.h" + +namespace +{ +char* DuplicatePlanPath(const char* path) +{ + if (path == NULL) + return NULL; + return DupStr(path); +} + +BOOL SetPlanOperand(EOperationPlanOperandKind& kind, char*& path, DWORD& value, + const char* operationValue, BOOL isPath) +{ + kind = isPath ? opokPath : opokDWORD; + if (isPath) + { + path = DuplicatePlanPath(operationValue); + return path != NULL; + } + else + value = (DWORD)(DWORD_PTR)operationValue; + return TRUE; +} + +BOOL IsPathSource(COperationCode opcode) +{ + return opcode != ocLabelForSkipOfCreateDir && opcode != ocCopyDirTime; +} + +BOOL IsPathTarget(COperationCode opcode) +{ + return opcode != ocChangeAttrs && opcode != ocLabelForSkipOfCreateDir && opcode != ocCopyDirTime; +} +} + +COperationPlanItem::COperationPlanItem() : Opcode(ocCopyFile), Size(0, 0), FileSize(0, 0), + Attr(0), OpFlags(0), SourceKind(opokNone), TargetKind(opokNone), + SourcePath(NULL), TargetPath(NULL), SourceValue(0), TargetValue(0) +{ +} + +COperationPlanItem::COperationPlanItem(const COperationPlanItem& item) : SourcePath(NULL), TargetPath(NULL) +{ + *this = item; +} + +COperationPlanItem::~COperationPlanItem() +{ + if (SourcePath != NULL) free(SourcePath); + if (TargetPath != NULL) free(TargetPath); +} + +COperationPlanItem& COperationPlanItem::operator=(const COperationPlanItem& item) +{ + if (this == &item) + return *this; + if (SourcePath != NULL) free(SourcePath); + if (TargetPath != NULL) free(TargetPath); + Opcode = item.Opcode; + Size = item.Size; + FileSize = item.FileSize; + Attr = item.Attr; + OpFlags = item.OpFlags; + SourceKind = item.SourceKind; + TargetKind = item.TargetKind; + SourcePath = item.SourceKind == opokPath ? DuplicatePlanPath(item.SourcePath) : NULL; + TargetPath = item.TargetKind == opokPath ? DuplicatePlanPath(item.TargetPath) : NULL; + SourceValue = item.SourceValue; + TargetValue = item.TargetValue; + return *this; +} + +BOOL COperationPlanItem::IsGood() const +{ + return (SourceKind != opokPath || SourcePath != NULL) && + (TargetKind != opokPath || TargetPath != NULL); +} + +COperationPlan::COperationPlan() : Items(16, 16) +{ + OperationId[0] = 0; +} + +BOOL COperationPlan::Capture(COperations& operations) +{ + if (Items.Count != 0) + return FALSE; // snapshots are immutable once exposed to the execution boundary + + // Capture the command-dispatch ID before copying items and releasing the live script. + lstrcpyn(OperationId, operations.GetCorrelationId(), _countof(OperationId)); + + for (int index = 0; index < operations.Count; ++index) + { + const COperation& operation = operations.At(index); + COperationPlanItem item; + item.Opcode = operation.Opcode; + item.Size = operation.Size; + if (operation.Opcode == ocCopyFile || operation.Opcode == ocMoveFile) + item.FileSize = operation.FileSize; + item.Attr = operation.Attr; + item.OpFlags = operation.OpFlags; + + if (operation.SourceName != NULL && + !SetPlanOperand(item.SourceKind, item.SourcePath, item.SourceValue, + operation.SourceName, IsPathSource(operation.Opcode))) + return FALSE; + if (operation.TargetName != NULL && + !SetPlanOperand(item.TargetKind, item.TargetPath, item.TargetValue, + operation.TargetName, IsPathTarget(operation.Opcode))) + return FALSE; + + int itemIndex = Items.Add(item); + if (itemIndex == ULONG_MAX || !Items.IsGood() || !Items.At(itemIndex).IsGood()) + { + Items.ResetState(); + return FALSE; + } + } + return TRUE; +} + +const char* COperationPlan::GetOpcodeName(COperationCode opcode) +{ + switch (opcode) + { + case ocCopyFile: return "copy-file"; + case ocMoveFile: return "move-file"; + case ocDeleteFile: return "delete-file"; + case ocCreateDir: return "create-dir"; + case ocMoveDir: return "move-dir"; + case ocDeleteDir: return "delete-dir"; + case ocDeleteDirLink: return "delete-dir-link"; + case ocChangeAttrs: return "change-attrs"; + case ocCountSize: return "count-size"; + case ocConvert: return "convert"; + case ocLabelForSkipOfCreateDir: return "skip-dir-label"; + case ocCopyDirTime: return "copy-dir-time"; + default: return "unknown"; + } +} diff --git a/src/operation_plan.h b/src/operation_plan.h new file mode 100644 index 00000000..5f912750 --- /dev/null +++ b/src/operation_plan.h @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: 2026 Taskscape Ltd +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +class COperations; + +// A script owns this stable identifier from command dispatch through all +// asynchronous handoffs, so concurrent operations cannot share a trace. +#define OPERATION_CORRELATION_ID_LENGTH 32 + +// The operation plan is deliberately separate from the live worker state. It +// contains only the deterministic instructions produced by the planner, so it +// can be captured, compared, or persisted before a worker starts prompting or +// touching the filesystem. +enum COperationCode +{ + ocCopyFile, + ocMoveFile, + ocDeleteFile, + ocCreateDir, + ocMoveDir, + ocDeleteDir, + ocDeleteDirLink, + ocChangeAttrs, // requested attributes are stored in TargetName as a DWORD + ocCountSize, + ocConvert, + ocLabelForSkipOfCreateDir, // SourceName/TargetName hold DWORD payloads; Attr holds a script index + ocCopyDirTime, // SourceName and Attr hold the last-write FILETIME payload +}; + +enum EOperationPlanOperandKind +{ + opokNone, + opokPath, + opokDWORD +}; + +struct COperationPlanItem +{ + COperationCode Opcode; + CQuadWord Size; + CQuadWord FileSize; + DWORD Attr; + DWORD OpFlags; + EOperationPlanOperandKind SourceKind; + EOperationPlanOperandKind TargetKind; + char* SourcePath; + char* TargetPath; + DWORD SourceValue; + DWORD TargetValue; + + COperationPlanItem(); + COperationPlanItem(const COperationPlanItem& item); + ~COperationPlanItem(); + COperationPlanItem& operator=(const COperationPlanItem& item); + BOOL IsGood() const; +}; + +class COperationPlan +{ +private: + TDirectArray Items; + // Retain the dispatch ID with the immutable snapshot for post-crash matching. + char OperationId[OPERATION_CORRELATION_ID_LENGTH]; + +public: + COperationPlan(); + + // Captures the current script without retaining its pointer-owned names or + // referencing worker, dialog, or filesystem state. + BOOL Capture(COperations& operations); + + int GetCount() const { return Items.Count; } + const COperationPlanItem& At(int index) { return Items.At(index); } + const char* GetOperationId() const { return OperationId; } + + static const char* GetOpcodeName(COperationCode opcode); +}; diff --git a/src/operation_result.h b/src/operation_result.h new file mode 100644 index 00000000..b11a45c6 --- /dev/null +++ b/src/operation_result.h @@ -0,0 +1,147 @@ +// SPDX-FileCopyrightText: 2026 Taskscape Ltd +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "retry_policy.h" + +// File-operation failures cross worker, journal, and dialog boundaries. Keep +// reservation, verification, and commit phases with their effect state so a +// later compatibility adapter cannot replace the causal error with a cleanup-side failure. +enum EOperationResultPhase +{ + orpNone, + orpPrepareTransactionalTarget, + orpVerifyDurableCopy, + orpVerifyDestinationIdentity, + orpCommitTransactionalTarget +}; + +enum EOperationPartialEffect +{ + opeNone = 0, + opeTemporaryTargetReady = 0x0001, + opeDestinationCommitted = 0x0002 +}; + +enum EOperationCleanupPhase +{ + orcpNone, + orcpCloseVerificationHandle, + orcpDeleteUnverifiedTarget +}; + +struct COperationCleanupError +{ + EOperationCleanupPhase Phase; + DWORD Win32Error; + const char* Path; +}; + +// This is intentionally a non-owning, lightweight transport object: operation +// paths remain owned by the script while the result is consumed by its dialog. +struct COperationResult +{ + EOperationResultPhase Phase; + DWORD Win32Error; + HRESULT HResult; + const char* Source; + const char* Destination; + BOOL Retryable; + DWORD PartialEffects; + COperationCleanupError CleanupErrors[2]; + DWORD CleanupErrorCount; + + static COperationResult Success(EOperationResultPhase phase, const char* source, + const char* destination, DWORD partialEffects = opeNone) + { + COperationResult result = {phase, ERROR_SUCCESS, S_OK, source, destination, FALSE, partialEffects, + {{orcpNone, ERROR_SUCCESS, NULL}, {orcpNone, ERROR_SUCCESS, NULL}}, 0}; + return result; + } + + static COperationResult Failure(EOperationResultPhase phase, DWORD error, const char* source, + const char* destination, BOOL retryable, + DWORD partialEffects = opeNone) + { + COperationResult result = {phase, error, HRESULT_FROM_WIN32(error), source, destination, + retryable, partialEffects, + {{orcpNone, ERROR_SUCCESS, NULL}, {orcpNone, ERROR_SUCCESS, NULL}}, 0}; + return result; + } + + BOOL Succeeded() const { return Win32Error == ERROR_SUCCESS; } + + // Cleanup is secondary evidence: preserve the first actionable failure even + // when closing or deleting subsequently changes the thread's last-error value. + void AppendCleanupError(EOperationCleanupPhase phase, DWORD error, const char* path) + { + if (error != ERROR_SUCCESS && CleanupErrorCount < _countof(CleanupErrors)) + { + CleanupErrors[CleanupErrorCount].Phase = phase; + CleanupErrors[CleanupErrorCount].Win32Error = error; + CleanupErrors[CleanupErrorCount].Path = path; + ++CleanupErrorCount; + } + } + + static const char* PhaseName(EOperationResultPhase phase) + { + switch (phase) + { + case orpPrepareTransactionalTarget: return "prepare-transactional-target"; + case orpVerifyDurableCopy: return "verify-durable-copy"; + case orpVerifyDestinationIdentity: return "verify-destination-identity"; + case orpCommitTransactionalTarget: return "commit-transactional-target"; + default: return "none"; + } + } + + static const char* CleanupPhaseName(EOperationCleanupPhase phase) + { + switch (phase) + { + case orcpCloseVerificationHandle: return "close-verification-handle"; + case orcpDeleteUnverifiedTarget: return "delete-unverified-target"; + default: return "none"; + } + } + + // This fixed-buffer text is suitable for existing message boxes, whose Ctrl+C + // behavior gives support a stable, complete diagnostic without heap allocation. + void BuildDiagnosticSummary(char* buffer, int bufferLength) const + { + if (buffer == NULL || bufferLength <= 0) + return; + + int written = _snprintf_s(buffer, bufferLength, _TRUNCATE, + "phase=%s; error=%lu (0x%08lX); source=%s; destination=%s; retryable=%s; effects=0x%08lX", + PhaseName(Phase), Win32Error, Win32Error, + Source != NULL ? Source : "", Destination != NULL ? Destination : "", + Retryable ? "yes" : "no", PartialEffects); + for (DWORD index = 0; written >= 0 && index < CleanupErrorCount; ++index) + { + written += _snprintf_s(buffer + written, bufferLength - written, _TRUNCATE, + "; cleanup[%lu]=%s error=%lu path=%s", index, + CleanupPhaseName(CleanupErrors[index].Phase), + CleanupErrors[index].Win32Error, + CleanupErrors[index].Path != NULL ? CleanupErrors[index].Path : ""); + } + } + + // Existing dialogs still expect BOOL plus an out error; this preserves their + // behavior while callers migrate to the complete result contract. + BOOL ToLegacyBool(DWORD* error) const + { + if (!Succeeded() && error != NULL) + *error = Win32Error; + return Succeeded(); + } +}; + +// Reuse the central transient classification; callers still retain the final +// decision because a retryable error can occur after a destructive commit. +inline BOOL IsRetryableOperationError(DWORD error) +{ + return IsTransientOperationError(error); +} diff --git a/src/operations_core.cpp b/src/operations_core.cpp index 4db26810..987b654e 100644 --- a/src/operations_core.cpp +++ b/src/operations_core.cpp @@ -7,6 +7,8 @@ #include "cfgdlg.h" #include "worker.h" #include "execlog.h" +#include "common/allochan.h" +#include "release_diagnostics.h" #include #include @@ -28,7 +30,7 @@ BOOL DoCopyFile(COperation* op, HWND hProgressDlg, void* buffer, DWORD clearReadonlyMask, BOOL* skip, BOOL lantasticCheck, int& mustDeleteFileBeforeOverwrite, int& allocWholeFileOnStart, CProgressDlgData& dlgData, BOOL copyADS, BOOL copyAsEncrypted, - BOOL isMove, CAsyncCopyParams*& asyncPar); + BOOL isMove, CAsyncCopyParams*& asyncPar, BOOL* suspiciousIoRetry); struct TMN_REPARSE_DATA_BUFFER { @@ -78,7 +80,7 @@ struct TMN_REPARSE_DATA_BUFFER return FALSE; */ -BOOL DoDeleteDirLinkAux(const char* nameDelLink, DWORD* err) +BOOL DoDeleteDirLinkAux(const char* nameDelLink, const COperation::CFileIdentity& expectedIdentity, DWORD* err) { // remove the reparse point from directory 'nameDelLink' if (err != NULL) @@ -87,14 +89,18 @@ BOOL DoDeleteDirLinkAux(const char* nameDelLink, DWORD* err) DWORD attr = GetFileAttributesUtf8(nameDelLink); if (attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_REPARSE_POINT)) { - HANDLE dir = HANDLES_Q(CreateFileUtf8(nameDelLink, GENERIC_WRITE /* | GENERIC_READ */, 0, 0, OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL)); + HANDLE dir = HANDLES_Q(CreateFileUtf8(nameDelLink, GENERIC_WRITE | DELETE | FILE_READ_ATTRIBUTES, 0, 0, OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL)); if (dir != INVALID_HANDLE_VALUE) { DWORD dummy; char buf[MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; REPARSE_GUID_DATA_BUFFER* juncData = (REPARSE_GUID_DATA_BUFFER*)buf; - if (DeviceIoControl(dir, FSCTL_GET_REPARSE_POINT, NULL, 0, juncData, + if (!VerifyFileHandleIdentity(dir, expectedIdentity, err)) + { + ok = FALSE; + } + else if (DeviceIoControl(dir, FSCTL_GET_REPARSE_POINT, NULL, 0, juncData, MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &dummy, NULL) == 0) { if (err != NULL) @@ -115,8 +121,11 @@ BOOL DoDeleteDirLinkAux(const char* nameDelLink, DWORD* err) rgdb.ReparseTag = juncData->ReparseTag; DWORD dwBytes; + FILE_DISPOSITION_INFO disposition; + disposition.DeleteFile = TRUE; if (DeviceIoControl(dir, FSCTL_DELETE_REPARSE_POINT, &rgdb, REPARSE_GUID_DATA_BUFFER_HEADER_SIZE, - NULL, 0, &dwBytes, 0) != 0) + NULL, 0, &dwBytes, 0) != 0 && + SetFileInformationByHandle(dir, FileDispositionInfo, &disposition, sizeof(disposition))) { ok = TRUE; } @@ -136,16 +145,7 @@ BOOL DoDeleteDirLinkAux(const char* nameDelLink, DWORD* err) } } else - ok = TRUE; // the reparse point is apparently gone; all that remains is to delete the empty directory... - // remove the empty directory (that remained after deleting the reparse point) - if (ok) - ClearReadOnlyAttr(nameDelLink, attr); // ensure it can be deleted even with the read-only attribute - if (ok && !RemoveDirectoryUtf8(nameDelLink)) - { - ok = FALSE; - if (err != NULL) - *err = GetLastError(); - } + ok = TRUE; // the reparse point is already gone return ok; } @@ -157,12 +157,19 @@ BOOL DeleteDirLink(const char* name, DWORD* err) char nameDelLinkCopy[3 * MAX_PATH]; MakeCopyWithBackslashIfNeeded(nameDelLink, nameDelLinkCopy); - return DoDeleteDirLinkAux(nameDelLink, err); + COperation operation; + memset(&operation, 0, sizeof(operation)); + operation.Opcode = ocDeleteDirLink; + operation.SourceName = (char*)nameDelLink; + if (!CaptureOperationFileIdentities(&operation, err)) + return FALSE; + return DoDeleteDirLinkAux(nameDelLink, operation.SourceIdentity, err); } -BOOL DoDeleteDirLink(HWND hProgressDlg, char* name, const CQuadWord& size, COperations* script, +BOOL DoDeleteDirLink(HWND hProgressDlg, COperation* operation, const CQuadWord& size, COperations* script, CQuadWord& totalDone, CProgressDlgData& dlgData) { + char* name = operation->SourceName; // if the path ends with a space/dot, we must append '\\'; otherwise CreateFile // and RemoveDirectory trim the spaces/dots and operate on a different path const char* nameDelLink = name; @@ -172,7 +179,7 @@ BOOL DoDeleteDirLink(HWND hProgressDlg, char* name, const CQuadWord& size, COper while (1) { DWORD err; - BOOL ok = DoDeleteDirLinkAux(nameDelLink, &err); + BOOL ok = DoDeleteDirLinkAux(nameDelLink, operation->SourceIdentity, &err); if (ok) { @@ -921,15 +928,17 @@ BOOL DoChangeAttrs(HWND hProgressDlg, char* name, const CQuadWord& size, DWORD a unsigned ThreadWorkerBody(void* parameter) { CALL_STACK_MESSAGE1("ThreadWorkerBody()"); - SetThreadNameInVCAndTrace("Worker"); - TRACE_I("Begin"); - CWorkerData* data = (CWorkerData*)parameter; + char workerName[64]; + // Put the dispatch ID in the worker name so debugger and trace threads match the owning dialog. + _snprintf_s(workerName, _countof(workerName), _TRUNCATE, "Worker-%s", data->CorrelationId); + SetThreadNameInVCAndTrace(workerName); + TRACE_I("Begin operation=" << data->CorrelationId); //--- create a local copy of the data HANDLE wContinue = data->WContinue; CProgressDlgData dlgData; dlgData.WorkerNotSuspended = data->WorkerNotSuspended; - dlgData.CancelWorker = data->CancelWorker; + dlgData.CancelWorker.Bind(data->Script); dlgData.OperationProgress = data->OperationProgress; dlgData.SummaryProgress = data->SummaryProgress; dlgData.OverwriteAll = dlgData.OverwriteHiddenAll = dlgData.DeleteHiddenAll = @@ -947,9 +956,11 @@ unsigned ThreadWorkerBody(void* parameter) dlgData.IgnoreAllSetAttrsErr = dlgData.IgnoreAllCopyPermErr = dlgData.IgnoreAllCopyDirTimeErr = dlgData.SkipAllFileOutLossEncr = dlgData.FileOutLossEncrAll = dlgData.SkipAllDirCrLossEncr = - dlgData.DirCrLossEncrAll = dlgData.IgnoreAllGetFileTimeErr = - dlgData.IgnoreAllSetFileTimeErr = dlgData.SkipAllGetFileTime = - dlgData.SkipAllSetFileTime = FALSE; + dlgData.DirCrLossEncrAll = dlgData.IgnoreAllGetFileTimeErr = + dlgData.IgnoreAllSetFileTimeErr = dlgData.SkipAllGetFileTime = + dlgData.SkipAllSetFileTime = FALSE; + dlgData.MetadataLosses.Clear(); + dlgData.KeepSourceAfterMetadataLoss = FALSE; dlgData.CnfrmFileOver = Configuration.CnfrmFileOver; dlgData.CnfrmDirOver = Configuration.CnfrmDirOver; dlgData.CnfrmSHFileOver = Configuration.CnfrmSHFileOver; @@ -1024,13 +1035,29 @@ unsigned ThreadWorkerBody(void* parameter) for (i = 0; !*dlgData.CancelWorker && i < script->Count; i++) { COperation* op = &script->At(i); + int attempt = script->BeginItemAttempt(i); + + DWORD identityError; + if (!CaptureOperationFileIdentities(op, &identityError)) + { + TRACE_E("Unable to capture handle identity before file-operation item " << i << ": " << GetErrorText(identityError)); + Error = TRUE; + break; + } + + if (!script->JournalBeginItem(i, op, attempt)) + { + TRACE_E("Unable to persist file-operation journal transition before item " << i); + Error = TRUE; + break; + } switch (op->Opcode) { case ocCopyFile: { const char* opName = "copy file"; - ExecLogFileOperationStart(opName, op->SourceName, op->TargetName); + ExecLogFileOperationStart(script->GetCorrelationId(), i, attempt, opName, op->SourceName, op->TargetName); pd.Operation = opStrCopying; pd.Source = op->SourceName; pd.Preposition = opStrCopyingPrep; @@ -1046,8 +1073,8 @@ unsigned ThreadWorkerBody(void* parameter) allocWholeFileOnStart, dlgData, (op->OpFlags & OPFL_COPY_ADS) != 0, (op->OpFlags & OPFL_AS_ENCRYPTED) != 0, - FALSE, asyncPar); - ExecLogFileOperationResult(opName, op->SourceName, op->TargetName, !Error); + FALSE, asyncPar, NULL); + ExecLogFileOperationResult(script->GetCorrelationId(), i, script->GetCurrentItemAttempt(), opName, op->SourceName, op->TargetName, !Error); break; } @@ -1055,7 +1082,7 @@ unsigned ThreadWorkerBody(void* parameter) case ocMoveFile: { const char* opName = op->Opcode == ocMoveDir ? "move dir" : "move file"; - ExecLogFileOperationStart(opName, op->SourceName, op->TargetName); + ExecLogFileOperationStart(script->GetCorrelationId(), i, attempt, opName, op->SourceName, op->TargetName); pd.Operation = opStrMoving; pd.Source = op->SourceName; pd.Preposition = opStrMovingPrep; @@ -1074,14 +1101,14 @@ unsigned ThreadWorkerBody(void* parameter) (op->OpFlags & OPFL_COPY_ADS) != 0, (op->OpFlags & OPFL_AS_ENCRYPTED) != 0, &setDirTimeAfterMove, asyncPar, ignInvalidName); - ExecLogFileOperationResult(opName, op->SourceName, op->TargetName, !Error); + ExecLogFileOperationResult(script->GetCorrelationId(), i, script->GetCurrentItemAttempt(), opName, op->SourceName, op->TargetName, !Error); break; } case ocCreateDir: { const char* opName = "create dir"; - ExecLogFileOperationStart(opName, op->TargetName, ""); + ExecLogFileOperationStart(script->GetCorrelationId(), i, attempt, opName, op->TargetName, ""); BOOL copyADS = (op->OpFlags & OPFL_COPY_ADS) != 0; BOOL crAsEncrypted = (op->OpFlags & OPFL_AS_ENCRYPTED) != 0; BOOL ignInvalidName = (op->OpFlags & OPFL_IGNORE_INVALID_NAME) != 0; @@ -1097,7 +1124,7 @@ unsigned ThreadWorkerBody(void* parameter) Error = !DoCreateDir(hProgressDlg, op->TargetName, op->Attr, clearReadonlyMask, dlgData, totalDone, op->Size, op->SourceName, copyADS, script, buffer, skip, alreadyExisted, crAsEncrypted, ignInvalidName); - ExecLogFileOperationResult(opName, op->TargetName, "", !Error); + ExecLogFileOperationResult(script->GetCorrelationId(), i, script->GetCurrentItemAttempt(), opName, op->TargetName, "", !Error); if (!Error) { if (skip) // skip directory creation @@ -1205,7 +1232,7 @@ unsigned ThreadWorkerBody(void* parameter) case ocDeleteDirLink: { const char* opName = op->Opcode == ocDeleteFile ? "delete file" : (op->Opcode == ocDeleteDir ? "delete dir" : "delete dir link"); - ExecLogFileOperationStart(opName, op->SourceName, ""); + ExecLogFileOperationStart(script->GetCorrelationId(), i, attempt, opName, op->SourceName, ""); pd.Operation = opStrDeleting; pd.Source = op->SourceName; pd.Preposition = ""; @@ -1214,33 +1241,45 @@ unsigned ThreadWorkerBody(void* parameter) SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); + if (script->IsCopyOrMoveOperation && !script->IsCopyOperation) + { + RecordPlannedMetadataLosses(dlgData, script, op->SourceName, NULL); + if (!ConfirmMetadataLossesBeforeSourceDeletion(hProgressDlg, dlgData, op->SourceName, NULL)) + { + totalDone += op->Size; + script->SetProgressSize(totalDone); + SetProgress(hProgressDlg, 0, CaclProg(totalDone, script->TotalSize), dlgData); + break; // the user chose to retain this move source + } + } + if (op->Opcode == ocDeleteFile) { - Error = !DoDeleteFile(hProgressDlg, op->SourceName, op->Size, + Error = !DoDeleteFile(hProgressDlg, op, op->Size, script, totalDone, op->Attr, dlgData); } else { if (op->Opcode == ocDeleteDir) { - Error = !DoDeleteDir(hProgressDlg, op->SourceName, op->Size, - script, totalDone, op->Attr, (DWORD)(DWORD_PTR)op->TargetName != -1, + Error = !DoDeleteDir(hProgressDlg, op, op->Size, + script, totalDone, op->Attr, (DWORD)(DWORD_PTR)op->TargetName != -1, dlgData); } else { - Error = !DoDeleteDirLink(hProgressDlg, op->SourceName, op->Size, - script, totalDone, dlgData); + Error = !DoDeleteDirLink(hProgressDlg, op, op->Size, + script, totalDone, dlgData); } } - ExecLogFileOperationResult(opName, op->SourceName, "", !Error); + ExecLogFileOperationResult(script->GetCorrelationId(), i, script->GetCurrentItemAttempt(), opName, op->SourceName, "", !Error); break; } case ocConvert: { const char* opName = "convert file"; - ExecLogFileOperationStart(opName, op->SourceName, ""); + ExecLogFileOperationStart(script->GetCorrelationId(), i, attempt, opName, op->SourceName, ""); // output buffer - the conversion will be performed in it (in the worst case, // when the input file contains only CR or LF and we translate them to CRLF, // this buffer is twice the size of sourceBuffer) and afterwards we will write from it @@ -1265,14 +1304,14 @@ unsigned ThreadWorkerBody(void* parameter) Error = !DoConvert(hProgressDlg, op->SourceName, (char*)buffer, tgtBuffer, op->Size, script, totalDone, convertData, dlgData); - ExecLogFileOperationResult(opName, op->SourceName, "", !Error); + ExecLogFileOperationResult(script->GetCorrelationId(), i, script->GetCurrentItemAttempt(), opName, op->SourceName, "", !Error); break; } case ocChangeAttrs: { const char* opName = "change attrs"; - ExecLogFileOperationStart(opName, op->SourceName, ""); + ExecLogFileOperationStart(script->GetCorrelationId(), i, attempt, opName, op->SourceName, ""); pd.Operation = opChangAttrs; pd.Source = op->SourceName; pd.Preposition = ""; @@ -1288,13 +1327,14 @@ unsigned ThreadWorkerBody(void* parameter) attrsData->ChangeTimeAccessed ? &attrsData->TimeAccessed : NULL, attrsData->ChangeCompression, attrsData->ChangeEncryption, op->Attr, dlgData); - ExecLogFileOperationResult(opName, op->SourceName, "", !Error); + ExecLogFileOperationResult(script->GetCorrelationId(), i, script->GetCurrentItemAttempt(), opName, op->SourceName, "", !Error); break; } case ocLabelForSkipOfCreateDir: break; // no action } + script->JournalCompleteItem(!Error); if (Error) break; WaitForSingleObject(dlgData.WorkerNotSuspended, INFINITE); // if we should be in suspend mode, wait ... @@ -1328,11 +1368,27 @@ unsigned ThreadWorkerBody(void* parameter) free(tgtBuffer); if (bufferIsAllocated) free(buffer); - *dlgData.CancelWorker = Error; // if this was triggered by Cancel, make that obvious ... - SendMessage(hProgressDlg, WM_COMMAND, IDOK, 0); // we're done ... - WaitForSingleObject(wContinue, INFINITE); // we need to stop the main thread - - FreeScript(script); // calls delete, so the main thread cannot be running + script->FinishJournal(Error, script->IsCancellationRequested()); + script->BeginStopping(); + script->Complete(Error); + EOperationState finalState = script->GetOperationState(); + BOOL cancellationRequested = script->IsCancellationRequested(); + + // The progress dialog must never be part of worker shutdown. In + // particular, it can be blocked in an owned modal dialog or waiting for + // this thread while a close/shutdown cancellation is in progress. Release + // all worker-owned data first, then transfer the small, self-contained + // result to the UI by a posted message. + CWorkerCompletion* completion = new CWorkerCompletion(finalState, cancellationRequested, script->GetCorrelationId()); + FreeScript(script); + if (!PostMessage(hProgressDlg, WM_USER_PROGRDLG_WORKERCOMPLETE, + (WPARAM)Error, (LPARAM)completion)) + { + // PostMessage can fail only when the progress window has already gone + // away. The worker is still fully cleaned up, and remains responsible + // for the result which was not transferred to the UI. + delete completion; + } TRACE_I("End"); return 0; @@ -1365,16 +1421,23 @@ DWORD WINAPI ThreadWorker(void* param) HANDLE StartWorker(COperations* script, HWND hDlg, CChangeAttrsData* attrsData, CConvertData* convertData, HANDLE wContinue, HANDLE workerNotSuspended, - BOOL* cancelWorker, int* operationProgress, int* summaryProgress) + int* operationProgress, int* summaryProgress) { + // Do not allocate worker buffers or start a destructive script after the + // allocator has declared the process unsafe for additional work. + if (IsAllocationEmergencyActive()) + { + script->Fail(); + return NULL; + } CWorkerData data; data.WorkerNotSuspended = workerNotSuspended; - data.CancelWorker = cancelWorker; data.OperationProgress = operationProgress; data.SummaryProgress = summaryProgress; data.WContinue = wContinue; data.ConvertData = convertData; data.Script = script; + lstrcpyn(data.CorrelationId, script->GetCorrelationId(), _countof(data.CorrelationId)); data.HProgressDlg = hDlg; data.ClearReadonlyMask = script->ClearReadonlyMask; if (attrsData != NULL) @@ -1389,12 +1452,24 @@ HANDLE StartWorker(COperations* script, HWND hDlg, CChangeAttrsData* attrsData, if (data.Buffer == NULL) { TRACE_E(LOW_MEMORY); + script->Fail(); return NULL; } } DWORD threadID; ResetEvent(wContinue); - *cancelWorker = FALSE; + if (!script->BeginJournal()) + { + if (data.BufferIsAllocated) + free(data.Buffer); + script->Fail(); + return NULL; + } + if (!script->Start()) + { + script->FinishJournal(TRUE, FALSE); + return NULL; + } // if (Worker != NULL) HANDLES(CloseHandle(Worker)); // was probably unnecessary HANDLE worker = HANDLES(CreateThread(NULL, 0, ThreadWorker, &data, 0, &threadID)); @@ -1403,10 +1478,14 @@ HANDLE StartWorker(COperations* script, HWND hDlg, CChangeAttrsData* attrsData, if (data.BufferIsAllocated) free(data.Buffer); TRACE_E("Unable to start Worker thread."); + script->FinishJournal(TRUE, FALSE); + script->Fail(); return NULL; } // SetThreadPriority(Worker, THREAD_PRIORITY_HIGHEST); - WaitForSingleObject(wContinue, INFINITE); // wait until it copies the data (they are on the stack) + DWORD copiedStartupData = WaitForSingleObject(wContinue, INFINITE); // wait until it copies the data (they are on the stack) + // This wait is intentionally label-only; the ring must not retain script data from the stack. + RecordReleaseDiagnosticWait("worker_startup", copiedStartupData); return worker; } @@ -1436,6 +1515,11 @@ BOOL COperationsQueue::AddOperation(HWND dlg, BOOL startOnIdle, BOOL* startPause { CALL_STACK_MESSAGE1("COperationsQueue::AddOperation()"); + // Queued work may allocate or outlive the recovery marker, so reject it + // once OOM has requested the controlled shutdown path. + if (IsAllocationEmergencyActive()) + return FALSE; + HANDLES(EnterCriticalSection(&QueueCritSect)); int i; diff --git a/src/parserbroker.cpp b/src/parserbroker.cpp new file mode 100644 index 00000000..33d64442 --- /dev/null +++ b/src/parserbroker.cpp @@ -0,0 +1,375 @@ +// SPDX-FileCopyrightText: 2026 Taskscape Ltd +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "precomp.h" + +#include "common\\checked_arithmetic.h" +#include "common\\scoped_native_resources.h" +#include "parserbroker.h" + +// A broker owns one pipe response at a time; retaining more callers only turns +// a slow untrusted parser into unbounded host-thread pressure. +#define PARSER_BROKER_MAX_PENDING_REQUESTS 8 +#include "thumbnl.h" + +CParserBrokerClient ParserBroker; + +static const DWORD BrokerConnectTimeout = 5000; +static const DWORD BrokerRequestTimeout = 10000; + +static BOOL BrokerOverlappedIo(HANDLE pipe, void* buffer, DWORD length, BOOL write, DWORD timeout) +{ + BYTE* current = (BYTE*)buffer; + DWORD remaining = length; + while (remaining != 0) + { + OVERLAPPED overlapped; + ZeroMemory(&overlapped, sizeof(overlapped)); + overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (overlapped.hEvent == NULL) + return FALSE; + + DWORD transferred = 0; + BOOL completed = write ? WriteFile(pipe, current, remaining, &transferred, &overlapped) + : ReadFile(pipe, current, remaining, &transferred, &overlapped); + if (!completed && GetLastError() == ERROR_IO_PENDING) + { + if (WaitForSingleObject(overlapped.hEvent, timeout) == WAIT_OBJECT_0) + completed = GetOverlappedResult(pipe, &overlapped, &transferred, FALSE); + else + { + CancelIoEx(pipe, &overlapped); + completed = FALSE; + SetLastError(WAIT_TIMEOUT); + } + } + CloseHandle(overlapped.hEvent); + if (!completed || transferred == 0) + return FALSE; + current += transferred; + remaining -= transferred; + } + return TRUE; +} + +static BOOL BrokerConnectPipe(HANDLE pipe) +{ + OVERLAPPED overlapped; + ZeroMemory(&overlapped, sizeof(overlapped)); + overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (overlapped.hEvent == NULL) + return FALSE; + + BOOL connected = ConnectNamedPipe(pipe, &overlapped); + if (!connected) + { + DWORD error = GetLastError(); + if (error == ERROR_PIPE_CONNECTED) + connected = TRUE; + else if (error == ERROR_IO_PENDING && WaitForSingleObject(overlapped.hEvent, BrokerConnectTimeout) == WAIT_OBJECT_0) + { + DWORD ignored; + connected = GetOverlappedResult(pipe, &overlapped, &ignored, FALSE); + } + else + CancelIoEx(pipe, &overlapped); + } + CloseHandle(overlapped.hEvent); + return connected; +} + +CParserBrokerClient::CParserBrokerClient() +{ + Pipe = NULL; + Process = NULL; + Job = NULL; + InitializeCriticalSection(&Lock); + NextCorrelationId = 1; + PipeName[0] = 0; + PendingRequests = 0; + AcceptedRequests = 0; + RejectedRequests = 0; + HighWaterMark = 0; +} + +CParserBrokerClient::~CParserBrokerClient() +{ + Stop(); + DeleteCriticalSection(&Lock); +} + +void CParserBrokerClient::Stop() +{ + if (Job != NULL) + { + TerminateJobObject(Job, ERROR_PROCESS_ABORTED); + CloseHandle(Job); + Job = NULL; + } + if (Process != NULL) + { + CloseHandle(Process); + Process = NULL; + } + if (Pipe != NULL) + { + DisconnectNamedPipe(Pipe); + CloseHandle(Pipe); + Pipe = NULL; + } +} + +BOOL CParserBrokerClient::Start() +{ + if (Pipe != NULL && Process != NULL && WaitForSingleObject(Process, 0) == WAIT_TIMEOUT) + return TRUE; + Stop(); + + wsprintfW(PipeName, L"\\\\.\\pipe\\OpenSal.ParserBroker.%08X", GetCurrentProcessId()); + Pipe = CreateNamedPipeW(PipeName, + PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS, + 1, PARSER_BROKER_MAX_PAYLOAD, PARSER_BROKER_MAX_PAYLOAD, 0, NULL); + if (Pipe == INVALID_HANDLE_VALUE) + { + Pipe = NULL; + return FALSE; + } + + WCHAR executable[MAX_PATH]; + DWORD executableLength = GetModuleFileNameW(NULL, executable, _countof(executable)); + if (executableLength == 0 || executableLength >= _countof(executable)) + { + Stop(); + return FALSE; + } + WCHAR* filename = wcsrchr(executable, L'\\'); + if (filename == NULL) + { + Stop(); + return FALSE; + } + lstrcpyW(filename + 1, L"salbroker.exe"); + + HANDLE currentToken = NULL; + HANDLE restrictedToken = NULL; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY | TOKEN_QUERY, ¤tToken) || + !CreateRestrictedToken(currentToken, DISABLE_MAX_PRIVILEGE, 0, NULL, 0, NULL, 0, NULL, &restrictedToken)) + { + if (currentToken != NULL) + CloseHandle(currentToken); + Stop(); + return FALSE; + } + CloseHandle(currentToken); + + WCHAR commandLine[2 * MAX_PATH + 32]; + wsprintfW(commandLine, L"\"%s\" --pipe \"%s\"", executable, PipeName); + STARTUPINFOW startup; + PROCESS_INFORMATION processInfo; + ZeroMemory(&startup, sizeof(startup)); + ZeroMemory(&processInfo, sizeof(processInfo)); + startup.cb = sizeof(startup); + BOOL created = CreateProcessAsUserW(restrictedToken, executable, commandLine, NULL, NULL, FALSE, + CREATE_SUSPENDED | CREATE_NO_WINDOW, NULL, NULL, &startup, &processInfo); + CloseHandle(restrictedToken); + if (!created) + { + Stop(); + return FALSE; + } + + Job = CreateJobObjectW(NULL, NULL); + JOBOBJECT_EXTENDED_LIMIT_INFORMATION limits; + ZeroMemory(&limits, sizeof(limits)); + limits.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | + JOB_OBJECT_LIMIT_PROCESS_MEMORY | + JOB_OBJECT_LIMIT_PROCESS_TIME; + limits.BasicLimitInformation.PerProcessUserTimeLimit.QuadPart = 30LL * 10000000LL; + limits.ProcessMemoryLimit = 256 * 1024 * 1024; + if (Job == NULL || !SetInformationJobObject(Job, JobObjectExtendedLimitInformation, &limits, sizeof(limits)) || + !AssignProcessToJobObject(Job, processInfo.hProcess)) + { + TerminateProcess(processInfo.hProcess, ERROR_ACCESS_DENIED); + CloseHandle(processInfo.hThread); + CloseHandle(processInfo.hProcess); + Stop(); + return FALSE; + } + Process = processInfo.hProcess; + ResumeThread(processInfo.hThread); + CloseHandle(processInfo.hThread); + + if (!BrokerConnectPipe(Pipe)) + { + Stop(); + return FALSE; + } + return TRUE; +} + +BOOL CParserBrokerClient::Invoke(WORD type, const void* request, DWORD requestLength, + WORD responseType, void* response, DWORD responseCapacity, + DWORD* responseLength) +{ + LONG pending = InterlockedIncrement(&PendingRequests); + if (pending > PARSER_BROKER_MAX_PENDING_REQUESTS) + { + InterlockedDecrement(&PendingRequests); + InterlockedIncrement64(&RejectedRequests); + TRACE_I("CParserBrokerClient::Invoke(): bounded broker queue rejected request type " << type); + return FALSE; + } + InterlockedIncrement64(&AcceptedRequests); + for (LONGLONG observed = InterlockedCompareExchange64(&HighWaterMark, 0, 0); + pending > observed && InterlockedCompareExchange64(&HighWaterMark, pending, observed) != observed; + observed = InterlockedCompareExchange64(&HighWaterMark, 0, 0)) + { + } + + // A pipe response is correlated with one request, so serialize access from + // the icon workers and archive navigation threads at the documented broker rank. + CScopedCriticalSection lock(&Lock, lkrExternalBroker, "ParserBroker.Lock"); + BOOL completed = FALSE; + for (int attempt = 0; attempt != 2; ++attempt) + { + if (Start() && InvokeOnce(type, request, requestLength, responseType, response, responseCapacity, responseLength)) + { + completed = TRUE; + break; + } + Stop(); // timeout, malformed response, or crash: kill and recreate the untrusted process. + } + InterlockedDecrement(&PendingRequests); + return completed; +} + +CParserBrokerQueueMetrics CParserBrokerClient::GetQueueMetrics() +{ + CParserBrokerQueueMetrics metrics; + metrics.Capacity = PARSER_BROKER_MAX_PENDING_REQUESTS; + metrics.Pending = InterlockedCompareExchange(&PendingRequests, 0, 0); + metrics.Accepted = InterlockedCompareExchange64(&AcceptedRequests, 0, 0); + metrics.Rejected = InterlockedCompareExchange64(&RejectedRequests, 0, 0); + metrics.HighWaterMark = InterlockedCompareExchange64(&HighWaterMark, 0, 0); + return metrics; +} + +BOOL CParserBrokerClient::InvokeOnce(WORD type, const void* request, DWORD requestLength, + WORD responseType, void* response, DWORD responseCapacity, + DWORD* responseLength) +{ + if (requestLength > PARSER_BROKER_MAX_PAYLOAD) + return FALSE; + CParserBrokerMessageHeader requestHeader; + requestHeader.Magic = PARSER_BROKER_MAGIC; + requestHeader.Version = PARSER_BROKER_VERSION; + requestHeader.Type = type; + requestHeader.PayloadLength = requestLength; + requestHeader.CorrelationId = NextCorrelationId++; + requestHeader.Status = pbsOk; + if (requestHeader.CorrelationId == 0) + requestHeader.CorrelationId = NextCorrelationId++; + + if (!BrokerOverlappedIo(Pipe, &requestHeader, sizeof(requestHeader), TRUE, BrokerRequestTimeout) || + (requestLength != 0 && !BrokerOverlappedIo(Pipe, (void*)request, requestLength, TRUE, BrokerRequestTimeout))) + return FALSE; + + CParserBrokerMessageHeader responseHeader; + if (!BrokerOverlappedIo(Pipe, &responseHeader, sizeof(responseHeader), FALSE, BrokerRequestTimeout) || + responseHeader.Magic != PARSER_BROKER_MAGIC || responseHeader.Version != PARSER_BROKER_VERSION || + responseHeader.Type != responseType || responseHeader.CorrelationId != requestHeader.CorrelationId || + responseHeader.Status != pbsOk || responseHeader.PayloadLength > responseCapacity || + responseHeader.PayloadLength > PARSER_BROKER_MAX_PAYLOAD) + return FALSE; + if (responseHeader.PayloadLength != 0 && + !BrokerOverlappedIo(Pipe, response, responseHeader.PayloadLength, FALSE, BrokerRequestTimeout)) + return FALSE; + *responseLength = responseHeader.PayloadLength; + return TRUE; +} + +static BOOL BrokerMakePathPayload(const char* path, const void* prefix, DWORD prefixLength, + BYTE* payload, DWORD payloadCapacity, DWORD* payloadLength) +{ + int chars = MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS, path, -1, NULL, 0); + if (chars <= 1) + return FALSE; + uint64_t pathBytes64; + DWORD pathBytes; + DWORD totalPayloadLength; + // The conversion result describes external path storage; reject an + // impossible multiply or packed-message length before writing the buffer. + if (!CheckedMultiplyUInt64((uint64_t)(chars - 1), (uint64_t)sizeof(WCHAR), &pathBytes64) || + !CheckedCastUInt64ToDword(pathBytes64, &pathBytes) || + !CheckedAddDword(prefixLength, pathBytes, &totalPayloadLength) || + pathBytes > PARSER_BROKER_MAX_PATH_BYTES || totalPayloadLength > payloadCapacity) + return FALSE; + memcpy(payload, prefix, prefixLength); + if (MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS, path, -1, (WCHAR*)(payload + prefixLength), chars) != chars) + return FALSE; + *payloadLength = totalPayloadLength; + return TRUE; +} + +BOOL CParserBrokerClient::LoadThumbnail(const char* path, int width, int height, BOOL fastThumbnail, + CSalamanderThumbnailMakerAbstract* maker) +{ + if (path == NULL || maker == NULL || width <= 0 || height <= 0 || + width > (int)PARSER_BROKER_MAX_THUMBNAIL_DIMENSION || height > (int)PARSER_BROKER_MAX_THUMBNAIL_DIMENSION) + return FALSE; + BYTE request[PARSER_BROKER_MAX_PATH_BYTES + sizeof(CParserBrokerThumbnailRequest)]; + CParserBrokerThumbnailRequest prefix; + prefix.Width = width; + prefix.Height = height; + prefix.Flags = fastThumbnail ? 1 : 0; + prefix.PathBytes = 0; + DWORD requestLength; + if (!BrokerMakePathPayload(path, &prefix, sizeof(prefix), request, sizeof(request), &requestLength)) + return FALSE; + ((CParserBrokerThumbnailRequest*)request)->PathBytes = requestLength - sizeof(prefix); + + BYTE response[PARSER_BROKER_MAX_PAYLOAD]; + DWORD responseLength; + if (!Invoke(pbmtThumbnailRequest, request, requestLength, pbmtThumbnailResponse, + response, sizeof(response), &responseLength) || responseLength < sizeof(CParserBrokerThumbnailResponse)) + return FALSE; + const CParserBrokerThumbnailResponse* thumbnail = (const CParserBrokerThumbnailResponse*)response; + uint64_t expectedPixelBytes; + DWORD expectedResponseLength; + // Width, height, and PixelBytes arrive over IPC. Calculate both the + // pixel allocation and enclosing response length without wraparound. + if (!CheckedMultiplyUInt64(thumbnail->Width, thumbnail->Height, &expectedPixelBytes) || + !CheckedMultiplyUInt64(expectedPixelBytes, (uint64_t)sizeof(DWORD), &expectedPixelBytes) || + !CheckedAddDword((DWORD)sizeof(*thumbnail), thumbnail->PixelBytes, &expectedResponseLength)) + return FALSE; + if (thumbnail->Width == 0 || thumbnail->Height == 0 || thumbnail->Width > PARSER_BROKER_MAX_THUMBNAIL_DIMENSION || + thumbnail->Height > PARSER_BROKER_MAX_THUMBNAIL_DIMENSION || thumbnail->PixelBytes != expectedPixelBytes || + responseLength != expectedResponseLength || + !maker->SetParameters((int)thumbnail->Width, (int)thumbnail->Height, 0)) + return FALSE; + return maker->ProcessBuffer(response + sizeof(*thumbnail), (int)thumbnail->Height); +} + +BOOL CParserBrokerClient::QueryArchiveMetadata(const char* path, CParserBrokerArchiveMetadata* metadata) +{ + if (path == NULL || metadata == NULL) + return FALSE; + BYTE request[PARSER_BROKER_MAX_PATH_BYTES + sizeof(CParserBrokerArchiveMetadataRequest)]; + CParserBrokerArchiveMetadataRequest prefix; + prefix.PathBytes = 0; + DWORD requestLength; + if (!BrokerMakePathPayload(path, &prefix, sizeof(prefix), request, sizeof(request), &requestLength)) + return FALSE; + ((CParserBrokerArchiveMetadataRequest*)request)->PathBytes = requestLength - sizeof(prefix); + + CParserBrokerArchiveMetadataResponse response; + DWORD responseLength; + if (!Invoke(pbmtArchiveMetadataRequest, request, requestLength, pbmtArchiveMetadataResponse, + &response, sizeof(response), &responseLength) || responseLength != sizeof(response)) + return FALSE; + metadata->FileSize = response.FileSize; + metadata->LastWriteTime = response.LastWriteTime; + metadata->ItemCount = response.ItemCount; + return TRUE; +} diff --git a/src/parserbroker.h b/src/parserbroker.h new file mode 100644 index 00000000..926939bb --- /dev/null +++ b/src/parserbroker.h @@ -0,0 +1,63 @@ +// SPDX-FileCopyrightText: 2026 Taskscape Ltd +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "parserbroker_protocol.h" + +class CSalamanderThumbnailMakerAbstract; + +struct CParserBrokerArchiveMetadata +{ + ULONGLONG FileSize; + FILETIME LastWriteTime; + DWORD ItemCount; +}; + +// Broker admission stays bounded even when slow thumbnail or metadata callers +// arrive from several panel and navigation workers at once. +struct CParserBrokerQueueMetrics +{ + LONG Capacity; + LONG Pending; + LONGLONG Accepted; + LONGLONG Rejected; + LONGLONG HighWaterMark; +}; + +class CParserBrokerClient +{ +private: + HANDLE Pipe; + HANDLE Process; + HANDLE Job; + CRITICAL_SECTION Lock; + DWORD NextCorrelationId; + WCHAR PipeName[MAX_PATH]; + volatile LONG PendingRequests; + volatile LONGLONG AcceptedRequests; + volatile LONGLONG RejectedRequests; + volatile LONGLONG HighWaterMark; + + BOOL Start(); + void Stop(); + BOOL Invoke(WORD type, const void* request, DWORD requestLength, + WORD responseType, void* response, DWORD responseCapacity, + DWORD* responseLength); + BOOL InvokeOnce(WORD type, const void* request, DWORD requestLength, + WORD responseType, void* response, DWORD responseCapacity, + DWORD* responseLength); + +public: + CParserBrokerClient(); + ~CParserBrokerClient(); + + BOOL LoadThumbnail(const char* path, int width, int height, BOOL fastThumbnail, + CSalamanderThumbnailMakerAbstract* maker); + BOOL QueryArchiveMetadata(const char* path, CParserBrokerArchiveMetadata* metadata); + + // Exposes broker backpressure without retaining callers behind the serialized pipe. + CParserBrokerQueueMetrics GetQueueMetrics(); +}; + +extern CParserBrokerClient ParserBroker; diff --git a/src/parserbroker/salbroker.cpp b/src/parserbroker/salbroker.cpp new file mode 100644 index 00000000..2fc40681 --- /dev/null +++ b/src/parserbroker/salbroker.cpp @@ -0,0 +1,228 @@ +// SPDX-FileCopyrightText: 2026 Taskscape Ltd +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include + +#include "../common/checked_arithmetic.h" +#include "../parserbroker_protocol.h" + +static BOOL ReadExact(HANDLE pipe, void* buffer, DWORD length) +{ + BYTE* current = (BYTE*)buffer; + while (length != 0) + { + DWORD received; + if (!ReadFile(pipe, current, length, &received, NULL) || received == 0) + return FALSE; + current += received; + length -= received; + } + return TRUE; +} + +static BOOL WriteExact(HANDLE pipe, const void* buffer, DWORD length) +{ + const BYTE* current = (const BYTE*)buffer; + while (length != 0) + { + DWORD sent; + if (!WriteFile(pipe, current, length, &sent, NULL) || sent == 0) + return FALSE; + current += sent; + length -= sent; + } + return TRUE; +} + +static BOOL IsValidPathPayload(const BYTE* payload, DWORD payloadLength, DWORD pathOffset, DWORD pathBytes, const WCHAR** path) +{ + if (pathBytes == 0 || pathBytes > PARSER_BROKER_MAX_PATH_BYTES || (pathBytes & 1) != 0 || + pathOffset > payloadLength || pathBytes > payloadLength - pathOffset) + return FALSE; + // The client sends no terminator over the wire. Make a private copy before using shell APIs. + *path = (const WCHAR*)(payload + pathOffset); + return TRUE; +} + +static BOOL CopyPath(const WCHAR* source, DWORD bytes, WCHAR* target, DWORD capacity) +{ + if (bytes / sizeof(WCHAR) >= capacity) + return FALSE; + memcpy(target, source, bytes); + target[bytes / sizeof(WCHAR)] = 0; + return TRUE; +} + +static EParserBrokerStatus MakeThumbnail(const BYTE* request, DWORD requestLength, BYTE* response, DWORD responseCapacity, DWORD* responseLength) +{ + if (requestLength < sizeof(CParserBrokerThumbnailRequest)) + return pbsInvalidRequest; + const CParserBrokerThumbnailRequest* thumbnailRequest = (const CParserBrokerThumbnailRequest*)request; + const WCHAR* pathPayload; + if (thumbnailRequest->Width == 0 || thumbnailRequest->Height == 0 || + thumbnailRequest->Width > PARSER_BROKER_MAX_THUMBNAIL_DIMENSION || + thumbnailRequest->Height > PARSER_BROKER_MAX_THUMBNAIL_DIMENSION || + !IsValidPathPayload(request, requestLength, sizeof(*thumbnailRequest), thumbnailRequest->PathBytes, &pathPayload)) + return pbsInvalidRequest; + WCHAR path[MAX_PATH]; + if (!CopyPath(pathPayload, thumbnailRequest->PathBytes, path, _countof(path))) + return pbsInvalidRequest; + + IShellItem* item = NULL; + HRESULT hr = SHCreateItemFromParsingName(path, NULL, IID_PPV_ARGS(&item)); + if (FAILED(hr)) + return pbsFailed; + IShellItemImageFactory* imageFactory = NULL; + hr = item->QueryInterface(IID_PPV_ARGS(&imageFactory)); + item->Release(); + if (FAILED(hr)) + return pbsUnsupported; + + SIZE size = { (LONG)thumbnailRequest->Width, (LONG)thumbnailRequest->Height }; + HBITMAP bitmap = NULL; + hr = imageFactory->GetImage(size, SIIGBF_RESIZETOFIT | SIIGBF_BIGGERSIZEOK, &bitmap); + imageFactory->Release(); + if (FAILED(hr) || bitmap == NULL) + return pbsFailed; + + BITMAP bitmapInfo; + if (GetObject(bitmap, sizeof(bitmapInfo), &bitmapInfo) != sizeof(bitmapInfo) || bitmapInfo.bmWidth <= 0 || bitmapInfo.bmHeight <= 0 || + bitmapInfo.bmWidth > (LONG)PARSER_BROKER_MAX_THUMBNAIL_DIMENSION || bitmapInfo.bmHeight > (LONG)PARSER_BROKER_MAX_THUMBNAIL_DIMENSION) + { + DeleteObject(bitmap); + return pbsFailed; + } + DWORD pixels; + DWORD pixelBytes; + DWORD packedResponseLength; + // Shell-returned dimensions still cross an isolation boundary. Guard the + // packed response arithmetic before its size controls the output buffer. + if (!CheckedMultiplyDword((DWORD)bitmapInfo.bmWidth, (DWORD)bitmapInfo.bmHeight, &pixels) || + !CheckedMultiplyDword(pixels, (DWORD)sizeof(DWORD), &pixelBytes) || + !CheckedAddDword((DWORD)sizeof(CParserBrokerThumbnailResponse), pixelBytes, &packedResponseLength) || + packedResponseLength > responseCapacity) + { + DeleteObject(bitmap); + return pbsResourceLimit; + } + BITMAPINFO info; + ZeroMemory(&info, sizeof(info)); + info.bmiHeader.biSize = sizeof(info.bmiHeader); + info.bmiHeader.biWidth = bitmapInfo.bmWidth; + info.bmiHeader.biHeight = -bitmapInfo.bmHeight; + info.bmiHeader.biPlanes = 1; + info.bmiHeader.biBitCount = 32; + info.bmiHeader.biCompression = BI_RGB; + HDC dc = GetDC(NULL); + int lines = GetDIBits(dc, bitmap, 0, bitmapInfo.bmHeight, + response + sizeof(CParserBrokerThumbnailResponse), &info, DIB_RGB_COLORS); + ReleaseDC(NULL, dc); + DeleteObject(bitmap); + if (lines != bitmapInfo.bmHeight) + return pbsFailed; + + CParserBrokerThumbnailResponse* thumbnailResponse = (CParserBrokerThumbnailResponse*)response; + thumbnailResponse->Width = bitmapInfo.bmWidth; + thumbnailResponse->Height = bitmapInfo.bmHeight; + thumbnailResponse->PixelBytes = pixelBytes; + *responseLength = packedResponseLength; + return pbsOk; +} + +static EParserBrokerStatus GetArchiveMetadata(const BYTE* request, DWORD requestLength, BYTE* response, DWORD responseCapacity, DWORD* responseLength) +{ + if (requestLength < sizeof(CParserBrokerArchiveMetadataRequest) || responseCapacity < sizeof(CParserBrokerArchiveMetadataResponse)) + return pbsInvalidRequest; + const CParserBrokerArchiveMetadataRequest* metadataRequest = (const CParserBrokerArchiveMetadataRequest*)request; + const WCHAR* pathPayload; + if (!IsValidPathPayload(request, requestLength, sizeof(*metadataRequest), metadataRequest->PathBytes, &pathPayload)) + return pbsInvalidRequest; + WCHAR path[MAX_PATH]; + if (!CopyPath(pathPayload, metadataRequest->PathBytes, path, _countof(path))) + return pbsInvalidRequest; + + WIN32_FILE_ATTRIBUTE_DATA attributes; + if (!GetFileAttributesExW(path, GetFileExInfoStandard, &attributes)) + return pbsFailed; + CParserBrokerArchiveMetadataResponse* metadata = (CParserBrokerArchiveMetadataResponse*)response; + metadata->FileSize = ((ULONGLONG)attributes.nFileSizeHigh << 32) | attributes.nFileSizeLow; + metadata->LastWriteTime = attributes.ftLastWriteTime; + metadata->ItemCount = 0; // Format-specific listing remains deliberately outside this v1 metadata contract. + *responseLength = sizeof(*metadata); + return pbsOk; +} + +static BOOL Serve(HANDLE pipe) +{ + for (;;) + { + CParserBrokerMessageHeader requestHeader; + if (!ReadExact(pipe, &requestHeader, sizeof(requestHeader))) + return FALSE; + if (requestHeader.Magic != PARSER_BROKER_MAGIC || requestHeader.Version != PARSER_BROKER_VERSION || + requestHeader.PayloadLength > PARSER_BROKER_MAX_PAYLOAD) + return FALSE; + BYTE request[PARSER_BROKER_MAX_PAYLOAD]; + if (requestHeader.PayloadLength != 0 && !ReadExact(pipe, request, requestHeader.PayloadLength)) + return FALSE; + + BYTE response[PARSER_BROKER_MAX_PAYLOAD]; + DWORD responseLength = 0; + WORD responseType = 0; + EParserBrokerStatus status = pbsUnsupported; + if (requestHeader.Type == pbmtThumbnailRequest) + { + responseType = pbmtThumbnailResponse; + status = MakeThumbnail(request, requestHeader.PayloadLength, response, sizeof(response), &responseLength); + } + else if (requestHeader.Type == pbmtArchiveMetadataRequest) + { + responseType = pbmtArchiveMetadataResponse; + status = GetArchiveMetadata(request, requestHeader.PayloadLength, response, sizeof(response), &responseLength); + } + + CParserBrokerMessageHeader responseHeader; + responseHeader.Magic = PARSER_BROKER_MAGIC; + responseHeader.Version = PARSER_BROKER_VERSION; + responseHeader.Type = responseType; + responseHeader.PayloadLength = status == pbsOk ? responseLength : 0; + responseHeader.CorrelationId = requestHeader.CorrelationId; + responseHeader.Status = status; + if (!WriteExact(pipe, &responseHeader, sizeof(responseHeader)) || + (responseHeader.PayloadLength != 0 && !WriteExact(pipe, response, responseHeader.PayloadLength))) + return FALSE; + } +} + +int WINAPI wWinMain(HINSTANCE, HINSTANCE, PWSTR commandLine, int) +{ + const WCHAR* marker = wcsstr(commandLine, L"--pipe"); + if (marker == NULL) + return ERROR_INVALID_PARAMETER; + marker += 6; + while (*marker == L' ') + ++marker; + if (*marker == L'\"') + ++marker; + WCHAR pipeName[MAX_PATH]; + int length = 0; + while (marker[length] != 0 && marker[length] != L'\"' && length + 1 < _countof(pipeName)) + { + pipeName[length] = marker[length]; + ++length; + } + pipeName[length] = 0; + if (length == 0) + return ERROR_INVALID_PARAMETER; + + HANDLE pipe = CreateFileW(pipeName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (pipe == INVALID_HANDLE_VALUE) + return GetLastError(); + CoInitializeEx(NULL, COINIT_MULTITHREADED); + BOOL served = Serve(pipe); + CoUninitialize(); + CloseHandle(pipe); + return served ? ERROR_SUCCESS : ERROR_BROKEN_PIPE; +} diff --git a/src/parserbroker_protocol.h b/src/parserbroker_protocol.h new file mode 100644 index 00000000..fb94b886 --- /dev/null +++ b/src/parserbroker_protocol.h @@ -0,0 +1,73 @@ +// SPDX-FileCopyrightText: 2026 Taskscape Ltd +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +// This protocol is intentionally a plain, packed byte contract. It must not +// contain pointers, handles, C++ objects, or structures whose layout changes +// with the host SDK. +static const DWORD PARSER_BROKER_MAGIC = 0x42525350; // 'PSRB' +static const WORD PARSER_BROKER_VERSION = 1; +static const DWORD PARSER_BROKER_MAX_PAYLOAD = 1024 * 1024; +static const DWORD PARSER_BROKER_MAX_PATH_BYTES = 32768; +static const DWORD PARSER_BROKER_MAX_THUMBNAIL_DIMENSION = 512; + +enum EParserBrokerMessageType +{ + pbmtThumbnailRequest = 1, + pbmtThumbnailResponse = 2, + pbmtArchiveMetadataRequest = 3, + pbmtArchiveMetadataResponse = 4, +}; + +enum EParserBrokerStatus +{ + pbsOk = 0, + pbsInvalidRequest = 1, + pbsUnsupported = 2, + pbsFailed = 3, + pbsResourceLimit = 4, +}; + +#pragma pack(push, 1) +struct CParserBrokerMessageHeader +{ + DWORD Magic; + WORD Version; + WORD Type; + DWORD PayloadLength; + DWORD CorrelationId; + DWORD Status; +}; + +struct CParserBrokerThumbnailRequest +{ + DWORD Width; + DWORD Height; + DWORD Flags; + DWORD PathBytes; +}; + +struct CParserBrokerThumbnailResponse +{ + DWORD Width; + DWORD Height; + DWORD PixelBytes; +}; + +struct CParserBrokerArchiveMetadataRequest +{ + DWORD PathBytes; +}; + +struct CParserBrokerArchiveMetadataResponse +{ + ULONGLONG FileSize; + FILETIME LastWriteTime; + DWORD ItemCount; +}; +#pragma pack(pop) + +static_assert(sizeof(CParserBrokerMessageHeader) == 20, "The broker header is an IPC wire format."); diff --git a/src/path_checking.cpp b/src/path_checking.cpp index b500120c..91b4b218 100644 --- a/src/path_checking.cpp +++ b/src/path_checking.cpp @@ -10,6 +10,7 @@ #include "pack.h" #include "codetbl.h" #include "dialogs.h" +#include "common/scoped_kernel_handle.h" CSystemPolicies SystemPolicies; @@ -17,7 +18,9 @@ const int ctsNotRunning = 0x00; // can be started const int ctsActive = 0x01; // this thread is active/just finishing const int ctsCanTerminate = 0x02; // can be terminated - already initialized from global data -HANDLE ThreadCheckPath[NUM_OF_CHECKTHREADS]; +// Each check worker owns its thread and completion state so shutdown cannot +// close a running worker handle or free the data it still observes. +CThreadOwner ThreadCheckPath[NUM_OF_CHECKTHREADS]; int ThreadCheckState[NUM_OF_CHECKTHREADS]; // state of individual threads char ThreadPath[MAX_PATH]; // input of active thread BOOL ThreadValid; // result of active thread @@ -27,7 +30,6 @@ CRITICAL_SECTION CheckPathCS; // critical section for check-path, necessary due // optimization: first check-path thread does not terminate - used repeatedly BOOL CPFirstFree = FALSE; // is it possible to use first check-path thread? -volatile LONG CPFirstTerminate; // should the first check-path thread terminate? HANDLE CPFirstStart = NULL; // event for starting the first check-path thread HANDLE CPFirstEnd = NULL; // event for testing completion of the first check-path thread DWORD CPFirstExit; // replacement for exit code of first check-path thread (does not terminate) @@ -35,7 +37,8 @@ DWORD CPFirstExit; // replacement for exit code of first check-path char CheckPathRootWithRetryMsgBox[MAX_PATH] = ""; // root of drive (including UNC), for which a "drive not ready" messagebox with Retry+Cancel buttons is displayed (used for automatic Retry after inserting media into drive) HWND LastDriveSelectErrDlgHWnd = NULL; // "drive not ready" dialog with Retry+Cancel buttons (used for automatic Retry after inserting media into drive) -DWORD WINAPI ThreadCheckPathF(void* param); +DWORD WINAPI ThreadCheckPathF(void* param, HANDLE stopEvent); +DWORD WINAPI ThreadCheckPathOwnedF(void* param, HANDLE stopEvent); CRITICAL_SECTION OpenHtmlHelpCS; // critical section for OpenHtmlHelp() @@ -55,12 +58,9 @@ BOOL InitializeCheckThread() CALL_STACK_MESSAGE_NONE HANDLES(InitializeCriticalSection(&CheckPathCS)); HANDLES(InitializeCriticalSection(&ReadCDVolNameCS)); - InterlockedExchange(&CPFirstTerminate, FALSE); - int i; for (i = 0; i < NUM_OF_CHECKTHREADS; i++) { - ThreadCheckPath[i] = NULL; ThreadCheckState[i] = ctsNotRunning; } @@ -73,9 +73,7 @@ BOOL InitializeCheckThread() } // try to start the first check-path thread - DWORD ThreadID; - ThreadCheckPath[0] = HANDLES(CreateThread(NULL, 0, ThreadCheckPathF, (void*)0, 0, &ThreadID)); - if (ThreadCheckPath[0] == NULL) // failed, but it doesn't matter ... + if (!ThreadCheckPath[0].Start(ThreadCheckPathOwnedF, (void*)0, "CheckPath")) // failed, but it doesn't matter ... { TRACE_E("Unable to start the first CheckPath thread."); } @@ -89,24 +87,21 @@ void ReleaseCheckThreads() if (CPFirstStart != NULL) { - InterlockedExchange(&CPFirstTerminate, TRUE); // let the first check-path thread terminate - SetEvent(CPFirstStart); + // The reusable worker waits on its owner stop event, so shutdown wakes + // it immediately without manufacturing a work request. + ThreadCheckPath[0].RequestStop(); } int i; for (i = 0; i < NUM_OF_CHECKTHREADS; i++) { - if (ThreadCheckPath[i] != NULL) + if (ThreadCheckPath[i].HasThread()) { - if (WaitForSingleObject(ThreadCheckPath[i], 1000) == WAIT_TIMEOUT) - TRACE_E("Check-path thread did not stop within the shutdown deadline; waiting for a safe join."); - - // A network attribute query can be slow, but the worker may still use the - // shared state. Never release that state until it has finished naturally. - WaitForSingleObject(ThreadCheckPath[i], INFINITE); + // Attribute queries can block in redirectors, so give cancellation + // and recovery separate deadlines before the mandatory safe join. + if (ThreadCheckPath[i].StopAndJoin(CThreadShutdownDeadline("check-path worker")) == WAIT_TIMEOUT) + TRACE_E("Check-path worker missed a shutdown deadline and completed only after the diagnostic wait."); ThreadCheckState[i] = ctsNotRunning; - HANDLES(CloseHandle(ThreadCheckPath[i])); - ThreadCheckPath[i] = NULL; } } if (CPFirstStart != NULL) @@ -124,13 +119,12 @@ void ReleaseCheckThreads() HANDLES(DeleteCriticalSection(&CheckPathCS)); } -unsigned ThreadCheckPathFBody(void* param) // test directory accessibility +unsigned ThreadCheckPathFBody(void* param, HANDLE stopEvent) // test directory accessibility { CALL_STACK_MESSAGE1("ThreadCheckPathFBody()"); int i = (int)(INT_PTR)param; char threadPath[MAX_PATH + 5]; - SetThreadNameInVCAndTrace("CheckPath"); // if (i == 0) TRACE_I("First check-path thread: Begin"); // else TRACE_I("Begin"); @@ -139,15 +133,22 @@ unsigned ThreadCheckPathFBody(void* param) // test directory accessibility if (i == 0) // first check-path thread (optimization: runs continuously) { CPFirstFree = TRUE; // for thread entry, otherwise unnecessary safeguard ;-) - // TRACE_I("First check-path thread: Wait for start"); - WaitForSingleObject(CPFirstStart, INFINITE); // wait for start or termination - // TRACE_I("First check-path thread: Wait satisfied"); - CPFirstFree = FALSE; - if (InterlockedCompareExchange(&CPFirstTerminate, FALSE, FALSE)) // termination - { - // TRACE_I("First check-path thread: End"); + // A request starts work; the owner stop event is a distinct shutdown + // wake reason, so the idle worker never needs polling to terminate. + HANDLE waits[] = {CPFirstStart, stopEvent}; + DWORD wait = WaitForMultipleObjects(2, waits, FALSE, INFINITE); + if (wait == WAIT_OBJECT_0 + 1) return 0; + if (wait != WAIT_OBJECT_0) + { + TRACE_E("CheckPath worker wait failed: " << GetErrorText(GetLastError())); + return 1; } + // Prefer shutdown when a request and stop race, preserving the former + // terminate-flag behaviour without running one last stale request. + if (WaitForSingleObject(stopEvent, 0) == WAIT_OBJECT_0) + return 0; + CPFirstFree = FALSE; } // TRACE_I("Testing path " << ThreadPath); @@ -193,7 +194,6 @@ unsigned ThreadCheckPathFBody(void* param) // test directory accessibility if (!threadValid && error != ERROR_SUCCESS) { - // SetThreadNameInVCAndTrace("CheckPath"); // TRACE_I("Error: " << GetErrorText(error)); } @@ -222,14 +222,14 @@ unsigned ThreadCheckPathFBody(void* param) // test directory accessibility return ret; } -unsigned ThreadCheckPathFEH(void* param) +unsigned ThreadCheckPathFEH(void* param, HANDLE stopEvent) { CALL_STACK_MESSAGE_NONE #ifndef CALLSTK_DISABLE __try { #endif // CALLSTK_DISABLE - return ThreadCheckPathFBody(param); + return ThreadCheckPathFBody(param, stopEvent); #ifndef CALLSTK_DISABLE } __except (CCallStack::HandleException(GetExceptionInformation())) @@ -242,13 +242,114 @@ unsigned ThreadCheckPathFEH(void* param) #endif // CALLSTK_DISABLE } -DWORD WINAPI ThreadCheckPathF(void* param) +DWORD WINAPI ThreadCheckPathF(void* param, HANDLE stopEvent) { CALL_STACK_MESSAGE_NONE #ifndef CALLSTK_DISABLE CCallStack stack; #endif // CALLSTK_DISABLE - return ThreadCheckPathFEH(param); + return ThreadCheckPathFEH(param, stopEvent); +} + +DWORD WINAPI ThreadCheckPathOwnedF(void* param, HANDLE stopEvent) +{ + // Forward the owner cancellation signal into the reusable-worker wait. + return ThreadCheckPathF(param, stopEvent); +} + +enum CCheckPathWaitReason +{ + cpwrWorkCompleted, + cpwrUserCancelled, + cpwrDeadlineElapsed, + cpwrFailed +}; + +static CCheckPathWaitReason WaitForCheckPathCompletionOrDeadline(HANDLE completionEvent, + HANDLE cancellationEvent, + DWORD deadlineMilliseconds) +{ + CScopedKernelHandle deadlineTimer(HANDLES(CreateWaitableTimer(NULL, TRUE, NULL))); + if (!deadlineTimer.IsValid()) + { + TRACE_E("Unable to create CheckPath deadline timer: " << GetErrorText(GetLastError())); + return cpwrFailed; + } + + LARGE_INTEGER dueTime; + dueTime.QuadPart = -((LONGLONG)deadlineMilliseconds * 10000); + if (!SetWaitableTimer(deadlineTimer.Get(), &dueTime, 0, NULL, NULL, FALSE)) + { + TRACE_E("Unable to set CheckPath deadline timer: " << GetErrorText(GetLastError())); + return cpwrFailed; + } + + if (completionEvent == NULL && cancellationEvent == NULL) + { + // Retry-only waits have no work producer; the timer is their explicit + // deadline signal and preserves the former bounded delay. + return WaitForSingleObject(deadlineTimer.Get(), INFINITE) == WAIT_OBJECT_0 + ? cpwrDeadlineElapsed + : cpwrFailed; + } + + // Work, user cancellation, and the deadline are independent wake reasons; + // no fixed delay stands in for a worker state change or a Close action. + HANDLE waits[] = {completionEvent, cancellationEvent, deadlineTimer.Get()}; + DWORD waitCount = cancellationEvent != NULL ? 3 : 2; + if (cancellationEvent == NULL) + waits[1] = deadlineTimer.Get(); + DWORD wait = WaitForMultipleObjects(waitCount, waits, FALSE, INFINITE); + if (wait == WAIT_OBJECT_0) + return cpwrWorkCompleted; + if (cancellationEvent != NULL && wait == WAIT_OBJECT_0 + 1) + return cpwrUserCancelled; + if (wait == WAIT_OBJECT_0 + waitCount - 1) + return cpwrDeadlineElapsed; + + TRACE_E("CheckPath completion/deadline wait failed: " << GetErrorText(GetLastError())); + return cpwrFailed; +} + +static BOOL WaitForAvailableCheckPathWorker() +{ + HANDLE completions[NUM_OF_CHECKTHREADS]; + DWORD completionCount = 0; + + if (!CPFirstFree && CPFirstEnd != NULL) + completions[completionCount++] = CPFirstEnd; + + for (int i = 1; i < NUM_OF_CHECKTHREADS; i++) + { + if ((ThreadCheckState[i] & ctsActive) && ThreadCheckPath[i].HasThread()) + { + HANDLE completion = ThreadCheckPath[i].GetCompletionEvent(); + if (completion != NULL) + completions[completionCount++] = completion; + } + } + + if (completionCount == 0) + { + TRACE_E("CheckPath found no active worker to await."); + return FALSE; + } + + // Waiting on the actual completion events releases the caller as soon as + // a slot opens instead of imposing the former 100 ms polling latency. + DWORD wait = WaitForMultipleObjects(completionCount, completions, FALSE, INFINITE); + if (wait >= WAIT_OBJECT_0 && wait < WAIT_OBJECT_0 + completionCount) + return TRUE; + + TRACE_E("CheckPath worker-availability wait failed: " << GetErrorText(GetLastError())); + return FALSE; +} + +static void WaitForCheckPathRetryDeadline(DWORD deadlineMilliseconds) +{ + // A retry is periodic work, so its only wake reason is an explicit timer + // deadline rather than a scheduler-dependent Sleep call. + WaitForCheckPathCompletionOrDeadline(NULL, NULL, deadlineMilliseconds); } DWORD SalCheckPath(BOOL echo, const char* path, DWORD err, BOOL postRefresh, HWND parent) @@ -297,15 +398,13 @@ DWORD SalCheckPath(BOOL echo, const char* path, DWORD err, BOOL postRefresh, HWN { if (ThreadCheckState[freeThreadIndex] & ctsActive) continue; - else if (ThreadCheckPath[freeThreadIndex] != NULL) + else if (ThreadCheckPath[freeThreadIndex].HasThread()) { DWORD exit; - if (!GetExitCodeThread(ThreadCheckPath[freeThreadIndex], &exit) || + if (!ThreadCheckPath[freeThreadIndex].GetExitCode(&exit) || exit != STILL_ACTIVE) // already finished { ThreadCheckState[freeThreadIndex] = ctsNotRunning; - HANDLES(CloseHandle(ThreadCheckPath[freeThreadIndex])); - ThreadCheckPath[freeThreadIndex] = NULL; runThread = TRUE; break; } @@ -340,13 +439,15 @@ DWORD SalCheckPath(BOOL echo, const char* path, DWORD err, BOOL postRefresh, HWN } else // je sitovy -> do jednoho z vedl. threadu { - Sleep(100); // let's take a short break and test again - goto TEST_AGAIN; + if (WaitForAvailableCheckPathWorker()) + goto TEST_AGAIN; + + valid = (SalGetFileAttributes(path) != 0xFFFFFFFF); + lastError = valid ? ERROR_SUCCESS : GetLastError(); } } else { - DWORD ThreadID; BOOL success = TRUE; ThreadCheckState[freeThreadIndex] = ctsActive; if (freeThreadIndex == 0) // odstartujeme prvni check-path thread @@ -356,10 +457,9 @@ DWORD SalCheckPath(BOOL echo, const char* path, DWORD err, BOOL postRefresh, HWN } else // start ostatnich { - ThreadCheckPath[freeThreadIndex] = HANDLES(CreateThread(NULL, 0, ThreadCheckPathF, - (void*)(INT_PTR)freeThreadIndex, - 0, &ThreadID)); - if (ThreadCheckPath[freeThreadIndex] == NULL) + if (!ThreadCheckPath[freeThreadIndex].Start(ThreadCheckPathOwnedF, + (void*)(INT_PTR)freeThreadIndex, + "CheckPath")) { TRACE_E("Unable to start CheckPath thread."); ThreadCheckState[freeThreadIndex] = ctsNotRunning; @@ -378,7 +478,7 @@ DWORD SalCheckPath(BOOL echo, const char* path, DWORD err, BOOL postRefresh, HWN GetAsyncKeyState(VK_ESCAPE); // init GetAsyncKeyState - viz help if (freeThreadIndex == 0) // prvni check-path thread, kontrola dokonceni { - if (WaitForSingleObject(CPFirstEnd, 200) != WAIT_TIMEOUT) // 200 ms - doba hajeni + if (WaitForCheckPathCompletionOrDeadline(CPFirstEnd, NULL, 200) == cpwrWorkCompleted) { exit = CPFirstExit; // nahrada navratove hodnoty } @@ -387,8 +487,8 @@ DWORD SalCheckPath(BOOL echo, const char* path, DWORD err, BOOL postRefresh, HWN } else { - WaitForSingleObject(ThreadCheckPath[freeThreadIndex], 200); // 200 ms - doba hajeni - if (!GetExitCodeThread(ThreadCheckPath[freeThreadIndex], &exit)) + WaitForCheckPathCompletionOrDeadline(ThreadCheckPath[freeThreadIndex].GetCompletionEvent(), NULL, 200); + if (!ThreadCheckPath[freeThreadIndex].GetExitCode(&exit)) exit = STILL_ACTIVE; } if (exit == STILL_ACTIVE) // take care of kill via ESC @@ -397,36 +497,38 @@ DWORD SalCheckPath(BOOL echo, const char* path, DWORD err, BOOL postRefresh, HWN char buf[MAX_PATH + 100]; sprintf(buf, LoadStr(IDS_CHECKINGPATHESC), path); CreateSafeWaitWindow(buf, NULL, 4800 + 200, TRUE, NULL); + CScopedKernelHandle cancellationEvent(GetSafeWaitWindowCancelEvent()); while (1) { - if (ThreadCheckState[freeThreadIndex] & ctsCanTerminate) + CCheckPathWaitReason waitReason; + if (freeThreadIndex == 0) + waitReason = WaitForCheckPathCompletionOrDeadline(CPFirstEnd, cancellationEvent.Get(), 200); + else + waitReason = WaitForCheckPathCompletionOrDeadline( + ThreadCheckPath[freeThreadIndex].GetCompletionEvent(), cancellationEvent.Get(), 200); + + if (waitReason == cpwrUserCancelled || + (waitReason == cpwrDeadlineElapsed && UserWantsToCancelSafeWaitWindow())) { - if (UserWantsToCancelSafeWaitWindow()) - { - exit = 1; - ThreadCheckState[freeThreadIndex] &= ~ctsActive; - // The thread must finish its current system call naturally. Its result - // is ignored once cancellation has been requested. - break; - } + exit = 1; + ThreadCheckState[freeThreadIndex] &= ~ctsActive; + // The thread must finish its current system call naturally. Its result + // is ignored once cancellation has been requested. + break; } - if (freeThreadIndex == 0) // prvni check-path thread, kontrola dokonceni + if (waitReason == cpwrWorkCompleted && freeThreadIndex == 0) { - if (WaitForSingleObject(CPFirstEnd, 200) != WAIT_TIMEOUT) // 200 ms pred dalsim testem - { - exit = CPFirstExit; // nahrada navratove hodnoty - } - else - exit = STILL_ACTIVE; // still running + exit = CPFirstExit; // nahrada navratove hodnoty } - else + else if (waitReason == cpwrWorkCompleted) { - WaitForSingleObject(ThreadCheckPath[freeThreadIndex], 200); // 200 ms pred dalsim testem - if (!GetExitCodeThread(ThreadCheckPath[freeThreadIndex], &exit)) + if (!ThreadCheckPath[freeThreadIndex].GetExitCode(&exit)) exit = STILL_ACTIVE; } + else + exit = STILL_ACTIVE; if (exit != STILL_ACTIVE) break; } @@ -439,8 +541,7 @@ DWORD SalCheckPath(BOOL echo, const char* path, DWORD err, BOOL postRefresh, HWN ThreadCheckState[freeThreadIndex] = ctsNotRunning; if (freeThreadIndex != 0) { - HANDLES(CloseHandle(ThreadCheckPath[freeThreadIndex])); - ThreadCheckPath[freeThreadIndex] = NULL; + ThreadCheckPath[freeThreadIndex].StopAndJoin(0); } } else // was terminated, let it finish @@ -615,7 +716,7 @@ BOOL SalCheckAndRestorePathWithCut(HWND parent, char* path, BOOL& tryNet, DWORD& if (err == ERROR_SEM_TIMEOUT && !semTimeoutOccured) { // Vista: pri zmene fyzickeho pripojeni (napr. Wi-Fi a pak LAN) to nepochopitelne hlasi tuto chybu a na podruhe uz je vse OK, takze tenhle opruz delame za uzivatele semTimeoutOccured = TRUE; - Sleep(300); + WaitForCheckPathRetryDeadline(300); continue; } if (err == ERROR_USER_TERMINATED) @@ -1456,27 +1557,51 @@ void CSystemPolicies::LoadFromRegistry() } } -BOOL SalGetFileSize(HANDLE file, CQuadWord& size, DWORD& err) +CFileOffsetResult SalGetFileSizeEx(HANDLE file) { - CALL_STACK_MESSAGE1("SalGetFileSize(, ,)"); + CALL_STACK_MESSAGE1("SalGetFileSizeEx()"); if (file == NULL || file == INVALID_HANDLE_VALUE) { - TRACE_E("SalGetFileSize(): file handle is invalid!"); - err = ERROR_INVALID_HANDLE; - size.Set(0, 0); - return FALSE; + TRACE_E("SalGetFileSizeEx(): file handle is invalid!"); + return CFileOffsetResult(ERROR_INVALID_HANDLE); } - BOOL ret = FALSE; - size.LoDWord = GetFileSize(file, &size.HiDWord); - if ((size.LoDWord != INVALID_FILE_SIZE || (err = GetLastError()) == NO_ERROR)) + LARGE_INTEGER size; + if (!GetFileSizeEx(file, &size)) + return CFileOffsetResult(GetLastError()); + + CQuadWord value; + value.SetUI64((unsigned __int64)size.QuadPart); + return CFileOffsetResult(value); +} + +CFileOffsetResult SalSetFilePointerEx(HANDLE file, const CQuadWord& distance, DWORD moveMethod) +{ + CALL_STACK_MESSAGE1("SalSetFilePointerEx()"); + if (file == NULL || file == INVALID_HANDLE_VALUE) { - ret = TRUE; - err = NO_ERROR; + TRACE_E("SalSetFilePointerEx(): file handle is invalid!"); + return CFileOffsetResult(ERROR_INVALID_HANDLE); } - else - size.Set(0, 0); - return ret; + + LARGE_INTEGER input; + input.QuadPart = (__int64)distance.Value; + LARGE_INTEGER output; + if (!SetFilePointerEx(file, input, &output, moveMethod)) + return CFileOffsetResult(GetLastError()); + + CQuadWord value; + value.SetUI64((unsigned __int64)output.QuadPart); + return CFileOffsetResult(value); +} + +BOOL SalGetFileSize(HANDLE file, CQuadWord& size, DWORD& err) +{ + CALL_STACK_MESSAGE1("SalGetFileSize(, ,)"); + CFileOffsetResult result = SalGetFileSizeEx(file); + size = result.Value; + err = result.Error; + return result.Succeeded; } BOOL SalGetFileSize2(const char* fileName, CQuadWord& size, DWORD* err) @@ -1673,7 +1798,7 @@ void WaitForESCRelease() { if ((GetAsyncKeyState(VK_ESCAPE) & 0x8001) == 0) break; - Sleep(10); + WaitForCheckPathRetryDeadline(10); } } diff --git a/src/path_utils.cpp b/src/path_utils.cpp index 6c325aec..56e68032 100644 --- a/src/path_utils.cpp +++ b/src/path_utils.cpp @@ -671,16 +671,29 @@ BOOL SalGetFullName(char* name, int* errTextID, const char* curDir, char* nextFo // **************************************************************************** -TDirectArray AuxThreads(10, 5); +struct CAuxThread +{ + HANDLE Thread; + LPCSTR Description; -void AuxThreadBody(BOOL add, HANDLE thread, BOOL testIfFinished) + CAuxThread(HANDLE thread, LPCSTR description) + : Thread(thread), Description(description) + { + } +}; + +// Keep a purpose beside each legacy handle so a shutdown deadline tells the +// operator which component is still preventing resource teardown. +TDirectArray AuxThreads(10, 5); + +void AuxThreadBody(BOOL add, HANDLE thread, BOOL testIfFinished, LPCSTR description) { // Prevent re-entrance static CCriticalSection cs; CEnterCriticalSection enterCS(cs); static BOOL finished = FALSE; - if (!finished) // after calling TerminateAuxThreads() we no longer accept anything + if (!finished) // after calling ShutdownAuxThreads() we no longer accept anything { if (add) { @@ -688,9 +701,9 @@ void AuxThreadBody(BOOL add, HANDLE thread, BOOL testIfFinished) for (int i = 0; i < AuxThreads.Count; i++) { DWORD code; - if (!GetExitCodeThread(AuxThreads[i], &code) || code != STILL_ACTIVE) + if (!GetExitCodeThread(AuxThreads[i].Thread, &code) || code != STILL_ACTIVE) { // thread uz dobehl - HANDLES(CloseHandle(AuxThreads[i])); + HANDLES(CloseHandle(AuxThreads[i].Thread)); AuxThreads.Delete(i); i--; } @@ -707,37 +720,39 @@ void AuxThreadBody(BOOL add, HANDLE thread, BOOL testIfFinished) } // pridame novy thread if (!skipAdd) - AuxThreads.Add(thread); + AuxThreads.Add(CAuxThread(thread, description)); } else { finished = TRUE; for (int i = 0; i < AuxThreads.Count; i++) { - HANDLE t = AuxThreads[i]; + CAuxThread& auxiliary = AuxThreads[i]; + HANDLE t = auxiliary.Thread; DWORD code; if (GetExitCodeThread(t, &code) && code == STILL_ACTIVE) - { // thread stale bezi, terminujeme ho - TerminateThread(t, 666); - WaitForSingleObject(t, INFINITE); // pockame az thread skutecne skonci, nekdy mu to dost trva - } + // These legacy workers may still reference host globals, so + // deadline breach is diagnostic and cannot justify detaching. + CThreadShutdownDeadline(auxiliary.Description).WaitForSafeJoin(t); HANDLES(CloseHandle(t)); } AuxThreads.DestroyMembers(); } } else - TRACE_E("AuxThreadBody(): calling after TerminateAuxThreads() is not supported! add=" << add); + TRACE_E("AuxThreadBody(): calling after ShutdownAuxThreads() is not supported! add=" << add); } -void AddAuxThread(HANDLE thread, BOOL testIfFinished) +void AddAuxThread(HANDLE thread, BOOL testIfFinished, LPCSTR description) { - AuxThreadBody(TRUE, thread, testIfFinished); + // Preserve a stable component label for deadline diagnostics at process exit. + AuxThreadBody(TRUE, thread, testIfFinished, description); } -void TerminateAuxThreads() +void ShutdownAuxThreads() { - AuxThreadBody(FALSE, NULL, FALSE); + // Final teardown cannot release globals until every legacy worker is joined. + AuxThreadBody(FALSE, NULL, FALSE, NULL); } // **************************************************************************** @@ -897,7 +912,7 @@ pokud bude jeste nekdy potreba ozivit tenhle kod, vyuzit toho, ze lze nahradit ( } if (MainWindow != NULL && MainWindow->NeedToResentDispachChangeNotif && - !AlreadyInPlugin) // if it is still in the plug-in, sending the message makes no sense + !IsInPlugin()) // if it is still in the plug-in, sending the message makes no sense { MainWindow->NeedToResentDispachChangeNotif = FALSE; @@ -2454,7 +2469,8 @@ void CUserMenuIconBkgndReader::StartBkgndReadingIcons(CUserMenuIconDataArr* bkgn bkgndReaderData = NULL; // jsou predana v threadu, nebudeme je uvolnovat zde CurIRThreadIDIsValid = TRUE; CurIRThreadID = newThreadID; - AddAuxThread(thread); // if the thread does not finish in time, kill it before closing the application + // The reader uses shared menu state, so shutdown tracks it to a safe join. + AddAuxThread(thread, FALSE, "user-menu icon reader"); } else TRACE_E("CUserMenuIconBkgndReader::StartBkgndReadingIcons(): unable to start thread for reading user menu icons."); diff --git a/src/plugins.h b/src/plugins.h index b2695404..7920c4ab 100644 --- a/src/plugins.h +++ b/src/plugins.h @@ -4,6 +4,8 @@ #pragma once +#include + // when changing this header search for "BuiltForVersion" - tests for older plugin versions will no longer make sense and should be removed #define PLUGIN_REQVER 103 // ("5.0") load only plugins that return at least this required Salamander version @@ -21,6 +23,75 @@ // functions called before and after invoking any plugin method void EnterPlugin(); void LeavePlugin(); +BOOL IsInPlugin(); + +// The main application thread owns creation and teardown through CPlugins. Plug-in +// callbacks may arrive on host worker threads, so the depth is atomic and the +// first-entry/last-exit transitions are serialized by the state object. +class CPluginCallbackState +{ +public: + CPluginCallbackState(); + + void Enter(); + void Leave(); + BOOL IsEntered() const; + +private: + CPluginCallbackState(const CPluginCallbackState&); + CPluginCallbackState& operator=(const CPluginCallbackState&); + + std::atomic CallbackDepth; + SRWLOCK TransitionLock; +}; + +// Every host-to-plug-in call must use this boundary. The exception filter restores +// the EnterPlugin invariants, records the owning plug-in and queues a safe unload. +// Callers initialize result variables before the macro so a failed callback always +// returns a valid conservative value. +int WINAPI HandlePluginCallbackException(const void* pluginInterface, const char* callback, + EXCEPTION_POINTERS* exceptionInfo); +void HandlePluginCallbackFailure(const void* pluginInterface, const char* callback); + +class CPluginCallbackInvoker +{ +public: + virtual void Invoke() = 0; +}; + +// Defined in plugins_loading.cpp so that the SEH frame never shares a function +// with C++ objects that require unwinding. +BOOL CallPluginCallback(const void* pluginInterface, const char* callback, + CPluginCallbackInvoker* invoker); + +template +class CPluginCallbackInvokerImpl : public CPluginCallbackInvoker +{ +public: + CPluginCallbackInvokerImpl(T& callback) : Callback(callback) {} + virtual void Invoke() { Callback(); } + +private: + T& Callback; +}; + +#define PLUGIN_CALLBACK(pluginInterface, callback, call) \ + do \ + { \ + auto pluginCallbackLambda = [&]() \ + { \ + try \ + { \ + call; \ + } \ + catch (...) \ + { \ + HandlePluginCallbackFailure(pluginInterface, callback); \ + } \ + }; \ + CPluginCallbackInvokerImpl pluginCallbackInvoker(pluginCallbackLambda); \ + CallPluginCallback(pluginInterface, callback, &pluginCallbackInvoker); \ + } while (0) class CPluginInterfaceForArchiverEncapsulation { @@ -410,14 +481,15 @@ class CPluginInterfaceEncapsulation void About(HWND parent) { EnterPlugin(); - Interface->About(parent); + PLUGIN_CALLBACK(Interface, "About", Interface->About(parent)); LeavePlugin(); } BOOL Release(HWND parent, BOOL force) { EnterPlugin(); - BOOL r = Interface->Release(parent, force); + BOOL r = FALSE; + PLUGIN_CALLBACK(Interface, "Release", r = Interface->Release(parent, force)); LeavePlugin(); return r; } @@ -425,28 +497,28 @@ class CPluginInterfaceEncapsulation void LoadConfiguration(HWND parent, HKEY regKey, CSalamanderRegistryAbstract* registry) { EnterPlugin(); - Interface->LoadConfiguration(parent, regKey, registry); + PLUGIN_CALLBACK(Interface, "LoadConfiguration", Interface->LoadConfiguration(parent, regKey, registry)); LeavePlugin(); } void SaveConfiguration(HWND parent, HKEY regKey, CSalamanderRegistryAbstract* registry) { EnterPlugin(); - Interface->SaveConfiguration(parent, regKey, registry); + PLUGIN_CALLBACK(Interface, "SaveConfiguration", Interface->SaveConfiguration(parent, regKey, registry)); LeavePlugin(); } void Configuration(HWND parent) { EnterPlugin(); - Interface->Configuration(parent); + PLUGIN_CALLBACK(Interface, "Configuration", Interface->Configuration(parent)); LeavePlugin(); } void Connect(HWND parent, CSalamanderConnectAbstract* salamander) { EnterPlugin(); - Interface->Connect(parent, salamander); + PLUGIN_CALLBACK(Interface, "Connect", Interface->Connect(parent, salamander)); LeavePlugin(); } @@ -456,7 +528,8 @@ class CPluginInterfaceEncapsulation CPluginInterfaceForArchiverAbstract* GetInterfaceForArchiver() { EnterPlugin(); - CPluginInterfaceForArchiverAbstract* r = Interface->GetInterfaceForArchiver(); + CPluginInterfaceForArchiverAbstract* r = NULL; + PLUGIN_CALLBACK(Interface, "GetInterfaceForArchiver", r = Interface->GetInterfaceForArchiver()); LeavePlugin(); return r; } @@ -464,7 +537,8 @@ class CPluginInterfaceEncapsulation CPluginInterfaceForViewerAbstract* GetInterfaceForViewer() { EnterPlugin(); - CPluginInterfaceForViewerAbstract* r = Interface->GetInterfaceForViewer(); + CPluginInterfaceForViewerAbstract* r = NULL; + PLUGIN_CALLBACK(Interface, "GetInterfaceForViewer", r = Interface->GetInterfaceForViewer()); LeavePlugin(); return r; } @@ -472,7 +546,8 @@ class CPluginInterfaceEncapsulation CPluginInterfaceForMenuExtAbstract* GetInterfaceForMenuExt() { EnterPlugin(); - CPluginInterfaceForMenuExtAbstract* r = Interface->GetInterfaceForMenuExt(); + CPluginInterfaceForMenuExtAbstract* r = NULL; + PLUGIN_CALLBACK(Interface, "GetInterfaceForMenuExt", r = Interface->GetInterfaceForMenuExt()); LeavePlugin(); return r; } @@ -480,7 +555,8 @@ class CPluginInterfaceEncapsulation CPluginInterfaceForFSAbstract* GetInterfaceForFS() { EnterPlugin(); - CPluginInterfaceForFSAbstract* r = Interface->GetInterfaceForFS(); + CPluginInterfaceForFSAbstract* r = NULL; + PLUGIN_CALLBACK(Interface, "GetInterfaceForFS", r = Interface->GetInterfaceForFS()); LeavePlugin(); return r; } @@ -488,7 +564,8 @@ class CPluginInterfaceEncapsulation CPluginInterfaceForThumbLoaderAbstract* GetInterfaceForThumbLoader() { EnterPlugin(); - CPluginInterfaceForThumbLoaderAbstract* r = Interface->GetInterfaceForThumbLoader(); + CPluginInterfaceForThumbLoaderAbstract* r = NULL; + PLUGIN_CALLBACK(Interface, "GetInterfaceForThumbLoader", r = Interface->GetInterfaceForThumbLoader()); LeavePlugin(); return r; } @@ -496,28 +573,28 @@ class CPluginInterfaceEncapsulation void Event(int event, DWORD param) { EnterPlugin(); - Interface->Event(event, param); + PLUGIN_CALLBACK(Interface, "Event", Interface->Event(event, param)); LeavePlugin(); } void ClearHistory(HWND parent) { EnterPlugin(); - Interface->ClearHistory(parent); + PLUGIN_CALLBACK(Interface, "ClearHistory", Interface->ClearHistory(parent)); LeavePlugin(); } void AcceptChangeOnPathNotification(const char* path, BOOL includingSubdirs) { EnterPlugin(); - Interface->AcceptChangeOnPathNotification(path, includingSubdirs); + PLUGIN_CALLBACK(Interface, "AcceptChangeOnPathNotification", Interface->AcceptChangeOnPathNotification(path, includingSubdirs)); LeavePlugin(); } void PasswordManagerEvent(HWND parent, int event) { EnterPlugin(); - Interface->PasswordManagerEvent(parent, event); + PLUGIN_CALLBACK(Interface, "PasswordManagerEvent", Interface->PasswordManagerEvent(parent, event)); LeavePlugin(); } }; @@ -2766,13 +2843,14 @@ struct CPluginOrder struct CPluginFSTimer { - DWORD AbsTimeout; // absolute timeout value for the timer + // Plugin timer deadlines must survive host uptimes longer than 49.7 days. + CMonotonicTimePoint AbsTimeout; CPluginFSInterfaceAbstract* TimerOwner; // FS object that should receive the timer timeout DWORD TimerParam; // timer parameter (plugin data) DWORD TimerAddedTime; // "time" the timer was added (prevents endless loops inside CPlugins::HandlePluginFSTimers()) - CPluginFSTimer(DWORD absTimeout, CPluginFSInterfaceAbstract* timerOwner, DWORD timerParam, DWORD timerAddedTime) + CPluginFSTimer(CMonotonicTimePoint absTimeout, CPluginFSInterfaceAbstract* timerOwner, DWORD timerParam, DWORD timerAddedTime) { AbsTimeout = absTimeout; TimerOwner = timerOwner; @@ -2784,6 +2862,9 @@ struct CPluginFSTimer class CPlugins { protected: + // Keeps callback lifetime state with its plug-in owner instead of in a + // translation-unit global shared with shutdown and re-entrant callbacks. + CPluginCallbackState CallbackState; TIndirectArray Data; CRITICAL_SECTION DataCS; // critical section used only to synchronize data accessed by GetIndex() method @@ -2830,6 +2911,10 @@ class CPlugins void EnterDataCS() { HANDLES(EnterCriticalSection(&DataCS)); } void LeaveDataCS() { HANDLES(LeaveCriticalSection(&DataCS)); } + // Compatibility entry points retain the legacy call surface while callers + // that manage one callback scope can receive this narrow state reference. + CPluginCallbackState& GetCallbackState() { return CallbackState; } + // loads the object from registry key 'regKey' or default values (if regKey==NULL) (ZIP + TAR + PAK) // 'parent' is the parent window for message boxes (may be NULL) void Load(HWND parent, HKEY regKey); @@ -2960,6 +3045,10 @@ class CPlugins // NOTE: the pointer is valid only until the number of plugins changes (the array expands or shrinks) CPluginData* GetPluginData(const CPluginInterfaceForFSAbstract* plugin); + // Finds the plug-in that owns any SDK interface. Failure handling uses + // this instead of trusting a callback's data pointers after an exception. + CPluginData* GetPluginData(const void* pluginInterface); + // returns the CPluginData with iface 'plugin', or NULL if none exists // NOTE: the pointer is valid only until the number of plugins changes (the array expands or shrinks) CPluginData* GetPluginData(const CPluginInterfaceAbstract* plugin, int* lastIndex = NULL); @@ -3025,6 +3114,9 @@ class CPlugins // 'parent' is the parent window for message boxes; returns TRUE if panel selection should be cleared BOOL ExecuteMenuItem(CFilesWindow* panel, HWND parent, int suid); + // Test control resolves a plug-in-owned ID only after normal menu allocation, avoiding a duplicated host command ID. + int ResolveMenuItemCommandForTests(HWND parent, const char* dllSuffix, int id); + // shows help for the menu command with the identifier 'suid' (WM_COMMAND in HelpMode) // 'parent' is the parent window for message boxes; returns TRUE if the help was shown BOOL HelpForMenuItem(HWND parent, int suid); @@ -3131,8 +3223,7 @@ class CPlugins BOOL GetShowInChDrv(int index); void SetShowInChDrv(int index, BOOL showInChDrv); - // adds a new timer to the PluginFSTimers array; the absolute timeout is GetTickCount() + 'relTimeout'. - // Once GetTickCount() returns a value greater than or equal to that timeout, + // Adds a timer using the process monotonic 64-bit clock. Once its deadline has passed, // the FS object's Event() method is called with FSE_TIMER and 'timerParam'. // Returns TRUE on success (timer successfully added) BOOL AddPluginFSTimer(DWORD relTimeout, CPluginFSInterfaceAbstract* timerOwner, DWORD timerParam); @@ -3213,7 +3304,7 @@ class CPlugins BOOL PluginVisibleInBar(const char* dllName); // helper function: finds the index position for inserting a timer with 'timeoutAbs' into the PluginFSTimers array - int FindIndexForNewPluginFSTimer(DWORD timeoutAbs); + int FindIndexForNewPluginFSTimer(CMonotonicTimePoint timeoutAbs); }; // @@ -3405,8 +3496,6 @@ class CSalamanderBuildMenu : public CSalamanderBuildMenuAbstract }; extern CPlugins Plugins; -extern int AlreadyInPlugin; - // internal function for handling plugin icons BOOL CreateGrayscaleDIB(HBITMAP hSource, COLORREF transparent, HBITMAP& hGrayscale); //HICON GetIconFromDIB(HBITMAP hBitmap, int index); diff --git a/src/plugins/7zip/7za/c/7z.h b/src/plugins/7zip/7za/c/7z.h index 47681519..9e27c015 100644 --- a/src/plugins/7zip/7za/c/7z.h +++ b/src/plugins/7zip/7za/c/7z.h @@ -1,8 +1,8 @@ /* 7z.h -- 7z interface -2015-11-18 : Igor Pavlov : Public domain */ +2023-04-02 : Igor Pavlov : Public domain */ -#ifndef __7Z_H -#define __7Z_H +#ifndef ZIP7_INC_7Z_H +#define ZIP7_INC_7Z_H #include "7zTypes.h" @@ -91,14 +91,16 @@ typedef struct UInt64 *CoderUnpackSizes; // for all coders in all folders Byte *CodersData; + + UInt64 RangeLimit; } CSzAr; UInt64 SzAr_GetFolderUnpackSize(const CSzAr *p, UInt32 folderIndex); SRes SzAr_DecodeFolder(const CSzAr *p, UInt32 folderIndex, - ILookInStream *stream, UInt64 startPos, + ILookInStreamPtr stream, UInt64 startPos, Byte *outBuffer, size_t outSize, - ISzAlloc *allocMain); + ISzAllocPtr allocMain); typedef struct { @@ -131,7 +133,7 @@ typedef struct #define SzArEx_GetFileSize(p, i) ((p)->UnpackPositions[(i) + 1] - (p)->UnpackPositions[i]) void SzArEx_Init(CSzArEx *p); -void SzArEx_Free(CSzArEx *p, ISzAlloc *alloc); +void SzArEx_Free(CSzArEx *p, ISzAllocPtr alloc); UInt64 SzArEx_GetFolderStreamPos(const CSzArEx *p, UInt32 folderIndex, UInt32 indexInFolder); int SzArEx_GetFolderFullPackSize(const CSzArEx *p, UInt32 folderIndex, UInt64 *resSize); @@ -172,15 +174,15 @@ UInt16 *SzArEx_GetFullNameUtf16_Back(const CSzArEx *p, size_t fileIndex, UInt16 SRes SzArEx_Extract( const CSzArEx *db, - ILookInStream *inStream, + ILookInStreamPtr inStream, UInt32 fileIndex, /* index of file */ UInt32 *blockIndex, /* index of solid block */ Byte **outBuffer, /* pointer to pointer to output buffer (allocated with allocMain) */ size_t *outBufferSize, /* buffer size for output buffer */ size_t *offset, /* offset of stream for required file in *outBuffer */ size_t *outSizeProcessed, /* size of file in *outBuffer */ - ISzAlloc *allocMain, - ISzAlloc *allocTemp); + ISzAllocPtr allocMain, + ISzAllocPtr allocTemp); /* @@ -194,8 +196,8 @@ SZ_ERROR_INPUT_EOF SZ_ERROR_FAIL */ -SRes SzArEx_Open(CSzArEx *p, ILookInStream *inStream, - ISzAlloc *allocMain, ISzAlloc *allocTemp); +SRes SzArEx_Open(CSzArEx *p, ILookInStreamPtr inStream, + ISzAllocPtr allocMain, ISzAllocPtr allocTemp); EXTERN_C_END diff --git a/src/plugins/7zip/7za/c/7zAlloc.c b/src/plugins/7zip/7za/c/7zAlloc.c index 3e848c98..2f0659af 100644 --- a/src/plugins/7zip/7za/c/7zAlloc.c +++ b/src/plugins/7zip/7za/c/7zAlloc.c @@ -1,78 +1,89 @@ -/* 7zAlloc.c -- Allocation functions -2015-11-09 : Igor Pavlov : Public domain */ +/* 7zAlloc.c -- Allocation functions for 7z processing +2023-03-04 : Igor Pavlov : Public domain */ #include "Precomp.h" +#include + #include "7zAlloc.h" -/* #define _SZ_ALLOC_DEBUG */ -/* use _SZ_ALLOC_DEBUG to debug alloc/free operations */ +/* #define SZ_ALLOC_DEBUG */ +/* use SZ_ALLOC_DEBUG to debug alloc/free operations */ -#ifdef _SZ_ALLOC_DEBUG +#ifdef SZ_ALLOC_DEBUG +/* #ifdef _WIN32 -#include +#include "7zWindows.h" #endif +*/ #include -int g_allocCount = 0; -int g_allocCountTemp = 0; +static int g_allocCount = 0; +static int g_allocCountTemp = 0; +static void Print_Alloc(const char *s, size_t size, int *counter) +{ + const unsigned size2 = (unsigned)size; + fprintf(stderr, "\n%s count = %10d : %10u bytes; ", s, *counter, size2); + (*counter)++; +} +static void Print_Free(const char *s, int *counter) +{ + (*counter)--; + fprintf(stderr, "\n%s count = %10d", s, *counter); +} #endif -void *SzAlloc(void *p, size_t size) +void *SzAlloc(ISzAllocPtr p, size_t size) { - UNUSED_VAR(p); + UNUSED_VAR(p) if (size == 0) return 0; - #ifdef _SZ_ALLOC_DEBUG - fprintf(stderr, "\nAlloc %10u bytes; count = %10d", (unsigned)size, g_allocCount); - g_allocCount++; + #ifdef SZ_ALLOC_DEBUG + Print_Alloc("Alloc", size, &g_allocCount); #endif return malloc(size); } -void SzFree(void *p, void *address) +void SzFree(ISzAllocPtr p, void *address) { - UNUSED_VAR(p); - #ifdef _SZ_ALLOC_DEBUG - if (address != 0) - { - g_allocCount--; - fprintf(stderr, "\nFree; count = %10d", g_allocCount); - } + UNUSED_VAR(p) + #ifdef SZ_ALLOC_DEBUG + if (address) + Print_Free("Free ", &g_allocCount); #endif free(address); } -void *SzAllocTemp(void *p, size_t size) +void *SzAllocTemp(ISzAllocPtr p, size_t size) { - UNUSED_VAR(p); + UNUSED_VAR(p) if (size == 0) return 0; - #ifdef _SZ_ALLOC_DEBUG - fprintf(stderr, "\nAlloc_temp %10u bytes; count = %10d", (unsigned)size, g_allocCountTemp); - g_allocCountTemp++; + #ifdef SZ_ALLOC_DEBUG + Print_Alloc("Alloc_temp", size, &g_allocCountTemp); + /* #ifdef _WIN32 return HeapAlloc(GetProcessHeap(), 0, size); #endif + */ #endif return malloc(size); } -void SzFreeTemp(void *p, void *address) +void SzFreeTemp(ISzAllocPtr p, void *address) { - UNUSED_VAR(p); - #ifdef _SZ_ALLOC_DEBUG - if (address != 0) - { - g_allocCountTemp--; - fprintf(stderr, "\nFree_temp; count = %10d", g_allocCountTemp); - } + UNUSED_VAR(p) + #ifdef SZ_ALLOC_DEBUG + if (address) + Print_Free("Free_temp ", &g_allocCountTemp); + /* #ifdef _WIN32 HeapFree(GetProcessHeap(), 0, address); return; #endif + */ #endif free(address); } diff --git a/src/plugins/7zip/7za/c/7zAlloc.h b/src/plugins/7zip/7za/c/7zAlloc.h index 2fd5bdbc..b2b8b0cd 100644 --- a/src/plugins/7zip/7za/c/7zAlloc.h +++ b/src/plugins/7zip/7za/c/7zAlloc.h @@ -1,23 +1,19 @@ /* 7zAlloc.h -- Allocation functions -2013-03-25 : Igor Pavlov : Public domain */ +2023-03-04 : Igor Pavlov : Public domain */ -#ifndef __7Z_ALLOC_H -#define __7Z_ALLOC_H +#ifndef ZIP7_INC_7Z_ALLOC_H +#define ZIP7_INC_7Z_ALLOC_H -#include +#include "7zTypes.h" -#ifdef __cplusplus -extern "C" { -#endif +EXTERN_C_BEGIN -void *SzAlloc(void *p, size_t size); -void SzFree(void *p, void *address); +void *SzAlloc(ISzAllocPtr p, size_t size); +void SzFree(ISzAllocPtr p, void *address); -void *SzAllocTemp(void *p, size_t size); -void SzFreeTemp(void *p, void *address); +void *SzAllocTemp(ISzAllocPtr p, size_t size); +void SzFreeTemp(ISzAllocPtr p, void *address); -#ifdef __cplusplus -} -#endif +EXTERN_C_END #endif diff --git a/src/plugins/7zip/7za/c/7zArcIn.c b/src/plugins/7zip/7za/c/7zArcIn.c index 2beed3d5..f6f6e7c3 100644 --- a/src/plugins/7zip/7za/c/7zArcIn.c +++ b/src/plugins/7zip/7za/c/7zArcIn.c @@ -1,5 +1,5 @@ /* 7zArcIn.c -- 7z Input functions -2016-05-16 : Igor Pavlov : Public domain */ +: Igor Pavlov : Public domain */ #include "Precomp.h" @@ -10,18 +10,17 @@ #include "7zCrc.h" #include "CpuArch.h" -#define MY_ALLOC(T, p, size, alloc) { \ - if ((p = (T *)IAlloc_Alloc(alloc, (size) * sizeof(T))) == NULL) return SZ_ERROR_MEM; } +#define MY_ALLOC(T, p, size, alloc) \ + { if ((p = (T *)ISzAlloc_Alloc(alloc, (size_t)(size) * sizeof(T))) == NULL) return SZ_ERROR_MEM; } -#define MY_ALLOC_ZE(T, p, size, alloc) { if ((size) == 0) p = NULL; else MY_ALLOC(T, p, size, alloc) } +#define MY_ALLOC_ZE(T, p, size, alloc) \ + { if ((size) == 0) p = NULL; else MY_ALLOC(T, p, size, alloc) } #define MY_ALLOC_AND_CPY(to, size, from, alloc) \ { MY_ALLOC(Byte, to, size, alloc); memcpy(to, from, size); } #define MY_ALLOC_ZE_AND_CPY(to, size, from, alloc) \ - { if ((size) == 0) p = NULL; else { MY_ALLOC_AND_CPY(to, size, from, alloc) } } - -#define k7zMajorVersion 0 + { if ((size) == 0) to = NULL; else { MY_ALLOC_AND_CPY(to, size, from, alloc) } } enum EIdEnum { @@ -51,16 +50,14 @@ enum EIdEnum k7zIdEncodedHeader, k7zIdStartPos, k7zIdDummy - // k7zNtSecure, - // k7zParent, - // k7zIsReal }; const Byte k7zSignature[k7zSignatureSize] = {'7', 'z', 0xBC, 0xAF, 0x27, 0x1C}; -#define SzBitUi32s_Init(p) { (p)->Defs = NULL; (p)->Vals = NULL; } +#define SzBitUi32s_INIT(p) { (p)->Defs = NULL; (p)->Vals = NULL; } -static SRes SzBitUi32s_Alloc(CSzBitUi32s *p, size_t num, ISzAlloc *alloc) +Z7_FORCE_INLINE +static SRes SzBitUi32s_Alloc(CSzBitUi32s *p, size_t num, ISzAllocPtr alloc) { if (num == 0) { @@ -69,34 +66,36 @@ static SRes SzBitUi32s_Alloc(CSzBitUi32s *p, size_t num, ISzAlloc *alloc) } else { - MY_ALLOC(Byte, p->Defs, (num + 7) >> 3, alloc); - MY_ALLOC(UInt32, p->Vals, num, alloc); + MY_ALLOC(Byte, p->Defs, (num + 7) >> 3, alloc) + MY_ALLOC(UInt32, p->Vals, num, alloc) } return SZ_OK; } -void SzBitUi32s_Free(CSzBitUi32s *p, ISzAlloc *alloc) +Z7_FORCE_INLINE +static void SzBitUi32s_Free(CSzBitUi32s *p, ISzAllocPtr alloc) { - IAlloc_Free(alloc, p->Defs); p->Defs = NULL; - IAlloc_Free(alloc, p->Vals); p->Vals = NULL; + ISzAlloc_Free(alloc, p->Defs); p->Defs = NULL; + ISzAlloc_Free(alloc, p->Vals); p->Vals = NULL; } -#define SzBitUi64s_Init(p) { (p)->Defs = NULL; (p)->Vals = NULL; } +#define SzBitUi64s_INIT(p) { (p)->Defs = NULL; (p)->Vals = NULL; } -void SzBitUi64s_Free(CSzBitUi64s *p, ISzAlloc *alloc) +Z7_FORCE_INLINE +static void SzBitUi64s_Free(CSzBitUi64s *p, ISzAllocPtr alloc) { - IAlloc_Free(alloc, p->Defs); p->Defs = NULL; - IAlloc_Free(alloc, p->Vals); p->Vals = NULL; + ISzAlloc_Free(alloc, p->Defs); p->Defs = NULL; + ISzAlloc_Free(alloc, p->Vals); p->Vals = NULL; } - +Z7_NO_INLINE static void SzAr_Init(CSzAr *p) { p->NumPackStreams = 0; p->NumFolders = 0; p->PackPositions = NULL; - SzBitUi32s_Init(&p->FolderCRCs); + SzBitUi32s_INIT(&p->FolderCRCs) p->FoCodersOffsets = NULL; p->FoStartPackStreamIndex = NULL; @@ -105,25 +104,26 @@ static void SzAr_Init(CSzAr *p) p->CoderUnpackSizes = NULL; p->CodersData = NULL; + + p->RangeLimit = 0; } -static void SzAr_Free(CSzAr *p, ISzAlloc *alloc) +Z7_NO_INLINE +static void SzAr_Free(CSzAr *p, ISzAllocPtr alloc) { - IAlloc_Free(alloc, p->PackPositions); + ISzAlloc_Free(alloc, p->PackPositions); SzBitUi32s_Free(&p->FolderCRCs, alloc); - - IAlloc_Free(alloc, p->FoCodersOffsets); - IAlloc_Free(alloc, p->FoStartPackStreamIndex); - IAlloc_Free(alloc, p->FoToCoderUnpackSizes); - IAlloc_Free(alloc, p->FoToMainUnpackSizeIndex); - IAlloc_Free(alloc, p->CoderUnpackSizes); - - IAlloc_Free(alloc, p->CodersData); + ISzAlloc_Free(alloc, p->FoCodersOffsets); + ISzAlloc_Free(alloc, p->FoStartPackStreamIndex); + ISzAlloc_Free(alloc, p->FoToCoderUnpackSizes); + ISzAlloc_Free(alloc, p->FoToMainUnpackSizeIndex); + ISzAlloc_Free(alloc, p->CoderUnpackSizes); + ISzAlloc_Free(alloc, p->CodersData); SzAr_Init(p); } - +Z7_NO_INLINE void SzArEx_Init(CSzArEx *p) { SzAr_Init(&p->db); @@ -140,23 +140,24 @@ void SzArEx_Init(CSzArEx *p) p->FileNameOffsets = NULL; p->FileNames = NULL; - SzBitUi32s_Init(&p->CRCs); - SzBitUi32s_Init(&p->Attribs); - // SzBitUi32s_Init(&p->Parents); - SzBitUi64s_Init(&p->MTime); - SzBitUi64s_Init(&p->CTime); + SzBitUi32s_INIT(&p->CRCs) + SzBitUi32s_INIT(&p->Attribs) + // SzBitUi32s_INIT(&p->Parents) + SzBitUi64s_INIT(&p->MTime) + SzBitUi64s_INIT(&p->CTime) } -void SzArEx_Free(CSzArEx *p, ISzAlloc *alloc) +Z7_NO_INLINE +void SzArEx_Free(CSzArEx *p, ISzAllocPtr alloc) { - IAlloc_Free(alloc, p->UnpackPositions); - IAlloc_Free(alloc, p->IsDirs); + ISzAlloc_Free(alloc, p->UnpackPositions); + ISzAlloc_Free(alloc, p->IsDirs); - IAlloc_Free(alloc, p->FolderToFile); - IAlloc_Free(alloc, p->FileToFolder); + ISzAlloc_Free(alloc, p->FolderToFile); + ISzAlloc_Free(alloc, p->FileToFolder); - IAlloc_Free(alloc, p->FileNameOffsets); - IAlloc_Free(alloc, p->FileNames); + ISzAlloc_Free(alloc, p->FileNameOffsets); + ISzAlloc_Free(alloc, p->FileNames); SzBitUi32s_Free(&p->CRCs, alloc); SzBitUi32s_Free(&p->Attribs, alloc); @@ -169,68 +170,63 @@ void SzArEx_Free(CSzArEx *p, ISzAlloc *alloc) } -static int TestSignatureCandidate(const Byte *testBytes) -{ - unsigned i; - for (i = 0; i < k7zSignatureSize; i++) - if (testBytes[i] != k7zSignature[i]) - return 0; - return 1; -} +#define SzData_CLEAR(p) { (p)->Data = NULL; (p)->Size = 0; } -#define SzData_Clear(p) { (p)->Data = NULL; (p)->Size = 0; } +#define SZ_READ_BYTE_SD_NOCHECK(_sd_, dest) \ + (_sd_)->Size--; dest = *(_sd_)->Data++; + +#define SZ_READ_BYTE_SD(_sd_, dest) \ + if ((_sd_)->Size == 0) return SZ_ERROR_ARCHIVE; \ + SZ_READ_BYTE_SD_NOCHECK(_sd_, dest) -#define SZ_READ_BYTE_SD(_sd_, dest) if ((_sd_)->Size == 0) return SZ_ERROR_ARCHIVE; (_sd_)->Size--; dest = *(_sd_)->Data++; #define SZ_READ_BYTE(dest) SZ_READ_BYTE_SD(sd, dest) -#define SZ_READ_BYTE_2(dest) if (sd.Size == 0) return SZ_ERROR_ARCHIVE; sd.Size--; dest = *sd.Data++; + +#define SZ_READ_BYTE_2(dest) \ + if (sd.Size == 0) return SZ_ERROR_ARCHIVE; \ + sd.Size--; dest = *sd.Data++; #define SKIP_DATA(sd, size) { sd->Size -= (size_t)(size); sd->Data += (size_t)(size); } #define SKIP_DATA2(sd, size) { sd.Size -= (size_t)(size); sd.Data += (size_t)(size); } -#define SZ_READ_32(dest) if (sd.Size < 4) return SZ_ERROR_ARCHIVE; \ - dest = GetUi32(sd.Data); SKIP_DATA2(sd, 4); - -static MY_NO_INLINE SRes ReadNumber(CSzData *sd, UInt64 *value) +static Z7_NO_INLINE SRes ReadNumber(CSzData *sd, UInt64 *value) { - Byte firstByte, mask; - unsigned i; - UInt32 v; + unsigned firstByte, mask, i, v; - SZ_READ_BYTE(firstByte); + SZ_READ_BYTE(firstByte) if ((firstByte & 0x80) == 0) { *value = firstByte; return SZ_OK; } - SZ_READ_BYTE(v); + SZ_READ_BYTE(v) if ((firstByte & 0x40) == 0) { *value = (((UInt32)firstByte & 0x3F) << 8) | v; return SZ_OK; } - SZ_READ_BYTE(mask); + SZ_READ_BYTE(mask) *value = v | ((UInt32)mask << 8); mask = 0x20; - for (i = 2; i < 8; i++) + for (i = 2 * 8; i < 8 * 8; i += 8) { - Byte b; + unsigned b; if ((firstByte & mask) == 0) { - UInt64 highPart = (unsigned)firstByte & (unsigned)(mask - 1); - *value |= (highPart << (8 * i)); + const UInt64 highPart = (unsigned)firstByte & (unsigned)(mask - 1); + *value |= (highPart << i); return SZ_OK; } - SZ_READ_BYTE(b); - *value |= ((UInt64)b << (8 * i)); mask >>= 1; + SZ_READ_BYTE(b) + *value |= ((UInt64)b << i); } return SZ_OK; } -static MY_NO_INLINE SRes SzReadNumber32(CSzData *sd, UInt32 *value) +static Z7_NO_INLINE SRes SzReadNumber32(CSzData *sd, UInt32 *value) { - Byte firstByte; + unsigned firstByte; UInt64 value64; if (sd->Size == 0) return SZ_ERROR_ARCHIVE; @@ -242,7 +238,7 @@ static MY_NO_INLINE SRes SzReadNumber32(CSzData *sd, UInt32 *value) sd->Size--; return SZ_OK; } - RINOK(ReadNumber(sd, &value64)); + RINOK(ReadNumber(sd, &value64)) if (value64 >= (UInt32)0x80000000 - 1) return SZ_ERROR_UNSUPPORTED; if (value64 >= ((UInt64)(1) << ((sizeof(size_t) - 1) * 8 + 4))) @@ -253,43 +249,37 @@ static MY_NO_INLINE SRes SzReadNumber32(CSzData *sd, UInt32 *value) #define ReadID(sd, value) ReadNumber(sd, value) +Z7_NO_INLINE static SRes SkipData(CSzData *sd) { UInt64 size; - RINOK(ReadNumber(sd, &size)); + RINOK(ReadNumber(sd, &size)) if (size > sd->Size) return SZ_ERROR_ARCHIVE; - SKIP_DATA(sd, size); + SKIP_DATA(sd, size) return SZ_OK; } +Z7_NO_INLINE static SRes WaitId(CSzData *sd, UInt32 id) { for (;;) { UInt64 type; - RINOK(ReadID(sd, &type)); + RINOK(ReadID(sd, &type)) if (type == id) return SZ_OK; if (type == k7zIdEnd) return SZ_ERROR_ARCHIVE; - RINOK(SkipData(sd)); + RINOK(SkipData(sd)) } } -static SRes RememberBitVector(CSzData *sd, UInt32 numItems, const Byte **v) -{ - UInt32 numBytes = (numItems + 7) >> 3; - if (numBytes > sd->Size) - return SZ_ERROR_ARCHIVE; - *v = sd->Data; - SKIP_DATA(sd, numBytes); - return SZ_OK; -} +Z7_NO_INLINE static UInt32 CountDefinedBits(const Byte *bits, UInt32 numItems) { - Byte b = 0; + unsigned b = 0; unsigned m = 0; UInt32 sum = 0; for (; numItems != 0; numItems--) @@ -300,223 +290,219 @@ static UInt32 CountDefinedBits(const Byte *bits, UInt32 numItems) m = 8; } m--; - sum += ((b >> m) & 1); + sum += (UInt32)((b >> m) & 1); } return sum; } -static MY_NO_INLINE SRes ReadBitVector(CSzData *sd, UInt32 numItems, Byte **v, ISzAlloc *alloc) +static Z7_NO_INLINE SRes ReadBitVector(CSzData *sd, size_t numItems, Byte **v, ISzAllocPtr alloc) { Byte allAreDefined; Byte *v2; - UInt32 numBytes = (numItems + 7) >> 3; + const size_t numBytes = (numItems + 7) >> 3; *v = NULL; - SZ_READ_BYTE(allAreDefined); + SZ_READ_BYTE(allAreDefined) if (numBytes == 0) return SZ_OK; if (allAreDefined == 0) { if (numBytes > sd->Size) return SZ_ERROR_ARCHIVE; - MY_ALLOC_AND_CPY(*v, numBytes, sd->Data, alloc); - SKIP_DATA(sd, numBytes); + MY_ALLOC_AND_CPY(*v, numBytes, sd->Data, alloc) + SKIP_DATA(sd, numBytes) return SZ_OK; } - MY_ALLOC(Byte, *v, numBytes, alloc); + MY_ALLOC(Byte, *v, numBytes, alloc) v2 = *v; memset(v2, 0xFF, (size_t)numBytes); { - unsigned numBits = (unsigned)numItems & 7; - if (numBits != 0) - v2[numBytes - 1] = (Byte)((((UInt32)1 << numBits) - 1) << (8 - numBits)); + const unsigned numBits = (unsigned)numItems & 7; + if (numBits) + v2[(size_t)numBytes - 1] = (Byte)(0xff00u >> numBits); } return SZ_OK; } -static MY_NO_INLINE SRes ReadUi32s(CSzData *sd2, UInt32 numItems, CSzBitUi32s *crcs, ISzAlloc *alloc) +static Z7_NO_INLINE SRes ReadUi32s(CSzData *sd2, size_t numItems, CSzBitUi32s *crcs, ISzAllocPtr alloc) { - UInt32 i; - CSzData sd; + size_t i; + const Byte *data; + size_t size; UInt32 *vals; const Byte *defs; - MY_ALLOC_ZE(UInt32, crcs->Vals, numItems, alloc); - sd = *sd2; + MY_ALLOC_ZE(UInt32, vals, numItems, alloc) + crcs->Vals = vals; defs = crcs->Defs; - vals = crcs->Vals; + data = sd2->Data; + size = sd2->Size; for (i = 0; i < numItems; i++) if (SzBitArray_Check(defs, i)) { - SZ_READ_32(vals[i]); + if (size < 4) + return SZ_ERROR_ARCHIVE; + size -= 4; + vals[i] = GetUi32(data); + data += 4; } else vals[i] = 0; - *sd2 = sd; + sd2->Data = data; + sd2->Size = size; return SZ_OK; } -static SRes ReadBitUi32s(CSzData *sd, UInt32 numItems, CSzBitUi32s *crcs, ISzAlloc *alloc) +static SRes ReadBitUi32s(CSzData *sd, size_t numItems, CSzBitUi32s *crcs, ISzAllocPtr alloc) { - SzBitUi32s_Free(crcs, alloc); - RINOK(ReadBitVector(sd, numItems, &crcs->Defs, alloc)); + if (crcs->Defs) + return SZ_ERROR_ARCHIVE; + RINOK(ReadBitVector(sd, numItems, &crcs->Defs, alloc)) return ReadUi32s(sd, numItems, crcs, alloc); } +Z7_NO_INLINE static SRes SkipBitUi32s(CSzData *sd, UInt32 numItems) { Byte allAreDefined; UInt32 numDefined = numItems; - SZ_READ_BYTE(allAreDefined); + SZ_READ_BYTE(allAreDefined) if (!allAreDefined) { - size_t numBytes = (numItems + 7) >> 3; + const size_t numBytes = (numItems + 7) >> 3; if (numBytes > sd->Size) return SZ_ERROR_ARCHIVE; numDefined = CountDefinedBits(sd->Data, numItems); - SKIP_DATA(sd, numBytes); + SKIP_DATA(sd, numBytes) } if (numDefined > (sd->Size >> 2)) return SZ_ERROR_ARCHIVE; - SKIP_DATA(sd, (size_t)numDefined * 4); + SKIP_DATA(sd, (size_t)numDefined * 4) return SZ_OK; } -static SRes ReadPackInfo(CSzAr *p, CSzData *sd, ISzAlloc *alloc) +static SRes ReadPackInfo(CSzAr *p, CSzData *sd, ISzAllocPtr alloc) { - RINOK(SzReadNumber32(sd, &p->NumPackStreams)); - - RINOK(WaitId(sd, k7zIdSize)); - MY_ALLOC(UInt64, p->PackPositions, (size_t)p->NumPackStreams + 1, alloc); + RINOK(SzReadNumber32(sd, &p->NumPackStreams)) + RINOK(WaitId(sd, k7zIdSize)) { - UInt64 sum = 0; - UInt32 i; - UInt32 numPackStreams = p->NumPackStreams; - for (i = 0; i < numPackStreams; i++) + UInt64 *packPositions; + UInt64 sum; + size_t num = (size_t)p->NumPackStreams + 1; + MY_ALLOC(UInt64, packPositions, num, alloc) + p->PackPositions = packPositions; + sum = 0; + for (;;) { UInt64 packSize; - p->PackPositions[i] = sum; - RINOK(ReadNumber(sd, &packSize)); + *packPositions++ = sum; + if (--num == 0) + break; + RINOK(ReadNumber(sd, &packSize)) sum += packSize; if (sum < packSize) return SZ_ERROR_ARCHIVE; } - p->PackPositions[i] = sum; } - for (;;) { UInt64 type; - RINOK(ReadID(sd, &type)); + RINOK(ReadID(sd, &type)) if (type == k7zIdEnd) return SZ_OK; if (type == k7zIdCRC) { /* CRC of packed streams is unused now */ - RINOK(SkipBitUi32s(sd, p->NumPackStreams)); + RINOK(SkipBitUi32s(sd, p->NumPackStreams)) continue; } - RINOK(SkipData(sd)); + RINOK(SkipData(sd)) } } -/* -static SRes SzReadSwitch(CSzData *sd) -{ - Byte external; - RINOK(SzReadByte(sd, &external)); - return (external == 0) ? SZ_OK: SZ_ERROR_UNSUPPORTED; -} -*/ #define k_NumCodersStreams_in_Folder_MAX (SZ_NUM_BONDS_IN_FOLDER_MAX + SZ_NUM_PACK_STREAMS_IN_FOLDER_MAX) SRes SzGetNextFolderItem(CSzFolder *f, CSzData *sd) { - UInt32 numCoders, i; - UInt32 numInStreams = 0; - const Byte *dataStart = sd->Data; + UInt32 numCoders, i, numInStreams = 0; + const Byte * const dataStart = sd->Data; - f->NumCoders = 0; - f->NumBonds = 0; - f->NumPackStreams = 0; + // f->NumCoders = 0; + // f->NumBonds = 0; + // f->NumPackStreams = 0; f->UnpackStream = 0; - RINOK(SzReadNumber32(sd, &numCoders)); + RINOK(SzReadNumber32(sd, &numCoders)) if (numCoders == 0 || numCoders > SZ_NUM_CODERS_IN_FOLDER_MAX) return SZ_ERROR_UNSUPPORTED; + f->NumCoders = numCoders; for (i = 0; i < numCoders; i++) { Byte mainByte; - CSzCoderInfo *coder = f->Coders + i; - unsigned idSize, j; - UInt64 id; - - SZ_READ_BYTE(mainByte); - if ((mainByte & 0xC0) != 0) - return SZ_ERROR_UNSUPPORTED; - - idSize = (unsigned)(mainByte & 0xF); - if (idSize > sizeof(id)) - return SZ_ERROR_UNSUPPORTED; - if (idSize > sd->Size) - return SZ_ERROR_ARCHIVE; - id = 0; - for (j = 0; j < idSize; j++) + CSzCoderInfo *coder; { - id = ((id << 8) | *sd->Data); - sd->Data++; + unsigned idSize, j; + UInt64 id; + const Byte *data; + + if (!sd->Size) + return SZ_ERROR_ARCHIVE; sd->Size--; + data = sd->Data; + mainByte = *data++; + if (mainByte & 0xC0) + return SZ_ERROR_UNSUPPORTED; + idSize = mainByte & 0xF; + if (idSize > sizeof(id)) + return SZ_ERROR_UNSUPPORTED; + if (idSize > sd->Size) + return SZ_ERROR_ARCHIVE; + sd->Size -= idSize; + id = 0; + for (j = 0; j < idSize; j++) + id = (id << 8) | *data++; + sd->Data = data; + if (id > (UInt32)0xFFFFFFFF) + return SZ_ERROR_UNSUPPORTED; + coder = f->Coders + i; + coder->MethodID = (UInt32)id; } - if (id > (UInt32)0xFFFFFFFF) - return SZ_ERROR_UNSUPPORTED; - coder->MethodID = (UInt32)id; coder->NumStreams = 1; coder->PropsOffset = 0; coder->PropsSize = 0; - if ((mainByte & 0x10) != 0) + if (mainByte & 0x10) { UInt32 numStreams; - - RINOK(SzReadNumber32(sd, &numStreams)); + RINOK(SzReadNumber32(sd, &numStreams)) if (numStreams > k_NumCodersStreams_in_Folder_MAX) return SZ_ERROR_UNSUPPORTED; coder->NumStreams = (Byte)numStreams; - - RINOK(SzReadNumber32(sd, &numStreams)); + RINOK(SzReadNumber32(sd, &numStreams)) if (numStreams != 1) return SZ_ERROR_UNSUPPORTED; } numInStreams += coder->NumStreams; - if (numInStreams > k_NumCodersStreams_in_Folder_MAX) return SZ_ERROR_UNSUPPORTED; - if ((mainByte & 0x20) != 0) + if (mainByte & 0x20) { - UInt32 propsSize = 0; - RINOK(SzReadNumber32(sd, &propsSize)); + UInt32 propsSize; + RINOK(SzReadNumber32(sd, &propsSize)) if (propsSize > sd->Size) return SZ_ERROR_ARCHIVE; if (propsSize >= 0x80) return SZ_ERROR_UNSUPPORTED; - coder->PropsOffset = sd->Data - dataStart; + coder->PropsOffset = (size_t)(sd->Data - dataStart); coder->PropsSize = (Byte)propsSize; sd->Data += (size_t)propsSize; sd->Size -= (size_t)propsSize; } } - /* - if (numInStreams == 1 && numCoders == 1) - { - f->NumPackStreams = 1; - f->PackStreams[0] = 0; - } - else - */ { Byte streamUsed[k_NumCodersStreams_in_Folder_MAX]; UInt32 numBonds, numPackStreams; @@ -533,96 +519,86 @@ SRes SzGetNextFolderItem(CSzFolder *f, CSzData *sd) return SZ_ERROR_UNSUPPORTED; f->NumPackStreams = numPackStreams; - for (i = 0; i < numInStreams; i++) + for (i = 0; i < Z7_ARRAY_SIZE(streamUsed); i++) // numInStreams streamUsed[i] = False; - if (numBonds != 0) + if (numBonds) { Byte coderUsed[SZ_NUM_CODERS_IN_FOLDER_MAX]; - - for (i = 0; i < numCoders; i++) + for (i = 0; i < Z7_ARRAY_SIZE(coderUsed); i++) // numCoders coderUsed[i] = False; for (i = 0; i < numBonds; i++) { CSzBond *bp = f->Bonds + i; - RINOK(SzReadNumber32(sd, &bp->InIndex)); + RINOK(SzReadNumber32(sd, &bp->InIndex)) if (bp->InIndex >= numInStreams || streamUsed[bp->InIndex]) return SZ_ERROR_ARCHIVE; streamUsed[bp->InIndex] = True; - RINOK(SzReadNumber32(sd, &bp->OutIndex)); + RINOK(SzReadNumber32(sd, &bp->OutIndex)) if (bp->OutIndex >= numCoders || coderUsed[bp->OutIndex]) return SZ_ERROR_ARCHIVE; coderUsed[bp->OutIndex] = True; } - for (i = 0; i < numCoders; i++) + for (i = 0;;) + { if (!coderUsed[i]) - { - f->UnpackStream = i; break; - } - - if (i == numCoders) - return SZ_ERROR_ARCHIVE; + if (++i == numCoders) + return SZ_ERROR_ARCHIVE; + } + f->UnpackStream = i; } if (numPackStreams == 1) { - for (i = 0; i < numInStreams; i++) + for (i = 0;; i++) + { + if (i == numInStreams) + return SZ_ERROR_ARCHIVE; if (!streamUsed[i]) break; - if (i == numInStreams) - return SZ_ERROR_ARCHIVE; + } f->PackStreams[0] = i; } else for (i = 0; i < numPackStreams; i++) { UInt32 index; - RINOK(SzReadNumber32(sd, &index)); + RINOK(SzReadNumber32(sd, &index)) if (index >= numInStreams || streamUsed[index]) return SZ_ERROR_ARCHIVE; streamUsed[index] = True; f->PackStreams[i] = index; } } - - f->NumCoders = numCoders; - return SZ_OK; } -static MY_NO_INLINE SRes SkipNumbers(CSzData *sd2, UInt32 num) +static SRes SkipNumbers(CSzData *sd2, UInt32 num) { - CSzData sd; - sd = *sd2; + const Byte *data = sd2->Data; + size_t size = sd2->Size; for (; num != 0; num--) { - Byte firstByte, mask; + unsigned firstByte; unsigned i; - SZ_READ_BYTE_2(firstByte); - if ((firstByte & 0x80) == 0) - continue; - if ((firstByte & 0x40) == 0) - { - if (sd.Size == 0) - return SZ_ERROR_ARCHIVE; - sd.Size--; - sd.Data++; - continue; - } - mask = 0x20; - for (i = 2; i < 8 && (firstByte & mask) != 0; i++) - mask >>= 1; - if (i > sd.Size) + if (size == 0) + return SZ_ERROR_ARCHIVE; + firstByte = *data; + for (i = 1; firstByte & 0x80; i++) + firstByte <<= 1; + if (size < i) return SZ_ERROR_ARCHIVE; - SKIP_DATA2(sd, i); + size -= i; + data += i; } - *sd2 = sd; + sd2->Data = data; + sd2->Size = size; return SZ_OK; } @@ -630,43 +606,38 @@ static MY_NO_INLINE SRes SkipNumbers(CSzData *sd2, UInt32 num) #define k_Scan_NumCoders_MAX 64 #define k_Scan_NumCodersStreams_in_Folder_MAX 64 - -static SRes ReadUnpackInfo(CSzAr *p, - CSzData *sd2, - UInt32 numFoldersMax, - const CBuf *tempBufs, UInt32 numTempBufs, - ISzAlloc *alloc) +static SRes ReadUnpackInfo(CSzAr *p, CSzData *sd2, const UInt32 numFoldersMax, + const CBuf *tempBufs, UInt32 numTempBufs, ISzAllocPtr alloc) { CSzData sd; - UInt32 fo, numFolders, numCodersOutStreams, packStreamIndex; const Byte *startBufPtr; Byte external; - RINOK(WaitId(sd2, k7zIdFolder)); + RINOK(WaitId(sd2, k7zIdFolder)) - RINOK(SzReadNumber32(sd2, &numFolders)); + RINOK(SzReadNumber32(sd2, &numFolders)) if (numFolders > numFoldersMax) return SZ_ERROR_UNSUPPORTED; p->NumFolders = numFolders; - SZ_READ_BYTE_SD(sd2, external); + SZ_READ_BYTE_SD(sd2, external) if (external == 0) sd = *sd2; else { UInt32 index; - RINOK(SzReadNumber32(sd2, &index)); + RINOK(SzReadNumber32(sd2, &index)) if (index >= numTempBufs) return SZ_ERROR_ARCHIVE; sd.Data = tempBufs[index].data; sd.Size = tempBufs[index].size; } - MY_ALLOC(size_t, p->FoCodersOffsets, (size_t)numFolders + 1, alloc); - MY_ALLOC(UInt32, p->FoStartPackStreamIndex, (size_t)numFolders + 1, alloc); - MY_ALLOC(UInt32, p->FoToCoderUnpackSizes, (size_t)numFolders + 1, alloc); - MY_ALLOC(Byte, p->FoToMainUnpackSizeIndex, (size_t)numFolders, alloc); + MY_ALLOC(size_t, p->FoCodersOffsets, (size_t)numFolders + 1, alloc) + MY_ALLOC(UInt32, p->FoStartPackStreamIndex, (size_t)numFolders + 1, alloc) + MY_ALLOC(UInt32, p->FoToCoderUnpackSizes, (size_t)numFolders + 1, alloc) + MY_ALLOC_ZE(Byte, p->FoToMainUnpackSizeIndex, (size_t)numFolders, alloc) startBufPtr = sd.Data; @@ -677,9 +648,9 @@ static SRes ReadUnpackInfo(CSzAr *p, { UInt32 numCoders, ci, numInStreams = 0; - p->FoCodersOffsets[fo] = sd.Data - startBufPtr; + p->FoCodersOffsets[fo] = (size_t)(sd.Data - startBufPtr); - RINOK(SzReadNumber32(&sd, &numCoders)); + RINOK(SzReadNumber32(&sd, &numCoders)) if (numCoders == 0 || numCoders > k_Scan_NumCoders_MAX) return SZ_ERROR_UNSUPPORTED; @@ -689,7 +660,7 @@ static SRes ReadUnpackInfo(CSzAr *p, unsigned idSize; UInt32 coderInStreams; - SZ_READ_BYTE_2(mainByte); + SZ_READ_BYTE_2(mainByte) if ((mainByte & 0xC0) != 0) return SZ_ERROR_UNSUPPORTED; idSize = (mainByte & 0xF); @@ -697,15 +668,15 @@ static SRes ReadUnpackInfo(CSzAr *p, return SZ_ERROR_UNSUPPORTED; if (idSize > sd.Size) return SZ_ERROR_ARCHIVE; - SKIP_DATA2(sd, idSize); + SKIP_DATA2(sd, idSize) coderInStreams = 1; if ((mainByte & 0x10) != 0) { UInt32 coderOutStreams; - RINOK(SzReadNumber32(&sd, &coderInStreams)); - RINOK(SzReadNumber32(&sd, &coderOutStreams)); + RINOK(SzReadNumber32(&sd, &coderInStreams)) + RINOK(SzReadNumber32(&sd, &coderOutStreams)) if (coderInStreams > k_Scan_NumCodersStreams_in_Folder_MAX || coderOutStreams != 1) return SZ_ERROR_UNSUPPORTED; } @@ -715,10 +686,10 @@ static SRes ReadUnpackInfo(CSzAr *p, if ((mainByte & 0x20) != 0) { UInt32 propsSize; - RINOK(SzReadNumber32(&sd, &propsSize)); + RINOK(SzReadNumber32(&sd, &propsSize)) if (propsSize > sd.Size) return SZ_ERROR_ARCHIVE; - SKIP_DATA2(sd, propsSize); + SKIP_DATA2(sd, propsSize) } } @@ -730,15 +701,12 @@ static SRes ReadUnpackInfo(CSzAr *p, { Byte streamUsed[k_Scan_NumCodersStreams_in_Folder_MAX]; Byte coderUsed[k_Scan_NumCoders_MAX]; - UInt32 i; - UInt32 numBonds = numCoders - 1; + const UInt32 numBonds = numCoders - 1; if (numInStreams < numBonds) return SZ_ERROR_ARCHIVE; - if (numInStreams > k_Scan_NumCodersStreams_in_Folder_MAX) return SZ_ERROR_UNSUPPORTED; - for (i = 0; i < numInStreams; i++) streamUsed[i] = False; for (i = 0; i < numCoders; i++) @@ -748,12 +716,12 @@ static SRes ReadUnpackInfo(CSzAr *p, { UInt32 index; - RINOK(SzReadNumber32(&sd, &index)); + RINOK(SzReadNumber32(&sd, &index)) if (index >= numInStreams || streamUsed[index]) return SZ_ERROR_ARCHIVE; streamUsed[index] = True; - RINOK(SzReadNumber32(&sd, &index)); + RINOK(SzReadNumber32(&sd, &index)) if (index >= numCoders || coderUsed[index]) return SZ_ERROR_ARCHIVE; coderUsed[index] = True; @@ -765,21 +733,20 @@ static SRes ReadUnpackInfo(CSzAr *p, for (i = 0; i < numPackStreams; i++) { UInt32 index; - RINOK(SzReadNumber32(&sd, &index)); + RINOK(SzReadNumber32(&sd, &index)) if (index >= numInStreams || streamUsed[index]) return SZ_ERROR_ARCHIVE; streamUsed[index] = True; } - - for (i = 0; i < numCoders; i++) + + for (i = 0;;) + { if (!coderUsed[i]) - { - indexOfMainStream = i; break; - } - - if (i == numCoders) - return SZ_ERROR_ARCHIVE; + if (++i == numCoders) + return SZ_ERROR_ARCHIVE; + } + indexOfMainStream = i; } p->FoStartPackStreamIndex[fo] = packStreamIndex; @@ -794,49 +761,55 @@ static SRes ReadUnpackInfo(CSzAr *p, } } + { + const size_t k_numCodersOutStreams_Limit = (size_t)1 << (sizeof(size_t) * 8 - 4); + if (numCodersOutStreams >= k_numCodersOutStreams_Limit) + return SZ_ERROR_UNSUPPORTED; + } p->FoToCoderUnpackSizes[fo] = numCodersOutStreams; - + p->FoStartPackStreamIndex[fo] = packStreamIndex; { - size_t dataSize = sd.Data - startBufPtr; - p->FoStartPackStreamIndex[fo] = packStreamIndex; + const size_t dataSize = (size_t)(sd.Data - startBufPtr); p->FoCodersOffsets[fo] = dataSize; - MY_ALLOC_ZE_AND_CPY(p->CodersData, dataSize, startBufPtr, alloc); + MY_ALLOC_ZE_AND_CPY(p->CodersData, dataSize, startBufPtr, alloc) } - if (external != 0) + if (external) { - if (sd.Size != 0) + if (sd.Size) return SZ_ERROR_ARCHIVE; sd = *sd2; } - RINOK(WaitId(&sd, k7zIdCodersUnpackSize)); - - MY_ALLOC_ZE(UInt64, p->CoderUnpackSizes, (size_t)numCodersOutStreams, alloc); + RINOK(WaitId(&sd, k7zIdCodersUnpackSize)) { - UInt32 i; - for (i = 0; i < numCodersOutStreams; i++) - { - RINOK(ReadNumber(&sd, p->CoderUnpackSizes + i)); - } + UInt64 *sizes; + MY_ALLOC_ZE(UInt64, sizes, (size_t)numCodersOutStreams, alloc) + p->CoderUnpackSizes = sizes; + if (numCodersOutStreams) + do + { + RINOK(ReadNumber(&sd, sizes)) + sizes++; + } + while (--numCodersOutStreams); } for (;;) { UInt64 type; - RINOK(ReadID(&sd, &type)); + RINOK(ReadID(&sd, &type)) if (type == k7zIdEnd) - { - *sd2 = sd; - return SZ_OK; - } + break; if (type == k7zIdCRC) { - RINOK(ReadBitUi32s(&sd, numFolders, &p->FolderCRCs, alloc)); + RINOK(ReadBitUi32s(&sd, numFolders, &p->FolderCRCs, alloc)) continue; } - RINOK(SkipData(&sd)); + RINOK(SkipData(&sd)) } + *sd2 = sd; + return SZ_OK; } @@ -856,49 +829,47 @@ typedef struct } CSubStreamInfo; -static SRes ReadSubStreamsInfo(CSzAr *p, CSzData *sd, CSubStreamInfo *ssi) +static SRes ReadSubStreamsInfo(const CSzAr *p, CSzData *sd, CSubStreamInfo *ssi) { UInt64 type = 0; - UInt32 numSubDigests = 0; - UInt32 numFolders = p->NumFolders; + const UInt32 numFolders = p->NumFolders; UInt32 numUnpackStreams = numFolders; + UInt32 numSubDigests = numFolders; UInt32 numUnpackSizesInData = 0; for (;;) { - RINOK(ReadID(sd, &type)); + RINOK(ReadID(sd, &type)) if (type == k7zIdNumUnpackStream) { UInt32 i; + if (ssi->sdNumSubStreams.Data) + return SZ_ERROR_UNSUPPORTED; ssi->sdNumSubStreams.Data = sd->Data; numUnpackStreams = 0; numSubDigests = 0; for (i = 0; i < numFolders; i++) { UInt32 numStreams; - RINOK(SzReadNumber32(sd, &numStreams)); - if (numUnpackStreams > numUnpackStreams + numStreams) - return SZ_ERROR_UNSUPPORTED; + RINOK(SzReadNumber32(sd, &numStreams)) numUnpackStreams += numStreams; - if (numStreams != 0) + if (numUnpackStreams < numStreams) + return SZ_ERROR_UNSUPPORTED; + if (numStreams) numUnpackSizesInData += (numStreams - 1); if (numStreams != 1 || !SzBitWithVals_Check(&p->FolderCRCs, i)) numSubDigests += numStreams; } - ssi->sdNumSubStreams.Size = sd->Data - ssi->sdNumSubStreams.Data; + ssi->sdNumSubStreams.Size = (size_t)(sd->Data - ssi->sdNumSubStreams.Data); continue; } if (type == k7zIdCRC || type == k7zIdSize || type == k7zIdEnd) break; - RINOK(SkipData(sd)); + RINOK(SkipData(sd)) } - if (!ssi->sdNumSubStreams.Data) - { - numSubDigests = numFolders; - if (p->FolderCRCs.Defs) - numSubDigests = numFolders - CountDefinedBits(p->FolderCRCs.Defs, numFolders); - } + if (!ssi->sdNumSubStreams.Data && p->FolderCRCs.Defs) + numSubDigests = numFolders - CountDefinedBits(p->FolderCRCs.Defs, numFolders); ssi->NumTotalSubStreams = numUnpackStreams; ssi->NumSubDigests = numSubDigests; @@ -906,9 +877,9 @@ static SRes ReadSubStreamsInfo(CSzAr *p, CSzData *sd, CSubStreamInfo *ssi) if (type == k7zIdSize) { ssi->sdSizes.Data = sd->Data; - RINOK(SkipNumbers(sd, numUnpackSizesInData)); - ssi->sdSizes.Size = sd->Data - ssi->sdSizes.Data; - RINOK(ReadID(sd, &type)); + RINOK(SkipNumbers(sd, numUnpackSizesInData)) + ssi->sdSizes.Size = (size_t)(sd->Data - ssi->sdSizes.Data); + RINOK(ReadID(sd, &type)) } for (;;) @@ -917,48 +888,56 @@ static SRes ReadSubStreamsInfo(CSzAr *p, CSzData *sd, CSubStreamInfo *ssi) return SZ_OK; if (type == k7zIdCRC) { + if (ssi->sdCRCs.Data) + return SZ_ERROR_UNSUPPORTED; ssi->sdCRCs.Data = sd->Data; - RINOK(SkipBitUi32s(sd, numSubDigests)); - ssi->sdCRCs.Size = sd->Data - ssi->sdCRCs.Data; + RINOK(SkipBitUi32s(sd, numSubDigests)) + ssi->sdCRCs.Size = (size_t)(sd->Data - ssi->sdCRCs.Data); } else { - RINOK(SkipData(sd)); + RINOK(SkipData(sd)) } - RINOK(ReadID(sd, &type)); + RINOK(ReadID(sd, &type)) } } + static SRes SzReadStreamsInfo(CSzAr *p, CSzData *sd, - UInt32 numFoldersMax, const CBuf *tempBufs, UInt32 numTempBufs, + const UInt32 numFoldersMax, + const CBuf * const tempBufs, const UInt32 numTempBufs, UInt64 *dataOffset, CSubStreamInfo *ssi, - ISzAlloc *alloc) + ISzAllocPtr alloc) { UInt64 type; - SzData_Clear(&ssi->sdSizes); - SzData_Clear(&ssi->sdCRCs); - SzData_Clear(&ssi->sdNumSubStreams); + SzData_CLEAR(&ssi->sdSizes) + SzData_CLEAR(&ssi->sdCRCs) + SzData_CLEAR(&ssi->sdNumSubStreams) *dataOffset = 0; - RINOK(ReadID(sd, &type)); + RINOK(ReadID(sd, &type)) if (type == k7zIdPackInfo) { - RINOK(ReadNumber(sd, dataOffset)); - RINOK(ReadPackInfo(p, sd, alloc)); - RINOK(ReadID(sd, &type)); + RINOK(ReadNumber(sd, dataOffset)) + if (*dataOffset > p->RangeLimit) + return SZ_ERROR_ARCHIVE; + RINOK(ReadPackInfo(p, sd, alloc)) + if (p->PackPositions[p->NumPackStreams] > p->RangeLimit - *dataOffset) + return SZ_ERROR_ARCHIVE; + RINOK(ReadID(sd, &type)) } if (type == k7zIdUnpackInfo) { - RINOK(ReadUnpackInfo(p, sd, numFoldersMax, tempBufs, numTempBufs, alloc)); - RINOK(ReadID(sd, &type)); + RINOK(ReadUnpackInfo(p, sd, numFoldersMax, tempBufs, numTempBufs, alloc)) + RINOK(ReadID(sd, &type)) } if (type == k7zIdSubStreamsInfo) { - RINOK(ReadSubStreamsInfo(p, sd, ssi)); - RINOK(ReadID(sd, &type)); + RINOK(ReadSubStreamsInfo(p, sd, ssi)) + RINOK(ReadID(sd, &type)) } else { @@ -969,20 +948,21 @@ static SRes SzReadStreamsInfo(CSzAr *p, return (type == k7zIdEnd ? SZ_OK : SZ_ERROR_UNSUPPORTED); } + static SRes SzReadAndDecodePackedStreams( - ILookInStream *inStream, + ILookInStreamPtr inStream, CSzData *sd, - CBuf *tempBufs, - UInt32 numFoldersMax, - UInt64 baseOffset, + CBuf * const tempBufs, + const UInt32 numFoldersMax, + const UInt64 baseOffset, CSzAr *p, - ISzAlloc *allocTemp) + ISzAllocPtr allocTemp) { UInt64 dataStartPos; UInt32 fo; CSubStreamInfo ssi; - RINOK(SzReadStreamsInfo(p, sd, numFoldersMax, NULL, 0, &dataStartPos, &ssi, allocTemp)); + RINOK(SzReadStreamsInfo(p, sd, numFoldersMax, NULL, 0, &dataStartPos, &ssi, allocTemp)) dataStartPos += baseOffset; if (p->NumFolders == 0) @@ -994,7 +974,7 @@ static SRes SzReadAndDecodePackedStreams( for (fo = 0; fo < p->NumFolders; fo++) { CBuf *tempBuf = tempBufs + fo; - UInt64 unpackSize = SzAr_GetFolderUnpackSize(p, fo); + const UInt64 unpackSize = SzAr_GetFolderUnpackSize(p, fo); if ((size_t)unpackSize != unpackSize) return SZ_ERROR_MEM; if (!Buf_Create(tempBuf, (size_t)unpackSize, allocTemp)) @@ -1004,86 +984,89 @@ static SRes SzReadAndDecodePackedStreams( for (fo = 0; fo < p->NumFolders; fo++) { const CBuf *tempBuf = tempBufs + fo; - RINOK(LookInStream_SeekTo(inStream, dataStartPos)); - RINOK(SzAr_DecodeFolder(p, fo, inStream, dataStartPos, tempBuf->data, tempBuf->size, allocTemp)); + RINOK(LookInStream_SeekTo(inStream, dataStartPos)) + RINOK(SzAr_DecodeFolder(p, fo, inStream, dataStartPos, tempBuf->data, tempBuf->size, allocTemp)) } return SZ_OK; } + +// (size & 1) == 0 +// (data) is aligned for 2-bytes static SRes SzReadFileNames(const Byte *data, size_t size, UInt32 numFiles, size_t *offsets) { - size_t pos = 0; + const Byte *p, *lim; *offsets++ = 0; if (numFiles == 0) return (size == 0) ? SZ_OK : SZ_ERROR_ARCHIVE; if (size < 2) return SZ_ERROR_ARCHIVE; - if (data[size - 2] != 0 || data[size - 1] != 0) + lim = data + size; + if (*(const UInt16 *)(const void *)(lim - 2)) return SZ_ERROR_ARCHIVE; + p = data; do { - const Byte *p; - if (pos == size) + if (p >= lim) return SZ_ERROR_ARCHIVE; - for (p = data + pos; - #ifdef _WIN32 - *(const UInt16 *)p != 0 - #else - p[0] != 0 || p[1] != 0 - #endif - ; p += 2); - pos = p - data + 2; - *offsets++ = (pos >> 1); + for (; *(const UInt16 *)(const void *)p; p += 2); + p += 2; + *offsets++ = (size_t)(p - data) >> 1; } while (--numFiles); - return (pos == size) ? SZ_OK : SZ_ERROR_ARCHIVE; + return (p == lim) ? SZ_OK : SZ_ERROR_ARCHIVE; } -static MY_NO_INLINE SRes ReadTime(CSzBitUi64s *p, UInt32 num, - CSzData *sd2, - const CBuf *tempBufs, UInt32 numTempBufs, - ISzAlloc *alloc) + +static SRes ReadTime(CSzBitUi64s *p, size_t num, CSzData *sd2, + const CBuf *tempBufs, UInt32 numTempBufs, ISzAllocPtr alloc) { - CSzData sd; - UInt32 i; + const Byte *data; + size_t size; + size_t i; CNtfsFileTime *vals; Byte *defs; Byte external; - RINOK(ReadBitVector(sd2, num, &p->Defs, alloc)); + if (p->Defs) + return SZ_ERROR_ARCHIVE; + RINOK(ReadBitVector(sd2, num, &p->Defs, alloc)) - SZ_READ_BYTE_SD(sd2, external); + SZ_READ_BYTE_SD(sd2, external) if (external == 0) - sd = *sd2; + { + data = sd2->Data; + size = sd2->Size; + } else { UInt32 index; - RINOK(SzReadNumber32(sd2, &index)); - if (index >= numTempBufs) + RINOK(SzReadNumber32(sd2, &index)) + if (index >= numTempBufs || sd2->Size) return SZ_ERROR_ARCHIVE; - sd.Data = tempBufs[index].data; - sd.Size = tempBufs[index].size; + data = tempBufs[index].data; + size = tempBufs[index].size; } - MY_ALLOC_ZE(CNtfsFileTime, p->Vals, num, alloc); + MY_ALLOC_ZE(CNtfsFileTime, p->Vals, num, alloc) vals = p->Vals; defs = p->Defs; for (i = 0; i < num; i++) if (SzBitArray_Check(defs, i)) { - if (sd.Size < 8) + if (size < 8) return SZ_ERROR_ARCHIVE; - vals[i].Low = GetUi32(sd.Data); - vals[i].High = GetUi32(sd.Data + 4); - SKIP_DATA2(sd, 8); + size -= 8; + vals[i].Low = GetUi32(data); + vals[i].High = GetUi32(data + 4); + data += 8; } else vals[i].High = vals[i].Low = 0; - if (external == 0) - *sd2 = sd; - + if (size) + return SZ_ERROR_ARCHIVE; return SZ_OK; } @@ -1091,40 +1074,35 @@ static MY_NO_INLINE SRes ReadTime(CSzBitUi64s *p, UInt32 num, #define NUM_ADDITIONAL_STREAMS_MAX 8 -static SRes SzReadHeader2( - CSzArEx *p, /* allocMain */ - CSzData *sd, - ILookInStream *inStream, - CBuf *tempBufs, UInt32 *numTempBufs, - ISzAlloc *allocMain, - ISzAlloc *allocTemp - ) +static SRes SzReadHeader2(CSzArEx *p, CSzData *sd, ILookInStreamPtr inStream, + CBuf * const tempBufs, ISzAllocPtr allocMain, ISzAllocPtr allocTemp) { CSubStreamInfo ssi; + UInt32 numTempBufs = 0; { UInt64 type; - SzData_Clear(&ssi.sdSizes); - SzData_Clear(&ssi.sdCRCs); - SzData_Clear(&ssi.sdNumSubStreams); + SzData_CLEAR(&ssi.sdSizes) + SzData_CLEAR(&ssi.sdCRCs) + SzData_CLEAR(&ssi.sdNumSubStreams) ssi.NumSubDigests = 0; ssi.NumTotalSubStreams = 0; - RINOK(ReadID(sd, &type)); + RINOK(ReadID(sd, &type)) if (type == k7zIdArchiveProperties) { for (;;) { UInt64 type2; - RINOK(ReadID(sd, &type2)); + RINOK(ReadID(sd, &type2)) if (type2 == k7zIdEnd) break; - RINOK(SkipData(sd)); + RINOK(SkipData(sd)) } - RINOK(ReadID(sd, &type)); + RINOK(ReadID(sd, &type)) } if (type == k7zIdAdditionalStreamsInfo) @@ -1133,144 +1111,144 @@ static SRes SzReadHeader2( SRes res; SzAr_Init(&tempAr); + tempAr.RangeLimit = p->db.RangeLimit; + res = SzReadAndDecodePackedStreams(inStream, sd, tempBufs, NUM_ADDITIONAL_STREAMS_MAX, p->startPosAfterHeader, &tempAr, allocTemp); - *numTempBufs = tempAr.NumFolders; + numTempBufs = tempAr.NumFolders; SzAr_Free(&tempAr, allocTemp); if (res != SZ_OK) return res; - RINOK(ReadID(sd, &type)); + RINOK(ReadID(sd, &type)) } if (type == k7zIdMainStreamsInfo) { - RINOK(SzReadStreamsInfo(&p->db, sd, (UInt32)1 << 30, tempBufs, *numTempBufs, - &p->dataPos, &ssi, allocMain)); + static const UInt32 k_numFoldersMax = (UInt32)1 << 30; + RINOK(SzReadStreamsInfo(&p->db, sd, k_numFoldersMax, tempBufs, numTempBufs, + &p->dataPos, &ssi, allocMain)) p->dataPos += p->startPosAfterHeader; - RINOK(ReadID(sd, &type)); + RINOK(ReadID(sd, &type)) } if (type == k7zIdEnd) - { return SZ_OK; - } - if (type != k7zIdFilesInfo) return SZ_ERROR_ARCHIVE; } { - UInt32 numFiles = 0; - UInt32 numEmptyStreams = 0; + UInt32 numFiles, numEmptyStreams = 0; const Byte *emptyStreams = NULL; const Byte *emptyFiles = NULL; - RINOK(SzReadNumber32(sd, &numFiles)); + RINOK(SzReadNumber32(sd, &numFiles)) p->NumFiles = numFiles; for (;;) { + CSzData sdSwitch; UInt64 type; - UInt64 size; - RINOK(ReadID(sd, &type)); + RINOK(ReadID(sd, &type)) if (type == k7zIdEnd) break; - RINOK(ReadNumber(sd, &size)); - if (size > sd->Size) - return SZ_ERROR_ARCHIVE; - - if (type >= ((UInt32)1 << 8)) { - SKIP_DATA(sd, size); + UInt64 size; + RINOK(ReadNumber(sd, &size)) + if (size > sd->Size) + return SZ_ERROR_ARCHIVE; + sdSwitch.Data = sd->Data; + sdSwitch.Size = (size_t)size; + SKIP_DATA(sd, size) } - else switch ((unsigned)type) + if (type >= 64) + continue; + + switch ((unsigned)type) { + case k7zIdEmptyStream: + { + if (emptyStreams || emptyFiles) + return SZ_ERROR_ARCHIVE; + if ((numFiles + 7) >> 3 != sdSwitch.Size) + return SZ_ERROR_ARCHIVE; + emptyStreams = sdSwitch.Data; + numEmptyStreams = CountDefinedBits(emptyStreams, numFiles); + break; + } + + case k7zIdEmptyFile: + { + if (emptyFiles) + return SZ_ERROR_ARCHIVE; + if ((numEmptyStreams + 7) >> 3 != sdSwitch.Size) + return SZ_ERROR_ARCHIVE; + emptyFiles = sdSwitch.Data; + break; + } + case k7zIdName: { size_t namesSize; const Byte *namesData; Byte external; - - SZ_READ_BYTE(external); + if (p->FileNameOffsets) + return SZ_ERROR_ARCHIVE; + SZ_READ_BYTE_SD(&sdSwitch, external) if (external == 0) { - namesSize = (size_t)size - 1; - namesData = sd->Data; + namesData = sdSwitch.Data; + namesSize = sdSwitch.Size; } else { UInt32 index; - RINOK(SzReadNumber32(sd, &index)); - if (index >= *numTempBufs) + RINOK(SzReadNumber32(&sdSwitch, &index)) + if (index >= numTempBufs || sdSwitch.Size) return SZ_ERROR_ARCHIVE; - namesData = (tempBufs)[index].data; - namesSize = (tempBufs)[index].size; + namesData = tempBufs[index].data; + namesSize = tempBufs[index].size; } - - if ((namesSize & 1) != 0) + if (namesSize & 1) return SZ_ERROR_ARCHIVE; - MY_ALLOC(size_t, p->FileNameOffsets, numFiles + 1, allocMain); - MY_ALLOC_ZE_AND_CPY(p->FileNames, namesSize, namesData, allocMain); + MY_ALLOC(size_t, p->FileNameOffsets, numFiles + 1, allocMain) + MY_ALLOC_ZE_AND_CPY(p->FileNames, namesSize, namesData, allocMain) RINOK(SzReadFileNames(p->FileNames, namesSize, numFiles, p->FileNameOffsets)) - if (external == 0) - { - SKIP_DATA(sd, namesSize); - } - break; - } - case k7zIdEmptyStream: - { - RINOK(RememberBitVector(sd, numFiles, &emptyStreams)); - numEmptyStreams = CountDefinedBits(emptyStreams, numFiles); - emptyFiles = NULL; - break; - } - case k7zIdEmptyFile: - { - RINOK(RememberBitVector(sd, numEmptyStreams, &emptyFiles)); break; } + case k7zIdWinAttrib: { Byte external; - CSzData sdSwitch; - CSzData *sdPtr; - SzBitUi32s_Free(&p->Attribs, allocMain); - RINOK(ReadBitVector(sd, numFiles, &p->Attribs.Defs, allocMain)); - - SZ_READ_BYTE(external); - if (external == 0) - sdPtr = sd; - else + if (p->Attribs.Defs) + return SZ_ERROR_ARCHIVE; + RINOK(ReadBitVector(&sdSwitch, numFiles, &p->Attribs.Defs, allocMain)) + SZ_READ_BYTE_SD(&sdSwitch, external) + if (external) { UInt32 index; - RINOK(SzReadNumber32(sd, &index)); - if (index >= *numTempBufs) + RINOK(SzReadNumber32(&sdSwitch, &index)) + if (index >= numTempBufs || sdSwitch.Size) return SZ_ERROR_ARCHIVE; - sdSwitch.Data = (tempBufs)[index].data; - sdSwitch.Size = (tempBufs)[index].size; - sdPtr = &sdSwitch; + sdSwitch.Data = tempBufs[index].data; + sdSwitch.Size = tempBufs[index].size; } - RINOK(ReadUi32s(sdPtr, numFiles, &p->Attribs, allocMain)); + RINOK(ReadUi32s(&sdSwitch, numFiles, &p->Attribs, allocMain)) + if (sdSwitch.Size) + return SZ_ERROR_ARCHIVE; break; } - /* - case k7zParent: + + case k7zIdMTime: + case k7zIdCTime: { - SzBitUi32s_Free(&p->Parents, allocMain); - RINOK(ReadBitVector(sd, numFiles, &p->Parents.Defs, allocMain)); - RINOK(SzReadSwitch(sd)); - RINOK(ReadUi32s(sd, numFiles, &p->Parents, allocMain)); + RINOK(ReadTime((unsigned)type == k7zIdMTime ? &p->MTime: &p->CTime, + numFiles, &sdSwitch, tempBufs, numTempBufs, allocMain)) break; } - */ - case k7zIdMTime: RINOK(ReadTime(&p->MTime, numFiles, sd, tempBufs, *numTempBufs, allocMain)); break; - case k7zIdCTime: RINOK(ReadTime(&p->CTime, numFiles, sd, tempBufs, *numTempBufs, allocMain)); break; - default: - { - SKIP_DATA(sd, size); - } + + default: break; } } @@ -1280,10 +1258,10 @@ static SRes SzReadHeader2( for (;;) { UInt64 type; - RINOK(ReadID(sd, &type)); + RINOK(ReadID(sd, &type)) if (type == k7zIdEnd) break; - RINOK(SkipData(sd)); + RINOK(SkipData(sd)) } { @@ -1295,40 +1273,37 @@ static SRes SzReadHeader2( UInt64 unpackPos = 0; const Byte *digestsDefs = NULL; const Byte *digestsVals = NULL; - UInt32 digestsValsIndex = 0; - UInt32 digestIndex; - Byte allDigestsDefined = 0; + UInt32 digestIndex = 0; Byte isDirMask = 0; Byte crcMask = 0; Byte mask = 0x80; - MY_ALLOC(UInt32, p->FolderToFile, p->db.NumFolders + 1, allocMain); - MY_ALLOC_ZE(UInt32, p->FileToFolder, p->NumFiles, allocMain); - MY_ALLOC(UInt64, p->UnpackPositions, p->NumFiles + 1, allocMain); - MY_ALLOC_ZE(Byte, p->IsDirs, (p->NumFiles + 7) >> 3, allocMain); + MY_ALLOC(UInt32, p->FolderToFile, p->db.NumFolders + 1, allocMain) + MY_ALLOC_ZE(UInt32, p->FileToFolder, p->NumFiles, allocMain) + MY_ALLOC(UInt64, p->UnpackPositions, p->NumFiles + 1, allocMain) + MY_ALLOC_ZE(Byte, p->IsDirs, (p->NumFiles + 7) >> 3, allocMain) - RINOK(SzBitUi32s_Alloc(&p->CRCs, p->NumFiles, allocMain)); + RINOK(SzBitUi32s_Alloc(&p->CRCs, p->NumFiles, allocMain)) if (ssi.sdCRCs.Size != 0) { - SZ_READ_BYTE_SD(&ssi.sdCRCs, allDigestsDefined); + Byte allDigestsDefined = 0; + SZ_READ_BYTE_SD_NOCHECK(&ssi.sdCRCs, allDigestsDefined) if (allDigestsDefined) digestsVals = ssi.sdCRCs.Data; else { - size_t numBytes = (ssi.NumSubDigests + 7) >> 3; + const size_t numBytes = (ssi.NumSubDigests + 7) >> 3; digestsDefs = ssi.sdCRCs.Data; digestsVals = digestsDefs + numBytes; } } - digestIndex = 0; - for (i = 0; i < numFiles; i++, mask >>= 1) { if (mask == 0) { - UInt32 byteIndex = (i - 1) >> 3; + const UInt32 byteIndex = (i - 1) >> 3; p->IsDirs[byteIndex] = isDirMask; p->CRCs.Defs[byteIndex] = crcMask; isDirMask = 0; @@ -1366,18 +1341,17 @@ static SRes SzReadHeader2( numSubStreams = 1; if (ssi.sdNumSubStreams.Data) { - RINOK(SzReadNumber32(&ssi.sdNumSubStreams, &numSubStreams)); + RINOK(SzReadNumber32(&ssi.sdNumSubStreams, &numSubStreams)) } remSubStreams = numSubStreams; if (numSubStreams != 0) break; { - UInt64 folderUnpackSize = SzAr_GetFolderUnpackSize(&p->db, folderIndex); + const UInt64 folderUnpackSize = SzAr_GetFolderUnpackSize(&p->db, folderIndex); unpackPos += folderUnpackSize; if (unpackPos < folderUnpackSize) return SZ_ERROR_ARCHIVE; } - folderIndex++; } } @@ -1389,47 +1363,44 @@ static SRes SzReadHeader2( if (--remSubStreams == 0) { - UInt64 folderUnpackSize = SzAr_GetFolderUnpackSize(&p->db, folderIndex); - UInt64 startFolderUnpackPos = p->UnpackPositions[p->FolderToFile[folderIndex]]; + const UInt64 folderUnpackSize = SzAr_GetFolderUnpackSize(&p->db, folderIndex); + const UInt64 startFolderUnpackPos = p->UnpackPositions[p->FolderToFile[folderIndex]]; if (folderUnpackSize < unpackPos - startFolderUnpackPos) return SZ_ERROR_ARCHIVE; unpackPos = startFolderUnpackPos + folderUnpackSize; if (unpackPos < folderUnpackSize) return SZ_ERROR_ARCHIVE; - if (numSubStreams == 1 && SzBitWithVals_Check(&p->db.FolderCRCs, i)) + if (numSubStreams == 1 && SzBitWithVals_Check(&p->db.FolderCRCs, folderIndex)) { p->CRCs.Vals[i] = p->db.FolderCRCs.Vals[folderIndex]; crcMask |= mask; } - else if (allDigestsDefined || (digestsDefs && SzBitArray_Check(digestsDefs, digestIndex))) - { - p->CRCs.Vals[i] = GetUi32(digestsVals + (size_t)digestsValsIndex * 4); - digestsValsIndex++; - crcMask |= mask; - } - folderIndex++; } else { UInt64 v; - RINOK(ReadNumber(&ssi.sdSizes, &v)); + RINOK(ReadNumber(&ssi.sdSizes, &v)) unpackPos += v; if (unpackPos < v) return SZ_ERROR_ARCHIVE; - if (allDigestsDefined || (digestsDefs && SzBitArray_Check(digestsDefs, digestIndex))) + } + if ((crcMask & mask) == 0 && digestsVals) + { + if (!digestsDefs || SzBitArray_Check(digestsDefs, digestIndex)) { - p->CRCs.Vals[i] = GetUi32(digestsVals + (size_t)digestsValsIndex * 4); - digestsValsIndex++; + p->CRCs.Vals[i] = GetUi32(digestsVals); + digestsVals += 4; crcMask |= mask; } + digestIndex++; } } if (mask != 0x80) { - UInt32 byteIndex = (i - 1) >> 3; + const UInt32 byteIndex = (i - 1) >> 3; p->IsDirs[byteIndex] = isDirMask; p->CRCs.Defs[byteIndex] = crcMask; } @@ -1446,7 +1417,7 @@ static SRes SzReadHeader2( break; if (!ssi.sdNumSubStreams.Data) return SZ_ERROR_ARCHIVE; - RINOK(SzReadNumber32(&ssi.sdNumSubStreams, &numSubStreams)); + RINOK(SzReadNumber32(&ssi.sdNumSubStreams, &numSubStreams)) if (numSubStreams != 0) return SZ_ERROR_ARCHIVE; /* @@ -1471,85 +1442,80 @@ static SRes SzReadHeader2( static SRes SzReadHeader( CSzArEx *p, CSzData *sd, - ILookInStream *inStream, - ISzAlloc *allocMain, - ISzAlloc *allocTemp) + ILookInStreamPtr inStream, + ISzAllocPtr allocMain, + ISzAllocPtr allocTemp) { UInt32 i; - UInt32 numTempBufs = 0; SRes res; CBuf tempBufs[NUM_ADDITIONAL_STREAMS_MAX]; for (i = 0; i < NUM_ADDITIONAL_STREAMS_MAX; i++) Buf_Init(tempBufs + i); - res = SzReadHeader2(p, sd, inStream, - tempBufs, &numTempBufs, - allocMain, allocTemp); + res = SzReadHeader2(p, sd, inStream, tempBufs, allocMain, allocTemp); for (i = 0; i < NUM_ADDITIONAL_STREAMS_MAX; i++) Buf_Free(tempBufs + i, allocTemp); - RINOK(res); - - if (sd->Size != 0) - return SZ_ERROR_FAIL; - + if (res == SZ_OK && sd->Size != 0) + return SZ_ERROR_ARCHIVE; return res; } static SRes SzArEx_Open2( CSzArEx *p, - ILookInStream *inStream, - ISzAlloc *allocMain, - ISzAlloc *allocTemp) + ILookInStreamPtr inStream, + ISzAllocPtr allocMain, + ISzAllocPtr allocTemp) { Byte header[k7zStartHeaderSize]; - Int64 startArcPos; + Int64 startPosAfterHeader; UInt64 nextHeaderOffset, nextHeaderSize; size_t nextHeaderSizeT; UInt32 nextHeaderCRC; CBuf buf; SRes res; - startArcPos = 0; - RINOK(inStream->Seek(inStream, &startArcPos, SZ_SEEK_CUR)); - - RINOK(LookInStream_Read2(inStream, header, k7zStartHeaderSize, SZ_ERROR_NO_ARCHIVE)); - - if (!TestSignatureCandidate(header)) - return SZ_ERROR_NO_ARCHIVE; - if (header[6] != k7zMajorVersion) - return SZ_ERROR_UNSUPPORTED; + RINOK(LookInStream_Read2(inStream, header, k7zStartHeaderSize, SZ_ERROR_NO_ARCHIVE)) + startPosAfterHeader = 0; // k7zStartHeaderSize + RINOK(ILookInStream_Seek(inStream, &startPosAfterHeader, SZ_SEEK_CUR)) + p->startPosAfterHeader = (UInt64)startPosAfterHeader; + { + unsigned i; + for (i = 0; i < k7zSignatureSize; i++) + if (header[i] != k7zSignature[i]) + return SZ_ERROR_NO_ARCHIVE; + if (header[6] != 0) // k7zMajorVersion + return SZ_ERROR_UNSUPPORTED; + } + if (CrcCalc(header + 12, 20) != GetUi32(header + 8)) + return SZ_ERROR_CRC; nextHeaderOffset = GetUi64(header + 12); nextHeaderSize = GetUi64(header + 20); nextHeaderCRC = GetUi32(header + 28); - p->startPosAfterHeader = startArcPos + k7zStartHeaderSize; - - if (CrcCalc(header + 12, 20) != GetUi32(header + 8)) - return SZ_ERROR_CRC; - + p->db.RangeLimit = nextHeaderOffset; + if (nextHeaderOffset >= (UInt64)1 << 62 || + nextHeaderSize >= (UInt64)1 << 48) + return SZ_ERROR_NO_ARCHIVE; nextHeaderSizeT = (size_t)nextHeaderSize; if (nextHeaderSizeT != nextHeaderSize) return SZ_ERROR_MEM; if (nextHeaderSizeT == 0) + { + if (nextHeaderOffset != 0 || nextHeaderCRC != 0) + return SZ_ERROR_NO_ARCHIVE; return SZ_OK; - if (nextHeaderOffset > nextHeaderOffset + nextHeaderSize || - nextHeaderOffset > nextHeaderOffset + nextHeaderSize + k7zStartHeaderSize) - return SZ_ERROR_NO_ARCHIVE; - + } { Int64 pos = 0; - RINOK(inStream->Seek(inStream, &pos, SZ_SEEK_END)); - if ((UInt64)pos < startArcPos + nextHeaderOffset || - (UInt64)pos < startArcPos + k7zStartHeaderSize + nextHeaderOffset || - (UInt64)pos < startArcPos + k7zStartHeaderSize + nextHeaderOffset + nextHeaderSize) + RINOK(ILookInStream_Seek(inStream, &pos, SZ_SEEK_END)) + if ((UInt64)(pos - startPosAfterHeader) < nextHeaderOffset + nextHeaderSize) return SZ_ERROR_INPUT_EOF; } - - RINOK(LookInStream_SeekTo(inStream, startArcPos + k7zStartHeaderSize + nextHeaderOffset)); + RINOK(LookInStream_SeekTo(inStream, (UInt64)startPosAfterHeader + nextHeaderOffset)) if (!Buf_Create(&buf, nextHeaderSizeT, allocTemp)) return SZ_ERROR_MEM; @@ -1575,6 +1541,8 @@ static SRes SzArEx_Open2( Buf_Init(&tempBuf); SzAr_Init(&tempAr); + tempAr.RangeLimit = p->db.RangeLimit; + res = SzReadAndDecodePackedStreams(inStream, &sd, &tempBuf, 1, p->startPosAfterHeader, &tempAr, allocTemp); SzAr_Free(&tempAr, allocTemp); @@ -1622,10 +1590,10 @@ static SRes SzArEx_Open2( } -SRes SzArEx_Open(CSzArEx *p, ILookInStream *inStream, - ISzAlloc *allocMain, ISzAlloc *allocTemp) +SRes SzArEx_Open(CSzArEx *p, ILookInStreamPtr inStream, + ISzAllocPtr allocMain, ISzAllocPtr allocTemp) { - SRes res = SzArEx_Open2(p, inStream, allocMain, allocTemp); + const SRes res = SzArEx_Open2(p, inStream, allocMain, allocTemp); if (res != SZ_OK) SzArEx_Free(p, allocMain); return res; @@ -1634,17 +1602,17 @@ SRes SzArEx_Open(CSzArEx *p, ILookInStream *inStream, SRes SzArEx_Extract( const CSzArEx *p, - ILookInStream *inStream, + ILookInStreamPtr inStream, UInt32 fileIndex, UInt32 *blockIndex, Byte **tempBuf, size_t *outBufferSize, size_t *offset, size_t *outSizeProcessed, - ISzAlloc *allocMain, - ISzAlloc *allocTemp) + ISzAllocPtr allocMain, + ISzAllocPtr allocTemp) { - UInt32 folderIndex = p->FileToFolder[fileIndex]; + const UInt32 folderIndex = p->FileToFolder[fileIndex]; SRes res = SZ_OK; *offset = 0; @@ -1652,7 +1620,7 @@ SRes SzArEx_Extract( if (folderIndex == (UInt32)-1) { - IAlloc_Free(allocMain, *tempBuf); + ISzAlloc_Free(allocMain, *tempBuf); *blockIndex = folderIndex; *tempBuf = NULL; *outBufferSize = 0; @@ -1661,18 +1629,18 @@ SRes SzArEx_Extract( if (*tempBuf == NULL || *blockIndex != folderIndex) { - UInt64 unpackSizeSpec = SzAr_GetFolderUnpackSize(&p->db, folderIndex); + const UInt64 unpackSizeSpec = SzAr_GetFolderUnpackSize(&p->db, folderIndex); /* UInt64 unpackSizeSpec = - p->UnpackPositions[p->FolderToFile[folderIndex + 1]] - + p->UnpackPositions[p->FolderToFile[(size_t)folderIndex + 1]] - p->UnpackPositions[p->FolderToFile[folderIndex]]; */ - size_t unpackSize = (size_t)unpackSizeSpec; + const size_t unpackSize = (size_t)unpackSizeSpec; if (unpackSize != unpackSizeSpec) return SZ_ERROR_MEM; *blockIndex = folderIndex; - IAlloc_Free(allocMain, *tempBuf); + ISzAlloc_Free(allocMain, *tempBuf); *tempBuf = NULL; if (res == SZ_OK) @@ -1680,7 +1648,7 @@ SRes SzArEx_Extract( *outBufferSize = unpackSize; if (unpackSize != 0) { - *tempBuf = (Byte *)IAlloc_Alloc(allocMain, unpackSize); + *tempBuf = (Byte *)ISzAlloc_Alloc(allocMain, unpackSize); if (*tempBuf == NULL) res = SZ_ERROR_MEM; } @@ -1695,9 +1663,9 @@ SRes SzArEx_Extract( if (res == SZ_OK) { - UInt64 unpackPos = p->UnpackPositions[fileIndex]; + const UInt64 unpackPos = p->UnpackPositions[fileIndex]; *offset = (size_t)(unpackPos - p->UnpackPositions[p->FolderToFile[folderIndex]]); - *outSizeProcessed = (size_t)(p->UnpackPositions[fileIndex + 1] - unpackPos); + *outSizeProcessed = (size_t)(p->UnpackPositions[(size_t)fileIndex + 1] - unpackPos); if (*offset + *outSizeProcessed > *outBufferSize) return SZ_ERROR_FAIL; if (SzBitWithVals_Check(&p->CRCs, fileIndex)) @@ -1711,61 +1679,23 @@ SRes SzArEx_Extract( size_t SzArEx_GetFileNameUtf16(const CSzArEx *p, size_t fileIndex, UInt16 *dest) { - size_t offs = p->FileNameOffsets[fileIndex]; - size_t len = p->FileNameOffsets[fileIndex + 1] - offs; - if (dest != 0) + const size_t * const offsets = p->FileNameOffsets; + if (!offsets) { - size_t i; - const Byte *src = p->FileNames + offs * 2; - for (i = 0; i < len; i++) - dest[i] = GetUi16(src + i * 2); - } - return len; -} - -/* -size_t SzArEx_GetFullNameLen(const CSzArEx *p, size_t fileIndex) -{ - size_t len; - if (!p->FileNameOffsets) + if (dest) + *dest = 0; return 1; - len = 0; - for (;;) - { - UInt32 parent = (UInt32)(Int32)-1; - len += p->FileNameOffsets[fileIndex + 1] - p->FileNameOffsets[fileIndex]; - if SzBitWithVals_Check(&p->Parents, fileIndex) - parent = p->Parents.Vals[fileIndex]; - if (parent == (UInt32)(Int32)-1) - return len; - fileIndex = parent; - } -} - -UInt16 *SzArEx_GetFullNameUtf16_Back(const CSzArEx *p, size_t fileIndex, UInt16 *dest) -{ - Bool needSlash; - if (!p->FileNameOffsets) - { - *(--dest) = 0; - return dest; } - needSlash = False; - for (;;) { - UInt32 parent = (UInt32)(Int32)-1; - size_t curLen = p->FileNameOffsets[fileIndex + 1] - p->FileNameOffsets[fileIndex]; - SzArEx_GetFileNameUtf16(p, fileIndex, dest - curLen); - if (needSlash) - *(dest - 1) = '/'; - needSlash = True; - dest -= curLen; - - if SzBitWithVals_Check(&p->Parents, fileIndex) - parent = p->Parents.Vals[fileIndex]; - if (parent == (UInt32)(Int32)-1) - return dest; - fileIndex = parent; + const size_t offs = offsets[fileIndex]; + const size_t len = offsets[fileIndex + 1] - offs; + if (dest) + { + size_t i; + const Byte *src = p->FileNames + offs * 2; + for (i = 0; i < len; i++) + dest[i] = GetUi16a(src + i * 2); + } + return len; } } -*/ diff --git a/src/plugins/7zip/7za/c/7zBuf.c b/src/plugins/7zip/7za/c/7zBuf.c index 089a5c4f..8865c32a 100644 --- a/src/plugins/7zip/7za/c/7zBuf.c +++ b/src/plugins/7zip/7za/c/7zBuf.c @@ -1,5 +1,5 @@ /* 7zBuf.c -- Byte Buffer -2013-01-21 : Igor Pavlov : Public domain */ +2017-04-03 : Igor Pavlov : Public domain */ #include "Precomp.h" @@ -11,7 +11,7 @@ void Buf_Init(CBuf *p) p->size = 0; } -int Buf_Create(CBuf *p, size_t size, ISzAlloc *alloc) +int Buf_Create(CBuf *p, size_t size, ISzAllocPtr alloc) { p->size = 0; if (size == 0) @@ -19,8 +19,8 @@ int Buf_Create(CBuf *p, size_t size, ISzAlloc *alloc) p->data = 0; return 1; } - p->data = (Byte *)alloc->Alloc(alloc, size); - if (p->data != 0) + p->data = (Byte *)ISzAlloc_Alloc(alloc, size); + if (p->data) { p->size = size; return 1; @@ -28,9 +28,9 @@ int Buf_Create(CBuf *p, size_t size, ISzAlloc *alloc) return 0; } -void Buf_Free(CBuf *p, ISzAlloc *alloc) +void Buf_Free(CBuf *p, ISzAllocPtr alloc) { - alloc->Free(alloc, p->data); + ISzAlloc_Free(alloc, p->data); p->data = 0; p->size = 0; } diff --git a/src/plugins/7zip/7za/c/7zBuf.h b/src/plugins/7zip/7za/c/7zBuf.h index 65f1d7a7..c0ba8a7b 100644 --- a/src/plugins/7zip/7za/c/7zBuf.h +++ b/src/plugins/7zip/7za/c/7zBuf.h @@ -1,8 +1,8 @@ /* 7zBuf.h -- Byte Buffer -2013-01-18 : Igor Pavlov : Public domain */ +2023-03-04 : Igor Pavlov : Public domain */ -#ifndef __7Z_BUF_H -#define __7Z_BUF_H +#ifndef ZIP7_INC_7Z_BUF_H +#define ZIP7_INC_7Z_BUF_H #include "7zTypes.h" @@ -15,8 +15,8 @@ typedef struct } CBuf; void Buf_Init(CBuf *p); -int Buf_Create(CBuf *p, size_t size, ISzAlloc *alloc); -void Buf_Free(CBuf *p, ISzAlloc *alloc); +int Buf_Create(CBuf *p, size_t size, ISzAllocPtr alloc); +void Buf_Free(CBuf *p, ISzAllocPtr alloc); typedef struct { @@ -27,8 +27,8 @@ typedef struct void DynBuf_Construct(CDynBuf *p); void DynBuf_SeekToBeg(CDynBuf *p); -int DynBuf_Write(CDynBuf *p, const Byte *buf, size_t size, ISzAlloc *alloc); -void DynBuf_Free(CDynBuf *p, ISzAlloc *alloc); +int DynBuf_Write(CDynBuf *p, const Byte *buf, size_t size, ISzAllocPtr alloc); +void DynBuf_Free(CDynBuf *p, ISzAllocPtr alloc); EXTERN_C_END diff --git a/src/plugins/7zip/7za/c/7zBuf2.c b/src/plugins/7zip/7za/c/7zBuf2.c index 430f76bf..20834741 100644 --- a/src/plugins/7zip/7za/c/7zBuf2.c +++ b/src/plugins/7zip/7za/c/7zBuf2.c @@ -1,5 +1,5 @@ /* 7zBuf2.c -- Byte Buffer -2014-08-22 : Igor Pavlov : Public domain */ +2017-04-03 : Igor Pavlov : Public domain */ #include "Precomp.h" @@ -19,19 +19,20 @@ void DynBuf_SeekToBeg(CDynBuf *p) p->pos = 0; } -int DynBuf_Write(CDynBuf *p, const Byte *buf, size_t size, ISzAlloc *alloc) +int DynBuf_Write(CDynBuf *p, const Byte *buf, size_t size, ISzAllocPtr alloc) { if (size > p->size - p->pos) { size_t newSize = p->pos + size; Byte *data; newSize += newSize / 4; - data = (Byte *)alloc->Alloc(alloc, newSize); - if (data == 0) + data = (Byte *)ISzAlloc_Alloc(alloc, newSize); + if (!data) return 0; p->size = newSize; - memcpy(data, p->data, p->pos); - alloc->Free(alloc, p->data); + if (p->pos != 0) + memcpy(data, p->data, p->pos); + ISzAlloc_Free(alloc, p->data); p->data = data; } if (size != 0) @@ -42,9 +43,9 @@ int DynBuf_Write(CDynBuf *p, const Byte *buf, size_t size, ISzAlloc *alloc) return 1; } -void DynBuf_Free(CDynBuf *p, ISzAlloc *alloc) +void DynBuf_Free(CDynBuf *p, ISzAllocPtr alloc) { - alloc->Free(alloc, p->data); + ISzAlloc_Free(alloc, p->data); p->data = 0; p->size = 0; p->pos = 0; diff --git a/src/plugins/7zip/7za/c/7zCrc.c b/src/plugins/7zip/7za/c/7zCrc.c index dc6d6abd..6e2db9ea 100644 --- a/src/plugins/7zip/7za/c/7zCrc.c +++ b/src/plugins/7zip/7za/c/7zCrc.c @@ -1,128 +1,420 @@ -/* 7zCrc.c -- CRC32 init -2015-03-10 : Igor Pavlov : Public domain */ +/* 7zCrc.c -- CRC32 calculation and init +2024-03-01 : Igor Pavlov : Public domain */ #include "Precomp.h" #include "7zCrc.h" #include "CpuArch.h" -#define kCrcPoly 0xEDB88320 +// for debug: +// #define __ARM_FEATURE_CRC32 1 -#ifdef MY_CPU_LE - #define CRC_NUM_TABLES 8 +#ifdef __ARM_FEATURE_CRC32 +// #pragma message("__ARM_FEATURE_CRC32") +#define Z7_CRC_HW_FORCE +#endif + +// #define Z7_CRC_DEBUG_BE +#ifdef Z7_CRC_DEBUG_BE +#undef MY_CPU_LE +#define MY_CPU_BE +#endif + +#ifdef Z7_CRC_HW_FORCE + #define Z7_CRC_NUM_TABLES_USE 1 #else - #define CRC_NUM_TABLES 9 +#ifdef Z7_CRC_NUM_TABLES + #define Z7_CRC_NUM_TABLES_USE Z7_CRC_NUM_TABLES +#else + #define Z7_CRC_NUM_TABLES_USE 12 +#endif +#endif - #define CRC_UINT32_SWAP(v) ((v >> 24) | ((v >> 8) & 0xFF00) | ((v << 8) & 0xFF0000) | (v << 24)) +#if Z7_CRC_NUM_TABLES_USE < 1 + #error Stop_Compiling_Bad_Z7_CRC_NUM_TABLES +#endif - UInt32 MY_FAST_CALL CrcUpdateT1_BeT4(UInt32 v, const void *data, size_t size, const UInt32 *table); - UInt32 MY_FAST_CALL CrcUpdateT1_BeT8(UInt32 v, const void *data, size_t size, const UInt32 *table); +#if defined(MY_CPU_LE) || (Z7_CRC_NUM_TABLES_USE == 1) + #define Z7_CRC_NUM_TABLES_TOTAL Z7_CRC_NUM_TABLES_USE +#else + #define Z7_CRC_NUM_TABLES_TOTAL (Z7_CRC_NUM_TABLES_USE + 1) #endif +#ifndef Z7_CRC_HW_FORCE + +#if Z7_CRC_NUM_TABLES_USE == 1 \ + || (!defined(MY_CPU_LE) && !defined(MY_CPU_BE)) +#define CRC_UPDATE_BYTE_2(crc, b) (table[((crc) ^ (b)) & 0xFF] ^ ((crc) >> 8)) +#define Z7_CRC_UPDATE_T1_FUNC_NAME CrcUpdateGT1 +static UInt32 Z7_FASTCALL Z7_CRC_UPDATE_T1_FUNC_NAME(UInt32 v, const void *data, size_t size) +{ + const UInt32 *table = g_CrcTable; + const Byte *p = (const Byte *)data; + const Byte *lim = p + size; + for (; p != lim; p++) + v = CRC_UPDATE_BYTE_2(v, *p); + return v; +} +#endif + + +#if Z7_CRC_NUM_TABLES_USE != 1 #ifndef MY_CPU_BE - UInt32 MY_FAST_CALL CrcUpdateT4(UInt32 v, const void *data, size_t size, const UInt32 *table); - UInt32 MY_FAST_CALL CrcUpdateT8(UInt32 v, const void *data, size_t size, const UInt32 *table); + #define FUNC_NAME_LE_2(s) CrcUpdateT ## s + #define FUNC_NAME_LE_1(s) FUNC_NAME_LE_2(s) + #define FUNC_NAME_LE FUNC_NAME_LE_1(Z7_CRC_NUM_TABLES_USE) + UInt32 Z7_FASTCALL FUNC_NAME_LE (UInt32 v, const void *data, size_t size, const UInt32 *table); +#endif +#ifndef MY_CPU_LE + #define FUNC_NAME_BE_2(s) CrcUpdateT1_BeT ## s + #define FUNC_NAME_BE_1(s) FUNC_NAME_BE_2(s) + #define FUNC_NAME_BE FUNC_NAME_BE_1(Z7_CRC_NUM_TABLES_USE) + UInt32 Z7_FASTCALL FUNC_NAME_BE (UInt32 v, const void *data, size_t size, const UInt32 *table); +#endif #endif -typedef UInt32 (MY_FAST_CALL *CRC_FUNC)(UInt32 v, const void *data, size_t size, const UInt32 *table); +#endif // Z7_CRC_HW_FORCE + +/* ---------- hardware CRC ---------- */ + +#ifdef MY_CPU_LE + +#if defined(MY_CPU_ARM_OR_ARM64) +// #pragma message("ARM*") -CRC_FUNC g_CrcUpdateT4; -CRC_FUNC g_CrcUpdateT8; -CRC_FUNC g_CrcUpdate; + #if (defined(__clang__) && (__clang_major__ >= 3)) \ + || defined(__GNUC__) && (__GNUC__ >= 6) && defined(MY_CPU_ARM64) \ + || defined(__GNUC__) && (__GNUC__ >= 8) + #if !defined(__ARM_FEATURE_CRC32) +// #pragma message("!defined(__ARM_FEATURE_CRC32)") +Z7_DIAGNOSTIC_IGNORE_BEGIN_RESERVED_MACRO_IDENTIFIER + #define __ARM_FEATURE_CRC32 1 +Z7_DIAGNOSTIC_IGNORE_END_RESERVED_MACRO_IDENTIFIER + #define Z7_ARM_FEATURE_CRC32_WAS_SET + #if defined(__clang__) + #if defined(MY_CPU_ARM64) + #define ATTRIB_CRC __attribute__((__target__("crc"))) + #else + #define ATTRIB_CRC __attribute__((__target__("armv8-a,crc"))) + #endif + #else + #if defined(MY_CPU_ARM64) +#if !defined(Z7_GCC_VERSION) || (Z7_GCC_VERSION >= 60000) + #define ATTRIB_CRC __attribute__((__target__("+crc"))) +#endif + #else +#if !defined(Z7_GCC_VERSION) || (__GNUC__ >= 8) +#if defined(__ARM_FP) && __GNUC__ >= 8 +// for -mfloat-abi=hard: similar to + #define ATTRIB_CRC __attribute__((__target__("arch=armv8-a+crc+simd"))) +#else + #define ATTRIB_CRC __attribute__((__target__("arch=armv8-a+crc"))) +#endif +#endif + #endif + #endif + #endif + #if defined(__ARM_FEATURE_CRC32) + // #pragma message("") +/* +arm_acle.h (GGC): + before Nov 17, 2017: +#ifdef __ARM_FEATURE_CRC32 -UInt32 g_CrcTable[256 * CRC_NUM_TABLES]; + Nov 17, 2017: gcc10.0 (gcc 9.2.0) checked" +#if __ARM_ARCH >= 8 +#pragma GCC target ("arch=armv8-a+crc") -UInt32 MY_FAST_CALL CrcUpdate(UInt32 v, const void *data, size_t size) + Aug 22, 2019: GCC 8.4?, 9.2.1, 10.1: +#ifdef __ARM_FEATURE_CRC32 +#ifdef __ARM_FP +#pragma GCC target ("arch=armv8-a+crc+simd") +#else +#pragma GCC target ("arch=armv8-a+crc") +#endif +*/ +#if defined(__ARM_ARCH) && __ARM_ARCH < 8 +#if defined(Z7_GCC_VERSION) && (__GNUC__ == 8) && (Z7_GCC_VERSION < 80400) \ + || defined(Z7_GCC_VERSION) && (__GNUC__ == 9) && (Z7_GCC_VERSION < 90201) \ + || defined(Z7_GCC_VERSION) && (__GNUC__ == 10) && (Z7_GCC_VERSION < 100100) +Z7_DIAGNOSTIC_IGNORE_BEGIN_RESERVED_MACRO_IDENTIFIER +// #pragma message("#define __ARM_ARCH 8") +#undef __ARM_ARCH +#define __ARM_ARCH 8 +Z7_DIAGNOSTIC_IGNORE_END_RESERVED_MACRO_IDENTIFIER +#endif +#endif + #define Z7_CRC_HW_USE + #include + #endif + #elif defined(_MSC_VER) + #if defined(MY_CPU_ARM64) + #if (_MSC_VER >= 1910) + #ifdef __clang__ + // #define Z7_CRC_HW_USE + // #include + #else + #define Z7_CRC_HW_USE + #include + #endif + #endif + #endif + #endif + +#else // non-ARM* + +// #define Z7_CRC_HW_USE // for debug : we can test HW-branch of code +#ifdef Z7_CRC_HW_USE +#include "7zCrcEmu.h" +#endif + +#endif // non-ARM* + + + +#if defined(Z7_CRC_HW_USE) + +// #pragma message("USE ARM HW CRC") + +#ifdef MY_CPU_64BIT + #define CRC_HW_WORD_TYPE UInt64 + #define CRC_HW_WORD_FUNC __crc32d +#else + #define CRC_HW_WORD_TYPE UInt32 + #define CRC_HW_WORD_FUNC __crc32w +#endif + +#define CRC_HW_UNROLL_BYTES (sizeof(CRC_HW_WORD_TYPE) * 4) + +#ifdef ATTRIB_CRC + ATTRIB_CRC +#endif +Z7_NO_INLINE +#ifdef Z7_CRC_HW_FORCE + UInt32 Z7_FASTCALL CrcUpdate +#else + static UInt32 Z7_FASTCALL CrcUpdate_HW +#endif + (UInt32 v, const void *data, size_t size) { - return g_CrcUpdate(v, data, size, g_CrcTable); + const Byte *p = (const Byte *)data; + for (; size != 0 && ((unsigned)(ptrdiff_t)p & (CRC_HW_UNROLL_BYTES - 1)) != 0; size--) + v = __crc32b(v, *p++); + if (size >= CRC_HW_UNROLL_BYTES) + { + const Byte *lim = p + size; + size &= CRC_HW_UNROLL_BYTES - 1; + lim -= size; + do + { + v = CRC_HW_WORD_FUNC(v, *(const CRC_HW_WORD_TYPE *)(const void *)(p)); + v = CRC_HW_WORD_FUNC(v, *(const CRC_HW_WORD_TYPE *)(const void *)(p + sizeof(CRC_HW_WORD_TYPE))); + p += 2 * sizeof(CRC_HW_WORD_TYPE); + v = CRC_HW_WORD_FUNC(v, *(const CRC_HW_WORD_TYPE *)(const void *)(p)); + v = CRC_HW_WORD_FUNC(v, *(const CRC_HW_WORD_TYPE *)(const void *)(p + sizeof(CRC_HW_WORD_TYPE))); + p += 2 * sizeof(CRC_HW_WORD_TYPE); + } + while (p != lim); + } + + for (; size != 0; size--) + v = __crc32b(v, *p++); + + return v; } -UInt32 MY_FAST_CALL CrcCalc(const void *data, size_t size) +#ifdef Z7_ARM_FEATURE_CRC32_WAS_SET +Z7_DIAGNOSTIC_IGNORE_BEGIN_RESERVED_MACRO_IDENTIFIER +#undef __ARM_FEATURE_CRC32 +Z7_DIAGNOSTIC_IGNORE_END_RESERVED_MACRO_IDENTIFIER +#undef Z7_ARM_FEATURE_CRC32_WAS_SET +#endif + +#endif // defined(Z7_CRC_HW_USE) +#endif // MY_CPU_LE + + + +#ifndef Z7_CRC_HW_FORCE + +#if defined(Z7_CRC_HW_USE) || defined(Z7_CRC_UPDATE_T1_FUNC_NAME) +/* +typedef UInt32 (Z7_FASTCALL *Z7_CRC_UPDATE_WITH_TABLE_FUNC) + (UInt32 v, const void *data, size_t size, const UInt32 *table); +Z7_CRC_UPDATE_WITH_TABLE_FUNC g_CrcUpdate; +*/ +static unsigned g_Crc_Algo; +#if (!defined(MY_CPU_LE) && !defined(MY_CPU_BE)) +static unsigned g_Crc_Be; +#endif +#endif // defined(Z7_CRC_HW_USE) || defined(Z7_CRC_UPDATE_T1_FUNC_NAME) + + + +Z7_NO_INLINE +#ifdef Z7_CRC_HW_USE + static UInt32 Z7_FASTCALL CrcUpdate_Base +#else + UInt32 Z7_FASTCALL CrcUpdate +#endif + (UInt32 crc, const void *data, size_t size) { - return g_CrcUpdate(CRC_INIT_VAL, data, size, g_CrcTable) ^ CRC_INIT_VAL; +#if Z7_CRC_NUM_TABLES_USE == 1 + return Z7_CRC_UPDATE_T1_FUNC_NAME(crc, data, size); +#else // Z7_CRC_NUM_TABLES_USE != 1 +#ifdef Z7_CRC_UPDATE_T1_FUNC_NAME + if (g_Crc_Algo == 1) + return Z7_CRC_UPDATE_T1_FUNC_NAME(crc, data, size); +#endif + +#ifdef MY_CPU_LE + return FUNC_NAME_LE(crc, data, size, g_CrcTable); +#elif defined(MY_CPU_BE) + return FUNC_NAME_BE(crc, data, size, g_CrcTable); +#else + if (g_Crc_Be) + return FUNC_NAME_BE(crc, data, size, g_CrcTable); + else + return FUNC_NAME_LE(crc, data, size, g_CrcTable); +#endif +#endif // Z7_CRC_NUM_TABLES_USE != 1 } -#define CRC_UPDATE_BYTE_2(crc, b) (table[((crc) ^ (b)) & 0xFF] ^ ((crc) >> 8)) -UInt32 MY_FAST_CALL CrcUpdateT1(UInt32 v, const void *data, size_t size, const UInt32 *table) +#ifdef Z7_CRC_HW_USE +Z7_NO_INLINE +UInt32 Z7_FASTCALL CrcUpdate(UInt32 crc, const void *data, size_t size) { - const Byte *p = (const Byte *)data; - const Byte *pEnd = p + size; - for (; p != pEnd; p++) - v = CRC_UPDATE_BYTE_2(v, *p); - return v; + if (g_Crc_Algo == 0) + return CrcUpdate_HW(crc, data, size); + return CrcUpdate_Base(crc, data, size); +} +#endif + +#endif // !defined(Z7_CRC_HW_FORCE) + + + +UInt32 Z7_FASTCALL CrcCalc(const void *data, size_t size) +{ + return CrcUpdate(CRC_INIT_VAL, data, size) ^ CRC_INIT_VAL; } -void MY_FAST_CALL CrcGenerateTable() + +MY_ALIGN(64) +UInt32 g_CrcTable[256 * Z7_CRC_NUM_TABLES_TOTAL]; + + +void Z7_FASTCALL CrcGenerateTable(void) { UInt32 i; for (i = 0; i < 256; i++) { +#if defined(Z7_CRC_HW_FORCE) + g_CrcTable[i] = __crc32b(i, 0); +#else + #define kCrcPoly 0xEDB88320 UInt32 r = i; unsigned j; for (j = 0; j < 8; j++) - r = (r >> 1) ^ (kCrcPoly & ~((r & 1) - 1)); + r = (r >> 1) ^ (kCrcPoly & ((UInt32)0 - (r & 1))); g_CrcTable[i] = r; +#endif } - for (; i < 256 * CRC_NUM_TABLES; i++) + for (i = 256; i < 256 * Z7_CRC_NUM_TABLES_USE; i++) { - UInt32 r = g_CrcTable[i - 256]; + const UInt32 r = g_CrcTable[(size_t)i - 256]; g_CrcTable[i] = g_CrcTable[r & 0xFF] ^ (r >> 8); } - #if CRC_NUM_TABLES < 4 - - g_CrcUpdate = CrcUpdateT1; - - #else - - #ifdef MY_CPU_LE - - g_CrcUpdateT4 = CrcUpdateT4; - g_CrcUpdate = CrcUpdateT4; +#if !defined(Z7_CRC_HW_FORCE) && \ + (defined(Z7_CRC_HW_USE) || defined(Z7_CRC_UPDATE_T1_FUNC_NAME) || defined(MY_CPU_BE)) - #if CRC_NUM_TABLES >= 8 - g_CrcUpdateT8 = CrcUpdateT8; - - #ifdef MY_CPU_X86_OR_AMD64 - if (!CPU_Is_InOrder()) - g_CrcUpdate = CrcUpdateT8; - #endif - #endif +#if Z7_CRC_NUM_TABLES_USE <= 1 + g_Crc_Algo = 1; +#else // Z7_CRC_NUM_TABLES_USE <= 1 - #else +#if defined(MY_CPU_LE) + g_Crc_Algo = Z7_CRC_NUM_TABLES_USE; +#else // !defined(MY_CPU_LE) { - #ifndef MY_CPU_BE +#ifndef MY_CPU_BE UInt32 k = 0x01020304; const Byte *p = (const Byte *)&k; if (p[0] == 4 && p[1] == 3) - { - g_CrcUpdateT4 = CrcUpdateT4; - g_CrcUpdate = CrcUpdateT4; - #if CRC_NUM_TABLES >= 8 - g_CrcUpdateT8 = CrcUpdateT8; - // g_CrcUpdate = CrcUpdateT8; - #endif - } + g_Crc_Algo = Z7_CRC_NUM_TABLES_USE; else if (p[0] != 1 || p[1] != 2) - g_CrcUpdate = CrcUpdateT1; + g_Crc_Algo = 1; else - #endif +#endif // MY_CPU_BE { - for (i = 256 * CRC_NUM_TABLES - 1; i >= 256; i--) + for (i = 256 * Z7_CRC_NUM_TABLES_TOTAL - 1; i >= 256; i--) { - UInt32 x = g_CrcTable[i - 256]; - g_CrcTable[i] = CRC_UINT32_SWAP(x); + const UInt32 x = g_CrcTable[(size_t)i - 256]; + g_CrcTable[i] = Z7_BSWAP32(x); } - g_CrcUpdateT4 = CrcUpdateT1_BeT4; - g_CrcUpdate = CrcUpdateT1_BeT4; - #if CRC_NUM_TABLES >= 8 - g_CrcUpdateT8 = CrcUpdateT1_BeT8; - // g_CrcUpdate = CrcUpdateT1_BeT8; - #endif +#if defined(Z7_CRC_UPDATE_T1_FUNC_NAME) + g_Crc_Algo = Z7_CRC_NUM_TABLES_USE; +#endif +#if (!defined(MY_CPU_LE) && !defined(MY_CPU_BE)) + g_Crc_Be = 1; +#endif } } - #endif +#endif // !defined(MY_CPU_LE) + +#ifdef MY_CPU_LE +#ifdef Z7_CRC_HW_USE + if (CPU_IsSupported_CRC32()) + g_Crc_Algo = 0; +#endif // Z7_CRC_HW_USE +#endif // MY_CPU_LE + +#endif // Z7_CRC_NUM_TABLES_USE <= 1 +#endif // g_Crc_Algo was declared +} + +Z7_CRC_UPDATE_FUNC z7_GetFunc_CrcUpdate(unsigned algo) +{ + if (algo == 0) + return &CrcUpdate; + +#if defined(Z7_CRC_HW_USE) + if (algo == sizeof(CRC_HW_WORD_TYPE) * 8) + { +#ifdef Z7_CRC_HW_FORCE + return &CrcUpdate; +#else + if (g_Crc_Algo == 0) + return &CrcUpdate_HW; +#endif + } +#endif +#ifndef Z7_CRC_HW_FORCE + if (algo == Z7_CRC_NUM_TABLES_USE) + return + #ifdef Z7_CRC_HW_USE + &CrcUpdate_Base; + #else + &CrcUpdate; #endif +#endif + + return NULL; } + +#undef kCrcPoly +#undef Z7_CRC_NUM_TABLES_USE +#undef Z7_CRC_NUM_TABLES_TOTAL +#undef CRC_UPDATE_BYTE_2 +#undef FUNC_NAME_LE_2 +#undef FUNC_NAME_LE_1 +#undef FUNC_NAME_LE +#undef FUNC_NAME_BE_2 +#undef FUNC_NAME_BE_1 +#undef FUNC_NAME_BE + +#undef CRC_HW_UNROLL_BYTES +#undef CRC_HW_WORD_FUNC +#undef CRC_HW_WORD_TYPE diff --git a/src/plugins/7zip/7za/c/7zCrc.h b/src/plugins/7zip/7za/c/7zCrc.h index 8fd57958..3e6d408b 100644 --- a/src/plugins/7zip/7za/c/7zCrc.h +++ b/src/plugins/7zip/7za/c/7zCrc.h @@ -1,8 +1,8 @@ /* 7zCrc.h -- CRC32 calculation -2013-01-18 : Igor Pavlov : Public domain */ +2024-01-22 : Igor Pavlov : Public domain */ -#ifndef __7Z_CRC_H -#define __7Z_CRC_H +#ifndef ZIP7_INC_7Z_CRC_H +#define ZIP7_INC_7Z_CRC_H #include "7zTypes.h" @@ -11,14 +11,17 @@ EXTERN_C_BEGIN extern UInt32 g_CrcTable[]; /* Call CrcGenerateTable one time before other CRC functions */ -void MY_FAST_CALL CrcGenerateTable(void); +void Z7_FASTCALL CrcGenerateTable(void); #define CRC_INIT_VAL 0xFFFFFFFF #define CRC_GET_DIGEST(crc) ((crc) ^ CRC_INIT_VAL) #define CRC_UPDATE_BYTE(crc, b) (g_CrcTable[((crc) ^ (b)) & 0xFF] ^ ((crc) >> 8)) -UInt32 MY_FAST_CALL CrcUpdate(UInt32 crc, const void *data, size_t size); -UInt32 MY_FAST_CALL CrcCalc(const void *data, size_t size); +UInt32 Z7_FASTCALL CrcUpdate(UInt32 crc, const void *data, size_t size); +UInt32 Z7_FASTCALL CrcCalc(const void *data, size_t size); + +typedef UInt32 (Z7_FASTCALL *Z7_CRC_UPDATE_FUNC)(UInt32 v, const void *data, size_t size); +Z7_CRC_UPDATE_FUNC z7_GetFunc_CrcUpdate(unsigned algo); EXTERN_C_END diff --git a/src/plugins/7zip/7za/c/7zCrcOpt.c b/src/plugins/7zip/7za/c/7zCrcOpt.c index d1e1cd7b..9408017e 100644 --- a/src/plugins/7zip/7za/c/7zCrcOpt.c +++ b/src/plugins/7zip/7za/c/7zCrcOpt.c @@ -1,115 +1,199 @@ -/* 7zCrcOpt.c -- CRC32 calculation -2015-03-01 : Igor Pavlov : Public domain */ +/* 7zCrcOpt.c -- CRC32 calculation (optimized functions) +2023-12-07 : Igor Pavlov : Public domain */ #include "Precomp.h" #include "CpuArch.h" +#if !defined(Z7_CRC_NUM_TABLES) || Z7_CRC_NUM_TABLES > 1 + +// for debug only : define Z7_CRC_DEBUG_BE to test big-endian code in little-endian cpu +// #define Z7_CRC_DEBUG_BE +#ifdef Z7_CRC_DEBUG_BE +#undef MY_CPU_LE +#define MY_CPU_BE +#endif + +// the value Z7_CRC_NUM_TABLES_USE must be defined to same value as in 7zCrc.c +#ifdef Z7_CRC_NUM_TABLES +#define Z7_CRC_NUM_TABLES_USE Z7_CRC_NUM_TABLES +#else +#define Z7_CRC_NUM_TABLES_USE 12 +#endif + +#if Z7_CRC_NUM_TABLES_USE % 4 || \ + Z7_CRC_NUM_TABLES_USE < 4 * 1 || \ + Z7_CRC_NUM_TABLES_USE > 4 * 6 + #error Stop_Compiling_Bad_Z7_CRC_NUM_TABLES +#endif + + #ifndef MY_CPU_BE -#define CRC_UPDATE_BYTE_2(crc, b) (table[((crc) ^ (b)) & 0xFF] ^ ((crc) >> 8)) +#define CRC_UPDATE_BYTE_2(crc, b) (table[((crc) ^ (b)) & 0xFF] ^ ((crc) >> 8)) -UInt32 MY_FAST_CALL CrcUpdateT4(UInt32 v, const void *data, size_t size, const UInt32 *table) -{ - const Byte *p = (const Byte *)data; - for (; size > 0 && ((unsigned)(ptrdiff_t)p & 3) != 0; size--, p++) - v = CRC_UPDATE_BYTE_2(v, *p); - for (; size >= 4; size -= 4, p += 4) - { - v ^= *(const UInt32 *)p; - v = - table[0x300 + ((v ) & 0xFF)] - ^ table[0x200 + ((v >> 8) & 0xFF)] - ^ table[0x100 + ((v >> 16) & 0xFF)] - ^ table[0x000 + ((v >> 24))]; - } - for (; size > 0; size--, p++) - v = CRC_UPDATE_BYTE_2(v, *p); - return v; -} +#define Q(n, d) \ + ( (table + ((n) * 4 + 3) * 0x100)[(Byte)(d)] \ + ^ (table + ((n) * 4 + 2) * 0x100)[((d) >> 1 * 8) & 0xFF] \ + ^ (table + ((n) * 4 + 1) * 0x100)[((d) >> 2 * 8) & 0xFF] \ + ^ (table + ((n) * 4 + 0) * 0x100)[((d) >> 3 * 8)] ) + +#define R(a) *((const UInt32 *)(const void *)p + (a)) + +#define CRC_FUNC_PRE_LE2(step) \ +UInt32 Z7_FASTCALL CrcUpdateT ## step (UInt32 v, const void *data, size_t size, const UInt32 *table) -UInt32 MY_FAST_CALL CrcUpdateT8(UInt32 v, const void *data, size_t size, const UInt32 *table) +#define CRC_FUNC_PRE_LE(step) \ + CRC_FUNC_PRE_LE2(step); \ + CRC_FUNC_PRE_LE2(step) + +CRC_FUNC_PRE_LE(Z7_CRC_NUM_TABLES_USE) { const Byte *p = (const Byte *)data; - for (; size > 0 && ((unsigned)(ptrdiff_t)p & 7) != 0; size--, p++) + const Byte *lim; + for (; size && ((unsigned)(ptrdiff_t)p & (7 - (Z7_CRC_NUM_TABLES_USE & 4))) != 0; size--, p++) v = CRC_UPDATE_BYTE_2(v, *p); - for (; size >= 8; size -= 8, p += 8) + lim = p + size; + if (size >= Z7_CRC_NUM_TABLES_USE) { - UInt32 d; - v ^= *(const UInt32 *)p; - v = - table[0x700 + ((v ) & 0xFF)] - ^ table[0x600 + ((v >> 8) & 0xFF)] - ^ table[0x500 + ((v >> 16) & 0xFF)] - ^ table[0x400 + ((v >> 24))]; - d = *((const UInt32 *)p + 1); - v ^= - table[0x300 + ((d ) & 0xFF)] - ^ table[0x200 + ((d >> 8) & 0xFF)] - ^ table[0x100 + ((d >> 16) & 0xFF)] - ^ table[0x000 + ((d >> 24))]; + lim -= Z7_CRC_NUM_TABLES_USE; + do + { + v ^= R(0); + { +#if Z7_CRC_NUM_TABLES_USE == 1 * 4 + v = Q(0, v); +#else +#define U2(r, op) \ + { d = R(r); x op Q(Z7_CRC_NUM_TABLES_USE / 4 - 1 - (r), d); } + UInt32 d, x; + U2(1, =) +#if Z7_CRC_NUM_TABLES_USE >= 3 * 4 +#define U(r) U2(r, ^=) + U(2) +#if Z7_CRC_NUM_TABLES_USE >= 4 * 4 + U(3) +#if Z7_CRC_NUM_TABLES_USE >= 5 * 4 + U(4) +#if Z7_CRC_NUM_TABLES_USE >= 6 * 4 + U(5) +#if Z7_CRC_NUM_TABLES_USE >= 7 * 4 +#error Stop_Compiling_Bad_Z7_CRC_NUM_TABLES +#endif +#endif +#endif +#endif +#endif +#undef U +#undef U2 + v = x ^ Q(Z7_CRC_NUM_TABLES_USE / 4 - 1, v); +#endif + } + p += Z7_CRC_NUM_TABLES_USE; + } + while (p <= lim); + lim += Z7_CRC_NUM_TABLES_USE; } - for (; size > 0; size--, p++) + for (; p < lim; p++) v = CRC_UPDATE_BYTE_2(v, *p); return v; } +#undef CRC_UPDATE_BYTE_2 +#undef R +#undef Q +#undef CRC_FUNC_PRE_LE +#undef CRC_FUNC_PRE_LE2 + #endif + + #ifndef MY_CPU_LE -#define CRC_UINT32_SWAP(v) ((v >> 24) | ((v >> 8) & 0xFF00) | ((v << 8) & 0xFF0000) | (v << 24)) +#define CRC_UPDATE_BYTE_2_BE(crc, b) (table[((crc) >> 24) ^ (b)] ^ ((crc) << 8)) -#define CRC_UPDATE_BYTE_2_BE(crc, b) (table[(((crc) >> 24) ^ (b))] ^ ((crc) << 8)) +#define Q(n, d) \ + ( (table + ((n) * 4 + 0) * 0x100)[((d)) & 0xFF] \ + ^ (table + ((n) * 4 + 1) * 0x100)[((d) >> 1 * 8) & 0xFF] \ + ^ (table + ((n) * 4 + 2) * 0x100)[((d) >> 2 * 8) & 0xFF] \ + ^ (table + ((n) * 4 + 3) * 0x100)[((d) >> 3 * 8)] ) -UInt32 MY_FAST_CALL CrcUpdateT1_BeT4(UInt32 v, const void *data, size_t size, const UInt32 *table) -{ - const Byte *p = (const Byte *)data; - table += 0x100; - v = CRC_UINT32_SWAP(v); - for (; size > 0 && ((unsigned)(ptrdiff_t)p & 3) != 0; size--, p++) - v = CRC_UPDATE_BYTE_2_BE(v, *p); - for (; size >= 4; size -= 4, p += 4) - { - v ^= *(const UInt32 *)p; - v = - table[0x000 + ((v ) & 0xFF)] - ^ table[0x100 + ((v >> 8) & 0xFF)] - ^ table[0x200 + ((v >> 16) & 0xFF)] - ^ table[0x300 + ((v >> 24))]; - } - for (; size > 0; size--, p++) - v = CRC_UPDATE_BYTE_2_BE(v, *p); - return CRC_UINT32_SWAP(v); -} +#ifdef Z7_CRC_DEBUG_BE + #define R(a) GetBe32a((const UInt32 *)(const void *)p + (a)) +#else + #define R(a) *((const UInt32 *)(const void *)p + (a)) +#endif + + +#define CRC_FUNC_PRE_BE2(step) \ +UInt32 Z7_FASTCALL CrcUpdateT1_BeT ## step (UInt32 v, const void *data, size_t size, const UInt32 *table) -UInt32 MY_FAST_CALL CrcUpdateT1_BeT8(UInt32 v, const void *data, size_t size, const UInt32 *table) +#define CRC_FUNC_PRE_BE(step) \ + CRC_FUNC_PRE_BE2(step); \ + CRC_FUNC_PRE_BE2(step) + +CRC_FUNC_PRE_BE(Z7_CRC_NUM_TABLES_USE) { const Byte *p = (const Byte *)data; + const Byte *lim; table += 0x100; - v = CRC_UINT32_SWAP(v); - for (; size > 0 && ((unsigned)(ptrdiff_t)p & 7) != 0; size--, p++) + v = Z7_BSWAP32(v); + for (; size && ((unsigned)(ptrdiff_t)p & (7 - (Z7_CRC_NUM_TABLES_USE & 4))) != 0; size--, p++) v = CRC_UPDATE_BYTE_2_BE(v, *p); - for (; size >= 8; size -= 8, p += 8) + lim = p + size; + if (size >= Z7_CRC_NUM_TABLES_USE) { - UInt32 d; - v ^= *(const UInt32 *)p; - v = - table[0x400 + ((v ) & 0xFF)] - ^ table[0x500 + ((v >> 8) & 0xFF)] - ^ table[0x600 + ((v >> 16) & 0xFF)] - ^ table[0x700 + ((v >> 24))]; - d = *((const UInt32 *)p + 1); - v ^= - table[0x000 + ((d ) & 0xFF)] - ^ table[0x100 + ((d >> 8) & 0xFF)] - ^ table[0x200 + ((d >> 16) & 0xFF)] - ^ table[0x300 + ((d >> 24))]; + lim -= Z7_CRC_NUM_TABLES_USE; + do + { + v ^= R(0); + { +#if Z7_CRC_NUM_TABLES_USE == 1 * 4 + v = Q(0, v); +#else +#define U2(r, op) \ + { d = R(r); x op Q(Z7_CRC_NUM_TABLES_USE / 4 - 1 - (r), d); } + UInt32 d, x; + U2(1, =) +#if Z7_CRC_NUM_TABLES_USE >= 3 * 4 +#define U(r) U2(r, ^=) + U(2) +#if Z7_CRC_NUM_TABLES_USE >= 4 * 4 + U(3) +#if Z7_CRC_NUM_TABLES_USE >= 5 * 4 + U(4) +#if Z7_CRC_NUM_TABLES_USE >= 6 * 4 + U(5) +#if Z7_CRC_NUM_TABLES_USE >= 7 * 4 +#error Stop_Compiling_Bad_Z7_CRC_NUM_TABLES +#endif +#endif +#endif +#endif +#endif +#undef U +#undef U2 + v = x ^ Q(Z7_CRC_NUM_TABLES_USE / 4 - 1, v); +#endif + } + p += Z7_CRC_NUM_TABLES_USE; + } + while (p <= lim); + lim += Z7_CRC_NUM_TABLES_USE; } - for (; size > 0; size--, p++) + for (; p < lim; p++) v = CRC_UPDATE_BYTE_2_BE(v, *p); - return CRC_UINT32_SWAP(v); + return Z7_BSWAP32(v); } +#undef CRC_UPDATE_BYTE_2_BE +#undef R +#undef Q +#undef CRC_FUNC_PRE_BE +#undef CRC_FUNC_PRE_BE2 + +#endif +#undef Z7_CRC_NUM_TABLES_USE #endif diff --git a/src/plugins/7zip/7za/c/7zDec.c b/src/plugins/7zip/7za/c/7zDec.c index c45d6bf8..520cbfd8 100644 --- a/src/plugins/7zip/7za/c/7zDec.c +++ b/src/plugins/7zip/7za/c/7zDec.c @@ -1,11 +1,11 @@ /* 7zDec.c -- Decoding from 7z folder -2015-11-18 : Igor Pavlov : Public domain */ +: Igor Pavlov : Public domain */ #include "Precomp.h" #include -/* #define _7ZIP_PPMD_SUPPPORT */ +/* #define Z7_PPMD_SUPPORT */ #include "7z.h" #include "7zCrc.h" @@ -16,68 +16,94 @@ #include "Delta.h" #include "LzmaDec.h" #include "Lzma2Dec.h" -#ifdef _7ZIP_PPMD_SUPPPORT +#ifdef Z7_PPMD_SUPPORT #include "Ppmd7.h" #endif #define k_Copy 0 -#define k_Delta 3 +#ifndef Z7_NO_METHOD_LZMA2 #define k_LZMA2 0x21 +#endif #define k_LZMA 0x30101 -#define k_BCJ 0x3030103 #define k_BCJ2 0x303011B + +#if !defined(Z7_NO_METHODS_FILTERS) +#define Z7_USE_BRANCH_FILTER +#endif + +#if !defined(Z7_NO_METHODS_FILTERS) || \ + defined(Z7_USE_NATIVE_BRANCH_FILTER) && defined(MY_CPU_ARM64) +#define Z7_USE_FILTER_ARM64 +#ifndef Z7_USE_BRANCH_FILTER +#define Z7_USE_BRANCH_FILTER +#endif +#define k_ARM64 0xa +#endif + +#if !defined(Z7_NO_METHODS_FILTERS) || \ + defined(Z7_USE_NATIVE_BRANCH_FILTER) && defined(MY_CPU_ARMT) +#define Z7_USE_FILTER_ARMT +#ifndef Z7_USE_BRANCH_FILTER +#define Z7_USE_BRANCH_FILTER +#endif +#define k_ARMT 0x3030701 +#endif + +#ifndef Z7_NO_METHODS_FILTERS +#define k_Delta 3 +#define k_RISCV 0xb +#define k_BCJ 0x3030103 #define k_PPC 0x3030205 #define k_IA64 0x3030401 #define k_ARM 0x3030501 -#define k_ARMT 0x3030701 #define k_SPARC 0x3030805 +#endif - -#ifdef _7ZIP_PPMD_SUPPPORT +#ifdef Z7_PPMD_SUPPORT #define k_PPMD 0x30401 typedef struct { - IByteIn p; + IByteIn vt; const Byte *cur; const Byte *end; const Byte *begin; UInt64 processed; - Bool extra; + BoolInt extra; SRes res; - ILookInStream *inStream; + ILookInStreamPtr inStream; } CByteInToLook; -static Byte ReadByte(void *pp) +static Byte ReadByte(IByteInPtr pp) { - CByteInToLook *p = (CByteInToLook *)pp; + Z7_CONTAINER_FROM_VTBL_TO_DECL_VAR_pp_vt_p(CByteInToLook) if (p->cur != p->end) return *p->cur++; if (p->res == SZ_OK) { - size_t size = p->cur - p->begin; + size_t size = (size_t)(p->cur - p->begin); p->processed += size; - p->res = p->inStream->Skip(p->inStream, size); + p->res = ILookInStream_Skip(p->inStream, size); size = (1 << 25); - p->res = p->inStream->Look(p->inStream, (const void **)&p->begin, &size); + p->res = ILookInStream_Look(p->inStream, (const void **)&p->begin, &size); p->cur = p->begin; p->end = p->begin + size; if (size != 0) - return *p->cur++;; + return *p->cur++; } p->extra = True; return 0; } -static SRes SzDecodePpmd(const Byte *props, unsigned propsSize, UInt64 inSize, ILookInStream *inStream, - Byte *outBuffer, SizeT outSize, ISzAlloc *allocMain) +static SRes SzDecodePpmd(const Byte *props, unsigned propsSize, UInt64 inSize, ILookInStreamPtr inStream, + Byte *outBuffer, SizeT outSize, ISzAllocPtr allocMain) { CPpmd7 ppmd; CByteInToLook s; SRes res = SZ_OK; - s.p.Read = ReadByte; + s.vt.Read = ReadByte; s.inStream = inStream; s.begin = s.end = s.cur = NULL; s.extra = False; @@ -101,28 +127,32 @@ static SRes SzDecodePpmd(const Byte *props, unsigned propsSize, UInt64 inSize, I Ppmd7_Init(&ppmd, order); } { - CPpmd7z_RangeDec rc; - Ppmd7z_RangeDec_CreateVTable(&rc); - rc.Stream = &s.p; - if (!Ppmd7z_RangeDec_Init(&rc)) + ppmd.rc.dec.Stream = &s.vt; + if (!Ppmd7z_RangeDec_Init(&ppmd.rc.dec)) res = SZ_ERROR_DATA; - else if (s.extra) - res = (s.res != SZ_OK ? s.res : SZ_ERROR_DATA); - else + else if (!s.extra) { - SizeT i; - for (i = 0; i < outSize; i++) + Byte *buf = outBuffer; + const Byte *lim = buf + outSize; + for (; buf != lim; buf++) { - int sym = Ppmd7_DecodeSymbol(&ppmd, &rc.p); + int sym = Ppmd7z_DecodeSymbol(&ppmd); if (s.extra || sym < 0) break; - outBuffer[i] = (Byte)sym; + *buf = (Byte)sym; } - if (i != outSize) - res = (s.res != SZ_OK ? s.res : SZ_ERROR_DATA); - else if (s.processed + (s.cur - s.begin) != inSize || !Ppmd7z_RangeDec_IsFinishedOK(&rc)) + if (buf != lim) res = SZ_ERROR_DATA; + else if (!Ppmd7z_RangeDec_IsFinishedOK(&ppmd.rc.dec)) + { + /* if (Ppmd7z_DecodeSymbol(&ppmd) != PPMD7_SYM_END || !Ppmd7z_RangeDec_IsFinishedOK(&ppmd.rc.dec)) */ + res = SZ_ERROR_DATA; + } } + if (s.extra) + res = (s.res != SZ_OK ? s.res : SZ_ERROR_DATA); + else if (s.processed + (size_t)(s.cur - s.begin) != inSize) + res = SZ_ERROR_DATA; } Ppmd7_Free(&ppmd, allocMain); return res; @@ -131,14 +161,14 @@ static SRes SzDecodePpmd(const Byte *props, unsigned propsSize, UInt64 inSize, I #endif -static SRes SzDecodeLzma(const Byte *props, unsigned propsSize, UInt64 inSize, ILookInStream *inStream, - Byte *outBuffer, SizeT outSize, ISzAlloc *allocMain) +static SRes SzDecodeLzma(const Byte *props, unsigned propsSize, UInt64 inSize, ILookInStreamPtr inStream, + Byte *outBuffer, SizeT outSize, ISzAllocPtr allocMain) { CLzmaDec state; SRes res = SZ_OK; - LzmaDec_Construct(&state); - RINOK(LzmaDec_AllocateProbs(&state, props, propsSize, allocMain)); + LzmaDec_CONSTRUCT(&state) + RINOK(LzmaDec_AllocateProbs(&state, props, propsSize, allocMain)) state.dic = outBuffer; state.dicBufSize = outSize; LzmaDec_Init(&state); @@ -149,14 +179,14 @@ static SRes SzDecodeLzma(const Byte *props, unsigned propsSize, UInt64 inSize, I size_t lookahead = (1 << 18); if (lookahead > inSize) lookahead = (size_t)inSize; - res = inStream->Look(inStream, &inBuf, &lookahead); + res = ILookInStream_Look(inStream, &inBuf, &lookahead); if (res != SZ_OK) break; { SizeT inProcessed = (SizeT)lookahead, dicPos = state.dicPos; ELzmaStatus status; - res = LzmaDec_DecodeToDic(&state, outSize, inBuf, &inProcessed, LZMA_FINISH_END, &status); + res = LzmaDec_DecodeToDic(&state, outSize, (const Byte *)inBuf, &inProcessed, LZMA_FINISH_END, &status); lookahead -= inProcessed; inSize -= inProcessed; if (res != SZ_OK) @@ -178,7 +208,7 @@ static SRes SzDecodeLzma(const Byte *props, unsigned propsSize, UInt64 inSize, I break; } - res = inStream->Skip((void *)inStream, inProcessed); + res = ILookInStream_Skip(inStream, inProcessed); if (res != SZ_OK) break; } @@ -189,18 +219,18 @@ static SRes SzDecodeLzma(const Byte *props, unsigned propsSize, UInt64 inSize, I } -#ifndef _7Z_NO_METHOD_LZMA2 +#ifndef Z7_NO_METHOD_LZMA2 -static SRes SzDecodeLzma2(const Byte *props, unsigned propsSize, UInt64 inSize, ILookInStream *inStream, - Byte *outBuffer, SizeT outSize, ISzAlloc *allocMain) +static SRes SzDecodeLzma2(const Byte *props, unsigned propsSize, UInt64 inSize, ILookInStreamPtr inStream, + Byte *outBuffer, SizeT outSize, ISzAllocPtr allocMain) { CLzma2Dec state; SRes res = SZ_OK; - Lzma2Dec_Construct(&state); + Lzma2Dec_CONSTRUCT(&state) if (propsSize != 1) return SZ_ERROR_DATA; - RINOK(Lzma2Dec_AllocateProbs(&state, props[0], allocMain)); + RINOK(Lzma2Dec_AllocateProbs(&state, props[0], allocMain)) state.decoder.dic = outBuffer; state.decoder.dicBufSize = outSize; Lzma2Dec_Init(&state); @@ -211,14 +241,14 @@ static SRes SzDecodeLzma2(const Byte *props, unsigned propsSize, UInt64 inSize, size_t lookahead = (1 << 18); if (lookahead > inSize) lookahead = (size_t)inSize; - res = inStream->Look(inStream, &inBuf, &lookahead); + res = ILookInStream_Look(inStream, &inBuf, &lookahead); if (res != SZ_OK) break; { SizeT inProcessed = (SizeT)lookahead, dicPos = state.decoder.dicPos; ELzmaStatus status; - res = Lzma2Dec_DecodeToDic(&state, outSize, inBuf, &inProcessed, LZMA_FINISH_END, &status); + res = Lzma2Dec_DecodeToDic(&state, outSize, (const Byte *)inBuf, &inProcessed, LZMA_FINISH_END, &status); lookahead -= inProcessed; inSize -= inProcessed; if (res != SZ_OK) @@ -237,7 +267,7 @@ static SRes SzDecodeLzma2(const Byte *props, unsigned propsSize, UInt64 inSize, break; } - res = inStream->Skip((void *)inStream, inProcessed); + res = ILookInStream_Skip(inStream, inProcessed); if (res != SZ_OK) break; } @@ -250,7 +280,7 @@ static SRes SzDecodeLzma2(const Byte *props, unsigned propsSize, UInt64 inSize, #endif -static SRes SzDecodeCopy(UInt64 inSize, ILookInStream *inStream, Byte *outBuffer) +static SRes SzDecodeCopy(UInt64 inSize, ILookInStreamPtr inStream, Byte *outBuffer) { while (inSize > 0) { @@ -258,35 +288,36 @@ static SRes SzDecodeCopy(UInt64 inSize, ILookInStream *inStream, Byte *outBuffer size_t curSize = (1 << 18); if (curSize > inSize) curSize = (size_t)inSize; - RINOK(inStream->Look(inStream, &inBuf, &curSize)); + RINOK(ILookInStream_Look(inStream, &inBuf, &curSize)) if (curSize == 0) return SZ_ERROR_INPUT_EOF; memcpy(outBuffer, inBuf, curSize); outBuffer += curSize; inSize -= curSize; - RINOK(inStream->Skip((void *)inStream, curSize)); + RINOK(ILookInStream_Skip(inStream, curSize)) } return SZ_OK; } -static Bool IS_MAIN_METHOD(UInt32 m) +static BoolInt IS_MAIN_METHOD(UInt32 m) { switch (m) { case k_Copy: case k_LZMA: - #ifndef _7Z_NO_METHOD_LZMA2 + #ifndef Z7_NO_METHOD_LZMA2 case k_LZMA2: - #endif - #ifdef _7ZIP_PPMD_SUPPPORT + #endif + #ifdef Z7_PPMD_SUPPORT case k_PPMD: - #endif + #endif return True; + default: + return False; } - return False; } -static Bool IS_SUPPORTED_CODER(const CSzCoderInfo *c) +static BoolInt IS_SUPPORTED_CODER(const CSzCoderInfo *c) { return c->NumStreams == 1 @@ -310,7 +341,7 @@ static SRes CheckSupportedFolder(const CSzFolder *f) } - #ifndef _7Z_NO_METHODS_FILTERS + #if defined(Z7_USE_BRANCH_FILTER) if (f->NumCoders == 2) { @@ -326,13 +357,21 @@ static SRes CheckSupportedFolder(const CSzFolder *f) return SZ_ERROR_UNSUPPORTED; switch ((UInt32)c->MethodID) { + #if !defined(Z7_NO_METHODS_FILTERS) case k_Delta: case k_BCJ: case k_PPC: case k_IA64: case k_SPARC: case k_ARM: + case k_RISCV: + #endif + #ifdef Z7_USE_FILTER_ARM64 + case k_ARM64: + #endif + #ifdef Z7_USE_FILTER_ARMT case k_ARMT: + #endif break; default: return SZ_ERROR_UNSUPPORTED; @@ -365,14 +404,17 @@ static SRes CheckSupportedFolder(const CSzFolder *f) return SZ_ERROR_UNSUPPORTED; } -#define CASE_BRA_CONV(isa) case k_ ## isa: isa ## _Convert(outBuffer, outSize, 0, 0); break; + + + + static SRes SzFolder_Decode2(const CSzFolder *folder, const Byte *propsData, const UInt64 *unpackSizes, const UInt64 *packPositions, - ILookInStream *inStream, UInt64 startPos, - Byte *outBuffer, SizeT outSize, ISzAlloc *allocMain, + ILookInStreamPtr inStream, UInt64 startPos, + Byte *outBuffer, SizeT outSize, ISzAllocPtr allocMain, Byte *tempBuf[]) { UInt32 ci; @@ -380,7 +422,7 @@ static SRes SzFolder_Decode2(const CSzFolder *folder, SizeT tempSize3 = 0; Byte *tempBuf3 = 0; - RINOK(CheckSupportedFolder(folder)); + RINOK(CheckSupportedFolder(folder)) for (ci = 0; ci < folder->NumCoders; ci++) { @@ -395,8 +437,8 @@ static SRes SzFolder_Decode2(const CSzFolder *folder, SizeT outSizeCur = outSize; if (folder->NumCoders == 4) { - UInt32 indices[] = { 3, 2, 0 }; - UInt64 unpackSize = unpackSizes[ci]; + const UInt32 indices[] = { 3, 2, 0 }; + const UInt64 unpackSize = unpackSizes[ci]; si = indices[ci]; if (ci < 2) { @@ -404,7 +446,7 @@ static SRes SzFolder_Decode2(const CSzFolder *folder, outSizeCur = (SizeT)unpackSize; if (outSizeCur != unpackSize) return SZ_ERROR_MEM; - temp = (Byte *)IAlloc_Alloc(allocMain, outSizeCur); + temp = (Byte *)ISzAlloc_Alloc(allocMain, outSizeCur); if (!temp && outSizeCur != 0) return SZ_ERROR_MEM; outBufCur = tempBuf[1 - ci] = temp; @@ -421,38 +463,38 @@ static SRes SzFolder_Decode2(const CSzFolder *folder, return SZ_ERROR_UNSUPPORTED; } offset = packPositions[si]; - inSize = packPositions[si + 1] - offset; - RINOK(LookInStream_SeekTo(inStream, startPos + offset)); + inSize = packPositions[(size_t)si + 1] - offset; + RINOK(LookInStream_SeekTo(inStream, startPos + offset)) if (coder->MethodID == k_Copy) { if (inSize != outSizeCur) /* check it */ return SZ_ERROR_DATA; - RINOK(SzDecodeCopy(inSize, inStream, outBufCur)); + RINOK(SzDecodeCopy(inSize, inStream, outBufCur)) } else if (coder->MethodID == k_LZMA) { - RINOK(SzDecodeLzma(propsData + coder->PropsOffset, coder->PropsSize, inSize, inStream, outBufCur, outSizeCur, allocMain)); + RINOK(SzDecodeLzma(propsData + coder->PropsOffset, coder->PropsSize, inSize, inStream, outBufCur, outSizeCur, allocMain)) } - #ifndef _7Z_NO_METHOD_LZMA2 + #ifndef Z7_NO_METHOD_LZMA2 else if (coder->MethodID == k_LZMA2) { - RINOK(SzDecodeLzma2(propsData + coder->PropsOffset, coder->PropsSize, inSize, inStream, outBufCur, outSizeCur, allocMain)); + RINOK(SzDecodeLzma2(propsData + coder->PropsOffset, coder->PropsSize, inSize, inStream, outBufCur, outSizeCur, allocMain)) } - #endif - #ifdef _7ZIP_PPMD_SUPPPORT + #endif + #ifdef Z7_PPMD_SUPPORT else if (coder->MethodID == k_PPMD) { - RINOK(SzDecodePpmd(propsData + coder->PropsOffset, coder->PropsSize, inSize, inStream, outBufCur, outSizeCur, allocMain)); + RINOK(SzDecodePpmd(propsData + coder->PropsOffset, coder->PropsSize, inSize, inStream, outBufCur, outSizeCur, allocMain)) } - #endif + #endif else return SZ_ERROR_UNSUPPORTED; } else if (coder->MethodID == k_BCJ2) { - UInt64 offset = packPositions[1]; - UInt64 s3Size = packPositions[2] - offset; + const UInt64 offset = packPositions[1]; + const UInt64 s3Size = packPositions[2] - offset; if (ci != 3) return SZ_ERROR_UNSUPPORTED; @@ -460,12 +502,12 @@ static SRes SzFolder_Decode2(const CSzFolder *folder, tempSizes[2] = (SizeT)s3Size; if (tempSizes[2] != s3Size) return SZ_ERROR_MEM; - tempBuf[2] = (Byte *)IAlloc_Alloc(allocMain, tempSizes[2]); + tempBuf[2] = (Byte *)ISzAlloc_Alloc(allocMain, tempSizes[2]); if (!tempBuf[2] && tempSizes[2] != 0) return SZ_ERROR_MEM; - RINOK(LookInStream_SeekTo(inStream, startPos + offset)); - RINOK(SzDecodeCopy(s3Size, inStream, tempBuf[2])); + RINOK(LookInStream_SeekTo(inStream, startPos + offset)) + RINOK(SzDecodeCopy(s3Size, inStream, tempBuf[2])) if ((tempSizes[0] & 3) != 0 || (tempSizes[1] & 3) != 0 || @@ -484,26 +526,22 @@ static SRes SzFolder_Decode2(const CSzFolder *folder, p.destLim = outBuffer + outSize; Bcj2Dec_Init(&p); - RINOK(Bcj2Dec_Decode(&p)); + RINOK(Bcj2Dec_Decode(&p)) { unsigned i; for (i = 0; i < 4; i++) if (p.bufs[i] != p.lims[i]) return SZ_ERROR_DATA; - - if (!Bcj2Dec_IsFinished(&p)) - return SZ_ERROR_DATA; - - if (p.dest != p.destLim - || p.state != BCJ2_STREAM_MAIN) + if (p.dest != p.destLim || !Bcj2Dec_IsMaybeFinished(&p)) return SZ_ERROR_DATA; } } } - #ifndef _7Z_NO_METHODS_FILTERS +#if defined(Z7_USE_BRANCH_FILTER) else if (ci == 1) { +#if !defined(Z7_NO_METHODS_FILTERS) if (coder->MethodID == k_Delta) { if (coder->PropsSize != 1) @@ -513,31 +551,75 @@ static SRes SzFolder_Decode2(const CSzFolder *folder, Delta_Init(state); Delta_Decode(state, (unsigned)(propsData[coder->PropsOffset]) + 1, outBuffer, outSize); } + continue; } - else +#endif + +#ifdef Z7_USE_FILTER_ARM64 + if (coder->MethodID == k_ARM64) + { + UInt32 pc = 0; + if (coder->PropsSize == 4) + { + pc = GetUi32(propsData + coder->PropsOffset); + if (pc & 3) + return SZ_ERROR_UNSUPPORTED; + } + else if (coder->PropsSize != 0) + return SZ_ERROR_UNSUPPORTED; + z7_BranchConv_ARM64_Dec(outBuffer, outSize, pc); + continue; + } +#endif + +#if !defined(Z7_NO_METHODS_FILTERS) + if (coder->MethodID == k_RISCV) + { + UInt32 pc = 0; + if (coder->PropsSize == 4) + { + pc = GetUi32(propsData + coder->PropsOffset); + if (pc & 1) + return SZ_ERROR_UNSUPPORTED; + } + else if (coder->PropsSize != 0) + return SZ_ERROR_UNSUPPORTED; + z7_BranchConv_RISCV_Dec(outBuffer, outSize, pc); + continue; + } +#endif + +#if !defined(Z7_NO_METHODS_FILTERS) || defined(Z7_USE_FILTER_ARMT) { if (coder->PropsSize != 0) return SZ_ERROR_UNSUPPORTED; + #define CASE_BRA_CONV(isa) case k_ ## isa: Z7_BRANCH_CONV_DEC(isa)(outBuffer, outSize, 0); break; // pc = 0; switch (coder->MethodID) { + #if !defined(Z7_NO_METHODS_FILTERS) case k_BCJ: { - UInt32 state; - x86_Convert_Init(state); - x86_Convert(outBuffer, outSize, 0, &state, 0); + UInt32 state = Z7_BRANCH_CONV_ST_X86_STATE_INIT_VAL; + z7_BranchConvSt_X86_Dec(outBuffer, outSize, 0, &state); // pc = 0 break; } - CASE_BRA_CONV(PPC) + case k_PPC: Z7_BRANCH_CONV_DEC_2(BranchConv_PPC)(outBuffer, outSize, 0); break; // pc = 0; + // CASE_BRA_CONV(PPC) CASE_BRA_CONV(IA64) CASE_BRA_CONV(SPARC) CASE_BRA_CONV(ARM) + #endif + #if !defined(Z7_NO_METHODS_FILTERS) || defined(Z7_USE_FILTER_ARMT) CASE_BRA_CONV(ARMT) + #endif default: return SZ_ERROR_UNSUPPORTED; } + continue; } - } - #endif +#endif + } // (c == 1) +#endif // Z7_USE_BRANCH_FILTER else return SZ_ERROR_UNSUPPORTED; } @@ -547,9 +629,9 @@ static SRes SzFolder_Decode2(const CSzFolder *folder, SRes SzAr_DecodeFolder(const CSzAr *p, UInt32 folderIndex, - ILookInStream *inStream, UInt64 startPos, + ILookInStreamPtr inStream, UInt64 startPos, Byte *outBuffer, size_t outSize, - ISzAlloc *allocMain) + ISzAllocPtr allocMain) { SRes res; CSzFolder folder; @@ -557,7 +639,7 @@ SRes SzAr_DecodeFolder(const CSzAr *p, UInt32 folderIndex, const Byte *data = p->CodersData + p->FoCodersOffsets[folderIndex]; sd.Data = data; - sd.Size = p->FoCodersOffsets[folderIndex + 1] - p->FoCodersOffsets[folderIndex]; + sd.Size = p->FoCodersOffsets[(size_t)folderIndex + 1] - p->FoCodersOffsets[folderIndex]; res = SzGetNextFolderItem(&folder, &sd); @@ -579,7 +661,7 @@ SRes SzAr_DecodeFolder(const CSzAr *p, UInt32 folderIndex, outBuffer, (SizeT)outSize, allocMain, tempBuf); for (i = 0; i < 3; i++) - IAlloc_Free(allocMain, tempBuf[i]); + ISzAlloc_Free(allocMain, tempBuf[i]); if (res == SZ_OK) if (SzBitWithVals_Check(&p->FolderCRCs, folderIndex)) diff --git a/src/plugins/7zip/7za/c/7zFile.c b/src/plugins/7zip/7za/c/7zFile.c index 041e5b15..ba5daa13 100644 --- a/src/plugins/7zip/7za/c/7zFile.c +++ b/src/plugins/7zip/7za/c/7zFile.c @@ -1,5 +1,5 @@ /* 7zFile.c -- File IO -2009-11-24 : Igor Pavlov : Public domain */ +2023-04-02 : Igor Pavlov : Public domain */ #include "Precomp.h" @@ -7,9 +7,19 @@ #ifndef USE_WINDOWS_FILE -#ifndef UNDER_CE -#include -#endif + #include + + #ifndef USE_FOPEN + #include + #include + #ifdef _WIN32 + #include + typedef int ssize_t; + typedef int off_t; + #else + #include + #endif + #endif #else @@ -23,30 +33,36 @@ And message can be "Network connection was lost" */ -#define kChunkSizeMax (1 << 22) - #endif +#define kChunkSizeMax (1 << 22) + void File_Construct(CSzFile *p) { #ifdef USE_WINDOWS_FILE p->handle = INVALID_HANDLE_VALUE; - #else + #elif defined(USE_FOPEN) p->file = NULL; + #else + p->fd = -1; #endif } #if !defined(UNDER_CE) || !defined(USE_WINDOWS_FILE) + static WRes File_Open(CSzFile *p, const char *name, int writeMode) { #ifdef USE_WINDOWS_FILE + p->handle = CreateFileA(name, writeMode ? GENERIC_WRITE : GENERIC_READ, FILE_SHARE_READ, NULL, writeMode ? CREATE_ALWAYS : OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); return (p->handle != INVALID_HANDLE_VALUE) ? 0 : GetLastError(); - #else + + #elif defined(USE_FOPEN) + p->file = fopen(name, writeMode ? "wb+" : "rb"); return (p->file != 0) ? 0 : #ifdef UNDER_CE @@ -54,13 +70,34 @@ static WRes File_Open(CSzFile *p, const char *name, int writeMode) #else errno; #endif + + #else + + int flags = (writeMode ? (O_CREAT | O_EXCL | O_WRONLY) : O_RDONLY); + #ifdef O_BINARY + flags |= O_BINARY; + #endif + p->fd = open(name, flags, 0666); + return (p->fd != -1) ? 0 : errno; + #endif } WRes InFile_Open(CSzFile *p, const char *name) { return File_Open(p, name, 0); } -WRes OutFile_Open(CSzFile *p, const char *name) { return File_Open(p, name, 1); } + +WRes OutFile_Open(CSzFile *p, const char *name) +{ + #if defined(USE_WINDOWS_FILE) || defined(USE_FOPEN) + return File_Open(p, name, 1); + #else + p->fd = creat(name, 0666); + return (p->fd != -1) ? 0 : errno; + #endif +} + #endif + #ifdef USE_WINDOWS_FILE static WRes File_OpenW(CSzFile *p, const WCHAR *name, int writeMode) { @@ -78,74 +115,124 @@ WRes OutFile_OpenW(CSzFile *p, const WCHAR *name) { return File_OpenW(p, name, 1 WRes File_Close(CSzFile *p) { #ifdef USE_WINDOWS_FILE + if (p->handle != INVALID_HANDLE_VALUE) { if (!CloseHandle(p->handle)) return GetLastError(); p->handle = INVALID_HANDLE_VALUE; } - #else + + #elif defined(USE_FOPEN) + if (p->file != NULL) { int res = fclose(p->file); if (res != 0) + { + if (res == EOF) + return errno; return res; + } p->file = NULL; } + + #else + + if (p->fd != -1) + { + if (close(p->fd) != 0) + return errno; + p->fd = -1; + } + #endif + return 0; } + WRes File_Read(CSzFile *p, void *data, size_t *size) { size_t originalSize = *size; + *size = 0; if (originalSize == 0) return 0; #ifdef USE_WINDOWS_FILE - *size = 0; do { - DWORD curSize = (originalSize > kChunkSizeMax) ? kChunkSizeMax : (DWORD)originalSize; + const DWORD curSize = (originalSize > kChunkSizeMax) ? kChunkSizeMax : (DWORD)originalSize; DWORD processed = 0; - BOOL res = ReadFile(p->handle, data, curSize, &processed, NULL); + const BOOL res = ReadFile(p->handle, data, curSize, &processed, NULL); data = (void *)((Byte *)data + processed); originalSize -= processed; *size += processed; if (!res) return GetLastError(); + // debug : we can break here for partial reading mode + if (processed == 0) + break; + } + while (originalSize > 0); + + #elif defined(USE_FOPEN) + + do + { + const size_t curSize = (originalSize > kChunkSizeMax) ? kChunkSizeMax : originalSize; + const size_t processed = fread(data, 1, curSize, p->file); + data = (void *)((Byte *)data + (size_t)processed); + originalSize -= processed; + *size += processed; + if (processed != curSize) + return ferror(p->file); + // debug : we can break here for partial reading mode if (processed == 0) break; } while (originalSize > 0); - return 0; #else - - *size = fread(data, 1, originalSize, p->file); - if (*size == originalSize) - return 0; - return ferror(p->file); - + + do + { + const size_t curSize = (originalSize > kChunkSizeMax) ? kChunkSizeMax : originalSize; + const ssize_t processed = read(p->fd, data, curSize); + if (processed == -1) + return errno; + if (processed == 0) + break; + data = (void *)((Byte *)data + (size_t)processed); + originalSize -= (size_t)processed; + *size += (size_t)processed; + // debug : we can break here for partial reading mode + // break; + } + while (originalSize > 0); + #endif + + return 0; } + WRes File_Write(CSzFile *p, const void *data, size_t *size) { size_t originalSize = *size; + *size = 0; if (originalSize == 0) return 0; #ifdef USE_WINDOWS_FILE - *size = 0; do { - DWORD curSize = (originalSize > kChunkSizeMax) ? kChunkSizeMax : (DWORD)originalSize; + const DWORD curSize = (originalSize > kChunkSizeMax) ? kChunkSizeMax : (DWORD)originalSize; DWORD processed = 0; - BOOL res = WriteFile(p->handle, data, curSize, &processed, NULL); - data = (void *)((Byte *)data + processed); + const BOOL res = WriteFile(p->handle, data, curSize, &processed, NULL); + data = (const void *)((const Byte *)data + processed); originalSize -= processed; *size += processed; if (!res) @@ -154,61 +241,106 @@ WRes File_Write(CSzFile *p, const void *data, size_t *size) break; } while (originalSize > 0); - return 0; + + #elif defined(USE_FOPEN) + + do + { + const size_t curSize = (originalSize > kChunkSizeMax) ? kChunkSizeMax : originalSize; + const size_t processed = fwrite(data, 1, curSize, p->file); + data = (void *)((Byte *)data + (size_t)processed); + originalSize -= processed; + *size += processed; + if (processed != curSize) + return ferror(p->file); + if (processed == 0) + break; + } + while (originalSize > 0); #else - *size = fwrite(data, 1, originalSize, p->file); - if (*size == originalSize) - return 0; - return ferror(p->file); - + do + { + const size_t curSize = (originalSize > kChunkSizeMax) ? kChunkSizeMax : originalSize; + const ssize_t processed = write(p->fd, data, curSize); + if (processed == -1) + return errno; + if (processed == 0) + break; + data = (const void *)((const Byte *)data + (size_t)processed); + originalSize -= (size_t)processed; + *size += (size_t)processed; + } + while (originalSize > 0); + #endif + + return 0; } + WRes File_Seek(CSzFile *p, Int64 *pos, ESzSeek origin) { #ifdef USE_WINDOWS_FILE - LARGE_INTEGER value; DWORD moveMethod; - value.LowPart = (DWORD)*pos; - value.HighPart = (LONG)((UInt64)*pos >> 16 >> 16); /* for case when UInt64 is 32-bit only */ - switch (origin) + UInt32 low = (UInt32)*pos; + LONG high = (LONG)((UInt64)*pos >> 16 >> 16); /* for case when UInt64 is 32-bit only */ + // (int) to eliminate clang warning + switch ((int)origin) { case SZ_SEEK_SET: moveMethod = FILE_BEGIN; break; case SZ_SEEK_CUR: moveMethod = FILE_CURRENT; break; case SZ_SEEK_END: moveMethod = FILE_END; break; default: return ERROR_INVALID_PARAMETER; } - value.LowPart = SetFilePointer(p->handle, value.LowPart, &value.HighPart, moveMethod); - if (value.LowPart == 0xFFFFFFFF) + low = SetFilePointer(p->handle, (LONG)low, &high, moveMethod); + if (low == (UInt32)0xFFFFFFFF) { WRes res = GetLastError(); if (res != NO_ERROR) return res; } - *pos = ((Int64)value.HighPart << 32) | value.LowPart; + *pos = ((Int64)high << 32) | low; return 0; #else - int moveMethod; - int res; - switch (origin) + int moveMethod; // = origin; + + switch ((int)origin) { case SZ_SEEK_SET: moveMethod = SEEK_SET; break; case SZ_SEEK_CUR: moveMethod = SEEK_CUR; break; case SZ_SEEK_END: moveMethod = SEEK_END; break; - default: return 1; + default: return EINVAL; } - res = fseek(p->file, (long)*pos, moveMethod); - *pos = ftell(p->file); - return res; - #endif + #if defined(USE_FOPEN) + { + int res = fseek(p->file, (long)*pos, moveMethod); + if (res == -1) + return errno; + *pos = ftell(p->file); + if (*pos == -1) + return errno; + return 0; + } + #else + { + off_t res = lseek(p->fd, (off_t)*pos, moveMethod); + if (res == -1) + return errno; + *pos = res; + return 0; + } + + #endif // USE_FOPEN + #endif // USE_WINDOWS_FILE } + WRes File_GetLength(CSzFile *p, UInt64 *length) { #ifdef USE_WINDOWS_FILE @@ -224,13 +356,31 @@ WRes File_GetLength(CSzFile *p, UInt64 *length) *length = (((UInt64)sizeHigh) << 32) + sizeLow; return 0; - #else + #elif defined(USE_FOPEN) long pos = ftell(p->file); int res = fseek(p->file, 0, SEEK_END); *length = ftell(p->file); fseek(p->file, pos, SEEK_SET); return res; + + #else + + off_t pos; + *length = 0; + pos = lseek(p->fd, 0, SEEK_CUR); + if (pos != -1) + { + const off_t len2 = lseek(p->fd, 0, SEEK_END); + const off_t res2 = lseek(p->fd, pos, SEEK_SET); + if (len2 != -1) + { + *length = (UInt64)len2; + if (res2 != -1) + return 0; + } + } + return errno; #endif } @@ -238,49 +388,56 @@ WRes File_GetLength(CSzFile *p, UInt64 *length) /* ---------- FileSeqInStream ---------- */ -static SRes FileSeqInStream_Read(void *pp, void *buf, size_t *size) +static SRes FileSeqInStream_Read(ISeqInStreamPtr pp, void *buf, size_t *size) { - CFileSeqInStream *p = (CFileSeqInStream *)pp; - return File_Read(&p->file, buf, size) == 0 ? SZ_OK : SZ_ERROR_READ; + Z7_CONTAINER_FROM_VTBL_TO_DECL_VAR_pp_vt_p(CFileSeqInStream) + const WRes wres = File_Read(&p->file, buf, size); + p->wres = wres; + return (wres == 0) ? SZ_OK : SZ_ERROR_READ; } void FileSeqInStream_CreateVTable(CFileSeqInStream *p) { - p->s.Read = FileSeqInStream_Read; + p->vt.Read = FileSeqInStream_Read; } /* ---------- FileInStream ---------- */ -static SRes FileInStream_Read(void *pp, void *buf, size_t *size) +static SRes FileInStream_Read(ISeekInStreamPtr pp, void *buf, size_t *size) { - CFileInStream *p = (CFileInStream *)pp; - return (File_Read(&p->file, buf, size) == 0) ? SZ_OK : SZ_ERROR_READ; + Z7_CONTAINER_FROM_VTBL_TO_DECL_VAR_pp_vt_p(CFileInStream) + const WRes wres = File_Read(&p->file, buf, size); + p->wres = wres; + return (wres == 0) ? SZ_OK : SZ_ERROR_READ; } -static SRes FileInStream_Seek(void *pp, Int64 *pos, ESzSeek origin) +static SRes FileInStream_Seek(ISeekInStreamPtr pp, Int64 *pos, ESzSeek origin) { - CFileInStream *p = (CFileInStream *)pp; - return File_Seek(&p->file, pos, origin); + Z7_CONTAINER_FROM_VTBL_TO_DECL_VAR_pp_vt_p(CFileInStream) + const WRes wres = File_Seek(&p->file, pos, origin); + p->wres = wres; + return (wres == 0) ? SZ_OK : SZ_ERROR_READ; } void FileInStream_CreateVTable(CFileInStream *p) { - p->s.Read = FileInStream_Read; - p->s.Seek = FileInStream_Seek; + p->vt.Read = FileInStream_Read; + p->vt.Seek = FileInStream_Seek; } /* ---------- FileOutStream ---------- */ -static size_t FileOutStream_Write(void *pp, const void *data, size_t size) +static size_t FileOutStream_Write(ISeqOutStreamPtr pp, const void *data, size_t size) { - CFileOutStream *p = (CFileOutStream *)pp; - File_Write(&p->file, data, &size); + Z7_CONTAINER_FROM_VTBL_TO_DECL_VAR_pp_vt_p(CFileOutStream) + const WRes wres = File_Write(&p->file, data, &size); + p->wres = wres; return size; } void FileOutStream_CreateVTable(CFileOutStream *p) { - p->s.Write = FileOutStream_Write; + p->vt.Write = FileOutStream_Write; } diff --git a/src/plugins/7zip/7za/c/7zFile.h b/src/plugins/7zip/7za/c/7zFile.h index 658987ed..c842f919 100644 --- a/src/plugins/7zip/7za/c/7zFile.h +++ b/src/plugins/7zip/7za/c/7zFile.h @@ -1,17 +1,20 @@ /* 7zFile.h -- File IO -2013-01-18 : Igor Pavlov : Public domain */ +: Igor Pavlov : Public domain */ -#ifndef __7Z_FILE_H -#define __7Z_FILE_H +#ifndef ZIP7_INC_FILE_H +#define ZIP7_INC_FILE_H #ifdef _WIN32 #define USE_WINDOWS_FILE #endif #ifdef USE_WINDOWS_FILE -#include +#include "7zWindows.h" + #else -#include +// note: USE_FOPEN mode is limited to 32-bit file size +// #define USE_FOPEN +// #include #endif #include "7zTypes.h" @@ -24,8 +27,10 @@ typedef struct { #ifdef USE_WINDOWS_FILE HANDLE handle; - #else + #elif defined(USE_FOPEN) FILE *file; + #else + int fd; #endif } CSzFile; @@ -54,8 +59,9 @@ WRes File_GetLength(CSzFile *p, UInt64 *length); typedef struct { - ISeqInStream s; + ISeqInStream vt; CSzFile file; + WRes wres; } CFileSeqInStream; void FileSeqInStream_CreateVTable(CFileSeqInStream *p); @@ -63,8 +69,9 @@ void FileSeqInStream_CreateVTable(CFileSeqInStream *p); typedef struct { - ISeekInStream s; + ISeekInStream vt; CSzFile file; + WRes wres; } CFileInStream; void FileInStream_CreateVTable(CFileInStream *p); @@ -72,8 +79,9 @@ void FileInStream_CreateVTable(CFileInStream *p); typedef struct { - ISeqOutStream s; + ISeqOutStream vt; CSzFile file; + WRes wres; } CFileOutStream; void FileOutStream_CreateVTable(CFileOutStream *p); diff --git a/src/plugins/7zip/7za/c/7zStream.c b/src/plugins/7zip/7za/c/7zStream.c index 88f9c42b..74e75b65 100644 --- a/src/plugins/7zip/7za/c/7zStream.c +++ b/src/plugins/7zip/7za/c/7zStream.c @@ -1,5 +1,5 @@ /* 7zStream.c -- 7z Stream functions -2013-11-12 : Igor Pavlov : Public domain */ +2023-04-02 : Igor Pavlov : Public domain */ #include "Precomp.h" @@ -7,12 +7,33 @@ #include "7zTypes.h" -SRes SeqInStream_Read2(ISeqInStream *stream, void *buf, size_t size, SRes errorType) + +SRes SeqInStream_ReadMax(ISeqInStreamPtr stream, void *buf, size_t *processedSize) +{ + size_t size = *processedSize; + *processedSize = 0; + while (size != 0) + { + size_t cur = size; + const SRes res = ISeqInStream_Read(stream, buf, &cur); + *processedSize += cur; + buf = (void *)((Byte *)buf + cur); + size -= cur; + if (res != SZ_OK) + return res; + if (cur == 0) + return SZ_OK; + } + return SZ_OK; +} + +/* +SRes SeqInStream_Read2(ISeqInStreamPtr stream, void *buf, size_t size, SRes errorType) { while (size != 0) { size_t processed = size; - RINOK(stream->Read(stream, buf, &processed)); + RINOK(ISeqInStream_Read(stream, buf, &processed)) if (processed == 0) return errorType; buf = (void *)((Byte *)buf + processed); @@ -21,40 +42,44 @@ SRes SeqInStream_Read2(ISeqInStream *stream, void *buf, size_t size, SRes errorT return SZ_OK; } -SRes SeqInStream_Read(ISeqInStream *stream, void *buf, size_t size) +SRes SeqInStream_Read(ISeqInStreamPtr stream, void *buf, size_t size) { return SeqInStream_Read2(stream, buf, size, SZ_ERROR_INPUT_EOF); } +*/ + -SRes SeqInStream_ReadByte(ISeqInStream *stream, Byte *buf) +SRes SeqInStream_ReadByte(ISeqInStreamPtr stream, Byte *buf) { size_t processed = 1; - RINOK(stream->Read(stream, buf, &processed)); + RINOK(ISeqInStream_Read(stream, buf, &processed)) return (processed == 1) ? SZ_OK : SZ_ERROR_INPUT_EOF; } -SRes LookInStream_SeekTo(ILookInStream *stream, UInt64 offset) + + +SRes LookInStream_SeekTo(ILookInStreamPtr stream, UInt64 offset) { - Int64 t = offset; - return stream->Seek(stream, &t, SZ_SEEK_SET); + Int64 t = (Int64)offset; + return ILookInStream_Seek(stream, &t, SZ_SEEK_SET); } -SRes LookInStream_LookRead(ILookInStream *stream, void *buf, size_t *size) +SRes LookInStream_LookRead(ILookInStreamPtr stream, void *buf, size_t *size) { const void *lookBuf; if (*size == 0) return SZ_OK; - RINOK(stream->Look(stream, &lookBuf, size)); + RINOK(ILookInStream_Look(stream, &lookBuf, size)) memcpy(buf, lookBuf, *size); - return stream->Skip(stream, *size); + return ILookInStream_Skip(stream, *size); } -SRes LookInStream_Read2(ILookInStream *stream, void *buf, size_t size, SRes errorType) +SRes LookInStream_Read2(ILookInStreamPtr stream, void *buf, size_t size, SRes errorType) { while (size != 0) { size_t processed = size; - RINOK(stream->Read(stream, buf, &processed)); + RINOK(ILookInStream_Read(stream, buf, &processed)) if (processed == 0) return errorType; buf = (void *)((Byte *)buf + processed); @@ -63,61 +88,67 @@ SRes LookInStream_Read2(ILookInStream *stream, void *buf, size_t size, SRes erro return SZ_OK; } -SRes LookInStream_Read(ILookInStream *stream, void *buf, size_t size) +SRes LookInStream_Read(ILookInStreamPtr stream, void *buf, size_t size) { return LookInStream_Read2(stream, buf, size, SZ_ERROR_INPUT_EOF); } -static SRes LookToRead_Look_Lookahead(void *pp, const void **buf, size_t *size) + + +#define GET_LookToRead2 Z7_CONTAINER_FROM_VTBL_TO_DECL_VAR_pp_vt_p(CLookToRead2) + +static SRes LookToRead2_Look_Lookahead(ILookInStreamPtr pp, const void **buf, size_t *size) { SRes res = SZ_OK; - CLookToRead *p = (CLookToRead *)pp; + GET_LookToRead2 size_t size2 = p->size - p->pos; - if (size2 == 0 && *size > 0) + if (size2 == 0 && *size != 0) { p->pos = 0; - size2 = LookToRead_BUF_SIZE; - res = p->realStream->Read(p->realStream, p->buf, &size2); + p->size = 0; + size2 = p->bufSize; + res = ISeekInStream_Read(p->realStream, p->buf, &size2); p->size = size2; } - if (size2 < *size) + if (*size > size2) *size = size2; *buf = p->buf + p->pos; return res; } -static SRes LookToRead_Look_Exact(void *pp, const void **buf, size_t *size) +static SRes LookToRead2_Look_Exact(ILookInStreamPtr pp, const void **buf, size_t *size) { SRes res = SZ_OK; - CLookToRead *p = (CLookToRead *)pp; + GET_LookToRead2 size_t size2 = p->size - p->pos; - if (size2 == 0 && *size > 0) + if (size2 == 0 && *size != 0) { p->pos = 0; - if (*size > LookToRead_BUF_SIZE) - *size = LookToRead_BUF_SIZE; - res = p->realStream->Read(p->realStream, p->buf, size); + p->size = 0; + if (*size > p->bufSize) + *size = p->bufSize; + res = ISeekInStream_Read(p->realStream, p->buf, size); size2 = p->size = *size; } - if (size2 < *size) + if (*size > size2) *size = size2; *buf = p->buf + p->pos; return res; } -static SRes LookToRead_Skip(void *pp, size_t offset) +static SRes LookToRead2_Skip(ILookInStreamPtr pp, size_t offset) { - CLookToRead *p = (CLookToRead *)pp; + GET_LookToRead2 p->pos += offset; return SZ_OK; } -static SRes LookToRead_Read(void *pp, void *buf, size_t *size) +static SRes LookToRead2_Read(ILookInStreamPtr pp, void *buf, size_t *size) { - CLookToRead *p = (CLookToRead *)pp; + GET_LookToRead2 size_t rem = p->size - p->pos; if (rem == 0) - return p->realStream->Read(p->realStream, buf, size); + return ISeekInStream_Read(p->realStream, buf, size); if (rem > *size) rem = *size; memcpy(buf, p->buf + p->pos, rem); @@ -126,46 +157,43 @@ static SRes LookToRead_Read(void *pp, void *buf, size_t *size) return SZ_OK; } -static SRes LookToRead_Seek(void *pp, Int64 *pos, ESzSeek origin) +static SRes LookToRead2_Seek(ILookInStreamPtr pp, Int64 *pos, ESzSeek origin) { - CLookToRead *p = (CLookToRead *)pp; + GET_LookToRead2 p->pos = p->size = 0; - return p->realStream->Seek(p->realStream, pos, origin); + return ISeekInStream_Seek(p->realStream, pos, origin); } -void LookToRead_CreateVTable(CLookToRead *p, int lookahead) +void LookToRead2_CreateVTable(CLookToRead2 *p, int lookahead) { - p->s.Look = lookahead ? - LookToRead_Look_Lookahead : - LookToRead_Look_Exact; - p->s.Skip = LookToRead_Skip; - p->s.Read = LookToRead_Read; - p->s.Seek = LookToRead_Seek; + p->vt.Look = lookahead ? + LookToRead2_Look_Lookahead : + LookToRead2_Look_Exact; + p->vt.Skip = LookToRead2_Skip; + p->vt.Read = LookToRead2_Read; + p->vt.Seek = LookToRead2_Seek; } -void LookToRead_Init(CLookToRead *p) -{ - p->pos = p->size = 0; -} -static SRes SecToLook_Read(void *pp, void *buf, size_t *size) + +static SRes SecToLook_Read(ISeqInStreamPtr pp, void *buf, size_t *size) { - CSecToLook *p = (CSecToLook *)pp; + Z7_CONTAINER_FROM_VTBL_TO_DECL_VAR_pp_vt_p(CSecToLook) return LookInStream_LookRead(p->realStream, buf, size); } void SecToLook_CreateVTable(CSecToLook *p) { - p->s.Read = SecToLook_Read; + p->vt.Read = SecToLook_Read; } -static SRes SecToRead_Read(void *pp, void *buf, size_t *size) +static SRes SecToRead_Read(ISeqInStreamPtr pp, void *buf, size_t *size) { - CSecToRead *p = (CSecToRead *)pp; - return p->realStream->Read(p->realStream, buf, size); + Z7_CONTAINER_FROM_VTBL_TO_DECL_VAR_pp_vt_p(CSecToRead) + return ILookInStream_Read(p->realStream, buf, size); } void SecToRead_CreateVTable(CSecToRead *p) { - p->s.Read = SecToRead_Read; + p->vt.Read = SecToRead_Read; } diff --git a/src/plugins/7zip/7za/c/7zTypes.h b/src/plugins/7zip/7za/c/7zTypes.h index 778413ef..8aaabc8f 100644 --- a/src/plugins/7zip/7za/c/7zTypes.h +++ b/src/plugins/7zip/7za/c/7zTypes.h @@ -1,11 +1,13 @@ /* 7zTypes.h -- Basic types -2013-11-12 : Igor Pavlov : Public domain */ +: Igor Pavlov : Public domain */ -#ifndef __7Z_TYPES_H -#define __7Z_TYPES_H +#ifndef ZIP7_7Z_TYPES_H +#define ZIP7_7Z_TYPES_H #ifdef _WIN32 /* #include */ +#else +#include #endif #include @@ -42,22 +44,137 @@ EXTERN_C_BEGIN typedef int SRes; + +#ifdef _MSC_VER + #define MY_ALIGN_IN_STRUCT(n) __declspec(align(n)) + #if _MSC_VER > 1200 + #define MY_ALIGN(n) MY_ALIGN_IN_STRUCT(n) + #else + #define MY_ALIGN(n) + #endif +#else + /* + // C11/C++11: + #include + #define MY_ALIGN(n) alignas(n) + */ + #define MY_ALIGN(n) __attribute__ ((aligned(n))) + #define MY_ALIGN_IN_STRUCT(n) MY_ALIGN(n) +#endif + + #ifdef _WIN32 + /* typedef DWORD WRes; */ typedef unsigned WRes; -#else +#define MY_SRes_HRESULT_FROM_WRes(x) HRESULT_FROM_WIN32(x) + +// #define MY_HRES_ERROR_INTERNAL_ERROR MY_SRes_HRESULT_FROM_WRes(ERROR_INTERNAL_ERROR) + +#else // _WIN32 + +// #define ENV_HAVE_LSTAT typedef int WRes; + +// (FACILITY_ERRNO = 0x800) is 7zip's FACILITY constant to represent (errno) errors in HRESULT +#define MY_FACILITY_ERRNO 0x800 +#define MY_FACILITY_WIN32 7 +#define MY_FACILITY_WRes MY_FACILITY_ERRNO + +#define MY_HRESULT_FROM_errno_CONST_ERROR(x) ((HRESULT)( \ + ( (HRESULT)(x) & 0x0000FFFF) \ + | (MY_FACILITY_WRes << 16) \ + | (HRESULT)0x80000000 )) + +#define MY_SRes_HRESULT_FROM_WRes(x) \ + ((HRESULT)(x) <= 0 ? ((HRESULT)(x)) : MY_HRESULT_FROM_errno_CONST_ERROR(x)) + +// we call macro HRESULT_FROM_WIN32 for system errors (WRes) that are (errno) +#define HRESULT_FROM_WIN32(x) MY_SRes_HRESULT_FROM_WRes(x) + +/* +#define ERROR_FILE_NOT_FOUND 2L +#define ERROR_ACCESS_DENIED 5L +#define ERROR_NO_MORE_FILES 18L +#define ERROR_LOCK_VIOLATION 33L +#define ERROR_FILE_EXISTS 80L +#define ERROR_DISK_FULL 112L +#define ERROR_NEGATIVE_SEEK 131L +#define ERROR_ALREADY_EXISTS 183L +#define ERROR_DIRECTORY 267L +#define ERROR_TOO_MANY_POSTS 298L + +#define ERROR_INTERNAL_ERROR 1359L +#define ERROR_INVALID_REPARSE_DATA 4392L +#define ERROR_REPARSE_TAG_INVALID 4393L +#define ERROR_REPARSE_TAG_MISMATCH 4394L +*/ + +// we use errno equivalents for some WIN32 errors: + +#define ERROR_INVALID_PARAMETER EINVAL +#define ERROR_INVALID_FUNCTION EINVAL +#define ERROR_ALREADY_EXISTS EEXIST +#define ERROR_FILE_EXISTS EEXIST +#define ERROR_PATH_NOT_FOUND ENOENT +#define ERROR_FILE_NOT_FOUND ENOENT +#define ERROR_DISK_FULL ENOSPC +// #define ERROR_INVALID_HANDLE EBADF + +// we use FACILITY_WIN32 for errors that has no errno equivalent +// Too many posts were made to a semaphore. +#define ERROR_TOO_MANY_POSTS ((HRESULT)0x8007012AL) +#define ERROR_INVALID_REPARSE_DATA ((HRESULT)0x80071128L) +#define ERROR_REPARSE_TAG_INVALID ((HRESULT)0x80071129L) + +// if (MY_FACILITY_WRes != FACILITY_WIN32), +// we use FACILITY_WIN32 for COM errors: +#define E_OUTOFMEMORY ((HRESULT)0x8007000EL) +#define E_INVALIDARG ((HRESULT)0x80070057L) +#define MY_E_ERROR_NEGATIVE_SEEK ((HRESULT)0x80070083L) + +/* +// we can use FACILITY_ERRNO for some COM errors, that have errno equivalents: +#define E_OUTOFMEMORY MY_HRESULT_FROM_errno_CONST_ERROR(ENOMEM) +#define E_INVALIDARG MY_HRESULT_FROM_errno_CONST_ERROR(EINVAL) +#define MY_E_ERROR_NEGATIVE_SEEK MY_HRESULT_FROM_errno_CONST_ERROR(EINVAL) +*/ + +#define TEXT(quote) quote + +#define FILE_ATTRIBUTE_READONLY 0x0001 +#define FILE_ATTRIBUTE_HIDDEN 0x0002 +#define FILE_ATTRIBUTE_SYSTEM 0x0004 +#define FILE_ATTRIBUTE_DIRECTORY 0x0010 +#define FILE_ATTRIBUTE_ARCHIVE 0x0020 +#define FILE_ATTRIBUTE_DEVICE 0x0040 +#define FILE_ATTRIBUTE_NORMAL 0x0080 +#define FILE_ATTRIBUTE_TEMPORARY 0x0100 +#define FILE_ATTRIBUTE_SPARSE_FILE 0x0200 +#define FILE_ATTRIBUTE_REPARSE_POINT 0x0400 +#define FILE_ATTRIBUTE_COMPRESSED 0x0800 +#define FILE_ATTRIBUTE_OFFLINE 0x1000 +#define FILE_ATTRIBUTE_NOT_CONTENT_INDEXED 0x2000 +#define FILE_ATTRIBUTE_ENCRYPTED 0x4000 + +#define FILE_ATTRIBUTE_UNIX_EXTENSION 0x8000 /* trick for Unix */ + #endif + #ifndef RINOK -#define RINOK(x) { int __result__ = (x); if (__result__ != 0) return __result__; } +#define RINOK(x) { const int _result_ = (x); if (_result_ != 0) return _result_; } +#endif + +#ifndef RINOK_WRes +#define RINOK_WRes(x) { const WRes _result_ = (x); if (_result_ != 0) return _result_; } #endif typedef unsigned char Byte; typedef short Int16; typedef unsigned short UInt16; -#ifdef _LZMA_UINT32_IS_ULONG +#ifdef Z7_DECL_Int32_AS_long typedef long Int32; typedef unsigned long UInt32; #else @@ -65,95 +182,186 @@ typedef int Int32; typedef unsigned int UInt32; #endif -#ifdef _SZ_NO_INT_64 -/* define _SZ_NO_INT_64, if your compiler doesn't support 64-bit integers. - NOTES: Some code will work incorrectly in that case! */ +#ifndef _WIN32 + +typedef int INT; +typedef Int32 INT32; +typedef unsigned int UINT; +typedef UInt32 UINT32; +typedef INT32 LONG; // LONG, ULONG and DWORD must be 32-bit for _WIN32 compatibility +typedef UINT32 ULONG; + +#undef DWORD +typedef UINT32 DWORD; + +#define VOID void + +#define HRESULT LONG + +typedef void *LPVOID; +// typedef void VOID; +// typedef ULONG_PTR DWORD_PTR, *PDWORD_PTR; +// gcc / clang on Unix : sizeof(long==sizeof(void*) in 32 or 64 bits) +typedef long INT_PTR; +typedef unsigned long UINT_PTR; +typedef long LONG_PTR; +typedef unsigned long DWORD_PTR; + +typedef size_t SIZE_T; + +#endif // _WIN32 + + +#define MY_HRES_ERROR_INTERNAL_ERROR ((HRESULT)0x8007054FL) + + +#ifdef Z7_DECL_Int64_AS_long typedef long Int64; typedef unsigned long UInt64; #else -#if defined(_MSC_VER) || defined(__BORLANDC__) +#if (defined(_MSC_VER) || defined(__BORLANDC__)) && !defined(__clang__) typedef __int64 Int64; typedef unsigned __int64 UInt64; -#define UINT64_CONST(n) n +#else +#if defined(__clang__) || defined(__GNUC__) +#include +typedef int64_t Int64; +typedef uint64_t UInt64; #else typedef long long int Int64; typedef unsigned long long int UInt64; -#define UINT64_CONST(n) n ## ULL +// #define UINT64_CONST(n) n ## ULL +#endif #endif #endif -#ifdef _LZMA_NO_SYSTEM_SIZE_T -typedef UInt32 SizeT; +#define UINT64_CONST(n) n + + +#ifdef Z7_DECL_SizeT_AS_unsigned_int +typedef unsigned int SizeT; #else typedef size_t SizeT; #endif -typedef int Bool; +/* +#if (defined(_MSC_VER) && _MSC_VER <= 1200) +typedef size_t MY_uintptr_t; +#else +#include +typedef uintptr_t MY_uintptr_t; +#endif +*/ + +typedef int BoolInt; +/* typedef BoolInt Bool; */ #define True 1 #define False 0 #ifdef _WIN32 -#define MY_STD_CALL __stdcall +#define Z7_STDCALL __stdcall #else -#define MY_STD_CALL +#define Z7_STDCALL #endif #ifdef _MSC_VER #if _MSC_VER >= 1300 -#define MY_NO_INLINE __declspec(noinline) +#define Z7_NO_INLINE __declspec(noinline) #else -#define MY_NO_INLINE +#define Z7_NO_INLINE #endif -#define MY_CDECL __cdecl -#define MY_FAST_CALL __fastcall +#define Z7_FORCE_INLINE __forceinline +#define Z7_CDECL __cdecl +#define Z7_FASTCALL __fastcall + +#else // _MSC_VER + +#if (defined(__GNUC__) && (__GNUC__ >= 4)) \ + || (defined(__clang__) && (__clang_major__ >= 4)) \ + || defined(__INTEL_COMPILER) \ + || defined(__xlC__) +#define Z7_NO_INLINE __attribute__((noinline)) +#define Z7_FORCE_INLINE __attribute__((always_inline)) inline #else +#define Z7_NO_INLINE +#define Z7_FORCE_INLINE +#endif -#define MY_NO_INLINE -#define MY_CDECL -#define MY_FAST_CALL +#define Z7_CDECL +#if defined(_M_IX86) \ + || defined(__i386__) +// #define Z7_FASTCALL __attribute__((fastcall)) +// #define Z7_FASTCALL __attribute__((cdecl)) +#define Z7_FASTCALL +#elif defined(MY_CPU_AMD64) +// #define Z7_FASTCALL __attribute__((ms_abi)) +#define Z7_FASTCALL +#else +#define Z7_FASTCALL #endif +#endif // _MSC_VER + /* The following interfaces use first parameter as pointer to structure */ -typedef struct +// #define Z7_C_IFACE_CONST_QUAL +#define Z7_C_IFACE_CONST_QUAL const + +#define Z7_C_IFACE_DECL(a) \ + struct a ## _; \ + typedef Z7_C_IFACE_CONST_QUAL struct a ## _ * a ## Ptr; \ + typedef struct a ## _ a; \ + struct a ## _ + + +Z7_C_IFACE_DECL (IByteIn) { - Byte (*Read)(void *p); /* reads one byte, returns 0 in case of EOF or error */ -} IByteIn; + Byte (*Read)(IByteInPtr p); /* reads one byte, returns 0 in case of EOF or error */ +}; +#define IByteIn_Read(p) (p)->Read(p) -typedef struct + +Z7_C_IFACE_DECL (IByteOut) { - void (*Write)(void *p, Byte b); -} IByteOut; + void (*Write)(IByteOutPtr p, Byte b); +}; +#define IByteOut_Write(p, b) (p)->Write(p, b) -typedef struct + +Z7_C_IFACE_DECL (ISeqInStream) { - SRes (*Read)(void *p, void *buf, size_t *size); + SRes (*Read)(ISeqInStreamPtr p, void *buf, size_t *size); /* if (input(*size) != 0 && output(*size) == 0) means end_of_stream. (output(*size) < input(*size)) is allowed */ -} ISeqInStream; +}; +#define ISeqInStream_Read(p, buf, size) (p)->Read(p, buf, size) +/* try to read as much as avail in stream and limited by (*processedSize) */ +SRes SeqInStream_ReadMax(ISeqInStreamPtr stream, void *buf, size_t *processedSize); /* it can return SZ_ERROR_INPUT_EOF */ -SRes SeqInStream_Read(ISeqInStream *stream, void *buf, size_t size); -SRes SeqInStream_Read2(ISeqInStream *stream, void *buf, size_t size, SRes errorType); -SRes SeqInStream_ReadByte(ISeqInStream *stream, Byte *buf); +// SRes SeqInStream_Read(ISeqInStreamPtr stream, void *buf, size_t size); +// SRes SeqInStream_Read2(ISeqInStreamPtr stream, void *buf, size_t size, SRes errorType); +SRes SeqInStream_ReadByte(ISeqInStreamPtr stream, Byte *buf); -typedef struct + +Z7_C_IFACE_DECL (ISeqOutStream) { - size_t (*Write)(void *p, const void *buf, size_t size); + size_t (*Write)(ISeqOutStreamPtr p, const void *buf, size_t size); /* Returns: result - the number of actually written bytes. (result < size) means error */ -} ISeqOutStream; +}; +#define ISeqOutStream_Write(p, buf, size) (p)->Write(p, buf, size) typedef enum { @@ -162,78 +370,197 @@ typedef enum SZ_SEEK_END = 2 } ESzSeek; -typedef struct + +Z7_C_IFACE_DECL (ISeekInStream) { - SRes (*Read)(void *p, void *buf, size_t *size); /* same as ISeqInStream::Read */ - SRes (*Seek)(void *p, Int64 *pos, ESzSeek origin); -} ISeekInStream; + SRes (*Read)(ISeekInStreamPtr p, void *buf, size_t *size); /* same as ISeqInStream::Read */ + SRes (*Seek)(ISeekInStreamPtr p, Int64 *pos, ESzSeek origin); +}; +#define ISeekInStream_Read(p, buf, size) (p)->Read(p, buf, size) +#define ISeekInStream_Seek(p, pos, origin) (p)->Seek(p, pos, origin) -typedef struct + +Z7_C_IFACE_DECL (ILookInStream) { - SRes (*Look)(void *p, const void **buf, size_t *size); + SRes (*Look)(ILookInStreamPtr p, const void **buf, size_t *size); /* if (input(*size) != 0 && output(*size) == 0) means end_of_stream. (output(*size) > input(*size)) is not allowed (output(*size) < input(*size)) is allowed */ - SRes (*Skip)(void *p, size_t offset); + SRes (*Skip)(ILookInStreamPtr p, size_t offset); /* offset must be <= output(*size) of Look */ - - SRes (*Read)(void *p, void *buf, size_t *size); + SRes (*Read)(ILookInStreamPtr p, void *buf, size_t *size); /* reads directly (without buffer). It's same as ISeqInStream::Read */ - SRes (*Seek)(void *p, Int64 *pos, ESzSeek origin); -} ILookInStream; + SRes (*Seek)(ILookInStreamPtr p, Int64 *pos, ESzSeek origin); +}; -SRes LookInStream_LookRead(ILookInStream *stream, void *buf, size_t *size); -SRes LookInStream_SeekTo(ILookInStream *stream, UInt64 offset); +#define ILookInStream_Look(p, buf, size) (p)->Look(p, buf, size) +#define ILookInStream_Skip(p, offset) (p)->Skip(p, offset) +#define ILookInStream_Read(p, buf, size) (p)->Read(p, buf, size) +#define ILookInStream_Seek(p, pos, origin) (p)->Seek(p, pos, origin) + + +SRes LookInStream_LookRead(ILookInStreamPtr stream, void *buf, size_t *size); +SRes LookInStream_SeekTo(ILookInStreamPtr stream, UInt64 offset); /* reads via ILookInStream::Read */ -SRes LookInStream_Read2(ILookInStream *stream, void *buf, size_t size, SRes errorType); -SRes LookInStream_Read(ILookInStream *stream, void *buf, size_t size); +SRes LookInStream_Read2(ILookInStreamPtr stream, void *buf, size_t size, SRes errorType); +SRes LookInStream_Read(ILookInStreamPtr stream, void *buf, size_t size); -#define LookToRead_BUF_SIZE (1 << 14) typedef struct { - ILookInStream s; - ISeekInStream *realStream; + ILookInStream vt; + ISeekInStreamPtr realStream; + size_t pos; - size_t size; - Byte buf[LookToRead_BUF_SIZE]; -} CLookToRead; + size_t size; /* it's data size */ + + /* the following variables must be set outside */ + Byte *buf; + size_t bufSize; +} CLookToRead2; + +void LookToRead2_CreateVTable(CLookToRead2 *p, int lookahead); + +#define LookToRead2_INIT(p) { (p)->pos = (p)->size = 0; } -void LookToRead_CreateVTable(CLookToRead *p, int lookahead); -void LookToRead_Init(CLookToRead *p); typedef struct { - ISeqInStream s; - ILookInStream *realStream; + ISeqInStream vt; + ILookInStreamPtr realStream; } CSecToLook; void SecToLook_CreateVTable(CSecToLook *p); + + typedef struct { - ISeqInStream s; - ILookInStream *realStream; + ISeqInStream vt; + ILookInStreamPtr realStream; } CSecToRead; void SecToRead_CreateVTable(CSecToRead *p); -typedef struct + +Z7_C_IFACE_DECL (ICompressProgress) { - SRes (*Progress)(void *p, UInt64 inSize, UInt64 outSize); + SRes (*Progress)(ICompressProgressPtr p, UInt64 inSize, UInt64 outSize); /* Returns: result. (result != SZ_OK) means break. Value (UInt64)(Int64)-1 for size means unknown value. */ -} ICompressProgress; +}; -typedef struct +#define ICompressProgress_Progress(p, inSize, outSize) (p)->Progress(p, inSize, outSize) + + + +typedef struct ISzAlloc ISzAlloc; +typedef const ISzAlloc * ISzAllocPtr; + +struct ISzAlloc { - void *(*Alloc)(void *p, size_t size); - void (*Free)(void *p, void *address); /* address can be 0 */ -} ISzAlloc; + void *(*Alloc)(ISzAllocPtr p, size_t size); + void (*Free)(ISzAllocPtr p, void *address); /* address can be 0 */ +}; + +#define ISzAlloc_Alloc(p, size) (p)->Alloc(p, size) +#define ISzAlloc_Free(p, a) (p)->Free(p, a) + +/* deprecated */ +#define IAlloc_Alloc(p, size) ISzAlloc_Alloc(p, size) +#define IAlloc_Free(p, a) ISzAlloc_Free(p, a) + + + + + +#ifndef MY_offsetof + #ifdef offsetof + #define MY_offsetof(type, m) offsetof(type, m) + /* + #define MY_offsetof(type, m) FIELD_OFFSET(type, m) + */ + #else + #define MY_offsetof(type, m) ((size_t)&(((type *)0)->m)) + #endif +#endif + + + +#ifndef Z7_container_of + +/* +#define Z7_container_of(ptr, type, m) container_of(ptr, type, m) +#define Z7_container_of(ptr, type, m) CONTAINING_RECORD(ptr, type, m) +#define Z7_container_of(ptr, type, m) ((type *)((char *)(ptr) - offsetof(type, m))) +#define Z7_container_of(ptr, type, m) (&((type *)0)->m == (ptr), ((type *)(((char *)(ptr)) - MY_offsetof(type, m)))) +*/ + +/* + GCC shows warning: "perhaps the 'offsetof' macro was used incorrectly" + GCC 3.4.4 : classes with constructor + GCC 4.8.1 : classes with non-public variable members" +*/ + +#define Z7_container_of(ptr, type, m) \ + ((type *)(void *)((char *)(void *) \ + (1 ? (ptr) : &((type *)NULL)->m) - MY_offsetof(type, m))) + +#define Z7_container_of_CONST(ptr, type, m) \ + ((const type *)(const void *)((const char *)(const void *) \ + (1 ? (ptr) : &((type *)NULL)->m) - MY_offsetof(type, m))) + +/* +#define Z7_container_of_NON_CONST_FROM_CONST(ptr, type, m) \ + ((type *)(void *)(const void *)((const char *)(const void *) \ + (1 ? (ptr) : &((type *)NULL)->m) - MY_offsetof(type, m))) +*/ + +#endif + +#define Z7_CONTAINER_FROM_VTBL_SIMPLE(ptr, type, m) ((type *)(void *)(ptr)) + +// #define Z7_CONTAINER_FROM_VTBL(ptr, type, m) Z7_CONTAINER_FROM_VTBL_SIMPLE(ptr, type, m) +#define Z7_CONTAINER_FROM_VTBL(ptr, type, m) Z7_container_of(ptr, type, m) +// #define Z7_CONTAINER_FROM_VTBL(ptr, type, m) Z7_container_of_NON_CONST_FROM_CONST(ptr, type, m) + +#define Z7_CONTAINER_FROM_VTBL_CONST(ptr, type, m) Z7_container_of_CONST(ptr, type, m) + +#define Z7_CONTAINER_FROM_VTBL_CLS(ptr, type, m) Z7_CONTAINER_FROM_VTBL_SIMPLE(ptr, type, m) +/* +#define Z7_CONTAINER_FROM_VTBL_CLS(ptr, type, m) Z7_CONTAINER_FROM_VTBL(ptr, type, m) +*/ +#if defined (__clang__) || defined(__GNUC__) +#define Z7_DIAGNOSTIC_IGNORE_BEGIN_CAST_QUAL \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wcast-qual\"") +#define Z7_DIAGNOSTIC_IGNORE_END_CAST_QUAL \ + _Pragma("GCC diagnostic pop") +#else +#define Z7_DIAGNOSTIC_IGNORE_BEGIN_CAST_QUAL +#define Z7_DIAGNOSTIC_IGNORE_END_CAST_QUAL +#endif + +#define Z7_CONTAINER_FROM_VTBL_TO_DECL_VAR(ptr, type, m, p) \ + Z7_DIAGNOSTIC_IGNORE_BEGIN_CAST_QUAL \ + type *p = Z7_CONTAINER_FROM_VTBL(ptr, type, m); \ + Z7_DIAGNOSTIC_IGNORE_END_CAST_QUAL + +#define Z7_CONTAINER_FROM_VTBL_TO_DECL_VAR_pp_vt_p(type) \ + Z7_CONTAINER_FROM_VTBL_TO_DECL_VAR(pp, type, vt, p) + + +// #define ZIP7_DECLARE_HANDLE(name) typedef void *name; +#define Z7_DECLARE_HANDLE(name) struct name##_dummy{int unused;}; typedef struct name##_dummy *name; + + +#define Z7_memset_0_ARRAY(a) memset((a), 0, sizeof(a)) + +#ifndef Z7_ARRAY_SIZE +#define Z7_ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#endif -#define IAlloc_Alloc(p, size) (p)->Alloc((p), size) -#define IAlloc_Free(p, a) (p)->Free((p), a) #ifdef _WIN32 @@ -251,6 +578,22 @@ typedef struct #endif +#define k_PropVar_TimePrec_0 0 +#define k_PropVar_TimePrec_Unix 1 +#define k_PropVar_TimePrec_DOS 2 +#define k_PropVar_TimePrec_HighPrec 3 +#define k_PropVar_TimePrec_Base 16 +#define k_PropVar_TimePrec_100ns (k_PropVar_TimePrec_Base + 7) +#define k_PropVar_TimePrec_1ns (k_PropVar_TimePrec_Base + 9) + EXTERN_C_END #endif + +/* +#ifndef Z7_ST +#ifdef _7ZIP_ST +#define Z7_ST +#endif +#endif +*/ diff --git a/src/plugins/7zip/7za/c/7zVersion.h b/src/plugins/7zip/7za/c/7zVersion.h index 4f15081d..50ccebb4 100644 --- a/src/plugins/7zip/7za/c/7zVersion.h +++ b/src/plugins/7zip/7za/c/7zVersion.h @@ -1,14 +1,21 @@ -#define MY_VER_MAJOR 16 -#define MY_VER_MINOR 04 +#define MY_VER_MAJOR 26 +#define MY_VER_MINOR 2 #define MY_VER_BUILD 0 -#define MY_VERSION_NUMBERS "16.04" -#define MY_VERSION "16.04" -#define MY_DATE "2016-10-04" +#define MY_VERSION_NUMBERS "26.02" +#define MY_VERSION MY_VERSION_NUMBERS + +#ifdef MY_CPU_NAME + #define MY_VERSION_CPU MY_VERSION " (" MY_CPU_NAME ")" +#else + #define MY_VERSION_CPU MY_VERSION +#endif + +#define MY_DATE "2026-06-25" #undef MY_COPYRIGHT #undef MY_VERSION_COPYRIGHT_DATE #define MY_AUTHOR_NAME "Igor Pavlov" #define MY_COPYRIGHT_PD "Igor Pavlov : Public domain" -#define MY_COPYRIGHT_CR "Copyright (c) 1999-2016 Igor Pavlov" +#define MY_COPYRIGHT_CR "Copyright (c) 1999-2026 Igor Pavlov" #ifdef USE_COPYRIGHT_CR #define MY_COPYRIGHT MY_COPYRIGHT_CR @@ -16,4 +23,5 @@ #define MY_COPYRIGHT MY_COPYRIGHT_PD #endif -#define MY_VERSION_COPYRIGHT_DATE MY_VERSION " : " MY_COPYRIGHT " : " MY_DATE +#define MY_COPYRIGHT_DATE MY_COPYRIGHT " : " MY_DATE +#define MY_VERSION_COPYRIGHT_DATE MY_VERSION_CPU " : " MY_COPYRIGHT " : " MY_DATE diff --git a/src/plugins/7zip/7za/c/7zWindows.h b/src/plugins/7zip/7za/c/7zWindows.h new file mode 100644 index 00000000..381159ed --- /dev/null +++ b/src/plugins/7zip/7za/c/7zWindows.h @@ -0,0 +1,107 @@ +/* 7zWindows.h -- Windows.h and related code +Igor Pavlov : Public domain */ + +#ifndef ZIP7_INC_7Z_WINDOWS_H +#define ZIP7_INC_7Z_WINDOWS_H + +#ifdef _WIN32 + +#if defined(_MSC_VER) && _MSC_VER >= 1950 && !defined(__clang__) // VS2026 +// and some another windows files need that option +// VS2026: wtypesbase.h: warning C4865: 'tagCLSCTX': the underlying type will change from 'int' to 'unsigned int' when '/Zc:enumTypes' is specified on the command line +#pragma warning(disable : 4865) +#endif + +#if defined(__clang__) +# pragma clang diagnostic push +#endif + +#if defined(_MSC_VER) + +#pragma warning(push) +#pragma warning(disable : 4668) // '_WIN32_WINNT' is not defined as a preprocessor macro, replacing with '0' for '#if/#elif' + +#if _MSC_VER == 1900 +// for old kit10 versions +// #pragma warning(disable : 4255) // winuser.h(13979): warning C4255: 'GetThreadDpiAwarenessContext': +#endif +// win10 Windows Kit: +#endif // _MSC_VER + +#if defined(_MSC_VER) && _MSC_VER <= 1200 && !defined(_WIN64) +// for msvc6 without sdk2003 +#define RPC_NO_WINDOWS_H +#endif + +#if defined(__MINGW32__) || defined(__MINGW64__) +// #if defined(__GNUC__) && !defined(__clang__) +#include +#else +#include +#endif +// #include +// #include + +// but if precompiled with clang-cl then we need +// #include +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + +#if defined(_MSC_VER) && _MSC_VER <= 1200 && !defined(_WIN64) +#ifndef _W64 + +typedef long LONG_PTR, *PLONG_PTR; +typedef unsigned long ULONG_PTR, *PULONG_PTR; +typedef ULONG_PTR DWORD_PTR, *PDWORD_PTR; + +#define Z7_OLD_WIN_SDK +#endif // _W64 +#endif // _MSC_VER == 1200 + +#ifdef Z7_OLD_WIN_SDK + +#ifndef INVALID_FILE_ATTRIBUTES +#define INVALID_FILE_ATTRIBUTES ((DWORD)-1) +#endif +#ifndef INVALID_SET_FILE_POINTER +#define INVALID_SET_FILE_POINTER ((DWORD)-1) +#endif +#ifndef FILE_SPECIAL_ACCESS +#define FILE_SPECIAL_ACCESS (FILE_ANY_ACCESS) +#endif + +// ShlObj.h: +// #define BIF_NEWDIALOGSTYLE 0x0040 + +#pragma warning(disable : 4201) +// #pragma warning(disable : 4115) + +#undef VARIANT_TRUE +#define VARIANT_TRUE ((VARIANT_BOOL)-1) +#endif + +#endif // Z7_OLD_WIN_SDK + +#ifdef UNDER_CE +#undef VARIANT_TRUE +#define VARIANT_TRUE ((VARIANT_BOOL)-1) +#endif + + +#if defined(_MSC_VER) +#if _MSC_VER >= 1400 && _MSC_VER <= 1600 + // BaseTsd.h(148) : 'HandleToULong' : unreferenced inline function has been removed + // string.h + // #pragma warning(disable : 4514) +#endif +#endif + + +/* #include "7zTypes.h" */ + +#endif diff --git a/src/plugins/7zip/7za/c/7zip_gcc_c.mak b/src/plugins/7zip/7za/c/7zip_gcc_c.mak new file mode 100644 index 00000000..006cfe06 --- /dev/null +++ b/src/plugins/7zip/7za/c/7zip_gcc_c.mak @@ -0,0 +1,360 @@ + +MY_ARCH_2 = $(MY_ARCH) + +MY_ASM = jwasm +MY_ASM = asmc + +ifndef RC +#RC=windres.exe --target=pe-x86-64 +#RC=windres.exe -F pe-i386 +RC=windres.exe +endif + +PROGPATH = $(O)/$(PROG) +PROGPATH_STATIC = $(O)/$(PROG)s + +ifneq ($(CC), xlc) +CFLAGS_WARN_WALL = -Wall -Werror -Wextra +endif + +# for object file +CFLAGS_BASE_LIST = -c +# for ASM file +# CFLAGS_BASE_LIST = -S + +FLAGS_FLTO = -flto +FLAGS_FLTO = + +CFLAGS_BASE = $(MY_ARCH_2) -O2 $(CFLAGS_BASE_LIST) $(CFLAGS_WARN_WALL) $(CFLAGS_WARN) \ + -DNDEBUG -D_REENTRANT -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE + + +ifdef SystemDrive +IS_MINGW = 1 +else +ifdef SYSTEMDRIVE +# ifdef OS +IS_MINGW = 1 +endif +endif + +ifdef IS_MINGW +LDFLAGS_STATIC_2 = -static +else +ifndef DEF_FILE +ifndef IS_NOT_STANDALONE +ifndef MY_DYNAMIC_LINK +ifneq ($(CC), clang) +LDFLAGS_STATIC_2 = +# -static +# -static-libstdc++ -static-libgcc +endif +endif +endif +endif +endif + +LDFLAGS_STATIC = -DNDEBUG $(LDFLAGS_STATIC_2) + +ifdef DEF_FILE + + +ifdef IS_MINGW +SHARED_EXT=.dll +LDFLAGS = -shared -DEF $(DEF_FILE) $(LDFLAGS_STATIC) +else +SHARED_EXT=.so +LDFLAGS = -shared -fPIC $(LDFLAGS_STATIC) +CC_SHARED=-fPIC +endif + + +else + +LDFLAGS = $(LDFLAGS_STATIC) +# -s is not required for clang, do we need it for GGC ??? +# -s + +#-static -static-libgcc -static-libstdc++ + +ifdef IS_MINGW +SHARED_EXT=.exe +else +SHARED_EXT= +endif + +endif + + +PROGPATH = $(O)/$(PROG)$(SHARED_EXT) +PROGPATH_STATIC = $(O)/$(PROG)s$(SHARED_EXT) + +ifndef O +O=_o +endif + +ifdef IS_MINGW + +ifdef MSYSTEM +RM = rm -f +MY_MKDIR=mkdir -p +DEL_OBJ_EXE = -$(RM) $(PROGPATH) $(PROGPATH_STATIC) $(OBJS) +else +RM = del +MY_MKDIR=mkdir +DEL_OBJ_EXE = -$(RM) $(O)\*.o $(O)\$(PROG).exe $(O)\$(PROG).dll +endif + + +LIB2 = -lole32 -loleaut32 -luuid -ladvapi32 -luser32 -lshell32 + +CFLAGS_EXTRA = -DUNICODE -D_UNICODE +# -Wno-delete-non-virtual-dtor + + +else + +RM = rm -f +MY_MKDIR=mkdir -p +# CFLAGS_BASE := $(CFLAGS_BASE) -DZ7_ST +# CFLAGS_EXTRA = -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE + +# LOCAL_LIBS=-lpthread +# LOCAL_LIBS_DLL=$(LOCAL_LIBS) -ldl +LIB2 = -lpthread -ldl + +DEL_OBJ_EXE = -$(RM) $(PROGPATH) $(PROGPATH_STATIC) $(OBJS) + +endif + + +ifdef IS_X64 +AFLAGS_ABI = -elf64 -DABI_LINUX +else +AFLAGS_ABI = -elf -DABI_LINUX -DABI_CDECL +# -DABI_CDECL +# -DABI_LINUX +# -DABI_CDECL +endif +AFLAGS = $(AFLAGS_ABI) -Fo$(O)/ + +C_WARN_FLAGS = + +CFLAGS = $(LOCAL_FLAGS) $(CFLAGS_BASE2) $(CFLAGS_BASE) $(CFLAGS_EXTRA) $(C_WARN_FLAGS) $(FLAGS_FLTO) $(CC_SHARED) -o $@ + +STATIC_TARGET= +ifdef COMPL_STATIC +STATIC_TARGET=$(PROGPATH_STATIC) +endif + + +all: $(O) $(PROGPATH) $(STATIC_TARGET) + +$(O): + $(MY_MKDIR) $(O) + +ifneq ($(CC), $(CROSS_COMPILE)clang) +LFLAGS_STRIP = -s +endif + +LFLAGS_ALL = $(LFLAGS_STRIP) $(MY_ARCH_2) $(LDFLAGS) $(FLAGS_FLTO) $(LD_arch) $(OBJS) $(MY_LIBS) $(LIB2) +$(PROGPATH): $(OBJS) + $(CC) -o $(PROGPATH) $(LFLAGS_ALL) + +$(PROGPATH_STATIC): $(OBJS) + $(CC) -static -o $(PROGPATH_STATIC) $(LFLAGS_ALL) + + +ifndef NO_DEFAULT_RES +# old mingw without -FO +# windres.exe $(RFLAGS) resource.rc $O/resource.o +$O/resource.o: resource.rc + $(RC) $(RFLAGS) resource.rc $(O)/resource.o +endif +# windres.exe $(RFLAGS) resource.rc $(O)\resource.o +# windres.exe $(RFLAGS) resource.rc -FO $(O)/resource.o +# $(RC) $(RFLAGS) resource.rc -FO $(O)/resource.o + + + +$O/7zAlloc.o: ../../../C/7zAlloc.c + $(CC) $(CFLAGS) $< +$O/7zArcIn.o: ../../../C/7zArcIn.c + $(CC) $(CFLAGS) $< +$O/7zBuf.o: ../../../C/7zBuf.c + $(CC) $(CFLAGS) $< +$O/7zBuf2.o: ../../../C/7zBuf2.c + $(CC) $(CFLAGS) $< +$O/7zCrc.o: ../../../C/7zCrc.c + $(CC) $(CFLAGS) $< +$O/7zDec.o: ../../../C/7zDec.c + $(CC) $(CFLAGS) $< +$O/7zFile.o: ../../../C/7zFile.c + $(CC) $(CFLAGS) $< +$O/7zStream.o: ../../../C/7zStream.c + $(CC) $(CFLAGS) $< +$O/Aes.o: ../../../C/Aes.c + $(CC) $(CFLAGS) $< +$O/Alloc.o: ../../../C/Alloc.c + $(CC) $(CFLAGS) $< +$O/Bcj2.o: ../../../C/Bcj2.c + $(CC) $(CFLAGS) $< +$O/Bcj2Enc.o: ../../../C/Bcj2Enc.c + $(CC) $(CFLAGS) $< +$O/Blake2s.o: ../../../C/Blake2s.c + $(CC) $(CFLAGS) $< +$O/Bra.o: ../../../C/Bra.c + $(CC) $(CFLAGS) $< +$O/Bra86.o: ../../../C/Bra86.c + $(CC) $(CFLAGS) $< +$O/BraIA64.o: ../../../C/BraIA64.c + $(CC) $(CFLAGS) $< +$O/BwtSort.o: ../../../C/BwtSort.c + $(CC) $(CFLAGS) $< + +$O/CpuArch.o: ../../../C/CpuArch.c + $(CC) $(CFLAGS) $< +$O/Delta.o: ../../../C/Delta.c + $(CC) $(CFLAGS) $< +$O/DllSecur.o: ../../../C/DllSecur.c + $(CC) $(CFLAGS) $< +$O/HuffEnc.o: ../../../C/HuffEnc.c + $(CC) $(CFLAGS) $< +$O/LzFind.o: ../../../C/LzFind.c + $(CC) $(CFLAGS) $< + +# ifdef MT_FILES +$O/LzFindMt.o: ../../../C/LzFindMt.c + $(CC) $(CFLAGS) $< +$O/LzFindOpt.o: ../../../C/LzFindOpt.c + $(CC) $(CFLAGS) $< + +$O/Threads.o: ../../../C/Threads.c + $(CC) $(CFLAGS) $< +# endif + +$O/LzmaEnc.o: ../../../C/LzmaEnc.c + $(CC) $(CFLAGS) $< +$O/Lzma86Dec.o: ../../../C/Lzma86Dec.c + $(CC) $(CFLAGS) $< +$O/Lzma86Enc.o: ../../../C/Lzma86Enc.c + $(CC) $(CFLAGS) $< +$O/Lzma2Dec.o: ../../../C/Lzma2Dec.c + $(CC) $(CFLAGS) $< +$O/Lzma2DecMt.o: ../../../C/Lzma2DecMt.c + $(CC) $(CFLAGS) $< +$O/Lzma2Enc.o: ../../../C/Lzma2Enc.c + $(CC) $(CFLAGS) $< +$O/LzmaLib.o: ../../../C/LzmaLib.c + $(CC) $(CFLAGS) $< +$O/MtCoder.o: ../../../C/MtCoder.c + $(CC) $(CFLAGS) $< +$O/MtDec.o: ../../../C/MtDec.c + $(CC) $(CFLAGS) $< +$O/Ppmd7.o: ../../../C/Ppmd7.c + $(CC) $(CFLAGS) $< +$O/Ppmd7aDec.o: ../../../C/Ppmd7aDec.c + $(CC) $(CFLAGS) $< +$O/Ppmd7Dec.o: ../../../C/Ppmd7Dec.c + $(CC) $(CFLAGS) $< +$O/Ppmd7Enc.o: ../../../C/Ppmd7Enc.c + $(CC) $(CFLAGS) $< +$O/Ppmd8.o: ../../../C/Ppmd8.c + $(CC) $(CFLAGS) $< +$O/Ppmd8Dec.o: ../../../C/Ppmd8Dec.c + $(CC) $(CFLAGS) $< +$O/Ppmd8Enc.o: ../../../C/Ppmd8Enc.c + $(CC) $(CFLAGS) $< +$O/Sha1.o: ../../../C/Sha1.c + $(CC) $(CFLAGS) $< +$O/Sha256.o: ../../../C/Sha256.c + $(CC) $(CFLAGS) $< +$O/Sort.o: ../../../C/Sort.c + $(CC) $(CFLAGS) $< +$O/SwapBytes.o: ../../../C/SwapBytes.c + $(CC) $(CFLAGS) $< +$O/Xz.o: ../../../C/Xz.c + $(CC) $(CFLAGS) $< +$O/XzCrc64.o: ../../../C/XzCrc64.c + $(CC) $(CFLAGS) $< +$O/XzDec.o: ../../../C/XzDec.c + $(CC) $(CFLAGS) $< +$O/XzEnc.o: ../../../C/XzEnc.c + $(CC) $(CFLAGS) $< +$O/XzIn.o: ../../../C/XzIn.c + $(CC) $(CFLAGS) $< + + +ifdef USE_ASM +ifdef IS_X64 +USE_X86_ASM=1 +else +ifdef IS_X86 +USE_X86_ASM=1 +endif +endif +endif + +ifdef USE_X86_ASM +$O/7zCrcOpt.o: ../../../Asm/x86/7zCrcOpt.asm + $(MY_ASM) $(AFLAGS) $< +$O/XzCrc64Opt.o: ../../../Asm/x86/XzCrc64Opt.asm + $(MY_ASM) $(AFLAGS) $< +$O/AesOpt.o: ../../../Asm/x86/AesOpt.asm + $(MY_ASM) $(AFLAGS) $< +$O/Sha1Opt.o: ../../../Asm/x86/Sha1Opt.asm + $(MY_ASM) $(AFLAGS) $< +$O/Sha256Opt.o: ../../../Asm/x86/Sha256Opt.asm + $(MY_ASM) $(AFLAGS) $< +else +$O/7zCrcOpt.o: ../../7zCrcOpt.c + $(CC) $(CFLAGS) $< +$O/XzCrc64Opt.o: ../../XzCrc64Opt.c + $(CC) $(CFLAGS) $< +$O/Sha1Opt.o: ../../Sha1Opt.c + $(CC) $(CFLAGS) $< +$O/Sha256Opt.o: ../../Sha256Opt.c + $(CC) $(CFLAGS) $< +$O/AesOpt.o: ../../AesOpt.c + $(CC) $(CFLAGS) $< +endif + + +ifdef USE_LZMA_DEC_ASM + +ifdef IS_X64 +$O/LzmaDecOpt.o: ../../../Asm/x86/LzmaDecOpt.asm + $(MY_ASM) $(AFLAGS) $< +endif + +ifdef IS_ARM64 +$O/LzmaDecOpt.o: ../../../Asm/arm64/LzmaDecOpt.S ../../../Asm/arm64/7zAsm.S + $(CC) $(CFLAGS) $(ASM_FLAGS) $< +endif + +$O/LzmaDec.o: ../../LzmaDec.c + $(CC) $(CFLAGS) -DZ7_LZMA_DEC_OPT $< + +else + +$O/LzmaDec.o: ../../LzmaDec.c + $(CC) $(CFLAGS) $< + +endif + + + +$O/7zMain.o: ../../../C/Util/7z/7zMain.c + $(CC) $(CFLAGS) $< +$O/7zipInstall.o: ../../../C/Util/7zipInstall/7zipInstall.c + $(CC) $(CFLAGS) $< +$O/7zipUninstall.o: ../../../C/Util/7zipUninstall/7zipUninstall.c + $(CC) $(CFLAGS) $< +$O/LzmaUtil.o: ../../../C/Util/Lzma/LzmaUtil.c + $(CC) $(CFLAGS) $< +$O/XzUtil.o: ../../../C/Util/Xz/XzUtil.c + $(CC) $(CFLAGS) $< + + +clean: + -$(DEL_OBJ_EXE) diff --git a/src/plugins/7zip/7za/c/Aes.c b/src/plugins/7zip/7za/c/Aes.c index b10b9e58..abc5d24b 100644 --- a/src/plugins/7zip/7za/c/Aes.c +++ b/src/plugins/7zip/7za/c/Aes.c @@ -1,12 +1,21 @@ /* Aes.c -- AES encryption / decryption -2016-05-21 : Igor Pavlov : Public domain */ +2024-03-01 : Igor Pavlov : Public domain */ #include "Precomp.h" -#include "Aes.h" #include "CpuArch.h" +#include "Aes.h" + +AES_CODE_FUNC g_AesCbc_Decode; +#ifndef Z7_SFX +AES_CODE_FUNC g_AesCbc_Encode; +AES_CODE_FUNC g_AesCtr_Code; +UInt32 g_Aes_SupportedFunctions_Flags; +#endif +MY_ALIGN(64) static UInt32 T[256 * 4]; +MY_ALIGN(64) static const Byte Sbox[256] = { 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, @@ -25,23 +34,12 @@ static const Byte Sbox[256] = { 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16}; -void MY_FAST_CALL AesCbc_Encode(UInt32 *ivAes, Byte *data, size_t numBlocks); -void MY_FAST_CALL AesCbc_Decode(UInt32 *ivAes, Byte *data, size_t numBlocks); -void MY_FAST_CALL AesCtr_Code(UInt32 *ivAes, Byte *data, size_t numBlocks); - -void MY_FAST_CALL AesCbc_Encode_Intel(UInt32 *ivAes, Byte *data, size_t numBlocks); -void MY_FAST_CALL AesCbc_Decode_Intel(UInt32 *ivAes, Byte *data, size_t numBlocks); -void MY_FAST_CALL AesCtr_Code_Intel(UInt32 *ivAes, Byte *data, size_t numBlocks); - -AES_CODE_FUNC g_AesCbc_Encode; -AES_CODE_FUNC g_AesCbc_Decode; -AES_CODE_FUNC g_AesCtr_Code; +MY_ALIGN(64) static UInt32 D[256 * 4]; +MY_ALIGN(64) static Byte InvS[256]; -static const Byte Rcon[11] = { 0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36 }; - #define xtime(x) ((((x) << 1) ^ (((x) & 0x80) != 0 ? 0x1B : 0)) & 0xFF) #define Ui32(a0, a1, a2, a3) ((UInt32)(a0) | ((UInt32)(a1) << 8) | ((UInt32)(a2) << 16) | ((UInt32)(a3) << 24)) @@ -49,7 +47,73 @@ static const Byte Rcon[11] = { 0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0 #define gb0(x) ( (x) & 0xFF) #define gb1(x) (((x) >> ( 8)) & 0xFF) #define gb2(x) (((x) >> (16)) & 0xFF) -#define gb3(x) (((x) >> (24)) & 0xFF) +#define gb3(x) (((x) >> (24))) + +#define gb(n, x) gb ## n(x) + +#define TT(x) (T + (x << 8)) +#define DD(x) (D + (x << 8)) + + +// #define Z7_SHOW_AES_STATUS + +#ifdef MY_CPU_X86_OR_AMD64 + + #if defined(__INTEL_COMPILER) + #if (__INTEL_COMPILER >= 1110) + #define USE_HW_AES + #if (__INTEL_COMPILER >= 1900) + #define USE_HW_VAES + #endif + #endif + #elif defined(Z7_CLANG_VERSION) && (Z7_CLANG_VERSION >= 30800) \ + || defined(Z7_GCC_VERSION) && (Z7_GCC_VERSION >= 40400) + #define USE_HW_AES + #if defined(__clang__) && (__clang_major__ >= 8) \ + || defined(__GNUC__) && (__GNUC__ >= 8) + #define USE_HW_VAES + #endif + #elif defined(_MSC_VER) + #define USE_HW_AES + #define USE_HW_VAES + #endif + +#elif defined(MY_CPU_ARM_OR_ARM64) && defined(MY_CPU_LE) + + #if defined(__ARM_FEATURE_AES) \ + || defined(__ARM_FEATURE_CRYPTO) + #define USE_HW_AES + #else + #if defined(MY_CPU_ARM64) \ + || defined(__ARM_ARCH) && (__ARM_ARCH >= 4) \ + || defined(Z7_MSC_VER_ORIGINAL) + #if defined(__ARM_FP) && \ + ( defined(Z7_CLANG_VERSION) && (Z7_CLANG_VERSION >= 30800) \ + || defined(__GNUC__) && (__GNUC__ >= 6) \ + ) \ + || defined(Z7_MSC_VER_ORIGINAL) && (_MSC_VER >= 1910) + #if defined(MY_CPU_ARM64) \ + || !defined(Z7_CLANG_VERSION) \ + || defined(__ARM_NEON) && \ + (Z7_CLANG_VERSION < 170000 || \ + Z7_CLANG_VERSION > 170001) + #define USE_HW_AES + #endif + #endif + #endif + #endif +#endif + +#ifdef USE_HW_AES +// #pragma message("=== Aes.c USE_HW_AES === ") +#ifdef Z7_SHOW_AES_STATUS +#include +#define PRF(x) x +#else +#define PRF(x) +#endif +#endif + void AesGenTables(void) { @@ -60,46 +124,78 @@ void AesGenTables(void) for (i = 0; i < 256; i++) { { - UInt32 a1 = Sbox[i]; - UInt32 a2 = xtime(a1); - UInt32 a3 = a2 ^ a1; - T[ i] = Ui32(a2, a1, a1, a3); - T[0x100 + i] = Ui32(a3, a2, a1, a1); - T[0x200 + i] = Ui32(a1, a3, a2, a1); - T[0x300 + i] = Ui32(a1, a1, a3, a2); + const UInt32 a1 = Sbox[i]; + const UInt32 a2 = xtime(a1); + const UInt32 a3 = a2 ^ a1; + TT(0)[i] = Ui32(a2, a1, a1, a3); + TT(1)[i] = Ui32(a3, a2, a1, a1); + TT(2)[i] = Ui32(a1, a3, a2, a1); + TT(3)[i] = Ui32(a1, a1, a3, a2); } { - UInt32 a1 = InvS[i]; - UInt32 a2 = xtime(a1); - UInt32 a4 = xtime(a2); - UInt32 a8 = xtime(a4); - UInt32 a9 = a8 ^ a1; - UInt32 aB = a8 ^ a2 ^ a1; - UInt32 aD = a8 ^ a4 ^ a1; - UInt32 aE = a8 ^ a4 ^ a2; - D[ i] = Ui32(aE, a9, aD, aB); - D[0x100 + i] = Ui32(aB, aE, a9, aD); - D[0x200 + i] = Ui32(aD, aB, aE, a9); - D[0x300 + i] = Ui32(a9, aD, aB, aE); + const UInt32 a1 = InvS[i]; + const UInt32 a2 = xtime(a1); + const UInt32 a4 = xtime(a2); + const UInt32 a8 = xtime(a4); + const UInt32 a9 = a8 ^ a1; + const UInt32 aB = a8 ^ a2 ^ a1; + const UInt32 aD = a8 ^ a4 ^ a1; + const UInt32 aE = a8 ^ a4 ^ a2; + DD(0)[i] = Ui32(aE, a9, aD, aB); + DD(1)[i] = Ui32(aB, aE, a9, aD); + DD(2)[i] = Ui32(aD, aB, aE, a9); + DD(3)[i] = Ui32(a9, aD, aB, aE); } } - g_AesCbc_Encode = AesCbc_Encode; - g_AesCbc_Decode = AesCbc_Decode; - g_AesCtr_Code = AesCtr_Code; + { + AES_CODE_FUNC d = AesCbc_Decode; + #ifndef Z7_SFX + AES_CODE_FUNC e = AesCbc_Encode; + AES_CODE_FUNC c = AesCtr_Code; + UInt32 flags = 0; + #endif - #ifdef MY_CPU_X86_OR_AMD64 - if (CPU_Is_Aes_Supported()) + #ifdef USE_HW_AES + if (CPU_IsSupported_AES()) { - g_AesCbc_Encode = AesCbc_Encode_Intel; - g_AesCbc_Decode = AesCbc_Decode_Intel; - g_AesCtr_Code = AesCtr_Code_Intel; + // #pragma message ("AES HW") + PRF(printf("\n===AES HW\n")); + d = AesCbc_Decode_HW; + + #ifndef Z7_SFX + e = AesCbc_Encode_HW; + c = AesCtr_Code_HW; + flags = k_Aes_SupportedFunctions_HW; + #endif + + #ifdef MY_CPU_X86_OR_AMD64 + #ifdef USE_HW_VAES + if (CPU_IsSupported_VAES_AVX2()) + { + PRF(printf("\n===vaes avx2\n")); + d = AesCbc_Decode_HW_256; + #ifndef Z7_SFX + c = AesCtr_Code_HW_256; + flags |= k_Aes_SupportedFunctions_HW_256; + #endif + } + #endif + #endif } #endif + + g_AesCbc_Decode = d; + #ifndef Z7_SFX + g_AesCbc_Encode = e; + g_AesCtr_Code = c; + g_Aes_SupportedFunctions_Flags = flags; + #endif + } } -#define HT(i, x, s) (T + (x << 8))[gb ## x(s[(i + x) & 3])] +#define HT(i, x, s) TT(x)[gb(x, s[(i + x) & 3])] #define HT4(m, i, s, p) m[i] = \ HT(i, 0, s) ^ \ @@ -113,11 +209,11 @@ void AesGenTables(void) HT4(m, 2, s, p); \ HT4(m, 3, s, p); \ -#define FT(i, x) Sbox[gb ## x(m[(i + x) & 3])] +#define FT(i, x) Sbox[gb(x, m[(i + x) & 3])] #define FT4(i) dest[i] = Ui32(FT(i, 0), FT(i, 1), FT(i, 2), FT(i, 3)) ^ w[i]; -#define HD(i, x, s) (D + (x << 8))[gb ## x(s[(i - x) & 3])] +#define HD(i, x, s) DD(x)[gb(x, s[(i - x) & 3])] #define HD4(m, i, s, p) m[i] = \ HD(i, 0, s) ^ \ @@ -131,13 +227,16 @@ void AesGenTables(void) HD4(m, 2, s, p); \ HD4(m, 3, s, p); \ -#define FD(i, x) InvS[gb ## x(m[(i - x) & 3])] +#define FD(i, x) InvS[gb(x, m[(i - x) & 3])] #define FD4(i) dest[i] = Ui32(FD(i, 0), FD(i, 1), FD(i, 2), FD(i, 3)) ^ w[i]; -void MY_FAST_CALL Aes_SetKey_Enc(UInt32 *w, const Byte *key, unsigned keySize) +void Z7_FASTCALL Aes_SetKey_Enc(UInt32 *w, const Byte *key, unsigned keySize) { - unsigned i, wSize; - wSize = keySize + 28; + unsigned i, m; + const UInt32 *wLim; + UInt32 t; + UInt32 rcon = 1; + keySize /= 4; w[0] = ((UInt32)keySize / 2) + 3; w += 4; @@ -145,19 +244,29 @@ void MY_FAST_CALL Aes_SetKey_Enc(UInt32 *w, const Byte *key, unsigned keySize) for (i = 0; i < keySize; i++, key += 4) w[i] = GetUi32(key); - for (; i < wSize; i++) + t = w[(size_t)keySize - 1]; + wLim = w + (size_t)keySize * 3 + 28; + m = 0; + do { - UInt32 t = w[i - 1]; - unsigned rem = i % keySize; - if (rem == 0) - t = Ui32(Sbox[gb1(t)] ^ Rcon[i / keySize], Sbox[gb2(t)], Sbox[gb3(t)], Sbox[gb0(t)]); - else if (keySize > 6 && rem == 4) + if (m == 0) + { + t = Ui32(Sbox[gb1(t)] ^ rcon, Sbox[gb2(t)], Sbox[gb3(t)], Sbox[gb0(t)]); + rcon <<= 1; + if (rcon & 0x100) + rcon = 0x1b; + m = keySize; + } + else if (m == 4 && keySize > 6) t = Ui32(Sbox[gb0(t)], Sbox[gb1(t)], Sbox[gb2(t)], Sbox[gb3(t)]); - w[i] = w[i - keySize] ^ t; + m--; + t ^= w[0]; + w[keySize] = t; } + while (++w != wLim); } -void MY_FAST_CALL Aes_SetKey_Dec(UInt32 *w, const Byte *key, unsigned keySize) +void Z7_FASTCALL Aes_SetKey_Dec(UInt32 *w, const Byte *key, unsigned keySize) { unsigned i, num; Aes_SetKey_Enc(w, key, keySize); @@ -167,10 +276,10 @@ void MY_FAST_CALL Aes_SetKey_Dec(UInt32 *w, const Byte *key, unsigned keySize) { UInt32 r = w[i]; w[i] = - D[ (unsigned)Sbox[gb0(r)]] ^ - D[0x100 + (unsigned)Sbox[gb1(r)]] ^ - D[0x200 + (unsigned)Sbox[gb2(r)]] ^ - D[0x300 + (unsigned)Sbox[gb3(r)]]; + DD(0)[Sbox[gb0(r)]] ^ + DD(1)[Sbox[gb1(r)]] ^ + DD(2)[Sbox[gb2(r)]] ^ + DD(3)[Sbox[gb3(r)]]; } } @@ -178,6 +287,7 @@ void MY_FAST_CALL Aes_SetKey_Dec(UInt32 *w, const Byte *key, unsigned keySize) src and dest are pointers to 4 UInt32 words. src and dest can point to same block */ +// Z7_FORCE_INLINE static void Aes_Encode(const UInt32 *w, UInt32 *dest, const UInt32 *src) { UInt32 s[4]; @@ -191,16 +301,20 @@ static void Aes_Encode(const UInt32 *w, UInt32 *dest, const UInt32 *src) w += 4; for (;;) { - HT16(m, s, 0); + HT16(m, s, 0) if (--numRounds2 == 0) break; - HT16(s, m, 4); + HT16(s, m, 4) w += 8; } w += 4; - FT4(0); FT4(1); FT4(2); FT4(3); + FT4(0) + FT4(1) + FT4(2) + FT4(3) } +Z7_FORCE_INLINE static void Aes_Decode(const UInt32 *w, UInt32 *dest, const UInt32 *src) { UInt32 s[4]; @@ -214,12 +328,15 @@ static void Aes_Decode(const UInt32 *w, UInt32 *dest, const UInt32 *src) for (;;) { w -= 8; - HD16(m, s, 4); + HD16(m, s, 4) if (--numRounds2 == 0) break; - HD16(s, m, 0); + HD16(s, m, 0) } - FD4(0); FD4(1); FD4(2); FD4(3); + FD4(0) + FD4(1) + FD4(2) + FD4(3) } void AesCbc_Init(UInt32 *p, const Byte *iv) @@ -229,7 +346,7 @@ void AesCbc_Init(UInt32 *p, const Byte *iv) p[i] = GetUi32(iv + i * 4); } -void MY_FAST_CALL AesCbc_Encode(UInt32 *p, Byte *data, size_t numBlocks) +void Z7_FASTCALL AesCbc_Encode(UInt32 *p, Byte *data, size_t numBlocks) { for (; numBlocks != 0; numBlocks--, data += AES_BLOCK_SIZE) { @@ -240,14 +357,14 @@ void MY_FAST_CALL AesCbc_Encode(UInt32 *p, Byte *data, size_t numBlocks) Aes_Encode(p + 4, p, p); - SetUi32(data, p[0]); - SetUi32(data + 4, p[1]); - SetUi32(data + 8, p[2]); - SetUi32(data + 12, p[3]); + SetUi32(data, p[0]) + SetUi32(data + 4, p[1]) + SetUi32(data + 8, p[2]) + SetUi32(data + 12, p[3]) } } -void MY_FAST_CALL AesCbc_Decode(UInt32 *p, Byte *data, size_t numBlocks) +void Z7_FASTCALL AesCbc_Decode(UInt32 *p, Byte *data, size_t numBlocks) { UInt32 in[4], out[4]; for (; numBlocks != 0; numBlocks--, data += AES_BLOCK_SIZE) @@ -259,10 +376,10 @@ void MY_FAST_CALL AesCbc_Decode(UInt32 *p, Byte *data, size_t numBlocks) Aes_Decode(p + 4, out, in); - SetUi32(data, p[0] ^ out[0]); - SetUi32(data + 4, p[1] ^ out[1]); - SetUi32(data + 8, p[2] ^ out[2]); - SetUi32(data + 12, p[3] ^ out[3]); + SetUi32(data, p[0] ^ out[0]) + SetUi32(data + 4, p[1] ^ out[1]) + SetUi32(data + 8, p[2] ^ out[2]) + SetUi32(data + 12, p[3] ^ out[3]) p[0] = in[0]; p[1] = in[1]; @@ -271,25 +388,42 @@ void MY_FAST_CALL AesCbc_Decode(UInt32 *p, Byte *data, size_t numBlocks) } } -void MY_FAST_CALL AesCtr_Code(UInt32 *p, Byte *data, size_t numBlocks) +void Z7_FASTCALL AesCtr_Code(UInt32 *p, Byte *data, size_t numBlocks) { for (; numBlocks != 0; numBlocks--) { UInt32 temp[4]; - Byte buf[16]; - int i; + unsigned i; if (++p[0] == 0) p[1]++; Aes_Encode(p + 4, temp, p); - SetUi32(buf, temp[0]); - SetUi32(buf + 4, temp[1]); - SetUi32(buf + 8, temp[2]); - SetUi32(buf + 12, temp[3]); - - for (i = 0; i < 16; i++) - *data++ ^= buf[i]; + for (i = 0; i < 4; i++, data += 4) + { + const UInt32 t = temp[i]; + + #ifdef MY_CPU_LE_UNALIGN + *((UInt32 *)(void *)data) ^= t; + #else + data[0] = (Byte)(data[0] ^ (t & 0xFF)); + data[1] = (Byte)(data[1] ^ ((t >> 8) & 0xFF)); + data[2] = (Byte)(data[2] ^ ((t >> 16) & 0xFF)); + data[3] = (Byte)(data[3] ^ ((t >> 24))); + #endif + } } } + +#undef xtime +#undef Ui32 +#undef gb0 +#undef gb1 +#undef gb2 +#undef gb3 +#undef gb +#undef TT +#undef DD +#undef USE_HW_AES +#undef PRF diff --git a/src/plugins/7zip/7za/c/Aes.h b/src/plugins/7zip/7za/c/Aes.h index 64979b5b..7f0182ac 100644 --- a/src/plugins/7zip/7za/c/Aes.h +++ b/src/plugins/7zip/7za/c/Aes.h @@ -1,8 +1,8 @@ /* Aes.h -- AES encryption / decryption -2013-01-18 : Igor Pavlov : Public domain */ +2023-04-02 : Igor Pavlov : Public domain */ -#ifndef __AES_H -#define __AES_H +#ifndef ZIP7_INC_AES_H +#define ZIP7_INC_AES_H #include "7zTypes.h" @@ -20,18 +20,40 @@ void AesGenTables(void); /* aes - 16-byte aligned pointer to keyMode+roundKeys sequence */ /* keySize = 16 or 24 or 32 (bytes) */ -typedef void (MY_FAST_CALL *AES_SET_KEY_FUNC)(UInt32 *aes, const Byte *key, unsigned keySize); -void MY_FAST_CALL Aes_SetKey_Enc(UInt32 *aes, const Byte *key, unsigned keySize); -void MY_FAST_CALL Aes_SetKey_Dec(UInt32 *aes, const Byte *key, unsigned keySize); +typedef void (Z7_FASTCALL *AES_SET_KEY_FUNC)(UInt32 *aes, const Byte *key, unsigned keySize); +void Z7_FASTCALL Aes_SetKey_Enc(UInt32 *aes, const Byte *key, unsigned keySize); +void Z7_FASTCALL Aes_SetKey_Dec(UInt32 *aes, const Byte *key, unsigned keySize); /* ivAes - 16-byte aligned pointer to iv+keyMode+roundKeys sequence: UInt32[AES_NUM_IVMRK_WORDS] */ void AesCbc_Init(UInt32 *ivAes, const Byte *iv); /* iv size is AES_BLOCK_SIZE */ + /* data - 16-byte aligned pointer to data */ /* numBlocks - the number of 16-byte blocks in data array */ -typedef void (MY_FAST_CALL *AES_CODE_FUNC)(UInt32 *ivAes, Byte *data, size_t numBlocks); -extern AES_CODE_FUNC g_AesCbc_Encode; +typedef void (Z7_FASTCALL *AES_CODE_FUNC)(UInt32 *ivAes, Byte *data, size_t numBlocks); + extern AES_CODE_FUNC g_AesCbc_Decode; +#ifndef Z7_SFX +extern AES_CODE_FUNC g_AesCbc_Encode; extern AES_CODE_FUNC g_AesCtr_Code; +#define k_Aes_SupportedFunctions_HW (1 << 2) +#define k_Aes_SupportedFunctions_HW_256 (1 << 3) +extern UInt32 g_Aes_SupportedFunctions_Flags; +#endif + + +#define Z7_DECLARE_AES_CODE_FUNC(funcName) \ + void Z7_FASTCALL funcName(UInt32 *ivAes, Byte *data, size_t numBlocks); + +Z7_DECLARE_AES_CODE_FUNC (AesCbc_Encode) +Z7_DECLARE_AES_CODE_FUNC (AesCbc_Decode) +Z7_DECLARE_AES_CODE_FUNC (AesCtr_Code) + +Z7_DECLARE_AES_CODE_FUNC (AesCbc_Encode_HW) +Z7_DECLARE_AES_CODE_FUNC (AesCbc_Decode_HW) +Z7_DECLARE_AES_CODE_FUNC (AesCtr_Code_HW) + +Z7_DECLARE_AES_CODE_FUNC (AesCbc_Decode_HW_256) +Z7_DECLARE_AES_CODE_FUNC (AesCtr_Code_HW_256) EXTERN_C_END diff --git a/src/plugins/7zip/7za/c/AesOpt.c b/src/plugins/7zip/7za/c/AesOpt.c index 10a8fb25..b2818073 100644 --- a/src/plugins/7zip/7za/c/AesOpt.c +++ b/src/plugins/7zip/7za/c/AesOpt.c @@ -1,184 +1,1002 @@ -/* AesOpt.c -- Intel's AES -2013-11-12 : Igor Pavlov : Public domain */ +/* AesOpt.c -- AES optimized code for x86 AES hardware instructions +Igor Pavlov : Public domain */ #include "Precomp.h" +#include "Aes.h" #include "CpuArch.h" #ifdef MY_CPU_X86_OR_AMD64 -#if _MSC_VER >= 1500 -#define USE_INTEL_AES -#endif -#endif + + #if defined(__INTEL_COMPILER) + #if (__INTEL_COMPILER >= 1110) + #define USE_INTEL_AES + #if (__INTEL_COMPILER >= 1900) + #define USE_INTEL_VAES + #endif + #endif + #elif defined(Z7_CLANG_VERSION) && (Z7_CLANG_VERSION >= 30800) \ + || defined(Z7_GCC_VERSION) && (Z7_GCC_VERSION >= 40400) + #define USE_INTEL_AES + #if !defined(__AES__) + #define ATTRIB_AES __attribute__((__target__("aes"))) + #endif + #if defined(__clang__) && (__clang_major__ >= 8) \ + || defined(__GNUC__) && (__GNUC__ >= 8) + #define USE_INTEL_VAES + #if !defined(__AES__) || !defined(__VAES__) || !defined(__AVX__) || !defined(__AVX2__) + #define ATTRIB_VAES __attribute__((__target__("aes,vaes,avx,avx2"))) + #endif + #endif + #elif defined(_MSC_VER) + #if (_MSC_VER > 1500) || (_MSC_FULL_VER >= 150030729) + #define USE_INTEL_AES + #if (_MSC_VER >= 1910) + #define USE_INTEL_VAES + #endif + #endif + #ifndef USE_INTEL_AES + #define Z7_USE_AES_HW_STUB + #endif + #ifndef USE_INTEL_VAES + #define Z7_USE_VAES_HW_STUB + #endif + #endif + + #ifndef USE_INTEL_AES + // #define Z7_USE_AES_HW_STUB // for debug + #endif + #ifndef USE_INTEL_VAES + // #define Z7_USE_VAES_HW_STUB // for debug + #endif + #ifdef USE_INTEL_AES #include -void MY_FAST_CALL AesCbc_Encode_Intel(__m128i *p, __m128i *data, size_t numBlocks) +#if !defined(USE_INTEL_VAES) && defined(Z7_USE_VAES_HW_STUB) +#define AES_TYPE_keys UInt32 +#define AES_TYPE_data Byte +// #define AES_TYPE_keys __m128i +// #define AES_TYPE_data __m128i +#endif + +#ifndef ATTRIB_AES + #define ATTRIB_AES +#endif + +#define AES_FUNC_START(name) \ + void Z7_FASTCALL name(UInt32 *ivAes, Byte *data8, size_t numBlocks) + // void Z7_FASTCALL name(__m128i *p, __m128i *data, size_t numBlocks) + +#define AES_FUNC_START2(name) \ +AES_FUNC_START (name); \ +ATTRIB_AES \ +AES_FUNC_START (name) + +#define MM_OP(op, dest, src) dest = op(dest, src); +#define MM_OP_m(op, src) MM_OP(op, m, src) + +#define MM_XOR( dest, src) MM_OP(_mm_xor_si128, dest, src) + +#if 1 +// use aligned SSE load/store for data. +// It is required for our Aes functions, that data is aligned for 16-bytes. +// So we can use this branch of code. +// and compiler can use fused load-op SSE instructions: +// xorps xmm0, XMMWORD PTR [rdx] +#define LOAD_128(pp) (*(__m128i *)(void *)(pp)) +#define STORE_128(pp, _v) *(__m128i *)(void *)(pp) = _v +// use aligned SSE load/store for data. Alternative code with direct access +// #define LOAD_128(pp) _mm_load_si128(pp) +// #define STORE_128(pp, _v) _mm_store_si128(pp, _v) +#else +// use unaligned load/store for data: movdqu XMMWORD PTR [rdx] +#define LOAD_128(pp) _mm_loadu_si128(pp) +#define STORE_128(pp, _v) _mm_storeu_si128(pp, _v) +#endif + +AES_FUNC_START2 (AesCbc_Encode_HW) { + if (numBlocks == 0) + return; + { + __m128i *p = (__m128i *)(void *)ivAes; + __m128i *data = (__m128i *)(void *)data8; __m128i m = *p; - for (; numBlocks != 0; numBlocks--, data++) + const __m128i k0 = p[2]; + const __m128i k1 = p[3]; + const UInt32 numRounds2 = *(const UInt32 *)(p + 1) - 1; + do { - UInt32 numRounds2 = *(const UInt32 *)(p + 1) - 1; - const __m128i *w = p + 3; - m = _mm_xor_si128(m, *data); - m = _mm_xor_si128(m, p[2]); + UInt32 r = numRounds2; + const __m128i *w = p + 4; + __m128i temp = LOAD_128(data); + MM_XOR (temp, k0) + MM_XOR (m, temp) + MM_OP_m (_mm_aesenc_si128, k1) do { - m = _mm_aesenc_si128(m, w[0]); - m = _mm_aesenc_si128(m, w[1]); + MM_OP_m (_mm_aesenc_si128, w[0]) + MM_OP_m (_mm_aesenc_si128, w[1]) w += 2; } - while (--numRounds2 != 0); - m = _mm_aesenc_si128(m, w[0]); - m = _mm_aesenclast_si128(m, w[1]); - *data = m; + while (--r); + MM_OP_m (_mm_aesenclast_si128, w[0]) + STORE_128(data, m); + data++; } + while (--numBlocks); *p = m; + } } -#define NUM_WAYS 3 -#define AES_OP_W(op, n) { \ - const __m128i t = w[n]; \ - m0 = op(m0, t); \ - m1 = op(m1, t); \ - m2 = op(m2, t); \ - } +#define WOP_1(op) +#define WOP_2(op) WOP_1 (op) op (m1, 1) +#define WOP_3(op) WOP_2 (op) op (m2, 2) +#define WOP_4(op) WOP_3 (op) op (m3, 3) +#ifdef MY_CPU_AMD64 +#define WOP_5(op) WOP_4 (op) op (m4, 4) +#define WOP_6(op) WOP_5 (op) op (m5, 5) +#define WOP_7(op) WOP_6 (op) op (m6, 6) +#define WOP_8(op) WOP_7 (op) op (m7, 7) +#endif +/* +#define WOP_9(op) WOP_8 (op) op (m8, 8); +#define WOP_10(op) WOP_9 (op) op (m9, 9); +#define WOP_11(op) WOP_10(op) op (m10, 10); +#define WOP_12(op) WOP_11(op) op (m11, 11); +#define WOP_13(op) WOP_12(op) op (m12, 12); +#define WOP_14(op) WOP_13(op) op (m13, 13); +*/ + +#ifdef MY_CPU_AMD64 + #define NUM_WAYS 8 + #define WOP_M1 WOP_8 +#else + #define NUM_WAYS 4 + #define WOP_M1 WOP_4 +#endif + +#define WOP(op) op (m0, 0) WOP_M1(op) + +#define DECLARE_VAR(reg, ii) __m128i reg; +#define LOAD_data_ii(ii) LOAD_128(data + (ii)) +#define LOAD_data( reg, ii) reg = LOAD_data_ii(ii); +#define STORE_data( reg, ii) STORE_128(data + (ii), reg); +#if (NUM_WAYS > 1) +#define XOR_data_M1(reg, ii) MM_XOR (reg, LOAD_128(data + (ii- 1))) +#endif + +#define MM_OP_key(op, reg) MM_OP(op, reg, key); -#define AES_DEC(n) AES_OP_W(_mm_aesdec_si128, n) -#define AES_DEC_LAST(n) AES_OP_W(_mm_aesdeclast_si128, n) -#define AES_ENC(n) AES_OP_W(_mm_aesenc_si128, n) -#define AES_ENC_LAST(n) AES_OP_W(_mm_aesenclast_si128, n) +#define AES_DEC( reg, ii) MM_OP_key (_mm_aesdec_si128, reg) +#define AES_DEC_LAST( reg, ii) MM_OP_key (_mm_aesdeclast_si128, reg) +#define AES_ENC( reg, ii) MM_OP_key (_mm_aesenc_si128, reg) +#define AES_ENC_LAST( reg, ii) MM_OP_key (_mm_aesenclast_si128, reg) +#define AES_XOR( reg, ii) MM_OP_key (_mm_xor_si128, reg) -void MY_FAST_CALL AesCbc_Decode_Intel(__m128i *p, __m128i *data, size_t numBlocks) +#define CTR_START(reg, ii) MM_OP (_mm_add_epi64, ctr, one) reg = ctr; +#define CTR_END( reg, ii) STORE_128(data + (ii), _mm_xor_si128(reg, \ + LOAD_128 (data + (ii)))); +#define WOP_KEY(op, n) { \ + const __m128i key = w[n]; \ + WOP(op) } + +#define WIDE_LOOP_START \ + dataEnd = data + numBlocks; \ + if (numBlocks >= NUM_WAYS) \ + { dataEnd -= NUM_WAYS; do { \ + +#define WIDE_LOOP_END \ + data += NUM_WAYS; \ + } while (data <= dataEnd); \ + dataEnd += NUM_WAYS; } \ + +#define SINGLE_LOOP \ + for (; data < dataEnd; data++) + + + +#ifdef USE_INTEL_VAES + +#define AVX_XOR(dest, src) MM_OP(_mm256_xor_si256, dest, src) +#define AVX_DECLARE_VAR(reg, ii) __m256i reg; + +#if 1 +// use unaligned AVX load/store for data. +// It is required for our Aes functions, that data is aligned for 16-bytes. +// But we need 32-bytes reading. +// So we use intrinsics for unaligned AVX load/store. +// notes for _mm256_storeu_si256: +// msvc2022: uses vmovdqu and keeps the order of instruction sequence. +// new gcc11 uses vmovdqu +// old gcc9 could use pair of instructions: +// vmovups %xmm7, -224(%rax) +// vextracti128 $0x1, %ymm7, -208(%rax) +#define AVX_LOAD(p) _mm256_loadu_si256((const __m256i *)(const void *)(p)) +#define AVX_STORE(p, _v) _mm256_storeu_si256((__m256i *)(void *)(p), _v); +#else +// use aligned AVX load/store for data. +// for debug: we can use this branch, if we are sure that data is aligned for 32-bytes. +// msvc2022 uses vmovdqu still +// gcc uses vmovdqa (that requires 32-bytes alignment) +#define AVX_LOAD(p) (*(const __m256i *)(const void *)(p)) +#define AVX_STORE(p, _v) (*(__m256i *)(void *)(p)) = _v; +#endif + +#define AVX_LOAD_data( reg, ii) reg = AVX_LOAD((const __m256i *)(const void *)data + (ii)); +#define AVX_STORE_data( reg, ii) AVX_STORE((__m256i *)(void *)data + (ii), reg) +/* +AVX_XOR_data_M1() needs unaligned memory load, even if (data) +is aligned for 256-bits, because we read 32-bytes chunk that +crosses (data) position: from (data - 16bytes) to (data + 16bytes). +*/ +#define AVX_XOR_data_M1(reg, ii) AVX_XOR (reg, _mm256_loadu_si256((const __m256i *)(const void *)(data - 1) + (ii))) + +#define AVX_AES_DEC( reg, ii) MM_OP_key (_mm256_aesdec_epi128, reg) +#define AVX_AES_DEC_LAST( reg, ii) MM_OP_key (_mm256_aesdeclast_epi128, reg) +#define AVX_AES_ENC( reg, ii) MM_OP_key (_mm256_aesenc_epi128, reg) +#define AVX_AES_ENC_LAST( reg, ii) MM_OP_key (_mm256_aesenclast_epi128, reg) +#define AVX_AES_XOR( reg, ii) MM_OP_key (_mm256_xor_si256, reg) +#define AVX_CTR_START(reg, ii) \ + MM_OP (_mm256_add_epi64, ctr2, two) \ + reg = _mm256_xor_si256(ctr2, key); + +#define AVX_CTR_END(reg, ii) \ + AVX_STORE((__m256i *)(void *)data + (ii), _mm256_xor_si256(reg, \ + AVX_LOAD ((__m256i *)(void *)data + (ii)))); + +#define AVX_WOP_KEY(op, n) { \ + const __m256i key = w[n]; \ + WOP(op) } + +#define NUM_AES_KEYS_MAX 15 + +#define WIDE_LOOP_START_AVX(OP) \ + dataEnd = data + numBlocks; \ + if (numBlocks >= NUM_WAYS * 2) \ + { __m256i keys[NUM_AES_KEYS_MAX]; \ + OP \ + { UInt32 ii; for (ii = 0; ii < numRounds; ii++) \ + keys[ii] = _mm256_broadcastsi128_si256(p[ii]); } \ + dataEnd -= NUM_WAYS * 2; \ + do { \ + +#define WIDE_LOOP_END_AVX(OP) \ + data += NUM_WAYS * 2; \ + } while (data <= dataEnd); \ + dataEnd += NUM_WAYS * 2; \ + OP \ + _mm256_zeroupper(); \ + } \ + +/* MSVC for x86: If we don't call _mm256_zeroupper(), and -arch:IA32 is not specified, + MSVC still can insert vzeroupper instruction. */ + +#endif + + + +AES_FUNC_START2 (AesCbc_Decode_HW) { + __m128i *p = (__m128i *)(void *)ivAes; + __m128i *data = (__m128i *)(void *)data8; __m128i iv = *p; - for (; numBlocks >= NUM_WAYS; numBlocks -= NUM_WAYS, data += NUM_WAYS) + const __m128i * const wStart = p + (size_t)*(const UInt32 *)(p + 1) * 2 + 2 - 1; + const __m128i *dataEnd; + p += 2; + + WIDE_LOOP_START { - UInt32 numRounds2 = *(const UInt32 *)(p + 1); - const __m128i *w = p + numRounds2 * 2; - __m128i m0, m1, m2; + const __m128i *w = wStart; + WOP (DECLARE_VAR) + WOP (LOAD_data) + WOP_KEY (AES_XOR, 1) + do { - const __m128i t = w[2]; - m0 = _mm_xor_si128(t, data[0]); - m1 = _mm_xor_si128(t, data[1]); - m2 = _mm_xor_si128(t, data[2]); + WOP_KEY (AES_DEC, 0) + + w--; } - numRounds2--; + while (w != p); + WOP_KEY (AES_DEC_LAST, 0) + + MM_XOR (m0, iv) + WOP_M1 (XOR_data_M1) + LOAD_data(iv, NUM_WAYS - 1) + WOP (STORE_data) + } + WIDE_LOOP_END + + SINGLE_LOOP + { + const __m128i *w = wStart - 1; + __m128i m = _mm_xor_si128 (w[2], LOAD_data_ii(0)); + do { - AES_DEC(1) - AES_DEC(0) + MM_OP_m (_mm_aesdec_si128, w[1]) + MM_OP_m (_mm_aesdec_si128, w[0]) w -= 2; } - while (--numRounds2 != 0); - AES_DEC(1) - AES_DEC_LAST(0) + while (w != p); + MM_OP_m (_mm_aesdec_si128, w[1]) + MM_OP_m (_mm_aesdeclast_si128, w[0]) + MM_XOR (m, iv) + LOAD_data(iv, 0) + STORE_data(m, 0) + } + + p[-2] = iv; +} + + +AES_FUNC_START2 (AesCtr_Code_HW) +{ + __m128i *p = (__m128i *)(void *)ivAes; + __m128i *data = (__m128i *)(void *)data8; + __m128i ctr = *p; + const UInt32 numRoundsMinus2 = *(const UInt32 *)(p + 1) * 2 - 1; + const __m128i *dataEnd; + const __m128i one = _mm_cvtsi32_si128(1); + p += 2; + + WIDE_LOOP_START + { + const __m128i *w = p; + UInt32 r = numRoundsMinus2; + WOP (DECLARE_VAR) + WOP (CTR_START) + WOP_KEY (AES_XOR, 0) + w += 1; + do { - __m128i t; - t = _mm_xor_si128(m0, iv); iv = data[0]; data[0] = t; - t = _mm_xor_si128(m1, iv); iv = data[1]; data[1] = t; - t = _mm_xor_si128(m2, iv); iv = data[2]; data[2] = t; + WOP_KEY (AES_ENC, 0) + w += 1; } + while (--r); + WOP_KEY (AES_ENC_LAST, 0) + WOP (CTR_END) } - for (; numBlocks != 0; numBlocks--, data++) + WIDE_LOOP_END + + SINGLE_LOOP { - UInt32 numRounds2 = *(const UInt32 *)(p + 1); - const __m128i *w = p + numRounds2 * 2; - __m128i m = _mm_xor_si128(w[2], *data); - numRounds2--; + UInt32 numRounds2 = *(const UInt32 *)(p - 2 + 1) - 1; + const __m128i *w = p; + __m128i m; + MM_OP (_mm_add_epi64, ctr, one) + m = _mm_xor_si128 (ctr, p[0]); + w += 1; do { - m = _mm_aesdec_si128(m, w[1]); - m = _mm_aesdec_si128(m, w[0]); - w -= 2; + MM_OP_m (_mm_aesenc_si128, w[0]) + MM_OP_m (_mm_aesenc_si128, w[1]) + w += 2; } - while (--numRounds2 != 0); - m = _mm_aesdec_si128(m, w[1]); - m = _mm_aesdeclast_si128(m, w[0]); + while (--numRounds2); + MM_OP_m (_mm_aesenc_si128, w[0]) + MM_OP_m (_mm_aesenclast_si128, w[1]) + CTR_END (m, 0) + } + + p[-2] = ctr; +} + + - m = _mm_xor_si128(m, iv); - iv = *data; - *data = m; +#ifdef USE_INTEL_VAES + +/* +GCC before 2013-Jun: + : + #ifdef __AVX__ + #include + #endif +GCC after 2013-Jun: + : + #include +CLANG 3.8+: +{ + : + #if !defined(_MSC_VER) || defined(__AVX__) + #include + #endif + + if (the compiler is clang for Windows and if global arch is not set for __AVX__) + [ if (defined(_MSC_VER) && !defined(__AVX__)) ] + { + doesn't include + and we have 2 ways to fix it: + 1) we can define required __AVX__ before + or + 2) we can include after } - *p = iv; } -void MY_FAST_CALL AesCtr_Code_Intel(__m128i *p, __m128i *data, size_t numBlocks) +If we include manually for GCC/CLANG, it's +required that must be included before . +*/ + +/* +#if defined(__clang__) && defined(_MSC_VER) +#define __AVX__ +#define __AVX2__ +#define __VAES__ +#endif +*/ + +#include +#if defined(__clang__) && defined(_MSC_VER) + #if !defined(__AVX__) + #include + #endif + #if !defined(__AVX2__) + #include + #endif + #if !defined(__VAES__) + #include + #endif +#endif // __clang__ && _MSC_VER + +#ifndef ATTRIB_VAES + #define ATTRIB_VAES +#endif + +#define VAES_FUNC_START2(name) \ +AES_FUNC_START (name); \ +ATTRIB_VAES \ +AES_FUNC_START (name) + +VAES_FUNC_START2 (AesCbc_Decode_HW_256) { - __m128i ctr = *p; - __m128i one; - one.m128i_u64[0] = 1; - one.m128i_u64[1] = 0; - for (; numBlocks >= NUM_WAYS; numBlocks -= NUM_WAYS, data += NUM_WAYS) + __m128i *p = (__m128i *)(void *)ivAes; + __m128i *data = (__m128i *)(void *)data8; + __m128i iv = *p; + const __m128i *dataEnd; + const UInt32 numRounds = *(const UInt32 *)(p + 1) * 2 + 1; + p += 2; + + WIDE_LOOP_START_AVX(;) { - UInt32 numRounds2 = *(const UInt32 *)(p + 1) - 1; - const __m128i *w = p; - __m128i m0, m1, m2; + const __m256i *w = keys + numRounds - 2; + + WOP (AVX_DECLARE_VAR) + WOP (AVX_LOAD_data) + AVX_WOP_KEY (AVX_AES_XOR, 1) + + do { - const __m128i t = w[2]; - ctr = _mm_add_epi64(ctr, one); m0 = _mm_xor_si128(ctr, t); - ctr = _mm_add_epi64(ctr, one); m1 = _mm_xor_si128(ctr, t); - ctr = _mm_add_epi64(ctr, one); m2 = _mm_xor_si128(ctr, t); + AVX_WOP_KEY (AVX_AES_DEC, 0) + w--; } - w += 3; + while (w != keys); + AVX_WOP_KEY (AVX_AES_DEC_LAST, 0) + + AVX_XOR (m0, _mm256_setr_m128i(iv, LOAD_data_ii(0))) + WOP_M1 (AVX_XOR_data_M1) + LOAD_data (iv, NUM_WAYS * 2 - 1) + WOP (AVX_STORE_data) + } + WIDE_LOOP_END_AVX(;) + + SINGLE_LOOP + { + const __m128i *w = p - 2 + (size_t)*(const UInt32 *)(p + 1 - 2) * 2; + __m128i m = _mm_xor_si128 (w[2], LOAD_data_ii(0)); do { - AES_ENC(0) - AES_ENC(1) - w += 2; + MM_OP_m (_mm_aesdec_si128, w[1]) + MM_OP_m (_mm_aesdec_si128, w[0]) + w -= 2; + } + while (w != p); + MM_OP_m (_mm_aesdec_si128, w[1]) + MM_OP_m (_mm_aesdeclast_si128, w[0]) + + MM_XOR (m, iv) + LOAD_data(iv, 0) + STORE_data(m, 0) + } + + p[-2] = iv; +} + + +/* +SSE2: _mm_cvtsi32_si128 : movd +AVX: _mm256_setr_m128i : vinsertf128 +AVX2: _mm256_add_epi64 : vpaddq ymm, ymm, ymm + _mm256_extracti128_si256 : vextracti128 + _mm256_broadcastsi128_si256 : vbroadcasti128 +*/ + +#define AVX_CTR_LOOP_START \ + ctr2 = _mm256_setr_m128i(_mm_sub_epi64(ctr, one), ctr); \ + two = _mm256_setr_m128i(one, one); \ + two = _mm256_add_epi64(two, two); \ + +// two = _mm256_setr_epi64x(2, 0, 2, 0); + +#define AVX_CTR_LOOP_ENC \ + ctr = _mm256_extracti128_si256 (ctr2, 1); \ + +VAES_FUNC_START2 (AesCtr_Code_HW_256) +{ + __m128i *p = (__m128i *)(void *)ivAes; + __m128i *data = (__m128i *)(void *)data8; + __m128i ctr = *p; + const UInt32 numRounds = *(const UInt32 *)(p + 1) * 2 + 1; + const __m128i *dataEnd; + const __m128i one = _mm_cvtsi32_si128(1); + __m256i ctr2, two; + p += 2; + + WIDE_LOOP_START_AVX (AVX_CTR_LOOP_START) + { + const __m256i *w = keys; + UInt32 r = numRounds - 2; + WOP (AVX_DECLARE_VAR) + AVX_WOP_KEY (AVX_CTR_START, 0) + + w += 1; + do + { + AVX_WOP_KEY (AVX_AES_ENC, 0) + w += 1; } - while (--numRounds2 != 0); - AES_ENC(0) - AES_ENC_LAST(1) - data[0] = _mm_xor_si128(data[0], m0); - data[1] = _mm_xor_si128(data[1], m1); - data[2] = _mm_xor_si128(data[2], m2); + while (--r); + AVX_WOP_KEY (AVX_AES_ENC_LAST, 0) + + WOP (AVX_CTR_END) } - for (; numBlocks != 0; numBlocks--, data++) + WIDE_LOOP_END_AVX (AVX_CTR_LOOP_ENC) + + SINGLE_LOOP { - UInt32 numRounds2 = *(const UInt32 *)(p + 1) - 1; + UInt32 numRounds2 = *(const UInt32 *)(p - 2 + 1) - 1; const __m128i *w = p; __m128i m; - ctr = _mm_add_epi64(ctr, one); - m = _mm_xor_si128(ctr, p[2]); - w += 3; + MM_OP (_mm_add_epi64, ctr, one) + m = _mm_xor_si128 (ctr, p[0]); + w += 1; do { - m = _mm_aesenc_si128(m, w[0]); - m = _mm_aesenc_si128(m, w[1]); + MM_OP_m (_mm_aesenc_si128, w[0]) + MM_OP_m (_mm_aesenc_si128, w[1]) w += 2; } - while (--numRounds2 != 0); - m = _mm_aesenc_si128(m, w[0]); - m = _mm_aesenclast_si128(m, w[1]); - *data = _mm_xor_si128(*data, m); + while (--numRounds2); + MM_OP_m (_mm_aesenc_si128, w[0]) + MM_OP_m (_mm_aesenclast_si128, w[1]) + CTR_END (m, 0) } - *p = ctr; + + p[-2] = ctr; } +#endif // USE_INTEL_VAES + +#else // USE_INTEL_AES + +/* no USE_INTEL_AES */ + +#if defined(Z7_USE_AES_HW_STUB) +// We can compile this file with another C compiler, +// or we can compile asm version. +// So we can generate real code instead of this stub function. +// #if defined(_MSC_VER) +#pragma message("AES HW_SW stub was used") +// #endif + +#if !defined(USE_INTEL_VAES) && defined(Z7_USE_VAES_HW_STUB) +#define AES_TYPE_keys UInt32 +#define AES_TYPE_data Byte +#endif + +#define AES_FUNC_START(name) \ + void Z7_FASTCALL name(UInt32 *p, Byte *data, size_t numBlocks) \ + +#define AES_COMPAT_STUB(name) \ + AES_FUNC_START(name); \ + AES_FUNC_START(name ## _HW) \ + { name(p, data, numBlocks); } + +AES_COMPAT_STUB (AesCbc_Encode) +AES_COMPAT_STUB (AesCbc_Decode) +AES_COMPAT_STUB (AesCtr_Code) +#endif // Z7_USE_AES_HW_STUB + +#endif // USE_INTEL_AES + + +#ifndef USE_INTEL_VAES +#if defined(Z7_USE_VAES_HW_STUB) +// #if defined(_MSC_VER) +#pragma message("VAES HW_SW stub was used") +// #endif + +#define VAES_COMPAT_STUB(name) \ + void Z7_FASTCALL name ## _256(UInt32 *p, Byte *data, size_t numBlocks); \ + void Z7_FASTCALL name ## _256(UInt32 *p, Byte *data, size_t numBlocks) \ + { name((AES_TYPE_keys *)(void *)p, (AES_TYPE_data *)(void *)data, numBlocks); } + +VAES_COMPAT_STUB (AesCbc_Decode_HW) +VAES_COMPAT_STUB (AesCtr_Code_HW) +#endif +#endif // ! USE_INTEL_VAES + + + + +#elif defined(MY_CPU_ARM_OR_ARM64) && defined(MY_CPU_LE) + + #if defined(__ARM_FEATURE_AES) \ + || defined(__ARM_FEATURE_CRYPTO) + #define USE_HW_AES + #else + #if defined(MY_CPU_ARM64) \ + || defined(__ARM_ARCH) && (__ARM_ARCH >= 4) \ + || defined(Z7_MSC_VER_ORIGINAL) + #if defined(__ARM_FP) && \ + ( defined(Z7_CLANG_VERSION) && (Z7_CLANG_VERSION >= 30800) \ + || defined(__GNUC__) && (__GNUC__ >= 6) \ + ) \ + || defined(Z7_MSC_VER_ORIGINAL) && (_MSC_VER >= 1910) + #if defined(MY_CPU_ARM64) \ + || !defined(Z7_CLANG_VERSION) \ + || defined(__ARM_NEON) && \ + (Z7_CLANG_VERSION < 170000 || \ + Z7_CLANG_VERSION > 170001) + #define USE_HW_AES + #endif + #endif + #endif + #endif + +#ifdef USE_HW_AES + +// #pragma message("=== AES HW === ") +// __ARM_FEATURE_CRYPTO macro is deprecated in favor of the finer grained feature macro __ARM_FEATURE_AES + +#if defined(__clang__) || defined(__GNUC__) +#if !defined(__ARM_FEATURE_AES) && \ + !defined(__ARM_FEATURE_CRYPTO) + #ifdef MY_CPU_ARM64 +#if defined(__clang__) + #define ATTRIB_AES __attribute__((__target__("crypto"))) #else + #define ATTRIB_AES __attribute__((__target__("+crypto"))) +#endif + #else +#if defined(__clang__) + #define ATTRIB_AES __attribute__((__target__("armv8-a,aes"))) +#else + #define ATTRIB_AES __attribute__((__target__("fpu=crypto-neon-fp-armv8"))) +#endif + #endif +#endif +#else + // _MSC_VER + // for arm32 + #define _ARM_USE_NEW_NEON_INTRINSICS +#endif + +#ifndef ATTRIB_AES + #define ATTRIB_AES +#endif + +#if defined(Z7_MSC_VER_ORIGINAL) && defined(MY_CPU_ARM64) +#include +#else +/* + clang-17.0.1: error : Cannot select: intrinsic %llvm.arm.neon.aese + clang + 3.8.1 : __ARM_NEON : defined(__ARM_FEATURE_CRYPTO) + 7.0.1 : __ARM_NEON : __ARM_ARCH >= 8 && defined(__ARM_FEATURE_CRYPTO) + 11.?.0 : __ARM_NEON && __ARM_FP : __ARM_ARCH >= 8 && defined(__ARM_FEATURE_CRYPTO) + 13.0.1 : __ARM_NEON && __ARM_FP : __ARM_ARCH >= 8 && defined(__ARM_FEATURE_AES) + 16 : __ARM_NEON && __ARM_FP : __ARM_ARCH >= 8 +*/ +#if defined(__clang__) && __clang_major__ < 16 +#if !defined(__ARM_FEATURE_AES) && \ + !defined(__ARM_FEATURE_CRYPTO) +// #pragma message("=== we set __ARM_FEATURE_CRYPTO 1 === ") + Z7_DIAGNOSTIC_IGNORE_BEGIN_RESERVED_MACRO_IDENTIFIER + #define Z7_ARM_FEATURE_CRYPTO_WAS_SET 1 +// #if defined(__clang__) && __clang_major__ < 13 + #define __ARM_FEATURE_CRYPTO 1 +// #else + #define __ARM_FEATURE_AES 1 +// #endif + Z7_DIAGNOSTIC_IGNORE_END_RESERVED_MACRO_IDENTIFIER +#endif +#endif // clang + +#if defined(__clang__) + +#if defined(__ARM_ARCH) && __ARM_ARCH < 8 + Z7_DIAGNOSTIC_IGNORE_BEGIN_RESERVED_MACRO_IDENTIFIER +// #pragma message("#define __ARM_ARCH 8") + #undef __ARM_ARCH + #define __ARM_ARCH 8 + Z7_DIAGNOSTIC_IGNORE_END_RESERVED_MACRO_IDENTIFIER +#endif + +#endif // clang + +#include + +#if defined(Z7_ARM_FEATURE_CRYPTO_WAS_SET) && \ + defined(__ARM_FEATURE_CRYPTO) && \ + defined(__ARM_FEATURE_AES) +Z7_DIAGNOSTIC_IGNORE_BEGIN_RESERVED_MACRO_IDENTIFIER + #undef __ARM_FEATURE_CRYPTO + #undef __ARM_FEATURE_AES + #undef Z7_ARM_FEATURE_CRYPTO_WAS_SET +Z7_DIAGNOSTIC_IGNORE_END_RESERVED_MACRO_IDENTIFIER +// #pragma message("=== we undefine __ARM_FEATURE_CRYPTO === ") +#endif + +#endif // Z7_MSC_VER_ORIGINAL + +typedef uint8x16_t v128; -void MY_FAST_CALL AesCbc_Encode(UInt32 *ivAes, Byte *data, size_t numBlocks); -void MY_FAST_CALL AesCbc_Decode(UInt32 *ivAes, Byte *data, size_t numBlocks); -void MY_FAST_CALL AesCtr_Code(UInt32 *ivAes, Byte *data, size_t numBlocks); +#define AES_FUNC_START(name) \ + void Z7_FASTCALL name(UInt32 *ivAes, Byte *data8, size_t numBlocks) + // void Z7_FASTCALL name(v128 *p, v128 *data, size_t numBlocks) -void MY_FAST_CALL AesCbc_Encode_Intel(UInt32 *p, Byte *data, size_t numBlocks) +#define AES_FUNC_START2(name) \ +AES_FUNC_START (name); \ +ATTRIB_AES \ +AES_FUNC_START (name) + +#define MM_OP(op, dest, src) dest = op(dest, src); +#define MM_OP_m(op, src) MM_OP(op, m, src) +#define MM_OP1_m(op) m = op(m); + +#define MM_XOR( dest, src) MM_OP(veorq_u8, dest, src) +#define MM_XOR_m( src) MM_XOR(m, src) + +#define AES_E_m(k) MM_OP_m (vaeseq_u8, k) +#define AES_E_MC_m(k) AES_E_m (k) MM_OP1_m(vaesmcq_u8) + + +AES_FUNC_START2 (AesCbc_Encode_HW) { - AesCbc_Encode(p, data, numBlocks); + if (numBlocks == 0) + return; + { + v128 * const p = (v128 *)(void *)ivAes; + v128 *data = (v128 *)(void *)data8; + v128 m = *p; + const UInt32 numRounds2 = *(const UInt32 *)(p + 1); + const v128 *w = p + (size_t)numRounds2 * 2; + const v128 k0 = p[2]; + const v128 k1 = p[3]; + const v128 k2 = p[4]; + const v128 k3 = p[5]; + const v128 k4 = p[6]; + const v128 k5 = p[7]; + const v128 k6 = p[8]; + const v128 k7 = p[9]; + const v128 k8 = p[10]; + const v128 k9 = p[11]; + const v128 k_z4 = w[-2]; + const v128 k_z3 = w[-1]; + const v128 k_z2 = w[0]; + const v128 k_z1 = w[1]; + const v128 k_z0 = w[2]; + // we don't use optimization veorq_u8(*data, k_z0) that can reduce one cycle, + // because gcc/clang compilers are not good for that optimization. + do + { + MM_XOR_m (*data) + AES_E_MC_m (k0) + AES_E_MC_m (k1) + AES_E_MC_m (k2) + AES_E_MC_m (k3) + AES_E_MC_m (k4) + AES_E_MC_m (k5) + if (numRounds2 >= 6) + { + AES_E_MC_m (k6) + AES_E_MC_m (k7) + if (numRounds2 != 6) + { + AES_E_MC_m (k8) + AES_E_MC_m (k9) + } + } + AES_E_MC_m (k_z4) + AES_E_MC_m (k_z3) + AES_E_MC_m (k_z2) + AES_E_m (k_z1) + MM_XOR_m (k_z0) + *data++ = m; + } + while (--numBlocks); + *p = m; + } } -void MY_FAST_CALL AesCbc_Decode_Intel(UInt32 *p, Byte *data, size_t numBlocks) + +#define WOP_1(op) +#define WOP_2(op) WOP_1 (op) op (m1, 1) +#define WOP_3(op) WOP_2 (op) op (m2, 2) +#define WOP_4(op) WOP_3 (op) op (m3, 3) +#define WOP_5(op) WOP_4 (op) op (m4, 4) +#define WOP_6(op) WOP_5 (op) op (m5, 5) +#define WOP_7(op) WOP_6 (op) op (m6, 6) +#define WOP_8(op) WOP_7 (op) op (m7, 7) + + #define NUM_WAYS 8 + #define WOP_M1 WOP_8 + +#define WOP(op) op (m0, 0) WOP_M1(op) + +#define DECLARE_VAR(reg, ii) v128 reg; +#define LOAD_data( reg, ii) reg = data[ii]; +#define STORE_data( reg, ii) data[ii] = reg; +#if (NUM_WAYS > 1) +#define XOR_data_M1(reg, ii) MM_XOR (reg, data[ii- 1]) +#endif + +#define MM_OP_key(op, reg) MM_OP (op, reg, key) + +#define AES_D_m(k) MM_OP_m (vaesdq_u8, k) +#define AES_D_IMC_m(k) AES_D_m (k) MM_OP1_m (vaesimcq_u8) + +#define AES_XOR( reg, ii) MM_OP_key (veorq_u8, reg) +#define AES_D( reg, ii) MM_OP_key (vaesdq_u8, reg) +#define AES_E( reg, ii) MM_OP_key (vaeseq_u8, reg) + +#define AES_D_IMC( reg, ii) AES_D (reg, ii) reg = vaesimcq_u8(reg); +#define AES_E_MC( reg, ii) AES_E (reg, ii) reg = vaesmcq_u8(reg); + +#define CTR_START(reg, ii) MM_OP (vaddq_u64, ctr, one) reg = vreinterpretq_u8_u64(ctr); +#define CTR_END( reg, ii) MM_XOR (data[ii], reg) + +#define WOP_KEY(op, n) { \ + const v128 key = w[n]; \ + WOP(op) } + +#define WIDE_LOOP_START \ + dataEnd = data + numBlocks; \ + if (numBlocks >= NUM_WAYS) \ + { dataEnd -= NUM_WAYS; do { \ + +#define WIDE_LOOP_END \ + data += NUM_WAYS; \ + } while (data <= dataEnd); \ + dataEnd += NUM_WAYS; } \ + +#define SINGLE_LOOP \ + for (; data < dataEnd; data++) + + +AES_FUNC_START2 (AesCbc_Decode_HW) { - AesCbc_Decode(p, data, numBlocks); + v128 *p = (v128 *)(void *)ivAes; + v128 *data = (v128 *)(void *)data8; + v128 iv = *p; + const v128 * const wStart = p + (size_t)*(const UInt32 *)(p + 1) * 2; + const v128 *dataEnd; + p += 2; + + WIDE_LOOP_START + { + const v128 *w = wStart; + WOP (DECLARE_VAR) + WOP (LOAD_data) + WOP_KEY (AES_D_IMC, 2) + do + { + WOP_KEY (AES_D_IMC, 1) + WOP_KEY (AES_D_IMC, 0) + w -= 2; + } + while (w != p); + WOP_KEY (AES_D, 1) + WOP_KEY (AES_XOR, 0) + MM_XOR (m0, iv) + WOP_M1 (XOR_data_M1) + LOAD_data(iv, NUM_WAYS - 1) + WOP (STORE_data) + } + WIDE_LOOP_END + + SINGLE_LOOP + { + const v128 *w = wStart; + v128 m; LOAD_data(m, 0) + AES_D_IMC_m (w[2]) + do + { + AES_D_IMC_m (w[1]) + AES_D_IMC_m (w[0]) + w -= 2; + } + while (w != p); + AES_D_m (w[1]) + MM_XOR_m (w[0]) + MM_XOR_m (iv) + LOAD_data(iv, 0) + STORE_data(m, 0) + } + + p[-2] = iv; } -void MY_FAST_CALL AesCtr_Code_Intel(UInt32 *p, Byte *data, size_t numBlocks) + +AES_FUNC_START2 (AesCtr_Code_HW) { - AesCtr_Code(p, data, numBlocks); + v128 *p = (v128 *)(void *)ivAes; + v128 *data = (v128 *)(void *)data8; + uint64x2_t ctr = vreinterpretq_u64_u8(*p); + const v128 * const wEnd = p + (size_t)*(const UInt32 *)(p + 1) * 2; + const v128 *dataEnd; +// the bug in clang: +// __builtin_neon_vsetq_lane_i64(__s0, (int8x16_t)__s1, __p2); +#if defined(__clang__) && (__clang_major__ <= 9) +#pragma GCC diagnostic ignored "-Wvector-conversion" +#endif + const uint64x2_t one = vsetq_lane_u64(1, vdupq_n_u64(0), 0); + p += 2; + + WIDE_LOOP_START + { + const v128 *w = p; + WOP (DECLARE_VAR) + WOP (CTR_START) + do + { + WOP_KEY (AES_E_MC, 0) + WOP_KEY (AES_E_MC, 1) + w += 2; + } + while (w != wEnd); + WOP_KEY (AES_E_MC, 0) + WOP_KEY (AES_E, 1) + WOP_KEY (AES_XOR, 2) + WOP (CTR_END) + } + WIDE_LOOP_END + + SINGLE_LOOP + { + const v128 *w = p; + v128 m; + CTR_START (m, 0) + do + { + AES_E_MC_m (w[0]) + AES_E_MC_m (w[1]) + w += 2; + } + while (w != wEnd); + AES_E_MC_m (w[0]) + AES_E_m (w[1]) + MM_XOR_m (w[2]) + CTR_END (m, 0) + } + + p[-2] = vreinterpretq_u8_u64(ctr); } -#endif +#endif // USE_HW_AES + +#endif // MY_CPU_ARM_OR_ARM64 + +#undef NUM_WAYS +#undef WOP_M1 +#undef WOP +#undef DECLARE_VAR +#undef LOAD_data +#undef STORE_data +#undef USE_INTEL_AES +#undef USE_HW_AES diff --git a/src/plugins/7zip/7za/c/Alloc.c b/src/plugins/7zip/7za/c/Alloc.c index 3f6ac419..419fa375 100644 --- a/src/plugins/7zip/7za/c/Alloc.c +++ b/src/plugins/7zip/7za/c/Alloc.c @@ -1,33 +1,184 @@ /* Alloc.c -- Memory allocation functions -2015-02-21 : Igor Pavlov : Public domain */ +: Igor Pavlov : Public domain */ #include "Precomp.h" #ifdef _WIN32 -#include +#include "7zWindows.h" #endif #include #include "Alloc.h" -/* #define _SZ_ALLOC_DEBUG */ +#if defined(Z7_LARGE_PAGES) && defined(_WIN32) && \ + (!defined(Z7_WIN32_WINNT_MIN) || Z7_WIN32_WINNT_MIN < 0x0502) // < Win2003 (xp-64) + #define Z7_USE_DYN_GetLargePageMinimum +#endif + +// for debug: +#if 0 +#if defined(__CHERI__) && defined(__SIZEOF_POINTER__) && (__SIZEOF_POINTER__ == 16) +// #pragma message("=== Z7_ALLOC_NO_OFFSET_ALLOCATOR === ") +#define Z7_ALLOC_NO_OFFSET_ALLOCATOR +#endif +#endif -/* use _SZ_ALLOC_DEBUG to debug alloc/free operations */ -#ifdef _SZ_ALLOC_DEBUG +// #define SZ_ALLOC_DEBUG +/* use SZ_ALLOC_DEBUG to debug alloc/free operations */ +#ifdef SZ_ALLOC_DEBUG + +#include #include -int g_allocCount = 0; -int g_allocCountMid = 0; -int g_allocCountBig = 0; +static int g_allocCount = 0; +#ifdef _WIN32 +static int g_allocCountMid = 0; +#ifdef Z7_LARGE_PAGES +static int g_allocCountBig = 0; +#endif +#endif + +#define CONVERT_INT_TO_STR(charType, tempSize) \ + char temp[tempSize]; unsigned i = 0; \ + while (val >= 10) { temp[i++] = (char)('0' + (unsigned)(val % 10)); val /= 10; } \ + *s++ = (charType)('0' + (unsigned)val); \ + while (i != 0) { i--; *s++ = temp[i]; } \ + *s = 0; + +static void ConvertUInt64ToString(UInt64 val, char *s) +{ + CONVERT_INT_TO_STR(char, 24) +} + +#define GET_HEX_CHAR(t) ((char)(((t < 10) ? ('0' + t) : ('A' + (t - 10))))) + +static void ConvertUInt64ToHex(UInt64 val, char *s) +{ + UInt64 v = val; + unsigned i; + for (i = 1;; i++) + { + v >>= 4; + if (v == 0) + break; + } + s[i] = 0; + do + { + unsigned t = (unsigned)(val & 0xF); + val >>= 4; + s[--i] = GET_HEX_CHAR(t); + } + while (i); +} + +#define DEBUG_OUT_STREAM stderr + +static void Print(const char *s) +{ + fputs(s, DEBUG_OUT_STREAM); +} + +static void PrintAligned(const char *s, size_t align) +{ + size_t len = strlen(s); + for(;;) + { + fputc(' ', DEBUG_OUT_STREAM); + if (len >= align) + break; + ++len; + } + Print(s); +} + +static void PrintLn(void) +{ + Print("\n"); +} + +static void PrintHex(UInt64 v, size_t align) +{ + char s[32]; + ConvertUInt64ToHex(v, s); + PrintAligned(s, align); +} + +static void PrintDec(int v, size_t align) +{ + char s[32]; + ConvertUInt64ToString((unsigned)v, s); + PrintAligned(s, align); +} + +static void PrintAddr(void *p) +{ + PrintHex((UInt64)(size_t)(ptrdiff_t)p, 12); +} + + +#define PRINT_REALLOC(name, cnt, size, ptr) { \ + Print(name " "); \ + if (!ptr) PrintDec(cnt++, 10); \ + PrintHex(size, 10); \ + PrintAddr(ptr); \ + PrintLn(); } + +#define PRINT_ALLOC(name, cnt, size, ptr) { \ + Print(name " "); \ + PrintDec(cnt++, 10); \ + PrintHex(size, 10); \ + PrintAddr(ptr); \ + PrintLn(); } + +#define PRINT_FREE(name, cnt, ptr) if (ptr) { \ + Print(name " "); \ + PrintDec(--cnt, 10); \ + PrintAddr(ptr); \ + PrintLn(); } + +#else + +#ifdef _WIN32 +#ifdef Z7_LARGE_PAGES +#define PRINT_ALLOC(name, cnt, size, ptr) +#endif +#endif +#define PRINT_FREE(name, cnt, ptr) +#define Print(s) +#define PrintLn() +#ifndef Z7_ALLOC_NO_OFFSET_ALLOCATOR +#define PrintHex(v, align) +#endif +#define PrintAddr(p) + #endif + +/* +by specification: + malloc(non_NULL, 0) : returns NULL or a unique pointer value that can later be successfully passed to free() + realloc(NULL, size) : the call is equivalent to malloc(size) + realloc(non_NULL, 0) : the call is equivalent to free(ptr) + +in main compilers: + malloc(0) : returns non_NULL + realloc(NULL, 0) : returns non_NULL + realloc(non_NULL, 0) : returns NULL +*/ + + void *MyAlloc(size_t size) { if (size == 0) - return 0; - #ifdef _SZ_ALLOC_DEBUG + return NULL; + // PRINT_ALLOC("Alloc ", g_allocCount, size, NULL) + #ifdef SZ_ALLOC_DEBUG { void *p = malloc(size); - fprintf(stderr, "\nAlloc %10d bytes, count = %10d, addr = %8X", size, g_allocCount++, (unsigned)p); + if (p) + { + PRINT_ALLOC("Alloc ", g_allocCount, size, p) + } return p; } #else @@ -37,100 +188,587 @@ void *MyAlloc(size_t size) void MyFree(void *address) { - #ifdef _SZ_ALLOC_DEBUG - if (address != 0) - fprintf(stderr, "\nFree; count = %10d, addr = %8X", --g_allocCount, (unsigned)address); - #endif + PRINT_FREE("Free ", g_allocCount, address) + free(address); } +void *MyRealloc(void *address, size_t size) +{ + if (size == 0) + { + MyFree(address); + return NULL; + } + // PRINT_REALLOC("Realloc ", g_allocCount, size, address) + #ifdef SZ_ALLOC_DEBUG + { + void *p = realloc(address, size); + if (p) + { + PRINT_REALLOC("Realloc ", g_allocCount, size, address) + } + return p; + } + #else + return realloc(address, size); + #endif +} + + #ifdef _WIN32 void *MidAlloc(size_t size) { if (size == 0) - return 0; - #ifdef _SZ_ALLOC_DEBUG - fprintf(stderr, "\nAlloc_Mid %10d bytes; count = %10d", size, g_allocCountMid++); + return NULL; + #ifdef SZ_ALLOC_DEBUG + { + void *p = VirtualAlloc(NULL, size, MEM_COMMIT, PAGE_READWRITE); + if (p) + { + PRINT_ALLOC("Alloc-Mid", g_allocCountMid, size, p) + } + return p; + } + #else + return VirtualAlloc(NULL, size, MEM_COMMIT, PAGE_READWRITE); #endif - return VirtualAlloc(0, size, MEM_COMMIT, PAGE_READWRITE); } void MidFree(void *address) { - #ifdef _SZ_ALLOC_DEBUG - if (address != 0) - fprintf(stderr, "\nFree_Mid; count = %10d", --g_allocCountMid); - #endif - if (address == 0) + PRINT_FREE("Free-Mid", g_allocCountMid, address) + + if (!address) return; VirtualFree(address, 0, MEM_RELEASE); } -#ifndef MEM_LARGE_PAGES -#undef _7ZIP_LARGE_PAGES -#endif +#ifdef Z7_LARGE_PAGES +// #pragma message("Z7_LARGE_PAGES") -#ifdef _7ZIP_LARGE_PAGES -SIZE_T g_LargePageSize = 0; -typedef SIZE_T (WINAPI *GetLargePageMinimumP)(); +#ifdef MEM_LARGE_PAGES + #define MY_MEM_LARGE_PAGES MEM_LARGE_PAGES +#else + #define MY_MEM_LARGE_PAGES 0x20000000 #endif -void SetLargePageSize() +extern +size_t g_LargePageSize; +size_t g_LargePageSize = 0; +extern +size_t g_LargePageThresholdMin; +size_t g_LargePageThresholdMin = 0; +extern +UInt32 g_LargePageFlags; +UInt32 g_LargePageFlags = 0; + +void *BigAlloc(size_t size) { - #ifdef _7ZIP_LARGE_PAGES - SIZE_T size = 0; - GetLargePageMinimumP largePageMinimum = (GetLargePageMinimumP) - GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "GetLargePageMinimum"); - if (largePageMinimum == 0) - return; - size = largePageMinimum(); - if (size == 0 || (size & (size - 1)) != 0) - return; - g_LargePageSize = size; + if (size == 0) + return NULL; + + PRINT_ALLOC("Alloc-Big", g_allocCountBig, size, NULL) + + #ifdef Z7_LARGE_PAGES + { + const size_t ps = g_LargePageSize - 1; + if (ps < (1u << 30) && size > g_LargePageThresholdMin) + { + const size_t size2 = (size + ps) & ~ps; + if (size2 >= size) + { + void *p = VirtualAlloc(NULL, size2, MEM_COMMIT | MY_MEM_LARGE_PAGES, PAGE_READWRITE); + if (p) + { + PRINT_ALLOC("Alloc-BM ", g_allocCountMid, size2, p) + return p; + } + if (g_LargePageFlags & Z7_LARGE_PAGES_FLAG_FAIL_STOP) + return p; + } + } + } #endif + + return MidAlloc(size); } +void BigFree(void *address) +{ + PRINT_FREE("Free-Big", g_allocCountBig, address) + MidFree(address); +} + +#endif // Z7_LARGE_PAGES +#endif // _WIN32 + + +static void *SzAlloc(ISzAllocPtr p, size_t size) { UNUSED_VAR(p) return MyAlloc(size); } +static void SzFree(ISzAllocPtr p, void *address) { UNUSED_VAR(p) MyFree(address); } +const ISzAlloc g_Alloc = { SzAlloc, SzFree }; + +#ifdef _WIN32 +static void *SzMidAlloc(ISzAllocPtr p, size_t size) { UNUSED_VAR(p) return MidAlloc(size); } +static void SzMidFree(ISzAllocPtr p, void *address) { UNUSED_VAR(p) MidFree(address); } +const ISzAlloc g_MidAlloc = { SzMidAlloc, SzMidFree }; +#endif + +#if defined(Z7_LARGE_PAGES) +static void *SzBigAlloc(ISzAllocPtr p, size_t size) { UNUSED_VAR(p) return BigAlloc(size); } +static void SzBigFree(ISzAllocPtr p, void *address) { UNUSED_VAR(p) BigFree(address); } +const ISzAlloc g_BigAlloc = { SzBigAlloc, SzBigFree }; +#endif + +#ifndef Z7_ALLOC_NO_OFFSET_ALLOCATOR + +#define ADJUST_ALLOC_SIZE 0 +/* +#define ADJUST_ALLOC_SIZE (sizeof(void *) - 1) +*/ +/* + Use (ADJUST_ALLOC_SIZE = (sizeof(void *) - 1)), if + MyAlloc() can return address that is NOT multiple of sizeof(void *). +*/ + +/* + uintptr_t : C99 (optional) + : unsupported in VS6 +*/ +typedef + #ifdef _WIN32 + UINT_PTR + #elif 1 + uintptr_t + #else + ptrdiff_t + #endif + MY_uintptr_t; + +#if 0 \ + || (defined(__CHERI__) \ + || defined(__SIZEOF_POINTER__) && (__SIZEOF_POINTER__ > 8)) +// for 128-bit pointers (cheri): +#define MY_ALIGN_PTR_DOWN(p, align) \ + ((void *)((char *)(p) - ((size_t)(MY_uintptr_t)(p) & ((align) - 1)))) +#else +#define MY_ALIGN_PTR_DOWN(p, align) \ + ((void *)((((MY_uintptr_t)(p)) & ~((MY_uintptr_t)(align) - 1)))) +#endif + +#endif + +#ifndef _WIN32 +#include // for _POSIX_ADVISORY_INFO : for some linux +#if (defined(Z7_ALLOC_NO_OFFSET_ALLOCATOR) \ + || defined(_POSIX_C_SOURCE) && (_POSIX_C_SOURCE >= 200112L) \ + || defined(_POSIX_ADVISORY_INFO) && (_POSIX_ADVISORY_INFO >= 200112L) \ + || defined(__APPLE__) \ + /* || defined(__linux__) */) + #define USE_posix_memalign + // #pragma message("USE_posix_memalign") +#endif +#endif + +#ifndef USE_posix_memalign +#define MY_ALIGN_PTR_UP_PLUS(p, align) MY_ALIGN_PTR_DOWN(((char *)(p) + (align) + ADJUST_ALLOC_SIZE), align) +#endif + +/* + This posix_memalign() is for test purposes only. + We also need special Free() function instead of free(), + if this posix_memalign() is used. +*/ + +/* +static int posix_memalign(void **ptr, size_t align, size_t size) +{ + size_t newSize = size + align; + void *p; + void *pAligned; + *ptr = NULL; + if (newSize < size) + return 12; // ENOMEM + p = MyAlloc(newSize); + if (!p) + return 12; // ENOMEM + pAligned = MY_ALIGN_PTR_UP_PLUS(p, align); + ((void **)pAligned)[-1] = p; + *ptr = pAligned; + return 0; +} +*/ + +/* + ALLOC_ALIGN_SIZE >= sizeof(void *) + ALLOC_ALIGN_SIZE >= cache_line_size +*/ + +#define ALLOC_ALIGN_SIZE ((size_t)1 << 7) + +void *z7_AlignedAlloc(size_t size) +{ +#ifndef USE_posix_memalign + + void *p; + void *pAligned; + size_t newSize; + + /* also we can allocate additional dummy ALLOC_ALIGN_SIZE bytes after aligned + block to prevent cache line sharing with another allocated blocks */ + + newSize = size + ALLOC_ALIGN_SIZE * 1 + ADJUST_ALLOC_SIZE; + if (newSize < size) + return NULL; + + p = MyAlloc(newSize); + + if (!p) + return NULL; + pAligned = MY_ALIGN_PTR_UP_PLUS(p, ALLOC_ALIGN_SIZE); + + Print(" size="); PrintHex(size, 8); + Print(" a_size="); PrintHex(newSize, 8); + Print(" ptr="); PrintAddr(p); + Print(" a_ptr="); PrintAddr(pAligned); + PrintLn(); + + ((void **)pAligned)[-1] = p; + + return pAligned; + +#else + + void *p; + if (posix_memalign(&p, ALLOC_ALIGN_SIZE, size)) + return NULL; + + Print(" posix_memalign="); PrintAddr(p); + PrintLn(); + + return p; + +#endif +} + + +void z7_AlignedFree(void *address) +{ +#ifndef USE_posix_memalign + if (address) + MyFree(((void **)address)[-1]); +#else + free(address); +#endif +} + + +static void *SzAlignedAlloc(ISzAllocPtr pp, size_t size) +{ + UNUSED_VAR(pp) + return z7_AlignedAlloc(size); +} + + +static void SzAlignedFree(ISzAllocPtr pp, void *address) +{ + UNUSED_VAR(pp) +#ifndef USE_posix_memalign + if (address) + MyFree(((void **)address)[-1]); +#else + free(address); +#endif +} + +#ifndef _WIN32 + +#ifdef Z7_LARGE_PAGES + +#if 0 // 1 for debug + #include + #include // for strerror() + #define PRF(x) x +#else + #define PRF(x) +#endif + +#ifdef USE_posix_memalign + /* madvise(): + glibc <= 2.19 : _BSD_SOURCE + glibc > 2.19 : _DEFAULT_SOURCE + */ + /* && (defined(_DEFAULT_SOURCE) || defined(_BSD_SOURCE)) */ +#if 1 && !defined(Z7_NO_MADVISE) && \ + (defined(__linux__) || defined(__unix__) || defined(__APPLE__)) +#include // for madvise +// #pragma message("sys/mman.h") +#if (defined(MADV_HUGEPAGE) && defined(MADV_NOHUGEPAGE)) + #define Z7_USE_BIG_ALLOC_MADVISE + // #pragma message("Z7_USE_BIG_ALLOC_MADVISE") +#endif +#endif +#endif // USE_posix_memalign + +#ifdef Z7_USE_BIG_ALLOC_MADVISE +#define LARGE_PAGE_SIZE_DEFAULT (1 << 21) +#else +#define LARGE_PAGE_SIZE_DEFAULT 0 +#endif + +extern +size_t g_LargePageSize; +size_t g_LargePageSize = LARGE_PAGE_SIZE_DEFAULT; +extern +size_t g_LargePageThresholdMin; +size_t g_LargePageThresholdMin = LARGE_PAGE_SIZE_DEFAULT / 2; +extern +UInt32 g_LargePageFlags; +UInt32 g_LargePageFlags = 0; void *BigAlloc(size_t size) { if (size == 0) - return 0; - #ifdef _SZ_ALLOC_DEBUG - fprintf(stderr, "\nAlloc_Big %10d bytes; count = %10d", size, g_allocCountBig++); - #endif - - #ifdef _7ZIP_LARGE_PAGES - if (g_LargePageSize != 0 && g_LargePageSize <= (1 << 30) && size >= (1 << 18)) + return NULL; +#ifdef USE_posix_memalign { - void *res = VirtualAlloc(0, (size + g_LargePageSize - 1) & (~(g_LargePageSize - 1)), - MEM_COMMIT | MEM_LARGE_PAGES, PAGE_READWRITE); - if (res != 0) - return res; + const size_t pageSize = g_LargePageSize; + void *buf = NULL; // on Linux (and other systems), posix_memalign() does not modify memptr on failure (POSIX.1-2008 TC2). + PRF(printf("\nBigAlloc 0x%08x=%5uMB", (unsigned)(size), (unsigned)(size >> 20));) + if (pageSize && size > g_LargePageThresholdMin) + { + int res; + const size_t mask = pageSize - 1; + /* we can allocate aligned size, so data at the end of buffer also will use huge page + if (size2 for madvise() is not aligned for huge page size) + { Last data block will use small pages. It reduces memory allocation, + but last data block with small pages can work slower. + It's useful, if we have very large HUGE_PAGE: 32MB or 512MB. } + */ + size_t size2 = (size + mask) & ~mask; + if (size2 < size || (size & mask) <= g_LargePageThresholdMin) + size2 = size; + res = posix_memalign(&buf, pageSize, size2); + PRF(printf(" posix_memalign size=0x%08x=%5uMB align=%u", + (unsigned)(size2), (unsigned)(size2 >> 20), (unsigned)pageSize);) + PRF(printf(" buf=%p", (void *)buf);) + if (res == 0) + { +#ifdef Z7_USE_BIG_ALLOC_MADVISE + if ((g_LargePageFlags & Z7_LARGE_PAGES_FLAG_NO_MADVISE) == 0) + { + // Advise the kernel to use huge pages for this memory range + // MADV_HUGEPAGE / MADV_NOHUGEPAGE : since Linux 2.6.38 + // madvise() only operates on whole pages, therefore addr must be page-aligned (4KB/8KB/16KB/64KB). + // The value of size is rounded up to a multiple of page size. + PRF(printf(" madvise g_LargePageFlags=%x", (unsigned)g_LargePageFlags);) + res = madvise(buf, size2, (g_LargePageFlags & Z7_LARGE_PAGES_FLAG_NO_HUGEPAGE) ? MADV_NOHUGEPAGE : MADV_HUGEPAGE); + if (res) + { + PRF(printf("\nERROR res=%d, errno=%d=%s\n", res, (int)errno, strerror(errno));) + if (g_LargePageFlags & Z7_LARGE_PAGES_FLAG_FAIL_STOP) + { + free(buf); + return NULL; + } + } + } +#endif // Z7_USE_BIG_ALLOC_MADVISE + PRF(printf("\n");) + return buf; + } + PRF(printf("\nERROR res=%d=%s\n", res, strerror(res));) + if (g_LargePageFlags & Z7_LARGE_PAGES_FLAG_FAIL_STOP) + return NULL; + // (res == ENOMEM) "Out of memory" is possible, if pageSize is too big. + // so we do second attempt with smaller alignment + } } - #endif - return VirtualAlloc(0, size, MEM_COMMIT, PAGE_READWRITE); +#endif // !USE_posix_memalign + PRF(printf(" z7_AlignedAlloc size=0x%08x=%5uMB\n", (unsigned)(size), (unsigned)(size >> 20));) + return z7_AlignedAlloc(size); } + void BigFree(void *address) { - #ifdef _SZ_ALLOC_DEBUG - if (address != 0) - fprintf(stderr, "\nFree_Big; count = %10d", --g_allocCountBig); - #endif - - if (address == 0) - return; - VirtualFree(address, 0, MEM_RELEASE); + z7_AlignedFree(address); } +#endif // Z7_LARGE_PAGES +#endif // !_WIN32 + + +#ifdef Z7_LARGE_PAGES +void z7_LargePage_Set(UInt32 flags, size_t pageSize, size_t threshold) +{ + g_LargePageFlags = flags; +#ifdef _WIN32 + if ((flags & Z7_LARGE_PAGES_FLAG_USE_HUGEPAGE) == 0) + { + g_LargePageSize = 0; + g_LargePageThresholdMin = 0; + } + else + { + if ((flags & Z7_LARGE_PAGES_FLAG_DIRECT_PAGE_SIZE) == 0) + { +#ifdef Z7_USE_DYN_GetLargePageMinimum + Z7_DIAGNOSTIC_IGNORE_CAST_FUNCTION +typedef SIZE_T (WINAPI *Func_GetLargePageMinimum)(VOID); + const + Func_GetLargePageMinimum fn = + (Func_GetLargePageMinimum) Z7_CAST_FUNC_C GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), + "GetLargePageMinimum"); + if (fn) + pageSize = fn(); + else + pageSize = 0; +#else + pageSize = GetLargePageMinimum(); #endif + if (pageSize & (pageSize - 1)) + pageSize = 0; + } + g_LargePageSize = pageSize; + if ((flags & Z7_LARGE_PAGES_FLAG_DIRECT_THRESHOLD) == 0) + threshold = pageSize / 2; + g_LargePageThresholdMin = threshold; + } + +#else // !_WIN32 + + if (flags & Z7_LARGE_PAGES_FLAG_NO_PAGECODE) + { + g_LargePageSize = 0; + g_LargePageThresholdMin = 0; + } + else + { + if ((flags & Z7_LARGE_PAGES_FLAG_DIRECT_PAGE_SIZE) == 0) + pageSize = LARGE_PAGE_SIZE_DEFAULT; + g_LargePageSize = pageSize; + if ((flags & Z7_LARGE_PAGES_FLAG_DIRECT_THRESHOLD) == 0) + threshold = pageSize / 2; + g_LargePageThresholdMin = threshold; + } + // PRF(printf("\ng_LargePageSize=%x g_LargePageThresholdMin = %x g_LargePageFlags = %x", (unsigned)g_LargePageSize, (unsigned)g_LargePageThresholdMin, (unsigned)g_LargePageFlags);) +#endif // !_WIN32 +} +#endif // Z7_LARGE_PAGES + +const ISzAlloc g_AlignedAlloc = { SzAlignedAlloc, SzAlignedFree }; + -static void *SzAlloc(void *p, size_t size) { UNUSED_VAR(p); return MyAlloc(size); } -static void SzFree(void *p, void *address) { UNUSED_VAR(p); MyFree(address); } -ISzAlloc g_Alloc = { SzAlloc, SzFree }; +/* we align ptr to support cases where CAlignOffsetAlloc::offset is not multiply of sizeof(void *) */ +#ifndef Z7_ALLOC_NO_OFFSET_ALLOCATOR +#if 1 + #define MY_ALIGN_PTR_DOWN_1(p) MY_ALIGN_PTR_DOWN(p, sizeof(void *)) + #define REAL_BLOCK_PTR_VAR(p) ((void **)MY_ALIGN_PTR_DOWN_1(p))[-1] +#else + // we can use this simplified code, + // if (CAlignOffsetAlloc::offset == (k * sizeof(void *)) + #define REAL_BLOCK_PTR_VAR(p) (((void **)(p))[-1]) +#endif +#endif + -static void *SzBigAlloc(void *p, size_t size) { UNUSED_VAR(p); return BigAlloc(size); } -static void SzBigFree(void *p, void *address) { UNUSED_VAR(p); BigFree(address); } -ISzAlloc g_BigAlloc = { SzBigAlloc, SzBigFree }; +#if 0 +#ifndef Z7_ALLOC_NO_OFFSET_ALLOCATOR +#include +static void PrintPtr(const char *s, const void *p) +{ + const Byte *p2 = (const Byte *)&p; + unsigned i; + printf("%s %p ", s, p); + for (i = sizeof(p); i != 0;) + { + i--; + printf("%02x", p2[i]); + } + printf("\n"); +} +#endif +#endif + + +static void *AlignOffsetAlloc_Alloc(ISzAllocPtr pp, size_t size) +{ +#if defined(Z7_ALLOC_NO_OFFSET_ALLOCATOR) + UNUSED_VAR(pp) + return z7_AlignedAlloc(size); +#else + const CAlignOffsetAlloc *p = Z7_CONTAINER_FROM_VTBL_CONST(pp, CAlignOffsetAlloc, vt); + void *adr; + void *pAligned; + size_t newSize; + size_t extra; + size_t alignSize = (size_t)1 << p->numAlignBits; + + if (alignSize < sizeof(void *)) + alignSize = sizeof(void *); + + if (p->offset >= alignSize) + return NULL; + + /* also we can allocate additional dummy ALLOC_ALIGN_SIZE bytes after aligned + block to prevent cache line sharing with another allocated blocks */ + extra = p->offset & (sizeof(void *) - 1); + newSize = size + alignSize + extra + ADJUST_ALLOC_SIZE; + if (newSize < size) + return NULL; + + adr = ISzAlloc_Alloc(p->baseAlloc, newSize); + + if (!adr) + return NULL; + + pAligned = (char *)MY_ALIGN_PTR_DOWN((char *)adr + + alignSize - p->offset + extra + ADJUST_ALLOC_SIZE, alignSize) + p->offset; + +#if 0 + printf("\nalignSize = %6x, offset=%6x, size=%8x \n", (unsigned)alignSize, (unsigned)p->offset, (unsigned)size); + PrintPtr("base", adr); + PrintPtr("alig", pAligned); +#endif + + PrintLn(); + Print("- Aligned: "); + Print(" size="); PrintHex(size, 8); + Print(" a_size="); PrintHex(newSize, 8); + Print(" ptr="); PrintAddr(adr); + Print(" a_ptr="); PrintAddr(pAligned); + PrintLn(); + + REAL_BLOCK_PTR_VAR(pAligned) = adr; + + return pAligned; +#endif +} + + +static void AlignOffsetAlloc_Free(ISzAllocPtr pp, void *address) +{ +#if defined(Z7_ALLOC_NO_OFFSET_ALLOCATOR) + UNUSED_VAR(pp) + z7_AlignedFree(address); +#else + if (address) + { + const CAlignOffsetAlloc *p = Z7_CONTAINER_FROM_VTBL_CONST(pp, CAlignOffsetAlloc, vt); + PrintLn(); + Print("- Aligned Free: "); + PrintLn(); + ISzAlloc_Free(p->baseAlloc, REAL_BLOCK_PTR_VAR(address)); + } +#endif +} + + +void AlignOffsetAlloc_CreateVTable(CAlignOffsetAlloc *p) +{ + p->vt.Alloc = AlignOffsetAlloc_Alloc; + p->vt.Free = AlignOffsetAlloc_Free; +} diff --git a/src/plugins/7zip/7za/c/Alloc.h b/src/plugins/7zip/7za/c/Alloc.h index bac0d87e..eedd2952 100644 --- a/src/plugins/7zip/7za/c/Alloc.h +++ b/src/plugins/7zip/7za/c/Alloc.h @@ -1,36 +1,75 @@ /* Alloc.h -- Memory allocation functions -2015-02-21 : Igor Pavlov : Public domain */ +: Igor Pavlov : Public domain */ -#ifndef __COMMON_ALLOC_H -#define __COMMON_ALLOC_H +#ifndef ZIP7_INC_ALLOC_H +#define ZIP7_INC_ALLOC_H #include "7zTypes.h" EXTERN_C_BEGIN +/* + MyFree(NULL) : is allowed, as free(NULL) + MyAlloc(0) : returns NULL : but malloc(0) is allowed to return NULL or non_NULL + MyRealloc(NULL, 0) : returns NULL : but realloc(NULL, 0) is allowed to return NULL or non_NULL +MyRealloc() is similar to realloc() for the following cases: + MyRealloc(non_NULL, 0) : returns NULL and always calls MyFree(ptr) + MyRealloc(NULL, non_ZERO) : returns NULL, if allocation failed + MyRealloc(non_NULL, non_ZERO) : returns NULL, if reallocation failed +*/ + void *MyAlloc(size_t size); void MyFree(void *address); +void *MyRealloc(void *address, size_t size); + +void *z7_AlignedAlloc(size_t size); +void z7_AlignedFree(void *p); + +extern const ISzAlloc g_Alloc; +extern const ISzAlloc g_AlignedAlloc; #ifdef _WIN32 + void *MidAlloc(size_t size); + void MidFree(void *address); + extern const ISzAlloc g_MidAlloc; +#else + #define MidAlloc(size) z7_AlignedAlloc(size) + #define MidFree(address) z7_AlignedFree(address) + #define g_MidAlloc g_AlignedAlloc +#endif -void SetLargePageSize(); +#ifdef Z7_LARGE_PAGES -void *MidAlloc(size_t size); -void MidFree(void *address); -void *BigAlloc(size_t size); -void BigFree(void *address); +#define Z7_LARGE_PAGES_FLAG_USE_HUGEPAGE (1 << 0) // PAGE_ALIGNED / MADV_HUGEPAGE +#define Z7_LARGE_PAGES_FLAG_NO_PAGECODE (1 << 1) // no PAGE_ALIGNED / no madvise +#define Z7_LARGE_PAGES_FLAG_NO_MADVISE (1 << 2) // PAGE_ALIGNED / no madvise : for THP=always +#define Z7_LARGE_PAGES_FLAG_NO_HUGEPAGE (1 << 3) // PAGE_ALIGNED / MADV_NOHUGEPAGE +#define Z7_LARGE_PAGES_FLAG_FAIL_STOP (1 << 15) // for benchmarks +#define Z7_LARGE_PAGES_FLAG_DIRECT_PAGE_SIZE (1 << 16) +#define Z7_LARGE_PAGES_FLAG_DIRECT_THRESHOLD (1 << 17) +void z7_LargePage_Set(UInt32 flags, size_t pageSize, size_t threshold); + + void *BigAlloc(size_t size); + void BigFree(void *address); + extern const ISzAlloc g_BigAlloc; #else + #define BigAlloc(size) MidAlloc(size) + #define BigFree(address) MidFree(address) + #define g_BigAlloc g_MidAlloc +#endif -#define MidAlloc(size) MyAlloc(size) -#define MidFree(address) MyFree(address) -#define BigAlloc(size) MyAlloc(size) -#define BigFree(address) MyFree(address) -#endif +typedef struct +{ + ISzAlloc vt; + ISzAllocPtr baseAlloc; + unsigned numAlignBits; /* ((1 << numAlignBits) >= sizeof(void *)) */ + size_t offset; /* (offset == (k * sizeof(void *)) && offset < (1 << numAlignBits) */ +} CAlignOffsetAlloc; + +void AlignOffsetAlloc_CreateVTable(CAlignOffsetAlloc *p); -extern ISzAlloc g_Alloc; -extern ISzAlloc g_BigAlloc; EXTERN_C_END diff --git a/src/plugins/7zip/7za/c/Asm_c.mak b/src/plugins/7zip/7za/c/Asm_c.mak new file mode 100644 index 00000000..94318163 --- /dev/null +++ b/src/plugins/7zip/7za/c/Asm_c.mak @@ -0,0 +1,12 @@ +!IFDEF ASM_OBJS +!IF "$(PLATFORM)" == "arm64" +$(ASM_OBJS): ../../../Asm/arm64/$(*B).S + $(COMPL_ASM_CLANG) +!ELSEIF "$(PLATFORM)" == "arm" +$(ASM_OBJS): ../../../Asm/arm/$(*B).asm + $(COMPL_ASM) +!ELSEIF "$(PLATFORM)" != "ia64" && "$(PLATFORM)" != "mips" +$(ASM_OBJS): ../../../Asm/x86/$(*B).asm + $(COMPL_ASM) +!ENDIF +!ENDIF diff --git a/src/plugins/7zip/7za/c/Bcj2.c b/src/plugins/7zip/7za/c/Bcj2.c index 3c88e44f..7cb57ad6 100644 --- a/src/plugins/7zip/7za/c/Bcj2.c +++ b/src/plugins/7zip/7za/c/Bcj2.c @@ -1,29 +1,24 @@ /* Bcj2.c -- BCJ2 Decoder (Converter for x86 code) -2015-08-01 : Igor Pavlov : Public domain */ +2023-03-01 : Igor Pavlov : Public domain */ #include "Precomp.h" #include "Bcj2.h" #include "CpuArch.h" -#define CProb UInt16 - #define kTopValue ((UInt32)1 << 24) -#define kNumModelBits 11 -#define kBitModelTotal (1 << kNumModelBits) +#define kNumBitModelTotalBits 11 +#define kBitModelTotal (1 << kNumBitModelTotalBits) #define kNumMoveBits 5 -#define _IF_BIT_0 ttt = *prob; bound = (p->range >> kNumModelBits) * ttt; if (p->code < bound) -#define _UPDATE_0 p->range = bound; *prob = (CProb)(ttt + ((kBitModelTotal - ttt) >> kNumMoveBits)); -#define _UPDATE_1 p->range -= bound; p->code -= bound; *prob = (CProb)(ttt - (ttt >> kNumMoveBits)); +// UInt32 bcj2_stats[256 + 2][2]; void Bcj2Dec_Init(CBcj2Dec *p) { unsigned i; - - p->state = BCJ2_DEC_STATE_OK; + p->state = BCJ2_STREAM_RC; // BCJ2_DEC_STATE_OK; p->ip = 0; - p->temp[3] = 0; + p->temp = 0; p->range = 0; p->code = 0; for (i = 0; i < sizeof(p->probs) / sizeof(p->probs[0]); i++) @@ -32,216 +27,248 @@ void Bcj2Dec_Init(CBcj2Dec *p) SRes Bcj2Dec_Decode(CBcj2Dec *p) { + UInt32 v = p->temp; + // const Byte *src; if (p->range <= 5) { - p->state = BCJ2_DEC_STATE_OK; + UInt32 code = p->code; + p->state = BCJ2_DEC_STATE_ERROR; /* for case if we return SZ_ERROR_DATA; */ for (; p->range != 5; p->range++) { - if (p->range == 1 && p->code != 0) + if (p->range == 1 && code != 0) return SZ_ERROR_DATA; - if (p->bufs[BCJ2_STREAM_RC] == p->lims[BCJ2_STREAM_RC]) { p->state = BCJ2_STREAM_RC; return SZ_OK; } - - p->code = (p->code << 8) | *(p->bufs[BCJ2_STREAM_RC])++; + code = (code << 8) | *(p->bufs[BCJ2_STREAM_RC])++; + p->code = code; } - - if (p->code == 0xFFFFFFFF) + if (code == 0xffffffff) return SZ_ERROR_DATA; - - p->range = 0xFFFFFFFF; + p->range = 0xffffffff; } - else if (p->state >= BCJ2_DEC_STATE_ORIG_0) + // else { - while (p->state <= BCJ2_DEC_STATE_ORIG_3) + unsigned state = p->state; + // we check BCJ2_IS_32BIT_STREAM() here instead of check in the main loop + if (BCJ2_IS_32BIT_STREAM(state)) { - Byte *dest = p->dest; - if (dest == p->destLim) + const Byte *cur = p->bufs[state]; + if (cur == p->lims[state]) return SZ_OK; - *dest = p->temp[p->state++ - BCJ2_DEC_STATE_ORIG_0]; - p->dest = dest + 1; + p->bufs[state] = cur + 4; + { + const UInt32 ip = p->ip + 4; + v = GetBe32a(cur) - ip; + p->ip = ip; + } + state = BCJ2_DEC_STATE_ORIG_0; } - } - - /* - if (BCJ2_IS_32BIT_STREAM(p->state)) - { - const Byte *cur = p->bufs[p->state]; - if (cur == p->lims[p->state]) - return SZ_OK; - p->bufs[p->state] = cur + 4; - + if ((unsigned)(state - BCJ2_DEC_STATE_ORIG_0) < 4) { - UInt32 val; - Byte *dest; - SizeT rem; - - p->ip += 4; - val = GetBe32(cur) - p->ip; - dest = p->dest; - rem = p->destLim - dest; - if (rem < 4) + Byte *dest = p->dest; + for (;;) { - SizeT i; - SetUi32(p->temp, val); - for (i = 0; i < rem; i++) - dest[i] = p->temp[i]; - p->dest = dest + rem; - p->state = BCJ2_DEC_STATE_ORIG_0 + (unsigned)rem; - return SZ_OK; + if (dest == p->destLim) + { + p->state = state; + p->temp = v; + return SZ_OK; + } + *dest++ = (Byte)v; + p->dest = dest; + if (++state == BCJ2_DEC_STATE_ORIG_3 + 1) + break; + v >>= 8; } - SetUi32(dest, val); - p->temp[3] = (Byte)(val >> 24); - p->dest = dest + 4; - p->state = BCJ2_DEC_STATE_OK; } } - */ + // src = p->bufs[BCJ2_STREAM_MAIN]; for (;;) { + /* if (BCJ2_IS_32BIT_STREAM(p->state)) p->state = BCJ2_DEC_STATE_OK; else + */ { if (p->range < kTopValue) { if (p->bufs[BCJ2_STREAM_RC] == p->lims[BCJ2_STREAM_RC]) { p->state = BCJ2_STREAM_RC; + p->temp = v; return SZ_OK; } p->range <<= 8; p->code = (p->code << 8) | *(p->bufs[BCJ2_STREAM_RC])++; } - { const Byte *src = p->bufs[BCJ2_STREAM_MAIN]; const Byte *srcLim; - Byte *dest; - SizeT num = p->lims[BCJ2_STREAM_MAIN] - src; - - if (num == 0) + Byte *dest = p->dest; { - p->state = BCJ2_STREAM_MAIN; - return SZ_OK; + const SizeT rem = (SizeT)(p->lims[BCJ2_STREAM_MAIN] - src); + SizeT num = (SizeT)(p->destLim - dest); + if (num >= rem) + num = rem; + #define NUM_ITERS 4 + #if (NUM_ITERS & (NUM_ITERS - 1)) == 0 + num &= ~((SizeT)NUM_ITERS - 1); // if (NUM_ITERS == (1 << x)) + #else + num -= num % NUM_ITERS; // if (NUM_ITERS != (1 << x)) + #endif + srcLim = src + num; } - - dest = p->dest; - if (num > (SizeT)(p->destLim - dest)) + + #define NUM_SHIFT_BITS 24 + #define ONE_ITER(indx) { \ + const unsigned b = src[indx]; \ + *dest++ = (Byte)b; \ + v = (v << NUM_SHIFT_BITS) | b; \ + if (((b + (0x100 - 0xe8)) & 0xfe) == 0) break; \ + if (((v - (((UInt32)0x0f << (NUM_SHIFT_BITS)) + 0x80)) & \ + ((((UInt32)1 << (4 + NUM_SHIFT_BITS)) - 0x1) << 4)) == 0) break; \ + /* ++dest */; /* v = b; */ } + + if (src != srcLim) + for (;;) { - num = p->destLim - dest; - if (num == 0) - { - p->state = BCJ2_DEC_STATE_ORIG; - return SZ_OK; - } + /* The dependency chain of 2-cycle for (v) calculation is not big problem here. + But we can remove dependency chain with v = b in the end of loop. */ + ONE_ITER(0) + #if (NUM_ITERS > 1) + ONE_ITER(1) + #if (NUM_ITERS > 2) + ONE_ITER(2) + #if (NUM_ITERS > 3) + ONE_ITER(3) + #if (NUM_ITERS > 4) + ONE_ITER(4) + #if (NUM_ITERS > 5) + ONE_ITER(5) + #if (NUM_ITERS > 6) + ONE_ITER(6) + #if (NUM_ITERS > 7) + ONE_ITER(7) + #endif + #endif + #endif + #endif + #endif + #endif + #endif + + src += NUM_ITERS; + if (src == srcLim) + break; } - - srcLim = src + num; - if (p->temp[3] == 0x0F && (src[0] & 0xF0) == 0x80) - *dest = src[0]; - else for (;;) + if (src == srcLim) + #if (NUM_ITERS > 1) + for (;;) + #endif { - Byte b = *src; - *dest = b; - if (b != 0x0F) + #if (NUM_ITERS > 1) + if (src == p->lims[BCJ2_STREAM_MAIN] || dest == p->destLim) + #endif { - if ((b & 0xFE) == 0xE8) - break; - dest++; - if (++src != srcLim) - continue; - break; + const SizeT num = (SizeT)(src - p->bufs[BCJ2_STREAM_MAIN]); + p->bufs[BCJ2_STREAM_MAIN] = src; + p->dest = dest; + p->ip += (UInt32)num; + /* state BCJ2_STREAM_MAIN has more priority than BCJ2_STATE_ORIG */ + p->state = + src == p->lims[BCJ2_STREAM_MAIN] ? + (unsigned)BCJ2_STREAM_MAIN : + (unsigned)BCJ2_DEC_STATE_ORIG; + p->temp = v; + return SZ_OK; } - dest++; - if (++src == srcLim) - break; - if ((*src & 0xF0) != 0x80) - continue; - *dest = *src; - break; + #if (NUM_ITERS > 1) + ONE_ITER(0) + src++; + #endif } - - num = src - p->bufs[BCJ2_STREAM_MAIN]; - - if (src == srcLim) + { - p->temp[3] = src[-1]; - p->bufs[BCJ2_STREAM_MAIN] = src; + const SizeT num = (SizeT)(dest - p->dest); + p->dest = dest; // p->dest += num; + p->bufs[BCJ2_STREAM_MAIN] += num; // = src; p->ip += (UInt32)num; - p->dest += num; - p->state = - p->bufs[BCJ2_STREAM_MAIN] == - p->lims[BCJ2_STREAM_MAIN] ? - (unsigned)BCJ2_STREAM_MAIN : - (unsigned)BCJ2_DEC_STATE_ORIG; - return SZ_OK; } - { UInt32 bound, ttt; - CProb *prob; - Byte b = src[0]; - Byte prev = (Byte)(num == 0 ? p->temp[3] : src[-1]); - - p->temp[3] = b; - p->bufs[BCJ2_STREAM_MAIN] = src + 1; - num++; - p->ip += (UInt32)num; - p->dest += num; - - prob = p->probs + (unsigned)(b == 0xE8 ? 2 + (unsigned)prev : (b == 0xE9 ? 1 : 0)); - - _IF_BIT_0 + CBcj2Prob *prob; // unsigned index; + /* + prob = p->probs + (unsigned)((Byte)v == 0xe8 ? + 2 + (Byte)(v >> 8) : + ((v >> 5) & 1)); // ((Byte)v < 0xe8 ? 0 : 1)); + */ { - _UPDATE_0 + const unsigned c = ((v + 0x17) >> 6) & 1; + prob = p->probs + (unsigned) + (((0 - c) & (Byte)(v >> NUM_SHIFT_BITS)) + c + ((v >> 5) & 1)); + // (Byte) + // 8x->0 : e9->1 : xxe8->xx+2 + // 8x->0x100 : e9->0x101 : xxe8->xx + // (((0x100 - (e & ~v)) & (0x100 | (v >> 8))) + (e & v)); + // (((0x101 + (~e | v)) & (0x100 | (v >> 8))) + (e & v)); + } + ttt = *prob; + bound = (p->range >> kNumBitModelTotalBits) * ttt; + if (p->code < bound) + { + // bcj2_stats[prob - p->probs][0]++; + p->range = bound; + *prob = (CBcj2Prob)(ttt + ((kBitModelTotal - ttt) >> kNumMoveBits)); continue; } - _UPDATE_1 - + { + // bcj2_stats[prob - p->probs][1]++; + p->range -= bound; + p->code -= bound; + *prob = (CBcj2Prob)(ttt - (ttt >> kNumMoveBits)); + } } } } - { - UInt32 val; - unsigned cj = (p->temp[3] == 0xE8) ? BCJ2_STREAM_CALL : BCJ2_STREAM_JUMP; + /* (v == 0xe8 ? 0 : 1) uses setcc instruction with additional zero register usage in x64 MSVC. */ + // const unsigned cj = ((Byte)v == 0xe8) ? BCJ2_STREAM_CALL : BCJ2_STREAM_JUMP; + const unsigned cj = (((v + 0x57) >> 6) & 1) + BCJ2_STREAM_CALL; const Byte *cur = p->bufs[cj]; Byte *dest; SizeT rem; - if (cur == p->lims[cj]) { p->state = cj; break; } - - val = GetBe32(cur); + v = GetBe32a(cur); p->bufs[cj] = cur + 4; - - p->ip += 4; - val -= p->ip; + { + const UInt32 ip = p->ip + 4; + v -= ip; + p->ip = ip; + } dest = p->dest; - rem = p->destLim - dest; - + rem = (SizeT)(p->destLim - dest); if (rem < 4) { - SizeT i; - SetUi32(p->temp, val); - for (i = 0; i < rem; i++) - dest[i] = p->temp[i]; + if ((unsigned)rem > 0) { dest[0] = (Byte)v; v >>= 8; + if ((unsigned)rem > 1) { dest[1] = (Byte)v; v >>= 8; + if ((unsigned)rem > 2) { dest[2] = (Byte)v; v >>= 8; }}} + p->temp = v; p->dest = dest + rem; p->state = BCJ2_DEC_STATE_ORIG_0 + (unsigned)rem; break; } - - SetUi32(dest, val); - p->temp[3] = (Byte)(val >> 24); + SetUi32(dest, v) + v >>= 24; p->dest = dest + 4; } } @@ -251,6 +278,13 @@ SRes Bcj2Dec_Decode(CBcj2Dec *p) p->range <<= 8; p->code = (p->code << 8) | *(p->bufs[BCJ2_STREAM_RC])++; } - return SZ_OK; } + +#undef NUM_ITERS +#undef ONE_ITER +#undef NUM_SHIFT_BITS +#undef kTopValue +#undef kNumBitModelTotalBits +#undef kBitModelTotal +#undef kNumMoveBits diff --git a/src/plugins/7zip/7za/c/Bcj2.h b/src/plugins/7zip/7za/c/Bcj2.h index 8824080a..4575545b 100644 --- a/src/plugins/7zip/7za/c/Bcj2.h +++ b/src/plugins/7zip/7za/c/Bcj2.h @@ -1,8 +1,8 @@ -/* Bcj2.h -- BCJ2 Converter for x86 code -2014-11-10 : Igor Pavlov : Public domain */ +/* Bcj2.h -- BCJ2 converter for x86 code (Branch CALL/JUMP variant2) +2023-03-02 : Igor Pavlov : Public domain */ -#ifndef __BCJ2_H -#define __BCJ2_H +#ifndef ZIP7_INC_BCJ2_H +#define ZIP7_INC_BCJ2_H #include "7zTypes.h" @@ -26,37 +26,68 @@ enum BCJ2_DEC_STATE_ORIG_3, BCJ2_DEC_STATE_ORIG, - BCJ2_DEC_STATE_OK + BCJ2_DEC_STATE_ERROR /* after detected data error */ }; enum { BCJ2_ENC_STATE_ORIG = BCJ2_NUM_STREAMS, - BCJ2_ENC_STATE_OK + BCJ2_ENC_STATE_FINISHED /* it's state after fully encoded stream */ }; -#define BCJ2_IS_32BIT_STREAM(s) ((s) == BCJ2_STREAM_CALL || (s) == BCJ2_STREAM_JUMP) +/* #define BCJ2_IS_32BIT_STREAM(s) ((s) == BCJ2_STREAM_CALL || (s) == BCJ2_STREAM_JUMP) */ +#define BCJ2_IS_32BIT_STREAM(s) ((unsigned)((unsigned)(s) - (unsigned)BCJ2_STREAM_CALL) < 2) /* CBcj2Dec / CBcj2Enc bufs sizes: BUF_SIZE(n) = lims[n] - bufs[n] -bufs sizes for BCJ2_STREAM_CALL and BCJ2_STREAM_JUMP must be mutliply of 4: +bufs sizes for BCJ2_STREAM_CALL and BCJ2_STREAM_JUMP must be multiply of 4: (BUF_SIZE(BCJ2_STREAM_CALL) & 3) == 0 (BUF_SIZE(BCJ2_STREAM_JUMP) & 3) == 0 */ +// typedef UInt32 CBcj2Prob; +typedef UInt16 CBcj2Prob; + +/* +BCJ2 encoder / decoder internal requirements: + - If last bytes of stream contain marker (e8/e8/0f8x), then + there is also encoded symbol (0 : no conversion) in RC stream. + - One case of overlapped instructions is supported, + if last byte of converted instruction is (0f) and next byte is (8x): + marker [xx xx xx 0f] 8x + then the pair (0f 8x) is treated as marker. +*/ + +/* ---------- BCJ2 Decoder ---------- */ + /* CBcj2Dec: -dest is allowed to overlap with bufs[BCJ2_STREAM_MAIN], with the following conditions: +(dest) is allowed to overlap with bufs[BCJ2_STREAM_MAIN], with the following conditions: bufs[BCJ2_STREAM_MAIN] >= dest && - bufs[BCJ2_STREAM_MAIN] - dest >= tempReserv + + bufs[BCJ2_STREAM_MAIN] - dest >= BUF_SIZE(BCJ2_STREAM_CALL) + BUF_SIZE(BCJ2_STREAM_JUMP) - tempReserv = 0 : for first call of Bcj2Dec_Decode - tempReserv = 4 : for any other calls of Bcj2Dec_Decode - overlap with offset = 1 is not allowed + reserve = bufs[BCJ2_STREAM_MAIN] - dest - + ( BUF_SIZE(BCJ2_STREAM_CALL) + + BUF_SIZE(BCJ2_STREAM_JUMP) ) + and additional conditions: + if (it's first call of Bcj2Dec_Decode() after Bcj2Dec_Init()) + { + (reserve != 1) : if (ver < v23.00) + } + else // if there are more than one calls of Bcj2Dec_Decode() after Bcj2Dec_Init()) + { + (reserve >= 6) : if (ver < v23.00) + (reserve >= 4) : if (ver >= v23.00) + We need that (reserve) because after first call of Bcj2Dec_Decode(), + CBcj2Dec::temp can contain up to 4 bytes for writing to (dest). + } + (reserve == 0) is allowed, if we decode full stream via single call of Bcj2Dec_Decode(). + (reserve == 0) also is allowed in case of multi-call, if we use fixed buffers, + and (reserve) is calculated from full (final) sizes of all streams before first call. */ typedef struct @@ -68,21 +99,65 @@ typedef struct unsigned state; /* BCJ2_STREAM_MAIN has more priority than BCJ2_STATE_ORIG */ - UInt32 ip; - Byte temp[4]; + UInt32 ip; /* property of starting base for decoding */ + UInt32 temp; /* Byte temp[4]; */ UInt32 range; UInt32 code; - UInt16 probs[2 + 256]; + CBcj2Prob probs[2 + 256]; } CBcj2Dec; + +/* Note: + Bcj2Dec_Init() sets (CBcj2Dec::ip = 0) + if (ip != 0) property is required, the caller must set CBcj2Dec::ip after Bcj2Dec_Init() +*/ void Bcj2Dec_Init(CBcj2Dec *p); -/* Returns: SZ_OK or SZ_ERROR_DATA */ + +/* Bcj2Dec_Decode(): + returns: + SZ_OK + SZ_ERROR_DATA : if data in 5 starting bytes of BCJ2_STREAM_RC stream are not correct +*/ SRes Bcj2Dec_Decode(CBcj2Dec *p); -#define Bcj2Dec_IsFinished(_p_) ((_p_)->code == 0) +/* To check that decoding was finished you can compare + sizes of processed streams with sizes known from another sources. + You must do at least one mandatory check from the two following options: + - the check for size of processed output (ORIG) stream. + - the check for size of processed input (MAIN) stream. + additional optional checks: + - the checks for processed sizes of all input streams (MAIN, CALL, JUMP, RC) + - the checks Bcj2Dec_IsMaybeFinished*() + also before actual decoding you can check that the + following condition is met for stream sizes: + ( size(ORIG) == size(MAIN) + size(CALL) + size(JUMP) ) +*/ +/* (state == BCJ2_STREAM_MAIN) means that decoder is ready for + additional input data in BCJ2_STREAM_MAIN stream. + Note that (state == BCJ2_STREAM_MAIN) is allowed for non-finished decoding. +*/ +#define Bcj2Dec_IsMaybeFinished_state_MAIN(_p_) ((_p_)->state == BCJ2_STREAM_MAIN) +/* if the stream decoding was finished correctly, then range decoder + part of CBcj2Dec also was finished, and then (CBcj2Dec::code == 0). + Note that (CBcj2Dec::code == 0) is allowed for non-finished decoding. +*/ +#define Bcj2Dec_IsMaybeFinished_code(_p_) ((_p_)->code == 0) + +/* use Bcj2Dec_IsMaybeFinished() only as additional check + after at least one mandatory check from the two following options: + - the check for size of processed output (ORIG) stream. + - the check for size of processed input (MAIN) stream. +*/ +#define Bcj2Dec_IsMaybeFinished(_p_) ( \ + Bcj2Dec_IsMaybeFinished_state_MAIN(_p_) && \ + Bcj2Dec_IsMaybeFinished_code(_p_)) + + + +/* ---------- BCJ2 Encoder ---------- */ typedef enum { @@ -91,6 +166,91 @@ typedef enum BCJ2_ENC_FINISH_MODE_END_STREAM } EBcj2Enc_FinishMode; +/* + BCJ2_ENC_FINISH_MODE_CONTINUE: + process non finished encoding. + It notifies the encoder that additional further calls + can provide more input data (src) than provided by current call. + In that case the CBcj2Enc encoder still can move (src) pointer + up to (srcLim), but CBcj2Enc encoder can store some of the last + processed bytes (up to 4 bytes) from src to internal CBcj2Enc::temp[] buffer. + at return: + (CBcj2Enc::src will point to position that includes + processed data and data copied to (temp[]) buffer) + That data from (temp[]) buffer will be used in further calls. + + BCJ2_ENC_FINISH_MODE_END_BLOCK: + finish encoding of current block (ended at srcLim) without RC flushing. + at return: if (CBcj2Enc::state == BCJ2_ENC_STATE_ORIG) && + CBcj2Enc::src == CBcj2Enc::srcLim) + : it shows that block encoding was finished. And the encoder is + ready for new (src) data or for stream finish operation. + finished block means + { + CBcj2Enc has completed block encoding up to (srcLim). + (1 + 4 bytes) or (2 + 4 bytes) CALL/JUMP cortages will + not cross block boundary at (srcLim). + temporary CBcj2Enc buffer for (ORIG) src data is empty. + 3 output uncompressed streams (MAIN, CALL, JUMP) were flushed. + RC stream was not flushed. And RC stream will cross block boundary. + } + Note: some possible implementation of BCJ2 encoder could + write branch marker (e8/e8/0f8x) in one call of Bcj2Enc_Encode(), + and it could calculate symbol for RC in another call of Bcj2Enc_Encode(). + BCJ2 encoder uses ip/fileIp/fileSize/relatLimit values to calculate RC symbol. + And these CBcj2Enc variables can have different values in different Bcj2Enc_Encode() calls. + So caller must finish each block with BCJ2_ENC_FINISH_MODE_END_BLOCK + to ensure that RC symbol is calculated and written in proper block. + + BCJ2_ENC_FINISH_MODE_END_STREAM + finish encoding of stream (ended at srcLim) fully including RC flushing. + at return: if (CBcj2Enc::state == BCJ2_ENC_STATE_FINISHED) + : it shows that stream encoding was finished fully, + and all output streams were flushed fully. + also Bcj2Enc_IsFinished() can be called. +*/ + + +/* + 32-bit relative offset in JUMP/CALL commands is + - (mod 4 GiB) for 32-bit x86 code + - signed Int32 for 64-bit x86-64 code + BCJ2 encoder also does internal relative to absolute address conversions. + And there are 2 possible ways to do it: + before v23: we used 32-bit variables and (mod 4 GiB) conversion + since v23: we use 64-bit variables and (signed Int32 offset) conversion. + The absolute address condition for conversion in v23: + ((UInt64)((Int64)ip64 - (Int64)fileIp64 + 5 + (Int32)offset) < (UInt64)fileSize64) + note that if (fileSize64 > 2 GiB). there is difference between + old (mod 4 GiB) way (v22) and new (signed Int32 offset) way (v23). + And new (v23) way is more suitable to encode 64-bit x86-64 code for (fileSize64 > 2 GiB) cases. +*/ + +/* +// for old (v22) way for conversion: +typedef UInt32 CBcj2Enc_ip_unsigned; +typedef Int32 CBcj2Enc_ip_signed; +#define BCJ2_ENC_FileSize_MAX ((UInt32)1 << 31) +*/ +typedef UInt64 CBcj2Enc_ip_unsigned; +typedef Int64 CBcj2Enc_ip_signed; + +/* maximum size of file that can be used for conversion condition */ +#define BCJ2_ENC_FileSize_MAX ((CBcj2Enc_ip_unsigned)0 - 2) + +/* default value of fileSize64_minus1 variable that means + that absolute address limitation will not be used */ +#define BCJ2_ENC_FileSizeField_UNLIMITED ((CBcj2Enc_ip_unsigned)0 - 1) + +/* calculate value that later can be set to CBcj2Enc::fileSize64_minus1 */ +#define BCJ2_ENC_GET_FileSizeField_VAL_FROM_FileSize(fileSize) \ + ((CBcj2Enc_ip_unsigned)(fileSize) - 1) + +/* set CBcj2Enc::fileSize64_minus1 variable from size of file */ +#define Bcj2Enc_SET_FileSize(p, fileSize) \ + (p)->fileSize64_minus1 = BCJ2_ENC_GET_FileSizeField_VAL_FROM_FileSize(fileSize); + + typedef struct { Byte *bufs[BCJ2_NUM_STREAMS]; @@ -101,45 +261,71 @@ typedef struct unsigned state; EBcj2Enc_FinishMode finishMode; - Byte prevByte; + Byte context; + Byte flushRem; + Byte isFlushState; Byte cache; UInt32 range; UInt64 low; UInt64 cacheSize; + + // UInt32 context; // for marker version, it can include marker flag. - UInt32 ip; - - /* 32-bit ralative offset in JUMP/CALL commands is - - (mod 4 GB) in 32-bit mode - - signed Int32 in 64-bit mode - We use (mod 4 GB) check for fileSize. - Use fileSize up to 2 GB, if you want to support 32-bit and 64-bit code conversion. */ - UInt32 fileIp; - UInt32 fileSize; /* (fileSize <= ((UInt32)1 << 31)), 0 means no_limit */ - UInt32 relatLimit; /* (relatLimit <= ((UInt32)1 << 31)), 0 means desable_conversion */ + /* (ip64) and (fileIp64) correspond to virtual source stream position + that doesn't include data in temp[] */ + CBcj2Enc_ip_unsigned ip64; /* current (ip) position */ + CBcj2Enc_ip_unsigned fileIp64; /* start (ip) position of current file */ + CBcj2Enc_ip_unsigned fileSize64_minus1; /* size of current file (for conversion limitation) */ + UInt32 relatLimit; /* (relatLimit <= ((UInt32)1 << 31)) : 0 means disable_conversion */ + // UInt32 relatExcludeBits; UInt32 tempTarget; - unsigned tempPos; - Byte temp[4 * 2]; - - unsigned flushPos; - - UInt16 probs[2 + 256]; + unsigned tempPos; /* the number of bytes that were copied to temp[] buffer + (tempPos <= 4) outside of Bcj2Enc_Encode() */ + // Byte temp[4]; // for marker version + Byte temp[8]; + CBcj2Prob probs[2 + 256]; } CBcj2Enc; void Bcj2Enc_Init(CBcj2Enc *p); -void Bcj2Enc_Encode(CBcj2Enc *p); -#define Bcj2Enc_Get_InputData_Size(p) ((SizeT)((p)->srcLim - (p)->src) + (p)->tempPos) -#define Bcj2Enc_IsFinished(p) ((p)->flushPos == 5) +/* +Bcj2Enc_Encode(): at exit: + p->State < BCJ2_NUM_STREAMS : we need more buffer space for output stream + (bufs[p->State] == lims[p->State]) + p->State == BCJ2_ENC_STATE_ORIG : we need more data in input src stream + (src == srcLim) + p->State == BCJ2_ENC_STATE_FINISHED : after fully encoded stream +*/ +void Bcj2Enc_Encode(CBcj2Enc *p); -#define BCJ2_RELAT_LIMIT_NUM_BITS 26 -#define BCJ2_RELAT_LIMIT ((UInt32)1 << BCJ2_RELAT_LIMIT_NUM_BITS) +/* Bcj2Enc encoder can look ahead for up 4 bytes of source stream. + CBcj2Enc::tempPos : is the number of bytes that were copied from input stream to temp[] buffer. + (CBcj2Enc::src) after Bcj2Enc_Encode() is starting position after + fully processed data and after data copied to temp buffer. + So if the caller needs to get real number of fully processed input + bytes (without look ahead data in temp buffer), + the caller must subtruct (CBcj2Enc::tempPos) value from processed size + value that is calculated based on current (CBcj2Enc::src): + cur_processed_pos = Calc_Big_Processed_Pos(enc.src)) - + Bcj2Enc_Get_AvailInputSize_in_Temp(&enc); +*/ +/* get the size of input data that was stored in temp[] buffer: */ +#define Bcj2Enc_Get_AvailInputSize_in_Temp(p) ((p)->tempPos) -/* limit for CBcj2Enc::fileSize variable */ -#define BCJ2_FileSize_MAX ((UInt32)1 << 31) +#define Bcj2Enc_IsFinished(p) ((p)->flushRem == 0) + +/* Note : the decoder supports overlapping of marker (0f 80). + But we can eliminate such overlapping cases by setting + the limit for relative offset conversion as + CBcj2Enc::relatLimit <= (0x0f << 24) == (240 MiB) +*/ +/* default value for CBcj2Enc::relatLimit */ +#define BCJ2_ENC_RELAT_LIMIT_DEFAULT ((UInt32)0x0f << 24) +#define BCJ2_ENC_RELAT_LIMIT_MAX ((UInt32)1 << 31) +// #define BCJ2_RELAT_EXCLUDE_NUM_BITS 5 EXTERN_C_END diff --git a/src/plugins/7zip/7za/c/Bcj2Enc.c b/src/plugins/7zip/7za/c/Bcj2Enc.c index 0ea36462..79460bb1 100644 --- a/src/plugins/7zip/7za/c/Bcj2Enc.c +++ b/src/plugins/7zip/7za/c/Bcj2Enc.c @@ -1,61 +1,62 @@ -/* Bcj2Enc.c -- BCJ2 Encoder (Converter for x86 code) -2014-11-10 : Igor Pavlov : Public domain */ +/* Bcj2Enc.c -- BCJ2 Encoder converter for x86 code (Branch CALL/JUMP variant2) +2023-04-02 : Igor Pavlov : Public domain */ #include "Precomp.h" /* #define SHOW_STAT */ - #ifdef SHOW_STAT #include -#define PRF(x) x +#define PRF2(s) printf("%s ip=%8x tempPos=%d src= %8x\n", s, (unsigned)p->ip64, p->tempPos, (unsigned)(p->srcLim - p->src)); #else -#define PRF(x) +#define PRF2(s) #endif -#include -#include - #include "Bcj2.h" #include "CpuArch.h" -#define CProb UInt16 - #define kTopValue ((UInt32)1 << 24) -#define kNumModelBits 11 -#define kBitModelTotal (1 << kNumModelBits) +#define kNumBitModelTotalBits 11 +#define kBitModelTotal (1 << kNumBitModelTotalBits) #define kNumMoveBits 5 void Bcj2Enc_Init(CBcj2Enc *p) { unsigned i; - - p->state = BCJ2_ENC_STATE_OK; + p->state = BCJ2_ENC_STATE_ORIG; p->finishMode = BCJ2_ENC_FINISH_MODE_CONTINUE; - - p->prevByte = 0; - + p->context = 0; + p->flushRem = 5; + p->isFlushState = 0; p->cache = 0; - p->range = 0xFFFFFFFF; + p->range = 0xffffffff; p->low = 0; p->cacheSize = 1; - - p->ip = 0; - - p->fileIp = 0; - p->fileSize = 0; - p->relatLimit = BCJ2_RELAT_LIMIT; - + p->ip64 = 0; + p->fileIp64 = 0; + p->fileSize64_minus1 = BCJ2_ENC_FileSizeField_UNLIMITED; + p->relatLimit = BCJ2_ENC_RELAT_LIMIT_DEFAULT; + // p->relatExcludeBits = 0; p->tempPos = 0; - - p->flushPos = 0; - for (i = 0; i < sizeof(p->probs) / sizeof(p->probs[0]); i++) p->probs[i] = kBitModelTotal >> 1; } -static Bool MY_FAST_CALL RangeEnc_ShiftLow(CBcj2Enc *p) +// Z7_NO_INLINE +Z7_FORCE_INLINE +static BoolInt Bcj2_RangeEnc_ShiftLow(CBcj2Enc *p) { - if ((UInt32)p->low < (UInt32)0xFF000000 || (UInt32)(p->low >> 32) != 0) + const UInt32 low = (UInt32)p->low; + const unsigned high = (unsigned) + #if defined(Z7_MSC_VER_ORIGINAL) \ + && defined(MY_CPU_X86) \ + && defined(MY_CPU_LE) \ + && !defined(MY_CPU_64BIT) + // we try to rid of __aullshr() call in MSVS-x86 + (((const UInt32 *)&p->low)[1]); // [1] : for little-endian only + #else + (p->low >> 32); + #endif + if (low < (UInt32)0xff000000 || high != 0) { Byte *buf = p->bufs[BCJ2_STREAM_RC]; do @@ -66,247 +67,440 @@ static Bool MY_FAST_CALL RangeEnc_ShiftLow(CBcj2Enc *p) p->bufs[BCJ2_STREAM_RC] = buf; return True; } - *buf++ = (Byte)(p->cache + (Byte)(p->low >> 32)); - p->cache = 0xFF; + *buf++ = (Byte)(p->cache + high); + p->cache = 0xff; } while (--p->cacheSize); p->bufs[BCJ2_STREAM_RC] = buf; - p->cache = (Byte)((UInt32)p->low >> 24); + p->cache = (Byte)(low >> 24); } p->cacheSize++; - p->low = (UInt32)p->low << 8; + p->low = low << 8; return False; } -static void Bcj2Enc_Encode_2(CBcj2Enc *p) -{ - if (BCJ2_IS_32BIT_STREAM(p->state)) + +/* +We can use 2 alternative versions of code: +1) non-marker version: + Byte CBcj2Enc::context + Byte temp[8]; + Last byte of marker (e8/e9/[0f]8x) can be written to temp[] buffer. + Encoder writes last byte of marker (e8/e9/[0f]8x) to dest, only in conjunction + with writing branch symbol to range coder in same Bcj2Enc_Encode_2() call. + +2) marker version: + UInt32 CBcj2Enc::context + Byte CBcj2Enc::temp[4]; + MARKER_FLAG in CBcj2Enc::context shows that CBcj2Enc::context contains finded marker. + it's allowed that + one call of Bcj2Enc_Encode_2() writes last byte of marker (e8/e9/[0f]8x) to dest, + and another call of Bcj2Enc_Encode_2() does offset conversion. + So different values of (fileIp) and (fileSize) are possible + in these different Bcj2Enc_Encode_2() calls. + +Also marker version requires additional if((v & MARKER_FLAG) == 0) check in main loop. +So we use non-marker version. +*/ + +/* + Corner cases with overlap in multi-block. + before v23: there was one corner case, where converted instruction + could start in one sub-stream and finish in next sub-stream. + If multi-block (solid) encoding is used, + and BCJ2_ENC_FINISH_MODE_END_BLOCK is used for each sub-stream. + and (0f) is last byte of previous sub-stream + and (8x) is first byte of current sub-stream + then (0f 8x) pair is treated as marker by BCJ2 encoder and decoder. + BCJ2 encoder can converts 32-bit offset for that (0f 8x) cortage, + if that offset meets limit requirements. + If encoder allows 32-bit offset conversion for such overlap case, + then the data in 3 uncompressed BCJ2 streams for some sub-stream + can depend from data of previous sub-stream. + That corner case is not big problem, and it's rare case. + Since v23.00 we do additional check to prevent conversions in such overlap cases. +*/ + +/* + Bcj2Enc_Encode_2() output variables at exit: { - Byte *cur = p->bufs[p->state]; - if (cur == p->lims[p->state]) - return; - SetBe32(cur, p->tempTarget); - p->bufs[p->state] = cur + 4; + if (Bcj2Enc_Encode_2() exits with (p->state == BCJ2_ENC_STATE_ORIG)) + { + it means that encoder needs more input data. + if (p->srcLim == p->src) at exit, then + { + (p->finishMode != BCJ2_ENC_FINISH_MODE_END_STREAM) + all input data were read and processed, and we are ready for + new input data. + } + else + { + (p->srcLim != p->src) + (p->finishMode == BCJ2_ENC_FINISH_MODE_CONTINUE) + The encoder have found e8/e9/0f_8x marker, + and p->src points to last byte of that marker, + Bcj2Enc_Encode_2() needs more input data to get totally + 5 bytes (last byte of marker and 32-bit branch offset) + as continuous array starting from p->src. + (p->srcLim - p->src < 5) requirement is met after exit. + So non-processed resedue from p->src to p->srcLim is always less than 5 bytes. + } + } } +*/ - p->state = BCJ2_ENC_STATE_ORIG; - - for (;;) +Z7_NO_INLINE +static void Bcj2Enc_Encode_2(CBcj2Enc *p) +{ + if (!p->isFlushState) { - if (p->range < kTopValue) + const Byte *src; + UInt32 v; { - if (RangeEnc_ShiftLow(p)) - return; - p->range <<= 8; + const unsigned state = p->state; + if (BCJ2_IS_32BIT_STREAM(state)) + { + Byte *cur = p->bufs[state]; + if (cur == p->lims[state]) + return; + SetBe32a(cur, p->tempTarget) + p->bufs[state] = cur + 4; + } } + p->state = BCJ2_ENC_STATE_ORIG; // for main reason of exit + src = p->src; + v = p->context; + + // #define WRITE_CONTEXT p->context = v; // for marker version + #define WRITE_CONTEXT p->context = (Byte)v; + #define WRITE_CONTEXT_AND_SRC p->src = src; WRITE_CONTEXT + for (;;) { + // const Byte *src; + // UInt32 v; + CBcj2Enc_ip_unsigned ip; + if (p->range < kTopValue) + { + // to reduce register pressure and code size: we save and restore local variables. + WRITE_CONTEXT_AND_SRC + if (Bcj2_RangeEnc_ShiftLow(p)) + return; + p->range <<= 8; + src = p->src; + v = p->context; + } + // src = p->src; + // #define MARKER_FLAG ((UInt32)1 << 17) + // if ((v & MARKER_FLAG) == 0) // for marker version { - const Byte *src = p->src; const Byte *srcLim; - Byte *dest; - SizeT num = p->srcLim - src; - - if (p->finishMode == BCJ2_ENC_FINISH_MODE_CONTINUE) + Byte *dest = p->bufs[BCJ2_STREAM_MAIN]; { - if (num <= 4) - return; - num -= 4; + const SizeT remSrc = (SizeT)(p->srcLim - src); + SizeT rem = (SizeT)(p->lims[BCJ2_STREAM_MAIN] - dest); + if (rem >= remSrc) + rem = remSrc; + srcLim = src + rem; } - else if (num == 0) - break; - - dest = p->bufs[BCJ2_STREAM_MAIN]; - if (num > (SizeT)(p->lims[BCJ2_STREAM_MAIN] - dest)) + /* p->context contains context of previous byte: + bits [0 : 7] : src[-1], if (src) was changed in this call + bits [8 : 31] : are undefined for non-marker version + */ + // v = p->context; + #define NUM_SHIFT_BITS 24 + #define CONV_FLAG ((UInt32)1 << 16) + #define ONE_ITER { \ + b = src[0]; \ + *dest++ = (Byte)b; \ + v = (v << NUM_SHIFT_BITS) | b; \ + if (((b + (0x100 - 0xe8)) & 0xfe) == 0) break; \ + if (((v - (((UInt32)0x0f << (NUM_SHIFT_BITS)) + 0x80)) & \ + ((((UInt32)1 << (4 + NUM_SHIFT_BITS)) - 0x1) << 4)) == 0) break; \ + src++; if (src == srcLim) { break; } } + + if (src != srcLim) + for (;;) { - num = p->lims[BCJ2_STREAM_MAIN] - dest; - if (num == 0) - { - p->state = BCJ2_STREAM_MAIN; - return; - } + /* clang can generate ineffective code with setne instead of two jcc instructions. + we can use 2 iterations and external (unsigned b) to avoid that ineffective code genaration. */ + unsigned b; + ONE_ITER + ONE_ITER } - - srcLim = src + num; + + ip = p->ip64 + (CBcj2Enc_ip_unsigned)(SizeT)(dest - p->bufs[BCJ2_STREAM_MAIN]); + p->bufs[BCJ2_STREAM_MAIN] = dest; + p->ip64 = ip; - if (p->prevByte == 0x0F && (src[0] & 0xF0) == 0x80) - *dest = src[0]; - else for (;;) + if (src == srcLim) { - Byte b = *src; - *dest = b; - if (b != 0x0F) + WRITE_CONTEXT_AND_SRC + if (src != p->srcLim) { - if ((b & 0xFE) == 0xE8) - break; - dest++; - if (++src != srcLim) - continue; - break; + p->state = BCJ2_STREAM_MAIN; + return; } - dest++; - if (++src == srcLim) - break; - if ((*src & 0xF0) != 0x80) - continue; - *dest = *src; + /* (p->src == p->srcLim) + (p->state == BCJ2_ENC_STATE_ORIG) */ + if (p->finishMode != BCJ2_ENC_FINISH_MODE_END_STREAM) + return; + /* (p->finishMode == BCJ2_ENC_FINISH_MODE_END_STREAM */ + // (p->flushRem == 5); + p->isFlushState = 1; break; } - - num = src - p->src; - - if (src == srcLim) - { - p->prevByte = src[-1]; - p->bufs[BCJ2_STREAM_MAIN] = dest; - p->src = src; - p->ip += (UInt32)num; - continue; - } - + src++; + // p->src = src; + } + // ip = p->ip; // for marker version + /* marker was found */ + /* (v) contains marker that was found: + bits [NUM_SHIFT_BITS : NUM_SHIFT_BITS + 7] + : value of src[-2] : xx/xx/0f + bits [0 : 7] : value of src[-1] : e8/e9/8x + */ + { { - Byte context = (Byte)(num == 0 ? p->prevByte : src[-1]); - Bool needConvert; - - p->bufs[BCJ2_STREAM_MAIN] = dest + 1; - p->ip += (UInt32)num + 1; - src++; - - needConvert = False; - + #if NUM_SHIFT_BITS != 24 + v &= ~(UInt32)CONV_FLAG; + #endif + // UInt32 relat = 0; if ((SizeT)(p->srcLim - src) >= 4) { - UInt32 relatVal = GetUi32(src); - if ((p->fileSize == 0 || (UInt32)(p->ip + 4 + relatVal - p->fileIp) < p->fileSize) - && ((relatVal + p->relatLimit) >> 1) < p->relatLimit) - needConvert = True; + /* + if (relat != 0 || (Byte)v != 0xe8) + BoolInt isBigOffset = True; + */ + const UInt32 relat = GetUi32(src); + /* + #define EXCLUDE_FLAG ((UInt32)1 << 4) + #define NEED_CONVERT(rel) ((((rel) + EXCLUDE_FLAG) & (0 - EXCLUDE_FLAG * 2)) != 0) + if (p->relatExcludeBits != 0) + { + const UInt32 flag = (UInt32)1 << (p->relatExcludeBits - 1); + isBigOffset = (((relat + flag) & (0 - flag * 2)) != 0); + } + // isBigOffset = False; // for debug + */ + ip -= p->fileIp64; + // Use the following if check, if (ip) is 64-bit: + if (ip > (((v + 0x20) >> 5) & 1)) // 23.00 : we eliminate milti-block overlap for (Of 80) and (e8/e9) + if ((CBcj2Enc_ip_unsigned)((CBcj2Enc_ip_signed)ip + 4 + (Int32)relat) <= p->fileSize64_minus1) + if (((UInt32)(relat + p->relatLimit) >> 1) < p->relatLimit) + v |= CONV_FLAG; } - + else if (p->finishMode == BCJ2_ENC_FINISH_MODE_CONTINUE) { - UInt32 bound; - unsigned ttt; - Byte b = src[-1]; - CProb *prob = p->probs + (unsigned)(b == 0xE8 ? 2 + (unsigned)context : (b == 0xE9 ? 1 : 0)); - - ttt = *prob; - bound = (p->range >> kNumModelBits) * ttt; - - if (!needConvert) + // (p->srcLim - src < 4) + // /* + // for non-marker version + p->ip64--; // p->ip = ip - 1; + p->bufs[BCJ2_STREAM_MAIN]--; + src--; + v >>= NUM_SHIFT_BITS; + // (0 < p->srcLim - p->src <= 4) + // */ + // v |= MARKER_FLAG; // for marker version + /* (p->state == BCJ2_ENC_STATE_ORIG) */ + WRITE_CONTEXT_AND_SRC + return; + } + { + const unsigned c = ((v + 0x17) >> 6) & 1; + CBcj2Prob *prob = p->probs + (unsigned) + (((0 - c) & (Byte)(v >> NUM_SHIFT_BITS)) + c + ((v >> 5) & 1)); + /* + ((Byte)v == 0xe8 ? 2 + ((Byte)(v >> 8)) : + ((Byte)v < 0xe8 ? 0 : 1)); // ((v >> 5) & 1)); + */ + const unsigned ttt = *prob; + const UInt32 bound = (p->range >> kNumBitModelTotalBits) * ttt; + if ((v & CONV_FLAG) == 0) { + // static int yyy = 0; yyy++; printf("\n!needConvert = %d\n", yyy); + // v = (Byte)v; // for marker version p->range = bound; - *prob = (CProb)(ttt + ((kBitModelTotal - ttt) >> kNumMoveBits)); - p->src = src; - p->prevByte = b; + *prob = (CBcj2Prob)(ttt + ((kBitModelTotal - ttt) >> kNumMoveBits)); + // WRITE_CONTEXT_AND_SRC continue; } - p->low += bound; p->range -= bound; - *prob = (CProb)(ttt - (ttt >> kNumMoveBits)); - + *prob = (CBcj2Prob)(ttt - (ttt >> kNumMoveBits)); + } + // p->context = src[3]; + { + // const unsigned cj = ((Byte)v == 0xe8 ? BCJ2_STREAM_CALL : BCJ2_STREAM_JUMP); + const unsigned cj = (((v + 0x57) >> 6) & 1) + BCJ2_STREAM_CALL; + ip = p->ip64; + v = GetUi32(src); // relat + ip += 4; + p->ip64 = ip; + src += 4; + // p->src = src; { - UInt32 relatVal = GetUi32(src); - UInt32 absVal; - p->ip += 4; - absVal = p->ip + relatVal; - p->prevByte = src[3]; - src += 4; - p->src = src; + const UInt32 absol = (UInt32)ip + v; + Byte *cur = p->bufs[cj]; + v >>= 24; + // WRITE_CONTEXT + if (cur == p->lims[cj]) { - unsigned cj = (b == 0xE8) ? BCJ2_STREAM_CALL : BCJ2_STREAM_JUMP; - Byte *cur = p->bufs[cj]; - if (cur == p->lims[cj]) - { - p->state = cj; - p->tempTarget = absVal; - return; - } - SetBe32(cur, absVal); - p->bufs[cj] = cur + 4; + p->state = cj; + p->tempTarget = absol; + WRITE_CONTEXT_AND_SRC + return; } + SetBe32a(cur, absol) + p->bufs[cj] = cur + 4; } } } } - } + } // end of loop } - if (p->finishMode != BCJ2_ENC_FINISH_MODE_END_STREAM) - return; - - for (; p->flushPos < 5; p->flushPos++) - if (RangeEnc_ShiftLow(p)) + for (; p->flushRem != 0; p->flushRem--) + if (Bcj2_RangeEnc_ShiftLow(p)) return; - p->state = BCJ2_ENC_STATE_OK; + p->state = BCJ2_ENC_STATE_FINISHED; } +/* +BCJ2 encoder needs look ahead for up to 4 bytes in (src) buffer. +So base function Bcj2Enc_Encode_2() + in BCJ2_ENC_FINISH_MODE_CONTINUE mode can return with + (p->state == BCJ2_ENC_STATE_ORIG && p->src < p->srcLim) +Bcj2Enc_Encode() solves that look ahead problem by using p->temp[] buffer. + so if (p->state == BCJ2_ENC_STATE_ORIG) after Bcj2Enc_Encode(), + then (p->src == p->srcLim). + And the caller's code is simpler with Bcj2Enc_Encode(). +*/ + +Z7_NO_INLINE void Bcj2Enc_Encode(CBcj2Enc *p) { - PRF(printf("\n")); - PRF(printf("---- ip = %8d tempPos = %8d src = %8d\n", p->ip, p->tempPos, p->srcLim - p->src)); - + PRF2("\n----") if (p->tempPos != 0) { + /* extra: number of bytes that were copied from (src) to (temp) buffer in this call */ unsigned extra = 0; - + /* We will touch only minimal required number of bytes in input (src) stream. + So we will add input bytes from (src) stream to temp[] with step of 1 byte. + We don't add new bytes to temp[] before Bcj2Enc_Encode_2() call + in first loop iteration because + - previous call of Bcj2Enc_Encode() could use another (finishMode), + - previous call could finish with (p->state != BCJ2_ENC_STATE_ORIG). + the case with full temp[] buffer (p->tempPos == 4) is possible here. + */ for (;;) { + // (0 < p->tempPos <= 5) // in non-marker version + /* p->src : the current src data position including extra bytes + that were copied to temp[] buffer in this call */ const Byte *src = p->src; const Byte *srcLim = p->srcLim; - unsigned finishMode = p->finishMode; - - p->src = p->temp; - p->srcLim = p->temp + p->tempPos; + const EBcj2Enc_FinishMode finishMode = p->finishMode; if (src != srcLim) + { + /* if there are some src data after the data copied to temp[], + then we use MODE_CONTINUE for temp data */ p->finishMode = BCJ2_ENC_FINISH_MODE_CONTINUE; - - PRF(printf(" ip = %8d tempPos = %8d src = %8d\n", p->ip, p->tempPos, p->srcLim - p->src)); - + } + p->src = p->temp; + p->srcLim = p->temp + p->tempPos; + PRF2(" ") Bcj2Enc_Encode_2(p); - { - unsigned num = (unsigned)(p->src - p->temp); - unsigned tempPos = p->tempPos - num; + const unsigned num = (unsigned)(p->src - p->temp); + const unsigned tempPos = p->tempPos - num; unsigned i; p->tempPos = tempPos; for (i = 0; i < tempPos; i++) - p->temp[i] = p->temp[i + num]; - + p->temp[i] = p->temp[(SizeT)i + num]; + // tempPos : number of bytes in temp buffer p->src = src; p->srcLim = srcLim; p->finishMode = finishMode; - - if (p->state != BCJ2_ENC_STATE_ORIG || src == srcLim) + if (p->state != BCJ2_ENC_STATE_ORIG) + { + // (p->tempPos <= 4) // in non-marker version + /* if (the reason of exit from Bcj2Enc_Encode_2() + is not BCJ2_ENC_STATE_ORIG), + then we exit from Bcj2Enc_Encode() with same reason */ + // optional code begin : we rollback (src) and tempPos, if it's possible: + if (extra >= tempPos) + extra = tempPos; + p->src = src - extra; + p->tempPos = tempPos - extra; + // optional code end : rollback of (src) and tempPos return; - + } + /* (p->tempPos <= 4) + (p->state == BCJ2_ENC_STATE_ORIG) + so encoder needs more data than in temp[] */ + if (src == srcLim) + return; // src buffer has no more input data. + /* (src != srcLim) + so we can provide more input data from src for Bcj2Enc_Encode_2() */ if (extra >= tempPos) { - p->src = src - tempPos; + /* (extra >= tempPos) means that temp buffer contains + only data from src buffer of this call. + So now we can encode without temp buffer */ + p->src = src - tempPos; // rollback (src) p->tempPos = 0; break; } - - p->temp[tempPos] = src[0]; + // we append one additional extra byte from (src) to temp[] buffer: + p->temp[tempPos] = *src; p->tempPos = tempPos + 1; + // (0 < p->tempPos <= 5) // in non-marker version p->src = src + 1; extra++; } } } - PRF(printf("++++ ip = %8d tempPos = %8d src = %8d\n", p->ip, p->tempPos, p->srcLim - p->src)); - + PRF2("++++") + // (p->tempPos == 0) Bcj2Enc_Encode_2(p); + PRF2("====") if (p->state == BCJ2_ENC_STATE_ORIG) { const Byte *src = p->src; - unsigned rem = (unsigned)(p->srcLim - src); - unsigned i; - for (i = 0; i < rem; i++) - p->temp[i] = src[i]; - p->tempPos = rem; - p->src = src + rem; + const Byte *srcLim = p->srcLim; + const unsigned rem = (unsigned)(srcLim - src); + /* (rem <= 4) here. + if (p->src != p->srcLim), then + - we copy non-processed bytes from (p->src) to temp[] buffer, + - we set p->src equal to p->srcLim. + */ + if (rem) + { + unsigned i = 0; + p->src = srcLim; + p->tempPos = rem; + // (0 < p->tempPos <= 4) + do + p->temp[i] = src[i]; + while (++i != rem); + } + // (p->tempPos <= 4) + // (p->src == p->srcLim) } } + +#undef PRF2 +#undef CONV_FLAG +#undef MARKER_FLAG +#undef WRITE_CONTEXT +#undef WRITE_CONTEXT_AND_SRC +#undef ONE_ITER +#undef NUM_SHIFT_BITS +#undef kTopValue +#undef kNumBitModelTotalBits +#undef kBitModelTotal +#undef kNumMoveBits diff --git a/src/plugins/7zip/7za/c/Blake2.h b/src/plugins/7zip/7za/c/Blake2.h index 14f3cb64..801ea7ac 100644 --- a/src/plugins/7zip/7za/c/Blake2.h +++ b/src/plugins/7zip/7za/c/Blake2.h @@ -1,47 +1,104 @@ -/* Blake2.h -- BLAKE2 Hash -2015-06-30 : Igor Pavlov : Public domain -2015 : Samuel Neves : Public domain */ +/* Blake2.h -- BLAKE2sp Hash +2024-01-17 : Igor Pavlov : Public domain */ -#ifndef __BLAKE2_H -#define __BLAKE2_H +#ifndef ZIP7_INC_BLAKE2_H +#define ZIP7_INC_BLAKE2_H #include "7zTypes.h" -EXTERN_C_BEGIN +#if 0 +#include "Compiler.h" +#include "CpuArch.h" +#if defined(MY_CPU_X86_OR_AMD64) +#if defined(__SSE2__) \ + || defined(_MSC_VER) && _MSC_VER > 1200 \ + || defined(Z7_GCC_VERSION) && (Z7_GCC_VERSION >= 30300) \ + || defined(__clang__) \ + || defined(__INTEL_COMPILER) +#include // SSE2 +#endif -#define BLAKE2S_BLOCK_SIZE 64 -#define BLAKE2S_DIGEST_SIZE 32 -#define BLAKE2SP_PARALLEL_DEGREE 8 +#if defined(__AVX2__) \ + || defined(Z7_GCC_VERSION) && (Z7_GCC_VERSION >= 40900) \ + || defined(Z7_APPLE_CLANG_VERSION) && (Z7_APPLE_CLANG_VERSION >= 40600) \ + || defined(Z7_LLVM_CLANG_VERSION) && (Z7_LLVM_CLANG_VERSION >= 30100) \ + || defined(Z7_MSC_VER_ORIGINAL) && (Z7_MSC_VER_ORIGINAL >= 1800) \ + || defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 1400) +#include +#if defined(__clang__) +#include +#include +#endif +#endif // avx2 +#endif // MY_CPU_X86_OR_AMD64 +#endif // 0 -typedef struct -{ - UInt32 h[8]; - UInt32 t[2]; - UInt32 f[2]; - Byte buf[BLAKE2S_BLOCK_SIZE]; - UInt32 bufPos; - UInt32 lastNode_f1; - UInt32 dummy[2]; /* for sizeof(CBlake2s) alignment */ -} CBlake2s; - -/* You need to xor CBlake2s::h[i] with input parameter block after Blake2s_Init0() */ -/* -void Blake2s_Init0(CBlake2s *p); -void Blake2s_Update(CBlake2s *p, const Byte *data, size_t size); -void Blake2s_Final(CBlake2s *p, Byte *digest); -*/ +EXTERN_C_BEGIN +#define Z7_BLAKE2S_BLOCK_SIZE 64 +#define Z7_BLAKE2S_DIGEST_SIZE 32 +#define Z7_BLAKE2SP_PARALLEL_DEGREE 8 +#define Z7_BLAKE2SP_NUM_STRUCT_WORDS 16 +#if 1 || defined(Z7_BLAKE2SP_USE_FUNCTIONS) +typedef void (Z7_FASTCALL *Z7_BLAKE2SP_FUNC_COMPRESS)(UInt32 *states, const Byte *data, const Byte *end); +typedef void (Z7_FASTCALL *Z7_BLAKE2SP_FUNC_INIT)(UInt32 *states); +#endif + +// it's required that CBlake2sp is aligned for 32-bytes, +// because the code can use unaligned access with sse and avx256. +// but 64-bytes alignment can be better. +MY_ALIGN(64) typedef struct { - CBlake2s S[BLAKE2SP_PARALLEL_DEGREE]; - unsigned bufPos; -} CBlake2sp; + union + { +#if 0 +#if defined(MY_CPU_X86_OR_AMD64) +#if defined(__SSE2__) \ + || defined(_MSC_VER) && _MSC_VER > 1200 \ + || defined(Z7_GCC_VERSION) && (Z7_GCC_VERSION >= 30300) \ + || defined(__clang__) \ + || defined(__INTEL_COMPILER) + __m128i _pad_align_128bit[4]; +#endif // sse2 +#if defined(__AVX2__) \ + || defined(Z7_GCC_VERSION) && (Z7_GCC_VERSION >= 40900) \ + || defined(Z7_APPLE_CLANG_VERSION) && (Z7_APPLE_CLANG_VERSION >= 40600) \ + || defined(Z7_LLVM_CLANG_VERSION) && (Z7_LLVM_CLANG_VERSION >= 30100) \ + || defined(Z7_MSC_VER_ORIGINAL) && (Z7_MSC_VER_ORIGINAL >= 1800) \ + || defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 1400) + __m256i _pad_align_256bit[2]; +#endif // avx2 +#endif // x86 +#endif // 0 + void * _pad_align_ptr[8]; + UInt32 _pad_align_32bit[16]; + struct + { + unsigned cycPos; + unsigned _pad_unused; +#if 1 || defined(Z7_BLAKE2SP_USE_FUNCTIONS) + Z7_BLAKE2SP_FUNC_COMPRESS func_Compress_Fast; + Z7_BLAKE2SP_FUNC_COMPRESS func_Compress_Single; + Z7_BLAKE2SP_FUNC_INIT func_Init; + Z7_BLAKE2SP_FUNC_INIT func_Final; +#endif + } header; + } u; + // MY_ALIGN(64) + UInt32 states[Z7_BLAKE2SP_PARALLEL_DEGREE * Z7_BLAKE2SP_NUM_STRUCT_WORDS]; + // MY_ALIGN(64) + UInt32 buf32[Z7_BLAKE2SP_PARALLEL_DEGREE * Z7_BLAKE2SP_NUM_STRUCT_WORDS * 2]; +} CBlake2sp; +BoolInt Blake2sp_SetFunction(CBlake2sp *p, unsigned algo); void Blake2sp_Init(CBlake2sp *p); +void Blake2sp_InitState(CBlake2sp *p); void Blake2sp_Update(CBlake2sp *p, const Byte *data, size_t size); void Blake2sp_Final(CBlake2sp *p, Byte *digest); +void z7_Black2sp_Prepare(void); EXTERN_C_END diff --git a/src/plugins/7zip/7za/c/Blake2s.c b/src/plugins/7zip/7za/c/Blake2s.c index 6527415e..abb907d7 100644 --- a/src/plugins/7zip/7za/c/Blake2s.c +++ b/src/plugins/7zip/7za/c/Blake2s.c @@ -1,244 +1,2672 @@ -/* Blake2s.c -- BLAKE2s and BLAKE2sp Hash -2015-06-30 : Igor Pavlov : Public domain -2015 : Samuel Neves : Public domain */ +/* Blake2s.c -- BLAKE2sp Hash +2024-05-18 : Igor Pavlov : Public domain +2015-2019 : Samuel Neves : original code : CC0 1.0 Universal (CC0 1.0). */ +#include "Precomp.h" + +// #include #include #include "Blake2.h" -#include "CpuArch.h" #include "RotateDefs.h" +#include "Compiler.h" +#include "CpuArch.h" + +/* + if defined(__AVX512F__) && defined(__AVX512VL__) + { + we define Z7_BLAKE2S_USE_AVX512_ALWAYS, + but the compiler can use avx512 for any code. + } + else if defined(Z7_BLAKE2S_USE_AVX512_ALWAYS) + { we use avx512 only for sse* and avx* branches of code. } +*/ +// #define Z7_BLAKE2S_USE_AVX512_ALWAYS // for debug + +#if defined(__SSE2__) + #define Z7_BLAKE2S_USE_VECTORS +#elif defined(MY_CPU_X86_OR_AMD64) + #if defined(_MSC_VER) && _MSC_VER > 1200 \ + || defined(Z7_GCC_VERSION) && (Z7_GCC_VERSION >= 30300) \ + || defined(__clang__) \ + || defined(__INTEL_COMPILER) + #define Z7_BLAKE2S_USE_VECTORS + #endif +#endif + +#ifdef Z7_BLAKE2S_USE_VECTORS + +#define Z7_BLAKE2SP_USE_FUNCTIONS + +// define Z7_BLAKE2SP_STRUCT_IS_NOT_ALIGNED, if CBlake2sp can be non aligned for 32-bytes. +// #define Z7_BLAKE2SP_STRUCT_IS_NOT_ALIGNED + +// SSSE3 : for _mm_shuffle_epi8 (pshufb) that improves the performance for 5-15%. +#if defined(__SSSE3__) + #define Z7_BLAKE2S_USE_SSSE3 +#elif defined(Z7_MSC_VER_ORIGINAL) && (Z7_MSC_VER_ORIGINAL >= 1500) \ + || defined(Z7_GCC_VERSION) && (Z7_GCC_VERSION >= 40300) \ + || defined(Z7_APPLE_CLANG_VERSION) && (Z7_APPLE_CLANG_VERSION >= 40000) \ + || defined(Z7_LLVM_CLANG_VERSION) && (Z7_LLVM_CLANG_VERSION >= 20300) \ + || defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 1000) + #define Z7_BLAKE2S_USE_SSSE3 +#endif + +#ifdef Z7_BLAKE2S_USE_SSSE3 +/* SSE41 : for _mm_insert_epi32 (pinsrd) + it can slightly reduce code size and improves the performance in some cases. + it's used only for last 512-1024 bytes, if FAST versions (2 or 3) of vector algos are used. + it can be used for all blocks in another algos (4+). +*/ +#if defined(__SSE4_1__) + #define Z7_BLAKE2S_USE_SSE41 +#elif defined(Z7_MSC_VER_ORIGINAL) && (Z7_MSC_VER_ORIGINAL >= 1500) \ + || defined(Z7_GCC_VERSION) && (Z7_GCC_VERSION >= 40300) \ + || defined(Z7_APPLE_CLANG_VERSION) && (Z7_APPLE_CLANG_VERSION >= 40000) \ + || defined(Z7_LLVM_CLANG_VERSION) && (Z7_LLVM_CLANG_VERSION >= 20300) \ + || defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 1000) + #define Z7_BLAKE2S_USE_SSE41 +#endif +#endif // SSSE3 + +#if defined(__GNUC__) || defined(__clang__) +#if defined(Z7_BLAKE2S_USE_AVX512_ALWAYS) && !(defined(__AVX512F__) && defined(__AVX512VL__)) + #define BLAKE2S_ATTRIB_128BIT __attribute__((__target__("avx512vl,avx512f"))) +#else + #if defined(Z7_BLAKE2S_USE_SSE41) + #define BLAKE2S_ATTRIB_128BIT __attribute__((__target__("sse4.1"))) + #elif defined(Z7_BLAKE2S_USE_SSSE3) + #define BLAKE2S_ATTRIB_128BIT __attribute__((__target__("ssse3"))) + #else + #define BLAKE2S_ATTRIB_128BIT __attribute__((__target__("sse2"))) + #endif +#endif +#endif + + +#if defined(__AVX2__) + #define Z7_BLAKE2S_USE_AVX2 +#else + #if defined(Z7_GCC_VERSION) && (Z7_GCC_VERSION >= 40900) \ + || defined(Z7_APPLE_CLANG_VERSION) && (Z7_APPLE_CLANG_VERSION >= 40600) \ + || defined(Z7_LLVM_CLANG_VERSION) && (Z7_LLVM_CLANG_VERSION >= 30100) + #define Z7_BLAKE2S_USE_AVX2 + #ifdef Z7_BLAKE2S_USE_AVX2 +#if defined(Z7_BLAKE2S_USE_AVX512_ALWAYS) && !(defined(__AVX512F__) && defined(__AVX512VL__)) + #define BLAKE2S_ATTRIB_AVX2 __attribute__((__target__("avx512vl,avx512f"))) +#else + #define BLAKE2S_ATTRIB_AVX2 __attribute__((__target__("avx2"))) +#endif + #endif + #elif defined(Z7_MSC_VER_ORIGINAL) && (Z7_MSC_VER_ORIGINAL >= 1800) \ + || defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 1400) + #if (Z7_MSC_VER_ORIGINAL == 1900) + #pragma warning(disable : 4752) // found Intel(R) Advanced Vector Extensions; consider using /arch:AVX + #endif + #define Z7_BLAKE2S_USE_AVX2 + #endif +#endif + +#ifdef Z7_BLAKE2S_USE_SSE41 +#include // SSE4.1 +#elif defined(Z7_BLAKE2S_USE_SSSE3) +#include // SSSE3 +#else +#include // SSE2 +#endif + +#ifdef Z7_BLAKE2S_USE_AVX2 +#include +#if defined(__clang__) +#include +#include +#endif +#endif // avx2 + + +#if defined(__AVX512F__) && defined(__AVX512VL__) + // && defined(Z7_MSC_VER_ORIGINAL) && (Z7_MSC_VER_ORIGINAL > 1930) + #ifndef Z7_BLAKE2S_USE_AVX512_ALWAYS + #define Z7_BLAKE2S_USE_AVX512_ALWAYS + #endif + // #pragma message ("=== Blake2s AVX512") +#endif + + +#define Z7_BLAKE2S_USE_V128_FAST +// for speed optimization for small messages: +// #define Z7_BLAKE2S_USE_V128_WAY2 + +#ifdef Z7_BLAKE2S_USE_AVX2 + +// for debug: +// gather is slow +// #define Z7_BLAKE2S_USE_GATHER + + #define Z7_BLAKE2S_USE_AVX2_FAST +// for speed optimization for small messages: +// #define Z7_BLAKE2S_USE_AVX2_WAY2 +// #define Z7_BLAKE2S_USE_AVX2_WAY4 +#if defined(Z7_BLAKE2S_USE_AVX2_WAY2) || \ + defined(Z7_BLAKE2S_USE_AVX2_WAY4) + #define Z7_BLAKE2S_USE_AVX2_WAY_SLOW +#endif +#endif + + #define Z7_BLAKE2SP_ALGO_DEFAULT 0 + #define Z7_BLAKE2SP_ALGO_SCALAR 1 +#ifdef Z7_BLAKE2S_USE_V128_FAST + #define Z7_BLAKE2SP_ALGO_V128_FAST 2 +#endif +#ifdef Z7_BLAKE2S_USE_AVX2_FAST + #define Z7_BLAKE2SP_ALGO_V256_FAST 3 +#endif + #define Z7_BLAKE2SP_ALGO_V128_WAY1 4 +#ifdef Z7_BLAKE2S_USE_V128_WAY2 + #define Z7_BLAKE2SP_ALGO_V128_WAY2 5 +#endif +#ifdef Z7_BLAKE2S_USE_AVX2_WAY2 + #define Z7_BLAKE2SP_ALGO_V256_WAY2 6 +#endif +#ifdef Z7_BLAKE2S_USE_AVX2_WAY4 + #define Z7_BLAKE2SP_ALGO_V256_WAY4 7 +#endif + +#endif // Z7_BLAKE2S_USE_VECTORS + + + -#define rotr32 rotrFixed +#define BLAKE2S_FINAL_FLAG (~(UInt32)0) +#define NSW Z7_BLAKE2SP_NUM_STRUCT_WORDS +#define SUPER_BLOCK_SIZE (Z7_BLAKE2S_BLOCK_SIZE * Z7_BLAKE2SP_PARALLEL_DEGREE) +#define SUPER_BLOCK_MASK (SUPER_BLOCK_SIZE - 1) -#define BLAKE2S_NUM_ROUNDS 10 -#define BLAKE2S_FINAL_FLAG (~(UInt32)0) +#define V_INDEX_0_0 0 +#define V_INDEX_1_0 1 +#define V_INDEX_2_0 2 +#define V_INDEX_3_0 3 +#define V_INDEX_0_1 4 +#define V_INDEX_1_1 5 +#define V_INDEX_2_1 6 +#define V_INDEX_3_1 7 +#define V_INDEX_0_2 8 +#define V_INDEX_1_2 9 +#define V_INDEX_2_2 10 +#define V_INDEX_3_2 11 +#define V_INDEX_0_3 12 +#define V_INDEX_1_3 13 +#define V_INDEX_2_3 14 +#define V_INDEX_3_3 15 +#define V_INDEX_4_0 0 +#define V_INDEX_5_0 1 +#define V_INDEX_6_0 2 +#define V_INDEX_7_0 3 +#define V_INDEX_7_1 4 +#define V_INDEX_4_1 5 +#define V_INDEX_5_1 6 +#define V_INDEX_6_1 7 +#define V_INDEX_6_2 8 +#define V_INDEX_7_2 9 +#define V_INDEX_4_2 10 +#define V_INDEX_5_2 11 +#define V_INDEX_5_3 12 +#define V_INDEX_6_3 13 +#define V_INDEX_7_3 14 +#define V_INDEX_4_3 15 +#define V(row, col) v[V_INDEX_ ## row ## _ ## col] + +#define k_Blake2s_IV_0 0x6A09E667UL +#define k_Blake2s_IV_1 0xBB67AE85UL +#define k_Blake2s_IV_2 0x3C6EF372UL +#define k_Blake2s_IV_3 0xA54FF53AUL +#define k_Blake2s_IV_4 0x510E527FUL +#define k_Blake2s_IV_5 0x9B05688CUL +#define k_Blake2s_IV_6 0x1F83D9ABUL +#define k_Blake2s_IV_7 0x5BE0CD19UL + +#define KIV(n) (k_Blake2s_IV_## n) + +#ifdef Z7_BLAKE2S_USE_VECTORS +MY_ALIGN(16) static const UInt32 k_Blake2s_IV[8] = { - 0x6A09E667UL, 0xBB67AE85UL, 0x3C6EF372UL, 0xA54FF53AUL, - 0x510E527FUL, 0x9B05688CUL, 0x1F83D9ABUL, 0x5BE0CD19UL + KIV(0), KIV(1), KIV(2), KIV(3), KIV(4), KIV(5), KIV(6), KIV(7) }; +#endif -static const Byte k_Blake2s_Sigma[BLAKE2S_NUM_ROUNDS][16] = -{ - { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 } , - { 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 } , - { 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 } , - { 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 } , - { 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 } , - { 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 } , - { 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 } , - { 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 } , - { 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 } , - { 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13 , 0 } , -}; +#define STATE_T(s) ((s) + 8) +#define STATE_F(s) ((s) + 10) +#ifdef Z7_BLAKE2S_USE_VECTORS -void Blake2s_Init0(CBlake2s *p) +#define LOAD_128(p) _mm_load_si128 ((const __m128i *)(const void *)(p)) +#define LOADU_128(p) _mm_loadu_si128((const __m128i *)(const void *)(p)) +#ifdef Z7_BLAKE2SP_STRUCT_IS_NOT_ALIGNED + // here we use unaligned load and stores + // use this branch if CBlake2sp can be unaligned for 16 bytes + #define STOREU_128(p, r) _mm_storeu_si128((__m128i *)(void *)(p), r) + #define LOAD_128_FROM_STRUCT(p) LOADU_128(p) + #define STORE_128_TO_STRUCT(p, r) STOREU_128(p, r) +#else + // here we use aligned load and stores + // use this branch if CBlake2sp is aligned for 16 bytes + #define STORE_128(p, r) _mm_store_si128((__m128i *)(void *)(p), r) + #define LOAD_128_FROM_STRUCT(p) LOAD_128(p) + #define STORE_128_TO_STRUCT(p, r) STORE_128(p, r) +#endif + +#endif // Z7_BLAKE2S_USE_VECTORS + + +#if 0 +static void PrintState(const UInt32 *s, unsigned num) +{ + unsigned i; + printf("\n"); + for (i = 0; i < num; i++) + printf(" %08x", (unsigned)s[i]); +} +static void PrintStates2(const UInt32 *s, unsigned x, unsigned y) { unsigned i; - for (i = 0; i < 8; i++) - p->h[i] = k_Blake2s_IV[i]; - p->t[0] = 0; - p->t[1] = 0; - p->f[0] = 0; - p->f[1] = 0; - p->bufPos = 0; - p->lastNode_f1 = 0; + for (i = 0; i < y; i++) + PrintState(s + i * x, x); + printf("\n"); } +#endif + + +#define REP8_MACRO(m) { m(0) m(1) m(2) m(3) m(4) m(5) m(6) m(7) } + +#define BLAKE2S_NUM_ROUNDS 10 + +#if defined(Z7_BLAKE2S_USE_VECTORS) +#define ROUNDS_LOOP(mac) \ + { unsigned r; for (r = 0; r < BLAKE2S_NUM_ROUNDS; r++) mac(r) } +#endif +/* +#define ROUNDS_LOOP_2(mac) \ + { unsigned r; for (r = 0; r < BLAKE2S_NUM_ROUNDS; r += 2) { mac(r) mac(r + 1) } } +*/ +#if 0 || 1 && !defined(Z7_BLAKE2S_USE_VECTORS) +#define ROUNDS_LOOP_UNROLLED(m) \ + { m(0) m(1) m(2) m(3) m(4) m(5) m(6) m(7) m(8) m(9) } +#endif +#define SIGMA_TABLE(M) \ + M( 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 ), \ + M( 14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3 ), \ + M( 11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4 ), \ + M( 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8 ), \ + M( 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13 ), \ + M( 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9 ), \ + M( 12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11 ), \ + M( 13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10 ), \ + M( 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5 ), \ + M( 10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0 ) -static void Blake2s_Compress(CBlake2s *p) +#define SIGMA_TABLE_MULT(m, a0,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15) \ + { a0*m,a1*m,a2*m,a3*m,a4*m,a5*m,a6*m,a7*m,a8*m,a9*m,a10*m,a11*m,a12*m,a13*m,a14*m,a15*m } +#define SIGMA_TABLE_MULT_4( a0,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15) \ + SIGMA_TABLE_MULT(4, a0,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15) + +// MY_ALIGN(32) +MY_ALIGN(16) +static const Byte k_Blake2s_Sigma_4[BLAKE2S_NUM_ROUNDS][16] = + { SIGMA_TABLE(SIGMA_TABLE_MULT_4) }; + +#define GET_SIGMA_PTR(p, index) \ + ((const void *)((const Byte *)(const void *)(p) + (index))) + +#define GET_STATE_TABLE_PTR_FROM_BYTE_POS(s, pos) \ + ((UInt32 *)(void *)((Byte *)(void *)(s) + (pos))) + + +#ifdef Z7_BLAKE2S_USE_VECTORS + + +#if 0 + // use loading constants from memory + // is faster for some compilers. + #define KK4(n) KIV(n), KIV(n), KIV(n), KIV(n) +MY_ALIGN(64) +static const UInt32 k_Blake2s_IV_WAY4[]= { - UInt32 m[16]; - UInt32 v[16]; - + KK4(0), KK4(1), KK4(2), KK4(3), KK4(4), KK4(5), KK4(6), KK4(7) +}; + #define GET_128_IV_WAY4(i) LOAD_128(k_Blake2s_IV_WAY4 + 4 * (i)) +#else + // use constant generation: + #define GET_128_IV_WAY4(i) _mm_set1_epi32((Int32)KIV(i)) +#endif + + +#ifdef Z7_BLAKE2S_USE_AVX2_WAY_SLOW +#define GET_CONST_128_FROM_ARRAY32(k) \ + _mm_set_epi32((Int32)(k)[3], (Int32)(k)[2], (Int32)(k)[1], (Int32)(k)[0]) +#endif + + +#if 0 +#define k_r8 _mm_set_epi8(12, 15, 14, 13, 8, 11, 10, 9, 4, 7, 6, 5, 0, 3, 2, 1) +#define k_r16 _mm_set_epi8(13, 12, 15, 14, 9, 8, 11, 10, 5, 4, 7, 6, 1, 0, 3, 2) +#define k_inc _mm_set_epi32(0, 0, 0, Z7_BLAKE2S_BLOCK_SIZE) +#define k_iv0_128 GET_CONST_128_FROM_ARRAY32(k_Blake2s_IV + 0) +#define k_iv4_128 GET_CONST_128_FROM_ARRAY32(k_Blake2s_IV + 4) +#else +#if defined(Z7_BLAKE2S_USE_SSSE3) && \ + !defined(Z7_BLAKE2S_USE_AVX512_ALWAYS) +MY_ALIGN(16) static const Byte k_r8_arr [16] = { 1, 2, 3, 0, 5, 6, 7, 4, 9, 10, 11, 8 ,13, 14, 15, 12 }; +MY_ALIGN(16) static const Byte k_r16_arr[16] = { 2, 3, 0, 1, 6, 7, 4, 5, 10, 11, 8, 9, 14, 15, 12, 13 }; +#define k_r8 LOAD_128(k_r8_arr) +#define k_r16 LOAD_128(k_r16_arr) +#endif +MY_ALIGN(16) static const UInt32 k_inc_arr[4] = { Z7_BLAKE2S_BLOCK_SIZE, 0, 0, 0 }; +#define k_inc LOAD_128(k_inc_arr) +#define k_iv0_128 LOAD_128(k_Blake2s_IV + 0) +#define k_iv4_128 LOAD_128(k_Blake2s_IV + 4) +#endif + + +#ifdef Z7_BLAKE2S_USE_AVX2_WAY_SLOW + +#ifdef Z7_BLAKE2S_USE_AVX2 +#if defined(Z7_GCC_VERSION) && (Z7_GCC_VERSION < 80000) + #define MY_mm256_set_m128i(hi, lo) _mm256_insertf128_si256(_mm256_castsi128_si256(lo), (hi), 1) +#else + #define MY_mm256_set_m128i _mm256_set_m128i +#endif + +#define SET_FROM_128(a) MY_mm256_set_m128i(a, a) + +#ifndef Z7_BLAKE2S_USE_AVX512_ALWAYS +MY_ALIGN(32) static const Byte k_r8_arr_256 [32] = +{ + 1, 2, 3, 0, 5, 6, 7, 4, 9, 10, 11, 8 ,13, 14, 15, 12, + 1, 2, 3, 0, 5, 6, 7, 4, 9, 10, 11, 8 ,13, 14, 15, 12 +}; +MY_ALIGN(32) static const Byte k_r16_arr_256[32] = +{ + 2, 3, 0, 1, 6, 7, 4, 5, 10, 11, 8, 9, 14, 15, 12, 13, + 2, 3, 0, 1, 6, 7, 4, 5, 10, 11, 8, 9, 14, 15, 12, 13 +}; +#define k_r8_256 LOAD_256(k_r8_arr_256) +#define k_r16_256 LOAD_256(k_r16_arr_256) +#endif + +// #define k_r8_256 SET_FROM_128(_mm_set_epi8(12, 15, 14, 13, 8, 11, 10, 9, 4, 7, 6, 5, 0, 3, 2, 1)) +// #define k_r16_256 SET_FROM_128(_mm_set_epi8(13, 12, 15, 14, 9, 8, 11, 10, 5, 4, 7, 6, 1, 0, 3, 2)) +// #define k_inc_256 SET_FROM_128(_mm_set_epi32(0, 0, 0, Z7_BLAKE2S_BLOCK_SIZE)) +// #define k_iv0_256 SET_FROM_128(GET_CONST_128_FROM_ARRAY32(k_Blake2s_IV + 0)) +#define k_iv4_256 SET_FROM_128(GET_CONST_128_FROM_ARRAY32(k_Blake2s_IV + 4)) +#endif // Z7_BLAKE2S_USE_AVX2_WAY_SLOW +#endif + + +/* +IPC(TP) ports: +1 p__5 : skl- : SSE : shufps : _mm_shuffle_ps +2 p_15 : icl+ +1 p__5 : nhm-bdw : SSE : xorps : _mm_xor_ps +3 p015 : skl+ + +3 p015 : SSE2 : pxor : _mm_xor_si128 +2 p_15: snb-bdw : SSE2 : padd : _mm_add_epi32 +2 p0_5: mrm-wsm : +3 p015 : skl+ + +2 p_15 : ivb-,icl+ : SSE2 : punpcklqdq, punpckhqdq, punpckldq, punpckhdq +2 p_15 : : SSE2 : pshufd : _mm_shuffle_epi32 +2 p_15 : : SSE2 : pshuflw : _mm_shufflelo_epi16 +2 p_15 : : SSE2 : psrldq : +2 p_15 : : SSE3 : pshufb : _mm_shuffle_epi8 +2 p_15 : : SSE4 : pblendw : _mm_blend_epi16 +1 p__5 : hsw-skl : * + +1 p0 : SSE2 : pslld (i8) : _mm_slli_si128 +2 p01 : skl+ : + +2 p_15 : ivb- : SSE3 : palignr +1 p__5 : hsw+ + +2 p_15 + p23 : ivb-, icl+ : SSE4 : pinsrd : _mm_insert_epi32(xmm, m32, i8) +1 p__5 + p23 : hsw-skl +1 p_15 + p5 : ivb-, ice+ : SSE4 : pinsrd : _mm_insert_epi32(xmm, r32, i8) +0.5 2*p5 : hsw-skl + +2 p23 : SSE2 : movd (m32) +3 p23A : adl : +1 p5: : SSE2 : movd (r32) +*/ + +#if 0 && defined(__XOP__) +// we must debug and test __XOP__ instruction +#include +#include + #define LOAD_ROTATE_CONSTS + #define MM_ROR_EPI32(r, c) _mm_roti_epi32(r, -(c)) + #define Z7_BLAKE2S_MM_ROR_EPI32_IS_SUPPORTED +#elif 1 && defined(Z7_BLAKE2S_USE_AVX512_ALWAYS) + #define LOAD_ROTATE_CONSTS + #define MM_ROR_EPI32(r, c) _mm_ror_epi32(r, c) + #define Z7_BLAKE2S_MM_ROR_EPI32_IS_SUPPORTED +#else + +// MSVC_1937+ uses "orps" instruction for _mm_or_si128(). +// But "orps" has low throughput: TP=1 for bdw-nhm. +// So it can be better to use _mm_add_epi32()/"paddd" (TP=2 for bdw-nhm) instead of "xorps". +// But "orps" is fast for modern cpus (skl+). +// So we are default with "or" version: +#if 0 || 0 && defined(Z7_MSC_VER_ORIGINAL) && Z7_MSC_VER_ORIGINAL > 1937 + // minor optimization for some old cpus, if "xorps" is slow. + #define MM128_EPI32_OR_or_ADD _mm_add_epi32 +#else + #define MM128_EPI32_OR_or_ADD _mm_or_si128 +#endif + + #define MM_ROR_EPI32_VIA_SHIFT(r, c)( \ + MM128_EPI32_OR_or_ADD( \ + _mm_srli_epi32((r), (c)), \ + _mm_slli_epi32((r), 32-(c)))) + #if defined(Z7_BLAKE2S_USE_SSSE3) || defined(Z7_BLAKE2S_USE_SSE41) + #define LOAD_ROTATE_CONSTS \ + const __m128i r8 = k_r8; \ + const __m128i r16 = k_r16; + #define MM_ROR_EPI32(r, c) ( \ + ( 8==(c)) ? _mm_shuffle_epi8(r,r8) \ + : (16==(c)) ? _mm_shuffle_epi8(r,r16) \ + : MM_ROR_EPI32_VIA_SHIFT(r, c)) + #else + #define LOAD_ROTATE_CONSTS + #define MM_ROR_EPI32(r, c) ( \ + (16==(c)) ? _mm_shufflehi_epi16(_mm_shufflelo_epi16(r, 0xb1), 0xb1) \ + : MM_ROR_EPI32_VIA_SHIFT(r, c)) + #endif +#endif + +/* +we have 3 main ways to load 4 32-bit integers to __m128i: + 1) SSE2: _mm_set_epi32() + 2) SSE2: _mm_unpacklo_epi64() / _mm_unpacklo_epi32 / _mm_cvtsi32_si128() + 3) SSE41: _mm_insert_epi32() and _mm_cvtsi32_si128() +good compiler for _mm_set_epi32() generates these instructions: +{ + movd xmm, [m32]; vpunpckldq; vpunpckldq; vpunpcklqdq; +} +good new compiler generates one instruction +{ + for _mm_insert_epi32() : { pinsrd xmm, [m32], i } + for _mm_cvtsi32_si128() : { movd xmm, [m32] } +} +but vc2010 generates slow pair of instructions: +{ + for _mm_insert_epi32() : { mov r32, [m32]; pinsrd xmm, r32, i } + for _mm_cvtsi32_si128() : { mov r32, [m32]; movd xmm, r32 } +} +_mm_insert_epi32() (pinsrd) code reduces xmm register pressure +in comparison with _mm_set_epi32() (movd + vpunpckld) code. +Note that variant with "movd xmm, r32" can be more slow, +but register pressure can be more important. +So we can force to "pinsrd" always. +*/ +// #if !defined(Z7_MSC_VER_ORIGINAL) || Z7_MSC_VER_ORIGINAL > 1600 || defined(MY_CPU_X86) +#ifdef Z7_BLAKE2S_USE_SSE41 + /* _mm_set_epi32() can be more effective for GCC and CLANG + _mm_insert_epi32() is more effective for MSVC */ + #if 0 || 1 && defined(Z7_MSC_VER_ORIGINAL) + #define Z7_BLAKE2S_USE_INSERT_INSTRUCTION + #endif +#endif // USE_SSE41 +// #endif + +#ifdef Z7_BLAKE2S_USE_INSERT_INSTRUCTION + // for SSE4.1 +#define MM_LOAD_EPI32_FROM_4_POINTERS(p0, p1, p2, p3) \ + _mm_insert_epi32( \ + _mm_insert_epi32( \ + _mm_insert_epi32( \ + _mm_cvtsi32_si128( \ + *(const Int32 *)p0), \ + *(const Int32 *)p1, 1), \ + *(const Int32 *)p2, 2), \ + *(const Int32 *)p3, 3) +#elif 0 || 1 && defined(Z7_MSC_VER_ORIGINAL) +/* MSVC 1400 implements _mm_set_epi32() via slow memory write/read. + Also _mm_unpacklo_epi32 is more effective for another MSVC compilers. + But _mm_set_epi32() is more effective for GCC and CLANG. + So we use _mm_unpacklo_epi32 for MSVC only */ +#define MM_LOAD_EPI32_FROM_4_POINTERS(p0, p1, p2, p3) \ + _mm_unpacklo_epi64( \ + _mm_unpacklo_epi32( _mm_cvtsi32_si128(*(const Int32 *)p0), \ + _mm_cvtsi32_si128(*(const Int32 *)p1)), \ + _mm_unpacklo_epi32( _mm_cvtsi32_si128(*(const Int32 *)p2), \ + _mm_cvtsi32_si128(*(const Int32 *)p3))) +#else +#define MM_LOAD_EPI32_FROM_4_POINTERS(p0, p1, p2, p3) \ + _mm_set_epi32( \ + *(const Int32 *)p3, \ + *(const Int32 *)p2, \ + *(const Int32 *)p1, \ + *(const Int32 *)p0) +#endif + +#define SET_ROW_FROM_SIGMA_BASE(input, i0, i1, i2, i3) \ + MM_LOAD_EPI32_FROM_4_POINTERS( \ + GET_SIGMA_PTR(input, i0), \ + GET_SIGMA_PTR(input, i1), \ + GET_SIGMA_PTR(input, i2), \ + GET_SIGMA_PTR(input, i3)) + +#define SET_ROW_FROM_SIGMA(input, sigma_index) \ + SET_ROW_FROM_SIGMA_BASE(input, \ + sigma[(sigma_index) ], \ + sigma[(sigma_index) + 2 * 1], \ + sigma[(sigma_index) + 2 * 2], \ + sigma[(sigma_index) + 2 * 3]) \ + + +#define ADD_128(a, b) _mm_add_epi32(a, b) +#define XOR_128(a, b) _mm_xor_si128(a, b) + +#define D_ADD_128(dest, src) dest = ADD_128(dest, src) +#define D_XOR_128(dest, src) dest = XOR_128(dest, src) +#define D_ROR_128(dest, shift) dest = MM_ROR_EPI32(dest, shift) +#define D_ADD_EPI64_128(dest, src) dest = _mm_add_epi64(dest, src) + + +#define AXR(a, b, d, shift) \ + D_ADD_128(a, b); \ + D_XOR_128(d, a); \ + D_ROR_128(d, shift); + +#define AXR2(a, b, c, d, input, sigma_index, shift1, shift2) \ + a = _mm_add_epi32 (a, SET_ROW_FROM_SIGMA(input, sigma_index)); \ + AXR(a, b, d, shift1) \ + AXR(c, d, b, shift2) + +#define ROTATE_WORDS_TO_RIGHT(a, n) \ + a = _mm_shuffle_epi32(a, _MM_SHUFFLE((3+n)&3, (2+n)&3, (1+n)&3, (0+n)&3)); + +#define AXR4(a, b, c, d, input, sigma_index) \ + AXR2(a, b, c, d, input, sigma_index, 16, 12) \ + AXR2(a, b, c, d, input, sigma_index + 1, 8, 7) \ + +#define RR2(a, b, c, d, input) \ + { \ + AXR4(a, b, c, d, input, 0) \ + ROTATE_WORDS_TO_RIGHT(b, 1) \ + ROTATE_WORDS_TO_RIGHT(c, 2) \ + ROTATE_WORDS_TO_RIGHT(d, 3) \ + AXR4(a, b, c, d, input, 8) \ + ROTATE_WORDS_TO_RIGHT(b, 3) \ + ROTATE_WORDS_TO_RIGHT(c, 2) \ + ROTATE_WORDS_TO_RIGHT(d, 1) \ + } + + +/* +Way1: +per 64 bytes block: +10 rounds * 4 iters * (7 + 2) = 360 cycles = if pslld TP=1 + * (7 + 1) = 320 cycles = if pslld TP=2 (skl+) +additional operations per 7_op_iter : +4 movzx byte mem +1 movd mem +3 pinsrd mem +1.5 pshufd +*/ + +static +#if 0 || 0 && (defined(Z7_BLAKE2S_USE_V128_WAY2) || \ + defined(Z7_BLAKE2S_USE_V256_WAY2)) + Z7_NO_INLINE +#else + Z7_FORCE_INLINE +#endif +#ifdef BLAKE2S_ATTRIB_128BIT + BLAKE2S_ATTRIB_128BIT +#endif +void +Z7_FASTCALL +Blake2s_Compress_V128_Way1(UInt32 * const s, const Byte * const input) +{ + __m128i a, b, c, d; + __m128i f0, f1; + + LOAD_ROTATE_CONSTS + d = LOAD_128_FROM_STRUCT(STATE_T(s)); + c = k_iv0_128; + a = f0 = LOAD_128_FROM_STRUCT(s); + b = f1 = LOAD_128_FROM_STRUCT(s + 4); + D_ADD_EPI64_128(d, k_inc); + STORE_128_TO_STRUCT (STATE_T(s), d); + D_XOR_128(d, k_iv4_128); + +#define RR(r) { const Byte * const sigma = k_Blake2s_Sigma_4[r]; \ + RR2(a, b, c, d, input) } + + ROUNDS_LOOP(RR) +#undef RR + + STORE_128_TO_STRUCT(s , XOR_128(f0, XOR_128(a, c))); + STORE_128_TO_STRUCT(s + 4, XOR_128(f1, XOR_128(b, d))); +} + + +static +Z7_NO_INLINE +#ifdef BLAKE2S_ATTRIB_128BIT + BLAKE2S_ATTRIB_128BIT +#endif +void +Z7_FASTCALL +Blake2sp_Compress2_V128_Way1(UInt32 *s_items, const Byte *data, const Byte *end) +{ + size_t pos = 0; + do { - unsigned i; + UInt32 * const s = GET_STATE_TABLE_PTR_FROM_BYTE_POS(s_items, pos); + Blake2s_Compress_V128_Way1(s, data); + data += Z7_BLAKE2S_BLOCK_SIZE; + pos += Z7_BLAKE2S_BLOCK_SIZE; + pos &= SUPER_BLOCK_MASK; + } + while (data != end); +} + + +#if defined(Z7_BLAKE2S_USE_V128_WAY2) || \ + defined(Z7_BLAKE2S_USE_AVX2_WAY2) +#if 1 + #define Z7_BLAKE2S_CompressSingleBlock(s, data) \ + Blake2sp_Compress2_V128_Way1(s, data, \ + (const Byte *)(const void *)(data) + Z7_BLAKE2S_BLOCK_SIZE) +#else + #define Z7_BLAKE2S_CompressSingleBlock Blake2s_Compress_V128_Way1 +#endif +#endif + + +#if (defined(Z7_BLAKE2S_USE_AVX2_WAY_SLOW) || \ + defined(Z7_BLAKE2S_USE_V128_WAY2)) && \ + !defined(Z7_BLAKE2S_USE_GATHER) +#define AXR2_LOAD_INDEXES(sigma_index) \ + const unsigned i0 = sigma[(sigma_index)]; \ + const unsigned i1 = sigma[(sigma_index) + 2 * 1]; \ + const unsigned i2 = sigma[(sigma_index) + 2 * 2]; \ + const unsigned i3 = sigma[(sigma_index) + 2 * 3]; \ + +#define SET_ROW_FROM_SIGMA_W(input) \ + SET_ROW_FROM_SIGMA_BASE(input, i0, i1, i2, i3) +#endif + + +#ifdef Z7_BLAKE2S_USE_V128_WAY2 + +#if 1 || !defined(Z7_BLAKE2S_USE_SSE41) +/* we use SET_ROW_FROM_SIGMA_BASE, that uses + (SSE4) _mm_insert_epi32(), if Z7_BLAKE2S_USE_INSERT_INSTRUCTION is defined + (SSE2) _mm_set_epi32() + MSVC can be faster for this branch: +*/ +#define AXR2_W(sigma_index, shift1, shift2) \ + { \ + AXR2_LOAD_INDEXES(sigma_index) \ + a0 = _mm_add_epi32(a0, SET_ROW_FROM_SIGMA_W(data)); \ + a1 = _mm_add_epi32(a1, SET_ROW_FROM_SIGMA_W(data + Z7_BLAKE2S_BLOCK_SIZE)); \ + AXR(a0, b0, d0, shift1) \ + AXR(a1, b1, d1, shift1) \ + AXR(c0, d0, b0, shift2) \ + AXR(c1, d1, b1, shift2) \ + } +#else +/* we use interleaved _mm_insert_epi32(): + GCC can be faster for this branch: +*/ +#define AXR2_W_PRE_INSERT(sigma_index, i) \ + { const unsigned ii = sigma[(sigma_index) + i * 2]; \ + t0 = _mm_insert_epi32(t0, *(const Int32 *)GET_SIGMA_PTR(data, ii), i); \ + t1 = _mm_insert_epi32(t1, *(const Int32 *)GET_SIGMA_PTR(data, Z7_BLAKE2S_BLOCK_SIZE + ii), i); \ + } +#define AXR2_W(sigma_index, shift1, shift2) \ + { __m128i t0, t1; \ + { const unsigned ii = sigma[sigma_index]; \ + t0 = _mm_cvtsi32_si128(*(const Int32 *)GET_SIGMA_PTR(data, ii)); \ + t1 = _mm_cvtsi32_si128(*(const Int32 *)GET_SIGMA_PTR(data, Z7_BLAKE2S_BLOCK_SIZE + ii)); \ + } \ + AXR2_W_PRE_INSERT(sigma_index, 1) \ + AXR2_W_PRE_INSERT(sigma_index, 2) \ + AXR2_W_PRE_INSERT(sigma_index, 3) \ + a0 = _mm_add_epi32(a0, t0); \ + a1 = _mm_add_epi32(a1, t1); \ + AXR(a0, b0, d0, shift1) \ + AXR(a1, b1, d1, shift1) \ + AXR(c0, d0, b0, shift2) \ + AXR(c1, d1, b1, shift2) \ + } +#endif + + +#define AXR4_W(sigma_index) \ + AXR2_W(sigma_index, 16, 12) \ + AXR2_W(sigma_index + 1, 8, 7) \ + +#define WW(r) \ + { const Byte * const sigma = k_Blake2s_Sigma_4[r]; \ + AXR4_W(0) \ + ROTATE_WORDS_TO_RIGHT(b0, 1) \ + ROTATE_WORDS_TO_RIGHT(b1, 1) \ + ROTATE_WORDS_TO_RIGHT(c0, 2) \ + ROTATE_WORDS_TO_RIGHT(c1, 2) \ + ROTATE_WORDS_TO_RIGHT(d0, 3) \ + ROTATE_WORDS_TO_RIGHT(d1, 3) \ + AXR4_W(8) \ + ROTATE_WORDS_TO_RIGHT(b0, 3) \ + ROTATE_WORDS_TO_RIGHT(b1, 3) \ + ROTATE_WORDS_TO_RIGHT(c0, 2) \ + ROTATE_WORDS_TO_RIGHT(c1, 2) \ + ROTATE_WORDS_TO_RIGHT(d0, 1) \ + ROTATE_WORDS_TO_RIGHT(d1, 1) \ + } + + +static +Z7_NO_INLINE +#ifdef BLAKE2S_ATTRIB_128BIT + BLAKE2S_ATTRIB_128BIT +#endif +void +Z7_FASTCALL +Blake2sp_Compress2_V128_Way2(UInt32 *s_items, const Byte *data, const Byte *end) +{ + size_t pos = 0; + end -= Z7_BLAKE2S_BLOCK_SIZE; + + if (data != end) + { + LOAD_ROTATE_CONSTS + do + { + UInt32 * const s = GET_STATE_TABLE_PTR_FROM_BYTE_POS(s_items, pos); + __m128i a0, b0, c0, d0; + __m128i a1, b1, c1, d1; + { + const __m128i inc = k_inc; + const __m128i temp = k_iv4_128; + d0 = LOAD_128_FROM_STRUCT (STATE_T(s)); + d1 = LOAD_128_FROM_STRUCT (STATE_T(s + NSW)); + D_ADD_EPI64_128(d0, inc); + D_ADD_EPI64_128(d1, inc); + STORE_128_TO_STRUCT (STATE_T(s ), d0); + STORE_128_TO_STRUCT (STATE_T(s + NSW), d1); + D_XOR_128(d0, temp); + D_XOR_128(d1, temp); + } + c1 = c0 = k_iv0_128; + a0 = LOAD_128_FROM_STRUCT(s); + b0 = LOAD_128_FROM_STRUCT(s + 4); + a1 = LOAD_128_FROM_STRUCT(s + NSW); + b1 = LOAD_128_FROM_STRUCT(s + NSW + 4); + + ROUNDS_LOOP (WW) + +#undef WW + + D_XOR_128(a0, c0); + D_XOR_128(b0, d0); + D_XOR_128(a1, c1); + D_XOR_128(b1, d1); + + D_XOR_128(a0, LOAD_128_FROM_STRUCT(s)); + D_XOR_128(b0, LOAD_128_FROM_STRUCT(s + 4)); + D_XOR_128(a1, LOAD_128_FROM_STRUCT(s + NSW)); + D_XOR_128(b1, LOAD_128_FROM_STRUCT(s + NSW + 4)); + + STORE_128_TO_STRUCT(s, a0); + STORE_128_TO_STRUCT(s + 4, b0); + STORE_128_TO_STRUCT(s + NSW, a1); + STORE_128_TO_STRUCT(s + NSW + 4, b1); + + data += Z7_BLAKE2S_BLOCK_SIZE * 2; + pos += Z7_BLAKE2S_BLOCK_SIZE * 2; + pos &= SUPER_BLOCK_MASK; + } + while (data < end); + if (data != end) + return; + } + { + UInt32 * const s = GET_STATE_TABLE_PTR_FROM_BYTE_POS(s_items, pos); + Z7_BLAKE2S_CompressSingleBlock(s, data); + } +} +#endif // Z7_BLAKE2S_USE_V128_WAY2 + + +#ifdef Z7_BLAKE2S_USE_V128_WAY2 + #define Z7_BLAKE2S_Compress2_V128 Blake2sp_Compress2_V128_Way2 +#else + #define Z7_BLAKE2S_Compress2_V128 Blake2sp_Compress2_V128_Way1 +#endif + + + +#ifdef Z7_BLAKE2S_MM_ROR_EPI32_IS_SUPPORTED + #define ROT_128_8(x) MM_ROR_EPI32(x, 8) + #define ROT_128_16(x) MM_ROR_EPI32(x, 16) + #define ROT_128_7(x) MM_ROR_EPI32(x, 7) + #define ROT_128_12(x) MM_ROR_EPI32(x, 12) +#else +#if defined(Z7_BLAKE2S_USE_SSSE3) || defined(Z7_BLAKE2S_USE_SSE41) + #define ROT_128_8(x) _mm_shuffle_epi8(x, r8) // k_r8 + #define ROT_128_16(x) _mm_shuffle_epi8(x, r16) // k_r16 +#else + #define ROT_128_8(x) MM_ROR_EPI32_VIA_SHIFT(x, 8) + #define ROT_128_16(x) MM_ROR_EPI32_VIA_SHIFT(x, 16) +#endif + #define ROT_128_7(x) MM_ROR_EPI32_VIA_SHIFT(x, 7) + #define ROT_128_12(x) MM_ROR_EPI32_VIA_SHIFT(x, 12) +#endif + + +#if 1 +// this branch can provide similar speed on x86* in most cases, +// because [base + index*4] provides same speed as [base + index]. +// but some compilers can generate different code with this branch, that can be faster sometimes. +// this branch uses additional table of 10*16=160 bytes. +#define SIGMA_TABLE_MULT_16( a0,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15) \ + SIGMA_TABLE_MULT(16, a0,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15) +MY_ALIGN(16) +static const Byte k_Blake2s_Sigma_16[BLAKE2S_NUM_ROUNDS][16] = + { SIGMA_TABLE(SIGMA_TABLE_MULT_16) }; +#define GET_SIGMA_PTR_128(r) const Byte * const sigma = k_Blake2s_Sigma_16[r]; +#define GET_SIGMA_VAL_128(n) (sigma[n]) +#else +#define GET_SIGMA_PTR_128(r) const Byte * const sigma = k_Blake2s_Sigma_4[r]; +#define GET_SIGMA_VAL_128(n) (4 * (size_t)sigma[n]) +#endif + + +#ifdef Z7_BLAKE2S_USE_AVX2_FAST +#if 1 +#define SIGMA_TABLE_MULT_32( a0,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15) \ + SIGMA_TABLE_MULT(32, a0,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15) +MY_ALIGN(64) +static const UInt16 k_Blake2s_Sigma_32[BLAKE2S_NUM_ROUNDS][16] = + { SIGMA_TABLE(SIGMA_TABLE_MULT_32) }; +#define GET_SIGMA_PTR_256(r) const UInt16 * const sigma = k_Blake2s_Sigma_32[r]; +#define GET_SIGMA_VAL_256(n) (sigma[n]) +#else +#define GET_SIGMA_PTR_256(r) const Byte * const sigma = k_Blake2s_Sigma_4[r]; +#define GET_SIGMA_VAL_256(n) (8 * (size_t)sigma[n]) +#endif +#endif // Z7_BLAKE2S_USE_AVX2_FAST + + +#define D_ROT_128_7(dest) dest = ROT_128_7(dest) +#define D_ROT_128_8(dest) dest = ROT_128_8(dest) +#define D_ROT_128_12(dest) dest = ROT_128_12(dest) +#define D_ROT_128_16(dest) dest = ROT_128_16(dest) + +#define OP_L(a, i) D_ADD_128 (V(a, 0), \ + LOAD_128((const Byte *)(w) + GET_SIGMA_VAL_128(2*(a)+(i)))); + +#define OP_0(a) OP_L(a, 0) +#define OP_7(a) OP_L(a, 1) + +#define OP_1(a) D_ADD_128 (V(a, 0), V(a, 1)); +#define OP_2(a) D_XOR_128 (V(a, 3), V(a, 0)); +#define OP_4(a) D_ADD_128 (V(a, 2), V(a, 3)); +#define OP_5(a) D_XOR_128 (V(a, 1), V(a, 2)); + +#define OP_3(a) D_ROT_128_16 (V(a, 3)); +#define OP_6(a) D_ROT_128_12 (V(a, 1)); +#define OP_8(a) D_ROT_128_8 (V(a, 3)); +#define OP_9(a) D_ROT_128_7 (V(a, 1)); + + +// for 32-bit x86 : interleave mode works slower, because of register pressure. + +#if 0 || 1 && (defined(MY_CPU_X86) \ + || defined(__GNUC__) && !defined(__clang__)) +// non-inteleaved version: +// is fast for x86 32-bit. +// is fast for GCC x86-64. + +#define V4G(a) \ + OP_0 (a) \ + OP_1 (a) \ + OP_2 (a) \ + OP_3 (a) \ + OP_4 (a) \ + OP_5 (a) \ + OP_6 (a) \ + OP_7 (a) \ + OP_1 (a) \ + OP_2 (a) \ + OP_8 (a) \ + OP_4 (a) \ + OP_5 (a) \ + OP_9 (a) \ + +#define V4R \ +{ \ + V4G (0) \ + V4G (1) \ + V4G (2) \ + V4G (3) \ + V4G (4) \ + V4G (5) \ + V4G (6) \ + V4G (7) \ +} + +#elif 0 || 1 && defined(MY_CPU_X86) + +#define OP_INTER_2(op, a,b) \ + op (a) \ + op (b) \ + +#define V4G(a,b) \ + OP_INTER_2 (OP_0, a,b) \ + OP_INTER_2 (OP_1, a,b) \ + OP_INTER_2 (OP_2, a,b) \ + OP_INTER_2 (OP_3, a,b) \ + OP_INTER_2 (OP_4, a,b) \ + OP_INTER_2 (OP_5, a,b) \ + OP_INTER_2 (OP_6, a,b) \ + OP_INTER_2 (OP_7, a,b) \ + OP_INTER_2 (OP_1, a,b) \ + OP_INTER_2 (OP_2, a,b) \ + OP_INTER_2 (OP_8, a,b) \ + OP_INTER_2 (OP_4, a,b) \ + OP_INTER_2 (OP_5, a,b) \ + OP_INTER_2 (OP_9, a,b) \ + +#define V4R \ +{ \ + V4G (0, 1) \ + V4G (2, 3) \ + V4G (4, 5) \ + V4G (6, 7) \ +} + +#else +// iterleave-4 version is fast for x64 (MSVC/CLANG) + +#define OP_INTER_4(op, a,b,c,d) \ + op (a) \ + op (b) \ + op (c) \ + op (d) \ + +#define V4G(a,b,c,d) \ + OP_INTER_4 (OP_0, a,b,c,d) \ + OP_INTER_4 (OP_1, a,b,c,d) \ + OP_INTER_4 (OP_2, a,b,c,d) \ + OP_INTER_4 (OP_3, a,b,c,d) \ + OP_INTER_4 (OP_4, a,b,c,d) \ + OP_INTER_4 (OP_5, a,b,c,d) \ + OP_INTER_4 (OP_6, a,b,c,d) \ + OP_INTER_4 (OP_7, a,b,c,d) \ + OP_INTER_4 (OP_1, a,b,c,d) \ + OP_INTER_4 (OP_2, a,b,c,d) \ + OP_INTER_4 (OP_8, a,b,c,d) \ + OP_INTER_4 (OP_4, a,b,c,d) \ + OP_INTER_4 (OP_5, a,b,c,d) \ + OP_INTER_4 (OP_9, a,b,c,d) \ + +#define V4R \ +{ \ + V4G (0, 1, 2, 3) \ + V4G (4, 5, 6, 7) \ +} + +#endif + +#define V4_ROUND(r) { GET_SIGMA_PTR_128(r); V4R } + + +#define V4_LOAD_MSG_1(w, m, i) \ +{ \ + __m128i m0, m1, m2, m3; \ + __m128i t0, t1, t2, t3; \ + m0 = LOADU_128((m) + ((i) + 0 * 4) * 16); \ + m1 = LOADU_128((m) + ((i) + 1 * 4) * 16); \ + m2 = LOADU_128((m) + ((i) + 2 * 4) * 16); \ + m3 = LOADU_128((m) + ((i) + 3 * 4) * 16); \ + t0 = _mm_unpacklo_epi32(m0, m1); \ + t1 = _mm_unpackhi_epi32(m0, m1); \ + t2 = _mm_unpacklo_epi32(m2, m3); \ + t3 = _mm_unpackhi_epi32(m2, m3); \ + w[(i) * 4 + 0] = _mm_unpacklo_epi64(t0, t2); \ + w[(i) * 4 + 1] = _mm_unpackhi_epi64(t0, t2); \ + w[(i) * 4 + 2] = _mm_unpacklo_epi64(t1, t3); \ + w[(i) * 4 + 3] = _mm_unpackhi_epi64(t1, t3); \ +} + +#define V4_LOAD_MSG(w, m) \ +{ \ + V4_LOAD_MSG_1 (w, m, 0) \ + V4_LOAD_MSG_1 (w, m, 1) \ + V4_LOAD_MSG_1 (w, m, 2) \ + V4_LOAD_MSG_1 (w, m, 3) \ +} + +#define V4_LOAD_UNPACK_PAIR_128(src32, i, d0, d1) \ +{ \ + const __m128i v0 = LOAD_128_FROM_STRUCT((src32) + (i ) * 4); \ + const __m128i v1 = LOAD_128_FROM_STRUCT((src32) + (i + 1) * 4); \ + d0 = _mm_unpacklo_epi32(v0, v1); \ + d1 = _mm_unpackhi_epi32(v0, v1); \ +} + +#define V4_UNPACK_PAIR_128(dest32, i, s0, s1) \ +{ \ + STORE_128_TO_STRUCT((dest32) + i * 4 , _mm_unpacklo_epi64(s0, s1)); \ + STORE_128_TO_STRUCT((dest32) + i * 4 + 16, _mm_unpackhi_epi64(s0, s1)); \ +} + +#define V4_UNPACK_STATE(dest32, src32) \ +{ \ + __m128i t0, t1, t2, t3, t4, t5, t6, t7; \ + V4_LOAD_UNPACK_PAIR_128(src32, 0, t0, t1) \ + V4_LOAD_UNPACK_PAIR_128(src32, 2, t2, t3) \ + V4_LOAD_UNPACK_PAIR_128(src32, 4, t4, t5) \ + V4_LOAD_UNPACK_PAIR_128(src32, 6, t6, t7) \ + V4_UNPACK_PAIR_128(dest32, 0, t0, t2) \ + V4_UNPACK_PAIR_128(dest32, 8, t1, t3) \ + V4_UNPACK_PAIR_128(dest32, 1, t4, t6) \ + V4_UNPACK_PAIR_128(dest32, 9, t5, t7) \ +} + + +static +Z7_NO_INLINE +#ifdef BLAKE2S_ATTRIB_128BIT + BLAKE2S_ATTRIB_128BIT +#endif +void +Z7_FASTCALL +Blake2sp_Compress2_V128_Fast(UInt32 *s_items, const Byte *data, const Byte *end) +{ + // PrintStates2(s_items, 8, 16); + size_t pos = 0; + pos /= 2; + do + { +#if defined(Z7_BLAKE2S_USE_SSSE3) && \ + !defined(Z7_BLAKE2S_MM_ROR_EPI32_IS_SUPPORTED) + const __m128i r8 = k_r8; + const __m128i r16 = k_r16; +#endif + __m128i w[16]; + __m128i v[16]; + UInt32 *s; + V4_LOAD_MSG(w, data) + s = GET_STATE_TABLE_PTR_FROM_BYTE_POS(s_items, pos); + { + __m128i ctr = LOAD_128_FROM_STRUCT(s + 64); + D_ADD_EPI64_128 (ctr, k_inc); + STORE_128_TO_STRUCT(s + 64, ctr); + v[12] = XOR_128 (GET_128_IV_WAY4(4), _mm_shuffle_epi32(ctr, _MM_SHUFFLE(0, 0, 0, 0))); + v[13] = XOR_128 (GET_128_IV_WAY4(5), _mm_shuffle_epi32(ctr, _MM_SHUFFLE(1, 1, 1, 1))); + } + v[ 8] = GET_128_IV_WAY4(0); + v[ 9] = GET_128_IV_WAY4(1); + v[10] = GET_128_IV_WAY4(2); + v[11] = GET_128_IV_WAY4(3); + v[14] = GET_128_IV_WAY4(6); + v[15] = GET_128_IV_WAY4(7); - for (i = 0; i < 16; i++) - m[i] = GetUi32(p->buf + i * sizeof(m[i])); +#define LOAD_STATE_128_FROM_STRUCT(i) \ + v[i] = LOAD_128_FROM_STRUCT(s + (i) * 4); + +#define UPDATE_STATE_128_IN_STRUCT(i) \ + STORE_128_TO_STRUCT(s + (i) * 4, XOR_128( \ + XOR_128(v[i], v[(i) + 8]), \ + LOAD_128_FROM_STRUCT(s + (i) * 4))); - for (i = 0; i < 8; i++) - v[i] = p->h[i]; + REP8_MACRO (LOAD_STATE_128_FROM_STRUCT) + ROUNDS_LOOP (V4_ROUND) + REP8_MACRO (UPDATE_STATE_128_IN_STRUCT) + + data += Z7_BLAKE2S_BLOCK_SIZE * 4; + pos += Z7_BLAKE2S_BLOCK_SIZE * 4 / 2; + pos &= SUPER_BLOCK_SIZE / 2 - 1; } + while (data != end); +} - v[ 8] = k_Blake2s_IV[0]; - v[ 9] = k_Blake2s_IV[1]; - v[10] = k_Blake2s_IV[2]; - v[11] = k_Blake2s_IV[3]; - - v[12] = p->t[0] ^ k_Blake2s_IV[4]; - v[13] = p->t[1] ^ k_Blake2s_IV[5]; - v[14] = p->f[0] ^ k_Blake2s_IV[6]; - v[15] = p->f[1] ^ k_Blake2s_IV[7]; - #define G(r,i,a,b,c,d) \ - a += b + m[sigma[2*i+0]]; d ^= a; d = rotr32(d, 16); c += d; b ^= c; b = rotr32(b, 12); \ - a += b + m[sigma[2*i+1]]; d ^= a; d = rotr32(d, 8); c += d; b ^= c; b = rotr32(b, 7); \ +static +Z7_NO_INLINE +#ifdef BLAKE2S_ATTRIB_128BIT + BLAKE2S_ATTRIB_128BIT +#endif +void +Z7_FASTCALL +Blake2sp_Final_V128_Fast(UInt32 *states) +{ + const __m128i ctr = LOAD_128_FROM_STRUCT(states + 64); + // printf("\nBlake2sp_Compress2_V128_Fast_Final4\n"); + // PrintStates2(states, 8, 16); + { + ptrdiff_t pos = 8 * 4; + do + { + UInt32 *src32 = states + (size_t)(pos * 1); + UInt32 *dest32 = states + (size_t)(pos * 2); + V4_UNPACK_STATE(dest32, src32) + pos -= 8 * 4; + } + while (pos >= 0); + } + { + unsigned k; + for (k = 0; k < 8; k++) + { + UInt32 *s = states + (size_t)k * 16; + STORE_128_TO_STRUCT (STATE_T(s), ctr); + } + } + // PrintStates2(states, 8, 16); +} + + + +#ifdef Z7_BLAKE2S_USE_AVX2 + +#define ADD_256(a, b) _mm256_add_epi32(a, b) +#define XOR_256(a, b) _mm256_xor_si256(a, b) + +#if 1 && defined(Z7_BLAKE2S_USE_AVX512_ALWAYS) + #define MM256_ROR_EPI32 _mm256_ror_epi32 + #define Z7_MM256_ROR_EPI32_IS_SUPPORTED +#ifdef Z7_BLAKE2S_USE_AVX2_WAY2 + #define LOAD_ROTATE_CONSTS_256 +#endif +#else +#ifdef Z7_BLAKE2S_USE_AVX2_WAY_SLOW +#ifdef Z7_BLAKE2S_USE_AVX2_WAY2 + #define LOAD_ROTATE_CONSTS_256 \ + const __m256i r8 = k_r8_256; \ + const __m256i r16 = k_r16_256; +#endif // AVX2_WAY2 + + #define MM256_ROR_EPI32(r, c) ( \ + ( 8==(c)) ? _mm256_shuffle_epi8(r,r8) \ + : (16==(c)) ? _mm256_shuffle_epi8(r,r16) \ + : _mm256_or_si256( \ + _mm256_srli_epi32((r), (c)), \ + _mm256_slli_epi32((r), 32-(c)))) +#endif // WAY_SLOW +#endif + + +#define D_ADD_256(dest, src) dest = ADD_256(dest, src) +#define D_XOR_256(dest, src) dest = XOR_256(dest, src) + +#define LOADU_256(p) _mm256_loadu_si256((const __m256i *)(const void *)(p)) + +#ifdef Z7_BLAKE2S_USE_AVX2_FAST + +#ifdef Z7_MM256_ROR_EPI32_IS_SUPPORTED +#define ROT_256_16(x) MM256_ROR_EPI32((x), 16) +#define ROT_256_12(x) MM256_ROR_EPI32((x), 12) +#define ROT_256_8(x) MM256_ROR_EPI32((x), 8) +#define ROT_256_7(x) MM256_ROR_EPI32((x), 7) +#else +#define ROTATE8 _mm256_set_epi8(12, 15, 14, 13, 8, 11, 10, 9, 4, 7, 6, 5, 0, 3, 2, 1, \ + 12, 15, 14, 13, 8, 11, 10, 9, 4, 7, 6, 5, 0, 3, 2, 1) +#define ROTATE16 _mm256_set_epi8(13, 12, 15, 14, 9, 8, 11, 10, 5, 4, 7, 6, 1, 0, 3, 2, \ + 13, 12, 15, 14, 9, 8, 11, 10, 5, 4, 7, 6, 1, 0, 3, 2) +#define ROT_256_16(x) _mm256_shuffle_epi8((x), ROTATE16) +#define ROT_256_12(x) _mm256_or_si256(_mm256_srli_epi32((x), 12), _mm256_slli_epi32((x), 20)) +#define ROT_256_8(x) _mm256_shuffle_epi8((x), ROTATE8) +#define ROT_256_7(x) _mm256_or_si256(_mm256_srli_epi32((x), 7), _mm256_slli_epi32((x), 25)) +#endif + +#define D_ROT_256_7(dest) dest = ROT_256_7(dest) +#define D_ROT_256_8(dest) dest = ROT_256_8(dest) +#define D_ROT_256_12(dest) dest = ROT_256_12(dest) +#define D_ROT_256_16(dest) dest = ROT_256_16(dest) + +#define LOAD_256(p) _mm256_load_si256((const __m256i *)(const void *)(p)) +#ifdef Z7_BLAKE2SP_STRUCT_IS_NOT_ALIGNED + #define STOREU_256(p, r) _mm256_storeu_si256((__m256i *)(void *)(p), r) + #define LOAD_256_FROM_STRUCT(p) LOADU_256(p) + #define STORE_256_TO_STRUCT(p, r) STOREU_256(p, r) +#else + // if struct is aligned for 32-bytes + #define STORE_256(p, r) _mm256_store_si256((__m256i *)(void *)(p), r) + #define LOAD_256_FROM_STRUCT(p) LOAD_256(p) + #define STORE_256_TO_STRUCT(p, r) STORE_256(p, r) +#endif + +#endif // Z7_BLAKE2S_USE_AVX2_FAST + + + +#ifdef Z7_BLAKE2S_USE_AVX2_WAY_SLOW + +#if 0 + #define DIAG_PERM2(s) \ + { \ + const __m256i a = LOAD_256_FROM_STRUCT((s) ); \ + const __m256i b = LOAD_256_FROM_STRUCT((s) + NSW); \ + STORE_256_TO_STRUCT((s ), _mm256_permute2x128_si256(a, b, 0x20)); \ + STORE_256_TO_STRUCT((s + NSW), _mm256_permute2x128_si256(a, b, 0x31)); \ + } +#else + #define DIAG_PERM2(s) \ + { \ + const __m128i a = LOAD_128_FROM_STRUCT((s) + 4); \ + const __m128i b = LOAD_128_FROM_STRUCT((s) + NSW); \ + STORE_128_TO_STRUCT((s) + NSW, a); \ + STORE_128_TO_STRUCT((s) + 4 , b); \ + } +#endif + #define DIAG_PERM8(s_items) \ + { \ + DIAG_PERM2(s_items) \ + DIAG_PERM2(s_items + NSW * 2) \ + DIAG_PERM2(s_items + NSW * 4) \ + DIAG_PERM2(s_items + NSW * 6) \ + } + + +#define AXR256(a, b, d, shift) \ + D_ADD_256(a, b); \ + D_XOR_256(d, a); \ + d = MM256_ROR_EPI32(d, shift); \ + + + +#ifdef Z7_BLAKE2S_USE_GATHER + + #define TABLE_GATHER_256_4(a0,a1,a2,a3) \ + a0,a1,a2,a3, a0+16,a1+16,a2+16,a3+16 + #define TABLE_GATHER_256( \ + a0,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15) \ + { TABLE_GATHER_256_4(a0,a2,a4,a6), \ + TABLE_GATHER_256_4(a1,a3,a5,a7), \ + TABLE_GATHER_256_4(a8,a10,a12,a14), \ + TABLE_GATHER_256_4(a9,a11,a13,a15) } +MY_ALIGN(64) +static const UInt32 k_Blake2s_Sigma_gather256[BLAKE2S_NUM_ROUNDS][16 * 2] = + { SIGMA_TABLE(TABLE_GATHER_256) }; + #define GET_SIGMA(r) \ + const UInt32 * const sigma = k_Blake2s_Sigma_gather256[r]; + #define AXR2_LOAD_INDEXES_AVX(sigma_index) \ + const __m256i i01234567 = LOAD_256(sigma + (sigma_index)); + #define SET_ROW_FROM_SIGMA_AVX(in) \ + _mm256_i32gather_epi32((const void *)(in), i01234567, 4) + #define SIGMA_INTERLEAVE 8 + #define SIGMA_HALF_ROW_SIZE 16 + +#else // !Z7_BLAKE2S_USE_GATHER + + #define GET_SIGMA(r) \ + const Byte * const sigma = k_Blake2s_Sigma_4[r]; + #define AXR2_LOAD_INDEXES_AVX(sigma_index) \ + AXR2_LOAD_INDEXES(sigma_index) + #define SET_ROW_FROM_SIGMA_AVX(in) \ + MY_mm256_set_m128i( \ + SET_ROW_FROM_SIGMA_W((in) + Z7_BLAKE2S_BLOCK_SIZE), \ + SET_ROW_FROM_SIGMA_W(in)) + #define SIGMA_INTERLEAVE 1 + #define SIGMA_HALF_ROW_SIZE 8 +#endif // !Z7_BLAKE2S_USE_GATHER - #define R(r) \ - G(r,0,v[ 0],v[ 4],v[ 8],v[12]); \ - G(r,1,v[ 1],v[ 5],v[ 9],v[13]); \ - G(r,2,v[ 2],v[ 6],v[10],v[14]); \ - G(r,3,v[ 3],v[ 7],v[11],v[15]); \ - G(r,4,v[ 0],v[ 5],v[10],v[15]); \ - G(r,5,v[ 1],v[ 6],v[11],v[12]); \ - G(r,6,v[ 2],v[ 7],v[ 8],v[13]); \ - G(r,7,v[ 3],v[ 4],v[ 9],v[14]); \ +#define ROTATE_WORDS_TO_RIGHT_256(a, n) \ + a = _mm256_shuffle_epi32(a, _MM_SHUFFLE((3+n)&3, (2+n)&3, (1+n)&3, (0+n)&3)); + + + +#ifdef Z7_BLAKE2S_USE_AVX2_WAY2 + +#define AXR2_A(sigma_index, shift1, shift2) \ + AXR2_LOAD_INDEXES_AVX(sigma_index) \ + D_ADD_256( a0, SET_ROW_FROM_SIGMA_AVX(data)); \ + AXR256(a0, b0, d0, shift1) \ + AXR256(c0, d0, b0, shift2) \ + +#define AXR4_A(sigma_index) \ + { AXR2_A(sigma_index, 16, 12) } \ + { AXR2_A(sigma_index + SIGMA_INTERLEAVE, 8, 7) } + +#define EE1(r) \ + { GET_SIGMA(r) \ + AXR4_A(0) \ + ROTATE_WORDS_TO_RIGHT_256(b0, 1) \ + ROTATE_WORDS_TO_RIGHT_256(c0, 2) \ + ROTATE_WORDS_TO_RIGHT_256(d0, 3) \ + AXR4_A(SIGMA_HALF_ROW_SIZE) \ + ROTATE_WORDS_TO_RIGHT_256(b0, 3) \ + ROTATE_WORDS_TO_RIGHT_256(c0, 2) \ + ROTATE_WORDS_TO_RIGHT_256(d0, 1) \ + } + +static +Z7_NO_INLINE +#ifdef BLAKE2S_ATTRIB_AVX2 + BLAKE2S_ATTRIB_AVX2 +#endif +void +Z7_FASTCALL +Blake2sp_Compress2_AVX2_Way2(UInt32 *s_items, const Byte *data, const Byte *end) +{ + size_t pos = 0; + end -= Z7_BLAKE2S_BLOCK_SIZE; + + if (data != end) { - unsigned r; - for (r = 0; r < BLAKE2S_NUM_ROUNDS; r++) + LOAD_ROTATE_CONSTS_256 + DIAG_PERM8(s_items) + do { - const Byte *sigma = k_Blake2s_Sigma[r]; - R(r); + UInt32 * const s = GET_STATE_TABLE_PTR_FROM_BYTE_POS(s_items, pos); + __m256i a0, b0, c0, d0; + { + const __m128i inc = k_inc; + __m128i d0_128 = LOAD_128_FROM_STRUCT (STATE_T(s)); + __m128i d1_128 = LOAD_128_FROM_STRUCT (STATE_T(s + NSW)); + D_ADD_EPI64_128(d0_128, inc); + D_ADD_EPI64_128(d1_128, inc); + STORE_128_TO_STRUCT (STATE_T(s ), d0_128); + STORE_128_TO_STRUCT (STATE_T(s + NSW), d1_128); + d0 = MY_mm256_set_m128i(d1_128, d0_128); + D_XOR_256(d0, k_iv4_256); + } + c0 = SET_FROM_128(k_iv0_128); + a0 = LOAD_256_FROM_STRUCT(s + NSW * 0); + b0 = LOAD_256_FROM_STRUCT(s + NSW * 1); + + ROUNDS_LOOP (EE1) + + D_XOR_256(a0, c0); + D_XOR_256(b0, d0); + + D_XOR_256(a0, LOAD_256_FROM_STRUCT(s + NSW * 0)); + D_XOR_256(b0, LOAD_256_FROM_STRUCT(s + NSW * 1)); + + STORE_256_TO_STRUCT(s + NSW * 0, a0); + STORE_256_TO_STRUCT(s + NSW * 1, b0); + + data += Z7_BLAKE2S_BLOCK_SIZE * 2; + pos += Z7_BLAKE2S_BLOCK_SIZE * 2; + pos &= SUPER_BLOCK_MASK; } - /* R(0); R(1); R(2); R(3); R(4); R(5); R(6); R(7); R(8); R(9); */ + while (data < end); + DIAG_PERM8(s_items) + if (data != end) + return; + } + { + UInt32 * const s = GET_STATE_TABLE_PTR_FROM_BYTE_POS(s_items, pos); + Z7_BLAKE2S_CompressSingleBlock(s, data); } +} - #undef G - #undef R +#endif // Z7_BLAKE2S_USE_AVX2_WAY2 + + +#ifdef Z7_BLAKE2S_USE_AVX2_WAY4 + +#define AXR2_X(sigma_index, shift1, shift2) \ + AXR2_LOAD_INDEXES_AVX(sigma_index) \ + D_ADD_256( a0, SET_ROW_FROM_SIGMA_AVX(data)); \ + D_ADD_256( a1, SET_ROW_FROM_SIGMA_AVX((data) + Z7_BLAKE2S_BLOCK_SIZE * 2)); \ + AXR256(a0, b0, d0, shift1) \ + AXR256(a1, b1, d1, shift1) \ + AXR256(c0, d0, b0, shift2) \ + AXR256(c1, d1, b1, shift2) \ + +#define AXR4_X(sigma_index) \ + { AXR2_X(sigma_index, 16, 12) } \ + { AXR2_X(sigma_index + SIGMA_INTERLEAVE, 8, 7) } + +#define EE2(r) \ + { GET_SIGMA(r) \ + AXR4_X(0) \ + ROTATE_WORDS_TO_RIGHT_256(b0, 1) \ + ROTATE_WORDS_TO_RIGHT_256(b1, 1) \ + ROTATE_WORDS_TO_RIGHT_256(c0, 2) \ + ROTATE_WORDS_TO_RIGHT_256(c1, 2) \ + ROTATE_WORDS_TO_RIGHT_256(d0, 3) \ + ROTATE_WORDS_TO_RIGHT_256(d1, 3) \ + AXR4_X(SIGMA_HALF_ROW_SIZE) \ + ROTATE_WORDS_TO_RIGHT_256(b0, 3) \ + ROTATE_WORDS_TO_RIGHT_256(b1, 3) \ + ROTATE_WORDS_TO_RIGHT_256(c0, 2) \ + ROTATE_WORDS_TO_RIGHT_256(c1, 2) \ + ROTATE_WORDS_TO_RIGHT_256(d0, 1) \ + ROTATE_WORDS_TO_RIGHT_256(d1, 1) \ + } + +static +Z7_NO_INLINE +#ifdef BLAKE2S_ATTRIB_AVX2 + BLAKE2S_ATTRIB_AVX2 +#endif +void +Z7_FASTCALL +Blake2sp_Compress2_AVX2_Way4(UInt32 *s_items, const Byte *data, const Byte *end) +{ + size_t pos = 0; + + if ((size_t)(end - data) >= Z7_BLAKE2S_BLOCK_SIZE * 4) { - unsigned i; - for (i = 0; i < 8; i++) - p->h[i] ^= v[i] ^ v[i + 8]; +#ifndef Z7_MM256_ROR_EPI32_IS_SUPPORTED + const __m256i r8 = k_r8_256; + const __m256i r16 = k_r16_256; +#endif + end -= Z7_BLAKE2S_BLOCK_SIZE * 3; + DIAG_PERM8(s_items) + do + { + UInt32 * const s = GET_STATE_TABLE_PTR_FROM_BYTE_POS(s_items, pos); + __m256i a0, b0, c0, d0; + __m256i a1, b1, c1, d1; + { + const __m128i inc = k_inc; + __m128i d0_128 = LOAD_128_FROM_STRUCT (STATE_T(s)); + __m128i d1_128 = LOAD_128_FROM_STRUCT (STATE_T(s + NSW)); + __m128i d2_128 = LOAD_128_FROM_STRUCT (STATE_T(s + NSW * 2)); + __m128i d3_128 = LOAD_128_FROM_STRUCT (STATE_T(s + NSW * 3)); + D_ADD_EPI64_128(d0_128, inc); + D_ADD_EPI64_128(d1_128, inc); + D_ADD_EPI64_128(d2_128, inc); + D_ADD_EPI64_128(d3_128, inc); + STORE_128_TO_STRUCT (STATE_T(s ), d0_128); + STORE_128_TO_STRUCT (STATE_T(s + NSW * 1), d1_128); + STORE_128_TO_STRUCT (STATE_T(s + NSW * 2), d2_128); + STORE_128_TO_STRUCT (STATE_T(s + NSW * 3), d3_128); + d0 = MY_mm256_set_m128i(d1_128, d0_128); + d1 = MY_mm256_set_m128i(d3_128, d2_128); + D_XOR_256(d0, k_iv4_256); + D_XOR_256(d1, k_iv4_256); + } + c1 = c0 = SET_FROM_128(k_iv0_128); + a0 = LOAD_256_FROM_STRUCT(s + NSW * 0); + b0 = LOAD_256_FROM_STRUCT(s + NSW * 1); + a1 = LOAD_256_FROM_STRUCT(s + NSW * 2); + b1 = LOAD_256_FROM_STRUCT(s + NSW * 3); + + ROUNDS_LOOP (EE2) + + D_XOR_256(a0, c0); + D_XOR_256(b0, d0); + D_XOR_256(a1, c1); + D_XOR_256(b1, d1); + + D_XOR_256(a0, LOAD_256_FROM_STRUCT(s + NSW * 0)); + D_XOR_256(b0, LOAD_256_FROM_STRUCT(s + NSW * 1)); + D_XOR_256(a1, LOAD_256_FROM_STRUCT(s + NSW * 2)); + D_XOR_256(b1, LOAD_256_FROM_STRUCT(s + NSW * 3)); + + STORE_256_TO_STRUCT(s + NSW * 0, a0); + STORE_256_TO_STRUCT(s + NSW * 1, b0); + STORE_256_TO_STRUCT(s + NSW * 2, a1); + STORE_256_TO_STRUCT(s + NSW * 3, b1); + + data += Z7_BLAKE2S_BLOCK_SIZE * 4; + pos += Z7_BLAKE2S_BLOCK_SIZE * 4; + pos &= SUPER_BLOCK_MASK; + } + while (data < end); + DIAG_PERM8(s_items) + end += Z7_BLAKE2S_BLOCK_SIZE * 3; + } + if (data == end) + return; + // Z7_BLAKE2S_Compress2_V128(s_items, data, end, pos); + do + { + UInt32 * const s = GET_STATE_TABLE_PTR_FROM_BYTE_POS(s_items, pos); + Z7_BLAKE2S_CompressSingleBlock(s, data); + data += Z7_BLAKE2S_BLOCK_SIZE; + pos += Z7_BLAKE2S_BLOCK_SIZE; + pos &= SUPER_BLOCK_MASK; } + while (data != end); } +#endif // Z7_BLAKE2S_USE_AVX2_WAY4 +#endif // Z7_BLAKE2S_USE_AVX2_WAY_SLOW + -#define Blake2s_Increment_Counter(S, inc) \ - { p->t[0] += (inc); p->t[1] += (p->t[0] < (inc)); } +// --------------------------------------------------------- -#define Blake2s_Set_LastBlock(p) \ - { p->f[0] = BLAKE2S_FINAL_FLAG; p->f[1] = p->lastNode_f1; } +#ifdef Z7_BLAKE2S_USE_AVX2_FAST +#define OP256_L(a, i) D_ADD_256 (V(a, 0), \ + LOAD_256((const Byte *)(w) + GET_SIGMA_VAL_256(2*(a)+(i)))); + +#define OP256_0(a) OP256_L(a, 0) +#define OP256_7(a) OP256_L(a, 1) + +#define OP256_1(a) D_ADD_256 (V(a, 0), V(a, 1)); +#define OP256_2(a) D_XOR_256 (V(a, 3), V(a, 0)); +#define OP256_4(a) D_ADD_256 (V(a, 2), V(a, 3)); +#define OP256_5(a) D_XOR_256 (V(a, 1), V(a, 2)); + +#define OP256_3(a) D_ROT_256_16 (V(a, 3)); +#define OP256_6(a) D_ROT_256_12 (V(a, 1)); +#define OP256_8(a) D_ROT_256_8 (V(a, 3)); +#define OP256_9(a) D_ROT_256_7 (V(a, 1)); + + +#if 0 || 1 && defined(MY_CPU_X86) + +#define V8_G(a) \ + OP256_0 (a) \ + OP256_1 (a) \ + OP256_2 (a) \ + OP256_3 (a) \ + OP256_4 (a) \ + OP256_5 (a) \ + OP256_6 (a) \ + OP256_7 (a) \ + OP256_1 (a) \ + OP256_2 (a) \ + OP256_8 (a) \ + OP256_4 (a) \ + OP256_5 (a) \ + OP256_9 (a) \ + +#define V8R { \ + V8_G (0); \ + V8_G (1); \ + V8_G (2); \ + V8_G (3); \ + V8_G (4); \ + V8_G (5); \ + V8_G (6); \ + V8_G (7); \ +} + +#else + +#define OP256_INTER_4(op, a,b,c,d) \ + op (a) \ + op (b) \ + op (c) \ + op (d) \ + +#define V8_G(a,b,c,d) \ + OP256_INTER_4 (OP256_0, a,b,c,d) \ + OP256_INTER_4 (OP256_1, a,b,c,d) \ + OP256_INTER_4 (OP256_2, a,b,c,d) \ + OP256_INTER_4 (OP256_3, a,b,c,d) \ + OP256_INTER_4 (OP256_4, a,b,c,d) \ + OP256_INTER_4 (OP256_5, a,b,c,d) \ + OP256_INTER_4 (OP256_6, a,b,c,d) \ + OP256_INTER_4 (OP256_7, a,b,c,d) \ + OP256_INTER_4 (OP256_1, a,b,c,d) \ + OP256_INTER_4 (OP256_2, a,b,c,d) \ + OP256_INTER_4 (OP256_8, a,b,c,d) \ + OP256_INTER_4 (OP256_4, a,b,c,d) \ + OP256_INTER_4 (OP256_5, a,b,c,d) \ + OP256_INTER_4 (OP256_9, a,b,c,d) \ + +#define V8R { \ + V8_G (0, 1, 2, 3) \ + V8_G (4, 5, 6, 7) \ +} +#endif + +#define V8_ROUND(r) { GET_SIGMA_PTR_256(r); V8R } + + +// for debug: +// #define Z7_BLAKE2S_PERMUTE_WITH_GATHER +#if defined(Z7_BLAKE2S_PERMUTE_WITH_GATHER) +// gather instruction is slow. +#define V8_LOAD_MSG(w, m) \ +{ \ + unsigned i; \ + for (i = 0; i < 16; ++i) { \ + w[i] = _mm256_i32gather_epi32( \ + (const void *)((m) + i * sizeof(UInt32)),\ + _mm256_set_epi32(0x70, 0x60, 0x50, 0x40, 0x30, 0x20, 0x10, 0x00), \ + sizeof(UInt32)); \ + } \ +} +#else // !Z7_BLAKE2S_PERMUTE_WITH_GATHER + +#define V8_LOAD_MSG_2(w, a0, a1) \ +{ \ + (w)[0] = _mm256_permute2x128_si256(a0, a1, 0x20); \ + (w)[4] = _mm256_permute2x128_si256(a0, a1, 0x31); \ +} + +#define V8_LOAD_MSG_4(w, z0, z1, z2, z3) \ +{ \ + __m256i s0, s1, s2, s3; \ + s0 = _mm256_unpacklo_epi64(z0, z1); \ + s1 = _mm256_unpackhi_epi64(z0, z1); \ + s2 = _mm256_unpacklo_epi64(z2, z3); \ + s3 = _mm256_unpackhi_epi64(z2, z3); \ + V8_LOAD_MSG_2((w) + 0, s0, s2) \ + V8_LOAD_MSG_2((w) + 1, s1, s3) \ +} + +#define V8_LOAD_MSG_0(t0, t1, m) \ +{ \ + __m256i m0, m1; \ + m0 = LOADU_256(m); \ + m1 = LOADU_256((m) + 2 * 32); \ + t0 = _mm256_unpacklo_epi32(m0, m1); \ + t1 = _mm256_unpackhi_epi32(m0, m1); \ +} + +#define V8_LOAD_MSG_8(w, m) \ +{ \ + __m256i t0, t1, t2, t3, t4, t5, t6, t7; \ + V8_LOAD_MSG_0(t0, t4, (m) + 0 * 4 * 32) \ + V8_LOAD_MSG_0(t1, t5, (m) + 1 * 4 * 32) \ + V8_LOAD_MSG_0(t2, t6, (m) + 2 * 4 * 32) \ + V8_LOAD_MSG_0(t3, t7, (m) + 3 * 4 * 32) \ + V8_LOAD_MSG_4((w) , t0, t1, t2, t3) \ + V8_LOAD_MSG_4((w) + 2, t4, t5, t6, t7) \ +} + +#define V8_LOAD_MSG(w, m) \ +{ \ + V8_LOAD_MSG_8(w, m) \ + V8_LOAD_MSG_8((w) + 8, (m) + 32) \ +} + +#endif // !Z7_BLAKE2S_PERMUTE_WITH_GATHER + + +#define V8_PERM_PAIR_STORE(u, a0, a2) \ +{ \ + STORE_256_TO_STRUCT((u), _mm256_permute2x128_si256(a0, a2, 0x20)); \ + STORE_256_TO_STRUCT((u) + 8, _mm256_permute2x128_si256(a0, a2, 0x31)); \ +} -static void Blake2s_Update(CBlake2s *p, const Byte *data, size_t size) +#define V8_UNPACK_STORE_4(u, z0, z1, z2, z3) \ +{ \ + __m256i s0, s1, s2, s3; \ + s0 = _mm256_unpacklo_epi64(z0, z1); \ + s1 = _mm256_unpackhi_epi64(z0, z1); \ + s2 = _mm256_unpacklo_epi64(z2, z3); \ + s3 = _mm256_unpackhi_epi64(z2, z3); \ + V8_PERM_PAIR_STORE(u + 0, s0, s2) \ + V8_PERM_PAIR_STORE(u + 2, s1, s3) \ +} + +#define V8_UNPACK_STORE_0(src32, d0, d1) \ +{ \ + const __m256i v0 = LOAD_256_FROM_STRUCT ((src32) ); \ + const __m256i v1 = LOAD_256_FROM_STRUCT ((src32) + 8); \ + d0 = _mm256_unpacklo_epi32(v0, v1); \ + d1 = _mm256_unpackhi_epi32(v0, v1); \ +} + +#define V8_UNPACK_STATE(dest32, src32) \ +{ \ + __m256i t0, t1, t2, t3, t4, t5, t6, t7; \ + V8_UNPACK_STORE_0 ((src32) + 16 * 0, t0, t4) \ + V8_UNPACK_STORE_0 ((src32) + 16 * 1, t1, t5) \ + V8_UNPACK_STORE_0 ((src32) + 16 * 2, t2, t6) \ + V8_UNPACK_STORE_0 ((src32) + 16 * 3, t3, t7) \ + V8_UNPACK_STORE_4 ((__m256i *)(void *)(dest32) , t0, t1, t2, t3) \ + V8_UNPACK_STORE_4 ((__m256i *)(void *)(dest32) + 4, t4, t5, t6, t7) \ +} + + + +#define V8_LOAD_STATE_256_FROM_STRUCT(i) \ + v[i] = LOAD_256_FROM_STRUCT(s_items + (i) * 8); + +#if 0 || 0 && defined(MY_CPU_X86) +#define Z7_BLAKE2S_AVX2_FAST_USE_STRUCT +#endif + +#ifdef Z7_BLAKE2S_AVX2_FAST_USE_STRUCT +// this branch doesn't use (iv) array +// so register pressure can be lower. +// it can be faster sometimes +#define V8_LOAD_STATE_256(i) V8_LOAD_STATE_256_FROM_STRUCT(i) +#define V8_UPDATE_STATE_256(i) \ +{ \ + STORE_256_TO_STRUCT(s_items + (i) * 8, XOR_256( \ + XOR_256(v[i], v[(i) + 8]), \ + LOAD_256_FROM_STRUCT(s_items + (i) * 8))); \ +} +#else +// it uses more variables (iv) registers +// it's better for gcc +// maybe that branch is better, if register pressure will be lower (avx512) +#define V8_LOAD_STATE_256(i) { iv[i] = v[i]; } +#define V8_UPDATE_STATE_256(i) { v[i] = XOR_256(XOR_256(v[i], v[i + 8]), iv[i]); } +#define V8_STORE_STATE_256(i) { STORE_256_TO_STRUCT(s_items + (i) * 8, v[i]); } +#endif + + +#if 0 + // use loading constants from memory + #define KK8(n) KIV(n), KIV(n), KIV(n), KIV(n), KIV(n), KIV(n), KIV(n), KIV(n) +MY_ALIGN(64) +static const UInt32 k_Blake2s_IV_WAY8[]= { - while (size != 0) - { - unsigned pos = (unsigned)p->bufPos; - unsigned rem = BLAKE2S_BLOCK_SIZE - pos; + KK8(0), KK8(1), KK8(2), KK8(3), KK8(4), KK8(5), KK8(6), KK8(7) +}; + #define GET_256_IV_WAY8(i) LOAD_256(k_Blake2s_IV_WAY8 + 8 * (i)) +#else + // use constant generation: + #define GET_256_IV_WAY8(i) _mm256_set1_epi32((Int32)KIV(i)) +#endif + - if (size <= rem) +static +Z7_NO_INLINE +#ifdef BLAKE2S_ATTRIB_AVX2 + BLAKE2S_ATTRIB_AVX2 +#endif +void +Z7_FASTCALL +Blake2sp_Compress2_AVX2_Fast(UInt32 *s_items, const Byte *data, const Byte *end) +{ +#ifndef Z7_BLAKE2S_AVX2_FAST_USE_STRUCT + __m256i v[16]; +#endif + + // PrintStates2(s_items, 8, 16); + +#ifndef Z7_BLAKE2S_AVX2_FAST_USE_STRUCT + REP8_MACRO (V8_LOAD_STATE_256_FROM_STRUCT) +#endif + + do + { + __m256i w[16]; +#ifdef Z7_BLAKE2S_AVX2_FAST_USE_STRUCT + __m256i v[16]; +#else + __m256i iv[8]; +#endif + V8_LOAD_MSG(w, data) { - memcpy(p->buf + pos, data, size); - p->bufPos += (UInt32)size; - return; + // we use load/store ctr inside loop to reduce register pressure: +#if 1 || 1 && defined(MY_CPU_X86) + const __m256i ctr = _mm256_add_epi64( + LOAD_256_FROM_STRUCT(s_items + 64), + _mm256_set_epi32( + 0, 0, 0, Z7_BLAKE2S_BLOCK_SIZE, + 0, 0, 0, Z7_BLAKE2S_BLOCK_SIZE)); + STORE_256_TO_STRUCT(s_items + 64, ctr); +#else + const UInt64 ctr64 = *(const UInt64 *)(const void *)(s_items + 64) + + Z7_BLAKE2S_BLOCK_SIZE; + const __m256i ctr = _mm256_set_epi64x(0, (Int64)ctr64, 0, (Int64)ctr64); + *(UInt64 *)(void *)(s_items + 64) = ctr64; +#endif + v[12] = XOR_256 (GET_256_IV_WAY8(4), _mm256_shuffle_epi32(ctr, _MM_SHUFFLE(0, 0, 0, 0))); + v[13] = XOR_256 (GET_256_IV_WAY8(5), _mm256_shuffle_epi32(ctr, _MM_SHUFFLE(1, 1, 1, 1))); } + v[ 8] = GET_256_IV_WAY8(0); + v[ 9] = GET_256_IV_WAY8(1); + v[10] = GET_256_IV_WAY8(2); + v[11] = GET_256_IV_WAY8(3); + v[14] = GET_256_IV_WAY8(6); + v[15] = GET_256_IV_WAY8(7); - memcpy(p->buf + pos, data, rem); - Blake2s_Increment_Counter(S, BLAKE2S_BLOCK_SIZE); - Blake2s_Compress(p); - p->bufPos = 0; - data += rem; - size -= rem; + REP8_MACRO (V8_LOAD_STATE_256) + ROUNDS_LOOP (V8_ROUND) + REP8_MACRO (V8_UPDATE_STATE_256) + data += SUPER_BLOCK_SIZE; } + while (data != end); + +#ifndef Z7_BLAKE2S_AVX2_FAST_USE_STRUCT + REP8_MACRO (V8_STORE_STATE_256) +#endif } -static void Blake2s_Final(CBlake2s *p, Byte *digest) +static +Z7_NO_INLINE +#ifdef BLAKE2S_ATTRIB_AVX2 + BLAKE2S_ATTRIB_AVX2 +#endif +void +Z7_FASTCALL +Blake2sp_Final_AVX2_Fast(UInt32 *states) { - unsigned i; + const __m128i ctr = LOAD_128_FROM_STRUCT(states + 64); + // PrintStates2(states, 8, 16); + V8_UNPACK_STATE(states, states) + // PrintStates2(states, 8, 16); + { + unsigned k; + for (k = 0; k < 8; k++) + { + UInt32 *s = states + (size_t)k * 16; + STORE_128_TO_STRUCT (STATE_T(s), ctr); + } + } + // PrintStates2(states, 8, 16); + // printf("\nafter V8_UNPACK_STATE \n"); +} + +#endif // Z7_BLAKE2S_USE_AVX2_FAST +#endif // avx2 +#endif // vector + + +/* +#define Blake2s_Increment_Counter(s, inc) \ + { STATE_T(s)[0] += (inc); STATE_T(s)[1] += (STATE_T(s)[0] < (inc)); } +#define Blake2s_Increment_Counter_Small(s, inc) \ + { STATE_T(s)[0] += (inc); } +*/ + +#define Blake2s_Set_LastBlock(s) \ + { STATE_F(s)[0] = BLAKE2S_FINAL_FLAG; /* STATE_F(s)[1] = p->u.header.lastNode_f1; */ } + + +#if 0 || 1 && defined(Z7_MSC_VER_ORIGINAL) && Z7_MSC_VER_ORIGINAL >= 1600 + // good for vs2022 + #define LOOP_8(mac) { unsigned kkk; for (kkk = 0; kkk < 8; kkk++) mac(kkk) } +#else + // good for Z7_BLAKE2S_UNROLL for GCC9 (arm*/x86*) and MSC_VER_1400-x64. + #define LOOP_8(mac) { REP8_MACRO(mac) } +#endif + + +static +Z7_FORCE_INLINE +// Z7_NO_INLINE +void +Z7_FASTCALL +Blake2s_Compress(UInt32 *s, const Byte *input) +{ + UInt32 m[16]; + UInt32 v[16]; + { + unsigned i; + for (i = 0; i < 16; i++) + m[i] = GetUi32(input + i * 4); + } + +#define INIT_v_FROM_s(i) v[i] = s[i]; + + LOOP_8(INIT_v_FROM_s) + + // Blake2s_Increment_Counter(s, Z7_BLAKE2S_BLOCK_SIZE) + { + const UInt32 t0 = STATE_T(s)[0] + Z7_BLAKE2S_BLOCK_SIZE; + const UInt32 t1 = STATE_T(s)[1] + (t0 < Z7_BLAKE2S_BLOCK_SIZE); + STATE_T(s)[0] = t0; + STATE_T(s)[1] = t1; + v[12] = t0 ^ KIV(4); + v[13] = t1 ^ KIV(5); + } + // v[12] = STATE_T(s)[0] ^ KIV(4); + // v[13] = STATE_T(s)[1] ^ KIV(5); + v[14] = STATE_F(s)[0] ^ KIV(6); + v[15] = STATE_F(s)[1] ^ KIV(7); + + v[ 8] = KIV(0); + v[ 9] = KIV(1); + v[10] = KIV(2); + v[11] = KIV(3); + // PrintStates2((const UInt32 *)v, 1, 16); + + #define ADD_SIGMA(a, index) V(a, 0) += *(const UInt32 *)GET_SIGMA_PTR(m, sigma[index]); + #define ADD32M(dest, src, a) V(a, dest) += V(a, src); + #define XOR32M(dest, src, a) V(a, dest) ^= V(a, src); + #define RTR32M(dest, shift, a) V(a, dest) = rotrFixed(V(a, dest), shift); + +// big interleaving can provides big performance gain, if scheduler queues are small. +#if 0 || 1 && defined(MY_CPU_X86) + // interleave-1: for small register number (x86-32bit) + #define G2(index, a, x, y) \ + ADD_SIGMA (a, (index) + 2 * 0) \ + ADD32M (0, 1, a) \ + XOR32M (3, 0, a) \ + RTR32M (3, x, a) \ + ADD32M (2, 3, a) \ + XOR32M (1, 2, a) \ + RTR32M (1, y, a) \ + + #define G(a) \ + G2(a * 2 , a, 16, 12) \ + G2(a * 2 + 1, a, 8, 7) \ + + #define R2 \ + G(0) \ + G(1) \ + G(2) \ + G(3) \ + G(4) \ + G(5) \ + G(6) \ + G(7) \ + +#elif 0 || 1 && defined(MY_CPU_X86_OR_AMD64) + // interleave-2: is good if the number of registers is not big (x86-64). + + #define REP2(mac, dest, src, a, b) \ + mac(dest, src, a) \ + mac(dest, src, b) + + #define G2(index, a, b, x, y) \ + ADD_SIGMA (a, (index) + 2 * 0) \ + ADD_SIGMA (b, (index) + 2 * 1) \ + REP2 (ADD32M, 0, 1, a, b) \ + REP2 (XOR32M, 3, 0, a, b) \ + REP2 (RTR32M, 3, x, a, b) \ + REP2 (ADD32M, 2, 3, a, b) \ + REP2 (XOR32M, 1, 2, a, b) \ + REP2 (RTR32M, 1, y, a, b) \ + + #define G(a, b) \ + G2(a * 2 , a, b, 16, 12) \ + G2(a * 2 + 1, a, b, 8, 7) \ + + #define R2 \ + G(0, 1) \ + G(2, 3) \ + G(4, 5) \ + G(6, 7) \ + +#else + // interleave-4: + // it has big register pressure for x86/x64. + // and MSVC compilers for x86/x64 are slow for this branch. + // but if we have big number of registers, this branch can be faster. + + #define REP4(mac, dest, src, a, b, c, d) \ + mac(dest, src, a) \ + mac(dest, src, b) \ + mac(dest, src, c) \ + mac(dest, src, d) - Blake2s_Increment_Counter(S, (UInt32)p->bufPos); - Blake2s_Set_LastBlock(p); - memset(p->buf + p->bufPos, 0, BLAKE2S_BLOCK_SIZE - p->bufPos); - Blake2s_Compress(p); + #define G2(index, a, b, c, d, x, y) \ + ADD_SIGMA (a, (index) + 2 * 0) \ + ADD_SIGMA (b, (index) + 2 * 1) \ + ADD_SIGMA (c, (index) + 2 * 2) \ + ADD_SIGMA (d, (index) + 2 * 3) \ + REP4 (ADD32M, 0, 1, a, b, c, d) \ + REP4 (XOR32M, 3, 0, a, b, c, d) \ + REP4 (RTR32M, 3, x, a, b, c, d) \ + REP4 (ADD32M, 2, 3, a, b, c, d) \ + REP4 (XOR32M, 1, 2, a, b, c, d) \ + REP4 (RTR32M, 1, y, a, b, c, d) \ - for (i = 0; i < 8; i++) - SetUi32(digest + sizeof(p->h[i]) * i, p->h[i]); + #define G(a, b, c, d) \ + G2(a * 2 , a, b, c, d, 16, 12) \ + G2(a * 2 + 1, a, b, c, d, 8, 7) \ + + #define R2 \ + G(0, 1, 2, 3) \ + G(4, 5, 6, 7) \ + +#endif + + #define R(r) { const Byte *sigma = k_Blake2s_Sigma_4[r]; R2 } + + // Z7_BLAKE2S_UNROLL gives 5-6 KB larger code, but faster: + // 20-40% faster for (x86/x64) VC2010+/GCC/CLANG. + // 30-60% faster for (arm64-arm32) GCC. + // 5-11% faster for (arm64) CLANG-MAC. + // so Z7_BLAKE2S_UNROLL is good optimization, if there is no vector branch. + // But if there is vectors branch (for x86*), this scalar code will be unused mostly. + // So we want smaller code (without unrolling) in that case (x86*). +#if 0 || 1 && !defined(Z7_BLAKE2S_USE_VECTORS) + #define Z7_BLAKE2S_UNROLL +#endif + +#ifdef Z7_BLAKE2S_UNROLL + ROUNDS_LOOP_UNROLLED (R) +#else + ROUNDS_LOOP (R) +#endif + + #undef G + #undef G2 + #undef R + #undef R2 + + // printf("\n v after: \n"); + // PrintStates2((const UInt32 *)v, 1, 16); +#define XOR_s_PAIR_v(i) s[i] ^= v[i] ^ v[i + 8]; + + LOOP_8(XOR_s_PAIR_v) + // printf("\n s after:\n"); + // PrintStates2((const UInt32 *)s, 1, 16); } -/* ---------- BLAKE2s ---------- */ +static +Z7_NO_INLINE +void +Z7_FASTCALL +Blake2sp_Compress2(UInt32 *s_items, const Byte *data, const Byte *end) +{ + size_t pos = 0; + // PrintStates2(s_items, 8, 16); + do + { + UInt32 * const s = GET_STATE_TABLE_PTR_FROM_BYTE_POS(s_items, pos); + Blake2s_Compress(s, data); + data += Z7_BLAKE2S_BLOCK_SIZE; + pos += Z7_BLAKE2S_BLOCK_SIZE; + pos &= SUPER_BLOCK_MASK; + } + while (data != end); +} + -/* we need to xor CBlake2s::h[i] with input parameter block after Blake2s_Init0() */ +#ifdef Z7_BLAKE2S_USE_VECTORS + +static Z7_BLAKE2SP_FUNC_COMPRESS g_Z7_BLAKE2SP_FUNC_COMPRESS_Fast = Blake2sp_Compress2; +static Z7_BLAKE2SP_FUNC_COMPRESS g_Z7_BLAKE2SP_FUNC_COMPRESS_Single = Blake2sp_Compress2; +static Z7_BLAKE2SP_FUNC_INIT g_Z7_BLAKE2SP_FUNC_INIT_Init; +static Z7_BLAKE2SP_FUNC_INIT g_Z7_BLAKE2SP_FUNC_INIT_Final; +static unsigned g_z7_Blake2sp_SupportedFlags; + + #define Z7_BLAKE2SP_Compress_Fast(p) (p)->u.header.func_Compress_Fast + #define Z7_BLAKE2SP_Compress_Single(p) (p)->u.header.func_Compress_Single +#else + #define Z7_BLAKE2SP_Compress_Fast(p) Blake2sp_Compress2 + #define Z7_BLAKE2SP_Compress_Single(p) Blake2sp_Compress2 +#endif // Z7_BLAKE2S_USE_VECTORS + + +#if 1 && defined(MY_CPU_LE) + #define GET_DIGEST(_s, _digest) \ + { memcpy(_digest, _s, Z7_BLAKE2S_DIGEST_SIZE); } +#else + #define GET_DIGEST(_s, _digest) \ + { unsigned _i; for (_i = 0; _i < 8; _i++) \ + { SetUi32((_digest) + 4 * _i, (_s)[_i]) } \ + } +#endif + + +/* ---------- BLAKE2s ---------- */ /* +// we need to xor CBlake2s::h[i] with input parameter block after Blake2s_Init0() typedef struct { Byte digest_length; Byte key_length; - Byte fanout; - Byte depth; + Byte fanout; // = 1 : in sequential mode + Byte depth; // = 1 : in sequential mode UInt32 leaf_length; - Byte node_offset[6]; - Byte node_depth; - Byte inner_length; + Byte node_offset[6]; // 0 for the first, leftmost, leaf, or in sequential mode + Byte node_depth; // 0 for the leaves, or in sequential mode + Byte inner_length; // [0, 32], 0 in sequential mode Byte salt[BLAKE2S_SALTBYTES]; Byte personal[BLAKE2S_PERSONALBYTES]; } CBlake2sParam; */ +#define k_Blake2sp_IV_0 \ + (KIV(0) ^ (Z7_BLAKE2S_DIGEST_SIZE | ((UInt32)Z7_BLAKE2SP_PARALLEL_DEGREE << 16) | ((UInt32)2 << 24))) +#define k_Blake2sp_IV_3_FROM_NODE_DEPTH(node_depth) \ + (KIV(3) ^ ((UInt32)(node_depth) << 16) ^ ((UInt32)Z7_BLAKE2S_DIGEST_SIZE << 24)) -static void Blake2sp_Init_Spec(CBlake2s *p, unsigned node_offset, unsigned node_depth) +Z7_FORCE_INLINE +static void Blake2sp_Init_Spec(UInt32 *s, unsigned node_offset, unsigned node_depth) { - Blake2s_Init0(p); - - p->h[0] ^= (BLAKE2S_DIGEST_SIZE | ((UInt32)BLAKE2SP_PARALLEL_DEGREE << 16) | ((UInt32)2 << 24)); - p->h[2] ^= ((UInt32)node_offset); - p->h[3] ^= ((UInt32)node_depth << 16) | ((UInt32)BLAKE2S_DIGEST_SIZE << 24); - /* - P->digest_length = BLAKE2S_DIGEST_SIZE; - P->key_length = 0; - P->fanout = BLAKE2SP_PARALLEL_DEGREE; - P->depth = 2; - P->leaf_length = 0; - store48(P->node_offset, node_offset); - P->node_depth = node_depth; - P->inner_length = BLAKE2S_DIGEST_SIZE; - */ + s[0] = k_Blake2sp_IV_0; + s[1] = KIV(1); + s[2] = KIV(2) ^ (UInt32)node_offset; + s[3] = k_Blake2sp_IV_3_FROM_NODE_DEPTH(node_depth); + s[4] = KIV(4); + s[5] = KIV(5); + s[6] = KIV(6); + s[7] = KIV(7); + + STATE_T(s)[0] = 0; + STATE_T(s)[1] = 0; + STATE_F(s)[0] = 0; + STATE_F(s)[1] = 0; +} + + +#ifdef Z7_BLAKE2S_USE_V128_FAST + +static +Z7_NO_INLINE +#ifdef BLAKE2S_ATTRIB_128BIT + BLAKE2S_ATTRIB_128BIT +#endif +void +Z7_FASTCALL +Blake2sp_InitState_V128_Fast(UInt32 *states) +{ +#define STORE_128_PAIR_INIT_STATES_2(i, t0, t1) \ + { STORE_128_TO_STRUCT(states + 0 + 4 * (i), (t0)); \ + STORE_128_TO_STRUCT(states + 32 + 4 * (i), (t1)); \ + } +#define STORE_128_PAIR_INIT_STATES_1(i, mac) \ + { const __m128i t = mac; \ + STORE_128_PAIR_INIT_STATES_2(i, t, t) \ + } +#define STORE_128_PAIR_INIT_STATES_IV(i) \ + STORE_128_PAIR_INIT_STATES_1(i, GET_128_IV_WAY4(i)) + + STORE_128_PAIR_INIT_STATES_1 (0, _mm_set1_epi32((Int32)k_Blake2sp_IV_0)) + STORE_128_PAIR_INIT_STATES_IV (1) + { + const __m128i t = GET_128_IV_WAY4(2); + STORE_128_PAIR_INIT_STATES_2 (2, + XOR_128(t, _mm_set_epi32(3, 2, 1, 0)), + XOR_128(t, _mm_set_epi32(7, 6, 5, 4))) + } + STORE_128_PAIR_INIT_STATES_1 (3, _mm_set1_epi32((Int32)k_Blake2sp_IV_3_FROM_NODE_DEPTH(0))) + STORE_128_PAIR_INIT_STATES_IV (4) + STORE_128_PAIR_INIT_STATES_IV (5) + STORE_128_PAIR_INIT_STATES_IV (6) + STORE_128_PAIR_INIT_STATES_IV (7) + STORE_128_PAIR_INIT_STATES_1 (16, _mm_set_epi32(0, 0, 0, 0)) + // printf("\n== exit Blake2sp_InitState_V128_Fast ctr=%d\n", states[64]); +} + +#endif // Z7_BLAKE2S_USE_V128_FAST + + +#ifdef Z7_BLAKE2S_USE_AVX2_FAST + +static +Z7_NO_INLINE +#ifdef BLAKE2S_ATTRIB_AVX2 + BLAKE2S_ATTRIB_AVX2 +#endif +void +Z7_FASTCALL +Blake2sp_InitState_AVX2_Fast(UInt32 *states) +{ +#define STORE_256_INIT_STATES(i, t) \ + STORE_256_TO_STRUCT(states + 8 * (i), t); +#define STORE_256_INIT_STATES_IV(i) \ + STORE_256_INIT_STATES(i, GET_256_IV_WAY8(i)) + + STORE_256_INIT_STATES (0, _mm256_set1_epi32((Int32)k_Blake2sp_IV_0)) + STORE_256_INIT_STATES_IV (1) + STORE_256_INIT_STATES (2, XOR_256( GET_256_IV_WAY8(2), + _mm256_set_epi32(7, 6, 5, 4, 3, 2, 1, 0))) + STORE_256_INIT_STATES (3, _mm256_set1_epi32((Int32)k_Blake2sp_IV_3_FROM_NODE_DEPTH(0))) + STORE_256_INIT_STATES_IV (4) + STORE_256_INIT_STATES_IV (5) + STORE_256_INIT_STATES_IV (6) + STORE_256_INIT_STATES_IV (7) + STORE_256_INIT_STATES (8, _mm256_set_epi32(0, 0, 0, 0, 0, 0, 0, 0)) + // printf("\n== exit Blake2sp_InitState_AVX2_Fast\n"); } +#endif // Z7_BLAKE2S_USE_AVX2_FAST + + + +Z7_NO_INLINE +void Blake2sp_InitState(CBlake2sp *p) +{ + size_t i; + // memset(p->states, 0, sizeof(p->states)); // for debug + p->u.header.cycPos = 0; +#ifdef Z7_BLAKE2SP_USE_FUNCTIONS + if (p->u.header.func_Init) + { + p->u.header.func_Init(p->states); + return; + } +#endif + for (i = 0; i < Z7_BLAKE2SP_PARALLEL_DEGREE; i++) + Blake2sp_Init_Spec(p->states + i * NSW, (unsigned)i, 0); +} void Blake2sp_Init(CBlake2sp *p) { - unsigned i; - - p->bufPos = 0; +#ifdef Z7_BLAKE2SP_USE_FUNCTIONS + p->u.header.func_Compress_Fast = +#ifdef Z7_BLAKE2S_USE_VECTORS + g_Z7_BLAKE2SP_FUNC_COMPRESS_Fast; +#else + NULL; +#endif + + p->u.header.func_Compress_Single = +#ifdef Z7_BLAKE2S_USE_VECTORS + g_Z7_BLAKE2SP_FUNC_COMPRESS_Single; +#else + NULL; +#endif - for (i = 0; i < BLAKE2SP_PARALLEL_DEGREE; i++) - Blake2sp_Init_Spec(&p->S[i], i, 0); + p->u.header.func_Init = +#ifdef Z7_BLAKE2S_USE_VECTORS + g_Z7_BLAKE2SP_FUNC_INIT_Init; +#else + NULL; +#endif - p->S[BLAKE2SP_PARALLEL_DEGREE - 1].lastNode_f1 = BLAKE2S_FINAL_FLAG; + p->u.header.func_Final = +#ifdef Z7_BLAKE2S_USE_VECTORS + g_Z7_BLAKE2SP_FUNC_INIT_Final; +#else + NULL; +#endif +#endif + + Blake2sp_InitState(p); } void Blake2sp_Update(CBlake2sp *p, const Byte *data, size_t size) { - unsigned pos = p->bufPos; - while (size != 0) + size_t pos; + // printf("\nsize = 0x%6x, cycPos = %5u data = %p\n", (unsigned)size, (unsigned)p->u.header.cycPos, data); + if (size == 0) + return; + pos = p->u.header.cycPos; + // pos < SUPER_BLOCK_SIZE * 2 : is expected + // pos == SUPER_BLOCK_SIZE * 2 : is not expected, but is supported also + { + const size_t pos2 = pos & SUPER_BLOCK_MASK; + if (pos2) + { + const size_t rem = SUPER_BLOCK_SIZE - pos2; + if (rem > size) + { + p->u.header.cycPos = (unsigned)(pos + size); + // cycPos < SUPER_BLOCK_SIZE * 2 + memcpy((Byte *)(void *)p->buf32 + pos, data, size); + /* to simpilify the code here we don't try to process first superblock, + if (cycPos > SUPER_BLOCK_SIZE * 2 - Z7_BLAKE2S_BLOCK_SIZE) */ + return; + } + // (rem <= size) + memcpy((Byte *)(void *)p->buf32 + pos, data, rem); + pos += rem; + data += rem; + size -= rem; + } + } + + // pos <= SUPER_BLOCK_SIZE * 2 + // pos % SUPER_BLOCK_SIZE == 0 + if (pos) + { + /* pos == SUPER_BLOCK_SIZE || + pos == SUPER_BLOCK_SIZE * 2 */ + size_t end = pos; + if (size > SUPER_BLOCK_SIZE - Z7_BLAKE2S_BLOCK_SIZE + || (end -= SUPER_BLOCK_SIZE)) + { + Z7_BLAKE2SP_Compress_Fast(p)(p->states, + (const Byte *)(const void *)p->buf32, + (const Byte *)(const void *)p->buf32 + end); + if (pos -= end) + memcpy(p->buf32, (const Byte *)(const void *)p->buf32 + + SUPER_BLOCK_SIZE, SUPER_BLOCK_SIZE); + } + } + + // pos == 0 || (pos == SUPER_BLOCK_SIZE && size <= SUPER_BLOCK_SIZE - Z7_BLAKE2S_BLOCK_SIZE) + if (size > SUPER_BLOCK_SIZE * 2 - Z7_BLAKE2S_BLOCK_SIZE) + { + // pos == 0 + const Byte *end; + const size_t size2 = (size - (SUPER_BLOCK_SIZE - Z7_BLAKE2S_BLOCK_SIZE + 1)) + & ~(size_t)SUPER_BLOCK_MASK; + size -= size2; + // size < SUPER_BLOCK_SIZE * 2 + end = data + size2; + Z7_BLAKE2SP_Compress_Fast(p)(p->states, data, end); + data = end; + } + + if (size != 0) { - unsigned index = pos / BLAKE2S_BLOCK_SIZE; - unsigned rem = BLAKE2S_BLOCK_SIZE - (pos & (BLAKE2S_BLOCK_SIZE - 1)); - if (rem > size) - rem = (unsigned)size; - Blake2s_Update(&p->S[index], data, rem); - size -= rem; - data += rem; - pos += rem; - pos &= (BLAKE2S_BLOCK_SIZE * BLAKE2SP_PARALLEL_DEGREE - 1); + memcpy((Byte *)(void *)p->buf32 + pos, data, size); + pos += size; } - p->bufPos = pos; + p->u.header.cycPos = (unsigned)pos; + // cycPos < SUPER_BLOCK_SIZE * 2 } void Blake2sp_Final(CBlake2sp *p, Byte *digest) { - CBlake2s R; - unsigned i; + // UInt32 * const R_states = p->states; + // printf("\nBlake2sp_Final \n"); +#ifdef Z7_BLAKE2SP_USE_FUNCTIONS + if (p->u.header.func_Final) + p->u.header.func_Final(p->states); +#endif + // printf("\n=====\nBlake2sp_Final \n"); + // PrintStates(p->states, 32); + + // (p->u.header.cycPos == SUPER_BLOCK_SIZE) can be processed in any branch: + if (p->u.header.cycPos <= SUPER_BLOCK_SIZE) + { + unsigned pos; + memset((Byte *)(void *)p->buf32 + p->u.header.cycPos, + 0, SUPER_BLOCK_SIZE - p->u.header.cycPos); + STATE_F(&p->states[(Z7_BLAKE2SP_PARALLEL_DEGREE - 1) * NSW])[1] = BLAKE2S_FINAL_FLAG; + for (pos = 0; pos < SUPER_BLOCK_SIZE; pos += Z7_BLAKE2S_BLOCK_SIZE) + { + UInt32 * const s = GET_STATE_TABLE_PTR_FROM_BYTE_POS(p->states, pos); + Blake2s_Set_LastBlock(s) + if (pos + Z7_BLAKE2S_BLOCK_SIZE > p->u.header.cycPos) + { + UInt32 delta = Z7_BLAKE2S_BLOCK_SIZE; + if (pos < p->u.header.cycPos) + delta -= p->u.header.cycPos & (Z7_BLAKE2S_BLOCK_SIZE - 1); + // 0 < delta <= Z7_BLAKE2S_BLOCK_SIZE + { + const UInt32 v = STATE_T(s)[0]; + STATE_T(s)[1] -= v < delta; // (v < delta) is same condition here as (v == 0) + STATE_T(s)[0] = v - delta; + } + } + } + // PrintStates(p->states, 16); + Z7_BLAKE2SP_Compress_Single(p)(p->states, + (Byte *)(void *)p->buf32, + (Byte *)(void *)p->buf32 + SUPER_BLOCK_SIZE); + // PrintStates(p->states, 16); + } + else + { + // (p->u.header.cycPos > SUPER_BLOCK_SIZE) + unsigned pos; + for (pos = 0; pos < SUPER_BLOCK_SIZE; pos += Z7_BLAKE2S_BLOCK_SIZE) + { + UInt32 * const s = GET_STATE_TABLE_PTR_FROM_BYTE_POS(p->states, pos); + if (pos + SUPER_BLOCK_SIZE >= p->u.header.cycPos) + Blake2s_Set_LastBlock(s) + } + if (p->u.header.cycPos <= SUPER_BLOCK_SIZE * 2 - Z7_BLAKE2S_BLOCK_SIZE) + STATE_F(&p->states[(Z7_BLAKE2SP_PARALLEL_DEGREE - 1) * NSW])[1] = BLAKE2S_FINAL_FLAG; + + Z7_BLAKE2SP_Compress_Single(p)(p->states, + (Byte *)(void *)p->buf32, + (Byte *)(void *)p->buf32 + SUPER_BLOCK_SIZE); + + // if (p->u.header.cycPos > SUPER_BLOCK_SIZE * 2 - Z7_BLAKE2S_BLOCK_SIZE; + STATE_F(&p->states[(Z7_BLAKE2SP_PARALLEL_DEGREE - 1) * NSW])[1] = BLAKE2S_FINAL_FLAG; + + // if (p->u.header.cycPos != SUPER_BLOCK_SIZE) + { + pos = SUPER_BLOCK_SIZE; + for (;;) + { + UInt32 * const s = GET_STATE_TABLE_PTR_FROM_BYTE_POS(p->states, pos & SUPER_BLOCK_MASK); + Blake2s_Set_LastBlock(s) + pos += Z7_BLAKE2S_BLOCK_SIZE; + if (pos >= p->u.header.cycPos) + { + if (pos != p->u.header.cycPos) + { + const UInt32 delta = pos - p->u.header.cycPos; + const UInt32 v = STATE_T(s)[0]; + STATE_T(s)[1] -= v < delta; + STATE_T(s)[0] = v - delta; + memset((Byte *)(void *)p->buf32 + p->u.header.cycPos, 0, delta); + } + break; + } + } + Z7_BLAKE2SP_Compress_Single(p)(p->states, + (Byte *)(void *)p->buf32 + SUPER_BLOCK_SIZE, + (Byte *)(void *)p->buf32 + pos); + } + } + + { + size_t pos; + for (pos = 0; pos < SUPER_BLOCK_SIZE / 2; pos += Z7_BLAKE2S_BLOCK_SIZE / 2) + { + const UInt32 * const s = GET_STATE_TABLE_PTR_FROM_BYTE_POS(p->states, (pos * 2)); + Byte *dest = (Byte *)(void *)p->buf32 + pos; + GET_DIGEST(s, dest) + } + } + Blake2sp_Init_Spec(p->states, 0, 1); + { + size_t pos; + for (pos = 0; pos < (Z7_BLAKE2SP_PARALLEL_DEGREE * Z7_BLAKE2S_DIGEST_SIZE) + - Z7_BLAKE2S_BLOCK_SIZE; pos += Z7_BLAKE2S_BLOCK_SIZE) + { + Z7_BLAKE2SP_Compress_Single(p)(p->states, + (const Byte *)(const void *)p->buf32 + pos, + (const Byte *)(const void *)p->buf32 + pos + Z7_BLAKE2S_BLOCK_SIZE); + } + } + // Blake2s_Final(p->states, 0, digest, p, (Byte *)(void *)p->buf32 + i); + Blake2s_Set_LastBlock(p->states) + STATE_F(p->states)[1] = BLAKE2S_FINAL_FLAG; + { + Z7_BLAKE2SP_Compress_Single(p)(p->states, + (const Byte *)(const void *)p->buf32 + Z7_BLAKE2SP_PARALLEL_DEGREE / 2 * Z7_BLAKE2S_BLOCK_SIZE - Z7_BLAKE2S_BLOCK_SIZE, + (const Byte *)(const void *)p->buf32 + Z7_BLAKE2SP_PARALLEL_DEGREE / 2 * Z7_BLAKE2S_BLOCK_SIZE); + } + GET_DIGEST(p->states, digest) + // printf("\n Blake2sp_Final 555 numDataInBufs = %5u\n", (unsigned)p->u.header.numDataInBufs); +} + + +BoolInt Blake2sp_SetFunction(CBlake2sp *p, unsigned algo) +{ + // printf("\n========== setfunction = %d ======== \n", algo); +#ifdef Z7_BLAKE2SP_USE_FUNCTIONS + Z7_BLAKE2SP_FUNC_COMPRESS func = NULL; + Z7_BLAKE2SP_FUNC_COMPRESS func_Single = NULL; + Z7_BLAKE2SP_FUNC_INIT func_Final = NULL; + Z7_BLAKE2SP_FUNC_INIT func_Init = NULL; +#else + UNUSED_VAR(p) +#endif + +#ifdef Z7_BLAKE2S_USE_VECTORS - Blake2sp_Init_Spec(&R, 0, 1); - R.lastNode_f1 = BLAKE2S_FINAL_FLAG; + func = func_Single = Blake2sp_Compress2; - for (i = 0; i < BLAKE2SP_PARALLEL_DEGREE; i++) + if (algo != Z7_BLAKE2SP_ALGO_SCALAR) { - Byte hash[BLAKE2S_DIGEST_SIZE]; - Blake2s_Final(&p->S[i], hash); - Blake2s_Update(&R, hash, BLAKE2S_DIGEST_SIZE); + // printf("\n========== setfunction NON-SCALER ======== \n"); + if (algo == Z7_BLAKE2SP_ALGO_DEFAULT) + { + func = g_Z7_BLAKE2SP_FUNC_COMPRESS_Fast; + func_Single = g_Z7_BLAKE2SP_FUNC_COMPRESS_Single; + func_Init = g_Z7_BLAKE2SP_FUNC_INIT_Init; + func_Final = g_Z7_BLAKE2SP_FUNC_INIT_Final; + } + else + { + if ((g_z7_Blake2sp_SupportedFlags & (1u << algo)) == 0) + return False; + +#ifdef Z7_BLAKE2S_USE_AVX2 + + func_Single = +#if defined(Z7_BLAKE2S_USE_AVX2_WAY2) + Blake2sp_Compress2_AVX2_Way2; +#else + Z7_BLAKE2S_Compress2_V128; +#endif + +#ifdef Z7_BLAKE2S_USE_AVX2_FAST + if (algo == Z7_BLAKE2SP_ALGO_V256_FAST) + { + func = Blake2sp_Compress2_AVX2_Fast; + func_Final = Blake2sp_Final_AVX2_Fast; + func_Init = Blake2sp_InitState_AVX2_Fast; + } + else +#endif +#ifdef Z7_BLAKE2S_USE_AVX2_WAY2 + if (algo == Z7_BLAKE2SP_ALGO_V256_WAY2) + func = Blake2sp_Compress2_AVX2_Way2; + else +#endif +#ifdef Z7_BLAKE2S_USE_AVX2_WAY4 + if (algo == Z7_BLAKE2SP_ALGO_V256_WAY4) + { + func_Single = func = Blake2sp_Compress2_AVX2_Way4; + } + else +#endif +#endif // avx2 + { + if (algo == Z7_BLAKE2SP_ALGO_V128_FAST) + { + func = Blake2sp_Compress2_V128_Fast; + func_Final = Blake2sp_Final_V128_Fast; + func_Init = Blake2sp_InitState_V128_Fast; + func_Single = Z7_BLAKE2S_Compress2_V128; + } + else +#ifdef Z7_BLAKE2S_USE_V128_WAY2 + if (algo == Z7_BLAKE2SP_ALGO_V128_WAY2) + func = func_Single = Blake2sp_Compress2_V128_Way2; + else +#endif + { + if (algo != Z7_BLAKE2SP_ALGO_V128_WAY1) + return False; + func = func_Single = Blake2sp_Compress2_V128_Way1; + } + } + } } +#else // !VECTORS + if (algo > 1) // Z7_BLAKE2SP_ALGO_SCALAR + return False; +#endif // !VECTORS - Blake2s_Final(&R, digest); +#ifdef Z7_BLAKE2SP_USE_FUNCTIONS + p->u.header.func_Compress_Fast = func; + p->u.header.func_Compress_Single = func_Single; + p->u.header.func_Final = func_Final; + p->u.header.func_Init = func_Init; +#endif + // printf("\n p->u.header.func_Compress = %p", p->u.header.func_Compress); + return True; } + + +void z7_Black2sp_Prepare(void) +{ +#ifdef Z7_BLAKE2S_USE_VECTORS + unsigned flags = 0; // (1u << Z7_BLAKE2SP_ALGO_V128_SCALAR); + + Z7_BLAKE2SP_FUNC_COMPRESS func_Fast = Blake2sp_Compress2; + Z7_BLAKE2SP_FUNC_COMPRESS func_Single = Blake2sp_Compress2; + Z7_BLAKE2SP_FUNC_INIT func_Init = NULL; + Z7_BLAKE2SP_FUNC_INIT func_Final = NULL; + +#if defined(MY_CPU_X86_OR_AMD64) + #if defined(Z7_BLAKE2S_USE_AVX512_ALWAYS) + // optional check + #if 0 || !(defined(__AVX512F__) && defined(__AVX512VL__)) + if (CPU_IsSupported_AVX512F_AVX512VL()) + #endif + #elif defined(Z7_BLAKE2S_USE_SSE41) + if (CPU_IsSupported_SSE41()) + #elif defined(Z7_BLAKE2S_USE_SSSE3) + if (CPU_IsSupported_SSSE3()) + #elif !defined(MY_CPU_AMD64) + if (CPU_IsSupported_SSE2()) + #endif +#endif + { + #if defined(Z7_BLAKE2S_USE_SSE41) + // printf("\n========== Blake2s SSE41 128-bit\n"); + #elif defined(Z7_BLAKE2S_USE_SSSE3) + // printf("\n========== Blake2s SSSE3 128-bit\n"); + #else + // printf("\n========== Blake2s SSE2 128-bit\n"); + #endif + // func_Fast = f_vector = Blake2sp_Compress2_V128_Way2; + // printf("\n========== Blake2sp_Compress2_V128_Way2\n"); + func_Fast = + func_Single = Z7_BLAKE2S_Compress2_V128; + flags |= (1u << Z7_BLAKE2SP_ALGO_V128_WAY1); +#ifdef Z7_BLAKE2S_USE_V128_WAY2 + flags |= (1u << Z7_BLAKE2SP_ALGO_V128_WAY2); +#endif +#ifdef Z7_BLAKE2S_USE_V128_FAST + flags |= (1u << Z7_BLAKE2SP_ALGO_V128_FAST); + func_Fast = Blake2sp_Compress2_V128_Fast; + func_Init = Blake2sp_InitState_V128_Fast; + func_Final = Blake2sp_Final_V128_Fast; +#endif + +#ifdef Z7_BLAKE2S_USE_AVX2 +#if defined(MY_CPU_X86_OR_AMD64) + + #if defined(Z7_BLAKE2S_USE_AVX512_ALWAYS) + #if 0 + if (CPU_IsSupported_AVX512F_AVX512VL()) + #endif + #else + if (CPU_IsSupported_AVX2()) + #endif +#endif + { + // #pragma message ("=== Blake2s AVX2") + // printf("\n========== Blake2s AVX2\n"); + +#ifdef Z7_BLAKE2S_USE_AVX2_WAY2 + func_Single = Blake2sp_Compress2_AVX2_Way2; + flags |= (1u << Z7_BLAKE2SP_ALGO_V256_WAY2); +#endif +#ifdef Z7_BLAKE2S_USE_AVX2_WAY4 + flags |= (1u << Z7_BLAKE2SP_ALGO_V256_WAY4); +#endif + +#ifdef Z7_BLAKE2S_USE_AVX2_FAST + flags |= (1u << Z7_BLAKE2SP_ALGO_V256_FAST); + func_Fast = Blake2sp_Compress2_AVX2_Fast; + func_Init = Blake2sp_InitState_AVX2_Fast; + func_Final = Blake2sp_Final_AVX2_Fast; +#elif defined(Z7_BLAKE2S_USE_AVX2_WAY4) + func_Fast = Blake2sp_Compress2_AVX2_Way4; +#elif defined(Z7_BLAKE2S_USE_AVX2_WAY2) + func_Fast = Blake2sp_Compress2_AVX2_Way2; +#endif + } // avx2 +#endif // avx2 + } // sse* + g_Z7_BLAKE2SP_FUNC_COMPRESS_Fast = func_Fast; + g_Z7_BLAKE2SP_FUNC_COMPRESS_Single = func_Single; + g_Z7_BLAKE2SP_FUNC_INIT_Init = func_Init; + g_Z7_BLAKE2SP_FUNC_INIT_Final = func_Final; + g_z7_Blake2sp_SupportedFlags = flags; + // printf("\nflags=%x\n", flags); +#endif // vectors +} + +/* +#ifdef Z7_BLAKE2S_USE_VECTORS +void align_test2(CBlake2sp *sp); +void align_test2(CBlake2sp *sp) +{ + __m128i a = LOAD_128(sp->states); + D_XOR_128(a, LOAD_128(sp->states + 4)); + STORE_128(sp->states, a); +} +void align_test2(void); +void align_test2(void) +{ + CBlake2sp sp; + Blake2sp_Init(&sp); + Blake2sp_Update(&sp, NULL, 0); +} +#endif +*/ diff --git a/src/plugins/7zip/7za/c/Bra.c b/src/plugins/7zip/7za/c/Bra.c index cdb94569..e61edf8f 100644 --- a/src/plugins/7zip/7za/c/Bra.c +++ b/src/plugins/7zip/7za/c/Bra.c @@ -1,135 +1,709 @@ -/* Bra.c -- Converters for RISC code -2010-04-16 : Igor Pavlov : Public domain */ +/* Bra.c -- Branch converters for RISC code +2024-01-20 : Igor Pavlov : Public domain */ #include "Precomp.h" #include "Bra.h" +#include "RotateDefs.h" +#include "CpuArch.h" -SizeT ARM_Convert(Byte *data, SizeT size, UInt32 ip, int encoding) +#if defined(MY_CPU_SIZEOF_POINTER) \ + && ( MY_CPU_SIZEOF_POINTER == 4 \ + || MY_CPU_SIZEOF_POINTER == 8) + #define BR_CONV_USE_OPT_PC_PTR +#endif + +#ifdef BR_CONV_USE_OPT_PC_PTR +#define BR_PC_INIT pc -= (UInt32)(SizeT)p; +#define BR_PC_GET (pc + (UInt32)(SizeT)p) +#else +#define BR_PC_INIT pc += (UInt32)size; +#define BR_PC_GET (pc - (UInt32)(SizeT)(lim - p)) +// #define BR_PC_INIT +// #define BR_PC_GET (pc + (UInt32)(SizeT)(p - data)) +#endif + +#define BR_CONVERT_VAL(v, c) if (encoding) v += c; else v -= c; +// #define BR_CONVERT_VAL(v, c) if (!encoding) c = (UInt32)0 - c; v += c; + +#define Z7_BRANCH_CONV(name) z7_ ## name + +#define Z7_BRANCH_FUNC_MAIN(name) \ +static \ +Z7_FORCE_INLINE \ +Z7_ATTRIB_NO_VECTOR \ +Byte *Z7_BRANCH_CONV(name)(Byte *p, SizeT size, UInt32 pc, int encoding) + +#define Z7_BRANCH_FUNC_IMP(name, m, encoding) \ +Z7_NO_INLINE \ +Z7_ATTRIB_NO_VECTOR \ +Byte *m(name)(Byte *data, SizeT size, UInt32 pc) \ + { return Z7_BRANCH_CONV(name)(data, size, pc, encoding); } \ + +#ifdef Z7_EXTRACT_ONLY +#define Z7_BRANCH_FUNCS_IMP(name) \ + Z7_BRANCH_FUNC_IMP(name, Z7_BRANCH_CONV_DEC_2, 0) +#else +#define Z7_BRANCH_FUNCS_IMP(name) \ + Z7_BRANCH_FUNC_IMP(name, Z7_BRANCH_CONV_DEC_2, 0) \ + Z7_BRANCH_FUNC_IMP(name, Z7_BRANCH_CONV_ENC_2, 1) +#endif + +#if defined(__clang__) +#define BR_EXTERNAL_FOR +#define BR_NEXT_ITERATION continue; +#else +#define BR_EXTERNAL_FOR for (;;) +#define BR_NEXT_ITERATION break; +#endif + +#if defined(__clang__) && (__clang_major__ >= 8) \ + || defined(__GNUC__) && (__GNUC__ >= 1000) \ + // GCC is not good for __builtin_expect() here + /* || defined(_MSC_VER) && (_MSC_VER >= 1920) */ + // #define Z7_unlikely [[unlikely]] + // #define Z7_LIKELY(x) (__builtin_expect((x), 1)) + #define Z7_UNLIKELY(x) (__builtin_expect((x), 0)) + // #define Z7_likely [[likely]] +#else + // #define Z7_LIKELY(x) (x) + #define Z7_UNLIKELY(x) (x) + // #define Z7_likely +#endif + + +Z7_BRANCH_FUNC_MAIN(BranchConv_ARM64) { - SizeT i; - if (size < 4) - return 0; - size -= 4; - ip += 8; - for (i = 0; i <= size; i += 4) + // Byte *p = data; + const Byte *lim; + const UInt32 flag = (UInt32)1 << (24 - 4); + const UInt32 mask = ((UInt32)1 << 24) - (flag << 1); + size &= ~(SizeT)3; + // if (size == 0) return p; + lim = p + size; + BR_PC_INIT + pc -= 4; // because (p) will point to next instruction + + BR_EXTERNAL_FOR { - if (data[i + 3] == 0xEB) + // Z7_PRAGMA_OPT_DISABLE_LOOP_UNROLL_VECTORIZE + for (;;) { - UInt32 dest; - UInt32 src = ((UInt32)data[i + 2] << 16) | ((UInt32)data[i + 1] << 8) | (data[i + 0]); - src <<= 2; - if (encoding) - dest = ip + (UInt32)i + src; - else - dest = src - (ip + (UInt32)i); - dest >>= 2; - data[i + 2] = (Byte)(dest >> 16); - data[i + 1] = (Byte)(dest >> 8); - data[i + 0] = (Byte)dest; + UInt32 v; + if Z7_UNLIKELY(p == lim) + return p; + v = GetUi32a(p); + p += 4; + if Z7_UNLIKELY(((v - 0x94000000) & 0xfc000000) == 0) + { + UInt32 c = BR_PC_GET >> 2; + BR_CONVERT_VAL(v, c) + v &= 0x03ffffff; + v |= 0x94000000; + SetUi32a(p - 4, v) + BR_NEXT_ITERATION + } + // v = rotlFixed(v, 8); v += (flag << 8) - 0x90; if Z7_UNLIKELY((v & ((mask << 8) + 0x9f)) == 0) + v -= 0x90000000; if Z7_UNLIKELY((v & 0x9f000000) == 0) + { + UInt32 z, c; + // v = rotrFixed(v, 8); + v += flag; if Z7_UNLIKELY(v & mask) continue; + z = (v & 0xffffffe0) | (v >> 26); + c = (BR_PC_GET >> (12 - 3)) & ~(UInt32)7; + BR_CONVERT_VAL(z, c) + v &= 0x1f; + v |= 0x90000000; + v |= z << 26; + v |= 0x00ffffe0 & ((z & (((flag << 1) - 1))) - flag); + SetUi32a(p - 4, v) + } } } - return i; } +Z7_BRANCH_FUNCS_IMP(BranchConv_ARM64) + -SizeT ARMT_Convert(Byte *data, SizeT size, UInt32 ip, int encoding) +Z7_BRANCH_FUNC_MAIN(BranchConv_ARM) { - SizeT i; - if (size < 4) - return 0; - size -= 4; - ip += 4; - for (i = 0; i <= size; i += 2) + // Byte *p = data; + const Byte *lim; + size &= ~(SizeT)3; + lim = p + size; + BR_PC_INIT + /* in ARM: branch offset is relative to the +2 instructions from current instruction. + (p) will point to next instruction */ + pc += 8 - 4; + + for (;;) { - if ((data[i + 1] & 0xF8) == 0xF0 && - (data[i + 3] & 0xF8) == 0xF8) - { - UInt32 dest; - UInt32 src = - (((UInt32)data[i + 1] & 0x7) << 19) | - ((UInt32)data[i + 0] << 11) | - (((UInt32)data[i + 3] & 0x7) << 8) | - (data[i + 2]); - - src <<= 1; - if (encoding) - dest = ip + (UInt32)i + src; - else - dest = src - (ip + (UInt32)i); - dest >>= 1; - - data[i + 1] = (Byte)(0xF0 | ((dest >> 19) & 0x7)); - data[i + 0] = (Byte)(dest >> 11); - data[i + 3] = (Byte)(0xF8 | ((dest >> 8) & 0x7)); - data[i + 2] = (Byte)dest; - i += 2; + for (;;) + { + if Z7_UNLIKELY(p >= lim) { return p; } p += 4; if Z7_UNLIKELY(p[-1] == 0xeb) break; + if Z7_UNLIKELY(p >= lim) { return p; } p += 4; if Z7_UNLIKELY(p[-1] == 0xeb) break; + } + { + UInt32 v = GetUi32a(p - 4); + UInt32 c = BR_PC_GET >> 2; + BR_CONVERT_VAL(v, c) + v &= 0x00ffffff; + v |= 0xeb000000; + SetUi32a(p - 4, v) } } - return i; } +Z7_BRANCH_FUNCS_IMP(BranchConv_ARM) + -SizeT PPC_Convert(Byte *data, SizeT size, UInt32 ip, int encoding) +Z7_BRANCH_FUNC_MAIN(BranchConv_PPC) { - SizeT i; - if (size < 4) - return 0; - size -= 4; - for (i = 0; i <= size; i += 4) + // Byte *p = data; + const Byte *lim; + size &= ~(SizeT)3; + lim = p + size; + BR_PC_INIT + pc -= 4; // because (p) will point to next instruction + + for (;;) { - if ((data[i] >> 2) == 0x12 && (data[i + 3] & 3) == 1) - { - UInt32 src = ((UInt32)(data[i + 0] & 3) << 24) | - ((UInt32)data[i + 1] << 16) | - ((UInt32)data[i + 2] << 8) | - ((UInt32)data[i + 3] & (~3)); - - UInt32 dest; - if (encoding) - dest = ip + (UInt32)i + src; - else - dest = src - (ip + (UInt32)i); - data[i + 0] = (Byte)(0x48 | ((dest >> 24) & 0x3)); - data[i + 1] = (Byte)(dest >> 16); - data[i + 2] = (Byte)(dest >> 8); - data[i + 3] &= 0x3; - data[i + 3] |= dest; + UInt32 v; + for (;;) + { + if Z7_UNLIKELY(p == lim) + return p; + // v = GetBe32a(p); + v = *(UInt32 *)(void *)p; + p += 4; + // if ((v & 0xfc000003) == 0x48000001) break; + // if ((p[-4] & 0xFC) == 0x48 && (p[-1] & 3) == 1) break; + if Z7_UNLIKELY( + ((v - Z7_CONV_BE_TO_NATIVE_CONST32(0x48000001)) + & Z7_CONV_BE_TO_NATIVE_CONST32(0xfc000003)) == 0) break; + } + { + v = Z7_CONV_NATIVE_TO_BE_32(v); + { + UInt32 c = BR_PC_GET; + BR_CONVERT_VAL(v, c) + } + v &= 0x03ffffff; + v |= 0x48000000; + SetBe32a(p - 4, v) } } - return i; } +Z7_BRANCH_FUNCS_IMP(BranchConv_PPC) + + +#ifdef Z7_CPU_FAST_ROTATE_SUPPORTED +#define BR_SPARC_USE_ROTATE +#endif -SizeT SPARC_Convert(Byte *data, SizeT size, UInt32 ip, int encoding) +Z7_BRANCH_FUNC_MAIN(BranchConv_SPARC) { - UInt32 i; - if (size < 4) - return 0; - size -= 4; - for (i = 0; i <= size; i += 4) + // Byte *p = data; + const Byte *lim; + const UInt32 flag = (UInt32)1 << 22; + size &= ~(SizeT)3; + lim = p + size; + BR_PC_INIT + pc -= 4; // because (p) will point to next instruction + for (;;) { - if ((data[i] == 0x40 && (data[i + 1] & 0xC0) == 0x00) || - (data[i] == 0x7F && (data[i + 1] & 0xC0) == 0xC0)) - { - UInt32 src = - ((UInt32)data[i + 0] << 24) | - ((UInt32)data[i + 1] << 16) | - ((UInt32)data[i + 2] << 8) | - ((UInt32)data[i + 3]); - UInt32 dest; - - src <<= 2; - if (encoding) - dest = ip + i + src; - else - dest = src - (ip + i); - dest >>= 2; - - dest = (((0 - ((dest >> 22) & 1)) << 22) & 0x3FFFFFFF) | (dest & 0x3FFFFF) | 0x40000000; - - data[i + 0] = (Byte)(dest >> 24); - data[i + 1] = (Byte)(dest >> 16); - data[i + 2] = (Byte)(dest >> 8); - data[i + 3] = (Byte)dest; + UInt32 v; + for (;;) + { + if Z7_UNLIKELY(p == lim) + return p; + /* // the code without GetBe32a(): + { const UInt32 v = GetUi16a(p) & 0xc0ff; p += 4; if (v == 0x40 || v == 0xc07f) break; } + */ + v = GetBe32a(p); + p += 4; + #ifdef BR_SPARC_USE_ROTATE + v = rotlFixed(v, 2); + v += (flag << 2) - 1; + if Z7_UNLIKELY((v & (3 - (flag << 3))) == 0) + #else + v += (UInt32)5 << 29; + v ^= (UInt32)7 << 29; + v += flag; + if Z7_UNLIKELY((v & (0 - (flag << 1))) == 0) + #endif + break; + } + { + // UInt32 v = GetBe32a(p - 4); + #ifndef BR_SPARC_USE_ROTATE + v <<= 2; + #endif + { + UInt32 c = BR_PC_GET; + BR_CONVERT_VAL(v, c) + } + v &= (flag << 3) - 1; + #ifdef BR_SPARC_USE_ROTATE + v -= (flag << 2) - 1; + v = rotrFixed(v, 2); + #else + v -= (flag << 2); + v >>= 2; + v |= (UInt32)1 << 30; + #endif + SetBe32a(p - 4, v) + } + } +} +Z7_BRANCH_FUNCS_IMP(BranchConv_SPARC) + + +Z7_BRANCH_FUNC_MAIN(BranchConv_ARMT) +{ + // Byte *p = data; + Byte *lim; + size &= ~(SizeT)1; + // if (size == 0) return p; + if (size <= 2) return p; + size -= 2; + lim = p + size; + BR_PC_INIT + /* in ARM: branch offset is relative to the +2 instructions from current instruction. + (p) will point to the +2 instructions from current instruction */ + // pc += 4 - 4; + // if (encoding) pc -= 0xf800 << 1; else pc += 0xf800 << 1; + // #define ARMT_TAIL_PROC { goto armt_tail; } + #define ARMT_TAIL_PROC { return p; } + + do + { + /* in MSVC 32-bit x86 compilers: + UInt32 version : it loads value from memory with movzx + Byte version : it loads value to 8-bit register (AL/CL) + movzx version is slightly faster in some cpus + */ + unsigned b1; + // Byte / unsigned + b1 = p[1]; + // optimized version to reduce one (p >= lim) check: + // unsigned a1 = p[1]; b1 = p[3]; p += 2; if Z7_LIKELY((b1 & (a1 ^ 8)) < 0xf8) + for (;;) + { + unsigned b3; // Byte / UInt32 + /* (Byte)(b3) normalization can use low byte computations in MSVC. + It gives smaller code, and no loss of speed in some compilers/cpus. + But new MSVC 32-bit x86 compilers use more slow load + from memory to low byte register in that case. + So we try to use full 32-bit computations for faster code. + */ + // if (p >= lim) { ARMT_TAIL_PROC } b3 = b1 + 8; b1 = p[3]; p += 2; if ((b3 & b1) >= 0xf8) break; + if Z7_UNLIKELY(p >= lim) { ARMT_TAIL_PROC } b3 = p[3]; p += 2; if Z7_UNLIKELY((b3 & (b1 ^ 8)) >= 0xf8) break; + if Z7_UNLIKELY(p >= lim) { ARMT_TAIL_PROC } b1 = p[3]; p += 2; if Z7_UNLIKELY((b1 & (b3 ^ 8)) >= 0xf8) break; + } + { + /* we can adjust pc for (0xf800) to rid of (& 0x7FF) operation. + But gcc/clang for arm64 can use bfi instruction for full code here */ + UInt32 v = + ((UInt32)GetUi16a(p - 2) << 11) | + ((UInt32)GetUi16a(p) & 0x7FF); + /* + UInt32 v = + ((UInt32)p[1 - 2] << 19) + + (((UInt32)p[1] & 0x7) << 8) + + (((UInt32)p[-2] << 11)) + + (p[0]); + */ + p += 2; + { + UInt32 c = BR_PC_GET >> 1; + BR_CONVERT_VAL(v, c) + } + SetUi16a(p - 4, (UInt16)(((v >> 11) & 0x7ff) | 0xf000)) + SetUi16a(p - 2, (UInt16)(v | 0xf800)) + /* + p[-4] = (Byte)(v >> 11); + p[-3] = (Byte)(0xf0 | ((v >> 19) & 0x7)); + p[-2] = (Byte)v; + p[-1] = (Byte)(0xf8 | (v >> 8)); + */ + } + } + while (p < lim); + return p; + // armt_tail: + // if ((Byte)((lim[1] & 0xf8)) != 0xf0) { lim += 2; } return lim; + // return (Byte *)(lim + ((Byte)((lim[1] ^ 0xf0) & 0xf8) == 0 ? 0 : 2)); + // return (Byte *)(lim + (((lim[1] ^ ~0xfu) & ~7u) == 0 ? 0 : 2)); + // return (Byte *)(lim + 2 - (((((unsigned)lim[1] ^ 8) + 8) >> 7) & 2)); +} +Z7_BRANCH_FUNCS_IMP(BranchConv_ARMT) + + +// #define BR_IA64_NO_INLINE + +Z7_BRANCH_FUNC_MAIN(BranchConv_IA64) +{ + // Byte *p = data; + const Byte *lim; + size &= ~(SizeT)15; + lim = p + size; + pc -= 1 << 4; + pc >>= 4 - 1; + // pc -= 1 << 1; + + for (;;) + { + unsigned m; + for (;;) + { + if Z7_UNLIKELY(p == lim) + return p; + m = (unsigned)((UInt32)0x334b0000 >> (*p & 0x1e)); + p += 16; + pc += 1 << 1; + if (m &= 3) + break; + } + { + p += (ptrdiff_t)m * 5 - 20; // negative value is expected here. + do + { + const UInt32 t = + #if defined(MY_CPU_X86_OR_AMD64) + // we use 32-bit load here to reduce code size on x86: + GetUi32(p); + #else + GetUi16(p); + #endif + UInt32 z = GetUi32(p + 1) >> m; + p += 5; + if (((t >> m) & (0x70 << 1)) == 0 + && ((z - (0x5000000 << 1)) & (0xf000000 << 1)) == 0) + { + UInt32 v = (UInt32)((0x8fffff << 1) | 1) & z; + z ^= v; + #ifdef BR_IA64_NO_INLINE + v |= (v & ((UInt32)1 << (23 + 1))) >> 3; + { + UInt32 c = pc; + BR_CONVERT_VAL(v, c) + } + v &= (0x1fffff << 1) | 1; + #else + { + if (encoding) + { + // pc &= ~(0xc00000 << 1); // we just need to clear at least 2 bits + pc &= (0x1fffff << 1) | 1; + v += pc; + } + else + { + // pc |= 0xc00000 << 1; // we need to set at least 2 bits + pc |= ~(UInt32)((0x1fffff << 1) | 1); + v -= pc; + } + } + v &= ~(UInt32)(0x600000 << 1); + #endif + v += (0x700000 << 1); + v &= (0x8fffff << 1) | 1; + z |= v; + z <<= m; + SetUi32(p + 1 - 5, z) + } + m++; + } + while (m &= 3); // while (m < 4); } } - return i; +} +Z7_BRANCH_FUNCS_IMP(BranchConv_IA64) + + +#define BR_CONVERT_VAL_ENC(v) v += BR_PC_GET; +#define BR_CONVERT_VAL_DEC(v) v -= BR_PC_GET; + +#if 1 && defined(MY_CPU_LE_UNALIGN) + #define RISCV_USE_UNALIGNED_LOAD +#endif + +#ifdef RISCV_USE_UNALIGNED_LOAD + #define RISCV_GET_UI32(p) GetUi32(p) + #define RISCV_SET_UI32(p, v) { SetUi32(p, v) } +#else + #define RISCV_GET_UI32(p) \ + ((UInt32)GetUi16a(p) + \ + ((UInt32)GetUi16a((p) + 2) << 16)) + #define RISCV_SET_UI32(p, v) { \ + SetUi16a(p, (UInt16)(v)) \ + SetUi16a((p) + 2, (UInt16)(v >> 16)) } +#endif + +#if 1 && defined(MY_CPU_LE) + #define RISCV_USE_16BIT_LOAD +#endif + +#ifdef RISCV_USE_16BIT_LOAD + #define RISCV_LOAD_VAL(p) GetUi16a(p) +#else + #define RISCV_LOAD_VAL(p) (*(p)) +#endif + +#define RISCV_INSTR_SIZE 2 +#define RISCV_STEP_1 (4 + RISCV_INSTR_SIZE) +#define RISCV_STEP_2 4 +#define RISCV_REG_VAL (2 << 7) +#define RISCV_CMD_VAL 3 +#if 1 + // for code size optimization: + #define RISCV_DELTA_7F 0x7f +#else + #define RISCV_DELTA_7F 0 +#endif + +#define RISCV_CHECK_1(v, b) \ + (((((b) - RISCV_CMD_VAL) ^ ((v) << 8)) & (0xf8000 + RISCV_CMD_VAL)) == 0) + +#if 1 + #define RISCV_CHECK_2(v, r) \ + ((((v) - ((RISCV_CMD_VAL << 12) | RISCV_REG_VAL | 8)) \ + << 18) \ + < ((r) & 0x1d)) +#else + // this branch gives larger code, because + // compilers generate larger code for big constants. + #define RISCV_CHECK_2(v, r) \ + ((((v) - ((RISCV_CMD_VAL << 12) | RISCV_REG_VAL)) \ + & ((RISCV_CMD_VAL << 12) | RISCV_REG_VAL)) \ + < ((r) & 0x1d)) +#endif + + +#define RISCV_SCAN_LOOP \ + Byte *lim; \ + size &= ~(SizeT)(RISCV_INSTR_SIZE - 1); \ + if (size <= 6) return p; \ + size -= 6; \ + lim = p + size; \ + BR_PC_INIT \ + for (;;) \ + { \ + UInt32 a, v; \ + /* Z7_PRAGMA_OPT_DISABLE_LOOP_UNROLL_VECTORIZE */ \ + for (;;) \ + { \ + if Z7_UNLIKELY(p >= lim) { return p; } \ + a = (RISCV_LOAD_VAL(p) ^ 0x10u) + 1; \ + if ((a & 0x77) == 0) break; \ + a = (RISCV_LOAD_VAL(p + RISCV_INSTR_SIZE) ^ 0x10u) + 1; \ + p += RISCV_INSTR_SIZE * 2; \ + if ((a & 0x77) == 0) \ + { \ + p -= RISCV_INSTR_SIZE; \ + if Z7_UNLIKELY(p >= lim) { return p; } \ + break; \ + } \ + } +// (xx6f ^ 10) + 1 = xx7f + 1 = xx80 : JAL +// (xxef ^ 10) + 1 = xxff + 1 = xx00 + 100 : JAL +// (xx17 ^ 10) + 1 = xx07 + 1 = xx08 : AUIPC +// (xx97 ^ 10) + 1 = xx87 + 1 = xx88 : AUIPC + +Byte * Z7_BRANCH_CONV_ENC(RISCV)(Byte *p, SizeT size, UInt32 pc) +{ + RISCV_SCAN_LOOP + v = a; + a = RISCV_GET_UI32(p); +#ifndef RISCV_USE_16BIT_LOAD + v += (UInt32)p[1] << 8; +#endif + + if ((v & 8) == 0) // JAL + { + if ((v - (0x100 /* - RISCV_DELTA_7F */)) & 0xd80) + { + p += RISCV_INSTR_SIZE; + continue; + } + { + v = ((a & 1u << 31) >> 11) + | ((a & 0x3ff << 21) >> 20) + | ((a & 1 << 20) >> 9) + | (a & 0xff << 12); + BR_CONVERT_VAL_ENC(v) + // ((v & 1) == 0) + // v: bits [1 : 20] contain offset bits +#if 0 && defined(RISCV_USE_UNALIGNED_LOAD) + a &= 0xfff; + a |= ((UInt32)(v << 23)) + | ((UInt32)(v << 7) & ((UInt32)0xff << 16)) + | ((UInt32)(v >> 5) & ((UInt32)0xf0 << 8)); + RISCV_SET_UI32(p, a) +#else // aligned +#if 0 + SetUi16a(p, (UInt16)(((v >> 5) & 0xf000) | (a & 0xfff))) +#else + p[1] = (Byte)(((v >> 13) & 0xf0) | ((a >> 8) & 0xf)); +#endif + +#if 1 && defined(Z7_CPU_FAST_BSWAP_SUPPORTED) && defined(MY_CPU_LE) + v <<= 15; + v = Z7_BSWAP32(v); + SetUi16a(p + 2, (UInt16)v) +#else + p[2] = (Byte)(v >> 9); + p[3] = (Byte)(v >> 1); +#endif +#endif // aligned + } + p += 4; + continue; + } // JAL + + { + // AUIPC + if (v & 0xe80) // (not x0) and (not x2) + { + const UInt32 b = RISCV_GET_UI32(p + 4); + if (RISCV_CHECK_1(v, b)) + { + { + const UInt32 temp = (b << 12) | (0x17 + RISCV_REG_VAL); + RISCV_SET_UI32(p, temp) + } + a &= 0xfffff000; + { +#if 1 + const int t = -1 >> 1; + if (t != -1) + a += (b >> 20) - ((b >> 19) & 0x1000); // arithmetic right shift emulation + else +#endif + a += (UInt32)((Int32)b >> 20); // arithmetic right shift (sign-extension). + } + BR_CONVERT_VAL_ENC(a) +#if 1 && defined(Z7_CPU_FAST_BSWAP_SUPPORTED) && defined(MY_CPU_LE) + a = Z7_BSWAP32(a); + RISCV_SET_UI32(p + 4, a) +#else + SetBe32(p + 4, a) +#endif + p += 8; + } + else + p += RISCV_STEP_1; + } + else + { + UInt32 r = a >> 27; + if (RISCV_CHECK_2(v, r)) + { + v = RISCV_GET_UI32(p + 4); + r = (r << 7) + 0x17 + (v & 0xfffff000); + a = (a >> 12) | (v << 20); + RISCV_SET_UI32(p, r) + RISCV_SET_UI32(p + 4, a) + p += 8; + } + else + p += RISCV_STEP_2; + } + } + } // for +} + + +Byte * Z7_BRANCH_CONV_DEC(RISCV)(Byte *p, SizeT size, UInt32 pc) +{ + RISCV_SCAN_LOOP +#ifdef RISCV_USE_16BIT_LOAD + if ((a & 8) == 0) + { +#else + v = a; + a += (UInt32)p[1] << 8; + if ((v & 8) == 0) + { +#endif + // JAL + a -= 0x100 - RISCV_DELTA_7F; + if (a & 0xd80) + { + p += RISCV_INSTR_SIZE; + continue; + } + { + const UInt32 a_old = (a + (0xef - RISCV_DELTA_7F)) & 0xfff; +#if 0 // unaligned + a = GetUi32(p); + v = (UInt32)(a >> 23) & ((UInt32)0xff << 1) + | (UInt32)(a >> 7) & ((UInt32)0xff << 9) +#elif 1 && defined(Z7_CPU_FAST_BSWAP_SUPPORTED) && defined(MY_CPU_LE) + v = GetUi16a(p + 2); + v = Z7_BSWAP32(v) >> 15 +#else + v = (UInt32)p[3] << 1 + | (UInt32)p[2] << 9 +#endif + | (UInt32)((a & 0xf000) << 5); + BR_CONVERT_VAL_DEC(v) + a = a_old + | (v << 11 & 1u << 31) + | (v << 20 & 0x3ff << 21) + | (v << 9 & 1 << 20) + | (v & 0xff << 12); + RISCV_SET_UI32(p, a) + } + p += 4; + continue; + } // JAL + + { + // AUIPC + v = a; +#if 1 && defined(RISCV_USE_UNALIGNED_LOAD) + a = GetUi32(p); +#else + a |= (UInt32)GetUi16a(p + 2) << 16; +#endif + if ((v & 0xe80) == 0) // x0/x2 + { + const UInt32 r = a >> 27; + if (RISCV_CHECK_2(v, r)) + { + UInt32 b; +#if 1 && defined(Z7_CPU_FAST_BSWAP_SUPPORTED) && defined(MY_CPU_LE) + b = RISCV_GET_UI32(p + 4); + b = Z7_BSWAP32(b); +#else + b = GetBe32(p + 4); +#endif + v = a >> 12; + BR_CONVERT_VAL_DEC(b) + a = (r << 7) + 0x17; + a += (b + 0x800) & 0xfffff000; + v |= b << 20; + RISCV_SET_UI32(p, a) + RISCV_SET_UI32(p + 4, v) + p += 8; + } + else + p += RISCV_STEP_2; + } + else + { + const UInt32 b = RISCV_GET_UI32(p + 4); + if (!RISCV_CHECK_1(v, b)) + p += RISCV_STEP_1; + else + { + v = (a & 0xfffff000) | (b >> 20); + a = (b << 12) | (0x17 + RISCV_REG_VAL); + RISCV_SET_UI32(p, a) + RISCV_SET_UI32(p + 4, v) + p += 8; + } + } + } + } // for } diff --git a/src/plugins/7zip/7za/c/Bra.h b/src/plugins/7zip/7za/c/Bra.h index 855e37a6..b47112ce 100644 --- a/src/plugins/7zip/7za/c/Bra.h +++ b/src/plugins/7zip/7za/c/Bra.h @@ -1,64 +1,105 @@ /* Bra.h -- Branch converters for executables -2013-01-18 : Igor Pavlov : Public domain */ +2024-01-20 : Igor Pavlov : Public domain */ -#ifndef __BRA_H -#define __BRA_H +#ifndef ZIP7_INC_BRA_H +#define ZIP7_INC_BRA_H #include "7zTypes.h" EXTERN_C_BEGIN +/* #define PPC BAD_PPC_11 // for debug */ + +#define Z7_BRANCH_CONV_DEC_2(name) z7_ ## name ## _Dec +#define Z7_BRANCH_CONV_ENC_2(name) z7_ ## name ## _Enc +#define Z7_BRANCH_CONV_DEC(name) Z7_BRANCH_CONV_DEC_2(BranchConv_ ## name) +#define Z7_BRANCH_CONV_ENC(name) Z7_BRANCH_CONV_ENC_2(BranchConv_ ## name) +#define Z7_BRANCH_CONV_ST_DEC(name) z7_BranchConvSt_ ## name ## _Dec +#define Z7_BRANCH_CONV_ST_ENC(name) z7_BranchConvSt_ ## name ## _Enc + +#define Z7_BRANCH_CONV_DECL(name) Byte * name(Byte *data, SizeT size, UInt32 pc) +#define Z7_BRANCH_CONV_ST_DECL(name) Byte * name(Byte *data, SizeT size, UInt32 pc, UInt32 *state) + +typedef Z7_BRANCH_CONV_DECL( (*z7_Func_BranchConv)); +typedef Z7_BRANCH_CONV_ST_DECL((*z7_Func_BranchConvSt)); + +#define Z7_BRANCH_CONV_ST_X86_STATE_INIT_VAL 0 +Z7_BRANCH_CONV_ST_DECL (Z7_BRANCH_CONV_ST_DEC(X86)); +Z7_BRANCH_CONV_ST_DECL (Z7_BRANCH_CONV_ST_ENC(X86)); + +#define Z7_BRANCH_FUNCS_DECL(name) \ +Z7_BRANCH_CONV_DECL (Z7_BRANCH_CONV_DEC_2(name)); \ +Z7_BRANCH_CONV_DECL (Z7_BRANCH_CONV_ENC_2(name)); + +Z7_BRANCH_FUNCS_DECL (BranchConv_ARM64) +Z7_BRANCH_FUNCS_DECL (BranchConv_ARM) +Z7_BRANCH_FUNCS_DECL (BranchConv_ARMT) +Z7_BRANCH_FUNCS_DECL (BranchConv_PPC) +Z7_BRANCH_FUNCS_DECL (BranchConv_SPARC) +Z7_BRANCH_FUNCS_DECL (BranchConv_IA64) +Z7_BRANCH_FUNCS_DECL (BranchConv_RISCV) + /* -These functions convert relative addresses to absolute addresses -in CALL instructions to increase the compression ratio. - - In: - data - data buffer - size - size of data - ip - current virtual Instruction Pinter (IP) value - state - state variable for x86 converter - encoding - 0 (for decoding), 1 (for encoding) - - Out: - state - state variable for x86 converter +These functions convert data that contain CPU instructions. +Each such function converts relative addresses to absolute addresses in some +branch instructions: CALL (in all converters) and JUMP (X86 converter only). +Such conversion allows to increase compression ratio, if we compress that data. + +There are 2 types of converters: + Byte * Conv_RISC (Byte *data, SizeT size, UInt32 pc); + Byte * ConvSt_X86(Byte *data, SizeT size, UInt32 pc, UInt32 *state); +Each Converter supports 2 versions: one for encoding +and one for decoding (_Enc/_Dec postfixes in function name). - Returns: - The number of processed bytes. If you call these functions with multiple calls, - you must start next call with first byte after block of processed bytes. +In params: + data : data buffer + size : size of data + pc : current virtual Program Counter (Instruction Pointer) value +In/Out param: + state : pointer to state variable (for X86 converter only) + +Return: + The pointer to position in (data) buffer after last byte that was processed. + If the caller calls converter again, it must call it starting with that position. + But the caller is allowed to move data in buffer. So pointer to + current processed position also will be changed for next call. + Also the caller must increase internal (pc) value for next call. +Each converter has some characteristics: Endian, Alignment, LookAhead. Type Endian Alignment LookAhead - x86 little 1 4 + X86 little 1 4 ARMT little 2 2 + RISCV little 2 6 ARM little 4 0 + ARM64 little 4 0 PPC big 4 0 SPARC big 4 0 IA64 little 16 0 - size must be >= Alignment + LookAhead, if it's not last block. - If (size < Alignment + LookAhead), converter returns 0. - - Example: + (data) must be aligned for (Alignment). + processed size can be calculated as: + SizeT processed = Conv(data, size, pc) - data; + if (processed == 0) + it means that converter needs more data for processing. + If (size < Alignment + LookAhead) + then (processed == 0) is allowed. - UInt32 ip = 0; - for () - { - ; size must be >= Alignment + LookAhead, if it's not last block - SizeT processed = Convert(data, size, ip, 1); - data += processed; - size -= processed; - ip += processed; - } +Example code for conversion in loop: + UInt32 pc = 0; + size = 0; + for (;;) + { + size += Load_more_input_data(data + size); + SizeT processed = Conv(data, size, pc) - data; + if (processed == 0 && no_more_input_data_after_size) + break; // we stop convert loop + data += processed; + size -= processed; + pc += processed; + } */ -#define x86_Convert_Init(state) { state = 0; } -SizeT x86_Convert(Byte *data, SizeT size, UInt32 ip, UInt32 *state, int encoding); -SizeT ARM_Convert(Byte *data, SizeT size, UInt32 ip, int encoding); -SizeT ARMT_Convert(Byte *data, SizeT size, UInt32 ip, int encoding); -SizeT PPC_Convert(Byte *data, SizeT size, UInt32 ip, int encoding); -SizeT SPARC_Convert(Byte *data, SizeT size, UInt32 ip, int encoding); -SizeT IA64_Convert(Byte *data, SizeT size, UInt32 ip, int encoding); - EXTERN_C_END #endif diff --git a/src/plugins/7zip/7za/c/Bra86.c b/src/plugins/7zip/7za/c/Bra86.c index 6db15e7e..d81f392a 100644 --- a/src/plugins/7zip/7za/c/Bra86.c +++ b/src/plugins/7zip/7za/c/Bra86.c @@ -1,82 +1,187 @@ -/* Bra86.c -- Converter for x86 code (BCJ) -2013-11-12 : Igor Pavlov : Public domain */ +/* Bra86.c -- Branch converter for X86 code (BCJ) +2023-04-02 : Igor Pavlov : Public domain */ #include "Precomp.h" #include "Bra.h" +#include "CpuArch.h" -#define Test86MSByte(b) ((((b) + 1) & 0xFE) == 0) -SizeT x86_Convert(Byte *data, SizeT size, UInt32 ip, UInt32 *state, int encoding) +#if defined(MY_CPU_SIZEOF_POINTER) \ + && ( MY_CPU_SIZEOF_POINTER == 4 \ + || MY_CPU_SIZEOF_POINTER == 8) + #define BR_CONV_USE_OPT_PC_PTR +#endif + +#ifdef BR_CONV_USE_OPT_PC_PTR +#define BR_PC_INIT pc -= (UInt32)(SizeT)p; // (MY_uintptr_t) +#define BR_PC_GET (pc + (UInt32)(SizeT)p) +#else +#define BR_PC_INIT pc += (UInt32)size; +#define BR_PC_GET (pc - (UInt32)(SizeT)(lim - p)) +// #define BR_PC_INIT +// #define BR_PC_GET (pc + (UInt32)(SizeT)(p - data)) +#endif + +#define BR_CONVERT_VAL(v, c) if (encoding) v += c; else v -= c; +// #define BR_CONVERT_VAL(v, c) if (!encoding) c = (UInt32)0 - c; v += c; + +#define Z7_BRANCH_CONV_ST(name) z7_BranchConvSt_ ## name + +#define BR86_NEED_CONV_FOR_MS_BYTE(b) ((((b) + 1) & 0xfe) == 0) + +#ifdef MY_CPU_LE_UNALIGN + #define BR86_PREPARE_BCJ_SCAN const UInt32 v = GetUi32(p) ^ 0xe8e8e8e8; + #define BR86_IS_BCJ_BYTE(n) ((v & ((UInt32)0xfe << (n) * 8)) == 0) +#else + #define BR86_PREPARE_BCJ_SCAN + // bad for MSVC X86 (partial write to byte reg): + #define BR86_IS_BCJ_BYTE(n) ((p[n - 4] & 0xfe) == 0xe8) + // bad for old MSVC (partial write to byte reg): + // #define BR86_IS_BCJ_BYTE(n) (((*p ^ 0xe8) & 0xfe) == 0) +#endif + +static +Z7_FORCE_INLINE +Z7_ATTRIB_NO_VECTOR +Byte *Z7_BRANCH_CONV_ST(X86)(Byte *p, SizeT size, UInt32 pc, UInt32 *state, int encoding) { - SizeT pos = 0; - UInt32 mask = *state & 7; if (size < 5) - return 0; - size -= 4; - ip += 5; + return p; + { + // Byte *p = data; + const Byte *lim = p + size - 4; + unsigned mask = (unsigned)*state; // & 7; +#ifdef BR_CONV_USE_OPT_PC_PTR + /* if BR_CONV_USE_OPT_PC_PTR is defined: we need to adjust (pc) for (+4), + because call/jump offset is relative to the next instruction. + if BR_CONV_USE_OPT_PC_PTR is not defined : we don't need to adjust (pc) for (+4), + because BR_PC_GET uses (pc - (lim - p)), and lim was adjusted for (-4) before. + */ + pc += 4; +#endif + BR_PC_INIT + goto start; - for (;;) + for (;; mask |= 4) { - Byte *p = data + pos; - const Byte *limit = data + size; - for (; p < limit; p++) - if ((*p & 0xFE) == 0xE8) - break; - + // cont: mask |= 4; + start: + if (p >= lim) + goto fin; { - SizeT d = (SizeT)(p - data - pos); - pos = (SizeT)(p - data); - if (p >= limit) - { - *state = (d > 2 ? 0 : mask >> (unsigned)d); - return pos; - } - if (d > 2) - mask = 0; - else - { - mask >>= (unsigned)d; - if (mask != 0 && (mask > 4 || mask == 3 || Test86MSByte(p[(mask >> 1) + 1]))) - { - mask = (mask >> 1) | 4; - pos++; - continue; - } - } + BR86_PREPARE_BCJ_SCAN + p += 4; + if (BR86_IS_BCJ_BYTE(0)) { goto m0; } mask >>= 1; + if (BR86_IS_BCJ_BYTE(1)) { goto m1; } mask >>= 1; + if (BR86_IS_BCJ_BYTE(2)) { goto m2; } mask = 0; + if (BR86_IS_BCJ_BYTE(3)) { goto a3; } } + goto main_loop; - if (Test86MSByte(p[4])) + m0: p--; + m1: p--; + m2: p--; + if (mask == 0) + goto a3; + if (p > lim) + goto fin_p; + + // if (((0x17u >> mask) & 1) == 0) + if (mask > 4 || mask == 3) + { + mask >>= 1; + continue; // goto cont; + } + mask >>= 1; + if (BR86_NEED_CONV_FOR_MS_BYTE(p[mask])) + continue; // goto cont; + // if (!BR86_NEED_CONV_FOR_MS_BYTE(p[3])) continue; // goto cont; { - UInt32 v = ((UInt32)p[4] << 24) | ((UInt32)p[3] << 16) | ((UInt32)p[2] << 8) | ((UInt32)p[1]); - UInt32 cur = ip + (UInt32)pos; - pos += 5; - if (encoding) - v += cur; - else - v -= cur; - if (mask != 0) + UInt32 v = GetUi32(p); + UInt32 c; + v += (1 << 24); if (v & 0xfe000000) continue; // goto cont; + c = BR_PC_GET; + BR_CONVERT_VAL(v, c) { - unsigned sh = (mask & 6) << 2; - if (Test86MSByte((Byte)(v >> sh))) + mask <<= 3; + if (BR86_NEED_CONV_FOR_MS_BYTE(v >> mask)) { - v ^= (((UInt32)0x100 << sh) - 1); - if (encoding) - v += cur; - else - v -= cur; + v ^= (((UInt32)0x100 << mask) - 1); + #ifdef MY_CPU_X86 + // for X86 : we can recalculate (c) to reduce register pressure + c = BR_PC_GET; + #endif + BR_CONVERT_VAL(v, c) } mask = 0; } - p[1] = (Byte)v; - p[2] = (Byte)(v >> 8); - p[3] = (Byte)(v >> 16); - p[4] = (Byte)(0 - ((v >> 24) & 1)); + // v = (v & ((1 << 24) - 1)) - (v & (1 << 24)); + v &= (1 << 25) - 1; v -= (1 << 24); + SetUi32(p, v) + p += 4; + goto main_loop; } - else + + main_loop: + if (p >= lim) + goto fin; + for (;;) { - mask = (mask >> 1) | 4; - pos++; + BR86_PREPARE_BCJ_SCAN + p += 4; + if (BR86_IS_BCJ_BYTE(0)) { goto a0; } + if (BR86_IS_BCJ_BYTE(1)) { goto a1; } + if (BR86_IS_BCJ_BYTE(2)) { goto a2; } + if (BR86_IS_BCJ_BYTE(3)) { goto a3; } + if (p >= lim) + goto fin; + } + + a0: p--; + a1: p--; + a2: p--; + a3: + if (p > lim) + goto fin_p; + // if (!BR86_NEED_CONV_FOR_MS_BYTE(p[3])) continue; // goto cont; + { + UInt32 v = GetUi32(p); + UInt32 c; + v += (1 << 24); if (v & 0xfe000000) continue; // goto cont; + c = BR_PC_GET; + BR_CONVERT_VAL(v, c) + // v = (v & ((1 << 24) - 1)) - (v & (1 << 24)); + v &= (1 << 25) - 1; v -= (1 << 24); + SetUi32(p, v) + p += 4; + goto main_loop; } } + +fin_p: + p--; +fin: + // the following processing for tail is optional and can be commented + /* + lim += 4; + for (; p < lim; p++, mask >>= 1) + if ((*p & 0xfe) == 0xe8) + break; + */ + *state = (UInt32)mask; + return p; + } } + + +#define Z7_BRANCH_CONV_ST_FUNC_IMP(name, m, encoding) \ +Z7_NO_INLINE \ +Z7_ATTRIB_NO_VECTOR \ +Byte *m(name)(Byte *data, SizeT size, UInt32 pc, UInt32 *state) \ + { return Z7_BRANCH_CONV_ST(name)(data, size, pc, state, encoding); } + +Z7_BRANCH_CONV_ST_FUNC_IMP(X86, Z7_BRANCH_CONV_ST_DEC, 0) +#ifndef Z7_EXTRACT_ONLY +Z7_BRANCH_CONV_ST_FUNC_IMP(X86, Z7_BRANCH_CONV_ST_ENC, 1) +#endif diff --git a/src/plugins/7zip/7za/c/BraIA64.c b/src/plugins/7zip/7za/c/BraIA64.c index fa60356b..9dfe3e28 100644 --- a/src/plugins/7zip/7za/c/BraIA64.c +++ b/src/plugins/7zip/7za/c/BraIA64.c @@ -1,69 +1,14 @@ /* BraIA64.c -- Converter for IA-64 code -2013-11-12 : Igor Pavlov : Public domain */ +2023-02-20 : Igor Pavlov : Public domain */ #include "Precomp.h" -#include "Bra.h" +// the code was moved to Bra.c -static const Byte kBranchTable[32] = -{ - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 4, 4, 6, 6, 0, 0, 7, 7, - 4, 4, 0, 0, 4, 4, 0, 0 -}; +#ifdef _MSC_VER +#pragma warning(disable : 4206) // nonstandard extension used : translation unit is empty +#endif -SizeT IA64_Convert(Byte *data, SizeT size, UInt32 ip, int encoding) -{ - SizeT i; - if (size < 16) - return 0; - size -= 16; - for (i = 0; i <= size; i += 16) - { - UInt32 instrTemplate = data[i] & 0x1F; - UInt32 mask = kBranchTable[instrTemplate]; - UInt32 bitPos = 5; - int slot; - for (slot = 0; slot < 3; slot++, bitPos += 41) - { - UInt32 bytePos, bitRes; - UInt64 instruction, instNorm; - int j; - if (((mask >> slot) & 1) == 0) - continue; - bytePos = (bitPos >> 3); - bitRes = bitPos & 0x7; - instruction = 0; - for (j = 0; j < 6; j++) - instruction += (UInt64)data[i + j + bytePos] << (8 * j); - - instNorm = instruction >> bitRes; - if (((instNorm >> 37) & 0xF) == 0x5 && ((instNorm >> 9) & 0x7) == 0) - { - UInt32 src = (UInt32)((instNorm >> 13) & 0xFFFFF); - UInt32 dest; - src |= ((UInt32)(instNorm >> 36) & 1) << 20; - - src <<= 4; - - if (encoding) - dest = ip + (UInt32)i + src; - else - dest = src - (ip + (UInt32)i); - - dest >>= 4; - - instNorm &= ~((UInt64)(0x8FFFFF) << 13); - instNorm |= ((UInt64)(dest & 0xFFFFF) << 13); - instNorm |= ((UInt64)(dest & 0x100000) << (36 - 20)); - - instruction &= (1 << bitRes) - 1; - instruction |= (instNorm << bitRes); - for (j = 0; j < 6; j++) - data[i + j + bytePos] = (Byte)(instruction >> (8 * j)); - } - } - } - return i; -} +#if defined(__clang__) +#pragma GCC diagnostic ignored "-Wempty-translation-unit" +#endif diff --git a/src/plugins/7zip/7za/c/BwtSort.c b/src/plugins/7zip/7za/c/BwtSort.c index 36501cc9..8f64f9d7 100644 --- a/src/plugins/7zip/7za/c/BwtSort.c +++ b/src/plugins/7zip/7za/c/BwtSort.c @@ -1,5 +1,5 @@ /* BwtSort.c -- BWT block sorting -2013-11-12 : Igor Pavlov : Public domain */ +: Igor Pavlov : Public domain */ #include "Precomp.h" @@ -7,8 +7,44 @@ #include "Sort.h" /* #define BLOCK_SORT_USE_HEAP_SORT */ +// #define BLOCK_SORT_USE_HEAP_SORT + +#ifdef BLOCK_SORT_USE_HEAP_SORT + +#define HeapSortRefDown(p, vals, n, size, temp) \ + { size_t k = n; UInt32 val = vals[temp]; for (;;) { \ + size_t s = k << 1; \ + if (s > size) break; \ + if (s < size && vals[p[s + 1]] > vals[p[s]]) s++; \ + if (val >= vals[p[s]]) break; \ + p[k] = p[s]; k = s; \ + } p[k] = temp; } + +void HeapSortRef(UInt32 *p, UInt32 *vals, size_t size) +{ + if (size <= 1) + return; + p--; + { + size_t i = size / 2; + do + { + UInt32 temp = p[i]; + HeapSortRefDown(p, vals, i, size, temp); + } + while (--i != 0); + } + do + { + UInt32 temp = p[size]; + p[size--] = p[1]; + HeapSortRefDown(p, vals, 1, size, temp); + } + while (size > 1); +} + +#endif // BLOCK_SORT_USE_HEAP_SORT -#define NO_INLINE MY_FAST_CALL /* Don't change it !!! */ #define kNumHashBytes 2 @@ -29,26 +65,27 @@ #else -#define kNumBitsMax 20 -#define kIndexMask ((1 << kNumBitsMax) - 1) -#define kNumExtraBits (32 - kNumBitsMax) -#define kNumExtra0Bits (kNumExtraBits - 2) -#define kNumExtra0Mask ((1 << kNumExtra0Bits) - 1) +#define kNumBitsMax 20 +#define kIndexMask (((UInt32)1 << kNumBitsMax) - 1) +#define kNumExtraBits (32 - kNumBitsMax) +#define kNumExtra0Bits (kNumExtraBits - 2) +#define kNumExtra0Mask ((1 << kNumExtra0Bits) - 1) #define SetFinishedGroupSize(p, size) \ - { *(p) |= ((((size) - 1) & kNumExtra0Mask) << kNumBitsMax); \ + { *(p) |= ((((UInt32)(size) - 1) & kNumExtra0Mask) << kNumBitsMax); \ if ((size) > (1 << kNumExtra0Bits)) { \ - *(p) |= 0x40000000; *((p) + 1) |= ((((size) - 1)>> kNumExtra0Bits) << kNumBitsMax); } } \ + *(p) |= 0x40000000; \ + *((p) + 1) |= (((UInt32)(size) - 1) >> kNumExtra0Bits) << kNumBitsMax; } } \ -static void SetGroupSize(UInt32 *p, UInt32 size) +static void SetGroupSize(UInt32 *p, size_t size) { if (--size == 0) return; - *p |= 0x80000000 | ((size & kNumExtra0Mask) << kNumBitsMax); + *p |= 0x80000000 | (((UInt32)size & kNumExtra0Mask) << kNumBitsMax); if (size >= (1 << kNumExtra0Bits)) { *p |= 0x40000000; - p[1] |= ((size >> kNumExtra0Bits) << kNumBitsMax); + p[1] |= (((UInt32)size >> kNumExtra0Bits) << kNumBitsMax); } } @@ -60,10 +97,15 @@ SortGroup - is recursive Range-Sort function with HeapSort optimization for smal returns: 1 - if there are groups, 0 - no more groups */ -UInt32 NO_INLINE SortGroup(UInt32 BlockSize, UInt32 NumSortedBytes, UInt32 groupOffset, UInt32 groupSize, int NumRefBits, UInt32 *Indices - #ifndef BLOCK_SORT_USE_HEAP_SORT - , UInt32 left, UInt32 range - #endif +static +unsigned +Z7_FASTCALL +SortGroup(size_t BlockSize, size_t NumSortedBytes, + size_t groupOffset, size_t groupSize, + unsigned NumRefBits, UInt32 *Indices +#ifndef BLOCK_SORT_USE_HEAP_SORT + , size_t left, size_t range +#endif ) { UInt32 *ind2 = Indices + groupOffset; @@ -72,96 +114,99 @@ UInt32 NO_INLINE SortGroup(UInt32 BlockSize, UInt32 NumSortedBytes, UInt32 group { /* #ifndef BLOCK_SORT_EXTERNAL_FLAGS - SetFinishedGroupSize(ind2, 1); + SetFinishedGroupSize(ind2, 1) #endif */ return 0; } Groups = Indices + BlockSize + BS_TEMP_SIZE; - if (groupSize <= ((UInt32)1 << NumRefBits) - #ifndef BLOCK_SORT_USE_HEAP_SORT + if (groupSize <= ((size_t)1 << NumRefBits) +#ifndef BLOCK_SORT_USE_HEAP_SORT && groupSize <= range - #endif +#endif ) { UInt32 *temp = Indices + BlockSize; - UInt32 j; - UInt32 mask, thereAreGroups, group, cg; + size_t j, group; + UInt32 mask, cg; + unsigned thereAreGroups; { UInt32 gPrev; UInt32 gRes = 0; { - UInt32 sp = ind2[0] + NumSortedBytes; - if (sp >= BlockSize) sp -= BlockSize; + size_t sp = ind2[0] + NumSortedBytes; + if (sp >= BlockSize) + sp -= BlockSize; gPrev = Groups[sp]; - temp[0] = (gPrev << NumRefBits); + temp[0] = gPrev << NumRefBits; } for (j = 1; j < groupSize; j++) { - UInt32 sp = ind2[j] + NumSortedBytes; + size_t sp = ind2[j] + NumSortedBytes; UInt32 g; - if (sp >= BlockSize) sp -= BlockSize; + if (sp >= BlockSize) + sp -= BlockSize; g = Groups[sp]; - temp[j] = (g << NumRefBits) | j; + temp[j] = (g << NumRefBits) | (UInt32)j; gRes |= (gPrev ^ g); } if (gRes == 0) { - #ifndef BLOCK_SORT_EXTERNAL_FLAGS +#ifndef BLOCK_SORT_EXTERNAL_FLAGS SetGroupSize(ind2, groupSize); - #endif +#endif return 1; } } HeapSort(temp, groupSize); - mask = ((1 << NumRefBits) - 1); + mask = ((UInt32)1 << NumRefBits) - 1; thereAreGroups = 0; group = groupOffset; - cg = (temp[0] >> NumRefBits); + cg = temp[0] >> NumRefBits; temp[0] = ind2[temp[0] & mask]; { - #ifdef BLOCK_SORT_EXTERNAL_FLAGS +#ifdef BLOCK_SORT_EXTERNAL_FLAGS UInt32 *Flags = Groups + BlockSize; - #else - UInt32 prevGroupStart = 0; - #endif +#else + size_t prevGroupStart = 0; +#endif for (j = 1; j < groupSize; j++) { - UInt32 val = temp[j]; - UInt32 cgCur = (val >> NumRefBits); + const UInt32 val = temp[j]; + const UInt32 cgCur = val >> NumRefBits; if (cgCur != cg) { cg = cgCur; group = groupOffset + j; - #ifdef BLOCK_SORT_EXTERNAL_FLAGS +#ifdef BLOCK_SORT_EXTERNAL_FLAGS { - UInt32 t = group - 1; - Flags[t >> kNumFlagsBits] &= ~(1 << (t & kFlagsMask)); + const size_t t = group - 1; + Flags[t >> kNumFlagsBits] &= ~((UInt32)1 << (t & kFlagsMask)); } - #else +#else SetGroupSize(temp + prevGroupStart, j - prevGroupStart); prevGroupStart = j; - #endif +#endif } else thereAreGroups = 1; { - UInt32 ind = ind2[val & mask]; - temp[j] = ind; - Groups[ind] = group; + const UInt32 ind = ind2[val & mask]; + temp[j] = ind; + Groups[ind] = (UInt32)group; } } - #ifndef BLOCK_SORT_EXTERNAL_FLAGS +#ifndef BLOCK_SORT_EXTERNAL_FLAGS SetGroupSize(temp + prevGroupStart, j - prevGroupStart); - #endif +#endif } for (j = 0; j < groupSize; j++) @@ -171,37 +216,42 @@ UInt32 NO_INLINE SortGroup(UInt32 BlockSize, UInt32 NumSortedBytes, UInt32 group /* Check that all strings are in one group (cannot sort) */ { - UInt32 group, j; - UInt32 sp = ind2[0] + NumSortedBytes; if (sp >= BlockSize) sp -= BlockSize; + UInt32 group; + size_t j; + size_t sp = ind2[0] + NumSortedBytes; + if (sp >= BlockSize) + sp -= BlockSize; group = Groups[sp]; for (j = 1; j < groupSize; j++) { - sp = ind2[j] + NumSortedBytes; if (sp >= BlockSize) sp -= BlockSize; + sp = ind2[j] + NumSortedBytes; + if (sp >= BlockSize) + sp -= BlockSize; if (Groups[sp] != group) break; } if (j == groupSize) { - #ifndef BLOCK_SORT_EXTERNAL_FLAGS +#ifndef BLOCK_SORT_EXTERNAL_FLAGS SetGroupSize(ind2, groupSize); - #endif +#endif return 1; } } - #ifndef BLOCK_SORT_USE_HEAP_SORT +#ifndef BLOCK_SORT_USE_HEAP_SORT { /* ---------- Range Sort ---------- */ - UInt32 i; - UInt32 mid; + size_t i; + size_t mid; for (;;) { - UInt32 j; + size_t j; if (range <= 1) { - #ifndef BLOCK_SORT_EXTERNAL_FLAGS +#ifndef BLOCK_SORT_EXTERNAL_FLAGS SetGroupSize(ind2, groupSize); - #endif +#endif return 1; } mid = left + ((range + 1) >> 1); @@ -209,7 +259,7 @@ UInt32 NO_INLINE SortGroup(UInt32 BlockSize, UInt32 NumSortedBytes, UInt32 group i = 0; do { - UInt32 sp = ind2[i] + NumSortedBytes; if (sp >= BlockSize) sp -= BlockSize; + size_t sp = ind2[i] + NumSortedBytes; if (sp >= BlockSize) sp -= BlockSize; if (Groups[sp] >= mid) { for (j--; j > i; j--) @@ -237,51 +287,53 @@ UInt32 NO_INLINE SortGroup(UInt32 BlockSize, UInt32 NumSortedBytes, UInt32 group break; } - #ifdef BLOCK_SORT_EXTERNAL_FLAGS +#ifdef BLOCK_SORT_EXTERNAL_FLAGS { - UInt32 t = (groupOffset + i - 1); + const size_t t = groupOffset + i - 1; UInt32 *Flags = Groups + BlockSize; - Flags[t >> kNumFlagsBits] &= ~(1 << (t & kFlagsMask)); + Flags[t >> kNumFlagsBits] &= ~((UInt32)1 << (t & kFlagsMask)); } - #endif +#endif { - UInt32 j; + size_t j; for (j = i; j < groupSize; j++) - Groups[ind2[j]] = groupOffset + i; + Groups[ind2[j]] = (UInt32)(groupOffset + i); } { - UInt32 res = SortGroup(BlockSize, NumSortedBytes, groupOffset, i, NumRefBits, Indices, left, mid - left); - return res | SortGroup(BlockSize, NumSortedBytes, groupOffset + i, groupSize - i, NumRefBits, Indices, mid, range - (mid - left)); + unsigned res = SortGroup(BlockSize, NumSortedBytes, groupOffset, i, NumRefBits, Indices, left, mid - left); + return res | SortGroup(BlockSize, NumSortedBytes, groupOffset + i, groupSize - i, NumRefBits, Indices, mid, range - (mid - left)); } } - #else +#else // BLOCK_SORT_USE_HEAP_SORT /* ---------- Heap Sort ---------- */ { - UInt32 j; + size_t j; for (j = 0; j < groupSize; j++) { - UInt32 sp = ind2[j] + NumSortedBytes; if (sp >= BlockSize) sp -= BlockSize; - ind2[j] = sp; + size_t sp = ind2[j] + NumSortedBytes; + if (sp >= BlockSize) + sp -= BlockSize; + ind2[j] = (UInt32)sp; } HeapSortRef(ind2, Groups, groupSize); /* Write Flags */ { - UInt32 sp = ind2[0]; + size_t sp = ind2[0]; UInt32 group = Groups[sp]; - #ifdef BLOCK_SORT_EXTERNAL_FLAGS +#ifdef BLOCK_SORT_EXTERNAL_FLAGS UInt32 *Flags = Groups + BlockSize; - #else - UInt32 prevGroupStart = 0; - #endif +#else + size_t prevGroupStart = 0; +#endif for (j = 1; j < groupSize; j++) { @@ -289,149 +341,210 @@ UInt32 NO_INLINE SortGroup(UInt32 BlockSize, UInt32 NumSortedBytes, UInt32 group if (Groups[sp] != group) { group = Groups[sp]; - #ifdef BLOCK_SORT_EXTERNAL_FLAGS +#ifdef BLOCK_SORT_EXTERNAL_FLAGS { - UInt32 t = groupOffset + j - 1; + const size_t t = groupOffset + j - 1; Flags[t >> kNumFlagsBits] &= ~(1 << (t & kFlagsMask)); } - #else +#else SetGroupSize(ind2 + prevGroupStart, j - prevGroupStart); prevGroupStart = j; - #endif +#endif } } - #ifndef BLOCK_SORT_EXTERNAL_FLAGS +#ifndef BLOCK_SORT_EXTERNAL_FLAGS SetGroupSize(ind2 + prevGroupStart, j - prevGroupStart); - #endif +#endif } { /* Write new Groups values and Check that there are groups */ - UInt32 thereAreGroups = 0; + unsigned thereAreGroups = 0; for (j = 0; j < groupSize; j++) { - UInt32 group = groupOffset + j; - #ifndef BLOCK_SORT_EXTERNAL_FLAGS + size_t group = groupOffset + j; +#ifndef BLOCK_SORT_EXTERNAL_FLAGS UInt32 subGroupSize = ((ind2[j] & ~0xC0000000) >> kNumBitsMax); - if ((ind2[j] & 0x40000000) != 0) - subGroupSize += ((ind2[j + 1] >> kNumBitsMax) << kNumExtra0Bits); + if (ind2[j] & 0x40000000) + subGroupSize += ((ind2[(size_t)j + 1] >> kNumBitsMax) << kNumExtra0Bits); subGroupSize++; for (;;) { - UInt32 original = ind2[j]; - UInt32 sp = original & kIndexMask; - if (sp < NumSortedBytes) sp += BlockSize; sp -= NumSortedBytes; - ind2[j] = sp | (original & ~kIndexMask); - Groups[sp] = group; + const UInt32 original = ind2[j]; + size_t sp = original & kIndexMask; + if (sp < NumSortedBytes) + sp += BlockSize; + sp -= NumSortedBytes; + ind2[j] = (UInt32)sp | (original & ~kIndexMask); + Groups[sp] = (UInt32)group; if (--subGroupSize == 0) break; j++; thereAreGroups = 1; } - #else +#else UInt32 *Flags = Groups + BlockSize; for (;;) { - UInt32 sp = ind2[j]; if (sp < NumSortedBytes) sp += BlockSize; sp -= NumSortedBytes; - ind2[j] = sp; - Groups[sp] = group; + size_t sp = ind2[j]; + if (sp < NumSortedBytes) + sp += BlockSize; + sp -= NumSortedBytes; + ind2[j] = (UInt32)sp; + Groups[sp] = (UInt32)group; if ((Flags[(groupOffset + j) >> kNumFlagsBits] & (1 << ((groupOffset + j) & kFlagsMask))) == 0) break; j++; thereAreGroups = 1; } - #endif +#endif } return thereAreGroups; } } - #endif +#endif // BLOCK_SORT_USE_HEAP_SORT } + /* conditions: blockSize > 0 */ -UInt32 BlockSort(UInt32 *Indices, const Byte *data, UInt32 blockSize) +UInt32 BlockSort(UInt32 *Indices, const Byte *data, size_t blockSize) { UInt32 *counters = Indices + blockSize; - UInt32 i; + size_t i; UInt32 *Groups; - #ifdef BLOCK_SORT_EXTERNAL_FLAGS +#ifdef BLOCK_SORT_EXTERNAL_FLAGS UInt32 *Flags; - #endif +#endif - /* Radix-Sort for 2 bytes */ +/* Radix-Sort for 2 bytes */ +// { UInt32 yyy; for (yyy = 0; yyy < 100; yyy++) { for (i = 0; i < kNumHashValues; i++) counters[i] = 0; - for (i = 0; i < blockSize - 1; i++) - counters[((UInt32)data[i] << 8) | data[i + 1]]++; - counters[((UInt32)data[i] << 8) | data[0]]++; + { + const Byte *data2 = data; + size_t a = data[(size_t)blockSize - 1]; + const Byte *data_lim = data + blockSize; + if (blockSize >= 4) + { + data_lim -= 3; + do + { + size_t b; + b = data2[0]; counters[(a << 8) | b]++; + a = data2[1]; counters[(b << 8) | a]++; + b = data2[2]; counters[(a << 8) | b]++; + a = data2[3]; counters[(b << 8) | a]++; + data2 += 4; + } + while (data2 < data_lim); + data_lim += 3; + } + while (data2 != data_lim) + { + size_t b = *data2++; + counters[(a << 8) | b]++; + a = b; + } + } +// }} Groups = counters + BS_TEMP_SIZE; - #ifdef BLOCK_SORT_EXTERNAL_FLAGS +#ifdef BLOCK_SORT_EXTERNAL_FLAGS Flags = Groups + blockSize; - { - UInt32 numWords = (blockSize + kFlagsMask) >> kNumFlagsBits; - for (i = 0; i < numWords; i++) - Flags[i] = kAllFlags; - } - #endif + { + const size_t numWords = (blockSize + kFlagsMask) >> kNumFlagsBits; + for (i = 0; i < numWords; i++) + Flags[i] = kAllFlags; + } +#endif { UInt32 sum = 0; for (i = 0; i < kNumHashValues; i++) { - UInt32 groupSize = counters[i]; - if (groupSize > 0) + const UInt32 groupSize = counters[i]; + counters[i] = sum; + sum += groupSize; +#ifdef BLOCK_SORT_EXTERNAL_FLAGS + if (groupSize) { - #ifdef BLOCK_SORT_EXTERNAL_FLAGS - UInt32 t = sum + groupSize - 1; - Flags[t >> kNumFlagsBits] &= ~(1 << (t & kFlagsMask)); - #endif - sum += groupSize; + const UInt32 t = sum - 1; + Flags[t >> kNumFlagsBits] &= ~((UInt32)1 << (t & kFlagsMask)); } - counters[i] = sum - groupSize; +#endif } + } - for (i = 0; i < blockSize - 1; i++) - Groups[i] = counters[((UInt32)data[i] << 8) | data[i + 1]]; - Groups[i] = counters[((UInt32)data[i] << 8) | data[0]]; + for (i = 0; i < blockSize - 1; i++) + Groups[i] = counters[((unsigned)data[i] << 8) | data[(size_t)i + 1]]; + Groups[i] = counters[((unsigned)data[i] << 8) | data[0]]; + + { +#define SET_Indices(a, b, i) \ + { UInt32 c; \ + a = (a << 8) | (b); \ + c = counters[a]; \ + Indices[c] = (UInt32)i++; \ + counters[a] = c + 1; \ + } - for (i = 0; i < blockSize - 1; i++) - Indices[counters[((UInt32)data[i] << 8) | data[i + 1]]++] = i; - Indices[counters[((UInt32)data[i] << 8) | data[0]]++] = i; - - #ifndef BLOCK_SORT_EXTERNAL_FLAGS + size_t a = data[0]; + const Byte *data_ptr = data + 1; + i = 0; + if (blockSize >= 3) + { + blockSize -= 2; + do + { + size_t b; + b = data_ptr[0]; SET_Indices(a, b, i) + a = data_ptr[1]; SET_Indices(b, a, i) + data_ptr += 2; + } + while (i < blockSize); + blockSize += 2; + } + if (i < blockSize - 1) { + SET_Indices(a, data[(size_t)i + 1], i) + a = (Byte)a; + } + SET_Indices(a, data[0], i) + } + +#ifndef BLOCK_SORT_EXTERNAL_FLAGS + { UInt32 prev = 0; for (i = 0; i < kNumHashValues; i++) { - UInt32 prevGroupSize = counters[i] - prev; + const UInt32 prevGroupSize = counters[i] - prev; if (prevGroupSize == 0) continue; SetGroupSize(Indices + prev, prevGroupSize); prev = counters[i]; } - } - #endif } +#endif { - int NumRefBits; - UInt32 NumSortedBytes; - for (NumRefBits = 0; ((blockSize - 1) >> NumRefBits) != 0; NumRefBits++); + unsigned NumRefBits; + size_t NumSortedBytes; + for (NumRefBits = 0; ((blockSize - 1) >> NumRefBits) != 0; NumRefBits++) + {} NumRefBits = 32 - NumRefBits; if (NumRefBits > kNumRefBitsMax) - NumRefBits = kNumRefBitsMax; + NumRefBits = kNumRefBitsMax; for (NumSortedBytes = kNumHashBytes; ; NumSortedBytes <<= 1) { - #ifndef BLOCK_SORT_EXTERNAL_FLAGS - UInt32 finishedGroupSize = 0; - #endif - UInt32 newLimit = 0; +#ifndef BLOCK_SORT_EXTERNAL_FLAGS + size_t finishedGroupSize = 0; +#endif + size_t newLimit = 0; for (i = 0; i < blockSize;) { - UInt32 groupSize; - #ifdef BLOCK_SORT_EXTERNAL_FLAGS + size_t groupSize; +#ifdef BLOCK_SORT_EXTERNAL_FLAGS if ((Flags[i >> kNumFlagsBits] & (1 << (i & kFlagsMask))) == 0) { @@ -440,56 +553,56 @@ UInt32 BlockSort(UInt32 *Indices, const Byte *data, UInt32 blockSize) } for (groupSize = 1; (Flags[(i + groupSize) >> kNumFlagsBits] & (1 << ((i + groupSize) & kFlagsMask))) != 0; - groupSize++); - + groupSize++) + {} groupSize++; - #else +#else - groupSize = ((Indices[i] & ~0xC0000000) >> kNumBitsMax); + groupSize = (Indices[i] & ~0xC0000000) >> kNumBitsMax; { - Bool finishedGroup = ((Indices[i] & 0x80000000) == 0); - if ((Indices[i] & 0x40000000) != 0) - { - groupSize += ((Indices[i + 1] >> kNumBitsMax) << kNumExtra0Bits); - Indices[i + 1] &= kIndexMask; - } - Indices[i] &= kIndexMask; - groupSize++; - if (finishedGroup || groupSize == 1) - { - Indices[i - finishedGroupSize] &= kIndexMask; - if (finishedGroupSize > 1) - Indices[i - finishedGroupSize + 1] &= kIndexMask; + const BoolInt finishedGroup = ((Indices[i] & 0x80000000) == 0); + if (Indices[i] & 0x40000000) { - UInt32 newGroupSize = groupSize + finishedGroupSize; - SetFinishedGroupSize(Indices + i - finishedGroupSize, newGroupSize); - finishedGroupSize = newGroupSize; + groupSize += ((Indices[(size_t)i + 1] >> kNumBitsMax) << kNumExtra0Bits); + Indices[(size_t)i + 1] &= kIndexMask; } - i += groupSize; - continue; - } - finishedGroupSize = 0; + Indices[i] &= kIndexMask; + groupSize++; + if (finishedGroup || groupSize == 1) + { + Indices[i - finishedGroupSize] &= kIndexMask; + if (finishedGroupSize > 1) + Indices[(size_t)(i - finishedGroupSize) + 1] &= kIndexMask; + { + const size_t newGroupSize = groupSize + finishedGroupSize; + SetFinishedGroupSize(Indices + i - finishedGroupSize, newGroupSize) + finishedGroupSize = newGroupSize; + } + i += groupSize; + continue; + } + finishedGroupSize = 0; } - #endif +#endif if (NumSortedBytes >= blockSize) { - UInt32 j; + size_t j; for (j = 0; j < groupSize; j++) { - UInt32 t = (i + j); + size_t t = i + j; /* Flags[t >> kNumFlagsBits] &= ~(1 << (t & kFlagsMask)); */ - Groups[Indices[t]] = t; + Groups[Indices[t]] = (UInt32)t; } } else if (SortGroup(blockSize, NumSortedBytes, i, groupSize, NumRefBits, Indices - #ifndef BLOCK_SORT_USE_HEAP_SORT - , 0, blockSize - #endif - ) != 0) + #ifndef BLOCK_SORT_USE_HEAP_SORT + , 0, blockSize + #endif + )) newLimit = i + groupSize; i += groupSize; } @@ -497,19 +610,19 @@ UInt32 BlockSort(UInt32 *Indices, const Byte *data, UInt32 blockSize) break; } } - #ifndef BLOCK_SORT_EXTERNAL_FLAGS +#ifndef BLOCK_SORT_EXTERNAL_FLAGS for (i = 0; i < blockSize;) { - UInt32 groupSize = ((Indices[i] & ~0xC0000000) >> kNumBitsMax); - if ((Indices[i] & 0x40000000) != 0) + size_t groupSize = (Indices[i] & ~0xC0000000) >> kNumBitsMax; + if (Indices[i] & 0x40000000) { - groupSize += ((Indices[i + 1] >> kNumBitsMax) << kNumExtra0Bits); - Indices[i + 1] &= kIndexMask; + groupSize += (Indices[(size_t)i + 1] >> kNumBitsMax) << kNumExtra0Bits; + Indices[(size_t)i + 1] &= kIndexMask; } Indices[i] &= kIndexMask; groupSize++; i += groupSize; } - #endif +#endif return Groups[0]; } diff --git a/src/plugins/7zip/7za/c/BwtSort.h b/src/plugins/7zip/7za/c/BwtSort.h index 7e989a99..1bd2316d 100644 --- a/src/plugins/7zip/7za/c/BwtSort.h +++ b/src/plugins/7zip/7za/c/BwtSort.h @@ -1,8 +1,8 @@ /* BwtSort.h -- BWT block sorting -2013-01-18 : Igor Pavlov : Public domain */ +: Igor Pavlov : Public domain */ -#ifndef __BWT_SORT_H -#define __BWT_SORT_H +#ifndef ZIP7_INC_BWT_SORT_H +#define ZIP7_INC_BWT_SORT_H #include "7zTypes.h" @@ -10,16 +10,17 @@ EXTERN_C_BEGIN /* use BLOCK_SORT_EXTERNAL_FLAGS if blockSize can be > 1M */ /* #define BLOCK_SORT_EXTERNAL_FLAGS */ +// #define BLOCK_SORT_EXTERNAL_FLAGS #ifdef BLOCK_SORT_EXTERNAL_FLAGS -#define BLOCK_SORT_EXTERNAL_SIZE(blockSize) ((((blockSize) + 31) >> 5)) +#define BLOCK_SORT_EXTERNAL_SIZE(blockSize) (((blockSize) + 31) >> 5) #else #define BLOCK_SORT_EXTERNAL_SIZE(blockSize) 0 #endif #define BLOCK_SORT_BUF_SIZE(blockSize) ((blockSize) * 2 + BLOCK_SORT_EXTERNAL_SIZE(blockSize) + (1 << 16)) -UInt32 BlockSort(UInt32 *indices, const Byte *data, UInt32 blockSize); +UInt32 BlockSort(UInt32 *indices, const Byte *data, size_t blockSize); EXTERN_C_END diff --git a/src/plugins/7zip/7za/c/Compiler.h b/src/plugins/7zip/7za/c/Compiler.h index 5bba7ee5..c06b8836 100644 --- a/src/plugins/7zip/7za/c/Compiler.h +++ b/src/plugins/7zip/7za/c/Compiler.h @@ -1,8 +1,116 @@ -/* Compiler.h -2015-08-02 : Igor Pavlov : Public domain */ +/* Compiler.h : Compiler specific defines and pragmas +: Igor Pavlov : Public domain */ + +#ifndef ZIP7_INC_COMPILER_H +#define ZIP7_INC_COMPILER_H + +#if defined(__clang__) +# define Z7_CLANG_VERSION (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) +#endif +#if defined(__clang__) && defined(__apple_build_version__) +# define Z7_APPLE_CLANG_VERSION Z7_CLANG_VERSION +#elif defined(__clang__) +# define Z7_LLVM_CLANG_VERSION Z7_CLANG_VERSION +#elif defined(__GNUC__) +# define Z7_GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) +#endif + +#ifdef _MSC_VER +#if !defined(__clang__) && !defined(__GNUC__) +#define Z7_MSC_VER_ORIGINAL _MSC_VER +#endif +#endif + +#if defined(__MINGW32__) || defined(__MINGW64__) +#define Z7_MINGW +#endif + +#if defined(__LCC__) && (defined(__MCST__) || defined(__e2k__)) +#define Z7_MCST_LCC +#define Z7_MCST_LCC_VERSION (__LCC__ * 100 + __LCC_MINOR__) +#endif + +/* +#if defined(__AVX2__) \ + || defined(Z7_GCC_VERSION) && (Z7_GCC_VERSION >= 40900) \ + || defined(Z7_APPLE_CLANG_VERSION) && (Z7_APPLE_CLANG_VERSION >= 40600) \ + || defined(Z7_LLVM_CLANG_VERSION) && (Z7_LLVM_CLANG_VERSION >= 30100) \ + || defined(Z7_MSC_VER_ORIGINAL) && (Z7_MSC_VER_ORIGINAL >= 1800) \ + || defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 1400) + #define Z7_COMPILER_AVX2_SUPPORTED + #endif +#endif +*/ + +// #pragma GCC diagnostic ignored "-Wunknown-pragmas" + +#ifdef __clang__ +// padding size of '' with 4 bytes to alignment boundary +#pragma GCC diagnostic ignored "-Wpadded" + +#if defined(Z7_LLVM_CLANG_VERSION) && (__clang_major__ == 13) \ + && defined(__FreeBSD__) +// freebsd: +#pragma GCC diagnostic ignored "-Wexcess-padding" +#endif + +#if defined(Z7_APPLE_CLANG_VERSION) && __clang_major__ >= 21 +// warning: function MyAlloc might be an allocator wrapper +// clang in xcode: clang 21.0.0 +#pragma GCC diagnostic ignored "-Wallocator-wrappers" +#endif + +#if __clang_major__ >= 16 +#pragma GCC diagnostic ignored "-Wunsafe-buffer-usage" +#endif + +#if __clang_major__ == 13 +#if defined(__SIZEOF_POINTER__) && (__SIZEOF_POINTER__ == 16) +// cheri +#pragma GCC diagnostic ignored "-Wcapability-to-integer-cast" +#endif +#endif + +#if __clang_major__ == 13 + // for + #pragma GCC diagnostic ignored "-Wreserved-identifier" +#endif + +#if __clang_major__ >= 22 +// for "StdAfx.h" and "Precomp.h" +#pragma GCC diagnostic ignored "-Wshadow-header" +#endif + +#endif // __clang__ + +#if defined(__clang__) && __clang_major__ >= 16 +// #pragma GCC diagnostic ignored "-Wcast-function-type-strict" +#define Z7_DIAGNOSTIC_IGNORE_CAST_FUNCTION \ + _Pragma("GCC diagnostic ignored \"-Wcast-function-type-strict\"") +#else +#define Z7_DIAGNOSTIC_IGNORE_CAST_FUNCTION +#endif + +typedef void (*Z7_void_Function)(void); +#if defined(__clang__) || defined(__GNUC__) +#define Z7_CAST_FUNC_C (Z7_void_Function) +#elif defined(_MSC_VER) && _MSC_VER > 1920 +#define Z7_CAST_FUNC_C (void *) +// #pragma warning(disable : 4191) // 'type cast': unsafe conversion from 'FARPROC' to 'void (__cdecl *)()' +#else +#define Z7_CAST_FUNC_C +#endif +/* +#if (defined(__GNUC__) && (__GNUC__ >= 8)) || defined(__clang__) + // #pragma GCC diagnostic ignored "-Wcast-function-type" +#endif +*/ +#ifdef __GNUC__ +#if defined(Z7_GCC_VERSION) && (Z7_GCC_VERSION >= 40000) && (Z7_GCC_VERSION < 70000) +#pragma GCC diagnostic ignored "-Wstrict-aliasing" +#endif +#endif -#ifndef __7Z_COMPILER_H -#define __7Z_COMPILER_H #ifdef _MSC_VER @@ -13,17 +121,134 @@ #pragma warning(disable : 4214) // nonstandard extension used : bit field types other than int #endif - #if _MSC_VER >= 1300 - #pragma warning(disable : 4996) // This function or variable may be unsafe - #else - #pragma warning(disable : 4511) // copy constructor could not be generated - #pragma warning(disable : 4512) // assignment operator could not be generated - #pragma warning(disable : 4514) // unreferenced inline function has been removed - #pragma warning(disable : 4702) // unreachable code - #pragma warning(disable : 4710) // not inlined - #pragma warning(disable : 4786) // identifier was truncated to '255' characters in the debug information - #endif +#if defined(_MSC_VER) && _MSC_VER >= 1800 +#pragma warning(disable : 4464) // relative include path contains '..' +#endif + +// == 1200 : -O1 : for __forceinline +// >= 1900 : -O1 : for printf +#pragma warning(disable : 4710) // function not inlined + +#if _MSC_VER < 1900 +// winnt.h: 'Int64ShllMod32' +#pragma warning(disable : 4514) // unreferenced inline function has been removed +#endif + +#if _MSC_VER < 1300 +// #pragma warning(disable : 4702) // unreachable code +// Bra.c : -O1: +#pragma warning(disable : 4714) // function marked as __forceinline not inlined +#endif + +/* +#if _MSC_VER > 1400 && _MSC_VER <= 1900 +// strcat: This function or variable may be unsafe +// sysinfoapi.h: kit10: GetVersion was declared deprecated +#pragma warning(disable : 4996) +#endif +*/ + +#if _MSC_VER > 1200 +// -Wall warnings + +#pragma warning(disable : 4711) // function selected for automatic inline expansion +#pragma warning(disable : 4820) // '2' bytes padding added after data member + +#if _MSC_VER >= 1400 && _MSC_VER < 1920 +// 1400: string.h: _DBG_MEMCPY_INLINE_ +// 1600 - 191x : smmintrin.h __cplusplus' +// is not defined as a preprocessor macro, replacing with '0' for '#if/#elif' +#pragma warning(disable : 4668) + +// 1400 - 1600 : WinDef.h : 'FARPROC' : +// 1900 - 191x : immintrin.h: _readfsbase_u32 +// no function prototype given : converting '()' to '(void)' +#pragma warning(disable : 4255) +#endif + +#if _MSC_VER >= 1914 +// Compiler will insert Spectre mitigation for memory load if /Qspectre switch specified +#pragma warning(disable : 5045) +#endif + +#endif // _MSC_VER > 1200 +#endif // _MSC_VER + + +#if defined(__clang__) && (__clang_major__ >= 4) + #define Z7_PRAGMA_OPT_DISABLE_LOOP_UNROLL_VECTORIZE \ + _Pragma("clang loop unroll(disable)") \ + _Pragma("clang loop vectorize(disable)") + #define Z7_ATTRIB_NO_VECTORIZE +#elif defined(__GNUC__) && (__GNUC__ >= 5) \ + && (!defined(Z7_MCST_LCC_VERSION) || (Z7_MCST_LCC_VERSION >= 12610)) + #define Z7_ATTRIB_NO_VECTORIZE __attribute__((optimize("no-tree-vectorize"))) + // __attribute__((optimize("no-unroll-loops"))); + #define Z7_PRAGMA_OPT_DISABLE_LOOP_UNROLL_VECTORIZE +#elif defined(_MSC_VER) && (_MSC_VER >= 1920) + #define Z7_PRAGMA_OPT_DISABLE_LOOP_UNROLL_VECTORIZE \ + _Pragma("loop( no_vector )") + #define Z7_ATTRIB_NO_VECTORIZE +#else + #define Z7_PRAGMA_OPT_DISABLE_LOOP_UNROLL_VECTORIZE + #define Z7_ATTRIB_NO_VECTORIZE +#endif + +#if defined(Z7_MSC_VER_ORIGINAL) && (Z7_MSC_VER_ORIGINAL >= 1920) + #define Z7_PRAGMA_OPTIMIZE_FOR_CODE_SIZE _Pragma("optimize ( \"s\", on )") + #define Z7_PRAGMA_OPTIMIZE_DEFAULT _Pragma("optimize ( \"\", on )") +#else + #define Z7_PRAGMA_OPTIMIZE_FOR_CODE_SIZE + #define Z7_PRAGMA_OPTIMIZE_DEFAULT +#endif + + + +#if defined(MY_CPU_X86_OR_AMD64) && ( \ + defined(__clang__) && (__clang_major__ >= 4) \ + || defined(__GNUC__) && (__GNUC__ >= 5)) + #define Z7_ATTRIB_NO_SSE __attribute__((__target__("no-sse"))) +#else + #define Z7_ATTRIB_NO_SSE +#endif + +#define Z7_ATTRIB_NO_VECTOR \ + Z7_ATTRIB_NO_VECTORIZE \ + Z7_ATTRIB_NO_SSE + + +#if defined(__clang__) && (__clang_major__ >= 8) \ + || defined(__GNUC__) && (__GNUC__ >= 1000) \ + /* || defined(_MSC_VER) && (_MSC_VER >= 1920) */ + // GCC is not good for __builtin_expect() + #define Z7_LIKELY(x) (__builtin_expect((x), 1)) + #define Z7_UNLIKELY(x) (__builtin_expect((x), 0)) + // #define Z7_unlikely [[unlikely]] + // #define Z7_likely [[likely]] +#else + #define Z7_LIKELY(x) (x) + #define Z7_UNLIKELY(x) (x) + // #define Z7_likely +#endif + + +#if (defined(Z7_CLANG_VERSION) && (Z7_CLANG_VERSION >= 30600)) + +#if (Z7_CLANG_VERSION < 130000) +#define Z7_DIAGNOSTIC_IGNORE_BEGIN_RESERVED_MACRO_IDENTIFIER \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wreserved-id-macro\"") +#else +#define Z7_DIAGNOSTIC_IGNORE_BEGIN_RESERVED_MACRO_IDENTIFIER \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wreserved-macro-identifier\"") +#endif +#define Z7_DIAGNOSTIC_IGNORE_END_RESERVED_MACRO_IDENTIFIER \ + _Pragma("GCC diagnostic pop") +#else +#define Z7_DIAGNOSTIC_IGNORE_BEGIN_RESERVED_MACRO_IDENTIFIER +#define Z7_DIAGNOSTIC_IGNORE_END_RESERVED_MACRO_IDENTIFIER #endif #define UNUSED_VAR(x) (void)x; diff --git a/src/plugins/7zip/7za/c/CpuArch.c b/src/plugins/7zip/7za/c/CpuArch.c index 0464b82f..342280d0 100644 --- a/src/plugins/7zip/7za/c/CpuArch.c +++ b/src/plugins/7zip/7za/c/CpuArch.c @@ -1,230 +1,970 @@ /* CpuArch.c -- CPU specific code -2016-02-25: Igor Pavlov : Public domain */ +Igor Pavlov : Public domain */ #include "Precomp.h" +// #include + #include "CpuArch.h" #ifdef MY_CPU_X86_OR_AMD64 -#if (defined(_MSC_VER) && !defined(MY_CPU_AMD64)) || defined(__GNUC__) -#define USE_ASM +#undef NEED_CHECK_FOR_CPUID +#if !defined(MY_CPU_AMD64) +#define NEED_CHECK_FOR_CPUID #endif -#if !defined(USE_ASM) && _MSC_VER >= 1500 -#include +/* + cpuid instruction supports (subFunction) parameter in ECX, + that is used only with some specific (function) parameter values. + most functions use only (subFunction==0). +*/ +/* + __cpuid(): MSVC and GCC/CLANG use same function/macro name + but parameters are different. + We use MSVC __cpuid() parameters style for our z7_x86_cpuid() function. +*/ + +#if defined(__GNUC__) /* && (__GNUC__ >= 10) */ \ + || defined(__clang__) /* && (__clang_major__ >= 10) */ + +/* there was some CLANG/GCC compilers that have issues with + rbx(ebx) handling in asm blocks in -fPIC mode (__PIC__ is defined). + compiler's contains the macro __cpuid() that is similar to our code. + The history of __cpuid() changes in CLANG/GCC: + GCC: + 2007: it preserved ebx for (__PIC__ && __i386__) + 2013: it preserved rbx and ebx for __PIC__ + 2014: it doesn't preserves rbx and ebx anymore + we suppose that (__GNUC__ >= 5) fixed that __PIC__ ebx/rbx problem. + CLANG: + 2014+: it preserves rbx, but only for 64-bit code. No __PIC__ check. + Why CLANG cares about 64-bit mode only, and doesn't care about ebx (in 32-bit)? + Do we need __PIC__ test for CLANG or we must care about rbx even if + __PIC__ is not defined? +*/ + +#define ASM_LN "\n" + +#if defined(MY_CPU_AMD64) && defined(__PIC__) \ + && ((defined (__GNUC__) && (__GNUC__ < 5)) || defined(__clang__)) + + /* "=&r" selects free register. It can select even rbx, if that register is free. + "=&D" for (RDI) also works, but the code can be larger with "=&D" + "2"(subFun) : 2 is (zero-based) index in the output constraint list "=c" (ECX). */ + +#define x86_cpuid_MACRO_2(p, func, subFunc) { \ + __asm__ __volatile__ ( \ + ASM_LN "mov %%rbx, %q1" \ + ASM_LN "cpuid" \ + ASM_LN "xchg %%rbx, %q1" \ + : "=a" ((p)[0]), "=&r" ((p)[1]), "=c" ((p)[2]), "=d" ((p)[3]) : "0" (func), "2"(subFunc)); } + +#elif defined(MY_CPU_X86) && defined(__PIC__) \ + && ((defined (__GNUC__) && (__GNUC__ < 5)) || defined(__clang__)) + +#define x86_cpuid_MACRO_2(p, func, subFunc) { \ + __asm__ __volatile__ ( \ + ASM_LN "mov %%ebx, %k1" \ + ASM_LN "cpuid" \ + ASM_LN "xchg %%ebx, %k1" \ + : "=a" ((p)[0]), "=&r" ((p)[1]), "=c" ((p)[2]), "=d" ((p)[3]) : "0" (func), "2"(subFunc)); } + +#else + +#define x86_cpuid_MACRO_2(p, func, subFunc) { \ + __asm__ __volatile__ ( \ + ASM_LN "cpuid" \ + : "=a" ((p)[0]), "=b" ((p)[1]), "=c" ((p)[2]), "=d" ((p)[3]) : "0" (func), "2"(subFunc)); } + #endif -#if defined(USE_ASM) && !defined(MY_CPU_AMD64) -static UInt32 CheckFlag(UInt32 flag) +#define x86_cpuid_MACRO(p, func) x86_cpuid_MACRO_2(p, func, 0) + +void Z7_FASTCALL z7_x86_cpuid(UInt32 p[4], UInt32 func) { - #ifdef _MSC_VER - __asm pushfd; - __asm pop EAX; - __asm mov EDX, EAX; - __asm xor EAX, flag; - __asm push EAX; - __asm popfd; - __asm pushfd; - __asm pop EAX; - __asm xor EAX, EDX; - __asm push EDX; - __asm popfd; - __asm and flag, EAX; - #else - __asm__ __volatile__ ( - "pushf\n\t" - "pop %%EAX\n\t" - "movl %%EAX,%%EDX\n\t" - "xorl %0,%%EAX\n\t" - "push %%EAX\n\t" - "popf\n\t" - "pushf\n\t" - "pop %%EAX\n\t" - "xorl %%EDX,%%EAX\n\t" - "push %%EDX\n\t" - "popf\n\t" - "andl %%EAX, %0\n\t": - "=c" (flag) : "c" (flag) : - "%eax", "%edx"); - #endif - return flag; + x86_cpuid_MACRO(p, func) } -#define CHECK_CPUID_IS_SUPPORTED if (CheckFlag(1 << 18) == 0 || CheckFlag(1 << 21) == 0) return False; -#else -#define CHECK_CPUID_IS_SUPPORTED -#endif -void MyCPUID(UInt32 function, UInt32 *a, UInt32 *b, UInt32 *c, UInt32 *d) +static +void Z7_FASTCALL z7_x86_cpuid_subFunc(UInt32 p[4], UInt32 func, UInt32 subFunc) { - #ifdef USE_ASM + x86_cpuid_MACRO_2(p, func, subFunc) +} - #ifdef _MSC_VER - UInt32 a2, b2, c2, d2; - __asm xor EBX, EBX; - __asm xor ECX, ECX; - __asm xor EDX, EDX; - __asm mov EAX, function; - __asm cpuid; - __asm mov a2, EAX; - __asm mov b2, EBX; - __asm mov c2, ECX; - __asm mov d2, EDX; +Z7_NO_INLINE +UInt32 Z7_FASTCALL z7_x86_cpuid_GetMaxFunc(void) +{ + #if defined(NEED_CHECK_FOR_CPUID) + #define EFALGS_CPUID_BIT 21 + UInt32 a; + __asm__ __volatile__ ( + ASM_LN "pushf" + ASM_LN "pushf" + ASM_LN "pop %0" + // ASM_LN "movl %0, %1" + // ASM_LN "xorl $0x200000, %0" + ASM_LN "btc %1, %0" + ASM_LN "push %0" + ASM_LN "popf" + ASM_LN "pushf" + ASM_LN "pop %0" + ASM_LN "xorl (%%esp), %0" - *a = a2; - *b = b2; - *c = c2; - *d = d2; + ASM_LN "popf" + ASM_LN + : "=&r" (a) // "=a" + : "i" (EFALGS_CPUID_BIT) + ); + if ((a & (1 << EFALGS_CPUID_BIT)) == 0) + return 0; + #endif + { + UInt32 p[4]; + x86_cpuid_MACRO(p, 0) + return p[0]; + } +} - #else +#undef ASM_LN - __asm__ __volatile__ ( - #if defined(MY_CPU_AMD64) && defined(__PIC__) - "mov %%rbx, %%rdi;" - "cpuid;" - "xchg %%rbx, %%rdi;" - : "=a" (*a) , - "=D" (*b) , - #elif defined(MY_CPU_X86) && defined(__PIC__) - "mov %%ebx, %%edi;" - "cpuid;" - "xchgl %%ebx, %%edi;" - : "=a" (*a) , - "=D" (*b) , - #else - "cpuid" - : "=a" (*a) , - "=b" (*b) , - #endif - "=c" (*c) , - "=d" (*d) - : "0" (function)) ; +#elif !defined(_MSC_VER) - #endif - - #else +/* +// for gcc/clang and other: we can try to use __cpuid macro: +#include +void Z7_FASTCALL z7_x86_cpuid(UInt32 p[4], UInt32 func) +{ + __cpuid(func, p[0], p[1], p[2], p[3]); +} +UInt32 Z7_FASTCALL z7_x86_cpuid_GetMaxFunc(void) +{ + return (UInt32)__get_cpuid_max(0, NULL); +} +*/ +// for unsupported cpuid: +void Z7_FASTCALL z7_x86_cpuid(UInt32 p[4], UInt32 func) +{ + UNUSED_VAR(func) + p[0] = p[1] = p[2] = p[3] = 0; +} +UInt32 Z7_FASTCALL z7_x86_cpuid_GetMaxFunc(void) +{ + return 0; +} + +#else // _MSC_VER - int CPUInfo[4]; - __cpuid(CPUInfo, function); - *a = CPUInfo[0]; - *b = CPUInfo[1]; - *c = CPUInfo[2]; - *d = CPUInfo[3]; +#if !defined(MY_CPU_AMD64) +UInt32 __declspec(naked) Z7_FASTCALL z7_x86_cpuid_GetMaxFunc(void) +{ + #if defined(NEED_CHECK_FOR_CPUID) + #define EFALGS_CPUID_BIT 21 + __asm pushfd + __asm pushfd + /* + __asm pop eax + // __asm mov edx, eax + __asm btc eax, EFALGS_CPUID_BIT + __asm push eax + */ + __asm btc dword ptr [esp], EFALGS_CPUID_BIT + __asm popfd + __asm pushfd + __asm pop eax + // __asm xor eax, edx + __asm xor eax, [esp] + // __asm push edx + __asm popfd + __asm and eax, (1 shl EFALGS_CPUID_BIT) + __asm jz end_func + #endif + __asm push ebx + __asm xor eax, eax // func + __asm xor ecx, ecx // subFunction (optional) for (func == 0) + __asm cpuid + __asm pop ebx + #if defined(NEED_CHECK_FOR_CPUID) + end_func: #endif + __asm ret 0 +} + +void __declspec(naked) Z7_FASTCALL z7_x86_cpuid(UInt32 p[4], UInt32 func) +{ + UNUSED_VAR(p) + UNUSED_VAR(func) + __asm push ebx + __asm push edi + __asm mov edi, ecx // p + __asm mov eax, edx // func + __asm xor ecx, ecx // subfunction (optional) for (func == 0) + __asm cpuid + __asm mov [edi ], eax + __asm mov [edi + 4], ebx + __asm mov [edi + 8], ecx + __asm mov [edi + 12], edx + __asm pop edi + __asm pop ebx + __asm ret 0 +} + +static +void __declspec(naked) Z7_FASTCALL z7_x86_cpuid_subFunc(UInt32 p[4], UInt32 func, UInt32 subFunc) +{ + UNUSED_VAR(p) + UNUSED_VAR(func) + UNUSED_VAR(subFunc) + __asm push ebx + __asm push edi + __asm mov edi, ecx // p + __asm mov eax, edx // func + __asm mov ecx, [esp + 12] // subFunc + __asm cpuid + __asm mov [edi ], eax + __asm mov [edi + 4], ebx + __asm mov [edi + 8], ecx + __asm mov [edi + 12], edx + __asm pop edi + __asm pop ebx + __asm ret 4 +} + +#else // MY_CPU_AMD64 + + #if _MSC_VER >= 1600 + #include + #define MY_cpuidex __cpuidex + +static +void Z7_FASTCALL z7_x86_cpuid_subFunc(UInt32 p[4], UInt32 func, UInt32 subFunc) +{ + __cpuidex((int *)p, func, subFunc); +} + + #else +/* + __cpuid (func == (0 or 7)) requires subfunction number in ECX. + MSDN: The __cpuid intrinsic clears the ECX register before calling the cpuid instruction. + __cpuid() in new MSVC clears ECX. + __cpuid() in old MSVC (14.00) x64 doesn't clear ECX + We still can use __cpuid for low (func) values that don't require ECX, + but __cpuid() in old MSVC will be incorrect for some func values: (func == 7). + So here we use the hack for old MSVC to send (subFunction) in ECX register to cpuid instruction, + where ECX value is first parameter for FASTCALL / NO_INLINE func. + So the caller of MY_cpuidex_HACK() sets ECX as subFunction, and + old MSVC for __cpuid() doesn't change ECX and cpuid instruction gets (subFunction) value. + +DON'T remove Z7_NO_INLINE and Z7_FASTCALL for MY_cpuidex_HACK(): !!! +*/ +static +Z7_NO_INLINE void Z7_FASTCALL MY_cpuidex_HACK(Int32 subFunction, Int32 func, Int32 *CPUInfo) +{ + UNUSED_VAR(subFunction) + __cpuid(CPUInfo, func); } + #define MY_cpuidex(info, func, func2) MY_cpuidex_HACK(func2, func, info) + #pragma message("======== MY_cpuidex_HACK WAS USED ========") +static +void Z7_FASTCALL z7_x86_cpuid_subFunc(UInt32 p[4], UInt32 func, UInt32 subFunc) +{ + MY_cpuidex_HACK(subFunc, func, (Int32 *)p); +} + #endif // _MSC_VER >= 1600 + +#if !defined(MY_CPU_AMD64) +/* inlining for __cpuid() in MSVC x86 (32-bit) produces big ineffective code, + so we disable inlining here */ +Z7_NO_INLINE +#endif +void Z7_FASTCALL z7_x86_cpuid(UInt32 p[4], UInt32 func) +{ + MY_cpuidex((Int32 *)p, (Int32)func, 0); +} + +Z7_NO_INLINE +UInt32 Z7_FASTCALL z7_x86_cpuid_GetMaxFunc(void) +{ + Int32 a[4]; + MY_cpuidex(a, 0, 0); + return a[0]; +} + +#endif // MY_CPU_AMD64 +#endif // _MSC_VER + +#if defined(NEED_CHECK_FOR_CPUID) +#define CHECK_CPUID_IS_SUPPORTED { if (z7_x86_cpuid_GetMaxFunc() == 0) return 0; } +#else +#define CHECK_CPUID_IS_SUPPORTED +#endif +#undef NEED_CHECK_FOR_CPUID + -Bool x86cpuid_CheckAndRead(Cx86cpuid *p) +static +BoolInt x86cpuid_Func_1(UInt32 *p) { CHECK_CPUID_IS_SUPPORTED - MyCPUID(0, &p->maxFunc, &p->vendor[0], &p->vendor[2], &p->vendor[1]); - MyCPUID(1, &p->ver, &p->b, &p->c, &p->d); + z7_x86_cpuid(p, 1); return True; } -static const UInt32 kVendors[][3] = +/* +static const UInt32 kVendors[][1] = { - { 0x756E6547, 0x49656E69, 0x6C65746E}, - { 0x68747541, 0x69746E65, 0x444D4163}, - { 0x746E6543, 0x48727561, 0x736C7561} + { 0x756E6547 }, // , 0x49656E69, 0x6C65746E }, + { 0x68747541 }, // , 0x69746E65, 0x444D4163 }, + { 0x746E6543 } // , 0x48727561, 0x736C7561 } }; +*/ + +/* +typedef struct +{ + UInt32 maxFunc; + UInt32 vendor[3]; + UInt32 ver; + UInt32 b; + UInt32 c; + UInt32 d; +} Cx86cpuid; + +enum +{ + CPU_FIRM_INTEL, + CPU_FIRM_AMD, + CPU_FIRM_VIA +}; +int x86cpuid_GetFirm(const Cx86cpuid *p); +#define x86cpuid_ver_GetFamily(ver) (((ver >> 16) & 0xff0) | ((ver >> 8) & 0xf)) +#define x86cpuid_ver_GetModel(ver) (((ver >> 12) & 0xf0) | ((ver >> 4) & 0xf)) +#define x86cpuid_ver_GetStepping(ver) (ver & 0xf) int x86cpuid_GetFirm(const Cx86cpuid *p) { unsigned i; - for (i = 0; i < sizeof(kVendors) / sizeof(kVendors[i]); i++) + for (i = 0; i < sizeof(kVendors) / sizeof(kVendors[0]); i++) { const UInt32 *v = kVendors[i]; - if (v[0] == p->vendor[0] && - v[1] == p->vendor[1] && - v[2] == p->vendor[2]) + if (v[0] == p->vendor[0] + // && v[1] == p->vendor[1] + // && v[2] == p->vendor[2] + ) return (int)i; } return -1; } -Bool CPU_Is_InOrder() +BoolInt CPU_Is_InOrder() { Cx86cpuid p; - int firm; UInt32 family, model; if (!x86cpuid_CheckAndRead(&p)) return True; - family = x86cpuid_GetFamily(p.ver); - model = x86cpuid_GetModel(p.ver); - - firm = x86cpuid_GetFirm(&p); + family = x86cpuid_ver_GetFamily(p.ver); + model = x86cpuid_ver_GetModel(p.ver); - switch (firm) + switch (x86cpuid_GetFirm(&p)) { case CPU_FIRM_INTEL: return (family < 6 || (family == 6 && ( - /* In-Order Atom CPU */ - model == 0x1C /* 45 nm, N4xx, D4xx, N5xx, D5xx, 230, 330 */ - || model == 0x26 /* 45 nm, Z6xx */ - || model == 0x27 /* 32 nm, Z2460 */ - || model == 0x35 /* 32 nm, Z2760 */ - || model == 0x36 /* 32 nm, N2xxx, D2xxx */ + // In-Order Atom CPU + model == 0x1C // 45 nm, N4xx, D4xx, N5xx, D5xx, 230, 330 + || model == 0x26 // 45 nm, Z6xx + || model == 0x27 // 32 nm, Z2460 + || model == 0x35 // 32 nm, Z2760 + || model == 0x36 // 32 nm, N2xxx, D2xxx ))); case CPU_FIRM_AMD: return (family < 5 || (family == 5 && (model < 6 || model == 0xA))); case CPU_FIRM_VIA: return (family < 6 || (family == 6 && model < 0xF)); } - return True; + return False; // v23 : unknown processors are not In-Order } +*/ + +#ifdef _WIN32 +#include "7zWindows.h" +#endif #if !defined(MY_CPU_AMD64) && defined(_WIN32) -#include - -// OPENSAL_7ZIP_PATCH BEGIN -// **************************************************************************** -// SalIsWindowsVersionOrGreater -// -// Based on SDK 8.1 VersionHelpers.h -// Indicates if the current OS version matches, or is greater than, the provided -// version information. This function is useful in confirming a version of Windows -// Server that doesn't share a version number with a client release. -// http://msdn.microsoft.com/en-us/library/windows/desktop/dn424964%28v=vs.85%29.aspx -// - -BOOL SalIsWindowsVersionOrGreater(WORD wMajorVersion, WORD wMinorVersion, WORD wServicePackMajor) -{ - OSVERSIONINFOEXW osvi; - DWORDLONG const dwlConditionMask = VerSetConditionMask(VerSetConditionMask(VerSetConditionMask(0, - VER_MAJORVERSION, VER_GREATER_EQUAL), - VER_MINORVERSION, VER_GREATER_EQUAL), - VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); - - SecureZeroMemory(&osvi, sizeof(osvi)); // replacement for memset (does not require the RTL) - osvi.dwOSVersionInfoSize = sizeof(osvi); - osvi.dwMajorVersion = wMajorVersion; - osvi.dwMinorVersion = wMinorVersion; - osvi.wServicePackMajor = wServicePackMajor; - return VerifyVersionInfoW(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, dwlConditionMask) != FALSE; -} -static Bool CPU_Sys_Is_SSE_Supported() -{ -// OSVERSIONINFO vi; -// vi.dwOSVersionInfoSize = sizeof(vi); -// if (!GetVersionEx(&vi)) -// return False; -// return (vi.dwMajorVersion >= 5); - return SalIsWindowsVersionOrGreater(5, 0, 0) ? True : False; // Petr: is this at least Windows 2000? -} -// OPENSAL_7ZIP_PATCH END +/* for legacy SSE ia32: there is no user-space cpu instruction to check + that OS supports SSE register storing/restoring on context switches. + So we need some OS-specific function to check that it's safe to use SSE registers. +*/ + +Z7_FORCE_INLINE +static BoolInt CPU_Sys_Is_SSE_Supported(void) +{ +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4996) // `GetVersion': was declared deprecated +#endif + /* low byte is major version of Windows + We suppose that any Windows version since + Windows2000 (major == 5) supports SSE registers */ + return (Byte)GetVersion() >= 5; +#if defined(_MSC_VER) + #pragma warning(pop) +#endif +} #define CHECK_SYS_SSE_SUPPORT if (!CPU_Sys_Is_SSE_Supported()) return False; #else #define CHECK_SYS_SSE_SUPPORT #endif -Bool CPU_Is_Aes_Supported() + +#if !defined(MY_CPU_AMD64) + +BoolInt CPU_IsSupported_CMOV(void) { - Cx86cpuid p; + UInt32 a[4]; + if (!x86cpuid_Func_1(&a[0])) + return 0; + return (BoolInt)(a[3] >> 15) & 1; +} + +BoolInt CPU_IsSupported_SSE(void) +{ + UInt32 a[4]; CHECK_SYS_SSE_SUPPORT - if (!x86cpuid_CheckAndRead(&p)) + if (!x86cpuid_Func_1(&a[0])) + return 0; + return (BoolInt)(a[3] >> 25) & 1; +} + +BoolInt CPU_IsSupported_SSE2(void) +{ + UInt32 a[4]; + CHECK_SYS_SSE_SUPPORT + if (!x86cpuid_Func_1(&a[0])) + return 0; + return (BoolInt)(a[3] >> 26) & 1; +} + +#endif + + +static UInt32 x86cpuid_Func_1_ECX(void) +{ + UInt32 a[4]; + CHECK_SYS_SSE_SUPPORT + if (!x86cpuid_Func_1(&a[0])) + return 0; + return a[2]; +} + +BoolInt CPU_IsSupported_AES(void) +{ + return (BoolInt)(x86cpuid_Func_1_ECX() >> 25) & 1; +} + +BoolInt CPU_IsSupported_SSSE3(void) +{ + return (BoolInt)(x86cpuid_Func_1_ECX() >> 9) & 1; +} + +BoolInt CPU_IsSupported_SSE41(void) +{ + return (BoolInt)(x86cpuid_Func_1_ECX() >> 19) & 1; +} + +BoolInt CPU_IsSupported_SHA(void) +{ + CHECK_SYS_SSE_SUPPORT + + if (z7_x86_cpuid_GetMaxFunc() < 7) + return False; + { + UInt32 d[4]; + z7_x86_cpuid(d, 7); + return (BoolInt)(d[1] >> 29) & 1; + } +} + + +BoolInt CPU_IsSupported_SHA512(void) +{ + if (!CPU_IsSupported_AVX2()) return False; // maybe CPU_IsSupported_AVX() is enough here + + if (z7_x86_cpuid_GetMaxFunc() < 7) return False; - return (p.c >> 25) & 1; + { + UInt32 d[4]; + z7_x86_cpuid_subFunc(d, 7, 0); + if (d[0] < 1) // d[0] - is max supported subleaf value + return False; + z7_x86_cpuid_subFunc(d, 7, 1); + return (BoolInt)(d[0]) & 1; + } +} + +/* +MSVC: _xgetbv() intrinsic is available since VS2010SP1. + MSVC also defines (_XCR_XFEATURE_ENABLED_MASK) macro in + that we can use or check. + For any 32-bit x86 we can use asm code in MSVC, + but MSVC asm code is huge after compilation. + So _xgetbv() is better + +ICC: _xgetbv() intrinsic is available (in what version of ICC?) + ICC defines (__GNUC___) and it supports gnu assembler + also ICC supports MASM style code with -use-msasm switch. + but ICC doesn't support __attribute__((__target__)) + +GCC/CLANG 9: + _xgetbv() is macro that works via __builtin_ia32_xgetbv() + and we need __attribute__((__target__("xsave")). + But with __target__("xsave") the function will be not + inlined to function that has no __target__("xsave") attribute. + If we want _xgetbv() call inlining, then we should use asm version + instead of calling _xgetbv(). + Note:intrinsic is broke before GCC 8.2: + https://gcc.gnu.org/bugzilla/show_bug.cgi?id=85684 +*/ + +#if defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 1100) \ + || defined(_MSC_VER) && (_MSC_VER >= 1600) && (_MSC_FULL_VER >= 160040219) \ + || defined(__GNUC__) && (__GNUC__ >= 9) \ + || defined(__clang__) && (__clang_major__ >= 9) +// we define ATTRIB_XGETBV, if we want to use predefined _xgetbv() from compiler +#if defined(__INTEL_COMPILER) +#define ATTRIB_XGETBV +#elif defined(__GNUC__) || defined(__clang__) +// we don't define ATTRIB_XGETBV here, because asm version is better for inlining. +// #define ATTRIB_XGETBV __attribute__((__target__("xsave"))) +#else +#define ATTRIB_XGETBV +#endif +#endif + +#if defined(ATTRIB_XGETBV) +#include +#endif + + +// XFEATURE_ENABLED_MASK/XCR0 +#define MY_XCR_XFEATURE_ENABLED_MASK 0 + +#if defined(ATTRIB_XGETBV) +ATTRIB_XGETBV +#endif +static UInt64 x86_xgetbv_0(UInt32 num) +{ +#if defined(ATTRIB_XGETBV) + { + return + #if (defined(_MSC_VER)) + _xgetbv(num); + #else + __builtin_ia32_xgetbv( + #if !defined(__clang__) + (int) + #endif + num); + #endif + } + +#elif defined(__GNUC__) || defined(__clang__) || defined(__SUNPRO_CC) + + UInt32 a, d; + #if defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 4)) + __asm__ + ( + "xgetbv" + : "=a"(a), "=d"(d) : "c"(num) : "cc" + ); + #else // is old gcc + __asm__ + ( + ".byte 0x0f, 0x01, 0xd0" "\n\t" + : "=a"(a), "=d"(d) : "c"(num) : "cc" + ); + #endif + return ((UInt64)d << 32) | a; + // return a; + +#elif defined(_MSC_VER) && !defined(MY_CPU_AMD64) + + UInt32 a, d; + __asm { + push eax + push edx + push ecx + mov ecx, num; + // xor ecx, ecx // = MY_XCR_XFEATURE_ENABLED_MASK + _emit 0x0f + _emit 0x01 + _emit 0xd0 + mov a, eax + mov d, edx + pop ecx + pop edx + pop eax + } + return ((UInt64)d << 32) | a; + // return a; + +#else // it's unknown compiler + // #error "Need xgetbv function" + UNUSED_VAR(num) + // for MSVC-X64 we could call external function from external file. + /* Actually we had checked OSXSAVE/AVX in cpuid before. + So it's expected that OS supports at least AVX and below. */ + // if (num != MY_XCR_XFEATURE_ENABLED_MASK) return 0; // if not XCR0 + return + // (1 << 0) | // x87 + (1 << 1) // SSE + | (1 << 2); // AVX + +#endif +} + +#ifdef _WIN32 +/* + Windows versions do not know about new ISA extensions that + can be introduced. But we still can use new extensions, + even if Windows doesn't report about supporting them, + But we can use new extensions, only if Windows knows about new ISA extension + that changes the number or size of registers: SSE, AVX/XSAVE, AVX512 + So it's enough to check + MY_PF_AVX_INSTRUCTIONS_AVAILABLE + instead of + MY_PF_AVX2_INSTRUCTIONS_AVAILABLE +*/ +#define MY_PF_XSAVE_ENABLED 17 +// #define MY_PF_SSSE3_INSTRUCTIONS_AVAILABLE 36 +// #define MY_PF_SSE4_1_INSTRUCTIONS_AVAILABLE 37 +// #define MY_PF_SSE4_2_INSTRUCTIONS_AVAILABLE 38 +// #define MY_PF_AVX_INSTRUCTIONS_AVAILABLE 39 +// #define MY_PF_AVX2_INSTRUCTIONS_AVAILABLE 40 +// #define MY_PF_AVX512F_INSTRUCTIONS_AVAILABLE 41 +#endif + +BoolInt CPU_IsSupported_AVX(void) +{ + #ifdef _WIN32 + if (!IsProcessorFeaturePresent(MY_PF_XSAVE_ENABLED)) + return False; + /* PF_AVX_INSTRUCTIONS_AVAILABLE probably is supported starting from + some latest Win10 revisions. But we need AVX in older Windows also. + So we don't use the following check: */ + /* + if (!IsProcessorFeaturePresent(MY_PF_AVX_INSTRUCTIONS_AVAILABLE)) + return False; + */ + #endif + + /* + OS must use new special XSAVE/XRSTOR instructions to save + AVX registers when it required for context switching. + At OS statring: + OS sets CR4.OSXSAVE flag to signal the processor that OS supports the XSAVE extensions. + Also OS sets bitmask in XCR0 register that defines what + registers will be processed by XSAVE instruction: + XCR0.SSE[bit 0] - x87 registers and state + XCR0.SSE[bit 1] - SSE registers and state + XCR0.AVX[bit 2] - AVX registers and state + CR4.OSXSAVE is reflected to CPUID.1:ECX.OSXSAVE[bit 27]. + So we can read that bit in user-space. + XCR0 is available for reading in user-space by new XGETBV instruction. + */ + { + const UInt32 c = x86cpuid_Func_1_ECX(); + if (0 == (1 + & (c >> 28) // AVX instructions are supported by hardware + & (c >> 27))) // OSXSAVE bit: XSAVE and related instructions are enabled by OS. + return False; + } + + /* also we can check + CPUID.1:ECX.XSAVE [bit 26] : that shows that + XSAVE, XRESTOR, XSETBV, XGETBV instructions are supported by hardware. + But that check is redundant, because if OSXSAVE bit is set, then XSAVE is also set */ + + /* If OS have enabled XSAVE extension instructions (OSXSAVE == 1), + in most cases we expect that OS also will support storing/restoring + for AVX and SSE states at least. + But to be ensure for that we call user-space instruction + XGETBV(0) to get XCR0 value that contains bitmask that defines + what exact states(registers) OS have enabled for storing/restoring. + */ + + { + const UInt32 bm = (UInt32)x86_xgetbv_0(MY_XCR_XFEATURE_ENABLED_MASK); + // printf("\n=== XGetBV=0x%x\n", bm); + return 1 + & (BoolInt)(bm >> 1) // SSE state is supported (set by OS) for storing/restoring + & (BoolInt)(bm >> 2); // AVX state is supported (set by OS) for storing/restoring + } + // since Win7SP1: we can use GetEnabledXStateFeatures(); +} + + +BoolInt CPU_IsSupported_AVX2(void) +{ + if (!CPU_IsSupported_AVX()) + return False; + if (z7_x86_cpuid_GetMaxFunc() < 7) + return False; + { + UInt32 d[4]; + z7_x86_cpuid(d, 7); + // printf("\ncpuid(7): ebx=%8x ecx=%8x\n", d[1], d[2]); + return 1 + & (BoolInt)(d[1] >> 5); // avx2 + } +} + +#if 0 +BoolInt CPU_IsSupported_AVX512F_AVX512VL(void) +{ + if (!CPU_IsSupported_AVX()) + return False; + if (z7_x86_cpuid_GetMaxFunc() < 7) + return False; + { + UInt32 d[4]; + BoolInt v; + z7_x86_cpuid(d, 7); + // printf("\ncpuid(7): ebx=%8x ecx=%8x\n", d[1], d[2]); + v = 1 + & (BoolInt)(d[1] >> 16) // avx512f + & (BoolInt)(d[1] >> 31); // avx512vl + if (!v) + return False; + } + { + const UInt32 bm = (UInt32)x86_xgetbv_0(MY_XCR_XFEATURE_ENABLED_MASK); + // printf("\n=== XGetBV=0x%x\n", bm); + return 1 + & (BoolInt)(bm >> 5) // OPMASK + & (BoolInt)(bm >> 6) // ZMM upper 256-bit + & (BoolInt)(bm >> 7); // ZMM16 ... ZMM31 + } +} +#endif + +BoolInt CPU_IsSupported_VAES_AVX2(void) +{ + if (!CPU_IsSupported_AVX()) + return False; + if (z7_x86_cpuid_GetMaxFunc() < 7) + return False; + { + UInt32 d[4]; + z7_x86_cpuid(d, 7); + // printf("\ncpuid(7): ebx=%8x ecx=%8x\n", d[1], d[2]); + return 1 + & (BoolInt)(d[1] >> 5) // avx2 + // & (d[1] >> 31) // avx512vl + & (BoolInt)(d[2] >> 9); // vaes // VEX-256/EVEX + } +} + +BoolInt CPU_IsSupported_PageGB(void) +{ + CHECK_CPUID_IS_SUPPORTED + { + UInt32 d[4]; + z7_x86_cpuid(d, 0x80000000); + if (d[0] < 0x80000001) + return False; + z7_x86_cpuid(d, 0x80000001); + return (BoolInt)(d[3] >> 26) & 1; + } +} + + +#elif defined(MY_CPU_ARM_OR_ARM64) + +#ifdef _WIN32 + +#include "7zWindows.h" + +BoolInt CPU_IsSupported_CRC32(void) { return IsProcessorFeaturePresent(PF_ARM_V8_CRC32_INSTRUCTIONS_AVAILABLE) ? 1 : 0; } +BoolInt CPU_IsSupported_CRYPTO(void) { return IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE) ? 1 : 0; } +BoolInt CPU_IsSupported_NEON(void) { return IsProcessorFeaturePresent(PF_ARM_NEON_INSTRUCTIONS_AVAILABLE) ? 1 : 0; } + +#else + +#if defined(__APPLE__) + +/* +#include +#include +static void Print_sysctlbyname(const char *name) +{ + size_t bufSize = 256; + char buf[256]; + int res = sysctlbyname(name, &buf, &bufSize, NULL, 0); + { + int i; + printf("\nres = %d : %s : '%s' : bufSize = %d, numeric", res, name, buf, (unsigned)bufSize); + for (i = 0; i < 20; i++) + printf(" %2x", (unsigned)(Byte)buf[i]); + + } +} +*/ +/* + Print_sysctlbyname("hw.pagesize"); + Print_sysctlbyname("machdep.cpu.brand_string"); +*/ + +static BoolInt z7_sysctlbyname_Get_BoolInt(const char *name) +{ + UInt32 val = 0; + if (z7_sysctlbyname_Get_UInt32(name, &val) == 0 && val == 1) + return 1; + return 0; +} + +BoolInt CPU_IsSupported_CRC32(void) +{ + return z7_sysctlbyname_Get_BoolInt("hw.optional.armv8_crc32"); +} + +BoolInt CPU_IsSupported_NEON(void) +{ + return z7_sysctlbyname_Get_BoolInt("hw.optional.neon"); +} + +BoolInt CPU_IsSupported_SHA512(void) +{ + return z7_sysctlbyname_Get_BoolInt("hw.optional.armv8_2_sha512"); +} + +/* +BoolInt CPU_IsSupported_SHA3(void) +{ + return z7_sysctlbyname_Get_BoolInt("hw.optional.armv8_2_sha3"); +} +*/ + +#ifdef MY_CPU_ARM64 +#define APPLE_CRYPTO_SUPPORT_VAL 1 +#else +#define APPLE_CRYPTO_SUPPORT_VAL 0 +#endif + +BoolInt CPU_IsSupported_SHA1(void) { return APPLE_CRYPTO_SUPPORT_VAL; } +BoolInt CPU_IsSupported_SHA2(void) { return APPLE_CRYPTO_SUPPORT_VAL; } +BoolInt CPU_IsSupported_AES (void) { return APPLE_CRYPTO_SUPPORT_VAL; } + + +#else // __APPLE__ + +#if defined(__GLIBC__) && (__GLIBC__ * 100 + __GLIBC_MINOR__ >= 216) + #define Z7_GETAUXV_AVAILABLE +#elif !defined(__QNXNTO__) +// #pragma message("=== is not NEW GLIBC === ") + #if defined __has_include + #if __has_include () +// #pragma message("=== sys/auxv.h is avail=== ") + #define Z7_GETAUXV_AVAILABLE + #endif + #endif +#endif + +#ifdef Z7_GETAUXV_AVAILABLE +// #pragma message("=== Z7_GETAUXV_AVAILABLE === ") +#include +#define USE_HWCAP +#endif + +#ifdef USE_HWCAP + +#if defined(__FreeBSD__) || defined(__OpenBSD__) +static unsigned long MY_getauxval(int aux) +{ + unsigned long val; + if (elf_aux_info(aux, &val, sizeof(val))) + return 0; + return val; +} +#else +#define MY_getauxval getauxval + #if defined __has_include + #if __has_include () +#include + #endif + #endif +#endif + + #define MY_HWCAP_CHECK_FUNC_2(name1, name2) \ + BoolInt CPU_IsSupported_ ## name1(void) { return (MY_getauxval(AT_HWCAP) & (HWCAP_ ## name2)); } + +#ifdef MY_CPU_ARM64 + #define MY_HWCAP_CHECK_FUNC(name) \ + MY_HWCAP_CHECK_FUNC_2(name, name) +#if 1 || defined(__ARM_NEON) + BoolInt CPU_IsSupported_NEON(void) { return True; } +#else + MY_HWCAP_CHECK_FUNC_2(NEON, ASIMD) +#endif +// MY_HWCAP_CHECK_FUNC (ASIMD) +#elif defined(MY_CPU_ARM) + #define MY_HWCAP_CHECK_FUNC(name) \ + BoolInt CPU_IsSupported_ ## name(void) { return (MY_getauxval(AT_HWCAP2) & (HWCAP2_ ## name)); } + MY_HWCAP_CHECK_FUNC_2(NEON, NEON) +#endif + +#else // USE_HWCAP + + #define MY_HWCAP_CHECK_FUNC(name) \ + BoolInt CPU_IsSupported_ ## name(void) { return 0; } +#if defined(__ARM_NEON) + BoolInt CPU_IsSupported_NEON(void) { return True; } +#else + MY_HWCAP_CHECK_FUNC(NEON) +#endif + +#endif // USE_HWCAP + +MY_HWCAP_CHECK_FUNC (CRC32) +MY_HWCAP_CHECK_FUNC (SHA1) +MY_HWCAP_CHECK_FUNC (SHA2) +MY_HWCAP_CHECK_FUNC (AES) +#ifdef MY_CPU_ARM64 +// supports HWCAP_SHA512 and HWCAP_SHA3 since 2017. +// we define them here, if they are not defined +#ifndef HWCAP_SHA3 +// #define HWCAP_SHA3 (1 << 17) +#endif +#ifndef HWCAP_SHA512 +// #pragma message("=== HWCAP_SHA512 define === ") +#define HWCAP_SHA512 (1 << 21) +#endif +MY_HWCAP_CHECK_FUNC (SHA512) +// MY_HWCAP_CHECK_FUNC (SHA3) +#endif + +#endif // __APPLE__ +#endif // _WIN32 + +#endif // MY_CPU_ARM_OR_ARM64 + + + +#ifdef __APPLE__ + +#include + +int z7_sysctlbyname_Get(const char *name, void *buf, size_t *bufSize) +{ + return sysctlbyname(name, buf, bufSize, NULL, 0); +} + +int z7_sysctlbyname_Get_UInt32(const char *name, UInt32 *val) +{ + size_t bufSize = sizeof(*val); + const int res = z7_sysctlbyname_Get(name, val, &bufSize); + if (res == 0 && bufSize != sizeof(*val)) + return EFAULT; + return res; } #endif diff --git a/src/plugins/7zip/7za/c/CpuArch.h b/src/plugins/7zip/7za/c/CpuArch.h index e42ce281..348db0a4 100644 --- a/src/plugins/7zip/7za/c/CpuArch.h +++ b/src/plugins/7zip/7za/c/CpuArch.h @@ -1,8 +1,8 @@ /* CpuArch.h -- CPU specific code -2016-06-09: Igor Pavlov : Public domain */ +Igor Pavlov : Public domain */ -#ifndef __CPU_ARCH_H -#define __CPU_ARCH_H +#ifndef ZIP7_INC_CPU_ARCH_H +#define ZIP7_INC_CPU_ARCH_H #include "7zTypes.h" @@ -14,50 +14,251 @@ MY_CPU_BE means that CPU is BIG ENDIAN. If MY_CPU_LE and MY_CPU_BE are not defined, we don't know about ENDIANNESS of platform. MY_CPU_LE_UNALIGN means that CPU is LITTLE ENDIAN and CPU supports unaligned memory accesses. + +MY_CPU_64BIT means that processor can work with 64-bit registers. + MY_CPU_64BIT can be used to select fast code branch + MY_CPU_64BIT doesn't mean that (sizeof(void *) == 8) */ -#if defined(_M_X64) \ - || defined(_M_AMD64) \ - || defined(__x86_64__) \ - || defined(__AMD64__) \ - || defined(__amd64__) +#if !defined(_M_ARM64EC) +#if defined(_M_X64) \ + || defined(_M_AMD64) \ + || defined(__x86_64__) \ + || defined(__AMD64__) \ + || defined(__amd64__) #define MY_CPU_AMD64 + #ifdef __ILP32__ + #define MY_CPU_NAME "x32" + #define MY_CPU_SIZEOF_POINTER 4 + #else + #if defined(__APX_EGPR__) || defined(__EGPR__) + #define MY_CPU_NAME "x64-apx" + #define MY_CPU_AMD64_APX + #else + #define MY_CPU_NAME "x64" + #endif + #define MY_CPU_SIZEOF_POINTER 8 + #endif + #define MY_CPU_64BIT +#endif #endif -#if defined(MY_CPU_AMD64) \ - || defined(_M_IA64) \ - || defined(__AARCH64EL__) \ - || defined(__AARCH64EB__) + +#if defined(_M_IX86) \ + || defined(__i386__) + #define MY_CPU_X86 + #define MY_CPU_NAME "x86" + /* #define MY_CPU_32BIT */ + #define MY_CPU_SIZEOF_POINTER 4 +#endif + +#if defined(__SSE2__) \ + || defined(MY_CPU_AMD64) \ + || defined(_M_IX86_FP) && (_M_IX86_FP >= 2) +#define MY_CPU_SSE2 +#endif + + +#if defined(_M_ARM64) \ + || defined(_M_ARM64EC) \ + || defined(__AARCH64EL__) \ + || defined(__AARCH64EB__) \ + || defined(__aarch64__) + #define MY_CPU_ARM64 +#if defined(__ILP32__) \ + || defined(__SIZEOF_POINTER__) && (__SIZEOF_POINTER__ == 4) + #define MY_CPU_NAME "arm64-32" + #define MY_CPU_SIZEOF_POINTER 4 +#elif defined(__SIZEOF_POINTER__) && (__SIZEOF_POINTER__ == 16) + #define MY_CPU_NAME "arm64-128" + #define MY_CPU_SIZEOF_POINTER 16 +#else +#if defined(_M_ARM64EC) + #define MY_CPU_NAME "arm64ec" +#else + #define MY_CPU_NAME "arm64" +#endif + #define MY_CPU_SIZEOF_POINTER 8 +#endif + #define MY_CPU_64BIT +#endif + + +#if defined(_M_ARM) \ + || defined(_M_ARM_NT) \ + || defined(_M_ARMT) \ + || defined(__arm__) \ + || defined(__thumb__) \ + || defined(__ARMEL__) \ + || defined(__ARMEB__) \ + || defined(__THUMBEL__) \ + || defined(__THUMBEB__) + #define MY_CPU_ARM + + #if defined(__thumb__) || defined(__THUMBEL__) || defined(_M_ARMT) + #define MY_CPU_ARMT + #define MY_CPU_NAME "armt" + #else + #define MY_CPU_ARM32 + #define MY_CPU_NAME "arm" + #endif + /* #define MY_CPU_32BIT */ + #define MY_CPU_SIZEOF_POINTER 4 +#endif + + +#if defined(_M_IA64) \ + || defined(__ia64__) + #define MY_CPU_IA64 + #define MY_CPU_NAME "ia64" + #define MY_CPU_64BIT +#endif + + +#if defined(__mips64) \ + || defined(__mips64__) \ + || (defined(__mips) && (__mips == 64 || __mips == 4 || __mips == 3)) + #define MY_CPU_NAME "mips64" + #define MY_CPU_64BIT +#elif defined(__mips__) + #define MY_CPU_NAME "mips" + /* #define MY_CPU_32BIT */ +#endif + + +#if defined(__ppc64__) \ + || defined(__powerpc64__) \ + || defined(__ppc__) \ + || defined(__powerpc__) \ + || defined(__PPC__) \ + || defined(_POWER) + +#define MY_CPU_PPC_OR_PPC64 + +#if defined(__ppc64__) \ + || defined(__powerpc64__) \ + || defined(_LP64) \ + || defined(__64BIT__) + #ifdef __ILP32__ + #define MY_CPU_NAME "ppc64-32" + #define MY_CPU_SIZEOF_POINTER 4 + #else + #define MY_CPU_NAME "ppc64" + #define MY_CPU_SIZEOF_POINTER 8 + #endif + #define MY_CPU_64BIT +#else + #define MY_CPU_NAME "ppc" + #define MY_CPU_SIZEOF_POINTER 4 + /* #define MY_CPU_32BIT */ +#endif +#endif + + +#if defined(__sparc__) \ + || defined(__sparc) + #define MY_CPU_SPARC + #if defined(__LP64__) \ + || defined(_LP64) \ + || defined(__SIZEOF_POINTER__) && (__SIZEOF_POINTER__ == 8) + #define MY_CPU_NAME "sparcv9" + #define MY_CPU_SIZEOF_POINTER 8 + #define MY_CPU_64BIT + #elif defined(__sparc_v9__) \ + || defined(__sparcv9) + #define MY_CPU_64BIT + #if defined(__SIZEOF_POINTER__) && (__SIZEOF_POINTER__ == 4) + #define MY_CPU_NAME "sparcv9-32" + #else + #define MY_CPU_NAME "sparcv9m" + #endif + #elif defined(__sparc_v8__) \ + || defined(__sparcv8) + #define MY_CPU_NAME "sparcv8" + #define MY_CPU_SIZEOF_POINTER 4 + #else + #define MY_CPU_NAME "sparc" + #endif +#endif + + +#if defined(__riscv) \ + || defined(__riscv__) + #define MY_CPU_RISCV + #if __riscv_xlen == 32 + #define MY_CPU_NAME "riscv32" + #elif __riscv_xlen == 64 + #define MY_CPU_NAME "riscv64" + #else + #define MY_CPU_NAME "riscv" + #endif +#endif + + +#if defined(__loongarch__) + #define MY_CPU_LOONGARCH + #if defined(__loongarch64) || defined(__loongarch_grlen) && (__loongarch_grlen == 64) #define MY_CPU_64BIT + #endif + #if defined(__loongarch64) + #define MY_CPU_NAME "loongarch64" + #define MY_CPU_LOONGARCH64 + #else + #define MY_CPU_NAME "loongarch" + #endif #endif -#if defined(_M_IX86) || defined(__i386__) -#define MY_CPU_X86 + +// #undef MY_CPU_NAME +// #undef MY_CPU_SIZEOF_POINTER +// #define __e2k__ +// #define __SIZEOF_POINTER__ 4 +#if defined(__e2k__) + #define MY_CPU_E2K + #if defined(__ILP32__) || defined(__SIZEOF_POINTER__) && (__SIZEOF_POINTER__ == 4) + #define MY_CPU_NAME "e2k-32" + #define MY_CPU_SIZEOF_POINTER 4 + #else + #define MY_CPU_NAME "e2k" + #if defined(__LP64__) || defined(__SIZEOF_POINTER__) && (__SIZEOF_POINTER__ == 8) + #define MY_CPU_SIZEOF_POINTER 8 + #endif + #endif + #define MY_CPU_64BIT #endif + #if defined(MY_CPU_X86) || defined(MY_CPU_AMD64) #define MY_CPU_X86_OR_AMD64 #endif -#if defined(MY_CPU_X86) \ - || defined(_M_ARM) \ - || defined(__ARMEL__) \ - || defined(__THUMBEL__) \ - || defined(__ARMEB__) \ - || defined(__THUMBEB__) - #define MY_CPU_32BIT +#if defined(MY_CPU_ARM) || defined(MY_CPU_ARM64) +#define MY_CPU_ARM_OR_ARM64 #endif -#if defined(_WIN32) && defined(_M_ARM) -#define MY_CPU_ARM_LE -#endif -#if defined(_WIN32) && defined(_M_IA64) -#define MY_CPU_IA64_LE +#ifdef _WIN32 + + #ifdef MY_CPU_ARM + #define MY_CPU_ARM_LE + #endif + + #ifdef MY_CPU_ARM64 + #define MY_CPU_ARM64_LE + #endif + + #ifdef _M_IA64 + #define MY_CPU_IA64_LE + #endif + #endif + +// _LITTLE_ENDIAN macro can be defined for big-endian platform with some compilers + #if defined(MY_CPU_X86_OR_AMD64) \ || defined(MY_CPU_ARM_LE) \ + || defined(MY_CPU_ARM64_LE) \ || defined(MY_CPU_IA64_LE) \ || defined(__LITTLE_ENDIAN__) \ || defined(__ARMEL__) \ @@ -86,15 +287,186 @@ MY_CPU_LE_UNALIGN means that CPU is LITTLE ENDIAN and CPU supports unaligned mem #define MY_CPU_BE #endif + #if defined(MY_CPU_LE) && defined(MY_CPU_BE) -Stop_Compiling_Bad_Endian + #error Stop_Compiling_Bad_Endian +#endif + +#if !defined(MY_CPU_LE) && !defined(MY_CPU_BE) + #error Stop_Compiling_CPU_ENDIAN_must_be_detected_at_compile_time +#endif + +#if defined(MY_CPU_32BIT) && defined(MY_CPU_64BIT) + #error Stop_Compiling_Bad_32_64_BIT +#endif + +#ifdef __SIZEOF_POINTER__ + #ifdef MY_CPU_SIZEOF_POINTER + #if MY_CPU_SIZEOF_POINTER != __SIZEOF_POINTER__ + #error Stop_Compiling_Bad_MY_CPU_PTR_SIZE + #endif + #else + #define MY_CPU_SIZEOF_POINTER __SIZEOF_POINTER__ + #endif +#endif + +#if defined(MY_CPU_SIZEOF_POINTER) && (MY_CPU_SIZEOF_POINTER == 4) +#if defined (_LP64) + #error Stop_Compiling_Bad_MY_CPU_PTR_SIZE +#endif +#endif + +#ifdef _MSC_VER + #if _MSC_VER >= 1300 + #define MY_CPU_pragma_pack_push_1 __pragma(pack(push, 1)) + #define MY_CPU_pragma_pop __pragma(pack(pop)) + #else + #define MY_CPU_pragma_pack_push_1 + #define MY_CPU_pragma_pop + #endif +#else + #ifdef __xlC__ + #define MY_CPU_pragma_pack_push_1 _Pragma("pack(1)") + #define MY_CPU_pragma_pop _Pragma("pack()") + #else + #define MY_CPU_pragma_pack_push_1 _Pragma("pack(push, 1)") + #define MY_CPU_pragma_pop _Pragma("pack(pop)") + #endif +#endif + + +#ifndef MY_CPU_NAME + // #define MY_CPU_IS_UNKNOWN + #ifdef MY_CPU_LE + #define MY_CPU_NAME "LE" + #elif defined(MY_CPU_BE) + #define MY_CPU_NAME "BE" + #else + /* + #define MY_CPU_NAME "" + */ + #endif #endif + + + +#ifdef __has_builtin + #define Z7_has_builtin(x) __has_builtin(x) +#else + #define Z7_has_builtin(x) 0 +#endif + + +#define Z7_BSWAP32_CONST(v) \ + ( (((UInt32)(v) << 24) ) \ + | (((UInt32)(v) << 8) & (UInt32)0xff0000) \ + | (((UInt32)(v) >> 8) & (UInt32)0xff00 ) \ + | (((UInt32)(v) >> 24) )) + + +#if defined(_MSC_VER) && (_MSC_VER >= 1300) + +#include + +/* Note: these macros will use bswap instruction (486), that is unsupported in 386 cpu */ + +#pragma intrinsic(_byteswap_ushort) +#pragma intrinsic(_byteswap_ulong) +#pragma intrinsic(_byteswap_uint64) + +#define Z7_BSWAP16(v) _byteswap_ushort(v) +#define Z7_BSWAP32(v) _byteswap_ulong (v) +#define Z7_BSWAP64(v) _byteswap_uint64(v) +#define Z7_CPU_FAST_BSWAP_SUPPORTED + +/* GCC can generate slow code that calls function for __builtin_bswap32() for: + - GCC for RISCV, if Zbb/XTHeadBb extension is not used. + - GCC for SPARC. + The code from CLANG for SPARC also is not fastest. + So we don't define Z7_CPU_FAST_BSWAP_SUPPORTED in some cases. +*/ +#elif (!defined(MY_CPU_RISCV) || defined (__riscv_zbb) || defined(__riscv_xtheadbb)) \ + && !defined(MY_CPU_SPARC) \ + && ( \ + (defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3))) \ + || (defined(__clang__) && Z7_has_builtin(__builtin_bswap16)) \ + ) + +#define Z7_BSWAP16(v) __builtin_bswap16(v) +#define Z7_BSWAP32(v) __builtin_bswap32(v) +#define Z7_BSWAP64(v) __builtin_bswap64(v) +#define Z7_CPU_FAST_BSWAP_SUPPORTED + +#else + +#define Z7_BSWAP16(v) ((UInt16) \ + ( ((UInt32)(v) << 8) \ + | ((UInt32)(v) >> 8) \ + )) + +#define Z7_BSWAP32(v) Z7_BSWAP32_CONST(v) + +#define Z7_BSWAP64(v) \ + ( ( ( (UInt64)(v) ) << 8 * 7 ) \ + | ( ( (UInt64)(v) & ((UInt32)0xff << 8 * 1) ) << 8 * 5 ) \ + | ( ( (UInt64)(v) & ((UInt32)0xff << 8 * 2) ) << 8 * 3 ) \ + | ( ( (UInt64)(v) & ((UInt32)0xff << 8 * 3) ) << 8 * 1 ) \ + | ( ( (UInt64)(v) >> 8 * 1 ) & ((UInt32)0xff << 8 * 3) ) \ + | ( ( (UInt64)(v) >> 8 * 3 ) & ((UInt32)0xff << 8 * 2) ) \ + | ( ( (UInt64)(v) >> 8 * 5 ) & ((UInt32)0xff << 8 * 1) ) \ + | ( ( (UInt64)(v) >> 8 * 7 ) ) \ + ) + +#endif + + + #ifdef MY_CPU_LE #if defined(MY_CPU_X86_OR_AMD64) \ - /* || defined(__AARCH64EL__) */ + || defined(MY_CPU_ARM64) \ + || defined(MY_CPU_RISCV) && defined(__riscv_misaligned_fast) \ + || defined(MY_CPU_E2K) && defined(__iset__) && (__iset__ >= 6) #define MY_CPU_LE_UNALIGN + #define MY_CPU_LE_UNALIGN_64 + #elif defined(__ARM_FEATURE_UNALIGNED) +/* === ALIGNMENT on 32-bit arm and LDRD/STRD/LDM/STM instructions. + Description of problems: +problem-1 : 32-bit ARM architecture: + multi-access (pair of 32-bit accesses) instructions (LDRD/STRD/LDM/STM) + require 32-bit (WORD) alignment (by 32-bit ARM architecture). + So there is "Alignment fault exception", if data is not aligned for 32-bit. + +problem-2 : 32-bit kernels and arm64 kernels: + 32-bit linux kernels provide fixup for these "paired" instruction "Alignment fault exception". + So unaligned paired-access instructions work via exception handler in kernel in 32-bit linux. + + But some arm64 kernels do not handle these faults in 32-bit programs. + So we have unhandled exception for such instructions. + Probably some new arm64 kernels have fixed it, and unaligned + paired-access instructions work in new kernels? + +problem-3 : compiler for 32-bit arm: + Compilers use LDRD/STRD/LDM/STM for UInt64 accesses + and for another cases where two 32-bit accesses are fused + to one multi-access instruction. + So UInt64 variables must be aligned for 32-bit, and each + 32-bit access must be aligned for 32-bit, if we want to + avoid "Alignment fault" exception (handled or unhandled). + +problem-4 : performace: + Even if unaligned access is handled by kernel, it will be slow. + So if we allow unaligned access, we can get fast unaligned + single-access, and slow unaligned paired-access. + + We don't allow unaligned access on 32-bit arm, because compiler + genarates paired-access instructions that require 32-bit alignment, + and some arm64 kernels have no handler for these instructions. + Also unaligned paired-access instructions will be slow, if kernel handles them. +*/ + // it must be disabled: + // #define MY_CPU_LE_UNALIGN #endif #endif @@ -103,11 +475,13 @@ Stop_Compiling_Bad_Endian #define GetUi16(p) (*(const UInt16 *)(const void *)(p)) #define GetUi32(p) (*(const UInt32 *)(const void *)(p)) +#ifdef MY_CPU_LE_UNALIGN_64 #define GetUi64(p) (*(const UInt64 *)(const void *)(p)) +#define SetUi64(p, v) { *(UInt64 *)(void *)(p) = (v); } +#endif -#define SetUi16(p, v) { *(UInt16 *)(p) = (v); } -#define SetUi32(p, v) { *(UInt32 *)(p) = (v); } -#define SetUi64(p, v) { *(UInt64 *)(p) = (v); } +#define SetUi16(p, v) { *(UInt16 *)(void *)(p) = (v); } +#define SetUi32(p, v) { *(UInt32 *)(void *)(p) = (v); } #else @@ -121,8 +495,6 @@ Stop_Compiling_Bad_Endian ((UInt32)((const Byte *)(p))[2] << 16) | \ ((UInt32)((const Byte *)(p))[3] << 24)) -#define GetUi64(p) (GetUi32(p) | ((UInt64)GetUi32(((const Byte *)(p)) + 4) << 32)) - #define SetUi16(p, v) { Byte *_ppp_ = (Byte *)(p); UInt32 _vvv_ = (v); \ _ppp_[0] = (Byte)_vvv_; \ _ppp_[1] = (Byte)(_vvv_ >> 8); } @@ -133,32 +505,36 @@ Stop_Compiling_Bad_Endian _ppp_[2] = (Byte)(_vvv_ >> 16); \ _ppp_[3] = (Byte)(_vvv_ >> 24); } -#define SetUi64(p, v) { Byte *_ppp2_ = (Byte *)(p); UInt64 _vvv2_ = (v); \ - SetUi32(_ppp2_ , (UInt32)_vvv2_); \ - SetUi32(_ppp2_ + 4, (UInt32)(_vvv2_ >> 32)); } - #endif -#if defined(MY_CPU_LE_UNALIGN) && /* defined(_WIN64) && */ (_MSC_VER >= 1300) - -/* Note: we use bswap instruction, that is unsupported in 386 cpu */ +#ifndef GetUi64 +#define GetUi64(p) (GetUi32(p) | ((UInt64)GetUi32(((const Byte *)(p)) + 4) << 32)) +#endif -#include +#ifndef SetUi64 +#define SetUi64(p, v) { Byte *_ppp2_ = (Byte *)(p); UInt64 _vvv2_ = (v); \ + SetUi32(_ppp2_ , (UInt32)_vvv2_) \ + SetUi32(_ppp2_ + 4, (UInt32)(_vvv2_ >> 32)) } +#endif -#pragma intrinsic(_byteswap_ulong) -#pragma intrinsic(_byteswap_uint64) -#define GetBe32(p) _byteswap_ulong(*(const UInt32 *)(const Byte *)(p)) -#define GetBe64(p) _byteswap_uint64(*(const UInt64 *)(const Byte *)(p)) -#define SetBe32(p, v) (*(UInt32 *)(void *)(p)) = _byteswap_ulong(v) +#if defined(MY_CPU_LE_UNALIGN) && defined(Z7_CPU_FAST_BSWAP_SUPPORTED) -#elif defined(MY_CPU_LE_UNALIGN) && defined (__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)) +#if 0 +// Z7_BSWAP16 can be slow for x86-msvc +#define GetBe16_to32(p) (Z7_BSWAP16 (*(const UInt16 *)(const void *)(p))) +#else +#define GetBe16_to32(p) (Z7_BSWAP32 (*(const UInt16 *)(const void *)(p)) >> 16) +#endif -#define GetBe32(p) __builtin_bswap32(*(const UInt32 *)(const Byte *)(p)) -#define GetBe64(p) __builtin_bswap64(*(const UInt64 *)(const Byte *)(p)) +#define GetBe32(p) Z7_BSWAP32 (*(const UInt32 *)(const void *)(p)) +#define SetBe32(p, v) { (*(UInt32 *)(void *)(p)) = Z7_BSWAP32(v); } -#define SetBe32(p, v) (*(UInt32 *)(void *)(p)) = __builtin_bswap32(v) +#if defined(MY_CPU_LE_UNALIGN_64) +#define GetBe64(p) Z7_BSWAP64 (*(const UInt64 *)(const void *)(p)) +#define SetBe64(p, v) { (*(UInt64 *)(void *)(p)) = Z7_BSWAP64(v); } +#endif #else @@ -168,8 +544,6 @@ Stop_Compiling_Bad_Endian ((UInt32)((const Byte *)(p))[2] << 8) | \ ((const Byte *)(p))[3] ) -#define GetBe64(p) (((UInt64)GetBe32(p) << 32) | GetBe32(((const Byte *)(p)) + 4)) - #define SetBe32(p, v) { Byte *_ppp_ = (Byte *)(p); UInt32 _vvv_ = (v); \ _ppp_[0] = (Byte)(_vvv_ >> 24); \ _ppp_[1] = (Byte)(_vvv_ >> 16); \ @@ -178,44 +552,151 @@ Stop_Compiling_Bad_Endian #endif +#ifndef GetBe64 +#define GetBe64(p) (((UInt64)GetBe32(p) << 32) | GetBe32(((const Byte *)(p)) + 4)) +#endif + +#ifndef SetBe64 +#define SetBe64(p, v) { Byte *_ppp_ = (Byte *)(p); UInt64 _vvv_ = (v); \ + _ppp_[0] = (Byte)(_vvv_ >> 56); \ + _ppp_[1] = (Byte)(_vvv_ >> 48); \ + _ppp_[2] = (Byte)(_vvv_ >> 40); \ + _ppp_[3] = (Byte)(_vvv_ >> 32); \ + _ppp_[4] = (Byte)(_vvv_ >> 24); \ + _ppp_[5] = (Byte)(_vvv_ >> 16); \ + _ppp_[6] = (Byte)(_vvv_ >> 8); \ + _ppp_[7] = (Byte)_vvv_; } +#endif +#ifndef GetBe16 +#ifdef GetBe16_to32 +#define GetBe16(p) ( (UInt16) GetBe16_to32(p)) +#else #define GetBe16(p) ( (UInt16) ( \ ((UInt16)((const Byte *)(p))[0] << 8) | \ ((const Byte *)(p))[1] )) +#endif +#endif +#if defined(MY_CPU_BE) +#define Z7_CONV_BE_TO_NATIVE_CONST32(v) (v) +#define Z7_CONV_LE_TO_NATIVE_CONST32(v) Z7_BSWAP32_CONST(v) +#define Z7_CONV_NATIVE_TO_BE_32(v) (v) +// #define Z7_GET_NATIVE16_FROM_2_BYTES(b0, b1) ((b1) | ((b0) << 8)) +#elif defined(MY_CPU_LE) +#define Z7_CONV_BE_TO_NATIVE_CONST32(v) Z7_BSWAP32_CONST(v) +#define Z7_CONV_LE_TO_NATIVE_CONST32(v) (v) +#define Z7_CONV_NATIVE_TO_BE_32(v) Z7_BSWAP32(v) +// #define Z7_GET_NATIVE16_FROM_2_BYTES(b0, b1) ((b0) | ((b1) << 8)) +#else +#error Stop_Compiling_Unknown_Endian_CONV +#endif -#ifdef MY_CPU_X86_OR_AMD64 -typedef struct -{ - UInt32 maxFunc; - UInt32 vendor[3]; - UInt32 ver; - UInt32 b; - UInt32 c; - UInt32 d; -} Cx86cpuid; +#if defined(MY_CPU_BE) + +#define GetBe64a(p) (*(const UInt64 *)(const void *)(p)) +#define GetBe32a(p) (*(const UInt32 *)(const void *)(p)) +#define GetBe16a(p) (*(const UInt16 *)(const void *)(p)) +#define SetBe32a(p, v) { *(UInt32 *)(void *)(p) = (v); } +#define SetBe16a(p, v) { *(UInt16 *)(void *)(p) = (v); } + +// gcc and clang for powerpc can transform load byte access to load reverse word access. +// sp we can use byte access instead of word access. Z7_BSWAP64 cab be slow +#if 1 && defined(Z7_CPU_FAST_BSWAP_SUPPORTED) && defined(MY_CPU_64BIT) +#define GetUi64a(p) Z7_BSWAP64 (*(const UInt64 *)(const void *)(p)) +#else +#define GetUi64a(p) GetUi64(p) +#endif + +#if 1 && defined(Z7_CPU_FAST_BSWAP_SUPPORTED) +#define GetUi32a(p) Z7_BSWAP32 (*(const UInt32 *)(const void *)(p)) +#else +#define GetUi32a(p) GetUi32(p) +#endif + +#define GetUi16a(p) GetUi16(p) +#define SetUi32a(p, v) SetUi32(p, v) +#define SetUi16a(p, v) SetUi16(p, v) + +#elif defined(MY_CPU_LE) -enum -{ - CPU_FIRM_INTEL, - CPU_FIRM_AMD, - CPU_FIRM_VIA -}; +#define GetUi64a(p) (*(const UInt64 *)(const void *)(p)) +#define GetUi32a(p) (*(const UInt32 *)(const void *)(p)) +#define GetUi16a(p) (*(const UInt16 *)(const void *)(p)) +#define SetUi32a(p, v) { *(UInt32 *)(void *)(p) = (v); } +#define SetUi16a(p, v) { *(UInt16 *)(void *)(p) = (v); } -void MyCPUID(UInt32 function, UInt32 *a, UInt32 *b, UInt32 *c, UInt32 *d); +#define GetBe64a(p) GetBe64(p) +#define GetBe32a(p) GetBe32(p) +#define GetBe16a(p) GetBe16(p) +#define SetBe32a(p, v) SetBe32(p, v) +#define SetBe16a(p, v) SetBe16(p, v) + +#else +#error Stop_Compiling_Unknown_Endian_CPU_a +#endif -Bool x86cpuid_CheckAndRead(Cx86cpuid *p); -int x86cpuid_GetFirm(const Cx86cpuid *p); -#define x86cpuid_GetFamily(ver) (((ver >> 16) & 0xFF0) | ((ver >> 8) & 0xF)) -#define x86cpuid_GetModel(ver) (((ver >> 12) & 0xF0) | ((ver >> 4) & 0xF)) -#define x86cpuid_GetStepping(ver) (ver & 0xF) +#ifndef GetBe16_to32 +#define GetBe16_to32(p) GetBe16(p) +#endif + + +#if defined(MY_CPU_X86_OR_AMD64) \ + || defined(MY_CPU_ARM_OR_ARM64) \ + || defined(MY_CPU_PPC_OR_PPC64) + #define Z7_CPU_FAST_ROTATE_SUPPORTED +#endif -Bool CPU_Is_InOrder(); -Bool CPU_Is_Aes_Supported(); +#ifdef MY_CPU_X86_OR_AMD64 + +void Z7_FASTCALL z7_x86_cpuid(UInt32 a[4], UInt32 function); +UInt32 Z7_FASTCALL z7_x86_cpuid_GetMaxFunc(void); +#if defined(MY_CPU_AMD64) +#define Z7_IF_X86_CPUID_SUPPORTED +#else +#define Z7_IF_X86_CPUID_SUPPORTED if (z7_x86_cpuid_GetMaxFunc()) +#endif + +BoolInt CPU_IsSupported_AES(void); +BoolInt CPU_IsSupported_AVX(void); +BoolInt CPU_IsSupported_AVX2(void); +BoolInt CPU_IsSupported_AVX512F_AVX512VL(void); +BoolInt CPU_IsSupported_VAES_AVX2(void); +BoolInt CPU_IsSupported_CMOV(void); +BoolInt CPU_IsSupported_SSE(void); +BoolInt CPU_IsSupported_SSE2(void); +BoolInt CPU_IsSupported_SSSE3(void); +BoolInt CPU_IsSupported_SSE41(void); +BoolInt CPU_IsSupported_SHA(void); +BoolInt CPU_IsSupported_SHA512(void); +BoolInt CPU_IsSupported_PageGB(void); + +#elif defined(MY_CPU_ARM_OR_ARM64) + +BoolInt CPU_IsSupported_CRC32(void); +BoolInt CPU_IsSupported_NEON(void); + +#if defined(_WIN32) +BoolInt CPU_IsSupported_CRYPTO(void); +#define CPU_IsSupported_SHA1 CPU_IsSupported_CRYPTO +#define CPU_IsSupported_SHA2 CPU_IsSupported_CRYPTO +#define CPU_IsSupported_AES CPU_IsSupported_CRYPTO +#else +BoolInt CPU_IsSupported_SHA1(void); +BoolInt CPU_IsSupported_SHA2(void); +BoolInt CPU_IsSupported_AES(void); +#endif +BoolInt CPU_IsSupported_SHA512(void); + +#endif + +#if defined(__APPLE__) +int z7_sysctlbyname_Get(const char *name, void *buf, size_t *bufSize); +int z7_sysctlbyname_Get_UInt32(const char *name, UInt32 *val); #endif EXTERN_C_END diff --git a/src/plugins/7zip/7za/c/Delta.c b/src/plugins/7zip/7za/c/Delta.c index e3edd21e..c4a4499f 100644 --- a/src/plugins/7zip/7za/c/Delta.c +++ b/src/plugins/7zip/7za/c/Delta.c @@ -1,5 +1,5 @@ /* Delta.c -- Delta converter -2009-05-26 : Igor Pavlov : Public domain */ +2021-02-09 : Igor Pavlov : Public domain */ #include "Precomp.h" @@ -12,53 +12,158 @@ void Delta_Init(Byte *state) state[i] = 0; } -static void MyMemCpy(Byte *dest, const Byte *src, unsigned size) -{ - unsigned i; - for (i = 0; i < size; i++) - dest[i] = src[i]; -} void Delta_Encode(Byte *state, unsigned delta, Byte *data, SizeT size) { - Byte buf[DELTA_STATE_SIZE]; - unsigned j = 0; - MyMemCpy(buf, state, delta); + Byte temp[DELTA_STATE_SIZE]; + + if (size == 0) + return; + + { + unsigned i = 0; + do + temp[i] = state[i]; + while (++i != delta); + } + + if (size <= delta) + { + unsigned i = 0, k; + do + { + Byte b = *data; + *data++ = (Byte)(b - temp[i]); + temp[i] = b; + } + while (++i != size); + + k = 0; + + do + { + if (i == delta) + i = 0; + state[k] = temp[i++]; + } + while (++k != delta); + + return; + } + { - SizeT i; - for (i = 0; i < size;) + Byte *p = data + size - delta; + { + unsigned i = 0; + do + state[i] = *p++; + while (++i != delta); + } { - for (j = 0; j < delta && i < size; i++, j++) + const Byte *lim = data + delta; + ptrdiff_t dif = -(ptrdiff_t)delta; + + if (((ptrdiff_t)size + dif) & 1) { - Byte b = data[i]; - data[i] = (Byte)(b - buf[j]); - buf[j] = b; + --p; *p = (Byte)(*p - p[dif]); } + + while (p != lim) + { + --p; *p = (Byte)(*p - p[dif]); + --p; *p = (Byte)(*p - p[dif]); + } + + dif = -dif; + + do + { + --p; *p = (Byte)(*p - temp[--dif]); + } + while (dif != 0); } } - if (j == delta) - j = 0; - MyMemCpy(state, buf + j, delta - j); - MyMemCpy(state + delta - j, buf, j); } + void Delta_Decode(Byte *state, unsigned delta, Byte *data, SizeT size) { - Byte buf[DELTA_STATE_SIZE]; - unsigned j = 0; - MyMemCpy(buf, state, delta); + unsigned i; + const Byte *lim; + + if (size == 0) + return; + + i = 0; + lim = data + size; + + if (size <= delta) + { + do + *data = (Byte)(*data + state[i++]); + while (++data != lim); + + for (; delta != i; state++, delta--) + *state = state[i]; + data -= i; + } + else { - SizeT i; - for (i = 0; i < size;) + /* + #define B(n) b ## n + #define I(n) Byte B(n) = state[n]; + #define U(n) { B(n) = (Byte)((B(n)) + *data++); data[-1] = (B(n)); } + #define F(n) if (data != lim) { U(n) } + + if (delta == 1) + { + I(0) + if ((lim - data) & 1) { U(0) } + while (data != lim) { U(0) U(0) } + data -= 1; + } + else if (delta == 2) { - for (j = 0; j < delta && i < size; i++, j++) + I(0) I(1) + lim -= 1; while (data < lim) { U(0) U(1) } + lim += 1; F(0) + data -= 2; + } + else if (delta == 3) + { + I(0) I(1) I(2) + lim -= 2; while (data < lim) { U(0) U(1) U(2) } + lim += 2; F(0) F(1) + data -= 3; + } + else if (delta == 4) + { + I(0) I(1) I(2) I(3) + lim -= 3; while (data < lim) { U(0) U(1) U(2) U(3) } + lim += 3; F(0) F(1) F(2) + data -= 4; + } + else + */ + { + do + { + *data = (Byte)(*data + state[i++]); + data++; + } + while (i != delta); + { - buf[j] = data[i] = (Byte)(buf[j] + data[i]); + ptrdiff_t dif = -(ptrdiff_t)delta; + do + *data = (Byte)(*data + data[dif]); + while (++data != lim); + data += dif; } } } - if (j == delta) - j = 0; - MyMemCpy(state, buf + j, delta - j); - MyMemCpy(state + delta - j, buf, j); + + do + *state++ = *data; + while (++data != lim); } diff --git a/src/plugins/7zip/7za/c/Delta.h b/src/plugins/7zip/7za/c/Delta.h index 2fa54ad6..70609541 100644 --- a/src/plugins/7zip/7za/c/Delta.h +++ b/src/plugins/7zip/7za/c/Delta.h @@ -1,8 +1,8 @@ /* Delta.h -- Delta converter -2013-01-18 : Igor Pavlov : Public domain */ +2023-03-03 : Igor Pavlov : Public domain */ -#ifndef __DELTA_H -#define __DELTA_H +#ifndef ZIP7_INC_DELTA_H +#define ZIP7_INC_DELTA_H #include "7zTypes.h" diff --git a/src/plugins/7zip/7za/c/DllSecur.c b/src/plugins/7zip/7za/c/DllSecur.c new file mode 100644 index 00000000..d3852593 --- /dev/null +++ b/src/plugins/7zip/7za/c/DllSecur.c @@ -0,0 +1,99 @@ +/* DllSecur.c -- DLL loading security +: Igor Pavlov : Public domain */ + +#include "Precomp.h" + +#ifdef _WIN32 + +#include "7zWindows.h" + +#include "DllSecur.h" + +#ifndef UNDER_CE + +Z7_DIAGNOSTIC_IGNORE_CAST_FUNCTION + +typedef BOOL (WINAPI *Func_SetDefaultDllDirectories)(DWORD DirectoryFlags); + +#define MY_LOAD_LIBRARY_SEARCH_USER_DIRS 0x400 +#define MY_LOAD_LIBRARY_SEARCH_SYSTEM32 0x800 + +#define DELIM "\0" + +static const char * const g_Dlls = + "userenv" + DELIM "setupapi" + DELIM "apphelp" + DELIM "propsys" + DELIM "dwmapi" + DELIM "cryptbase" + DELIM "oleacc" + DELIM "clbcatq" + DELIM "version" + #ifndef _CONSOLE + DELIM "uxtheme" + #endif + DELIM; + +#endif + +#ifdef __clang__ + #pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif +#if defined (_MSC_VER) && _MSC_VER >= 1900 +// sysinfoapi.h: kit10: GetVersion was declared deprecated +#pragma warning(disable : 4996) +#endif + +#define IF_NON_VISTA_SET_DLL_DIRS_AND_RETURN \ + if ((UInt16)GetVersion() != 6) { \ + const \ + Func_SetDefaultDllDirectories setDllDirs = \ + (Func_SetDefaultDllDirectories) Z7_CAST_FUNC_C GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), \ + "SetDefaultDllDirectories"); \ + if (setDllDirs) if (setDllDirs(MY_LOAD_LIBRARY_SEARCH_SYSTEM32 | MY_LOAD_LIBRARY_SEARCH_USER_DIRS)) return; } + +void My_SetDefaultDllDirectories(void) +{ + #ifndef UNDER_CE + IF_NON_VISTA_SET_DLL_DIRS_AND_RETURN + #endif +} + + +void LoadSecurityDlls(void) +{ + #ifndef UNDER_CE + // at Vista (ver 6.0) : CoCreateInstance(CLSID_ShellLink, ...) doesn't work after SetDefaultDllDirectories() : Check it ??? + IF_NON_VISTA_SET_DLL_DIRS_AND_RETURN + { + WCHAR buf[MAX_PATH + 100]; + const char *dll; + unsigned pos = GetSystemDirectoryW(buf, MAX_PATH + 2); + if (pos == 0 || pos > MAX_PATH) + return; + if (buf[pos - 1] != '\\') + buf[pos++] = '\\'; + for (dll = g_Dlls; *dll != 0;) + { + WCHAR *dest = &buf[pos]; + for (;;) + { + const char c = *dll++; + if (c == 0) + break; + *dest++ = (Byte)c; + } + dest[0] = '.'; + dest[1] = 'd'; + dest[2] = 'l'; + dest[3] = 'l'; + dest[4] = 0; + // lstrcatW(buf, L".dll"); + LoadLibraryExW(buf, NULL, LOAD_WITH_ALTERED_SEARCH_PATH); + } + } + #endif +} + +#endif // _WIN32 diff --git a/src/plugins/7zip/7za/c/DllSecur.h b/src/plugins/7zip/7za/c/DllSecur.h new file mode 100644 index 00000000..9fa41538 --- /dev/null +++ b/src/plugins/7zip/7za/c/DllSecur.h @@ -0,0 +1,20 @@ +/* DllSecur.h -- DLL loading for security +2023-03-03 : Igor Pavlov : Public domain */ + +#ifndef ZIP7_INC_DLL_SECUR_H +#define ZIP7_INC_DLL_SECUR_H + +#include "7zTypes.h" + +EXTERN_C_BEGIN + +#ifdef _WIN32 + +void My_SetDefaultDllDirectories(void); +void LoadSecurityDlls(void); + +#endif + +EXTERN_C_END + +#endif diff --git a/src/plugins/7zip/7za/c/HuffEnc.c b/src/plugins/7zip/7za/c/HuffEnc.c index 55b497e5..297b41ae 100644 --- a/src/plugins/7zip/7za/c/HuffEnc.c +++ b/src/plugins/7zip/7za/c/HuffEnc.c @@ -1,60 +1,126 @@ /* HuffEnc.c -- functions for Huffman encoding -2016-05-16 : Igor Pavlov : Public domain */ +Igor Pavlov : Public domain */ #include "Precomp.h" +#include + #include "HuffEnc.h" #include "Sort.h" +#include "CpuArch.h" -#define kMaxLen 16 -#define NUM_BITS 10 -#define MASK ((1 << NUM_BITS) - 1) - -#define NUM_COUNTERS 64 +#define kMaxLen Z7_HUFFMAN_LEN_MAX +#define NUM_BITS 10 +#define MASK ((1u << NUM_BITS) - 1) +#define FREQ_MASK (~(UInt32)MASK) +#define NUM_COUNTERS (104 * 2) // (80 * 2) or (128 * 2) : ((prime_number + 1) * 2) for smaller code. -#define HUFFMAN_SPEED_OPT +#if 1 && (defined(MY_CPU_LE) || defined(MY_CPU_BE)) +#if defined(MY_CPU_LE) + #define HI_HALF_OFFSET 1 +#else + #define HI_HALF_OFFSET 0 +#endif +#define LOAD_PARENT(p) ((unsigned)*((const UInt16 *)(p) + HI_HALF_OFFSET)) +#define STORE_PARENT(p, fb, val) *((UInt16 *)(p) + HI_HALF_OFFSET) = (UInt16)(val); +#define STORE_PARENT_DIRECT(p, fb, hi) STORE_PARENT(p, fb, hi) +#define UPDATE_E(eHi) eHi++; +#else +#define LOAD_PARENT(p) ((unsigned)(*(p) >> NUM_BITS)) +#define STORE_PARENT_DIRECT(p, fb, hi) *(p) = ((fb) & MASK) | (hi); // set parent field +#define STORE_PARENT(p, fb, val) STORE_PARENT_DIRECT(p, fb, ((UInt32)(val) << NUM_BITS)) +#define UPDATE_E(eHi) eHi += 1 << NUM_BITS; +#endif -void Huffman_Generate(const UInt32 *freqs, UInt32 *p, Byte *lens, UInt32 numSymbols, UInt32 maxLen) +void Huffman_Generate(const UInt32 *freqs, UInt32 *p, Byte *lens, unsigned numSymbols, unsigned maxLen) { - UInt32 num = 0; - /* if (maxLen > 10) maxLen = 10; */ +#if NUM_COUNTERS > 2 + unsigned counters[NUM_COUNTERS]; +#endif +#if 1 && NUM_COUNTERS > (kMaxLen + 4) * 2 + #define lenCounters (counters) + #define codes (counters + kMaxLen + 4) +#else + unsigned lenCounters[kMaxLen + 1]; + UInt32 codes[kMaxLen + 1]; +#endif + + unsigned num; { - UInt32 i; - - #ifdef HUFFMAN_SPEED_OPT + unsigned i; + // UInt32 sum = 0; + +#if NUM_COUNTERS > 2 - UInt32 counters[NUM_COUNTERS]; +#define CTR_ITEM_FOR_FREQ(freq) \ + counters[(freq) >= NUM_COUNTERS - 1 ? NUM_COUNTERS - 1 : (unsigned)(freq)] + for (i = 0; i < NUM_COUNTERS; i++) counters[i] = 0; - for (i = 0; i < numSymbols; i++) + memset(lens, 0, numSymbols); { - UInt32 freq = freqs[i]; - counters[(freq < NUM_COUNTERS - 1) ? freq : NUM_COUNTERS - 1]++; + const UInt32 *fp = freqs + numSymbols; +#define NUM_UNROLLS 1 +#if NUM_UNROLLS > 1 // use 1 if odd (numSymbols) is possisble + if (numSymbols & 1) + { + UInt32 f; + f = *--fp; CTR_ITEM_FOR_FREQ(f)++; + // sum += f; + } +#endif + do + { + UInt32 f; + fp -= NUM_UNROLLS; + f = fp[0]; CTR_ITEM_FOR_FREQ(f)++; + // sum += f; +#if NUM_UNROLLS > 1 + f = fp[1]; CTR_ITEM_FOR_FREQ(f)++; + // sum += f; +#endif + } + while (fp != freqs); } - - for (i = 1; i < NUM_COUNTERS; i++) +#if 0 + printf("\nsum=%8u numSymbols =%3u ctrs:", sum, numSymbols); { - UInt32 temp = counters[i]; - counters[i] = num; - num += temp; + unsigned k = 0; + for (k = 0; k < NUM_COUNTERS; k++) + printf(" %u", counters[k]); } - - for (i = 0; i < numSymbols; i++) +#endif + + num = counters[1]; + counters[1] = 0; + for (i = 2; i != NUM_COUNTERS; i += 2) { - UInt32 freq = freqs[i]; - if (freq == 0) - lens[i] = 0; - else - p[counters[((freq < NUM_COUNTERS - 1) ? freq : NUM_COUNTERS - 1)]++] = i | (freq << NUM_BITS); + const unsigned c0 = (counters )[i]; + const unsigned c1 = (counters + 1)[i]; + (counters )[i] = num; num += c0; + (counters + 1)[i] = num; num += c1; + } + counters[0] = num; // we want to write (freq==0) symbols to the end of (p) array + { + i = 0; + do + { + const UInt32 f = freqs[i]; +#if 0 + if (f == 0) lens[i] = 0; else +#endif + p[CTR_ITEM_FOR_FREQ(f)++] = i | (f << NUM_BITS); + } + while (++i != numSymbols); } - counters[0] = 0; HeapSort(p + counters[NUM_COUNTERS - 2], counters[NUM_COUNTERS - 1] - counters[NUM_COUNTERS - 2]); - #else - +#else // NUM_COUNTERS <= 2 + + num = 0; for (i = 0; i < numSymbols; i++) { - UInt32 freq = freqs[i]; + const UInt32 freq = freqs[i]; if (freq == 0) lens[i] = 0; else @@ -62,17 +128,27 @@ void Huffman_Generate(const UInt32 *freqs, UInt32 *p, Byte *lens, UInt32 numSymb } HeapSort(p, num); - #endif +#endif } - if (num < 2) + if (num <= 2) { unsigned minCode = 0; unsigned maxCode = 1; - if (num == 1) + if (num) { - maxCode = (unsigned)p[0] & MASK; - if (maxCode == 0) + maxCode = (unsigned)p[(size_t)num - 1] & MASK; + if (num == 2) + { + minCode = (unsigned)p[0] & MASK; + if (minCode > maxCode) + { + const unsigned temp = minCode; + minCode = maxCode; + maxCode = temp; + } + } + else if (maxCode == 0) maxCode++; } p[minCode] = 0; @@ -80,69 +156,206 @@ void Huffman_Generate(const UInt32 *freqs, UInt32 *p, Byte *lens, UInt32 numSymb lens[minCode] = lens[maxCode] = 1; return; } - { - UInt32 b, e, i; - - i = b = e = 0; - do + unsigned i; + for (i = 0; i <= kMaxLen; i++) + lenCounters[i] = 0; + lenCounters[1] = 2; // by default root node has 2 child leaves at level 1. + } + // if (num != 2) + { + // num > 2 + // the binary tree will contain (num - 1) internal nodes. + // p[num - 2] will be root node of binary tree. + UInt32 *b; + UInt32 *n; + // first node will have two leaf childs: p[0] and p[1]: + // p[0] += p[1] & FREQ_MASK; // set frequency sum of child leafs + // if (pi == n) exit(0); + // if (pi != n) { - UInt32 n, m, freq; - n = (i != num && (b == e || (p[i] >> NUM_BITS) <= (p[b] >> NUM_BITS))) ? i++ : b++; - freq = (p[n] & ~MASK); - p[n] = (p[n] & MASK) | (e << NUM_BITS); - m = (i != num && (b == e || (p[i] >> NUM_BITS) <= (p[b] >> NUM_BITS))) ? i++ : b++; - freq += (p[m] & ~MASK); - p[m] = (p[m] & MASK) | (e << NUM_BITS); - p[e] = (p[e] & MASK) | freq; - e++; + UInt32 fb = (p[1] & FREQ_MASK) + p[0]; + UInt32 f = p[2] & FREQ_MASK; + const UInt32 *pi = p + 2; + UInt32 *e = p; + UInt32 eHi = 0; + n = p + num; + b = p; + // p[0] = fb; + for (;;) + { + // (b <= e) + UInt32 sum; + e++; + UPDATE_E(eHi) + + // (b < e) + + // p range : high bits + // [0, b) : parent : processed nodes that have parent and childs + // [b, e) : FREQ : non-processed nodes that have no parent but have childs + // [e, pi) : FREQ : processed leaves for which parent node was created + // [pi, n) : FREQ : non-processed leaves for which parent node was not created + + // first child + // note : (*b < f) is same result as ((*b & FREQ_MASK) < f) + if (fb < f) + { + // node freq is smaller + sum = fb & FREQ_MASK; + STORE_PARENT_DIRECT (b, fb, eHi) + b++; + fb = *b; + if (b == e) + { + if (++pi == n) + break; + sum += f; + fb &= MASK; + fb |= sum; + *e = fb; + f = *pi & FREQ_MASK; + continue; + } + } + else if (++pi == n) + { + STORE_PARENT_DIRECT (b, fb, eHi) + b++; + break; + } + else + { + sum = f; + f = *pi & FREQ_MASK; + } + + // (b < e) + + // second child + if (fb < f) + { + sum += fb; + sum &= FREQ_MASK; + STORE_PARENT_DIRECT (b, fb, eHi) + b++; + *e = (*e & MASK) | sum; // set frequency sum + // (b <= e) is possible here + fb = *b; + } + else if (++pi == n) + break; + else + { + sum += f; + f = *pi & FREQ_MASK; + *e = (*e & MASK) | sum; // set frequency sum + } + } } - while (num - e > 1); + // printf("\nnum-e=%3u, numSymbols=%3u, num=%3u, b=%3u", n - e, numSymbols, n - p, b - p); { - UInt32 lenCounters[kMaxLen + 1]; - for (i = 0; i <= kMaxLen; i++) - lenCounters[i] = 0; - - p[--e] &= MASK; - lenCounters[1] = 2; - while (e > 0) - { - UInt32 len = (p[p[--e] >> NUM_BITS] >> NUM_BITS) + 1; - p[e] = (p[e] & MASK) | (len << NUM_BITS); - if (len >= maxLen) - for (len = maxLen - 1; lenCounters[len] == 0; len--); - lenCounters[len]--; - lenCounters[len + 1] += 2; - } - + n -= 2; + *n &= MASK; // root node : we clear high bits (zero bits mean level == 0) + if (n != b) { - UInt32 len; - i = 0; - for (len = maxLen; len != 0; len--) + // We go here, if we have some number of non-created nodes up to root. + // We process them in simplified code: + // position of parent for each pair of nodes is known. + // n[-2], n[-1] : current pair of child nodes + // (p1) : parent node for current pair. + UInt32 *p1 = n; + do { - UInt32 k; - for (k = lenCounters[len]; k != 0; k--) - lens[p[i++] & MASK] = (Byte)len; + const unsigned len = LOAD_PARENT(p1) + 1; + p1--; + (lenCounters )[len] -= 2; // we remove 2 leaves from level (len) + (lenCounters + 1)[len] += 2 * 2; // we add 4 leaves at level (len + 1) + n -= 2; + STORE_PARENT (n , n[0], len) + STORE_PARENT (n + 1, n[1], len) } + while (n != b); } - + } + + if (b != p) + { + // we detect level of each node (realtive to root), + // and update lenCounters[]. + // We process only intermediate nodes and we don't process leaves. + do { - UInt32 nextCodes[kMaxLen + 1]; - { - UInt32 code = 0; - UInt32 len; - for (len = 1; len <= kMaxLen; len++) - nextCodes[len] = code = (code + lenCounters[len - 1]) << 1; - } - /* if (code + lenCounters[kMaxLen] - 1 != (1 << kMaxLen) - 1) throw 1; */ - + // if (ii < b) : parent_bits_of (p[ii]) == index of parent node : ii < (p[ii]) + // if (ii >= b) : parent_bits_of (p[ii]) == level of this (ii) node in tree + unsigned len; + b--; + len = (unsigned)LOAD_PARENT(p + LOAD_PARENT(b)) + 1; + STORE_PARENT (b, *b, len) + if (len >= maxLen) { - UInt32 k; - for (k = 0; k < numSymbols; k++) - p[k] = nextCodes[lens[k]]++; + // We are not allowed to create node at level (maxLen) and higher, + // because all leaves must be placed to level (maxLen) or lower. + // We find nearest allowed leaf and place current node to level of that leaf: + for (len = maxLen - 1; lenCounters[len] == 0; len--) {} } + lenCounters[len]--; // we remove 1 leaf from level (len) + (lenCounters + 1)[len] += 2; // we add 2 leaves at level (len + 1) } + while (b != p); + } + } + { + { + unsigned len = maxLen; + const UInt32 *p2 = p; + do + { + unsigned k = lenCounters[len]; + if (k) + do + lens[(unsigned)*p2++ & MASK] = (Byte)len; + while (--k); + } + while (--len); + } + codes[0] = 0; // we don't want garbage values to be written to p[] array. + // codes[1] = 0; + { + UInt32 code = 0; + unsigned len; + for (len = 0; len < kMaxLen; len++) + (codes + 1)[len] = code = (code + lenCounters[len]) << 1; + } + /* if (code + lenCounters[kMaxLen] - 1 != (1 << kMaxLen) - 1) throw 1; */ + { + const Byte * const limit = lens + numSymbols; + do + { + unsigned len; + UInt32 c; + len = lens[0]; c = codes[len]; p[0] = c; codes[len] = c + 1; + // len = lens[1]; c = codes[len]; p[1] = c; codes[len] = c + 1; + p += 1; + lens += 1; + } + while (lens != limit); } } } + +#undef kMaxLen +#undef NUM_BITS +#undef MASK +#undef FREQ_MASK +#undef NUM_COUNTERS +#undef CTR_ITEM_FOR_FREQ +#undef LOAD_PARENT +#undef STORE_PARENT +#undef STORE_PARENT_DIRECT +#undef UPDATE_E +#undef HI_HALF_OFFSET +#undef NUM_UNROLLS +#undef lenCounters +#undef codes diff --git a/src/plugins/7zip/7za/c/HuffEnc.h b/src/plugins/7zip/7za/c/HuffEnc.h index 92b6878d..6cca4a55 100644 --- a/src/plugins/7zip/7za/c/HuffEnc.h +++ b/src/plugins/7zip/7za/c/HuffEnc.h @@ -1,22 +1,22 @@ /* HuffEnc.h -- Huffman encoding -2013-01-18 : Igor Pavlov : Public domain */ +Igor Pavlov : Public domain */ -#ifndef __HUFF_ENC_H -#define __HUFF_ENC_H +#ifndef ZIP7_INC_HUFF_ENC_H +#define ZIP7_INC_HUFF_ENC_H #include "7zTypes.h" EXTERN_C_BEGIN +#define Z7_HUFFMAN_LEN_MAX 16 +#define Z7_HUFFMAN_FREQS_SUM_MAX ((1 << 22) - 1) /* Conditions: - num <= 1024 = 2 ^ NUM_BITS - Sum(freqs) < 4M = 2 ^ (32 - NUM_BITS) - maxLen <= 16 = kMaxLen - Num_Items(p) >= HUFFMAN_TEMP_SIZE(num) + 2 <= num <= 1024 = 2 ^ NUM_BITS + Sum(freqs) <= Z7_HUFFMAN_FREQS_SUM_MAX = 4M - 1 = 2 ^ (32 - NUM_BITS) - 1 + 1 <= maxLen <= 16 = Z7_HUFFMAN_LEN_MAX */ - -void Huffman_Generate(const UInt32 *freqs, UInt32 *p, Byte *lens, UInt32 num, UInt32 maxLen); +void Huffman_Generate(const UInt32 *freqs, UInt32 *p, Byte *lens, unsigned num, unsigned maxLen); EXTERN_C_END diff --git a/src/plugins/7zip/7za/c/LzFind.c b/src/plugins/7zip/7za/c/LzFind.c index 2d05fa39..330bc172 100644 --- a/src/plugins/7zip/7za/c/LzFind.c +++ b/src/plugins/7zip/7za/c/LzFind.c @@ -1,74 +1,140 @@ /* LzFind.c -- Match finder for LZ algorithms -2015-10-15 : Igor Pavlov : Public domain */ +: Igor Pavlov : Public domain */ #include "Precomp.h" #include +// #include +#include "CpuArch.h" #include "LzFind.h" #include "LzHash.h" +#define kBlockMoveAlign (1 << 7) // alignment for memmove() +#define kBlockSizeAlign (1 << 16) // alignment for block allocation +#define kBlockSizeReserveMin (1 << 24) // it's 1/256 from 4 GB dictinary + #define kEmptyHashValue 0 -#define kMaxValForNormalize ((UInt32)0xFFFFFFFF) -#define kNormalizeStepMin (1 << 10) /* it must be power of 2 */ -#define kNormalizeMask (~(UInt32)(kNormalizeStepMin - 1)) -#define kMaxHistorySize ((UInt32)7 << 29) -#define kStartMaxLen 3 +#define kMaxValForNormalize ((UInt32)0) +// #define kMaxValForNormalize ((UInt32)(1 << 20) + 0xfff) // for debug + +// #define kNormalizeAlign (1 << 7) // alignment for speculated accesses + +#define GET_AVAIL_BYTES(p) \ + Inline_MatchFinder_GetNumAvailableBytes(p) + + +// #define kFix5HashSize (kHash2Size + kHash3Size + kHash4Size) +#define kFix5HashSize kFix4HashSize + +/* + HASH2_CALC: + if (hv) match, then cur[0] and cur[1] also match +*/ +#define HASH2_CALC hv = GetUi16(cur); + +// (crc[0 ... 255] & 0xFF) provides one-to-one correspondence to [0 ... 255] -static void LzInWindow_Free(CMatchFinder *p, ISzAlloc *alloc) +/* + HASH3_CALC: + if (cur[0]) and (h2) match, then cur[1] also match + if (cur[0]) and (hv) match, then cur[1] and cur[2] also match +*/ +#define HASH3_CALC { \ + UInt32 temp = p->crc[cur[0]] ^ cur[1]; \ + h2 = temp & (kHash2Size - 1); \ + hv = (temp ^ ((UInt32)cur[2] << 8)) & p->hashMask; } + +#define HASH4_CALC { \ + UInt32 temp = p->crc[cur[0]] ^ cur[1]; \ + h2 = temp & (kHash2Size - 1); \ + temp ^= ((UInt32)cur[2] << 8); \ + h3 = temp & (kHash3Size - 1); \ + hv = (temp ^ (p->crc[cur[3]] << kLzHash_CrcShift_1)) & p->hashMask; } + +#define HASH5_CALC { \ + UInt32 temp = p->crc[cur[0]] ^ cur[1]; \ + h2 = temp & (kHash2Size - 1); \ + temp ^= ((UInt32)cur[2] << 8); \ + h3 = temp & (kHash3Size - 1); \ + temp ^= (p->crc[cur[3]] << kLzHash_CrcShift_1); \ + /* h4 = temp & p->hash4Mask; */ /* (kHash4Size - 1); */ \ + hv = (temp ^ (p->crc[cur[4]] << kLzHash_CrcShift_2)) & p->hashMask; } + +#define HASH_ZIP_CALC hv = ((cur[2] | ((UInt32)cur[0] << 8)) ^ p->crc[cur[1]]) & 0xFFFF; + + +static void LzInWindow_Free(CMatchFinder *p, ISzAllocPtr alloc) { - if (!p->directInput) + // if (!p->directInput) { - alloc->Free(alloc, p->bufferBase); - p->bufferBase = NULL; + ISzAlloc_Free(alloc, p->bufBase); + p->bufBase = NULL; } } -/* keepSizeBefore + keepSizeAfter + keepSizeReserv must be < 4G) */ -static int LzInWindow_Create(CMatchFinder *p, UInt32 keepSizeReserv, ISzAlloc *alloc) +static int LzInWindow_Create2(CMatchFinder *p, UInt32 blockSize, ISzAllocPtr alloc) { - UInt32 blockSize = p->keepSizeBefore + p->keepSizeAfter + keepSizeReserv; - if (p->directInput) - { - p->blockSize = blockSize; - return 1; - } - if (!p->bufferBase || p->blockSize != blockSize) + if (blockSize == 0) + return 0; + if (!p->bufBase || p->blockSize != blockSize) { + // size_t blockSizeT; LzInWindow_Free(p, alloc); p->blockSize = blockSize; - p->bufferBase = (Byte *)alloc->Alloc(alloc, (size_t)blockSize); + // blockSizeT = blockSize; + + // printf("\nblockSize = 0x%x\n", blockSize); + /* + #if defined _WIN64 + // we can allocate 4GiB, but still use UInt32 for (p->blockSize) + // we use UInt32 type for (p->blockSize), because + // we don't want to wrap over 4 GiB, + // when we use (p->streamPos - p->pos) that is UInt32. + if (blockSize >= (UInt32)0 - (UInt32)kBlockSizeAlign) + { + blockSizeT = ((size_t)1 << 32); + printf("\nchanged to blockSizeT = 4GiB\n"); + } + #endif + */ + + p->bufBase = (Byte *)ISzAlloc_Alloc(alloc, blockSize); + // printf("\nbufferBase = %p\n", p->bufBase); + // return 0; // for debug } - return (p->bufferBase != NULL); + return (p->bufBase != NULL); } -Byte *MatchFinder_GetPointerToCurrentPos(CMatchFinder *p) { return p->buffer; } - -UInt32 MatchFinder_GetNumAvailableBytes(CMatchFinder *p) { return p->streamPos - p->pos; } +static const Byte *MatchFinder_GetPointerToCurrentPos(void *p) +{ + return ((CMatchFinder *)p)->buffer; +} -void MatchFinder_ReduceOffsets(CMatchFinder *p, UInt32 subValue) +static UInt32 MatchFinder_GetNumAvailableBytes(void *p) { - p->posLimit -= subValue; - p->pos -= subValue; - p->streamPos -= subValue; + return GET_AVAIL_BYTES((CMatchFinder *)p); } + +Z7_NO_INLINE static void MatchFinder_ReadBlock(CMatchFinder *p) { if (p->streamEndWasReached || p->result != SZ_OK) return; - /* We use (p->streamPos - p->pos) value. (p->streamPos < p->pos) is allowed. */ + /* We use (p->streamPos - p->pos) value. + (p->streamPos < p->pos) is allowed. */ if (p->directInput) { - UInt32 curSize = 0xFFFFFFFF - (p->streamPos - p->pos); + UInt32 curSize = 0xFFFFFFFF - GET_AVAIL_BYTES(p); if (curSize > p->directInputRem) curSize = (UInt32)p->directInputRem; - p->directInputRem -= curSize; p->streamPos += curSize; + p->directInputRem -= curSize; if (p->directInputRem == 0) p->streamEndWasReached = 1; return; @@ -76,12 +142,31 @@ static void MatchFinder_ReadBlock(CMatchFinder *p) for (;;) { - Byte *dest = p->buffer + (p->streamPos - p->pos); - size_t size = (p->bufferBase + p->blockSize - dest); + const Byte *dest = p->buffer + GET_AVAIL_BYTES(p); + size_t size = (size_t)(p->bufBase + p->blockSize - dest); if (size == 0) + { + /* we call ReadBlock() after NeedMove() and MoveBlock(). + NeedMove() and MoveBlock() povide more than (keepSizeAfter) + to the end of (blockSize). + So we don't execute this branch in normal code flow. + We can go here, if we will call ReadBlock() before NeedMove(), MoveBlock(). + */ + // p->result = SZ_ERROR_FAIL; // we can show error here return; + } - p->result = p->stream->Read(p->stream, dest, &size); + // #define kRead 3 + // if (size > kRead) size = kRead; // for debug + + /* + // we need cast (Byte *)dest. + #ifdef __clang__ + #pragma GCC diagnostic ignored "-Wcast-qual" + #endif + */ + p->result = ISeqInStream_Read(p->stream, + p->bufBase + (dest - p->bufBase), &size); if (p->result != SZ_OK) return; if (size == 0) @@ -90,47 +175,60 @@ static void MatchFinder_ReadBlock(CMatchFinder *p) return; } p->streamPos += (UInt32)size; - if (p->streamPos - p->pos > p->keepSizeAfter) + if (GET_AVAIL_BYTES(p) > p->keepSizeAfter) return; + /* here and in another (p->keepSizeAfter) checks we keep on 1 byte more than was requested by Create() function + (GET_AVAIL_BYTES(p) >= p->keepSizeAfter) - minimal required size */ } + + // on exit: (p->result != SZ_OK || p->streamEndWasReached || GET_AVAIL_BYTES(p) > p->keepSizeAfter) } + + +Z7_NO_INLINE void MatchFinder_MoveBlock(CMatchFinder *p) { - memmove(p->bufferBase, - p->buffer - p->keepSizeBefore, - (size_t)(p->streamPos - p->pos) + p->keepSizeBefore); - p->buffer = p->bufferBase + p->keepSizeBefore; + const size_t offset = (size_t)(p->buffer - p->bufBase) - p->keepSizeBefore; + const size_t keepBefore = (offset & (kBlockMoveAlign - 1)) + p->keepSizeBefore; + p->buffer = p->bufBase + keepBefore; + memmove(p->bufBase, + p->bufBase + (offset & ~((size_t)kBlockMoveAlign - 1)), + keepBefore + (size_t)GET_AVAIL_BYTES(p)); } +/* We call MoveBlock() before ReadBlock(). + So MoveBlock() can be wasteful operation, if the whole input data + can fit in current block even without calling MoveBlock(). + in important case where (dataSize <= historySize) + condition (p->blockSize > dataSize + p->keepSizeAfter) is met + So there is no MoveBlock() in that case case. +*/ + int MatchFinder_NeedMove(CMatchFinder *p) { if (p->directInput) return 0; - /* if (p->streamEndWasReached) return 0; */ - return ((size_t)(p->bufferBase + p->blockSize - p->buffer) <= p->keepSizeAfter); + if (p->streamEndWasReached || p->result != SZ_OK) + return 0; + return ((size_t)(p->bufBase + p->blockSize - p->buffer) <= p->keepSizeAfter); } void MatchFinder_ReadIfRequired(CMatchFinder *p) { - if (p->streamEndWasReached) - return; - if (p->keepSizeAfter >= p->streamPos - p->pos) + if (p->keepSizeAfter >= GET_AVAIL_BYTES(p)) MatchFinder_ReadBlock(p); } -static void MatchFinder_CheckAndMoveAndRead(CMatchFinder *p) -{ - if (MatchFinder_NeedMove(p)) - MatchFinder_MoveBlock(p); - MatchFinder_ReadBlock(p); -} + static void MatchFinder_SetDefaultSettings(CMatchFinder *p) { p->cutValue = 32; p->btMode = 1; p->numHashBytes = 4; + p->numHashBytes_Min = 2; + p->numHashOutBits = 0; p->bigHash = 0; } @@ -138,113 +236,248 @@ static void MatchFinder_SetDefaultSettings(CMatchFinder *p) void MatchFinder_Construct(CMatchFinder *p) { - UInt32 i; - p->bufferBase = NULL; + unsigned i; + p->buffer = NULL; + p->bufBase = NULL; p->directInput = 0; + p->stream = NULL; p->hash = NULL; + p->expectedDataSize = (UInt64)(Int64)-1; MatchFinder_SetDefaultSettings(p); for (i = 0; i < 256; i++) { - UInt32 r = i; + UInt32 r = (UInt32)i; unsigned j; for (j = 0; j < 8; j++) - r = (r >> 1) ^ (kCrcPoly & ~((r & 1) - 1)); + r = (r >> 1) ^ (kCrcPoly & ((UInt32)0 - (r & 1))); p->crc[i] = r; } } -static void MatchFinder_FreeThisClassMemory(CMatchFinder *p, ISzAlloc *alloc) +#undef kCrcPoly + +static void MatchFinder_FreeThisClassMemory(CMatchFinder *p, ISzAllocPtr alloc) { - alloc->Free(alloc, p->hash); + ISzAlloc_Free(alloc, p->hash); p->hash = NULL; } -void MatchFinder_Free(CMatchFinder *p, ISzAlloc *alloc) +void MatchFinder_Free(CMatchFinder *p, ISzAllocPtr alloc) { MatchFinder_FreeThisClassMemory(p, alloc); LzInWindow_Free(p, alloc); } -static CLzRef* AllocRefs(size_t num, ISzAlloc *alloc) +static CLzRef* AllocRefs(size_t num, ISzAllocPtr alloc) { - size_t sizeInBytes = (size_t)num * sizeof(CLzRef); + const size_t sizeInBytes = (size_t)num * sizeof(CLzRef); if (sizeInBytes / sizeof(CLzRef) != num) return NULL; - return (CLzRef *)alloc->Alloc(alloc, sizeInBytes); + return (CLzRef *)ISzAlloc_Alloc(alloc, sizeInBytes); } -int MatchFinder_Create(CMatchFinder *p, UInt32 historySize, - UInt32 keepAddBufferBefore, UInt32 matchMaxLen, UInt32 keepAddBufferAfter, - ISzAlloc *alloc) +#if (kBlockSizeReserveMin < kBlockSizeAlign * 2) + #error Stop_Compiling_Bad_Reserve +#endif + + + +static UInt32 GetBlockSize(CMatchFinder *p, UInt32 historySize) { - UInt32 sizeReserv; - + UInt32 blockSize = (p->keepSizeBefore + p->keepSizeAfter); + /* if (historySize > kMaxHistorySize) - { - MatchFinder_Free(p, alloc); return 0; - } + */ + // printf("\nhistorySize == 0x%x\n", historySize); - sizeReserv = historySize >> 1; - if (historySize >= ((UInt32)3 << 30)) sizeReserv = historySize >> 3; - else if (historySize >= ((UInt32)2 << 30)) sizeReserv = historySize >> 2; + if (p->keepSizeBefore < historySize || blockSize < p->keepSizeBefore) // if 32-bit overflow + return 0; - sizeReserv += (keepAddBufferBefore + matchMaxLen + keepAddBufferAfter) / 2 + (1 << 19); + { + const UInt32 kBlockSizeMax = (UInt32)0 - (UInt32)kBlockSizeAlign; + const UInt32 rem = kBlockSizeMax - blockSize; + const UInt32 reserve = (blockSize >> (blockSize < ((UInt32)1 << 30) ? 1 : 2)) + + (1 << 12) + kBlockMoveAlign + kBlockSizeAlign; // do not overflow 32-bit here + if (blockSize >= kBlockSizeMax + || rem < kBlockSizeReserveMin) // we reject settings that will be slow + return 0; + if (reserve >= rem) + blockSize = kBlockSizeMax; + else + { + blockSize += reserve; + blockSize &= ~(UInt32)(kBlockSizeAlign - 1); + } + } + // printf("\n LzFind_blockSize = %x\n", blockSize); + // printf("\n LzFind_blockSize = %d\n", blockSize >> 20); + return blockSize; +} + + +// input is historySize +static UInt32 MatchFinder_GetHashMask2(CMatchFinder *p, UInt32 hs) +{ + if (p->numHashBytes == 2) + return (1 << 16) - 1; + if (hs != 0) + hs--; + hs |= (hs >> 1); + hs |= (hs >> 2); + hs |= (hs >> 4); + hs |= (hs >> 8); + // we propagated 16 bits in (hs). Low 16 bits must be set later + if (hs >= (1 << 24)) + { + if (p->numHashBytes == 3) + hs = (1 << 24) - 1; + /* if (bigHash) mode, GetHeads4b() in LzFindMt.c needs (hs >= ((1 << 24) - 1))) */ + } + // (hash_size >= (1 << 16)) : Required for (numHashBytes > 2) + hs |= (1 << 16) - 1; /* don't change it! */ + // bt5: we adjust the size with recommended minimum size + if (p->numHashBytes >= 5) + hs |= (256 << kLzHash_CrcShift_2) - 1; + return hs; +} +// input is historySize +static UInt32 MatchFinder_GetHashMask(CMatchFinder *p, UInt32 hs) +{ + if (p->numHashBytes == 2) + return (1 << 16) - 1; + if (hs != 0) + hs--; + hs |= (hs >> 1); + hs |= (hs >> 2); + hs |= (hs >> 4); + hs |= (hs >> 8); + // we propagated 16 bits in (hs). Low 16 bits must be set later + hs >>= 1; + if (hs >= (1 << 24)) + { + if (p->numHashBytes == 3) + hs = (1 << 24) - 1; + else + hs >>= 1; + /* if (bigHash) mode, GetHeads4b() in LzFindMt.c needs (hs >= ((1 << 24) - 1))) */ + } + // (hash_size >= (1 << 16)) : Required for (numHashBytes > 2) + hs |= (1 << 16) - 1; /* don't change it! */ + // bt5: we adjust the size with recommended minimum size + if (p->numHashBytes >= 5) + hs |= (256 << kLzHash_CrcShift_2) - 1; + return hs; +} + + +int MatchFinder_Create(CMatchFinder *p, UInt32 historySize, + UInt32 keepAddBufferBefore, UInt32 matchMaxLen, UInt32 keepAddBufferAfter, + ISzAllocPtr alloc) +{ + /* we need one additional byte in (p->keepSizeBefore), + since we use MoveBlock() after (p->pos++) and before dictionary using */ + // keepAddBufferBefore = (UInt32)0xFFFFFFFF - (1 << 22); // for debug p->keepSizeBefore = historySize + keepAddBufferBefore + 1; - p->keepSizeAfter = matchMaxLen + keepAddBufferAfter; - - /* we need one additional byte, since we use MoveBlock after pos++ and before dictionary using */ - - if (LzInWindow_Create(p, sizeReserv, alloc)) + + keepAddBufferAfter += matchMaxLen; + /* we need (p->keepSizeAfter >= p->numHashBytes) */ + if (keepAddBufferAfter < p->numHashBytes) + keepAddBufferAfter = p->numHashBytes; + // keepAddBufferAfter -= 2; // for debug + p->keepSizeAfter = keepAddBufferAfter; + + if (p->directInput) + p->blockSize = 0; + if (p->directInput || LzInWindow_Create2(p, GetBlockSize(p, historySize), alloc)) { - UInt32 newCyclicBufferSize = historySize + 1; - UInt32 hs; - p->matchMaxLen = matchMaxLen; + size_t hashSizeSum; { - p->fixedHashSize = 0; - if (p->numHashBytes == 2) - hs = (1 << 16) - 1; + UInt32 hs; + UInt32 hsCur; + + if (p->numHashOutBits != 0) + { + unsigned numBits = p->numHashOutBits; + const unsigned nbMax = + (p->numHashBytes == 2 ? 16 : + (p->numHashBytes == 3 ? 24 : 32)); + if (numBits >= nbMax) + numBits = nbMax; + if (numBits >= 32) + hs = (UInt32)0 - 1; + else + hs = ((UInt32)1 << numBits) - 1; + // (hash_size >= (1 << 16)) : Required for (numHashBytes > 2) + hs |= (1 << 16) - 1; /* don't change it! */ + if (p->numHashBytes >= 5) + hs |= (256 << kLzHash_CrcShift_2) - 1; + { + const UInt32 hs2 = MatchFinder_GetHashMask2(p, historySize); + if (hs >= hs2) + hs = hs2; + } + hsCur = hs; + if (p->expectedDataSize < historySize) + { + const UInt32 hs2 = MatchFinder_GetHashMask2(p, (UInt32)p->expectedDataSize); + if (hsCur >= hs2) + hsCur = hs2; + } + } else { - hs = historySize - 1; - hs |= (hs >> 1); - hs |= (hs >> 2); - hs |= (hs >> 4); - hs |= (hs >> 8); - hs >>= 1; - hs |= 0xFFFF; /* don't change it! It's required for Deflate */ - if (hs > (1 << 24)) + hs = MatchFinder_GetHashMask(p, historySize); + hsCur = hs; + if (p->expectedDataSize < historySize) { - if (p->numHashBytes == 3) - hs = (1 << 24) - 1; - else - hs >>= 1; - /* if (bigHash) mode, GetHeads4b() in LzFindMt.c needs (hs >= ((1 << 24) - 1))) */ + hsCur = MatchFinder_GetHashMask(p, (UInt32)p->expectedDataSize); + if (hsCur >= hs) // is it possible? + hsCur = hs; } } - p->hashMask = hs; - hs++; - if (p->numHashBytes > 2) p->fixedHashSize += kHash2Size; - if (p->numHashBytes > 3) p->fixedHashSize += kHash3Size; - if (p->numHashBytes > 4) p->fixedHashSize += kHash4Size; - hs += p->fixedHashSize; + + p->hashMask = hsCur; + + hashSizeSum = hs; + hashSizeSum++; + if (hashSizeSum < hs) + return 0; + { + UInt32 fixedHashSize = 0; + if (p->numHashBytes > 2 && p->numHashBytes_Min <= 2) fixedHashSize += kHash2Size; + if (p->numHashBytes > 3 && p->numHashBytes_Min <= 3) fixedHashSize += kHash3Size; + // if (p->numHashBytes > 4) p->fixedHashSize += hs4; // kHash4Size; + hashSizeSum += fixedHashSize; + p->fixedHashSize = fixedHashSize; + } } + p->matchMaxLen = matchMaxLen; + { size_t newSize; size_t numSons; + const UInt32 newCyclicBufferSize = historySize + 1; // do not change it p->historySize = historySize; - p->hashSizeSum = hs; - p->cyclicBufferSize = newCyclicBufferSize; + p->cyclicBufferSize = newCyclicBufferSize; // it must be = (historySize + 1) numSons = newCyclicBufferSize; if (p->btMode) numSons <<= 1; - newSize = hs + numSons; + newSize = hashSizeSum + numSons; + + if (numSons < newCyclicBufferSize || newSize < numSons) + return 0; + + // aligned size is not required here, but it can be better for some loops + #define NUM_REFS_ALIGN_MASK 0xF + newSize = (newSize + NUM_REFS_ALIGN_MASK) & ~(size_t)NUM_REFS_ALIGN_MASK; - if (p->hash && p->numRefs == newSize) + // 22.02: we don't reallocate buffer, if old size is enough + if (p->hash && p->numRefs >= newSize) return 1; MatchFinder_FreeThisClassMemory(p, alloc); @@ -253,7 +486,7 @@ int MatchFinder_Create(CMatchFinder *p, UInt32 historySize, if (p->hash) { - p->son = p->hash + p->hashSizeSum; + p->son = p->hash + hashSizeSum; return 1; } } @@ -263,110 +496,401 @@ int MatchFinder_Create(CMatchFinder *p, UInt32 historySize, return 0; } + static void MatchFinder_SetLimits(CMatchFinder *p) { - UInt32 limit = kMaxValForNormalize - p->pos; - UInt32 limit2 = p->cyclicBufferSize - p->cyclicBufferPos; - - if (limit2 < limit) - limit = limit2; - limit2 = p->streamPos - p->pos; + UInt32 k; + UInt32 n = kMaxValForNormalize - p->pos; + if (n == 0) + n = (UInt32)(Int32)-1; // we allow (pos == 0) at start even with (kMaxValForNormalize == 0) - if (limit2 <= p->keepSizeAfter) + k = p->cyclicBufferSize - p->cyclicBufferPos; + if (k < n) + n = k; + + k = GET_AVAIL_BYTES(p); { - if (limit2 > 0) - limit2 = 1; + const UInt32 ksa = p->keepSizeAfter; + UInt32 mm = p->matchMaxLen; + if (k > ksa) + k -= ksa; // we must limit exactly to keepSizeAfter for ReadBlock + else if (k >= mm) + { + // the limitation for (p->lenLimit) update + k -= mm; // optimization : to reduce the number of checks + k++; + // k = 1; // non-optimized version : for debug + } + else + { + mm = k; + if (k != 0) + k = 1; + } + p->lenLimit = mm; } - else - limit2 -= p->keepSizeAfter; + if (k < n) + n = k; - if (limit2 < limit) - limit = limit2; - - { - UInt32 lenLimit = p->streamPos - p->pos; - if (lenLimit > p->matchMaxLen) - lenLimit = p->matchMaxLen; - p->lenLimit = lenLimit; - } - p->posLimit = p->pos + limit; + p->posLimit = p->pos + n; } -void MatchFinder_Init_2(CMatchFinder *p, int readData) + +void MatchFinder_Init_LowHash(CMatchFinder *p) { - UInt32 i; - UInt32 *hash = p->hash; - UInt32 num = p->hashSizeSum; - for (i = 0; i < num; i++) - hash[i] = kEmptyHashValue; - - p->cyclicBufferPos = 0; - p->buffer = p->bufferBase; - p->pos = p->streamPos = p->cyclicBufferSize; + size_t i; + CLzRef *items = p->hash; + const size_t numItems = p->fixedHashSize; + for (i = 0; i < numItems; i++) + items[i] = kEmptyHashValue; +} + + +void MatchFinder_Init_HighHash(CMatchFinder *p) +{ + size_t i; + CLzRef *items = p->hash + p->fixedHashSize; + const size_t numItems = (size_t)p->hashMask + 1; + for (i = 0; i < numItems; i++) + items[i] = kEmptyHashValue; +} + + +void MatchFinder_Init_4(CMatchFinder *p) +{ + if (!p->directInput) + p->buffer = p->bufBase; + { + /* kEmptyHashValue = 0 (Zero) is used in hash tables as NO-VALUE marker. + the code in CMatchFinderMt expects (pos = 1) */ + p->pos = + p->streamPos = + 1; // it's smallest optimal value. do not change it + // 0; // for debug + } p->result = SZ_OK; p->streamEndWasReached = 0; - - if (readData) - MatchFinder_ReadBlock(p); - +} + + +// (CYC_TO_POS_OFFSET == 0) is expected by some optimized code +#define CYC_TO_POS_OFFSET 0 +// #define CYC_TO_POS_OFFSET 1 // for debug + +void MatchFinder_Init(void *_p) +{ + CMatchFinder *p = (CMatchFinder *)_p; + MatchFinder_Init_HighHash(p); + MatchFinder_Init_LowHash(p); + MatchFinder_Init_4(p); + // if (readData) + MatchFinder_ReadBlock(p); + + /* if we init (cyclicBufferPos = pos), then we can use one variable + instead of both (cyclicBufferPos) and (pos) : only before (cyclicBufferPos) wrapping */ + p->cyclicBufferPos = (p->pos - CYC_TO_POS_OFFSET); // init with relation to (pos) + // p->cyclicBufferPos = 0; // smallest value + // p->son[0] = p->son[1] = 0; // unused: we can init skipped record for speculated accesses. MatchFinder_SetLimits(p); } -void MatchFinder_Init(CMatchFinder *p) + + +#ifdef MY_CPU_X86_OR_AMD64 + #if defined(__clang__) && (__clang_major__ >= 4) \ + || defined(Z7_GCC_VERSION) && (Z7_GCC_VERSION >= 40900) + // || defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 1900) + + #define USE_LZFIND_SATUR_SUB_128 + #define USE_LZFIND_SATUR_SUB_256 + #define LZFIND_ATTRIB_SSE41 __attribute__((__target__("sse4.1"))) + #define LZFIND_ATTRIB_AVX2 __attribute__((__target__("avx2"))) + #elif defined(_MSC_VER) + #if (_MSC_VER >= 1600) + #define USE_LZFIND_SATUR_SUB_128 + #endif + #if (_MSC_VER >= 1900) + #define USE_LZFIND_SATUR_SUB_256 + #endif + #endif + +#elif defined(MY_CPU_ARM64) \ + /* || (defined(__ARM_ARCH) && (__ARM_ARCH >= 7)) */ + + #if defined(Z7_CLANG_VERSION) && (Z7_CLANG_VERSION >= 30800) \ + || defined(__GNUC__) && (__GNUC__ >= 6) + #define USE_LZFIND_SATUR_SUB_128 + #ifdef MY_CPU_ARM64 + // #define LZFIND_ATTRIB_SSE41 __attribute__((__target__(""))) + #else + #define LZFIND_ATTRIB_SSE41 __attribute__((__target__("fpu=neon"))) + #endif + + #elif defined(_MSC_VER) + #if (_MSC_VER >= 1910) + #define USE_LZFIND_SATUR_SUB_128 + #endif + #endif + + #if defined(Z7_MSC_VER_ORIGINAL) && defined(MY_CPU_ARM64) + #include + #else + #include + #endif + +#endif + + +#ifdef USE_LZFIND_SATUR_SUB_128 + +// #define Z7_SHOW_HW_STATUS + +#ifdef Z7_SHOW_HW_STATUS +#include +#define PRF(x) x +PRF(;) +#else +#define PRF(x) +#endif + + +#ifdef MY_CPU_ARM_OR_ARM64 + +#ifdef MY_CPU_ARM64 +// #define FORCE_LZFIND_SATUR_SUB_128 +#endif +typedef uint32x4_t LzFind_v128; +#define SASUB_128_V(v, s) \ + vsubq_u32(vmaxq_u32(v, s), s) + +#else // MY_CPU_ARM_OR_ARM64 + +#include // sse4.1 + +typedef __m128i LzFind_v128; +// SSE 4.1 +#define SASUB_128_V(v, s) \ + _mm_sub_epi32(_mm_max_epu32(v, s), s) + +#endif // MY_CPU_ARM_OR_ARM64 + + +#define SASUB_128(i) \ + *( LzFind_v128 *)( void *)(items + (i) * 4) = SASUB_128_V( \ + *(const LzFind_v128 *)(const void *)(items + (i) * 4), sub2); + + +Z7_NO_INLINE +static +#ifdef LZFIND_ATTRIB_SSE41 +LZFIND_ATTRIB_SSE41 +#endif +void +Z7_FASTCALL +LzFind_SaturSub_128(UInt32 subValue, CLzRef *items, const CLzRef *lim) { - MatchFinder_Init_2(p, True); + const LzFind_v128 sub2 = + #ifdef MY_CPU_ARM_OR_ARM64 + vdupq_n_u32(subValue); + #else + _mm_set_epi32((Int32)subValue, (Int32)subValue, (Int32)subValue, (Int32)subValue); + #endif + Z7_PRAGMA_OPT_DISABLE_LOOP_UNROLL_VECTORIZE + do + { + SASUB_128(0) SASUB_128(1) items += 2 * 4; + SASUB_128(0) SASUB_128(1) items += 2 * 4; + } + while (items != lim); } - -static UInt32 MatchFinder_GetSubValue(CMatchFinder *p) + + + +#ifdef USE_LZFIND_SATUR_SUB_256 + +#include // avx +/* +clang :immintrin.h uses +#if !(defined(_MSC_VER) || defined(__SCE__)) || __has_feature(modules) || \ + defined(__AVX2__) +#include +#endif +so we need for clang-cl */ + +#if defined(__clang__) +#include +#include +#endif + +// AVX2: +#define SASUB_256(i) \ + *( __m256i *)( void *)(items + (i) * 8) = \ + _mm256_sub_epi32(_mm256_max_epu32( \ + *(const __m256i *)(const void *)(items + (i) * 8), sub2), sub2); + +Z7_NO_INLINE +static +#ifdef LZFIND_ATTRIB_AVX2 +LZFIND_ATTRIB_AVX2 +#endif +void +Z7_FASTCALL +LzFind_SaturSub_256(UInt32 subValue, CLzRef *items, const CLzRef *lim) { - return (p->pos - p->historySize - 1) & kNormalizeMask; + const __m256i sub2 = _mm256_set_epi32( + (Int32)subValue, (Int32)subValue, (Int32)subValue, (Int32)subValue, + (Int32)subValue, (Int32)subValue, (Int32)subValue, (Int32)subValue); + Z7_PRAGMA_OPT_DISABLE_LOOP_UNROLL_VECTORIZE + do + { + SASUB_256(0) SASUB_256(1) items += 2 * 8; + SASUB_256(0) SASUB_256(1) items += 2 * 8; + } + while (items != lim); } +#endif // USE_LZFIND_SATUR_SUB_256 -void MatchFinder_Normalize3(UInt32 subValue, CLzRef *items, size_t numItems) +#ifndef FORCE_LZFIND_SATUR_SUB_128 +typedef void (Z7_FASTCALL *LZFIND_SATUR_SUB_CODE_FUNC)( + UInt32 subValue, CLzRef *items, const CLzRef *lim); +static LZFIND_SATUR_SUB_CODE_FUNC g_LzFind_SaturSub; +#endif // FORCE_LZFIND_SATUR_SUB_128 + +#endif // USE_LZFIND_SATUR_SUB_128 + + +// kEmptyHashValue must be zero +// #define SASUB_32(i) { UInt32 v = items[i]; UInt32 m = v - subValue; if (v < subValue) m = kEmptyHashValue; items[i] = m; } +#define SASUB_32(i) { UInt32 v = items[i]; if (v < subValue) v = subValue; items[i] = v - subValue; } + +#ifdef FORCE_LZFIND_SATUR_SUB_128 + +#define DEFAULT_SaturSub LzFind_SaturSub_128 + +#else + +#define DEFAULT_SaturSub LzFind_SaturSub_32 + +Z7_NO_INLINE +static +void +Z7_FASTCALL +LzFind_SaturSub_32(UInt32 subValue, CLzRef *items, const CLzRef *lim) { - size_t i; - for (i = 0; i < numItems; i++) + Z7_PRAGMA_OPT_DISABLE_LOOP_UNROLL_VECTORIZE + do { - UInt32 value = items[i]; - if (value <= subValue) - value = kEmptyHashValue; - else - value -= subValue; - items[i] = value; + SASUB_32(0) SASUB_32(1) items += 2; + SASUB_32(0) SASUB_32(1) items += 2; + SASUB_32(0) SASUB_32(1) items += 2; + SASUB_32(0) SASUB_32(1) items += 2; } + while (items != lim); } -static void MatchFinder_Normalize(CMatchFinder *p) +#endif + + +Z7_NO_INLINE +void MatchFinder_Normalize3(UInt32 subValue, CLzRef *items, size_t numItems) { - UInt32 subValue = MatchFinder_GetSubValue(p); - MatchFinder_Normalize3(subValue, p->hash, p->numRefs); - MatchFinder_ReduceOffsets(p, subValue); + #define LZFIND_NORM_ALIGN_BLOCK_SIZE (1 << 7) + Z7_PRAGMA_OPT_DISABLE_LOOP_UNROLL_VECTORIZE + for (; numItems != 0 && ((unsigned)(ptrdiff_t)items & (LZFIND_NORM_ALIGN_BLOCK_SIZE - 1)) != 0; numItems--) + { + SASUB_32(0) + items++; + } + { + const size_t k_Align_Mask = (LZFIND_NORM_ALIGN_BLOCK_SIZE / 4 - 1); + CLzRef *lim = items + (numItems & ~(size_t)k_Align_Mask); + numItems &= k_Align_Mask; + if (items != lim) + { + #if defined(USE_LZFIND_SATUR_SUB_128) && !defined(FORCE_LZFIND_SATUR_SUB_128) + if (g_LzFind_SaturSub) + g_LzFind_SaturSub(subValue, items, lim); + else + #endif + DEFAULT_SaturSub(subValue, items, lim); + } + items = lim; + } + Z7_PRAGMA_OPT_DISABLE_LOOP_UNROLL_VECTORIZE + for (; numItems != 0; numItems--) + { + SASUB_32(0) + items++; + } } + + +// call MatchFinder_CheckLimits() only after (p->pos++) update + +Z7_NO_INLINE static void MatchFinder_CheckLimits(CMatchFinder *p) { + if (// !p->streamEndWasReached && p->result == SZ_OK && + p->keepSizeAfter == GET_AVAIL_BYTES(p)) + { + // we try to read only in exact state (p->keepSizeAfter == GET_AVAIL_BYTES(p)) + if (MatchFinder_NeedMove(p)) + MatchFinder_MoveBlock(p); + MatchFinder_ReadBlock(p); + } + if (p->pos == kMaxValForNormalize) - MatchFinder_Normalize(p); - if (!p->streamEndWasReached && p->keepSizeAfter == p->streamPos - p->pos) - MatchFinder_CheckAndMoveAndRead(p); + if (GET_AVAIL_BYTES(p) >= p->numHashBytes) // optional optimization for last bytes of data. + /* + if we disable normalization for last bytes of data, and + if (data_size == 4 GiB), we don't call wastfull normalization, + but (pos) will be wrapped over Zero (0) in that case. + And we cannot resume later to normal operation + */ + { + // MatchFinder_Normalize(p); + /* after normalization we need (p->pos >= p->historySize + 1); */ + /* we can reduce subValue to aligned value, if want to keep alignment + of (p->pos) and (p->buffer) for speculated accesses. */ + const UInt32 subValue = (p->pos - p->historySize - 1) /* & ~(UInt32)(kNormalizeAlign - 1) */; + // const UInt32 subValue = (1 << 15); // for debug + // printf("\nMatchFinder_Normalize() subValue == 0x%x\n", subValue); + MatchFinder_REDUCE_OFFSETS(p, subValue) + MatchFinder_Normalize3(subValue, p->hash, (size_t)p->hashMask + 1 + p->fixedHashSize); + { + size_t numSonRefs = p->cyclicBufferSize; + if (p->btMode) + numSonRefs <<= 1; + MatchFinder_Normalize3(subValue, p->son, numSonRefs); + } + } + if (p->cyclicBufferPos == p->cyclicBufferSize) p->cyclicBufferPos = 0; + MatchFinder_SetLimits(p); } -static UInt32 * Hc_GetMatchesSpec(UInt32 lenLimit, UInt32 curMatch, UInt32 pos, const Byte *cur, CLzRef *son, - UInt32 _cyclicBufferPos, UInt32 _cyclicBufferSize, UInt32 cutValue, - UInt32 *distances, UInt32 maxLen) + +/* + (lenLimit > maxLen) +*/ +Z7_FORCE_INLINE +static UInt32 * Hc_GetMatchesSpec(size_t lenLimit, UInt32 curMatch, UInt32 pos, const Byte *cur, CLzRef *son, + size_t _cyclicBufferPos, UInt32 _cyclicBufferSize, UInt32 cutValue, + UInt32 *d, unsigned maxLen) { + /* son[_cyclicBufferPos] = curMatch; for (;;) { UInt32 delta = pos - curMatch; if (cutValue-- == 0 || delta >= _cyclicBufferSize) - return distances; + return d; { const Byte *pb = cur - delta; - curMatch = son[_cyclicBufferPos - delta + ((delta > _cyclicBufferPos) ? _cyclicBufferSize : 0)]; + curMatch = son[_cyclicBufferPos - delta + (_cyclicBufferPos < delta ? _cyclicBufferSize : 0)]; if (pb[maxLen] == cur[maxLen] && *pb == *cur) { UInt32 len = 0; @@ -375,35 +899,91 @@ static UInt32 * Hc_GetMatchesSpec(UInt32 lenLimit, UInt32 curMatch, UInt32 pos, break; if (maxLen < len) { - *distances++ = maxLen = len; - *distances++ = delta - 1; + maxLen = len; + *d++ = len; + *d++ = delta - 1; if (len == lenLimit) - return distances; + return d; } } } } + */ + + const Byte *lim = cur + lenLimit; + son[_cyclicBufferPos] = curMatch; + + do + { + UInt32 delta; + + if (curMatch == 0) + break; + // if (curMatch2 >= curMatch) return NULL; + delta = pos - curMatch; + if (delta >= _cyclicBufferSize) + break; + { + ptrdiff_t diff; + curMatch = son[_cyclicBufferPos - delta + (_cyclicBufferPos < delta ? _cyclicBufferSize : 0)]; + diff = (ptrdiff_t)0 - (ptrdiff_t)delta; + if (cur[maxLen] == cur[(ptrdiff_t)maxLen + diff]) + { + const Byte *c = cur; + while (*c == c[diff]) + { + if (++c == lim) + { + d[0] = (UInt32)(lim - cur); + d[1] = delta - 1; + return d + 2; + } + } + { + const unsigned len = (unsigned)(c - cur); + if (maxLen < len) + { + maxLen = len; + d[0] = (UInt32)len; + d[1] = delta - 1; + d += 2; + } + } + } + } + } + while (--cutValue); + + return d; } + +Z7_FORCE_INLINE UInt32 * GetMatchesSpec1(UInt32 lenLimit, UInt32 curMatch, UInt32 pos, const Byte *cur, CLzRef *son, - UInt32 _cyclicBufferPos, UInt32 _cyclicBufferSize, UInt32 cutValue, - UInt32 *distances, UInt32 maxLen) + size_t _cyclicBufferPos, UInt32 _cyclicBufferSize, UInt32 cutValue, + UInt32 *d, UInt32 maxLen) { - CLzRef *ptr0 = son + (_cyclicBufferPos << 1) + 1; - CLzRef *ptr1 = son + (_cyclicBufferPos << 1); - UInt32 len0 = 0, len1 = 0; - for (;;) + CLzRef *ptr0 = son + ((size_t)_cyclicBufferPos << 1) + 1; + CLzRef *ptr1 = son + ((size_t)_cyclicBufferPos << 1); + unsigned len0 = 0, len1 = 0; + + UInt32 cmCheck; + + // if (curMatch >= pos) { *ptr0 = *ptr1 = kEmptyHashValue; return NULL; } + + cmCheck = (UInt32)(pos - _cyclicBufferSize); + if ((UInt32)pos < _cyclicBufferSize) + cmCheck = 0; + + if (cmCheck < curMatch) + do { - UInt32 delta = pos - curMatch; - if (cutValue-- == 0 || delta >= _cyclicBufferSize) + const UInt32 delta = pos - curMatch; { - *ptr0 = *ptr1 = kEmptyHashValue; - return distances; - } - { - CLzRef *pair = son + ((_cyclicBufferPos - delta + ((delta > _cyclicBufferPos) ? _cyclicBufferSize : 0)) << 1); + CLzRef *pair = son + ((size_t)(_cyclicBufferPos - delta + (_cyclicBufferPos < delta ? _cyclicBufferSize : 0)) << 1); const Byte *pb = cur - delta; - UInt32 len = (len0 < len1 ? len0 : len1); + unsigned len = (len0 < len1 ? len0 : len1); + const UInt32 pair0 = pair[0]; if (pb[len] == cur[len]) { if (++len != lenLimit && pb[len] == cur[len]) @@ -412,52 +992,65 @@ UInt32 * GetMatchesSpec1(UInt32 lenLimit, UInt32 curMatch, UInt32 pos, const Byt break; if (maxLen < len) { - *distances++ = maxLen = len; - *distances++ = delta - 1; + maxLen = (UInt32)len; + *d++ = (UInt32)len; + *d++ = delta - 1; if (len == lenLimit) { - *ptr1 = pair[0]; + *ptr1 = pair0; *ptr0 = pair[1]; - return distances; + return d; } } } if (pb[len] < cur[len]) { *ptr1 = curMatch; + // const UInt32 curMatch2 = pair[1]; + // if (curMatch2 >= curMatch) { *ptr0 = *ptr1 = kEmptyHashValue; return NULL; } + // curMatch = curMatch2; + curMatch = pair[1]; ptr1 = pair + 1; - curMatch = *ptr1; len1 = len; } else { *ptr0 = curMatch; + curMatch = pair[0]; ptr0 = pair; - curMatch = *ptr0; len0 = len; } } } + while(--cutValue && cmCheck < curMatch); + + *ptr0 = *ptr1 = kEmptyHashValue; + return d; } + static void SkipMatchesSpec(UInt32 lenLimit, UInt32 curMatch, UInt32 pos, const Byte *cur, CLzRef *son, - UInt32 _cyclicBufferPos, UInt32 _cyclicBufferSize, UInt32 cutValue) + size_t _cyclicBufferPos, UInt32 _cyclicBufferSize, UInt32 cutValue) { - CLzRef *ptr0 = son + (_cyclicBufferPos << 1) + 1; - CLzRef *ptr1 = son + (_cyclicBufferPos << 1); - UInt32 len0 = 0, len1 = 0; - for (;;) + CLzRef *ptr0 = son + ((size_t)_cyclicBufferPos << 1) + 1; + CLzRef *ptr1 = son + ((size_t)_cyclicBufferPos << 1); + unsigned len0 = 0, len1 = 0; + + UInt32 cmCheck; + + cmCheck = (UInt32)(pos - _cyclicBufferSize); + if ((UInt32)pos < _cyclicBufferSize) + cmCheck = 0; + + if (// curMatch >= pos || // failure + cmCheck < curMatch) + do { - UInt32 delta = pos - curMatch; - if (cutValue-- == 0 || delta >= _cyclicBufferSize) + const UInt32 delta = pos - curMatch; { - *ptr0 = *ptr1 = kEmptyHashValue; - return; - } - { - CLzRef *pair = son + ((_cyclicBufferPos - delta + ((delta > _cyclicBufferPos) ? _cyclicBufferSize : 0)) << 1); + CLzRef *pair = son + ((size_t)(_cyclicBufferPos - delta + (_cyclicBufferPos < delta ? _cyclicBufferSize : 0)) << 1); const Byte *pb = cur - delta; - UInt32 len = (len0 < len1 ? len0 : len1); + unsigned len = (len0 < len1 ? len0 : len1); if (pb[len] == cur[len]) { while (++len != lenLimit) @@ -475,570 +1068,679 @@ static void SkipMatchesSpec(UInt32 lenLimit, UInt32 curMatch, UInt32 pos, const if (pb[len] < cur[len]) { *ptr1 = curMatch; + curMatch = pair[1]; ptr1 = pair + 1; - curMatch = *ptr1; len1 = len; } else { *ptr0 = curMatch; + curMatch = pair[0]; ptr0 = pair; - curMatch = *ptr0; len0 = len; } } } + while(--cutValue && cmCheck < curMatch); + + *ptr0 = *ptr1 = kEmptyHashValue; + return; } + #define MOVE_POS \ - ++p->cyclicBufferPos; \ + p->cyclicBufferPos++; \ p->buffer++; \ - if (++p->pos == p->posLimit) MatchFinder_CheckLimits(p); + { const UInt32 pos1 = p->pos + 1; \ + p->pos = pos1; \ + if (pos1 == p->posLimit) MatchFinder_CheckLimits(p); } -#define MOVE_POS_RET MOVE_POS return offset; +#define MOVE_POS_RET MOVE_POS return distances; -static void MatchFinder_MovePos(CMatchFinder *p) { MOVE_POS; } +Z7_NO_INLINE +static void MatchFinder_MovePos(CMatchFinder *p) +{ + /* we go here at the end of stream data, when (avail < num_hash_bytes) + We don't update sons[cyclicBufferPos << btMode]. + So (sons) record will contain junk. And we cannot resume match searching + to normal operation, even if we will provide more input data in buffer. + p->sons[p->cyclicBufferPos << p->btMode] = 0; // kEmptyHashValue + if (p->btMode) + p->sons[(p->cyclicBufferPos << p->btMode) + 1] = 0; // kEmptyHashValue + */ + MOVE_POS +} #define GET_MATCHES_HEADER2(minLen, ret_op) \ - UInt32 lenLimit; UInt32 hv; const Byte *cur; UInt32 curMatch; \ - lenLimit = p->lenLimit; { if (lenLimit < minLen) { MatchFinder_MovePos(p); ret_op; }} \ + UInt32 hv; const Byte *cur; UInt32 curMatch; \ + UInt32 lenLimit = p->lenLimit; \ + if (lenLimit < minLen) { MatchFinder_MovePos(p); ret_op; } \ cur = p->buffer; -#define GET_MATCHES_HEADER(minLen) GET_MATCHES_HEADER2(minLen, return 0) -#define SKIP_HEADER(minLen) GET_MATCHES_HEADER2(minLen, continue) +#define GET_MATCHES_HEADER(minLen) GET_MATCHES_HEADER2(minLen, return distances) +#define SKIP_HEADER(minLen) \ + do { GET_MATCHES_HEADER2(minLen, continue) + +#define MF_PARAMS(p) lenLimit, curMatch, p->pos, p->buffer, p->son, \ + p->cyclicBufferPos, p->cyclicBufferSize, p->cutValue + +#define SKIP_FOOTER \ + SkipMatchesSpec(MF_PARAMS(p)); \ + MOVE_POS \ + } while (--num); + +#define GET_MATCHES_FOOTER_BASE(_maxLen_, func) \ + distances = func(MF_PARAMS(p), distances, (UInt32)_maxLen_); \ + MOVE_POS_RET + +#define GET_MATCHES_FOOTER_BT(_maxLen_) \ + GET_MATCHES_FOOTER_BASE(_maxLen_, GetMatchesSpec1) -#define MF_PARAMS(p) p->pos, p->buffer, p->son, p->cyclicBufferPos, p->cyclicBufferSize, p->cutValue +#define GET_MATCHES_FOOTER_HC(_maxLen_) \ + GET_MATCHES_FOOTER_BASE(_maxLen_, Hc_GetMatchesSpec) -#define GET_MATCHES_FOOTER(offset, maxLen) \ - offset = (UInt32)(GetMatchesSpec1(lenLimit, curMatch, MF_PARAMS(p), \ - distances + offset, maxLen) - distances); MOVE_POS_RET; -#define SKIP_FOOTER \ - SkipMatchesSpec(lenLimit, curMatch, MF_PARAMS(p)); MOVE_POS; #define UPDATE_maxLen { \ - ptrdiff_t diff = (ptrdiff_t)0 - d2; \ + const ptrdiff_t diff = (ptrdiff_t)0 - (ptrdiff_t)d2; \ const Byte *c = cur + maxLen; \ const Byte *lim = cur + lenLimit; \ for (; c != lim; c++) if (*(c + diff) != *c) break; \ - maxLen = (UInt32)(c - cur); } + maxLen = (unsigned)(c - cur); } -static UInt32 Bt2_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances) +static UInt32* Bt2_MatchFinder_GetMatches(void *_p, UInt32 *distances) { - UInt32 offset; + CMatchFinder *p = (CMatchFinder *)_p; GET_MATCHES_HEADER(2) - HASH2_CALC; + HASH2_CALC curMatch = p->hash[hv]; p->hash[hv] = p->pos; - offset = 0; - GET_MATCHES_FOOTER(offset, 1) + GET_MATCHES_FOOTER_BT(1) } -UInt32 Bt3Zip_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances) +UInt32* Bt3Zip_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances) { - UInt32 offset; GET_MATCHES_HEADER(3) - HASH_ZIP_CALC; + HASH_ZIP_CALC curMatch = p->hash[hv]; p->hash[hv] = p->pos; - offset = 0; - GET_MATCHES_FOOTER(offset, 2) + GET_MATCHES_FOOTER_BT(2) } -static UInt32 Bt3_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances) + +#define SET_mmm \ + mmm = p->cyclicBufferSize; \ + if (pos < mmm) \ + mmm = pos; + + +static UInt32* Bt3_MatchFinder_GetMatches(void *_p, UInt32 *distances) { - UInt32 h2, d2, maxLen, offset, pos; + CMatchFinder *p = (CMatchFinder *)_p; + UInt32 mmm; + UInt32 h2, d2, pos; + unsigned maxLen; UInt32 *hash; GET_MATCHES_HEADER(3) - HASH3_CALC; + HASH3_CALC hash = p->hash; pos = p->pos; d2 = pos - hash[h2]; - curMatch = hash[kFix3HashSize + hv]; + curMatch = (hash + kFix3HashSize)[hv]; hash[h2] = pos; - hash[kFix3HashSize + hv] = pos; + (hash + kFix3HashSize)[hv] = pos; + + SET_mmm maxLen = 2; - offset = 0; - if (d2 < p->cyclicBufferSize && *(cur - d2) == *cur) + if (d2 < mmm && *(cur - d2) == *cur) { UPDATE_maxLen - distances[0] = maxLen; + distances[0] = (UInt32)maxLen; distances[1] = d2 - 1; - offset = 2; + distances += 2; if (maxLen == lenLimit) { - SkipMatchesSpec(lenLimit, curMatch, MF_PARAMS(p)); - MOVE_POS_RET; + SkipMatchesSpec(MF_PARAMS(p)); + MOVE_POS_RET } } - GET_MATCHES_FOOTER(offset, maxLen) + GET_MATCHES_FOOTER_BT(maxLen) } -static UInt32 Bt4_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances) + +static UInt32* Bt4_MatchFinder_GetMatches(void *_p, UInt32 *distances) { - UInt32 h2, h3, d2, d3, maxLen, offset, pos; + CMatchFinder *p = (CMatchFinder *)_p; + UInt32 mmm; + UInt32 h2, h3, d2, d3, pos; + unsigned maxLen; UInt32 *hash; GET_MATCHES_HEADER(4) - HASH4_CALC; + HASH4_CALC hash = p->hash; pos = p->pos; - d2 = pos - hash[ h2]; - d3 = pos - hash[kFix3HashSize + h3]; + d2 = pos - hash [h2]; + d3 = pos - (hash + kFix3HashSize)[h3]; + curMatch = (hash + kFix4HashSize)[hv]; - curMatch = hash[kFix4HashSize + hv]; + hash [h2] = pos; + (hash + kFix3HashSize)[h3] = pos; + (hash + kFix4HashSize)[hv] = pos; - hash[ h2] = pos; - hash[kFix3HashSize + h3] = pos; - hash[kFix4HashSize + hv] = pos; + SET_mmm - maxLen = 0; - offset = 0; + maxLen = 3; - if (d2 < p->cyclicBufferSize && *(cur - d2) == *cur) - { - distances[0] = maxLen = 2; - distances[1] = d2 - 1; - offset = 2; - } - - if (d2 != d3 && d3 < p->cyclicBufferSize && *(cur - d3) == *cur) + for (;;) { - maxLen = 3; - distances[offset + 1] = d3 - 1; - offset += 2; - d2 = d3; - } + if (d2 < mmm && *(cur - d2) == *cur) + { + distances[0] = 2; + distances[1] = d2 - 1; + distances += 2; + if (*(cur - d2 + 2) == cur[2]) + { + // distances[-2] = 3; + } + else if (d3 < mmm && *(cur - d3) == *cur) + { + d2 = d3; + distances[1] = d3 - 1; + distances += 2; + } + else + break; + } + else if (d3 < mmm && *(cur - d3) == *cur) + { + d2 = d3; + distances[1] = d3 - 1; + distances += 2; + } + else + break; - if (offset != 0) - { UPDATE_maxLen - distances[offset - 2] = maxLen; + distances[-2] = (UInt32)maxLen; if (maxLen == lenLimit) { - SkipMatchesSpec(lenLimit, curMatch, MF_PARAMS(p)); - MOVE_POS_RET; + SkipMatchesSpec(MF_PARAMS(p)); + MOVE_POS_RET } + break; } - if (maxLen < 3) - maxLen = 3; - - GET_MATCHES_FOOTER(offset, maxLen) + GET_MATCHES_FOOTER_BT(maxLen) } -/* -static UInt32 Bt5_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances) + +static UInt32* Bt5_MatchFinder_GetMatches(void *_p, UInt32 *distances) { - UInt32 h2, h3, h4, d2, d3, d4, maxLen, offset, pos; + CMatchFinder *p = (CMatchFinder *)_p; + UInt32 mmm; + UInt32 h2, h3, d2, d3, pos; + unsigned maxLen; UInt32 *hash; GET_MATCHES_HEADER(5) - HASH5_CALC; + HASH5_CALC hash = p->hash; pos = p->pos; - d2 = pos - hash[ h2]; - d3 = pos - hash[kFix3HashSize + h3]; - d4 = pos - hash[kFix4HashSize + h4]; + d2 = pos - hash [h2]; + d3 = pos - (hash + kFix3HashSize)[h3]; + // d4 = pos - (hash + kFix4HashSize)[h4]; + + curMatch = (hash + kFix5HashSize)[hv]; - curMatch = hash[kFix5HashSize + hv]; + hash [h2] = pos; + (hash + kFix3HashSize)[h3] = pos; + // (hash + kFix4HashSize)[h4] = pos; + (hash + kFix5HashSize)[hv] = pos; - hash[ h2] = pos; - hash[kFix3HashSize + h3] = pos; - hash[kFix4HashSize + h4] = pos; - hash[kFix5HashSize + hv] = pos; + SET_mmm - maxLen = 0; - offset = 0; + maxLen = 4; - if (d2 < p->cyclicBufferSize && *(cur - d2) == *cur) + for (;;) { - distances[0] = maxLen = 2; - distances[1] = d2 - 1; - offset = 2; - if (*(cur - d2 + 2) == cur[2]) - distances[0] = maxLen = 3; - else if (d3 < p->cyclicBufferSize && *(cur - d3) == *cur) + if (d2 < mmm && *(cur - d2) == *cur) + { + distances[0] = 2; + distances[1] = d2 - 1; + distances += 2; + if (*(cur - d2 + 2) == cur[2]) + { + } + else if (d3 < mmm && *(cur - d3) == *cur) + { + distances[1] = d3 - 1; + distances += 2; + d2 = d3; + } + else + break; + } + else if (d3 < mmm && *(cur - d3) == *cur) { - distances[2] = maxLen = 3; - distances[3] = d3 - 1; - offset = 4; + distances[1] = d3 - 1; + distances += 2; d2 = d3; } - } - else if (d3 < p->cyclicBufferSize && *(cur - d3) == *cur) - { - distances[0] = maxLen = 3; - distances[1] = d3 - 1; - offset = 2; - d2 = d3; - } - - if (d2 != d4 && d4 < p->cyclicBufferSize - && *(cur - d4) == *cur - && *(cur - d4 + 3) == *(cur + 3)) - { - maxLen = 4; - distances[offset + 1] = d4 - 1; - offset += 2; - d2 = d4; - } - - if (offset != 0) - { + else + break; + + distances[-2] = 3; + if (*(cur - d2 + 3) != cur[3]) + break; UPDATE_maxLen - distances[offset - 2] = maxLen; + distances[-2] = (UInt32)maxLen; if (maxLen == lenLimit) { - SkipMatchesSpec(lenLimit, curMatch, MF_PARAMS(p)); - MOVE_POS_RET; + SkipMatchesSpec(MF_PARAMS(p)); + MOVE_POS_RET } + break; } - - if (maxLen < 4) - maxLen = 4; - GET_MATCHES_FOOTER(offset, maxLen) + GET_MATCHES_FOOTER_BT(maxLen) } -*/ -static UInt32 Hc4_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances) + +static UInt32* Hc4_MatchFinder_GetMatches(void *_p, UInt32 *distances) { - UInt32 h2, h3, d2, d3, maxLen, offset, pos; + CMatchFinder *p = (CMatchFinder *)_p; + UInt32 mmm; + UInt32 h2, h3, d2, d3, pos; + unsigned maxLen; UInt32 *hash; GET_MATCHES_HEADER(4) - HASH4_CALC; + HASH4_CALC hash = p->hash; pos = p->pos; - d2 = pos - hash[ h2]; - d3 = pos - hash[kFix3HashSize + h3]; - - curMatch = hash[kFix4HashSize + hv]; + d2 = pos - hash [h2]; + d3 = pos - (hash + kFix3HashSize)[h3]; + curMatch = (hash + kFix4HashSize)[hv]; - hash[ h2] = pos; - hash[kFix3HashSize + h3] = pos; - hash[kFix4HashSize + hv] = pos; + hash [h2] = pos; + (hash + kFix3HashSize)[h3] = pos; + (hash + kFix4HashSize)[hv] = pos; - maxLen = 0; - offset = 0; + SET_mmm - if (d2 < p->cyclicBufferSize && *(cur - d2) == *cur) - { - distances[0] = maxLen = 2; - distances[1] = d2 - 1; - offset = 2; - } - - if (d2 != d3 && d3 < p->cyclicBufferSize && *(cur - d3) == *cur) - { - maxLen = 3; - distances[offset + 1] = d3 - 1; - offset += 2; - d2 = d3; - } - - if (offset != 0) + maxLen = 3; + + for (;;) { + if (d2 < mmm && *(cur - d2) == *cur) + { + distances[0] = 2; + distances[1] = d2 - 1; + distances += 2; + if (*(cur - d2 + 2) == cur[2]) + { + // distances[-2] = 3; + } + else if (d3 < mmm && *(cur - d3) == *cur) + { + d2 = d3; + distances[1] = d3 - 1; + distances += 2; + } + else + break; + } + else if (d3 < mmm && *(cur - d3) == *cur) + { + d2 = d3; + distances[1] = d3 - 1; + distances += 2; + } + else + break; + UPDATE_maxLen - distances[offset - 2] = maxLen; + distances[-2] = (UInt32)maxLen; if (maxLen == lenLimit) { p->son[p->cyclicBufferPos] = curMatch; - MOVE_POS_RET; + MOVE_POS_RET } + break; } - if (maxLen < 3) - maxLen = 3; - - offset = (UInt32)(Hc_GetMatchesSpec(lenLimit, curMatch, MF_PARAMS(p), - distances + offset, maxLen) - (distances)); - MOVE_POS_RET + GET_MATCHES_FOOTER_HC(maxLen) } -/* -static UInt32 Hc5_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances) + +static UInt32 * Hc5_MatchFinder_GetMatches(void *_p, UInt32 *distances) { - UInt32 h2, h3, h4, d2, d3, d4, maxLen, offset, pos + CMatchFinder *p = (CMatchFinder *)_p; + UInt32 mmm; + UInt32 h2, h3, d2, d3, pos; + unsigned maxLen; UInt32 *hash; GET_MATCHES_HEADER(5) - HASH5_CALC; + HASH5_CALC hash = p->hash; pos = p->pos; - - d2 = pos - hash[ h2]; - d3 = pos - hash[kFix3HashSize + h3]; - d4 = pos - hash[kFix4HashSize + h4]; - curMatch = hash[kFix5HashSize + hv]; + d2 = pos - hash [h2]; + d3 = pos - (hash + kFix3HashSize)[h3]; + // d4 = pos - (hash + kFix4HashSize)[h4]; - hash[ h2] = pos; - hash[kFix3HashSize + h3] = pos; - hash[kFix4HashSize + h4] = pos; - hash[kFix5HashSize + hv] = pos; + curMatch = (hash + kFix5HashSize)[hv]; - maxLen = 0; - offset = 0; + hash [h2] = pos; + (hash + kFix3HashSize)[h3] = pos; + // (hash + kFix4HashSize)[h4] = pos; + (hash + kFix5HashSize)[hv] = pos; - if (d2 < p->cyclicBufferSize && *(cur - d2) == *cur) + SET_mmm + + maxLen = 4; + + for (;;) { - distances[0] = maxLen = 2; - distances[1] = d2 - 1; - offset = 2; - if (*(cur - d2 + 2) == cur[2]) - distances[0] = maxLen = 3; - else if (d3 < p->cyclicBufferSize && *(cur - d3) == *cur) + if (d2 < mmm && *(cur - d2) == *cur) + { + distances[0] = 2; + distances[1] = d2 - 1; + distances += 2; + if (*(cur - d2 + 2) == cur[2]) + { + } + else if (d3 < mmm && *(cur - d3) == *cur) + { + distances[1] = d3 - 1; + distances += 2; + d2 = d3; + } + else + break; + } + else if (d3 < mmm && *(cur - d3) == *cur) { - distances[2] = maxLen = 3; - distances[3] = d3 - 1; - offset = 4; + distances[1] = d3 - 1; + distances += 2; d2 = d3; } - } - else if (d3 < p->cyclicBufferSize && *(cur - d3) == *cur) - { - distances[0] = maxLen = 3; - distances[1] = d3 - 1; - offset = 2; - d2 = d3; - } - - if (d2 != d4 && d4 < p->cyclicBufferSize - && *(cur - d4) == *cur - && *(cur - d4 + 3) == *(cur + 3)) - { - maxLen = 4; - distances[offset + 1] = d4 - 1; - offset += 2; - d2 = d4; - } - - if (offset != 0) - { + else + break; + + distances[-2] = 3; + if (*(cur - d2 + 3) != cur[3]) + break; UPDATE_maxLen - distances[offset - 2] = maxLen; + distances[-2] = (UInt32)maxLen; if (maxLen == lenLimit) { p->son[p->cyclicBufferPos] = curMatch; - MOVE_POS_RET; + MOVE_POS_RET } + break; } - if (maxLen < 4) - maxLen = 4; - - offset = (UInt32)(Hc_GetMatchesSpec(lenLimit, curMatch, MF_PARAMS(p), - distances + offset, maxLen) - (distances)); - MOVE_POS_RET + GET_MATCHES_FOOTER_HC(maxLen) } -*/ -UInt32 Hc3Zip_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances) + +UInt32* Hc3Zip_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances) { - UInt32 offset; GET_MATCHES_HEADER(3) - HASH_ZIP_CALC; + HASH_ZIP_CALC curMatch = p->hash[hv]; p->hash[hv] = p->pos; - offset = (UInt32)(Hc_GetMatchesSpec(lenLimit, curMatch, MF_PARAMS(p), - distances, 2) - (distances)); - MOVE_POS_RET + GET_MATCHES_FOOTER_HC(2) } -static void Bt2_MatchFinder_Skip(CMatchFinder *p, UInt32 num) + +static void Bt2_MatchFinder_Skip(void *_p, UInt32 num) { - do + CMatchFinder *p = (CMatchFinder *)_p; + SKIP_HEADER(2) { - SKIP_HEADER(2) - HASH2_CALC; + HASH2_CALC curMatch = p->hash[hv]; p->hash[hv] = p->pos; - SKIP_FOOTER } - while (--num != 0); + SKIP_FOOTER } void Bt3Zip_MatchFinder_Skip(CMatchFinder *p, UInt32 num) { - do + SKIP_HEADER(3) { - SKIP_HEADER(3) - HASH_ZIP_CALC; + HASH_ZIP_CALC curMatch = p->hash[hv]; p->hash[hv] = p->pos; - SKIP_FOOTER } - while (--num != 0); + SKIP_FOOTER } -static void Bt3_MatchFinder_Skip(CMatchFinder *p, UInt32 num) +static void Bt3_MatchFinder_Skip(void *_p, UInt32 num) { - do + CMatchFinder *p = (CMatchFinder *)_p; + SKIP_HEADER(3) { UInt32 h2; UInt32 *hash; - SKIP_HEADER(3) - HASH3_CALC; + HASH3_CALC hash = p->hash; - curMatch = hash[kFix3HashSize + hv]; + curMatch = (hash + kFix3HashSize)[hv]; hash[h2] = - hash[kFix3HashSize + hv] = p->pos; - SKIP_FOOTER + (hash + kFix3HashSize)[hv] = p->pos; } - while (--num != 0); + SKIP_FOOTER } -static void Bt4_MatchFinder_Skip(CMatchFinder *p, UInt32 num) +static void Bt4_MatchFinder_Skip(void *_p, UInt32 num) { - do + CMatchFinder *p = (CMatchFinder *)_p; + SKIP_HEADER(4) { UInt32 h2, h3; UInt32 *hash; - SKIP_HEADER(4) - HASH4_CALC; + HASH4_CALC hash = p->hash; - curMatch = hash[kFix4HashSize + hv]; - hash[ h2] = - hash[kFix3HashSize + h3] = - hash[kFix4HashSize + hv] = p->pos; - SKIP_FOOTER + curMatch = (hash + kFix4HashSize)[hv]; + hash [h2] = + (hash + kFix3HashSize)[h3] = + (hash + kFix4HashSize)[hv] = p->pos; } - while (--num != 0); + SKIP_FOOTER } -/* -static void Bt5_MatchFinder_Skip(CMatchFinder *p, UInt32 num) +static void Bt5_MatchFinder_Skip(void *_p, UInt32 num) { - do + CMatchFinder *p = (CMatchFinder *)_p; + SKIP_HEADER(5) { - UInt32 h2, h3, h4; + UInt32 h2, h3; UInt32 *hash; - SKIP_HEADER(5) - HASH5_CALC; + HASH5_CALC hash = p->hash; - curMatch = hash[kFix5HashSize + hv]; - hash[ h2] = - hash[kFix3HashSize + h3] = - hash[kFix4HashSize + h4] = - hash[kFix5HashSize + hv] = p->pos; - SKIP_FOOTER + curMatch = (hash + kFix5HashSize)[hv]; + hash [h2] = + (hash + kFix3HashSize)[h3] = + // (hash + kFix4HashSize)[h4] = + (hash + kFix5HashSize)[hv] = p->pos; } - while (--num != 0); + SKIP_FOOTER } -*/ -static void Hc4_MatchFinder_Skip(CMatchFinder *p, UInt32 num) + +#define HC_SKIP_HEADER(minLen) \ + do { if (p->lenLimit < minLen) { MatchFinder_MovePos(p); num--; continue; } { \ + const Byte *cur; \ + UInt32 *hash; \ + UInt32 *son; \ + UInt32 pos = p->pos; \ + UInt32 num2 = num; \ + /* (p->pos == p->posLimit) is not allowed here !!! */ \ + { const UInt32 rem = p->posLimit - pos; if (num2 >= rem) num2 = rem; } \ + num -= num2; \ + { const UInt32 cycPos = p->cyclicBufferPos; \ + son = p->son + cycPos; \ + p->cyclicBufferPos = cycPos + num2; } \ + cur = p->buffer; \ + hash = p->hash; \ + do { \ + UInt32 curMatch; \ + UInt32 hv; + + +#define HC_SKIP_FOOTER \ + cur++; pos++; *son++ = curMatch; \ + } while (--num2); \ + p->buffer = cur; \ + p->pos = pos; \ + if (pos == p->posLimit) MatchFinder_CheckLimits(p); \ + }} while(num); \ + + +static void Hc4_MatchFinder_Skip(void *_p, UInt32 num) { - do - { + CMatchFinder *p = (CMatchFinder *)_p; + HC_SKIP_HEADER(4) + UInt32 h2, h3; - UInt32 *hash; - SKIP_HEADER(4) - HASH4_CALC; - hash = p->hash; - curMatch = hash[kFix4HashSize + hv]; - hash[ h2] = - hash[kFix3HashSize + h3] = - hash[kFix4HashSize + hv] = p->pos; - p->son[p->cyclicBufferPos] = curMatch; - MOVE_POS - } - while (--num != 0); + HASH4_CALC + curMatch = (hash + kFix4HashSize)[hv]; + hash [h2] = + (hash + kFix3HashSize)[h3] = + (hash + kFix4HashSize)[hv] = pos; + + HC_SKIP_FOOTER } -/* -static void Hc5_MatchFinder_Skip(CMatchFinder *p, UInt32 num) + +static void Hc5_MatchFinder_Skip(void *_p, UInt32 num) { - do - { - UInt32 h2, h3, h4; - UInt32 *hash; - SKIP_HEADER(5) - HASH5_CALC; - hash = p->hash; - curMatch = p->hash[kFix5HashSize + hv]; - hash[ h2] = - hash[kFix3HashSize + h3] = - hash[kFix4HashSize + h4] = - hash[kFix5HashSize + hv] = p->pos; - p->son[p->cyclicBufferPos] = curMatch; - MOVE_POS - } - while (--num != 0); + CMatchFinder *p = (CMatchFinder *)_p; + HC_SKIP_HEADER(5) + + UInt32 h2, h3; + HASH5_CALC + curMatch = (hash + kFix5HashSize)[hv]; + hash [h2] = + (hash + kFix3HashSize)[h3] = + // (hash + kFix4HashSize)[h4] = + (hash + kFix5HashSize)[hv] = pos; + + HC_SKIP_FOOTER } -*/ + void Hc3Zip_MatchFinder_Skip(CMatchFinder *p, UInt32 num) { - do - { - SKIP_HEADER(3) - HASH_ZIP_CALC; - curMatch = p->hash[hv]; - p->hash[hv] = p->pos; - p->son[p->cyclicBufferPos] = curMatch; - MOVE_POS - } - while (--num != 0); + HC_SKIP_HEADER(3) + + HASH_ZIP_CALC + curMatch = hash[hv]; + hash[hv] = pos; + + HC_SKIP_FOOTER } -void MatchFinder_CreateVTable(CMatchFinder *p, IMatchFinder *vTable) + +void MatchFinder_CreateVTable(CMatchFinder *p, IMatchFinder2 *vTable) { - vTable->Init = (Mf_Init_Func)MatchFinder_Init; - vTable->GetNumAvailableBytes = (Mf_GetNumAvailableBytes_Func)MatchFinder_GetNumAvailableBytes; - vTable->GetPointerToCurrentPos = (Mf_GetPointerToCurrentPos_Func)MatchFinder_GetPointerToCurrentPos; + vTable->Init = MatchFinder_Init; + vTable->GetNumAvailableBytes = MatchFinder_GetNumAvailableBytes; + vTable->GetPointerToCurrentPos = MatchFinder_GetPointerToCurrentPos; if (!p->btMode) { - /* if (p->numHashBytes <= 4) */ + if (p->numHashBytes <= 4) { - vTable->GetMatches = (Mf_GetMatches_Func)Hc4_MatchFinder_GetMatches; - vTable->Skip = (Mf_Skip_Func)Hc4_MatchFinder_Skip; + vTable->GetMatches = Hc4_MatchFinder_GetMatches; + vTable->Skip = Hc4_MatchFinder_Skip; } - /* else { - vTable->GetMatches = (Mf_GetMatches_Func)Hc5_MatchFinder_GetMatches; - vTable->Skip = (Mf_Skip_Func)Hc5_MatchFinder_Skip; + vTable->GetMatches = Hc5_MatchFinder_GetMatches; + vTable->Skip = Hc5_MatchFinder_Skip; } - */ } else if (p->numHashBytes == 2) { - vTable->GetMatches = (Mf_GetMatches_Func)Bt2_MatchFinder_GetMatches; - vTable->Skip = (Mf_Skip_Func)Bt2_MatchFinder_Skip; + vTable->GetMatches = Bt2_MatchFinder_GetMatches; + vTable->Skip = Bt2_MatchFinder_Skip; } else if (p->numHashBytes == 3) { - vTable->GetMatches = (Mf_GetMatches_Func)Bt3_MatchFinder_GetMatches; - vTable->Skip = (Mf_Skip_Func)Bt3_MatchFinder_Skip; + vTable->GetMatches = Bt3_MatchFinder_GetMatches; + vTable->Skip = Bt3_MatchFinder_Skip; } - else /* if (p->numHashBytes == 4) */ + else if (p->numHashBytes == 4) { - vTable->GetMatches = (Mf_GetMatches_Func)Bt4_MatchFinder_GetMatches; - vTable->Skip = (Mf_Skip_Func)Bt4_MatchFinder_Skip; + vTable->GetMatches = Bt4_MatchFinder_GetMatches; + vTable->Skip = Bt4_MatchFinder_Skip; } - /* else { - vTable->GetMatches = (Mf_GetMatches_Func)Bt5_MatchFinder_GetMatches; - vTable->Skip = (Mf_Skip_Func)Bt5_MatchFinder_Skip; + vTable->GetMatches = Bt5_MatchFinder_GetMatches; + vTable->Skip = Bt5_MatchFinder_Skip; } - */ } + + + +void LzFindPrepare(void) +{ + #ifndef FORCE_LZFIND_SATUR_SUB_128 + #ifdef USE_LZFIND_SATUR_SUB_128 + LZFIND_SATUR_SUB_CODE_FUNC f = NULL; + #ifdef MY_CPU_ARM_OR_ARM64 + { + if (CPU_IsSupported_NEON()) + { + // #pragma message ("=== LzFind NEON") + PRF(printf("\n=== LzFind NEON\n")); + f = LzFind_SaturSub_128; + } + // f = 0; // for debug + } + #else // MY_CPU_ARM_OR_ARM64 + if (CPU_IsSupported_SSE41()) + { + // #pragma message ("=== LzFind SSE41") + PRF(printf("\n=== LzFind SSE41\n")); + f = LzFind_SaturSub_128; + + #ifdef USE_LZFIND_SATUR_SUB_256 + if (CPU_IsSupported_AVX2()) + { + // #pragma message ("=== LzFind AVX2") + PRF(printf("\n=== LzFind AVX2\n")); + f = LzFind_SaturSub_256; + } + #endif + } + #endif // MY_CPU_ARM_OR_ARM64 + g_LzFind_SaturSub = f; + #endif // USE_LZFIND_SATUR_SUB_128 + #endif // FORCE_LZFIND_SATUR_SUB_128 +} + + +#undef MOVE_POS +#undef MOVE_POS_RET +#undef PRF diff --git a/src/plugins/7zip/7za/c/LzFind.h b/src/plugins/7zip/7za/c/LzFind.h index d119944f..67e8a6e0 100644 --- a/src/plugins/7zip/7za/c/LzFind.h +++ b/src/plugins/7zip/7za/c/LzFind.h @@ -1,8 +1,8 @@ /* LzFind.h -- Match finder for LZ algorithms -2015-10-15 : Igor Pavlov : Public domain */ +2024-01-22 : Igor Pavlov : Public domain */ -#ifndef __LZ_FIND_H -#define __LZ_FIND_H +#ifndef ZIP7_INC_LZ_FIND_H +#define ZIP7_INC_LZ_FIND_H #include "7zTypes.h" @@ -10,12 +10,12 @@ EXTERN_C_BEGIN typedef UInt32 CLzRef; -typedef struct _CMatchFinder +typedef struct { - Byte *buffer; + const Byte *buffer; UInt32 pos; UInt32 posLimit; - UInt32 streamPos; + UInt32 streamPos; /* wrap over Zero is allowed (streamPos < pos). Use (UInt32)(streamPos - pos) */ UInt32 lenLimit; UInt32 cyclicBufferPos; @@ -32,8 +32,8 @@ typedef struct _CMatchFinder UInt32 hashMask; UInt32 cutValue; - Byte *bufferBase; - ISeqInStream *stream; + Byte *bufBase; + ISeqInStreamPtr stream; UInt32 blockSize; UInt32 keepSizeBefore; @@ -43,41 +43,79 @@ typedef struct _CMatchFinder size_t directInputRem; UInt32 historySize; UInt32 fixedHashSize; - UInt32 hashSizeSum; + Byte numHashBytes_Min; + Byte numHashOutBits; + Byte _pad2_[2]; SRes result; UInt32 crc[256]; size_t numRefs; + + UInt64 expectedDataSize; } CMatchFinder; -#define Inline_MatchFinder_GetPointerToCurrentPos(p) ((p)->buffer) +#define Inline_MatchFinder_GetPointerToCurrentPos(p) ((const Byte *)(p)->buffer) -#define Inline_MatchFinder_GetNumAvailableBytes(p) ((p)->streamPos - (p)->pos) +#define Inline_MatchFinder_GetNumAvailableBytes(p) ((UInt32)((p)->streamPos - (p)->pos)) +/* #define Inline_MatchFinder_IsFinishedOK(p) \ ((p)->streamEndWasReached \ && (p)->streamPos == (p)->pos \ && (!(p)->directInput || (p)->directInputRem == 0)) +*/ int MatchFinder_NeedMove(CMatchFinder *p); -Byte *MatchFinder_GetPointerToCurrentPos(CMatchFinder *p); +/* Byte *MatchFinder_GetPointerToCurrentPos(CMatchFinder *p); */ void MatchFinder_MoveBlock(CMatchFinder *p); void MatchFinder_ReadIfRequired(CMatchFinder *p); void MatchFinder_Construct(CMatchFinder *p); -/* Conditions: - historySize <= 3 GB - keepAddBufferBefore + matchMaxLen + keepAddBufferAfter < 511MB +/* (directInput = 0) is default value. + It's required to provide correct (directInput) value + before calling MatchFinder_Create(). + You can set (directInput) by any of the following calls: + - MatchFinder_SET_DIRECT_INPUT_BUF() + - MatchFinder_SET_STREAM() + - MatchFinder_SET_STREAM_MODE() +*/ + +#define MatchFinder_SET_DIRECT_INPUT_BUF(p, _src_, _srcLen_) { \ + (p)->stream = NULL; \ + (p)->directInput = 1; \ + (p)->buffer = (_src_); \ + (p)->directInputRem = (_srcLen_); } + +/* +#define MatchFinder_SET_STREAM_MODE(p) { \ + (p)->directInput = 0; } */ + +#define MatchFinder_SET_STREAM(p, _stream_) { \ + (p)->stream = _stream_; \ + (p)->directInput = 0; } + + int MatchFinder_Create(CMatchFinder *p, UInt32 historySize, UInt32 keepAddBufferBefore, UInt32 matchMaxLen, UInt32 keepAddBufferAfter, - ISzAlloc *alloc); -void MatchFinder_Free(CMatchFinder *p, ISzAlloc *alloc); + ISzAllocPtr alloc); +void MatchFinder_Free(CMatchFinder *p, ISzAllocPtr alloc); void MatchFinder_Normalize3(UInt32 subValue, CLzRef *items, size_t numItems); -void MatchFinder_ReduceOffsets(CMatchFinder *p, UInt32 subValue); + +/* +#define MatchFinder_INIT_POS(p, val) \ + (p)->pos = (val); \ + (p)->streamPos = (val); +*/ + +// void MatchFinder_ReduceOffsets(CMatchFinder *p, UInt32 subValue); +#define MatchFinder_REDUCE_OFFSETS(p, subValue) \ + (p)->pos -= (subValue); \ + (p)->streamPos -= (subValue); + UInt32 * GetMatchesSpec1(UInt32 lenLimit, UInt32 curMatch, UInt32 pos, const Byte *buffer, CLzRef *son, - UInt32 _cyclicBufferPos, UInt32 _cyclicBufferSize, UInt32 _cutValue, + size_t _cyclicBufferPos, UInt32 _cyclicBufferSize, UInt32 _cutValue, UInt32 *distances, UInt32 maxLen); /* @@ -89,29 +127,34 @@ UInt32 * GetMatchesSpec1(UInt32 lenLimit, UInt32 curMatch, UInt32 pos, const Byt typedef void (*Mf_Init_Func)(void *object); typedef UInt32 (*Mf_GetNumAvailableBytes_Func)(void *object); typedef const Byte * (*Mf_GetPointerToCurrentPos_Func)(void *object); -typedef UInt32 (*Mf_GetMatches_Func)(void *object, UInt32 *distances); +typedef UInt32 * (*Mf_GetMatches_Func)(void *object, UInt32 *distances); typedef void (*Mf_Skip_Func)(void *object, UInt32); -typedef struct _IMatchFinder +typedef struct { Mf_Init_Func Init; Mf_GetNumAvailableBytes_Func GetNumAvailableBytes; Mf_GetPointerToCurrentPos_Func GetPointerToCurrentPos; Mf_GetMatches_Func GetMatches; Mf_Skip_Func Skip; -} IMatchFinder; +} IMatchFinder2; -void MatchFinder_CreateVTable(CMatchFinder *p, IMatchFinder *vTable); +void MatchFinder_CreateVTable(CMatchFinder *p, IMatchFinder2 *vTable); -void MatchFinder_Init_2(CMatchFinder *p, int readData); -void MatchFinder_Init(CMatchFinder *p); +void MatchFinder_Init_LowHash(CMatchFinder *p); +void MatchFinder_Init_HighHash(CMatchFinder *p); +void MatchFinder_Init_4(CMatchFinder *p); +// void MatchFinder_Init(CMatchFinder *p); +void MatchFinder_Init(void *p); -UInt32 Bt3Zip_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances); -UInt32 Hc3Zip_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances); +UInt32* Bt3Zip_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances); +UInt32* Hc3Zip_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances); void Bt3Zip_MatchFinder_Skip(CMatchFinder *p, UInt32 num); void Hc3Zip_MatchFinder_Skip(CMatchFinder *p, UInt32 num); +void LzFindPrepare(void); + EXTERN_C_END #endif diff --git a/src/plugins/7zip/7za/c/LzFindMt.c b/src/plugins/7zip/7za/c/LzFindMt.c index 9137627c..c9c0561f 100644 --- a/src/plugins/7zip/7za/c/LzFindMt.c +++ b/src/plugins/7zip/7za/c/LzFindMt.c @@ -1,95 +1,217 @@ /* LzFindMt.c -- multithreaded Match finder for LZ algorithms -2015-10-15 : Igor Pavlov : Public domain */ +: Igor Pavlov : Public domain */ #include "Precomp.h" -#include "LzHash.h" +// #include + +#include "CpuArch.h" +#include "LzHash.h" #include "LzFindMt.h" +// #define LOG_ITERS + +// #define LOG_THREAD + +#ifdef LOG_THREAD +#include +#define PRF(x) x +#else +#define PRF(x) +#endif + +#ifdef LOG_ITERS +#include +extern UInt64 g_NumIters_Tree; +extern UInt64 g_NumIters_Loop; +extern UInt64 g_NumIters_Bytes; +#define LOG_ITER(x) x +#else +#define LOG_ITER(x) +#endif + +#define kMtHashBlockSize ((UInt32)1 << 17) +#define kMtHashNumBlocks (1 << 1) + +#define GET_HASH_BLOCK_OFFSET(i) (((i) & (kMtHashNumBlocks - 1)) * kMtHashBlockSize) + +#define kMtBtBlockSize ((UInt32)1 << 16) +#define kMtBtNumBlocks (1 << 4) + +#define GET_BT_BLOCK_OFFSET(i) (((i) & (kMtBtNumBlocks - 1)) * (size_t)kMtBtBlockSize) + +/* + HASH functions: + We use raw 8/16 bits from a[1] and a[2], + xored with crc(a[0]) and crc(a[3]). + We check a[0], a[3] only. We don't need to compare a[1] and a[2] in matches. + our crc() function provides one-to-one correspondence for low 8-bit values: + (crc[0...0xFF] & 0xFF) <-> [0...0xFF] +*/ + +#define MF(mt) ((mt)->MatchFinder) +#define MF_CRC (p->crc) + +// #define MF(mt) (&(mt)->MatchFinder) +// #define MF_CRC (p->MatchFinder.crc) + +#define MT_HASH2_CALC \ + h2 = (MF_CRC[cur[0]] ^ cur[1]) & (kHash2Size - 1); + +#define MT_HASH3_CALC { \ + UInt32 temp = MF_CRC[cur[0]] ^ cur[1]; \ + h2 = temp & (kHash2Size - 1); \ + h3 = (temp ^ ((UInt32)cur[2] << 8)) & (kHash3Size - 1); } + +/* +#define MT_HASH3_CALC__NO_2 { \ + UInt32 temp = p->crc[cur[0]] ^ cur[1]; \ + h3 = (temp ^ ((UInt32)cur[2] << 8)) & (kHash3Size - 1); } + +#define MT_HASH4_CALC { \ + UInt32 temp = p->crc[cur[0]] ^ cur[1]; \ + h2 = temp & (kHash2Size - 1); \ + temp ^= ((UInt32)cur[2] << 8); \ + h3 = temp & (kHash3Size - 1); \ + h4 = (temp ^ (p->crc[cur[3]] << kLzHash_CrcShift_1)) & p->hash4Mask; } + // (kHash4Size - 1); +*/ + + +Z7_NO_INLINE static void MtSync_Construct(CMtSync *p) { + p->affinityGroup = -1; + p->affinityInGroup = 0; + p->affinity = 0; p->wasCreated = False; p->csWasInitialized = False; p->csWasEntered = False; - Thread_Construct(&p->thread); + Thread_CONSTRUCT(&p->thread) Event_Construct(&p->canStart); - Event_Construct(&p->wasStarted); Event_Construct(&p->wasStopped); Semaphore_Construct(&p->freeSemaphore); Semaphore_Construct(&p->filledSemaphore); } -static void MtSync_GetNextBlock(CMtSync *p) + +// #define DEBUG_BUFFER_LOCK // define it to debug lock state + +#ifdef DEBUG_BUFFER_LOCK +#include +#define BUFFER_MUST_BE_LOCKED(p) if (!(p)->csWasEntered) exit(1); +#define BUFFER_MUST_BE_UNLOCKED(p) if ( (p)->csWasEntered) exit(1); +#else +#define BUFFER_MUST_BE_LOCKED(p) +#define BUFFER_MUST_BE_UNLOCKED(p) +#endif + +#define LOCK_BUFFER(p) { \ + BUFFER_MUST_BE_UNLOCKED(p); \ + CriticalSection_Enter(&(p)->cs); \ + (p)->csWasEntered = True; } + +#define UNLOCK_BUFFER(p) { \ + BUFFER_MUST_BE_LOCKED(p); \ + CriticalSection_Leave(&(p)->cs); \ + (p)->csWasEntered = False; } + + +Z7_NO_INLINE +static UInt32 MtSync_GetNextBlock(CMtSync *p) { + UInt32 numBlocks = 0; if (p->needStart) { + BUFFER_MUST_BE_UNLOCKED(p) p->numProcessedBlocks = 1; p->needStart = False; p->stopWriting = False; p->exit = False; - Event_Reset(&p->wasStarted); Event_Reset(&p->wasStopped); - Event_Set(&p->canStart); - Event_Wait(&p->wasStarted); } else { - CriticalSection_Leave(&p->cs); - p->csWasEntered = False; - p->numProcessedBlocks++; + UNLOCK_BUFFER(p) + // we free current block + numBlocks = p->numProcessedBlocks++; Semaphore_Release1(&p->freeSemaphore); } + + // buffer is UNLOCKED here Semaphore_Wait(&p->filledSemaphore); - CriticalSection_Enter(&p->cs); - p->csWasEntered = True; + LOCK_BUFFER(p) + return numBlocks; } -/* MtSync_StopWriting must be called if Writing was started */ +/* if Writing (Processing) thread was started, we must call MtSync_StopWriting() */ + +Z7_NO_INLINE static void MtSync_StopWriting(CMtSync *p) { - UInt32 myNumBlocks = p->numProcessedBlocks; if (!Thread_WasCreated(&p->thread) || p->needStart) return; - p->stopWriting = True; + + PRF(printf("\nMtSync_StopWriting %p\n", p)); + if (p->csWasEntered) { - CriticalSection_Leave(&p->cs); - p->csWasEntered = False; + /* we don't use buffer in this thread after StopWriting(). + So we UNLOCK buffer. + And we restore default UNLOCKED state for stopped thread */ + UNLOCK_BUFFER(p) } - Semaphore_Release1(&p->freeSemaphore); - + + /* We send (p->stopWriting) message and release freeSemaphore + to free current block. + So the thread will see (p->stopWriting) at some + iteration after Wait(freeSemaphore). + The thread doesn't need to fill all avail free blocks, + so we can get fast thread stop. + */ + + p->stopWriting = True; + Semaphore_Release1(&p->freeSemaphore); // check semaphore count !!! + + PRF(printf("\nMtSync_StopWriting %p : Event_Wait(&p->wasStopped)\n", p)); Event_Wait(&p->wasStopped); + PRF(printf("\nMtSync_StopWriting %p : Event_Wait() finsihed\n", p)); + + /* 21.03 : we don't restore samaphore counters here. + We will recreate and reinit samaphores in next start */ - while (myNumBlocks++ != p->numProcessedBlocks) - { - Semaphore_Wait(&p->filledSemaphore); - Semaphore_Release1(&p->freeSemaphore); - } p->needStart = True; } + +Z7_NO_INLINE static void MtSync_Destruct(CMtSync *p) { + PRF(printf("\nMtSync_Destruct %p\n", p)); + if (Thread_WasCreated(&p->thread)) { + /* we want thread to be in Stopped state before sending EXIT command. + note: stop(btSync) will stop (htSync) also */ MtSync_StopWriting(p); + /* thread in Stopped state here : (p->needStart == true) */ p->exit = True; - if (p->needStart) - Event_Set(&p->canStart); - Thread_Wait(&p->thread); - Thread_Close(&p->thread); + // if (p->needStart) // it's (true) + Event_Set(&p->canStart); // we send EXIT command to thread + Thread_Wait_Close(&p->thread); // we wait thread finishing } + if (p->csWasInitialized) { CriticalSection_Delete(&p->cs); p->csWasInitialized = False; } + p->csWasEntered = False; Event_Close(&p->canStart); - Event_Close(&p->wasStarted); Event_Close(&p->wasStopped); Semaphore_Close(&p->freeSemaphore); Semaphore_Close(&p->filledSemaphore); @@ -97,77 +219,257 @@ static void MtSync_Destruct(CMtSync *p) p->wasCreated = False; } -#define RINOK_THREAD(x) { if ((x) != 0) return SZ_ERROR_THREAD; } -static SRes MtSync_Create2(CMtSync *p, THREAD_FUNC_TYPE startAddress, void *obj, UInt32 numBlocks) +// #define RINOK_THREAD(x) { if ((x) != 0) return SZ_ERROR_THREAD; } +// we want to get real system error codes here instead of SZ_ERROR_THREAD +#define RINOK_THREAD(x) RINOK_WRes(x) + + +// call it before each new file (when new starting is required): +Z7_NO_INLINE +static SRes MtSync_Init(CMtSync *p, UInt32 numBlocks) +{ + WRes wres; + // BUFFER_MUST_BE_UNLOCKED(p) + if (!p->needStart || p->csWasEntered) + return SZ_ERROR_FAIL; + wres = Semaphore_OptCreateInit(&p->freeSemaphore, numBlocks, numBlocks); + if (wres == 0) + wres = Semaphore_OptCreateInit(&p->filledSemaphore, 0, numBlocks); + return MY_SRes_HRESULT_FROM_WRes(wres); +} + + +static WRes MtSync_Create_WRes(CMtSync *p, THREAD_FUNC_TYPE startAddress, void *obj) { + WRes wres; + if (p->wasCreated) return SZ_OK; - RINOK_THREAD(CriticalSection_Init(&p->cs)); + RINOK_THREAD(CriticalSection_Init(&p->cs)) p->csWasInitialized = True; + p->csWasEntered = False; - RINOK_THREAD(AutoResetEvent_CreateNotSignaled(&p->canStart)); - RINOK_THREAD(AutoResetEvent_CreateNotSignaled(&p->wasStarted)); - RINOK_THREAD(AutoResetEvent_CreateNotSignaled(&p->wasStopped)); - - RINOK_THREAD(Semaphore_Create(&p->freeSemaphore, numBlocks, numBlocks)); - RINOK_THREAD(Semaphore_Create(&p->filledSemaphore, 0, numBlocks)); + RINOK_THREAD(AutoResetEvent_CreateNotSignaled(&p->canStart)) + RINOK_THREAD(AutoResetEvent_CreateNotSignaled(&p->wasStopped)) p->needStart = True; - - RINOK_THREAD(Thread_Create(&p->thread, startAddress, obj)); + p->exit = True; /* p->exit is unused before (canStart) Event. + But in case of some unexpected code failure we will get fast exit from thread */ + + // return ERROR_TOO_MANY_POSTS; // for debug + // return EINVAL; // for debug + +#ifdef _WIN32 + if (p->affinityGroup >= 0) + wres = Thread_Create_With_Group(&p->thread, startAddress, obj, + (unsigned)(UInt32)p->affinityGroup, (CAffinityMask)p->affinityInGroup); + else +#endif + if (p->affinity != 0) + wres = Thread_Create_With_Affinity(&p->thread, startAddress, obj, (CAffinityMask)p->affinity); + else + wres = Thread_Create(&p->thread, startAddress, obj); + + RINOK_THREAD(wres) p->wasCreated = True; return SZ_OK; } -static SRes MtSync_Create(CMtSync *p, THREAD_FUNC_TYPE startAddress, void *obj, UInt32 numBlocks) + +Z7_NO_INLINE +static SRes MtSync_Create(CMtSync *p, THREAD_FUNC_TYPE startAddress, void *obj) { - SRes res = MtSync_Create2(p, startAddress, obj, numBlocks); - if (res != SZ_OK) - MtSync_Destruct(p); - return res; + const WRes wres = MtSync_Create_WRes(p, startAddress, obj); + if (wres == 0) + return 0; + MtSync_Destruct(p); + return MY_SRes_HRESULT_FROM_WRes(wres); } -void MtSync_Init(CMtSync *p) { p->needStart = True; } + +// ---------- HASH THREAD ---------- #define kMtMaxValForNormalize 0xFFFFFFFF +// #define kMtMaxValForNormalize ((1 << 21)) // for debug +// #define kNormalizeAlign (1 << 7) // alignment for speculated accesses -#define DEF_GetHeads2(name, v, action) \ - static void GetHeads ## name(const Byte *p, UInt32 pos, \ - UInt32 *hash, UInt32 hashMask, UInt32 *heads, UInt32 numHeads, const UInt32 *crc) \ - { action; for (; numHeads != 0; numHeads--) { \ - const UInt32 value = (v); p++; *heads++ = pos - hash[value]; hash[value] = pos++; } } +#ifdef MY_CPU_LE_UNALIGN + #define GetUi24hi_from32(p) ((UInt32)GetUi32(p) >> 8) +#else + #define GetUi24hi_from32(p) ((p)[1] ^ ((UInt32)(p)[2] << 8) ^ ((UInt32)(p)[3] << 16)) +#endif + +#define GetHeads_DECL(name) \ + static void GetHeads ## name(const Byte *p, UInt32 pos, \ + UInt32 *hash, UInt32 hashMask, UInt32 *heads, UInt32 numHeads, const UInt32 *crc) +#define GetHeads_LOOP(v) \ + for (; numHeads != 0; numHeads--) { \ + const UInt32 value = (v); \ + p++; \ + *heads++ = pos - hash[value]; \ + hash[value] = pos++; } + +#define DEF_GetHeads2(name, v, action) \ + GetHeads_DECL(name) { action \ + GetHeads_LOOP(v) } + #define DEF_GetHeads(name, v) DEF_GetHeads2(name, v, ;) -DEF_GetHeads2(2, (p[0] | ((UInt32)p[1] << 8)), UNUSED_VAR(hashMask); UNUSED_VAR(crc); ) -DEF_GetHeads(3, (crc[p[0]] ^ p[1] ^ ((UInt32)p[2] << 8)) & hashMask) -DEF_GetHeads(4, (crc[p[0]] ^ p[1] ^ ((UInt32)p[2] << 8) ^ (crc[p[3]] << 5)) & hashMask) -DEF_GetHeads(4b, (crc[p[0]] ^ p[1] ^ ((UInt32)p[2] << 8) ^ ((UInt32)p[3] << 16)) & hashMask) -/* DEF_GetHeads(5, (crc[p[0]] ^ p[1] ^ ((UInt32)p[2] << 8) ^ (crc[p[3]] << 5) ^ (crc[p[4]] << 3)) & hashMask) */ +DEF_GetHeads2(2, GetUi16(p), UNUSED_VAR(hashMask); UNUSED_VAR(crc); ) +DEF_GetHeads(3, (crc[p[0]] ^ GetUi16(p + 1)) & hashMask) +DEF_GetHeads2(3b, GetUi16(p) ^ ((UInt32)(p)[2] << 16), UNUSED_VAR(hashMask); UNUSED_VAR(crc); ) +// BT3 is not good for crc collisions for big hashMask values. + +/* +GetHeads_DECL(3b) +{ + UNUSED_VAR(hashMask); + UNUSED_VAR(crc); + { + const Byte *pLim = p + numHeads; + if (numHeads == 0) + return; + pLim--; + while (p < pLim) + { + UInt32 v1 = GetUi32(p); + UInt32 v0 = v1 & 0xFFFFFF; + UInt32 h0, h1; + p += 2; + v1 >>= 8; + h0 = hash[v0]; hash[v0] = pos; heads[0] = pos - h0; pos++; + h1 = hash[v1]; hash[v1] = pos; heads[1] = pos - h1; pos++; + heads += 2; + } + if (p == pLim) + { + UInt32 v0 = GetUi16(p) ^ ((UInt32)(p)[2] << 16); + *heads = pos - hash[v0]; + hash[v0] = pos; + } + } +} +*/ + +/* +GetHeads_DECL(4) +{ + unsigned sh = 0; + UNUSED_VAR(crc) + while ((hashMask & 0x80000000) == 0) + { + hashMask <<= 1; + sh++; + } + GetHeads_LOOP((GetUi32(p) * 0xa54a1) >> sh) +} +#define GetHeads4b GetHeads4 +*/ + +#define USE_GetHeads_LOCAL_CRC + +#ifdef USE_GetHeads_LOCAL_CRC + +GetHeads_DECL(4) +{ + UInt32 crc0[256]; + UInt32 crc1[256]; + { + unsigned i; + for (i = 0; i < 256; i++) + { + UInt32 v = crc[i]; + crc0[i] = v & hashMask; + crc1[i] = (v << kLzHash_CrcShift_1) & hashMask; + // crc1[i] = rotlFixed(v, 8) & hashMask; + } + } + GetHeads_LOOP(crc0[p[0]] ^ crc1[p[3]] ^ (UInt32)GetUi16(p+1)) +} + +GetHeads_DECL(4b) +{ + UInt32 crc0[256]; + { + unsigned i; + for (i = 0; i < 256; i++) + crc0[i] = crc[i] & hashMask; + } + GetHeads_LOOP(crc0[p[0]] ^ GetUi24hi_from32(p)) +} + +GetHeads_DECL(5) +{ + UInt32 crc0[256]; + UInt32 crc1[256]; + UInt32 crc2[256]; + { + unsigned i; + for (i = 0; i < 256; i++) + { + UInt32 v = crc[i]; + crc0[i] = v & hashMask; + crc1[i] = (v << kLzHash_CrcShift_1) & hashMask; + crc2[i] = (v << kLzHash_CrcShift_2) & hashMask; + } + } + GetHeads_LOOP(crc0[p[0]] ^ crc1[p[3]] ^ crc2[p[4]] ^ (UInt32)GetUi16(p+1)) +} + +GetHeads_DECL(5b) +{ + UInt32 crc0[256]; + UInt32 crc1[256]; + { + unsigned i; + for (i = 0; i < 256; i++) + { + UInt32 v = crc[i]; + crc0[i] = v & hashMask; + crc1[i] = (v << kLzHash_CrcShift_1) & hashMask; + } + } + GetHeads_LOOP(crc0[p[0]] ^ crc1[p[4]] ^ GetUi24hi_from32(p)) +} + +#else + +DEF_GetHeads(4, (crc[p[0]] ^ (crc[p[3]] << kLzHash_CrcShift_1) ^ (UInt32)GetUi16(p+1)) & hashMask) +DEF_GetHeads(4b, (crc[p[0]] ^ GetUi24hi_from32(p)) & hashMask) +DEF_GetHeads(5, (crc[p[0]] ^ (crc[p[3]] << kLzHash_CrcShift_1) ^ (crc[p[4]] << kLzHash_CrcShift_2) ^ (UInt32)GetUi16(p + 1)) & hashMask) +DEF_GetHeads(5b, (crc[p[0]] ^ (crc[p[4]] << kLzHash_CrcShift_1) ^ GetUi24hi_from32(p)) & hashMask) + +#endif + static void HashThreadFunc(CMatchFinderMt *mt) { CMtSync *p = &mt->hashSync; + PRF(printf("\nHashThreadFunc\n")); + for (;;) { - UInt32 numProcessedBlocks = 0; + UInt32 blockIndex = 0; + PRF(printf("\nHashThreadFunc : Event_Wait(&p->canStart)\n")); Event_Wait(&p->canStart); - Event_Set(&p->wasStarted); + PRF(printf("\nHashThreadFunc : Event_Wait(&p->canStart) : after \n")); + if (p->exit) + { + PRF(printf("\nHashThreadFunc : exit \n")); + return; + } + + MatchFinder_Init_HighHash(MF(mt)); + for (;;) { - if (p->exit) - return; - if (p->stopWriting) - { - p->numProcessedBlocks = numProcessedBlocks; - Event_Set(&p->wasStopped); - break; - } + PRF(printf("Hash thread block = %d pos = %d\n", (unsigned)blockIndex, mt->MatchFinder->pos)); { - CMatchFinder *mf = mt->MatchFinder; + CMatchFinder *mf = MF(mt); if (MatchFinder_NeedMove(mf)) { CriticalSection_Enter(&mt->btSync.cs); @@ -180,163 +482,178 @@ static void HashThreadFunc(CMatchFinderMt *mt) mt->pointerToCurPos -= offset; mt->buffer -= offset; } - CriticalSection_Leave(&mt->btSync.cs); CriticalSection_Leave(&mt->hashSync.cs); + CriticalSection_Leave(&mt->btSync.cs); continue; } Semaphore_Wait(&p->freeSemaphore); + if (p->exit) // exit is unexpected here. But we check it here for some failure case + return; + + // for faster stop : we check (p->stopWriting) after Wait(freeSemaphore) + if (p->stopWriting) + break; + MatchFinder_ReadIfRequired(mf); - if (mf->pos > (kMtMaxValForNormalize - kMtHashBlockSize)) - { - UInt32 subValue = (mf->pos - mf->historySize - 1); - MatchFinder_ReduceOffsets(mf, subValue); - MatchFinder_Normalize3(subValue, mf->hash + mf->fixedHashSize, (size_t)mf->hashMask + 1); - } { - UInt32 *heads = mt->hashBuf + ((numProcessedBlocks++) & kMtHashNumBlocksMask) * kMtHashBlockSize; - UInt32 num = mf->streamPos - mf->pos; + UInt32 *heads = mt->hashBuf + GET_HASH_BLOCK_OFFSET(blockIndex++); + UInt32 num = Inline_MatchFinder_GetNumAvailableBytes(mf); heads[0] = 2; heads[1] = num; + + /* heads[1] contains the number of avail bytes: + if (avail < mf->numHashBytes) : + { + it means that stream was finished + HASH_THREAD and BT_TREAD must move position for heads[1] (avail) bytes. + HASH_THREAD doesn't stop, + HASH_THREAD fills only the header (2 numbers) for all next blocks: + {2, NumHashBytes - 1}, {2,0}, {2,0}, ... , {2,0} + } + else + { + HASH_THREAD and BT_TREAD must move position for (heads[0] - 2) bytes; + } + */ + if (num >= mf->numHashBytes) { num = num - mf->numHashBytes + 1; if (num > kMtHashBlockSize - 2) num = kMtHashBlockSize - 2; + + if (mf->pos > (UInt32)kMtMaxValForNormalize - num) + { + const UInt32 subValue = (mf->pos - mf->historySize - 1); // & ~(UInt32)(kNormalizeAlign - 1); + MatchFinder_REDUCE_OFFSETS(mf, subValue) + MatchFinder_Normalize3(subValue, mf->hash + mf->fixedHashSize, (size_t)mf->hashMask + 1); + } + + heads[0] = 2 + num; mt->GetHeadsFunc(mf->buffer, mf->pos, mf->hash + mf->fixedHashSize, mf->hashMask, heads + 2, num, mf->crc); - heads[0] += num; } - mf->pos += num; + + mf->pos += num; // wrap over zero is allowed at the end of stream mf->buffer += num; } } Semaphore_Release1(&p->filledSemaphore); - } - } -} + } // for() processing end -static void MatchFinderMt_GetNextBlock_Hash(CMatchFinderMt *p) -{ - MtSync_GetNextBlock(&p->hashSync); - p->hashBufPosLimit = p->hashBufPos = ((p->hashSync.numProcessedBlocks - 1) & kMtHashNumBlocksMask) * kMtHashBlockSize; - p->hashBufPosLimit += p->hashBuf[p->hashBufPos++]; - p->hashNumAvail = p->hashBuf[p->hashBufPos++]; + // p->numBlocks_Sent = blockIndex; + Event_Set(&p->wasStopped); + } // for() thread end } -#define kEmptyHashValue 0 -/* #define MFMT_GM_INLINE */ + + +// ---------- BT THREAD ---------- + +/* we use one variable instead of two (cyclicBufferPos == pos) before CyclicBuf wrap. + here we define fixed offset of (p->pos) from (p->cyclicBufferPos) */ +#define CYC_TO_POS_OFFSET 0 +// #define CYC_TO_POS_OFFSET 1 // for debug + +#define MFMT_GM_INLINE #ifdef MFMT_GM_INLINE -#define NO_INLINE MY_FAST_CALL +/* + we use size_t for (pos) instead of UInt32 + to eliminate "movsx" BUG in old MSVC x64 compiler. +*/ -static Int32 NO_INLINE GetMatchesSpecN(UInt32 lenLimit, UInt32 pos, const Byte *cur, CLzRef *son, - UInt32 _cyclicBufferPos, UInt32 _cyclicBufferSize, UInt32 _cutValue, - UInt32 *_distances, UInt32 _maxLen, const UInt32 *hash, Int32 limit, UInt32 size, UInt32 *posRes) -{ - do - { - UInt32 *distances = _distances + 1; - UInt32 curMatch = pos - *hash++; - CLzRef *ptr0 = son + (_cyclicBufferPos << 1) + 1; - CLzRef *ptr1 = son + (_cyclicBufferPos << 1); - UInt32 len0 = 0, len1 = 0; - UInt32 cutValue = _cutValue; - UInt32 maxLen = _maxLen; - for (;;) - { - UInt32 delta = pos - curMatch; - if (cutValue-- == 0 || delta >= _cyclicBufferSize) - { - *ptr0 = *ptr1 = kEmptyHashValue; - break; - } - { - CLzRef *pair = son + ((_cyclicBufferPos - delta + ((delta > _cyclicBufferPos) ? _cyclicBufferSize : 0)) << 1); - const Byte *pb = cur - delta; - UInt32 len = (len0 < len1 ? len0 : len1); - if (pb[len] == cur[len]) - { - if (++len != lenLimit && pb[len] == cur[len]) - while (++len != lenLimit) - if (pb[len] != cur[len]) - break; - if (maxLen < len) - { - *distances++ = maxLen = len; - *distances++ = delta - 1; - if (len == lenLimit) - { - *ptr1 = pair[0]; - *ptr0 = pair[1]; - break; - } - } - } - if (pb[len] < cur[len]) - { - *ptr1 = curMatch; - ptr1 = pair + 1; - curMatch = *ptr1; - len1 = len; - } - else - { - *ptr0 = curMatch; - ptr0 = pair; - curMatch = *ptr0; - len0 = len; - } - } - } - pos++; - _cyclicBufferPos++; - cur++; - { - UInt32 num = (UInt32)(distances - _distances); - *_distances = num - 1; - _distances += num; - limit -= num; - } - } - while (limit > 0 && --size != 0); - *posRes = pos; - return limit; -} +UInt32 * Z7_FASTCALL GetMatchesSpecN_2(const Byte *lenLimit, size_t pos, const Byte *cur, CLzRef *son, + UInt32 _cutValue, UInt32 *d, size_t _maxLen, const UInt32 *hash, const UInt32 *limit, const UInt32 *size, + size_t _cyclicBufferPos, UInt32 _cyclicBufferSize, + UInt32 *posRes); #endif -static void BtGetMatches(CMatchFinderMt *p, UInt32 *distances) + +static void BtGetMatches(CMatchFinderMt *p, UInt32 *d) { UInt32 numProcessed = 0; UInt32 curPos = 2; - UInt32 limit = kMtBtBlockSize - (p->matchMaxLen * 2); - distances[1] = p->hashNumAvail; + /* GetMatchesSpec() functions don't create (len = 1) + in [len, dist] match pairs, if (p->numHashBytes >= 2) + Also we suppose here that (matchMaxLen >= 2). + So the following code for (reserve) is not required + UInt32 reserve = (p->matchMaxLen * 2); + const UInt32 kNumHashBytes_Max = 5; // BT_HASH_BYTES_MAX + if (reserve < kNumHashBytes_Max - 1) + reserve = kNumHashBytes_Max - 1; + const UInt32 limit = kMtBtBlockSize - (reserve); + */ + + const UInt32 limit = kMtBtBlockSize - (p->matchMaxLen * 2); + + d[1] = p->hashNumAvail; + + if (p->failure_BT) + { + // printf("\n == 1 BtGetMatches() p->failure_BT\n"); + d[0] = 0; + // d[1] = 0; + return; + } while (curPos < limit) { if (p->hashBufPos == p->hashBufPosLimit) { - MatchFinderMt_GetNextBlock_Hash(p); - distances[1] = numProcessed + p->hashNumAvail; - if (p->hashNumAvail >= p->numHashBytes) + // MatchFinderMt_GetNextBlock_Hash(p); + UInt32 avail; + { + const UInt32 bi = MtSync_GetNextBlock(&p->hashSync); + const UInt32 k = GET_HASH_BLOCK_OFFSET(bi); + const UInt32 *h = p->hashBuf + k; + avail = h[1]; + p->hashBufPosLimit = k + h[0]; + p->hashNumAvail = avail; + p->hashBufPos = k + 2; + } + + { + /* we must prevent UInt32 overflow for avail total value, + if avail was increased with new hash block */ + UInt32 availSum = numProcessed + avail; + if (availSum < numProcessed) + availSum = (UInt32)(Int32)-1; + d[1] = availSum; + } + + if (avail >= p->numHashBytes) continue; - distances[0] = curPos + p->hashNumAvail; - distances += curPos; - for (; p->hashNumAvail != 0; p->hashNumAvail--) - *distances++ = 0; + + // if (p->hashBufPos != p->hashBufPosLimit) exit(1); + + /* (avail < p->numHashBytes) + It means that stream was finished. + And (avail) - is a number of remaining bytes, + we fill (d) for (avail) bytes for LZ_THREAD (receiver). + but we don't update (p->pos) and (p->cyclicBufferPos) here in BT_THREAD */ + + /* here we suppose that we have space enough: + (kMtBtBlockSize - curPos >= p->hashNumAvail) */ + p->hashNumAvail = 0; + d[0] = curPos + avail; + d += curPos; + for (; avail != 0; avail--) + *d++ = 0; return; } { UInt32 size = p->hashBufPosLimit - p->hashBufPos; - UInt32 lenLimit = p->matchMaxLen; UInt32 pos = p->pos; UInt32 cyclicBufferPos = p->cyclicBufferPos; + UInt32 lenLimit = p->matchMaxLen; if (lenLimit >= p->hashNumAvail) lenLimit = p->hashNumAvail; { @@ -348,10 +665,18 @@ static void BtGetMatches(CMatchFinderMt *p, UInt32 *distances) size = size2; } + if (pos > (UInt32)kMtMaxValForNormalize - size) + { + const UInt32 subValue = (pos - p->cyclicBufferSize); // & ~(UInt32)(kNormalizeAlign - 1); + pos -= subValue; + p->pos = pos; + MatchFinder_Normalize3(subValue, p->son, (size_t)p->cyclicBufferSize * 2); + } + #ifndef MFMT_GM_INLINE while (curPos < limit && size-- != 0) { - UInt32 *startDistances = distances + curPos; + UInt32 *startDistances = d + curPos; UInt32 num = (UInt32)(GetMatchesSpec1(lenLimit, pos - p->hashBuf[p->hashBufPos++], pos, p->buffer, p->son, cyclicBufferPos, p->cyclicBufferSize, p->cutValue, startDistances + 1, p->numHashBytes - 1) - startDistances); @@ -363,79 +688,112 @@ static void BtGetMatches(CMatchFinderMt *p, UInt32 *distances) } #else { - UInt32 posRes; - curPos = limit - GetMatchesSpecN(lenLimit, pos, p->buffer, p->son, cyclicBufferPos, p->cyclicBufferSize, p->cutValue, - distances + curPos, p->numHashBytes - 1, p->hashBuf + p->hashBufPos, (Int32)(limit - curPos), size, &posRes); - p->hashBufPos += posRes - pos; - cyclicBufferPos += posRes - pos; - p->buffer += posRes - pos; - pos = posRes; + UInt32 posRes = pos; + const UInt32 *d_end; + { + d_end = GetMatchesSpecN_2( + p->buffer + lenLimit - 1, + pos, p->buffer, p->son, p->cutValue, d + curPos, + p->numHashBytes - 1, p->hashBuf + p->hashBufPos, + d + limit, p->hashBuf + p->hashBufPos + size, + cyclicBufferPos, p->cyclicBufferSize, + &posRes); + } + { + if (!d_end) + { + // printf("\n == 2 BtGetMatches() p->failure_BT\n"); + // internal data failure + p->failure_BT = True; + d[0] = 0; + // d[1] = 0; + return; + } + } + curPos = (UInt32)(d_end - d); + { + const UInt32 processed = posRes - pos; + pos = posRes; + p->hashBufPos += processed; + cyclicBufferPos += processed; + p->buffer += processed; + } } #endif - numProcessed += pos - p->pos; - p->hashNumAvail -= pos - p->pos; - p->pos = pos; + { + const UInt32 processed = pos - p->pos; + numProcessed += processed; + p->hashNumAvail -= processed; + p->pos = pos; + } if (cyclicBufferPos == p->cyclicBufferSize) cyclicBufferPos = 0; p->cyclicBufferPos = cyclicBufferPos; } } - distances[0] = curPos; + d[0] = curPos; } + static void BtFillBlock(CMatchFinderMt *p, UInt32 globalBlockIndex) { CMtSync *sync = &p->hashSync; + + BUFFER_MUST_BE_UNLOCKED(sync) + if (!sync->needStart) { - CriticalSection_Enter(&sync->cs); - sync->csWasEntered = True; + LOCK_BUFFER(sync) } - BtGetMatches(p, p->btBuf + (globalBlockIndex & kMtBtNumBlocksMask) * kMtBtBlockSize); - - if (p->pos > kMtMaxValForNormalize - kMtBtBlockSize) - { - UInt32 subValue = p->pos - p->cyclicBufferSize; - MatchFinder_Normalize3(subValue, p->son, (size_t)p->cyclicBufferSize * 2); - p->pos -= subValue; - } + BtGetMatches(p, p->btBuf + GET_BT_BLOCK_OFFSET(globalBlockIndex)); + + /* We suppose that we have called GetNextBlock() from start. + So buffer is LOCKED */ - if (!sync->needStart) - { - CriticalSection_Leave(&sync->cs); - sync->csWasEntered = False; - } + UNLOCK_BUFFER(sync) } -void BtThreadFunc(CMatchFinderMt *mt) + +Z7_NO_INLINE +static void BtThreadFunc(CMatchFinderMt *mt) { CMtSync *p = &mt->btSync; for (;;) { UInt32 blockIndex = 0; Event_Wait(&p->canStart); - Event_Set(&p->wasStarted); + for (;;) { + PRF(printf(" BT thread block = %d pos = %d\n", (unsigned)blockIndex, mt->pos)); + /* (p->exit == true) is possible after (p->canStart) at first loop iteration + and is unexpected after more Wait(freeSemaphore) iterations */ if (p->exit) return; + + Semaphore_Wait(&p->freeSemaphore); + + // for faster stop : we check (p->stopWriting) after Wait(freeSemaphore) if (p->stopWriting) - { - p->numProcessedBlocks = blockIndex; - MtSync_StopWriting(&mt->hashSync); - Event_Set(&p->wasStopped); break; - } - Semaphore_Wait(&p->freeSemaphore); + BtFillBlock(mt, blockIndex++); + Semaphore_Release1(&p->filledSemaphore); } + + // we stop HASH_THREAD here + MtSync_StopWriting(&mt->hashSync); + + // p->numBlocks_Sent = blockIndex; + Event_Set(&p->wasStopped); } } + void MatchFinderMt_Construct(CMatchFinderMt *p) { p->hashBuf = NULL; @@ -443,32 +801,55 @@ void MatchFinderMt_Construct(CMatchFinderMt *p) MtSync_Construct(&p->btSync); } -static void MatchFinderMt_FreeMem(CMatchFinderMt *p, ISzAlloc *alloc) +static void MatchFinderMt_FreeMem(CMatchFinderMt *p, ISzAllocPtr alloc) { - alloc->Free(alloc, p->hashBuf); + ISzAlloc_Free(alloc, p->hashBuf); p->hashBuf = NULL; } -void MatchFinderMt_Destruct(CMatchFinderMt *p, ISzAlloc *alloc) +void MatchFinderMt_Destruct(CMatchFinderMt *p, ISzAllocPtr alloc) { - MtSync_Destruct(&p->hashSync); + /* + HASH_THREAD can use CriticalSection(s) btSync.cs and hashSync.cs. + So we must be sure that HASH_THREAD will not use CriticalSection(s) + after deleting CriticalSection here. + + we call ReleaseStream(p) + that calls StopWriting(btSync) + that calls StopWriting(hashSync), if it's required to stop HASH_THREAD. + after StopWriting() it's safe to destruct MtSync(s) in any order */ + + MatchFinderMt_ReleaseStream(p); + MtSync_Destruct(&p->btSync); + MtSync_Destruct(&p->hashSync); + + LOG_ITER( + printf("\nTree %9d * %7d iter = %9d = sum : bytes = %9d\n", + (UInt32)(g_NumIters_Tree / 1000), + (UInt32)(((UInt64)g_NumIters_Loop * 1000) / (g_NumIters_Tree + 1)), + (UInt32)(g_NumIters_Loop / 1000), + (UInt32)(g_NumIters_Bytes / 1000) + )); + MatchFinderMt_FreeMem(p, alloc); } + #define kHashBufferSize (kMtHashBlockSize * kMtHashNumBlocks) #define kBtBufferSize (kMtBtBlockSize * kMtBtNumBlocks) -static THREAD_FUNC_RET_TYPE THREAD_FUNC_CALL_TYPE HashThreadFunc2(void *p) { HashThreadFunc((CMatchFinderMt *)p); return 0; } -// OPENSAL_7ZIP_PATCH BEGIN +static THREAD_FUNC_DECL HashThreadFunc2(void *p) { HashThreadFunc((CMatchFinderMt *)p); return 0; } + +// Keep 7-Zip worker stacks visible to the host's crash diagnostics. DWORD RunThreadWithCallStackObject(LPTHREAD_START_ROUTINE startAddress, LPVOID parameter); -static THREAD_FUNC_RET_TYPE THREAD_FUNC_CALL_TYPE HashThreadFunc2Wrap(void *p) { - return (THREAD_FUNC_RET_TYPE) RunThreadWithCallStackObject((LPTHREAD_START_ROUTINE) HashThreadFunc2, p); +static THREAD_FUNC_DECL HashThreadFunc2WithSalamanderStack(void *p) +{ + return (THREAD_FUNC_RET_TYPE)RunThreadWithCallStackObject((LPTHREAD_START_ROUTINE)HashThreadFunc2, p); } -// OPENSAL_7ZIP_PATCH END -static THREAD_FUNC_RET_TYPE THREAD_FUNC_CALL_TYPE BtThreadFunc2(void *p) +static THREAD_FUNC_DECL BtThreadFunc2(void *p) { Byte allocaDummy[0x180]; unsigned i = 0; @@ -479,22 +860,22 @@ static THREAD_FUNC_RET_TYPE THREAD_FUNC_CALL_TYPE BtThreadFunc2(void *p) return 0; } -// OPENSAL_7ZIP_PATCH BEGIN -static THREAD_FUNC_RET_TYPE THREAD_FUNC_CALL_TYPE BtThreadFunc2Wrap(void *p) { - return (THREAD_FUNC_RET_TYPE) RunThreadWithCallStackObject((LPTHREAD_START_ROUTINE) BtThreadFunc2, p); +static THREAD_FUNC_DECL BtThreadFunc2WithSalamanderStack(void *p) +{ + return (THREAD_FUNC_RET_TYPE)RunThreadWithCallStackObject((LPTHREAD_START_ROUTINE)BtThreadFunc2, p); } -// OPENSAL_7ZIP_PATCH END + SRes MatchFinderMt_Create(CMatchFinderMt *p, UInt32 historySize, UInt32 keepAddBufferBefore, - UInt32 matchMaxLen, UInt32 keepAddBufferAfter, ISzAlloc *alloc) + UInt32 matchMaxLen, UInt32 keepAddBufferAfter, ISzAllocPtr alloc) { - CMatchFinder *mf = p->MatchFinder; + CMatchFinder *mf = MF(p); p->historySize = historySize; if (kMtBtBlockSize <= matchMaxLen * 4) return SZ_ERROR_PARAM; if (!p->hashBuf) { - p->hashBuf = (UInt32 *)alloc->Alloc(alloc, (kHashBufferSize + kBtBufferSize) * sizeof(UInt32)); + p->hashBuf = (UInt32 *)ISzAlloc_Alloc(alloc, ((size_t)kHashBufferSize + (size_t)kBtBufferSize) * sizeof(UInt32)); if (!p->hashBuf) return SZ_ERROR_MEM; p->btBuf = p->hashBuf + kHashBufferSize; @@ -504,252 +885,472 @@ SRes MatchFinderMt_Create(CMatchFinderMt *p, UInt32 historySize, UInt32 keepAddB if (!MatchFinder_Create(mf, historySize, keepAddBufferBefore, matchMaxLen, keepAddBufferAfter, alloc)) return SZ_ERROR_MEM; -// OPENSAL_7ZIP_PATCH BEGIN - RINOK(MtSync_Create(&p->hashSync, HashThreadFunc2Wrap, p, kMtHashNumBlocks)); - RINOK(MtSync_Create(&p->btSync, BtThreadFunc2Wrap, p, kMtBtNumBlocks)); -// OPENSAL_7ZIP_PATCH END + RINOK(MtSync_Create(&p->hashSync, HashThreadFunc2WithSalamanderStack, p)) + RINOK(MtSync_Create(&p->btSync, BtThreadFunc2WithSalamanderStack, p)) return SZ_OK; } -/* Call it after ReleaseStream / SetStream */ -void MatchFinderMt_Init(CMatchFinderMt *p) + +SRes MatchFinderMt_InitMt(CMatchFinderMt *p) { - CMatchFinder *mf = p->MatchFinder; - p->btBufPos = p->btBufPosLimit = 0; - p->hashBufPos = p->hashBufPosLimit = 0; + RINOK(MtSync_Init(&p->hashSync, kMtHashNumBlocks)) + return MtSync_Init(&p->btSync, kMtBtNumBlocks); +} + + +static void MatchFinderMt_Init(void *_p) +{ + CMatchFinderMt *p = (CMatchFinderMt *)_p; + CMatchFinder *mf = MF(p); + + p->btBufPos = + p->btBufPosLimit = NULL; + p->hashBufPos = + p->hashBufPosLimit = 0; + p->hashNumAvail = 0; // 21.03 + + p->failure_BT = False; /* Init without data reading. We don't want to read data in this thread */ - MatchFinder_Init_2(mf, False); + MatchFinder_Init_4(mf); + + MatchFinder_Init_LowHash(mf); p->pointerToCurPos = Inline_MatchFinder_GetPointerToCurrentPos(mf); p->btNumAvailBytes = 0; - p->lzPos = p->historySize + 1; + p->failure_LZ_BT = False; + // p->failure_LZ_LZ = False; + + p->lzPos = + 1; // optimal smallest value + // 0; // for debug: ignores match to start + // kNormalizeAlign; // for debug p->hash = mf->hash; p->fixedHashSize = mf->fixedHashSize; + // p->hash4Mask = mf->hash4Mask; p->crc = mf->crc; + // memcpy(p->crc, mf->crc, sizeof(mf->crc)); p->son = mf->son; p->matchMaxLen = mf->matchMaxLen; p->numHashBytes = mf->numHashBytes; - p->pos = mf->pos; - p->buffer = mf->buffer; - p->cyclicBufferPos = mf->cyclicBufferPos; + + /* (mf->pos) and (mf->streamPos) were already initialized to 1 in MatchFinder_Init_4() */ + // mf->streamPos = mf->pos = 1; // optimal smallest value + // 0; // for debug: ignores match to start + // kNormalizeAlign; // for debug + + /* we must init (p->pos = mf->pos) for BT, because + BT code needs (p->pos == delta_value_for_empty_hash_record == mf->pos) */ + p->pos = mf->pos; // do not change it + + p->cyclicBufferPos = (p->pos - CYC_TO_POS_OFFSET); p->cyclicBufferSize = mf->cyclicBufferSize; + p->buffer = mf->buffer; p->cutValue = mf->cutValue; + // p->son[0] = p->son[1] = 0; // unused: to init skipped record for speculated accesses. } + /* ReleaseStream is required to finish multithreading */ void MatchFinderMt_ReleaseStream(CMatchFinderMt *p) { + // Sleep(1); // for debug MtSync_StopWriting(&p->btSync); + // Sleep(200); // for debug /* p->MatchFinder->ReleaseStream(); */ } -static void MatchFinderMt_Normalize(CMatchFinderMt *p) -{ - MatchFinder_Normalize3(p->lzPos - p->historySize - 1, p->hash, p->fixedHashSize); - p->lzPos = p->historySize + 1; -} -static void MatchFinderMt_GetNextBlock_Bt(CMatchFinderMt *p) +Z7_NO_INLINE +static UInt32 MatchFinderMt_GetNextBlock_Bt(CMatchFinderMt *p) { - UInt32 blockIndex; - MtSync_GetNextBlock(&p->btSync); - blockIndex = ((p->btSync.numProcessedBlocks - 1) & kMtBtNumBlocksMask); - p->btBufPosLimit = p->btBufPos = blockIndex * kMtBtBlockSize; - p->btBufPosLimit += p->btBuf[p->btBufPos++]; - p->btNumAvailBytes = p->btBuf[p->btBufPos++]; - if (p->lzPos >= kMtMaxValForNormalize - kMtBtBlockSize) - MatchFinderMt_Normalize(p); + if (p->failure_LZ_BT) + p->btBufPos = p->failureBuf; + else + { + const UInt32 bi = MtSync_GetNextBlock(&p->btSync); + const UInt32 *bt = p->btBuf + GET_BT_BLOCK_OFFSET(bi); + { + const UInt32 numItems = bt[0]; + p->btBufPosLimit = bt + numItems; + p->btNumAvailBytes = bt[1]; + p->btBufPos = bt + 2; + if (numItems < 2 || numItems > kMtBtBlockSize) + { + p->failureBuf[0] = 0; + p->btBufPos = p->failureBuf; + p->btBufPosLimit = p->failureBuf + 1; + p->failure_LZ_BT = True; + // p->btNumAvailBytes = 0; + /* we don't want to decrease AvailBytes, that was load before. + that can be unxepected for the code that have loaded anopther value before */ + } + } + + if (p->lzPos >= (UInt32)kMtMaxValForNormalize - (UInt32)kMtBtBlockSize) + { + /* we don't check (lzPos) over exact avail bytes in (btBuf). + (fixedHashSize) is small, so normalization is fast */ + const UInt32 subValue = (p->lzPos - p->historySize - 1); // & ~(UInt32)(kNormalizeAlign - 1); + p->lzPos -= subValue; + MatchFinder_Normalize3(subValue, p->hash, p->fixedHashSize); + } + } + return p->btNumAvailBytes; } -static const Byte * MatchFinderMt_GetPointerToCurrentPos(CMatchFinderMt *p) + + +static const Byte * MatchFinderMt_GetPointerToCurrentPos(void *_p) { + CMatchFinderMt *p = (CMatchFinderMt *)_p; return p->pointerToCurPos; } + #define GET_NEXT_BLOCK_IF_REQUIRED if (p->btBufPos == p->btBufPosLimit) MatchFinderMt_GetNextBlock_Bt(p); -static UInt32 MatchFinderMt_GetNumAvailableBytes(CMatchFinderMt *p) + +static UInt32 MatchFinderMt_GetNumAvailableBytes(void *_p) { - GET_NEXT_BLOCK_IF_REQUIRED; - return p->btNumAvailBytes; + CMatchFinderMt *p = (CMatchFinderMt *)_p; + if (p->btBufPos != p->btBufPosLimit) + return p->btNumAvailBytes; + return MatchFinderMt_GetNextBlock_Bt(p); } -static UInt32 * MixMatches2(CMatchFinderMt *p, UInt32 matchMinPos, UInt32 *distances) + +// #define CHECK_FAILURE_LZ(_match_, _pos_) if (_match_ >= _pos_) { p->failure_LZ_LZ = True; return d; } +#define CHECK_FAILURE_LZ(_match_, _pos_) + +static UInt32 * MixMatches2(CMatchFinderMt *p, UInt32 matchMinPos, UInt32 *d) { - UInt32 h2, curMatch2; + UInt32 h2, c2; UInt32 *hash = p->hash; const Byte *cur = p->pointerToCurPos; - UInt32 lzPos = p->lzPos; + const UInt32 m = p->lzPos; MT_HASH2_CALC - curMatch2 = hash[h2]; - hash[h2] = lzPos; + c2 = hash[h2]; + hash[h2] = m; - if (curMatch2 >= matchMinPos) - if (cur[(ptrdiff_t)curMatch2 - lzPos] == cur[0]) + if (c2 >= matchMinPos) + { + CHECK_FAILURE_LZ(c2, m) + if (cur[(ptrdiff_t)c2 - (ptrdiff_t)m] == cur[0]) { - *distances++ = 2; - *distances++ = lzPos - curMatch2 - 1; + *d++ = 2; + *d++ = m - c2 - 1; } + } - return distances; + return d; } -static UInt32 * MixMatches3(CMatchFinderMt *p, UInt32 matchMinPos, UInt32 *distances) +static UInt32 * MixMatches3(CMatchFinderMt *p, UInt32 matchMinPos, UInt32 *d) { - UInt32 h2, h3, curMatch2, curMatch3; + UInt32 h2, h3, c2, c3; UInt32 *hash = p->hash; const Byte *cur = p->pointerToCurPos; - UInt32 lzPos = p->lzPos; + const UInt32 m = p->lzPos; MT_HASH3_CALC - curMatch2 = hash[ h2]; - curMatch3 = hash[kFix3HashSize + h3]; + c2 = hash[h2]; + c3 = (hash + kFix3HashSize)[h3]; - hash[ h2] = lzPos; - hash[kFix3HashSize + h3] = lzPos; + hash[h2] = m; + (hash + kFix3HashSize)[h3] = m; - if (curMatch2 >= matchMinPos && cur[(ptrdiff_t)curMatch2 - lzPos] == cur[0]) + if (c2 >= matchMinPos) { - distances[1] = lzPos - curMatch2 - 1; - if (cur[(ptrdiff_t)curMatch2 - lzPos + 2] == cur[2]) + CHECK_FAILURE_LZ(c2, m) + if (cur[(ptrdiff_t)c2 - (ptrdiff_t)m] == cur[0]) { - distances[0] = 3; - return distances + 2; + d[1] = m - c2 - 1; + if (cur[(ptrdiff_t)c2 - (ptrdiff_t)m + 2] == cur[2]) + { + d[0] = 3; + return d + 2; + } + d[0] = 2; + d += 2; } - distances[0] = 2; - distances += 2; } - if (curMatch3 >= matchMinPos && cur[(ptrdiff_t)curMatch3 - lzPos] == cur[0]) + if (c3 >= matchMinPos) { - *distances++ = 3; - *distances++ = lzPos - curMatch3 - 1; + CHECK_FAILURE_LZ(c3, m) + if (cur[(ptrdiff_t)c3 - (ptrdiff_t)m] == cur[0]) + { + *d++ = 3; + *d++ = m - c3 - 1; + } } - return distances; + return d; } + +#define INCREASE_LZ_POS p->lzPos++; p->pointerToCurPos++; + /* -static UInt32 *MixMatches4(CMatchFinderMt *p, UInt32 matchMinPos, UInt32 *distances) +static +UInt32* MatchFinderMt_GetMatches_Bt4(CMatchFinderMt *p, UInt32 *d) { - UInt32 h2, h3, h4, curMatch2, curMatch3, curMatch4; + const UInt32 *bt = p->btBufPos; + const UInt32 len = *bt++; + const UInt32 *btLim = bt + len; + UInt32 matchMinPos; + UInt32 avail = p->btNumAvailBytes - 1; + p->btBufPos = btLim; + + { + p->btNumAvailBytes = avail; + + #define BT_HASH_BYTES_MAX 5 + + matchMinPos = p->lzPos; + + if (len != 0) + matchMinPos -= bt[1]; + else if (avail < (BT_HASH_BYTES_MAX - 1) - 1) + { + INCREASE_LZ_POS + return d; + } + else + { + const UInt32 hs = p->historySize; + if (matchMinPos > hs) + matchMinPos -= hs; + else + matchMinPos = 1; + } + } + + for (;;) + { + + UInt32 h2, h3, c2, c3; UInt32 *hash = p->hash; const Byte *cur = p->pointerToCurPos; - UInt32 lzPos = p->lzPos; - MT_HASH4_CALC - - curMatch2 = hash[ h2]; - curMatch3 = hash[kFix3HashSize + h3]; - curMatch4 = hash[kFix4HashSize + h4]; + UInt32 m = p->lzPos; + MT_HASH3_CALC + + c2 = hash[h2]; + c3 = (hash + kFix3HashSize)[h3]; + + hash[h2] = m; + (hash + kFix3HashSize)[h3] = m; + + if (c2 >= matchMinPos && cur[(ptrdiff_t)c2 - (ptrdiff_t)m] == cur[0]) + { + d[1] = m - c2 - 1; + if (cur[(ptrdiff_t)c2 - (ptrdiff_t)m + 2] == cur[2]) + { + d[0] = 3; + d += 2; + break; + } + // else + { + d[0] = 2; + d += 2; + } + } + if (c3 >= matchMinPos && cur[(ptrdiff_t)c3 - (ptrdiff_t)m] == cur[0]) + { + *d++ = 3; + *d++ = m - c3 - 1; + } + break; + } + + if (len != 0) + { + do + { + const UInt32 v0 = bt[0]; + const UInt32 v1 = bt[1]; + bt += 2; + d[0] = v0; + d[1] = v1; + d += 2; + } + while (bt != btLim); + } + INCREASE_LZ_POS + return d; +} +*/ + + +static UInt32 * MixMatches4(CMatchFinderMt *p, UInt32 matchMinPos, UInt32 *d) +{ + UInt32 h2, h3, /* h4, */ c2, c3 /* , c4 */; + UInt32 *hash = p->hash; + const Byte *cur = p->pointerToCurPos; + const UInt32 m = p->lzPos; + MT_HASH3_CALC + // MT_HASH4_CALC + c2 = hash[h2]; + c3 = (hash + kFix3HashSize)[h3]; + // c4 = (hash + kFix4HashSize)[h4]; - hash[ h2] = lzPos; - hash[kFix3HashSize + h3] = lzPos; - hash[kFix4HashSize + h4] = lzPos; + hash[h2] = m; + (hash + kFix3HashSize)[h3] = m; + // (hash + kFix4HashSize)[h4] = m; - if (curMatch2 >= matchMinPos && cur[(ptrdiff_t)curMatch2 - lzPos] == cur[0]) + // #define BT5_USE_H2 + // #ifdef BT5_USE_H2 + if (c2 >= matchMinPos && cur[(ptrdiff_t)c2 - (ptrdiff_t)m] == cur[0]) { - distances[1] = lzPos - curMatch2 - 1; - if (cur[(ptrdiff_t)curMatch2 - lzPos + 2] == cur[2]) + d[1] = m - c2 - 1; + if (cur[(ptrdiff_t)c2 - (ptrdiff_t)m + 2] == cur[2]) { - distances[0] = (cur[(ptrdiff_t)curMatch2 - lzPos + 3] == cur[3]) ? 4 : 3; - return distances + 2; + // d[0] = (cur[(ptrdiff_t)c2 - (ptrdiff_t)m + 3] == cur[3]) ? 4 : 3; + // return d + 2; + + if (cur[(ptrdiff_t)c2 - (ptrdiff_t)m + 3] == cur[3]) + { + d[0] = 4; + return d + 2; + } + d[0] = 3; + d += 2; + + #ifdef BT5_USE_H4 + if (c4 >= matchMinPos) + if ( + cur[(ptrdiff_t)c4 - (ptrdiff_t)m] == cur[0] && + cur[(ptrdiff_t)c4 - (ptrdiff_t)m + 3] == cur[3] + ) + { + *d++ = 4; + *d++ = m - c4 - 1; + } + #endif + return d; } - distances[0] = 2; - distances += 2; + d[0] = 2; + d += 2; } + // #endif - if (curMatch3 >= matchMinPos && cur[(ptrdiff_t)curMatch3 - lzPos] == cur[0]) + if (c3 >= matchMinPos && cur[(ptrdiff_t)c3 - (ptrdiff_t)m] == cur[0]) { - distances[1] = lzPos - curMatch3 - 1; - if (cur[(ptrdiff_t)curMatch3 - lzPos + 3] == cur[3]) + d[1] = m - c3 - 1; + if (cur[(ptrdiff_t)c3 - (ptrdiff_t)m + 3] == cur[3]) { - distances[0] = 4; - return distances + 2; + d[0] = 4; + return d + 2; } - distances[0] = 3; - distances += 2; + d[0] = 3; + d += 2; } - if (curMatch4 >= matchMinPos) + #ifdef BT5_USE_H4 + if (c4 >= matchMinPos) if ( - cur[(ptrdiff_t)curMatch4 - lzPos] == cur[0] && - cur[(ptrdiff_t)curMatch4 - lzPos + 3] == cur[3] + cur[(ptrdiff_t)c4 - (ptrdiff_t)m] == cur[0] && + cur[(ptrdiff_t)c4 - (ptrdiff_t)m + 3] == cur[3] ) { - *distances++ = 4; - *distances++ = lzPos - curMatch4 - 1; + *d++ = 4; + *d++ = m - c4 - 1; } + #endif - return distances; + return d; } -*/ -#define INCREASE_LZ_POS p->lzPos++; p->pointerToCurPos++; -static UInt32 MatchFinderMt2_GetMatches(CMatchFinderMt *p, UInt32 *distances) +static UInt32 * MatchFinderMt2_GetMatches(void *_p, UInt32 *d) { - const UInt32 *btBuf = p->btBuf + p->btBufPos; - UInt32 len = *btBuf++; - p->btBufPos += 1 + len; + CMatchFinderMt *p = (CMatchFinderMt *)_p; + const UInt32 *bt = p->btBufPos; + const UInt32 len = *bt++; + const UInt32 *btLim = bt + len; + p->btBufPos = btLim; p->btNumAvailBytes--; + INCREASE_LZ_POS { - UInt32 i; - for (i = 0; i < len; i += 2) + while (bt != btLim) { - *distances++ = *btBuf++; - *distances++ = *btBuf++; + const UInt32 v0 = bt[0]; + const UInt32 v1 = bt[1]; + bt += 2; + d[0] = v0; + d[1] = v1; + d += 2; } } - INCREASE_LZ_POS - return len; + return d; } -static UInt32 MatchFinderMt_GetMatches(CMatchFinderMt *p, UInt32 *distances) -{ - const UInt32 *btBuf = p->btBuf + p->btBufPos; - UInt32 len = *btBuf++; - p->btBufPos += 1 + len; + +static UInt32 * MatchFinderMt_GetMatches(void *_p, UInt32 *d) +{ + CMatchFinderMt *p = (CMatchFinderMt *)_p; + const UInt32 *bt = p->btBufPos; + UInt32 len = *bt++; + const UInt32 avail = p->btNumAvailBytes - 1; + p->btNumAvailBytes = avail; + p->btBufPos = bt + len; if (len == 0) { - /* change for bt5 ! */ - if (p->btNumAvailBytes-- >= 4) - len = (UInt32)(p->MixMatchesFunc(p, p->lzPos - p->historySize, distances) - (distances)); + #define BT_HASH_BYTES_MAX 5 + if (avail >= (BT_HASH_BYTES_MAX - 1) - 1) + { + UInt32 m = p->lzPos; + if (m > p->historySize) + m -= p->historySize; + else + m = 1; + d = p->MixMatchesFunc(p, m, d); + } } else { - /* Condition: there are matches in btBuf with length < p->numHashBytes */ - UInt32 *distances2; - p->btNumAvailBytes--; - distances2 = p->MixMatchesFunc(p, p->lzPos - btBuf[1], distances); + /* + first match pair from BinTree: (match_len, match_dist), + (match_len >= numHashBytes). + MixMatchesFunc() inserts only hash matches that are nearer than (match_dist) + */ + d = p->MixMatchesFunc(p, p->lzPos - bt[1], d); + // if (d) // check for failure do { - *distances2++ = *btBuf++; - *distances2++ = *btBuf++; + const UInt32 v0 = bt[0]; + const UInt32 v1 = bt[1]; + bt += 2; + d[0] = v0; + d[1] = v1; + d += 2; } - while ((len -= 2) != 0); - len = (UInt32)(distances2 - (distances)); + while (len -= 2); } INCREASE_LZ_POS - return len; + return d; } #define SKIP_HEADER2_MT do { GET_NEXT_BLOCK_IF_REQUIRED #define SKIP_HEADER_MT(n) SKIP_HEADER2_MT if (p->btNumAvailBytes-- >= (n)) { const Byte *cur = p->pointerToCurPos; UInt32 *hash = p->hash; -#define SKIP_FOOTER_MT } INCREASE_LZ_POS p->btBufPos += p->btBuf[p->btBufPos] + 1; } while (--num != 0); +#define SKIP_FOOTER_MT } INCREASE_LZ_POS p->btBufPos += (size_t)*p->btBufPos + 1; } while (--num != 0); -static void MatchFinderMt0_Skip(CMatchFinderMt *p, UInt32 num) +static void MatchFinderMt0_Skip(void *_p, UInt32 num) { + CMatchFinderMt *p = (CMatchFinderMt *)_p; SKIP_HEADER2_MT { p->btNumAvailBytes--; SKIP_FOOTER_MT } -static void MatchFinderMt2_Skip(CMatchFinderMt *p, UInt32 num) +static void MatchFinderMt2_Skip(void *_p, UInt32 num) { + CMatchFinderMt *p = (CMatchFinderMt *)_p; SKIP_HEADER_MT(2) UInt32 h2; MT_HASH2_CALC @@ -757,63 +1358,78 @@ static void MatchFinderMt2_Skip(CMatchFinderMt *p, UInt32 num) SKIP_FOOTER_MT } -static void MatchFinderMt3_Skip(CMatchFinderMt *p, UInt32 num) +static void MatchFinderMt3_Skip(void *_p, UInt32 num) { + CMatchFinderMt *p = (CMatchFinderMt *)_p; SKIP_HEADER_MT(3) UInt32 h2, h3; MT_HASH3_CALC - hash[kFix3HashSize + h3] = + (hash + kFix3HashSize)[h3] = hash[ h2] = p->lzPos; SKIP_FOOTER_MT } /* +// MatchFinderMt4_Skip() is similar to MatchFinderMt3_Skip(). +// The difference is that MatchFinderMt3_Skip() updates hash for last 3 bytes of stream. + static void MatchFinderMt4_Skip(CMatchFinderMt *p, UInt32 num) { SKIP_HEADER_MT(4) - UInt32 h2, h3, h4; - MT_HASH4_CALC - hash[kFix4HashSize + h4] = - hash[kFix3HashSize + h3] = + UInt32 h2, h3; // h4 + MT_HASH3_CALC + // MT_HASH4_CALC + // (hash + kFix4HashSize)[h4] = + (hash + kFix3HashSize)[h3] = hash[ h2] = p->lzPos; SKIP_FOOTER_MT } */ -void MatchFinderMt_CreateVTable(CMatchFinderMt *p, IMatchFinder *vTable) +void MatchFinderMt_CreateVTable(CMatchFinderMt *p, IMatchFinder2 *vTable) { - vTable->Init = (Mf_Init_Func)MatchFinderMt_Init; - vTable->GetNumAvailableBytes = (Mf_GetNumAvailableBytes_Func)MatchFinderMt_GetNumAvailableBytes; - vTable->GetPointerToCurrentPos = (Mf_GetPointerToCurrentPos_Func)MatchFinderMt_GetPointerToCurrentPos; - vTable->GetMatches = (Mf_GetMatches_Func)MatchFinderMt_GetMatches; + vTable->Init = MatchFinderMt_Init; + vTable->GetNumAvailableBytes = MatchFinderMt_GetNumAvailableBytes; + vTable->GetPointerToCurrentPos = MatchFinderMt_GetPointerToCurrentPos; + vTable->GetMatches = MatchFinderMt_GetMatches; - switch (p->MatchFinder->numHashBytes) + switch (MF(p)->numHashBytes) { case 2: p->GetHeadsFunc = GetHeads2; - p->MixMatchesFunc = (Mf_Mix_Matches)0; - vTable->Skip = (Mf_Skip_Func)MatchFinderMt0_Skip; - vTable->GetMatches = (Mf_GetMatches_Func)MatchFinderMt2_GetMatches; + p->MixMatchesFunc = NULL; + vTable->Skip = MatchFinderMt0_Skip; + vTable->GetMatches = MatchFinderMt2_GetMatches; break; case 3: - p->GetHeadsFunc = GetHeads3; - p->MixMatchesFunc = (Mf_Mix_Matches)MixMatches2; - vTable->Skip = (Mf_Skip_Func)MatchFinderMt2_Skip; + p->GetHeadsFunc = MF(p)->bigHash ? GetHeads3b : GetHeads3; + p->MixMatchesFunc = MixMatches2; + vTable->Skip = MatchFinderMt2_Skip; break; - default: - /* case 4: */ - p->GetHeadsFunc = p->MatchFinder->bigHash ? GetHeads4b : GetHeads4; - p->MixMatchesFunc = (Mf_Mix_Matches)MixMatches3; - vTable->Skip = (Mf_Skip_Func)MatchFinderMt3_Skip; + case 4: + p->GetHeadsFunc = MF(p)->bigHash ? GetHeads4b : GetHeads4; + + // it's fast inline version of GetMatches() + // vTable->GetMatches = MatchFinderMt_GetMatches_Bt4; + + p->MixMatchesFunc = MixMatches3; + vTable->Skip = MatchFinderMt3_Skip; break; - /* default: - p->GetHeadsFunc = GetHeads5; - p->MixMatchesFunc = (Mf_Mix_Matches)MixMatches4; - vTable->Skip = (Mf_Skip_Func)MatchFinderMt4_Skip; + p->GetHeadsFunc = MF(p)->bigHash ? GetHeads5b : GetHeads5; + p->MixMatchesFunc = MixMatches4; + vTable->Skip = + MatchFinderMt3_Skip; + // MatchFinderMt4_Skip; break; - */ } } + +#undef RINOK_THREAD +#undef PRF +#undef MF +#undef GetUi24hi_from32 +#undef LOCK_BUFFER +#undef UNLOCK_BUFFER diff --git a/src/plugins/7zip/7za/c/LzFindMt.h b/src/plugins/7zip/7za/c/LzFindMt.h index 89b91fef..89984f52 100644 --- a/src/plugins/7zip/7za/c/LzFindMt.h +++ b/src/plugins/7zip/7za/c/LzFindMt.h @@ -1,42 +1,42 @@ /* LzFindMt.h -- multithreaded Match finder for LZ algorithms -2015-05-03 : Igor Pavlov : Public domain */ +: Igor Pavlov : Public domain */ -#ifndef __LZ_FIND_MT_H -#define __LZ_FIND_MT_H +#ifndef ZIP7_INC_LZ_FIND_MT_H +#define ZIP7_INC_LZ_FIND_MT_H #include "LzFind.h" #include "Threads.h" EXTERN_C_BEGIN -#define kMtHashBlockSize (1 << 13) -#define kMtHashNumBlocks (1 << 3) -#define kMtHashNumBlocksMask (kMtHashNumBlocks - 1) +typedef struct +{ + UInt32 numProcessedBlocks; + Int32 affinityGroup; + UInt64 affinityInGroup; + UInt64 affinity; + CThread thread; -#define kMtBtBlockSize (1 << 14) -#define kMtBtNumBlocks (1 << 6) -#define kMtBtNumBlocksMask (kMtBtNumBlocks - 1) + BoolInt wasCreated; + BoolInt needStart; + BoolInt csWasInitialized; + BoolInt csWasEntered; -typedef struct _CMtSync -{ - Bool wasCreated; - Bool needStart; - Bool exit; - Bool stopWriting; + BoolInt exit; + BoolInt stopWriting; - CThread thread; CAutoResetEvent canStart; - CAutoResetEvent wasStarted; CAutoResetEvent wasStopped; CSemaphore freeSemaphore; CSemaphore filledSemaphore; - Bool csWasInitialized; - Bool csWasEntered; CCriticalSection cs; - UInt32 numProcessedBlocks; + // UInt32 numBlocks_Sent; } CMtSync; -typedef UInt32 * (*Mf_Mix_Matches)(void *p, UInt32 matchMinPos, UInt32 *distances); + +struct CMatchFinderMt_; + +typedef UInt32 * (*Mf_Mix_Matches)(struct CMatchFinderMt_ *p, UInt32 matchMinPos, UInt32 *distances); /* kMtCacheLineDummy must be >= size_of_CPU_cache_line */ #define kMtCacheLineDummy 128 @@ -44,23 +44,28 @@ typedef UInt32 * (*Mf_Mix_Matches)(void *p, UInt32 matchMinPos, UInt32 *distance typedef void (*Mf_GetHeads)(const Byte *buffer, UInt32 pos, UInt32 *hash, UInt32 hashMask, UInt32 *heads, UInt32 numHeads, const UInt32 *crc); -typedef struct _CMatchFinderMt +typedef struct CMatchFinderMt_ { /* LZ */ const Byte *pointerToCurPos; UInt32 *btBuf; - UInt32 btBufPos; - UInt32 btBufPosLimit; + const UInt32 *btBufPos; + const UInt32 *btBufPosLimit; UInt32 lzPos; UInt32 btNumAvailBytes; UInt32 *hash; UInt32 fixedHashSize; + // UInt32 hash4Mask; UInt32 historySize; const UInt32 *crc; Mf_Mix_Matches MixMatchesFunc; - + UInt32 failure_LZ_BT; // failure in BT transfered to LZ + // UInt32 failure_LZ_LZ; // failure in LZ tables + UInt32 failureBuf[1]; + // UInt32 crc[256]; + /* LZ + BT */ CMtSync btSync; Byte btDummy[kMtCacheLineDummy]; @@ -70,6 +75,8 @@ typedef struct _CMatchFinderMt UInt32 hashBufPos; UInt32 hashBufPosLimit; UInt32 hashNumAvail; + UInt32 failure_BT; + CLzRef *son; UInt32 matchMaxLen; @@ -77,7 +84,7 @@ typedef struct _CMatchFinderMt UInt32 pos; const Byte *buffer; UInt32 cyclicBufferPos; - UInt32 cyclicBufferSize; /* it must be historySize + 1 */ + UInt32 cyclicBufferSize; /* it must be = (historySize + 1) */ UInt32 cutValue; /* BT + Hash */ @@ -87,13 +94,19 @@ typedef struct _CMatchFinderMt /* Hash */ Mf_GetHeads GetHeadsFunc; CMatchFinder *MatchFinder; + // CMatchFinder MatchFinder; } CMatchFinderMt; +// only for Mt part void MatchFinderMt_Construct(CMatchFinderMt *p); -void MatchFinderMt_Destruct(CMatchFinderMt *p, ISzAlloc *alloc); +void MatchFinderMt_Destruct(CMatchFinderMt *p, ISzAllocPtr alloc); + SRes MatchFinderMt_Create(CMatchFinderMt *p, UInt32 historySize, UInt32 keepAddBufferBefore, - UInt32 matchMaxLen, UInt32 keepAddBufferAfter, ISzAlloc *alloc); -void MatchFinderMt_CreateVTable(CMatchFinderMt *p, IMatchFinder *vTable); + UInt32 matchMaxLen, UInt32 keepAddBufferAfter, ISzAllocPtr alloc); +void MatchFinderMt_CreateVTable(CMatchFinderMt *p, IMatchFinder2 *vTable); + +/* call MatchFinderMt_InitMt() before IMatchFinder::Init() */ +SRes MatchFinderMt_InitMt(CMatchFinderMt *p); void MatchFinderMt_ReleaseStream(CMatchFinderMt *p); EXTERN_C_END diff --git a/src/plugins/7zip/7za/c/LzFindOpt.c b/src/plugins/7zip/7za/c/LzFindOpt.c new file mode 100644 index 00000000..85bdc136 --- /dev/null +++ b/src/plugins/7zip/7za/c/LzFindOpt.c @@ -0,0 +1,578 @@ +/* LzFindOpt.c -- multithreaded Match finder for LZ algorithms +2023-04-02 : Igor Pavlov : Public domain */ + +#include "Precomp.h" + +#include "CpuArch.h" +#include "LzFind.h" + +// #include "LzFindMt.h" + +// #define LOG_ITERS + +// #define LOG_THREAD + +#ifdef LOG_THREAD +#include +#define PRF(x) x +#else +// #define PRF(x) +#endif + +#ifdef LOG_ITERS +#include +UInt64 g_NumIters_Tree; +UInt64 g_NumIters_Loop; +UInt64 g_NumIters_Bytes; +#define LOG_ITER(x) x +#else +#define LOG_ITER(x) +#endif + +// ---------- BT THREAD ---------- + +#define USE_SON_PREFETCH +#define USE_LONG_MATCH_OPT + +#define kEmptyHashValue 0 + +// #define CYC_TO_POS_OFFSET 0 + +// #define CYC_TO_POS_OFFSET 1 // for debug + +/* +Z7_NO_INLINE +UInt32 * Z7_FASTCALL GetMatchesSpecN_1(const Byte *lenLimit, size_t pos, const Byte *cur, CLzRef *son, + UInt32 _cutValue, UInt32 *d, size_t _maxLen, const UInt32 *hash, const UInt32 *limit, const UInt32 *size, UInt32 *posRes) +{ + do + { + UInt32 delta; + if (hash == size) + break; + delta = *hash++; + + if (delta == 0 || delta > (UInt32)pos) + return NULL; + + lenLimit++; + + if (delta == (UInt32)pos) + { + CLzRef *ptr1 = son + ((size_t)pos << 1) - CYC_TO_POS_OFFSET * 2; + *d++ = 0; + ptr1[0] = kEmptyHashValue; + ptr1[1] = kEmptyHashValue; + } +else +{ + UInt32 *_distances = ++d; + + CLzRef *ptr0 = son + ((size_t)(pos) << 1) - CYC_TO_POS_OFFSET * 2 + 1; + CLzRef *ptr1 = son + ((size_t)(pos) << 1) - CYC_TO_POS_OFFSET * 2; + + const Byte *len0 = cur, *len1 = cur; + UInt32 cutValue = _cutValue; + const Byte *maxLen = cur + _maxLen; + + for (LOG_ITER(g_NumIters_Tree++);;) + { + LOG_ITER(g_NumIters_Loop++); + { + const ptrdiff_t diff = (ptrdiff_t)0 - (ptrdiff_t)delta; + CLzRef *pair = son + ((size_t)(((ptrdiff_t)pos - CYC_TO_POS_OFFSET) + diff) << 1); + const Byte *len = (len0 < len1 ? len0 : len1); + + #ifdef USE_SON_PREFETCH + const UInt32 pair0 = *pair; + #endif + + if (len[diff] == len[0]) + { + if (++len != lenLimit && len[diff] == len[0]) + while (++len != lenLimit) + { + LOG_ITER(g_NumIters_Bytes++); + if (len[diff] != len[0]) + break; + } + if (maxLen < len) + { + maxLen = len; + *d++ = (UInt32)(len - cur); + *d++ = delta - 1; + + if (len == lenLimit) + { + const UInt32 pair1 = pair[1]; + *ptr1 = + #ifdef USE_SON_PREFETCH + pair0; + #else + pair[0]; + #endif + *ptr0 = pair1; + + _distances[-1] = (UInt32)(d - _distances); + + #ifdef USE_LONG_MATCH_OPT + + if (hash == size || *hash != delta || lenLimit[diff] != lenLimit[0] || d >= limit) + break; + + { + for (;;) + { + hash++; + pos++; + cur++; + lenLimit++; + { + CLzRef *ptr = son + ((size_t)(pos) << 1) - CYC_TO_POS_OFFSET * 2; + #if 0 + *(UInt64 *)(void *)ptr = ((const UInt64 *)(const void *)ptr)[diff]; + #else + const UInt32 p0 = ptr[0 + (diff * 2)]; + const UInt32 p1 = ptr[1 + (diff * 2)]; + ptr[0] = p0; + ptr[1] = p1; + // ptr[0] = ptr[0 + (diff * 2)]; + // ptr[1] = ptr[1 + (diff * 2)]; + #endif + } + // PrintSon(son + 2, pos - 1); + // printf("\npos = %x delta = %x\n", pos, delta); + len++; + *d++ = 2; + *d++ = (UInt32)(len - cur); + *d++ = delta - 1; + if (hash == size || *hash != delta || lenLimit[diff] != lenLimit[0] || d >= limit) + break; + } + } + #endif + + break; + } + } + } + + { + const UInt32 curMatch = (UInt32)pos - delta; // (UInt32)(pos + diff); + if (len[diff] < len[0]) + { + delta = pair[1]; + if (delta >= curMatch) + return NULL; + *ptr1 = curMatch; + ptr1 = pair + 1; + len1 = len; + } + else + { + delta = *pair; + if (delta >= curMatch) + return NULL; + *ptr0 = curMatch; + ptr0 = pair; + len0 = len; + } + + delta = (UInt32)pos - delta; + + if (--cutValue == 0 || delta >= pos) + { + *ptr0 = *ptr1 = kEmptyHashValue; + _distances[-1] = (UInt32)(d - _distances); + break; + } + } + } + } // for (tree iterations) +} + pos++; + cur++; + } + while (d < limit); + *posRes = (UInt32)pos; + return d; +} +*/ + +/* define cbs if you use 2 functions. + GetMatchesSpecN_1() : (pos < _cyclicBufferSize) + GetMatchesSpecN_2() : (pos >= _cyclicBufferSize) + + do not define cbs if you use 1 function: + GetMatchesSpecN_2() +*/ + +// #define cbs _cyclicBufferSize + +/* + we use size_t for (pos) and (_cyclicBufferPos_ instead of UInt32 + to eliminate "movsx" BUG in old MSVC x64 compiler. +*/ + +UInt32 * Z7_FASTCALL GetMatchesSpecN_2(const Byte *lenLimit, size_t pos, const Byte *cur, CLzRef *son, + UInt32 _cutValue, UInt32 *d, size_t _maxLen, const UInt32 *hash, const UInt32 *limit, const UInt32 *size, + size_t _cyclicBufferPos, UInt32 _cyclicBufferSize, + UInt32 *posRes); + +Z7_NO_INLINE +UInt32 * Z7_FASTCALL GetMatchesSpecN_2(const Byte *lenLimit, size_t pos, const Byte *cur, CLzRef *son, + UInt32 _cutValue, UInt32 *d, size_t _maxLen, const UInt32 *hash, const UInt32 *limit, const UInt32 *size, + size_t _cyclicBufferPos, UInt32 _cyclicBufferSize, + UInt32 *posRes) +{ + do // while (hash != size) + { + UInt32 delta; + + #ifndef cbs + UInt32 cbs; + #endif + + if (hash == size) + break; + + delta = *hash++; + + if (delta == 0) + return NULL; + + lenLimit++; + + #ifndef cbs + cbs = _cyclicBufferSize; + if ((UInt32)pos < cbs) + { + if (delta > (UInt32)pos) + return NULL; + cbs = (UInt32)pos; + } + #endif + + if (delta >= cbs) + { + CLzRef *ptr1 = son + ((size_t)_cyclicBufferPos << 1); + *d++ = 0; + ptr1[0] = kEmptyHashValue; + ptr1[1] = kEmptyHashValue; + } +else +{ + UInt32 *_distances = ++d; + + CLzRef *ptr0 = son + ((size_t)_cyclicBufferPos << 1) + 1; + CLzRef *ptr1 = son + ((size_t)_cyclicBufferPos << 1); + + UInt32 cutValue = _cutValue; + const Byte *len0 = cur, *len1 = cur; + const Byte *maxLen = cur + _maxLen; + + // if (cutValue == 0) { *ptr0 = *ptr1 = kEmptyHashValue; } else + for (LOG_ITER(g_NumIters_Tree++);;) + { + LOG_ITER(g_NumIters_Loop++); + { + // SPEC code + CLzRef *pair = son + ((size_t)((ptrdiff_t)_cyclicBufferPos - (ptrdiff_t)delta + + (ptrdiff_t)(UInt32)(_cyclicBufferPos < delta ? cbs : 0) + ) << 1); + + const ptrdiff_t diff = (ptrdiff_t)0 - (ptrdiff_t)delta; + const Byte *len = (len0 < len1 ? len0 : len1); + + #ifdef USE_SON_PREFETCH + const UInt32 pair0 = *pair; + #endif + + if (len[diff] == len[0]) + { + if (++len != lenLimit && len[diff] == len[0]) + while (++len != lenLimit) + { + LOG_ITER(g_NumIters_Bytes++); + if (len[diff] != len[0]) + break; + } + if (maxLen < len) + { + maxLen = len; + *d++ = (UInt32)(len - cur); + *d++ = delta - 1; + + if (len == lenLimit) + { + const UInt32 pair1 = pair[1]; + *ptr1 = + #ifdef USE_SON_PREFETCH + pair0; + #else + pair[0]; + #endif + *ptr0 = pair1; + + _distances[-1] = (UInt32)(d - _distances); + + #ifdef USE_LONG_MATCH_OPT + + if (hash == size || *hash != delta || lenLimit[diff] != lenLimit[0] || d >= limit) + break; + + { + for (;;) + { + *d++ = 2; + *d++ = (UInt32)(lenLimit - cur); + *d++ = delta - 1; + cur++; + lenLimit++; + // SPEC + _cyclicBufferPos++; + { + // SPEC code + CLzRef *dest = son + ((size_t)(_cyclicBufferPos) << 1); + const CLzRef *src = dest + ((diff + + (ptrdiff_t)(UInt32)((_cyclicBufferPos < delta) ? cbs : 0)) << 1); + // CLzRef *ptr = son + ((size_t)(pos) << 1) - CYC_TO_POS_OFFSET * 2; + #if 0 + *(UInt64 *)(void *)dest = *((const UInt64 *)(const void *)src); + #else + const UInt32 p0 = src[0]; + const UInt32 p1 = src[1]; + dest[0] = p0; + dest[1] = p1; + #endif + } + pos++; + hash++; + if (hash == size || *hash != delta || lenLimit[diff] != lenLimit[0] || d >= limit) + break; + } // for() end for long matches + } + #endif + + break; // break from TREE iterations + } + } + } + { + const UInt32 curMatch = (UInt32)pos - delta; // (UInt32)(pos + diff); + if (len[diff] < len[0]) + { + delta = pair[1]; + *ptr1 = curMatch; + ptr1 = pair + 1; + len1 = len; + if (delta >= curMatch) + return NULL; + } + else + { + delta = *pair; + *ptr0 = curMatch; + ptr0 = pair; + len0 = len; + if (delta >= curMatch) + return NULL; + } + delta = (UInt32)pos - delta; + + if (--cutValue == 0 || delta >= cbs) + { + *ptr0 = *ptr1 = kEmptyHashValue; + _distances[-1] = (UInt32)(d - _distances); + break; + } + } + } + } // for (tree iterations) +} + pos++; + _cyclicBufferPos++; + cur++; + } + while (d < limit); + *posRes = (UInt32)pos; + return d; +} + + + +/* +typedef UInt32 uint32plus; // size_t + +UInt32 * Z7_FASTCALL GetMatchesSpecN_3(uint32plus lenLimit, size_t pos, const Byte *cur, CLzRef *son, + UInt32 _cutValue, UInt32 *d, uint32plus _maxLen, const UInt32 *hash, const UInt32 *limit, const UInt32 *size, + size_t _cyclicBufferPos, UInt32 _cyclicBufferSize, + UInt32 *posRes) +{ + do // while (hash != size) + { + UInt32 delta; + + #ifndef cbs + UInt32 cbs; + #endif + + if (hash == size) + break; + + delta = *hash++; + + if (delta == 0) + return NULL; + + #ifndef cbs + cbs = _cyclicBufferSize; + if ((UInt32)pos < cbs) + { + if (delta > (UInt32)pos) + return NULL; + cbs = (UInt32)pos; + } + #endif + + if (delta >= cbs) + { + CLzRef *ptr1 = son + ((size_t)_cyclicBufferPos << 1); + *d++ = 0; + ptr1[0] = kEmptyHashValue; + ptr1[1] = kEmptyHashValue; + } +else +{ + CLzRef *ptr0 = son + ((size_t)_cyclicBufferPos << 1) + 1; + CLzRef *ptr1 = son + ((size_t)_cyclicBufferPos << 1); + UInt32 *_distances = ++d; + uint32plus len0 = 0, len1 = 0; + UInt32 cutValue = _cutValue; + uint32plus maxLen = _maxLen; + // lenLimit++; // const Byte *lenLimit = cur + _lenLimit; + + for (LOG_ITER(g_NumIters_Tree++);;) + { + LOG_ITER(g_NumIters_Loop++); + { + // const ptrdiff_t diff = (ptrdiff_t)0 - (ptrdiff_t)delta; + CLzRef *pair = son + ((size_t)((ptrdiff_t)_cyclicBufferPos - delta + + (ptrdiff_t)(UInt32)(_cyclicBufferPos < delta ? cbs : 0) + ) << 1); + const Byte *pb = cur - delta; + uint32plus len = (len0 < len1 ? len0 : len1); + + #ifdef USE_SON_PREFETCH + const UInt32 pair0 = *pair; + #endif + + if (pb[len] == cur[len]) + { + if (++len != lenLimit && pb[len] == cur[len]) + while (++len != lenLimit) + if (pb[len] != cur[len]) + break; + if (maxLen < len) + { + maxLen = len; + *d++ = (UInt32)len; + *d++ = delta - 1; + if (len == lenLimit) + { + { + const UInt32 pair1 = pair[1]; + *ptr0 = pair1; + *ptr1 = + #ifdef USE_SON_PREFETCH + pair0; + #else + pair[0]; + #endif + } + + _distances[-1] = (UInt32)(d - _distances); + + #ifdef USE_LONG_MATCH_OPT + + if (hash == size || *hash != delta || pb[lenLimit] != cur[lenLimit] || d >= limit) + break; + + { + const ptrdiff_t diff = (ptrdiff_t)0 - (ptrdiff_t)delta; + for (;;) + { + *d++ = 2; + *d++ = (UInt32)lenLimit; + *d++ = delta - 1; + _cyclicBufferPos++; + { + CLzRef *dest = son + ((size_t)_cyclicBufferPos << 1); + const CLzRef *src = dest + ((diff + + (ptrdiff_t)(UInt32)(_cyclicBufferPos < delta ? cbs : 0)) << 1); + #if 0 + *(UInt64 *)(void *)dest = *((const UInt64 *)(const void *)src); + #else + const UInt32 p0 = src[0]; + const UInt32 p1 = src[1]; + dest[0] = p0; + dest[1] = p1; + #endif + } + hash++; + pos++; + cur++; + pb++; + if (hash == size || *hash != delta || pb[lenLimit] != cur[lenLimit] || d >= limit) + break; + } + } + #endif + + break; + } + } + } + { + const UInt32 curMatch = (UInt32)pos - delta; + if (pb[len] < cur[len]) + { + delta = pair[1]; + *ptr1 = curMatch; + ptr1 = pair + 1; + len1 = len; + } + else + { + delta = *pair; + *ptr0 = curMatch; + ptr0 = pair; + len0 = len; + } + + { + if (delta >= curMatch) + return NULL; + delta = (UInt32)pos - delta; + if (delta >= cbs + // delta >= _cyclicBufferSize || delta >= pos + || --cutValue == 0) + { + *ptr0 = *ptr1 = kEmptyHashValue; + _distances[-1] = (UInt32)(d - _distances); + break; + } + } + } + } + } // for (tree iterations) +} + pos++; + _cyclicBufferPos++; + cur++; + } + while (d < limit); + *posRes = (UInt32)pos; + return d; +} +*/ diff --git a/src/plugins/7zip/7za/c/LzHash.h b/src/plugins/7zip/7za/c/LzHash.h index e7c94230..2b6290b6 100644 --- a/src/plugins/7zip/7za/c/LzHash.h +++ b/src/plugins/7zip/7za/c/LzHash.h @@ -1,57 +1,34 @@ -/* LzHash.h -- HASH functions for LZ algorithms -2015-04-12 : Igor Pavlov : Public domain */ +/* LzHash.h -- HASH constants for LZ algorithms +2023-03-05 : Igor Pavlov : Public domain */ -#ifndef __LZ_HASH_H -#define __LZ_HASH_H +#ifndef ZIP7_INC_LZ_HASH_H +#define ZIP7_INC_LZ_HASH_H + +/* + (kHash2Size >= (1 << 8)) : Required + (kHash3Size >= (1 << 16)) : Required +*/ #define kHash2Size (1 << 10) #define kHash3Size (1 << 16) -#define kHash4Size (1 << 20) +// #define kHash4Size (1 << 20) #define kFix3HashSize (kHash2Size) #define kFix4HashSize (kHash2Size + kHash3Size) -#define kFix5HashSize (kHash2Size + kHash3Size + kHash4Size) - -#define HASH2_CALC hv = cur[0] | ((UInt32)cur[1] << 8); - -#define HASH3_CALC { \ - UInt32 temp = p->crc[cur[0]] ^ cur[1]; \ - h2 = temp & (kHash2Size - 1); \ - hv = (temp ^ ((UInt32)cur[2] << 8)) & p->hashMask; } - -#define HASH4_CALC { \ - UInt32 temp = p->crc[cur[0]] ^ cur[1]; \ - h2 = temp & (kHash2Size - 1); \ - temp ^= ((UInt32)cur[2] << 8); \ - h3 = temp & (kHash3Size - 1); \ - hv = (temp ^ (p->crc[cur[3]] << 5)) & p->hashMask; } - -#define HASH5_CALC { \ - UInt32 temp = p->crc[cur[0]] ^ cur[1]; \ - h2 = temp & (kHash2Size - 1); \ - temp ^= ((UInt32)cur[2] << 8); \ - h3 = temp & (kHash3Size - 1); \ - temp ^= (p->crc[cur[3]] << 5); \ - h4 = temp & (kHash4Size - 1); \ - hv = (temp ^ (p->crc[cur[4]] << 3)) & p->hashMask; } - -/* #define HASH_ZIP_CALC hv = ((cur[0] | ((UInt32)cur[1] << 8)) ^ p->crc[cur[2]]) & 0xFFFF; */ -#define HASH_ZIP_CALC hv = ((cur[2] | ((UInt32)cur[0] << 8)) ^ p->crc[cur[1]]) & 0xFFFF; - - -#define MT_HASH2_CALC \ - h2 = (p->crc[cur[0]] ^ cur[1]) & (kHash2Size - 1); - -#define MT_HASH3_CALC { \ - UInt32 temp = p->crc[cur[0]] ^ cur[1]; \ - h2 = temp & (kHash2Size - 1); \ - h3 = (temp ^ ((UInt32)cur[2] << 8)) & (kHash3Size - 1); } - -#define MT_HASH4_CALC { \ - UInt32 temp = p->crc[cur[0]] ^ cur[1]; \ - h2 = temp & (kHash2Size - 1); \ - temp ^= ((UInt32)cur[2] << 8); \ - h3 = temp & (kHash3Size - 1); \ - h4 = (temp ^ (p->crc[cur[3]] << 5)) & (kHash4Size - 1); } +// #define kFix5HashSize (kHash2Size + kHash3Size + kHash4Size) + +/* + We use up to 3 crc values for hash: + crc0 + crc1 << Shift_1 + crc2 << Shift_2 + (Shift_1 = 5) and (Shift_2 = 10) is good tradeoff. + Small values for Shift are not good for collision rate. + Big value for Shift_2 increases the minimum size + of hash table, that will be slow for small files. +*/ + +#define kLzHash_CrcShift_1 5 +#define kLzHash_CrcShift_2 10 #endif diff --git a/src/plugins/7zip/7za/c/Lzma2Dec.c b/src/plugins/7zip/7za/c/Lzma2Dec.c index b84f88a4..8bf54e49 100644 --- a/src/plugins/7zip/7za/c/Lzma2Dec.c +++ b/src/plugins/7zip/7za/c/Lzma2Dec.c @@ -1,5 +1,5 @@ /* Lzma2Dec.c -- LZMA2 Decoder -2015-11-09 : Igor Pavlov : Public domain */ +2024-03-01 : Igor Pavlov : Public domain */ /* #define SHOW_DEBUG_INFO */ @@ -14,28 +14,22 @@ #include "Lzma2Dec.h" /* -00000000 - EOS -00000001 U U - Uncompressed Reset Dic -00000010 U U - Uncompressed No Reset -100uuuuu U U P P - LZMA no reset -101uuuuu U U P P - LZMA reset state -110uuuuu U U P P S - LZMA reset state + new prop -111uuuuu U U P P S - LZMA reset state + new prop + reset dic +00000000 - End of data +00000001 U U - Uncompressed, reset dic, need reset state and set new prop +00000010 U U - Uncompressed, no reset +100uuuuu U U P P - LZMA, no reset +101uuuuu U U P P - LZMA, reset state +110uuuuu U U P P S - LZMA, reset state + set new prop +111uuuuu U U P P S - LZMA, reset state + set new prop, reset dic u, U - Unpack Size P - Pack Size S - Props */ -#define LZMA2_CONTROL_LZMA (1 << 7) -#define LZMA2_CONTROL_COPY_NO_RESET 2 #define LZMA2_CONTROL_COPY_RESET_DIC 1 -#define LZMA2_CONTROL_EOF 0 -#define LZMA2_IS_UNCOMPRESSED_STATE(p) (((p)->control & LZMA2_CONTROL_LZMA) == 0) - -#define LZMA2_GET_LZMA_MODE(p) (((p)->control >> 5) & 3) -#define LZMA2_IS_THERE_PROP(mode) ((mode) >= 2) +#define LZMA2_IS_UNCOMPRESSED_STATE(p) (((p)->control & (1 << 7)) == 0) #define LZMA2_LCLP_MAX 4 #define LZMA2_DIC_SIZE_FROM_PROP(p) (((UInt32)2 | ((p) & 1)) << ((p) / 2 + 11)) @@ -74,47 +68,57 @@ static SRes Lzma2Dec_GetOldProps(Byte prop, Byte *props) return SZ_OK; } -SRes Lzma2Dec_AllocateProbs(CLzma2Dec *p, Byte prop, ISzAlloc *alloc) +SRes Lzma2Dec_AllocateProbs(CLzma2Dec *p, Byte prop, ISzAllocPtr alloc) { Byte props[LZMA_PROPS_SIZE]; - RINOK(Lzma2Dec_GetOldProps(prop, props)); + RINOK(Lzma2Dec_GetOldProps(prop, props)) return LzmaDec_AllocateProbs(&p->decoder, props, LZMA_PROPS_SIZE, alloc); } -SRes Lzma2Dec_Allocate(CLzma2Dec *p, Byte prop, ISzAlloc *alloc) +SRes Lzma2Dec_Allocate(CLzma2Dec *p, Byte prop, ISzAllocPtr alloc) { Byte props[LZMA_PROPS_SIZE]; - RINOK(Lzma2Dec_GetOldProps(prop, props)); + RINOK(Lzma2Dec_GetOldProps(prop, props)) return LzmaDec_Allocate(&p->decoder, props, LZMA_PROPS_SIZE, alloc); } void Lzma2Dec_Init(CLzma2Dec *p) { p->state = LZMA2_STATE_CONTROL; - p->needInitDic = True; - p->needInitState = True; - p->needInitProp = True; + p->needInitLevel = 0xE0; + p->isExtraMode = False; + p->unpackSize = 0; + + // p->decoder.dicPos = 0; // we can use it instead of full init LzmaDec_Init(&p->decoder); } -static ELzma2State Lzma2Dec_UpdateState(CLzma2Dec *p, Byte b) +// ELzma2State +static unsigned Lzma2Dec_UpdateState(CLzma2Dec *p, Byte b) { switch (p->state) { case LZMA2_STATE_CONTROL: + p->isExtraMode = False; p->control = b; - PRF(printf("\n %4X ", (unsigned)p->decoder.dicPos)); - PRF(printf(" %2X", (unsigned)b)); - if (p->control == 0) + PRF(printf("\n %8X", (unsigned)p->decoder.dicPos)); + PRF(printf(" %02X", (unsigned)b)); + if (b == 0) return LZMA2_STATE_FINISHED; if (LZMA2_IS_UNCOMPRESSED_STATE(p)) { - if ((p->control & 0x7F) > 2) + if (b == LZMA2_CONTROL_COPY_RESET_DIC) + p->needInitLevel = 0xC0; + else if (b > 2 || p->needInitLevel == 0xE0) return LZMA2_STATE_ERROR; - p->unpackSize = 0; } else - p->unpackSize = (UInt32)(p->control & 0x1F) << 16; + { + if (b < p->needInitLevel) + return LZMA2_STATE_ERROR; + p->needInitLevel = 0; + p->unpackSize = (UInt32)(b & 0x1F) << 16; + } return LZMA2_STATE_UNPACK0; case LZMA2_STATE_UNPACK0: @@ -124,8 +128,8 @@ static ELzma2State Lzma2Dec_UpdateState(CLzma2Dec *p, Byte b) case LZMA2_STATE_UNPACK1: p->unpackSize |= (UInt32)b; p->unpackSize++; - PRF(printf(" %8u", (unsigned)p->unpackSize)); - return (LZMA2_IS_UNCOMPRESSED_STATE(p)) ? LZMA2_STATE_DATA : LZMA2_STATE_PACK0; + PRF(printf(" %7u", (unsigned)p->unpackSize)); + return LZMA2_IS_UNCOMPRESSED_STATE(p) ? LZMA2_STATE_DATA : LZMA2_STATE_PACK0; case LZMA2_STATE_PACK0: p->packSize = (UInt32)b << 8; @@ -134,9 +138,9 @@ static ELzma2State Lzma2Dec_UpdateState(CLzma2Dec *p, Byte b) case LZMA2_STATE_PACK1: p->packSize |= (UInt32)b; p->packSize++; - PRF(printf(" %8u", (unsigned)p->packSize)); - return LZMA2_IS_THERE_PROP(LZMA2_GET_LZMA_MODE(p)) ? LZMA2_STATE_PROP: - (p->needInitProp ? LZMA2_STATE_ERROR : LZMA2_STATE_DATA); + // if (p->packSize < 5) return LZMA2_STATE_ERROR; + PRF(printf(" %5u", (unsigned)p->packSize)); + return (p->control & 0x40) ? LZMA2_STATE_PROP : LZMA2_STATE_DATA; case LZMA2_STATE_PROP: { @@ -145,17 +149,18 @@ static ELzma2State Lzma2Dec_UpdateState(CLzma2Dec *p, Byte b) return LZMA2_STATE_ERROR; lc = b % 9; b /= 9; - p->decoder.prop.pb = b / 5; + p->decoder.prop.pb = (Byte)(b / 5); lp = b % 5; if (lc + lp > LZMA2_LCLP_MAX) return LZMA2_STATE_ERROR; - p->decoder.prop.lc = lc; - p->decoder.prop.lp = lp; - p->needInitProp = False; + p->decoder.prop.lc = (Byte)lc; + p->decoder.prop.lp = (Byte)lp; return LZMA2_STATE_DATA; } + + default: + return LZMA2_STATE_ERROR; } - return LZMA2_STATE_ERROR; } static void LzmaDec_UpdateWithUncompressed(CLzmaDec *p, const Byte *src, SizeT size) @@ -167,7 +172,8 @@ static void LzmaDec_UpdateWithUncompressed(CLzmaDec *p, const Byte *src, SizeT s p->processedPos += (UInt32)size; } -void LzmaDec_InitDicAndState(CLzmaDec *p, Bool initDic, Bool initState); +void LzmaDec_InitDicAndState(CLzmaDec *p, BoolInt initDic, BoolInt initState); + SRes Lzma2Dec_DecodeToDic(CLzma2Dec *p, SizeT dicLimit, const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status) @@ -176,12 +182,17 @@ SRes Lzma2Dec_DecodeToDic(CLzma2Dec *p, SizeT dicLimit, *srcLen = 0; *status = LZMA_STATUS_NOT_SPECIFIED; - while (p->state != LZMA2_STATE_FINISHED) + while (p->state != LZMA2_STATE_ERROR) { - SizeT dicPos = p->decoder.dicPos; + SizeT dicPos; + + if (p->state == LZMA2_STATE_FINISHED) + { + *status = LZMA_STATUS_FINISHED_WITH_MARK; + return SZ_OK; + } - if (p->state == LZMA2_STATE_ERROR) - return SZ_ERROR_DATA; + dicPos = p->decoder.dicPos; if (dicPos == dicLimit && finishMode == LZMA_FINISH_ANY) { @@ -198,29 +209,25 @@ SRes Lzma2Dec_DecodeToDic(CLzma2Dec *p, SizeT dicLimit, } (*srcLen)++; p->state = Lzma2Dec_UpdateState(p, *src++); - if (dicPos == dicLimit && p->state != LZMA2_STATE_FINISHED) - { - p->state = LZMA2_STATE_ERROR; - return SZ_ERROR_DATA; - } + break; continue; } { - SizeT destSizeCur = dicLimit - dicPos; - SizeT srcSizeCur = inSize - *srcLen; + SizeT inCur = inSize - *srcLen; + SizeT outCur = dicLimit - dicPos; ELzmaFinishMode curFinishMode = LZMA_FINISH_ANY; - if (p->unpackSize <= destSizeCur) + if (outCur >= p->unpackSize) { - destSizeCur = (SizeT)p->unpackSize; + outCur = (SizeT)p->unpackSize; curFinishMode = LZMA_FINISH_END; } if (LZMA2_IS_UNCOMPRESSED_STATE(p)) { - if (*srcLen == inSize) + if (inCur == 0) { *status = LZMA_STATUS_NEEDS_MORE_INPUT; return SZ_OK; @@ -228,143 +235,249 @@ SRes Lzma2Dec_DecodeToDic(CLzma2Dec *p, SizeT dicLimit, if (p->state == LZMA2_STATE_DATA) { - Bool initDic = (p->control == LZMA2_CONTROL_COPY_RESET_DIC); - if (initDic) - p->needInitProp = p->needInitState = True; - else if (p->needInitDic) - { - p->state = LZMA2_STATE_ERROR; - return SZ_ERROR_DATA; - } - p->needInitDic = False; + BoolInt initDic = (p->control == LZMA2_CONTROL_COPY_RESET_DIC); LzmaDec_InitDicAndState(&p->decoder, initDic, False); } - if (srcSizeCur > destSizeCur) - srcSizeCur = destSizeCur; - - if (srcSizeCur == 0) - { - p->state = LZMA2_STATE_ERROR; - return SZ_ERROR_DATA; - } + if (inCur > outCur) + inCur = outCur; + if (inCur == 0) + break; - LzmaDec_UpdateWithUncompressed(&p->decoder, src, srcSizeCur); + LzmaDec_UpdateWithUncompressed(&p->decoder, src, inCur); - src += srcSizeCur; - *srcLen += srcSizeCur; - p->unpackSize -= (UInt32)srcSizeCur; + src += inCur; + *srcLen += inCur; + p->unpackSize -= (UInt32)inCur; p->state = (p->unpackSize == 0) ? LZMA2_STATE_CONTROL : LZMA2_STATE_DATA_CONT; } else { - SizeT outSizeProcessed; SRes res; if (p->state == LZMA2_STATE_DATA) { - unsigned mode = LZMA2_GET_LZMA_MODE(p); - Bool initDic = (mode == 3); - Bool initState = (mode != 0); - if ((!initDic && p->needInitDic) || (!initState && p->needInitState)) - { - p->state = LZMA2_STATE_ERROR; - return SZ_ERROR_DATA; - } - + BoolInt initDic = (p->control >= 0xE0); + BoolInt initState = (p->control >= 0xA0); LzmaDec_InitDicAndState(&p->decoder, initDic, initState); - p->needInitDic = False; - p->needInitState = False; p->state = LZMA2_STATE_DATA_CONT; } - if (srcSizeCur > p->packSize) - srcSizeCur = (SizeT)p->packSize; - - res = LzmaDec_DecodeToDic(&p->decoder, dicPos + destSizeCur, src, &srcSizeCur, curFinishMode, status); + if (inCur > p->packSize) + inCur = (SizeT)p->packSize; - src += srcSizeCur; - *srcLen += srcSizeCur; - p->packSize -= (UInt32)srcSizeCur; + res = LzmaDec_DecodeToDic(&p->decoder, dicPos + outCur, src, &inCur, curFinishMode, status); - outSizeProcessed = p->decoder.dicPos - dicPos; - p->unpackSize -= (UInt32)outSizeProcessed; + src += inCur; + *srcLen += inCur; + p->packSize -= (UInt32)inCur; + outCur = p->decoder.dicPos - dicPos; + p->unpackSize -= (UInt32)outCur; - RINOK(res); + if (res != 0) + break; + if (*status == LZMA_STATUS_NEEDS_MORE_INPUT) - return res; + { + if (p->packSize == 0) + break; + return SZ_OK; + } - if (srcSizeCur == 0 && outSizeProcessed == 0) + if (inCur == 0 && outCur == 0) { if (*status != LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK || p->unpackSize != 0 || p->packSize != 0) - { - p->state = LZMA2_STATE_ERROR; - return SZ_ERROR_DATA; - } + break; p->state = LZMA2_STATE_CONTROL; } - if (*status == LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK) - *status = LZMA_STATUS_NOT_FINISHED; + *status = LZMA_STATUS_NOT_SPECIFIED; } } } - *status = LZMA_STATUS_FINISHED_WITH_MARK; - return SZ_OK; + *status = LZMA_STATUS_NOT_SPECIFIED; + p->state = LZMA2_STATE_ERROR; + return SZ_ERROR_DATA; } + + + +ELzma2ParseStatus Lzma2Dec_Parse(CLzma2Dec *p, + SizeT outSize, + const Byte *src, SizeT *srcLen, + int checkFinishBlock) +{ + SizeT inSize = *srcLen; + *srcLen = 0; + + while (p->state != LZMA2_STATE_ERROR) + { + if (p->state == LZMA2_STATE_FINISHED) + return (ELzma2ParseStatus)LZMA_STATUS_FINISHED_WITH_MARK; + + if (outSize == 0 && !checkFinishBlock) + return (ELzma2ParseStatus)LZMA_STATUS_NOT_FINISHED; + + if (p->state != LZMA2_STATE_DATA && p->state != LZMA2_STATE_DATA_CONT) + { + if (*srcLen == inSize) + return (ELzma2ParseStatus)LZMA_STATUS_NEEDS_MORE_INPUT; + (*srcLen)++; + + p->state = Lzma2Dec_UpdateState(p, *src++); + + if (p->state == LZMA2_STATE_UNPACK0) + { + // if (p->decoder.dicPos != 0) + if (p->control == LZMA2_CONTROL_COPY_RESET_DIC || p->control >= 0xE0) + return LZMA2_PARSE_STATUS_NEW_BLOCK; + // if (outSize == 0) return LZMA_STATUS_NOT_FINISHED; + } + + // The following code can be commented. + // It's not big problem, if we read additional input bytes. + // It will be stopped later in LZMA2_STATE_DATA / LZMA2_STATE_DATA_CONT state. + + if (outSize == 0 && p->state != LZMA2_STATE_FINISHED) + { + // checkFinishBlock is true. So we expect that block must be finished, + // We can return LZMA_STATUS_NOT_SPECIFIED or LZMA_STATUS_NOT_FINISHED here + // break; + return (ELzma2ParseStatus)LZMA_STATUS_NOT_FINISHED; + } + + if (p->state == LZMA2_STATE_DATA) + return LZMA2_PARSE_STATUS_NEW_CHUNK; + + continue; + } + + if (outSize == 0) + return (ELzma2ParseStatus)LZMA_STATUS_NOT_FINISHED; + + { + SizeT inCur = inSize - *srcLen; + + if (LZMA2_IS_UNCOMPRESSED_STATE(p)) + { + if (inCur == 0) + return (ELzma2ParseStatus)LZMA_STATUS_NEEDS_MORE_INPUT; + if (inCur > p->unpackSize) + inCur = p->unpackSize; + if (inCur > outSize) + inCur = outSize; + p->decoder.dicPos += inCur; + src += inCur; + *srcLen += inCur; + outSize -= inCur; + p->unpackSize -= (UInt32)inCur; + p->state = (p->unpackSize == 0) ? LZMA2_STATE_CONTROL : LZMA2_STATE_DATA_CONT; + } + else + { + p->isExtraMode = True; + + if (inCur == 0) + { + if (p->packSize != 0) + return (ELzma2ParseStatus)LZMA_STATUS_NEEDS_MORE_INPUT; + } + else if (p->state == LZMA2_STATE_DATA) + { + p->state = LZMA2_STATE_DATA_CONT; + if (*src != 0) + { + // first byte of lzma chunk must be Zero + *srcLen += 1; + p->packSize--; + break; + } + } + + if (inCur > p->packSize) + inCur = (SizeT)p->packSize; + + src += inCur; + *srcLen += inCur; + p->packSize -= (UInt32)inCur; + + if (p->packSize == 0) + { + SizeT rem = outSize; + if (rem > p->unpackSize) + rem = p->unpackSize; + p->decoder.dicPos += rem; + p->unpackSize -= (UInt32)rem; + outSize -= rem; + if (p->unpackSize == 0) + p->state = LZMA2_STATE_CONTROL; + } + } + } + } + + p->state = LZMA2_STATE_ERROR; + return (ELzma2ParseStatus)LZMA_STATUS_NOT_SPECIFIED; +} + + + + SRes Lzma2Dec_DecodeToBuf(CLzma2Dec *p, Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status) { SizeT outSize = *destLen, inSize = *srcLen; *srcLen = *destLen = 0; + for (;;) { - SizeT srcSizeCur = inSize, outSizeCur, dicPos; + SizeT inCur = inSize, outCur, dicPos; ELzmaFinishMode curFinishMode; SRes res; + if (p->decoder.dicPos == p->decoder.dicBufSize) p->decoder.dicPos = 0; dicPos = p->decoder.dicPos; - if (outSize > p->decoder.dicBufSize - dicPos) - { - outSizeCur = p->decoder.dicBufSize; - curFinishMode = LZMA_FINISH_ANY; - } - else + curFinishMode = LZMA_FINISH_ANY; + outCur = p->decoder.dicBufSize - dicPos; + + if (outCur >= outSize) { - outSizeCur = dicPos + outSize; + outCur = outSize; curFinishMode = finishMode; } - res = Lzma2Dec_DecodeToDic(p, outSizeCur, src, &srcSizeCur, curFinishMode, status); - src += srcSizeCur; - inSize -= srcSizeCur; - *srcLen += srcSizeCur; - outSizeCur = p->decoder.dicPos - dicPos; - memcpy(dest, p->decoder.dic + dicPos, outSizeCur); - dest += outSizeCur; - outSize -= outSizeCur; - *destLen += outSizeCur; + res = Lzma2Dec_DecodeToDic(p, dicPos + outCur, src, &inCur, curFinishMode, status); + + src += inCur; + inSize -= inCur; + *srcLen += inCur; + outCur = p->decoder.dicPos - dicPos; + memcpy(dest, p->decoder.dic + dicPos, outCur); + dest += outCur; + outSize -= outCur; + *destLen += outCur; if (res != 0) return res; - if (outSizeCur == 0 || outSize == 0) + if (outCur == 0 || outSize == 0) return SZ_OK; } } + SRes Lzma2Decode(Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen, - Byte prop, ELzmaFinishMode finishMode, ELzmaStatus *status, ISzAlloc *alloc) + Byte prop, ELzmaFinishMode finishMode, ELzmaStatus *status, ISzAllocPtr alloc) { CLzma2Dec p; SRes res; SizeT outSize = *destLen, inSize = *srcLen; *destLen = *srcLen = 0; *status = LZMA_STATUS_NOT_SPECIFIED; - Lzma2Dec_Construct(&p); - RINOK(Lzma2Dec_AllocateProbs(&p, prop, alloc)); + Lzma2Dec_CONSTRUCT(&p) + RINOK(Lzma2Dec_AllocateProbs(&p, prop, alloc)) p.decoder.dic = dest; p.decoder.dicBufSize = outSize; Lzma2Dec_Init(&p); @@ -376,3 +489,5 @@ SRes Lzma2Decode(Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen, Lzma2Dec_FreeProbs(&p, alloc); return res; } + +#undef PRF diff --git a/src/plugins/7zip/7za/c/Lzma2Dec.h b/src/plugins/7zip/7za/c/Lzma2Dec.h index e6a0f6ed..1f5233a7 100644 --- a/src/plugins/7zip/7za/c/Lzma2Dec.h +++ b/src/plugins/7zip/7za/c/Lzma2Dec.h @@ -1,8 +1,8 @@ /* Lzma2Dec.h -- LZMA2 Decoder -2015-05-13 : Igor Pavlov : Public domain */ +2023-03-03 : Igor Pavlov : Public domain */ -#ifndef __LZMA2_DEC_H -#define __LZMA2_DEC_H +#ifndef ZIP7_INC_LZMA2_DEC_H +#define ZIP7_INC_LZMA2_DEC_H #include "LzmaDec.h" @@ -12,25 +12,25 @@ EXTERN_C_BEGIN typedef struct { - CLzmaDec decoder; - UInt32 packSize; - UInt32 unpackSize; unsigned state; Byte control; - Bool needInitDic; - Bool needInitState; - Bool needInitProp; + Byte needInitLevel; + Byte isExtraMode; + Byte _pad_; + UInt32 packSize; + UInt32 unpackSize; + CLzmaDec decoder; } CLzma2Dec; -#define Lzma2Dec_Construct(p) LzmaDec_Construct(&(p)->decoder) -#define Lzma2Dec_FreeProbs(p, alloc) LzmaDec_FreeProbs(&(p)->decoder, alloc); -#define Lzma2Dec_Free(p, alloc) LzmaDec_Free(&(p)->decoder, alloc); +#define Lzma2Dec_CONSTRUCT(p) LzmaDec_CONSTRUCT(&(p)->decoder) +#define Lzma2Dec_Construct(p) Lzma2Dec_CONSTRUCT(p) +#define Lzma2Dec_FreeProbs(p, alloc) LzmaDec_FreeProbs(&(p)->decoder, alloc) +#define Lzma2Dec_Free(p, alloc) LzmaDec_Free(&(p)->decoder, alloc) -SRes Lzma2Dec_AllocateProbs(CLzma2Dec *p, Byte prop, ISzAlloc *alloc); -SRes Lzma2Dec_Allocate(CLzma2Dec *p, Byte prop, ISzAlloc *alloc); +SRes Lzma2Dec_AllocateProbs(CLzma2Dec *p, Byte prop, ISzAllocPtr alloc); +SRes Lzma2Dec_Allocate(CLzma2Dec *p, Byte prop, ISzAllocPtr alloc); void Lzma2Dec_Init(CLzma2Dec *p); - /* finishMode: It has meaning only if the decoding reaches output limit (*destLen or dicLimit). @@ -53,6 +53,47 @@ SRes Lzma2Dec_DecodeToBuf(CLzma2Dec *p, Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status); +/* ---------- LZMA2 block and chunk parsing ---------- */ + +/* +Lzma2Dec_Parse() parses compressed data stream up to next independent block or next chunk data. +It can return LZMA_STATUS_* code or LZMA2_PARSE_STATUS_* code: + - LZMA2_PARSE_STATUS_NEW_BLOCK - there is new block, and 1 additional byte (control byte of next block header) was read from input. + - LZMA2_PARSE_STATUS_NEW_CHUNK - there is new chunk, and only lzma2 header of new chunk was read. + CLzma2Dec::unpackSize contains unpack size of that chunk +*/ + +typedef enum +{ +/* + LZMA_STATUS_NOT_SPECIFIED // data error + LZMA_STATUS_FINISHED_WITH_MARK + LZMA_STATUS_NOT_FINISHED // + LZMA_STATUS_NEEDS_MORE_INPUT + LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK // unused +*/ + LZMA2_PARSE_STATUS_NEW_BLOCK = LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK + 1, + LZMA2_PARSE_STATUS_NEW_CHUNK +} ELzma2ParseStatus; + +ELzma2ParseStatus Lzma2Dec_Parse(CLzma2Dec *p, + SizeT outSize, // output size + const Byte *src, SizeT *srcLen, + int checkFinishBlock // set (checkFinishBlock = 1), if it must read full input data, if decoder.dicPos reaches blockMax position. + ); + +/* +LZMA2 parser doesn't decode LZMA chunks, so we must read + full input LZMA chunk to decode some part of LZMA chunk. + +Lzma2Dec_GetUnpackExtra() returns the value that shows + max possible number of output bytes that can be output by decoder + at current input positon. +*/ + +#define Lzma2Dec_GetUnpackExtra(p) ((p)->isExtraMode ? (p)->unpackSize : 0) + + /* ---------- One Call Interface ---------- */ /* @@ -73,7 +114,7 @@ SRes Lzma2Dec_DecodeToBuf(CLzma2Dec *p, Byte *dest, SizeT *destLen, */ SRes Lzma2Decode(Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen, - Byte prop, ELzmaFinishMode finishMode, ELzmaStatus *status, ISzAlloc *alloc); + Byte prop, ELzmaFinishMode finishMode, ELzmaStatus *status, ISzAllocPtr alloc); EXTERN_C_END diff --git a/src/plugins/7zip/7za/c/Lzma2DecMt.c b/src/plugins/7zip/7za/c/Lzma2DecMt.c new file mode 100644 index 00000000..4bc4ddeb --- /dev/null +++ b/src/plugins/7zip/7za/c/Lzma2DecMt.c @@ -0,0 +1,1095 @@ +/* Lzma2DecMt.c -- LZMA2 Decoder Multi-thread +2023-04-13 : Igor Pavlov : Public domain */ + +#include "Precomp.h" + +// #define SHOW_DEBUG_INFO +// #define Z7_ST + +#ifdef SHOW_DEBUG_INFO +#include +#endif + +#include "Alloc.h" + +#include "Lzma2Dec.h" +#include "Lzma2DecMt.h" + +#ifndef Z7_ST +#include "MtDec.h" + +#define LZMA2DECMT_OUT_BLOCK_MAX_DEFAULT (1 << 28) +#endif + + +#ifndef Z7_ST +#ifdef SHOW_DEBUG_INFO +#define PRF(x) x +#else +#define PRF(x) +#endif +#define PRF_STR(s) PRF(printf("\n" s "\n");) +#define PRF_STR_INT_2(s, d1, d2) PRF(printf("\n" s " %d %d\n", (unsigned)d1, (unsigned)d2);) +#endif + + +void Lzma2DecMtProps_Init(CLzma2DecMtProps *p) +{ + p->inBufSize_ST = 1 << 20; + p->outStep_ST = 1 << 20; + + #ifndef Z7_ST + p->numThreads = 1; + p->inBufSize_MT = 1 << 18; + p->outBlockMax = LZMA2DECMT_OUT_BLOCK_MAX_DEFAULT; + p->inBlockMax = p->outBlockMax + p->outBlockMax / 16; + #endif +} + + + +#ifndef Z7_ST + +/* ---------- CLzma2DecMtThread ---------- */ + +typedef struct +{ + CLzma2Dec dec; + Byte dec_created; + Byte needInit; + + Byte *outBuf; + size_t outBufSize; + + EMtDecParseState state; + ELzma2ParseStatus parseStatus; + + size_t inPreSize; + size_t outPreSize; + + size_t inCodeSize; + size_t outCodeSize; + SRes codeRes; + + CAlignOffsetAlloc alloc; + + Byte mtPad[1 << 7]; +} CLzma2DecMtThread; + +#endif + + +/* ---------- CLzma2DecMt ---------- */ + +struct CLzma2DecMt +{ + // ISzAllocPtr alloc; + ISzAllocPtr allocMid; + + CAlignOffsetAlloc alignOffsetAlloc; + CLzma2DecMtProps props; + Byte prop; + + ISeqInStreamPtr inStream; + ISeqOutStreamPtr outStream; + ICompressProgressPtr progress; + + BoolInt finishMode; + BoolInt outSize_Defined; + UInt64 outSize; + + UInt64 outProcessed; + UInt64 inProcessed; + BoolInt readWasFinished; + SRes readRes; + + Byte *inBuf; + size_t inBufSize; + Byte dec_created; + CLzma2Dec dec; + + size_t inPos; + size_t inLim; + + #ifndef Z7_ST + UInt64 outProcessed_Parse; + BoolInt mtc_WasConstructed; + CMtDec mtc; + CLzma2DecMtThread coders[MTDEC_THREADS_MAX]; + #endif +}; + + + +CLzma2DecMtHandle Lzma2DecMt_Create(ISzAllocPtr alloc, ISzAllocPtr allocMid) +{ + CLzma2DecMt *p = (CLzma2DecMt *)ISzAlloc_Alloc(alloc, sizeof(CLzma2DecMt)); + if (!p) + return NULL; + + // p->alloc = alloc; + p->allocMid = allocMid; + + AlignOffsetAlloc_CreateVTable(&p->alignOffsetAlloc); + p->alignOffsetAlloc.numAlignBits = 7; + p->alignOffsetAlloc.offset = 0; + p->alignOffsetAlloc.baseAlloc = alloc; + + p->inBuf = NULL; + p->inBufSize = 0; + p->dec_created = False; + + // Lzma2DecMtProps_Init(&p->props); + + #ifndef Z7_ST + p->mtc_WasConstructed = False; + { + unsigned i; + for (i = 0; i < MTDEC_THREADS_MAX; i++) + { + CLzma2DecMtThread *t = &p->coders[i]; + t->dec_created = False; + t->outBuf = NULL; + t->outBufSize = 0; + } + } + #endif + + return (CLzma2DecMtHandle)(void *)p; +} + + +#ifndef Z7_ST + +static void Lzma2DecMt_FreeOutBufs(CLzma2DecMt *p) +{ + unsigned i; + for (i = 0; i < MTDEC_THREADS_MAX; i++) + { + CLzma2DecMtThread *t = &p->coders[i]; + if (t->outBuf) + { + ISzAlloc_Free(p->allocMid, t->outBuf); + t->outBuf = NULL; + t->outBufSize = 0; + } + } +} + +#endif + + +static void Lzma2DecMt_FreeSt(CLzma2DecMt *p) +{ + if (p->dec_created) + { + Lzma2Dec_Free(&p->dec, &p->alignOffsetAlloc.vt); + p->dec_created = False; + } + if (p->inBuf) + { + ISzAlloc_Free(p->allocMid, p->inBuf); + p->inBuf = NULL; + } + p->inBufSize = 0; +} + + +// #define GET_CLzma2DecMt_p CLzma2DecMt *p = (CLzma2DecMt *)(void *)pp; + +void Lzma2DecMt_Destroy(CLzma2DecMtHandle p) +{ + // GET_CLzma2DecMt_p + + Lzma2DecMt_FreeSt(p); + + #ifndef Z7_ST + + if (p->mtc_WasConstructed) + { + MtDec_Destruct(&p->mtc); + p->mtc_WasConstructed = False; + } + { + unsigned i; + for (i = 0; i < MTDEC_THREADS_MAX; i++) + { + CLzma2DecMtThread *t = &p->coders[i]; + if (t->dec_created) + { + // we don't need to free dict here + Lzma2Dec_FreeProbs(&t->dec, &t->alloc.vt); // p->alloc !!! + t->dec_created = False; + } + } + } + Lzma2DecMt_FreeOutBufs(p); + + #endif + + ISzAlloc_Free(p->alignOffsetAlloc.baseAlloc, p); +} + + + +#ifndef Z7_ST + +static void Lzma2DecMt_MtCallback_Parse(void *obj, unsigned coderIndex, CMtDecCallbackInfo *cc) +{ + CLzma2DecMt *me = (CLzma2DecMt *)obj; + CLzma2DecMtThread *t = &me->coders[coderIndex]; + + PRF_STR_INT_2("Parse", coderIndex, cc->srcSize) + + cc->state = MTDEC_PARSE_CONTINUE; + + if (cc->startCall) + { + if (!t->dec_created) + { + Lzma2Dec_CONSTRUCT(&t->dec) + t->dec_created = True; + AlignOffsetAlloc_CreateVTable(&t->alloc); + { + /* (1 << 12) is expected size of one way in data cache. + We optimize alignment for cache line size of 128 bytes and smaller */ + const unsigned kNumAlignBits = 12; + const unsigned kNumCacheLineBits = 7; /* <= kNumAlignBits */ + t->alloc.numAlignBits = kNumAlignBits; + t->alloc.offset = ((UInt32)coderIndex * (((unsigned)1 << 11) + (1 << 8) + (1 << 6))) & (((unsigned)1 << kNumAlignBits) - ((unsigned)1 << kNumCacheLineBits)); + t->alloc.baseAlloc = me->alignOffsetAlloc.baseAlloc; + } + } + Lzma2Dec_Init(&t->dec); + + t->inPreSize = 0; + t->outPreSize = 0; + // t->blockWasFinished = False; + // t->finishedWithMark = False; + t->parseStatus = (ELzma2ParseStatus)LZMA_STATUS_NOT_SPECIFIED; + t->state = MTDEC_PARSE_CONTINUE; + + t->inCodeSize = 0; + t->outCodeSize = 0; + t->codeRes = SZ_OK; + + // (cc->srcSize == 0) is allowed + } + + { + ELzma2ParseStatus status; + BoolInt overflow; + UInt32 unpackRem = 0; + + int checkFinishBlock = True; + size_t limit = me->props.outBlockMax; + if (me->outSize_Defined) + { + UInt64 rem = me->outSize - me->outProcessed_Parse; + if (limit >= rem) + { + limit = (size_t)rem; + if (!me->finishMode) + checkFinishBlock = False; + } + } + + // checkFinishBlock = False, if we want to decode partial data + // that must be finished at position <= outBlockMax. + + { + const size_t srcOrig = cc->srcSize; + SizeT srcSize_Point = 0; + SizeT dicPos_Point = 0; + + cc->srcSize = 0; + overflow = False; + + for (;;) + { + SizeT srcCur = (SizeT)(srcOrig - cc->srcSize); + + status = Lzma2Dec_Parse(&t->dec, + (SizeT)limit - t->dec.decoder.dicPos, + cc->src + cc->srcSize, &srcCur, + checkFinishBlock); + + cc->srcSize += srcCur; + + if (status == LZMA2_PARSE_STATUS_NEW_CHUNK) + { + if (t->dec.unpackSize > me->props.outBlockMax - t->dec.decoder.dicPos) + { + overflow = True; + break; + } + continue; + } + + if (status == LZMA2_PARSE_STATUS_NEW_BLOCK) + { + if (t->dec.decoder.dicPos == 0) + continue; + // we decode small blocks in one thread + if (t->dec.decoder.dicPos >= (1 << 14)) + break; + dicPos_Point = t->dec.decoder.dicPos; + srcSize_Point = (SizeT)cc->srcSize; + continue; + } + + if ((int)status == LZMA_STATUS_NOT_FINISHED && checkFinishBlock + // && limit == t->dec.decoder.dicPos + // && limit == me->props.outBlockMax + ) + { + overflow = True; + break; + } + + unpackRem = Lzma2Dec_GetUnpackExtra(&t->dec); + break; + } + + if (dicPos_Point != 0 + && (int)status != LZMA2_PARSE_STATUS_NEW_BLOCK + && (int)status != LZMA_STATUS_FINISHED_WITH_MARK + && (int)status != LZMA_STATUS_NOT_SPECIFIED) + { + // we revert to latest newBlock state + status = LZMA2_PARSE_STATUS_NEW_BLOCK; + unpackRem = 0; + t->dec.decoder.dicPos = dicPos_Point; + cc->srcSize = srcSize_Point; + overflow = False; + } + } + + t->inPreSize += cc->srcSize; + t->parseStatus = status; + + if (overflow) + cc->state = MTDEC_PARSE_OVERFLOW; + else + { + size_t dicPos = t->dec.decoder.dicPos; + + if ((int)status != LZMA_STATUS_NEEDS_MORE_INPUT) + { + if (status == LZMA2_PARSE_STATUS_NEW_BLOCK) + { + cc->state = MTDEC_PARSE_NEW; + cc->srcSize--; // we don't need control byte of next block + t->inPreSize--; + } + else + { + cc->state = MTDEC_PARSE_END; + if ((int)status != LZMA_STATUS_FINISHED_WITH_MARK) + { + // (status == LZMA_STATUS_NOT_SPECIFIED) + // (status == LZMA_STATUS_NOT_FINISHED) + if (unpackRem != 0) + { + /* we also reserve space for max possible number of output bytes of current LZMA chunk */ + size_t rem = limit - dicPos; + if (rem > unpackRem) + rem = unpackRem; + dicPos += rem; + } + } + } + + me->outProcessed_Parse += dicPos; + } + + cc->outPos = dicPos; + t->outPreSize = (size_t)dicPos; + } + + t->state = cc->state; + return; + } +} + + +static SRes Lzma2DecMt_MtCallback_PreCode(void *pp, unsigned coderIndex) +{ + CLzma2DecMt *me = (CLzma2DecMt *)pp; + CLzma2DecMtThread *t = &me->coders[coderIndex]; + Byte *dest = t->outBuf; + + if (t->inPreSize == 0) + { + t->codeRes = SZ_ERROR_DATA; + return t->codeRes; + } + + if (!dest || t->outBufSize < t->outPreSize) + { + if (dest) + { + ISzAlloc_Free(me->allocMid, dest); + t->outBuf = NULL; + t->outBufSize = 0; + } + + dest = (Byte *)ISzAlloc_Alloc(me->allocMid, t->outPreSize + // + (1 << 28) + ); + // Sleep(200); + if (!dest) + return SZ_ERROR_MEM; + t->outBuf = dest; + t->outBufSize = t->outPreSize; + } + + t->dec.decoder.dic = dest; + t->dec.decoder.dicBufSize = (SizeT)t->outPreSize; + + t->needInit = True; + + return Lzma2Dec_AllocateProbs(&t->dec, me->prop, &t->alloc.vt); // alloc.vt +} + + +static SRes Lzma2DecMt_MtCallback_Code(void *pp, unsigned coderIndex, + const Byte *src, size_t srcSize, int srcFinished, + // int finished, int blockFinished, + UInt64 *inCodePos, UInt64 *outCodePos, int *stop) +{ + CLzma2DecMt *me = (CLzma2DecMt *)pp; + CLzma2DecMtThread *t = &me->coders[coderIndex]; + + UNUSED_VAR(srcFinished) + + PRF_STR_INT_2("Code", coderIndex, srcSize) + + *inCodePos = t->inCodeSize; + *outCodePos = 0; + *stop = True; + + if (t->needInit) + { + Lzma2Dec_Init(&t->dec); + t->needInit = False; + } + + { + ELzmaStatus status; + SizeT srcProcessed = (SizeT)srcSize; + BoolInt blockWasFinished = + ((int)t->parseStatus == LZMA_STATUS_FINISHED_WITH_MARK + || t->parseStatus == LZMA2_PARSE_STATUS_NEW_BLOCK); + + SRes res = Lzma2Dec_DecodeToDic(&t->dec, + (SizeT)t->outPreSize, + src, &srcProcessed, + blockWasFinished ? LZMA_FINISH_END : LZMA_FINISH_ANY, + &status); + + t->codeRes = res; + + t->inCodeSize += srcProcessed; + *inCodePos = t->inCodeSize; + t->outCodeSize = t->dec.decoder.dicPos; + *outCodePos = t->dec.decoder.dicPos; + + if (res != SZ_OK) + return res; + + if (srcProcessed == srcSize) + *stop = False; + + if (blockWasFinished) + { + if (srcSize != srcProcessed) + return SZ_ERROR_FAIL; + + if (t->inPreSize == t->inCodeSize) + { + if (t->outPreSize != t->outCodeSize) + return SZ_ERROR_FAIL; + *stop = True; + } + } + else + { + if (t->outPreSize == t->outCodeSize) + *stop = True; + } + + return SZ_OK; + } +} + + +#define LZMA2DECMT_STREAM_WRITE_STEP (1 << 24) + +static SRes Lzma2DecMt_MtCallback_Write(void *pp, unsigned coderIndex, + BoolInt needWriteToStream, + const Byte *src, size_t srcSize, BoolInt isCross, + BoolInt *needContinue, BoolInt *canRecode) +{ + CLzma2DecMt *me = (CLzma2DecMt *)pp; + const CLzma2DecMtThread *t = &me->coders[coderIndex]; + size_t size = t->outCodeSize; + const Byte *data = t->outBuf; + BoolInt needContinue2 = True; + + UNUSED_VAR(src) + UNUSED_VAR(srcSize) + UNUSED_VAR(isCross) + + PRF_STR_INT_2("Write", coderIndex, srcSize) + + *needContinue = False; + *canRecode = True; + + if ( + // t->parseStatus == LZMA_STATUS_FINISHED_WITH_MARK + t->state == MTDEC_PARSE_OVERFLOW + || t->state == MTDEC_PARSE_END) + needContinue2 = False; + + + if (!needWriteToStream) + return SZ_OK; + + me->mtc.inProcessed += t->inCodeSize; + + if (t->codeRes == SZ_OK) + if ((int)t->parseStatus == LZMA_STATUS_FINISHED_WITH_MARK + || t->parseStatus == LZMA2_PARSE_STATUS_NEW_BLOCK) + if (t->outPreSize != t->outCodeSize + || t->inPreSize != t->inCodeSize) + return SZ_ERROR_FAIL; + + *canRecode = False; + + if (me->outStream) + { + for (;;) + { + size_t cur = size; + size_t written; + if (cur > LZMA2DECMT_STREAM_WRITE_STEP) + cur = LZMA2DECMT_STREAM_WRITE_STEP; + + written = ISeqOutStream_Write(me->outStream, data, cur); + + me->outProcessed += written; + // me->mtc.writtenTotal += written; + if (written != cur) + return SZ_ERROR_WRITE; + data += cur; + size -= cur; + if (size == 0) + { + *needContinue = needContinue2; + return SZ_OK; + } + RINOK(MtProgress_ProgressAdd(&me->mtc.mtProgress, 0, 0)) + } + } + + return SZ_ERROR_FAIL; + /* + if (size > me->outBufSize) + return SZ_ERROR_OUTPUT_EOF; + memcpy(me->outBuf, data, size); + me->outBufSize -= size; + me->outBuf += size; + *needContinue = needContinue2; + return SZ_OK; + */ +} + +#endif + + +static SRes Lzma2Dec_Prepare_ST(CLzma2DecMt *p) +{ + if (!p->dec_created) + { + Lzma2Dec_CONSTRUCT(&p->dec) + p->dec_created = True; + } + + RINOK(Lzma2Dec_Allocate(&p->dec, p->prop, &p->alignOffsetAlloc.vt)) + + if (!p->inBuf || p->inBufSize != p->props.inBufSize_ST) + { + ISzAlloc_Free(p->allocMid, p->inBuf); + p->inBufSize = 0; + p->inBuf = (Byte *)ISzAlloc_Alloc(p->allocMid, p->props.inBufSize_ST); + if (!p->inBuf) + return SZ_ERROR_MEM; + p->inBufSize = p->props.inBufSize_ST; + } + + Lzma2Dec_Init(&p->dec); + + return SZ_OK; +} + + +static SRes Lzma2Dec_Decode_ST(CLzma2DecMt *p + #ifndef Z7_ST + , BoolInt tMode + #endif + ) +{ + SizeT wrPos; + size_t inPos, inLim; + const Byte *inData; + UInt64 inPrev, outPrev; + + CLzma2Dec *dec; + + #ifndef Z7_ST + if (tMode) + { + Lzma2DecMt_FreeOutBufs(p); + tMode = MtDec_PrepareRead(&p->mtc); + } + #endif + + RINOK(Lzma2Dec_Prepare_ST(p)) + + dec = &p->dec; + + inPrev = p->inProcessed; + outPrev = p->outProcessed; + + inPos = 0; + inLim = 0; + inData = NULL; + wrPos = dec->decoder.dicPos; + + for (;;) + { + SizeT dicPos; + SizeT size; + ELzmaFinishMode finishMode; + SizeT inProcessed; + ELzmaStatus status; + SRes res; + + SizeT outProcessed; + BoolInt outFinished; + BoolInt needStop; + + if (inPos == inLim) + { + #ifndef Z7_ST + if (tMode) + { + inData = MtDec_Read(&p->mtc, &inLim); + inPos = 0; + if (inData) + continue; + tMode = False; + inLim = 0; + } + #endif + + if (!p->readWasFinished) + { + inPos = 0; + inLim = p->inBufSize; + inData = p->inBuf; + p->readRes = ISeqInStream_Read(p->inStream, (void *)(p->inBuf), &inLim); + // p->readProcessed += inLim; + // inLim -= 5; p->readWasFinished = True; // for test + if (inLim == 0 || p->readRes != SZ_OK) + p->readWasFinished = True; + } + } + + dicPos = dec->decoder.dicPos; + { + SizeT next = dec->decoder.dicBufSize; + if (next - wrPos > p->props.outStep_ST) + next = wrPos + (SizeT)p->props.outStep_ST; + size = next - dicPos; + } + + finishMode = LZMA_FINISH_ANY; + if (p->outSize_Defined) + { + const UInt64 rem = p->outSize - p->outProcessed; + if (size >= rem) + { + size = (SizeT)rem; + if (p->finishMode) + finishMode = LZMA_FINISH_END; + } + } + + inProcessed = (SizeT)(inLim - inPos); + + res = Lzma2Dec_DecodeToDic(dec, dicPos + size, inData + inPos, &inProcessed, finishMode, &status); + + inPos += inProcessed; + p->inProcessed += inProcessed; + outProcessed = dec->decoder.dicPos - dicPos; + p->outProcessed += outProcessed; + + outFinished = (p->outSize_Defined && p->outSize <= p->outProcessed); + + needStop = (res != SZ_OK + || (inProcessed == 0 && outProcessed == 0) + || status == LZMA_STATUS_FINISHED_WITH_MARK + || (!p->finishMode && outFinished)); + + if (needStop || outProcessed >= size) + { + SRes res2; + { + size_t writeSize = dec->decoder.dicPos - wrPos; + size_t written = ISeqOutStream_Write(p->outStream, dec->decoder.dic + wrPos, writeSize); + res2 = (written == writeSize) ? SZ_OK : SZ_ERROR_WRITE; + } + + if (dec->decoder.dicPos == dec->decoder.dicBufSize) + dec->decoder.dicPos = 0; + wrPos = dec->decoder.dicPos; + + RINOK(res2) + + if (needStop) + { + if (res != SZ_OK) + return res; + + if (status == LZMA_STATUS_FINISHED_WITH_MARK) + { + if (p->finishMode) + { + if (p->outSize_Defined && p->outSize != p->outProcessed) + return SZ_ERROR_DATA; + } + return SZ_OK; + } + + if (!p->finishMode && outFinished) + return SZ_OK; + + if (status == LZMA_STATUS_NEEDS_MORE_INPUT) + return SZ_ERROR_INPUT_EOF; + + return SZ_ERROR_DATA; + } + } + + if (p->progress) + { + UInt64 inDelta = p->inProcessed - inPrev; + UInt64 outDelta = p->outProcessed - outPrev; + if (inDelta >= (1 << 22) || outDelta >= (1 << 22)) + { + RINOK(ICompressProgress_Progress(p->progress, p->inProcessed, p->outProcessed)) + inPrev = p->inProcessed; + outPrev = p->outProcessed; + } + } + } +} + + + +SRes Lzma2DecMt_Decode(CLzma2DecMtHandle p, + Byte prop, + const CLzma2DecMtProps *props, + ISeqOutStreamPtr outStream, const UInt64 *outDataSize, int finishMode, + // Byte *outBuf, size_t *outBufSize, + ISeqInStreamPtr inStream, + // const Byte *inData, size_t inDataSize, + UInt64 *inProcessed, + // UInt64 *outProcessed, + int *isMT, + ICompressProgressPtr progress) +{ + // GET_CLzma2DecMt_p + #ifndef Z7_ST + BoolInt tMode; + #endif + + *inProcessed = 0; + + if (prop > 40) + return SZ_ERROR_UNSUPPORTED; + + p->prop = prop; + p->props = *props; + + p->inStream = inStream; + p->outStream = outStream; + p->progress = progress; + + p->outSize = 0; + p->outSize_Defined = False; + if (outDataSize) + { + p->outSize_Defined = True; + p->outSize = *outDataSize; + } + p->finishMode = finishMode; + + p->outProcessed = 0; + p->inProcessed = 0; + + p->readWasFinished = False; + p->readRes = SZ_OK; + + *isMT = False; + + + #ifndef Z7_ST + + tMode = False; + + // p->mtc.parseRes = SZ_OK; + + // p->mtc.numFilledThreads = 0; + // p->mtc.crossStart = 0; + // p->mtc.crossEnd = 0; + // p->mtc.allocError_for_Read_BlockIndex = 0; + // p->mtc.isAllocError = False; + + if (p->props.numThreads > 1) + { + IMtDecCallback2 vt; + + Lzma2DecMt_FreeSt(p); + + p->outProcessed_Parse = 0; + + if (!p->mtc_WasConstructed) + { + p->mtc_WasConstructed = True; + MtDec_Construct(&p->mtc); + } + + p->mtc.progress = progress; + p->mtc.inStream = inStream; + + // p->outBuf = NULL; + // p->outBufSize = 0; + /* + if (!outStream) + { + // p->outBuf = outBuf; + // p->outBufSize = *outBufSize; + // *outBufSize = 0; + return SZ_ERROR_PARAM; + } + */ + + // p->mtc.inBlockMax = p->props.inBlockMax; + p->mtc.alloc = &p->alignOffsetAlloc.vt; + // p->alignOffsetAlloc.baseAlloc; + // p->mtc.inData = inData; + // p->mtc.inDataSize = inDataSize; + p->mtc.mtCallback = &vt; + p->mtc.mtCallbackObject = p; + + p->mtc.inBufSize = p->props.inBufSize_MT; + + p->mtc.numThreadsMax = p->props.numThreads; + + *isMT = True; + + vt.Parse = Lzma2DecMt_MtCallback_Parse; + vt.PreCode = Lzma2DecMt_MtCallback_PreCode; + vt.Code = Lzma2DecMt_MtCallback_Code; + vt.Write = Lzma2DecMt_MtCallback_Write; + + { + BoolInt needContinue = False; + + SRes res = MtDec_Code(&p->mtc); + + /* + if (!outStream) + *outBufSize = p->outBuf - outBuf; + */ + + *inProcessed = p->mtc.inProcessed; + + needContinue = False; + + if (res == SZ_OK) + { + if (p->mtc.mtProgress.res != SZ_OK) + res = p->mtc.mtProgress.res; + else + needContinue = p->mtc.needContinue; + } + + if (!needContinue) + { + if (res == SZ_OK) + return p->mtc.readRes; + return res; + } + + tMode = True; + p->readRes = p->mtc.readRes; + p->readWasFinished = p->mtc.readWasFinished; + p->inProcessed = p->mtc.inProcessed; + + PRF_STR("----- decoding ST -----") + } + } + + #endif + + + *isMT = False; + + { + SRes res = Lzma2Dec_Decode_ST(p + #ifndef Z7_ST + , tMode + #endif + ); + + *inProcessed = p->inProcessed; + + // res = SZ_OK; // for test + if (res == SZ_ERROR_INPUT_EOF) + { + if (p->readRes != SZ_OK) + res = p->readRes; + } + else if (res == SZ_OK && p->readRes != SZ_OK) + res = p->readRes; + + /* + #ifndef Z7_ST + if (res == SZ_OK && tMode && p->mtc.parseRes != SZ_OK) + res = p->mtc.parseRes; + #endif + */ + + return res; + } +} + + +/* ---------- Read from CLzma2DecMtHandle Interface ---------- */ + +SRes Lzma2DecMt_Init(CLzma2DecMtHandle p, + Byte prop, + const CLzma2DecMtProps *props, + const UInt64 *outDataSize, int finishMode, + ISeqInStreamPtr inStream) +{ + // GET_CLzma2DecMt_p + + if (prop > 40) + return SZ_ERROR_UNSUPPORTED; + + p->prop = prop; + p->props = *props; + + p->inStream = inStream; + + p->outSize = 0; + p->outSize_Defined = False; + if (outDataSize) + { + p->outSize_Defined = True; + p->outSize = *outDataSize; + } + p->finishMode = finishMode; + + p->outProcessed = 0; + p->inProcessed = 0; + + p->inPos = 0; + p->inLim = 0; + + return Lzma2Dec_Prepare_ST(p); +} + + +SRes Lzma2DecMt_Read(CLzma2DecMtHandle p, + Byte *data, size_t *outSize, + UInt64 *inStreamProcessed) +{ + // GET_CLzma2DecMt_p + ELzmaFinishMode finishMode; + SRes readRes; + size_t size = *outSize; + + *outSize = 0; + *inStreamProcessed = 0; + + finishMode = LZMA_FINISH_ANY; + if (p->outSize_Defined) + { + const UInt64 rem = p->outSize - p->outProcessed; + if (size >= rem) + { + size = (size_t)rem; + if (p->finishMode) + finishMode = LZMA_FINISH_END; + } + } + + readRes = SZ_OK; + + for (;;) + { + SizeT inCur; + SizeT outCur; + ELzmaStatus status; + SRes res; + + if (p->inPos == p->inLim && readRes == SZ_OK) + { + p->inPos = 0; + p->inLim = p->props.inBufSize_ST; + readRes = ISeqInStream_Read(p->inStream, p->inBuf, &p->inLim); + } + + inCur = (SizeT)(p->inLim - p->inPos); + outCur = (SizeT)size; + + res = Lzma2Dec_DecodeToBuf(&p->dec, data, &outCur, + p->inBuf + p->inPos, &inCur, finishMode, &status); + + p->inPos += inCur; + p->inProcessed += inCur; + *inStreamProcessed += inCur; + p->outProcessed += outCur; + *outSize += outCur; + size -= outCur; + data += outCur; + + if (res != 0) + return res; + + /* + if (status == LZMA_STATUS_FINISHED_WITH_MARK) + return readRes; + + if (size == 0 && status != LZMA_STATUS_NEEDS_MORE_INPUT) + { + if (p->finishMode && p->outSize_Defined && p->outProcessed >= p->outSize) + return SZ_ERROR_DATA; + return readRes; + } + */ + + if (inCur == 0 && outCur == 0) + return readRes; + } +} + +#undef PRF +#undef PRF_STR +#undef PRF_STR_INT_2 diff --git a/src/plugins/7zip/7za/c/Lzma2DecMt.h b/src/plugins/7zip/7za/c/Lzma2DecMt.h new file mode 100644 index 00000000..93a5cd59 --- /dev/null +++ b/src/plugins/7zip/7za/c/Lzma2DecMt.h @@ -0,0 +1,81 @@ +/* Lzma2DecMt.h -- LZMA2 Decoder Multi-thread +2023-04-13 : Igor Pavlov : Public domain */ + +#ifndef ZIP7_INC_LZMA2_DEC_MT_H +#define ZIP7_INC_LZMA2_DEC_MT_H + +#include "7zTypes.h" + +EXTERN_C_BEGIN + +typedef struct +{ + size_t inBufSize_ST; + size_t outStep_ST; + + #ifndef Z7_ST + unsigned numThreads; + size_t inBufSize_MT; + size_t outBlockMax; + size_t inBlockMax; + #endif +} CLzma2DecMtProps; + +/* init to single-thread mode */ +void Lzma2DecMtProps_Init(CLzma2DecMtProps *p); + + +/* ---------- CLzma2DecMtHandle Interface ---------- */ + +/* Lzma2DecMt_ * functions can return the following exit codes: +SRes: + SZ_OK - OK + SZ_ERROR_MEM - Memory allocation error + SZ_ERROR_PARAM - Incorrect paramater in props + SZ_ERROR_WRITE - ISeqOutStream write callback error + // SZ_ERROR_OUTPUT_EOF - output buffer overflow - version with (Byte *) output + SZ_ERROR_PROGRESS - some break from progress callback + SZ_ERROR_THREAD - error in multithreading functions (only for Mt version) +*/ + +typedef struct CLzma2DecMt CLzma2DecMt; +typedef CLzma2DecMt * CLzma2DecMtHandle; +// Z7_DECLARE_HANDLE(CLzma2DecMtHandle) + +CLzma2DecMtHandle Lzma2DecMt_Create(ISzAllocPtr alloc, ISzAllocPtr allocMid); +void Lzma2DecMt_Destroy(CLzma2DecMtHandle p); + +SRes Lzma2DecMt_Decode(CLzma2DecMtHandle p, + Byte prop, + const CLzma2DecMtProps *props, + ISeqOutStreamPtr outStream, + const UInt64 *outDataSize, // NULL means undefined + int finishMode, // 0 - partial unpacking is allowed, 1 - if lzma2 stream must be finished + // Byte *outBuf, size_t *outBufSize, + ISeqInStreamPtr inStream, + // const Byte *inData, size_t inDataSize, + + // out variables: + UInt64 *inProcessed, + int *isMT, /* out: (*isMT == 0), if single thread decoding was used */ + + // UInt64 *outProcessed, + ICompressProgressPtr progress); + + +/* ---------- Read from CLzma2DecMtHandle Interface ---------- */ + +SRes Lzma2DecMt_Init(CLzma2DecMtHandle pp, + Byte prop, + const CLzma2DecMtProps *props, + const UInt64 *outDataSize, int finishMode, + ISeqInStreamPtr inStream); + +SRes Lzma2DecMt_Read(CLzma2DecMtHandle pp, + Byte *data, size_t *outSize, + UInt64 *inStreamProcessed); + + +EXTERN_C_END + +#endif diff --git a/src/plugins/7zip/7za/c/Lzma2Enc.c b/src/plugins/7zip/7za/c/Lzma2Enc.c index 1b77a2bb..72aec695 100644 --- a/src/plugins/7zip/7za/c/Lzma2Enc.c +++ b/src/plugins/7zip/7za/c/Lzma2Enc.c @@ -1,19 +1,18 @@ /* Lzma2Enc.c -- LZMA2 Encoder -2015-10-04 : Igor Pavlov : Public domain */ +: Igor Pavlov : Public domain */ #include "Precomp.h" -/* #include */ #include -/* #define _7ZIP_ST */ +/* #define Z7_ST */ #include "Lzma2Enc.h" -#ifndef _7ZIP_ST +#ifndef Z7_ST #include "MtCoder.h" #else -#define NUM_MT_CODER_THREADS_MAX 1 +#define MTCODER_THREADS_MAX 1 #endif #define LZMA2_CONTROL_LZMA (1 << 7) @@ -35,50 +34,106 @@ #define PRF(x) /* x */ + +/* ---------- CLimitedSeqInStream ---------- */ + +typedef struct +{ + ISeqInStream vt; + ISeqInStreamPtr realStream; + UInt64 limit; + UInt64 processed; + int finished; +} CLimitedSeqInStream; + +static void LimitedSeqInStream_Init(CLimitedSeqInStream *p) +{ + p->limit = (UInt64)(Int64)-1; + p->processed = 0; + p->finished = 0; +} + +static SRes LimitedSeqInStream_Read(ISeqInStreamPtr pp, void *data, size_t *size) +{ + Z7_CONTAINER_FROM_VTBL_TO_DECL_VAR_pp_vt_p(CLimitedSeqInStream) + size_t size2 = *size; + SRes res = SZ_OK; + + if (p->limit != (UInt64)(Int64)-1) + { + const UInt64 rem = p->limit - p->processed; + if (size2 > rem) + size2 = (size_t)rem; + } + if (size2 != 0) + { + res = ISeqInStream_Read(p->realStream, data, &size2); + p->finished = (size2 == 0 ? 1 : 0); + p->processed += size2; + } + *size = size2; + return res; +} + + /* ---------- CLzma2EncInt ---------- */ typedef struct { CLzmaEncHandle enc; + Byte propsAreSet; + Byte propsByte; + Byte needInitState; + Byte needInitProp; UInt64 srcPos; - Byte props; - Bool needInitState; - Bool needInitProp; } CLzma2EncInt; -static SRes Lzma2EncInt_Init(CLzma2EncInt *p, const CLzma2EncProps *props) + +static SRes Lzma2EncInt_InitStream(CLzma2EncInt *p, const CLzma2EncProps *props) +{ + if (!p->propsAreSet) + { + SizeT propsSize = LZMA_PROPS_SIZE; + Byte propsEncoded[LZMA_PROPS_SIZE]; + RINOK(LzmaEnc_SetProps(p->enc, &props->lzmaProps)) + RINOK(LzmaEnc_WriteProperties(p->enc, propsEncoded, &propsSize)) + p->propsByte = propsEncoded[0]; + p->propsAreSet = True; + } + return SZ_OK; +} + +static void Lzma2EncInt_InitBlock(CLzma2EncInt *p) { - Byte propsEncoded[LZMA_PROPS_SIZE]; - SizeT propsSize = LZMA_PROPS_SIZE; - RINOK(LzmaEnc_SetProps(p->enc, &props->lzmaProps)); - RINOK(LzmaEnc_WriteProperties(p->enc, propsEncoded, &propsSize)); p->srcPos = 0; - p->props = propsEncoded[0]; p->needInitState = True; p->needInitProp = True; - return SZ_OK; } -SRes LzmaEnc_PrepareForLzma2(CLzmaEncHandle pp, ISeqInStream *inStream, UInt32 keepWindowSize, - ISzAlloc *alloc, ISzAlloc *allocBig); -SRes LzmaEnc_MemPrepare(CLzmaEncHandle pp, const Byte *src, SizeT srcLen, - UInt32 keepWindowSize, ISzAlloc *alloc, ISzAlloc *allocBig); -SRes LzmaEnc_CodeOneMemBlock(CLzmaEncHandle pp, Bool reInit, + +SRes LzmaEnc_PrepareForLzma2(CLzmaEncHandle p, ISeqInStreamPtr inStream, UInt32 keepWindowSize, + ISzAllocPtr alloc, ISzAllocPtr allocBig); +SRes LzmaEnc_MemPrepare(CLzmaEncHandle p, const Byte *src, SizeT srcLen, + UInt32 keepWindowSize, ISzAllocPtr alloc, ISzAllocPtr allocBig); +SRes LzmaEnc_CodeOneMemBlock(CLzmaEncHandle p, BoolInt reInit, Byte *dest, size_t *destLen, UInt32 desiredPackSize, UInt32 *unpackSize); -const Byte *LzmaEnc_GetCurBuf(CLzmaEncHandle pp); -void LzmaEnc_Finish(CLzmaEncHandle pp); -void LzmaEnc_SaveState(CLzmaEncHandle pp); -void LzmaEnc_RestoreState(CLzmaEncHandle pp); +const Byte *LzmaEnc_GetCurBuf(CLzmaEncHandle p); +void LzmaEnc_Finish(CLzmaEncHandle p); +void LzmaEnc_SaveState(CLzmaEncHandle p); +void LzmaEnc_RestoreState(CLzmaEncHandle p); +/* +UInt32 LzmaEnc_GetNumAvailableBytes(CLzmaEncHandle p); +*/ static SRes Lzma2EncInt_EncodeSubblock(CLzma2EncInt *p, Byte *outBuf, - size_t *packSizeRes, ISeqOutStream *outStream) + size_t *packSizeRes, ISeqOutStreamPtr outStream) { size_t packSizeLimit = *packSizeRes; size_t packSize = packSizeLimit; UInt32 unpackSize = LZMA2_UNPACK_SIZE_MAX; unsigned lzHeaderSize = 5 + (p->needInitProp ? 1 : 0); - Bool useCopyBlock; + BoolInt useCopyBlock; SRes res; *packSizeRes = 0; @@ -112,7 +167,7 @@ static SRes Lzma2EncInt_EncodeSubblock(CLzma2EncInt *p, Byte *outBuf, while (unpackSize > 0) { - UInt32 u = (unpackSize < LZMA2_COPY_CHUNK_SIZE) ? unpackSize : LZMA2_COPY_CHUNK_SIZE; + const UInt32 u = (unpackSize < LZMA2_COPY_CHUNK_SIZE) ? unpackSize : LZMA2_COPY_CHUNK_SIZE; if (packSizeLimit - destPos < u + 3) return SZ_ERROR_OUTPUT_EOF; outBuf[destPos++] = (Byte)(p->srcPos == 0 ? LZMA2_CONTROL_COPY_RESET_DIC : LZMA2_CONTROL_COPY_NO_RESET); @@ -126,7 +181,7 @@ static SRes Lzma2EncInt_EncodeSubblock(CLzma2EncInt *p, Byte *outBuf, if (outStream) { *packSizeRes += destPos; - if (outStream->Write(outStream, outBuf, destPos) != destPos) + if (ISeqOutStream_Write(outStream, outBuf, destPos) != destPos) return SZ_ERROR_WRITE; destPos = 0; } @@ -141,9 +196,9 @@ static SRes Lzma2EncInt_EncodeSubblock(CLzma2EncInt *p, Byte *outBuf, { size_t destPos = 0; - UInt32 u = unpackSize - 1; - UInt32 pm = (UInt32)(packSize - 1); - unsigned mode = (p->srcPos == 0) ? 3 : (p->needInitState ? (p->needInitProp ? 2 : 1) : 0); + const UInt32 u = unpackSize - 1; + const UInt32 pm = (UInt32)(packSize - 1); + const unsigned mode = (p->srcPos == 0) ? 3 : (p->needInitState ? (p->needInitProp ? 2 : 1) : 0); PRF(printf(" ")); @@ -154,7 +209,7 @@ static SRes Lzma2EncInt_EncodeSubblock(CLzma2EncInt *p, Byte *outBuf, outBuf[destPos++] = (Byte)pm; if (p->needInitProp) - outBuf[destPos++] = p->props; + outBuf[destPos++] = p->propsByte; p->needInitProp = False; p->needInitState = False; @@ -162,7 +217,7 @@ static SRes Lzma2EncInt_EncodeSubblock(CLzma2EncInt *p, Byte *outBuf, p->srcPos += unpackSize; if (outStream) - if (outStream->Write(outStream, outBuf, destPos) != destPos) + if (ISeqOutStream_Write(outStream, outBuf, destPos) != destPos) return SZ_ERROR_WRITE; *packSizeRes = destPos; @@ -176,14 +231,17 @@ static SRes Lzma2EncInt_EncodeSubblock(CLzma2EncInt *p, Byte *outBuf, void Lzma2EncProps_Init(CLzma2EncProps *p) { LzmaEncProps_Init(&p->lzmaProps); + p->blockSize = LZMA2_ENC_PROPS_BLOCK_SIZE_AUTO; + p->numBlockThreads_Reduced = -1; + p->numBlockThreads_Max = -1; p->numTotalThreads = -1; - p->numBlockThreads = -1; - p->blockSize = 0; + p->numThreadGroups = 0; } void Lzma2EncProps_Normalize(CLzma2EncProps *p) { - int t1, t1n, t2, t3; + UInt64 fileSize; + int t1, t1n, t2, t2r, t3; { CLzmaEncProps lzmaProps = p->lzmaProps; LzmaEncProps_Normalize(&lzmaProps); @@ -191,11 +249,11 @@ void Lzma2EncProps_Normalize(CLzma2EncProps *p) } t1 = p->lzmaProps.numThreads; - t2 = p->numBlockThreads; + t2 = p->numBlockThreads_Max; t3 = p->numTotalThreads; - if (t2 > NUM_MT_CODER_THREADS_MAX) - t2 = NUM_MT_CODER_THREADS_MAX; + if (t2 > MTCODER_THREADS_MAX) + t2 = MTCODER_THREADS_MAX; if (t3 <= 0) { @@ -211,8 +269,8 @@ void Lzma2EncProps_Normalize(CLzma2EncProps *p) t1 = 1; t2 = t3; } - if (t2 > NUM_MT_CODER_THREADS_MAX) - t2 = NUM_MT_CODER_THREADS_MAX; + if (t2 > MTCODER_THREADS_MAX) + t2 = MTCODER_THREADS_MAX; } else if (t1 <= 0) { @@ -225,232 +283,189 @@ void Lzma2EncProps_Normalize(CLzma2EncProps *p) p->lzmaProps.numThreads = t1; + t2r = t2; + + fileSize = p->lzmaProps.reduceSize; + + if ( p->blockSize != LZMA2_ENC_PROPS_BLOCK_SIZE_SOLID + && p->blockSize != LZMA2_ENC_PROPS_BLOCK_SIZE_AUTO + && (p->blockSize < fileSize || fileSize == (UInt64)(Int64)-1)) + p->lzmaProps.reduceSize = p->blockSize; + LzmaEncProps_Normalize(&p->lzmaProps); + p->lzmaProps.reduceSize = fileSize; + t1 = p->lzmaProps.numThreads; - if (p->blockSize == 0) + if (p->blockSize == LZMA2_ENC_PROPS_BLOCK_SIZE_SOLID) { - UInt32 dictSize = p->lzmaProps.dictSize; - UInt64 blockSize = (UInt64)dictSize << 2; - const UInt32 kMinSize = (UInt32)1 << 20; - const UInt32 kMaxSize = (UInt32)1 << 28; - if (blockSize < kMinSize) blockSize = kMinSize; - if (blockSize > kMaxSize) blockSize = kMaxSize; - if (blockSize < dictSize) blockSize = dictSize; - p->blockSize = (size_t)blockSize; + t2r = t2 = 1; + t3 = t1; } - - if (t2 > 1 && p->lzmaProps.reduceSize != (UInt64)(Int64)-1) + else if (p->blockSize == LZMA2_ENC_PROPS_BLOCK_SIZE_AUTO && t2 <= 1) { - UInt64 temp = p->lzmaProps.reduceSize + p->blockSize - 1; - if (temp > p->lzmaProps.reduceSize) + /* if there is no block multi-threading, we use SOLID block */ + p->blockSize = LZMA2_ENC_PROPS_BLOCK_SIZE_SOLID; + } + else + { + if (p->blockSize == LZMA2_ENC_PROPS_BLOCK_SIZE_AUTO) + { + const UInt32 kMinSize = (UInt32)1 << 20; + const UInt32 kMaxSize = (UInt32)1 << 28; + const UInt32 dictSize = p->lzmaProps.dictSize; + UInt64 blockSize = (UInt64)dictSize << 2; + if (blockSize < kMinSize) blockSize = kMinSize; + if (blockSize > kMaxSize) blockSize = kMaxSize; + if (blockSize < dictSize) blockSize = dictSize; + blockSize += (kMinSize - 1); + blockSize &= ~(UInt64)(kMinSize - 1); + p->blockSize = blockSize; + } + + if (t2 > 1 && fileSize != (UInt64)(Int64)-1) { - UInt64 numBlocks = temp / p->blockSize; + UInt64 numBlocks = fileSize / p->blockSize; + if (numBlocks * p->blockSize != fileSize) + numBlocks++; if (numBlocks < (unsigned)t2) { - t2 = (unsigned)numBlocks; - if (t2 == 0) - t2 = 1; - t3 = t1 * t2; + t2r = (int)numBlocks; + if (t2r == 0) + t2r = 1; + t3 = t1 * t2r; } } } - p->numBlockThreads = t2; + p->numBlockThreads_Max = t2; + p->numBlockThreads_Reduced = t2r; p->numTotalThreads = t3; } -static SRes Progress(ICompressProgress *p, UInt64 inSize, UInt64 outSize) +static SRes Progress(ICompressProgressPtr p, UInt64 inSize, UInt64 outSize) { - return (p && p->Progress(p, inSize, outSize) != SZ_OK) ? SZ_ERROR_PROGRESS : SZ_OK; + return (p && ICompressProgress_Progress(p, inSize, outSize) != SZ_OK) ? SZ_ERROR_PROGRESS : SZ_OK; } /* ---------- Lzma2 ---------- */ -typedef struct +struct CLzma2Enc { Byte propEncoded; CLzma2EncProps props; + UInt64 expectedDataSize; - Byte *outBuf; + Byte *tempBufLzma; - ISzAlloc *alloc; - ISzAlloc *allocBig; + ISzAllocPtr alloc; + ISzAllocPtr allocBig; - CLzma2EncInt coders[NUM_MT_CODER_THREADS_MAX]; + CLzma2EncInt coders[MTCODER_THREADS_MAX]; + + #ifndef Z7_ST + + ISeqOutStreamPtr outStream; + Byte *outBuf; + size_t outBuf_Rem; /* remainder in outBuf */ - #ifndef _7ZIP_ST + size_t outBufSize; /* size of allocated outBufs[i] */ + size_t outBufsDataSizes[MTCODER_BLOCKS_MAX]; + BoolInt mtCoder_WasConstructed; CMtCoder mtCoder; - #endif + Byte *outBufs[MTCODER_BLOCKS_MAX]; -} CLzma2Enc; + #endif +}; -/* ---------- Lzma2EncThread ---------- */ -static SRes Lzma2Enc_EncodeMt1(CLzma2EncInt *p, CLzma2Enc *mainEncoder, - ISeqOutStream *outStream, ISeqInStream *inStream, ICompressProgress *progress) +CLzma2EncHandle Lzma2Enc_Create(ISzAllocPtr alloc, ISzAllocPtr allocBig) { - UInt64 packTotal = 0; - SRes res = SZ_OK; - - if (!mainEncoder->outBuf) + CLzma2Enc *p = (CLzma2Enc *)ISzAlloc_Alloc(alloc, sizeof(CLzma2Enc)); + if (!p) + return NULL; + Lzma2EncProps_Init(&p->props); + Lzma2EncProps_Normalize(&p->props); + p->expectedDataSize = (UInt64)(Int64)-1; + p->tempBufLzma = NULL; + p->alloc = alloc; + p->allocBig = allocBig; { - mainEncoder->outBuf = (Byte *)IAlloc_Alloc(mainEncoder->alloc, LZMA2_CHUNK_SIZE_COMPRESSED_MAX); - if (!mainEncoder->outBuf) - return SZ_ERROR_MEM; + unsigned i; + for (i = 0; i < MTCODER_THREADS_MAX; i++) + p->coders[i].enc = NULL; } - RINOK(Lzma2EncInt_Init(p, &mainEncoder->props)); - RINOK(LzmaEnc_PrepareForLzma2(p->enc, inStream, LZMA2_KEEP_WINDOW_SIZE, - mainEncoder->alloc, mainEncoder->allocBig)); - - for (;;) + #ifndef Z7_ST + p->mtCoder_WasConstructed = False; { - size_t packSize = LZMA2_CHUNK_SIZE_COMPRESSED_MAX; - res = Lzma2EncInt_EncodeSubblock(p, mainEncoder->outBuf, &packSize, outStream); - if (res != SZ_OK) - break; - packTotal += packSize; - res = Progress(progress, p->srcPos, packTotal); - if (res != SZ_OK) - break; - if (packSize == 0) - break; + unsigned i; + for (i = 0; i < MTCODER_BLOCKS_MAX; i++) + p->outBufs[i] = NULL; + p->outBufSize = 0; } - - LzmaEnc_Finish(p->enc); + #endif - if (res == SZ_OK) - { - Byte b = 0; - if (outStream->Write(outStream, &b, 1) != 1) - return SZ_ERROR_WRITE; - } - - return res; + return (CLzma2EncHandle)p; } -#ifndef _7ZIP_ST - -typedef struct -{ - IMtCoderCallback funcTable; - CLzma2Enc *lzma2Enc; -} CMtCallbackImp; +#ifndef Z7_ST -static SRes MtCallbackImp_Code(void *pp, unsigned index, Byte *dest, size_t *destSize, - const Byte *src, size_t srcSize, int finished) +static void Lzma2Enc_FreeOutBufs(CLzma2Enc *p) { - CMtCallbackImp *imp = (CMtCallbackImp *)pp; - CLzma2Enc *mainEncoder = imp->lzma2Enc; - CLzma2EncInt *p = &mainEncoder->coders[index]; - - SRes res = SZ_OK; - { - size_t destLim = *destSize; - *destSize = 0; - - if (srcSize != 0) - { - RINOK(Lzma2EncInt_Init(p, &mainEncoder->props)); - - RINOK(LzmaEnc_MemPrepare(p->enc, src, srcSize, LZMA2_KEEP_WINDOW_SIZE, - mainEncoder->alloc, mainEncoder->allocBig)); - - while (p->srcPos < srcSize) - { - size_t packSize = destLim - *destSize; - res = Lzma2EncInt_EncodeSubblock(p, dest + *destSize, &packSize, NULL); - if (res != SZ_OK) - break; - *destSize += packSize; - - if (packSize == 0) - { - res = SZ_ERROR_FAIL; - break; - } - - if (MtProgress_Set(&mainEncoder->mtCoder.mtProgress, index, p->srcPos, *destSize) != SZ_OK) - { - res = SZ_ERROR_PROGRESS; - break; - } - } - - LzmaEnc_Finish(p->enc); - if (res != SZ_OK) - return res; - } - - if (finished) + unsigned i; + for (i = 0; i < MTCODER_BLOCKS_MAX; i++) + if (p->outBufs[i]) { - if (*destSize == destLim) - return SZ_ERROR_OUTPUT_EOF; - dest[(*destSize)++] = 0; + ISzAlloc_Free(p->alloc, p->outBufs[i]); + p->outBufs[i] = NULL; } - } - return res; + p->outBufSize = 0; } #endif +// #define GET_CLzma2Enc_p CLzma2Enc *p = (CLzma2Enc *)(void *)p; -/* ---------- Lzma2Enc ---------- */ - -CLzma2EncHandle Lzma2Enc_Create(ISzAlloc *alloc, ISzAlloc *allocBig) -{ - CLzma2Enc *p = (CLzma2Enc *)alloc->Alloc(alloc, sizeof(CLzma2Enc)); - if (!p) - return NULL; - Lzma2EncProps_Init(&p->props); - Lzma2EncProps_Normalize(&p->props); - p->outBuf = 0; - p->alloc = alloc; - p->allocBig = allocBig; - { - unsigned i; - for (i = 0; i < NUM_MT_CODER_THREADS_MAX; i++) - p->coders[i].enc = 0; - } - - #ifndef _7ZIP_ST - MtCoder_Construct(&p->mtCoder); - #endif - - return p; -} - -void Lzma2Enc_Destroy(CLzma2EncHandle pp) +void Lzma2Enc_Destroy(CLzma2EncHandle p) { - CLzma2Enc *p = (CLzma2Enc *)pp; + // GET_CLzma2Enc_p unsigned i; - for (i = 0; i < NUM_MT_CODER_THREADS_MAX; i++) + for (i = 0; i < MTCODER_THREADS_MAX; i++) { CLzma2EncInt *t = &p->coders[i]; if (t->enc) { LzmaEnc_Destroy(t->enc, p->alloc, p->allocBig); - t->enc = 0; + t->enc = NULL; } } - #ifndef _7ZIP_ST - MtCoder_Destruct(&p->mtCoder); + + #ifndef Z7_ST + if (p->mtCoder_WasConstructed) + { + MtCoder_Destruct(&p->mtCoder); + p->mtCoder_WasConstructed = False; + } + Lzma2Enc_FreeOutBufs(p); #endif - IAlloc_Free(p->alloc, p->outBuf); - IAlloc_Free(p->alloc, pp); + ISzAlloc_Free(p->alloc, p->tempBufLzma); + p->tempBufLzma = NULL; + + ISzAlloc_Free(p->alloc, p); } -SRes Lzma2Enc_SetProps(CLzma2EncHandle pp, const CLzma2EncProps *props) + +SRes Lzma2Enc_SetProps(CLzma2EncHandle p, const CLzma2EncProps *props) { - CLzma2Enc *p = (CLzma2Enc *)pp; + // GET_CLzma2Enc_p CLzmaEncProps lzmaProps = props->lzmaProps; LzmaEncProps_Normalize(&lzmaProps); if (lzmaProps.lc + lzmaProps.lp > LZMA2_LCLP_MAX) @@ -460,9 +475,17 @@ SRes Lzma2Enc_SetProps(CLzma2EncHandle pp, const CLzma2EncProps *props) return SZ_OK; } -Byte Lzma2Enc_WriteProperties(CLzma2EncHandle pp) + +void Lzma2Enc_SetDataSize(CLzma2EncHandle p, UInt64 expectedDataSiize) { - CLzma2Enc *p = (CLzma2Enc *)pp; + // GET_CLzma2Enc_p + p->expectedDataSize = expectedDataSiize; +} + + +Byte Lzma2Enc_WriteProperties(CLzma2EncHandle p) +{ + // GET_CLzma2Enc_p unsigned i; UInt32 dicSize = LzmaEncProps_GetDictSize(&p->props.lzmaProps); for (i = 0; i < 40; i++) @@ -471,50 +494,314 @@ Byte Lzma2Enc_WriteProperties(CLzma2EncHandle pp) return (Byte)i; } -SRes Lzma2Enc_Encode(CLzma2EncHandle pp, - ISeqOutStream *outStream, ISeqInStream *inStream, ICompressProgress *progress) + +static SRes Lzma2Enc_EncodeMt1( + CLzma2Enc *me, + CLzma2EncInt *p, + ISeqOutStreamPtr outStream, + Byte *outBuf, size_t *outBufSize, + ISeqInStreamPtr inStream, + const Byte *inData, size_t inDataSize, + int finished, + ICompressProgressPtr progress) { - CLzma2Enc *p = (CLzma2Enc *)pp; - int i; + UInt64 unpackTotal = 0; + UInt64 packTotal = 0; + size_t outLim = 0; + CLimitedSeqInStream limitedInStream; + + if (outBuf) + { + outLim = *outBufSize; + *outBufSize = 0; + } + + if (!p->enc) + { + p->propsAreSet = False; + p->enc = LzmaEnc_Create(me->alloc); + if (!p->enc) + return SZ_ERROR_MEM; + } - for (i = 0; i < p->props.numBlockThreads; i++) + limitedInStream.realStream = inStream; + if (inStream) + { + limitedInStream.vt.Read = LimitedSeqInStream_Read; + } + + if (!outBuf) { - CLzma2EncInt *t = &p->coders[(unsigned)i]; - if (!t->enc) + // outStream version works only in one thread. So we use CLzma2Enc::tempBufLzma + if (!me->tempBufLzma) { - t->enc = LzmaEnc_Create(p->alloc); - if (!t->enc) + me->tempBufLzma = (Byte *)ISzAlloc_Alloc(me->alloc, LZMA2_CHUNK_SIZE_COMPRESSED_MAX); + if (!me->tempBufLzma) return SZ_ERROR_MEM; } } - #ifndef _7ZIP_ST - if (p->props.numBlockThreads > 1) + RINOK(Lzma2EncInt_InitStream(p, &me->props)) + + for (;;) { - CMtCallbackImp mtCallback; + SRes res = SZ_OK; + SizeT inSizeCur = 0; + + Lzma2EncInt_InitBlock(p); + + LimitedSeqInStream_Init(&limitedInStream); + limitedInStream.limit = me->props.blockSize; + + if (inStream) + { + UInt64 expected = (UInt64)(Int64)-1; + // inStream version works only in one thread. So we use CLzma2Enc::expectedDataSize + if (me->expectedDataSize != (UInt64)(Int64)-1 + && me->expectedDataSize >= unpackTotal) + expected = me->expectedDataSize - unpackTotal; + if (me->props.blockSize != LZMA2_ENC_PROPS_BLOCK_SIZE_SOLID + && expected > me->props.blockSize) + expected = (size_t)me->props.blockSize; + + LzmaEnc_SetDataSize(p->enc, expected); + + RINOK(LzmaEnc_PrepareForLzma2(p->enc, + &limitedInStream.vt, + LZMA2_KEEP_WINDOW_SIZE, + me->alloc, + me->allocBig)) + } + else + { + inSizeCur = (SizeT)(inDataSize - (size_t)unpackTotal); + if (me->props.blockSize != LZMA2_ENC_PROPS_BLOCK_SIZE_SOLID + && inSizeCur > me->props.blockSize) + inSizeCur = (SizeT)(size_t)me->props.blockSize; + + // LzmaEnc_SetDataSize(p->enc, inSizeCur); + + RINOK(LzmaEnc_MemPrepare(p->enc, + inData + (size_t)unpackTotal, inSizeCur, + LZMA2_KEEP_WINDOW_SIZE, + me->alloc, + me->allocBig)) + } + + for (;;) + { + size_t packSize = LZMA2_CHUNK_SIZE_COMPRESSED_MAX; + if (outBuf) + packSize = outLim - (size_t)packTotal; + + res = Lzma2EncInt_EncodeSubblock(p, + outBuf ? outBuf + (size_t)packTotal : me->tempBufLzma, &packSize, + outBuf ? NULL : outStream); + + if (res != SZ_OK) + break; + + packTotal += packSize; + if (outBuf) + *outBufSize = (size_t)packTotal; + + res = Progress(progress, unpackTotal + p->srcPos, packTotal); + if (res != SZ_OK) + break; + + /* + if (LzmaEnc_GetNumAvailableBytes(p->enc) == 0) + break; + */ + + if (packSize == 0) + break; + } + + LzmaEnc_Finish(p->enc); + + unpackTotal += p->srcPos; + + RINOK(res) - mtCallback.funcTable.Code = MtCallbackImp_Code; - mtCallback.lzma2Enc = p; + if (p->srcPos != (inStream ? limitedInStream.processed : inSizeCur)) + return SZ_ERROR_FAIL; + if (inStream ? limitedInStream.finished : (unpackTotal == inDataSize)) + { + if (finished) + { + if (outBuf) + { + const size_t destPos = *outBufSize; + if (destPos >= outLim) + return SZ_ERROR_OUTPUT_EOF; + outBuf[destPos] = LZMA2_CONTROL_EOF; // 0 + *outBufSize = destPos + 1; + } + else + { + const Byte b = LZMA2_CONTROL_EOF; // 0; + if (ISeqOutStream_Write(outStream, &b, 1) != 1) + return SZ_ERROR_WRITE; + } + } + return SZ_OK; + } + } +} + + + +#ifndef Z7_ST + +static SRes Lzma2Enc_MtCallback_Code(void *p, unsigned coderIndex, unsigned outBufIndex, + const Byte *src, size_t srcSize, int finished) +{ + CLzma2Enc *me = (CLzma2Enc *)p; + size_t destSize = me->outBufSize; + SRes res; + CMtProgressThunk progressThunk; + + Byte *dest = me->outBufs[outBufIndex]; + + me->outBufsDataSizes[outBufIndex] = 0; + + if (!dest) + { + dest = (Byte *)ISzAlloc_Alloc(me->alloc, me->outBufSize); + if (!dest) + return SZ_ERROR_MEM; + me->outBufs[outBufIndex] = dest; + } + + MtProgressThunk_CreateVTable(&progressThunk); + progressThunk.mtProgress = &me->mtCoder.mtProgress; + progressThunk.inSize = 0; + progressThunk.outSize = 0; + + res = Lzma2Enc_EncodeMt1(me, + &me->coders[coderIndex], + NULL, dest, &destSize, + NULL, src, srcSize, + finished, + &progressThunk.vt); + + me->outBufsDataSizes[outBufIndex] = destSize; + + return res; +} + + +static SRes Lzma2Enc_MtCallback_Write(void *p, unsigned outBufIndex) +{ + CLzma2Enc *me = (CLzma2Enc *)p; + size_t size = me->outBufsDataSizes[outBufIndex]; + const Byte *data = me->outBufs[outBufIndex]; + + if (me->outStream) + return ISeqOutStream_Write(me->outStream, data, size) == size ? SZ_OK : SZ_ERROR_WRITE; + + if (size > me->outBuf_Rem) + return SZ_ERROR_OUTPUT_EOF; + memcpy(me->outBuf, data, size); + me->outBuf_Rem -= size; + me->outBuf += size; + return SZ_OK; +} + +#endif + + + +SRes Lzma2Enc_Encode2(CLzma2EncHandle p, + ISeqOutStreamPtr outStream, + Byte *outBuf, size_t *outBufSize, + ISeqInStreamPtr inStream, + const Byte *inData, size_t inDataSize, + ICompressProgressPtr progress) +{ + // GET_CLzma2Enc_p + + if (inStream && inData) + return SZ_ERROR_PARAM; + + if (outStream && outBuf) + return SZ_ERROR_PARAM; + + { + unsigned i; + for (i = 0; i < MTCODER_THREADS_MAX; i++) + p->coders[i].propsAreSet = False; + } + + #ifndef Z7_ST + + if (p->props.numBlockThreads_Reduced > 1) + { + IMtCoderCallback2 vt; + + if (!p->mtCoder_WasConstructed) + { + p->mtCoder_WasConstructed = True; + MtCoder_Construct(&p->mtCoder); + } + + vt.Code = Lzma2Enc_MtCallback_Code; + vt.Write = Lzma2Enc_MtCallback_Write; + + p->outStream = outStream; + p->outBuf = NULL; + p->outBuf_Rem = 0; + if (!outStream) + { + p->outBuf = outBuf; + p->outBuf_Rem = *outBufSize; + *outBufSize = 0; + } + + p->mtCoder.allocBig = p->allocBig; p->mtCoder.progress = progress; p->mtCoder.inStream = inStream; - p->mtCoder.outStream = outStream; - p->mtCoder.alloc = p->alloc; - p->mtCoder.mtCallback = &mtCallback.funcTable; + p->mtCoder.inData = inData; + p->mtCoder.inDataSize = inDataSize; + p->mtCoder.mtCallback = &vt; + p->mtCoder.mtCallbackObject = p; + + p->mtCoder.blockSize = (size_t)p->props.blockSize; + if (p->mtCoder.blockSize != p->props.blockSize) + return SZ_ERROR_PARAM; /* SZ_ERROR_MEM */ - p->mtCoder.blockSize = p->props.blockSize; - p->mtCoder.destBlockSize = p->props.blockSize + (p->props.blockSize >> 10) + 16; - if (p->mtCoder.destBlockSize < p->props.blockSize) { - p->mtCoder.destBlockSize = (size_t)0 - 1; - if (p->mtCoder.destBlockSize < p->props.blockSize) - return SZ_ERROR_FAIL; + const size_t destBlockSize = p->mtCoder.blockSize + (p->mtCoder.blockSize >> 10) + 16; + if (destBlockSize < p->mtCoder.blockSize) + return SZ_ERROR_PARAM; + if (p->outBufSize != destBlockSize) + Lzma2Enc_FreeOutBufs(p); + p->outBufSize = destBlockSize; } - p->mtCoder.numThreads = p->props.numBlockThreads; + + p->mtCoder.numThreadsMax = (unsigned)p->props.numBlockThreads_Max; + p->mtCoder.numThreadGroups = p->props.numThreadGroups; + p->mtCoder.expectedDataSize = p->expectedDataSize; - return MtCoder_Code(&p->mtCoder); + { + const SRes res = MtCoder_Code(&p->mtCoder); + if (!outStream) + *outBufSize = (size_t)(p->outBuf - outBuf); + return res; + } } + #endif - return Lzma2Enc_EncodeMt1(&p->coders[0], p, outStream, inStream, progress); + + return Lzma2Enc_EncodeMt1(p, + &p->coders[0], + outStream, outBuf, outBufSize, + inStream, inData, inDataSize, + True, /* finished */ + progress); } + +#undef PRF diff --git a/src/plugins/7zip/7za/c/Lzma2Enc.h b/src/plugins/7zip/7za/c/Lzma2Enc.h index f409f184..1e6b50c6 100644 --- a/src/plugins/7zip/7za/c/Lzma2Enc.h +++ b/src/plugins/7zip/7za/c/Lzma2Enc.h @@ -1,19 +1,24 @@ /* Lzma2Enc.h -- LZMA2 Encoder -2013-01-18 : Igor Pavlov : Public domain */ +2023-04-13 : Igor Pavlov : Public domain */ -#ifndef __LZMA2_ENC_H -#define __LZMA2_ENC_H +#ifndef ZIP7_INC_LZMA2_ENC_H +#define ZIP7_INC_LZMA2_ENC_H #include "LzmaEnc.h" EXTERN_C_BEGIN +#define LZMA2_ENC_PROPS_BLOCK_SIZE_AUTO 0 +#define LZMA2_ENC_PROPS_BLOCK_SIZE_SOLID ((UInt64)(Int64)-1) + typedef struct { CLzmaEncProps lzmaProps; - size_t blockSize; - int numBlockThreads; + UInt64 blockSize; + int numBlockThreads_Reduced; + int numBlockThreads_Max; int numTotalThreads; + unsigned numThreadGroups; // 0 : no groups } CLzma2EncProps; void Lzma2EncProps_Init(CLzma2EncProps *p); @@ -22,40 +27,31 @@ void Lzma2EncProps_Normalize(CLzma2EncProps *p); /* ---------- CLzmaEnc2Handle Interface ---------- */ /* Lzma2Enc_* functions can return the following exit codes: -Returns: +SRes: SZ_OK - OK SZ_ERROR_MEM - Memory allocation error SZ_ERROR_PARAM - Incorrect paramater in props - SZ_ERROR_WRITE - Write callback error + SZ_ERROR_WRITE - ISeqOutStream write callback error + SZ_ERROR_OUTPUT_EOF - output buffer overflow - version with (Byte *) output SZ_ERROR_PROGRESS - some break from progress callback - SZ_ERROR_THREAD - errors in multithreading functions (only for Mt version) + SZ_ERROR_THREAD - error in multithreading functions (only for Mt version) */ -typedef void * CLzma2EncHandle; +typedef struct CLzma2Enc CLzma2Enc; +typedef CLzma2Enc * CLzma2EncHandle; +// Z7_DECLARE_HANDLE(CLzma2EncHandle) -CLzma2EncHandle Lzma2Enc_Create(ISzAlloc *alloc, ISzAlloc *allocBig); +CLzma2EncHandle Lzma2Enc_Create(ISzAllocPtr alloc, ISzAllocPtr allocBig); void Lzma2Enc_Destroy(CLzma2EncHandle p); SRes Lzma2Enc_SetProps(CLzma2EncHandle p, const CLzma2EncProps *props); +void Lzma2Enc_SetDataSize(CLzma2EncHandle p, UInt64 expectedDataSiize); Byte Lzma2Enc_WriteProperties(CLzma2EncHandle p); -SRes Lzma2Enc_Encode(CLzma2EncHandle p, - ISeqOutStream *outStream, ISeqInStream *inStream, ICompressProgress *progress); - -/* ---------- One Call Interface ---------- */ - -/* Lzma2Encode -Return code: - SZ_OK - OK - SZ_ERROR_MEM - Memory allocation error - SZ_ERROR_PARAM - Incorrect paramater - SZ_ERROR_OUTPUT_EOF - output buffer overflow - SZ_ERROR_THREAD - errors in multithreading functions (only for Mt version) -*/ - -/* -SRes Lzma2Encode(Byte *dest, SizeT *destLen, const Byte *src, SizeT srcLen, - const CLzmaEncProps *props, Byte *propsEncoded, int writeEndMark, - ICompressProgress *progress, ISzAlloc *alloc, ISzAlloc *allocBig); -*/ +SRes Lzma2Enc_Encode2(CLzma2EncHandle p, + ISeqOutStreamPtr outStream, + Byte *outBuf, size_t *outBufSize, + ISeqInStreamPtr inStream, + const Byte *inData, size_t inDataSize, + ICompressProgressPtr progress); EXTERN_C_END diff --git a/src/plugins/7zip/7za/c/Lzma86.h b/src/plugins/7zip/7za/c/Lzma86.h index bebed5cb..e7707e2c 100644 --- a/src/plugins/7zip/7za/c/Lzma86.h +++ b/src/plugins/7zip/7za/c/Lzma86.h @@ -1,8 +1,8 @@ /* Lzma86.h -- LZMA + x86 (BCJ) Filter -2013-01-18 : Igor Pavlov : Public domain */ +2023-03-03 : Igor Pavlov : Public domain */ -#ifndef __LZMA86_H -#define __LZMA86_H +#ifndef ZIP7_INC_LZMA86_H +#define ZIP7_INC_LZMA86_H #include "7zTypes.h" diff --git a/src/plugins/7zip/7za/c/Lzma86Dec.c b/src/plugins/7zip/7za/c/Lzma86Dec.c index 21031745..f094d4c3 100644 --- a/src/plugins/7zip/7za/c/Lzma86Dec.c +++ b/src/plugins/7zip/7za/c/Lzma86Dec.c @@ -1,5 +1,5 @@ /* Lzma86Dec.c -- LZMA + x86 (BCJ) Filter Decoder -2016-05-16 : Igor Pavlov : Public domain */ +2023-03-03 : Igor Pavlov : Public domain */ #include "Precomp.h" @@ -46,9 +46,8 @@ SRes Lzma86_Decode(Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen) return res; if (useFilter == 1) { - UInt32 x86State; - x86_Convert_Init(x86State); - x86_Convert(dest, *destLen, 0, &x86State, 0); + UInt32 x86State = Z7_BRANCH_CONV_ST_X86_STATE_INIT_VAL; + z7_BranchConvSt_X86_Dec(dest, *destLen, 0, &x86State); } return SZ_OK; } diff --git a/src/plugins/7zip/7za/c/Lzma86Enc.c b/src/plugins/7zip/7za/c/Lzma86Enc.c index a15b58d6..0cdde1c9 100644 --- a/src/plugins/7zip/7za/c/Lzma86Enc.c +++ b/src/plugins/7zip/7za/c/Lzma86Enc.c @@ -1,5 +1,5 @@ /* Lzma86Enc.c -- LZMA + x86 (BCJ) Filter Encoder -2016-05-16 : Igor Pavlov : Public domain */ +2023-03-03 : Igor Pavlov : Public domain */ #include "Precomp.h" @@ -11,14 +11,12 @@ #include "Bra.h" #include "LzmaEnc.h" -#define SZE_OUT_OVERFLOW SZE_DATA_ERROR - int Lzma86_Encode(Byte *dest, size_t *destLen, const Byte *src, size_t srcLen, int level, UInt32 dictSize, int filterMode) { size_t outSize2 = *destLen; Byte *filteredStream; - Bool useFilter; + BoolInt useFilter; int mainResult = SZ_ERROR_OUTPUT_EOF; CLzmaEncProps props; LzmaEncProps_Init(&props); @@ -48,15 +46,14 @@ int Lzma86_Encode(Byte *dest, size_t *destLen, const Byte *src, size_t srcLen, memcpy(filteredStream, src, srcLen); } { - UInt32 x86State; - x86_Convert_Init(x86State); - x86_Convert(filteredStream, srcLen, 0, &x86State, 1); + UInt32 x86State = Z7_BRANCH_CONV_ST_X86_STATE_INIT_VAL; + z7_BranchConvSt_X86_Enc(filteredStream, srcLen, 0, &x86State); } } { size_t minSize = 0; - Bool bestIsFiltered = False; + BoolInt bestIsFiltered = False; /* passes for SZ_FILTER_AUTO: 0 - BCJ + LZMA @@ -71,7 +68,7 @@ int Lzma86_Encode(Byte *dest, size_t *destLen, const Byte *src, size_t srcLen, size_t outSizeProcessed = outSize2 - LZMA86_HEADER_SIZE; size_t outPropsSize = 5; SRes curRes; - Bool curModeIsFiltered = (numPasses > 1 && i == numPasses - 1); + BoolInt curModeIsFiltered = (numPasses > 1 && i == numPasses - 1); if (curModeIsFiltered && !bestIsFiltered) break; if (useFilter && i == 0) diff --git a/src/plugins/7zip/7za/c/LzmaDec.c b/src/plugins/7zip/7za/c/LzmaDec.c index 12dce11f..69bb8bba 100644 --- a/src/plugins/7zip/7za/c/LzmaDec.c +++ b/src/plugins/7zip/7za/c/LzmaDec.c @@ -1,90 +1,107 @@ /* LzmaDec.c -- LZMA Decoder -2016-05-16 : Igor Pavlov : Public domain */ +2023-04-07 : Igor Pavlov : Public domain */ #include "Precomp.h" -#include "LzmaDec.h" - #include -#define kNumTopBits 24 -#define kTopValue ((UInt32)1 << kNumTopBits) +/* #include "CpuArch.h" */ +#include "LzmaDec.h" + +// #define kNumTopBits 24 +#define kTopValue ((UInt32)1 << 24) #define kNumBitModelTotalBits 11 #define kBitModelTotal (1 << kNumBitModelTotalBits) -#define kNumMoveBits 5 #define RC_INIT_SIZE 5 +#ifndef Z7_LZMA_DEC_OPT + +#define kNumMoveBits 5 #define NORMALIZE if (range < kTopValue) { range <<= 8; code = (code << 8) | (*buf++); } -#define IF_BIT_0(p) ttt = *(p); NORMALIZE; bound = (range >> kNumBitModelTotalBits) * ttt; if (code < bound) +#define IF_BIT_0(p) ttt = *(p); NORMALIZE; bound = (range >> kNumBitModelTotalBits) * (UInt32)ttt; if (code < bound) #define UPDATE_0(p) range = bound; *(p) = (CLzmaProb)(ttt + ((kBitModelTotal - ttt) >> kNumMoveBits)); #define UPDATE_1(p) range -= bound; code -= bound; *(p) = (CLzmaProb)(ttt - (ttt >> kNumMoveBits)); #define GET_BIT2(p, i, A0, A1) IF_BIT_0(p) \ - { UPDATE_0(p); i = (i + i); A0; } else \ - { UPDATE_1(p); i = (i + i) + 1; A1; } -#define GET_BIT(p, i) GET_BIT2(p, i, ; , ;) + { UPDATE_0(p) i = (i + i); A0; } else \ + { UPDATE_1(p) i = (i + i) + 1; A1; } + +#define TREE_GET_BIT(probs, i) { GET_BIT2(probs + i, i, ;, ;); } + +#define REV_BIT(p, i, A0, A1) IF_BIT_0(p + i) \ + { UPDATE_0(p + i) A0; } else \ + { UPDATE_1(p + i) A1; } +#define REV_BIT_VAR( p, i, m) REV_BIT(p, i, i += m; m += m, m += m; i += m; ) +#define REV_BIT_CONST(p, i, m) REV_BIT(p, i, i += m; , i += m * 2; ) +#define REV_BIT_LAST( p, i, m) REV_BIT(p, i, i -= m , ; ) -#define TREE_GET_BIT(probs, i) { GET_BIT((probs + i), i); } #define TREE_DECODE(probs, limit, i) \ { i = 1; do { TREE_GET_BIT(probs, i); } while (i < limit); i -= limit; } -/* #define _LZMA_SIZE_OPT */ +/* #define Z7_LZMA_SIZE_OPT */ -#ifdef _LZMA_SIZE_OPT +#ifdef Z7_LZMA_SIZE_OPT #define TREE_6_DECODE(probs, i) TREE_DECODE(probs, (1 << 6), i) #else #define TREE_6_DECODE(probs, i) \ { i = 1; \ - TREE_GET_BIT(probs, i); \ - TREE_GET_BIT(probs, i); \ - TREE_GET_BIT(probs, i); \ - TREE_GET_BIT(probs, i); \ - TREE_GET_BIT(probs, i); \ - TREE_GET_BIT(probs, i); \ + TREE_GET_BIT(probs, i) \ + TREE_GET_BIT(probs, i) \ + TREE_GET_BIT(probs, i) \ + TREE_GET_BIT(probs, i) \ + TREE_GET_BIT(probs, i) \ + TREE_GET_BIT(probs, i) \ i -= 0x40; } #endif -#define NORMAL_LITER_DEC GET_BIT(prob + symbol, symbol) +#define NORMAL_LITER_DEC TREE_GET_BIT(prob, symbol) #define MATCHED_LITER_DEC \ - matchByte <<= 1; \ - bit = (matchByte & offs); \ - probLit = prob + offs + bit + symbol; \ - GET_BIT2(probLit, symbol, offs &= ~bit, offs &= bit) + matchByte += matchByte; \ + bit = offs; \ + offs &= matchByte; \ + probLit = prob + (offs + bit + symbol); \ + GET_BIT2(probLit, symbol, offs ^= bit; , ;) + +#endif // Z7_LZMA_DEC_OPT + -#define NORMALIZE_CHECK if (range < kTopValue) { if (buf >= bufLimit) return DUMMY_ERROR; range <<= 8; code = (code << 8) | (*buf++); } +#define NORMALIZE_CHECK if (range < kTopValue) { if (buf >= bufLimit) return DUMMY_INPUT_EOF; range <<= 8; code = (code << 8) | (*buf++); } -#define IF_BIT_0_CHECK(p) ttt = *(p); NORMALIZE_CHECK; bound = (range >> kNumBitModelTotalBits) * ttt; if (code < bound) +#define IF_BIT_0_CHECK(p) ttt = *(p); NORMALIZE_CHECK bound = (range >> kNumBitModelTotalBits) * (UInt32)ttt; if (code < bound) #define UPDATE_0_CHECK range = bound; #define UPDATE_1_CHECK range -= bound; code -= bound; #define GET_BIT2_CHECK(p, i, A0, A1) IF_BIT_0_CHECK(p) \ - { UPDATE_0_CHECK; i = (i + i); A0; } else \ - { UPDATE_1_CHECK; i = (i + i) + 1; A1; } + { UPDATE_0_CHECK i = (i + i); A0; } else \ + { UPDATE_1_CHECK i = (i + i) + 1; A1; } #define GET_BIT_CHECK(p, i) GET_BIT2_CHECK(p, i, ; , ;) #define TREE_DECODE_CHECK(probs, limit, i) \ { i = 1; do { GET_BIT_CHECK(probs + i, i) } while (i < limit); i -= limit; } +#define REV_BIT_CHECK(p, i, m) IF_BIT_0_CHECK(p + i) \ + { UPDATE_0_CHECK i += m; m += m; } else \ + { UPDATE_1_CHECK m += m; i += m; } + + #define kNumPosBitsMax 4 #define kNumPosStatesMax (1 << kNumPosBitsMax) #define kLenNumLowBits 3 #define kLenNumLowSymbols (1 << kLenNumLowBits) -#define kLenNumMidBits 3 -#define kLenNumMidSymbols (1 << kLenNumMidBits) #define kLenNumHighBits 8 #define kLenNumHighSymbols (1 << kLenNumHighBits) -#define LenChoice 0 -#define LenChoice2 (LenChoice + 1) -#define LenLow (LenChoice2 + 1) -#define LenMid (LenLow + (kNumPosStatesMax << kLenNumLowBits)) -#define LenHigh (LenMid + (kNumPosStatesMax << kLenNumMidBits)) +#define LenLow 0 +#define LenHigh (LenLow + 2 * (kNumPosStatesMax << kLenNumLowBits)) #define kNumLenProbs (LenHigh + kLenNumHighSymbols) +#define LenChoice LenLow +#define LenChoice2 (LenLow + (1 << kLenNumLowBits)) #define kNumStates 12 +#define kNumStates2 16 #define kNumLitStates 7 #define kStartPosModelIndex 4 @@ -98,54 +115,130 @@ #define kAlignTableSize (1 << kNumAlignBits) #define kMatchMinLen 2 -#define kMatchSpecLenStart (kMatchMinLen + kLenNumLowSymbols + kLenNumMidSymbols + kLenNumHighSymbols) +#define kMatchSpecLenStart (kMatchMinLen + kLenNumLowSymbols * 2 + kLenNumHighSymbols) + +#define kMatchSpecLen_Error_Data (1 << 9) +#define kMatchSpecLen_Error_Fail (kMatchSpecLen_Error_Data - 1) -#define IsMatch 0 -#define IsRep (IsMatch + (kNumStates << kNumPosBitsMax)) +/* External ASM code needs same CLzmaProb array layout. So don't change it. */ + +/* (probs_1664) is faster and better for code size at some platforms */ +/* +#ifdef MY_CPU_X86_OR_AMD64 +*/ +#define kStartOffset 1664 +#define GET_PROBS p->probs_1664 +/* +#define GET_PROBS p->probs + kStartOffset +#else +#define kStartOffset 0 +#define GET_PROBS p->probs +#endif +*/ + +#define SpecPos (-kStartOffset) +#define IsRep0Long (SpecPos + kNumFullDistances) +#define RepLenCoder (IsRep0Long + (kNumStates2 << kNumPosBitsMax)) +#define LenCoder (RepLenCoder + kNumLenProbs) +#define IsMatch (LenCoder + kNumLenProbs) +#define Align (IsMatch + (kNumStates2 << kNumPosBitsMax)) +#define IsRep (Align + kAlignTableSize) #define IsRepG0 (IsRep + kNumStates) #define IsRepG1 (IsRepG0 + kNumStates) #define IsRepG2 (IsRepG1 + kNumStates) -#define IsRep0Long (IsRepG2 + kNumStates) -#define PosSlot (IsRep0Long + (kNumStates << kNumPosBitsMax)) -#define SpecPos (PosSlot + (kNumLenToPosStates << kNumPosSlotBits)) -#define Align (SpecPos + kNumFullDistances - kEndPosModelIndex) -#define LenCoder (Align + kAlignTableSize) -#define RepLenCoder (LenCoder + kNumLenProbs) -#define Literal (RepLenCoder + kNumLenProbs) - -#define LZMA_BASE_SIZE 1846 -#define LZMA_LIT_SIZE 0x300 +#define PosSlot (IsRepG2 + kNumStates) +#define Literal (PosSlot + (kNumLenToPosStates << kNumPosSlotBits)) +#define NUM_BASE_PROBS (Literal + kStartOffset) -#if Literal != LZMA_BASE_SIZE -StopCompilingDueBUG +#if Align != 0 && kStartOffset != 0 + #error Stop_Compiling_Bad_LZMA_kAlign #endif -#define LzmaProps_GetNumProbs(p) (Literal + ((UInt32)LZMA_LIT_SIZE << ((p)->lc + (p)->lp))) +#if NUM_BASE_PROBS != 1984 + #error Stop_Compiling_Bad_LZMA_PROBS +#endif + + +#define LZMA_LIT_SIZE 0x300 + +#define LzmaProps_GetNumProbs(p) (NUM_BASE_PROBS + ((UInt32)LZMA_LIT_SIZE << ((p)->lc + (p)->lp))) + + +#define CALC_POS_STATE(processedPos, pbMask) (((processedPos) & (pbMask)) << 4) +#define COMBINED_PS_STATE (posState + state) +#define GET_LEN_STATE (posState) #define LZMA_DIC_MIN (1 << 12) -/* First LZMA-symbol is always decoded. -And it decodes new LZMA-symbols while (buf < bufLimit), but "buf" is without last normalization +/* +p->remainLen : shows status of LZMA decoder: + < kMatchSpecLenStart : the number of bytes to be copied with (p->rep0) offset + = kMatchSpecLenStart : the LZMA stream was finished with end mark + = kMatchSpecLenStart + 1 : need init range coder + = kMatchSpecLenStart + 2 : need init range coder and state + = kMatchSpecLen_Error_Fail : Internal Code Failure + = kMatchSpecLen_Error_Data + [0 ... 273] : LZMA Data Error +*/ + +/* ---------- LZMA_DECODE_REAL ---------- */ +/* +LzmaDec_DecodeReal_3() can be implemented in external ASM file. +3 - is the code compatibility version of that function for check at link time. +*/ + +#define LZMA_DECODE_REAL LzmaDec_DecodeReal_3 + +/* +LZMA_DECODE_REAL() +In: + RangeCoder is normalized + if (p->dicPos == limit) + { + LzmaDec_TryDummy() was called before to exclude LITERAL and MATCH-REP cases. + So first symbol can be only MATCH-NON-REP. And if that MATCH-NON-REP symbol + is not END_OF_PAYALOAD_MARKER, then the function doesn't write any byte to dictionary, + the function returns SZ_OK, and the caller can use (p->remainLen) and (p->reps[0]) later. + } + +Processing: + The first LZMA symbol will be decoded in any case. + All main checks for limits are at the end of main loop, + It decodes additional LZMA-symbols while (p->buf < bufLimit && dicPos < limit), + RangeCoder is still without last normalization when (p->buf < bufLimit) is being checked. + But if (p->buf < bufLimit), the caller provided at least (LZMA_REQUIRED_INPUT_MAX + 1) bytes for + next iteration before limit (bufLimit + LZMA_REQUIRED_INPUT_MAX), + that is enough for worst case LZMA symbol with one additional RangeCoder normalization for one bit. + So that function never reads bufLimit [LZMA_REQUIRED_INPUT_MAX] byte. + Out: + RangeCoder is normalized Result: SZ_OK - OK - SZ_ERROR_DATA - Error - p->remainLen: - < kMatchSpecLenStart : normal remain - = kMatchSpecLenStart : finished - = kMatchSpecLenStart + 1 : Flush marker (unused now) - = kMatchSpecLenStart + 2 : State Init Marker (unused now) + p->remainLen: + < kMatchSpecLenStart : the number of bytes to be copied with (p->reps[0]) offset + = kMatchSpecLenStart : the LZMA stream was finished with end mark + + SZ_ERROR_DATA - error, when the MATCH-Symbol refers out of dictionary + p->remainLen : undefined + p->reps[*] : undefined */ -static int MY_FAST_CALL LzmaDec_DecodeReal(CLzmaDec *p, SizeT limit, const Byte *bufLimit) -{ - CLzmaProb *probs = p->probs; - unsigned state = p->state; +#ifdef Z7_LZMA_DEC_OPT + +int Z7_FASTCALL LZMA_DECODE_REAL(CLzmaDec *p, SizeT limit, const Byte *bufLimit); + +#else + +static +int Z7_FASTCALL LZMA_DECODE_REAL(CLzmaDec *p, SizeT limit, const Byte *bufLimit) +{ + CLzmaProb *probs = GET_PROBS; + unsigned state = (unsigned)p->state; UInt32 rep0 = p->reps[0], rep1 = p->reps[1], rep2 = p->reps[2], rep3 = p->reps[3]; unsigned pbMask = ((unsigned)1 << (p->prop.pb)) - 1; - unsigned lpMask = ((unsigned)1 << (p->prop.lp)) - 1; unsigned lc = p->prop.lc; + unsigned lpMask = ((unsigned)0x100 << p->prop.lp) - ((unsigned)0x100 >> lc); Byte *dic = p->dic; SizeT dicBufSize = p->dicBufSize; @@ -164,24 +257,23 @@ static int MY_FAST_CALL LzmaDec_DecodeReal(CLzmaDec *p, SizeT limit, const Byte CLzmaProb *prob; UInt32 bound; unsigned ttt; - unsigned posState = processedPos & pbMask; + unsigned posState = CALC_POS_STATE(processedPos, pbMask); - prob = probs + IsMatch + (state << kNumPosBitsMax) + posState; + prob = probs + IsMatch + COMBINED_PS_STATE; IF_BIT_0(prob) { unsigned symbol; - UPDATE_0(prob); + UPDATE_0(prob) prob = probs + Literal; if (processedPos != 0 || checkDicSize != 0) - prob += ((UInt32)LZMA_LIT_SIZE * (((processedPos & lpMask) << lc) + - (dic[(dicPos == 0 ? dicBufSize : dicPos) - 1] >> (8 - lc)))); + prob += (UInt32)3 * ((((processedPos << 8) + dic[(dicPos == 0 ? dicBufSize : dicPos) - 1]) & lpMask) << lc); processedPos++; if (state < kNumLitStates) { state -= (state < 4) ? state : 3; symbol = 1; - #ifdef _LZMA_SIZE_OPT + #ifdef Z7_LZMA_SIZE_OPT do { NORMAL_LITER_DEC } while (symbol < 0x100); #else NORMAL_LITER_DEC @@ -200,7 +292,7 @@ static int MY_FAST_CALL LzmaDec_DecodeReal(CLzmaDec *p, SizeT limit, const Byte unsigned offs = 0x100; state -= (state < 10) ? 3 : 6; symbol = 1; - #ifdef _LZMA_SIZE_OPT + #ifdef Z7_LZMA_SIZE_OPT do { unsigned bit; @@ -229,57 +321,62 @@ static int MY_FAST_CALL LzmaDec_DecodeReal(CLzmaDec *p, SizeT limit, const Byte } { - UPDATE_1(prob); + UPDATE_1(prob) prob = probs + IsRep + state; IF_BIT_0(prob) { - UPDATE_0(prob); + UPDATE_0(prob) state += kNumStates; prob = probs + LenCoder; } else { - UPDATE_1(prob); - if (checkDicSize == 0 && processedPos == 0) - return SZ_ERROR_DATA; + UPDATE_1(prob) prob = probs + IsRepG0 + state; IF_BIT_0(prob) { - UPDATE_0(prob); - prob = probs + IsRep0Long + (state << kNumPosBitsMax) + posState; + UPDATE_0(prob) + prob = probs + IsRep0Long + COMBINED_PS_STATE; IF_BIT_0(prob) { - UPDATE_0(prob); + UPDATE_0(prob) + + // that case was checked before with kBadRepCode + // if (checkDicSize == 0 && processedPos == 0) { len = kMatchSpecLen_Error_Data + 1; break; } + // The caller doesn't allow (dicPos == limit) case here + // so we don't need the following check: + // if (dicPos == limit) { state = state < kNumLitStates ? 9 : 11; len = 1; break; } + dic[dicPos] = dic[dicPos - rep0 + (dicPos < rep0 ? dicBufSize : 0)]; dicPos++; processedPos++; state = state < kNumLitStates ? 9 : 11; continue; } - UPDATE_1(prob); + UPDATE_1(prob) } else { UInt32 distance; - UPDATE_1(prob); + UPDATE_1(prob) prob = probs + IsRepG1 + state; IF_BIT_0(prob) { - UPDATE_0(prob); + UPDATE_0(prob) distance = rep1; } else { - UPDATE_1(prob); + UPDATE_1(prob) prob = probs + IsRepG2 + state; IF_BIT_0(prob) { - UPDATE_0(prob); + UPDATE_0(prob) distance = rep2; } else { - UPDATE_1(prob); + UPDATE_1(prob) distance = rep3; rep3 = rep2; } @@ -292,37 +389,37 @@ static int MY_FAST_CALL LzmaDec_DecodeReal(CLzmaDec *p, SizeT limit, const Byte prob = probs + RepLenCoder; } - #ifdef _LZMA_SIZE_OPT + #ifdef Z7_LZMA_SIZE_OPT { unsigned lim, offset; CLzmaProb *probLen = prob + LenChoice; IF_BIT_0(probLen) { - UPDATE_0(probLen); - probLen = prob + LenLow + (posState << kLenNumLowBits); + UPDATE_0(probLen) + probLen = prob + LenLow + GET_LEN_STATE; offset = 0; lim = (1 << kLenNumLowBits); } else { - UPDATE_1(probLen); + UPDATE_1(probLen) probLen = prob + LenChoice2; IF_BIT_0(probLen) { - UPDATE_0(probLen); - probLen = prob + LenMid + (posState << kLenNumMidBits); + UPDATE_0(probLen) + probLen = prob + LenLow + GET_LEN_STATE + (1 << kLenNumLowBits); offset = kLenNumLowSymbols; - lim = (1 << kLenNumMidBits); + lim = (1 << kLenNumLowBits); } else { - UPDATE_1(probLen); + UPDATE_1(probLen) probLen = prob + LenHigh; - offset = kLenNumLowSymbols + kLenNumMidSymbols; + offset = kLenNumLowSymbols * 2; lim = (1 << kLenNumHighBits); } } - TREE_DECODE(probLen, lim, len); + TREE_DECODE(probLen, lim, len) len += offset; } #else @@ -330,33 +427,33 @@ static int MY_FAST_CALL LzmaDec_DecodeReal(CLzmaDec *p, SizeT limit, const Byte CLzmaProb *probLen = prob + LenChoice; IF_BIT_0(probLen) { - UPDATE_0(probLen); - probLen = prob + LenLow + (posState << kLenNumLowBits); + UPDATE_0(probLen) + probLen = prob + LenLow + GET_LEN_STATE; len = 1; - TREE_GET_BIT(probLen, len); - TREE_GET_BIT(probLen, len); - TREE_GET_BIT(probLen, len); + TREE_GET_BIT(probLen, len) + TREE_GET_BIT(probLen, len) + TREE_GET_BIT(probLen, len) len -= 8; } else { - UPDATE_1(probLen); + UPDATE_1(probLen) probLen = prob + LenChoice2; IF_BIT_0(probLen) { - UPDATE_0(probLen); - probLen = prob + LenMid + (posState << kLenNumMidBits); + UPDATE_0(probLen) + probLen = prob + LenLow + GET_LEN_STATE + (1 << kLenNumLowBits); len = 1; - TREE_GET_BIT(probLen, len); - TREE_GET_BIT(probLen, len); - TREE_GET_BIT(probLen, len); + TREE_GET_BIT(probLen, len) + TREE_GET_BIT(probLen, len) + TREE_GET_BIT(probLen, len) } else { - UPDATE_1(probLen); + UPDATE_1(probLen) probLen = prob + LenHigh; - TREE_DECODE(probLen, (1 << kLenNumHighBits), len); - len += kLenNumLowSymbols + kLenNumMidSymbols; + TREE_DECODE(probLen, (1 << kLenNumHighBits), len) + len += kLenNumLowSymbols * 2; } } } @@ -367,7 +464,7 @@ static int MY_FAST_CALL LzmaDec_DecodeReal(CLzmaDec *p, SizeT limit, const Byte UInt32 distance; prob = probs + PosSlot + ((len < kNumLenToPosStates ? len : kNumLenToPosStates - 1) << kNumPosSlotBits); - TREE_6_DECODE(prob, distance); + TREE_6_DECODE(prob, distance) if (distance >= kStartPosModelIndex) { unsigned posSlot = (unsigned)distance; @@ -376,16 +473,16 @@ static int MY_FAST_CALL LzmaDec_DecodeReal(CLzmaDec *p, SizeT limit, const Byte if (posSlot < kEndPosModelIndex) { distance <<= numDirectBits; - prob = probs + SpecPos + distance - posSlot - 1; + prob = probs + SpecPos; { - UInt32 mask = 1; - unsigned i = 1; + UInt32 m = 1; + distance++; do { - GET_BIT2(prob + i, i, ; , distance |= mask); - mask <<= 1; + REV_BIT_VAR(prob, distance, m) } - while (--numDirectBits != 0); + while (--numDirectBits); + distance -= m; } } else @@ -412,19 +509,20 @@ static int MY_FAST_CALL LzmaDec_DecodeReal(CLzmaDec *p, SizeT limit, const Byte } */ } - while (--numDirectBits != 0); + while (--numDirectBits); prob = probs + Align; distance <<= kNumAlignBits; { unsigned i = 1; - GET_BIT2(prob + i, i, ; , distance |= 1); - GET_BIT2(prob + i, i, ; , distance |= 2); - GET_BIT2(prob + i, i, ; , distance |= 4); - GET_BIT2(prob + i, i, ; , distance |= 8); + REV_BIT_CONST(prob, i, 1) + REV_BIT_CONST(prob, i, 2) + REV_BIT_CONST(prob, i, 4) + REV_BIT_LAST (prob, i, 8) + distance |= i; } if (distance == (UInt32)0xFFFFFFFF) { - len += kMatchSpecLenStart; + len = kMatchSpecLenStart; state -= kNumStates; break; } @@ -435,20 +533,14 @@ static int MY_FAST_CALL LzmaDec_DecodeReal(CLzmaDec *p, SizeT limit, const Byte rep2 = rep1; rep1 = rep0; rep0 = distance + 1; - if (checkDicSize == 0) - { - if (distance >= processedPos) - { - p->dicPos = dicPos; - return SZ_ERROR_DATA; - } - } - else if (distance >= checkDicSize) + state = (state < kNumStates + kNumLitStates) ? kNumLitStates : kNumLitStates + 3; + if (distance >= (checkDicSize == 0 ? processedPos: checkDicSize)) { - p->dicPos = dicPos; - return SZ_ERROR_DATA; + len += kMatchSpecLen_Error_Data + kMatchMinLen; + // len = kMatchSpecLen_Error_Data; + // len += kMatchMinLen; + break; } - state = (state < kNumStates + kNumLitStates) ? kNumLitStates : kNumLitStates + 3; } len += kMatchMinLen; @@ -460,14 +552,19 @@ static int MY_FAST_CALL LzmaDec_DecodeReal(CLzmaDec *p, SizeT limit, const Byte if ((rem = limit - dicPos) == 0) { - p->dicPos = dicPos; - return SZ_ERROR_DATA; + /* + We stop decoding and return SZ_OK, and we can resume decoding later. + Any error conditions can be tested later in caller code. + For more strict mode we can stop decoding with error + // len += kMatchSpecLen_Error_Data; + */ + break; } curLen = ((rem < len) ? (unsigned)rem : len); pos = dicPos - rep0 + (dicPos < rep0 ? dicBufSize : 0); - processedPos += curLen; + processedPos += (UInt32)curLen; len -= curLen; if (curLen <= dicBufSize - pos) @@ -475,7 +572,7 @@ static int MY_FAST_CALL LzmaDec_DecodeReal(CLzmaDec *p, SizeT limit, const Byte Byte *dest = dic + dicPos; ptrdiff_t src = (ptrdiff_t)pos - (ptrdiff_t)dicPos; const Byte *lim = dest + curLen; - dicPos += curLen; + dicPos += (SizeT)curLen; do *(dest) = (Byte)*(dest + src); while (++dest != lim); @@ -495,113 +592,152 @@ static int MY_FAST_CALL LzmaDec_DecodeReal(CLzmaDec *p, SizeT limit, const Byte } while (dicPos < limit && buf < bufLimit); - NORMALIZE; + NORMALIZE p->buf = buf; p->range = range; p->code = code; - p->remainLen = len; + p->remainLen = (UInt32)len; // & (kMatchSpecLen_Error_Data - 1); // we can write real length for error matches too. p->dicPos = dicPos; p->processedPos = processedPos; p->reps[0] = rep0; p->reps[1] = rep1; p->reps[2] = rep2; p->reps[3] = rep3; - p->state = state; - + p->state = (UInt32)state; + if (len >= kMatchSpecLen_Error_Data) + return SZ_ERROR_DATA; return SZ_OK; } +#endif + + -static void MY_FAST_CALL LzmaDec_WriteRem(CLzmaDec *p, SizeT limit) +static void Z7_FASTCALL LzmaDec_WriteRem(CLzmaDec *p, SizeT limit) { - if (p->remainLen != 0 && p->remainLen < kMatchSpecLenStart) + unsigned len = (unsigned)p->remainLen; + if (len == 0 /* || len >= kMatchSpecLenStart */) + return; { - Byte *dic = p->dic; SizeT dicPos = p->dicPos; - SizeT dicBufSize = p->dicBufSize; - unsigned len = p->remainLen; - SizeT rep0 = p->reps[0]; /* we use SizeT to avoid the BUG of VC14 for AMD64 */ - SizeT rem = limit - dicPos; - if (rem < len) - len = (unsigned)(rem); + Byte *dic; + SizeT dicBufSize; + SizeT rep0; /* we use SizeT to avoid the BUG of VC14 for AMD64 */ + { + SizeT rem = limit - dicPos; + if (rem < len) + { + len = (unsigned)(rem); + if (len == 0) + return; + } + } if (p->checkDicSize == 0 && p->prop.dicSize - p->processedPos <= len) p->checkDicSize = p->prop.dicSize; - p->processedPos += len; - p->remainLen -= len; - while (len != 0) + p->processedPos += (UInt32)len; + p->remainLen -= (UInt32)len; + dic = p->dic; + rep0 = p->reps[0]; + dicBufSize = p->dicBufSize; + do { - len--; dic[dicPos] = dic[dicPos - rep0 + (dicPos < rep0 ? dicBufSize : 0)]; dicPos++; } + while (--len); p->dicPos = dicPos; } } -static int MY_FAST_CALL LzmaDec_DecodeReal2(CLzmaDec *p, SizeT limit, const Byte *bufLimit) + +/* +At staring of new stream we have one of the following symbols: + - Literal - is allowed + - Non-Rep-Match - is allowed only if it's end marker symbol + - Rep-Match - is not allowed +We use early check of (RangeCoder:Code) over kBadRepCode to simplify main decoding code +*/ + +#define kRange0 0xFFFFFFFF +#define kBound0 ((kRange0 >> kNumBitModelTotalBits) << (kNumBitModelTotalBits - 1)) +#define kBadRepCode (kBound0 + (((kRange0 - kBound0) >> kNumBitModelTotalBits) << (kNumBitModelTotalBits - 1))) +#if kBadRepCode != (0xC0000000 - 0x400) + #error Stop_Compiling_Bad_LZMA_Check +#endif + + +/* +LzmaDec_DecodeReal2(): + It calls LZMA_DECODE_REAL() and it adjusts limit according (p->checkDicSize). + +We correct (p->checkDicSize) after LZMA_DECODE_REAL() and in LzmaDec_WriteRem(), +and we support the following state of (p->checkDicSize): + if (total_processed < p->prop.dicSize) then + { + (total_processed == p->processedPos) + (p->checkDicSize == 0) + } + else + (p->checkDicSize == p->prop.dicSize) +*/ + +static int Z7_FASTCALL LzmaDec_DecodeReal2(CLzmaDec *p, SizeT limit, const Byte *bufLimit) { - do + if (p->checkDicSize == 0) { - SizeT limit2 = limit; - if (p->checkDicSize == 0) - { - UInt32 rem = p->prop.dicSize - p->processedPos; - if (limit - p->dicPos > rem) - limit2 = p->dicPos + rem; - } - - RINOK(LzmaDec_DecodeReal(p, limit2, bufLimit)); - + UInt32 rem = p->prop.dicSize - p->processedPos; + if (limit - p->dicPos > rem) + limit = p->dicPos + rem; + } + { + int res = LZMA_DECODE_REAL(p, limit, bufLimit); if (p->checkDicSize == 0 && p->processedPos >= p->prop.dicSize) p->checkDicSize = p->prop.dicSize; - - LzmaDec_WriteRem(p, limit); + return res; } - while (p->dicPos < limit && p->buf < bufLimit && p->remainLen < kMatchSpecLenStart); +} - if (p->remainLen > kMatchSpecLenStart) - p->remainLen = kMatchSpecLenStart; - return 0; -} typedef enum { - DUMMY_ERROR, /* unexpected end of input stream */ + DUMMY_INPUT_EOF, /* need more input data */ DUMMY_LIT, DUMMY_MATCH, DUMMY_REP } ELzmaDummy; -static ELzmaDummy LzmaDec_TryDummy(const CLzmaDec *p, const Byte *buf, SizeT inSize) + +#define IS_DUMMY_END_MARKER_POSSIBLE(dummyRes) ((dummyRes) == DUMMY_MATCH) + +static ELzmaDummy LzmaDec_TryDummy(const CLzmaDec *p, const Byte *buf, const Byte **bufOut) { UInt32 range = p->range; UInt32 code = p->code; - const Byte *bufLimit = buf + inSize; - const CLzmaProb *probs = p->probs; - unsigned state = p->state; + const Byte *bufLimit = *bufOut; + const CLzmaProb *probs = GET_PROBS; + unsigned state = (unsigned)p->state; ELzmaDummy res; + for (;;) { const CLzmaProb *prob; UInt32 bound; unsigned ttt; - unsigned posState = (p->processedPos) & ((1 << p->prop.pb) - 1); + unsigned posState = CALC_POS_STATE(p->processedPos, ((unsigned)1 << p->prop.pb) - 1); - prob = probs + IsMatch + (state << kNumPosBitsMax) + posState; + prob = probs + IsMatch + COMBINED_PS_STATE; IF_BIT_0_CHECK(prob) { UPDATE_0_CHECK - /* if (bufLimit - buf >= 7) return DUMMY_LIT; */ - prob = probs + Literal; if (p->checkDicSize != 0 || p->processedPos != 0) prob += ((UInt32)LZMA_LIT_SIZE * - ((((p->processedPos) & ((1 << (p->prop.lp)) - 1)) << p->prop.lc) + - (p->dic[(p->dicPos == 0 ? p->dicBufSize : p->dicPos) - 1] >> (8 - p->prop.lc)))); + ((((p->processedPos) & (((unsigned)1 << (p->prop.lp)) - 1)) << p->prop.lc) + + ((unsigned)p->dic[(p->dicPos == 0 ? p->dicBufSize : p->dicPos) - 1] >> (8 - p->prop.lc)))); if (state < kNumLitStates) { @@ -618,10 +754,11 @@ static ELzmaDummy LzmaDec_TryDummy(const CLzmaDec *p, const Byte *buf, SizeT inS { unsigned bit; const CLzmaProb *probLit; - matchByte <<= 1; - bit = (matchByte & offs); - probLit = prob + offs + bit + symbol; - GET_BIT2_CHECK(probLit, symbol, offs &= ~bit, offs &= bit) + matchByte += matchByte; + bit = offs; + offs &= matchByte; + probLit = prob + (offs + bit + symbol); + GET_BIT2_CHECK(probLit, symbol, offs ^= bit; , ; ) } while (symbol < 0x100); } @@ -630,55 +767,54 @@ static ELzmaDummy LzmaDec_TryDummy(const CLzmaDec *p, const Byte *buf, SizeT inS else { unsigned len; - UPDATE_1_CHECK; + UPDATE_1_CHECK prob = probs + IsRep + state; IF_BIT_0_CHECK(prob) { - UPDATE_0_CHECK; + UPDATE_0_CHECK state = 0; prob = probs + LenCoder; res = DUMMY_MATCH; } else { - UPDATE_1_CHECK; + UPDATE_1_CHECK res = DUMMY_REP; prob = probs + IsRepG0 + state; IF_BIT_0_CHECK(prob) { - UPDATE_0_CHECK; - prob = probs + IsRep0Long + (state << kNumPosBitsMax) + posState; + UPDATE_0_CHECK + prob = probs + IsRep0Long + COMBINED_PS_STATE; IF_BIT_0_CHECK(prob) { - UPDATE_0_CHECK; - NORMALIZE_CHECK; - return DUMMY_REP; + UPDATE_0_CHECK + break; } else { - UPDATE_1_CHECK; + UPDATE_1_CHECK } } else { - UPDATE_1_CHECK; + UPDATE_1_CHECK prob = probs + IsRepG1 + state; IF_BIT_0_CHECK(prob) { - UPDATE_0_CHECK; + UPDATE_0_CHECK } else { - UPDATE_1_CHECK; + UPDATE_1_CHECK prob = probs + IsRepG2 + state; IF_BIT_0_CHECK(prob) { - UPDATE_0_CHECK; + UPDATE_0_CHECK } else { - UPDATE_1_CHECK; + UPDATE_1_CHECK } } } @@ -690,31 +826,31 @@ static ELzmaDummy LzmaDec_TryDummy(const CLzmaDec *p, const Byte *buf, SizeT inS const CLzmaProb *probLen = prob + LenChoice; IF_BIT_0_CHECK(probLen) { - UPDATE_0_CHECK; - probLen = prob + LenLow + (posState << kLenNumLowBits); + UPDATE_0_CHECK + probLen = prob + LenLow + GET_LEN_STATE; offset = 0; limit = 1 << kLenNumLowBits; } else { - UPDATE_1_CHECK; + UPDATE_1_CHECK probLen = prob + LenChoice2; IF_BIT_0_CHECK(probLen) { - UPDATE_0_CHECK; - probLen = prob + LenMid + (posState << kLenNumMidBits); + UPDATE_0_CHECK + probLen = prob + LenLow + GET_LEN_STATE + (1 << kLenNumLowBits); offset = kLenNumLowSymbols; - limit = 1 << kLenNumMidBits; + limit = 1 << kLenNumLowBits; } else { - UPDATE_1_CHECK; + UPDATE_1_CHECK probLen = prob + LenHigh; - offset = kLenNumLowSymbols + kLenNumMidSymbols; + offset = kLenNumLowSymbols * 2; limit = 1 << kLenNumHighBits; } } - TREE_DECODE_CHECK(probLen, limit, len); + TREE_DECODE_CHECK(probLen, limit, len) len += offset; } @@ -722,18 +858,16 @@ static ELzmaDummy LzmaDec_TryDummy(const CLzmaDec *p, const Byte *buf, SizeT inS { unsigned posSlot; prob = probs + PosSlot + - ((len < kNumLenToPosStates ? len : kNumLenToPosStates - 1) << + ((len < kNumLenToPosStates - 1 ? len : kNumLenToPosStates - 1) << kNumPosSlotBits); - TREE_DECODE_CHECK(prob, 1 << kNumPosSlotBits, posSlot); + TREE_DECODE_CHECK(prob, 1 << kNumPosSlotBits, posSlot) if (posSlot >= kStartPosModelIndex) { unsigned numDirectBits = ((posSlot >> 1) - 1); - /* if (bufLimit - buf >= 8) return DUMMY_MATCH; */ - if (posSlot < kEndPosModelIndex) { - prob = probs + SpecPos + ((2 | (posSlot & 1)) << numDirectBits) - posSlot - 1; + prob = probs + SpecPos + ((2 | (posSlot & 1)) << numDirectBits); } else { @@ -745,41 +879,44 @@ static ELzmaDummy LzmaDec_TryDummy(const CLzmaDec *p, const Byte *buf, SizeT inS code -= range & (((code - range) >> 31) - 1); /* if (code >= range) code -= range; */ } - while (--numDirectBits != 0); + while (--numDirectBits); prob = probs + Align; numDirectBits = kNumAlignBits; } { unsigned i = 1; + unsigned m = 1; do { - GET_BIT_CHECK(prob + i, i); + REV_BIT_CHECK(prob, i, m) } - while (--numDirectBits != 0); + while (--numDirectBits); } } } } + break; } - NORMALIZE_CHECK; + NORMALIZE_CHECK + + *bufOut = buf; return res; } - -void LzmaDec_InitDicAndState(CLzmaDec *p, Bool initDic, Bool initState) +void LzmaDec_InitDicAndState(CLzmaDec *p, BoolInt initDic, BoolInt initState); +void LzmaDec_InitDicAndState(CLzmaDec *p, BoolInt initDic, BoolInt initState) { - p->needFlush = 1; - p->remainLen = 0; + p->remainLen = kMatchSpecLenStart + 1; p->tempBufSize = 0; if (initDic) { p->processedPos = 0; p->checkDicSize = 0; - p->needInitState = 1; + p->remainLen = kMatchSpecLenStart + 2; } if (initState) - p->needInitState = 1; + p->remainLen = kMatchSpecLenStart + 2; } void LzmaDec_Init(CLzmaDec *p) @@ -788,53 +925,96 @@ void LzmaDec_Init(CLzmaDec *p) LzmaDec_InitDicAndState(p, True, True); } -static void LzmaDec_InitStateReal(CLzmaDec *p) -{ - SizeT numProbs = LzmaProps_GetNumProbs(&p->prop); - SizeT i; - CLzmaProb *probs = p->probs; - for (i = 0; i < numProbs; i++) - probs[i] = kBitModelTotal >> 1; - p->reps[0] = p->reps[1] = p->reps[2] = p->reps[3] = 1; - p->state = 0; - p->needInitState = 0; -} + +/* +LZMA supports optional end_marker. +So the decoder can lookahead for one additional LZMA-Symbol to check end_marker. +That additional LZMA-Symbol can require up to LZMA_REQUIRED_INPUT_MAX bytes in input stream. +When the decoder reaches dicLimit, it looks (finishMode) parameter: + if (finishMode == LZMA_FINISH_ANY), the decoder doesn't lookahead + if (finishMode != LZMA_FINISH_ANY), the decoder lookahead, if end_marker is possible for current position + +When the decoder lookahead, and the lookahead symbol is not end_marker, we have two ways: + 1) Strict mode (default) : the decoder returns SZ_ERROR_DATA. + 2) The relaxed mode (alternative mode) : we could return SZ_OK, and the caller + must check (status) value. The caller can show the error, + if the end of stream is expected, and the (status) is noit + LZMA_STATUS_FINISHED_WITH_MARK or LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK. +*/ + + +#define RETURN_NOT_FINISHED_FOR_FINISH \ + *status = LZMA_STATUS_NOT_FINISHED; \ + return SZ_ERROR_DATA; // for strict mode + // return SZ_OK; // for relaxed mode + SRes LzmaDec_DecodeToDic(CLzmaDec *p, SizeT dicLimit, const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status) { SizeT inSize = *srcLen; (*srcLen) = 0; - LzmaDec_WriteRem(p, dicLimit); - *status = LZMA_STATUS_NOT_SPECIFIED; - while (p->remainLen != kMatchSpecLenStart) + if (p->remainLen > kMatchSpecLenStart) { - int checkEndMarkNow; + if (p->remainLen > kMatchSpecLenStart + 2) + return p->remainLen == kMatchSpecLen_Error_Fail ? SZ_ERROR_FAIL : SZ_ERROR_DATA; + + for (; inSize > 0 && p->tempBufSize < RC_INIT_SIZE; (*srcLen)++, inSize--) + p->tempBuf[p->tempBufSize++] = *src++; + if (p->tempBufSize != 0 && p->tempBuf[0] != 0) + return SZ_ERROR_DATA; + if (p->tempBufSize < RC_INIT_SIZE) + { + *status = LZMA_STATUS_NEEDS_MORE_INPUT; + return SZ_OK; + } + p->code = + ((UInt32)p->tempBuf[1] << 24) + | ((UInt32)p->tempBuf[2] << 16) + | ((UInt32)p->tempBuf[3] << 8) + | ((UInt32)p->tempBuf[4]); - if (p->needFlush) - { - for (; inSize > 0 && p->tempBufSize < RC_INIT_SIZE; (*srcLen)++, inSize--) - p->tempBuf[p->tempBufSize++] = *src++; - if (p->tempBufSize < RC_INIT_SIZE) - { - *status = LZMA_STATUS_NEEDS_MORE_INPUT; - return SZ_OK; - } - if (p->tempBuf[0] != 0) - return SZ_ERROR_DATA; - p->code = - ((UInt32)p->tempBuf[1] << 24) - | ((UInt32)p->tempBuf[2] << 16) - | ((UInt32)p->tempBuf[3] << 8) - | ((UInt32)p->tempBuf[4]); - p->range = 0xFFFFFFFF; - p->needFlush = 0; - p->tempBufSize = 0; - } + if (p->checkDicSize == 0 + && p->processedPos == 0 + && p->code >= kBadRepCode) + return SZ_ERROR_DATA; + + p->range = 0xFFFFFFFF; + p->tempBufSize = 0; + + if (p->remainLen > kMatchSpecLenStart + 1) + { + SizeT numProbs = LzmaProps_GetNumProbs(&p->prop); + SizeT i; + CLzmaProb *probs = p->probs; + for (i = 0; i < numProbs; i++) + probs[i] = kBitModelTotal >> 1; + p->reps[0] = p->reps[1] = p->reps[2] = p->reps[3] = 1; + p->state = 0; + } + + p->remainLen = 0; + } + + for (;;) + { + if (p->remainLen == kMatchSpecLenStart) + { + if (p->code != 0) + return SZ_ERROR_DATA; + *status = LZMA_STATUS_FINISHED_WITH_MARK; + return SZ_OK; + } + + LzmaDec_WriteRem(p, dicLimit); + + { + // (p->remainLen == 0 || p->dicPos == dicLimit) + + int checkEndMarkNow = 0; - checkEndMarkNow = 0; if (p->dicPos >= dicLimit) { if (p->remainLen == 0 && p->code == 0) @@ -849,92 +1029,174 @@ SRes LzmaDec_DecodeToDic(CLzmaDec *p, SizeT dicLimit, const Byte *src, SizeT *sr } if (p->remainLen != 0) { - *status = LZMA_STATUS_NOT_FINISHED; - return SZ_ERROR_DATA; + RETURN_NOT_FINISHED_FOR_FINISH } checkEndMarkNow = 1; } - if (p->needInitState) - LzmaDec_InitStateReal(p); - + // (p->remainLen == 0) + if (p->tempBufSize == 0) { - SizeT processed; const Byte *bufLimit; + int dummyProcessed = -1; + if (inSize < LZMA_REQUIRED_INPUT_MAX || checkEndMarkNow) { - int dummyRes = LzmaDec_TryDummy(p, src, inSize); - if (dummyRes == DUMMY_ERROR) + const Byte *bufOut = src + inSize; + + ELzmaDummy dummyRes = LzmaDec_TryDummy(p, src, &bufOut); + + if (dummyRes == DUMMY_INPUT_EOF) { - memcpy(p->tempBuf, src, inSize); - p->tempBufSize = (unsigned)inSize; + size_t i; + if (inSize >= LZMA_REQUIRED_INPUT_MAX) + break; (*srcLen) += inSize; + p->tempBufSize = (unsigned)inSize; + for (i = 0; i < inSize; i++) + p->tempBuf[i] = src[i]; *status = LZMA_STATUS_NEEDS_MORE_INPUT; return SZ_OK; } - if (checkEndMarkNow && dummyRes != DUMMY_MATCH) + + dummyProcessed = (int)(bufOut - src); + if ((unsigned)dummyProcessed > LZMA_REQUIRED_INPUT_MAX) + break; + + if (checkEndMarkNow && !IS_DUMMY_END_MARKER_POSSIBLE(dummyRes)) { - *status = LZMA_STATUS_NOT_FINISHED; - return SZ_ERROR_DATA; + unsigned i; + (*srcLen) += (unsigned)dummyProcessed; + p->tempBufSize = (unsigned)dummyProcessed; + for (i = 0; i < (unsigned)dummyProcessed; i++) + p->tempBuf[i] = src[i]; + // p->remainLen = kMatchSpecLen_Error_Data; + RETURN_NOT_FINISHED_FOR_FINISH } + bufLimit = src; + // we will decode only one iteration } else bufLimit = src + inSize - LZMA_REQUIRED_INPUT_MAX; + p->buf = src; - if (LzmaDec_DecodeReal2(p, dicLimit, bufLimit) != 0) - return SZ_ERROR_DATA; - processed = (SizeT)(p->buf - src); - (*srcLen) += processed; - src += processed; - inSize -= processed; + + { + int res = LzmaDec_DecodeReal2(p, dicLimit, bufLimit); + + SizeT processed = (SizeT)(p->buf - src); + + if (dummyProcessed < 0) + { + if (processed > inSize) + break; + } + else if ((unsigned)dummyProcessed != processed) + break; + + src += processed; + inSize -= processed; + (*srcLen) += processed; + + if (res != SZ_OK) + { + p->remainLen = kMatchSpecLen_Error_Data; + return SZ_ERROR_DATA; + } + } + continue; } - else + { - unsigned rem = p->tempBufSize, lookAhead = 0; - while (rem < LZMA_REQUIRED_INPUT_MAX && lookAhead < inSize) - p->tempBuf[rem++] = src[lookAhead++]; - p->tempBufSize = rem; + // we have some data in (p->tempBuf) + // in strict mode: tempBufSize is not enough for one Symbol decoding. + // in relaxed mode: tempBufSize not larger than required for one Symbol decoding. + + unsigned rem = p->tempBufSize; + unsigned ahead = 0; + int dummyProcessed = -1; + + while (rem < LZMA_REQUIRED_INPUT_MAX && ahead < inSize) + p->tempBuf[rem++] = src[ahead++]; + + // ahead - the size of new data copied from (src) to (p->tempBuf) + // rem - the size of temp buffer including new data from (src) + if (rem < LZMA_REQUIRED_INPUT_MAX || checkEndMarkNow) { - int dummyRes = LzmaDec_TryDummy(p, p->tempBuf, rem); - if (dummyRes == DUMMY_ERROR) + const Byte *bufOut = p->tempBuf + rem; + + ELzmaDummy dummyRes = LzmaDec_TryDummy(p, p->tempBuf, &bufOut); + + if (dummyRes == DUMMY_INPUT_EOF) { - (*srcLen) += lookAhead; + if (rem >= LZMA_REQUIRED_INPUT_MAX) + break; + p->tempBufSize = rem; + (*srcLen) += (SizeT)ahead; *status = LZMA_STATUS_NEEDS_MORE_INPUT; return SZ_OK; } - if (checkEndMarkNow && dummyRes != DUMMY_MATCH) + + dummyProcessed = (int)(bufOut - p->tempBuf); + + if ((unsigned)dummyProcessed < p->tempBufSize) + break; + + if (checkEndMarkNow && !IS_DUMMY_END_MARKER_POSSIBLE(dummyRes)) { - *status = LZMA_STATUS_NOT_FINISHED; - return SZ_ERROR_DATA; + (*srcLen) += (unsigned)dummyProcessed - p->tempBufSize; + p->tempBufSize = (unsigned)dummyProcessed; + // p->remainLen = kMatchSpecLen_Error_Data; + RETURN_NOT_FINISHED_FOR_FINISH } } + p->buf = p->tempBuf; - if (LzmaDec_DecodeReal2(p, dicLimit, p->buf) != 0) - return SZ_ERROR_DATA; { - unsigned kkk = (unsigned)(p->buf - p->tempBuf); - if (rem < kkk) - return SZ_ERROR_FAIL; /* some internal error */ - rem -= kkk; - if (lookAhead < rem) - return SZ_ERROR_FAIL; /* some internal error */ - lookAhead -= rem; + // we decode one symbol from (p->tempBuf) here, so the (bufLimit) is equal to (p->buf) + int res = LzmaDec_DecodeReal2(p, dicLimit, p->buf); + + SizeT processed = (SizeT)(p->buf - p->tempBuf); + rem = p->tempBufSize; + + if (dummyProcessed < 0) + { + if (processed > LZMA_REQUIRED_INPUT_MAX) + break; + if (processed < rem) + break; + } + else if ((unsigned)dummyProcessed != processed) + break; + + processed -= rem; + + src += processed; + inSize -= processed; + (*srcLen) += processed; + p->tempBufSize = 0; + + if (res != SZ_OK) + { + p->remainLen = kMatchSpecLen_Error_Data; + return SZ_ERROR_DATA; + } } - (*srcLen) += lookAhead; - src += lookAhead; - inSize -= lookAhead; - p->tempBufSize = 0; } + } } - if (p->code == 0) - *status = LZMA_STATUS_FINISHED_WITH_MARK; - return (p->code == 0) ? SZ_OK : SZ_ERROR_DATA; + + /* Some unexpected error: internal error of code, memory corruption or hardware failure */ + p->remainLen = kMatchSpecLen_Error_Fail; + return SZ_ERROR_FAIL; } + + SRes LzmaDec_DecodeToBuf(CLzmaDec *p, Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status) { SizeT outSize = *destLen; @@ -975,19 +1237,19 @@ SRes LzmaDec_DecodeToBuf(CLzmaDec *p, Byte *dest, SizeT *destLen, const Byte *sr } } -void LzmaDec_FreeProbs(CLzmaDec *p, ISzAlloc *alloc) +void LzmaDec_FreeProbs(CLzmaDec *p, ISzAllocPtr alloc) { - alloc->Free(alloc, p->probs); + ISzAlloc_Free(alloc, p->probs); p->probs = NULL; } -static void LzmaDec_FreeDict(CLzmaDec *p, ISzAlloc *alloc) +static void LzmaDec_FreeDict(CLzmaDec *p, ISzAllocPtr alloc) { - alloc->Free(alloc, p->dic); + ISzAlloc_Free(alloc, p->dic); p->dic = NULL; } -void LzmaDec_Free(CLzmaDec *p, ISzAlloc *alloc) +void LzmaDec_Free(CLzmaDec *p, ISzAllocPtr alloc) { LzmaDec_FreeProbs(p, alloc); LzmaDec_FreeDict(p, alloc); @@ -1011,49 +1273,50 @@ SRes LzmaProps_Decode(CLzmaProps *p, const Byte *data, unsigned size) if (d >= (9 * 5 * 5)) return SZ_ERROR_UNSUPPORTED; - p->lc = d % 9; + p->lc = (Byte)(d % 9); d /= 9; - p->pb = d / 5; - p->lp = d % 5; + p->pb = (Byte)(d / 5); + p->lp = (Byte)(d % 5); return SZ_OK; } -static SRes LzmaDec_AllocateProbs2(CLzmaDec *p, const CLzmaProps *propNew, ISzAlloc *alloc) +static SRes LzmaDec_AllocateProbs2(CLzmaDec *p, const CLzmaProps *propNew, ISzAllocPtr alloc) { UInt32 numProbs = LzmaProps_GetNumProbs(propNew); if (!p->probs || numProbs != p->numProbs) { LzmaDec_FreeProbs(p, alloc); - p->probs = (CLzmaProb *)alloc->Alloc(alloc, numProbs * sizeof(CLzmaProb)); - p->numProbs = numProbs; + p->probs = (CLzmaProb *)ISzAlloc_Alloc(alloc, numProbs * sizeof(CLzmaProb)); if (!p->probs) return SZ_ERROR_MEM; + p->probs_1664 = p->probs + 1664; + p->numProbs = numProbs; } return SZ_OK; } -SRes LzmaDec_AllocateProbs(CLzmaDec *p, const Byte *props, unsigned propsSize, ISzAlloc *alloc) +SRes LzmaDec_AllocateProbs(CLzmaDec *p, const Byte *props, unsigned propsSize, ISzAllocPtr alloc) { CLzmaProps propNew; - RINOK(LzmaProps_Decode(&propNew, props, propsSize)); - RINOK(LzmaDec_AllocateProbs2(p, &propNew, alloc)); + RINOK(LzmaProps_Decode(&propNew, props, propsSize)) + RINOK(LzmaDec_AllocateProbs2(p, &propNew, alloc)) p->prop = propNew; return SZ_OK; } -SRes LzmaDec_Allocate(CLzmaDec *p, const Byte *props, unsigned propsSize, ISzAlloc *alloc) +SRes LzmaDec_Allocate(CLzmaDec *p, const Byte *props, unsigned propsSize, ISzAllocPtr alloc) { CLzmaProps propNew; SizeT dicBufSize; - RINOK(LzmaProps_Decode(&propNew, props, propsSize)); - RINOK(LzmaDec_AllocateProbs2(p, &propNew, alloc)); + RINOK(LzmaProps_Decode(&propNew, props, propsSize)) + RINOK(LzmaDec_AllocateProbs2(p, &propNew, alloc)) { UInt32 dictSize = propNew.dicSize; SizeT mask = ((UInt32)1 << 12) - 1; if (dictSize >= ((UInt32)1 << 30)) mask = ((UInt32)1 << 22) - 1; - else if (dictSize >= ((UInt32)1 << 22)) mask = ((UInt32)1 << 20) - 1;; + else if (dictSize >= ((UInt32)1 << 22)) mask = ((UInt32)1 << 20) - 1; dicBufSize = ((SizeT)dictSize + mask) & ~mask; if (dicBufSize < dictSize) dicBufSize = dictSize; @@ -1062,7 +1325,7 @@ SRes LzmaDec_Allocate(CLzmaDec *p, const Byte *props, unsigned propsSize, ISzAll if (!p->dic || dicBufSize != p->dicBufSize) { LzmaDec_FreeDict(p, alloc); - p->dic = (Byte *)alloc->Alloc(alloc, dicBufSize); + p->dic = (Byte *)ISzAlloc_Alloc(alloc, dicBufSize); if (!p->dic) { LzmaDec_FreeProbs(p, alloc); @@ -1076,7 +1339,7 @@ SRes LzmaDec_Allocate(CLzmaDec *p, const Byte *props, unsigned propsSize, ISzAll SRes LzmaDecode(Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen, const Byte *propData, unsigned propSize, ELzmaFinishMode finishMode, - ELzmaStatus *status, ISzAlloc *alloc) + ELzmaStatus *status, ISzAllocPtr alloc) { CLzmaDec p; SRes res; @@ -1085,8 +1348,8 @@ SRes LzmaDecode(Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen, *status = LZMA_STATUS_NOT_SPECIFIED; if (inSize < RC_INIT_SIZE) return SZ_ERROR_INPUT_EOF; - LzmaDec_Construct(&p); - RINOK(LzmaDec_AllocateProbs(&p, propData, propSize, alloc)); + LzmaDec_CONSTRUCT(&p) + RINOK(LzmaDec_AllocateProbs(&p, propData, propSize, alloc)) p.dic = dest; p.dicBufSize = outSize; LzmaDec_Init(&p); diff --git a/src/plugins/7zip/7za/c/LzmaDec.h b/src/plugins/7zip/7za/c/LzmaDec.h index cc44daef..b0ce28fa 100644 --- a/src/plugins/7zip/7za/c/LzmaDec.h +++ b/src/plugins/7zip/7za/c/LzmaDec.h @@ -1,31 +1,36 @@ /* LzmaDec.h -- LZMA Decoder -2013-01-18 : Igor Pavlov : Public domain */ +2023-04-02 : Igor Pavlov : Public domain */ -#ifndef __LZMA_DEC_H -#define __LZMA_DEC_H +#ifndef ZIP7_INC_LZMA_DEC_H +#define ZIP7_INC_LZMA_DEC_H #include "7zTypes.h" EXTERN_C_BEGIN -/* #define _LZMA_PROB32 */ -/* _LZMA_PROB32 can increase the speed on some CPUs, +/* #define Z7_LZMA_PROB32 */ +/* Z7_LZMA_PROB32 can increase the speed on some CPUs, but memory usage for CLzmaDec::probs will be doubled in that case */ -#ifdef _LZMA_PROB32 -#define CLzmaProb UInt32 +typedef +#ifdef Z7_LZMA_PROB32 + UInt32 #else -#define CLzmaProb UInt16 + UInt16 #endif + CLzmaProb; /* ---------- LZMA Properties ---------- */ #define LZMA_PROPS_SIZE 5 -typedef struct _CLzmaProps +typedef struct { - unsigned lc, lp, pb; + Byte lc; + Byte lp; + Byte pb; + Byte _pad_; UInt32 dicSize; } CLzmaProps; @@ -47,32 +52,35 @@ SRes LzmaProps_Decode(CLzmaProps *p, const Byte *data, unsigned size); typedef struct { + /* Don't change this structure. ASM code can use it. */ CLzmaProps prop; CLzmaProb *probs; + CLzmaProb *probs_1664; Byte *dic; - const Byte *buf; - UInt32 range, code; - SizeT dicPos; SizeT dicBufSize; + SizeT dicPos; + const Byte *buf; + UInt32 range; + UInt32 code; UInt32 processedPos; UInt32 checkDicSize; - unsigned state; UInt32 reps[4]; - unsigned remainLen; - int needFlush; - int needInitState; + UInt32 state; + UInt32 remainLen; + UInt32 numProbs; unsigned tempBufSize; Byte tempBuf[LZMA_REQUIRED_INPUT_MAX]; } CLzmaDec; -#define LzmaDec_Construct(p) { (p)->dic = 0; (p)->probs = 0; } +#define LzmaDec_CONSTRUCT(p) { (p)->dic = NULL; (p)->probs = NULL; } +#define LzmaDec_Construct(p) LzmaDec_CONSTRUCT(p) void LzmaDec_Init(CLzmaDec *p); /* There are two types of LZMA streams: - 0) Stream with end mark. That end mark adds about 6 bytes to compressed size. - 1) Stream without end mark. You must know exact uncompressed size to decompress such stream. */ + - Stream with end mark. That end mark adds about 6 bytes to compressed size. + - Stream without end mark. You must know exact uncompressed size to decompress such stream. */ typedef enum { @@ -129,11 +137,11 @@ LzmaDec_Allocate* can return: SZ_ERROR_UNSUPPORTED - Unsupported properties */ -SRes LzmaDec_AllocateProbs(CLzmaDec *p, const Byte *props, unsigned propsSize, ISzAlloc *alloc); -void LzmaDec_FreeProbs(CLzmaDec *p, ISzAlloc *alloc); +SRes LzmaDec_AllocateProbs(CLzmaDec *p, const Byte *props, unsigned propsSize, ISzAllocPtr alloc); +void LzmaDec_FreeProbs(CLzmaDec *p, ISzAllocPtr alloc); -SRes LzmaDec_Allocate(CLzmaDec *state, const Byte *prop, unsigned propsSize, ISzAlloc *alloc); -void LzmaDec_Free(CLzmaDec *state, ISzAlloc *alloc); +SRes LzmaDec_Allocate(CLzmaDec *p, const Byte *props, unsigned propsSize, ISzAllocPtr alloc); +void LzmaDec_Free(CLzmaDec *p, ISzAllocPtr alloc); /* ---------- Dictionary Interface ---------- */ @@ -142,7 +150,7 @@ void LzmaDec_Free(CLzmaDec *state, ISzAlloc *alloc); You must work with CLzmaDec variables directly in this interface. STEPS: - LzmaDec_Constr() + LzmaDec_Construct() LzmaDec_Allocate() for (each new stream) { @@ -174,6 +182,7 @@ void LzmaDec_Free(CLzmaDec *state, ISzAlloc *alloc); LZMA_STATUS_NEEDS_MORE_INPUT LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK SZ_ERROR_DATA - Data error + SZ_ERROR_FAIL - Some unexpected error: internal error of code, memory corruption or hardware failure */ SRes LzmaDec_DecodeToDic(CLzmaDec *p, SizeT dicLimit, @@ -216,11 +225,12 @@ SRes LzmaDec_DecodeToBuf(CLzmaDec *p, Byte *dest, SizeT *destLen, SZ_ERROR_MEM - Memory allocation error SZ_ERROR_UNSUPPORTED - Unsupported properties SZ_ERROR_INPUT_EOF - It needs more bytes in input buffer (src). + SZ_ERROR_FAIL - Some unexpected error: internal error of code, memory corruption or hardware failure */ SRes LzmaDecode(Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen, const Byte *propData, unsigned propSize, ELzmaFinishMode finishMode, - ELzmaStatus *status, ISzAlloc *alloc); + ELzmaStatus *status, ISzAllocPtr alloc); EXTERN_C_END diff --git a/src/plugins/7zip/7za/c/LzmaEnc.c b/src/plugins/7zip/7za/c/LzmaEnc.c index 70df4569..60f1d21d 100644 --- a/src/plugins/7zip/7za/c/LzmaEnc.c +++ b/src/plugins/7zip/7za/c/LzmaEnc.c @@ -1,5 +1,5 @@ /* LzmaEnc.c -- LZMA Encoder -2016-05-16 : Igor Pavlov : Public domain */ +Igor Pavlov : Public domain */ #include "Precomp.h" @@ -12,31 +12,36 @@ #include #endif +#include "CpuArch.h" #include "LzmaEnc.h" #include "LzFind.h" -#ifndef _7ZIP_ST +#ifndef Z7_ST #include "LzFindMt.h" #endif +/* the following LzmaEnc_* declarations is internal LZMA interface for LZMA2 encoder */ + +SRes LzmaEnc_PrepareForLzma2(CLzmaEncHandle p, ISeqInStreamPtr inStream, UInt32 keepWindowSize, + ISzAllocPtr alloc, ISzAllocPtr allocBig); +SRes LzmaEnc_MemPrepare(CLzmaEncHandle p, const Byte *src, SizeT srcLen, + UInt32 keepWindowSize, ISzAllocPtr alloc, ISzAllocPtr allocBig); +SRes LzmaEnc_CodeOneMemBlock(CLzmaEncHandle p, BoolInt reInit, + Byte *dest, size_t *destLen, UInt32 desiredPackSize, UInt32 *unpackSize); +const Byte *LzmaEnc_GetCurBuf(CLzmaEncHandle p); +void LzmaEnc_Finish(CLzmaEncHandle p); +void LzmaEnc_SaveState(CLzmaEncHandle p); +void LzmaEnc_RestoreState(CLzmaEncHandle p); + #ifdef SHOW_STAT static unsigned g_STAT_OFFSET = 0; #endif -#define kMaxHistorySize ((UInt32)3 << 29) -/* #define kMaxHistorySize ((UInt32)7 << 29) */ - -#define kBlockSizeMax ((1 << LZMA_NUM_BLOCK_SIZE_BITS) - 1) - -#define kBlockSize (9 << 10) -#define kUnpackBlockSize (1 << 18) -#define kMatchArraySize (1 << 21) -#define kMatchRecordMaxSize ((LZMA_MATCH_LEN_MAX * 2 + 3) * LZMA_MATCH_LEN_MAX) +/* for good normalization speed we still reserve 256 MB before 4 GB range */ +#define kLzmaMaxHistorySize ((UInt32)15 << 28) -#define kNumMaxDirectBits (31) - -#define kNumTopBits 24 -#define kTopValue ((UInt32)1 << kNumTopBits) +// #define kNumTopBits 24 +#define kTopValue ((UInt32)1 << 24) #define kNumBitModelTotalBits 11 #define kBitModelTotal (1 << kNumBitModelTotalBits) @@ -45,7 +50,9 @@ static unsigned g_STAT_OFFSET = 0; #define kNumMoveReducingBits 4 #define kNumBitPriceShiftBits 4 -#define kBitPrice (1 << kNumBitPriceShiftBits) +// #define kBitPrice (1 << kNumBitPriceShiftBits) + +#define REP_LEN_COUNT 64 void LzmaEncProps_Init(CLzmaEncProps *p) { @@ -53,7 +60,11 @@ void LzmaEncProps_Init(CLzmaEncProps *p) p->dictSize = p->mc = 0; p->reduceSize = (UInt64)(Int64)-1; p->lc = p->lp = p->pb = p->algo = p->fb = p->btMode = p->numHashBytes = p->numThreads = -1; + p->numHashOutBits = 0; p->writeEndMark = 0; + p->affinityGroup = -1; + p->affinity = 0; + p->affinityInGroup = 0; } void LzmaEncProps_Normalize(CLzmaEncProps *p) @@ -62,30 +73,36 @@ void LzmaEncProps_Normalize(CLzmaEncProps *p) if (level < 0) level = 5; p->level = level; - if (p->dictSize == 0) p->dictSize = (level <= 5 ? (1 << (level * 2 + 14)) : (level == 6 ? (1 << 25) : (1 << 26))); + if (p->dictSize == 0) + p->dictSize = (unsigned)level <= 4 ? + (UInt32)1 << (level * 2 + 16) : + (unsigned)level <= sizeof(size_t) / 2 + 4 ? + (UInt32)1 << (level + 20) : + (UInt32)1 << (sizeof(size_t) / 2 + 24); + if (p->dictSize > p->reduceSize) { - unsigned i; - for (i = 11; i <= 30; i++) - { - if ((UInt32)p->reduceSize <= ((UInt32)2 << i)) { p->dictSize = ((UInt32)2 << i); break; } - if ((UInt32)p->reduceSize <= ((UInt32)3 << i)) { p->dictSize = ((UInt32)3 << i); break; } - } + UInt32 v = (UInt32)p->reduceSize; + const UInt32 kReduceMin = ((UInt32)1 << 12); + if (v < kReduceMin) + v = kReduceMin; + if (p->dictSize > v) + p->dictSize = v; } if (p->lc < 0) p->lc = 3; if (p->lp < 0) p->lp = 0; if (p->pb < 0) p->pb = 2; - if (p->algo < 0) p->algo = (level < 5 ? 0 : 1); - if (p->fb < 0) p->fb = (level < 7 ? 32 : 64); + if (p->algo < 0) p->algo = (unsigned)level < 5 ? 0 : 1; + if (p->fb < 0) p->fb = (unsigned)level < 7 ? 32 : 64; if (p->btMode < 0) p->btMode = (p->algo == 0 ? 0 : 1); - if (p->numHashBytes < 0) p->numHashBytes = 4; - if (p->mc == 0) p->mc = (16 + (p->fb >> 1)) >> (p->btMode ? 0 : 1); + if (p->numHashBytes < 0) p->numHashBytes = (p->btMode ? 4 : 5); + if (p->mc == 0) p->mc = (16 + ((unsigned)p->fb >> 1)) >> (p->btMode ? 0 : 1); if (p->numThreads < 0) p->numThreads = - #ifndef _7ZIP_ST + #ifndef Z7_ST ((p->btMode && p->algo) ? 2 : 1); #else 1; @@ -99,30 +116,97 @@ UInt32 LzmaEncProps_GetDictSize(const CLzmaEncProps *props2) return props.dictSize; } -#if (_MSC_VER >= 1400) -/* BSR code is fast for some new CPUs */ -/* #define LZMA_LOG_BSR */ + +/* +x86/x64: + +BSR: + IF (SRC == 0) ZF = 1, DEST is undefined; + AMD : DEST is unchanged; + IF (SRC != 0) ZF = 0; DEST is index of top non-zero bit + BSR is slow in some processors + +LZCNT: + IF (SRC == 0) CF = 1, DEST is size_in_bits_of_register(src) (32 or 64) + IF (SRC != 0) CF = 0, DEST = num_lead_zero_bits + IF (DEST == 0) ZF = 1; + +LZCNT works only in new processors starting from Haswell. +if LZCNT is not supported by processor, then it's executed as BSR. +LZCNT can be faster than BSR, if supported. +*/ + +// #define LZMA_LOG_BSR + +#if defined(MY_CPU_ARM_OR_ARM64) /* || defined(MY_CPU_X86_OR_AMD64) */ + + #if (defined(__clang__) && (__clang_major__ >= 6)) \ + || (defined(__GNUC__) && (__GNUC__ >= 6)) + #define LZMA_LOG_BSR + #elif defined(_MSC_VER) && (_MSC_VER >= 1300) + // #if defined(MY_CPU_ARM_OR_ARM64) + #define LZMA_LOG_BSR + // #endif + #endif #endif +// #include + #ifdef LZMA_LOG_BSR -#define kDicLogSizeMaxCompress 32 +#if defined(__clang__) \ + || defined(__GNUC__) + +/* + C code: : (30 - __builtin_clz(x)) + gcc9/gcc10 for x64 /x86 : 30 - (bsr(x) xor 31) + clang10 for x64 : 31 + (bsr(x) xor -32) +*/ + + #define MY_clz(x) ((unsigned)__builtin_clz(x)) + // __lzcnt32 + // __builtin_ia32_lzcnt_u32 + +#else // #if defined(_MSC_VER) + + #ifdef MY_CPU_ARM_OR_ARM64 + + #define MY_clz _CountLeadingZeros + + #else // if defined(MY_CPU_X86_OR_AMD64) + + // #define MY_clz __lzcnt // we can use lzcnt (unsupported by old CPU) + // _BitScanReverse code is not optimal for some MSVC compilers + #define BSR2_RET(pos, res) { unsigned long zz; _BitScanReverse(&zz, (pos)); zz--; \ + res = (zz + zz) + (pos >> zz); } + + #endif // MY_CPU_X86_OR_AMD64 + +#endif // _MSC_VER + + +#ifndef BSR2_RET + + #define BSR2_RET(pos, res) { unsigned zz = 30 - MY_clz(pos); \ + res = (zz + zz) + (pos >> zz); } + +#endif -#define BSR2_RET(pos, res) { unsigned long zz; _BitScanReverse(&zz, (pos)); res = (zz + zz) + ((pos >> (zz - 1)) & 1); } -static UInt32 GetPosSlot1(UInt32 pos) +unsigned GetPosSlot1(UInt32 pos); +unsigned GetPosSlot1(UInt32 pos) { - UInt32 res; - BSR2_RET(pos, res); + unsigned res; + BSR2_RET(pos, res) return res; } -#define GetPosSlot2(pos, res) { BSR2_RET(pos, res); } -#define GetPosSlot(pos, res) { if (pos < 2) res = pos; else BSR2_RET(pos, res); } +#define GetPosSlot2(pos, res) { BSR2_RET(pos, res) } +#define GetPosSlot(pos, res) { if (pos < 2) res = pos; else BSR2_RET(pos, res) } -#else -#define kNumLogBits (9 + sizeof(size_t) / 2) -/* #define kNumLogBits (11 + sizeof(size_t) / 8 * 3) */ +#else // ! LZMA_LOG_BSR + +#define kNumLogBits (11 + sizeof(size_t) / 8 * 3) #define kDicLogSizeMaxCompress ((kNumLogBits - 1) * 2 + 7) @@ -145,18 +229,18 @@ static void LzmaEnc_FastPosInit(Byte *g_FastPos) /* we can use ((limit - pos) >> 31) only if (pos < ((UInt32)1 << 31)) */ /* -#define BSR2_RET(pos, res) { UInt32 zz = 6 + ((kNumLogBits - 1) & \ +#define BSR2_RET(pos, res) { unsigned zz = 6 + ((kNumLogBits - 1) & \ (0 - (((((UInt32)1 << (kNumLogBits + 6)) - 1) - pos) >> 31))); \ res = p->g_FastPos[pos >> zz] + (zz * 2); } */ /* -#define BSR2_RET(pos, res) { UInt32 zz = 6 + ((kNumLogBits - 1) & \ +#define BSR2_RET(pos, res) { unsigned zz = 6 + ((kNumLogBits - 1) & \ (0 - (((((UInt32)1 << (kNumLogBits)) - 1) - (pos >> 6)) >> 31))); \ res = p->g_FastPos[pos >> zz] + (zz * 2); } */ -#define BSR2_RET(pos, res) { UInt32 zz = (pos < (1 << (kNumLogBits + 6))) ? 6 : 6 + kNumLogBits - 1; \ +#define BSR2_RET(pos, res) { unsigned zz = (pos < (1 << (kNumLogBits + 6))) ? 6 : 6 + kNumLogBits - 1; \ res = p->g_FastPos[pos >> zz] + (zz * 2); } /* @@ -167,55 +251,57 @@ static void LzmaEnc_FastPosInit(Byte *g_FastPos) #define GetPosSlot1(pos) p->g_FastPos[pos] #define GetPosSlot2(pos, res) { BSR2_RET(pos, res); } -#define GetPosSlot(pos, res) { if (pos < kNumFullDistances) res = p->g_FastPos[pos]; else BSR2_RET(pos, res); } +#define GetPosSlot(pos, res) { if (pos < kNumFullDistances) res = p->g_FastPos[pos & (kNumFullDistances - 1)]; else BSR2_RET(pos, res); } -#endif +#endif // LZMA_LOG_BSR #define LZMA_NUM_REPS 4 -typedef unsigned CState; +typedef UInt16 CState; +typedef UInt16 CExtra; typedef struct { UInt32 price; - CState state; - int prev1IsChar; - int prev2; - - UInt32 posPrev2; - UInt32 backPrev2; - - UInt32 posPrev; - UInt32 backPrev; - UInt32 backs[LZMA_NUM_REPS]; + CExtra extra; + // 0 : normal + // 1 : LIT : MATCH + // > 1 : MATCH (extra-1) : LIT : REP0 (len) + UInt32 len; + UInt32 dist; + UInt32 reps[LZMA_NUM_REPS]; } COptimal; -#define kNumOpts (1 << 12) + +// 18.06 +#define kNumOpts (1 << 11) +#define kPackReserve (kNumOpts * 8) +// #define kNumOpts (1 << 12) +// #define kPackReserve (1 + kNumOpts * 2) #define kNumLenToPosStates 4 #define kNumPosSlotBits 6 -#define kDicLogSizeMin 0 +// #define kDicLogSizeMin 0 #define kDicLogSizeMax 32 #define kDistTableSizeMax (kDicLogSizeMax * 2) - #define kNumAlignBits 4 #define kAlignTableSize (1 << kNumAlignBits) #define kAlignMask (kAlignTableSize - 1) #define kStartPosModelIndex 4 #define kEndPosModelIndex 14 -#define kNumPosModels (kEndPosModelIndex - kStartPosModelIndex) - #define kNumFullDistances (1 << (kEndPosModelIndex >> 1)) -#ifdef _LZMA_PROB32 -#define CLzmaProb UInt32 +typedef +#ifdef Z7_LZMA_PROB32 + UInt32 #else -#define CLzmaProb UInt16 + UInt16 #endif + CLzmaProb; #define LZMA_PB_MAX 4 #define LZMA_LC_MAX 8 @@ -223,15 +309,11 @@ typedef struct #define LZMA_NUM_PB_STATES_MAX (1 << LZMA_PB_MAX) - #define kLenNumLowBits 3 #define kLenNumLowSymbols (1 << kLenNumLowBits) -#define kLenNumMidBits 3 -#define kLenNumMidSymbols (1 << kLenNumMidBits) #define kLenNumHighBits 8 #define kLenNumHighSymbols (1 << kLenNumHighBits) - -#define kLenNumSymbolsTotal (kLenNumLowSymbols + kLenNumMidSymbols + kLenNumHighSymbols) +#define kLenNumSymbolsTotal (kLenNumLowSymbols * 2 + kLenNumHighSymbols) #define LZMA_MATCH_LEN_MIN 2 #define LZMA_MATCH_LEN_MAX (LZMA_MATCH_LEN_MIN + kLenNumSymbolsTotal - 1) @@ -241,33 +323,37 @@ typedef struct typedef struct { - CLzmaProb choice; - CLzmaProb choice2; - CLzmaProb low[LZMA_NUM_PB_STATES_MAX << kLenNumLowBits]; - CLzmaProb mid[LZMA_NUM_PB_STATES_MAX << kLenNumMidBits]; + CLzmaProb low[LZMA_NUM_PB_STATES_MAX << (kLenNumLowBits + 1)]; CLzmaProb high[kLenNumHighSymbols]; } CLenEnc; typedef struct { - CLenEnc p; - UInt32 tableSize; + unsigned tableSize; UInt32 prices[LZMA_NUM_PB_STATES_MAX][kLenNumSymbolsTotal]; - UInt32 counters[LZMA_NUM_PB_STATES_MAX]; + // UInt32 prices1[LZMA_NUM_PB_STATES_MAX][kLenNumLowSymbols * 2]; + // UInt32 prices2[kLenNumSymbolsTotal]; } CLenPriceEnc; +#define GET_PRICE_LEN(p, posState, len) \ + ((p)->prices[posState][(size_t)(len) - LZMA_MATCH_LEN_MIN]) + +/* +#define GET_PRICE_LEN(p, posState, len) \ + ((p)->prices2[(size_t)(len) - 2] + ((p)->prices1[posState][((len) - 2) & (kLenNumLowSymbols * 2 - 1)] & (((len) - 2 - kLenNumLowSymbols * 2) >> 9))) +*/ typedef struct { UInt32 range; - Byte cache; + unsigned cache; UInt64 low; UInt64 cacheSize; Byte *buf; Byte *bufLim; Byte *bufBase; - ISeqOutStream *outStream; + ISeqOutStreamPtr outStream; UInt64 processed; SRes res; } CRangeEnc; @@ -277,208 +363,233 @@ typedef struct { CLzmaProb *litProbs; - UInt32 state; + unsigned state; UInt32 reps[LZMA_NUM_REPS]; - CLzmaProb isMatch[kNumStates][LZMA_NUM_PB_STATES_MAX]; + CLzmaProb posAlignEncoder[1 << kNumAlignBits]; CLzmaProb isRep[kNumStates]; CLzmaProb isRepG0[kNumStates]; CLzmaProb isRepG1[kNumStates]; CLzmaProb isRepG2[kNumStates]; + CLzmaProb isMatch[kNumStates][LZMA_NUM_PB_STATES_MAX]; CLzmaProb isRep0Long[kNumStates][LZMA_NUM_PB_STATES_MAX]; CLzmaProb posSlotEncoder[kNumLenToPosStates][1 << kNumPosSlotBits]; - CLzmaProb posEncoders[kNumFullDistances - kEndPosModelIndex]; - CLzmaProb posAlignEncoder[1 << kNumAlignBits]; + CLzmaProb posEncoders[kNumFullDistances]; - CLenPriceEnc lenEnc; - CLenPriceEnc repLenEnc; + CLenEnc lenProbs; + CLenEnc repLenProbs; + } CSaveState; -typedef struct +typedef UInt32 CProbPrice; + + +struct CLzmaEnc { void *matchFinderObj; - IMatchFinder matchFinder; + IMatchFinder2 matchFinder; - UInt32 optimumEndIndex; - UInt32 optimumCurrentIndex; + unsigned optCur; + unsigned optEnd; - UInt32 longestMatchLength; - UInt32 numPairs; + unsigned longestMatchLen; + unsigned numPairs; UInt32 numAvail; - UInt32 numFastBytes; - UInt32 additionalOffset; + unsigned state; + unsigned numFastBytes; + unsigned additionalOffset; UInt32 reps[LZMA_NUM_REPS]; - UInt32 state; + unsigned lpMask, pbMask; + CLzmaProb *litProbs; + CRangeEnc rc; + + UInt32 backRes; unsigned lc, lp, pb; - unsigned lpMask, pbMask; unsigned lclp; - CLzmaProb *litProbs; - - Bool fastMode; - Bool writeEndMark; - Bool finished; - Bool multiThread; - Bool needInit; + BoolInt fastMode; + BoolInt writeEndMark; + BoolInt finished; + BoolInt multiThread; + BoolInt needInit; + // BoolInt _maxMode; UInt64 nowPos64; - UInt32 matchPriceCount; - UInt32 alignPriceCount; + unsigned matchPriceCount; + // unsigned alignPriceCount; + int repLenEncCounter; - UInt32 distTableSize; + unsigned distTableSize; UInt32 dictSize; SRes result; - CRangeEnc rc; - - #ifndef _7ZIP_ST - Bool mtMode; + #ifndef Z7_ST + BoolInt mtMode; + // begin of CMatchFinderMt is used in LZ thread CMatchFinderMt matchFinderMt; + // end of CMatchFinderMt is used in BT and HASH threads + // #else + // CMatchFinder matchFinderBase; #endif - CMatchFinder matchFinderBase; - #ifndef _7ZIP_ST + + // we suppose that we have 8-bytes alignment after CMatchFinder + + #ifndef Z7_ST Byte pad[128]; #endif - COptimal opt[kNumOpts]; - - #ifndef LZMA_LOG_BSR - Byte g_FastPos[1 << kNumLogBits]; - #endif + // LZ thread + CProbPrice ProbPrices[kBitModelTotal >> kNumMoveReducingBits]; - UInt32 ProbPrices[kBitModelTotal >> kNumMoveReducingBits]; - UInt32 matches[LZMA_MATCH_LEN_MAX * 2 + 2 + 1]; + // we want {len , dist} pairs to be 8-bytes aligned in matches array + UInt32 matches[LZMA_MATCH_LEN_MAX * 2 + 2]; + // we want 8-bytes alignment here + UInt32 alignPrices[kAlignTableSize]; UInt32 posSlotPrices[kNumLenToPosStates][kDistTableSizeMax]; UInt32 distancesPrices[kNumLenToPosStates][kNumFullDistances]; - UInt32 alignPrices[kAlignTableSize]; - CLzmaProb isMatch[kNumStates][LZMA_NUM_PB_STATES_MAX]; + CLzmaProb posAlignEncoder[1 << kNumAlignBits]; CLzmaProb isRep[kNumStates]; CLzmaProb isRepG0[kNumStates]; CLzmaProb isRepG1[kNumStates]; CLzmaProb isRepG2[kNumStates]; + CLzmaProb isMatch[kNumStates][LZMA_NUM_PB_STATES_MAX]; CLzmaProb isRep0Long[kNumStates][LZMA_NUM_PB_STATES_MAX]; - CLzmaProb posSlotEncoder[kNumLenToPosStates][1 << kNumPosSlotBits]; - CLzmaProb posEncoders[kNumFullDistances - kEndPosModelIndex]; - CLzmaProb posAlignEncoder[1 << kNumAlignBits]; + CLzmaProb posEncoders[kNumFullDistances]; + CLenEnc lenProbs; + CLenEnc repLenProbs; + + #ifndef LZMA_LOG_BSR + Byte g_FastPos[1 << kNumLogBits]; + #endif + CLenPriceEnc lenEnc; CLenPriceEnc repLenEnc; + COptimal opt[kNumOpts]; + CSaveState saveState; - #ifndef _7ZIP_ST + // BoolInt mf_Failure; + #ifndef Z7_ST Byte pad2[128]; #endif -} CLzmaEnc; +}; -void LzmaEnc_SaveState(CLzmaEncHandle pp) -{ - CLzmaEnc *p = (CLzmaEnc *)pp; - CSaveState *dest = &p->saveState; - int i; - dest->lenEnc = p->lenEnc; - dest->repLenEnc = p->repLenEnc; - dest->state = p->state; +#define MFB (p->matchFinderBase) +/* +#ifndef Z7_ST +#define MFB (p->matchFinderMt.MatchFinder) +#endif +*/ - for (i = 0; i < kNumStates; i++) - { - memcpy(dest->isMatch[i], p->isMatch[i], sizeof(p->isMatch[i])); - memcpy(dest->isRep0Long[i], p->isRep0Long[i], sizeof(p->isRep0Long[i])); - } - for (i = 0; i < kNumLenToPosStates; i++) - memcpy(dest->posSlotEncoder[i], p->posSlotEncoder[i], sizeof(p->posSlotEncoder[i])); - memcpy(dest->isRep, p->isRep, sizeof(p->isRep)); - memcpy(dest->isRepG0, p->isRepG0, sizeof(p->isRepG0)); - memcpy(dest->isRepG1, p->isRepG1, sizeof(p->isRepG1)); - memcpy(dest->isRepG2, p->isRepG2, sizeof(p->isRepG2)); - memcpy(dest->posEncoders, p->posEncoders, sizeof(p->posEncoders)); - memcpy(dest->posAlignEncoder, p->posAlignEncoder, sizeof(p->posAlignEncoder)); - memcpy(dest->reps, p->reps, sizeof(p->reps)); - memcpy(dest->litProbs, p->litProbs, ((UInt32)0x300 << p->lclp) * sizeof(CLzmaProb)); -} +// #define GET_CLzmaEnc_p CLzmaEnc *p = (CLzmaEnc*)(void *)p; +// #define GET_const_CLzmaEnc_p const CLzmaEnc *p = (const CLzmaEnc*)(const void *)p; + +#define COPY_ARR(dest, src, arr) memcpy((dest)->arr, (src)->arr, sizeof((src)->arr)); -void LzmaEnc_RestoreState(CLzmaEncHandle pp) +#define COPY_LZMA_ENC_STATE(d, s, p) \ + (d)->state = (s)->state; \ + COPY_ARR(d, s, reps) \ + COPY_ARR(d, s, posAlignEncoder) \ + COPY_ARR(d, s, isRep) \ + COPY_ARR(d, s, isRepG0) \ + COPY_ARR(d, s, isRepG1) \ + COPY_ARR(d, s, isRepG2) \ + COPY_ARR(d, s, isMatch) \ + COPY_ARR(d, s, isRep0Long) \ + COPY_ARR(d, s, posSlotEncoder) \ + COPY_ARR(d, s, posEncoders) \ + (d)->lenProbs = (s)->lenProbs; \ + (d)->repLenProbs = (s)->repLenProbs; \ + memcpy((d)->litProbs, (s)->litProbs, ((size_t)0x300 * sizeof(CLzmaProb)) << (p)->lclp); + +void LzmaEnc_SaveState(CLzmaEncHandle p) { - CLzmaEnc *dest = (CLzmaEnc *)pp; - const CSaveState *p = &dest->saveState; - int i; - dest->lenEnc = p->lenEnc; - dest->repLenEnc = p->repLenEnc; - dest->state = p->state; + // GET_CLzmaEnc_p + CSaveState *v = &p->saveState; + COPY_LZMA_ENC_STATE(v, p, p) +} - for (i = 0; i < kNumStates; i++) - { - memcpy(dest->isMatch[i], p->isMatch[i], sizeof(p->isMatch[i])); - memcpy(dest->isRep0Long[i], p->isRep0Long[i], sizeof(p->isRep0Long[i])); - } - for (i = 0; i < kNumLenToPosStates; i++) - memcpy(dest->posSlotEncoder[i], p->posSlotEncoder[i], sizeof(p->posSlotEncoder[i])); - memcpy(dest->isRep, p->isRep, sizeof(p->isRep)); - memcpy(dest->isRepG0, p->isRepG0, sizeof(p->isRepG0)); - memcpy(dest->isRepG1, p->isRepG1, sizeof(p->isRepG1)); - memcpy(dest->isRepG2, p->isRepG2, sizeof(p->isRepG2)); - memcpy(dest->posEncoders, p->posEncoders, sizeof(p->posEncoders)); - memcpy(dest->posAlignEncoder, p->posAlignEncoder, sizeof(p->posAlignEncoder)); - memcpy(dest->reps, p->reps, sizeof(p->reps)); - memcpy(dest->litProbs, p->litProbs, ((UInt32)0x300 << dest->lclp) * sizeof(CLzmaProb)); +void LzmaEnc_RestoreState(CLzmaEncHandle p) +{ + // GET_CLzmaEnc_p + const CSaveState *v = &p->saveState; + COPY_LZMA_ENC_STATE(p, v, p) } -SRes LzmaEnc_SetProps(CLzmaEncHandle pp, const CLzmaEncProps *props2) + +Z7_NO_INLINE +SRes LzmaEnc_SetProps(CLzmaEncHandle p, const CLzmaEncProps *props2) { - CLzmaEnc *p = (CLzmaEnc *)pp; + // GET_CLzmaEnc_p CLzmaEncProps props = *props2; LzmaEncProps_Normalize(&props); if (props.lc > LZMA_LC_MAX || props.lp > LZMA_LP_MAX - || props.pb > LZMA_PB_MAX - || props.dictSize > ((UInt64)1 << kDicLogSizeMaxCompress) - || props.dictSize > kMaxHistorySize) + || props.pb > LZMA_PB_MAX) return SZ_ERROR_PARAM; + + if (props.dictSize > kLzmaMaxHistorySize) + props.dictSize = kLzmaMaxHistorySize; + + #ifndef LZMA_LOG_BSR + { + const UInt64 dict64 = props.dictSize; + if (dict64 > ((UInt64)1 << kDicLogSizeMaxCompress)) + return SZ_ERROR_PARAM; + } + #endif + p->dictSize = props.dictSize; { - unsigned fb = props.fb; + unsigned fb = (unsigned)props.fb; if (fb < 5) fb = 5; if (fb > LZMA_MATCH_LEN_MAX) fb = LZMA_MATCH_LEN_MAX; p->numFastBytes = fb; } - p->lc = props.lc; - p->lp = props.lp; - p->pb = props.pb; + p->lc = (unsigned)props.lc; + p->lp = (unsigned)props.lp; + p->pb = (unsigned)props.pb; p->fastMode = (props.algo == 0); - p->matchFinderBase.btMode = (Byte)(props.btMode ? 1 : 0); + // p->_maxMode = True; + MFB.btMode = (Byte)(props.btMode ? 1 : 0); + // MFB.btMode = (Byte)(props.btMode); { - UInt32 numHashBytes = 4; + unsigned numHashBytes = 4; if (props.btMode) { - if (props.numHashBytes < 2) - numHashBytes = 2; - else if (props.numHashBytes < 4) - numHashBytes = props.numHashBytes; + if (props.numHashBytes < 2) numHashBytes = 2; + else if (props.numHashBytes < 4) numHashBytes = (unsigned)props.numHashBytes; } - p->matchFinderBase.numHashBytes = numHashBytes; + if (props.numHashBytes >= 5) numHashBytes = 5; + + MFB.numHashBytes = numHashBytes; + // MFB.numHashBytes_Min = 2; + MFB.numHashOutBits = (Byte)props.numHashOutBits; } - p->matchFinderBase.cutValue = props.mc; + MFB.cutValue = props.mc; - p->writeEndMark = props.writeEndMark; + p->writeEndMark = (BoolInt)props.writeEndMark; - #ifndef _7ZIP_ST + #ifndef Z7_ST /* if (newMultiThread != _multiThread) { @@ -487,18 +598,38 @@ SRes LzmaEnc_SetProps(CLzmaEncHandle pp, const CLzmaEncProps *props2) } */ p->multiThread = (props.numThreads > 1); + p->matchFinderMt.btSync.affinity = + p->matchFinderMt.hashSync.affinity = props.affinity; + p->matchFinderMt.btSync.affinityGroup = + p->matchFinderMt.hashSync.affinityGroup = props.affinityGroup; + p->matchFinderMt.btSync.affinityInGroup = + p->matchFinderMt.hashSync.affinityInGroup = props.affinityInGroup; #endif return SZ_OK; } -static const int kLiteralNextStates[kNumStates] = {0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 4, 5}; -static const int kMatchNextStates[kNumStates] = {7, 7, 7, 7, 7, 7, 7, 10, 10, 10, 10, 10}; -static const int kRepNextStates[kNumStates] = {8, 8, 8, 8, 8, 8, 8, 11, 11, 11, 11, 11}; -static const int kShortRepNextStates[kNumStates]= {9, 9, 9, 9, 9, 9, 9, 11, 11, 11, 11, 11}; -#define IsCharState(s) ((s) < 7) +void LzmaEnc_SetDataSize(CLzmaEncHandle p, UInt64 expectedDataSiize) +{ + // GET_CLzmaEnc_p + MFB.expectedDataSize = expectedDataSiize; +} + + +#define kState_Start 0 +#define kState_LitAfterMatch 4 +#define kState_LitAfterRep 5 +#define kState_MatchAfterLit 7 +#define kState_RepAfterLit 8 + +static const Byte kLiteralNextStates[kNumStates] = {0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 4, 5}; +static const Byte kMatchNextStates[kNumStates] = {7, 7, 7, 7, 7, 7, 7, 10, 10, 10, 10, 10}; +static const Byte kRepNextStates[kNumStates] = {8, 8, 8, 8, 8, 8, 8, 11, 11, 11, 11, 11}; +static const Byte kShortRepNextStates[kNumStates]= {9, 9, 9, 9, 9, 9, 9, 11, 11, 11, 11, 11}; +#define IsLitState(s) ((s) < 7) +#define GetLenToPosState2(len) (((len) < kNumLenToPosStates - 1) ? (len) : kNumLenToPosStates - 1) #define GetLenToPosState(len) (((len) < kNumLenToPosStates + 1) ? (len) - 2 : kNumLenToPosStates - 1) #define kInfinityPrice (1 << 30) @@ -509,14 +640,16 @@ static void RangeEnc_Construct(CRangeEnc *p) p->bufBase = NULL; } -#define RangeEnc_GetProcessed(p) ((p)->processed + ((p)->buf - (p)->bufBase) + (p)->cacheSize) +#define RangeEnc_GetProcessed(p) ( (p)->processed + (size_t)((p)->buf - (p)->bufBase) + (p)->cacheSize) +#define RangeEnc_GetProcessed_sizet(p) ((size_t)(p)->processed + (size_t)((p)->buf - (p)->bufBase) + (size_t)(p)->cacheSize) #define RC_BUF_SIZE (1 << 16) -static int RangeEnc_Alloc(CRangeEnc *p, ISzAlloc *alloc) + +static int RangeEnc_Alloc(CRangeEnc *p, ISzAllocPtr alloc) { if (!p->bufBase) { - p->bufBase = (Byte *)alloc->Alloc(alloc, RC_BUF_SIZE); + p->bufBase = (Byte *)ISzAlloc_Alloc(alloc, RC_BUF_SIZE); if (!p->bufBase) return 0; p->bufLim = p->bufBase + RC_BUF_SIZE; @@ -524,19 +657,18 @@ static int RangeEnc_Alloc(CRangeEnc *p, ISzAlloc *alloc) return 1; } -static void RangeEnc_Free(CRangeEnc *p, ISzAlloc *alloc) +static void RangeEnc_Free(CRangeEnc *p, ISzAllocPtr alloc) { - alloc->Free(alloc, p->bufBase); - p->bufBase = 0; + ISzAlloc_Free(alloc, p->bufBase); + p->bufBase = NULL; } static void RangeEnc_Init(CRangeEnc *p) { - /* Stream.Init(); */ - p->low = 0; p->range = 0xFFFFFFFF; - p->cacheSize = 1; p->cache = 0; + p->low = 0; + p->cacheSize = 0; p->buf = p->bufBase; @@ -544,37 +676,48 @@ static void RangeEnc_Init(CRangeEnc *p) p->res = SZ_OK; } -static void RangeEnc_FlushStream(CRangeEnc *p) +Z7_NO_INLINE static void RangeEnc_FlushStream(CRangeEnc *p) { - size_t num; - if (p->res != SZ_OK) - return; - num = p->buf - p->bufBase; - if (num != p->outStream->Write(p->outStream, p->bufBase, num)) - p->res = SZ_ERROR_WRITE; + const size_t num = (size_t)(p->buf - p->bufBase); + if (p->res == SZ_OK) + { + if (num != ISeqOutStream_Write(p->outStream, p->bufBase, num)) + p->res = SZ_ERROR_WRITE; + } p->processed += num; p->buf = p->bufBase; } -static void MY_FAST_CALL RangeEnc_ShiftLow(CRangeEnc *p) +Z7_NO_INLINE static void Z7_FASTCALL RangeEnc_ShiftLow(CRangeEnc *p) { - if ((UInt32)p->low < (UInt32)0xFF000000 || (unsigned)(p->low >> 32) != 0) + UInt32 low = (UInt32)p->low; + unsigned high = (unsigned)(p->low >> 32); + p->low = (UInt32)(low << 8); + if (low < (UInt32)0xFF000000 || high != 0) { - Byte temp = p->cache; - do { Byte *buf = p->buf; - *buf++ = (Byte)(temp + (Byte)(p->low >> 32)); + *buf++ = (Byte)(p->cache + high); + p->cache = (unsigned)(low >> 24); + p->buf = buf; + if (buf == p->bufLim) + RangeEnc_FlushStream(p); + if (p->cacheSize == 0) + return; + } + high += 0xFF; + for (;;) + { + Byte *buf = p->buf; + *buf++ = (Byte)(high); p->buf = buf; if (buf == p->bufLim) RangeEnc_FlushStream(p); - temp = 0xFF; + if (--p->cacheSize == 0) + return; } - while (--p->cacheSize != 0); - p->cache = (Byte)((UInt32)p->low >> 24); } p->cacheSize++; - p->low = (UInt32)p->low << 8; } static void RangeEnc_FlushData(CRangeEnc *p) @@ -584,78 +727,121 @@ static void RangeEnc_FlushData(CRangeEnc *p) RangeEnc_ShiftLow(p); } -static void RangeEnc_EncodeDirectBits(CRangeEnc *p, UInt32 value, unsigned numBits) -{ - do - { - p->range >>= 1; - p->low += p->range & (0 - ((value >> --numBits) & 1)); - if (p->range < kTopValue) - { - p->range <<= 8; - RangeEnc_ShiftLow(p); - } - } - while (numBits != 0); -} +#define RC_NORM(p) if (range < kTopValue) { range <<= 8; RangeEnc_ShiftLow(p); } -static void RangeEnc_EncodeBit(CRangeEnc *p, CLzmaProb *prob, UInt32 symbol) -{ - UInt32 ttt = *prob; - UInt32 newBound = (p->range >> kNumBitModelTotalBits) * ttt; - if (symbol == 0) - { - p->range = newBound; - ttt += (kBitModelTotal - ttt) >> kNumMoveBits; - } - else - { - p->low += newBound; - p->range -= newBound; - ttt -= ttt >> kNumMoveBits; +#define RC_BIT_PRE(p, prob) \ + ttt = *(prob); \ + newBound = (range >> kNumBitModelTotalBits) * ttt; + +// #define Z7_LZMA_ENC_USE_BRANCH + +#ifdef Z7_LZMA_ENC_USE_BRANCH + +#define RC_BIT(p, prob, bit) { \ + RC_BIT_PRE(p, prob) \ + if (bit == 0) { range = newBound; ttt += (kBitModelTotal - ttt) >> kNumMoveBits; } \ + else { (p)->low += newBound; range -= newBound; ttt -= ttt >> kNumMoveBits; } \ + *(prob) = (CLzmaProb)ttt; \ + RC_NORM(p) \ } - *prob = (CLzmaProb)ttt; - if (p->range < kTopValue) - { - p->range <<= 8; - RangeEnc_ShiftLow(p); + +#else + +#define RC_BIT(p, prob, bit) { \ + UInt32 mask; \ + RC_BIT_PRE(p, prob) \ + mask = 0 - (UInt32)bit; \ + range &= mask; \ + mask &= newBound; \ + range -= mask; \ + (p)->low += mask; \ + mask = (UInt32)bit - 1; \ + range += newBound & mask; \ + mask &= (kBitModelTotal - ((1 << kNumMoveBits) - 1)); \ + mask += ((1 << kNumMoveBits) - 1); \ + ttt += (UInt32)((Int32)(mask - ttt) >> kNumMoveBits); \ + *(prob) = (CLzmaProb)ttt; \ + RC_NORM(p) \ } + +#endif + + + + +#define RC_BIT_0_BASE(p, prob) \ + range = newBound; *(prob) = (CLzmaProb)(ttt + ((kBitModelTotal - ttt) >> kNumMoveBits)); + +#define RC_BIT_1_BASE(p, prob) \ + range -= newBound; (p)->low += newBound; *(prob) = (CLzmaProb)(ttt - (ttt >> kNumMoveBits)); \ + +#define RC_BIT_0(p, prob) \ + RC_BIT_0_BASE(p, prob) \ + RC_NORM(p) + +#define RC_BIT_1(p, prob) \ + RC_BIT_1_BASE(p, prob) \ + RC_NORM(p) + +static void RangeEnc_EncodeBit_0(CRangeEnc *p, CLzmaProb *prob) +{ + UInt32 range, ttt, newBound; + range = p->range; + RC_BIT_PRE(p, prob) + RC_BIT_0(p, prob) + p->range = range; } -static void LitEnc_Encode(CRangeEnc *p, CLzmaProb *probs, UInt32 symbol) +static void LitEnc_Encode(CRangeEnc *p, CLzmaProb *probs, UInt32 sym) { - symbol |= 0x100; + UInt32 range = p->range; + sym |= 0x100; do { - RangeEnc_EncodeBit(p, probs + (symbol >> 8), (symbol >> 7) & 1); - symbol <<= 1; + UInt32 ttt, newBound; + // RangeEnc_EncodeBit(p, probs + (sym >> 8), (sym >> 7) & 1); + CLzmaProb *prob = probs + (sym >> 8); + UInt32 bit = (sym >> 7) & 1; + sym <<= 1; + RC_BIT(p, prob, bit) } - while (symbol < 0x10000); + while (sym < 0x10000); + p->range = range; } -static void LitEnc_EncodeMatched(CRangeEnc *p, CLzmaProb *probs, UInt32 symbol, UInt32 matchByte) +static void LitEnc_EncodeMatched(CRangeEnc *p, CLzmaProb *probs, UInt32 sym, UInt32 matchByte) { + UInt32 range = p->range; UInt32 offs = 0x100; - symbol |= 0x100; + sym |= 0x100; do { + UInt32 ttt, newBound; + CLzmaProb *prob; + UInt32 bit; matchByte <<= 1; - RangeEnc_EncodeBit(p, probs + (offs + (matchByte & offs) + (symbol >> 8)), (symbol >> 7) & 1); - symbol <<= 1; - offs &= ~(matchByte ^ symbol); + // RangeEnc_EncodeBit(p, probs + (offs + (matchByte & offs) + (sym >> 8)), (sym >> 7) & 1); + prob = probs + (offs + (matchByte & offs) + (sym >> 8)); + bit = (sym >> 7) & 1; + sym <<= 1; + offs &= ~(matchByte ^ sym); + RC_BIT(p, prob, bit) } - while (symbol < 0x10000); + while (sym < 0x10000); + p->range = range; } -static void LzmaEnc_InitPriceTables(UInt32 *ProbPrices) + + +static void LzmaEnc_InitPriceTables(CProbPrice *ProbPrices) { UInt32 i; - for (i = (1 << kNumMoveReducingBits) / 2; i < kBitModelTotal; i += (1 << kNumMoveReducingBits)) + for (i = 0; i < (kBitModelTotal >> kNumMoveReducingBits); i++) { - const int kCyclesBits = kNumBitPriceShiftBits; - UInt32 w = i; - UInt32 bitCount = 0; - int j; + const unsigned kCyclesBits = kNumBitPriceShiftBits; + UInt32 w = (i << kNumMoveReducingBits) + (1 << (kNumMoveReducingBits - 1)); + unsigned bitCount = 0; + unsigned j; for (j = 0; j < kCyclesBits; j++) { w = w * w; @@ -666,567 +852,663 @@ static void LzmaEnc_InitPriceTables(UInt32 *ProbPrices) bitCount++; } } - ProbPrices[i >> kNumMoveReducingBits] = ((kNumBitModelTotalBits << kCyclesBits) - 15 - bitCount); + ProbPrices[i] = (CProbPrice)(((unsigned)kNumBitModelTotalBits << kCyclesBits) - 15 - bitCount); + // printf("\n%3d: %5d", i, ProbPrices[i]); } } -#define GET_PRICE(prob, symbol) \ - p->ProbPrices[((prob) ^ (((-(int)(symbol))) & (kBitModelTotal - 1))) >> kNumMoveReducingBits]; +#define GET_PRICE(prob, bit) \ + p->ProbPrices[((prob) ^ (unsigned)(((-(int)(bit))) & (kBitModelTotal - 1))) >> kNumMoveReducingBits] -#define GET_PRICEa(prob, symbol) \ - ProbPrices[((prob) ^ ((-((int)(symbol))) & (kBitModelTotal - 1))) >> kNumMoveReducingBits]; +#define GET_PRICEa(prob, bit) \ + ProbPrices[((prob) ^ (unsigned)((-((int)(bit))) & (kBitModelTotal - 1))) >> kNumMoveReducingBits] #define GET_PRICE_0(prob) p->ProbPrices[(prob) >> kNumMoveReducingBits] #define GET_PRICE_1(prob) p->ProbPrices[((prob) ^ (kBitModelTotal - 1)) >> kNumMoveReducingBits] -#define GET_PRICE_0a(prob) ProbPrices[(prob) >> kNumMoveReducingBits] -#define GET_PRICE_1a(prob) ProbPrices[((prob) ^ (kBitModelTotal - 1)) >> kNumMoveReducingBits] +#define GET_PRICEa_0(prob) ProbPrices[(prob) >> kNumMoveReducingBits] +#define GET_PRICEa_1(prob) ProbPrices[((prob) ^ (kBitModelTotal - 1)) >> kNumMoveReducingBits] + -static UInt32 LitEnc_GetPrice(const CLzmaProb *probs, UInt32 symbol, const UInt32 *ProbPrices) +static UInt32 LitEnc_GetPrice(const CLzmaProb *probs, UInt32 sym, const CProbPrice *ProbPrices) { UInt32 price = 0; - symbol |= 0x100; + sym |= 0x100; do { - price += GET_PRICEa(probs[symbol >> 8], (symbol >> 7) & 1); - symbol <<= 1; + unsigned bit = sym & 1; + sym >>= 1; + price += GET_PRICEa(probs[sym], bit); } - while (symbol < 0x10000); + while (sym >= 2); return price; } -static UInt32 LitEnc_GetPriceMatched(const CLzmaProb *probs, UInt32 symbol, UInt32 matchByte, const UInt32 *ProbPrices) + +static UInt32 LitEnc_Matched_GetPrice(const CLzmaProb *probs, UInt32 sym, UInt32 matchByte, const CProbPrice *ProbPrices) { UInt32 price = 0; UInt32 offs = 0x100; - symbol |= 0x100; + sym |= 0x100; do { matchByte <<= 1; - price += GET_PRICEa(probs[offs + (matchByte & offs) + (symbol >> 8)], (symbol >> 7) & 1); - symbol <<= 1; - offs &= ~(matchByte ^ symbol); + price += GET_PRICEa(probs[offs + (matchByte & offs) + (sym >> 8)], (sym >> 7) & 1); + sym <<= 1; + offs &= ~(matchByte ^ sym); } - while (symbol < 0x10000); + while (sym < 0x10000); return price; } -static void RcTree_Encode(CRangeEnc *rc, CLzmaProb *probs, int numBitLevels, UInt32 symbol) -{ - UInt32 m = 1; - int i; - for (i = numBitLevels; i != 0;) - { - UInt32 bit; - i--; - bit = (symbol >> i) & 1; - RangeEnc_EncodeBit(rc, probs + m, bit); - m = (m << 1) | bit; - } -} - -static void RcTree_ReverseEncode(CRangeEnc *rc, CLzmaProb *probs, int numBitLevels, UInt32 symbol) +static void RcTree_ReverseEncode(CRangeEnc *rc, CLzmaProb *probs, unsigned numBits, unsigned sym) { - UInt32 m = 1; - int i; - for (i = 0; i < numBitLevels; i++) + UInt32 range = rc->range; + unsigned m = 1; + do { - UInt32 bit = symbol & 1; - RangeEnc_EncodeBit(rc, probs + m, bit); + UInt32 ttt, newBound; + unsigned bit = sym & 1; + // RangeEnc_EncodeBit(rc, probs + m, bit); + sym >>= 1; + RC_BIT(rc, probs + m, bit) m = (m << 1) | bit; - symbol >>= 1; - } -} - -static UInt32 RcTree_GetPrice(const CLzmaProb *probs, int numBitLevels, UInt32 symbol, const UInt32 *ProbPrices) -{ - UInt32 price = 0; - symbol |= (1 << numBitLevels); - while (symbol != 1) - { - price += GET_PRICEa(probs[symbol >> 1], symbol & 1); - symbol >>= 1; } - return price; + while (--numBits); + rc->range = range; } -static UInt32 RcTree_ReverseGetPrice(const CLzmaProb *probs, int numBitLevels, UInt32 symbol, const UInt32 *ProbPrices) -{ - UInt32 price = 0; - UInt32 m = 1; - int i; - for (i = numBitLevels; i != 0; i--) - { - UInt32 bit = symbol & 1; - symbol >>= 1; - price += GET_PRICEa(probs[m], bit); - m = (m << 1) | bit; - } - return price; -} static void LenEnc_Init(CLenEnc *p) { unsigned i; - p->choice = p->choice2 = kProbInitValue; - for (i = 0; i < (LZMA_NUM_PB_STATES_MAX << kLenNumLowBits); i++) + for (i = 0; i < (LZMA_NUM_PB_STATES_MAX << (kLenNumLowBits + 1)); i++) p->low[i] = kProbInitValue; - for (i = 0; i < (LZMA_NUM_PB_STATES_MAX << kLenNumMidBits); i++) - p->mid[i] = kProbInitValue; for (i = 0; i < kLenNumHighSymbols; i++) p->high[i] = kProbInitValue; } -static void LenEnc_Encode(CLenEnc *p, CRangeEnc *rc, UInt32 symbol, UInt32 posState) +static void LenEnc_Encode(CLenEnc *p, CRangeEnc *rc, unsigned sym, unsigned posState) { - if (symbol < kLenNumLowSymbols) - { - RangeEnc_EncodeBit(rc, &p->choice, 0); - RcTree_Encode(rc, p->low + (posState << kLenNumLowBits), kLenNumLowBits, symbol); - } - else + UInt32 range, ttt, newBound; + CLzmaProb *probs = p->low; + range = rc->range; + RC_BIT_PRE(rc, probs) + if (sym >= kLenNumLowSymbols) { - RangeEnc_EncodeBit(rc, &p->choice, 1); - if (symbol < kLenNumLowSymbols + kLenNumMidSymbols) - { - RangeEnc_EncodeBit(rc, &p->choice2, 0); - RcTree_Encode(rc, p->mid + (posState << kLenNumMidBits), kLenNumMidBits, symbol - kLenNumLowSymbols); - } - else + RC_BIT_1(rc, probs) + probs += kLenNumLowSymbols; + RC_BIT_PRE(rc, probs) + if (sym >= kLenNumLowSymbols * 2) { - RangeEnc_EncodeBit(rc, &p->choice2, 1); - RcTree_Encode(rc, p->high, kLenNumHighBits, symbol - kLenNumLowSymbols - kLenNumMidSymbols); + RC_BIT_1(rc, probs) + rc->range = range; + // RcTree_Encode(rc, p->high, kLenNumHighBits, sym - kLenNumLowSymbols * 2); + LitEnc_Encode(rc, p->high, sym - kLenNumLowSymbols * 2); + return; } + sym -= kLenNumLowSymbols; } -} -static void LenEnc_SetPrices(CLenEnc *p, UInt32 posState, UInt32 numSymbols, UInt32 *prices, const UInt32 *ProbPrices) -{ - UInt32 a0 = GET_PRICE_0a(p->choice); - UInt32 a1 = GET_PRICE_1a(p->choice); - UInt32 b0 = a1 + GET_PRICE_0a(p->choice2); - UInt32 b1 = a1 + GET_PRICE_1a(p->choice2); - UInt32 i = 0; - for (i = 0; i < kLenNumLowSymbols; i++) - { - if (i >= numSymbols) - return; - prices[i] = a0 + RcTree_GetPrice(p->low + (posState << kLenNumLowBits), kLenNumLowBits, i, ProbPrices); - } - for (; i < kLenNumLowSymbols + kLenNumMidSymbols; i++) + // RcTree_Encode(rc, probs + (posState << kLenNumLowBits), kLenNumLowBits, sym); { - if (i >= numSymbols) - return; - prices[i] = b0 + RcTree_GetPrice(p->mid + (posState << kLenNumMidBits), kLenNumMidBits, i - kLenNumLowSymbols, ProbPrices); + unsigned m; + unsigned bit; + RC_BIT_0(rc, probs) + probs += (posState << (1 + kLenNumLowBits)); + bit = (sym >> 2) ; RC_BIT(rc, probs + 1, bit) m = (1 << 1) + bit; + bit = (sym >> 1) & 1; RC_BIT(rc, probs + m, bit) m = (m << 1) + bit; + bit = sym & 1; RC_BIT(rc, probs + m, bit) + rc->range = range; } - for (; i < numSymbols; i++) - prices[i] = b1 + RcTree_GetPrice(p->high, kLenNumHighBits, i - kLenNumLowSymbols - kLenNumMidSymbols, ProbPrices); } -static void MY_FAST_CALL LenPriceEnc_UpdateTable(CLenPriceEnc *p, UInt32 posState, const UInt32 *ProbPrices) +static void SetPrices_3(const CLzmaProb *probs, UInt32 startPrice, UInt32 *prices, const CProbPrice *ProbPrices) { - LenEnc_SetPrices(&p->p, posState, p->tableSize, p->prices[posState], ProbPrices); - p->counters[posState] = p->tableSize; + unsigned i; + for (i = 0; i < 8; i += 2) + { + UInt32 price = startPrice; + UInt32 prob; + price += GET_PRICEa(probs[1 ], (i >> 2)); + price += GET_PRICEa(probs[2 + (i >> 2)], (i >> 1) & 1); + prob = probs[4 + (i >> 1)]; + prices[i ] = price + GET_PRICEa_0(prob); + prices[i + 1] = price + GET_PRICEa_1(prob); + } } -static void LenPriceEnc_UpdateTables(CLenPriceEnc *p, UInt32 numPosStates, const UInt32 *ProbPrices) -{ - UInt32 posState; - for (posState = 0; posState < numPosStates; posState++) - LenPriceEnc_UpdateTable(p, posState, ProbPrices); -} -static void LenEnc_Encode2(CLenPriceEnc *p, CRangeEnc *rc, UInt32 symbol, UInt32 posState, Bool updatePrice, const UInt32 *ProbPrices) +Z7_NO_INLINE static void Z7_FASTCALL LenPriceEnc_UpdateTables( + CLenPriceEnc *p, + unsigned numPosStates, + const CLenEnc *enc, + const CProbPrice *ProbPrices) { - LenEnc_Encode(&p->p, rc, symbol, posState); - if (updatePrice) - if (--p->counters[posState] == 0) - LenPriceEnc_UpdateTable(p, posState, ProbPrices); -} + UInt32 b; + + { + unsigned prob = enc->low[0]; + UInt32 a, c; + unsigned posState; + b = GET_PRICEa_1(prob); + a = GET_PRICEa_0(prob); + c = b + GET_PRICEa_0(enc->low[kLenNumLowSymbols]); + for (posState = 0; posState < numPosStates; posState++) + { + UInt32 *prices = p->prices[posState]; + const CLzmaProb *probs = enc->low + (posState << (1 + kLenNumLowBits)); + SetPrices_3(probs, a, prices, ProbPrices); + SetPrices_3(probs + kLenNumLowSymbols, c, prices + kLenNumLowSymbols, ProbPrices); + } + } + + /* + { + unsigned i; + UInt32 b; + a = GET_PRICEa_0(enc->low[0]); + for (i = 0; i < kLenNumLowSymbols; i++) + p->prices2[i] = a; + a = GET_PRICEa_1(enc->low[0]); + b = a + GET_PRICEa_0(enc->low[kLenNumLowSymbols]); + for (i = kLenNumLowSymbols; i < kLenNumLowSymbols * 2; i++) + p->prices2[i] = b; + a += GET_PRICEa_1(enc->low[kLenNumLowSymbols]); + } + */ + + // p->counter = numSymbols; + // p->counter = 64; + { + unsigned i = p->tableSize; + + if (i > kLenNumLowSymbols * 2) + { + const CLzmaProb *probs = enc->high; + UInt32 *prices = p->prices[0] + kLenNumLowSymbols * 2; + i -= kLenNumLowSymbols * 2 - 1; + i >>= 1; + b += GET_PRICEa_1(enc->low[kLenNumLowSymbols]); + do + { + /* + p->prices2[i] = a + + // RcTree_GetPrice(enc->high, kLenNumHighBits, i - kLenNumLowSymbols * 2, ProbPrices); + LitEnc_GetPrice(probs, i - kLenNumLowSymbols * 2, ProbPrices); + */ + // UInt32 price = a + RcTree_GetPrice(probs, kLenNumHighBits - 1, sym, ProbPrices); + unsigned sym = --i + (1 << (kLenNumHighBits - 1)); + UInt32 price = b; + do + { + const unsigned bit = sym & 1; + sym >>= 1; + price += GET_PRICEa(probs[sym], bit); + } + while (sym >= 2); + { + const unsigned prob = probs[(size_t)i + (1 << (kLenNumHighBits - 1))]; + prices[(size_t)i * 2 ] = price + GET_PRICEa_0(prob); + prices[(size_t)i * 2 + 1] = price + GET_PRICEa_1(prob); + } + } + while (i); + { + unsigned posState; + const size_t num = (p->tableSize - kLenNumLowSymbols * 2) * sizeof(p->prices[0][0]); + for (posState = 1; posState < numPosStates; posState++) + memcpy(p->prices[posState] + kLenNumLowSymbols * 2, p->prices[0] + kLenNumLowSymbols * 2, num); + } + } + } +} -static void MovePos(CLzmaEnc *p, UInt32 num) -{ +/* #ifdef SHOW_STAT g_STAT_OFFSET += num; printf("\n MovePos %u", num); #endif +*/ - if (num != 0) - { - p->additionalOffset += num; - p->matchFinder.Skip(p->matchFinderObj, num); - } -} +#define MOVE_POS(p, num) { \ + p->additionalOffset += (num); \ + p->matchFinder.Skip(p->matchFinderObj, (UInt32)(num)); } -static UInt32 ReadMatchDistances(CLzmaEnc *p, UInt32 *numDistancePairsRes) + +static unsigned ReadMatchDistances(CLzmaEnc *p, unsigned *numPairsRes) { - UInt32 lenRes = 0, numPairs; + unsigned numPairs; + + p->additionalOffset++; p->numAvail = p->matchFinder.GetNumAvailableBytes(p->matchFinderObj); - numPairs = p->matchFinder.GetMatches(p->matchFinderObj, p->matches); + { + const UInt32 *d = p->matchFinder.GetMatches(p->matchFinderObj, p->matches); + // if (!d) { p->mf_Failure = True; *numPairsRes = 0; return 0; } + numPairs = (unsigned)(d - p->matches); + } + *numPairsRes = numPairs; #ifdef SHOW_STAT printf("\n i = %u numPairs = %u ", g_STAT_OFFSET, numPairs / 2); g_STAT_OFFSET++; { - UInt32 i; + unsigned i; for (i = 0; i < numPairs; i += 2) printf("%2u %6u | ", p->matches[i], p->matches[i + 1]); } #endif - if (numPairs > 0) + if (numPairs == 0) + return 0; { - lenRes = p->matches[numPairs - 2]; - if (lenRes == p->numFastBytes) + const unsigned len = p->matches[(size_t)numPairs - 2]; + if (len != p->numFastBytes) + return len; { UInt32 numAvail = p->numAvail; if (numAvail > LZMA_MATCH_LEN_MAX) numAvail = LZMA_MATCH_LEN_MAX; { - const Byte *pbyCur = p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - 1; - const Byte *pby = pbyCur + lenRes; - ptrdiff_t dif = (ptrdiff_t)-1 - p->matches[numPairs - 1]; - const Byte *pbyLim = pbyCur + numAvail; - for (; pby != pbyLim && *pby == pby[dif]; pby++); - lenRes = (UInt32)(pby - pbyCur); + const Byte *p1 = p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - 1; + const Byte *p2 = p1 + len; + const ptrdiff_t dif = (ptrdiff_t)-1 - (ptrdiff_t)p->matches[(size_t)numPairs - 1]; + const Byte *lim = p1 + numAvail; + for (; p2 != lim && *p2 == p2[dif]; p2++) + {} + return (unsigned)(p2 - p1); } } } - p->additionalOffset++; - *numDistancePairsRes = numPairs; - return lenRes; } +#define MARK_LIT ((UInt32)(Int32)-1) -#define MakeAsChar(p) (p)->backPrev = (UInt32)(-1); (p)->prev1IsChar = False; -#define MakeAsShortRep(p) (p)->backPrev = 0; (p)->prev1IsChar = False; -#define IsShortRep(p) ((p)->backPrev == 0) +#define MakeAs_Lit(p) { (p)->dist = MARK_LIT; (p)->extra = 0; } +#define MakeAs_ShortRep(p) { (p)->dist = 0; (p)->extra = 0; } +#define IsShortRep(p) ((p)->dist == 0) -static UInt32 GetRepLen1Price(CLzmaEnc *p, UInt32 state, UInt32 posState) -{ - return - GET_PRICE_0(p->isRepG0[state]) + - GET_PRICE_0(p->isRep0Long[state][posState]); -} -static UInt32 GetPureRepPrice(CLzmaEnc *p, UInt32 repIndex, UInt32 state, UInt32 posState) +#define GetPrice_ShortRep(p, state, posState) \ + ( GET_PRICE_0(p->isRepG0[state]) + GET_PRICE_0(p->isRep0Long[state][posState])) + +#define GetPrice_Rep_0(p, state, posState) ( \ + GET_PRICE_1(p->isMatch[state][posState]) \ + + GET_PRICE_1(p->isRep0Long[state][posState])) \ + + GET_PRICE_1(p->isRep[state]) \ + + GET_PRICE_0(p->isRepG0[state]) + +Z7_FORCE_INLINE +static UInt32 GetPrice_PureRep(const CLzmaEnc *p, unsigned repIndex, size_t state, size_t posState) { UInt32 price; + UInt32 prob = p->isRepG0[state]; if (repIndex == 0) { - price = GET_PRICE_0(p->isRepG0[state]); + price = GET_PRICE_0(prob); price += GET_PRICE_1(p->isRep0Long[state][posState]); } else { - price = GET_PRICE_1(p->isRepG0[state]); + price = GET_PRICE_1(prob); + prob = p->isRepG1[state]; if (repIndex == 1) - price += GET_PRICE_0(p->isRepG1[state]); + price += GET_PRICE_0(prob); else { - price += GET_PRICE_1(p->isRepG1[state]); + price += GET_PRICE_1(prob); price += GET_PRICE(p->isRepG2[state], repIndex - 2); } } return price; } -static UInt32 GetRepPrice(CLzmaEnc *p, UInt32 repIndex, UInt32 len, UInt32 state, UInt32 posState) -{ - return p->repLenEnc.prices[posState][len - LZMA_MATCH_LEN_MIN] + - GetPureRepPrice(p, repIndex, state, posState); -} -static UInt32 Backward(CLzmaEnc *p, UInt32 *backRes, UInt32 cur) +static unsigned Backward(CLzmaEnc *p, unsigned cur) { - UInt32 posMem = p->opt[cur].posPrev; - UInt32 backMem = p->opt[cur].backPrev; - p->optimumEndIndex = cur; - do + unsigned wr = cur + 1; + p->optEnd = wr; + + for (;;) { - if (p->opt[cur].prev1IsChar) + UInt32 dist = p->opt[cur].dist; + unsigned len = (unsigned)p->opt[cur].len; + unsigned extra = (unsigned)p->opt[cur].extra; + cur -= len; + + if (extra) { - MakeAsChar(&p->opt[posMem]) - p->opt[posMem].posPrev = posMem - 1; - if (p->opt[cur].prev2) + wr--; + p->opt[wr].len = (UInt32)len; + cur -= extra; + len = extra; + if (extra == 1) + { + p->opt[wr].dist = dist; + dist = MARK_LIT; + } + else { - p->opt[posMem - 1].prev1IsChar = False; - p->opt[posMem - 1].posPrev = p->opt[cur].posPrev2; - p->opt[posMem - 1].backPrev = p->opt[cur].backPrev2; + p->opt[wr].dist = 0; + len--; + wr--; + p->opt[wr].dist = MARK_LIT; + p->opt[wr].len = 1; } } + + if (cur == 0) { - UInt32 posPrev = posMem; - UInt32 backCur = backMem; - - backMem = p->opt[posPrev].backPrev; - posMem = p->opt[posPrev].posPrev; - - p->opt[posPrev].backPrev = backCur; - p->opt[posPrev].posPrev = cur; - cur = posPrev; + p->backRes = dist; + p->optCur = wr; + return len; } + + wr--; + p->opt[wr].dist = dist; + p->opt[wr].len = (UInt32)len; } - while (cur != 0); - *backRes = p->opt[0].backPrev; - p->optimumCurrentIndex = p->opt[0].posPrev; - return p->optimumCurrentIndex; } -#define LIT_PROBS(pos, prevByte) (p->litProbs + ((((pos) & p->lpMask) << p->lc) + ((prevByte) >> (8 - p->lc))) * (UInt32)0x300) -static UInt32 GetOptimum(CLzmaEnc *p, UInt32 position, UInt32 *backRes) -{ - UInt32 lenEnd, cur; - UInt32 reps[LZMA_NUM_REPS], repLens[LZMA_NUM_REPS]; - UInt32 *matches; - { +#define LIT_PROBS(pos, prevByte) \ + (p->litProbs + (UInt32)3 * (((((pos) << 8) + (prevByte)) & p->lpMask) << p->lc)) - UInt32 numAvail, mainLen, numPairs, repMaxIndex, i, posState, len; - UInt32 matchPrice, repMatchPrice, normalMatchPrice; - const Byte *data; - Byte curByte, matchByte; - if (p->optimumEndIndex != p->optimumCurrentIndex) - { - const COptimal *opt = &p->opt[p->optimumCurrentIndex]; - UInt32 lenRes = opt->posPrev - p->optimumCurrentIndex; - *backRes = opt->backPrev; - p->optimumCurrentIndex = opt->posPrev; - return lenRes; - } - p->optimumCurrentIndex = p->optimumEndIndex = 0; - - if (p->additionalOffset == 0) - mainLen = ReadMatchDistances(p, &numPairs); - else - { - mainLen = p->longestMatchLength; - numPairs = p->numPairs; - } - - numAvail = p->numAvail; - if (numAvail < 2) - { - *backRes = (UInt32)(-1); - return 1; - } - if (numAvail > LZMA_MATCH_LEN_MAX) - numAvail = LZMA_MATCH_LEN_MAX; +static unsigned GetOptimum(CLzmaEnc *p, UInt32 position) +{ + unsigned last, cur; + UInt32 reps[LZMA_NUM_REPS]; + unsigned repLens[LZMA_NUM_REPS]; + UInt32 *matches; - data = p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - 1; - repMaxIndex = 0; - for (i = 0; i < LZMA_NUM_REPS; i++) { - UInt32 lenTest; - const Byte *data2; - reps[i] = p->reps[i]; - data2 = data - reps[i] - 1; - if (data[0] != data2[0] || data[1] != data2[1]) + UInt32 numAvail; + unsigned numPairs, mainLen, repMaxIndex, i, posState; + UInt32 matchPrice, repMatchPrice; + const Byte *data; + Byte curByte, matchByte; + + p->optCur = p->optEnd = 0; + + if (p->additionalOffset == 0) + mainLen = ReadMatchDistances(p, &numPairs); + else { - repLens[i] = 0; - continue; + mainLen = p->longestMatchLen; + numPairs = p->numPairs; } - for (lenTest = 2; lenTest < numAvail && data[lenTest] == data2[lenTest]; lenTest++); - repLens[i] = lenTest; - if (lenTest > repLens[repMaxIndex]) - repMaxIndex = i; - } - if (repLens[repMaxIndex] >= p->numFastBytes) - { - UInt32 lenRes; - *backRes = repMaxIndex; - lenRes = repLens[repMaxIndex]; - MovePos(p, lenRes - 1); - return lenRes; - } - - matches = p->matches; - if (mainLen >= p->numFastBytes) - { - *backRes = matches[numPairs - 1] + LZMA_NUM_REPS; - MovePos(p, mainLen - 1); - return mainLen; - } - curByte = *data; - matchByte = *(data - (reps[0] + 1)); - - if (mainLen < 2 && curByte != matchByte && repLens[repMaxIndex] < 2) - { - *backRes = (UInt32)-1; - return 1; - } - - p->opt[0].state = (CState)p->state; - - posState = (position & p->pbMask); - - { - const CLzmaProb *probs = LIT_PROBS(position, *(data - 1)); - p->opt[1].price = GET_PRICE_0(p->isMatch[p->state][posState]) + - (!IsCharState(p->state) ? - LitEnc_GetPriceMatched(probs, curByte, matchByte, p->ProbPrices) : - LitEnc_GetPrice(probs, curByte, p->ProbPrices)); - } - - MakeAsChar(&p->opt[1]); - - matchPrice = GET_PRICE_1(p->isMatch[p->state][posState]); - repMatchPrice = matchPrice + GET_PRICE_1(p->isRep[p->state]); - - if (matchByte == curByte) - { - UInt32 shortRepPrice = repMatchPrice + GetRepLen1Price(p, p->state, posState); - if (shortRepPrice < p->opt[1].price) + + numAvail = p->numAvail; + if (numAvail < 2) { - p->opt[1].price = shortRepPrice; - MakeAsShortRep(&p->opt[1]); + p->backRes = MARK_LIT; + return 1; } - } - lenEnd = ((mainLen >= repLens[repMaxIndex]) ? mainLen : repLens[repMaxIndex]); - - if (lenEnd < 2) - { - *backRes = p->opt[1].backPrev; - return 1; - } - - p->opt[1].posPrev = 0; - for (i = 0; i < LZMA_NUM_REPS; i++) - p->opt[0].backs[i] = reps[i]; - - len = lenEnd; - do - p->opt[len--].price = kInfinityPrice; - while (len >= 2); - - for (i = 0; i < LZMA_NUM_REPS; i++) - { - UInt32 repLen = repLens[i]; - UInt32 price; - if (repLen < 2) - continue; - price = repMatchPrice + GetPureRepPrice(p, i, p->state, posState); - do + if (numAvail > LZMA_MATCH_LEN_MAX) + numAvail = LZMA_MATCH_LEN_MAX; + + data = p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - 1; + repMaxIndex = 0; + + for (i = 0; i < LZMA_NUM_REPS; i++) { - UInt32 curAndLenPrice = price + p->repLenEnc.prices[posState][repLen - 2]; - COptimal *opt = &p->opt[repLen]; - if (curAndLenPrice < opt->price) + unsigned len; + const Byte *data2; + reps[i] = p->reps[i]; + data2 = data - reps[i]; + if (data[0] != data2[0] || data[1] != data2[1]) { - opt->price = curAndLenPrice; - opt->posPrev = 0; - opt->backPrev = i; - opt->prev1IsChar = False; + repLens[i] = 0; + continue; } + for (len = 2; len < numAvail && data[len] == data2[len]; len++) + {} + repLens[i] = len; + if (len > repLens[repMaxIndex]) + repMaxIndex = i; + if (len == LZMA_MATCH_LEN_MAX) // 21.03 : optimization + break; } - while (--repLen >= 2); - } - - normalMatchPrice = matchPrice + GET_PRICE_0(p->isRep[p->state]); + + if (repLens[repMaxIndex] >= p->numFastBytes) + { + unsigned len; + p->backRes = (UInt32)repMaxIndex; + len = repLens[repMaxIndex]; + MOVE_POS(p, len - 1) + return len; + } + + matches = p->matches; + #define MATCHES matches + // #define MATCHES p->matches + + if (mainLen >= p->numFastBytes) + { + p->backRes = MATCHES[(size_t)numPairs - 1] + LZMA_NUM_REPS; + MOVE_POS(p, mainLen - 1) + return mainLen; + } + + curByte = *data; + matchByte = *(data - reps[0]); - len = ((repLens[0] >= 2) ? repLens[0] + 1 : 2); - if (len <= mainLen) - { - UInt32 offs = 0; - while (len > matches[offs]) - offs += 2; - for (; ; len++) + last = repLens[repMaxIndex]; + if (last <= mainLen) + last = mainLen; + + if (last < 2 && curByte != matchByte) + { + p->backRes = MARK_LIT; + return 1; + } + + p->opt[0].state = (CState)p->state; + + posState = (position & p->pbMask); + { - COptimal *opt; - UInt32 distance = matches[offs + 1]; + const CLzmaProb *probs = LIT_PROBS(position, *(data - 1)); + p->opt[1].price = GET_PRICE_0(p->isMatch[p->state][posState]) + + (!IsLitState(p->state) ? + LitEnc_Matched_GetPrice(probs, curByte, matchByte, p->ProbPrices) : + LitEnc_GetPrice(probs, curByte, p->ProbPrices)); + } - UInt32 curAndLenPrice = normalMatchPrice + p->lenEnc.prices[posState][len - LZMA_MATCH_LEN_MIN]; - UInt32 lenToPosState = GetLenToPosState(len); - if (distance < kNumFullDistances) - curAndLenPrice += p->distancesPrices[lenToPosState][distance]; - else + MakeAs_Lit(&p->opt[1]) + + matchPrice = GET_PRICE_1(p->isMatch[p->state][posState]); + repMatchPrice = matchPrice + GET_PRICE_1(p->isRep[p->state]); + + // 18.06 + if (matchByte == curByte && repLens[0] == 0) + { + UInt32 shortRepPrice = repMatchPrice + GetPrice_ShortRep(p, p->state, posState); + if (shortRepPrice < p->opt[1].price) { - UInt32 slot; - GetPosSlot2(distance, slot); - curAndLenPrice += p->alignPrices[distance & kAlignMask] + p->posSlotPrices[lenToPosState][slot]; + p->opt[1].price = shortRepPrice; + MakeAs_ShortRep(&p->opt[1]) } - opt = &p->opt[len]; - if (curAndLenPrice < opt->price) + if (last < 2) { - opt->price = curAndLenPrice; - opt->posPrev = 0; - opt->backPrev = distance + LZMA_NUM_REPS; - opt->prev1IsChar = False; + p->backRes = p->opt[1].dist; + return 1; } - if (len == matches[offs]) + } + + p->opt[1].len = 1; + + p->opt[0].reps[0] = reps[0]; + p->opt[0].reps[1] = reps[1]; + p->opt[0].reps[2] = reps[2]; + p->opt[0].reps[3] = reps[3]; + + // ---------- REP ---------- + + for (i = 0; i < LZMA_NUM_REPS; i++) + { + unsigned repLen = repLens[i]; + UInt32 price; + if (repLen < 2) + continue; + price = repMatchPrice + GetPrice_PureRep(p, i, p->state, posState); + do { - offs += 2; - if (offs == numPairs) - break; + UInt32 price2 = price + GET_PRICE_LEN(&p->repLenEnc, posState, repLen); + COptimal *opt = &p->opt[repLen]; + if (price2 < opt->price) + { + opt->price = price2; + opt->len = (UInt32)repLen; + opt->dist = (UInt32)i; + opt->extra = 0; + } } + while (--repLen >= 2); } - } + + + // ---------- MATCH ---------- + { + unsigned len = repLens[0] + 1; + if (len <= mainLen) + { + unsigned offs = 0; + UInt32 normalMatchPrice = matchPrice + GET_PRICE_0(p->isRep[p->state]); + + if (len < 2) + len = 2; + else + while (len > MATCHES[offs]) + offs += 2; + + for (; ; len++) + { + COptimal *opt; + UInt32 dist = MATCHES[(size_t)offs + 1]; + UInt32 price = normalMatchPrice + GET_PRICE_LEN(&p->lenEnc, posState, len); + unsigned lenToPosState = GetLenToPosState(len); + + if (dist < kNumFullDistances) + price += p->distancesPrices[lenToPosState][dist & (kNumFullDistances - 1)]; + else + { + unsigned slot; + GetPosSlot2(dist, slot) + price += p->alignPrices[dist & kAlignMask]; + price += p->posSlotPrices[lenToPosState][slot]; + } + + opt = &p->opt[len]; + + if (price < opt->price) + { + opt->price = price; + opt->len = (UInt32)len; + opt->dist = dist + LZMA_NUM_REPS; + opt->extra = 0; + } + + if (len == MATCHES[offs]) + { + offs += 2; + if (offs == numPairs) + break; + } + } + } + } + - cur = 0; + cur = 0; #ifdef SHOW_STAT2 /* if (position >= 0) */ { unsigned i; printf("\n pos = %4X", position); - for (i = cur; i <= lenEnd; i++) + for (i = cur; i <= last; i++) printf("\nprice[%4X] = %u", position - cur + i, p->opt[i].price); } #endif - } + + + // ---------- Optimal Parsing ---------- + for (;;) { - UInt32 numAvail; - UInt32 numAvailFull, newLen, numPairs, posPrev, state, posState, startLen; - UInt32 curPrice, curAnd1Price, matchPrice, repMatchPrice; - Bool nextIsChar; + unsigned numAvail; + UInt32 numAvailFull; + unsigned newLen, numPairs, prev, state, posState, startLen; + UInt32 litPrice, matchPrice, repMatchPrice; + BoolInt nextIsLit; Byte curByte, matchByte; const Byte *data; - COptimal *curOpt; - COptimal *nextOpt; + COptimal *curOpt, *nextOpt; - cur++; - if (cur == lenEnd) - return Backward(p, backRes, cur); + if (++cur == last) + break; + + // 18.06 + if (cur >= kNumOpts - 64) + { + unsigned j, best; + UInt32 price = p->opt[cur].price; + best = cur; + for (j = cur + 1; j <= last; j++) + { + UInt32 price2 = p->opt[j].price; + if (price >= price2) + { + price = price2; + best = j; + } + } + { + unsigned delta = best - cur; + if (delta != 0) + { + MOVE_POS(p, delta) + } + } + cur = best; + break; + } newLen = ReadMatchDistances(p, &numPairs); + if (newLen >= p->numFastBytes) { p->numPairs = numPairs; - p->longestMatchLength = newLen; - return Backward(p, backRes, cur); + p->longestMatchLen = newLen; + break; } - position++; + curOpt = &p->opt[cur]; - posPrev = curOpt->posPrev; - if (curOpt->prev1IsChar) - { - posPrev--; - if (curOpt->prev2) - { - state = p->opt[curOpt->posPrev2].state; - if (curOpt->backPrev2 < LZMA_NUM_REPS) - state = kRepNextStates[state]; - else - state = kMatchNextStates[state]; - } - else - state = p->opt[posPrev].state; - state = kLiteralNextStates[state]; - } - else - state = p->opt[posPrev].state; - if (posPrev == cur - 1) + + position++; + + // we need that check here, if skip_items in p->opt are possible + /* + if (curOpt->price >= kInfinityPrice) + continue; + */ + + prev = cur - curOpt->len; + + if (curOpt->len == 1) { + state = (unsigned)p->opt[prev].state; if (IsShortRep(curOpt)) state = kShortRepNextStates[state]; else @@ -1234,357 +1516,499 @@ static UInt32 GetOptimum(CLzmaEnc *p, UInt32 position, UInt32 *backRes) } else { - UInt32 pos; const COptimal *prevOpt; - if (curOpt->prev1IsChar && curOpt->prev2) + UInt32 b0; + UInt32 dist = curOpt->dist; + + if (curOpt->extra) { - posPrev = curOpt->posPrev2; - pos = curOpt->backPrev2; - state = kRepNextStates[state]; + prev -= (unsigned)curOpt->extra; + state = kState_RepAfterLit; + if (curOpt->extra == 1) + state = (dist < LZMA_NUM_REPS ? kState_RepAfterLit : kState_MatchAfterLit); } else { - pos = curOpt->backPrev; - if (pos < LZMA_NUM_REPS) + state = (unsigned)p->opt[prev].state; + if (dist < LZMA_NUM_REPS) state = kRepNextStates[state]; else state = kMatchNextStates[state]; } - prevOpt = &p->opt[posPrev]; - if (pos < LZMA_NUM_REPS) + + prevOpt = &p->opt[prev]; + b0 = prevOpt->reps[0]; + + if (dist < LZMA_NUM_REPS) { - UInt32 i; - reps[0] = prevOpt->backs[pos]; - for (i = 1; i <= pos; i++) - reps[i] = prevOpt->backs[i - 1]; - for (; i < LZMA_NUM_REPS; i++) - reps[i] = prevOpt->backs[i]; + if (dist == 0) + { + reps[0] = b0; + reps[1] = prevOpt->reps[1]; + reps[2] = prevOpt->reps[2]; + reps[3] = prevOpt->reps[3]; + } + else + { + reps[1] = b0; + b0 = prevOpt->reps[1]; + if (dist == 1) + { + reps[0] = b0; + reps[2] = prevOpt->reps[2]; + reps[3] = prevOpt->reps[3]; + } + else + { + reps[2] = b0; + reps[0] = prevOpt->reps[dist]; + reps[3] = prevOpt->reps[dist ^ 1]; + } + } } else { - UInt32 i; - reps[0] = (pos - LZMA_NUM_REPS); - for (i = 1; i < LZMA_NUM_REPS; i++) - reps[i] = prevOpt->backs[i - 1]; + reps[0] = (dist - LZMA_NUM_REPS + 1); + reps[1] = b0; + reps[2] = prevOpt->reps[1]; + reps[3] = prevOpt->reps[2]; } } + curOpt->state = (CState)state; + curOpt->reps[0] = reps[0]; + curOpt->reps[1] = reps[1]; + curOpt->reps[2] = reps[2]; + curOpt->reps[3] = reps[3]; - curOpt->backs[0] = reps[0]; - curOpt->backs[1] = reps[1]; - curOpt->backs[2] = reps[2]; - curOpt->backs[3] = reps[3]; - - curPrice = curOpt->price; - nextIsChar = False; data = p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - 1; curByte = *data; - matchByte = *(data - (reps[0] + 1)); + matchByte = *(data - reps[0]); posState = (position & p->pbMask); - curAnd1Price = curPrice + GET_PRICE_0(p->isMatch[state][posState]); + /* + The order of Price checks: + < LIT + <= SHORT_REP + < LIT : REP_0 + < REP [ : LIT : REP_0 ] + < MATCH [ : LIT : REP_0 ] + */ + { - const CLzmaProb *probs = LIT_PROBS(position, *(data - 1)); - curAnd1Price += - (!IsCharState(state) ? - LitEnc_GetPriceMatched(probs, curByte, matchByte, p->ProbPrices) : - LitEnc_GetPrice(probs, curByte, p->ProbPrices)); + UInt32 curPrice = curOpt->price; + unsigned prob = p->isMatch[state][posState]; + matchPrice = curPrice + GET_PRICE_1(prob); + litPrice = curPrice + GET_PRICE_0(prob); } - nextOpt = &p->opt[cur + 1]; - - if (curAnd1Price < nextOpt->price) + nextOpt = &p->opt[(size_t)cur + 1]; + nextIsLit = False; + + // here we can allow skip_items in p->opt, if we don't check (nextOpt->price < kInfinityPrice) + // 18.new.06 + if ((nextOpt->price < kInfinityPrice + // && !IsLitState(state) + && matchByte == curByte) + || litPrice > nextOpt->price + ) + litPrice = 0; + else { - nextOpt->price = curAnd1Price; - nextOpt->posPrev = cur; - MakeAsChar(nextOpt); - nextIsChar = True; + const CLzmaProb *probs = LIT_PROBS(position, *(data - 1)); + litPrice += (!IsLitState(state) ? + LitEnc_Matched_GetPrice(probs, curByte, matchByte, p->ProbPrices) : + LitEnc_GetPrice(probs, curByte, p->ProbPrices)); + + if (litPrice < nextOpt->price) + { + nextOpt->price = litPrice; + nextOpt->len = 1; + MakeAs_Lit(nextOpt) + nextIsLit = True; + } } - matchPrice = curPrice + GET_PRICE_1(p->isMatch[state][posState]); repMatchPrice = matchPrice + GET_PRICE_1(p->isRep[state]); - if (matchByte == curByte && !(nextOpt->posPrev < cur && nextOpt->backPrev == 0)) + numAvailFull = p->numAvail; + { + unsigned temp = kNumOpts - 1 - cur; + if (numAvailFull > temp) + numAvailFull = (UInt32)temp; + } + + // 18.06 + // ---------- SHORT_REP ---------- + if (IsLitState(state)) // 18.new + if (matchByte == curByte) + if (repMatchPrice < nextOpt->price) // 18.new + // if (numAvailFull < 2 || data[1] != *(data - reps[0] + 1)) + if ( + // nextOpt->price >= kInfinityPrice || + nextOpt->len < 2 // we can check nextOpt->len, if skip items are not allowed in p->opt + || (nextOpt->dist != 0 + // && nextOpt->extra <= 1 // 17.old + ) + ) { - UInt32 shortRepPrice = repMatchPrice + GetRepLen1Price(p, state, posState); - if (shortRepPrice <= nextOpt->price) + UInt32 shortRepPrice = repMatchPrice + GetPrice_ShortRep(p, state, posState); + // if (shortRepPrice <= nextOpt->price) // 17.old + if (shortRepPrice < nextOpt->price) // 18.new { nextOpt->price = shortRepPrice; - nextOpt->posPrev = cur; - MakeAsShortRep(nextOpt); - nextIsChar = True; + nextOpt->len = 1; + MakeAs_ShortRep(nextOpt) + nextIsLit = False; } } - numAvailFull = p->numAvail; - { - UInt32 temp = kNumOpts - 1 - cur; - if (temp < numAvailFull) - numAvailFull = temp; - } - + if (numAvailFull < 2) continue; numAvail = (numAvailFull <= p->numFastBytes ? numAvailFull : p->numFastBytes); - if (!nextIsChar && matchByte != curByte) /* speed optimization */ + // numAvail <= p->numFastBytes + + // ---------- LIT : REP_0 ---------- + + if (!nextIsLit + && litPrice != 0 // 18.new + && matchByte != curByte + && numAvailFull > 2) { - /* try Literal + rep0 */ - UInt32 temp; - UInt32 lenTest2; - const Byte *data2 = data - reps[0] - 1; - UInt32 limit = p->numFastBytes + 1; - if (limit > numAvailFull) - limit = numAvailFull; - - for (temp = 1; temp < limit && data[temp] == data2[temp]; temp++); - lenTest2 = temp - 1; - if (lenTest2 >= 2) + const Byte *data2 = data - reps[0]; + if (data[1] == data2[1] && data[2] == data2[2]) { - UInt32 state2 = kLiteralNextStates[state]; - UInt32 posStateNext = (position + 1) & p->pbMask; - UInt32 nextRepMatchPrice = curAnd1Price + - GET_PRICE_1(p->isMatch[state2][posStateNext]) + - GET_PRICE_1(p->isRep[state2]); - /* for (; lenTest2 >= 2; lenTest2--) */ + unsigned len; + unsigned limit = p->numFastBytes + 1; + if (limit > numAvailFull) + limit = numAvailFull; + for (len = 3; len < limit && data[len] == data2[len]; len++) + {} + { - UInt32 curAndLenPrice; - COptimal *opt; - UInt32 offset = cur + 1 + lenTest2; - while (lenEnd < offset) - p->opt[++lenEnd].price = kInfinityPrice; - curAndLenPrice = nextRepMatchPrice + GetRepPrice(p, 0, lenTest2, state2, posStateNext); - opt = &p->opt[offset]; - if (curAndLenPrice < opt->price) + unsigned state2 = kLiteralNextStates[state]; + unsigned posState2 = (position + 1) & p->pbMask; + UInt32 price = litPrice + GetPrice_Rep_0(p, state2, posState2); { - opt->price = curAndLenPrice; - opt->posPrev = cur + 1; - opt->backPrev = 0; - opt->prev1IsChar = True; - opt->prev2 = False; + unsigned offset = cur + len; + + if (last < offset) + last = offset; + + // do + { + UInt32 price2; + COptimal *opt; + len--; + // price2 = price + GetPrice_Len_Rep_0(p, len, state2, posState2); + price2 = price + GET_PRICE_LEN(&p->repLenEnc, posState2, len); + + opt = &p->opt[offset]; + // offset--; + if (price2 < opt->price) + { + opt->price = price2; + opt->len = (UInt32)len; + opt->dist = 0; + opt->extra = 1; + } + } + // while (len >= 3); } } } } startLen = 2; /* speed optimization */ + { - UInt32 repIndex; - for (repIndex = 0; repIndex < LZMA_NUM_REPS; repIndex++) - { - UInt32 lenTest; - UInt32 lenTestTemp; - UInt32 price; - const Byte *data2 = data - reps[repIndex] - 1; - if (data[0] != data2[0] || data[1] != data2[1]) - continue; - for (lenTest = 2; lenTest < numAvail && data[lenTest] == data2[lenTest]; lenTest++); - while (lenEnd < cur + lenTest) - p->opt[++lenEnd].price = kInfinityPrice; - lenTestTemp = lenTest; - price = repMatchPrice + GetPureRepPrice(p, repIndex, state, posState); - do + // ---------- REP ---------- + unsigned repIndex = 0; // 17.old + // unsigned repIndex = IsLitState(state) ? 0 : 1; // 18.notused + for (; repIndex < LZMA_NUM_REPS; repIndex++) { - UInt32 curAndLenPrice = price + p->repLenEnc.prices[posState][lenTest - 2]; - COptimal *opt = &p->opt[cur + lenTest]; - if (curAndLenPrice < opt->price) + unsigned len; + UInt32 price; + const Byte *data2 = data - reps[repIndex]; + if (data[0] != data2[0] || data[1] != data2[1]) + continue; + + for (len = 2; len < numAvail && data[len] == data2[len]; len++) + {} + + // if (len < startLen) continue; // 18.new: speed optimization + { - opt->price = curAndLenPrice; - opt->posPrev = cur; - opt->backPrev = repIndex; - opt->prev1IsChar = False; + unsigned offset = cur + len; + if (last < offset) + last = offset; + } + { + unsigned len2 = len; + price = repMatchPrice + GetPrice_PureRep(p, repIndex, state, posState); + do + { + UInt32 price2 = price + GET_PRICE_LEN(&p->repLenEnc, posState, len2); + COptimal *opt = &p->opt[cur + len2]; + if (price2 < opt->price) + { + opt->price = price2; + opt->len = (UInt32)len2; + opt->dist = (UInt32)repIndex; + opt->extra = 0; + } + } + while (--len2 >= 2); } - } - while (--lenTest >= 2); - lenTest = lenTestTemp; - - if (repIndex == 0) - startLen = lenTest + 1; - /* if (_maxMode) */ + if (repIndex == 0) startLen = len + 1; // 17.old + // startLen = len + 1; // 18.new + + /* if (_maxMode) */ { - UInt32 lenTest2 = lenTest + 1; - UInt32 limit = lenTest2 + p->numFastBytes; + // ---------- REP : LIT : REP_0 ---------- + // numFastBytes + 1 + numFastBytes + + unsigned len2 = len + 1; + unsigned limit = len2 + p->numFastBytes; if (limit > numAvailFull) limit = numAvailFull; - for (; lenTest2 < limit && data[lenTest2] == data2[lenTest2]; lenTest2++); - lenTest2 -= lenTest + 1; - if (lenTest2 >= 2) + + len2 += 2; + if (len2 <= limit) + if (data[len2 - 2] == data2[len2 - 2]) + if (data[len2 - 1] == data2[len2 - 1]) { - UInt32 nextRepMatchPrice; - UInt32 state2 = kRepNextStates[state]; - UInt32 posStateNext = (position + lenTest) & p->pbMask; - UInt32 curAndLenCharPrice = - price + p->repLenEnc.prices[posState][lenTest - 2] + - GET_PRICE_0(p->isMatch[state2][posStateNext]) + - LitEnc_GetPriceMatched(LIT_PROBS(position + lenTest, data[lenTest - 1]), - data[lenTest], data2[lenTest], p->ProbPrices); - state2 = kLiteralNextStates[state2]; - posStateNext = (position + lenTest + 1) & p->pbMask; - nextRepMatchPrice = curAndLenCharPrice + - GET_PRICE_1(p->isMatch[state2][posStateNext]) + - GET_PRICE_1(p->isRep[state2]); + unsigned state2 = kRepNextStates[state]; + unsigned posState2 = (position + len) & p->pbMask; + price += GET_PRICE_LEN(&p->repLenEnc, posState, len) + + GET_PRICE_0(p->isMatch[state2][posState2]) + + LitEnc_Matched_GetPrice(LIT_PROBS(position + len, data[(size_t)len - 1]), + data[len], data2[len], p->ProbPrices); - /* for (; lenTest2 >= 2; lenTest2--) */ + // state2 = kLiteralNextStates[state2]; + state2 = kState_LitAfterRep; + posState2 = (posState2 + 1) & p->pbMask; + + + price += GetPrice_Rep_0(p, state2, posState2); + + for (; len2 < limit && data[len2] == data2[len2]; len2++) + {} + + len2 -= len; + // if (len2 >= 3) + { { - UInt32 curAndLenPrice; - COptimal *opt; - UInt32 offset = cur + lenTest + 1 + lenTest2; - while (lenEnd < offset) - p->opt[++lenEnd].price = kInfinityPrice; - curAndLenPrice = nextRepMatchPrice + GetRepPrice(p, 0, lenTest2, state2, posStateNext); - opt = &p->opt[offset]; - if (curAndLenPrice < opt->price) + unsigned offset = cur + len + len2; + + if (last < offset) + last = offset; + // do { - opt->price = curAndLenPrice; - opt->posPrev = cur + lenTest + 1; - opt->backPrev = 0; - opt->prev1IsChar = True; - opt->prev2 = True; - opt->posPrev2 = cur; - opt->backPrev2 = repIndex; + UInt32 price2; + COptimal *opt; + len2--; + // price2 = price + GetPrice_Len_Rep_0(p, len2, state2, posState2); + price2 = price + GET_PRICE_LEN(&p->repLenEnc, posState2, len2); + + opt = &p->opt[offset]; + // offset--; + if (price2 < opt->price) + { + opt->price = price2; + opt->len = (UInt32)len2; + opt->extra = (CExtra)(len + 1); + opt->dist = (UInt32)repIndex; + } } + // while (len2 >= 3); } } + } } + } } - } - /* for (UInt32 lenTest = 2; lenTest <= newLen; lenTest++) */ + + + // ---------- MATCH ---------- + /* for (unsigned len = 2; len <= newLen; len++) */ if (newLen > numAvail) { newLen = numAvail; - for (numPairs = 0; newLen > matches[numPairs]; numPairs += 2); - matches[numPairs] = newLen; + for (numPairs = 0; newLen > MATCHES[numPairs]; numPairs += 2); + MATCHES[numPairs] = (UInt32)newLen; numPairs += 2; } + + // startLen = 2; /* speed optimization */ + if (newLen >= startLen) { UInt32 normalMatchPrice = matchPrice + GET_PRICE_0(p->isRep[state]); - UInt32 offs, curBack, posSlot; - UInt32 lenTest; - while (lenEnd < cur + newLen) - p->opt[++lenEnd].price = kInfinityPrice; + UInt32 dist; + unsigned offs, posSlot, len; + + { + unsigned offset = cur + newLen; + if (last < offset) + last = offset; + } offs = 0; - while (startLen > matches[offs]) + while (startLen > MATCHES[offs]) offs += 2; - curBack = matches[offs + 1]; - GetPosSlot2(curBack, posSlot); - for (lenTest = /*2*/ startLen; ; lenTest++) - { - UInt32 curAndLenPrice = normalMatchPrice + p->lenEnc.prices[posState][lenTest - LZMA_MATCH_LEN_MIN]; - { - UInt32 lenToPosState = GetLenToPosState(lenTest); - COptimal *opt; - if (curBack < kNumFullDistances) - curAndLenPrice += p->distancesPrices[lenToPosState][curBack]; - else - curAndLenPrice += p->posSlotPrices[lenToPosState][posSlot] + p->alignPrices[curBack & kAlignMask]; - - opt = &p->opt[cur + lenTest]; - if (curAndLenPrice < opt->price) + dist = MATCHES[(size_t)offs + 1]; + + // if (dist >= kNumFullDistances) + GetPosSlot2(dist, posSlot) + + for (len = /*2*/ startLen; ; len++) + { + UInt32 price = normalMatchPrice + GET_PRICE_LEN(&p->lenEnc, posState, len); { - opt->price = curAndLenPrice; - opt->posPrev = cur; - opt->backPrev = curBack + LZMA_NUM_REPS; - opt->prev1IsChar = False; - } + COptimal *opt; + unsigned lenNorm = len - 2; + lenNorm = GetLenToPosState2(lenNorm); + if (dist < kNumFullDistances) + price += p->distancesPrices[lenNorm][dist & (kNumFullDistances - 1)]; + else + price += p->posSlotPrices[lenNorm][posSlot] + p->alignPrices[dist & kAlignMask]; + + opt = &p->opt[cur + len]; + if (price < opt->price) + { + opt->price = price; + opt->len = (UInt32)len; + opt->dist = dist + LZMA_NUM_REPS; + opt->extra = 0; + } } - if (/*_maxMode && */lenTest == matches[offs]) + if (len == MATCHES[offs]) { - /* Try Match + Literal + Rep0 */ - const Byte *data2 = data - curBack - 1; - UInt32 lenTest2 = lenTest + 1; - UInt32 limit = lenTest2 + p->numFastBytes; + // if (p->_maxMode) { + // MATCH : LIT : REP_0 + + const Byte *data2 = data - dist - 1; + unsigned len2 = len + 1; + unsigned limit = len2 + p->numFastBytes; if (limit > numAvailFull) limit = numAvailFull; - for (; lenTest2 < limit && data[lenTest2] == data2[lenTest2]; lenTest2++); - lenTest2 -= lenTest + 1; - if (lenTest2 >= 2) + + len2 += 2; + if (len2 <= limit) + if (data[len2 - 2] == data2[len2 - 2]) + if (data[len2 - 1] == data2[len2 - 1]) { - UInt32 nextRepMatchPrice; - UInt32 state2 = kMatchNextStates[state]; - UInt32 posStateNext = (position + lenTest) & p->pbMask; - UInt32 curAndLenCharPrice = curAndLenPrice + - GET_PRICE_0(p->isMatch[state2][posStateNext]) + - LitEnc_GetPriceMatched(LIT_PROBS(position + lenTest, data[lenTest - 1]), - data[lenTest], data2[lenTest], p->ProbPrices); - state2 = kLiteralNextStates[state2]; - posStateNext = (posStateNext + 1) & p->pbMask; - nextRepMatchPrice = curAndLenCharPrice + - GET_PRICE_1(p->isMatch[state2][posStateNext]) + - GET_PRICE_1(p->isRep[state2]); - - /* for (; lenTest2 >= 2; lenTest2--) */ + for (; len2 < limit && data[len2] == data2[len2]; len2++) + {} + + len2 -= len; + + // if (len2 >= 3) + { + unsigned state2 = kMatchNextStates[state]; + unsigned posState2 = (position + len) & p->pbMask; + unsigned offset; + price += GET_PRICE_0(p->isMatch[state2][posState2]); + price += LitEnc_Matched_GetPrice(LIT_PROBS(position + len, data[(size_t)len - 1]), + data[len], data2[len], p->ProbPrices); + + // state2 = kLiteralNextStates[state2]; + state2 = kState_LitAfterMatch; + + posState2 = (posState2 + 1) & p->pbMask; + price += GetPrice_Rep_0(p, state2, posState2); + + offset = cur + len + len2; + + if (last < offset) + last = offset; + // do { - UInt32 offset = cur + lenTest + 1 + lenTest2; - UInt32 curAndLenPrice2; + UInt32 price2; COptimal *opt; - while (lenEnd < offset) - p->opt[++lenEnd].price = kInfinityPrice; - curAndLenPrice2 = nextRepMatchPrice + GetRepPrice(p, 0, lenTest2, state2, posStateNext); + len2--; + // price2 = price + GetPrice_Len_Rep_0(p, len2, state2, posState2); + price2 = price + GET_PRICE_LEN(&p->repLenEnc, posState2, len2); opt = &p->opt[offset]; - if (curAndLenPrice2 < opt->price) + // offset--; + if (price2 < opt->price) { - opt->price = curAndLenPrice2; - opt->posPrev = cur + lenTest + 1; - opt->backPrev = 0; - opt->prev1IsChar = True; - opt->prev2 = True; - opt->posPrev2 = cur; - opt->backPrev2 = curBack + LZMA_NUM_REPS; + opt->price = price2; + opt->len = (UInt32)len2; + opt->extra = (CExtra)(len + 1); + opt->dist = dist + LZMA_NUM_REPS; } } + // while (len2 >= 3); + } + } + offs += 2; if (offs == numPairs) break; - curBack = matches[offs + 1]; - if (curBack >= kNumFullDistances) - GetPosSlot2(curBack, posSlot); + dist = MATCHES[(size_t)offs + 1]; + // if (dist >= kNumFullDistances) + GetPosSlot2(dist, posSlot) } } } } + + do + p->opt[last].price = kInfinityPrice; + while (--last); + + return Backward(p, cur); } + + #define ChangePair(smallDist, bigDist) (((bigDist) >> 7) > (smallDist)) -static UInt32 GetOptimumFast(CLzmaEnc *p, UInt32 *backRes) + + +static unsigned GetOptimumFast(CLzmaEnc *p) { - UInt32 numAvail, mainLen, mainDist, numPairs, repIndex, repLen, i; + UInt32 numAvail, mainDist; + unsigned mainLen, numPairs, repIndex, repLen, i; const Byte *data; - const UInt32 *matches; if (p->additionalOffset == 0) mainLen = ReadMatchDistances(p, &numPairs); else { - mainLen = p->longestMatchLength; + mainLen = p->longestMatchLen; numPairs = p->numPairs; } numAvail = p->numAvail; - *backRes = (UInt32)-1; + p->backRes = MARK_LIT; if (numAvail < 2) return 1; + // if (mainLen < 2 && p->state == 0) return 1; // 18.06.notused if (numAvail > LZMA_MATCH_LEN_MAX) numAvail = LZMA_MATCH_LEN_MAX; data = p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - 1; - repLen = repIndex = 0; + for (i = 0; i < LZMA_NUM_REPS; i++) { - UInt32 len; - const Byte *data2 = data - p->reps[i] - 1; + unsigned len; + const Byte *data2 = data - p->reps[i]; if (data[0] != data2[0] || data[1] != data2[1]) continue; - for (len = 2; len < numAvail && data[len] == data2[len]; len++); + for (len = 2; len < numAvail && data[len] == data2[len]; len++) + {} if (len >= p->numFastBytes) { - *backRes = i; - MovePos(p, len - 1); + p->backRes = (UInt32)i; + MOVE_POS(p, len - 1) return len; } if (len > repLen) @@ -1594,98 +2018,182 @@ static UInt32 GetOptimumFast(CLzmaEnc *p, UInt32 *backRes) } } - matches = p->matches; if (mainLen >= p->numFastBytes) { - *backRes = matches[numPairs - 1] + LZMA_NUM_REPS; - MovePos(p, mainLen - 1); + p->backRes = p->matches[(size_t)numPairs - 1] + LZMA_NUM_REPS; + MOVE_POS(p, mainLen - 1) return mainLen; } mainDist = 0; /* for GCC */ + if (mainLen >= 2) { - mainDist = matches[numPairs - 1]; - while (numPairs > 2 && mainLen == matches[numPairs - 4] + 1) + mainDist = p->matches[(size_t)numPairs - 1]; + while (numPairs > 2) { - if (!ChangePair(matches[numPairs - 3], mainDist)) + UInt32 dist2; + if (mainLen != p->matches[(size_t)numPairs - 4] + 1) + break; + dist2 = p->matches[(size_t)numPairs - 3]; + if (!ChangePair(dist2, mainDist)) break; numPairs -= 2; - mainLen = matches[numPairs - 2]; - mainDist = matches[numPairs - 1]; + mainLen--; + mainDist = dist2; } if (mainLen == 2 && mainDist >= 0x80) mainLen = 1; } - if (repLen >= 2 && ( - (repLen + 1 >= mainLen) || - (repLen + 2 >= mainLen && mainDist >= (1 << 9)) || - (repLen + 3 >= mainLen && mainDist >= (1 << 15)))) + if (repLen >= 2) + if ( repLen + 1 >= mainLen + || (repLen + 2 >= mainLen && mainDist >= (1 << 9)) + || (repLen + 3 >= mainLen && mainDist >= (1 << 15))) { - *backRes = repIndex; - MovePos(p, repLen - 1); + p->backRes = (UInt32)repIndex; + MOVE_POS(p, repLen - 1) return repLen; } if (mainLen < 2 || numAvail <= 2) return 1; - p->longestMatchLength = ReadMatchDistances(p, &p->numPairs); - if (p->longestMatchLength >= 2) { - UInt32 newDistance = matches[p->numPairs - 1]; - if ((p->longestMatchLength >= mainLen && newDistance < mainDist) || - (p->longestMatchLength == mainLen + 1 && !ChangePair(mainDist, newDistance)) || - (p->longestMatchLength > mainLen + 1) || - (p->longestMatchLength + 1 >= mainLen && mainLen >= 3 && ChangePair(newDistance, mainDist))) - return 1; + unsigned len1 = ReadMatchDistances(p, &p->numPairs); + p->longestMatchLen = len1; + + if (len1 >= 2) + { + UInt32 newDist = p->matches[(size_t)p->numPairs - 1]; + if ( (len1 >= mainLen && newDist < mainDist) + || (len1 == mainLen + 1 && !ChangePair(mainDist, newDist)) + || (len1 > mainLen + 1) + || (len1 + 1 >= mainLen && mainLen >= 3 && ChangePair(newDist, mainDist))) + return 1; + } } data = p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - 1; + for (i = 0; i < LZMA_NUM_REPS; i++) { - UInt32 len, limit; - const Byte *data2 = data - p->reps[i] - 1; + unsigned len, limit; + const Byte *data2 = data - p->reps[i]; if (data[0] != data2[0] || data[1] != data2[1]) continue; limit = mainLen - 1; - for (len = 2; len < limit && data[len] == data2[len]; len++); - if (len >= limit) - return 1; + for (len = 2;; len++) + { + if (len >= limit) + return 1; + if (data[len] != data2[len]) + break; + } + } + + p->backRes = mainDist + LZMA_NUM_REPS; + if (mainLen != 2) + { + MOVE_POS(p, mainLen - 2) } - *backRes = mainDist + LZMA_NUM_REPS; - MovePos(p, mainLen - 2); return mainLen; } -static void WriteEndMarker(CLzmaEnc *p, UInt32 posState) + + + +static void WriteEndMarker(CLzmaEnc *p, unsigned posState) { - UInt32 len; - RangeEnc_EncodeBit(&p->rc, &p->isMatch[p->state][posState], 1); - RangeEnc_EncodeBit(&p->rc, &p->isRep[p->state], 0); + UInt32 range; + range = p->rc.range; + { + UInt32 ttt, newBound; + CLzmaProb *prob = &p->isMatch[p->state][posState]; + RC_BIT_PRE(&p->rc, prob) + RC_BIT_1(&p->rc, prob) + prob = &p->isRep[p->state]; + RC_BIT_PRE(&p->rc, prob) + RC_BIT_0(&p->rc, prob) + } p->state = kMatchNextStates[p->state]; - len = LZMA_MATCH_LEN_MIN; - LenEnc_Encode2(&p->lenEnc, &p->rc, len - LZMA_MATCH_LEN_MIN, posState, !p->fastMode, p->ProbPrices); - RcTree_Encode(&p->rc, p->posSlotEncoder[GetLenToPosState(len)], kNumPosSlotBits, (1 << kNumPosSlotBits) - 1); - RangeEnc_EncodeDirectBits(&p->rc, (((UInt32)1 << 30) - 1) >> kNumAlignBits, 30 - kNumAlignBits); - RcTree_ReverseEncode(&p->rc, p->posAlignEncoder, kNumAlignBits, kAlignMask); + + p->rc.range = range; + LenEnc_Encode(&p->lenProbs, &p->rc, 0, posState); + range = p->rc.range; + + { + // RcTree_Encode_PosSlot(&p->rc, p->posSlotEncoder[0], (1 << kNumPosSlotBits) - 1); + CLzmaProb *probs = p->posSlotEncoder[0]; + unsigned m = 1; + do + { + UInt32 ttt, newBound; + RC_BIT_PRE(p, probs + m) + RC_BIT_1(&p->rc, probs + m) + m = (m << 1) + 1; + } + while (m < (1 << kNumPosSlotBits)); + } + { + // RangeEnc_EncodeDirectBits(&p->rc, ((UInt32)1 << (30 - kNumAlignBits)) - 1, 30 - kNumAlignBits); UInt32 range = p->range; + unsigned numBits = 30 - kNumAlignBits; + do + { + range >>= 1; + p->rc.low += range; + RC_NORM(&p->rc) + } + while (--numBits); + } + + { + // RcTree_ReverseEncode(&p->rc, p->posAlignEncoder, kNumAlignBits, kAlignMask); + CLzmaProb *probs = p->posAlignEncoder; + unsigned m = 1; + do + { + UInt32 ttt, newBound; + RC_BIT_PRE(p, probs + m) + RC_BIT_1(&p->rc, probs + m) + m = (m << 1) + 1; + } + while (m < kAlignTableSize); + } + p->rc.range = range; } + static SRes CheckErrors(CLzmaEnc *p) { if (p->result != SZ_OK) return p->result; if (p->rc.res != SZ_OK) p->result = SZ_ERROR_WRITE; - if (p->matchFinderBase.result != SZ_OK) + + #ifndef Z7_ST + if ( + // p->mf_Failure || + (p->mtMode && + ( // p->matchFinderMt.failure_LZ_LZ || + p->matchFinderMt.failure_LZ_BT)) + ) + { + p->result = MY_HRES_ERROR_INTERNAL_ERROR; + // printf("\nCheckErrors p->matchFinderMt.failureLZ\n"); + } + #endif + + if (MFB.result != SZ_OK) p->result = SZ_ERROR_READ; + if (p->result != SZ_OK) p->finished = True; return p->result; } -static SRes Flush(CLzmaEnc *p, UInt32 nowPos) + +Z7_NO_INLINE static SRes Flush(CLzmaEnc *p, UInt32 nowPos) { /* ReleaseMFStream(); */ p->finished = True; @@ -1696,61 +2204,140 @@ static SRes Flush(CLzmaEnc *p, UInt32 nowPos) return CheckErrors(p); } -static void FillAlignPrices(CLzmaEnc *p) + +Z7_NO_INLINE static void FillAlignPrices(CLzmaEnc *p) { - UInt32 i; - for (i = 0; i < kAlignTableSize; i++) - p->alignPrices[i] = RcTree_ReverseGetPrice(p->posAlignEncoder, kNumAlignBits, i, p->ProbPrices); - p->alignPriceCount = 0; + unsigned i; + const CProbPrice *ProbPrices = p->ProbPrices; + const CLzmaProb *probs = p->posAlignEncoder; + // p->alignPriceCount = 0; + for (i = 0; i < kAlignTableSize / 2; i++) + { + UInt32 price = 0; + unsigned sym = i; + unsigned m = 1; + unsigned bit; + UInt32 prob; + bit = sym & 1; sym >>= 1; price += GET_PRICEa(probs[m], bit); m = (m << 1) + bit; + bit = sym & 1; sym >>= 1; price += GET_PRICEa(probs[m], bit); m = (m << 1) + bit; + bit = sym & 1; sym >>= 1; price += GET_PRICEa(probs[m], bit); m = (m << 1) + bit; + prob = probs[m]; + p->alignPrices[i ] = price + GET_PRICEa_0(prob); + p->alignPrices[i + 8] = price + GET_PRICEa_1(prob); + // p->alignPrices[i] = RcTree_ReverseGetPrice(p->posAlignEncoder, kNumAlignBits, i, p->ProbPrices); + } } -static void FillDistancesPrices(CLzmaEnc *p) + +Z7_NO_INLINE static void FillDistancesPrices(CLzmaEnc *p) { + // int y; for (y = 0; y < 100; y++) { + UInt32 tempPrices[kNumFullDistances]; - UInt32 i, lenToPosState; - for (i = kStartPosModelIndex; i < kNumFullDistances; i++) - { - UInt32 posSlot = GetPosSlot1(i); - UInt32 footerBits = ((posSlot >> 1) - 1); - UInt32 base = ((2 | (posSlot & 1)) << footerBits); - tempPrices[i] = RcTree_ReverseGetPrice(p->posEncoders + base - posSlot - 1, footerBits, i - base, p->ProbPrices); + unsigned i, lps; + + const CProbPrice *ProbPrices = p->ProbPrices; + p->matchPriceCount = 0; + + for (i = kStartPosModelIndex / 2; i < kNumFullDistances / 2; i++) + { + unsigned posSlot = GetPosSlot1(i); + unsigned footerBits = (posSlot >> 1) - 1; + unsigned base = ((2 | (posSlot & 1)) << footerBits); + const CLzmaProb *probs = p->posEncoders + (size_t)base * 2; + // tempPrices[i] = RcTree_ReverseGetPrice(p->posEncoders + base, footerBits, i - base, p->ProbPrices); + UInt32 price = 0; + unsigned m = 1; + unsigned sym = i; + unsigned offset = (unsigned)1 << footerBits; + base += i; + + if (footerBits) + do + { + unsigned bit = sym & 1; + sym >>= 1; + price += GET_PRICEa(probs[m], bit); + m = (m << 1) + bit; + } + while (--footerBits); + + { + unsigned prob = probs[m]; + tempPrices[base ] = price + GET_PRICEa_0(prob); + tempPrices[base + offset] = price + GET_PRICEa_1(prob); + } } - for (lenToPosState = 0; lenToPosState < kNumLenToPosStates; lenToPosState++) + for (lps = 0; lps < kNumLenToPosStates; lps++) { - UInt32 posSlot; - const CLzmaProb *encoder = p->posSlotEncoder[lenToPosState]; - UInt32 *posSlotPrices = p->posSlotPrices[lenToPosState]; - for (posSlot = 0; posSlot < p->distTableSize; posSlot++) - posSlotPrices[posSlot] = RcTree_GetPrice(encoder, kNumPosSlotBits, posSlot, p->ProbPrices); - for (posSlot = kEndPosModelIndex; posSlot < p->distTableSize; posSlot++) - posSlotPrices[posSlot] += ((((posSlot >> 1) - 1) - kNumAlignBits) << kNumBitPriceShiftBits); + unsigned slot; + unsigned distTableSize2 = (p->distTableSize + 1) >> 1; + UInt32 *posSlotPrices = p->posSlotPrices[lps]; + const CLzmaProb *probs = p->posSlotEncoder[lps]; + + for (slot = 0; slot < distTableSize2; slot++) + { + // posSlotPrices[slot] = RcTree_GetPrice(encoder, kNumPosSlotBits, slot, p->ProbPrices); + UInt32 price; + unsigned bit; + unsigned sym = slot + (1 << (kNumPosSlotBits - 1)); + unsigned prob; + bit = sym & 1; sym >>= 1; price = GET_PRICEa(probs[sym], bit); + bit = sym & 1; sym >>= 1; price += GET_PRICEa(probs[sym], bit); + bit = sym & 1; sym >>= 1; price += GET_PRICEa(probs[sym], bit); + bit = sym & 1; sym >>= 1; price += GET_PRICEa(probs[sym], bit); + bit = sym & 1; sym >>= 1; price += GET_PRICEa(probs[sym], bit); + prob = probs[(size_t)slot + (1 << (kNumPosSlotBits - 1))]; + posSlotPrices[(size_t)slot * 2 ] = price + GET_PRICEa_0(prob); + posSlotPrices[(size_t)slot * 2 + 1] = price + GET_PRICEa_1(prob); + } + + { + UInt32 delta = ((UInt32)((kEndPosModelIndex / 2 - 1) - kNumAlignBits) << kNumBitPriceShiftBits); + for (slot = kEndPosModelIndex / 2; slot < distTableSize2; slot++) + { + posSlotPrices[(size_t)slot * 2 ] += delta; + posSlotPrices[(size_t)slot * 2 + 1] += delta; + delta += ((UInt32)1 << kNumBitPriceShiftBits); + } + } { - UInt32 *distancesPrices = p->distancesPrices[lenToPosState]; - for (i = 0; i < kStartPosModelIndex; i++) - distancesPrices[i] = posSlotPrices[i]; - for (; i < kNumFullDistances; i++) - distancesPrices[i] = posSlotPrices[GetPosSlot1(i)] + tempPrices[i]; + UInt32 *dp = p->distancesPrices[lps]; + + dp[0] = posSlotPrices[0]; + dp[1] = posSlotPrices[1]; + dp[2] = posSlotPrices[2]; + dp[3] = posSlotPrices[3]; + + for (i = 4; i < kNumFullDistances; i += 2) + { + UInt32 slotPrice = posSlotPrices[GetPosSlot1(i)]; + dp[i ] = slotPrice + tempPrices[i]; + dp[i + 1] = slotPrice + tempPrices[i + 1]; + } } } - p->matchPriceCount = 0; + // } } -void LzmaEnc_Construct(CLzmaEnc *p) + + +static void LzmaEnc_Construct(CLzmaEnc *p) { RangeEnc_Construct(&p->rc); - MatchFinder_Construct(&p->matchFinderBase); + MatchFinder_Construct(&MFB); - #ifndef _7ZIP_ST + #ifndef Z7_ST + p->matchFinderMt.MatchFinder = &MFB; MatchFinderMt_Construct(&p->matchFinderMt); - p->matchFinderMt.MatchFinder = &p->matchFinderBase; #endif { CLzmaEncProps props; LzmaEncProps_Init(&props); - LzmaEnc_SetProps(p, &props); + LzmaEnc_SetProps((CLzmaEncHandle)(void *)p, &props); } #ifndef LZMA_LOG_BSR @@ -1762,65 +2349,73 @@ void LzmaEnc_Construct(CLzmaEnc *p) p->saveState.litProbs = NULL; } -CLzmaEncHandle LzmaEnc_Create(ISzAlloc *alloc) +CLzmaEncHandle LzmaEnc_Create(ISzAllocPtr alloc) { - void *p; - p = alloc->Alloc(alloc, sizeof(CLzmaEnc)); + CLzmaEncHandle p = (CLzmaEncHandle)ISzAlloc_Alloc(alloc, sizeof(CLzmaEnc)); if (p) - LzmaEnc_Construct((CLzmaEnc *)p); + LzmaEnc_Construct(p); return p; } -void LzmaEnc_FreeLits(CLzmaEnc *p, ISzAlloc *alloc) +static void LzmaEnc_FreeLits(CLzmaEnc *p, ISzAllocPtr alloc) { - alloc->Free(alloc, p->litProbs); - alloc->Free(alloc, p->saveState.litProbs); + ISzAlloc_Free(alloc, p->litProbs); + ISzAlloc_Free(alloc, p->saveState.litProbs); p->litProbs = NULL; p->saveState.litProbs = NULL; } -void LzmaEnc_Destruct(CLzmaEnc *p, ISzAlloc *alloc, ISzAlloc *allocBig) +static void LzmaEnc_Destruct(CLzmaEnc *p, ISzAllocPtr alloc, ISzAllocPtr allocBig) { - #ifndef _7ZIP_ST + #ifndef Z7_ST MatchFinderMt_Destruct(&p->matchFinderMt, allocBig); #endif - MatchFinder_Free(&p->matchFinderBase, allocBig); + MatchFinder_Free(&MFB, allocBig); LzmaEnc_FreeLits(p, alloc); RangeEnc_Free(&p->rc, alloc); } -void LzmaEnc_Destroy(CLzmaEncHandle p, ISzAlloc *alloc, ISzAlloc *allocBig) +void LzmaEnc_Destroy(CLzmaEncHandle p, ISzAllocPtr alloc, ISzAllocPtr allocBig) { - LzmaEnc_Destruct((CLzmaEnc *)p, alloc, allocBig); - alloc->Free(alloc, p); + // GET_CLzmaEnc_p + LzmaEnc_Destruct(p, alloc, allocBig); + ISzAlloc_Free(alloc, p); } -static SRes LzmaEnc_CodeOneBlock(CLzmaEnc *p, Bool useLimits, UInt32 maxPackSize, UInt32 maxUnpackSize) + +Z7_NO_INLINE +static SRes LzmaEnc_CodeOneBlock(CLzmaEnc *p, UInt32 maxPackSize, UInt32 maxUnpackSize) { UInt32 nowPos32, startPos32; if (p->needInit) { + #ifndef Z7_ST + if (p->mtMode) + { + RINOK(MatchFinderMt_InitMt(&p->matchFinderMt)) + } + #endif p->matchFinder.Init(p->matchFinderObj); p->needInit = 0; } if (p->finished) return p->result; - RINOK(CheckErrors(p)); + RINOK(CheckErrors(p)) nowPos32 = (UInt32)p->nowPos64; startPos32 = nowPos32; if (p->nowPos64 == 0) { - UInt32 numPairs; + unsigned numPairs; Byte curByte; if (p->matchFinder.GetNumAvailableBytes(p->matchFinderObj) == 0) return Flush(p, nowPos32); ReadMatchDistances(p, &numPairs); - RangeEnc_EncodeBit(&p->rc, &p->isMatch[p->state][0], 0); - p->state = kLiteralNextStates[p->state]; + RangeEnc_EncodeBit_0(&p->rc, &p->isMatch[kState_Start][0]); + // p->state = kLiteralNextStates[p->state]; curByte = *(p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - p->additionalOffset); LitEnc_Encode(&p->rc, p->litProbs, curByte); p->additionalOffset--; @@ -1828,123 +2423,253 @@ static SRes LzmaEnc_CodeOneBlock(CLzmaEnc *p, Bool useLimits, UInt32 maxPackSize } if (p->matchFinder.GetNumAvailableBytes(p->matchFinderObj) != 0) + for (;;) { - UInt32 pos, len, posState; - + UInt32 dist; + unsigned len, posState; + UInt32 range, ttt, newBound; + CLzmaProb *probs; + if (p->fastMode) - len = GetOptimumFast(p, &pos); + len = GetOptimumFast(p); else - len = GetOptimum(p, nowPos32, &pos); + { + unsigned oci = p->optCur; + if (p->optEnd == oci) + len = GetOptimum(p, nowPos32); + else + { + const COptimal *opt = &p->opt[oci]; + len = opt->len; + p->backRes = opt->dist; + p->optCur = oci + 1; + } + } + + posState = (unsigned)nowPos32 & p->pbMask; + range = p->rc.range; + probs = &p->isMatch[p->state][posState]; + + RC_BIT_PRE(&p->rc, probs) + + dist = p->backRes; #ifdef SHOW_STAT2 - printf("\n pos = %4X, len = %u pos = %u", nowPos32, len, pos); + printf("\n pos = %6X, len = %3u pos = %6u", nowPos32, len, dist); #endif - posState = nowPos32 & p->pbMask; - if (len == 1 && pos == (UInt32)-1) + if (dist == MARK_LIT) { Byte curByte; - CLzmaProb *probs; const Byte *data; + unsigned state; - RangeEnc_EncodeBit(&p->rc, &p->isMatch[p->state][posState], 0); + RC_BIT_0(&p->rc, probs) + p->rc.range = range; data = p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - p->additionalOffset; - curByte = *data; probs = LIT_PROBS(nowPos32, *(data - 1)); - if (IsCharState(p->state)) + curByte = *data; + state = p->state; + p->state = kLiteralNextStates[state]; + if (IsLitState(state)) LitEnc_Encode(&p->rc, probs, curByte); else - LitEnc_EncodeMatched(&p->rc, probs, curByte, *(data - p->reps[0] - 1)); - p->state = kLiteralNextStates[p->state]; + LitEnc_EncodeMatched(&p->rc, probs, curByte, *(data - p->reps[0])); } else { - RangeEnc_EncodeBit(&p->rc, &p->isMatch[p->state][posState], 1); - if (pos < LZMA_NUM_REPS) + RC_BIT_1(&p->rc, probs) + probs = &p->isRep[p->state]; + RC_BIT_PRE(&p->rc, probs) + + if (dist < LZMA_NUM_REPS) { - RangeEnc_EncodeBit(&p->rc, &p->isRep[p->state], 1); - if (pos == 0) + RC_BIT_1(&p->rc, probs) + probs = &p->isRepG0[p->state]; + RC_BIT_PRE(&p->rc, probs) + if (dist == 0) { - RangeEnc_EncodeBit(&p->rc, &p->isRepG0[p->state], 0); - RangeEnc_EncodeBit(&p->rc, &p->isRep0Long[p->state][posState], ((len == 1) ? 0 : 1)); + RC_BIT_0(&p->rc, probs) + probs = &p->isRep0Long[p->state][posState]; + RC_BIT_PRE(&p->rc, probs) + if (len != 1) + { + RC_BIT_1_BASE(&p->rc, probs) + } + else + { + RC_BIT_0_BASE(&p->rc, probs) + p->state = kShortRepNextStates[p->state]; + } } else { - UInt32 distance = p->reps[pos]; - RangeEnc_EncodeBit(&p->rc, &p->isRepG0[p->state], 1); - if (pos == 1) - RangeEnc_EncodeBit(&p->rc, &p->isRepG1[p->state], 0); + RC_BIT_1(&p->rc, probs) + probs = &p->isRepG1[p->state]; + RC_BIT_PRE(&p->rc, probs) + if (dist == 1) + { + RC_BIT_0_BASE(&p->rc, probs) + dist = p->reps[1]; + } else { - RangeEnc_EncodeBit(&p->rc, &p->isRepG1[p->state], 1); - RangeEnc_EncodeBit(&p->rc, &p->isRepG2[p->state], pos - 2); - if (pos == 3) + RC_BIT_1(&p->rc, probs) + probs = &p->isRepG2[p->state]; + RC_BIT_PRE(&p->rc, probs) + if (dist == 2) + { + RC_BIT_0_BASE(&p->rc, probs) + dist = p->reps[2]; + } + else + { + RC_BIT_1_BASE(&p->rc, probs) + dist = p->reps[3]; p->reps[3] = p->reps[2]; + } p->reps[2] = p->reps[1]; } p->reps[1] = p->reps[0]; - p->reps[0] = distance; + p->reps[0] = dist; } - if (len == 1) - p->state = kShortRepNextStates[p->state]; - else + + RC_NORM(&p->rc) + + p->rc.range = range; + + if (len != 1) { - LenEnc_Encode2(&p->repLenEnc, &p->rc, len - LZMA_MATCH_LEN_MIN, posState, !p->fastMode, p->ProbPrices); + LenEnc_Encode(&p->repLenProbs, &p->rc, len - LZMA_MATCH_LEN_MIN, posState); + --p->repLenEncCounter; p->state = kRepNextStates[p->state]; } } else { - UInt32 posSlot; - RangeEnc_EncodeBit(&p->rc, &p->isRep[p->state], 0); + unsigned posSlot; + RC_BIT_0(&p->rc, probs) + p->rc.range = range; p->state = kMatchNextStates[p->state]; - LenEnc_Encode2(&p->lenEnc, &p->rc, len - LZMA_MATCH_LEN_MIN, posState, !p->fastMode, p->ProbPrices); - pos -= LZMA_NUM_REPS; - GetPosSlot(pos, posSlot); - RcTree_Encode(&p->rc, p->posSlotEncoder[GetLenToPosState(len)], kNumPosSlotBits, posSlot); + + LenEnc_Encode(&p->lenProbs, &p->rc, len - LZMA_MATCH_LEN_MIN, posState); + // --p->lenEnc.counter; + + dist -= LZMA_NUM_REPS; + p->reps[3] = p->reps[2]; + p->reps[2] = p->reps[1]; + p->reps[1] = p->reps[0]; + p->reps[0] = dist + 1; - if (posSlot >= kStartPosModelIndex) + p->matchPriceCount++; + GetPosSlot(dist, posSlot) + // RcTree_Encode_PosSlot(&p->rc, p->posSlotEncoder[GetLenToPosState(len)], posSlot); + { + UInt32 sym = (UInt32)posSlot + (1 << kNumPosSlotBits); + range = p->rc.range; + probs = p->posSlotEncoder[GetLenToPosState(len)]; + do + { + CLzmaProb *prob = probs + (sym >> kNumPosSlotBits); + UInt32 bit = (sym >> (kNumPosSlotBits - 1)) & 1; + sym <<= 1; + RC_BIT(&p->rc, prob, bit) + } + while (sym < (1 << kNumPosSlotBits * 2)); + p->rc.range = range; + } + + if (dist >= kStartPosModelIndex) { - UInt32 footerBits = ((posSlot >> 1) - 1); - UInt32 base = ((2 | (posSlot & 1)) << footerBits); - UInt32 posReduced = pos - base; + unsigned footerBits = ((posSlot >> 1) - 1); - if (posSlot < kEndPosModelIndex) - RcTree_ReverseEncode(&p->rc, p->posEncoders + base - posSlot - 1, footerBits, posReduced); + if (dist < kNumFullDistances) + { + unsigned base = ((2 | (posSlot & 1)) << footerBits); + RcTree_ReverseEncode(&p->rc, p->posEncoders + base, footerBits, (unsigned)(dist /* - base */)); + } else { - RangeEnc_EncodeDirectBits(&p->rc, posReduced >> kNumAlignBits, footerBits - kNumAlignBits); - RcTree_ReverseEncode(&p->rc, p->posAlignEncoder, kNumAlignBits, posReduced & kAlignMask); - p->alignPriceCount++; + UInt32 pos2 = (dist | 0xF) << (32 - footerBits); + range = p->rc.range; + // RangeEnc_EncodeDirectBits(&p->rc, posReduced >> kNumAlignBits, footerBits - kNumAlignBits); + /* + do + { + range >>= 1; + p->rc.low += range & (0 - ((dist >> --footerBits) & 1)); + RC_NORM(&p->rc) + } + while (footerBits > kNumAlignBits); + */ + do + { + range >>= 1; + p->rc.low += range & (0 - (pos2 >> 31)); + pos2 += pos2; + RC_NORM(&p->rc) + } + while (pos2 != 0xF0000000); + + + // RcTree_ReverseEncode(&p->rc, p->posAlignEncoder, kNumAlignBits, posReduced & kAlignMask); + + { + unsigned m = 1; + unsigned bit; + bit = dist & 1; dist >>= 1; RC_BIT(&p->rc, p->posAlignEncoder + m, bit) m = (m << 1) + bit; + bit = dist & 1; dist >>= 1; RC_BIT(&p->rc, p->posAlignEncoder + m, bit) m = (m << 1) + bit; + bit = dist & 1; dist >>= 1; RC_BIT(&p->rc, p->posAlignEncoder + m, bit) m = (m << 1) + bit; + bit = dist & 1; RC_BIT(&p->rc, p->posAlignEncoder + m, bit) + p->rc.range = range; + // p->alignPriceCount++; + } } } - p->reps[3] = p->reps[2]; - p->reps[2] = p->reps[1]; - p->reps[1] = p->reps[0]; - p->reps[0] = pos; - p->matchPriceCount++; } } + + nowPos32 += (UInt32)len; p->additionalOffset -= len; - nowPos32 += len; + if (p->additionalOffset == 0) { UInt32 processed; + if (!p->fastMode) { - if (p->matchPriceCount >= (1 << 7)) + /* + if (p->alignPriceCount >= 16) // kAlignTableSize + FillAlignPrices(p); + if (p->matchPriceCount >= 128) FillDistancesPrices(p); - if (p->alignPriceCount >= kAlignTableSize) + if (p->lenEnc.counter <= 0) + LenPriceEnc_UpdateTables(&p->lenEnc, 1 << p->pb, &p->lenProbs, p->ProbPrices); + */ + if (p->matchPriceCount >= 64) + { FillAlignPrices(p); + // { int y; for (y = 0; y < 100; y++) { + FillDistancesPrices(p); + // }} + LenPriceEnc_UpdateTables(&p->lenEnc, (unsigned)1 << p->pb, &p->lenProbs, p->ProbPrices); + } + if (p->repLenEncCounter <= 0) + { + p->repLenEncCounter = REP_LEN_COUNT; + LenPriceEnc_UpdateTables(&p->repLenEnc, (unsigned)1 << p->pb, &p->repLenProbs, p->ProbPrices); + } } + if (p->matchFinder.GetNumAvailableBytes(p->matchFinderObj) == 0) break; processed = nowPos32 - startPos32; - if (useLimits) + + if (maxPackSize) { - if (processed + kNumOpts + 300 >= maxUnpackSize || - RangeEnc_GetProcessed(&p->rc) + kNumOpts * 2 >= maxPackSize) + if (processed + kNumOpts + 300 >= maxUnpackSize + || RangeEnc_GetProcessed_sizet(&p->rc) + kPackReserve >= maxPackSize) break; } else if (processed >= (1 << 17)) @@ -1954,29 +2679,34 @@ static SRes LzmaEnc_CodeOneBlock(CLzmaEnc *p, Bool useLimits, UInt32 maxPackSize } } } + p->nowPos64 += nowPos32 - startPos32; return Flush(p, nowPos32); } + + #define kBigHashDicLimit ((UInt32)1 << 24) -static SRes LzmaEnc_Alloc(CLzmaEnc *p, UInt32 keepWindowSize, ISzAlloc *alloc, ISzAlloc *allocBig) +static SRes LzmaEnc_Alloc(CLzmaEnc *p, UInt32 keepWindowSize, ISzAllocPtr alloc, ISzAllocPtr allocBig) { UInt32 beforeSize = kNumOpts; + UInt32 dictSize; + if (!RangeEnc_Alloc(&p->rc, alloc)) return SZ_ERROR_MEM; - #ifndef _7ZIP_ST - p->mtMode = (p->multiThread && !p->fastMode && (p->matchFinderBase.btMode != 0)); + #ifndef Z7_ST + p->mtMode = (p->multiThread && !p->fastMode && (MFB.btMode != 0)); #endif { - unsigned lclp = p->lc + p->lp; + const unsigned lclp = p->lc + p->lp; if (!p->litProbs || !p->saveState.litProbs || p->lclp != lclp) { LzmaEnc_FreeLits(p, alloc); - p->litProbs = (CLzmaProb *)alloc->Alloc(alloc, ((UInt32)0x300 << lclp) * sizeof(CLzmaProb)); - p->saveState.litProbs = (CLzmaProb *)alloc->Alloc(alloc, ((UInt32)0x300 << lclp) * sizeof(CLzmaProb)); + p->litProbs = (CLzmaProb *)ISzAlloc_Alloc(alloc, ((size_t)0x300 * sizeof(CLzmaProb)) << lclp); + p->saveState.litProbs = (CLzmaProb *)ISzAlloc_Alloc(alloc, ((size_t)0x300 * sizeof(CLzmaProb)) << lclp); if (!p->litProbs || !p->saveState.litProbs) { LzmaEnc_FreeLits(p, alloc); @@ -1986,43 +2716,71 @@ static SRes LzmaEnc_Alloc(CLzmaEnc *p, UInt32 keepWindowSize, ISzAlloc *alloc, I } } - p->matchFinderBase.bigHash = (Byte)(p->dictSize > kBigHashDicLimit ? 1 : 0); + MFB.bigHash = (Byte)(p->dictSize > kBigHashDicLimit ? 1 : 0); + + + dictSize = p->dictSize; + if (dictSize == ((UInt32)2 << 30) || + dictSize == ((UInt32)3 << 30)) + { + /* 21.03 : here we reduce the dictionary for 2 reasons: + 1) we don't want 32-bit back_distance matches in decoder for 2 GB dictionary. + 2) we want to elimate useless last MatchFinder_Normalize3() for corner cases, + where data size is aligned for 1 GB: 5/6/8 GB. + That reducing must be >= 1 for such corner cases. */ + dictSize -= 1; + } + + if (beforeSize + dictSize < keepWindowSize) + beforeSize = keepWindowSize - dictSize; - if (beforeSize + p->dictSize < keepWindowSize) - beforeSize = keepWindowSize - p->dictSize; + /* in worst case we can look ahead for + max(LZMA_MATCH_LEN_MAX, numFastBytes + 1 + numFastBytes) bytes. + we send larger value for (keepAfter) to MantchFinder_Create(): + (numFastBytes + LZMA_MATCH_LEN_MAX + 1) + */ - #ifndef _7ZIP_ST + #ifndef Z7_ST if (p->mtMode) { - RINOK(MatchFinderMt_Create(&p->matchFinderMt, p->dictSize, beforeSize, p->numFastBytes, LZMA_MATCH_LEN_MAX, allocBig)); + RINOK(MatchFinderMt_Create(&p->matchFinderMt, dictSize, beforeSize, + p->numFastBytes, LZMA_MATCH_LEN_MAX + 1 /* 18.04 */ + , allocBig)) p->matchFinderObj = &p->matchFinderMt; + MFB.bigHash = (Byte)(MFB.hashMask >= 0xFFFFFF ? 1 : 0); MatchFinderMt_CreateVTable(&p->matchFinderMt, &p->matchFinder); } else #endif { - if (!MatchFinder_Create(&p->matchFinderBase, p->dictSize, beforeSize, p->numFastBytes, LZMA_MATCH_LEN_MAX, allocBig)) + if (!MatchFinder_Create(&MFB, dictSize, beforeSize, + p->numFastBytes, LZMA_MATCH_LEN_MAX + 1 /* 21.03 */ + , allocBig)) return SZ_ERROR_MEM; - p->matchFinderObj = &p->matchFinderBase; - MatchFinder_CreateVTable(&p->matchFinderBase, &p->matchFinder); + p->matchFinderObj = &MFB; + MatchFinder_CreateVTable(&MFB, &p->matchFinder); } return SZ_OK; } -void LzmaEnc_Init(CLzmaEnc *p) +static void LzmaEnc_Init(CLzmaEnc *p) { - UInt32 i; + unsigned i; p->state = 0; - for (i = 0 ; i < LZMA_NUM_REPS; i++) - p->reps[i] = 0; + p->reps[0] = + p->reps[1] = + p->reps[2] = + p->reps[3] = 1; RangeEnc_Init(&p->rc); + for (i = 0; i < (1 << kNumAlignBits); i++) + p->posAlignEncoder[i] = kProbInitValue; for (i = 0; i < kNumStates; i++) { - UInt32 j; + unsigned j; for (j = 0; j < LZMA_NUM_PB_STATES_MAX; j++) { p->isMatch[i][j] = kProbInitValue; @@ -2034,42 +2792,50 @@ void LzmaEnc_Init(CLzmaEnc *p) p->isRepG2[i] = kProbInitValue; } - { - UInt32 num = (UInt32)0x300 << (p->lp + p->lc); - CLzmaProb *probs = p->litProbs; - for (i = 0; i < num; i++) - probs[i] = kProbInitValue; - } - { for (i = 0; i < kNumLenToPosStates; i++) { CLzmaProb *probs = p->posSlotEncoder[i]; - UInt32 j; + unsigned j; for (j = 0; j < (1 << kNumPosSlotBits); j++) probs[j] = kProbInitValue; } } { - for (i = 0; i < kNumFullDistances - kEndPosModelIndex; i++) + for (i = 0; i < kNumFullDistances; i++) p->posEncoders[i] = kProbInitValue; } - LenEnc_Init(&p->lenEnc.p); - LenEnc_Init(&p->repLenEnc.p); + { + const size_t num = (size_t)0x300 << (p->lp + p->lc); + size_t k; + CLzmaProb *probs = p->litProbs; + for (k = 0; k < num; k++) + probs[k] = kProbInitValue; + } - for (i = 0; i < (1 << kNumAlignBits); i++) - p->posAlignEncoder[i] = kProbInitValue; - p->optimumEndIndex = 0; - p->optimumCurrentIndex = 0; + LenEnc_Init(&p->lenProbs); + LenEnc_Init(&p->repLenProbs); + + p->optEnd = 0; + p->optCur = 0; + + { + for (i = 0; i < kNumOpts; i++) + p->opt[i].price = kInfinityPrice; + } + p->additionalOffset = 0; - p->pbMask = (1 << p->pb) - 1; - p->lpMask = (1 << p->lp) - 1; + p->pbMask = ((unsigned)1 << p->pb) - 1; + p->lpMask = ((UInt32)0x100 << p->lp) - ((unsigned)0x100 >> p->lc); + + // p->mf_Failure = False; } -void LzmaEnc_InitPrices(CLzmaEnc *p) + +static void LzmaEnc_InitPrices(CLzmaEnc *p) { if (!p->fastMode) { @@ -2080,122 +2846,125 @@ void LzmaEnc_InitPrices(CLzmaEnc *p) p->lenEnc.tableSize = p->repLenEnc.tableSize = p->numFastBytes + 1 - LZMA_MATCH_LEN_MIN; - LenPriceEnc_UpdateTables(&p->lenEnc, 1 << p->pb, p->ProbPrices); - LenPriceEnc_UpdateTables(&p->repLenEnc, 1 << p->pb, p->ProbPrices); + + p->repLenEncCounter = REP_LEN_COUNT; + + LenPriceEnc_UpdateTables(&p->lenEnc, (unsigned)1 << p->pb, &p->lenProbs, p->ProbPrices); + LenPriceEnc_UpdateTables(&p->repLenEnc, (unsigned)1 << p->pb, &p->repLenProbs, p->ProbPrices); } -static SRes LzmaEnc_AllocAndInit(CLzmaEnc *p, UInt32 keepWindowSize, ISzAlloc *alloc, ISzAlloc *allocBig) +static SRes LzmaEnc_AllocAndInit(CLzmaEnc *p, UInt32 keepWindowSize, ISzAllocPtr alloc, ISzAllocPtr allocBig) { - UInt32 i; - for (i = 0; i < (UInt32)kDicLogSizeMaxCompress; i++) + unsigned i; + for (i = kEndPosModelIndex / 2; i < kDicLogSizeMax; i++) if (p->dictSize <= ((UInt32)1 << i)) break; p->distTableSize = i * 2; p->finished = False; p->result = SZ_OK; - RINOK(LzmaEnc_Alloc(p, keepWindowSize, alloc, allocBig)); + p->nowPos64 = 0; + p->needInit = 1; + RINOK(LzmaEnc_Alloc(p, keepWindowSize, alloc, allocBig)) LzmaEnc_Init(p); LzmaEnc_InitPrices(p); - p->nowPos64 = 0; return SZ_OK; } -static SRes LzmaEnc_Prepare(CLzmaEncHandle pp, ISeqOutStream *outStream, ISeqInStream *inStream, - ISzAlloc *alloc, ISzAlloc *allocBig) +static SRes LzmaEnc_Prepare(CLzmaEncHandle p, + ISeqOutStreamPtr outStream, + ISeqInStreamPtr inStream, + ISzAllocPtr alloc, ISzAllocPtr allocBig) { - CLzmaEnc *p = (CLzmaEnc *)pp; - p->matchFinderBase.stream = inStream; - p->needInit = 1; + // GET_CLzmaEnc_p + MatchFinder_SET_STREAM(&MFB, inStream) p->rc.outStream = outStream; return LzmaEnc_AllocAndInit(p, 0, alloc, allocBig); } -SRes LzmaEnc_PrepareForLzma2(CLzmaEncHandle pp, - ISeqInStream *inStream, UInt32 keepWindowSize, - ISzAlloc *alloc, ISzAlloc *allocBig) +SRes LzmaEnc_PrepareForLzma2(CLzmaEncHandle p, + ISeqInStreamPtr inStream, UInt32 keepWindowSize, + ISzAllocPtr alloc, ISzAllocPtr allocBig) { - CLzmaEnc *p = (CLzmaEnc *)pp; - p->matchFinderBase.stream = inStream; - p->needInit = 1; + // GET_CLzmaEnc_p + MatchFinder_SET_STREAM(&MFB, inStream) return LzmaEnc_AllocAndInit(p, keepWindowSize, alloc, allocBig); } -static void LzmaEnc_SetInputBuf(CLzmaEnc *p, const Byte *src, SizeT srcLen) +SRes LzmaEnc_MemPrepare(CLzmaEncHandle p, + const Byte *src, SizeT srcLen, + UInt32 keepWindowSize, + ISzAllocPtr alloc, ISzAllocPtr allocBig) { - p->matchFinderBase.directInput = 1; - p->matchFinderBase.bufferBase = (Byte *)src; - p->matchFinderBase.directInputRem = srcLen; -} - -SRes LzmaEnc_MemPrepare(CLzmaEncHandle pp, const Byte *src, SizeT srcLen, - UInt32 keepWindowSize, ISzAlloc *alloc, ISzAlloc *allocBig) -{ - CLzmaEnc *p = (CLzmaEnc *)pp; - LzmaEnc_SetInputBuf(p, src, srcLen); - p->needInit = 1; - + // GET_CLzmaEnc_p + MatchFinder_SET_DIRECT_INPUT_BUF(&MFB, src, srcLen) + LzmaEnc_SetDataSize(p, srcLen); return LzmaEnc_AllocAndInit(p, keepWindowSize, alloc, allocBig); } -void LzmaEnc_Finish(CLzmaEncHandle pp) +void LzmaEnc_Finish(CLzmaEncHandle p) { - #ifndef _7ZIP_ST - CLzmaEnc *p = (CLzmaEnc *)pp; + #ifndef Z7_ST + // GET_CLzmaEnc_p if (p->mtMode) MatchFinderMt_ReleaseStream(&p->matchFinderMt); #else - UNUSED_VAR(pp); + UNUSED_VAR(p) #endif } typedef struct { - ISeqOutStream funcTable; + ISeqOutStream vt; Byte *data; - SizeT rem; - Bool overflow; -} CSeqOutStreamBuf; + size_t rem; + BoolInt overflow; +} CLzmaEnc_SeqOutStreamBuf; -static size_t MyWrite(void *pp, const void *data, size_t size) +static size_t SeqOutStreamBuf_Write(ISeqOutStreamPtr pp, const void *data, size_t size) { - CSeqOutStreamBuf *p = (CSeqOutStreamBuf *)pp; + Z7_CONTAINER_FROM_VTBL_TO_DECL_VAR_pp_vt_p(CLzmaEnc_SeqOutStreamBuf) if (p->rem < size) { size = p->rem; p->overflow = True; } - memcpy(p->data, data, size); - p->rem -= size; - p->data += size; + if (size != 0) + { + memcpy(p->data, data, size); + p->rem -= size; + p->data += size; + } return size; } -UInt32 LzmaEnc_GetNumAvailableBytes(CLzmaEncHandle pp) +/* +UInt32 LzmaEnc_GetNumAvailableBytes(CLzmaEncHandle p) { - const CLzmaEnc *p = (CLzmaEnc *)pp; + GET_const_CLzmaEnc_p return p->matchFinder.GetNumAvailableBytes(p->matchFinderObj); } +*/ - -const Byte *LzmaEnc_GetCurBuf(CLzmaEncHandle pp) +const Byte *LzmaEnc_GetCurBuf(CLzmaEncHandle p) { - const CLzmaEnc *p = (CLzmaEnc *)pp; + // GET_const_CLzmaEnc_p return p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - p->additionalOffset; } -SRes LzmaEnc_CodeOneMemBlock(CLzmaEncHandle pp, Bool reInit, +// (desiredPackSize == 0) is not allowed +SRes LzmaEnc_CodeOneMemBlock(CLzmaEncHandle p, BoolInt reInit, Byte *dest, size_t *destLen, UInt32 desiredPackSize, UInt32 *unpackSize) { - CLzmaEnc *p = (CLzmaEnc *)pp; + // GET_CLzmaEnc_p UInt64 nowPos64; SRes res; - CSeqOutStreamBuf outStream; + CLzmaEnc_SeqOutStreamBuf outStream; - outStream.funcTable.Write = MyWrite; + outStream.vt.Write = SeqOutStreamBuf_Write; outStream.data = dest; outStream.rem = *destLen; outStream.overflow = False; @@ -2207,11 +2976,11 @@ SRes LzmaEnc_CodeOneMemBlock(CLzmaEncHandle pp, Bool reInit, if (reInit) LzmaEnc_Init(p); LzmaEnc_InitPrices(p); - nowPos64 = p->nowPos64; RangeEnc_Init(&p->rc); - p->rc.outStream = &outStream.funcTable; - - res = LzmaEnc_CodeOneBlock(p, True, desiredPackSize, *unpackSize); + p->rc.outStream = &outStream.vt; + nowPos64 = p->nowPos64; + + res = LzmaEnc_CodeOneBlock(p, desiredPackSize, *unpackSize); *unpackSize = (UInt32)(p->nowPos64 - nowPos64); *destLen -= outStream.rem; @@ -2222,11 +2991,12 @@ SRes LzmaEnc_CodeOneMemBlock(CLzmaEncHandle pp, Bool reInit, } -static SRes LzmaEnc_Encode2(CLzmaEnc *p, ICompressProgress *progress) +Z7_NO_INLINE +static SRes LzmaEnc_Encode2(CLzmaEnc *p, ICompressProgressPtr progress) { SRes res = SZ_OK; - #ifndef _7ZIP_ST + #ifndef Z7_ST Byte allocaDummy[0x300]; allocaDummy[0] = 0; allocaDummy[1] = allocaDummy[0]; @@ -2234,12 +3004,12 @@ static SRes LzmaEnc_Encode2(CLzmaEnc *p, ICompressProgress *progress) for (;;) { - res = LzmaEnc_CodeOneBlock(p, False, 0, 0); + res = LzmaEnc_CodeOneBlock(p, 0, 0); if (res != SZ_OK || p->finished) break; if (progress) { - res = progress->Progress(progress, p->nowPos64, RangeEnc_GetProcessed(&p->rc)); + res = ICompressProgress_Progress(progress, p->nowPos64, RangeEnc_GetProcessed(&p->rc)); if (res != SZ_OK) { res = SZ_ERROR_PROGRESS; @@ -2248,10 +3018,10 @@ static SRes LzmaEnc_Encode2(CLzmaEnc *p, ICompressProgress *progress) } } - LzmaEnc_Finish(p); + LzmaEnc_Finish((CLzmaEncHandle)(void *)p); /* - if (res == S_OK && !Inline_MatchFinder_IsFinishedOK(&p->matchFinderBase)) + if (res == SZ_OK && !Inline_MatchFinder_IsFinishedOK(&MFB)) res = SZ_ERROR_FAIL; } */ @@ -2260,59 +3030,75 @@ static SRes LzmaEnc_Encode2(CLzmaEnc *p, ICompressProgress *progress) } -SRes LzmaEnc_Encode(CLzmaEncHandle pp, ISeqOutStream *outStream, ISeqInStream *inStream, ICompressProgress *progress, - ISzAlloc *alloc, ISzAlloc *allocBig) +SRes LzmaEnc_Encode(CLzmaEncHandle p, ISeqOutStreamPtr outStream, ISeqInStreamPtr inStream, ICompressProgressPtr progress, + ISzAllocPtr alloc, ISzAllocPtr allocBig) { - RINOK(LzmaEnc_Prepare(pp, outStream, inStream, alloc, allocBig)); - return LzmaEnc_Encode2((CLzmaEnc *)pp, progress); + // GET_CLzmaEnc_p + RINOK(LzmaEnc_Prepare(p, outStream, inStream, alloc, allocBig)) + return LzmaEnc_Encode2(p, progress); } -SRes LzmaEnc_WriteProperties(CLzmaEncHandle pp, Byte *props, SizeT *size) +SRes LzmaEnc_WriteProperties(CLzmaEncHandle p, Byte *props, SizeT *size) { - CLzmaEnc *p = (CLzmaEnc *)pp; - unsigned i; - UInt32 dictSize = p->dictSize; if (*size < LZMA_PROPS_SIZE) return SZ_ERROR_PARAM; *size = LZMA_PROPS_SIZE; - props[0] = (Byte)((p->pb * 5 + p->lp) * 9 + p->lc); - - if (dictSize >= ((UInt32)1 << 22)) { - UInt32 kDictMask = ((UInt32)1 << 20) - 1; - if (dictSize < (UInt32)0xFFFFFFFF - kDictMask) - dictSize = (dictSize + kDictMask) & ~kDictMask; - } - else for (i = 11; i <= 30; i++) - { - if (dictSize <= ((UInt32)2 << i)) { dictSize = (2 << i); break; } - if (dictSize <= ((UInt32)3 << i)) { dictSize = (3 << i); break; } + // GET_CLzmaEnc_p + const UInt32 dictSize = p->dictSize; + UInt32 v; + props[0] = (Byte)((p->pb * 5 + p->lp) * 9 + p->lc); + + // we write aligned dictionary value to properties for lzma decoder + if (dictSize >= ((UInt32)1 << 21)) + { + const UInt32 kDictMask = ((UInt32)1 << 20) - 1; + v = (dictSize + kDictMask) & ~kDictMask; + if (v < dictSize) + v = dictSize; + } + else + { + unsigned i = 11 * 2; + do + { + v = (UInt32)(2 + (i & 1)) << (i >> 1); + i++; + } + while (v < dictSize); + } + + SetUi32(props + 1, v) + return SZ_OK; } +} - for (i = 0; i < 4; i++) - props[1 + i] = (Byte)(dictSize >> (8 * i)); - return SZ_OK; + +unsigned LzmaEnc_IsWriteEndMark(CLzmaEncHandle p) +{ + // GET_CLzmaEnc_p + return (unsigned)p->writeEndMark; } -SRes LzmaEnc_MemEncode(CLzmaEncHandle pp, Byte *dest, SizeT *destLen, const Byte *src, SizeT srcLen, - int writeEndMark, ICompressProgress *progress, ISzAlloc *alloc, ISzAlloc *allocBig) +SRes LzmaEnc_MemEncode(CLzmaEncHandle p, Byte *dest, SizeT *destLen, const Byte *src, SizeT srcLen, + int writeEndMark, ICompressProgressPtr progress, ISzAllocPtr alloc, ISzAllocPtr allocBig) { SRes res; - CLzmaEnc *p = (CLzmaEnc *)pp; + // GET_CLzmaEnc_p - CSeqOutStreamBuf outStream; + CLzmaEnc_SeqOutStreamBuf outStream; - outStream.funcTable.Write = MyWrite; + outStream.vt.Write = SeqOutStreamBuf_Write; outStream.data = dest; outStream.rem = *destLen; outStream.overflow = False; p->writeEndMark = writeEndMark; - p->rc.outStream = &outStream.funcTable; + p->rc.outStream = &outStream.vt; - res = LzmaEnc_MemPrepare(pp, src, srcLen, 0, alloc, allocBig); + res = LzmaEnc_MemPrepare(p, src, srcLen, 0, alloc, allocBig); if (res == SZ_OK) { @@ -2321,7 +3107,7 @@ SRes LzmaEnc_MemEncode(CLzmaEncHandle pp, Byte *dest, SizeT *destLen, const Byte res = SZ_ERROR_FAIL; } - *destLen -= outStream.rem; + *destLen -= (SizeT)outStream.rem; if (outStream.overflow) return SZ_ERROR_OUTPUT_EOF; return res; @@ -2330,9 +3116,9 @@ SRes LzmaEnc_MemEncode(CLzmaEncHandle pp, Byte *dest, SizeT *destLen, const Byte SRes LzmaEncode(Byte *dest, SizeT *destLen, const Byte *src, SizeT srcLen, const CLzmaEncProps *props, Byte *propsEncoded, SizeT *propsSize, int writeEndMark, - ICompressProgress *progress, ISzAlloc *alloc, ISzAlloc *allocBig) + ICompressProgressPtr progress, ISzAllocPtr alloc, ISzAllocPtr allocBig) { - CLzmaEnc *p = (CLzmaEnc *)LzmaEnc_Create(alloc); + CLzmaEncHandle p = LzmaEnc_Create(alloc); SRes res; if (!p) return SZ_ERROR_MEM; @@ -2349,3 +3135,15 @@ SRes LzmaEncode(Byte *dest, SizeT *destLen, const Byte *src, SizeT srcLen, LzmaEnc_Destroy(p, alloc, allocBig); return res; } + + +/* +#ifndef Z7_ST +void LzmaEnc_GetLzThreads(CLzmaEncHandle p, HANDLE lz_threads[2]) +{ + GET_const_CLzmaEnc_p + lz_threads[0] = p->matchFinderMt.hashSync.thread; + lz_threads[1] = p->matchFinderMt.btSync.thread; +} +#endif +*/ diff --git a/src/plugins/7zip/7za/c/LzmaEnc.h b/src/plugins/7zip/7za/c/LzmaEnc.h index cffe220b..3feb5b4a 100644 --- a/src/plugins/7zip/7za/c/LzmaEnc.h +++ b/src/plugins/7zip/7za/c/LzmaEnc.h @@ -1,8 +1,8 @@ /* LzmaEnc.h -- LZMA Encoder -2013-01-18 : Igor Pavlov : Public domain */ +: Igor Pavlov : Public domain */ -#ifndef __LZMA_ENC_H -#define __LZMA_ENC_H +#ifndef ZIP7_INC_LZMA_ENC_H +#define ZIP7_INC_LZMA_ENC_H #include "7zTypes.h" @@ -10,14 +10,12 @@ EXTERN_C_BEGIN #define LZMA_PROPS_SIZE 5 -typedef struct _CLzmaEncProps +typedef struct { - int level; /* 0 <= level <= 9 */ + int level; /* 0 <= level <= 9 */ UInt32 dictSize; /* (1 << 12) <= dictSize <= (1 << 27) for 32-bit version - (1 << 12) <= dictSize <= (1 << 30) for 64-bit version - default = (1 << 24) */ - UInt64 reduceSize; /* estimated size of data that will be compressed. default = 0xFFFFFFFF. - Encoder uses this value to reduce dictionary size */ + (1 << 12) <= dictSize <= (3 << 29) for 64-bit version + default = (1 << 24) */ int lc; /* 0 <= lc <= 8, default = 3 */ int lp; /* 0 <= lp <= 4, default = 0 */ int pb; /* 0 <= pb <= 4, default = 2 */ @@ -25,9 +23,19 @@ typedef struct _CLzmaEncProps int fb; /* 5 <= fb <= 273, default = 32 */ int btMode; /* 0 - hashChain Mode, 1 - binTree mode - normal, default = 1 */ int numHashBytes; /* 2, 3 or 4, default = 4 */ - UInt32 mc; /* 1 <= mc <= (1 << 30), default = 32 */ + unsigned numHashOutBits; /* default = ? */ + UInt32 mc; /* 1 <= mc <= (1 << 30), default = 32 */ unsigned writeEndMark; /* 0 - do not write EOPM, 1 - write EOPM, default = 0 */ int numThreads; /* 1 or 2, default = 2 */ + + // int _pad; + Int32 affinityGroup; + + UInt64 reduceSize; /* estimated size of data that will be compressed. default = (UInt64)(Int64)-1. + Encoder uses this value to reduce dictionary size */ + + UInt64 affinity; + UInt64 affinityInGroup; } CLzmaEncProps; void LzmaEncProps_Init(CLzmaEncProps *p); @@ -37,41 +45,40 @@ UInt32 LzmaEncProps_GetDictSize(const CLzmaEncProps *props2); /* ---------- CLzmaEncHandle Interface ---------- */ -/* LzmaEnc_* functions can return the following exit codes: -Returns: +/* LzmaEnc* functions can return the following exit codes: +SRes: SZ_OK - OK SZ_ERROR_MEM - Memory allocation error SZ_ERROR_PARAM - Incorrect paramater in props - SZ_ERROR_WRITE - Write callback error. + SZ_ERROR_WRITE - ISeqOutStream write callback error + SZ_ERROR_OUTPUT_EOF - output buffer overflow - version with (Byte *) output SZ_ERROR_PROGRESS - some break from progress callback - SZ_ERROR_THREAD - errors in multithreading functions (only for Mt version) + SZ_ERROR_THREAD - error in multithreading functions (only for Mt version) */ -typedef void * CLzmaEncHandle; +typedef struct CLzmaEnc CLzmaEnc; +typedef CLzmaEnc * CLzmaEncHandle; +// Z7_DECLARE_HANDLE(CLzmaEncHandle) + +CLzmaEncHandle LzmaEnc_Create(ISzAllocPtr alloc); +void LzmaEnc_Destroy(CLzmaEncHandle p, ISzAllocPtr alloc, ISzAllocPtr allocBig); -CLzmaEncHandle LzmaEnc_Create(ISzAlloc *alloc); -void LzmaEnc_Destroy(CLzmaEncHandle p, ISzAlloc *alloc, ISzAlloc *allocBig); SRes LzmaEnc_SetProps(CLzmaEncHandle p, const CLzmaEncProps *props); +void LzmaEnc_SetDataSize(CLzmaEncHandle p, UInt64 expectedDataSiize); SRes LzmaEnc_WriteProperties(CLzmaEncHandle p, Byte *properties, SizeT *size); -SRes LzmaEnc_Encode(CLzmaEncHandle p, ISeqOutStream *outStream, ISeqInStream *inStream, - ICompressProgress *progress, ISzAlloc *alloc, ISzAlloc *allocBig); +unsigned LzmaEnc_IsWriteEndMark(CLzmaEncHandle p); + +SRes LzmaEnc_Encode(CLzmaEncHandle p, ISeqOutStreamPtr outStream, ISeqInStreamPtr inStream, + ICompressProgressPtr progress, ISzAllocPtr alloc, ISzAllocPtr allocBig); SRes LzmaEnc_MemEncode(CLzmaEncHandle p, Byte *dest, SizeT *destLen, const Byte *src, SizeT srcLen, - int writeEndMark, ICompressProgress *progress, ISzAlloc *alloc, ISzAlloc *allocBig); + int writeEndMark, ICompressProgressPtr progress, ISzAllocPtr alloc, ISzAllocPtr allocBig); -/* ---------- One Call Interface ---------- */ -/* LzmaEncode -Return code: - SZ_OK - OK - SZ_ERROR_MEM - Memory allocation error - SZ_ERROR_PARAM - Incorrect paramater - SZ_ERROR_OUTPUT_EOF - output buffer overflow - SZ_ERROR_THREAD - errors in multithreading functions (only for Mt version) -*/ +/* ---------- One Call Interface ---------- */ SRes LzmaEncode(Byte *dest, SizeT *destLen, const Byte *src, SizeT srcLen, const CLzmaEncProps *props, Byte *propsEncoded, SizeT *propsSize, int writeEndMark, - ICompressProgress *progress, ISzAlloc *alloc, ISzAlloc *allocBig); + ICompressProgressPtr progress, ISzAllocPtr alloc, ISzAllocPtr allocBig); EXTERN_C_END diff --git a/src/plugins/7zip/7za/c/LzmaLib.c b/src/plugins/7zip/7za/c/LzmaLib.c index 706e9e58..785e8848 100644 --- a/src/plugins/7zip/7za/c/LzmaLib.c +++ b/src/plugins/7zip/7za/c/LzmaLib.c @@ -1,12 +1,14 @@ /* LzmaLib.c -- LZMA library wrapper -2015-06-13 : Igor Pavlov : Public domain */ +2023-04-02 : Igor Pavlov : Public domain */ + +#include "Precomp.h" #include "Alloc.h" #include "LzmaDec.h" #include "LzmaEnc.h" #include "LzmaLib.h" -MY_STDAPI LzmaCompress(unsigned char *dest, size_t *destLen, const unsigned char *src, size_t srcLen, +Z7_STDAPI LzmaCompress(unsigned char *dest, size_t *destLen, const unsigned char *src, size_t srcLen, unsigned char *outProps, size_t *outPropsSize, int level, /* 0 <= level <= 9, default = 5 */ unsigned dictSize, /* use (1 << N) or (3 << N). 4 KB < dictSize <= 128 MB */ @@ -32,7 +34,7 @@ MY_STDAPI LzmaCompress(unsigned char *dest, size_t *destLen, const unsigned char } -MY_STDAPI LzmaUncompress(unsigned char *dest, size_t *destLen, const unsigned char *src, size_t *srcLen, +Z7_STDAPI LzmaUncompress(unsigned char *dest, size_t *destLen, const unsigned char *src, size_t *srcLen, const unsigned char *props, size_t propsSize) { ELzmaStatus status; diff --git a/src/plugins/7zip/7za/c/LzmaLib.h b/src/plugins/7zip/7za/c/LzmaLib.h index 88fa87d3..d7c0724d 100644 --- a/src/plugins/7zip/7za/c/LzmaLib.h +++ b/src/plugins/7zip/7za/c/LzmaLib.h @@ -1,14 +1,14 @@ /* LzmaLib.h -- LZMA library interface -2013-01-18 : Igor Pavlov : Public domain */ +2023-04-02 : Igor Pavlov : Public domain */ -#ifndef __LZMA_LIB_H -#define __LZMA_LIB_H +#ifndef ZIP7_INC_LZMA_LIB_H +#define ZIP7_INC_LZMA_LIB_H #include "7zTypes.h" EXTERN_C_BEGIN -#define MY_STDAPI int MY_STD_CALL +#define Z7_STDAPI int Z7_STDCALL #define LZMA_PROPS_SIZE 5 @@ -40,14 +40,16 @@ outPropsSize - level - compression level: 0 <= level <= 9; level dictSize algo fb - 0: 16 KB 0 32 - 1: 64 KB 0 32 - 2: 256 KB 0 32 - 3: 1 MB 0 32 - 4: 4 MB 0 32 + 0: 64 KB 0 32 + 1: 256 KB 0 32 + 2: 1 MB 0 32 + 3: 4 MB 0 32 + 4: 16 MB 0 32 5: 16 MB 1 32 6: 32 MB 1 32 - 7+: 64 MB 1 64 + 7: 32 MB 1 64 + 8: 64 MB 1 64 + 9: 64 MB 1 64 The default value for "level" is 5. @@ -83,6 +85,11 @@ fb - Word size (the number of fast bytes). numThreads - The number of thereads. 1 or 2. The default value is 2. Fast mode (algo = 0) can use only 1 thread. +In: + dest - output data buffer + destLen - output data buffer size + src - input data + srcLen - input data size Out: destLen - processed output size Returns: @@ -93,7 +100,7 @@ numThreads - The number of thereads. 1 or 2. The default value is 2. SZ_ERROR_THREAD - errors in multithreading functions (only for Mt version) */ -MY_STDAPI LzmaCompress(unsigned char *dest, size_t *destLen, const unsigned char *src, size_t srcLen, +Z7_STDAPI LzmaCompress(unsigned char *dest, size_t *destLen, const unsigned char *src, size_t srcLen, unsigned char *outProps, size_t *outPropsSize, /* *outPropsSize must be = 5 */ int level, /* 0 <= level <= 9, default = 5 */ unsigned dictSize, /* default = (1 << 24) */ @@ -108,8 +115,8 @@ MY_STDAPI LzmaCompress(unsigned char *dest, size_t *destLen, const unsigned char LzmaUncompress -------------- In: - dest - output data - destLen - output data size + dest - output data buffer + destLen - output data buffer size src - input data srcLen - input data size Out: @@ -123,7 +130,7 @@ LzmaUncompress SZ_ERROR_INPUT_EOF - it needs more bytes in input buffer (src) */ -MY_STDAPI LzmaUncompress(unsigned char *dest, size_t *destLen, const unsigned char *src, SizeT *srcLen, +Z7_STDAPI LzmaUncompress(unsigned char *dest, size_t *destLen, const unsigned char *src, SizeT *srcLen, const unsigned char *props, size_t propsSize); EXTERN_C_END diff --git a/src/plugins/7zip/7za/c/Md5.c b/src/plugins/7zip/7za/c/Md5.c new file mode 100644 index 00000000..1b745d70 --- /dev/null +++ b/src/plugins/7zip/7za/c/Md5.c @@ -0,0 +1,206 @@ +/* Md5.c -- MD5 Hash +: Igor Pavlov : Public domain +This code is based on Colin Plumb's public domain md5.c code */ + +#include "Precomp.h" + +#include + +#include "Md5.h" +#include "RotateDefs.h" +#include "CpuArch.h" + +#define MD5_UPDATE_BLOCKS(p) Md5_UpdateBlocks + +Z7_NO_INLINE +void Md5_Init(CMd5 *p) +{ + p->count = 0; + p->state[0] = 0x67452301; + p->state[1] = 0xefcdab89; + p->state[2] = 0x98badcfe; + p->state[3] = 0x10325476; +} + +#if 0 && !defined(MY_CPU_LE_UNALIGN) +// optional optimization for Big-endian processors or processors without unaligned access: +// it is intended to reduce the number of complex LE32 memory reading from 64 to 16. +// But some compilers (sparc, armt) are better without this optimization. +#define Z7_MD5_USE_DATA32_ARRAY +#endif + +#define LOAD_DATA(i) GetUi32((const UInt32 *)(const void *)data + (i)) + +#ifdef Z7_MD5_USE_DATA32_ARRAY +#define D(i) data32[i] +#else +#define D(i) LOAD_DATA(i) +#endif + +#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)) + +#define R1(i, f, start, step, w, x, y, z, s, k) \ + w += D((start + step * (i)) % 16) + k; \ + w += f(x, y, z); \ + w = rotlFixed(w, s) + x; \ + +#define R4(i4, f, start, step, s0,s1,s2,s3, k0,k1,k2,k3) \ + R1 (i4*4+0, f, start, step, a,b,c,d, s0, k0) \ + R1 (i4*4+1, f, start, step, d,a,b,c, s1, k1) \ + R1 (i4*4+2, f, start, step, c,d,a,b, s2, k2) \ + R1 (i4*4+3, f, start, step, b,c,d,a, s3, k3) \ + +#define R16(f, start, step, s0,s1,s2,s3, k00,k01,k02,k03, k10,k11,k12,k13, k20,k21,k22,k23, k30,k31,k32,k33) \ + R4 (0, f, start, step, s0,s1,s2,s3, k00,k01,k02,k03) \ + R4 (1, f, start, step, s0,s1,s2,s3, k10,k11,k12,k13) \ + R4 (2, f, start, step, s0,s1,s2,s3, k20,k21,k22,k23) \ + R4 (3, f, start, step, s0,s1,s2,s3, k30,k31,k32,k33) \ + +static +Z7_NO_INLINE +void Z7_FASTCALL Md5_UpdateBlocks(UInt32 state[4], const Byte *data, size_t numBlocks) +{ + UInt32 a, b, c, d; + // if (numBlocks == 0) return; + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + do + { +#ifdef Z7_MD5_USE_DATA32_ARRAY + UInt32 data32[MD5_NUM_BLOCK_WORDS]; + { +#define LOAD_data32_x4(i) { \ + data32[i ] = LOAD_DATA(i ); \ + data32[i + 1] = LOAD_DATA(i + 1); \ + data32[i + 2] = LOAD_DATA(i + 2); \ + data32[i + 3] = LOAD_DATA(i + 3); } +#if 1 + LOAD_data32_x4 (0 * 4) + LOAD_data32_x4 (1 * 4) + LOAD_data32_x4 (2 * 4) + LOAD_data32_x4 (3 * 4) +#else + unsigned i; + for (i = 0; i < MD5_NUM_BLOCK_WORDS; i += 4) + { + LOAD_data32_x4(i) + } +#endif + } +#endif + + R16 (F1, 0, 1, 7,12,17,22, 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, + 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, + 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821) + R16 (F2, 1, 5, 5, 9,14,20, 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, + 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, + 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a) + R16 (F3, 5, 3, 4,11,16,23, 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, + 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, + 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665) + R16 (F4, 0, 7, 6,10,15,21, 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, + 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, + 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391) + + a += state[0]; + b += state[1]; + c += state[2]; + d += state[3]; + + state[0] = a; + state[1] = b; + state[2] = c; + state[3] = d; + + data += MD5_BLOCK_SIZE; + } + while (--numBlocks); +} + + +#define Md5_UpdateBlock(p) MD5_UPDATE_BLOCKS(p)(p->state, p->buffer, 1) + +void Md5_Update(CMd5 *p, const Byte *data, size_t size) +{ + if (size == 0) + return; + { + const unsigned pos = (unsigned)p->count & (MD5_BLOCK_SIZE - 1); + const unsigned num = MD5_BLOCK_SIZE - pos; + p->count += size; + if (num > size) + { + memcpy(p->buffer + pos, data, size); + return; + } + if (pos != 0) + { + size -= num; + memcpy(p->buffer + pos, data, num); + data += num; + Md5_UpdateBlock(p); + } + } + { + const size_t numBlocks = size >> 6; + if (numBlocks) + MD5_UPDATE_BLOCKS(p)(p->state, data, numBlocks); + size &= MD5_BLOCK_SIZE - 1; + if (size == 0) + return; + data += (numBlocks << 6); + memcpy(p->buffer, data, size); + } +} + + +void Md5_Final(CMd5 *p, Byte *digest) +{ + unsigned pos = (unsigned)p->count & (MD5_BLOCK_SIZE - 1); + p->buffer[pos++] = 0x80; + if (pos > (MD5_BLOCK_SIZE - 4 * 2)) + { + while (pos != MD5_BLOCK_SIZE) { p->buffer[pos++] = 0; } + // memset(&p->buf.buffer[pos], 0, MD5_BLOCK_SIZE - pos); + Md5_UpdateBlock(p); + pos = 0; + } + memset(&p->buffer[pos], 0, (MD5_BLOCK_SIZE - 4 * 2) - pos); + { + const UInt64 numBits = p->count << 3; +#if defined(MY_CPU_LE_UNALIGN) + SetUi64 (p->buffer + MD5_BLOCK_SIZE - 4 * 2, numBits) +#else + SetUi32a(p->buffer + MD5_BLOCK_SIZE - 4 * 2, (UInt32)(numBits)) + SetUi32a(p->buffer + MD5_BLOCK_SIZE - 4 * 1, (UInt32)(numBits >> 32)) +#endif + } + Md5_UpdateBlock(p); + + SetUi32(digest, p->state[0]) + SetUi32(digest + 4, p->state[1]) + SetUi32(digest + 8, p->state[2]) + SetUi32(digest + 12, p->state[3]) + + Md5_Init(p); +} + +#undef R1 +#undef R4 +#undef R16 +#undef D +#undef LOAD_DATA +#undef LOAD_data32_x4 +#undef F1 +#undef F2 +#undef F3 +#undef F4 diff --git a/src/plugins/7zip/7za/c/Md5.h b/src/plugins/7zip/7za/c/Md5.h new file mode 100644 index 00000000..49c0741f --- /dev/null +++ b/src/plugins/7zip/7za/c/Md5.h @@ -0,0 +1,34 @@ +/* Md5.h -- MD5 Hash +: Igor Pavlov : Public domain */ + +#ifndef ZIP7_INC_MD5_H +#define ZIP7_INC_MD5_H + +#include "7zTypes.h" + +EXTERN_C_BEGIN + +#define MD5_NUM_BLOCK_WORDS 16 +#define MD5_NUM_DIGEST_WORDS 4 + +#define MD5_BLOCK_SIZE (MD5_NUM_BLOCK_WORDS * 4) +#define MD5_DIGEST_SIZE (MD5_NUM_DIGEST_WORDS * 4) + +typedef struct +{ + UInt64 count; + UInt64 _pad_1; + // we want 16-bytes alignment here + UInt32 state[MD5_NUM_DIGEST_WORDS]; + UInt64 _pad_2[4]; + // we want 64-bytes alignment here + Byte buffer[MD5_BLOCK_SIZE]; +} CMd5; + +void Md5_Init(CMd5 *p); +void Md5_Update(CMd5 *p, const Byte *data, size_t size); +void Md5_Final(CMd5 *p, Byte *digest); + +EXTERN_C_END + +#endif diff --git a/src/plugins/7zip/7za/c/MtCoder.c b/src/plugins/7zip/7za/c/MtCoder.c index 8d9731d0..923b19ac 100644 --- a/src/plugins/7zip/7za/c/MtCoder.c +++ b/src/plugins/7zip/7za/c/MtCoder.c @@ -1,327 +1,604 @@ /* MtCoder.c -- Multi-thread Coder -2015-10-13 : Igor Pavlov : Public domain */ +: Igor Pavlov : Public domain */ #include "Precomp.h" #include "MtCoder.h" -void LoopThread_Construct(CLoopThread *p) -{ - Thread_Construct(&p->thread); - Event_Construct(&p->startEvent); - Event_Construct(&p->finishedEvent); -} - -void LoopThread_Close(CLoopThread *p) -{ - Thread_Close(&p->thread); - Event_Close(&p->startEvent); - Event_Close(&p->finishedEvent); -} +#ifndef Z7_ST -static THREAD_FUNC_RET_TYPE THREAD_FUNC_CALL_TYPE LoopThreadFunc(void *pp) +static SRes MtProgressThunk_Progress(ICompressProgressPtr pp, UInt64 inSize, UInt64 outSize) { - CLoopThread *p = (CLoopThread *)pp; - for (;;) + Z7_CONTAINER_FROM_VTBL_TO_DECL_VAR_pp_vt_p(CMtProgressThunk) + UInt64 inSize2 = 0; + UInt64 outSize2 = 0; + if (inSize != (UInt64)(Int64)-1) { - if (Event_Wait(&p->startEvent) != 0) - return SZ_ERROR_THREAD; - if (p->stop) - return 0; - p->res = p->func(p->param); - if (Event_Set(&p->finishedEvent) != 0) - return SZ_ERROR_THREAD; + inSize2 = inSize - p->inSize; + p->inSize = inSize; + } + if (outSize != (UInt64)(Int64)-1) + { + outSize2 = outSize - p->outSize; + p->outSize = outSize; } + return MtProgress_ProgressAdd(p->mtProgress, inSize2, outSize2); } -WRes LoopThread_Create(CLoopThread *p) -{ - p->stop = 0; - RINOK(AutoResetEvent_CreateNotSignaled(&p->startEvent)); - RINOK(AutoResetEvent_CreateNotSignaled(&p->finishedEvent)); - return Thread_Create(&p->thread, LoopThreadFunc, p); -} -WRes LoopThread_StopAndWait(CLoopThread *p) +void MtProgressThunk_CreateVTable(CMtProgressThunk *p) { - p->stop = 1; - if (Event_Set(&p->startEvent) != 0) - return SZ_ERROR_THREAD; - return Thread_Wait(&p->thread); + p->vt.Progress = MtProgressThunk_Progress; } -WRes LoopThread_StartSubThread(CLoopThread *p) { return Event_Set(&p->startEvent); } -WRes LoopThread_WaitSubThread(CLoopThread *p) { return Event_Wait(&p->finishedEvent); } -static SRes Progress(ICompressProgress *p, UInt64 inSize, UInt64 outSize) -{ - return (p && p->Progress(p, inSize, outSize) != SZ_OK) ? SZ_ERROR_PROGRESS : SZ_OK; -} -static void MtProgress_Init(CMtProgress *p, ICompressProgress *progress) -{ - unsigned i; - for (i = 0; i < NUM_MT_CODER_THREADS_MAX; i++) - p->inSizes[i] = p->outSizes[i] = 0; - p->totalInSize = p->totalOutSize = 0; - p->progress = progress; - p->res = SZ_OK; -} +#define RINOK_THREAD(x) { if ((x) != 0) return SZ_ERROR_THREAD; } -static void MtProgress_Reinit(CMtProgress *p, unsigned index) -{ - p->inSizes[index] = 0; - p->outSizes[index] = 0; -} -#define UPDATE_PROGRESS(size, prev, total) \ - if (size != (UInt64)(Int64)-1) { total += size - prev; prev = size; } +static THREAD_FUNC_DECL ThreadFunc(void *pp); -SRes MtProgress_Set(CMtProgress *p, unsigned index, UInt64 inSize, UInt64 outSize) -{ - SRes res; - CriticalSection_Enter(&p->cs); - UPDATE_PROGRESS(inSize, p->inSizes[index], p->totalInSize) - UPDATE_PROGRESS(outSize, p->outSizes[index], p->totalOutSize) - if (p->res == SZ_OK) - p->res = Progress(p->progress, p->totalInSize, p->totalOutSize); - res = p->res; - CriticalSection_Leave(&p->cs); - return res; -} -static void MtProgress_SetError(CMtProgress *p, SRes res) +static SRes MtCoderThread_CreateAndStart(CMtCoderThread *t +#ifdef _WIN32 + , CMtCoder * const mtc +#endif + ) { - CriticalSection_Enter(&p->cs); - if (p->res == SZ_OK) - p->res = res; - CriticalSection_Leave(&p->cs); + WRes wres = AutoResetEvent_OptCreate_And_Reset(&t->startEvent); + // printf("\n====== MtCoderThread_CreateAndStart : \n"); + if (wres == 0) + { + t->stop = False; + if (!Thread_WasCreated(&t->thread)) + { +#ifdef _WIN32 + if (mtc->numThreadGroups) + wres = Thread_Create_With_Group(&t->thread, ThreadFunc, t, + ThreadNextGroup_GetNext(&mtc->nextGroup), // group + 0); // affinityMask + else +#endif + wres = Thread_Create(&t->thread, ThreadFunc, t); + } + if (wres == 0) + wres = Event_Set(&t->startEvent); + } + if (wres == 0) + return SZ_OK; + return MY_SRes_HRESULT_FROM_WRes(wres); } -static void MtCoder_SetError(CMtCoder* p, SRes res) + +Z7_FORCE_INLINE +static void MtCoderThread_Destruct(CMtCoderThread *t) { - CriticalSection_Enter(&p->cs); - if (p->res == SZ_OK) - p->res = res; - CriticalSection_Leave(&p->cs); -} + if (Thread_WasCreated(&t->thread)) + { + t->stop = 1; + Event_Set(&t->startEvent); + Thread_Wait_Close(&t->thread); + } -/* ---------- MtThread ---------- */ + Event_Close(&t->startEvent); -void CMtThread_Construct(CMtThread *p, CMtCoder *mtCoder) -{ - p->mtCoder = mtCoder; - p->outBuf = 0; - p->inBuf = 0; - Event_Construct(&p->canRead); - Event_Construct(&p->canWrite); - LoopThread_Construct(&p->thread); + if (t->inBuf) + { + ISzAlloc_Free(t->mtCoder->allocBig, t->inBuf); + t->inBuf = NULL; + } } -#define RINOK_THREAD(x) { if ((x) != 0) return SZ_ERROR_THREAD; } -static void CMtThread_CloseEvents(CMtThread *p) -{ - Event_Close(&p->canRead); - Event_Close(&p->canWrite); -} -static void CMtThread_Destruct(CMtThread *p) + +/* + ThreadFunc2() returns: + SZ_OK - in all normal cases (even for stream error or memory allocation error) + SZ_ERROR_THREAD - in case of failure in system synch function +*/ + +static SRes ThreadFunc2(CMtCoderThread *t) { - CMtThread_CloseEvents(p); + CMtCoder * const mtc = t->mtCoder; - if (Thread_WasCreated(&p->thread.thread)) + for (;;) { - LoopThread_StopAndWait(&p->thread); - LoopThread_Close(&p->thread); - } + unsigned bi; + SRes res; + SRes res2; + BoolInt finished; + unsigned bufIndex; + size_t size; + const Byte *inData; + UInt64 readProcessed = 0; + + RINOK_THREAD(Event_Wait(&mtc->readEvent)) + + /* after Event_Wait(&mtc->readEvent) we must call Event_Set(&mtc->readEvent) in any case to unlock another threads */ + + if (mtc->stopReading) + { + return Event_Set(&mtc->readEvent) == 0 ? SZ_OK : SZ_ERROR_THREAD; + } - if (p->mtCoder->alloc) - IAlloc_Free(p->mtCoder->alloc, p->outBuf); - p->outBuf = 0; + res = MtProgress_GetError(&mtc->mtProgress); + + size = 0; + inData = NULL; + finished = True; - if (p->mtCoder->alloc) - IAlloc_Free(p->mtCoder->alloc, p->inBuf); - p->inBuf = 0; -} + if (res == SZ_OK) + { + size = mtc->blockSize; + if (mtc->inStream) + { + if (!t->inBuf) + { + t->inBuf = (Byte *)ISzAlloc_Alloc(mtc->allocBig, mtc->blockSize); + if (!t->inBuf) + res = SZ_ERROR_MEM; + } + if (res == SZ_OK) + { + res = SeqInStream_ReadMax(mtc->inStream, t->inBuf, &size); + readProcessed = mtc->readProcessed + size; + mtc->readProcessed = readProcessed; + } + if (res != SZ_OK) + { + mtc->readRes = res; + /* after reading error - we can stop encoding of previous blocks */ + MtProgress_SetError(&mtc->mtProgress, res); + } + else + finished = (size != mtc->blockSize); + } + else + { + size_t rem; + readProcessed = mtc->readProcessed; + rem = mtc->inDataSize - (size_t)readProcessed; + if (size > rem) + size = rem; + inData = mtc->inData + (size_t)readProcessed; + readProcessed += size; + mtc->readProcessed = readProcessed; + finished = (mtc->inDataSize == (size_t)readProcessed); + } + } -#define MY_BUF_ALLOC(buf, size, newSize) \ - if (buf == 0 || size != newSize) \ - { IAlloc_Free(p->mtCoder->alloc, buf); \ - size = newSize; buf = (Byte *)IAlloc_Alloc(p->mtCoder->alloc, size); \ - if (buf == 0) return SZ_ERROR_MEM; } + /* we must get some block from blocksSemaphore before Event_Set(&mtc->readEvent) */ -static SRes CMtThread_Prepare(CMtThread *p) -{ - MY_BUF_ALLOC(p->inBuf, p->inBufSize, p->mtCoder->blockSize) - MY_BUF_ALLOC(p->outBuf, p->outBufSize, p->mtCoder->destBlockSize) + res2 = SZ_OK; - p->stopReading = False; - p->stopWriting = False; - RINOK_THREAD(AutoResetEvent_CreateNotSignaled(&p->canRead)); - RINOK_THREAD(AutoResetEvent_CreateNotSignaled(&p->canWrite)); + if (Semaphore_Wait(&mtc->blocksSemaphore) != 0) + { + res2 = SZ_ERROR_THREAD; + if (res == SZ_OK) + { + res = res2; + // MtProgress_SetError(&mtc->mtProgress, res); + } + } - return SZ_OK; -} + bi = mtc->blockIndex; -static SRes FullRead(ISeqInStream *stream, Byte *data, size_t *processedSize) -{ - size_t size = *processedSize; - *processedSize = 0; - while (size != 0) - { - size_t curSize = size; - SRes res = stream->Read(stream, data, &curSize); - *processedSize += curSize; - data += curSize; - size -= curSize; - RINOK(res); - if (curSize == 0) - return SZ_OK; - } - return SZ_OK; -} + if (++mtc->blockIndex >= mtc->numBlocksMax) + mtc->blockIndex = 0; -#define GET_NEXT_THREAD(p) &p->mtCoder->threads[p->index == p->mtCoder->numThreads - 1 ? 0 : p->index + 1] + bufIndex = (unsigned)(int)-1; -static SRes MtThread_Process(CMtThread *p, Bool *stop) -{ - CMtThread *next; - *stop = True; - if (Event_Wait(&p->canRead) != 0) - return SZ_ERROR_THREAD; - - next = GET_NEXT_THREAD(p); - - if (p->stopReading) - { - next->stopReading = True; - return Event_Set(&next->canRead) == 0 ? SZ_OK : SZ_ERROR_THREAD; - } + if (res == SZ_OK) + res = MtProgress_GetError(&mtc->mtProgress); - { - size_t size = p->mtCoder->blockSize; - size_t destSize = p->outBufSize; - - RINOK(FullRead(p->mtCoder->inStream, p->inBuf, &size)); - next->stopReading = *stop = (size != p->mtCoder->blockSize); - if (Event_Set(&next->canRead) != 0) - return SZ_ERROR_THREAD; - - RINOK(p->mtCoder->mtCallback->Code(p->mtCoder->mtCallback, p->index, - p->outBuf, &destSize, p->inBuf, size, *stop)); - - MtProgress_Reinit(&p->mtCoder->mtProgress, p->index); - - if (Event_Wait(&p->canWrite) != 0) - return SZ_ERROR_THREAD; - if (p->stopWriting) - return SZ_ERROR_FAIL; - if (p->mtCoder->outStream->Write(p->mtCoder->outStream, p->outBuf, destSize) != destSize) - return SZ_ERROR_WRITE; - return Event_Set(&next->canWrite) == 0 ? SZ_OK : SZ_ERROR_THREAD; + if (res != SZ_OK) + finished = True; + + if (!finished) + { + if (mtc->numStartedThreads < mtc->numStartedThreadsLimit + && mtc->expectedDataSize != readProcessed) + { + res = MtCoderThread_CreateAndStart(&mtc->threads[mtc->numStartedThreads] +#ifdef _WIN32 + , mtc +#endif + ); + if (res == SZ_OK) + mtc->numStartedThreads++; + else + { + MtProgress_SetError(&mtc->mtProgress, res); + finished = True; + } + } + } + + if (finished) + mtc->stopReading = True; + + RINOK_THREAD(Event_Set(&mtc->readEvent)) + + if (res2 != SZ_OK) + return res2; + + if (res == SZ_OK) + { + CriticalSection_Enter(&mtc->cs); + bufIndex = mtc->freeBlockHead; + mtc->freeBlockHead = mtc->freeBlockList[bufIndex]; + CriticalSection_Leave(&mtc->cs); + + res = mtc->mtCallback->Code(mtc->mtCallbackObject, t->index, bufIndex, + mtc->inStream ? t->inBuf : inData, size, finished); + + // MtProgress_Reinit(&mtc->mtProgress, t->index); + + if (res != SZ_OK) + MtProgress_SetError(&mtc->mtProgress, res); + } + + { + CMtCoderBlock * const block = &mtc->blocks[bi]; + block->res = res; + block->bufIndex = bufIndex; + block->finished = finished; + } + + #ifdef MTCODER_USE_WRITE_THREAD + RINOK_THREAD(Event_Set(&mtc->writeEvents[bi])) + #else + { + unsigned wi; + { + CriticalSection_Enter(&mtc->cs); + wi = mtc->writeIndex; + if (wi == bi) + mtc->writeIndex = (unsigned)(int)-1; + else + mtc->ReadyBlocks[bi] = True; + CriticalSection_Leave(&mtc->cs); + } + + if (wi != bi) + { + if (res != SZ_OK || finished) + return 0; + continue; + } + + if (mtc->writeRes != SZ_OK) + res = mtc->writeRes; + + for (;;) + { + if (res == SZ_OK && bufIndex != (unsigned)(int)-1) + { + res = mtc->mtCallback->Write(mtc->mtCallbackObject, bufIndex); + if (res != SZ_OK) + { + mtc->writeRes = res; + MtProgress_SetError(&mtc->mtProgress, res); + } + } + + if (++wi >= mtc->numBlocksMax) + wi = 0; + { + BoolInt isReady; + + CriticalSection_Enter(&mtc->cs); + + if (bufIndex != (unsigned)(int)-1) + { + mtc->freeBlockList[bufIndex] = mtc->freeBlockHead; + mtc->freeBlockHead = bufIndex; + } + + isReady = mtc->ReadyBlocks[wi]; + + if (isReady) + mtc->ReadyBlocks[wi] = False; + else + mtc->writeIndex = wi; + + CriticalSection_Leave(&mtc->cs); + + RINOK_THREAD(Semaphore_Release1(&mtc->blocksSemaphore)) + + if (!isReady) + break; + } + + { + CMtCoderBlock *block = &mtc->blocks[wi]; + if (res == SZ_OK && block->res != SZ_OK) + res = block->res; + bufIndex = block->bufIndex; + finished = block->finished; + } + } + } + #endif + + if (finished || res != SZ_OK) + return 0; } } -static THREAD_FUNC_RET_TYPE THREAD_FUNC_CALL_TYPE ThreadFunc(void *pp) + +static THREAD_FUNC_DECL ThreadFunc(void *pp) { - CMtThread *p = (CMtThread *)pp; + CMtCoderThread * const t = (CMtCoderThread *)pp; for (;;) { - Bool stop; - CMtThread *next = GET_NEXT_THREAD(p); - SRes res = MtThread_Process(p, &stop); - if (res != SZ_OK) + if (Event_Wait(&t->startEvent) != 0) + return (THREAD_FUNC_RET_TYPE)SZ_ERROR_THREAD; + if (t->stop) + return 0; { - MtCoder_SetError(p->mtCoder, res); - MtProgress_SetError(&p->mtCoder->mtProgress, res); - next->stopReading = True; - next->stopWriting = True; - Event_Set(&next->canRead); - Event_Set(&next->canWrite); - return res; + const SRes res = ThreadFunc2(t); + CMtCoder *mtc = t->mtCoder; + if (res != SZ_OK) + { + MtProgress_SetError(&mtc->mtProgress, res); + } + + #ifndef MTCODER_USE_WRITE_THREAD + { + const unsigned numFinished = (unsigned)InterlockedIncrement(&mtc->numFinishedThreads); + if (numFinished == mtc->numStartedThreads) + if (Event_Set(&mtc->finishedEvent) != 0) + return (THREAD_FUNC_RET_TYPE)SZ_ERROR_THREAD; + } + #endif } - if (stop) - return 0; } } -void MtCoder_Construct(CMtCoder* p) + + +void MtCoder_Construct(CMtCoder *p) { unsigned i; - p->alloc = 0; - for (i = 0; i < NUM_MT_CODER_THREADS_MAX; i++) + + p->blockSize = 0; + p->numThreadsMax = 0; + p->numThreadGroups = 0; + p->expectedDataSize = (UInt64)(Int64)-1; + + p->inStream = NULL; + p->inData = NULL; + p->inDataSize = 0; + + p->progress = NULL; + p->allocBig = NULL; + + p->mtCallback = NULL; + p->mtCallbackObject = NULL; + + p->allocatedBufsSize = 0; + + Event_Construct(&p->readEvent); + Semaphore_Construct(&p->blocksSemaphore); + + for (i = 0; i < MTCODER_THREADS_MAX; i++) { - CMtThread *t = &p->threads[i]; + CMtCoderThread *t = &p->threads[i]; + t->mtCoder = p; t->index = i; - CMtThread_Construct(t, p); + t->inBuf = NULL; + t->stop = False; + Event_Construct(&t->startEvent); + Thread_CONSTRUCT(&t->thread) } + + #ifdef MTCODER_USE_WRITE_THREAD + for (i = 0; i < MTCODER_BLOCKS_MAX; i++) + Event_Construct(&p->writeEvents[i]); + #else + Event_Construct(&p->finishedEvent); + #endif + CriticalSection_Init(&p->cs); CriticalSection_Init(&p->mtProgress.cs); } -void MtCoder_Destruct(CMtCoder* p) + + + +static void MtCoder_Free(CMtCoder *p) { unsigned i; - for (i = 0; i < NUM_MT_CODER_THREADS_MAX; i++) - CMtThread_Destruct(&p->threads[i]); + + /* + p->stopReading = True; + if (Event_IsCreated(&p->readEvent)) + Event_Set(&p->readEvent); + */ + + for (i = 0; i < MTCODER_THREADS_MAX; i++) + MtCoderThread_Destruct(&p->threads[i]); + + Event_Close(&p->readEvent); + Semaphore_Close(&p->blocksSemaphore); + + #ifdef MTCODER_USE_WRITE_THREAD + for (i = 0; i < MTCODER_BLOCKS_MAX; i++) + Event_Close(&p->writeEvents[i]); + #else + Event_Close(&p->finishedEvent); + #endif +} + + +void MtCoder_Destruct(CMtCoder *p) +{ + MtCoder_Free(p); + CriticalSection_Delete(&p->cs); CriticalSection_Delete(&p->mtProgress.cs); } + SRes MtCoder_Code(CMtCoder *p) { - unsigned i, numThreads = p->numThreads; + unsigned numThreads = p->numThreadsMax; + unsigned numBlocksMax; + unsigned i; SRes res = SZ_OK; - p->res = SZ_OK; - MtProgress_Init(&p->mtProgress, p->progress); + // printf("\n====== MtCoder_Code : \n"); + + if (numThreads > MTCODER_THREADS_MAX) + numThreads = MTCODER_THREADS_MAX; + numBlocksMax = MTCODER_GET_NUM_BLOCKS_FROM_THREADS(numThreads); + + if (p->blockSize < ((UInt32)1 << 26)) numBlocksMax++; + if (p->blockSize < ((UInt32)1 << 24)) numBlocksMax++; + if (p->blockSize < ((UInt32)1 << 22)) numBlocksMax++; + + if (numBlocksMax > MTCODER_BLOCKS_MAX) + numBlocksMax = MTCODER_BLOCKS_MAX; - for (i = 0; i < numThreads; i++) + if (p->blockSize != p->allocatedBufsSize) { - RINOK(CMtThread_Prepare(&p->threads[i])); + for (i = 0; i < MTCODER_THREADS_MAX; i++) + { + CMtCoderThread *t = &p->threads[i]; + if (t->inBuf) + { + ISzAlloc_Free(p->allocBig, t->inBuf); + t->inBuf = NULL; + } + } + p->allocatedBufsSize = p->blockSize; } - for (i = 0; i < numThreads; i++) - { - CMtThread *t = &p->threads[i]; - CLoopThread *lt = &t->thread; + p->readRes = SZ_OK; - if (!Thread_WasCreated(<->thread)) + MtProgress_Init(&p->mtProgress, p->progress); + + #ifdef MTCODER_USE_WRITE_THREAD + for (i = 0; i < numBlocksMax; i++) { - lt->func = ThreadFunc; - lt->param = t; + RINOK_THREAD(AutoResetEvent_OptCreate_And_Reset(&p->writeEvents[i])) + } + #else + RINOK_THREAD(AutoResetEvent_OptCreate_And_Reset(&p->finishedEvent)) + #endif - if (LoopThread_Create(lt) != SZ_OK) - { - res = SZ_ERROR_THREAD; - break; - } + { + RINOK_THREAD(AutoResetEvent_OptCreate_And_Reset(&p->readEvent)) + RINOK_THREAD(Semaphore_OptCreateInit(&p->blocksSemaphore, (UInt32)numBlocksMax, (UInt32)numBlocksMax)) + } + + for (i = 0; i < MTCODER_BLOCKS_MAX - 1; i++) + p->freeBlockList[i] = i + 1; + p->freeBlockList[MTCODER_BLOCKS_MAX - 1] = (unsigned)(int)-1; + p->freeBlockHead = 0; + + p->readProcessed = 0; + p->blockIndex = 0; + p->numBlocksMax = numBlocksMax; + p->stopReading = False; + + #ifndef MTCODER_USE_WRITE_THREAD + p->writeIndex = 0; + p->writeRes = SZ_OK; + for (i = 0; i < MTCODER_BLOCKS_MAX; i++) + p->ReadyBlocks[i] = False; + p->numFinishedThreads = 0; + #endif + + p->numStartedThreadsLimit = numThreads; + p->numStartedThreads = 0; + ThreadNextGroup_Init(&p->nextGroup, p->numThreadGroups, 0); // startGroup + + // for (i = 0; i < numThreads; i++) + { + // here we create new thread for first block. + // And each new thread will create another new thread after block reading + // until numStartedThreadsLimit is reached. + CMtCoderThread *nextThread = &p->threads[p->numStartedThreads++]; + { + const SRes res2 = MtCoderThread_CreateAndStart(nextThread +#ifdef _WIN32 + , p +#endif + ); + RINOK(res2) } } - if (res == SZ_OK) + RINOK_THREAD(Event_Set(&p->readEvent)) + + #ifdef MTCODER_USE_WRITE_THREAD { - unsigned j; - for (i = 0; i < numThreads; i++) + unsigned bi = 0; + + for (;; bi++) { - CMtThread *t = &p->threads[i]; - if (LoopThread_StartSubThread(&t->thread) != SZ_OK) + if (bi >= numBlocksMax) + bi = 0; + + RINOK_THREAD(Event_Wait(&p->writeEvents[bi])) + { - res = SZ_ERROR_THREAD; - p->threads[0].stopReading = True; - break; + const CMtCoderBlock * const block = &p->blocks[bi]; + const unsigned bufIndex = block->bufIndex; + const BoolInt finished = block->finished; + if (res == SZ_OK && block->res != SZ_OK) + res = block->res; + + if (bufIndex != (unsigned)(int)-1) + { + if (res == SZ_OK) + { + res = p->mtCallback->Write(p->mtCallbackObject, bufIndex); + if (res != SZ_OK) + MtProgress_SetError(&p->mtProgress, res); + } + + CriticalSection_Enter(&p->cs); + { + p->freeBlockList[bufIndex] = p->freeBlockHead; + p->freeBlockHead = bufIndex; + } + CriticalSection_Leave(&p->cs); + } + + RINOK_THREAD(Semaphore_Release1(&p->blocksSemaphore)) + + if (finished) + break; } } + } + #else + { + const WRes wres = Event_Wait(&p->finishedEvent); + res = MY_SRes_HRESULT_FROM_WRes(wres); + } + #endif - Event_Set(&p->threads[0].canWrite); - Event_Set(&p->threads[0].canRead); + if (res == SZ_OK) + res = p->readRes; - for (j = 0; j < i; j++) - LoopThread_WaitSubThread(&p->threads[j].thread); - } + if (res == SZ_OK) + res = p->mtProgress.res; + + #ifndef MTCODER_USE_WRITE_THREAD + if (res == SZ_OK) + res = p->writeRes; + #endif - for (i = 0; i < numThreads; i++) - CMtThread_CloseEvents(&p->threads[i]); - return (res == SZ_OK) ? p->res : res; + if (res != SZ_OK) + MtCoder_Free(p); + return res; } + +#endif + +#undef RINOK_THREAD diff --git a/src/plugins/7zip/7za/c/MtCoder.h b/src/plugins/7zip/7za/c/MtCoder.h index f0f06da2..8166ccac 100644 --- a/src/plugins/7zip/7za/c/MtCoder.h +++ b/src/plugins/7zip/7za/c/MtCoder.h @@ -1,98 +1,144 @@ /* MtCoder.h -- Multi-thread Coder -2009-11-19 : Igor Pavlov : Public domain */ +: Igor Pavlov : Public domain */ -#ifndef __MT_CODER_H -#define __MT_CODER_H +#ifndef ZIP7_INC_MT_CODER_H +#define ZIP7_INC_MT_CODER_H -#include "Threads.h" +#include "MtDec.h" EXTERN_C_BEGIN -typedef struct -{ - CThread thread; - CAutoResetEvent startEvent; - CAutoResetEvent finishedEvent; - int stop; - - THREAD_FUNC_TYPE func; - LPVOID param; - THREAD_FUNC_RET_TYPE res; -} CLoopThread; - -void LoopThread_Construct(CLoopThread *p); -void LoopThread_Close(CLoopThread *p); -WRes LoopThread_Create(CLoopThread *p); -WRes LoopThread_StopAndWait(CLoopThread *p); -WRes LoopThread_StartSubThread(CLoopThread *p); -WRes LoopThread_WaitSubThread(CLoopThread *p); - -#ifndef _7ZIP_ST -#define NUM_MT_CODER_THREADS_MAX 32 +/* + if ( defined MTCODER_USE_WRITE_THREAD) : main thread writes all data blocks to output stream + if (not defined MTCODER_USE_WRITE_THREAD) : any coder thread can write data blocks to output stream +*/ +/* #define MTCODER_USE_WRITE_THREAD */ + +#ifndef Z7_ST + #define MTCODER_GET_NUM_BLOCKS_FROM_THREADS(numThreads) ((numThreads) + (numThreads) / 8 + 1) + #define MTCODER_THREADS_MAX 256 + #define MTCODER_BLOCKS_MAX (MTCODER_GET_NUM_BLOCKS_FROM_THREADS(MTCODER_THREADS_MAX) + 3) #else -#define NUM_MT_CODER_THREADS_MAX 1 + #define MTCODER_THREADS_MAX 1 + #define MTCODER_BLOCKS_MAX 1 #endif + +#ifndef Z7_ST + + typedef struct { - UInt64 totalInSize; - UInt64 totalOutSize; - ICompressProgress *progress; - SRes res; - CCriticalSection cs; - UInt64 inSizes[NUM_MT_CODER_THREADS_MAX]; - UInt64 outSizes[NUM_MT_CODER_THREADS_MAX]; -} CMtProgress; + ICompressProgress vt; + CMtProgress *mtProgress; + UInt64 inSize; + UInt64 outSize; +} CMtProgressThunk; -SRes MtProgress_Set(CMtProgress *p, unsigned index, UInt64 inSize, UInt64 outSize); +void MtProgressThunk_CreateVTable(CMtProgressThunk *p); + +#define MtProgressThunk_INIT(p) { (p)->inSize = 0; (p)->outSize = 0; } + + +struct CMtCoder_; -struct _CMtCoder; typedef struct { - struct _CMtCoder *mtCoder; - Byte *outBuf; - size_t outBufSize; - Byte *inBuf; - size_t inBufSize; + struct CMtCoder_ *mtCoder; unsigned index; - CLoopThread thread; + int stop; + Byte *inBuf; + + CAutoResetEvent startEvent; + CThread thread; +} CMtCoderThread; - Bool stopReading; - Bool stopWriting; - CAutoResetEvent canRead; - CAutoResetEvent canWrite; -} CMtThread; typedef struct { - SRes (*Code)(void *p, unsigned index, Byte *dest, size_t *destSize, + SRes (*Code)(void *p, unsigned coderIndex, unsigned outBufIndex, const Byte *src, size_t srcSize, int finished); -} IMtCoderCallback; + SRes (*Write)(void *p, unsigned outBufIndex); +} IMtCoderCallback2; + -typedef struct _CMtCoder +typedef struct { - size_t blockSize; - size_t destBlockSize; - unsigned numThreads; + SRes res; + unsigned bufIndex; + BoolInt finished; +} CMtCoderBlock; + + +typedef struct CMtCoder_ +{ + /* input variables */ - ISeqInStream *inStream; - ISeqOutStream *outStream; - ICompressProgress *progress; - ISzAlloc *alloc; + size_t blockSize; /* size of input block */ + unsigned numThreadsMax; + unsigned numThreadGroups; + UInt64 expectedDataSize; + + ISeqInStreamPtr inStream; + const Byte *inData; + size_t inDataSize; + + ICompressProgressPtr progress; + ISzAllocPtr allocBig; + + IMtCoderCallback2 *mtCallback; + void *mtCallbackObject; + + + /* internal variables */ + + size_t allocatedBufsSize; + + CAutoResetEvent readEvent; + CSemaphore blocksSemaphore; + + BoolInt stopReading; + SRes readRes; + + #ifdef MTCODER_USE_WRITE_THREAD + CAutoResetEvent writeEvents[MTCODER_BLOCKS_MAX]; + #else + CAutoResetEvent finishedEvent; + SRes writeRes; + unsigned writeIndex; + Byte ReadyBlocks[MTCODER_BLOCKS_MAX]; + LONG numFinishedThreads; + #endif + + unsigned numStartedThreadsLimit; + unsigned numStartedThreads; + + unsigned numBlocksMax; + unsigned blockIndex; + UInt64 readProcessed; - IMtCoderCallback *mtCallback; CCriticalSection cs; - SRes res; + + unsigned freeBlockHead; + unsigned freeBlockList[MTCODER_BLOCKS_MAX]; CMtProgress mtProgress; - CMtThread threads[NUM_MT_CODER_THREADS_MAX]; + CMtCoderBlock blocks[MTCODER_BLOCKS_MAX]; + CMtCoderThread threads[MTCODER_THREADS_MAX]; + + CThreadNextGroup nextGroup; } CMtCoder; -void MtCoder_Construct(CMtCoder* p); -void MtCoder_Destruct(CMtCoder* p); + +void MtCoder_Construct(CMtCoder *p); +void MtCoder_Destruct(CMtCoder *p); SRes MtCoder_Code(CMtCoder *p); + +#endif + + EXTERN_C_END #endif diff --git a/src/plugins/7zip/7za/c/MtDec.c b/src/plugins/7zip/7za/c/MtDec.c new file mode 100644 index 00000000..71fc1eef --- /dev/null +++ b/src/plugins/7zip/7za/c/MtDec.c @@ -0,0 +1,1124 @@ +/* MtDec.c -- Multi-thread Decoder +: Igor Pavlov : Public domain */ + +#include "Precomp.h" + +// #define SHOW_DEBUG_INFO + +// #include +#include + +#ifdef SHOW_DEBUG_INFO +#include +#endif + +#include "MtDec.h" + +#ifndef Z7_ST + +#ifdef SHOW_DEBUG_INFO +#define PRF(x) x +#else +#define PRF(x) +#endif + +#define PRF_STR_INT(s, d) PRF(printf("\n" s " %d\n", (unsigned)d);) + +void MtProgress_Init(CMtProgress *p, ICompressProgressPtr progress) +{ + p->progress = progress; + p->res = SZ_OK; + p->totalInSize = 0; + p->totalOutSize = 0; +} + + +SRes MtProgress_Progress_ST(CMtProgress *p) +{ + if (p->res == SZ_OK && p->progress) + if (ICompressProgress_Progress(p->progress, p->totalInSize, p->totalOutSize) != SZ_OK) + p->res = SZ_ERROR_PROGRESS; + return p->res; +} + + +SRes MtProgress_ProgressAdd(CMtProgress *p, UInt64 inSize, UInt64 outSize) +{ + SRes res; + CriticalSection_Enter(&p->cs); + + p->totalInSize += inSize; + p->totalOutSize += outSize; + if (p->res == SZ_OK && p->progress) + if (ICompressProgress_Progress(p->progress, p->totalInSize, p->totalOutSize) != SZ_OK) + p->res = SZ_ERROR_PROGRESS; + res = p->res; + + CriticalSection_Leave(&p->cs); + return res; +} + + +SRes MtProgress_GetError(CMtProgress *p) +{ + SRes res; + CriticalSection_Enter(&p->cs); + res = p->res; + CriticalSection_Leave(&p->cs); + return res; +} + + +void MtProgress_SetError(CMtProgress *p, SRes res) +{ + CriticalSection_Enter(&p->cs); + if (p->res == SZ_OK) + p->res = res; + CriticalSection_Leave(&p->cs); +} + + +#define RINOK_THREAD(x) RINOK_WRes(x) + + +struct CMtDecBufLink_ +{ + struct CMtDecBufLink_ *next; + void *pad[3]; +}; + +typedef struct CMtDecBufLink_ CMtDecBufLink; + +#define MTDEC__LINK_DATA_OFFSET sizeof(CMtDecBufLink) +#define MTDEC__DATA_PTR_FROM_LINK(link) ((Byte *)(link) + MTDEC__LINK_DATA_OFFSET) + + + +static THREAD_FUNC_DECL MtDec_ThreadFunc(void *pp); + + +static WRes MtDecThread_CreateEvents(CMtDecThread *t) +{ + WRes wres = AutoResetEvent_OptCreate_And_Reset(&t->canWrite); + if (wres == 0) + { + wres = AutoResetEvent_OptCreate_And_Reset(&t->canRead); + if (wres == 0) + return SZ_OK; + } + return wres; +} + + +static SRes MtDecThread_CreateAndStart(CMtDecThread *t) +{ + WRes wres = MtDecThread_CreateEvents(t); + // wres = 17; // for test + if (wres == 0) + { + if (Thread_WasCreated(&t->thread)) + return SZ_OK; + wres = Thread_Create(&t->thread, MtDec_ThreadFunc, t); + if (wres == 0) + return SZ_OK; + } + return MY_SRes_HRESULT_FROM_WRes(wres); +} + + +void MtDecThread_FreeInBufs(CMtDecThread *t) +{ + if (t->inBuf) + { + void *link = t->inBuf; + t->inBuf = NULL; + do + { + void *next = ((CMtDecBufLink *)link)->next; + ISzAlloc_Free(t->mtDec->alloc, link); + link = next; + } + while (link); + } +} + + +static void MtDecThread_CloseThread(CMtDecThread *t) +{ + if (Thread_WasCreated(&t->thread)) + { + Event_Set(&t->canWrite); /* we can disable it. There are no threads waiting canWrite in normal cases */ + Event_Set(&t->canRead); + Thread_Wait_Close(&t->thread); + } + + Event_Close(&t->canRead); + Event_Close(&t->canWrite); +} + +static void MtDec_CloseThreads(CMtDec *p) +{ + unsigned i; + for (i = 0; i < MTDEC_THREADS_MAX; i++) + MtDecThread_CloseThread(&p->threads[i]); +} + +static void MtDecThread_Destruct(CMtDecThread *t) +{ + MtDecThread_CloseThread(t); + MtDecThread_FreeInBufs(t); +} + + + +static SRes MtDec_GetError_Spec(CMtDec *p, UInt64 interruptIndex, BoolInt *wasInterrupted) +{ + SRes res; + CriticalSection_Enter(&p->mtProgress.cs); + *wasInterrupted = (p->needInterrupt && interruptIndex > p->interruptIndex); + res = p->mtProgress.res; + CriticalSection_Leave(&p->mtProgress.cs); + return res; +} + +static SRes MtDec_Progress_GetError_Spec(CMtDec *p, UInt64 inSize, UInt64 outSize, UInt64 interruptIndex, BoolInt *wasInterrupted) +{ + SRes res; + CriticalSection_Enter(&p->mtProgress.cs); + + p->mtProgress.totalInSize += inSize; + p->mtProgress.totalOutSize += outSize; + if (p->mtProgress.res == SZ_OK && p->mtProgress.progress) + if (ICompressProgress_Progress(p->mtProgress.progress, p->mtProgress.totalInSize, p->mtProgress.totalOutSize) != SZ_OK) + p->mtProgress.res = SZ_ERROR_PROGRESS; + + *wasInterrupted = (p->needInterrupt && interruptIndex > p->interruptIndex); + res = p->mtProgress.res; + + CriticalSection_Leave(&p->mtProgress.cs); + + return res; +} + +static void MtDec_Interrupt(CMtDec *p, UInt64 interruptIndex) +{ + CriticalSection_Enter(&p->mtProgress.cs); + if (!p->needInterrupt || interruptIndex < p->interruptIndex) + { + p->interruptIndex = interruptIndex; + p->needInterrupt = True; + } + CriticalSection_Leave(&p->mtProgress.cs); +} + +Byte *MtDec_GetCrossBuff(CMtDec *p) +{ + Byte *cr = p->crossBlock; + if (!cr) + { + cr = (Byte *)ISzAlloc_Alloc(p->alloc, MTDEC__LINK_DATA_OFFSET + p->inBufSize); + if (!cr) + return NULL; + p->crossBlock = cr; + } + return MTDEC__DATA_PTR_FROM_LINK(cr); +} + + +/* + MtDec_ThreadFunc2() returns: + 0 - in all normal cases (even for stream error or memory allocation error) + (!= 0) - WRes error return by system threading function +*/ + +// #define MTDEC_ProgessStep (1 << 22) +#define MTDEC_ProgessStep (1 << 0) + +static WRes MtDec_ThreadFunc2(CMtDecThread *t) +{ + CMtDec *p = t->mtDec; + + PRF_STR_INT("MtDec_ThreadFunc2", t->index) + + // SetThreadAffinityMask(GetCurrentThread(), 1 << t->index); + + for (;;) + { + SRes res, codeRes; + BoolInt wasInterrupted, isAllocError, overflow, finish; + SRes threadingErrorSRes; + BoolInt needCode, needWrite, needContinue; + + size_t inDataSize_Start; + UInt64 inDataSize; + // UInt64 inDataSize_Full; + + UInt64 blockIndex; + + UInt64 inPrev = 0; + UInt64 outPrev = 0; + UInt64 inCodePos; + UInt64 outCodePos; + + Byte *afterEndData = NULL; + size_t afterEndData_Size = 0; + BoolInt afterEndData_IsCross = False; + + BoolInt canCreateNewThread = False; + // CMtDecCallbackInfo parse; + CMtDecThread *nextThread; + + PRF_STR_INT("=============== Event_Wait(&t->canRead)", t->index) + + RINOK_THREAD(Event_Wait(&t->canRead)) + if (p->exitThread) + return 0; + + PRF_STR_INT("after Event_Wait(&t->canRead)", t->index) + + // if (t->index == 3) return 19; // for test + + blockIndex = p->blockIndex++; + + // PRF(printf("\ncanRead\n")) + + res = MtDec_Progress_GetError_Spec(p, 0, 0, blockIndex, &wasInterrupted); + + finish = p->readWasFinished; + needCode = False; + needWrite = False; + isAllocError = False; + overflow = False; + + inDataSize_Start = 0; + inDataSize = 0; + // inDataSize_Full = 0; + + if (res == SZ_OK && !wasInterrupted) + { + // if (p->inStream) + { + CMtDecBufLink *prev = NULL; + CMtDecBufLink *link = (CMtDecBufLink *)t->inBuf; + size_t crossSize = p->crossEnd - p->crossStart; + + PRF(printf("\ncrossSize = %d\n", crossSize)); + + for (;;) + { + if (!link) + { + link = (CMtDecBufLink *)ISzAlloc_Alloc(p->alloc, MTDEC__LINK_DATA_OFFSET + p->inBufSize); + if (!link) + { + finish = True; + // p->allocError_for_Read_BlockIndex = blockIndex; + isAllocError = True; + break; + } + link->next = NULL; + if (prev) + { + // static unsigned g_num = 0; + // printf("\n%6d : %x", ++g_num, (unsigned)(size_t)((Byte *)link - (Byte *)prev)); + prev->next = link; + } + else + t->inBuf = (void *)link; + } + + { + Byte *data = MTDEC__DATA_PTR_FROM_LINK(link); + Byte *parseData = data; + size_t size; + + if (crossSize != 0) + { + inDataSize = crossSize; + // inDataSize_Full = inDataSize; + inDataSize_Start = crossSize; + size = crossSize; + parseData = MTDEC__DATA_PTR_FROM_LINK(p->crossBlock) + p->crossStart; + PRF(printf("\ncross : crossStart = %7d crossEnd = %7d finish = %1d", + (int)p->crossStart, (int)p->crossEnd, (int)finish)); + } + else + { + size = p->inBufSize; + + res = SeqInStream_ReadMax(p->inStream, data, &size); + + // size = 10; // test + + inDataSize += size; + // inDataSize_Full = inDataSize; + if (!prev) + inDataSize_Start = size; + + p->readProcessed += size; + finish = (size != p->inBufSize); + if (finish) + p->readWasFinished = True; + + // res = E_INVALIDARG; // test + + if (res != SZ_OK) + { + // PRF(printf("\nRead error = %d\n", res)) + // we want to decode all data before error + p->readRes = res; + // p->readError_BlockIndex = blockIndex; + p->readWasFinished = True; + finish = True; + res = SZ_OK; + // break; + } + + if (inDataSize - inPrev >= MTDEC_ProgessStep) + { + res = MtDec_Progress_GetError_Spec(p, 0, 0, blockIndex, &wasInterrupted); + if (res != SZ_OK || wasInterrupted) + break; + inPrev = inDataSize; + } + } + + { + CMtDecCallbackInfo parse; + + parse.startCall = (prev == NULL); + parse.src = parseData; + parse.srcSize = size; + parse.srcFinished = finish; + parse.canCreateNewThread = True; + + PRF(printf("\nParse size = %d\n", (unsigned)size)); + + p->mtCallback->Parse(p->mtCallbackObject, t->index, &parse); + + PRF(printf(" Parse processed = %d, state = %d \n", (unsigned)parse.srcSize, (unsigned)parse.state)); + + needWrite = True; + canCreateNewThread = parse.canCreateNewThread; + + // printf("\n\n%12I64u %12I64u", (UInt64)p->mtProgress.totalInSize, (UInt64)p->mtProgress.totalOutSize); + + if ( + // parseRes != SZ_OK || + // inDataSize - (size - parse.srcSize) > p->inBlockMax + // || + parse.state == MTDEC_PARSE_OVERFLOW + // || wasInterrupted + ) + { + // Overflow or Parse error - switch from MT decoding to ST decoding + finish = True; + overflow = True; + + { + PRF(printf("\n Overflow")); + // PRF(printf("\nisBlockFinished = %d", (unsigned)parse.blockWasFinished)); + PRF(printf("\n inDataSize = %d", (unsigned)inDataSize)); + } + + if (crossSize != 0) + memcpy(data, parseData, size); + p->crossStart = 0; + p->crossEnd = 0; + break; + } + + if (crossSize != 0) + { + memcpy(data, parseData, parse.srcSize); + p->crossStart += parse.srcSize; + } + + if (parse.state != MTDEC_PARSE_CONTINUE || finish) + { + // we don't need to parse in current thread anymore + + if (parse.state == MTDEC_PARSE_END) + finish = True; + + needCode = True; + // p->crossFinished = finish; + + if (parse.srcSize == size) + { + // full parsed - no cross transfer + p->crossStart = 0; + p->crossEnd = 0; + break; + } + + if (parse.state == MTDEC_PARSE_END) + { + afterEndData = parseData + parse.srcSize; + afterEndData_Size = size - parse.srcSize; + if (crossSize != 0) + afterEndData_IsCross = True; + // we reduce data size to required bytes (parsed only) + inDataSize -= afterEndData_Size; + if (!prev) + inDataSize_Start = parse.srcSize; + break; + } + + { + // partial parsed - need cross transfer + if (crossSize != 0) + inDataSize = parse.srcSize; // it's only parsed now + else + { + // partial parsed - is not in initial cross block - we need to copy new data to cross block + Byte *cr = MtDec_GetCrossBuff(p); + if (!cr) + { + { + PRF(printf("\ncross alloc error error\n")); + // res = SZ_ERROR_MEM; + finish = True; + // p->allocError_for_Read_BlockIndex = blockIndex; + isAllocError = True; + break; + } + } + + { + size_t crSize = size - parse.srcSize; + inDataSize -= crSize; + p->crossEnd = crSize; + p->crossStart = 0; + memcpy(cr, parseData + parse.srcSize, crSize); + } + } + + // inDataSize_Full = inDataSize; + if (!prev) + inDataSize_Start = parse.srcSize; // it's partial size (parsed only) + + finish = False; + break; + } + } + + if (parse.srcSize != size) + { + res = SZ_ERROR_FAIL; + PRF(printf("\nfinished error SZ_ERROR_FAIL = %d\n", res)); + break; + } + } + } + + prev = link; + link = link->next; + + if (crossSize != 0) + { + crossSize = 0; + p->crossStart = 0; + p->crossEnd = 0; + } + } + } + + if (res == SZ_OK) + res = MtDec_GetError_Spec(p, blockIndex, &wasInterrupted); + } + + codeRes = SZ_OK; + + if (res == SZ_OK && needCode && !wasInterrupted) + { + codeRes = p->mtCallback->PreCode(p->mtCallbackObject, t->index); + if (codeRes != SZ_OK) + { + needCode = False; + finish = True; + // SZ_ERROR_MEM is expected error here. + // if (codeRes == SZ_ERROR_MEM) - we will try single-thread decoding later. + // if (codeRes != SZ_ERROR_MEM) - we can stop decoding or try single-thread decoding. + } + } + + if (res != SZ_OK || wasInterrupted) + finish = True; + + nextThread = NULL; + threadingErrorSRes = SZ_OK; + + if (!finish) + { + if (p->numStartedThreads < p->numStartedThreads_Limit && canCreateNewThread) + { + SRes res2 = MtDecThread_CreateAndStart(&p->threads[p->numStartedThreads]); + if (res2 == SZ_OK) + { + // if (p->numStartedThreads % 1000 == 0) PRF(printf("\n numStartedThreads=%d\n", p->numStartedThreads)); + p->numStartedThreads++; + } + else + { + PRF(printf("\nERROR: numStartedThreads=%d\n", p->numStartedThreads)); + if (p->numStartedThreads == 1) + { + // if only one thread is possible, we leave muti-threading code + finish = True; + needCode = False; + threadingErrorSRes = res2; + } + else + p->numStartedThreads_Limit = p->numStartedThreads; + } + } + + if (!finish) + { + unsigned nextIndex = t->index + 1; + nextThread = &p->threads[nextIndex >= p->numStartedThreads ? 0 : nextIndex]; + RINOK_THREAD(Event_Set(&nextThread->canRead)) + // We have started executing for new iteration (with next thread) + // And that next thread now is responsible for possible exit from decoding (threading_code) + } + } + + // each call of Event_Set(&nextThread->canRead) must be followed by call of Event_Set(&nextThread->canWrite) + // if ( !finish ) we must call Event_Set(&nextThread->canWrite) in any case + // if ( finish ) we switch to single-thread mode and there are 2 ways at the end of current iteration (current block): + // - if (needContinue) after Write(&needContinue), we restore decoding with new iteration + // - otherwise we stop decoding and exit from MtDec_ThreadFunc2() + + // Don't change (finish) variable in the further code + + + // ---------- CODE ---------- + + inPrev = 0; + outPrev = 0; + inCodePos = 0; + outCodePos = 0; + + if (res == SZ_OK && needCode && codeRes == SZ_OK) + { + BoolInt isStartBlock = True; + CMtDecBufLink *link = (CMtDecBufLink *)t->inBuf; + + for (;;) + { + size_t inSize; + int stop; + + if (isStartBlock) + inSize = inDataSize_Start; + else + { + UInt64 rem = inDataSize - inCodePos; + inSize = p->inBufSize; + if (inSize > rem) + inSize = (size_t)rem; + } + + inCodePos += inSize; + stop = True; + + codeRes = p->mtCallback->Code(p->mtCallbackObject, t->index, + (const Byte *)MTDEC__DATA_PTR_FROM_LINK(link), inSize, + (inCodePos == inDataSize), // srcFinished + &inCodePos, &outCodePos, &stop); + + if (codeRes != SZ_OK) + { + PRF(printf("\nCode Interrupt error = %x\n", codeRes)); + // we interrupt only later blocks + MtDec_Interrupt(p, blockIndex); + break; + } + + if (stop || inCodePos == inDataSize) + break; + + { + const UInt64 inDelta = inCodePos - inPrev; + const UInt64 outDelta = outCodePos - outPrev; + if (inDelta >= MTDEC_ProgessStep || outDelta >= MTDEC_ProgessStep) + { + // Sleep(1); + res = MtDec_Progress_GetError_Spec(p, inDelta, outDelta, blockIndex, &wasInterrupted); + if (res != SZ_OK || wasInterrupted) + break; + inPrev = inCodePos; + outPrev = outCodePos; + } + } + + link = link->next; + isStartBlock = False; + } + } + + + // ---------- WRITE ---------- + + RINOK_THREAD(Event_Wait(&t->canWrite)) + + { + BoolInt isErrorMode = False; + BoolInt canRecode = True; + BoolInt needWriteToStream = needWrite; + + if (p->exitThread) return 0; // it's never executed in normal cases + + if (p->wasInterrupted) + wasInterrupted = True; + else + { + if (codeRes != SZ_OK) // || !needCode // check it !!! + { + p->wasInterrupted = True; + p->codeRes = codeRes; + if (codeRes == SZ_ERROR_MEM) + isAllocError = True; + } + + if (threadingErrorSRes) + { + p->wasInterrupted = True; + p->threadingErrorSRes = threadingErrorSRes; + needWriteToStream = False; + } + if (isAllocError) + { + p->wasInterrupted = True; + p->isAllocError = True; + needWriteToStream = False; + } + if (overflow) + { + p->wasInterrupted = True; + p->overflow = True; + needWriteToStream = False; + } + } + + if (needCode) + { + if (wasInterrupted) + { + inCodePos = 0; + outCodePos = 0; + } + { + const UInt64 inDelta = inCodePos - inPrev; + const UInt64 outDelta = outCodePos - outPrev; + // if (inDelta != 0 || outDelta != 0) + res = MtProgress_ProgressAdd(&p->mtProgress, inDelta, outDelta); + } + } + + needContinue = (!finish); + + // if (res == SZ_OK && needWrite && !wasInterrupted) + if (needWrite) + { + // p->inProcessed += inCodePos; + + PRF(printf("\n--Write afterSize = %d\n", (unsigned)afterEndData_Size)); + + res = p->mtCallback->Write(p->mtCallbackObject, t->index, + res == SZ_OK && needWriteToStream && !wasInterrupted, // needWrite + afterEndData, afterEndData_Size, afterEndData_IsCross, + &needContinue, + &canRecode); + + // res = SZ_ERROR_FAIL; // for test + + PRF(printf("\nAfter Write needContinue = %d\n", (unsigned)needContinue)); + PRF(printf("\nprocessed = %d\n", (unsigned)p->inProcessed)); + + if (res != SZ_OK) + { + PRF(printf("\nWrite error = %d\n", res)); + isErrorMode = True; + p->wasInterrupted = True; + } + if (res != SZ_OK + || (!needContinue && !finish)) + { + PRF(printf("\nWrite Interrupt error = %x\n", res)); + MtDec_Interrupt(p, blockIndex); + } + } + + if (canRecode) + if (!needCode + || res != SZ_OK + || p->wasInterrupted + || codeRes != SZ_OK + || wasInterrupted + || p->numFilledThreads != 0 + || isErrorMode) + { + if (p->numFilledThreads == 0) + p->filledThreadStart = t->index; + if (inDataSize != 0 || !finish) + { + t->inDataSize_Start = inDataSize_Start; + t->inDataSize = inDataSize; + p->numFilledThreads++; + } + PRF(printf("\np->numFilledThreads = %d\n", p->numFilledThreads)); + PRF(printf("p->filledThreadStart = %d\n", p->filledThreadStart)); + } + + if (!finish) + { + RINOK_THREAD(Event_Set(&nextThread->canWrite)) + } + else + { + if (needContinue) + { + // we restore decoding with new iteration + RINOK_THREAD(Event_Set(&p->threads[0].canWrite)) + } + else + { + // we exit from decoding + if (t->index == 0) + return SZ_OK; + p->exitThread = True; + } + RINOK_THREAD(Event_Set(&p->threads[0].canRead)) + } + } + } +} + +#ifdef _WIN32 +#define USE_ALLOCA +#endif + +#ifdef USE_ALLOCA +#ifdef _WIN32 +#include +#else +#include +#endif +#endif + + +typedef + #ifdef _WIN32 + UINT_PTR + #elif 1 + uintptr_t + #else + ptrdiff_t + #endif + MY_uintptr_t; + +static THREAD_FUNC_DECL MtDec_ThreadFunc1(void *pp) +{ + WRes res; + + CMtDecThread *t = (CMtDecThread *)pp; + CMtDec *p; + + // fprintf(stdout, "\n%d = %p\n", t->index, &t); + + res = MtDec_ThreadFunc2(t); + p = t->mtDec; + if (res == 0) + return (THREAD_FUNC_RET_TYPE)(MY_uintptr_t)p->exitThreadWRes; + { + // it's unexpected situation for some threading function error + if (p->exitThreadWRes == 0) + p->exitThreadWRes = res; + PRF(printf("\nthread exit error = %d\n", res)); + p->exitThread = True; + Event_Set(&p->threads[0].canRead); + Event_Set(&p->threads[0].canWrite); + MtProgress_SetError(&p->mtProgress, MY_SRes_HRESULT_FROM_WRes(res)); + } + return (THREAD_FUNC_RET_TYPE)(MY_uintptr_t)res; +} + +static Z7_NO_INLINE THREAD_FUNC_DECL MtDec_ThreadFunc(void *pp) +{ + #ifdef USE_ALLOCA + CMtDecThread *t = (CMtDecThread *)pp; + // fprintf(stderr, "\n%d = %p - before", t->index, &t); + t->allocaPtr = alloca(t->index * 128); + #endif + return MtDec_ThreadFunc1(pp); +} + + +int MtDec_PrepareRead(CMtDec *p) +{ + if (p->crossBlock && p->crossStart == p->crossEnd) + { + ISzAlloc_Free(p->alloc, p->crossBlock); + p->crossBlock = NULL; + } + + { + unsigned i; + for (i = 0; i < MTDEC_THREADS_MAX; i++) + if (i > p->numStartedThreads + || p->numFilledThreads <= + (i >= p->filledThreadStart ? + i - p->filledThreadStart : + i + p->numStartedThreads - p->filledThreadStart)) + MtDecThread_FreeInBufs(&p->threads[i]); + } + + return (p->numFilledThreads != 0) || (p->crossStart != p->crossEnd); +} + + +const Byte *MtDec_Read(CMtDec *p, size_t *inLim) +{ + while (p->numFilledThreads != 0) + { + CMtDecThread *t = &p->threads[p->filledThreadStart]; + + if (*inLim != 0) + { + { + void *link = t->inBuf; + void *next = ((CMtDecBufLink *)link)->next; + ISzAlloc_Free(p->alloc, link); + t->inBuf = next; + } + + if (t->inDataSize == 0) + { + MtDecThread_FreeInBufs(t); + if (--p->numFilledThreads == 0) + break; + if (++p->filledThreadStart == p->numStartedThreads) + p->filledThreadStart = 0; + t = &p->threads[p->filledThreadStart]; + } + } + + { + size_t lim = t->inDataSize_Start; + if (lim != 0) + t->inDataSize_Start = 0; + else + { + UInt64 rem = t->inDataSize; + lim = p->inBufSize; + if (lim > rem) + lim = (size_t)rem; + } + t->inDataSize -= lim; + *inLim = lim; + return (const Byte *)MTDEC__DATA_PTR_FROM_LINK(t->inBuf); + } + } + + { + size_t crossSize = p->crossEnd - p->crossStart; + if (crossSize != 0) + { + const Byte *data = MTDEC__DATA_PTR_FROM_LINK(p->crossBlock) + p->crossStart; + *inLim = crossSize; + p->crossStart = 0; + p->crossEnd = 0; + return data; + } + *inLim = 0; + if (p->crossBlock) + { + ISzAlloc_Free(p->alloc, p->crossBlock); + p->crossBlock = NULL; + } + return NULL; + } +} + + +void MtDec_Construct(CMtDec *p) +{ + unsigned i; + + p->inBufSize = (size_t)1 << 18; + + p->numThreadsMax = 0; + + p->inStream = NULL; + + // p->inData = NULL; + // p->inDataSize = 0; + + p->crossBlock = NULL; + p->crossStart = 0; + p->crossEnd = 0; + + p->numFilledThreads = 0; + + p->progress = NULL; + p->alloc = NULL; + + p->mtCallback = NULL; + p->mtCallbackObject = NULL; + + p->allocatedBufsSize = 0; + + for (i = 0; i < MTDEC_THREADS_MAX; i++) + { + CMtDecThread *t = &p->threads[i]; + t->mtDec = p; + t->index = i; + t->inBuf = NULL; + Event_Construct(&t->canRead); + Event_Construct(&t->canWrite); + Thread_CONSTRUCT(&t->thread) + } + + // Event_Construct(&p->finishedEvent); + + CriticalSection_Init(&p->mtProgress.cs); +} + + +static void MtDec_Free(CMtDec *p) +{ + unsigned i; + + p->exitThread = True; + + for (i = 0; i < MTDEC_THREADS_MAX; i++) + MtDecThread_Destruct(&p->threads[i]); + + // Event_Close(&p->finishedEvent); + + if (p->crossBlock) + { + ISzAlloc_Free(p->alloc, p->crossBlock); + p->crossBlock = NULL; + } +} + + +void MtDec_Destruct(CMtDec *p) +{ + MtDec_Free(p); + + CriticalSection_Delete(&p->mtProgress.cs); +} + + +SRes MtDec_Code(CMtDec *p) +{ + unsigned i; + + p->inProcessed = 0; + + p->blockIndex = 1; // it must be larger than not_defined index (0) + p->isAllocError = False; + p->overflow = False; + p->threadingErrorSRes = SZ_OK; + + p->needContinue = True; + + p->readWasFinished = False; + p->needInterrupt = False; + p->interruptIndex = (UInt64)(Int64)-1; + + p->readProcessed = 0; + p->readRes = SZ_OK; + p->codeRes = SZ_OK; + p->wasInterrupted = False; + + p->crossStart = 0; + p->crossEnd = 0; + + p->filledThreadStart = 0; + p->numFilledThreads = 0; + + { + unsigned numThreads = p->numThreadsMax; + if (numThreads > MTDEC_THREADS_MAX) + numThreads = MTDEC_THREADS_MAX; + p->numStartedThreads_Limit = numThreads; + p->numStartedThreads = 0; + } + + if (p->inBufSize != p->allocatedBufsSize) + { + for (i = 0; i < MTDEC_THREADS_MAX; i++) + { + CMtDecThread *t = &p->threads[i]; + if (t->inBuf) + MtDecThread_FreeInBufs(t); + } + if (p->crossBlock) + { + ISzAlloc_Free(p->alloc, p->crossBlock); + p->crossBlock = NULL; + } + + p->allocatedBufsSize = p->inBufSize; + } + + MtProgress_Init(&p->mtProgress, p->progress); + + // RINOK_THREAD(AutoResetEvent_OptCreate_And_Reset(&p->finishedEvent)) + p->exitThread = False; + p->exitThreadWRes = 0; + + { + WRes wres; + SRes sres; + CMtDecThread *nextThread = &p->threads[p->numStartedThreads++]; + // wres = MtDecThread_CreateAndStart(nextThread); + wres = MtDecThread_CreateEvents(nextThread); + if (wres == 0) { wres = Event_Set(&nextThread->canWrite); + if (wres == 0) { wres = Event_Set(&nextThread->canRead); + if (wres == 0) { THREAD_FUNC_RET_TYPE res = MtDec_ThreadFunc(nextThread); + wres = (WRes)(MY_uintptr_t)res; + if (wres != 0) + { + p->needContinue = False; + MtDec_CloseThreads(p); + }}}} + + // wres = 17; // for test + // wres = Event_Wait(&p->finishedEvent); + + sres = MY_SRes_HRESULT_FROM_WRes(wres); + + if (sres != 0) + p->threadingErrorSRes = sres; + + if ( + // wres == 0 + // wres != 0 + // || p->mtc.codeRes == SZ_ERROR_MEM + p->isAllocError + || p->threadingErrorSRes != SZ_OK + || p->overflow) + { + // p->needContinue = True; + } + else + p->needContinue = False; + + if (p->needContinue) + return SZ_OK; + + // if (sres != SZ_OK) + return sres; + // return SZ_ERROR_FAIL; + } +} + +#endif + +#undef PRF diff --git a/src/plugins/7zip/7za/c/MtDec.h b/src/plugins/7zip/7za/c/MtDec.h new file mode 100644 index 00000000..c28e8d9a --- /dev/null +++ b/src/plugins/7zip/7za/c/MtDec.h @@ -0,0 +1,202 @@ +/* MtDec.h -- Multi-thread Decoder +2023-04-02 : Igor Pavlov : Public domain */ + +#ifndef ZIP7_INC_MT_DEC_H +#define ZIP7_INC_MT_DEC_H + +#include "7zTypes.h" + +#ifndef Z7_ST +#include "Threads.h" +#endif + +EXTERN_C_BEGIN + +#ifndef Z7_ST + +#ifndef Z7_ST + #define MTDEC_THREADS_MAX 32 +#else + #define MTDEC_THREADS_MAX 1 +#endif + + +typedef struct +{ + ICompressProgressPtr progress; + SRes res; + UInt64 totalInSize; + UInt64 totalOutSize; + CCriticalSection cs; +} CMtProgress; + +void MtProgress_Init(CMtProgress *p, ICompressProgressPtr progress); +SRes MtProgress_Progress_ST(CMtProgress *p); +SRes MtProgress_ProgressAdd(CMtProgress *p, UInt64 inSize, UInt64 outSize); +SRes MtProgress_GetError(CMtProgress *p); +void MtProgress_SetError(CMtProgress *p, SRes res); + +struct CMtDec; + +typedef struct +{ + struct CMtDec_ *mtDec; + unsigned index; + void *inBuf; + + size_t inDataSize_Start; // size of input data in start block + UInt64 inDataSize; // total size of input data in all blocks + + CThread thread; + CAutoResetEvent canRead; + CAutoResetEvent canWrite; + void *allocaPtr; +} CMtDecThread; + +void MtDecThread_FreeInBufs(CMtDecThread *t); + + +typedef enum +{ + MTDEC_PARSE_CONTINUE, // continue this block with more input data + MTDEC_PARSE_OVERFLOW, // MT buffers overflow, need switch to single-thread + MTDEC_PARSE_NEW, // new block + MTDEC_PARSE_END // end of block threading. But we still can return to threading after Write(&needContinue) +} EMtDecParseState; + +typedef struct +{ + // in + int startCall; + const Byte *src; + size_t srcSize; + // in : (srcSize == 0) is allowed + // out : it's allowed to return less that actually was used ? + int srcFinished; + + // out + EMtDecParseState state; + BoolInt canCreateNewThread; + UInt64 outPos; // check it (size_t) +} CMtDecCallbackInfo; + + +typedef struct +{ + void (*Parse)(void *p, unsigned coderIndex, CMtDecCallbackInfo *ci); + + // PreCode() and Code(): + // (SRes_return_result != SZ_OK) means stop decoding, no need another blocks + SRes (*PreCode)(void *p, unsigned coderIndex); + SRes (*Code)(void *p, unsigned coderIndex, + const Byte *src, size_t srcSize, int srcFinished, + UInt64 *inCodePos, UInt64 *outCodePos, int *stop); + // stop - means stop another Code calls + + + /* Write() must be called, if Parse() was called + set (needWrite) if + { + && (was not interrupted by progress) + && (was not interrupted in previous block) + } + + out: + if (*needContinue), decoder still need to continue decoding with new iteration, + even after MTDEC_PARSE_END + if (*canRecode), we didn't flush current block data, so we still can decode current block later. + */ + SRes (*Write)(void *p, unsigned coderIndex, + BoolInt needWriteToStream, + const Byte *src, size_t srcSize, BoolInt isCross, + // int srcFinished, + BoolInt *needContinue, + BoolInt *canRecode); + +} IMtDecCallback2; + + + +typedef struct CMtDec_ +{ + /* input variables */ + + size_t inBufSize; /* size of input block */ + unsigned numThreadsMax; + // size_t inBlockMax; + unsigned numThreadsMax_2; + + ISeqInStreamPtr inStream; + // const Byte *inData; + // size_t inDataSize; + + ICompressProgressPtr progress; + ISzAllocPtr alloc; + + IMtDecCallback2 *mtCallback; + void *mtCallbackObject; + + + /* internal variables */ + + size_t allocatedBufsSize; + + BoolInt exitThread; + WRes exitThreadWRes; + + UInt64 blockIndex; + BoolInt isAllocError; + BoolInt overflow; + SRes threadingErrorSRes; + + BoolInt needContinue; + + // CAutoResetEvent finishedEvent; + + SRes readRes; + SRes codeRes; + + BoolInt wasInterrupted; + + unsigned numStartedThreads_Limit; + unsigned numStartedThreads; + + Byte *crossBlock; + size_t crossStart; + size_t crossEnd; + UInt64 readProcessed; + BoolInt readWasFinished; + UInt64 inProcessed; + + unsigned filledThreadStart; + unsigned numFilledThreads; + + #ifndef Z7_ST + BoolInt needInterrupt; + UInt64 interruptIndex; + CMtProgress mtProgress; + CMtDecThread threads[MTDEC_THREADS_MAX]; + #endif +} CMtDec; + + +void MtDec_Construct(CMtDec *p); +void MtDec_Destruct(CMtDec *p); + +/* +MtDec_Code() returns: + SZ_OK - in most cases + MY_SRes_HRESULT_FROM_WRes(WRes_error) - in case of unexpected error in threading function +*/ + +SRes MtDec_Code(CMtDec *p); +Byte *MtDec_GetCrossBuff(CMtDec *p); + +int MtDec_PrepareRead(CMtDec *p); +const Byte *MtDec_Read(CMtDec *p, size_t *inLim); + +#endif + +EXTERN_C_END + +#endif diff --git a/src/plugins/7zip/7za/c/Ppmd.h b/src/plugins/7zip/7za/c/Ppmd.h index 5655b26d..66b26266 100644 --- a/src/plugins/7zip/7za/c/Ppmd.h +++ b/src/plugins/7zip/7za/c/Ppmd.h @@ -1,15 +1,24 @@ /* Ppmd.h -- PPMD codec common code -2016-05-16 : Igor Pavlov : Public domain +2023-03-05 : Igor Pavlov : Public domain This code is based on PPMd var.H (2001): Dmitry Shkarin : Public domain */ -#ifndef __PPMD_H -#define __PPMD_H +#ifndef ZIP7_INC_PPMD_H +#define ZIP7_INC_PPMD_H #include "CpuArch.h" EXTERN_C_BEGIN -#ifdef MY_CPU_32BIT +#if defined(MY_CPU_SIZEOF_POINTER) && (MY_CPU_SIZEOF_POINTER == 4) +/* + PPMD code always uses 32-bit internal fields in PPMD structures to store internal references in main block. + if (PPMD_32BIT is defined), the PPMD code stores internal pointers to 32-bit reference fields. + if (PPMD_32BIT is NOT defined), the PPMD code stores internal UInt32 offsets to reference fields. + if (pointer size is 64-bit), then (PPMD_32BIT) mode is not allowed, + if (pointer size is 32-bit), then (PPMD_32BIT) mode is optional, + and it's allowed to disable PPMD_32BIT mode even if pointer is 32-bit. + PPMD code works slightly faster in (PPMD_32BIT) mode. +*/ #define PPMD_32BIT #endif @@ -28,7 +37,7 @@ EXTERN_C_BEGIN #define PPMD_N4 ((128 + 3 - 1 * PPMD_N1 - 2 * PPMD_N2 - 3 * PPMD_N3) / 4) #define PPMD_NUM_INDEXES (PPMD_N1 + PPMD_N2 + PPMD_N3 + PPMD_N4) -#pragma pack(push, 1) +MY_CPU_pragma_pack_push_1 /* Most compilers works OK here even without #pragma pack(push, 1), but some GCC compilers need it. */ /* SEE-contexts for PPM-contexts with masked symbols */ @@ -39,45 +48,120 @@ typedef struct Byte Count; /* Count to next change of Shift */ } CPpmd_See; -#define Ppmd_See_Update(p) if ((p)->Shift < PPMD_PERIOD_BITS && --(p)->Count == 0) \ - { (p)->Summ <<= 1; (p)->Count = (Byte)(3 << (p)->Shift++); } +#define Ppmd_See_UPDATE(p) \ + { if ((p)->Shift < PPMD_PERIOD_BITS && --(p)->Count == 0) \ + { (p)->Summ = (UInt16)((p)->Summ << 1); \ + (p)->Count = (Byte)(3 << (p)->Shift++); }} + typedef struct { Byte Symbol; Byte Freq; - UInt16 SuccessorLow; - UInt16 SuccessorHigh; + UInt16 Successor_0; + UInt16 Successor_1; } CPpmd_State; -#pragma pack(pop) - -typedef - #ifdef PPMD_32BIT - CPpmd_State * - #else - UInt32 - #endif - CPpmd_State_Ref; - -typedef - #ifdef PPMD_32BIT - void * - #else - UInt32 - #endif - CPpmd_Void_Ref; - -typedef - #ifdef PPMD_32BIT - Byte * - #else - UInt32 - #endif - CPpmd_Byte_Ref; +typedef struct CPpmd_State2_ +{ + Byte Symbol; + Byte Freq; +} CPpmd_State2; + +typedef struct CPpmd_State4_ +{ + UInt16 Successor_0; + UInt16 Successor_1; +} CPpmd_State4; + +MY_CPU_pragma_pop + +/* + PPMD code can write full CPpmd_State structure data to CPpmd*_Context + at (byte offset = 2) instead of some fields of original CPpmd*_Context structure. + + If we use pointers to different types, but that point to shared + memory space, we can have aliasing problem (strict aliasing). + + XLC compiler in -O2 mode can change the order of memory write instructions + in relation to read instructions, if we have use pointers to different types. + + To solve that aliasing problem we use combined CPpmd*_Context structure + with unions that contain the fields from both structures: + the original CPpmd*_Context and CPpmd_State. + So we can access the fields from both structures via one pointer, + and the compiler doesn't change the order of write instructions + in relation to read instructions. + + If we don't use memory write instructions to shared memory in + some local code, and we use only reading instructions (read only), + then probably it's safe to use pointers to different types for reading. +*/ + + + +#ifdef PPMD_32BIT + + #define Ppmd_Ref_Type(type) type * + #define Ppmd_GetRef(p, ptr) (ptr) + #define Ppmd_GetPtr(p, ptr) (ptr) + #define Ppmd_GetPtr_Type(p, ptr, note_type) (ptr) + +#else + + #define Ppmd_Ref_Type(type) UInt32 + #define Ppmd_GetRef(p, ptr) ((UInt32)((Byte *)(ptr) - (p)->Base)) + #define Ppmd_GetPtr(p, offs) ((void *)((p)->Base + (offs))) + #define Ppmd_GetPtr_Type(p, offs, type) ((type *)Ppmd_GetPtr(p, offs)) + +#endif // PPMD_32BIT + + +typedef Ppmd_Ref_Type(CPpmd_State) CPpmd_State_Ref; +typedef Ppmd_Ref_Type(void) CPpmd_Void_Ref; +typedef Ppmd_Ref_Type(Byte) CPpmd_Byte_Ref; + + +/* +#ifdef MY_CPU_LE_UNALIGN +// the unaligned 32-bit access latency can be too large, if the data is not in L1 cache. +#define Ppmd_GET_SUCCESSOR(p) ((CPpmd_Void_Ref)*(const UInt32 *)(const void *)&(p)->Successor_0) +#define Ppmd_SET_SUCCESSOR(p, v) *(UInt32 *)(void *)(void *)&(p)->Successor_0 = (UInt32)(v) + +#else +*/ + +/* + We can write 16-bit halves to 32-bit (Successor) field in any selected order. + But the native order is more consistent way. + So we use the native order, if LE/BE order can be detected here at compile time. +*/ + +#ifdef MY_CPU_BE + + #define Ppmd_GET_SUCCESSOR(p) \ + ( (CPpmd_Void_Ref) (((UInt32)(p)->Successor_0 << 16) | (p)->Successor_1) ) + + #define Ppmd_SET_SUCCESSOR(p, v) { \ + (p)->Successor_0 = (UInt16)(((UInt32)(v) >> 16) /* & 0xFFFF */); \ + (p)->Successor_1 = (UInt16)((UInt32)(v) /* & 0xFFFF */); } + +#else + + #define Ppmd_GET_SUCCESSOR(p) \ + ( (CPpmd_Void_Ref) ((p)->Successor_0 | ((UInt32)(p)->Successor_1 << 16)) ) + + #define Ppmd_SET_SUCCESSOR(p, v) { \ + (p)->Successor_0 = (UInt16)((UInt32)(v) /* & 0xFFFF */); \ + (p)->Successor_1 = (UInt16)(((UInt32)(v) >> 16) /* & 0xFFFF */); } + +#endif + +// #endif + #define PPMD_SetAllBitsIn256Bytes(p) \ - { unsigned z; for (z = 0; z < 256 / sizeof(p[0]); z += 8) { \ + { size_t z; for (z = 0; z < 256 / sizeof(p[0]); z += 8) { \ p[z+7] = p[z+6] = p[z+5] = p[z+4] = p[z+3] = p[z+2] = p[z+1] = p[z+0] = ~(size_t)0; }} EXTERN_C_END diff --git a/src/plugins/7zip/7za/c/Ppmd7.c b/src/plugins/7zip/7za/c/Ppmd7.c index eda8eb70..efcc5d86 100644 --- a/src/plugins/7zip/7za/c/Ppmd7.c +++ b/src/plugins/7zip/7za/c/Ppmd7.c @@ -1,5 +1,5 @@ /* Ppmd7.c -- PPMdH codec -2016-05-21 : Igor Pavlov : Public domain +2023-09-07 : Igor Pavlov : Public domain This code is based on PPMd var.H (2001): Dmitry Shkarin : Public domain */ #include "Precomp.h" @@ -8,21 +8,23 @@ This code is based on PPMd var.H (2001): Dmitry Shkarin : Public domain */ #include "Ppmd7.h" -const Byte PPMD7_kExpEscape[16] = { 25, 14, 9, 7, 5, 5, 4, 4, 4, 3, 3, 3, 2, 2, 2, 2 }; -static const UInt16 kInitBinEsc[] = { 0x3CDD, 0x1F3F, 0x59BF, 0x48F3, 0x64A1, 0x5ABC, 0x6632, 0x6051}; +/* define PPMD7_ORDER_0_SUPPPORT to suport order-0 mode, unsupported by orignal PPMd var.H. code */ +// #define PPMD7_ORDER_0_SUPPPORT + +MY_ALIGN(16) +static const Byte PPMD7_kExpEscape[16] = { 25, 14, 9, 7, 5, 5, 4, 4, 4, 3, 3, 3, 2, 2, 2, 2 }; +MY_ALIGN(16) +static const UInt16 PPMD7_kInitBinEsc[] = { 0x3CDD, 0x1F3F, 0x59BF, 0x48F3, 0x64A1, 0x5ABC, 0x6632, 0x6051}; #define MAX_FREQ 124 #define UNIT_SIZE 12 #define U2B(nu) ((UInt32)(nu) * UNIT_SIZE) -#define U2I(nu) (p->Units2Indx[(nu) - 1]) -#define I2U(indx) (p->Indx2Units[indx]) +#define U2I(nu) (p->Units2Indx[(size_t)(nu) - 1]) +#define I2U(indx) ((unsigned)p->Indx2Units[indx]) +#define I2U_UInt16(indx) ((UInt16)p->Indx2Units[indx]) -#ifdef PPMD_32BIT - #define REF(ptr) (ptr) -#else - #define REF(ptr) ((UInt32)((Byte *)(ptr) - (p)->Base)) -#endif +#define REF(ptr) Ppmd_GetRef(p, ptr) #define STATS_REF(ptr) ((CPpmd_State_Ref)REF(ptr)) @@ -31,17 +33,11 @@ static const UInt16 kInitBinEsc[] = { 0x3CDD, 0x1F3F, 0x59BF, 0x48F3, 0x64A1, 0x #define ONE_STATE(ctx) Ppmd7Context_OneState(ctx) #define SUFFIX(ctx) CTX((ctx)->Suffix) -typedef CPpmd7_Context * CTX_PTR; +typedef CPpmd7_Context * PPMD7_CTX_PTR; struct CPpmd7_Node_; -typedef - #ifdef PPMD_32BIT - struct CPpmd7_Node_ * - #else - UInt32 - #endif - CPpmd7_Node_Ref; +typedef Ppmd_Ref_Type(struct CPpmd7_Node_) CPpmd7_Node_Ref; typedef struct CPpmd7_Node_ { @@ -51,17 +47,13 @@ typedef struct CPpmd7_Node_ CPpmd7_Node_Ref Prev; } CPpmd7_Node; -#ifdef PPMD_32BIT - #define NODE(ptr) (ptr) -#else - #define NODE(offs) ((CPpmd7_Node *)(p->Base + (offs))) -#endif +#define NODE(r) Ppmd_GetPtr_Type(p, r, CPpmd7_Node) void Ppmd7_Construct(CPpmd7 *p) { unsigned i, k, m; - p->Base = 0; + p->Base = NULL; for (i = 0, k = 0; i < PPMD_NUM_INDEXES; i++) { @@ -77,6 +69,7 @@ void Ppmd7_Construct(CPpmd7 *p) for (i = 0; i < 3; i++) p->NS2Indx[i] = (Byte)i; + for (m = i, k = 1; i < 256; i++) { p->NS2Indx[i] = (Byte)m; @@ -84,181 +77,245 @@ void Ppmd7_Construct(CPpmd7 *p) k = (++m) - 2; } - memset(p->HB2Flag, 0, 0x40); - memset(p->HB2Flag + 0x40, 8, 0x100 - 0x40); + memcpy(p->ExpEscape, PPMD7_kExpEscape, 16); } -void Ppmd7_Free(CPpmd7 *p, ISzAlloc *alloc) + +void Ppmd7_Free(CPpmd7 *p, ISzAllocPtr alloc) { - alloc->Free(alloc, p->Base); + ISzAlloc_Free(alloc, p->Base); p->Size = 0; - p->Base = 0; + p->Base = NULL; } -Bool Ppmd7_Alloc(CPpmd7 *p, UInt32 size, ISzAlloc *alloc) + +BoolInt Ppmd7_Alloc(CPpmd7 *p, UInt32 size, ISzAllocPtr alloc) { - if (p->Base == 0 || p->Size != size) + if (!p->Base || p->Size != size) { Ppmd7_Free(p, alloc); - p->AlignOffset = - #ifdef PPMD_32BIT - (4 - size) & 3; - #else - 4 - (size & 3); - #endif - if ((p->Base = (Byte *)alloc->Alloc(alloc, p->AlignOffset + size - #ifndef PPMD_32BIT - + UNIT_SIZE - #endif - )) == 0) + p->AlignOffset = (4 - size) & 3; + if ((p->Base = (Byte *)ISzAlloc_Alloc(alloc, p->AlignOffset + size)) == NULL) return False; p->Size = size; } return True; } -static void InsertNode(CPpmd7 *p, void *node, unsigned indx) + + +// ---------- Internal Memory Allocator ---------- + +/* We can use CPpmd7_Node in list of free units (as in Ppmd8) + But we still need one additional list walk pass in Ppmd7_GlueFreeBlocks(). + So we use simple CPpmd_Void_Ref instead of CPpmd7_Node in Ppmd7_InsertNode() / Ppmd7_RemoveNode() +*/ + +#define EMPTY_NODE 0 + + +static void Ppmd7_InsertNode(CPpmd7 *p, void *node, unsigned indx) { *((CPpmd_Void_Ref *)node) = p->FreeList[indx]; + // ((CPpmd7_Node *)node)->Next = (CPpmd7_Node_Ref)p->FreeList[indx]; + p->FreeList[indx] = REF(node); + } -static void *RemoveNode(CPpmd7 *p, unsigned indx) + +static void *Ppmd7_RemoveNode(CPpmd7 *p, unsigned indx) { CPpmd_Void_Ref *node = (CPpmd_Void_Ref *)Ppmd7_GetPtr(p, p->FreeList[indx]); p->FreeList[indx] = *node; + // CPpmd7_Node *node = NODE((CPpmd7_Node_Ref)p->FreeList[indx]); + // p->FreeList[indx] = node->Next; return node; } -static void SplitBlock(CPpmd7 *p, void *ptr, unsigned oldIndx, unsigned newIndx) + +static void Ppmd7_SplitBlock(CPpmd7 *p, void *ptr, unsigned oldIndx, unsigned newIndx) { unsigned i, nu = I2U(oldIndx) - I2U(newIndx); ptr = (Byte *)ptr + U2B(I2U(newIndx)); if (I2U(i = U2I(nu)) != nu) { unsigned k = I2U(--i); - InsertNode(p, ((Byte *)ptr) + U2B(k), nu - k - 1); + Ppmd7_InsertNode(p, ((Byte *)ptr) + U2B(k), nu - k - 1); } - InsertNode(p, ptr, i); + Ppmd7_InsertNode(p, ptr, i); } -static void GlueFreeBlocks(CPpmd7 *p) + +/* we use CPpmd7_Node_Union union to solve XLC -O2 strict pointer aliasing problem */ + +typedef union { - #ifdef PPMD_32BIT - CPpmd7_Node headItem; - CPpmd7_Node_Ref head = &headItem; - #else - CPpmd7_Node_Ref head = p->AlignOffset + p->Size; - #endif - - CPpmd7_Node_Ref n = head; - unsigned i; + CPpmd7_Node Node; + CPpmd7_Node_Ref NextRef; +} CPpmd7_Node_Union; + +/* Original PPmdH (Ppmd7) code uses doubly linked list in Ppmd7_GlueFreeBlocks() + we use single linked list similar to Ppmd8 code */ + +static void Ppmd7_GlueFreeBlocks(CPpmd7 *p) +{ + /* + we use first UInt16 field of 12-bytes UNITs as record type stamp + CPpmd_State { Byte Symbol; Byte Freq; : Freq != 0 + CPpmd7_Context { UInt16 NumStats; : NumStats != 0 + CPpmd7_Node { UInt16 Stamp : Stamp == 0 for free record + : Stamp == 1 for head record and guard + Last 12-bytes UNIT in array is always contains 12-bytes order-0 CPpmd7_Context record. + */ + CPpmd7_Node_Ref head, n = 0; + p->GlueCount = 255; - /* create doubly-linked list of free blocks */ - for (i = 0; i < PPMD_NUM_INDEXES; i++) + + /* we set guard NODE at LoUnit */ + if (p->LoUnit != p->HiUnit) + ((CPpmd7_Node *)(void *)p->LoUnit)->Stamp = 1; + { - UInt16 nu = I2U(i); - CPpmd7_Node_Ref next = (CPpmd7_Node_Ref)p->FreeList[i]; - p->FreeList[i] = 0; - while (next != 0) + /* Create list of free blocks. + We still need one additional list walk pass before Glue. */ + unsigned i; + for (i = 0; i < PPMD_NUM_INDEXES; i++) { - CPpmd7_Node *node = NODE(next); - node->Next = n; - n = NODE(n)->Prev = next; - next = *(const CPpmd7_Node_Ref *)node; - node->Stamp = 0; - node->NU = (UInt16)nu; + const UInt16 nu = I2U_UInt16(i); + CPpmd7_Node_Ref next = (CPpmd7_Node_Ref)p->FreeList[i]; + p->FreeList[i] = 0; + while (next != 0) + { + /* Don't change the order of the following commands: */ + CPpmd7_Node_Union *un = (CPpmd7_Node_Union *)NODE(next); + const CPpmd7_Node_Ref tmp = next; + next = un->NextRef; + un->Node.Stamp = EMPTY_NODE; + un->Node.NU = nu; + un->Node.Next = n; + n = tmp; + } } } - NODE(head)->Stamp = 1; - NODE(head)->Next = n; - NODE(n)->Prev = head; - if (p->LoUnit != p->HiUnit) - ((CPpmd7_Node *)p->LoUnit)->Stamp = 1; - - /* Glue free blocks */ - while (n != head) + + head = n; + /* Glue and Fill must walk the list in same direction */ { - CPpmd7_Node *node = NODE(n); - UInt32 nu = (UInt32)node->NU; - for (;;) + /* Glue free blocks */ + CPpmd7_Node_Ref *prev = &head; + while (n) { - CPpmd7_Node *node2 = NODE(n) + nu; - nu += node2->NU; - if (node2->Stamp != 0 || nu >= 0x10000) - break; - NODE(node2->Prev)->Next = node2->Next; - NODE(node2->Next)->Prev = node2->Prev; - node->NU = (UInt16)nu; + CPpmd7_Node *node = NODE(n); + UInt32 nu = node->NU; + n = node->Next; + if (nu == 0) + { + *prev = n; + continue; + } + prev = &node->Next; + for (;;) + { + CPpmd7_Node *node2 = node + nu; + nu += node2->NU; + if (node2->Stamp != EMPTY_NODE || nu >= 0x10000) + break; + node->NU = (UInt16)nu; + node2->NU = 0; + } } - n = node->Next; } - + /* Fill lists of free blocks */ - for (n = NODE(head)->Next; n != head;) + for (n = head; n != 0;) { CPpmd7_Node *node = NODE(n); - unsigned nu; - CPpmd7_Node_Ref next = node->Next; - for (nu = node->NU; nu > 128; nu -= 128, node += 128) - InsertNode(p, node, PPMD_NUM_INDEXES - 1); + UInt32 nu = node->NU; + unsigned i; + n = node->Next; + if (nu == 0) + continue; + for (; nu > 128; nu -= 128, node += 128) + Ppmd7_InsertNode(p, node, PPMD_NUM_INDEXES - 1); if (I2U(i = U2I(nu)) != nu) { unsigned k = I2U(--i); - InsertNode(p, node + k, nu - k - 1); + Ppmd7_InsertNode(p, node + k, (unsigned)nu - k - 1); } - InsertNode(p, node, i); - n = next; + Ppmd7_InsertNode(p, node, i); } } -static void *AllocUnitsRare(CPpmd7 *p, unsigned indx) + +Z7_NO_INLINE +static void *Ppmd7_AllocUnitsRare(CPpmd7 *p, unsigned indx) { unsigned i; - void *retVal; + if (p->GlueCount == 0) { - GlueFreeBlocks(p); + Ppmd7_GlueFreeBlocks(p); if (p->FreeList[indx] != 0) - return RemoveNode(p, indx); + return Ppmd7_RemoveNode(p, indx); } + i = indx; + do { if (++i == PPMD_NUM_INDEXES) { UInt32 numBytes = U2B(I2U(indx)); + Byte *us = p->UnitsStart; p->GlueCount--; - return ((UInt32)(p->UnitsStart - p->Text) > numBytes) ? (p->UnitsStart -= numBytes) : (NULL); + return ((UInt32)(us - p->Text) > numBytes) ? (p->UnitsStart = us - numBytes) : NULL; } } while (p->FreeList[i] == 0); - retVal = RemoveNode(p, i); - SplitBlock(p, retVal, i, indx); - return retVal; + + { + void *block = Ppmd7_RemoveNode(p, i); + Ppmd7_SplitBlock(p, block, i, indx); + return block; + } } -static void *AllocUnits(CPpmd7 *p, unsigned indx) + +static void *Ppmd7_AllocUnits(CPpmd7 *p, unsigned indx) { - UInt32 numBytes; if (p->FreeList[indx] != 0) - return RemoveNode(p, indx); - numBytes = U2B(I2U(indx)); - if (numBytes <= (UInt32)(p->HiUnit - p->LoUnit)) + return Ppmd7_RemoveNode(p, indx); { - void *retVal = p->LoUnit; - p->LoUnit += numBytes; - return retVal; + UInt32 numBytes = U2B(I2U(indx)); + Byte *lo = p->LoUnit; + if ((UInt32)(p->HiUnit - lo) >= numBytes) + { + p->LoUnit = lo + numBytes; + return lo; + } } - return AllocUnitsRare(p, indx); + return Ppmd7_AllocUnitsRare(p, indx); } -#define MyMem12Cpy(dest, src, num) \ - { UInt32 *d = (UInt32 *)dest; const UInt32 *s = (const UInt32 *)src; UInt32 n = num; \ - do { d[0] = s[0]; d[1] = s[1]; d[2] = s[2]; s += 3; d += 3; } while (--n); } +#define MEM_12_CPY(dest, src, num) \ + { UInt32 *d = (UInt32 *)(dest); \ + const UInt32 *z = (const UInt32 *)(src); \ + unsigned n = (num); \ + do { \ + d[0] = z[0]; \ + d[1] = z[1]; \ + d[2] = z[2]; \ + z += 3; \ + d += 3; \ + } while (--n); \ + } + + +/* static void *ShrinkUnits(CPpmd7 *p, void *oldPtr, unsigned oldNU, unsigned newNU) { unsigned i0 = U2I(oldNU); @@ -267,28 +324,33 @@ static void *ShrinkUnits(CPpmd7 *p, void *oldPtr, unsigned oldNU, unsigned newNU return oldPtr; if (p->FreeList[i1] != 0) { - void *ptr = RemoveNode(p, i1); - MyMem12Cpy(ptr, oldPtr, newNU); - InsertNode(p, oldPtr, i0); + void *ptr = Ppmd7_RemoveNode(p, i1); + MEM_12_CPY(ptr, oldPtr, newNU) + Ppmd7_InsertNode(p, oldPtr, i0); return ptr; } - SplitBlock(p, oldPtr, i0, i1); + Ppmd7_SplitBlock(p, oldPtr, i0, i1); return oldPtr; } +*/ -#define SUCCESSOR(p) ((CPpmd_Void_Ref)((p)->SuccessorLow | ((UInt32)(p)->SuccessorHigh << 16))) +#define SUCCESSOR(p) Ppmd_GET_SUCCESSOR(p) static void SetSuccessor(CPpmd_State *p, CPpmd_Void_Ref v) { - (p)->SuccessorLow = (UInt16)((UInt32)(v) & 0xFFFF); - (p)->SuccessorHigh = (UInt16)(((UInt32)(v) >> 16) & 0xFFFF); + Ppmd_SET_SUCCESSOR(p, v) } -static void RestartModel(CPpmd7 *p) + + +Z7_NO_INLINE +static +void Ppmd7_RestartModel(CPpmd7 *p) { - unsigned i, k, m; + unsigned i, k; memset(p->FreeList, 0, sizeof(p->FreeList)); + p->Text = p->Base + p->AlignOffset; p->HiUnit = p->Text + p->Size; p->LoUnit = p->UnitsStart = p->HiUnit - p->Size / 8 / UNIT_SIZE * 7 * UNIT_SIZE; @@ -298,57 +360,110 @@ static void RestartModel(CPpmd7 *p) p->RunLength = p->InitRL = -(Int32)((p->MaxOrder < 12) ? p->MaxOrder : 12) - 1; p->PrevSuccess = 0; - p->MinContext = p->MaxContext = (CTX_PTR)(p->HiUnit -= UNIT_SIZE); /* AllocContext(p); */ - p->MinContext->Suffix = 0; - p->MinContext->NumStats = 256; - p->MinContext->SummFreq = 256 + 1; - p->FoundState = (CPpmd_State *)p->LoUnit; /* AllocUnits(p, PPMD_NUM_INDEXES - 1); */ - p->LoUnit += U2B(256 / 2); - p->MinContext->Stats = REF(p->FoundState); - for (i = 0; i < 256; i++) { - CPpmd_State *s = &p->FoundState[i]; - s->Symbol = (Byte)i; - s->Freq = 1; - SetSuccessor(s, 0); + CPpmd7_Context *mc = (PPMD7_CTX_PTR)(void *)(p->HiUnit -= UNIT_SIZE); /* AllocContext(p); */ + CPpmd_State *s = (CPpmd_State *)p->LoUnit; /* Ppmd7_AllocUnits(p, PPMD_NUM_INDEXES - 1); */ + + p->LoUnit += U2B(256 / 2); + p->MaxContext = p->MinContext = mc; + p->FoundState = s; + + mc->NumStats = 256; + mc->Union2.SummFreq = 256 + 1; + mc->Union4.Stats = REF(s); + mc->Suffix = 0; + + for (i = 0; i < 256; i++, s++) + { + s->Symbol = (Byte)i; + s->Freq = 1; + SetSuccessor(s, 0); + } + + #ifdef PPMD7_ORDER_0_SUPPPORT + if (p->MaxOrder == 0) + { + CPpmd_Void_Ref r = REF(mc); + s = p->FoundState; + for (i = 0; i < 256; i++, s++) + SetSuccessor(s, r); + return; + } + #endif } for (i = 0; i < 128; i++) + + + for (k = 0; k < 8; k++) { + unsigned m; UInt16 *dest = p->BinSumm[i] + k; - UInt16 val = (UInt16)(PPMD_BIN_SCALE - kInitBinEsc[k] / (i + 2)); + const UInt16 val = (UInt16)(PPMD_BIN_SCALE - PPMD7_kInitBinEsc[k] / (i + 2)); for (m = 0; m < 64; m += 8) dest[m] = val; } - + + for (i = 0; i < 25; i++) - for (k = 0; k < 16; k++) + { + + CPpmd_See *s = p->See[i]; + + + + unsigned summ = ((5 * i + 10) << (PPMD_PERIOD_BITS - 4)); + for (k = 0; k < 16; k++, s++) { - CPpmd_See *s = &p->See[i][k]; - s->Summ = (UInt16)((5 * i + 10) << (s->Shift = PPMD_PERIOD_BITS - 4)); + s->Summ = (UInt16)summ; + s->Shift = (PPMD_PERIOD_BITS - 4); s->Count = 4; } + } + + p->DummySee.Summ = 0; /* unused */ + p->DummySee.Shift = PPMD_PERIOD_BITS; + p->DummySee.Count = 64; /* unused */ } + void Ppmd7_Init(CPpmd7 *p, unsigned maxOrder) { p->MaxOrder = maxOrder; - RestartModel(p); - p->DummySee.Shift = PPMD_PERIOD_BITS; - p->DummySee.Summ = 0; /* unused */ - p->DummySee.Count = 64; /* unused */ + + Ppmd7_RestartModel(p); } -static CTX_PTR CreateSuccessors(CPpmd7 *p, Bool skip) + + +/* + Ppmd7_CreateSuccessors() + It's called when (FoundState->Successor) is RAW-Successor, + that is the link to position in Raw text. + So we create Context records and write the links to + FoundState->Successor and to identical RAW-Successors in suffix + contexts of MinContex. + + The function returns: + if (OrderFall == 0) then MinContext is already at MAX order, + { return pointer to new or existing context of same MAX order } + else + { return pointer to new real context that will be (Order+1) in comparison with MinContext + + also it can return pointer to real context of same order, +*/ + +Z7_NO_INLINE +static PPMD7_CTX_PTR Ppmd7_CreateSuccessors(CPpmd7 *p) { - CPpmd_State upState; - CTX_PTR c = p->MinContext; + PPMD7_CTX_PTR c = p->MinContext; CPpmd_Byte_Ref upBranch = (CPpmd_Byte_Ref)SUCCESSOR(p->FoundState); - CPpmd_State *ps[PPMD7_MAX_ORDER]; + Byte newSym, newFreq; unsigned numPs = 0; - - if (!skip) + CPpmd_State *ps[PPMD7_MAX_ORDER]; + + if (p->OrderFall != 0) ps[numPs++] = p->FoundState; while (c->Suffix) @@ -356,54 +471,83 @@ static CTX_PTR CreateSuccessors(CPpmd7 *p, Bool skip) CPpmd_Void_Ref successor; CPpmd_State *s; c = SUFFIX(c); + + if (c->NumStats != 1) { - for (s = STATS(c); s->Symbol != p->FoundState->Symbol; s++); + Byte sym = p->FoundState->Symbol; + for (s = STATS(c); s->Symbol != sym; s++); + } else + { s = ONE_STATE(c); + + } successor = SUCCESSOR(s); if (successor != upBranch) { + // (c) is real record Context here, c = CTX(successor); if (numPs == 0) + { + // (c) is real record MAX Order Context here, + // So we don't need to create any new contexts. return c; + } break; } ps[numPs++] = s; } - upState.Symbol = *(const Byte *)Ppmd7_GetPtr(p, upBranch); - SetSuccessor(&upState, upBranch + 1); + // All created contexts will have single-symbol with new RAW-Successor + // All new RAW-Successors will point to next position in RAW text + // after FoundState->Successor + + newSym = *(const Byte *)Ppmd7_GetPtr(p, upBranch); + upBranch++; + if (c->NumStats == 1) - upState.Freq = ONE_STATE(c)->Freq; + newFreq = ONE_STATE(c)->Freq; else { UInt32 cf, s0; CPpmd_State *s; - for (s = STATS(c); s->Symbol != upState.Symbol; s++); - cf = s->Freq - 1; - s0 = c->SummFreq - c->NumStats - cf; - upState.Freq = (Byte)(1 + ((2 * cf <= s0) ? (5 * cf > s0) : ((2 * cf + 3 * s0 - 1) / (2 * s0)))); + for (s = STATS(c); s->Symbol != newSym; s++); + cf = (UInt32)s->Freq - 1; + s0 = (UInt32)c->Union2.SummFreq - c->NumStats - cf; + /* + cf - is frequency of symbol that will be Successor in new context records. + s0 - is commulative frequency sum of another symbols from parent context. + max(newFreq)= (s->Freq + 1), when (s0 == 1) + we have requirement (Ppmd7Context_OneState()->Freq <= 128) in BinSumm[] + so (s->Freq < 128) - is requirement for multi-symbol contexts + */ + newFreq = (Byte)(1 + ((2 * cf <= s0) ? (5 * cf > s0) : (2 * cf + s0 - 1) / (2 * s0) + 1)); } + // Create new single-symbol contexts from low order to high order in loop + do { - /* Create Child */ - CTX_PTR c1; /* = AllocContext(p); */ + PPMD7_CTX_PTR c1; + /* = AllocContext(p); */ if (p->HiUnit != p->LoUnit) - c1 = (CTX_PTR)(p->HiUnit -= UNIT_SIZE); + c1 = (PPMD7_CTX_PTR)(void *)(p->HiUnit -= UNIT_SIZE); else if (p->FreeList[0] != 0) - c1 = (CTX_PTR)RemoveNode(p, 0); + c1 = (PPMD7_CTX_PTR)Ppmd7_RemoveNode(p, 0); else { - c1 = (CTX_PTR)AllocUnitsRare(p, 0); + c1 = (PPMD7_CTX_PTR)Ppmd7_AllocUnitsRare(p, 0); if (!c1) return NULL; } + c1->NumStats = 1; - *ONE_STATE(c1) = upState; + ONE_STATE(c1)->Symbol = newSym; + ONE_STATE(c1)->Freq = newFreq; + SetSuccessor(ONE_STATE(c1), upBranch); c1->Suffix = REF(c); SetSuccessor(ps[--numPs], REF(c1)); c = c1; @@ -413,21 +557,26 @@ static CTX_PTR CreateSuccessors(CPpmd7 *p, Bool skip) return c; } -static void SwapStates(CPpmd_State *t1, CPpmd_State *t2) -{ - CPpmd_State tmp = *t1; - *t1 = *t2; - *t2 = tmp; -} -static void UpdateModel(CPpmd7 *p) + +#define SWAP_STATES(s) \ + { CPpmd_State tmp = s[0]; s[0] = s[-1]; s[-1] = tmp; } + + +void Ppmd7_UpdateModel(CPpmd7 *p); +Z7_NO_INLINE +void Ppmd7_UpdateModel(CPpmd7 *p) { - CPpmd_Void_Ref successor, fSuccessor = SUCCESSOR(p->FoundState); - CTX_PTR c; + CPpmd_Void_Ref maxSuccessor, minSuccessor; + PPMD7_CTX_PTR c, mc; unsigned s0, ns; - + + + if (p->FoundState->Freq < MAX_FREQ / 4 && p->MinContext->Suffix != 0) { + /* Update Freqs in Suffix Context */ + c = SUFFIX(p->MinContext); if (c->NumStats == 1) @@ -439,166 +588,273 @@ static void UpdateModel(CPpmd7 *p) else { CPpmd_State *s = STATS(c); - if (s->Symbol != p->FoundState->Symbol) + Byte sym = p->FoundState->Symbol; + + if (s->Symbol != sym) { - do { s++; } while (s->Symbol != p->FoundState->Symbol); + do + { + // s++; if (s->Symbol == sym) break; + s++; + } + while (s->Symbol != sym); + if (s[0].Freq >= s[-1].Freq) { - SwapStates(&s[0], &s[-1]); + SWAP_STATES(s) s--; } } + if (s->Freq < MAX_FREQ - 9) { - s->Freq += 2; - c->SummFreq += 2; + s->Freq = (Byte)(s->Freq + 2); + c->Union2.SummFreq = (UInt16)(c->Union2.SummFreq + 2); } } } + if (p->OrderFall == 0) { - p->MinContext = p->MaxContext = CreateSuccessors(p, True); - if (p->MinContext == 0) + /* MAX ORDER context */ + /* (FoundState->Successor) is RAW-Successor. */ + p->MaxContext = p->MinContext = Ppmd7_CreateSuccessors(p); + if (!p->MinContext) { - RestartModel(p); + Ppmd7_RestartModel(p); return; } SetSuccessor(p->FoundState, REF(p->MinContext)); return; } + + + /* NON-MAX ORDER context */ - *p->Text++ = p->FoundState->Symbol; - successor = REF(p->Text); - if (p->Text >= p->UnitsStart) { - RestartModel(p); - return; + Byte *text = p->Text; + *text++ = p->FoundState->Symbol; + p->Text = text; + if (text >= p->UnitsStart) + { + Ppmd7_RestartModel(p); + return; + } + maxSuccessor = REF(text); } - if (fSuccessor) + minSuccessor = SUCCESSOR(p->FoundState); + + if (minSuccessor) { - if (fSuccessor <= successor) + // there is Successor for FoundState in MinContext. + // So the next context will be one order higher than MinContext. + + if (minSuccessor <= maxSuccessor) { - CTX_PTR cs = CreateSuccessors(p, False); - if (cs == NULL) + // minSuccessor is RAW-Successor. So we will create real contexts records: + PPMD7_CTX_PTR cs = Ppmd7_CreateSuccessors(p); + if (!cs) { - RestartModel(p); + Ppmd7_RestartModel(p); return; } - fSuccessor = REF(cs); + minSuccessor = REF(cs); } + + // minSuccessor now is real Context pointer that points to existing (Order+1) context + if (--p->OrderFall == 0) { - successor = fSuccessor; + /* + if we move to MaxOrder context, then minSuccessor will be common Succesor for both: + MinContext that is (MaxOrder - 1) + MaxContext that is (MaxOrder) + so we don't need new RAW-Successor, and we can use real minSuccessor + as succssors for both MinContext and MaxContext. + */ + maxSuccessor = minSuccessor; + + /* + if (MaxContext != MinContext) + { + there was order fall from MaxOrder and we don't need current symbol + to transfer some RAW-Succesors to real contexts. + So we roll back pointer in raw data for one position. + } + */ p->Text -= (p->MaxContext != p->MinContext); } } else { - SetSuccessor(p->FoundState, successor); - fSuccessor = REF(p->MinContext); + /* + FoundState has NULL-Successor here. + And only root 0-order context can contain NULL-Successors. + We change Successor in FoundState to RAW-Successor, + And next context will be same 0-order root Context. + */ + SetSuccessor(p->FoundState, maxSuccessor); + minSuccessor = REF(p->MinContext); } - - s0 = p->MinContext->SummFreq - (ns = p->MinContext->NumStats) - (p->FoundState->Freq - 1); - - for (c = p->MaxContext; c != p->MinContext; c = SUFFIX(c)) + + mc = p->MinContext; + c = p->MaxContext; + + p->MaxContext = p->MinContext = CTX(minSuccessor); + + if (c == mc) + return; + + // s0 : is pure Escape Freq + s0 = mc->Union2.SummFreq - (ns = mc->NumStats) - ((unsigned)p->FoundState->Freq - 1); + + do { unsigned ns1; - UInt32 cf, sf; + UInt32 sum; + if ((ns1 = c->NumStats) != 1) { if ((ns1 & 1) == 0) { /* Expand for one UNIT */ - unsigned oldNU = ns1 >> 1; - unsigned i = U2I(oldNU); - if (i != U2I(oldNU + 1)) + const unsigned oldNU = ns1 >> 1; + const unsigned i = U2I(oldNU); + if (i != U2I((size_t)oldNU + 1)) { - void *ptr = AllocUnits(p, i + 1); + void *ptr = Ppmd7_AllocUnits(p, i + 1); void *oldPtr; if (!ptr) { - RestartModel(p); + Ppmd7_RestartModel(p); return; } oldPtr = STATS(c); - MyMem12Cpy(ptr, oldPtr, oldNU); - InsertNode(p, oldPtr, i); - c->Stats = STATS_REF(ptr); + MEM_12_CPY(ptr, oldPtr, oldNU) + Ppmd7_InsertNode(p, oldPtr, i); + c->Union4.Stats = STATS_REF(ptr); } } - c->SummFreq = (UInt16)(c->SummFreq + (2 * ns1 < ns) + 2 * ((4 * ns1 <= ns) & (c->SummFreq <= 8 * ns1))); + sum = c->Union2.SummFreq; + /* max increase of Escape_Freq is 3 here. + total increase of Union2.SummFreq for all symbols is less than 256 here */ + sum += (UInt32)(unsigned)((2 * ns1 < ns) + 2 * ((unsigned)(4 * ns1 <= ns) & (sum <= 8 * ns1))); + /* original PPMdH uses 16-bit variable for (sum) here. + But (sum < 0x9000). So we don't truncate (sum) to 16-bit */ + // sum = (UInt16)sum; } else { - CPpmd_State *s = (CPpmd_State*)AllocUnits(p, 0); + // instead of One-symbol context we create 2-symbol context + CPpmd_State *s = (CPpmd_State*)Ppmd7_AllocUnits(p, 0); if (!s) { - RestartModel(p); + Ppmd7_RestartModel(p); return; } - *s = *ONE_STATE(c); - c->Stats = REF(s); - if (s->Freq < MAX_FREQ / 4 - 1) - s->Freq <<= 1; - else - s->Freq = MAX_FREQ - 4; - c->SummFreq = (UInt16)(s->Freq + p->InitEsc + (ns > 3)); - } - cf = 2 * (UInt32)p->FoundState->Freq * (c->SummFreq + 6); - sf = (UInt32)s0 + c->SummFreq; - if (cf < 6 * sf) - { - cf = 1 + (cf > sf) + (cf >= 4 * sf); - c->SummFreq += 3; - } - else - { - cf = 4 + (cf >= 9 * sf) + (cf >= 12 * sf) + (cf >= 15 * sf); - c->SummFreq = (UInt16)(c->SummFreq + cf); + { + unsigned freq = c->Union2.State2.Freq; + // s = *ONE_STATE(c); + s->Symbol = c->Union2.State2.Symbol; + s->Successor_0 = c->Union4.State4.Successor_0; + s->Successor_1 = c->Union4.State4.Successor_1; + // SetSuccessor(s, c->Union4.Stats); // call it only for debug purposes to check the order of + // (Successor_0 and Successor_1) in LE/BE. + c->Union4.Stats = REF(s); + if (freq < MAX_FREQ / 4 - 1) + freq <<= 1; + else + freq = MAX_FREQ - 4; + // (max(s->freq) == 120), when we convert from 1-symbol into 2-symbol context + s->Freq = (Byte)freq; + // max(InitEsc = PPMD7_kExpEscape[*]) is 25. So the max(escapeFreq) is 26 here + sum = (UInt32)(freq + p->InitEsc + (ns > 3)); + } } + { CPpmd_State *s = STATS(c) + ns1; - SetSuccessor(s, successor); + UInt32 cf = 2 * (sum + 6) * (UInt32)p->FoundState->Freq; + UInt32 sf = (UInt32)s0 + sum; s->Symbol = p->FoundState->Symbol; - s->Freq = (Byte)cf; c->NumStats = (UInt16)(ns1 + 1); + SetSuccessor(s, maxSuccessor); + + if (cf < 6 * sf) + { + cf = (UInt32)1 + (cf > sf) + (cf >= 4 * sf); + sum += 3; + /* It can add (0, 1, 2) to Escape_Freq */ + } + else + { + cf = (UInt32)4 + (cf >= 9 * sf) + (cf >= 12 * sf) + (cf >= 15 * sf); + sum += cf; + } + + c->Union2.SummFreq = (UInt16)sum; + s->Freq = (Byte)cf; } + c = SUFFIX(c); } - p->MaxContext = p->MinContext = CTX(fSuccessor); + while (c != mc); } -static void Rescale(CPpmd7 *p) + + +Z7_NO_INLINE +static void Ppmd7_Rescale(CPpmd7 *p) { unsigned i, adder, sumFreq, escFreq; CPpmd_State *stats = STATS(p->MinContext); CPpmd_State *s = p->FoundState; + + /* Sort the list by Freq */ + if (s != stats) { CPpmd_State tmp = *s; - for (; s != stats; s--) + do s[0] = s[-1]; + while (--s != stats); *s = tmp; } - escFreq = p->MinContext->SummFreq - s->Freq; - s->Freq += 4; - adder = (p->OrderFall != 0); - s->Freq = (Byte)((s->Freq + adder) >> 1); + sumFreq = s->Freq; + escFreq = p->MinContext->Union2.SummFreq - sumFreq; + + /* + if (p->OrderFall == 0), adder = 0 : it's allowed to remove symbol from MAX Order context + if (p->OrderFall != 0), adder = 1 : it's NOT allowed to remove symbol from NON-MAX Order context + */ + + adder = (p->OrderFall != 0); + + #ifdef PPMD7_ORDER_0_SUPPPORT + adder |= (p->MaxOrder == 0); // we don't remove symbols from order-0 context + #endif + + sumFreq = (sumFreq + 4 + adder) >> 1; + i = (unsigned)p->MinContext->NumStats - 1; + s->Freq = (Byte)sumFreq; - i = p->MinContext->NumStats - 1; do { - escFreq -= (++s)->Freq; - s->Freq = (Byte)((s->Freq + adder) >> 1); - sumFreq += s->Freq; - if (s[0].Freq > s[-1].Freq) + unsigned freq = (++s)->Freq; + escFreq -= freq; + freq = (freq + adder) >> 1; + sumFreq += freq; + s->Freq = (Byte)freq; + if (freq > s[-1].Freq) { + CPpmd_State tmp = *s; CPpmd_State *s1 = s; - CPpmd_State tmp = *s1; do + { s1[0] = s1[-1]; - while (--s1 != stats && tmp.Freq > s1[-1].Freq); + } + while (--s1 != stats && freq > s1[-1].Freq); *s1 = tmp; } } @@ -606,48 +862,90 @@ static void Rescale(CPpmd7 *p) if (s->Freq == 0) { - unsigned numStats = p->MinContext->NumStats; - unsigned n0, n1; - do { i++; } while ((--s)->Freq == 0); + /* Remove all items with Freq == 0 */ + CPpmd7_Context *mc; + unsigned numStats, numStatsNew, n0, n1; + + i = 0; do { i++; } while ((--s)->Freq == 0); + + /* We increase (escFreq) for the number of removed symbols. + So we will have (0.5) increase for Escape_Freq in avarage per + removed symbol after Escape_Freq halving */ escFreq += i; - p->MinContext->NumStats = (UInt16)(p->MinContext->NumStats - i); - if (p->MinContext->NumStats == 1) + mc = p->MinContext; + numStats = mc->NumStats; + numStatsNew = numStats - i; + mc->NumStats = (UInt16)(numStatsNew); + n0 = (numStats + 1) >> 1; + + if (numStatsNew == 1) { - CPpmd_State tmp = *stats; + /* Create Single-Symbol context */ + unsigned freq = stats->Freq; + do { - tmp.Freq = (Byte)(tmp.Freq - (tmp.Freq >> 1)); escFreq >>= 1; + freq = (freq + 1) >> 1; } while (escFreq > 1); - InsertNode(p, stats, U2I(((numStats + 1) >> 1))); - *(p->FoundState = ONE_STATE(p->MinContext)) = tmp; + + s = ONE_STATE(mc); + *s = *stats; + s->Freq = (Byte)freq; // (freq <= 260 / 4) + p->FoundState = s; + Ppmd7_InsertNode(p, stats, U2I(n0)); return; } - n0 = (numStats + 1) >> 1; - n1 = (p->MinContext->NumStats + 1) >> 1; + + n1 = (numStatsNew + 1) >> 1; if (n0 != n1) - p->MinContext->Stats = STATS_REF(ShrinkUnits(p, stats, n0, n1)); + { + // p->MinContext->Union4.Stats = STATS_REF(ShrinkUnits(p, stats, n0, n1)); + unsigned i0 = U2I(n0); + unsigned i1 = U2I(n1); + if (i0 != i1) + { + if (p->FreeList[i1] != 0) + { + void *ptr = Ppmd7_RemoveNode(p, i1); + p->MinContext->Union4.Stats = STATS_REF(ptr); + MEM_12_CPY(ptr, (const void *)stats, n1) + Ppmd7_InsertNode(p, stats, i0); + } + else + Ppmd7_SplitBlock(p, stats, i0, i1); + } + } + } + { + CPpmd7_Context *mc = p->MinContext; + mc->Union2.SummFreq = (UInt16)(sumFreq + escFreq - (escFreq >> 1)); + // Escape_Freq halving here + p->FoundState = STATS(mc); } - p->MinContext->SummFreq = (UInt16)(sumFreq + escFreq - (escFreq >> 1)); - p->FoundState = STATS(p->MinContext); } + CPpmd_See *Ppmd7_MakeEscFreq(CPpmd7 *p, unsigned numMasked, UInt32 *escFreq) { CPpmd_See *see; - unsigned nonMasked = p->MinContext->NumStats - numMasked; - if (p->MinContext->NumStats != 256) + const CPpmd7_Context *mc = p->MinContext; + unsigned numStats = mc->NumStats; + if (numStats != 256) { - see = p->See[(unsigned)p->NS2Indx[nonMasked - 1]] + - (nonMasked < (unsigned)SUFFIX(p->MinContext)->NumStats - p->MinContext->NumStats) + - 2 * (unsigned)(p->MinContext->SummFreq < 11 * p->MinContext->NumStats) + - 4 * (unsigned)(numMasked > nonMasked) + + unsigned nonMasked = numStats - numMasked; + see = p->See[(unsigned)p->NS2Indx[(size_t)nonMasked - 1]] + + (nonMasked < (unsigned)SUFFIX(mc)->NumStats - numStats) + + 2 * (unsigned)(mc->Union2.SummFreq < 11 * numStats) + + 4 * (unsigned)(numMasked > nonMasked) + p->HiBitsFlag; { - unsigned r = (see->Summ >> see->Shift); - see->Summ = (UInt16)(see->Summ - r); - *escFreq = r + (r == 0); + // if (see->Summ) field is larger than 16-bit, we need only low 16 bits of Summ + const unsigned summ = (UInt16)see->Summ; // & 0xFFFF + const unsigned r = (summ >> see->Shift); + see->Summ = (UInt16)(summ - r); + *escFreq = (UInt32)(r + (r == 0)); } } else @@ -658,53 +956,176 @@ CPpmd_See *Ppmd7_MakeEscFreq(CPpmd7 *p, unsigned numMasked, UInt32 *escFreq) return see; } -static void NextContext(CPpmd7 *p) + +static void Ppmd7_NextContext(CPpmd7 *p) { - CTX_PTR c = CTX(SUCCESSOR(p->FoundState)); - if (p->OrderFall == 0 && (Byte *)c > p->Text) - p->MinContext = p->MaxContext = c; + PPMD7_CTX_PTR c = CTX(SUCCESSOR(p->FoundState)); + if (p->OrderFall == 0 && (const Byte *)c > p->Text) + p->MaxContext = p->MinContext = c; else - UpdateModel(p); + Ppmd7_UpdateModel(p); } + void Ppmd7_Update1(CPpmd7 *p) { CPpmd_State *s = p->FoundState; - s->Freq += 4; - p->MinContext->SummFreq += 4; - if (s[0].Freq > s[-1].Freq) + unsigned freq = s->Freq; + freq += 4; + p->MinContext->Union2.SummFreq = (UInt16)(p->MinContext->Union2.SummFreq + 4); + s->Freq = (Byte)freq; + if (freq > s[-1].Freq) { - SwapStates(&s[0], &s[-1]); + SWAP_STATES(s) p->FoundState = --s; - if (s->Freq > MAX_FREQ) - Rescale(p); + if (freq > MAX_FREQ) + Ppmd7_Rescale(p); } - NextContext(p); + Ppmd7_NextContext(p); } + void Ppmd7_Update1_0(CPpmd7 *p) { - p->PrevSuccess = (2 * p->FoundState->Freq > p->MinContext->SummFreq); - p->RunLength += p->PrevSuccess; - p->MinContext->SummFreq += 4; - if ((p->FoundState->Freq += 4) > MAX_FREQ) - Rescale(p); - NextContext(p); + CPpmd_State *s = p->FoundState; + CPpmd7_Context *mc = p->MinContext; + unsigned freq = s->Freq; + const unsigned summFreq = mc->Union2.SummFreq; + p->PrevSuccess = (2 * freq > summFreq); + p->RunLength += (Int32)p->PrevSuccess; + mc->Union2.SummFreq = (UInt16)(summFreq + 4); + freq += 4; + s->Freq = (Byte)freq; + if (freq > MAX_FREQ) + Ppmd7_Rescale(p); + Ppmd7_NextContext(p); } + +/* void Ppmd7_UpdateBin(CPpmd7 *p) { - p->FoundState->Freq = (Byte)(p->FoundState->Freq + (p->FoundState->Freq < 128 ? 1: 0)); + unsigned freq = p->FoundState->Freq; + p->FoundState->Freq = (Byte)(freq + (freq < 128)); p->PrevSuccess = 1; p->RunLength++; - NextContext(p); + Ppmd7_NextContext(p); } +*/ void Ppmd7_Update2(CPpmd7 *p) { - p->MinContext->SummFreq += 4; - if ((p->FoundState->Freq += 4) > MAX_FREQ) - Rescale(p); + CPpmd_State *s = p->FoundState; + unsigned freq = s->Freq; + freq += 4; p->RunLength = p->InitRL; - UpdateModel(p); + p->MinContext->Union2.SummFreq = (UInt16)(p->MinContext->Union2.SummFreq + 4); + s->Freq = (Byte)freq; + if (freq > MAX_FREQ) + Ppmd7_Rescale(p); + Ppmd7_UpdateModel(p); +} + + + +/* +PPMd Memory Map: +{ + [ 0 ] contains subset of original raw text, that is required to create context + records, Some symbols are not written, when max order context was reached + [ Text ] free area + [ UnitsStart ] CPpmd_State vectors and CPpmd7_Context records + [ LoUnit ] free area for CPpmd_State and CPpmd7_Context items +[ HiUnit ] CPpmd7_Context records + [ Size ] end of array } + +These addresses don't cross at any time. +And the following condtions is true for addresses: + (0 <= Text < UnitsStart <= LoUnit <= HiUnit <= Size) + +Raw text is BYTE--aligned. +the data in block [ UnitsStart ... Size ] contains 12-bytes aligned UNITs. + +Last UNIT of array at offset (Size - 12) is root order-0 CPpmd7_Context record. +The code can free UNITs memory blocks that were allocated to store CPpmd_State vectors. +The code doesn't free UNITs allocated for CPpmd7_Context records. + +The code calls Ppmd7_RestartModel(), when there is no free memory for allocation. +And Ppmd7_RestartModel() changes the state to orignal start state, with full free block. + + +The code allocates UNITs with the following order: + +Allocation of 1 UNIT for Context record + - from free space (HiUnit) down to (LoUnit) + - from FreeList[0] + - Ppmd7_AllocUnitsRare() + +Ppmd7_AllocUnits() for CPpmd_State vectors: + - from FreeList[i] + - from free space (LoUnit) up to (HiUnit) + - Ppmd7_AllocUnitsRare() + +Ppmd7_AllocUnitsRare() + - if (GlueCount == 0) + { Glue lists, GlueCount = 255, allocate from FreeList[i]] } + - loop for all higher sized FreeList[...] lists + - from (UnitsStart - Text), GlueCount-- + - ERROR + + +Each Record with Context contains the CPpmd_State vector, where each +CPpmd_State contains the link to Successor. +There are 3 types of Successor: + 1) NULL-Successor - NULL pointer. NULL-Successor links can be stored + only in 0-order Root Context Record. + We use 0 value as NULL-Successor + 2) RAW-Successor - the link to position in raw text, + that "RAW-Successor" is being created after first + occurrence of new symbol for some existing context record. + (RAW-Successor > 0). + 3) RECORD-Successor - the link to CPpmd7_Context record of (Order+1), + that record is being created when we go via RAW-Successor again. + +For any successors at any time: the following condtions are true for Successor links: +(NULL-Successor < RAW-Successor < UnitsStart <= RECORD-Successor) + + +---------- Symbol Frequency, SummFreq and Range in Range_Coder ---------- + +CPpmd7_Context::SummFreq = Sum(Stats[].Freq) + Escape_Freq + +The PPMd code tries to fulfill the condition: + (SummFreq <= (256 * 128 = RC::kBot)) + +We have (Sum(Stats[].Freq) <= 256 * 124), because of (MAX_FREQ = 124) +So (4 = 128 - 124) is average reserve for Escape_Freq for each symbol. +If (CPpmd_State::Freq) is not aligned for 4, the reserve can be 5, 6 or 7. +SummFreq and Escape_Freq can be changed in Ppmd7_Rescale() and *Update*() functions. +Ppmd7_Rescale() can remove symbols only from max-order contexts. So Escape_Freq can increase after multiple calls of Ppmd7_Rescale() for +max-order context. + +When the PPMd code still break (Total <= RC::Range) condition in range coder, +we have two ways to resolve that problem: + 1) we can report error, if we want to keep compatibility with original PPMd code that has no fix for such cases. + 2) we can reduce (Total) value to (RC::Range) by reducing (Escape_Freq) part of (Total) value. +*/ + +#undef MAX_FREQ +#undef UNIT_SIZE +#undef U2B +#undef U2I +#undef I2U +#undef I2U_UInt16 +#undef REF +#undef STATS_REF +#undef CTX +#undef STATS +#undef ONE_STATE +#undef SUFFIX +#undef NODE +#undef EMPTY_NODE +#undef MEM_12_CPY +#undef SUCCESSOR +#undef SWAP_STATES diff --git a/src/plugins/7zip/7za/c/Ppmd7.h b/src/plugins/7zip/7za/c/Ppmd7.h index 87eefde8..d9eb326d 100644 --- a/src/plugins/7zip/7za/c/Ppmd7.h +++ b/src/plugins/7zip/7za/c/Ppmd7.h @@ -1,13 +1,11 @@ -/* Ppmd7.h -- PPMdH compression codec -2016-05-21 : Igor Pavlov : Public domain -This code is based on PPMd var.H (2001): Dmitry Shkarin : Public domain */ - -/* This code supports virtual RangeDecoder and includes the implementation -of RangeCoder from 7z, instead of RangeCoder from original PPMd var.H. -If you need the compatibility with original PPMd var.H, you can use external RangeDecoder */ +/* Ppmd7.h -- Ppmd7 (PPMdH) compression codec +2023-04-02 : Igor Pavlov : Public domain +This code is based on: + PPMd var.H (2001): Dmitry Shkarin : Public domain */ + -#ifndef __PPMD7_H -#define __PPMD7_H +#ifndef ZIP7_INC_PPMD7_H +#define ZIP7_INC_PPMD7_H #include "Ppmd.h" @@ -21,23 +19,56 @@ EXTERN_C_BEGIN struct CPpmd7_Context_; -typedef - #ifdef PPMD_32BIT - struct CPpmd7_Context_ * - #else - UInt32 - #endif - CPpmd7_Context_Ref; +typedef Ppmd_Ref_Type(struct CPpmd7_Context_) CPpmd7_Context_Ref; + +// MY_CPU_pragma_pack_push_1 typedef struct CPpmd7_Context_ { UInt16 NumStats; - UInt16 SummFreq; - CPpmd_State_Ref Stats; + + + union + { + UInt16 SummFreq; + CPpmd_State2 State2; + } Union2; + + union + { + CPpmd_State_Ref Stats; + CPpmd_State4 State4; + } Union4; + CPpmd7_Context_Ref Suffix; } CPpmd7_Context; -#define Ppmd7Context_OneState(p) ((CPpmd_State *)&(p)->SummFreq) +// MY_CPU_pragma_pop + +#define Ppmd7Context_OneState(p) ((CPpmd_State *)&(p)->Union2) + + + + +typedef struct +{ + UInt32 Range; + UInt32 Code; + UInt32 Low; + IByteInPtr Stream; +} CPpmd7_RangeDec; + + +typedef struct +{ + UInt32 Range; + Byte Cache; + // Byte _dummy_[3]; + UInt64 Low; + UInt64 CacheSize; + IByteOutPtr Stream; +} CPpmd7z_RangeEnc; + typedef struct { @@ -48,92 +79,102 @@ typedef struct UInt32 Size; UInt32 GlueCount; - Byte *Base, *LoUnit, *HiUnit, *Text, *UnitsStart; UInt32 AlignOffset; + Byte *Base, *LoUnit, *HiUnit, *Text, *UnitsStart; + - Byte Indx2Units[PPMD_NUM_INDEXES]; + + + union + { + CPpmd7_RangeDec dec; + CPpmd7z_RangeEnc enc; + } rc; + + Byte Indx2Units[PPMD_NUM_INDEXES + 2]; // +2 for alignment Byte Units2Indx[128]; CPpmd_Void_Ref FreeList[PPMD_NUM_INDEXES]; - Byte NS2Indx[256], NS2BSIndx[256], HB2Flag[256]; + + Byte NS2BSIndx[256], NS2Indx[256]; + Byte ExpEscape[16]; CPpmd_See DummySee, See[25][16]; UInt16 BinSumm[128][64]; + // int LastSymbol; } CPpmd7; + void Ppmd7_Construct(CPpmd7 *p); -Bool Ppmd7_Alloc(CPpmd7 *p, UInt32 size, ISzAlloc *alloc); -void Ppmd7_Free(CPpmd7 *p, ISzAlloc *alloc); +BoolInt Ppmd7_Alloc(CPpmd7 *p, UInt32 size, ISzAllocPtr alloc); +void Ppmd7_Free(CPpmd7 *p, ISzAllocPtr alloc); void Ppmd7_Init(CPpmd7 *p, unsigned maxOrder); #define Ppmd7_WasAllocated(p) ((p)->Base != NULL) /* ---------- Internal Functions ---------- */ -extern const Byte PPMD7_kExpEscape[16]; - -#ifdef PPMD_32BIT - #define Ppmd7_GetPtr(p, ptr) (ptr) - #define Ppmd7_GetContext(p, ptr) (ptr) - #define Ppmd7_GetStats(p, ctx) ((ctx)->Stats) -#else - #define Ppmd7_GetPtr(p, offs) ((void *)((p)->Base + (offs))) - #define Ppmd7_GetContext(p, offs) ((CPpmd7_Context *)Ppmd7_GetPtr((p), (offs))) - #define Ppmd7_GetStats(p, ctx) ((CPpmd_State *)Ppmd7_GetPtr((p), ((ctx)->Stats))) -#endif +#define Ppmd7_GetPtr(p, ptr) Ppmd_GetPtr(p, ptr) +#define Ppmd7_GetContext(p, ptr) Ppmd_GetPtr_Type(p, ptr, CPpmd7_Context) +#define Ppmd7_GetStats(p, ctx) Ppmd_GetPtr_Type(p, (ctx)->Union4.Stats, CPpmd_State) void Ppmd7_Update1(CPpmd7 *p); void Ppmd7_Update1_0(CPpmd7 *p); void Ppmd7_Update2(CPpmd7 *p); -void Ppmd7_UpdateBin(CPpmd7 *p); + +#define PPMD7_HiBitsFlag_3(sym) ((((unsigned)sym + 0xC0) >> (8 - 3)) & (1 << 3)) +#define PPMD7_HiBitsFlag_4(sym) ((((unsigned)sym + 0xC0) >> (8 - 4)) & (1 << 4)) +// #define PPMD7_HiBitsFlag_3(sym) ((sym) < 0x40 ? 0 : (1 << 3)) +// #define PPMD7_HiBitsFlag_4(sym) ((sym) < 0x40 ? 0 : (1 << 4)) #define Ppmd7_GetBinSumm(p) \ - &p->BinSumm[(unsigned)Ppmd7Context_OneState(p->MinContext)->Freq - 1][p->PrevSuccess + \ - p->NS2BSIndx[Ppmd7_GetContext(p, p->MinContext->Suffix)->NumStats - 1] + \ - (p->HiBitsFlag = p->HB2Flag[p->FoundState->Symbol]) + \ - 2 * p->HB2Flag[(unsigned)Ppmd7Context_OneState(p->MinContext)->Symbol] + \ - ((p->RunLength >> 26) & 0x20)] + &p->BinSumm[(size_t)(unsigned)Ppmd7Context_OneState(p->MinContext)->Freq - 1] \ + [ p->PrevSuccess + ((p->RunLength >> 26) & 0x20) \ + + p->NS2BSIndx[(size_t)Ppmd7_GetContext(p, p->MinContext->Suffix)->NumStats - 1] \ + + PPMD7_HiBitsFlag_4(Ppmd7Context_OneState(p->MinContext)->Symbol) \ + + (p->HiBitsFlag = PPMD7_HiBitsFlag_3(p->FoundState->Symbol)) ] CPpmd_See *Ppmd7_MakeEscFreq(CPpmd7 *p, unsigned numMasked, UInt32 *scale); -/* ---------- Decode ---------- */ +/* +We support two versions of Ppmd7 (PPMdH) methods that use same CPpmd7 structure: + 1) Ppmd7a_*: original PPMdH + 2) Ppmd7z_*: modified PPMdH with 7z Range Coder +Ppmd7_*: the structures and functions that are common for both versions of PPMd7 (PPMdH) +*/ -typedef struct -{ - UInt32 (*GetThreshold)(void *p, UInt32 total); - void (*Decode)(void *p, UInt32 start, UInt32 size); - UInt32 (*DecodeBit)(void *p, UInt32 size0); -} IPpmd7_RangeDec; +/* ---------- Decode ---------- */ -typedef struct -{ - IPpmd7_RangeDec p; - UInt32 Range; - UInt32 Code; - IByteIn *Stream; -} CPpmd7z_RangeDec; +#define PPMD7_SYM_END (-1) +#define PPMD7_SYM_ERROR (-2) -void Ppmd7z_RangeDec_CreateVTable(CPpmd7z_RangeDec *p); -Bool Ppmd7z_RangeDec_Init(CPpmd7z_RangeDec *p); -#define Ppmd7z_RangeDec_IsFinishedOK(p) ((p)->Code == 0) +/* +You must set (CPpmd7::rc.dec.Stream) before Ppmd7*_RangeDec_Init() -int Ppmd7_DecodeSymbol(CPpmd7 *p, IPpmd7_RangeDec *rc); +Ppmd7*_DecodeSymbol() +out: + >= 0 : decoded byte + -1 : PPMD7_SYM_END : End of payload marker + -2 : PPMD7_SYM_ERROR : Data error +*/ +/* Ppmd7a_* : original PPMdH */ +BoolInt Ppmd7a_RangeDec_Init(CPpmd7_RangeDec *p); +#define Ppmd7a_RangeDec_IsFinishedOK(p) ((p)->Code == 0) +int Ppmd7a_DecodeSymbol(CPpmd7 *p); -/* ---------- Encode ---------- */ +/* Ppmd7z_* : modified PPMdH with 7z Range Coder */ +BoolInt Ppmd7z_RangeDec_Init(CPpmd7_RangeDec *p); +#define Ppmd7z_RangeDec_IsFinishedOK(p) ((p)->Code == 0) +int Ppmd7z_DecodeSymbol(CPpmd7 *p); +// Byte *Ppmd7z_DecodeSymbols(CPpmd7 *p, Byte *buf, const Byte *lim); -typedef struct -{ - UInt64 Low; - UInt32 Range; - Byte Cache; - UInt64 CacheSize; - IByteOut *Stream; -} CPpmd7z_RangeEnc; -void Ppmd7z_RangeEnc_Init(CPpmd7z_RangeEnc *p); -void Ppmd7z_RangeEnc_FlushData(CPpmd7z_RangeEnc *p); +/* ---------- Encode ---------- */ -void Ppmd7_EncodeSymbol(CPpmd7 *p, CPpmd7z_RangeEnc *rc, int symbol); +void Ppmd7z_Init_RangeEnc(CPpmd7 *p); +void Ppmd7z_Flush_RangeEnc(CPpmd7 *p); +// void Ppmd7z_EncodeSymbol(CPpmd7 *p, int symbol); +void Ppmd7z_EncodeSymbols(CPpmd7 *p, const Byte *buf, const Byte *lim); EXTERN_C_END diff --git a/src/plugins/7zip/7za/c/Ppmd7Dec.c b/src/plugins/7zip/7za/c/Ppmd7Dec.c index 04b4b09e..081ab895 100644 --- a/src/plugins/7zip/7za/c/Ppmd7Dec.c +++ b/src/plugins/7zip/7za/c/Ppmd7Dec.c @@ -1,189 +1,312 @@ -/* Ppmd7Dec.c -- PPMdH Decoder -2010-03-12 : Igor Pavlov : Public domain -This code is based on PPMd var.H (2001): Dmitry Shkarin : Public domain */ +/* Ppmd7Dec.c -- Ppmd7z (PPMdH with 7z Range Coder) Decoder +2023-09-07 : Igor Pavlov : Public domain +This code is based on: + PPMd var.H (2001): Dmitry Shkarin : Public domain */ + #include "Precomp.h" #include "Ppmd7.h" -#define kTopValue (1 << 24) +#define kTopValue ((UInt32)1 << 24) + + +#define READ_BYTE(p) IByteIn_Read((p)->Stream) -Bool Ppmd7z_RangeDec_Init(CPpmd7z_RangeDec *p) +BoolInt Ppmd7z_RangeDec_Init(CPpmd7_RangeDec *p) { unsigned i; p->Code = 0; p->Range = 0xFFFFFFFF; - if (p->Stream->Read((void *)p->Stream) != 0) + if (READ_BYTE(p) != 0) return False; for (i = 0; i < 4; i++) - p->Code = (p->Code << 8) | p->Stream->Read((void *)p->Stream); + p->Code = (p->Code << 8) | READ_BYTE(p); return (p->Code < 0xFFFFFFFF); } -static UInt32 Range_GetThreshold(void *pp, UInt32 total) -{ - CPpmd7z_RangeDec *p = (CPpmd7z_RangeDec *)pp; - return (p->Code) / (p->Range /= total); -} +#define RC_NORM_BASE(p) if ((p)->Range < kTopValue) \ + { (p)->Code = ((p)->Code << 8) | READ_BYTE(p); (p)->Range <<= 8; -static void Range_Normalize(CPpmd7z_RangeDec *p) -{ - if (p->Range < kTopValue) - { - p->Code = (p->Code << 8) | p->Stream->Read((void *)p->Stream); - p->Range <<= 8; - if (p->Range < kTopValue) - { - p->Code = (p->Code << 8) | p->Stream->Read((void *)p->Stream); - p->Range <<= 8; - } - } -} +#define RC_NORM_1(p) RC_NORM_BASE(p) } +#define RC_NORM(p) RC_NORM_BASE(p) RC_NORM_BASE(p) }} -static void Range_Decode(void *pp, UInt32 start, UInt32 size) -{ - CPpmd7z_RangeDec *p = (CPpmd7z_RangeDec *)pp; - p->Code -= start * p->Range; - p->Range *= size; - Range_Normalize(p); -} +// we must use only one type of Normalization from two: LOCAL or REMOTE +#define RC_NORM_LOCAL(p) // RC_NORM(p) +#define RC_NORM_REMOTE(p) RC_NORM(p) -static UInt32 Range_DecodeBit(void *pp, UInt32 size0) -{ - CPpmd7z_RangeDec *p = (CPpmd7z_RangeDec *)pp; - UInt32 newBound = (p->Range >> 14) * size0; - UInt32 symbol; - if (p->Code < newBound) - { - symbol = 0; - p->Range = newBound; - } - else - { - symbol = 1; - p->Code -= newBound; - p->Range -= newBound; - } - Range_Normalize(p); - return symbol; -} +#define R (&p->rc.dec) -void Ppmd7z_RangeDec_CreateVTable(CPpmd7z_RangeDec *p) +Z7_FORCE_INLINE +// Z7_NO_INLINE +static void Ppmd7z_RD_Decode(CPpmd7 *p, UInt32 start, UInt32 size) { - p->p.GetThreshold = Range_GetThreshold; - p->p.Decode = Range_Decode; - p->p.DecodeBit = Range_DecodeBit; + + + R->Code -= start * R->Range; + R->Range *= size; + RC_NORM_LOCAL(R) } +#define RC_Decode(start, size) Ppmd7z_RD_Decode(p, start, size); +#define RC_DecodeFinal(start, size) RC_Decode(start, size) RC_NORM_REMOTE(R) +#define RC_GetThreshold(total) (R->Code / (R->Range /= (total))) + -#define MASK(sym) ((signed char *)charMask)[sym] +#define CTX(ref) ((CPpmd7_Context *)Ppmd7_GetContext(p, ref)) +// typedef CPpmd7_Context * CTX_PTR; +#define SUCCESSOR(p) Ppmd_GET_SUCCESSOR(p) +void Ppmd7_UpdateModel(CPpmd7 *p); -int Ppmd7_DecodeSymbol(CPpmd7 *p, IPpmd7_RangeDec *rc) +#define MASK(sym) ((Byte *)charMask)[sym] +// Z7_FORCE_INLINE +// static +int Ppmd7z_DecodeSymbol(CPpmd7 *p) { size_t charMask[256 / sizeof(size_t)]; + if (p->MinContext->NumStats != 1) { CPpmd_State *s = Ppmd7_GetStats(p, p->MinContext); unsigned i; UInt32 count, hiCnt; - if ((count = rc->GetThreshold(rc, p->MinContext->SummFreq)) < (hiCnt = s->Freq)) + const UInt32 summFreq = p->MinContext->Union2.SummFreq; + + + + + count = RC_GetThreshold(summFreq); + hiCnt = count; + + if ((Int32)(count -= s->Freq) < 0) { - Byte symbol; - rc->Decode(rc, 0, s->Freq); + Byte sym; + RC_DecodeFinal(0, s->Freq) p->FoundState = s; - symbol = s->Symbol; + sym = s->Symbol; Ppmd7_Update1_0(p); - return symbol; + return sym; } + p->PrevSuccess = 0; - i = p->MinContext->NumStats - 1; + i = (unsigned)p->MinContext->NumStats - 1; + do { - if ((hiCnt += (++s)->Freq) > count) + if ((Int32)(count -= (++s)->Freq) < 0) { - Byte symbol; - rc->Decode(rc, hiCnt - s->Freq, s->Freq); + Byte sym; + RC_DecodeFinal((hiCnt - count) - s->Freq, s->Freq) p->FoundState = s; - symbol = s->Symbol; + sym = s->Symbol; Ppmd7_Update1(p); - return symbol; + return sym; } } while (--i); - if (count >= p->MinContext->SummFreq) - return -2; - p->HiBitsFlag = p->HB2Flag[p->FoundState->Symbol]; - rc->Decode(rc, hiCnt, p->MinContext->SummFreq - hiCnt); - PPMD_SetAllBitsIn256Bytes(charMask); - MASK(s->Symbol) = 0; - i = p->MinContext->NumStats - 1; - do { MASK((--s)->Symbol) = 0; } while (--i); + + if (hiCnt >= summFreq) + return PPMD7_SYM_ERROR; + + hiCnt -= count; + RC_Decode(hiCnt, summFreq - hiCnt) + + p->HiBitsFlag = PPMD7_HiBitsFlag_3(p->FoundState->Symbol); + PPMD_SetAllBitsIn256Bytes(charMask) + // i = p->MinContext->NumStats - 1; + // do { MASK((--s)->Symbol) = 0; } while (--i); + { + CPpmd_State *s2 = Ppmd7_GetStats(p, p->MinContext); + MASK(s->Symbol) = 0; + do + { + const unsigned sym0 = s2[0].Symbol; + const unsigned sym1 = s2[1].Symbol; + s2 += 2; + MASK(sym0) = 0; + MASK(sym1) = 0; + } + while (s2 < s); + } } else { + CPpmd_State *s = Ppmd7Context_OneState(p->MinContext); UInt16 *prob = Ppmd7_GetBinSumm(p); - if (rc->DecodeBit(rc, *prob) == 0) + UInt32 pr = *prob; + UInt32 size0 = (R->Range >> 14) * pr; + pr = PPMD_UPDATE_PROB_1(pr); + + if (R->Code < size0) { - Byte symbol; - *prob = (UInt16)PPMD_UPDATE_PROB_0(*prob); - symbol = (p->FoundState = Ppmd7Context_OneState(p->MinContext))->Symbol; - Ppmd7_UpdateBin(p); - return symbol; + Byte sym; + *prob = (UInt16)(pr + (1 << PPMD_INT_BITS)); + + // RangeDec_DecodeBit0(size0); + R->Range = size0; + RC_NORM_1(R) + /* we can use single byte normalization here because of + (min(BinSumm[][]) = 95) > (1 << (14 - 8)) */ + + // sym = (p->FoundState = Ppmd7Context_OneState(p->MinContext))->Symbol; + // Ppmd7_UpdateBin(p); + { + unsigned freq = s->Freq; + CPpmd7_Context *c = CTX(SUCCESSOR(s)); + sym = s->Symbol; + p->FoundState = s; + p->PrevSuccess = 1; + p->RunLength++; + s->Freq = (Byte)(freq + (freq < 128)); + // NextContext(p); + if (p->OrderFall == 0 && (const Byte *)c > p->Text) + p->MaxContext = p->MinContext = c; + else + Ppmd7_UpdateModel(p); + } + return sym; } - *prob = (UInt16)PPMD_UPDATE_PROB_1(*prob); - p->InitEsc = PPMD7_kExpEscape[*prob >> 10]; - PPMD_SetAllBitsIn256Bytes(charMask); + + *prob = (UInt16)pr; + p->InitEsc = p->ExpEscape[pr >> 10]; + + // RangeDec_DecodeBit1(size0); + + R->Code -= size0; + R->Range -= size0; + RC_NORM_LOCAL(R) + + PPMD_SetAllBitsIn256Bytes(charMask) MASK(Ppmd7Context_OneState(p->MinContext)->Symbol) = 0; p->PrevSuccess = 0; } + for (;;) { - CPpmd_State *ps[256], *s; + CPpmd_State *s, *s2; UInt32 freqSum, count, hiCnt; + CPpmd_See *see; - unsigned i, num, numMasked = p->MinContext->NumStats; + CPpmd7_Context *mc; + unsigned numMasked; + RC_NORM_REMOTE(R) + mc = p->MinContext; + numMasked = mc->NumStats; + do { p->OrderFall++; - if (!p->MinContext->Suffix) - return -1; - p->MinContext = Ppmd7_GetContext(p, p->MinContext->Suffix); + if (!mc->Suffix) + return PPMD7_SYM_END; + mc = Ppmd7_GetContext(p, mc->Suffix); } - while (p->MinContext->NumStats == numMasked); - hiCnt = 0; - s = Ppmd7_GetStats(p, p->MinContext); - i = 0; - num = p->MinContext->NumStats - numMasked; - do + while (mc->NumStats == numMasked); + + s = Ppmd7_GetStats(p, mc); + { - int k = (int)(MASK(s->Symbol)); - hiCnt += (s->Freq & k); - ps[i] = s++; - i -= k; + unsigned num = mc->NumStats; + unsigned num2 = num / 2; + + num &= 1; + hiCnt = (s->Freq & (UInt32)(MASK(s->Symbol))) & (0 - (UInt32)num); + s += num; + p->MinContext = mc; + + do + { + const unsigned sym0 = s[0].Symbol; + const unsigned sym1 = s[1].Symbol; + s += 2; + hiCnt += (s[-2].Freq & (UInt32)(MASK(sym0))); + hiCnt += (s[-1].Freq & (UInt32)(MASK(sym1))); + } + while (--num2); } - while (i != num); - + see = Ppmd7_MakeEscFreq(p, numMasked, &freqSum); freqSum += hiCnt; - count = rc->GetThreshold(rc, freqSum); + + + + + count = RC_GetThreshold(freqSum); if (count < hiCnt) { - Byte symbol; - CPpmd_State **pps = ps; - for (hiCnt = 0; (hiCnt += (*pps)->Freq) <= count; pps++); - s = *pps; - rc->Decode(rc, hiCnt - s->Freq, s->Freq); - Ppmd_See_Update(see); + Byte sym; + + s = Ppmd7_GetStats(p, p->MinContext); + hiCnt = count; + // count -= s->Freq & (UInt32)(MASK(s->Symbol)); + // if ((Int32)count >= 0) + { + for (;;) + { + count -= s->Freq & (UInt32)(MASK((s)->Symbol)); s++; if ((Int32)count < 0) break; + // count -= s->Freq & (UInt32)(MASK((s)->Symbol)); s++; if ((Int32)count < 0) break; + } + } + s--; + RC_DecodeFinal((hiCnt - count) - s->Freq, s->Freq) + + // new (see->Summ) value can overflow over 16-bits in some rare cases + Ppmd_See_UPDATE(see) p->FoundState = s; - symbol = s->Symbol; + sym = s->Symbol; Ppmd7_Update2(p); - return symbol; + return sym; } + if (count >= freqSum) - return -2; - rc->Decode(rc, hiCnt, freqSum - hiCnt); + return PPMD7_SYM_ERROR; + + RC_Decode(hiCnt, freqSum - hiCnt) + + // We increase (see->Summ) for sum of Freqs of all non_Masked symbols. + // new (see->Summ) value can overflow over 16-bits in some rare cases see->Summ = (UInt16)(see->Summ + freqSum); - do { MASK(ps[--i]->Symbol) = 0; } while (i != 0); + + s = Ppmd7_GetStats(p, p->MinContext); + s2 = s + p->MinContext->NumStats; + do + { + MASK(s->Symbol) = 0; + s++; + } + while (s != s2); } } + +/* +Byte *Ppmd7z_DecodeSymbols(CPpmd7 *p, Byte *buf, const Byte *lim) +{ + int sym = 0; + if (buf != lim) + do + { + sym = Ppmd7z_DecodeSymbol(p); + if (sym < 0) + break; + *buf = (Byte)sym; + } + while (++buf < lim); + p->LastSymbol = sym; + return buf; +} +*/ + +#undef kTopValue +#undef READ_BYTE +#undef RC_NORM_BASE +#undef RC_NORM_1 +#undef RC_NORM +#undef RC_NORM_LOCAL +#undef RC_NORM_REMOTE +#undef R +#undef RC_Decode +#undef RC_DecodeFinal +#undef RC_GetThreshold +#undef CTX +#undef SUCCESSOR +#undef MASK diff --git a/src/plugins/7zip/7za/c/Ppmd7Enc.c b/src/plugins/7zip/7za/c/Ppmd7Enc.c index e6dd3d2f..49cbbe64 100644 --- a/src/plugins/7zip/7za/c/Ppmd7Enc.c +++ b/src/plugins/7zip/7za/c/Ppmd7Enc.c @@ -1,104 +1,123 @@ -/* Ppmd7Enc.c -- PPMdH Encoder -2015-09-28 : Igor Pavlov : Public domain -This code is based on PPMd var.H (2001): Dmitry Shkarin : Public domain */ +/* Ppmd7Enc.c -- Ppmd7z (PPMdH with 7z Range Coder) Encoder +2023-09-07 : Igor Pavlov : Public domain +This code is based on: + PPMd var.H (2001): Dmitry Shkarin : Public domain */ + #include "Precomp.h" #include "Ppmd7.h" -#define kTopValue (1 << 24) +#define kTopValue ((UInt32)1 << 24) + +#define R (&p->rc.enc) -void Ppmd7z_RangeEnc_Init(CPpmd7z_RangeEnc *p) +void Ppmd7z_Init_RangeEnc(CPpmd7 *p) { - p->Low = 0; - p->Range = 0xFFFFFFFF; - p->Cache = 0; - p->CacheSize = 1; + R->Low = 0; + R->Range = 0xFFFFFFFF; + R->Cache = 0; + R->CacheSize = 1; } -static void RangeEnc_ShiftLow(CPpmd7z_RangeEnc *p) +Z7_NO_INLINE +static void Ppmd7z_RangeEnc_ShiftLow(CPpmd7 *p) { - if ((UInt32)p->Low < (UInt32)0xFF000000 || (unsigned)(p->Low >> 32) != 0) + if ((UInt32)R->Low < (UInt32)0xFF000000 || (unsigned)(R->Low >> 32) != 0) { - Byte temp = p->Cache; + Byte temp = R->Cache; do { - p->Stream->Write(p->Stream, (Byte)(temp + (Byte)(p->Low >> 32))); + IByteOut_Write(R->Stream, (Byte)(temp + (Byte)(R->Low >> 32))); temp = 0xFF; } - while (--p->CacheSize != 0); - p->Cache = (Byte)((UInt32)p->Low >> 24); + while (--R->CacheSize != 0); + R->Cache = (Byte)((UInt32)R->Low >> 24); } - p->CacheSize++; - p->Low = (UInt32)p->Low << 8; + R->CacheSize++; + R->Low = (UInt32)((UInt32)R->Low << 8); } -static void RangeEnc_Encode(CPpmd7z_RangeEnc *p, UInt32 start, UInt32 size, UInt32 total) -{ - p->Low += start * (p->Range /= total); - p->Range *= size; - while (p->Range < kTopValue) - { - p->Range <<= 8; - RangeEnc_ShiftLow(p); - } -} +#define RC_NORM_BASE(p) if (R->Range < kTopValue) { R->Range <<= 8; Ppmd7z_RangeEnc_ShiftLow(p); +#define RC_NORM_1(p) RC_NORM_BASE(p) } +#define RC_NORM(p) RC_NORM_BASE(p) RC_NORM_BASE(p) }} -static void RangeEnc_EncodeBit_0(CPpmd7z_RangeEnc *p, UInt32 size0) -{ - p->Range = (p->Range >> 14) * size0; - while (p->Range < kTopValue) - { - p->Range <<= 8; - RangeEnc_ShiftLow(p); - } -} +// we must use only one type of Normalization from two: LOCAL or REMOTE +#define RC_NORM_LOCAL(p) // RC_NORM(p) +#define RC_NORM_REMOTE(p) RC_NORM(p) -static void RangeEnc_EncodeBit_1(CPpmd7z_RangeEnc *p, UInt32 size0) +/* +#define Ppmd7z_RangeEnc_Encode(p, start, _size_) \ + { UInt32 size = _size_; \ + R->Low += start * R->Range; \ + R->Range *= size; \ + RC_NORM_LOCAL(p); } +*/ + +Z7_FORCE_INLINE +// Z7_NO_INLINE +static void Ppmd7z_RangeEnc_Encode(CPpmd7 *p, UInt32 start, UInt32 size) { - UInt32 newBound = (p->Range >> 14) * size0; - p->Low += newBound; - p->Range -= newBound; - while (p->Range < kTopValue) - { - p->Range <<= 8; - RangeEnc_ShiftLow(p); - } + R->Low += start * R->Range; + R->Range *= size; + RC_NORM_LOCAL(p) } -void Ppmd7z_RangeEnc_FlushData(CPpmd7z_RangeEnc *p) +void Ppmd7z_Flush_RangeEnc(CPpmd7 *p) { unsigned i; for (i = 0; i < 5; i++) - RangeEnc_ShiftLow(p); + Ppmd7z_RangeEnc_ShiftLow(p); } -#define MASK(sym) ((signed char *)charMask)[sym] -void Ppmd7_EncodeSymbol(CPpmd7 *p, CPpmd7z_RangeEnc *rc, int symbol) +#define RC_Encode(start, size) Ppmd7z_RangeEnc_Encode(p, start, size); +#define RC_EncodeFinal(start, size) RC_Encode(start, size) RC_NORM_REMOTE(p) + +#define CTX(ref) ((CPpmd7_Context *)Ppmd7_GetContext(p, ref)) +#define SUFFIX(ctx) CTX((ctx)->Suffix) +// typedef CPpmd7_Context * CTX_PTR; +#define SUCCESSOR(p) Ppmd_GET_SUCCESSOR(p) + +void Ppmd7_UpdateModel(CPpmd7 *p); + +#define MASK(sym) ((Byte *)charMask)[sym] + +Z7_FORCE_INLINE +static +void Ppmd7z_EncodeSymbol(CPpmd7 *p, int symbol) { size_t charMask[256 / sizeof(size_t)]; + if (p->MinContext->NumStats != 1) { CPpmd_State *s = Ppmd7_GetStats(p, p->MinContext); UInt32 sum; unsigned i; + + + + + R->Range /= p->MinContext->Union2.SummFreq; + if (s->Symbol == symbol) { - RangeEnc_Encode(rc, 0, s->Freq, p->MinContext->SummFreq); + // R->Range /= p->MinContext->Union2.SummFreq; + RC_EncodeFinal(0, s->Freq) p->FoundState = s; Ppmd7_Update1_0(p); return; } p->PrevSuccess = 0; sum = s->Freq; - i = p->MinContext->NumStats - 1; + i = (unsigned)p->MinContext->NumStats - 1; do { if ((++s)->Symbol == symbol) { - RangeEnc_Encode(rc, sum, s->Freq, p->MinContext->SummFreq); + // R->Range /= p->MinContext->Union2.SummFreq; + RC_EncodeFinal(sum, s->Freq) p->FoundState = s; Ppmd7_Update1(p); return; @@ -106,82 +125,213 @@ void Ppmd7_EncodeSymbol(CPpmd7 *p, CPpmd7z_RangeEnc *rc, int symbol) sum += s->Freq; } while (--i); + + // R->Range /= p->MinContext->Union2.SummFreq; + RC_Encode(sum, p->MinContext->Union2.SummFreq - sum) - p->HiBitsFlag = p->HB2Flag[p->FoundState->Symbol]; - PPMD_SetAllBitsIn256Bytes(charMask); - MASK(s->Symbol) = 0; - i = p->MinContext->NumStats - 1; - do { MASK((--s)->Symbol) = 0; } while (--i); - RangeEnc_Encode(rc, sum, p->MinContext->SummFreq - sum, p->MinContext->SummFreq); + p->HiBitsFlag = PPMD7_HiBitsFlag_3(p->FoundState->Symbol); + PPMD_SetAllBitsIn256Bytes(charMask) + // MASK(s->Symbol) = 0; + // i = p->MinContext->NumStats - 1; + // do { MASK((--s)->Symbol) = 0; } while (--i); + { + CPpmd_State *s2 = Ppmd7_GetStats(p, p->MinContext); + MASK(s->Symbol) = 0; + do + { + const unsigned sym0 = s2[0].Symbol; + const unsigned sym1 = s2[1].Symbol; + s2 += 2; + MASK(sym0) = 0; + MASK(sym1) = 0; + } + while (s2 < s); + } } else { UInt16 *prob = Ppmd7_GetBinSumm(p); CPpmd_State *s = Ppmd7Context_OneState(p->MinContext); + UInt32 pr = *prob; + const UInt32 bound = (R->Range >> 14) * pr; + pr = PPMD_UPDATE_PROB_1(pr); if (s->Symbol == symbol) { - RangeEnc_EncodeBit_0(rc, *prob); - *prob = (UInt16)PPMD_UPDATE_PROB_0(*prob); - p->FoundState = s; - Ppmd7_UpdateBin(p); + *prob = (UInt16)(pr + (1 << PPMD_INT_BITS)); + // RangeEnc_EncodeBit_0(p, bound); + R->Range = bound; + RC_NORM_1(p) + + // p->FoundState = s; + // Ppmd7_UpdateBin(p); + { + const unsigned freq = s->Freq; + CPpmd7_Context *c = CTX(SUCCESSOR(s)); + p->FoundState = s; + p->PrevSuccess = 1; + p->RunLength++; + s->Freq = (Byte)(freq + (freq < 128)); + // NextContext(p); + if (p->OrderFall == 0 && (const Byte *)c > p->Text) + p->MaxContext = p->MinContext = c; + else + Ppmd7_UpdateModel(p); + } return; } - else - { - RangeEnc_EncodeBit_1(rc, *prob); - *prob = (UInt16)PPMD_UPDATE_PROB_1(*prob); - p->InitEsc = PPMD7_kExpEscape[*prob >> 10]; - PPMD_SetAllBitsIn256Bytes(charMask); - MASK(s->Symbol) = 0; - p->PrevSuccess = 0; - } + + *prob = (UInt16)pr; + p->InitEsc = p->ExpEscape[pr >> 10]; + // RangeEnc_EncodeBit_1(p, bound); + R->Low += bound; + R->Range -= bound; + RC_NORM_LOCAL(p) + + PPMD_SetAllBitsIn256Bytes(charMask) + MASK(s->Symbol) = 0; + p->PrevSuccess = 0; } + for (;;) { - UInt32 escFreq; CPpmd_See *see; CPpmd_State *s; - UInt32 sum; - unsigned i, numMasked = p->MinContext->NumStats; + UInt32 sum, escFreq; + CPpmd7_Context *mc; + unsigned i, numMasked; + + RC_NORM_REMOTE(p) + + mc = p->MinContext; + numMasked = mc->NumStats; + do { p->OrderFall++; - if (!p->MinContext->Suffix) + if (!mc->Suffix) return; /* EndMarker (symbol = -1) */ - p->MinContext = Ppmd7_GetContext(p, p->MinContext->Suffix); + mc = Ppmd7_GetContext(p, mc->Suffix); + i = mc->NumStats; } - while (p->MinContext->NumStats == numMasked); + while (i == numMasked); + + p->MinContext = mc; - see = Ppmd7_MakeEscFreq(p, numMasked, &escFreq); - s = Ppmd7_GetStats(p, p->MinContext); + // see = Ppmd7_MakeEscFreq(p, numMasked, &escFreq); + { + if (i != 256) + { + unsigned nonMasked = i - numMasked; + see = p->See[(unsigned)p->NS2Indx[(size_t)nonMasked - 1]] + + p->HiBitsFlag + + (nonMasked < (unsigned)SUFFIX(mc)->NumStats - i) + + 2 * (unsigned)(mc->Union2.SummFreq < 11 * i) + + 4 * (unsigned)(numMasked > nonMasked); + { + // if (see->Summ) field is larger than 16-bit, we need only low 16 bits of Summ + unsigned summ = (UInt16)see->Summ; // & 0xFFFF + unsigned r = (summ >> see->Shift); + see->Summ = (UInt16)(summ - r); + escFreq = r + (r == 0); + } + } + else + { + see = &p->DummySee; + escFreq = 1; + } + } + + s = Ppmd7_GetStats(p, mc); sum = 0; - i = p->MinContext->NumStats; + // i = mc->NumStats; + do { - int cur = s->Symbol; - if (cur == symbol) + const unsigned cur = s->Symbol; + if ((int)cur == symbol) { - UInt32 low = sum; - CPpmd_State *s1 = s; - do + const UInt32 low = sum; + const UInt32 freq = s->Freq; + unsigned num2; + + Ppmd_See_UPDATE(see) + p->FoundState = s; + sum += escFreq; + + num2 = i / 2; + i &= 1; + sum += freq & (0 - (UInt32)i); + if (num2 != 0) { - sum += (s->Freq & (int)(MASK(s->Symbol))); - s++; + s += i; + do + { + const unsigned sym0 = s[0].Symbol; + const unsigned sym1 = s[1].Symbol; + s += 2; + sum += (s[-2].Freq & (unsigned)(MASK(sym0))); + sum += (s[-1].Freq & (unsigned)(MASK(sym1))); + } + while (--num2); } - while (--i); - RangeEnc_Encode(rc, low, s1->Freq, sum + escFreq); - Ppmd_See_Update(see); - p->FoundState = s1; + + + R->Range /= sum; + RC_EncodeFinal(low, freq) Ppmd7_Update2(p); return; } - sum += (s->Freq & (int)(MASK(cur))); - MASK(cur) = 0; + sum += (s->Freq & (unsigned)(MASK(cur))); s++; } while (--i); - RangeEnc_Encode(rc, sum, escFreq, sum + escFreq); - see->Summ = (UInt16)(see->Summ + sum + escFreq); + { + const UInt32 total = sum + escFreq; + see->Summ = (UInt16)(see->Summ + total); + + R->Range /= total; + RC_Encode(sum, escFreq) + } + + { + const CPpmd_State *s2 = Ppmd7_GetStats(p, p->MinContext); + s--; + MASK(s->Symbol) = 0; + do + { + const unsigned sym0 = s2[0].Symbol; + const unsigned sym1 = s2[1].Symbol; + s2 += 2; + MASK(sym0) = 0; + MASK(sym1) = 0; + } + while (s2 < s); + } } } + + +void Ppmd7z_EncodeSymbols(CPpmd7 *p, const Byte *buf, const Byte *lim) +{ + for (; buf < lim; buf++) + { + Ppmd7z_EncodeSymbol(p, *buf); + } +} + +#undef kTopValue +#undef WRITE_BYTE +#undef RC_NORM_BASE +#undef RC_NORM_1 +#undef RC_NORM +#undef RC_NORM_LOCAL +#undef RC_NORM_REMOTE +#undef R +#undef RC_Encode +#undef RC_EncodeFinal +#undef SUFFIX +#undef CTX +#undef SUCCESSOR +#undef MASK diff --git a/src/plugins/7zip/7za/c/Ppmd7aDec.c b/src/plugins/7zip/7za/c/Ppmd7aDec.c new file mode 100644 index 00000000..ef86dde7 --- /dev/null +++ b/src/plugins/7zip/7za/c/Ppmd7aDec.c @@ -0,0 +1,295 @@ +/* Ppmd7aDec.c -- PPMd7a (PPMdH) Decoder +2023-09-07 : Igor Pavlov : Public domain +This code is based on: + PPMd var.H (2001): Dmitry Shkarin : Public domain + Carryless rangecoder (1999): Dmitry Subbotin : Public domain */ + +#include "Precomp.h" + +#include "Ppmd7.h" + +#define kTop ((UInt32)1 << 24) +#define kBot ((UInt32)1 << 15) + +#define READ_BYTE(p) IByteIn_Read((p)->Stream) + +BoolInt Ppmd7a_RangeDec_Init(CPpmd7_RangeDec *p) +{ + unsigned i; + p->Code = 0; + p->Range = 0xFFFFFFFF; + p->Low = 0; + + for (i = 0; i < 4; i++) + p->Code = (p->Code << 8) | READ_BYTE(p); + return (p->Code < 0xFFFFFFFF); +} + +#define RC_NORM(p) \ + while ((p->Low ^ (p->Low + p->Range)) < kTop \ + || (p->Range < kBot && ((p->Range = (0 - p->Low) & (kBot - 1)), 1))) { \ + p->Code = (p->Code << 8) | READ_BYTE(p); \ + p->Range <<= 8; p->Low <<= 8; } + +// we must use only one type of Normalization from two: LOCAL or REMOTE +#define RC_NORM_LOCAL(p) // RC_NORM(p) +#define RC_NORM_REMOTE(p) RC_NORM(p) + +#define R (&p->rc.dec) + +Z7_FORCE_INLINE +// Z7_NO_INLINE +static void Ppmd7a_RD_Decode(CPpmd7 *p, UInt32 start, UInt32 size) +{ + start *= R->Range; + R->Low += start; + R->Code -= start; + R->Range *= size; + RC_NORM_LOCAL(R) +} + +#define RC_Decode(start, size) Ppmd7a_RD_Decode(p, start, size); +#define RC_DecodeFinal(start, size) RC_Decode(start, size) RC_NORM_REMOTE(R) +#define RC_GetThreshold(total) (R->Code / (R->Range /= (total))) + + +#define CTX(ref) ((CPpmd7_Context *)Ppmd7_GetContext(p, ref)) +typedef CPpmd7_Context * CTX_PTR; +#define SUCCESSOR(p) Ppmd_GET_SUCCESSOR(p) +void Ppmd7_UpdateModel(CPpmd7 *p); + +#define MASK(sym) ((Byte *)charMask)[sym] + + +int Ppmd7a_DecodeSymbol(CPpmd7 *p) +{ + size_t charMask[256 / sizeof(size_t)]; + + if (p->MinContext->NumStats != 1) + { + CPpmd_State *s = Ppmd7_GetStats(p, p->MinContext); + unsigned i; + UInt32 count, hiCnt; + const UInt32 summFreq = p->MinContext->Union2.SummFreq; + + if (summFreq > R->Range) + return PPMD7_SYM_ERROR; + + count = RC_GetThreshold(summFreq); + hiCnt = count; + + if ((Int32)(count -= s->Freq) < 0) + { + Byte sym; + RC_DecodeFinal(0, s->Freq) + p->FoundState = s; + sym = s->Symbol; + Ppmd7_Update1_0(p); + return sym; + } + + p->PrevSuccess = 0; + i = (unsigned)p->MinContext->NumStats - 1; + + do + { + if ((Int32)(count -= (++s)->Freq) < 0) + { + Byte sym; + RC_DecodeFinal((hiCnt - count) - s->Freq, s->Freq) + p->FoundState = s; + sym = s->Symbol; + Ppmd7_Update1(p); + return sym; + } + } + while (--i); + + if (hiCnt >= summFreq) + return PPMD7_SYM_ERROR; + + hiCnt -= count; + RC_Decode(hiCnt, summFreq - hiCnt) + + p->HiBitsFlag = PPMD7_HiBitsFlag_3(p->FoundState->Symbol); + PPMD_SetAllBitsIn256Bytes(charMask) + // i = p->MinContext->NumStats - 1; + // do { MASK((--s)->Symbol) = 0; } while (--i); + { + CPpmd_State *s2 = Ppmd7_GetStats(p, p->MinContext); + MASK(s->Symbol) = 0; + do + { + const unsigned sym0 = s2[0].Symbol; + const unsigned sym1 = s2[1].Symbol; + s2 += 2; + MASK(sym0) = 0; + MASK(sym1) = 0; + } + while (s2 < s); + } + } + else + { + CPpmd_State *s = Ppmd7Context_OneState(p->MinContext); + UInt16 *prob = Ppmd7_GetBinSumm(p); + UInt32 pr = *prob; + UInt32 size0 = (R->Range >> 14) * pr; + pr = PPMD_UPDATE_PROB_1(pr); + + if (R->Code < size0) + { + Byte sym; + *prob = (UInt16)(pr + (1 << PPMD_INT_BITS)); + + // RangeDec_DecodeBit0(size0); + R->Range = size0; + RC_NORM(R) + + + + // sym = (p->FoundState = Ppmd7Context_OneState(p->MinContext))->Symbol; + // Ppmd7_UpdateBin(p); + { + unsigned freq = s->Freq; + CTX_PTR c = CTX(SUCCESSOR(s)); + sym = s->Symbol; + p->FoundState = s; + p->PrevSuccess = 1; + p->RunLength++; + s->Freq = (Byte)(freq + (freq < 128)); + // NextContext(p); + if (p->OrderFall == 0 && (const Byte *)c > p->Text) + p->MaxContext = p->MinContext = c; + else + Ppmd7_UpdateModel(p); + } + return sym; + } + + *prob = (UInt16)pr; + p->InitEsc = p->ExpEscape[pr >> 10]; + + // RangeDec_DecodeBit1(size0); + R->Low += size0; + R->Code -= size0; + R->Range = (R->Range & ~((UInt32)PPMD_BIN_SCALE - 1)) - size0; + RC_NORM_LOCAL(R) + + PPMD_SetAllBitsIn256Bytes(charMask) + MASK(Ppmd7Context_OneState(p->MinContext)->Symbol) = 0; + p->PrevSuccess = 0; + } + + for (;;) + { + CPpmd_State *s, *s2; + UInt32 freqSum, count, hiCnt; + + CPpmd_See *see; + CPpmd7_Context *mc; + unsigned numMasked; + RC_NORM_REMOTE(R) + mc = p->MinContext; + numMasked = mc->NumStats; + + do + { + p->OrderFall++; + if (!mc->Suffix) + return PPMD7_SYM_END; + mc = Ppmd7_GetContext(p, mc->Suffix); + } + while (mc->NumStats == numMasked); + + s = Ppmd7_GetStats(p, mc); + + { + unsigned num = mc->NumStats; + unsigned num2 = num / 2; + + num &= 1; + hiCnt = (s->Freq & (UInt32)(MASK(s->Symbol))) & (0 - (UInt32)num); + s += num; + p->MinContext = mc; + + do + { + const unsigned sym0 = s[0].Symbol; + const unsigned sym1 = s[1].Symbol; + s += 2; + hiCnt += (s[-2].Freq & (UInt32)(MASK(sym0))); + hiCnt += (s[-1].Freq & (UInt32)(MASK(sym1))); + } + while (--num2); + } + + see = Ppmd7_MakeEscFreq(p, numMasked, &freqSum); + freqSum += hiCnt; + + if (freqSum > R->Range) + return PPMD7_SYM_ERROR; + + count = RC_GetThreshold(freqSum); + + if (count < hiCnt) + { + Byte sym; + + s = Ppmd7_GetStats(p, p->MinContext); + hiCnt = count; + // count -= s->Freq & (UInt32)(MASK(s->Symbol)); + // if ((Int32)count >= 0) + { + for (;;) + { + count -= s->Freq & (UInt32)(MASK((s)->Symbol)); s++; if ((Int32)count < 0) break; + // count -= s->Freq & (UInt32)(MASK((s)->Symbol)); s++; if ((Int32)count < 0) break; + } + } + s--; + RC_DecodeFinal((hiCnt - count) - s->Freq, s->Freq) + + // new (see->Summ) value can overflow over 16-bits in some rare cases + Ppmd_See_UPDATE(see) + p->FoundState = s; + sym = s->Symbol; + Ppmd7_Update2(p); + return sym; + } + + if (count >= freqSum) + return PPMD7_SYM_ERROR; + + RC_Decode(hiCnt, freqSum - hiCnt) + + // We increase (see->Summ) for sum of Freqs of all non_Masked symbols. + // new (see->Summ) value can overflow over 16-bits in some rare cases + see->Summ = (UInt16)(see->Summ + freqSum); + + s = Ppmd7_GetStats(p, p->MinContext); + s2 = s + p->MinContext->NumStats; + do + { + MASK(s->Symbol) = 0; + s++; + } + while (s != s2); + } +} + +#undef kTop +#undef kBot +#undef READ_BYTE +#undef RC_NORM_BASE +#undef RC_NORM_1 +#undef RC_NORM +#undef RC_NORM_LOCAL +#undef RC_NORM_REMOTE +#undef R +#undef RC_Decode +#undef RC_DecodeFinal +#undef RC_GetThreshold +#undef CTX +#undef SUCCESSOR +#undef MASK diff --git a/src/plugins/7zip/7za/c/Ppmd8.c b/src/plugins/7zip/7za/c/Ppmd8.c index b60d10fe..774b30c2 100644 --- a/src/plugins/7zip/7za/c/Ppmd8.c +++ b/src/plugins/7zip/7za/c/Ppmd8.c @@ -1,5 +1,5 @@ /* Ppmd8.c -- PPMdI codec -2016-05-21 : Igor Pavlov : Public domain +: Igor Pavlov : Public domain This code is based on PPMd var.I (2002): Dmitry Shkarin : Public domain */ #include "Precomp.h" @@ -8,21 +8,23 @@ This code is based on PPMd var.I (2002): Dmitry Shkarin : Public domain */ #include "Ppmd8.h" -const Byte PPMD8_kExpEscape[16] = { 25, 14, 9, 7, 5, 5, 4, 4, 4, 3, 3, 3, 2, 2, 2, 2 }; -static const UInt16 kInitBinEsc[] = { 0x3CDD, 0x1F3F, 0x59BF, 0x48F3, 0x64A1, 0x5ABC, 0x6632, 0x6051}; + + + +MY_ALIGN(16) +static const Byte PPMD8_kExpEscape[16] = { 25, 14, 9, 7, 5, 5, 4, 4, 4, 3, 3, 3, 2, 2, 2, 2 }; +MY_ALIGN(16) +static const UInt16 PPMD8_kInitBinEsc[] = { 0x3CDD, 0x1F3F, 0x59BF, 0x48F3, 0x64A1, 0x5ABC, 0x6632, 0x6051}; #define MAX_FREQ 124 #define UNIT_SIZE 12 #define U2B(nu) ((UInt32)(nu) * UNIT_SIZE) -#define U2I(nu) (p->Units2Indx[(nu) - 1]) -#define I2U(indx) (p->Indx2Units[indx]) +#define U2I(nu) (p->Units2Indx[(size_t)(nu) - 1]) +#define I2U(indx) ((unsigned)p->Indx2Units[indx]) -#ifdef PPMD_32BIT - #define REF(ptr) (ptr) -#else - #define REF(ptr) ((UInt32)((Byte *)(ptr) - (p)->Base)) -#endif + +#define REF(ptr) Ppmd_GetRef(p, ptr) #define STATS_REF(ptr) ((CPpmd_State_Ref)REF(ptr)) @@ -31,38 +33,27 @@ static const UInt16 kInitBinEsc[] = { 0x3CDD, 0x1F3F, 0x59BF, 0x48F3, 0x64A1, 0x #define ONE_STATE(ctx) Ppmd8Context_OneState(ctx) #define SUFFIX(ctx) CTX((ctx)->Suffix) -typedef CPpmd8_Context * CTX_PTR; +typedef CPpmd8_Context * PPMD8_CTX_PTR; struct CPpmd8_Node_; -typedef - #ifdef PPMD_32BIT - struct CPpmd8_Node_ * - #else - UInt32 - #endif - CPpmd8_Node_Ref; +typedef Ppmd_Ref_Type(struct CPpmd8_Node_) CPpmd8_Node_Ref; typedef struct CPpmd8_Node_ { UInt32 Stamp; + CPpmd8_Node_Ref Next; UInt32 NU; } CPpmd8_Node; -#ifdef PPMD_32BIT - #define NODE(ptr) (ptr) -#else - #define NODE(offs) ((CPpmd8_Node *)(p->Base + (offs))) -#endif - -#define EMPTY_NODE 0xFFFFFFFF +#define NODE(r) Ppmd_GetPtr_Type(p, r, CPpmd8_Node) void Ppmd8_Construct(CPpmd8 *p) { unsigned i, k, m; - p->Base = 0; + p->Base = NULL; for (i = 0, k = 0; i < PPMD_NUM_INDEXES; i++) { @@ -78,40 +69,52 @@ void Ppmd8_Construct(CPpmd8 *p) for (i = 0; i < 5; i++) p->NS2Indx[i] = (Byte)i; + for (m = i, k = 1; i < 260; i++) { p->NS2Indx[i] = (Byte)m; if (--k == 0) k = (++m) - 4; } + + memcpy(p->ExpEscape, PPMD8_kExpEscape, 16); } -void Ppmd8_Free(CPpmd8 *p, ISzAlloc *alloc) + +void Ppmd8_Free(CPpmd8 *p, ISzAllocPtr alloc) { - alloc->Free(alloc, p->Base); + ISzAlloc_Free(alloc, p->Base); p->Size = 0; - p->Base = 0; + p->Base = NULL; } -Bool Ppmd8_Alloc(CPpmd8 *p, UInt32 size, ISzAlloc *alloc) + +BoolInt Ppmd8_Alloc(CPpmd8 *p, UInt32 size, ISzAllocPtr alloc) { - if (p->Base == 0 || p->Size != size) + if (!p->Base || p->Size != size) { Ppmd8_Free(p, alloc); - p->AlignOffset = - #ifdef PPMD_32BIT - (4 - size) & 3; - #else - 4 - (size & 3); - #endif - if ((p->Base = (Byte *)alloc->Alloc(alloc, p->AlignOffset + size)) == 0) + p->AlignOffset = (4 - size) & 3; + if ((p->Base = (Byte *)ISzAlloc_Alloc(alloc, p->AlignOffset + size)) == NULL) return False; p->Size = size; } return True; } -static void InsertNode(CPpmd8 *p, void *node, unsigned indx) + + +// ---------- Internal Memory Allocator ---------- + + + + + + +#define EMPTY_NODE 0xFFFFFFFF + + +static void Ppmd8_InsertNode(CPpmd8 *p, void *node, unsigned indx) { ((CPpmd8_Node *)node)->Stamp = EMPTY_NODE; ((CPpmd8_Node *)node)->Next = (CPpmd8_Node_Ref)p->FreeList[indx]; @@ -120,128 +123,198 @@ static void InsertNode(CPpmd8 *p, void *node, unsigned indx) p->Stamps[indx]++; } -static void *RemoveNode(CPpmd8 *p, unsigned indx) + +static void *Ppmd8_RemoveNode(CPpmd8 *p, unsigned indx) { CPpmd8_Node *node = NODE((CPpmd8_Node_Ref)p->FreeList[indx]); p->FreeList[indx] = node->Next; p->Stamps[indx]--; + return node; } -static void SplitBlock(CPpmd8 *p, void *ptr, unsigned oldIndx, unsigned newIndx) + +static void Ppmd8_SplitBlock(CPpmd8 *p, void *ptr, unsigned oldIndx, unsigned newIndx) { unsigned i, nu = I2U(oldIndx) - I2U(newIndx); ptr = (Byte *)ptr + U2B(I2U(newIndx)); if (I2U(i = U2I(nu)) != nu) { unsigned k = I2U(--i); - InsertNode(p, ((Byte *)ptr) + U2B(k), nu - k - 1); + Ppmd8_InsertNode(p, ((Byte *)ptr) + U2B(k), nu - k - 1); } - InsertNode(p, ptr, i); + Ppmd8_InsertNode(p, ptr, i); } -static void GlueFreeBlocks(CPpmd8 *p) + + + + + + + + + + + + + +static void Ppmd8_GlueFreeBlocks(CPpmd8 *p) { - CPpmd8_Node_Ref head = 0; - CPpmd8_Node_Ref *prev = &head; - unsigned i; + /* + we use first UInt32 field of 12-bytes UNITs as record type stamp + CPpmd_State { Byte Symbol; Byte Freq; : Freq != 0xFF + CPpmd8_Context { Byte NumStats; Byte Flags; UInt16 SummFreq; : Flags != 0xFF ??? + CPpmd8_Node { UInt32 Stamp : Stamp == 0xFFFFFFFF for free record + : Stamp == 0 for guard + Last 12-bytes UNIT in array is always contains 12-bytes order-0 CPpmd8_Context record + */ + CPpmd8_Node_Ref n; p->GlueCount = 1 << 13; memset(p->Stamps, 0, sizeof(p->Stamps)); - /* Order-0 context is always at top UNIT, so we don't need guard NODE at the end. - All blocks up to p->LoUnit can be free, so we need guard NODE at LoUnit. */ + /* we set guard NODE at LoUnit */ if (p->LoUnit != p->HiUnit) - ((CPpmd8_Node *)p->LoUnit)->Stamp = 0; + ((CPpmd8_Node *)(void *)p->LoUnit)->Stamp = 0; - /* Glue free blocks */ - for (i = 0; i < PPMD_NUM_INDEXES; i++) { - CPpmd8_Node_Ref next = (CPpmd8_Node_Ref)p->FreeList[i]; - p->FreeList[i] = 0; - while (next != 0) + /* Glue free blocks */ + CPpmd8_Node_Ref *prev = &n; + unsigned i; + for (i = 0; i < PPMD_NUM_INDEXES; i++) { - CPpmd8_Node *node = NODE(next); - if (node->NU != 0) + + CPpmd8_Node_Ref next = (CPpmd8_Node_Ref)p->FreeList[i]; + p->FreeList[i] = 0; + while (next != 0) { - CPpmd8_Node *node2; + CPpmd8_Node *node = NODE(next); + UInt32 nu = node->NU; *prev = next; - prev = &(node->Next); - while ((node2 = node + node->NU)->Stamp == EMPTY_NODE) + next = node->Next; + if (nu != 0) { - node->NU += node2->NU; - node2->NU = 0; + CPpmd8_Node *node2; + prev = &(node->Next); + while ((node2 = node + nu)->Stamp == EMPTY_NODE) + { + nu += node2->NU; + node2->NU = 0; + node->NU = nu; + } } } - next = node->Next; } + + *prev = 0; } - *prev = 0; + + + + + + + + + + + + + + + + + + + /* Fill lists of free blocks */ - while (head != 0) + while (n != 0) { - CPpmd8_Node *node = NODE(head); - unsigned nu; - head = node->Next; - nu = node->NU; + CPpmd8_Node *node = NODE(n); + UInt32 nu = node->NU; + unsigned i; + n = node->Next; if (nu == 0) continue; for (; nu > 128; nu -= 128, node += 128) - InsertNode(p, node, PPMD_NUM_INDEXES - 1); + Ppmd8_InsertNode(p, node, PPMD_NUM_INDEXES - 1); if (I2U(i = U2I(nu)) != nu) { unsigned k = I2U(--i); - InsertNode(p, node + k, nu - k - 1); + Ppmd8_InsertNode(p, node + k, (unsigned)nu - k - 1); } - InsertNode(p, node, i); + Ppmd8_InsertNode(p, node, i); } } -static void *AllocUnitsRare(CPpmd8 *p, unsigned indx) + +Z7_NO_INLINE +static void *Ppmd8_AllocUnitsRare(CPpmd8 *p, unsigned indx) { unsigned i; - void *retVal; + if (p->GlueCount == 0) { - GlueFreeBlocks(p); + Ppmd8_GlueFreeBlocks(p); if (p->FreeList[indx] != 0) - return RemoveNode(p, indx); + return Ppmd8_RemoveNode(p, indx); } + i = indx; + do { if (++i == PPMD_NUM_INDEXES) { UInt32 numBytes = U2B(I2U(indx)); + Byte *us = p->UnitsStart; p->GlueCount--; - return ((UInt32)(p->UnitsStart - p->Text) > numBytes) ? (p->UnitsStart -= numBytes) : (NULL); + return ((UInt32)(us - p->Text) > numBytes) ? (p->UnitsStart = us - numBytes) : (NULL); } } while (p->FreeList[i] == 0); - retVal = RemoveNode(p, i); - SplitBlock(p, retVal, i, indx); - return retVal; + + { + void *block = Ppmd8_RemoveNode(p, i); + Ppmd8_SplitBlock(p, block, i, indx); + return block; + } } -static void *AllocUnits(CPpmd8 *p, unsigned indx) + +static void *Ppmd8_AllocUnits(CPpmd8 *p, unsigned indx) { - UInt32 numBytes; if (p->FreeList[indx] != 0) - return RemoveNode(p, indx); - numBytes = U2B(I2U(indx)); - if (numBytes <= (UInt32)(p->HiUnit - p->LoUnit)) + return Ppmd8_RemoveNode(p, indx); { - void *retVal = p->LoUnit; - p->LoUnit += numBytes; - return retVal; + UInt32 numBytes = U2B(I2U(indx)); + Byte *lo = p->LoUnit; + if ((UInt32)(p->HiUnit - lo) >= numBytes) + { + p->LoUnit = lo + numBytes; + return lo; + } } - return AllocUnitsRare(p, indx); + return Ppmd8_AllocUnitsRare(p, indx); } -#define MyMem12Cpy(dest, src, num) \ - { UInt32 *d = (UInt32 *)dest; const UInt32 *z = (const UInt32 *)src; UInt32 n = num; \ - do { d[0] = z[0]; d[1] = z[1]; d[2] = z[2]; z += 3; d += 3; } while (--n); } + +#define MEM_12_CPY(dest, src, num) \ + { UInt32 *d = (UInt32 *)(dest); \ + const UInt32 *z = (const UInt32 *)(src); \ + unsigned n = (num); \ + do { \ + d[0] = z[0]; \ + d[1] = z[1]; \ + d[2] = z[2]; \ + z += 3; \ + d += 3; \ + } while (--n); \ + } + + static void *ShrinkUnits(CPpmd8 *p, void *oldPtr, unsigned oldNU, unsigned newNU) { @@ -251,102 +324,118 @@ static void *ShrinkUnits(CPpmd8 *p, void *oldPtr, unsigned oldNU, unsigned newNU return oldPtr; if (p->FreeList[i1] != 0) { - void *ptr = RemoveNode(p, i1); - MyMem12Cpy(ptr, oldPtr, newNU); - InsertNode(p, oldPtr, i0); + void *ptr = Ppmd8_RemoveNode(p, i1); + MEM_12_CPY(ptr, oldPtr, newNU) + Ppmd8_InsertNode(p, oldPtr, i0); return ptr; } - SplitBlock(p, oldPtr, i0, i1); + Ppmd8_SplitBlock(p, oldPtr, i0, i1); return oldPtr; } + static void FreeUnits(CPpmd8 *p, void *ptr, unsigned nu) { - InsertNode(p, ptr, U2I(nu)); + Ppmd8_InsertNode(p, ptr, U2I(nu)); } + static void SpecialFreeUnit(CPpmd8 *p, void *ptr) { if ((Byte *)ptr != p->UnitsStart) - InsertNode(p, ptr, 0); + Ppmd8_InsertNode(p, ptr, 0); else { #ifdef PPMD8_FREEZE_SUPPORT - *(UInt32 *)ptr = EMPTY_NODE; /* it's used for (Flags == 0xFF) check in RemoveBinContexts */ + *(UInt32 *)ptr = EMPTY_NODE; /* it's used for (Flags == 0xFF) check in RemoveBinContexts() */ #endif p->UnitsStart += UNIT_SIZE; } } + +/* static void *MoveUnitsUp(CPpmd8 *p, void *oldPtr, unsigned nu) { unsigned indx = U2I(nu); void *ptr; - if ((Byte *)oldPtr > p->UnitsStart + 16 * 1024 || REF(oldPtr) > p->FreeList[indx]) + if ((Byte *)oldPtr > p->UnitsStart + (1 << 14) || REF(oldPtr) > p->FreeList[indx]) return oldPtr; - ptr = RemoveNode(p, indx); - MyMem12Cpy(ptr, oldPtr, nu); - if ((Byte*)oldPtr != p->UnitsStart) - InsertNode(p, oldPtr, indx); + ptr = Ppmd8_RemoveNode(p, indx); + MEM_12_CPY(ptr, oldPtr, nu) + if ((Byte *)oldPtr != p->UnitsStart) + Ppmd8_InsertNode(p, oldPtr, indx); else p->UnitsStart += U2B(I2U(indx)); return ptr; } +*/ static void ExpandTextArea(CPpmd8 *p) { UInt32 count[PPMD_NUM_INDEXES]; unsigned i; + memset(count, 0, sizeof(count)); if (p->LoUnit != p->HiUnit) - ((CPpmd8_Node *)p->LoUnit)->Stamp = 0; + ((CPpmd8_Node *)(void *)p->LoUnit)->Stamp = 0; { - CPpmd8_Node *node = (CPpmd8_Node *)p->UnitsStart; - for (; node->Stamp == EMPTY_NODE; node += node->NU) + CPpmd8_Node *node = (CPpmd8_Node *)(void *)p->UnitsStart; + while (node->Stamp == EMPTY_NODE) { + UInt32 nu = node->NU; node->Stamp = 0; - count[U2I(node->NU)]++; + count[U2I(nu)]++; + node += nu; } p->UnitsStart = (Byte *)node; } for (i = 0; i < PPMD_NUM_INDEXES; i++) { - CPpmd8_Node_Ref *next = (CPpmd8_Node_Ref *)&p->FreeList[i]; - while (count[i] != 0) + UInt32 cnt = count[i]; + if (cnt == 0) + continue; { - CPpmd8_Node *node = NODE(*next); - while (node->Stamp == 0) + CPpmd8_Node_Ref *prev = (CPpmd8_Node_Ref *)&p->FreeList[i]; + CPpmd8_Node_Ref n = *prev; + p->Stamps[i] -= cnt; + for (;;) { - *next = node->Next; - node = NODE(*next); - p->Stamps[i]--; - if (--count[i] == 0) + CPpmd8_Node *node = NODE(n); + n = node->Next; + if (node->Stamp != 0) + { + prev = &node->Next; + continue; + } + *prev = n; + if (--cnt == 0) break; } - next = &node->Next; } } } -#define SUCCESSOR(p) ((CPpmd_Void_Ref)((p)->SuccessorLow | ((UInt32)(p)->SuccessorHigh << 16))) -static void SetSuccessor(CPpmd_State *p, CPpmd_Void_Ref v) +#define SUCCESSOR(p) Ppmd_GET_SUCCESSOR(p) +static void Ppmd8State_SetSuccessor(CPpmd_State *p, CPpmd_Void_Ref v) { - (p)->SuccessorLow = (UInt16)((UInt32)(v) & 0xFFFF); - (p)->SuccessorHigh = (UInt16)(((UInt32)(v) >> 16) & 0xFFFF); + Ppmd_SET_SUCCESSOR(p, v) } #define RESET_TEXT(offs) { p->Text = p->Base + p->AlignOffset + (offs); } -static void RestartModel(CPpmd8 *p) +Z7_NO_INLINE +static +void Ppmd8_RestartModel(CPpmd8 *p) { - unsigned i, k, m, r; + unsigned i, k, m; memset(p->FreeList, 0, sizeof(p->FreeList)); memset(p->Stamps, 0, sizeof(p->Stamps)); - RESET_TEXT(0); + RESET_TEXT(0) p->HiUnit = p->Text + p->Size; p->LoUnit = p->UnitsStart = p->HiUnit - p->Size / 8 / UNIT_SIZE * 7 * UNIT_SIZE; p->GlueCount = 0; @@ -355,30 +444,47 @@ static void RestartModel(CPpmd8 *p) p->RunLength = p->InitRL = -(Int32)((p->MaxOrder < 12) ? p->MaxOrder : 12) - 1; p->PrevSuccess = 0; - p->MinContext = p->MaxContext = (CTX_PTR)(p->HiUnit -= UNIT_SIZE); /* AllocContext(p); */ - p->MinContext->Suffix = 0; - p->MinContext->NumStats = 255; - p->MinContext->Flags = 0; - p->MinContext->SummFreq = 256 + 1; - p->FoundState = (CPpmd_State *)p->LoUnit; /* AllocUnits(p, PPMD_NUM_INDEXES - 1); */ - p->LoUnit += U2B(256 / 2); - p->MinContext->Stats = REF(p->FoundState); - for (i = 0; i < 256; i++) { - CPpmd_State *s = &p->FoundState[i]; - s->Symbol = (Byte)i; - s->Freq = 1; - SetSuccessor(s, 0); + CPpmd8_Context *mc = (PPMD8_CTX_PTR)(void *)(p->HiUnit -= UNIT_SIZE); /* AllocContext(p); */ + CPpmd_State *s = (CPpmd_State *)p->LoUnit; /* Ppmd8_AllocUnits(p, PPMD_NUM_INDEXES - 1); */ + + p->LoUnit += U2B(256 / 2); + p->MaxContext = p->MinContext = mc; + p->FoundState = s; + mc->Flags = 0; + mc->NumStats = 256 - 1; + mc->Union2.SummFreq = 256 + 1; + mc->Union4.Stats = REF(s); + mc->Suffix = 0; + + for (i = 0; i < 256; i++, s++) + { + s->Symbol = (Byte)i; + s->Freq = 1; + Ppmd8State_SetSuccessor(s, 0); + } } + + + + + + + + + + + for (i = m = 0; m < 25; m++) { while (p->NS2Indx[i] == m) i++; for (k = 0; k < 8; k++) { - UInt16 val = (UInt16)(PPMD_BIN_SCALE - kInitBinEsc[k] / (i + 1)); + unsigned r; UInt16 *dest = p->BinSumm[m] + k; + const UInt16 val = (UInt16)(PPMD_BIN_SCALE - PPMD8_kInitBinEsc[k] / (i + 1)); for (r = 0; r < 64; r += 8) dest[r] = val; } @@ -386,149 +492,274 @@ static void RestartModel(CPpmd8 *p) for (i = m = 0; m < 24; m++) { - while (p->NS2Indx[i + 3] == m + 3) + unsigned summ; + CPpmd_See *s; + while (p->NS2Indx[(size_t)i + 3] == m + 3) i++; - for (k = 0; k < 32; k++) + s = p->See[m]; + summ = ((2 * i + 5) << (PPMD_PERIOD_BITS - 4)); + for (k = 0; k < 32; k++, s++) { - CPpmd_See *s = &p->See[m][k]; - s->Summ = (UInt16)((2 * i + 5) << (s->Shift = PPMD_PERIOD_BITS - 4)); + s->Summ = (UInt16)summ; + s->Shift = (PPMD_PERIOD_BITS - 4); s->Count = 7; } } + + p->DummySee.Summ = 0; /* unused */ + p->DummySee.Shift = PPMD_PERIOD_BITS; + p->DummySee.Count = 64; /* unused */ } + void Ppmd8_Init(CPpmd8 *p, unsigned maxOrder, unsigned restoreMethod) { p->MaxOrder = maxOrder; p->RestoreMethod = restoreMethod; - RestartModel(p); - p->DummySee.Shift = PPMD_PERIOD_BITS; - p->DummySee.Summ = 0; /* unused */ - p->DummySee.Count = 64; /* unused */ + Ppmd8_RestartModel(p); } -static void Refresh(CPpmd8 *p, CTX_PTR ctx, unsigned oldNU, unsigned scale) + +#define FLAG_RESCALED (1 << 2) +// #define FLAG_SYM_HIGH (1 << 3) +#define FLAG_PREV_HIGH (1 << 4) + +#define HiBits_Prepare(sym) ((unsigned)(sym) + 0xC0) + +#define HiBits_Convert_3(flags) (((flags) >> (8 - 3)) & (1 << 3)) +#define HiBits_Convert_4(flags) (((flags) >> (8 - 4)) & (1 << 4)) + +#define PPMD8_HiBitsFlag_3(sym) HiBits_Convert_3(HiBits_Prepare(sym)) +#define PPMD8_HiBitsFlag_4(sym) HiBits_Convert_4(HiBits_Prepare(sym)) + +// #define PPMD8_HiBitsFlag_3(sym) (0x08 * ((sym) >= 0x40)) +// #define PPMD8_HiBitsFlag_4(sym) (0x10 * ((sym) >= 0x40)) + +/* +Refresh() is called when we remove some symbols (successors) in context. +It increases Escape_Freq for sum of all removed symbols. +*/ + +static void Refresh(CPpmd8 *p, PPMD8_CTX_PTR ctx, unsigned oldNU, unsigned scale) { unsigned i = ctx->NumStats, escFreq, sumFreq, flags; CPpmd_State *s = (CPpmd_State *)ShrinkUnits(p, STATS(ctx), oldNU, (i + 2) >> 1); - ctx->Stats = REF(s); - #ifdef PPMD8_FREEZE_SUPPORT - /* fixed over Shkarin's code. Fixed code is not compatible with original code for some files in FREEZE mode. */ - scale |= (ctx->SummFreq >= ((UInt32)1 << 15)); - #endif - flags = (ctx->Flags & (0x10 + 0x04 * scale)) + 0x08 * (s->Symbol >= 0x40); - escFreq = ctx->SummFreq - s->Freq; - sumFreq = (s->Freq = (Byte)((s->Freq + scale) >> scale)); + ctx->Union4.Stats = REF(s); + + // #ifdef PPMD8_FREEZE_SUPPORT + /* + (ctx->Union2.SummFreq >= ((UInt32)1 << 15)) can be in FREEZE mode for some files. + It's not good for range coder. So new versions of support fix: + - original PPMdI code rev.1 + + original PPMdI code rev.2 + - 7-Zip default ((PPMD8_FREEZE_SUPPORT is not defined) + + 7-Zip (p->RestoreMethod >= PPMD8_RESTORE_METHOD_FREEZE) + if we use that fixed line, we can lose compatibility with some files created before fix + if we don't use that fixed line, the program can work incorrectly in FREEZE mode in rare case. + */ + // if (p->RestoreMethod >= PPMD8_RESTORE_METHOD_FREEZE) + { + scale |= (ctx->Union2.SummFreq >= ((UInt32)1 << 15)); + } + // #endif + + + + flags = HiBits_Prepare(s->Symbol); + { + unsigned freq = s->Freq; + escFreq = ctx->Union2.SummFreq - freq; + freq = (freq + scale) >> scale; + sumFreq = freq; + s->Freq = (Byte)freq; + } + do { - escFreq -= (++s)->Freq; - sumFreq += (s->Freq = (Byte)((s->Freq + scale) >> scale)); - flags |= 0x08 * (s->Symbol >= 0x40); + unsigned freq = (++s)->Freq; + escFreq -= freq; + freq = (freq + scale) >> scale; + sumFreq += freq; + s->Freq = (Byte)freq; + flags |= HiBits_Prepare(s->Symbol); } while (--i); - ctx->SummFreq = (UInt16)(sumFreq + ((escFreq + scale) >> scale)); - ctx->Flags = (Byte)flags; + + ctx->Union2.SummFreq = (UInt16)(sumFreq + ((escFreq + scale) >> scale)); + ctx->Flags = (Byte)((ctx->Flags & (FLAG_PREV_HIGH + FLAG_RESCALED * scale)) + HiBits_Convert_3(flags)); } -static void SwapStates(CPpmd_State *t1, CPpmd_State *t2) + +static void SWAP_STATES(CPpmd_State *t1, CPpmd_State *t2) { CPpmd_State tmp = *t1; *t1 = *t2; *t2 = tmp; } -static CPpmd_Void_Ref CutOff(CPpmd8 *p, CTX_PTR ctx, unsigned order) + +/* +CutOff() reduces contexts: + It conversts Successors at MaxOrder to another Contexts to NULL-Successors + It removes RAW-Successors and NULL-Successors that are not Order-0 + and it removes contexts when it has no Successors. + if the (Union4.Stats) is close to (UnitsStart), it moves it up. +*/ + +static CPpmd_Void_Ref CutOff(CPpmd8 *p, PPMD8_CTX_PTR ctx, unsigned order) { - int i; - unsigned tmp; - CPpmd_State *s; + int ns = ctx->NumStats; + unsigned nu; + CPpmd_State *stats; - if (!ctx->NumStats) + if (ns == 0) { - s = ONE_STATE(ctx); - if ((Byte *)Ppmd8_GetPtr(p, SUCCESSOR(s)) >= p->UnitsStart) + CPpmd_State *s = ONE_STATE(ctx); + CPpmd_Void_Ref successor = SUCCESSOR(s); + if ((Byte *)Ppmd8_GetPtr(p, successor) >= p->UnitsStart) { if (order < p->MaxOrder) - SetSuccessor(s, CutOff(p, CTX(SUCCESSOR(s)), order + 1)); + successor = CutOff(p, CTX(successor), order + 1); else - SetSuccessor(s, 0); - if (SUCCESSOR(s) || order <= 9) /* O_BOUND */ + successor = 0; + Ppmd8State_SetSuccessor(s, successor); + if (successor || order <= 9) /* O_BOUND */ return REF(ctx); } SpecialFreeUnit(p, ctx); return 0; } - ctx->Stats = STATS_REF(MoveUnitsUp(p, STATS(ctx), tmp = ((unsigned)ctx->NumStats + 2) >> 1)); + nu = ((unsigned)ns + 2) >> 1; + // ctx->Union4.Stats = STATS_REF(MoveUnitsUp(p, STATS(ctx), nu)); + { + unsigned indx = U2I(nu); + stats = STATS(ctx); - for (s = STATS(ctx) + (i = ctx->NumStats); s >= STATS(ctx); s--) - if ((Byte *)Ppmd8_GetPtr(p, SUCCESSOR(s)) < p->UnitsStart) + if ((UInt32)((Byte *)stats - p->UnitsStart) <= (1 << 14) + && (CPpmd_Void_Ref)ctx->Union4.Stats <= p->FreeList[indx]) { - CPpmd_State *s2 = STATS(ctx) + (i--); - SetSuccessor(s, 0); - SwapStates(s, s2); + void *ptr = Ppmd8_RemoveNode(p, indx); + ctx->Union4.Stats = STATS_REF(ptr); + MEM_12_CPY(ptr, (const void *)stats, nu) + if ((Byte *)stats != p->UnitsStart) + Ppmd8_InsertNode(p, stats, indx); + else + p->UnitsStart += U2B(I2U(indx)); + stats = (CPpmd_State *)ptr; } - else if (order < p->MaxOrder) - SetSuccessor(s, CutOff(p, CTX(SUCCESSOR(s)), order + 1)); - else - SetSuccessor(s, 0); - - if (i != ctx->NumStats && order) + } + { - ctx->NumStats = (Byte)i; - s = STATS(ctx); - if (i < 0) + CPpmd_State *s = stats + (unsigned)ns; + do { - FreeUnits(p, s, tmp); + CPpmd_Void_Ref successor = SUCCESSOR(s); + if ((Byte *)Ppmd8_GetPtr(p, successor) < p->UnitsStart) + { + CPpmd_State *s2 = stats + (unsigned)(ns--); + if (order) + { + if (s != s2) + *s = *s2; + } + else + { + SWAP_STATES(s, s2); + Ppmd8State_SetSuccessor(s2, 0); + } + } + else + { + if (order < p->MaxOrder) + Ppmd8State_SetSuccessor(s, CutOff(p, CTX(successor), order + 1)); + else + Ppmd8State_SetSuccessor(s, 0); + } + } + while (--s >= stats); + } + + if (ns != ctx->NumStats && order) + { + if (ns < 0) + { + FreeUnits(p, stats, nu); SpecialFreeUnit(p, ctx); return 0; } - if (i == 0) + ctx->NumStats = (Byte)ns; + if (ns == 0) { - ctx->Flags = (Byte)((ctx->Flags & 0x10) + 0x08 * (s->Symbol >= 0x40)); - *ONE_STATE(ctx) = *s; - FreeUnits(p, s, tmp); - /* 9.31: the code was fixed. It's was not BUG, if Freq <= MAX_FREQ = 124 */ - ONE_STATE(ctx)->Freq = (Byte)(((unsigned)ONE_STATE(ctx)->Freq + 11) >> 3); + const Byte sym = stats->Symbol; + ctx->Flags = (Byte)((ctx->Flags & FLAG_PREV_HIGH) + PPMD8_HiBitsFlag_3(sym)); + // *ONE_STATE(ctx) = *stats; + ctx->Union2.State2.Symbol = sym; + ctx->Union2.State2.Freq = (Byte)(((unsigned)stats->Freq + 11) >> 3); + ctx->Union4.State4.Successor_0 = stats->Successor_0; + ctx->Union4.State4.Successor_1 = stats->Successor_1; + FreeUnits(p, stats, nu); } else - Refresh(p, ctx, tmp, ctx->SummFreq > 16 * i); + { + Refresh(p, ctx, nu, ctx->Union2.SummFreq > 16 * (unsigned)ns); + } } + return REF(ctx); } + + #ifdef PPMD8_FREEZE_SUPPORT -static CPpmd_Void_Ref RemoveBinContexts(CPpmd8 *p, CTX_PTR ctx, unsigned order) + +/* +RemoveBinContexts() + It conversts Successors at MaxOrder to another Contexts to NULL-Successors + It changes RAW-Successors to NULL-Successors + removes Bin Context without Successor, if suffix of that context is also binary. +*/ + +static CPpmd_Void_Ref RemoveBinContexts(CPpmd8 *p, PPMD8_CTX_PTR ctx, unsigned order) { - CPpmd_State *s; if (!ctx->NumStats) { - s = ONE_STATE(ctx); - if ((Byte *)Ppmd8_GetPtr(p, SUCCESSOR(s)) >= p->UnitsStart && order < p->MaxOrder) - SetSuccessor(s, RemoveBinContexts(p, CTX(SUCCESSOR(s)), order + 1)); + CPpmd_State *s = ONE_STATE(ctx); + CPpmd_Void_Ref successor = SUCCESSOR(s); + if ((Byte *)Ppmd8_GetPtr(p, successor) >= p->UnitsStart && order < p->MaxOrder) + successor = RemoveBinContexts(p, CTX(successor), order + 1); else - SetSuccessor(s, 0); + successor = 0; + Ppmd8State_SetSuccessor(s, successor); /* Suffix context can be removed already, since different (high-order) Successors may refer to same context. So we check Flags == 0xFF (Stamp == EMPTY_NODE) */ - if (!SUCCESSOR(s) && (!SUFFIX(ctx)->NumStats || SUFFIX(ctx)->Flags == 0xFF)) + if (!successor && (!SUFFIX(ctx)->NumStats || SUFFIX(ctx)->Flags == 0xFF)) { FreeUnits(p, ctx, 1); return 0; } - else - return REF(ctx); } - - for (s = STATS(ctx) + ctx->NumStats; s >= STATS(ctx); s--) - if ((Byte *)Ppmd8_GetPtr(p, SUCCESSOR(s)) >= p->UnitsStart && order < p->MaxOrder) - SetSuccessor(s, RemoveBinContexts(p, CTX(SUCCESSOR(s)), order + 1)); - else - SetSuccessor(s, 0); + else + { + CPpmd_State *s = STATS(ctx) + ctx->NumStats; + do + { + CPpmd_Void_Ref successor = SUCCESSOR(s); + if ((Byte *)Ppmd8_GetPtr(p, successor) >= p->UnitsStart && order < p->MaxOrder) + Ppmd8State_SetSuccessor(s, RemoveBinContexts(p, CTX(successor), order + 1)); + else + Ppmd8State_SetSuccessor(s, 0); + } + while (--s >= STATS(ctx)); + } return REF(ctx); } + #endif + + static UInt32 GetUsedMemory(const CPpmd8 *p) { UInt32 v = 0; @@ -544,52 +775,72 @@ static UInt32 GetUsedMemory(const CPpmd8 *p) #define RESTORE_MODEL(c1, fSuccessor) RestoreModel(p, c1) #endif -static void RestoreModel(CPpmd8 *p, CTX_PTR c1 + +static void RestoreModel(CPpmd8 *p, PPMD8_CTX_PTR ctxError #ifdef PPMD8_FREEZE_SUPPORT - , CTX_PTR fSuccessor + , PPMD8_CTX_PTR fSuccessor #endif ) { - CTX_PTR c; + PPMD8_CTX_PTR c; CPpmd_State *s; - RESET_TEXT(0); - for (c = p->MaxContext; c != c1; c = SUFFIX(c)) + RESET_TEXT(0) + + // we go here in cases of error of allocation for context (c1) + // Order(MinContext) < Order(ctxError) <= Order(MaxContext) + + // We remove last symbol from each of contexts [p->MaxContext ... ctxError) contexts + // So we rollback all created (symbols) before error. + for (c = p->MaxContext; c != ctxError; c = SUFFIX(c)) if (--(c->NumStats) == 0) { s = STATS(c); - c->Flags = (Byte)((c->Flags & 0x10) + 0x08 * (s->Symbol >= 0x40)); - *ONE_STATE(c) = *s; + c->Flags = (Byte)((c->Flags & FLAG_PREV_HIGH) + PPMD8_HiBitsFlag_3(s->Symbol)); + // *ONE_STATE(c) = *s; + c->Union2.State2.Symbol = s->Symbol; + c->Union2.State2.Freq = (Byte)(((unsigned)s->Freq + 11) >> 3); + c->Union4.State4.Successor_0 = s->Successor_0; + c->Union4.State4.Successor_1 = s->Successor_1; + SpecialFreeUnit(p, s); - ONE_STATE(c)->Freq = (Byte)(((unsigned)ONE_STATE(c)->Freq + 11) >> 3); } else - Refresh(p, c, (c->NumStats+3) >> 1, 0); + { + /* Refresh() can increase Escape_Freq on value of Freq of last symbol, that was added before error. + so the largest possible increase for Escape_Freq is (8) from value before ModelUpoadet() */ + Refresh(p, c, ((unsigned)c->NumStats + 3) >> 1, 0); + } + // increase Escape Freq for context [ctxError ... p->MinContext) for (; c != p->MinContext; c = SUFFIX(c)) - if (!c->NumStats) - ONE_STATE(c)->Freq = (Byte)(ONE_STATE(c)->Freq - (ONE_STATE(c)->Freq >> 1)); - else if ((c->SummFreq += 4) > 128 + 4 * c->NumStats) - Refresh(p, c, (c->NumStats + 2) >> 1, 1); + if (c->NumStats == 0) + { + // ONE_STATE(c) + c->Union2.State2.Freq = (Byte)(((unsigned)c->Union2.State2.Freq + 1) >> 1); + } + else if ((c->Union2.SummFreq = (UInt16)(c->Union2.SummFreq + 4)) > 128 + 4 * c->NumStats) + Refresh(p, c, ((unsigned)c->NumStats + 2) >> 1, 1); #ifdef PPMD8_FREEZE_SUPPORT if (p->RestoreMethod > PPMD8_RESTORE_METHOD_FREEZE) { p->MaxContext = fSuccessor; - p->GlueCount += !(p->Stamps[1] & 1); + p->GlueCount += !(p->Stamps[1] & 1); // why? } else if (p->RestoreMethod == PPMD8_RESTORE_METHOD_FREEZE) { while (p->MaxContext->Suffix) p->MaxContext = SUFFIX(p->MaxContext); RemoveBinContexts(p, p->MaxContext, 0); - p->RestoreMethod++; + // we change the current mode to (PPMD8_RESTORE_METHOD_FREEZE + 1) + p->RestoreMethod = PPMD8_RESTORE_METHOD_FREEZE + 1; p->GlueCount = 0; p->OrderFall = p->MaxOrder; } else #endif if (p->RestoreMethod == PPMD8_RESTORE_METHOD_RESTART || GetUsedMemory(p) < (p->Size >> 1)) - RestartModel(p); + Ppmd8_RestartModel(p); else { while (p->MaxContext->Suffix) @@ -603,16 +854,19 @@ static void RestoreModel(CPpmd8 *p, CTX_PTR c1 p->GlueCount = 0; p->OrderFall = p->MaxOrder; } + p->MinContext = p->MaxContext; } -static CTX_PTR CreateSuccessors(CPpmd8 *p, Bool skip, CPpmd_State *s1, CTX_PTR c) + + +Z7_NO_INLINE +static PPMD8_CTX_PTR Ppmd8_CreateSuccessors(CPpmd8 *p, BoolInt skip, CPpmd_State *s1, PPMD8_CTX_PTR c) { - CPpmd_State upState; - Byte flags; + CPpmd_Byte_Ref upBranch = (CPpmd_Byte_Ref)SUCCESSOR(p->FoundState); - /* fixed over Shkarin's code. Maybe it could work without + 1 too. */ - CPpmd_State *ps[PPMD8_MAX_ORDER + 1]; + Byte newSym, newFreq, flags; unsigned numPs = 0; + CPpmd_State *ps[PPMD8_MAX_ORDER + 1]; /* fixed over Shkarin's code. Maybe it could work without + 1 too. */ if (!skip) ps[numPs++] = p->FoundState; @@ -622,19 +876,13 @@ static CTX_PTR CreateSuccessors(CPpmd8 *p, Bool skip, CPpmd_State *s1, CTX_PTR c CPpmd_Void_Ref successor; CPpmd_State *s; c = SUFFIX(c); - if (s1) - { - s = s1; - s1 = NULL; - } + + if (s1) { s = s1; s1 = NULL; } else if (c->NumStats != 0) { - for (s = STATS(c); s->Symbol != p->FoundState->Symbol; s++); - if (s->Freq < MAX_FREQ - 9) - { - s->Freq++; - c->SummFreq++; - } + Byte sym = p->FoundState->Symbol; + for (s = STATS(c); s->Symbol != sym; s++); + if (s->Freq < MAX_FREQ - 9) { s->Freq++; c->Union2.SummFreq++; } } else { @@ -644,49 +892,69 @@ static CTX_PTR CreateSuccessors(CPpmd8 *p, Bool skip, CPpmd_State *s1, CTX_PTR c successor = SUCCESSOR(s); if (successor != upBranch) { + c = CTX(successor); if (numPs == 0) + { + + return c; + } break; } ps[numPs++] = s; } - upState.Symbol = *(const Byte *)Ppmd8_GetPtr(p, upBranch); - SetSuccessor(&upState, upBranch + 1); - flags = (Byte)(0x10 * (p->FoundState->Symbol >= 0x40) + 0x08 * (upState.Symbol >= 0x40)); - + + + + + newSym = *(const Byte *)Ppmd8_GetPtr(p, upBranch); + upBranch++; + flags = (Byte)(PPMD8_HiBitsFlag_4(p->FoundState->Symbol) + PPMD8_HiBitsFlag_3(newSym)); + if (c->NumStats == 0) - upState.Freq = ONE_STATE(c)->Freq; + newFreq = c->Union2.State2.Freq; else { UInt32 cf, s0; CPpmd_State *s; - for (s = STATS(c); s->Symbol != upState.Symbol; s++); - cf = s->Freq - 1; - s0 = c->SummFreq - c->NumStats - cf; - upState.Freq = (Byte)(1 + ((2 * cf <= s0) ? (5 * cf > s0) : ((cf + 2 * s0 - 3) / s0))); + for (s = STATS(c); s->Symbol != newSym; s++); + cf = (UInt32)s->Freq - 1; + s0 = (UInt32)c->Union2.SummFreq - c->NumStats - cf; + /* + + + max(newFreq)= (s->Freq - 1), when (s0 == 1) + + + */ + newFreq = (Byte)(1 + ((2 * cf <= s0) ? (5 * cf > s0) : ((cf + 2 * s0 - 3) / s0))); } + + do { - /* Create Child */ - CTX_PTR c1; /* = AllocContext(p); */ + PPMD8_CTX_PTR c1; + /* = AllocContext(p); */ if (p->HiUnit != p->LoUnit) - c1 = (CTX_PTR)(p->HiUnit -= UNIT_SIZE); + c1 = (PPMD8_CTX_PTR)(void *)(p->HiUnit -= UNIT_SIZE); else if (p->FreeList[0] != 0) - c1 = (CTX_PTR)RemoveNode(p, 0); + c1 = (PPMD8_CTX_PTR)Ppmd8_RemoveNode(p, 0); else { - c1 = (CTX_PTR)AllocUnitsRare(p, 0); + c1 = (PPMD8_CTX_PTR)Ppmd8_AllocUnitsRare(p, 0); if (!c1) return NULL; } - c1->NumStats = 0; c1->Flags = flags; - *ONE_STATE(c1) = upState; + c1->NumStats = 0; + c1->Union2.State2.Symbol = newSym; + c1->Union2.State2.Freq = newFreq; + Ppmd8State_SetSuccessor(ONE_STATE(c1), upBranch); c1->Suffix = REF(c); - SetSuccessor(ps[--numPs], REF(c1)); + Ppmd8State_SetSuccessor(ps[--numPs], REF(c1)); c = c1; } while (numPs != 0); @@ -694,10 +962,11 @@ static CTX_PTR CreateSuccessors(CPpmd8 *p, Bool skip, CPpmd_State *s1, CTX_PTR c return c; } -static CTX_PTR ReduceOrder(CPpmd8 *p, CPpmd_State *s1, CTX_PTR c) + +static PPMD8_CTX_PTR ReduceOrder(CPpmd8 *p, CPpmd_State *s1, PPMD8_CTX_PTR c) { CPpmd_State *s = NULL; - CTX_PTR c1 = c; + PPMD8_CTX_PTR c1 = c; CPpmd_Void_Ref upBranch = REF(p->Text); #ifdef PPMD8_FREEZE_SUPPORT @@ -707,7 +976,7 @@ static CTX_PTR ReduceOrder(CPpmd8 *p, CPpmd_State *s1, CTX_PTR c) ps[numPs++] = p->FoundState; #endif - SetSuccessor(p->FoundState, upBranch); + Ppmd8State_SetSuccessor(p->FoundState, upBranch); p->OrderFall++; for (;;) @@ -725,8 +994,8 @@ static CTX_PTR ReduceOrder(CPpmd8 *p, CPpmd_State *s1, CTX_PTR c) #ifdef PPMD8_FREEZE_SUPPORT if (p->RestoreMethod > PPMD8_RESTORE_METHOD_FREEZE) { - do { SetSuccessor(ps[--numPs], REF(c)); } while (numPs); - RESET_TEXT(1); + do { Ppmd8State_SetSuccessor(ps[--numPs], REF(c)); } while (numPs); + RESET_TEXT(1) p->OrderFall = 1; } #endif @@ -739,8 +1008,8 @@ static CTX_PTR ReduceOrder(CPpmd8 *p, CPpmd_State *s1, CTX_PTR c) do { s++; } while (s->Symbol != p->FoundState->Symbol); if (s->Freq < MAX_FREQ - 9) { - s->Freq += 2; - c->SummFreq += 2; + s->Freq = (Byte)(s->Freq + 2); + c->Union2.SummFreq = (UInt16)(c->Union2.SummFreq + 2); } } else @@ -754,7 +1023,7 @@ static CTX_PTR ReduceOrder(CPpmd8 *p, CPpmd_State *s1, CTX_PTR c) #ifdef PPMD8_FREEZE_SUPPORT ps[numPs++] = s; #endif - SetSuccessor(s, upBranch); + Ppmd8State_SetSuccessor(s, upBranch); p->OrderFall++; } @@ -762,8 +1031,8 @@ static CTX_PTR ReduceOrder(CPpmd8 *p, CPpmd_State *s1, CTX_PTR c) if (p->RestoreMethod > PPMD8_RESTORE_METHOD_FREEZE) { c = CTX(SUCCESSOR(s)); - do { SetSuccessor(ps[--numPs], REF(c)); } while (numPs); - RESET_TEXT(1); + do { Ppmd8State_SetSuccessor(ps[--numPs], REF(c)); } while (numPs); + RESET_TEXT(1) p->OrderFall = 1; return c; } @@ -771,38 +1040,47 @@ static CTX_PTR ReduceOrder(CPpmd8 *p, CPpmd_State *s1, CTX_PTR c) #endif if (SUCCESSOR(s) <= upBranch) { - CTX_PTR successor; + PPMD8_CTX_PTR successor; CPpmd_State *s2 = p->FoundState; p->FoundState = s; - successor = CreateSuccessors(p, False, NULL, c); - if (successor == NULL) - SetSuccessor(s, 0); + successor = Ppmd8_CreateSuccessors(p, False, NULL, c); + if (!successor) + Ppmd8State_SetSuccessor(s, 0); else - SetSuccessor(s, REF(successor)); + Ppmd8State_SetSuccessor(s, REF(successor)); p->FoundState = s2; } - if (p->OrderFall == 1 && c1 == p->MaxContext) { - SetSuccessor(p->FoundState, SUCCESSOR(s)); - p->Text--; + CPpmd_Void_Ref successor = SUCCESSOR(s); + if (p->OrderFall == 1 && c1 == p->MaxContext) + { + Ppmd8State_SetSuccessor(p->FoundState, successor); + p->Text--; + } + if (successor == 0) + return NULL; + return CTX(successor); } - if (SUCCESSOR(s) == 0) - return NULL; - return CTX(SUCCESSOR(s)); } -static void UpdateModel(CPpmd8 *p) + + +void Ppmd8_UpdateModel(CPpmd8 *p); +Z7_NO_INLINE +void Ppmd8_UpdateModel(CPpmd8 *p) { - CPpmd_Void_Ref successor, fSuccessor = SUCCESSOR(p->FoundState); - CTX_PTR c; + CPpmd_Void_Ref maxSuccessor, minSuccessor = SUCCESSOR(p->FoundState); + PPMD8_CTX_PTR c; unsigned s0, ns, fFreq = p->FoundState->Freq; Byte flag, fSymbol = p->FoundState->Symbol; + { CPpmd_State *s = NULL; - if (p->FoundState->Freq < MAX_FREQ / 4 && p->MinContext->Suffix != 0) { + /* Update Freqs in Suffix Context */ + c = SUFFIX(p->MinContext); if (c->NumStats == 0) @@ -813,189 +1091,274 @@ static void UpdateModel(CPpmd8 *p) } else { + Byte sym = p->FoundState->Symbol; s = STATS(c); - if (s->Symbol != p->FoundState->Symbol) + + if (s->Symbol != sym) { - do { s++; } while (s->Symbol != p->FoundState->Symbol); + do + { + + s++; + } + while (s->Symbol != sym); + if (s[0].Freq >= s[-1].Freq) { - SwapStates(&s[0], &s[-1]); + SWAP_STATES(&s[0], &s[-1]); s--; } } + if (s->Freq < MAX_FREQ - 9) { - s->Freq += 2; - c->SummFreq += 2; + s->Freq = (Byte)(s->Freq + 2); + c->Union2.SummFreq = (UInt16)(c->Union2.SummFreq + 2); } } } c = p->MaxContext; - if (p->OrderFall == 0 && fSuccessor) + if (p->OrderFall == 0 && minSuccessor) { - CTX_PTR cs = CreateSuccessors(p, True, s, p->MinContext); - if (cs == 0) + PPMD8_CTX_PTR cs = Ppmd8_CreateSuccessors(p, True, s, p->MinContext); + if (!cs) { - SetSuccessor(p->FoundState, 0); - RESTORE_MODEL(c, CTX(fSuccessor)); - } - else - { - SetSuccessor(p->FoundState, REF(cs)); - p->MaxContext = cs; + Ppmd8State_SetSuccessor(p->FoundState, 0); + RESTORE_MODEL(c, CTX(minSuccessor)); + return; } + Ppmd8State_SetSuccessor(p->FoundState, REF(cs)); + p->MinContext = p->MaxContext = cs; return; } - *p->Text++ = p->FoundState->Symbol; - successor = REF(p->Text); - if (p->Text >= p->UnitsStart) + + + { - RESTORE_MODEL(c, CTX(fSuccessor)); /* check it */ - return; + Byte *text = p->Text; + *text++ = p->FoundState->Symbol; + p->Text = text; + if (text >= p->UnitsStart) + { + RESTORE_MODEL(c, CTX(minSuccessor)); /* check it */ + return; + } + maxSuccessor = REF(text); } - - if (!fSuccessor) + + if (!minSuccessor) { - CTX_PTR cs = ReduceOrder(p, s, p->MinContext); - if (cs == NULL) + PPMD8_CTX_PTR cs = ReduceOrder(p, s, p->MinContext); + if (!cs) { - RESTORE_MODEL(c, 0); + RESTORE_MODEL(c, NULL); return; } - fSuccessor = REF(cs); + minSuccessor = REF(cs); } - else if ((Byte *)Ppmd8_GetPtr(p, fSuccessor) < p->UnitsStart) + else if ((Byte *)Ppmd8_GetPtr(p, minSuccessor) < p->UnitsStart) { - CTX_PTR cs = CreateSuccessors(p, False, s, p->MinContext); - if (cs == NULL) + PPMD8_CTX_PTR cs = Ppmd8_CreateSuccessors(p, False, s, p->MinContext); + if (!cs) { - RESTORE_MODEL(c, 0); + RESTORE_MODEL(c, NULL); return; } - fSuccessor = REF(cs); + minSuccessor = REF(cs); } if (--p->OrderFall == 0) { - successor = fSuccessor; + maxSuccessor = minSuccessor; p->Text -= (p->MaxContext != p->MinContext); } #ifdef PPMD8_FREEZE_SUPPORT else if (p->RestoreMethod > PPMD8_RESTORE_METHOD_FREEZE) { - successor = fSuccessor; - RESET_TEXT(0); + maxSuccessor = minSuccessor; + RESET_TEXT(0) p->OrderFall = 0; } #endif + } + + + + + + + + + + + - s0 = p->MinContext->SummFreq - (ns = p->MinContext->NumStats) - fFreq; - flag = (Byte)(0x08 * (fSymbol >= 0x40)); + + + + + + + + + + + + + + + + + flag = (Byte)(PPMD8_HiBitsFlag_3(fSymbol)); + s0 = p->MinContext->Union2.SummFreq - (ns = p->MinContext->NumStats) - fFreq; for (; c != p->MinContext; c = SUFFIX(c)) { unsigned ns1; - UInt32 cf, sf; + UInt32 sum; + if ((ns1 = c->NumStats) != 0) { if ((ns1 & 1) != 0) { /* Expand for one UNIT */ - unsigned oldNU = (ns1 + 1) >> 1; - unsigned i = U2I(oldNU); - if (i != U2I(oldNU + 1)) + const unsigned oldNU = (ns1 + 1) >> 1; + const unsigned i = U2I(oldNU); + if (i != U2I((size_t)oldNU + 1)) { - void *ptr = AllocUnits(p, i + 1); + void *ptr = Ppmd8_AllocUnits(p, i + 1); void *oldPtr; if (!ptr) { - RESTORE_MODEL(c, CTX(fSuccessor)); + RESTORE_MODEL(c, CTX(minSuccessor)); return; } oldPtr = STATS(c); - MyMem12Cpy(ptr, oldPtr, oldNU); - InsertNode(p, oldPtr, i); - c->Stats = STATS_REF(ptr); + MEM_12_CPY(ptr, oldPtr, oldNU) + Ppmd8_InsertNode(p, oldPtr, i); + c->Union4.Stats = STATS_REF(ptr); } } - c->SummFreq = (UInt16)(c->SummFreq + (3 * ns1 + 1 < ns)); + sum = c->Union2.SummFreq; + /* max increase of Escape_Freq is 1 here. + an average increase is 1/3 per symbol */ + sum += (UInt32)(unsigned)(3 * ns1 + 1 < ns); + /* original PPMdH uses 16-bit variable for (sum) here. + But (sum < ???). Do we need to truncate (sum) to 16-bit */ + // sum = (UInt16)sum; } else { - CPpmd_State *s2 = (CPpmd_State*)AllocUnits(p, 0); - if (!s2) + + CPpmd_State *s = (CPpmd_State*)Ppmd8_AllocUnits(p, 0); + if (!s) { - RESTORE_MODEL(c, CTX(fSuccessor)); + RESTORE_MODEL(c, CTX(minSuccessor)); return; } - *s2 = *ONE_STATE(c); - c->Stats = REF(s2); - if (s2->Freq < MAX_FREQ / 4 - 1) - s2->Freq <<= 1; - else - s2->Freq = MAX_FREQ - 4; - c->SummFreq = (UInt16)(s2->Freq + p->InitEsc + (ns > 2)); - } - cf = 2 * fFreq * (c->SummFreq + 6); - sf = (UInt32)s0 + c->SummFreq; - if (cf < 6 * sf) - { - cf = 1 + (cf > sf) + (cf >= 4 * sf); - c->SummFreq += 4; - } - else - { - cf = 4 + (cf > 9 * sf) + (cf > 12 * sf) + (cf > 15 * sf); - c->SummFreq = (UInt16)(c->SummFreq + cf); + { + unsigned freq = c->Union2.State2.Freq; + // s = *ONE_STATE(c); + s->Symbol = c->Union2.State2.Symbol; + s->Successor_0 = c->Union4.State4.Successor_0; + s->Successor_1 = c->Union4.State4.Successor_1; + // Ppmd8State_SetSuccessor(s, c->Union4.Stats); // call it only for debug purposes to check the order of + // (Successor_0 and Successor_1) in LE/BE. + c->Union4.Stats = REF(s); + if (freq < MAX_FREQ / 4 - 1) + freq <<= 1; + else + freq = MAX_FREQ - 4; + + s->Freq = (Byte)freq; + + sum = (UInt32)(freq + p->InitEsc + (ns > 2)); // Ppmd8 (> 2) + } } + { - CPpmd_State *s2 = STATS(c) + ns1 + 1; - SetSuccessor(s2, successor); - s2->Symbol = fSymbol; - s2->Freq = (Byte)cf; - c->Flags |= flag; + CPpmd_State *s = STATS(c) + ns1 + 1; + UInt32 cf = 2 * (sum + 6) * (UInt32)fFreq; + UInt32 sf = (UInt32)s0 + sum; + s->Symbol = fSymbol; c->NumStats = (Byte)(ns1 + 1); + Ppmd8State_SetSuccessor(s, maxSuccessor); + c->Flags |= flag; + if (cf < 6 * sf) + { + cf = (unsigned)1 + (cf > sf) + (cf >= 4 * sf); + sum += 4; + /* It can add (1, 2, 3) to Escape_Freq */ + } + else + { + cf = (unsigned)4 + (cf > 9 * sf) + (cf > 12 * sf) + (cf > 15 * sf); + sum += cf; + } + + c->Union2.SummFreq = (UInt16)sum; + s->Freq = (Byte)cf; } + } - p->MaxContext = p->MinContext = CTX(fSuccessor); + p->MaxContext = p->MinContext = CTX(minSuccessor); } -static void Rescale(CPpmd8 *p) + + +Z7_NO_INLINE +static void Ppmd8_Rescale(CPpmd8 *p) { unsigned i, adder, sumFreq, escFreq; CPpmd_State *stats = STATS(p->MinContext); CPpmd_State *s = p->FoundState; + + /* Sort the list by Freq */ + if (s != stats) { CPpmd_State tmp = *s; - for (; s != stats; s--) + do s[0] = s[-1]; + while (--s != stats); *s = tmp; } - escFreq = p->MinContext->SummFreq - s->Freq; - s->Freq += 4; - adder = (p->OrderFall != 0 - #ifdef PPMD8_FREEZE_SUPPORT - || p->RestoreMethod > PPMD8_RESTORE_METHOD_FREEZE - #endif - ); - s->Freq = (Byte)((s->Freq + adder) >> 1); + sumFreq = s->Freq; + escFreq = p->MinContext->Union2.SummFreq - sumFreq; + + + + + + adder = (p->OrderFall != 0); + + #ifdef PPMD8_FREEZE_SUPPORT + adder |= (p->RestoreMethod > PPMD8_RESTORE_METHOD_FREEZE); + #endif + + sumFreq = (sumFreq + 4 + adder) >> 1; i = p->MinContext->NumStats; + s->Freq = (Byte)sumFreq; + do { - escFreq -= (++s)->Freq; - s->Freq = (Byte)((s->Freq + adder) >> 1); - sumFreq += s->Freq; - if (s[0].Freq > s[-1].Freq) + unsigned freq = (++s)->Freq; + escFreq -= freq; + freq = (freq + adder) >> 1; + sumFreq += freq; + s->Freq = (Byte)freq; + if (freq > s[-1].Freq) { + CPpmd_State tmp = *s; CPpmd_State *s1 = s; - CPpmd_State tmp = *s1; do + { s1[0] = s1[-1]; - while (--s1 != stats && tmp.Freq > s1[-1].Freq); + } + while (--s1 != stats && freq > s1[-1].Freq); *s1 = tmp; } } @@ -1003,50 +1366,90 @@ static void Rescale(CPpmd8 *p) if (s->Freq == 0) { - unsigned numStats = p->MinContext->NumStats; - unsigned n0, n1; - do { i++; } while ((--s)->Freq == 0); + /* Remove all items with Freq == 0 */ + CPpmd8_Context *mc; + unsigned numStats, numStatsNew, n0, n1; + + i = 0; do { i++; } while ((--s)->Freq == 0); + + + + escFreq += i; - p->MinContext->NumStats = (Byte)(p->MinContext->NumStats - i); - if (p->MinContext->NumStats == 0) + mc = p->MinContext; + numStats = mc->NumStats; + numStatsNew = numStats - i; + mc->NumStats = (Byte)(numStatsNew); + n0 = (numStats + 2) >> 1; + + if (numStatsNew == 0) { - CPpmd_State tmp = *stats; - tmp.Freq = (Byte)((2 * tmp.Freq + escFreq - 1) / escFreq); - if (tmp.Freq > MAX_FREQ / 3) - tmp.Freq = MAX_FREQ / 3; - InsertNode(p, stats, U2I((numStats + 2) >> 1)); - p->MinContext->Flags = (Byte)((p->MinContext->Flags & 0x10) + 0x08 * (tmp.Symbol >= 0x40)); - *(p->FoundState = ONE_STATE(p->MinContext)) = tmp; + + unsigned freq = (2 * (unsigned)stats->Freq + escFreq - 1) / escFreq; + if (freq > MAX_FREQ / 3) + freq = MAX_FREQ / 3; + mc->Flags = (Byte)((mc->Flags & FLAG_PREV_HIGH) + PPMD8_HiBitsFlag_3(stats->Symbol)); + + + + + + s = ONE_STATE(mc); + *s = *stats; + s->Freq = (Byte)freq; + p->FoundState = s; + Ppmd8_InsertNode(p, stats, U2I(n0)); return; } - n0 = (numStats + 2) >> 1; - n1 = (p->MinContext->NumStats + 2) >> 1; + + n1 = (numStatsNew + 2) >> 1; if (n0 != n1) - p->MinContext->Stats = STATS_REF(ShrinkUnits(p, stats, n0, n1)); - p->MinContext->Flags &= ~0x08; - p->MinContext->Flags |= 0x08 * ((s = STATS(p->MinContext))->Symbol >= 0x40); - i = p->MinContext->NumStats; - do { p->MinContext->Flags |= 0x08*((++s)->Symbol >= 0x40); } while (--i); + mc->Union4.Stats = STATS_REF(ShrinkUnits(p, stats, n0, n1)); + { + // here we are for max order only. So Ppmd8_MakeEscFreq() doesn't use mc->Flags + // but we still need current (Flags & FLAG_PREV_HIGH), if we will convert context to 1-symbol context later. + /* + unsigned flags = HiBits_Prepare((s = STATS(mc))->Symbol); + i = mc->NumStats; + do { flags |= HiBits_Prepare((++s)->Symbol); } while (--i); + mc->Flags = (Byte)((mc->Flags & ~FLAG_SYM_HIGH) + HiBits_Convert_3(flags)); + */ + } + } + + + + + + + { + CPpmd8_Context *mc = p->MinContext; + mc->Union2.SummFreq = (UInt16)(sumFreq + escFreq - (escFreq >> 1)); + mc->Flags |= FLAG_RESCALED; + p->FoundState = STATS(mc); } - p->MinContext->SummFreq = (UInt16)(sumFreq + escFreq - (escFreq >> 1)); - p->MinContext->Flags |= 0x4; - p->FoundState = STATS(p->MinContext); } + CPpmd_See *Ppmd8_MakeEscFreq(CPpmd8 *p, unsigned numMasked1, UInt32 *escFreq) { CPpmd_See *see; - if (p->MinContext->NumStats != 0xFF) + const CPpmd8_Context *mc = p->MinContext; + unsigned numStats = mc->NumStats; + if (numStats != 0xFF) { - see = p->See[(unsigned)p->NS2Indx[(unsigned)p->MinContext->NumStats + 2] - 3] + - (p->MinContext->SummFreq > 11 * ((unsigned)p->MinContext->NumStats + 1)) + - 2 * (unsigned)(2 * (unsigned)p->MinContext->NumStats < - ((unsigned)SUFFIX(p->MinContext)->NumStats + numMasked1)) + - p->MinContext->Flags; + // (3 <= numStats + 2 <= 256) (3 <= NS2Indx[3] and NS2Indx[256] === 26) + see = p->See[(size_t)(unsigned)p->NS2Indx[(size_t)numStats + 2] - 3] + + (mc->Union2.SummFreq > 11 * (numStats + 1)) + + 2 * (unsigned)(2 * numStats < ((unsigned)SUFFIX(mc)->NumStats + numMasked1)) + + mc->Flags; + { - unsigned r = (see->Summ >> see->Shift); - see->Summ = (UInt16)(see->Summ - r); - *escFreq = r + (r == 0); + // if (see->Summ) field is larger than 16-bit, we need only low 16 bits of Summ + const unsigned summ = (UInt16)see->Summ; // & 0xFFFF + const unsigned r = (summ >> see->Shift); + see->Summ = (UInt16)(summ - r); + *escFreq = (UInt32)(r + (r == 0)); } } else @@ -1057,67 +1460,115 @@ CPpmd_See *Ppmd8_MakeEscFreq(CPpmd8 *p, unsigned numMasked1, UInt32 *escFreq) return see; } -static void NextContext(CPpmd8 *p) + +static void Ppmd8_NextContext(CPpmd8 *p) { - CTX_PTR c = CTX(SUCCESSOR(p->FoundState)); - if (p->OrderFall == 0 && (Byte *)c >= p->UnitsStart) - p->MinContext = p->MaxContext = c; + PPMD8_CTX_PTR c = CTX(SUCCESSOR(p->FoundState)); + if (p->OrderFall == 0 && (const Byte *)c >= p->UnitsStart) + p->MaxContext = p->MinContext = c; else - { - UpdateModel(p); - p->MinContext = p->MaxContext; - } + Ppmd8_UpdateModel(p); } + void Ppmd8_Update1(CPpmd8 *p) { CPpmd_State *s = p->FoundState; - s->Freq += 4; - p->MinContext->SummFreq += 4; - if (s[0].Freq > s[-1].Freq) + unsigned freq = s->Freq; + freq += 4; + p->MinContext->Union2.SummFreq = (UInt16)(p->MinContext->Union2.SummFreq + 4); + s->Freq = (Byte)freq; + if (freq > s[-1].Freq) { - SwapStates(&s[0], &s[-1]); + SWAP_STATES(s, &s[-1]); p->FoundState = --s; - if (s->Freq > MAX_FREQ) - Rescale(p); + if (freq > MAX_FREQ) + Ppmd8_Rescale(p); } - NextContext(p); + Ppmd8_NextContext(p); } + void Ppmd8_Update1_0(CPpmd8 *p) { - p->PrevSuccess = (2 * p->FoundState->Freq >= p->MinContext->SummFreq); - p->RunLength += p->PrevSuccess; - p->MinContext->SummFreq += 4; - if ((p->FoundState->Freq += 4) > MAX_FREQ) - Rescale(p); - NextContext(p); + CPpmd_State *s = p->FoundState; + CPpmd8_Context *mc = p->MinContext; + unsigned freq = s->Freq; + const unsigned summFreq = mc->Union2.SummFreq; + p->PrevSuccess = (2 * freq >= summFreq); // Ppmd8 (>=) + p->RunLength += (Int32)p->PrevSuccess; + mc->Union2.SummFreq = (UInt16)(summFreq + 4); + freq += 4; + s->Freq = (Byte)freq; + if (freq > MAX_FREQ) + Ppmd8_Rescale(p); + Ppmd8_NextContext(p); } + +/* void Ppmd8_UpdateBin(CPpmd8 *p) { - p->FoundState->Freq = (Byte)(p->FoundState->Freq + (p->FoundState->Freq < 196)); + unsigned freq = p->FoundState->Freq; + p->FoundState->Freq = (Byte)(freq + (freq < 196)); // Ppmd8 (196) p->PrevSuccess = 1; p->RunLength++; - NextContext(p); + Ppmd8_NextContext(p); } +*/ void Ppmd8_Update2(CPpmd8 *p) { - p->MinContext->SummFreq += 4; - if ((p->FoundState->Freq += 4) > MAX_FREQ) - Rescale(p); + CPpmd_State *s = p->FoundState; + unsigned freq = s->Freq; + freq += 4; p->RunLength = p->InitRL; - UpdateModel(p); - p->MinContext = p->MaxContext; + p->MinContext->Union2.SummFreq = (UInt16)(p->MinContext->Union2.SummFreq + 4); + s->Freq = (Byte)freq; + if (freq > MAX_FREQ) + Ppmd8_Rescale(p); + Ppmd8_UpdateModel(p); } /* H->I changes: NS2Indx - GlewCount, and Glue method + GlueCount, and Glue method BinSum See / EscFreq - CreateSuccessors updates more suffix contexts - UpdateModel consts. + Ppmd8_CreateSuccessors updates more suffix contexts + Ppmd8_UpdateModel consts. PrevSuccess Update + +Flags: + (1 << 2) - the Context was Rescaled + (1 << 3) - there is symbol in Stats with (sym >= 0x40) in + (1 << 4) - main symbol of context is (sym >= 0x40) */ + +#undef RESET_TEXT +#undef FLAG_RESCALED +#undef FLAG_PREV_HIGH +#undef HiBits_Prepare +#undef HiBits_Convert_3 +#undef HiBits_Convert_4 +#undef PPMD8_HiBitsFlag_3 +#undef PPMD8_HiBitsFlag_4 +#undef RESTORE_MODEL + +#undef MAX_FREQ +#undef UNIT_SIZE +#undef U2B +#undef U2I +#undef I2U + +#undef REF +#undef STATS_REF +#undef CTX +#undef STATS +#undef ONE_STATE +#undef SUFFIX +#undef NODE +#undef EMPTY_NODE +#undef MEM_12_CPY +#undef SUCCESSOR +#undef SWAP_STATES diff --git a/src/plugins/7zip/7za/c/Ppmd8.h b/src/plugins/7zip/7za/c/Ppmd8.h index 7dcfc916..d5bb57e1 100644 --- a/src/plugins/7zip/7za/c/Ppmd8.h +++ b/src/plugins/7zip/7za/c/Ppmd8.h @@ -1,11 +1,11 @@ -/* Ppmd8.h -- PPMdI codec -2011-01-27 : Igor Pavlov : Public domain +/* Ppmd8.h -- Ppmd8 (PPMdI) compression codec +2023-04-02 : Igor Pavlov : Public domain This code is based on: PPMd var.I (2002): Dmitry Shkarin : Public domain Carryless rangecoder (1999): Dmitry Subbotin : Public domain */ -#ifndef __PPMD8_H -#define __PPMD8_H +#ifndef ZIP7_INC_PPMD8_H +#define ZIP7_INC_PPMD8_H #include "Ppmd.h" @@ -14,35 +14,45 @@ EXTERN_C_BEGIN #define PPMD8_MIN_ORDER 2 #define PPMD8_MAX_ORDER 16 + + + struct CPpmd8_Context_; -typedef - #ifdef PPMD_32BIT - struct CPpmd8_Context_ * - #else - UInt32 - #endif - CPpmd8_Context_Ref; +typedef Ppmd_Ref_Type(struct CPpmd8_Context_) CPpmd8_Context_Ref; -#pragma pack(push, 1) +// MY_CPU_pragma_pack_push_1 typedef struct CPpmd8_Context_ { Byte NumStats; Byte Flags; - UInt16 SummFreq; - CPpmd_State_Ref Stats; + + union + { + UInt16 SummFreq; + CPpmd_State2 State2; + } Union2; + + union + { + CPpmd_State_Ref Stats; + CPpmd_State4 State4; + } Union4; + CPpmd8_Context_Ref Suffix; } CPpmd8_Context; -#pragma pack(pop) +// MY_CPU_pragma_pop -#define Ppmd8Context_OneState(p) ((CPpmd_State *)&(p)->SummFreq) +#define Ppmd8Context_OneState(p) ((CPpmd_State *)&(p)->Union2) -/* The BUG in Shkarin's code for FREEZE mode was fixed, but that fixed - code is not compatible with original code for some files compressed +/* PPMdI code rev.2 contains the fix over PPMdI code rev.1. + But the code PPMdI.2 is not compatible with PPMdI.1 for some files compressed in FREEZE mode. So we disable FREEZE mode support. */ +// #define PPMD8_FREEZE_SUPPORT + enum { PPMD8_RESTORE_METHOD_RESTART, @@ -50,87 +60,121 @@ enum #ifdef PPMD8_FREEZE_SUPPORT , PPMD8_RESTORE_METHOD_FREEZE #endif + , PPMD8_RESTORE_METHOD_UNSUPPPORTED }; + + + + + + + typedef struct { CPpmd8_Context *MinContext, *MaxContext; CPpmd_State *FoundState; - unsigned OrderFall, InitEsc, PrevSuccess, MaxOrder; + unsigned OrderFall, InitEsc, PrevSuccess, MaxOrder, RestoreMethod; Int32 RunLength, InitRL; /* must be 32-bit at least */ UInt32 Size; UInt32 GlueCount; - Byte *Base, *LoUnit, *HiUnit, *Text, *UnitsStart; UInt32 AlignOffset; - unsigned RestoreMethod; + Byte *Base, *LoUnit, *HiUnit, *Text, *UnitsStart; - /* Range Coder */ UInt32 Range; UInt32 Code; UInt32 Low; union { - IByteIn *In; - IByteOut *Out; + IByteInPtr In; + IByteOutPtr Out; } Stream; - Byte Indx2Units[PPMD_NUM_INDEXES]; + Byte Indx2Units[PPMD_NUM_INDEXES + 2]; // +2 for alignment Byte Units2Indx[128]; CPpmd_Void_Ref FreeList[PPMD_NUM_INDEXES]; UInt32 Stamps[PPMD_NUM_INDEXES]; - Byte NS2BSIndx[256], NS2Indx[260]; + Byte ExpEscape[16]; CPpmd_See DummySee, See[24][32]; UInt16 BinSumm[25][64]; + } CPpmd8; + void Ppmd8_Construct(CPpmd8 *p); -Bool Ppmd8_Alloc(CPpmd8 *p, UInt32 size, ISzAlloc *alloc); -void Ppmd8_Free(CPpmd8 *p, ISzAlloc *alloc); +BoolInt Ppmd8_Alloc(CPpmd8 *p, UInt32 size, ISzAllocPtr alloc); +void Ppmd8_Free(CPpmd8 *p, ISzAllocPtr alloc); void Ppmd8_Init(CPpmd8 *p, unsigned maxOrder, unsigned restoreMethod); #define Ppmd8_WasAllocated(p) ((p)->Base != NULL) /* ---------- Internal Functions ---------- */ -extern const Byte PPMD8_kExpEscape[16]; - -#ifdef PPMD_32BIT - #define Ppmd8_GetPtr(p, ptr) (ptr) - #define Ppmd8_GetContext(p, ptr) (ptr) - #define Ppmd8_GetStats(p, ctx) ((ctx)->Stats) -#else - #define Ppmd8_GetPtr(p, offs) ((void *)((p)->Base + (offs))) - #define Ppmd8_GetContext(p, offs) ((CPpmd8_Context *)Ppmd8_GetPtr((p), (offs))) - #define Ppmd8_GetStats(p, ctx) ((CPpmd_State *)Ppmd8_GetPtr((p), ((ctx)->Stats))) -#endif +#define Ppmd8_GetPtr(p, ptr) Ppmd_GetPtr(p, ptr) +#define Ppmd8_GetContext(p, ptr) Ppmd_GetPtr_Type(p, ptr, CPpmd8_Context) +#define Ppmd8_GetStats(p, ctx) Ppmd_GetPtr_Type(p, (ctx)->Union4.Stats, CPpmd_State) void Ppmd8_Update1(CPpmd8 *p); void Ppmd8_Update1_0(CPpmd8 *p); void Ppmd8_Update2(CPpmd8 *p); -void Ppmd8_UpdateBin(CPpmd8 *p); + + + + + #define Ppmd8_GetBinSumm(p) \ - &p->BinSumm[p->NS2Indx[Ppmd8Context_OneState(p->MinContext)->Freq - 1]][ \ - p->NS2BSIndx[Ppmd8_GetContext(p, p->MinContext->Suffix)->NumStats] + \ - p->PrevSuccess + p->MinContext->Flags + ((p->RunLength >> 26) & 0x20)] + &p->BinSumm[p->NS2Indx[(size_t)Ppmd8Context_OneState(p->MinContext)->Freq - 1]] \ + [ p->PrevSuccess + ((p->RunLength >> 26) & 0x20) \ + + p->NS2BSIndx[Ppmd8_GetContext(p, p->MinContext->Suffix)->NumStats] + \ + + p->MinContext->Flags ] + CPpmd_See *Ppmd8_MakeEscFreq(CPpmd8 *p, unsigned numMasked, UInt32 *scale); +/* 20.01: the original PPMdI encoder and decoder probably could work incorrectly in some rare cases, + where the original PPMdI code can give "Divide by Zero" operation. + We use the following fix to allow correct working of encoder and decoder in any cases. + We correct (Escape_Freq) and (_sum_), if (_sum_) is larger than p->Range) */ +#define PPMD8_CORRECT_SUM_RANGE(p, _sum_) if (_sum_ > p->Range /* /1 */) _sum_ = p->Range; + + /* ---------- Decode ---------- */ -Bool Ppmd8_RangeDec_Init(CPpmd8 *p); +#define PPMD8_SYM_END (-1) +#define PPMD8_SYM_ERROR (-2) + +/* +You must set (CPpmd8::Stream.In) before Ppmd8_RangeDec_Init() + +Ppmd8_DecodeSymbol() +out: + >= 0 : decoded byte + -1 : PPMD8_SYM_END : End of payload marker + -2 : PPMD8_SYM_ERROR : Data error +*/ + + +BoolInt Ppmd8_Init_RangeDec(CPpmd8 *p); #define Ppmd8_RangeDec_IsFinishedOK(p) ((p)->Code == 0) -int Ppmd8_DecodeSymbol(CPpmd8 *p); /* returns: -1 as EndMarker, -2 as DataError */ +int Ppmd8_DecodeSymbol(CPpmd8 *p); + + + + + + /* ---------- Encode ---------- */ -#define Ppmd8_RangeEnc_Init(p) { (p)->Low = 0; (p)->Range = 0xFFFFFFFF; } -void Ppmd8_RangeEnc_FlushData(CPpmd8 *p); -void Ppmd8_EncodeSymbol(CPpmd8 *p, int symbol); /* symbol = -1 means EndMarker */ +#define Ppmd8_Init_RangeEnc(p) { (p)->Low = 0; (p)->Range = 0xFFFFFFFF; } +void Ppmd8_Flush_RangeEnc(CPpmd8 *p); +void Ppmd8_EncodeSymbol(CPpmd8 *p, int symbol); + EXTERN_C_END diff --git a/src/plugins/7zip/7za/c/Ppmd8Dec.c b/src/plugins/7zip/7za/c/Ppmd8Dec.c index 317bd651..ff911674 100644 --- a/src/plugins/7zip/7za/c/Ppmd8Dec.c +++ b/src/plugins/7zip/7za/c/Ppmd8Dec.c @@ -1,5 +1,5 @@ -/* Ppmd8Dec.c -- PPMdI Decoder -2010-04-16 : Igor Pavlov : Public domain +/* Ppmd8Dec.c -- Ppmd8 (PPMdI) Decoder +2023-09-07 : Igor Pavlov : Public domain This code is based on: PPMd var.I (2002): Dmitry Shkarin : Public domain Carryless rangecoder (1999): Dmitry Subbotin : Public domain */ @@ -8,150 +8,288 @@ This code is based on: #include "Ppmd8.h" -#define kTop (1 << 24) -#define kBot (1 << 15) +#define kTop ((UInt32)1 << 24) +#define kBot ((UInt32)1 << 15) -Bool Ppmd8_RangeDec_Init(CPpmd8 *p) +#define READ_BYTE(p) IByteIn_Read((p)->Stream.In) + +BoolInt Ppmd8_Init_RangeDec(CPpmd8 *p) { unsigned i; - p->Low = 0; - p->Range = 0xFFFFFFFF; p->Code = 0; + p->Range = 0xFFFFFFFF; + p->Low = 0; + for (i = 0; i < 4; i++) - p->Code = (p->Code << 8) | p->Stream.In->Read(p->Stream.In); + p->Code = (p->Code << 8) | READ_BYTE(p); return (p->Code < 0xFFFFFFFF); } -static UInt32 RangeDec_GetThreshold(CPpmd8 *p, UInt32 total) +#define RC_NORM(p) \ + while ((p->Low ^ (p->Low + p->Range)) < kTop \ + || (p->Range < kBot && ((p->Range = (0 - p->Low) & (kBot - 1)), 1))) { \ + p->Code = (p->Code << 8) | READ_BYTE(p); \ + p->Range <<= 8; p->Low <<= 8; } + +// we must use only one type of Normalization from two: LOCAL or REMOTE +#define RC_NORM_LOCAL(p) // RC_NORM(p) +#define RC_NORM_REMOTE(p) RC_NORM(p) + +#define R p + +Z7_FORCE_INLINE +// Z7_NO_INLINE +static void Ppmd8_RD_Decode(CPpmd8 *p, UInt32 start, UInt32 size) { - return p->Code / (p->Range /= total); + start *= R->Range; + R->Low += start; + R->Code -= start; + R->Range *= size; + RC_NORM_LOCAL(R) } -static void RangeDec_Decode(CPpmd8 *p, UInt32 start, UInt32 size) -{ - start *= p->Range; - p->Low += start; - p->Code -= start; - p->Range *= size; +#define RC_Decode(start, size) Ppmd8_RD_Decode(p, start, size); +#define RC_DecodeFinal(start, size) RC_Decode(start, size) RC_NORM_REMOTE(R) +#define RC_GetThreshold(total) (R->Code / (R->Range /= (total))) - while ((p->Low ^ (p->Low + p->Range)) < kTop || - (p->Range < kBot && ((p->Range = (0 - p->Low) & (kBot - 1)), 1))) - { - p->Code = (p->Code << 8) | p->Stream.In->Read(p->Stream.In); - p->Range <<= 8; - p->Low <<= 8; - } -} -#define MASK(sym) ((signed char *)charMask)[sym] +#define CTX(ref) ((CPpmd8_Context *)Ppmd8_GetContext(p, ref)) +// typedef CPpmd8_Context * CTX_PTR; +#define SUCCESSOR(p) Ppmd_GET_SUCCESSOR(p) +void Ppmd8_UpdateModel(CPpmd8 *p); + +#define MASK(sym) ((Byte *)charMask)[sym] + int Ppmd8_DecodeSymbol(CPpmd8 *p) { size_t charMask[256 / sizeof(size_t)]; + if (p->MinContext->NumStats != 0) { CPpmd_State *s = Ppmd8_GetStats(p, p->MinContext); unsigned i; UInt32 count, hiCnt; - if ((count = RangeDec_GetThreshold(p, p->MinContext->SummFreq)) < (hiCnt = s->Freq)) + UInt32 summFreq = p->MinContext->Union2.SummFreq; + + PPMD8_CORRECT_SUM_RANGE(p, summFreq) + + + count = RC_GetThreshold(summFreq); + hiCnt = count; + + if ((Int32)(count -= s->Freq) < 0) { - Byte symbol; - RangeDec_Decode(p, 0, s->Freq); + Byte sym; + RC_DecodeFinal(0, s->Freq) p->FoundState = s; - symbol = s->Symbol; + sym = s->Symbol; Ppmd8_Update1_0(p); - return symbol; + return sym; } + p->PrevSuccess = 0; i = p->MinContext->NumStats; + do { - if ((hiCnt += (++s)->Freq) > count) + if ((Int32)(count -= (++s)->Freq) < 0) { - Byte symbol; - RangeDec_Decode(p, hiCnt - s->Freq, s->Freq); + Byte sym; + RC_DecodeFinal((hiCnt - count) - s->Freq, s->Freq) p->FoundState = s; - symbol = s->Symbol; + sym = s->Symbol; Ppmd8_Update1(p); - return symbol; + return sym; } } while (--i); - if (count >= p->MinContext->SummFreq) - return -2; - RangeDec_Decode(p, hiCnt, p->MinContext->SummFreq - hiCnt); - PPMD_SetAllBitsIn256Bytes(charMask); - MASK(s->Symbol) = 0; - i = p->MinContext->NumStats; - do { MASK((--s)->Symbol) = 0; } while (--i); + + if (hiCnt >= summFreq) + return PPMD8_SYM_ERROR; + + hiCnt -= count; + RC_Decode(hiCnt, summFreq - hiCnt) + + + PPMD_SetAllBitsIn256Bytes(charMask) + // i = p->MinContext->NumStats - 1; + // do { MASK((--s)->Symbol) = 0; } while (--i); + { + CPpmd_State *s2 = Ppmd8_GetStats(p, p->MinContext); + MASK(s->Symbol) = 0; + do + { + const unsigned sym0 = s2[0].Symbol; + const unsigned sym1 = s2[1].Symbol; + s2 += 2; + MASK(sym0) = 0; + MASK(sym1) = 0; + } + while (s2 < s); + } } else { + CPpmd_State *s = Ppmd8Context_OneState(p->MinContext); UInt16 *prob = Ppmd8_GetBinSumm(p); - if (((p->Code / (p->Range >>= 14)) < *prob)) + UInt32 pr = *prob; + UInt32 size0 = (R->Range >> 14) * pr; + pr = PPMD_UPDATE_PROB_1(pr); + + if (R->Code < size0) { - Byte symbol; - RangeDec_Decode(p, 0, *prob); - *prob = (UInt16)PPMD_UPDATE_PROB_0(*prob); - symbol = (p->FoundState = Ppmd8Context_OneState(p->MinContext))->Symbol; - Ppmd8_UpdateBin(p); - return symbol; + Byte sym; + *prob = (UInt16)(pr + (1 << PPMD_INT_BITS)); + + // RangeDec_DecodeBit0(size0); + R->Range = size0; + RC_NORM(R) + + + + // sym = (p->FoundState = Ppmd8Context_OneState(p->MinContext))->Symbol; + // Ppmd8_UpdateBin(p); + { + unsigned freq = s->Freq; + CPpmd8_Context *c = CTX(SUCCESSOR(s)); + sym = s->Symbol; + p->FoundState = s; + p->PrevSuccess = 1; + p->RunLength++; + s->Freq = (Byte)(freq + (freq < 196)); + // NextContext(p); + if (p->OrderFall == 0 && (const Byte *)c >= p->UnitsStart) + p->MaxContext = p->MinContext = c; + else + Ppmd8_UpdateModel(p); + } + return sym; } - RangeDec_Decode(p, *prob, (1 << 14) - *prob); - *prob = (UInt16)PPMD_UPDATE_PROB_1(*prob); - p->InitEsc = PPMD8_kExpEscape[*prob >> 10]; - PPMD_SetAllBitsIn256Bytes(charMask); + + *prob = (UInt16)pr; + p->InitEsc = p->ExpEscape[pr >> 10]; + + // RangeDec_DecodeBit1(rc2, size0); + R->Low += size0; + R->Code -= size0; + R->Range = (R->Range & ~((UInt32)PPMD_BIN_SCALE - 1)) - size0; + RC_NORM_LOCAL(R) + + PPMD_SetAllBitsIn256Bytes(charMask) MASK(Ppmd8Context_OneState(p->MinContext)->Symbol) = 0; p->PrevSuccess = 0; } + for (;;) { - CPpmd_State *ps[256], *s; + CPpmd_State *s, *s2; UInt32 freqSum, count, hiCnt; + UInt32 freqSum2; CPpmd_See *see; - unsigned i, num, numMasked = p->MinContext->NumStats; + CPpmd8_Context *mc; + unsigned numMasked; + RC_NORM_REMOTE(R) + mc = p->MinContext; + numMasked = mc->NumStats; + do { p->OrderFall++; - if (!p->MinContext->Suffix) - return -1; - p->MinContext = Ppmd8_GetContext(p, p->MinContext->Suffix); + if (!mc->Suffix) + return PPMD8_SYM_END; + mc = Ppmd8_GetContext(p, mc->Suffix); } - while (p->MinContext->NumStats == numMasked); - hiCnt = 0; - s = Ppmd8_GetStats(p, p->MinContext); - i = 0; - num = p->MinContext->NumStats - numMasked; - do + while (mc->NumStats == numMasked); + + s = Ppmd8_GetStats(p, mc); + { - int k = (int)(MASK(s->Symbol)); - hiCnt += (s->Freq & k); - ps[i] = s++; - i -= k; + unsigned num = (unsigned)mc->NumStats + 1; + unsigned num2 = num / 2; + + num &= 1; + hiCnt = (s->Freq & (UInt32)(MASK(s->Symbol))) & (0 - (UInt32)num); + s += num; + p->MinContext = mc; + + do + { + const unsigned sym0 = s[0].Symbol; + const unsigned sym1 = s[1].Symbol; + s += 2; + hiCnt += (s[-2].Freq & (UInt32)(MASK(sym0))); + hiCnt += (s[-1].Freq & (UInt32)(MASK(sym1))); + } + while (--num2); } - while (i != num); see = Ppmd8_MakeEscFreq(p, numMasked, &freqSum); freqSum += hiCnt; - count = RangeDec_GetThreshold(p, freqSum); + freqSum2 = freqSum; + PPMD8_CORRECT_SUM_RANGE(R, freqSum2) + + + count = RC_GetThreshold(freqSum2); if (count < hiCnt) { - Byte symbol; - CPpmd_State **pps = ps; - for (hiCnt = 0; (hiCnt += (*pps)->Freq) <= count; pps++); - s = *pps; - RangeDec_Decode(p, hiCnt - s->Freq, s->Freq); - Ppmd_See_Update(see); + Byte sym; + // Ppmd_See_UPDATE(see) // new (see->Summ) value can overflow over 16-bits in some rare cases + s = Ppmd8_GetStats(p, p->MinContext); + hiCnt = count; + + + { + for (;;) + { + count -= s->Freq & (UInt32)(MASK((s)->Symbol)); s++; if ((Int32)count < 0) break; + // count -= s->Freq & (UInt32)(MASK((s)->Symbol)); s++; if ((Int32)count < 0) break; + } + } + s--; + RC_DecodeFinal((hiCnt - count) - s->Freq, s->Freq) + + // new (see->Summ) value can overflow over 16-bits in some rare cases + Ppmd_See_UPDATE(see) p->FoundState = s; - symbol = s->Symbol; + sym = s->Symbol; Ppmd8_Update2(p); - return symbol; + return sym; } - if (count >= freqSum) - return -2; - RangeDec_Decode(p, hiCnt, freqSum - hiCnt); + + if (count >= freqSum2) + return PPMD8_SYM_ERROR; + + RC_Decode(hiCnt, freqSum2 - hiCnt) + + // We increase (see->Summ) for sum of Freqs of all non_Masked symbols. + // new (see->Summ) value can overflow over 16-bits in some rare cases see->Summ = (UInt16)(see->Summ + freqSum); - do { MASK(ps[--i]->Symbol) = 0; } while (i != 0); + + s = Ppmd8_GetStats(p, p->MinContext); + s2 = s + p->MinContext->NumStats + 1; + do + { + MASK(s->Symbol) = 0; + s++; + } + while (s != s2); } } + +#undef kTop +#undef kBot +#undef READ_BYTE +#undef RC_NORM_BASE +#undef RC_NORM_1 +#undef RC_NORM +#undef RC_NORM_LOCAL +#undef RC_NORM_REMOTE +#undef R +#undef RC_Decode +#undef RC_DecodeFinal +#undef RC_GetThreshold +#undef CTX +#undef SUCCESSOR +#undef MASK diff --git a/src/plugins/7zip/7za/c/Ppmd8Enc.c b/src/plugins/7zip/7za/c/Ppmd8Enc.c index 5e389be4..b0e34c45 100644 --- a/src/plugins/7zip/7za/c/Ppmd8Enc.c +++ b/src/plugins/7zip/7za/c/Ppmd8Enc.c @@ -1,5 +1,5 @@ -/* Ppmd8Enc.c -- PPMdI Encoder -2010-04-16 : Igor Pavlov : Public domain +/* Ppmd8Enc.c -- Ppmd8 (PPMdI) Encoder +2023-09-07 : Igor Pavlov : Public domain This code is based on: PPMd var.I (2002): Dmitry Shkarin : Public domain Carryless rangecoder (1999): Dmitry Subbotin : Public domain */ @@ -8,62 +8,103 @@ This code is based on: #include "Ppmd8.h" -#define kTop (1 << 24) -#define kBot (1 << 15) +#define kTop ((UInt32)1 << 24) +#define kBot ((UInt32)1 << 15) -void Ppmd8_RangeEnc_FlushData(CPpmd8 *p) +#define WRITE_BYTE(p) IByteOut_Write(p->Stream.Out, (Byte)(p->Low >> 24)) + +void Ppmd8_Flush_RangeEnc(CPpmd8 *p) { unsigned i; for (i = 0; i < 4; i++, p->Low <<= 8 ) - p->Stream.Out->Write(p->Stream.Out, (Byte)(p->Low >> 24)); + WRITE_BYTE(p); } -static void RangeEnc_Normalize(CPpmd8 *p) -{ - while ((p->Low ^ (p->Low + p->Range)) < kTop || - (p->Range < kBot && ((p->Range = (0 - p->Low) & (kBot - 1)), 1))) - { - p->Stream.Out->Write(p->Stream.Out, (Byte)(p->Low >> 24)); - p->Range <<= 8; - p->Low <<= 8; - } -} -static void RangeEnc_Encode(CPpmd8 *p, UInt32 start, UInt32 size, UInt32 total) -{ - p->Low += start * (p->Range /= total); - p->Range *= size; - RangeEnc_Normalize(p); -} -static void RangeEnc_EncodeBit_0(CPpmd8 *p, UInt32 size0) -{ - p->Range >>= 14; - p->Range *= size0; - RangeEnc_Normalize(p); -} -static void RangeEnc_EncodeBit_1(CPpmd8 *p, UInt32 size0) + + +#define RC_NORM(p) \ + while ((p->Low ^ (p->Low + p->Range)) < kTop \ + || (p->Range < kBot && ((p->Range = (0 - p->Low) & (kBot - 1)), 1))) \ + { WRITE_BYTE(p); p->Range <<= 8; p->Low <<= 8; } + + + + + + + + + + + + + +// we must use only one type of Normalization from two: LOCAL or REMOTE +#define RC_NORM_LOCAL(p) // RC_NORM(p) +#define RC_NORM_REMOTE(p) RC_NORM(p) + +// #define RC_PRE(total) p->Range /= total; +// #define RC_PRE(total) + +#define R p + + + + +Z7_FORCE_INLINE +// Z7_NO_INLINE +static void Ppmd8_RangeEnc_Encode(CPpmd8 *p, UInt32 start, UInt32 size, UInt32 total) { - p->Low += size0 * (p->Range >>= 14); - p->Range *= ((1 << 14) - size0); - RangeEnc_Normalize(p); + R->Low += start * (R->Range /= total); + R->Range *= size; + RC_NORM_LOCAL(R) } -#define MASK(sym) ((signed char *)charMask)[sym] + + + + + + + +#define RC_Encode(start, size, total) Ppmd8_RangeEnc_Encode(p, start, size, total); +#define RC_EncodeFinal(start, size, total) RC_Encode(start, size, total) RC_NORM_REMOTE(p) + +#define CTX(ref) ((CPpmd8_Context *)Ppmd8_GetContext(p, ref)) + +// typedef CPpmd8_Context * CTX_PTR; +#define SUCCESSOR(p) Ppmd_GET_SUCCESSOR(p) + +void Ppmd8_UpdateModel(CPpmd8 *p); + +#define MASK(sym) ((Byte *)charMask)[sym] + +// Z7_FORCE_INLINE +// static void Ppmd8_EncodeSymbol(CPpmd8 *p, int symbol) { size_t charMask[256 / sizeof(size_t)]; + if (p->MinContext->NumStats != 0) { CPpmd_State *s = Ppmd8_GetStats(p, p->MinContext); UInt32 sum; unsigned i; + UInt32 summFreq = p->MinContext->Union2.SummFreq; + + PPMD8_CORRECT_SUM_RANGE(p, summFreq) + + // RC_PRE(summFreq); + if (s->Symbol == symbol) { - RangeEnc_Encode(p, 0, s->Freq, p->MinContext->SummFreq); + + RC_EncodeFinal(0, s->Freq, summFreq) p->FoundState = s; Ppmd8_Update1_0(p); return; @@ -75,7 +116,8 @@ void Ppmd8_EncodeSymbol(CPpmd8 *p, int symbol) { if ((++s)->Symbol == symbol) { - RangeEnc_Encode(p, sum, s->Freq, p->MinContext->SummFreq); + + RC_EncodeFinal(sum, s->Freq, summFreq) p->FoundState = s; Ppmd8_Update1(p); return; @@ -84,80 +126,212 @@ void Ppmd8_EncodeSymbol(CPpmd8 *p, int symbol) } while (--i); - PPMD_SetAllBitsIn256Bytes(charMask); - MASK(s->Symbol) = 0; - i = p->MinContext->NumStats; - do { MASK((--s)->Symbol) = 0; } while (--i); - RangeEnc_Encode(p, sum, p->MinContext->SummFreq - sum, p->MinContext->SummFreq); + + RC_Encode(sum, summFreq - sum, summFreq) + + + PPMD_SetAllBitsIn256Bytes(charMask) + // MASK(s->Symbol) = 0; + // i = p->MinContext->NumStats; + // do { MASK((--s)->Symbol) = 0; } while (--i); + { + CPpmd_State *s2 = Ppmd8_GetStats(p, p->MinContext); + MASK(s->Symbol) = 0; + do + { + const unsigned sym0 = s2[0].Symbol; + const unsigned sym1 = s2[1].Symbol; + s2 += 2; + MASK(sym0) = 0; + MASK(sym1) = 0; + } + while (s2 < s); + } } else { UInt16 *prob = Ppmd8_GetBinSumm(p); CPpmd_State *s = Ppmd8Context_OneState(p->MinContext); + UInt32 pr = *prob; + const UInt32 bound = (R->Range >> 14) * pr; + pr = PPMD_UPDATE_PROB_1(pr); if (s->Symbol == symbol) { - RangeEnc_EncodeBit_0(p, *prob); - *prob = (UInt16)PPMD_UPDATE_PROB_0(*prob); - p->FoundState = s; - Ppmd8_UpdateBin(p); + *prob = (UInt16)(pr + (1 << PPMD_INT_BITS)); + // RangeEnc_EncodeBit_0(p, bound); + R->Range = bound; + RC_NORM(R) + + // p->FoundState = s; + // Ppmd8_UpdateBin(p); + { + const unsigned freq = s->Freq; + CPpmd8_Context *c = CTX(SUCCESSOR(s)); + p->FoundState = s; + p->PrevSuccess = 1; + p->RunLength++; + s->Freq = (Byte)(freq + (freq < 196)); // Ppmd8 (196) + // NextContext(p); + if (p->OrderFall == 0 && (const Byte *)c >= p->UnitsStart) + p->MaxContext = p->MinContext = c; + else + Ppmd8_UpdateModel(p); + } return; } - else - { - RangeEnc_EncodeBit_1(p, *prob); - *prob = (UInt16)PPMD_UPDATE_PROB_1(*prob); - p->InitEsc = PPMD8_kExpEscape[*prob >> 10]; - PPMD_SetAllBitsIn256Bytes(charMask); - MASK(s->Symbol) = 0; - p->PrevSuccess = 0; - } + + *prob = (UInt16)pr; + p->InitEsc = p->ExpEscape[pr >> 10]; + // RangeEnc_EncodeBit_1(p, bound); + R->Low += bound; + R->Range = (R->Range & ~((UInt32)PPMD_BIN_SCALE - 1)) - bound; + RC_NORM_LOCAL(R) + + PPMD_SetAllBitsIn256Bytes(charMask) + MASK(s->Symbol) = 0; + p->PrevSuccess = 0; } + for (;;) { - UInt32 escFreq; CPpmd_See *see; CPpmd_State *s; - UInt32 sum; - unsigned i, numMasked = p->MinContext->NumStats; + UInt32 sum, escFreq; + CPpmd8_Context *mc; + unsigned i, numMasked; + + RC_NORM_REMOTE(p) + + mc = p->MinContext; + numMasked = mc->NumStats; + do { p->OrderFall++; - if (!p->MinContext->Suffix) + if (!mc->Suffix) return; /* EndMarker (symbol = -1) */ - p->MinContext = Ppmd8_GetContext(p, p->MinContext->Suffix); + mc = Ppmd8_GetContext(p, mc->Suffix); + } - while (p->MinContext->NumStats == numMasked); + while (mc->NumStats == numMasked); + p->MinContext = mc; + see = Ppmd8_MakeEscFreq(p, numMasked, &escFreq); + + + + + + + + + + + + + + + + + + + + + + + + s = Ppmd8_GetStats(p, p->MinContext); sum = 0; - i = p->MinContext->NumStats + 1; + i = (unsigned)p->MinContext->NumStats + 1; + do { - int cur = s->Symbol; - if (cur == symbol) + const unsigned cur = s->Symbol; + if ((int)cur == symbol) { - UInt32 low = sum; - CPpmd_State *s1 = s; - do + const UInt32 low = sum; + const UInt32 freq = s->Freq; + unsigned num2; + + Ppmd_See_UPDATE(see) + p->FoundState = s; + sum += escFreq; + + num2 = i / 2; + i &= 1; + sum += freq & (0 - (UInt32)i); + if (num2 != 0) { - sum += (s->Freq & (int)(MASK(s->Symbol))); - s++; + s += i; + do + { + const unsigned sym0 = s[0].Symbol; + const unsigned sym1 = s[1].Symbol; + s += 2; + sum += (s[-2].Freq & (unsigned)(MASK(sym0))); + sum += (s[-1].Freq & (unsigned)(MASK(sym1))); + } + while (--num2); } - while (--i); - RangeEnc_Encode(p, low, s1->Freq, sum + escFreq); - Ppmd_See_Update(see); - p->FoundState = s1; + + PPMD8_CORRECT_SUM_RANGE(p, sum) + + RC_EncodeFinal(low, freq, sum) Ppmd8_Update2(p); return; } - sum += (s->Freq & (int)(MASK(cur))); - MASK(cur) = 0; + sum += (s->Freq & (unsigned)(MASK(cur))); s++; } while (--i); - RangeEnc_Encode(p, sum, escFreq, sum + escFreq); - see->Summ = (UInt16)(see->Summ + sum + escFreq); + { + UInt32 total = sum + escFreq; + see->Summ = (UInt16)(see->Summ + total); + PPMD8_CORRECT_SUM_RANGE(p, total) + + RC_Encode(sum, total - sum, total) + } + + { + const CPpmd_State *s2 = Ppmd8_GetStats(p, p->MinContext); + s--; + MASK(s->Symbol) = 0; + do + { + const unsigned sym0 = s2[0].Symbol; + const unsigned sym1 = s2[1].Symbol; + s2 += 2; + MASK(sym0) = 0; + MASK(sym1) = 0; + } + while (s2 < s); + } } } + + + + + + + + + +#undef kTop +#undef kBot +#undef WRITE_BYTE +#undef RC_NORM_BASE +#undef RC_NORM_1 +#undef RC_NORM +#undef RC_NORM_LOCAL +#undef RC_NORM_REMOTE +#undef R +#undef RC_Encode +#undef RC_EncodeFinal + +#undef CTX +#undef SUCCESSOR +#undef MASK diff --git a/src/plugins/7zip/7za/c/Precomp.h b/src/plugins/7zip/7za/c/Precomp.h index e8ff8b40..83b720e1 100644 --- a/src/plugins/7zip/7za/c/Precomp.h +++ b/src/plugins/7zip/7za/c/Precomp.h @@ -1,10 +1,127 @@ -/* Precomp.h -- StdAfx -2013-11-12 : Igor Pavlov : Public domain */ +/* Precomp.h -- precompilation file +: Igor Pavlov : Public domain */ -#ifndef __7Z_PRECOMP_H -#define __7Z_PRECOMP_H +#ifndef ZIP7_INC_PRECOMP_H +#define ZIP7_INC_PRECOMP_H + +/* + this file must be included before another *.h files and before . + this file is included from the following files: + C\*.c + C\Util\*\Precomp.h <- C\Util\*\*.c + CPP\Common\Common.h <- *\StdAfx.h <- *\*.cpp + + this file can set the following macros: + Z7_LARGE_PAGES 1 + Z7_LONG_PATH 1 + Z7_WIN32_WINNT_MIN 0x0500 (or higher) : we require at least win2000+ for 7-Zip + _WIN32_WINNT 0x0500 (or higher) + WINVER _WIN32_WINNT + UNICODE 1 + _UNICODE 1 +*/ #include "Compiler.h" -/* #include "7zTypes.h" */ + +#ifdef _MSC_VER +// #pragma warning(disable : 4206) // nonstandard extension used : translation unit is empty +#if _MSC_VER >= 1912 +// #pragma warning(disable : 5039) // pointer or reference to potentially throwing function passed to 'extern "C"' function under - EHc.Undefined behavior may occur if this function throws an exception. +#endif +#endif + +/* +// for debug: +#define UNICODE 1 +#define _UNICODE 1 +#define _WIN32_WINNT 0x0500 // win2000 +#ifndef WINVER + #define WINVER _WIN32_WINNT +#endif +*/ + +#ifndef Z7_LARGE_PAGES +#if !defined(Z7_NO_LARGE_PAGES) && !defined(UNDER_CE) +#define Z7_LARGE_PAGES 1 +#endif +#endif + +#ifdef _WIN32 +/* + this "Precomp.h" file must be included before , + if we want to define _WIN32_WINNT before . +*/ + +#ifndef Z7_LONG_PATH +#ifndef Z7_NO_LONG_PATH +#define Z7_LONG_PATH 1 +#endif +#endif + +#ifndef Z7_DEVICE_FILE +#ifndef Z7_NO_DEVICE_FILE +// #define Z7_DEVICE_FILE 1 +#endif +#endif + +// we don't change macros if included after +#ifndef _WINDOWS_ + +#ifndef Z7_WIN32_WINNT_MIN + #if defined(_M_ARM64) || defined(__aarch64__) + // #define Z7_WIN32_WINNT_MIN 0x0a00 // win10 + #define Z7_WIN32_WINNT_MIN 0x0600 // vista + #elif defined(_M_ARM) && defined(_M_ARMT) && defined(_M_ARM_NT) + // #define Z7_WIN32_WINNT_MIN 0x0602 // win8 + #define Z7_WIN32_WINNT_MIN 0x0600 // vista + #elif defined(_M_X64) || defined(_M_AMD64) || defined(__x86_64__) || defined(_M_IA64) + #define Z7_WIN32_WINNT_MIN 0x0503 // win2003 + // #elif defined(_M_IX86) || defined(__i386__) + // #define Z7_WIN32_WINNT_MIN 0x0500 // win2000 + #else // x86 and another(old) systems + #define Z7_WIN32_WINNT_MIN 0x0500 // win2000 + // #define Z7_WIN32_WINNT_MIN 0x0502 // win2003 // for debug + #endif +#endif // Z7_WIN32_WINNT_MIN + + +#ifndef Z7_DO_NOT_DEFINE_WIN32_WINNT +#ifdef _WIN32_WINNT + // #error Stop_Compiling_Bad_WIN32_WINNT +#else + #ifndef Z7_NO_DEFINE_WIN32_WINNT +Z7_DIAGNOSTIC_IGNORE_BEGIN_RESERVED_MACRO_IDENTIFIER + #define _WIN32_WINNT Z7_WIN32_WINNT_MIN +Z7_DIAGNOSTIC_IGNORE_END_RESERVED_MACRO_IDENTIFIER + #endif +#endif // _WIN32_WINNT + +#ifndef WINVER + #define WINVER _WIN32_WINNT +#endif +#endif // Z7_DO_NOT_DEFINE_WIN32_WINNT + + +#ifndef _MBCS +#ifndef Z7_NO_UNICODE +// UNICODE and _UNICODE are used by and by 7-zip code. + +#ifndef UNICODE +#define UNICODE 1 +#endif + +#ifndef _UNICODE +Z7_DIAGNOSTIC_IGNORE_BEGIN_RESERVED_MACRO_IDENTIFIER +#define _UNICODE 1 +Z7_DIAGNOSTIC_IGNORE_END_RESERVED_MACRO_IDENTIFIER +#endif + +#endif // Z7_NO_UNICODE +#endif // _MBCS +#endif // _WINDOWS_ + +// #include "7zWindows.h" + +#endif // _WIN32 #endif diff --git a/src/plugins/7zip/7za/c/RotateDefs.h b/src/plugins/7zip/7za/c/RotateDefs.h index 8f01d1a6..c16b4f8e 100644 --- a/src/plugins/7zip/7za/c/RotateDefs.h +++ b/src/plugins/7zip/7za/c/RotateDefs.h @@ -1,14 +1,14 @@ /* RotateDefs.h -- Rotate functions -2015-03-25 : Igor Pavlov : Public domain */ +2023-06-18 : Igor Pavlov : Public domain */ -#ifndef __ROTATE_DEFS_H -#define __ROTATE_DEFS_H +#ifndef ZIP7_INC_ROTATE_DEFS_H +#define ZIP7_INC_ROTATE_DEFS_H #ifdef _MSC_VER #include -/* don't use _rotl with MINGW. It can insert slow call to function. */ +/* don't use _rotl with old MINGW. It can insert slow call to function. */ /* #if (_MSC_VER >= 1200) */ #pragma intrinsic(_rotl) @@ -18,12 +18,32 @@ #define rotlFixed(x, n) _rotl((x), (n)) #define rotrFixed(x, n) _rotr((x), (n)) +#if (_MSC_VER >= 1300) +#define Z7_ROTL64(x, n) _rotl64((x), (n)) +#define Z7_ROTR64(x, n) _rotr64((x), (n)) +#else +#define Z7_ROTL64(x, n) (((x) << (n)) | ((x) >> (64 - (n)))) +#define Z7_ROTR64(x, n) (((x) >> (n)) | ((x) << (64 - (n)))) +#endif + #else /* new compilers can translate these macros to fast commands. */ +#if defined(__clang__) && (__clang_major__ >= 4) \ + || defined(__GNUC__) && (__GNUC__ >= 5) +/* GCC 4.9.0 and clang 3.5 can recognize more correct version: */ +#define rotlFixed(x, n) (((x) << (n)) | ((x) >> (-(n) & 31))) +#define rotrFixed(x, n) (((x) >> (n)) | ((x) << (-(n) & 31))) +#define Z7_ROTL64(x, n) (((x) << (n)) | ((x) >> (-(n) & 63))) +#define Z7_ROTR64(x, n) (((x) >> (n)) | ((x) << (-(n) & 63))) +#else +/* for old GCC / clang: */ #define rotlFixed(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) #define rotrFixed(x, n) (((x) >> (n)) | ((x) << (32 - (n)))) +#define Z7_ROTL64(x, n) (((x) << (n)) | ((x) >> (64 - (n)))) +#define Z7_ROTR64(x, n) (((x) >> (n)) | ((x) << (64 - (n)))) +#endif #endif diff --git a/src/plugins/7zip/7za/c/Sha1.c b/src/plugins/7zip/7za/c/Sha1.c index 55c1c63c..4ca21d7d 100644 --- a/src/plugins/7zip/7za/c/Sha1.c +++ b/src/plugins/7zip/7za/c/Sha1.c @@ -1,340 +1,441 @@ /* Sha1.c -- SHA-1 Hash -2016-05-20 : Igor Pavlov : Public domain +: Igor Pavlov : Public domain This code is based on public domain code of Steve Reid from Wei Dai's Crypto++ library. */ #include "Precomp.h" #include -#include "CpuArch.h" -#include "RotateDefs.h" #include "Sha1.h" +#include "RotateDefs.h" +#include "CpuArch.h" -// define it for speed optimization -// #define _SHA1_UNROLL +#ifdef MY_CPU_X86_OR_AMD64 + #if defined(Z7_LLVM_CLANG_VERSION) && (Z7_LLVM_CLANG_VERSION >= 30800) \ + || defined(Z7_APPLE_CLANG_VERSION) && (Z7_APPLE_CLANG_VERSION >= 50100) \ + || defined(Z7_GCC_VERSION) && (Z7_GCC_VERSION >= 40900) \ + || defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 1600) \ + || defined(_MSC_VER) && (_MSC_VER >= 1200) + #define Z7_COMPILER_SHA1_SUPPORTED + #endif +#elif defined(MY_CPU_ARM_OR_ARM64) && defined(MY_CPU_LE) \ + && (!defined(Z7_MSC_VER_ORIGINAL) || (_MSC_VER >= 1929) && (_MSC_FULL_VER >= 192930037)) + #if defined(__ARM_FEATURE_SHA2) \ + || defined(__ARM_FEATURE_CRYPTO) + #define Z7_COMPILER_SHA1_SUPPORTED + #else + #if defined(MY_CPU_ARM64) \ + || defined(__ARM_ARCH) && (__ARM_ARCH >= 4) \ + || defined(Z7_MSC_VER_ORIGINAL) + #if defined(__ARM_FP) && \ + ( defined(Z7_CLANG_VERSION) && (Z7_CLANG_VERSION >= 30800) \ + || defined(__GNUC__) && (__GNUC__ >= 6) \ + ) \ + || defined(Z7_MSC_VER_ORIGINAL) && (_MSC_VER >= 1910) + #if defined(MY_CPU_ARM64) \ + || !defined(Z7_CLANG_VERSION) \ + || defined(__ARM_NEON) && \ + (Z7_CLANG_VERSION < 170000 || \ + Z7_CLANG_VERSION > 170001) + #define Z7_COMPILER_SHA1_SUPPORTED + #endif + #endif + #endif + #endif +#endif -#ifdef _SHA1_UNROLL - #define kNumW 16 - #define WW(i) W[(i)&15] +void Z7_FASTCALL Sha1_UpdateBlocks(UInt32 state[5], const Byte *data, size_t numBlocks); + +#ifdef Z7_COMPILER_SHA1_SUPPORTED + void Z7_FASTCALL Sha1_UpdateBlocks_HW(UInt32 state[5], const Byte *data, size_t numBlocks); + + static SHA1_FUNC_UPDATE_BLOCKS g_SHA1_FUNC_UPDATE_BLOCKS = Sha1_UpdateBlocks; + static SHA1_FUNC_UPDATE_BLOCKS g_SHA1_FUNC_UPDATE_BLOCKS_HW; + + #define SHA1_UPDATE_BLOCKS(p) p->v.vars.func_UpdateBlocks +#else + #define SHA1_UPDATE_BLOCKS(p) Sha1_UpdateBlocks +#endif + + +BoolInt Sha1_SetFunction(CSha1 *p, unsigned algo) +{ + SHA1_FUNC_UPDATE_BLOCKS func = Sha1_UpdateBlocks; + + #ifdef Z7_COMPILER_SHA1_SUPPORTED + if (algo != SHA1_ALGO_SW) + { + if (algo == SHA1_ALGO_DEFAULT) + func = g_SHA1_FUNC_UPDATE_BLOCKS; + else + { + if (algo != SHA1_ALGO_HW) + return False; + func = g_SHA1_FUNC_UPDATE_BLOCKS_HW; + if (!func) + return False; + } + } + #else + if (algo > 1) + return False; + #endif + + p->v.vars.func_UpdateBlocks = func; + return True; +} + + +/* define it for speed optimization */ +// #define Z7_SHA1_UNROLL + +// allowed unroll steps: (1, 2, 4, 5, 20) + +#undef Z7_SHA1_BIG_W +#ifdef Z7_SHA1_UNROLL + #define STEP_PRE 20 + #define STEP_MAIN 20 #else + #define Z7_SHA1_BIG_W + #define STEP_PRE 5 + #define STEP_MAIN 5 +#endif + + +#ifdef Z7_SHA1_BIG_W #define kNumW 80 - #define WW(i) W[i] + #define w(i) W[i] +#else + #define kNumW 16 + #define w(i) W[(i)&15] #endif -#define w0(i) (W[i] = data[i]) +#define w0(i) (W[i] = GetBe32(data + (size_t)(i) * 4)) +#define w1(i) (w(i) = rotlFixed(w((size_t)(i)-3) ^ w((size_t)(i)-8) ^ w((size_t)(i)-14) ^ w((size_t)(i)-16), 1)) -#define w1(i) (WW(i) = rotlFixed(WW((i)-3) ^ WW((i)-8) ^ WW((i)-14) ^ WW((i)-16), 1)) +#define f0(x,y,z) ( 0x5a827999 + (z^(x&(y^z))) ) +#define f1(x,y,z) ( 0x6ed9eba1 + (x^y^z) ) +#define f2(x,y,z) ( 0x8f1bbcdc + ((x&y)|(z&(x|y))) ) +#define f3(x,y,z) ( 0xca62c1d6 + (x^y^z) ) -#define f1(x,y,z) (z^(x&(y^z))) -#define f2(x,y,z) (x^y^z) -#define f3(x,y,z) ((x&y)|(z&(x|y))) -#define f4(x,y,z) (x^y^z) +/* +#define T1(fx, ww) \ + tmp = e + fx(b,c,d) + ww + rotlFixed(a, 5); \ + e = d; \ + d = c; \ + c = rotlFixed(b, 30); \ + b = a; \ + a = tmp; \ +*/ -#define RK(a,b,c,d,e, fx, w, k) e += fx(b,c,d) + w + k + rotlFixed(a,5); b = rotlFixed(b,30); +#define T5(a,b,c,d,e, fx, ww) \ + e += fx(b,c,d) + ww + rotlFixed(a, 5); \ + b = rotlFixed(b, 30); \ -#define R0(a,b,c,d,e, i) RK(a,b,c,d,e, f1, w0(i), 0x5A827999) -#define R1(a,b,c,d,e, i) RK(a,b,c,d,e, f1, w1(i), 0x5A827999) -#define R2(a,b,c,d,e, i) RK(a,b,c,d,e, f2, w1(i), 0x6ED9EBA1) -#define R3(a,b,c,d,e, i) RK(a,b,c,d,e, f3, w1(i), 0x8F1BBCDC) -#define R4(a,b,c,d,e, i) RK(a,b,c,d,e, f4, w1(i), 0xCA62C1D6) -#define RX_1_4(rx1, rx4, i) \ - rx1(a,b,c,d,e, i); \ - rx4(e,a,b,c,d, i+1); \ - rx4(d,e,a,b,c, i+2); \ - rx4(c,d,e,a,b, i+3); \ - rx4(b,c,d,e,a, i+4); \ +/* +#define R1(i, fx, wx) \ + T1 ( fx, wx(i)); \ -#define RX_5(rx, i) RX_1_4(rx, rx, i); +#define R2(i, fx, wx) \ + R1 ( (i) , fx, wx); \ + R1 ( (i) + 1, fx, wx); \ -#ifdef _SHA1_UNROLL +#define R4(i, fx, wx) \ + R2 ( (i) , fx, wx); \ + R2 ( (i) + 2, fx, wx); \ +*/ - #define RX_15 \ - RX_5(R0, 0); \ - RX_5(R0, 5); \ - RX_5(R0, 10); +#define M5(i, fx, wx0, wx1) \ + T5 ( a,b,c,d,e, fx, wx0((i) ) ) \ + T5 ( e,a,b,c,d, fx, wx1((i)+1) ) \ + T5 ( d,e,a,b,c, fx, wx1((i)+2) ) \ + T5 ( c,d,e,a,b, fx, wx1((i)+3) ) \ + T5 ( b,c,d,e,a, fx, wx1((i)+4) ) \ + +#define R5(i, fx, wx) \ + M5 ( i, fx, wx, wx) \ + + +#if STEP_PRE > 5 + + #define R20_START \ + R5 ( 0, f0, w0) \ + R5 ( 5, f0, w0) \ + R5 ( 10, f0, w0) \ + M5 ( 15, f0, w0, w1) \ - #define RX_20(rx, i) \ - RX_5(rx, i); \ - RX_5(rx, i + 5); \ - RX_5(rx, i + 10); \ - RX_5(rx, i + 15); + #elif STEP_PRE == 5 + + #define R20_START \ + { size_t i; for (i = 0; i < 15; i += STEP_PRE) \ + { R5(i, f0, w0) } } \ + M5 ( 15, f0, w0, w1) \ #else - -#define RX_15 { unsigned i; for (i = 0; i < 15; i += 5) { RX_5(R0, i); } } -#define RX_20(rx, ii) { unsigned i; i = ii; for (; i < ii + 20; i += 5) { RX_5(rx, i); } } + + #if STEP_PRE == 1 + #define R_PRE R1 + #elif STEP_PRE == 2 + #define R_PRE R2 + #elif STEP_PRE == 4 + #define R_PRE R4 + #endif + + #define R20_START \ + { size_t i; for (i = 0; i < 16; i += STEP_PRE) \ + { R_PRE(i, f0, w0) } } \ + R4 ( 16, f0, w1) \ #endif -void Sha1_Init(CSha1 *p) + +#if STEP_MAIN > 5 + + #define R20(ii, fx) \ + R5 ( (ii) , fx, w1) \ + R5 ( (ii) + 5 , fx, w1) \ + R5 ( (ii) + 10, fx, w1) \ + R5 ( (ii) + 15, fx, w1) \ + +#else + + #if STEP_MAIN == 1 + #define R_MAIN R1 + #elif STEP_MAIN == 2 + #define R_MAIN R2 + #elif STEP_MAIN == 4 + #define R_MAIN R4 + #elif STEP_MAIN == 5 + #define R_MAIN R5 + #endif + + #define R20(ii, fx) \ + { size_t i; for (i = (ii); i < (ii) + 20; i += STEP_MAIN) \ + { R_MAIN(i, fx, w1) } } \ + +#endif + + + +void Sha1_InitState(CSha1 *p) { + p->v.vars.count = 0; p->state[0] = 0x67452301; p->state[1] = 0xEFCDAB89; p->state[2] = 0x98BADCFE; p->state[3] = 0x10325476; p->state[4] = 0xC3D2E1F0; - p->count = 0; } -void Sha1_GetBlockDigest(CSha1 *p, const UInt32 *data, UInt32 *destDigest) +void Sha1_Init(CSha1 *p) { - UInt32 a, b, c, d, e; - UInt32 W[kNumW]; - - a = p->state[0]; - b = p->state[1]; - c = p->state[2]; - d = p->state[3]; - e = p->state[4]; - - RX_15 - - RX_1_4(R0, R1, 15); - - RX_20(R2, 20); - RX_20(R3, 40); - RX_20(R4, 60); - - destDigest[0] = p->state[0] + a; - destDigest[1] = p->state[1] + b; - destDigest[2] = p->state[2] + c; - destDigest[3] = p->state[3] + d; - destDigest[4] = p->state[4] + e; + p->v.vars.func_UpdateBlocks = + #ifdef Z7_COMPILER_SHA1_SUPPORTED + g_SHA1_FUNC_UPDATE_BLOCKS; + #else + NULL; + #endif + Sha1_InitState(p); } -void Sha1_UpdateBlock_Rar(CSha1 *p, UInt32 *data, int returnRes) + +Z7_NO_INLINE +void Z7_FASTCALL Sha1_UpdateBlocks(UInt32 state[5], const Byte *data, size_t numBlocks) { UInt32 a, b, c, d, e; UInt32 W[kNumW]; - a = p->state[0]; - b = p->state[1]; - c = p->state[2]; - d = p->state[3]; - e = p->state[4]; - - RX_15 - - RX_1_4(R0, R1, 15); - - RX_20(R2, 20); - RX_20(R3, 40); - RX_20(R4, 60); + if (numBlocks == 0) + return; - p->state[0] += a; - p->state[1] += b; - p->state[2] += c; - p->state[3] += d; - p->state[4] += e; + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; - if (returnRes) + do { - unsigned i; - for (i = 0 ; i < SHA1_NUM_BLOCK_WORDS; i++) - data[i] = W[kNumW - SHA1_NUM_BLOCK_WORDS + i]; + #if STEP_PRE < 5 || STEP_MAIN < 5 + UInt32 tmp; + #endif + + R20_START + R20(20, f1) + R20(40, f2) + R20(60, f3) + + a += state[0]; + b += state[1]; + c += state[2]; + d += state[3]; + e += state[4]; + + state[0] = a; + state[1] = b; + state[2] = c; + state[3] = d; + state[4] = e; + + data += SHA1_BLOCK_SIZE; } + while (--numBlocks); } -#define Sha1_UpdateBlock(p) Sha1_GetBlockDigest(p, p->buffer, p->state) + +#define Sha1_UpdateBlock(p) SHA1_UPDATE_BLOCKS(p)(p->state, p->buffer, 1) void Sha1_Update(CSha1 *p, const Byte *data, size_t size) { - unsigned pos, pos2; if (size == 0) return; - pos = (unsigned)p->count & 0x3F; - p->count += size; - pos2 = pos & 3; - pos >>= 2; - - if (pos2 != 0) { - UInt32 w; - pos2 = (3 - pos2) * 8; - w = ((UInt32)*data++) << pos2; - if (--size && pos2) + const unsigned pos = (unsigned)p->v.vars.count & (SHA1_BLOCK_SIZE - 1); + const unsigned num = SHA1_BLOCK_SIZE - pos; + p->v.vars.count += size; + if (num > size) { - pos2 -= 8; - w |= ((UInt32)*data++) << pos2; - if (--size && pos2) - { - pos2 -= 8; - w |= ((UInt32)*data++) << pos2; - size--; - } + memcpy(p->buffer + pos, data, size); + return; } - p->buffer[pos] |= w; - if (pos2 == 0) - pos++; - } - - for (;;) - { - if (pos == SHA1_NUM_BLOCK_WORDS) + if (pos != 0) { - for (;;) - { - unsigned i; - Sha1_UpdateBlock(p); - if (size < SHA1_BLOCK_SIZE) - break; - size -= SHA1_BLOCK_SIZE; - for (i = 0; i < SHA1_NUM_BLOCK_WORDS; i += 2) - { - p->buffer[i ] = GetBe32(data); - p->buffer[i + 1] = GetBe32(data + 4); - data += 8; - } - } - pos = 0; + size -= num; + memcpy(p->buffer + pos, data, num); + data += num; + Sha1_UpdateBlock(p); } - if (size < 4) - break; - - p->buffer[pos] = GetBe32(data); - data += 4; - size -= 4; - pos++; } - - if (size != 0) { - UInt32 w = ((UInt32)data[0]) << 24; - if (size > 1) - { - w |= ((UInt32)data[1]) << 16; - if (size > 2) - w |= ((UInt32)data[2]) << 8; - } - p->buffer[pos] = w; + const size_t numBlocks = size >> 6; + // if (numBlocks) + SHA1_UPDATE_BLOCKS(p)(p->state, data, numBlocks); + size &= SHA1_BLOCK_SIZE - 1; + if (size == 0) + return; + data += (numBlocks << 6); + memcpy(p->buffer, data, size); } } -void Sha1_Update_Rar(CSha1 *p, Byte *data, size_t size /* , int rar350Mode */) -{ - int returnRes = False; - - unsigned pos = (unsigned)p->count & 0x3F; - p->count += size; - - while (size--) - { - unsigned pos2 = (pos & 3); - UInt32 v = ((UInt32)*data++) << (8 * (3 - pos2)); - UInt32 *ref = &(p->buffer[pos >> 2]); - pos++; - if (pos2 == 0) - { - *ref = v; - continue; - } - *ref |= v; - - if (pos == SHA1_BLOCK_SIZE) - { - pos = 0; - Sha1_UpdateBlock_Rar(p, p->buffer, returnRes); - if (returnRes) - { - unsigned i; - for (i = 0; i < SHA1_NUM_BLOCK_WORDS; i++) - { - UInt32 d = p->buffer[i]; - Byte *prev = data + i * 4 - SHA1_BLOCK_SIZE; - SetUi32(prev, d); - } - } - // returnRes = rar350Mode; - returnRes = True; - } - } -} void Sha1_Final(CSha1 *p, Byte *digest) { - unsigned pos = (unsigned)p->count & 0x3F; - unsigned pos2 = (pos & 3); - UInt64 numBits; - UInt32 w; - unsigned i; - - pos >>= 2; - - w = 0; - if (pos2 != 0) - w = p->buffer[pos]; - p->buffer[pos++] = w | (((UInt32)0x80000000) >> (8 * pos2)); - - while (pos != (SHA1_NUM_BLOCK_WORDS - 2)) + unsigned pos = (unsigned)p->v.vars.count & (SHA1_BLOCK_SIZE - 1); + p->buffer[pos++] = 0x80; + if (pos > (SHA1_BLOCK_SIZE - 4 * 2)) { - pos &= 0xF; - if (pos == 0) - Sha1_UpdateBlock(p); - p->buffer[pos++] = 0; + while (pos != SHA1_BLOCK_SIZE) { p->buffer[pos++] = 0; } + // memset(&p->buf.buffer[pos], 0, SHA1_BLOCK_SIZE - pos); + Sha1_UpdateBlock(p); + pos = 0; } - - numBits = (p->count << 3); - p->buffer[SHA1_NUM_BLOCK_WORDS - 2] = (UInt32)(numBits >> 32); - p->buffer[SHA1_NUM_BLOCK_WORDS - 1] = (UInt32)(numBits); - Sha1_UpdateBlock(p); - - for (i = 0; i < SHA1_NUM_DIGEST_WORDS; i++) + memset(&p->buffer[pos], 0, (SHA1_BLOCK_SIZE - 4 * 2) - pos); { - UInt32 v = p->state[i]; - SetBe32(digest, v); - digest += 4; + const UInt64 numBits = p->v.vars.count << 3; + SetBe32(p->buffer + SHA1_BLOCK_SIZE - 4 * 2, (UInt32)(numBits >> 32)) + SetBe32(p->buffer + SHA1_BLOCK_SIZE - 4 * 1, (UInt32)(numBits)) } + Sha1_UpdateBlock(p); - Sha1_Init(p); + SetBe32(digest, p->state[0]) + SetBe32(digest + 4, p->state[1]) + SetBe32(digest + 8, p->state[2]) + SetBe32(digest + 12, p->state[3]) + SetBe32(digest + 16, p->state[4]) + + Sha1_InitState(p); } -void Sha1_32_PrepareBlock(const CSha1 *p, UInt32 *block, unsigned size) +void Sha1_PrepareBlock(const CSha1 *p, Byte *block, unsigned size) { - const UInt64 numBits = (p->count + size) << 5; - block[SHA1_NUM_BLOCK_WORDS - 2] = (UInt32)(numBits >> 32); - block[SHA1_NUM_BLOCK_WORDS - 1] = (UInt32)(numBits); - block[size++] = 0x80000000; - while (size != (SHA1_NUM_BLOCK_WORDS - 2)) - block[size++] = 0; -} - -void Sha1_32_Update(CSha1 *p, const UInt32 *data, size_t size) -{ - unsigned pos = (unsigned)p->count & 0xF; - p->count += size; - while (size--) + const UInt64 numBits = (p->v.vars.count + size) << 3; + SetBe32(&((UInt32 *)(void *)block)[SHA1_NUM_BLOCK_WORDS - 2], (UInt32)(numBits >> 32)) + SetBe32(&((UInt32 *)(void *)block)[SHA1_NUM_BLOCK_WORDS - 1], (UInt32)(numBits)) + // SetBe32((UInt32 *)(block + size), 0x80000000); + SetUi32((UInt32 *)(void *)(block + size), 0x80) + size += 4; + while (size != (SHA1_NUM_BLOCK_WORDS - 2) * 4) { - p->buffer[pos++] = *data++; - if (pos == SHA1_NUM_BLOCK_WORDS) - { - pos = 0; - Sha1_UpdateBlock(p); - } + *((UInt32 *)(void *)(block + size)) = 0; + size += 4; } } -void Sha1_32_Final(CSha1 *p, UInt32 *digest) +void Sha1_GetBlockDigest(const CSha1 *p, const Byte *data, Byte *destDigest) { - UInt64 numBits; - unsigned pos = (unsigned)p->count & 0xF; - p->buffer[pos++] = 0x80000000; + MY_ALIGN (16) + UInt32 st[SHA1_NUM_DIGEST_WORDS]; + + st[0] = p->state[0]; + st[1] = p->state[1]; + st[2] = p->state[2]; + st[3] = p->state[3]; + st[4] = p->state[4]; + + SHA1_UPDATE_BLOCKS(p)(st, data, 1); - while (pos != (SHA1_NUM_BLOCK_WORDS - 2)) + SetBe32(destDigest + 0 , st[0]) + SetBe32(destDigest + 1 * 4, st[1]) + SetBe32(destDigest + 2 * 4, st[2]) + SetBe32(destDigest + 3 * 4, st[3]) + SetBe32(destDigest + 4 * 4, st[4]) +} + + +void Sha1Prepare(void) +{ +#ifdef Z7_COMPILER_SHA1_SUPPORTED + SHA1_FUNC_UPDATE_BLOCKS f, f_hw; + f = Sha1_UpdateBlocks; + f_hw = NULL; +#ifdef MY_CPU_X86_OR_AMD64 + if (CPU_IsSupported_SHA() + && CPU_IsSupported_SSSE3() + ) +#else + if (CPU_IsSupported_SHA1()) +#endif { - pos &= 0xF; - if (pos == 0) - Sha1_UpdateBlock(p); - p->buffer[pos++] = 0; + // printf("\n========== HW SHA1 ======== \n"); +#if 1 && defined(MY_CPU_ARM_OR_ARM64) && defined(Z7_MSC_VER_ORIGINAL) && (_MSC_FULL_VER < 192930037) + /* there was bug in MSVC compiler for ARM64 -O2 before version VS2019 16.10 (19.29.30037). + It generated incorrect SHA-1 code. */ + #pragma message("== SHA1 code can work incorrectly with this compiler") + #error Stop_Compiling_MSC_Compiler_BUG_SHA1 +#endif + { + f = f_hw = Sha1_UpdateBlocks_HW; + } } - - numBits = (p->count << 5); - p->buffer[SHA1_NUM_BLOCK_WORDS - 2] = (UInt32)(numBits >> 32); - p->buffer[SHA1_NUM_BLOCK_WORDS - 1] = (UInt32)(numBits); - - Sha1_GetBlockDigest(p, p->buffer, digest); - - Sha1_Init(p); + g_SHA1_FUNC_UPDATE_BLOCKS = f; + g_SHA1_FUNC_UPDATE_BLOCKS_HW = f_hw; +#endif } + +#undef kNumW +#undef w +#undef w0 +#undef w1 +#undef f0 +#undef f1 +#undef f2 +#undef f3 +#undef T1 +#undef T5 +#undef M5 +#undef R1 +#undef R2 +#undef R4 +#undef R5 +#undef R20_START +#undef R_PRE +#undef R_MAIN +#undef STEP_PRE +#undef STEP_MAIN +#undef Z7_SHA1_BIG_W +#undef Z7_SHA1_UNROLL +#undef Z7_COMPILER_SHA1_SUPPORTED diff --git a/src/plugins/7zip/7za/c/Sha1.h b/src/plugins/7zip/7za/c/Sha1.h index aa22ec36..529be4d8 100644 --- a/src/plugins/7zip/7za/c/Sha1.h +++ b/src/plugins/7zip/7za/c/Sha1.h @@ -1,8 +1,8 @@ /* Sha1.h -- SHA-1 Hash -2016-05-20 : Igor Pavlov : Public domain */ +: Igor Pavlov : Public domain */ -#ifndef __7Z_SHA1_H -#define __7Z_SHA1_H +#ifndef ZIP7_INC_SHA1_H +#define ZIP7_INC_SHA1_H #include "7zTypes.h" @@ -14,24 +14,72 @@ EXTERN_C_BEGIN #define SHA1_BLOCK_SIZE (SHA1_NUM_BLOCK_WORDS * 4) #define SHA1_DIGEST_SIZE (SHA1_NUM_DIGEST_WORDS * 4) + + + +typedef void (Z7_FASTCALL *SHA1_FUNC_UPDATE_BLOCKS)(UInt32 state[5], const Byte *data, size_t numBlocks); + +/* + if (the system supports different SHA1 code implementations) + { + (CSha1::func_UpdateBlocks) will be used + (CSha1::func_UpdateBlocks) can be set by + Sha1_Init() - to default (fastest) + Sha1_SetFunction() - to any algo + } + else + { + (CSha1::func_UpdateBlocks) is ignored. + } +*/ + typedef struct { + union + { + struct + { + SHA1_FUNC_UPDATE_BLOCKS func_UpdateBlocks; + UInt64 count; + } vars; + UInt64 _pad_64bit[4]; + void *_pad_align_ptr[2]; + } v; UInt32 state[SHA1_NUM_DIGEST_WORDS]; - UInt64 count; - UInt32 buffer[SHA1_NUM_BLOCK_WORDS]; + UInt32 _pad_3[3]; + Byte buffer[SHA1_BLOCK_SIZE]; } CSha1; -void Sha1_Init(CSha1 *p); -void Sha1_GetBlockDigest(CSha1 *p, const UInt32 *data, UInt32 *destDigest); +#define SHA1_ALGO_DEFAULT 0 +#define SHA1_ALGO_SW 1 +#define SHA1_ALGO_HW 2 + +/* +Sha1_SetFunction() +return: + 0 - (algo) value is not supported, and func_UpdateBlocks was not changed + 1 - func_UpdateBlocks was set according (algo) value. +*/ + +BoolInt Sha1_SetFunction(CSha1 *p, unsigned algo); + +void Sha1_InitState(CSha1 *p); +void Sha1_Init(CSha1 *p); void Sha1_Update(CSha1 *p, const Byte *data, size_t size); void Sha1_Final(CSha1 *p, Byte *digest); -void Sha1_Update_Rar(CSha1 *p, Byte *data, size_t size /* , int rar350Mode */); +void Sha1_PrepareBlock(const CSha1 *p, Byte *block, unsigned size); +void Sha1_GetBlockDigest(const CSha1 *p, const Byte *data, Byte *destDigest); + +// void Z7_FASTCALL Sha1_UpdateBlocks(UInt32 state[5], const Byte *data, size_t numBlocks); + +/* +call Sha1Prepare() once at program start. +It prepares all supported implementations, and detects the fastest implementation. +*/ -void Sha1_32_PrepareBlock(const CSha1 *p, UInt32 *block, unsigned size); -void Sha1_32_Update(CSha1 *p, const UInt32 *data, size_t size); -void Sha1_32_Final(CSha1 *p, UInt32 *digest); +void Sha1Prepare(void); EXTERN_C_END diff --git a/src/plugins/7zip/7za/c/Sha1Opt.c b/src/plugins/7zip/7za/c/Sha1Opt.c new file mode 100644 index 00000000..8738b94f --- /dev/null +++ b/src/plugins/7zip/7za/c/Sha1Opt.c @@ -0,0 +1,424 @@ +/* Sha1Opt.c -- SHA-1 optimized code for SHA-1 hardware instructions +: Igor Pavlov : Public domain */ + +#include "Precomp.h" +#include "Compiler.h" +#include "CpuArch.h" + +// #define Z7_USE_HW_SHA_STUB // for debug +#ifdef MY_CPU_X86_OR_AMD64 + #if defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 1600) // fix that check + #define USE_HW_SHA + #elif defined(Z7_LLVM_CLANG_VERSION) && (Z7_LLVM_CLANG_VERSION >= 30800) \ + || defined(Z7_APPLE_CLANG_VERSION) && (Z7_APPLE_CLANG_VERSION >= 50100) \ + || defined(Z7_GCC_VERSION) && (Z7_GCC_VERSION >= 40900) + #define USE_HW_SHA + #if !defined(__INTEL_COMPILER) + // icc defines __GNUC__, but icc doesn't support __attribute__(__target__) + #if !defined(__SHA__) || !defined(__SSSE3__) + #define ATTRIB_SHA __attribute__((__target__("sha,ssse3"))) + #endif + #endif + #elif defined(_MSC_VER) + #if (_MSC_VER >= 1900) + #define USE_HW_SHA + #else + #define Z7_USE_HW_SHA_STUB + #endif + #endif +// #endif // MY_CPU_X86_OR_AMD64 +#ifndef USE_HW_SHA + // #define Z7_USE_HW_SHA_STUB // for debug +#endif + +#ifdef USE_HW_SHA + +// #pragma message("Sha1 HW") + + + + +// sse/sse2/ssse3: +#include +// sha*: +#include + +#if defined (__clang__) && defined(_MSC_VER) + #if !defined(__SHA__) + #include + #endif +#else + +#endif + +/* +SHA1 uses: +SSE2: + _mm_loadu_si128 + _mm_storeu_si128 + _mm_set_epi32 + _mm_add_epi32 + _mm_shuffle_epi32 / pshufd + _mm_xor_si128 + _mm_cvtsi128_si32 + _mm_cvtsi32_si128 +SSSE3: + _mm_shuffle_epi8 / pshufb + +SHA: + _mm_sha1* +*/ + +#define XOR_SI128(dest, src) dest = _mm_xor_si128(dest, src); +#define SHUFFLE_EPI8(dest, mask) dest = _mm_shuffle_epi8(dest, mask); +#define SHUFFLE_EPI32(dest, mask) dest = _mm_shuffle_epi32(dest, mask); +#ifdef __clang__ +#define SHA1_RNDS4_RET_TYPE_CAST (__m128i) +#else +#define SHA1_RNDS4_RET_TYPE_CAST +#endif +#define SHA1_RND4(abcd, e0, f) abcd = SHA1_RNDS4_RET_TYPE_CAST _mm_sha1rnds4_epu32(abcd, e0, f); +#define SHA1_NEXTE(e, m) e = _mm_sha1nexte_epu32(e, m); +#define ADD_EPI32(dest, src) dest = _mm_add_epi32(dest, src); +#define SHA1_MSG1(dest, src) dest = _mm_sha1msg1_epu32(dest, src); +#define SHA1_MSG2(dest, src) dest = _mm_sha1msg2_epu32(dest, src); + +#define LOAD_SHUFFLE(m, k) \ + m = _mm_loadu_si128((const __m128i *)(const void *)(data + (k) * 16)); \ + SHUFFLE_EPI8(m, mask) \ + +#define NNN(m0, m1, m2, m3) + +#define SM1(m0, m1, m2, m3) \ + SHA1_MSG1(m0, m1) \ + +#define SM2(m0, m1, m2, m3) \ + XOR_SI128(m3, m1) \ + SHA1_MSG2(m3, m2) \ + +#define SM3(m0, m1, m2, m3) \ + XOR_SI128(m3, m1) \ + SM1(m0, m1, m2, m3) \ + SHA1_MSG2(m3, m2) \ + +#define R4(k, m0, m1, m2, m3, e0, e1, OP) \ + e1 = abcd; \ + SHA1_RND4(abcd, e0, (k) / 5) \ + SHA1_NEXTE(e1, m1) \ + OP(m0, m1, m2, m3) \ + + + +#define R16(k, mx, OP0, OP1, OP2, OP3) \ + R4 ( (k)*4+0, m0,m1,m2,m3, e0,e1, OP0 ) \ + R4 ( (k)*4+1, m1,m2,m3,m0, e1,e0, OP1 ) \ + R4 ( (k)*4+2, m2,m3,m0,m1, e0,e1, OP2 ) \ + R4 ( (k)*4+3, m3,mx,m1,m2, e1,e0, OP3 ) \ + +#define PREPARE_STATE \ + SHUFFLE_EPI32 (abcd, 0x1B) \ + SHUFFLE_EPI32 (e0, 0x1B) \ + + + + + +void Z7_FASTCALL Sha1_UpdateBlocks_HW(UInt32 state[5], const Byte *data, size_t numBlocks); +#ifdef ATTRIB_SHA +ATTRIB_SHA +#endif +void Z7_FASTCALL Sha1_UpdateBlocks_HW(UInt32 state[5], const Byte *data, size_t numBlocks) +{ + const __m128i mask = _mm_set_epi32(0x00010203, 0x04050607, 0x08090a0b, 0x0c0d0e0f); + + + __m128i abcd, e0; + + if (numBlocks == 0) + return; + + abcd = _mm_loadu_si128((const __m128i *) (const void *) &state[0]); // dbca + e0 = _mm_cvtsi32_si128((int)state[4]); // 000e + + PREPARE_STATE + + do + { + __m128i abcd_save, e2; + __m128i m0, m1, m2, m3; + __m128i e1; + + + abcd_save = abcd; + e2 = e0; + + LOAD_SHUFFLE (m0, 0) + LOAD_SHUFFLE (m1, 1) + LOAD_SHUFFLE (m2, 2) + LOAD_SHUFFLE (m3, 3) + + ADD_EPI32(e0, m0) + + R16 ( 0, m0, SM1, SM3, SM3, SM3 ) + R16 ( 1, m0, SM3, SM3, SM3, SM3 ) + R16 ( 2, m0, SM3, SM3, SM3, SM3 ) + R16 ( 3, m0, SM3, SM3, SM3, SM3 ) + R16 ( 4, e2, SM2, NNN, NNN, NNN ) + + ADD_EPI32(abcd, abcd_save) + + data += 64; + } + while (--numBlocks); + + PREPARE_STATE + + _mm_storeu_si128((__m128i *) (void *) state, abcd); + *(state + 4) = (UInt32)_mm_cvtsi128_si32(e0); +} + +#endif // USE_HW_SHA + +#elif defined(MY_CPU_ARM_OR_ARM64) && defined(MY_CPU_LE) \ + && (!defined(Z7_MSC_VER_ORIGINAL) || (_MSC_VER >= 1929) && (_MSC_FULL_VER >= 192930037)) + #if defined(__ARM_FEATURE_SHA2) \ + || defined(__ARM_FEATURE_CRYPTO) + #define USE_HW_SHA + #else + #if defined(MY_CPU_ARM64) \ + || defined(__ARM_ARCH) && (__ARM_ARCH >= 4) \ + || defined(Z7_MSC_VER_ORIGINAL) + #if defined(__ARM_FP) && \ + ( defined(Z7_CLANG_VERSION) && (Z7_CLANG_VERSION >= 30800) \ + || defined(__GNUC__) && (__GNUC__ >= 6) \ + ) \ + || defined(Z7_MSC_VER_ORIGINAL) && (_MSC_VER >= 1910) + #if defined(MY_CPU_ARM64) \ + || !defined(Z7_CLANG_VERSION) \ + || defined(__ARM_NEON) && \ + (Z7_CLANG_VERSION < 170000 || \ + Z7_CLANG_VERSION > 170001) + #define USE_HW_SHA + #endif + #endif + #endif + #endif + +#ifdef USE_HW_SHA + +// #pragma message("=== Sha1 HW === ") +// __ARM_FEATURE_CRYPTO macro is deprecated in favor of the finer grained feature macro __ARM_FEATURE_SHA2 + +#if defined(__clang__) || defined(__GNUC__) +#if !defined(__ARM_FEATURE_SHA2) && \ + !defined(__ARM_FEATURE_CRYPTO) + #ifdef MY_CPU_ARM64 +#if defined(__clang__) + #define ATTRIB_SHA __attribute__((__target__("crypto"))) +#else + #define ATTRIB_SHA __attribute__((__target__("+crypto"))) +#endif + #else +#if defined(__clang__) && (__clang_major__ >= 1) + #define ATTRIB_SHA __attribute__((__target__("armv8-a,sha2"))) +#else + #define ATTRIB_SHA __attribute__((__target__("fpu=crypto-neon-fp-armv8"))) +#endif + #endif +#endif +#else + // _MSC_VER + // for arm32 + #define _ARM_USE_NEW_NEON_INTRINSICS +#endif + +#if defined(Z7_MSC_VER_ORIGINAL) && defined(MY_CPU_ARM64) +#include +#else + +#if defined(__clang__) && __clang_major__ < 16 +#if !defined(__ARM_FEATURE_SHA2) && \ + !defined(__ARM_FEATURE_CRYPTO) +// #pragma message("=== we set __ARM_FEATURE_CRYPTO 1 === ") + Z7_DIAGNOSTIC_IGNORE_BEGIN_RESERVED_MACRO_IDENTIFIER + #define Z7_ARM_FEATURE_CRYPTO_WAS_SET 1 +// #if defined(__clang__) && __clang_major__ < 13 + #define __ARM_FEATURE_CRYPTO 1 +// #else + #define __ARM_FEATURE_SHA2 1 +// #endif + Z7_DIAGNOSTIC_IGNORE_END_RESERVED_MACRO_IDENTIFIER +#endif +#endif // clang + +#if defined(__clang__) + +#if defined(__ARM_ARCH) && __ARM_ARCH < 8 + Z7_DIAGNOSTIC_IGNORE_BEGIN_RESERVED_MACRO_IDENTIFIER +// #pragma message("#define __ARM_ARCH 8") + #undef __ARM_ARCH + #define __ARM_ARCH 8 + Z7_DIAGNOSTIC_IGNORE_END_RESERVED_MACRO_IDENTIFIER +#endif + +#endif // clang + +#include + +#if defined(Z7_ARM_FEATURE_CRYPTO_WAS_SET) && \ + defined(__ARM_FEATURE_CRYPTO) && \ + defined(__ARM_FEATURE_SHA2) +Z7_DIAGNOSTIC_IGNORE_BEGIN_RESERVED_MACRO_IDENTIFIER + #undef __ARM_FEATURE_CRYPTO + #undef __ARM_FEATURE_SHA2 + #undef Z7_ARM_FEATURE_CRYPTO_WAS_SET +Z7_DIAGNOSTIC_IGNORE_END_RESERVED_MACRO_IDENTIFIER +// #pragma message("=== we undefine __ARM_FEATURE_CRYPTO === ") +#endif + +#endif // Z7_MSC_VER_ORIGINAL + +typedef uint32x4_t v128; +// typedef __n128 v128; // MSVC +// the bug in clang 3.8.1: +// __builtin_neon_vgetq_lane_i32((int8x16_t)__s0, __p1); +#if defined(__clang__) && (__clang_major__ <= 9) +#pragma GCC diagnostic ignored "-Wvector-conversion" +#endif + +#ifdef MY_CPU_BE + #define MY_rev32_for_LE(x) x +#else + #define MY_rev32_for_LE(x) vrev32q_u8(x) +#endif + +#define LOAD_128_32(_p) vld1q_u32(_p) +#define LOAD_128_8(_p) vld1q_u8 (_p) +#define STORE_128_32(_p, _v) vst1q_u32(_p, _v) + +#define LOAD_SHUFFLE(m, k) \ + m = vreinterpretq_u32_u8( \ + MY_rev32_for_LE( \ + LOAD_128_8(data + (k) * 16))); \ + +#define N0(dest, src2, src3) +#define N1(dest, src) +#define U0(dest, src2, src3) dest = vsha1su0q_u32(dest, src2, src3); +#define U1(dest, src) dest = vsha1su1q_u32(dest, src); +#define C(e) abcd = vsha1cq_u32(abcd, e, t) +#define P(e) abcd = vsha1pq_u32(abcd, e, t) +#define M(e) abcd = vsha1mq_u32(abcd, e, t) +#define H(e) e = vsha1h_u32(vgetq_lane_u32(abcd, 0)) +#define T(m, c) t = vaddq_u32(m, c) + +#define R16(d0,d1,d2,d3, f0,z0, f1,z1, f2,z2, f3,z3, w0,w1,w2,w3) \ + T(m0, d0); f0(m3, m0, m1) z0(m2, m1) H(e1); w0(e0); \ + T(m1, d1); f1(m0, m1, m2) z1(m3, m2) H(e0); w1(e1); \ + T(m2, d2); f2(m1, m2, m3) z2(m0, m3) H(e1); w2(e0); \ + T(m3, d3); f3(m2, m3, m0) z3(m1, m0) H(e0); w3(e1); \ + + +void Z7_FASTCALL Sha1_UpdateBlocks_HW(UInt32 state[8], const Byte *data, size_t numBlocks); +#ifdef ATTRIB_SHA +ATTRIB_SHA +#endif +void Z7_FASTCALL Sha1_UpdateBlocks_HW(UInt32 state[8], const Byte *data, size_t numBlocks) +{ + v128 abcd; + v128 c0, c1, c2, c3; + uint32_t e0; + + if (numBlocks == 0) + return; + + c0 = vdupq_n_u32(0x5a827999); + c1 = vdupq_n_u32(0x6ed9eba1); + c2 = vdupq_n_u32(0x8f1bbcdc); + c3 = vdupq_n_u32(0xca62c1d6); + + abcd = LOAD_128_32(&state[0]); + e0 = state[4]; + + do + { + v128 abcd_save; + v128 m0, m1, m2, m3; + v128 t; + uint32_t e0_save, e1; + + abcd_save = abcd; + e0_save = e0; + + LOAD_SHUFFLE (m0, 0) + LOAD_SHUFFLE (m1, 1) + LOAD_SHUFFLE (m2, 2) + LOAD_SHUFFLE (m3, 3) + + R16 ( c0,c0,c0,c0, N0,N1, U0,N1, U0,U1, U0,U1, C,C,C,C ) + R16 ( c0,c1,c1,c1, U0,U1, U0,U1, U0,U1, U0,U1, C,P,P,P ) + R16 ( c1,c1,c2,c2, U0,U1, U0,U1, U0,U1, U0,U1, P,P,M,M ) + R16 ( c2,c2,c2,c3, U0,U1, U0,U1, U0,U1, U0,U1, M,M,M,P ) + R16 ( c3,c3,c3,c3, U0,U1, N0,U1, N0,N1, N0,N1, P,P,P,P ) + + abcd = vaddq_u32(abcd, abcd_save); + e0 += e0_save; + + data += 64; + } + while (--numBlocks); + + STORE_128_32(&state[0], abcd); + state[4] = e0; +} + +#endif // USE_HW_SHA + +#endif // MY_CPU_ARM_OR_ARM64 + +#if !defined(USE_HW_SHA) && defined(Z7_USE_HW_SHA_STUB) +// #error Stop_Compiling_UNSUPPORTED_SHA +// #include +// #include "Sha1.h" +// #if defined(_MSC_VER) +#pragma message("Sha1 HW-SW stub was used") +// #endif +void Z7_FASTCALL Sha1_UpdateBlocks (UInt32 state[5], const Byte *data, size_t numBlocks); +void Z7_FASTCALL Sha1_UpdateBlocks_HW(UInt32 state[5], const Byte *data, size_t numBlocks); +void Z7_FASTCALL Sha1_UpdateBlocks_HW(UInt32 state[5], const Byte *data, size_t numBlocks) +{ + Sha1_UpdateBlocks(state, data, numBlocks); + /* + UNUSED_VAR(state); + UNUSED_VAR(data); + UNUSED_VAR(numBlocks); + exit(1); + return; + */ +} +#endif + +#undef U0 +#undef U1 +#undef N0 +#undef N1 +#undef C +#undef P +#undef M +#undef H +#undef T +#undef MY_rev32_for_LE +#undef NNN +#undef LOAD_128 +#undef STORE_128 +#undef LOAD_SHUFFLE +#undef SM1 +#undef SM2 +#undef SM3 +#undef NNN +#undef R4 +#undef R16 +#undef PREPARE_STATE +#undef USE_HW_SHA +#undef ATTRIB_SHA +#undef USE_VER_MIN +#undef Z7_USE_HW_SHA_STUB diff --git a/src/plugins/7zip/7za/c/Sha256.c b/src/plugins/7zip/7za/c/Sha256.c index b5bc19b9..ea7ed8e7 100644 --- a/src/plugins/7zip/7za/c/Sha256.c +++ b/src/plugins/7zip/7za/c/Sha256.c @@ -1,25 +1,113 @@ -/* Crypto/Sha256.c -- SHA-256 Hash -2015-11-14 : Igor Pavlov : Public domain +/* Sha256.c -- SHA-256 Hash +: Igor Pavlov : Public domain This code is based on public domain code from Wei Dai's Crypto++ library. */ #include "Precomp.h" #include -#include "CpuArch.h" -#include "RotateDefs.h" #include "Sha256.h" +#include "RotateDefs.h" +#include "CpuArch.h" + +#ifdef MY_CPU_X86_OR_AMD64 + #if defined(Z7_LLVM_CLANG_VERSION) && (Z7_LLVM_CLANG_VERSION >= 30800) \ + || defined(Z7_APPLE_CLANG_VERSION) && (Z7_APPLE_CLANG_VERSION >= 50100) \ + || defined(Z7_GCC_VERSION) && (Z7_GCC_VERSION >= 40900) \ + || defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 1600) \ + || defined(_MSC_VER) && (_MSC_VER >= 1200) + #define Z7_COMPILER_SHA256_SUPPORTED + #endif +#elif defined(MY_CPU_ARM_OR_ARM64) && defined(MY_CPU_LE) + + #if defined(__ARM_FEATURE_SHA2) \ + || defined(__ARM_FEATURE_CRYPTO) + #define Z7_COMPILER_SHA256_SUPPORTED + #else + #if defined(MY_CPU_ARM64) \ + || defined(__ARM_ARCH) && (__ARM_ARCH >= 4) \ + || defined(Z7_MSC_VER_ORIGINAL) + #if defined(__ARM_FP) && \ + ( defined(Z7_CLANG_VERSION) && (Z7_CLANG_VERSION >= 30800) \ + || defined(__GNUC__) && (__GNUC__ >= 6) \ + ) \ + || defined(Z7_MSC_VER_ORIGINAL) && (_MSC_VER >= 1910) + #if defined(MY_CPU_ARM64) \ + || !defined(Z7_CLANG_VERSION) \ + || defined(__ARM_NEON) && \ + (Z7_CLANG_VERSION < 170000 || \ + Z7_CLANG_VERSION > 170001) + #define Z7_COMPILER_SHA256_SUPPORTED + #endif + #endif + #endif + #endif +#endif + +void Z7_FASTCALL Sha256_UpdateBlocks(UInt32 state[8], const Byte *data, size_t numBlocks); + +#ifdef Z7_COMPILER_SHA256_SUPPORTED + void Z7_FASTCALL Sha256_UpdateBlocks_HW(UInt32 state[8], const Byte *data, size_t numBlocks); + + static SHA256_FUNC_UPDATE_BLOCKS g_SHA256_FUNC_UPDATE_BLOCKS = Sha256_UpdateBlocks; + static SHA256_FUNC_UPDATE_BLOCKS g_SHA256_FUNC_UPDATE_BLOCKS_HW; + + #define SHA256_UPDATE_BLOCKS(p) p->v.vars.func_UpdateBlocks +#else + #define SHA256_UPDATE_BLOCKS(p) Sha256_UpdateBlocks +#endif + + +BoolInt Sha256_SetFunction(CSha256 *p, unsigned algo) +{ + SHA256_FUNC_UPDATE_BLOCKS func = Sha256_UpdateBlocks; + + #ifdef Z7_COMPILER_SHA256_SUPPORTED + if (algo != SHA256_ALGO_SW) + { + if (algo == SHA256_ALGO_DEFAULT) + func = g_SHA256_FUNC_UPDATE_BLOCKS; + else + { + if (algo != SHA256_ALGO_HW) + return False; + func = g_SHA256_FUNC_UPDATE_BLOCKS_HW; + if (!func) + return False; + } + } + #else + if (algo > 1) + return False; + #endif + + p->v.vars.func_UpdateBlocks = func; + return True; +} + /* define it for speed optimization */ -#ifndef _SFX -#define _SHA256_UNROLL -#define _SHA256_UNROLL2 + +#ifdef Z7_SFX + #define STEP_PRE 1 + #define STEP_MAIN 1 +#else + #define STEP_PRE 2 + #define STEP_MAIN 4 + // #define Z7_SHA256_UNROLL #endif -/* #define _SHA256_UNROLL2 */ +#undef Z7_SHA256_BIG_W +#if STEP_MAIN != 16 + #define Z7_SHA256_BIG_W +#endif -void Sha256_Init(CSha256 *p) + + + +void Sha256_InitState(CSha256 *p) { + p->v.vars.count = 0; p->state[0] = 0x6a09e667; p->state[1] = 0xbb67ae85; p->state[2] = 0x3c6ef372; @@ -28,69 +116,121 @@ void Sha256_Init(CSha256 *p) p->state[5] = 0x9b05688c; p->state[6] = 0x1f83d9ab; p->state[7] = 0x5be0cd19; - p->count = 0; } -#define S0(x) (rotrFixed(x, 2) ^ rotrFixed(x,13) ^ rotrFixed(x, 22)) -#define S1(x) (rotrFixed(x, 6) ^ rotrFixed(x,11) ^ rotrFixed(x, 25)) -#define s0(x) (rotrFixed(x, 7) ^ rotrFixed(x,18) ^ (x >> 3)) -#define s1(x) (rotrFixed(x,17) ^ rotrFixed(x,19) ^ (x >> 10)) -#define blk0(i) (W[i]) -#define blk2(i) (W[i] += s1(W[((i)-2)&15]) + W[((i)-7)&15] + s0(W[((i)-15)&15])) -#define Ch(x,y,z) (z^(x&(y^z))) -#define Maj(x,y,z) ((x&y)|(z&(x|y))) -#ifdef _SHA256_UNROLL2 -#define R(a,b,c,d,e,f,g,h, i) \ - h += S1(e) + Ch(e,f,g) + K[(i)+(j)] + (j ? blk2(i) : blk0(i)); \ - d += h; \ - h += S0(a) + Maj(a, b, c) -#define RX_8(i) \ - R(a,b,c,d,e,f,g,h, i); \ - R(h,a,b,c,d,e,f,g, i+1); \ - R(g,h,a,b,c,d,e,f, i+2); \ - R(f,g,h,a,b,c,d,e, i+3); \ - R(e,f,g,h,a,b,c,d, i+4); \ - R(d,e,f,g,h,a,b,c, i+5); \ - R(c,d,e,f,g,h,a,b, i+6); \ - R(b,c,d,e,f,g,h,a, i+7) -#define RX_16 RX_8(0); RX_8(8); -#else +void Sha256_Init(CSha256 *p) +{ + p->v.vars.func_UpdateBlocks = + #ifdef Z7_COMPILER_SHA256_SUPPORTED + g_SHA256_FUNC_UPDATE_BLOCKS; + #else + NULL; + #endif + Sha256_InitState(p); +} -#define a(i) T[(0-(i))&7] -#define b(i) T[(1-(i))&7] -#define c(i) T[(2-(i))&7] -#define d(i) T[(3-(i))&7] -#define e(i) T[(4-(i))&7] -#define f(i) T[(5-(i))&7] -#define g(i) T[(6-(i))&7] -#define h(i) T[(7-(i))&7] +#define S0(x) (rotrFixed(x, 2) ^ rotrFixed(x,13) ^ rotrFixed(x,22)) +#define S1(x) (rotrFixed(x, 6) ^ rotrFixed(x,11) ^ rotrFixed(x,25)) +#define s0(x) (rotrFixed(x, 7) ^ rotrFixed(x,18) ^ (x >> 3)) +#define s1(x) (rotrFixed(x,17) ^ rotrFixed(x,19) ^ (x >>10)) + +#define Ch(x,y,z) (z^(x&(y^z))) +#define Maj(x,y,z) ((x&y)|(z&(x|y))) -#define R(i) \ - h(i) += S1(e(i)) + Ch(e(i),f(i),g(i)) + K[(i)+(j)] + (j ? blk2(i) : blk0(i)); \ - d(i) += h(i); \ - h(i) += S0(a(i)) + Maj(a(i), b(i), c(i)) \ -#ifdef _SHA256_UNROLL +#define W_PRE(i) (W[(i) + (size_t)(j)] = GetBe32(data + ((size_t)(j) + i) * 4)) -#define RX_8(i) R(i+0); R(i+1); R(i+2); R(i+3); R(i+4); R(i+5); R(i+6); R(i+7); -#define RX_16 RX_8(0); RX_8(8); +#define blk2_main(j, i) s1(w(j, (i)-2)) + w(j, (i)-7) + s0(w(j, (i)-15)) +#ifdef Z7_SHA256_BIG_W + // we use +i instead of +(i) to change the order to solve CLANG compiler warning for signed/unsigned. + #define w(j, i) W[(size_t)(j) + i] + #define blk2(j, i) (w(j, i) = w(j, (i)-16) + blk2_main(j, i)) #else + #if STEP_MAIN == 16 + #define w(j, i) W[(i) & 15] + #else + #define w(j, i) W[((size_t)(j) + (i)) & 15] + #endif + #define blk2(j, i) (w(j, i) += blk2_main(j, i)) +#endif + +#define W_MAIN(i) blk2(j, i) + + +#define T1(wx, i) \ + tmp = h + S1(e) + Ch(e,f,g) + K[(i)+(size_t)(j)] + wx(i); \ + h = g; \ + g = f; \ + f = e; \ + e = d + tmp; \ + tmp += S0(a) + Maj(a, b, c); \ + d = c; \ + c = b; \ + b = a; \ + a = tmp; \ -#define RX_16 unsigned i; for (i = 0; i < 16; i++) { R(i); } +#define R1_PRE(i) T1( W_PRE, i) +#define R1_MAIN(i) T1( W_MAIN, i) + +#if (!defined(Z7_SHA256_UNROLL) || STEP_MAIN < 8) && (STEP_MAIN >= 4) +#define R2_MAIN(i) \ + R1_MAIN(i) \ + R1_MAIN(i + 1) \ #endif + + +#if defined(Z7_SHA256_UNROLL) && STEP_MAIN >= 8 + +#define T4( a,b,c,d,e,f,g,h, wx, i) \ + h += S1(e) + Ch(e,f,g) + K[(i)+(size_t)(j)] + wx(i); \ + tmp = h; \ + h += d; \ + d = tmp + S0(a) + Maj(a, b, c); \ + +#define R4( wx, i) \ + T4 ( a,b,c,d,e,f,g,h, wx, (i )); \ + T4 ( d,a,b,c,h,e,f,g, wx, (i+1)); \ + T4 ( c,d,a,b,g,h,e,f, wx, (i+2)); \ + T4 ( b,c,d,a,f,g,h,e, wx, (i+3)); \ + +#define R4_PRE(i) R4( W_PRE, i) +#define R4_MAIN(i) R4( W_MAIN, i) + + +#define T8( a,b,c,d,e,f,g,h, wx, i) \ + h += S1(e) + Ch(e,f,g) + K[(i)+(size_t)(j)] + wx(i); \ + d += h; \ + h += S0(a) + Maj(a, b, c); \ + +#define R8( wx, i) \ + T8 ( a,b,c,d,e,f,g,h, wx, i ); \ + T8 ( h,a,b,c,d,e,f,g, wx, i+1); \ + T8 ( g,h,a,b,c,d,e,f, wx, i+2); \ + T8 ( f,g,h,a,b,c,d,e, wx, i+3); \ + T8 ( e,f,g,h,a,b,c,d, wx, i+4); \ + T8 ( d,e,f,g,h,a,b,c, wx, i+5); \ + T8 ( c,d,e,f,g,h,a,b, wx, i+6); \ + T8 ( b,c,d,e,f,g,h,a, wx, i+7); \ + +#define R8_PRE(i) R8( W_PRE, i) +#define R8_MAIN(i) R8( W_MAIN, i) + #endif -static const UInt32 K[64] = { + +extern +MY_ALIGN(64) const UInt32 SHA256_K_ARRAY[64]; +MY_ALIGN(64) const UInt32 SHA256_K_ARRAY[64] = { 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, @@ -109,30 +249,29 @@ static const UInt32 K[64] = { 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 }; -static void Sha256_WriteByteBlock(CSha256 *p) -{ - UInt32 W[16]; - unsigned j; - UInt32 *state; - #ifdef _SHA256_UNROLL2 - UInt32 a,b,c,d,e,f,g,h; - #else - UInt32 T[8]; - #endif - for (j = 0; j < 16; j += 4) - { - const Byte *ccc = p->buffer + j * 4; - W[j ] = GetBe32(ccc); - W[j + 1] = GetBe32(ccc + 4); - W[j + 2] = GetBe32(ccc + 8); - W[j + 3] = GetBe32(ccc + 12); - } - state = p->state; - #ifdef _SHA256_UNROLL2 +#define K SHA256_K_ARRAY + +Z7_NO_INLINE +void Z7_FASTCALL Sha256_UpdateBlocks(UInt32 state[8], const Byte *data, size_t numBlocks) +{ + UInt32 W +#ifdef Z7_SHA256_BIG_W + [64]; +#else + [16]; +#endif + unsigned j; + UInt32 a,b,c,d,e,f,g,h; +#if !defined(Z7_SHA256_UNROLL) || (STEP_MAIN <= 4) || (STEP_PRE <= 4) + UInt32 tmp; +#endif + + if (numBlocks == 0) return; + a = state[0]; b = state[1]; c = state[2]; @@ -141,108 +280,213 @@ static void Sha256_WriteByteBlock(CSha256 *p) f = state[5]; g = state[6]; h = state[7]; - #else - for (j = 0; j < 8; j++) - T[j] = state[j]; - #endif - for (j = 0; j < 64; j += 16) + do + { + + for (j = 0; j < 16; j += STEP_PRE) + { + #if STEP_PRE > 4 + + #if STEP_PRE < 8 + R4_PRE(0); + #else + R8_PRE(0); + #if STEP_PRE == 16 + R8_PRE(8); + #endif + #endif + + #else + + R1_PRE(0) + #if STEP_PRE >= 2 + R1_PRE(1) + #if STEP_PRE >= 4 + R1_PRE(2) + R1_PRE(3) + #endif + #endif + + #endif + } + + for (j = 16; j < 64; j += STEP_MAIN) { - RX_16 + #if defined(Z7_SHA256_UNROLL) && STEP_MAIN >= 8 + + #if STEP_MAIN < 8 + R4_MAIN(0) + #else + R8_MAIN(0) + #if STEP_MAIN == 16 + R8_MAIN(8) + #endif + #endif + + #else + + R1_MAIN(0) + #if STEP_MAIN >= 2 + R1_MAIN(1) + #if STEP_MAIN >= 4 + R2_MAIN(2) + #if STEP_MAIN >= 8 + R2_MAIN(4) + R2_MAIN(6) + #if STEP_MAIN >= 16 + R2_MAIN(8) + R2_MAIN(10) + R2_MAIN(12) + R2_MAIN(14) + #endif + #endif + #endif + #endif + #endif } - #ifdef _SHA256_UNROLL2 - state[0] += a; - state[1] += b; - state[2] += c; - state[3] += d; - state[4] += e; - state[5] += f; - state[6] += g; - state[7] += h; - #else - for (j = 0; j < 8; j++) - state[j] += T[j]; - #endif - - /* Wipe variables */ - /* memset(W, 0, sizeof(W)); */ - /* memset(T, 0, sizeof(T)); */ + a += state[0]; state[0] = a; + b += state[1]; state[1] = b; + c += state[2]; state[2] = c; + d += state[3]; state[3] = d; + e += state[4]; state[4] = e; + f += state[5]; state[5] = f; + g += state[6]; state[6] = g; + h += state[7]; state[7] = h; + + data += SHA256_BLOCK_SIZE; + } + while (--numBlocks); } -#undef S0 -#undef S1 -#undef s0 -#undef s1 + +#define Sha256_UpdateBlock(p) SHA256_UPDATE_BLOCKS(p)(p->state, p->buffer, 1) void Sha256_Update(CSha256 *p, const Byte *data, size_t size) { if (size == 0) return; - { - unsigned pos = (unsigned)p->count & 0x3F; - unsigned num; - - p->count += size; - - num = 64 - pos; + const unsigned pos = (unsigned)p->v.vars.count & (SHA256_BLOCK_SIZE - 1); + const unsigned num = SHA256_BLOCK_SIZE - pos; + p->v.vars.count += size; if (num > size) { memcpy(p->buffer + pos, data, size); return; } - - size -= num; - memcpy(p->buffer + pos, data, num); - data += num; + if (pos != 0) + { + size -= num; + memcpy(p->buffer + pos, data, num); + data += num; + Sha256_UpdateBlock(p); + } } - - for (;;) { - Sha256_WriteByteBlock(p); - if (size < 64) - break; - size -= 64; - memcpy(p->buffer, data, 64); - data += 64; - } - - if (size != 0) + const size_t numBlocks = size >> 6; + // if (numBlocks) + SHA256_UPDATE_BLOCKS(p)(p->state, data, numBlocks); + size &= SHA256_BLOCK_SIZE - 1; + if (size == 0) + return; + data += (numBlocks << 6); memcpy(p->buffer, data, size); + } } + void Sha256_Final(CSha256 *p, Byte *digest) { - unsigned pos = (unsigned)p->count & 0x3F; - unsigned i; - + unsigned pos = (unsigned)p->v.vars.count & (SHA256_BLOCK_SIZE - 1); p->buffer[pos++] = 0x80; - - while (pos != (64 - 8)) + if (pos > (SHA256_BLOCK_SIZE - 4 * 2)) { - pos &= 0x3F; - if (pos == 0) - Sha256_WriteByteBlock(p); - p->buffer[pos++] = 0; + while (pos != SHA256_BLOCK_SIZE) { p->buffer[pos++] = 0; } + // memset(&p->buf.buffer[pos], 0, SHA256_BLOCK_SIZE - pos); + Sha256_UpdateBlock(p); + pos = 0; } - + memset(&p->buffer[pos], 0, (SHA256_BLOCK_SIZE - 4 * 2) - pos); { - UInt64 numBits = (p->count << 3); - SetBe32(p->buffer + 64 - 8, (UInt32)(numBits >> 32)); - SetBe32(p->buffer + 64 - 4, (UInt32)(numBits)); + const UInt64 numBits = p->v.vars.count << 3; + SetBe32(p->buffer + SHA256_BLOCK_SIZE - 4 * 2, (UInt32)(numBits >> 32)) + SetBe32(p->buffer + SHA256_BLOCK_SIZE - 4 * 1, (UInt32)(numBits)) + } + Sha256_UpdateBlock(p); +#if 1 && defined(MY_CPU_BE) + memcpy(digest, p->state, SHA256_DIGEST_SIZE); +#else + { + unsigned i; + for (i = 0; i < 8; i += 2) + { + const UInt32 v0 = p->state[i]; + const UInt32 v1 = p->state[(size_t)i + 1]; + SetBe32(digest , v0) + SetBe32(digest + 4, v1) + digest += 4 * 2; + } } - - Sha256_WriteByteBlock(p); - for (i = 0; i < 8; i += 2) + + + +#endif + Sha256_InitState(p); +} + + +void Sha256Prepare(void) +{ +#ifdef Z7_COMPILER_SHA256_SUPPORTED + SHA256_FUNC_UPDATE_BLOCKS f, f_hw; + f = Sha256_UpdateBlocks; + f_hw = NULL; +#ifdef MY_CPU_X86_OR_AMD64 + if (CPU_IsSupported_SHA() + && CPU_IsSupported_SSSE3() + ) +#else + if (CPU_IsSupported_SHA2()) +#endif { - UInt32 v0 = p->state[i]; - UInt32 v1 = p->state[i + 1]; - SetBe32(digest , v0); - SetBe32(digest + 4, v1); - digest += 8; + // printf("\n========== HW SHA256 ======== \n"); + f = f_hw = Sha256_UpdateBlocks_HW; } - - Sha256_Init(p); + g_SHA256_FUNC_UPDATE_BLOCKS = f; + g_SHA256_FUNC_UPDATE_BLOCKS_HW = f_hw; +#endif } + +#undef U64C +#undef K +#undef S0 +#undef S1 +#undef s0 +#undef s1 +#undef Ch +#undef Maj +#undef W_MAIN +#undef W_PRE +#undef w +#undef blk2_main +#undef blk2 +#undef T1 +#undef T4 +#undef T8 +#undef R1_PRE +#undef R1_MAIN +#undef R2_MAIN +#undef R4 +#undef R4_PRE +#undef R4_MAIN +#undef R8 +#undef R8_PRE +#undef R8_MAIN +#undef STEP_PRE +#undef STEP_MAIN +#undef Z7_SHA256_BIG_W +#undef Z7_SHA256_UNROLL +#undef Z7_COMPILER_SHA256_SUPPORTED diff --git a/src/plugins/7zip/7za/c/Sha256.h b/src/plugins/7zip/7za/c/Sha256.h index 3f455dbc..75329cdf 100644 --- a/src/plugins/7zip/7za/c/Sha256.h +++ b/src/plugins/7zip/7za/c/Sha256.h @@ -1,26 +1,86 @@ /* Sha256.h -- SHA-256 Hash -2013-01-18 : Igor Pavlov : Public domain */ +: Igor Pavlov : Public domain */ -#ifndef __CRYPTO_SHA256_H -#define __CRYPTO_SHA256_H +#ifndef ZIP7_INC_SHA256_H +#define ZIP7_INC_SHA256_H #include "7zTypes.h" EXTERN_C_BEGIN -#define SHA256_DIGEST_SIZE 32 +#define SHA256_NUM_BLOCK_WORDS 16 +#define SHA256_NUM_DIGEST_WORDS 8 + +#define SHA256_BLOCK_SIZE (SHA256_NUM_BLOCK_WORDS * 4) +#define SHA256_DIGEST_SIZE (SHA256_NUM_DIGEST_WORDS * 4) + + + + +typedef void (Z7_FASTCALL *SHA256_FUNC_UPDATE_BLOCKS)(UInt32 state[8], const Byte *data, size_t numBlocks); + +/* + if (the system supports different SHA256 code implementations) + { + (CSha256::func_UpdateBlocks) will be used + (CSha256::func_UpdateBlocks) can be set by + Sha256_Init() - to default (fastest) + Sha256_SetFunction() - to any algo + } + else + { + (CSha256::func_UpdateBlocks) is ignored. + } +*/ typedef struct { - UInt32 state[8]; - UInt64 count; - Byte buffer[64]; + union + { + struct + { + SHA256_FUNC_UPDATE_BLOCKS func_UpdateBlocks; + UInt64 count; + } vars; + UInt64 _pad_64bit[4]; + void *_pad_align_ptr[2]; + } v; + UInt32 state[SHA256_NUM_DIGEST_WORDS]; + + Byte buffer[SHA256_BLOCK_SIZE]; } CSha256; + +#define SHA256_ALGO_DEFAULT 0 +#define SHA256_ALGO_SW 1 +#define SHA256_ALGO_HW 2 + +/* +Sha256_SetFunction() +return: + 0 - (algo) value is not supported, and func_UpdateBlocks was not changed + 1 - func_UpdateBlocks was set according (algo) value. +*/ + +BoolInt Sha256_SetFunction(CSha256 *p, unsigned algo); + +void Sha256_InitState(CSha256 *p); void Sha256_Init(CSha256 *p); void Sha256_Update(CSha256 *p, const Byte *data, size_t size); void Sha256_Final(CSha256 *p, Byte *digest); + + + +// void Z7_FASTCALL Sha256_UpdateBlocks(UInt32 state[8], const Byte *data, size_t numBlocks); + +/* +call Sha256Prepare() once at program start. +It prepares all supported implementations, and detects the fastest implementation. +*/ + +void Sha256Prepare(void); + EXTERN_C_END #endif diff --git a/src/plugins/7zip/7za/c/Sha256Opt.c b/src/plugins/7zip/7za/c/Sha256Opt.c new file mode 100644 index 00000000..1c6b50f8 --- /dev/null +++ b/src/plugins/7zip/7za/c/Sha256Opt.c @@ -0,0 +1,451 @@ +/* Sha256Opt.c -- SHA-256 optimized code for SHA-256 hardware instructions +: Igor Pavlov : Public domain */ + +#include "Precomp.h" +#include "Compiler.h" +#include "CpuArch.h" + +// #define Z7_USE_HW_SHA_STUB // for debug +#ifdef MY_CPU_X86_OR_AMD64 + #if defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 1600) // fix that check + #define USE_HW_SHA + #elif defined(Z7_LLVM_CLANG_VERSION) && (Z7_LLVM_CLANG_VERSION >= 30800) \ + || defined(Z7_APPLE_CLANG_VERSION) && (Z7_APPLE_CLANG_VERSION >= 50100) \ + || defined(Z7_GCC_VERSION) && (Z7_GCC_VERSION >= 40900) + #define USE_HW_SHA + #if !defined(__INTEL_COMPILER) + // icc defines __GNUC__, but icc doesn't support __attribute__(__target__) + #if !defined(__SHA__) || !defined(__SSSE3__) + #define ATTRIB_SHA __attribute__((__target__("sha,ssse3"))) + #endif + #endif + #elif defined(_MSC_VER) + #if (_MSC_VER >= 1900) + #define USE_HW_SHA + #else + #define Z7_USE_HW_SHA_STUB + #endif + #endif +// #endif // MY_CPU_X86_OR_AMD64 +#ifndef USE_HW_SHA + // #define Z7_USE_HW_SHA_STUB // for debug +#endif + +#ifdef USE_HW_SHA + +// #pragma message("Sha256 HW") + + + + +// sse/sse2/ssse3: +#include +// sha*: +#include + +#if defined (__clang__) && defined(_MSC_VER) + #if !defined(__SHA__) + #include + #endif +#else + +#endif + +/* +SHA256 uses: +SSE2: + _mm_loadu_si128 + _mm_storeu_si128 + _mm_set_epi32 + _mm_add_epi32 + _mm_shuffle_epi32 / pshufd + + + +SSSE3: + _mm_shuffle_epi8 / pshufb + _mm_alignr_epi8 +SHA: + _mm_sha256* +*/ + +// K array must be aligned for 16-bytes at least. +// The compiler can look align attribute and selects +// movdqu - for code without align attribute +// movdqa - for code with align attribute +extern +MY_ALIGN(64) +const UInt32 SHA256_K_ARRAY[64]; +#define K SHA256_K_ARRAY + + +#define ADD_EPI32(dest, src) dest = _mm_add_epi32(dest, src); +#define SHA256_MSG1(dest, src) dest = _mm_sha256msg1_epu32(dest, src); +#define SHA256_MSG2(dest, src) dest = _mm_sha256msg2_epu32(dest, src); + +#define LOAD_SHUFFLE(m, k) \ + m = _mm_loadu_si128((const __m128i *)(const void *)(data + (k) * 16)); \ + m = _mm_shuffle_epi8(m, mask); \ + +#define NNN(m0, m1, m2, m3) + +#define SM1(m1, m2, m3, m0) \ + SHA256_MSG1(m0, m1); \ + +#define SM2(m2, m3, m0, m1) \ + ADD_EPI32(m0, _mm_alignr_epi8(m3, m2, 4)) \ + SHA256_MSG2(m0, m3); \ + +#define RND2(t0, t1) \ + t0 = _mm_sha256rnds2_epu32(t0, t1, msg); + + + +#define R4(k, m0, m1, m2, m3, OP0, OP1) \ + msg = _mm_add_epi32(m0, *(const __m128i *) (const void *) &K[(k) * 4]); \ + RND2(state0, state1); \ + msg = _mm_shuffle_epi32(msg, 0x0E); \ + OP0(m0, m1, m2, m3) \ + RND2(state1, state0); \ + OP1(m0, m1, m2, m3) \ + +#define R16(k, OP0, OP1, OP2, OP3, OP4, OP5, OP6, OP7) \ + R4 ( (k)*4+0, m0,m1,m2,m3, OP0, OP1 ) \ + R4 ( (k)*4+1, m1,m2,m3,m0, OP2, OP3 ) \ + R4 ( (k)*4+2, m2,m3,m0,m1, OP4, OP5 ) \ + R4 ( (k)*4+3, m3,m0,m1,m2, OP6, OP7 ) \ + +#define PREPARE_STATE \ + tmp = _mm_shuffle_epi32(state0, 0x1B); /* abcd */ \ + state0 = _mm_shuffle_epi32(state1, 0x1B); /* efgh */ \ + state1 = state0; \ + state0 = _mm_unpacklo_epi64(state0, tmp); /* cdgh */ \ + state1 = _mm_unpackhi_epi64(state1, tmp); /* abef */ \ + + +void Z7_FASTCALL Sha256_UpdateBlocks_HW(UInt32 state[8], const Byte *data, size_t numBlocks); +#ifdef ATTRIB_SHA +ATTRIB_SHA +#endif +void Z7_FASTCALL Sha256_UpdateBlocks_HW(UInt32 state[8], const Byte *data, size_t numBlocks) +{ + const __m128i mask = _mm_set_epi32(0x0c0d0e0f, 0x08090a0b, 0x04050607, 0x00010203); + + + __m128i tmp, state0, state1; + + if (numBlocks == 0) + return; + + state0 = _mm_loadu_si128((const __m128i *) (const void *) &state[0]); + state1 = _mm_loadu_si128((const __m128i *) (const void *) &state[4]); + + PREPARE_STATE + + do + { + __m128i state0_save, state1_save; + __m128i m0, m1, m2, m3; + __m128i msg; + // #define msg tmp + + state0_save = state0; + state1_save = state1; + + LOAD_SHUFFLE (m0, 0) + LOAD_SHUFFLE (m1, 1) + LOAD_SHUFFLE (m2, 2) + LOAD_SHUFFLE (m3, 3) + + + + R16 ( 0, NNN, NNN, SM1, NNN, SM1, SM2, SM1, SM2 ) + R16 ( 1, SM1, SM2, SM1, SM2, SM1, SM2, SM1, SM2 ) + R16 ( 2, SM1, SM2, SM1, SM2, SM1, SM2, SM1, SM2 ) + R16 ( 3, SM1, SM2, NNN, SM2, NNN, NNN, NNN, NNN ) + + ADD_EPI32(state0, state0_save) + ADD_EPI32(state1, state1_save) + + data += 64; + } + while (--numBlocks); + + PREPARE_STATE + + _mm_storeu_si128((__m128i *) (void *) &state[0], state0); + _mm_storeu_si128((__m128i *) (void *) &state[4], state1); +} + +#endif // USE_HW_SHA + +#elif defined(MY_CPU_ARM_OR_ARM64) && defined(MY_CPU_LE) + + #if defined(__ARM_FEATURE_SHA2) \ + || defined(__ARM_FEATURE_CRYPTO) + #define USE_HW_SHA + #else + #if defined(MY_CPU_ARM64) \ + || defined(__ARM_ARCH) && (__ARM_ARCH >= 4) \ + || defined(Z7_MSC_VER_ORIGINAL) + #if defined(__ARM_FP) && \ + ( defined(Z7_CLANG_VERSION) && (Z7_CLANG_VERSION >= 30800) \ + || defined(__GNUC__) && (__GNUC__ >= 6) \ + ) \ + || defined(Z7_MSC_VER_ORIGINAL) && (_MSC_VER >= 1910) + #if defined(MY_CPU_ARM64) \ + || !defined(Z7_CLANG_VERSION) \ + || defined(__ARM_NEON) && \ + (Z7_CLANG_VERSION < 170000 || \ + Z7_CLANG_VERSION > 170001) + #define USE_HW_SHA + #endif + #endif + #endif + #endif + +#ifdef USE_HW_SHA + +// #pragma message("=== Sha256 HW === ") + + +#if defined(__clang__) || defined(__GNUC__) +#if !defined(__ARM_FEATURE_SHA2) && \ + !defined(__ARM_FEATURE_CRYPTO) + #ifdef MY_CPU_ARM64 +#if defined(__clang__) + #define ATTRIB_SHA __attribute__((__target__("crypto"))) +#else + #define ATTRIB_SHA __attribute__((__target__("+crypto"))) +#endif + #else +#if defined(__clang__) && (__clang_major__ >= 1) + #define ATTRIB_SHA __attribute__((__target__("armv8-a,sha2"))) +#else + #define ATTRIB_SHA __attribute__((__target__("fpu=crypto-neon-fp-armv8"))) +#endif + #endif +#endif +#else + // _MSC_VER + // for arm32 + #define _ARM_USE_NEW_NEON_INTRINSICS +#endif + +#if defined(Z7_MSC_VER_ORIGINAL) && defined(MY_CPU_ARM64) +#include +#else + +#if defined(__clang__) && __clang_major__ < 16 +#if !defined(__ARM_FEATURE_SHA2) && \ + !defined(__ARM_FEATURE_CRYPTO) +// #pragma message("=== we set __ARM_FEATURE_CRYPTO 1 === ") + Z7_DIAGNOSTIC_IGNORE_BEGIN_RESERVED_MACRO_IDENTIFIER + #define Z7_ARM_FEATURE_CRYPTO_WAS_SET 1 +// #if defined(__clang__) && __clang_major__ < 13 + #define __ARM_FEATURE_CRYPTO 1 +// #else + #define __ARM_FEATURE_SHA2 1 +// #endif + Z7_DIAGNOSTIC_IGNORE_END_RESERVED_MACRO_IDENTIFIER +#endif +#endif // clang + +#if defined(__clang__) + +#if defined(__ARM_ARCH) && __ARM_ARCH < 8 + Z7_DIAGNOSTIC_IGNORE_BEGIN_RESERVED_MACRO_IDENTIFIER +// #pragma message("#define __ARM_ARCH 8") + #undef __ARM_ARCH + #define __ARM_ARCH 8 + Z7_DIAGNOSTIC_IGNORE_END_RESERVED_MACRO_IDENTIFIER +#endif + +#endif // clang + +#include + +#if defined(Z7_ARM_FEATURE_CRYPTO_WAS_SET) && \ + defined(__ARM_FEATURE_CRYPTO) && \ + defined(__ARM_FEATURE_SHA2) +Z7_DIAGNOSTIC_IGNORE_BEGIN_RESERVED_MACRO_IDENTIFIER + #undef __ARM_FEATURE_CRYPTO + #undef __ARM_FEATURE_SHA2 + #undef Z7_ARM_FEATURE_CRYPTO_WAS_SET +Z7_DIAGNOSTIC_IGNORE_END_RESERVED_MACRO_IDENTIFIER +// #pragma message("=== we undefine __ARM_FEATURE_CRYPTO === ") +#endif + +#endif // Z7_MSC_VER_ORIGINAL + +typedef uint32x4_t v128; +// typedef __n128 v128; // MSVC + +#ifdef MY_CPU_BE + #define MY_rev32_for_LE(x) x +#else + #define MY_rev32_for_LE(x) vrev32q_u8(x) +#endif + +#if 1 // 0 for debug +// for arm32: it works slower by some reason than direct code +/* +for arm32 it generates: +MSVC-2022, GCC-9: + vld1.32 {d18,d19}, [r10] + vst1.32 {d4,d5}, [r3] + vld1.8 {d20-d21}, [r4] +there is no align hint (like [r10:128]). So instruction allows unaligned access +*/ +#define LOAD_128_32(_p) vld1q_u32(_p) +#define LOAD_128_8(_p) vld1q_u8 (_p) +#define STORE_128_32(_p, _v) vst1q_u32(_p, _v) +#else +/* +for arm32: +MSVC-2022: + vldm r10,{d18,d19} + vstm r3,{d4,d5} + does it require strict alignment? +GCC-9: + vld1.64 {d30-d31}, [r0:64] + vldr d28, [r0, #16] + vldr d29, [r0, #24] + vst1.64 {d30-d31}, [r0:64] + vstr d28, [r0, #16] + vstr d29, [r0, #24] +there is hint [r0:64], so does it requires 64-bit alignment. +*/ +#define LOAD_128_32(_p) (*(const v128 *)(const void *)(_p)) +#define LOAD_128_8(_p) vreinterpretq_u8_u32(*(const v128 *)(const void *)(_p)) +#define STORE_128_32(_p, _v) *(v128 *)(void *)(_p) = (_v) +#endif + +#define LOAD_SHUFFLE(m, k) \ + m = vreinterpretq_u32_u8( \ + MY_rev32_for_LE( \ + LOAD_128_8(data + (k) * 16))); \ + +// K array must be aligned for 16-bytes at least. +extern +MY_ALIGN(64) +const UInt32 SHA256_K_ARRAY[64]; +#define K SHA256_K_ARRAY + +#define SHA256_SU0(dest, src) dest = vsha256su0q_u32(dest, src); +#define SHA256_SU1(dest, src2, src3) dest = vsha256su1q_u32(dest, src2, src3); + +#define SM1(m0, m1, m2, m3) SHA256_SU0(m3, m0) +#define SM2(m0, m1, m2, m3) SHA256_SU1(m2, m0, m1) +#define NNN(m0, m1, m2, m3) + +#define R4(k, m0, m1, m2, m3, OP0, OP1) \ + msg = vaddq_u32(m0, *(const v128 *) (const void *) &K[(k) * 4]); \ + tmp = state0; \ + state0 = vsha256hq_u32( state0, state1, msg ); \ + state1 = vsha256h2q_u32( state1, tmp, msg ); \ + OP0(m0, m1, m2, m3); \ + OP1(m0, m1, m2, m3); \ + + +#define R16(k, OP0, OP1, OP2, OP3, OP4, OP5, OP6, OP7) \ + R4 ( (k)*4+0, m0, m1, m2, m3, OP0, OP1 ) \ + R4 ( (k)*4+1, m1, m2, m3, m0, OP2, OP3 ) \ + R4 ( (k)*4+2, m2, m3, m0, m1, OP4, OP5 ) \ + R4 ( (k)*4+3, m3, m0, m1, m2, OP6, OP7 ) \ + + +void Z7_FASTCALL Sha256_UpdateBlocks_HW(UInt32 state[8], const Byte *data, size_t numBlocks); +#ifdef ATTRIB_SHA +ATTRIB_SHA +#endif +void Z7_FASTCALL Sha256_UpdateBlocks_HW(UInt32 state[8], const Byte *data, size_t numBlocks) +{ + v128 state0, state1; + + if (numBlocks == 0) + return; + + state0 = LOAD_128_32(&state[0]); + state1 = LOAD_128_32(&state[4]); + + do + { + v128 state0_save, state1_save; + v128 m0, m1, m2, m3; + v128 msg, tmp; + + state0_save = state0; + state1_save = state1; + + LOAD_SHUFFLE (m0, 0) + LOAD_SHUFFLE (m1, 1) + LOAD_SHUFFLE (m2, 2) + LOAD_SHUFFLE (m3, 3) + + R16 ( 0, NNN, NNN, SM1, NNN, SM1, SM2, SM1, SM2 ) + R16 ( 1, SM1, SM2, SM1, SM2, SM1, SM2, SM1, SM2 ) + R16 ( 2, SM1, SM2, SM1, SM2, SM1, SM2, SM1, SM2 ) + R16 ( 3, SM1, SM2, NNN, SM2, NNN, NNN, NNN, NNN ) + + state0 = vaddq_u32(state0, state0_save); + state1 = vaddq_u32(state1, state1_save); + + data += 64; + } + while (--numBlocks); + + STORE_128_32(&state[0], state0); + STORE_128_32(&state[4], state1); +} + +#endif // USE_HW_SHA + +#endif // MY_CPU_ARM_OR_ARM64 + + +#if !defined(USE_HW_SHA) && defined(Z7_USE_HW_SHA_STUB) +// #error Stop_Compiling_UNSUPPORTED_SHA +// #include +// We can compile this file with another C compiler, +// or we can compile asm version. +// So we can generate real code instead of this stub function. +// #include "Sha256.h" +// #if defined(_MSC_VER) +#pragma message("Sha256 HW-SW stub was used") +// #endif +void Z7_FASTCALL Sha256_UpdateBlocks (UInt32 state[8], const Byte *data, size_t numBlocks); +void Z7_FASTCALL Sha256_UpdateBlocks_HW(UInt32 state[8], const Byte *data, size_t numBlocks); +void Z7_FASTCALL Sha256_UpdateBlocks_HW(UInt32 state[8], const Byte *data, size_t numBlocks) +{ + Sha256_UpdateBlocks(state, data, numBlocks); + /* + UNUSED_VAR(state); + UNUSED_VAR(data); + UNUSED_VAR(numBlocks); + exit(1); + return; + */ +} +#endif + + +#undef K +#undef RND2 +#undef MY_rev32_for_LE + +#undef NNN +#undef LOAD_128 +#undef STORE_128 +#undef LOAD_SHUFFLE +#undef SM1 +#undef SM2 + + +#undef R4 +#undef R16 +#undef PREPARE_STATE +#undef USE_HW_SHA +#undef ATTRIB_SHA +#undef USE_VER_MIN +#undef Z7_USE_HW_SHA_STUB diff --git a/src/plugins/7zip/7za/c/Sha3.c b/src/plugins/7zip/7za/c/Sha3.c new file mode 100644 index 00000000..be972d61 --- /dev/null +++ b/src/plugins/7zip/7za/c/Sha3.c @@ -0,0 +1,359 @@ +/* Sha3.c -- SHA-3 Hash +: Igor Pavlov : Public domain +This code is based on public domain code from Wei Dai's Crypto++ library. */ + +#include "Precomp.h" + +#include + +#include "Sha3.h" +#include "RotateDefs.h" +#include "CpuArch.h" + +#define U64C(x) UINT64_CONST(x) + +static +MY_ALIGN(64) +const UInt64 SHA3_K_ARRAY[24] = +{ + U64C(0x0000000000000001), U64C(0x0000000000008082), + U64C(0x800000000000808a), U64C(0x8000000080008000), + U64C(0x000000000000808b), U64C(0x0000000080000001), + U64C(0x8000000080008081), U64C(0x8000000000008009), + U64C(0x000000000000008a), U64C(0x0000000000000088), + U64C(0x0000000080008009), U64C(0x000000008000000a), + U64C(0x000000008000808b), U64C(0x800000000000008b), + U64C(0x8000000000008089), U64C(0x8000000000008003), + U64C(0x8000000000008002), U64C(0x8000000000000080), + U64C(0x000000000000800a), U64C(0x800000008000000a), + U64C(0x8000000080008081), U64C(0x8000000000008080), + U64C(0x0000000080000001), U64C(0x8000000080008008) +}; + +void Sha3_Init(CSha3 *p) +{ + p->count = 0; + memset(p->state, 0, sizeof(p->state)); +} + +#define GET_state(i, a) UInt64 a = state[i]; +#define SET_state(i, a) state[i] = a; + +#define LS_5(M, i, a0,a1,a2,a3,a4) \ + M ((i) * 5 , a0) \ + M ((i) * 5 + 1, a1) \ + M ((i) * 5 + 2, a2) \ + M ((i) * 5 + 3, a3) \ + M ((i) * 5 + 4, a4) \ + +#define LS_25(M) \ + LS_5 (M, 0, a50, a51, a52, a53, a54) \ + LS_5 (M, 1, a60, a61, a62, a63, a64) \ + LS_5 (M, 2, a70, a71, a72, a73, a74) \ + LS_5 (M, 3, a80, a81, a82, a83, a84) \ + LS_5 (M, 4, a90, a91, a92, a93, a94) \ + + +#define XOR_1(i, a0) \ + a0 ^= GetUi64(data + (i) * 8); \ + +#define XOR_4(i, a0,a1,a2,a3) \ + XOR_1 ((i) , a0); \ + XOR_1 ((i) + 1, a1); \ + XOR_1 ((i) + 2, a2); \ + XOR_1 ((i) + 3, a3); \ + +#define D(d,b1,b2) \ + d = b1 ^ Z7_ROTL64(b2, 1); + +#define D5 \ + D (d0, c4, c1) \ + D (d1, c0, c2) \ + D (d2, c1, c3) \ + D (d3, c2, c4) \ + D (d4, c3, c0) \ + +#define C0(c,a,d) \ + c = a ^ d; \ + +#define C(c,a,d,k) \ + c = a ^ d; \ + c = Z7_ROTL64(c, k); \ + +#define E4(e1,e2,e3,e4) \ + e1 = c1 ^ (~c2 & c3); \ + e2 = c2 ^ (~c3 & c4); \ + e3 = c3 ^ (~c4 & c0); \ + e4 = c4 ^ (~c0 & c1); \ + +#define CK( v0,w0, \ + v1,w1,k1, \ + v2,w2,k2, \ + v3,w3,k3, \ + v4,w4,k4, e0,e1,e2,e3,e4, keccak_c) \ + C0(c0,v0,w0) \ + C (c1,v1,w1,k1) \ + C (c2,v2,w2,k2) \ + C (c3,v3,w3,k3) \ + C (c4,v4,w4,k4) \ + e0 = c0 ^ (~c1 & c2) ^ keccak_c; \ + E4(e1,e2,e3,e4) \ + +#define CE( v0,w0,k0, \ + v1,w1,k1, \ + v2,w2,k2, \ + v3,w3,k3, \ + v4,w4,k4, e0,e1,e2,e3,e4) \ + C (c0,v0,w0,k0) \ + C (c1,v1,w1,k1) \ + C (c2,v2,w2,k2) \ + C (c3,v3,w3,k3) \ + C (c4,v4,w4,k4) \ + e0 = c0 ^ (~c1 & c2); \ + E4(e1,e2,e3,e4) \ + +// numBlocks != 0 +static +Z7_NO_INLINE +void Z7_FASTCALL Sha3_UpdateBlocks(UInt64 state[SHA3_NUM_STATE_WORDS], + const Byte *data, size_t numBlocks, size_t blockSize) +{ + LS_25 (GET_state) + + do + { + unsigned round; + XOR_4 ( 0, a50, a51, a52, a53) + XOR_4 ( 4, a54, a60, a61, a62) + XOR_1 ( 8, a63) + if (blockSize > 8 * 9) { XOR_4 ( 9, a64, a70, a71, a72) // sha3-384 + if (blockSize > 8 * 13) { XOR_4 (13, a73, a74, a80, a81) // sha3-256 + if (blockSize > 8 * 17) { XOR_1 (17, a82) // sha3-224 + if (blockSize > 8 * 18) { XOR_1 (18, a83) // shake128 + XOR_1 (19, a84) + XOR_1 (20, a90) }}}} + data += blockSize; + + for (round = 0; round < 24; round += 2) + { + UInt64 c0, c1, c2, c3, c4; + UInt64 d0, d1, d2, d3, d4; + UInt64 e50, e51, e52, e53, e54; + UInt64 e60, e61, e62, e63, e64; + UInt64 e70, e71, e72, e73, e74; + UInt64 e80, e81, e82, e83, e84; + UInt64 e90, e91, e92, e93, e94; + + c0 = a50^a60^a70^a80^a90; + c1 = a51^a61^a71^a81^a91; + c2 = a52^a62^a72^a82^a92; + c3 = a53^a63^a73^a83^a93; + c4 = a54^a64^a74^a84^a94; + D5 + CK( a50, d0, + a61, d1, 44, + a72, d2, 43, + a83, d3, 21, + a94, d4, 14, e50, e51, e52, e53, e54, SHA3_K_ARRAY[round]) + CE( a53, d3, 28, + a64, d4, 20, + a70, d0, 3, + a81, d1, 45, + a92, d2, 61, e60, e61, e62, e63, e64) + CE( a51, d1, 1, + a62, d2, 6, + a73, d3, 25, + a84, d4, 8, + a90, d0, 18, e70, e71, e72, e73, e74) + CE( a54, d4, 27, + a60, d0, 36, + a71, d1, 10, + a82, d2, 15, + a93, d3, 56, e80, e81, e82, e83, e84) + CE( a52, d2, 62, + a63, d3, 55, + a74, d4, 39, + a80, d0, 41, + a91, d1, 2, e90, e91, e92, e93, e94) + + // ---------- ROUND + 1 ---------- + + c0 = e50^e60^e70^e80^e90; + c1 = e51^e61^e71^e81^e91; + c2 = e52^e62^e72^e82^e92; + c3 = e53^e63^e73^e83^e93; + c4 = e54^e64^e74^e84^e94; + D5 + CK( e50, d0, + e61, d1, 44, + e72, d2, 43, + e83, d3, 21, + e94, d4, 14, a50, a51, a52, a53, a54, SHA3_K_ARRAY[(size_t)round + 1]) + CE( e53, d3, 28, + e64, d4, 20, + e70, d0, 3, + e81, d1, 45, + e92, d2, 61, a60, a61, a62, a63, a64) + CE( e51, d1, 1, + e62, d2, 6, + e73, d3, 25, + e84, d4, 8, + e90, d0, 18, a70, a71, a72, a73, a74) + CE (e54, d4, 27, + e60, d0, 36, + e71, d1, 10, + e82, d2, 15, + e93, d3, 56, a80, a81, a82, a83, a84) + CE (e52, d2, 62, + e63, d3, 55, + e74, d4, 39, + e80, d0, 41, + e91, d1, 2, a90, a91, a92, a93, a94) + } + } + while (--numBlocks); + + LS_25 (SET_state) +} + + +#define Sha3_UpdateBlock(p) \ + Sha3_UpdateBlocks(p->state, p->buffer, 1, p->blockSize) + +void Sha3_Update(CSha3 *p, const Byte *data, size_t size) +{ +/* + for (;;) + { + if (size == 0) + return; + unsigned cur = p->blockSize - p->count; + if (cur > size) + cur = (unsigned)size; + size -= cur; + unsigned pos = p->count; + p->count = pos + cur; + while (pos & 7) + { + if (cur == 0) + return; + Byte *pb = &(((Byte *)p->state)[pos]); + *pb = (Byte)(*pb ^ *data++); + cur--; + pos++; + } + if (cur >= 8) + { + do + { + *(UInt64 *)(void *)&(((Byte *)p->state)[pos]) ^= GetUi64(data); + data += 8; + pos += 8; + cur -= 8; + } + while (cur >= 8); + } + if (pos != p->blockSize) + { + if (cur) + { + Byte *pb = &(((Byte *)p->state)[pos]); + do + { + *pb = (Byte)(*pb ^ *data++); + pb++; + } + while (--cur); + } + return; + } + Sha3_UpdateBlock(p->state); + p->count = 0; + } +*/ + if (size == 0) + return; + { + const unsigned pos = p->count; + const unsigned num = p->blockSize - pos; + if (num > size) + { + p->count = pos + (unsigned)size; + memcpy(p->buffer + pos, data, size); + return; + } + if (pos != 0) + { + size -= num; + memcpy(p->buffer + pos, data, num); + data += num; + Sha3_UpdateBlock(p); + } + } + if (size >= p->blockSize) + { + const size_t numBlocks = size / p->blockSize; + const Byte *dataOld = data; + data += numBlocks * p->blockSize; + size = (size_t)(dataOld + size - data); + Sha3_UpdateBlocks(p->state, dataOld, numBlocks, p->blockSize); + } + p->count = (unsigned)size; + if (size) + memcpy(p->buffer, data, size); +} + + +// we support only (digestSize % 4 == 0) cases +void Sha3_Final(CSha3 *p, Byte *digest, unsigned digestSize, unsigned shake) +{ + memset(p->buffer + p->count, 0, p->blockSize - p->count); + // we write bits markers from low to higher in current byte: + // - if sha-3 : 2 bits : 0,1 + // - if shake : 4 bits : 1111 + // then we write bit 1 to same byte. + // And we write bit 1 to highest bit of last byte of block. + p->buffer[p->count] = (Byte)(shake ? 0x1f : 0x06); + // we need xor operation (^= 0x80) here because we must write 0x80 bit + // to same byte as (0x1f : 0x06), if (p->count == p->blockSize - 1) !!! + p->buffer[p->blockSize - 1] ^= 0x80; +/* + ((Byte *)p->state)[p->count] ^= (Byte)(shake ? 0x1f : 0x06); + ((Byte *)p->state)[p->blockSize - 1] ^= 0x80; +*/ + Sha3_UpdateBlock(p); +#if 1 && defined(MY_CPU_LE) + memcpy(digest, p->state, digestSize); +#else + { + const unsigned numWords = digestSize >> 3; + unsigned i; + for (i = 0; i < numWords; i++) + { + const UInt64 v = p->state[i]; + SetUi64(digest, v) + digest += 8; + } + if (digestSize & 4) // for SHA3-224 + { + const UInt32 v = (UInt32)p->state[numWords]; + SetUi32(digest, v) + } + } +#endif + Sha3_Init(p); +} + +#undef GET_state +#undef SET_state +#undef LS_5 +#undef LS_25 +#undef XOR_1 +#undef XOR_4 +#undef D +#undef D5 +#undef C0 +#undef C +#undef E4 +#undef CK +#undef CE diff --git a/src/plugins/7zip/7za/c/Sha3.h b/src/plugins/7zip/7za/c/Sha3.h new file mode 100644 index 00000000..c5909c94 --- /dev/null +++ b/src/plugins/7zip/7za/c/Sha3.h @@ -0,0 +1,36 @@ +/* Sha3.h -- SHA-3 Hash +: Igor Pavlov : Public domain */ + +#ifndef ZIP7_INC_MD5_H +#define ZIP7_INC_MD5_H + +#include "7zTypes.h" + +EXTERN_C_BEGIN + +#define SHA3_NUM_STATE_WORDS 25 + +#define SHA3_BLOCK_SIZE_FROM_DIGEST_SIZE(digestSize) \ + (SHA3_NUM_STATE_WORDS * 8 - (digestSize) * 2) + +typedef struct +{ + UInt32 count; // < blockSize + UInt32 blockSize; // <= SHA3_NUM_STATE_WORDS * 8 + UInt64 _pad1[3]; + // we want 32-bytes alignment here + UInt64 state[SHA3_NUM_STATE_WORDS]; + UInt64 _pad2[3]; + // we want 64-bytes alignment here + Byte buffer[SHA3_NUM_STATE_WORDS * 8]; // last bytes will be unused with predefined blockSize values +} CSha3; + +#define Sha3_SET_blockSize(p, blockSize) { (p)->blockSize = (blockSize); } + +void Sha3_Init(CSha3 *p); +void Sha3_Update(CSha3 *p, const Byte *data, size_t size); +void Sha3_Final(CSha3 *p, Byte *digest, unsigned digestSize, unsigned shake); + +EXTERN_C_END + +#endif diff --git a/src/plugins/7zip/7za/c/Sha512.c b/src/plugins/7zip/7za/c/Sha512.c new file mode 100644 index 00000000..f0787fd8 --- /dev/null +++ b/src/plugins/7zip/7za/c/Sha512.c @@ -0,0 +1,711 @@ +/* Sha512.c -- SHA-512 Hash +: Igor Pavlov : Public domain +This code is based on public domain code from Wei Dai's Crypto++ library. */ + +#include "Precomp.h" + +#include + +#include "Sha512.h" +#include "RotateDefs.h" +#include "CpuArch.h" + +#ifdef MY_CPU_X86_OR_AMD64 + #if defined(Z7_LLVM_CLANG_VERSION) && (Z7_LLVM_CLANG_VERSION >= 170001) \ + || defined(Z7_APPLE_CLANG_VERSION) && (Z7_APPLE_CLANG_VERSION >= 170001) \ + || defined(Z7_GCC_VERSION) && (Z7_GCC_VERSION >= 140000) \ + || defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 2400) && (__INTEL_COMPILER <= 9900) \ + || defined(_MSC_VER) && (_MSC_VER >= 1940) + #define Z7_COMPILER_SHA512_SUPPORTED + #endif +#elif defined(MY_CPU_ARM64) && defined(MY_CPU_LE) + #if defined(__ARM_FEATURE_SHA512) + #define Z7_COMPILER_SHA512_SUPPORTED + #else + #if (defined(Z7_CLANG_VERSION) && (Z7_CLANG_VERSION >= 130000) \ + || defined(__GNUC__) && (__GNUC__ >= 9) \ + ) \ + || defined(Z7_MSC_VER_ORIGINAL) && (_MSC_VER >= 1940) // fix it + #define Z7_COMPILER_SHA512_SUPPORTED + #endif + #endif +#endif + + + + + + + + + + + + + + +void Z7_FASTCALL Sha512_UpdateBlocks(UInt64 state[8], const Byte *data, size_t numBlocks); + +#ifdef Z7_COMPILER_SHA512_SUPPORTED + void Z7_FASTCALL Sha512_UpdateBlocks_HW(UInt64 state[8], const Byte *data, size_t numBlocks); + + static SHA512_FUNC_UPDATE_BLOCKS g_SHA512_FUNC_UPDATE_BLOCKS = Sha512_UpdateBlocks; + static SHA512_FUNC_UPDATE_BLOCKS g_SHA512_FUNC_UPDATE_BLOCKS_HW; + + #define SHA512_UPDATE_BLOCKS(p) p->v.vars.func_UpdateBlocks +#else + #define SHA512_UPDATE_BLOCKS(p) Sha512_UpdateBlocks +#endif + + +BoolInt Sha512_SetFunction(CSha512 *p, unsigned algo) +{ + SHA512_FUNC_UPDATE_BLOCKS func = Sha512_UpdateBlocks; + + #ifdef Z7_COMPILER_SHA512_SUPPORTED + if (algo != SHA512_ALGO_SW) + { + if (algo == SHA512_ALGO_DEFAULT) + func = g_SHA512_FUNC_UPDATE_BLOCKS; + else + { + if (algo != SHA512_ALGO_HW) + return False; + func = g_SHA512_FUNC_UPDATE_BLOCKS_HW; + if (!func) + return False; + } + } + #else + if (algo > 1) + return False; + #endif + + p->v.vars.func_UpdateBlocks = func; + return True; +} + + +/* define it for speed optimization */ + +#if 0 // 1 for size optimization + #define STEP_PRE 1 + #define STEP_MAIN 1 +#else + #define STEP_PRE 2 + #define STEP_MAIN 4 + // #define Z7_SHA512_UNROLL +#endif + +#undef Z7_SHA512_BIG_W +#if STEP_MAIN != 16 + #define Z7_SHA512_BIG_W +#endif + + +#define U64C(x) UINT64_CONST(x) + +static MY_ALIGN(64) const UInt64 SHA512_INIT_ARRAYS[4][8] = { +{ U64C(0x8c3d37c819544da2), U64C(0x73e1996689dcd4d6), U64C(0x1dfab7ae32ff9c82), U64C(0x679dd514582f9fcf), + U64C(0x0f6d2b697bd44da8), U64C(0x77e36f7304c48942), U64C(0x3f9d85a86a1d36c8), U64C(0x1112e6ad91d692a1) +}, +{ U64C(0x22312194fc2bf72c), U64C(0x9f555fa3c84c64c2), U64C(0x2393b86b6f53b151), U64C(0x963877195940eabd), + U64C(0x96283ee2a88effe3), U64C(0xbe5e1e2553863992), U64C(0x2b0199fc2c85b8aa), U64C(0x0eb72ddc81c52ca2) +}, +{ U64C(0xcbbb9d5dc1059ed8), U64C(0x629a292a367cd507), U64C(0x9159015a3070dd17), U64C(0x152fecd8f70e5939), + U64C(0x67332667ffc00b31), U64C(0x8eb44a8768581511), U64C(0xdb0c2e0d64f98fa7), U64C(0x47b5481dbefa4fa4) +}, +{ U64C(0x6a09e667f3bcc908), U64C(0xbb67ae8584caa73b), U64C(0x3c6ef372fe94f82b), U64C(0xa54ff53a5f1d36f1), + U64C(0x510e527fade682d1), U64C(0x9b05688c2b3e6c1f), U64C(0x1f83d9abfb41bd6b), U64C(0x5be0cd19137e2179) +}}; + +void Sha512_InitState(CSha512 *p, unsigned digestSize) +{ + p->v.vars.count = 0; + memcpy(p->state, SHA512_INIT_ARRAYS[(size_t)(digestSize >> 4) - 1], sizeof(p->state)); +} + +void Sha512_Init(CSha512 *p, unsigned digestSize) +{ + p->v.vars.func_UpdateBlocks = + #ifdef Z7_COMPILER_SHA512_SUPPORTED + g_SHA512_FUNC_UPDATE_BLOCKS; + #else + NULL; + #endif + Sha512_InitState(p, digestSize); +} + +#define S0(x) (Z7_ROTR64(x,28) ^ Z7_ROTR64(x,34) ^ Z7_ROTR64(x,39)) +#define S1(x) (Z7_ROTR64(x,14) ^ Z7_ROTR64(x,18) ^ Z7_ROTR64(x,41)) +#define s0(x) (Z7_ROTR64(x, 1) ^ Z7_ROTR64(x, 8) ^ (x >> 7)) +#define s1(x) (Z7_ROTR64(x,19) ^ Z7_ROTR64(x,61) ^ (x >> 6)) + +#define Ch(x,y,z) (z^(x&(y^z))) +#define Maj(x,y,z) ((x&y)|(z&(x|y))) + + +#define W_PRE(i) (W[(i) + (size_t)(j)] = GetBe64(data + ((size_t)(j) + i) * 8)) + +#define blk2_main(j, i) s1(w(j, (i)-2)) + w(j, (i)-7) + s0(w(j, (i)-15)) + +#ifdef Z7_SHA512_BIG_W + // we use +i instead of +(i) to change the order to solve CLANG compiler warning for signed/unsigned. + #define w(j, i) W[(size_t)(j) + i] + #define blk2(j, i) (w(j, i) = w(j, (i)-16) + blk2_main(j, i)) +#else + #if STEP_MAIN == 16 + #define w(j, i) W[(i) & 15] + #else + #define w(j, i) W[((size_t)(j) + (i)) & 15] + #endif + #define blk2(j, i) (w(j, i) += blk2_main(j, i)) +#endif + +#define W_MAIN(i) blk2(j, i) + + +#define T1(wx, i) \ + tmp = h + S1(e) + Ch(e,f,g) + K[(i)+(size_t)(j)] + wx(i); \ + h = g; \ + g = f; \ + f = e; \ + e = d + tmp; \ + tmp += S0(a) + Maj(a, b, c); \ + d = c; \ + c = b; \ + b = a; \ + a = tmp; \ + +#define R1_PRE(i) T1( W_PRE, i) +#define R1_MAIN(i) T1( W_MAIN, i) + +#if (!defined(Z7_SHA512_UNROLL) || STEP_MAIN < 8) && (STEP_MAIN >= 4) +#define R2_MAIN(i) \ + R1_MAIN(i) \ + R1_MAIN(i + 1) \ + +#endif + + + +#if defined(Z7_SHA512_UNROLL) && STEP_MAIN >= 8 + +#define T4( a,b,c,d,e,f,g,h, wx, i) \ + h += S1(e) + Ch(e,f,g) + K[(i)+(size_t)(j)] + wx(i); \ + tmp = h; \ + h += d; \ + d = tmp + S0(a) + Maj(a, b, c); \ + +#define R4( wx, i) \ + T4 ( a,b,c,d,e,f,g,h, wx, (i )); \ + T4 ( d,a,b,c,h,e,f,g, wx, (i+1)); \ + T4 ( c,d,a,b,g,h,e,f, wx, (i+2)); \ + T4 ( b,c,d,a,f,g,h,e, wx, (i+3)); \ + +#define R4_PRE(i) R4( W_PRE, i) +#define R4_MAIN(i) R4( W_MAIN, i) + + +#define T8( a,b,c,d,e,f,g,h, wx, i) \ + h += S1(e) + Ch(e,f,g) + K[(i)+(size_t)(j)] + wx(i); \ + d += h; \ + h += S0(a) + Maj(a, b, c); \ + +#define R8( wx, i) \ + T8 ( a,b,c,d,e,f,g,h, wx, i ); \ + T8 ( h,a,b,c,d,e,f,g, wx, i+1); \ + T8 ( g,h,a,b,c,d,e,f, wx, i+2); \ + T8 ( f,g,h,a,b,c,d,e, wx, i+3); \ + T8 ( e,f,g,h,a,b,c,d, wx, i+4); \ + T8 ( d,e,f,g,h,a,b,c, wx, i+5); \ + T8 ( c,d,e,f,g,h,a,b, wx, i+6); \ + T8 ( b,c,d,e,f,g,h,a, wx, i+7); \ + +#define R8_PRE(i) R8( W_PRE, i) +#define R8_MAIN(i) R8( W_MAIN, i) + +#endif + + +extern +MY_ALIGN(64) const UInt64 SHA512_K_ARRAY[80]; +MY_ALIGN(64) const UInt64 SHA512_K_ARRAY[80] = { + U64C(0x428a2f98d728ae22), U64C(0x7137449123ef65cd), U64C(0xb5c0fbcfec4d3b2f), U64C(0xe9b5dba58189dbbc), + U64C(0x3956c25bf348b538), U64C(0x59f111f1b605d019), U64C(0x923f82a4af194f9b), U64C(0xab1c5ed5da6d8118), + U64C(0xd807aa98a3030242), U64C(0x12835b0145706fbe), U64C(0x243185be4ee4b28c), U64C(0x550c7dc3d5ffb4e2), + U64C(0x72be5d74f27b896f), U64C(0x80deb1fe3b1696b1), U64C(0x9bdc06a725c71235), U64C(0xc19bf174cf692694), + U64C(0xe49b69c19ef14ad2), U64C(0xefbe4786384f25e3), U64C(0x0fc19dc68b8cd5b5), U64C(0x240ca1cc77ac9c65), + U64C(0x2de92c6f592b0275), U64C(0x4a7484aa6ea6e483), U64C(0x5cb0a9dcbd41fbd4), U64C(0x76f988da831153b5), + U64C(0x983e5152ee66dfab), U64C(0xa831c66d2db43210), U64C(0xb00327c898fb213f), U64C(0xbf597fc7beef0ee4), + U64C(0xc6e00bf33da88fc2), U64C(0xd5a79147930aa725), U64C(0x06ca6351e003826f), U64C(0x142929670a0e6e70), + U64C(0x27b70a8546d22ffc), U64C(0x2e1b21385c26c926), U64C(0x4d2c6dfc5ac42aed), U64C(0x53380d139d95b3df), + U64C(0x650a73548baf63de), U64C(0x766a0abb3c77b2a8), U64C(0x81c2c92e47edaee6), U64C(0x92722c851482353b), + U64C(0xa2bfe8a14cf10364), U64C(0xa81a664bbc423001), U64C(0xc24b8b70d0f89791), U64C(0xc76c51a30654be30), + U64C(0xd192e819d6ef5218), U64C(0xd69906245565a910), U64C(0xf40e35855771202a), U64C(0x106aa07032bbd1b8), + U64C(0x19a4c116b8d2d0c8), U64C(0x1e376c085141ab53), U64C(0x2748774cdf8eeb99), U64C(0x34b0bcb5e19b48a8), + U64C(0x391c0cb3c5c95a63), U64C(0x4ed8aa4ae3418acb), U64C(0x5b9cca4f7763e373), U64C(0x682e6ff3d6b2b8a3), + U64C(0x748f82ee5defb2fc), U64C(0x78a5636f43172f60), U64C(0x84c87814a1f0ab72), U64C(0x8cc702081a6439ec), + U64C(0x90befffa23631e28), U64C(0xa4506cebde82bde9), U64C(0xbef9a3f7b2c67915), U64C(0xc67178f2e372532b), + U64C(0xca273eceea26619c), U64C(0xd186b8c721c0c207), U64C(0xeada7dd6cde0eb1e), U64C(0xf57d4f7fee6ed178), + U64C(0x06f067aa72176fba), U64C(0x0a637dc5a2c898a6), U64C(0x113f9804bef90dae), U64C(0x1b710b35131c471b), + U64C(0x28db77f523047d84), U64C(0x32caab7b40c72493), U64C(0x3c9ebe0a15c9bebc), U64C(0x431d67c49c100d4c), + U64C(0x4cc5d4becb3e42b6), U64C(0x597f299cfc657e2a), U64C(0x5fcb6fab3ad6faec), U64C(0x6c44198c4a475817) +}; + +#define K SHA512_K_ARRAY + +Z7_NO_INLINE +void Z7_FASTCALL Sha512_UpdateBlocks(UInt64 state[8], const Byte *data, size_t numBlocks) +{ + UInt64 W +#ifdef Z7_SHA512_BIG_W + [80]; +#else + [16]; +#endif + unsigned j; + UInt64 a,b,c,d,e,f,g,h; +#if !defined(Z7_SHA512_UNROLL) || (STEP_MAIN <= 4) || (STEP_PRE <= 4) + UInt64 tmp; +#endif + + if (numBlocks == 0) return; + + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + f = state[5]; + g = state[6]; + h = state[7]; + + do + { + + for (j = 0; j < 16; j += STEP_PRE) + { + #if STEP_PRE > 4 + + #if STEP_PRE < 8 + R4_PRE(0); + #else + R8_PRE(0); + #if STEP_PRE == 16 + R8_PRE(8); + #endif + #endif + + #else + + R1_PRE(0) + #if STEP_PRE >= 2 + R1_PRE(1) + #if STEP_PRE >= 4 + R1_PRE(2) + R1_PRE(3) + #endif + #endif + + #endif + } + + for (j = 16; j < 80; j += STEP_MAIN) + { + #if defined(Z7_SHA512_UNROLL) && STEP_MAIN >= 8 + + #if STEP_MAIN < 8 + R4_MAIN(0) + #else + R8_MAIN(0) + #if STEP_MAIN == 16 + R8_MAIN(8) + #endif + #endif + + #else + + R1_MAIN(0) + #if STEP_MAIN >= 2 + R1_MAIN(1) + #if STEP_MAIN >= 4 + R2_MAIN(2) + #if STEP_MAIN >= 8 + R2_MAIN(4) + R2_MAIN(6) + #if STEP_MAIN >= 16 + R2_MAIN(8) + R2_MAIN(10) + R2_MAIN(12) + R2_MAIN(14) + #endif + #endif + #endif + #endif + #endif + } + + a += state[0]; state[0] = a; + b += state[1]; state[1] = b; + c += state[2]; state[2] = c; + d += state[3]; state[3] = d; + e += state[4]; state[4] = e; + f += state[5]; state[5] = f; + g += state[6]; state[6] = g; + h += state[7]; state[7] = h; + + data += SHA512_BLOCK_SIZE; + } + while (--numBlocks); +} + + +#define Sha512_UpdateBlock(p) SHA512_UPDATE_BLOCKS(p)(p->state, p->buffer, 1) + +void Sha512_Update(CSha512 *p, const Byte *data, size_t size) +{ + if (size == 0) + return; + { + const unsigned pos = (unsigned)p->v.vars.count & (SHA512_BLOCK_SIZE - 1); + const unsigned num = SHA512_BLOCK_SIZE - pos; + p->v.vars.count += size; + if (num > size) + { + memcpy(p->buffer + pos, data, size); + return; + } + if (pos != 0) + { + size -= num; + memcpy(p->buffer + pos, data, num); + data += num; + Sha512_UpdateBlock(p); + } + } + { + const size_t numBlocks = size >> 7; + // if (numBlocks) + SHA512_UPDATE_BLOCKS(p)(p->state, data, numBlocks); + size &= SHA512_BLOCK_SIZE - 1; + if (size == 0) + return; + data += (numBlocks << 7); + memcpy(p->buffer, data, size); + } +} + + +void Sha512_Final(CSha512 *p, Byte *digest, unsigned digestSize) +{ + unsigned pos = (unsigned)p->v.vars.count & (SHA512_BLOCK_SIZE - 1); + p->buffer[pos++] = 0x80; + if (pos > (SHA512_BLOCK_SIZE - 8 * 2)) + { + while (pos != SHA512_BLOCK_SIZE) { p->buffer[pos++] = 0; } + // memset(&p->buf.buffer[pos], 0, SHA512_BLOCK_SIZE - pos); + Sha512_UpdateBlock(p); + pos = 0; + } + memset(&p->buffer[pos], 0, (SHA512_BLOCK_SIZE - 8 * 2) - pos); + { + const UInt64 numBits = p->v.vars.count << 3; + SetBe64(p->buffer + SHA512_BLOCK_SIZE - 8 * 2, 0) // = (p->v.vars.count >> (64 - 3)); (high 64-bits) + SetBe64(p->buffer + SHA512_BLOCK_SIZE - 8 * 1, numBits) + } + Sha512_UpdateBlock(p); +#if 1 && defined(MY_CPU_BE) + memcpy(digest, p->state, digestSize); +#else + { + const unsigned numWords = digestSize >> 3; + unsigned i; + for (i = 0; i < numWords; i++) + { + const UInt64 v = p->state[i]; + SetBe64(digest, v) + digest += 8; + } + if (digestSize & 4) // digestSize == SHA512_224_DIGEST_SIZE + { + const UInt32 v = (UInt32)((p->state[numWords]) >> 32); + SetBe32(digest, v) + } + } +#endif + Sha512_InitState(p, digestSize); +} + + + +// #define Z7_SHA512_PROBE_DEBUG // for debug + +#if defined(Z7_SHA512_PROBE_DEBUG) || defined(Z7_COMPILER_SHA512_SUPPORTED) + +#if defined(Z7_SHA512_PROBE_DEBUG) \ + || defined(_WIN32) && defined(MY_CPU_ARM64) +#ifndef Z7_SHA512_USE_PROBE +#define Z7_SHA512_USE_PROBE +#endif +#endif + +#ifdef Z7_SHA512_USE_PROBE + +#ifdef Z7_SHA512_PROBE_DEBUG +#include +#define PRF(x) x +#else +#define PRF(x) +#endif + +#if 0 || !defined(_MSC_VER) // 1 || : for debug LONGJMP mode +// MINGW doesn't support __try. So we use signal() / longjmp(). +// Note: signal() / longjmp() probably is not thread-safe. +// So we must call Sha512Prepare() from main thread at program start. +#ifndef Z7_SHA512_USE_LONGJMP +#define Z7_SHA512_USE_LONGJMP +#endif +#endif + +#ifdef Z7_SHA512_USE_LONGJMP +#include +#include +static jmp_buf g_Sha512_jmp_buf; +// static int g_Sha512_Unsupported; + +#if defined(__GNUC__) && (__GNUC__ >= 8) \ + || defined(__clang__) && (__clang_major__ >= 3) + __attribute__((noreturn)) +#endif +static void Z7_CDECL Sha512_signal_Handler(int v) +{ + PRF(printf("======== Sha512_signal_Handler = %x\n", (unsigned)v);) + // g_Sha512_Unsupported = 1; + longjmp(g_Sha512_jmp_buf, 1); +} +#endif // Z7_SHA512_USE_LONGJMP + + +#if defined(_WIN32) +#include "7zWindows.h" +#endif + +#if defined(MY_CPU_ARM64) +// #define Z7_SHA512_USE_SIMPLIFIED_PROBE // for debug +#endif + +#ifdef Z7_SHA512_USE_SIMPLIFIED_PROBE +#include +#if defined(__clang__) + __attribute__((__target__("sha3"))) +#elif !defined(_MSC_VER) + __attribute__((__target__("arch=armv8.2-a+sha3"))) +#endif +#endif +static BoolInt CPU_IsSupported_SHA512_Probe(void) +{ + PRF(printf("\n== CPU_IsSupported_SHA512_Probe\n");) +#if defined(_WIN32) && defined(MY_CPU_ARM64) + // we have no SHA512 flag for IsProcessorFeaturePresent() still. + if (!CPU_IsSupported_CRYPTO()) + return False; + PRF(printf("==== Registry check\n");) + { + // we can't read ID_AA64ISAR0_EL1 register from application. + // but ID_AA64ISAR0_EL1 register is mapped to "CP 4030" registry value. + HKEY key = NULL; + LONG res = RegOpenKeyEx(HKEY_LOCAL_MACHINE, + TEXT("HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0"), + 0, KEY_READ, &key); + if (res != ERROR_SUCCESS) + return False; + { + DWORD type = 0; + DWORD count = sizeof(UInt64); + UInt64 val = 0; + res = RegQueryValueEx(key, TEXT("CP 4030"), NULL, + &type, (LPBYTE)&val, &count); + RegCloseKey(key); + if (res != ERROR_SUCCESS + || type != REG_QWORD + || count != sizeof(UInt64) + || ((unsigned)(val >> 12) & 0xf) != 2) + return False; + // we parse SHA2 field of ID_AA64ISAR0_EL1 register: + // 0 : No SHA2 instructions implemented + // 1 : SHA256 implemented + // 2 : SHA256 and SHA512 implemented + } + } +#endif // defined(_WIN32) && defined(MY_CPU_ARM64) + + +#if 1 // 0 for debug to disable SHA512 PROBE code + +/* +----- SHA512 PROBE ----- + +We suppose that "CP 4030" registry reading is enough. +But we use additional SHA512 PROBE code, because +we can catch exception here, and we don't catch exceptions, +if we call Sha512 functions from main code. + +NOTE: arm64 PROBE code doesn't work, if we call it via Wine in linux-arm64. +The program just stops. +Also x64 version of PROBE code doesn't work, if we run it via Intel SDE emulator +without SHA512 support (-skl switch), +The program stops, and we have message from SDE: + TID 0 SDE-ERROR: Executed instruction not valid for specified chip (SKYLAKE): vsha512msg1 +But we still want to catch that exception instead of process stopping. +Does this PROBE code work in native Windows-arm64 (with/without sha512 hw instructions)? +Are there any ways to fix the problems with arm64-wine and x64-SDE cases? +*/ + + PRF(printf("==== CPU_IsSupported_SHA512 PROBE\n");) + { + BoolInt isSupported = False; +#ifdef Z7_SHA512_USE_LONGJMP + void (Z7_CDECL *signal_prev)(int); + /* + if (g_Sha512_Unsupported) + { + PRF(printf("==== g_Sha512_Unsupported\n");) + return False; + } + */ + printf("====== signal(SIGILL)\n"); + signal_prev = signal(SIGILL, Sha512_signal_Handler); + if (signal_prev == SIG_ERR) + { + PRF(printf("====== signal fail\n");) + return False; + } + // PRF(printf("==== signal_prev = %p\n", (void *)signal_prev);) + // docs: Before the specified function is executed, + // the value of func is set to SIG_DFL. + // So we can exit if (setjmp(g_Sha512_jmp_buf) != 0). + PRF(printf("====== setjmp\n");) + if (!setjmp(g_Sha512_jmp_buf)) +#else // Z7_SHA512_USE_LONGJMP + +#ifdef _MSC_VER +#ifdef __clang_major__ + #pragma GCC diagnostic ignored "-Wlanguage-extension-token" +#endif + __try +#endif +#endif // Z7_SHA512_USE_LONGJMP + + { +#if defined(Z7_COMPILER_SHA512_SUPPORTED) +#ifdef Z7_SHA512_USE_SIMPLIFIED_PROBE + // simplified sha512 check for arm64: + const uint64x2_t a = vdupq_n_u64(1); + const uint64x2_t b = vsha512hq_u64(a, a, a); + PRF(printf("======== vsha512hq_u64 probe\n");) + if ((UInt32)vgetq_lane_u64(b, 0) == 0x11800002) +#else + MY_ALIGN(16) + UInt64 temp[SHA512_NUM_DIGEST_WORDS + SHA512_NUM_BLOCK_WORDS]; + memset(temp, 0x5a, sizeof(temp)); + PRF(printf("======== Sha512_UpdateBlocks_HW\n");) + Sha512_UpdateBlocks_HW(temp, + (const Byte *)(const void *)(temp + SHA512_NUM_DIGEST_WORDS), 1); + // PRF(printf("======== t = %x\n", (UInt32)temp[0]);) + if ((UInt32)temp[0] == 0xa33cfdf7) +#endif + { + PRF(printf("======== PROBE SHA512: SHA512 is supported\n");) + isSupported = True; + } +#else // Z7_COMPILER_SHA512_SUPPORTED + // for debug : we generate bad instrction or raise exception. + // __except() doesn't catch raise() calls. +#ifdef Z7_SHA512_USE_LONGJMP + PRF(printf("====== raise(SIGILL)\n");) + raise(SIGILL); +#else +#if defined(_MSC_VER) && defined(MY_CPU_X86) + __asm ud2 +#endif +#endif // Z7_SHA512_USE_LONGJMP +#endif // Z7_COMPILER_SHA512_SUPPORTED + } + +#ifdef Z7_SHA512_USE_LONGJMP + PRF(printf("====== restore signal SIGILL\n");) + signal(SIGILL, signal_prev); +#elif _MSC_VER + __except (EXCEPTION_EXECUTE_HANDLER) + { + PRF(printf("==== CPU_IsSupported_SHA512 __except(EXCEPTION_EXECUTE_HANDLER)\n");) + } +#endif + PRF(printf("== return (sha512 supported) = %d\n", isSupported);) + return isSupported; + } +#else + // without SHA512 PROBE code + return True; +#endif +} + +#endif // Z7_SHA512_USE_PROBE +#endif // defined(Z7_SHA512_PROBE_DEBUG) || defined(Z7_COMPILER_SHA512_SUPPORTED) + + +void Sha512Prepare(void) +{ +#ifdef Z7_COMPILER_SHA512_SUPPORTED + SHA512_FUNC_UPDATE_BLOCKS f, f_hw; + f = Sha512_UpdateBlocks; + f_hw = NULL; +#ifdef Z7_SHA512_USE_PROBE + if (CPU_IsSupported_SHA512_Probe()) +#elif defined(MY_CPU_X86_OR_AMD64) + if (CPU_IsSupported_SHA512() && CPU_IsSupported_AVX2()) +#else + if (CPU_IsSupported_SHA512()) +#endif + { + // printf("\n========== HW SHA512 ======== \n"); + f = f_hw = Sha512_UpdateBlocks_HW; + } + g_SHA512_FUNC_UPDATE_BLOCKS = f; + g_SHA512_FUNC_UPDATE_BLOCKS_HW = f_hw; +#elif defined(Z7_SHA512_PROBE_DEBUG) + CPU_IsSupported_SHA512_Probe(); // for debug +#endif +} + + +#undef K +#undef S0 +#undef S1 +#undef s0 +#undef s1 +#undef Ch +#undef Maj +#undef W_MAIN +#undef W_PRE +#undef w +#undef blk2_main +#undef blk2 +#undef T1 +#undef T4 +#undef T8 +#undef R1_PRE +#undef R1_MAIN +#undef R2_MAIN +#undef R4 +#undef R4_PRE +#undef R4_MAIN +#undef R8 +#undef R8_PRE +#undef R8_MAIN +#undef STEP_PRE +#undef STEP_MAIN +#undef Z7_SHA512_BIG_W +#undef Z7_SHA512_UNROLL +#undef Z7_COMPILER_SHA512_SUPPORTED diff --git a/src/plugins/7zip/7za/c/Sha512.h b/src/plugins/7zip/7za/c/Sha512.h new file mode 100644 index 00000000..1f3a4d1e --- /dev/null +++ b/src/plugins/7zip/7za/c/Sha512.h @@ -0,0 +1,86 @@ +/* Sha512.h -- SHA-512 Hash +: Igor Pavlov : Public domain */ + +#ifndef ZIP7_INC_SHA512_H +#define ZIP7_INC_SHA512_H + +#include "7zTypes.h" + +EXTERN_C_BEGIN + +#define SHA512_NUM_BLOCK_WORDS 16 +#define SHA512_NUM_DIGEST_WORDS 8 + +#define SHA512_BLOCK_SIZE (SHA512_NUM_BLOCK_WORDS * 8) +#define SHA512_DIGEST_SIZE (SHA512_NUM_DIGEST_WORDS * 8) +#define SHA512_224_DIGEST_SIZE (224 / 8) +#define SHA512_256_DIGEST_SIZE (256 / 8) +#define SHA512_384_DIGEST_SIZE (384 / 8) + +typedef void (Z7_FASTCALL *SHA512_FUNC_UPDATE_BLOCKS)(UInt64 state[8], const Byte *data, size_t numBlocks); + +/* + if (the system supports different SHA512 code implementations) + { + (CSha512::func_UpdateBlocks) will be used + (CSha512::func_UpdateBlocks) can be set by + Sha512_Init() - to default (fastest) + Sha512_SetFunction() - to any algo + } + else + { + (CSha512::func_UpdateBlocks) is ignored. + } +*/ + +typedef struct +{ + union + { + struct + { + SHA512_FUNC_UPDATE_BLOCKS func_UpdateBlocks; + UInt64 count; + } vars; + UInt64 _pad_64bit[8]; + void *_pad_align_ptr[2]; + } v; + UInt64 state[SHA512_NUM_DIGEST_WORDS]; + + Byte buffer[SHA512_BLOCK_SIZE]; +} CSha512; + + +#define SHA512_ALGO_DEFAULT 0 +#define SHA512_ALGO_SW 1 +#define SHA512_ALGO_HW 2 + +/* +Sha512_SetFunction() +return: + 0 - (algo) value is not supported, and func_UpdateBlocks was not changed + 1 - func_UpdateBlocks was set according (algo) value. +*/ + +BoolInt Sha512_SetFunction(CSha512 *p, unsigned algo); +// we support only these (digestSize) values: 224/8, 256/8, 384/8, 512/8 +void Sha512_InitState(CSha512 *p, unsigned digestSize); +void Sha512_Init(CSha512 *p, unsigned digestSize); +void Sha512_Update(CSha512 *p, const Byte *data, size_t size); +void Sha512_Final(CSha512 *p, Byte *digest, unsigned digestSize); + + + + +// void Z7_FASTCALL Sha512_UpdateBlocks(UInt64 state[8], const Byte *data, size_t numBlocks); + +/* +call Sha512Prepare() once at program start. +It prepares all supported implementations, and detects the fastest implementation. +*/ + +void Sha512Prepare(void); + +EXTERN_C_END + +#endif diff --git a/src/plugins/7zip/7za/c/Sha512Opt.c b/src/plugins/7zip/7za/c/Sha512Opt.c new file mode 100644 index 00000000..3a138684 --- /dev/null +++ b/src/plugins/7zip/7za/c/Sha512Opt.c @@ -0,0 +1,395 @@ +/* Sha512Opt.c -- SHA-512 optimized code for SHA-512 hardware instructions +: Igor Pavlov : Public domain */ + +#include "Precomp.h" +#include "Compiler.h" +#include "CpuArch.h" + +// #define Z7_USE_HW_SHA_STUB // for debug +#ifdef MY_CPU_X86_OR_AMD64 + #if defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 2400) && (__INTEL_COMPILER <= 9900) // fix it + #define USE_HW_SHA + #elif defined(Z7_LLVM_CLANG_VERSION) && (Z7_LLVM_CLANG_VERSION >= 170001) \ + || defined(Z7_APPLE_CLANG_VERSION) && (Z7_APPLE_CLANG_VERSION >= 170001) \ + || defined(Z7_GCC_VERSION) && (Z7_GCC_VERSION >= 140000) + #define USE_HW_SHA + #if !defined(__INTEL_COMPILER) + // icc defines __GNUC__, but icc doesn't support __attribute__(__target__) + #if !defined(__SHA512__) || !defined(__AVX2__) + #define ATTRIB_SHA512 __attribute__((__target__("sha512,avx2"))) + #endif + #endif + #elif defined(Z7_MSC_VER_ORIGINAL) + #if (_MSC_VER >= 1940) + #define USE_HW_SHA + #else + // #define Z7_USE_HW_SHA_STUB + #endif + #endif +// #endif // MY_CPU_X86_OR_AMD64 +#ifndef USE_HW_SHA + // #define Z7_USE_HW_SHA_STUB // for debug +#endif + +#ifdef USE_HW_SHA + +// #pragma message("Sha512 HW") + +#include + +#if defined (__clang__) && defined(_MSC_VER) + #if !defined(__AVX__) + #include + #endif + #if !defined(__AVX2__) + #include + #endif + #if !defined(__SHA512__) + #include + #endif +#else + +#endif + +/* +SHA512 uses: +AVX: + _mm256_loadu_si256 (vmovdqu) + _mm256_storeu_si256 + _mm256_set_epi32 (unused) +AVX2: + _mm256_add_epi64 : vpaddq + _mm256_shuffle_epi8 : vpshufb + _mm256_shuffle_epi32 : pshufd + _mm256_blend_epi32 : vpblendd + _mm256_permute4x64_epi64 : vpermq : 3c + _mm256_permute2x128_si256: vperm2i128 : 3c + _mm256_extracti128_si256 : vextracti128 : 3c +SHA512: + _mm256_sha512* +*/ + +// K array must be aligned for 32-bytes at least. +// The compiler can look align attribute and selects +// vmovdqu - for code without align attribute +// vmovdqa - for code with align attribute +extern +MY_ALIGN(64) +const UInt64 SHA512_K_ARRAY[80]; +#define K SHA512_K_ARRAY + + +#define ADD_EPI64(dest, src) dest = _mm256_add_epi64(dest, src); +#define SHA512_MSG1(dest, src) dest = _mm256_sha512msg1_epi64(dest, _mm256_extracti128_si256(src, 0)); +#define SHA512_MSG2(dest, src) dest = _mm256_sha512msg2_epi64(dest, src); + +#define LOAD_SHUFFLE(m, k) \ + m = _mm256_loadu_si256((const __m256i *)(const void *)(data + (k) * 32)); \ + m = _mm256_shuffle_epi8(m, mask); \ + +#define NNN(m0, m1, m2, m3) + +#define SM1(m1, m2, m3, m0) \ + SHA512_MSG1(m0, m1); \ + +#define SM2(m2, m3, m0, m1) \ + ADD_EPI64(m0, _mm256_permute4x64_epi64(_mm256_blend_epi32(m2, m3, 3), 0x39)); \ + SHA512_MSG2(m0, m3); \ + +#define RND2(t0, t1, lane) \ + t0 = _mm256_sha512rnds2_epi64(t0, t1, _mm256_extracti128_si256(msg, lane)); + + + +#define R4(k, m0, m1, m2, m3, OP0, OP1) \ + msg = _mm256_add_epi64(m0, *(const __m256i *) (const void *) &K[(k) * 4]); \ + RND2(state0, state1, 0); OP0(m0, m1, m2, m3) \ + RND2(state1, state0, 1); OP1(m0, m1, m2, m3) \ + + + + +#define R16(k, OP0, OP1, OP2, OP3, OP4, OP5, OP6, OP7) \ + R4 ( (k)*4+0, m0,m1,m2,m3, OP0, OP1 ) \ + R4 ( (k)*4+1, m1,m2,m3,m0, OP2, OP3 ) \ + R4 ( (k)*4+2, m2,m3,m0,m1, OP4, OP5 ) \ + R4 ( (k)*4+3, m3,m0,m1,m2, OP6, OP7 ) \ + +#define PREPARE_STATE \ + state0 = _mm256_shuffle_epi32(state0, 0x4e); /* cdab */ \ + state1 = _mm256_shuffle_epi32(state1, 0x4e); /* ghef */ \ + tmp = state0; \ + state0 = _mm256_permute2x128_si256(state0, state1, 0x13); /* cdgh */ \ + state1 = _mm256_permute2x128_si256(tmp, state1, 2); /* abef */ \ + + +void Z7_FASTCALL Sha512_UpdateBlocks_HW(UInt64 state[8], const Byte *data, size_t numBlocks); +#ifdef ATTRIB_SHA512 +ATTRIB_SHA512 +#endif +void Z7_FASTCALL Sha512_UpdateBlocks_HW(UInt64 state[8], const Byte *data, size_t numBlocks) +{ + const __m256i mask = _mm256_set_epi32( + 0x08090a0b,0x0c0d0e0f, 0x00010203,0x04050607, + 0x08090a0b,0x0c0d0e0f, 0x00010203,0x04050607); + __m256i tmp, state0, state1; + + if (numBlocks == 0) + return; + + state0 = _mm256_loadu_si256((const __m256i *) (const void *) &state[0]); + state1 = _mm256_loadu_si256((const __m256i *) (const void *) &state[4]); + + PREPARE_STATE + + do + { + __m256i state0_save, state1_save; + __m256i m0, m1, m2, m3; + __m256i msg; + // #define msg tmp + + state0_save = state0; + state1_save = state1; + + LOAD_SHUFFLE (m0, 0) + LOAD_SHUFFLE (m1, 1) + LOAD_SHUFFLE (m2, 2) + LOAD_SHUFFLE (m3, 3) + + + + R16 ( 0, NNN, NNN, SM1, NNN, SM1, SM2, SM1, SM2 ) + R16 ( 1, SM1, SM2, SM1, SM2, SM1, SM2, SM1, SM2 ) + R16 ( 2, SM1, SM2, SM1, SM2, SM1, SM2, SM1, SM2 ) + R16 ( 3, SM1, SM2, SM1, SM2, SM1, SM2, SM1, SM2 ) + R16 ( 4, SM1, SM2, NNN, SM2, NNN, NNN, NNN, NNN ) + ADD_EPI64(state0, state0_save) + ADD_EPI64(state1, state1_save) + + data += 128; + } + while (--numBlocks); + + PREPARE_STATE + + _mm256_storeu_si256((__m256i *) (void *) &state[0], state0); + _mm256_storeu_si256((__m256i *) (void *) &state[4], state1); +} + +#endif // USE_HW_SHA + +// gcc 8.5 also supports sha512, but we need also support in assembler that is called by gcc +#elif defined(MY_CPU_ARM64) && defined(MY_CPU_LE) + + #if defined(__ARM_FEATURE_SHA512) + #define USE_HW_SHA + #else + #if (defined(Z7_CLANG_VERSION) && (Z7_CLANG_VERSION >= 130000) \ + || defined(__GNUC__) && (__GNUC__ >= 9) \ + ) \ + || defined(Z7_MSC_VER_ORIGINAL) && (_MSC_VER >= 1940) // fix it + #define USE_HW_SHA + #endif + #endif + +#ifdef USE_HW_SHA + +// #pragma message("=== Sha512 HW === ") + + +#if defined(__clang__) || defined(__GNUC__) +#if !defined(__ARM_FEATURE_SHA512) +// #pragma message("=== we define SHA3 ATTRIB_SHA512 === ") +#if defined(__clang__) + #define ATTRIB_SHA512 __attribute__((__target__("sha3"))) // "armv8.2-a,sha3" +#else + #define ATTRIB_SHA512 __attribute__((__target__("arch=armv8.2-a+sha3"))) +#endif +#endif +#endif + + +#if defined(Z7_MSC_VER_ORIGINAL) +#include +#else + +#if defined(__clang__) && __clang_major__ < 16 +#if !defined(__ARM_FEATURE_SHA512) +// #pragma message("=== we set __ARM_FEATURE_SHA512 1 === ") + Z7_DIAGNOSTIC_IGNORE_BEGIN_RESERVED_MACRO_IDENTIFIER + #define Z7_ARM_FEATURE_SHA512_WAS_SET 1 + #define __ARM_FEATURE_SHA512 1 + Z7_DIAGNOSTIC_IGNORE_END_RESERVED_MACRO_IDENTIFIER +#endif +#endif // clang + +#include + +#if defined(Z7_ARM_FEATURE_SHA512_WAS_SET) && \ + defined(__ARM_FEATURE_SHA512) + Z7_DIAGNOSTIC_IGNORE_BEGIN_RESERVED_MACRO_IDENTIFIER + #undef __ARM_FEATURE_SHA512 + #undef Z7_ARM_FEATURE_SHA512_WAS_SET + Z7_DIAGNOSTIC_IGNORE_END_RESERVED_MACRO_IDENTIFIER +// #pragma message("=== we undefine __ARM_FEATURE_CRYPTO === ") +#endif + +#endif // Z7_MSC_VER_ORIGINAL + +typedef uint64x2_t v128_64; +// typedef __n128 v128_64; // MSVC + +#ifdef MY_CPU_BE + #define MY_rev64_for_LE(x) x +#else + #define MY_rev64_for_LE(x) vrev64q_u8(x) +#endif + +#define LOAD_128_64(_p) vld1q_u64(_p) +#define LOAD_128_8(_p) vld1q_u8 (_p) +#define STORE_128_64(_p, _v) vst1q_u64(_p, _v) + +#define LOAD_SHUFFLE(m, k) \ + m = vreinterpretq_u64_u8( \ + MY_rev64_for_LE( \ + LOAD_128_8(data + (k) * 16))); \ + +// K array must be aligned for 16-bytes at least. +extern +MY_ALIGN(64) +const UInt64 SHA512_K_ARRAY[80]; +#define K SHA512_K_ARRAY + +#define NN(m0, m1, m4, m5, m7) +#define SM(m0, m1, m4, m5, m7) \ + m0 = vsha512su1q_u64(vsha512su0q_u64(m0, m1), m7, vextq_u64(m4, m5, 1)); + +#define R2(k, m0,m1,m2,m3,m4,m5,m6,m7, a0,a1,a2,a3, OP) \ + OP(m0, m1, m4, m5, m7) \ + t = vaddq_u64(m0, vld1q_u64(k)); \ + t = vaddq_u64(vextq_u64(t, t, 1), a3); \ + t = vsha512hq_u64(t, vextq_u64(a2, a3, 1), vextq_u64(a1, a2, 1)); \ + a3 = vsha512h2q_u64(t, a1, a0); \ + a1 = vaddq_u64(a1, t); \ + +#define R8(k, m0,m1,m2,m3,m4,m5,m6,m7, OP) \ + R2 ( (k)+0*2, m0,m1,m2,m3,m4,m5,m6,m7, a0,a1,a2,a3, OP ) \ + R2 ( (k)+1*2, m1,m2,m3,m4,m5,m6,m7,m0, a3,a0,a1,a2, OP ) \ + R2 ( (k)+2*2, m2,m3,m4,m5,m6,m7,m0,m1, a2,a3,a0,a1, OP ) \ + R2 ( (k)+3*2, m3,m4,m5,m6,m7,m0,m1,m2, a1,a2,a3,a0, OP ) \ + +#define R16(k, OP) \ + R8 ( (k)+0*2, m0,m1,m2,m3,m4,m5,m6,m7, OP ) \ + R8 ( (k)+4*2, m4,m5,m6,m7,m0,m1,m2,m3, OP ) \ + + +void Z7_FASTCALL Sha512_UpdateBlocks_HW(UInt64 state[8], const Byte *data, size_t numBlocks); +#ifdef ATTRIB_SHA512 +ATTRIB_SHA512 +#endif +void Z7_FASTCALL Sha512_UpdateBlocks_HW(UInt64 state[8], const Byte *data, size_t numBlocks) +{ + v128_64 a0, a1, a2, a3; + + if (numBlocks == 0) + return; + a0 = LOAD_128_64(&state[0]); + a1 = LOAD_128_64(&state[2]); + a2 = LOAD_128_64(&state[4]); + a3 = LOAD_128_64(&state[6]); + do + { + v128_64 a0_save, a1_save, a2_save, a3_save; + v128_64 m0, m1, m2, m3, m4, m5, m6, m7; + v128_64 t; + unsigned i; + const UInt64 *k_ptr; + + LOAD_SHUFFLE (m0, 0) + LOAD_SHUFFLE (m1, 1) + LOAD_SHUFFLE (m2, 2) + LOAD_SHUFFLE (m3, 3) + LOAD_SHUFFLE (m4, 4) + LOAD_SHUFFLE (m5, 5) + LOAD_SHUFFLE (m6, 6) + LOAD_SHUFFLE (m7, 7) + + a0_save = a0; + a1_save = a1; + a2_save = a2; + a3_save = a3; + + R16 ( K, NN ) + k_ptr = K + 16; + for (i = 0; i < 4; i++) + { + R16 ( k_ptr, SM ) + k_ptr += 16; + } + + a0 = vaddq_u64(a0, a0_save); + a1 = vaddq_u64(a1, a1_save); + a2 = vaddq_u64(a2, a2_save); + a3 = vaddq_u64(a3, a3_save); + + data += 128; + } + while (--numBlocks); + + STORE_128_64(&state[0], a0); + STORE_128_64(&state[2], a1); + STORE_128_64(&state[4], a2); + STORE_128_64(&state[6], a3); +} + +#endif // USE_HW_SHA + +#endif // MY_CPU_ARM_OR_ARM64 + + +#if !defined(USE_HW_SHA) && defined(Z7_USE_HW_SHA_STUB) +// #error Stop_Compiling_UNSUPPORTED_SHA +// #include +// We can compile this file with another C compiler, +// or we can compile asm version. +// So we can generate real code instead of this stub function. +// #include "Sha512.h" +// #if defined(_MSC_VER) +#pragma message("Sha512 HW-SW stub was used") +// #endif +void Z7_FASTCALL Sha512_UpdateBlocks (UInt64 state[8], const Byte *data, size_t numBlocks); +void Z7_FASTCALL Sha512_UpdateBlocks_HW(UInt64 state[8], const Byte *data, size_t numBlocks); +void Z7_FASTCALL Sha512_UpdateBlocks_HW(UInt64 state[8], const Byte *data, size_t numBlocks) +{ + Sha512_UpdateBlocks(state, data, numBlocks); + /* + UNUSED_VAR(state); + UNUSED_VAR(data); + UNUSED_VAR(numBlocks); + exit(1); + return; + */ +} +#endif + + +#undef K +#undef RND2 +#undef MY_rev64_for_LE +#undef NN +#undef NNN +#undef LOAD_128 +#undef STORE_128 +#undef LOAD_SHUFFLE +#undef SM1 +#undef SM2 +#undef SM +#undef R2 +#undef R4 +#undef R16 +#undef PREPARE_STATE +#undef USE_HW_SHA +#undef ATTRIB_SHA512 +#undef USE_VER_MIN +#undef Z7_USE_HW_SHA_STUB diff --git a/src/plugins/7zip/7za/c/Sort.c b/src/plugins/7zip/7za/c/Sort.c index e1097e38..20e3e69d 100644 --- a/src/plugins/7zip/7za/c/Sort.c +++ b/src/plugins/7zip/7za/c/Sort.c @@ -1,141 +1,268 @@ /* Sort.c -- Sort functions -2014-04-05 : Igor Pavlov : Public domain */ +: Igor Pavlov : Public domain */ #include "Precomp.h" #include "Sort.h" +#include "CpuArch.h" -#define HeapSortDown(p, k, size, temp) \ - { for (;;) { \ - size_t s = (k << 1); \ - if (s > size) break; \ - if (s < size && p[s + 1] > p[s]) s++; \ - if (temp >= p[s]) break; \ - p[k] = p[s]; k = s; \ - } p[k] = temp; } +#if ( (defined(__GNUC__) && (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1))) \ + || (defined(__clang__) && Z7_has_builtin(__builtin_prefetch)) \ + ) +// the code with prefetch is slow for small arrays on x86. +// So we disable prefetch for x86. +#ifndef MY_CPU_X86 + // #pragma message("Z7_PREFETCH : __builtin_prefetch") + #define Z7_PREFETCH(a) __builtin_prefetch((a)) +#endif -void HeapSort(UInt32 *p, size_t size) -{ - if (size <= 1) - return; - p--; - { - size_t i = size / 2; - do - { - UInt32 temp = p[i]; - size_t k = i; - HeapSortDown(p, k, size, temp) - } - while (--i != 0); - } - /* - do - { - size_t k = 1; - UInt32 temp = p[size]; - p[size--] = p[1]; - HeapSortDown(p, k, size, temp) - } - while (size > 1); - */ - while (size > 3) - { - UInt32 temp = p[size]; - size_t k = (p[3] > p[2]) ? 3 : 2; - p[size--] = p[1]; - p[1] = p[k]; - HeapSortDown(p, k, size, temp) - } - { - UInt32 temp = p[size]; - p[size] = p[1]; - if (size > 2 && p[2] < temp) - { - p[1] = p[2]; - p[2] = temp; - } - else - p[1] = temp; - } +#elif defined(_WIN32) // || defined(_MSC_VER) && (_MSC_VER >= 1200) + +#include "7zWindows.h" + +// NOTE: CLANG/GCC/MSVC can define different values for _MM_HINT_T0 / PF_TEMPORAL_LEVEL_1. +// For example, clang-cl can generate "prefetcht2" instruction for +// PreFetchCacheLine(PF_TEMPORAL_LEVEL_1) call. +// But we want to generate "prefetcht0" instruction. +// So for CLANG/GCC we must use __builtin_prefetch() in code branch above +// instead of PreFetchCacheLine() / _mm_prefetch(). + +// New msvc-x86 compiler generates "prefetcht0" instruction for PreFetchCacheLine() call. +// But old x86 cpus don't support "prefetcht0". +// So we will use PreFetchCacheLine(), only if we are sure that +// generated instruction is supported by all cpus of that isa. +#if defined(MY_CPU_AMD64) \ + || defined(MY_CPU_ARM64) \ + || defined(MY_CPU_IA64) +// we need to use additional braces for (a) in PreFetchCacheLine call, because +// PreFetchCacheLine macro doesn't use braces: +// #define PreFetchCacheLine(l, a) _mm_prefetch((CHAR CONST *) a, l) + // #pragma message("Z7_PREFETCH : PreFetchCacheLine") + #define Z7_PREFETCH(a) PreFetchCacheLine(PF_TEMPORAL_LEVEL_1, (a)) +#endif + +#endif // _WIN32 + + +#define PREFETCH_NO(p,k,s,size) + +#ifndef Z7_PREFETCH + #define SORT_PREFETCH(p,k,s,size) +#else + +// #define PREFETCH_LEVEL 2 // use it if cache line is 32-bytes +#define PREFETCH_LEVEL 3 // it is fast for most cases (64-bytes cache line prefetch) +// #define PREFETCH_LEVEL 4 // it can be faster for big array (128-bytes prefetch) + +#if PREFETCH_LEVEL == 0 + + #define SORT_PREFETCH(p,k,s,size) + +#else // PREFETCH_LEVEL != 0 + +/* +if defined(USE_PREFETCH_FOR_ALIGNED_ARRAY) + we prefetch one value per cache line. + Use it if array is aligned for cache line size (64 bytes) + or if array is small (less than L1 cache size). + +if !defined(USE_PREFETCH_FOR_ALIGNED_ARRAY) + we perfetch all cache lines that can be required. + it can be faster for big unaligned arrays. +*/ + #define USE_PREFETCH_FOR_ALIGNED_ARRAY + +// s == k * 2 +#if 0 && PREFETCH_LEVEL <= 3 && defined(MY_CPU_X86_OR_AMD64) + // x86 supports (lea r1*8+offset) + #define PREFETCH_OFFSET(k,s) ((s) << PREFETCH_LEVEL) +#else + #define PREFETCH_OFFSET(k,s) ((k) << (PREFETCH_LEVEL + 1)) +#endif + +#if 1 && PREFETCH_LEVEL <= 3 && defined(USE_PREFETCH_FOR_ALIGNED_ARRAY) + #define PREFETCH_ADD_OFFSET 0 +#else + // last offset that can be reqiured in PREFETCH_LEVEL step: + #define PREFETCH_RANGE ((2 << PREFETCH_LEVEL) - 1) + #define PREFETCH_ADD_OFFSET PREFETCH_RANGE / 2 +#endif + +#if PREFETCH_LEVEL <= 3 + +#ifdef USE_PREFETCH_FOR_ALIGNED_ARRAY + #define SORT_PREFETCH(p,k,s,size) \ + { const size_t s2 = PREFETCH_OFFSET(k,s) + PREFETCH_ADD_OFFSET; \ + if (s2 <= size) { \ + Z7_PREFETCH((p + s2)); \ + }} +#else /* for unaligned array */ + #define SORT_PREFETCH(p,k,s,size) \ + { const size_t s2 = PREFETCH_OFFSET(k,s) + PREFETCH_RANGE; \ + if (s2 <= size) { \ + Z7_PREFETCH((p + s2 - PREFETCH_RANGE)); \ + Z7_PREFETCH((p + s2)); \ + }} +#endif + +#else // PREFETCH_LEVEL > 3 + +#ifdef USE_PREFETCH_FOR_ALIGNED_ARRAY + #define SORT_PREFETCH(p,k,s,size) \ + { const size_t s2 = PREFETCH_OFFSET(k,s) + PREFETCH_RANGE - 16 / 2; \ + if (s2 <= size) { \ + Z7_PREFETCH((p + s2 - 16)); \ + Z7_PREFETCH((p + s2)); \ + }} +#else /* for unaligned array */ + #define SORT_PREFETCH(p,k,s,size) \ + { const size_t s2 = PREFETCH_OFFSET(k,s) + PREFETCH_RANGE; \ + if (s2 <= size) { \ + Z7_PREFETCH((p + s2 - PREFETCH_RANGE)); \ + Z7_PREFETCH((p + s2 - PREFETCH_RANGE / 2)); \ + Z7_PREFETCH((p + s2)); \ + }} +#endif + +#endif // PREFETCH_LEVEL > 3 +#endif // PREFETCH_LEVEL != 0 +#endif // Z7_PREFETCH + + +#if defined(MY_CPU_ARM64) \ + /* || defined(MY_CPU_AMD64) */ \ + /* || defined(MY_CPU_ARM) && !defined(_MSC_VER) */ + // we want to use cmov, if cmov is very fast: + // - this cmov version is slower for clang-x64. + // - this cmov version is faster for gcc-arm64 for some fast arm64 cpus. + #define Z7_FAST_CMOV_SUPPORTED +#endif + +#ifdef Z7_FAST_CMOV_SUPPORTED + // we want to use cmov here, if cmov is fast: new arm64 cpus. + // we want the compiler to use conditional move for this branch + #define GET_MAX_VAL(n0, n1, max_val_slow) if (n0 < n1) n0 = n1; +#else + // use this branch, if cpu doesn't support fast conditional move. + // it uses slow array access reading: + #define GET_MAX_VAL(n0, n1, max_val_slow) n0 = max_val_slow; +#endif + +#define HeapSortDown(p, k, size, temp, macro_prefetch) \ +{ \ + for (;;) { \ + UInt32 n0, n1; \ + size_t s = k * 2; \ + if (s >= size) { \ + if (s == size) { \ + n0 = p[s]; \ + p[k] = n0; \ + if (temp < n0) k = s; \ + } \ + break; \ + } \ + n0 = p[k * 2]; \ + n1 = p[k * 2 + 1]; \ + s += n0 < n1; \ + GET_MAX_VAL(n0, n1, p[s]) \ + if (temp >= n0) break; \ + macro_prefetch(p, k, s, size) \ + p[k] = n0; \ + k = s; \ + } \ + p[k] = temp; \ } -void HeapSort64(UInt64 *p, size_t size) + +/* +stage-1 : O(n) : + we generate intermediate partially sorted binary tree: + p[0] : it's additional item for better alignment of tree structure in memory. + p[1] + p[2] p[3] + p[4] p[5] p[6] p[7] + ... + p[x] >= p[x * 2] + p[x] >= p[x * 2 + 1] + +stage-2 : O(n)*log2(N): + we move largest item p[0] from head of tree to the end of array + and insert last item to sorted binary tree. +*/ + +// (p) must be aligned for cache line size (64-bytes) for best performance + +void Z7_FASTCALL HeapSort(UInt32 *p, size_t size) { - if (size <= 1) + if (size < 2) return; - p--; - { - size_t i = size / 2; - do - { - UInt64 temp = p[i]; - size_t k = i; - HeapSortDown(p, k, size, temp) - } - while (--i != 0); - } - /* - do + if (size == 2) { - size_t k = 1; - UInt64 temp = p[size]; - p[size--] = p[1]; - HeapSortDown(p, k, size, temp) - } - while (size > 1); - */ - while (size > 3) - { - UInt64 temp = p[size]; - size_t k = (p[3] > p[2]) ? 3 : 2; - p[size--] = p[1]; - p[1] = p[k]; - HeapSortDown(p, k, size, temp) + const UInt32 a0 = p[0]; + const UInt32 a1 = p[1]; + const unsigned k = a1 < a0; + p[k] = a0; + p[k ^ 1] = a1; + return; } { - UInt64 temp = p[size]; - p[size] = p[1]; - if (size > 2 && p[2] < temp) + // stage-1 : O(n) + // we transform array to partially sorted binary tree. + size_t i = --size / 2; + // (size) now is the index of the last item in tree, + // if (i) { - p[1] = p[2]; - p[2] = temp; + do + { + const UInt32 temp = p[i]; + size_t k = i; + HeapSortDown(p, k, size, temp, PREFETCH_NO) + } + while (--i); + } + { + const UInt32 temp = p[0]; + const UInt32 a1 = p[1]; + if (temp < a1) + { + size_t k = 1; + p[0] = a1; + HeapSortDown(p, k, size, temp, PREFETCH_NO) + } } - else - p[1] = temp; } -} -/* -#define HeapSortRefDown(p, vals, n, size, temp) \ - { size_t k = n; UInt32 val = vals[temp]; for (;;) { \ - size_t s = (k << 1); \ - if (s > size) break; \ - if (s < size && vals[p[s + 1]] > vals[p[s]]) s++; \ - if (val >= vals[p[s]]) break; \ - p[k] = p[s]; k = s; \ - } p[k] = temp; } - -void HeapSortRef(UInt32 *p, UInt32 *vals, size_t size) -{ - if (size <= 1) + if (size < 3) + { + // size == 2 + const UInt32 a0 = p[0]; + p[0] = p[2]; + p[2] = a0; return; - p--; + } + if (size != 3) { - size_t i = size / 2; + // stage-2 : O(size) * log2(size): + // we move largest item p[0] from head to the end of array, + // and insert last item to sorted binary tree. do { - UInt32 temp = p[i]; - HeapSortRefDown(p, vals, i, size, temp); + const UInt32 temp = p[size]; + size_t k = p[2] < p[3] ? 3 : 2; + p[size--] = p[0]; + p[0] = p[1]; + p[1] = p[k]; + HeapSortDown(p, k, size, temp, SORT_PREFETCH) // PREFETCH_NO } - while (--i != 0); + while (size != 3); } - do { - UInt32 temp = p[size]; - p[size--] = p[1]; - HeapSortRefDown(p, vals, 1, size, temp); + const UInt32 a2 = p[2]; + const UInt32 a3 = p[3]; + const size_t k = a2 < a3; + p[2] = p[1]; + p[3] = p[0]; + p[k] = a3; + p[k ^ 1] = a2; } - while (size > 1); } -*/ diff --git a/src/plugins/7zip/7za/c/Sort.h b/src/plugins/7zip/7za/c/Sort.h index 2e2963a2..de5a4e86 100644 --- a/src/plugins/7zip/7za/c/Sort.h +++ b/src/plugins/7zip/7za/c/Sort.h @@ -1,17 +1,14 @@ /* Sort.h -- Sort functions -2014-04-05 : Igor Pavlov : Public domain */ +: Igor Pavlov : Public domain */ -#ifndef __7Z_SORT_H -#define __7Z_SORT_H +#ifndef ZIP7_INC_SORT_H +#define ZIP7_INC_SORT_H #include "7zTypes.h" EXTERN_C_BEGIN -void HeapSort(UInt32 *p, size_t size); -void HeapSort64(UInt64 *p, size_t size); - -/* void HeapSortRef(UInt32 *p, UInt32 *vals, size_t size); */ +void Z7_FASTCALL HeapSort(UInt32 *p, size_t size); EXTERN_C_END diff --git a/src/plugins/7zip/7za/c/SwapBytes.c b/src/plugins/7zip/7za/c/SwapBytes.c new file mode 100644 index 00000000..9290592b --- /dev/null +++ b/src/plugins/7zip/7za/c/SwapBytes.c @@ -0,0 +1,835 @@ +/* SwapBytes.c -- Byte Swap conversion filter +2024-03-01 : Igor Pavlov : Public domain */ + +#include "Precomp.h" + +#include "Compiler.h" +#include "CpuArch.h" +#include "RotateDefs.h" +#include "SwapBytes.h" + +typedef UInt16 CSwapUInt16; +typedef UInt32 CSwapUInt32; + +// #define k_SwapBytes_Mode_BASE 0 + +#ifdef MY_CPU_X86_OR_AMD64 + +#define k_SwapBytes_Mode_SSE2 1 +#define k_SwapBytes_Mode_SSSE3 2 +#define k_SwapBytes_Mode_AVX2 3 + + // #if defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 1900) + #if defined(__clang__) && (__clang_major__ >= 4) \ + || defined(Z7_GCC_VERSION) && (Z7_GCC_VERSION >= 40701) + #define k_SwapBytes_Mode_MAX k_SwapBytes_Mode_AVX2 + #define SWAP_ATTRIB_SSE2 __attribute__((__target__("sse2"))) + #define SWAP_ATTRIB_SSSE3 __attribute__((__target__("ssse3"))) + #define SWAP_ATTRIB_AVX2 __attribute__((__target__("avx2"))) + #elif defined(_MSC_VER) + #if (_MSC_VER == 1900) + #pragma warning(disable : 4752) // found Intel(R) Advanced Vector Extensions; consider using /arch:AVX + #endif + #if (_MSC_VER >= 1900) + #define k_SwapBytes_Mode_MAX k_SwapBytes_Mode_AVX2 + #elif (_MSC_VER >= 1500) // (VS2008) + #define k_SwapBytes_Mode_MAX k_SwapBytes_Mode_SSSE3 + #elif (_MSC_VER >= 1310) // (VS2003) + #define k_SwapBytes_Mode_MAX k_SwapBytes_Mode_SSE2 + #endif + #endif // _MSC_VER + +/* +// for debug +#ifdef k_SwapBytes_Mode_MAX +#undef k_SwapBytes_Mode_MAX +#endif +*/ + +#ifndef k_SwapBytes_Mode_MAX +#define k_SwapBytes_Mode_MAX 0 +#endif + +#if (k_SwapBytes_Mode_MAX != 0) && defined(MY_CPU_AMD64) + #define k_SwapBytes_Mode_MIN k_SwapBytes_Mode_SSE2 +#else + #define k_SwapBytes_Mode_MIN 0 +#endif + +#if (k_SwapBytes_Mode_MAX >= k_SwapBytes_Mode_AVX2) + #define USE_SWAP_AVX2 +#endif +#if (k_SwapBytes_Mode_MAX >= k_SwapBytes_Mode_SSSE3) + #define USE_SWAP_SSSE3 +#endif +#if (k_SwapBytes_Mode_MAX >= k_SwapBytes_Mode_SSE2) + #define USE_SWAP_128 +#endif + +#if k_SwapBytes_Mode_MAX <= k_SwapBytes_Mode_MIN || !defined(USE_SWAP_128) +#define FORCE_SWAP_MODE +#endif + + +#ifdef USE_SWAP_128 +/* + MMX + SSE + SSE2 + SSE3 + SSSE3 + SSE4.1 + SSE4.2 + SSE4A + AES + AVX, AVX2, FMA +*/ + +#include // sse2 +// typedef __m128i v128; + +#define SWAP2_128(i) { \ + const __m128i v = *(const __m128i *)(const void *)(items + (i) * 8); \ + *( __m128i *)( void *)(items + (i) * 8) = \ + _mm_or_si128( \ + _mm_slli_epi16(v, 8), \ + _mm_srli_epi16(v, 8)); } +// _mm_or_si128() has more ports to execute than _mm_add_epi16(). + +static +#ifdef SWAP_ATTRIB_SSE2 +SWAP_ATTRIB_SSE2 +#endif +void +Z7_FASTCALL +SwapBytes2_128(CSwapUInt16 *items, const CSwapUInt16 *lim) +{ + Z7_PRAGMA_OPT_DISABLE_LOOP_UNROLL_VECTORIZE + do + { + SWAP2_128(0) SWAP2_128(1) items += 2 * 8; + SWAP2_128(0) SWAP2_128(1) items += 2 * 8; + } + while (items != lim); +} + +/* +// sse2 +#define SWAP4_128_pack(i) { \ + __m128i v = *(const __m128i *)(const void *)(items + (i) * 4); \ + __m128i v0 = _mm_unpacklo_epi8(v, mask); \ + __m128i v1 = _mm_unpackhi_epi8(v, mask); \ + v0 = _mm_shufflelo_epi16(v0, 0x1b); \ + v1 = _mm_shufflelo_epi16(v1, 0x1b); \ + v0 = _mm_shufflehi_epi16(v0, 0x1b); \ + v1 = _mm_shufflehi_epi16(v1, 0x1b); \ + *(__m128i *)(void *)(items + (i) * 4) = _mm_packus_epi16(v0, v1); } + +static +#ifdef SWAP_ATTRIB_SSE2 +SWAP_ATTRIB_SSE2 +#endif +void +Z7_FASTCALL +SwapBytes4_128_pack(CSwapUInt32 *items, const CSwapUInt32 *lim) +{ + const __m128i mask = _mm_setzero_si128(); + // const __m128i mask = _mm_set_epi16(0, 0, 0, 0, 0, 0, 0, 0); + Z7_PRAGMA_OPT_DISABLE_LOOP_UNROLL_VECTORIZE + do + { + SWAP4_128_pack(0); items += 1 * 4; + // SWAP4_128_pack(0); SWAP4_128_pack(1); items += 2 * 4; + } + while (items != lim); +} + +// sse2 +#define SWAP4_128_shift(i) { \ + __m128i v = *(const __m128i *)(const void *)(items + (i) * 4); \ + __m128i v2; \ + v2 = _mm_or_si128( \ + _mm_slli_si128(_mm_and_si128(v, mask), 1), \ + _mm_and_si128(_mm_srli_si128(v, 1), mask)); \ + v = _mm_or_si128( \ + _mm_slli_epi32(v, 24), \ + _mm_srli_epi32(v, 24)); \ + *(__m128i *)(void *)(items + (i) * 4) = _mm_or_si128(v2, v); } + +static +#ifdef SWAP_ATTRIB_SSE2 +SWAP_ATTRIB_SSE2 +#endif +void +Z7_FASTCALL +SwapBytes4_128_shift(CSwapUInt32 *items, const CSwapUInt32 *lim) +{ + #define M1 0xff00 + const __m128i mask = _mm_set_epi32(M1, M1, M1, M1); + Z7_PRAGMA_OPT_DISABLE_LOOP_UNROLL_VECTORIZE + do + { + // SWAP4_128_shift(0) SWAP4_128_shift(1) items += 2 * 4; + // SWAP4_128_shift(0) SWAP4_128_shift(1) items += 2 * 4; + SWAP4_128_shift(0); items += 1 * 4; + } + while (items != lim); +} +*/ + + +#if defined(USE_SWAP_SSSE3) || defined(USE_SWAP_AVX2) + +#define SWAP_SHUF_REV_SEQ_2_VALS(v) (v)+1, (v) +#define SWAP_SHUF_REV_SEQ_4_VALS(v) (v)+3, (v)+2, (v)+1, (v) + +#define SWAP2_SHUF_MASK_16_BYTES \ + SWAP_SHUF_REV_SEQ_2_VALS (0 * 2), \ + SWAP_SHUF_REV_SEQ_2_VALS (1 * 2), \ + SWAP_SHUF_REV_SEQ_2_VALS (2 * 2), \ + SWAP_SHUF_REV_SEQ_2_VALS (3 * 2), \ + SWAP_SHUF_REV_SEQ_2_VALS (4 * 2), \ + SWAP_SHUF_REV_SEQ_2_VALS (5 * 2), \ + SWAP_SHUF_REV_SEQ_2_VALS (6 * 2), \ + SWAP_SHUF_REV_SEQ_2_VALS (7 * 2) + +#define SWAP4_SHUF_MASK_16_BYTES \ + SWAP_SHUF_REV_SEQ_4_VALS (0 * 4), \ + SWAP_SHUF_REV_SEQ_4_VALS (1 * 4), \ + SWAP_SHUF_REV_SEQ_4_VALS (2 * 4), \ + SWAP_SHUF_REV_SEQ_4_VALS (3 * 4) + +#if defined(USE_SWAP_AVX2) +/* if we use 256_BIT_INIT_MASK, each static array mask will be larger for 16 bytes */ +// #define SWAP_USE_256_BIT_INIT_MASK +#endif + +#if defined(SWAP_USE_256_BIT_INIT_MASK) && defined(USE_SWAP_AVX2) +#define SWAP_MASK_INIT_SIZE 32 +#else +#define SWAP_MASK_INIT_SIZE 16 +#endif + +MY_ALIGN(SWAP_MASK_INIT_SIZE) +static const Byte k_ShufMask_Swap2[] = +{ + SWAP2_SHUF_MASK_16_BYTES + #if SWAP_MASK_INIT_SIZE > 16 + , SWAP2_SHUF_MASK_16_BYTES + #endif +}; + +MY_ALIGN(SWAP_MASK_INIT_SIZE) +static const Byte k_ShufMask_Swap4[] = +{ + SWAP4_SHUF_MASK_16_BYTES + #if SWAP_MASK_INIT_SIZE > 16 + , SWAP4_SHUF_MASK_16_BYTES + #endif +}; + + +#ifdef USE_SWAP_SSSE3 + +#include // ssse3 + +#define SHUF_128(i) *(items + (i)) = \ + _mm_shuffle_epi8(*(items + (i)), mask); // SSSE3 + +// Z7_NO_INLINE +static +#ifdef SWAP_ATTRIB_SSSE3 +SWAP_ATTRIB_SSSE3 +#endif +Z7_ATTRIB_NO_VECTORIZE +void +Z7_FASTCALL +ShufBytes_128(void *items8, const void *lim8, const void *mask128_ptr) +{ + __m128i *items = (__m128i *)items8; + const __m128i *lim = (const __m128i *)lim8; + // const __m128i mask = _mm_set_epi8(SHUF_SWAP2_MASK_16_VALS); + // const __m128i mask = _mm_set_epi8(SHUF_SWAP4_MASK_16_VALS); + // const __m128i mask = _mm_load_si128((const __m128i *)(const void *)&(k_ShufMask_Swap4[0])); + // const __m128i mask = _mm_load_si128((const __m128i *)(const void *)&(k_ShufMask_Swap4[0])); + // const __m128i mask = *(const __m128i *)(const void *)&(k_ShufMask_Swap4[0]); + const __m128i mask = *(const __m128i *)mask128_ptr; + Z7_PRAGMA_OPT_DISABLE_LOOP_UNROLL_VECTORIZE + do + { + SHUF_128(0) SHUF_128(1) items += 2; + SHUF_128(0) SHUF_128(1) items += 2; + } + while (items != lim); +} + +#endif // USE_SWAP_SSSE3 + + + +#ifdef USE_SWAP_AVX2 + +#include // avx, avx2 +#if defined(__clang__) +#include +#include +#endif + +#define SHUF_256(i) *(items + (i)) = \ + _mm256_shuffle_epi8(*(items + (i)), mask); // AVX2 + +// Z7_NO_INLINE +static +#ifdef SWAP_ATTRIB_AVX2 +SWAP_ATTRIB_AVX2 +#endif +Z7_ATTRIB_NO_VECTORIZE +void +Z7_FASTCALL +ShufBytes_256(void *items8, const void *lim8, const void *mask128_ptr) +{ + __m256i *items = (__m256i *)items8; + const __m256i *lim = (const __m256i *)lim8; + /* + UNUSED_VAR(mask128_ptr) + __m256i mask = + for Swap4: _mm256_setr_epi8(SWAP4_SHUF_MASK_16_BYTES, SWAP4_SHUF_MASK_16_BYTES); + for Swap2: _mm256_setr_epi8(SWAP2_SHUF_MASK_16_BYTES, SWAP2_SHUF_MASK_16_BYTES); + */ + const __m256i mask = + #if SWAP_MASK_INIT_SIZE > 16 + *(const __m256i *)(const void *)mask128_ptr; + #else + /* msvc: broadcastsi128() version reserves the stack for no reason + msvc 19.29-: _mm256_insertf128_si256() / _mm256_set_m128i)) versions use non-avx movdqu xmm0,XMMWORD PTR [r8] + msvc 19.30+ (VS2022): replaces _mm256_set_m128i(m,m) to vbroadcastf128(m) as we want + */ + // _mm256_broadcastsi128_si256(*mask128_ptr); +#if defined(Z7_GCC_VERSION) && (Z7_GCC_VERSION < 80000) + #define MY_mm256_set_m128i(hi, lo) _mm256_insertf128_si256(_mm256_castsi128_si256(lo), (hi), 1) +#else + #define MY_mm256_set_m128i _mm256_set_m128i +#endif + MY_mm256_set_m128i( + *(const __m128i *)mask128_ptr, + *(const __m128i *)mask128_ptr); + #endif + + Z7_PRAGMA_OPT_DISABLE_LOOP_UNROLL_VECTORIZE + do + { + SHUF_256(0) SHUF_256(1) items += 2; + SHUF_256(0) SHUF_256(1) items += 2; + } + while (items != lim); +} + +#endif // USE_SWAP_AVX2 +#endif // USE_SWAP_SSSE3 || USE_SWAP_AVX2 +#endif // USE_SWAP_128 + + + +// compile message "NEON intrinsics not available with the soft-float ABI" +#elif defined(MY_CPU_ARM_OR_ARM64) \ + && defined(MY_CPU_LE) \ + && !defined(Z7_DISABLE_ARM_NEON) + + #if defined(__clang__) && (__clang_major__ >= 8) \ + || defined(__GNUC__) && (__GNUC__ >= 6) + #if defined(__ARM_FP) + #if (defined(__ARM_ARCH) && (__ARM_ARCH >= 4)) \ + || defined(MY_CPU_ARM64) + #if defined(MY_CPU_ARM64) \ + || !defined(Z7_CLANG_VERSION) \ + || defined(__ARM_NEON) + #define USE_SWAP_128 + #ifdef MY_CPU_ARM64 + // #define SWAP_ATTRIB_NEON __attribute__((__target__(""))) + #else +#if defined(Z7_CLANG_VERSION) + // #define SWAP_ATTRIB_NEON __attribute__((__target__("neon"))) +#else + // #pragma message("SWAP_ATTRIB_NEON __attribute__((__target__(fpu=neon))") + #define SWAP_ATTRIB_NEON __attribute__((__target__("fpu=neon"))) +#endif + #endif // MY_CPU_ARM64 + #endif // __ARM_NEON + #endif // __ARM_ARCH + #endif // __ARM_FP + + #elif defined(_MSC_VER) + #if (_MSC_VER >= 1910) + #define USE_SWAP_128 + #endif + #endif + + #ifdef USE_SWAP_128 + #if defined(Z7_MSC_VER_ORIGINAL) && defined(MY_CPU_ARM64) + #include + #else + +/* +#if !defined(__ARM_NEON) +#if defined(Z7_GCC_VERSION) && (__GNUC__ < 5) \ + || defined(Z7_GCC_VERSION) && (__GNUC__ == 5) && (Z7_GCC_VERSION < 90201) \ + || defined(Z7_GCC_VERSION) && (__GNUC__ == 5) && (Z7_GCC_VERSION < 100100) +Z7_DIAGNOSTIC_IGNORE_BEGIN_RESERVED_MACRO_IDENTIFIER +#pragma message("#define __ARM_NEON 1") +// #define __ARM_NEON 1 +Z7_DIAGNOSTIC_IGNORE_END_RESERVED_MACRO_IDENTIFIER +#endif +#endif +*/ + #include + #endif + #endif + +#ifndef USE_SWAP_128 + #define FORCE_SWAP_MODE +#else + +#ifdef MY_CPU_ARM64 + // for debug : comment it + #define FORCE_SWAP_MODE +#else + #define k_SwapBytes_Mode_NEON 1 +#endif +// typedef uint8x16_t v128; +#define SWAP2_128(i) *(uint8x16_t *) (void *)(items + (i) * 8) = \ + vrev16q_u8(*(const uint8x16_t *)(const void *)(items + (i) * 8)); +#define SWAP4_128(i) *(uint8x16_t *) (void *)(items + (i) * 4) = \ + vrev32q_u8(*(const uint8x16_t *)(const void *)(items + (i) * 4)); + +// Z7_NO_INLINE +static +#ifdef SWAP_ATTRIB_NEON +SWAP_ATTRIB_NEON +#endif +Z7_ATTRIB_NO_VECTORIZE +void +Z7_FASTCALL +SwapBytes2_128(CSwapUInt16 *items, const CSwapUInt16 *lim) +{ + Z7_PRAGMA_OPT_DISABLE_LOOP_UNROLL_VECTORIZE + do + { + SWAP2_128(0) SWAP2_128(1) items += 2 * 8; + SWAP2_128(0) SWAP2_128(1) items += 2 * 8; + } + while (items != lim); +} + +// Z7_NO_INLINE +static +#ifdef SWAP_ATTRIB_NEON +SWAP_ATTRIB_NEON +#endif +Z7_ATTRIB_NO_VECTORIZE +void +Z7_FASTCALL +SwapBytes4_128(CSwapUInt32 *items, const CSwapUInt32 *lim) +{ + Z7_PRAGMA_OPT_DISABLE_LOOP_UNROLL_VECTORIZE + do + { + SWAP4_128(0) SWAP4_128(1) items += 2 * 4; + SWAP4_128(0) SWAP4_128(1) items += 2 * 4; + } + while (items != lim); +} + +#endif // USE_SWAP_128 + +#else // MY_CPU_ARM_OR_ARM64 +#define FORCE_SWAP_MODE +#endif // MY_CPU_ARM_OR_ARM64 + + + + + + +#if defined(Z7_MSC_VER_ORIGINAL) && defined(MY_CPU_X86) + /* _byteswap_ushort() in MSVC x86 32-bit works via slow { mov dh, al; mov dl, ah } + So we use own versions of byteswap function */ + #if (_MSC_VER < 1400 ) // old MSVC-X86 without _rotr16() support + #define SWAP2_16(i) { UInt32 v = items[i]; v += (v << 16); v >>= 8; items[i] = (CSwapUInt16)v; } + #else // is new MSVC-X86 with fast _rotr16() + #include + #define SWAP2_16(i) { items[i] = _rotr16(items[i], 8); } + #endif +#else // is not MSVC-X86 + #define SWAP2_16(i) { CSwapUInt16 v = items[i]; items[i] = Z7_BSWAP16(v); } +#endif // MSVC-X86 + +#if defined(Z7_CPU_FAST_BSWAP_SUPPORTED) + #define SWAP4_32(i) { CSwapUInt32 v = items[i]; items[i] = Z7_BSWAP32(v); } +#else + #define SWAP4_32(i) \ + { UInt32 v = items[i]; \ + v = ((v & 0xff00ff) << 8) + ((v >> 8) & 0xff00ff); \ + v = rotlFixed(v, 16); \ + items[i] = v; } +#endif + + + + +#if defined(FORCE_SWAP_MODE) && defined(USE_SWAP_128) + #define DEFAULT_Swap2 SwapBytes2_128 + #if !defined(MY_CPU_X86_OR_AMD64) + #define DEFAULT_Swap4 SwapBytes4_128 + #endif +#endif + +#if !defined(DEFAULT_Swap2) || !defined(DEFAULT_Swap4) + +#define SWAP_BASE_FUNCS_PREFIXES \ +Z7_FORCE_INLINE \ +static \ +Z7_ATTRIB_NO_VECTOR \ +void Z7_FASTCALL + + +#if defined(MY_CPU_ARM_OR_ARM64) +#if defined(__clang__) +#pragma GCC diagnostic ignored "-Wlanguage-extension-token" +#endif +#endif + + +#ifdef MY_CPU_64BIT + +#if defined(MY_CPU_ARM64) \ + && defined(__ARM_ARCH) && (__ARM_ARCH >= 8) \ + && ( (defined(__GNUC__) && (__GNUC__ >= 4)) \ + || (defined(__clang__) && (__clang_major__ >= 4))) + + #define SWAP2_64_VAR(v) asm ("rev16 %x0,%x0" : "+r" (v)); + #define SWAP4_64_VAR(v) asm ("rev32 %x0,%x0" : "+r" (v)); + +#else // is not ARM64-GNU + +#if !defined(MY_CPU_X86_OR_AMD64) || (k_SwapBytes_Mode_MIN == 0) || !defined(USE_SWAP_128) + #define SWAP2_64_VAR(v) \ + v = ( 0x00ff00ff00ff00ff & (v >> 8)) \ + + ((0x00ff00ff00ff00ff & v) << 8); + /* plus gives faster code in MSVC */ +#endif + +#ifdef Z7_CPU_FAST_BSWAP_SUPPORTED + #define SWAP4_64_VAR(v) \ + v = Z7_BSWAP64(v); \ + v = Z7_ROTL64(v, 32); +#else + #define SWAP4_64_VAR(v) \ + v = ( 0x000000ff000000ff & (v >> 24)) \ + + ((0x000000ff000000ff & v) << 24 ) \ + + ( 0x0000ff000000ff00 & (v >> 8)) \ + + ((0x0000ff000000ff00 & v) << 8 ) \ + ; +#endif + +#endif // ARM64-GNU + + +#ifdef SWAP2_64_VAR + +#define SWAP2_64(i) { \ + UInt64 v = *(const UInt64 *)(const void *)(items + (i) * 4); \ + SWAP2_64_VAR(v) \ + *(UInt64 *)(void *)(items + (i) * 4) = v; } + +SWAP_BASE_FUNCS_PREFIXES +SwapBytes2_64(CSwapUInt16 *items, const CSwapUInt16 *lim) +{ + Z7_PRAGMA_OPT_DISABLE_LOOP_UNROLL_VECTORIZE + do + { + SWAP2_64(0) SWAP2_64(1) items += 2 * 4; + SWAP2_64(0) SWAP2_64(1) items += 2 * 4; + } + while (items != lim); +} + + #define DEFAULT_Swap2 SwapBytes2_64 + #if !defined(FORCE_SWAP_MODE) + #define SWAP2_DEFAULT_MODE 0 + #endif +#else // !defined(SWAP2_64_VAR) + #define DEFAULT_Swap2 SwapBytes2_128 + #if !defined(FORCE_SWAP_MODE) + #define SWAP2_DEFAULT_MODE 1 + #endif +#endif // SWAP2_64_VAR + + +#define SWAP4_64(i) { \ + UInt64 v = *(const UInt64 *)(const void *)(items + (i) * 2); \ + SWAP4_64_VAR(v) \ + *(UInt64 *)(void *)(items + (i) * 2) = v; } + +SWAP_BASE_FUNCS_PREFIXES +SwapBytes4_64(CSwapUInt32 *items, const CSwapUInt32 *lim) +{ + Z7_PRAGMA_OPT_DISABLE_LOOP_UNROLL_VECTORIZE + do + { + SWAP4_64(0) SWAP4_64(1) items += 2 * 2; + SWAP4_64(0) SWAP4_64(1) items += 2 * 2; + } + while (items != lim); +} + +#define DEFAULT_Swap4 SwapBytes4_64 + +#else // is not 64BIT + + +#if defined(MY_CPU_ARM_OR_ARM64) \ + && defined(__ARM_ARCH) && (__ARM_ARCH >= 6) \ + && ( (defined(__GNUC__) && (__GNUC__ >= 4)) \ + || (defined(__clang__) && (__clang_major__ >= 4))) + +#ifdef MY_CPU_64BIT + #define SWAP2_32_VAR(v) asm ("rev16 %w0,%w0" : "+r" (v)); +#else + #define SWAP2_32_VAR(v) asm ("rev16 %0,%0" : "+r" (v)); // for clang/gcc + // asm ("rev16 %r0,%r0" : "+r" (a)); // for gcc +#endif + +#elif defined(_MSC_VER) && (_MSC_VER < 1300) && defined(MY_CPU_X86) \ + || !defined(Z7_CPU_FAST_BSWAP_SUPPORTED) \ + || !defined(Z7_CPU_FAST_ROTATE_SUPPORTED) + // old msvc doesn't support _byteswap_ulong() + #define SWAP2_32_VAR(v) \ + v = ((v & 0xff00ff) << 8) + ((v >> 8) & 0xff00ff); + +#else // is not ARM and is not old-MSVC-X86 and fast BSWAP/ROTATE are supported + #define SWAP2_32_VAR(v) \ + v = Z7_BSWAP32(v); \ + v = rotlFixed(v, 16); + +#endif // GNU-ARM* + +#define SWAP2_32(i) { \ + UInt32 v = *(const UInt32 *)(const void *)(items + (i) * 2); \ + SWAP2_32_VAR(v); \ + *(UInt32 *)(void *)(items + (i) * 2) = v; } + + +SWAP_BASE_FUNCS_PREFIXES +SwapBytes2_32(CSwapUInt16 *items, const CSwapUInt16 *lim) +{ + Z7_PRAGMA_OPT_DISABLE_LOOP_UNROLL_VECTORIZE + do + { + SWAP2_32(0) SWAP2_32(1) items += 2 * 2; + SWAP2_32(0) SWAP2_32(1) items += 2 * 2; + } + while (items != lim); +} + + +SWAP_BASE_FUNCS_PREFIXES +SwapBytes4_32(CSwapUInt32 *items, const CSwapUInt32 *lim) +{ + Z7_PRAGMA_OPT_DISABLE_LOOP_UNROLL_VECTORIZE + do + { + SWAP4_32(0) SWAP4_32(1) items += 2; + SWAP4_32(0) SWAP4_32(1) items += 2; + } + while (items != lim); +} + +#define DEFAULT_Swap2 SwapBytes2_32 +#define DEFAULT_Swap4 SwapBytes4_32 +#if !defined(FORCE_SWAP_MODE) + #define SWAP2_DEFAULT_MODE 0 +#endif + +#endif // MY_CPU_64BIT +#endif // if !defined(DEFAULT_Swap2) || !defined(DEFAULT_Swap4) + + + +#if !defined(FORCE_SWAP_MODE) +static unsigned g_SwapBytes_Mode; +#endif + +/* size of largest unrolled loop iteration: 128 bytes = 4 * 32 bytes (AVX). */ +#define SWAP_ITERATION_BLOCK_SIZE_MAX (1 << 7) + +// 32 bytes for (AVX) or 2 * 16-bytes for NEON. +#define SWAP_VECTOR_ALIGN_SIZE (1 << 5) + +Z7_NO_INLINE +void z7_SwapBytes2(CSwapUInt16 *items, size_t numItems) +{ + Z7_PRAGMA_OPT_DISABLE_LOOP_UNROLL_VECTORIZE + for (; numItems != 0 && ((unsigned)(ptrdiff_t)items & (SWAP_VECTOR_ALIGN_SIZE - 1)) != 0; numItems--) + { + SWAP2_16(0) + items++; + } + { + const size_t k_Align_Mask = SWAP_ITERATION_BLOCK_SIZE_MAX / sizeof(CSwapUInt16) - 1; + size_t numItems2 = numItems; + CSwapUInt16 *lim; + numItems &= k_Align_Mask; + numItems2 &= ~(size_t)k_Align_Mask; + lim = items + numItems2; + if (numItems2 != 0) + { + #if !defined(FORCE_SWAP_MODE) + #ifdef MY_CPU_X86_OR_AMD64 + #ifdef USE_SWAP_AVX2 + if (g_SwapBytes_Mode > k_SwapBytes_Mode_SSSE3) + ShufBytes_256((__m256i *)(void *)items, + (const __m256i *)(const void *)lim, + (const __m128i *)(const void *)&(k_ShufMask_Swap2[0])); + else + #endif + #ifdef USE_SWAP_SSSE3 + if (g_SwapBytes_Mode >= k_SwapBytes_Mode_SSSE3) + ShufBytes_128((__m128i *)(void *)items, + (const __m128i *)(const void *)lim, + (const __m128i *)(const void *)&(k_ShufMask_Swap2[0])); + else + #endif + #endif // MY_CPU_X86_OR_AMD64 + #if SWAP2_DEFAULT_MODE == 0 + if (g_SwapBytes_Mode != 0) + SwapBytes2_128(items, lim); + else + #endif + #endif // FORCE_SWAP_MODE + DEFAULT_Swap2(items, lim); + } + items = lim; + } + Z7_PRAGMA_OPT_DISABLE_LOOP_UNROLL_VECTORIZE + for (; numItems != 0; numItems--) + { + SWAP2_16(0) + items++; + } +} + + +Z7_NO_INLINE +void z7_SwapBytes4(CSwapUInt32 *items, size_t numItems) +{ + Z7_PRAGMA_OPT_DISABLE_LOOP_UNROLL_VECTORIZE + for (; numItems != 0 && ((unsigned)(ptrdiff_t)items & (SWAP_VECTOR_ALIGN_SIZE - 1)) != 0; numItems--) + { + SWAP4_32(0) + items++; + } + { + const size_t k_Align_Mask = SWAP_ITERATION_BLOCK_SIZE_MAX / sizeof(CSwapUInt32) - 1; + size_t numItems2 = numItems; + CSwapUInt32 *lim; + numItems &= k_Align_Mask; + numItems2 &= ~(size_t)k_Align_Mask; + lim = items + numItems2; + if (numItems2 != 0) + { + #if !defined(FORCE_SWAP_MODE) + #ifdef MY_CPU_X86_OR_AMD64 + #ifdef USE_SWAP_AVX2 + if (g_SwapBytes_Mode > k_SwapBytes_Mode_SSSE3) + ShufBytes_256((__m256i *)(void *)items, + (const __m256i *)(const void *)lim, + (const __m128i *)(const void *)&(k_ShufMask_Swap4[0])); + else + #endif + #ifdef USE_SWAP_SSSE3 + if (g_SwapBytes_Mode >= k_SwapBytes_Mode_SSSE3) + ShufBytes_128((__m128i *)(void *)items, + (const __m128i *)(const void *)lim, + (const __m128i *)(const void *)&(k_ShufMask_Swap4[0])); + else + #endif + #else // MY_CPU_X86_OR_AMD64 + + if (g_SwapBytes_Mode != 0) + SwapBytes4_128(items, lim); + else + #endif // MY_CPU_X86_OR_AMD64 + #endif // FORCE_SWAP_MODE + DEFAULT_Swap4(items, lim); + } + items = lim; + } + Z7_PRAGMA_OPT_DISABLE_LOOP_UNROLL_VECTORIZE + for (; numItems != 0; numItems--) + { + SWAP4_32(0) + items++; + } +} + + +// #define SHOW_HW_STATUS + +#ifdef SHOW_HW_STATUS +#include +#define PRF(x) x +#else +#define PRF(x) +#endif + +void z7_SwapBytesPrepare(void) +{ +#ifndef FORCE_SWAP_MODE + unsigned mode = 0; // k_SwapBytes_Mode_BASE; + +#ifdef MY_CPU_ARM_OR_ARM64 + { + if (CPU_IsSupported_NEON()) + { + // #pragma message ("=== SwapBytes NEON") + PRF(printf("\n=== SwapBytes NEON\n");) + mode = k_SwapBytes_Mode_NEON; + } + } +#else // MY_CPU_ARM_OR_ARM64 + { + #ifdef USE_SWAP_AVX2 + if (CPU_IsSupported_AVX2()) + { + // #pragma message ("=== SwapBytes AVX2") + PRF(printf("\n=== SwapBytes AVX2\n");) + mode = k_SwapBytes_Mode_AVX2; + } + else + #endif + #ifdef USE_SWAP_SSSE3 + if (CPU_IsSupported_SSSE3()) + { + // #pragma message ("=== SwapBytes SSSE3") + PRF(printf("\n=== SwapBytes SSSE3\n");) + mode = k_SwapBytes_Mode_SSSE3; + } + else + #endif + #if !defined(MY_CPU_AMD64) + if (CPU_IsSupported_SSE2()) + #endif + { + // #pragma message ("=== SwapBytes SSE2") + PRF(printf("\n=== SwapBytes SSE2\n");) + mode = k_SwapBytes_Mode_SSE2; + } + } +#endif // MY_CPU_ARM_OR_ARM64 + g_SwapBytes_Mode = mode; + // g_SwapBytes_Mode = 0; // for debug +#endif // FORCE_SWAP_MODE + PRF(printf("\n=== SwapBytesPrepare\n");) +} + +#undef PRF diff --git a/src/plugins/7zip/7za/c/SwapBytes.h b/src/plugins/7zip/7za/c/SwapBytes.h new file mode 100644 index 00000000..d4424673 --- /dev/null +++ b/src/plugins/7zip/7za/c/SwapBytes.h @@ -0,0 +1,17 @@ +/* SwapBytes.h -- Byte Swap conversion filter +2023-04-02 : Igor Pavlov : Public domain */ + +#ifndef ZIP7_INC_SWAP_BYTES_H +#define ZIP7_INC_SWAP_BYTES_H + +#include "7zTypes.h" + +EXTERN_C_BEGIN + +void z7_SwapBytes2(UInt16 *data, size_t numItems); +void z7_SwapBytes4(UInt32 *data, size_t numItems); +void z7_SwapBytesPrepare(void); + +EXTERN_C_END + +#endif diff --git a/src/plugins/7zip/7za/c/Threads.c b/src/plugins/7zip/7za/c/Threads.c index d3d0912d..08dd58a9 100644 --- a/src/plugins/7zip/7za/c/Threads.c +++ b/src/plugins/7zip/7za/c/Threads.c @@ -1,55 +1,355 @@ /* Threads.c -- multithreading library -2014-09-21 : Igor Pavlov : Public domain */ +: Igor Pavlov : Public domain */ #include "Precomp.h" -#ifndef UNDER_CE +#ifdef _WIN32 + +#ifndef USE_THREADS_CreateThread #include #endif #include "Threads.h" -static WRes GetError() +static WRes GetError(void) { - DWORD res = GetLastError(); - return (res) ? (WRes)(res) : 1; + const DWORD res = GetLastError(); + return res ? (WRes)res : 1; } -WRes HandleToWRes(HANDLE h) { return (h != 0) ? 0 : GetError(); } -WRes BOOLToWRes(BOOL v) { return v ? 0 : GetError(); } +static WRes HandleToWRes(HANDLE h) { return (h != NULL) ? 0 : GetError(); } +static WRes BOOLToWRes(BOOL v) { return v ? 0 : GetError(); } WRes HandlePtr_Close(HANDLE *p) { if (*p != NULL) + { if (!CloseHandle(*p)) return GetError(); - *p = NULL; + *p = NULL; + } return 0; } -WRes Handle_WaitObject(HANDLE h) { return (WRes)WaitForSingleObject(h, INFINITE); } +WRes Handle_WaitObject(HANDLE h) +{ + DWORD dw = WaitForSingleObject(h, INFINITE); + /* + (dw) result: + WAIT_OBJECT_0 // 0 + WAIT_ABANDONED // 0x00000080 : is not compatible with Win32 Error space + WAIT_TIMEOUT // 0x00000102 : is compatible with Win32 Error space + WAIT_FAILED // 0xFFFFFFFF + */ + if (dw == WAIT_FAILED) + { + dw = GetLastError(); + if (dw == 0) + return WAIT_FAILED; + } + return (WRes)dw; +} + +#define Thread_Wait(p) Handle_WaitObject(*(p)) + +WRes Thread_Wait_Close(CThread *p) +{ + WRes res = Thread_Wait(p); + WRes res2 = Thread_Close(p); + return (res != 0 ? res : res2); +} + +typedef struct MY_PROCESSOR_NUMBER { + WORD Group; + BYTE Number; + BYTE Reserved; +} MY_PROCESSOR_NUMBER, *MY_PPROCESSOR_NUMBER; + +typedef struct MY_GROUP_AFFINITY { +#if defined(Z7_GCC_VERSION) && (Z7_GCC_VERSION < 100000) + // KAFFINITY is not defined in old mingw + ULONG_PTR +#else + KAFFINITY +#endif + Mask; + WORD Group; + WORD Reserved[3]; +} MY_GROUP_AFFINITY, *MY_PGROUP_AFFINITY; + +typedef BOOL (WINAPI *Func_SetThreadGroupAffinity)( + HANDLE hThread, + CONST MY_GROUP_AFFINITY *GroupAffinity, + MY_PGROUP_AFFINITY PreviousGroupAffinity); + +typedef BOOL (WINAPI *Func_GetThreadGroupAffinity)( + HANDLE hThread, + MY_PGROUP_AFFINITY GroupAffinity); + +typedef BOOL (WINAPI *Func_GetProcessGroupAffinity)( + HANDLE hProcess, + PUSHORT GroupCount, + PUSHORT GroupArray); + +Z7_DIAGNOSTIC_IGNORE_CAST_FUNCTION + +#if 0 +#include +#define PRF(x) x +/* +-- + before call of SetThreadGroupAffinity() + GetProcessGroupAffinity return one group. + after call of SetThreadGroupAffinity(): + GetProcessGroupAffinity return more than group, + if SetThreadGroupAffinity() was to another group. +-- + GetProcessAffinityMask MS DOCs: + { + If the calling process contains threads in multiple groups, + the function returns zero for both affinity masks. + } + but tests in win10 with 2 groups (less than 64 cores total): + GetProcessAffinityMask() still returns non-zero affinity masks + even after SetThreadGroupAffinity() calls. +*/ +static void PrintProcess_Info() +{ + { + const + Func_GetProcessGroupAffinity fn_GetProcessGroupAffinity = + (Func_GetProcessGroupAffinity) Z7_CAST_FUNC_C GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), + "GetProcessGroupAffinity"); + if (fn_GetProcessGroupAffinity) + { + unsigned i; + USHORT GroupCounts[64]; + USHORT GroupCount = Z7_ARRAY_SIZE(GroupCounts); + BOOL boolRes = fn_GetProcessGroupAffinity(GetCurrentProcess(), + &GroupCount, GroupCounts); + printf("\n====== GetProcessGroupAffinity : " + "boolRes=%u GroupCounts = %u :", + boolRes, (unsigned)GroupCount); + for (i = 0; i < GroupCount; i++) + printf(" %u", GroupCounts[i]); + printf("\n"); + } + } + { + DWORD_PTR processAffinityMask, systemAffinityMask; + if (GetProcessAffinityMask(GetCurrentProcess(), &processAffinityMask, &systemAffinityMask)) + { + PRF(printf("\n====== GetProcessAffinityMask : " + ": processAffinityMask=%x, systemAffinityMask=%x\n", + (UInt32)processAffinityMask, (UInt32)systemAffinityMask);) + } + else + printf("\n==GetProcessAffinityMask FAIL"); + } +} +#else +#ifndef USE_THREADS_CreateThread +// #define PRF(x) +#endif +#endif + +/* if we send (stackSize=0) to CreateThread(), it will + use default value PE::SizeOfStackReserve from exe file. + PE::SizeOfStackReserve == 1 MiB in exe file with default linker options. + Windows aligns specified value to the next 64 KB range. */ +static const unsigned k_StackSize_ReserveSize = + #ifdef UNDER_CE + 1 << 17; + #else + 1 << 20; + #endif WRes Thread_Create(CThread *p, THREAD_FUNC_TYPE func, LPVOID param) { /* Windows Me/98/95: threadId parameter may not be NULL in _beginthreadex/CreateThread functions */ - - #ifdef UNDER_CE - - DWORD threadId; - *p = CreateThread(0, 0, func, param, 0, &threadId); + #ifdef USE_THREADS_CreateThread + + DWORD threadId; + *p = CreateThread(NULL, k_StackSize_ReserveSize, func, param, STACK_SIZE_PARAM_IS_A_RESERVATION, &threadId); + #else +#define CALL_beginthreadex(func2, param2, flags, threadIdPtr) \ + ((HANDLE)(_beginthreadex(NULL, k_StackSize_ReserveSize, func2, param2, (flags) | STACK_SIZE_PARAM_IS_A_RESERVATION, threadIdPtr))) + unsigned threadId; - *p = (HANDLE)_beginthreadex(NULL, 0, func, param, 0, &threadId); - + *p = CALL_beginthreadex(func, param, 0, &threadId); + +#if 0 // 1 : for debug + { + DWORD_PTR prevMask; + DWORD_PTR affinity = 1 << 0; + prevMask = SetThreadAffinityMask(*p, (DWORD_PTR)affinity); + prevMask = prevMask; + } +#endif +#if 0 // 1 : for debug + { + /* win10: new thread will be created in same group that is assigned to parent thread + but affinity mask will contain all allowed threads of that group, + even if affinity mask of parent group is not full + win11: what group it will be created, if we have set + affinity of parent thread with ThreadGroupAffinity? + */ + const + Func_GetThreadGroupAffinity fn = + (Func_GetThreadGroupAffinity) Z7_CAST_FUNC_C GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), + "GetThreadGroupAffinity"); + if (fn) + { + // BOOL wres2; + MY_GROUP_AFFINITY groupAffinity; + memset(&groupAffinity, 0, sizeof(groupAffinity)); + /* wres2 = */ fn(*p, &groupAffinity); + PRF(printf("\n==Thread_Create cur = %6u GetThreadGroupAffinity(): " + "wres2_BOOL = %u, group=%u mask=%x\n", + GetCurrentThreadId(), + wres2, + groupAffinity.Group, + (UInt32)groupAffinity.Mask);) + } + } +#endif + #endif /* maybe we must use errno here, but probably GetLastError() is also OK. */ return HandleToWRes(*p); } -WRes Event_Create(CEvent *p, BOOL manualReset, int signaled) + +WRes Thread_Create_With_Affinity(CThread *p, THREAD_FUNC_TYPE func, LPVOID param, CAffinityMask affinity) +{ + #ifdef USE_THREADS_CreateThread + + UNUSED_VAR(affinity) + return Thread_Create(p, func, param); + + #else + + /* Windows Me/98/95: threadId parameter may not be NULL in _beginthreadex/CreateThread functions */ + HANDLE h; + WRes wres; + unsigned threadId; + h = CALL_beginthreadex(func, param, CREATE_SUSPENDED, &threadId); + *p = h; + wres = HandleToWRes(h); + if (h) + { + { + // DWORD_PTR prevMask = + SetThreadAffinityMask(h, (DWORD_PTR)affinity); + /* + if (prevMask == 0) + { + // affinity change is non-critical error, so we can ignore it + // wres = GetError(); + } + */ + } + { + const DWORD prevSuspendCount = ResumeThread(h); + /* ResumeThread() returns: + 0 : was_not_suspended + 1 : was_resumed + -1 : error + */ + if (prevSuspendCount == (DWORD)-1) + wres = GetError(); + } + } + + /* maybe we must use errno here, but probably GetLastError() is also OK. */ + return wres; + + #endif +} + + +WRes Thread_Create_With_Group(CThread *p, THREAD_FUNC_TYPE func, LPVOID param, unsigned group, CAffinityMask affinityMask) +{ +#ifdef USE_THREADS_CreateThread + + UNUSED_VAR(group) + UNUSED_VAR(affinityMask) + return Thread_Create(p, func, param); + +#else + + /* Windows Me/98/95: threadId parameter may not be NULL in _beginthreadex/CreateThread functions */ + HANDLE h; + WRes wres; + unsigned threadId; + h = CALL_beginthreadex(func, param, CREATE_SUSPENDED, &threadId); + *p = h; + wres = HandleToWRes(h); + if (h) + { + // PrintProcess_Info(); + { + const + Func_SetThreadGroupAffinity fn = + (Func_SetThreadGroupAffinity) Z7_CAST_FUNC_C GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), + "SetThreadGroupAffinity"); + if (fn) + { + // WRes wres2; + MY_GROUP_AFFINITY groupAffinity, prev_groupAffinity; + memset(&groupAffinity, 0, sizeof(groupAffinity)); + // groupAffinity.Mask must use only bits that supported by current group + // (groupAffinity.Mask = 0) means all allowed bits + groupAffinity.Mask = affinityMask; + groupAffinity.Group = (WORD)group; + // wres2 = + fn(h, &groupAffinity, &prev_groupAffinity); + /* + if (groupAffinity.Group == prev_groupAffinity.Group) + wres2 = wres2; + else + wres2 = wres2; + if (wres2 == 0) + { + wres2 = GetError(); + PRF(printf("\n==SetThreadGroupAffinity error: %u\n", wres2);) + } + else + { + PRF(printf("\n==Thread_Create_With_Group::SetThreadGroupAffinity()" + " threadId = %6u" + " group=%u mask=%x\n", + threadId, + prev_groupAffinity.Group, + (UInt32)prev_groupAffinity.Mask);) + } + */ + } + } + { + const DWORD prevSuspendCount = ResumeThread(h); + /* ResumeThread() returns: + 0 : was_not_suspended + 1 : was_resumed + -1 : error + */ + if (prevSuspendCount == (DWORD)-1) + wres = GetError(); + } + } + + /* maybe we must use errno here, but probably GetLastError() is also OK. */ + return wres; + + #endif +} + + +static WRes Event_Create(CEvent *p, BOOL manualReset, int signaled) { *p = CreateEvent(NULL, manualReset, (signaled ? TRUE : FALSE), NULL); return HandleToWRes(*p); @@ -66,10 +366,22 @@ WRes AutoResetEvent_CreateNotSignaled(CAutoResetEvent *p) { return AutoResetEven WRes Semaphore_Create(CSemaphore *p, UInt32 initCount, UInt32 maxCount) { + // negative ((LONG)maxCount) is not supported in WIN32::CreateSemaphore() *p = CreateSemaphore(NULL, (LONG)initCount, (LONG)maxCount, NULL); return HandleToWRes(*p); } +WRes Semaphore_OptCreateInit(CSemaphore *p, UInt32 initCount, UInt32 maxCount) +{ + // if (Semaphore_IsCreated(p)) + { + WRes wres = Semaphore_Close(p); + if (wres != 0) + return wres; + } + return Semaphore_Create(p, initCount, maxCount); +} + static WRes Semaphore_Release(CSemaphore *p, LONG releaseCount, LONG *previousCount) { return BOOLToWRes(ReleaseSemaphore(*p, releaseCount, previousCount)); } WRes Semaphore_ReleaseN(CSemaphore *p, UInt32 num) @@ -78,8 +390,13 @@ WRes Semaphore_Release1(CSemaphore *p) { return Semaphore_ReleaseN(p, 1); } WRes CriticalSection_Init(CCriticalSection *p) { - /* InitializeCriticalSection can raise only STATUS_NO_MEMORY exception */ + /* InitializeCriticalSection() can raise exception: + Windows XP, 2003 : can raise a STATUS_NO_MEMORY exception + Windows Vista+ : no exceptions */ #ifdef _MSC_VER + #ifdef __clang__ + #pragma GCC diagnostic ignored "-Wlanguage-extension-token" + #endif __try #endif { @@ -87,7 +404,423 @@ WRes CriticalSection_Init(CCriticalSection *p) /* InitializeCriticalSectionAndSpinCount(p, 0); */ } #ifdef _MSC_VER - __except (EXCEPTION_EXECUTE_HANDLER) { return 1; } + __except (EXCEPTION_EXECUTE_HANDLER) { return ERROR_NOT_ENOUGH_MEMORY; } #endif return 0; } + + + + +#else // _WIN32 + +// ---------- POSIX ---------- + +#if defined(__linux__) && !defined(__APPLE__) && !defined(_AIX) && !defined(__ANDROID__) +#ifndef Z7_AFFINITY_DISABLE +// _GNU_SOURCE can be required for pthread_setaffinity_np() / CPU_ZERO / CPU_SET +// clang < 3.6 : unknown warning group '-Wreserved-id-macro' +// clang 3.6 - 12.01 : gives warning "macro name is a reserved identifier" +// clang >= 13 : do not give warning +#if !defined(_GNU_SOURCE) +Z7_DIAGNOSTIC_IGNORE_BEGIN_RESERVED_MACRO_IDENTIFIER +// #define _GNU_SOURCE +Z7_DIAGNOSTIC_IGNORE_END_RESERVED_MACRO_IDENTIFIER +#endif // !defined(_GNU_SOURCE) +#endif // Z7_AFFINITY_DISABLE +#endif // __linux__ + +#include "Threads.h" + +#include +#include +#include +#ifdef Z7_AFFINITY_SUPPORTED +// #include +#endif + + +// #include +// #define PRF(p) p +#define PRF(p) +#define Print(s) PRF(printf("\n%s\n", s);) + +WRes Thread_Create_With_CpuSet(CThread *p, THREAD_FUNC_TYPE func, LPVOID param, const CCpuSet *cpuSet) +{ + // new thread in Posix probably inherits affinity from parrent thread + Print("Thread_Create_With_CpuSet") + + pthread_attr_t attr; + int ret; + // int ret2; + + p->_created = 0; + + RINOK(pthread_attr_init(&attr)) + + ret = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + + if (!ret) + { + if (cpuSet) + { + // pthread_attr_setaffinity_np() is not supported for MUSL compile. + // so we check for __GLIBC__ here +#if defined(Z7_AFFINITY_SUPPORTED) && defined( __GLIBC__) + /* + printf("\n affinity :"); + unsigned i; + for (i = 0; i < sizeof(*cpuSet) && i < 8; i++) + { + Byte b = *((const Byte *)cpuSet + i); + char temp[32]; + #define GET_HEX_CHAR(t) ((char)(((t < 10) ? ('0' + t) : ('A' + (t - 10))))) + temp[0] = GET_HEX_CHAR((b & 0xF)); + temp[1] = GET_HEX_CHAR((b >> 4)); + // temp[0] = GET_HEX_CHAR((b >> 4)); // big-endian + // temp[1] = GET_HEX_CHAR((b & 0xF)); // big-endian + temp[2] = 0; + printf("%s", temp); + } + printf("\n"); + */ + + // ret2 = + pthread_attr_setaffinity_np(&attr, sizeof(*cpuSet), cpuSet); + // if (ret2) ret = ret2; +#endif + } + + ret = pthread_create(&p->_tid, &attr, func, param); + + if (!ret) + { + p->_created = 1; + /* + if (cpuSet) + { + // ret2 = + pthread_setaffinity_np(p->_tid, sizeof(*cpuSet), cpuSet); + // if (ret2) ret = ret2; + } + */ + } + } + // ret2 = + pthread_attr_destroy(&attr); + // if (ret2 != 0) ret = ret2; + return ret; +} + + +WRes Thread_Create(CThread *p, THREAD_FUNC_TYPE func, LPVOID param) +{ + return Thread_Create_With_CpuSet(p, func, param, NULL); +} + +/* +WRes Thread_Create_With_Group(CThread *p, THREAD_FUNC_TYPE func, LPVOID param, unsigned group, CAffinityMask affinity) +{ + UNUSED_VAR(group) + return Thread_Create_With_Affinity(p, func, param, affinity); +} +*/ + +WRes Thread_Create_With_Affinity(CThread *p, THREAD_FUNC_TYPE func, LPVOID param, CAffinityMask affinity) +{ + Print("Thread_Create_WithAffinity") + CCpuSet cs; + unsigned i; + CpuSet_Zero(&cs); + for (i = 0; i < sizeof(affinity) * 8; i++) + { + if (affinity == 0) + break; + if (affinity & 1) + { + CpuSet_Set(&cs, i); + } + affinity >>= 1; + } + return Thread_Create_With_CpuSet(p, func, param, &cs); +} + + +WRes Thread_Close(CThread *p) +{ + // Print("Thread_Close") + int ret; + if (!p->_created) + return 0; + + ret = pthread_detach(p->_tid); + p->_tid = 0; + p->_created = 0; + return ret; +} + + +WRes Thread_Wait_Close(CThread *p) +{ + // Print("Thread_Wait_Close") + void *thread_return; + int ret; + if (!p->_created) + return EINVAL; + + ret = pthread_join(p->_tid, &thread_return); + // probably we can't use that (_tid) after pthread_join(), so we close thread here + p->_created = 0; + p->_tid = 0; + return ret; +} + + + +static WRes Event_Create(CEvent *p, int manualReset, int signaled) +{ + RINOK(pthread_mutex_init(&p->_mutex, NULL)) + RINOK(pthread_cond_init(&p->_cond, NULL)) + p->_manual_reset = manualReset; + p->_state = (signaled ? True : False); + p->_created = 1; + return 0; +} + +WRes ManualResetEvent_Create(CManualResetEvent *p, int signaled) + { return Event_Create(p, True, signaled); } +WRes ManualResetEvent_CreateNotSignaled(CManualResetEvent *p) + { return ManualResetEvent_Create(p, 0); } +WRes AutoResetEvent_Create(CAutoResetEvent *p, int signaled) + { return Event_Create(p, False, signaled); } +WRes AutoResetEvent_CreateNotSignaled(CAutoResetEvent *p) + { return AutoResetEvent_Create(p, 0); } + + +#if defined(Z7_LLVM_CLANG_VERSION) && (__clang_major__ == 13) +// freebsd: +#pragma GCC diagnostic ignored "-Wthread-safety-analysis" +#endif + +WRes Event_Set(CEvent *p) +{ + RINOK(pthread_mutex_lock(&p->_mutex)) + p->_state = True; + { + const int res1 = pthread_cond_broadcast(&p->_cond); + const int res2 = pthread_mutex_unlock(&p->_mutex); + return (res2 ? res2 : res1); + } +} + +WRes Event_Reset(CEvent *p) +{ + RINOK(pthread_mutex_lock(&p->_mutex)) + p->_state = False; + return pthread_mutex_unlock(&p->_mutex); +} + +WRes Event_Wait(CEvent *p) +{ + RINOK(pthread_mutex_lock(&p->_mutex)) + while (p->_state == False) + { + // ETIMEDOUT + // ret = + pthread_cond_wait(&p->_cond, &p->_mutex); + // if (ret != 0) break; + } + if (p->_manual_reset == False) + { + p->_state = False; + } + return pthread_mutex_unlock(&p->_mutex); +} + +WRes Event_Close(CEvent *p) +{ + if (!p->_created) + return 0; + p->_created = 0; + { + const int res1 = pthread_mutex_destroy(&p->_mutex); + const int res2 = pthread_cond_destroy(&p->_cond); + return (res1 ? res1 : res2); + } +} + + +WRes Semaphore_Create(CSemaphore *p, UInt32 initCount, UInt32 maxCount) +{ + if (initCount > maxCount || maxCount < 1) + return EINVAL; + RINOK(pthread_mutex_init(&p->_mutex, NULL)) + RINOK(pthread_cond_init(&p->_cond, NULL)) + p->_count = initCount; + p->_maxCount = maxCount; + p->_created = 1; + return 0; +} + + +WRes Semaphore_OptCreateInit(CSemaphore *p, UInt32 initCount, UInt32 maxCount) +{ + if (Semaphore_IsCreated(p)) + { + /* + WRes wres = Semaphore_Close(p); + if (wres != 0) + return wres; + */ + if (initCount > maxCount || maxCount < 1) + return EINVAL; + // return EINVAL; // for debug + p->_count = initCount; + p->_maxCount = maxCount; + return 0; + } + return Semaphore_Create(p, initCount, maxCount); +} + + +WRes Semaphore_ReleaseN(CSemaphore *p, UInt32 releaseCount) +{ + UInt32 newCount; + int ret; + + if (releaseCount < 1) + return EINVAL; + + RINOK(pthread_mutex_lock(&p->_mutex)) + + newCount = p->_count + releaseCount; + if (newCount > p->_maxCount) + ret = ERROR_TOO_MANY_POSTS; // EINVAL; + else + { + p->_count = newCount; + ret = pthread_cond_broadcast(&p->_cond); + } + RINOK(pthread_mutex_unlock(&p->_mutex)) + return ret; +} + +WRes Semaphore_Wait(CSemaphore *p) +{ + RINOK(pthread_mutex_lock(&p->_mutex)) + while (p->_count < 1) + { + pthread_cond_wait(&p->_cond, &p->_mutex); + } + p->_count--; + return pthread_mutex_unlock(&p->_mutex); +} + +WRes Semaphore_Close(CSemaphore *p) +{ + if (!p->_created) + return 0; + p->_created = 0; + { + const int res1 = pthread_mutex_destroy(&p->_mutex); + const int res2 = pthread_cond_destroy(&p->_cond); + return (res1 ? res1 : res2); + } +} + + + +WRes CriticalSection_Init(CCriticalSection *p) +{ + // Print("CriticalSection_Init") + if (!p) + return EINTR; + return pthread_mutex_init(&p->_mutex, NULL); +} + +void CriticalSection_Enter(CCriticalSection *p) +{ + // Print("CriticalSection_Enter") + if (p) + { + // int ret = + pthread_mutex_lock(&p->_mutex); + } +} + +void CriticalSection_Leave(CCriticalSection *p) +{ + // Print("CriticalSection_Leave") + if (p) + { + // int ret = + pthread_mutex_unlock(&p->_mutex); + } +} + +void CriticalSection_Delete(CCriticalSection *p) +{ + // Print("CriticalSection_Delete") + if (p) + { + // int ret = + pthread_mutex_destroy(&p->_mutex); + } +} + +LONG InterlockedIncrement(LONG volatile *addend) +{ + // Print("InterlockedIncrement") + #ifdef USE_HACK_UNSAFE_ATOMIC + LONG val = *addend + 1; + *addend = val; + return val; + #else + + #if defined(__clang__) && (__clang_major__ >= 8) + #pragma GCC diagnostic ignored "-Watomic-implicit-seq-cst" + #endif + return __sync_add_and_fetch(addend, 1); + #endif +} + +LONG InterlockedDecrement(LONG volatile *addend) +{ + // Print("InterlockedDecrement") + #ifdef USE_HACK_UNSAFE_ATOMIC + LONG val = *addend - 1; + *addend = val; + return val; + #else + return __sync_sub_and_fetch(addend, 1); + #endif +} + +#endif // _WIN32 + +WRes AutoResetEvent_OptCreate_And_Reset(CAutoResetEvent *p) +{ + if (Event_IsCreated(p)) + return Event_Reset(p); + return AutoResetEvent_CreateNotSignaled(p); +} + +void ThreadNextGroup_Init(CThreadNextGroup *p, UInt32 numGroups, UInt32 startGroup) +{ + // printf("\n====== ThreadNextGroup_Init numGroups = %x: startGroup=%x\n", numGroups, startGroup); + if (numGroups == 0) + numGroups = 1; + p->NumGroups = numGroups; + p->NextGroup = startGroup % numGroups; +} + + +UInt32 ThreadNextGroup_GetNext(CThreadNextGroup *p) +{ + const UInt32 next = p->NextGroup; + p->NextGroup = (next + 1) % p->NumGroups; + return next; +} + +#undef PRF +#undef Print diff --git a/src/plugins/7zip/7za/c/Threads.h b/src/plugins/7zip/7za/c/Threads.h index 9b3e1c55..be12e6e7 100644 --- a/src/plugins/7zip/7za/c/Threads.h +++ b/src/plugins/7zip/7za/c/Threads.h @@ -1,38 +1,163 @@ /* Threads.h -- multithreading library -2013-11-12 : Igor Pavlov : Public domain */ +: Igor Pavlov : Public domain */ -#ifndef __7Z_THREADS_H -#define __7Z_THREADS_H +#ifndef ZIP7_INC_THREADS_H +#define ZIP7_INC_THREADS_H #ifdef _WIN32 -#include +#include "7zWindows.h" + +#else + +#include "Compiler.h" + +// #define Z7_AFFINITY_DISABLE +#if defined(__linux__) +#if !defined(__APPLE__) && !defined(_AIX) && !defined(__ANDROID__) +#ifndef Z7_AFFINITY_DISABLE +#define Z7_AFFINITY_SUPPORTED +// #pragma message(" ==== Z7_AFFINITY_SUPPORTED") +#if !defined(_GNU_SOURCE) +// #pragma message(" ==== _GNU_SOURCE set") +// we need _GNU_SOURCE for cpu_set_t, if we compile for MUSL +Z7_DIAGNOSTIC_IGNORE_BEGIN_RESERVED_MACRO_IDENTIFIER +#define _GNU_SOURCE +Z7_DIAGNOSTIC_IGNORE_END_RESERVED_MACRO_IDENTIFIER +#endif +#endif +#endif +#endif + +#include + #endif #include "7zTypes.h" EXTERN_C_BEGIN +#ifdef _WIN32 + WRes HandlePtr_Close(HANDLE *h); WRes Handle_WaitObject(HANDLE h); typedef HANDLE CThread; -#define Thread_Construct(p) *(p) = NULL + +#define Thread_CONSTRUCT(p) { *(p) = NULL; } #define Thread_WasCreated(p) (*(p) != NULL) #define Thread_Close(p) HandlePtr_Close(p) -#define Thread_Wait(p) Handle_WaitObject(*(p)) +// #define Thread_Wait(p) Handle_WaitObject(*(p)) -typedef #ifdef UNDER_CE - DWORD + // if (USE_THREADS_CreateThread is defined), we use _beginthreadex() + // if (USE_THREADS_CreateThread is not definned), we use CreateThread() + #define USE_THREADS_CreateThread +#endif + +typedef + #ifdef USE_THREADS_CreateThread + DWORD + #else + unsigned + #endif + THREAD_FUNC_RET_TYPE; + +#define THREAD_FUNC_RET_ZERO 0 + +typedef DWORD_PTR CAffinityMask; +typedef DWORD_PTR CCpuSet; + +#define CpuSet_Zero(p) *(p) = (0) +#define CpuSet_Set(p, cpu) *(p) |= ((DWORD_PTR)1 << (cpu)) + +#else // _WIN32 + +typedef struct +{ + pthread_t _tid; + int _created; +} CThread; + +#define Thread_CONSTRUCT(p) { (p)->_tid = 0; (p)->_created = 0; } +#define Thread_WasCreated(p) ((p)->_created != 0) +WRes Thread_Close(CThread *p); +// #define Thread_Wait Thread_Wait_Close + +typedef void * THREAD_FUNC_RET_TYPE; +#define THREAD_FUNC_RET_ZERO NULL + + +typedef UInt64 CAffinityMask; + +#ifdef Z7_AFFINITY_SUPPORTED + +typedef cpu_set_t CCpuSet; +#define CpuSet_Zero(p) CPU_ZERO(p) +#define CpuSet_Set(p, cpu) CPU_SET(cpu, p) +#define CpuSet_IsSet(p, cpu) CPU_ISSET(cpu, p) + +#else + +typedef UInt64 CCpuSet; +#define CpuSet_Zero(p) *(p) = (0) +#define CpuSet_Set(p, cpu) *(p) |= ((UInt64)1 << (cpu)) +#define CpuSet_IsSet(p, cpu) ((*(p) & ((UInt64)1 << (cpu))) != 0) + +#endif + + +#endif // _WIN32 + + +#define THREAD_FUNC_CALL_TYPE Z7_STDCALL + +#if defined(_WIN32) && defined(__GNUC__) +/* GCC compiler for x86 32-bit uses the rule: + the stack is 16-byte aligned before CALL instruction for function calling. + But only root function main() contains instructions that + set 16-byte alignment for stack pointer. And another functions + just keep alignment, if it was set in some parent function. + + The problem: + if we create new thread in MinGW (GCC) 32-bit x86 via _beginthreadex() or CreateThread(), + the root function of thread doesn't set 16-byte alignment. + And stack frames in all child functions also will be unaligned in that case. + + Here we set (force_align_arg_pointer) attribute for root function of new thread. + Do we need (force_align_arg_pointer) also for another systems? */ + + #define THREAD_FUNC_ATTRIB_ALIGN_ARG __attribute__((force_align_arg_pointer)) + // #define THREAD_FUNC_ATTRIB_ALIGN_ARG // for debug : bad alignment in SSE functions #else - unsigned + #define THREAD_FUNC_ATTRIB_ALIGN_ARG #endif - THREAD_FUNC_RET_TYPE; -#define THREAD_FUNC_CALL_TYPE MY_STD_CALL -#define THREAD_FUNC_DECL THREAD_FUNC_RET_TYPE THREAD_FUNC_CALL_TYPE +#define THREAD_FUNC_DECL THREAD_FUNC_ATTRIB_ALIGN_ARG THREAD_FUNC_RET_TYPE THREAD_FUNC_CALL_TYPE + typedef THREAD_FUNC_RET_TYPE (THREAD_FUNC_CALL_TYPE * THREAD_FUNC_TYPE)(void *); WRes Thread_Create(CThread *p, THREAD_FUNC_TYPE func, LPVOID param); +WRes Thread_Create_With_Affinity(CThread *p, THREAD_FUNC_TYPE func, LPVOID param, CAffinityMask affinity); +WRes Thread_Wait_Close(CThread *p); + +#ifdef _WIN32 +WRes Thread_Create_With_Group(CThread *p, THREAD_FUNC_TYPE func, LPVOID param, unsigned group, CAffinityMask affinityMask); +#define Thread_Create_With_CpuSet(p, func, param, cs) \ + Thread_Create_With_Affinity(p, func, param, *cs) +#else +WRes Thread_Create_With_CpuSet(CThread *p, THREAD_FUNC_TYPE func, LPVOID param, const CCpuSet *cpuSet); +#endif + +typedef struct +{ + unsigned NumGroups; + unsigned NextGroup; +} CThreadNextGroup; + +void ThreadNextGroup_Init(CThreadNextGroup *p, unsigned numGroups, unsigned startGroup); +unsigned ThreadNextGroup_GetNext(CThreadNextGroup *p); + + +#ifdef _WIN32 typedef HANDLE CEvent; typedef CEvent CAutoResetEvent; @@ -49,10 +174,12 @@ WRes AutoResetEvent_Create(CAutoResetEvent *p, int signaled); WRes AutoResetEvent_CreateNotSignaled(CAutoResetEvent *p); typedef HANDLE CSemaphore; -#define Semaphore_Construct(p) (*p) = NULL +#define Semaphore_Construct(p) *(p) = NULL +#define Semaphore_IsCreated(p) (*(p) != NULL) #define Semaphore_Close(p) HandlePtr_Close(p) #define Semaphore_Wait(p) Handle_WaitObject(*(p)) WRes Semaphore_Create(CSemaphore *p, UInt32 initCount, UInt32 maxCount); +WRes Semaphore_OptCreateInit(CSemaphore *p, UInt32 initCount, UInt32 maxCount); WRes Semaphore_ReleaseN(CSemaphore *p, UInt32 num); WRes Semaphore_Release1(CSemaphore *p); @@ -62,6 +189,72 @@ WRes CriticalSection_Init(CCriticalSection *p); #define CriticalSection_Enter(p) EnterCriticalSection(p) #define CriticalSection_Leave(p) LeaveCriticalSection(p) + +#else // _WIN32 + +typedef struct +{ + int _created; + int _manual_reset; + int _state; + pthread_mutex_t _mutex; + pthread_cond_t _cond; +} CEvent; + +typedef CEvent CAutoResetEvent; +typedef CEvent CManualResetEvent; + +#define Event_Construct(p) (p)->_created = 0 +#define Event_IsCreated(p) ((p)->_created) + +WRes ManualResetEvent_Create(CManualResetEvent *p, int signaled); +WRes ManualResetEvent_CreateNotSignaled(CManualResetEvent *p); +WRes AutoResetEvent_Create(CAutoResetEvent *p, int signaled); +WRes AutoResetEvent_CreateNotSignaled(CAutoResetEvent *p); + +WRes Event_Set(CEvent *p); +WRes Event_Reset(CEvent *p); +WRes Event_Wait(CEvent *p); +WRes Event_Close(CEvent *p); + + +typedef struct +{ + int _created; + UInt32 _count; + UInt32 _maxCount; + pthread_mutex_t _mutex; + pthread_cond_t _cond; +} CSemaphore; + +#define Semaphore_Construct(p) (p)->_created = 0 +#define Semaphore_IsCreated(p) ((p)->_created) + +WRes Semaphore_Create(CSemaphore *p, UInt32 initCount, UInt32 maxCount); +WRes Semaphore_OptCreateInit(CSemaphore *p, UInt32 initCount, UInt32 maxCount); +WRes Semaphore_ReleaseN(CSemaphore *p, UInt32 num); +#define Semaphore_Release1(p) Semaphore_ReleaseN(p, 1) +WRes Semaphore_Wait(CSemaphore *p); +WRes Semaphore_Close(CSemaphore *p); + + +typedef struct +{ + pthread_mutex_t _mutex; +} CCriticalSection; + +WRes CriticalSection_Init(CCriticalSection *p); +void CriticalSection_Delete(CCriticalSection *cs); +void CriticalSection_Enter(CCriticalSection *cs); +void CriticalSection_Leave(CCriticalSection *cs); + +LONG InterlockedIncrement(LONG volatile *addend); +LONG InterlockedDecrement(LONG volatile *addend); + +#endif // _WIN32 + +WRes AutoResetEvent_OptCreate_And_Reset(CAutoResetEvent *p); + EXTERN_C_END #endif diff --git a/src/plugins/7zip/7za/c/Util/7z/7z.dsp b/src/plugins/7zip/7za/c/Util/7z/7z.dsp new file mode 100644 index 00000000..474c6608 --- /dev/null +++ b/src/plugins/7zip/7za/c/Util/7z/7z.dsp @@ -0,0 +1,249 @@ +# Microsoft Developer Studio Project File - Name="7z" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=7z - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "7z.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "7z.mak" CFG="7z - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "7z - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "7z - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "7z - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /MD /W4 /WX /GX /O2 /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /D "Z7_PPMD_SUPPORT" /D "Z7_EXTRACT_ONLY" /FAcs /Yu"Precomp.h" /FD /c +# ADD BASE RSC /l 0x419 /d "NDEBUG" +# ADD RSC /l 0x419 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 /out:"c:\util\7zDec.exe" /opt:NOWIN98 +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /W4 /WX /Gm /GX /ZI /Od /D "_DEBUG" /D "_SZ_ALLOC_DEBUG2" /D "_SZ_NO_INT_64_A" /D "WIN32" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /D "Z7_PPMD_SUPPORT" /D "Z7_EXTRACT_ONLY" /Yu"Precomp.h" /FD /GZ /c +# ADD BASE RSC /l 0x419 /d "_DEBUG" +# ADD RSC /l 0x419 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /out:"c:\util\7zDec.exe" /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "7z - Win32 Release" +# Name "7z - Win32 Debug" +# Begin Group "Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\7z.h +# End Source File +# Begin Source File + +SOURCE=..\..\7zAlloc.c +# End Source File +# Begin Source File + +SOURCE=..\..\7zAlloc.h +# End Source File +# Begin Source File + +SOURCE=..\..\7zArcIn.c +# End Source File +# Begin Source File + +SOURCE=..\..\7zBuf.c +# End Source File +# Begin Source File + +SOURCE=..\..\7zBuf.h +# End Source File +# Begin Source File + +SOURCE=..\..\7zCrc.c +# End Source File +# Begin Source File + +SOURCE=..\..\7zCrc.h +# End Source File +# Begin Source File + +SOURCE=..\..\7zCrcOpt.c +# End Source File +# Begin Source File + +SOURCE=..\..\7zDec.c +# ADD CPP /D "_7ZIP_PPMD_SUPPPORT" +# End Source File +# Begin Source File + +SOURCE=..\..\7zFile.c +# End Source File +# Begin Source File + +SOURCE=..\..\7zFile.h +# End Source File +# Begin Source File + +SOURCE=..\..\7zStream.c +# End Source File +# Begin Source File + +SOURCE=..\..\7zTypes.h +# End Source File +# Begin Source File + +SOURCE=..\..\7zWindows.h +# End Source File +# Begin Source File + +SOURCE=..\..\Bcj2.c +# End Source File +# Begin Source File + +SOURCE=..\..\Bcj2.h +# End Source File +# Begin Source File + +SOURCE=..\..\Bra.c +# End Source File +# Begin Source File + +SOURCE=..\..\Bra.h +# End Source File +# Begin Source File + +SOURCE=..\..\Bra86.c +# End Source File +# Begin Source File + +SOURCE=..\..\BraIA64.c +# End Source File +# Begin Source File + +SOURCE=..\..\CpuArch.c +# End Source File +# Begin Source File + +SOURCE=..\..\CpuArch.h +# End Source File +# Begin Source File + +SOURCE=..\..\Delta.c +# End Source File +# Begin Source File + +SOURCE=..\..\Delta.h +# End Source File +# Begin Source File + +SOURCE=..\..\Lzma2Dec.c +# End Source File +# Begin Source File + +SOURCE=..\..\Lzma2Dec.h +# End Source File +# Begin Source File + +SOURCE=..\..\LzmaDec.c +# End Source File +# Begin Source File + +SOURCE=..\..\LzmaDec.h +# End Source File +# Begin Source File + +SOURCE=..\..\Ppmd.h +# End Source File +# Begin Source File + +SOURCE=..\..\Ppmd7.c +# End Source File +# Begin Source File + +SOURCE=..\..\Ppmd7.h +# End Source File +# Begin Source File + +SOURCE=..\..\Ppmd7Dec.c +# End Source File +# End Group +# Begin Group "Spec" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Compiler.h +# End Source File +# Begin Source File + +SOURCE=.\Precomp.c +# ADD CPP /Yc"Precomp.h" +# End Source File +# Begin Source File + +SOURCE=..\..\Precomp.h +# End Source File +# Begin Source File + +SOURCE=.\Precomp.h +# End Source File +# End Group +# Begin Source File + +SOURCE=.\7zMain.c +# End Source File +# End Target +# End Project diff --git a/src/plugins/7zip/7za/c/Util/7z/7z.dsw b/src/plugins/7zip/7za/c/Util/7z/7z.dsw new file mode 100644 index 00000000..848d13cb --- /dev/null +++ b/src/plugins/7zip/7za/c/Util/7z/7z.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "7z"=.\7z.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/src/plugins/7zip/7za/c/Util/7z/7zMain.c b/src/plugins/7zip/7za/c/Util/7z/7zMain.c new file mode 100644 index 00000000..300c3639 --- /dev/null +++ b/src/plugins/7zip/7za/c/Util/7z/7zMain.c @@ -0,0 +1,1207 @@ +/* 7zMain.c - 7z archive decoding program +: Igor Pavlov : Public domain */ + +#include "Precomp.h" + +#include +#include + +#include "../../7zFile.h" + +#ifndef USE_WINDOWS_FILE +#ifdef _WIN32 +#include // for _mkdir() +#else +#include +#include +#ifdef __GNUC__ +#include +#endif +#include +// #include +#include +#include +#endif +#endif + +#ifdef _WIN32 +// for _isatty() +#include "../../7zWindows.h" +#include +#else +#include // for isatty() +#endif + +#include "../../7z.h" +#include "../../7zAlloc.h" +#include "../../7zBuf.h" +#include "../../7zCrc.h" +#include "../../7zVersion.h" + +#include "../../CpuArch.h" + +#define kInputBufSize ((size_t)1 << 18) + +static const ISzAlloc g_Alloc = { SzAlloc, SzFree }; +// static const ISzAlloc g_Alloc_temp = { SzAllocTemp, SzFreeTemp }; + + +static void Print(const char *s) +{ + fputs(s, stdout); +} + + +static Z7_FORCE_INLINE BoolInt MY_IS_TERMINAL(FILE *x) +{ +#ifdef _WIN32 + const int fd = _fileno(x); + HANDLE h; + DWORD st; + if (fd < 0) + return False; + if (!_isatty(fd)) + return False; + h = (HANDLE)(_get_osfhandle(fd)); + if (h == NULL || h == INVALID_HANDLE_VALUE) + return False; + return GetConsoleMode(h, &st) != 0; +#else + return isatty(fileno(x)) != 0; +#endif +} + +static int Buf_EnsureSize(CBuf *dest, size_t size) +{ + if (dest->size >= size) + return 1; + Buf_Free(dest, &g_Alloc); + return Buf_Create(dest, size, &g_Alloc); +} + +#ifndef _WIN32 +#define MY_USE_UTF8 +#endif + +/* #define MY_USE_UTF8 */ + +#ifdef MY_USE_UTF8 + +#define MY_UTF8_START(n) (0x100 - (1 << (7 - (n)))) + +#define MY_UTF8_RANGE(n) (((UInt32)1) << ((n) * 5 + 6)) + +#define MY_UTF8_HEAD(n, val) ((Byte)(MY_UTF8_START(n) + (val >> (6 * (n))))) +#define MY_UTF8_CHAR(n, val) ((Byte)(0x80 + (((val) >> (6 * (n))) & 0x3F))) + +static size_t Utf16_To_Utf8_Calc(const UInt16 *src, const UInt16 *srcLim) +{ + size_t size = 0; + for (;;) + { + UInt32 val; + if (src == srcLim) + return size; + + size++; + val = *src++; + + if (val < 0x80) + continue; + + if (val < MY_UTF8_RANGE(1)) + { + size++; + continue; + } + + if (val >= 0xD800 && val < 0xDC00 && src != srcLim) + { + const UInt32 c2 = *src; + if (c2 >= 0xDC00 && c2 < 0xE000) + { + src++; + size += 3; + continue; + } + } + + size += 2; + } +} + +static Byte *Utf16_To_Utf8(Byte *dest, const UInt16 *src, const UInt16 *srcLim) +{ + for (;;) + { + UInt32 val; + if (src == srcLim) + return dest; + + val = *src++; + + if (val < 0x80) + { + *dest++ = (Byte)val; + continue; + } + + if (val < MY_UTF8_RANGE(1)) + { + dest[0] = MY_UTF8_HEAD(1, val); + dest[1] = MY_UTF8_CHAR(0, val); + dest += 2; + continue; + } + + if (val >= 0xD800 && val < 0xDC00 && src != srcLim) + { + const UInt32 c2 = *src; + if (c2 >= 0xDC00 && c2 < 0xE000) + { + src++; + val = (((val - 0xD800) << 10) | (c2 - 0xDC00)) + 0x10000; + dest[0] = MY_UTF8_HEAD(3, val); + dest[1] = MY_UTF8_CHAR(2, val); + dest[2] = MY_UTF8_CHAR(1, val); + dest[3] = MY_UTF8_CHAR(0, val); + dest += 4; + continue; + } + } + + dest[0] = MY_UTF8_HEAD(2, val); + dest[1] = MY_UTF8_CHAR(1, val); + dest[2] = MY_UTF8_CHAR(0, val); + dest += 3; + } +} + +static SRes Utf16_To_Utf8Buf(CBuf *dest, const UInt16 *src, size_t srcLen) +{ + size_t destLen = Utf16_To_Utf8_Calc(src, src + srcLen); + destLen += 1; + if (!Buf_EnsureSize(dest, destLen)) + return SZ_ERROR_MEM; + *Utf16_To_Utf8(dest->data, src, src + srcLen) = 0; + return SZ_OK; +} + +#endif + +static SRes Utf16_To_Char(CBuf *buf, const UInt16 *s + #ifndef MY_USE_UTF8 + , UINT codePage + #endif + ) +{ + size_t len = 0; + for (len = 0; s[len] != 0; len++) {} + + #ifndef MY_USE_UTF8 + { + const size_t size = len * 3 + 100; + if (!Buf_EnsureSize(buf, size)) + return SZ_ERROR_MEM; + { + buf->data[0] = 0; + if (len != 0) + { + const char defaultChar = '_'; + BOOL defUsed; + const unsigned numChars = (unsigned)WideCharToMultiByte( + codePage, 0, (LPCWSTR)s, (int)len, (char *)buf->data, (int)size, &defaultChar, &defUsed); + if (numChars == 0 || numChars >= size) + return SZ_ERROR_FAIL; + buf->data[numChars] = 0; + } + return SZ_OK; + } + } + #else + return Utf16_To_Utf8Buf(buf, s, len); + #endif +} + + +#ifdef _WIN32 + +static Z7_FORCE_INLINE unsigned MyCharLower_Ascii(unsigned c) +{ + if (c >= 'A' && c <= 'Z') + return (unsigned)(c + 0x20); + return c; +} + +static Z7_FORCE_INLINE +BoolInt IsString1PrefixedByString2_NoCase_Ascii(const UInt16 *s1, const char *s2) +{ + for (;;) + { + wchar_t c1; + const char c2 = *s2++; if (c2 == 0) return True; + c1 = *s1++; + if (c1 != (unsigned char)c2 && MyCharLower_Ascii(c1) != MyCharLower_Ascii((unsigned char)c2)) + return False; + } +} + + +static const unsigned g_ReservedWithNum_Index = 4; +static const char * const g_ReservedNames[] = +{ + "CON", "PRN", "AUX", "NUL", + "COM", "LPT" +}; + +static Z7_FORCE_INLINE +BoolInt IsReservedFsName(const UInt16 *name, size_t sLen) +{ + unsigned i; + for (i = 0; i < Z7_ARRAY_SIZE(g_ReservedNames); i++) + { + const char *reservedName = g_ReservedNames[i]; + size_t len = strlen(reservedName); + if (sLen < len) + continue; + if (!IsString1PrefixedByString2_NoCase_Ascii(name, reservedName)) + continue; + if (i >= g_ReservedWithNum_Index) + { + if ((unsigned)((unsigned)name[len] - (unsigned)'0') > 9) + continue; + len++; + } + for (;;) + { + unsigned c; + if (len >= sLen) + return True; + c = name[len++]; + if (c == 0 || c == '.') + return True; + if (c != ' ') + break; + } + } + return False; +} + + +static Z7_FORCE_INLINE +void Correct_FileName_for_FS(UInt16 *s, size_t size) +{ + while (size) + { + const unsigned c = s[--size]; + if (c != '.' && c != ' ') + break; + s[size] = '_'; + } +} + +#endif // _WIN32 + + +// in: (s) uses WCHAR_PATH_SEPARATOR path separators +static Z7_FORCE_INLINE +void NormalizesPathParts_and_Dots(UInt16 *dest, const UInt16 *s, BoolInt isDir) +{ + UInt16 * const destStart = dest; + size_t len; + + // remove absolute path prefixes + for (; *s == WCHAR_PATH_SEPARATOR; s++) + {} + + len = 0; + for (;;) + { + const unsigned c = s[len]; + if (c != 0 && c != WCHAR_PATH_SEPARATOR) + { + len++; + continue; + } + { + if (len == 0 + || (len == 1 && s[len - 1] == '.') + || (len == 2 && s[len - 1] == '.' && s[len - 2] == '.')) + { + if (c == 0) + { + if (!isDir) + { + *dest++ = '_'; + // if (len == 2) *dest++ = '_'; // for ".." -> "__" + } + break; + } + } + else + { +#ifdef _WIN32 + if (IsReservedFsName(s, len)) + *dest++ = '_'; +#endif + memcpy(dest, s, sizeof(s[0]) * len); +#ifdef _WIN32 + Correct_FileName_for_FS(dest, len); +#endif + dest += len; + if (c == 0) + break; + *dest++ = WCHAR_PATH_SEPARATOR; + } + s += len + 1; + len = 0; + } + } + + if (dest != destStart && dest[-1] == WCHAR_PATH_SEPARATOR) + { + if (isDir) + dest--; + else + *dest++ = '_'; + } + + if (dest == destStart) + *dest++ = '_'; + *dest = 0; +} + + +#if WCHAR_PATH_SEPARATOR != L'/' + +#ifdef _WIN32 +// WSL scheme +#define WCHAR_IN_FILE_NAME_BACKSLASH_REPLACEMENT ((unsigned)((unsigned)(0xF000) + (unsigned)'\\')) +#else +#define WCHAR_IN_FILE_NAME_BACKSLASH_REPLACEMENT '_' +#endif + +static Z7_FORCE_INLINE +void Normalize_Path_from_7z_to_FS(UInt16 *s) +{ + // Normalize_Path_from_7z_to_FS(s); + for (;; s++) + { + unsigned c = *s; + if (c == 0) + break; + if (c == '/') // separator inside 7z archive + c = WCHAR_PATH_SEPARATOR; + else if (c == WCHAR_PATH_SEPARATOR) + c = WCHAR_IN_FILE_NAME_BACKSLASH_REPLACEMENT; +#ifdef _WIN32 + else if (c < 0x20 || c == ':' || c == '*' || c == '?' || c == '<' || c == '>' || c == '|' || c == '"') + c = '_'; + else + continue; + *s = (UInt16)c; +#endif + } +} +#endif + + +// this function changes both strings: (dest) and (s), +// but length of (s) string will not changed. +// dest[] must provide additional space: size(dest) >= len(len) * 2 + 2 +// in: (s) uses slash path separators (/) +// out: (s) : temporary string +// out: (dest) uses WCHAR_PATH_SEPARATOR path separator +static Z7_FORCE_INLINE +void NormalizePath_for_FS(UInt16 *dest, UInt16 *s, BoolInt isDir) +{ +#if WCHAR_PATH_SEPARATOR != L'/' + Normalize_Path_from_7z_to_FS(s); +#endif + NormalizesPathParts_and_Dots(dest, s , isDir); +} + + + +#ifdef _WIN32 + #ifndef USE_WINDOWS_FILE + static UINT g_FileCodePage = CP_ACP; + #define MY_FILE_CODE_PAGE_PARAM ,g_FileCodePage + #endif +#else + #define MY_FILE_CODE_PAGE_PARAM +#endif + +#ifdef USE_WINDOWS_FILE + +static WRes My_DeleteFileAlways(const UInt16 *path) +{ + const DWORD attrib = GetFileAttributesW(path); + if (// attrib != INVALID_FILE_ATTRIBUTES && + (attrib & FILE_ATTRIBUTE_DIRECTORY) == 0 && + (attrib & FILE_ATTRIBUTE_READONLY)) + { + if (!SetFileAttributes(path, attrib & ~(DWORD)FILE_ATTRIBUTE_READONLY)) + return GetLastError(); + } + if (DeleteFileW(path)) + return 0; + return GetLastError(); +} + +#else + +static WRes My_DeleteFileAlways(const UInt16 *path) +{ + CBuf buf; + WRes res; + Buf_Init(&buf); + RINOK_WRes(Utf16_To_Char(&buf, path MY_FILE_CODE_PAGE_PARAM)) + res = remove((const char *)buf.data) == 0 ? 0 : errno; + Buf_Free(&buf, &g_Alloc); + return res; +} + +#endif + +static WRes MyCreateDir(const UInt16 *name) +{ + #ifdef USE_WINDOWS_FILE + + return CreateDirectoryW((LPCWSTR)name, NULL) ? 0 : GetLastError(); + + #else + + CBuf buf; + WRes res; + Buf_Init(&buf); + RINOK_WRes(Utf16_To_Char(&buf, name MY_FILE_CODE_PAGE_PARAM)) + + res = + #ifdef _WIN32 + _mkdir((const char *)buf.data) + #else + mkdir((const char *)buf.data, 0777) + #endif + == 0 ? 0 : errno; + Buf_Free(&buf, &g_Alloc); + return res; + + #endif +} + +static WRes OutFile_OpenUtf16(CSzFile *p, const UInt16 *name) +{ + #ifdef USE_WINDOWS_FILE + return OutFile_OpenW(p, (LPCWSTR)name); + #else + CBuf buf; + WRes res; + Buf_Init(&buf); + RINOK(Utf16_To_Char(&buf, name MY_FILE_CODE_PAGE_PARAM)) + res = OutFile_Open(p, (const char *)buf.data); + Buf_Free(&buf, &g_Alloc); + return res; + #endif +} + + +static Z7_FORCE_INLINE BoolInt IsDangerousTerminalChar(wchar_t c) +{ + // if (c < 0) return False; // it's not expected case + if (c < 0x20) return True; + if (c < 0x7F) return False; + if (c < 0x9F + 1) return True; + // Unicode Bidirectional (BiDi) control characters: + if (c < 0x202A) return False; + if (c < 0x202E + 1) return True; + if (c < 0x2066) return False; + if (c < 0x2069 + 1) return True; + return False; +} + + +// (size(dest) >= len(src) + 3), if (isDir == True) +// s[] uses slash path separator (/) +static SRes PrintPath(const UInt16 *s, UInt16 *dest, BoolInt isDir, BoolInt isTerminalMode) +{ + CBuf buf; + SRes res; + size_t i; + for (i = 0;; i++) + { + UInt16 c = s[i]; + if (c == 0) + break; + if (isTerminalMode ? IsDangerousTerminalChar(c) : + (c == '\n' +#ifdef _WIN32 + || c == '\r' +#endif + )) + c = '_'; + dest[i] = c; + } + + if (i == 0) + dest[i++] = '_'; + if (isDir && dest[i - 1] != '/') + dest[i++] = '/'; + dest[i] = 0; + + Buf_Init(&buf); + res = Utf16_To_Char(&buf, dest + #ifndef MY_USE_UTF8 + , CP_OEMCP + #endif + ); + if (res == SZ_OK) + Print((const char *)buf.data); + Buf_Free(&buf, &g_Alloc); + return res; +} + +static void UInt64ToStr(UInt64 value, char *s, int numDigits) +{ + char temp[32]; + int pos = 0; + do + { + temp[pos++] = (char)('0' + (unsigned)(value % 10)); + value /= 10; + } + while (value != 0); + + for (numDigits -= pos; numDigits > 0; numDigits--) + *s++ = ' '; + + do + *s++ = temp[--pos]; + while (pos); + *s = '\0'; +} + +static char *UIntToStr(char *s, unsigned value, int numDigits) +{ + char temp[16]; + int pos = 0; + do + temp[pos++] = (char)('0' + (value % 10)); + while (value /= 10); + + for (numDigits -= pos; numDigits > 0; numDigits--) + *s++ = '0'; + + do + *s++ = temp[--pos]; + while (pos); + *s = '\0'; + return s; +} + +static void UIntToStr_2(char *s, unsigned value) +{ + s[0] = (char)('0' + (value / 10)); + s[1] = (char)('0' + (value % 10)); +} + + +#define PERIOD_4 (4 * 365 + 1) +#define PERIOD_100 (PERIOD_4 * 25 - 1) +#define PERIOD_400 (PERIOD_100 * 4 + 1) + + + +#ifndef _WIN32 + +// MS uses long for BOOL, but long is 32-bit in MS. So we use int. +// typedef long BOOL; +typedef int BOOL; + +typedef struct +{ + DWORD dwLowDateTime; + DWORD dwHighDateTime; +} FILETIME; + +static LONG TIME_GetBias(void) +{ + const time_t utc = time(NULL); + struct tm *ptm = localtime(&utc); + const int localdaylight = ptm->tm_isdst; /* daylight for local timezone */ + ptm = gmtime(&utc); + ptm->tm_isdst = localdaylight; /* use local daylight, not that of Greenwich */ + return (int)(mktime(ptm) - utc); +} + +#define TICKS_PER_SEC 10000000 + +#define GET_TIME_64(pft) ((pft)->dwLowDateTime | ((UInt64)(pft)->dwHighDateTime << 32)) + +#define SET_FILETIME(ft, v64) \ + (ft)->dwLowDateTime = (DWORD)v64; \ + (ft)->dwHighDateTime = (DWORD)(v64 >> 32); + +#define WINAPI +#define TRUE 1 + +static BOOL WINAPI FileTimeToLocalFileTime(const FILETIME *fileTime, FILETIME *localFileTime) +{ + UInt64 v = GET_TIME_64(fileTime); + v = (UInt64)((Int64)v - (Int64)TIME_GetBias() * TICKS_PER_SEC); + SET_FILETIME(localFileTime, v) + return TRUE; +} + +static const UInt32 kNumTimeQuantumsInSecond = 10000000; +static const UInt32 kFileTimeStartYear = 1601; +static const UInt32 kUnixTimeStartYear = 1970; + +static Int64 Time_FileTimeToUnixTime64(const FILETIME *ft) +{ + const UInt64 kUnixTimeOffset = + (UInt64)60 * 60 * 24 * (89 + 365 * (kUnixTimeStartYear - kFileTimeStartYear)); + const UInt64 winTime = GET_TIME_64(ft); + return (Int64)(winTime / kNumTimeQuantumsInSecond) - (Int64)kUnixTimeOffset; +} + +#if defined(_AIX) + #define MY_ST_TIMESPEC st_timespec +#else + #define MY_ST_TIMESPEC timespec +#endif + +static void FILETIME_To_timespec(const FILETIME *ft, struct MY_ST_TIMESPEC *ts) +{ + if (ft) + { + const Int64 sec = Time_FileTimeToUnixTime64(ft); + // time_t is long + const time_t sec2 = (time_t)sec; + if (sec2 == sec) + { + ts->tv_sec = sec2; + { + const UInt64 winTime = GET_TIME_64(ft); + ts->tv_nsec = (long)((winTime % 10000000) * 100); + } + return; + } + } + // else + { + ts->tv_sec = 0; + // ts.tv_nsec = UTIME_NOW; // set to the current time + ts->tv_nsec = UTIME_OMIT; // keep old timesptamp + } +} + +static WRes Set_File_FILETIME(const UInt16 *name, const FILETIME *mTime) +{ + struct timespec times[2]; + + const int flags = 0; // follow link + // = AT_SYMLINK_NOFOLLOW; // don't follow link + + CBuf buf; + int res; + Buf_Init(&buf); + RINOK(Utf16_To_Char(&buf, name MY_FILE_CODE_PAGE_PARAM)) + FILETIME_To_timespec(NULL, ×[0]); + FILETIME_To_timespec(mTime, ×[1]); + res = utimensat(AT_FDCWD, (const char *)buf.data, times, flags); + Buf_Free(&buf, &g_Alloc); + if (res == 0) + return 0; + return errno; +} + +#endif + +static void NtfsFileTime_to_FILETIME(const CNtfsFileTime *t, FILETIME *ft) +{ + ft->dwLowDateTime = (DWORD)(t->Low); + ft->dwHighDateTime = (DWORD)(t->High); +} + +static void ConvertFileTimeToString(const CNtfsFileTime *nTime, char *s) +{ + unsigned year, mon, hour, min, sec; + Byte ms[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + UInt32 t; + UInt32 v; + // UInt64 v64 = nt->Low | ((UInt64)nt->High << 32); + UInt64 v64; + { + FILETIME fileTime, locTime; + NtfsFileTime_to_FILETIME(nTime, &fileTime); + if (!FileTimeToLocalFileTime(&fileTime, &locTime)) + { + locTime.dwHighDateTime = + locTime.dwLowDateTime = 0; + } + v64 = locTime.dwLowDateTime | ((UInt64)locTime.dwHighDateTime << 32); + } + v64 /= 10000000; + sec = (unsigned)(v64 % 60); v64 /= 60; + min = (unsigned)(v64 % 60); v64 /= 60; + hour = (unsigned)(v64 % 24); v64 /= 24; + + v = (UInt32)v64; + + year = (unsigned)(1601 + v / PERIOD_400 * 400); + v %= PERIOD_400; + + t = v / PERIOD_100; if (t == 4) t = 3; year += t * 100; v -= t * PERIOD_100; + t = v / PERIOD_4; if (t == 25) t = 24; year += t * 4; v -= t * PERIOD_4; + t = v / 365; if (t == 4) t = 3; year += t; v -= t * 365; + + if (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) + ms[1] = 29; + for (mon = 0;; mon++) + { + const UInt32 d = ms[mon]; + if (v < d) + break; + v -= d; + } + s = UIntToStr(s, year, 4); *s++ = '-'; + UIntToStr_2(s, mon + 1); s[2] = '-'; s += 3; + UIntToStr_2(s, (unsigned)v + 1); s[2] = ' '; s += 3; + UIntToStr_2(s, hour); s[2] = ':'; s += 3; + UIntToStr_2(s, min); s[2] = ':'; s += 3; + UIntToStr_2(s, sec); s[2] = 0; +} + +static void PrintLF(void) +{ + Print("\n"); +} + +static void PrintError(char *s) +{ + Print("\nERROR: "); + Print(s); + PrintLF(); +} + +static void PrintError_WRes(const char *message, WRes wres) +{ + Print("\nERROR: "); + Print(message); + PrintLF(); + { + char s[32]; + UIntToStr(s, (unsigned)wres, 1); + Print("System error code: "); + Print(s); + } + // sprintf(buffer + strlen(buffer), "\nSystem error code: %d", (unsigned)wres); + #ifdef USE_WINDOWS_FILE // _WIN32 + { + char *s = NULL; + if (FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, wres, 0, (LPSTR) &s, 0, NULL) != 0 && s) + { + Print(" : "); + Print(s); + LocalFree(s); + } + } + #else + { + const char *s = strerror(wres); + if (s) + { + Print(" : "); + Print(s); + } + } + #endif + PrintLF(); +} + +static void GetAttribString(UInt32 wa, BoolInt isDir, char *s) +{ + #ifdef USE_WINDOWS_FILE + s[0] = (char)(((wa & FILE_ATTRIBUTE_DIRECTORY) != 0 || isDir) ? 'D' : '.'); + s[1] = (char)(((wa & FILE_ATTRIBUTE_READONLY ) != 0) ? 'R': '.'); + s[2] = (char)(((wa & FILE_ATTRIBUTE_HIDDEN ) != 0) ? 'H': '.'); + s[3] = (char)(((wa & FILE_ATTRIBUTE_SYSTEM ) != 0) ? 'S': '.'); + s[4] = (char)(((wa & FILE_ATTRIBUTE_ARCHIVE ) != 0) ? 'A': '.'); + s[5] = 0; + #else + s[0] = (char)(((wa & (1 << 4)) != 0 || isDir) ? 'D' : '.'); + s[1] = 0; + #endif +} + + +int Z7_CDECL main(int numargs, char *args[]) +{ + ISzAlloc allocImp; + ISzAlloc allocTempImp; + + CFileInStream archiveStream; + CLookToRead2 lookStream; + CSzArEx db; + SRes res; + UInt16 *temp = NULL; + UInt16 *temp2 = NULL; + size_t tempSize = 0; + const BoolInt isTerminalMode = MY_IS_TERMINAL(stdout); + + Print("\n7z Decoder " MY_VERSION_CPU " : " MY_COPYRIGHT_DATE "\n\n"); + + if (numargs == 1) + { + Print( + "Usage: 7zDec \n\n" + "\n" + " e: Extract files from archive (without using directory names)\n" + " l: List contents of archive\n" + " t: Test integrity of archive\n" + " x: eXtract files with full paths\n"); + return 0; + } + + if (numargs < 3) + { + PrintError("incorrect command"); + return 1; + } + + #if defined(_WIN32) && !defined(USE_WINDOWS_FILE) && !defined(UNDER_CE) + g_FileCodePage = AreFileApisANSI() ? CP_ACP : CP_OEMCP; + #endif + + + allocImp = g_Alloc; + allocTempImp = g_Alloc; + // allocTempImp = g_Alloc_temp; + + { + WRes wres = + #ifdef UNDER_CE + InFile_OpenW(&archiveStream.file, L"\test.7z"); // change it + #else + InFile_Open(&archiveStream.file, args[2]); + #endif + if (wres != 0) + { + PrintError_WRes("cannot open input file", wres); + return 1; + } + } + + FileInStream_CreateVTable(&archiveStream); + archiveStream.wres = 0; + LookToRead2_CreateVTable(&lookStream, False); + lookStream.buf = NULL; + + res = SZ_OK; + + { + lookStream.buf = (Byte *)ISzAlloc_Alloc(&allocImp, kInputBufSize); + if (!lookStream.buf) + res = SZ_ERROR_MEM; + else + { + lookStream.bufSize = kInputBufSize; + lookStream.realStream = &archiveStream.vt; + LookToRead2_INIT(&lookStream) + } + } + + CrcGenerateTable(); + + SzArEx_Init(&db); + + if (res == SZ_OK) + { + res = SzArEx_Open(&db, &lookStream.vt, &allocImp, &allocTempImp); + } + + if (res == SZ_OK) + { + char *command = args[1]; + int listCommand = 0, testCommand = 0, fullPaths = 0; + + if (strcmp(command, "l") == 0) listCommand = 1; + else if (strcmp(command, "t") == 0) testCommand = 1; + else if (strcmp(command, "e") == 0) { } + else if (strcmp(command, "x") == 0) { fullPaths = 1; } + else + { + PrintError("incorrect command"); + res = SZ_ERROR_FAIL; + } + + if (res == SZ_OK) + { + UInt32 i; + + /* + if you need cache, use these 3 variables. + if you use external function, you can make these variable as static. + */ + UInt32 blockIndex = 0xFFFFFFFF; /* it can have any value before first call (if outBuffer = 0) */ + Byte *outBuffer = 0; /* it must be 0 before first call for each new archive. */ + size_t outBufferSize = 0; /* it can have any value before first call (if outBuffer = 0) */ + + for (i = 0; i < db.NumFiles; i++) + { + size_t offset = 0; + size_t outSizeProcessed = 0; + // const CSzFileItem *f = db.Files + i; + size_t len; + const BoolInt isDir = SzArEx_IsDir(&db, i); + if (listCommand == 0 && isDir && !fullPaths) + continue; + len = SzArEx_GetFileNameUtf16(&db, i, NULL); + + if (len > (1u << 20)) + { + res = SZ_ERROR_UNSUPPORTED; + break; + } + + if (len > tempSize) + { + SzFree(NULL, temp); + tempSize = len; + // temp2 requires additional space for "_" additions for "COM1" and empty names, + // and up to 2 additional characters for PrintPath(). + temp = (UInt16 *)SzAlloc(NULL, (tempSize * 3 + 16) * sizeof(temp[0])); + if (!temp) + { + res = SZ_ERROR_MEM; + break; + } + temp2 = temp + tempSize; + } + + SzArEx_GetFileNameUtf16(&db, i, temp); + /* + if (SzArEx_GetFullNameUtf16_Back(&db, i, temp + len) != temp) + { + res = SZ_ERROR_FAIL; + break; + } + */ + + if (listCommand) + { + char attr[8], s[32], t[32]; + UInt64 fileSize; + + GetAttribString(SzBitWithVals_Check(&db.Attribs, i) ? db.Attribs.Vals[i] : 0, isDir, attr); + + fileSize = SzArEx_GetFileSize(&db, i); + UInt64ToStr(fileSize, s, 10); + + if (SzBitWithVals_Check(&db.MTime, i)) + ConvertFileTimeToString(&db.MTime.Vals[i], t); + else + { + size_t j; + for (j = 0; j < 19; j++) + t[j] = ' '; + t[j] = '\0'; + } + + Print(t); + Print(" "); + Print(attr); + Print(" "); + Print(s); + Print(" "); + res = PrintPath(temp, temp2, isDir, isTerminalMode); + if (res != SZ_OK) + break; + PrintLF(); + continue; + } + + Print(testCommand ? "T ": "- "); + res = PrintPath(temp, temp2, isDir, isTerminalMode); + if (res != SZ_OK) + break; + + if (!isDir) + { + res = SzArEx_Extract(&db, &lookStream.vt, i, + &blockIndex, &outBuffer, &outBufferSize, + &offset, &outSizeProcessed, + &allocImp, &allocTempImp); + if (res != SZ_OK) + break; + } + + if (!testCommand) + { + CSzFile outFile; + size_t processedSize; + size_t j; + + UInt16 *name = (UInt16 *)temp2; + const UInt16 *destPath = (const UInt16 *)name; + + // memset(temp2, 0, (tempSize) * sizeof(temp[0])); // for debug + NormalizePath_for_FS(temp2, temp, isDir); + // PrintLF(); PrintPath(temp2, temp, isDir, isTerminalMode); // for debuf + + for (j = 0; name[j] != 0; j++) + if (name[j] == CHAR_PATH_SEPARATOR) + { + if (fullPaths) + { + name[j] = 0; + MyCreateDir(name); + name[j] = CHAR_PATH_SEPARATOR; + } + else + destPath = name + j + 1; + } + + if (isDir) + { + MyCreateDir(destPath); + PrintLF(); + continue; + } + else + { + { + // const WRes wres = + My_DeleteFileAlways(destPath); + /* + if (wres && wres != ERROR_FILE_NOT_FOUND) + { + PrintError_WRes("cannot delete output file", wres); + res = SZ_ERROR_FAIL; + break; + } + */ + } + { + const WRes wres = OutFile_OpenUtf16(&outFile, destPath); + if (wres) + { + PrintError_WRes("cannot open output file", wres); + res = SZ_ERROR_FAIL; + break; + } + } + } + + processedSize = outSizeProcessed; + + { + const WRes wres = File_Write(&outFile, outBuffer + offset, &processedSize); + if (wres != 0 || processedSize != outSizeProcessed) + { + PrintError_WRes("cannot write output file", wres); + res = SZ_ERROR_FAIL; + break; + } + } + + { + FILETIME mtime; + FILETIME *mtimePtr = NULL; + + #ifdef USE_WINDOWS_FILE + FILETIME ctime; + FILETIME *ctimePtr = NULL; + #endif + + if (SzBitWithVals_Check(&db.MTime, i)) + { + const CNtfsFileTime *t = &db.MTime.Vals[i]; + mtime.dwLowDateTime = (DWORD)(t->Low); + mtime.dwHighDateTime = (DWORD)(t->High); + mtimePtr = &mtime; + } + + #ifdef USE_WINDOWS_FILE + if (SzBitWithVals_Check(&db.CTime, i)) + { + const CNtfsFileTime *t = &db.CTime.Vals[i]; + ctime.dwLowDateTime = (DWORD)(t->Low); + ctime.dwHighDateTime = (DWORD)(t->High); + ctimePtr = &ctime; + } + + if (mtimePtr || ctimePtr) + SetFileTime(outFile.handle, ctimePtr, NULL, mtimePtr); + #endif + + { + const WRes wres = File_Close(&outFile); + if (wres != 0) + { + PrintError_WRes("cannot close output file", wres); + res = SZ_ERROR_FAIL; + break; + } + } + + #ifndef USE_WINDOWS_FILE + #ifdef _WIN32 + mtimePtr = mtimePtr; + #else + if (mtimePtr) + Set_File_FILETIME(destPath, mtimePtr); + #endif + #endif + } + + #ifdef USE_WINDOWS_FILE + if (SzBitWithVals_Check(&db.Attribs, i)) + { + UInt32 attrib = db.Attribs.Vals[i]; + /* p7zip stores posix attributes in high 16 bits and adds 0x8000 as marker. + We remove posix bits, if we detect posix mode field */ + if (attrib & 0xF0000000) + attrib &= 0x7FFF; + SetFileAttributesW((LPCWSTR)destPath, attrib); + } + #endif + } + PrintLF(); + } + ISzAlloc_Free(&allocImp, outBuffer); + } + } + + SzFree(NULL, temp); + SzArEx_Free(&db, &allocImp); + ISzAlloc_Free(&allocImp, lookStream.buf); + + File_Close(&archiveStream.file); + + if (res == SZ_OK) + { + Print("\nEverything is Ok\n"); + return 0; + } + + if (res == SZ_ERROR_UNSUPPORTED) + PrintError("decoder doesn't support this archive"); + else if (res == SZ_ERROR_MEM) + PrintError("cannot allocate memory"); + else if (res == SZ_ERROR_CRC) + PrintError("CRC error"); + else if (res == SZ_ERROR_READ /* || archiveStream.Res != 0 */) + PrintError_WRes("Read Error", archiveStream.wres); + else + { + char s[32]; + UInt64ToStr((unsigned)res, s, 0); + PrintError(s); + } + + return 1; +} diff --git a/src/plugins/7zip/7za/c/Util/7z/Precomp.c b/src/plugins/7zip/7za/c/Util/7z/Precomp.c new file mode 100644 index 00000000..01605e3c --- /dev/null +++ b/src/plugins/7zip/7za/c/Util/7z/Precomp.c @@ -0,0 +1,4 @@ +/* Precomp.c -- StdAfx +2013-01-21 : Igor Pavlov : Public domain */ + +#include "Precomp.h" diff --git a/src/plugins/7zip/7za/c/Util/7z/Precomp.h b/src/plugins/7zip/7za/c/Util/7z/Precomp.h new file mode 100644 index 00000000..13a41eff --- /dev/null +++ b/src/plugins/7zip/7za/c/Util/7z/Precomp.h @@ -0,0 +1,13 @@ +/* Precomp.h -- Precomp +2024-01-23 : Igor Pavlov : Public domain */ + +// #ifndef ZIP7_INC_PRECOMP_LOC_H +// #define ZIP7_INC_PRECOMP_LOC_H + +#if defined(_MSC_VER) && _MSC_VER >= 1800 +#pragma warning(disable : 4464) // relative include path contains '..' +#endif + +#include "../../Precomp.h" + +// #endif diff --git a/src/plugins/7zip/7za/c/Util/7z/makefile b/src/plugins/7zip/7za/c/Util/7z/makefile new file mode 100644 index 00000000..987f0656 --- /dev/null +++ b/src/plugins/7zip/7za/c/Util/7z/makefile @@ -0,0 +1,44 @@ +CFLAGS = $(CFLAGS) -DZ7_PPMD_SUPPORT -DZ7_EXTRACT_ONLY + +PROG = 7zDec.exe + +C_OBJS = \ + $O\7zAlloc.obj \ + $O\7zBuf.obj \ + $O\7zFile.obj \ + $O\7zDec.obj \ + $O\7zArcIn.obj \ + $O\7zStream.obj \ + $O\Bcj2.obj \ + $O\Bra.obj \ + $O\Bra86.obj \ + $O\BraIA64.obj \ + $O\CpuArch.obj \ + $O\Delta.obj \ + $O\Lzma2Dec.obj \ + $O\LzmaDec.obj \ + $O\Ppmd7.obj \ + $O\Ppmd7Dec.obj \ + +7Z_OBJS = \ + $O\7zMain.obj \ + +!include "../../../CPP/7zip/Crc.mak" +!include "../../../CPP/7zip/LzmaDec.mak" + +OBJS = \ + $O\Precomp.obj \ + $(7Z_OBJS) \ + $(C_OBJS) \ + $(ASM_OBJS) \ + +!include "../../../CPP/Build.mak" + +$(7Z_OBJS): $(*B).c + $(CCOMPL_USE) +$(C_OBJS): ../../$(*B).c + $(CCOMPL_USE) +$O\Precomp.obj: Precomp.c + $(CCOMPL_PCH) + +!include "../../Asm_c.mak" diff --git a/src/plugins/7zip/7za/c/Util/7z/makefile.gcc b/src/plugins/7zip/7za/c/Util/7z/makefile.gcc new file mode 100644 index 00000000..f48d3621 --- /dev/null +++ b/src/plugins/7zip/7za/c/Util/7z/makefile.gcc @@ -0,0 +1,32 @@ +PROG = 7zdec + +LOCAL_FLAGS = -DZ7_PPMD_SUPPORT -DZ7_EXTRACT_ONLY + +include ../../../CPP/7zip/LzmaDec_gcc.mak + + +OBJS = \ + $(LZMA_DEC_OPT_OBJS) \ + $O/Bcj2.o \ + $O/Bra.o \ + $O/Bra86.o \ + $O/BraIA64.o \ + $O/CpuArch.o \ + $O/Delta.o \ + $O/Lzma2Dec.o \ + $O/LzmaDec.o \ + $O/Ppmd7.o \ + $O/Ppmd7Dec.o \ + $O/7zCrc.o \ + $O/7zCrcOpt.o \ + $O/7zAlloc.o \ + $O/7zArcIn.o \ + $O/7zBuf.o \ + $O/7zBuf2.o \ + $O/7zDec.o \ + $O/7zMain.o \ + $O/7zFile.o \ + $O/7zStream.o \ + + +include ../../7zip_gcc_c.mak diff --git a/src/plugins/7zip/7za/c/Util/7zipInstall/7zip.ico b/src/plugins/7zip/7za/c/Util/7zipInstall/7zip.ico new file mode 100644 index 00000000..47ffb781 Binary files /dev/null and b/src/plugins/7zip/7za/c/Util/7zipInstall/7zip.ico differ diff --git a/src/plugins/7zip/7za/c/Util/7zipInstall/7zipInstall.c b/src/plugins/7zip/7za/c/Util/7zipInstall/7zipInstall.c new file mode 100644 index 00000000..ce30732d --- /dev/null +++ b/src/plugins/7zip/7za/c/Util/7zipInstall/7zipInstall.c @@ -0,0 +1,1699 @@ +/* 7zipInstall.c - 7-Zip Installer +: Igor Pavlov : Public domain */ + +#include "Precomp.h" + +#define SZ_ERROR_ABORT 100 + +#include "../../7zWindows.h" + +#if defined(_MSC_VER) && _MSC_VER < 1600 +#pragma warning(disable : 4201) // nonstandard extension used : nameless struct/union +#endif + +Z7_DIAGNOSTIC_IGNORE_CAST_FUNCTION + +#ifdef Z7_OLD_WIN_SDK +struct IShellView; +#define SHFOLDERAPI EXTERN_C DECLSPEC_IMPORT HRESULT STDAPICALLTYPE +SHFOLDERAPI SHGetFolderPathW(HWND hwnd, int csidl, HANDLE hToken, DWORD dwFlags, LPWSTR pszPath); +#define BIF_NEWDIALOGSTYLE 0x0040 // Use the new dialog layout with the ability to resize +typedef enum { + SHGFP_TYPE_CURRENT = 0, // current value for user, verify it exists + SHGFP_TYPE_DEFAULT = 1, // default value, may not exist +} SHGFP_TYPE; +#endif +#if defined(__MINGW32__) || defined(__MINGW64__) +#include +#else +#include +#endif + +#include "../../7z.h" +#include "../../7zAlloc.h" +#include "../../7zCrc.h" +#include "../../7zFile.h" +#include "../../7zVersion.h" +#include "../../CpuArch.h" +#include "../../DllSecur.h" + +#include "resource.h" + +#if (defined(__GNUC__) && (__GNUC__ >= 8)) || defined(__clang__) + // #pragma GCC diagnostic ignored "-Wcast-function-type" +#endif + +#define LLL_(quote) L##quote +#define LLL(quote) LLL_(quote) + +#define wcscat lstrcatW +#define wcslen (size_t)lstrlenW +#define wcscpy lstrcpyW +// wcsncpy() and lstrcpynW() work differently. We don't use them. + +#define kInputBufSize ((size_t)1 << 18) + +#define Z7_7ZIP_CUR_VER ((MY_VER_MAJOR << 16) | MY_VER_MINOR) +#define Z7_7ZIP_DLL_VER_COMPAT ((16 << 16) | 3) + +static LPCSTR const k_7zip = "7-Zip"; + +static LPCWSTR const k_Reg_Software_7zip = L"Software\\7-Zip"; + +// #define Z7_64BIT_INSTALLER 1 + +#ifdef _WIN64 + #define Z7_64BIT_INSTALLER 1 +#endif + +#define k_7zip_with_Ver_base L"7-Zip " LLL(MY_VERSION) + +#ifdef Z7_64BIT_INSTALLER + + // #define USE_7ZIP_32_DLL + + #if defined(_M_ARM64) || defined(_M_ARM) + #define k_Postfix L" (arm64)" + #else + #define k_Postfix L" (x64)" + #define USE_7ZIP_32_DLL + #endif +#else + #if defined(_M_ARM64) || defined(_M_ARM) + #define k_Postfix L" (arm)" + #else + // #define k_Postfix L" (x86)" + #define k_Postfix + #endif +#endif + +#define k_7zip_with_Ver k_7zip_with_Ver_base k_Postfix + + +static LPCWSTR const k_7zip_with_Ver_str = k_7zip_with_Ver; + +static LPCWSTR const k_7zip_Setup = k_7zip_with_Ver L" Setup"; + +static LPCWSTR const k_Reg_Path = L"Path"; + +static LPCWSTR const k_Reg_Path32 = L"Path" + #ifdef Z7_64BIT_INSTALLER + L"64" + #else + L"32" + #endif + ; + +#if defined(Z7_64BIT_INSTALLER) && !defined(_WIN64) + #define k_Reg_WOW_Flag KEY_WOW64_64KEY +#else + #define k_Reg_WOW_Flag 0 +#endif + +#ifdef USE_7ZIP_32_DLL +#ifdef _WIN64 + #define k_Reg_WOW_Flag_32 KEY_WOW64_32KEY +#else + #define k_Reg_WOW_Flag_32 0 +#endif +#endif + +#define k_7zip_CLSID L"{23170F69-40C1-278A-1000-000100020000}" + +static LPCWSTR const k_Reg_CLSID_7zip = L"CLSID\\" k_7zip_CLSID; +static LPCWSTR const k_Reg_CLSID_7zip_Inproc = L"CLSID\\" k_7zip_CLSID L"\\InprocServer32"; + +#define g_AllUsers True + +static BoolInt g_Install_was_Pressed; +static BoolInt g_Finished; +static BoolInt g_SilentMode; + +static HWND g_HWND; +static HWND g_Path_HWND; +static HWND g_InfoLine_HWND; +static HWND g_Progress_HWND; + +static DWORD g_TotalSize; + +static WCHAR cmd[MAX_PATH + 4]; +static WCHAR cmdError[MAX_PATH + 4]; +static WCHAR path[MAX_PATH * 2 + 40]; + + + +static void CpyAscii(WCHAR *dest, const char *s) +{ + for (;;) + { + Byte b = (Byte)*s++; + *dest++ = b; + if (b == 0) + return; + } +} + +static void CatAscii(WCHAR *dest, const char *s) +{ + dest += wcslen(dest); + CpyAscii(dest, s); +} + +static void PrintErrorMessage(const char *s1, const WCHAR *s2) +{ + WCHAR m[MAX_PATH + 512]; + m[0] = 0; + CatAscii(m, "ERROR:"); + if (s1) + { + CatAscii(m, "\n"); + CatAscii(m, s1); + } + if (s2) + { + CatAscii(m, "\n"); + wcscat(m, s2); + } + MessageBoxW(g_HWND, m, k_7zip_with_Ver_str, MB_ICONERROR | MB_OK); +} + + +typedef DWORD (WINAPI * Func_GetFileVersionInfoSizeW)(LPCWSTR lptstrFilename, LPDWORD lpdwHandle); +typedef BOOL (WINAPI * Func_GetFileVersionInfoW)(LPCWSTR lptstrFilename, DWORD dwHandle, DWORD dwLen, LPVOID lpData); +typedef BOOL (WINAPI * Func_VerQueryValueW)(const LPVOID pBlock, LPWSTR lpSubBlock, LPVOID * lplpBuffer, PUINT puLen); + +static HMODULE g_version_dll_hModule; + +static DWORD GetFileVersion(LPCWSTR s) +{ + DWORD size = 0; + void *vi = NULL; + DWORD version = 0; + + Func_GetFileVersionInfoSizeW my_GetFileVersionInfoSizeW; + Func_GetFileVersionInfoW my_GetFileVersionInfoW; + Func_VerQueryValueW my_VerQueryValueW; + + if (!g_version_dll_hModule) + { + WCHAR buf[MAX_PATH + 100]; + { + unsigned len = GetSystemDirectoryW(buf, MAX_PATH + 2); + if (len == 0 || len > MAX_PATH) + return 0; + } + { + unsigned pos = (unsigned)lstrlenW(buf); + if (buf[pos - 1] != '\\') + buf[pos++] = '\\'; + lstrcpyW(buf + pos, L"version.dll"); + } + g_version_dll_hModule = LoadLibraryW(buf); + if (!g_version_dll_hModule) + return 0; + } + + my_GetFileVersionInfoSizeW = (Func_GetFileVersionInfoSizeW) Z7_CAST_FUNC_C GetProcAddress(g_version_dll_hModule, + "GetFileVersionInfoSizeW"); + my_GetFileVersionInfoW = (Func_GetFileVersionInfoW) Z7_CAST_FUNC_C GetProcAddress(g_version_dll_hModule, + "GetFileVersionInfoW"); + my_VerQueryValueW = (Func_VerQueryValueW) Z7_CAST_FUNC_C GetProcAddress(g_version_dll_hModule, + "VerQueryValueW"); + + if (!my_GetFileVersionInfoSizeW + || !my_GetFileVersionInfoW + || !my_VerQueryValueW) + return 0; + + size = my_GetFileVersionInfoSizeW(s, NULL); + if (size == 0) + return 0; + + vi = malloc(size); + if (!vi) + return 0; + + if (my_GetFileVersionInfoW(s, 0, size, vi)) + { + VS_FIXEDFILEINFO *fi = NULL; + UINT fiLen = 0; + if (my_VerQueryValueW(vi, L"\\", (LPVOID *)&fi, &fiLen)) + version = fi->dwFileVersionMS; + } + + free(vi); + return version; +} + + +static WRes MyCreateDir(LPCWSTR name) +{ + return CreateDirectoryW(name, NULL) ? 0 : GetLastError(); +} + +#define IS_SEPAR(c) (c == WCHAR_PATH_SEPARATOR) +#define IS_LETTER_CHAR(c) (((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z')) +#define IS_DRIVE_PATH(s) (IS_LETTER_CHAR(s[0]) && s[1] == ':' && IS_SEPAR(s[2])) + +static int ReverseFind_PathSepar(const WCHAR *s) +{ + int separ = -1; + int i; + for (i = 0;; i++) + { + WCHAR c = s[i]; + if (c == 0) + return separ; + if (IS_SEPAR(c)) + separ = i; + } +} + +static WRes CreateComplexDir(void) +{ + WCHAR s[MAX_PATH + 10]; + + unsigned prefixSize = 0; + WRes wres; + + { + size_t len = wcslen(path); + if (len > MAX_PATH) + return ERROR_INVALID_NAME; + wcscpy(s, path); + } + + if (IS_DRIVE_PATH(s)) + prefixSize = 3; + else if (IS_SEPAR(s[0]) && IS_SEPAR(s[1])) + prefixSize = 2; + else + return ERROR_INVALID_NAME; + + { + DWORD attrib = GetFileAttributesW(s); + if (attrib != INVALID_FILE_ATTRIBUTES) + return (attrib & FILE_ATTRIBUTE_DIRECTORY) != 0 ? 0 : ERROR_ALREADY_EXISTS; + } + + wres = MyCreateDir(s); + if (wres == 0 || wres == ERROR_ALREADY_EXISTS) + return 0; + + { + size_t len = wcslen(s); + { + const int pos = ReverseFind_PathSepar(s); + if (pos < 0) + return wres; + if ((unsigned)pos < prefixSize) + return wres; + if ((unsigned)pos == len - 1) + { + if (len == 1) + return 0; + s[pos] = 0; + len = (unsigned)pos; + } + } + + for (;;) + { + int pos; + wres = MyCreateDir(s); + if (wres == 0) + break; + if (wres == ERROR_ALREADY_EXISTS) + { + const DWORD attrib = GetFileAttributesW(s); + if (attrib != INVALID_FILE_ATTRIBUTES) + if ((attrib & FILE_ATTRIBUTE_DIRECTORY) == 0) + return ERROR_ALREADY_EXISTS; + break; + } + pos = ReverseFind_PathSepar(s); + if (pos < 0 || pos == 0 || (unsigned)pos < prefixSize) + return wres; + s[pos] = 0; + } + + for (;;) + { + const size_t pos = wcslen(s); + if (pos >= len) + return 0; + s[pos] = CHAR_PATH_SEPARATOR; + wres = MyCreateDir(s); + if (wres != 0) + return wres; + } + } +} + + +static int MyRegistry_QueryString(HKEY hKey, LPCWSTR name, LPWSTR dest) +{ + DWORD cnt = MAX_PATH * sizeof(name[0]); + DWORD type = 0; + const LONG res = RegQueryValueExW(hKey, name, NULL, &type, (LPBYTE)dest, &cnt); + if (type != REG_SZ) + return False; + return res == ERROR_SUCCESS; +} + +static int MyRegistry_QueryString2(HKEY hKey, LPCWSTR keyName, LPCWSTR valName, LPWSTR dest) +{ + HKEY key = 0; + const LONG res = RegOpenKeyExW(hKey, keyName, 0, KEY_READ | k_Reg_WOW_Flag, &key); + if (res != ERROR_SUCCESS) + return False; + { + const BoolInt res2 = MyRegistry_QueryString(key, valName, dest); + RegCloseKey(key); + return res2; + } +} + +static LONG MyRegistry_SetString(HKEY hKey, LPCWSTR name, LPCWSTR val) +{ + return RegSetValueExW(hKey, name, 0, REG_SZ, + (const BYTE *)val, (DWORD)(wcslen(val) + 1) * sizeof(val[0])); +} + +static LONG MyRegistry_SetDWORD(HKEY hKey, LPCWSTR name, DWORD val) +{ + return RegSetValueExW(hKey, name, 0, REG_DWORD, (const BYTE *)&val, sizeof(DWORD)); +} + + +static LONG MyRegistry_CreateKey(HKEY parentKey, LPCWSTR name, HKEY *destKey) +{ + return RegCreateKeyExW(parentKey, name, 0, NULL, + REG_OPTION_NON_VOLATILE, + KEY_ALL_ACCESS | k_Reg_WOW_Flag, + NULL, destKey, NULL); +} + +static LONG MyRegistry_CreateKeyAndVal(HKEY parentKey, LPCWSTR keyName, LPCWSTR valName, LPCWSTR val) +{ + HKEY destKey = 0; + LONG res = MyRegistry_CreateKey(parentKey, keyName, &destKey); + if (res == ERROR_SUCCESS) + { + res = MyRegistry_SetString(destKey, valName, val); + /* res = */ RegCloseKey(destKey); + } + return res; +} + + +#ifdef USE_7ZIP_32_DLL + +static LONG MyRegistry_CreateKey_32(HKEY parentKey, LPCWSTR name, HKEY *destKey) +{ + return RegCreateKeyExW(parentKey, name, 0, NULL, + REG_OPTION_NON_VOLATILE, + KEY_ALL_ACCESS | k_Reg_WOW_Flag_32, + NULL, destKey, NULL); +} + +static LONG MyRegistry_CreateKeyAndVal_32(HKEY parentKey, LPCWSTR keyName, LPCWSTR valName, LPCWSTR val) +{ + HKEY destKey = 0; + LONG res = MyRegistry_CreateKey_32(parentKey, keyName, &destKey); + if (res == ERROR_SUCCESS) + { + res = MyRegistry_SetString(destKey, valName, val); + /* res = */ RegCloseKey(destKey); + } + return res; +} + +#endif + + + +#ifdef UNDER_CE + #define kBufSize (1 << 13) +#else + #define kBufSize (1 << 15) +#endif + +#define kSignatureSearchLimit (1 << 22) + +static BoolInt FindSignature(CSzFile *stream, UInt64 *resPos) +{ + Byte buf[kBufSize]; + size_t numPrevBytes = 0; + *resPos = 0; + + for (;;) + { + size_t processed, pos; + if (*resPos > kSignatureSearchLimit) + return False; + processed = kBufSize - numPrevBytes; + if (File_Read(stream, buf + numPrevBytes, &processed) != 0) + return False; + processed += numPrevBytes; + if (processed < k7zStartHeaderSize || + (processed == k7zStartHeaderSize && numPrevBytes != 0)) + return False; + processed -= k7zStartHeaderSize; + for (pos = 0; pos <= processed; pos++) + { + for (; pos <= processed && buf[pos] != '7'; pos++); + if (pos > processed) + break; + if (memcmp(buf + pos, k7zSignature, k7zSignatureSize) == 0) + if (CrcCalc(buf + pos + 12, 20) == GetUi32(buf + pos + 8)) + { + *resPos += pos; + return True; + } + } + *resPos += processed; + numPrevBytes = k7zStartHeaderSize; + memmove(buf, buf + processed, k7zStartHeaderSize); + } +} + +static void HexToString(UInt32 val, WCHAR *s) +{ + UInt64 v = val; + unsigned i; + for (i = 1;; i++) + { + v >>= 4; + if (v == 0) + break; + } + s[i] = 0; + do + { + unsigned t = (unsigned)((val & 0xF)); + val >>= 4; + s[--i] = (WCHAR)(unsigned)((t < 10) ? ('0' + t) : ('A' + (t - 10))); + } + while (i); +} + + +#ifndef UNDER_CE + +static int CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lp, LPARAM data) +{ + UNUSED_VAR(lp) + UNUSED_VAR(data) + UNUSED_VAR(hwnd) + + switch (uMsg) + { + case BFFM_INITIALIZED: + { + SendMessage(hwnd, BFFM_SETSELECTIONW, TRUE, data); + break; + } + case BFFM_SELCHANGED: + { + // show selected path for BIF_STATUSTEXT + WCHAR dir[MAX_PATH]; + if (!SHGetPathFromIDListW((LPITEMIDLIST)lp, dir)) + dir[0] = 0; + SendMessage(hwnd, BFFM_SETSTATUSTEXTW, 0, (LPARAM)dir); + break; + } + default: + break; + } + return 0; +} + +static BoolInt MyBrowseForFolder(HWND owner, LPCWSTR title, UINT ulFlags, + LPCWSTR initialFolder, LPWSTR resultPath) +{ + WCHAR displayName[MAX_PATH]; + BROWSEINFOW browseInfo; + + displayName[0] = 0; + browseInfo.hwndOwner = owner; + browseInfo.pidlRoot = NULL; + + // there are Unicode/Astring problems in some WinCE SDK ? + browseInfo.pszDisplayName = displayName; + browseInfo.lpszTitle = title; + browseInfo.ulFlags = ulFlags; + browseInfo.lpfn = (initialFolder != NULL) ? BrowseCallbackProc : NULL; + browseInfo.lParam = (LPARAM)initialFolder; + { + LPITEMIDLIST idlist = SHBrowseForFolderW(&browseInfo); + if (idlist) + { + SHGetPathFromIDListW(idlist, resultPath); + // free idlist + // CoTaskMemFree(idlist); + return True; + } + return False; + } +} + +#endif + +static void NormalizePrefix(WCHAR *s) +{ + size_t i = 0; + + for (;; i++) + { + const WCHAR c = s[i]; + if (c == 0) + break; + if (c == '/') + s[i] = WCHAR_PATH_SEPARATOR; + } + + if (i != 0 && s[i - 1] != WCHAR_PATH_SEPARATOR) + { + s[i] = WCHAR_PATH_SEPARATOR; + s[i + 1] = 0; + } +} + +static char MyCharLower_Ascii(char c) +{ + if (c >= 'A' && c <= 'Z') + return (char)((unsigned char)c + 0x20); + return c; +} + +static WCHAR MyWCharLower_Ascii(WCHAR c) +{ + if (c >= 'A' && c <= 'Z') + return (WCHAR)(c + 0x20); + return c; +} + +static LPCWSTR FindSubString(LPCWSTR s1, const char *s2) +{ + for (;;) + { + unsigned i; + if (*s1 == 0) + return NULL; + for (i = 0;; i++) + { + const char b = s2[i]; + if (b == 0) + return s1; + if (MyWCharLower_Ascii(s1[i]) != (Byte)MyCharLower_Ascii(b)) + { + s1++; + break; + } + } + } +} + +static void Set7zipPostfix(WCHAR *s) +{ + NormalizePrefix(s); + if (FindSubString(s, "7-Zip")) + return; + CatAscii(s, "7-Zip\\"); +} + + +static int Install(void); + +static void OnClose(void) +{ + if (g_Install_was_Pressed && !g_Finished) + { + if (MessageBoxW(g_HWND, + L"Do you want to cancel " k_7zip_with_Ver L" installation?", + k_7zip_with_Ver, + MB_ICONQUESTION | MB_YESNO | MB_DEFBUTTON2) != IDYES) + return; + } + DestroyWindow(g_HWND); + g_HWND = NULL; +} + +static +#ifdef Z7_OLD_WIN_SDK + BOOL +#else + INT_PTR +#endif +CALLBACK MyDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + // UNUSED_VAR(hwnd) + UNUSED_VAR(lParam) + + switch (message) + { + case WM_INITDIALOG: + g_Path_HWND = GetDlgItem(hwnd, IDE_EXTRACT_PATH); + g_InfoLine_HWND = GetDlgItem(hwnd, IDT_CUR_FILE); + g_Progress_HWND = GetDlgItem(hwnd, IDC_PROGRESS); + + SetWindowTextW(hwnd, k_7zip_Setup); + SetDlgItemTextW(hwnd, IDE_EXTRACT_PATH, path); + + ShowWindow(g_Progress_HWND, SW_HIDE); + ShowWindow(g_InfoLine_HWND, SW_HIDE); + + break; + + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDOK: + { + if (g_Finished) + { + OnClose(); + break; + } + if (!g_Install_was_Pressed) + { + SendMessage(hwnd, WM_NEXTDLGCTL, (WPARAM)(void *)GetDlgItem(hwnd, IDCANCEL), TRUE); + + EnableWindow(g_Path_HWND, FALSE); + EnableWindow(GetDlgItem(hwnd, IDB_EXTRACT_SET_PATH), FALSE); + EnableWindow(GetDlgItem(hwnd, IDOK), FALSE); + + g_Install_was_Pressed = True; + return TRUE; + } + break; + } + + case IDCANCEL: + { + OnClose(); + break; + } + + case IDB_EXTRACT_SET_PATH: + { + #ifndef UNDER_CE + + WCHAR s[MAX_PATH]; + WCHAR s2[MAX_PATH]; + GetDlgItemTextW(hwnd, IDE_EXTRACT_PATH, s, MAX_PATH); + if (MyBrowseForFolder(hwnd, L"Select the folder for installation:" , + 0 + | BIF_NEWDIALOGSTYLE // 5.0 of ?.dll ? + | BIF_RETURNONLYFSDIRS + // | BIF_STATUSTEXT // doesn't work for BIF_NEWDIALOGSTYLE + , s, s2)) + { + Set7zipPostfix(s2); + SetDlgItemTextW(hwnd, IDE_EXTRACT_PATH, s2); + } + + #endif + break; + } + + default: return FALSE; + } + break; + + case WM_CLOSE: + OnClose(); + break; + /* + case WM_DESTROY: + PostQuitMessage(0); + return TRUE; + */ + default: + return FALSE; + } + + return TRUE; +} + + + +static LONG SetRegKey_Path2(HKEY parentKey) +{ + HKEY destKey = 0; + LONG res = MyRegistry_CreateKey(parentKey, k_Reg_Software_7zip, &destKey); + if (res == ERROR_SUCCESS) + { + res = MyRegistry_SetString(destKey, k_Reg_Path32, path); + /* res = */ MyRegistry_SetString(destKey, k_Reg_Path, path); + /* res = */ RegCloseKey(destKey); + } + return res; +} + +static void SetRegKey_Path(void) +{ + SetRegKey_Path2(HKEY_CURRENT_USER); + SetRegKey_Path2(HKEY_LOCAL_MACHINE); +} + + +static HRESULT CreateShellLink(LPCWSTR srcPath, LPCWSTR targetPath) +{ + IShellLinkW* sl; + + // CoInitialize has already been called. + HRESULT hres = CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, &IID_IShellLinkW, (LPVOID*)&sl); + + if (SUCCEEDED(hres)) + { + IPersistFile* pf; + + sl->lpVtbl->SetPath(sl, targetPath); + // sl->lpVtbl->SetDescription(sl, description); + hres = sl->lpVtbl->QueryInterface(sl, &IID_IPersistFile, (LPVOID*)&pf); + + if (SUCCEEDED(hres)) + { + hres = pf->lpVtbl->Save(pf, srcPath, TRUE); + pf->lpVtbl->Release(pf); + } + sl->lpVtbl->Release(sl); + } + + return hres; +} + +static void SetShellProgramsGroup(HWND hwndOwner) +{ + #ifdef UNDER_CE + + // CpyAscii(link, "\\Program Files\\"); + UNUSED_VAR(hwndOwner) + + #else + + unsigned i = (g_AllUsers ? 0 : 2); + + for (; i < 3; i++) + { + BoolInt isOK = True; + WCHAR link[MAX_PATH + 40]; + WCHAR destPath[MAX_PATH + 40]; + + link[0] = 0; + + if (SHGetFolderPathW(hwndOwner, + i == 1 ? CSIDL_COMMON_PROGRAMS : CSIDL_PROGRAMS, + NULL, SHGFP_TYPE_CURRENT, link) != S_OK) + continue; + + NormalizePrefix(link); + CatAscii(link, k_7zip); + // CatAscii(link, "2"); + + if (i != 0) + MyCreateDir(link); + + NormalizePrefix(link); + + { + unsigned baseLen = (unsigned)wcslen(link); + unsigned k; + + for (k = 0; k < 2; k++) + { + CpyAscii(link + baseLen, k == 0 ? + "7-Zip File Manager.lnk" : + "7-Zip Help.lnk" + ); + wcscpy(destPath, path); + CatAscii(destPath, k == 0 ? + "7zFM.exe" : + "7-zip.chm"); + + if (i == 0) + DeleteFileW(link); + else if (CreateShellLink(link, destPath) != S_OK) + isOK = False; + } + } + + if (i != 0 && isOK) + break; + } + + #endif +} + +static LPCWSTR const k_Shell_Approved = L"Software\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved"; +static LPCWSTR const k_7zip_ShellExtension = L"7-Zip Shell Extension"; + +static void WriteCLSID(void) +{ + HKEY destKey; + LONG res; + + #ifdef USE_7ZIP_32_DLL + + MyRegistry_CreateKeyAndVal_32(HKEY_CLASSES_ROOT, k_Reg_CLSID_7zip, NULL, k_7zip_ShellExtension); + + res = MyRegistry_CreateKey_32(HKEY_CLASSES_ROOT, k_Reg_CLSID_7zip_Inproc, &destKey); + + if (res == ERROR_SUCCESS) + { + WCHAR destPath[MAX_PATH + 40]; + wcscpy(destPath, path); + CatAscii(destPath, "7-zip32.dll"); + /* res = */ MyRegistry_SetString(destKey, NULL, destPath); + /* res = */ MyRegistry_SetString(destKey, L"ThreadingModel", L"Apartment"); + // DeleteRegValue(destKey, L"InprocServer32"); + /* res = */ RegCloseKey(destKey); + } + + #endif + + + MyRegistry_CreateKeyAndVal(HKEY_CLASSES_ROOT, k_Reg_CLSID_7zip, NULL, k_7zip_ShellExtension); + + destKey = 0; + res = MyRegistry_CreateKey(HKEY_CLASSES_ROOT, k_Reg_CLSID_7zip_Inproc, &destKey); + + if (res == ERROR_SUCCESS) + { + WCHAR destPath[MAX_PATH + 40]; + wcscpy(destPath, path); + CatAscii(destPath, "7-zip.dll"); + /* res = */ MyRegistry_SetString(destKey, NULL, destPath); + /* res = */ MyRegistry_SetString(destKey, L"ThreadingModel", L"Apartment"); + // DeleteRegValue(destKey, L"InprocServer32"); + /* res = */ RegCloseKey(destKey); + } +} + +static LPCSTR const k_ShellEx_Items[] = +{ + "*\\shellex\\ContextMenuHandlers" + , "Directory\\shellex\\ContextMenuHandlers" + , "Folder\\shellex\\ContextMenuHandlers" + , "Directory\\shellex\\DragDropHandlers" + , "Drive\\shellex\\DragDropHandlers" +}; + +static void WriteShellEx(void) +{ + unsigned i; + WCHAR destPath[MAX_PATH + 40]; + + for (i = 0; i < Z7_ARRAY_SIZE(k_ShellEx_Items); i++) + { + CpyAscii(destPath, k_ShellEx_Items[i]); + CatAscii(destPath, "\\7-Zip"); + + #ifdef USE_7ZIP_32_DLL + MyRegistry_CreateKeyAndVal_32(HKEY_CLASSES_ROOT, destPath, NULL, k_7zip_CLSID); + #endif + MyRegistry_CreateKeyAndVal (HKEY_CLASSES_ROOT, destPath, NULL, k_7zip_CLSID); + } + + #ifdef USE_7ZIP_32_DLL + MyRegistry_CreateKeyAndVal_32(HKEY_LOCAL_MACHINE, k_Shell_Approved, k_7zip_CLSID, k_7zip_ShellExtension); + #endif + MyRegistry_CreateKeyAndVal (HKEY_LOCAL_MACHINE, k_Shell_Approved, k_7zip_CLSID, k_7zip_ShellExtension); + + + wcscpy(destPath, path); + CatAscii(destPath, "7zFM.exe"); + + { + HKEY destKey = 0; + LONG res = MyRegistry_CreateKey(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\7zFM.exe", &destKey); + if (res == ERROR_SUCCESS) + { + MyRegistry_SetString(destKey, NULL, destPath); + MyRegistry_SetString(destKey, L"Path", path); + RegCloseKey(destKey); + } + + } + + { + HKEY destKey = 0; + LONG res = MyRegistry_CreateKey(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\7-Zip", &destKey); + if (res == ERROR_SUCCESS) + { + MyRegistry_SetString(destKey, L"DisplayName", k_7zip_with_Ver_str); + MyRegistry_SetString(destKey, L"DisplayVersion", LLL(MY_VERSION_NUMBERS)); + MyRegistry_SetString(destKey, L"DisplayIcon", destPath); + MyRegistry_SetString(destKey, L"InstallLocation", path); + + destPath[0] = '\"'; + wcscpy(destPath + 1, path); + CatAscii(destPath, "Uninstall.exe\""); + MyRegistry_SetString(destKey, L"UninstallString", destPath); + + CatAscii(destPath, " /S"); + MyRegistry_SetString(destKey, L"QuietUninstallString", destPath); + + MyRegistry_SetDWORD(destKey, L"NoModify", 1); + MyRegistry_SetDWORD(destKey, L"NoRepair", 1); + + MyRegistry_SetDWORD(destKey, L"EstimatedSize", g_TotalSize >> 10); + + MyRegistry_SetDWORD(destKey, L"VersionMajor", MY_VER_MAJOR); + MyRegistry_SetDWORD(destKey, L"VersionMinor", MY_VER_MINOR); + + MyRegistry_SetString(destKey, L"Publisher", LLL(MY_AUTHOR_NAME)); + + // MyRegistry_SetString(destKey, L"HelpLink", L"http://www.7-zip.org/support.html"); + // MyRegistry_SetString(destKey, L"URLInfoAbout", L"http://www.7-zip.org/"); + // MyRegistry_SetString(destKey, L"URLUpdateInfo", L"http://www.7-zip.org/"); + + RegCloseKey(destKey); + } + } +} + + +static const WCHAR *GetCmdParam(const WCHAR *s) +{ + unsigned pos = 0; + BoolInt quoteMode = False; + for (;; s++) + { + WCHAR c = *s; + if (c == 0 || (c == L' ' && !quoteMode)) + break; + if (c == L'\"') + { + quoteMode = !quoteMode; + continue; + } + if (pos >= Z7_ARRAY_SIZE(cmd) - 1) + exit(1); + cmd[pos++] = c; + } + cmd[pos] = 0; + return s; +} + + +static void RemoveQuotes(WCHAR *s) +{ + const WCHAR *src = s; + for (;;) + { + WCHAR c = *src++; + if (c == '\"') + continue; + *s++ = c; + if (c == 0) + return; + } +} + +// #define IS_LIMIT_CHAR(c) (c == 0 || c == ' ') + + +typedef BOOL (WINAPI *Func_IsWow64Process)(HANDLE, PBOOL); + +int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, + #ifdef UNDER_CE + LPWSTR + #else + LPSTR + #endif + lpCmdLine, int nCmdShow) +{ + + UNUSED_VAR(hPrevInstance) + UNUSED_VAR(lpCmdLine) + UNUSED_VAR(nCmdShow) + + #ifndef UNDER_CE + LoadSecurityDlls(); + CoInitialize(NULL); + #endif + + CrcGenerateTable(); + + { + const WCHAR *s = GetCommandLineW(); + + #ifndef UNDER_CE + s = GetCmdParam(s); + #endif + + for (;;) + { + { + const WCHAR c = *s; + if (c == 0) + break; + if (c == ' ') + { + s++; + continue; + } + } + + { + const WCHAR *s2 = GetCmdParam(s); + BoolInt error = True; + if (cmd[0] == '/') + { + if (cmd[1] == 'S') + { + if (cmd[2] == 0) + { + g_SilentMode = True; + error = False; + } + } + else if (cmd[1] == 'D' && cmd[2] == '=') + { + wcscpy(path, cmd + 3); + // RemoveQuotes(path); + error = False; + } + } + s = s2; + if (error && cmdError[0] == 0) + wcscpy(cmdError, cmd); + } + } + + if (cmdError[0] != 0) + { + if (!g_SilentMode) + PrintErrorMessage("Unsupported command:", cmdError); + return 1; + } + } + + #if defined(Z7_64BIT_INSTALLER) && !defined(_WIN64) + { + BOOL isWow64 = FALSE; + const Func_IsWow64Process func_IsWow64Process = (Func_IsWow64Process) + Z7_CAST_FUNC_C GetProcAddress(GetModuleHandleW(L"kernel32.dll"), + "IsWow64Process"); + + if (func_IsWow64Process) + func_IsWow64Process(GetCurrentProcess(), &isWow64); + + if (!isWow64) + { + if (!g_SilentMode) + PrintErrorMessage("This installation requires Windows " + #ifdef MY_CPU_X86_OR_AMD64 + "x64" + #else + "64-bit" + #endif + , NULL); + return 1; + } + } + #endif + + + if (path[0] == 0) + { + HKEY key = 0; + BoolInt ok = False; + const LONG res = RegOpenKeyExW(HKEY_CURRENT_USER, k_Reg_Software_7zip, 0, KEY_READ | k_Reg_WOW_Flag, &key); + if (res == ERROR_SUCCESS) + { + ok = MyRegistry_QueryString(key, k_Reg_Path32, path); + // ok = MyRegistry_QueryString(key, k_Reg_Path, path); + RegCloseKey(key); + } + + // ok = False; + if (!ok) + { + /* + #ifdef UNDER_CE + CpyAscii(path, "\\Program Files\\"); + #else + + #ifdef Z7_64BIT_INSTALLER + { + DWORD ttt = GetEnvironmentVariableW(L"ProgramW6432", path, MAX_PATH); + if (ttt == 0 || ttt > MAX_PATH) + CpyAscii(path, "C:\\"); + } + #else + if (!SHGetSpecialFolderPathW(0, path, CSIDL_PROGRAM_FILES, FALSE)) + CpyAscii(path, "C:\\"); + #endif + #endif + */ + if (!MyRegistry_QueryString2(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows\\CurrentVersion", L"ProgramFilesDir", path)) + CpyAscii(path, + #ifdef UNDER_CE + "\\Program Files\\" + #else + "C:\\" + #endif + ); + + Set7zipPostfix(path); + } + } + + NormalizePrefix(path); + + if (g_SilentMode) + return Install(); + + { + int retCode = 1; + // INT_PTR res = DialogBox( + g_HWND = CreateDialog( + hInstance, + // GetModuleHandle(NULL), + MAKEINTRESOURCE(IDD_INSTALL), NULL, MyDlgProc); + if (!g_HWND) + return 1; + + { + const HICON hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON)); + // SendMessage(g_HWND, WM_SETICON, (WPARAM)ICON_SMALL, (LPARAM)hIcon); + SendMessage(g_HWND, WM_SETICON, (WPARAM)ICON_BIG, (LPARAM)hIcon); + } + + + { + BOOL bRet; + MSG msg; + + // we need messages for all thread windows (including EDITTEXT window in dialog) + while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0) + { + if (bRet == -1) + return retCode; + if (!g_HWND) + return retCode; + + if (!IsDialogMessage(g_HWND, &msg)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + if (!g_HWND) + return retCode; + + if (g_Install_was_Pressed && !g_Finished) + { + retCode = Install(); + g_Finished = True; + if (retCode != 0) + break; + if (!g_HWND) + break; + { + SetDlgItemTextW(g_HWND, IDOK, L"Close"); + EnableWindow(GetDlgItem(g_HWND, IDOK), TRUE); + EnableWindow(GetDlgItem(g_HWND, IDCANCEL), FALSE); + SendMessage(g_HWND, WM_NEXTDLGCTL, (WPARAM)(void *)GetDlgItem(g_HWND, IDOK), TRUE); + } + } + } + + if (g_HWND) + { + DestroyWindow(g_HWND); + g_HWND = NULL; + } + } + + return retCode; + } +} + + +static BoolInt GetErrorMessage(DWORD errorCode, WCHAR *message) +{ + LPWSTR msgBuf; + if (FormatMessageW( + FORMAT_MESSAGE_ALLOCATE_BUFFER + | FORMAT_MESSAGE_FROM_SYSTEM + | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, errorCode, 0, (LPWSTR) &msgBuf, 0, NULL) == 0) + return False; + wcscpy(message, msgBuf); + LocalFree(msgBuf); + return True; +} + + + +static int Install(void) +{ + CFileInStream archiveStream; + CLookToRead2 lookStream; + CSzArEx db; + + SRes res = SZ_OK; + WRes winRes = 0; + const char *errorMessage = NULL; + + ISzAlloc allocImp; + ISzAlloc allocTempImp; + WCHAR sfxPath[MAX_PATH + 2]; + + int needRebootLevel = 0; + + allocImp.Alloc = SzAlloc; + allocImp.Free = SzFree; + + allocTempImp.Alloc = SzAllocTemp; + allocTempImp.Free = SzFreeTemp; + + { + const DWORD len = GetModuleFileNameW(NULL, sfxPath, MAX_PATH); + if (len == 0 || len > MAX_PATH) + return 1; + } + + winRes = InFile_OpenW(&archiveStream.file, sfxPath); + + if (winRes == 0) + { + UInt64 pos = 0; + if (!FindSignature(&archiveStream.file, &pos)) + errorMessage = "Can't find 7z archive"; + else + winRes = File_Seek(&archiveStream.file, (Int64 *)&pos, SZ_SEEK_SET); + } + + if (winRes != 0) + res = SZ_ERROR_FAIL; + + if (errorMessage) + res = SZ_ERROR_FAIL; + +if (res == SZ_OK) +{ + size_t pathLen; + if (!g_SilentMode) + { + GetDlgItemTextW(g_HWND, IDE_EXTRACT_PATH, path, MAX_PATH); + } + + FileInStream_CreateVTable(&archiveStream); + LookToRead2_CreateVTable(&lookStream, False); + lookStream.buf = NULL; + + RemoveQuotes(path); + { + // Remove post spaces + unsigned endPos = 0; + unsigned i = 0; + + for (;;) + { + const WCHAR c = path[i++]; + if (c == 0) + break; + if (c != ' ') + endPos = i; + } + + path[endPos] = 0; + if (path[0] == 0) + { + PrintErrorMessage("Incorrect path", NULL); + return 1; + } + } + + NormalizePrefix(path); + winRes = CreateComplexDir(); + + if (winRes != 0) + res = SZ_ERROR_FAIL; + + pathLen = wcslen(path); + + if (res == SZ_OK) + { + lookStream.buf = (Byte *)ISzAlloc_Alloc(&allocImp, kInputBufSize); + if (!lookStream.buf) + res = SZ_ERROR_MEM; + else + { + lookStream.bufSize = kInputBufSize; + lookStream.realStream = &archiveStream.vt; + LookToRead2_INIT(&lookStream) + } + } + + SzArEx_Init(&db); + + if (res == SZ_OK) + { + res = SzArEx_Open(&db, &lookStream.vt, &allocImp, &allocTempImp); + } + + if (res == SZ_OK) + { + UInt32 i; + UInt32 blockIndex = 0xFFFFFFFF; /* it can have any value before first call, if (!outBuf) */ + Byte *outBuf = NULL; /* it must be NULL before first call for each new archive. */ + size_t outBufSize = 0; /* it can have any value before first call, if (!outBuf) */ + + g_TotalSize = 0; + + if (!g_SilentMode) + { + ShowWindow(g_Progress_HWND, SW_SHOW); + ShowWindow(g_InfoLine_HWND, SW_SHOW); + SendMessage(g_Progress_HWND, PBM_SETRANGE32, 0, db.NumFiles); + } + + for (i = 0; i < db.NumFiles; i++) + { + size_t offset = 0; + size_t outSizeProcessed = 0; + WCHAR *temp; + + if (!g_SilentMode) + { + MSG msg; + + // g_HWND + while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) + { + if (!IsDialogMessage(g_HWND, &msg)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + if (!g_HWND) + return 1; + } + + // Sleep(10); + SendMessage(g_Progress_HWND, PBM_SETPOS, i, 0); + } + + { + const size_t len = SzArEx_GetFileNameUtf16(&db, i, NULL); + if (len >= MAX_PATH) + { + res = SZ_ERROR_FAIL; + break; + } + } + + temp = path + pathLen; + + SzArEx_GetFileNameUtf16(&db, i, (UInt16 *)temp); + + if (!g_SilentMode) + SetWindowTextW(g_InfoLine_HWND, temp); + + { + res = SzArEx_Extract(&db, &lookStream.vt, i, + &blockIndex, &outBuf, &outBufSize, + &offset, &outSizeProcessed, + &allocImp, &allocTempImp); + if (res != SZ_OK) + break; + } + + { + CSzFile outFile; + size_t processedSize; + size_t j; + // size_t nameStartPos = 0; + UInt32 tempIndex = 0; + int fileLevel = 1 << 2; + WCHAR origPath[MAX_PATH * 2 + 10]; + + for (j = 0; temp[j] != 0; j++) + { + if (temp[j] == '/') + { + temp[j] = 0; + MyCreateDir(path); + temp[j] = CHAR_PATH_SEPARATOR; + // nameStartPos = j + 1; + } + } + + if (SzArEx_IsDir(&db, i)) + { + MyCreateDir(path); + continue; + } + + { + // BoolInt skipFile = False; + + wcscpy(origPath, path); + + for (;;) + { + WRes openRes; + + if (tempIndex != 0) + { + if (tempIndex > 100) + { + res = SZ_ERROR_FAIL; + break; + } + wcscpy(path, origPath); + CatAscii(path, ".tmp"); + if (tempIndex > 1) + HexToString(tempIndex, path + wcslen(path)); + if (GetFileAttributesW(path) != INVALID_FILE_ATTRIBUTES) + { + tempIndex++; + continue; + } + } + + { + SetFileAttributesW(path, 0); + openRes = OutFile_OpenW(&outFile, path); + if (openRes == 0) + break; + } + + if (tempIndex != 0) + { + tempIndex++; + continue; + } + + if (FindSubString(temp, "7-zip.dll") + #ifdef USE_7ZIP_32_DLL + || FindSubString(temp, "7-zip32.dll") + #endif + ) + { + const DWORD ver = GetFileVersion(path); + fileLevel = ((ver < Z7_7ZIP_DLL_VER_COMPAT || ver > Z7_7ZIP_CUR_VER) ? 2 : 1); + tempIndex++; + continue; + } + + if (g_SilentMode) + { + tempIndex++; + continue; + } + { + WCHAR message[MAX_PATH * 3 + 100]; + int mbRes; + + CpyAscii(message, "Can't open file\n"); + wcscat(message, path); + CatAscii(message, "\n"); + + GetErrorMessage(openRes, message + wcslen(message)); + + mbRes = MessageBoxW(g_HWND, message, L"Error", MB_ICONERROR | MB_ABORTRETRYIGNORE | MB_DEFBUTTON3); + if (mbRes == IDABORT) + { + res = SZ_ERROR_ABORT; + tempIndex = 0; + break; + } + if (mbRes == IDIGNORE) + { + // skipFile = True; + tempIndex++; + } + } + } + + if (res != SZ_OK) + break; + + /* + if (skipFile) + continue; + */ + } + + // if (res == SZ_OK) + { + processedSize = outSizeProcessed; + winRes = File_Write(&outFile, outBuf + offset, &processedSize); + if (winRes != 0 || processedSize != outSizeProcessed) + { + errorMessage = "Can't write output file"; + res = SZ_ERROR_FAIL; + } + + g_TotalSize += (DWORD)outSizeProcessed; + + #ifdef USE_WINDOWS_FILE + if (SzBitWithVals_Check(&db.MTime, i)) + { + const CNtfsFileTime *t = db.MTime.Vals + i; + FILETIME mTime; + mTime.dwLowDateTime = t->Low; + mTime.dwHighDateTime = t->High; + SetFileTime(outFile.handle, NULL, NULL, &mTime); + } + #endif + + { + const WRes winRes2 = File_Close(&outFile); + if (res != SZ_OK) + break; + if (winRes2 != 0) + { + winRes = winRes2; + break; + } + } + + #ifdef USE_WINDOWS_FILE + if (SzBitWithVals_Check(&db.Attribs, i)) + SetFileAttributesW(path, db.Attribs.Vals[i]); + #endif + } + + if (tempIndex != 0) + { + // is it supported at win2000 ? + #ifndef UNDER_CE + if (!MoveFileExW(path, origPath, MOVEFILE_DELAY_UNTIL_REBOOT | MOVEFILE_REPLACE_EXISTING)) + { + winRes = GetLastError(); + break; + } + needRebootLevel |= fileLevel; + #endif + } + + } + } + + ISzAlloc_Free(&allocImp, outBuf); + + if (!g_SilentMode) + SendMessage(g_Progress_HWND, PBM_SETPOS, i, 0); + + path[pathLen] = 0; + + if (i == db.NumFiles) + { + SetRegKey_Path(); + WriteCLSID(); + WriteShellEx(); + + SetShellProgramsGroup(g_HWND); + if (!g_SilentMode) + SetWindowTextW(g_InfoLine_HWND, k_7zip_with_Ver L" is installed"); + } + } + + SzArEx_Free(&db, &allocImp); + + ISzAlloc_Free(&allocImp, lookStream.buf); + + File_Close(&archiveStream.file); + +} + + if (winRes != 0) + res = SZ_ERROR_FAIL; + + if (res == SZ_OK) + { + if (!g_SilentMode && needRebootLevel > 1) + { + if (MessageBoxW(g_HWND, L"You must restart your system to complete the installation.\nRestart now?", + k_7zip_Setup, MB_YESNO | MB_DEFBUTTON2) == IDYES) + { + #ifndef UNDER_CE + + // Get a token for this process. + HANDLE hToken; + + if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) + { + TOKEN_PRIVILEGES tkp; + // Get the LUID for the shutdown privilege. + LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME, &tkp.Privileges[0].Luid); + tkp.PrivilegeCount = 1; // one privilege to set + tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + // Get the shutdown privilege for this process. + AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, (PTOKEN_PRIVILEGES)NULL, 0); + + if (GetLastError() == ERROR_SUCCESS) + { + if (!ExitWindowsEx(EWX_REBOOT, 0)) + { + } + } + } + + #endif + } + } + + if (res == SZ_OK) + return 0; + } + + if (!g_SilentMode) + { + if (winRes != 0) + { + WCHAR m[MAX_PATH + 100]; + m[0] = 0; + GetErrorMessage(winRes, m); + PrintErrorMessage(NULL, m); + } + else + { + if (res == SZ_ERROR_ABORT) + return 2; + + if (res == SZ_ERROR_UNSUPPORTED) + errorMessage = "Decoder doesn't support this archive"; + else if (res == SZ_ERROR_MEM) + errorMessage = "Can't allocate required memory"; + else if (res == SZ_ERROR_CRC) + errorMessage = "CRC error"; + else if (res == SZ_ERROR_DATA) + errorMessage = "Data error"; + + if (!errorMessage) + errorMessage = "ERROR"; + PrintErrorMessage(errorMessage, NULL); + } + } + + return 1; +} diff --git a/src/plugins/7zip/7za/c/Util/7zipInstall/7zipInstall.dsp b/src/plugins/7zip/7za/c/Util/7zipInstall/7zipInstall.dsp new file mode 100644 index 00000000..4c6ea4d8 --- /dev/null +++ b/src/plugins/7zip/7za/c/Util/7zipInstall/7zipInstall.dsp @@ -0,0 +1,248 @@ +# Microsoft Developer Studio Project File - Name="7zipInstall" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Application" 0x0101 + +CFG=7zipInstall - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "7zipInstall.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "7zipInstall.mak" CFG="7zipInstall - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "7zipInstall - Win32 Release" (based on "Win32 (x86) Application") +!MESSAGE "7zipInstall - Win32 Debug" (based on "Win32 (x86) Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "7zipInstall - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /Gr /MD /W4 /WX /GX /O1 /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "_UNICODE2" /D "UNICODE2" /Yu"Precomp.h" /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "NDEBUG" +# ADD RSC /l 0x419 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib version.lib /nologo /subsystem:windows /machine:I386 + +!ELSEIF "$(CFG)" == "7zipInstall - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /W4 /WX /Gm /GX /ZI /Od /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "_UNICODE2" /D "UNICODE2" /Yu"Precomp.h" /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "_DEBUG" +# ADD RSC /l 0x419 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib version.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "7zipInstall - Win32 Release" +# Name "7zipInstall - Win32 Debug" +# Begin Group "Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\7z.h +# End Source File +# Begin Source File + +SOURCE=..\..\7zAlloc.c +# End Source File +# Begin Source File + +SOURCE=..\..\7zAlloc.h +# End Source File +# Begin Source File + +SOURCE=..\..\7zArcIn.c +# End Source File +# Begin Source File + +SOURCE=..\..\7zBuf.c +# End Source File +# Begin Source File + +SOURCE=..\..\7zBuf.h +# End Source File +# Begin Source File + +SOURCE=..\..\7zCrc.c +# End Source File +# Begin Source File + +SOURCE=..\..\7zCrc.h +# End Source File +# Begin Source File + +SOURCE=..\..\7zCrcOpt.c +# End Source File +# Begin Source File + +SOURCE=..\..\7zDec.c +# End Source File +# Begin Source File + +SOURCE=..\..\7zFile.c +# End Source File +# Begin Source File + +SOURCE=..\..\7zFile.h +# End Source File +# Begin Source File + +SOURCE=..\..\7zStream.c +# End Source File +# Begin Source File + +SOURCE=..\..\7zTypes.h +# End Source File +# Begin Source File + +SOURCE=..\..\7zVersion.h +# End Source File +# Begin Source File + +SOURCE=..\..\7zWindows.h +# End Source File +# Begin Source File + +SOURCE=..\..\Bcj2.c +# End Source File +# Begin Source File + +SOURCE=..\..\Bcj2.h +# End Source File +# Begin Source File + +SOURCE=..\..\Bra.c +# End Source File +# Begin Source File + +SOURCE=..\..\Bra.h +# End Source File +# Begin Source File + +SOURCE=..\..\Bra86.c +# End Source File +# Begin Source File + +SOURCE=..\..\BraIA64.c +# End Source File +# Begin Source File + +SOURCE=..\..\CpuArch.c +# End Source File +# Begin Source File + +SOURCE=..\..\CpuArch.h +# End Source File +# Begin Source File + +SOURCE=..\..\Delta.c +# End Source File +# Begin Source File + +SOURCE=..\..\Delta.h +# End Source File +# Begin Source File + +SOURCE=..\..\DllSecur.c +# End Source File +# Begin Source File + +SOURCE=..\..\DllSecur.h +# End Source File +# Begin Source File + +SOURCE=..\..\Lzma2Dec.c +# End Source File +# Begin Source File + +SOURCE=..\..\Lzma2Dec.h +# End Source File +# Begin Source File + +SOURCE=..\..\LzmaDec.c +# End Source File +# Begin Source File + +SOURCE=..\..\LzmaDec.h +# End Source File +# End Group +# Begin Group "Spec" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Compiler.h +# End Source File +# Begin Source File + +SOURCE=.\Precomp.c +# ADD CPP /Yc"Precomp.h" +# End Source File +# Begin Source File + +SOURCE=.\Precomp.h +# End Source File +# End Group +# Begin Source File + +SOURCE=.\7zipInstall.c +# End Source File +# Begin Source File + +SOURCE=.\resource.rc +# End Source File +# End Target +# End Project diff --git a/src/plugins/7zip/7za/c/Util/7zipInstall/7zipInstall.dsw b/src/plugins/7zip/7za/c/Util/7zipInstall/7zipInstall.dsw new file mode 100644 index 00000000..b7db73f4 --- /dev/null +++ b/src/plugins/7zip/7za/c/Util/7zipInstall/7zipInstall.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "7zipInstall"=.\7zipInstall.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/src/plugins/7zip/7za/c/Util/7zipInstall/7zipInstall.manifest b/src/plugins/7zip/7za/c/Util/7zipInstall/7zipInstall.manifest new file mode 100644 index 00000000..f5c3ae5e --- /dev/null +++ b/src/plugins/7zip/7za/c/Util/7zipInstall/7zipInstall.manifest @@ -0,0 +1,18 @@ + + + +7-Zip Installer + + + + + + + + + + + + +true + diff --git a/src/plugins/7zip/7za/c/Util/7zipInstall/Precomp.c b/src/plugins/7zip/7za/c/Util/7zipInstall/Precomp.c new file mode 100644 index 00000000..01605e3c --- /dev/null +++ b/src/plugins/7zip/7za/c/Util/7zipInstall/Precomp.c @@ -0,0 +1,4 @@ +/* Precomp.c -- StdAfx +2013-01-21 : Igor Pavlov : Public domain */ + +#include "Precomp.h" diff --git a/src/plugins/7zip/7za/c/Util/7zipInstall/Precomp.h b/src/plugins/7zip/7za/c/Util/7zipInstall/Precomp.h new file mode 100644 index 00000000..13a41eff --- /dev/null +++ b/src/plugins/7zip/7za/c/Util/7zipInstall/Precomp.h @@ -0,0 +1,13 @@ +/* Precomp.h -- Precomp +2024-01-23 : Igor Pavlov : Public domain */ + +// #ifndef ZIP7_INC_PRECOMP_LOC_H +// #define ZIP7_INC_PRECOMP_LOC_H + +#if defined(_MSC_VER) && _MSC_VER >= 1800 +#pragma warning(disable : 4464) // relative include path contains '..' +#endif + +#include "../../Precomp.h" + +// #endif diff --git a/src/plugins/7zip/7za/c/Util/7zipInstall/makefile b/src/plugins/7zip/7za/c/Util/7zipInstall/makefile new file mode 100644 index 00000000..424bd6cc --- /dev/null +++ b/src/plugins/7zip/7za/c/Util/7zipInstall/makefile @@ -0,0 +1,47 @@ +PROG = 7zipInstall.exe +MY_FIXED = 1 + +!IFDEF Z7_64BIT_INSTALLER +CFLAGS = $(CFLAGS) -DZ7_64BIT_INSTALLER +!ENDIF + +CFLAGS = $(CFLAGS) \ + -DZ7_LZMA_SIZE_OPT \ + -DZ7_NO_METHOD_LZMA2 \ + -DZ7_NO_METHODS_FILTERS \ + -DZ7_USE_NATIVE_BRANCH_FILTER \ + -DZ7_EXTRACT_ONLY \ + +MAIN_OBJS = \ + $O\7zipInstall.obj \ + +C_OBJS = \ + $O\7zAlloc.obj \ + $O\7zArcIn.obj \ + $O\7zBuf.obj \ + $O\7zFile.obj \ + $O\7zDec.obj \ + $O\7zStream.obj \ + $O\Bcj2.obj \ + $O\Bra.obj \ + $O\CpuArch.obj \ + $O\DllSecur.obj \ + $O\LzmaDec.obj \ + +OBJS = \ + $(MAIN_OBJS) \ + $(C_OBJS) \ + $(ASM_OBJS) \ + $O\resource.res + +!include "../../../CPP/7zip/Crc.mak" +# !include "../../../CPP/7zip/LzmaDec.mak" + +!include "../../../CPP/Build.mak" + +$(MAIN_OBJS): $(*B).c + $(COMPL_O1) +$(C_OBJS): ../../$(*B).c + $(COMPL_O1) + +!include "../../Asm_c.mak" diff --git a/src/plugins/7zip/7za/c/Util/7zipInstall/resource.h b/src/plugins/7zip/7za/c/Util/7zipInstall/resource.h new file mode 100644 index 00000000..63c6b4c2 --- /dev/null +++ b/src/plugins/7zip/7za/c/Util/7zipInstall/resource.h @@ -0,0 +1,9 @@ +#define IDD_INSTALL 100 + +#define IDT_EXTRACT_EXTRACT_TO 110 +#define IDE_EXTRACT_PATH 111 +#define IDB_EXTRACT_SET_PATH 112 +#define IDT_CUR_FILE 113 +#define IDC_PROGRESS 114 + +#define IDI_ICON 1 diff --git a/src/plugins/7zip/7za/c/Util/7zipInstall/resource.rc b/src/plugins/7zip/7za/c/Util/7zipInstall/resource.rc new file mode 100644 index 00000000..40ed5807 --- /dev/null +++ b/src/plugins/7zip/7za/c/Util/7zipInstall/resource.rc @@ -0,0 +1,48 @@ +#include +// #include +// #include +#include + +#define USE_COPYRIGHT_CR +#include "../../7zVersion.rc" +#include "resource.h" + +MY_VERSION_INFO(MY_VFT_APP, "7-Zip Installer", "7zipInstall", "7zipInstall.exe") + +1 ICON "7zip.ico" + +#define xc 184 +#define yc 96 + +#define m 8 +#define bxs 64 +#define bys 16 +#define bxsDots 20 + +#define xs (xc + m + m) +#define ys (yc + m + m) + +#define bx1 (xs - m - bxs) +#define bx2 (bx1 - m - bxs) + +#define by (ys - m - bys) + +IDD_INSTALL DIALOG 0, 0, xs, ys +STYLE DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_VISIBLE +CAPTION "Install 7-Zip" +FONT 8, "MS Shell Dlg" +BEGIN + LTEXT "Destination folder:", IDT_EXTRACT_EXTRACT_TO, m, m, xc, 8 + EDITTEXT IDE_EXTRACT_PATH, m, 21, xc - bxsDots - 12, 14, ES_AUTOHSCROLL + PUSHBUTTON "...", IDB_EXTRACT_SET_PATH, xs - m - bxsDots, 20, bxsDots, bys, WS_GROUP + + LTEXT "", IDT_CUR_FILE, m, 50, xc, 8 + CONTROL "", IDC_PROGRESS, "msctls_progress32", WS_BORDER, m, 64, xc, 10 + + DEFPUSHBUTTON "&Install", IDOK, bx2, by, bxs, bys, WS_GROUP + PUSHBUTTON "Cancel", IDCANCEL, bx1, by, bxs, bys +END + +#ifndef UNDER_CE +1 24 MOVEABLE PURE "7zipInstall.manifest" +#endif diff --git a/src/plugins/7zip/7za/c/Util/7zipUninstall/7zipUninstall.c b/src/plugins/7zip/7za/c/Util/7zipUninstall/7zipUninstall.c new file mode 100644 index 00000000..2c1333ba --- /dev/null +++ b/src/plugins/7zip/7za/c/Util/7zipUninstall/7zipUninstall.c @@ -0,0 +1,1234 @@ +/* 7zipUninstall.c - 7-Zip Uninstaller +: Igor Pavlov : Public domain */ + +#include "Precomp.h" + +// #define SZ_ERROR_ABORT 100 + +#include "../../7zTypes.h" +#include "../../7zWindows.h" + +#if defined(_MSC_VER) && _MSC_VER < 1600 +#pragma warning(disable : 4201) // nonstandard extension used : nameless struct/union +#endif + +#ifdef Z7_OLD_WIN_SDK +struct IShellView; +#define SHFOLDERAPI EXTERN_C DECLSPEC_IMPORT HRESULT STDAPICALLTYPE +SHFOLDERAPI SHGetFolderPathW(HWND hwnd, int csidl, HANDLE hToken, DWORD dwFlags, LPWSTR pszPath); +#define BIF_NEWDIALOGSTYLE 0x0040 // Use the new dialog layout with the ability to resize +typedef enum { + SHGFP_TYPE_CURRENT = 0, // current value for user, verify it exists + SHGFP_TYPE_DEFAULT = 1, // default value, may not exist +} SHGFP_TYPE; +#endif +#if defined(__MINGW32__) || defined(__MINGW64__) +#include +#else +#include +#endif + +#include "../../7zVersion.h" + +#include "resource.h" + + + + +#define LLL_(quote) L##quote +#define LLL(quote) LLL_(quote) + +#define wcscat lstrcatW +#define wcslen (size_t)lstrlenW +#define wcscpy lstrcpyW + +// static LPCWSTR const k_7zip = L"7-Zip"; + +// #define Z7_64BIT_INSTALLER 1 + +#ifdef _WIN64 + #define Z7_64BIT_INSTALLER 1 +#endif + +#define k_7zip_with_Ver_base L"7-Zip " LLL(MY_VERSION) + +#ifdef Z7_64BIT_INSTALLER + + // #define USE_7ZIP_32_DLL + + #if defined(_M_ARM64) || defined(_M_ARM) + #define k_Postfix L" (arm64)" + #else + #define k_Postfix L" (x64)" + #define USE_7ZIP_32_DLL + #endif +#else + #if defined(_M_ARM64) || defined(_M_ARM) + #define k_Postfix L" (arm)" + #else + // #define k_Postfix L" (x86)" + #define k_Postfix + #endif +#endif + +#define k_7zip_with_Ver k_7zip_with_Ver_base k_Postfix + +static LPCWSTR const k_7zip_with_Ver_Uninstall = k_7zip_with_Ver L" Uninstall"; + +static LPCWSTR const k_Reg_Software_7zip = L"Software\\7-Zip"; + +static LPCWSTR const k_Reg_Path = L"Path"; + +static LPCWSTR const k_Reg_Path32 = L"Path" + #ifdef Z7_64BIT_INSTALLER + L"64" + #else + L"32" + #endif + ; + +#if defined(Z7_64BIT_INSTALLER) && !defined(_WIN64) + #define k_Reg_WOW_Flag KEY_WOW64_64KEY +#else + #define k_Reg_WOW_Flag 0 +#endif + +#ifdef USE_7ZIP_32_DLL +#ifdef _WIN64 + #define k_Reg_WOW_Flag_32 KEY_WOW64_32KEY +#else + #define k_Reg_WOW_Flag_32 0 +#endif +#endif + +#define k_7zip_CLSID L"{23170F69-40C1-278A-1000-000100020000}" + +static LPCWSTR const k_Reg_CLSID_7zip = L"CLSID\\" k_7zip_CLSID; +static LPCWSTR const k_Reg_CLSID_7zip_Inproc = L"CLSID\\" k_7zip_CLSID L"\\InprocServer32"; + + +#define g_AllUsers True + +static BoolInt g_Install_was_Pressed; +static BoolInt g_Finished; +static BoolInt g_SilentMode; + +static HWND g_HWND; +static HWND g_Path_HWND; +static HWND g_InfoLine_HWND; +static HWND g_Progress_HWND; + +// RegDeleteKeyExW is supported starting from win2003sp1/xp-pro-x64 +// Z7_WIN32_WINNT_MIN < 0x0600 // Vista +#if !defined(Z7_WIN32_WINNT_MIN) \ + || Z7_WIN32_WINNT_MIN < 0x0502 /* < win2003 */ \ + || Z7_WIN32_WINNT_MIN == 0x0502 && !defined(_M_AMD64) +#define Z7_USE_DYN_RegDeleteKeyExW +#endif + +#ifdef Z7_USE_DYN_RegDeleteKeyExW +Z7_DIAGNOSTIC_IGNORE_CAST_FUNCTION +typedef LONG (APIENTRY *Func_RegDeleteKeyExW)(HKEY hKey, LPCWSTR lpSubKey, REGSAM samDesired, DWORD Reserved); +static Func_RegDeleteKeyExW func_RegDeleteKeyExW; +#endif + +static WCHAR cmd[MAX_PATH + 4]; +static WCHAR cmdError[MAX_PATH + 4]; +static WCHAR path[MAX_PATH * 2 + 40]; +static WCHAR workDir[MAX_PATH + 10]; +static WCHAR modulePath[MAX_PATH + 10]; +static WCHAR modulePrefix[MAX_PATH + 10]; +static WCHAR tempPath[MAX_PATH * 2 + 40]; +static WCHAR cmdLine[MAX_PATH * 3 + 40]; +static WCHAR copyPath[MAX_PATH * 2 + 40]; + +static LPCWSTR const kUninstallExe = L"Uninstall.exe"; + +#define MAKE_CHAR_UPPER(c) ((((c) >= 'a' && (c) <= 'z') ? (c) - 0x20 : (c))) + + +static void CpyAscii(WCHAR *dest, const char *s) +{ + for (;;) + { + const Byte b = (Byte)*s++; + *dest++ = b; + if (b == 0) + return; + } +} + +static void CatAscii(WCHAR *dest, const char *s) +{ + dest += wcslen(dest); + CpyAscii(dest, s); +} + +static void PrintErrorMessage(const char *s1, const WCHAR *s2) +{ + WCHAR m[MAX_PATH + 512]; + m[0] = 0; + CatAscii(m, "ERROR:"); + if (s1) + { + CatAscii(m, "\n"); + CatAscii(m, s1); + } + if (s2) + { + CatAscii(m, "\n"); + wcscat(m, s2); + } + MessageBoxW(g_HWND, m, k_7zip_with_Ver_Uninstall, MB_ICONERROR | MB_OK); +} + + +static BoolInt AreStringsEqual_NoCase(const WCHAR *s1, const WCHAR *s2) +{ + for (;;) + { + WCHAR c1 = *s1++; + WCHAR c2 = *s2++; + if (c1 != c2 && MAKE_CHAR_UPPER(c1) != MAKE_CHAR_UPPER(c2)) + return False; + if (c2 == 0) + return True; + } +} + +static BoolInt IsString1PrefixedByString2_NoCase(const WCHAR *s1, const WCHAR *s2) +{ + for (;;) + { + WCHAR c1; + const WCHAR c2 = *s2++; + if (c2 == 0) + return True; + c1 = *s1++; + if (c1 != c2 && MAKE_CHAR_UPPER(c1) != MAKE_CHAR_UPPER(c2)) + return False; + } +} + +static void NormalizePrefix(WCHAR *s) +{ + const size_t len = wcslen(s); + if (len != 0) + if (s[len - 1] != WCHAR_PATH_SEPARATOR) + { + s[len] = WCHAR_PATH_SEPARATOR; + s[len + 1] = 0; + } +} + +static int MyRegistry_QueryString(HKEY hKey, LPCWSTR name, LPWSTR dest) +{ + DWORD cnt = MAX_PATH * sizeof(name[0]); + DWORD type = 0; + const LONG res = RegQueryValueExW(hKey, name, NULL, &type, (LPBYTE)dest, &cnt); + if (type != REG_SZ) + return False; + return res == ERROR_SUCCESS; +} + +static int MyRegistry_QueryString2(HKEY hKey, LPCWSTR keyName, LPCWSTR valName, LPWSTR dest) +{ + HKEY key = 0; + const LONG res = RegOpenKeyExW(hKey, keyName, 0, KEY_READ | k_Reg_WOW_Flag, &key); + if (res != ERROR_SUCCESS) + return False; + { + const BoolInt res2 = MyRegistry_QueryString(key, valName, dest); + RegCloseKey(key); + return res2; + } +} + +static LONG MyRegistry_OpenKey_ReadWrite(HKEY parentKey, LPCWSTR name, HKEY *destKey) +{ + return RegOpenKeyExW(parentKey, name, 0, KEY_READ | KEY_WRITE | k_Reg_WOW_Flag, destKey); +} + +static LONG MyRegistry_DeleteKey(HKEY parentKey, LPCWSTR name) +{ +#if k_Reg_WOW_Flag != 0 +#ifdef Z7_USE_DYN_RegDeleteKeyExW + if (!func_RegDeleteKeyExW) + return E_FAIL; + return func_RegDeleteKeyExW +#else + return RegDeleteKeyExW +#endif + (parentKey, name, k_Reg_WOW_Flag, 0); +#else + return RegDeleteKeyW(parentKey, name); +#endif +} + +#ifdef USE_7ZIP_32_DLL + +static int MyRegistry_QueryString2_32(HKEY hKey, LPCWSTR keyName, LPCWSTR valName, LPWSTR dest) +{ + HKEY key = 0; + const LONG res = RegOpenKeyExW(hKey, keyName, 0, KEY_READ | k_Reg_WOW_Flag_32, &key); + if (res != ERROR_SUCCESS) + return False; + { + const BoolInt res2 = MyRegistry_QueryString(key, valName, dest); + RegCloseKey(key); + return res2; + } +} + +static LONG MyRegistry_OpenKey_ReadWrite_32(HKEY parentKey, LPCWSTR name, HKEY *destKey) +{ + return RegOpenKeyExW(parentKey, name, 0, KEY_READ | KEY_WRITE | k_Reg_WOW_Flag_32, destKey); +} + +static LONG MyRegistry_DeleteKey_32(HKEY parentKey, LPCWSTR name) +{ +#if k_Reg_WOW_Flag_32 != 0 +#ifdef Z7_USE_DYN_RegDeleteKeyExW + if (!func_RegDeleteKeyExW) + return E_FAIL; + return func_RegDeleteKeyExW +#else + return RegDeleteKeyExW +#endif + (parentKey, name, k_Reg_WOW_Flag_32, 0); +#else + return RegDeleteKeyW(parentKey, name); +#endif +} + +#endif + + + + +static void MyReg_DeleteVal_Path_if_Equal(HKEY hKey, LPCWSTR name) +{ + WCHAR s[MAX_PATH + 10]; + if (MyRegistry_QueryString(hKey, name, s)) + { + NormalizePrefix(s); + if (AreStringsEqual_NoCase(s, path)) + RegDeleteValueW(hKey, name); + } +} + +static void SetRegKey_Path2(HKEY parentKey) +{ + HKEY key = 0; + const LONG res = MyRegistry_OpenKey_ReadWrite(parentKey, k_Reg_Software_7zip, &key); + if (res == ERROR_SUCCESS) + { + MyReg_DeleteVal_Path_if_Equal(key, k_Reg_Path32); + MyReg_DeleteVal_Path_if_Equal(key, k_Reg_Path); + + RegCloseKey(key); + // MyRegistry_DeleteKey(parentKey, k_Reg_Software_7zip); + } +} + +static void SetRegKey_Path(void) +{ + SetRegKey_Path2(HKEY_CURRENT_USER); + SetRegKey_Path2(HKEY_LOCAL_MACHINE); +} + +static HRESULT CreateShellLink(LPCWSTR srcPath, LPCWSTR targetPath) +{ + IShellLinkW *sl; + + // CoInitialize has already been called. + HRESULT hres = CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, &IID_IShellLinkW, (LPVOID*)&sl); + + if (SUCCEEDED(hres)) + { + IPersistFile *pf; + + hres = sl->lpVtbl->QueryInterface(sl, &IID_IPersistFile, (LPVOID *)&pf); + + if (SUCCEEDED(hres)) + { + WCHAR s[MAX_PATH + 10]; + hres = pf->lpVtbl->Load(pf, srcPath, TRUE); + pf->lpVtbl->Release(pf); + + if (SUCCEEDED(hres)) + { + hres = sl->lpVtbl->GetPath(sl, s, MAX_PATH, NULL, 0); // SLGP_RAWPATH + if (!AreStringsEqual_NoCase(s, targetPath)) + hres = S_FALSE; + } + } + + sl->lpVtbl->Release(sl); + } + + return hres; +} + +static void SetShellProgramsGroup(HWND hwndOwner) +{ + #ifdef UNDER_CE + + UNUSED_VAR(hwndOwner) + + #else + + unsigned i = (g_AllUsers ? 1 : 2); + + for (; i < 3; i++) + { + // BoolInt isOK = True; + WCHAR link[MAX_PATH + 40]; + WCHAR destPath[MAX_PATH + 40]; + + link[0] = 0; + + if (SHGetFolderPathW(hwndOwner, + i == 1 ? CSIDL_COMMON_PROGRAMS : CSIDL_PROGRAMS, + NULL, SHGFP_TYPE_CURRENT, link) != S_OK) + continue; + + NormalizePrefix(link); + CatAscii(link, "7-Zip\\"); + + { + const size_t baseLen = wcslen(link); + unsigned k; + BoolInt needDelete = False; + + for (k = 0; k < 2; k++) + { + CpyAscii(link + baseLen, k == 0 ? + "7-Zip File Manager.lnk" : + "7-Zip Help.lnk"); + wcscpy(destPath, path); + CatAscii(destPath, k == 0 ? + "7zFM.exe" : + "7-zip.chm"); + + if (CreateShellLink(link, destPath) == S_OK) + { + needDelete = True; + DeleteFileW(link); + } + } + + if (needDelete) + { + link[baseLen] = 0; + RemoveDirectoryW(link); + } + } + } + + #endif +} + + +static LPCSTR const k_ShellEx_Items[] = +{ + "*\\shellex\\ContextMenuHandlers" + , "Directory\\shellex\\ContextMenuHandlers" + , "Folder\\shellex\\ContextMenuHandlers" + , "Directory\\shellex\\DragDropHandlers" + , "Drive\\shellex\\DragDropHandlers" +}; + +static LPCWSTR const k_Shell_Approved = L"Software\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved"; + +static LPCWSTR const k_AppPaths_7zFm = L"Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\7zFM.exe"; +#define k_REG_Uninstall L"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\" +static LPCWSTR const k_Uninstall_7zip = k_REG_Uninstall L"7-Zip"; + + +static void RemoveQuotes(WCHAR *s) +{ + const size_t len = wcslen(s); + size_t i; + if (len == 0 || s[0] != '\"' || s[len - 1] != '\"') + return; + for (i = 0; i < len; i++) + s[i] = s[i + 1]; + s[len - 2] = 0; +} + +static BoolInt AreEqual_Path_PrefixName(const WCHAR *s, const WCHAR *prefix, const WCHAR *name) +{ + if (!IsString1PrefixedByString2_NoCase(s, prefix)) + return False; + return AreStringsEqual_NoCase(s + wcslen(prefix), name); +} + +static void WriteCLSID(void) +{ + WCHAR s[MAX_PATH + 30]; + + if (MyRegistry_QueryString2(HKEY_CLASSES_ROOT, k_Reg_CLSID_7zip_Inproc, NULL, s)) + { + if (AreEqual_Path_PrefixName(s, path, L"7-zip.dll")) + { + { + const LONG res = MyRegistry_DeleteKey(HKEY_CLASSES_ROOT, k_Reg_CLSID_7zip_Inproc); + if (res == ERROR_SUCCESS) + MyRegistry_DeleteKey(HKEY_CLASSES_ROOT, k_Reg_CLSID_7zip); + } + + { + unsigned i; + for (i = 0; i < Z7_ARRAY_SIZE(k_ShellEx_Items); i++) + { + WCHAR destPath[MAX_PATH]; + CpyAscii(destPath, k_ShellEx_Items[i]); + CatAscii(destPath, "\\7-Zip"); + + MyRegistry_DeleteKey(HKEY_CLASSES_ROOT, destPath); + } + } + + { + HKEY destKey = 0; + const LONG res = MyRegistry_OpenKey_ReadWrite(HKEY_LOCAL_MACHINE, k_Shell_Approved, &destKey); + if (res == ERROR_SUCCESS) + { + RegDeleteValueW(destKey, k_7zip_CLSID); + /* res = */ RegCloseKey(destKey); + } + } + } + } + + + #ifdef USE_7ZIP_32_DLL + + if (MyRegistry_QueryString2_32(HKEY_CLASSES_ROOT, k_Reg_CLSID_7zip_Inproc, NULL, s)) + { + if (AreEqual_Path_PrefixName(s, path, L"7-zip32.dll")) + { + { + const LONG res = MyRegistry_DeleteKey_32(HKEY_CLASSES_ROOT, k_Reg_CLSID_7zip_Inproc); + if (res == ERROR_SUCCESS) + MyRegistry_DeleteKey_32(HKEY_CLASSES_ROOT, k_Reg_CLSID_7zip); + } + + { + unsigned i; + for (i = 0; i < Z7_ARRAY_SIZE(k_ShellEx_Items); i++) + { + WCHAR destPath[MAX_PATH]; + CpyAscii(destPath, k_ShellEx_Items[i]); + CatAscii(destPath, "\\7-Zip"); + + MyRegistry_DeleteKey_32(HKEY_CLASSES_ROOT, destPath); + } + } + + { + HKEY destKey = 0; + const LONG res = MyRegistry_OpenKey_ReadWrite_32(HKEY_LOCAL_MACHINE, k_Shell_Approved, &destKey); + if (res == ERROR_SUCCESS) + { + RegDeleteValueW(destKey, k_7zip_CLSID); + /* res = */ RegCloseKey(destKey); + } + } + } + } + + #endif + + + if (MyRegistry_QueryString2(HKEY_LOCAL_MACHINE, k_AppPaths_7zFm, NULL, s)) + { + // RemoveQuotes(s); + if (AreEqual_Path_PrefixName(s, path, L"7zFM.exe")) + MyRegistry_DeleteKey(HKEY_LOCAL_MACHINE, k_AppPaths_7zFm); + } + + if (MyRegistry_QueryString2(HKEY_LOCAL_MACHINE, k_Uninstall_7zip, L"UninstallString", s)) + { + RemoveQuotes(s); + if (AreEqual_Path_PrefixName(s, path, kUninstallExe)) + MyRegistry_DeleteKey(HKEY_LOCAL_MACHINE, k_Uninstall_7zip); + } +} + + +static const WCHAR *GetCmdParam(const WCHAR *s) +{ + unsigned pos = 0; + BoolInt quoteMode = False; + for (;; s++) + { + const WCHAR c = *s; + if (c == 0 || (c == L' ' && !quoteMode)) + break; + if (c == L'\"') + { + quoteMode = !quoteMode; + continue; + } + if (pos >= Z7_ARRAY_SIZE(cmd) - 1) + exit(1); + cmd[pos++] = c; + } + cmd[pos] = 0; + return s; +} + +/* +static void RemoveQuotes(WCHAR *s) +{ + const WCHAR *src = s; + for (;;) + { + WCHAR c = *src++; + if (c == '\"') + continue; + *s++ = c; + if (c == 0) + return; + } +} +*/ + +static BoolInt DoesFileOrDirExist(void) +{ + return (GetFileAttributesW(path) != INVALID_FILE_ATTRIBUTES); +} + +static BOOL RemoveFileAfterReboot2(const WCHAR *s) +{ + #ifndef UNDER_CE + return MoveFileExW(s, NULL, MOVEFILE_DELAY_UNTIL_REBOOT); + #else + UNUSED_VAR(s) + return TRUE; + #endif +} + +static BOOL RemoveFileAfterReboot(void) +{ + return RemoveFileAfterReboot2(path); +} + +// #define IS_LIMIT_CHAR(c) (c == 0 || c == ' ') + +static BoolInt IsThereSpace(const WCHAR *s) +{ + for (;;) + { + const WCHAR c = *s++; + if (c == 0) + return False; + if (c == ' ') + return True; + } +} + +static void AddPathParam(WCHAR *dest, const WCHAR *src) +{ + const BoolInt needQuote = IsThereSpace(src); + if (needQuote) + CatAscii(dest, "\""); + wcscat(dest, src); + if (needQuote) + CatAscii(dest, "\""); +} + + + +static BoolInt GetErrorMessage(DWORD errorCode, WCHAR *message) +{ + LPWSTR msgBuf; + if (FormatMessageW( + FORMAT_MESSAGE_ALLOCATE_BUFFER + | FORMAT_MESSAGE_FROM_SYSTEM + | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, errorCode, 0, (LPWSTR) &msgBuf, 0, NULL) == 0) + return False; + wcscpy(message, msgBuf); + LocalFree(msgBuf); + return True; +} + +static BOOL RemoveDir(void) +{ + const DWORD attrib = GetFileAttributesW(path); + if (attrib == INVALID_FILE_ATTRIBUTES) + return TRUE; + if (RemoveDirectoryW(path)) + return TRUE; + return RemoveFileAfterReboot(); +} + + + + + +#define k_Lang "Lang" + +// NUM_LANG_TXT_FILES files are placed before en.ttt +#define NUM_LANG_TXT_FILES 92 + +#ifdef USE_7ZIP_32_DLL + #define NUM_EXTRA_FILES_64BIT 1 +#else + #define NUM_EXTRA_FILES_64BIT 0 +#endif + +#define NUM_FILES (NUM_LANG_TXT_FILES + 1 + 13 + NUM_EXTRA_FILES_64BIT) + +static const char * const k_Names = + "af an ar ast az ba be bg bn br ca co cs cy da de el eo es et eu ext" + " fa fi fr fur fy ga gl gu he hi hr hu hy id io is it ja ka kaa kab kk ko ku ku-ckb ky" + " lij lt lv mk mn mng mng2 mr ms nb ne nl nn pa-in pl ps pt pt-br ro ru" + " sa si sk sl sq sr-spc sr-spl sv sw ta tg th tk tr tt ug uk uz uz-cyrl va vi yo zh-cn zh-tw" + " en.ttt" + " descript.ion" + " History.txt" + " License.txt" + " readme.txt" + " 7-zip.chm" + " 7z.sfx" + " 7zCon.sfx" + " 7z.exe" + " 7zG.exe" + " 7z.dll" + " 7zFM.exe" + #ifdef USE_7ZIP_32_DLL + " 7-zip32.dll" + #endif + " 7-zip.dll" + " Uninstall.exe"; + + + +static int Install(void) +{ + SRes res = SZ_OK; + WRes winRes = 0; + + // BoolInt needReboot = False; + const size_t pathLen = wcslen(path); + + if (!g_SilentMode) + { + ShowWindow(g_Progress_HWND, SW_SHOW); + ShowWindow(g_InfoLine_HWND, SW_SHOW); + SendMessage(g_Progress_HWND, PBM_SETRANGE32, 0, NUM_FILES); + } + + { + unsigned i; + const char *curName = k_Names; + + for (i = 0; *curName != 0; i++) + { + WCHAR *temp; + + if (!g_SilentMode) + { + MSG msg; + + // g_HWND + while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) + { + if (!IsDialogMessage(g_HWND, &msg)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + if (!g_HWND) + return 1; + } + + // Sleep(1); + SendMessage(g_Progress_HWND, PBM_SETPOS, i, 0); + } + + path[pathLen] = 0; + temp = path + pathLen; + + if (i <= NUM_LANG_TXT_FILES) + CpyAscii(temp, k_Lang "\\"); + + { + WCHAR *dest = temp + wcslen(temp); + + for (;;) + { + const char c = *curName; + if (c == 0) + break; + curName++; + if (c == ' ') + break; + *dest++ = (Byte)c; + } + + *dest = 0; + } + + if (i < NUM_LANG_TXT_FILES) + CatAscii(temp, ".txt"); + + if (!g_SilentMode) + SetWindowTextW(g_InfoLine_HWND, temp); + + { + const DWORD attrib = GetFileAttributesW(path); + if (attrib == INVALID_FILE_ATTRIBUTES) + continue; + if (attrib & FILE_ATTRIBUTE_READONLY) + SetFileAttributesW(path, 0); + if (!DeleteFileW(path)) + { + if (!RemoveFileAfterReboot()) + { + winRes = GetLastError(); + } + /* + else + needReboot = True; + */ + } + } + } + + CpyAscii(path + pathLen, k_Lang); + RemoveDir(); + + path[pathLen] = 0; + RemoveDir(); + + if (!g_SilentMode) + SendMessage(g_Progress_HWND, PBM_SETPOS, i, 0); + + if (*curName == 0) + { + SetRegKey_Path(); + WriteCLSID(); + SetShellProgramsGroup(g_HWND); + if (!g_SilentMode) + SetWindowTextW(g_InfoLine_HWND, k_7zip_with_Ver L" is uninstalled"); + } + } + + if (winRes != 0) + res = SZ_ERROR_FAIL; + + if (res == SZ_OK) + { + // if (!g_SilentMode && needReboot); + return 0; + } + + if (!g_SilentMode) + { + WCHAR m[MAX_PATH + 100]; + m[0] = 0; + if (winRes == 0 || !GetErrorMessage(winRes, m)) + CpyAscii(m, "ERROR"); + PrintErrorMessage("System ERROR:", m); + } + + return 1; +} + + +static void OnClose(void) +{ + if (g_Install_was_Pressed && !g_Finished) + { + if (MessageBoxW(g_HWND, + L"Do you want to cancel uninstallation?", + k_7zip_with_Ver_Uninstall, + MB_ICONQUESTION | MB_YESNO | MB_DEFBUTTON2) != IDYES) + return; + } + DestroyWindow(g_HWND); + g_HWND = NULL; +} + +static +#ifdef Z7_OLD_WIN_SDK + BOOL +#else + INT_PTR +#endif +CALLBACK MyDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + UNUSED_VAR(lParam) + + switch (message) + { + case WM_INITDIALOG: + g_Path_HWND = GetDlgItem(hwnd, IDE_EXTRACT_PATH); + g_InfoLine_HWND = GetDlgItem(hwnd, IDT_CUR_FILE); + g_Progress_HWND = GetDlgItem(hwnd, IDC_PROGRESS); + + SetWindowTextW(hwnd, k_7zip_with_Ver_Uninstall); + SetDlgItemTextW(hwnd, IDE_EXTRACT_PATH, path); + + ShowWindow(g_Progress_HWND, SW_HIDE); + ShowWindow(g_InfoLine_HWND, SW_HIDE); + + break; + + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDOK: + { + if (g_Finished) + { + OnClose(); + break; + } + if (!g_Install_was_Pressed) + { + SendMessage(hwnd, WM_NEXTDLGCTL, (WPARAM)(void *)GetDlgItem(hwnd, IDCANCEL), TRUE); + + EnableWindow(g_Path_HWND, FALSE); + EnableWindow(GetDlgItem(hwnd, IDOK), FALSE); + + g_Install_was_Pressed = True; + return TRUE; + } + break; + } + + case IDCANCEL: + { + OnClose(); + break; + } + + default: return FALSE; + } + break; + + case WM_CLOSE: + OnClose(); + break; + /* + case WM_DESTROY: + PostQuitMessage(0); + return TRUE; + */ + default: + return FALSE; + } + + return TRUE; +} + + +int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, + #ifdef UNDER_CE + LPWSTR + #else + LPSTR + #endif + lpCmdLine, int nCmdShow) +{ + const WCHAR *cmdParams; + BoolInt useTemp = True; + + UNUSED_VAR(hPrevInstance) + UNUSED_VAR(lpCmdLine) + UNUSED_VAR(nCmdShow) + +#ifndef UNDER_CE + CoInitialize(NULL); +#endif + +#ifndef UNDER_CE +#ifdef Z7_USE_DYN_RegDeleteKeyExW + func_RegDeleteKeyExW = + (Func_RegDeleteKeyExW) Z7_CAST_FUNC_C GetProcAddress(GetModuleHandleW(L"advapi32.dll"), + "RegDeleteKeyExW"); +#endif +#endif + + { + const WCHAR *s = GetCommandLineW(); + + #ifndef UNDER_CE + s = GetCmdParam(s); + #endif + + cmdParams = s; + + for (;;) + { + { + WCHAR c = *s; + if (c == 0) + break; + if (c == ' ') + { + s++; + continue; + } + } + + { + const WCHAR *s2 = GetCmdParam(s); + BoolInt error = True; + if (cmd[0] == '/') + { + if (cmd[1] == 'S') + { + if (cmd[2] == 0) + { + g_SilentMode = True; + error = False; + } + } + else if (cmd[1] == 'N') + { + if (cmd[2] == 0) + { + useTemp = False; + error = False; + } + } + else if (cmd[1] == 'D' && cmd[2] == '=') + { + wcscpy(workDir, cmd + 3); + // RemoveQuotes(workDir); + useTemp = False; + error = False; + } + } + s = s2; + if (error && cmdError[0] == 0) + wcscpy(cmdError, cmd); + } + } + + if (cmdError[0] != 0) + { + if (!g_SilentMode) + PrintErrorMessage("Unsupported command:", cmdError); + return 1; + } + } + + { + WCHAR *name; + const DWORD len = GetModuleFileNameW(NULL, modulePath, MAX_PATH); + if (len == 0 || len > MAX_PATH) + return 1; + + name = NULL; + wcscpy(modulePrefix, modulePath); + + { + WCHAR *s = modulePrefix; + for (;;) + { + const WCHAR c = *s++; + if (c == 0) + break; + if (c == WCHAR_PATH_SEPARATOR) + name = s; + } + } + + if (!name) + return 1; + + if (!AreStringsEqual_NoCase(name, kUninstallExe)) + useTemp = False; + + *name = 0; // keep only prefix for modulePrefix + } + + + if (useTemp) + { + DWORD winRes = GetTempPathW(MAX_PATH, path); + + // GetTempPath: the returned string ends with a backslash + /* + { + WCHAR s[MAX_PATH + 1]; + wcscpy(s, path); + GetLongPathNameW(s, path, MAX_PATH); + } + */ + + if (winRes != 0 && winRes <= MAX_PATH + 1 + && !IsString1PrefixedByString2_NoCase(modulePrefix, path)) + { + unsigned i; + DWORD d; + + const size_t pathLen = wcslen(path); + d = (GetTickCount() << 12) ^ (GetCurrentThreadId() << 14) ^ GetCurrentProcessId(); + + for (i = 0; i < 100; i++, d += GetTickCount()) + { + CpyAscii(path + pathLen, "7z"); + + { + WCHAR *s = path + wcslen(path); + UInt32 value = d; + unsigned k; + for (k = 0; k < 8; k++) + { + const unsigned t = value & 0xF; + value >>= 4; + s[7 - k] = (WCHAR)((t < 10) ? ('0' + t) : ('A' + (t - 10))); + } + s[k] = 0; + } + + if (DoesFileOrDirExist()) + continue; + if (CreateDirectoryW(path, NULL)) + { + CatAscii(path, STRING_PATH_SEPARATOR); + wcscpy(tempPath, path); + break; + } + if (GetLastError() != ERROR_ALREADY_EXISTS) + break; + } + + if (tempPath[0] != 0) + { + wcscpy(copyPath, tempPath); + CatAscii(copyPath, "Uninst.exe"); // we need not "Uninstall.exe" here + + if (CopyFileW(modulePath, copyPath, TRUE)) + { + RemoveFileAfterReboot2(copyPath); + RemoveFileAfterReboot2(tempPath); + + { + STARTUPINFOW si; + PROCESS_INFORMATION pi; + cmdLine[0] = 0; + + // maybe CreateProcess supports path with spaces even without quotes. + AddPathParam(cmdLine, copyPath); + CatAscii(cmdLine, " /N /D="); + AddPathParam(cmdLine, modulePrefix); + + if (cmdParams[0] != 0 && wcslen(cmdParams) < MAX_PATH * 2 + 10) + wcscat(cmdLine, cmdParams); + + memset(&si, 0, sizeof(si)); + si.cb = sizeof(si); + + if (CreateProcessW(NULL, cmdLine, NULL, NULL, FALSE, 0, NULL, tempPath, &si, &pi)) + { + CloseHandle(pi.hThread); + if (pi.hProcess) + { + CloseHandle(pi.hProcess); + return 0; + } + } + } + } + } + } + } + + wcscpy(path, modulePrefix); + + if (workDir[0] != 0) + { + wcscpy(path, workDir); + NormalizePrefix(path); + } + + /* + if (path[0] == 0) + { + HKEY key = 0; + BoolInt ok = False; + LONG res = RegOpenKeyExW(HKEY_CURRENT_USER, k_Reg_Software_7zip, 0, KEY_READ | k_Reg_WOW_Flag, &key); + if (res == ERROR_SUCCESS) + { + ok = MyRegistry_QueryString(key, k_Reg_Path32, path); + // ok = MyRegistry_QueryString(key, k_Reg_Path, path); + RegCloseKey(key); + } + } + */ + + + if (g_SilentMode) + return Install(); + + { + int retCode = 1; + g_HWND = CreateDialog( + hInstance, + // GetModuleHandle(NULL), + MAKEINTRESOURCE(IDD_INSTALL), NULL, MyDlgProc); + if (!g_HWND) + return 1; + + { + const HICON hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON)); + // SendMessage(g_HWND, WM_SETICON, (WPARAM)ICON_SMALL, (LPARAM)hIcon); + SendMessage(g_HWND, WM_SETICON, (WPARAM)ICON_BIG, (LPARAM)hIcon); + } + + { + BOOL bRet; + MSG msg; + + while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0) + { + if (bRet == -1) + return retCode; + if (!g_HWND) + return retCode; + + if (!IsDialogMessage(g_HWND, &msg)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + if (!g_HWND) + return retCode; + + if (g_Install_was_Pressed && !g_Finished) + { + retCode = Install(); + g_Finished = True; + if (retCode != 0) + break; + if (!g_HWND) + break; + { + SetDlgItemTextW(g_HWND, IDOK, L"Close"); + EnableWindow(GetDlgItem(g_HWND, IDOK), TRUE); + EnableWindow(GetDlgItem(g_HWND, IDCANCEL), FALSE); + SendMessage(g_HWND, WM_NEXTDLGCTL, (WPARAM)(void *)GetDlgItem(g_HWND, IDOK), TRUE); + } + } + } + + if (g_HWND) + { + DestroyWindow(g_HWND); + g_HWND = NULL; + } + } + + return retCode; + } +} diff --git a/src/plugins/7zip/7za/c/Util/7zipUninstall/7zipUninstall.dsp b/src/plugins/7zip/7za/c/Util/7zipUninstall/7zipUninstall.dsp new file mode 100644 index 00000000..bb7473fc --- /dev/null +++ b/src/plugins/7zip/7za/c/Util/7zipUninstall/7zipUninstall.dsp @@ -0,0 +1,132 @@ +# Microsoft Developer Studio Project File - Name="7zipUninstall" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Application" 0x0101 + +CFG=7zipUninstall - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "7zipUninstall.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "7zipUninstall.mak" CFG="7zipUninstall - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "7zipUninstall - Win32 Release" (based on "Win32 (x86) Application") +!MESSAGE "7zipUninstall - Win32 Debug" (based on "Win32 (x86) Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "7zipUninstall - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /Gr /MD /W4 /WX /GX /O1 /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "_UNICODE2" /D "UNICODE2" /FAcs /Yu"Precomp.h" /FD /GF /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "NDEBUG" +# ADD RSC /l 0x419 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386 /out:"Release/Uninstall.exe" + +!ELSEIF "$(CFG)" == "7zipUninstall - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /W4 /WX /Gm /GX /ZI /Od /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "_UNICODE2" /D "UNICODE2" /Yu"Precomp.h" /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "_DEBUG" +# ADD RSC /l 0x419 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /out:"Debug/Uninstall.exe" /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "7zipUninstall - Win32 Release" +# Name "7zipUninstall - Win32 Debug" +# Begin Group "Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\7zTypes.h +# End Source File +# Begin Source File + +SOURCE=..\..\7zVersion.h +# End Source File +# End Group +# Begin Group "Spec" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\7zWindows.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compiler.h +# End Source File +# Begin Source File + +SOURCE=.\Precomp.c +# ADD CPP /Yc"Precomp.h" +# End Source File +# Begin Source File + +SOURCE=.\Precomp.h +# End Source File +# End Group +# Begin Source File + +SOURCE=.\7zipUninstall.c +# End Source File +# Begin Source File + +SOURCE=.\resource.rc +# End Source File +# End Target +# End Project diff --git a/src/plugins/7zip/7za/c/Util/7zipUninstall/7zipUninstall.dsw b/src/plugins/7zip/7za/c/Util/7zipUninstall/7zipUninstall.dsw new file mode 100644 index 00000000..2873eda6 --- /dev/null +++ b/src/plugins/7zip/7za/c/Util/7zipUninstall/7zipUninstall.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "7zipUninstall"=.\7zipUninstall.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/src/plugins/7zip/7za/c/Util/7zipUninstall/7zipUninstall.ico b/src/plugins/7zip/7za/c/Util/7zipUninstall/7zipUninstall.ico new file mode 100644 index 00000000..19eb20ca Binary files /dev/null and b/src/plugins/7zip/7za/c/Util/7zipUninstall/7zipUninstall.ico differ diff --git a/src/plugins/7zip/7za/c/Util/7zipUninstall/7zipUninstall.manifest b/src/plugins/7zip/7za/c/Util/7zipUninstall/7zipUninstall.manifest new file mode 100644 index 00000000..a6014434 --- /dev/null +++ b/src/plugins/7zip/7za/c/Util/7zipUninstall/7zipUninstall.manifest @@ -0,0 +1,18 @@ + + + +7-Zip Uninstaller + + + + + + + + + + + + +true + diff --git a/src/plugins/7zip/7za/c/Util/7zipUninstall/Precomp.c b/src/plugins/7zip/7za/c/Util/7zipUninstall/Precomp.c new file mode 100644 index 00000000..01605e3c --- /dev/null +++ b/src/plugins/7zip/7za/c/Util/7zipUninstall/Precomp.c @@ -0,0 +1,4 @@ +/* Precomp.c -- StdAfx +2013-01-21 : Igor Pavlov : Public domain */ + +#include "Precomp.h" diff --git a/src/plugins/7zip/7za/c/Util/7zipUninstall/Precomp.h b/src/plugins/7zip/7za/c/Util/7zipUninstall/Precomp.h new file mode 100644 index 00000000..13a41eff --- /dev/null +++ b/src/plugins/7zip/7za/c/Util/7zipUninstall/Precomp.h @@ -0,0 +1,13 @@ +/* Precomp.h -- Precomp +2024-01-23 : Igor Pavlov : Public domain */ + +// #ifndef ZIP7_INC_PRECOMP_LOC_H +// #define ZIP7_INC_PRECOMP_LOC_H + +#if defined(_MSC_VER) && _MSC_VER >= 1800 +#pragma warning(disable : 4464) // relative include path contains '..' +#endif + +#include "../../Precomp.h" + +// #endif diff --git a/src/plugins/7zip/7za/c/Util/7zipUninstall/makefile b/src/plugins/7zip/7za/c/Util/7zipUninstall/makefile new file mode 100644 index 00000000..9d8aafca --- /dev/null +++ b/src/plugins/7zip/7za/c/Util/7zipUninstall/makefile @@ -0,0 +1,18 @@ +PROG = 7zipUninstall.exe +MY_FIXED = 1 + +!IFDEF Z7_64BIT_INSTALLER +CFLAGS = $(CFLAGS) -DZ7_64BIT_INSTALLER +!ENDIF + +MAIN_OBJS = \ + $O\7zipUninstall.obj \ + +OBJS = \ + $(MAIN_OBJS) \ + $O\resource.res + +!include "../../../CPP/Build.mak" + +$(MAIN_OBJS): $(*B).c + $(COMPL_O1) diff --git a/src/plugins/7zip/7za/c/Util/7zipUninstall/resource.h b/src/plugins/7zip/7za/c/Util/7zipUninstall/resource.h new file mode 100644 index 00000000..b5c33ff1 --- /dev/null +++ b/src/plugins/7zip/7za/c/Util/7zipUninstall/resource.h @@ -0,0 +1,9 @@ +#define IDD_INSTALL 100 + +#define IDT_EXTRACT_EXTRACT_TO 110 +#define IDE_EXTRACT_PATH 111 + +#define IDT_CUR_FILE 113 +#define IDC_PROGRESS 114 + +#define IDI_ICON 1 diff --git a/src/plugins/7zip/7za/c/Util/7zipUninstall/resource.rc b/src/plugins/7zip/7za/c/Util/7zipUninstall/resource.rc new file mode 100644 index 00000000..79400c67 --- /dev/null +++ b/src/plugins/7zip/7za/c/Util/7zipUninstall/resource.rc @@ -0,0 +1,48 @@ +#include +// #include +// #include +#include + +#define USE_COPYRIGHT_CR +#include "../../7zVersion.rc" +#include "resource.h" + +MY_VERSION_INFO(MY_VFT_APP, "7-Zip Uninstaller", "Uninstall", "Uninstall.exe") + +1 ICON "7zipUninstall.ico" + +#define xc 184 +#define yc 96 + +#define m 8 +#define bxs 64 +#define bys 16 + + +#define xs (xc + m + m) +#define ys (yc + m + m) + +#define bx1 (xs - m - bxs) +#define bx2 (bx1 - m - bxs) + +#define by (ys - m - bys) + +IDD_INSTALL DIALOG 0, 0, xs, ys +STYLE DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_VISIBLE +CAPTION "Uninstall 7-Zip" +FONT 8, "MS Shell Dlg" +BEGIN + LTEXT "Uninstall from:", IDT_EXTRACT_EXTRACT_TO, m, m, xc, 8 + EDITTEXT IDE_EXTRACT_PATH, m, 21, xc, 14, ES_AUTOHSCROLL | WS_DISABLED + + + LTEXT "", IDT_CUR_FILE, m, 50, xc, 8 + CONTROL "", IDC_PROGRESS, "msctls_progress32", WS_BORDER, m, 64, xc, 10 + + DEFPUSHBUTTON "&Uninstall", IDOK, bx2, by, bxs, bys, WS_GROUP + PUSHBUTTON "Cancel", IDCANCEL, bx1, by, bxs, bys +END + +#ifndef UNDER_CE +1 24 MOVEABLE PURE "7zipUninstall.manifest" +#endif diff --git a/src/plugins/7zip/7za/c/Util/Lzma/LzmaUtil.c b/src/plugins/7zip/7za/c/Util/Lzma/LzmaUtil.c new file mode 100644 index 00000000..b9b974bd --- /dev/null +++ b/src/plugins/7zip/7za/c/Util/Lzma/LzmaUtil.c @@ -0,0 +1,313 @@ +/* LzmaUtil.c -- Test application for LZMA compression +2023-03-07 : Igor Pavlov : Public domain */ + +#include "Precomp.h" + +#include +#include +#include + +#include "../../CpuArch.h" + +#include "../../Alloc.h" +#include "../../7zFile.h" +#include "../../7zVersion.h" +#include "../../LzFind.h" +#include "../../LzmaDec.h" +#include "../../LzmaEnc.h" + +static const char * const kCantReadMessage = "Cannot read input file"; +static const char * const kCantWriteMessage = "Cannot write output file"; +static const char * const kCantAllocateMessage = "Cannot allocate memory"; +static const char * const kDataErrorMessage = "Data error"; + +static void Print(const char *s) +{ + fputs(s, stdout); +} + +static void PrintHelp(void) +{ + Print( + "\n" "LZMA-C " MY_VERSION_CPU " : " MY_COPYRIGHT_DATE + "\n" + "\n" "Usage: lzma inputFile outputFile" + "\n" " e: encode file" + "\n" " d: decode file" + "\n"); +} + +static int PrintError(const char *message) +{ + Print("\nError: "); + Print(message); + Print("\n"); + return 1; +} + +#define CONVERT_INT_TO_STR(charType, tempSize) \ + unsigned char temp[tempSize]; unsigned i = 0; \ + while (val >= 10) { temp[i++] = (unsigned char)('0' + (unsigned)(val % 10)); val /= 10; } \ + *s++ = (charType)('0' + (unsigned)val); \ + while (i != 0) { i--; *s++ = (charType)temp[i]; } \ + *s = 0; \ + return s; + +static char * Convert_unsigned_To_str(unsigned val, char *s) +{ + CONVERT_INT_TO_STR(char, 32) +} + +static void Print_unsigned(unsigned code) +{ + char str[32]; + Convert_unsigned_To_str(code, str); + Print(str); +} + +static int PrintError_WRes(const char *message, WRes wres) +{ + PrintError(message); + Print("\nSystem error code: "); + Print_unsigned((unsigned)wres); + #ifndef _WIN32 + { + const char *s = strerror(wres); + if (s) + { + Print(" : "); + Print(s); + } + } + #endif + Print("\n"); + return 1; +} + +static int PrintErrorNumber(SRes val) +{ + Print("\n7-Zip error code: "); + Print_unsigned((unsigned)val); + Print("\n"); + return 1; +} + +static int PrintUserError(void) +{ + return PrintError("Incorrect command"); +} + + +#define IN_BUF_SIZE (1 << 16) +#define OUT_BUF_SIZE (1 << 16) + + +static SRes Decode2(CLzmaDec *state, ISeqOutStreamPtr outStream, ISeqInStreamPtr inStream, + UInt64 unpackSize) +{ + const int thereIsSize = (unpackSize != (UInt64)(Int64)-1); + Byte inBuf[IN_BUF_SIZE]; + Byte outBuf[OUT_BUF_SIZE]; + size_t inPos = 0, inSize = 0, outPos = 0; + LzmaDec_Init(state); + for (;;) + { + if (inPos == inSize) + { + inSize = IN_BUF_SIZE; + RINOK(inStream->Read(inStream, inBuf, &inSize)) + inPos = 0; + } + { + SRes res; + SizeT inProcessed = inSize - inPos; + SizeT outProcessed = OUT_BUF_SIZE - outPos; + ELzmaFinishMode finishMode = LZMA_FINISH_ANY; + ELzmaStatus status; + if (thereIsSize && outProcessed > unpackSize) + { + outProcessed = (SizeT)unpackSize; + finishMode = LZMA_FINISH_END; + } + + res = LzmaDec_DecodeToBuf(state, outBuf + outPos, &outProcessed, + inBuf + inPos, &inProcessed, finishMode, &status); + inPos += inProcessed; + outPos += outProcessed; + unpackSize -= outProcessed; + + if (outStream) + if (outStream->Write(outStream, outBuf, outPos) != outPos) + return SZ_ERROR_WRITE; + + outPos = 0; + + if (res != SZ_OK || (thereIsSize && unpackSize == 0)) + return res; + + if (inProcessed == 0 && outProcessed == 0) + { + if (thereIsSize || status != LZMA_STATUS_FINISHED_WITH_MARK) + return SZ_ERROR_DATA; + return res; + } + } + } +} + + +static SRes Decode(ISeqOutStreamPtr outStream, ISeqInStreamPtr inStream) +{ + UInt64 unpackSize; + int i; + SRes res = 0; + + CLzmaDec state; + + /* header: 5 bytes of LZMA properties and 8 bytes of uncompressed size */ + unsigned char header[LZMA_PROPS_SIZE + 8]; + + /* Read and parse header */ + + { + size_t size = sizeof(header); + RINOK(SeqInStream_ReadMax(inStream, header, &size)) + if (size != sizeof(header)) + return SZ_ERROR_INPUT_EOF; + } + unpackSize = 0; + for (i = 0; i < 8; i++) + unpackSize += (UInt64)header[LZMA_PROPS_SIZE + i] << (i * 8); + + LzmaDec_CONSTRUCT(&state) + RINOK(LzmaDec_Allocate(&state, header, LZMA_PROPS_SIZE, &g_Alloc)) + res = Decode2(&state, outStream, inStream, unpackSize); + LzmaDec_Free(&state, &g_Alloc); + return res; +} + +static SRes Encode(ISeqOutStreamPtr outStream, ISeqInStreamPtr inStream, UInt64 fileSize) +{ + CLzmaEncHandle enc; + SRes res; + CLzmaEncProps props; + + enc = LzmaEnc_Create(&g_Alloc); + if (enc == 0) + return SZ_ERROR_MEM; + + LzmaEncProps_Init(&props); + res = LzmaEnc_SetProps(enc, &props); + + if (res == SZ_OK) + { + Byte header[LZMA_PROPS_SIZE + 8]; + size_t headerSize = LZMA_PROPS_SIZE; + int i; + + res = LzmaEnc_WriteProperties(enc, header, &headerSize); + for (i = 0; i < 8; i++) + header[headerSize++] = (Byte)(fileSize >> (8 * i)); + if (outStream->Write(outStream, header, headerSize) != headerSize) + res = SZ_ERROR_WRITE; + else + { + if (res == SZ_OK) + res = LzmaEnc_Encode(enc, outStream, inStream, NULL, &g_Alloc, &g_Alloc); + } + } + LzmaEnc_Destroy(enc, &g_Alloc, &g_Alloc); + return res; +} + + +int Z7_CDECL main(int numArgs, const char *args[]) +{ + CFileSeqInStream inStream; + CFileOutStream outStream; + char c; + int res; + int encodeMode; + BoolInt useOutFile = False; + + LzFindPrepare(); + + FileSeqInStream_CreateVTable(&inStream); + File_Construct(&inStream.file); + inStream.wres = 0; + + FileOutStream_CreateVTable(&outStream); + File_Construct(&outStream.file); + outStream.wres = 0; + + if (numArgs == 1) + { + PrintHelp(); + return 0; + } + + if (numArgs < 3 || numArgs > 4 || strlen(args[1]) != 1) + return PrintUserError(); + + c = args[1][0]; + encodeMode = (c == 'e' || c == 'E'); + if (!encodeMode && c != 'd' && c != 'D') + return PrintUserError(); + + /* + { + size_t t4 = sizeof(UInt32); + size_t t8 = sizeof(UInt64); + if (t4 != 4 || t8 != 8) + return PrintError("Incorrect UInt32 or UInt64"); + } + */ + + { + const WRes wres = InFile_Open(&inStream.file, args[2]); + if (wres != 0) + return PrintError_WRes("Cannot open input file", wres); + } + + if (numArgs > 3) + { + WRes wres; + useOutFile = True; + wres = OutFile_Open(&outStream.file, args[3]); + if (wres != 0) + return PrintError_WRes("Cannot open output file", wres); + } + else if (encodeMode) + PrintUserError(); + + if (encodeMode) + { + UInt64 fileSize; + const WRes wres = File_GetLength(&inStream.file, &fileSize); + if (wres != 0) + return PrintError_WRes("Cannot get file length", wres); + res = Encode(&outStream.vt, &inStream.vt, fileSize); + } + else + { + res = Decode(&outStream.vt, useOutFile ? &inStream.vt : NULL); + } + + if (useOutFile) + File_Close(&outStream.file); + File_Close(&inStream.file); + + if (res != SZ_OK) + { + if (res == SZ_ERROR_MEM) + return PrintError(kCantAllocateMessage); + else if (res == SZ_ERROR_DATA) + return PrintError(kDataErrorMessage); + else if (res == SZ_ERROR_WRITE) + return PrintError_WRes(kCantWriteMessage, outStream.wres); + else if (res == SZ_ERROR_READ) + return PrintError_WRes(kCantReadMessage, inStream.wres); + return PrintErrorNumber(res); + } + return 0; +} diff --git a/src/plugins/7zip/7za/c/Util/Lzma/LzmaUtil.dsp b/src/plugins/7zip/7za/c/Util/Lzma/LzmaUtil.dsp new file mode 100644 index 00000000..71de950d --- /dev/null +++ b/src/plugins/7zip/7za/c/Util/Lzma/LzmaUtil.dsp @@ -0,0 +1,192 @@ +# Microsoft Developer Studio Project File - Name="LzmaUtil" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=LzmaUtil - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "LzmaUtil.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "LzmaUtil.mak" CFG="LzmaUtil - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "LzmaUtil - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "LzmaUtil - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "LzmaUtil - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /MT /W4 /WX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /FD /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x419 /d "NDEBUG" +# ADD RSC /l 0x419 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 /out:"c:\util\7lzma.exe" + +!ELSEIF "$(CFG)" == "LzmaUtil - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W4 /WX /Gm /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /FD /GZ /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x419 /d "_DEBUG" +# ADD RSC /l 0x419 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /out:"c:\util\7lzma.exe" /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "LzmaUtil - Win32 Release" +# Name "LzmaUtil - Win32 Debug" +# Begin Source File + +SOURCE=..\..\7zFile.c +# End Source File +# Begin Source File + +SOURCE=..\..\7zFile.h +# End Source File +# Begin Source File + +SOURCE=..\..\7zStream.c +# End Source File +# Begin Source File + +SOURCE=..\..\7zTypes.h +# End Source File +# Begin Source File + +SOURCE=..\..\7zVersion.h +# End Source File +# Begin Source File + +SOURCE=..\..\7zWindows.h +# End Source File +# Begin Source File + +SOURCE=..\..\Alloc.c +# End Source File +# Begin Source File + +SOURCE=..\..\Alloc.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compiler.h +# End Source File +# Begin Source File + +SOURCE=..\..\CpuArch.c +# End Source File +# Begin Source File + +SOURCE=..\..\CpuArch.h +# End Source File +# Begin Source File + +SOURCE=..\..\LzFind.c +# End Source File +# Begin Source File + +SOURCE=..\..\LzFind.h +# End Source File +# Begin Source File + +SOURCE=..\..\LzFindMt.c +# End Source File +# Begin Source File + +SOURCE=..\..\LzFindMt.h +# End Source File +# Begin Source File + +SOURCE=..\..\LzFindOpt.c +# End Source File +# Begin Source File + +SOURCE=..\..\LzHash.h +# End Source File +# Begin Source File + +SOURCE=..\..\LzmaDec.c +# End Source File +# Begin Source File + +SOURCE=..\..\LzmaDec.h +# End Source File +# Begin Source File + +SOURCE=..\..\LzmaEnc.c +# End Source File +# Begin Source File + +SOURCE=..\..\LzmaEnc.h +# End Source File +# Begin Source File + +SOURCE=.\LzmaUtil.c +# End Source File +# Begin Source File + +SOURCE=..\..\Precomp.h +# End Source File +# Begin Source File + +SOURCE=.\Precomp.h +# End Source File +# Begin Source File + +SOURCE=..\..\Threads.c +# End Source File +# Begin Source File + +SOURCE=..\..\Threads.h +# End Source File +# End Target +# End Project diff --git a/src/plugins/7zip/7za/c/Util/Lzma/LzmaUtil.dsw b/src/plugins/7zip/7za/c/Util/Lzma/LzmaUtil.dsw new file mode 100644 index 00000000..c52eaf6d --- /dev/null +++ b/src/plugins/7zip/7za/c/Util/Lzma/LzmaUtil.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "LzmaUtil"=.\LzmaUtil.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/src/plugins/7zip/7za/c/Util/Lzma/Precomp.h b/src/plugins/7zip/7za/c/Util/Lzma/Precomp.h new file mode 100644 index 00000000..13a41eff --- /dev/null +++ b/src/plugins/7zip/7za/c/Util/Lzma/Precomp.h @@ -0,0 +1,13 @@ +/* Precomp.h -- Precomp +2024-01-23 : Igor Pavlov : Public domain */ + +// #ifndef ZIP7_INC_PRECOMP_LOC_H +// #define ZIP7_INC_PRECOMP_LOC_H + +#if defined(_MSC_VER) && _MSC_VER >= 1800 +#pragma warning(disable : 4464) // relative include path contains '..' +#endif + +#include "../../Precomp.h" + +// #endif diff --git a/src/plugins/7zip/7za/c/Util/Lzma/makefile b/src/plugins/7zip/7za/c/Util/Lzma/makefile new file mode 100644 index 00000000..7813bdb0 --- /dev/null +++ b/src/plugins/7zip/7za/c/Util/Lzma/makefile @@ -0,0 +1,30 @@ +# MY_STATIC_LINK=1 +PROG = LZMAc.exe + +CFLAGS = $(CFLAGS) \ + +LIB_OBJS = \ + $O\LzmaUtil.obj \ + +C_OBJS = \ + $O\Alloc.obj \ + $O\CpuArch.obj \ + $O\LzFind.obj \ + $O\LzFindMt.obj \ + $O\LzFindOpt.obj \ + $O\LzmaDec.obj \ + $O\LzmaEnc.obj \ + $O\7zFile.obj \ + $O\7zStream.obj \ + $O\Threads.obj \ + +OBJS = \ + $(LIB_OBJS) \ + $(C_OBJS) \ + +!include "../../../CPP/Build.mak" + +$(LIB_OBJS): $(*B).c + $(COMPL_O2) +$(C_OBJS): ../../$(*B).c + $(COMPL_O2) diff --git a/src/plugins/7zip/7za/c/Util/Lzma/makefile.gcc b/src/plugins/7zip/7za/c/Util/Lzma/makefile.gcc new file mode 100644 index 00000000..2acb0b80 --- /dev/null +++ b/src/plugins/7zip/7za/c/Util/Lzma/makefile.gcc @@ -0,0 +1,21 @@ +PROG = 7lzma + +include ../../../CPP/7zip/LzmaDec_gcc.mak + + +OBJS = \ + $(LZMA_DEC_OPT_OBJS) \ + $O/7zFile.o \ + $O/7zStream.o \ + $O/Alloc.o \ + $O/CpuArch.o \ + $O/LzFind.o \ + $O/LzFindMt.o \ + $O/LzFindOpt.o \ + $O/LzmaDec.o \ + $O/LzmaEnc.o \ + $O/LzmaUtil.o \ + $O/Threads.o \ + + +include ../../7zip_gcc_c.mak diff --git a/src/plugins/7zip/7za/c/Util/LzmaLib/LzmaLib.def b/src/plugins/7zip/7za/c/Util/LzmaLib/LzmaLib.def new file mode 100644 index 00000000..8bc6add9 --- /dev/null +++ b/src/plugins/7zip/7za/c/Util/LzmaLib/LzmaLib.def @@ -0,0 +1,4 @@ +EXPORTS + LzmaCompress + LzmaUncompress + diff --git a/src/plugins/7zip/7za/c/Util/LzmaLib/LzmaLib.dsp b/src/plugins/7zip/7za/c/Util/LzmaLib/LzmaLib.dsp new file mode 100644 index 00000000..f413137a --- /dev/null +++ b/src/plugins/7zip/7za/c/Util/LzmaLib/LzmaLib.dsp @@ -0,0 +1,206 @@ +# Microsoft Developer Studio Project File - Name="LzmaLib" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=LzmaLib - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "LzmaLib.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "LzmaLib.mak" CFG="LzmaLib - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "LzmaLib - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "LzmaLib - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "LzmaLib - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "LZMALIB_EXPORTS" /YX /FD /c +# ADD CPP /nologo /Gr /MT /W4 /WX /O2 /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "LZMALIB_EXPORTS" /FD /c +# SUBTRACT CPP /YX +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "NDEBUG" +# ADD RSC /l 0x419 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 /out:"C:\Util\LZMA.dll" /opt:NOWIN98 +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "LzmaLib - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "LZMALIB_EXPORTS" /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W4 /WX /Gm /ZI /Od /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "LZMALIB_EXPORTS" /D "COMPRESS_MF_MT" /FD /GZ /c +# SUBTRACT CPP /YX +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "_DEBUG" +# ADD RSC /l 0x419 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /out:"C:\Util\LZMA.dll" /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "LzmaLib - Win32 Release" +# Name "LzmaLib - Win32 Debug" +# Begin Group "Spec" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=.\LzmaLib.def +# End Source File +# Begin Source File + +SOURCE=.\LzmaLibExports.c +# End Source File +# Begin Source File + +SOURCE=.\Precomp.h +# End Source File +# End Group +# Begin Source File + +SOURCE=..\..\7zTypes.h +# End Source File +# Begin Source File + +SOURCE=..\..\7zWindows.h +# End Source File +# Begin Source File + +SOURCE=..\..\Alloc.c +# End Source File +# Begin Source File + +SOURCE=..\..\Alloc.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compiler.h +# End Source File +# Begin Source File + +SOURCE=..\..\CpuArch.c +# End Source File +# Begin Source File + +SOURCE=..\..\CpuArch.h +# End Source File +# Begin Source File + +SOURCE=..\..\IStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\LzFind.c +# End Source File +# Begin Source File + +SOURCE=..\..\LzFind.h +# End Source File +# Begin Source File + +SOURCE=..\..\LzFindMt.c +# End Source File +# Begin Source File + +SOURCE=..\..\LzFindMt.h +# End Source File +# Begin Source File + +SOURCE=..\..\LzFindOpt.c +# End Source File +# Begin Source File + +SOURCE=..\..\LzHash.h +# End Source File +# Begin Source File + +SOURCE=..\..\LzmaDec.c +# End Source File +# Begin Source File + +SOURCE=..\..\LzmaDec.h +# End Source File +# Begin Source File + +SOURCE=..\..\LzmaEnc.c +# End Source File +# Begin Source File + +SOURCE=..\..\LzmaEnc.h +# End Source File +# Begin Source File + +SOURCE=..\..\LzmaLib.c +# End Source File +# Begin Source File + +SOURCE=..\..\LzmaLib.h +# End Source File +# Begin Source File + +SOURCE=..\..\Precomp.h +# End Source File +# Begin Source File + +SOURCE=.\resource.rc +# End Source File +# Begin Source File + +SOURCE=..\..\Threads.c +# End Source File +# Begin Source File + +SOURCE=..\..\Threads.h +# End Source File +# End Target +# End Project diff --git a/src/plugins/7zip/7za/c/Util/LzmaLib/LzmaLib.dsw b/src/plugins/7zip/7za/c/Util/LzmaLib/LzmaLib.dsw new file mode 100644 index 00000000..6faf3336 --- /dev/null +++ b/src/plugins/7zip/7za/c/Util/LzmaLib/LzmaLib.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "LzmaLib"=.\LzmaLib.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/src/plugins/7zip/7za/c/Util/LzmaLib/LzmaLibExports.c b/src/plugins/7zip/7za/c/Util/LzmaLib/LzmaLibExports.c new file mode 100644 index 00000000..a46c9a80 --- /dev/null +++ b/src/plugins/7zip/7za/c/Util/LzmaLib/LzmaLibExports.c @@ -0,0 +1,15 @@ +/* LzmaLibExports.c -- LZMA library DLL Entry point +2023-03-05 : Igor Pavlov : Public domain */ + +#include "Precomp.h" + +#include "../../7zWindows.h" + +BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved); +BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) +{ + UNUSED_VAR(hInstance) + UNUSED_VAR(dwReason) + UNUSED_VAR(lpReserved) + return TRUE; +} diff --git a/src/plugins/7zip/7za/c/Util/LzmaLib/Precomp.c b/src/plugins/7zip/7za/c/Util/LzmaLib/Precomp.c new file mode 100644 index 00000000..01605e3c --- /dev/null +++ b/src/plugins/7zip/7za/c/Util/LzmaLib/Precomp.c @@ -0,0 +1,4 @@ +/* Precomp.c -- StdAfx +2013-01-21 : Igor Pavlov : Public domain */ + +#include "Precomp.h" diff --git a/src/plugins/7zip/7za/c/Util/LzmaLib/Precomp.h b/src/plugins/7zip/7za/c/Util/LzmaLib/Precomp.h new file mode 100644 index 00000000..13a41eff --- /dev/null +++ b/src/plugins/7zip/7za/c/Util/LzmaLib/Precomp.h @@ -0,0 +1,13 @@ +/* Precomp.h -- Precomp +2024-01-23 : Igor Pavlov : Public domain */ + +// #ifndef ZIP7_INC_PRECOMP_LOC_H +// #define ZIP7_INC_PRECOMP_LOC_H + +#if defined(_MSC_VER) && _MSC_VER >= 1800 +#pragma warning(disable : 4464) // relative include path contains '..' +#endif + +#include "../../Precomp.h" + +// #endif diff --git a/src/plugins/7zip/7za/c/Util/LzmaLib/makefile b/src/plugins/7zip/7za/c/Util/LzmaLib/makefile new file mode 100644 index 00000000..9ed0aa49 --- /dev/null +++ b/src/plugins/7zip/7za/c/Util/LzmaLib/makefile @@ -0,0 +1,59 @@ +MY_STATIC_LINK=1 +SLIB = sLZMA.lib +PROG = LZMA.dll +SLIBPATH = $O\$(SLIB) + +DEF_FILE = LzmaLib.def +CFLAGS = $(CFLAGS) \ + +LIB_OBJS = \ + $O\LzmaLibExports.obj \ + +C_OBJS = \ + $O\Alloc.obj \ + $O\CpuArch.obj \ + $O\LzFind.obj \ + $O\LzFindMt.obj \ + $O\LzmaDec.obj \ + $O\LzmaEnc.obj \ + $O\LzmaLib.obj \ + $O\Threads.obj \ + +!include "../../../CPP/7zip/LzFindOpt.mak" +!include "../../../CPP/7zip/LzmaDec.mak" + +OBJS = \ + $O\Precomp.obj \ + $(LIB_OBJS) \ + $(C_OBJS) \ + $(ASM_OBJS) \ + $O\resource.res + +!include "../../../CPP/Build.mak" + +$(SLIBPATH): $O $(OBJS) + lib -out:$(SLIBPATH) $(OBJS) $(LIBS) + + +MAK_SINGLE_FILE = 1 + +$O\Precomp.obj: Precomp.c + $(CCOMPL_PCH) + +!IFDEF MAK_SINGLE_FILE + +$(LIB_OBJS): $(*B).c + $(CCOMPL_USE) +$(C_OBJS): ../../$(*B).c + $(CCOMPL_USE) + +!ELSE + +{.}.c{$O}.obj:: + $(CCOMPLB_USE) +{../../../C}.c{$O}.obj:: + $(CCOMPLB_USE) + +!ENDIF + +!include "../../Asm_c.mak" diff --git a/src/plugins/7zip/7za/c/Util/LzmaLib/resource.rc b/src/plugins/7zip/7za/c/Util/LzmaLib/resource.rc new file mode 100644 index 00000000..674832e0 --- /dev/null +++ b/src/plugins/7zip/7za/c/Util/LzmaLib/resource.rc @@ -0,0 +1,3 @@ +#include "../../7zVersion.rc" + +MY_VERSION_INFO_DLL("LZMA library", "LZMA") diff --git a/src/plugins/7zip/7za/c/Util/SfxSetup/Precomp.c b/src/plugins/7zip/7za/c/Util/SfxSetup/Precomp.c new file mode 100644 index 00000000..01605e3c --- /dev/null +++ b/src/plugins/7zip/7za/c/Util/SfxSetup/Precomp.c @@ -0,0 +1,4 @@ +/* Precomp.c -- StdAfx +2013-01-21 : Igor Pavlov : Public domain */ + +#include "Precomp.h" diff --git a/src/plugins/7zip/7za/c/Util/SfxSetup/Precomp.h b/src/plugins/7zip/7za/c/Util/SfxSetup/Precomp.h new file mode 100644 index 00000000..13a41eff --- /dev/null +++ b/src/plugins/7zip/7za/c/Util/SfxSetup/Precomp.h @@ -0,0 +1,13 @@ +/* Precomp.h -- Precomp +2024-01-23 : Igor Pavlov : Public domain */ + +// #ifndef ZIP7_INC_PRECOMP_LOC_H +// #define ZIP7_INC_PRECOMP_LOC_H + +#if defined(_MSC_VER) && _MSC_VER >= 1800 +#pragma warning(disable : 4464) // relative include path contains '..' +#endif + +#include "../../Precomp.h" + +// #endif diff --git a/src/plugins/7zip/7za/c/Util/SfxSetup/SfxSetup.c b/src/plugins/7zip/7za/c/Util/SfxSetup/SfxSetup.c new file mode 100644 index 00000000..344aa735 --- /dev/null +++ b/src/plugins/7zip/7za/c/Util/SfxSetup/SfxSetup.c @@ -0,0 +1,657 @@ +/* SfxSetup.c - 7z SFX Setup +: Igor Pavlov : Public domain */ + +#include "Precomp.h" + +#ifndef UNICODE +#define UNICODE +#endif + +#ifndef _UNICODE +#define _UNICODE +#endif + +#ifdef _CONSOLE +#include +#endif + +#include "../../7z.h" +#include "../../7zAlloc.h" +#include "../../7zCrc.h" +#include "../../7zFile.h" +#include "../../CpuArch.h" +#include "../../DllSecur.h" + +#define k_EXE_ExtIndex 2 + +#define kInputBufSize ((size_t)1 << 18) + + +#define wcscat lstrcatW +#define wcslen (size_t)lstrlenW +#define wcscpy lstrcpyW +// wcsncpy() and lstrcpynW() work differently. We don't use them. + +static const char * const kExts[] = +{ + "bat" + , "cmd" + , "exe" + , "inf" + , "msi" + #ifdef UNDER_CE + , "cab" + #endif + , "html" + , "htm" +}; + +static const char * const kNames[] = +{ + "setup" + , "install" + , "run" + , "start" +}; + +static unsigned FindExt(const WCHAR *s, unsigned *extLen) +{ + unsigned len = (unsigned)wcslen(s); + unsigned i; + for (i = len; i > 0; i--) + { + if (s[i - 1] == '.') + { + *extLen = len - i; + return i - 1; + } + } + *extLen = 0; + return len; +} + +#define MAKE_CHAR_UPPER(c) ((((c) >= 'a' && (c) <= 'z') ? (c) - 0x20 : (c))) + +static unsigned FindItem(const char * const *items, unsigned num, const WCHAR *s, unsigned len) +{ + unsigned i; + for (i = 0; i < num; i++) + { + const char *item = items[i]; + const unsigned itemLen = (unsigned)strlen(item); + unsigned j; + if (len != itemLen) + continue; + for (j = 0; j < len; j++) + { + const unsigned c = (Byte)item[j]; + if (c != s[j] && MAKE_CHAR_UPPER(c) != s[j]) + break; + } + if (j == len) + return i; + } + return i; +} + +#ifdef _CONSOLE +static BOOL WINAPI HandlerRoutine(DWORD ctrlType) +{ + UNUSED_VAR(ctrlType); + return TRUE; +} +#endif + + +#ifdef _CONSOLE +static void PrintStr(const char *s) +{ + fputs(s, stdout); +} +#endif + +static void PrintErrorMessage(const char *message) +{ + #ifdef _CONSOLE + PrintStr("\n7-Zip Error: "); + PrintStr(message); + PrintStr("\n"); + #else + #ifdef UNDER_CE + WCHAR messageW[256 + 4]; + unsigned i; + for (i = 0; i < 256 && message[i] != 0; i++) + messageW[i] = message[i]; + messageW[i] = 0; + MessageBoxW(0, messageW, L"7-Zip Error", MB_ICONERROR); + #else + MessageBoxA(0, message, "7-Zip Error", MB_ICONERROR); + #endif + #endif +} + +static WRes MyCreateDir(const WCHAR *name) +{ + return CreateDirectoryW(name, NULL) ? 0 : GetLastError(); +} + +#ifdef UNDER_CE +#define kBufferSize (1 << 13) +#else +#define kBufferSize (1 << 15) +#endif + +#define kSignatureSearchLimit (1 << 22) + +static BoolInt FindSignature(CSzFile *stream, UInt64 *resPos) +{ + Byte buf[kBufferSize]; + size_t numPrevBytes = 0; + *resPos = 0; + for (;;) + { + size_t processed, pos; + if (*resPos > kSignatureSearchLimit) + return False; + processed = kBufferSize - numPrevBytes; + if (File_Read(stream, buf + numPrevBytes, &processed) != 0) + return False; + processed += numPrevBytes; + if (processed < k7zStartHeaderSize || + (processed == k7zStartHeaderSize && numPrevBytes != 0)) + return False; + processed -= k7zStartHeaderSize; + for (pos = 0; pos <= processed; pos++) + { + for (; pos <= processed && buf[pos] != '7'; pos++); + if (pos > processed) + break; + if (memcmp(buf + pos, k7zSignature, k7zSignatureSize) == 0) + if (CrcCalc(buf + pos + 12, 20) == GetUi32(buf + pos + 8)) + { + *resPos += pos; + return True; + } + } + *resPos += processed; + numPrevBytes = k7zStartHeaderSize; + memmove(buf, buf + processed, k7zStartHeaderSize); + } +} + +static BoolInt DoesFileOrDirExist(const WCHAR *path) +{ + WIN32_FIND_DATAW fd; + HANDLE handle; + handle = FindFirstFileW(path, &fd); + if (handle == INVALID_HANDLE_VALUE) + return False; + FindClose(handle); + return True; +} + +static WRes RemoveDirWithSubItems(WCHAR *path) +{ + WIN32_FIND_DATAW fd; + HANDLE handle; + WRes res = 0; + const size_t len = wcslen(path); + wcscpy(path + len, L"*"); + handle = FindFirstFileW(path, &fd); + path[len] = L'\0'; + if (handle == INVALID_HANDLE_VALUE) + return GetLastError(); + + for (;;) + { + if (wcscmp(fd.cFileName, L".") != 0 && + wcscmp(fd.cFileName, L"..") != 0) + { + wcscpy(path + len, fd.cFileName); + if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) + { + wcscat(path, WSTRING_PATH_SEPARATOR); + res = RemoveDirWithSubItems(path); + } + else + { + SetFileAttributesW(path, 0); + if (DeleteFileW(path) == 0) + res = GetLastError(); + } + + if (res != 0) + break; + } + + if (!FindNextFileW(handle, &fd)) + { + res = GetLastError(); + if (res == ERROR_NO_MORE_FILES) + res = 0; + break; + } + } + + path[len] = L'\0'; + FindClose(handle); + if (res == 0) + { + if (!RemoveDirectoryW(path)) + res = GetLastError(); + } + return res; +} + +#ifdef _CONSOLE +int Z7_CDECL main(void) +#else +int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, + #ifdef UNDER_CE + LPWSTR + #else + LPSTR + #endif + lpCmdLine, int nCmdShow) +#endif +{ + CFileInStream archiveStream; + CLookToRead2 lookStream; + CSzArEx db; + SRes res = SZ_OK; + ISzAlloc allocImp; + ISzAlloc allocTempImp; + WCHAR sfxPath[MAX_PATH + 2]; + WCHAR path[MAX_PATH * 3 + 2]; + #ifndef UNDER_CE + WCHAR workCurDir[MAX_PATH + 32]; + #endif + size_t pathLen; + DWORD winRes; + const WCHAR *cmdLineParams; + const char *errorMessage = NULL; + BoolInt useShellExecute = True; + DWORD exitCode = 0; + + LoadSecurityDlls(); + + #ifdef _CONSOLE + SetConsoleCtrlHandler(HandlerRoutine, TRUE); + #else + UNUSED_VAR(hInstance) + UNUSED_VAR(hPrevInstance) + UNUSED_VAR(lpCmdLine) + UNUSED_VAR(nCmdShow) + #endif + + CrcGenerateTable(); + + allocImp.Alloc = SzAlloc; + allocImp.Free = SzFree; + + allocTempImp.Alloc = SzAllocTemp; + allocTempImp.Free = SzFreeTemp; + + FileInStream_CreateVTable(&archiveStream); + LookToRead2_CreateVTable(&lookStream, False); + lookStream.buf = NULL; + + winRes = GetModuleFileNameW(NULL, sfxPath, MAX_PATH); + if (winRes == 0 || winRes > MAX_PATH) + return 1; + { + cmdLineParams = GetCommandLineW(); + #ifndef UNDER_CE + { + BoolInt quoteMode = False; + for (;; cmdLineParams++) + { + const WCHAR c = *cmdLineParams; + if (c == L'\"') + quoteMode = !quoteMode; + else if (c == 0 || (c == L' ' && !quoteMode)) + break; + } + } + #endif + } + + { + unsigned i; + DWORD d; + winRes = GetTempPathW(MAX_PATH, path); + if (winRes == 0 || winRes > MAX_PATH) + return 1; + pathLen = wcslen(path); + d = (GetTickCount() << 12) ^ (GetCurrentThreadId() << 14) ^ GetCurrentProcessId(); + + for (i = 0;; i++, d += GetTickCount()) + { + if (i >= 100) + { + res = SZ_ERROR_FAIL; + break; + } + wcscpy(path + pathLen, L"7z"); + + { + WCHAR *s = path + wcslen(path); + UInt32 value = d; + unsigned k; + for (k = 0; k < 8; k++) + { + const unsigned t = value & 0xF; + value >>= 4; + s[7 - k] = (WCHAR)((t < 10) ? ('0' + t) : ('A' + (t - 10))); + } + s[k] = '\0'; + } + + if (DoesFileOrDirExist(path)) + continue; + if (CreateDirectoryW(path, NULL)) + { + wcscat(path, WSTRING_PATH_SEPARATOR); + pathLen = wcslen(path); + break; + } + if (GetLastError() != ERROR_ALREADY_EXISTS) + { + res = SZ_ERROR_FAIL; + break; + } + } + + #ifndef UNDER_CE + wcscpy(workCurDir, path); + #endif + if (res != SZ_OK) + errorMessage = "Can't create temp folder"; + } + + if (res != SZ_OK) + { + if (!errorMessage) + errorMessage = "Error"; + PrintErrorMessage(errorMessage); + return 1; + } + + if (InFile_OpenW(&archiveStream.file, sfxPath) != 0) + { + errorMessage = "can not open input file"; + res = SZ_ERROR_FAIL; + } + else + { + UInt64 pos = 0; + if (!FindSignature(&archiveStream.file, &pos)) + res = SZ_ERROR_FAIL; + else if (File_Seek(&archiveStream.file, (Int64 *)&pos, SZ_SEEK_SET) != 0) + res = SZ_ERROR_FAIL; + if (res != 0) + errorMessage = "Can't find 7z archive"; + } + + if (res == SZ_OK) + { + lookStream.buf = (Byte *)ISzAlloc_Alloc(&allocImp, kInputBufSize); + if (!lookStream.buf) + res = SZ_ERROR_MEM; + else + { + lookStream.bufSize = kInputBufSize; + lookStream.realStream = &archiveStream.vt; + LookToRead2_INIT(&lookStream) + } + } + + SzArEx_Init(&db); + + if (res == SZ_OK) + { + res = SzArEx_Open(&db, &lookStream.vt, &allocImp, &allocTempImp); + } + + if (res == SZ_OK) + { + UInt32 executeFileIndex = (UInt32)(Int32)-1; + UInt32 minPrice = 1 << 30; + UInt32 i; + UInt32 blockIndex = 0xFFFFFFFF; /* it can have any value before first call (if outBuffer = 0) */ + Byte *outBuffer = 0; /* it must be 0 before first call for each new archive. */ + size_t outBufferSize = 0; /* it can have any value before first call (if outBuffer = 0) */ + + for (i = 0; i < db.NumFiles; i++) + { + size_t offset = 0; + size_t outSizeProcessed = 0; + WCHAR *temp; + + if (SzArEx_GetFileNameUtf16(&db, i, NULL) >= MAX_PATH) + { + res = SZ_ERROR_FAIL; + break; + } + + temp = path + pathLen; + + SzArEx_GetFileNameUtf16(&db, i, (UInt16 *)temp); + { + res = SzArEx_Extract(&db, &lookStream.vt, i, + &blockIndex, &outBuffer, &outBufferSize, + &offset, &outSizeProcessed, + &allocImp, &allocTempImp); + if (res != SZ_OK) + break; + } + { + CSzFile outFile; + size_t processedSize; + size_t j; + size_t nameStartPos = 0; + for (j = 0; temp[j] != 0; j++) + { + if (temp[j] == '/') + { + temp[j] = 0; + MyCreateDir(path); + temp[j] = CHAR_PATH_SEPARATOR; + nameStartPos = j + 1; + } + } + + if (SzArEx_IsDir(&db, i)) + { + MyCreateDir(path); + continue; + } + else + { + unsigned extLen; + const WCHAR *name = temp + nameStartPos; + unsigned len = (unsigned)wcslen(name); + const unsigned nameLen = FindExt(temp + nameStartPos, &extLen); + const unsigned extPrice = FindItem(kExts, sizeof(kExts) / sizeof(kExts[0]), name + len - extLen, extLen); + const unsigned namePrice = FindItem(kNames, sizeof(kNames) / sizeof(kNames[0]), name, nameLen); + + const unsigned price = namePrice + extPrice * 64 + (nameStartPos == 0 ? 0 : (1 << 12)); + if (minPrice > price) + { + minPrice = price; + executeFileIndex = i; + useShellExecute = (extPrice != k_EXE_ExtIndex); + } + + if (DoesFileOrDirExist(path)) + { + errorMessage = "Duplicate file"; + res = SZ_ERROR_FAIL; + break; + } + if (OutFile_OpenW(&outFile, path)) + { + errorMessage = "Can't open output file"; + res = SZ_ERROR_FAIL; + break; + } + } + + processedSize = outSizeProcessed; + if (File_Write(&outFile, outBuffer + offset, &processedSize) != 0 || processedSize != outSizeProcessed) + { + errorMessage = "Can't write output file"; + res = SZ_ERROR_FAIL; + } + + #ifdef USE_WINDOWS_FILE + if (SzBitWithVals_Check(&db.MTime, i)) + { + const CNtfsFileTime *t = db.MTime.Vals + i; + FILETIME mTime; + mTime.dwLowDateTime = t->Low; + mTime.dwHighDateTime = t->High; + SetFileTime(outFile.handle, NULL, NULL, &mTime); + } + #endif + + { + const WRes res2 = File_Close(&outFile); + if (res != SZ_OK) + break; + if (res2 != 0) + { + errorMessage = "Can't close output file"; + res = SZ_ERROR_FAIL; + break; + } + } + #ifdef USE_WINDOWS_FILE + if (SzBitWithVals_Check(&db.Attribs, i)) + SetFileAttributesW(path, db.Attribs.Vals[i]); + #endif + } + } + + if (res == SZ_OK) + { + if (executeFileIndex == (UInt32)(Int32)-1) + { + errorMessage = "There is no file to execute"; + res = SZ_ERROR_FAIL; + } + else + { + WCHAR *temp = path + pathLen; + UInt32 j; + SzArEx_GetFileNameUtf16(&db, executeFileIndex, (UInt16 *)temp); + for (j = 0; temp[j] != 0; j++) + if (temp[j] == '/') + temp[j] = CHAR_PATH_SEPARATOR; + } + } + ISzAlloc_Free(&allocImp, outBuffer); + } + + SzArEx_Free(&db, &allocImp); + + ISzAlloc_Free(&allocImp, lookStream.buf); + + File_Close(&archiveStream.file); + + if (res == SZ_OK) + { + HANDLE hProcess = 0; + + #ifndef UNDER_CE + WCHAR oldCurDir[MAX_PATH + 2]; + oldCurDir[0] = 0; + { + const DWORD needLen = GetCurrentDirectory(MAX_PATH + 1, oldCurDir); + if (needLen == 0 || needLen > MAX_PATH) + oldCurDir[0] = 0; + SetCurrentDirectory(workCurDir); + } + #endif + + if (useShellExecute) + { + SHELLEXECUTEINFO ei; + UINT32 executeRes; + BOOL success; + + memset(&ei, 0, sizeof(ei)); + ei.cbSize = sizeof(ei); + ei.lpFile = path; + ei.fMask = SEE_MASK_NOCLOSEPROCESS + #ifndef UNDER_CE + | SEE_MASK_FLAG_DDEWAIT + #endif + /* | SEE_MASK_NO_CONSOLE */ + ; + if (wcslen(cmdLineParams) != 0) + ei.lpParameters = cmdLineParams; + ei.nShow = SW_SHOWNORMAL; /* SW_HIDE; */ + success = ShellExecuteEx(&ei); + executeRes = (UINT32)(UINT_PTR)ei.hInstApp; + if (!success || (executeRes <= 32 && executeRes != 0)) /* executeRes = 0 in Windows CE */ + res = SZ_ERROR_FAIL; + else + hProcess = ei.hProcess; + } + else + { + STARTUPINFOW si; + PROCESS_INFORMATION pi; + WCHAR cmdLine[MAX_PATH * 3]; + + wcscpy(cmdLine, path); + wcscat(cmdLine, cmdLineParams); + memset(&si, 0, sizeof(si)); + si.cb = sizeof(si); + if (CreateProcessW(NULL, cmdLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi) == 0) + res = SZ_ERROR_FAIL; + else + { + CloseHandle(pi.hThread); + hProcess = pi.hProcess; + } + } + + if (hProcess != 0) + { + WaitForSingleObject(hProcess, INFINITE); + if (!GetExitCodeProcess(hProcess, &exitCode)) + exitCode = 1; + CloseHandle(hProcess); + } + + #ifndef UNDER_CE + SetCurrentDirectory(oldCurDir); + #endif + } + + path[pathLen] = L'\0'; + RemoveDirWithSubItems(path); + + if (res == SZ_OK) + return (int)exitCode; + + { + if (res == SZ_ERROR_UNSUPPORTED) + errorMessage = "Decoder doesn't support this archive"; + else if (res == SZ_ERROR_MEM) + errorMessage = "Can't allocate required memory"; + else if (res == SZ_ERROR_CRC) + errorMessage = "CRC error"; + else + { + if (!errorMessage) + errorMessage = "ERROR"; + } + + if (errorMessage) + PrintErrorMessage(errorMessage); + } + return 1; +} diff --git a/src/plugins/7zip/7za/c/Util/SfxSetup/SfxSetup.dsp b/src/plugins/7zip/7za/c/Util/SfxSetup/SfxSetup.dsp new file mode 100644 index 00000000..60439a6f --- /dev/null +++ b/src/plugins/7zip/7za/c/Util/SfxSetup/SfxSetup.dsp @@ -0,0 +1,231 @@ +# Microsoft Developer Studio Project File - Name="SfxSetup" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Application" 0x0101 + +CFG=SfxSetup - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "SfxSetup.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "SfxSetup.mak" CFG="SfxSetup - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "SfxSetup - Win32 Release" (based on "Win32 (x86) Application") +!MESSAGE "SfxSetup - Win32 Debug" (based on "Win32 (x86) Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "SfxSetup - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /W4 /WX /GX /O2 /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "_UNICODE" /D "UNICODE" /Yu"Precomp.h" /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "NDEBUG" +# ADD RSC /l 0x419 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386 + +!ELSEIF "$(CFG)" == "SfxSetup - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /W4 /WX /Gm /GX /ZI /Od /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "_UNICODE" /D "UNICODE" /Yu"Precomp.h" /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "_DEBUG" +# ADD RSC /l 0x419 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "SfxSetup - Win32 Release" +# Name "SfxSetup - Win32 Debug" +# Begin Group "Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\7z.h +# End Source File +# Begin Source File + +SOURCE=..\..\7zAlloc.c +# End Source File +# Begin Source File + +SOURCE=..\..\7zAlloc.h +# End Source File +# Begin Source File + +SOURCE=..\..\7zArcIn.c +# End Source File +# Begin Source File + +SOURCE=..\..\7zBuf.c +# End Source File +# Begin Source File + +SOURCE=..\..\7zBuf.h +# End Source File +# Begin Source File + +SOURCE=..\..\7zCrc.c +# End Source File +# Begin Source File + +SOURCE=..\..\7zCrc.h +# End Source File +# Begin Source File + +SOURCE=..\..\7zCrcOpt.c +# End Source File +# Begin Source File + +SOURCE=..\..\7zDec.c +# End Source File +# Begin Source File + +SOURCE=..\..\7zFile.c +# End Source File +# Begin Source File + +SOURCE=..\..\7zFile.h +# End Source File +# Begin Source File + +SOURCE=..\..\7zStream.c +# End Source File +# Begin Source File + +SOURCE=..\..\7zTypes.h +# End Source File +# Begin Source File + +SOURCE=..\..\Bcj2.c +# End Source File +# Begin Source File + +SOURCE=..\..\Bcj2.h +# End Source File +# Begin Source File + +SOURCE=..\..\Bra.c +# End Source File +# Begin Source File + +SOURCE=..\..\Bra.h +# End Source File +# Begin Source File + +SOURCE=..\..\Bra86.c +# End Source File +# Begin Source File + +SOURCE=..\..\BraIA64.c +# End Source File +# Begin Source File + +SOURCE=..\..\CpuArch.c +# End Source File +# Begin Source File + +SOURCE=..\..\CpuArch.h +# End Source File +# Begin Source File + +SOURCE=..\..\Delta.c +# End Source File +# Begin Source File + +SOURCE=..\..\Delta.h +# End Source File +# Begin Source File + +SOURCE=..\..\DllSecur.c +# End Source File +# Begin Source File + +SOURCE=..\..\DllSecur.h +# End Source File +# Begin Source File + +SOURCE=..\..\Lzma2Dec.c +# End Source File +# Begin Source File + +SOURCE=..\..\Lzma2Dec.h +# End Source File +# Begin Source File + +SOURCE=..\..\LzmaDec.c +# End Source File +# Begin Source File + +SOURCE=..\..\LzmaDec.h +# End Source File +# End Group +# Begin Group "Spec" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=.\Precomp.c +# ADD CPP /Yc"Precomp.h" +# End Source File +# Begin Source File + +SOURCE=.\Precomp.h +# End Source File +# End Group +# Begin Source File + +SOURCE=.\SfxSetup.c +# End Source File +# End Target +# End Project diff --git a/src/plugins/7zip/7za/c/Util/SfxSetup/SfxSetup.dsw b/src/plugins/7zip/7za/c/Util/SfxSetup/SfxSetup.dsw new file mode 100644 index 00000000..ea231112 --- /dev/null +++ b/src/plugins/7zip/7za/c/Util/SfxSetup/SfxSetup.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "SfxSetup"=.\SfxSetup.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/src/plugins/7zip/7za/c/Util/SfxSetup/makefile b/src/plugins/7zip/7za/c/Util/SfxSetup/makefile new file mode 100644 index 00000000..b3f25a22 --- /dev/null +++ b/src/plugins/7zip/7za/c/Util/SfxSetup/makefile @@ -0,0 +1,44 @@ +PROG = 7zS2.sfx +MY_FIXED = 1 + +CFLAGS = $(CFLAGS) \ + -DZ7_EXTRACT_ONLY \ + +C_OBJS = \ + $O\7zAlloc.obj \ + $O\7zArcIn.obj \ + $O\7zBuf.obj \ + $O\7zBuf2.obj \ + $O\7zFile.obj \ + $O\7zDec.obj \ + $O\7zStream.obj \ + $O\Bcj2.obj \ + $O\Bra.obj \ + $O\Bra86.obj \ + $O\BraIA64.obj \ + $O\CpuArch.obj \ + $O\Delta.obj \ + $O\DllSecur.obj \ + $O\Lzma2Dec.obj \ + $O\LzmaDec.obj \ + +7Z_OBJS = \ + $O\SfxSetup.obj \ + +!include "../../../CPP/7zip/Crc.mak" +# !include "../../../CPP/7zip/LzmaDec.mak" + +OBJS = \ + $(7Z_OBJS) \ + $(C_OBJS) \ + $(ASM_OBJS) \ + $O\resource.res + +!include "../../../CPP/Build.mak" + +$(7Z_OBJS): $(*B).c + $(COMPL_O1) +$(C_OBJS): ../../$(*B).c + $(COMPL_O1) + +!include "../../Asm_c.mak" diff --git a/src/plugins/7zip/7za/c/Util/SfxSetup/makefile_con b/src/plugins/7zip/7za/c/Util/SfxSetup/makefile_con new file mode 100644 index 00000000..9f4b9166 --- /dev/null +++ b/src/plugins/7zip/7za/c/Util/SfxSetup/makefile_con @@ -0,0 +1,40 @@ +PROG = 7zS2con.sfx +MY_FIXED = 1 + +CFLAGS = $(CFLAGS) -D_CONSOLE \ + -DZ7_EXTRACT_ONLY \ + +C_OBJS = \ + $O\7zAlloc.obj \ + $O\7zArcIn.obj \ + $O\7zBuf.obj \ + $O\7zBuf2.obj \ + $O\7zCrc.obj \ + $O\7zCrcOpt.obj \ + $O\7zFile.obj \ + $O\7zDec.obj \ + $O\7zStream.obj \ + $O\Bcj2.obj \ + $O\Bra.obj \ + $O\Bra86.obj \ + $O\BraIA64.obj \ + $O\CpuArch.obj \ + $O\Delta.obj \ + $O\DllSecur.obj \ + $O\Lzma2Dec.obj \ + $O\LzmaDec.obj \ + +7Z_OBJS = \ + $O\SfxSetup.obj \ + +OBJS = \ + $(7Z_OBJS) \ + $(C_OBJS) \ + $O\resource.res + +!include "../../../CPP/Build.mak" + +$(7Z_OBJS): $(*B).c + $(COMPL_O1) +$(C_OBJS): ../../$(*B).c + $(COMPL_O1) diff --git a/src/plugins/7zip/7za/c/Util/SfxSetup/resource.rc b/src/plugins/7zip/7za/c/Util/SfxSetup/resource.rc new file mode 100644 index 00000000..0c1637f2 --- /dev/null +++ b/src/plugins/7zip/7za/c/Util/SfxSetup/resource.rc @@ -0,0 +1,5 @@ +#include "../../7zVersion.rc" + +MY_VERSION_INFO_APP("7z Setup SFX small", "7zS2.sfx") + +1 ICON "setup.ico" diff --git a/src/plugins/7zip/7za/c/Util/SfxSetup/setup.ico b/src/plugins/7zip/7za/c/Util/SfxSetup/setup.ico new file mode 100644 index 00000000..dbb6ca8b Binary files /dev/null and b/src/plugins/7zip/7za/c/Util/SfxSetup/setup.ico differ diff --git a/src/plugins/7zip/7za/c/Xxh64.c b/src/plugins/7zip/7za/c/Xxh64.c new file mode 100644 index 00000000..660e0bea --- /dev/null +++ b/src/plugins/7zip/7za/c/Xxh64.c @@ -0,0 +1,389 @@ +/* Xxh64.c -- XXH64 hash calculation +original code: Copyright (c) Yann Collet. +modified by Igor Pavlov. +This source code is licensed under BSD 2-Clause License. +*/ + +#include "Precomp.h" + +#include "CpuArch.h" +#include "RotateDefs.h" +#include "Xxh64.h" + +#define Z7_XXH_PRIME64_1 UINT64_CONST(0x9E3779B185EBCA87) +#define Z7_XXH_PRIME64_2 UINT64_CONST(0xC2B2AE3D27D4EB4F) +#define Z7_XXH_PRIME64_3 UINT64_CONST(0x165667B19E3779F9) +#define Z7_XXH_PRIME64_4 UINT64_CONST(0x85EBCA77C2B2AE63) +#define Z7_XXH_PRIME64_5 UINT64_CONST(0x27D4EB2F165667C5) + +void Xxh64State_Init(CXxh64State *p) +{ + const UInt64 seed = 0; + p->v[0] = seed + Z7_XXH_PRIME64_1 + Z7_XXH_PRIME64_2; + p->v[1] = seed + Z7_XXH_PRIME64_2; + p->v[2] = seed; + p->v[3] = seed - Z7_XXH_PRIME64_1; +} + +#if !defined(MY_CPU_64BIT) && defined(MY_CPU_X86) && defined(_MSC_VER) + #define Z7_XXH64_USE_ASM +#elif !defined(MY_CPU_LE_UNALIGN_64) // && defined (MY_CPU_LE) + #define Z7_XXH64_USE_ALIGNED +#endif + +#ifdef Z7_XXH64_USE_ALIGNED + #define Xxh64State_UpdateBlocks_Unaligned_Select Xxh64State_UpdateBlocks_Unaligned +#else + #define Xxh64State_UpdateBlocks_Unaligned_Select Xxh64State_UpdateBlocks +#endif + +#if !defined(MY_CPU_64BIT) && defined(MY_CPU_X86) \ + && defined(Z7_MSC_VER_ORIGINAL) && Z7_MSC_VER_ORIGINAL > 1200 +/* we try to avoid __allmul calls in MSVC for 64-bit multiply. + But MSVC6 still uses __allmul for our code. + So for MSVC6 we use default 64-bit multiply without our optimization. +*/ +#define LOW32(b) ((UInt32)(b & 0xffffffff)) +/* MSVC compiler (MSVC > 1200) can use "mul" instruction + without __allmul for our MY_emulu MACRO. + MY_emulu is similar to __emulu(a, b) MACRO */ +#define MY_emulu(a, b) ((UInt64)(a) * (b)) +#define MY_SET_HIGH32(a) ((UInt64)(a) << 32) +#define MY_MUL32_SET_HIGH32(a, b) MY_SET_HIGH32((UInt32)(a) * (UInt32)(b)) +// /* +#define MY_MUL64(a, b) \ + ( MY_emulu((UInt32)(a), LOW32(b)) + \ + MY_SET_HIGH32( \ + (UInt32)((a) >> 32) * LOW32(b) + \ + (UInt32)(a) * (UInt32)((b) >> 32) \ + )) +// */ +/* +#define MY_MUL64(a, b) \ + ( MY_emulu((UInt32)(a), LOW32(b)) \ + + MY_MUL32_SET_HIGH32((a) >> 32, LOW32(b)) + \ + + MY_MUL32_SET_HIGH32(a, (b) >> 32) \ + ) +*/ + +#define MY_MUL_32_64(a32, b) \ + ( MY_emulu((UInt32)(a32), LOW32(b)) \ + + MY_MUL32_SET_HIGH32(a32, (b) >> 32) \ + ) + +#else +#define MY_MUL64(a, b) ((a) * (b)) +#define MY_MUL_32_64(a32, b) ((a32) * (UInt64)(b)) +#endif + + +static +Z7_FORCE_INLINE +UInt64 Xxh64_Round(UInt64 acc, UInt64 input) +{ + acc += MY_MUL64(input, Z7_XXH_PRIME64_2); + acc = Z7_ROTL64(acc, 31); + return MY_MUL64(acc, Z7_XXH_PRIME64_1); +} + +static UInt64 Xxh64_Merge(UInt64 acc, UInt64 val) +{ + acc ^= Xxh64_Round(0, val); + return MY_MUL64(acc, Z7_XXH_PRIME64_1) + Z7_XXH_PRIME64_4; +} + + +#ifdef Z7_XXH64_USE_ASM + +#define Z7_XXH_PRIME64_1_HIGH 0x9E3779B1 +#define Z7_XXH_PRIME64_1_LOW 0x85EBCA87 +#define Z7_XXH_PRIME64_2_HIGH 0xC2B2AE3D +#define Z7_XXH_PRIME64_2_LOW 0x27D4EB4F + +void +Z7_NO_INLINE +__declspec(naked) +Z7_FASTCALL +Xxh64State_UpdateBlocks(CXxh64State *p, const void *data, const void *end) +{ + #if !defined(__clang__) + UNUSED_VAR(p) + UNUSED_VAR(data) + UNUSED_VAR(end) + #endif + __asm push ebx + __asm push ebp + __asm push esi + __asm push edi + + #define STACK_OFFSET 4 * 8 + __asm sub esp, STACK_OFFSET + +#define COPY_1(n) \ + __asm mov eax, [ecx + n * 4] \ + __asm mov [esp + n * 4], eax \ + +#define COPY_2(n) \ + __asm mov eax, [esp + n * 4] \ + __asm mov [ecx + n * 4], eax \ + + COPY_1(0) + __asm mov edi, [ecx + 1 * 4] \ + COPY_1(2) + COPY_1(3) + COPY_1(4) + COPY_1(5) + COPY_1(6) + COPY_1(7) + + __asm mov esi, edx \ + __asm mov [esp + 0 * 8 + 4], ecx + __asm mov ecx, Z7_XXH_PRIME64_2_LOW \ + __asm mov ebp, Z7_XXH_PRIME64_1_LOW \ + +#define R(n, state1, state1_reg) \ + __asm mov eax, [esi + n * 8] \ + __asm imul ebx, eax, Z7_XXH_PRIME64_2_HIGH \ + __asm add ebx, state1 \ + __asm mul ecx \ + __asm add edx, ebx \ + __asm mov ebx, [esi + n * 8 + 4] \ + __asm imul ebx, ecx \ + __asm add eax, [esp + n * 8] \ + __asm adc edx, ebx \ + __asm mov ebx, eax \ + __asm shld eax, edx, 31 \ + __asm shld edx, ebx, 31 \ + __asm imul state1_reg, eax, Z7_XXH_PRIME64_1_HIGH \ + __asm imul edx, ebp \ + __asm add state1_reg, edx \ + __asm mul ebp \ + __asm add state1_reg, edx \ + __asm mov [esp + n * 8], eax \ + +#define R2(n) \ + R(n, [esp + n * 8 + 4], ebx) \ + __asm mov [esp + n * 8 + 4], ebx \ + + __asm align 16 + __asm main_loop: + R(0, edi, edi) + R2(1) + R2(2) + R2(3) + __asm add esi, 32 + __asm cmp esi, [esp + STACK_OFFSET + 4 * 4 + 4] + __asm jne main_loop + + __asm mov ecx, [esp + 0 * 8 + 4] + + COPY_2(0) + __asm mov [ecx + 1 * 4], edi + COPY_2(2) + COPY_2(3) + COPY_2(4) + COPY_2(5) + COPY_2(6) + COPY_2(7) + + __asm add esp, STACK_OFFSET + __asm pop edi + __asm pop esi + __asm pop ebp + __asm pop ebx + __asm ret 4 +} + +#else + +#ifdef Z7_XXH64_USE_ALIGNED +static +#endif +void +Z7_NO_INLINE +Z7_FASTCALL +Xxh64State_UpdateBlocks_Unaligned_Select(CXxh64State *p, const void *_data, const void *end) +{ + const Byte *data = (const Byte *)_data; + UInt64 v0, v1, v2, v3; + v0 = p->v[0]; + v1 = p->v[1]; + v2 = p->v[2]; + v3 = p->v[3]; + do + { + v0 = Xxh64_Round(v0, GetUi64(data)); data += 8; + v1 = Xxh64_Round(v1, GetUi64(data)); data += 8; + v2 = Xxh64_Round(v2, GetUi64(data)); data += 8; + v3 = Xxh64_Round(v3, GetUi64(data)); data += 8; + } + while (data != end); + p->v[0] = v0; + p->v[1] = v1; + p->v[2] = v2; + p->v[3] = v3; +} + + +#ifdef Z7_XXH64_USE_ALIGNED + +static +void +Z7_NO_INLINE +Z7_FASTCALL +Xxh64State_UpdateBlocks_Aligned(CXxh64State *p, const void *_data, const void *end) +{ + const Byte *data = (const Byte *)_data; + UInt64 v0, v1, v2, v3; + v0 = p->v[0]; + v1 = p->v[1]; + v2 = p->v[2]; + v3 = p->v[3]; + do + { + v0 = Xxh64_Round(v0, GetUi64a(data)); data += 8; + v1 = Xxh64_Round(v1, GetUi64a(data)); data += 8; + v2 = Xxh64_Round(v2, GetUi64a(data)); data += 8; + v3 = Xxh64_Round(v3, GetUi64a(data)); data += 8; + } + while (data != end); + p->v[0] = v0; + p->v[1] = v1; + p->v[2] = v2; + p->v[3] = v3; +} + +void +Z7_NO_INLINE +Z7_FASTCALL +Xxh64State_UpdateBlocks(CXxh64State *p, const void *data, const void *end) +{ + if (((unsigned)(ptrdiff_t)data & 7) == 0) + Xxh64State_UpdateBlocks_Aligned(p, data, end); + else + Xxh64State_UpdateBlocks_Unaligned(p, data, end); +} + +#endif // Z7_XXH64_USE_ALIGNED +#endif // Z7_XXH64_USE_ASM + +UInt64 Xxh64State_Digest(const CXxh64State *p, const void *_data, UInt64 count) +{ + UInt64 h = p->v[2]; + + if (count >= 32) + { + h = Z7_ROTL64(p->v[0], 1) + + Z7_ROTL64(p->v[1], 7) + + Z7_ROTL64(h, 12) + + Z7_ROTL64(p->v[3], 18); + h = Xxh64_Merge(h, p->v[0]); + h = Xxh64_Merge(h, p->v[1]); + h = Xxh64_Merge(h, p->v[2]); + h = Xxh64_Merge(h, p->v[3]); + } + else + h += Z7_XXH_PRIME64_5; + + h += count; + + // XXH64_finalize(): + { + unsigned cnt = (unsigned)count & 31; + const Byte *data = (const Byte *)_data; + while (cnt >= 8) + { + h ^= Xxh64_Round(0, GetUi64(data)); + data += 8; + h = Z7_ROTL64(h, 27); + h = MY_MUL64(h, Z7_XXH_PRIME64_1) + Z7_XXH_PRIME64_4; + cnt -= 8; + } + if (cnt >= 4) + { + const UInt32 v = GetUi32(data); + data += 4; + h ^= MY_MUL_32_64(v, Z7_XXH_PRIME64_1); + h = Z7_ROTL64(h, 23); + h = MY_MUL64(h, Z7_XXH_PRIME64_2) + Z7_XXH_PRIME64_3; + cnt -= 4; + } + while (cnt) + { + const UInt32 v = *data++; + h ^= MY_MUL_32_64(v, Z7_XXH_PRIME64_5); + h = Z7_ROTL64(h, 11); + h = MY_MUL64(h, Z7_XXH_PRIME64_1); + cnt--; + } + // XXH64_avalanche(h): + h ^= h >> 33; h = MY_MUL64(h, Z7_XXH_PRIME64_2); + h ^= h >> 29; h = MY_MUL64(h, Z7_XXH_PRIME64_3); + h ^= h >> 32; + return h; + } +} + + +void Xxh64_Init(CXxh64 *p) +{ + Xxh64State_Init(&p->state); + p->count = 0; + p->buf64[0] = 0; + p->buf64[1] = 0; + p->buf64[2] = 0; + p->buf64[3] = 0; +} + +void Xxh64_Update(CXxh64 *p, const void *_data, size_t size) +{ + const Byte *data = (const Byte *)_data; + unsigned cnt; + if (size == 0) + return; + cnt = (unsigned)p->count; + p->count += size; + + if (cnt &= 31) + { + unsigned rem = 32 - cnt; + Byte *dest = (Byte *)p->buf64 + cnt; + if (rem > size) + rem = (unsigned)size; + size -= rem; + cnt += rem; + // memcpy((Byte *)p->buf64 + cnt, data, rem); + do + *dest++ = *data++; + while (--rem); + if (cnt != 32) + return; +#ifdef Z7_XXH64_USE_ALIGNED + Xxh64State_UpdateBlocks_Aligned +#else + Xxh64State_UpdateBlocks_Unaligned_Select +#endif + (&p->state, p->buf64, &p->buf64[4]); + } + + if (size &= ~(size_t)31) + { +#ifdef Z7_XXH64_USE_ALIGNED + if (((unsigned)(ptrdiff_t)data & 7) == 0) + Xxh64State_UpdateBlocks_Aligned(&p->state, data, data + size); + else +#endif + Xxh64State_UpdateBlocks_Unaligned_Select(&p->state, data, data + size); + data += size; + } + + cnt = (unsigned)p->count & 31; + if (cnt) + { + // memcpy(p->buf64, data, cnt); + Byte *dest = (Byte *)p->buf64; + do + *dest++ = *data++; + while (--cnt); + } +} diff --git a/src/plugins/7zip/7za/c/Xxh64.h b/src/plugins/7zip/7za/c/Xxh64.h new file mode 100644 index 00000000..efef65eb --- /dev/null +++ b/src/plugins/7zip/7za/c/Xxh64.h @@ -0,0 +1,50 @@ +/* Xxh64.h -- XXH64 hash calculation interfaces +2023-08-18 : Igor Pavlov : Public domain */ + +#ifndef ZIP7_INC_XXH64_H +#define ZIP7_INC_XXH64_H + +#include "7zTypes.h" + +EXTERN_C_BEGIN + +#define Z7_XXH64_BLOCK_SIZE (4 * 8) + +typedef struct +{ + UInt64 v[4]; +} CXxh64State; + +void Xxh64State_Init(CXxh64State *p); + +// end != data && end == data + Z7_XXH64_BLOCK_SIZE * numBlocks +void Z7_FASTCALL Xxh64State_UpdateBlocks(CXxh64State *p, const void *data, const void *end); + +/* +Xxh64State_Digest(): +data: + the function processes only + (totalCount & (Z7_XXH64_BLOCK_SIZE - 1)) bytes in (data): (smaller than 32 bytes). +totalCount: total size of hashed stream: + it includes total size of data processed by previous Xxh64State_UpdateBlocks() calls, + and it also includes current processed size in (data). +*/ +UInt64 Xxh64State_Digest(const CXxh64State *p, const void *data, UInt64 totalCount); + + +typedef struct +{ + CXxh64State state; + UInt64 count; + UInt64 buf64[4]; +} CXxh64; + +void Xxh64_Init(CXxh64 *p); +void Xxh64_Update(CXxh64 *p, const void *data, size_t size); + +#define Xxh64_Digest(p) \ + Xxh64State_Digest(&(p)->state, (p)->buf64, (p)->count) + +EXTERN_C_END + +#endif diff --git a/src/plugins/7zip/7za/c/Xz.c b/src/plugins/7zip/7za/c/Xz.c index 1ef68782..d07550d0 100644 --- a/src/plugins/7zip/7za/c/Xz.c +++ b/src/plugins/7zip/7za/c/Xz.c @@ -1,5 +1,5 @@ /* Xz.c - Xz -2015-05-01 : Igor Pavlov : Public domain */ +2024-03-01 : Igor Pavlov : Public domain */ #include "Precomp.h" @@ -9,7 +9,7 @@ #include "XzCrc64.h" const Byte XZ_SIG[XZ_SIG_SIZE] = { 0xFD, '7', 'z', 'X', 'Z', 0 }; -const Byte XZ_FOOTER_SIG[XZ_FOOTER_SIG_SIZE] = { 'Y', 'Z' }; +/* const Byte XZ_FOOTER_SIG[XZ_FOOTER_SIG_SIZE] = { 'Y', 'Z' }; */ unsigned Xz_WriteVarInt(Byte *buf, UInt64 v) { @@ -20,28 +20,28 @@ unsigned Xz_WriteVarInt(Byte *buf, UInt64 v) v >>= 7; } while (v != 0); - buf[i - 1] &= 0x7F; + buf[(size_t)i - 1] &= 0x7F; return i; } void Xz_Construct(CXzStream *p) { - p->numBlocks = p->numBlocksAllocated = 0; - p->blocks = 0; + p->numBlocks = 0; + p->blocks = NULL; p->flags = 0; } -void Xz_Free(CXzStream *p, ISzAlloc *alloc) +void Xz_Free(CXzStream *p, ISzAllocPtr alloc) { - alloc->Free(alloc, p->blocks); - p->numBlocks = p->numBlocksAllocated = 0; - p->blocks = 0; + ISzAlloc_Free(alloc, p->blocks); + p->numBlocks = 0; + p->blocks = NULL; } unsigned XzFlags_GetCheckSize(CXzStreamFlags f) { unsigned t = XzFlags_GetCheckType(f); - return (t == 0) ? 0 : (4 << ((t - 1) / 3)); + return (t == 0) ? 0 : ((unsigned)4 << ((t - 1) / 3)); } void XzCheck_Init(CXzCheck *p, unsigned mode) @@ -52,6 +52,7 @@ void XzCheck_Init(CXzCheck *p, unsigned mode) case XZ_CHECK_CRC32: p->crc = CRC_INIT_VAL; break; case XZ_CHECK_CRC64: p->crc64 = CRC64_INIT_VAL; break; case XZ_CHECK_SHA256: Sha256_Init(&p->sha); break; + default: break; } } @@ -62,6 +63,7 @@ void XzCheck_Update(CXzCheck *p, const void *data, size_t size) case XZ_CHECK_CRC32: p->crc = CrcUpdate(p->crc, data, size); break; case XZ_CHECK_CRC64: p->crc64 = Crc64Update(p->crc64, data, size); break; case XZ_CHECK_SHA256: Sha256_Update(&p->sha, (const Byte *)data, size); break; + default: break; } } @@ -70,7 +72,7 @@ int XzCheck_Final(CXzCheck *p, Byte *digest) switch (p->mode) { case XZ_CHECK_CRC32: - SetUi32(digest, CRC_GET_DIGEST(p->crc)); + SetUi32(digest, CRC_GET_DIGEST(p->crc)) break; case XZ_CHECK_CRC64: { diff --git a/src/plugins/7zip/7za/c/Xz.h b/src/plugins/7zip/7za/c/Xz.h index be3a1c36..ad63b48c 100644 --- a/src/plugins/7zip/7za/c/Xz.h +++ b/src/plugins/7zip/7za/c/Xz.h @@ -1,21 +1,24 @@ /* Xz.h - Xz interface -2015-05-01 : Igor Pavlov : Public domain */ +Igor Pavlov : Public domain */ -#ifndef __XZ_H -#define __XZ_H +#ifndef ZIP7_INC_XZ_H +#define ZIP7_INC_XZ_H #include "Sha256.h" +#include "Delta.h" EXTERN_C_BEGIN #define XZ_ID_Subblock 1 #define XZ_ID_Delta 3 -#define XZ_ID_X86 4 -#define XZ_ID_PPC 5 -#define XZ_ID_IA64 6 -#define XZ_ID_ARM 7 -#define XZ_ID_ARMT 8 +#define XZ_ID_X86 4 +#define XZ_ID_PPC 5 +#define XZ_ID_IA64 6 +#define XZ_ID_ARM 7 +#define XZ_ID_ARMT 8 #define XZ_ID_SPARC 9 +#define XZ_ID_ARM64 0xa +#define XZ_ID_RISCV 0xb #define XZ_ID_LZMA2 0x21 unsigned Xz_ReadVarInt(const Byte *p, size_t maxSize, UInt64 *value); @@ -47,12 +50,13 @@ typedef struct CXzFilter filters[XZ_NUM_FILTERS_MAX]; } CXzBlock; -#define XzBlock_GetNumFilters(p) (((p)->flags & XZ_BF_NUM_FILTERS_MASK) + 1) +#define XzBlock_GetNumFilters(p) (((unsigned)(p)->flags & XZ_BF_NUM_FILTERS_MASK) + 1) #define XzBlock_HasPackSize(p) (((p)->flags & XZ_BF_PACK_SIZE) != 0) #define XzBlock_HasUnpackSize(p) (((p)->flags & XZ_BF_UNPACK_SIZE) != 0) +#define XzBlock_HasUnsupportedFlags(p) (((p)->flags & ~(XZ_BF_NUM_FILTERS_MASK | XZ_BF_PACK_SIZE | XZ_BF_UNPACK_SIZE)) != 0) SRes XzBlock_Parse(CXzBlock *p, const Byte *header); -SRes XzBlock_ReadHeader(CXzBlock *p, ISeqInStream *inStream, Bool *isIndex, UInt32 *headerSizeRes); +SRes XzBlock_ReadHeader(CXzBlock *p, ISeqInStreamPtr inStream, BoolInt *isIndex, UInt32 *headerSizeRes); /* ---------- xz stream ---------- */ @@ -60,7 +64,13 @@ SRes XzBlock_ReadHeader(CXzBlock *p, ISeqInStream *inStream, Bool *isIndex, UInt #define XZ_FOOTER_SIG_SIZE 2 extern const Byte XZ_SIG[XZ_SIG_SIZE]; + +/* extern const Byte XZ_FOOTER_SIG[XZ_FOOTER_SIG_SIZE]; +*/ + +#define XZ_FOOTER_SIG_0 'Y' +#define XZ_FOOTER_SIG_1 'Z' #define XZ_STREAM_FLAGS_SIZE 2 #define XZ_STREAM_CRC_SIZE 4 @@ -94,7 +104,7 @@ typedef UInt16 CXzStreamFlags; unsigned XzFlags_GetCheckSize(CXzStreamFlags f); SRes Xz_ParseHeader(CXzStreamFlags *p, const Byte *buf); -SRes Xz_ReadHeader(CXzStreamFlags *p, ISeqInStream *inStream); +SRes Xz_ReadHeader(CXzStreamFlags *p, ISeqInStreamPtr inStream); typedef struct { @@ -105,14 +115,15 @@ typedef struct typedef struct { CXzStreamFlags flags; + // Byte _pad[6]; size_t numBlocks; - size_t numBlocksAllocated; CXzBlockSizes *blocks; UInt64 startOffset; } CXzStream; +#define Xz_CONSTRUCT(p) { (p)->numBlocks = 0; (p)->blocks = NULL; (p)->flags = 0; } void Xz_Construct(CXzStream *p); -void Xz_Free(CXzStream *p, ISzAlloc *alloc); +void Xz_Free(CXzStream *p, ISzAllocPtr alloc); #define XZ_SIZE_OVERFLOW ((UInt64)(Int64)-1) @@ -126,13 +137,21 @@ typedef struct CXzStream *streams; } CXzs; +#define Xzs_CONSTRUCT(p) { (p)->num = 0; (p)->numAllocated = 0; (p)->streams = NULL; } void Xzs_Construct(CXzs *p); -void Xzs_Free(CXzs *p, ISzAlloc *alloc); -SRes Xzs_ReadBackward(CXzs *p, ILookInStream *inStream, Int64 *startOffset, ICompressProgress *progress, ISzAlloc *alloc); +void Xzs_Free(CXzs *p, ISzAllocPtr alloc); +/* +Xzs_ReadBackward() must be called for empty CXzs object. +Xzs_ReadBackward() can return non empty object with (p->num != 0) even in case of error. +*/ +SRes Xzs_ReadBackward(CXzs *p, ILookInStreamPtr inStream, Int64 *startOffset, ICompressProgressPtr progress, ISzAllocPtr alloc); UInt64 Xzs_GetNumBlocks(const CXzs *p); UInt64 Xzs_GetUnpackSize(const CXzs *p); + +// ECoderStatus values are identical to ELzmaStatus values of LZMA2 decoder + typedef enum { CODER_STATUS_NOT_SPECIFIED, /* use main error code instead */ @@ -141,43 +160,69 @@ typedef enum CODER_STATUS_NEEDS_MORE_INPUT /* you must provide more input bytes */ } ECoderStatus; + +// ECoderFinishMode values are identical to ELzmaFinishMode + typedef enum { CODER_FINISH_ANY, /* finish at any point */ CODER_FINISH_END /* block must be finished at the end */ } ECoderFinishMode; -typedef struct _IStateCoder + +typedef struct { - void *p; - void (*Free)(void *p, ISzAlloc *alloc); - SRes (*SetProps)(void *p, const Byte *props, size_t propSize, ISzAlloc *alloc); + void *p; // state object; + void (*Free)(void *p, ISzAllocPtr alloc); + SRes (*SetProps)(void *p, const Byte *props, size_t propSize, ISzAllocPtr alloc); void (*Init)(void *p); - SRes (*Code)(void *p, Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen, - int srcWasFinished, ECoderFinishMode finishMode, int *wasFinished); + SRes (*Code2)(void *p, Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen, + int srcWasFinished, ECoderFinishMode finishMode, + // int *wasFinished, + ECoderStatus *status); + SizeT (*Filter)(void *p, Byte *data, SizeT size); } IStateCoder; + +typedef struct +{ + UInt32 methodId; + UInt32 delta; + UInt32 ip; + UInt32 X86_State; + Byte delta_State[DELTA_STATE_SIZE]; +} CXzBcFilterStateBase; + +typedef SizeT (*Xz_Func_BcFilterStateBase_Filter)(CXzBcFilterStateBase *p, Byte *data, SizeT size); + +SRes Xz_StateCoder_Bc_SetFromMethod_Func(IStateCoder *p, UInt64 id, + Xz_Func_BcFilterStateBase_Filter func, ISzAllocPtr alloc); + + #define MIXCODER_NUM_FILTERS_MAX 4 typedef struct { - ISzAlloc *alloc; + ISzAllocPtr alloc; Byte *buf; unsigned numCoders; + + Byte *outBuf; + size_t outBufSize; + size_t outWritten; // is equal to lzmaDecoder.dicPos (in outBuf mode) + BoolInt wasFinished; + SRes res; + ECoderStatus status; + // BoolInt SingleBufMode; + int finished[MIXCODER_NUM_FILTERS_MAX - 1]; size_t pos[MIXCODER_NUM_FILTERS_MAX - 1]; size_t size[MIXCODER_NUM_FILTERS_MAX - 1]; UInt64 ids[MIXCODER_NUM_FILTERS_MAX]; + SRes results[MIXCODER_NUM_FILTERS_MAX]; IStateCoder coders[MIXCODER_NUM_FILTERS_MAX]; } CMixCoder; -void MixCoder_Construct(CMixCoder *p, ISzAlloc *alloc); -void MixCoder_Free(CMixCoder *p); -void MixCoder_Init(CMixCoder *p); -SRes MixCoder_SetFromMethod(CMixCoder *p, unsigned coderIndex, UInt64 methodId); -SRes MixCoder_Code(CMixCoder *p, Byte *dest, SizeT *destLen, - const Byte *src, SizeT *srcLen, int srcWasFinished, - ECoderFinishMode finishMode, ECoderStatus *status); typedef enum { @@ -191,20 +236,21 @@ typedef enum XZ_STATE_BLOCK_FOOTER } EXzState; + typedef struct { EXzState state; - UInt32 pos; + unsigned pos; unsigned alignPos; unsigned indexPreSize; CXzStreamFlags streamFlags; - UInt32 blockHeaderSize; + unsigned blockHeaderSize; UInt64 packSize; UInt64 unpackSize; - UInt64 numBlocks; + UInt64 numBlocks; // number of finished blocks in current stream UInt64 indexSize; UInt64 indexPos; UInt64 padSize; @@ -218,14 +264,72 @@ typedef struct CXzBlock block; CXzCheck check; CSha256 sha; - Byte shaDigest[SHA256_DIGEST_SIZE]; - Byte buf[XZ_BLOCK_HEADER_SIZE_MAX]; + + BoolInt parseMode; + BoolInt headerParsedOk; + BoolInt decodeToStreamSignature; + unsigned decodeOnlyOneBlock; + + Byte *outBuf; + size_t outBufSize; + size_t outDataWritten; // the size of data in (outBuf) that were fully unpacked + + UInt32 shaDigest32[SHA256_DIGEST_SIZE / 4]; + Byte buf[XZ_BLOCK_HEADER_SIZE_MAX]; // it must be aligned for 4-bytes } CXzUnpacker; -void XzUnpacker_Construct(CXzUnpacker *p, ISzAlloc *alloc); +/* alloc : aligned for cache line allocation is better */ +void XzUnpacker_Construct(CXzUnpacker *p, ISzAllocPtr alloc); void XzUnpacker_Init(CXzUnpacker *p); +void XzUnpacker_SetOutBuf(CXzUnpacker *p, Byte *outBuf, size_t outBufSize); void XzUnpacker_Free(CXzUnpacker *p); +/* + XzUnpacker + The sequence for decoding functions: + { + XzUnpacker_Construct() + [Decoding_Calls] + XzUnpacker_Free() + } + + [Decoding_Calls] + + There are 3 types of interfaces for [Decoding_Calls] calls: + + Interface-1 : Partial output buffers: + { + XzUnpacker_Init() + for() + { + XzUnpacker_Code(); + } + XzUnpacker_IsStreamWasFinished() + } + + Interface-2 : Direct output buffer: + Use it, if you know exact size of decoded data, and you need + whole xz unpacked data in one output buffer. + xz unpacker doesn't allocate additional buffer for lzma2 dictionary in that mode. + { + XzUnpacker_Init() + XzUnpacker_SetOutBufMode(); // to set output buffer and size + for() + { + XzUnpacker_Code(); // (dest = NULL) in XzUnpacker_Code() + } + XzUnpacker_IsStreamWasFinished() + } + + Interface-3 : Direct output buffer : One call full decoding + It unpacks whole input buffer to output buffer in one call. + It uses Interface-2 internally. + { + XzUnpacker_CodeFull() + XzUnpacker_IsStreamWasFinished() + } +*/ + /* finishMode: It has meaning only if the decoding reaches output limit (*destLen). @@ -236,8 +340,12 @@ void XzUnpacker_Free(CXzUnpacker *p); SZ_OK status: CODER_STATUS_NOT_FINISHED, - CODER_STATUS_NEEDS_MORE_INPUT - maybe there are more xz streams, - call XzUnpacker_IsStreamWasFinished to check that current stream was finished + CODER_STATUS_NEEDS_MORE_INPUT - the decoder can return it in two cases: + 1) it needs more input data to finish current xz stream + 2) xz stream was finished successfully. But the decoder supports multiple + concatented xz streams. So it expects more input data for new xz streams. + Call XzUnpacker_IsStreamWasFinished() to check that latest xz stream was finished successfully. + SZ_ERROR_MEM - Memory allocation error SZ_ERROR_DATA - Data error SZ_ERROR_UNSUPPORTED - Unsupported method or method properties @@ -255,20 +363,179 @@ void XzUnpacker_Free(CXzUnpacker *p); SRes XzUnpacker_Code(CXzUnpacker *p, Byte *dest, SizeT *destLen, - const Byte *src, SizeT *srcLen, ECoderFinishMode finishMode, - ECoderStatus *status); + const Byte *src, SizeT *srcLen, int srcFinished, + ECoderFinishMode finishMode, ECoderStatus *status); + +SRes XzUnpacker_CodeFull(CXzUnpacker *p, Byte *dest, SizeT *destLen, + const Byte *src, SizeT *srcLen, + ECoderFinishMode finishMode, ECoderStatus *status); + +/* +If you decode full xz stream(s), then you can call XzUnpacker_IsStreamWasFinished() +after successful XzUnpacker_CodeFull() or after last call of XzUnpacker_Code(). +*/ -Bool XzUnpacker_IsStreamWasFinished(CXzUnpacker *p); +BoolInt XzUnpacker_IsStreamWasFinished(const CXzUnpacker *p); /* -Call XzUnpacker_GetExtraSize after XzUnpacker_Code function to detect real size of -xz stream in two cases: -XzUnpacker_Code() returns: +XzUnpacker_GetExtraSize() returns then number of unconfirmed bytes, + if it's in (XZ_STATE_STREAM_HEADER) state or in (XZ_STATE_STREAM_PADDING) state. +These bytes can be some data after xz archive, or +it can be start of new xz stream. + +Call XzUnpacker_GetExtraSize() after XzUnpacker_Code() function to detect real size of +xz stream in two cases, if XzUnpacker_Code() returns: res == SZ_OK && status == CODER_STATUS_NEEDS_MORE_INPUT res == SZ_ERROR_NO_ARCHIVE */ -UInt64 XzUnpacker_GetExtraSize(CXzUnpacker *p); +UInt64 XzUnpacker_GetExtraSize(const CXzUnpacker *p); + + +/* + for random block decoding: + XzUnpacker_Init(); + set CXzUnpacker::streamFlags + XzUnpacker_PrepareToRandomBlockDecoding() + loop + { + XzUnpacker_Code() + XzUnpacker_IsBlockFinished() + } +*/ + +void XzUnpacker_PrepareToRandomBlockDecoding(CXzUnpacker *p); +BoolInt XzUnpacker_IsBlockFinished(const CXzUnpacker *p); + +#define XzUnpacker_GetPackSizeForIndex(p) ((p)->packSize + (p)->blockHeaderSize + XzFlags_GetCheckSize((p)->streamFlags)) + + + + + + +/* ---- Single-Thread and Multi-Thread xz Decoding with Input/Output Streams ---- */ + +/* + if (CXzDecMtProps::numThreads > 1), the decoder can try to use + Multi-Threading. The decoder analyses xz block header, and if + there are pack size and unpack size values stored in xz block header, + the decoder reads compressed data of block to internal buffers, + and then it can start parallel decoding, if there are another blocks. + The decoder can switch back to Single-Thread decoding after some conditions. + + The sequence of calls for xz decoding with in/out Streams: + { + XzDecMt_Create() + XzDecMtProps_Init(XzDecMtProps) to set default values of properties + // then you can change some XzDecMtProps parameters with required values + // here you can set the number of threads and (memUseMax) - the maximum + Memory usage for multithreading decoding. + for() + { + XzDecMt_Decode() // one call per one file + } + XzDecMt_Destroy() + } +*/ + + +typedef struct +{ + size_t inBufSize_ST; // size of input buffer for Single-Thread decoding + size_t outStep_ST; // size of output buffer for Single-Thread decoding + BoolInt ignoreErrors; // if set to 1, the decoder can ignore some errors and it skips broken parts of data. + + #ifndef Z7_ST + unsigned numThreads; // the number of threads for Multi-Thread decoding. if (umThreads == 1) it will use Single-thread decoding + size_t inBufSize_MT; // size of small input data buffers for Multi-Thread decoding. Big number of such small buffers can be created + size_t memUseMax; // the limit of total memory usage for Multi-Thread decoding. + // it's recommended to set (memUseMax) manually to value that is smaller of total size of RAM in computer. + #endif +} CXzDecMtProps; + +void XzDecMtProps_Init(CXzDecMtProps *p); + +typedef struct CXzDecMt CXzDecMt; +typedef CXzDecMt * CXzDecMtHandle; +// Z7_DECLARE_HANDLE(CXzDecMtHandle) + +/* + alloc : XzDecMt uses CAlignOffsetAlloc internally for addresses allocated by (alloc). + allocMid : for big allocations, aligned allocation is better +*/ + +CXzDecMtHandle XzDecMt_Create(ISzAllocPtr alloc, ISzAllocPtr allocMid); +void XzDecMt_Destroy(CXzDecMtHandle p); + + +typedef struct +{ + Byte UnpackSize_Defined; + Byte NumStreams_Defined; + Byte NumBlocks_Defined; + + Byte DataAfterEnd; // there are some additional data after good xz streams, and that data is not new xz stream. + Byte DecodingTruncated; // Decoding was Truncated, we need only partial output data + + UInt64 InSize; // pack size processed. That value doesn't include the data after + // end of xz stream, if that data was not correct + UInt64 OutSize; + + UInt64 NumStreams; + UInt64 NumBlocks; + + SRes DecodeRes; // the error code of xz streams data decoding + SRes ReadRes; // error code from ISeqInStream:Read() + SRes ProgressRes; // error code from ICompressProgress:Progress() + + SRes CombinedRes; // Combined result error code that shows main rusult + // = S_OK, if there is no error. + // but check also (DataAfterEnd) that can show additional minor errors. + + SRes CombinedRes_Type; // = SZ_ERROR_READ, if error from ISeqInStream + // = SZ_ERROR_PROGRESS, if error from ICompressProgress + // = SZ_ERROR_WRITE, if error from ISeqOutStream + // = SZ_ERROR_* codes for decoding +} CXzStatInfo; + +void XzStatInfo_Clear(CXzStatInfo *p); + +/* + +XzDecMt_Decode() +SRes: it's combined decoding result. It also is equal to stat->CombinedRes. + + SZ_OK - no error + check also output value in (stat->DataAfterEnd) + that can show additional possible error + + SZ_ERROR_MEM - Memory allocation error + SZ_ERROR_NO_ARCHIVE - is not xz archive + SZ_ERROR_ARCHIVE - Headers error + SZ_ERROR_DATA - Data Error + SZ_ERROR_UNSUPPORTED - Unsupported method or method properties + SZ_ERROR_CRC - CRC Error + SZ_ERROR_INPUT_EOF - it needs more input data + SZ_ERROR_WRITE - ISeqOutStream error + (SZ_ERROR_READ) - ISeqInStream errors + (SZ_ERROR_PROGRESS) - ICompressProgress errors + // SZ_ERROR_THREAD - error in multi-threading functions + MY_SRes_HRESULT_FROM_WRes(WRes_error) - error in multi-threading function +*/ + +SRes XzDecMt_Decode(CXzDecMtHandle p, + const CXzDecMtProps *props, + const UInt64 *outDataSize, // NULL means undefined + int finishMode, // 0 - partial unpacking is allowed, 1 - xz stream(s) must be finished + ISeqOutStreamPtr outStream, + // Byte *outBuf, size_t *outBufSize, + ISeqInStreamPtr inStream, + // const Byte *inData, size_t inDataSize, + CXzStatInfo *stat, // out: decoding results and statistics + int *isMT, // out: 0 means that ST (Single-Thread) version was used + // 1 means that MT (Multi-Thread) version was used + ICompressProgressPtr progress); EXTERN_C_END diff --git a/src/plugins/7zip/7za/c/XzCrc64.c b/src/plugins/7zip/7za/c/XzCrc64.c index e2da63b6..94fc1afb 100644 --- a/src/plugins/7zip/7za/c/XzCrc64.c +++ b/src/plugins/7zip/7za/c/XzCrc64.c @@ -1,5 +1,5 @@ /* XzCrc64.c -- CRC64 calculation -2015-03-01 : Igor Pavlov : Public domain */ +2023-12-08 : Igor Pavlov : Public domain */ #include "Precomp.h" @@ -8,79 +8,133 @@ #define kCrc64Poly UINT64_CONST(0xC96C5795D7870F42) -#ifdef MY_CPU_LE - #define CRC_NUM_TABLES 4 +// for debug only : define Z7_CRC64_DEBUG_BE to test big-endian code in little-endian cpu +// #define Z7_CRC64_DEBUG_BE +#ifdef Z7_CRC64_DEBUG_BE +#undef MY_CPU_LE +#define MY_CPU_BE +#endif + +#ifdef Z7_CRC64_NUM_TABLES + #define Z7_CRC64_NUM_TABLES_USE Z7_CRC64_NUM_TABLES #else - #define CRC_NUM_TABLES 5 - #define CRC_UINT64_SWAP(v) \ - ((v >> 56) \ - | ((v >> 40) & ((UInt64)0xFF << 8)) \ - | ((v >> 24) & ((UInt64)0xFF << 16)) \ - | ((v >> 8) & ((UInt64)0xFF << 24)) \ - | ((v << 8) & ((UInt64)0xFF << 32)) \ - | ((v << 24) & ((UInt64)0xFF << 40)) \ - | ((v << 40) & ((UInt64)0xFF << 48)) \ - | ((v << 56))) - - UInt64 MY_FAST_CALL XzCrc64UpdateT1_BeT4(UInt64 v, const void *data, size_t size, const UInt64 *table); + #define Z7_CRC64_NUM_TABLES_USE 12 +#endif + +#if Z7_CRC64_NUM_TABLES_USE < 1 + #error Stop_Compiling_Bad_Z7_CRC_NUM_TABLES #endif + +#if Z7_CRC64_NUM_TABLES_USE != 1 + #ifndef MY_CPU_BE - UInt64 MY_FAST_CALL XzCrc64UpdateT4(UInt64 v, const void *data, size_t size, const UInt64 *table); + #define FUNC_NAME_LE_2(s) XzCrc64UpdateT ## s + #define FUNC_NAME_LE_1(s) FUNC_NAME_LE_2(s) + #define FUNC_NAME_LE FUNC_NAME_LE_1(Z7_CRC64_NUM_TABLES_USE) + UInt64 Z7_FASTCALL FUNC_NAME_LE (UInt64 v, const void *data, size_t size, const UInt64 *table); +#endif +#ifndef MY_CPU_LE + #define FUNC_NAME_BE_2(s) XzCrc64UpdateBeT ## s + #define FUNC_NAME_BE_1(s) FUNC_NAME_BE_2(s) + #define FUNC_NAME_BE FUNC_NAME_BE_1(Z7_CRC64_NUM_TABLES_USE) + UInt64 Z7_FASTCALL FUNC_NAME_BE (UInt64 v, const void *data, size_t size, const UInt64 *table); #endif -typedef UInt64 (MY_FAST_CALL *CRC_FUNC)(UInt64 v, const void *data, size_t size, const UInt64 *table); +#if defined(MY_CPU_LE) + #define FUNC_REF FUNC_NAME_LE +#elif defined(MY_CPU_BE) + #define FUNC_REF FUNC_NAME_BE +#else + #define FUNC_REF g_Crc64Update + static UInt64 (Z7_FASTCALL *FUNC_REF)(UInt64 v, const void *data, size_t size, const UInt64 *table); +#endif -static CRC_FUNC g_Crc64Update; -UInt64 g_Crc64Table[256 * CRC_NUM_TABLES]; +#endif + + +MY_ALIGN(64) +static UInt64 g_Crc64Table[256 * Z7_CRC64_NUM_TABLES_USE]; -UInt64 MY_FAST_CALL Crc64Update(UInt64 v, const void *data, size_t size) -{ - return g_Crc64Update(v, data, size, g_Crc64Table); -} -UInt64 MY_FAST_CALL Crc64Calc(const void *data, size_t size) +UInt64 Z7_FASTCALL Crc64Update(UInt64 v, const void *data, size_t size) { - return g_Crc64Update(CRC64_INIT_VAL, data, size, g_Crc64Table) ^ CRC64_INIT_VAL; +#if Z7_CRC64_NUM_TABLES_USE == 1 + #define CRC64_UPDATE_BYTE_2(crc, b) (table[((crc) ^ (b)) & 0xFF] ^ ((crc) >> 8)) + const UInt64 *table = g_Crc64Table; + const Byte *p = (const Byte *)data; + const Byte *lim = p + size; + for (; p != lim; p++) + v = CRC64_UPDATE_BYTE_2(v, *p); + return v; + #undef CRC64_UPDATE_BYTE_2 +#else + return FUNC_REF (v, data, size, g_Crc64Table); +#endif } -void MY_FAST_CALL Crc64GenerateTable() + +Z7_NO_INLINE +void Z7_FASTCALL Crc64GenerateTable(void) { - UInt32 i; + unsigned i; for (i = 0; i < 256; i++) { UInt64 r = i; unsigned j; for (j = 0; j < 8; j++) - r = (r >> 1) ^ (kCrc64Poly & ~((r & 1) - 1)); + r = (r >> 1) ^ (kCrc64Poly & ((UInt64)0 - (r & 1))); g_Crc64Table[i] = r; } - for (; i < 256 * CRC_NUM_TABLES; i++) + +#if Z7_CRC64_NUM_TABLES_USE != 1 +#if 1 || 1 && defined(MY_CPU_X86) // low register count + for (i = 0; i < 256 * (Z7_CRC64_NUM_TABLES_USE - 1); i++) { - UInt64 r = g_Crc64Table[i - 256]; - g_Crc64Table[i] = g_Crc64Table[r & 0xFF] ^ (r >> 8); + const UInt64 r0 = g_Crc64Table[(size_t)i]; + g_Crc64Table[(size_t)i + 256] = g_Crc64Table[(Byte)r0] ^ (r0 >> 8); } - - #ifdef MY_CPU_LE - - g_Crc64Update = XzCrc64UpdateT4; +#else + for (i = 0; i < 256 * (Z7_CRC64_NUM_TABLES_USE - 1); i += 2) + { + UInt64 r0 = g_Crc64Table[(size_t)(i) ]; + UInt64 r1 = g_Crc64Table[(size_t)(i) + 1]; + r0 = g_Crc64Table[(Byte)r0] ^ (r0 >> 8); + r1 = g_Crc64Table[(Byte)r1] ^ (r1 >> 8); + g_Crc64Table[(size_t)i + 256 ] = r0; + g_Crc64Table[(size_t)i + 256 + 1] = r1; + } +#endif - #else +#ifndef MY_CPU_LE { - #ifndef MY_CPU_BE +#ifndef MY_CPU_BE UInt32 k = 1; if (*(const Byte *)&k == 1) - g_Crc64Update = XzCrc64UpdateT4; + FUNC_REF = FUNC_NAME_LE; else - #endif +#endif { - for (i = 256 * CRC_NUM_TABLES - 1; i >= 256; i--) +#ifndef MY_CPU_BE + FUNC_REF = FUNC_NAME_BE; +#endif + for (i = 0; i < 256 * Z7_CRC64_NUM_TABLES_USE; i++) { - UInt64 x = g_Crc64Table[i - 256]; - g_Crc64Table[i] = CRC_UINT64_SWAP(x); + const UInt64 x = g_Crc64Table[i]; + g_Crc64Table[i] = Z7_BSWAP64(x); } - g_Crc64Update = XzCrc64UpdateT1_BeT4; } } - #endif +#endif // ndef MY_CPU_LE +#endif // Z7_CRC64_NUM_TABLES_USE != 1 } + +#undef kCrc64Poly +#undef Z7_CRC64_NUM_TABLES_USE +#undef FUNC_REF +#undef FUNC_NAME_LE_2 +#undef FUNC_NAME_LE_1 +#undef FUNC_NAME_LE +#undef FUNC_NAME_BE_2 +#undef FUNC_NAME_BE_1 +#undef FUNC_NAME_BE diff --git a/src/plugins/7zip/7za/c/XzCrc64.h b/src/plugins/7zip/7za/c/XzCrc64.h index 08dbc330..04f8153d 100644 --- a/src/plugins/7zip/7za/c/XzCrc64.h +++ b/src/plugins/7zip/7za/c/XzCrc64.h @@ -1,8 +1,8 @@ /* XzCrc64.h -- CRC64 calculation -2013-01-18 : Igor Pavlov : Public domain */ +2023-12-08 : Igor Pavlov : Public domain */ -#ifndef __XZ_CRC64_H -#define __XZ_CRC64_H +#ifndef ZIP7_INC_XZ_CRC64_H +#define ZIP7_INC_XZ_CRC64_H #include @@ -10,16 +10,16 @@ EXTERN_C_BEGIN -extern UInt64 g_Crc64Table[]; +// extern UInt64 g_Crc64Table[]; -void MY_FAST_CALL Crc64GenerateTable(void); +void Z7_FASTCALL Crc64GenerateTable(void); #define CRC64_INIT_VAL UINT64_CONST(0xFFFFFFFFFFFFFFFF) #define CRC64_GET_DIGEST(crc) ((crc) ^ CRC64_INIT_VAL) -#define CRC64_UPDATE_BYTE(crc, b) (g_Crc64Table[((crc) ^ (b)) & 0xFF] ^ ((crc) >> 8)) +// #define CRC64_UPDATE_BYTE(crc, b) (g_Crc64Table[((crc) ^ (b)) & 0xFF] ^ ((crc) >> 8)) -UInt64 MY_FAST_CALL Crc64Update(UInt64 crc, const void *data, size_t size); -UInt64 MY_FAST_CALL Crc64Calc(const void *data, size_t size); +UInt64 Z7_FASTCALL Crc64Update(UInt64 crc, const void *data, size_t size); +// UInt64 Z7_FASTCALL Crc64Calc(const void *data, size_t size); EXTERN_C_END diff --git a/src/plugins/7zip/7za/c/XzCrc64Opt.c b/src/plugins/7zip/7za/c/XzCrc64Opt.c index c4fde65a..6eea4a3b 100644 --- a/src/plugins/7zip/7za/c/XzCrc64Opt.c +++ b/src/plugins/7zip/7za/c/XzCrc64Opt.c @@ -1,69 +1,261 @@ -/* XzCrc64Opt.c -- CRC64 calculation -2015-03-01 : Igor Pavlov : Public domain */ +/* XzCrc64Opt.c -- CRC64 calculation (optimized functions) +: Igor Pavlov : Public domain */ #include "Precomp.h" #include "CpuArch.h" +#if !defined(Z7_CRC64_NUM_TABLES) || Z7_CRC64_NUM_TABLES > 1 + +// for debug only : define Z7_CRC64_DEBUG_BE to test big-endian code in little-endian cpu +// #define Z7_CRC64_DEBUG_BE +#ifdef Z7_CRC64_DEBUG_BE +#undef MY_CPU_LE +#define MY_CPU_BE +#endif + +#if defined(MY_CPU_64BIT) +#define Z7_CRC64_USE_64BIT +#endif + +// the value Z7_CRC64_NUM_TABLES_USE must be defined to same value as in XzCrc64.c +#ifdef Z7_CRC64_NUM_TABLES +#define Z7_CRC64_NUM_TABLES_USE Z7_CRC64_NUM_TABLES +#else +#define Z7_CRC64_NUM_TABLES_USE 12 +#endif + +#if Z7_CRC64_NUM_TABLES_USE % 4 || \ + Z7_CRC64_NUM_TABLES_USE < 4 || \ + Z7_CRC64_NUM_TABLES_USE > 4 * 4 + #error Stop_Compiling_Bad_CRC64_NUM_TABLES +#endif + + #ifndef MY_CPU_BE -#define CRC_UPDATE_BYTE_2(crc, b) (table[((crc) ^ (b)) & 0xFF] ^ ((crc) >> 8)) +#define CRC64_UPDATE_BYTE_2(crc, b) (table[((crc) ^ (b)) & 0xFF] ^ ((crc) >> 8)) + +#if defined(Z7_CRC64_USE_64BIT) && (Z7_CRC64_NUM_TABLES_USE % 8 == 0) + +#define Q64LE(n, d) \ + ( (table + ((n) * 8 + 7) * 0x100)[((d) ) & 0xFF] \ + ^ (table + ((n) * 8 + 6) * 0x100)[((d) >> 1 * 8) & 0xFF] \ + ^ (table + ((n) * 8 + 5) * 0x100)[((d) >> 2 * 8) & 0xFF] \ + ^ (table + ((n) * 8 + 4) * 0x100)[((d) >> 3 * 8) & 0xFF] \ + ^ (table + ((n) * 8 + 3) * 0x100)[((d) >> 4 * 8) & 0xFF] \ + ^ (table + ((n) * 8 + 2) * 0x100)[((d) >> 5 * 8) & 0xFF] \ + ^ (table + ((n) * 8 + 1) * 0x100)[((d) >> 6 * 8) & 0xFF] \ + ^ (table + ((n) * 8 + 0) * 0x100)[((d) >> 7 * 8)] ) + +#define R64(a) *((const UInt64 *)(const void *)p + (a)) + +#else + +#define Q32LE(n, d) \ + ( (table + ((n) * 4 + 3) * 0x100)[((d) ) & 0xFF] \ + ^ (table + ((n) * 4 + 2) * 0x100)[((d) >> 1 * 8) & 0xFF] \ + ^ (table + ((n) * 4 + 1) * 0x100)[((d) >> 2 * 8) & 0xFF] \ + ^ (table + ((n) * 4 + 0) * 0x100)[((d) >> 3 * 8)] ) + +#define R32(a) *((const UInt32 *)(const void *)p + (a)) + +#endif + + +#define CRC64_FUNC_PRE_LE2(step) \ +UInt64 Z7_FASTCALL XzCrc64UpdateT ## step (UInt64 v, const void *data, size_t size, const UInt64 *table) + +#define CRC64_FUNC_PRE_LE(step) \ + CRC64_FUNC_PRE_LE2(step); \ + CRC64_FUNC_PRE_LE2(step) -UInt64 MY_FAST_CALL XzCrc64UpdateT4(UInt64 v, const void *data, size_t size, const UInt64 *table) +CRC64_FUNC_PRE_LE(Z7_CRC64_NUM_TABLES_USE) { const Byte *p = (const Byte *)data; - for (; size > 0 && ((unsigned)(ptrdiff_t)p & 3) != 0; size--, p++) - v = CRC_UPDATE_BYTE_2(v, *p); - for (; size >= 4; size -= 4, p += 4) + const Byte *lim; + for (; size && ((unsigned)(ptrdiff_t)p & (7 - (Z7_CRC64_NUM_TABLES_USE & 4))) != 0; size--, p++) + v = CRC64_UPDATE_BYTE_2(v, *p); + lim = p + size; + if (size >= Z7_CRC64_NUM_TABLES_USE) { - UInt32 d = (UInt32)v ^ *(const UInt32 *)p; - v = (v >> 32) - ^ table[0x300 + ((d ) & 0xFF)] - ^ table[0x200 + ((d >> 8) & 0xFF)] - ^ table[0x100 + ((d >> 16) & 0xFF)] - ^ table[0x000 + ((d >> 24))]; + lim -= Z7_CRC64_NUM_TABLES_USE; + do + { +#if Z7_CRC64_NUM_TABLES_USE == 4 + const UInt32 d = (UInt32)v ^ R32(0); + v = (v >> 32) ^ Q32LE(0, d); +#elif Z7_CRC64_NUM_TABLES_USE == 8 +#ifdef Z7_CRC64_USE_64BIT + v ^= R64(0); + v = Q64LE(0, v); +#else + UInt32 v0, v1; + v0 = (UInt32)v ^ R32(0); + v1 = (UInt32)(v >> 32) ^ R32(1); + v = Q32LE(1, v0) ^ Q32LE(0, v1); +#endif +#elif Z7_CRC64_NUM_TABLES_USE == 12 + UInt32 w; + UInt32 v0, v1; + v0 = (UInt32)v ^ R32(0); + v1 = (UInt32)(v >> 32) ^ R32(1); + w = R32(2); + v = Q32LE(0, w); + v ^= Q32LE(2, v0) ^ Q32LE(1, v1); +#elif Z7_CRC64_NUM_TABLES_USE == 16 +#ifdef Z7_CRC64_USE_64BIT + UInt64 w; + UInt64 x; + w = R64(1); x = Q64LE(0, w); + v ^= R64(0); v = x ^ Q64LE(1, v); +#else + UInt32 v0, v1; + UInt32 r0, r1; + v0 = (UInt32)v ^ R32(0); + v1 = (UInt32)(v >> 32) ^ R32(1); + r0 = R32(2); + r1 = R32(3); + v = Q32LE(1, r0) ^ Q32LE(0, r1); + v ^= Q32LE(3, v0) ^ Q32LE(2, v1); +#endif +#else +#error Stop_Compiling_Bad_CRC64_NUM_TABLES +#endif + p += Z7_CRC64_NUM_TABLES_USE; + } + while (p <= lim); + lim += Z7_CRC64_NUM_TABLES_USE; } - for (; size > 0; size--, p++) - v = CRC_UPDATE_BYTE_2(v, *p); + for (; p < lim; p++) + v = CRC64_UPDATE_BYTE_2(v, *p); return v; } +#undef CRC64_UPDATE_BYTE_2 +#undef R32 +#undef R64 +#undef Q32LE +#undef Q64LE +#undef CRC64_FUNC_PRE_LE +#undef CRC64_FUNC_PRE_LE2 + #endif + + #ifndef MY_CPU_LE -#define CRC_UINT64_SWAP(v) \ - ((v >> 56) \ - | ((v >> 40) & ((UInt64)0xFF << 8)) \ - | ((v >> 24) & ((UInt64)0xFF << 16)) \ - | ((v >> 8) & ((UInt64)0xFF << 24)) \ - | ((v << 8) & ((UInt64)0xFF << 32)) \ - | ((v << 24) & ((UInt64)0xFF << 40)) \ - | ((v << 40) & ((UInt64)0xFF << 48)) \ - | ((v << 56))) +#define CRC64_UPDATE_BYTE_2_BE(crc, b) (table[((crc) >> 56) ^ (b)] ^ ((crc) << 8)) + +#if defined(Z7_CRC64_USE_64BIT) && (Z7_CRC64_NUM_TABLES_USE % 8 == 0) + +#define Q64BE(n, d) \ + ( (table + ((n) * 8 + 0) * 0x100)[(Byte)(d)] \ + ^ (table + ((n) * 8 + 1) * 0x100)[((d) >> 1 * 8) & 0xFF] \ + ^ (table + ((n) * 8 + 2) * 0x100)[((d) >> 2 * 8) & 0xFF] \ + ^ (table + ((n) * 8 + 3) * 0x100)[((d) >> 3 * 8) & 0xFF] \ + ^ (table + ((n) * 8 + 4) * 0x100)[((d) >> 4 * 8) & 0xFF] \ + ^ (table + ((n) * 8 + 5) * 0x100)[((d) >> 5 * 8) & 0xFF] \ + ^ (table + ((n) * 8 + 6) * 0x100)[((d) >> 6 * 8) & 0xFF] \ + ^ (table + ((n) * 8 + 7) * 0x100)[((d) >> 7 * 8)] ) + +#ifdef Z7_CRC64_DEBUG_BE + #define R64BE(a) GetBe64a((const UInt64 *)(const void *)p + (a)) +#else + #define R64BE(a) *((const UInt64 *)(const void *)p + (a)) +#endif + +#else -#define CRC_UPDATE_BYTE_2_BE(crc, b) (table[(Byte)((crc) >> 56) ^ (b)] ^ ((crc) << 8)) +#define Q32BE(n, d) \ + ( (table + ((n) * 4 + 0) * 0x100)[(Byte)(d)] \ + ^ (table + ((n) * 4 + 1) * 0x100)[((d) >> 1 * 8) & 0xFF] \ + ^ (table + ((n) * 4 + 2) * 0x100)[((d) >> 2 * 8) & 0xFF] \ + ^ (table + ((n) * 4 + 3) * 0x100)[((d) >> 3 * 8)] ) -UInt64 MY_FAST_CALL XzCrc64UpdateT1_BeT4(UInt64 v, const void *data, size_t size, const UInt64 *table) +#ifdef Z7_CRC64_DEBUG_BE + #define R32BE(a) GetBe32a((const UInt32 *)(const void *)p + (a)) +#else + #define R32BE(a) *((const UInt32 *)(const void *)p + (a)) +#endif + +#endif + +#define CRC64_FUNC_PRE_BE2(step) \ +UInt64 Z7_FASTCALL XzCrc64UpdateBeT ## step (UInt64 v, const void *data, size_t size, const UInt64 *table) + +#define CRC64_FUNC_PRE_BE(step) \ + CRC64_FUNC_PRE_BE2(step); \ + CRC64_FUNC_PRE_BE2(step) + +CRC64_FUNC_PRE_BE(Z7_CRC64_NUM_TABLES_USE) { const Byte *p = (const Byte *)data; - table += 0x100; - v = CRC_UINT64_SWAP(v); - for (; size > 0 && ((unsigned)(ptrdiff_t)p & 3) != 0; size--, p++) - v = CRC_UPDATE_BYTE_2_BE(v, *p); - for (; size >= 4; size -= 4, p += 4) + const Byte *lim; + v = Z7_BSWAP64(v); + for (; size && ((unsigned)(ptrdiff_t)p & (7 - (Z7_CRC64_NUM_TABLES_USE & 4))) != 0; size--, p++) + v = CRC64_UPDATE_BYTE_2_BE(v, *p); + lim = p + size; + if (size >= Z7_CRC64_NUM_TABLES_USE) { - UInt32 d = (UInt32)(v >> 32) ^ *(const UInt32 *)p; - v = (v << 32) - ^ table[0x000 + ((d ) & 0xFF)] - ^ table[0x100 + ((d >> 8) & 0xFF)] - ^ table[0x200 + ((d >> 16) & 0xFF)] - ^ table[0x300 + ((d >> 24))]; + lim -= Z7_CRC64_NUM_TABLES_USE; + do + { +#if Z7_CRC64_NUM_TABLES_USE == 4 + const UInt32 d = (UInt32)(v >> 32) ^ R32BE(0); + v = (v << 32) ^ Q32BE(0, d); +#elif Z7_CRC64_NUM_TABLES_USE == 12 + const UInt32 d1 = (UInt32)(v >> 32) ^ R32BE(0); + const UInt32 d0 = (UInt32)(v ) ^ R32BE(1); + const UInt32 w = R32BE(2); + v = Q32BE(0, w); + v ^= Q32BE(2, d1) ^ Q32BE(1, d0); + +#elif Z7_CRC64_NUM_TABLES_USE == 8 + #ifdef Z7_CRC64_USE_64BIT + v ^= R64BE(0); + v = Q64BE(0, v); + #else + const UInt32 d1 = (UInt32)(v >> 32) ^ R32BE(0); + const UInt32 d0 = (UInt32)(v ) ^ R32BE(1); + v = Q32BE(1, d1) ^ Q32BE(0, d0); + #endif +#elif Z7_CRC64_NUM_TABLES_USE == 16 + #ifdef Z7_CRC64_USE_64BIT + const UInt64 w = R64BE(1); + v ^= R64BE(0); + v = Q64BE(0, w) ^ Q64BE(1, v); + #else + const UInt32 d1 = (UInt32)(v >> 32) ^ R32BE(0); + const UInt32 d0 = (UInt32)(v ) ^ R32BE(1); + const UInt32 w1 = R32BE(2); + const UInt32 w0 = R32BE(3); + v = Q32BE(1, w1) ^ Q32BE(0, w0); + v ^= Q32BE(3, d1) ^ Q32BE(2, d0); + #endif +#else +#error Stop_Compiling_Bad_CRC64_NUM_TABLES +#endif + p += Z7_CRC64_NUM_TABLES_USE; + } + while (p <= lim); + lim += Z7_CRC64_NUM_TABLES_USE; } - for (; size > 0; size--, p++) - v = CRC_UPDATE_BYTE_2_BE(v, *p); - return CRC_UINT64_SWAP(v); + for (; p < lim; p++) + v = CRC64_UPDATE_BYTE_2_BE(v, *p); + return Z7_BSWAP64(v); } +#undef CRC64_UPDATE_BYTE_2_BE +#undef R32BE +#undef R64BE +#undef Q32BE +#undef Q64BE +#undef CRC64_FUNC_PRE_BE +#undef CRC64_FUNC_PRE_BE2 + +#endif +#undef Z7_CRC64_NUM_TABLES_USE #endif diff --git a/src/plugins/7zip/7za/c/XzDec.c b/src/plugins/7zip/7za/c/XzDec.c index 1edc0245..e4ae1448 100644 --- a/src/plugins/7zip/7za/c/XzDec.c +++ b/src/plugins/7zip/7za/c/XzDec.c @@ -1,14 +1,33 @@ /* XzDec.c -- Xz Decode -2015-11-09 : Igor Pavlov : Public domain */ +: Igor Pavlov : Public domain */ #include "Precomp.h" +// #include + +// #define XZ_DUMP + /* #define XZ_DUMP */ #ifdef XZ_DUMP #include #endif +// #define SHOW_DEBUG_INFO + +#ifdef SHOW_DEBUG_INFO +#include +#endif + +#ifdef SHOW_DEBUG_INFO +#define PRF(x) x +#else +#define PRF(x) +#endif + +#define PRF_STR(s) PRF(printf("\n" s "\n");) +#define PRF_STR_INT(s, d) PRF(printf("\n" s " %d\n", (unsigned)d);) + #include #include @@ -19,16 +38,18 @@ #include "Delta.h" #include "Lzma2Dec.h" +// #define USE_SUBBLOCK + #ifdef USE_SUBBLOCK #include "Bcj3Dec.c" -#include "SbDec.c" +#include "SbDec.h" #endif #include "Xz.h" #define XZ_CHECK_SIZE_MAX 64 -#define CODER_BUF_SIZE (1 << 17) +#define CODER_BUF_SIZE ((size_t)1 << 17) unsigned Xz_ReadVarInt(const Byte *p, size_t maxSize, UInt64 *value) { @@ -38,7 +59,7 @@ unsigned Xz_ReadVarInt(const Byte *p, size_t maxSize, UInt64 *value) for (i = 0; i < limit;) { - Byte b = p[i]; + const unsigned b = p[i]; *value |= (UInt64)(b & 0x7F) << (7 * i++); if ((b & 0x80) == 0) return (b == 0 && i != 1) ? 0 : i; @@ -46,7 +67,8 @@ unsigned Xz_ReadVarInt(const Byte *p, size_t maxSize, UInt64 *value) return 0; } -/* ---------- BraState ---------- */ + +/* ---------- XzBcFilterState ---------- */ #define BRA_BUF_SIZE (1 << 14) @@ -55,54 +77,60 @@ typedef struct size_t bufPos; size_t bufConv; size_t bufTotal; + Byte *buf; // must be aligned for 4 bytes + Xz_Func_BcFilterStateBase_Filter filter_func; + // int encodeMode; + CXzBcFilterStateBase base; + // Byte buf[BRA_BUF_SIZE]; +} CXzBcFilterState; - UInt32 methodId; - int encodeMode; - UInt32 delta; - UInt32 ip; - UInt32 x86State; - Byte deltaState[DELTA_STATE_SIZE]; - - Byte buf[BRA_BUF_SIZE]; -} CBraState; -static void BraState_Free(void *pp, ISzAlloc *alloc) +static void XzBcFilterState_Free(void *pp, ISzAllocPtr alloc) { - alloc->Free(alloc, pp); + if (pp) + { + CXzBcFilterState *p = ((CXzBcFilterState *)pp); + ISzAlloc_Free(alloc, p->buf); + ISzAlloc_Free(alloc, pp); + } } -static SRes BraState_SetProps(void *pp, const Byte *props, size_t propSize, ISzAlloc *alloc) + +static SRes XzBcFilterState_SetProps(void *pp, const Byte *props, size_t propSize, ISzAllocPtr alloc) { - CBraState *p = ((CBraState *)pp); - UNUSED_VAR(alloc); + CXzBcFilterStateBase *p = &((CXzBcFilterState *)pp)->base; + UNUSED_VAR(alloc) p->ip = 0; if (p->methodId == XZ_ID_Delta) { if (propSize != 1) return SZ_ERROR_UNSUPPORTED; - p->delta = (unsigned)props[0] + 1; + p->delta = (UInt32)props[0] + 1; } else { if (propSize == 4) { - UInt32 v = GetUi32(props); + const UInt32 v = GetUi32(props); switch (p->methodId) { case XZ_ID_PPC: case XZ_ID_ARM: case XZ_ID_SPARC: - if ((v & 3) != 0) + case XZ_ID_ARM64: + if (v & 3) return SZ_ERROR_UNSUPPORTED; break; case XZ_ID_ARMT: - if ((v & 1) != 0) + case XZ_ID_RISCV: + if (v & 1) return SZ_ERROR_UNSUPPORTED; break; case XZ_ID_IA64: - if ((v & 0xF) != 0) + if (v & 0xf) return SZ_ERROR_UNSUPPORTED; break; + default: break; } p->ip = v; } @@ -112,78 +140,111 @@ static SRes BraState_SetProps(void *pp, const Byte *props, size_t propSize, ISzA return SZ_OK; } -static void BraState_Init(void *pp) + +static void XzBcFilterState_Init(void *pp) { - CBraState *p = ((CBraState *)pp); + CXzBcFilterState *p = ((CXzBcFilterState *)pp); p->bufPos = p->bufConv = p->bufTotal = 0; - x86_Convert_Init(p->x86State); - if (p->methodId == XZ_ID_Delta) - Delta_Init(p->deltaState); + p->base.X86_State = Z7_BRANCH_CONV_ST_X86_STATE_INIT_VAL; + if (p->base.methodId == XZ_ID_Delta) + Delta_Init(p->base.delta_State); +} + + +static const z7_Func_BranchConv g_Funcs_BranchConv_RISC_Dec[] = +{ + Z7_BRANCH_CONV_DEC_2 (BranchConv_PPC), + Z7_BRANCH_CONV_DEC_2 (BranchConv_IA64), + Z7_BRANCH_CONV_DEC_2 (BranchConv_ARM), + Z7_BRANCH_CONV_DEC_2 (BranchConv_ARMT), + Z7_BRANCH_CONV_DEC_2 (BranchConv_SPARC), + Z7_BRANCH_CONV_DEC_2 (BranchConv_ARM64), + Z7_BRANCH_CONV_DEC_2 (BranchConv_RISCV) +}; + +static SizeT XzBcFilterStateBase_Filter_Dec(CXzBcFilterStateBase *p, Byte *data, SizeT size) +{ + switch (p->methodId) + { + case XZ_ID_Delta: + Delta_Decode(p->delta_State, p->delta, data, size); + break; + case XZ_ID_X86: + size = (SizeT)(z7_BranchConvSt_X86_Dec(data, size, p->ip, &p->X86_State) - data); + break; + default: + if (p->methodId >= XZ_ID_PPC) + { + const UInt32 i = p->methodId - XZ_ID_PPC; + if (i < Z7_ARRAY_SIZE(g_Funcs_BranchConv_RISC_Dec)) + size = (SizeT)(g_Funcs_BranchConv_RISC_Dec[i](data, size, p->ip) - data); + } + break; + } + p->ip += (UInt32)size; + return size; +} + + +static SizeT XzBcFilterState_Filter(void *pp, Byte *data, SizeT size) +{ + CXzBcFilterState *p = ((CXzBcFilterState *)pp); + return p->filter_func(&p->base, data, size); } -#define CASE_BRA_CONV(isa) case XZ_ID_ ## isa: p->bufConv = isa ## _Convert(p->buf, p->bufTotal, p->ip, p->encodeMode); break; -static SRes BraState_Code(void *pp, Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen, - int srcWasFinished, ECoderFinishMode finishMode, int *wasFinished) +static SRes XzBcFilterState_Code2(void *pp, + Byte *dest, SizeT *destLen, + const Byte *src, SizeT *srcLen, int srcWasFinished, + ECoderFinishMode finishMode, + // int *wasFinished + ECoderStatus *status) { - CBraState *p = ((CBraState *)pp); - SizeT destLenOrig = *destLen; - SizeT srcLenOrig = *srcLen; - UNUSED_VAR(finishMode); + CXzBcFilterState *p = ((CXzBcFilterState *)pp); + SizeT destRem = *destLen; + SizeT srcRem = *srcLen; + UNUSED_VAR(finishMode) + *destLen = 0; *srcLen = 0; - *wasFinished = 0; - while (destLenOrig > 0) + // *wasFinished = False; + *status = CODER_STATUS_NOT_FINISHED; + + while (destRem != 0) { - if (p->bufPos != p->bufConv) { - size_t curSize = p->bufConv - p->bufPos; - if (curSize > destLenOrig) - curSize = destLenOrig; - memcpy(dest, p->buf + p->bufPos, curSize); - p->bufPos += curSize; - *destLen += curSize; - dest += curSize; - destLenOrig -= curSize; - continue; + size_t size = p->bufConv - p->bufPos; + if (size) + { + if (size > destRem) + size = destRem; + memcpy(dest, p->buf + p->bufPos, size); + p->bufPos += size; + *destLen += size; + dest += size; + destRem -= size; + continue; + } } + p->bufTotal -= p->bufPos; memmove(p->buf, p->buf + p->bufPos, p->bufTotal); p->bufPos = 0; p->bufConv = 0; { - size_t curSize = BRA_BUF_SIZE - p->bufTotal; - if (curSize > srcLenOrig) - curSize = srcLenOrig; - memcpy(p->buf + p->bufTotal, src, curSize); - *srcLen += curSize; - src += curSize; - srcLenOrig -= curSize; - p->bufTotal += curSize; + size_t size = BRA_BUF_SIZE - p->bufTotal; + if (size > srcRem) + size = srcRem; + memcpy(p->buf + p->bufTotal, src, size); + *srcLen += size; + src += size; + srcRem -= size; + p->bufTotal += size; } if (p->bufTotal == 0) break; - switch (p->methodId) - { - case XZ_ID_Delta: - if (p->encodeMode) - Delta_Encode(p->deltaState, p->delta, p->buf, p->bufTotal); - else - Delta_Decode(p->deltaState, p->delta, p->buf, p->bufTotal); - p->bufConv = p->bufTotal; - break; - case XZ_ID_X86: - p->bufConv = x86_Convert(p->buf, p->bufTotal, p->ip, &p->x86State, p->encodeMode); - break; - CASE_BRA_CONV(PPC) - CASE_BRA_CONV(IA64) - CASE_BRA_CONV(ARM) - CASE_BRA_CONV(ARMT) - CASE_BRA_CONV(SPARC) - default: - return SZ_ERROR_UNSUPPORTED; - } - p->ip += (UInt32)p->bufConv; + + p->bufConv = p->filter_func(&p->base, p->buf, p->bufTotal); if (p->bufConv == 0) { @@ -192,52 +253,69 @@ static SRes BraState_Code(void *pp, Byte *dest, SizeT *destLen, const Byte *src, p->bufConv = p->bufTotal; } } - if (p->bufTotal == p->bufPos && srcLenOrig == 0 && srcWasFinished) - *wasFinished = 1; + + if (p->bufTotal == p->bufPos && srcRem == 0 && srcWasFinished) + { + *status = CODER_STATUS_FINISHED_WITH_MARK; + // *wasFinished = 1; + } + return SZ_OK; } -SRes BraState_SetFromMethod(IStateCoder *p, UInt64 id, int encodeMode, ISzAlloc *alloc) + +#define XZ_IS_SUPPORTED_FILTER_ID(id) \ + ((id) >= XZ_ID_Delta && (id) <= XZ_ID_RISCV) + +SRes Xz_StateCoder_Bc_SetFromMethod_Func(IStateCoder *p, UInt64 id, + Xz_Func_BcFilterStateBase_Filter func, ISzAllocPtr alloc) { - CBraState *decoder; - if (id != XZ_ID_Delta && - id != XZ_ID_X86 && - id != XZ_ID_PPC && - id != XZ_ID_IA64 && - id != XZ_ID_ARM && - id != XZ_ID_ARMT && - id != XZ_ID_SPARC) + CXzBcFilterState *decoder; + if (!XZ_IS_SUPPORTED_FILTER_ID(id)) return SZ_ERROR_UNSUPPORTED; - p->p = 0; - decoder = (CBraState *)alloc->Alloc(alloc, sizeof(CBraState)); - if (decoder == 0) - return SZ_ERROR_MEM; - decoder->methodId = (UInt32)id; - decoder->encodeMode = encodeMode; - p->p = decoder; - p->Free = BraState_Free; - p->SetProps = BraState_SetProps; - p->Init = BraState_Init; - p->Code = BraState_Code; + decoder = (CXzBcFilterState *)p->p; + if (!decoder) + { + decoder = (CXzBcFilterState *)ISzAlloc_Alloc(alloc, sizeof(CXzBcFilterState)); + if (!decoder) + return SZ_ERROR_MEM; + decoder->buf = (Byte *)ISzAlloc_Alloc(alloc, BRA_BUF_SIZE); + if (!decoder->buf) + { + ISzAlloc_Free(alloc, decoder); + return SZ_ERROR_MEM; + } + p->p = decoder; + p->Free = XzBcFilterState_Free; + p->SetProps = XzBcFilterState_SetProps; + p->Init = XzBcFilterState_Init; + p->Code2 = XzBcFilterState_Code2; + p->Filter = XzBcFilterState_Filter; + decoder->filter_func = func; + } + decoder->base.methodId = (UInt32)id; + // decoder->encodeMode = encodeMode; return SZ_OK; } + + /* ---------- SbState ---------- */ #ifdef USE_SUBBLOCK -static void SbState_Free(void *pp, ISzAlloc *alloc) +static void SbState_Free(void *pp, ISzAllocPtr alloc) { CSbDec *p = (CSbDec *)pp; SbDec_Free(p); - alloc->Free(alloc, pp); + ISzAlloc_Free(alloc, pp); } -static SRes SbState_SetProps(void *pp, const Byte *props, size_t propSize, ISzAlloc *alloc) +static SRes SbState_SetProps(void *pp, const Byte *props, size_t propSize, ISzAllocPtr alloc) { - UNUSED_VAR(pp); - UNUSED_VAR(props); - UNUSED_VAR(alloc); + UNUSED_VAR(pp) + UNUSED_VAR(props) + UNUSED_VAR(alloc) return (propSize == 0) ? SZ_OK : SZ_ERROR_UNSUPPORTED; } @@ -246,12 +324,14 @@ static void SbState_Init(void *pp) SbDec_Init((CSbDec *)pp); } -static SRes SbState_Code(void *pp, Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen, - int srcWasFinished, ECoderFinishMode finishMode, int *wasFinished) +static SRes SbState_Code2(void *pp, Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen, + int srcWasFinished, ECoderFinishMode finishMode, + // int *wasFinished + ECoderStatus *status) { CSbDec *p = (CSbDec *)pp; SRes res; - UNUSED_VAR(srcWasFinished); + UNUSED_VAR(srcWasFinished) p->dest = dest; p->destLen = *destLen; p->src = src; @@ -260,102 +340,185 @@ static SRes SbState_Code(void *pp, Byte *dest, SizeT *destLen, const Byte *src, res = SbDec_Decode((CSbDec *)pp); *destLen -= p->destLen; *srcLen -= p->srcLen; - *wasFinished = (*destLen == 0 && *srcLen == 0); /* change it */ + // *wasFinished = (*destLen == 0 && *srcLen == 0); /* change it */ + *status = (*destLen == 0 && *srcLen == 0) ? + CODER_STATUS_FINISHED_WITH_MARK : + CODER_STATUS_NOT_FINISHED; return res; } -SRes SbState_SetFromMethod(IStateCoder *p, ISzAlloc *alloc) +static SRes SbState_SetFromMethod(IStateCoder *p, ISzAllocPtr alloc) { - CSbDec *decoder; - p->p = 0; - decoder = alloc->Alloc(alloc, sizeof(CSbDec)); - if (decoder == 0) - return SZ_ERROR_MEM; - p->p = decoder; - p->Free = SbState_Free; - p->SetProps = SbState_SetProps; - p->Init = SbState_Init; - p->Code = SbState_Code; + CSbDec *decoder = (CSbDec *)p->p; + if (!decoder) + { + decoder = (CSbDec *)ISzAlloc_Alloc(alloc, sizeof(CSbDec)); + if (!decoder) + return SZ_ERROR_MEM; + p->p = decoder; + p->Free = SbState_Free; + p->SetProps = SbState_SetProps; + p->Init = SbState_Init; + p->Code2 = SbState_Code2; + p->Filter = NULL; + } SbDec_Construct(decoder); SbDec_SetAlloc(decoder, alloc); return SZ_OK; } + #endif -/* ---------- Lzma2State ---------- */ -static void Lzma2State_Free(void *pp, ISzAlloc *alloc) + +/* ---------- Lzma2 ---------- */ + +typedef struct +{ + CLzma2Dec decoder; + BoolInt outBufMode; +} CLzma2Dec_Spec; + + +static void Lzma2State_Free(void *pp, ISzAllocPtr alloc) { - Lzma2Dec_Free((CLzma2Dec *)pp, alloc); - alloc->Free(alloc, pp); + CLzma2Dec_Spec *p = (CLzma2Dec_Spec *)pp; + if (p->outBufMode) + Lzma2Dec_FreeProbs(&p->decoder, alloc); + else + Lzma2Dec_Free(&p->decoder, alloc); + ISzAlloc_Free(alloc, pp); } -static SRes Lzma2State_SetProps(void *pp, const Byte *props, size_t propSize, ISzAlloc *alloc) +static SRes Lzma2State_SetProps(void *pp, const Byte *props, size_t propSize, ISzAllocPtr alloc) { if (propSize != 1) return SZ_ERROR_UNSUPPORTED; - return Lzma2Dec_Allocate((CLzma2Dec *)pp, props[0], alloc); + { + CLzma2Dec_Spec *p = (CLzma2Dec_Spec *)pp; + if (p->outBufMode) + return Lzma2Dec_AllocateProbs(&p->decoder, props[0], alloc); + else + return Lzma2Dec_Allocate(&p->decoder, props[0], alloc); + } } static void Lzma2State_Init(void *pp) { - Lzma2Dec_Init((CLzma2Dec *)pp); + Lzma2Dec_Init(&((CLzma2Dec_Spec *)pp)->decoder); } -static SRes Lzma2State_Code(void *pp, Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen, - int srcWasFinished, ECoderFinishMode finishMode, int *wasFinished) + +/* + if (outBufMode), then (dest) is not used. Use NULL. + Data is unpacked to (spec->decoder.decoder.dic) output buffer. +*/ + +static SRes Lzma2State_Code2(void *pp, Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen, + int srcWasFinished, ECoderFinishMode finishMode, + // int *wasFinished, + ECoderStatus *status) { - ELzmaStatus status; + CLzma2Dec_Spec *spec = (CLzma2Dec_Spec *)pp; + ELzmaStatus status2; /* ELzmaFinishMode fm = (finishMode == LZMA_FINISH_ANY) ? LZMA_FINISH_ANY : LZMA_FINISH_END; */ - SRes res = Lzma2Dec_DecodeToBuf((CLzma2Dec *)pp, dest, destLen, src, srcLen, (ELzmaFinishMode)finishMode, &status); - UNUSED_VAR(srcWasFinished); - *wasFinished = (status == LZMA_STATUS_FINISHED_WITH_MARK); + SRes res; + UNUSED_VAR(srcWasFinished) + if (spec->outBufMode) + { + SizeT dicPos = spec->decoder.decoder.dicPos; + SizeT dicLimit = dicPos + *destLen; + res = Lzma2Dec_DecodeToDic(&spec->decoder, dicLimit, src, srcLen, (ELzmaFinishMode)finishMode, &status2); + *destLen = spec->decoder.decoder.dicPos - dicPos; + } + else + res = Lzma2Dec_DecodeToBuf(&spec->decoder, dest, destLen, src, srcLen, (ELzmaFinishMode)finishMode, &status2); + // *wasFinished = (status2 == LZMA_STATUS_FINISHED_WITH_MARK); + // ECoderStatus values are identical to ELzmaStatus values of LZMA2 decoder + *status = (ECoderStatus)status2; return res; } -static SRes Lzma2State_SetFromMethod(IStateCoder *p, ISzAlloc *alloc) + +static SRes Lzma2State_SetFromMethod(IStateCoder *p, Byte *outBuf, size_t outBufSize, ISzAllocPtr alloc) +{ + CLzma2Dec_Spec *spec = (CLzma2Dec_Spec *)p->p; + if (!spec) + { + spec = (CLzma2Dec_Spec *)ISzAlloc_Alloc(alloc, sizeof(CLzma2Dec_Spec)); + if (!spec) + return SZ_ERROR_MEM; + p->p = spec; + p->Free = Lzma2State_Free; + p->SetProps = Lzma2State_SetProps; + p->Init = Lzma2State_Init; + p->Code2 = Lzma2State_Code2; + p->Filter = NULL; + Lzma2Dec_CONSTRUCT(&spec->decoder) + } + spec->outBufMode = False; + if (outBuf) + { + spec->outBufMode = True; + spec->decoder.decoder.dic = outBuf; + spec->decoder.decoder.dicBufSize = outBufSize; + } + return SZ_OK; +} + + +static SRes Lzma2State_ResetOutBuf(IStateCoder *p, Byte *outBuf, size_t outBufSize) { - CLzma2Dec *decoder = (CLzma2Dec *)alloc->Alloc(alloc, sizeof(CLzma2Dec)); - p->p = decoder; - if (decoder == 0) - return SZ_ERROR_MEM; - p->Free = Lzma2State_Free; - p->SetProps = Lzma2State_SetProps; - p->Init = Lzma2State_Init; - p->Code = Lzma2State_Code; - Lzma2Dec_Construct(decoder); + CLzma2Dec_Spec *spec = (CLzma2Dec_Spec *)p->p; + if ((spec->outBufMode && !outBuf) || (!spec->outBufMode && outBuf)) + return SZ_ERROR_FAIL; + if (outBuf) + { + spec->decoder.decoder.dic = outBuf; + spec->decoder.decoder.dicBufSize = outBufSize; + } return SZ_OK; } -void MixCoder_Construct(CMixCoder *p, ISzAlloc *alloc) + +static void MixCoder_Construct(CMixCoder *p, ISzAllocPtr alloc) { unsigned i; p->alloc = alloc; p->buf = NULL; p->numCoders = 0; + + p->outBufSize = 0; + p->outBuf = NULL; + // p->SingleBufMode = False; + for (i = 0; i < MIXCODER_NUM_FILTERS_MAX; i++) p->coders[i].p = NULL; } -void MixCoder_Free(CMixCoder *p) + +static void MixCoder_Free(CMixCoder *p) { unsigned i; - for (i = 0; i < p->numCoders; i++) + p->numCoders = 0; + for (i = 0; i < MIXCODER_NUM_FILTERS_MAX; i++) { IStateCoder *sc = &p->coders[i]; - if (p->alloc && sc->p) + if (sc->p) + { sc->Free(sc->p, p->alloc); + sc->p = NULL; + } } - p->numCoders = 0; if (p->buf) { - p->alloc->Free(p->alloc, p->buf); + ISzAlloc_Free(p->alloc, p->buf); p->buf = NULL; /* 9.31: the BUG was fixed */ } } -void MixCoder_Init(CMixCoder *p) +static void MixCoder_Init(CMixCoder *p) { unsigned i; for (i = 0; i < MIXCODER_NUM_FILTERS_MAX - 1; i++) @@ -368,50 +531,138 @@ void MixCoder_Init(CMixCoder *p) { IStateCoder *coder = &p->coders[i]; coder->Init(coder->p); + p->results[i] = SZ_OK; } + p->outWritten = 0; + p->wasFinished = False; + p->res = SZ_OK; + p->status = CODER_STATUS_NOT_SPECIFIED; } -SRes MixCoder_SetFromMethod(CMixCoder *p, unsigned coderIndex, UInt64 methodId) + +static SRes MixCoder_SetFromMethod(CMixCoder *p, unsigned coderIndex, UInt64 methodId, Byte *outBuf, size_t outBufSize) { IStateCoder *sc = &p->coders[coderIndex]; p->ids[coderIndex] = methodId; - switch (methodId) - { - case XZ_ID_LZMA2: return Lzma2State_SetFromMethod(sc, p->alloc); - #ifdef USE_SUBBLOCK - case XZ_ID_Subblock: return SbState_SetFromMethod(sc, p->alloc); - #endif - } + if (methodId == XZ_ID_LZMA2) + return Lzma2State_SetFromMethod(sc, outBuf, outBufSize, p->alloc); +#ifdef USE_SUBBLOCK + if (methodId == XZ_ID_Subblock) + return SbState_SetFromMethod(sc, p->alloc); +#endif if (coderIndex == 0) return SZ_ERROR_UNSUPPORTED; - return BraState_SetFromMethod(sc, methodId, 0, p->alloc); + return Xz_StateCoder_Bc_SetFromMethod_Func(sc, methodId, + XzBcFilterStateBase_Filter_Dec, p->alloc); +} + + +static SRes MixCoder_ResetFromMethod(CMixCoder *p, unsigned coderIndex, UInt64 methodId, Byte *outBuf, size_t outBufSize) +{ + IStateCoder *sc = &p->coders[coderIndex]; + if (methodId == XZ_ID_LZMA2) + return Lzma2State_ResetOutBuf(sc, outBuf, outBufSize); + return SZ_ERROR_UNSUPPORTED; } -SRes MixCoder_Code(CMixCoder *p, Byte *dest, SizeT *destLen, + + +/* + if (destFinish) - then unpack data block is finished at (*destLen) position, + and we can return data that were not processed by filter + +output (status) can be : + CODER_STATUS_NOT_FINISHED + CODER_STATUS_FINISHED_WITH_MARK + CODER_STATUS_NEEDS_MORE_INPUT +*/ + +static SRes MixCoder_Code(CMixCoder *p, + Byte *dest, SizeT *destLen, int destFinish, const Byte *src, SizeT *srcLen, int srcWasFinished, - ECoderFinishMode finishMode, ECoderStatus *status) + ECoderFinishMode finishMode) { - SizeT destLenOrig = *destLen; - SizeT srcLenOrig = *srcLen; - Bool allFinished = True; + const SizeT destLenOrig = *destLen; + const SizeT srcLenOrig = *srcLen; + *destLen = 0; *srcLen = 0; - *status = CODER_STATUS_NOT_FINISHED; - if (!p->buf) + if (p->wasFinished) + return p->res; + + p->status = CODER_STATUS_NOT_FINISHED; + + // if (p->SingleBufMode) + if (p->outBuf) { - p->buf = (Byte *)p->alloc->Alloc(p->alloc, CODER_BUF_SIZE * (MIXCODER_NUM_FILTERS_MAX - 1)); - if (!p->buf) - return SZ_ERROR_MEM; + SRes res; + SizeT destLen2; + int wasFinished; + + PRF_STR("------- MixCoder Single ----------") + + destLen2 = destLenOrig; + if (p->numCoders != 1) + { + if (destLen2 < p->outWritten) + return SZ_ERROR_FAIL; + destLen2 -= p->outWritten; + } + *srcLen = srcLenOrig; + { + IStateCoder *coder = &p->coders[0]; + res = coder->Code2(coder->p, NULL, &destLen2, src, srcLen, srcWasFinished, finishMode, &p->status); + } + p->res = res; + p->outWritten += destLen2; + wasFinished = (p->status == CODER_STATUS_FINISHED_WITH_MARK); + + if (res != SZ_OK || srcWasFinished || wasFinished) + p->wasFinished = True; + + if (p->numCoders == 1) + *destLen = destLen2; + else if (p->wasFinished) + { + unsigned i; + size_t processed = p->outWritten; + + for (i = 1; i < p->numCoders; i++) + { + IStateCoder *coder = &p->coders[i]; + processed = coder->Filter(coder->p, p->outBuf, processed); + if (wasFinished || (destFinish && p->outWritten == destLenOrig)) + processed = p->outWritten; + PRF_STR_INT("filter", i) + } + *destLen = processed; + } + return res; } + PRF_STR("standard mix") + if (p->numCoders != 1) + { + if (!p->buf) + { + p->buf = (Byte *)ISzAlloc_Alloc(p->alloc, CODER_BUF_SIZE * (MIXCODER_NUM_FILTERS_MAX - 1)); + if (!p->buf) + return SZ_ERROR_MEM; + } + finishMode = CODER_FINISH_ANY; + } for (;;) { - Bool processed = False; + BoolInt processed = False; + BoolInt allFinished = True; + SRes resMain = SZ_OK; unsigned i; + + p->status = CODER_STATUS_NOT_FINISHED; /* if (p->numCoders == 1 && *destLen == destLenOrig && finishMode == LZMA_FINISH_ANY) break; @@ -421,79 +672,106 @@ SRes MixCoder_Code(CMixCoder *p, Byte *dest, SizeT *destLen, { SRes res; IStateCoder *coder = &p->coders[i]; - Byte *destCur; - SizeT destLenCur, srcLenCur; - const Byte *srcCur; - int srcFinishedCur; + Byte *dest2; + SizeT destLen2, srcLen2; // destLen2_Orig; + const Byte *src2; + int srcFinished2; int encodingWasFinished; + ECoderStatus status2; if (i == 0) { - srcCur = src; - srcLenCur = srcLenOrig - *srcLen; - srcFinishedCur = srcWasFinished; + src2 = src; + srcLen2 = srcLenOrig - *srcLen; + srcFinished2 = srcWasFinished; } else { - srcCur = p->buf + (CODER_BUF_SIZE * (i - 1)) + p->pos[i - 1]; - srcLenCur = p->size[i - 1] - p->pos[i - 1]; - srcFinishedCur = p->finished[i - 1]; + size_t k = i - 1; + src2 = p->buf + (CODER_BUF_SIZE * k) + p->pos[k]; + srcLen2 = p->size[k] - p->pos[k]; + srcFinished2 = p->finished[k]; } if (i == p->numCoders - 1) { - destCur = dest; - destLenCur = destLenOrig - *destLen; + dest2 = dest; + destLen2 = destLenOrig - *destLen; } else { if (p->pos[i] != p->size[i]) continue; - destCur = p->buf + (CODER_BUF_SIZE * i); - destLenCur = CODER_BUF_SIZE; + dest2 = p->buf + (CODER_BUF_SIZE * i); + destLen2 = CODER_BUF_SIZE; } - res = coder->Code(coder->p, destCur, &destLenCur, srcCur, &srcLenCur, srcFinishedCur, finishMode, &encodingWasFinished); + // destLen2_Orig = destLen2; + + if (p->results[i] != SZ_OK) + { + if (resMain == SZ_OK) + resMain = p->results[i]; + continue; + } + + res = coder->Code2(coder->p, + dest2, &destLen2, + src2, &srcLen2, srcFinished2, + finishMode, + // &encodingWasFinished, + &status2); + if (res != SZ_OK) + { + p->results[i] = res; + if (resMain == SZ_OK) + resMain = res; + } + + encodingWasFinished = (status2 == CODER_STATUS_FINISHED_WITH_MARK); + if (!encodingWasFinished) + { allFinished = False; + if (p->numCoders == 1 && res == SZ_OK) + p->status = status2; + } if (i == 0) { - *srcLen += srcLenCur; - src += srcLenCur; + *srcLen += srcLen2; + src += srcLen2; } else - { - p->pos[i - 1] += srcLenCur; - } + p->pos[(size_t)i - 1] += srcLen2; if (i == p->numCoders - 1) { - *destLen += destLenCur; - dest += destLenCur; + *destLen += destLen2; + dest += destLen2; } else { - p->size[i] = destLenCur; + p->size[i] = destLen2; p->pos[i] = 0; p->finished[i] = encodingWasFinished; } - if (res != SZ_OK) - return res; - - if (destLenCur != 0 || srcLenCur != 0) + if (destLen2 != 0 || srcLen2 != 0) processed = True; } + if (!processed) - break; + { + if (allFinished) + p->status = CODER_STATUS_FINISHED_WITH_MARK; + return resMain; + } } - if (allFinished) - *status = CODER_STATUS_FINISHED_WITH_MARK; - return SZ_OK; } + SRes Xz_ParseHeader(CXzStreamFlags *p, const Byte *buf) { *p = (CXzStreamFlags)GetBe16(buf + XZ_SIG_SIZE); @@ -503,18 +781,44 @@ SRes Xz_ParseHeader(CXzStreamFlags *p, const Byte *buf) return XzFlags_IsSupported(*p) ? SZ_OK : SZ_ERROR_UNSUPPORTED; } -static Bool Xz_CheckFooter(CXzStreamFlags flags, UInt64 indexSize, const Byte *buf) +static BoolInt Xz_CheckFooter(CXzStreamFlags flags, UInt64 indexSize, const Byte *buf) { - return - indexSize == (((UInt64)GetUi32(buf + 4) + 1) << 2) && - (GetUi32(buf) == CrcCalc(buf + 4, 6) && - flags == GetBe16(buf + 8) && - memcmp(buf + 10, XZ_FOOTER_SIG, XZ_FOOTER_SIG_SIZE) == 0); + return indexSize == (((UInt64)GetUi32a(buf + 4) + 1) << 2) + && GetUi32a(buf) == CrcCalc(buf + 4, 6) + && flags == GetBe16a(buf + 8) + && GetUi16a(buf + 10) == (XZ_FOOTER_SIG_0 | (XZ_FOOTER_SIG_1 << 8)); } #define READ_VARINT_AND_CHECK(buf, pos, size, res) \ - { unsigned s = Xz_ReadVarInt(buf + pos, size - pos, res); \ - if (s == 0) return SZ_ERROR_ARCHIVE; pos += s; } + { const unsigned s = Xz_ReadVarInt(buf + pos, size - pos, res); \ + if (s == 0) return SZ_ERROR_ARCHIVE; \ + pos += s; } + + +static BoolInt XzBlock_AreSupportedFilters(const CXzBlock *p) +{ + const unsigned numFilters = XzBlock_GetNumFilters(p) - 1; + unsigned i; + { + const CXzFilter *f = &p->filters[numFilters]; + if (f->id != XZ_ID_LZMA2 || f->propsSize != 1 || f->props[0] > 40) + return False; + } + + for (i = 0; i < numFilters; i++) + { + const CXzFilter *f = &p->filters[i]; + if (f->id == XZ_ID_Delta) + { + if (f->propsSize != 1) + return False; + } + else if (!XZ_IS_SUPPORTED_FILTER_ID(f->id) + || (f->propsSize != 0 && f->propsSize != 4)) + return False; + } + return True; +} SRes XzBlock_Parse(CXzBlock *p, const Byte *header) @@ -523,31 +827,35 @@ SRes XzBlock_Parse(CXzBlock *p, const Byte *header) unsigned numFilters, i; unsigned headerSize = (unsigned)header[0] << 2; + /* (headerSize != 0) : another code checks */ + if (CrcCalc(header, headerSize) != GetUi32(header + headerSize)) return SZ_ERROR_ARCHIVE; pos = 1; - if (pos == headerSize) - return SZ_ERROR_ARCHIVE; p->flags = header[pos++]; + p->packSize = (UInt64)(Int64)-1; if (XzBlock_HasPackSize(p)) { - READ_VARINT_AND_CHECK(header, pos, headerSize, &p->packSize); + READ_VARINT_AND_CHECK(header, pos, headerSize, &p->packSize) if (p->packSize == 0 || p->packSize + headerSize >= (UInt64)1 << 63) return SZ_ERROR_ARCHIVE; } + p->unpackSize = (UInt64)(Int64)-1; if (XzBlock_HasUnpackSize(p)) - READ_VARINT_AND_CHECK(header, pos, headerSize, &p->unpackSize); + { + READ_VARINT_AND_CHECK(header, pos, headerSize, &p->unpackSize) + } numFilters = XzBlock_GetNumFilters(p); for (i = 0; i < numFilters; i++) { CXzFilter *filter = p->filters + i; UInt64 size; - READ_VARINT_AND_CHECK(header, pos, headerSize, &filter->id); - READ_VARINT_AND_CHECK(header, pos, headerSize, &size); + READ_VARINT_AND_CHECK(header, pos, headerSize, &filter->id) + READ_VARINT_AND_CHECK(header, pos, headerSize, &size) if (size > headerSize - pos || size > XZ_FILTER_PROPS_SIZE_MAX) return SZ_ERROR_ARCHIVE; filter->propsSize = (UInt32)size; @@ -564,48 +872,69 @@ SRes XzBlock_Parse(CXzBlock *p, const Byte *header) #endif } + if (XzBlock_HasUnsupportedFlags(p)) + return SZ_ERROR_UNSUPPORTED; + while (pos < headerSize) if (header[pos++] != 0) return SZ_ERROR_ARCHIVE; return SZ_OK; } -SRes XzDec_Init(CMixCoder *p, const CXzBlock *block) + + + +static SRes XzDecMix_Init(CMixCoder *p, const CXzBlock *block, Byte *outBuf, size_t outBufSize) { unsigned i; - Bool needReInit = True; + BoolInt needReInit = True; unsigned numFilters = XzBlock_GetNumFilters(block); - - if (numFilters == p->numCoders) + + if (numFilters == p->numCoders && ((p->outBuf && outBuf) || (!p->outBuf && !outBuf))) { + needReInit = False; for (i = 0; i < numFilters; i++) if (p->ids[i] != block->filters[numFilters - 1 - i].id) + { + needReInit = True; break; - needReInit = (i != numFilters); + } } + + // p->SingleBufMode = (outBuf != NULL); + p->outBuf = outBuf; + p->outBufSize = outBufSize; + + // p->SingleBufMode = False; + // outBuf = NULL; if (needReInit) { MixCoder_Free(p); - p->numCoders = numFilters; for (i = 0; i < numFilters; i++) { - const CXzFilter *f = &block->filters[numFilters - 1 - i]; - RINOK(MixCoder_SetFromMethod(p, i, f->id)); + RINOK(MixCoder_SetFromMethod(p, i, block->filters[numFilters - 1 - i].id, outBuf, outBufSize)) } + p->numCoders = numFilters; } - + else + { + RINOK(MixCoder_ResetFromMethod(p, 0, block->filters[numFilters - 1].id, outBuf, outBufSize)) + } + for (i = 0; i < numFilters; i++) { const CXzFilter *f = &block->filters[numFilters - 1 - i]; IStateCoder *sc = &p->coders[i]; - RINOK(sc->SetProps(sc->p, f->props, f->propsSize, p->alloc)); + RINOK(sc->SetProps(sc->p, f->props, f->propsSize, p->alloc)) } MixCoder_Init(p); return SZ_OK; } + + void XzUnpacker_Init(CXzUnpacker *p) { p->state = XZ_STATE_STREAM_HEADER; @@ -614,81 +943,177 @@ void XzUnpacker_Init(CXzUnpacker *p) p->numFinishedStreams = 0; p->numTotalBlocks = 0; p->padSize = 0; + p->decodeOnlyOneBlock = 0; + + p->parseMode = False; + p->decodeToStreamSignature = False; + + // p->outBuf = NULL; + // p->outBufSize = 0; + p->outDataWritten = 0; +} + + +void XzUnpacker_SetOutBuf(CXzUnpacker *p, Byte *outBuf, size_t outBufSize) +{ + p->outBuf = outBuf; + p->outBufSize = outBufSize; } -void XzUnpacker_Construct(CXzUnpacker *p, ISzAlloc *alloc) + +void XzUnpacker_Construct(CXzUnpacker *p, ISzAllocPtr alloc) { MixCoder_Construct(&p->decoder, alloc); + p->outBuf = NULL; + p->outBufSize = 0; XzUnpacker_Init(p); } + void XzUnpacker_Free(CXzUnpacker *p) { MixCoder_Free(&p->decoder); } + +void XzUnpacker_PrepareToRandomBlockDecoding(CXzUnpacker *p) +{ + p->indexSize = 0; + p->numBlocks = 0; + Sha256_Init(&p->sha); + p->state = XZ_STATE_BLOCK_HEADER; + p->pos = 0; + p->decodeOnlyOneBlock = 1; +} + + +static void XzUnpacker_UpdateIndex(CXzUnpacker *p, UInt64 packSize, UInt64 unpackSize) +{ + Byte temp[32]; + unsigned num = Xz_WriteVarInt(temp, packSize); + num += Xz_WriteVarInt(temp + num, unpackSize); + Sha256_Update(&p->sha, temp, num); + p->indexSize += num; + p->numBlocks++; +} + + + SRes XzUnpacker_Code(CXzUnpacker *p, Byte *dest, SizeT *destLen, - const Byte *src, SizeT *srcLen, ECoderFinishMode finishMode, ECoderStatus *status) + const Byte *src, SizeT *srcLen, int srcFinished, + ECoderFinishMode finishMode, ECoderStatus *status) { - SizeT destLenOrig = *destLen; - SizeT srcLenOrig = *srcLen; + const SizeT destLenOrig = *destLen; + const SizeT srcLenOrig = *srcLen; *destLen = 0; *srcLen = 0; *status = CODER_STATUS_NOT_SPECIFIED; + for (;;) { - SizeT srcRem = srcLenOrig - *srcLen; + SizeT srcRem; if (p->state == XZ_STATE_BLOCK) { SizeT destLen2 = destLenOrig - *destLen; SizeT srcLen2 = srcLenOrig - *srcLen; SRes res; + + ECoderFinishMode finishMode2 = finishMode; + BoolInt srcFinished2 = (BoolInt)srcFinished; + BoolInt destFinish = False; + + if (p->block.packSize != (UInt64)(Int64)-1) + { + UInt64 rem = p->block.packSize - p->packSize; + if (srcLen2 >= rem) + { + srcFinished2 = True; + srcLen2 = (SizeT)rem; + } + if (rem == 0 && p->block.unpackSize == p->unpackSize) + return SZ_ERROR_DATA; + } + + if (p->block.unpackSize != (UInt64)(Int64)-1) + { + UInt64 rem = p->block.unpackSize - p->unpackSize; + if (destLen2 >= rem) + { + destFinish = True; + finishMode2 = CODER_FINISH_END; + destLen2 = (SizeT)rem; + } + } + + /* if (srcLen2 == 0 && destLen2 == 0) { *status = CODER_STATUS_NOT_FINISHED; return SZ_OK; } + */ - res = MixCoder_Code(&p->decoder, dest, &destLen2, src, &srcLen2, False, finishMode, status); - XzCheck_Update(&p->check, dest, destLen2); + { + res = MixCoder_Code(&p->decoder, + (p->outBuf ? NULL : dest), &destLen2, destFinish, + src, &srcLen2, srcFinished2, + finishMode2); + + *status = p->decoder.status; + XzCheck_Update(&p->check, (p->outBuf ? p->outBuf + p->outDataWritten : dest), destLen2); + if (!p->outBuf) + dest += destLen2; + p->outDataWritten += destLen2; + } (*srcLen) += srcLen2; src += srcLen2; p->packSize += srcLen2; - (*destLen) += destLen2; - dest += destLen2; p->unpackSize += destLen2; - - RINOK(res); - - if (*status == CODER_STATUS_FINISHED_WITH_MARK) - { - Byte temp[32]; - unsigned num = Xz_WriteVarInt(temp, p->packSize + p->blockHeaderSize + XzFlags_GetCheckSize(p->streamFlags)); - num += Xz_WriteVarInt(temp + num, p->unpackSize); - Sha256_Update(&p->sha, temp, num); - p->indexSize += num; - p->numBlocks++; + + RINOK(res) + + if (*status != CODER_STATUS_FINISHED_WITH_MARK) + { + if (p->block.packSize == p->packSize + && *status == CODER_STATUS_NEEDS_MORE_INPUT) + { + PRF_STR("CODER_STATUS_NEEDS_MORE_INPUT") + *status = CODER_STATUS_NOT_SPECIFIED; + return SZ_ERROR_DATA; + } + return SZ_OK; + } + { + XzUnpacker_UpdateIndex(p, XzUnpacker_GetPackSizeForIndex(p), p->unpackSize); p->state = XZ_STATE_BLOCK_FOOTER; p->pos = 0; p->alignPos = 0; + *status = CODER_STATUS_NOT_SPECIFIED; + + if ((p->block.packSize != (UInt64)(Int64)-1 && p->block.packSize != p->packSize) + || (p->block.unpackSize != (UInt64)(Int64)-1 && p->block.unpackSize != p->unpackSize)) + { + PRF_STR("ERROR: block.size mismatch") + return SZ_ERROR_DATA; + } } - else if (srcLen2 == 0 && destLen2 == 0) - return SZ_OK; - - continue; + // continue; } - if (srcRem == 0) + srcRem = srcLenOrig - *srcLen; + + // XZ_STATE_BLOCK_FOOTER can transit to XZ_STATE_BLOCK_HEADER without input bytes + if (srcRem == 0 && p->state != XZ_STATE_BLOCK_FOOTER) { *status = CODER_STATUS_NEEDS_MORE_INPUT; return SZ_OK; } - switch (p->state) + switch ((int)p->state) { case XZ_STATE_STREAM_HEADER: { @@ -696,17 +1121,19 @@ SRes XzUnpacker_Code(CXzUnpacker *p, Byte *dest, SizeT *destLen, { if (p->pos < XZ_SIG_SIZE && *src != XZ_SIG[p->pos]) return SZ_ERROR_NO_ARCHIVE; + if (p->decodeToStreamSignature) + return SZ_OK; p->buf[p->pos++] = *src++; (*srcLen)++; } else { - RINOK(Xz_ParseHeader(&p->streamFlags, p->buf)); + RINOK(Xz_ParseHeader(&p->streamFlags, p->buf)) p->numStartedStreams++; - p->state = XZ_STATE_BLOCK_HEADER; - Sha256_Init(&p->sha); p->indexSize = 0; p->numBlocks = 0; + Sha256_Init(&p->sha); + p->state = XZ_STATE_BLOCK_HEADER; p->pos = 0; } break; @@ -720,21 +1147,26 @@ SRes XzUnpacker_Code(CXzUnpacker *p, Byte *dest, SizeT *destLen, (*srcLen)++; if (p->buf[0] == 0) { + if (p->decodeOnlyOneBlock) + return SZ_ERROR_DATA; p->indexPreSize = 1 + Xz_WriteVarInt(p->buf + 1, p->numBlocks); p->indexPos = p->indexPreSize; p->indexSize += p->indexPreSize; - Sha256_Final(&p->sha, p->shaDigest); + Sha256_Final(&p->sha, (Byte *)(void *)p->shaDigest32); Sha256_Init(&p->sha); p->crc = CrcUpdate(CRC_INIT_VAL, p->buf, p->indexPreSize); p->state = XZ_STATE_STREAM_INDEX; + break; } - p->blockHeaderSize = ((UInt32)p->buf[0] << 2) + 4; + p->blockHeaderSize = ((unsigned)p->buf[0] << 2) + 4; + break; } - else if (p->pos != p->blockHeaderSize) + + if (p->pos != p->blockHeaderSize) { - UInt32 cur = p->blockHeaderSize - p->pos; + unsigned cur = p->blockHeaderSize - p->pos; if (cur > srcRem) - cur = (UInt32)srcRem; + cur = (unsigned)srcRem; memcpy(p->buf + p->pos, src, cur); p->pos += cur; (*srcLen) += cur; @@ -742,21 +1174,33 @@ SRes XzUnpacker_Code(CXzUnpacker *p, Byte *dest, SizeT *destLen, } else { - RINOK(XzBlock_Parse(&p->block, p->buf)); + RINOK(XzBlock_Parse(&p->block, p->buf)) + if (!XzBlock_AreSupportedFilters(&p->block)) + return SZ_ERROR_UNSUPPORTED; p->numTotalBlocks++; p->state = XZ_STATE_BLOCK; p->packSize = 0; p->unpackSize = 0; XzCheck_Init(&p->check, XzFlags_GetCheckType(p->streamFlags)); - RINOK(XzDec_Init(&p->decoder, &p->block)); + if (p->parseMode) + { + p->headerParsedOk = True; + return SZ_OK; + } + RINOK(XzDecMix_Init(&p->decoder, &p->block, p->outBuf, p->outBufSize)) } break; } case XZ_STATE_BLOCK_FOOTER: { - if (((p->packSize + p->alignPos) & 3) != 0) + if ((((unsigned)p->packSize + p->alignPos) & 3) != 0) { + if (srcRem == 0) + { + *status = CODER_STATUS_NEEDS_MORE_INPUT; + return SZ_OK; + } (*srcLen)++; p->alignPos++; if (*src++ != 0) @@ -764,24 +1208,35 @@ SRes XzUnpacker_Code(CXzUnpacker *p, Byte *dest, SizeT *destLen, } else { - UInt32 checkSize = XzFlags_GetCheckSize(p->streamFlags); - UInt32 cur = checkSize - p->pos; + const unsigned checkSize = XzFlags_GetCheckSize(p->streamFlags); + unsigned cur = checkSize - p->pos; if (cur != 0) { + if (srcRem == 0) + { + *status = CODER_STATUS_NEEDS_MORE_INPUT; + return SZ_OK; + } if (cur > srcRem) - cur = (UInt32)srcRem; + cur = (unsigned)srcRem; memcpy(p->buf + p->pos, src, cur); p->pos += cur; (*srcLen) += cur; src += cur; + if (checkSize != p->pos) + break; } - else { - Byte digest[XZ_CHECK_SIZE_MAX]; + UInt32 digest32[XZ_CHECK_SIZE_MAX / 4]; p->state = XZ_STATE_BLOCK_HEADER; p->pos = 0; - if (XzCheck_Final(&p->check, digest) && memcmp(digest, p->buf, checkSize) != 0) + if (XzCheck_Final(&p->check, (Byte *)(void *)digest32) && memcmp(digest32, p->buf, checkSize) != 0) return SZ_ERROR_CRC; + if (p->decodeOnlyOneBlock) + { + *status = CODER_STATUS_FINISHED_WITH_MARK; + return SZ_OK; + } } } break; @@ -820,12 +1275,12 @@ SRes XzUnpacker_Code(CXzUnpacker *p, Byte *dest, SizeT *destLen, } else { - Byte digest[SHA256_DIGEST_SIZE]; + UInt32 digest32[SHA256_DIGEST_SIZE / 4]; p->state = XZ_STATE_STREAM_INDEX_CRC; p->indexSize += 4; p->pos = 0; - Sha256_Final(&p->sha, digest); - if (memcmp(digest, p->shaDigest, SHA256_DIGEST_SIZE) != 0) + Sha256_Final(&p->sha, (Byte *)(void *)digest32); + if (memcmp(digest32, p->shaDigest32, SHA256_DIGEST_SIZE) != 0) return SZ_ERROR_CRC; } } @@ -841,9 +1296,10 @@ SRes XzUnpacker_Code(CXzUnpacker *p, Byte *dest, SizeT *destLen, } else { + const Byte *ptr = p->buf; p->state = XZ_STATE_STREAM_FOOTER; p->pos = 0; - if (CRC_GET_DIGEST(p->crc) != GetUi32(p->buf)) + if (CRC_GET_DIGEST(p->crc) != GetUi32a(ptr)) return SZ_ERROR_CRC; } break; @@ -851,9 +1307,9 @@ SRes XzUnpacker_Code(CXzUnpacker *p, Byte *dest, SizeT *destLen, case XZ_STATE_STREAM_FOOTER: { - UInt32 cur = XZ_STREAM_FOOTER_SIZE - p->pos; + unsigned cur = XZ_STREAM_FOOTER_SIZE - p->pos; if (cur > srcRem) - cur = (UInt32)srcRem; + cur = (unsigned)srcRem; memcpy(p->buf + p->pos, src, cur); p->pos += cur; (*srcLen) += cur; @@ -873,7 +1329,7 @@ SRes XzUnpacker_Code(CXzUnpacker *p, Byte *dest, SizeT *destLen, { if (*src != 0) { - if (((UInt32)p->padSize & 3) != 0) + if ((unsigned)p->padSize & 3) return SZ_ERROR_NO_ARCHIVE; p->pos = 0; p->state = XZ_STATE_STREAM_HEADER; @@ -888,6 +1344,8 @@ SRes XzUnpacker_Code(CXzUnpacker *p, Byte *dest, SizeT *destLen, } case XZ_STATE_BLOCK: break; /* to disable GCC warning */ + + default: return SZ_ERROR_FAIL; } } /* @@ -897,17 +1355,1509 @@ SRes XzUnpacker_Code(CXzUnpacker *p, Byte *dest, SizeT *destLen, */ } -Bool XzUnpacker_IsStreamWasFinished(CXzUnpacker *p) + +SRes XzUnpacker_CodeFull(CXzUnpacker *p, Byte *dest, SizeT *destLen, + const Byte *src, SizeT *srcLen, + ECoderFinishMode finishMode, ECoderStatus *status) +{ + XzUnpacker_Init(p); + XzUnpacker_SetOutBuf(p, dest, *destLen); + + return XzUnpacker_Code(p, + NULL, destLen, + src, srcLen, True, + finishMode, status); +} + + +BoolInt XzUnpacker_IsBlockFinished(const CXzUnpacker *p) +{ + return (p->state == XZ_STATE_BLOCK_HEADER) && (p->pos == 0); +} + +BoolInt XzUnpacker_IsStreamWasFinished(const CXzUnpacker *p) { return (p->state == XZ_STATE_STREAM_PADDING) && (((UInt32)p->padSize & 3) == 0); } -UInt64 XzUnpacker_GetExtraSize(CXzUnpacker *p) +UInt64 XzUnpacker_GetExtraSize(const CXzUnpacker *p) { UInt64 num = 0; if (p->state == XZ_STATE_STREAM_PADDING) - num += p->padSize; + num = p->padSize; else if (p->state == XZ_STATE_STREAM_HEADER) - num += p->padSize + p->pos; + num = p->padSize + p->pos; return num; } + + + + + + + + + + + + + + + + + + + + + +#ifndef Z7_ST +#include "MtDec.h" +#endif + + +void XzDecMtProps_Init(CXzDecMtProps *p) +{ + p->inBufSize_ST = 1 << 18; + p->outStep_ST = 1 << 20; + p->ignoreErrors = False; + + #ifndef Z7_ST + p->numThreads = 1; + p->inBufSize_MT = 1 << 18; + p->memUseMax = sizeof(size_t) << 28; + #endif +} + + + +#ifndef Z7_ST + +/* ---------- CXzDecMtThread ---------- */ + +typedef struct +{ + Byte *outBuf; + size_t outBufSize; + size_t outPreSize; + size_t inPreSize; + size_t inPreHeaderSize; + size_t blockPackSize_for_Index; // including block header and checksum. + size_t blockPackTotal; // including stream header, block header and checksum. + size_t inCodeSize; + size_t outCodeSize; + ECoderStatus status; + SRes codeRes; + BoolInt skipMode; + // BoolInt finishedWithMark; + EMtDecParseState parseState; + BoolInt parsing_Truncated; + BoolInt atBlockHeader; + CXzStreamFlags streamFlags; + // UInt64 numFinishedStreams + UInt64 numStreams; + UInt64 numTotalBlocks; + UInt64 numBlocks; + + BoolInt dec_created; + CXzUnpacker dec; + + Byte mtPad[1 << 7]; +} CXzDecMtThread; + +#endif + + +/* ---------- CXzDecMt ---------- */ + +struct CXzDecMt +{ + CAlignOffsetAlloc alignOffsetAlloc; + ISzAllocPtr allocMid; + + CXzDecMtProps props; + size_t unpackBlockMaxSize; + + ISeqInStreamPtr inStream; + ISeqOutStreamPtr outStream; + ICompressProgressPtr progress; + + BoolInt finishMode; + BoolInt outSize_Defined; + UInt64 outSize; + + UInt64 outProcessed; + UInt64 inProcessed; + UInt64 readProcessed; + BoolInt readWasFinished; + SRes readRes; + SRes writeRes; + + Byte *outBuf; + size_t outBufSize; + Byte *inBuf; + size_t inBufSize; + + CXzUnpacker dec; + + ECoderStatus status; + SRes codeRes; + + #ifndef Z7_ST + BoolInt mainDecoderWasCalled; + // int statErrorDefined; + int finishedDecoderIndex; + + // global values that are used in Parse stage + CXzStreamFlags streamFlags; + // UInt64 numFinishedStreams + UInt64 numStreams; + UInt64 numTotalBlocks; + UInt64 numBlocks; + + // UInt64 numBadBlocks; + SRes mainErrorCode; // it's set to error code, if the size Code() output doesn't patch the size from Parsing stage + // it can be = SZ_ERROR_INPUT_EOF + // it can be = SZ_ERROR_DATA, in some another cases + BoolInt isBlockHeaderState_Parse; + BoolInt isBlockHeaderState_Write; + UInt64 outProcessed_Parse; + BoolInt parsing_Truncated; + + BoolInt mtc_WasConstructed; + CMtDec mtc; + CXzDecMtThread coders[MTDEC_THREADS_MAX]; + #endif +}; + + + +CXzDecMtHandle XzDecMt_Create(ISzAllocPtr alloc, ISzAllocPtr allocMid) +{ + CXzDecMt *p = (CXzDecMt *)ISzAlloc_Alloc(alloc, sizeof(CXzDecMt)); + if (!p) + return NULL; + + AlignOffsetAlloc_CreateVTable(&p->alignOffsetAlloc); + p->alignOffsetAlloc.baseAlloc = alloc; + p->alignOffsetAlloc.numAlignBits = 7; + p->alignOffsetAlloc.offset = 0; + + p->allocMid = allocMid; + + p->outBuf = NULL; + p->outBufSize = 0; + p->inBuf = NULL; + p->inBufSize = 0; + + XzUnpacker_Construct(&p->dec, &p->alignOffsetAlloc.vt); + + p->unpackBlockMaxSize = 0; + + XzDecMtProps_Init(&p->props); + + #ifndef Z7_ST + p->mtc_WasConstructed = False; + { + unsigned i; + for (i = 0; i < MTDEC_THREADS_MAX; i++) + { + CXzDecMtThread *coder = &p->coders[i]; + coder->dec_created = False; + coder->outBuf = NULL; + coder->outBufSize = 0; + } + } + #endif + + return (CXzDecMtHandle)p; +} + + +#ifndef Z7_ST + +static void XzDecMt_FreeOutBufs(CXzDecMt *p) +{ + unsigned i; + for (i = 0; i < MTDEC_THREADS_MAX; i++) + { + CXzDecMtThread *coder = &p->coders[i]; + if (coder->outBuf) + { + ISzAlloc_Free(p->allocMid, coder->outBuf); + coder->outBuf = NULL; + coder->outBufSize = 0; + } + } + p->unpackBlockMaxSize = 0; +} + +#endif + + + +static void XzDecMt_FreeSt(CXzDecMt *p) +{ + XzUnpacker_Free(&p->dec); + + if (p->outBuf) + { + ISzAlloc_Free(p->allocMid, p->outBuf); + p->outBuf = NULL; + } + p->outBufSize = 0; + + if (p->inBuf) + { + ISzAlloc_Free(p->allocMid, p->inBuf); + p->inBuf = NULL; + } + p->inBufSize = 0; +} + + +// #define GET_CXzDecMt_p CXzDecMt *p = pp; + +void XzDecMt_Destroy(CXzDecMtHandle p) +{ + // GET_CXzDecMt_p + + XzDecMt_FreeSt(p); + + #ifndef Z7_ST + + if (p->mtc_WasConstructed) + { + MtDec_Destruct(&p->mtc); + p->mtc_WasConstructed = False; + } + { + unsigned i; + for (i = 0; i < MTDEC_THREADS_MAX; i++) + { + CXzDecMtThread *t = &p->coders[i]; + if (t->dec_created) + { + // we don't need to free dict here + XzUnpacker_Free(&t->dec); + t->dec_created = False; + } + } + } + XzDecMt_FreeOutBufs(p); + + #endif + + ISzAlloc_Free(p->alignOffsetAlloc.baseAlloc, p); +} + + + +#ifndef Z7_ST + +static void XzDecMt_Callback_Parse(void *obj, unsigned coderIndex, CMtDecCallbackInfo *cc) +{ + CXzDecMt *me = (CXzDecMt *)obj; + CXzDecMtThread *coder = &me->coders[coderIndex]; + size_t srcSize = cc->srcSize; + + cc->srcSize = 0; + cc->outPos = 0; + cc->state = MTDEC_PARSE_CONTINUE; + + cc->canCreateNewThread = True; + + if (cc->startCall) + { + coder->outPreSize = 0; + coder->inPreSize = 0; + coder->inPreHeaderSize = 0; + coder->parseState = MTDEC_PARSE_CONTINUE; + coder->parsing_Truncated = False; + coder->skipMode = False; + coder->codeRes = SZ_OK; + coder->status = CODER_STATUS_NOT_SPECIFIED; + coder->inCodeSize = 0; + coder->outCodeSize = 0; + + coder->numStreams = me->numStreams; + coder->numTotalBlocks = me->numTotalBlocks; + coder->numBlocks = me->numBlocks; + + if (!coder->dec_created) + { + XzUnpacker_Construct(&coder->dec, &me->alignOffsetAlloc.vt); + coder->dec_created = True; + } + + XzUnpacker_Init(&coder->dec); + + if (me->isBlockHeaderState_Parse) + { + coder->dec.streamFlags = me->streamFlags; + coder->atBlockHeader = True; + XzUnpacker_PrepareToRandomBlockDecoding(&coder->dec); + } + else + { + coder->atBlockHeader = False; + me->isBlockHeaderState_Parse = True; + } + + coder->dec.numStartedStreams = me->numStreams; + coder->dec.numTotalBlocks = me->numTotalBlocks; + coder->dec.numBlocks = me->numBlocks; + } + + while (!coder->skipMode) + { + ECoderStatus status; + SRes res; + size_t srcSize2 = srcSize; + size_t destSize = (size_t)0 - 1; + + coder->dec.parseMode = True; + coder->dec.headerParsedOk = False; + + PRF_STR_INT("Parse", srcSize2) + + res = XzUnpacker_Code(&coder->dec, + NULL, &destSize, + cc->src, &srcSize2, cc->srcFinished, + CODER_FINISH_END, &status); + + // PRF(printf(" res = %d, srcSize2 = %d", res, (unsigned)srcSize2)); + + coder->codeRes = res; + coder->status = status; + cc->srcSize += srcSize2; + srcSize -= srcSize2; + coder->inPreHeaderSize += srcSize2; + coder->inPreSize = coder->inPreHeaderSize; + + if (res != SZ_OK) + { + cc->state = + coder->parseState = MTDEC_PARSE_END; + /* + if (res == SZ_ERROR_MEM) + return res; + return SZ_OK; + */ + return; // res; + } + + if (coder->dec.headerParsedOk) + { + const CXzBlock *block = &coder->dec.block; + if (XzBlock_HasUnpackSize(block) + // && block->unpackSize <= me->props.outBlockMax + && XzBlock_HasPackSize(block)) + { + { + if (block->unpackSize * 2 * me->mtc.numStartedThreads > me->props.memUseMax) + { + cc->state = MTDEC_PARSE_OVERFLOW; + return; // SZ_OK; + } + } + { + const UInt64 packSize = block->packSize; + const UInt64 packSizeAligned = packSize + ((0 - (unsigned)packSize) & 3); + const unsigned checkSize = XzFlags_GetCheckSize(coder->dec.streamFlags); + const UInt64 blockPackSum = coder->inPreSize + packSizeAligned + checkSize; + // if (blockPackSum <= me->props.inBlockMax) + // unpackBlockMaxSize + { + coder->blockPackSize_for_Index = (size_t)(coder->dec.blockHeaderSize + packSize + checkSize); + coder->blockPackTotal = (size_t)blockPackSum; + coder->outPreSize = (size_t)block->unpackSize; + coder->streamFlags = coder->dec.streamFlags; + me->streamFlags = coder->dec.streamFlags; + coder->skipMode = True; + break; + } + } + } + } + else + // if (coder->inPreSize <= me->props.inBlockMax) + { + if (!cc->srcFinished) + return; // SZ_OK; + cc->state = + coder->parseState = MTDEC_PARSE_END; + return; // SZ_OK; + } + cc->state = MTDEC_PARSE_OVERFLOW; + return; // SZ_OK; + } + + // ---------- skipMode ---------- + { + UInt64 rem = coder->blockPackTotal - coder->inPreSize; + size_t cur = srcSize; + if (cur > rem) + cur = (size_t)rem; + cc->srcSize += cur; + coder->inPreSize += cur; + srcSize -= cur; + + if (coder->inPreSize == coder->blockPackTotal) + { + if (srcSize == 0) + { + if (!cc->srcFinished) + return; // SZ_OK; + cc->state = MTDEC_PARSE_END; + } + else if ((cc->src)[cc->srcSize] == 0) // we check control byte of next block + cc->state = MTDEC_PARSE_END; + else + { + cc->state = MTDEC_PARSE_NEW; + + { + size_t blockMax = me->unpackBlockMaxSize; + if (blockMax < coder->outPreSize) + blockMax = coder->outPreSize; + { + UInt64 required = (UInt64)blockMax * (me->mtc.numStartedThreads + 1) * 2; + if (me->props.memUseMax < required) + cc->canCreateNewThread = False; + } + } + + if (me->outSize_Defined) + { + // next block can be zero size + const UInt64 rem2 = me->outSize - me->outProcessed_Parse; + if (rem2 < coder->outPreSize) + { + coder->parsing_Truncated = True; + cc->state = MTDEC_PARSE_END; + } + me->outProcessed_Parse += coder->outPreSize; + } + } + } + else if (cc->srcFinished) + cc->state = MTDEC_PARSE_END; + else + return; // SZ_OK; + + coder->parseState = cc->state; + cc->outPos = coder->outPreSize; + + me->numStreams = coder->dec.numStartedStreams; + me->numTotalBlocks = coder->dec.numTotalBlocks; + me->numBlocks = coder->dec.numBlocks + 1; + return; // SZ_OK; + } +} + + +static SRes XzDecMt_Callback_PreCode(void *pp, unsigned coderIndex) +{ + CXzDecMt *me = (CXzDecMt *)pp; + CXzDecMtThread *coder = &me->coders[coderIndex]; + Byte *dest; + + if (!coder->dec.headerParsedOk) + return SZ_OK; + + dest = coder->outBuf; + + if (!dest || coder->outBufSize < coder->outPreSize) + { + if (dest) + { + ISzAlloc_Free(me->allocMid, dest); + coder->outBuf = NULL; + coder->outBufSize = 0; + } + { + size_t outPreSize = coder->outPreSize; + if (outPreSize == 0) + outPreSize = 1; + dest = (Byte *)ISzAlloc_Alloc(me->allocMid, outPreSize); + } + if (!dest) + return SZ_ERROR_MEM; + coder->outBuf = dest; + coder->outBufSize = coder->outPreSize; + + if (coder->outBufSize > me->unpackBlockMaxSize) + me->unpackBlockMaxSize = coder->outBufSize; + } + + // return SZ_ERROR_MEM; + + XzUnpacker_SetOutBuf(&coder->dec, coder->outBuf, coder->outBufSize); + + { + SRes res = XzDecMix_Init(&coder->dec.decoder, &coder->dec.block, coder->outBuf, coder->outBufSize); + // res = SZ_ERROR_UNSUPPORTED; // to test + coder->codeRes = res; + if (res != SZ_OK) + { + // if (res == SZ_ERROR_MEM) return res; + if (me->props.ignoreErrors && res != SZ_ERROR_MEM) + return SZ_OK; + return res; + } + } + + return SZ_OK; +} + + +static SRes XzDecMt_Callback_Code(void *pp, unsigned coderIndex, + const Byte *src, size_t srcSize, int srcFinished, + // int finished, int blockFinished, + UInt64 *inCodePos, UInt64 *outCodePos, int *stop) +{ + CXzDecMt *me = (CXzDecMt *)pp; + CXzDecMtThread *coder = &me->coders[coderIndex]; + + *inCodePos = coder->inCodeSize; + *outCodePos = coder->outCodeSize; + *stop = True; + + if (srcSize > coder->inPreSize - coder->inCodeSize) + return SZ_ERROR_FAIL; + + if (coder->inCodeSize < coder->inPreHeaderSize) + { + size_t step = coder->inPreHeaderSize - coder->inCodeSize; + if (step > srcSize) + step = srcSize; + src += step; + srcSize -= step; + coder->inCodeSize += step; + *inCodePos = coder->inCodeSize; + if (coder->inCodeSize < coder->inPreHeaderSize) + { + *stop = False; + return SZ_OK; + } + } + + if (!coder->dec.headerParsedOk) + return SZ_OK; + if (!coder->outBuf) + return SZ_OK; + + if (coder->codeRes == SZ_OK) + { + ECoderStatus status; + SRes res; + size_t srcProcessed = srcSize; + size_t outSizeCur = coder->outPreSize - coder->dec.outDataWritten; + + // PRF(printf("\nCallback_Code: Code %d %d\n", (unsigned)srcSize, (unsigned)outSizeCur)); + + res = XzUnpacker_Code(&coder->dec, + NULL, &outSizeCur, + src, &srcProcessed, srcFinished, + // coder->finishedWithMark ? CODER_FINISH_END : CODER_FINISH_ANY, + CODER_FINISH_END, + &status); + + // PRF(printf(" res = %d, srcSize2 = %d, outSizeCur = %d", res, (unsigned)srcProcessed, (unsigned)outSizeCur)); + + coder->codeRes = res; + coder->status = status; + coder->inCodeSize += srcProcessed; + coder->outCodeSize = coder->dec.outDataWritten; + *inCodePos = coder->inCodeSize; + *outCodePos = coder->outCodeSize; + + if (res == SZ_OK) + { + if (srcProcessed == srcSize) + *stop = False; + return SZ_OK; + } + } + + if (me->props.ignoreErrors && coder->codeRes != SZ_ERROR_MEM) + { + *inCodePos = coder->inPreSize; + *outCodePos = coder->outPreSize; + return SZ_OK; + } + return coder->codeRes; +} + + +#define XZDECMT_STREAM_WRITE_STEP (1 << 24) + +static SRes XzDecMt_Callback_Write(void *pp, unsigned coderIndex, + BoolInt needWriteToStream, + const Byte *src, size_t srcSize, BoolInt isCross, + // int srcFinished, + BoolInt *needContinue, + BoolInt *canRecode) +{ + CXzDecMt *me = (CXzDecMt *)pp; + const CXzDecMtThread *coder = &me->coders[coderIndex]; + + // PRF(printf("\nWrite processed = %d srcSize = %d\n", (unsigned)me->mtc.inProcessed, (unsigned)srcSize)); + + *needContinue = False; + *canRecode = True; + + if (!needWriteToStream) + return SZ_OK; + + if (!coder->dec.headerParsedOk || !coder->outBuf) + { + if (me->finishedDecoderIndex < 0) + me->finishedDecoderIndex = (int)coderIndex; + return SZ_OK; + } + + if (me->finishedDecoderIndex >= 0) + return SZ_OK; + + me->mtc.inProcessed += coder->inCodeSize; + + *canRecode = False; + + { + SRes res; + size_t size = coder->outCodeSize; + Byte *data = coder->outBuf; + + // we use in me->dec: sha, numBlocks, indexSize + + if (!me->isBlockHeaderState_Write) + { + XzUnpacker_PrepareToRandomBlockDecoding(&me->dec); + me->dec.decodeOnlyOneBlock = False; + me->dec.numStartedStreams = coder->dec.numStartedStreams; + me->dec.streamFlags = coder->streamFlags; + + me->isBlockHeaderState_Write = True; + } + + me->dec.numTotalBlocks = coder->dec.numTotalBlocks; + XzUnpacker_UpdateIndex(&me->dec, coder->blockPackSize_for_Index, coder->outPreSize); + + if (coder->outPreSize != size) + { + if (me->props.ignoreErrors) + { + memset(data + size, 0, coder->outPreSize - size); + size = coder->outPreSize; + } + // me->numBadBlocks++; + if (me->mainErrorCode == SZ_OK) + { + if ((int)coder->status == LZMA_STATUS_NEEDS_MORE_INPUT) + me->mainErrorCode = SZ_ERROR_INPUT_EOF; + else + me->mainErrorCode = SZ_ERROR_DATA; + } + } + + if (me->writeRes != SZ_OK) + return me->writeRes; + + res = SZ_OK; + { + if (me->outSize_Defined) + { + const UInt64 rem = me->outSize - me->outProcessed; + if (size > rem) + size = (SizeT)rem; + } + + for (;;) + { + size_t cur = size; + size_t written; + if (cur > XZDECMT_STREAM_WRITE_STEP) + cur = XZDECMT_STREAM_WRITE_STEP; + + written = ISeqOutStream_Write(me->outStream, data, cur); + + // PRF(printf("\nWritten ask = %d written = %d\n", (unsigned)cur, (unsigned)written)); + + me->outProcessed += written; + if (written != cur) + { + me->writeRes = SZ_ERROR_WRITE; + res = me->writeRes; + break; + } + data += cur; + size -= cur; + // PRF_STR_INT("Written size =", size) + if (size == 0) + break; + res = MtProgress_ProgressAdd(&me->mtc.mtProgress, 0, 0); + if (res != SZ_OK) + break; + } + } + + if (coder->codeRes != SZ_OK) + if (!me->props.ignoreErrors) + { + me->finishedDecoderIndex = (int)coderIndex; + return res; + } + + RINOK(res) + + if (coder->inPreSize != coder->inCodeSize + || coder->blockPackTotal != coder->inCodeSize) + { + me->finishedDecoderIndex = (int)coderIndex; + return SZ_OK; + } + + if (coder->parseState != MTDEC_PARSE_END) + { + *needContinue = True; + return SZ_OK; + } + } + + // (coder->state == MTDEC_PARSE_END) means that there are no other working threads + // so we can use mtc variables without lock + + PRF_STR_INT("Write MTDEC_PARSE_END", me->mtc.inProcessed) + + me->mtc.mtProgress.totalInSize = me->mtc.inProcessed; + { + CXzUnpacker *dec = &me->dec; + + PRF_STR_INT("PostSingle", srcSize) + + { + size_t srcProcessed = srcSize; + ECoderStatus status; + size_t outSizeCur = 0; + SRes res; + + // dec->decodeOnlyOneBlock = False; + dec->decodeToStreamSignature = True; + + me->mainDecoderWasCalled = True; + + if (coder->parsing_Truncated) + { + me->parsing_Truncated = True; + return SZ_OK; + } + + /* + We have processed all xz-blocks of stream, + And xz unpacker is at XZ_STATE_BLOCK_HEADER state, where + (src) is a pointer to xz-Index structure. + We finish reading of current xz-Stream, including Zero padding after xz-Stream. + We exit, if we reach extra byte (first byte of new-Stream or another data). + But we don't update input stream pointer for that new extra byte. + If extra byte is not correct first byte of xz-signature, + we have SZ_ERROR_NO_ARCHIVE error here. + */ + + res = XzUnpacker_Code(dec, + NULL, &outSizeCur, + src, &srcProcessed, + me->mtc.readWasFinished, // srcFinished + CODER_FINISH_END, // CODER_FINISH_ANY, + &status); + + // res = SZ_ERROR_ARCHIVE; // for failure test + + me->status = status; + me->codeRes = res; + + if (isCross) + me->mtc.crossStart += srcProcessed; + + me->mtc.inProcessed += srcProcessed; + me->mtc.mtProgress.totalInSize = me->mtc.inProcessed; + + srcSize -= srcProcessed; + src += srcProcessed; + + if (res != SZ_OK) + { + return SZ_OK; + // return res; + } + + if (dec->state == XZ_STATE_STREAM_HEADER) + { + *needContinue = True; + me->isBlockHeaderState_Parse = False; + me->isBlockHeaderState_Write = False; + + if (!isCross) + { + Byte *crossBuf = MtDec_GetCrossBuff(&me->mtc); + if (!crossBuf) + return SZ_ERROR_MEM; + if (srcSize != 0) + memcpy(crossBuf, src, srcSize); + me->mtc.crossStart = 0; + me->mtc.crossEnd = srcSize; + } + + PRF_STR_INT("XZ_STATE_STREAM_HEADER crossEnd = ", (unsigned)me->mtc.crossEnd) + + return SZ_OK; + } + + if (status != CODER_STATUS_NEEDS_MORE_INPUT || srcSize != 0) + { + return SZ_ERROR_FAIL; + } + + if (me->mtc.readWasFinished) + { + return SZ_OK; + } + } + + { + size_t inPos; + size_t inLim; + // const Byte *inData; + UInt64 inProgressPrev = me->mtc.inProcessed; + + // XzDecMt_Prepare_InBuf_ST(p); + Byte *crossBuf = MtDec_GetCrossBuff(&me->mtc); + if (!crossBuf) + return SZ_ERROR_MEM; + + inPos = 0; + inLim = 0; + + // inData = crossBuf; + + for (;;) + { + SizeT inProcessed; + SizeT outProcessed; + ECoderStatus status; + SRes res; + + if (inPos == inLim) + { + if (!me->mtc.readWasFinished) + { + inPos = 0; + inLim = me->mtc.inBufSize; + me->mtc.readRes = ISeqInStream_Read(me->inStream, (void *)crossBuf, &inLim); + me->mtc.readProcessed += inLim; + if (inLim == 0 || me->mtc.readRes != SZ_OK) + me->mtc.readWasFinished = True; + } + } + + inProcessed = inLim - inPos; + outProcessed = 0; + + res = XzUnpacker_Code(dec, + NULL, &outProcessed, + crossBuf + inPos, &inProcessed, + (inProcessed == 0), // srcFinished + CODER_FINISH_END, &status); + + me->codeRes = res; + me->status = status; + inPos += inProcessed; + me->mtc.inProcessed += inProcessed; + me->mtc.mtProgress.totalInSize = me->mtc.inProcessed; + + if (res != SZ_OK) + { + return SZ_OK; + // return res; + } + + if (dec->state == XZ_STATE_STREAM_HEADER) + { + *needContinue = True; + me->mtc.crossStart = inPos; + me->mtc.crossEnd = inLim; + me->isBlockHeaderState_Parse = False; + me->isBlockHeaderState_Write = False; + return SZ_OK; + } + + if (status != CODER_STATUS_NEEDS_MORE_INPUT) + return SZ_ERROR_FAIL; + + if (me->mtc.progress) + { + UInt64 inDelta = me->mtc.inProcessed - inProgressPrev; + if (inDelta >= (1 << 22)) + { + RINOK(MtProgress_Progress_ST(&me->mtc.mtProgress)) + inProgressPrev = me->mtc.inProcessed; + } + } + if (me->mtc.readWasFinished) + return SZ_OK; + } + } + } +} + + +#endif + + + +void XzStatInfo_Clear(CXzStatInfo *p) +{ + p->InSize = 0; + p->OutSize = 0; + + p->NumStreams = 0; + p->NumBlocks = 0; + + p->UnpackSize_Defined = False; + + p->NumStreams_Defined = False; + p->NumBlocks_Defined = False; + + p->DataAfterEnd = False; + p->DecodingTruncated = False; + + p->DecodeRes = SZ_OK; + p->ReadRes = SZ_OK; + p->ProgressRes = SZ_OK; + + p->CombinedRes = SZ_OK; + p->CombinedRes_Type = SZ_OK; +} + + + +/* + XzDecMt_Decode_ST() can return SZ_OK or the following errors + - SZ_ERROR_MEM for memory allocation error + - error from XzUnpacker_Code() function + - SZ_ERROR_WRITE for ISeqOutStream::Write(). stat->CombinedRes_Type = SZ_ERROR_WRITE in that case + - ICompressProgress::Progress() error, stat->CombinedRes_Type = SZ_ERROR_PROGRESS. + But XzDecMt_Decode_ST() doesn't return ISeqInStream::Read() errors. + ISeqInStream::Read() result is set to p->readRes. + also it can set stat->CombinedRes_Type to SZ_ERROR_WRITE or SZ_ERROR_PROGRESS. +*/ + +static SRes XzDecMt_Decode_ST(CXzDecMt *p + #ifndef Z7_ST + , BoolInt tMode + #endif + , CXzStatInfo *stat) +{ + size_t outPos; + size_t inPos, inLim; + const Byte *inData; + UInt64 inPrev, outPrev; + + CXzUnpacker *dec; + + #ifndef Z7_ST + if (tMode) + { + XzDecMt_FreeOutBufs(p); + tMode = (BoolInt)MtDec_PrepareRead(&p->mtc); + } + #endif + + if (!p->outBuf || p->outBufSize != p->props.outStep_ST) + { + ISzAlloc_Free(p->allocMid, p->outBuf); + p->outBufSize = 0; + p->outBuf = (Byte *)ISzAlloc_Alloc(p->allocMid, p->props.outStep_ST); + if (!p->outBuf) + return SZ_ERROR_MEM; + p->outBufSize = p->props.outStep_ST; + } + + if (!p->inBuf || p->inBufSize != p->props.inBufSize_ST) + { + ISzAlloc_Free(p->allocMid, p->inBuf); + p->inBufSize = 0; + p->inBuf = (Byte *)ISzAlloc_Alloc(p->allocMid, p->props.inBufSize_ST); + if (!p->inBuf) + return SZ_ERROR_MEM; + p->inBufSize = p->props.inBufSize_ST; + } + + dec = &p->dec; + dec->decodeToStreamSignature = False; + // dec->decodeOnlyOneBlock = False; + + XzUnpacker_SetOutBuf(dec, NULL, 0); + + inPrev = p->inProcessed; + outPrev = p->outProcessed; + + inPos = 0; + inLim = 0; + inData = NULL; + outPos = 0; + + for (;;) + { + SizeT outSize; + BoolInt finished; + ECoderFinishMode finishMode; + SizeT inProcessed; + ECoderStatus status; + SRes res; + + SizeT outProcessed; + + + + if (inPos == inLim) + { + #ifndef Z7_ST + if (tMode) + { + inData = MtDec_Read(&p->mtc, &inLim); + inPos = 0; + if (inData) + continue; + tMode = False; + inLim = 0; + } + #endif + + if (!p->readWasFinished) + { + inPos = 0; + inLim = p->inBufSize; + inData = p->inBuf; + p->readRes = ISeqInStream_Read(p->inStream, (void *)p->inBuf, &inLim); + p->readProcessed += inLim; + if (inLim == 0 || p->readRes != SZ_OK) + p->readWasFinished = True; + } + } + + outSize = p->props.outStep_ST - outPos; + + finishMode = CODER_FINISH_ANY; + if (p->outSize_Defined) + { + const UInt64 rem = p->outSize - p->outProcessed; + if (outSize >= rem) + { + outSize = (SizeT)rem; + if (p->finishMode) + finishMode = CODER_FINISH_END; + } + } + + inProcessed = inLim - inPos; + outProcessed = outSize; + + res = XzUnpacker_Code(dec, p->outBuf + outPos, &outProcessed, + inData + inPos, &inProcessed, + (inPos == inLim), // srcFinished + finishMode, &status); + + p->codeRes = res; + p->status = status; + + inPos += inProcessed; + outPos += outProcessed; + p->inProcessed += inProcessed; + p->outProcessed += outProcessed; + + finished = ((inProcessed == 0 && outProcessed == 0) || res != SZ_OK); + + if (finished || outProcessed >= outSize) + if (outPos != 0) + { + const size_t written = ISeqOutStream_Write(p->outStream, p->outBuf, outPos); + // p->outProcessed += written; // 21.01: BUG fixed + if (written != outPos) + { + stat->CombinedRes_Type = SZ_ERROR_WRITE; + return SZ_ERROR_WRITE; + } + outPos = 0; + } + + if (p->progress && res == SZ_OK) + { + if (p->inProcessed - inPrev >= (1 << 22) || + p->outProcessed - outPrev >= (1 << 22)) + { + res = ICompressProgress_Progress(p->progress, p->inProcessed, p->outProcessed); + if (res != SZ_OK) + { + stat->CombinedRes_Type = SZ_ERROR_PROGRESS; + stat->ProgressRes = res; + return res; + } + inPrev = p->inProcessed; + outPrev = p->outProcessed; + } + } + + if (finished) + { + // p->codeRes is preliminary error from XzUnpacker_Code. + // and it can be corrected later as final result + // so we return SZ_OK here instead of (res); + return SZ_OK; + // return res; + } + } +} + + + +/* +XzStatInfo_SetStat() transforms + CXzUnpacker return code and status to combined CXzStatInfo results. + it can convert SZ_OK to SZ_ERROR_INPUT_EOF + it can convert SZ_ERROR_NO_ARCHIVE to SZ_OK and (DataAfterEnd = 1) +*/ + +static void XzStatInfo_SetStat(const CXzUnpacker *dec, + int finishMode, + // UInt64 readProcessed, + UInt64 inProcessed, + SRes res, // it's result from CXzUnpacker unpacker + ECoderStatus status, + BoolInt decodingTruncated, + CXzStatInfo *stat) +{ + UInt64 extraSize; + + stat->DecodingTruncated = (Byte)(decodingTruncated ? 1 : 0); + stat->InSize = inProcessed; + stat->NumStreams = dec->numStartedStreams; + stat->NumBlocks = dec->numTotalBlocks; + + stat->UnpackSize_Defined = True; + stat->NumStreams_Defined = True; + stat->NumBlocks_Defined = True; + + extraSize = XzUnpacker_GetExtraSize(dec); + + if (res == SZ_OK) + { + if (status == CODER_STATUS_NEEDS_MORE_INPUT) + { + // CODER_STATUS_NEEDS_MORE_INPUT is expected status for correct xz streams + // any extra data is part of correct data + extraSize = 0; + // if xz stream was not finished, then we need more data + if (!XzUnpacker_IsStreamWasFinished(dec)) + res = SZ_ERROR_INPUT_EOF; + } + else + { + // CODER_STATUS_FINISHED_WITH_MARK is not possible for multi stream xz decoding + // so he we have (status == CODER_STATUS_NOT_FINISHED) + // if (status != CODER_STATUS_FINISHED_WITH_MARK) + if (!decodingTruncated || finishMode) + res = SZ_ERROR_DATA; + } + } + else if (res == SZ_ERROR_NO_ARCHIVE) + { + /* + SZ_ERROR_NO_ARCHIVE is possible for 2 states: + XZ_STATE_STREAM_HEADER - if bad signature or bad CRC + XZ_STATE_STREAM_PADDING - if non-zero padding data + extraSize and inProcessed don't include "bad" byte + */ + // if (inProcessed == extraSize), there was no any good xz stream header, and we keep error + if (inProcessed != extraSize) // if there were good xz streams before error + { + // if (extraSize != 0 || readProcessed != inProcessed) + { + // he we suppose that all xz streams were finsihed OK, and we have + // some extra data after all streams + stat->DataAfterEnd = True; + res = SZ_OK; + } + } + } + + if (stat->DecodeRes == SZ_OK) + stat->DecodeRes = res; + + stat->InSize -= extraSize; +} + + + +SRes XzDecMt_Decode(CXzDecMtHandle p, + const CXzDecMtProps *props, + const UInt64 *outDataSize, int finishMode, + ISeqOutStreamPtr outStream, + // Byte *outBuf, size_t *outBufSize, + ISeqInStreamPtr inStream, + // const Byte *inData, size_t inDataSize, + CXzStatInfo *stat, + int *isMT, + ICompressProgressPtr progress) +{ + // GET_CXzDecMt_p + #ifndef Z7_ST + BoolInt tMode; + #endif + + XzStatInfo_Clear(stat); + + p->props = *props; + + p->inStream = inStream; + p->outStream = outStream; + p->progress = progress; + // p->stat = stat; + + p->outSize = 0; + p->outSize_Defined = False; + if (outDataSize) + { + p->outSize_Defined = True; + p->outSize = *outDataSize; + } + + p->finishMode = (BoolInt)finishMode; + + // p->outSize = 457; p->outSize_Defined = True; p->finishMode = False; // for test + + p->writeRes = SZ_OK; + p->outProcessed = 0; + p->inProcessed = 0; + p->readProcessed = 0; + p->readWasFinished = False; + p->readRes = SZ_OK; + + p->codeRes = SZ_OK; + p->status = CODER_STATUS_NOT_SPECIFIED; + + XzUnpacker_Init(&p->dec); + + *isMT = False; + + /* + p->outBuf = NULL; + p->outBufSize = 0; + if (!outStream) + { + p->outBuf = outBuf; + p->outBufSize = *outBufSize; + *outBufSize = 0; + } + */ + + + #ifndef Z7_ST + + p->isBlockHeaderState_Parse = False; + p->isBlockHeaderState_Write = False; + // p->numBadBlocks = 0; + p->mainErrorCode = SZ_OK; + p->mainDecoderWasCalled = False; + + tMode = False; + + if (p->props.numThreads > 1) + { + IMtDecCallback2 vt; + BoolInt needContinue; + SRes res; + // we just free ST buffers here + // but we still keep state variables, that was set in XzUnpacker_Init() + XzDecMt_FreeSt(p); + + p->outProcessed_Parse = 0; + p->parsing_Truncated = False; + + p->numStreams = 0; + p->numTotalBlocks = 0; + p->numBlocks = 0; + p->finishedDecoderIndex = -1; + + if (!p->mtc_WasConstructed) + { + p->mtc_WasConstructed = True; + MtDec_Construct(&p->mtc); + } + + p->mtc.mtCallback = &vt; + p->mtc.mtCallbackObject = p; + + p->mtc.progress = progress; + p->mtc.inStream = inStream; + p->mtc.alloc = &p->alignOffsetAlloc.vt; + // p->mtc.inData = inData; + // p->mtc.inDataSize = inDataSize; + p->mtc.inBufSize = p->props.inBufSize_MT; + // p->mtc.inBlockMax = p->props.inBlockMax; + p->mtc.numThreadsMax = p->props.numThreads; + + *isMT = True; + + vt.Parse = XzDecMt_Callback_Parse; + vt.PreCode = XzDecMt_Callback_PreCode; + vt.Code = XzDecMt_Callback_Code; + vt.Write = XzDecMt_Callback_Write; + + + res = MtDec_Code(&p->mtc); + + + stat->InSize = p->mtc.inProcessed; + + p->inProcessed = p->mtc.inProcessed; + p->readRes = p->mtc.readRes; + p->readWasFinished = p->mtc.readWasFinished; + p->readProcessed = p->mtc.readProcessed; + + tMode = True; + needContinue = False; + + if (res == SZ_OK) + { + if (p->mtc.mtProgress.res != SZ_OK) + { + res = p->mtc.mtProgress.res; + stat->ProgressRes = res; + stat->CombinedRes_Type = SZ_ERROR_PROGRESS; + } + else + needContinue = p->mtc.needContinue; + } + + if (!needContinue) + { + { + SRes codeRes; + BoolInt truncated = False; + ECoderStatus status; + const CXzUnpacker *dec; + + stat->OutSize = p->outProcessed; + + if (p->finishedDecoderIndex >= 0) + { + const CXzDecMtThread *coder = &p->coders[(unsigned)p->finishedDecoderIndex]; + codeRes = coder->codeRes; + dec = &coder->dec; + status = coder->status; + } + else if (p->mainDecoderWasCalled) + { + codeRes = p->codeRes; + dec = &p->dec; + status = p->status; + truncated = p->parsing_Truncated; + } + else + return SZ_ERROR_FAIL; + + if (p->mainErrorCode != SZ_OK) + stat->DecodeRes = p->mainErrorCode; + + XzStatInfo_SetStat(dec, p->finishMode, + // p->mtc.readProcessed, + p->mtc.inProcessed, + codeRes, status, + truncated, + stat); + } + + if (res == SZ_OK) + { + stat->ReadRes = p->mtc.readRes; + + if (p->writeRes != SZ_OK) + { + res = p->writeRes; + stat->CombinedRes_Type = SZ_ERROR_WRITE; + } + else if (p->mtc.readRes != SZ_OK + // && p->mtc.inProcessed == p->mtc.readProcessed + && stat->DecodeRes == SZ_ERROR_INPUT_EOF) + { + res = p->mtc.readRes; + stat->CombinedRes_Type = SZ_ERROR_READ; + } + else if (stat->DecodeRes != SZ_OK) + res = stat->DecodeRes; + } + + stat->CombinedRes = res; + if (stat->CombinedRes_Type == SZ_OK) + stat->CombinedRes_Type = res; + return res; + } + + PRF_STR("----- decoding ST -----") + } + + #endif + + + *isMT = False; + + { + SRes res = XzDecMt_Decode_ST(p + #ifndef Z7_ST + , tMode + #endif + , stat + ); + + #ifndef Z7_ST + // we must set error code from MT decoding at first + if (p->mainErrorCode != SZ_OK) + stat->DecodeRes = p->mainErrorCode; + #endif + + XzStatInfo_SetStat(&p->dec, + p->finishMode, + // p->readProcessed, + p->inProcessed, + p->codeRes, p->status, + False, // truncated + stat); + + stat->ReadRes = p->readRes; + + if (res == SZ_OK) + { + if (p->readRes != SZ_OK + // && p->inProcessed == p->readProcessed + && stat->DecodeRes == SZ_ERROR_INPUT_EOF) + { + // we set read error as combined error, only if that error was the reason + // of decoding problem + res = p->readRes; + stat->CombinedRes_Type = SZ_ERROR_READ; + } + else if (stat->DecodeRes != SZ_OK) + res = stat->DecodeRes; + } + + stat->CombinedRes = res; + if (stat->CombinedRes_Type == SZ_OK) + stat->CombinedRes_Type = res; + return res; + } +} + +#undef PRF +#undef PRF_STR +#undef PRF_STR_INT_2 diff --git a/src/plugins/7zip/7za/c/XzEnc.c b/src/plugins/7zip/7za/c/XzEnc.c index ccac7a8e..e40f0c88 100644 --- a/src/plugins/7zip/7za/c/XzEnc.c +++ b/src/plugins/7zip/7za/c/XzEnc.c @@ -1,5 +1,5 @@ /* XzEnc.c -- Xz Encode -2015-09-16 : Igor Pavlov : Public domain */ +: Igor Pavlov : Public domain */ #include "Precomp.h" @@ -7,7 +7,6 @@ #include #include "7zCrc.h" -#include "Alloc.h" #include "Bra.h" #include "CpuArch.h" @@ -19,23 +18,43 @@ #include "XzEnc.h" -#define XzBlock_ClearFlags(p) (p)->flags = 0; -#define XzBlock_SetNumFilters(p, n) (p)->flags |= ((n) - 1); +// #define Z7_ST + +#ifndef Z7_ST +#include "MtCoder.h" +#else +#define MTCODER_THREADS_MAX 1 +#define MTCODER_BLOCKS_MAX 1 +#endif + +#define XZ_GET_PAD_SIZE(dataSize) ((4 - ((unsigned)(dataSize) & 3)) & 3) + +#define XZ_CHECK_SIZE_MAX 64 +/* max pack size for LZMA2 block + pad4 + check_size: */ +#define XZ_GET_MAX_BLOCK_PACK_SIZE(unpackSize) ((unpackSize) + ((unpackSize) >> 10) + 16 + XZ_CHECK_SIZE_MAX) + +#define XZ_GET_ESTIMATED_BLOCK_TOTAL_PACK_SIZE(unpackSize) (XZ_BLOCK_HEADER_SIZE_MAX + XZ_GET_MAX_BLOCK_PACK_SIZE(unpackSize)) + + +// #define XzBlock_ClearFlags(p) (p)->flags = 0; +#define XzBlock_ClearFlags_SetNumFilters(p, n) (p)->flags = (Byte)((n) - 1); #define XzBlock_SetHasPackSize(p) (p)->flags |= XZ_BF_PACK_SIZE; #define XzBlock_SetHasUnpackSize(p) (p)->flags |= XZ_BF_UNPACK_SIZE; -static SRes WriteBytes(ISeqOutStream *s, const void *buf, UInt32 size) + +static SRes WriteBytes(ISeqOutStreamPtr s, const void *buf, size_t size) { - return (s->Write(s, buf, size) == size) ? SZ_OK : SZ_ERROR_WRITE; + return (ISeqOutStream_Write(s, buf, size) == size) ? SZ_OK : SZ_ERROR_WRITE; } -static SRes WriteBytesAndCrc(ISeqOutStream *s, const void *buf, UInt32 size, UInt32 *crc) +static SRes WriteBytes_UpdateCrc(ISeqOutStreamPtr s, const void *buf, size_t size, UInt32 *crc) { *crc = CrcUpdate(*crc, buf, size); return WriteBytes(s, buf, size); } -static SRes Xz_WriteHeader(CXzStreamFlags f, ISeqOutStream *s) + +static SRes Xz_WriteHeader(CXzStreamFlags f, ISeqOutStreamPtr s) { UInt32 crc; Byte header[XZ_STREAM_HEADER_SIZE]; @@ -43,12 +62,12 @@ static SRes Xz_WriteHeader(CXzStreamFlags f, ISeqOutStream *s) header[XZ_SIG_SIZE] = (Byte)(f >> 8); header[XZ_SIG_SIZE + 1] = (Byte)(f & 0xFF); crc = CrcCalc(header + XZ_SIG_SIZE, XZ_STREAM_FLAGS_SIZE); - SetUi32(header + XZ_SIG_SIZE + XZ_STREAM_FLAGS_SIZE, crc); + SetUi32(header + XZ_SIG_SIZE + XZ_STREAM_FLAGS_SIZE, crc) return WriteBytes(s, header, XZ_STREAM_HEADER_SIZE); } -static SRes XzBlock_WriteHeader(const CXzBlock *p, ISeqOutStream *s) +static SRes XzBlock_WriteHeader(const CXzBlock *p, ISeqOutStreamPtr s) { Byte header[XZ_BLOCK_HEADER_SIZE_MAX]; @@ -73,103 +92,159 @@ static SRes XzBlock_WriteHeader(const CXzBlock *p, ISeqOutStream *s) header[pos++] = 0; header[0] = (Byte)(pos >> 2); - SetUi32(header + pos, CrcCalc(header, pos)); + SetUi32(header + pos, CrcCalc(header, pos)) return WriteBytes(s, header, pos + 4); } -static SRes Xz_WriteFooter(CXzStream *p, ISeqOutStream *s) + + +typedef struct { - Byte buf[32]; - UInt64 globalPos; - { - UInt32 crc = CRC_INIT_VAL; - unsigned pos = 1 + Xz_WriteVarInt(buf + 1, p->numBlocks); - size_t i; + size_t numBlocks; + size_t size; + size_t allocated; + Byte *blocks; +} CXzEncIndex; - globalPos = pos; - buf[0] = 0; - RINOK(WriteBytesAndCrc(s, buf, pos, &crc)); - for (i = 0; i < p->numBlocks; i++) - { - const CXzBlockSizes *block = &p->blocks[i]; - pos = Xz_WriteVarInt(buf, block->totalSize); - pos += Xz_WriteVarInt(buf + pos, block->unpackSize); - globalPos += pos; - RINOK(WriteBytesAndCrc(s, buf, pos, &crc)); - } - - pos = ((unsigned)globalPos & 3); - - if (pos != 0) - { - buf[0] = buf[1] = buf[2] = 0; - RINOK(WriteBytesAndCrc(s, buf, 4 - pos, &crc)); - globalPos += 4 - pos; - } - { - SetUi32(buf, CRC_GET_DIGEST(crc)); - RINOK(WriteBytes(s, buf, 4)); - globalPos += 4; - } - } +static void XzEncIndex_Construct(CXzEncIndex *p) +{ + p->numBlocks = 0; + p->size = 0; + p->allocated = 0; + p->blocks = NULL; +} +static void XzEncIndex_Init(CXzEncIndex *p) +{ + p->numBlocks = 0; + p->size = 0; +} + +static void XzEncIndex_Free(CXzEncIndex *p, ISzAllocPtr alloc) +{ + if (p->blocks) { - UInt32 indexSize = (UInt32)((globalPos >> 2) - 1); - SetUi32(buf + 4, indexSize); - buf[8] = (Byte)(p->flags >> 8); - buf[9] = (Byte)(p->flags & 0xFF); - SetUi32(buf, CrcCalc(buf + 4, 6)); - memcpy(buf + 10, XZ_FOOTER_SIG, XZ_FOOTER_SIG_SIZE); - return WriteBytes(s, buf, 12); + ISzAlloc_Free(alloc, p->blocks); + p->blocks = NULL; } + p->numBlocks = 0; + p->size = 0; + p->allocated = 0; } -static SRes Xz_AddIndexRecord(CXzStream *p, UInt64 unpackSize, UInt64 totalSize, ISzAlloc *alloc) +static SRes XzEncIndex_ReAlloc(CXzEncIndex *p, size_t newSize, ISzAllocPtr alloc) { - if (!p->blocks || p->numBlocksAllocated == p->numBlocks) + Byte *blocks = (Byte *)ISzAlloc_Alloc(alloc, newSize); + if (!blocks) + return SZ_ERROR_MEM; + if (p->size != 0) + memcpy(blocks, p->blocks, p->size); + if (p->blocks) + ISzAlloc_Free(alloc, p->blocks); + p->blocks = blocks; + p->allocated = newSize; + return SZ_OK; +} + + +static SRes XzEncIndex_PreAlloc(CXzEncIndex *p, UInt64 numBlocks, UInt64 unpackSize, UInt64 totalSize, ISzAllocPtr alloc) +{ + UInt64 pos; { - size_t num = p->numBlocks * 2 + 1; - size_t newSize = sizeof(CXzBlockSizes) * num; - CXzBlockSizes *blocks; - if (newSize / sizeof(CXzBlockSizes) != num) - return SZ_ERROR_MEM; - blocks = (CXzBlockSizes *)alloc->Alloc(alloc, newSize); - if (!blocks) + Byte buf[32]; + unsigned pos2 = Xz_WriteVarInt(buf, totalSize); + pos2 += Xz_WriteVarInt(buf + pos2, unpackSize); + pos = numBlocks * pos2; + } + + if (pos <= p->allocated - p->size) + return SZ_OK; + { + UInt64 newSize64 = p->size + pos; + size_t newSize = (size_t)newSize64; + if (newSize != newSize64) return SZ_ERROR_MEM; - if (p->numBlocks != 0) - { - memcpy(blocks, p->blocks, p->numBlocks * sizeof(CXzBlockSizes)); - alloc->Free(alloc, p->blocks); - } - p->blocks = blocks; - p->numBlocksAllocated = num; + return XzEncIndex_ReAlloc(p, newSize, alloc); } +} + + +static SRes XzEncIndex_AddIndexRecord(CXzEncIndex *p, UInt64 unpackSize, UInt64 totalSize, ISzAllocPtr alloc) +{ + Byte buf[32]; + unsigned pos = Xz_WriteVarInt(buf, totalSize); + pos += Xz_WriteVarInt(buf + pos, unpackSize); + + if (pos > p->allocated - p->size) { - CXzBlockSizes *block = &p->blocks[p->numBlocks++]; - block->unpackSize = unpackSize; - block->totalSize = totalSize; + size_t newSize = p->allocated * 2 + 16 * 2; + if (newSize < p->size + pos) + return SZ_ERROR_MEM; + RINOK(XzEncIndex_ReAlloc(p, newSize, alloc)) } + memcpy(p->blocks + p->size, buf, pos); + p->size += pos; + p->numBlocks++; return SZ_OK; } +static SRes XzEncIndex_WriteFooter(const CXzEncIndex *p, CXzStreamFlags flags, ISeqOutStreamPtr s) +{ + Byte buf[32]; + UInt64 globalPos; + UInt32 crc = CRC_INIT_VAL; + unsigned pos = 1 + Xz_WriteVarInt(buf + 1, p->numBlocks); + + globalPos = pos; + buf[0] = 0; + RINOK(WriteBytes_UpdateCrc(s, buf, pos, &crc)) + RINOK(WriteBytes_UpdateCrc(s, p->blocks, p->size, &crc)) + globalPos += p->size; + + pos = XZ_GET_PAD_SIZE(globalPos); + buf[1] = 0; + buf[2] = 0; + buf[3] = 0; + globalPos += pos; + + crc = CrcUpdate(crc, buf + 4 - pos, pos); + SetUi32(buf + 4, CRC_GET_DIGEST(crc)) + + SetUi32(buf + 8 + 4, (UInt32)(globalPos >> 2)) + buf[8 + 8] = (Byte)(flags >> 8); + buf[8 + 9] = (Byte)(flags & 0xFF); + SetUi32(buf + 8, CrcCalc(buf + 8 + 4, 6)) + buf[8 + 10] = XZ_FOOTER_SIG_0; + buf[8 + 11] = XZ_FOOTER_SIG_1; + + return WriteBytes(s, buf + 4 - pos, pos + 4 + 12); +} + + + /* ---------- CSeqCheckInStream ---------- */ typedef struct { - ISeqInStream p; - ISeqInStream *realStream; + ISeqInStream vt; + ISeqInStreamPtr realStream; + const Byte *data; + UInt64 limit; UInt64 processed; + int realStreamFinished; CXzCheck check; } CSeqCheckInStream; -static void SeqCheckInStream_Init(CSeqCheckInStream *p, unsigned mode) +static void SeqCheckInStream_Init(CSeqCheckInStream *p, unsigned checkMode) { + p->limit = (UInt64)(Int64)-1; p->processed = 0; - XzCheck_Init(&p->check, mode); + p->realStreamFinished = 0; + XzCheck_Init(&p->check, checkMode); } static void SeqCheckInStream_GetDigest(CSeqCheckInStream *p, Byte *digest) @@ -177,12 +252,31 @@ static void SeqCheckInStream_GetDigest(CSeqCheckInStream *p, Byte *digest) XzCheck_Final(&p->check, digest); } -static SRes SeqCheckInStream_Read(void *pp, void *data, size_t *size) +static SRes SeqCheckInStream_Read(ISeqInStreamPtr pp, void *data, size_t *size) { - CSeqCheckInStream *p = (CSeqCheckInStream *)pp; - SRes res = p->realStream->Read(p->realStream, data, size); - XzCheck_Update(&p->check, data, *size); - p->processed += *size; + Z7_CONTAINER_FROM_VTBL_TO_DECL_VAR_pp_vt_p(CSeqCheckInStream) + size_t size2 = *size; + SRes res = SZ_OK; + + if (p->limit != (UInt64)(Int64)-1) + { + UInt64 rem = p->limit - p->processed; + if (size2 > rem) + size2 = (size_t)rem; + } + if (size2 != 0) + { + if (p->realStream) + { + res = ISeqInStream_Read(p->realStream, data, &size2); + p->realStreamFinished = (size2 == 0) ? 1 : 0; + } + else + memcpy(data, p->data + (size_t)p->processed, size2); + XzCheck_Update(&p->check, data, size2); + p->processed += size2; + } + *size = size2; return res; } @@ -191,15 +285,24 @@ static SRes SeqCheckInStream_Read(void *pp, void *data, size_t *size) typedef struct { - ISeqOutStream p; - ISeqOutStream *realStream; + ISeqOutStream vt; + ISeqOutStreamPtr realStream; + Byte *outBuf; + size_t outBufLimit; UInt64 processed; } CSeqSizeOutStream; -static size_t MyWrite(void *pp, const void *data, size_t size) +static size_t SeqSizeOutStream_Write(ISeqOutStreamPtr pp, const void *data, size_t size) { - CSeqSizeOutStream *p = (CSeqSizeOutStream *)pp; - size = p->realStream->Write(p->realStream, data, size); + Z7_CONTAINER_FROM_VTBL_TO_DECL_VAR_pp_vt_p(CSeqSizeOutStream) + if (p->realStream) + size = ISeqOutStream_Write(p->realStream, data, size); + else + { + if (size > p->outBufLimit - (size_t)p->processed) + return 0; + memcpy(p->outBuf + (size_t)p->processed, data, size); + } p->processed += size; return size; } @@ -211,8 +314,8 @@ static size_t MyWrite(void *pp, const void *data, size_t size) typedef struct { - ISeqInStream p; - ISeqInStream *realStream; + ISeqInStream vt; + ISeqInStreamPtr realStream; IStateCoder StateCoder; Byte *buf; size_t curPos; @@ -220,10 +323,63 @@ typedef struct int srcWasFinished; } CSeqInFilter; -static SRes SeqInFilter_Read(void *pp, void *data, size_t *size) + +static const z7_Func_BranchConv g_Funcs_BranchConv_RISC_Enc[] = { - CSeqInFilter *p = (CSeqInFilter *)pp; - size_t sizeOriginal = *size; + Z7_BRANCH_CONV_ENC_2 (BranchConv_PPC), + Z7_BRANCH_CONV_ENC_2 (BranchConv_IA64), + Z7_BRANCH_CONV_ENC_2 (BranchConv_ARM), + Z7_BRANCH_CONV_ENC_2 (BranchConv_ARMT), + Z7_BRANCH_CONV_ENC_2 (BranchConv_SPARC), + Z7_BRANCH_CONV_ENC_2 (BranchConv_ARM64), + Z7_BRANCH_CONV_ENC_2 (BranchConv_RISCV) +}; + +static SizeT XzBcFilterStateBase_Filter_Enc(CXzBcFilterStateBase *p, Byte *data, SizeT size) +{ + switch (p->methodId) + { + case XZ_ID_Delta: + Delta_Encode(p->delta_State, p->delta, data, size); + break; + case XZ_ID_X86: + size = (SizeT)(z7_BranchConvSt_X86_Enc(data, size, p->ip, &p->X86_State) - data); + break; + default: + if (p->methodId >= XZ_ID_PPC) + { + const UInt32 i = p->methodId - XZ_ID_PPC; + if (i < Z7_ARRAY_SIZE(g_Funcs_BranchConv_RISC_Enc)) + size = (SizeT)(g_Funcs_BranchConv_RISC_Enc[i](data, size, p->ip) - data); + } + break; + } + p->ip += (UInt32)size; + return size; +} + + +static SRes SeqInFilter_Init(CSeqInFilter *p, const CXzFilter *props, ISzAllocPtr alloc) +{ + if (!p->buf) + { + p->buf = (Byte *)ISzAlloc_Alloc(alloc, FILTER_BUF_SIZE); + if (!p->buf) + return SZ_ERROR_MEM; + } + p->curPos = p->endPos = 0; + p->srcWasFinished = 0; + RINOK(Xz_StateCoder_Bc_SetFromMethod_Func(&p->StateCoder, props->id, XzBcFilterStateBase_Filter_Enc, alloc)) + RINOK(p->StateCoder.SetProps(p->StateCoder.p, props->props, props->propsSize, alloc)) + p->StateCoder.Init(p->StateCoder.p); + return SZ_OK; +} + + +static SRes SeqInFilter_Read(ISeqInStreamPtr pp, void *data, size_t *size) +{ + Z7_CONTAINER_FROM_VTBL_TO_DECL_VAR_pp_vt_p(CSeqInFilter) + const size_t sizeOriginal = *size; if (sizeOriginal == 0) return SZ_OK; *size = 0; @@ -234,55 +390,48 @@ static SRes SeqInFilter_Read(void *pp, void *data, size_t *size) { p->curPos = 0; p->endPos = FILTER_BUF_SIZE; - RINOK(p->realStream->Read(p->realStream, p->buf, &p->endPos)); + RINOK(ISeqInStream_Read(p->realStream, p->buf, &p->endPos)) if (p->endPos == 0) p->srcWasFinished = 1; } { SizeT srcLen = p->endPos - p->curPos; - int wasFinished; + ECoderStatus status; SRes res; *size = sizeOriginal; - res = p->StateCoder.Code(p->StateCoder.p, data, size, p->buf + p->curPos, &srcLen, - p->srcWasFinished, CODER_FINISH_ANY, &wasFinished); + res = p->StateCoder.Code2(p->StateCoder.p, + (Byte *)data, size, + p->buf + p->curPos, &srcLen, + p->srcWasFinished, CODER_FINISH_ANY, + &status); p->curPos += srcLen; - if (*size != 0 || srcLen == 0 || res != 0) + if (*size != 0 || srcLen == 0 || res != SZ_OK) return res; } } } +Z7_FORCE_INLINE static void SeqInFilter_Construct(CSeqInFilter *p) { p->buf = NULL; - p->p.Read = SeqInFilter_Read; + p->StateCoder.p = NULL; + p->vt.Read = SeqInFilter_Read; } -static void SeqInFilter_Free(CSeqInFilter *p) +Z7_FORCE_INLINE +static void SeqInFilter_Free(CSeqInFilter *p, ISzAllocPtr alloc) { - if (p->buf) + if (p->StateCoder.p) { - g_Alloc.Free(&g_Alloc, p->buf); - p->buf = NULL; + p->StateCoder.Free(p->StateCoder.p, alloc); + p->StateCoder.p = NULL; } -} - -SRes BraState_SetFromMethod(IStateCoder *p, UInt64 id, int encodeMode, ISzAlloc *alloc); - -static SRes SeqInFilter_Init(CSeqInFilter *p, const CXzFilter *props) -{ - if (!p->buf) + if (p->buf) { - p->buf = g_Alloc.Alloc(&g_Alloc, FILTER_BUF_SIZE); - if (!p->buf) - return SZ_ERROR_MEM; + ISzAlloc_Free(alloc, p->buf); + p->buf = NULL; } - p->curPos = p->endPos = 0; - p->srcWasFinished = 0; - RINOK(BraState_SetFromMethod(&p->StateCoder, props->id, 1, &g_Alloc)); - RINOK(p->StateCoder.SetProps(p->StateCoder.p, props->props, props->propsSize, &g_Alloc)); - p->StateCoder.Init(p->StateCoder.p); - return SZ_OK; } @@ -292,24 +441,24 @@ static SRes SeqInFilter_Init(CSeqInFilter *p, const CXzFilter *props) typedef struct { - ISeqInStream p; - ISeqInStream *inStream; + ISeqInStream vt; + ISeqInStreamPtr inStream; CSbEnc enc; } CSbEncInStream; -static SRes SbEncInStream_Read(void *pp, void *data, size_t *size) +static SRes SbEncInStream_Read(ISeqInStreamPtr pp, void *data, size_t *size) { - CSbEncInStream *p = (CSbEncInStream *)pp; + CSbEncInStream *p = Z7_CONTAINER_FROM_VTBL(pp, CSbEncInStream, vt); size_t sizeOriginal = *size; if (sizeOriginal == 0) - return S_OK; + return SZ_OK; for (;;) { if (p->enc.needRead && !p->enc.readWasFinished) { size_t processed = p->enc.needReadSizeMax; - RINOK(p->inStream->Read(p->inStream, p->enc.buf + p->enc.readPos, &processed)); + RINOK(p->inStream->Read(p->inStream, p->enc.buf + p->enc.readPos, &processed)) p->enc.readPos += processed; if (processed == 0) { @@ -320,16 +469,16 @@ static SRes SbEncInStream_Read(void *pp, void *data, size_t *size) } *size = sizeOriginal; - RINOK(SbEnc_Read(&p->enc, data, size)); + RINOK(SbEnc_Read(&p->enc, data, size)) if (*size != 0 || !p->enc.needRead) - return S_OK; + return SZ_OK; } } -void SbEncInStream_Construct(CSbEncInStream *p, ISzAlloc *alloc) +void SbEncInStream_Construct(CSbEncInStream *p, ISzAllocPtr alloc) { SbEnc_Construct(&p->enc, alloc); - p->p.Read = SbEncInStream_Read; + p->vt.Read = SbEncInStream_Read; } SRes SbEncInStream_Init(CSbEncInStream *p) @@ -345,43 +494,236 @@ void SbEncInStream_Free(CSbEncInStream *p) #endif + +/* ---------- CXzProps ---------- */ + + +void XzFilterProps_Init(CXzFilterProps *p) +{ + p->id = 0; + p->delta = 0; + p->ip = 0; + p->ipDefined = False; +} + +void XzProps_Init(CXzProps *p) +{ + p->checkId = XZ_CHECK_CRC32; + p->numThreadGroups = 0; + p->blockSize = XZ_PROPS_BLOCK_SIZE_AUTO; + p->numBlockThreads_Reduced = -1; + p->numBlockThreads_Max = -1; + p->numTotalThreads = -1; + p->reduceSize = (UInt64)(Int64)-1; + p->forceWriteSizesInHeader = 0; + // p->forceWriteSizesInHeader = 1; + + XzFilterProps_Init(&p->filterProps); + Lzma2EncProps_Init(&p->lzma2Props); +} + + +static void XzEncProps_Normalize_Fixed(CXzProps *p) +{ + UInt64 fileSize; + int t1, t1n, t2, t2r, t3; + { + CLzma2EncProps tp = p->lzma2Props; + if (tp.numTotalThreads <= 0) + tp.numTotalThreads = p->numTotalThreads; + Lzma2EncProps_Normalize(&tp); + t1n = tp.numTotalThreads; + } + + t1 = p->lzma2Props.numTotalThreads; + t2 = p->numBlockThreads_Max; + t3 = p->numTotalThreads; + + if (t2 > MTCODER_THREADS_MAX) + t2 = MTCODER_THREADS_MAX; + + if (t3 <= 0) + { + if (t2 <= 0) + t2 = 1; + t3 = t1n * t2; + } + else if (t2 <= 0) + { + t2 = t3 / t1n; + if (t2 == 0) + { + t1 = 1; + t2 = t3; + } + if (t2 > MTCODER_THREADS_MAX) + t2 = MTCODER_THREADS_MAX; + } + else if (t1 <= 0) + { + t1 = t3 / t2; + if (t1 == 0) + t1 = 1; + } + else + t3 = t1n * t2; + + p->lzma2Props.numTotalThreads = t1; + + t2r = t2; + + fileSize = p->reduceSize; + + if ((p->blockSize < fileSize || fileSize == (UInt64)(Int64)-1)) + p->lzma2Props.lzmaProps.reduceSize = p->blockSize; + + Lzma2EncProps_Normalize(&p->lzma2Props); + + t1 = p->lzma2Props.numTotalThreads; + + { + if (t2 > 1 && fileSize != (UInt64)(Int64)-1) + { + UInt64 numBlocks = fileSize / p->blockSize; + if (numBlocks * p->blockSize != fileSize) + numBlocks++; + if (numBlocks < (unsigned)t2) + { + t2r = (int)numBlocks; + if (t2r == 0) + t2r = 1; + t3 = t1 * t2r; + } + } + } + + p->numBlockThreads_Max = t2; + p->numBlockThreads_Reduced = t2r; + p->numTotalThreads = t3; +} + + +static void XzProps_Normalize(CXzProps *p) +{ + /* we normalize xzProps properties, but we normalize only some of CXzProps::lzma2Props properties. + Lzma2Enc_SetProps() will normalize lzma2Props later. */ + + if (p->blockSize == XZ_PROPS_BLOCK_SIZE_SOLID) + { + p->lzma2Props.lzmaProps.reduceSize = p->reduceSize; + p->numBlockThreads_Reduced = 1; + p->numBlockThreads_Max = 1; + if (p->lzma2Props.numTotalThreads <= 0) + p->lzma2Props.numTotalThreads = p->numTotalThreads; + return; + } + else + { + CLzma2EncProps *lzma2 = &p->lzma2Props; + if (p->blockSize == LZMA2_ENC_PROPS_BLOCK_SIZE_AUTO) + { + // xz-auto + p->lzma2Props.lzmaProps.reduceSize = p->reduceSize; + + if (lzma2->blockSize == LZMA2_ENC_PROPS_BLOCK_SIZE_SOLID) + { + // if (xz-auto && lzma2-solid) - we use solid for both + p->blockSize = XZ_PROPS_BLOCK_SIZE_SOLID; + p->numBlockThreads_Reduced = 1; + p->numBlockThreads_Max = 1; + if (p->lzma2Props.numTotalThreads <= 0) + p->lzma2Props.numTotalThreads = p->numTotalThreads; + } + else + { + // if (xz-auto && (lzma2-auto || lzma2-fixed_) + // we calculate block size for lzma2 and use that block size for xz, lzma2 uses single-chunk per block + CLzma2EncProps tp = p->lzma2Props; + if (tp.numTotalThreads <= 0) + tp.numTotalThreads = p->numTotalThreads; + + Lzma2EncProps_Normalize(&tp); + + p->blockSize = tp.blockSize; // fixed or solid + p->numBlockThreads_Reduced = tp.numBlockThreads_Reduced; + p->numBlockThreads_Max = tp.numBlockThreads_Max; + if (lzma2->blockSize == LZMA2_ENC_PROPS_BLOCK_SIZE_AUTO) + lzma2->blockSize = tp.blockSize; // fixed or solid, LZMA2_ENC_PROPS_BLOCK_SIZE_SOLID + if (lzma2->lzmaProps.reduceSize > tp.blockSize && tp.blockSize != LZMA2_ENC_PROPS_BLOCK_SIZE_SOLID) + lzma2->lzmaProps.reduceSize = tp.blockSize; + lzma2->numBlockThreads_Reduced = 1; + lzma2->numBlockThreads_Max = 1; + return; + } + } + else + { + // xz-fixed + // we can use xz::reduceSize or xz::blockSize as base for lzmaProps::reduceSize + + p->lzma2Props.lzmaProps.reduceSize = p->reduceSize; + { + UInt64 r = p->reduceSize; + if (r > p->blockSize || r == (UInt64)(Int64)-1) + r = p->blockSize; + lzma2->lzmaProps.reduceSize = r; + } + if (lzma2->blockSize == LZMA2_ENC_PROPS_BLOCK_SIZE_AUTO) + lzma2->blockSize = LZMA2_ENC_PROPS_BLOCK_SIZE_SOLID; + else if (lzma2->blockSize > p->blockSize && lzma2->blockSize != LZMA2_ENC_PROPS_BLOCK_SIZE_SOLID) + lzma2->blockSize = p->blockSize; + + XzEncProps_Normalize_Fixed(p); + } + } +} + + +/* ---------- CLzma2WithFilters ---------- */ + typedef struct { CLzma2EncHandle lzma2; + CSeqInFilter filter; + #ifdef USE_SUBBLOCK CSbEncInStream sb; #endif - CSeqInFilter filter; - ISzAlloc *alloc; - ISzAlloc *bigAlloc; } CLzma2WithFilters; -static void Lzma2WithFilters_Construct(CLzma2WithFilters *p, ISzAlloc *alloc, ISzAlloc *bigAlloc) +Z7_FORCE_INLINE +static void Lzma2WithFilters_Construct(CLzma2WithFilters *p) { - p->alloc = alloc; - p->bigAlloc = bigAlloc; p->lzma2 = NULL; + SeqInFilter_Construct(&p->filter); + #ifdef USE_SUBBLOCK SbEncInStream_Construct(&p->sb, alloc); #endif - SeqInFilter_Construct(&p->filter); } -static SRes Lzma2WithFilters_Create(CLzma2WithFilters *p) + +static SRes Lzma2WithFilters_Create(CLzma2WithFilters *p, ISzAllocPtr alloc, ISzAllocPtr bigAlloc) { - p->lzma2 = Lzma2Enc_Create(p->alloc, p->bigAlloc); if (!p->lzma2) - return SZ_ERROR_MEM; + { + p->lzma2 = Lzma2Enc_Create(alloc, bigAlloc); + if (!p->lzma2) + return SZ_ERROR_MEM; + } return SZ_OK; } -static void Lzma2WithFilters_Free(CLzma2WithFilters *p) + +Z7_FORCE_INLINE +static void Lzma2WithFilters_Free(CLzma2WithFilters *p, ISzAllocPtr alloc) { - SeqInFilter_Free(&p->filter); #ifdef USE_SUBBLOCK SbEncInStream_Free(&p->sb); #endif + + SeqInFilter_Free(&p->filter, alloc); if (p->lzma2) { Lzma2Enc_Destroy(p->lzma2); @@ -390,149 +732,643 @@ static void Lzma2WithFilters_Free(CLzma2WithFilters *p) } -void XzProps_Init(CXzProps *p) +typedef struct { - p->lzma2Props = NULL; - p->filterProps = NULL; - p->checkId = XZ_CHECK_CRC32; + UInt64 unpackSize; + UInt64 totalSize; + size_t headerSize; +} CXzEncBlockInfo; + + +static SRes Xz_CompressBlock( + CLzma2WithFilters *lzmaf, + + ISeqOutStreamPtr outStream, + Byte *outBufHeader, + Byte *outBufData, size_t outBufDataLimit, + + ISeqInStreamPtr inStream, + // UInt64 expectedSize, + const Byte *inBuf, // used if (!inStream) + size_t inBufSize, // used if (!inStream), it's block size, props->blockSize is ignored + + const CXzProps *props, + ICompressProgressPtr progress, + int *inStreamFinished, /* only for inStream version */ + CXzEncBlockInfo *blockSizes, + ISzAllocPtr alloc, + ISzAllocPtr allocBig) +{ + CSeqCheckInStream checkInStream; + CSeqSizeOutStream seqSizeOutStream; + CXzBlock block; + unsigned filterIndex = 0; + CXzFilter *filter = NULL; + const CXzFilterProps *fp = &props->filterProps; + if (fp->id == 0) + fp = NULL; + + *inStreamFinished = False; + + RINOK(Lzma2WithFilters_Create(lzmaf, alloc, allocBig)) + + RINOK(Lzma2Enc_SetProps(lzmaf->lzma2, &props->lzma2Props)) + + // XzBlock_ClearFlags(&block) + XzBlock_ClearFlags_SetNumFilters(&block, 1 + (fp ? 1 : 0)) + + if (fp) + { + filter = &block.filters[filterIndex++]; + filter->id = fp->id; + filter->propsSize = 0; + + if (fp->id == XZ_ID_Delta) + { + filter->props[0] = (Byte)(fp->delta - 1); + filter->propsSize = 1; + } + else if (fp->ipDefined) + { + Byte *ptr = filter->props; + SetUi32(ptr, fp->ip) + filter->propsSize = 4; + } + } + + { + CXzFilter *f = &block.filters[filterIndex++]; + f->id = XZ_ID_LZMA2; + f->propsSize = 1; + f->props[0] = Lzma2Enc_WriteProperties(lzmaf->lzma2); + } + + seqSizeOutStream.vt.Write = SeqSizeOutStream_Write; + seqSizeOutStream.realStream = outStream; + seqSizeOutStream.outBuf = outBufData; + seqSizeOutStream.outBufLimit = outBufDataLimit; + seqSizeOutStream.processed = 0; + + /* + if (expectedSize != (UInt64)(Int64)-1) + { + block.unpackSize = expectedSize; + if (props->blockSize != (UInt64)(Int64)-1) + if (expectedSize > props->blockSize) + block.unpackSize = props->blockSize; + XzBlock_SetHasUnpackSize(&block) + } + */ + + if (outStream) + { + RINOK(XzBlock_WriteHeader(&block, &seqSizeOutStream.vt)) + } + + checkInStream.vt.Read = SeqCheckInStream_Read; + SeqCheckInStream_Init(&checkInStream, props->checkId); + + checkInStream.realStream = inStream; + checkInStream.data = inBuf; + checkInStream.limit = props->blockSize; + if (!inStream) + checkInStream.limit = inBufSize; + + if (fp) + { + #ifdef USE_SUBBLOCK + if (fp->id == XZ_ID_Subblock) + { + lzmaf->sb.inStream = &checkInStream.vt; + RINOK(SbEncInStream_Init(&lzmaf->sb)) + } + else + #endif + { + lzmaf->filter.realStream = &checkInStream.vt; + RINOK(SeqInFilter_Init(&lzmaf->filter, filter, alloc)) + } + } + + { + SRes res; + Byte *outBuf = NULL; + size_t outSize = 0; + BoolInt useStream = (fp || inStream); + // useStream = True; + + if (!useStream) + { + XzCheck_Update(&checkInStream.check, inBuf, inBufSize); + checkInStream.processed = inBufSize; + } + + if (!outStream) + { + outBuf = seqSizeOutStream.outBuf; // + (size_t)seqSizeOutStream.processed; + outSize = seqSizeOutStream.outBufLimit; // - (size_t)seqSizeOutStream.processed; + } + + res = Lzma2Enc_Encode2(lzmaf->lzma2, + outBuf ? NULL : &seqSizeOutStream.vt, + outBuf, + outBuf ? &outSize : NULL, + + useStream ? + (fp ? + ( + #ifdef USE_SUBBLOCK + (fp->id == XZ_ID_Subblock) ? &lzmaf->sb.vt: + #endif + &lzmaf->filter.vt) : + &checkInStream.vt) : NULL, + + useStream ? NULL : inBuf, + useStream ? 0 : inBufSize, + + progress); + + if (outBuf) + seqSizeOutStream.processed += outSize; + + RINOK(res) + blockSizes->unpackSize = checkInStream.processed; + } + { + Byte buf[4 + XZ_CHECK_SIZE_MAX]; + const unsigned padSize = XZ_GET_PAD_SIZE(seqSizeOutStream.processed); + const UInt64 packSize = seqSizeOutStream.processed; + + buf[0] = 0; + buf[1] = 0; + buf[2] = 0; + buf[3] = 0; + + SeqCheckInStream_GetDigest(&checkInStream, buf + 4); + RINOK(WriteBytes(&seqSizeOutStream.vt, buf + (4 - padSize), + padSize + XzFlags_GetCheckSize((CXzStreamFlags)props->checkId))) + + blockSizes->totalSize = seqSizeOutStream.processed - padSize; + + if (!outStream) + { + seqSizeOutStream.outBuf = outBufHeader; + seqSizeOutStream.outBufLimit = XZ_BLOCK_HEADER_SIZE_MAX; + seqSizeOutStream.processed = 0; + + block.unpackSize = blockSizes->unpackSize; + XzBlock_SetHasUnpackSize(&block) + + block.packSize = packSize; + XzBlock_SetHasPackSize(&block) + + RINOK(XzBlock_WriteHeader(&block, &seqSizeOutStream.vt)) + + blockSizes->headerSize = (size_t)seqSizeOutStream.processed; + blockSizes->totalSize += seqSizeOutStream.processed; + } + } + + if (inStream) + *inStreamFinished = checkInStream.realStreamFinished; + else + { + *inStreamFinished = False; + if (checkInStream.processed != inBufSize) + return SZ_ERROR_FAIL; + } + + return SZ_OK; } -void XzFilterProps_Init(CXzFilterProps *p) + + +typedef struct { - p->id = 0; - p->delta = 0; - p->ip = 0; - p->ipDefined = False; + ICompressProgress vt; + ICompressProgressPtr progress; + UInt64 inOffset; + UInt64 outOffset; +} CCompressProgress_XzEncOffset; + + +static SRes CompressProgress_XzEncOffset_Progress(ICompressProgressPtr pp, UInt64 inSize, UInt64 outSize) +{ + const CCompressProgress_XzEncOffset *p = Z7_CONTAINER_FROM_VTBL_CONST(pp, CCompressProgress_XzEncOffset, vt); + inSize += p->inOffset; + outSize += p->outOffset; + return ICompressProgress_Progress(p->progress, inSize, outSize); } -static SRes Xz_Compress(CXzStream *xz, CLzma2WithFilters *lzmaf, - ISeqOutStream *outStream, ISeqInStream *inStream, - const CXzProps *props, ICompressProgress *progress) + + +struct CXzEnc { - xz->flags = (Byte)props->checkId; + ISzAllocPtr alloc; + ISzAllocPtr allocBig; + + CXzProps xzProps; + UInt64 expectedDataSize; + + CXzEncIndex xzIndex; + + CLzma2WithFilters lzmaf_Items[MTCODER_THREADS_MAX]; + + size_t outBufSize; /* size of allocated outBufs[i] */ + Byte *outBufs[MTCODER_BLOCKS_MAX]; + + #ifndef Z7_ST + unsigned checkType; + ISeqOutStreamPtr outStream; + BoolInt mtCoder_WasConstructed; + CMtCoder mtCoder; + CXzEncBlockInfo EncBlocks[MTCODER_BLOCKS_MAX]; + #endif +}; + + +static void XzEnc_Construct(CXzEnc *p) +{ + unsigned i; + + XzEncIndex_Construct(&p->xzIndex); - RINOK(Lzma2Enc_SetProps(lzmaf->lzma2, props->lzma2Props)); - RINOK(Xz_WriteHeader(xz->flags, outStream)); + for (i = 0; i < MTCODER_THREADS_MAX; i++) + Lzma2WithFilters_Construct(&p->lzmaf_Items[i]); + #ifndef Z7_ST + p->mtCoder_WasConstructed = False; { - CSeqCheckInStream checkInStream; - CSeqSizeOutStream seqSizeOutStream; - CXzBlock block; - unsigned filterIndex = 0; - CXzFilter *filter = NULL; - const CXzFilterProps *fp = props->filterProps; + for (i = 0; i < MTCODER_BLOCKS_MAX; i++) + p->outBufs[i] = NULL; + p->outBufSize = 0; + } + #endif +} + + +static void XzEnc_FreeOutBufs(CXzEnc *p) +{ + unsigned i; + for (i = 0; i < MTCODER_BLOCKS_MAX; i++) + if (p->outBufs[i]) + { + ISzAlloc_Free(p->alloc, p->outBufs[i]); + p->outBufs[i] = NULL; + } + p->outBufSize = 0; +} + + +static void XzEnc_Free(CXzEnc *p, ISzAllocPtr alloc) +{ + unsigned i; + + XzEncIndex_Free(&p->xzIndex, alloc); + + for (i = 0; i < MTCODER_THREADS_MAX; i++) + Lzma2WithFilters_Free(&p->lzmaf_Items[i], alloc); + + #ifndef Z7_ST + if (p->mtCoder_WasConstructed) + { + MtCoder_Destruct(&p->mtCoder); + p->mtCoder_WasConstructed = False; + } + XzEnc_FreeOutBufs(p); + #endif +} + + +CXzEncHandle XzEnc_Create(ISzAllocPtr alloc, ISzAllocPtr allocBig) +{ + CXzEnc *p = (CXzEnc *)ISzAlloc_Alloc(alloc, sizeof(CXzEnc)); + if (!p) + return NULL; + XzEnc_Construct(p); + XzProps_Init(&p->xzProps); + XzProps_Normalize(&p->xzProps); + p->expectedDataSize = (UInt64)(Int64)-1; + p->alloc = alloc; + p->allocBig = allocBig; + return (CXzEncHandle)p; +} + +// #define GET_CXzEnc_p CXzEnc *p = (CXzEnc *)(void *)pp; + +void XzEnc_Destroy(CXzEncHandle p) +{ + // GET_CXzEnc_p + XzEnc_Free(p, p->alloc); + ISzAlloc_Free(p->alloc, p); +} + + +SRes XzEnc_SetProps(CXzEncHandle p, const CXzProps *props) +{ + // GET_CXzEnc_p + p->xzProps = *props; + XzProps_Normalize(&p->xzProps); + return SZ_OK; +} + + +void XzEnc_SetDataSize(CXzEncHandle p, UInt64 expectedDataSiize) +{ + // GET_CXzEnc_p + p->expectedDataSize = expectedDataSiize; +} + + + + +#ifndef Z7_ST + +static SRes XzEnc_MtCallback_Code(void *pp, unsigned coderIndex, unsigned outBufIndex, + const Byte *src, size_t srcSize, int finished) +{ + CXzEnc *me = (CXzEnc *)pp; + SRes res; + CMtProgressThunk progressThunk; + Byte *dest; + UNUSED_VAR(finished) + { + CXzEncBlockInfo *bInfo = &me->EncBlocks[outBufIndex]; + bInfo->totalSize = 0; + bInfo->unpackSize = 0; + bInfo->headerSize = 0; + // v23.02: we don't compress empty blocks + // also we must ignore that empty block in XzEnc_MtCallback_Write() + if (srcSize == 0) + return SZ_OK; + } + dest = me->outBufs[outBufIndex]; + if (!dest) + { + dest = (Byte *)ISzAlloc_Alloc(me->alloc, me->outBufSize); + if (!dest) + return SZ_ERROR_MEM; + me->outBufs[outBufIndex] = dest; + } + + MtProgressThunk_CreateVTable(&progressThunk); + progressThunk.mtProgress = &me->mtCoder.mtProgress; + MtProgressThunk_INIT(&progressThunk) + + { + CXzEncBlockInfo blockSizes; + int inStreamFinished; + + res = Xz_CompressBlock( + &me->lzmaf_Items[coderIndex], + + NULL, + dest, + dest + XZ_BLOCK_HEADER_SIZE_MAX, me->outBufSize - XZ_BLOCK_HEADER_SIZE_MAX, + + NULL, + // srcSize, // expectedSize + src, srcSize, + + &me->xzProps, + &progressThunk.vt, + &inStreamFinished, + &blockSizes, + me->alloc, + me->allocBig); - XzBlock_ClearFlags(&block); - XzBlock_SetNumFilters(&block, 1 + (fp ? 1 : 0)); + if (res == SZ_OK) + me->EncBlocks[outBufIndex] = blockSizes; + + return res; + } +} + + +static SRes XzEnc_MtCallback_Write(void *pp, unsigned outBufIndex) +{ + CXzEnc *me = (CXzEnc *)pp; + const CXzEncBlockInfo *bInfo = &me->EncBlocks[outBufIndex]; + // v23.02: we don't write empty blocks + // note: if (bInfo->unpackSize == 0) then there is no compressed data of block + if (bInfo->unpackSize == 0) + return SZ_OK; + { + const Byte *data = me->outBufs[outBufIndex]; + RINOK(WriteBytes(me->outStream, data, bInfo->headerSize)) + { + const UInt64 totalPackFull = bInfo->totalSize + XZ_GET_PAD_SIZE(bInfo->totalSize); + RINOK(WriteBytes(me->outStream, data + XZ_BLOCK_HEADER_SIZE_MAX, (size_t)totalPackFull - bInfo->headerSize)) + } + return XzEncIndex_AddIndexRecord(&me->xzIndex, bInfo->unpackSize, bInfo->totalSize, me->alloc); + } +} + +#endif + + + +SRes XzEnc_Encode(CXzEncHandle p, ISeqOutStreamPtr outStream, ISeqInStreamPtr inStream, ICompressProgressPtr progress) +{ + // GET_CXzEnc_p + + const CXzProps *props = &p->xzProps; + + XzEncIndex_Init(&p->xzIndex); + { + UInt64 numBlocks = 1; + UInt64 blockSize = props->blockSize; - if (fp) + if (blockSize != XZ_PROPS_BLOCK_SIZE_SOLID + && props->reduceSize != (UInt64)(Int64)-1) { - filter = &block.filters[filterIndex++]; - filter->id = fp->id; - filter->propsSize = 0; - - if (fp->id == XZ_ID_Delta) - { - filter->props[0] = (Byte)(fp->delta - 1); - filter->propsSize = 1; - } - else if (fp->ipDefined) - { - SetUi32(filter->props, fp->ip); - filter->propsSize = 4; - } + numBlocks = props->reduceSize / blockSize; + if (numBlocks * blockSize != props->reduceSize) + numBlocks++; + } + else + blockSize = (UInt64)1 << 62; + + RINOK(XzEncIndex_PreAlloc(&p->xzIndex, numBlocks, blockSize, XZ_GET_ESTIMATED_BLOCK_TOTAL_PACK_SIZE(blockSize), p->alloc)) + } + + RINOK(Xz_WriteHeader((CXzStreamFlags)props->checkId, outStream)) + + + #ifndef Z7_ST + if (props->numBlockThreads_Reduced > 1) + { + IMtCoderCallback2 vt; + + if (!p->mtCoder_WasConstructed) + { + p->mtCoder_WasConstructed = True; + MtCoder_Construct(&p->mtCoder); } + vt.Code = XzEnc_MtCallback_Code; + vt.Write = XzEnc_MtCallback_Write; + + p->checkType = props->checkId; + p->xzProps = *props; + + p->outStream = outStream; + + p->mtCoder.allocBig = p->allocBig; + p->mtCoder.progress = progress; + p->mtCoder.inStream = inStream; + p->mtCoder.inData = NULL; + p->mtCoder.inDataSize = 0; + p->mtCoder.mtCallback = &vt; + p->mtCoder.mtCallbackObject = p; + + if ( props->blockSize == XZ_PROPS_BLOCK_SIZE_SOLID + || props->blockSize == XZ_PROPS_BLOCK_SIZE_AUTO) + return SZ_ERROR_FAIL; + + p->mtCoder.blockSize = (size_t)props->blockSize; + if (p->mtCoder.blockSize != props->blockSize) + return SZ_ERROR_PARAM; /* SZ_ERROR_MEM */ + { - CXzFilter *f = &block.filters[filterIndex++]; - f->id = XZ_ID_LZMA2; - f->propsSize = 1; - f->props[0] = Lzma2Enc_WriteProperties(lzmaf->lzma2); + size_t destBlockSize = XZ_BLOCK_HEADER_SIZE_MAX + XZ_GET_MAX_BLOCK_PACK_SIZE(p->mtCoder.blockSize); + if (destBlockSize < p->mtCoder.blockSize) + return SZ_ERROR_PARAM; + if (p->outBufSize != destBlockSize) + XzEnc_FreeOutBufs(p); + p->outBufSize = destBlockSize; } - seqSizeOutStream.p.Write = MyWrite; - seqSizeOutStream.realStream = outStream; - seqSizeOutStream.processed = 0; + p->mtCoder.numThreadsMax = (unsigned)props->numBlockThreads_Max; + p->mtCoder.numThreadGroups = props->numThreadGroups; + p->mtCoder.expectedDataSize = p->expectedDataSize; - RINOK(XzBlock_WriteHeader(&block, &seqSizeOutStream.p)); + RINOK(MtCoder_Code(&p->mtCoder)) + } + else + #endif + { + int writeStartSizes; + CCompressProgress_XzEncOffset progress2; + Byte *bufData = NULL; + size_t bufSize = 0; + + progress2.vt.Progress = CompressProgress_XzEncOffset_Progress; + progress2.inOffset = 0; + progress2.outOffset = 0; + progress2.progress = progress; - checkInStream.p.Read = SeqCheckInStream_Read; - checkInStream.realStream = inStream; - SeqCheckInStream_Init(&checkInStream, XzFlags_GetCheckType(xz->flags)); + writeStartSizes = 0; - if (fp) + if (props->blockSize != XZ_PROPS_BLOCK_SIZE_SOLID) { - #ifdef USE_SUBBLOCK - if (fp->id == XZ_ID_Subblock) - { - lzmaf->sb.inStream = &checkInStream.p; - RINOK(SbEncInStream_Init(&lzmaf->sb)); - } - else - #endif + writeStartSizes = (props->forceWriteSizesInHeader > 0); + + if (writeStartSizes) { - lzmaf->filter.realStream = &checkInStream.p; - RINOK(SeqInFilter_Init(&lzmaf->filter, filter)); + size_t t2; + size_t t = (size_t)props->blockSize; + if (t != props->blockSize) + return SZ_ERROR_PARAM; + t = XZ_GET_MAX_BLOCK_PACK_SIZE(t); + if (t < props->blockSize) + return SZ_ERROR_PARAM; + t2 = XZ_BLOCK_HEADER_SIZE_MAX + t; + if (!p->outBufs[0] || t2 != p->outBufSize) + { + XzEnc_FreeOutBufs(p); + p->outBufs[0] = (Byte *)ISzAlloc_Alloc(p->alloc, t2); + if (!p->outBufs[0]) + return SZ_ERROR_MEM; + p->outBufSize = t2; + } + bufData = p->outBufs[0] + XZ_BLOCK_HEADER_SIZE_MAX; + bufSize = t; } } - + + for (;;) { - UInt64 packPos = seqSizeOutStream.processed; + CXzEncBlockInfo blockSizes; + int inStreamFinished; - SRes res = Lzma2Enc_Encode(lzmaf->lzma2, &seqSizeOutStream.p, - fp ? - #ifdef USE_SUBBLOCK - (fp->id == XZ_ID_Subblock) ? &lzmaf->sb.p: - #endif - &lzmaf->filter.p: - &checkInStream.p, - progress); + /* + UInt64 rem = (UInt64)(Int64)-1; + if (props->reduceSize != (UInt64)(Int64)-1 + && props->reduceSize >= progress2.inOffset) + rem = props->reduceSize - progress2.inOffset; + */ + + blockSizes.headerSize = 0; // for GCC - RINOK(res); - block.unpackSize = checkInStream.processed; - block.packSize = seqSizeOutStream.processed - packPos; - } + RINOK(Xz_CompressBlock( + &p->lzmaf_Items[0], + + writeStartSizes ? NULL : outStream, + writeStartSizes ? p->outBufs[0] : NULL, + bufData, bufSize, + + inStream, + // rem, + NULL, 0, + + props, + progress ? &progress2.vt : NULL, + &inStreamFinished, + &blockSizes, + p->alloc, + p->allocBig)) - { - unsigned padSize = 0; - Byte buf[128]; - while ((((unsigned)block.packSize + padSize) & 3) != 0) - buf[padSize++] = 0; - SeqCheckInStream_GetDigest(&checkInStream, buf + padSize); - RINOK(WriteBytes(&seqSizeOutStream.p, buf, padSize + XzFlags_GetCheckSize(xz->flags))); - RINOK(Xz_AddIndexRecord(xz, block.unpackSize, seqSizeOutStream.processed - padSize, &g_Alloc)); + { + UInt64 totalPackFull = blockSizes.totalSize + XZ_GET_PAD_SIZE(blockSizes.totalSize); + + if (writeStartSizes) + { + RINOK(WriteBytes(outStream, p->outBufs[0], blockSizes.headerSize)) + RINOK(WriteBytes(outStream, bufData, (size_t)totalPackFull - blockSizes.headerSize)) + } + + RINOK(XzEncIndex_AddIndexRecord(&p->xzIndex, blockSizes.unpackSize, blockSizes.totalSize, p->alloc)) + + progress2.inOffset += blockSizes.unpackSize; + progress2.outOffset += totalPackFull; + } + + if (inStreamFinished) + break; } } - return Xz_WriteFooter(xz, outStream); + + return XzEncIndex_WriteFooter(&p->xzIndex, (CXzStreamFlags)props->checkId, outStream); } -SRes Xz_Encode(ISeqOutStream *outStream, ISeqInStream *inStream, - const CXzProps *props, ICompressProgress *progress) +#include "Alloc.h" + +SRes Xz_Encode(ISeqOutStreamPtr outStream, ISeqInStreamPtr inStream, + const CXzProps *props, ICompressProgressPtr progress) { SRes res; - CXzStream xz; - CLzma2WithFilters lzmaf; - Xz_Construct(&xz); - Lzma2WithFilters_Construct(&lzmaf, &g_Alloc, &g_BigAlloc); - res = Lzma2WithFilters_Create(&lzmaf); + CXzEncHandle xz = XzEnc_Create(&g_Alloc, &g_BigAlloc); + if (!xz) + return SZ_ERROR_MEM; + res = XzEnc_SetProps(xz, props); if (res == SZ_OK) - res = Xz_Compress(&xz, &lzmaf, outStream, inStream, props, progress); - Lzma2WithFilters_Free(&lzmaf); - Xz_Free(&xz, &g_Alloc); + res = XzEnc_Encode(xz, outStream, inStream, progress); + XzEnc_Destroy(xz); return res; } -SRes Xz_EncodeEmpty(ISeqOutStream *outStream) +SRes Xz_EncodeEmpty(ISeqOutStreamPtr outStream) { SRes res; - CXzStream xz; - Xz_Construct(&xz); - res = Xz_WriteHeader(xz.flags, outStream); + CXzEncIndex xzIndex; + XzEncIndex_Construct(&xzIndex); + res = Xz_WriteHeader((CXzStreamFlags)0, outStream); if (res == SZ_OK) - res = Xz_WriteFooter(&xz, outStream); - Xz_Free(&xz, &g_Alloc); + res = XzEncIndex_WriteFooter(&xzIndex, (CXzStreamFlags)0, outStream); + XzEncIndex_Free(&xzIndex, NULL); // g_Alloc return res; } diff --git a/src/plugins/7zip/7za/c/XzEnc.h b/src/plugins/7zip/7za/c/XzEnc.h index c3c19eca..ac6bbf79 100644 --- a/src/plugins/7zip/7za/c/XzEnc.h +++ b/src/plugins/7zip/7za/c/XzEnc.h @@ -1,8 +1,8 @@ /* XzEnc.h -- Xz Encode -2011-02-07 : Igor Pavlov : Public domain */ +: Igor Pavlov : Public domain */ -#ifndef __XZ_ENC_H -#define __XZ_ENC_H +#ifndef ZIP7_INC_XZ_ENC_H +#define ZIP7_INC_XZ_ENC_H #include "Lzma2Enc.h" @@ -10,6 +10,11 @@ EXTERN_C_BEGIN + +#define XZ_PROPS_BLOCK_SIZE_AUTO LZMA2_ENC_PROPS_BLOCK_SIZE_AUTO +#define XZ_PROPS_BLOCK_SIZE_SOLID LZMA2_ENC_PROPS_BLOCK_SIZE_SOLID + + typedef struct { UInt32 id; @@ -20,19 +25,37 @@ typedef struct void XzFilterProps_Init(CXzFilterProps *p); + typedef struct { - const CLzma2EncProps *lzma2Props; - const CXzFilterProps *filterProps; + CLzma2EncProps lzma2Props; + CXzFilterProps filterProps; unsigned checkId; + unsigned numThreadGroups; // 0 : no groups + UInt64 blockSize; + int numBlockThreads_Reduced; + int numBlockThreads_Max; + int numTotalThreads; + int forceWriteSizesInHeader; + UInt64 reduceSize; } CXzProps; void XzProps_Init(CXzProps *p); -SRes Xz_Encode(ISeqOutStream *outStream, ISeqInStream *inStream, - const CXzProps *props, ICompressProgress *progress); +typedef struct CXzEnc CXzEnc; +typedef CXzEnc * CXzEncHandle; +// Z7_DECLARE_HANDLE(CXzEncHandle) + +CXzEncHandle XzEnc_Create(ISzAllocPtr alloc, ISzAllocPtr allocBig); +void XzEnc_Destroy(CXzEncHandle p); +SRes XzEnc_SetProps(CXzEncHandle p, const CXzProps *props); +void XzEnc_SetDataSize(CXzEncHandle p, UInt64 expectedDataSiize); +SRes XzEnc_Encode(CXzEncHandle p, ISeqOutStreamPtr outStream, ISeqInStreamPtr inStream, ICompressProgressPtr progress); + +SRes Xz_Encode(ISeqOutStreamPtr outStream, ISeqInStreamPtr inStream, + const CXzProps *props, ICompressProgressPtr progress); -SRes Xz_EncodeEmpty(ISeqOutStream *outStream); +SRes Xz_EncodeEmpty(ISeqOutStreamPtr outStream); EXTERN_C_END diff --git a/src/plugins/7zip/7za/c/XzIn.c b/src/plugins/7zip/7za/c/XzIn.c index 07b1cc39..ba316360 100644 --- a/src/plugins/7zip/7za/c/XzIn.c +++ b/src/plugins/7zip/7za/c/XzIn.c @@ -1,34 +1,44 @@ /* XzIn.c - Xz input -2015-11-08 : Igor Pavlov : Public domain */ +: Igor Pavlov : Public domain */ #include "Precomp.h" #include #include "7zCrc.h" -#include "CpuArch.h" #include "Xz.h" +#include "CpuArch.h" + +#define XZ_FOOTER_12B_ALIGNED16_SIG_CHECK(p) \ + (GetUi16a((const Byte *)(const void *)(p) + 10) == \ + (XZ_FOOTER_SIG_0 | (XZ_FOOTER_SIG_1 << 8))) -SRes Xz_ReadHeader(CXzStreamFlags *p, ISeqInStream *inStream) +SRes Xz_ReadHeader(CXzStreamFlags *p, ISeqInStreamPtr inStream) { - Byte sig[XZ_STREAM_HEADER_SIZE]; - RINOK(SeqInStream_Read2(inStream, sig, XZ_STREAM_HEADER_SIZE, SZ_ERROR_NO_ARCHIVE)); - if (memcmp(sig, XZ_SIG, XZ_SIG_SIZE) != 0) + UInt32 data32[XZ_STREAM_HEADER_SIZE / 4]; + size_t processedSize = XZ_STREAM_HEADER_SIZE; + RINOK(SeqInStream_ReadMax(inStream, data32, &processedSize)) + if (processedSize != XZ_STREAM_HEADER_SIZE + || memcmp(data32, XZ_SIG, XZ_SIG_SIZE) != 0) return SZ_ERROR_NO_ARCHIVE; - return Xz_ParseHeader(p, sig); + return Xz_ParseHeader(p, (const Byte *)(const void *)data32); } -#define READ_VARINT_AND_CHECK(buf, pos, size, res) \ - { unsigned s = Xz_ReadVarInt(buf + pos, size - pos, res); \ - if (s == 0) return SZ_ERROR_ARCHIVE; pos += s; } +#define READ_VARINT_AND_CHECK(buf, size, res) \ +{ const unsigned s = Xz_ReadVarInt(buf, size, res); \ + if (s == 0) return SZ_ERROR_ARCHIVE; \ + size -= s; \ + buf += s; \ +} -SRes XzBlock_ReadHeader(CXzBlock *p, ISeqInStream *inStream, Bool *isIndex, UInt32 *headerSizeRes) +SRes XzBlock_ReadHeader(CXzBlock *p, ISeqInStreamPtr inStream, BoolInt *isIndex, UInt32 *headerSizeRes) { + MY_ALIGN(4) Byte header[XZ_BLOCK_HEADER_SIZE_MAX]; unsigned headerSize; *headerSizeRes = 0; - RINOK(SeqInStream_ReadByte(inStream, &header[0])); - headerSize = ((unsigned)header[0] << 2) + 4; + RINOK(SeqInStream_ReadByte(inStream, &header[0])) + headerSize = header[0]; if (headerSize == 0) { *headerSizeRes = 1; @@ -37,20 +47,32 @@ SRes XzBlock_ReadHeader(CXzBlock *p, ISeqInStream *inStream, Bool *isIndex, UInt } *isIndex = False; - *headerSizeRes = headerSize; - RINOK(SeqInStream_Read(inStream, header + 1, headerSize - 1)); + headerSize = (headerSize << 2) + 4; + *headerSizeRes = (UInt32)headerSize; + { + size_t processedSize = headerSize - 1; + RINOK(SeqInStream_ReadMax(inStream, header + 1, &processedSize)) + if (processedSize != headerSize - 1) + return SZ_ERROR_INPUT_EOF; + } return XzBlock_Parse(p, header); } -#define ADD_SIZE_CHECH(size, val) \ - { UInt64 newSize = size + (val); if (newSize < size) return XZ_SIZE_OVERFLOW; size = newSize; } + +#define ADD_SIZE_CHECK(size, val) \ +{ const UInt64 newSize = size + (val); \ + if (newSize < size) return XZ_SIZE_OVERFLOW; \ + size = newSize; \ +} UInt64 Xz_GetUnpackSize(const CXzStream *p) { UInt64 size = 0; size_t i; for (i = 0; i < p->numBlocks; i++) - ADD_SIZE_CHECH(size, p->blocks[i].unpackSize); + { + ADD_SIZE_CHECK(size, p->blocks[i].unpackSize) + } return size; } @@ -59,172 +81,204 @@ UInt64 Xz_GetPackSize(const CXzStream *p) UInt64 size = 0; size_t i; for (i = 0; i < p->numBlocks; i++) - ADD_SIZE_CHECH(size, (p->blocks[i].totalSize + 3) & ~(UInt64)3); + { + ADD_SIZE_CHECK(size, (p->blocks[i].totalSize + 3) & ~(UInt64)3) + } return size; } -/* -SRes XzBlock_ReadFooter(CXzBlock *p, CXzStreamFlags f, ISeqInStream *inStream) -{ - return SeqInStream_Read(inStream, p->check, XzFlags_GetCheckSize(f)); -} -*/ -static SRes Xz_ReadIndex2(CXzStream *p, const Byte *buf, size_t size, ISzAlloc *alloc) +// input; +// CXzStream (p) is empty object. +// size != 0 +// (size & 3) == 0 +// (buf) is aligned for at least 4 bytes. +// output: +// p->numBlocks is number of allocated items in p->blocks +// p->blocks[*] values must be ignored, if function returns error. +static SRes Xz_ParseIndex(CXzStream *p, const Byte *buf, size_t size, ISzAllocPtr alloc) { - size_t numBlocks, pos = 1; - UInt32 crc; - + size_t numBlocks; if (size < 5 || buf[0] != 0) return SZ_ERROR_ARCHIVE; - size -= 4; - crc = CrcCalc(buf, size); - if (crc != GetUi32(buf + size)) - return SZ_ERROR_ARCHIVE; - + { + const UInt32 crc = CrcCalc(buf, size); + if (crc != GetUi32a(buf + size)) + return SZ_ERROR_ARCHIVE; + } + buf++; + size--; { UInt64 numBlocks64; - READ_VARINT_AND_CHECK(buf, pos, size, &numBlocks64); - numBlocks = (size_t)numBlocks64; - if (numBlocks != numBlocks64 || numBlocks * 2 > size) + READ_VARINT_AND_CHECK(buf, size, &numBlocks64) + // (numBlocks64) is 63-bit value, so we can calculate (numBlocks64 * 2): + if (numBlocks64 * 2 > size) return SZ_ERROR_ARCHIVE; + if (numBlocks64 >= ((size_t)1 << (sizeof(size_t) * 8 - 1)) / sizeof(CXzBlockSizes)) + return SZ_ERROR_MEM; // SZ_ERROR_ARCHIVE + numBlocks = (size_t)numBlocks64; } - - Xz_Free(p, alloc); - if (numBlocks != 0) + // Xz_Free(p, alloc); // it's optional, because (p) is empty already + if (numBlocks) { - size_t i; - p->numBlocks = numBlocks; - p->numBlocksAllocated = numBlocks; - p->blocks = alloc->Alloc(alloc, sizeof(CXzBlockSizes) * numBlocks); - if (p->blocks == 0) + CXzBlockSizes *blocks = (CXzBlockSizes *)ISzAlloc_Alloc(alloc, sizeof(CXzBlockSizes) * numBlocks); + if (!blocks) return SZ_ERROR_MEM; - for (i = 0; i < numBlocks; i++) + p->blocks = blocks; + p->numBlocks = numBlocks; + // the caller will call Xz_Free() in case of error + do { - CXzBlockSizes *block = &p->blocks[i]; - READ_VARINT_AND_CHECK(buf, pos, size, &block->totalSize); - READ_VARINT_AND_CHECK(buf, pos, size, &block->unpackSize); - if (block->totalSize == 0) + READ_VARINT_AND_CHECK(buf, size, &blocks->totalSize) + READ_VARINT_AND_CHECK(buf, size, &blocks->unpackSize) + if (blocks->totalSize == 0) return SZ_ERROR_ARCHIVE; + blocks++; } + while (--numBlocks); } - while ((pos & 3) != 0) - if (buf[pos++] != 0) + if (size >= 4) + return SZ_ERROR_ARCHIVE; + while (size) + if (buf[--size]) return SZ_ERROR_ARCHIVE; - return (pos == size) ? SZ_OK : SZ_ERROR_ARCHIVE; + return SZ_OK; } -static SRes Xz_ReadIndex(CXzStream *p, ILookInStream *stream, UInt64 indexSize, ISzAlloc *alloc) + +/* +static SRes Xz_ReadIndex(CXzStream *p, ILookInStreamPtr stream, UInt64 indexSize, ISzAllocPtr alloc) { SRes res; size_t size; Byte *buf; - if (indexSize > ((UInt32)1 << 31)) - return SZ_ERROR_UNSUPPORTED; + if (indexSize >= ((size_t)1 << (sizeof(size_t) * 8 - 1))) + return SZ_ERROR_MEM; // SZ_ERROR_ARCHIVE size = (size_t)indexSize; - if (size != indexSize) - return SZ_ERROR_UNSUPPORTED; - buf = alloc->Alloc(alloc, size); - if (buf == 0) + buf = (Byte *)ISzAlloc_Alloc(alloc, size); + if (!buf) return SZ_ERROR_MEM; res = LookInStream_Read2(stream, buf, size, SZ_ERROR_UNSUPPORTED); if (res == SZ_OK) - res = Xz_ReadIndex2(p, buf, size, alloc); - alloc->Free(alloc, buf); + res = Xz_ParseIndex(p, buf, size, alloc); + ISzAlloc_Free(alloc, buf); return res; } +*/ -static SRes LookInStream_SeekRead_ForArc(ILookInStream *stream, UInt64 offset, void *buf, size_t size) +static SRes LookInStream_SeekRead_ForArc(ILookInStreamPtr stream, UInt64 offset, void *buf, size_t size) { - RINOK(LookInStream_SeekTo(stream, offset)); + RINOK(LookInStream_SeekTo(stream, offset)) return LookInStream_Read(stream, buf, size); /* return LookInStream_Read2(stream, buf, size, SZ_ERROR_NO_ARCHIVE); */ } -static SRes Xz_ReadBackward(CXzStream *p, ILookInStream *stream, Int64 *startOffset, ISzAlloc *alloc) + +/* +in: + (*startOffset) is position in (stream) where xz_stream must be finished. +out: + if returns SZ_OK, then (*startOffset) is position in stream that shows start of xz_stream. +*/ +static SRes Xz_ReadBackward(CXzStream *p, ILookInStreamPtr stream, Int64 *startOffset, ISzAllocPtr alloc) { - UInt64 indexSize; - Byte buf[XZ_STREAM_FOOTER_SIZE]; - UInt64 pos = *startOffset; + #define TEMP_BUF_SIZE (1 << 10) + UInt32 buf32[TEMP_BUF_SIZE / 4]; + UInt64 pos = (UInt64)*startOffset; - if ((pos & 3) != 0 || pos < XZ_STREAM_FOOTER_SIZE) + if ((pos & 3) || pos < XZ_STREAM_FOOTER_SIZE) return SZ_ERROR_NO_ARCHIVE; - pos -= XZ_STREAM_FOOTER_SIZE; - RINOK(LookInStream_SeekRead_ForArc(stream, pos, buf, XZ_STREAM_FOOTER_SIZE)); + RINOK(LookInStream_SeekRead_ForArc(stream, pos, buf32, XZ_STREAM_FOOTER_SIZE)) - if (memcmp(buf + 10, XZ_FOOTER_SIG, XZ_FOOTER_SIG_SIZE) != 0) + if (!XZ_FOOTER_12B_ALIGNED16_SIG_CHECK(buf32)) { - UInt32 total = 0; pos += XZ_STREAM_FOOTER_SIZE; - for (;;) { - size_t i; - #define TEMP_BUF_SIZE (1 << 10) - Byte temp[TEMP_BUF_SIZE]; - - i = (pos > TEMP_BUF_SIZE) ? TEMP_BUF_SIZE : (size_t)pos; + // pos != 0 + // (pos & 3) == 0 + size_t i = pos >= TEMP_BUF_SIZE ? TEMP_BUF_SIZE : (size_t)pos; pos -= i; - RINOK(LookInStream_SeekRead_ForArc(stream, pos, temp, i)); - total += (UInt32)i; - for (; i != 0; i--) - if (temp[i - 1] != 0) + RINOK(LookInStream_SeekRead_ForArc(stream, pos, buf32, i)) + i /= 4; + do + if (buf32[i - 1] != 0) break; - if (i != 0) - { - if ((i & 3) != 0) - return SZ_ERROR_NO_ARCHIVE; - pos += i; - break; - } - if (pos < XZ_STREAM_FOOTER_SIZE || total > (1 << 16)) + while (--i); + + pos += i * 4; + #define XZ_STREAM_BACKWARD_READING_PAD_MAX (1 << 16) + // here we don't support rare case with big padding for xz stream. + // so we have padding limit for backward reading. + if ((UInt64)*startOffset - pos > XZ_STREAM_BACKWARD_READING_PAD_MAX) return SZ_ERROR_NO_ARCHIVE; + if (i) + break; } - + // we try to open xz stream after skipping zero padding. + // ((UInt64)*startOffset == pos) is possible here! if (pos < XZ_STREAM_FOOTER_SIZE) return SZ_ERROR_NO_ARCHIVE; pos -= XZ_STREAM_FOOTER_SIZE; - RINOK(LookInStream_SeekRead_ForArc(stream, pos, buf, XZ_STREAM_FOOTER_SIZE)); - if (memcmp(buf + 10, XZ_FOOTER_SIG, XZ_FOOTER_SIG_SIZE) != 0) + RINOK(LookInStream_SeekRead_ForArc(stream, pos, buf32, XZ_STREAM_FOOTER_SIZE)) + if (!XZ_FOOTER_12B_ALIGNED16_SIG_CHECK(buf32)) return SZ_ERROR_NO_ARCHIVE; } - p->flags = (CXzStreamFlags)GetBe16(buf + 8); - + p->flags = (CXzStreamFlags)GetBe16a(buf32 + 2); if (!XzFlags_IsSupported(p->flags)) return SZ_ERROR_UNSUPPORTED; - - if (GetUi32(buf) != CrcCalc(buf + 4, 6)) - return SZ_ERROR_ARCHIVE; - - indexSize = ((UInt64)GetUi32(buf + 4) + 1) << 2; - - if (pos < indexSize) - return SZ_ERROR_ARCHIVE; - - pos -= indexSize; - RINOK(LookInStream_SeekTo(stream, pos)); - RINOK(Xz_ReadIndex(p, stream, indexSize, alloc)); - { - UInt64 totalSize = Xz_GetPackSize(p); - if (totalSize == XZ_SIZE_OVERFLOW - || totalSize >= ((UInt64)1 << 63) - || pos < totalSize + XZ_STREAM_HEADER_SIZE) + /* to eliminate GCC 6.3 warning: + dereferencing type-punned pointer will break strict-aliasing rules */ + const UInt32 *buf_ptr = buf32; + if (GetUi32a(buf_ptr) != CrcCalc(buf32 + 1, 6)) + return SZ_ERROR_ARCHIVE; + } + { + const UInt64 indexSize = ((UInt64)GetUi32a(buf32 + 1) + 1) << 2; + if (pos < indexSize) return SZ_ERROR_ARCHIVE; - pos -= (totalSize + XZ_STREAM_HEADER_SIZE); - RINOK(LookInStream_SeekTo(stream, pos)); - *startOffset = pos; + pos -= indexSize; + // v25.00: relaxed indexSize check. We allow big index table. + // if (indexSize > ((UInt32)1 << 31)) + if (indexSize >= ((size_t)1 << (sizeof(size_t) * 8 - 1))) + return SZ_ERROR_MEM; // SZ_ERROR_ARCHIVE + RINOK(LookInStream_SeekTo(stream, pos)) + // RINOK(Xz_ReadIndex(p, stream, indexSize, alloc)) + { + SRes res; + const size_t size = (size_t)indexSize; + // if (size != indexSize) return SZ_ERROR_UNSUPPORTED; + Byte *buf = (Byte *)ISzAlloc_Alloc(alloc, size); + if (!buf) + return SZ_ERROR_MEM; + res = LookInStream_Read2(stream, buf, size, SZ_ERROR_UNSUPPORTED); + if (res == SZ_OK) + res = Xz_ParseIndex(p, buf, size, alloc); + ISzAlloc_Free(alloc, buf); + RINOK(res) + } + } + { + UInt64 total = Xz_GetPackSize(p); + if (total == XZ_SIZE_OVERFLOW || total >= ((UInt64)1 << 63)) + return SZ_ERROR_ARCHIVE; + total += XZ_STREAM_HEADER_SIZE; + if (pos < total) + return SZ_ERROR_ARCHIVE; + pos -= total; + RINOK(LookInStream_SeekTo(stream, pos)) + *startOffset = (Int64)pos; } { CXzStreamFlags headerFlags; CSecToRead secToRead; SecToRead_CreateVTable(&secToRead); secToRead.realStream = stream; - - RINOK(Xz_ReadHeader(&headerFlags, &secToRead.s)); + RINOK(Xz_ReadHeader(&headerFlags, &secToRead.vt)) return (p->flags == headerFlags) ? SZ_OK : SZ_ERROR_ARCHIVE; } } @@ -234,18 +288,17 @@ static SRes Xz_ReadBackward(CXzStream *p, ILookInStream *stream, Int64 *startOff void Xzs_Construct(CXzs *p) { - p->num = p->numAllocated = 0; - p->streams = 0; + Xzs_CONSTRUCT(p) } -void Xzs_Free(CXzs *p, ISzAlloc *alloc) +void Xzs_Free(CXzs *p, ISzAllocPtr alloc) { size_t i; for (i = 0; i < p->num; i++) Xz_Free(&p->streams[i], alloc); - alloc->Free(alloc, p->streams); + ISzAlloc_Free(alloc, p->streams); p->num = p->numAllocated = 0; - p->streams = 0; + p->streams = NULL; } UInt64 Xzs_GetNumBlocks(const CXzs *p) @@ -262,7 +315,9 @@ UInt64 Xzs_GetUnpackSize(const CXzs *p) UInt64 size = 0; size_t i; for (i = 0; i < p->num; i++) - ADD_SIZE_CHECH(size, Xz_GetUnpackSize(&p->streams[i])); + { + ADD_SIZE_CHECK(size, Xz_GetUnpackSize(&p->streams[i])) + } return size; } @@ -272,42 +327,59 @@ UInt64 Xzs_GetPackSize(const CXzs *p) UInt64 size = 0; size_t i; for (i = 0; i < p->num; i++) - ADD_SIZE_CHECH(size, Xz_GetTotalSize(&p->streams[i])); + { + ADD_SIZE_CHECK(size, Xz_GetTotalSize(&p->streams[i])) + } return size; } */ -SRes Xzs_ReadBackward(CXzs *p, ILookInStream *stream, Int64 *startOffset, ICompressProgress *progress, ISzAlloc *alloc) +SRes Xzs_ReadBackward(CXzs *p, ILookInStreamPtr stream, Int64 *startOffset, ICompressProgressPtr progress, ISzAllocPtr alloc) { Int64 endOffset = 0; - RINOK(stream->Seek(stream, &endOffset, SZ_SEEK_END)); + // it's supposed that CXzs object is empty here. + // if CXzs object is not empty, it will add new streams to that non-empty object. + // Xzs_Free(p, alloc); // it's optional call to empty CXzs object. + RINOK(ILookInStream_Seek(stream, &endOffset, SZ_SEEK_END)) *startOffset = endOffset; for (;;) { CXzStream st; SRes res; - Xz_Construct(&st); + Xz_CONSTRUCT(&st) res = Xz_ReadBackward(&st, stream, startOffset, alloc); - st.startOffset = *startOffset; - RINOK(res); + // if (res == SZ_OK), then (*startOffset) is start offset of new stream if + // if (res != SZ_OK), then (*startOffset) is unchend or it's expected start offset of stream with error + st.startOffset = (UInt64)*startOffset; + // we must store (st) object to array, or we must free (st) local object. + if (res != SZ_OK) + { + Xz_Free(&st, alloc); + return res; + } if (p->num == p->numAllocated) { - size_t newNum = p->num + p->num / 4 + 1; - Byte *data = (Byte *)alloc->Alloc(alloc, newNum * sizeof(CXzStream)); - if (data == 0) + const size_t newNum = p->num + p->num / 4 + 1; + void *data = ISzAlloc_Alloc(alloc, newNum * sizeof(CXzStream)); + if (!data) + { + Xz_Free(&st, alloc); return SZ_ERROR_MEM; + } p->numAllocated = newNum; if (p->num != 0) memcpy(data, p->streams, p->num * sizeof(CXzStream)); - alloc->Free(alloc, p->streams); + ISzAlloc_Free(alloc, p->streams); p->streams = (CXzStream *)data; } + // we use direct copying of raw data from local variable (st) to object in array. + // so we don't need to call Xz_Free(&st, alloc) after copying and after p->num++ p->streams[p->num++] = st; if (*startOffset == 0) - break; - RINOK(LookInStream_SeekTo(stream, *startOffset)); - if (progress && progress->Progress(progress, endOffset - *startOffset, (UInt64)(Int64)-1) != SZ_OK) + return SZ_OK; + // seek operation is optional: + // RINOK(LookInStream_SeekTo(stream, (UInt64)*startOffset)) + if (progress && ICompressProgress_Progress(progress, (UInt64)(endOffset - *startOffset), (UInt64)(Int64)-1) != SZ_OK) return SZ_ERROR_PROGRESS; } - return SZ_OK; } diff --git a/src/plugins/7zip/7za/c/ZstdDec.c b/src/plugins/7zip/7za/c/ZstdDec.c new file mode 100644 index 00000000..6ad47ebc --- /dev/null +++ b/src/plugins/7zip/7za/c/ZstdDec.c @@ -0,0 +1,4067 @@ +/* ZstdDec.c -- Zstd Decoder +2024-06-18 : the code was developed by Igor Pavlov, using Zstandard format + specification and original zstd decoder code as reference code. +original zstd decoder code: Copyright (c) Facebook, Inc. All rights reserved. +This source code is licensed under BSD 3-Clause License. +*/ + +#include "Precomp.h" + +#include +#include +// #include + +#include "Alloc.h" +#include "Xxh64.h" +#include "ZstdDec.h" +#include "CpuArch.h" + +#if defined(MY_CPU_ARM64) +#include +#endif + +/* original-zstd still doesn't support window larger than 2 GiB. + So we also limit our decoder for 2 GiB window: */ +#if defined(MY_CPU_64BIT) && 0 == 1 + #define MAX_WINDOW_SIZE_LOG 41 +#else + #define MAX_WINDOW_SIZE_LOG 31 +#endif + +typedef + #if MAX_WINDOW_SIZE_LOG < 32 + UInt32 + #else + size_t + #endif + CZstdDecOffset; + +// for debug: simpler and smaller code but slow: +// #define Z7_ZSTD_DEC_USE_HUF_STREAM1_ALWAYS + +// #define SHOW_STAT +#ifdef SHOW_STAT +#include +static unsigned g_Num_Blocks_Compressed = 0; +static unsigned g_Num_Blocks_memcpy = 0; +static unsigned g_Num_Wrap_memmove_Num = 0; +static unsigned g_Num_Wrap_memmove_Bytes = 0; +static unsigned g_NumSeqs_total = 0; +// static unsigned g_NumCopy = 0; +static unsigned g_NumOver = 0; +static unsigned g_NumOver2 = 0; +static unsigned g_Num_Match = 0; +static unsigned g_Num_Lits = 0; +static unsigned g_Num_LitsBig = 0; +static unsigned g_Num_Lit0 = 0; +static unsigned g_Num_Rep0 = 0; +static unsigned g_Num_Rep1 = 0; +static unsigned g_Num_Rep2 = 0; +static unsigned g_Num_Rep3 = 0; +static unsigned g_Num_Threshold_0 = 0; +static unsigned g_Num_Threshold_1 = 0; +static unsigned g_Num_Threshold_0sum = 0; +static unsigned g_Num_Threshold_1sum = 0; +#define STAT_UPDATE(v) v +#else +#define STAT_UPDATE(v) +#endif +#define STAT_INC(v) STAT_UPDATE(v++;) + + +typedef struct +{ + const Byte *ptr; + size_t len; +} +CInBufPair; + + +#if defined(MY_CPU_ARM_OR_ARM64) || defined(MY_CPU_X86_OR_AMD64) + #if (defined(__clang__) && (__clang_major__ >= 6)) \ + || (defined(__GNUC__) && (__GNUC__ >= 6)) + // disable for debug: + #define Z7_ZSTD_DEC_USE_BSR + #elif defined(_MSC_VER) && (_MSC_VER >= 1300) + // #if defined(MY_CPU_ARM_OR_ARM64) + #if (_MSC_VER >= 1600) + #include + #endif + // disable for debug: + #define Z7_ZSTD_DEC_USE_BSR + #endif +#endif + +#ifdef Z7_ZSTD_DEC_USE_BSR + #if defined(__clang__) || defined(__GNUC__) + #define MY_clz(x) ((unsigned)__builtin_clz((UInt32)x)) + #else // #if defined(_MSC_VER) + #ifdef MY_CPU_ARM_OR_ARM64 + #define MY_clz _CountLeadingZeros + #endif // MY_CPU_X86_OR_AMD64 + #endif // _MSC_VER +#elif !defined(Z7_ZSTD_DEC_USE_LOG_TABLE) + #define Z7_ZSTD_DEC_USE_LOG_TABLE +#endif + + +static +Z7_FORCE_INLINE +unsigned GetHighestSetBit_32_nonzero_big(UInt32 num) +{ + // (num != 0) + #ifdef MY_clz + return 31 - MY_clz(num); + #elif defined(Z7_ZSTD_DEC_USE_BSR) + { + unsigned long zz; + _BitScanReverse(&zz, num); + return zz; + } + #else + { + int i = -1; + for (;;) + { + i++; + num >>= 1; + if (num == 0) + return (unsigned)i; + } + } + #endif +} + +#ifdef Z7_ZSTD_DEC_USE_LOG_TABLE + +#define R1(a) a, a +#define R2(a) R1(a), R1(a) +#define R3(a) R2(a), R2(a) +#define R4(a) R3(a), R3(a) +#define R5(a) R4(a), R4(a) +#define R6(a) R5(a), R5(a) +#define R7(a) R6(a), R6(a) +#define R8(a) R7(a), R7(a) +#define R9(a) R8(a), R8(a) + +#define Z7_ZSTD_FSE_MAX_ACCURACY 9 +// states[] values in FSE_Generate() can use (Z7_ZSTD_FSE_MAX_ACCURACY + 1) bits. +static const Byte k_zstd_LogTable[2 << Z7_ZSTD_FSE_MAX_ACCURACY] = +{ + R1(0), R1(1), R2(2), R3(3), R4(4), R5(5), R6(6), R7(7), R8(8), R9(9) +}; + +#define GetHighestSetBit_32_nonzero_small(num) (k_zstd_LogTable[num]) +#else +#define GetHighestSetBit_32_nonzero_small GetHighestSetBit_32_nonzero_big +#endif + + +#ifdef MY_clz + #define UPDATE_BIT_OFFSET_FOR_PADDING(b, bitOffset) \ + bitOffset -= (CBitCtr)(MY_clz(b) - 23); +#elif defined(Z7_ZSTD_DEC_USE_BSR) + #define UPDATE_BIT_OFFSET_FOR_PADDING(b, bitOffset) \ + { unsigned long zz; _BitScanReverse(&zz, b); bitOffset -= 8; bitOffset += zz; } +#else + #define UPDATE_BIT_OFFSET_FOR_PADDING(b, bitOffset) \ + for (;;) { bitOffset--; if (b & 0x80) { break; } b <<= 1; } +#endif + +#define SET_bitOffset_TO_PAD(bitOffset, src, srcLen) \ +{ \ + unsigned lastByte = (src)[(size_t)(srcLen) - 1]; \ + if (lastByte == 0) return SZ_ERROR_DATA; \ + bitOffset = (CBitCtr)((srcLen) * 8); \ + UPDATE_BIT_OFFSET_FOR_PADDING(lastByte, bitOffset) \ +} + +#ifndef Z7_ZSTD_DEC_USE_HUF_STREAM1_ALWAYS + +#define SET_bitOffset_TO_PAD_and_SET_BIT_SIZE(bitOffset, src, srcLen_res) \ +{ \ + unsigned lastByte = (src)[(size_t)(srcLen_res) - 1]; \ + if (lastByte == 0) return SZ_ERROR_DATA; \ + srcLen_res *= 8; \ + bitOffset = (CBitCtr)srcLen_res; \ + UPDATE_BIT_OFFSET_FOR_PADDING(lastByte, bitOffset) \ +} + +#endif + +/* +typedef Int32 CBitCtr_signed; +typedef Int32 CBitCtr; +*/ +// /* +typedef ptrdiff_t CBitCtr_signed; +typedef ptrdiff_t CBitCtr; +// */ + + +#define MATCH_LEN_MIN 3 +#define kBlockSizeMax (1u << 17) + +// #define Z7_ZSTD_DEC_PRINT_TABLE + +#ifdef Z7_ZSTD_DEC_PRINT_TABLE +#define NUM_OFFSET_SYMBOLS_PREDEF 29 +#endif +#define NUM_OFFSET_SYMBOLS_MAX (MAX_WINDOW_SIZE_LOG + 1) // 32 +#define NUM_LL_SYMBOLS 36 +#define NUM_ML_SYMBOLS 53 +#define FSE_NUM_SYMBOLS_MAX 53 // NUM_ML_SYMBOLS + +// /* +#if !defined(MY_CPU_X86) || defined(__PIC__) || defined(MY_CPU_64BIT) +#define Z7_ZSTD_DEC_USE_BASES_IN_OBJECT +#endif +// */ +// for debug: +// #define Z7_ZSTD_DEC_USE_BASES_LOCAL +// #define Z7_ZSTD_DEC_USE_BASES_IN_OBJECT + +#define GLOBAL_TABLE(n) k_ ## n + +#if defined(Z7_ZSTD_DEC_USE_BASES_LOCAL) + #define BASES_TABLE(n) a_ ## n +#elif defined(Z7_ZSTD_DEC_USE_BASES_IN_OBJECT) + #define BASES_TABLE(n) p->m_ ## n +#else + #define BASES_TABLE(n) GLOBAL_TABLE(n) +#endif + +#define Z7_ZSTD_DEC_USE_ML_PLUS3 + +#if defined(Z7_ZSTD_DEC_USE_BASES_LOCAL) || \ + defined(Z7_ZSTD_DEC_USE_BASES_IN_OBJECT) + +#define SEQ_EXTRA_TABLES(n) \ + Byte n ## SEQ_LL_EXTRA [NUM_LL_SYMBOLS]; \ + Byte n ## SEQ_ML_EXTRA [NUM_ML_SYMBOLS]; \ + UInt32 n ## SEQ_LL_BASES [NUM_LL_SYMBOLS]; \ + UInt32 n ## SEQ_ML_BASES [NUM_ML_SYMBOLS]; \ + +#define Z7_ZSTD_DEC_USE_BASES_CALC + +#ifdef Z7_ZSTD_DEC_USE_BASES_CALC + + #define FILL_LOC_BASES(n, startSum) \ + { unsigned i; UInt32 sum = startSum; \ + for (i = 0; i != Z7_ARRAY_SIZE(GLOBAL_TABLE(n ## _EXTRA)); i++) \ + { const unsigned a = GLOBAL_TABLE(n ## _EXTRA)[i]; \ + BASES_TABLE(n ## _BASES)[i] = sum; \ + /* if (sum != GLOBAL_TABLE(n ## _BASES)[i]) exit(1); */ \ + sum += (UInt32)1 << a; \ + BASES_TABLE(n ## _EXTRA)[i] = (Byte)a; }} + + #define FILL_LOC_BASES_ALL \ + FILL_LOC_BASES (SEQ_LL, 0) \ + FILL_LOC_BASES (SEQ_ML, MATCH_LEN_MIN) \ + +#else + #define COPY_GLOBAL_ARR(n) \ + memcpy(BASES_TABLE(n), GLOBAL_TABLE(n), sizeof(GLOBAL_TABLE(n))); + #define FILL_LOC_BASES_ALL \ + COPY_GLOBAL_ARR (SEQ_LL_EXTRA) \ + COPY_GLOBAL_ARR (SEQ_ML_EXTRA) \ + COPY_GLOBAL_ARR (SEQ_LL_BASES) \ + COPY_GLOBAL_ARR (SEQ_ML_BASES) \ + +#endif + +#endif + + + +/// The sequence decoding baseline and number of additional bits to read/add +#if !defined(Z7_ZSTD_DEC_USE_BASES_CALC) +static const UInt32 GLOBAL_TABLE(SEQ_LL_BASES) [NUM_LL_SYMBOLS] = +{ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 18, 20, 22, 24, 28, 32, 40, 48, 64, 0x80, 0x100, 0x200, 0x400, 0x800, 0x1000, + 0x2000, 0x4000, 0x8000, 0x10000 +}; +#endif + +static const Byte GLOBAL_TABLE(SEQ_LL_EXTRA) [NUM_LL_SYMBOLS] = +{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 2, 2, 3, 3, 4, 6, 7, 8, 9, 10, 11, 12, + 13, 14, 15, 16 +}; + +#if !defined(Z7_ZSTD_DEC_USE_BASES_CALC) +static const UInt32 GLOBAL_TABLE(SEQ_ML_BASES) [NUM_ML_SYMBOLS] = +{ + 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, 37, 39, 41, 43, 47, 51, 59, 67, 83, 99, 0x83, 0x103, 0x203, 0x403, 0x803, + 0x1003, 0x2003, 0x4003, 0x8003, 0x10003 +}; +#endif + +static const Byte GLOBAL_TABLE(SEQ_ML_EXTRA) [NUM_ML_SYMBOLS] = +{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 2, 2, 3, 3, 4, 4, 5, 7, 8, 9, 10, 11, + 12, 13, 14, 15, 16 +}; + + +#ifdef Z7_ZSTD_DEC_PRINT_TABLE + +static const Int16 SEQ_LL_PREDEF_DIST [NUM_LL_SYMBOLS] = +{ + 4, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 1, 1, 1, 1, 1, + -1,-1,-1,-1 +}; +static const Int16 SEQ_OFFSET_PREDEF_DIST [NUM_OFFSET_SYMBOLS_PREDEF] = +{ + 1, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1,-1,-1,-1,-1,-1 +}; +static const Int16 SEQ_ML_PREDEF_DIST [NUM_ML_SYMBOLS] = +{ + 1, 4, 3, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,-1,-1, + -1,-1,-1,-1,-1 +}; + +#endif + +// typedef int FastInt; +// typedef Int32 FastInt32; +typedef unsigned FastInt; +typedef UInt32 FastInt32; +typedef FastInt32 CFseRecord; + + +#define FSE_REC_LEN_OFFSET 8 +#define FSE_REC_STATE_OFFSET 16 +#define GET_FSE_REC_SYM(st) ((Byte)(st)) +#define GET_FSE_REC_LEN(st) ((Byte)((st) >> FSE_REC_LEN_OFFSET)) +#define GET_FSE_REC_STATE(st) ((st) >> FSE_REC_STATE_OFFSET) + +// #define FSE_REC_SYM_MASK (0xff) +// #define GET_FSE_REC_SYM(st) (st & FSE_REC_SYM_MASK) + +#define W_BASE(state, len, sym) \ + (((UInt32)state << (4 + FSE_REC_STATE_OFFSET)) + \ + (len << FSE_REC_LEN_OFFSET) + (sym)) +#define W(state, len, sym) W_BASE(state, len, sym) +static const CFseRecord k_PredefRecords_LL[1 << 6] = { +W(0,4, 0),W(1,4, 0),W(2,5, 1),W(0,5, 3),W(0,5, 4),W(0,5, 6),W(0,5, 7),W(0,5, 9), +W(0,5,10),W(0,5,12),W(0,6,14),W(0,5,16),W(0,5,18),W(0,5,19),W(0,5,21),W(0,5,22), +W(0,5,24),W(2,5,25),W(0,5,26),W(0,6,27),W(0,6,29),W(0,6,31),W(2,4, 0),W(0,4, 1), +W(0,5, 2),W(2,5, 4),W(0,5, 5),W(2,5, 7),W(0,5, 8),W(2,5,10),W(0,5,11),W(0,6,13), +W(2,5,16),W(0,5,17),W(2,5,19),W(0,5,20),W(2,5,22),W(0,5,23),W(0,4,25),W(1,4,25), +W(2,5,26),W(0,6,28),W(0,6,30),W(3,4, 0),W(1,4, 1),W(2,5, 2),W(2,5, 3),W(2,5, 5), +W(2,5, 6),W(2,5, 8),W(2,5, 9),W(2,5,11),W(2,5,12),W(0,6,15),W(2,5,17),W(2,5,18), +W(2,5,20),W(2,5,21),W(2,5,23),W(2,5,24),W(0,6,35),W(0,6,34),W(0,6,33),W(0,6,32) +}; +static const CFseRecord k_PredefRecords_OF[1 << 5] = { +W(0,5, 0),W(0,4, 6),W(0,5, 9),W(0,5,15),W(0,5,21),W(0,5, 3),W(0,4, 7),W(0,5,12), +W(0,5,18),W(0,5,23),W(0,5, 5),W(0,4, 8),W(0,5,14),W(0,5,20),W(0,5, 2),W(1,4, 7), +W(0,5,11),W(0,5,17),W(0,5,22),W(0,5, 4),W(1,4, 8),W(0,5,13),W(0,5,19),W(0,5, 1), +W(1,4, 6),W(0,5,10),W(0,5,16),W(0,5,28),W(0,5,27),W(0,5,26),W(0,5,25),W(0,5,24) +}; +#if defined(Z7_ZSTD_DEC_USE_ML_PLUS3) +#undef W +#define W(state, len, sym) W_BASE(state, len, (sym + MATCH_LEN_MIN)) +#endif +static const CFseRecord k_PredefRecords_ML[1 << 6] = { +W(0,6, 0),W(0,4, 1),W(2,5, 2),W(0,5, 3),W(0,5, 5),W(0,5, 6),W(0,5, 8),W(0,6,10), +W(0,6,13),W(0,6,16),W(0,6,19),W(0,6,22),W(0,6,25),W(0,6,28),W(0,6,31),W(0,6,33), +W(0,6,35),W(0,6,37),W(0,6,39),W(0,6,41),W(0,6,43),W(0,6,45),W(1,4, 1),W(0,4, 2), +W(2,5, 3),W(0,5, 4),W(2,5, 6),W(0,5, 7),W(0,6, 9),W(0,6,12),W(0,6,15),W(0,6,18), +W(0,6,21),W(0,6,24),W(0,6,27),W(0,6,30),W(0,6,32),W(0,6,34),W(0,6,36),W(0,6,38), +W(0,6,40),W(0,6,42),W(0,6,44),W(2,4, 1),W(3,4, 1),W(1,4, 2),W(2,5, 4),W(2,5, 5), +W(2,5, 7),W(2,5, 8),W(0,6,11),W(0,6,14),W(0,6,17),W(0,6,20),W(0,6,23),W(0,6,26), +W(0,6,29),W(0,6,52),W(0,6,51),W(0,6,50),W(0,6,49),W(0,6,48),W(0,6,47),W(0,6,46) +}; + + +// sum of freqs[] must be correct +// (numSyms != 0) +// (accuracy >= 5) +static +Z7_NO_INLINE +// Z7_FORCE_INLINE +void FSE_Generate(CFseRecord *table, + const Int16 *const freqs, const size_t numSyms, + const unsigned accuracy, UInt32 delta) +{ + size_t size = (size_t)1 << accuracy; + // max value in states[x] is ((1 << accuracy) * 2) + UInt16 states[FSE_NUM_SYMBOLS_MAX]; + { + /* Symbols with "less than 1" probability get a single cell, + starting from the end of the table. + These symbols define a full state reset, reading (accuracy) bits. */ + size_t threshold = size; + { + size_t s = 0; + do + if (freqs[s] == -1) + { + table[--threshold] = (CFseRecord)s; + states[s] = 1; + } + while (++s != numSyms); + } + + #ifdef SHOW_STAT + if (threshold == size) + { + STAT_INC(g_Num_Threshold_0) + STAT_UPDATE(g_Num_Threshold_0sum += (unsigned)size;) + } + else + { + STAT_INC(g_Num_Threshold_1) + STAT_UPDATE(g_Num_Threshold_1sum += (unsigned)size;) + } + #endif + + // { unsigned uuu; for (uuu = 0; uuu < 400; uuu++) + { + // Each (symbol) gets freqs[symbol] cells. + // Cell allocation is spread, not linear. + const size_t step = (size >> 1) + (size >> 3) + 3; + size_t pos = 0; + // const unsigned mask = size - 1; + /* + if (threshold == size) + { + size_t s = 0; + size--; + do + { + int freq = freqs[s]; + if (freq <= 0) + continue; + states[s] = (UInt16)freq; + do + { + table[pos] (CFseRecord)s; + pos = (pos + step) & size; // & mask; + } + while (--freq); + } + while (++s != numSyms); + } + else + */ + { + size_t s = 0; + size--; + do + { + int freq = freqs[s]; + if (freq <= 0) + continue; + states[s] = (UInt16)freq; + do + { + table[pos] = (CFseRecord)s; + // we skip position, if it's already occupied by a "less than 1" probability symbol. + // (step) is coprime to table size, so the cycle will visit each position exactly once + do + pos = (pos + step) & size; // & mask; + while (pos >= threshold); + } + while (--freq); + } + while (++s != numSyms); + } + size++; + // (pos != 0) is unexpected case that means that freqs[] are not correct. + // so it's some failure in code (for example, incorrect predefined freq[] table) + // if (pos != 0) return SZ_ERROR_FAIL; + } + // } + } + { + const CFseRecord * const limit = table + size; + delta = ((UInt32)size << FSE_REC_STATE_OFFSET) - delta; + /* State increases by symbol over time, decreasing number of bits. + Baseline increases until the bit threshold is passed, at which point it resets to 0 */ + do + { + #define TABLE_ITER(a) \ + { \ + const FastInt sym = (FastInt)table[a]; \ + const unsigned nextState = states[sym]; \ + unsigned nb; \ + states[sym] = (UInt16)(nextState + 1); \ + nb = accuracy - GetHighestSetBit_32_nonzero_small(nextState); \ + table[a] = (CFseRecord)(sym - delta \ + + ((UInt32)nb << FSE_REC_LEN_OFFSET) \ + + ((UInt32)nextState << FSE_REC_STATE_OFFSET << nb)); \ + } + TABLE_ITER(0) + TABLE_ITER(1) + table += 2; + } + while (table != limit); + } +} + + +#ifdef Z7_ZSTD_DEC_PRINT_TABLE + +static void Print_Predef(unsigned predefAccuracy, + const unsigned numSymsPredef, + const Int16 * const predefFreqs, + const CFseRecord *checkTable) +{ + CFseRecord table[1 << 6]; + unsigned i; + FSE_Generate(table, predefFreqs, numSymsPredef, predefAccuracy, + #if defined(Z7_ZSTD_DEC_USE_ML_PLUS3) + numSymsPredef == NUM_ML_SYMBOLS ? MATCH_LEN_MIN : + #endif + 0 + ); + if (memcmp(table, checkTable, sizeof(UInt32) << predefAccuracy) != 0) + exit(1); + for (i = 0; i < (1u << predefAccuracy); i++) + { + const UInt32 v = table[i]; + const unsigned state = (unsigned)(GET_FSE_REC_STATE(v)); + if (state & 0xf) + exit(1); + if (i != 0) + { + printf(","); + if (i % 8 == 0) + printf("\n"); + } + printf("W(%d,%d,%2d)", + (unsigned)(state >> 4), + (unsigned)((v >> FSE_REC_LEN_OFFSET) & 0xff), + (unsigned)GET_FSE_REC_SYM(v)); + } + printf("\n\n"); +} + +#endif + + +#define GET16(dest, p) { const Byte *ptr = p; dest = GetUi16(ptr); } +#define GET32(dest, p) { const Byte *ptr = p; dest = GetUi32(ptr); } + +// (1 <= numBits <= 9) +#define FORWARD_READ_BITS(destVal, numBits, mask) \ + { const CBitCtr_signed bos3 = (bitOffset) >> 3; \ + if (bos3 >= 0) return SZ_ERROR_DATA; \ + GET16(destVal, src + bos3) \ + destVal >>= (bitOffset) & 7; \ + bitOffset += (CBitCtr_signed)(numBits); \ + mask = (1u << (numBits)) - 1; \ + destVal &= mask; \ + } + +#define FORWARD_READ_1BIT(destVal) \ + { const CBitCtr_signed bos3 = (bitOffset) >> 3; \ + if (bos3 >= 0) return SZ_ERROR_DATA; \ + destVal = *(src + bos3); \ + destVal >>= (bitOffset) & 7; \ + (bitOffset)++; \ + destVal &= 1; \ + } + + +// in: (accuracyMax <= 9) +// at least 2 bytes will be processed from (in) stream. +// at return: (in->len > 0) +static +Z7_NO_INLINE +SRes FSE_DecodeHeader(CFseRecord *const table, + CInBufPair *const in, + const unsigned accuracyMax, + Byte *const accuracyRes, + unsigned numSymbolsMax) +{ + unsigned accuracy; + unsigned remain1; + unsigned syms; + Int16 freqs[FSE_NUM_SYMBOLS_MAX + 3]; // +3 for overwrite (repeat) + const Byte *src = in->ptr; + CBitCtr_signed bitOffset = (CBitCtr_signed)in->len - 1; + if (bitOffset <= 0) + return SZ_ERROR_DATA; + accuracy = *src & 0xf; + accuracy += 5; + if (accuracy > accuracyMax) + return SZ_ERROR_DATA; + *accuracyRes = (Byte)accuracy; + remain1 = (1u << accuracy) + 1; // (it's remain_freqs_sum + 1) + syms = 0; + src += bitOffset; // src points to last byte + bitOffset = 4 - (bitOffset << 3); + + for (;;) + { + // (2 <= remain1) + const unsigned bits = GetHighestSetBit_32_nonzero_small((unsigned)remain1); + // (1 <= bits <= accuracy) + unsigned val; // it must be unsigned or int + unsigned mask; + FORWARD_READ_BITS(val, bits, mask) + { + const unsigned val2 = remain1 + val - mask; + if (val2 > mask) + { + unsigned bit; + FORWARD_READ_1BIT(bit) + if (bit) + val = val2; + } + } + { + // (remain1 >= 2) + // (0 <= (int)val <= remain1) + val = (unsigned)((int)val - 1); + // val now is "probability" of symbol + // (probability == -1) means "less than 1" frequency. + // (-1 <= (int)val <= remain1 - 1) + freqs[syms++] = (Int16)(int)val; + if (val != 0) + { + remain1 -= (int)val < 0 ? 1u : (unsigned)val; + // remain1 -= val; + // val >>= (sizeof(val) * 8 - 2); + // remain1 -= val & 2; + // freqs[syms++] = (Int16)(int)val; + // syms++; + if (remain1 == 1) + break; + if (syms >= FSE_NUM_SYMBOLS_MAX) + return SZ_ERROR_DATA; + } + else // if (val == 0) + { + // freqs[syms++] = 0; + // syms++; + for (;;) + { + unsigned repeat; + FORWARD_READ_BITS(repeat, 2, mask) + freqs[syms ] = 0; + freqs[syms + 1] = 0; + freqs[syms + 2] = 0; + syms += repeat; + if (syms >= FSE_NUM_SYMBOLS_MAX) + return SZ_ERROR_DATA; + if (repeat != 3) + break; + } + } + } + } + + if (syms > numSymbolsMax) + return SZ_ERROR_DATA; + bitOffset += 7; + bitOffset >>= 3; + if (bitOffset > 0) + return SZ_ERROR_DATA; + in->ptr = src + bitOffset; + in->len = (size_t)(1 - bitOffset); + { + // unsigned uuu; for (uuu = 0; uuu < 50; uuu++) + FSE_Generate(table, freqs, syms, accuracy, + #if defined(Z7_ZSTD_DEC_USE_ML_PLUS3) + numSymbolsMax == NUM_ML_SYMBOLS ? MATCH_LEN_MIN : + #endif + 0 + ); + } + return SZ_OK; +} + + +// ---------- HUFFMAN ---------- + +#define HUF_MAX_BITS 12 +#define HUF_MAX_SYMBS 256 +#define HUF_DUMMY_SIZE (128 + 8 * 2) // it must multiple of 8 +// #define HUF_DUMMY_SIZE 0 +#define HUF_TABLE_SIZE ((2 << HUF_MAX_BITS) + HUF_DUMMY_SIZE) +#define HUF_GET_SYMBOLS(table) ((table) + (1 << HUF_MAX_BITS) + HUF_DUMMY_SIZE) +// #define HUF_GET_LENS(table) (table) + +typedef struct +{ + // Byte table[HUF_TABLE_SIZE]; + UInt64 table64[HUF_TABLE_SIZE / sizeof(UInt64)]; +} +CZstdDecHufTable; + +/* +Input: + numSyms != 0 + (bits) array size must be aligned for 2 + if (numSyms & 1), then bits[numSyms] == 0, + Huffman tree must be correct before Huf_Build() call: + (sum (1/2^bits[i]) == 1). + && (bits[i] <= HUF_MAX_BITS) +*/ +static +Z7_FORCE_INLINE +void Huf_Build(Byte * const table, + const Byte *bits, const unsigned numSyms) +{ + unsigned counts0[HUF_MAX_BITS + 1]; + unsigned counts1[HUF_MAX_BITS + 1]; + const Byte * const bitsEnd = bits + numSyms; + // /* + { + unsigned t; + for (t = 0; t < Z7_ARRAY_SIZE(counts0); t++) counts0[t] = 0; + for (t = 0; t < Z7_ARRAY_SIZE(counts1); t++) counts1[t] = 0; + } + // */ + // memset(counts0, 0, sizeof(counts0)); + // memset(counts1, 0, sizeof(counts1)); + { + const Byte *bits2 = bits; + // we access additional bits[symbol] if (numSyms & 1) + do + { + counts0[bits2[0]]++; + counts1[bits2[1]]++; + } + while ((bits2 += 2) < bitsEnd); + } + { + unsigned r = 0; + unsigned i = HUF_MAX_BITS; + // Byte *lens = HUF_GET_LENS(symbols); + do + { + const unsigned num = (counts0[i] + counts1[i]) << (HUF_MAX_BITS - i); + counts0[i] = r; + if (num) + { + Byte *lens = &table[r]; + r += num; + memset(lens, (int)i, num); + } + } + while (--i); + counts0[0] = 0; // for speculated loads + // no need for check: + // if (r != (UInt32)1 << HUF_MAX_BITS) exit(0); + } + { + #ifdef MY_CPU_64BIT + UInt64 + #else + UInt32 + #endif + v = 0; + Byte *symbols = HUF_GET_SYMBOLS(table); + do + { + const unsigned nb = *bits++; + if (nb) + { + const unsigned code = counts0[nb]; + const unsigned num = (1u << HUF_MAX_BITS) >> nb; + counts0[nb] = code + num; + // memset(&symbols[code], i, num); + // /* + { + Byte *s2 = &symbols[code]; + if (num <= 2) + { + s2[0] = (Byte)v; + s2[(size_t)num - 1] = (Byte)v; + } + else if (num <= 8) + { + *(UInt32 *)(void *)s2 = (UInt32)v; + *(UInt32 *)(void *)(s2 + (size_t)num - 4) = (UInt32)v; + } + else + { + #ifdef MY_CPU_64BIT + UInt64 *s = (UInt64 *)(void *)s2; + const UInt64 *lim = (UInt64 *)(void *)(s2 + num); + do + { + s[0] = v; s[1] = v; s += 2; + } + while (s != lim); + #else + UInt32 *s = (UInt32 *)(void *)s2; + const UInt32 *lim = (const UInt32 *)(const void *)(s2 + num); + do + { + s[0] = v; s[1] = v; s += 2; + s[0] = v; s[1] = v; s += 2; + } + while (s != lim); + #endif + } + } + // */ + } + v += + #ifdef MY_CPU_64BIT + 0x0101010101010101; + #else + 0x01010101; + #endif + } + while (bits != bitsEnd); + } +} + + + +// how many bytes (src) was moved back from original value. +// we need (HUF_SRC_OFFSET == 3) for optimized 32-bit memory access +#define HUF_SRC_OFFSET 3 + +// v <<= 8 - (bitOffset & 7) + numBits; +// v >>= 32 - HUF_MAX_BITS; +#define HUF_GET_STATE(v, bitOffset, numBits) \ + GET32(v, src + (HUF_SRC_OFFSET - 3) + ((CBitCtr_signed)bitOffset >> 3)) \ + v >>= 32 - HUF_MAX_BITS - 8 + ((unsigned)bitOffset & 7) - numBits; \ + v &= (1u << HUF_MAX_BITS) - 1; \ + + +#ifndef Z7_ZSTD_DEC_USE_HUF_STREAM1_ALWAYS +#if defined(MY_CPU_AMD64) && defined(_MSC_VER) && _MSC_VER == 1400 \ + || !defined(MY_CPU_X86_OR_AMD64) \ + // || 1 == 1 /* for debug : to force STREAM4_PRELOAD mode */ + // we need big number (>=16) of registers for PRELOAD4 + #define Z7_ZSTD_DEC_USE_HUF_STREAM4_PRELOAD4 + // #define Z7_ZSTD_DEC_USE_HUF_STREAM4_PRELOAD2 // for debug +#endif +#endif + +// for debug: simpler and smaller code but slow: +// #define Z7_ZSTD_DEC_USE_HUF_STREAM1_SIMPLE + +#if defined(Z7_ZSTD_DEC_USE_HUF_STREAM1_SIMPLE) || \ + !defined(Z7_ZSTD_DEC_USE_HUF_STREAM1_ALWAYS) + +#define HUF_DECODE(bitOffset, dest) \ +{ \ + UInt32 v; \ + HUF_GET_STATE(v, bitOffset, 0) \ + bitOffset -= table[v]; \ + *(dest) = symbols[v]; \ + if ((CBitCtr_signed)bitOffset < 0) return SZ_ERROR_DATA; \ +} + +#endif + +#if !defined(Z7_ZSTD_DEC_USE_HUF_STREAM1_SIMPLE) || \ + defined(Z7_ZSTD_DEC_USE_HUF_STREAM4_PRELOAD4) || \ + defined(Z7_ZSTD_DEC_USE_HUF_STREAM4_PRELOAD2) \ + +#define HUF_DECODE_2_INIT(v, bitOffset) \ + HUF_GET_STATE(v, bitOffset, 0) + +#define HUF_DECODE_2(v, bitOffset, dest) \ +{ \ + unsigned numBits; \ + numBits = table[v]; \ + *(dest) = symbols[v]; \ + HUF_GET_STATE(v, bitOffset, numBits) \ + bitOffset -= (CBitCtr)numBits; \ + if ((CBitCtr_signed)bitOffset < 0) return SZ_ERROR_DATA; \ +} + +#endif + + +// src == ptr - HUF_SRC_OFFSET +// we are allowed to access 3 bytes before start of input buffer +static +Z7_NO_INLINE +SRes Huf_Decompress_1stream(const Byte * const table, + const Byte *src, const size_t srcLen, + Byte *dest, const size_t destLen) +{ + CBitCtr bitOffset; + if (srcLen == 0) + return SZ_ERROR_DATA; + SET_bitOffset_TO_PAD (bitOffset, src + HUF_SRC_OFFSET, srcLen) + if (destLen) + { + const Byte *symbols = HUF_GET_SYMBOLS(table); + const Byte *destLim = dest + destLen; + #ifdef Z7_ZSTD_DEC_USE_HUF_STREAM1_SIMPLE + { + do + { + HUF_DECODE (bitOffset, dest) + } + while (++dest != destLim); + } + #else + { + UInt32 v; + HUF_DECODE_2_INIT (v, bitOffset) + do + { + HUF_DECODE_2 (v, bitOffset, dest) + } + while (++dest != destLim); + } + #endif + } + return bitOffset == 0 ? SZ_OK : SZ_ERROR_DATA; +} + + +// for debug : it reduces register pressure : by array copy can be slow : +// #define Z7_ZSTD_DEC_USE_HUF_LOCAL + +// src == ptr + (6 - HUF_SRC_OFFSET) +// srcLen >= 10 +// we are allowed to access 3 bytes before start of input buffer +static +Z7_NO_INLINE +SRes Huf_Decompress_4stream(const Byte * const + #ifdef Z7_ZSTD_DEC_USE_HUF_LOCAL + table2, + #else + table, + #endif + const Byte *src, size_t srcLen, + Byte *dest, size_t destLen) +{ + #ifdef Z7_ZSTD_DEC_USE_HUF_LOCAL + Byte table[HUF_TABLE_SIZE]; + #endif + UInt32 sizes[3]; + const size_t delta = (destLen + 3) / 4; + if ((sizes[0] = GetUi16(src + (0 + HUF_SRC_OFFSET - 6))) == 0) return SZ_ERROR_DATA; + if ((sizes[1] = GetUi16(src + (2 + HUF_SRC_OFFSET - 6))) == 0) return SZ_ERROR_DATA; + sizes[1] += sizes[0]; + if ((sizes[2] = GetUi16(src + (4 + HUF_SRC_OFFSET - 6))) == 0) return SZ_ERROR_DATA; + sizes[2] += sizes[1]; + srcLen -= 6; + if (srcLen <= sizes[2]) + return SZ_ERROR_DATA; + + #ifdef Z7_ZSTD_DEC_USE_HUF_LOCAL + { + // unsigned i = 0; for(; i < 1000; i++) + memcpy(table, table2, HUF_TABLE_SIZE); + } + #endif + + #ifndef Z7_ZSTD_DEC_USE_HUF_STREAM1_ALWAYS + { + CBitCtr bitOffset_0, + bitOffset_1, + bitOffset_2, + bitOffset_3; + { + SET_bitOffset_TO_PAD_and_SET_BIT_SIZE (bitOffset_0, src + HUF_SRC_OFFSET, sizes[0]) + SET_bitOffset_TO_PAD_and_SET_BIT_SIZE (bitOffset_1, src + HUF_SRC_OFFSET, sizes[1]) + SET_bitOffset_TO_PAD_and_SET_BIT_SIZE (bitOffset_2, src + HUF_SRC_OFFSET, sizes[2]) + SET_bitOffset_TO_PAD (bitOffset_3, src + HUF_SRC_OFFSET, srcLen) + } + { + const Byte * const symbols = HUF_GET_SYMBOLS(table); + Byte *destLim = dest + destLen - delta * 3; + + if (dest != destLim) + #ifdef Z7_ZSTD_DEC_USE_HUF_STREAM4_PRELOAD4 + { + UInt32 v_0, v_1, v_2, v_3; + HUF_DECODE_2_INIT (v_0, bitOffset_0) + HUF_DECODE_2_INIT (v_1, bitOffset_1) + HUF_DECODE_2_INIT (v_2, bitOffset_2) + HUF_DECODE_2_INIT (v_3, bitOffset_3) + // #define HUF_DELTA (1 << 17) / 4 + do + { + HUF_DECODE_2 (v_3, bitOffset_3, dest + delta * 3) + HUF_DECODE_2 (v_2, bitOffset_2, dest + delta * 2) + HUF_DECODE_2 (v_1, bitOffset_1, dest + delta) + HUF_DECODE_2 (v_0, bitOffset_0, dest) + } + while (++dest != destLim); + /* + {// unsigned y = 0; for (;y < 1; y++) + { + const size_t num = destLen - delta * 3; + Byte *orig = dest - num; + memmove (orig + delta , orig + HUF_DELTA, num); + memmove (orig + delta * 2, orig + HUF_DELTA * 2, num); + memmove (orig + delta * 3, orig + HUF_DELTA * 3, num); + }} + */ + } + #elif defined(Z7_ZSTD_DEC_USE_HUF_STREAM4_PRELOAD2) + { + UInt32 v_0, v_1, v_2, v_3; + HUF_DECODE_2_INIT (v_0, bitOffset_0) + HUF_DECODE_2_INIT (v_1, bitOffset_1) + do + { + HUF_DECODE_2 (v_0, bitOffset_0, dest) + HUF_DECODE_2 (v_1, bitOffset_1, dest + delta) + } + while (++dest != destLim); + dest = destLim - (destLen - delta * 3); + dest += delta * 2; + destLim += delta * 2; + HUF_DECODE_2_INIT (v_2, bitOffset_2) + HUF_DECODE_2_INIT (v_3, bitOffset_3) + do + { + HUF_DECODE_2 (v_2, bitOffset_2, dest) + HUF_DECODE_2 (v_3, bitOffset_3, dest + delta) + } + while (++dest != destLim); + dest -= delta * 2; + destLim -= delta * 2; + } + #else + { + do + { + HUF_DECODE (bitOffset_3, dest + delta * 3) + HUF_DECODE (bitOffset_2, dest + delta * 2) + HUF_DECODE (bitOffset_1, dest + delta) + HUF_DECODE (bitOffset_0, dest) + } + while (++dest != destLim); + } + #endif + + if (bitOffset_3 != (CBitCtr)sizes[2]) + return SZ_ERROR_DATA; + if (destLen &= 3) + { + destLim = dest + 4 - destLen; + do + { + HUF_DECODE (bitOffset_2, dest + delta * 2) + HUF_DECODE (bitOffset_1, dest + delta) + HUF_DECODE (bitOffset_0, dest) + } + while (++dest != destLim); + } + if ( bitOffset_0 != 0 + || bitOffset_1 != (CBitCtr)sizes[0] + || bitOffset_2 != (CBitCtr)sizes[1]) + return SZ_ERROR_DATA; + } + } + #else // Z7_ZSTD_DEC_USE_HUF_STREAM1_ALWAYS + { + unsigned i; + for (i = 0; i < 4; i++) + { + size_t d = destLen; + size_t size = srcLen; + if (i != 3) + { + d = delta; + size = sizes[i]; + } + if (i != 0) + size -= sizes[i - 1]; + destLen -= d; + RINOK(Huf_Decompress_1stream(table, src, size, dest, d)) + dest += d; + src += size; + } + } + #endif + + return SZ_OK; +} + + + +// (in->len != 0) +// we are allowed to access in->ptr[-3] +// at least 2 bytes in (in->ptr) will be processed +static SRes Huf_DecodeTable(CZstdDecHufTable *const p, CInBufPair *const in) +{ + Byte weights[HUF_MAX_SYMBS + 1]; // +1 for extra write for loop unroll + unsigned numSyms; + const unsigned header = *(in->ptr)++; + in->len--; + // memset(weights, 0, sizeof(weights)); + if (header >= 128) + { + // direct representation: 4 bits field (0-15) per weight + numSyms = header - 127; + // numSyms != 0 + { + const size_t numBytes = (numSyms + 1) / 2; + const Byte *const ws = in->ptr; + size_t i = 0; + if (in->len < numBytes) + return SZ_ERROR_DATA; + in->ptr += numBytes; + in->len -= numBytes; + do + { + const unsigned b = ws[i]; + weights[i * 2 ] = (Byte)(b >> 4); + weights[i * 2 + 1] = (Byte)(b & 0xf); + } + while (++i != numBytes); + /* 7ZIP: we can restore correct zero value for weights[numSyms], + if we want to use zero values starting from numSyms in code below. */ + // weights[numSyms] = 0; + } + } + else + { + #define MAX_ACCURACY_LOG_FOR_WEIGHTS 6 + CFseRecord table[1 << MAX_ACCURACY_LOG_FOR_WEIGHTS]; + + Byte accuracy; + const Byte *src; + size_t srcLen; + if (in->len < header) + return SZ_ERROR_DATA; + { + CInBufPair fse_stream; + fse_stream.len = header; + fse_stream.ptr = in->ptr; + in->ptr += header; + in->len -= header; + RINOK(FSE_DecodeHeader(table, &fse_stream, + MAX_ACCURACY_LOG_FOR_WEIGHTS, + &accuracy, + 16 // num weight symbols max (max-symbol is 15) + )) + // at least 2 bytes were processed in fse_stream. + // (srcLen > 0) after FSE_DecodeHeader() + // if (srcLen == 0) return SZ_ERROR_DATA; + src = fse_stream.ptr; + srcLen = fse_stream.len; + } + // we are allowed to access src[-5] + { + // unsigned yyy = 200; do { + CBitCtr bitOffset; + FastInt32 state1, state2; + SET_bitOffset_TO_PAD (bitOffset, src, srcLen) + state1 = accuracy; + src -= state1 >> 2; // src -= 1; // for GET16() optimization + state1 <<= FSE_REC_LEN_OFFSET; + state2 = state1; + numSyms = 0; + for (;;) + { + #define FSE_WEIGHT_DECODE(st) \ + { \ + const unsigned bits = GET_FSE_REC_LEN(st); \ + FastInt r; \ + GET16(r, src + (bitOffset >> 3)) \ + r >>= (unsigned)bitOffset & 7; \ + if ((CBitCtr_signed)(bitOffset -= (CBitCtr)bits) < 0) \ + { if (bitOffset + (CBitCtr)bits != 0) \ + return SZ_ERROR_DATA; \ + break; } \ + r &= 0xff; \ + r >>= 8 - bits; \ + st = table[GET_FSE_REC_STATE(st) + r]; \ + weights[numSyms++] = (Byte)GET_FSE_REC_SYM(st); \ + } + FSE_WEIGHT_DECODE (state1) + FSE_WEIGHT_DECODE (state2) + if (numSyms == HUF_MAX_SYMBS) + return SZ_ERROR_DATA; + } + // src += (unsigned)accuracy >> 2; } while (--yyy); + } + } + + // Build using weights: + { + UInt32 sum = 0; + { + // numSyms >= 1 + unsigned i = 0; + weights[numSyms] = 0; + do + { + sum += ((UInt32)1 << weights[i ]) & ~(UInt32)1; + sum += ((UInt32)1 << weights[i + 1]) & ~(UInt32)1; + i += 2; + } + while (i < numSyms); + if (sum == 0) + return SZ_ERROR_DATA; + } + { + const unsigned maxBits = GetHighestSetBit_32_nonzero_big(sum) + 1; + { + const UInt32 left = ((UInt32)1 << maxBits) - sum; + // (left != 0) + // (left) must be power of 2 in correct stream + if (left & (left - 1)) + return SZ_ERROR_DATA; + weights[numSyms++] = (Byte)GetHighestSetBit_32_nonzero_big(left); + } + // if (numSyms & 1) + weights[numSyms] = 0; // for loop unroll + // numSyms >= 2 + { + unsigned i = 0; + do + { + /* + #define WEIGHT_ITER(a) \ + { unsigned w = weights[i + (a)]; \ + const unsigned t = maxBits - w; \ + w = w ? t: w; \ + if (w > HUF_MAX_BITS) return SZ_ERROR_DATA; \ + weights[i + (a)] = (Byte)w; } + */ + // /* + #define WEIGHT_ITER(a) \ + { unsigned w = weights[i + (a)]; \ + if (w) { \ + w = maxBits - w; \ + if (w > HUF_MAX_BITS) return SZ_ERROR_DATA; \ + weights[i + (a)] = (Byte)w; }} + // */ + WEIGHT_ITER(0) + // WEIGHT_ITER(1) + // i += 2; + } + while (++i != numSyms); + } + } + } + { + // unsigned yyy; for (yyy = 0; yyy < 100; yyy++) + Huf_Build((Byte *)(void *)p->table64, weights, numSyms); + } + return SZ_OK; +} + + +typedef enum +{ + k_SeqMode_Predef = 0, + k_SeqMode_RLE = 1, + k_SeqMode_FSE = 2, + k_SeqMode_Repeat = 3 +} +z7_zstd_enum_SeqMode; + +// predefAccuracy == 5 for OFFSET symbols +// predefAccuracy == 6 for MATCH/LIT LEN symbols +static +SRes +Z7_NO_INLINE +// Z7_FORCE_INLINE +FSE_Decode_SeqTable(CFseRecord * const table, + CInBufPair * const in, + unsigned predefAccuracy, + Byte * const accuracyRes, + unsigned numSymbolsMax, + const CFseRecord * const predefs, + const unsigned seqMode) +{ + // UNUSED_VAR(numSymsPredef) + // UNUSED_VAR(predefFreqs) + if (seqMode == k_SeqMode_FSE) + { + // unsigned y = 50; CInBufPair in2 = *in; do { *in = in2; RINOK( + return + FSE_DecodeHeader(table, in, + predefAccuracy + 3, // accuracyMax + accuracyRes, + numSymbolsMax) + ; + // )} while (--y); return SZ_OK; + } + // numSymsMax = numSymsPredef + ((predefAccuracy & 1) * (32 - 29))); // numSymsMax + // numSymsMax == 32 for offsets + + if (seqMode == k_SeqMode_Predef) + { + *accuracyRes = (Byte)predefAccuracy; + memcpy(table, predefs, sizeof(UInt32) << predefAccuracy); + return SZ_OK; + } + + // (seqMode == k_SeqMode_RLE) + if (in->len == 0) + return SZ_ERROR_DATA; + in->len--; + { + const Byte *ptr = in->ptr; + const unsigned sym = ptr[0]; + in->ptr = ptr + 1; + if (sym >= numSymbolsMax) + return SZ_ERROR_DATA; + table[0] = (FastInt32)sym + #if defined(Z7_ZSTD_DEC_USE_ML_PLUS3) + + (numSymbolsMax == NUM_ML_SYMBOLS ? MATCH_LEN_MIN : 0) + #endif + ; + *accuracyRes = 0; + } + return SZ_OK; +} + + +typedef struct +{ + CFseRecord of[1 << 8]; + CFseRecord ll[1 << 9]; + CFseRecord ml[1 << 9]; +} +CZstdDecFseTables; + + +typedef struct +{ + Byte *win; + SizeT cycSize; + /* + if (outBuf_fromCaller) : cycSize = outBufSize_fromCaller + else { + if ( isCyclicMode) : cycSize = cyclic_buffer_size = (winSize + extra_space) + if (!isCyclicMode) : cycSize = ContentSize, + (isCyclicMode == true) if (ContetSize >= winSize) or ContetSize is unknown + } + */ + SizeT winPos; + + CZstdDecOffset reps[3]; + + Byte ll_accuracy; + Byte of_accuracy; + Byte ml_accuracy; + // Byte seqTables_wereSet; + Byte litHuf_wasSet; + + Byte *literalsBase; + + size_t winSize; // from header + size_t totalOutCheck; // totalOutCheck <= winSize + + #ifdef Z7_ZSTD_DEC_USE_BASES_IN_OBJECT + SEQ_EXTRA_TABLES(m_) + #endif + // UInt64 _pad_Alignment; // is not required now + CZstdDecFseTables fse; + CZstdDecHufTable huf; +} +CZstdDec1; + +#define ZstdDec1_GET_BLOCK_SIZE_LIMIT(p) \ + ((p)->winSize < kBlockSizeMax ? (UInt32)(p)->winSize : kBlockSizeMax) + +#define SEQ_TABLES_WERE_NOT_SET_ml_accuracy 1 // accuracy=1 is not used by zstd +#define IS_SEQ_TABLES_WERE_SET(p) (((p)->ml_accuracy != SEQ_TABLES_WERE_NOT_SET_ml_accuracy)) +// #define IS_SEQ_TABLES_WERE_SET(p) ((p)->seqTables_wereSet) + + +static void ZstdDec1_Construct(CZstdDec1 *p) +{ + #ifdef Z7_ZSTD_DEC_PRINT_TABLE + Print_Predef(6, NUM_LL_SYMBOLS, SEQ_LL_PREDEF_DIST, k_PredefRecords_LL); + Print_Predef(5, NUM_OFFSET_SYMBOLS_PREDEF, SEQ_OFFSET_PREDEF_DIST, k_PredefRecords_OF); + Print_Predef(6, NUM_ML_SYMBOLS, SEQ_ML_PREDEF_DIST, k_PredefRecords_ML); + #endif + + p->win = NULL; + p->cycSize = 0; + p->literalsBase = NULL; + #ifdef Z7_ZSTD_DEC_USE_BASES_IN_OBJECT + FILL_LOC_BASES_ALL + #endif +} + + +static void ZstdDec1_Init(CZstdDec1 *p) +{ + p->reps[0] = 1; + p->reps[1] = 4; + p->reps[2] = 8; + // p->seqTables_wereSet = False; + p->ml_accuracy = SEQ_TABLES_WERE_NOT_SET_ml_accuracy; + p->litHuf_wasSet = False; + p->totalOutCheck = 0; +} + + + +#ifdef MY_CPU_LE_UNALIGN + #define Z7_ZSTD_DEC_USE_UNALIGNED_COPY +#endif + +#ifdef Z7_ZSTD_DEC_USE_UNALIGNED_COPY + + #define COPY_CHUNK_SIZE 16 + + #define COPY_CHUNK_4_2(dest, src) \ + { \ + ((UInt32 *)(void *)dest)[0] = ((const UInt32 *)(const void *)src)[0]; \ + ((UInt32 *)(void *)dest)[1] = ((const UInt32 *)(const void *)src)[1]; \ + src += 4 * 2; \ + dest += 4 * 2; \ + } + + /* sse2 doesn't help here in GCC and CLANG. + so we disabled sse2 here */ + /* + #if defined(MY_CPU_AMD64) + #define Z7_ZSTD_DEC_USE_SSE2 + #elif defined(MY_CPU_X86) + #if defined(_MSC_VER) && _MSC_VER >= 1300 && defined(_M_IX86_FP) && (_M_IX86_FP >= 2) \ + || defined(__SSE2__) \ + // || 1 == 1 // for debug only + #define Z7_ZSTD_DEC_USE_SSE2 + #endif + #endif + */ + + #if defined(MY_CPU_ARM64) + #define COPY_OFFSET_MIN 16 + #define COPY_CHUNK1(dest, src) \ + { \ + vst1q_u8((uint8_t *)(void *)dest, \ + vld1q_u8((const uint8_t *)(const void *)src)); \ + src += 16; \ + dest += 16; \ + } + + #define COPY_CHUNK(dest, src) \ + { \ + COPY_CHUNK1(dest, src) \ + if ((len -= COPY_CHUNK_SIZE) == 0) break; \ + COPY_CHUNK1(dest, src) \ + } + + #elif defined(Z7_ZSTD_DEC_USE_SSE2) + #include // sse2 + #define COPY_OFFSET_MIN 16 + + #define COPY_CHUNK1(dest, src) \ + { \ + _mm_storeu_si128((__m128i *)(void *)dest, \ + _mm_loadu_si128((const __m128i *)(const void *)src)); \ + src += 16; \ + dest += 16; \ + } + + #define COPY_CHUNK(dest, src) \ + { \ + COPY_CHUNK1(dest, src) \ + if ((len -= COPY_CHUNK_SIZE) == 0) break; \ + COPY_CHUNK1(dest, src) \ + } + + #elif defined(MY_CPU_64BIT) + #define COPY_OFFSET_MIN 8 + + #define COPY_CHUNK(dest, src) \ + { \ + ((UInt64 *)(void *)dest)[0] = ((const UInt64 *)(const void *)src)[0]; \ + ((UInt64 *)(void *)dest)[1] = ((const UInt64 *)(const void *)src)[1]; \ + src += 8 * 2; \ + dest += 8 * 2; \ + } + + #else + #define COPY_OFFSET_MIN 4 + + #define COPY_CHUNK(dest, src) \ + { \ + COPY_CHUNK_4_2(dest, src); \ + COPY_CHUNK_4_2(dest, src); \ + } + + #endif +#endif + + +#ifndef COPY_CHUNK_SIZE + #define COPY_OFFSET_MIN 4 + #define COPY_CHUNK_SIZE 8 + #define COPY_CHUNK_2(dest, src) \ + { \ + const Byte a0 = src[0]; \ + const Byte a1 = src[1]; \ + dest[0] = a0; \ + dest[1] = a1; \ + src += 2; \ + dest += 2; \ + } + #define COPY_CHUNK(dest, src) \ + { \ + COPY_CHUNK_2(dest, src) \ + COPY_CHUNK_2(dest, src) \ + COPY_CHUNK_2(dest, src) \ + COPY_CHUNK_2(dest, src) \ + } +#endif + + +#define COPY_PREPARE \ + len += (COPY_CHUNK_SIZE - 1); \ + len &= ~(size_t)(COPY_CHUNK_SIZE - 1); \ + { if (len > rem) \ + { len = rem; \ + rem &= (COPY_CHUNK_SIZE - 1); \ + if (rem) { \ + len -= rem; \ + Z7_PRAGMA_OPT_DISABLE_LOOP_UNROLL_VECTORIZE \ + do *dest++ = *src++; while (--rem); \ + if (len == 0) return; }}} + +#define COPY_CHUNKS \ +{ \ + Z7_PRAGMA_OPT_DISABLE_LOOP_UNROLL_VECTORIZE \ + do { COPY_CHUNK(dest, src) } \ + while (len -= COPY_CHUNK_SIZE); \ +} + +// (len != 0) +// (len <= rem) +static +Z7_FORCE_INLINE +// Z7_ATTRIB_NO_VECTOR +void CopyLiterals(Byte *dest, Byte const *src, size_t len, size_t rem) +{ + COPY_PREPARE + COPY_CHUNKS +} + + +/* we can define Z7_STD_DEC_USE_AFTER_CYC_BUF, if we want to use additional + space after cycSize that can be used to reduce the code in CopyMatch(): */ +// for debug: +// #define Z7_STD_DEC_USE_AFTER_CYC_BUF + +/* +CopyMatch() +if wrap (offset > winPos) +{ + then we have at least (COPY_CHUNK_SIZE) avail in (dest) before we will overwrite (src): + (cycSize >= offset + COPY_CHUNK_SIZE) + if defined(Z7_STD_DEC_USE_AFTER_CYC_BUF) + we are allowed to read win[cycSize + COPY_CHUNK_SIZE - 1], +} +(len != 0) +*/ +static +Z7_FORCE_INLINE +// Z7_ATTRIB_NO_VECTOR +void CopyMatch(size_t offset, size_t len, + Byte *win, size_t winPos, size_t rem, const size_t cycSize) +{ + Byte *dest = win + winPos; + const Byte *src; + // STAT_INC(g_NumCopy) + + if (offset > winPos) + { + size_t back = offset - winPos; + // src = win + cycSize - back; + // cycSize -= offset; + STAT_INC(g_NumOver) + src = dest + (cycSize - offset); + // (src >= dest) here + #ifdef Z7_STD_DEC_USE_AFTER_CYC_BUF + if (back < len) + { + #else + if (back < len + (COPY_CHUNK_SIZE - 1)) + { + if (back >= len) + { + Z7_PRAGMA_OPT_DISABLE_LOOP_UNROLL_VECTORIZE + do + *dest++ = *src++; + while (--len); + return; + } + #endif + // back < len + STAT_INC(g_NumOver2) + len -= back; + rem -= back; + Z7_PRAGMA_OPT_DISABLE_LOOP_UNROLL_VECTORIZE + do + *dest++ = *src++; + while (--back); + src = dest - offset; + // src = win; + // we go to MAIN-COPY + } + } + else + src = dest - offset; + + // len != 0 + // do *dest++ = *src++; while (--len); return; + + // --- MAIN COPY --- + // if (src >= dest), then ((size_t)(src - dest) >= COPY_CHUNK_SIZE) + // so we have at least COPY_CHUNK_SIZE space before overlap for writing. + COPY_PREPARE + + /* now (len == COPY_CHUNK_SIZE * x) + so we can unroll for aligned copy */ + { + // const unsigned b0 = src[0]; + // (COPY_OFFSET_MIN >= 4) + + if (offset >= COPY_OFFSET_MIN) + { + COPY_CHUNKS + // return; + } + else + #if (COPY_OFFSET_MIN > 4) + #if COPY_CHUNK_SIZE < 8 + #error Stop_Compiling_Bad_COPY_CHUNK_SIZE + #endif + if (offset >= 4) + { + Z7_PRAGMA_OPT_DISABLE_LOOP_UNROLL_VECTORIZE + do + { + COPY_CHUNK_4_2(dest, src) + #if COPY_CHUNK_SIZE != 16 + if (len == 8) break; + #endif + COPY_CHUNK_4_2(dest, src) + } + while (len -= 16); + // return; + } + else + #endif + { + // (offset < 4) + const unsigned b0 = src[0]; + if (offset < 2) + { + #if defined(Z7_ZSTD_DEC_USE_UNALIGNED_COPY) && (COPY_CHUNK_SIZE == 16) + #if defined(MY_CPU_64BIT) + { + const UInt64 v64 = (UInt64)b0 * 0x0101010101010101; + Z7_PRAGMA_OPT_DISABLE_LOOP_UNROLL_VECTORIZE + do + { + ((UInt64 *)(void *)dest)[0] = v64; + ((UInt64 *)(void *)dest)[1] = v64; + dest += 16; + } + while (len -= 16); + } + #else + { + UInt32 v = b0; + v |= v << 8; + v |= v << 16; + do + { + ((UInt32 *)(void *)dest)[0] = v; + ((UInt32 *)(void *)dest)[1] = v; + dest += 8; + ((UInt32 *)(void *)dest)[0] = v; + ((UInt32 *)(void *)dest)[1] = v; + dest += 8; + } + while (len -= 16); + } + #endif + #else + do + { + dest[0] = (Byte)b0; + dest[1] = (Byte)b0; + dest += 2; + dest[0] = (Byte)b0; + dest[1] = (Byte)b0; + dest += 2; + } + while (len -= 4); + #endif + } + else if (offset == 2) + { + const Byte b1 = src[1]; + { + do + { + dest[0] = (Byte)b0; + dest[1] = b1; + dest += 2; + } + while (len -= 2); + } + } + else // (offset == 3) + { + const Byte *lim = dest + len - 2; + const Byte b1 = src[1]; + const Byte b2 = src[2]; + do + { + dest[0] = (Byte)b0; + dest[1] = b1; + dest[2] = b2; + dest += 3; + } + while (dest < lim); + lim++; // points to last byte that must be written + if (dest <= lim) + { + *dest = (Byte)b0; + if (dest != lim) + dest[1] = b1; + } + } + } + } +} + + + +#define UPDATE_TOTAL_OUT(p, size) \ +{ \ + size_t _toc = (p)->totalOutCheck + (size); \ + const size_t _ws = (p)->winSize; \ + if (_toc >= _ws) _toc = _ws; \ + (p)->totalOutCheck = _toc; \ +} + + +#if defined(MY_CPU_64BIT) && defined(MY_CPU_LE_UNALIGN) +// we can disable it for debug: +#define Z7_ZSTD_DEC_USE_64BIT_LOADS +#endif +// #define Z7_ZSTD_DEC_USE_64BIT_LOADS // for debug : slow in 32-bit + +// SEQ_SRC_OFFSET: how many bytes (src) (seqSrc) was moved back from original value. +// we need (SEQ_SRC_OFFSET != 0) for optimized memory access +#ifdef Z7_ZSTD_DEC_USE_64BIT_LOADS + #define SEQ_SRC_OFFSET 7 +#else + #define SEQ_SRC_OFFSET 3 +#endif +#define SRC_PLUS_FOR_4BYTES(bitOffset) (SEQ_SRC_OFFSET - 3) + ((CBitCtr_signed)(bitOffset) >> 3) +#define BIT_OFFSET_7BITS(bitOffset) ((unsigned)(bitOffset) & 7) +/* + if (BIT_OFFSET_DELTA_BITS == 0) : bitOffset == number_of_unprocessed_bits + if (BIT_OFFSET_DELTA_BITS == 1) : bitOffset == number_of_unprocessed_bits - 1 + and we can read 1 bit more in that mode : (8 * n + 1). +*/ +// #define BIT_OFFSET_DELTA_BITS 0 +#define BIT_OFFSET_DELTA_BITS 1 +#if BIT_OFFSET_DELTA_BITS == 1 + #define GET_SHIFT_FROM_BOFFS7(boff7) (7 ^ (boff7)) +#else + #define GET_SHIFT_FROM_BOFFS7(boff7) (8 - BIT_OFFSET_DELTA_BITS - (boff7)) +#endif + +#define UPDATE_BIT_OFFSET(bitOffset, numBits) \ + (bitOffset) -= (CBitCtr)(numBits); + +#define GET_SHIFT(bitOffset) GET_SHIFT_FROM_BOFFS7(BIT_OFFSET_7BITS(bitOffset)) + + +#if defined(Z7_ZSTD_DEC_USE_64BIT_LOADS) + #if (NUM_OFFSET_SYMBOLS_MAX - BIT_OFFSET_DELTA_BITS < 32) + /* if (NUM_OFFSET_SYMBOLS_MAX == 32 && BIT_OFFSET_DELTA_BITS == 1), + we have depth 31 + 9 + 9 + 8 = 57 bits that can b read with single read. */ + #define Z7_ZSTD_DEC_USE_64BIT_PRELOAD_OF + #endif + #ifndef Z7_ZSTD_DEC_USE_64BIT_PRELOAD_OF + #if (BIT_OFFSET_DELTA_BITS == 1) + /* if (winLimit - winPos <= (kBlockSizeMax = (1 << 17))) + { + the case (16 bits literal extra + 16 match extra) is not possible + in correct stream. So error will be detected for (16 + 16) case. + And longest correct sequence after offset reading is (31 + 9 + 9 + 8 = 57 bits). + So we can use just one 64-bit load here in that case. + } + */ + #define Z7_ZSTD_DEC_USE_64BIT_PRELOAD_ML + #endif + #endif +#endif + + +#if !defined(Z7_ZSTD_DEC_USE_64BIT_LOADS) || \ + (!defined(Z7_ZSTD_DEC_USE_64BIT_PRELOAD_OF) && \ + !defined(Z7_ZSTD_DEC_USE_64BIT_PRELOAD_ML)) +// in : (0 < bits <= (24 or 25)): +#define STREAM_READ_BITS(dest, bits) \ +{ \ + GET32(dest, src + SRC_PLUS_FOR_4BYTES(bitOffset)) \ + dest <<= GET_SHIFT(bitOffset); \ + UPDATE_BIT_OFFSET(bitOffset, bits) \ + dest >>= 32 - bits; \ +} +#endif + + +#define FSE_Peek_1(table, state) table[state] + +#define STATE_VAR(name) state_ ## name + +// in : (0 <= accuracy <= (24 or 25)) +#define FSE_INIT_STATE(name, cond) \ +{ \ + UInt32 r; \ + const unsigned bits = p->name ## _accuracy; \ + GET32(r, src + SRC_PLUS_FOR_4BYTES(bitOffset)) \ + r <<= GET_SHIFT(bitOffset); \ + r >>= 1; \ + r >>= 31 ^ bits; \ + UPDATE_BIT_OFFSET(bitOffset, bits) \ + cond \ + STATE_VAR(name) = FSE_Peek_1(FSE_TABLE(name), r); \ + /* STATE_VAR(name) = dest << 16; */ \ +} + + +#define FSE_Peek_Plus(name, r) \ + STATE_VAR(name) = FSE_Peek_1(FSE_TABLE(name), \ + GET_FSE_REC_STATE(STATE_VAR(name)) + r); + +#define LZ_LOOP_ERROR_EXIT { return SZ_ERROR_DATA; } + +#define BO_OVERFLOW_CHECK \ + { if ((CBitCtr_signed)bitOffset < 0) LZ_LOOP_ERROR_EXIT } + + +#ifdef Z7_ZSTD_DEC_USE_64BIT_LOADS + +#define GET64(dest, p) { const Byte *ptr = p; dest = GetUi64(ptr); } + +#define FSE_PRELOAD \ +{ \ + GET64(v, src - 4 + SRC_PLUS_FOR_4BYTES(bitOffset)) \ + v <<= GET_SHIFT(bitOffset); \ +} + +#define FSE_UPDATE_STATE_2(name, cond) \ +{ \ + const unsigned bits = GET_FSE_REC_LEN(STATE_VAR(name)); \ + UInt64 r = v; \ + v <<= bits; \ + r >>= 1; \ + UPDATE_BIT_OFFSET(bitOffset, bits) \ + cond \ + r >>= 63 ^ bits; \ + FSE_Peek_Plus(name, r); \ +} + +#define FSE_UPDATE_STATES \ + FSE_UPDATE_STATE_2 (ll, {} ) \ + FSE_UPDATE_STATE_2 (ml, {} ) \ + FSE_UPDATE_STATE_2 (of, BO_OVERFLOW_CHECK) \ + +#else // Z7_ZSTD_DEC_USE_64BIT_LOADS + +// it supports 8 bits accuracy for any code +// it supports 9 bits accuracy, if (BIT_OFFSET_DELTA_BITS == 1) +#define FSE_UPDATE_STATE_0(name, cond) \ +{ \ + UInt32 r; \ + const unsigned bits = GET_FSE_REC_LEN(STATE_VAR(name)); \ + GET16(r, src + 2 + SRC_PLUS_FOR_4BYTES(bitOffset)) \ + r >>= (bitOffset & 7); \ + r &= (1 << (8 + BIT_OFFSET_DELTA_BITS)) - 1; \ + UPDATE_BIT_OFFSET(bitOffset, bits) \ + cond \ + r >>= (8 + BIT_OFFSET_DELTA_BITS) - bits; \ + FSE_Peek_Plus(name, r); \ +} + +// for debug (slow): +// #define Z7_ZSTD_DEC_USE_FSE_FUSION_FORCE +#if BIT_OFFSET_DELTA_BITS == 0 || defined(Z7_ZSTD_DEC_USE_FSE_FUSION_FORCE) + #define Z7_ZSTD_DEC_USE_FSE_FUSION +#endif + +#ifdef Z7_ZSTD_DEC_USE_FSE_FUSION +#define FSE_UPDATE_STATE_1(name) \ +{ UInt32 rest2; \ +{ \ + UInt32 r; \ + unsigned bits; \ + GET32(r, src + SRC_PLUS_FOR_4BYTES(bitOffset)) \ + bits = GET_FSE_REC_LEN(STATE_VAR(name)); \ + r <<= GET_SHIFT(bitOffset); \ + rest2 = r << bits; \ + r >>= 1; \ + UPDATE_BIT_OFFSET(bitOffset, bits) \ + r >>= 31 ^ bits; \ + FSE_Peek_Plus(name, r); \ +} + +#define FSE_UPDATE_STATE_3(name) \ +{ \ + const unsigned bits = GET_FSE_REC_LEN(STATE_VAR(name)); \ + rest2 >>= 1; \ + UPDATE_BIT_OFFSET(bitOffset, bits) \ + rest2 >>= 31 ^ bits; \ + FSE_Peek_Plus(name, rest2); \ +}} + +#define FSE_UPDATE_STATES \ + FSE_UPDATE_STATE_1 (ll) \ + FSE_UPDATE_STATE_3 (ml) \ + FSE_UPDATE_STATE_0 (of, BO_OVERFLOW_CHECK) \ + +#else // Z7_ZSTD_DEC_USE_64BIT_LOADS + +#define FSE_UPDATE_STATES \ + FSE_UPDATE_STATE_0 (ll, {} ) \ + FSE_UPDATE_STATE_0 (ml, {} ) \ + FSE_UPDATE_STATE_0 (of, BO_OVERFLOW_CHECK) \ + +#endif // Z7_ZSTD_DEC_USE_FSE_FUSION +#endif // Z7_ZSTD_DEC_USE_64BIT_LOADS + + + +typedef struct +{ + UInt32 numSeqs; + UInt32 literalsLen; + const Byte *literals; +} +CZstdDec1_Vars; + + +// if (BIT_OFFSET_DELTA_BITS != 0), we need (BIT_OFFSET_DELTA_BYTES > 0) +#define BIT_OFFSET_DELTA_BYTES BIT_OFFSET_DELTA_BITS + +/* if (NUM_OFFSET_SYMBOLS_MAX == 32) + max_seq_bit_length = (31) + 16 + 16 + 9 + 8 + 9 = 89 bits + if defined(Z7_ZSTD_DEC_USE_64BIT_PRELOAD_OF) we have longest backward + lookahead offset, and we read UInt64 after literal_len reading. + if (BIT_OFFSET_DELTA_BITS == 1 && NUM_OFFSET_SYMBOLS_MAX == 32) + MAX_BACKWARD_DEPTH = 16 bytes +*/ +#define MAX_BACKWARD_DEPTH \ + ((NUM_OFFSET_SYMBOLS_MAX - 1 + 16 + 16 + 7) / 8 + 7 + BIT_OFFSET_DELTA_BYTES) + +/* srcLen != 0 + src == real_data_ptr - SEQ_SRC_OFFSET - BIT_OFFSET_DELTA_BYTES + if defined(Z7_ZSTD_DEC_USE_64BIT_PRELOAD_ML) then + (winLimit - p->winPos <= (1 << 17)) is required +*/ +static +Z7_NO_INLINE +// Z7_ATTRIB_NO_VECTOR +SRes Decompress_Sequences(CZstdDec1 * const p, + const Byte *src, const size_t srcLen, + const size_t winLimit, + const CZstdDec1_Vars * const vars) +{ +#ifdef Z7_ZSTD_DEC_USE_BASES_LOCAL + SEQ_EXTRA_TABLES(a_) +#endif + + // for debug: + // #define Z7_ZSTD_DEC_USE_LOCAL_FSE_TABLES +#ifdef Z7_ZSTD_DEC_USE_LOCAL_FSE_TABLES + #define FSE_TABLE(n) fse. n + const CZstdDecFseTables fse = p->fse; + /* + CZstdDecFseTables fse; + #define COPY_FSE_TABLE(n) \ + memcpy(fse. n, p->fse. n, (size_t)4 << p-> n ## _accuracy); + COPY_FSE_TABLE(of) + COPY_FSE_TABLE(ll) + COPY_FSE_TABLE(ml) + */ +#else + #define FSE_TABLE(n) (p->fse. n) +#endif + +#ifdef Z7_ZSTD_DEC_USE_BASES_LOCAL + FILL_LOC_BASES_ALL +#endif + + { + unsigned numSeqs = vars->numSeqs; + const Byte *literals = vars->literals; + ptrdiff_t literalsLen = (ptrdiff_t)vars->literalsLen; + Byte * const win = p->win; + size_t winPos = p->winPos; + const size_t cycSize = p->cycSize; + size_t totalOutCheck = p->totalOutCheck; + const size_t winSize = p->winSize; + size_t reps_0 = p->reps[0]; + size_t reps_1 = p->reps[1]; + size_t reps_2 = p->reps[2]; + UInt32 STATE_VAR(ll), STATE_VAR(of), STATE_VAR(ml); + CBitCtr bitOffset; + + SET_bitOffset_TO_PAD (bitOffset, src + SEQ_SRC_OFFSET, srcLen + BIT_OFFSET_DELTA_BYTES) + + bitOffset -= BIT_OFFSET_DELTA_BITS; + + FSE_INIT_STATE(ll, {} ) + FSE_INIT_STATE(of, {} ) + FSE_INIT_STATE(ml, BO_OVERFLOW_CHECK) + + for (;;) + { + size_t matchLen; + #ifdef Z7_ZSTD_DEC_USE_64BIT_LOADS + UInt64 v; + #endif + + #ifdef Z7_ZSTD_DEC_USE_64BIT_PRELOAD_OF + FSE_PRELOAD + #endif + + // if (of_code == 0) + if ((Byte)STATE_VAR(of) == 0) + { + if (GET_FSE_REC_SYM(STATE_VAR(ll)) == 0) + { + const size_t offset = reps_1; + reps_1 = reps_0; + reps_0 = offset; + STAT_INC(g_Num_Rep1) + } + STAT_UPDATE(else g_Num_Rep0++;) + } + else + { + const unsigned of_code = (Byte)STATE_VAR(of); + + #ifdef Z7_ZSTD_DEC_USE_64BIT_LOADS + #if !defined(Z7_ZSTD_DEC_USE_64BIT_PRELOAD_OF) + FSE_PRELOAD + #endif + #else + UInt32 v; + { + const Byte *src4 = src + SRC_PLUS_FOR_4BYTES(bitOffset); + const unsigned skip = GET_SHIFT(bitOffset); + GET32(v, src4) + v <<= skip; + v |= (UInt32)src4[-1] >> (8 - skip); + } + #endif + + UPDATE_BIT_OFFSET(bitOffset, of_code) + + if (of_code == 1) + { + // read 1 bit + #if defined(Z7_MSC_VER_ORIGINAL) || defined(MY_CPU_X86_OR_AMD64) + #ifdef Z7_ZSTD_DEC_USE_64BIT_LOADS + #define CHECK_HIGH_BIT_64(a) ((Int64)(UInt64)(a) < 0) + #else + #define CHECK_HIGH_BIT_32(a) ((Int32)(UInt32)(a) < 0) + #endif + #else + #ifdef Z7_ZSTD_DEC_USE_64BIT_LOADS + #define CHECK_HIGH_BIT_64(a) ((UInt64)(a) & ((UInt64)1 << 63)) + #else + #define CHECK_HIGH_BIT_32(a) ((UInt32)(a) & ((UInt32)1 << 31)) + #endif + #endif + + if + #ifdef Z7_ZSTD_DEC_USE_64BIT_LOADS + CHECK_HIGH_BIT_64 (((UInt64)GET_FSE_REC_SYM(STATE_VAR(ll)) - 1) ^ v) + #else + CHECK_HIGH_BIT_32 (((UInt32)GET_FSE_REC_SYM(STATE_VAR(ll)) - 1) ^ v) + #endif + { + v <<= 1; + { + const size_t offset = reps_2; + reps_2 = reps_1; + reps_1 = reps_0; + reps_0 = offset; + STAT_INC(g_Num_Rep2) + } + } + else + { + if (GET_FSE_REC_SYM(STATE_VAR(ll)) == 0) + { + // litLen == 0 && bit == 1 + STAT_INC(g_Num_Rep3) + v <<= 1; + reps_2 = reps_1; + reps_1 = reps_0; + if (--reps_0 == 0) + { + // LZ_LOOP_ERROR_EXIT + // original-zstd decoder : input is corrupted; force offset to 1 + // reps_0 = 1; + reps_0++; + } + } + else + { + // litLen != 0 && bit == 0 + v <<= 1; + { + const size_t offset = reps_1; + reps_1 = reps_0; + reps_0 = offset; + STAT_INC(g_Num_Rep1) + } + } + } + } + else + { + // (2 <= of_code) + // if (of_code >= 32) LZ_LOOP_ERROR_EXIT // optional check + // we don't allow (of_code >= 32) cases in another code + reps_2 = reps_1; + reps_1 = reps_0; + reps_0 = ((size_t)1 << of_code) - 3 + (size_t) + #ifdef Z7_ZSTD_DEC_USE_64BIT_LOADS + (v >> (64 - of_code)); + v <<= of_code; + #else + (v >> (32 - of_code)); + #endif + } + } + + #ifdef Z7_ZSTD_DEC_USE_64BIT_PRELOAD_ML + FSE_PRELOAD + #endif + + matchLen = (size_t)GET_FSE_REC_SYM(STATE_VAR(ml)) + #ifndef Z7_ZSTD_DEC_USE_ML_PLUS3 + + MATCH_LEN_MIN + #endif + ; + { + { + if (matchLen >= 32 + MATCH_LEN_MIN) // if (state_ml & 0x20) + { + const unsigned extra = BASES_TABLE(SEQ_ML_EXTRA) [(size_t)matchLen - MATCH_LEN_MIN]; + matchLen = BASES_TABLE(SEQ_ML_BASES) [(size_t)matchLen - MATCH_LEN_MIN]; + #if defined(Z7_ZSTD_DEC_USE_64BIT_LOADS) && \ + (defined(Z7_ZSTD_DEC_USE_64BIT_PRELOAD_ML) || \ + defined(Z7_ZSTD_DEC_USE_64BIT_PRELOAD_OF)) + { + UPDATE_BIT_OFFSET(bitOffset, extra) + matchLen += (size_t)(v >> (64 - extra)); + #if defined(Z7_ZSTD_DEC_USE_64BIT_PRELOAD_OF) + FSE_PRELOAD + #else + v <<= extra; + #endif + } + #else + { + UInt32 v32; + STREAM_READ_BITS(v32, extra) + matchLen += v32; + } + #endif + STAT_INC(g_Num_Match) + } + } + } + + #if defined(Z7_ZSTD_DEC_USE_64BIT_LOADS) && \ + !defined(Z7_ZSTD_DEC_USE_64BIT_PRELOAD_OF) && \ + !defined(Z7_ZSTD_DEC_USE_64BIT_PRELOAD_ML) + FSE_PRELOAD + #endif + + { + size_t litLen = GET_FSE_REC_SYM(STATE_VAR(ll)); + if (litLen) + { + // if (STATE_VAR(ll) & 0x70) + if (litLen >= 16) + { + const unsigned extra = BASES_TABLE(SEQ_LL_EXTRA) [litLen]; + litLen = BASES_TABLE(SEQ_LL_BASES) [litLen]; + #ifdef Z7_ZSTD_DEC_USE_64BIT_LOADS + { + UPDATE_BIT_OFFSET(bitOffset, extra) + litLen += (size_t)(v >> (64 - extra)); + #if defined(Z7_ZSTD_DEC_USE_64BIT_PRELOAD_OF) + FSE_PRELOAD + #else + v <<= extra; + #endif + } + #else + { + UInt32 v32; + STREAM_READ_BITS(v32, extra) + litLen += v32; + } + #endif + STAT_INC(g_Num_LitsBig) + } + + if ((literalsLen -= (ptrdiff_t)litLen) < 0) + LZ_LOOP_ERROR_EXIT + totalOutCheck += litLen; + { + const size_t rem = winLimit - winPos; + if (litLen > rem) + LZ_LOOP_ERROR_EXIT + { + const Byte *literals_temp = literals; + Byte *d = win + winPos; + literals += litLen; + winPos += litLen; + CopyLiterals(d, literals_temp, litLen, rem); + } + } + } + STAT_UPDATE(else g_Num_Lit0++;) + } + + #define COPY_MATCH \ + { if (reps_0 > winSize || reps_0 > totalOutCheck) LZ_LOOP_ERROR_EXIT \ + totalOutCheck += matchLen; \ + { const size_t rem = winLimit - winPos; \ + if (matchLen > rem) LZ_LOOP_ERROR_EXIT \ + { const size_t winPos_temp = winPos; \ + winPos += matchLen; \ + CopyMatch(reps_0, matchLen, win, winPos_temp, rem, cycSize); }}} + + if (--numSeqs == 0) + { + COPY_MATCH + break; + } + FSE_UPDATE_STATES + COPY_MATCH + } // for + + if ((CBitCtr_signed)bitOffset != BIT_OFFSET_DELTA_BYTES * 8 - BIT_OFFSET_DELTA_BITS) + return SZ_ERROR_DATA; + + if (literalsLen) + { + const size_t rem = winLimit - winPos; + if ((size_t)literalsLen > rem) + return SZ_ERROR_DATA; + { + Byte *d = win + winPos; + winPos += (size_t)literalsLen; + totalOutCheck += (size_t)literalsLen; + CopyLiterals + // memcpy + (d, literals, (size_t)literalsLen, rem); + } + } + if (totalOutCheck >= winSize) + totalOutCheck = winSize; + p->totalOutCheck = totalOutCheck; + p->winPos = winPos; + p->reps[0] = (CZstdDecOffset)reps_0; + p->reps[1] = (CZstdDecOffset)reps_1; + p->reps[2] = (CZstdDecOffset)reps_2; + } + return SZ_OK; +} + + +// for debug: define to check that ZstdDec1_NeedTempBufferForInput() works correctly: +// #define Z7_ZSTD_DEC_USE_CHECK_OF_NEED_TEMP // define it for debug only +#ifdef Z7_ZSTD_DEC_USE_CHECK_OF_NEED_TEMP +static unsigned g_numSeqs; +#endif + + +#define k_LitBlockType_Flag_RLE_or_Treeless 1 +#define k_LitBlockType_Flag_Compressed 2 + +// outLimit : is strong limit +// outLimit <= ZstdDec1_GET_BLOCK_SIZE_LIMIT(p) +// inSize != 0 +static +Z7_NO_INLINE +SRes ZstdDec1_DecodeBlock(CZstdDec1 *p, + const Byte *src, SizeT inSize, SizeT afterAvail, + const size_t outLimit) +{ + CZstdDec1_Vars vars; + vars.literals = p->literalsBase; + { + const unsigned b0 = *src++; + UInt32 numLits, compressedSize; + const Byte *litStream; + Byte *literalsDest; + inSize--; + + if ((b0 & k_LitBlockType_Flag_Compressed) == 0) + { + // we need at least one additional byte for (numSeqs). + // so we check for that additional byte in conditions. + numLits = b0 >> 3; + if (b0 & 4) + { + UInt32 v; + if (inSize < 1 + 1) // we need at least 1 byte here and 1 byte for (numSeqs). + return SZ_ERROR_DATA; + numLits >>= 1; + v = GetUi16(src); + src += 2; + inSize -= 2; + if ((b0 & 8) == 0) + { + src--; + inSize++; + v = (Byte)v; + } + numLits += v << 4; + } + compressedSize = 1; + if ((b0 & k_LitBlockType_Flag_RLE_or_Treeless) == 0) + compressedSize = numLits; + } + else if (inSize < 4) + return SZ_ERROR_DATA; + else + { + const unsigned mode4Streams = b0 & 0xc; + const unsigned numBytes = (3 * mode4Streams + 32) >> 4; + const unsigned numBits = 4 * numBytes - 2; + const UInt32 mask = ((UInt32)16 << numBits) - 1; + compressedSize = GetUi32(src); + numLits = (( + #ifdef MY_CPU_LE_UNALIGN + GetUi32(src - 1) + #else + ((compressedSize << 8) + b0) + #endif + ) >> 4) & mask; + src += numBytes; + inSize -= numBytes; + compressedSize >>= numBits; + compressedSize &= mask; + /* + if (numLits != 0) printf("inSize = %7u num_lits=%7u compressed=%7u ratio = %u ratio2 = %u\n", + i1, numLits, (unsigned)compressedSize * 1, (unsigned)compressedSize * 100 / numLits, + (unsigned)numLits * 100 / (unsigned)inSize); + } + */ + if (compressedSize == 0) + return SZ_ERROR_DATA; // (compressedSize == 0) is not allowed + } + + STAT_UPDATE(g_Num_Lits += numLits;) + + vars.literalsLen = numLits; + + if (compressedSize >= inSize) + return SZ_ERROR_DATA; + litStream = src; + src += compressedSize; + inSize -= compressedSize; + // inSize != 0 + { + UInt32 numSeqs = *src++; + inSize--; + if (numSeqs > 127) + { + UInt32 b1; + if (inSize == 0) + return SZ_ERROR_DATA; + numSeqs -= 128; + b1 = *src++; + inSize--; + if (numSeqs == 127) + { + if (inSize == 0) + return SZ_ERROR_DATA; + numSeqs = (UInt32)(*src++) + 127; + inSize--; + } + numSeqs = (numSeqs << 8) + b1; + } + if (numSeqs * MATCH_LEN_MIN + numLits > outLimit) + return SZ_ERROR_DATA; + vars.numSeqs = numSeqs; + + STAT_UPDATE(g_NumSeqs_total += numSeqs;) + /* + #ifdef SHOW_STAT + printf("\n %5u : %8u, %8u : %5u", (int)g_Num_Blocks_Compressed, (int)numSeqs, (int)g_NumSeqs_total, + (int)g_NumSeqs_total / g_Num_Blocks_Compressed); + #endif + // printf("\nnumSeqs2 = %d", numSeqs); + */ + #ifdef Z7_ZSTD_DEC_USE_CHECK_OF_NEED_TEMP + if (numSeqs != g_numSeqs) return SZ_ERROR_DATA; // for debug + #endif + if (numSeqs == 0) + { + if (inSize != 0) + return SZ_ERROR_DATA; + literalsDest = p->win + p->winPos; + } + else + literalsDest = p->literalsBase; + } + + if ((b0 & k_LitBlockType_Flag_Compressed) == 0) + { + if (b0 & k_LitBlockType_Flag_RLE_or_Treeless) + { + memset(literalsDest, litStream[0], numLits); + if (vars.numSeqs) + { + // literalsDest == p->literalsBase == vars.literals + #if COPY_CHUNK_SIZE > 1 + memset(p->literalsBase + numLits, 0, COPY_CHUNK_SIZE); + #endif + } + } + else + { + // unsigned y; + // for (y = 0; y < 10000; y++) + memcpy(literalsDest, litStream, numLits); + if (vars.numSeqs) + { + /* we need up to (15 == COPY_CHUNK_SIZE - 1) space for optimized CopyLiterals(). + If we have additional space in input stream after literals stream, + we use direct copy of rar literals in input stream */ + if ((size_t)(src + inSize - litStream) - numLits + afterAvail >= (COPY_CHUNK_SIZE - 1)) + vars.literals = litStream; + else + { + // literalsDest == p->literalsBase == vars.literals + #if COPY_CHUNK_SIZE > 1 + /* CopyLiterals(): + 1) we don't want reading non-initialized data + 2) we will copy only zero byte after literals buffer */ + memset(p->literalsBase + numLits, 0, COPY_CHUNK_SIZE); + #endif + } + } + } + } + else + { + CInBufPair hufStream; + hufStream.ptr = litStream; + hufStream.len = compressedSize; + + if ((b0 & k_LitBlockType_Flag_RLE_or_Treeless) == 0) + { + // unsigned y = 100; CInBufPair hs2 = hufStream; do { hufStream = hs2; + RINOK(Huf_DecodeTable(&p->huf, &hufStream)) + p->litHuf_wasSet = True; + // } while (--y); + } + else if (!p->litHuf_wasSet) + return SZ_ERROR_DATA; + + { + // int yyy; for (yyy = 0; yyy < 34; yyy++) { + SRes sres; + if ((b0 & 0xc) == 0) // mode4Streams + sres = Huf_Decompress_1stream((const Byte *)(const void *)p->huf.table64, + hufStream.ptr - HUF_SRC_OFFSET, hufStream.len, literalsDest, numLits); + else + { + // 6 bytes for the jump table + 4x1 bytes of end-padding Bytes) + if (hufStream.len < 6 + 4) + return SZ_ERROR_DATA; + // the condition from original-zstd decoder: + #define Z7_ZSTD_MIN_LITERALS_FOR_4_STREAMS 6 + if (numLits < Z7_ZSTD_MIN_LITERALS_FOR_4_STREAMS) + return SZ_ERROR_DATA; + sres = Huf_Decompress_4stream((const Byte *)(const void *)p->huf.table64, + hufStream.ptr + (6 - HUF_SRC_OFFSET), hufStream.len, literalsDest, numLits); + } + RINOK(sres) + // } + } + } + + if (vars.numSeqs == 0) + { + p->winPos += numLits; + UPDATE_TOTAL_OUT(p, numLits) + return SZ_OK; + } + } + { + CInBufPair in; + unsigned mode; + unsigned seqMode; + + in.ptr = src; + in.len = inSize; + if (in.len == 0) + return SZ_ERROR_DATA; + in.len--; + mode = *in.ptr++; + if (mode & 3) // Reserved bits + return SZ_ERROR_DATA; + + seqMode = (mode >> 6); + if (seqMode == k_SeqMode_Repeat) + { if (!IS_SEQ_TABLES_WERE_SET(p)) return SZ_ERROR_DATA; } + else RINOK(FSE_Decode_SeqTable( + p->fse.ll, + &in, + 6, // predefAccuracy + &p->ll_accuracy, + NUM_LL_SYMBOLS, + k_PredefRecords_LL, + seqMode)) + + seqMode = (mode >> 4) & 3; + if (seqMode == k_SeqMode_Repeat) + { if (!IS_SEQ_TABLES_WERE_SET(p)) return SZ_ERROR_DATA; } + else RINOK(FSE_Decode_SeqTable( + p->fse.of, + &in, + 5, // predefAccuracy + &p->of_accuracy, + NUM_OFFSET_SYMBOLS_MAX, + k_PredefRecords_OF, + seqMode)) + + seqMode = (mode >> 2) & 3; + if (seqMode == k_SeqMode_Repeat) + { if (!IS_SEQ_TABLES_WERE_SET(p)) return SZ_ERROR_DATA; } + else + { + RINOK(FSE_Decode_SeqTable( + p->fse.ml, + &in, + 6, // predefAccuracy + &p->ml_accuracy, + NUM_ML_SYMBOLS, + k_PredefRecords_ML, + seqMode)) + /* + #if defined(Z7_ZSTD_DEC_USE_ML_PLUS3) + // { unsigned y = 1 << 10; do + { + const unsigned accuracy = p->ml_accuracy; + if (accuracy == 0) + p->fse.ml[0] += 3; + else + #ifdef MY_CPU_64BIT + { + // alignemt (UInt64 _pad_Alignment) in fse.ml is required for that code + UInt64 *table = (UInt64 *)(void *)p->fse.ml; + const UInt64 *end = (const UInt64 *)(const void *) + ((const Byte *)(const void *)table + ((size_t)sizeof(CFseRecord) << accuracy)); + do + { + table[0] += ((UInt64)MATCH_LEN_MIN << 32) + MATCH_LEN_MIN; + table[1] += ((UInt64)MATCH_LEN_MIN << 32) + MATCH_LEN_MIN; + table += 2; + } + while (table != end); + } + #else + { + UInt32 *table = p->fse.ml; + const UInt32 *end = (const UInt32 *)(const void *) + ((const Byte *)(const void *)table + ((size_t)sizeof(CFseRecord) << accuracy)); + do + { + table[0] += MATCH_LEN_MIN; + table[1] += MATCH_LEN_MIN; + table += 2; + table[0] += MATCH_LEN_MIN; + table[1] += MATCH_LEN_MIN; + table += 2; + } + while (table != end); + } + #endif + } + // while (--y); } + #endif + */ + } + + // p->seqTables_wereSet = True; + if (in.len == 0) + return SZ_ERROR_DATA; + return Decompress_Sequences(p, + in.ptr - SEQ_SRC_OFFSET - BIT_OFFSET_DELTA_BYTES, in.len, + p->winPos + outLimit, &vars); + } +} + + + + +// inSize != 0 +// it must do similar to ZstdDec1_DecodeBlock() +static size_t ZstdDec1_NeedTempBufferForInput( + const SizeT beforeSize, const Byte * const src, const SizeT inSize) +{ + unsigned b0; + UInt32 pos; + + #ifdef Z7_ZSTD_DEC_USE_CHECK_OF_NEED_TEMP + g_numSeqs = 1 << 24; + #else + // we have at least 3 bytes before seq data: litBlockType, numSeqs, seqMode + #define MIN_BLOCK_LZ_HEADERS_SIZE 3 + if (beforeSize >= MAX_BACKWARD_DEPTH - MIN_BLOCK_LZ_HEADERS_SIZE) + return 0; + #endif + + b0 = src[0]; + + if ((b0 & k_LitBlockType_Flag_Compressed) == 0) + { + UInt32 numLits = b0 >> 3; + pos = 1; + if (b0 & 4) + { + UInt32 v; + if (inSize < 3) + return 0; + numLits >>= 1; + v = GetUi16(src + 1); + pos = 3; + if ((b0 & 8) == 0) + { + pos = 2; + v = (Byte)v; + } + numLits += v << 4; + } + if (b0 & k_LitBlockType_Flag_RLE_or_Treeless) + numLits = 1; + pos += numLits; + } + else if (inSize < 5) + return 0; + else + { + const unsigned mode4Streams = b0 & 0xc; + const unsigned numBytes = (3 * mode4Streams + 48) >> 4; + const unsigned numBits = 4 * numBytes - 6; + UInt32 cs = GetUi32(src + 1); + cs >>= numBits; + cs &= ((UInt32)16 << numBits) - 1; + if (cs == 0) + return 0; + pos = numBytes + cs; + } + + if (pos >= inSize) + return 0; + { + UInt32 numSeqs = src[pos++]; + if (numSeqs > 127) + { + UInt32 b1; + if (pos >= inSize) + return 0; + numSeqs -= 128; + b1 = src[pos++]; + if (numSeqs == 127) + { + if (pos >= inSize) + return 0; + numSeqs = (UInt32)(src[pos++]) + 127; + } + numSeqs = (numSeqs << 8) + b1; + } + #ifdef Z7_ZSTD_DEC_USE_CHECK_OF_NEED_TEMP + g_numSeqs = numSeqs; // for debug + #endif + if (numSeqs == 0) + return 0; + } + /* + if (pos >= inSize) + return 0; + pos++; + */ + // we will have one additional byte for seqMode: + if (beforeSize + pos >= MAX_BACKWARD_DEPTH - 1) + return 0; + return 1; +} + + + +// ---------- ZSTD FRAME ---------- + +#define kBlockType_Raw 0 +#define kBlockType_RLE 1 +#define kBlockType_Compressed 2 +#define kBlockType_Reserved 3 + +typedef enum +{ + // begin: states that require 4 bytes: + ZSTD2_STATE_SIGNATURE, + ZSTD2_STATE_HASH, + ZSTD2_STATE_SKIP_HEADER, + // end of states that require 4 bytes + + ZSTD2_STATE_SKIP_DATA, + ZSTD2_STATE_FRAME_HEADER, + ZSTD2_STATE_AFTER_HEADER, + ZSTD2_STATE_BLOCK, + ZSTD2_STATE_DATA, + ZSTD2_STATE_FINISHED +} EZstd2State; + + +struct CZstdDec +{ + EZstd2State frameState; + unsigned tempSize; + + Byte temp[14]; // 14 is required + + Byte descriptor; + Byte windowDescriptor; + Byte isLastBlock; + Byte blockType; + Byte isErrorState; + Byte hashError; + Byte disableHash; + Byte isCyclicMode; + + UInt32 blockSize; + UInt32 dictionaryId; + UInt32 curBlockUnpackRem; // for compressed blocks only + UInt32 inTempPos; + + UInt64 contentSize; + UInt64 contentProcessed; + CXxh64State xxh64; + + Byte *inTemp; + SizeT winBufSize_Allocated; + Byte *win_Base; + + ISzAllocPtr alloc_Small; + ISzAllocPtr alloc_Big; + + CZstdDec1 decoder; +}; + +#define ZstdDec_GET_UNPROCESSED_XXH64_SIZE(p) \ + ((unsigned)(p)->contentProcessed & (Z7_XXH64_BLOCK_SIZE - 1)) + +#define ZSTD_DEC_IS_LAST_BLOCK(p) ((p)->isLastBlock) + + +static void ZstdDec_FreeWindow(CZstdDec * const p) +{ + if (p->win_Base) + { + ISzAlloc_Free(p->alloc_Big, p->win_Base); + p->win_Base = NULL; + // p->decoder.win = NULL; + p->winBufSize_Allocated = 0; + } +} + + +CZstdDecHandle ZstdDec_Create(ISzAllocPtr alloc_Small, ISzAllocPtr alloc_Big) +{ + CZstdDec *p = (CZstdDec *)ISzAlloc_Alloc(alloc_Small, sizeof(CZstdDec)); + if (!p) + return NULL; + p->alloc_Small = alloc_Small; + p->alloc_Big = alloc_Big; + // ZstdDec_CONSTRUCT(p) + p->inTemp = NULL; + p->win_Base = NULL; + p->winBufSize_Allocated = 0; + p->disableHash = False; + ZstdDec1_Construct(&p->decoder); + return p; +} + +void ZstdDec_Destroy(CZstdDecHandle p) +{ + #ifdef SHOW_STAT + #define PRINT_STAT1(name, v) \ + printf("\n%25s = %9u", name, v); + PRINT_STAT1("g_Num_Blocks_Compressed", g_Num_Blocks_Compressed) + PRINT_STAT1("g_Num_Blocks_memcpy", g_Num_Blocks_memcpy) + PRINT_STAT1("g_Num_Wrap_memmove_Num", g_Num_Wrap_memmove_Num) + PRINT_STAT1("g_Num_Wrap_memmove_Bytes", g_Num_Wrap_memmove_Bytes) + if (g_Num_Blocks_Compressed) + { + #define PRINT_STAT(name, v) \ + printf("\n%17s = %9u, per_block = %8u", name, v, v / g_Num_Blocks_Compressed); + PRINT_STAT("g_NumSeqs", g_NumSeqs_total) + // PRINT_STAT("g_NumCopy", g_NumCopy) + PRINT_STAT("g_NumOver", g_NumOver) + PRINT_STAT("g_NumOver2", g_NumOver2) + PRINT_STAT("g_Num_Match", g_Num_Match) + PRINT_STAT("g_Num_Lits", g_Num_Lits) + PRINT_STAT("g_Num_LitsBig", g_Num_LitsBig) + PRINT_STAT("g_Num_Lit0", g_Num_Lit0) + PRINT_STAT("g_Num_Rep_0", g_Num_Rep0) + PRINT_STAT("g_Num_Rep_1", g_Num_Rep1) + PRINT_STAT("g_Num_Rep_2", g_Num_Rep2) + PRINT_STAT("g_Num_Rep_3", g_Num_Rep3) + PRINT_STAT("g_Num_Threshold_0", g_Num_Threshold_0) + PRINT_STAT("g_Num_Threshold_1", g_Num_Threshold_1) + PRINT_STAT("g_Num_Threshold_0sum", g_Num_Threshold_0sum) + PRINT_STAT("g_Num_Threshold_1sum", g_Num_Threshold_1sum) + } + printf("\n"); + #endif + + ISzAlloc_Free(p->alloc_Small, p->decoder.literalsBase); + // p->->decoder.literalsBase = NULL; + ISzAlloc_Free(p->alloc_Small, p->inTemp); + // p->inTemp = NULL; + ZstdDec_FreeWindow(p); + ISzAlloc_Free(p->alloc_Small, p); +} + + + +#define kTempBuffer_PreSize (1u << 6) +#if kTempBuffer_PreSize < MAX_BACKWARD_DEPTH + #error Stop_Compiling_Bad_kTempBuffer_PreSize +#endif + +static SRes ZstdDec_AllocateMisc(CZstdDec *p) +{ + #define k_Lit_AfterAvail (1u << 6) + #if k_Lit_AfterAvail < (COPY_CHUNK_SIZE - 1) + #error Stop_Compiling_Bad_k_Lit_AfterAvail + #endif + // return ZstdDec1_Allocate(&p->decoder, p->alloc_Small); + if (!p->decoder.literalsBase) + { + p->decoder.literalsBase = (Byte *)ISzAlloc_Alloc(p->alloc_Small, + kBlockSizeMax + k_Lit_AfterAvail); + if (!p->decoder.literalsBase) + return SZ_ERROR_MEM; + } + if (!p->inTemp) + { + // we need k_Lit_AfterAvail here for owerread from raw literals stream + p->inTemp = (Byte *)ISzAlloc_Alloc(p->alloc_Small, + kBlockSizeMax + kTempBuffer_PreSize + k_Lit_AfterAvail); + if (!p->inTemp) + return SZ_ERROR_MEM; + } + return SZ_OK; +} + + +static void ZstdDec_Init_ForNewFrame(CZstdDec *p) +{ + p->frameState = ZSTD2_STATE_SIGNATURE; + p->tempSize = 0; + + p->isErrorState = False; + p->hashError = False; + p->isCyclicMode = False; + p->contentProcessed = 0; + Xxh64State_Init(&p->xxh64); + ZstdDec1_Init(&p->decoder); +} + + +void ZstdDec_Init(CZstdDec *p) +{ + ZstdDec_Init_ForNewFrame(p); + p->decoder.winPos = 0; + memset(p->temp, 0, sizeof(p->temp)); +} + + +#define DESCRIPTOR_Get_DictionaryId_Flag(d) ((d) & 3) +#define DESCRIPTOR_FLAG_CHECKSUM (1 << 2) +#define DESCRIPTOR_FLAG_RESERVED (1 << 3) +// #define DESCRIPTOR_FLAG_UNUSED (1 << 4) +#define DESCRIPTOR_FLAG_SINGLE (1 << 5) +#define DESCRIPTOR_Get_ContentSize_Flag3(d) ((d) >> 5) +#define DESCRIPTOR_Is_ContentSize_Defined(d) (((d) & 0xe0) != 0) + + +static EZstd2State ZstdDec_UpdateState(CZstdDec * const p, const Byte b, CZstdDecInfo * const info) +{ + unsigned tempSize = p->tempSize; + p->temp[tempSize++] = b; + p->tempSize = tempSize; + + if (p->frameState == ZSTD2_STATE_BLOCK) + { + if (tempSize < 3) + return ZSTD2_STATE_BLOCK; + { + UInt32 b0 = GetUi32(p->temp); + const unsigned type = ((unsigned)b0 >> 1) & 3; + if (type == kBlockType_RLE && tempSize == 3) + return ZSTD2_STATE_BLOCK; + // info->num_Blocks_forType[type]++; + info->num_Blocks++; + if (type == kBlockType_Reserved) + { + p->isErrorState = True; // SZ_ERROR_UNSUPPORTED + return ZSTD2_STATE_BLOCK; + } + p->blockType = (Byte)type; + p->isLastBlock = (Byte)(b0 & 1); + p->inTempPos = 0; + p->tempSize = 0; + b0 >>= 3; + b0 &= 0x1fffff; + // info->num_BlockBytes_forType[type] += b0; + if (b0 == 0) + { + // empty RAW/RLE blocks are allowed in original-zstd decoder + if (type == kBlockType_Compressed) + { + p->isErrorState = True; + return ZSTD2_STATE_BLOCK; + } + if (!ZSTD_DEC_IS_LAST_BLOCK(p)) + return ZSTD2_STATE_BLOCK; + if (p->descriptor & DESCRIPTOR_FLAG_CHECKSUM) + return ZSTD2_STATE_HASH; + return ZSTD2_STATE_FINISHED; + } + p->blockSize = b0; + { + UInt32 blockLim = ZstdDec1_GET_BLOCK_SIZE_LIMIT(&p->decoder); + // compressed and uncompressed block sizes cannot be larger than min(kBlockSizeMax, window_size) + if (b0 > blockLim) + { + p->isErrorState = True; // SZ_ERROR_UNSUPPORTED; + return ZSTD2_STATE_BLOCK; + } + if (DESCRIPTOR_Is_ContentSize_Defined(p->descriptor)) + { + const UInt64 rem = p->contentSize - p->contentProcessed; + if (blockLim > rem) + blockLim = (UInt32)rem; + } + p->curBlockUnpackRem = blockLim; + // uncompressed block size cannot be larger than remain data size: + if (type != kBlockType_Compressed) + { + if (b0 > blockLim) + { + p->isErrorState = True; // SZ_ERROR_UNSUPPORTED; + return ZSTD2_STATE_BLOCK; + } + } + } + } + return ZSTD2_STATE_DATA; + } + + if ((unsigned)p->frameState < ZSTD2_STATE_SKIP_DATA) + { + UInt32 v; + if (tempSize != 4) + return p->frameState; + v = GetUi32(p->temp); + if ((unsigned)p->frameState < ZSTD2_STATE_HASH) // == ZSTD2_STATE_SIGNATURE + { + if (v == 0xfd2fb528) + { + p->tempSize = 0; + info->num_DataFrames++; + return ZSTD2_STATE_FRAME_HEADER; + } + if ((v & 0xfffffff0) == 0x184d2a50) + { + p->tempSize = 0; + info->num_SkipFrames++; + return ZSTD2_STATE_SKIP_HEADER; + } + p->isErrorState = True; + return ZSTD2_STATE_SIGNATURE; + // return ZSTD2_STATE_ERROR; // is not ZSTD stream + } + if (p->frameState == ZSTD2_STATE_HASH) + { + info->checksum_Defined = True; + info->checksum = v; + // #ifndef DISABLE_XXH_CHECK + if (!p->disableHash) + { + if (p->decoder.winPos < ZstdDec_GET_UNPROCESSED_XXH64_SIZE(p)) + { + // unexpected code failure + p->isErrorState = True; + // SZ_ERROR_FAIL; + } + else + if ((UInt32)Xxh64State_Digest(&p->xxh64, + p->decoder.win + (p->decoder.winPos - ZstdDec_GET_UNPROCESSED_XXH64_SIZE(p)), + p->contentProcessed) != v) + { + p->hashError = True; + // return ZSTD2_STATE_ERROR; // hash error + } + } + // #endif + return ZSTD2_STATE_FINISHED; + } + // (p->frameState == ZSTD2_STATE_SKIP_HEADER) + { + p->blockSize = v; + info->skipFrames_Size += v; + p->tempSize = 0; + /* we want the caller could know that there was finished frame + finished frame. So we allow the case where + we have ZSTD2_STATE_SKIP_DATA state with (blockSize == 0). + */ + // if (v == 0) return ZSTD2_STATE_SIGNATURE; + return ZSTD2_STATE_SKIP_DATA; + } + } + + // if (p->frameState == ZSTD2_STATE_FRAME_HEADER) + { + unsigned descriptor; + const Byte *h; + descriptor = p->temp[0]; + p->descriptor = (Byte)descriptor; + if (descriptor & DESCRIPTOR_FLAG_RESERVED) // reserved bit + { + p->isErrorState = True; + return ZSTD2_STATE_FRAME_HEADER; + // return ZSTD2_STATE_ERROR; + } + { + const unsigned n = DESCRIPTOR_Get_ContentSize_Flag3(descriptor); + // tempSize -= 1 + ((1u << (n >> 1)) | ((n + 1) & 1)); + tempSize -= (0x9a563422u >> (n * 4)) & 0xf; + } + if (tempSize != (4u >> (3 - DESCRIPTOR_Get_DictionaryId_Flag(descriptor)))) + return ZSTD2_STATE_FRAME_HEADER; + + info->descriptor_OR = (Byte)(info->descriptor_OR | descriptor); + info->descriptor_NOT_OR = (Byte)(info->descriptor_NOT_OR | ~descriptor); + + h = &p->temp[1]; + { + Byte w = 0; + if ((descriptor & DESCRIPTOR_FLAG_SINGLE) == 0) + { + w = *h++; + if (info->windowDescriptor_MAX < w) + info->windowDescriptor_MAX = w; + // info->are_WindowDescriptors = True; + // info->num_WindowDescriptors++; + } + else + { + // info->are_SingleSegments = True; + // info->num_SingleSegments++; + } + p->windowDescriptor = w; + } + { + unsigned n = DESCRIPTOR_Get_DictionaryId_Flag(descriptor); + UInt32 d = 0; + if (n) + { + n = 1u << (n - 1); + d = GetUi32(h) & ((UInt32)(Int32)-1 >> (32 - 8u * n)); + h += n; + } + p->dictionaryId = d; + // info->dictionaryId_Cur = d; + if (d != 0) + { + if (info->dictionaryId == 0) + info->dictionaryId = d; + else if (info->dictionaryId != d) + info->are_DictionaryId_Different = True; + } + } + { + unsigned n = DESCRIPTOR_Get_ContentSize_Flag3(descriptor); + UInt64 v = 0; + if (n) + { + n >>= 1; + if (n == 1) + v = 256; + v += GetUi64(h) & ((UInt64)(Int64)-1 >> (64 - (8u << n))); + // info->are_ContentSize_Known = True; + // info->num_Frames_with_ContentSize++; + if (info->contentSize_MAX < v) + info->contentSize_MAX = v; + info->contentSize_Total += v; + } + else + { + info->are_ContentSize_Unknown = True; + // info->num_Frames_without_ContentSize++; + } + p->contentSize = v; + } + // if ((size_t)(h - p->temp) != headerSize) return ZSTD2_STATE_ERROR; // it's unexpected internal code failure + p->tempSize = 0; + + info->checksum_Defined = False; + /* + if (descriptor & DESCRIPTOR_FLAG_CHECKSUM) + info->are_Checksums = True; + else + info->are_Non_Checksums = True; + */ + + return ZSTD2_STATE_AFTER_HEADER; // ZSTD2_STATE_BLOCK; + } +} + + +static void ZstdDec_Update_XXH(CZstdDec * const p, size_t xxh64_winPos) +{ + /* + #ifdef DISABLE_XXH_CHECK + UNUSED_VAR(data) + #else + */ + if (!p->disableHash && (p->descriptor & DESCRIPTOR_FLAG_CHECKSUM)) + { + // const size_t pos = p->xxh64_winPos; + const size_t size = (p->decoder.winPos - xxh64_winPos) & ~(size_t)31; + if (size) + { + // p->xxh64_winPos = pos + size; + Xxh64State_UpdateBlocks(&p->xxh64, + p->decoder.win + xxh64_winPos, + p->decoder.win + xxh64_winPos + size); + } + } +} + + +/* +in: + (winLimit) : is relaxed limit, where this function is allowed to stop writing of decoded data (if possible). + - this function uses (winLimit) for RAW/RLE blocks only, + because this function can decode single RAW/RLE block in several different calls. + - this function DOESN'T use (winLimit) for Compressed blocks, + because this function decodes full compressed block in single call. + (CZstdDec1::winPos <= winLimit) + (winLimit <= CZstdDec1::cycSize). + Note: if (ds->outBuf_fromCaller) mode is used, then + { + (strong_limit) is stored in CZstdDec1::cycSize. + So (winLimit) is more strong than (strong_limit). + } + +exit: + Note: (CZstdDecState::winPos) will be set by caller after exit of this function. + + This function can exit for any of these conditions: + - (frameState == ZSTD2_STATE_AFTER_HEADER) + - (frameState == ZSTD2_STATE_FINISHED) : frame was finished : (status == ZSTD_STATUS_FINISHED_FRAME) is set + - finished non-empty non-last block. So (CZstdDec1::winPos_atExit != winPos_atFuncStart). + - ZSTD_STATUS_NEEDS_MORE_INPUT in src + - (CZstdDec1::winPos) have reached (winLimit) in non-finished RAW/RLE block + + This function decodes no more than one non-empty block. + So it fulfills the condition at exit: + (CZstdDec1::winPos_atExit - winPos_atFuncStart <= block_size_max) + Note: (winPos_atExit > winLimit) is possible in some cases after compressed block decoding. + + if (ds->outBuf_fromCaller) mode (useAdditionalWinLimit medo) + { + then this function uses additional strong limit from (CZstdDec1::cycSize). + So this function will not write any data after (CZstdDec1::cycSize) + And it fulfills the condition at exit: + (CZstdDec1::winPos_atExit <= CZstdDec1::cycSize) + } +*/ +static SRes ZstdDec_DecodeBlock(CZstdDec * const p, CZstdDecState * const ds, + SizeT winLimitAdd) +{ + const Byte *src = ds->inBuf; + SizeT * const srcLen = &ds->inPos; + const SizeT inSize = ds->inLim; + // const int useAdditionalWinLimit = ds->outBuf_fromCaller ? 1 : 0; + enum_ZstdStatus * const status = &ds->status; + CZstdDecInfo * const info = &ds->info; + SizeT winLimit; + + const SizeT winPos_atFuncStart = p->decoder.winPos; + src += *srcLen; + *status = ZSTD_STATUS_NOT_SPECIFIED; + + // finishMode = ZSTD_FINISH_ANY; + if (ds->outSize_Defined) + { + if (ds->outSize < ds->outProcessed) + { + // p->isAfterSizeMode = 2; // we have extra bytes already + *status = ZSTD_STATUS_OUT_REACHED; + return SZ_OK; + // size = 0; + } + else + { + // p->outSize >= p->outProcessed + const UInt64 rem = ds->outSize - ds->outProcessed; + /* + if (rem == 0) + p->isAfterSizeMode = 1; // we have reached exact required size + */ + if (winLimitAdd >= rem) + { + winLimitAdd = (SizeT)rem; + // if (p->finishMode) finishMode = ZSTD_FINISH_END; + } + } + } + + winLimit = p->decoder.winPos + winLimitAdd; + // (p->decoder.winPos <= winLimit) + + // while (p->frameState != ZSTD2_STATE_ERROR) + while (!p->isErrorState) + { + SizeT inCur = inSize - *srcLen; + + if (p->frameState == ZSTD2_STATE_DATA) + { + /* (p->decoder.winPos == winPos_atFuncStart) is expected, + because this function doesn't start new block. + if it have finished some non-empty block in this call. */ + if (p->decoder.winPos != winPos_atFuncStart) + return SZ_ERROR_FAIL; // it's unexpected + + /* + if (p->decoder.winPos > winLimit) + { + // we can be here, if in this function call + // - we have extracted non-empty compressed block, and (winPos > winLimit) after that. + // - we have started new block decoding after that. + // It's unexpected case, because we exit after non-empty non-last block. + *status = (inSize == *srcLen) ? + ZSTD_STATUS_NEEDS_MORE_INPUT : + ZSTD_STATUS_NOT_FINISHED; + return SZ_OK; + } + */ + // p->decoder.winPos <= winLimit + + if (p->blockType != kBlockType_Compressed) + { + // it's RLE or RAW block. + // p->BlockSize != 0_ + // winLimit <= p->decoder.cycSize + /* So here we use more strong (winLimit), even for + (ds->outBuf_fromCaller) mode. */ + SizeT outCur = winLimit - p->decoder.winPos; + { + const UInt32 rem = p->blockSize; + if (outCur > rem) + outCur = rem; + } + if (p->blockType == kBlockType_Raw) + { + if (outCur > inCur) + outCur = inCur; + /* output buffer is better aligned for XXH code. + So we use hash for output buffer data */ + // ZstdDec_Update_XXH(p, src, outCur); // for debug: + memcpy(p->decoder.win + p->decoder.winPos, src, outCur); + src += outCur; + *srcLen += outCur; + } + else // kBlockType_RLE + { + #define RLE_BYTE_INDEX_IN_temp 3 + memset(p->decoder.win + p->decoder.winPos, + p->temp[RLE_BYTE_INDEX_IN_temp], outCur); + } + { + const SizeT xxh64_winPos = p->decoder.winPos - ZstdDec_GET_UNPROCESSED_XXH64_SIZE(p); + p->decoder.winPos += outCur; + UPDATE_TOTAL_OUT(&p->decoder, outCur) + p->contentProcessed += outCur; + ZstdDec_Update_XXH(p, xxh64_winPos); + } + // ds->winPos = p->decoder.winPos; // the caller does it instead. for debug: + ds->outProcessed += outCur; + if (p->blockSize -= (UInt32)outCur) + { + /* + if (ds->outSize_Defined) + { + if (ds->outSize <= ds->outProcessed) ds->isAfterSizeMode = (enum_ZstdStatus) + (ds->outSize == ds->outProcessed ? 1u: 2u); + } + */ + *status = (enum_ZstdStatus) + (ds->outSize_Defined && ds->outSize <= ds->outProcessed ? + ZSTD_STATUS_OUT_REACHED : (p->blockType == kBlockType_Raw && inSize == *srcLen) ? + ZSTD_STATUS_NEEDS_MORE_INPUT : + ZSTD_STATUS_NOT_FINISHED); + return SZ_OK; + } + } + else // kBlockType_Compressed + { + // p->blockSize != 0 + // (uncompressed_size_of_block == 0) is allowed + // (p->curBlockUnpackRem == 0) is allowed + /* + if (p->decoder.winPos >= winLimit) + { + if (p->decoder.winPos != winPos_atFuncStart) + { + // it's unexpected case + // We already have some data in finished blocks in this function call. + // So we don't decompress new block after (>=winLimit), + // even if it's empty block. + *status = (inSize == *srcLen) ? + ZSTD_STATUS_NEEDS_MORE_INPUT : + ZSTD_STATUS_NOT_FINISHED; + return SZ_OK; + } + // (p->decoder.winPos == winLimit == winPos_atFuncStart) + // we will decode current block, because that current + // block can be empty block and we want to make some visible + // change of (src) stream after function start. + } + */ + /* + if (ds->outSize_Defined && ds->outSize < ds->outProcessed) + { + // we don't want to start new block, if we have more extra decoded bytes already + *status = ZSTD_STATUS_OUT_REACHED; + return SZ_OK; + } + */ + { + const Byte *comprStream; + size_t afterAvail; + UInt32 inTempPos = p->inTempPos; + const UInt32 rem = p->blockSize - inTempPos; + // rem != 0 + if (inTempPos != 0 // (inTemp) buffer already contains some input data + || inCur < rem // available input data size is smaller than compressed block size + || ZstdDec1_NeedTempBufferForInput(*srcLen, src, rem)) + { + if (inCur > rem) + inCur = rem; + if (inCur) + { + STAT_INC(g_Num_Blocks_memcpy) + // we clear data for backward lookahead reading + if (inTempPos == 0) + memset(p->inTemp + kTempBuffer_PreSize - MAX_BACKWARD_DEPTH, 0, MAX_BACKWARD_DEPTH); + // { unsigned y = 0; for(;y < 1000; y++) + memcpy(p->inTemp + inTempPos + kTempBuffer_PreSize, src, inCur); + // } + src += inCur; + *srcLen += inCur; + inTempPos += (UInt32)inCur; + p->inTempPos = inTempPos; + } + if (inTempPos != p->blockSize) + { + *status = ZSTD_STATUS_NEEDS_MORE_INPUT; + return SZ_OK; + } + #if COPY_CHUNK_SIZE > 1 + memset(p->inTemp + kTempBuffer_PreSize + inTempPos, 0, COPY_CHUNK_SIZE); + #endif + comprStream = p->inTemp + kTempBuffer_PreSize; + afterAvail = k_Lit_AfterAvail; + // we don't want to read non-initialized data or junk in CopyMatch(): + } + else + { + // inCur >= rem + // we use direct decoding from (src) buffer: + afterAvail = inCur - rem; + comprStream = src; + src += rem; + *srcLen += rem; + } + + #ifdef Z7_ZSTD_DEC_USE_CHECK_OF_NEED_TEMP + ZstdDec1_NeedTempBufferForInput(*srcLen, comprStream, p->blockSize); + #endif + // printf("\nblockSize=%u", p->blockSize); + // printf("%x\n", (unsigned)p->contentProcessed); + STAT_INC(g_Num_Blocks_Compressed) + { + SRes sres; + const size_t winPos = p->decoder.winPos; + /* + if ( useAdditionalWinLimit), we use strong unpack limit: smallest from + - limit from stream : (curBlockUnpackRem) + - limit from caller : (cycSize - winPos) + if (!useAdditionalWinLimit), we use only relaxed limit: + - limit from stream : (curBlockUnpackRem) + */ + SizeT outLimit = p->curBlockUnpackRem; + if (ds->outBuf_fromCaller) + // if (useAdditionalWinLimit) + { + const size_t limit = p->decoder.cycSize - winPos; + if (outLimit > limit) + outLimit = limit; + } + sres = ZstdDec1_DecodeBlock(&p->decoder, + comprStream, p->blockSize, afterAvail, outLimit); + // ds->winPos = p->decoder.winPos; // the caller does it instead. for debug: + if (sres) + { + p->isErrorState = True; + return sres; + } + { + const SizeT xxh64_winPos = winPos - ZstdDec_GET_UNPROCESSED_XXH64_SIZE(p); + const size_t num = p->decoder.winPos - winPos; + ds->outProcessed += num; + p->contentProcessed += num; + ZstdDec_Update_XXH(p, xxh64_winPos); + } + } + // printf("\nwinPos=%x", (int)(unsigned)p->decoder.winPos); + } + } + + /* + if (ds->outSize_Defined) + { + if (ds->outSize <= ds->outProcessed) ds->isAfterSizeMode = (enum_ZstdStatus) + (ds->outSize == ds->outProcessed ? 1u: 2u); + } + */ + + if (!ZSTD_DEC_IS_LAST_BLOCK(p)) + { + p->frameState = ZSTD2_STATE_BLOCK; + if (ds->outSize_Defined && ds->outSize < ds->outProcessed) + { + *status = ZSTD_STATUS_OUT_REACHED; + return SZ_OK; + } + // we exit only if (winPos) was changed in this function call: + if (p->decoder.winPos != winPos_atFuncStart) + { + // decoded block was not empty. So we exit: + *status = (enum_ZstdStatus)( + (inSize == *srcLen) ? + ZSTD_STATUS_NEEDS_MORE_INPUT : + ZSTD_STATUS_NOT_FINISHED); + return SZ_OK; + } + // (p->decoder.winPos == winPos_atFuncStart) + // so current decoded block was empty. + // we will try to decode more blocks in this function. + continue; + } + + // decoded block was last in frame + if (p->descriptor & DESCRIPTOR_FLAG_CHECKSUM) + { + p->frameState = ZSTD2_STATE_HASH; + if (ds->outSize_Defined && ds->outSize < ds->outProcessed) + { + *status = ZSTD_STATUS_OUT_REACHED; + return SZ_OK; // disable if want to + /* We want to get same return codes for any input buffer sizes. + We want to get faster ZSTD_STATUS_OUT_REACHED status. + So we exit with ZSTD_STATUS_OUT_REACHED here, + instead of ZSTD2_STATE_HASH and ZSTD2_STATE_FINISHED processing. + that depends from input buffer size and that can set + ZSTD_STATUS_NEEDS_MORE_INPUT or return SZ_ERROR_DATA or SZ_ERROR_CRC. + */ + } + } + else + { + /* ZSTD2_STATE_FINISHED proccesing doesn't depend from input buffer */ + p->frameState = ZSTD2_STATE_FINISHED; + } + /* + p->frameState = (p->descriptor & DESCRIPTOR_FLAG_CHECKSUM) ? + ZSTD2_STATE_HASH : + ZSTD2_STATE_FINISHED; + */ + /* it's required to process ZSTD2_STATE_FINISHED state in this function call, + because we must check contentSize and hashError in ZSTD2_STATE_FINISHED code, + while the caller can reinit full state for ZSTD2_STATE_FINISHED + So we can't exit from function here. */ + continue; + } + + if (p->frameState == ZSTD2_STATE_FINISHED) + { + *status = ZSTD_STATUS_FINISHED_FRAME; + if (DESCRIPTOR_Is_ContentSize_Defined(p->descriptor) + && p->contentSize != p->contentProcessed) + return SZ_ERROR_DATA; + if (p->hashError) // for debug + return SZ_ERROR_CRC; + return SZ_OK; + // p->frameState = ZSTD2_STATE_SIGNATURE; + // continue; + } + + if (p->frameState == ZSTD2_STATE_AFTER_HEADER) + return SZ_OK; // we need memory allocation for that state + + if (p->frameState == ZSTD2_STATE_SKIP_DATA) + { + UInt32 blockSize = p->blockSize; + // (blockSize == 0) is possible + if (inCur > blockSize) + inCur = blockSize; + src += inCur; + *srcLen += inCur; + blockSize -= (UInt32)inCur; + p->blockSize = blockSize; + if (blockSize == 0) + { + p->frameState = ZSTD2_STATE_SIGNATURE; + // continue; // for debug: we can continue without return to caller. + // we notify the caller that skip frame was finished: + *status = ZSTD_STATUS_FINISHED_FRAME; + return SZ_OK; + } + // blockSize != 0 + // (inCur) was smaller than previous value of p->blockSize. + // (inSize == *srcLen) now + *status = ZSTD_STATUS_NEEDS_MORE_INPUT; + return SZ_OK; + } + + if (inCur == 0) + { + *status = ZSTD_STATUS_NEEDS_MORE_INPUT; + return SZ_OK; + } + + { + (*srcLen)++; + p->frameState = ZstdDec_UpdateState(p, *src++, info); + } + } + + *status = ZSTD_STATUS_NOT_SPECIFIED; + p->isErrorState = True; + // p->frameState = ZSTD2_STATE_ERROR; + // if (p->frameState = ZSTD2_STATE_SIGNATURE) return SZ_ERROR_NO_ARCHIVE + return SZ_ERROR_DATA; +} + + + + +SRes ZstdDec_Decode(CZstdDecHandle dec, CZstdDecState *p) +{ + p->needWrite_Size = 0; + p->status = ZSTD_STATUS_NOT_SPECIFIED; + dec->disableHash = p->disableHash; + + if (p->outBuf_fromCaller) + { + dec->decoder.win = p->outBuf_fromCaller; + dec->decoder.cycSize = p->outBufSize_fromCaller; + } + + // p->winPos = dec->decoder.winPos; + + for (;;) + { + SizeT winPos, size; + // SizeT outProcessed; + SRes res; + + if (p->wrPos > dec->decoder.winPos) + return SZ_ERROR_FAIL; + + if (dec->frameState == ZSTD2_STATE_FINISHED) + { + if (!p->outBuf_fromCaller) + { + // we need to set positions to zero for new frame. + if (p->wrPos != dec->decoder.winPos) + { + /* We have already asked the caller to flush all data + with (p->needWrite_Size) and (ZSTD_STATUS_FINISHED_FRAME) status. + So it's unexpected case */ + // p->winPos = dec->decoder.winPos; + // p->needWrite_Size = dec->decoder.winPos - p->wrPos; // flush size asking + // return SZ_OK; // ask to flush again + return SZ_ERROR_FAIL; + } + // (p->wrPos == dec->decoder.winPos), and we wrap to zero: + dec->decoder.winPos = 0; + p->winPos = 0; + p->wrPos = 0; + } + ZstdDec_Init_ForNewFrame(dec); + // continue; + } + + winPos = dec->decoder.winPos; + { + SizeT next = dec->decoder.cycSize; + /* cycSize == 0, if no buffer was allocated still, + or, if (outBuf_fromCaller) mode and (outBufSize_fromCaller == 0) */ + if (!p->outBuf_fromCaller + && next + && next <= winPos + && dec->isCyclicMode) + { + // (0 < decoder.cycSize <= winPos) in isCyclicMode. + // so we need to wrap (winPos) and (wrPos) over (cycSize). + const size_t delta = next; + // (delta) is how many bytes we remove from buffer. + /* + // we don't need data older than last (cycSize) bytes. + size_t delta = winPos - next; // num bytes after (cycSize) + if (delta <= next) // it's expected case + delta = next; + // delta == Max(cycSize, winPos - cycSize) + */ + if (p->wrPos < delta) + { + // (wrPos < decoder.cycSize) + // We have asked already the caller to flush required data + // p->status = ZSTD_STATUS_NOT_SPECIFIED; + // p->winPos = winPos; + // p->needWrite_Size = delta - p->wrPos; // flush size asking + // return SZ_OK; // ask to flush again + return SZ_ERROR_FAIL; + } + // p->wrPos >= decoder.cycSize + // we move extra data after (decoder.cycSize) to start of cyclic buffer: + winPos -= delta; + if (winPos) + { + if (winPos >= delta) + return SZ_ERROR_FAIL; + memmove(dec->decoder.win, dec->decoder.win + delta, winPos); + // printf("\nmemmove processed=%8x winPos=%8x\n", (unsigned)p->outProcessed, (unsigned)dec->decoder.winPos); + STAT_INC(g_Num_Wrap_memmove_Num) + STAT_UPDATE(g_Num_Wrap_memmove_Bytes += (unsigned)winPos;) + } + dec->decoder.winPos = winPos; + p->winPos = winPos; + p->wrPos -= delta; + // dec->xxh64_winPos -= delta; + + // (winPos < delta) + #ifdef Z7_STD_DEC_USE_AFTER_CYC_BUF + /* we set the data after cycSize, because + we don't want to read non-initialized data or junk in CopyMatch(). */ + memset(dec->decoder.win + next, 0, COPY_CHUNK_SIZE); + #endif + + /* + if (winPos == next) + { + if (winPos != p->wrPos) + { + // we already requested before to flush full data for that case. + // but we give the caller a second chance to flush data: + p->needWrite_Size = winPos - p->wrPos; + return SZ_OK; + } + // (decoder.cycSize == winPos == p->wrPos) + // so we do second wrapping to zero: + winPos = 0; + dec->decoder.winPos = 0; + p->winPos = 0; + p->wrPos = 0; + } + */ + // (winPos < next) + } + + if (winPos > next) + return SZ_ERROR_FAIL; // it's unexpected case + /* + if (!outBuf_fromCaller && isCyclicMode && cycSize != 0) + then (winPos < cycSize) + else (winPos <= cycSize) + */ + if (!p->outBuf_fromCaller) + { + // that code is optional. We try to optimize write chunk sizes. + /* (next2) is expected next write position in the caller, + if the caller writes by kBlockSizeMax chunks. + */ + /* + const size_t next2 = (winPos + kBlockSizeMax) & (kBlockSizeMax - 1); + if (winPos < next2 && next2 < next) + next = next2; + */ + } + size = next - winPos; + } + + // note: ZstdDec_DecodeBlock() uses (winLimit = winPos + size) only for RLE and RAW blocks + res = ZstdDec_DecodeBlock(dec, p, size); + /* + after one block decoding: + if (!outBuf_fromCaller && isCyclicMode && cycSize != 0) + then (winPos < cycSize + max_block_size) + else (winPos <= cycSize) + */ + + if (!p->outBuf_fromCaller) + p->win = dec->decoder.win; + p->winPos = dec->decoder.winPos; + + // outProcessed = dec->decoder.winPos - winPos; + // p->outProcessed += outProcessed; + + if (res != SZ_OK) + return res; + + if (dec->frameState != ZSTD2_STATE_AFTER_HEADER) + { + if (p->outBuf_fromCaller) + return SZ_OK; + { + // !p->outBuf_fromCaller + /* + if (ZSTD_STATUS_FINISHED_FRAME), we request full flushing here because + 1) it's simpler to work with allocation and extracting of next frame, + 2) it's better to start writing to next new frame with aligned memory + for faster xxh 64-bit reads. + */ + size_t end = dec->decoder.winPos; // end pos for all data flushing + if (p->status != ZSTD_STATUS_FINISHED_FRAME) + { + // we will request flush here only for cases when wrap in cyclic buffer can be required in next call. + if (!dec->isCyclicMode) + return SZ_OK; + // isCyclicMode + { + const size_t delta = dec->decoder.cycSize; + if (end < delta) + return SZ_OK; // (winPos < cycSize). no need for flush + // cycSize <= winPos + // So we ask the caller to flush of (cycSize - wrPos) bytes, + // and then we will wrap cylicBuffer in next call + end = delta; + } + } + p->needWrite_Size = end - p->wrPos; + } + return SZ_OK; + } + + // ZSTD2_STATE_AFTER_HEADER + { + BoolInt useCyclic = False; + size_t cycSize; + + // p->status = ZSTD_STATUS_NOT_FINISHED; + if (dec->dictionaryId != 0) + { + /* actually we can try to decode some data, + because it's possible that some data doesn't use dictionary */ + // p->status = ZSTD_STATUS_NOT_SPECIFIED; + return SZ_ERROR_UNSUPPORTED; + } + + { + UInt64 winSize = dec->contentSize; + UInt64 winSize_Allocate = winSize; + const unsigned descriptor = dec->descriptor; + + if ((descriptor & DESCRIPTOR_FLAG_SINGLE) == 0) + { + const Byte wd = dec->windowDescriptor; + winSize = (UInt64)(8 + (wd & 7)) << ((wd >> 3) + 10 - 3); + if (!DESCRIPTOR_Is_ContentSize_Defined(descriptor) + || winSize_Allocate > winSize) + { + winSize_Allocate = winSize; + useCyclic = True; + } + } + /* + else + { + if (p->info.singleSegment_ContentSize_MAX < winSize) + p->info.singleSegment_ContentSize_MAX = winSize; + // p->info.num_SingleSegments++; + } + */ + if (p->info.windowSize_MAX < winSize) + p->info.windowSize_MAX = winSize; + if (p->info.windowSize_Allocate_MAX < winSize_Allocate) + p->info.windowSize_Allocate_MAX = winSize_Allocate; + /* + winSize_Allocate is MIN(content_size, window_size_from_descriptor). + Wven if (content_size < (window_size_from_descriptor)) + original-zstd still uses (window_size_from_descriptor) to check that decoding is allowed. + We try to follow original-zstd, and here we check (winSize) instead of (winSize_Allocate)) + */ + if ( + // winSize_Allocate // it's relaxed check + winSize // it's more strict check to be compatible with original-zstd + > ((UInt64)1 << MAX_WINDOW_SIZE_LOG)) + return SZ_ERROR_UNSUPPORTED; // SZ_ERROR_MEM + cycSize = (size_t)winSize_Allocate; + if (cycSize != winSize_Allocate) + return SZ_ERROR_MEM; + // cycSize <= winSize + /* later we will use (CZstdDec1::winSize) to check match offsets and check block sizes. + if (there is window descriptor) + { + We will check block size with (window_size_from_descriptor) instead of (winSize_Allocate). + Does original-zstd do it that way also? + } + Here we must reduce full real 64-bit (winSize) to size_t for (CZstdDec1::winSize). + Also we don't want too big values for (CZstdDec1::winSize). + our (CZstdDec1::winSize) will meet the condition: + (CZstdDec1::winSize < kBlockSizeMax || CZstdDec1::winSize <= cycSize). + */ + dec->decoder.winSize = (winSize < kBlockSizeMax) ? (size_t)winSize: cycSize; + // note: (CZstdDec1::winSize > cycSize) is possible, if (!useCyclic) + } + + RINOK(ZstdDec_AllocateMisc(dec)) + + if (p->outBuf_fromCaller) + dec->isCyclicMode = False; + else + { + size_t d = cycSize; + + if (dec->decoder.winPos != p->wrPos) + return SZ_ERROR_FAIL; + + dec->decoder.winPos = 0; + p->wrPos = 0; + p->winPos = dec->decoder.winPos; + + /* + const size_t needWrite = dec->decoder.winPos - p->wrPos; + if (!needWrite) + { + dec->decoder.winPos = 0; + p->wrPos = 0; + p->winPos = dec->decoder.winPos; + } + */ + /* if (!useCyclic) we allocate only cycSize = ContentSize. + But if we want to support the case where new frame starts with winPos != 0, + then we will wrap over zero, and we still need + to set (useCyclic) and allocate additional buffer spaces. + Now we don't allow new frame starting with (winPos != 0). + so (dec->decoder->winPos == 0) + can use (!useCyclic) with reduced buffer sizes. + */ + /* + if (dec->decoder->winPos != 0) + useCyclic = True; + */ + + if (useCyclic) + { + /* cyclyc buffer size must be at least (COPY_CHUNK_SIZE - 1) bytes + larger than window size, because CopyMatch() can write additional + (COPY_CHUNK_SIZE - 1) bytes and overwrite oldests data in cyclyc buffer. + But for performance reasons we align (cycSize) for (kBlockSizeMax). + also we must provide (cycSize >= max_decoded_data_after_cycSize), + because after data move wrapping over zero we must provide (winPos < cycSize). + */ + const size_t alignSize = kBlockSizeMax; + /* here we add (1 << 7) instead of (COPY_CHUNK_SIZE - 1), because + we want to get same (cycSize) for different COPY_CHUNK_SIZE values. */ + // cycSize += (COPY_CHUNK_SIZE - 1) + (alignSize - 1); // for debug : we can get smallest (cycSize) + cycSize += (1 << 7) + alignSize; + cycSize &= ~(size_t)(alignSize - 1); + // cycSize must be aligned for 32, because xxh requires 32-bytes blocks. + // cycSize += 12345; // for debug + // cycSize += 1 << 10; // for debug + // cycSize += 32; // for debug + // cycSize += kBlockSizeMax; // for debug + if (cycSize < d) + return SZ_ERROR_MEM; + /* + in cyclic buffer mode we allow to decode one additional block + that exceeds (cycSize). + So we must allocate additional (kBlockSizeMax) bytes after (cycSize). + if defined(Z7_STD_DEC_USE_AFTER_CYC_BUF) + { + we can read (COPY_CHUNK_SIZE - 1) bytes after (cycSize) + but we aready allocate additional kBlockSizeMax that + is larger than COPY_CHUNK_SIZE. + So we don't need additional space of COPY_CHUNK_SIZE after (cycSize). + } + */ + /* + #ifdef Z7_STD_DEC_USE_AFTER_CYC_BUF + d = cycSize + (1 << 7); // we must add at least (COPY_CHUNK_SIZE - 1) + #endif + */ + d = cycSize + kBlockSizeMax; + if (d < cycSize) + return SZ_ERROR_MEM; + } + + { + const size_t kMinWinAllocSize = 1 << 12; + if (d < kMinWinAllocSize) + d = kMinWinAllocSize; + } + + if (d > dec->winBufSize_Allocated) + { + /* + if (needWrite) + { + p->needWrite_Size = needWrite; + return SZ_OK; + // return SZ_ERROR_FAIL; + } + */ + + if (dec->winBufSize_Allocated != 0) + { + const size_t k_extra = (useCyclic || d >= (1u << 20)) ? + 2 * kBlockSizeMax : 0; + unsigned i = useCyclic ? 17 : 12; + for (; i < sizeof(size_t) * 8; i++) + { + const size_t d2 = ((size_t)1 << i) + k_extra; + if (d2 >= d) + { + d = d2; + break; + } + } + } + // RINOK(ZstdDec_AllocateWindow(dec, d)) + ZstdDec_FreeWindow(dec); + dec->win_Base = (Byte *)ISzAlloc_Alloc(dec->alloc_Big, d); + if (!dec->win_Base) + return SZ_ERROR_MEM; + dec->decoder.win = dec->win_Base; + dec->winBufSize_Allocated = d; + } + /* + else + { + // for non-cyclycMode we want flush data, and set winPos = 0 + if (needWrite) + { + if (!useCyclic || dec->decoder.winPos >= cycSize) + { + p->needWrite_Size = needWrite; + return SZ_OK; + // return SZ_ERROR_FAIL; + } + } + } + */ + + dec->decoder.cycSize = cycSize; + p->win = dec->decoder.win; + // p->cycSize = dec->decoder.cycSize; + dec->isCyclicMode = (Byte)useCyclic; + } // (!p->outBuf_fromCaller) end + + // p->winPos = dec->decoder.winPos; + dec->frameState = ZSTD2_STATE_BLOCK; + // continue; + } // ZSTD2_STATE_AFTER_HEADER end + } +} + + +void ZstdDec_GetResInfo(const CZstdDec *dec, + const CZstdDecState *p, + SRes res, + CZstdDecResInfo *stat) +{ + // ZstdDecInfo_CLEAR(stat); + stat->extraSize = 0; + stat->is_NonFinishedFrame = False; + if (dec->frameState != ZSTD2_STATE_FINISHED) + { + if (dec->frameState == ZSTD2_STATE_SIGNATURE) + { + stat->extraSize = (Byte)dec->tempSize; + if (ZstdDecInfo_GET_NUM_FRAMES(&p->info) == 0) + res = SZ_ERROR_NO_ARCHIVE; + } + else + { + stat->is_NonFinishedFrame = True; + if (res == SZ_OK && p->status == ZSTD_STATUS_NEEDS_MORE_INPUT) + res = SZ_ERROR_INPUT_EOF; + } + } + stat->decode_SRes = res; +} + + +size_t ZstdDec_ReadUnusedFromInBuf( + CZstdDecHandle dec, + size_t afterDecoding_tempPos, + void *data, size_t size) +{ + size_t processed = 0; + if (dec->frameState == ZSTD2_STATE_SIGNATURE) + { + Byte *dest = (Byte *)data; + const size_t tempSize = dec->tempSize; + while (afterDecoding_tempPos < tempSize) + { + if (size == 0) + break; + size--; + *dest++ = dec->temp[afterDecoding_tempPos++]; + processed++; + } + } + return processed; +} + + +void ZstdDecState_Clear(CZstdDecState *p) +{ + memset(p, 0 , sizeof(*p)); +} diff --git a/src/plugins/7zip/7za/c/ZstdDec.h b/src/plugins/7zip/7za/c/ZstdDec.h new file mode 100644 index 00000000..cd261318 --- /dev/null +++ b/src/plugins/7zip/7za/c/ZstdDec.h @@ -0,0 +1,173 @@ +/* ZstdDec.h -- Zstd Decoder interfaces +2024-01-21 : Igor Pavlov : Public domain */ + +#ifndef ZIP7_INC_ZSTD_DEC_H +#define ZIP7_INC_ZSTD_DEC_H + +EXTERN_C_BEGIN + +typedef struct CZstdDec CZstdDec; +typedef CZstdDec * CZstdDecHandle; + +CZstdDecHandle ZstdDec_Create(ISzAllocPtr alloc_Small, ISzAllocPtr alloc_Big); +void ZstdDec_Destroy(CZstdDecHandle p); + +typedef enum +{ + ZSTD_STATUS_NOT_SPECIFIED, /* use main error code instead */ + ZSTD_STATUS_FINISHED_FRAME, /* data frame or skip frame was finished */ + ZSTD_STATUS_NOT_FINISHED, /* just finished non-empty block or unfinished RAW/RLE block */ + ZSTD_STATUS_NEEDS_MORE_INPUT, /* the callee needs more input bytes. It has more priority over ZSTD_STATUS_NOT_FINISHED */ + ZSTD_STATUS_OUT_REACHED /* is not finihed frame and ((outProcessed > outSize) || (outProcessed == outSize && unfinished RAW/RLE block) */ +} enum_ZstdStatus_Dummy; + +#define ZstdDecState_DOES_NEED_MORE_INPUT_OR_FINISHED_FRAME(p) \ + ((p)->status & ZSTD_STATUS_FINISHED_FRAME) +/* + ((p)->status == ZSTD_STATUS_NEEDS_MORE_INPUT || \ + (p)->status == ZSTD_STATUS_FINISHED_FRAME) +*/ + +typedef Byte enum_ZstdStatus; + + +void ZstdDec_Init(CZstdDecHandle p); + +typedef struct +{ + UInt64 num_Blocks; + Byte descriptor_OR; + Byte descriptor_NOT_OR; + Byte are_ContentSize_Unknown; + Byte windowDescriptor_MAX; + + // Byte are_ContentSize_Known; + // Byte are_SingleSegments; + // Byte are_WindowDescriptors; + Byte checksum_Defined; + // Byte are_Checksums; + // Byte are_Non_Checksums; + + // Byte are_DictionaryId; + Byte are_DictionaryId_Different; + + // Byte reserved[3]; + + UInt32 checksum; // checksum of last data frame + /// UInt32 dictionaryId_Cur; + UInt32 dictionaryId; // if there are non-zero dictionary IDs, then it's first dictionaryId + + UInt64 num_DataFrames; + UInt64 num_SkipFrames; + UInt64 skipFrames_Size; + UInt64 contentSize_Total; + UInt64 contentSize_MAX; + // UInt64 num_Checksums; + // UInt64 num_Non_Checksums; // frames without checksum + // UInt64 num_WindowDescriptors; + // UInt64 num_SingleSegments; + // UInt64 num_Frames_with_ContentSize; + // UInt64 num_Frames_without_ContentSize; + UInt64 windowSize_MAX; + UInt64 windowSize_Allocate_MAX; + // UInt64 num_DictionaryIds; + // UInt64 num_Blocks_forType[4]; + // UInt64 num_BlockBytes_forType[4]; + // UInt64 num_SingleSegments; + // UInt64 singleSegment_ContentSize_MAX; +} CZstdDecInfo; + +#define ZstdDecInfo_CLEAR(p) { memset(p, 0, sizeof(*(p))); } + +#define ZstdDecInfo_GET_NUM_FRAMES(p) ((p)->num_DataFrames + (p)->num_SkipFrames) + + +typedef struct CZstdDecState +{ + enum_ZstdStatus status; // out + Byte disableHash; + // Byte mustBeFinished; + Byte outSize_Defined; + // Byte isAfterSizeMode; + // UInt64 inProcessed; + // SRes codeRes; + // Byte needWrite_IsStrong; + + const Byte *inBuf; + size_t inPos; // in/out + size_t inLim; + + const Byte *win; // out + size_t winPos; // out + size_t wrPos; // in/out + // size_t cycSize; // out : if (!outBuf_fromCaller) + size_t needWrite_Size; // out + + Byte *outBuf_fromCaller; + size_t outBufSize_fromCaller; + /* (outBufSize_fromCaller >= full_uncompressed_size_of_all_frames) is required + for success decoding. + If outBufSize_fromCaller < full_uncompressed_size_of_all_frames), + decoding can give error message, because we decode per block basis. + */ + + // size_t outStep; + UInt64 outSize; // total in all frames + UInt64 outProcessed; // out decoded in all frames (it can be >= outSize) + + CZstdDecInfo info; +} CZstdDecState; + +void ZstdDecState_Clear(CZstdDecState *p); + +/* +ZstdDec_Decode() +return: + SZ_OK - no error + SZ_ERROR_DATA - Data Error + SZ_ERROR_MEM - Memory allocation error + SZ_ERROR_UNSUPPORTED - Unsupported method or method properties + SZ_ERROR_CRC - XXH hash Error + // SZ_ERROR_ARCHIVE - Headers error (not used now) +*/ +SRes ZstdDec_Decode(CZstdDecHandle dec, CZstdDecState *p); + +/* +ZstdDec_ReadUnusedFromInBuf(): +returns: the number of bytes that were read from InBuf +(*afterDecoding_tempPos) must be set to zero before first call of ZstdDec_ReadUnusedFromInBuf() +*/ +size_t ZstdDec_ReadUnusedFromInBuf( + CZstdDecHandle dec, + size_t afterDecoding_tempPos, // in/out + void *data, size_t size); + +typedef struct +{ + SRes decode_SRes; // error code of data decoding + Byte is_NonFinishedFrame; // there is unfinished decoding for data frame or skip frame + Byte extraSize; +} CZstdDecResInfo; + +/* +#define ZstdDecResInfo_CLEAR(p) \ +{ (p)->decode_SRes = 0; \ + (p)->is_NonFinishedFrame; \ + (p)->extraSize = 0; \ +} +// memset(p, 0, sizeof(*p)); +*/ + +/* +additional error codes for CZstdDecResInfo::decode_SRes: + SZ_ERROR_NO_ARCHIVE - is not zstd stream (no frames) + SZ_ERROR_INPUT_EOF - need more data in input stream +*/ +void ZstdDec_GetResInfo(const CZstdDec *dec, + const CZstdDecState *p, + SRes res, // it's result from ZstdDec_Decode() + CZstdDecResInfo *info); + +EXTERN_C_END + +#endif diff --git a/src/plugins/7zip/7za/c/var_clang.mak b/src/plugins/7zip/7za/c/var_clang.mak new file mode 100644 index 00000000..a6df26e7 --- /dev/null +++ b/src/plugins/7zip/7za/c/var_clang.mak @@ -0,0 +1,11 @@ +PLATFORM= +O=b/c +IS_X64= +IS_X86= +IS_ARM64= +CROSS_COMPILE= +MY_ARCH= +USE_ASM= +CC=$(CROSS_COMPILE)clang +CXX=$(CROSS_COMPILE)clang++ +USE_CLANG=1 diff --git a/src/plugins/7zip/7za/c/var_clang_arm64.mak b/src/plugins/7zip/7za/c/var_clang_arm64.mak new file mode 100644 index 00000000..971101a0 --- /dev/null +++ b/src/plugins/7zip/7za/c/var_clang_arm64.mak @@ -0,0 +1,12 @@ +PLATFORM=arm64 +O=b/c_$(PLATFORM) +IS_X64= +IS_X86= +IS_ARM64=1 +CROSS_COMPILE= +MY_ARCH= +USE_ASM=1 +ASM_FLAGS=-Wno-unused-macros +CC=$(CROSS_COMPILE)clang +CXX=$(CROSS_COMPILE)clang++ +USE_CLANG=1 diff --git a/src/plugins/7zip/7za/c/var_clang_x64.mak b/src/plugins/7zip/7za/c/var_clang_x64.mak new file mode 100644 index 00000000..34e1b49c --- /dev/null +++ b/src/plugins/7zip/7za/c/var_clang_x64.mak @@ -0,0 +1,11 @@ +PLATFORM=x64 +O=b/c_$(PLATFORM) +IS_X64=1 +IS_X86= +IS_ARM64= +CROSS_COMPILE= +MY_ARCH= +USE_ASM=1 +CC=$(CROSS_COMPILE)clang +CXX=$(CROSS_COMPILE)clang++ +USE_CLANG=1 diff --git a/src/plugins/7zip/7za/c/var_clang_x86.mak b/src/plugins/7zip/7za/c/var_clang_x86.mak new file mode 100644 index 00000000..bd2317c2 --- /dev/null +++ b/src/plugins/7zip/7za/c/var_clang_x86.mak @@ -0,0 +1,11 @@ +PLATFORM=x86 +O=b/c_$(PLATFORM) +IS_X64= +IS_X86=1 +IS_ARM64= +CROSS_COMPILE= +MY_ARCH=-m32 +USE_ASM=1 +CC=$(CROSS_COMPILE)clang +CXX=$(CROSS_COMPILE)clang++ +USE_CLANG=1 diff --git a/src/plugins/7zip/7za/c/var_gcc.mak b/src/plugins/7zip/7za/c/var_gcc.mak new file mode 100644 index 00000000..664491cf --- /dev/null +++ b/src/plugins/7zip/7za/c/var_gcc.mak @@ -0,0 +1,12 @@ +PLATFORM= +O=b/g +IS_X64= +IS_X86= +IS_ARM64= +CROSS_COMPILE= +MY_ARCH= +USE_ASM= +CC=$(CROSS_COMPILE)gcc +CXX=$(CROSS_COMPILE)g++ + +# -march=armv8-a+crc+crypto diff --git a/src/plugins/7zip/7za/c/var_gcc_arm64.mak b/src/plugins/7zip/7za/c/var_gcc_arm64.mak new file mode 100644 index 00000000..4bbb687d --- /dev/null +++ b/src/plugins/7zip/7za/c/var_gcc_arm64.mak @@ -0,0 +1,12 @@ +PLATFORM=arm64 +O=b/g_$(PLATFORM) +IS_X64= +IS_X86= +IS_ARM64=1 +CROSS_COMPILE= +MY_ARCH=-mtune=cortex-a53 +USE_ASM=1 +CC=$(CROSS_COMPILE)gcc +CXX=$(CROSS_COMPILE)g++ + +# -march=armv8-a+crc+crypto diff --git a/src/plugins/7zip/7za/c/var_gcc_x64.mak b/src/plugins/7zip/7za/c/var_gcc_x64.mak new file mode 100644 index 00000000..1acf604f --- /dev/null +++ b/src/plugins/7zip/7za/c/var_gcc_x64.mak @@ -0,0 +1,10 @@ +PLATFORM=x64 +O=b/g_$(PLATFORM) +IS_X64=1 +IS_X86= +IS_ARM64= +CROSS_COMPILE= +MY_ARCH= +USE_ASM=1 +CC=$(CROSS_COMPILE)gcc +CXX=$(CROSS_COMPILE)g++ diff --git a/src/plugins/7zip/7za/c/var_gcc_x86.mak b/src/plugins/7zip/7za/c/var_gcc_x86.mak new file mode 100644 index 00000000..f0718ec7 --- /dev/null +++ b/src/plugins/7zip/7za/c/var_gcc_x86.mak @@ -0,0 +1,10 @@ +PLATFORM=x86 +O=b/g_$(PLATFORM) +IS_X64= +IS_X86=1 +IS_ARM64= +CROSS_COMPILE= +MY_ARCH=-m32 +USE_ASM=1 +CC=$(CROSS_COMPILE)gcc +CXX=$(CROSS_COMPILE)g++ diff --git a/src/plugins/7zip/7za/c/var_mac_arm64.mak b/src/plugins/7zip/7za/c/var_mac_arm64.mak new file mode 100644 index 00000000..adf5fa1d --- /dev/null +++ b/src/plugins/7zip/7za/c/var_mac_arm64.mak @@ -0,0 +1,11 @@ +PLATFORM=arm64 +O=b/m_$(PLATFORM) +IS_X64= +IS_X86= +IS_ARM64=1 +CROSS_COMPILE= +MY_ARCH=-arch arm64 +USE_ASM=1 +CC=$(CROSS_COMPILE)clang +CXX=$(CROSS_COMPILE)clang++ +USE_CLANG=1 diff --git a/src/plugins/7zip/7za/c/var_mac_x64.mak b/src/plugins/7zip/7za/c/var_mac_x64.mak new file mode 100644 index 00000000..13d7aa7f --- /dev/null +++ b/src/plugins/7zip/7za/c/var_mac_x64.mak @@ -0,0 +1,11 @@ +PLATFORM=x64 +O=b/m_$(PLATFORM) +IS_X64=1 +IS_X86= +IS_ARM64= +CROSS_COMPILE= +MY_ARCH=-arch x86_64 +USE_ASM= +CC=$(CROSS_COMPILE)clang +CXX=$(CROSS_COMPILE)clang++ +USE_CLANG=1 diff --git a/src/plugins/7zip/7za/c/warn_clang.mak b/src/plugins/7zip/7za/c/warn_clang.mak new file mode 100644 index 00000000..0d6446d2 --- /dev/null +++ b/src/plugins/7zip/7za/c/warn_clang.mak @@ -0,0 +1 @@ +CFLAGS_WARN = -Weverything -Wfatal-errors diff --git a/src/plugins/7zip/7za/c/warn_clang_mac.mak b/src/plugins/7zip/7za/c/warn_clang_mac.mak new file mode 100644 index 00000000..44afc537 --- /dev/null +++ b/src/plugins/7zip/7za/c/warn_clang_mac.mak @@ -0,0 +1 @@ +CFLAGS_WARN = -Weverything -Wfatal-errors -Wno-poison-system-directories diff --git a/src/plugins/7zip/7za/c/warn_gcc.mak b/src/plugins/7zip/7za/c/warn_gcc.mak new file mode 100644 index 00000000..7aab7a44 --- /dev/null +++ b/src/plugins/7zip/7za/c/warn_gcc.mak @@ -0,0 +1,51 @@ +CFLAGS_WARN_GCC_4_5 = \ + +CFLAGS_WARN_GCC_6 = \ + -Waddress \ + -Waggressive-loop-optimizations \ + -Wattributes \ + -Wbool-compare \ + -Wcast-align \ + -Wcomment \ + -Wdiv-by-zero \ + -Wduplicated-cond \ + -Wformat-contains-nul \ + -Winit-self \ + -Wint-to-pointer-cast \ + -Wunused \ + -Wunused-macros \ + +# -Wno-strict-aliasing + +CFLAGS_WARN_GCC_9 = \ + -Waddress \ + -Waddress-of-packed-member \ + -Waggressive-loop-optimizations \ + -Wattributes \ + -Wbool-compare \ + -Wbool-operation \ + -Wcast-align \ + -Wcast-align=strict \ + -Wcomment \ + -Wdangling-else \ + -Wdiv-by-zero \ + -Wduplicated-branches \ + -Wduplicated-cond \ + -Wformat-contains-nul \ + -Wimplicit-fallthrough=5 \ + -Winit-self \ + -Wint-in-bool-context \ + -Wint-to-pointer-cast \ + -Wunused \ + -Wunused-macros \ + -Wconversion \ + +# -Wno-sign-conversion \ + +CFLAGS_WARN_GCC_PPMD_UNALIGNED = \ + -Wno-strict-aliasing \ + + +CFLAGS_WARN = $(CFLAGS_WARN_GCC_9) \ + +# $(CFLAGS_WARN_GCC_PPMD_UNALIGNED) diff --git a/src/plugins/7zip/7za/cpp/7zip/7zip.mak b/src/plugins/7zip/7za/cpp/7zip/7zip.mak new file mode 100644 index 00000000..8bf7e73e --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/7zip.mak @@ -0,0 +1,240 @@ +OBJS = \ + $O\StdAfx.obj \ + $(CURRENT_OBJS) \ + $(COMMON_OBJS) \ + $(WIN_OBJS) \ + $(WIN_CTRL_OBJS) \ + $(7ZIP_COMMON_OBJS) \ + $(UI_COMMON_OBJS) \ + $(AGENT_OBJS) \ + $(CONSOLE_OBJS) \ + $(EXPLORER_OBJS) \ + $(FM_OBJS) \ + $(GUI_OBJS) \ + $(AR_COMMON_OBJS) \ + $(AR_OBJS) \ + $(7Z_OBJS) \ + $(CAB_OBJS) \ + $(CHM_OBJS) \ + $(COM_OBJS) \ + $(ISO_OBJS) \ + $(NSIS_OBJS) \ + $(RAR_OBJS) \ + $(TAR_OBJS) \ + $(UDF_OBJS) \ + $(WIM_OBJS) \ + $(ZIP_OBJS) \ + $(COMPRESS_OBJS) \ + $(CRYPTO_OBJS) \ + $(C_OBJS) \ + $(ASM_OBJS) \ + $O\resource.res \ + +!include "../../../Build.mak" + +# MAK_SINGLE_FILE = 1 + +!IFDEF MAK_SINGLE_FILE + +!IFDEF CURRENT_OBJS +$(CURRENT_OBJS): ./$(*B).cpp + $(COMPL) +!ENDIF + + +!IFDEF COMMON_OBJS +$(COMMON_OBJS): ../../../Common/$(*B).cpp + $(COMPL) +!ENDIF + +!IFDEF WIN_OBJS +$(WIN_OBJS): ../../../Windows/$(*B).cpp + $(COMPL) +!ENDIF + +!IFDEF WIN_CTRL_OBJS +$(WIN_CTRL_OBJS): ../../../Windows/Control/$(*B).cpp + $(COMPL) +!ENDIF + +!IFDEF 7ZIP_COMMON_OBJS +$(7ZIP_COMMON_OBJS): ../../Common/$(*B).cpp + $(COMPL) +!ENDIF + +!IFDEF AR_OBJS +$(AR_OBJS): ../../Archive/$(*B).cpp + $(COMPL) +!ENDIF + +!IFDEF AR_COMMON_OBJS +$(AR_COMMON_OBJS): ../../Archive/Common/$(*B).cpp + $(COMPL) +!ENDIF + +!IFDEF 7Z_OBJS +$(7Z_OBJS): ../../Archive/7z/$(*B).cpp + $(COMPL) +!ENDIF + +!IFDEF CAB_OBJS +$(CAB_OBJS): ../../Archive/Cab/$(*B).cpp + $(COMPL) +!ENDIF + +!IFDEF CHM_OBJS +$(CHM_OBJS): ../../Archive/Chm/$(*B).cpp + $(COMPL) +!ENDIF + +!IFDEF COM_OBJS +$(COM_OBJS): ../../Archive/Com/$(*B).cpp + $(COMPL) +!ENDIF + +!IFDEF ISO_OBJS +$(ISO_OBJS): ../../Archive/Iso/$(*B).cpp + $(COMPL) +!ENDIF + +!IFDEF NSIS_OBJS +$(NSIS_OBJS): ../../Archive/Nsis/$(*B).cpp + $(COMPL) +!ENDIF + +!IFDEF RAR_OBJS +$(RAR_OBJS): ../../Archive/Rar/$(*B).cpp + $(COMPL) +!ENDIF + +!IFDEF TAR_OBJS +$(TAR_OBJS): ../../Archive/Tar/$(*B).cpp + $(COMPL) +!ENDIF + +!IFDEF UDF_OBJS +$(UDF_OBJS): ../../Archive/Udf/$(*B).cpp + $(COMPL) +!ENDIF + +!IFDEF WIM_OBJS +$(WIM_OBJS): ../../Archive/Wim/$(*B).cpp + $(COMPL) +!ENDIF + +!IFDEF ZIP_OBJS +$(ZIP_OBJS): ../../Archive/Zip/$(*B).cpp + $(COMPL) $(ZIP_FLAGS) +!ENDIF + +!IFDEF COMPRESS_OBJS +$(COMPRESS_OBJS): ../../Compress/$(*B).cpp + $(COMPL_O2) +!ENDIF + +!IFDEF CRYPTO_OBJS +$(CRYPTO_OBJS): ../../Crypto/$(*B).cpp + $(COMPL_O2) +!ENDIF + +!IFDEF UI_COMMON_OBJS +$(UI_COMMON_OBJS): ../../UI/Common/$(*B).cpp + $(COMPL) +!ENDIF + +!IFDEF AGENT_OBJS +$(AGENT_OBJS): ../../UI/Agent/$(*B).cpp + $(COMPL) +!ENDIF + +!IFDEF CONSOLE_OBJS +$(CONSOLE_OBJS): ../../UI/Console/$(*B).cpp + $(COMPL) $(CONSOLE_VARIANT_FLAGS) +!ENDIF + +!IFDEF EXPLORER_OBJS +$(EXPLORER_OBJS): ../../UI/Explorer/$(*B).cpp + $(COMPL) +!ENDIF + +!IFDEF FM_OBJS +$(FM_OBJS): ../../UI/FileManager/$(*B).cpp + $(COMPL) +!ENDIF + +!IFDEF GUI_OBJS +$(GUI_OBJS): ../../UI/GUI/$(*B).cpp + $(COMPL) +!ENDIF + +!IFDEF C_OBJS +$(C_OBJS): ../../../../C/$(*B).c + $(COMPL_O2) +!ENDIF + + +!ELSE + +{.}.cpp{$O}.obj:: + $(COMPLB) +{../../../Common}.cpp{$O}.obj:: + $(COMPLB) +{../../../Windows}.cpp{$O}.obj:: + $(COMPLB) +{../../../Windows/Control}.cpp{$O}.obj:: + $(COMPLB) +{../../Common}.cpp{$O}.obj:: + $(COMPLB) + +{../../UI/Common}.cpp{$O}.obj:: + $(COMPLB) +{../../UI/Agent}.cpp{$O}.obj:: + $(COMPLB) +{../../UI/Console}.cpp{$O}.obj:: + $(COMPLB) $(CONSOLE_VARIANT_FLAGS) +{../../UI/Explorer}.cpp{$O}.obj:: + $(COMPLB) +{../../UI/FileManager}.cpp{$O}.obj:: + $(COMPLB) +{../../UI/GUI}.cpp{$O}.obj:: + $(COMPLB) + + +{../../Archive}.cpp{$O}.obj:: + $(COMPLB) +{../../Archive/Common}.cpp{$O}.obj:: + $(COMPLB) + +{../../Archive/7z}.cpp{$O}.obj:: + $(COMPLB) +{../../Archive/Cab}.cpp{$O}.obj:: + $(COMPLB) +{../../Archive/Chm}.cpp{$O}.obj:: + $(COMPLB) +{../../Archive/Com}.cpp{$O}.obj:: + $(COMPLB) +{../../Archive/Iso}.cpp{$O}.obj:: + $(COMPLB) +{../../Archive/Nsis}.cpp{$O}.obj:: + $(COMPLB) +{../../Archive/Rar}.cpp{$O}.obj:: + $(COMPLB) +{../../Archive/Tar}.cpp{$O}.obj:: + $(COMPLB) +{../../Archive/Udf}.cpp{$O}.obj:: + $(COMPLB) +{../../Archive/Wim}.cpp{$O}.obj:: + $(COMPLB) +{../../Archive/Zip}.cpp{$O}.obj:: + $(COMPLB) $(ZIP_FLAGS) + +{../../Compress}.cpp{$O}.obj:: + $(COMPLB_O2) +{../../Crypto}.cpp{$O}.obj:: + $(COMPLB_O2) +{../../../../C}.c{$O}.obj:: + $(CCOMPLB) + +!ENDIF + +!include "Asm.mak" diff --git a/src/plugins/7zip/7za/cpp/7zip/7zip_gcc.mak b/src/plugins/7zip/7za/cpp/7zip/7zip_gcc.mak new file mode 100644 index 00000000..a78c0fab --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/7zip_gcc.mak @@ -0,0 +1,1370 @@ +# USE_CLANG=1 +# USE_ASM = 1 +# IS_X64 = 1 +# MY_ARCH = +# USE_ASM= +# USE_JWASM=1 + +MY_ARCH_2 = $(MY_ARCH) + +MY_ASM = asmc +ifdef USE_JWASM +MY_ASM = jwasm +endif + +ifndef RC +RC=windres.exe --target=pe-x86-64 +RC=windres.exe -F pe-i386 +RC=windres.exe +endif + + +PROGPATH = $(O)/$(PROG) +PROGPATH_STATIC = $(O)/$(PROG)s + + +ifneq ($(CC), xlc) +CFLAGS_WARN_WALL = -Werror -Wall -Wextra +endif + +# for object file +# -Wa,-aln=test.s +# -save-temps +FLAGS_BASE = -mbranch-protection=standard -march=armv8.5-a +FLAGS_BASE = -mbranch-protection=standard +FLAGS_BASE = +# FLAGS_BASE = -DZ7_NO_UNICODE + +CFLAGS_BASE_LIST = -c + + +#DEBUG_BUILD=1 + +ifdef DEBUG_BUILD +CFLAGS_DEBUG = -g +else +CFLAGS_DEBUG = -DNDEBUG +ifneq ($(CC), $(CROSS_COMPILE)clang) +LFLAGS_STRIP = -s +endif +endif + +# CFLAGS_BASE_LIST = -S +CFLAGS_BASE = -O2 $(CFLAGS_BASE_LIST) $(CFLAGS_WARN_WALL) $(CFLAGS_WARN) \ + $(CFLAGS_DEBUG) -D_REENTRANT -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE \ + -fPIC + +FLAGS_FLTO = -ffunction-sections +FLAGS_FLTO = -flto +FLAGS_FLTO = $(FLAGS_BASE) +# -DZ7_AFFINITY_DISABLE + + +ifdef SystemDrive +IS_MINGW = 1 +else +ifdef SYSTEMDRIVE +# ifdef OS +IS_MINGW = 1 +endif +endif + +ifdef IS_MINGW +LDFLAGS_STATIC_2 = -static +else +ifndef DEF_FILE +ifndef IS_NOT_STANDALONE +ifndef MY_DYNAMIC_LINK +ifneq ($(CC), clang) +LDFLAGS_STATIC_2 = +# -static +# -static-libstdc++ -static-libgcc +endif +endif +endif +endif +endif + +LDFLAGS_STATIC = $(CFLAGS_DEBUG) $(LDFLAGS_STATIC_2) $(LDFLAGS_STATIC_3) + +ifndef O + ifdef IS_MINGW + O=_o + else + O=_o + endif +endif + + +ifdef DEF_FILE + + +ifdef IS_MINGW +SHARED_EXT=.dll +LDFLAGS = -shared -DEF $(DEF_FILE) $(LDFLAGS_STATIC) +else +SHARED_EXT=.so +LDFLAGS = -shared -fPIC $(LDFLAGS_STATIC) +CC_SHARED=-fPIC +endif + + +else + +LDFLAGS = $(LDFLAGS_STATIC) +# -z force-bti +# -s is not required for clang, do we need it for GCC ??? + +#-static -static-libgcc -static-libstdc++ + +ifdef IS_MINGW +SHARED_EXT=.exe +else +SHARED_EXT= +endif + +endif + + +PROGPATH = $(O)/$(PROG)$(SHARED_EXT) +PROGPATH_STATIC = $(O)/$(PROG)s$(SHARED_EXT) + +ifdef IS_MINGW + +ifdef MSYSTEM +RM = rm -f +MY_MKDIR=mkdir -p +DEL_OBJ_EXE = -$(RM) $(PROGPATH) $(PROGPATH_STATIC) $(OBJS) +LIB_HTMLHELP=-lhtmlhelp +else +RM = del +MY_MKDIR=mkdir +DEL_OBJ_EXE = -$(RM) $(O)\*.o $(O)\$(PROG).exe $(O)\$(PROG).dll +endif + +LIB2_GUI = -lole32 -lgdi32 -lcomctl32 -lcomdlg32 -lshell32 $(LIB_HTMLHELP) +LIB2 = -loleaut32 -luuid -ladvapi32 -luser32 $(LIB2_GUI) + +# v24.00: -DUNICODE and -D_UNICODE are defined in precompilation header files +# CXXFLAGS_EXTRA = -DUNICODE -D_UNICODE +# -Wno-delete-non-virtual-dtor + + +else + +RM = rm -f +MY_MKDIR=mkdir -p +DEL_OBJ_EXE = -$(RM) $(PROGPATH) $(PROGPATH_STATIC) $(OBJS) + +# CFLAGS_BASE := $(CFLAGS_BASE) -DZ7_ST +# CXXFLAGS_EXTRA = -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE + +# LOCAL_LIBS=-lpthread +# LOCAL_LIBS_DLL=$(LOCAL_LIBS) -ldl +LIB2 = -lpthread +LIB2 = -lpthread -ldl + + +endif + + + +CFLAGS = $(MY_ARCH_2) $(LOCAL_FLAGS) $(CFLAGS_BASE2) $(CFLAGS_BASE) $(FLAGS_FLTO) $(CC_SHARED) -o $@ + + +ifdef IS_MINGW + +ifdef IS_X64 +AFLAGS_ABI = -win64 +else +AFLAGS_ABI = -coff -DABI_CDECL +# -DABI_CDECL +# -DABI_LINUX +# -DABI_CDECL +endif +AFLAGS = -nologo $(AFLAGS_ABI) -Fo$(O)/$(basename $(/dev/null && echo -z noexecstack || echo) +endif + +endif + + +LFLAGS_ALL = $(LFLAGS_STRIP) $(MY_ARCH_2) $(LDFLAGS) $(FLAGS_FLTO) $(LD_arch) $(LFLAGS_NOEXECSTACK) $(OBJS) $(MY_LIBS) $(LIB2) + +# -s : GCC : Remove all symbol table and relocation information from the executable. +# -s : CLANG : unsupported +# -s + +$(PROGPATH): $(OBJS) + $(CXX) -o $(PROGPATH) $(LFLAGS_ALL) + +$(PROGPATH_STATIC): $(OBJS) + $(CXX) -static -o $(PROGPATH_STATIC) $(LFLAGS_ALL) + +# -s strips debug sections from executable in GCC + + + + +ifndef NO_DEFAULT_RES +$O/resource.o: resource.rc + $(RC) $(RFLAGS) resource.rc $@ + +# windres.exe : in old version mingw: +# $(RFLAGS) resource.rc $O/resource.o +# windres.exe : in new version mingw: +# $(RC) $(RFLAGS) resource.rc -FO $@ + + +endif + +$O/LzmaAlone.o: LzmaAlone.cpp + $(CXX) $(CXXFLAGS) $< + + +$O/CommandLineParser.o: ../../../Common/CommandLineParser.cpp + $(CXX) $(CXXFLAGS) $< +$O/CRC.o: ../../../Common/CRC.cpp + $(CXX) $(CXXFLAGS) $< + +$O/CrcReg.o: ../../../Common/CrcReg.cpp + $(CXX) $(CXXFLAGS) $< + +$O/DynLimBuf.o: ../../../Common/DynLimBuf.cpp + $(CXX) $(CXXFLAGS) $< +$O/IntToString.o: ../../../Common/IntToString.cpp + $(CXX) $(CXXFLAGS) $< +$O/Lang.o: ../../../Common/Lang.cpp + $(CXX) $(CXXFLAGS) $< +$O/ListFileUtils.o: ../../../Common/ListFileUtils.cpp + $(CXX) $(CXXFLAGS) $< +$O/LzFindPrepare.o: ../../../Common/LzFindPrepare.cpp + $(CXX) $(CXXFLAGS) $< +$O/Md5Reg.o: ../../../Common/Md5Reg.cpp + $(CXX) $(CXXFLAGS) $< +$O/MyMap.o: ../../../Common/MyMap.cpp + $(CXX) $(CXXFLAGS) $< +$O/MyString.o: ../../../Common/MyString.cpp + $(CXX) $(CXXFLAGS) $< +$O/MyVector.o: ../../../Common/MyVector.cpp + $(CXX) $(CXXFLAGS) $< +$O/MyWindows.o: ../../../Common/MyWindows.cpp + $(CXX) $(CXXFLAGS) $< +$O/MyWindows2.o: ../../../Common/MyWindows2.cpp + $(CXX) $(CXXFLAGS) $< +$O/MyXml.o: ../../../Common/MyXml.cpp + $(CXX) $(CXXFLAGS) $< +$O/NewHandler.o: ../../../Common/NewHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/Random.o: ../../../Common/Random.cpp + $(CXX) $(CXXFLAGS) $< +$O/Sha1Prepare.o: ../../../Common/Sha1Prepare.cpp + $(CXX) $(CXXFLAGS) $< +$O/Sha1Reg.o: ../../../Common/Sha1Reg.cpp + $(CXX) $(CXXFLAGS) $< +$O/Sha256Prepare.o: ../../../Common/Sha256Prepare.cpp + $(CXX) $(CXXFLAGS) $< +$O/Sha256Reg.o: ../../../Common/Sha256Reg.cpp + $(CXX) $(CXXFLAGS) $< +$O/Sha3Reg.o: ../../../Common/Sha3Reg.cpp + $(CXX) $(CXXFLAGS) $< +$O/Sha512Prepare.o: ../../../Common/Sha512Prepare.cpp + $(CXX) $(CXXFLAGS) $< +$O/Sha512Reg.o: ../../../Common/Sha512Reg.cpp + $(CXX) $(CXXFLAGS) $< +$O/StdInStream.o: ../../../Common/StdInStream.cpp + $(CXX) $(CXXFLAGS) $< +$O/StdOutStream.o: ../../../Common/StdOutStream.cpp + $(CXX) $(CXXFLAGS) $< +$O/StringConvert.o: ../../../Common/StringConvert.cpp + $(CXX) $(CXXFLAGS) $< +$O/StringToInt.o: ../../../Common/StringToInt.cpp + $(CXX) $(CXXFLAGS) $< +$O/TextConfig.o: ../../../Common/TextConfig.cpp + $(CXX) $(CXXFLAGS) $< +$O/UTFConvert.o: ../../../Common/UTFConvert.cpp + $(CXX) $(CXXFLAGS) $< +$O/Wildcard.o: ../../../Common/Wildcard.cpp + $(CXX) $(CXXFLAGS) $< +$O/XzCrc64Init.o: ../../../Common/XzCrc64Init.cpp + $(CXX) $(CXXFLAGS) $< +$O/XzCrc64Reg.o: ../../../Common/XzCrc64Reg.cpp + $(CXX) $(CXXFLAGS) $< +$O/Xxh64Reg.o: ../../../Common/Xxh64Reg.cpp + $(CXX) $(CXXFLAGS) $< + + + +$O/Clipboard.o: ../../../Windows/Clipboard.cpp + $(CXX) $(CXXFLAGS) $< +$O/COM.o: ../../../Windows/COM.cpp + $(CXX) $(CXXFLAGS) $< +$O/CommonDialog.o: ../../../Windows/CommonDialog.cpp + $(CXX) $(CXXFLAGS) $< +$O/Console.o: ../../../Windows/Console.cpp + $(CXX) $(CXXFLAGS) $< +$O/DLL.o: ../../../Windows/DLL.cpp + $(CXX) $(CXXFLAGS) $< +$O/ErrorMsg.o: ../../../Windows/ErrorMsg.cpp + $(CXX) $(CXXFLAGS) $< +$O/FileDir.o: ../../../Windows/FileDir.cpp + $(CXX) $(CXXFLAGS) $< +$O/FileFind.o: ../../../Windows/FileFind.cpp + $(CXX) $(CXXFLAGS) $< + +$O/FileIO.o: ../../../Windows/FileIO.cpp + $(CXX) $(CXXFLAGS) $< + +$O/FileLink.o: ../../../Windows/FileLink.cpp + $(CXX) $(CXXFLAGS) $< +$O/FileMapping.o: ../../../Windows/FileMapping.cpp + $(CXX) $(CXXFLAGS) $< +$O/FileName.o: ../../../Windows/FileName.cpp + $(CXX) $(CXXFLAGS) $< +$O/FileSystem.o: ../../../Windows/FileSystem.cpp + $(CXX) $(CXXFLAGS) $< +$O/MemoryGlobal.o: ../../../Windows/MemoryGlobal.cpp + $(CXX) $(CXXFLAGS) $< +$O/MemoryLock.o: ../../../Windows/MemoryLock.cpp + $(CXX) $(CXXFLAGS) $< +$O/Menu.o: ../../../Windows/Menu.cpp + $(CXX) $(CXXFLAGS) $< +$O/NationalTime.o: ../../../Windows/NationalTime.cpp + $(CXX) $(CXXFLAGS) $< +$O/Net.o: ../../../Windows/Net.cpp + $(CXX) $(CXXFLAGS) $< +$O/ProcessMessages.o: ../../../Windows/ProcessMessages.cpp + $(CXX) $(CXXFLAGS) $< +$O/ProcessUtils.o: ../../../Windows/ProcessUtils.cpp + $(CXX) $(CXXFLAGS) $< +$O/PropVariant.o: ../../../Windows/PropVariant.cpp + $(CXX) $(CXXFLAGS) $< +$O/PropVariantConv.o: ../../../Windows/PropVariantConv.cpp + $(CXX) $(CXXFLAGS) $< +$O/PropVariantUtils.o: ../../../Windows/PropVariantUtils.cpp + $(CXX) $(CXXFLAGS) $< +$O/Registry.o: ../../../Windows/Registry.cpp + $(CXX) $(CXXFLAGS) $< +$O/ResourceString.o: ../../../Windows/ResourceString.cpp + $(CXX) $(CXXFLAGS) $< +$O/SecurityUtils.o: ../../../Windows/SecurityUtils.cpp + $(CXX) $(CXXFLAGS) $< +$O/Shell.o: ../../../Windows/Shell.cpp + $(CXX) $(CXXFLAGS) $< +$O/Synchronization.o: ../../../Windows/Synchronization.cpp + $(CXX) $(CXXFLAGS) $< +$O/System.o: ../../../Windows/System.cpp + $(CXX) $(CXXFLAGS) $< +$O/SystemInfo.o: ../../../Windows/SystemInfo.cpp + $(CXX) $(CXXFLAGS) $< +$O/TimeUtils.o: ../../../Windows/TimeUtils.cpp + $(CXX) $(CXXFLAGS) $< +$O/Window.o: ../../../Windows/Window.cpp + $(CXX) $(CXXFLAGS) $< + + + +$O/ComboBox.o: ../../../Windows/Control/ComboBox.cpp + $(CXX) $(CXXFLAGS) $< +$O/Dialog.o: ../../../Windows/Control/Dialog.cpp + $(CXX) $(CXXFLAGS) $< +$O/ImageList.o: ../../../Windows/Control/ImageList.cpp + $(CXX) $(CXXFLAGS) $< +$O/ListView.o: ../../../Windows/Control/ListView.cpp + $(CXX) $(CXXFLAGS) $< +$O/PropertyPage.o: ../../../Windows/Control/PropertyPage.cpp + $(CXX) $(CXXFLAGS) $< +$O/Window2.o: ../../../Windows/Control/Window2.cpp + $(CXX) $(CXXFLAGS) $< + + +$O/CreateCoder.o: ../../Common/CreateCoder.cpp + $(CXX) $(CXXFLAGS) $< +$O/CWrappers.o: ../../Common/CWrappers.cpp + $(CXX) $(CXXFLAGS) $< +$O/FilePathAutoRename.o: ../../Common/FilePathAutoRename.cpp + $(CXX) $(CXXFLAGS) $< +$O/FileStreams.o: ../../Common/FileStreams.cpp + $(CXX) $(CXXFLAGS) $< +$O/FilterCoder.o: ../../Common/FilterCoder.cpp + $(CXX) $(CXXFLAGS) $< +$O/InBuffer.o: ../../Common/InBuffer.cpp + $(CXX) $(CXXFLAGS) $< +$O/InOutTempBuffer.o: ../../Common/InOutTempBuffer.cpp + $(CXX) $(CXXFLAGS) $< +$O/LimitedStreams.o: ../../Common/LimitedStreams.cpp + $(CXX) $(CXXFLAGS) $< +$O/LockedStream.o: ../../Common/LockedStream.cpp + $(CXX) $(CXXFLAGS) $< +$O/MemBlocks.o: ../../Common/MemBlocks.cpp + $(CXX) $(CXXFLAGS) $< +$O/MethodId.o: ../../Common/MethodId.cpp + $(CXX) $(CXXFLAGS) $< +$O/MethodProps.o: ../../Common/MethodProps.cpp + $(CXX) $(CXXFLAGS) $< +$O/MultiOutStream.o: ../../Common/MultiOutStream.cpp + $(CXX) $(CXXFLAGS) $< +$O/OffsetStream.o: ../../Common/OffsetStream.cpp + $(CXX) $(CXXFLAGS) $< +$O/OutBuffer.o: ../../Common/OutBuffer.cpp + $(CXX) $(CXXFLAGS) $< +$O/OutMemStream.o: ../../Common/OutMemStream.cpp + $(CXX) $(CXXFLAGS) $< +$O/ProgressMt.o: ../../Common/ProgressMt.cpp + $(CXX) $(CXXFLAGS) $< +$O/ProgressUtils.o: ../../Common/ProgressUtils.cpp + $(CXX) $(CXXFLAGS) $< +$O/PropId.o: ../../Common/PropId.cpp + $(CXX) $(CXXFLAGS) $< +$O/StreamBinder.o: ../../Common/StreamBinder.cpp + $(CXX) $(CXXFLAGS) $< +$O/StreamObjects.o: ../../Common/StreamObjects.cpp + $(CXX) $(CXXFLAGS) $< +$O/StreamUtils.o: ../../Common/StreamUtils.cpp + $(CXX) $(CXXFLAGS) $< +$O/UniqBlocks.o: ../../Common/UniqBlocks.cpp + $(CXX) $(CXXFLAGS) $< +$O/VirtThread.o: ../../Common/VirtThread.cpp + $(CXX) $(CXXFLAGS) $< + + +$O/ApfsHandler.o: ../../Archive/ApfsHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/ApmHandler.o: ../../Archive/ApmHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/ArchiveExports.o: ../../Archive/ArchiveExports.cpp + $(CXX) $(CXXFLAGS) $< +$O/ArHandler.o: ../../Archive/ArHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/ArjHandler.o: ../../Archive/ArjHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/AvbHandler.o: ../../Archive/AvbHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/Base64Handler.o: ../../Archive/Base64Handler.cpp + $(CXX) $(CXXFLAGS) $< +$O/Bz2Handler.o: ../../Archive/Bz2Handler.cpp + $(CXX) $(CXXFLAGS) $< +$O/ComHandler.o: ../../Archive/ComHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/CpioHandler.o: ../../Archive/CpioHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/CramfsHandler.o: ../../Archive/CramfsHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/DeflateProps.o: ../../Archive/DeflateProps.cpp + $(CXX) $(CXXFLAGS) $< +$O/DllExports.o: ../../Archive/DllExports.cpp + $(CXX) $(CXXFLAGS) $< +$O/DllExports2.o: ../../Archive/DllExports2.cpp + $(CXX) $(CXXFLAGS) $< +$O/DmgHandler.o: ../../Archive/DmgHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/ElfHandler.o: ../../Archive/ElfHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/ExtHandler.o: ../../Archive/ExtHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/FatHandler.o: ../../Archive/FatHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/FlvHandler.o: ../../Archive/FlvHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/GptHandler.o: ../../Archive/GptHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/GzHandler.o: ../../Archive/GzHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/HandlerCont.o: ../../Archive/HandlerCont.cpp + $(CXX) $(CXXFLAGS) $< +$O/HfsHandler.o: ../../Archive/HfsHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/IhexHandler.o: ../../Archive/IhexHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/LpHandler.o: ../../Archive/LpHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/LvmHandler.o: ../../Archive/LvmHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/LzhHandler.o: ../../Archive/LzhHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/LzmaHandler.o: ../../Archive/LzmaHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/MachoHandler.o: ../../Archive/MachoHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/MbrHandler.o: ../../Archive/MbrHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/MslzHandler.o: ../../Archive/MslzHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/MubHandler.o: ../../Archive/MubHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/NtfsHandler.o: ../../Archive/NtfsHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/PeHandler.o: ../../Archive/PeHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/PpmdHandler.o: ../../Archive/PpmdHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/QcowHandler.o: ../../Archive/QcowHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/RpmHandler.o: ../../Archive/RpmHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/SparseHandler.o: ../../Archive/SparseHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/SplitHandler.o: ../../Archive/SplitHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/SquashfsHandler.o: ../../Archive/SquashfsHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/SwfHandler.o: ../../Archive/SwfHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/UefiHandler.o: ../../Archive/UefiHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/VdiHandler.o: ../../Archive/VdiHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/VhdHandler.o: ../../Archive/VhdHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/VhdxHandler.o: ../../Archive/VhdxHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/VmdkHandler.o: ../../Archive/VmdkHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/XarHandler.o: ../../Archive/XarHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/XzHandler.o: ../../Archive/XzHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/ZHandler.o: ../../Archive/ZHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/ZstdHandler.o: ../../Archive/ZstdHandler.cpp + $(CXX) $(CXXFLAGS) $< + + +$O/7zCompressionMode.o: ../../Archive/7z/7zCompressionMode.cpp + $(CXX) $(CXXFLAGS) $< +$O/7zDecode.o: ../../Archive/7z/7zDecode.cpp + $(CXX) $(CXXFLAGS) $< +$O/7zEncode.o: ../../Archive/7z/7zEncode.cpp + $(CXX) $(CXXFLAGS) $< +$O/7zExtract.o: ../../Archive/7z/7zExtract.cpp + $(CXX) $(CXXFLAGS) $< +$O/7zFolderInStream.o: ../../Archive/7z/7zFolderInStream.cpp + $(CXX) $(CXXFLAGS) $< +$O/7zHandler.o: ../../Archive/7z/7zHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/7zHandlerOut.o: ../../Archive/7z/7zHandlerOut.cpp + $(CXX) $(CXXFLAGS) $< +$O/7zHeader.o: ../../Archive/7z/7zHeader.cpp + $(CXX) $(CXXFLAGS) $< +$O/7zIn.o: ../../Archive/7z/7zIn.cpp + $(CXX) $(CXXFLAGS) $< +$O/7zOut.o: ../../Archive/7z/7zOut.cpp + $(CXX) $(CXXFLAGS) $< +$O/7zProperties.o: ../../Archive/7z/7zProperties.cpp + $(CXX) $(CXXFLAGS) $< +$O/7zRegister.o: ../../Archive/7z/7zRegister.cpp + $(CXX) $(CXXFLAGS) $< +$O/7zSpecStream.o: ../../Archive/7z/7zSpecStream.cpp + $(CXX) $(CXXFLAGS) $< +$O/7zUpdate.o: ../../Archive/7z/7zUpdate.cpp + $(CXX) $(CXXFLAGS) $< + + +$O/CabBlockInStream.o: ../../Archive/Cab/CabBlockInStream.cpp + $(CXX) $(CXXFLAGS) $< +$O/CabHandler.o: ../../Archive/Cab/CabHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/CabHeader.o: ../../Archive/Cab/CabHeader.cpp + $(CXX) $(CXXFLAGS) $< +$O/CabIn.o: ../../Archive/Cab/CabIn.cpp + $(CXX) $(CXXFLAGS) $< +$O/CabRegister.o: ../../Archive/Cab/CabRegister.cpp + $(CXX) $(CXXFLAGS) $< + + +$O/ChmHandler.o: ../../Archive/Chm/ChmHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/ChmIn.o: ../../Archive/Chm/ChmIn.cpp + $(CXX) $(CXXFLAGS) $< + +$O/IsoHandler.o: ../../Archive/Iso/IsoHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/IsoHeader.o: ../../Archive/Iso/IsoHeader.cpp + $(CXX) $(CXXFLAGS) $< +$O/IsoIn.o: ../../Archive/Iso/IsoIn.cpp + $(CXX) $(CXXFLAGS) $< +$O/IsoRegister.o: ../../Archive/Iso/IsoRegister.cpp + $(CXX) $(CXXFLAGS) $< + +$O/NsisDecode.o: ../../Archive/Nsis/NsisDecode.cpp + $(CXX) $(CXXFLAGS) $< +$O/NsisHandler.o: ../../Archive/Nsis/NsisHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/NsisIn.o: ../../Archive/Nsis/NsisIn.cpp + $(CXX) $(CXXFLAGS) $< +$O/NsisRegister.o: ../../Archive/Nsis/NsisRegister.cpp + $(CXX) $(CXXFLAGS) $< + +$O/Rar5Handler.o: ../../Archive/Rar/Rar5Handler.cpp + $(CXX) $(CXXFLAGS) $< +$O/RarHandler.o: ../../Archive/Rar/RarHandler.cpp + $(CXX) $(CXXFLAGS) $< + +$O/TarHandler.o: ../../Archive/Tar/TarHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/TarHandlerOut.o: ../../Archive/Tar/TarHandlerOut.cpp + $(CXX) $(CXXFLAGS) $< +$O/TarHeader.o: ../../Archive/Tar/TarHeader.cpp + $(CXX) $(CXXFLAGS) $< +$O/TarIn.o: ../../Archive/Tar/TarIn.cpp + $(CXX) $(CXXFLAGS) $< +$O/TarOut.o: ../../Archive/Tar/TarOut.cpp + $(CXX) $(CXXFLAGS) $< +$O/TarRegister.o: ../../Archive/Tar/TarRegister.cpp + $(CXX) $(CXXFLAGS) $< +$O/TarUpdate.o: ../../Archive/Tar/TarUpdate.cpp + $(CXX) $(CXXFLAGS) $< + +$O/UdfHandler.o: ../../Archive/Udf/UdfHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/UdfIn.o: ../../Archive/Udf/UdfIn.cpp + $(CXX) $(CXXFLAGS) $< + + +$O/WimHandler.o: ../../Archive/Wim/WimHandler.cpp + $(CXX) $(CXXFLAGS) $< +$O/WimHandlerOut.o: ../../Archive/Wim/WimHandlerOut.cpp + $(CXX) $(CXXFLAGS) $< +$O/WimIn.o: ../../Archive/Wim/WimIn.cpp + $(CXX) $(CXXFLAGS) $< +$O/WimRegister.o: ../../Archive/Wim/WimRegister.cpp + $(CXX) $(CXXFLAGS) $< + + +$O/ZipAddCommon.o: ../../Archive/Zip/ZipAddCommon.cpp + $(CXX) $(CXXFLAGS) $< +$O/ZipHandler.o: ../../Archive/Zip/ZipHandler.cpp + $(CXX) $(CXXFLAGS) $(ZIP_FLAGS) $< +$O/ZipHandlerOut.o: ../../Archive/Zip/ZipHandlerOut.cpp + $(CXX) $(CXXFLAGS) $< +$O/ZipIn.o: ../../Archive/Zip/ZipIn.cpp + $(CXX) $(CXXFLAGS) $< +$O/ZipItem.o: ../../Archive/Zip/ZipItem.cpp + $(CXX) $(CXXFLAGS) $< +$O/ZipOut.o: ../../Archive/Zip/ZipOut.cpp + $(CXX) $(CXXFLAGS) $< +$O/ZipUpdate.o: ../../Archive/Zip/ZipUpdate.cpp + $(CXX) $(CXXFLAGS) $< +$O/ZipRegister.o: ../../Archive/Zip/ZipRegister.cpp + $(CXX) $(CXXFLAGS) $< + + +$O/Bcj2Coder.o: ../../Compress/Bcj2Coder.cpp + $(CXX) $(CXXFLAGS) $< +$O/Bcj2Register.o: ../../Compress/Bcj2Register.cpp + $(CXX) $(CXXFLAGS) $< +$O/BcjCoder.o: ../../Compress/BcjCoder.cpp + $(CXX) $(CXXFLAGS) $< +$O/BcjRegister.o: ../../Compress/BcjRegister.cpp + $(CXX) $(CXXFLAGS) $< +$O/BitlDecoder.o: ../../Compress/BitlDecoder.cpp + $(CXX) $(CXXFLAGS) $< +$O/BranchMisc.o: ../../Compress/BranchMisc.cpp + $(CXX) $(CXXFLAGS) $< +$O/BranchRegister.o: ../../Compress/BranchRegister.cpp + $(CXX) $(CXXFLAGS) $< +$O/ByteSwap.o: ../../Compress/ByteSwap.cpp + $(CXX) $(CXXFLAGS) $< +$O/BZip2Crc.o: ../../Compress/BZip2Crc.cpp + $(CXX) $(CXXFLAGS) $< +$O/BZip2Decoder.o: ../../Compress/BZip2Decoder.cpp + $(CXX) $(CXXFLAGS) $< +$O/BZip2Encoder.o: ../../Compress/BZip2Encoder.cpp + $(CXX) $(CXXFLAGS) $< +$O/BZip2Register.o: ../../Compress/BZip2Register.cpp + $(CXX) $(CXXFLAGS) $< +$O/CodecExports.o: ../../Compress/CodecExports.cpp + $(CXX) $(CXXFLAGS) $< +$O/CopyCoder.o: ../../Compress/CopyCoder.cpp + $(CXX) $(CXXFLAGS) $< +$O/CopyRegister.o: ../../Compress/CopyRegister.cpp + $(CXX) $(CXXFLAGS) $< +$O/Deflate64Register.o: ../../Compress/Deflate64Register.cpp + $(CXX) $(CXXFLAGS) $< +$O/DeflateDecoder.o: ../../Compress/DeflateDecoder.cpp + $(CXX) $(CXXFLAGS) $< +$O/DeflateEncoder.o: ../../Compress/DeflateEncoder.cpp + $(CXX) $(CXXFLAGS) $< +$O/DeflateRegister.o: ../../Compress/DeflateRegister.cpp + $(CXX) $(CXXFLAGS) $< +$O/DeltaFilter.o: ../../Compress/DeltaFilter.cpp + $(CXX) $(CXXFLAGS) $< +$O/DllExports2Compress.o: ../../Compress/DllExports2Compress.cpp + $(CXX) $(CXXFLAGS) $< +$O/DllExportsCompress.o: ../../Compress/DllExportsCompress.cpp + $(CXX) $(CXXFLAGS) $< +$O/ImplodeDecoder.o: ../../Compress/ImplodeDecoder.cpp + $(CXX) $(CXXFLAGS) $< +$O/ImplodeHuffmanDecoder.o: ../../Compress/ImplodeHuffmanDecoder.cpp + $(CXX) $(CXXFLAGS) $< +$O/LzfseDecoder.o: ../../Compress/LzfseDecoder.cpp + $(CXX) $(CXXFLAGS) $< +$O/LzhDecoder.o: ../../Compress/LzhDecoder.cpp + $(CXX) $(CXXFLAGS) $< +$O/Lzma2Decoder.o: ../../Compress/Lzma2Decoder.cpp + $(CXX) $(CXXFLAGS) $< +$O/Lzma2Encoder.o: ../../Compress/Lzma2Encoder.cpp + $(CXX) $(CXXFLAGS) $< +$O/Lzma2Register.o: ../../Compress/Lzma2Register.cpp + $(CXX) $(CXXFLAGS) $< +$O/LzmaDecoder.o: ../../Compress/LzmaDecoder.cpp + $(CXX) $(CXXFLAGS) $< +$O/LzmaEncoder.o: ../../Compress/LzmaEncoder.cpp + $(CXX) $(CXXFLAGS) $< +$O/LzmaRegister.o: ../../Compress/LzmaRegister.cpp + $(CXX) $(CXXFLAGS) $< +$O/LzmsDecoder.o: ../../Compress/LzmsDecoder.cpp + $(CXX) $(CXXFLAGS) $< +$O/LzOutWindow.o: ../../Compress/LzOutWindow.cpp + $(CXX) $(CXXFLAGS) $< +$O/LzxDecoder.o: ../../Compress/LzxDecoder.cpp + $(CXX) $(CXXFLAGS) $< +$O/PpmdDecoder.o: ../../Compress/PpmdDecoder.cpp + $(CXX) $(CXXFLAGS) $< +$O/PpmdEncoder.o: ../../Compress/PpmdEncoder.cpp + $(CXX) $(CXXFLAGS) $< +$O/PpmdRegister.o: ../../Compress/PpmdRegister.cpp + $(CXX) $(CXXFLAGS) $< +$O/PpmdZip.o: ../../Compress/PpmdZip.cpp + $(CXX) $(CXXFLAGS) $< +$O/QuantumDecoder.o: ../../Compress/QuantumDecoder.cpp + $(CXX) $(CXXFLAGS) $< +$O/Rar1Decoder.o: ../../Compress/Rar1Decoder.cpp + $(CXX) $(CXXFLAGS) $< +$O/Rar2Decoder.o: ../../Compress/Rar2Decoder.cpp + $(CXX) $(CXXFLAGS) $< +$O/Rar3Decoder.o: ../../Compress/Rar3Decoder.cpp + $(CXX) $(CXXFLAGS) $< +$O/Rar3Vm.o: ../../Compress/Rar3Vm.cpp + $(CXX) $(CXXFLAGS) $< +$O/Rar5Decoder.o: ../../Compress/Rar5Decoder.cpp + $(CXX) $(CXXFLAGS) $< +$O/RarCodecsRegister.o: ../../Compress/RarCodecsRegister.cpp + $(CXX) $(CXXFLAGS) $< +$O/ShrinkDecoder.o: ../../Compress/ShrinkDecoder.cpp + $(CXX) $(CXXFLAGS) $< +$O/XpressDecoder.o: ../../Compress/XpressDecoder.cpp + $(CXX) $(CXXFLAGS) $< +$O/XzDecoder.o: ../../Compress/XzDecoder.cpp + $(CXX) $(CXXFLAGS) $< +$O/XzEncoder.o: ../../Compress/XzEncoder.cpp + $(CXX) $(CXXFLAGS) $< +$O/ZDecoder.o: ../../Compress/ZDecoder.cpp + $(CXX) $(CXXFLAGS) $< +$O/ZlibDecoder.o: ../../Compress/ZlibDecoder.cpp + $(CXX) $(CXXFLAGS) $< +$O/ZlibEncoder.o: ../../Compress/ZlibEncoder.cpp + $(CXX) $(CXXFLAGS) $< +$O/ZstdDecoder.o: ../../Compress/ZstdDecoder.cpp + $(CXX) $(CXXFLAGS) $< +$O/ZstdRegister.o: ../../Compress/ZstdRegister.cpp + $(CXX) $(CXXFLAGS) $< + + +$O/7zAes.o: ../../Crypto/7zAes.cpp + $(CXX) $(CXXFLAGS) $< +$O/7zAesRegister.o: ../../Crypto/7zAesRegister.cpp + $(CXX) $(CXXFLAGS) $< +$O/HmacSha1.o: ../../Crypto/HmacSha1.cpp + $(CXX) $(CXXFLAGS) $< +$O/HmacSha256.o: ../../Crypto/HmacSha256.cpp + $(CXX) $(CXXFLAGS) $< +$O/MyAes.o: ../../Crypto/MyAes.cpp + $(CXX) $(CXXFLAGS) $< +$O/MyAesReg.o: ../../Crypto/MyAesReg.cpp + $(CXX) $(CXXFLAGS) $< +$O/Pbkdf2HmacSha1.o: ../../Crypto/Pbkdf2HmacSha1.cpp + $(CXX) $(CXXFLAGS) $< +$O/RandGen.o: ../../Crypto/RandGen.cpp + $(CXX) $(CXXFLAGS) $< +$O/Rar20Crypto.o: ../../Crypto/Rar20Crypto.cpp + $(CXX) $(CXXFLAGS) $< +$O/Rar5Aes.o: ../../Crypto/Rar5Aes.cpp + $(CXX) $(CXXFLAGS) $< +$O/RarAes.o: ../../Crypto/RarAes.cpp + $(CXX) $(CXXFLAGS) $< +$O/WzAes.o: ../../Crypto/WzAes.cpp + $(CXX) $(CXXFLAGS) $< +$O/ZipCrypto.o: ../../Crypto/ZipCrypto.cpp + $(CXX) $(CXXFLAGS) $< +$O/ZipStrong.o: ../../Crypto/ZipStrong.cpp + $(CXX) $(CXXFLAGS) $< + + + +$O/CoderMixer2.o: ../../Archive/Common/CoderMixer2.cpp + $(CXX) $(CXXFLAGS) $< +$O/DummyOutStream.o: ../../Archive/Common/DummyOutStream.cpp + $(CXX) $(CXXFLAGS) $< +$O/FindSignature.o: ../../Archive/Common/FindSignature.cpp + $(CXX) $(CXXFLAGS) $< +$O/HandlerOut.o: ../../Archive/Common/HandlerOut.cpp + $(CXX) $(CXXFLAGS) $< +$O/InStreamWithCRC.o: ../../Archive/Common/InStreamWithCRC.cpp + $(CXX) $(CXXFLAGS) $< +$O/ItemNameUtils.o: ../../Archive/Common/ItemNameUtils.cpp + $(CXX) $(CXXFLAGS) $< +$O/MultiStream.o: ../../Archive/Common/MultiStream.cpp + $(CXX) $(CXXFLAGS) $< +$O/OutStreamWithCRC.o: ../../Archive/Common/OutStreamWithCRC.cpp + $(CXX) $(CXXFLAGS) $< +$O/OutStreamWithSha1.o: ../../Archive/Common/OutStreamWithSha1.cpp + $(CXX) $(CXXFLAGS) $< +$O/ParseProperties.o: ../../Archive/Common/ParseProperties.cpp + $(CXX) $(CXXFLAGS) $< + + + + +$O/ArchiveCommandLine.o: ../../UI/Common/ArchiveCommandLine.cpp + $(CXX) $(CXXFLAGS) $< +$O/ArchiveExtractCallback.o: ../../UI/Common/ArchiveExtractCallback.cpp + $(CXX) $(CXXFLAGS) $< +$O/ArchiveName.o: ../../UI/Common/ArchiveName.cpp + $(CXX) $(CXXFLAGS) $< +$O/ArchiveOpenCallback.o: ../../UI/Common/ArchiveOpenCallback.cpp + $(CXX) $(CXXFLAGS) $< +$O/Bench.o: ../../UI/Common/Bench.cpp + $(CXX) $(CXXFLAGS) $< +$O/CompressCall.o: ../../UI/Common/CompressCall.cpp + $(CXX) $(CXXFLAGS) $< +$O/CompressCall2.o: ../../UI/Common/CompressCall2.cpp + $(CXX) $(CXXFLAGS) $< +$O/DefaultName.o: ../../UI/Common/DefaultName.cpp + $(CXX) $(CXXFLAGS) $< +$O/EnumDirItems.o: ../../UI/Common/EnumDirItems.cpp + $(CXX) $(CXXFLAGS) $< +$O/Extract.o: ../../UI/Common/Extract.cpp + $(CXX) $(CXXFLAGS) $< +$O/ExtractingFilePath.o: ../../UI/Common/ExtractingFilePath.cpp + $(CXX) $(CXXFLAGS) $< +$O/HashCalc.o: ../../UI/Common/HashCalc.cpp + $(CXX) $(CXXFLAGS) $< +$O/LoadCodecs.o: ../../UI/Common/LoadCodecs.cpp + $(CXX) $(CXXFLAGS) $< +$O/OpenArchive.o: ../../UI/Common/OpenArchive.cpp + $(CXX) $(CXXFLAGS) $< +$O/PropIDUtils.o: ../../UI/Common/PropIDUtils.cpp + $(CXX) $(CXXFLAGS) $< +$O/SetProperties.o: ../../UI/Common/SetProperties.cpp + $(CXX) $(CXXFLAGS) $< +$O/SortUtils.o: ../../UI/Common/SortUtils.cpp + $(CXX) $(CXXFLAGS) $< +$O/TempFiles.o: ../../UI/Common/TempFiles.cpp + $(CXX) $(CXXFLAGS) $< +$O/Update.o: ../../UI/Common/Update.cpp + $(CXX) $(CXXFLAGS) $< +$O/UpdateAction.o: ../../UI/Common/UpdateAction.cpp + $(CXX) $(CXXFLAGS) $< +$O/UpdateCallback.o: ../../UI/Common/UpdateCallback.cpp + $(CXX) $(CXXFLAGS) $< +$O/UpdatePair.o: ../../UI/Common/UpdatePair.cpp + $(CXX) $(CXXFLAGS) $< +$O/UpdateProduce.o: ../../UI/Common/UpdateProduce.cpp + $(CXX) $(CXXFLAGS) $< +$O/WorkDir.o: ../../UI/Common/WorkDir.cpp + $(CXX) $(CXXFLAGS) $< +$O/ZipRegistry.o: ../../UI/Common/ZipRegistry.cpp + $(CXX) $(CXXFLAGS) $< + + +$O/Agent.o: ../../UI/Agent/Agent.cpp + $(CXX) $(CXXFLAGS) $< +$O/AgentOut.o: ../../UI/Agent/AgentOut.cpp + $(CXX) $(CXXFLAGS) $< +$O/AgentProxy.o: ../../UI/Agent/AgentProxy.cpp + $(CXX) $(CXXFLAGS) $< +$O/ArchiveFolder.o: ../../UI/Agent/ArchiveFolder.cpp + $(CXX) $(CXXFLAGS) $< +$O/ArchiveFolderOpen.o: ../../UI/Agent/ArchiveFolderOpen.cpp + $(CXX) $(CXXFLAGS) $< +$O/ArchiveFolderOut.o: ../../UI/Agent/ArchiveFolderOut.cpp + $(CXX) $(CXXFLAGS) $< +$O/UpdateCallbackAgent.o: ../../UI/Agent/UpdateCallbackAgent.cpp + $(CXX) $(CXXFLAGS) $< + + +$O/Client7z.o: ../../UI/Client7z/Client7z.cpp + $(CXX) $(CXXFLAGS) $< + + +$O/BenchCon.o: ../../UI/Console/BenchCon.cpp + $(CXX) $(CXXFLAGS) $< +$O/ConsoleClose.o: ../../UI/Console/ConsoleClose.cpp + $(CXX) $(CXXFLAGS) $< +$O/ExtractCallbackConsole.o: ../../UI/Console/ExtractCallbackConsole.cpp + $(CXX) $(CXXFLAGS) $< +$O/HashCon.o: ../../UI/Console/HashCon.cpp + $(CXX) $(CXXFLAGS) $< +$O/List.o: ../../UI/Console/List.cpp + $(CXX) $(CXXFLAGS) $< +$O/Main.o: ../../UI/Console/Main.cpp ../../../../C/7zVersion.h + $(CXX) $(CXXFLAGS) $(CONSOLE_VARIANT_FLAGS) $(CONSOLE_ASM_FLAGS) $< +$O/MainAr.o: ../../UI/Console/MainAr.cpp + $(CXX) $(CXXFLAGS) $< +$O/OpenCallbackConsole.o: ../../UI/Console/OpenCallbackConsole.cpp + $(CXX) $(CXXFLAGS) $< +$O/PercentPrinter.o: ../../UI/Console/PercentPrinter.cpp + $(CXX) $(CXXFLAGS) $< +$O/UpdateCallbackConsole.o: ../../UI/Console/UpdateCallbackConsole.cpp + $(CXX) $(CXXFLAGS) $< +$O/UserInputUtils.o: ../../UI/Console/UserInputUtils.cpp + $(CXX) $(CXXFLAGS) $< + +$O/BenchmarkDialog.o: ../../UI/GUI/BenchmarkDialog.cpp + $(CXX) $(CXXFLAGS) $< +$O/CompressDialog.o: ../../UI/GUI/CompressDialog.cpp + $(CXX) $(CXXFLAGS) $< +$O/ExtractDialog.o: ../../UI/GUI/ExtractDialog.cpp + $(CXX) $(CXXFLAGS) $< +$O/ExtractGUI.o: ../../UI/GUI/ExtractGUI.cpp + $(CXX) $(CXXFLAGS) $< +$O/GUI.o: ../../UI/GUI/GUI.cpp + $(CXX) $(CXXFLAGS) $< +$O/HashGUI.o: ../../UI/GUI/HashGUI.cpp + $(CXX) $(CXXFLAGS) $< +$O/UpdateCallbackGUI.o: ../../UI/GUI/UpdateCallbackGUI.cpp + $(CXX) $(CXXFLAGS) $< +$O/UpdateCallbackGUI2.o: ../../UI/GUI/UpdateCallbackGUI2.cpp + $(CXX) $(CXXFLAGS) $< +$O/UpdateGUI.o: ../../UI/GUI/UpdateGUI.cpp + $(CXX) $(CXXFLAGS) $< + +$O/MyMessages.o: ../../UI/Explorer/MyMessages.cpp + $(CXX) $(CXXFLAGS) $< +$O/ContextMenu.o: ../../UI/Explorer/ContextMenu.cpp + $(CXX) $(CXXFLAGS) $< +$O/DllExportsExplorer.o: ../../UI/Explorer/DllExportsExplorer.cpp + $(CXX) $(CXXFLAGS) $< +$O/RegistryContextMenu.o: ../../UI/Explorer/RegistryContextMenu.cpp + $(CXX) $(CXXFLAGS) $< + + + +$O/AboutDialog.o: ../../UI/FileManager/AboutDialog.cpp + $(CXX) $(CXXFLAGS) $< +$O/AltStreamsFolder.o: ../../UI/FileManager/AltStreamsFolder.cpp + $(CXX) $(CXXFLAGS) $< +$O/App.o: ../../UI/FileManager/App.cpp + $(CXX) $(CXXFLAGS) $< +$O/BrowseDialog.o: ../../UI/FileManager/BrowseDialog.cpp + $(CXX) $(CXXFLAGS) $< +$O/BrowseDialog2.o: ../../UI/FileManager/BrowseDialog2.cpp + $(CXX) $(CXXFLAGS) $< +$O/ClassDefs.o: ../../UI/FileManager/ClassDefs.cpp + $(CXX) $(CXXFLAGS) $< +$O/ComboDialog.o: ../../UI/FileManager/ComboDialog.cpp + $(CXX) $(CXXFLAGS) $< +$O/CopyDialog.o: ../../UI/FileManager/CopyDialog.cpp + $(CXX) $(CXXFLAGS) $< +$O/EditDialog.o: ../../UI/FileManager/EditDialog.cpp + $(CXX) $(CXXFLAGS) $< +$O/EditPage.o: ../../UI/FileManager/EditPage.cpp + $(CXX) $(CXXFLAGS) $< +$O/EnumFormatEtc.o: ../../UI/FileManager/EnumFormatEtc.cpp + $(CXX) $(CXXFLAGS) $< +$O/ExtractCallback.o: ../../UI/FileManager/ExtractCallback.cpp + $(CXX) $(CXXFLAGS) $< +$O/FileFolderPluginOpen.o: ../../UI/FileManager/FileFolderPluginOpen.cpp + $(CXX) $(CXXFLAGS) $< +$O/FilePlugins.o: ../../UI/FileManager/FilePlugins.cpp + $(CXX) $(CXXFLAGS) $< +$O/FM.o: ../../UI/FileManager/FM.cpp + $(CXX) $(CXXFLAGS) $< +$O/FoldersPage.o: ../../UI/FileManager/FoldersPage.cpp + $(CXX) $(CXXFLAGS) $< +$O/FormatUtils.o: ../../UI/FileManager/FormatUtils.cpp + $(CXX) $(CXXFLAGS) $< +$O/FSDrives.o: ../../UI/FileManager/FSDrives.cpp + $(CXX) $(CXXFLAGS) $< +$O/FSFolder.o: ../../UI/FileManager/FSFolder.cpp + $(CXX) $(CXXFLAGS) $< +$O/FSFolderCopy.o: ../../UI/FileManager/FSFolderCopy.cpp + $(CXX) $(CXXFLAGS) $< +$O/HelpUtils.o: ../../UI/FileManager/HelpUtils.cpp + $(CXX) $(CXXFLAGS) $< +$O/LangPage.o: ../../UI/FileManager/LangPage.cpp + $(CXX) $(CXXFLAGS) $< +$O/LangUtils.o: ../../UI/FileManager/LangUtils.cpp + $(CXX) $(CXXFLAGS) $< +$O/LinkDialog.o: ../../UI/FileManager/LinkDialog.cpp + $(CXX) $(CXXFLAGS) $< +$O/ListViewDialog.o: ../../UI/FileManager/ListViewDialog.cpp + $(CXX) $(CXXFLAGS) $< +$O/MemDialog.o: ../../UI/FileManager/MemDialog.cpp + $(CXX) $(CXXFLAGS) $< +$O/MenuPage.o: ../../UI/FileManager/MenuPage.cpp + $(CXX) $(CXXFLAGS) $< +$O/MessagesDialog.o: ../../UI/FileManager/MessagesDialog.cpp + $(CXX) $(CXXFLAGS) $< +$O/MyLoadMenu.o: ../../UI/FileManager/MyLoadMenu.cpp + $(CXX) $(CXXFLAGS) $< +$O/NetFolder.o: ../../UI/FileManager/NetFolder.cpp + $(CXX) $(CXXFLAGS) $< +$O/OpenCallback.o: ../../UI/FileManager/OpenCallback.cpp + $(CXX) $(CXXFLAGS) $< +$O/OptionsDialog.o: ../../UI/FileManager/OptionsDialog.cpp + $(CXX) $(CXXFLAGS) $< +$O/OverwriteDialog.o: ../../UI/FileManager/OverwriteDialog.cpp + $(CXX) $(CXXFLAGS) $< +$O/Panel.o: ../../UI/FileManager/Panel.cpp + $(CXX) $(CXXFLAGS) $< +$O/PanelCopy.o: ../../UI/FileManager/PanelCopy.cpp + $(CXX) $(CXXFLAGS) $< +$O/PanelCrc.o: ../../UI/FileManager/PanelCrc.cpp + $(CXX) $(CXXFLAGS) $< +$O/PanelDrag.o: ../../UI/FileManager/PanelDrag.cpp + $(CXX) $(CXXFLAGS) $< +$O/PanelFolderChange.o: ../../UI/FileManager/PanelFolderChange.cpp + $(CXX) $(CXXFLAGS) $< +$O/PanelItemOpen.o: ../../UI/FileManager/PanelItemOpen.cpp + $(CXX) $(CXXFLAGS) $< +$O/PanelItems.o: ../../UI/FileManager/PanelItems.cpp + $(CXX) $(CXXFLAGS) $< +$O/PanelKey.o: ../../UI/FileManager/PanelKey.cpp + $(CXX) $(CXXFLAGS) $< +$O/PanelListNotify.o: ../../UI/FileManager/PanelListNotify.cpp + $(CXX) $(CXXFLAGS) $< +$O/PanelMenu.o: ../../UI/FileManager/PanelMenu.cpp + $(CXX) $(CXXFLAGS) $< +$O/PanelOperations.o: ../../UI/FileManager/PanelOperations.cpp + $(CXX) $(CXXFLAGS) $< +$O/PanelSelect.o: ../../UI/FileManager/PanelSelect.cpp + $(CXX) $(CXXFLAGS) $< +$O/PanelSort.o: ../../UI/FileManager/PanelSort.cpp + $(CXX) $(CXXFLAGS) $< +$O/PanelSplitFile.o: ../../UI/FileManager/PanelSplitFile.cpp + $(CXX) $(CXXFLAGS) $< +$O/PasswordDialog.o: ../../UI/FileManager/PasswordDialog.cpp + $(CXX) $(CXXFLAGS) $< +$O/ProgramLocation.o: ../../UI/FileManager/ProgramLocation.cpp + $(CXX) $(CXXFLAGS) $< +$O/ProgressDialog.o: ../../UI/FileManager/ProgressDialog.cpp + $(CXX) $(CXXFLAGS) $< +$O/ProgressDialog2.o: ../../UI/FileManager/ProgressDialog2.cpp + $(CXX) $(CXXFLAGS) $< +$O/PropertyName.o: ../../UI/FileManager/PropertyName.cpp + $(CXX) $(CXXFLAGS) $< +$O/RegistryAssociations.o: ../../UI/FileManager/RegistryAssociations.cpp + $(CXX) $(CXXFLAGS) $< +$O/RegistryPlugins.o: ../../UI/FileManager/RegistryPlugins.cpp + $(CXX) $(CXXFLAGS) $< +$O/RegistryUtils.o: ../../UI/FileManager/RegistryUtils.cpp + $(CXX) $(CXXFLAGS) $< +$O/RootFolder.o: ../../UI/FileManager/RootFolder.cpp + $(CXX) $(CXXFLAGS) $< +$O/SettingsPage.o: ../../UI/FileManager/SettingsPage.cpp + $(CXX) $(CXXFLAGS) $< +$O/SplitDialog.o: ../../UI/FileManager/SplitDialog.cpp + $(CXX) $(CXXFLAGS) $< +$O/SplitUtils.o: ../../UI/FileManager/SplitUtils.cpp + $(CXX) $(CXXFLAGS) $< +$O/StringUtils.o: ../../UI/FileManager/StringUtils.cpp + $(CXX) $(CXXFLAGS) $< +$O/SysIconUtils.o: ../../UI/FileManager/SysIconUtils.cpp + $(CXX) $(CXXFLAGS) $< +$O/SystemPage.o: ../../UI/FileManager/SystemPage.cpp + $(CXX) $(CXXFLAGS) $< +$O/TextPairs.o: ../../UI/FileManager/TextPairs.cpp + $(CXX) $(CXXFLAGS) $< +$O/UpdateCallback100.o: ../../UI/FileManager/UpdateCallback100.cpp + $(CXX) $(CXXFLAGS) $< +$O/VerCtrl.o: ../../UI/FileManager/VerCtrl.cpp + $(CXX) $(CXXFLAGS) $< +$O/ViewSettings.o: ../../UI/FileManager/ViewSettings.cpp + $(CXX) $(CXXFLAGS) $< + +$O/SfxCon.o: ../../Bundles/SFXCon/SfxCon.cpp + $(CXX) $(CXXFLAGS) $< + +$O/$(FILE_IO).o: ../../../$(FILE_IO_2).cpp + $(CXX) $(CXXFLAGS) $< + + + + + + +$O/7zAlloc.o: ../../../../C/7zAlloc.c + $(CC) $(CFLAGS) $< +$O/7zArcIn.o: ../../../../C/7zArcIn.c + $(CC) $(CFLAGS) $< +$O/7zBuf.o: ../../../../C/7zBuf.c + $(CC) $(CFLAGS) $< +$O/7zBuf2.o: ../../../../C/7zBuf2.c + $(CC) $(CFLAGS) $< +$O/7zCrc.o: ../../../../C/7zCrc.c + $(CC) $(CFLAGS) $< +$O/7zDec.o: ../../../../C/7zDec.c + $(CC) $(CFLAGS) $< +$O/7zFile.o: ../../../../C/7zFile.c + $(CC) $(CFLAGS) $< +$O/7zStream.o: ../../../../C/7zStream.c + $(CC) $(CFLAGS) $< +$O/Aes.o: ../../../../C/Aes.c + $(CC) $(CFLAGS) $< +$O/Alloc.o: ../../../../C/Alloc.c + $(CC) $(CFLAGS) $< +$O/Bcj2.o: ../../../../C/Bcj2.c + $(CC) $(CFLAGS) $< +$O/Bcj2Enc.o: ../../../../C/Bcj2Enc.c + $(CC) $(CFLAGS) $< +$O/Blake2s.o: ../../../../C/Blake2s.c + $(CC) $(CFLAGS) $< +$O/Bra.o: ../../../../C/Bra.c + $(CC) $(CFLAGS) $< +$O/Bra86.o: ../../../../C/Bra86.c + $(CC) $(CFLAGS) $< +$O/BraIA64.o: ../../../../C/BraIA64.c + $(CC) $(CFLAGS) $< +$O/BwtSort.o: ../../../../C/BwtSort.c + $(CC) $(CFLAGS) $< + +$O/CpuArch.o: ../../../../C/CpuArch.c + $(CC) $(CFLAGS) $< +$O/Delta.o: ../../../../C/Delta.c + $(CC) $(CFLAGS) $< +$O/DllSecur.o: ../../../../C/DllSecur.c + $(CC) $(CFLAGS) $< +$O/HuffEnc.o: ../../../../C/HuffEnc.c + $(CC) $(CFLAGS) $< +$O/LzFind.o: ../../../../C/LzFind.c + $(CC) $(CFLAGS) $< + +# ifdef MT_FILES +$O/LzFindMt.o: ../../../../C/LzFindMt.c + $(CC) $(CFLAGS) $< + +$O/Threads.o: ../../../../C/Threads.c + $(CC) $(CFLAGS) $< +# endif + +$O/LzmaEnc.o: ../../../../C/LzmaEnc.c + $(CC) $(CFLAGS) $< +$O/Lzma86Dec.o: ../../../../C/Lzma86Dec.c + $(CC) $(CFLAGS) $< +$O/Lzma86Enc.o: ../../../../C/Lzma86Enc.c + $(CC) $(CFLAGS) $< +$O/Lzma2Dec.o: ../../../../C/Lzma2Dec.c + $(CC) $(CFLAGS) $< +$O/Lzma2DecMt.o: ../../../../C/Lzma2DecMt.c + $(CC) $(CFLAGS) $< +$O/Lzma2Enc.o: ../../../../C/Lzma2Enc.c + $(CC) $(CFLAGS) $< +$O/LzmaLib.o: ../../../../C/LzmaLib.c + $(CC) $(CFLAGS) $< +$O/Md5.o: ../../../../C/Md5.c + $(CC) $(CFLAGS) $< +$O/MtCoder.o: ../../../../C/MtCoder.c + $(CC) $(CFLAGS) $< +$O/MtDec.o: ../../../../C/MtDec.c + $(CC) $(CFLAGS) $< +$O/Ppmd7.o: ../../../../C/Ppmd7.c + $(CC) $(CFLAGS) $< +$O/Ppmd7aDec.o: ../../../../C/Ppmd7aDec.c + $(CC) $(CFLAGS) $< +$O/Ppmd7Dec.o: ../../../../C/Ppmd7Dec.c + $(CC) $(CFLAGS) $< +$O/Ppmd7Enc.o: ../../../../C/Ppmd7Enc.c + $(CC) $(CFLAGS) $< +$O/Ppmd8.o: ../../../../C/Ppmd8.c + $(CC) $(CFLAGS) $< +$O/Ppmd8Dec.o: ../../../../C/Ppmd8Dec.c + $(CC) $(CFLAGS) $< +$O/Ppmd8Enc.o: ../../../../C/Ppmd8Enc.c + $(CC) $(CFLAGS) $< +$O/Sha1.o: ../../../../C/Sha1.c + $(CC) $(CFLAGS) $< +$O/Sha256.o: ../../../../C/Sha256.c + $(CC) $(CFLAGS) $< +$O/Sha3.o: ../../../../C/Sha3.c + $(CC) $(CFLAGS) $< +$O/Sha512.o: ../../../../C/Sha512.c + $(CC) $(CFLAGS) $< +$O/Sha512Opt.o: ../../../../C/Sha512Opt.c + $(CC) $(CFLAGS) $< +$O/SwapBytes.o: ../../../../C/SwapBytes.c + $(CC) $(CFLAGS) $< +$O/Xxh64.o: ../../../../C/Xxh64.c + $(CC) $(CFLAGS) $< +$O/Xz.o: ../../../../C/Xz.c + $(CC) $(CFLAGS) $< +$O/XzCrc64.o: ../../../../C/XzCrc64.c + $(CC) $(CFLAGS) $< +$O/XzDec.o: ../../../../C/XzDec.c + $(CC) $(CFLAGS) $< +$O/XzEnc.o: ../../../../C/XzEnc.c + $(CC) $(CFLAGS) $< +$O/XzIn.o: ../../../../C/XzIn.c + $(CC) $(CFLAGS) $< +$O/ZstdDec.o: ../../../../C/ZstdDec.c + $(CC) $(CFLAGS) $< + + +ifdef USE_ASM +ifdef IS_X64 +USE_X86_ASM=1 +USE_X64_ASM=1 +else +ifdef IS_X86 +USE_X86_ASM=1 +endif +endif +endif + +ifdef USE_X86_ASM +$O/7zCrcOpt.o: ../../../../Asm/x86/7zCrcOpt.asm + $(MY_ASM) $(AFLAGS) $< +$O/XzCrc64Opt.o: ../../../../Asm/x86/XzCrc64Opt.asm + $(MY_ASM) $(AFLAGS) $< +$O/Sha1Opt.o: ../../../../Asm/x86/Sha1Opt.asm + $(MY_ASM) $(AFLAGS) $< +$O/Sha256Opt.o: ../../../../Asm/x86/Sha256Opt.asm + $(MY_ASM) $(AFLAGS) $< +$O/Sort.o: ../../../../Asm/x86/Sort.asm + $(MY_ASM) $(AFLAGS) $< + +ifndef USE_JWASM +USE_X86_ASM_AES=1 +endif + +else +$O/7zCrcOpt.o: ../../../../C/7zCrcOpt.c + $(CC) $(CFLAGS) $< +$O/XzCrc64Opt.o: ../../../../C/XzCrc64Opt.c + $(CC) $(CFLAGS) $< +$O/Sha1Opt.o: ../../../../C/Sha1Opt.c + $(CC) $(CFLAGS) $< +$O/Sha256Opt.o: ../../../../C/Sha256Opt.c + $(CC) $(CFLAGS) $< +$O/Sort.o: ../../../../C/Sort.c + $(CC) $(CFLAGS) $< +endif + + +ifdef USE_X86_ASM_AES +$O/AesOpt.o: ../../../../Asm/x86/AesOpt.asm + $(MY_ASM) $(AFLAGS) $< +else +$O/AesOpt.o: ../../../../C/AesOpt.c + $(CC) $(CFLAGS) $< +endif + + +ifdef USE_X64_ASM +$O/LzFindOpt.o: ../../../../Asm/x86/LzFindOpt.asm + $(MY_ASM) $(AFLAGS) $< +else +$O/LzFindOpt.o: ../../../../C/LzFindOpt.c + $(CC) $(CFLAGS) $< +endif + +ifdef USE_LZMA_DEC_ASM + +ifdef IS_X64 +$O/LzmaDecOpt.o: ../../../../Asm/x86/LzmaDecOpt.asm + $(MY_ASM) $(AFLAGS) $< +endif + +ifdef IS_ARM64 +$O/LzmaDecOpt.o: ../../../../Asm/arm64/LzmaDecOpt.S ../../../../Asm/arm64/7zAsm.S + $(CC) $(CFLAGS) $(ASM_FLAGS) $< +endif + +$O/LzmaDec.o: ../../../../C/LzmaDec.c + $(CC) $(CFLAGS) -DZ7_LZMA_DEC_OPT $< + +else + +$O/LzmaDec.o: ../../../../C/LzmaDec.c + $(CC) $(CFLAGS) $< + +endif + + + + +$O/7zMain.o: ../../../../C/Util/7z/7zMain.c + $(CC) $(CFLAGS) $< +$O/LzmaUtil.o: ../../../../C/Util/Lzma/LzmaUtil.c + $(CC) $(CFLAGS) $< + +ifneq ($(CC), xlc) +SHOW_PREDEF=-dM +else +SHOW_PREDEF= -qshowmacros=pre +endif + +predef_cc: + $(CC) $(CFLAGS) -E $(SHOW_PREDEF) ../../../../C/CpuArch.c > predef_cc_log +# $(CC) $(CFLAGS) -E -dM - < /dev/null +predef_cxx: + $(CXX) $(CFLAGS) -E $(SHOW_PREDEF) ../../../Common/CrcReg.cpp > predef_cxx_log + +predef: predef_cc predef_cxx + + +clean: + -$(DEL_OBJ_EXE) diff --git a/src/plugins/7zip/7za/cpp/7zip/Aes.mak b/src/plugins/7zip/7za/cpp/7zip/Aes.mak new file mode 100644 index 00000000..7d8da2d8 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Aes.mak @@ -0,0 +1,10 @@ +C_OBJS = $(C_OBJS) \ + $O\Aes.obj + +!IF defined(USE_C_AES) || "$(PLATFORM)" == "arm" || "$(PLATFORM)" == "arm64" +C_OBJS = $(C_OBJS) \ + $O\AesOpt.obj +!ELSEIF "$(PLATFORM)" != "ia64" && "$(PLATFORM)" != "mips" && "$(PLATFORM)" != "arm" && "$(PLATFORM)" != "arm64" +ASM_OBJS = $(ASM_OBJS) \ + $O\AesOpt.obj +!ENDIF diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7z.dsp b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7z.dsp index ffd28721..20ae33c0 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7z.dsp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7z.dsp @@ -43,7 +43,7 @@ RSC=rc.exe # PROP Ignore_Export_Lib 1 # PROP Target_Dir "" # ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "MY7Z_EXPORTS" /YX /FD /c -# ADD CPP /nologo /Gz /MD /W3 /GX /O1 /I "..\..\..\\" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "MY7Z_EXPORTS" /D "EXTERNAL_CODECS" /Yu"StdAfx.h" /FD /c +# ADD CPP /nologo /Gz /MD /W3 /GX /O1 /I "..\..\..\\" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "MY7Z_EXPORTS" /D "Z7_EXTERNAL_CODECS" /Yu"StdAfx.h" /FD /c # ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 # ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 # ADD BASE RSC /l 0x419 /d "NDEBUG" @@ -53,7 +53,7 @@ BSC32=bscmake.exe # ADD BSC32 /nologo LINK32=link.exe # ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 -# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 /out:"C:\Program Files\7-zip\Formats\7z.dll" /opt:NOWIN98 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 /out:"C:\Util\Formats\7z.dll" /opt:NOWIN98 # SUBTRACT LINK32 /pdb:none !ELSEIF "$(CFG)" == "7z - Win32 Debug" @@ -70,7 +70,7 @@ LINK32=link.exe # PROP Ignore_Export_Lib 1 # PROP Target_Dir "" # ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "MY7Z_EXPORTS" /YX /FD /GZ /c -# ADD CPP /nologo /Gz /MTd /W3 /Gm /GX /ZI /Od /I "..\..\..\\" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "MY7Z_EXPORTS" /D "EXTERNAL_CODECS" /Yu"StdAfx.h" /FD /GZ /c +# ADD CPP /nologo /Gz /MTd /W3 /Gm /GX /ZI /Od /I "..\..\..\\" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "MY7Z_EXPORTS" /D "Z7_EXTERNAL_CODECS" /Yu"StdAfx.h" /FD /GZ /c # ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 # ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 # ADD BASE RSC /l 0x419 /d "_DEBUG" @@ -80,7 +80,7 @@ BSC32=bscmake.exe # ADD BSC32 /nologo LINK32=link.exe # ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /pdbtype:sept -# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /out:"C:\Program Files\7-zip\Formats\7z.dll" /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /out:"C:\Util\Formats\7z.dll" /pdbtype:sept !ENDIF @@ -101,7 +101,11 @@ SOURCE=..\ArchiveExports.cpp # End Source File # Begin Source File -SOURCE=..\DllExports.cpp +SOURCE=..\..\Compress\CodecExports.cpp +# End Source File +# Begin Source File + +SOURCE=..\DllExports2.cpp # End Source File # Begin Source File @@ -514,14 +518,6 @@ SOURCE=..\..\Common\VirtThread.h # PROP Default_Filter "" # Begin Source File -SOURCE=..\..\..\Windows\DLL.cpp -# End Source File -# Begin Source File - -SOURCE=..\..\..\Windows\DLL.h -# End Source File -# Begin Source File - SOURCE=..\..\..\Windows\FileDir.cpp # End Source File # Begin Source File @@ -584,6 +580,14 @@ SOURCE=..\..\..\Windows\System.h SOURCE=..\..\..\Windows\Thread.h # End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\TimeUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\TimeUtils.h +# End Source File # End Group # Begin Group "Compress" @@ -638,5 +642,9 @@ SOURCE=..\..\..\..\C\Threads.c SOURCE=..\..\..\..\C\Threads.h # End Source File # End Group +# Begin Source File + +SOURCE=..\Icons\7z.ico +# End Source File # End Target # End Project diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zCompressionMode.h b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zCompressionMode.h index 8105ff04..ecfee7cf 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zCompressionMode.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zCompressionMode.h @@ -1,7 +1,7 @@ // 7zCompressionMode.h -#ifndef __7Z_COMPRESSION_MODE_H -#define __7Z_COMPRESSION_MODE_H +#ifndef ZIP7_INC_7Z_COMPRESSION_MODE_H +#define ZIP7_INC_7Z_COMPRESSION_MODE_H #include "../../Common/MethodId.h" #include "../../Common/MethodProps.h" @@ -13,7 +13,11 @@ struct CMethodFull: public CMethodProps { CMethodId Id; UInt32 NumStreams; + int CodecIndex; + UInt32 NumThreads; + bool Set_NumThreads; + CMethodFull(): CodecIndex(-1), NumThreads(1), Set_NumThreads(false) {} bool IsSimpleCoder() const { return NumStreams == 1; } }; @@ -48,25 +52,39 @@ struct CCompressionMethodMode bool DefaultMethod_was_Inserted; bool Filter_was_Inserted; + bool PasswordIsDefined; + bool MemoryUsageLimit_WasSet; - #ifndef _7ZIP_ST - UInt32 NumThreads; + #ifndef Z7_ST + bool NumThreads_WasForced; bool MultiThreadMixer; + UInt32 NumThreads; + UInt32 NumThreadGroups; #endif - - bool PasswordIsDefined; - UString Password; + UString Password; // _Wipe + UInt64 MemoryUsageLimit; + bool IsEmpty() const { return (Methods.IsEmpty() && !PasswordIsDefined); } CCompressionMethodMode(): - DefaultMethod_was_Inserted(false), - Filter_was_Inserted(false), - PasswordIsDefined(false) - #ifndef _7ZIP_ST - , NumThreads(1) + DefaultMethod_was_Inserted(false) + , Filter_was_Inserted(false) + , PasswordIsDefined(false) + , MemoryUsageLimit_WasSet(false) + #ifndef Z7_ST + , NumThreads_WasForced(false) , MultiThreadMixer(true) + , NumThreads(1) + , NumThreadGroups(0) #endif + , MemoryUsageLimit((UInt64)1 << 30) {} + +#ifdef Z7_CPP_IS_SUPPORTED_default + CCompressionMethodMode(const CCompressionMethodMode &) = default; + CCompressionMethodMode& operator =(const CCompressionMethodMode &) = default; +#endif + ~CCompressionMethodMode() { Password.Wipe_and_Empty(); } }; }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zDecode.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zDecode.cpp index b0d6dd83..50cbff62 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zDecode.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zDecode.cpp @@ -5,25 +5,23 @@ #include "../../Common/LimitedStreams.h" #include "../../Common/ProgressUtils.h" #include "../../Common/StreamObjects.h" +#include "../../Common/StreamUtils.h" #include "7zDecode.h" namespace NArchive { namespace N7z { -class CDecProgress: - public ICompressProgressInfo, - public CMyUnknownImp -{ +Z7_CLASS_IMP_COM_1( + CDecProgress + , ICompressProgressInfo +) CMyComPtr _progress; public: CDecProgress(ICompressProgressInfo *progress): _progress(progress) {} - - MY_UNKNOWN_IMP1(ICompressProgressInfo) - STDMETHOD(SetRatioInfo)(const UInt64 *inSize, const UInt64 *outSize); }; -STDMETHODIMP CDecProgress::SetRatioInfo(const UInt64 * /* inSize */, const UInt64 *outSize) +Z7_COM7F_IMF(CDecProgress::SetRatioInfo(const UInt64 * /* inSize */, const UInt64 *outSize)) { return _progress->SetRatioInfo(NULL, outSize); } @@ -110,20 +108,23 @@ static bool AreBindInfoExEqual(const CBindInfoEx &a1, const CBindInfoEx &a2) } CDecoder::CDecoder(bool useMixerMT): - _bindInfoPrev_Defined(false), - _useMixerMT(useMixerMT) -{} + _bindInfoPrev_Defined(false) +{ + #if defined(USE_MIXER_ST) && defined(USE_MIXER_MT) + _useMixerMT = useMixerMT; + #else + UNUSED_VAR(useMixerMT) + #endif +} -struct CLockedInStream: - public IUnknown, - public CMyUnknownImp -{ +Z7_CLASS_IMP_COM_0( + CLockedInStream +) +public: CMyComPtr Stream; UInt64 Pos; - MY_UNKNOWN_IMP - #ifdef USE_MIXER_MT NWindows::NSynchronization::CCriticalSection CriticalSection; #endif @@ -132,10 +133,10 @@ struct CLockedInStream: #ifdef USE_MIXER_MT -class CLockedSequentialInStreamMT: - public ISequentialInStream, - public CMyUnknownImp -{ +Z7_CLASS_IMP_COM_1( + CLockedSequentialInStreamMT + , ISequentialInStream +) CLockedInStream *_glob; UInt64 _pos; CMyComPtr _globRef; @@ -146,24 +147,20 @@ class CLockedSequentialInStreamMT: _glob = lockedInStream; _pos = startPos; } - - MY_UNKNOWN_IMP1(ISequentialInStream) - - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); }; -STDMETHODIMP CLockedSequentialInStreamMT::Read(void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CLockedSequentialInStreamMT::Read(void *data, UInt32 size, UInt32 *processedSize)) { NWindows::NSynchronization::CCriticalSectionLock lock(_glob->CriticalSection); if (_pos != _glob->Pos) { - RINOK(_glob->Stream->Seek(_pos, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekSet(_glob->Stream, _pos)) _glob->Pos = _pos; } UInt32 realProcessedSize = 0; - HRESULT res = _glob->Stream->Read(data, size, &realProcessedSize); + const HRESULT res = _glob->Stream->Read(data, size, &realProcessedSize); _pos += realProcessedSize; _glob->Pos = _pos; if (processedSize) @@ -176,10 +173,10 @@ STDMETHODIMP CLockedSequentialInStreamMT::Read(void *data, UInt32 size, UInt32 * #ifdef USE_MIXER_ST -class CLockedSequentialInStreamST: - public ISequentialInStream, - public CMyUnknownImp -{ +Z7_CLASS_IMP_COM_1( + CLockedSequentialInStreamST + , ISequentialInStream +) CLockedInStream *_glob; UInt64 _pos; CMyComPtr _globRef; @@ -190,22 +187,18 @@ class CLockedSequentialInStreamST: _glob = lockedInStream; _pos = startPos; } - - MY_UNKNOWN_IMP1(ISequentialInStream) - - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); }; -STDMETHODIMP CLockedSequentialInStreamST::Read(void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CLockedSequentialInStreamST::Read(void *data, UInt32 size, UInt32 *processedSize)) { if (_pos != _glob->Pos) { - RINOK(_glob->Stream->Seek(_pos, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekSet(_glob->Stream, _pos)) _glob->Pos = _pos; } UInt32 realProcessedSize = 0; - HRESULT res = _glob->Stream->Read(data, size, &realProcessedSize); + const HRESULT res = _glob->Stream->Read(data, size, &realProcessedSize); _pos += realProcessedSize; _glob->Pos = _pos; if (processedSize) @@ -226,19 +219,23 @@ HRESULT CDecoder::Decode( , ISequentialOutStream *outStream , ICompressProgressInfo *compressProgress + , ISequentialInStream ** + #ifdef USE_MIXER_ST + inStreamMainRes + #endif - #ifdef USE_MIXER_ST - inStreamMainRes - #endif + , bool &dataAfterEnd_Error - _7Z_DECODER_CRYPRO_VARS_DECL + Z7_7Z_DECODER_CRYPRO_VARS_DECL - #if !defined(_7ZIP_ST) && !defined(_SFX) - , bool mtMode, UInt32 numThreads + #if !defined(Z7_ST) + , bool mtMode, UInt32 numThreads, UInt64 memUsage #endif ) { + dataAfterEnd_Error = false; + const UInt64 *packPositions = &folders.PackPositions[folders.FoStartPackStreamIndex[folderIndex]]; CFolderEx folderInfo; folders.ParseFolderEx(folderIndex, folderInfo); @@ -264,7 +261,7 @@ HRESULT CDecoder::Decode( We don't need to init isEncrypted and passwordIsDefined We must upgrade them only - #ifndef _NO_CRYPTO + #ifndef Z7_NO_CRYPTO isEncrypted = false; passwordIsDefined = false; #endif @@ -272,6 +269,7 @@ HRESULT CDecoder::Decode( if (!_bindInfoPrev_Defined || !AreBindInfoExEqual(bindInfo, _bindInfoPrev)) { + _bindInfoPrev_Defined = false; _mixerRef.Release(); #ifdef USE_MIXER_MT @@ -295,22 +293,22 @@ HRESULT CDecoder::Decode( #endif } - RINOK(_mixer->SetBindInfo(bindInfo)); + RINOK(_mixer->SetBindInfo(bindInfo)) FOR_VECTOR(i, folderInfo.Coders) { const CCoderInfo &coderInfo = folderInfo.Coders[i]; - #ifndef _SFX + #ifndef Z7_SFX // we don't support RAR codecs here if ((coderInfo.MethodID >> 8) == 0x403) return E_NOTIMPL; #endif CCreatedCoder cod; - RINOK(CreateCoder( + RINOK(CreateCoder_Id( EXTERNAL_CODECS_LOC_VARS - coderInfo.MethodID, false, cod)); + coderInfo.MethodID, false, cod)) if (coderInfo.IsSimpleCoder()) { @@ -328,13 +326,13 @@ HRESULT CDecoder::Decode( // now there is no codec that uses another external codec /* - #ifdef EXTERNAL_CODECS + #ifdef Z7_EXTERNAL_CODECS CMyComPtr setCompressCodecsInfo; decoderUnknown.QueryInterface(IID_ISetCompressCodecsInfo, (void **)&setCompressCodecsInfo); if (setCompressCodecsInfo) { // we must use g_ExternalCodecs also - RINOK(setCompressCodecsInfo->SetCompressCodecsInfo(__externalCodecs->GetCodecs)); + RINOK(setCompressCodecsInfo->SetCompressCodecsInfo(_externalCodecs->GetCodecs)); } #endif */ @@ -344,83 +342,127 @@ HRESULT CDecoder::Decode( _bindInfoPrev_Defined = true; } - _mixer->ReInit(); + RINOK(_mixer->ReInit2()) UInt32 packStreamIndex = 0; UInt32 unpackStreamIndexStart = folders.FoToCoderUnpackSizes[folderIndex]; unsigned i; + #if !defined(Z7_ST) + bool mt_wasUsed = false; + #endif + for (i = 0; i < folderInfo.Coders.Size(); i++) { const CCoderInfo &coderInfo = folderInfo.Coders[i]; IUnknown *decoder = _mixer->GetCoder(i).GetUnknown(); + // now there is no codec that uses another external codec + /* + #ifdef Z7_EXTERNAL_CODECS { - CMyComPtr setDecoderProperties; - decoder->QueryInterface(IID_ICompressSetDecoderProperties2, (void **)&setDecoderProperties); - if (setDecoderProperties) + Z7_DECL_CMyComPtr_QI_FROM(ISetCompressCodecsInfo, + setCompressCodecsInfo, decoder) + if (setCompressCodecsInfo) { - const CByteBuffer &props = coderInfo.Props; - size_t size = props.Size(); - if (size > 0xFFFFFFFF) - return E_NOTIMPL; - HRESULT res = setDecoderProperties->SetDecoderProperties2((const Byte *)props, (UInt32)size); - if (res == E_INVALIDARG) - res = E_NOTIMPL; - RINOK(res); + // we must use g_ExternalCodecs also + RINOK(setCompressCodecsInfo->SetCompressCodecsInfo(_externalCodecs->GetCodecs)) } } + #endif + */ - #if !defined(_7ZIP_ST) && !defined(_SFX) - if (mtMode) + #if !defined(Z7_ST) + if (!mt_wasUsed) { - CMyComPtr setCoderMt; - decoder->QueryInterface(IID_ICompressSetCoderMt, (void **)&setCoderMt); - if (setCoderMt) + if (mtMode) { - RINOK(setCoderMt->SetNumberOfThreads(numThreads)); + Z7_DECL_CMyComPtr_QI_FROM(ICompressSetCoderMt, + setCoderMt, decoder) + if (setCoderMt) + { + mt_wasUsed = true; + RINOK(setCoderMt->SetNumberOfThreads(numThreads)) + } + } + // if (memUsage != 0) + { + Z7_DECL_CMyComPtr_QI_FROM(ICompressSetMemLimit, + setMemLimit, decoder) + if (setMemLimit) + { + mt_wasUsed = true; + RINOK(setMemLimit->SetMemLimit(memUsage)) + } } } #endif - #ifndef _NO_CRYPTO { - CMyComPtr cryptoSetPassword; - decoder->QueryInterface(IID_ICryptoSetPassword, (void **)&cryptoSetPassword); + Z7_DECL_CMyComPtr_QI_FROM( + ICompressSetDecoderProperties2, + setDecoderProperties, decoder) + const CByteBuffer &props = coderInfo.Props; + const UInt32 size32 = (UInt32)props.Size(); + if (props.Size() != size32) + return E_NOTIMPL; + if (setDecoderProperties) + { + HRESULT res = setDecoderProperties->SetDecoderProperties2((const Byte *)props, size32); + if (res == E_INVALIDARG) + res = E_NOTIMPL; + RINOK(res) + } + else if (size32 != 0) + { + // v23: we fail, if decoder doesn't support properties + return E_NOTIMPL; + } + } + + #ifndef Z7_NO_CRYPTO + { + Z7_DECL_CMyComPtr_QI_FROM( + ICryptoSetPassword, + cryptoSetPassword, decoder) if (cryptoSetPassword) { isEncrypted = true; if (!getTextPassword) return E_NOTIMPL; - CMyComBSTR passwordBSTR; - RINOK(getTextPassword->CryptoGetTextPassword(&passwordBSTR)); + CMyComBSTR_Wipe passwordBSTR; + RINOK(getTextPassword->CryptoGetTextPassword(&passwordBSTR)) passwordIsDefined = true; - password.Empty(); + password.Wipe_and_Empty(); size_t len = 0; if (passwordBSTR) { password = passwordBSTR; len = password.Len(); } - CByteBuffer buffer(len * 2); + CByteBuffer_Wipe buffer(len * 2); + const LPCOLESTR psw = passwordBSTR; for (size_t k = 0; k < len; k++) { - wchar_t c = passwordBSTR[k]; + const wchar_t c = psw[k]; ((Byte *)buffer)[k * 2] = (Byte)c; ((Byte *)buffer)[k * 2 + 1] = (Byte)(c >> 8); } - RINOK(cryptoSetPassword->CryptoSetPassword((const Byte *)buffer, (UInt32)buffer.Size())); + RINOK(cryptoSetPassword->CryptoSetPassword((const Byte *)buffer, (UInt32)buffer.Size())) } } #endif + bool finishMode = false; { - CMyComPtr setFinishMode; - decoder->QueryInterface(IID_ICompressSetFinishMode, (void **)&setFinishMode); + Z7_DECL_CMyComPtr_QI_FROM( + ICompressSetFinishMode, + setFinishMode, decoder) if (setFinishMode) { - RINOK(setFinishMode->SetFinishMode(BoolToInt(fullUnpack))); + finishMode = fullUnpack; + RINOK(setFinishMode->SetFinishMode(BoolToUInt(finishMode))) } } @@ -450,7 +492,7 @@ HRESULT CDecoder::Decode( unpackSize : &folders.CoderUnpackSizes[unpackStreamIndexStart + i]; - _mixer->SetCoderInfo(i, unpackSizesPointer, packSizesPointers); + _mixer->SetCoderInfo(i, unpackSizesPointer, packSizesPointers, finishMode); } if (outStream) @@ -460,44 +502,56 @@ HRESULT CDecoder::Decode( CObjectVector< CMyComPtr > inStreams; - CLockedInStream *lockedInStreamSpec = new CLockedInStream; - CMyComPtr lockedInStream = lockedInStreamSpec; + CMyComPtr2_Create lockedInStream; - bool needMtLock = false; + #ifdef USE_MIXER_MT + #ifdef USE_MIXER_ST + bool needMtLock = _useMixerMT; + #endif + #endif if (folderInfo.PackStreams.Size() > 1) { // lockedInStream.Pos = (UInt64)(Int64)-1; - // RINOK(inStream->Seek(0, STREAM_SEEK_CUR, &lockedInStream.Pos)); - RINOK(inStream->Seek(startPos + packPositions[0], STREAM_SEEK_SET, &lockedInStreamSpec->Pos)); - lockedInStreamSpec->Stream = inStream; + // RINOK(InStream_GetPos(inStream, lockedInStream.Pos)) + RINOK(inStream->Seek((Int64)(startPos + packPositions[0]), STREAM_SEEK_SET, &lockedInStream->Pos)) + lockedInStream->Stream = inStream; + #ifdef USE_MIXER_MT #ifdef USE_MIXER_ST - if (_mixer->IsThere_ExternalCoder_in_PackTree(_mixer->MainCoderIndex)) - #endif + /* + For ST-mixer mode: + If parallel input stream reading from pack streams is possible, + we must use MT-lock for packed streams. + Internal decoders in 7-Zip will not read pack streams in parallel in ST-mixer mode. + So we force to needMtLock mode only if there is unknown (external) decoder. + */ + if (!needMtLock && _mixer->IsThere_ExternalCoder_in_PackTree(_mixer->MainCoderIndex)) needMtLock = true; + #endif + #endif } for (unsigned j = 0; j < folderInfo.PackStreams.Size(); j++) { CMyComPtr packStream; - UInt64 packPos = startPos + packPositions[j]; + const UInt64 packPos = startPos + packPositions[j]; if (folderInfo.PackStreams.Size() == 1) { - RINOK(inStream->Seek(packPos, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekSet(inStream, packPos)) packStream = inStream; } else { #ifdef USE_MIXER_MT #ifdef USE_MIXER_ST - if (_useMixerMT || needMtLock) + if (needMtLock) #endif { CLockedSequentialInStreamMT *lockedStreamImpSpec = new CLockedSequentialInStreamMT; packStream = lockedStreamImpSpec; - lockedStreamImpSpec->Init(lockedInStreamSpec, packPos); + lockedStreamImpSpec->Init(lockedInStream.ClsPtr(), packPos); } #ifdef USE_MIXER_ST else @@ -507,7 +561,7 @@ HRESULT CDecoder::Decode( #ifdef USE_MIXER_ST CLockedSequentialInStreamST *lockedStreamImpSpec = new CLockedSequentialInStreamST; packStream = lockedStreamImpSpec; - lockedStreamImpSpec->Init(lockedInStreamSpec, packPos); + lockedStreamImpSpec->Init(lockedInStream.ClsPtr(), packPos); #endif } } @@ -518,7 +572,7 @@ HRESULT CDecoder::Decode( streamSpec->Init(packPositions[j + 1] - packPositions[j]); } - unsigned num = inStreams.Size(); + const unsigned num = inStreams.Size(); CObjArray inStreamPointers(num); for (i = 0; i < num; i++) inStreamPointers[i] = inStreams[i]; @@ -530,7 +584,9 @@ HRESULT CDecoder::Decode( progress2 = new CDecProgress(compressProgress); ISequentialOutStream *outStreamPointer = outStream; - return _mixer->Code(inStreamPointers, &outStreamPointer, progress2 ? (ICompressProgressInfo *)progress2 : compressProgress); + return _mixer->Code(inStreamPointers, &outStreamPointer, + progress2 ? (ICompressProgressInfo *)progress2 : compressProgress, + dataAfterEnd_Error); } #ifdef USE_MIXER_ST diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zDecode.h b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zDecode.h index 5b729f6c..ee9d9c25 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zDecode.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zDecode.h @@ -1,7 +1,7 @@ // 7zDecode.h -#ifndef __7Z_DECODE_H -#define __7Z_DECODE_H +#ifndef ZIP7_INC_7Z_DECODE_H +#define ZIP7_INC_7Z_DECODE_H #include "../Common/CoderMixer2.h" @@ -24,10 +24,13 @@ struct CBindInfoEx: public NCoderMixer2::CBindInfo class CDecoder { bool _bindInfoPrev_Defined; + #ifdef USE_MIXER_ST + #ifdef USE_MIXER_MT + bool _useMixerMT; + #endif + #endif CBindInfoEx _bindInfoPrev; - bool _useMixerMT; - #ifdef USE_MIXER_ST NCoderMixer2::CMixerST *_mixerST; #endif @@ -53,12 +56,14 @@ class CDecoder , ISequentialOutStream *outStream , ICompressProgressInfo *compressProgress + , ISequentialInStream **inStreamMainRes + , bool &dataAfterEnd_Error - _7Z_DECODER_CRYPRO_VARS_DECL + Z7_7Z_DECODER_CRYPRO_VARS_DECL - #if !defined(_7ZIP_ST) && !defined(_SFX) - , bool mtMode, UInt32 numThreads + #if !defined(Z7_ST) + , bool mtMode, UInt32 numThreads, UInt64 memUsage #endif ); }; diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zEncode.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zEncode.cpp index 97e9ad7a..71d1ddb7 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zEncode.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zEncode.cpp @@ -2,6 +2,8 @@ #include "StdAfx.h" +#include "../../../Common/ComTry.h" + #include "../../Common/CreateCoder.h" #include "../../Common/FilterCoder.h" #include "../../Common/LimitedStreams.h" @@ -19,11 +21,11 @@ void CEncoder::InitBindConv() { unsigned numIn = _bindInfo.Coders.Size(); - _SrcIn_to_DestOut.ClearAndSetSize(numIn); - _DestOut_to_SrcIn.ClearAndSetSize(numIn); + SrcIn_to_DestOut.ClearAndSetSize(numIn); + DestOut_to_SrcIn.ClearAndSetSize(numIn); unsigned numOut = _bindInfo.GetNum_Bonds_and_PackStreams(); - _SrcOut_to_DestIn.ClearAndSetSize(numOut); + SrcOut_to_DestIn.ClearAndSetSize(numOut); // _DestIn_to_SrcOut.ClearAndSetSize(numOut); UInt32 destIn = 0; @@ -38,15 +40,15 @@ void CEncoder::InitBindConv() numIn--; numOut -= coder.NumStreams; - _SrcIn_to_DestOut[numIn] = destOut; - _DestOut_to_SrcIn[destOut] = numIn; + SrcIn_to_DestOut[numIn] = destOut; + DestOut_to_SrcIn[destOut] = numIn; destOut++; for (UInt32 j = 0; j < coder.NumStreams; j++, destIn++) { UInt32 index = numOut + j; - _SrcOut_to_DestIn[index] = destIn; + SrcOut_to_DestIn[index] = destIn; // _DestIn_to_SrcOut[destIn] = index; } } @@ -62,8 +64,8 @@ void CEncoder::SetFolder(CFolder &folder) { CBond &fb = folder.Bonds[i]; const NCoderMixer2::CBond &mixerBond = _bindInfo.Bonds[_bindInfo.Bonds.Size() - 1 - i]; - fb.PackIndex = _SrcOut_to_DestIn[mixerBond.PackIndex]; - fb.UnpackIndex = _SrcIn_to_DestOut[mixerBond.UnpackIndex]; + fb.PackIndex = SrcOut_to_DestIn[mixerBond.PackIndex]; + fb.UnpackIndex = SrcIn_to_DestOut[mixerBond.UnpackIndex]; } folder.Coders.SetSize(_bindInfo.Coders.Size()); @@ -81,15 +83,16 @@ void CEncoder::SetFolder(CFolder &folder) folder.PackStreams.SetSize(_bindInfo.PackStreams.Size()); for (i = 0; i < _bindInfo.PackStreams.Size(); i++) - folder.PackStreams[i] = _SrcOut_to_DestIn[_bindInfo.PackStreams[i]]; + folder.PackStreams[i] = SrcOut_to_DestIn[_bindInfo.PackStreams[i]]; } static HRESULT SetCoderProps2(const CProps &props, const UInt64 *dataSizeReduce, IUnknown *coder) { - CMyComPtr setCoderProperties; - coder->QueryInterface(IID_ICompressSetCoderProperties, (void **)&setCoderProperties); + Z7_DECL_CMyComPtr_QI_FROM( + ICompressSetCoderProperties, + setCoderProperties, coder) if (setCoderProperties) return props.SetCoderProps(setCoderProperties, dataSizeReduce); return props.AreThereNonOptionalProps() ? E_INVALIDARG : S_OK; @@ -103,11 +106,11 @@ void CMtEncMultiProgress::Init(ICompressProgressInfo *progress) OutSize = 0; } -STDMETHODIMP CMtEncMultiProgress::SetRatioInfo(const UInt64 *inSize, const UInt64 * /* outSize */) +Z7_COM7F_IMF(CMtEncMultiProgress::SetRatioInfo(const UInt64 *inSize, const UInt64 * /* outSize */)) { UInt64 outSize2; { - #ifndef _7ZIP_ST + #ifndef Z7_ST NWindows::NSynchronization::CCriticalSectionLock lock(CriticalSection); #endif outSize2 = OutSize; @@ -146,7 +149,7 @@ HRESULT CEncoder::CreateMixerCoder( #endif } - RINOK(_mixer->SetBindInfo(_bindInfo)); + RINOK(_mixer->SetBindInfo(_bindInfo)) FOR_VECTOR (m, _options.Methods) { @@ -154,29 +157,46 @@ HRESULT CEncoder::CreateMixerCoder( CCreatedCoder cod; - RINOK(CreateCoder( + if (methodFull.CodecIndex >= 0) + { + RINOK(CreateCoder_Index( + EXTERNAL_CODECS_LOC_VARS + (unsigned)methodFull.CodecIndex, true, cod)) + } + else + { + RINOK(CreateCoder_Id( EXTERNAL_CODECS_LOC_VARS - methodFull.Id, true, cod)); + methodFull.Id, true, cod)) + } - if (cod.NumStreams != methodFull.NumStreams) - return E_FAIL; if (!cod.Coder && !cod.Coder2) + { + return E_NOTIMPL; // unsupported method, if encoder + // return E_FAIL; + } + + if (cod.NumStreams != methodFull.NumStreams) return E_FAIL; CMyComPtr encoderCommon = cod.Coder ? (IUnknown *)cod.Coder : (IUnknown *)cod.Coder2; - #ifndef _7ZIP_ST + #ifndef Z7_ST + if (methodFull.Set_NumThreads) { CMyComPtr setCoderMt; encoderCommon.QueryInterface(IID_ICompressSetCoderMt, &setCoderMt); if (setCoderMt) { - RINOK(setCoderMt->SetNumberOfThreads(_options.NumThreads)); + RINOK(setCoderMt->SetNumberOfThreads( + /* _options.NumThreads */ + methodFull.NumThreads + )) } } #endif - RINOK(SetCoderProps2(methodFull, inSizeForReduce, encoderCommon)); + RINOK(SetCoderProps2(methodFull, inSizeForReduce, encoderCommon)) /* CMyComPtr resetSalt; @@ -189,13 +209,13 @@ HRESULT CEncoder::CreateMixerCoder( // now there is no codec that uses another external codec /* - #ifdef EXTERNAL_CODECS + #ifdef Z7_EXTERNAL_CODECS CMyComPtr setCompressCodecsInfo; encoderCommon.QueryInterface(IID_ISetCompressCodecsInfo, (void **)&setCompressCodecsInfo); if (setCompressCodecsInfo) { // we must use g_ExternalCodecs also - RINOK(setCompressCodecsInfo->SetCompressCodecsInfo(__externalCodecs->GetCodecs)); + RINOK(setCompressCodecsInfo->SetCompressCodecsInfo(_externalCodecs->GetCodecs)); } #endif */ @@ -206,14 +226,14 @@ HRESULT CEncoder::CreateMixerCoder( if (cryptoSetPassword) { const unsigned sizeInBytes = _options.Password.Len() * 2; - CByteBuffer buffer(sizeInBytes); + CByteBuffer_Wipe buffer(sizeInBytes); for (unsigned i = 0; i < _options.Password.Len(); i++) { wchar_t c = _options.Password[i]; ((Byte *)buffer)[i * 2] = (Byte)c; ((Byte *)buffer)[i * 2 + 1] = (Byte)(c >> 8); } - RINOK(cryptoSetPassword->CryptoSetPassword((const Byte *)buffer, (UInt32)sizeInBytes)); + RINOK(cryptoSetPassword->CryptoSetPassword((const Byte *)buffer, (UInt32)sizeInBytes)) } _mixer->AddCoder(cod); @@ -223,117 +243,114 @@ HRESULT CEncoder::CreateMixerCoder( -class CSequentialOutTempBufferImp2: - public ISequentialOutStream, - public CMyUnknownImp -{ - CInOutTempBuffer *_buf; +Z7_CLASS_IMP_COM_1( + CSequentialOutTempBufferImp2 + , ISequentialOutStream +) public: - CMtEncMultiProgress *_mtProgresSpec; + CInOutTempBuffer TempBuffer; + CMtEncMultiProgress *_mtProgressSpec; - CSequentialOutTempBufferImp2(): _buf(0), _mtProgresSpec(NULL) {} - void Init(CInOutTempBuffer *buffer) { _buf = buffer; } - MY_UNKNOWN_IMP1(ISequentialOutStream) - - STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize); + CSequentialOutTempBufferImp2(): _mtProgressSpec(NULL) {} }; -STDMETHODIMP CSequentialOutTempBufferImp2::Write(const void *data, UInt32 size, UInt32 *processed) +Z7_COM7F_IMF(CSequentialOutTempBufferImp2::Write(const void *data, UInt32 size, UInt32 *processed)) { - if (!_buf->Write(data, size)) - { - if (processed) - *processed = 0; - return E_FAIL; - } + COM_TRY_BEGIN + if (processed) + *processed = 0; + RINOK(TempBuffer.Write_HRESULT(data, size)) if (processed) *processed = size; - if (_mtProgresSpec) - _mtProgresSpec->AddOutSize(size); + if (_mtProgressSpec) + _mtProgressSpec->AddOutSize(size); return S_OK; + COM_TRY_END } -class CSequentialOutMtNotify: - public ISequentialOutStream, - public CMyUnknownImp -{ +Z7_CLASS_IMP_COM_1( + CSequentialOutMtNotify + , ISequentialOutStream +) public: CMyComPtr _stream; - CMtEncMultiProgress *_mtProgresSpec; + CMtEncMultiProgress *_mtProgressSpec; - CSequentialOutMtNotify(): _mtProgresSpec(NULL) {} - MY_UNKNOWN_IMP1(ISequentialOutStream) - - STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize); + CSequentialOutMtNotify(): _mtProgressSpec(NULL) {} }; -STDMETHODIMP CSequentialOutMtNotify::Write(const void *data, UInt32 size, UInt32 *processed) +Z7_COM7F_IMF(CSequentialOutMtNotify::Write(const void *data, UInt32 size, UInt32 *processed)) { UInt32 realProcessed = 0; HRESULT res = _stream->Write(data, size, &realProcessed); if (processed) *processed = realProcessed; - if (_mtProgresSpec) - _mtProgresSpec->AddOutSize(size); + if (_mtProgressSpec) + _mtProgressSpec->AddOutSize(size); return res; } +static HRESULT FillProps_from_Coder(IUnknown *coder, CByteBuffer &props) +{ + Z7_DECL_CMyComPtr_QI_FROM( + ICompressWriteCoderProperties, + writeCoderProperties, coder) + if (writeCoderProperties) + { + CMyComPtr2_Create outStreamSpec; + outStreamSpec->Init(); + RINOK(writeCoderProperties->WriteCoderProperties(outStreamSpec)) + outStreamSpec->CopyToBuffer(props); + } + else + props.Free(); + return S_OK; +} -HRESULT CEncoder::Encode( +HRESULT CEncoder::Encode1( DECL_EXTERNAL_CODECS_LOC_VARS ISequentialInStream *inStream, // const UInt64 *inStreamSize, const UInt64 *inSizeForReduce, + UInt64 expectedDataSize, CFolder &folderItem, - CRecordVector &coderUnpackSizes, - UInt64 &unpackSize, + // CRecordVector &coderUnpackSizes, + // UInt64 &unpackSize, ISequentialOutStream *outStream, CRecordVector &packSizes, ICompressProgressInfo *compressProgress) { - RINOK(EncoderConstr()); + RINOK(EncoderConstr()) if (!_mixerRef) { - RINOK(CreateMixerCoder(EXTERNAL_CODECS_LOC_VARS inSizeForReduce)); + RINOK(CreateMixerCoder(EXTERNAL_CODECS_LOC_VARS inSizeForReduce)) } - _mixer->ReInit(); + RINOK(_mixer->ReInit2()) - CMtEncMultiProgress *mtProgressSpec = NULL; - CMyComPtr mtProgress; + CMyComPtr2 mtProgress; + CMyComPtr2 mtOutStreamNotify; - CSequentialOutMtNotify *mtOutStreamNotifySpec = NULL; - CMyComPtr mtOutStreamNotify; - - CObjectVector inOutTempBuffers; - CObjectVector tempBufferSpecs; + CRecordVector tempBufferSpecs; CObjectVector > tempBuffers; - unsigned numMethods = _bindInfo.Coders.Size(); - unsigned i; for (i = 1; i < _bindInfo.PackStreams.Size(); i++) { - CInOutTempBuffer &iotb = inOutTempBuffers.AddNew(); - iotb.Create(); - iotb.InitWriting(); - } - - for (i = 1; i < _bindInfo.PackStreams.Size(); i++) - { - CSequentialOutTempBufferImp2 *tempBufferSpec = new CSequentialOutTempBufferImp2; + CSequentialOutTempBufferImp2 *tempBufferSpec = new CSequentialOutTempBufferImp2(); CMyComPtr tempBuffer = tempBufferSpec; - tempBufferSpec->Init(&inOutTempBuffers[i - 1]); - tempBuffers.Add(tempBuffer); tempBufferSpecs.Add(tempBufferSpec); + tempBuffers.Add(tempBuffer); } + const unsigned numMethods = _bindInfo.Coders.Size(); + for (i = 0; i < numMethods; i++) - _mixer->SetCoderInfo(i, NULL, NULL); + _mixer->SetCoderInfo(i, NULL, NULL, false); /* inStreamSize can be used by BCJ2 to set optimal range of conversion. @@ -346,15 +363,19 @@ HRESULT CEncoder::Encode( */ + /* CSequentialInStreamSizeCount2 *inStreamSizeCountSpec = new CSequentialInStreamSizeCount2; CMyComPtr inStreamSizeCount = inStreamSizeCountSpec; + */ CSequentialOutStreamSizeCount *outStreamSizeCountSpec = NULL; CMyComPtr outStreamSizeCount; - inStreamSizeCountSpec->Init(inStream); + // inStreamSizeCountSpec->Init(inStream); + + // ISequentialInStream *inStreamPointer = inStreamSizeCount; + ISequentialInStream *inStreamPointer = inStream; - ISequentialInStream *inStreamPointer = inStreamSizeCount; CRecordVector outStreamPointers; SetFolder(folderItem); @@ -362,38 +383,66 @@ HRESULT CEncoder::Encode( for (i = 0; i < numMethods; i++) { IUnknown *coder = _mixer->GetCoder(i).GetUnknown(); + /* + { + CEncoder *sfEncoder = NULL; + Z7_DECL_CMyComPtr_QI_FROM( + IGetSfEncoderInternal, + sf, coder) + if (sf) + { + RINOK(sf->GetSfEncoder(&sfEncoder)); + if (!sfEncoder) + return E_FAIL; - CMyComPtr resetInitVector; - coder->QueryInterface(IID_ICryptoResetInitVector, (void **)&resetInitVector); - if (resetInitVector) + } + } + */ + /* + #ifdef Z7_EXTERNAL_CODECS { - resetInitVector->ResetInitVector(); + Z7_DECL_CMyComPtr_QI_FROM( + ISetCompressCodecsInfo, + setCompressCodecsInfo, coder) + if (setCompressCodecsInfo) + { + // we must use g_ExternalCodecs also + RINOK(setCompressCodecsInfo->SetCompressCodecsInfo(_externalCodecs->GetCodecs)) + } } - - CMyComPtr writeCoderProperties; - coder->QueryInterface(IID_ICompressWriteCoderProperties, (void **)&writeCoderProperties); - - CByteBuffer &props = folderItem.Coders[numMethods - 1 - i].Props; - - if (writeCoderProperties) + #endif + */ { - CDynBufSeqOutStream *outStreamSpec = new CDynBufSeqOutStream; - CMyComPtr dynOutStream(outStreamSpec); - outStreamSpec->Init(); - writeCoderProperties->WriteCoderProperties(dynOutStream); - outStreamSpec->CopyToBuffer(props); + Z7_DECL_CMyComPtr_QI_FROM( + ICryptoResetInitVector, + resetInitVector, coder) + if (resetInitVector) + { + RINOK(resetInitVector->ResetInitVector()) + } } - else - props.Free(); + { + Z7_DECL_CMyComPtr_QI_FROM( + ICompressSetCoderPropertiesOpt, + optProps, coder) + if (optProps) + { + const PROPID propID = NCoderPropID::kExpectedDataSize; + NWindows::NCOM::CPropVariant prop = (UInt64)expectedDataSize; + RINOK(optProps->SetCoderPropertiesOpt(&propID, &prop, 1)) + } + } + // we must write properties from coder after ResetInitVector() + RINOK(FillProps_from_Coder(coder, folderItem.Coders[numMethods - 1 - i].Props)) } _mixer->SelectMainCoder(false); - UInt32 mainCoder = _mixer->MainCoderIndex; + const UInt32 mainCoder = _mixer->MainCoderIndex; bool useMtProgress = false; if (!_mixer->Is_PackSize_Correct_for_Coder(mainCoder)) { - #ifdef _7ZIP_ST + #ifdef Z7_ST if (!_mixer->IsThere_ExternalCoder_in_PackTree(mainCoder)) #endif useMtProgress = true; @@ -401,18 +450,16 @@ HRESULT CEncoder::Encode( if (useMtProgress) { - mtProgressSpec = new CMtEncMultiProgress; - mtProgress = mtProgressSpec; - mtProgressSpec->Init(compressProgress); + mtProgress.SetFromCls(new CMtEncMultiProgress); + mtProgress->Init(compressProgress); - mtOutStreamNotifySpec = new CSequentialOutMtNotify; - mtOutStreamNotify = mtOutStreamNotifySpec; - mtOutStreamNotifySpec->_stream = outStream; - mtOutStreamNotifySpec->_mtProgresSpec = mtProgressSpec; + mtOutStreamNotify.SetFromCls(new CSequentialOutMtNotify); + mtOutStreamNotify->_stream = outStream; + mtOutStreamNotify->_mtProgressSpec = mtProgress.ClsPtr(); - FOR_VECTOR(t, tempBufferSpecs) + FOR_VECTOR (t, tempBufferSpecs) { - tempBufferSpecs[t]->_mtProgresSpec = mtProgressSpec; + tempBufferSpecs[t]->_mtProgressSpec = mtProgress.ClsPtr(); } } @@ -421,7 +468,8 @@ HRESULT CEncoder::Encode( { outStreamSizeCountSpec = new CSequentialOutStreamSizeCount; outStreamSizeCount = outStreamSizeCountSpec; - outStreamSizeCountSpec->SetStream(mtOutStreamNotify ? (ISequentialOutStream *)mtOutStreamNotify : outStream); + outStreamSizeCountSpec->SetStream(mtOutStreamNotify.IsDefined() ? + mtOutStreamNotify.Interface() : outStream); outStreamSizeCountSpec->Init(); outStreamPointers.Add(outStreamSizeCount); } @@ -429,38 +477,55 @@ HRESULT CEncoder::Encode( for (i = 1; i < _bindInfo.PackStreams.Size(); i++) outStreamPointers.Add(tempBuffers[i - 1]); + bool dataAfterEnd_Error; + RINOK(_mixer->Code( &inStreamPointer, - &outStreamPointers.Front(), - mtProgress ? (ICompressProgressInfo *)mtProgress : compressProgress)); + outStreamPointers.ConstData(), + mtProgress.IsDefined() ? mtProgress.Interface() : + compressProgress, dataAfterEnd_Error)) if (_bindInfo.PackStreams.Size() != 0) packSizes.Add(outStreamSizeCountSpec->GetSize()); for (i = 1; i < _bindInfo.PackStreams.Size(); i++) { - CInOutTempBuffer &inOutTempBuffer = inOutTempBuffers[i - 1]; - RINOK(inOutTempBuffer.WriteToStream(outStream)); - packSizes.Add(inOutTempBuffer.GetDataSize()); + CInOutTempBuffer &iotb = tempBufferSpecs[i - 1]->TempBuffer; + RINOK(iotb.WriteToStream(outStream)) + packSizes.Add(iotb.GetDataSize()); } - unpackSize = 0; - - for (i = 0; i < _bindInfo.Coders.Size(); i++) + /* Code() in some future codec can change properties. + v23: so we fill properties again after Code() */ + for (i = 0; i < numMethods; i++) + { + IUnknown *coder = _mixer->GetCoder(i).GetUnknown(); + RINOK(FillProps_from_Coder(coder, folderItem.Coders[numMethods - 1 - i].Props)) + } + + return S_OK; +} + + +void CEncoder::Encode_Post( + UInt64 unpackSize, + CRecordVector &coderUnpackSizes) +{ + // unpackSize = 0; + for (unsigned i = 0; i < _bindInfo.Coders.Size(); i++) { - int bond = _bindInfo.FindBond_for_UnpackStream(_DestOut_to_SrcIn[i]); + const int bond = _bindInfo.FindBond_for_UnpackStream(DestOut_to_SrcIn[i]); UInt64 streamSize; if (bond < 0) { - streamSize = inStreamSizeCountSpec->GetSize(); - unpackSize = streamSize; + // streamSize = inStreamSizeCountSpec->GetSize(); + // unpackSize = streamSize; + streamSize = unpackSize; } else - streamSize = _mixer->GetBondStreamSize(bond); + streamSize = _mixer->GetBondStreamSize((unsigned)bond); coderUnpackSizes.Add(streamSize); } - - return S_OK; } @@ -583,17 +648,17 @@ HRESULT CEncoder::EncoderConstr() if (_bindInfo.Coders[ci].NumStreams == 0) break; - UInt32 outIndex = _bindInfo.Coder_to_Stream[ci]; - int bond = _bindInfo.FindBond_for_PackStream(outIndex); + const UInt32 outIndex = _bindInfo.Coder_to_Stream[ci]; + const int bond = _bindInfo.FindBond_for_PackStream(outIndex); if (bond >= 0) { - ci = _bindInfo.Bonds[bond].UnpackIndex; + ci = _bindInfo.Bonds[(unsigned)bond].UnpackIndex; continue; } - int si = _bindInfo.FindStream_in_PackStreams(outIndex); + const int si = _bindInfo.FindStream_in_PackStreams(outIndex); if (si >= 0) - _bindInfo.PackStreams.MoveToFront(si); + _bindInfo.PackStreams.MoveToFront((unsigned)si); break; } } diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zEncode.h b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zEncode.h index f1a9b5ad..849ac63b 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zEncode.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zEncode.h @@ -1,7 +1,7 @@ // 7zEncode.h -#ifndef __7Z_ENCODE_H -#define __7Z_ENCODE_H +#ifndef ZIP7_INC_7Z_ENCODE_H +#define ZIP7_INC_7Z_ENCODE_H #include "7zCompressionMode.h" @@ -12,12 +12,12 @@ namespace NArchive { namespace N7z { -class CMtEncMultiProgress: - public ICompressProgressInfo, - public CMyUnknownImp -{ +Z7_CLASS_IMP_COM_1( + CMtEncMultiProgress, + ICompressProgressInfo +) CMyComPtr _progress; - #ifndef _7ZIP_ST + #ifndef Z7_ST NWindows::NSynchronization::CCriticalSection CriticalSection; #endif @@ -30,18 +30,15 @@ class CMtEncMultiProgress: void AddOutSize(UInt64 addOutSize) { - #ifndef _7ZIP_ST + #ifndef Z7_ST NWindows::NSynchronization::CCriticalSectionLock lock(CriticalSection); #endif OutSize += addOutSize; } - - MY_UNKNOWN_IMP1(ICompressProgressInfo) - - STDMETHOD(SetRatioInfo)(const UInt64 *inSize, const UInt64 *outSize); }; -class CEncoder + +class CEncoder Z7_final MY_UNCOPYABLE { #ifdef USE_MIXER_ST NCoderMixer2::CMixerST *_mixerST; @@ -57,10 +54,10 @@ class CEncoder NCoderMixer2::CBindInfo _bindInfo; CRecordVector _decompressionMethods; - CRecordVector _SrcIn_to_DestOut; - CRecordVector _SrcOut_to_DestIn; - // CRecordVector _DestIn_to_SrcOut; - CRecordVector _DestOut_to_SrcIn; + CRecordVector SrcIn_to_DestOut; + CRecordVector SrcOut_to_DestIn; + // CRecordVector DestIn_to_SrcOut; + CRecordVector DestOut_to_SrcIn; void InitBindConv(); void SetFolder(CFolder &folder); @@ -74,17 +71,23 @@ class CEncoder CEncoder(const CCompressionMethodMode &options); ~CEncoder(); HRESULT EncoderConstr(); - HRESULT Encode( + HRESULT Encode1( DECL_EXTERNAL_CODECS_LOC_VARS ISequentialInStream *inStream, // const UInt64 *inStreamSize, const UInt64 *inSizeForReduce, + UInt64 expectedDataSize, CFolder &folderItem, - CRecordVector &coderUnpackSizes, - UInt64 &unpackSize, + // CRecordVector &coderUnpackSizes, + // UInt64 &unpackSize, ISequentialOutStream *outStream, CRecordVector &packSizes, ICompressProgressInfo *compressProgress); + + void Encode_Post( + UInt64 unpackSize, + CRecordVector &coderUnpackSizes); + }; }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zExtract.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zExtract.cpp index f65ea89c..1c31e1d0 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zExtract.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zExtract.cpp @@ -16,10 +16,11 @@ namespace NArchive { namespace N7z { -class CFolderOutStream: - public ISequentialOutStream, - public CMyUnknownImp -{ +Z7_CLASS_IMP_COM_1( + CFolderOutStream + , ISequentialOutStream + /* , ICompressGetSubStreamSize */ +) CMyComPtr _stream; public: bool TestMode; @@ -31,6 +32,7 @@ class CFolderOutStream: UInt64 _rem; const UInt32 *_indexes; + // unsigned _startIndex; unsigned _numFiles; unsigned _fileIndex; @@ -40,8 +42,6 @@ class CFolderOutStream: HRESULT ProcessEmptyFiles(); public: - MY_UNKNOWN_IMP1(ISequentialOutStream) - const CDbEx *_db; CMyComPtr ExtractCallback; @@ -52,8 +52,6 @@ class CFolderOutStream: CheckCrc(true) {} - STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize); - HRESULT Init(unsigned startIndex, const UInt32 *indexes, unsigned numFiles); HRESULT FlushCorrupted(Int32 callbackOperationResult); @@ -63,6 +61,7 @@ class CFolderOutStream: HRESULT CFolderOutStream::Init(unsigned startIndex, const UInt32 *indexes, unsigned numFiles) { + // _startIndex = startIndex; _fileIndex = startIndex; _indexes = indexes; _numFiles = numFiles; @@ -76,11 +75,10 @@ HRESULT CFolderOutStream::Init(unsigned startIndex, const UInt32 *indexes, unsig HRESULT CFolderOutStream::OpenFile(bool isCorrupted) { const CFileItem &fi = _db->Files[_fileIndex]; - UInt32 nextFileIndex = (_indexes ? *_indexes : _fileIndex); - Int32 askMode = (_fileIndex == nextFileIndex) ? - (TestMode ? - NExtract::NAskMode::kTest : - NExtract::NAskMode::kExtract) : + const UInt32 nextFileIndex = (_indexes ? *_indexes : _fileIndex); + Int32 askMode = (_fileIndex == nextFileIndex) ? TestMode ? + NExtract::NAskMode::kTest : + NExtract::NAskMode::kExtract : NExtract::NAskMode::kSkip; if (isCorrupted @@ -90,7 +88,7 @@ HRESULT CFolderOutStream::OpenFile(bool isCorrupted) askMode = NExtract::NAskMode::kTest; CMyComPtr realOutStream; - RINOK(ExtractCallback->GetStream(_fileIndex, &realOutStream, askMode)); + RINOK(ExtractCallback->GetStream(_fileIndex, &realOutStream, askMode)) _stream = realOutStream; _crc = CRC_INIT_VAL; @@ -136,13 +134,13 @@ HRESULT CFolderOutStream::ProcessEmptyFiles() { while (_numFiles != 0 && _db->Files[_fileIndex].Size == 0) { - RINOK(OpenFile()); - RINOK(CloseFile()); + RINOK(OpenFile()) + RINOK(CloseFile()) } return S_OK; } -STDMETHODIMP CFolderOutStream::Write(const void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CFolderOutStream::Write(const void *data, UInt32 size, UInt32 *processedSize)) { if (processedSize) *processedSize = 0; @@ -152,6 +150,12 @@ STDMETHODIMP CFolderOutStream::Write(const void *data, UInt32 size, UInt32 *proc if (_fileIsOpen) { UInt32 cur = (size < _rem ? size : (UInt32)_rem); + if (_calcCrc) + { + const UInt32 k_Step = (UInt32)1 << 20; + if (cur > k_Step) + cur = k_Step; + } HRESULT result = S_OK; if (_stream) result = _stream->Write(data, cur, &cur); @@ -164,16 +168,16 @@ STDMETHODIMP CFolderOutStream::Write(const void *data, UInt32 size, UInt32 *proc _rem -= cur; if (_rem == 0) { - RINOK(CloseFile()); - RINOK(ProcessEmptyFiles()); + RINOK(CloseFile()) + RINOK(ProcessEmptyFiles()) } - RINOK(result); + RINOK(result) if (cur == 0) break; continue; } - RINOK(ProcessEmptyFiles()); + RINOK(ProcessEmptyFiles()) if (_numFiles == 0) { // we support partial extracting @@ -186,7 +190,7 @@ STDMETHODIMP CFolderOutStream::Write(const void *data, UInt32 size, UInt32 *proc // return S_FALSE; return k_My_HRESULT_WritingWasCut; } - RINOK(OpenFile()); + RINOK(OpenFile()) } return S_OK; @@ -198,19 +202,37 @@ HRESULT CFolderOutStream::FlushCorrupted(Int32 callbackOperationResult) { if (_fileIsOpen) { - RINOK(CloseFile_and_SetResult(callbackOperationResult)); + RINOK(CloseFile_and_SetResult(callbackOperationResult)) } else { - RINOK(OpenFile(true)); + RINOK(OpenFile(true)) } } return S_OK; } -STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, - Int32 testModeSpec, IArchiveExtractCallback *extractCallbackSpec) +/* +Z7_COM7F_IMF(CFolderOutStream::GetSubStreamSize(UInt64 subStream, UInt64 *value)) { + *value = 0; + // const unsigned numFiles_Original = _numFiles + _fileIndex - _startIndex; + const unsigned numFiles_Original = _numFiles; + if (subStream >= numFiles_Original) + return S_FALSE; // E_FAIL; + *value = _db->Files[_startIndex + (unsigned)subStream].Size; + return S_OK; +} +*/ + + +Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testModeSpec, IArchiveExtractCallback *extractCallbackSpec)) +{ + // for GCC + // CFolderOutStream *folderOutStream = new CFolderOutStream; + // CMyComPtr outStream(folderOutStream); + COM_TRY_BEGIN CMyComPtr extractCallback = extractCallbackSpec; @@ -219,7 +241,7 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, // numItems = (UInt32)(Int32)-1; - bool allFilesMode = (numItems == (UInt32)(Int32)-1); + const bool allFilesMode = (numItems == (UInt32)(Int32)-1); if (allFilesMode) numItems = _db.Files.Size(); @@ -234,8 +256,8 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, for (i = 0; i < numItems; i++) { - UInt32 fileIndex = allFilesMode ? i : indices[i]; - CNum folderIndex = _db.FileIndexToFolderIndexMap[fileIndex]; + const UInt32 fileIndex = allFilesMode ? i : indices[i]; + const CNum folderIndex = _db.FileIndexToFolderIndexMap[fileIndex]; if (folderIndex == kNumNoIndex) continue; if (folderIndex != prevFolder || fileIndex < nextFile) @@ -247,10 +269,9 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, } } - RINOK(extractCallback->SetTotal(importantTotalUnpacked)); + RINOK(extractCallback->SetTotal(importantTotalUnpacked)) - CLocalProgress *lps = new CLocalProgress; - CMyComPtr progress = lps; + CMyComPtr2_Create lps; lps->Init(extractCallback, false); CDecoder decoder( @@ -258,8 +279,8 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, false #elif !defined(USE_MIXER_ST) true - #elif !defined(__7Z_SET_PROPERTIES) - #ifdef _7ZIP_ST + #elif !defined(Z7_7Z_SET_PROPERTIES) + #ifdef Z7_ST false #else true @@ -271,8 +292,8 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, UInt64 curPacked, curUnpacked; - CMyComPtr callbackMessage; - extractCallback.QueryInterface(IID_IArchiveExtractCallbackMessage, &callbackMessage); + CMyComPtr callbackMessage; + extractCallback.QueryInterface(IID_IArchiveExtractCallbackMessage2, &callbackMessage); CFolderOutStream *folderOutStream = new CFolderOutStream; CMyComPtr outStream(folderOutStream); @@ -284,7 +305,7 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, for (UInt32 i = 0;; lps->OutSize += curUnpacked, lps->InSize += curPacked) { - RINOK(lps->SetCur()); + RINOK(lps->SetCur()) if (i >= numItems) break; @@ -293,7 +314,7 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, curPacked = 0; UInt32 fileIndex = allFilesMode ? i : indices[i]; - CNum folderIndex = _db.FileIndexToFolderIndexMap[fileIndex]; + const CNum folderIndex = _db.FileIndexToFolderIndexMap[fileIndex]; UInt32 numSolidFiles = 1; @@ -306,7 +327,7 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, for (k = i + 1; k < numItems; k++) { - UInt32 fileIndex2 = allFilesMode ? k : indices[k]; + const UInt32 fileIndex2 = allFilesMode ? k : indices[k]; if (_db.FileIndexToFolderIndexMap[fileIndex2] != folderIndex || fileIndex2 < nextFile) break; @@ -320,20 +341,26 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, } { - HRESULT result = folderOutStream->Init(fileIndex, + const HRESULT result = folderOutStream->Init(fileIndex, allFilesMode ? NULL : indices + i, numSolidFiles); i += numSolidFiles; - RINOK(result); + RINOK(result) } - // to test solid block with zero unpacked size we disable that code if (folderOutStream->WasWritingFinished()) + { + // for debug: to test zero size stream unpacking + // if (folderIndex == kNumNoIndex) // enable this check for debug continue; + } + + if (folderIndex == kNumNoIndex) + return E_FAIL; - #ifndef _NO_CRYPTO + #ifndef Z7_NO_CRYPTO CMyComPtr getTextPassword; if (extractCallback) extractCallback.QueryInterface(IID_ICryptoGetTextPassword, &getTextPassword); @@ -341,14 +368,15 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, try { - #ifndef _NO_CRYPTO + #ifndef Z7_NO_CRYPTO bool isEncrypted = false; bool passwordIsDefined = false; - UString password; + UString_Wipe password; #endif + bool dataAfterEnd_Error = false; - HRESULT result = decoder.Decode( + const HRESULT result = decoder.Decode( EXTERNAL_CODECS_VARS _inStream, _db.ArcInfo.DataStartPosition, @@ -356,51 +384,55 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, &curUnpacked, outStream, - progress, + lps, NULL // *inStreamMainRes + , dataAfterEnd_Error - _7Z_DECODER_CRYPRO_VARS - #if !defined(_7ZIP_ST) && !defined(_SFX) - , true, _numThreads + Z7_7Z_DECODER_CRYPRO_VARS + #if !defined(Z7_ST) + , true, _numThreads, _memUsage_Decompress #endif ); - if (result == S_FALSE || result == E_NOTIMPL) + if (result == S_FALSE || result == E_NOTIMPL || dataAfterEnd_Error) { - bool wasFinished = folderOutStream->WasWritingFinished(); - - int resOp = (result == S_FALSE ? - NExtract::NOperationResult::kDataError : - NExtract::NOperationResult::kUnsupportedMethod); + const bool wasFinished = folderOutStream->WasWritingFinished(); - RINOK(folderOutStream->FlushCorrupted(resOp)); + int resOp = NExtract::NOperationResult::kDataError; + + if (result != S_FALSE) + { + if (result == E_NOTIMPL) + resOp = NExtract::NOperationResult::kUnsupportedMethod; + else if (wasFinished && dataAfterEnd_Error) + resOp = NExtract::NOperationResult::kDataAfterEnd; + } + + RINOK(folderOutStream->FlushCorrupted(resOp)) if (wasFinished) { // we don't show error, if it's after required files if (/* !folderOutStream->ExtraWriteWasCut && */ callbackMessage) { - RINOK(callbackMessage->ReportExtractResult(NEventIndexType::kBlockIndex, folderIndex, resOp)); + RINOK(callbackMessage->ReportExtractResult(NEventIndexType::kBlockIndex, folderIndex, resOp)) } - continue; } - // JRY FIX: exit with error when incorrect password is used - // Otherwise Salamander doesn't report any error and can delete source 7z archive (an Unpack dialog option). - // Note: it looks like this problem is solved in 7-Zip 19.0 source code. - return result; + continue; } if (result != S_OK) return result; - RINOK(folderOutStream->FlushCorrupted(NExtract::NOperationResult::kDataError)); + RINOK(folderOutStream->FlushCorrupted(NExtract::NOperationResult::kDataError)) continue; } catch(...) { - RINOK(folderOutStream->FlushCorrupted(NExtract::NOperationResult::kDataError)); + RINOK(folderOutStream->FlushCorrupted(NExtract::NOperationResult::kDataError)) // continue; - return E_FAIL; + // return E_FAIL; + throw; } } diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zFolderInStream.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zFolderInStream.cpp index 14cdc436..08231893 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zFolderInStream.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zFolderInStream.cpp @@ -2,6 +2,8 @@ #include "StdAfx.h" +#include "../../../Windows/TimeUtils.h" + #include "7zFolderInStream.h" namespace NArchive { @@ -13,66 +15,129 @@ void CFolderInStream::Init(IArchiveUpdateCallback *updateCallback, _updateCallback = updateCallback; _indexes = indexes; _numFiles = numFiles; - _index = 0; + + _totalSize_for_Coder = 0; + ClearFileInfo(); Processed.ClearAndReserve(numFiles); - CRCs.ClearAndReserve(numFiles); Sizes.ClearAndReserve(numFiles); - - _pos = 0; - _crc = CRC_INIT_VAL; - _size_Defined = false; - _size = 0; + CRCs.ClearAndReserve(numFiles); + TimesDefined.ClearAndReserve(numFiles); + MTimes.ClearAndReserve(Need_MTime ? numFiles : (unsigned)0); + CTimes.ClearAndReserve(Need_CTime ? numFiles : (unsigned)0); + ATimes.ClearAndReserve(Need_ATime ? numFiles : (unsigned)0); + Attribs.ClearAndReserve(Need_Attrib ? numFiles : (unsigned)0); + // FolderCrc = CRC_INIT_VAL; _stream.Release(); } -HRESULT CFolderInStream::OpenStream() +void CFolderInStream::ClearFileInfo() { _pos = 0; _crc = CRC_INIT_VAL; _size_Defined = false; + _times_Defined = false; _size = 0; + FILETIME_Clear(_mTime); + FILETIME_Clear(_cTime); + FILETIME_Clear(_aTime); + _attrib = 0; +} - while (_index < _numFiles) +HRESULT CFolderInStream::OpenStream() +{ + while (Processed.Size() < _numFiles) { CMyComPtr stream; - HRESULT result = _updateCallback->GetStream(_indexes[_index], &stream); - if (result != S_OK) - { - if (result != S_FALSE) - return result; - } + const HRESULT result = _updateCallback->GetStream(_indexes[Processed.Size()], &stream); + if (result != S_OK && result != S_FALSE) + return result; _stream = stream; if (stream) { - CMyComPtr streamGetSize; - stream.QueryInterface(IID_IStreamGetSize, &streamGetSize); - if (streamGetSize) { - if (streamGetSize->GetSize(&_size) == S_OK) - _size_Defined = true; + CMyComPtr getProps; + stream.QueryInterface(IID_IStreamGetProps, (void **)&getProps); + if (getProps) + { + // access could be changed in first myx pass + if (getProps->GetProps(&_size, + Need_CTime ? &_cTime : NULL, + Need_ATime ? &_aTime : NULL, + Need_MTime ? &_mTime : NULL, + Need_Attrib ? &_attrib : NULL) + == S_OK) + { + _size_Defined = true; + _times_Defined = true; + } + return S_OK; + } + } + { + CMyComPtr streamGetSize; + stream.QueryInterface(IID_IStreamGetSize, &streamGetSize); + if (streamGetSize) + { + if (streamGetSize->GetSize(&_size) == S_OK) + _size_Defined = true; + } + return S_OK; } - return S_OK; } - _index++; - RINOK(_updateCallback->SetOperationResult(NArchive::NUpdate::NOperationResult::kOK)); - AddFileInfo(result == S_OK); + RINOK(AddFileInfo(result == S_OK)) } return S_OK; } -void CFolderInStream::AddFileInfo(bool isProcessed) +static void AddFt(CRecordVector &vec, const FILETIME &ft) { - Processed.Add(isProcessed); - Sizes.Add(_pos); - CRCs.Add(CRC_GET_DIGEST(_crc)); + vec.AddInReserved(FILETIME_To_UInt64(ft)); } -STDMETHODIMP CFolderInStream::Read(void *data, UInt32 size, UInt32 *processedSize) +/* +HRESULT ReportItemProps(IArchiveUpdateCallbackArcProp *reportArcProp, + UInt32 index, UInt64 size, const UInt32 *crc) +{ + PROPVARIANT prop; + prop.vt = VT_EMPTY; + prop.wReserved1 = 0; + + NWindows::NCOM::PropVarEm_Set_UInt64(&prop, size); + RINOK(reportArcProp->ReportProp(NEventIndexType::kOutArcIndex, index, kpidSize, &prop)); + if (crc) + { + NWindows::NCOM::PropVarEm_Set_UInt32(&prop, *crc); + RINOK(reportArcProp->ReportProp(NEventIndexType::kOutArcIndex, index, kpidCRC, &prop)); + } + return reportArcProp->ReportFinished(NEventIndexType::kOutArcIndex, index, NUpdate::NOperationResult::kOK); +} +*/ + +HRESULT CFolderInStream::AddFileInfo(bool isProcessed) +{ + // const UInt32 index = _indexes[Processed.Size()]; + Processed.AddInReserved(isProcessed); + Sizes.AddInReserved(_pos); + CRCs.AddInReserved(CRC_GET_DIGEST(_crc)); + if (Need_Attrib) Attribs.AddInReserved(_attrib); + TimesDefined.AddInReserved(_times_Defined); + if (Need_MTime) AddFt(MTimes, _mTime); + if (Need_CTime) AddFt(CTimes, _cTime); + if (Need_ATime) AddFt(ATimes, _aTime); + ClearFileInfo(); + /* + if (isProcessed && _reportArcProp) + RINOK(ReportItemProps(_reportArcProp, index, _pos, &crc)) + */ + return _updateCallback->SetOperationResult(NArchive::NUpdate::NOperationResult::kOK); +} + +Z7_COM7F_IMF(CFolderInStream::Read(void *data, UInt32 size, UInt32 *processedSize)) { if (processedSize) *processedSize = 0; @@ -80,43 +145,65 @@ STDMETHODIMP CFolderInStream::Read(void *data, UInt32 size, UInt32 *processedSiz { if (_stream) { - UInt32 processed2; - RINOK(_stream->Read(data, size, &processed2)); - if (processed2 != 0) + /* + if (_pos == 0) + { + const UInt32 align = (UInt32)1 << AlignLog; + const UInt32 offset = (UInt32)_totalSize_for_Coder & (align - 1); + if (offset != 0) + { + UInt32 cur = align - offset; + if (cur > size) + cur = size; + memset(data, 0, cur); + data = (Byte *)data + cur; + size -= cur; + // _pos += cur; // for debug + _totalSize_for_Coder += cur; + if (processedSize) + *processedSize += cur; + continue; + } + } + */ + UInt32 cur = size; + const UInt32 kMax = (UInt32)1 << 20; + if (cur > kMax) + cur = kMax; + RINOK(_stream->Read(data, cur, &cur)) + if (cur != 0) { - _crc = CrcUpdate(_crc, data, processed2); - _pos += processed2; + // if (Need_Crc) + _crc = CrcUpdate(_crc, data, cur); + /* + if (FolderCrc) + FolderCrc = CrcUpdate(FolderCrc, data, cur); + */ + _pos += cur; + _totalSize_for_Coder += cur; if (processedSize) - *processedSize = processed2; + *processedSize = cur; // use +=cur, if continue is possible in loop return S_OK; } _stream.Release(); - _index++; - AddFileInfo(true); - - _pos = 0; - _crc = CRC_INIT_VAL; - _size_Defined = false; - _size = 0; - - RINOK(_updateCallback->SetOperationResult(NArchive::NUpdate::NOperationResult::kOK)); + RINOK(AddFileInfo(true)) } - if (_index >= _numFiles) + if (Processed.Size() >= _numFiles) break; - RINOK(OpenStream()); + RINOK(OpenStream()) } return S_OK; } -STDMETHODIMP CFolderInStream::GetSubStreamSize(UInt64 subStream, UInt64 *value) +Z7_COM7F_IMF(CFolderInStream::GetSubStreamSize(UInt64 subStream, UInt64 *value)) { *value = 0; if (subStream > Sizes.Size()) return S_FALSE; // E_FAIL; - unsigned index = (unsigned)subStream; + const unsigned index = (unsigned)subStream; if (index < Sizes.Size()) { *value = Sizes[index]; @@ -133,4 +220,45 @@ STDMETHODIMP CFolderInStream::GetSubStreamSize(UInt64 subStream, UInt64 *value) return S_OK; } + +/* +HRESULT CFolderInStream::CloseCrcStream() +{ + if (!_crcStream) + return S_OK; + if (!_crcStream_Spec->WasFinished()) + return E_FAIL; + _crc = _crcStream_Spec->GetCRC() ^ CRC_INIT_VAL; // change it + const UInt64 size = _crcStream_Spec->GetSize(); + _pos = size; + _totalSize_for_Coder += size; + _crcStream.Release(); + // _crcStream->ReleaseStream(); + _stream.Release(); + return AddFileInfo(true); +} + +Z7_COM7F_IMF(CFolderInStream::GetNextInSubStream(UInt64 *streamIndexRes, ISequentialInStream **stream) +{ + RINOK(CloseCrcStream()) + *stream = NULL; + *streamIndexRes = Processed.Size(); + if (Processed.Size() >= _numFiles) + return S_OK; + RINOK(OpenStream()); + if (!_stream) + return S_OK; + if (!_crcStream) + { + _crcStream_Spec = new CSequentialInStreamWithCRC; + _crcStream = _crcStream_Spec; + } + _crcStream_Spec->Init(); + _crcStream_Spec->SetStream(_stream); + *stream = _crcStream; + _crcStream->AddRef(); + return S_OK; +} +*/ + }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zFolderInStream.h b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zFolderInStream.h index 805db54e..6447b25c 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zFolderInStream.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zFolderInStream.h @@ -1,12 +1,13 @@ // 7zFolderInStream.h -#ifndef __7Z_FOLDER_IN_STREAM_H -#define __7Z_FOLDER_IN_STREAM_H +#ifndef ZIP7_INC_7Z_FOLDER_IN_STREAM_H +#define ZIP7_INC_7Z_FOLDER_IN_STREAM_H #include "../../../../C/7zCrc.h" #include "../../../Common/MyCom.h" #include "../../../Common/MyVector.h" +// #include "../Common/InStreamWithCRC.h" #include "../../ICoder.h" #include "../IArchive.h" @@ -14,39 +15,67 @@ namespace NArchive { namespace N7z { -class CFolderInStream: - public ISequentialInStream, - public ICompressGetSubStreamSize, - public CMyUnknownImp -{ +Z7_CLASS_IMP_COM_2( + CFolderInStream + , ISequentialInStream + , ICompressGetSubStreamSize +) + /* + Z7_COM7F_IMP(GetNextStream(UInt64 *streamIndex)) + Z7_IFACE_COM7_IMP(ICompressInSubStreams) + */ + CMyComPtr _stream; + UInt64 _totalSize_for_Coder; UInt64 _pos; UInt32 _crc; bool _size_Defined; + bool _times_Defined; UInt64 _size; + FILETIME _mTime; + FILETIME _cTime; + FILETIME _aTime; + UInt32 _attrib; - const UInt32 *_indexes; unsigned _numFiles; - unsigned _index; + const UInt32 *_indexes; CMyComPtr _updateCallback; + void ClearFileInfo(); HRESULT OpenStream(); - void AddFileInfo(bool isProcessed); - + HRESULT AddFileInfo(bool isProcessed); + // HRESULT CloseCrcStream(); public: + bool Need_MTime; + bool Need_CTime; + bool Need_ATime; + bool Need_Attrib; + // bool Need_Crc; + // bool Need_FolderCrc; + // unsigned AlignLog; + CRecordVector Processed; - CRecordVector CRCs; CRecordVector Sizes; - - MY_UNKNOWN_IMP2(ISequentialInStream, ICompressGetSubStreamSize) - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); - STDMETHOD(GetSubStreamSize)(UInt64 subStream, UInt64 *value); + CRecordVector CRCs; + CRecordVector Attribs; + CRecordVector TimesDefined; + CRecordVector MTimes; + CRecordVector CTimes; + CRecordVector ATimes; + // UInt32 FolderCrc; + + // UInt32 GetFolderCrc() const { return CRC_GET_DIGEST(FolderCrc); } + // CSequentialInStreamWithCRC *_crcStream_Spec; + // CMyComPtr _crcStream; + // CMyComPtr _reportArcProp; void Init(IArchiveUpdateCallback *updateCallback, const UInt32 *indexes, unsigned numFiles); - bool WasFinished() const { return _index == _numFiles; } + bool WasFinished() const { return Processed.Size() == _numFiles; } + UInt64 Get_TotalSize_for_Coder() const { return _totalSize_for_Coder; } + /* UInt64 GetFullSize() const { UInt64 size = 0; @@ -54,6 +83,17 @@ class CFolderInStream: size += Sizes[i]; return size; } + */ + + CFolderInStream(): + Need_MTime(false), + Need_CTime(false), + Need_ATime(false), + Need_Attrib(false) + // , Need_Crc(true) + // , Need_FolderCrc(false) + // , AlignLog(0) + {} }; }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zHandler.cpp index ccd2c624..81dd9669 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zHandler.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zHandler.cpp @@ -7,7 +7,7 @@ #include "../../../Common/ComTry.h" #include "../../../Common/IntToString.h" -#ifndef __7Z_SET_PROPERTIES +#ifndef Z7_7Z_SET_PROPERTIES #include "../../../Windows/System.h" #endif @@ -16,8 +16,8 @@ #include "7zHandler.h" #include "7zProperties.h" -#ifdef __7Z_SET_PROPERTIES -#ifdef EXTRACT_ONLY +#ifdef Z7_7Z_SET_PROPERTIES +#ifdef Z7_EXTRACT_ONLY #include "../Common/ParseProperties.h" #endif #endif @@ -30,41 +30,40 @@ namespace N7z { CHandler::CHandler() { - #ifndef _NO_CRYPTO + #ifndef Z7_NO_CRYPTO _isEncrypted = false; _passwordIsDefined = false; #endif - #ifdef EXTRACT_ONLY + #ifdef Z7_EXTRACT_ONLY _crcSize = 4; - #ifdef __7Z_SET_PROPERTIES - _numThreads = NSystem::GetNumberOfProcessors(); + #ifdef Z7_7Z_SET_PROPERTIES _useMultiThreadMixer = true; #endif #endif } -STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) { *numItems = _db.Files.Size(); return S_OK; } -#ifdef _SFX +#ifdef Z7_SFX IMP_IInArchive_ArcProps_NO_Table -STDMETHODIMP CHandler::GetNumberOfProperties(UInt32 *numProps) +Z7_COM7F_IMF(CHandler::GetNumberOfProperties(UInt32 *numProps)) { *numProps = 0; return S_OK; } -STDMETHODIMP CHandler::GetPropertyInfo(UInt32 /* index */, - BSTR * /* name */, PROPID * /* propID */, VARTYPE * /* varType */) +Z7_COM7F_IMF(CHandler::GetPropertyInfo(UInt32 /* index */, + BSTR * /* name */, PROPID * /* propID */, VARTYPE * /* varType */)) { return E_NOTIMPL; } @@ -108,34 +107,61 @@ static void ConvertMethodIdToString(AString &res, UInt64 id) res += s + len - ConvertMethodIdToString_Back(s + len, id); } -static unsigned GetStringForSizeValue(char *s, UInt32 val) + +static char *GetStringForSizeValue(char *s, UInt32 val) { - unsigned i; - for (i = 0; i <= 31; i++) + for (unsigned i = 0; i < 32; i++) if (((UInt32)1 << i) == val) { - if (i < 10) + if (i >= 10) { - s[0] = (char)('0' + i); - s[1] = 0; - return 1; + *s++= (char)('0' + i / 10); + i %= 10; } - if (i < 20) { s[0] = '1'; s[1] = (char)('0' + i - 10); } - else if (i < 30) { s[0] = '2'; s[1] = (char)('0' + i - 20); } - else { s[0] = '3'; s[1] = (char)('0' + i - 30); } - s[2] = 0; - return 2; + *s++ = (char)('0' + i); + *s = 0; + return s; } + char c = 'b'; if ((val & ((1 << 20) - 1)) == 0) { val >>= 20; c = 'm'; } else if ((val & ((1 << 10) - 1)) == 0) { val >>= 10; c = 'k'; } - ::ConvertUInt32ToString(val, s); - unsigned pos = MyStringLen(s); - s[pos++] = c; - s[pos] = 0; - return pos; + s = ConvertUInt32ToString(val, s); + *s++ = c; + *s = 0; + return s; } + +static void GetLzma2String(char *s, unsigned d) +{ + if (d > 40) + { + *s = 0; + return; + // s = MyStpCpy(s, "unsup"); + } + else if ((d & 1) == 0) + d = (d >> 1) + 12; + else + { + // s = GetStringForSizeValue(s, (UInt32)3 << ((d >> 1) + 11)); + d = (d >> 1) + 1; + char c = 'k'; + if (d >= 10) + { + c = 'm'; + d -= 10; + } + s = ConvertUInt32ToString((UInt32)3 << d, s); + *s++ = c; + *s = 0; + return; + } + ConvertUInt32ToString(d, s); +} + + /* static inline void AddHexToString(UString &res, Byte value) { @@ -148,8 +174,7 @@ static char *AddProp32(char *s, const char *name, UInt32 v) { *s++ = ':'; s = MyStpCpy(s, name); - ::ConvertUInt32ToString(v, s); - return s + MyStringLen(s); + return ConvertUInt32ToString(v, s); } void CHandler::AddMethodName(AString &s, UInt64 id) @@ -164,15 +189,15 @@ void CHandler::AddMethodName(AString &s, UInt64 id) #endif -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { - #ifndef _SFX + #ifndef Z7_SFX COM_TRY_BEGIN #endif NCOM::CPropVariant prop; switch (propID) { - #ifndef _SFX + #ifndef Z7_SFX case kpidMethod: { AString s; @@ -185,10 +210,7 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) if (id == k_LZMA2) { s += "LZMA2:"; - if ((pm.Lzma2Prop & 1) == 0) - ConvertUInt32ToString((pm.Lzma2Prop >> 1) + 12, temp); - else - GetStringForSizeValue(temp, 3 << ((pm.Lzma2Prop >> 1) + 11)); + GetLzma2String(temp, pm.Lzma2Prop); s += temp; } else if (id == k_LZMA) @@ -197,6 +219,12 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) GetStringForSizeValue(temp, pm.LzmaDic); s += temp; } + /* + else if (id == k_ZSTD) + { + s += "ZSTD"; + } + */ else AddMethodName(s, id); } @@ -237,36 +265,43 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) prop = v; break; } + + case kpidReadOnly: + { + if (!_db.CanUpdate()) + prop = true; + break; + } + default: break; } - prop.Detach(value); - return S_OK; - #ifndef _SFX + return prop.Detach(value); + #ifndef Z7_SFX COM_TRY_END #endif } -static void SetFileTimeProp_From_UInt64Def(PROPVARIANT *prop, const CUInt64DefVector &v, int index) +static void SetFileTimeProp_From_UInt64Def(PROPVARIANT *prop, const CUInt64DefVector &v, unsigned index) { UInt64 value; if (v.GetItem(index, value)) - PropVarEm_Set_FileTime64(prop, value); + PropVarEm_Set_FileTime64_Prec(prop, value, k_PropVar_TimePrec_100ns); } bool CHandler::IsFolderEncrypted(CNum folderIndex) const { if (folderIndex == kNumNoIndex) return false; - size_t startPos = _db.FoCodersDataOffset[folderIndex]; - const Byte *p = _db.CodersData + startPos; - size_t size = _db.FoCodersDataOffset[folderIndex + 1] - startPos; + const size_t startPos = _db.FoCodersDataOffset[folderIndex]; + const Byte *p = _db.CodersData.ConstData() + startPos; + const size_t size = _db.FoCodersDataOffset[folderIndex + 1] - startPos; CInByte2 inByte; inByte.Init(p, size); CNum numCoders = inByte.ReadNum(); for (; numCoders != 0; numCoders--) { - Byte mainByte = inByte.ReadByte(); - unsigned idSize = (mainByte & 0xF); + const Byte mainByte = inByte.ReadByte(); + const unsigned idSize = (mainByte & 0xF); const Byte *longID = inByte.GetPtr(); UInt64 id64 = 0; for (unsigned j = 0; j < idSize; j++) @@ -280,20 +315,20 @@ bool CHandler::IsFolderEncrypted(CNum folderIndex) const return false; } -STDMETHODIMP CHandler::GetNumRawProps(UInt32 *numProps) +Z7_COM7F_IMF(CHandler::GetNumRawProps(UInt32 *numProps)) { *numProps = 0; return S_OK; } -STDMETHODIMP CHandler::GetRawPropInfo(UInt32 /* index */, BSTR *name, PROPID *propID) +Z7_COM7F_IMF(CHandler::GetRawPropInfo(UInt32 /* index */, BSTR *name, PROPID *propID)) { *name = NULL; *propID = kpidNtSecure; return S_OK; } -STDMETHODIMP CHandler::GetParent(UInt32 /* index */, UInt32 *parent, UInt32 *parentType) +Z7_COM7F_IMF(CHandler::GetParent(UInt32 /* index */, UInt32 *parent, UInt32 *parentType)) { /* const CFileItem &file = _db.Files[index]; @@ -305,7 +340,7 @@ STDMETHODIMP CHandler::GetParent(UInt32 /* index */, UInt32 *parent, UInt32 *par return S_OK; } -STDMETHODIMP CHandler::GetRawProp(UInt32 index, PROPID propID, const void **data, UInt32 *dataSize, UInt32 *propType) +Z7_COM7F_IMF(CHandler::GetRawProp(UInt32 index, PROPID propID, const void **data, UInt32 *dataSize, UInt32 *propType)) { *data = NULL; *dataSize = 0; @@ -316,11 +351,11 @@ STDMETHODIMP CHandler::GetRawProp(UInt32 index, PROPID propID, const void **data { if (_db.NameOffsets && _db.NamesBuf) { - size_t offset = _db.NameOffsets[index]; - size_t size = (_db.NameOffsets[index + 1] - offset) * 2; + const size_t offset = _db.NameOffsets[index]; + const size_t size = (_db.NameOffsets[index + 1] - offset) * 2; if (size < ((UInt32)1 << 31)) { - *data = (const void *)(_db.NamesBuf + offset * 2); + *data = (const void *)(_db.NamesBuf.ConstData() + offset * 2); *dataSize = (UInt32)size; *propType = NPropDataType::kUtf16z; } @@ -347,7 +382,7 @@ STDMETHODIMP CHandler::GetRawProp(UInt32 index, PROPID propID, const void **data return S_OK; } -#ifndef _SFX +#ifndef Z7_SFX HRESULT CHandler::SetMethodToProp(CNum folderIndex, PROPVARIANT *prop) const { @@ -360,9 +395,9 @@ HRESULT CHandler::SetMethodToProp(CNum folderIndex, PROPVARIANT *prop) const unsigned pos = kTempSize; temp[--pos] = 0; - size_t startPos = _db.FoCodersDataOffset[folderIndex]; - const Byte *p = _db.CodersData + startPos; - size_t size = _db.FoCodersDataOffset[folderIndex + 1] - startPos; + const size_t startPos = _db.FoCodersDataOffset[folderIndex]; + const Byte *p = _db.CodersData.ConstData() + startPos; + const size_t size = _db.FoCodersDataOffset[folderIndex + 1] - startPos; CInByte2 inByte; inByte.Init(p, size); @@ -374,10 +409,10 @@ HRESULT CHandler::SetMethodToProp(CNum folderIndex, PROPVARIANT *prop) const { if (pos < 32) // max size of property break; - Byte mainByte = inByte.ReadByte(); - unsigned idSize = (mainByte & 0xF); - const Byte *longID = inByte.GetPtr(); + const Byte mainByte = inByte.ReadByte(); UInt64 id64 = 0; + const unsigned idSize = (mainByte & 0xF); + const Byte *longID = inByte.GetPtr(); for (unsigned j = 0; j < idSize; j++) id64 = ((id64 << 8) | longID[j]); inByte.SkipDataNoCheck(idSize); @@ -403,21 +438,21 @@ HRESULT CHandler::SetMethodToProp(CNum folderIndex, PROPVARIANT *prop) const if (id64 <= (UInt32)0xFFFFFFFF) { - UInt32 id = (UInt32)id64; + const UInt32 id = (UInt32)id64; if (id == k_LZMA) { name = "LZMA"; if (propsSize == 5) { - UInt32 dicSize = GetUi32((const Byte *)props + 1); - char *dest = s + GetStringForSizeValue(s, dicSize); + const UInt32 dicSize = GetUi32((const Byte *)props + 1); + char *dest = GetStringForSizeValue(s, dicSize); UInt32 d = props[0]; if (d != 0x5D) { - UInt32 lc = d % 9; + const UInt32 lc = d % 9; d /= 9; - UInt32 pb = d / 5; - UInt32 lp = d % 5; + const UInt32 pb = d / 5; + const UInt32 lp = d % 5; if (lc != 3) dest = AddProp32(dest, "lc", lc); if (lp != 0) dest = AddProp32(dest, "lp", lp); if (pb != 2) dest = AddProp32(dest, "pb", pb); @@ -428,24 +463,16 @@ HRESULT CHandler::SetMethodToProp(CNum folderIndex, PROPVARIANT *prop) const { name = "LZMA2"; if (propsSize == 1) - { - Byte d = props[0]; - if ((d & 1) == 0) - ConvertUInt32ToString((UInt32)((d >> 1) + 12), s); - else - GetStringForSizeValue(s, 3 << ((d >> 1) + 11)); - } + GetLzma2String(s, props[0]); } else if (id == k_PPMD) { name = "PPMD"; if (propsSize == 5) { - Byte order = *props; char *dest = s; *dest++ = 'o'; - ConvertUInt32ToString(order, dest); - dest += MyStringLen(dest); + dest = ConvertUInt32ToString(*props, dest); dest = MyStpCpy(dest, ":mem"); GetStringForSizeValue(dest, GetUi32(props + 1)); } @@ -456,6 +483,16 @@ HRESULT CHandler::SetMethodToProp(CNum folderIndex, PROPVARIANT *prop) const if (propsSize == 1) ConvertUInt32ToString((UInt32)props[0] + 1, s); } + else if (id == k_ARM64 || id == k_RISCV) + { + name = id == k_ARM64 ? "ARM64" : "RISCV"; + if (propsSize == 4) + ConvertUInt32ToString(GetUi32(props), s); + /* + else if (propsSize != 0) + MyStringCopy(s, "unsupported"); + */ + } else if (id == k_BCJ2) name = "BCJ2"; else if (id == k_BCJ) name = "BCJ"; else if (id == k_AES) @@ -463,8 +500,8 @@ HRESULT CHandler::SetMethodToProp(CNum folderIndex, PROPVARIANT *prop) const name = "7zAES"; if (propsSize >= 1) { - Byte firstByte = props[0]; - UInt32 numCyclesPower = firstByte & 0x3F; + const Byte firstByte = props[0]; + const UInt32 numCyclesPower = firstByte & 0x3F; ConvertUInt32ToString(numCyclesPower, s); } } @@ -472,8 +509,8 @@ HRESULT CHandler::SetMethodToProp(CNum folderIndex, PROPVARIANT *prop) const if (name) { - unsigned nameLen = MyStringLen(name); - unsigned propsLen = MyStringLen(s); + const unsigned nameLen = MyStringLen(name); + const unsigned propsLen = MyStringLen(s); unsigned totalLen = nameLen + propsLen; if (propsLen != 0) totalLen++; @@ -502,7 +539,7 @@ HRESULT CHandler::SetMethodToProp(CNum folderIndex, PROPVARIANT *prop) const pos -= ConvertMethodIdToString_Back(temp + pos, id64); else { - unsigned len = methodName.Len(); + const unsigned len = methodName.Len(); if (len + 5 > pos) break; pos -= len; @@ -526,9 +563,9 @@ HRESULT CHandler::SetMethodToProp(CNum folderIndex, PROPVARIANT *prop) const #endif -STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) { - PropVariant_Clear(value); + RINOK(PropVariant_Clear(value)) // COM_TRY_BEGIN // NCOM::CPropVariant prop; @@ -540,7 +577,7 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val */ const CFileItem &item = _db.Files[index]; - UInt32 index2 = index; + const UInt32 index2 = index; switch (propID) { @@ -555,7 +592,7 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val { // prop = ref2.PackSize; { - CNum folderIndex = _db.FileIndexToFolderIndexMap[index2]; + const CNum folderIndex = _db.FileIndexToFolderIndexMap[index2]; if (folderIndex != kNumNoIndex) { if (_db.FolderStartFileIndex[folderIndex] == (CNum)index2) @@ -575,7 +612,7 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val case kpidCTime: SetFileTimeProp_From_UInt64Def(value, _db.CTime, index2); break; case kpidATime: SetFileTimeProp_From_UInt64Def(value, _db.ATime, index2); break; case kpidMTime: SetFileTimeProp_From_UInt64Def(value, _db.MTime, index2); break; - case kpidAttrib: if (item.AttribDefined) PropVarEm_Set_UInt32(value, item.Attrib); break; + case kpidAttrib: if (_db.Attrib.ValidAndDefined(index2)) PropVarEm_Set_UInt32(value, _db.Attrib.Vals[index2]); break; case kpidCRC: if (item.CrcDefined) PropVarEm_Set_UInt32(value, item.Crc); break; case kpidEncrypted: PropVarEm_Set_Bool(value, IsFolderEncrypted(_db.FileIndexToFolderIndexMap[index2])); break; case kpidIsAnti: PropVarEm_Set_Bool(value, _db.IsItemAnti(index2)); break; @@ -596,24 +633,24 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val case kpidPath: return _db.GetPath_Prop(index, value); - #ifndef _SFX + #ifndef Z7_SFX case kpidMethod: return SetMethodToProp(_db.FileIndexToFolderIndexMap[index2], value); case kpidBlock: { - CNum folderIndex = _db.FileIndexToFolderIndexMap[index2]; + const CNum folderIndex = _db.FileIndexToFolderIndexMap[index2]; if (folderIndex != kNumNoIndex) PropVarEm_Set_UInt32(value, (UInt32)folderIndex); } break; - /* + #ifdef Z7_7Z_SHOW_PACK_STREAMS_SIZES case kpidPackedSize0: case kpidPackedSize1: case kpidPackedSize2: case kpidPackedSize3: case kpidPackedSize4: { - CNum folderIndex = _db.FileIndexToFolderIndexMap[index2]; + const CNum folderIndex = _db.FileIndexToFolderIndexMap[index2]; if (folderIndex != kNumNoIndex) { if (_db.FolderStartFileIndex[folderIndex] == (CNum)index2 && @@ -627,22 +664,23 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val PropVarEm_Set_UInt64(value, 0); } break; - */ + #endif #endif + default: break; } - // prop.Detach(value); + // return prop.Detach(value); return S_OK; // COM_TRY_END } -STDMETHODIMP CHandler::Open(IInStream *stream, +Z7_COM7F_IMF(CHandler::Open(IInStream *stream, const UInt64 *maxCheckStartPosition, - IArchiveOpenCallback *openArchiveCallback) + IArchiveOpenCallback *openArchiveCallback)) { COM_TRY_BEGIN Close(); - #ifndef _SFX + #ifndef Z7_SFX _fileInfoPopIDs.Clear(); #endif @@ -650,31 +688,31 @@ STDMETHODIMP CHandler::Open(IInStream *stream, { CMyComPtr openArchiveCallbackTemp = openArchiveCallback; - #ifndef _NO_CRYPTO + #ifndef Z7_NO_CRYPTO CMyComPtr getTextPassword; if (openArchiveCallback) openArchiveCallbackTemp.QueryInterface(IID_ICryptoGetTextPassword, &getTextPassword); #endif CInArchive archive( - #ifdef __7Z_SET_PROPERTIES + #ifdef Z7_7Z_SET_PROPERTIES _useMultiThreadMixer #else true #endif ); _db.IsArc = false; - RINOK(archive.Open(stream, maxCheckStartPosition)); + RINOK(archive.Open(stream, maxCheckStartPosition)) _db.IsArc = true; HRESULT result = archive.ReadDatabase( EXTERNAL_CODECS_VARS _db - #ifndef _NO_CRYPTO + #ifndef Z7_NO_CRYPTO , getTextPassword, _isEncrypted, _passwordIsDefined, _password #endif ); - RINOK(result); + RINOK(result) _inStream = stream; } @@ -687,35 +725,35 @@ STDMETHODIMP CHandler::Open(IInStream *stream, return E_OUTOFMEMORY; } // _inStream = stream; - #ifndef _SFX + #ifndef Z7_SFX FillPopIDs(); #endif return S_OK; COM_TRY_END } -STDMETHODIMP CHandler::Close() +Z7_COM7F_IMF(CHandler::Close()) { COM_TRY_BEGIN _inStream.Release(); _db.Clear(); - #ifndef _NO_CRYPTO + #ifndef Z7_NO_CRYPTO _isEncrypted = false; _passwordIsDefined = false; - _password.Empty(); + _password.Wipe_and_Empty(); #endif return S_OK; COM_TRY_END } -#ifdef __7Z_SET_PROPERTIES -#ifdef EXTRACT_ONLY +#ifdef Z7_7Z_SET_PROPERTIES +#ifdef Z7_EXTRACT_ONLY -STDMETHODIMP CHandler::SetProperties(const wchar_t * const *names, const PROPVARIANT *values, UInt32 numProps) +Z7_COM7F_IMF(CHandler::SetProperties(const wchar_t * const *names, const PROPVARIANT *values, UInt32 numProps)) { COM_TRY_BEGIN - const UInt32 numProcessors = NSystem::GetNumberOfProcessors(); - _numThreads = numProcessors; + + InitCommon(); _useMultiThreadMixer = true; for (UInt32 i = 0; i < numProps; i++) @@ -726,21 +764,23 @@ STDMETHODIMP CHandler::SetProperties(const wchar_t * const *names, const PROPVAR return E_INVALIDARG; const PROPVARIANT &value = values[i]; UInt32 number; - unsigned index = ParseStringToUInt32(name, number); + const unsigned index = ParseStringToUInt32(name, number); if (index == 0) { if (name.IsEqualTo("mtf")) { - RINOK(PROPVARIANT_to_bool(value, _useMultiThreadMixer)); + RINOK(PROPVARIANT_to_bool(value, _useMultiThreadMixer)) continue; } - if (name.IsPrefixedBy_Ascii_NoCase("mt")) { - RINOK(ParseMtProp(name.Ptr(2), value, numProcessors, _numThreads)); - continue; + HRESULT hres; + if (SetCommonProperty(name, value, hres)) + { + RINOK(hres) + continue; + } } - else - return E_INVALIDARG; + return E_INVALIDARG; } } return S_OK; diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zHandler.h b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zHandler.h index d46401af..ed535f74 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zHandler.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zHandler.h @@ -1,45 +1,43 @@ // 7z/Handler.h -#ifndef __7Z_HANDLER_H -#define __7Z_HANDLER_H +#ifndef ZIP7_7Z_HANDLER_H +#define ZIP7_7Z_HANDLER_H #include "../../ICoder.h" #include "../IArchive.h" #include "../../Common/CreateCoder.h" -#ifndef EXTRACT_ONLY -#include "../Common/HandlerOut.h" +#ifndef Z7_7Z_SET_PROPERTIES + +#ifdef Z7_EXTRACT_ONLY + #if !defined(Z7_ST) && !defined(Z7_SFX) + #define Z7_7Z_SET_PROPERTIES + #endif +#else + #define Z7_7Z_SET_PROPERTIES +#endif + #endif +// #ifdef Z7_7Z_SET_PROPERTIES +#include "../Common/HandlerOut.h" +// #endif + #include "7zCompressionMode.h" #include "7zIn.h" namespace NArchive { namespace N7z { -#ifndef __7Z_SET_PROPERTIES -#ifdef EXTRACT_ONLY - #if !defined(_7ZIP_ST) && !defined(_SFX) - #define __7Z_SET_PROPERTIES - #endif -#else - #define __7Z_SET_PROPERTIES -#endif - -#endif - - -#ifndef EXTRACT_ONLY +#ifndef Z7_EXTRACT_ONLY class COutHandler: public CMultiMethodProps { HRESULT SetSolidFromString(const UString &s); HRESULT SetSolidFromPROPVARIANT(const PROPVARIANT &value); public: - bool _removeSfxBlock; - UInt64 _numSolidFiles; UInt64 _numSolidBytes; bool _numSolidBytesDefined; @@ -51,14 +49,18 @@ class COutHandler: public CMultiMethodProps bool _encryptHeaders; // bool _useParents; 9.26 - CBoolPair Write_CTime; - CBoolPair Write_ATime; - CBoolPair Write_MTime; + CHandlerTimeOptions TimeOptions; - bool _useMultiThreadMixer; + CBoolPair Write_Attrib; + bool _useMultiThreadMixer; + bool _removeSfxBlock; // bool _volumeMode; + UInt32 _decoderCompatibilityVersion; + CUIntVector _enabledFilters; + CUIntVector _disabledFilters; + void InitSolidFiles() { _numSolidFiles = (UInt64)(Int64)(-1); } void InitSolidSize() { _numSolidBytes = (UInt64)(Int64)(-1); } void InitSolid() @@ -69,72 +71,73 @@ class COutHandler: public CMultiMethodProps _numSolidBytesDefined = false; } + void InitProps7z(); void InitProps(); - COutHandler() { InitProps(); } + COutHandler() { InitProps7z(); } HRESULT SetProperty(const wchar_t *name, const PROPVARIANT &value); }; #endif -class CHandler: +class CHandler Z7_final: public IInArchive, public IArchiveGetRawProps, - #ifdef __7Z_SET_PROPERTIES + + #ifdef Z7_7Z_SET_PROPERTIES public ISetProperties, #endif - #ifndef EXTRACT_ONLY + + #ifndef Z7_EXTRACT_ONLY public IOutArchive, #endif - PUBLIC_ISetCompressCodecsInfo - public CMyUnknownImp - #ifndef EXTRACT_ONLY - , public COutHandler - #endif -{ -public: - MY_QUERYINTERFACE_BEGIN2(IInArchive) - MY_QUERYINTERFACE_ENTRY(IArchiveGetRawProps) - #ifdef __7Z_SET_PROPERTIES - MY_QUERYINTERFACE_ENTRY(ISetProperties) - #endif - #ifndef EXTRACT_ONLY - MY_QUERYINTERFACE_ENTRY(IOutArchive) - #endif - QUERY_ENTRY_ISetCompressCodecsInfo - MY_QUERYINTERFACE_END - MY_ADDREF_RELEASE - - INTERFACE_IInArchive(;) - INTERFACE_IArchiveGetRawProps(;) - - #ifdef __7Z_SET_PROPERTIES - STDMETHOD(SetProperties)(const wchar_t * const *names, const PROPVARIANT *values, UInt32 numProps); - #endif + + Z7_PUBLIC_ISetCompressCodecsInfo_IFEC + + public CMyUnknownImp, - #ifndef EXTRACT_ONLY - INTERFACE_IOutArchive(;) + #ifndef Z7_EXTRACT_ONLY + public COutHandler + #else + public CCommonMethodProps #endif - +{ + Z7_COM_QI_BEGIN2(IInArchive) + Z7_COM_QI_ENTRY(IArchiveGetRawProps) + #ifdef Z7_7Z_SET_PROPERTIES + Z7_COM_QI_ENTRY(ISetProperties) + #endif + #ifndef Z7_EXTRACT_ONLY + Z7_COM_QI_ENTRY(IOutArchive) + #endif + Z7_COM_QI_ENTRY_ISetCompressCodecsInfo_IFEC + Z7_COM_QI_END + Z7_COM_ADDREF_RELEASE + + Z7_IFACE_COM7_IMP(IInArchive) + Z7_IFACE_COM7_IMP(IArchiveGetRawProps) + #ifdef Z7_7Z_SET_PROPERTIES + Z7_IFACE_COM7_IMP(ISetProperties) + #endif + #ifndef Z7_EXTRACT_ONLY + Z7_IFACE_COM7_IMP(IOutArchive) + #endif DECL_ISetCompressCodecsInfo - CHandler(); - private: CMyComPtr _inStream; NArchive::N7z::CDbEx _db; - #ifndef _NO_CRYPTO + #ifndef Z7_NO_CRYPTO bool _isEncrypted; bool _passwordIsDefined; - UString _password; - #endif + UString _password; // _Wipe + #endif - #ifdef EXTRACT_ONLY + #ifdef Z7_EXTRACT_ONLY - #ifdef __7Z_SET_PROPERTIES - UInt32 _numThreads; + #ifdef Z7_7Z_SET_PROPERTIES bool _useMultiThreadMixer; #endif @@ -146,17 +149,12 @@ class CHandler: HRESULT PropsMethod_To_FullMethod(CMethodFull &dest, const COneMethodInfo &m); HRESULT SetHeaderMethod(CCompressionMethodMode &headerMethod); - HRESULT SetMainMethod(CCompressionMethodMode &method - #ifndef _7ZIP_ST - , UInt32 numThreads - #endif - ); - + HRESULT SetMainMethod(CCompressionMethodMode &method); #endif bool IsFolderEncrypted(CNum folderIndex) const; - #ifndef _SFX + #ifndef Z7_SFX CRecordVector _fileInfoPopIDs; void FillPopIDs(); @@ -166,6 +164,13 @@ class CHandler: #endif DECL_EXTERNAL_CODECS_VARS + +public: + CHandler(); + ~CHandler() + { + Close(); + } }; }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zHandlerOut.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zHandlerOut.cpp index 2b86ed26..c1c2b636 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zHandlerOut.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zHandlerOut.cpp @@ -13,16 +13,22 @@ #include "7zOut.h" #include "7zUpdate.h" +#ifndef Z7_EXTRACT_ONLY + using namespace NWindows; namespace NArchive { namespace N7z { -static const char *k_LZMA_Name = "LZMA"; -static const char *kDefaultMethodName = "LZMA2"; -static const char *k_Copy_Name = "Copy"; +static const UInt32 k_decoderCompatibilityVersion = 2301; +// 7-Zip version 2301 supports ARM64 filter + +#define k_LZMA_Name "LZMA" +#define kDefaultMethodName "LZMA2" +#define k_Copy_Name "Copy" + +#define k_MatchFinder_ForHeaders "BT2" -static const char *k_MatchFinder_ForHeaders = "BT2"; static const UInt32 k_NumFastBytes_ForHeaders = 273; static const UInt32 k_Level_ForHeaders = 5; static const UInt32 k_Dictionary_ForHeaders = @@ -32,7 +38,7 @@ static const UInt32 k_Dictionary_ForHeaders = 1 << 20; #endif -STDMETHODIMP CHandler::GetFileTimeType(UInt32 *type) +Z7_COM7F_IMF(CHandler::GetFileTimeType(UInt32 *type)) { *type = NFileTimeType::kWindows; return S_OK; @@ -40,9 +46,12 @@ STDMETHODIMP CHandler::GetFileTimeType(UInt32 *type) HRESULT CHandler::PropsMethod_To_FullMethod(CMethodFull &dest, const COneMethodInfo &m) { - if (!FindMethod( + bool isFilter; + dest.CodecIndex = FindMethod_Index( EXTERNAL_CODECS_VARS - m.MethodName, dest.Id, dest.NumStreams)) + m.MethodName, true, + dest.Id, dest.NumStreams, isFilter); + if (dest.CodecIndex < 0) return E_INVALIDARG; (CProps &)dest = (CProps &)m; return S_OK; @@ -64,15 +73,12 @@ HRESULT CHandler::SetHeaderMethod(CCompressionMethodMode &headerMethod) return PropsMethod_To_FullMethod(methodFull, m); } -HRESULT CHandler::SetMainMethod( - CCompressionMethodMode &methodMode - #ifndef _7ZIP_ST - , UInt32 numThreads - #endif - ) + +HRESULT CHandler::SetMainMethod(CCompressionMethodMode &methodMode) { methodMode.Bonds = _bonds; + // we create local copy of _methods. So we can modify it. CObjectVector methods = _methods; { @@ -105,61 +111,202 @@ HRESULT CHandler::SetMainMethod( } } - const UInt64 kSolidBytes_Min = (1 << 24); - const UInt64 kSolidBytes_Max = ((UInt64)1 << 32) - 1; + const UInt64 kSolidBytes_Min = 1 << 24; + const UInt64 kSolidBytes_Max = (UInt64)1 << 32; // for non-LZMA2 methods bool needSolid = false; FOR_VECTOR (i, methods) { COneMethodInfo &oneMethodInfo = methods[i]; - SetGlobalLevelAndThreads(oneMethodInfo - #ifndef _7ZIP_ST - , numThreads - #endif - ); + + SetGlobalLevelTo(oneMethodInfo); + +#ifndef Z7_ST + const bool numThreads_WasSpecifiedInMethod = (oneMethodInfo.Get_NumThreads() >= 0); + if (!numThreads_WasSpecifiedInMethod) + { + // here we set the (NCoderPropID::kNumThreads) property in each method, only if there is no such property already + CMultiMethodProps::SetMethodThreadsTo_IfNotFinded(oneMethodInfo, methodMode.NumThreads); + } + if (methodMode.NumThreadGroups > 1) + CMultiMethodProps::Set_Method_NumThreadGroups_IfNotFinded(oneMethodInfo, methodMode.NumThreadGroups); +#endif CMethodFull &methodFull = methodMode.Methods.AddNew(); - RINOK(PropsMethod_To_FullMethod(methodFull, oneMethodInfo)); + RINOK(PropsMethod_To_FullMethod(methodFull, oneMethodInfo)) + +#ifndef Z7_ST + methodFull.Set_NumThreads = true; + methodFull.NumThreads = methodMode.NumThreads; +#endif if (methodFull.Id != k_Copy) needSolid = true; - if (_numSolidBytesDefined) - continue; - - UInt32 dicSize; + UInt64 dicSize; switch (methodFull.Id) { case k_LZMA: case k_LZMA2: dicSize = oneMethodInfo.Get_Lzma_DicSize(); break; case k_PPMD: dicSize = oneMethodInfo.Get_Ppmd_MemSize(); break; case k_Deflate: dicSize = (UInt32)1 << 15; break; + case k_Deflate64: dicSize = (UInt32)1 << 16; break; case k_BZip2: dicSize = oneMethodInfo.Get_BZip2_BlockSize(); break; + // case k_ZSTD: dicSize = 1 << 23; break; default: continue; } - - _numSolidBytes = (UInt64)dicSize << 7; - if (_numSolidBytes < kSolidBytes_Min) _numSolidBytes = kSolidBytes_Min; - if (_numSolidBytes > kSolidBytes_Max) _numSolidBytes = kSolidBytes_Max; + + UInt64 numSolidBytes; + + /* + if (methodFull.Id == k_ZSTD) + { + // continue; + NCompress::NZstd::CEncoderProps encoderProps; + RINOK(oneMethodInfo.Set_PropsTo_zstd(encoderProps)); + CZstdEncProps &zstdProps = encoderProps.EncProps; + ZstdEncProps_NormalizeFull(&zstdProps); + UInt64 cs = (UInt64)(zstdProps.jobSize); + UInt32 winSize = (UInt32)(1 << zstdProps.windowLog); + if (cs < winSize) + cs = winSize; + numSolidBytes = cs << 6; + const UInt64 kSolidBytes_Zstd_Max = ((UInt64)1 << 34); + if (numSolidBytes > kSolidBytes_Zstd_Max) + numSolidBytes = kSolidBytes_Zstd_Max; + + methodFull.Set_NumThreads = false; // we don't use ICompressSetCoderMt::SetNumberOfThreads() for LZMA2 encoder + + #ifndef Z7_ST + if (!numThreads_WasSpecifiedInMethod + && !methodMode.NumThreads_WasForced + && methodMode.MemoryUsageLimit_WasSet + ) + { + const UInt32 numThreads_Original = methodMode.NumThreads; + const UInt32 numThreads_New = ZstdEncProps_GetNumThreads_for_MemUsageLimit( + &zstdProps, + methodMode.MemoryUsageLimit, + numThreads_Original); + if (numThreads_Original != numThreads_New) + { + CMultiMethodProps::SetMethodThreadsTo_Replace(methodFull, numThreads_New); + } + } + #endif + } + else + */ + if (methodFull.Id == k_LZMA2) + { + // he we calculate default chunk Size for LZMA2 as defined in LZMA2 encoder code + /* lzma2 code use dictionary up to fake 4 GiB to calculate ChunkSize. + So we do same */ + UInt64 cs = (UInt64)dicSize << 2; + const UInt32 kMinSize = (UInt32)1 << 20; + const UInt32 kMaxSize = (UInt32)1 << 28; + if (cs < kMinSize) cs = kMinSize; + if (cs > kMaxSize) cs = kMaxSize; + if (cs < dicSize) cs = dicSize; + cs += (kMinSize - 1); + cs &= ~(UInt64)(kMinSize - 1); + // we want to use at least 64 chunks (threads) per one solid block. + + // here we don't use chunkSize property + numSolidBytes = cs << 6; + + // here we get real chunkSize + cs = oneMethodInfo.Get_Xz_BlockSize(); + if (dicSize > cs) + dicSize = cs; + + const UInt64 kSolidBytes_Lzma2_Max = (UInt64)1 << 34; + if (numSolidBytes > kSolidBytes_Lzma2_Max) + numSolidBytes = kSolidBytes_Lzma2_Max; + + methodFull.Set_NumThreads = false; // we don't use ICompressSetCoderMt::SetNumberOfThreads() for LZMA2 encoder + + #ifndef Z7_ST + if (!numThreads_WasSpecifiedInMethod + && !methodMode.NumThreads_WasForced + && methodMode.MemoryUsageLimit_WasSet) + { + const UInt32 lzmaThreads = oneMethodInfo.Get_Lzma_NumThreads(); + const UInt32 numBlockThreads_Original = methodMode.NumThreads / lzmaThreads; + + if (numBlockThreads_Original > 1) + { + /* + const UInt32 kNumThreads_Max = 1024; + if (numBlockThreads > kNumMaxThreads) + numBlockThreads = kNumMaxThreads; + */ + + UInt32 numBlockThreads = numBlockThreads_Original; + const UInt64 lzmaMemUsage = oneMethodInfo.Get_Lzma_MemUsage(false); // solid + + for (; numBlockThreads > 1; numBlockThreads--) + { + UInt64 size = numBlockThreads * (lzmaMemUsage + cs); + UInt32 numPackChunks = numBlockThreads + (numBlockThreads / 8) + 1; + if (cs < ((UInt32)1 << 26)) numPackChunks++; + if (cs < ((UInt32)1 << 24)) numPackChunks++; + if (cs < ((UInt32)1 << 22)) numPackChunks++; + size += numPackChunks * cs; + // printf("\nnumBlockThreads = %d, size = %d\n", (unsigned)(numBlockThreads), (unsigned)(size >> 20)); + if (size <= methodMode.MemoryUsageLimit) + break; + } + + if (numBlockThreads == 0) + numBlockThreads = 1; + if (numBlockThreads != numBlockThreads_Original) + { + const UInt32 numThreads_New = numBlockThreads * lzmaThreads; + CMultiMethodProps::SetMethodThreadsTo_Replace(methodFull, numThreads_New); + } + } + } + #endif + } + else + { + numSolidBytes = (UInt64)dicSize << 7; + if (numSolidBytes > kSolidBytes_Max) + numSolidBytes = kSolidBytes_Max; + } + + if (_numSolidBytesDefined) + continue; + + if (numSolidBytes < kSolidBytes_Min) + numSolidBytes = kSolidBytes_Min; + _numSolidBytes = numSolidBytes; _numSolidBytesDefined = true; } if (!_numSolidBytesDefined) + { if (needSolid) _numSolidBytes = kSolidBytes_Max; else _numSolidBytes = 0; + } _numSolidBytesDefined = true; + + return S_OK; } -static HRESULT GetTime(IArchiveUpdateCallback *updateCallback, int index, PROPID propID, UInt64 &ft, bool &ftDefined) + + +static HRESULT GetTime(IArchiveUpdateCallback *updateCallback, unsigned index, PROPID propID, UInt64 &ft, bool &ftDefined) { // ft = 0; // ftDefined = false; NCOM::CPropVariant prop; - RINOK(updateCallback->GetProperty(index, propID, &prop)); + RINOK(updateCallback->GetProperty(index, propID, &prop)) if (prop.vt == VT_FILETIME) { ft = prop.filetime.dwLowDateTime | ((UInt64)prop.filetime.dwHighDateTime << 32); @@ -242,13 +389,13 @@ static int AddFolder(CObjectVector &treeFolders, int cur, const USt } */ -STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numItems, - IArchiveUpdateCallback *updateCallback) +Z7_COM7F_IMF(CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numItems, + IArchiveUpdateCallback *updateCallback)) { COM_TRY_BEGIN - const CDbEx *db = 0; - #ifdef _7Z_VOL + const CDbEx *db = NULL; + #ifdef Z7_7Z_VOL if (_volumes.Size() > 1) return E_FAIL; const CVolume *volume = 0; @@ -258,13 +405,17 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numIt db = &volume->Database; } #else - if (_inStream != 0) + if (_inStream) db = &_db; #endif + if (db && !db->CanUpdate()) + return E_NOTIMPL; + /* - CMyComPtr getRawProps; - updateCallback->QueryInterface(IID_IArchiveGetRawProps, (void **)&getRawProps); + Z7_DECL_CMyComPtr_QI_FROM( + IArchiveGetRawProps, + getRawProps, updateCallback) CUniqBlocks secureBlocks; secureBlocks.AddUniq(NULL, 0); @@ -279,18 +430,21 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numIt CObjectVector updateItems; - bool need_CTime = (Write_CTime.Def && Write_CTime.Val); - bool need_ATime = (Write_ATime.Def && Write_ATime.Val); - bool need_MTime = (Write_MTime.Def && Write_MTime.Val || !Write_MTime.Def); + bool need_CTime = (TimeOptions.Write_CTime.Def && TimeOptions.Write_CTime.Val); + bool need_ATime = (TimeOptions.Write_ATime.Def && TimeOptions.Write_ATime.Val); + bool need_MTime = (TimeOptions.Write_MTime.Def ? TimeOptions.Write_MTime.Val : true); + bool need_Attrib = (Write_Attrib.Def ? Write_Attrib.Val : true); if (db && !db->Files.IsEmpty()) { - if (!Write_CTime.Def) need_CTime = !db->CTime.Defs.IsEmpty(); - if (!Write_ATime.Def) need_ATime = !db->ATime.Defs.IsEmpty(); - if (!Write_MTime.Def) need_MTime = !db->MTime.Defs.IsEmpty(); + if (!TimeOptions.Write_CTime.Def) need_CTime = !db->CTime.Defs.IsEmpty(); + if (!TimeOptions.Write_ATime.Def) need_ATime = !db->ATime.Defs.IsEmpty(); + if (!TimeOptions.Write_MTime.Def) need_MTime = !db->MTime.Defs.IsEmpty(); + if (!Write_Attrib.Def) need_Attrib = !db->Attrib.Defs.IsEmpty(); } - UString s; + // UString s; + UString name; for (UInt32 i = 0; i < numItems; i++) { @@ -298,45 +452,46 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numIt UInt32 indexInArchive; if (!updateCallback) return E_FAIL; - RINOK(updateCallback->GetUpdateItemInfo(i, &newData, &newProps, &indexInArchive)); + RINOK(updateCallback->GetUpdateItemInfo(i, &newData, &newProps, &indexInArchive)) CUpdateItem ui; ui.NewProps = IntToBool(newProps); ui.NewData = IntToBool(newData); - ui.IndexInArchive = indexInArchive; + ui.IndexInArchive = (int)indexInArchive; ui.IndexInClient = i; ui.IsAnti = false; ui.Size = 0; - UString name; + name.Empty(); // bool isAltStream = false; if (ui.IndexInArchive != -1) { - if (db == 0 || (unsigned)ui.IndexInArchive >= db->Files.Size()) + if (!db || (unsigned)ui.IndexInArchive >= db->Files.Size()) return E_INVALIDARG; - const CFileItem &fi = db->Files[ui.IndexInArchive]; + const CFileItem &fi = db->Files[(unsigned)ui.IndexInArchive]; if (!ui.NewProps) { - _db.GetPath(ui.IndexInArchive, name); + _db.GetPath((unsigned)ui.IndexInArchive, name); } ui.IsDir = fi.IsDir; ui.Size = fi.Size; // isAltStream = fi.IsAltStream; - ui.IsAnti = db->IsItemAnti(ui.IndexInArchive); + ui.IsAnti = db->IsItemAnti((unsigned)ui.IndexInArchive); if (!ui.NewProps) { - ui.CTimeDefined = db->CTime.GetItem(ui.IndexInArchive, ui.CTime); - ui.ATimeDefined = db->ATime.GetItem(ui.IndexInArchive, ui.ATime); - ui.MTimeDefined = db->MTime.GetItem(ui.IndexInArchive, ui.MTime); + ui.CTimeDefined = db->CTime.GetItem((unsigned)ui.IndexInArchive, ui.CTime); + ui.ATimeDefined = db->ATime.GetItem((unsigned)ui.IndexInArchive, ui.ATime); + ui.MTimeDefined = db->MTime.GetItem((unsigned)ui.IndexInArchive, ui.MTime); } } if (ui.NewProps) { bool folderStatusIsDefined; + if (need_Attrib) { NCOM::CPropVariant prop; - RINOK(updateCallback->GetProperty(i, kpidAttrib, &prop)); + RINOK(updateCallback->GetProperty(i, kpidAttrib, &prop)) if (prop.vt == VT_EMPTY) ui.AttribDefined = false; else if (prop.vt != VT_UI4) @@ -349,9 +504,9 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numIt } // we need MTime to sort files. - if (need_CTime) RINOK(GetTime(updateCallback, i, kpidCTime, ui.CTime, ui.CTimeDefined)); - if (need_ATime) RINOK(GetTime(updateCallback, i, kpidATime, ui.ATime, ui.ATimeDefined)); - if (need_MTime) RINOK(GetTime(updateCallback, i, kpidMTime, ui.MTime, ui.MTimeDefined)); + if (need_CTime) RINOK(GetTime(updateCallback, i, kpidCTime, ui.CTime, ui.CTimeDefined)) + if (need_ATime) RINOK(GetTime(updateCallback, i, kpidATime, ui.ATime, ui.ATimeDefined)) + if (need_MTime) RINOK(GetTime(updateCallback, i, kpidMTime, ui.MTime, ui.MTimeDefined)) /* if (getRawProps) @@ -369,7 +524,7 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numIt { NCOM::CPropVariant prop; - RINOK(updateCallback->GetProperty(i, kpidPath, &prop)); + RINOK(updateCallback->GetProperty(i, kpidPath, &prop)) if (prop.vt == VT_EMPTY) { } @@ -377,12 +532,13 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numIt return E_INVALIDARG; else { - name = NItemName::MakeLegalName(prop.bstrVal); + name = prop.bstrVal; + NItemName::ReplaceSlashes_OsToUnix(name); } } { NCOM::CPropVariant prop; - RINOK(updateCallback->GetProperty(i, kpidIsDir, &prop)); + RINOK(updateCallback->GetProperty(i, kpidIsDir, &prop)) if (prop.vt == VT_EMPTY) folderStatusIsDefined = false; else if (prop.vt != VT_BOOL) @@ -396,7 +552,7 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numIt { NCOM::CPropVariant prop; - RINOK(updateCallback->GetProperty(i, kpidIsAnti, &prop)); + RINOK(updateCallback->GetProperty(i, kpidIsAnti, &prop)) if (prop.vt == VT_EMPTY) ui.IsAnti = false; else if (prop.vt != VT_BOOL) @@ -513,7 +669,7 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numIt if (!ui.IsDir) { NCOM::CPropVariant prop; - RINOK(updateCallback->GetProperty(i, kpidSize, &prop)); + RINOK(updateCallback->GetProperty(i, kpidSize, &prop)) if (prop.vt != VT_UI8) return E_INVALIDARG; ui.Size = (UInt64)prop.uhVal.QuadPart; @@ -537,32 +693,42 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numIt CCompressionMethodMode methodMode, headerMethod; - HRESULT res = SetMainMethod(methodMode - #ifndef _7ZIP_ST - , _numThreads - #endif - ); - RINOK(res); + methodMode.MemoryUsageLimit = _memUsage_Compress; + methodMode.MemoryUsageLimit_WasSet = _memUsage_WasSet; - RINOK(SetHeaderMethod(headerMethod)); - - #ifndef _7ZIP_ST - methodMode.NumThreads = _numThreads; - methodMode.MultiThreadMixer = _useMultiThreadMixer; - headerMethod.NumThreads = 1; - headerMethod.MultiThreadMixer = _useMultiThreadMixer; + #ifndef Z7_ST + { + UInt32 numThreads = _numThreads; + const UInt32 kNumThreads_Max = 1024; + if (numThreads > kNumThreads_Max) + numThreads = kNumThreads_Max; + methodMode.NumThreads = numThreads; + methodMode.NumThreads_WasForced = _numThreads_WasForced; + methodMode.MultiThreadMixer = _useMultiThreadMixer; +#ifdef _WIN32 + methodMode.NumThreadGroups = _numThreadGroups; // _change it +#endif + // headerMethod.NumThreads = 1; + headerMethod.MultiThreadMixer = _useMultiThreadMixer; + } #endif - CMyComPtr getPassword2; - updateCallback->QueryInterface(IID_ICryptoGetTextPassword2, (void **)&getPassword2); + const HRESULT res = SetMainMethod(methodMode); + RINOK(res) + + RINOK(SetHeaderMethod(headerMethod)) + + Z7_DECL_CMyComPtr_QI_FROM( + ICryptoGetTextPassword2, + getPassword2, updateCallback) methodMode.PasswordIsDefined = false; - methodMode.Password.Empty(); + methodMode.Password.Wipe_and_Empty(); if (getPassword2) { - CMyComBSTR password; + CMyComBSTR_Wipe password; Int32 passwordIsDefined; - RINOK(getPassword2->CryptoGetTextPassword2(&passwordIsDefined, &password)); + RINOK(getPassword2->CryptoGetTextPassword2(&passwordIsDefined, &password)) methodMode.PasswordIsDefined = IntToBool(passwordIsDefined); if (methodMode.PasswordIsDefined && password) methodMode.Password = password; @@ -572,7 +738,7 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numIt bool encryptHeaders = false; - #ifndef _NO_CRYPTO + #ifndef Z7_NO_CRYPTO if (!methodMode.PasswordIsDefined && _passwordIsDefined) { // if header is compressed, we use that password for updated archive @@ -585,7 +751,7 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numIt { if (_encryptHeadersSpecified) encryptHeaders = _encryptHeaders; - #ifndef _NO_CRYPTO + #ifndef Z7_NO_CRYPTO else encryptHeaders = _passwordIsDefined; #endif @@ -600,20 +766,32 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numIt if (numItems < 2) compressMainHeader = false; - int level = GetLevel(); + const int level = GetLevel(); CUpdateOptions options; + options.Need_CTime = need_CTime; + options.Need_ATime = need_ATime; + options.Need_MTime = need_MTime; + options.Need_Attrib = need_Attrib; + // options.Need_Crc = (_crcSize != 0); // for debug + options.Method = &methodMode; options.HeaderMethod = (_compressHeaders || encryptHeaders) ? &headerMethod : NULL; options.UseFilters = (level != 0 && _autoFilter && !methodMode.Filter_was_Inserted); options.MaxFilter = (level >= 8); options.AnalysisLevel = GetAnalysisLevel(); + options.SetFilterSupporting_ver_enabled_disabled( + _decoderCompatibilityVersion, + _enabledFilters, + _disabledFilters); + options.HeaderOptions.CompressMainHeader = compressMainHeader; /* options.HeaderOptions.WriteCTime = Write_CTime; options.HeaderOptions.WriteATime = Write_ATime; options.HeaderOptions.WriteMTime = Write_MTime; + options.HeaderOptions.WriteAttrib = Write_Attrib; */ options.NumSolidFiles = _numSolidFiles; @@ -626,12 +804,6 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numIt options.MultiThreadMixer = _useMultiThreadMixer; - COutArchive archive; - CArchiveDatabaseOut newDatabase; - - CMyComPtr getPassword; - updateCallback->QueryInterface(IID_ICryptoGetTextPassword, (void **)&getPassword); - /* if (secureBlocks.Sorted.Size() > 1) { @@ -644,9 +816,9 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numIt } */ - res = Update( + return Update( EXTERNAL_CODECS_VARS - #ifdef _7Z_VOL + #ifdef Z7_7Z_VOL volume ? volume->Stream: 0, volume ? db : 0, #else @@ -656,18 +828,7 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numIt updateItems, // treeFolders, // secureBlocks, - archive, newDatabase, outStream, updateCallback, options - #ifndef _NO_CRYPTO - , getPassword - #endif - ); - - RINOK(res); - - updateItems.ClearAndFree(); - - return archive.WriteDatabase(EXTERNAL_CODECS_VARS - newDatabase, options.HeaderMethod, options.HeaderOptions); + outStream, updateCallback, options); COM_TRY_END } @@ -676,7 +837,7 @@ static HRESULT ParseBond(UString &srcString, UInt32 &coder, UInt32 &stream) { stream = 0; { - unsigned index = ParseStringToUInt32(srcString, coder); + const unsigned index = ParseStringToUInt32(srcString, coder); if (index == 0) return E_INVALIDARG; srcString.DeleteFrontal(index); @@ -684,7 +845,7 @@ static HRESULT ParseBond(UString &srcString, UInt32 &coder, UInt32 &stream) if (srcString[0] == 's') { srcString.Delete(0); - unsigned index = ParseStringToUInt32(srcString, stream); + const unsigned index = ParseStringToUInt32(srcString, stream); if (index == 0) return E_INVALIDARG; srcString.DeleteFrontal(index); @@ -692,19 +853,16 @@ static HRESULT ParseBond(UString &srcString, UInt32 &coder, UInt32 &stream) return S_OK; } -void COutHandler::InitProps() +void COutHandler::InitProps7z() { - CMultiMethodProps::Init(); - _removeSfxBlock = false; _compressHeaders = true; _encryptHeadersSpecified = false; _encryptHeaders = false; // _useParents = false; - Write_CTime.Init(); - Write_ATime.Init(); - Write_MTime.Init(); + TimeOptions.Init(); + Write_Attrib.Init(); _useMultiThreadMixer = true; @@ -712,8 +870,20 @@ void COutHandler::InitProps() InitSolid(); _useTypeSorting = false; + + _decoderCompatibilityVersion = k_decoderCompatibilityVersion; + _enabledFilters.Clear(); + _disabledFilters.Clear(); +} + +void COutHandler::InitProps() +{ + CMultiMethodProps::Init(); + InitProps7z(); } + + HRESULT COutHandler::SetSolidFromString(const UString &s) { UString s2 = s; @@ -730,10 +900,10 @@ HRESULT COutHandler::SetSolidFromString(const UString &s) _solidExtension = true; continue; } - i += (int)(end - start); + i += (unsigned)(end - start); if (i == s2.Len()) return E_INVALIDARG; - wchar_t c = s2[i++]; + const wchar_t c = s2[i++]; if (c == 'f') { if (v < 1) @@ -754,6 +924,10 @@ HRESULT COutHandler::SetSolidFromString(const UString &s) } _numSolidBytes = (v << numBits); _numSolidBytesDefined = true; + /* + if (_numSolidBytes == 0) + _numSolidFiles = 1; + */ } } return S_OK; @@ -781,11 +955,34 @@ HRESULT COutHandler::SetSolidFromPROPVARIANT(const PROPVARIANT &value) static HRESULT PROPVARIANT_to_BoolPair(const PROPVARIANT &prop, CBoolPair &dest) { - RINOK(PROPVARIANT_to_bool(prop, dest.Val)); + RINOK(PROPVARIANT_to_bool(prop, dest.Val)) dest.Def = true; return S_OK; } +struct C_Id_Name_pair +{ + UInt32 Id; + const char *Name; +}; + +static const C_Id_Name_pair g_filter_pairs[] = +{ + { k_Delta, "Delta" }, + { k_ARM64, "ARM64" }, + { k_RISCV, "RISCV" }, + { k_SWAP2, "SWAP2" }, + { k_SWAP4, "SWAP4" }, + { k_BCJ, "BCJ" }, + { k_BCJ2 , "BCJ2" }, + { k_PPC, "PPC" }, + { k_IA64, "IA64" }, + { k_ARM, "ARM" }, + { k_ARMT, "ARMT" }, + { k_SPARC, "SPARC" } +}; + + HRESULT COutHandler::SetProperty(const wchar_t *nameSpec, const PROPVARIANT &value) { UString name = nameSpec; @@ -802,9 +999,9 @@ HRESULT COutHandler::SetProperty(const wchar_t *nameSpec, const PROPVARIANT &val return E_INVALIDARG; return SetSolidFromString(name); } - + UInt32 number; - int index = ParseStringToUInt32(name, number); + const unsigned index = ParseStringToUInt32(name, number); // UString realName = name.Ptr(index); if (index == 0) { @@ -815,31 +1012,80 @@ HRESULT COutHandler::SetProperty(const wchar_t *nameSpec, const PROPVARIANT &val if (name.IsEqualTo("hcf")) { bool compressHeadersFull = true; - RINOK(PROPVARIANT_to_bool(value, compressHeadersFull)); + RINOK(PROPVARIANT_to_bool(value, compressHeadersFull)) return compressHeadersFull ? S_OK: E_INVALIDARG; } if (name.IsEqualTo("he")) { - RINOK(PROPVARIANT_to_bool(value, _encryptHeaders)); + RINOK(PROPVARIANT_to_bool(value, _encryptHeaders)) _encryptHeadersSpecified = true; return S_OK; } - if (name.IsEqualTo("tc")) return PROPVARIANT_to_BoolPair(value, Write_CTime); - if (name.IsEqualTo("ta")) return PROPVARIANT_to_BoolPair(value, Write_ATime); - if (name.IsEqualTo("tm")) return PROPVARIANT_to_BoolPair(value, Write_MTime); + { + bool processed; + RINOK(TimeOptions.Parse(name, value, processed)) + if (processed) + { + if ( TimeOptions.Prec != (UInt32)(Int32)-1 + && TimeOptions.Prec != k_PropVar_TimePrec_0 + && TimeOptions.Prec != k_PropVar_TimePrec_HighPrec + && TimeOptions.Prec != k_PropVar_TimePrec_100ns) + return E_INVALIDARG; + return S_OK; + } + } + + if (name.IsEqualTo("tr")) return PROPVARIANT_to_BoolPair(value, Write_Attrib); if (name.IsEqualTo("mtf")) return PROPVARIANT_to_bool(value, _useMultiThreadMixer); if (name.IsEqualTo("qs")) return PROPVARIANT_to_bool(value, _useTypeSorting); + if (name.IsPrefixedBy_Ascii_NoCase("yv")) + { + name.Delete(0, 2); + UInt32 v = 1 << 16; // if no number is noit specified, we use big value + RINOK(ParsePropToUInt32(name, value, v)) + _decoderCompatibilityVersion = v; + // if (v == 0) _decoderCompatibilityVersion = k_decoderCompatibilityVersion; + return S_OK; + } + + if (name.IsPrefixedBy_Ascii_NoCase("yf")) + { + name.Delete(0, 2); + CUIntVector *vec; + if (name.IsEqualTo_Ascii_NoCase("a")) vec = &_enabledFilters; + else if (name.IsEqualTo_Ascii_NoCase("d")) vec = &_disabledFilters; + else return E_INVALIDARG; + + if (value.vt != VT_BSTR) + return E_INVALIDARG; + for (unsigned k = 0;; k++) + { + if (k == Z7_ARRAY_SIZE(g_filter_pairs)) + { + // maybe we can ignore unsupported filter names here? + return E_INVALIDARG; + } + const C_Id_Name_pair &pair = g_filter_pairs[k]; + if (StringsAreEqualNoCase_Ascii(value.bstrVal, pair.Name)) + { + vec->AddToUniqueSorted(pair.Id); + break; + } + } + return S_OK; + } + // if (name.IsEqualTo("v")) return PROPVARIANT_to_bool(value, _volumeMode); } return CMultiMethodProps::SetProperty(name, value); } -STDMETHODIMP CHandler::SetProperties(const wchar_t * const *names, const PROPVARIANT *values, UInt32 numProps) +Z7_COM7F_IMF(CHandler::SetProperties(const wchar_t * const *names, const PROPVARIANT *values, UInt32 numProps)) { COM_TRY_BEGIN _bonds.Clear(); @@ -854,6 +1100,7 @@ STDMETHODIMP CHandler::SetProperties(const wchar_t * const *names, const PROPVAR const PROPVARIANT &value = values[i]; + if (name.Find(L':') >= 0) // 'b' was used as NCoderPropID::kBlockSize2 before v23 if (name[0] == 'b') { if (value.vt != VT_EMPTY) @@ -861,12 +1108,12 @@ STDMETHODIMP CHandler::SetProperties(const wchar_t * const *names, const PROPVAR name.Delete(0); CBond2 bond; - RINOK(ParseBond(name, bond.OutCoder, bond.OutStream)); + RINOK(ParseBond(name, bond.OutCoder, bond.OutStream)) if (name[0] != ':') return E_INVALIDARG; name.Delete(0); UInt32 inStream = 0; - RINOK(ParseBond(name, bond.InCoder, inStream)); + RINOK(ParseBond(name, bond.InCoder, inStream)) if (inStream != 0) return E_INVALIDARG; if (!name.IsEmpty()) @@ -875,7 +1122,7 @@ STDMETHODIMP CHandler::SetProperties(const wchar_t * const *names, const PROPVAR continue; } - RINOK(SetProperty(name, value)); + RINOK(SetProperty(name, value)) } unsigned numEmptyMethods = GetNumEmptyMethods(); @@ -911,3 +1158,5 @@ STDMETHODIMP CHandler::SetProperties(const wchar_t * const *names, const PROPVAR } }} + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zHeader.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zHeader.cpp index acff2fdd..d5f94973 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zHeader.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zHeader.cpp @@ -8,7 +8,7 @@ namespace NArchive { namespace N7z { Byte kSignature[kSignatureSize] = {'7', 'z', 0xBC, 0xAF, 0x27, 0x1C}; -#ifdef _7Z_VOL +#ifdef Z7_7Z_VOL Byte kFinishSignature[kSignatureSize] = {'7', 'z', 0xBC, 0xAF, 0x27, 0x1C + 1}; #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zHeader.h b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zHeader.h index d7f0ae36..22e79607 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zHeader.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zHeader.h @@ -1,7 +1,7 @@ // 7z/7zHeader.h -#ifndef __7Z_HEADER_H -#define __7Z_HEADER_H +#ifndef ZIP7_INC_7Z_HEADER_H +#define ZIP7_INC_7Z_HEADER_H #include "../../../Common/MyTypes.h" @@ -11,13 +11,13 @@ namespace N7z { const unsigned kSignatureSize = 6; extern Byte kSignature[kSignatureSize]; -// #define _7Z_VOL +// #define Z7_7Z_VOL // 7z-MultiVolume is not finished yet. // It can work already, but I still do not like some // things of that new multivolume format. // So please keep it commented. -#ifdef _7Z_VOL +#ifdef Z7_7Z_VOL extern Byte kFinishSignature[kSignatureSize]; #endif @@ -38,7 +38,7 @@ struct CStartHeader const UInt32 kStartHeaderSize = 20; -#ifdef _7Z_VOL +#ifdef Z7_7Z_VOL struct CFinishHeader: public CStartHeader { UInt64 ArchiveStartOffset; // data offset from end if that struct @@ -99,6 +99,8 @@ namespace NID const UInt32 k_Copy = 0; const UInt32 k_Delta = 3; +const UInt32 k_ARM64 = 0xa; +const UInt32 k_RISCV = 0xb; const UInt32 k_LZMA2 = 0x21; @@ -108,8 +110,9 @@ const UInt32 k_SWAP4 = 0x20304; const UInt32 k_LZMA = 0x30101; const UInt32 k_PPMD = 0x30401; -const UInt32 k_Deflate = 0x40108; -const UInt32 k_BZip2 = 0x40202; +const UInt32 k_Deflate = 0x40108; +const UInt32 k_Deflate64 = 0x40109; +const UInt32 k_BZip2 = 0x40202; const UInt32 k_BCJ = 0x3030103; const UInt32 k_BCJ2 = 0x303011B; @@ -121,14 +124,18 @@ const UInt32 k_SPARC = 0x3030805; const UInt32 k_AES = 0x6F10701; +// const UInt32 k_ZSTD = 0x4015D; // winzip zstd +// 0x4F71101, 7z-zstd -static inline bool IsFilterMethod(UInt64 m) +inline bool IsFilterMethod(UInt64 m) { - if (m > (UInt64)0xFFFFFFFF) + if (m > (UInt32)0xFFFFFFFF) return false; switch ((UInt32)m) { case k_Delta: + case k_ARM64: + case k_RISCV: case k_BCJ: case k_BCJ2: case k_PPC: @@ -139,6 +146,7 @@ static inline bool IsFilterMethod(UInt64 m) case k_SWAP2: case k_SWAP4: return true; + default: break; } return false; } diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zIn.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zIn.cpp index d8c27175..eb39478f 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zIn.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zIn.cpp @@ -11,6 +11,9 @@ #include "../../../../C/7zCrc.h" #include "../../../../C/CpuArch.h" +#include "../../../Common/MyBuffer2.h" +// #include "../../../Common/UTFConvert.h" + #include "../../Common/StreamObjects.h" #include "../../Common/StreamUtils.h" @@ -22,30 +25,62 @@ #define Get64(p) GetUi64(p) // define FORMAT_7Z_RECOVERY if you want to recover multivolume archives with empty StartHeader -#ifndef _SFX +#ifndef Z7_SFX #define FORMAT_7Z_RECOVERY #endif using namespace NWindows; using namespace NCOM; -namespace NArchive { -namespace N7z { +unsigned BoolVector_CountSum(const CBoolVector &v); +Z7_NO_INLINE +unsigned BoolVector_CountSum(const CBoolVector &v) +{ + unsigned sum = 0; + const unsigned size = v.Size(); + if (size) + { + const bool *p = v.ConstData(); + const bool * const lim = p + size; + do + if (*p) + sum++; + while (++p != lim); + } + return sum; +} + +static inline bool BoolVector_Item_IsValidAndTrue(const CBoolVector &v, unsigned i) +{ + return i < v.Size() ? v[i] : false; +} +Z7_NO_INLINE static void BoolVector_Fill_False(CBoolVector &v, unsigned size) { v.ClearAndSetSize(size); - bool *p = &v[0]; + bool *p = v.NonConstData(); for (unsigned i = 0; i < size; i++) p[i] = false; } + +namespace NArchive { +namespace N7z { + +#define k_Scan_NumCoders_MAX 64 +#define k_Scan_NumCodersStreams_in_Folder_MAX 64 + class CInArchiveException {}; class CUnsupportedFeatureException: public CInArchiveException {}; +Z7_ATTR_NORETURN static void ThrowException() { throw CInArchiveException(); } +Z7_ATTR_NORETURN static inline void ThrowEndOfData() { ThrowException(); } +Z7_ATTR_NORETURN static inline void ThrowUnsupported() { throw CUnsupportedFeatureException(); } +Z7_ATTR_NORETURN static inline void ThrowIncorrect() { ThrowException(); } class CStreamSwitch @@ -90,12 +125,12 @@ void CStreamSwitch::Set(CInArchive *archive, const CByteBuffer &byteBuffer) void CStreamSwitch::Set(CInArchive *archive, const CObjectVector *dataVector) { Remove(); - Byte external = archive->ReadByte(); + const Byte external = archive->ReadByte(); if (external != 0) { if (!dataVector) ThrowIncorrect(); - CNum dataIndex = archive->ReadNum(); + const CNum dataIndex = archive->ReadNum(); if (dataIndex >= dataVector->Size()) ThrowIncorrect(); Set(archive, (*dataVector)[dataIndex]); @@ -148,7 +183,7 @@ static UInt64 ReadNumberSpec(const Byte *p, size_t size, size_t &processed) return 0; } - unsigned b = *p++; + const unsigned b = *p++; size--; if ((b & 0x80) == 0) @@ -169,10 +204,10 @@ static UInt64 ReadNumberSpec(const Byte *p, size_t size, size_t &processed) for (unsigned i = 1; i < 8; i++) { - unsigned mask = (unsigned)0x80 >> i; + const unsigned mask = (unsigned)0x80 >> i; if ((b & mask) == 0) { - UInt64 high = b & (mask - 1); + const UInt64 high = b & (mask - 1); value |= (high << (i * 8)); processed = i + 1; return value; @@ -196,7 +231,7 @@ static UInt64 ReadNumberSpec(const Byte *p, size_t size, size_t &processed) UInt64 CInByte2::ReadNumber() { size_t processed; - UInt64 res = ReadNumberSpec(_buffer + _pos, _size - _pos, processed); + const UInt64 res = ReadNumberSpec(_buffer + _pos, _size - _pos, processed); if (processed == 0) ThrowEndOfData(); _pos += processed; @@ -216,7 +251,7 @@ CNum CInByte2::ReadNum() } } */ - UInt64 value = ReadNumber(); + const UInt64 value = ReadNumber(); if (value > kNumMax) ThrowUnsupported(); return (CNum)value; @@ -226,7 +261,7 @@ UInt32 CInByte2::ReadUInt32() { if (_pos + 4 > _size) ThrowEndOfData(); - UInt32 res = Get32(_buffer + _pos); + const UInt32 res = Get32(_buffer + _pos); _pos += 4; return res; } @@ -235,54 +270,101 @@ UInt64 CInByte2::ReadUInt64() { if (_pos + 8 > _size) ThrowEndOfData(); - UInt64 res = Get64(_buffer + _pos); + const UInt64 res = Get64(_buffer + _pos); _pos += 8; return res; } -#define CHECK_SIGNATURE if (p[0] != '7' || p[1] != 'z' || p[2] != 0xBC || p[3] != 0xAF || p[4] != 0x27 || p[5] != 0x1C) return false; +#define Y0 '7' +#define Y1 'z' +#define Y2 0xBC +#define Y3 0xAF +#define Y4 0x27 +#define Y5 0x1C + +#define IS_SIGNATURE(p)( \ + (p)[2] == Y2 && \ + (p)[3] == Y3 && \ + (p)[5] == Y5 && \ + (p)[4] == Y4 && \ + (p)[1] == Y1 && \ + (p)[0] == Y0) + +/* FindSignature_10() is allowed to access data up to and including &limit[9]. + limit[10] access is not allowed. + return: + (return_ptr < limit) : signature was found at (return_ptr) + (return_ptr >= limit) : limit was reached or crossed. So no signature found before limit +*/ +Z7_NO_INLINE +static const Byte *FindSignature_10(const Byte *p, const Byte *limit) +{ + for (;;) + { + for (;;) + { + if (p >= limit) + return limit; + const Byte b = p[5]; + p += 6; + if (b == Y0) { break; } + if (b == Y1) { p -= 1; break; } + if (b == Y2) { p -= 2; break; } + if (b == Y3) { p -= 3; break; } + if (b == Y4) { p -= 4; break; } + if (b == Y5) { p -= 5; break; } + } + if (IS_SIGNATURE(p - 1)) + return p - 1; + } +} -static inline bool TestSignature(const Byte *p) + +static inline bool TestStartCrc(const Byte *p) { - CHECK_SIGNATURE return CrcCalc(p + 12, 20) == Get32(p + 8); } -#ifdef FORMAT_7Z_RECOVERY static inline bool TestSignature2(const Byte *p) { - CHECK_SIGNATURE; - if (CrcCalc(p + 12, 20) == Get32(p + 8)) + if (!IS_SIGNATURE(p)) + return false; + #ifdef FORMAT_7Z_RECOVERY + if (TestStartCrc(p)) return true; for (unsigned i = 8; i < kHeaderSize; i++) if (p[i] != 0) return false; return (p[6] != 0 || p[7] != 0); + #else + return TestStartCrc(p); + #endif } -#else -#define TestSignature2(p) TestSignature(p) -#endif + HRESULT CInArchive::FindAndReadSignature(IInStream *stream, const UInt64 *searchHeaderSizeLimit) { - RINOK(ReadStream_FALSE(stream, _header, kHeaderSize)); + RINOK(ReadStream_FALSE(stream, _header, kHeaderSize)) if (TestSignature2(_header)) return S_OK; if (searchHeaderSizeLimit && *searchHeaderSizeLimit == 0) return S_FALSE; - const UInt32 kBufSize = 1 << 15; - CByteArr buf(kBufSize); + const UInt32 kBufSize = (1 << 15) + kHeaderSize; // must be > (kHeaderSize * 2) + CAlignedBuffer1 buf(kBufSize); memcpy(buf, _header, kHeaderSize); UInt64 offset = 0; for (;;) { - UInt32 readSize = kBufSize - kHeaderSize; + UInt32 readSize = + (offset == 0) ? + kBufSize - kHeaderSize - kHeaderSize : + kBufSize - kHeaderSize; if (searchHeaderSizeLimit) { - UInt64 rem = *searchHeaderSizeLimit - offset; + const UInt64 rem = *searchHeaderSizeLimit - offset; if (readSize > rem) readSize = (UInt32)rem; if (readSize == 0) @@ -290,29 +372,28 @@ HRESULT CInArchive::FindAndReadSignature(IInStream *stream, const UInt64 *search } UInt32 processed = 0; - RINOK(stream->Read(buf + kHeaderSize, readSize, &processed)); + RINOK(stream->Read(buf + kHeaderSize, readSize, &processed)) if (processed == 0) return S_FALSE; + + /* &buf[0] was already tested for signature before. + So first search here will be for &buf[1] */ for (UInt32 pos = 0;;) { const Byte *p = buf + pos + 1; - const Byte *lim = buf + processed; - for (; p <= lim; p += 4) - { - if (p[0] == '7') break; - if (p[1] == '7') { p += 1; break; } - if (p[2] == '7') { p += 2; break; } - if (p[3] == '7') { p += 3; break; } - }; - if (p > lim) + const Byte *lim = buf + processed + 1; + /* we have (kHeaderSize - 1 = 31) filled bytes starting from (lim), + and it's safe to access just 10 bytes in that reserved area */ + p = FindSignature_10(p, lim); + if (p >= lim) break; pos = (UInt32)(p - buf); - if (TestSignature(p)) + if (TestStartCrc(p)) { memcpy(_header, p, kHeaderSize); _arhiveBeginStreamPosition += offset + pos; - return stream->Seek(_arhiveBeginStreamPosition + kHeaderSize, STREAM_SEEK_SET, NULL); + return InStream_SeekSet(stream, _arhiveBeginStreamPosition + kHeaderSize); } } @@ -326,10 +407,8 @@ HRESULT CInArchive::Open(IInStream *stream, const UInt64 *searchHeaderSizeLimit) { HeadersSize = 0; Close(); - RINOK(stream->Seek(0, STREAM_SEEK_CUR, &_arhiveBeginStreamPosition)) - RINOK(stream->Seek(0, STREAM_SEEK_END, &_fileEndPosition)) - RINOK(stream->Seek(_arhiveBeginStreamPosition, STREAM_SEEK_SET, NULL)) - RINOK(FindAndReadSignature(stream, searchHeaderSizeLimit)); + RINOK(InStream_GetPos_GetSize(stream, _arhiveBeginStreamPosition, _fileEndPosition)) + RINOK(FindAndReadSignature(stream, searchHeaderSizeLimit)) _stream = stream; return S_OK; } @@ -355,9 +434,9 @@ void CInArchive::ReadArchiveProperties(CInArchiveInfo & /* archiveInfo */) void CInByte2::ParseFolder(CFolder &folder) { - UInt32 numCoders = ReadNum(); + const UInt32 numCoders = ReadNum(); - if (numCoders == 0) + if (numCoders == 0 || numCoders > k_Scan_NumCoders_MAX) ThrowUnsupported(); folder.Coders.SetSize(numCoders); @@ -368,10 +447,10 @@ void CInByte2::ParseFolder(CFolder &folder) { CCoderInfo &coder = folder.Coders[i]; { - Byte mainByte = ReadByte(); + const Byte mainByte = ReadByte(); if ((mainByte & 0xC0) != 0) ThrowUnsupported(); - unsigned idSize = (mainByte & 0xF); + const unsigned idSize = (mainByte & 0xF); if (idSize > 8 || idSize > GetRem()) ThrowUnsupported(); const Byte *longID = GetPtr(); @@ -384,7 +463,9 @@ void CInByte2::ParseFolder(CFolder &folder) if ((mainByte & 0x10) != 0) { coder.NumStreams = ReadNum(); + // if (coder.NumStreams > k_Scan_NumCodersStreams_in_Folder_MAX) ThrowUnsupported(); /* numOutStreams = */ ReadNum(); + // if (ReadNum() != 1) // numOutStreams ThrowUnsupported(); } else { @@ -393,7 +474,7 @@ void CInByte2::ParseFolder(CFolder &folder) if ((mainByte & 0x20) != 0) { - CNum propsSize = ReadNum(); + const CNum propsSize = ReadNum(); coder.Props.Alloc((size_t)propsSize); ReadBytes((Byte *)coder.Props, (size_t)propsSize); } @@ -403,7 +484,7 @@ void CInByte2::ParseFolder(CFolder &folder) numInStreams += coder.NumStreams; } - UInt32 numBonds = numCoders - 1; + const UInt32 numBonds = numCoders - 1; folder.Bonds.SetSize(numBonds); for (i = 0; i < numBonds; i++) { @@ -414,7 +495,7 @@ void CInByte2::ParseFolder(CFolder &folder) if (numInStreams < numBonds) ThrowUnsupported(); - UInt32 numPackStreams = numInStreams - numBonds; + const UInt32 numPackStreams = numInStreams - numBonds; folder.PackStreams.SetSize(numPackStreams); if (numPackStreams == 1) @@ -435,9 +516,9 @@ void CInByte2::ParseFolder(CFolder &folder) void CFolders::ParseFolderInfo(unsigned folderIndex, CFolder &folder) const { - size_t startPos = FoCodersDataOffset[folderIndex]; + const size_t startPos = FoCodersDataOffset[folderIndex]; CInByte2 inByte; - inByte.Init(CodersData + startPos, FoCodersDataOffset[folderIndex + 1] - startPos); + inByte.Init(CodersData.ConstData() + startPos, FoCodersDataOffset[folderIndex + 1] - startPos); inByte.ParseFolder(folder); if (inByte.GetRem() != 0) throw 20120424; @@ -450,8 +531,8 @@ void CDatabase::GetPath(unsigned index, UString &path) const if (!NameOffsets || !NamesBuf) return; - size_t offset = NameOffsets[index]; - size_t size = NameOffsets[index + 1] - offset; + const size_t offset = NameOffsets[index]; + const size_t size = NameOffsets[index + 1] - offset; if (size >= (1 << 28)) return; @@ -462,7 +543,7 @@ void CDatabase::GetPath(unsigned index, UString &path) const #if defined(_WIN32) && defined(MY_CPU_LE) - wmemcpy(s, (const wchar_t *)p, size); + wmemcpy(s, (const wchar_t *)(const void *)p, size); #else @@ -484,16 +565,33 @@ HRESULT CDatabase::GetPath_Prop(unsigned index, PROPVARIANT *path) const throw() if (!NameOffsets || !NamesBuf) return S_OK; - size_t offset = NameOffsets[index]; - size_t size = NameOffsets[index + 1] - offset; + const size_t offset = NameOffsets[index]; + const size_t size = NameOffsets[index + 1] - offset; if (size >= (1 << 14)) return S_OK; - RINOK(PropVarEm_Alloc_Bstr(path, (unsigned)size - 1)); + // (size) includes null terminator + + /* + #if WCHAR_MAX > 0xffff + + const Byte *p = ((const Byte *)NamesBuf + offset * 2); + size = Utf16LE__Get_Num_WCHARs(p, size - 1); + // (size) doesn't include null terminator + RINOK(PropVarEm_Alloc_Bstr(path, (unsigned)size)); wchar_t *s = path->bstrVal; + wchar_t *sEnd = Utf16LE__To_WCHARs_Sep(p, size, s); + *sEnd = 0; + if (s + size != sEnd) return E_FAIL; + #else + */ + + RINOK(PropVarEm_Alloc_Bstr(path, (unsigned)size - 1)) + wchar_t *s = path->bstrVal; const Byte *p = ((const Byte *)NamesBuf + offset * 2); + // Utf16LE__To_WCHARs_Sep(p, size, s); for (size_t i = 0; i < size; i++) { @@ -502,10 +600,14 @@ HRESULT CDatabase::GetPath_Prop(unsigned index, PROPVARIANT *path) const throw() #if WCHAR_PATH_SEPARATOR != L'/' if (c == L'/') c = WCHAR_PATH_SEPARATOR; + else if (c == L'\\') + c = WCHAR_IN_FILE_NAME_BACKSLASH_REPLACEMENT; // WSL scheme #endif *s++ = c; } + // #endif + return S_OK; /* @@ -557,7 +659,7 @@ void CInArchive::WaitId(UInt64 id) { for (;;) { - UInt64 type = ReadID(); + const UInt64 type = ReadID(); if (type == id) return; if (type == NID::kEnd) @@ -566,46 +668,54 @@ void CInArchive::WaitId(UInt64 id) } } -void CInArchive::ReadHashDigests(unsigned numItems, CUInt32DefVector &crcs) + +void CInArchive::Read_UInt32_Vector(CUInt32DefVector &v) { - ReadBoolVector2(numItems, crcs.Defs); - crcs.Vals.ClearAndSetSize(numItems); - UInt32 *p = &crcs.Vals[0]; - const bool *defs = &crcs.Defs[0]; + const unsigned numItems = v.Defs.Size(); + v.Vals.ClearAndSetSize(numItems); + UInt32 *p = &v.Vals[0]; + const bool *defs = &v.Defs[0]; for (unsigned i = 0; i < numItems; i++) { - UInt32 crc = 0; + UInt32 a = 0; if (defs[i]) - crc = ReadUInt32(); - p[i] = crc; + a = ReadUInt32(); + p[i] = a; } } -#define k_Scan_NumCoders_MAX 64 -#define k_Scan_NumCodersStreams_in_Folder_MAX 64 + +void CInArchive::ReadHashDigests(unsigned numItems, CUInt32DefVector &crcs) +{ + ReadBoolVector2(numItems, crcs.Defs); + Read_UInt32_Vector(crcs); +} + void CInArchive::ReadPackInfo(CFolders &f) { - CNum numPackStreams = ReadNum(); + const CNum numPackStreams = ReadNum(); WaitId(NID::kSize); f.PackPositions.Alloc(numPackStreams + 1); + UInt64 * const packPositions = f.PackPositions; f.NumPackStreams = numPackStreams; UInt64 sum = 0; - for (CNum i = 0; i < numPackStreams; i++) + for (CNum i = 0;;) { - f.PackPositions[i] = sum; - UInt64 packSize = ReadNumber(); + packPositions[i] = sum; + if (i == numPackStreams) + break; + i++; + const UInt64 packSize = ReadNumber(); sum += packSize; if (sum < packSize) ThrowIncorrect(); } - f.PackPositions[numPackStreams] = sum; - UInt64 type; for (;;) { - type = ReadID(); + const UInt64 type = ReadID(); if (type == NID::kEnd) return; if (type == NID::kCRC) @@ -623,7 +733,7 @@ void CInArchive::ReadUnpackInfo( CFolders &folders) { WaitId(NID::kFolder); - CNum numFolders = ReadNum(); + const CNum numFolders = ReadNum(); CNum numCodersOutStreams = 0; { @@ -648,21 +758,21 @@ void CInArchive::ReadUnpackInfo( { UInt32 indexOfMainStream = 0; UInt32 numPackStreams = 0; - folders.FoCodersDataOffset[fo] = _inByteBack->GetPtr() - startBufPtr; + folders.FoCodersDataOffset[fo] = (size_t)(_inByteBack->GetPtr() - startBufPtr); CNum numInStreams = 0; - CNum numCoders = inByte->ReadNum(); + const CNum numCoders = inByte->ReadNum(); if (numCoders == 0 || numCoders > k_Scan_NumCoders_MAX) ThrowUnsupported(); for (CNum ci = 0; ci < numCoders; ci++) { - Byte mainByte = inByte->ReadByte(); + const Byte mainByte = inByte->ReadByte(); if ((mainByte & 0xC0) != 0) ThrowUnsupported(); - unsigned idSize = (mainByte & 0xF); + const unsigned idSize = (mainByte & 0xF); if (idSize > 8) ThrowUnsupported(); if (idSize > inByte->GetRem()) @@ -691,18 +801,18 @@ void CInArchive::ReadUnpackInfo( if ((mainByte & 0x20) != 0) { - CNum propsSize = inByte->ReadNum(); + const CNum propsSize = inByte->ReadNum(); if (propsSize > inByte->GetRem()) ThrowEndOfData(); if (id == k_LZMA2 && propsSize == 1) { - Byte v = *_inByteBack->GetPtr(); + const Byte v = *_inByteBack->GetPtr(); if (folders.ParsedMethods.Lzma2Prop < v) folders.ParsedMethods.Lzma2Prop = v; } else if (id == k_LZMA && propsSize == 5) { - UInt32 dicSize = GetUi32(_inByteBack->GetPtr() + 1); + const UInt32 dicSize = GetUi32(_inByteBack->GetPtr() + 1); if (folders.ParsedMethods.LzmaDic < dicSize) folders.ParsedMethods.LzmaDic = dicSize; } @@ -718,7 +828,7 @@ void CInArchive::ReadUnpackInfo( else { UInt32 i; - CNum numBonds = numCoders - 1; + const CNum numBonds = numCoders - 1; if (numInStreams < numBonds) ThrowUnsupported(); @@ -743,25 +853,26 @@ void CInArchive::ReadUnpackInfo( if (numPackStreams != 1) for (i = 0; i < numPackStreams; i++) { - CNum index = inByte->ReadNum(); // PackStreams + const CNum index = inByte->ReadNum(); // PackStreams if (index >= numInStreams || StreamUsed[index]) ThrowUnsupported(); StreamUsed[index] = true; } - for (i = 0; i < numCoders; i++) + for (i = 0;;) + { if (!CoderUsed[i]) - { - indexOfMainStream = i; break; - } - - if (i == numCoders) - ThrowUnsupported(); + if (++i == numCoders) + ThrowUnsupported(); + } + indexOfMainStream = i; } folders.FoToCoderUnpackSizes[fo] = numCodersOutStreams; numCodersOutStreams += numCoders; + if (numCodersOutStreams < numCoders) + ThrowUnsupported(); folders.FoStartPackStreamIndex[fo] = packStreamIndex; if (numPackStreams > folders.NumPackStreams - packStreamIndex) ThrowIncorrect(); @@ -769,10 +880,10 @@ void CInArchive::ReadUnpackInfo( folders.FoToMainUnpackSizeIndex[fo] = (Byte)indexOfMainStream; } - size_t dataSize = _inByteBack->GetPtr() - startBufPtr; + const size_t dataSize = (size_t)(_inByteBack->GetPtr() - startBufPtr); folders.FoToCoderUnpackSizes[fo] = numCodersOutStreams; folders.FoStartPackStreamIndex[fo] = packStreamIndex; - folders.FoCodersDataOffset[fo] = _inByteBack->GetPtr() - startBufPtr; + folders.FoCodersDataOffset[fo] = dataSize; folders.CodersData.CopyFrom(startBufPtr, dataSize); // if (folders.NumPackStreams != packStreamIndex) ThrowUnsupported(); @@ -785,11 +896,13 @@ void CInArchive::ReadUnpackInfo( for (;;) { - UInt64 type = ReadID(); + const UInt64 type = ReadID(); if (type == NID::kEnd) return; if (type == NID::kCRC) { + if (!folders.FolderCRCs.Defs.IsEmpty()) + ThrowIncorrect(); ReadHashDigests(numFolders, folders.FolderCRCs); continue; } @@ -803,19 +916,35 @@ void CInArchive::ReadSubStreamsInfo( CUInt32DefVector &digests) { folders.NumUnpackStreamsVector.Alloc(folders.NumFolders); - CNum i; - for (i = 0; i < folders.NumFolders; i++) - folders.NumUnpackStreamsVector[i] = 1; + bool NumUnpackStream_isFilled = false; UInt64 type; + + CNum numUnpackStreams = folders.NumFolders; for (;;) { type = ReadID(); if (type == NID::kNumUnpackStream) { - for (i = 0; i < folders.NumFolders; i++) - folders.NumUnpackStreamsVector[i] = ReadNum(); + if (NumUnpackStream_isFilled) + ThrowIncorrect(); + NumUnpackStream_isFilled = true; + CNum numFolders = folders.NumFolders; + numUnpackStreams = 0; + if (numFolders) + { + UInt32 *dest = folders.NumUnpackStreamsVector.NonConstData(); + do + { + const CNum num = ReadNum(); + *dest++ = num; + numUnpackStreams += num; + if (numUnpackStreams < num) + ThrowUnsupported(); + } + while (--numFolders); + } continue; } if (type == NID::kCRC || type == NID::kSize || type == NID::kEnd) @@ -823,28 +952,46 @@ void CInArchive::ReadSubStreamsInfo( SkipData(); } + if (!NumUnpackStream_isFilled) + { + CNum numFolders = folders.NumFolders; + if (numFolders) + { + UInt32 *dest = folders.NumUnpackStreamsVector.NonConstData(); + do + *dest++ = 1; + while (--numFolders); + } + } + + unpackSizes.ClearAndReserve(numUnpackStreams); + CNum i; if (type == NID::kSize) { for (i = 0; i < folders.NumFolders; i++) { // v3.13 incorrectly worked with empty folders // v4.07: we check that folder is empty - CNum numSubstreams = folders.NumUnpackStreamsVector[i]; + const CNum numSubstreams = folders.NumUnpackStreamsVector[i]; if (numSubstreams == 0) continue; UInt64 sum = 0; for (CNum j = 1; j < numSubstreams; j++) { - UInt64 size = ReadNumber(); - unpackSizes.Add(size); + if (unpackSizes.Size() >= numUnpackStreams) + ThrowIncorrect(); // internal failure + const UInt64 size = ReadNumber(); + unpackSizes.AddInReserved(size); sum += size; if (sum < size) ThrowIncorrect(); } - UInt64 folderUnpackSize = folders.GetFolderUnpackSize(i); + if (unpackSizes.Size() >= numUnpackStreams) + ThrowIncorrect(); // internal failure + const UInt64 folderUnpackSize = folders.GetFolderUnpackSize(i); if (folderUnpackSize < sum) ThrowIncorrect(); - unpackSizes.Add(folderUnpackSize - sum); + unpackSizes.AddInReserved(folderUnpackSize - sum); } type = ReadID(); } @@ -854,18 +1001,25 @@ void CInArchive::ReadSubStreamsInfo( { /* v9.26 - v9.29 incorrectly worked: if (folders.NumUnpackStreamsVector[i] == 0), it threw error */ - CNum val = folders.NumUnpackStreamsVector[i]; + const CNum val = folders.NumUnpackStreamsVector[i]; if (val > 1) ThrowIncorrect(); if (val == 1) - unpackSizes.Add(folders.GetFolderUnpackSize(i)); + { + if (unpackSizes.Size() >= numUnpackStreams) + ThrowIncorrect(); // internal failure + unpackSizes.AddInReserved(folders.GetFolderUnpackSize(i)); + } } } + if (unpackSizes.Size() != numUnpackStreams) + ThrowIncorrect(); + unsigned numDigests = 0; for (i = 0; i < folders.NumFolders; i++) { - CNum numSubstreams = folders.NumUnpackStreamsVector[i]; + const CNum numSubstreams = folders.NumUnpackStreamsVector[i]; if (numSubstreams != 1 || !folders.FolderCRCs.ValidAndDefined(i)) numDigests += numSubstreams; } @@ -876,6 +1030,8 @@ void CInArchive::ReadSubStreamsInfo( break; if (type == NID::kCRC) { + if (!digests.Defs.IsEmpty()) + ThrowIncorrect(); // CUInt32DefVector digests2; // ReadHashDigests(numDigests, digests2); CBoolVector digests2; @@ -888,7 +1044,7 @@ void CInArchive::ReadSubStreamsInfo( for (i = 0; i < folders.NumFolders; i++) { - CNum numSubstreams = folders.NumUnpackStreamsVector[i]; + const CNum numSubstreams = folders.NumUnpackStreamsVector[i]; if (numSubstreams == 1 && folders.FolderCRCs.ValidAndDefined(i)) { digests.Defs[k] = true; @@ -920,7 +1076,7 @@ void CInArchive::ReadSubStreamsInfo( unsigned k = 0; for (i = 0; i < folders.NumFolders; i++) { - CNum numSubstreams = folders.NumUnpackStreamsVector[i]; + const CNum numSubstreams = folders.NumUnpackStreamsVector[i]; if (numSubstreams == 1 && folders.FolderCRCs.ValidAndDefined(i)) { digests.Defs[k] = true; @@ -937,6 +1093,8 @@ void CInArchive::ReadSubStreamsInfo( } } + + void CInArchive::ReadStreamsInfo( const CObjectVector *dataVector, UInt64 &dataOffset, @@ -949,7 +1107,11 @@ void CInArchive::ReadStreamsInfo( if (type == NID::kPackInfo) { dataOffset = ReadNumber(); + if (dataOffset > _rangeLimit) + ThrowIncorrect(); ReadPackInfo(folders); + if (folders.PackPositions[folders.NumPackStreams] > _rangeLimit - dataOffset) + ThrowIncorrect(); type = ReadID(); } @@ -978,10 +1140,13 @@ void CInArchive::ReadStreamsInfo( So we don't need to fill digests with values. */ // digests.Vals.ClearAndSetSize(folders.NumFolders); // BoolVector_Fill_False(digests.Defs, folders.NumFolders); + unpackSizes.ClearAndSetSize(folders.NumFolders); + UInt64 * const unpackSizes2 = unpackSizes.NonConstData(); + CNum * const numUnpackStreamsVector2 = folders.NumUnpackStreamsVector; for (CNum i = 0; i < folders.NumFolders; i++) { - folders.NumUnpackStreamsVector[i] = 1; - unpackSizes.Add(folders.GetFolderUnpackSize(i)); + numUnpackStreamsVector2[i] = 1; + unpackSizes2[i] = folders.GetFolderUnpackSize(i); // digests.Vals[i] = 0; } } @@ -1004,13 +1169,13 @@ void CInArchive::ReadBoolVector(unsigned numItems, CBoolVector &v) mask = 0x80; } p[i] = ((b & mask) != 0); - mask >>= 1; + mask = (Byte)(mask >> 1); } } void CInArchive::ReadBoolVector2(unsigned numItems, CBoolVector &v) { - Byte allAreDefined = ReadByte(); + const Byte allAreDefined = ReadByte(); if (allAreDefined == 0) { ReadBoolVector(numItems, v); @@ -1047,7 +1212,7 @@ HRESULT CInArchive::ReadAndDecodePackedStreams( DECL_EXTERNAL_CODECS_LOC_VARS UInt64 baseOffset, UInt64 &dataOffset, CObjectVector &dataVector - _7Z_DECODER_CRYPRO_VARS_DECL + Z7_7Z_DECODER_CRYPRO_VARS_DECL ) { CFolders folders; @@ -1065,34 +1230,45 @@ HRESULT CInArchive::ReadAndDecodePackedStreams( for (CNum i = 0; i < folders.NumFolders; i++) { CByteBuffer &data = dataVector.AddNew(); - UInt64 unpackSize64 = folders.GetFolderUnpackSize(i); - size_t unpackSize = (size_t)unpackSize64; + const UInt64 unpackSize64 = folders.GetFolderUnpackSize(i); + const size_t unpackSize = (size_t)unpackSize64; if (unpackSize != unpackSize64) ThrowUnsupported(); data.Alloc(unpackSize); - CBufPtrSeqOutStream *outStreamSpec = new CBufPtrSeqOutStream; - CMyComPtr outStream = outStreamSpec; + CMyComPtr2_Create outStreamSpec; outStreamSpec->Init(data, unpackSize); + bool dataAfterEnd_Error = false; + HRESULT result = decoder.Decode( EXTERNAL_CODECS_LOC_VARS _stream, baseOffset + dataOffset, folders, i, - NULL, // *unpackSize + NULL, // &unpackSize64 - outStream, + outStreamSpec, NULL, // *compressProgress + NULL // **inStreamMainRes + , dataAfterEnd_Error - _7Z_DECODER_CRYPRO_VARS - #if !defined(_7ZIP_ST) && !defined(_SFX) + Z7_7Z_DECODER_CRYPRO_VARS + #if !defined(Z7_ST) , false // mtMode , 1 // numThreads + , 0 // memUsage #endif ); - RINOK(result); + RINOK(result) + + if (dataAfterEnd_Error) + ThereIsHeaderError = true; + + if (unpackSize != outStreamSpec->GetPos()) + ThrowIncorrect(); + if (folders.FolderCRCs.ValidAndDefined(i)) if (CrcCalc(data, unpackSize) != folders.FolderCRCs.Vals[i]) ThrowIncorrect(); @@ -1107,7 +1283,7 @@ HRESULT CInArchive::ReadAndDecodePackedStreams( HRESULT CInArchive::ReadHeader( DECL_EXTERNAL_CODECS_LOC_VARS CDbEx &db - _7Z_DECODER_CRYPRO_VARS_DECL + Z7_7Z_DECODER_CRYPRO_VARS_DECL ) { UInt64 type = ReadID(); @@ -1122,14 +1298,14 @@ HRESULT CInArchive::ReadHeader( if (type == NID::kAdditionalStreamsInfo) { - HRESULT result = ReadAndDecodePackedStreams( + const HRESULT result = ReadAndDecodePackedStreams( EXTERNAL_CODECS_LOC_VARS db.ArcInfo.StartPositionAfterHeader, db.ArcInfo.DataStartPosition2, dataVector - _7Z_DECODER_CRYPRO_VARS + Z7_7Z_DECODER_CRYPRO_VARS ); - RINOK(result); + RINOK(result) db.ArcInfo.DataStartPosition2 += db.ArcInfo.StartPositionAfterHeader; type = ReadID(); } @@ -1148,19 +1324,10 @@ HRESULT CInArchive::ReadHeader( type = ReadID(); } - db.Files.Clear(); - if (type == NID::kFilesInfo) { const CNum numFiles = ReadNum(); - db.Files.ClearAndSetSize(numFiles); - /* - db.Files.Reserve(numFiles); - CNum i; - for (i = 0; i < numFiles; i++) - db.Files.Add(CFileItem()); - */ db.ArcInfo.FileInfoPopIDs.Add(NID::kSize); // if (!db.PackSizes.IsEmpty()) @@ -1169,17 +1336,16 @@ HRESULT CInArchive::ReadHeader( db.ArcInfo.FileInfoPopIDs.Add(NID::kCRC); CBoolVector emptyStreamVector; - BoolVector_Fill_False(emptyStreamVector, (unsigned)numFiles); CBoolVector emptyFileVector; CBoolVector antiFileVector; - CNum numEmptyStreams = 0; + unsigned numEmptyStreams = 0; for (;;) { const UInt64 type2 = ReadID(); if (type2 == NID::kEnd) break; - UInt64 size = ReadNumber(); + const UInt64 size = ReadNumber(); if (size > _inByteBack->GetRem()) ThrowIncorrect(); CStreamSwitch switchProp; @@ -1194,16 +1360,16 @@ HRESULT CInArchive::ReadHeader( { CStreamSwitch streamSwitch; streamSwitch.Set(this, &dataVector); - size_t rem = _inByteBack->GetRem(); + const size_t rem = _inByteBack->GetRem(); db.NamesBuf.Alloc(rem); ReadBytes(db.NamesBuf, rem); - db.NameOffsets.Alloc(db.Files.Size() + 1); + db.NameOffsets.Alloc(numFiles + 1); size_t pos = 0; unsigned i; - for (i = 0; i < db.Files.Size(); i++) + for (i = 0; i < numFiles; i++) { - size_t curRem = (rem - pos) / 2; - const UInt16 *buf = (const UInt16 *)(db.NamesBuf + pos); + const size_t curRem = (rem - pos) / 2; + const UInt16 *buf = (const UInt16 *)(const void *)(db.NamesBuf.ConstData() + pos); size_t j; for (j = 0; j < curRem && buf[j] != 0; j++); if (j == curRem) @@ -1216,36 +1382,31 @@ HRESULT CInArchive::ReadHeader( ThereIsHeaderError = true; break; } + case NID::kWinAttrib: { - CBoolVector boolVector; - ReadBoolVector2(db.Files.Size(), boolVector); + ReadBoolVector2(numFiles, db.Attrib.Defs); CStreamSwitch streamSwitch; streamSwitch.Set(this, &dataVector); - for (CNum i = 0; i < numFiles; i++) - { - CFileItem &file = db.Files[i]; - file.AttribDefined = boolVector[i]; - if (file.AttribDefined) - file.Attrib = ReadUInt32(); - } + Read_UInt32_Vector(db.Attrib); break; } + /* case NID::kIsAux: { - ReadBoolVector(db.Files.Size(), db.IsAux); + ReadBoolVector(numFiles, db.IsAux); break; } case NID::kParent: { db.IsTree = true; // CBoolVector boolVector; - // ReadBoolVector2(db.Files.Size(), boolVector); + // ReadBoolVector2(numFiles, boolVector); // CStreamSwitch streamSwitch; // streamSwitch.Set(this, &dataVector); CBoolVector boolVector; - ReadBoolVector2(db.Files.Size(), boolVector); + ReadBoolVector2(numFiles, boolVector); db.ThereAreAltStreams = false; for (i = 0; i < numFiles; i++) @@ -1264,14 +1425,9 @@ HRESULT CInArchive::ReadHeader( case NID::kEmptyStream: { ReadBoolVector(numFiles, emptyStreamVector); - numEmptyStreams = 0; - for (CNum i = 0; i < (CNum)emptyStreamVector.Size(); i++) - if (emptyStreamVector[i]) - numEmptyStreams++; - - BoolVector_Fill_False(emptyFileVector, numEmptyStreams); - BoolVector_Fill_False(antiFileVector, numEmptyStreams); - + numEmptyStreams = BoolVector_CountSum(emptyStreamVector); + emptyFileVector.Clear(); + antiFileVector.Clear(); break; } case NID::kEmptyFile: ReadBoolVector(numEmptyStreams, emptyFileVector); break; @@ -1314,7 +1470,7 @@ HRESULT CInArchive::ReadHeader( ReadBytes(db.SecureBuf + offset, db.SecureOffsets[i + 1] - offset); } db.SecureIDs.Clear(); - for (unsigned i = 0; i < db.Files.Size(); i++) + for (unsigned i = 0; i < numFiles; i++) { db.SecureIDs.Add(ReadNum()); // db.SecureIDs.Add(ReadUInt32()); @@ -1359,22 +1515,21 @@ HRESULT CInArchive::ReadHeader( CNum emptyFileIndex = 0; CNum sizeIndex = 0; - CNum numAntiItems = 0; + const unsigned numAntiItems = BoolVector_CountSum(antiFileVector); - CNum i; + if (numAntiItems != 0) + db.IsAnti.ClearAndSetSize(numFiles); - for (i = 0; i < numEmptyStreams; i++) - if (antiFileVector[i]) - numAntiItems++; + db.Files.ClearAndSetSize(numFiles); - for (i = 0; i < numFiles; i++) + for (CNum i = 0; i < numFiles; i++) { CFileItem &file = db.Files[i]; bool isAnti; - file.HasStream = !emptyStreamVector[i]; file.Crc = 0; - if (file.HasStream) + if (!BoolVector_Item_IsValidAndTrue(emptyStreamVector, i)) { + file.HasStream = true; file.IsDir = false; isAnti = false; file.Size = unpackSizes[sizeIndex]; @@ -1385,26 +1540,31 @@ HRESULT CInArchive::ReadHeader( } else { - file.IsDir = !emptyFileVector[emptyFileIndex]; - isAnti = antiFileVector[emptyFileIndex]; + file.HasStream = false; + file.IsDir = !BoolVector_Item_IsValidAndTrue(emptyFileVector, emptyFileIndex); + isAnti = BoolVector_Item_IsValidAndTrue(antiFileVector, emptyFileIndex); emptyFileIndex++; file.Size = 0; file.CrcDefined = false; } if (numAntiItems != 0) - db.IsAnti.Add(isAnti); + db.IsAnti[i] = isAnti; } + } + db.FillLinks(); - /* - if (type != NID::kEnd) - ThrowIncorrect(); - if (_inByteBack->GetRem() != 0) - ThrowIncorrect(); - */ + + if (type != NID::kEnd || _inByteBack->GetRem() != 0) + { + db.UnsupportedFeatureWarning = true; + // ThrowIncorrect(); + } + return S_OK; } + void CDbEx::FillLinks() { FolderStartFileIndex.Alloc(NumFolders); @@ -1416,7 +1576,7 @@ void CDbEx::FillLinks() for (i = 0; i < Files.Size(); i++) { - bool emptyStream = !Files[i].HasStream; + const bool emptyStream = !Files[i].HasStream; if (indexInFolder == 0) { if (emptyStream) @@ -1447,29 +1607,33 @@ void CDbEx::FillLinks() } if (indexInFolder != 0) + { folderIndex++; - /* - if (indexInFolder != 0) - ThrowIncorrect(); - */ + // 18.06 + ThereIsHeaderError = true; + // ThrowIncorrect(); + } for (;;) { if (folderIndex >= NumFolders) return; FolderStartFileIndex[folderIndex] = i; - /* if (NumUnpackStreamsVector[folderIndex] != 0) - ThrowIncorrect();; - */ + { + // 18.06 + ThereIsHeaderError = true; + // ThrowIncorrect(); + } folderIndex++; } } + HRESULT CInArchive::ReadDatabase2( DECL_EXTERNAL_CODECS_LOC_VARS CDbEx &db - _7Z_DECODER_CRYPRO_VARS_DECL + Z7_7Z_DECODER_CRYPRO_VARS_DECL ) { db.Clear(); @@ -1489,22 +1653,22 @@ HRESULT CInArchive::ReadDatabase2( UInt32 nextHeaderCRC = Get32(_header + 28); #ifdef FORMAT_7Z_RECOVERY - UInt32 crcFromArc = Get32(_header + 8); + const UInt32 crcFromArc = Get32(_header + 8); if (crcFromArc == 0 && nextHeaderOffset == 0 && nextHeaderSize == 0 && nextHeaderCRC == 0) { UInt64 cur, fileSize; - RINOK(_stream->Seek(0, STREAM_SEEK_CUR, &cur)); + RINOK(InStream_GetPos(_stream, cur)) const unsigned kCheckSize = 512; Byte buf[kCheckSize]; - RINOK(_stream->Seek(0, STREAM_SEEK_END, &fileSize)); - UInt64 rem = fileSize - cur; + RINOK(InStream_GetSize_SeekToEnd(_stream, fileSize)) + const UInt64 rem = fileSize - cur; unsigned checkSize = kCheckSize; if (rem < kCheckSize) checkSize = (unsigned)(rem); if (checkSize < 3) return S_FALSE; - RINOK(_stream->Seek(fileSize - checkSize, STREAM_SEEK_SET, NULL)); - RINOK(ReadStream_FALSE(_stream, buf, (size_t)checkSize)); + RINOK(InStream_SeekSet(_stream, fileSize - checkSize)) + RINOK(ReadStream_FALSE(_stream, buf, (size_t)checkSize)) if (buf[checkSize - 1] != 0) return S_FALSE; @@ -1512,8 +1676,8 @@ HRESULT CInArchive::ReadDatabase2( unsigned i; for (i = checkSize - 2;; i--) { - if (buf[i] == NID::kEncodedHeader && buf[i + 1] == NID::kPackInfo || - buf[i] == NID::kHeader && buf[i + 1] == NID::kMainStreamsInfo) + if ((buf[i] == NID::kEncodedHeader && buf[i + 1] == NID::kPackInfo) || + (buf[i] == NID::kHeader && buf[i + 1] == NID::kMainStreamsInfo)) break; if (i == 0) return S_FALSE; @@ -1521,7 +1685,7 @@ HRESULT CInArchive::ReadDatabase2( nextHeaderSize = checkSize - i; nextHeaderOffset = rem - nextHeaderSize; nextHeaderCRC = CrcCalc(buf + i, (size_t)nextHeaderSize); - RINOK(_stream->Seek(cur, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekSet(_stream, cur)) db.StartHeaderWasRecovered = true; } else @@ -1535,35 +1699,42 @@ HRESULT CInArchive::ReadDatabase2( db.PhySize = kHeaderSize; db.IsArc = false; - if ((Int64)nextHeaderOffset < 0 || - nextHeaderSize > ((UInt64)1 << 62)) + if (nextHeaderOffset >= (UInt64)1 << 62 || + nextHeaderSize > ((UInt64)1 << 48)) return S_FALSE; + + HeadersSize = kHeaderSize; + if (nextHeaderSize == 0) { - if (nextHeaderOffset != 0) + if (nextHeaderOffset != 0 || nextHeaderCRC != 0) return S_FALSE; db.IsArc = true; + db.HeadersSize = HeadersSize; return S_OK; } if (!db.StartHeaderWasRecovered) db.IsArc = true; - HeadersSize += kHeaderSize + nextHeaderSize; + HeadersSize += nextHeaderSize; + // db.EndHeaderOffset = nextHeaderOffset; + _rangeLimit = nextHeaderOffset; + db.PhySize = kHeaderSize + nextHeaderOffset + nextHeaderSize; if (_fileEndPosition - db.ArcInfo.StartPositionAfterHeader < nextHeaderOffset + nextHeaderSize) { db.UnexpectedEnd = true; return S_FALSE; } - RINOK(_stream->Seek(nextHeaderOffset, STREAM_SEEK_CUR, NULL)); + RINOK(_stream->Seek((Int64)nextHeaderOffset, STREAM_SEEK_CUR, NULL)) - size_t nextHeaderSize_t = (size_t)nextHeaderSize; + const size_t nextHeaderSize_t = (size_t)nextHeaderSize; if (nextHeaderSize_t != nextHeaderSize) return E_OUTOFMEMORY; CByteBuffer buffer2(nextHeaderSize_t); - RINOK(ReadStream_FALSE(_stream, buffer2, nextHeaderSize_t)); + RINOK(ReadStream_FALSE(_stream, buffer2, nextHeaderSize_t)) if (CrcCalc(buffer2, nextHeaderSize_t) != nextHeaderCRC) ThrowIncorrect(); @@ -1576,19 +1747,19 @@ HRESULT CInArchive::ReadDatabase2( CObjectVector dataVector; - UInt64 type = ReadID(); + const UInt64 type = ReadID(); if (type != NID::kHeader) { if (type != NID::kEncodedHeader) ThrowIncorrect(); - HRESULT result = ReadAndDecodePackedStreams( + const HRESULT result = ReadAndDecodePackedStreams( EXTERNAL_CODECS_LOC_VARS db.ArcInfo.StartPositionAfterHeader, db.ArcInfo.DataStartPosition2, dataVector - _7Z_DECODER_CRYPRO_VARS + Z7_7Z_DECODER_CRYPRO_VARS ); - RINOK(result); + RINOK(result) if (dataVector.Size() == 0) return S_OK; if (dataVector.Size() > 1) @@ -1606,21 +1777,22 @@ HRESULT CInArchive::ReadDatabase2( return ReadHeader( EXTERNAL_CODECS_LOC_VARS db - _7Z_DECODER_CRYPRO_VARS + Z7_7Z_DECODER_CRYPRO_VARS ); } + HRESULT CInArchive::ReadDatabase( DECL_EXTERNAL_CODECS_LOC_VARS CDbEx &db - _7Z_DECODER_CRYPRO_VARS_DECL + Z7_7Z_DECODER_CRYPRO_VARS_DECL ) { try { - HRESULT res = ReadDatabase2( + const HRESULT res = ReadDatabase2( EXTERNAL_CODECS_LOC_VARS db - _7Z_DECODER_CRYPRO_VARS + Z7_7Z_DECODER_CRYPRO_VARS ); if (ThereIsHeaderError) db.ThereIsHeaderError = true; diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zIn.h b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zIn.h index 3592e99b..ddbe020e 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zIn.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zIn.h @@ -1,7 +1,7 @@ // 7zIn.h -#ifndef __7Z_IN_H -#define __7Z_IN_H +#ifndef ZIP7_INC_7Z_IN_H +#define ZIP7_INC_7Z_IN_H #include "../../../Common/MyCom.h" @@ -22,12 +22,12 @@ namespace N7z { We don't need to init isEncrypted and passwordIsDefined We must upgrade them only */ -#ifdef _NO_CRYPTO -#define _7Z_DECODER_CRYPRO_VARS_DECL -#define _7Z_DECODER_CRYPRO_VARS +#ifdef Z7_NO_CRYPTO +#define Z7_7Z_DECODER_CRYPRO_VARS_DECL +#define Z7_7Z_DECODER_CRYPRO_VARS #else -#define _7Z_DECODER_CRYPRO_VARS_DECL , ICryptoGetTextPassword *getTextPassword, bool &isEncrypted, bool &passwordIsDefined, UString &password -#define _7Z_DECODER_CRYPRO_VARS , getTextPassword, isEncrypted, passwordIsDefined, password +#define Z7_7Z_DECODER_CRYPRO_VARS_DECL , ICryptoGetTextPassword *getTextPassword, bool &isEncrypted, bool &passwordIsDefined, UString &password +#define Z7_7Z_DECODER_CRYPRO_VARS , getTextPassword, isEncrypted, passwordIsDefined, password #endif struct CParsedMethods @@ -115,6 +115,7 @@ struct CDatabase: public CFolders CUInt64DefVector ATime; CUInt64DefVector MTime; CUInt64DefVector StartPos; + CUInt32DefVector Attrib; CBoolVector IsAnti; /* CBoolVector IsAux; @@ -146,6 +147,7 @@ struct CDatabase: public CFolders ATime.Clear(); MTime.Clear(); StartPos.Clear(); + Attrib.Clear(); IsAnti.Clear(); // IsAux.Clear(); } @@ -172,13 +174,14 @@ struct CDatabase: public CFolders HRESULT GetPath_Prop(unsigned index, PROPVARIANT *path) const throw(); }; + struct CInArchiveInfo { CArchiveVersion Version; - UInt64 StartPosition; - UInt64 StartPositionAfterHeader; - UInt64 DataStartPosition; - UInt64 DataStartPosition2; + UInt64 StartPosition; // in stream + UInt64 StartPositionAfterHeader; // in stream + UInt64 DataStartPosition; // in stream + UInt64 DataStartPosition2; // in stream. it's for headers CRecordVector FileInfoPopIDs; void Clear() @@ -191,6 +194,7 @@ struct CInArchiveInfo } }; + struct CDbEx: public CDatabase { CInArchiveInfo ArcInfo; @@ -200,6 +204,7 @@ struct CDbEx: public CDatabase UInt64 HeadersSize; UInt64 PhySize; + // UInt64 EndHeaderOffset; // relative to position after StartHeader (32 bytes) /* CRecordVector SecureOffsets; @@ -253,37 +258,51 @@ struct CDbEx: public CDatabase HeadersSize = 0; PhySize = 0; + // EndHeaderOffset = 0; + } + + bool CanUpdate() const + { + if (ThereIsHeaderError + || UnexpectedEnd + || StartHeaderWasRecovered + || UnsupportedFeatureError) + return false; + return true; } void FillLinks(); - UInt64 GetFolderStreamPos(CNum folderIndex, unsigned indexInFolder) const + UInt64 GetFolderStreamPos(size_t folderIndex, size_t indexInFolder) const { - return ArcInfo.DataStartPosition + - PackPositions[FoStartPackStreamIndex[folderIndex] + indexInFolder]; + return ArcInfo.DataStartPosition + PackPositions.ConstData() + [FoStartPackStreamIndex.ConstData()[folderIndex] + indexInFolder]; } - UInt64 GetFolderFullPackSize(CNum folderIndex) const + UInt64 GetFolderFullPackSize(size_t folderIndex) const { return - PackPositions[FoStartPackStreamIndex[folderIndex + 1]] - - PackPositions[FoStartPackStreamIndex[folderIndex]]; + PackPositions[FoStartPackStreamIndex.ConstData()[folderIndex + 1]] - + PackPositions[FoStartPackStreamIndex.ConstData()[folderIndex]]; } - UInt64 GetFolderPackStreamSize(CNum folderIndex, unsigned streamIndex) const + UInt64 GetFolderPackStreamSize(size_t folderIndex, size_t streamIndex) const { - size_t i = FoStartPackStreamIndex[folderIndex] + streamIndex; - return PackPositions[i + 1] - PackPositions[i]; + const size_t i = FoStartPackStreamIndex.ConstData()[folderIndex] + streamIndex; + return PackPositions.ConstData()[i + 1] - + PackPositions.ConstData()[i]; } - UInt64 GetFilePackSize(CNum fileIndex) const + /* + UInt64 GetFilePackSize(size_t fileIndex) const { - CNum folderIndex = FileIndexToFolderIndexMap[fileIndex]; + const CNum folderIndex = FileIndexToFolderIndexMap[fileIndex]; if (folderIndex != kNumNoIndex) if (FolderStartFileIndex[folderIndex] == fileIndex) return GetFolderFullPackSize(folderIndex); return 0; } + */ }; const unsigned kNumBufLevelsMax = 4; @@ -337,6 +356,8 @@ class CInArchive UInt64 _arhiveBeginStreamPosition; UInt64 _fileEndPosition; + UInt64 _rangeLimit; // relative to position after StartHeader (32 bytes) + Byte _header[kHeaderSize]; UInt64 HeadersSize; @@ -369,6 +390,8 @@ class CInArchive void SkipData() { _inByteBack->SkipData(); } void WaitId(UInt64 id); + void Read_UInt32_Vector(CUInt32DefVector &v); + void ReadArchiveProperties(CInArchiveInfo &archiveInfo); void ReadHashDigests(unsigned numItems, CUInt32DefVector &crcs); @@ -398,17 +421,17 @@ class CInArchive DECL_EXTERNAL_CODECS_LOC_VARS UInt64 baseOffset, UInt64 &dataOffset, CObjectVector &dataVector - _7Z_DECODER_CRYPRO_VARS_DECL + Z7_7Z_DECODER_CRYPRO_VARS_DECL ); HRESULT ReadHeader( DECL_EXTERNAL_CODECS_LOC_VARS CDbEx &db - _7Z_DECODER_CRYPRO_VARS_DECL + Z7_7Z_DECODER_CRYPRO_VARS_DECL ); HRESULT ReadDatabase2( DECL_EXTERNAL_CODECS_LOC_VARS CDbEx &db - _7Z_DECODER_CRYPRO_VARS_DECL + Z7_7Z_DECODER_CRYPRO_VARS_DECL ); public: CInArchive(bool useMixerMT): @@ -422,7 +445,7 @@ class CInArchive HRESULT ReadDatabase( DECL_EXTERNAL_CODECS_LOC_VARS CDbEx &db - _7Z_DECODER_CRYPRO_VARS_DECL + Z7_7Z_DECODER_CRYPRO_VARS_DECL ); }; diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zItem.h b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zItem.h index 5e2b58f2..06737c81 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zItem.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zItem.h @@ -1,7 +1,7 @@ // 7zItem.h -#ifndef __7Z_ITEM_H -#define __7Z_ITEM_H +#ifndef ZIP7_INC_7Z_ITEM_H +#define ZIP7_INC_7Z_ITEM_H #include "../../../Common/MyBuffer.h" #include "../../../Common/MyString.h" @@ -26,15 +26,17 @@ struct CCoderInfo bool IsSimpleCoder() const { return NumStreams == 1; } }; + struct CBond { UInt32 PackIndex; UInt32 UnpackIndex; }; + struct CFolder { - CLASS_NO_COPY(CFolder) + Z7_CLASS_NO_COPY(CFolder) public: CObjArray2 Coders; CObjArray2 Bonds; @@ -48,7 +50,7 @@ struct CFolder { FOR_VECTOR(i, PackStreams) if (PackStreams[i] == packStream) - return i; + return (int)i; return -1; } @@ -56,7 +58,7 @@ struct CFolder { FOR_VECTOR(i, Bonds) if (Bonds[i].PackIndex == packStream) - return i; + return (int)i; return -1; } @@ -87,6 +89,7 @@ struct CFolder } }; + struct CUInt32DefVector { CBoolVector Defs; @@ -110,9 +113,30 @@ struct CUInt32DefVector Vals.ReserveDown(); } + bool GetItem(unsigned index, UInt32 &value) const + { + if (index < Defs.Size() && Defs[index]) + { + value = Vals[index]; + return true; + } + value = 0; + return false; + } + bool ValidAndDefined(unsigned i) const { return i < Defs.Size() && Defs[i]; } + + bool CheckSize(unsigned size) const { return Defs.Size() == size || Defs.Size() == 0; } + + void SetItem(unsigned index, bool defined, UInt32 value); + void if_NonEmpty_FillResidue_with_false(unsigned numItems) + { + if (Defs.Size() != 0 && Defs.Size() < numItems) + SetItem(numItems - 1, false, 0); + } }; + struct CUInt64DefVector { CBoolVector Defs; @@ -141,15 +165,15 @@ struct CUInt64DefVector return false; } - void SetItem(unsigned index, bool defined, UInt64 value); - bool CheckSize(unsigned size) const { return Defs.Size() == size || Defs.Size() == 0; } + + void SetItem(unsigned index, bool defined, UInt64 value); }; + struct CFileItem { UInt64 Size; - UInt32 Attrib; UInt32 Crc; /* int Parent; @@ -159,23 +183,23 @@ struct CFileItem // stream in some folder. It can be empty stream bool IsDir; bool CrcDefined; - bool AttribDefined; + + /* + void Clear() + { + HasStream = true; + IsDir = false; + CrcDefined = false; + } CFileItem(): - /* - Parent(-1), - IsAltStream(false), - */ + // Parent(-1), + // IsAltStream(false), HasStream(true), IsDir(false), CrcDefined(false), - AttribDefined(false) {} - void SetAttrib(UInt32 attrib) - { - AttribDefined = true; - Attrib = attrib; - } + */ }; }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zOut.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zOut.cpp index 3e70f466..d0c8cf29 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zOut.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zOut.cpp @@ -3,26 +3,45 @@ #include "StdAfx.h" #include "../../../../C/7zCrc.h" +#include "../../../../C/CpuArch.h" #include "../../../Common/AutoPtr.h" +// #include "../../../Common/UTFConvert.h" #include "../../Common/StreamObjects.h" +#include "../Common/OutStreamWithCRC.h" #include "7zOut.h" +unsigned BoolVector_CountSum(const CBoolVector &v); + +static UInt64 UInt64Vector_CountSum(const CRecordVector &v) +{ + UInt64 sum = 0; + const unsigned size = v.Size(); + if (size) + { + const UInt64 *p = v.ConstData(); + const UInt64 * const lim = p + size; + do + sum += *p++; + while (p != lim); + } + return sum; +} + + namespace NArchive { namespace N7z { -HRESULT COutArchive::WriteSignature() +static void FillSignature(Byte *buf) { - Byte buf[8]; memcpy(buf, kSignature, kSignatureSize); buf[kSignatureSize] = kMajorVersion; buf[kSignatureSize + 1] = 4; - return WriteDirect(buf, 8); } -#ifdef _7Z_VOL +#ifdef Z7_7Z_VOL HRESULT COutArchive::WriteFinishSignature() { RINOK(WriteDirect(kFinishSignature, kSignatureSize)); @@ -48,15 +67,16 @@ static void SetUInt64(Byte *p, UInt64 d) HRESULT COutArchive::WriteStartHeader(const CStartHeader &h) { - Byte buf[24]; - SetUInt64(buf + 4, h.NextHeaderOffset); - SetUInt64(buf + 12, h.NextHeaderSize); - SetUInt32(buf + 20, h.NextHeaderCRC); - SetUInt32(buf, CrcCalc(buf + 4, 20)); - return WriteDirect(buf, 24); + Byte buf[32]; + FillSignature(buf); + SetUInt64(buf + 8 + 4, h.NextHeaderOffset); + SetUInt64(buf + 8 + 12, h.NextHeaderSize); + SetUInt32(buf + 8 + 20, h.NextHeaderCRC); + SetUInt32(buf + 8, CrcCalc(buf + 8 + 4, 20)); + return WriteDirect(buf, sizeof(buf)); } -#ifdef _7Z_VOL +#ifdef Z7_7Z_VOL HRESULT COutArchive::WriteFinishHeader(const CFinishHeader &h) { CCRC crc; @@ -74,15 +94,15 @@ HRESULT COutArchive::WriteFinishHeader(const CFinishHeader &h) } #endif -HRESULT COutArchive::Create(ISequentialOutStream *stream, bool endMarker) +HRESULT COutArchive::Create_and_WriteStartPrefix(ISequentialOutStream *stream /* , bool endMarker */) { Close(); - #ifdef _7Z_VOL + #ifdef Z7_7Z_VOL // endMarker = false; _endMarker = endMarker; #endif SeqStream = stream; - if (!endMarker) + // if (!endMarker) { SeqStream.QueryInterface(IID_IOutStream, &Stream); if (!Stream) @@ -90,8 +110,13 @@ HRESULT COutArchive::Create(ISequentialOutStream *stream, bool endMarker) return E_NOTIMPL; // endMarker = true; } + RINOK(Stream->Seek(0, STREAM_SEEK_CUR, &_signatureHeaderPos)) + Byte buf[32]; + FillSignature(buf); + memset(&buf[8], 0, 32 - 8); + return WriteDirect(buf, sizeof(buf)); } - #ifdef _7Z_VOL + #ifdef Z7_7Z_VOL if (endMarker) { /* @@ -100,17 +125,10 @@ HRESULT COutArchive::Create(ISequentialOutStream *stream, bool endMarker) sh.NextHeaderSize = (UInt32)(Int32)-1; sh.NextHeaderCRC = 0; WriteStartHeader(sh); + return S_OK; */ } - else #endif - { - if (!Stream) - return E_FAIL; - RINOK(WriteSignature()); - RINOK(Stream->Seek(0, STREAM_SEEK_CUR, &_prefixHeaderPos)); - } - return S_OK; } void COutArchive::Close() @@ -119,17 +137,6 @@ void COutArchive::Close() Stream.Release(); } -HRESULT COutArchive::SkipPrefixArchiveHeader() -{ - #ifdef _7Z_VOL - if (_endMarker) - return S_OK; - #endif - Byte buf[24]; - memset(buf, 0, 24); - return WriteDirect(buf, 24); -} - UInt64 COutArchive::GetPos() const { if (_countMode) @@ -146,7 +153,7 @@ void COutArchive::WriteBytes(const void *data, size_t size) else if (_writeToStream) { _outByte.WriteBytes(data, size); - _crc = CrcUpdate(_crc, data, size); + // _crc = CrcUpdate(_crc, data, size); } else _outByte2.WriteBytes(data, size); @@ -157,14 +164,12 @@ void COutArchive::WriteByte(Byte b) if (_countMode) _countSize++; else if (_writeToStream) - { - _outByte.WriteByte(b); - _crc = CRC_UPDATE_BYTE(_crc, b); - } + WriteByte_ToStream(b); else _outByte2.WriteByte(b); } +/* void COutArchive::WriteUInt32(UInt32 value) { for (int i = 0; i < 4; i++) @@ -182,6 +187,7 @@ void COutArchive::WriteUInt64(UInt64 value) value >>= 8; } } +*/ void COutArchive::WriteNumber(UInt64 value) { @@ -196,7 +202,7 @@ void COutArchive::WriteNumber(UInt64 value) break; } firstByte |= mask; - mask >>= 1; + mask = (Byte)(mask >> 1); } WriteByte(firstByte); for (; i > 0; i--) @@ -206,16 +212,16 @@ void COutArchive::WriteNumber(UInt64 value) } } -static UInt32 GetBigNumberSize(UInt64 value) +static unsigned GetBigNumberSize(UInt64 value) { - int i; + unsigned i; for (i = 1; i < 9; i++) if (value < (((UInt64)1 << (i * 7)))) break; return i; } -#ifdef _7Z_VOL +#ifdef Z7_7Z_VOL UInt32 COutArchive::GetVolHeadersSize(UInt64 dataSize, int nameLength, bool props) { UInt32 result = GetBigNumberSize(dataSize) * 2 + 41; @@ -264,18 +270,18 @@ void COutArchive::WriteFolder(const CFolder &folder) for (idSize = 1; idSize < sizeof(id); idSize++) if ((id >> (8 * idSize)) == 0) break; - idSize &= 0xF; + // idSize &= 0xF; // idSize is smaller than 16 already Byte temp[16]; for (unsigned t = idSize; t != 0; t--, id >>= 8) temp[t] = (Byte)(id & 0xFF); - Byte b = (Byte)(idSize); - bool isComplex = !coder.IsSimpleCoder(); + unsigned b = idSize; + const bool isComplex = !coder.IsSimpleCoder(); b |= (isComplex ? 0x10 : 0); - size_t propsSize = coder.Props.Size(); + const size_t propsSize = coder.Props.Size(); b |= ((propsSize != 0) ? 0x20 : 0); - temp[0] = b; + temp[0] = (Byte)b; WriteBytes(temp, idSize + 1); if (isComplex) { @@ -301,7 +307,7 @@ void COutArchive::WriteFolder(const CFolder &folder) WriteNumber(folder.PackStreams[i]); } -void COutArchive::WriteBoolVector(const CBoolVector &boolVector) +void COutArchive::Write_BoolVector(const CBoolVector &boolVector) { Byte b = 0; Byte mask = 0x80; @@ -309,7 +315,7 @@ void COutArchive::WriteBoolVector(const CBoolVector &boolVector) { if (boolVector[i]) b |= mask; - mask >>= 1; + mask = (Byte)(mask >> 1); if (mask == 0) { WriteByte(b); @@ -327,32 +333,32 @@ void COutArchive::WritePropBoolVector(Byte id, const CBoolVector &boolVector) { WriteByte(id); WriteNumber(Bv_GetSizeInBytes(boolVector)); - WriteBoolVector(boolVector); + Write_BoolVector(boolVector); } -void COutArchive::WriteHashDigests(const CUInt32DefVector &digests) +void COutArchive::Write_BoolVector_numDefined(const CBoolVector &boolVector, unsigned numDefined) { - unsigned numDefined = 0; - unsigned i; - for (i = 0; i < digests.Defs.Size(); i++) - if (digests.Defs[i]) - numDefined++; - if (numDefined == 0) - return; - - WriteByte(NID::kCRC); - if (numDefined == digests.Defs.Size()) + if (numDefined == boolVector.Size()) WriteByte(1); else { WriteByte(0); - WriteBoolVector(digests.Defs); + Write_BoolVector(boolVector); } - for (i = 0; i < digests.Defs.Size(); i++) - if (digests.Defs[i]) - WriteUInt32(digests.Vals[i]); } + +void COutArchive::WriteHashDigests(const CUInt32DefVector &digests) +{ + const unsigned numDefined = BoolVector_CountSum(digests.Defs); + if (numDefined == 0) + return; + WriteByte(NID::kCRC); + Write_BoolVector_numDefined(digests.Defs, numDefined); + Write_UInt32DefVector_numDefined(digests, numDefined); +} + + void COutArchive::WritePackInfo( UInt64 dataOffset, const CRecordVector &packSizes, @@ -453,10 +459,12 @@ void COutArchive::WriteSubStreamsInfo(const CObjectVector &folders, // 7-Zip 4.50 - 4.58 contain BUG, so they do not support .7z archives with Unknown field. -void COutArchive::SkipAlign(unsigned pos, unsigned alignSize) +void COutArchive::SkipToAligned(unsigned pos, unsigned alignShifts) { if (!_useAlign) return; + + const unsigned alignSize = (unsigned)1 << alignShifts; pos += (unsigned)GetPos(); pos &= (alignSize - 1); if (pos == 0) @@ -471,62 +479,109 @@ void COutArchive::SkipAlign(unsigned pos, unsigned alignSize) WriteByte(0); } -void COutArchive::WriteAlignedBoolHeader(const CBoolVector &v, unsigned numDefined, Byte type, unsigned itemSize) +void COutArchive::WriteAlignedBools(const CBoolVector &v, unsigned numDefined, Byte type, unsigned itemSizeShifts) { const unsigned bvSize = (numDefined == v.Size()) ? 0 : Bv_GetSizeInBytes(v); - const UInt64 dataSize = (UInt64)numDefined * itemSize + bvSize + 2; - SkipAlign(3 + (unsigned)bvSize + (unsigned)GetBigNumberSize(dataSize), itemSize); + const UInt64 dataSize = ((UInt64)numDefined << itemSizeShifts) + bvSize + 2; + SkipToAligned(3 + bvSize + GetBigNumberSize(dataSize), itemSizeShifts); WriteByte(type); WriteNumber(dataSize); - if (numDefined == v.Size()) - WriteByte(1); - else - { - WriteByte(0); - WriteBoolVector(v); - } - WriteByte(0); + Write_BoolVector_numDefined(v, numDefined); + WriteByte(0); // 0 means no switching to external stream } -void COutArchive::WriteUInt64DefVector(const CUInt64DefVector &v, Byte type) + +void COutArchive::Write_UInt32DefVector_numDefined(const CUInt32DefVector &v, unsigned numDefined) { - unsigned numDefined = 0; + if (_countMode) + { + _countSize += (size_t)numDefined * 4; + return; + } + + const bool * const defs = v.Defs.ConstData(); + const UInt32 * const vals = v.Vals.ConstData(); + const size_t num = v.Defs.Size(); + + for (size_t i = 0; i < num; i++) + if (defs[i]) + { + UInt32 value = vals[i]; + for (int k = 0; k < 4; k++) + { + if (_writeToStream) + WriteByte_ToStream((Byte)value); + else + _outByte2.WriteByte((Byte)value); + // WriteByte((Byte)value); + value >>= 8; + } + // WriteUInt32(v.Vals[i]); + } +} - unsigned i; - for (i = 0; i < v.Defs.Size(); i++) - if (v.Defs[i]) - numDefined++; +void COutArchive::Write_UInt64DefVector_type(const CUInt64DefVector &v, Byte type) +{ + const unsigned numDefined = BoolVector_CountSum(v.Defs); if (numDefined == 0) return; - WriteAlignedBoolHeader(v.Defs, numDefined, type, 8); + WriteAlignedBools(v.Defs, numDefined, type, 3); - for (i = 0; i < v.Defs.Size(); i++) - if (v.Defs[i]) - WriteUInt64(v.Vals[i]); + if (_countMode) + { + _countSize += (size_t)numDefined * 8; + return; + } + + const bool * const defs = v.Defs.ConstData(); + const UInt64 * const vals = v.Vals.ConstData(); + const size_t num = v.Defs.Size(); + + for (size_t i = 0; i < num; i++) + if (defs[i]) + { + UInt64 value = vals[i]; + for (int k = 0; k < 8; k++) + { + if (_writeToStream) + WriteByte_ToStream((Byte)value); + else + _outByte2.WriteByte((Byte)value); + // WriteByte((Byte)value); + value >>= 8; + } + // WriteUInt64(v.Vals[i]); + } } + HRESULT COutArchive::EncodeStream( DECL_EXTERNAL_CODECS_LOC_VARS CEncoder &encoder, const CByteBuffer &data, CRecordVector &packSizes, CObjectVector &folders, COutFolders &outFolders) { - CBufInStream *streamSpec = new CBufInStream; - CMyComPtr stream = streamSpec; + CMyComPtr2_Create streamSpec; streamSpec->Init(data, data.Size()); outFolders.FolderUnpackCRCs.Defs.Add(true); outFolders.FolderUnpackCRCs.Vals.Add(CrcCalc(data, data.Size())); // outFolders.NumUnpackStreamsVector.Add(1); - UInt64 dataSize64 = data.Size(); - UInt64 unpackSize; - RINOK(encoder.Encode( + const UInt64 dataSize64 = data.Size(); + const UInt64 expectSize = data.Size(); + RINOK(encoder.Encode1( EXTERNAL_CODECS_LOC_VARS - stream, + streamSpec, // NULL, - &dataSize64, - folders.AddNew(), outFolders.CoderUnpackSizes, unpackSize, SeqStream, packSizes, NULL)) + &dataSize64, // inSizeForReduce + expectSize, + folders.AddNew(), + // outFolders.CoderUnpackSizes, unpackSize, + SeqStream, packSizes, NULL)) + if (!streamSpec->WasFinished()) + return E_FAIL; + encoder.Encode_Post(dataSize64, outFolders.CoderUnpackSizes); return S_OK; } @@ -540,17 +595,43 @@ void COutArchive::WriteHeader( */ _useAlign = true; + headerOffset = UInt64Vector_CountSum(db.PackSizes); + + WriteByte(NID::kHeader); + + /* { - UInt64 packSize = 0; - FOR_VECTOR (i, db.PackSizes) - packSize += db.PackSizes[i]; - headerOffset = packSize; - } + // It's example for per archive properies writing + + WriteByte(NID::kArchiveProperties); + // you must use random 40-bit number that will identify you + // then you can use same kDeveloperID for any properties and methods + const UInt64 kDeveloperID = 0x123456789A; // change that value to real random 40-bit number - WriteByte(NID::kHeader); + #define GENERATE_7Z_ID(developerID, subID) (((UInt64)0x3F << 56) | ((UInt64)developerID << 16) | subID) - // Archive Properties + { + const UInt64 kSubID = 0x1; // you can use small number for subID + const UInt64 kID = GENERATE_7Z_ID(kDeveloperID, kSubID); + WriteNumber(kID); + const unsigned kPropsSize = 3; // it's example size + WriteNumber(kPropsSize); + for (unsigned i = 0; i < kPropsSize; i++) + WriteByte((Byte)(i & 0xFF)); + } + { + const UInt64 kSubID = 0x2; // you can use small number for subID + const UInt64 kID = GENERATE_7Z_ID(kDeveloperID, kSubID); + WriteNumber(kID); + const unsigned kPropsSize = 5; // it's example size + WriteNumber(kPropsSize); + for (unsigned i = 0; i < kPropsSize; i++) + WriteByte((Byte)(i + 16)); + } + WriteByte(NID::kEnd); + } + */ if (db.Folders.Size() > 0) { @@ -635,85 +716,105 @@ void COutArchive::WriteHeader( { /* ---------- Names ---------- */ - unsigned numDefined = 0; size_t namesDataSize = 0; - FOR_VECTOR (i, db.Files) { - const UString &name = db.Names[i]; - if (!name.IsEmpty()) - numDefined++; - namesDataSize += (name.Len() + 1) * 2; + FOR_VECTOR (i, db.Files) + { + const UString &name = db.Names[i]; + const size_t numUtfChars = + /* + #if WCHAR_MAX > 0xffff + Get_Num_Utf16_chars_from_wchar_string(name.Ptr()); + #else + */ + name.Len(); + // #endif + namesDataSize += numUtfChars; + } } - - if (numDefined > 0) + if (namesDataSize) { - namesDataSize++; - SkipAlign(2 + GetBigNumberSize(namesDataSize), 16); - + namesDataSize += db.Files.Size(); // we will write tail zero wchar for each name + namesDataSize *= 2; // 2 bytes per wchar for UTF16 encoding + namesDataSize++; // for additional switch byte (zero value) + SkipToAligned(2 + GetBigNumberSize(namesDataSize), 4); WriteByte(NID::kName); WriteNumber(namesDataSize); - WriteByte(0); - FOR_VECTOR (i, db.Files) + + if (_countMode) + _countSize += namesDataSize; + else { - const UString &name = db.Names[i]; - for (unsigned t = 0; t <= name.Len(); t++) + WriteByte(0); + FOR_VECTOR (i, db.Files) { - wchar_t c = name[t]; - WriteByte((Byte)c); - WriteByte((Byte)(c >> 8)); + const UString &name = db.Names[i]; + const wchar_t *p = name.Ptr(); + const size_t len = (size_t)name.Len() + 1; + const wchar_t * const lim = p + len; + if (_writeToStream) + { + do + { + const wchar_t c = *p++; + WriteByte_ToStream((Byte)c); + WriteByte_ToStream((Byte)(c >> 8)); + } + while (p != lim); + } + else + { + Byte *dest = _outByte2.GetDest_and_Update(len * 2); + do + { + /* + #if WCHAR_MAX > 0xffff + if (c >= 0x10000) + { + c -= 0x10000; + if (c < (1 << 20)) + { + unsigned c0 = 0xd800 + ((c >> 10) & 0x3FF); + WriteByte((Byte)c0); + WriteByte((Byte)(c0 >> 8)); + c = 0xdc00 + (c & 0x3FF); + } + else + c = '_'; // we change character unsupported by UTF16 + } + #endif + */ + const wchar_t c = *p++; + SetUi16(dest, (UInt16)c) + dest += 2; + } + while (p != lim); + } } } } } - /* if (headerOptions.WriteCTime) */ WriteUInt64DefVector(db.CTime, NID::kCTime); - /* if (headerOptions.WriteATime) */ WriteUInt64DefVector(db.ATime, NID::kATime); - /* if (headerOptions.WriteMTime) */ WriteUInt64DefVector(db.MTime, NID::kMTime); - WriteUInt64DefVector(db.StartPos, NID::kStartPos); + /* if (headerOptions.WriteCTime) */ Write_UInt64DefVector_type(db.CTime, NID::kCTime); + /* if (headerOptions.WriteATime) */ Write_UInt64DefVector_type(db.ATime, NID::kATime); + /* if (headerOptions.WriteMTime) */ Write_UInt64DefVector_type(db.MTime, NID::kMTime); + Write_UInt64DefVector_type(db.StartPos, NID::kStartPos); { /* ---------- Write Attrib ---------- */ - CBoolVector boolVector; - boolVector.ClearAndSetSize(db.Files.Size()); - unsigned numDefined = 0; - - { - FOR_VECTOR (i, db.Files) - { - bool defined = db.Files[i].AttribDefined; - boolVector[i] = defined; - if (defined) - numDefined++; - } - } - + const unsigned numDefined = BoolVector_CountSum(db.Attrib.Defs); if (numDefined != 0) { - WriteAlignedBoolHeader(boolVector, numDefined, NID::kWinAttrib, 4); - FOR_VECTOR (i, db.Files) - { - const CFileItem &file = db.Files[i]; - if (file.AttribDefined) - WriteUInt32(file.Attrib); - } + WriteAlignedBools(db.Attrib.Defs, numDefined, NID::kWinAttrib, 2); + Write_UInt32DefVector_numDefined(db.Attrib, numDefined); } } /* { // ---------- Write IsAux ---------- - unsigned numAux = 0; - const CBoolVector &isAux = db.IsAux; - for (i = 0; i < isAux.Size(); i++) - if (isAux[i]) - numAux++; - if (numAux > 0) - { - const unsigned bvSize = Bv_GetSizeInBytes(isAux); - WriteByte(NID::kIsAux); - WriteNumber(bvSize); - WriteBoolVector(isAux); - } + if (BoolVector_CountSum(db.IsAux) != 0) + WritePropBoolVector(NID::kIsAux, db.IsAux); } { @@ -734,20 +835,14 @@ void COutArchive::WriteHeader( } if (numParentLinks > 0) { - // WriteAlignedBoolHeader(boolVector, numDefined, NID::kParent, 4); + // WriteAlignedBools(boolVector, numDefined, NID::kParent, 2); const unsigned bvSize = (numIsDir == boolVector.Size()) ? 0 : Bv_GetSizeInBytes(boolVector); const UInt64 dataSize = (UInt64)db.Files.Size() * 4 + bvSize + 1; - SkipAlign(2 + (unsigned)bvSize + (unsigned)GetBigNumberSize(dataSize), 4); + SkipToAligned(2 + (unsigned)bvSize + (unsigned)GetBigNumberSize(dataSize), 2); WriteByte(NID::kParent); WriteNumber(dataSize); - if (numIsDir == boolVector.Size()) - WriteByte(1); - else - { - WriteByte(0); - WriteBoolVector(boolVector); - } + Write_BoolVector_numDefined(boolVector, numIsDir); for (i = 0; i < db.Files.Size(); i++) { const CFileItem &file = db.Files[i]; @@ -765,7 +860,7 @@ void COutArchive::WriteHeader( // secureDataSize += db.SecureIDs.Size() * 4; for (i = 0; i < db.SecureIDs.Size(); i++) secureDataSize += GetBigNumberSize(db.SecureIDs[i]); - SkipAlign(2 + GetBigNumberSize(secureDataSize), 4); + SkipToAligned(2 + GetBigNumberSize(secureDataSize), 2); WriteByte(NID::kNtSecure); WriteNumber(secureDataSize); WriteByte(0); @@ -794,32 +889,34 @@ HRESULT COutArchive::WriteDatabase( if (!db.CheckNumFiles()) return E_FAIL; - UInt64 headerOffset; - UInt32 headerCRC; - UInt64 headerSize; - if (db.IsEmpty()) - { - headerSize = 0; - headerOffset = 0; - headerCRC = CrcCalc(0, 0); - } - else + CStartHeader sh; + sh.NextHeaderOffset = 0; + sh.NextHeaderSize = 0; + sh.NextHeaderCRC = 0; // CrcCalc(NULL, 0); + + if (!db.IsEmpty()) { + CMyComPtr2_Create crcStream; + crcStream->SetStream(SeqStream); + crcStream->Init(); + bool encodeHeaders = false; - if (options != 0) + if (options) if (options->IsEmpty()) - options = 0; - if (options != 0) + options = NULL; + if (options) if (options->PasswordIsDefined || headerOptions.CompressMainHeader) encodeHeaders = true; - _outByte.SetStream(SeqStream); + if (!_outByte.Create(1 << 16)) + return E_OUTOFMEMORY; + _outByte.SetStream(crcStream.Interface()); _outByte.Init(); - _crc = CRC_INIT_VAL; + // _crc = CRC_INIT_VAL; _countMode = encodeHeaders; _writeToStream = true; _countSize = 0; - WriteHeader(db, /* headerOptions, */ headerOffset); + WriteHeader(db, /* headerOptions, */ sh.NextHeaderOffset); if (encodeHeaders) { @@ -828,7 +925,7 @@ HRESULT COutArchive::WriteDatabase( _countMode = false; _writeToStream = false; - WriteHeader(db, /* headerOptions, */ headerOffset); + WriteHeader(db, /* headerOptions, */ sh.NextHeaderOffset); if (_countSize != _outByte2.GetPos()) return E_FAIL; @@ -844,7 +941,7 @@ HRESULT COutArchive::WriteDatabase( RINOK(EncodeStream( EXTERNAL_CODECS_LOC_VARS encoder, buf, - packSizes, folders, outFolders)); + packSizes, folders, outFolders)) _writeToStream = true; @@ -852,17 +949,19 @@ HRESULT COutArchive::WriteDatabase( throw 1; WriteID(NID::kEncodedHeader); - WritePackInfo(headerOffset, packSizes, CUInt32DefVector()); + WritePackInfo(sh.NextHeaderOffset, packSizes, CUInt32DefVector()); WriteUnpackInfo(folders, outFolders); WriteByte(NID::kEnd); - FOR_VECTOR (i, packSizes) - headerOffset += packSizes[i]; + + sh.NextHeaderOffset += UInt64Vector_CountSum(packSizes); } - RINOK(_outByte.Flush()); - headerCRC = CRC_GET_DIGEST(_crc); - headerSize = _outByte.GetProcessedSize(); + RINOK(_outByte.Flush()) + sh.NextHeaderCRC = crcStream->GetCRC(); + // sh.NextHeaderCRC = CRC_GET_DIGEST(_crc); + // if (CRC_GET_DIGEST(_crc) != sh.NextHeaderCRC) throw 1; + sh.NextHeaderSize = _outByte.GetProcessedSize(); } - #ifdef _7Z_VOL + #ifdef Z7_7Z_VOL if (_endMarker) { CFinishHeader h; @@ -878,14 +977,24 @@ HRESULT COutArchive::WriteDatabase( } else #endif + if (Stream) { - CStartHeader h; - h.NextHeaderSize = headerSize; - h.NextHeaderCRC = headerCRC; - h.NextHeaderOffset = headerOffset; - RINOK(Stream->Seek(_prefixHeaderPos, STREAM_SEEK_SET, NULL)); - return WriteStartHeader(h); + RINOK(Stream->Seek((Int64)_signatureHeaderPos, STREAM_SEEK_SET, NULL)) + return WriteStartHeader(sh); } + return S_OK; +} + +void CUInt32DefVector::SetItem(unsigned index, bool defined, UInt32 value) +{ + while (index >= Defs.Size()) + Defs.Add(false); + Defs[index] = defined; + if (!defined) + return; + while (index >= Vals.Size()) + Vals.Add(0); + Vals[index] = value; } void CUInt64DefVector::SetItem(unsigned index, bool defined, UInt64 value) @@ -907,6 +1016,7 @@ void CArchiveDatabaseOut::AddFile(const CFileItem &file, const CFileItem2 &file2 ATime.SetItem(index, file2.ATimeDefined, file2.ATime); MTime.SetItem(index, file2.MTimeDefined, file2.MTime); StartPos.SetItem(index, file2.StartPosDefined, file2.StartPos); + Attrib.SetItem(index, file2.AttribDefined, file2.Attrib); SetItem_Anti(index, file2.IsAnti); // SetItem_Aux(index, file2.IsAux); Names.Add(name); diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zOut.h b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zOut.h index 6c902668..c65d268d 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zOut.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zOut.h @@ -1,7 +1,7 @@ // 7zOut.h -#ifndef __7Z_OUT_H -#define __7Z_OUT_H +#ifndef ZIP7_INC_7Z_OUT_H +#define ZIP7_INC_7Z_OUT_H #include "7zCompressionMode.h" #include "7zEncode.h" @@ -14,37 +14,49 @@ namespace NArchive { namespace N7z { +const unsigned k_StartHeadersRewriteSize = 32; + class CWriteBufferLoc { Byte *_data; - size_t _size; - size_t _pos; + Byte *_dataLim; + Byte *_dataBase; public: - CWriteBufferLoc(): _size(0), _pos(0) {} + // CWriteBufferLoc(): _data(NULL), _dataLim(NULL), _dataBase(NULL) {} void Init(Byte *data, size_t size) { _data = data; - _size = size; - _pos = 0; + _dataBase = data; + _dataLim = data + size; + } + + Byte *GetDest_and_Update(size_t size) + { + Byte *dest = _data; + if (size > (size_t)(_dataLim - dest)) + throw 1; + _data = dest + size; + return dest; } void WriteBytes(const void *data, size_t size) { if (size == 0) return; - if (size > _size - _pos) - throw 1; - memcpy(_data + _pos, data, size); - _pos += size; + Byte *dest = GetDest_and_Update(size); + memcpy(dest, data, size); } void WriteByte(Byte b) { - if (_size == _pos) + Byte *dest = _data; + if (dest == _dataLim) throw 1; - _data[_pos++] = b; + *dest++ = b; + _data = dest; } - size_t GetPos() const { return _pos; } + size_t GetPos() const { return (size_t)(_data - _dataBase); } }; + struct CHeaderOptions { bool CompressMainHeader; @@ -71,24 +83,31 @@ struct CFileItem2 UInt64 ATime; UInt64 MTime; UInt64 StartPos; + UInt32 Attrib; + bool CTimeDefined; bool ATimeDefined; bool MTimeDefined; bool StartPosDefined; + bool AttribDefined; bool IsAnti; // bool IsAux; + /* void Init() { CTimeDefined = false; ATimeDefined = false; MTimeDefined = false; StartPosDefined = false; + AttribDefined = false; IsAnti = false; // IsAux = false; } + */ }; + struct COutFolders { CUInt32DefVector FolderUnpackCRCs; // Now we use it for headers only. @@ -111,6 +130,7 @@ struct COutFolders } }; + struct CArchiveDatabaseOut: public COutFolders { CRecordVector PackSizes; @@ -123,10 +143,11 @@ struct CArchiveDatabaseOut: public COutFolders CUInt64DefVector ATime; CUInt64DefVector MTime; CUInt64DefVector StartPos; - CRecordVector IsAnti; + CUInt32DefVector Attrib; + CBoolVector IsAnti; /* - CRecordVector IsAux; + CBoolVector IsAux; CByteBuffer SecureBuf; CRecordVector SecureSizes; @@ -154,6 +175,7 @@ struct CArchiveDatabaseOut: public COutFolders ATime.Clear(); MTime.Clear(); StartPos.Clear(); + Attrib.Clear(); IsAnti.Clear(); /* @@ -176,6 +198,7 @@ struct CArchiveDatabaseOut: public COutFolders ATime.ReserveDown(); MTime.ReserveDown(); StartPos.ReserveDown(); + Attrib.ReserveDown(); IsAnti.ReserveDown(); /* @@ -196,11 +219,12 @@ struct CArchiveDatabaseOut: public COutFolders { unsigned size = Files.Size(); return ( - CTime.CheckSize(size) && - ATime.CheckSize(size) && - MTime.CheckSize(size) && - StartPos.CheckSize(size) && - (size == IsAnti.Size() || IsAnti.Size() == 0)); + CTime.CheckSize(size) + && ATime.CheckSize(size) + && MTime.CheckSize(size) + && StartPos.CheckSize(size) + && Attrib.CheckSize(size) + && (size == IsAnti.Size() || IsAnti.Size() == 0)); } bool IsItemAnti(unsigned index) const { return (index < IsAnti.Size() && IsAnti[index]); } @@ -224,24 +248,29 @@ struct CArchiveDatabaseOut: public COutFolders void AddFile(const CFileItem &file, const CFileItem2 &file2, const UString &name); }; + class COutArchive { - UInt64 _prefixHeaderPos; - HRESULT WriteDirect(const void *data, UInt32 size) { return WriteStream(SeqStream, data, size); } UInt64 GetPos() const; void WriteBytes(const void *data, size_t size); void WriteBytes(const CByteBuffer &data) { WriteBytes(data, data.Size()); } void WriteByte(Byte b); - void WriteUInt32(UInt32 value); - void WriteUInt64(UInt64 value); + void WriteByte_ToStream(Byte b) + { + _outByte.WriteByte(b); + // _crc = CRC_UPDATE_BYTE(_crc, b); + } + // void WriteUInt32(UInt32 value); + // void WriteUInt64(UInt64 value); void WriteNumber(UInt64 value); void WriteID(UInt64 value) { WriteNumber(value); } void WriteFolder(const CFolder &folder); HRESULT WriteFileHeader(const CFileItem &itemInfo); - void WriteBoolVector(const CBoolVector &boolVector); + void Write_BoolVector(const CBoolVector &boolVector); + void Write_BoolVector_numDefined(const CBoolVector &boolVector, unsigned numDefined); void WritePropBoolVector(Byte id, const CBoolVector &boolVector); void WriteHashDigests(const CUInt32DefVector &digests); @@ -261,9 +290,10 @@ class COutArchive const CRecordVector &unpackSizes, const CUInt32DefVector &digests); - void SkipAlign(unsigned pos, unsigned alignSize); - void WriteAlignedBoolHeader(const CBoolVector &v, unsigned numDefined, Byte type, unsigned itemSize); - void WriteUInt64DefVector(const CUInt64DefVector &v, Byte type); + void SkipToAligned(unsigned pos, unsigned alignShifts); + void WriteAlignedBools(const CBoolVector &v, unsigned numDefined, Byte type, unsigned itemSizeShifts); + void Write_UInt32DefVector_numDefined(const CUInt32DefVector &v, unsigned numDefined); + void Write_UInt64DefVector_type(const CUInt64DefVector &v, Byte type); HRESULT EncodeStream( DECL_EXTERNAL_CODECS_LOC_VARS @@ -276,44 +306,39 @@ class COutArchive bool _countMode; bool _writeToStream; - size_t _countSize; - UInt32 _crc; - COutBuffer _outByte; - CWriteBufferLoc _outByte2; - - #ifdef _7Z_VOL + bool _useAlign; + #ifdef Z7_7Z_VOL bool _endMarker; #endif + // UInt32 _crc; + size_t _countSize; + CWriteBufferLoc _outByte2; + COutBuffer _outByte; + UInt64 _signatureHeaderPos; + CMyComPtr Stream; - bool _useAlign; - - HRESULT WriteSignature(); - #ifdef _7Z_VOL + #ifdef Z7_7Z_VOL HRESULT WriteFinishSignature(); - #endif - HRESULT WriteStartHeader(const CStartHeader &h); - #ifdef _7Z_VOL HRESULT WriteFinishHeader(const CFinishHeader &h); #endif - CMyComPtr Stream; -public: + HRESULT WriteStartHeader(const CStartHeader &h); - COutArchive() { _outByte.Create(1 << 16); } +public: CMyComPtr SeqStream; - HRESULT Create(ISequentialOutStream *stream, bool endMarker); + + // COutArchive(); + HRESULT Create_and_WriteStartPrefix(ISequentialOutStream *stream /* , bool endMarker */); void Close(); - HRESULT SkipPrefixArchiveHeader(); HRESULT WriteDatabase( DECL_EXTERNAL_CODECS_LOC_VARS const CArchiveDatabaseOut &db, const CCompressionMethodMode *options, const CHeaderOptions &headerOptions); - #ifdef _7Z_VOL + #ifdef Z7_7Z_VOL static UInt32 GetVolHeadersSize(UInt64 dataSize, int nameLength = 0, bool props = false); static UInt64 GetVolPureSize(UInt64 volSize, int nameLength = 0, bool props = false); #endif - }; }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zProperties.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zProperties.cpp index 4cb5a5e6..d3b3cbe5 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zProperties.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zProperties.cpp @@ -2,56 +2,63 @@ #include "StdAfx.h" -#include "7zProperties.h" -#include "7zHeader.h" #include "7zHandler.h" - -// #define _MULTI_PACK +#include "7zProperties.h" namespace NArchive { namespace N7z { struct CPropMap { - UInt32 FilePropID; - CStatProp StatProp; + Byte FilePropID; + // CStatProp StatProp; + VARTYPE vt; + UInt32 StatPropID; }; +// #define STAT_PROP(name, id, vt) { name, id, vt } +#define STAT_PROP(name, id, vt) vt, id + +#define STAT_PROP2(id, vt) STAT_PROP(NULL, id, vt) + +#define k_7z_id_Encrypted 97 +#define k_7z_id_Method 98 +#define k_7z_id_Block 99 + static const CPropMap kPropMap[] = { - { NID::kName, { NULL, kpidPath, VT_BSTR } }, - { NID::kSize, { NULL, kpidSize, VT_UI8 } }, - { NID::kPackInfo, { NULL, kpidPackSize, VT_UI8 } }, + { NID::kName, STAT_PROP2(kpidPath, VT_BSTR) }, + { NID::kSize, STAT_PROP2(kpidSize, VT_UI8) }, + { NID::kPackInfo, STAT_PROP2(kpidPackSize, VT_UI8) }, - #ifdef _MULTI_PACK - { 100, { "Pack0", kpidPackedSize0, VT_UI8 } }, - { 101, { "Pack1", kpidPackedSize1, VT_UI8 } }, - { 102, { "Pack2", kpidPackedSize2, VT_UI8 } }, - { 103, { "Pack3", kpidPackedSize3, VT_UI8 } }, - { 104, { "Pack4", kpidPackedSize4, VT_UI8 } }, + #ifdef Z7_7Z_SHOW_PACK_STREAMS_SIZES +#define k_7z_id_PackedSize0 100 + { k_7z_id_PackedSize0 + 0, STAT_PROP("Pack0", kpidPackedSize0, VT_UI8) }, + { k_7z_id_PackedSize0 + 1, STAT_PROP("Pack1", kpidPackedSize1, VT_UI8) }, + { k_7z_id_PackedSize0 + 2, STAT_PROP("Pack2", kpidPackedSize2, VT_UI8) }, + { k_7z_id_PackedSize0 + 3, STAT_PROP("Pack3", kpidPackedSize3, VT_UI8) }, + { k_7z_id_PackedSize0 + 4, STAT_PROP("Pack4", kpidPackedSize4, VT_UI8) }, #endif - { NID::kCTime, { NULL, kpidCTime, VT_FILETIME } }, - { NID::kMTime, { NULL, kpidMTime, VT_FILETIME } }, - { NID::kATime, { NULL, kpidATime, VT_FILETIME } }, - { NID::kWinAttrib, { NULL, kpidAttrib, VT_UI4 } }, - { NID::kStartPos, { NULL, kpidPosition, VT_UI8 } }, + { NID::kCTime, STAT_PROP2(kpidCTime, VT_FILETIME) }, + { NID::kMTime, STAT_PROP2(kpidMTime, VT_FILETIME) }, + { NID::kATime, STAT_PROP2(kpidATime, VT_FILETIME) }, + { NID::kWinAttrib, STAT_PROP2(kpidAttrib, VT_UI4) }, + { NID::kStartPos, STAT_PROP2(kpidPosition, VT_UI8) }, - { NID::kCRC, { NULL, kpidCRC, VT_UI4 } }, - -// { NID::kIsAux, { NULL, kpidIsAux, VT_BOOL } }, - { NID::kAnti, { NULL, kpidIsAnti, VT_BOOL } } - - #ifndef _SFX - , - { 97, { NULL, kpidEncrypted, VT_BOOL } }, - { 98, { NULL, kpidMethod, VT_BSTR } }, - { 99, { NULL, kpidBlock, VT_UI4 } } + { NID::kCRC, STAT_PROP2(kpidCRC, VT_UI4) }, + // { NID::kIsAux, STAT_PROP2(kpidIsAux, VT_BOOL) }, + { NID::kAnti, STAT_PROP2(kpidIsAnti, VT_BOOL) } + + #ifndef Z7_SFX + , { k_7z_id_Encrypted, STAT_PROP2(kpidEncrypted, VT_BOOL) } + , { k_7z_id_Method, STAT_PROP2(kpidMethod, VT_BSTR) } + , { k_7z_id_Block, STAT_PROP2(kpidBlock, VT_UI4) } #endif }; static void CopyOneItem(CRecordVector &src, - CRecordVector &dest, UInt32 item) + CRecordVector &dest, const UInt32 item) { FOR_VECTOR (i, src) if (src[i] == item) @@ -62,7 +69,7 @@ static void CopyOneItem(CRecordVector &src, } } -static void RemoveOneItem(CRecordVector &src, UInt32 item) +static void RemoveOneItem(CRecordVector &src, const UInt32 item) { FOR_VECTOR (i, src) if (src[i] == item) @@ -72,7 +79,7 @@ static void RemoveOneItem(CRecordVector &src, UInt32 item) } } -static void InsertToHead(CRecordVector &dest, UInt32 item) +static void InsertToHead(CRecordVector &dest, const UInt32 item) { FOR_VECTOR (i, dest) if (dest[i] == item) @@ -89,7 +96,7 @@ void CHandler::FillPopIDs() { _fileInfoPopIDs.Clear(); - #ifdef _7Z_VOL + #ifdef Z7_7Z_VOL if (_volumes.Size() < 1) return; const CVolume &volume = _volumes.Front(); @@ -105,34 +112,31 @@ void CHandler::FillPopIDs() RemoveOneItem(fileInfoPopIDs, NID::kNtSecure); */ - COPY_ONE_ITEM(kName); - COPY_ONE_ITEM(kAnti); - COPY_ONE_ITEM(kSize); - COPY_ONE_ITEM(kPackInfo); - COPY_ONE_ITEM(kCTime); - COPY_ONE_ITEM(kMTime); - COPY_ONE_ITEM(kATime); - COPY_ONE_ITEM(kWinAttrib); - COPY_ONE_ITEM(kCRC); - COPY_ONE_ITEM(kComment); + COPY_ONE_ITEM(kName) + COPY_ONE_ITEM(kAnti) + COPY_ONE_ITEM(kSize) + COPY_ONE_ITEM(kPackInfo) + COPY_ONE_ITEM(kCTime) + COPY_ONE_ITEM(kMTime) + COPY_ONE_ITEM(kATime) + COPY_ONE_ITEM(kWinAttrib) + COPY_ONE_ITEM(kCRC) + COPY_ONE_ITEM(kComment) _fileInfoPopIDs += fileInfoPopIDs; - #ifndef _SFX - _fileInfoPopIDs.Add(97); - _fileInfoPopIDs.Add(98); - _fileInfoPopIDs.Add(99); + #ifndef Z7_SFX + _fileInfoPopIDs.Add(k_7z_id_Encrypted); + _fileInfoPopIDs.Add(k_7z_id_Method); + _fileInfoPopIDs.Add(k_7z_id_Block); #endif - #ifdef _MULTI_PACK - _fileInfoPopIDs.Add(100); - _fileInfoPopIDs.Add(101); - _fileInfoPopIDs.Add(102); - _fileInfoPopIDs.Add(103); - _fileInfoPopIDs.Add(104); + #ifdef Z7_7Z_SHOW_PACK_STREAMS_SIZES + for (unsigned i = 0; i < 5; i++) + _fileInfoPopIDs.Add(k_7z_id_PackedSize0 + i); #endif - #ifndef _SFX + #ifndef Z7_SFX InsertToHead(_fileInfoPopIDs, NID::kMTime); InsertToHead(_fileInfoPopIDs, NID::kPackInfo); InsertToHead(_fileInfoPopIDs, NID::kSize); @@ -140,25 +144,29 @@ void CHandler::FillPopIDs() #endif } -STDMETHODIMP CHandler::GetNumberOfProperties(UInt32 *numProps) +Z7_COM7F_IMF(CHandler::GetNumberOfProperties(UInt32 *numProps)) { *numProps = _fileInfoPopIDs.Size(); return S_OK; } -STDMETHODIMP CHandler::GetPropertyInfo(UInt32 index, BSTR *name, PROPID *propID, VARTYPE *varType) +Z7_COM7F_IMF(CHandler::GetPropertyInfo(UInt32 index, BSTR *name, PROPID *propID, VARTYPE *varType)) { if (index >= _fileInfoPopIDs.Size()) return E_INVALIDARG; - UInt64 id = _fileInfoPopIDs[index]; - for (unsigned i = 0; i < ARRAY_SIZE(kPropMap); i++) + const UInt64 id = _fileInfoPopIDs[index]; + for (unsigned i = 0; i < Z7_ARRAY_SIZE(kPropMap); i++) { const CPropMap &pr = kPropMap[i]; if (pr.FilePropID == id) { + *propID = pr.StatPropID; + *varType = pr.vt; + /* const CStatProp &st = pr.StatProp; *propID = st.PropID; *varType = st.vt; + */ /* if (st.lpwstrName) *name = ::SysAllocString(st.lpwstrName); diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zProperties.h b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zProperties.h index 66181795..091c39b4 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zProperties.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zProperties.h @@ -1,13 +1,16 @@ // 7zProperties.h -#ifndef __7Z_PROPERTIES_H -#define __7Z_PROPERTIES_H +#ifndef ZIP7_INC_7Z_PROPERTIES_H +#define ZIP7_INC_7Z_PROPERTIES_H #include "../../PropID.h" namespace NArchive { namespace N7z { +// #define Z7_7Z_SHOW_PACK_STREAMS_SIZES // for debug + +#ifdef Z7_7Z_SHOW_PACK_STREAMS_SIZES enum { kpidPackedSize0 = kpidUserDefined, @@ -16,6 +19,7 @@ enum kpidPackedSize3, kpidPackedSize4 }; +#endif }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zRegister.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zRegister.cpp index 389b5407..1f11079e 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zRegister.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zRegister.cpp @@ -15,7 +15,13 @@ REGISTER_ARC_IO_DECREMENT_SIG( "7z", "7z", NULL, 7, k_Signature_Dec, 0, - NArcInfoFlags::kFindSignature, - NULL); + NArcInfoFlags::kFindSignature + | NArcInfoFlags::kCTime + | NArcInfoFlags::kATime + | NArcInfoFlags::kMTime + | NArcInfoFlags::kMTime_Default + , TIME_PREC_TO_ARC_FLAGS_MASK(NFileTimeType::kWindows) + | TIME_PREC_TO_ARC_FLAGS_TIME_DEFAULT(NFileTimeType::kWindows) + , NULL) }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zSpecStream.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zSpecStream.cpp index 8e45d987..8b531bc7 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zSpecStream.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zSpecStream.cpp @@ -4,19 +4,28 @@ #include "7zSpecStream.h" -STDMETHODIMP CSequentialInStreamSizeCount2::Read(void *data, UInt32 size, UInt32 *processedSize) +/* +Z7_COM7F_IMF(CSequentialInStreamSizeCount2::Read(void *data, UInt32 size, UInt32 *processedSize)) { UInt32 realProcessedSize; - HRESULT result = _stream->Read(data, size, &realProcessedSize); + const HRESULT result = _stream->Read(data, size, &realProcessedSize); _size += realProcessedSize; if (processedSize) *processedSize = realProcessedSize; return result; } -STDMETHODIMP CSequentialInStreamSizeCount2::GetSubStreamSize(UInt64 subStream, UInt64 *value) +Z7_COM7F_IMF(CSequentialInStreamSizeCount2::GetSubStreamSize(UInt64 subStream, UInt64 *value)) { if (!_getSubStreamSize) return E_NOTIMPL; return _getSubStreamSize->GetSubStreamSize(subStream, value); } + +Z7_COM7F_IMF(CSequentialInStreamSizeCount2::GetNextInSubStream(UInt64 *streamIndexRes, ISequentialInStream **stream)) +{ + if (!_compressGetSubStreamSize) + return E_NOTIMPL; + return _compressGetSubStreamSize->GetNextInSubStream(streamIndexRes, stream); +} +*/ diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zSpecStream.h b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zSpecStream.h index 21155069..78f631e4 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zSpecStream.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zSpecStream.h @@ -1,35 +1,49 @@ // 7zSpecStream.h -#ifndef __7Z_SPEC_STREAM_H -#define __7Z_SPEC_STREAM_H +#ifndef ZIP7_INC_7Z_SPEC_STREAM_H +#define ZIP7_INC_7Z_SPEC_STREAM_H #include "../../../Common/MyCom.h" #include "../../ICoder.h" -class CSequentialInStreamSizeCount2: +/* +#define Z7_COM_QI_ENTRY_AG_2(i, sub0, sub) else if (iid == IID_ ## i) \ + { if (!sub) RINOK(sub0->QueryInterface(IID_ ## i, (void **)&sub)) \ + { i *ti = this; *outObject = ti; } } + +class CSequentialInStreamSizeCount2 Z7_final: public ISequentialInStream, public ICompressGetSubStreamSize, + public ICompressInSubStreams, public CMyUnknownImp { + Z7_COM_QI_BEGIN2(ISequentialInStream) + Z7_COM_QI_ENTRY(ICompressGetSubStreamSize) + Z7_COM_QI_ENTRY_AG_2(ISequentialInStream, _stream, _compressGetSubStreamSize) + Z7_COM_QI_END + Z7_COM_ADDREF_RELEASE + + Z7_IFACE_COM7_IMP(ISequentialInStream) + Z7_IFACE_COM7_IMP(ICompressGetSubStreamSize) + Z7_IFACE_COM7_IMP(ICompressInSubStreams) + CMyComPtr _stream; CMyComPtr _getSubStreamSize; + CMyComPtr _compressGetSubStreamSize; UInt64 _size; public: void Init(ISequentialInStream *stream) { _size = 0; _getSubStreamSize.Release(); + _compressGetSubStreamSize.Release(); _stream = stream; _stream.QueryInterface(IID_ICompressGetSubStreamSize, &_getSubStreamSize); + _stream.QueryInterface(IID_ICompressInSubStreams, &_compressGetSubStreamSize); } UInt64 GetSize() const { return _size; } - - MY_UNKNOWN_IMP2(ISequentialInStream, ICompressGetSubStreamSize) - - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); - - STDMETHOD(GetSubStreamSize)(UInt64 subStream, UInt64 *value); }; +*/ #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zUpdate.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zUpdate.cpp index 68e57f09..b05d3cf7 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zUpdate.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zUpdate.cpp @@ -4,6 +4,8 @@ #include "../../../../C/CpuArch.h" +#include "../../../Common/MyLinux.h" +#include "../../../Common/StringToInt.h" #include "../../../Common/Wildcard.h" #include "../../Common/CreateCoder.h" @@ -24,24 +26,36 @@ namespace NArchive { namespace N7z { - #define k_X86 k_BCJ struct CFilterMode { UInt32 Id; - UInt32 Delta; + UInt32 Delta; // required File Size alignment, if Id is not k_Delta. + // (Delta == 0) means unknown alignment + UInt32 Offset; // for k_ARM64 / k_RISCV + // UInt32 AlignSizeOpt; // for k_ARM64 - CFilterMode(): Id(0), Delta(0) {} + void ClearFilterMode() + { + Id = 0; + Delta = 0; + Offset = 0; + // AlignSizeOpt = 0; + } + // it sets Delta as Align value, if Id is exe filter + // in another cases it sets Delta = 0, that void SetDelta() { if (Id == k_IA64) Delta = 16; - else if (Id == k_ARM || Id == k_PPC || Id == k_SPARC) + else if (Id == k_ARM64 || Id == k_ARM || Id == k_PPC || Id == k_SPARC) Delta = 4; - else if (Id == k_ARMT) + else if (Id == k_ARMT || Id == k_RISCV) Delta = 2; + else if (Id == k_BCJ || Id == k_BCJ2) + Delta = 1; // do we need it? else Delta = 0; } @@ -55,8 +69,8 @@ struct CFilterMode #define PE_SIG 0x00004550 #define PE_OptHeader_Magic_32 0x10B #define PE_OptHeader_Magic_64 0x20B -#define PE_SectHeaderSize 40 -#define PE_SECT_EXECUTE 0x20000000 +// #define PE_SectHeaderSize 40 +// #define PE_SECT_EXECUTE 0x20000000 static int Parse_EXE(const Byte *buf, size_t size, CFilterMode *filterMode) { @@ -74,10 +88,13 @@ static int Parse_EXE(const Byte *buf, size_t size, CFilterMode *filterMode) return 0; p += 4; - switch (GetUi16(p)) + const unsigned machine = GetUi16(p); + + switch (machine) { case 0x014C: case 0x8664: filterId = k_X86; break; + case 0xAA64: filterId = k_ARM64; break; /* IMAGE_FILE_MACHINE_ARM 0x01C0 // ARM LE @@ -90,10 +107,16 @@ static int Parse_EXE(const Byte *buf, size_t size, CFilterMode *filterMode) case 0x01C2: filterId = k_ARM; break; // WinCE new case 0x01C4: filterId = k_ARMT; break; // WinRT + case 0x5032: // RISCV32 + case 0x5064: // RISCV64 + // case 0x5128: // RISCV128 + filterId = k_RISCV; break; + case 0x0200: filterId = k_IA64; break; default: return 0; } + const UInt32 numSections = GetUi16(p + 2); optHeaderSize = GetUi16(p + 16); if (optHeaderSize > (1 << 10)) return 0; @@ -109,11 +132,94 @@ static int Parse_EXE(const Byte *buf, size_t size, CFilterMode *filterMode) return 0; } + // Windows exe file sizes are not aligned for 4 KiB. + // So we can't use (CFilterMode::Offset != 0) in solid archives. + // So we just don't set Offset here. +#define NUM_SCAN_SECTIONS_MAX (1 << 6) +// #define EXE_SECTION_OFFSET_MAX (1 << 27) +// #define EXE_SECTION_SIZE_MIN (1 << 8) +// #define EXE_SECTION_SIZE_MAX (1 << 27) +#define PE_SectHeaderSize 40 +// #define PE_SECT_EXECUTE 0x20000000 + +/* + if (numSections > NUM_SCAN_SECTIONS_MAX) + return 0; +*/ + + if ((size_t)(p - buf) + optHeaderSize <= size) + { + p += optHeaderSize; +/* + // UInt32 numExeSections = 0; + // bool execute_finded = false; + // UInt32 sect_va = 0; + // UInt32 sect_size = 0; + // UInt32 sect_offset = 0; +*/ + if (numSections <= NUM_SCAN_SECTIONS_MAX) + if (machine == 0x8664) + for (UInt32 i = 0; i < numSections + ; i++, p += PE_SectHeaderSize) + { + // UInt32 characts, rawSize, offset; + if ((UInt32)(p - buf) + PE_SectHeaderSize > size) + { + // return 0; + break; + } + if (memcmp(p, ".a64xrm", 8) == 0) + { + // ARM64EC + filterId = k_ARM64; + break; + } +/* + rawSize = GetUi32(p + 16); + offset = GetUi32(p + 20); + characts = GetUi32(p + 36); + if (rawSize >= EXE_SECTION_SIZE_MIN && + rawSize <= EXE_SECTION_SIZE_MAX && + offset <= EXE_SECTION_OFFSET_MAX && + // offset < limit && + offset > 0) + { + if ((characts & PE_SECT_EXECUTE) != 0) + { + // execute_finded = true; + // sect_va = GetUi32(p + 12); + // sect_size = rawSize; + // sect_offset = offset; + break; + } + } +*/ + } + } + + /* + filterMode->Offset = 0; + if (filterId == k_ARM64) + { + // filterMode->AlignSizeOpt = (1 << 12); + // const UInt32 offs = (sect_va - sect_offset) & 0xFFF; + // if (offs != 0) + // filterMode->Offset = offs; // change it + } + */ filterMode->Id = filterId; return 1; } +/* + Filters don't improve the compression ratio for relocatable object files (".o"). + But we can get compression ratio gain, if we compress object + files and executables in same solid block. + So we use filters for relocatable object files (".o"): +*/ +// #define Z7_7Z_CREATE_ARC_DISABLE_FILTER_FOR_OBJ + /* ---------- ELF ---------- */ #define ELF_SIG 0x464C457F @@ -124,13 +230,13 @@ static int Parse_EXE(const Byte *buf, size_t size, CFilterMode *filterMode) #define ELF_DATA_2LSB 1 #define ELF_DATA_2MSB 2 -static UInt16 Get16(const Byte *p, Bool be) { if (be) return (UInt16)GetBe16(p); return (UInt16)GetUi16(p); } -static UInt32 Get32(const Byte *p, Bool be) { if (be) return GetBe32(p); return GetUi32(p); } -// static UInt64 Get64(const Byte *p, Bool be) { if (be) return GetBe64(p); return GetUi64(p); } +static UInt16 Get16(const Byte *p, BoolInt be) { if (be) return (UInt16)GetBe16(p); return (UInt16)GetUi16(p); } +static UInt32 Get32(const Byte *p, BoolInt be) { if (be) return GetBe32(p); return GetUi32(p); } +// static UInt64 Get64(const Byte *p, BoolInt be) { if (be) return GetBe64(p); return GetUi64(p); } static int Parse_ELF(const Byte *buf, size_t size, CFilterMode *filterMode) { - Bool /* is32, */ be; + BoolInt /* is32, */ be; UInt32 filterId; if (size < 512 || buf[6] != 1) /* ver */ @@ -153,6 +259,12 @@ static int Parse_ELF(const Byte *buf, size_t size, CFilterMode *filterMode) default: return 0; } +#ifdef Z7_7Z_CREATE_ARC_DISABLE_FILTER_FOR_OBJ +#define ELF_ET_REL 1 + if (Get16(buf + 0x10, be) == ELF_ET_REL) + return 0; +#endif + switch (Get16(buf + 0x12, be)) { case 3: @@ -163,9 +275,11 @@ static int Parse_ELF(const Byte *buf, size_t size, CFilterMode *filterMode) case 43: filterId = k_SPARC; break; case 20: case 21: if (!be) return 0; filterId = k_PPC; break; - case 40: if ( be) return 0; filterId = k_ARM; break; - - /* Some IA-64 ELF exacutable have size that is not aligned for 16 bytes. + case 40: if (be) return 0; filterId = k_ARM; break; + case 183: if (be) return 0; filterId = k_ARM64; break; + case 243: if (be) return 0; filterId = k_RISCV; break; + + /* Some IA-64 ELF executables have size that is not aligned for 16 bytes. So we don't use IA-64 filter for IA-64 ELF */ // case 50: if ( be) return 0; filterId = k_IA64; break; @@ -192,6 +306,7 @@ static int Parse_ELF(const Byte *buf, size_t size, CFilterMode *filterMode) #define MACH_MACHINE_PPC 18 #define MACH_MACHINE_PPC64 (MACH_ARCH_ABI64 | MACH_MACHINE_PPC) #define MACH_MACHINE_AMD64 (MACH_ARCH_ABI64 | MACH_MACHINE_386) +#define MACH_MACHINE_ARM64 (MACH_ARCH_ABI64 | MACH_MACHINE_ARM) static unsigned Parse_MACH(const Byte *buf, size_t size, CFilterMode *filterMode) { @@ -200,7 +315,7 @@ static unsigned Parse_MACH(const Byte *buf, size_t size, CFilterMode *filterMode if (size < 512) return 0; - Bool /* mode64, */ be; + BoolInt /* mode64, */ be; switch (GetUi32(buf)) { case MACH_SIG_BE_32: /* mode64 = False; */ be = True; break; @@ -210,6 +325,12 @@ static unsigned Parse_MACH(const Byte *buf, size_t size, CFilterMode *filterMode default: return 0; } +#ifdef Z7_7Z_CREATE_ARC_DISABLE_FILTER_FOR_OBJ +#define MACH_TYPE_OBJECT 1 + if (Get32(buf + 0xC, be) == MACH_TYPE_OBJECT) + return 0; +#endif + switch (Get32(buf + 4, be)) { case MACH_MACHINE_386: @@ -218,6 +339,7 @@ static unsigned Parse_MACH(const Byte *buf, size_t size, CFilterMode *filterMode case MACH_MACHINE_SPARC: if (!be) return 0; filterId = k_SPARC; break; case MACH_MACHINE_PPC: case MACH_MACHINE_PPC64: if (!be) return 0; filterId = k_PPC; break; + case MACH_MACHINE_ARM64: if ( be) return 0; filterId = k_ARM64; break; default: return 0; } @@ -239,7 +361,7 @@ static unsigned Parse_MACH(const Byte *buf, size_t size, CFilterMode *filterMode #define RIFF_SIG 0x46464952 -static Bool Parse_WAV(const Byte *buf, size_t size, CFilterMode *filterMode) +static BoolInt Parse_WAV(const Byte *buf, size_t size, CFilterMode *filterMode) { UInt32 subChunkSize, pos; if (size < 0x2C) @@ -254,10 +376,12 @@ static Bool Parse_WAV(const Byte *buf, size_t size, CFilterMode *filterMode) if (subChunkSize < 0x10 || subChunkSize > 0x12 || GetUi16(buf + 0x14) != 1) return False; - unsigned numChannels = GetUi16(buf + 0x16); - unsigned bitsPerSample = GetUi16(buf + 0x22); - - if ((bitsPerSample & 0x7) != 0 || bitsPerSample >= 256 || numChannels >= 256) + const unsigned numChannels = GetUi16(buf + 0x16); + const unsigned bitsPerSample = GetUi16(buf + 0x22); + if ((bitsPerSample & 0x7) != 0) + return False; + const UInt32 delta = (UInt32)numChannels * (bitsPerSample >> 3); + if (delta == 0 || delta > 256) return False; pos = 0x14 + subChunkSize; @@ -271,9 +395,6 @@ static Bool Parse_WAV(const Byte *buf, size_t size, CFilterMode *filterMode) subChunkSize = GetUi32(buf + pos + 4); if (GetUi32(buf + pos) == WAV_SUBCHUNK_data) { - unsigned delta = numChannels * (bitsPerSample >> 3); - if (delta >= 256) - return False; filterMode->Id = k_Delta; filterMode->Delta = delta; return True; @@ -285,10 +406,15 @@ static Bool Parse_WAV(const Byte *buf, size_t size, CFilterMode *filterMode) return False; } -static Bool ParseFile(const Byte *buf, size_t size, CFilterMode *filterMode) + +/* + filterMode->Delta will be set as: + = delta value : [1, 256] : for k_Delta + = 0 for another filters (branch filters) +*/ +static BoolInt ParseFile(const Byte *buf, size_t size, CFilterMode *filterMode) { - filterMode->Id = 0; - filterMode->Delta = 0; + filterMode->ClearFilterMode(); if (Parse_EXE(buf, size, filterMode)) return True; if (Parse_ELF(buf, size, filterMode)) return True; @@ -304,7 +430,11 @@ struct CFilterMode2: public CFilterMode bool Encrypted; unsigned GroupIndex; - CFilterMode2(): Encrypted(false) {} + void Construct() + { + ClearFilterMode(); + Encrypted = false; + } int Compare(const CFilterMode2 &m) const { @@ -316,18 +446,44 @@ struct CFilterMode2: public CFilterMode else if (!m.Encrypted) return 1; - if (Id < m.Id) return -1; - if (Id > m.Id) return 1; + const UInt32 id1 = Id; + const UInt32 id2 = m.Id; + /* + // we can change the order to place k_ARM64 files close to another exe files + if (id1 <= k_SPARC && + id2 <= k_SPARC) + { + #define k_ARM64_FOR_SORT 0x3030901 + if (id1 == k_ARM64) id1 = k_ARM64_FOR_SORT; + if (id2 == k_ARM64) id2 = k_ARM64_FOR_SORT; + } + */ + if (id1 < id2) return -1; + if (id1 > id2) return 1; if (Delta < m.Delta) return -1; if (Delta > m.Delta) return 1; + if (Offset < m.Offset) return -1; + if (Offset > m.Offset) return 1; + + /* we don't go here, because GetGroup() + and operator ==(const CFilterMode2 &m) + add only unique CFilterMode2:: { Id, Delta, Offset, Encrypted } items. + */ + /* + if (GroupIndex < m.GroupIndex) return -1; + if (GroupIndex > m.GroupIndex) return 1; + */ return 0; } bool operator ==(const CFilterMode2 &m) const { - return Id == m.Id && Delta == m.Delta && Encrypted == m.Encrypted; + return Id == m.Id + && Delta == m.Delta + && Offset == m.Offset + && Encrypted == m.Encrypted; } }; @@ -368,6 +524,8 @@ static inline bool IsExeFilter(CMethodId m) { switch (m) { + case k_ARM64: + case k_RISCV: case k_BCJ: case k_BCJ2: case k_ARM: @@ -376,6 +534,7 @@ static inline bool IsExeFilter(CMethodId m) case k_SPARC: case k_IA64: return true; + default: break; } return false; } @@ -384,8 +543,10 @@ static unsigned Get_FilterGroup_for_Folder( CRecordVector &filters, const CFolderEx &f, bool extractFilter) { CFilterMode2 m; - m.Id = 0; - m.Delta = 0; + m.Construct(); + // m.Id = 0; + // m.Delta = 0; + // m.Offset = 0; m.Encrypted = f.IsEncrypted(); if (extractFilter) @@ -406,6 +567,10 @@ static unsigned Get_FilterGroup_for_Folder( if (m.Id == k_BCJ2) m.Id = k_BCJ; m.SetDelta(); + if (m.Id == k_ARM64 || + m.Id == k_RISCV) + if (coder.Props.Size() == 4) + m.Offset = GetUi32(coder.Props); } } @@ -418,16 +583,13 @@ static unsigned Get_FilterGroup_for_Folder( static HRESULT WriteRange(IInStream *inStream, ISequentialOutStream *outStream, UInt64 position, UInt64 size, ICompressProgressInfo *progress) { - RINOK(inStream->Seek(position, STREAM_SEEK_SET, 0)); - CLimitedSequentialInStream *streamSpec = new CLimitedSequentialInStream; - CMyComPtr inStreamLimited(streamSpec); + RINOK(InStream_SeekSet(inStream, position)) + CMyComPtr2_Create streamSpec; streamSpec->SetStream(inStream); streamSpec->Init(size); - - NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder; - CMyComPtr copyCoder = copyCoderSpec; - RINOK(copyCoder->Code(inStreamLimited, outStream, NULL, NULL, progress)); - return (copyCoderSpec->TotalSize == size ? S_OK : E_FAIL); + CMyComPtr2_Create copyCoder; + RINOK(copyCoder.Interface()->Code(streamSpec, outStream, NULL, NULL, progress)) + return (copyCoder->TotalSize == size ? S_OK : E_FAIL); } /* @@ -446,7 +608,7 @@ UString CUpdateItem::GetExtension() const } */ -#define RINOZ(x) { int __tt = (x); if (__tt != 0) return __tt; } +#define RINOZ(x) { const int _t_ = (x); if (_t_ != 0) return _t_; } #define RINOZ_COMP(a, b) RINOZ(MyCompare(a, b)) @@ -557,7 +719,7 @@ static int CompareEmptyItems(const unsigned *p1, const unsigned *p2, void *param return (u1.IsDir && u1.IsAnti) ? -n : n; } -static const char *g_Exts = +static const char * const g_Exts = " 7z xz lzma ace arc arj bz tbz bz2 tbz2 cab deb gz tgz ha lha lzh lzo lzx pak rar rpm sit zoo" " zip jar ear war msi" " 3gp avi mov mpeg mpg mpe wmv" @@ -631,30 +793,30 @@ struct CRefItem unsigned NamePos; unsigned ExtensionIndex; - CRefItem() {}; - CRefItem(UInt32 index, const CUpdateItem &ui, bool sortByType): - UpdateItem(&ui), - Index(index), - ExtensionPos(0), - NamePos(0), - ExtensionIndex(0) + void Construct(UInt32 index, const CUpdateItem &ui, bool sortByType) { + UpdateItem = &ui; + Index = index; + ExtensionPos = 0; + NamePos = 0; + ExtensionIndex = 0; + if (sortByType) { - int slashPos = ui.Name.ReverseFind_PathSepar(); - NamePos = slashPos + 1; - int dotPos = ui.Name.ReverseFind_Dot(); + const int slashPos = ui.Name.ReverseFind_PathSepar(); + NamePos = (unsigned)(slashPos + 1); + const int dotPos = ui.Name.ReverseFind_Dot(); if (dotPos <= slashPos) ExtensionPos = ui.Name.Len(); else { - ExtensionPos = dotPos + 1; + ExtensionPos = (unsigned)(dotPos + 1); if (ExtensionPos != ui.Name.Len()) { AString s; for (unsigned pos = ExtensionPos;; pos++) { - wchar_t c = ui.Name[pos]; + const wchar_t c = ui.Name[pos]; if (c >= 0x80) break; if (c == 0) @@ -662,7 +824,7 @@ struct CRefItem ExtensionIndex = GetExtIndex(s); break; } - s += (char)MyCharLower_Ascii((char)c); + s.Add_Char((char)MyCharLower_Ascii((char)c)); } } } @@ -711,16 +873,16 @@ static int CompareUpdateItems(const CRefItem *p1, const CRefItem *p2, void *para // bool sortByType = *(bool *)param; const CSortParam *sortParam = (const CSortParam *)param; - bool sortByType = sortParam->SortByType; + const bool sortByType = sortParam->SortByType; if (sortByType) { - RINOZ_COMP(a1.ExtensionIndex, a2.ExtensionIndex); - RINOZ(CompareFileNames(u1.Name.Ptr(a1.ExtensionPos), u2.Name.Ptr(a2.ExtensionPos))); - RINOZ(CompareFileNames(u1.Name.Ptr(a1.NamePos), u2.Name.Ptr(a2.NamePos))); + RINOZ_COMP(a1.ExtensionIndex, a2.ExtensionIndex) + RINOZ(CompareFileNames(u1.Name.Ptr(a1.ExtensionPos), u2.Name.Ptr(a2.ExtensionPos))) + RINOZ(CompareFileNames(u1.Name.Ptr(a1.NamePos), u2.Name.Ptr(a2.NamePos))) if (!u1.MTimeDefined && u2.MTimeDefined) return 1; if (u1.MTimeDefined && !u2.MTimeDefined) return -1; - if (u1.MTimeDefined && u2.MTimeDefined) RINOZ_COMP(u1.MTime, u2.MTime); - RINOZ_COMP(u1.Size, u2.Size); + if (u1.MTimeDefined && u2.MTimeDefined) RINOZ_COMP(u1.MTime, u2.MTime) + RINOZ_COMP(u1.Size, u2.Size) } /* int par1 = a1.UpdateItem->ParentFolderIndex; @@ -766,9 +928,9 @@ static int CompareUpdateItems(const CRefItem *p1, const CRefItem *p2, void *para } */ // RINOZ_COMP(a1.UpdateItem->ParentSortIndex, a2.UpdateItem->ParentSortIndex); - RINOK(CompareFileNames(u1.Name, u2.Name)); - RINOZ_COMP(a1.UpdateItem->IndexInClient, a2.UpdateItem->IndexInClient); - RINOZ_COMP(a1.UpdateItem->IndexInArchive, a2.UpdateItem->IndexInArchive); + RINOK(CompareFileNames(u1.Name, u2.Name)) + RINOZ_COMP(a1.UpdateItem->IndexInClient, a2.UpdateItem->IndexInClient) + RINOZ_COMP(a1.UpdateItem->IndexInArchive, a2.UpdateItem->IndexInArchive) return 0; } @@ -779,7 +941,7 @@ struct CSolidGroup CRecordVector folderRefs; }; -static const char * const g_ExeExts[] = +static const char * const g_Exe_Exts[] = { "dll" , "exe" @@ -788,14 +950,65 @@ static const char * const g_ExeExts[] = , "sys" }; -static bool IsExeExt(const wchar_t *ext) +static const char * const g_ExeUnix_Exts[] = { - for (unsigned i = 0; i < ARRAY_SIZE(g_ExeExts); i++) - if (StringsAreEqualNoCase_Ascii(ext, g_ExeExts[i])) + "so" + , "dylib" +}; + +static bool IsExt_Exe(const wchar_t *ext) +{ + for (unsigned i = 0; i < Z7_ARRAY_SIZE(g_Exe_Exts); i++) + if (StringsAreEqualNoCase_Ascii(ext, g_Exe_Exts[i])) return true; return false; } +/* +static bool IsExt_ExeUnix(const wchar_t *ext) +{ + for (unsigned i = 0; i < Z7_ARRAY_SIZE(g_ExeUnix_Exts); i++) + if (StringsAreEqualNoCase_Ascii(ext, g_ExeUnix_Exts[i])) + return true; + return false; +} +*/ + +// we try to find "so" extension in such name: libstdc++.so.6.0.29 +static bool IsExt_ExeUnix_NumericAllowed(const UString &path) +{ + unsigned pos = path.Len(); + unsigned dotPos = pos; + for (;;) + { + if (pos == 0) + return false; + const wchar_t c = path[--pos]; + if (IS_PATH_SEPAR(c)) + return false; + if (c == '.') + { + const unsigned num = (dotPos - pos) - 1; + if (num < 1) + return false; + const wchar_t *cur = path.Ptr(pos + 1); + for (unsigned i = 0; i < Z7_ARRAY_SIZE(g_ExeUnix_Exts); i++) + { + const char *ext = g_ExeUnix_Exts[i]; + if (num == MyStringLen(ext)) + if (IsString1PrefixedByString2_NoCase_Ascii(cur, ext)) + return true; + } + const wchar_t *end; + ConvertStringToUInt32(cur, &end); + if ((size_t)(end - cur) != num) + return false; + dotPos = pos; + } + } +} + + struct CAnalysis { CMyComPtr Callback; @@ -803,12 +1016,26 @@ struct CAnalysis bool ParseWav; bool ParseExe; + bool ParseExeUnix; + bool ParseNoExt; bool ParseAll; + /* + bool Need_ATime; + bool ATime_Defined; + FILETIME ATime; + */ + CAnalysis(): - ParseWav(true), + ParseWav(false), ParseExe(false), + ParseExeUnix(false), + ParseNoExt(false), ParseAll(false) + /* + , Need_ATime(false) + , ATime_Defined(false) + */ {} HRESULT GetFilterGroup(UInt32 index, const CUpdateItem &ui, CFilterMode &filterMode); @@ -820,32 +1047,46 @@ HRESULT CAnalysis::GetFilterGroup(UInt32 index, const CUpdateItem &ui, CFilterMo { filterMode.Id = 0; filterMode.Delta = 0; + filterMode.Offset = 0; CFilterMode filterModeTemp = filterMode; - int slashPos = ui.Name.ReverseFind_PathSepar(); - int dotPos = ui.Name.ReverseFind_Dot(); + const int slashPos = ui.Name.ReverseFind_PathSepar(); + const int dotPos = ui.Name.ReverseFind_Dot(); // if (dotPos > slashPos) { bool needReadFile = ParseAll; - + /* if (Callback) is not supported by client, + we still try to use file name extension to detect executable file */ bool probablyIsSameIsa = false; if (!needReadFile || !Callback) { - const wchar_t *ext; + const wchar_t *ext = NULL; if (dotPos > slashPos) - ext = ui.Name.Ptr(dotPos + 1); - else - ext = ui.Name.RightPtr(0); - - // p7zip uses the trick to store posix attributes in high 16 bits + ext = ui.Name.Ptr((unsigned)(dotPos + 1)); + // 7-zip stores posix attributes in high 16 bits and sets (0x8000) flag if (ui.Attrib & 0x8000) { - unsigned st_mode = ui.Attrib >> 16; - // st_mode = 00111; - if ((st_mode & 00111) && (ui.Size >= 2048)) + const unsigned st_mode = ui.Attrib >> 16; + /* note: executable ".so" can be without execute permission, + and symbolic link to such ".so" file is possible */ + // st_mode = 00111; // for debug + /* in Linux we expect such permissions: + 0755 : for most executables + 0644 : for some ".so" files + 0777 : in WSL for all files. + We can try to exclude some such 0777 cases from analysis, + if there is non-executable extension. + */ + + if ((st_mode & ( + MY_LIN_S_IXUSR | + MY_LIN_S_IXGRP | + MY_LIN_S_IXOTH)) != 0 + && MY_LIN_S_ISREG(st_mode) + && (ui.Size >= (1u << 11))) { #ifndef _WIN32 probablyIsSameIsa = true; @@ -854,73 +1095,120 @@ HRESULT CAnalysis::GetFilterGroup(UInt32 index, const CUpdateItem &ui, CFilterMo } } - if (IsExeExt(ext)) - { - needReadFile = true; - #ifdef _WIN32 - probablyIsSameIsa = true; - needReadFile = ParseExe; - #endif - } - else if (StringsAreEqualNoCase_Ascii(ext, "wav")) - { - needReadFile = ParseWav; - } - /* - else if (!needReadFile && ParseUnixExt) + if (!needReadFile) { - if (StringsAreEqualNoCase_Ascii(ext, "so") - || StringsAreEqualNoCase_Ascii(ext, "")) - - needReadFile = true; + if (!ext) + needReadFile = ParseNoExt; + else + { + bool isUnixExt = false; + if (ParseExeUnix) + isUnixExt = IsExt_ExeUnix_NumericAllowed(ui.Name); + if (isUnixExt) + { + needReadFile = true; + #ifndef _WIN32 + probablyIsSameIsa = true; + #endif + } + else if (IsExt_Exe(ext)) + { + needReadFile = ParseExe; + #ifdef _WIN32 + probablyIsSameIsa = true; + #endif + } + else if (StringsAreEqualNoCase_Ascii(ext, "wav")) + { + if (!needReadFile) + needReadFile = ParseWav; + } + } } - */ } - if (needReadFile && Callback) + if (needReadFile) { - if (Buffer.Size() != kAnalysisBufSize) - { - Buffer.Alloc(kAnalysisBufSize); - } + BoolInt parseRes = false; + if (Callback) { + if (Buffer.Size() != kAnalysisBufSize) + Buffer.Alloc(kAnalysisBufSize); CMyComPtr stream; HRESULT result = Callback->GetStream2(index, &stream, NUpdateNotifyOp::kAnalyze); if (result == S_OK && stream) { + /* + if (Need_ATime) + { + // access time could be changed in analysis pass + CMyComPtr getProps; + stream.QueryInterface(IID_IStreamGetProps, (void **)&getProps); + if (getProps) + if (getProps->GetProps(NULL, NULL, &ATime, NULL, NULL) == S_OK) + ATime_Defined = true; + } + */ size_t size = kAnalysisBufSize; result = ReadStream(stream, Buffer, &size); stream.Release(); // RINOK(Callback->SetOperationResult2(index, NUpdate::NOperationResult::kOK)); if (result == S_OK) { - Bool parseRes = ParseFile(Buffer, size, &filterModeTemp); - if (parseRes && filterModeTemp.Delta == 0) - { - filterModeTemp.SetDelta(); - if (filterModeTemp.Delta != 0 && filterModeTemp.Id != k_Delta) - { - if (ui.Size % filterModeTemp.Delta != 0) - { - parseRes = false; - } - } - } - if (!parseRes) + parseRes = ParseFile(Buffer, size, &filterModeTemp); + } + } + } // Callback + else if (probablyIsSameIsa) + { + #ifdef MY_CPU_X86_OR_AMD64 + filterModeTemp.Id = k_X86; + #endif + #ifdef MY_CPU_ARM64 + filterModeTemp.Id = k_ARM64; + #endif + #ifdef MY_CPU_RISCV + filterModeTemp.Id = k_RISCV; + #endif + #ifdef MY_CPU_SPARC + filterModeTemp.Id = k_SPARC; + #endif + parseRes = true; + } + + if (parseRes + && filterModeTemp.Id != k_Delta + && filterModeTemp.Delta == 0) + { + /* ParseFile() sets (filterModeTemp.Delta == 0) for all + methods except of k_Delta. */ + // it's not k_Delta + // So we call SetDelta() to set Delta + filterModeTemp.SetDelta(); + if (filterModeTemp.Delta > 1) + { + /* If file Size is not aligned, then branch filter + will not work for next file in solid block. + Maybe we should allow filter for non-aligned-size file in non-solid archives ? + */ + if (ui.Size % filterModeTemp.Delta != 0) + parseRes = false; + // windows exe files are not aligned for 4 KiB. + /* + else if (filterModeTemp.Id == k_ARM64 && filterModeTemp.Offset != 0) + { + if (ui.Size % (1 << 12) != 0) { - filterModeTemp.Id = 0; - filterModeTemp.Delta = 0; + // If Size is not aligned for 4 KiB, then Offset will not work for next file in solid block. + // so we place such file in group with (Offset==0). + filterModeTemp.Offset = 0; } } + */ } } - } - else if ((needReadFile && !Callback) || probablyIsSameIsa) - { - #ifdef MY_CPU_X86_OR_AMD64 - if (probablyIsSameIsa) - filterModeTemp.Id = k_X86; - #endif + if (!parseRes) + filterModeTemp.ClearFilterMode(); } } @@ -934,6 +1222,8 @@ static inline void GetMethodFull(UInt64 methodID, UInt32 numStreams, CMethodFull m.NumStreams = numStreams; } + +// we add bond for mode.Methods[0] that is filter static HRESULT AddBondForFilter(CCompressionMethodMode &mode) { for (unsigned c = 1; c < mode.Methods.Size(); c++) @@ -951,16 +1241,19 @@ static HRESULT AddBondForFilter(CCompressionMethodMode &mode) return E_INVALIDARG; } -static HRESULT AddFilterBond(CCompressionMethodMode &mode) +/* +static HRESULT AddBondForFilter_if_ThereAreBonds(CCompressionMethodMode &mode) { if (!mode.Bonds.IsEmpty()) return AddBondForFilter(mode); return S_OK; } +*/ static HRESULT AddBcj2Methods(CCompressionMethodMode &mode) { // mode.Methods[0] must be k_BCJ2 method ! + // mode.Methods[1] : we expect that there is at least one method after BCJ2 CMethodFull m; GetMethodFull(k_LZMA, 1, m); @@ -972,7 +1265,7 @@ static HRESULT AddBcj2Methods(CCompressionMethodMode &mode) m.AddProp32(NCoderPropID::kLitContextBits, 0); // m.AddProp_Ascii(NCoderPropID::kMatchFinder, "BT2"); - unsigned methodIndex = mode.Methods.Size(); + const unsigned methodIndex = mode.Methods.Size(); if (mode.Bonds.IsEmpty()) { @@ -989,117 +1282,162 @@ static HRESULT AddBcj2Methods(CCompressionMethodMode &mode) mode.Methods.Add(m); mode.Methods.Add(m); - RINOK(AddBondForFilter(mode)); + RINOK(AddBondForFilter(mode)) CBond2 bond; - bond.OutCoder = 0; + bond.OutCoder = 0; // index of BCJ2 coder bond.InCoder = methodIndex; bond.OutStream = 1; mode.Bonds.Add(bond); bond.InCoder = methodIndex + 1; bond.OutStream = 2; mode.Bonds.Add(bond); return S_OK; } + static HRESULT MakeExeMethod(CCompressionMethodMode &mode, - const CFilterMode &filterMode, /* bool addFilter, */ bool bcj2Filter) + const CFilterMode &filterMode, + const bool bcj2_IsAllowed, + const CUIntVector &disabledFilterIDs) { if (mode.Filter_was_Inserted) { + // filter was inserted, but bond for that filter was not added still. const CMethodFull &m = mode.Methods[0]; - CMethodId id = m.Id; - if (id == k_BCJ2) + if (m.Id == k_BCJ2) return AddBcj2Methods(mode); if (!m.IsSimpleCoder()) return E_NOTIMPL; - // if (Bonds.IsEmpty()) we can create bonds later - return AddFilterBond(mode); + if (mode.Bonds.IsEmpty()) + return S_OK; + return AddBondForFilter(mode); } if (filterMode.Id == 0) return S_OK; - CMethodFull &m = mode.Methods.InsertNew(0); + unsigned nextCoder; - { - FOR_VECTOR(k, mode.Bonds) - { - CBond2 &bond = mode.Bonds[k]; - bond.InCoder++; - bond.OutCoder++; - } - } + const bool useBcj2 = bcj2_IsAllowed + && Is86Filter(filterMode.Id) + && disabledFilterIDs.FindInSorted(k_BCJ2) < 0; - HRESULT res; - - if (bcj2Filter && Is86Filter(filterMode.Id)) + if (!useBcj2 && disabledFilterIDs.FindInSorted(filterMode.Id) >= 0) { - GetMethodFull(k_BCJ2, 4, m); - res = AddBcj2Methods(mode); + // required filter is disabled, + // but we still can use information about data alignment. +#if 0 // 1 for debug + // we can return here, if we want default lzma properties + return S_OK; +#else + // we will try to change lzma/lzma2 properties + nextCoder = 0; + if (!mode.Bonds.IsEmpty()) + for (unsigned c = 0;; c++) + { + if (c == mode.Methods.Size()) + return S_OK; + if (!mode.IsThereBond_to_Coder(c)) + { + nextCoder = c; + break; + } + } +#endif } else { + // we insert new filter method: + CMethodFull &m = mode.Methods.InsertNew(0); // 0 == index of new inserted item + { + // we move all coder indexes in bonds up for 1 position: + FOR_VECTOR (k, mode.Bonds) + { + CBond2 &bond = mode.Bonds[k]; + bond.InCoder++; + bond.OutCoder++; + } + } + if (useBcj2) + { + GetMethodFull(k_BCJ2, 4, m); + return AddBcj2Methods(mode); + } + GetMethodFull(filterMode.Id, 1, m); + if (filterMode.Id == k_Delta) m.AddProp32(NCoderPropID::kDefaultProp, filterMode.Delta); - res = AddFilterBond(mode); - - int alignBits = -1; - if (filterMode.Id == k_Delta || filterMode.Delta != 0) - { - if (filterMode.Delta == 1) alignBits = 0; - else if (filterMode.Delta == 2) alignBits = 1; - else if (filterMode.Delta == 4) alignBits = 2; - else if (filterMode.Delta == 8) alignBits = 3; - else if (filterMode.Delta == 16) alignBits = 4; - } - else + else if (filterMode.Id == k_ARM64 + || filterMode.Id == k_RISCV) { - // alignBits = GetAlignForFilterMethod(filterMode.Id); + // if (filterMode.Offset != 0) + m.AddProp32( + NCoderPropID::kDefaultProp, + // NCoderPropID::kBranchOffset, + filterMode.Offset); } - if (res == S_OK && alignBits >= 0) + nextCoder = 1; + if (!mode.Bonds.IsEmpty()) { - unsigned nextCoder = 1; - if (!mode.Bonds.IsEmpty()) - { - nextCoder = mode.Bonds.Back().InCoder; - } - if (nextCoder < mode.Methods.Size()) - { - CMethodFull &nextMethod = mode.Methods[nextCoder]; - if (nextMethod.Id == k_LZMA || nextMethod.Id == k_LZMA2) - { - if (!nextMethod.Are_Lzma_Model_Props_Defined()) - { - if (alignBits != 0) - { - if (alignBits > 2 || filterMode.Id == k_Delta) - nextMethod.AddProp32(NCoderPropID::kPosStateBits, alignBits); - unsigned lc = 0; - if (alignBits < 3) - lc = 3 - alignBits; - nextMethod.AddProp32(NCoderPropID::kLitContextBits, lc); - nextMethod.AddProp32(NCoderPropID::kLitPosBits, alignBits); - } - } - } - } + RINOK(AddBondForFilter(mode)) + nextCoder = mode.Bonds.Back().InCoder; } } - return res; + if (nextCoder >= mode.Methods.Size()) + { + // we don't expect that case, if there was non-filter method. + // but we return S_OK to support filter-only case. + return S_OK; + } + + int alignBits = -1; + { + const UInt32 delta = filterMode.Delta; + if (delta == 0 || delta > 16) + { + // if (delta == 0) alignBits = GetAlignForFilterMethod(filterMode.Id); + } + else if ((delta & ((1 << 4) - 1)) == 0) alignBits = 4; + else if ((delta & ((1 << 3) - 1)) == 0) alignBits = 3; + else if ((delta & ((1 << 2) - 1)) == 0) alignBits = 2; + else if ((delta & ((1 << 1) - 1)) == 0) alignBits = 1; + // else alignBits = 0; + /* alignBits=0 is default mode for lzma/lzma2. + So we don't set alignBits=0 here. */ + } + if (alignBits <= 0) + return S_OK; + // (alignBits > 0) + CMethodFull &nextMethod = mode.Methods[nextCoder]; + if (nextMethod.Id == k_LZMA || nextMethod.Id == k_LZMA2) + if (!nextMethod.Are_Lzma_Model_Props_Defined()) + { + if (alignBits > 2 || filterMode.Id == k_Delta) + nextMethod.AddProp32(NCoderPropID::kPosStateBits, (unsigned)alignBits); + const unsigned lc = (alignBits < 3) ? (unsigned)(3 - alignBits) : 0u; + nextMethod.AddProp32(NCoderPropID::kLitContextBits, lc); + nextMethod.AddProp32(NCoderPropID::kLitPosBits, (unsigned)alignBits); + } + return S_OK; } -static void FromUpdateItemToFileItem(const CUpdateItem &ui, - CFileItem &file, CFileItem2 &file2) +static void UpdateItem_To_FileItem2(const CUpdateItem &ui, CFileItem2 &file2) { - if (ui.AttribDefined) - file.SetAttrib(ui.Attrib); - + file2.Attrib = ui.Attrib; file2.AttribDefined = ui.AttribDefined; file2.CTime = ui.CTime; file2.CTimeDefined = ui.CTimeDefined; file2.ATime = ui.ATime; file2.ATimeDefined = ui.ATimeDefined; file2.MTime = ui.MTime; file2.MTimeDefined = ui.MTimeDefined; file2.IsAnti = ui.IsAnti; // file2.IsAux = false; file2.StartPosDefined = false; + // file2.StartPos = 0; +} + + +static void UpdateItem_To_FileItem(const CUpdateItem &ui, + CFileItem &file, CFileItem2 &file2) +{ + UpdateItem_To_FileItem2(ui, file2); file.Size = ui.Size; file.IsDir = ui.IsDir; @@ -1107,13 +1445,15 @@ static void FromUpdateItemToFileItem(const CUpdateItem &ui, // file.IsAltStream = ui.IsAltStream; } -class CRepackInStreamWithSizes: - public ISequentialInStream, - public ICompressGetSubStreamSize, - public CMyUnknownImp -{ + + +Z7_CLASS_IMP_COM_2( + CRepackInStreamWithSizes + , ISequentialInStream + , ICompressGetSubStreamSize +) CMyComPtr _stream; - // UInt64 _size; + UInt64 _size; const CBoolVector *_extractStatuses; UInt32 _startIndex; public: @@ -1123,37 +1463,28 @@ class CRepackInStreamWithSizes: { _startIndex = startIndex; _extractStatuses = extractStatuses; - // _size = 0; + _size = 0; _stream = stream; } - // UInt64 GetSize() const { return _size; } - - MY_UNKNOWN_IMP2(ISequentialInStream, ICompressGetSubStreamSize) - - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); - - STDMETHOD(GetSubStreamSize)(UInt64 subStream, UInt64 *value); + UInt64 GetSize() const { return _size; } }; -STDMETHODIMP CRepackInStreamWithSizes::Read(void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CRepackInStreamWithSizes::Read(void *data, UInt32 size, UInt32 *processedSize)) { - return _stream->Read(data, size, processedSize); - /* UInt32 realProcessedSize; - HRESULT result = _stream->Read(data, size, &realProcessedSize); + const HRESULT result = _stream->Read(data, size, &realProcessedSize); _size += realProcessedSize; if (processedSize) *processedSize = realProcessedSize; return result; - */ } -STDMETHODIMP CRepackInStreamWithSizes::GetSubStreamSize(UInt64 subStream, UInt64 *value) +Z7_COM7F_IMF(CRepackInStreamWithSizes::GetSubStreamSize(UInt64 subStream, UInt64 *value)) { *value = 0; if (subStream >= _extractStatuses->Size()) return S_FALSE; // E_FAIL; - unsigned index = (unsigned)subStream; + const unsigned index = (unsigned)subStream; if ((*_extractStatuses)[index]) { const CFileItem &fi = _db->Files[_startIndex + index]; @@ -1184,7 +1515,7 @@ class CRepackStreamBase public: const CDbEx *_db; CMyComPtr _opCallback; - CMyComPtr _extractCallback; + CMyComPtr _extractCallback; HRESULT Init(UInt32 startIndex, const CBoolVector *extractStatuses); HRESULT CheckFinishedState() const { return (_currentIndex == _extractStatuses->Size()) ? S_OK: E_FAIL; } @@ -1213,7 +1544,7 @@ HRESULT CRepackStreamBase::OpenFile() NEventIndexType::kInArcIndex, arcIndex, _needWrite ? NUpdateNotifyOp::kRepack : - NUpdateNotifyOp::kSkip)); + NUpdateNotifyOp::kSkip)) } _crc = CRC_INIT_VAL; @@ -1239,7 +1570,7 @@ HRESULT CRepackStreamBase::CloseFile() { RINOK(_extractCallback->ReportExtractResult( NEventIndexType::kInArcIndex, arcIndex, - NExtract::NOperationResult::kCRCError)); + NExtract::NOperationResult::kCRCError)) } // return S_FALSE; return k_My_HRESULT_CRC_ERROR; @@ -1249,30 +1580,28 @@ HRESULT CRepackStreamBase::ProcessEmptyFiles() { while (_currentIndex < _extractStatuses->Size() && _db->Files[_startIndex + _currentIndex].Size == 0) { - RINOK(OpenFile()); - RINOK(CloseFile()); + RINOK(OpenFile()) + RINOK(CloseFile()) } return S_OK; } -#ifndef _7ZIP_ST +#ifndef Z7_ST -class CFolderOutStream2: +class CFolderOutStream2 Z7_final: public CRepackStreamBase, public ISequentialOutStream, public CMyUnknownImp { + Z7_COM_UNKNOWN_IMP_0 + Z7_IFACE_COM7_IMP(ISequentialOutStream) public: CMyComPtr _stream; - - MY_UNKNOWN_IMP - - STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize); }; -STDMETHODIMP CFolderOutStream2::Write(const void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CFolderOutStream2::Write(const void *data, UInt32 size, UInt32 *processedSize)) { if (processedSize) *processedSize = 0; @@ -1294,22 +1623,22 @@ STDMETHODIMP CFolderOutStream2::Write(const void *data, UInt32 size, UInt32 *pro _rem -= cur; if (_rem == 0) { - RINOK(CloseFile()); - RINOK(ProcessEmptyFiles()); + RINOK(CloseFile()) + RINOK(ProcessEmptyFiles()) } - RINOK(result); + RINOK(result) if (cur == 0) break; continue; } - RINOK(ProcessEmptyFiles()); + RINOK(ProcessEmptyFiles()) if (_currentIndex == _extractStatuses->Size()) { // we don't support write cut here return E_FAIL; } - RINOK(OpenFile()); + RINOK(OpenFile()) } return S_OK; @@ -1321,18 +1650,19 @@ STDMETHODIMP CFolderOutStream2::Write(const void *data, UInt32 size, UInt32 *pro static const UInt32 kTempBufSize = 1 << 16; -class CFolderInStream2: +class CFolderInStream2 Z7_final: public CRepackStreamBase, public ISequentialInStream, public CMyUnknownImp { + Z7_COM_UNKNOWN_IMP_0 + Z7_IFACE_COM7_IMP(ISequentialInStream) + Byte *_buf; public: CMyComPtr _inStream; HRESULT Result; - MY_UNKNOWN_IMP - CFolderInStream2(): Result(S_OK) { @@ -1345,10 +1675,9 @@ class CFolderInStream2: } void Init() { Result = S_OK; } - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); }; -STDMETHODIMP CFolderInStream2::Read(void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CFolderInStream2::Read(void *data, UInt32 size, UInt32 *processedSize)) { if (processedSize) *processedSize = 0; @@ -1369,7 +1698,7 @@ STDMETHODIMP CFolderInStream2::Read(void *data, UInt32 size, UInt32 *processedSi cur = kTempBufSize; } - HRESULT result = _inStream->Read(buf, cur, &cur); + const HRESULT result = _inStream->Read(buf, cur, &cur); _crc = CrcUpdate(_crc, buf, cur); _rem -= cur; @@ -1386,11 +1715,11 @@ STDMETHODIMP CFolderInStream2::Read(void *data, UInt32 size, UInt32 *processedSi if (_rem == 0) { - RINOK(CloseFile()); - RINOK(ProcessEmptyFiles()); + RINOK(CloseFile()) + RINOK(ProcessEmptyFiles()) } - RINOK(result); + RINOK(result) if (cur == 0) return E_FAIL; @@ -1398,20 +1727,20 @@ STDMETHODIMP CFolderInStream2::Read(void *data, UInt32 size, UInt32 *processedSi continue; } - RINOK(ProcessEmptyFiles()); + RINOK(ProcessEmptyFiles()) if (_currentIndex == _extractStatuses->Size()) { return S_OK; } - RINOK(OpenFile()); + RINOK(OpenFile()) } return S_OK; } -class CThreadDecoder - #ifndef _7ZIP_ST +class CThreadDecoder Z7_final + #ifndef Z7_ST : public CVirtThread #endif { @@ -1421,7 +1750,7 @@ class CThreadDecoder CThreadDecoder(bool multiThreadMixer): Decoder(multiThreadMixer) { - #ifndef _7ZIP_ST + #ifndef Z7_ST if (multiThreadMixer) { MtMode = false; @@ -1435,8 +1764,9 @@ class CThreadDecoder // send_UnpackSize = false; } - #ifndef _7ZIP_ST + #ifndef Z7_ST + bool dataAfterEnd_Error; HRESULT Result; CMyComPtr InStream; @@ -1445,41 +1775,51 @@ class CThreadDecoder UInt64 StartPos; const CFolders *Folders; - int FolderIndex; + unsigned FolderIndex; // bool send_UnpackSize; // UInt64 UnpackSize; - #ifndef _NO_CRYPTO + #ifndef Z7_NO_CRYPTO CMyComPtr getTextPassword; #endif - DECL_EXTERNAL_CODECS_LOC_VARS2; + DECL_EXTERNAL_CODECS_LOC_VARS_DECL - #ifndef _7ZIP_ST + #ifndef Z7_ST bool MtMode; UInt32 NumThreads; #endif - ~CThreadDecoder() { CVirtThread::WaitThreadFinish(); } - virtual void Execute(); + ~CThreadDecoder() Z7_DESTRUCTOR_override + { + /* WaitThreadFinish() will be called in ~CVirtThread(). + But we need WaitThreadFinish() call before + destructors of this class members. + */ + CVirtThread::WaitThreadFinish(); + } +private: + virtual void Execute() Z7_override; #endif }; -#ifndef _7ZIP_ST +#ifndef Z7_ST void CThreadDecoder::Execute() { try { - #ifndef _NO_CRYPTO + #ifndef Z7_NO_CRYPTO bool isEncrypted = false; bool passwordIsDefined = false; UString password; #endif - + + dataAfterEnd_Error = false; + Result = Decoder.Decode( EXTERNAL_CODECS_LOC_VARS InStream, @@ -1491,12 +1831,16 @@ void CThreadDecoder::Execute() Fos, NULL, // compressProgress + NULL // *inStreamMainRes + , dataAfterEnd_Error - _7Z_DECODER_CRYPRO_VARS - #ifndef _7ZIP_ST - , MtMode, NumThreads + Z7_7Z_DECODER_CRYPRO_VARS + #ifndef Z7_ST + , MtMode, NumThreads, + 0 // MemUsage #endif + ); } catch(...) @@ -1513,20 +1857,17 @@ void CThreadDecoder::Execute() #endif -#ifndef _NO_CRYPTO +#ifndef Z7_NO_CRYPTO -class CCryptoGetTextPassword: - public ICryptoGetTextPassword, - public CMyUnknownImp -{ +Z7_CLASS_IMP_NOQIB_1( + CCryptoGetTextPassword + , ICryptoGetTextPassword +) public: UString Password; - - MY_UNKNOWN_IMP - STDMETHOD(CryptoGetTextPassword)(BSTR *password); }; -STDMETHODIMP CCryptoGetTextPassword::CryptoGetTextPassword(BSTR *password) +Z7_COM7F_IMF(CCryptoGetTextPassword::CryptoGetTextPassword(BSTR *password)) { return StringToBstr(Password, password); } @@ -1541,6 +1882,7 @@ static void GetFile(const CDatabase &inDb, unsigned index, CFileItem &file, CFil file2.ATimeDefined = inDb.ATime.GetItem(index, file2.ATime); file2.MTimeDefined = inDb.MTime.GetItem(index, file2.MTime); file2.StartPosDefined = inDb.StartPos.GetItem(index, file2.StartPos); + file2.AttribDefined = inDb.Attrib.GetItem(index, file2.Attrib); file2.IsAnti = inDb.IsItemAnti(index); // file2.IsAux = inDb.IsItemAux(index); } @@ -1549,52 +1891,73 @@ HRESULT Update( DECL_EXTERNAL_CODECS_LOC_VARS IInStream *inStream, const CDbEx *db, - const CObjectVector &updateItems, + CObjectVector &updateItems, // const CObjectVector &treeFolders, // const CUniqBlocks &secureBlocks, - COutArchive &archive, - CArchiveDatabaseOut &newDatabase, ISequentialOutStream *seqOutStream, IArchiveUpdateCallback *updateCallback, - const CUpdateOptions &options - #ifndef _NO_CRYPTO - , ICryptoGetTextPassword *getDecoderPassword - #endif - ) + const CUpdateOptions &options) { UInt64 numSolidFiles = options.NumSolidFiles; if (numSolidFiles == 0) numSolidFiles = 1; - CMyComPtr opCallback; - updateCallback->QueryInterface(IID_IArchiveUpdateCallbackFile, (void **)&opCallback); - - CMyComPtr extractCallback; - updateCallback->QueryInterface(IID_IArchiveExtractCallbackMessage, (void **)&extractCallback); + Z7_DECL_CMyComPtr_QI_FROM( + IArchiveUpdateCallbackFile, + opCallback, updateCallback) - // size_t totalSecureDataSize = (size_t)secureBlocks.GetTotalSizeInBytes(); + Z7_DECL_CMyComPtr_QI_FROM( + IArchiveExtractCallbackMessage2, + extractCallback, updateCallback) /* - CMyComPtr outStream; - RINOK(seqOutStream->QueryInterface(IID_IOutStream, (void **)&outStream)); - if (!outStream) - return E_NOTIMPL; + Z7_DECL_CMyComPtr_QI_FROM( + IArchiveUpdateCallbackArcProp, + reportArcProp, updateCallback) */ - UInt64 startBlockSize = db ? db->ArcInfo.StartPosition: 0; - if (startBlockSize > 0 && !options.RemoveSfxBlock) + // size_t totalSecureDataSize = (size_t)secureBlocks.GetTotalSizeInBytes(); + + CMyComPtr v_StreamSetRestriction; { - RINOK(WriteRange(inStream, seqOutStream, 0, startBlockSize, NULL)); + Z7_DECL_CMyComPtr_QI_FROM( + IOutStream, + outStream, seqOutStream) + if (!outStream) + return E_NOTIMPL; + const UInt64 sfxBlockSize = (db && !options.RemoveSfxBlock) ? + db->ArcInfo.StartPosition: 0; + seqOutStream->QueryInterface(IID_IStreamSetRestriction, (void **)&v_StreamSetRestriction); + if (v_StreamSetRestriction) + { + UInt64 offset = 0; + RINOK(outStream->Seek(0, STREAM_SEEK_CUR, &offset)) + RINOK(v_StreamSetRestriction->SetRestriction( + outStream ? offset + sfxBlockSize : 0, + outStream ? offset + sfxBlockSize + k_StartHeadersRewriteSize : 0)) + } + outStream.Release(); + if (sfxBlockSize != 0) + { + RINOK(WriteRange(inStream, seqOutStream, 0, sfxBlockSize, NULL)) + } } CIntArr fileIndexToUpdateIndexMap; UInt64 complexity = 0; + bool isThere_UnknownSize = false; UInt64 inSizeForReduce2 = 0; + + #ifndef Z7_NO_CRYPTO bool needEncryptedRepack = false; + #endif CRecordVector filters; CObjectVector groups; + + #ifndef Z7_ST bool thereAreRepacks = false; + #endif bool useFilters = options.UseFilters; if (useFilters) @@ -1602,11 +1965,15 @@ HRESULT Update( const CCompressionMethodMode &method = *options.Method; FOR_VECTOR (i, method.Methods) + { + /* IsFilterMethod() knows only built-in codecs + FIXME: we should check IsFilter status for external filters too */ if (IsFilterMethod(method.Methods[i].Id)) { useFilters = false; break; } + } } if (db) @@ -1621,24 +1988,27 @@ HRESULT Update( { int index = updateItems[i].IndexInArchive; if (index != -1) - fileIndexToUpdateIndexMap[(unsigned)index] = i; + fileIndexToUpdateIndexMap[(unsigned)index] = (int)i; } for (i = 0; i < db->NumFolders; i++) { CNum indexInFolder = 0; CNum numCopyItems = 0; - CNum numUnpackStreams = db->NumUnpackStreamsVector[i]; + const CNum numUnpackStreams = db->NumUnpackStreamsVector[i]; UInt64 repackSize = 0; for (CNum fi = db->FolderStartFileIndex[i]; indexInFolder < numUnpackStreams; fi++) { + if (fi >= db->Files.Size()) + return E_FAIL; + const CFileItem &file = db->Files[fi]; if (file.HasStream) { indexInFolder++; - int updateIndex = fileIndexToUpdateIndexMap[fi]; - if (updateIndex >= 0 && !updateItems[updateIndex].NewData) + const int updateIndex = fileIndexToUpdateIndexMap[fi]; + if (updateIndex >= 0 && !updateItems[(unsigned)updateIndex].NewData) { numCopyItems++; repackSize += file.Size; @@ -1655,11 +2025,13 @@ HRESULT Update( CFolderEx f; db->ParseFolderEx(i, f); + #ifndef Z7_NO_CRYPTO const bool isEncrypted = f.IsEncrypted(); + #endif const bool needCopy = (numCopyItems == numUnpackStreams); const bool extractFilter = (useFilters || needCopy); - unsigned groupIndex = Get_FilterGroup_for_Folder(filters, f, extractFilter); + const unsigned groupIndex = Get_FilterGroup_for_Folder(filters, f, extractFilter); while (groupIndex >= groups.Size()) groups.AddNew(); @@ -1670,81 +2042,100 @@ HRESULT Update( complexity += db->GetFolderFullPackSize(i); else { + #ifndef Z7_ST thereAreRepacks = true; + #endif complexity += repackSize; if (inSizeForReduce2 < repackSize) inSizeForReduce2 = repackSize; + #ifndef Z7_NO_CRYPTO if (isEncrypted) needEncryptedRepack = true; + #endif } } } UInt64 inSizeForReduce = 0; { + const bool isSolid = (numSolidFiles > 1 && options.NumSolidBytes != 0); FOR_VECTOR (i, updateItems) { const CUpdateItem &ui = updateItems[i]; if (ui.NewData) { - complexity += ui.Size; - if (numSolidFiles != 1) - inSizeForReduce += ui.Size; - else if (inSizeForReduce < ui.Size) - inSizeForReduce = ui.Size; + if (ui.Size == (UInt64)(Int64)-1) + isThere_UnknownSize = true; + else + { + complexity += ui.Size; + if (isSolid) + inSizeForReduce += ui.Size; + else if (inSizeForReduce < ui.Size) + inSizeForReduce = ui.Size; + } } } } + if (isThere_UnknownSize) + inSizeForReduce = (UInt64)(Int64)-1; + else + RINOK(updateCallback->SetTotal(complexity)) + if (inSizeForReduce < inSizeForReduce2) - inSizeForReduce = inSizeForReduce2; + inSizeForReduce = inSizeForReduce2; - RINOK(updateCallback->SetTotal(complexity)); - CLocalProgress *lps = new CLocalProgress; - CMyComPtr progress = lps; + CMyComPtr2_Create lps; lps->Init(updateCallback, true); - #ifndef _7ZIP_ST + #ifndef Z7_ST CStreamBinder sb; + /* if (options.MultiThreadMixer) { RINOK(sb.CreateEvents()); } + */ #endif CThreadDecoder threadDecoder(options.MultiThreadMixer); - #ifndef _7ZIP_ST + #ifndef Z7_ST if (options.MultiThreadMixer && thereAreRepacks) { - #ifdef EXTERNAL_CODECS - threadDecoder.__externalCodecs = __externalCodecs; + #ifdef Z7_EXTERNAL_CODECS + threadDecoder._externalCodecs = _externalCodecs; #endif - RINOK(threadDecoder.Create()); + const WRes wres = threadDecoder.Create(); + if (wres != 0) + return HRESULT_FROM_WIN32(wres); } #endif { CAnalysis analysis; - if (options.AnalysisLevel == 0) - { - analysis.ParseWav = false; - analysis.ParseExe = false; - analysis.ParseAll = false; - } - else + // analysis.Need_ATime = options.Need_ATime; + int analysisLevel = options.AnalysisLevel; + // (analysisLevel < 0) means default level (5) + if (analysisLevel < 0) + analysisLevel = 5; + if (analysisLevel != 0) { analysis.Callback = opCallback; - if (options.AnalysisLevel > 0) + analysis.ParseWav = true; + if (analysisLevel >= 5) { - analysis.ParseWav = true; - if (options.AnalysisLevel >= 7) + analysis.ParseExe = true; + analysis.ParseExeUnix = true; + // analysis.ParseNoExt = true; + if (analysisLevel >= 7) { - analysis.ParseExe = true; - if (options.AnalysisLevel >= 9) + analysis.ParseNoExt = true; + if (analysisLevel >= 9) analysis.ParseAll = true; } } @@ -1761,13 +2152,22 @@ HRESULT Update( continue; CFilterMode2 fm; + fm.Construct(); if (useFilters) { - RINOK(analysis.GetFilterGroup(i, ui, fm)); + // analysis.ATime_Defined = false; + RINOK(analysis.GetFilterGroup(i, ui, fm)) + /* + if (analysis.ATime_Defined) + { + ui.ATime = FILETIME_To_UInt64(analysis.ATime); + ui.ATime_WasReadByAnalysis = true; + } + */ } fm.Encrypted = method.PasswordIsDefined; - unsigned groupIndex = GetGroup(filters, fm); + const unsigned groupIndex = GetGroup(filters, fm); while (groupIndex >= groups.Size()) groups.AddNew(); groups[groupIndex].Indices.Add(i); @@ -1775,7 +2175,7 @@ HRESULT Update( } - #ifndef _NO_CRYPTO + #ifndef Z7_NO_CRYPTO CCryptoGetTextPassword *getPasswordSpec = NULL; CMyComPtr getTextPassword; @@ -1784,7 +2184,7 @@ HRESULT Update( getPasswordSpec = new CCryptoGetTextPassword; getTextPassword = getPasswordSpec; - #ifndef _7ZIP_ST + #ifndef Z7_ST threadDecoder.getTextPassword = getPasswordSpec; #endif @@ -1792,10 +2192,13 @@ HRESULT Update( getPasswordSpec->Password = options.Method->Password; else { + Z7_DECL_CMyComPtr_QI_FROM( + ICryptoGetTextPassword, + getDecoderPassword, updateCallback) if (!getDecoderPassword) return E_NOTIMPL; CMyComBSTR password; - RINOK(getDecoderPassword->CryptoGetTextPassword(&password)); + RINOK(getDecoderPassword->CryptoGetTextPassword(&password)) if (password) getPasswordSpec->Password = password; } @@ -1803,11 +2206,12 @@ HRESULT Update( #endif - // ---------- Compress ---------- - RINOK(archive.Create(seqOutStream, false)); - RINOK(archive.SkipPrefixArchiveHeader()); + COutArchive archive; + CArchiveDatabaseOut newDatabase; + + RINOK(archive.Create_and_WriteStartPrefix(seqOutStream)) /* CIntVector treeFolderToArcIndex; @@ -1837,7 +2241,7 @@ HRESULT Update( continue; secureID = ui.SecureIndex; if (ui.NewProps) - FromUpdateItemToFileItem(ui, file, file2); + UpdateItem_To_FileItem(ui, file, file2); else GetFile(*db, ui.IndexInArchive, file, file2); } @@ -1868,7 +2272,7 @@ HRESULT Update( if (ui.HasStream()) continue; } - else if (ui.IndexInArchive != -1 && db->Files[ui.IndexInArchive].HasStream) + else if (ui.IndexInArchive != -1 && db->Files[(unsigned)ui.IndexInArchive].HasStream) continue; /* if (ui.TreeFolderIndex >= 0) @@ -1887,13 +2291,14 @@ HRESULT Update( UString name; if (ui.NewProps) { - FromUpdateItemToFileItem(ui, file, file2); + UpdateItem_To_FileItem(ui, file, file2); + file.CrcDefined = false; name = ui.Name; } else { - GetFile(*db, ui.IndexInArchive, file, file2); - db->GetPath(ui.IndexInArchive, name); + GetFile(*db, (unsigned)ui.IndexInArchive, file, file2); + db->GetPath((unsigned)ui.IndexInArchive, name); } /* @@ -1909,7 +2314,6 @@ HRESULT Update( { // ---------- Sort Filters ---------- - FOR_VECTOR (i, filters) { filters[i].GroupIndex = i; @@ -1923,22 +2327,23 @@ HRESULT Update( CCompressionMethodMode method = *options.Method; { - HRESULT res = MakeExeMethod(method, filterMode, - #ifdef _7ZIP_ST + const HRESULT res = MakeExeMethod(method, filterMode, + // bcj2_IsAllowed: + #ifdef Z7_ST false #else options.MaxFilter && options.MultiThreadMixer #endif - ); + , options.DisabledFilterIDs); - RINOK(res); + RINOK(res) } if (filterMode.Encrypted) { if (!method.PasswordIsDefined) { - #ifndef _NO_CRYPTO + #ifndef Z7_NO_CRYPTO if (getPasswordSpec) method.Password = getPasswordSpec->Password; #endif @@ -1957,13 +2362,13 @@ HRESULT Update( const CSolidGroup &group = groups[filterMode.GroupIndex]; - FOR_VECTOR(folderRefIndex, group.folderRefs) + FOR_VECTOR (folderRefIndex, group.folderRefs) { const CFolderRepack &rep = group.folderRefs[folderRefIndex]; - unsigned folderIndex = rep.FolderIndex; + const unsigned folderIndex = rep.FolderIndex; - CNum numUnpackStreams = db->NumUnpackStreamsVector[folderIndex]; + const CNum numUnpackStreams = db->NumUnpackStreamsVector[folderIndex]; if (rep.NumCopyFiles == numUnpackStreams) { @@ -1971,7 +2376,7 @@ HRESULT Update( { RINOK(opCallback->ReportOperation( NEventIndexType::kBlockIndex, (UInt32)folderIndex, - NUpdateNotifyOp::kReplicate)); + NUpdateNotifyOp::kReplicate)) // ---------- Copy old solid block ---------- { @@ -1983,21 +2388,27 @@ HRESULT Update( indexInFolder++; RINOK(opCallback->ReportOperation( NEventIndexType::kInArcIndex, (UInt32)fi, - NUpdateNotifyOp::kReplicate)); + NUpdateNotifyOp::kReplicate)) } } } } - UInt64 packSize = db->GetFolderFullPackSize(folderIndex); + const UInt64 packSize = db->GetFolderFullPackSize(folderIndex); RINOK(WriteRange(inStream, archive.SeqStream, - db->GetFolderStreamPos(folderIndex, 0), packSize, progress)); + db->GetFolderStreamPos(folderIndex, 0), packSize, lps)) lps->ProgressOffset += packSize; - + + const unsigned folderIndex_New = newDatabase.Folders.Size(); CFolder &folder = newDatabase.Folders.AddNew(); + // v23.01: we copy FolderCrc, if FolderCrc was used + if (db->FolderCRCs.ValidAndDefined(folderIndex)) + newDatabase.FolderUnpackCRCs.SetItem(folderIndex_New, + true, db->FolderCRCs.Vals[folderIndex]); + db->ParseFolderInfo(folderIndex, folder); - CNum startIndex = db->FoStartPackStreamIndex[folderIndex]; - FOR_VECTOR(j, folder.PackStreams) + const CNum startIndex = db->FoStartPackStreamIndex[folderIndex]; + FOR_VECTOR (j, folder.PackStreams) { newDatabase.PackSizes.Add(db->GetStreamPackSize(startIndex + j)); // newDatabase.PackCRCsDefined.Add(db.PackCRCsDefined[startIndex + j]); @@ -2005,9 +2416,9 @@ HRESULT Update( } size_t indexStart = db->FoToCoderUnpackSizes[folderIndex]; - size_t indexEnd = db->FoToCoderUnpackSizes[folderIndex + 1]; + const size_t indexEnd = db->FoToCoderUnpackSizes[folderIndex + 1]; for (; indexStart < indexEnd; indexStart++) - newDatabase.CoderUnpackSizes.Add(db->CoderUnpackSizes[indexStart]); + newDatabase.CoderUnpackSizes.Add(db->CoderUnpackSizes.ConstData()[indexStart]); } else { @@ -2044,8 +2455,8 @@ HRESULT Update( if (file.HasStream) { indexInFolder++; - int updateIndex = fileIndexToUpdateIndexMap[fi]; - if (updateIndex >= 0 && !updateItems[updateIndex].NewData) + const int updateIndex = fileIndexToUpdateIndexMap[fi]; + if (updateIndex >= 0 && !updateItems[(unsigned)updateIndex].NewData) needExtract = true; // decodeSize += file.Size; } @@ -2066,7 +2477,6 @@ HRESULT Update( unsigned startPackIndex = newDatabase.PackSizes.Size(); UInt64 curUnpackSize; { - CMyComPtr sbInStream; CRepackStreamBase *repackBase; CFolderInStream2 *FosSpec2 = NULL; @@ -2074,13 +2484,13 @@ HRESULT Update( CRepackInStreamWithSizes *inStreamSizeCountSpec = new CRepackInStreamWithSizes; CMyComPtr inStreamSizeCount = inStreamSizeCountSpec; { - #ifndef _7ZIP_ST + #ifndef Z7_ST if (options.MultiThreadMixer) { repackBase = threadDecoder.FosSpec; CMyComPtr sbOutStream; - sb.CreateStreams(&sbInStream, &sbOutStream); - sb.ReInit(); + sb.CreateStreams2(sbInStream, sbOutStream); + RINOK(sb.Create_ReInit()) threadDecoder.FosSpec->_stream = sbOutStream; @@ -2100,14 +2510,16 @@ HRESULT Update( sbInStream = FosSpec2; repackBase = FosSpec2; - #ifndef _NO_CRYPTO + #ifndef Z7_NO_CRYPTO bool isEncrypted = false; bool passwordIsDefined = false; UString password; #endif CMyComPtr decodedStream; - HRESULT res = threadDecoder.Decoder.Decode( + bool dataAfterEnd_Error = false; + + const HRESULT res = threadDecoder.Decoder.Decode( EXTERNAL_CODECS_LOC_VARS inStream, db->ArcInfo.DataStartPosition, // db->GetFolderStreamPos(folderIndex, 0);, @@ -2117,16 +2529,19 @@ HRESULT Update( NULL, // *outStream NULL, // *compressProgress + &decodedStream + , dataAfterEnd_Error - _7Z_DECODER_CRYPRO_VARS - #ifndef _7ZIP_ST + Z7_7Z_DECODER_CRYPRO_VARS + #ifndef Z7_ST , false // mtMode , 1 // numThreads + , 0 // memUsage #endif ); - RINOK(res); + RINOK(res) if (!decodedStream) return E_FAIL; @@ -2138,32 +2553,44 @@ HRESULT Update( repackBase->_extractCallback = extractCallback; UInt32 startIndex = db->FolderStartFileIndex[folderIndex]; - RINOK(repackBase->Init(startIndex, &extractStatuses)); + RINOK(repackBase->Init(startIndex, &extractStatuses)) inStreamSizeCountSpec->_db = db; inStreamSizeCountSpec->Init(sbInStream, startIndex, &extractStatuses); - #ifndef _7ZIP_ST + #ifndef Z7_ST if (options.MultiThreadMixer) { - threadDecoder.Start(); + WRes wres = threadDecoder.Start(); + if (wres != 0) + return HRESULT_FROM_WIN32(wres); } #endif } + // curUnpackSize = sizeToEncode; - HRESULT encodeRes = encoder.Encode( + HRESULT encodeRes = encoder.Encode1( EXTERNAL_CODECS_LOC_VARS inStreamSizeCount, // NULL, &inSizeForReduce, - newDatabase.Folders.AddNew(), newDatabase.CoderUnpackSizes, curUnpackSize, - archive.SeqStream, newDatabase.PackSizes, progress); + sizeToEncode, // expectedDataSize + newDatabase.Folders.AddNew(), + // newDatabase.CoderUnpackSizes, curUnpackSize, + archive.SeqStream, newDatabase.PackSizes, lps); if (encodeRes == k_My_HRESULT_CRC_ERROR) return E_FAIL; - #ifndef _7ZIP_ST + curUnpackSize = inStreamSizeCountSpec->GetSize(); + + if (encodeRes == S_OK) + { + encoder.Encode_Post(curUnpackSize, newDatabase.CoderUnpackSizes); + } + + #ifndef Z7_ST if (options.MultiThreadMixer) { // 16.00: hang was fixed : for case if decoding was not finished. @@ -2171,22 +2598,29 @@ HRESULT Update( inStreamSizeCount.Release(); sbInStream.Release(); - threadDecoder.WaitExecuteFinish(); + { + const WRes wres = threadDecoder.WaitExecuteFinish(); + if (wres != 0) + return HRESULT_FROM_WIN32(wres); + } - HRESULT decodeRes = threadDecoder.Result; + const HRESULT decodeRes = threadDecoder.Result; // if (res == k_My_HRESULT_CRC_ERROR) - if (decodeRes == S_FALSE) + if (decodeRes == S_FALSE || threadDecoder.dataAfterEnd_Error) { if (extractCallback) { RINOK(extractCallback->ReportExtractResult( NEventIndexType::kInArcIndex, db->FolderStartFileIndex[folderIndex], // NEventIndexType::kBlockIndex, (UInt32)folderIndex, - NExtract::NOperationResult::kDataError)); + (decodeRes != S_OK ? + NExtract::NOperationResult::kDataError : + NExtract::NOperationResult::kDataAfterEnd))) } - return E_FAIL; + if (decodeRes != S_OK) + return E_FAIL; } - RINOK(decodeRes); + RINOK(decodeRes) if (encodeRes == S_OK) if (sb.ProcessedSize != sizeToEncode) encodeRes = E_FAIL; @@ -2200,15 +2634,15 @@ HRESULT Update( { RINOK(extractCallback->ReportExtractResult( NEventIndexType::kBlockIndex, (UInt32)folderIndex, - NExtract::NOperationResult::kDataError)); + NExtract::NOperationResult::kDataError)) } return E_FAIL; } - RINOK(FosSpec2->Result); + RINOK(FosSpec2->Result) } - RINOK(encodeRes); - RINOK(repackBase->CheckFinishedState()); + RINOK(encodeRes) + RINOK(repackBase->CheckFinishedState()) if (curUnpackSize != sizeToEncode) return E_FAIL; @@ -2224,31 +2658,30 @@ HRESULT Update( CNum indexInFolder = 0; for (CNum fi = db->FolderStartFileIndex[folderIndex]; indexInFolder < numUnpackStreams; fi++) { - CFileItem file; - CFileItem2 file2; - GetFile(*db, fi, file, file2); - UString name; - db->GetPath(fi, name); - if (file.HasStream) + if (db->Files[fi].HasStream) { indexInFolder++; - int updateIndex = fileIndexToUpdateIndexMap[fi]; + const int updateIndex = fileIndexToUpdateIndexMap[fi]; if (updateIndex >= 0) { - const CUpdateItem &ui = updateItems[updateIndex]; + const CUpdateItem &ui = updateItems[(unsigned)updateIndex]; if (ui.NewData) continue; + + UString name; + CFileItem file; + CFileItem2 file2; + GetFile(*db, fi, file, file2); + if (ui.NewProps) { - CFileItem uf; - FromUpdateItemToFileItem(ui, uf, file2); - uf.Size = file.Size; - uf.Crc = file.Crc; - uf.CrcDefined = file.CrcDefined; - uf.HasStream = file.HasStream; - file = uf; + UpdateItem_To_FileItem2(ui, file2); + file.IsDir = ui.IsDir; name = ui.Name; } + else + db->GetPath(fi, name); + /* file.Parent = ui.ParentFolderIndex; if (ui.TreeFolderIndex >= 0) @@ -2265,17 +2698,18 @@ HRESULT Update( // ---------- Compress files to new solid blocks ---------- - unsigned numFiles = group.Indices.Size(); + const unsigned numFiles = group.Indices.Size(); if (numFiles == 0) continue; CRecordVector refItems; refItems.ClearAndSetSize(numFiles); - bool sortByType = (options.UseTypeSorting && numSolidFiles > 1); + // bool sortByType = (options.UseTypeSorting && isSoid); // numSolidFiles > 1 + const bool sortByType = options.UseTypeSorting; unsigned i; for (i = 0; i < numFiles; i++) - refItems[i] = CRefItem(group.Indices[i], updateItems[group.Indices[i]], sortByType); + refItems[i].Construct(group.Indices[i], updateItems[group.Indices[i]], sortByType); CSortParam sortParam; // sortParam.TreeFolders = &treeFolders; @@ -2286,13 +2720,13 @@ HRESULT Update( for (i = 0; i < numFiles; i++) { - UInt32 index = refItems[i].Index; + const UInt32 index = refItems[i].Index; indices[i] = index; /* const CUpdateItem &ui = updateItems[index]; CFileItem file; if (ui.NewProps) - FromUpdateItemToFileItem(ui, file); + UpdateItem_To_FileItem(ui, file); else file = db.Files[ui.IndexInArchive]; if (file.IsAnti || file.IsDir) @@ -2316,9 +2750,9 @@ HRESULT Update( break; if (options.SolidExtension) { - int slashPos = ui.Name.ReverseFind_PathSepar(); - int dotPos = ui.Name.ReverseFind_Dot(); - const wchar_t *ext = ui.Name.Ptr(dotPos <= slashPos ? ui.Name.Len() : dotPos + 1); + const int slashPos = ui.Name.ReverseFind_PathSepar(); + const int dotPos = ui.Name.ReverseFind_Dot(); + const wchar_t *ext = ui.Name.Ptr(dotPos <= slashPos ? ui.Name.Len() : (unsigned)(dotPos + 1)); if (numSubFiles == 0) prevExtension = ext; else if (!StringsAreEqualNoCase(ext, prevExtension)) @@ -2329,36 +2763,76 @@ HRESULT Update( if (numSubFiles < 1) numSubFiles = 1; - RINOK(lps->SetCur()); + RINOK(lps->SetCur()) + + /* + const unsigned folderIndex = newDatabase.NumUnpackStreamsVector.Size(); + + if (opCallback) + { + RINOK(opCallback->ReportOperation( + NEventIndexType::kBlockIndex, (UInt32)folderIndex, + NUpdateNotifyOp::kAdd)); + } + */ + + + CMyComPtr2_Create inStreamSpec; // solidInStream; + + // inStreamSpec->_reportArcProp = reportArcProp; + + inStreamSpec->Need_CTime = options.Need_CTime; + inStreamSpec->Need_ATime = options.Need_ATime; + inStreamSpec->Need_MTime = options.Need_MTime; + inStreamSpec->Need_Attrib = options.Need_Attrib; + // inStreamSpec->Need_Crc = options.Need_Crc; - CFolderInStream *inStreamSpec = new CFolderInStream; - CMyComPtr solidInStream(inStreamSpec); inStreamSpec->Init(updateCallback, &indices[i], numSubFiles); unsigned startPackIndex = newDatabase.PackSizes.Size(); - UInt64 curFolderUnpackSize; - RINOK(encoder.Encode( + // UInt64 curFolderUnpackSize = totalSize; + // curFolderUnpackSize = (UInt64)(Int64)-1; // for debug + const UInt64 expectedDataSize = totalSize; + + // const unsigned folderIndex_New = newDatabase.Folders.Size(); + + RINOK(encoder.Encode1( EXTERNAL_CODECS_LOC_VARS - solidInStream, + inStreamSpec, // NULL, &inSizeForReduce, - newDatabase.Folders.AddNew(), newDatabase.CoderUnpackSizes, curFolderUnpackSize, - archive.SeqStream, newDatabase.PackSizes, progress)); + expectedDataSize, // expected size + newDatabase.Folders.AddNew(), + // newDatabase.CoderUnpackSizes, curFolderUnpackSize, + archive.SeqStream, newDatabase.PackSizes, lps)) if (!inStreamSpec->WasFinished()) return E_FAIL; + /* + if (inStreamSpec->Need_FolderCrc) + newDatabase.FolderUnpackCRCs.SetItem(folderIndex_New, + true, inStreamSpec->GetFolderCrc()); + */ + + const UInt64 curFolderUnpackSize = inStreamSpec->Get_TotalSize_for_Coder(); + encoder.Encode_Post(curFolderUnpackSize, newDatabase.CoderUnpackSizes); + + UInt64 packSize = 0; + // const UInt32 numStreams = newDatabase.PackSizes.Size() - startPackIndex; for (; startPackIndex < newDatabase.PackSizes.Size(); startPackIndex++) - lps->OutSize += newDatabase.PackSizes[startPackIndex]; + packSize += newDatabase.PackSizes[startPackIndex]; + lps->OutSize += packSize; - lps->InSize += curFolderUnpackSize; // for () // newDatabase.PackCRCsDefined.Add(false); // newDatabase.PackCRCs.Add(0); CNum numUnpackStreams = 0; UInt64 skippedSize = 0; - + UInt64 procSize = 0; + // unsigned numProcessedFiles = 0; + for (unsigned subIndex = 0; subIndex < numSubFiles; subIndex++) { const CUpdateItem &ui = updateItems[indices[i + subIndex]]; @@ -2367,13 +2841,13 @@ HRESULT Update( UString name; if (ui.NewProps) { - FromUpdateItemToFileItem(ui, file, file2); + UpdateItem_To_FileItem(ui, file, file2); name = ui.Name; } else { - GetFile(*db, ui.IndexInArchive, file, file2); - db->GetPath(ui.IndexInArchive, name); + GetFile(*db, (unsigned)ui.IndexInArchive, file, file2); + db->GetPath((unsigned)ui.IndexInArchive, name); } if (file2.IsAnti || file.IsDir) return E_FAIL; @@ -2384,18 +2858,21 @@ HRESULT Update( */ if (!inStreamSpec->Processed[subIndex]) { + // we don't add file here skippedSize += ui.Size; - continue; - // file.Name.AddAscii(".locked"); + continue; // comment it for debug + // name += ".locked"; // for debug } + // if (inStreamSpec->Need_Crc) file.Crc = inStreamSpec->CRCs[subIndex]; file.Size = inStreamSpec->Sizes[subIndex]; - // if (file.Size >= 0) // test purposes + procSize += file.Size; + // if (file.Size >= 0) // for debug: test purposes if (file.Size != 0) { - file.CrcDefined = true; + file.CrcDefined = true; // inStreamSpec->Need_Crc; file.HasStream = true; numUnpackStreams++; } @@ -2405,6 +2882,23 @@ HRESULT Update( file.HasStream = false; } + if (inStreamSpec->TimesDefined[subIndex]) + { + if (inStreamSpec->Need_CTime) + { file2.CTimeDefined = true; file2.CTime = inStreamSpec->CTimes[subIndex]; } + if (inStreamSpec->Need_ATime + // && !ui.ATime_WasReadByAnalysis + ) + { file2.ATimeDefined = true; file2.ATime = inStreamSpec->ATimes[subIndex]; } + if (inStreamSpec->Need_MTime) + { file2.MTimeDefined = true; file2.MTime = inStreamSpec->MTimes[subIndex]; } + if (inStreamSpec->Need_Attrib) + { + file2.AttribDefined = true; + file2.Attrib = inStreamSpec->Attribs[subIndex]; + } + } + /* file.Parent = ui.ParentFolderIndex; if (ui.TreeFolderIndex >= 0) @@ -2412,9 +2906,79 @@ HRESULT Update( if (totalSecureDataSize != 0) newDatabase.SecureIDs.Add(ui.SecureIndex); */ + /* + if (reportArcProp) + { + RINOK(ReportItemProps(reportArcProp, ui.IndexInClient, file.Size, + file.CrcDefined ? &file.Crc : NULL)) + } + */ + + // numProcessedFiles++; newDatabase.AddFile(file, file2, name); } + /* + // for debug: + // we can write crc to folders area, if folder contains only one file + if (numUnpackStreams == 1 && numSubFiles == 1) + { + const CFileItem &file = newDatabase.Files.Back(); + if (file.CrcDefined) + newDatabase.FolderUnpackCRCs.SetItem(folderIndex_New, true, file.Crc); + } + */ + + /* + // it's optional check to ensure that sizes are correct + if (inStreamSpec->TotalSize_for_Coder != curFolderUnpackSize) + return E_FAIL; + */ + // if (inStreamSpec->AlignLog == 0) + { + if (procSize != curFolderUnpackSize) + return E_FAIL; + } + // else + { + /* + { + const CFolder &old = newDatabase.Folders.Back(); + CFolder &folder = newDatabase.Folders.AddNew(); + { + const unsigned numBonds = old.Bonds.Size(); + folder.Bonds.SetSize(numBonds + 1); + for (unsigned k = 0; k < numBonds; k++) + folder.Bonds[k] = old.Bonds[k]; + CBond &bond = folder.Bonds[numBonds]; + bond.PackIndex = 0; + bond.UnpackIndex = 0; + } + { + const unsigned numCoders = old.Coders.Size(); + folder.Coders.SetSize(numCoders + 1); + for (unsigned k = 0; k < numCoders; k++) + folder.Coders[k] = old.Coders[k]; + CCoderInfo &cod = folder.Coders[numCoders]; + cod.Props.Alloc(1); + cod.Props[0] = (Byte)inStreamSpec->AlignLog; + cod.NumStreams = 1; + } + { + const unsigned numPackStreams = old.Coders.Size(); + folder.Coders.SetSize(numPackStreams); + for (unsigned k = 0; k < numPackStreams; k++) + folder.PackStreams[k] = old.PackStreams[k]; + } + } + newDatabase.Folders.Delete(newDatabase.Folders.Size() - 2); + */ + } + + + lps->InSize += procSize; + // lps->InSize += curFolderUnpackSize; + // numUnpackStreams = 0 is very bad case for locked files // v3.13 doesn't understand it. newDatabase.NumUnpackStreamsVector.Add(numUnpackStreams); @@ -2423,12 +2987,50 @@ HRESULT Update( if (skippedSize != 0 && complexity >= skippedSize) { complexity -= skippedSize; - RINOK(updateCallback->SetTotal(complexity)); + RINOK(updateCallback->SetTotal(complexity)) } + + /* + if (reportArcProp) + { + PROPVARIANT prop; + prop.vt = VT_EMPTY; + prop.wReserved1 = 0; + { + NWindows::NCOM::PropVarEm_Set_UInt32(&prop, numProcessedFiles); + RINOK(reportArcProp->ReportProp( + NEventIndexType::kBlockIndex, (UInt32)folderIndex, kpidNumSubFiles, &prop)); + } + { + NWindows::NCOM::PropVarEm_Set_UInt64(&prop, curFolderUnpackSize); + RINOK(reportArcProp->ReportProp( + NEventIndexType::kBlockIndex, (UInt32)folderIndex, kpidSize, &prop)); + } + { + NWindows::NCOM::PropVarEm_Set_UInt64(&prop, packSize); + RINOK(reportArcProp->ReportProp( + NEventIndexType::kBlockIndex, (UInt32)folderIndex, kpidPackSize, &prop)); + } + { + NWindows::NCOM::PropVarEm_Set_UInt32(&prop, numStreams); + RINOK(reportArcProp->ReportProp( + NEventIndexType::kBlockIndex, (UInt32)folderIndex, kpidNumStreams, &prop)); + } + RINOK(reportArcProp->ReportFinished(NEventIndexType::kBlockIndex, (UInt32)folderIndex, NUpdate::NOperationResult::kOK)); + } + */ + /* + if (opCallback) + { + RINOK(opCallback->ReportOperation( + NEventIndexType::kBlockIndex, (UInt32)folderIndex, + NUpdateNotifyOp::kOpFinished)); + } + */ } } - RINOK(lps->SetCur()); + RINOK(lps->SetCur()) /* fileIndexToUpdateIndexMap.ClearAndFree(); @@ -2458,10 +3060,26 @@ HRESULT Update( } } */ + + { + const unsigned numFolders = newDatabase.Folders.Size(); + if (newDatabase.NumUnpackStreamsVector.Size() != numFolders + || newDatabase.FolderUnpackCRCs.Defs.Size() > numFolders) + return E_FAIL; + newDatabase.FolderUnpackCRCs.if_NonEmpty_FillResidue_with_false(numFolders); + } + + updateItems.ClearAndFree(); newDatabase.ReserveDown(); if (opCallback) - RINOK(opCallback->ReportOperation(NEventIndexType::kNoIndex, (UInt32)(Int32)-1, NUpdateNotifyOp::kHeader)); + RINOK(opCallback->ReportOperation(NEventIndexType::kNoIndex, (UInt32)(Int32)-1, NUpdateNotifyOp::kHeader)) + + RINOK(archive.WriteDatabase(EXTERNAL_CODECS_LOC_VARS + newDatabase, options.HeaderMethod, options.HeaderOptions)) + + if (v_StreamSetRestriction) + RINOK(v_StreamSetRestriction->SetRestriction(0, 0)) return S_OK; } diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zUpdate.h b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zUpdate.h index a7abf779..83a27743 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zUpdate.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/7zUpdate.h @@ -1,7 +1,7 @@ // 7zUpdate.h -#ifndef __7Z_UPDATE_H -#define __7Z_UPDATE_H +#ifndef ZIP7_INC_7Z_UPDATE_H +#define ZIP7_INC_7Z_UPDATE_H #include "../IArchive.h" @@ -9,7 +9,6 @@ #include "7zCompressionMode.h" #include "7zIn.h" -#include "7zOut.h" namespace NArchive { namespace N7z { @@ -31,7 +30,7 @@ struct CTreeFolder struct CUpdateItem { int IndexInArchive; - int IndexInClient; + unsigned IndexInClient; UInt64 CTime; UInt64 ATime; @@ -62,6 +61,8 @@ struct CUpdateItem bool ATimeDefined; bool MTimeDefined; + // bool ATime_WasReadByAnalysis; + // int SecureIndex; // 0 means (no_security) bool HasStream() const { return !IsDir && !IsAnti && Size != 0; } @@ -76,6 +77,7 @@ struct CUpdateItem CTimeDefined(false), ATimeDefined(false), MTimeDefined(false) + // , ATime_WasReadByAnalysis(false) // SecureIndex(0) {} void SetDirStatusFromAttrib() { IsDir = ((Attrib & FILE_ATTRIBUTE_DIRECTORY) != 0); } @@ -92,8 +94,6 @@ struct CUpdateOptions bool MaxFilter; // use BCJ2 filter instead of BCJ int AnalysisLevel; - CHeaderOptions HeaderOptions; - UInt64 NumSolidFiles; UInt64 NumSolidBytes; bool SolidExtension; @@ -103,6 +103,33 @@ struct CUpdateOptions bool RemoveSfxBlock; bool MultiThreadMixer; + bool Need_CTime; + bool Need_ATime; + bool Need_MTime; + bool Need_Attrib; + // bool Need_Crc; + + CHeaderOptions HeaderOptions; + + CUIntVector DisabledFilterIDs; + + void Add_DisabledFilter_for_id(UInt32 id, + const CUIntVector &enabledFilters) + { + if (enabledFilters.FindInSorted(id) < 0) + DisabledFilterIDs.AddToUniqueSorted(id); + } + + void SetFilterSupporting_ver_enabled_disabled( + UInt32 compatVer, + const CUIntVector &enabledFilters, + const CUIntVector &disabledFilters) + { + DisabledFilterIDs = disabledFilters; + if (compatVer < 2300) Add_DisabledFilter_for_id(k_ARM64, enabledFilters); + if (compatVer < 2402) Add_DisabledFilter_for_id(k_RISCV, enabledFilters); + } + CUpdateOptions(): Method(NULL), HeaderMethod(NULL), @@ -114,26 +141,27 @@ struct CUpdateOptions SolidExtension(false), UseTypeSorting(true), RemoveSfxBlock(false), - MultiThreadMixer(true) - {} + MultiThreadMixer(true), + Need_CTime(false), + Need_ATime(false), + Need_MTime(false), + Need_Attrib(false) + // , Need_Crc(true) + { + DisabledFilterIDs.Add(k_RISCV); + } }; HRESULT Update( DECL_EXTERNAL_CODECS_LOC_VARS IInStream *inStream, const CDbEx *db, - const CObjectVector &updateItems, + CObjectVector &updateItems, // const CObjectVector &treeFolders, // treeFolders[0] is root // const CUniqBlocks &secureBlocks, - COutArchive &archive, - CArchiveDatabaseOut &newDatabase, ISequentialOutStream *seqOutStream, IArchiveUpdateCallback *updateCallback, - const CUpdateOptions &options - #ifndef _NO_CRYPTO - , ICryptoGetTextPassword *getDecoderPassword - #endif - ); + const CUpdateOptions &options); }} #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/StdAfx.h b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/StdAfx.h index 2854ff3e..035267cc 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/StdAfx.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/StdAfx.h @@ -1,8 +1,11 @@ // StdAfx.h -#ifndef __STDAFX_H -#define __STDAFX_H +#ifndef ZIP7_INC_STDAFX_H +#define ZIP7_INC_STDAFX_H +#if defined(_MSC_VER) && _MSC_VER >= 1800 +#pragma warning(disable : 4464) // relative include path contains '..' +#endif #include "../../../Common/Common.h" #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/makefile b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/makefile index a3b077da..ff60e4d4 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/7z/makefile +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/7z/makefile @@ -1,11 +1,11 @@ PROG = 7z.dll DEF_FILE = ../Archive.def CFLAGS = $(CFLAGS) \ - -DEXTERNAL_CODECS \ + -DZ7_EXTERNAL_CODECS \ AR_OBJS = \ $O\ArchiveExports.obj \ - $O\DllExports.obj \ + $O\DllExports2.obj \ 7Z_OBJS = \ $O\7zCompressionMode.obj \ @@ -34,7 +34,6 @@ COMMON_OBJS = \ $O\Wildcard.obj \ WIN_OBJS = \ - $O\DLL.obj \ $O\FileDir.obj \ $O\FileFind.obj \ $O\FileIO.obj \ @@ -61,6 +60,7 @@ WIN_OBJS = \ COMPRESS_OBJS = \ $O\CopyCoder.obj \ + $O\CodecExports.obj \ AR_COMMON_OBJS = \ $O\CoderMixer2.obj \ diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/ApfsHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/ApfsHandler.cpp new file mode 100644 index 00000000..28769918 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/ApfsHandler.cpp @@ -0,0 +1,4470 @@ +// ApfsHandler.cpp + +#include "StdAfx.h" + +// #define SHOW_DEBUG_INFO + +#ifdef SHOW_DEBUG_INFO +#include +#define PRF(x) x +#else +#define PRF(x) +#endif + +#include "../../../C/CpuArch.h" +#include "../../../C/Sha256.h" + +#include "../../Common/ComTry.h" +#include "../../Common/IntToString.h" +#include "../../Common/MyBuffer2.h" +#include "../../Common/MyLinux.h" +#include "../../Common/UTFConvert.h" + +#include "../../Windows/PropVariantConv.h" +#include "../../Windows/PropVariantUtils.h" +#include "../../Windows/TimeUtils.h" + +#include "../Common/LimitedStreams.h" +#include "../Common/ProgressUtils.h" +#include "../Common/RegisterArc.h" +#include "../Common/StreamObjects.h" +#include "../Common/StreamUtils.h" + +#include "../Compress/CopyCoder.h" + +#include "Common/ItemNameUtils.h" + +#include "HfsHandler.h" + +// if APFS_SHOW_ALT_STREAMS is defined, the handler will show attribute files. +#define APFS_SHOW_ALT_STREAMS + +#define VI_MINUS1 ((unsigned)(int)-1) +#define IsViDef(x) ((int)(x) != -1) +#define IsViNotDef(x) ((int)(x) == -1) + +#define Get16(p) GetUi16(p) +#define Get32(p) GetUi32(p) +#define Get64(p) GetUi64(p) + +#define G16(_offs_, dest) dest = Get16(p + (_offs_)); +#define G32(_offs_, dest) dest = Get32(p + (_offs_)); +#define G64(_offs_, dest) dest = Get64(p + (_offs_)); + +namespace NArchive { +namespace NApfs { + +struct CUuid +{ + Byte Data[16]; + + void SetHex_To_str(char *s) const + { + ConvertDataToHex_Lower(s, Data, sizeof(Data)); + } + + void AddHexToString(UString &dest) const + { + char temp[sizeof(Data) * 2 + 4]; + SetHex_To_str(temp); + dest += temp; + } + + void SetFrom(const Byte *p) { memcpy(Data, p, 16); } +}; + + +typedef UInt64 oid_t; +typedef UInt64 xid_t; +// typedef Int64 paddr_t; +typedef UInt64 paddr_t_unsigned; + +#define G64o G64 +#define G64x G64 +// #define G64a G64 + +/* +struct prange_t +{ + paddr_t start_paddr; + UInt64 block_count; + + void Parse(const Byte *p) + { + G64a (0, start_paddr); + G64 (8, block_count); + } +}; +*/ + +#define OBJECT_TYPE_NX_SUPERBLOCK 0x1 +#define OBJECT_TYPE_BTREE 0x2 +#define OBJECT_TYPE_BTREE_NODE 0x3 +/* +#define OBJECT_TYPE_SPACEMAN 0x5 +#define OBJECT_TYPE_SPACEMAN_CAB 0x6 +#define OBJECT_TYPE_SPACEMAN_CIB 0x7 +#define OBJECT_TYPE_SPACEMAN_BITMAP 0x8 +#define OBJECT_TYPE_SPACEMAN_FREE_QUEUE 0x9 +#define OBJECT_TYPE_EXTENT_LIST_TREE 0xa +*/ +#define OBJECT_TYPE_OMAP 0xb +/* +#define OBJECT_TYPE_CHECKPOINT_MAP 0xc +*/ +#define OBJECT_TYPE_FS 0xd +#define OBJECT_TYPE_FSTREE 0xe +/* +#define OBJECT_TYPE_BLOCKREFTREE 0xf +#define OBJECT_TYPE_SNAPMETATREE 0x10 +#define OBJECT_TYPE_NX_REAPER 0x11 +#define OBJECT_TYPE_NX_REAP_LIST 0x12 +#define OBJECT_TYPE_OMAP_SNAPSHOT 0x13 +#define OBJECT_TYPE_EFI_JUMPSTART 0x14 +#define OBJECT_TYPE_FUSION_MIDDLE_TREE 0x15 +#define OBJECT_TYPE_NX_FUSION_WBC 0x16 +#define OBJECT_TYPE_NX_FUSION_WBC_LIST 0x17 +#define OBJECT_TYPE_ER_STATE 0x18 +#define OBJECT_TYPE_GBITMAP 0x19 +#define OBJECT_TYPE_GBITMAP_TREE 0x1a +#define OBJECT_TYPE_GBITMAP_BLOCK 0x1b +#define OBJECT_TYPE_ER_RECOVERY_BLOCK 0x1c +#define OBJECT_TYPE_SNAP_META_EXT 0x1d +*/ +#define OBJECT_TYPE_INTEGRITY_META 0x1e +#define OBJECT_TYPE_FEXT_TREE 0x1f +/* +#define OBJECT_TYPE_RESERVED_20 0x20 + +#define OBJECT_TYPE_INVALID 0x0 +#define OBJECT_TYPE_TEST 0xff +#define OBJECT_TYPE_CONTAINER_KEYBAG 'keys' +#define OBJECT_TYPE_VOLUME_KEYBAG 'recs' +#define OBJECT_TYPE_MEDIA_KEYBAG 'mkey' + +#define OBJ_VIRTUAL 0x0 +#define OBJ_EPHEMERAL 0x80000000 +*/ +#define OBJ_PHYSICAL 0x40000000 +/* +#define OBJ_NOHEADER 0x20000000 +#define OBJ_ENCRYPTED 0x10000000 +#define OBJ_NONPERSISTENT 0x08000000 +*/ +#define OBJECT_TYPE_MASK 0x0000ffff +/* +#define OBJECT_TYPE_FLAGS_MASK 0xffff0000 +#define OBJ_STORAGETYPE_MASK 0xc0000000 +#define OBJECT_TYPE_FLAGS_DEFINED_MASK 0xf8000000 +*/ + +// #define MAX_CKSUM_SIZE 8 + +static const unsigned k_obj_phys_Size = 0x20; + +// obj_phys_t +struct CPhys +{ + // Byte cksum[MAX_CKSUM_SIZE]; + oid_t oid; + xid_t xid; + UInt32 type; + UInt32 subtype; + + UInt32 GetType() const { return type & OBJECT_TYPE_MASK; } + void Parse(const Byte *p); +}; + +void CPhys::Parse(const Byte *p) +{ + // memcpy(cksum, p, MAX_CKSUM_SIZE); + G64o (8, oid) + G64x (0x10, xid) + G32 (0x18, type) + G32 (0x1C, subtype) +} + +#define NX_MAX_FILE_SYSTEMS 100 +/* +#define NX_EPH_INFO_COUNT 4 +#define NX_EPH_MIN_BLOCK_COUNT 8 +#define NX_MAX_FILE_SYSTEM_EPH_STRUCTS 4 +#define NX_TX_MIN_CHECKPOINT_COUNT 4 +#define NX_EPH_INFO_VERSION_1 1 +*/ + +/* +typedef enum +{ + NX_CNTR_OBJ_CKSUM_SET = 0, + NX_CNTR_OBJ_CKSUM_FAIL = 1, + NX_NUM_COUNTERS = 32 +} counter_id_t; +*/ + +/* Incompatible volume feature flags */ +#define APFS_INCOMPAT_CASE_INSENSITIVE (1 << 0) +/* +#define APFS_INCOMPAT_DATALESS_SNAPS (1 << 1) +#define APFS_INCOMPAT_ENC_ROLLED (1 << 2) +*/ +#define APFS_INCOMPAT_NORMALIZATION_INSENSITIVE (1 << 3) +/* +#define APFS_INCOMPAT_INCOMPLETE_RESTORE (1 << 4) +*/ +#define APFS_INCOMPAT_SEALED_VOLUME (1 << 5) +/* +#define APFS_INCOMPAT_RESERVED_40 (1 << 6) +*/ + +static const char * const g_APFS_INCOMPAT_Flags[] = +{ + "CASE_INSENSITIVE" + , "DATALESS_SNAPS" + , "ENC_ROLLED" + , "NORMALIZATION_INSENSITIVE" + , "INCOMPLETE_RESTORE" + , "SEALED_VOLUME" +}; + +/* +#define APFS_SUPPORTED_INCOMPAT_MASK \ + ( APFS_INCOMPAT_CASE_INSENSITIVE \ + | APFS_INCOMPAT_DATALESS_SNAPS \ + | APFS_INCOMPAT_ENC_ROLLED \ + | APFS_INCOMPAT_NORMALIZATION_INSENSITIVE \ + | APFS_INCOMPAT_INCOMPLETE_RESTORE \ + | APFS_INCOMPAT_SEALED_VOLUME \ + | APFS_INCOMPAT_RESERVED_40 \ +) +*/ + +// superblock_t +struct CSuperBlock +{ + // CPhys o; + // UInt32 magic; + UInt32 block_size; + unsigned block_size_Log; + UInt64 block_count; + // UInt64 features; + // UInt64 readonly_compatible_features; + // UInt64 incompatible_features; + CUuid uuid; + /* + oid_t next_oid; + xid_t next_xid; + UInt32 xp_desc_blocks; + UInt32 xp_data_blocks; + paddr_t xp_desc_base; + paddr_t xp_data_base; + UInt32 xp_desc_next; + UInt32 xp_data_next; + UInt32 xp_desc_index; + UInt32 xp_desc_len; + UInt32 xp_data_index; + UInt32 xp_data_len; + oid_t spaceman_oid; + */ + oid_t omap_oid; + // oid_t reaper_oid; + // UInt32 test_type; + UInt32 max_file_systems; + // oid_t fs_oid[NX_MAX_FILE_SYSTEMS]; + /* + UInt64 counters[NX_NUM_COUNTERS]; // counter_id_t + prange_t blocked_out_prange; + oid_t evict_mapping_tree_oid; + UInt64 flags; + paddr_t efi_jumpstart; + CUuid fusion_uuid; + prange_t keylocker; + UInt64 ephemeral_info[NX_EPH_INFO_COUNT]; + oid_t test_oid; + oid_t fusion_mt_oid; + oid_t fusion_wbc_oid; + prange_t fusion_wbc; + UInt64 newest_mounted_version; + prange_t mkb_locker; + */ + + bool Parse(const Byte *p); +}; + +struct CSuperBlock2 +{ + oid_t fs_oid[NX_MAX_FILE_SYSTEMS]; + void Parse(const Byte *p) + { + for (unsigned i = 0; i < NX_MAX_FILE_SYSTEMS; i++) + { + G64o (0xb8 + i * 8, fs_oid[i]) + } + } +}; + + +// we include one additional byte of next field (block_size) +static const unsigned k_SignatureOffset = 32; +static const Byte k_Signature[] = { 'N', 'X', 'S', 'B', 0 }; + +// size must be 4 bytes aligned +static UInt64 Fletcher64(const Byte *data, size_t size) +{ + const UInt32 kMax32 = 0xffffffff; + const UInt64 val = 0; // startVal + UInt64 a = val & kMax32; + UInt64 b = (val >> 32) & kMax32; + for (size_t i = 0; i < size; i += 4) + { + a += GetUi32(data + i); + b += a; + } + a %= kMax32; + b %= kMax32; + b = (UInt32)(kMax32 - ((a + b) % kMax32)); + a = (UInt32)(kMax32 - ((a + b) % kMax32)); + return (a << 32) | b; +} + +static bool CheckFletcher64(const Byte *p, size_t size) +{ + const UInt64 calculated_checksum = Fletcher64(p + 8, size - 8); + const UInt64 stored_checksum = Get64(p); + return (stored_checksum == calculated_checksum); +} + + +static unsigned GetLogSize(UInt32 size) +{ + unsigned k; + for (k = 0; k < 32; k++) + if (((UInt32)1 << k) == size) + return k; + return k; +} + +static const unsigned kApfsHeaderSize = 1 << 12; + +// #define OID_INVALID 0 +#define OID_NX_SUPERBLOCK 1 +// #define OID_RESERVED_COUNT 1024 +// This range of identifiers is reserved for physical, virtual, and ephemeral objects + +bool CSuperBlock::Parse(const Byte *p) +{ + CPhys o; + o.Parse(p); + if (o.oid != OID_NX_SUPERBLOCK) + return false; + if (o.GetType() != OBJECT_TYPE_NX_SUPERBLOCK) + return false; + if (o.subtype != 0) + return false; + if (memcmp(p + k_SignatureOffset, k_Signature, 4) != 0) + return false; + if (!CheckFletcher64(p, kApfsHeaderSize)) + return false; + + G32 (0x24, block_size) + { + const unsigned logSize = GetLogSize(block_size); + // don't change it. Some code requires (block_size <= 16) + if (logSize < 12 || logSize > 16) + return false; + block_size_Log = logSize; + } + + G64 (0x28, block_count) + + const UInt64 kArcSize_MAX = (UInt64)1 << 62; + if (block_count > (kArcSize_MAX >> block_size_Log)) + return false; + + // G64 (0x30, features); + // G64 (0x38, readonly_compatible_features); + // G64 (0x40, incompatible_features); + uuid.SetFrom(p + 0x48); + /* + G64o (0x58, next_oid); + G64x (0x60, next_xid); + G32 (0x68, xp_desc_blocks); + G32 (0x6c, xp_data_blocks); + G64a (0x70, xp_desc_base); + G64a (0x78, xp_data_base); + G32 (0x80, xp_desc_next); + G32 (0x84, xp_data_next); + G32 (0x88, xp_desc_index); + G32 (0x8c, xp_desc_len); + G32 (0x90, xp_data_index); + G32 (0x94, xp_data_len); + G64o (0x98, spaceman_oid); + */ + G64o (0xa0, omap_oid) + // G64o (0xa8, reaper_oid); + // G32 (0xb0, test_type); + G32 (0xb4, max_file_systems) + if (max_file_systems > NX_MAX_FILE_SYSTEMS) + return false; + /* + { + for (unsigned i = 0; i < NX_MAX_FILE_SYSTEMS; i++) + { + G64o (0xb8 + i * 8, fs_oid[i]); + } + } + */ + /* + { + for (unsigned i = 0; i < NX_NUM_COUNTERS; i++) + { + G64 (0x3d8 + i * 8, counters[i]); + } + } + blocked_out_prange.Parse(p + 0x4d8); + G64o (0x4e8, evict_mapping_tree_oid); + #define NX_CRYPTO_SW 0x00000004LL + G64 (0x4f0, flags); + G64a (0x4f8, efi_jumpstart); + fusion_uuid.SetFrom(p + 0x500); + keylocker.Parse(p + 0x510); + { + for (unsigned i = 0; i < NX_EPH_INFO_COUNT; i++) + { + G64 (0x520 + i * 8, ephemeral_info[i]); + } + } + G64o (0x540, test_oid); + G64o (0x548, fusion_mt_oid); + G64o (0x550, fusion_wbc_oid); + fusion_wbc.Parse(p + 0x558); + G64 (0x568, newest_mounted_version); // decimal 1412141 001 000 000 + mkb_locker.Parse(p + 0x570); + */ + + return true; +} + + +enum apfs_hash_type_t +{ + APFS_HASH_INVALID = 0, + APFS_HASH_SHA256 = 1, + APFS_HASH_SHA512_256 = 2, + APFS_HASH_SHA384 = 3, + APFS_HASH_SHA512 = 4, + + APFS_HASH_MIN = APFS_HASH_SHA256, + APFS_HASH_MAX = APFS_HASH_SHA512, + + APFS_HASH_DEFAULT = APFS_HASH_SHA256 +}; + +static unsigned GetHashSize(unsigned hashType) +{ + if (hashType > APFS_HASH_MAX) return 0; + if (hashType < APFS_HASH_MIN) return 0; + if (hashType == APFS_HASH_SHA256) return 32; + return hashType * 16; +} + +#define APFS_HASH_MAX_SIZE 64 + +static const char * const g_hash_types[] = +{ + NULL + , "SHA256" + , "SHA512_256" + , "SHA384" + , "SHA512" +}; + + +struct C_integrity_meta_phys +{ + // CPhys im_o; + // UInt32 im_version; + UInt32 im_flags; + // apfs_hash_type_t + UInt32 im_hash_type; + // UInt32 im_root_hash_offset; + // xid_t im_broken_xid; + // UInt64 im_reserved[9]; + + unsigned HashSize; + Byte Hash[APFS_HASH_MAX_SIZE]; + + bool Is_SHA256() const { return im_hash_type == APFS_HASH_SHA256; } + + C_integrity_meta_phys() + { + memset(this, 0, sizeof(*this)); + } + bool Parse(const Byte *p, size_t size, oid_t oid); +}; + +bool C_integrity_meta_phys::Parse(const Byte *p, size_t size, oid_t oid) +{ + CPhys o; + if (!CheckFletcher64(p, size)) + return false; + o.Parse(p); + if (o.GetType() != OBJECT_TYPE_INTEGRITY_META) + return false; + if (o.oid != oid) + return false; + // G32 (0x20, im_version); + G32 (0x24, im_flags) + G32 (0x28, im_hash_type) + UInt32 im_root_hash_offset; + G32 (0x2C, im_root_hash_offset) + // G64x (0x30, im_broken_xid); + const unsigned hashSize = GetHashSize(im_hash_type); + HashSize = hashSize; + if (im_root_hash_offset >= size || size - im_root_hash_offset < hashSize) + return false; + memcpy(Hash, p + im_root_hash_offset, hashSize); + return true; +} + + +struct C_omap_phys +{ + // om_ prefix + // CPhys o; + /* + UInt32 flags; + UInt32 snap_count; + UInt32 tree_type; + UInt32 snapshot_tree_type; + */ + oid_t tree_oid; + /* + oid_t snapshot_tree_oid; + xid_t most_recent_snap; + xid_t pending_revert_min; + xid_t pending_revert_max; + */ + bool Parse(const Byte *p, size_t size, oid_t oid); +}; + +bool C_omap_phys::Parse(const Byte *p, size_t size, oid_t oid) +{ + CPhys o; + if (!CheckFletcher64(p, size)) + return false; + o.Parse(p); + if (o.GetType() != OBJECT_TYPE_OMAP) + return false; + if (o.oid != oid) + return false; + /* + G32 (0x20, flags); + G32 (0x24, snap_count); + G32 (0x28, tree_type); + G32 (0x2C, snapshot_tree_type); + */ + G64o (0x30, tree_oid) + /* + G64o (0x38, snapshot_tree_oid); + G64x (0x40, most_recent_snap); + G64x (0x48, pending_revert_min); + G64x (0x50, pending_revert_max); + */ + return true; +} + + +// #define BTOFF_INVALID 0xffff +/* This value is stored in the off field of nloc_t to indicate that +there's no offset. For example, the last entry in a free +list has no entry after it, so it uses this value for its off field. */ + +// A location within a B-tree node +struct nloc +{ + UInt16 off; + UInt16 len; + + void Parse(const Byte *p) + { + G16 (0, off) + G16 (2, len) + } + UInt32 GetEnd() const { return (UInt32)off + len; } + bool CheckOverLimit(UInt32 limit) + { + return off < limit && len <= limit - off; + } +}; + + +// The location, within a B-tree node, of a key and value +struct kvloc +{ + nloc k; + nloc v; + + void Parse(const Byte *p) + { + k.Parse(p); + v.Parse(p + 4); + } +}; + + +// The location, within a B-tree node, of a fixed-size key and value +struct kvoff +{ + UInt16 k; + UInt16 v; + + void Parse(const Byte *p) + { + G16 (0, k) + G16 (2, v) + } +}; + + +#define BTNODE_ROOT (1 << 0) +#define BTNODE_LEAF (1 << 1) +#define BTNODE_FIXED_KV_SIZE (1 << 2) +#define BTNODE_HASHED (1 << 3) +#define BTNODE_NOHEADER (1 << 4) +/* +#define BTNODE_CHECK_KOFF_INVAL (1 << 15) +*/ + +static const unsigned k_Toc_offset = 0x38; + +// btree_node_phys +struct CBTreeNodePhys +{ + // btn_ prefix + CPhys ophys; + UInt16 flags; + UInt16 level; // the number of child levels below this node. 0 - for a leaf node, 1 for the immediate parent of a leaf node + UInt32 nkeys; // The number of keys stored in this node. + nloc table_space; + /* + nloc free_space; + nloc key_free_list; + nloc val_free_list; + */ + + bool Is_FIXED_KV_SIZE() const { return (flags & BTNODE_FIXED_KV_SIZE) != 0; } + bool Is_NOHEADER() const { return (flags & BTNODE_NOHEADER) != 0; } + bool Is_HASHED() const { return (flags & BTNODE_HASHED) != 0; } + + bool Parse(const Byte *p, size_t size, bool noHeader = false) + { + G16 (0x20, flags) + G16 (0x22, level) + G32 (0x24, nkeys) + table_space.Parse(p + 0x28); + /* + free_space.Parse(p + 0x2C); + key_free_list.Parse(p + 0x30); + val_free_list.Parse(p + 0x34); + */ + memset(&ophys, 0, sizeof(ophys)); + if (noHeader) + { + for (unsigned i = 0; i < k_obj_phys_Size; i++) + if (p[i] != 0) + return false; + } + else + { + if (!CheckFletcher64(p, size)) + return false; + ophys.Parse(p); + } + if (Is_NOHEADER() != noHeader) + return false; + return true; + } +}; + +/* +#define BTREE_UINT64_KEYS (1 << 0) +#define BTREE_SEQUENTIAL_INSERT (1 << 1) +#define BTREE_ALLOW_GHOSTS (1 << 2) +*/ +#define BTREE_EPHEMERAL (1 << 3) +#define BTREE_PHYSICAL (1 << 4) +/* +#define BTREE_NONPERSISTENT (1 << 5) +#define BTREE_KV_NONALIGNED (1 << 6) +#define BTREE_HASHED (1 << 7) +*/ +#define BTREE_NOHEADER (1 << 8) + +/* + BTREE_EPHEMERAL: The nodes in the B-tree use ephemeral object identifiers to link to child nodes + BTREE_PHYSICAL : The nodes in the B-tree use physical object identifiers to link to child nodes. + If neither flag is set, nodes in the B-tree use virtual object + identifiers to link to their child nodes. +*/ + +// Static information about a B-tree. +struct btree_info_fixed +{ + UInt32 flags; + UInt32 node_size; + UInt32 key_size; + UInt32 val_size; + + void Parse(const Byte *p) + { + G32 (0, flags) + G32 (4, node_size) + G32 (8, key_size) + G32 (12, val_size) + } +}; + +static const unsigned k_btree_info_Size = 0x28; + +struct btree_info +{ + btree_info_fixed fixed; + UInt32 longest_key; + UInt32 longest_val; + UInt64 key_count; + UInt64 node_count; + + bool Is_EPHEMERAL() const { return (fixed.flags & BTREE_EPHEMERAL) != 0; } + bool Is_PHYSICAL() const { return (fixed.flags & BTREE_PHYSICAL) != 0; } + bool Is_NOHEADER() const { return (fixed.flags & BTREE_NOHEADER) != 0; } + + void Parse(const Byte *p) + { + fixed.Parse(p); + G32 (0x10, longest_key) + G32 (0x14, longest_val) + G64 (0x18, key_count) + G64 (0x20, node_count) + } +}; + + +/* +typedef UInt32 cp_key_class_t; +typedef UInt32 cp_key_os_version_t; +typedef UInt16 cp_key_revision_t; +typedef UInt32 crypto_flags_t; + +struct wrapped_meta_crypto_state +{ + UInt16 major_version; + UInt16 minor_version; + crypto_flags_t cpflags; + cp_key_class_t persistent_class; + cp_key_os_version_t key_os_version; + cp_key_revision_t key_revision; + // UInt16 unused; + + void Parse(const Byte *p) + { + G16 (0, major_version); + G16 (2, minor_version); + G32 (4, cpflags); + G32 (8, persistent_class); + G32 (12, key_os_version); + G16 (16, key_revision); + } +}; +*/ + + +#define APFS_MODIFIED_NAMELEN 32 +#define sizeof_apfs_modified_by_t (APFS_MODIFIED_NAMELEN + 16) + +struct apfs_modified_by_t +{ + Byte id[APFS_MODIFIED_NAMELEN]; + UInt64 timestamp; + xid_t last_xid; + + void Parse(const Byte *p) + { + memcpy(id, p, APFS_MODIFIED_NAMELEN); + p += APFS_MODIFIED_NAMELEN; + G64 (0, timestamp) + G64x (8, last_xid) + } +}; + + +#define APFS_MAX_HIST 8 +#define APFS_VOLNAME_LEN 256 + +struct CApfs +{ + // apfs_ + CPhys o; + // UInt32 magic; + UInt32 fs_index; // index of the object identifier for this volume's file system in the container's array of file systems. + // UInt64 features; + // UInt64 readonly_compatible_features; + UInt64 incompatible_features; + UInt64 unmount_time; + // UInt64 fs_reserve_block_count; + // UInt64 fs_quota_block_count; + UInt64 fs_alloc_count; + // wrapped_meta_crypto_state meta_crypto; + // UInt32 root_tree_type; + /* The type of the root file-system tree. + The value is typically OBJ_VIRTUAL | OBJECT_TYPE_BTREE, + with a subtype of OBJECT_TYPE_FSTREE */ + + // UInt32 extentref_tree_type; + // UInt32 snap_meta_tree_type; + oid_t omap_oid; + oid_t root_tree_oid; + /* + oid_t extentref_tree_oid; + oid_t snap_meta_tree_oid; + xid_t revert_to_xid; + oid_t revert_to_sblock_oid; + UInt64 next_obj_id; + */ + UInt64 num_files; + UInt64 num_directories; + UInt64 num_symlinks; + UInt64 num_other_fsobjects; + UInt64 num_snapshots; + UInt64 total_blocks_alloced; + UInt64 total_blocks_freed; + CUuid vol_uuid; + UInt64 last_mod_time; + UInt64 fs_flags; + apfs_modified_by_t formatted_by; + apfs_modified_by_t modified_by[APFS_MAX_HIST]; + Byte volname[APFS_VOLNAME_LEN]; + /* + UInt32 next_doc_id; + UInt16 role; // APFS_VOL_ROLE_NONE APFS_VOL_ROLE_SYSTEM .... + UInt16 reserved; + xid_t root_to_xid; + oid_t er_state_oid; + UInt64 cloneinfo_id_epoch; + UInt64 cloneinfo_xid; + oid_t snap_meta_ext_oid; + CUuid volume_group_id; + */ + oid_t integrity_meta_oid; // virtual object identifier of the integrity metadata object + oid_t fext_tree_oid; // virtual object identifier of the file extent tree. + UInt32 fext_tree_type; // The type of the file extent tree. + /* + UInt32 reserved_type; + oid_t reserved_oid; + */ + + UInt64 GetTotalItems() const + { + return num_files + num_directories + num_symlinks + num_other_fsobjects; + } + + bool IsHashedName() const + { + return + (incompatible_features & APFS_INCOMPAT_CASE_INSENSITIVE) != 0 || + (incompatible_features & APFS_INCOMPAT_NORMALIZATION_INSENSITIVE) != 0; + } + + bool Parse(const Byte *p, size_t size); +}; + + +bool CApfs::Parse(const Byte *p, size_t size) +{ + o.Parse(p); + if (Get32(p + 32) != 0x42535041) // { 'A', 'P', 'S', 'B' }; + return false; + if (o.GetType() != OBJECT_TYPE_FS) + return false; + if (!CheckFletcher64(p, size)) + return false; + // if (o.GetType() != OBJECT_TYPE_NX_SUPERBLOCK) return false; + + G32 (0x24, fs_index) + // G64 (0x28, features); + // G64 (0x30, readonly_compatible_features); + G64 (0x38, incompatible_features) + G64 (0x40, unmount_time) + // G64 (0x48, fs_reserve_block_count); + // G64 (0x50, fs_quota_block_count); + G64 (0x58, fs_alloc_count) + // meta_crypto.Parse(p + 0x60); + // G32 (0x74, root_tree_type); + // G32 (0x78, extentref_tree_type); + // G32 (0x7C, snap_meta_tree_type); + + G64o (0x80, omap_oid) + G64o (0x88, root_tree_oid) + /* + G64o (0x90, extentref_tree_oid); + G64o (0x98, snap_meta_tree_oid); + G64x (0xa0, revert_to_xid); + G64o (0xa8, revert_to_sblock_oid); + G64 (0xb0, next_obj_id); + */ + G64 (0xb8, num_files) + G64 (0xc0, num_directories) + G64 (0xc8, num_symlinks) + G64 (0xd0, num_other_fsobjects) + G64 (0xd8, num_snapshots) + G64 (0xe0, total_blocks_alloced) + G64 (0xe8, total_blocks_freed) + vol_uuid.SetFrom(p + 0xf0); + G64 (0x100, last_mod_time) + G64 (0x108, fs_flags) + p += 0x110; + formatted_by.Parse(p); + p += sizeof_apfs_modified_by_t; + for (unsigned i = 0; i < APFS_MAX_HIST; i++) + { + modified_by[i].Parse(p); + p += sizeof_apfs_modified_by_t; + } + memcpy(volname, p, APFS_VOLNAME_LEN); + p += APFS_VOLNAME_LEN; + /* + G32 (0, next_doc_id); + G16 (4, role); + G16 (6, reserved); + G64x (8, root_to_xid); + G64o (0x10, er_state_oid); + G64 (0x18, cloneinfo_id_epoch); + G64 (0x20, cloneinfo_xid); + G64o (0x28, snap_meta_ext_oid); + volume_group_id.SetFrom(p + 0x30); + */ + G64o (0x40, integrity_meta_oid) + G64o (0x48, fext_tree_oid) + G32 (0x50, fext_tree_type) + /* + G32 (0x54, reserved_type); + G64o (0x58, reserved_oid); + */ + return true; +} + + +#define OBJ_ID_MASK 0x0fffffffffffffff +/* +#define OBJ_TYPE_MASK 0xf000000000000000 +#define SYSTEM_OBJ_ID_MARK 0x0fffffff00000000 +*/ +#define OBJ_TYPE_SHIFT 60 + +typedef enum +{ + APFS_TYPE_ANY = 0, + APFS_TYPE_SNAP_METADATA = 1, + APFS_TYPE_EXTENT = 2, + APFS_TYPE_INODE = 3, + APFS_TYPE_XATTR = 4, + APFS_TYPE_SIBLING_LINK = 5, + APFS_TYPE_DSTREAM_ID = 6, + APFS_TYPE_CRYPTO_STATE = 7, + APFS_TYPE_FILE_EXTENT = 8, + APFS_TYPE_DIR_REC = 9, + APFS_TYPE_DIR_STATS = 10, + APFS_TYPE_SNAP_NAME = 11, + APFS_TYPE_SIBLING_MAP = 12, + APFS_TYPE_FILE_INFO = 13, + APFS_TYPE_MAX_VALID = 13, + APFS_TYPE_MAX = 15, + APFS_TYPE_INVALID = 15 +} j_obj_types; + + +struct j_key_t +{ + UInt64 obj_id_and_type; + + void Parse(const Byte *p) { G64(0, obj_id_and_type) } + unsigned GetType() const { return (unsigned)(obj_id_and_type >> OBJ_TYPE_SHIFT); } + UInt64 GetID() const { return obj_id_and_type & OBJ_ID_MASK; } +}; + + + +#define J_DREC_LEN_MASK 0x000003ff +/* +#define J_DREC_HASH_MASK 0xfffff400 +#define J_DREC_HASH_SHIFT 10 +*/ + +static const unsigned k_SizeOf_j_drec_val = 0x12; + +struct j_drec_val +{ + UInt64 file_id; + UInt64 date_added; /* The time that this directory entry was added to the directory. + It's not updated when modifying the directory entry for example, + by renaming a file without moving it to a different directory. */ + UInt16 flags; + + // bool IsFlags_File() const { return flags == MY_LIN_DT_REG; } + bool IsFlags_Unknown() const { return flags == MY_LIN_DT_UNKNOWN; } + bool IsFlags_Dir() const { return flags == MY_LIN_DT_DIR; } + + // uint8_t xfields[]; + void Parse(const Byte *p) + { + G64 (0, file_id) + G64 (8, date_added) + G16 (0x10, flags) + } +}; + + +struct CItem +{ + // j_key_t hdr; + UInt64 ParentId; + AString Name; + j_drec_val Val; + + unsigned ParentItemIndex; + unsigned RefIndex; + // unsigned iNode_Index; + + void Clear() + { + Name.Empty(); + ParentItemIndex = VI_MINUS1; + RefIndex = VI_MINUS1; + } +}; + + +/* +#define INVALID_INO_NUM 0 +#define ROOT_DIR_PARENT 1 // parent for "root" and "private-dir", there's no inode on disk with this inode number. +*/ +#define ROOT_DIR_INO_NUM 2 // "root" - parent for all main files +#define PRIV_DIR_INO_NUM 3 // "private-dir" +/* +#define SNAP_DIR_INO_NUM 6 // the directory where snapshot metadata is stored. Snapshot inodes are stored in the snapshot metedata tree. +#define PURGEABLE_DIR_INO_NUM 7 +#define MIN_USER_INO_NUM 16 + +#define UNIFIED_ID_SPACE_MARK 0x0800000000000000 +*/ + +//typedef enum +// { +/* +INODE_IS_APFS_PRIVATE = 0x00000001, +INODE_MAINTAIN_DIR_STATS = 0x00000002, +INODE_DIR_STATS_ORIGIN = 0x00000004, +INODE_PROT_CLASS_EXPLICIT = 0x00000008, +INODE_WAS_CLONED = 0x00000010, +INODE_FLAG_UNUSED = 0x00000020, +INODE_HAS_SECURITY_EA = 0x00000040, +INODE_BEING_TRUNCATED = 0x00000080, +INODE_HAS_FINDER_INFO = 0x00000100, +INODE_IS_SPARSE = 0x00000200, +INODE_WAS_EVER_CLONED = 0x00000400, +INODE_ACTIVE_FILE_TRIMMED = 0x00000800, +INODE_PINNED_TO_MAIN = 0x00001000, +INODE_PINNED_TO_TIER2 = 0x00002000, +*/ +// INODE_HAS_RSRC_FORK = 0x00004000, +/* +INODE_NO_RSRC_FORK = 0x00008000, +INODE_ALLOCATION_SPILLEDOVER = 0x00010000, +INODE_FAST_PROMOTE = 0x00020000, +*/ +#define INODE_HAS_UNCOMPRESSED_SIZE 0x00040000 +/* +INODE_IS_PURGEABLE = 0x00080000, +INODE_WANTS_TO_BE_PURGEABLE = 0x00100000, +INODE_IS_SYNC_ROOT = 0x00200000, +INODE_SNAPSHOT_COW_EXEMPTION = 0x00400000, + + +INODE_INHERITED_INTERNAL_FLAGS = \ + ( INODE_MAINTAIN_DIR_STATS \ + | INODE_SNAPSHOT_COW_EXEMPTION), + +INODE_CLONED_INTERNAL_FLAGS = \ + ( INODE_HAS_RSRC_FORK \ + | INODE_NO_RSRC_FORK \ + | INODE_HAS_FINDER_INFO \ + | INODE_SNAPSHOT_COW_EXEMPTION), +*/ +// } +// j_inode_flags; + + +/* +#define APFS_VALID_INTERNAL_INODE_FLAGS \ +( INODE_IS_APFS_PRIVATE \ +| INODE_MAINTAIN_DIR_STATS \ +| INODE_DIR_STATS_ORIGIN \ +| INODE_PROT_CLASS_EXPLICIT \ +| INODE_WAS_CLONED \ +| INODE_HAS_SECURITY_EA \ +| INODE_BEING_TRUNCATED \ +| INODE_HAS_FINDER_INFO \ +| INODE_IS_SPARSE \ +| INODE_WAS_EVER_CLONED \ +| INODE_ACTIVE_FILE_TRIMMED \ +| INODE_PINNED_TO_MAIN \ +| INODE_PINNED_TO_TIER2 \ +| INODE_HAS_RSRC_FORK \ +| INODE_NO_RSRC_FORK \ +| INODE_ALLOCATION_SPILLEDOVER \ +| INODE_FAST_PROMOTE \ +| INODE_HAS_UNCOMPRESSED_SIZE \ +| INODE_IS_PURGEABLE \ +| INODE_WANTS_TO_BE_PURGEABLE \ +| INODE_IS_SYNC_ROOT \ +| INODE_SNAPSHOT_COW_EXEMPTION) + +#define APFS_INODE_PINNED_MASK (INODE_PINNED_TO_MAIN | INODE_PINNED_TO_TIER2) +*/ + +static const char * const g_INODE_Flags[] = +{ + "IS_APFS_PRIVATE" + , "MAINTAIN_DIR_STATS" + , "DIR_STATS_ORIGIN" + , "PROT_CLASS_EXPLICIT" + , "WAS_CLONED" + , "FLAG_UNUSED" + , "HAS_SECURITY_EA" + , "BEING_TRUNCATED" + , "HAS_FINDER_INFO" + , "IS_SPARSE" + , "WAS_EVER_CLONED" + , "ACTIVE_FILE_TRIMMED" + , "PINNED_TO_MAIN" + , "PINNED_TO_TIER2" + , "HAS_RSRC_FORK" + , "NO_RSRC_FORK" + , "ALLOCATION_SPILLEDOVER" + , "FAST_PROMOTE" + , "HAS_UNCOMPRESSED_SIZE" + , "IS_PURGEABLE" + , "WANTS_TO_BE_PURGEABLE" + , "IS_SYNC_ROOT" + , "SNAPSHOT_COW_EXEMPTION" +}; + + +// bsd stat.h +/* +#define MY_UF_SETTABLE 0x0000ffff // mask of owner changeable flags +#define MY_UF_NODUMP 0x00000001 // do not dump file +#define MY_UF_IMMUTABLE 0x00000002 // file may not be changed +#define MY_UF_APPEND 0x00000004 // writes to file may only append +#define MY_UF_OPAQUE 0x00000008 // directory is opaque wrt. union +#define MY_UF_NOUNLINK 0x00000010 // file entry may not be removed or renamed Not implement in MacOS +#define MY_UF_COMPRESSED 0x00000020 // file entry is compressed +#define MY_UF_TRACKED 0x00000040 // notify about file entry changes +#define MY_UF_DATAVAULT 0x00000080 // entitlement required for reading and writing +#define MY_UF_HIDDEN 0x00008000 // file entry is hidden + +#define MY_SF_SETTABLE 0xffff0000 // mask of superuser changeable flags +#define MY_SF_ARCHIVED 0x00010000 // file is archived +#define MY_SF_IMMUTABLE 0x00020000 // file may not be changed +#define MY_SF_APPEND 0x00040000 // writes to file may only append +#define MY_SF_RESTRICTED 0x00080000 // entitlement required for writing +#define MY_SF_NOUNLINK 0x00100000 // file entry may not be removed, renamed or used as mount point +#define MY_SF_SNAPSHOT 0x00200000 // snapshot inode +Not implement in MacOS +*/ + +static const char * const g_INODE_BSD_Flags[] = +{ + "UF_NODUMP" + , "UF_IMMUTABLE" + , "UF_APPEND" + , "UF_OPAQUE" + , "UF_NOUNLINK" + , "UF_COMPRESSED" + , "UF_TRACKED" + , "UF_DATAVAULT" + , NULL, NULL, NULL, NULL + , NULL, NULL, NULL + , "UF_HIDDEN" + + , "SF_ARCHIVE" + , "SF_IMMUTABLE" + , "SF_APPEND" + , "SF_RESTRICTED" + , "SF_NOUNLINK" + , "SF_SNAPSHOT" +}; + +/* +#define INO_EXT_TYPE_SNAP_XID 1 +#define INO_EXT_TYPE_DELTA_TREE_OID 2 +#define INO_EXT_TYPE_DOCUMENT_ID 3 +*/ +#define INO_EXT_TYPE_NAME 4 +/* +#define INO_EXT_TYPE_PREV_FSIZE 5 +#define INO_EXT_TYPE_RESERVED_6 6 +#define INO_EXT_TYPE_FINDER_INFO 7 +*/ +#define INO_EXT_TYPE_DSTREAM 8 +/* +#define INO_EXT_TYPE_RESERVED_9 9 +#define INO_EXT_TYPE_DIR_STATS_KEY 10 +#define INO_EXT_TYPE_FS_UUID 11 +#define INO_EXT_TYPE_RESERVED_12 12 +#define INO_EXT_TYPE_SPARSE_BYTES 13 +#define INO_EXT_TYPE_RDEV 14 +#define INO_EXT_TYPE_PURGEABLE_FLAGS 15 +#define INO_EXT_TYPE_ORIG_SYNC_ROOT_ID 16 +*/ + + +static const unsigned k_SizeOf_j_dstream = 8 * 5; + +struct j_dstream +{ + UInt64 size; + UInt64 alloced_size; + UInt64 default_crypto_id; + UInt64 total_bytes_written; + UInt64 total_bytes_read; + + void Parse(const Byte *p) + { + G64 (0, size) + G64 (0x8, alloced_size) + G64 (0x10, default_crypto_id) + G64 (0x18, total_bytes_written) + G64 (0x20, total_bytes_read) + } +}; + +static const unsigned k_SizeOf_j_file_extent_val = 8 * 3; + +#define J_FILE_EXTENT_LEN_MASK 0x00ffffffffffffffU +// #define J_FILE_EXTENT_FLAG_MASK 0xff00000000000000U +// #define J_FILE_EXTENT_FLAG_SHIFT 56 + +#define EXTENT_GET_LEN(x) ((x) & J_FILE_EXTENT_LEN_MASK) + +struct j_file_extent_val +{ + UInt64 len_and_flags; // The length must be a multiple of the block size defined by the nx_block_size field of nx_superblock_t. + // There are currently no flags defined + UInt64 phys_block_num; // The physical block address that the extent starts at + // UInt64 crypto_id; // The encryption key or the encryption tweak used in this extent. + + void Parse(const Byte *p) + { + G64 (0, len_and_flags) + G64 (0x8, phys_block_num) + // G64 (0x10, crypto_id); + } +}; + + +struct CExtent +{ + UInt64 logical_offset; + UInt64 len_and_flags; // The length must be a multiple of the block size defined by the nx_block_size field of nx_superblock_t. + // There are currently no flags defined + UInt64 phys_block_num; // The physical block address that the extent starts at + + UInt64 GetEndOffset() const { return logical_offset + EXTENT_GET_LEN(len_and_flags); } +}; + + +typedef UInt32 MY_uid_t; +typedef UInt32 MY_gid_t; +typedef UInt16 MY_mode_t; + + +typedef enum +{ + XATTR_DATA_STREAM = 1 << 0, + XATTR_DATA_EMBEDDED = 1 << 1, + XATTR_FILE_SYSTEM_OWNED = 1 << 2, + XATTR_RESERVED_8 = 1 << 3 +} j_xattr_flags; + + +struct CAttr +{ + AString Name; + UInt32 flags; + bool dstream_defined; + bool NeedShow; + CByteBuffer Data; + + j_dstream dstream; + UInt64 Id; + + bool Is_dstream_OK_for_SymLink() const + { + return dstream_defined && dstream.size <= (1 << 12) && dstream.size != 0; + } + + UInt64 GetSize() const + { + if (dstream_defined) // dstream has more priority + return dstream.size; + return Data.Size(); + } + + void Clear() + { + dstream_defined = false; + NeedShow = true; + Data.Free(); + Name.Empty(); + } + + bool Is_STREAM() const { return (flags & XATTR_DATA_STREAM) != 0; } + bool Is_EMBEDDED() const { return (flags & XATTR_DATA_EMBEDDED) != 0; } +}; + + +// j_inode_val_t +struct CNode +{ + unsigned ItemIndex; // index to CItem. We set it only if Node is directory. + unsigned NumCalcedLinks; // Num links to that node + // unsigned NumItems; // Num Items in that node + + UInt64 parent_id; // The identifier of the file system record for the parent directory. + UInt64 private_id; + UInt64 create_time; + UInt64 mod_time; + UInt64 change_time; + UInt64 access_time; + UInt64 internal_flags; + union + { + UInt32 nchildren; /* The number of directory entries. + is valid only if the inode is a directory */ + UInt32 nlink; /* The number of hard links whose target is this inode. + is valid only if the inode isn't a directory. + Inodes with multiple hard links (nlink > 1) + - The parent_id field refers to the parent directory of the primary link. + - The name field contains the name of the primary link. + - The INO_EXT_TYPE_NAME extended field contains the name of this link. + */ + }; + // cp_key_class_t default_protection_class; + UInt32 write_generation_counter; + UInt32 bsd_flags; + MY_uid_t owner; + MY_gid_t group; + MY_mode_t mode; + UInt16 pad1; + UInt64 uncompressed_size; + + j_dstream dstream; + AString PrimaryName; + + bool dstream_defined; + bool refcnt_defined; + + UInt32 refcnt; // j_dstream_id_val_t + CRecordVector Extents; + CObjectVector Attrs; + unsigned SymLinkIndex; // index in Attrs + unsigned DecmpfsIndex; // index in Attrs + unsigned ResourceIndex; // index in Attrs + + NHfs::CCompressHeader CompressHeader; + + CNode(): + ItemIndex(VI_MINUS1), + NumCalcedLinks(0), + // NumItems(0), + dstream_defined(false), + refcnt_defined(false), + SymLinkIndex(VI_MINUS1), + DecmpfsIndex(VI_MINUS1), + ResourceIndex(VI_MINUS1) + {} + + bool IsDir() const { return MY_LIN_S_ISDIR(mode); } + bool IsSymLink() const { return MY_LIN_S_ISLNK(mode); } + + bool Has_UNCOMPRESSED_SIZE() const { return (internal_flags & INODE_HAS_UNCOMPRESSED_SIZE) != 0; } + + unsigned Get_Type_From_mode() const { return mode >> 12; } + + bool GetSize(unsigned attrIndex, UInt64 &size) const + { + if (IsViNotDef(attrIndex)) + { + if (dstream_defined) + { + size = dstream.size; + return true; + } + size = 0; + if (Has_UNCOMPRESSED_SIZE()) + { + size = uncompressed_size; + return true; + } + if (!IsSymLink()) + return false; + attrIndex = SymLinkIndex; + if (IsViNotDef(attrIndex)) + return false; + } + const CAttr &attr = Attrs[(unsigned)attrIndex]; + if (attr.dstream_defined) + size = attr.dstream.size; + else + size = attr.Data.Size(); + return true; + } + + bool GetPackSize(unsigned attrIndex, UInt64 &size) const + { + if (IsViNotDef(attrIndex)) + { + if (dstream_defined) + { + size = dstream.alloced_size; + return true; + } + size = 0; + + if (IsSymLink()) + attrIndex = SymLinkIndex; + else + { + if (!CompressHeader.IsCorrect || + !CompressHeader.IsSupported) + return false; + const CAttr &attr = Attrs[DecmpfsIndex]; + if (!CompressHeader.IsMethod_Resource()) + { + size = attr.Data.Size() - CompressHeader.DataPos; + return true; + } + attrIndex = ResourceIndex; + } + if (IsViNotDef(attrIndex)) + return false; + } + const CAttr &attr = Attrs[(unsigned)attrIndex]; + if (attr.dstream_defined) + size = attr.dstream.alloced_size; + else + size = attr.Data.Size(); + return true; + } + + void Parse(const Byte *p); +}; + + +// it's used for Attr streams +struct CSmallNode +{ + CRecordVector Extents; + // UInt32 NumLinks; + // CSmallNode(): NumLinks(0) {} +}; + +static const unsigned k_SizeOf_j_inode_val = 0x5c; + +void CNode::Parse(const Byte *p) +{ + G64 (0, parent_id) + G64 (0x8, private_id) + G64 (0x10, create_time) + G64 (0x18, mod_time) + G64 (0x20, change_time) + G64 (0x28, access_time) + G64 (0x30, internal_flags) + { + G32 (0x38, nchildren) + // G32 (0x38, nlink); + } + // G32 (0x3c, default_protection_class); + G32 (0x40, write_generation_counter) + G32 (0x44, bsd_flags) + G32 (0x48, owner) + G32 (0x4c, group) + G16 (0x50, mode) + // G16 (0x52, pad1); + G64 (0x54, uncompressed_size) +} + + +struct CRef +{ + unsigned ItemIndex; + unsigned NodeIndex; + unsigned ParentRefIndex; + + #ifdef APFS_SHOW_ALT_STREAMS + unsigned AttrIndex; + bool IsAltStream() const { return IsViDef(AttrIndex); } + unsigned GetAttrIndex() const { return AttrIndex; } + #else + // bool IsAltStream() const { return false; } + unsigned GetAttrIndex() const { return VI_MINUS1; } + #endif +}; + + +struct CRef2 +{ + unsigned VolIndex; + unsigned RefIndex; +}; + + +struct CHashChunk +{ + UInt64 lba; + UInt32 hashed_len; // the value is UInt16 + Byte hash[APFS_HASH_MAX_SIZE]; +}; + +typedef CRecordVector CStreamHashes; + + +Z7_CLASS_IMP_NOQIB_1( + COutStreamWithHash + , ISequentialOutStream +) + bool _hashError; + CAlignedBuffer1 _sha; + CMyComPtr _stream; + const CStreamHashes *_hashes; + unsigned _blockSizeLog; + unsigned _chunkIndex; + UInt32 _offsetInChunk; + // UInt64 _size; + + CSha256 *Sha() { return (CSha256 *)(void *)(Byte *)_sha; } +public: + COutStreamWithHash(): _sha(sizeof(CSha256)) {} + + void SetStream(ISequentialOutStream *stream) { _stream = stream; } + // void ReleaseStream() { _stream.Release(); } + void Init(const CStreamHashes *hashes, unsigned blockSizeLog) + { + _hashes = hashes; + _blockSizeLog = blockSizeLog; + _chunkIndex = 0; + _offsetInChunk = 0; + _hashError = false; + // _size = 0; + } + // UInt64 GetSize() const { return _size; } + bool FinalCheck(); +}; + + +static bool Sha256_Final_and_CheckDigest(CSha256 *sha256, const Byte *digest) +{ + MY_ALIGN (16) + UInt32 temp[SHA256_NUM_DIGEST_WORDS]; + Sha256_Final(sha256, (Byte *)temp); + return memcmp(temp, digest, SHA256_DIGEST_SIZE) == 0; +} + + +Z7_COM7F_IMF(COutStreamWithHash::Write(const void *data, UInt32 size, UInt32 *processedSize)) +{ + HRESULT result = S_OK; + if (_stream) + result = _stream->Write(data, size, &size); + if (processedSize) + *processedSize = size; + while (size != 0) + { + if (_hashError) + break; + if (_chunkIndex >= _hashes->Size()) + { + _hashError = true; + break; + } + if (_offsetInChunk == 0) + Sha256_Init(Sha()); + const CHashChunk &chunk = (*_hashes)[_chunkIndex]; + /* (_blockSizeLog <= 16) && chunk.hashed_len is 16-bit. + so we can use 32-bit chunkSize here */ + const UInt32 chunkSize = ((UInt32)chunk.hashed_len << _blockSizeLog); + const UInt32 rem = chunkSize - _offsetInChunk; + UInt32 cur = size; + if (cur > rem) + cur = (UInt32)rem; + Sha256_Update(Sha(), (const Byte *)data, cur); + data = (const void *)((const Byte *)data + cur); + size -= cur; + // _size += cur; + _offsetInChunk += cur; + if (chunkSize == _offsetInChunk) + { + if (!Sha256_Final_and_CheckDigest(Sha(), chunk.hash)) + _hashError = true; + _offsetInChunk = 0; + _chunkIndex++; + } + } + return result; +} + + +bool COutStreamWithHash::FinalCheck() +{ + if (_hashError) + return false; + + if (_offsetInChunk != 0) + { + const CHashChunk &chunk = (*_hashes)[_chunkIndex]; + { + const UInt32 chunkSize = ((UInt32)chunk.hashed_len << _blockSizeLog); + const UInt32 rem = chunkSize - _offsetInChunk; + Byte b = 0; + for (UInt32 i = 0; i < rem; i++) + Sha256_Update(Sha(), &b, 1); + } + if (!Sha256_Final_and_CheckDigest(Sha(), chunk.hash)) + _hashError = true; + _offsetInChunk = 0; + _chunkIndex++; + } + if (_chunkIndex != _hashes->Size()) + _hashError = true; + return !_hashError; +} + + + +struct CVol +{ + CObjectVector Nodes; + CRecordVector NodeIDs; + CObjectVector Items; + CRecordVector Refs; + + CObjectVector SmallNodes; + CRecordVector SmallNodeIDs; + + CObjectVector FEXT_Nodes; + CRecordVector FEXT_NodeIDs; + + CObjectVector Hash_Vectors; + CRecordVector Hash_IDs; + + unsigned StartRef2Index; // ref2_Index for Refs[0] item + unsigned RootRef2Index; // ref2_Index of virtual root folder (Volume1) + CApfs apfs; + C_integrity_meta_phys integrity; + + bool NodeNotFound; + bool ThereAreUnlinkedNodes; + bool WrongInodeLink; + bool UnsupportedFeature; + bool UnsupportedMethod; + + unsigned NumItems_In_PrivateDir; + unsigned NumAltStreams; + + UString RootName; + + bool ThereAreErrors() const + { + return NodeNotFound || ThereAreUnlinkedNodes || WrongInodeLink; + } + + void AddComment(UString &s) const; + + HRESULT FillRefs(); + + CVol(): + StartRef2Index(0), + RootRef2Index(VI_MINUS1), + NodeNotFound(false), + ThereAreUnlinkedNodes(false), + WrongInodeLink(false), + UnsupportedFeature(false), + UnsupportedMethod(false), + NumItems_In_PrivateDir(0), + NumAltStreams(0) + {} +}; + + +static void ApfsTimeToFileTime(UInt64 apfsTime, FILETIME &ft, UInt32 &ns100) +{ + const UInt64 s = apfsTime / 1000000000; + const UInt32 ns = (UInt32)(apfsTime % 1000000000); + ns100 = (ns % 100); + const UInt64 v = NWindows::NTime::UnixTime64_To_FileTime64((Int64)s) + ns / 100; + ft.dwLowDateTime = (DWORD)v; + ft.dwHighDateTime = (DWORD)(v >> 32); +} + +static void AddComment_Name(UString &s, const char *name) +{ + s += name; + s += ": "; +} + +/* +static void AddComment_Bool(UString &s, const char *name, bool val) +{ + AddComment_Name(s, name); + s += val ? "+" : "-"; + s.Add_LF(); +} +*/ + +static void AddComment_UInt64(UString &s, const char *name, UInt64 v) +{ + AddComment_Name(s, name); + s.Add_UInt64(v); + s.Add_LF(); +} + + +static void AddComment_Time(UString &s, const char *name, UInt64 v) +{ + AddComment_Name(s, name); + + FILETIME ft; + UInt32 ns100; + ApfsTimeToFileTime(v, ft, ns100); + char temp[64]; + ConvertUtcFileTimeToString2(ft, ns100, temp + // , kTimestampPrintLevel_SEC); + , kTimestampPrintLevel_NS); + s += temp; + s.Add_LF(); +} + + +static void AddComment_modified_by_t(UString &s, const char *name, const apfs_modified_by_t &v) +{ + AddComment_Name(s, name); + AString s2; + s2.SetFrom_CalcLen((const char *)v.id, sizeof(v.id)); + s += s2; + s.Add_LF(); + s += " "; + AddComment_Time(s, "timestamp", v.timestamp); + s += " "; + AddComment_UInt64(s, "last_xid", v.last_xid); +} + + +static void AddVolInternalName_toString(UString &s, const CApfs &apfs) +{ + AString temp; + temp.SetFrom_CalcLen((const char *)apfs.volname, sizeof(apfs.volname)); + UString unicode; + ConvertUTF8ToUnicode(temp, unicode); + s += unicode; +} + + +void CVol::AddComment(UString &s) const +{ + AddComment_UInt64(s, "fs_index", apfs.fs_index); + { + AddComment_Name(s, "volume_name"); + AddVolInternalName_toString(s, apfs); + s.Add_LF(); + } + AddComment_Name(s, "vol_uuid"); + apfs.vol_uuid.AddHexToString(s); + s.Add_LF(); + + AddComment_Name(s, "incompatible_features"); + s += FlagsToString(g_APFS_INCOMPAT_Flags, + Z7_ARRAY_SIZE(g_APFS_INCOMPAT_Flags), + (UInt32)apfs.incompatible_features); + s.Add_LF(); + + + if (apfs.integrity_meta_oid != 0) + { + /* + AddComment_Name(s, "im_version"); + s.Add_UInt32(integrity.im_version); + s.Add_LF(); + */ + AddComment_Name(s, "im_flags"); + s.Add_UInt32(integrity.im_flags); + s.Add_LF(); + AddComment_Name(s, "im_hash_type"); + const char *p = NULL; + if (integrity.im_hash_type < Z7_ARRAY_SIZE(g_hash_types)) + p = g_hash_types[integrity.im_hash_type]; + if (p) + s += p; + else + s.Add_UInt32(integrity.im_hash_type); + s.Add_LF(); + } + + + // AddComment_UInt64(s, "reserve_block_count", apfs.fs_reserve_block_count, false); + // AddComment_UInt64(s, "quota_block_count", apfs.fs_quota_block_count); + AddComment_UInt64(s, "fs_alloc_count", apfs.fs_alloc_count); + + AddComment_UInt64(s, "num_files", apfs.num_files); + AddComment_UInt64(s, "num_directories", apfs.num_directories); + AddComment_UInt64(s, "num_symlinks", apfs.num_symlinks); + AddComment_UInt64(s, "num_other_fsobjects", apfs.num_other_fsobjects); + + AddComment_UInt64(s, "Num_Attr_Streams", NumAltStreams); + + AddComment_UInt64(s, "num_snapshots", apfs.num_snapshots); + AddComment_UInt64(s, "total_blocks_alloced", apfs.total_blocks_alloced); + AddComment_UInt64(s, "total_blocks_freed", apfs.total_blocks_freed); + + AddComment_Time(s, "unmounted", apfs.unmount_time); + AddComment_Time(s, "last_modified", apfs.last_mod_time); + AddComment_modified_by_t(s, "formatted_by", apfs.formatted_by); + for (unsigned i = 0; i < Z7_ARRAY_SIZE(apfs.modified_by); i++) + { + const apfs_modified_by_t &v = apfs.modified_by[i]; + if (v.last_xid == 0 && v.timestamp == 0 && v.id[0] == 0) + continue; + AString name ("modified_by["); + name.Add_UInt32(i); + name += ']'; + AddComment_modified_by_t(s, name.Ptr(), v); + } +} + + + +struct CKeyValPair +{ + CByteBuffer Key; + CByteBuffer Val; + // unsigned ValPos; // for alognment +}; + + +struct omap_key +{ + oid_t oid; // The object identifier + xid_t xid; // The transaction identifier + void Parse(const Byte *p) + { + G64o (0, oid) + G64x (8, xid) + } +}; + +/* +#define OMAP_VAL_DELETED (1 << 0) +#define OMAP_VAL_SAVED (1 << 1) +#define OMAP_VAL_ENCRYPTED (1 << 2) +*/ +#define OMAP_VAL_NOHEADER (1 << 3) +/* +#define OMAP_VAL_CRYPTO_GENERATION (1 << 4) +*/ + +struct omap_val +{ + UInt32 flags; + UInt32 size; + // paddr_t paddr; + paddr_t_unsigned paddr; + + bool IsFlag_NoHeader() const { return (flags & OMAP_VAL_NOHEADER) != 0; } + + void Parse(const Byte *p) + { + G32 (0, flags) + G32 (4, size) + G64 (8, paddr) + } +}; + + +struct CObjectMap +{ + CRecordVector Keys; + CRecordVector Vals; + + bool Parse(const CObjectVector &pairs); + int FindKey(UInt64 id) const { return Keys.FindInSorted(id); } +}; + +bool CObjectMap::Parse(const CObjectVector &pairs) +{ + omap_key prev; + prev.oid = 0; + prev.xid = 0; + FOR_VECTOR (i, pairs) + { + const CKeyValPair &pair = pairs[i]; + if (pair.Key.Size() != 16 || pair.Val.Size() != 16) + return false; + omap_key key; + key.Parse(pair.Key); + omap_val val; + val.Parse(pair.Val); + /* Object map B-trees are sorted by object identifier and then by transaction identifier + but it's possible to have identical Ids in map ? + do we need to look transaction id ? + and search key with largest transaction id? */ + if (key.oid <= prev.oid) + return false; + prev = key; + Keys.Add(key.oid); + Vals.Add(val); + } + return true; +} + + +struct CMap +{ + CObjectVector Pairs; + + CObjectMap Omap; + btree_info bti; + UInt64 NumNodes; + + // we use thnese options to check: + UInt32 Subtype; + bool IsPhysical; + + bool CheckAtFinish() const + { + return NumNodes == bti.node_count && Pairs.Size() == bti.key_count; + } + + CMap(): + NumNodes(0), + Subtype(0), + IsPhysical(true) + {} +}; + + + +struct CDatabase +{ + CRecordVector Refs2; + CObjectVector Vols; + + bool HeadersError; + bool ThereAreAltStreams; + bool UnsupportedFeature; + bool UnsupportedMethod; + + CSuperBlock sb; + + IInStream *OpenInStream; + IArchiveOpenCallback *OpenCallback; + UInt64 ProgressVal_Cur; + UInt64 ProgressVal_Prev; + UInt64 ProgressVal_NumFilesTotal; + CObjectVector Buffers; + + UInt32 MethodsMask; + UInt64 GetSize(const UInt32 index) const; + + void Clear() + { + HeadersError = false; + ThereAreAltStreams = false; + UnsupportedFeature = false; + UnsupportedMethod = false; + + ProgressVal_Cur = 0; + ProgressVal_Prev = 0; + ProgressVal_NumFilesTotal = 0; + + MethodsMask = 0; + + Vols.Clear(); + Refs2.Clear(); + Buffers.Clear(); + } + + HRESULT SeekReadBlock_FALSE(UInt64 oid, void *data); + void GetItemPath(unsigned index, const CNode *inode, NWindows::NCOM::CPropVariant &path) const; + HRESULT ReadMap(UInt64 oid, bool noHeader, CVol *vol, const Byte *hash, + CMap &map, unsigned recurseLevel); + HRESULT ReadObjectMap(UInt64 oid, CVol *vol, CObjectMap &map); + HRESULT OpenVolume(const CObjectMap &omap, const oid_t fs_oid); + HRESULT Open2(); + + HRESULT GetAttrStream(IInStream *apfsInStream, const CVol &vol, + const CAttr &attr, ISequentialInStream **stream); + + HRESULT GetAttrStream_dstream(IInStream *apfsInStream, const CVol &vol, + const CAttr &attr, ISequentialInStream **stream); + + HRESULT GetStream2( + IInStream *apfsInStream, + const CRecordVector *extents, UInt64 rem, + ISequentialInStream **stream); +}; + + +HRESULT CDatabase::SeekReadBlock_FALSE(UInt64 oid, void *data) +{ + if (OpenCallback) + { + if (ProgressVal_Cur - ProgressVal_Prev >= (1 << 22)) + { + RINOK(OpenCallback->SetCompleted(NULL, &ProgressVal_Cur)) + ProgressVal_Prev = ProgressVal_Cur; + } + ProgressVal_Cur += sb.block_size; + } + if (oid == 0 || oid >= sb.block_count) + return S_FALSE; + RINOK(InStream_SeekSet(OpenInStream, oid << sb.block_size_Log)) + return ReadStream_FALSE(OpenInStream, data, sb.block_size); +} + + + +API_FUNC_static_IsArc IsArc_APFS(const Byte *p, size_t size) +{ + if (size < kApfsHeaderSize) + return k_IsArc_Res_NEED_MORE; + CSuperBlock sb; + if (!sb.Parse(p)) + return k_IsArc_Res_NO; + return k_IsArc_Res_YES; +} +} + + +static bool CheckHash(unsigned hashAlgo, const Byte *data, size_t size, const Byte *digest) +{ + if (hashAlgo == APFS_HASH_SHA256) + { + MY_ALIGN (16) + CSha256 sha; + Sha256_Init(&sha); + Sha256_Update(&sha, data, size); + return Sha256_Final_and_CheckDigest(&sha, digest); + } + return true; +} + + +HRESULT CDatabase::ReadMap(UInt64 oid, bool noHeader, + CVol *vol, const Byte *hash, + CMap &map, unsigned recurseLevel) +{ + // is it allowed to use big number of levels ? + if (recurseLevel > (1 << 10)) + return S_FALSE; + + const UInt32 blockSize = sb.block_size; + if (Buffers.Size() <= recurseLevel) + { + Buffers.AddNew(); + if (Buffers.Size() <= recurseLevel) + throw 123; + Buffers.Back().Alloc(blockSize); + } + const Byte *buf; + { + CByteBuffer &buf2 = Buffers[recurseLevel]; + RINOK(SeekReadBlock_FALSE(oid, buf2)) + buf = buf2; + } + + if (hash && vol) + if (!CheckHash(vol->integrity.im_hash_type, buf, blockSize, hash)) + return S_FALSE; + + CBTreeNodePhys bt; + if (!bt.Parse(buf, blockSize, noHeader)) + return S_FALSE; + + map.NumNodes++; + + /* Specification: All values are stored in leaf nodes, which + makes these B+ trees, and the values in nonleaf nodes are object + identifiers of child nodes. + + The entries in the table of contents are sorted by key. The comparison function used for sorting depends on the keys type + - Object map B-trees are sorted by object identifier and then by transaction identifier. + - Free queue B-trees are sorted by transaction identifier and then by physical address. + - File-system records are sorted according to the rules listed in File-System Objects. + */ + + if (!noHeader) + if (bt.ophys.subtype != map.Subtype) + return S_FALSE; + + unsigned endLimit = blockSize; + + if (recurseLevel == 0) + { + if (!noHeader) + if (bt.ophys.GetType() != OBJECT_TYPE_BTREE) + return S_FALSE; + if ((bt.flags & BTNODE_ROOT) == 0) + return S_FALSE; + endLimit -= k_btree_info_Size; + map.bti.Parse(buf + endLimit); + btree_info &bti = map.bti; + if (bti.fixed.key_size >= blockSize) + return S_FALSE; + if (bti.Is_EPHEMERAL() && + bti.Is_PHYSICAL()) + return S_FALSE; + if (bti.Is_PHYSICAL() != map.IsPhysical) + return S_FALSE; + if (bti.Is_NOHEADER() != noHeader) + return S_FALSE; + // we don't allow volumes with big number of Keys + const unsigned kNumItemsMax = k_VectorSizeMax; + if (map.bti.node_count > kNumItemsMax) + return S_FALSE; + if (map.bti.key_count > kNumItemsMax) + return S_FALSE; + } + else + { + if (!noHeader) + if (bt.ophys.GetType() != OBJECT_TYPE_BTREE_NODE) + return S_FALSE; + if ((bt.flags & BTNODE_ROOT) != 0) + return S_FALSE; + if (map.NumNodes > map.bti.node_count + || map.Pairs.Size() > map.bti.key_count) + return S_FALSE; + } + + const bool isLeaf = (bt.flags & BTNODE_LEAF) != 0; + + if (isLeaf) + { + if (bt.level != 0) + return S_FALSE; + } + else + { + if (bt.level == 0) + return S_FALSE; + } + + if (!bt.table_space.CheckOverLimit(endLimit - k_Toc_offset)) + return S_FALSE; + + const unsigned tableEnd = k_Toc_offset + bt.table_space.GetEnd(); + const unsigned keyValRange = endLimit - tableEnd; + const unsigned tocEntrySize = bt.Is_FIXED_KV_SIZE() ? 4 : 8; + if (bt.table_space.len / tocEntrySize < bt.nkeys) + return S_FALSE; + + for (unsigned i = 0; i < bt.nkeys; i++) + { + const Byte *p = buf + k_Toc_offset + bt.table_space.off + i * tocEntrySize; + if (bt.Is_FIXED_KV_SIZE()) + { + kvoff a; + a.Parse(p); + if (a.k + map.bti.fixed.key_size > keyValRange + || a.v > keyValRange) + return S_FALSE; + { + CKeyValPair pair; + + const Byte *p2 = buf + k_Toc_offset + bt.table_space.len; + p2 += a.k; + pair.Key.CopyFrom(p2, map.bti.fixed.key_size); + + p2 = buf + endLimit; + p2 -= a.v; + if (isLeaf) + { + if (a.v < map.bti.fixed.val_size) + return S_FALSE; + pair.Val.CopyFrom(p2, map.bti.fixed.val_size); + // pair.ValPos = endLimit - a.v; + map.Pairs.Add(pair); + continue; + } + { + if (a.v < 8) + return S_FALSE; + // value is only 64-bit for non leaf. + const oid_t oidNext = Get64(p2); + if (map.bti.Is_PHYSICAL()) + { + RINOK(ReadMap(oidNext, noHeader, vol, + NULL, /* hash */ + map, recurseLevel + 1)) + continue; + } + else + { + // fixme + return S_FALSE; + } + } + } + } + else + { + kvloc a; + a.Parse(p); + if (!a.k.CheckOverLimit(keyValRange) + || a.v.off > keyValRange + || a.v.len > a.v.off) + return S_FALSE; + { + CKeyValPair pair; + const Byte *p2 = buf + k_Toc_offset + bt.table_space.len; + p2 += a.k.off; + pair.Key.CopyFrom(p2, a.k.len); + + p2 = buf + endLimit; + p2 -= a.v.off; + if (isLeaf) + { + pair.Val.CopyFrom(p2, a.v.len); + // pair.ValPos = endLimit - a.v.off; + map.Pairs.Add(pair); + continue; + } + { + if (a.v.len < 8) + return S_FALSE; + + const Byte *hashNew = NULL; + oid_t oidNext = Get64(p2); + + if (bt.Is_HASHED()) + { + if (!vol) + return S_FALSE; + /* + struct btn_index_node_val { + oid_t binv_child_oid; + uint8_t binv_child_hash[BTREE_NODE_HASH_SIZE_MAX]; + }; + */ + /* (a.v.len == 40) is possible if Is_HASHED() + so there is hash (for example, 256-bit) after 64-bit id) */ + if (a.v.len != 8 + vol->integrity.HashSize) + return S_FALSE; + hashNew = p2 + 8; + /* we need to add root_tree_oid here, + but where it's defined in apfs specification ? */ + oidNext += vol->apfs.root_tree_oid; + } + else + { + if (a.v.len != 8) + return S_FALSE; + } + + // value is only 64-bit for non leaf. + + if (map.bti.Is_PHYSICAL()) + { + return S_FALSE; + // the code was not tested: + // RINOK(ReadMap(oidNext, map, recurseLevel + 1)); + // continue; + } + /* + else if (map.bti.Is_EPHEMERAL()) + { + // Ephemeral objects are stored in memory while the container is mounted and in a checkpoint when the container isn't mounted + // the code was not tested: + return S_FALSE; + } + */ + else + { + /* nodes in the B-tree use virtual object identifiers to link to their child nodes. */ + const int index = map.Omap.FindKey(oidNext); + if (index == -1) + return S_FALSE; + const omap_val &ov = map.Omap.Vals[(unsigned)index]; + if (ov.size != blockSize) // change it : it must be multiple of + return S_FALSE; + RINOK(ReadMap(ov.paddr, ov.IsFlag_NoHeader(), vol, hashNew, + map, recurseLevel + 1)) + continue; + } + } + } + } + } + + if (recurseLevel == 0) + if (!map.CheckAtFinish()) + return S_FALSE; + return S_OK; +} + + + +HRESULT CDatabase::ReadObjectMap(UInt64 oid, CVol *vol, CObjectMap &omap) +{ + CByteBuffer buf; + const size_t blockSize = sb.block_size; + buf.Alloc(blockSize); + RINOK(SeekReadBlock_FALSE(oid, buf)) + C_omap_phys op; + if (!op.Parse(buf, blockSize, oid)) + return S_FALSE; + CMap map; + map.Subtype = OBJECT_TYPE_OMAP; + map.IsPhysical = true; + RINOK(ReadMap(op.tree_oid, + false /* noHeader */, + vol, + NULL, /* hash */ + map, 0)) + if (!omap.Parse(map.Pairs)) + return S_FALSE; + return S_OK; +} + + + +HRESULT CDatabase::Open2() +{ + Clear(); + CSuperBlock2 sb2; + { + Byte buf[kApfsHeaderSize]; + RINOK(ReadStream_FALSE(OpenInStream, buf, kApfsHeaderSize)) + if (!sb.Parse(buf)) + return S_FALSE; + sb2.Parse(buf); + } + + { + CObjectMap omap; + RINOK(ReadObjectMap(sb.omap_oid, + NULL, /* CVol */ + omap)) + unsigned numRefs = 0; + for (unsigned i = 0; i < sb.max_file_systems; i++) + { + const oid_t oid = sb2.fs_oid[i]; + if (oid == 0) + continue; + // for (unsigned k = 0; k < 1; k++) // for debug + RINOK(OpenVolume(omap, oid)) + const unsigned a = Vols.Back().Refs.Size(); + numRefs += a; + if (numRefs < a) + return S_FALSE; // overflow + } + } + + const bool needVolumePrefix = (Vols.Size() > 1); + // const bool needVolumePrefix = true; // for debug + { + unsigned numRefs = 0; + FOR_VECTOR (i, Vols) + { + const unsigned a = Vols[i].Refs.Size(); + numRefs += a; + if (numRefs < a) + return S_FALSE; // overflow + } + numRefs += Vols.Size(); + if (numRefs < Vols.Size()) + return S_FALSE; // overflow + Refs2.Reserve(numRefs); + } + { + FOR_VECTOR (i, Vols) + { + CVol &vol = Vols[i]; + + CRef2 ref2; + ref2.VolIndex = i; + + if (needVolumePrefix) + { + vol.RootName = "Volume"; + vol.RootName.Add_UInt32(1 + (UInt32)i); + vol.RootRef2Index = Refs2.Size(); + ref2.RefIndex = VI_MINUS1; + Refs2.Add(ref2); + } + + vol.StartRef2Index = Refs2.Size(); + const unsigned numItems = vol.Refs.Size(); + for (unsigned k = 0; k < numItems; k++) + { + ref2.RefIndex = k; + Refs2.Add(ref2); + } + } + } + return S_OK; +} + + +HRESULT CDatabase::OpenVolume(const CObjectMap &omap, const oid_t fs_oid) +{ + const size_t blockSize = sb.block_size; + CByteBuffer buf; + { + const int index = omap.FindKey(fs_oid); + if (index == -1) + return S_FALSE; + const omap_val &ov = omap.Vals[(unsigned)index]; + if (ov.size != blockSize) // change it : it must be multiple of + return S_FALSE; + buf.Alloc(blockSize); + RINOK(SeekReadBlock_FALSE(ov.paddr, buf)) + } + + CVol &vol = Vols.AddNew(); + CApfs &apfs = vol.apfs; + + if (!apfs.Parse(buf, blockSize)) + return S_FALSE; + + if (apfs.fext_tree_oid != 0) + { + if ((apfs.incompatible_features & APFS_INCOMPAT_SEALED_VOLUME) == 0) + return S_FALSE; + if ((apfs.fext_tree_type & OBJ_PHYSICAL) == 0) + return S_FALSE; + if ((apfs.fext_tree_type & OBJECT_TYPE_MASK) != OBJECT_TYPE_BTREE) + return S_FALSE; + + CMap map2; + map2.Subtype = OBJECT_TYPE_FEXT_TREE; + map2.IsPhysical = true; + + RINOK(ReadMap(apfs.fext_tree_oid, + false /* noHeader */, + &vol, + NULL, /* hash */ + map2, 0)) + + UInt64 prevId = 1; + + FOR_VECTOR (i, map2.Pairs) + { + if (OpenCallback && (i & 0xffff) == 1) + { + const UInt64 numFiles = ProgressVal_NumFilesTotal + + (vol.Items.Size() + vol.Nodes.Size()) / 2; + RINOK(OpenCallback->SetCompleted(&numFiles, &ProgressVal_Cur)) + } + // The key half of a record from a file extent tree. + // struct fext_tree_key + const CKeyValPair &pair = map2.Pairs[i]; + if (pair.Key.Size() != 16) + return S_FALSE; + const Byte *p = pair.Key; + const UInt64 id = GetUi64(p); + if (id < prevId) + return S_FALSE; // IDs must be sorted + prevId = id; + + CExtent ext; + ext.logical_offset = GetUi64(p + 8); + { + if (pair.Val.Size() != 16) + return S_FALSE; + const Byte *p2 = pair.Val; + ext.len_and_flags = GetUi64(p2); + ext.phys_block_num = GetUi64(p2 + 8); + } + + PRF(printf("\n%6d: id=%6d logical_addr = %2d len_and_flags=%5x phys_block_num = %5d", + i, (unsigned)id, + (unsigned)ext.logical_offset, + (unsigned)ext.len_and_flags, + (unsigned)ext.phys_block_num)); + + if (vol.FEXT_NodeIDs.IsEmpty() || + vol.FEXT_NodeIDs.Back() != id) + { + vol.FEXT_NodeIDs.Add(id); + vol.FEXT_Nodes.AddNew(); + } + CRecordVector &extents = vol.FEXT_Nodes.Back().Extents; + if (!extents.IsEmpty()) + if (ext.logical_offset != extents.Back().GetEndOffset()) + return S_FALSE; + extents.Add(ext); + continue; + } + + PRF(printf("\n\n")); + } + + /* For each volume, read the root file system tree's virtual object + identifier from the apfs_root_tree_oid field, + and then look it up in the volume object map indicated + by the omap_oid field. */ + + CMap map; + ReadObjectMap(apfs.omap_oid, &vol, map.Omap); + + const Byte *hash_for_root = NULL; + + if (apfs.integrity_meta_oid != 0) + { + if ((apfs.incompatible_features & APFS_INCOMPAT_SEALED_VOLUME) == 0) + return S_FALSE; + const int index = map.Omap.FindKey(apfs.integrity_meta_oid); + if (index == -1) + return S_FALSE; + const omap_val &ov = map.Omap.Vals[(unsigned)index]; + if (ov.size != blockSize) + return S_FALSE; + RINOK(SeekReadBlock_FALSE(ov.paddr, buf)) + if (!vol.integrity.Parse(buf, blockSize, apfs.integrity_meta_oid)) + return S_FALSE; + if (vol.integrity.im_hash_type != 0) + hash_for_root = vol.integrity.Hash; + } + + { + const int index = map.Omap.FindKey(apfs.root_tree_oid); + if (index == -1) + return S_FALSE; + const omap_val &ov = map.Omap.Vals[(unsigned)index]; + if (ov.size != blockSize) // change it : it must be multiple of + return S_FALSE; + map.Subtype = OBJECT_TYPE_FSTREE; + map.IsPhysical = false; + RINOK(ReadMap(ov.paddr, ov.IsFlag_NoHeader(), + &vol, hash_for_root, + map, 0)) + } + + bool needParseAttr = false; + + { + const bool isHashed = apfs.IsHashedName(); + UInt64 prevId = 1; + + { + const UInt64 numApfsItems = vol.apfs.GetTotalItems() + + 2; // we will have 2 additional hidden directories: root and private-dir + const UInt64 numApfsItems_Reserve = numApfsItems + + 16; // we reserve 16 for some possible unexpected items + if (numApfsItems_Reserve < map.Pairs.Size()) + { + vol.Items.ClearAndReserve((unsigned)numApfsItems_Reserve); + vol.Nodes.ClearAndReserve((unsigned)numApfsItems_Reserve); + vol.NodeIDs.ClearAndReserve((unsigned)numApfsItems_Reserve); + } + if (OpenCallback) + { + const UInt64 numFiles = ProgressVal_NumFilesTotal + numApfsItems; + RINOK(OpenCallback->SetTotal(&numFiles, NULL)) + } + } + + CAttr attr; + CItem item; + + FOR_VECTOR (i, map.Pairs) + { + if (OpenCallback && (i & 0xffff) == 1) + { + const UInt64 numFiles = ProgressVal_NumFilesTotal + + (vol.Items.Size() + vol.Nodes.Size()) / 2; + RINOK(OpenCallback->SetCompleted(&numFiles, &ProgressVal_Cur)) + } + + const CKeyValPair &pair = map.Pairs[i]; + j_key_t jkey; + if (pair.Key.Size() < 8) + return S_FALSE; + const Byte *p = pair.Key; + jkey.Parse(p); + const unsigned type = jkey.GetType(); + const UInt64 id = jkey.GetID(); + if (id < prevId) + return S_FALSE; // IDs must be sorted + prevId = id; + + PRF(printf("\n%6d: id=%6d type = %2d ", i, (unsigned)id, type)); + + if (type == APFS_TYPE_INODE) + { + PRF(printf("INODE")); + if (pair.Key.Size() != 8 || + pair.Val.Size() < k_SizeOf_j_inode_val) + return S_FALSE; + + CNode inode; + inode.Parse(pair.Val); + + if (inode.private_id != id) + { + /* private_id : The unique identifier used by this file's data stream. + This identifier appears in the owning_obj_id field of j_phys_ext_val_t + records that describe the extents where the data is stored. + For an inode that doesn't have data, the value of this + field is the file-system object's identifier. + */ + // APFS_TYPE_EXTENT allow to link physical address extents. + // we don't support case (private_id != id) + UnsupportedFeature = true; + // return S_FALSE; + } + const UInt32 extraSize = (UInt32)pair.Val.Size() - k_SizeOf_j_inode_val; + if (extraSize != 0) + { + if (extraSize < 4) + return S_FALSE; + /* + struct xf_blob + { + uint16_t xf_num_exts; + uint16_t xf_used_data; + uint8_t xf_data[]; + }; + */ + const Byte *p2 = pair.Val + k_SizeOf_j_inode_val; + const UInt32 xf_num_exts = Get16(p2); + const UInt32 xf_used_data = Get16(p2 + 2); + UInt32 offset = 4 + (UInt32)xf_num_exts * 4; + if (offset + xf_used_data != extraSize) + return S_FALSE; + + PRF(printf(" parent_id = %d", (unsigned)inode.parent_id)); + + for (unsigned k = 0; k < xf_num_exts; k++) + { + // struct x_field + const Byte *p3 = p2 + 4 + k * 4; + const Byte x_type = p3[0]; + // const Byte x_flags = p3[1]; + const UInt32 x_size = Get16(p3 + 2); + const UInt32 x_size_ceil = (x_size + 7) & ~(UInt32)7; + if (offset + x_size_ceil > extraSize) + return S_FALSE; + const Byte *p4 = p2 + offset; + if (x_type == INO_EXT_TYPE_NAME) + { + if (x_size < 2) + return S_FALSE; + inode.PrimaryName.SetFrom_CalcLen((const char *)p4, x_size); + PRF(printf(" PrimaryName = %s", inode.PrimaryName.Ptr())); + if (inode.PrimaryName.Len() != x_size - 1) + HeadersError = true; + // return S_FALSE; + } + else if (x_type == INO_EXT_TYPE_DSTREAM) + { + if (x_size != k_SizeOf_j_dstream) + return S_FALSE; + if (inode.dstream_defined) + return S_FALSE; + inode.dstream.Parse(p4); + inode.dstream_defined = true; + } + else + { + // UnsupportedFeature = true; + // return S_FALSE; + } + offset += x_size_ceil; + } + if (offset != extraSize) + return S_FALSE; + } + + if (!vol.NodeIDs.IsEmpty()) + if (id <= vol.NodeIDs.Back()) + return S_FALSE; + vol.Nodes.Add(inode); + vol.NodeIDs.Add(id); + continue; + } + + if (type == APFS_TYPE_XATTR) + { + PRF(printf(" XATTR")); + + /* + struct j_xattr_key + { + j_key_t hdr; + uint16_t name_len; + uint8_t name[0]; + } + */ + + UInt32 len; + unsigned nameOffset; + { + nameOffset = 8 + 2; + if (pair.Key.Size() < nameOffset + 1) + return S_FALSE; + len = Get16(p + 8); + } + if (nameOffset + len != pair.Key.Size()) + return S_FALSE; + + attr.Clear(); + attr.Name.SetFrom_CalcLen((const char *)p + nameOffset, len); + if (attr.Name.Len() != len - 1) + return S_FALSE; + + PRF(printf(" name=%s", attr.Name.Ptr())); + + const unsigned k_SizeOf_j_xattr_val = 4; + if (pair.Val.Size() < k_SizeOf_j_xattr_val) + return S_FALSE; + /* + struct j_xattr_val + { + uint16_t flags; + uint16_t xdata_len; + uint8_t xdata[0]; + } + */ + attr.flags = Get16(pair.Val); + const UInt32 xdata_len = Get16(pair.Val + 2); + + PRF(printf(" flags=%x xdata_len = %d", + (unsigned)attr.flags, + (unsigned)xdata_len)); + + const Byte *p4 = pair.Val + 4; + + if (k_SizeOf_j_xattr_val + xdata_len != pair.Val.Size()) + return S_FALSE; + if (attr.Is_EMBEDDED()) + attr.Data.CopyFrom(p4, xdata_len); + else if (attr.Is_STREAM()) + { + // why (attr.flags == 0x11) here? (0x11 is undocummented flag) + if (k_SizeOf_j_xattr_val + 8 + k_SizeOf_j_dstream != pair.Val.Size()) + return S_FALSE; + attr.Id = Get64(p4); + attr.dstream.Parse(p4 + 8); + attr.dstream_defined = true; + PRF(printf(" streamID=%d streamSize=%d", (unsigned)attr.Id, (unsigned)attr.dstream.size)); + } + else + { + // unknown attribute + // UnsupportedFeature = true; + // return S_FALSE; + } + + if (vol.NodeIDs.IsEmpty() || + vol.NodeIDs.Back() != id) + { + return S_FALSE; + // UnsupportedFeature = true; + // continue; + } + + CNode &inode = vol.Nodes.Back(); + + if (attr.Name.IsEqualTo("com.apple.fs.symlink")) + { + inode.SymLinkIndex = inode.Attrs.Size(); + if (attr.Is_dstream_OK_for_SymLink()) + needParseAttr = true; + } + else if (attr.Name.IsEqualTo("com.apple.decmpfs")) + { + inode.DecmpfsIndex = inode.Attrs.Size(); + // if (attr.dstream_defined) + needParseAttr = true; + } + else if (attr.Name.IsEqualTo("com.apple.ResourceFork")) + { + inode.ResourceIndex = inode.Attrs.Size(); + } + inode.Attrs.Add(attr); + continue; + } + + if (type == APFS_TYPE_DSTREAM_ID) + { + PRF(printf(" DSTREAM_ID")); + if (pair.Key.Size() != 8) + return S_FALSE; + // j_dstream_id_val_t + if (pair.Val.Size() != 4) + return S_FALSE; + const UInt32 refcnt = Get32(pair.Val); + + // The data stream record can be deleted when its reference count reaches zero. + PRF(printf(" refcnt = %d", (unsigned)refcnt)); + + if (vol.NodeIDs.IsEmpty()) + return S_FALSE; + + if (vol.NodeIDs.Back() != id) + { + // is it possible ? + // continue; + return S_FALSE; + } + + CNode &inode = vol.Nodes.Back(); + + if (inode.refcnt_defined) + return S_FALSE; + + inode.refcnt = refcnt; + inode.refcnt_defined = true; + if (inode.refcnt != (UInt32)inode.nlink) + { + PRF(printf(" refcnt != nlink")); + // is it possible ? + // return S_FALSE; + } + continue; + } + + if (type == APFS_TYPE_FILE_EXTENT) + { + PRF(printf(" FILE_EXTENT")); + /* + struct j_file_extent_key + { + j_key_t hdr; + uint64_t logical_addr; + } + */ + if (pair.Key.Size() != 16) + return S_FALSE; + // The offset within the file's data, in bytes, for the data stored in this extent + const UInt64 logical_addr = Get64(p + 8); + + j_file_extent_val eval; + if (pair.Val.Size() != k_SizeOf_j_file_extent_val) + return S_FALSE; + eval.Parse(pair.Val); + + if (logical_addr != 0) + { + PRF(printf(" logical_addr = %d", (unsigned)logical_addr)); + } + PRF(printf(" len = %8d pos = %8d", + (unsigned)eval.len_and_flags, + (unsigned)eval.phys_block_num + )); + + CExtent ext; + ext.logical_offset = logical_addr; + ext.len_and_flags = eval.len_and_flags; + ext.phys_block_num = eval.phys_block_num; + + if (vol.NodeIDs.IsEmpty()) + return S_FALSE; + if (vol.NodeIDs.Back() != id) + { + // extents for Attributs; + if (vol.SmallNodeIDs.IsEmpty() || + vol.SmallNodeIDs.Back() != id) + { + vol.SmallNodeIDs.Add(id); + vol.SmallNodes.AddNew(); + } + vol.SmallNodes.Back().Extents.Add(ext); + continue; + // return S_FALSE; + } + + CNode &inode = vol.Nodes.Back(); + inode.Extents.Add(ext); + continue; + } + + if (type == APFS_TYPE_DIR_REC) + { + UInt32 len; + unsigned nameOffset; + + if (isHashed) + { + /* + struct j_drec_hashed_key + { + j_key_t hdr; + UInt32 name_len_and_hash; + uint8_t name[0]; + } + */ + nameOffset = 8 + 4; + if (pair.Key.Size() < nameOffset + 1) + return S_FALSE; + const UInt32 name_len_and_hash = Get32(p + 8); + len = name_len_and_hash & J_DREC_LEN_MASK; + } + else + { + /* + struct j_drec_key + { + j_key_t hdr; + UInt16 name_len; // The length of the name, including the final null character + uint8_t name[0]; // The name, represented as a null-terminated UTF-8 string + } + */ + nameOffset = 8 + 2; + if (pair.Key.Size() < nameOffset + 1) + return S_FALSE; + len = Get16(p + 8); + } + if (nameOffset + len != pair.Key.Size()) + return S_FALSE; + + // CItem item; + item.Clear(); + + item.ParentId = id; + item.Name.SetFrom_CalcLen((const char *)p + nameOffset, len); + if (item.Name.Len() != len - 1) + return S_FALSE; + + if (pair.Val.Size() < k_SizeOf_j_drec_val) + return S_FALSE; + + item.Val.Parse(pair.Val); + + if (pair.Val.Size() > k_SizeOf_j_drec_val) + { + // fixme: parse extra fields; + // UnsupportedFeature = true; + // return S_FALSE; + } + + vol.Items.Add(item); + + /* + if (!vol.NodeIDs.IsEmpty() && vol.NodeIDs.Back() == id) + vol.Nodes.Back().NumItems++; + */ + if (id == PRIV_DIR_INO_NUM) + vol.NumItems_In_PrivateDir++; + + PRF(printf(" DIR_REC next=%6d flags=%2x %s", + (unsigned)item.Val.file_id, + (unsigned)item.Val.flags, + item.Name.Ptr())); + continue; + } + + if (type == APFS_TYPE_FILE_INFO) + { + if (pair.Key.Size() != 16) + return S_FALSE; + // j_file_info_key + const UInt64 info_and_lba = Get64(p + 8); + + #define J_FILE_INFO_LBA_MASK 0x00ffffffffffffffUL + // #define J_FILE_INFO_TYPE_MASK 0xff00000000000000UL + #define J_FILE_INFO_TYPE_SHIFT 56 + #define APFS_FILE_INFO_DATA_HASH 1 + + const unsigned infoType = (unsigned)(info_and_lba >> J_FILE_INFO_TYPE_SHIFT); + // address is a paddr_t + const UInt64 lba = info_and_lba & J_FILE_INFO_LBA_MASK; + // j_file_data_hash_val_t + /* Use this field of the union if the type stored in the info_and_lba field of j_file_info_val_t is + APFS_FILE_INFO_DATA_HASH */ + if (infoType != APFS_FILE_INFO_DATA_HASH) + return S_FALSE; + if (pair.Val.Size() != 3 + vol.integrity.HashSize) + return S_FALSE; + /* + struct j_file_data_hash_val + { + UInt16 hashed_len; // The length, in blocks, of the data segment that was hashed. + UInt8 hash_size; // must be consistent with integrity_meta_phys_t::im_hash_type + UInt8 hash[0]; + } + */ + + const unsigned hash_size = pair.Val[2]; + if (hash_size != vol.integrity.HashSize) + return S_FALSE; + + CHashChunk hr; + hr.hashed_len = GetUi16(pair.Val); + if (hr.hashed_len == 0) + return S_FALSE; + memcpy(hr.hash, (const Byte *)pair.Val + 3, vol.integrity.HashSize); + // (hashed_len <= 4) : we have seen + hr.lba = lba; + + PRF(printf(" FILE_INFO lba = %6x, hashed_len=%6d", + (unsigned)lba, + (unsigned)hr.hashed_len)); + + if (vol.Hash_IDs.IsEmpty() || vol.Hash_IDs.Back() != id) + { + vol.Hash_Vectors.AddNew(); + vol.Hash_IDs.Add(id); + } + CStreamHashes &hashes = vol.Hash_Vectors.Back(); + if (hashes.Size() != 0) + { + const CHashChunk &hr_Back = hashes.Back(); + if (lba != hr_Back.lba + ((UInt64)hr_Back.hashed_len << sb.block_size_Log)) + return S_FALSE; + } + hashes.Add(hr); + continue; + } + + if (type == APFS_TYPE_SNAP_METADATA) + { + if (pair.Key.Size() != 8) + return S_FALSE; + PRF(printf(" SNAP_METADATA")); + // continue; + } + + /* SIBLING items are used, if there are more than one hard link to some inode + key : value + parent_id_1 DIR_REC : inode_id, name_1 + parent_id_2 DIR_REC : inode_id, name_2 + inode_id INODE : parent_id_1, name_1 + inode_id SIBLING_LINK sibling_id_1 : parent_id_1, name_1 + inode_id SIBLING_LINK sibling_id_2 : parent_id_2, name_2 + sibling_id_1 SIBLING_MAP : inode_id + sibling_id_2 SIBLING_MAP : inode_id + */ + + if (type == APFS_TYPE_SIBLING_LINK) + { + if (pair.Key.Size() != 16) + return S_FALSE; + if (pair.Val.Size() < 10 + 1) + return S_FALSE; + /* + // struct j_sibling_key + // The sibling's unique identifier. + // This value matches the object identifier for the sibling map record + const UInt64 sibling_id = Get64(p + 8); + // struct j_sibling_val + const Byte *v = pair.Val; + const UInt64 parent_id = Get64(v); // The object identifier for the inode that's the parent directory. + const unsigned name_len = Get16(v + 8); // len including the final null character + if (10 + name_len != pair.Val.Size()) + return S_FALSE; + if (v[10 + name_len - 1] != 0) + return S_FALSE; + AString name ((const char *)(v + 10)); + if (name.Len() != name_len - 1) + return S_FALSE; + PRF(printf(" SIBLING_LINK sibling_id = %6d : parent_id=%6d %s", + (unsigned)sibling_id, (unsigned)parent_id, name.Ptr())); + */ + continue; + } + + if (type == APFS_TYPE_SIBLING_MAP) + { + // struct j_sibling_map_key + // The object identifier in the header is the sibling's unique identifier + if (pair.Key.Size() != 8 || pair.Val.Size() != 8) + return S_FALSE; + /* + // j_sibling_map_val + // The inode number of the underlying file + const UInt64 file_id = Get64(pair.Val); + PRF(printf(" SIBLING_MAP : file_id = %d", (unsigned)file_id)); + */ + continue; + } + + UnsupportedFeature = true; + // return S_FALSE; + } + ProgressVal_NumFilesTotal += vol.Items.Size(); + } + + + if (needParseAttr) + { + /* we read external streams for attributes + So we can get SymLink for GetProperty(kpidSymLink) later */ + FOR_VECTOR (i, vol.Nodes) + { + CNode &node = vol.Nodes[i]; + + FOR_VECTOR (a, node.Attrs) + { + CAttr &attr = node.Attrs[a]; + if (attr.Data.Size() != 0 || !attr.dstream_defined) + continue; + if (a == node.SymLinkIndex) + { + if (!attr.Is_dstream_OK_for_SymLink()) + continue; + } + else + { + if (a != node.DecmpfsIndex + // && a != node.ResourceIndex + ) + continue; + } + // we don't expect big streams here + // largest dstream for Decmpfs attribute is (2Kib+17) + if (attr.dstream.size > ((UInt32)1 << 16)) + continue; + CMyComPtr inStream; + const HRESULT res = GetAttrStream_dstream(OpenInStream, vol, attr, &inStream); + if (res == S_OK && inStream) + { + CByteBuffer buf2; + const size_t size = (size_t)attr.dstream.size; + buf2.Alloc(size); + if (ReadStream_FAIL(inStream, buf2, size) == S_OK) + attr.Data = buf2; + + ProgressVal_Cur += size; + if (OpenCallback) + if (ProgressVal_Cur - ProgressVal_Prev >= (1 << 22)) + { + + RINOK(OpenCallback->SetCompleted( + &ProgressVal_NumFilesTotal, + &ProgressVal_Cur)) + ProgressVal_Prev = ProgressVal_Cur; + } + } + } + + if (node.Has_UNCOMPRESSED_SIZE()) + if (IsViDef(node.DecmpfsIndex)) + { + CAttr &attr = node.Attrs[node.DecmpfsIndex]; + node.CompressHeader.Parse(attr.Data, attr.Data.Size()); + + if (node.CompressHeader.IsCorrect) + if (node.CompressHeader.Method < sizeof(MethodsMask) * 8) + MethodsMask |= ((UInt32)1 << node.CompressHeader.Method); + + if (node.CompressHeader.IsCorrect + && node.CompressHeader.IsSupported + && node.CompressHeader.UnpackSize == node.uncompressed_size) + { + attr.NeedShow = false; + if (node.CompressHeader.IsMethod_Resource() + && IsViDef(node.ResourceIndex)) + node.Attrs[node.ResourceIndex].NeedShow = false; + } + else + { + vol.UnsupportedMethod = true; + } + } + } + } + + const HRESULT res = vol.FillRefs(); + + if (vol.ThereAreErrors()) + HeadersError = true; + if (vol.UnsupportedFeature) + UnsupportedFeature = true; + if (vol.UnsupportedMethod) + UnsupportedMethod = true; + if (vol.NumAltStreams != 0) + ThereAreAltStreams = true; + + return res; +} + + + +HRESULT CVol::FillRefs() +{ + { + Refs.Reserve(Items.Size()); + // we fill Refs[*] + // we + // and set Nodes[*].ItemIndex for Nodes that are directories; + FOR_VECTOR (i, Items) + { + CItem &item = Items[i]; + const UInt64 id = item.Val.file_id; + // if (item.Id == ROOT_DIR_PARENT) continue; + /* for two root folders items + we don't set Node.ItemIndex; */ + // so nodes + if (id == ROOT_DIR_INO_NUM) + continue; + if (id == PRIV_DIR_INO_NUM) + if (NumItems_In_PrivateDir == 0) // if (inode.NumItems == 0) + continue; + + CRef ref; + ref.ItemIndex = i; + // ref.NodeIndex = VI_MINUS1; + ref.ParentRefIndex = VI_MINUS1; + #ifdef APFS_SHOW_ALT_STREAMS + ref.AttrIndex = VI_MINUS1; + #endif + const int index = NodeIDs.FindInSorted(id); + // const int index = -1; // for debug + ref.NodeIndex = (unsigned)index; + item.RefIndex = Refs.Size(); + Refs.Add(ref); + + if (index == -1) + { + NodeNotFound = true; + continue; + // return S_FALSE; + } + + // item.iNode_Index = index; + CNode &inode = Nodes[(unsigned)index]; + if (!item.Val.IsFlags_Unknown() + && inode.Get_Type_From_mode() != item.Val.flags) + { + Refs.Back().NodeIndex = VI_MINUS1; + WrongInodeLink = true; + continue; + // return S_FALSE; + } + + const bool isDir = inode.IsDir(); + if (isDir) + { + if (IsViDef(inode.ItemIndex)) + { + // hard links to dirs are not supported + Refs.Back().NodeIndex = VI_MINUS1; + WrongInodeLink = true; + continue; + } + inode.ItemIndex = i; + } + inode.NumCalcedLinks++; + + #ifdef APFS_SHOW_ALT_STREAMS + if (!isDir) + { + // we use alt streams only for files + const unsigned numAttrs = inode.Attrs.Size(); + if (numAttrs != 0) + { + ref.ParentRefIndex = item.RefIndex; + for (unsigned k = 0; k < numAttrs; k++) + { + // comment it for debug + const CAttr &attr = inode.Attrs[k]; + if (!attr.NeedShow) + continue; + + if (k == inode.SymLinkIndex) + continue; + ref.AttrIndex = k; + NumAltStreams++; + Refs.Add(ref); + /* + if (attr.dstream_defined) + { + const int idIndex = SmallNodeIDs.FindInSorted(attr.Id); + if (idIndex != -1) + SmallNodes[(unsigned)idIndex].NumLinks++; // for debug + } + */ + } + } + } + #endif + } + } + + + { + // fill ghost nodes + CRef ref; + ref.ItemIndex = VI_MINUS1; + ref.ParentRefIndex = VI_MINUS1; + #ifdef APFS_SHOW_ALT_STREAMS + ref.AttrIndex = VI_MINUS1; + #endif + FOR_VECTOR (i, Nodes) + { + if (Nodes[i].NumCalcedLinks != 0) + continue; + const UInt64 id = NodeIDs[i]; + if (id == ROOT_DIR_INO_NUM || + id == PRIV_DIR_INO_NUM) + continue; + ThereAreUnlinkedNodes = true; + ref.NodeIndex = i; + Refs.Add(ref); + } + } + + /* if want to create Refs for ghost data streams, + we need additional CRef::SmallNodeIndex field */ + + { + /* all Nodes[*].ItemIndex were already filled for directory Nodes, + except of "root" and "private-dir" Nodes. */ + + // now we fill Items[*].ParentItemIndex and Refs[*].ParentRefIndex + + UInt64 prev_ID = (UInt64)(Int64)-1; + unsigned prev_ParentItemIndex = VI_MINUS1; + + FOR_VECTOR (i, Items) + { + CItem &item = Items[i]; + const UInt64 id = item.ParentId; // it's id of parent NODE + if (id != prev_ID) + { + prev_ID = id; + prev_ParentItemIndex = VI_MINUS1; + const int index = NodeIDs.FindInSorted(id); + if (index == -1) + continue; + prev_ParentItemIndex = Nodes[(unsigned)index].ItemIndex; + } + + if (IsViNotDef(prev_ParentItemIndex)) + continue; + item.ParentItemIndex = prev_ParentItemIndex; + if (IsViNotDef(item.RefIndex)) + { + // RefIndex is not set for 2 Items (root folders) + // but there is no node for them usually + continue; + } + CRef &ref = Refs[item.RefIndex]; + + /* + // it's optional check that parent_id is set correclty + if (IsViDef(ref.NodeIndex)) + { + const CNode &node = Nodes[ref.NodeIndex]; + if (node.IsDir() && node.parent_id != id) + return S_FALSE; + } + */ + + /* + if (id == ROOT_DIR_INO_NUM) + { + // ItemIndex in Node for ROOT_DIR_INO_NUM was not set bofere + // probably unused now. + ref.ParentRefIndex = VI_MINUS1; + } + else + */ + ref.ParentRefIndex = Items[prev_ParentItemIndex].RefIndex; + } + } + + { + // check for loops + const unsigned numItems = Items.Size(); + if (numItems + 1 == 0) + return S_FALSE; + CUIntArr arr; + arr.Alloc(numItems); + { + for (unsigned i = 0; i < numItems; i++) + arr[i] = 0; + } + for (unsigned i = 0; i < numItems;) + { + unsigned k = i++; + for (;;) + { + const unsigned a = arr[k]; + if (a != 0) + { + if (a == i) + return S_FALSE; + break; + } + arr[k] = i; + k = Items[k].ParentItemIndex; + if (IsViNotDef(k)) + break; + } + } + } + + return S_OK; +} + + + +Z7_class_CHandler_final: + public IInArchive, + public IArchiveGetRawProps, + public IInArchiveGetStream, + public CMyUnknownImp, + public CDatabase +{ + Z7_IFACES_IMP_UNK_3( + IInArchive, + IArchiveGetRawProps, + IInArchiveGetStream) + + CMyComPtr _stream; + int FindHashIndex_for_Item(UInt32 index); +}; + + +Z7_COM7F_IMF(CHandler::Open(IInStream *inStream, + const UInt64 * /* maxCheckStartPosition */, + IArchiveOpenCallback *callback)) +{ + COM_TRY_BEGIN + Close(); + OpenInStream = inStream; + OpenCallback = callback; + RINOK(Open2()) + _stream = inStream; + return S_OK; + COM_TRY_END +} + + +Z7_COM7F_IMF(CHandler::Close()) +{ + _stream.Release(); + Clear(); + return S_OK; +} + + +enum +{ + kpidBytesWritten = kpidUserDefined, + kpidBytesRead, + kpidPrimeName, + kpidParentINode, + kpidAddTime, + kpidGeneration, + kpidBsdFlags + // kpidUncompressedSize +}; + +static const CStatProp kProps[] = +{ + { NULL, kpidPath, VT_BSTR }, + { NULL, kpidSize, VT_UI8 }, + { NULL, kpidPackSize, VT_UI8 }, + { NULL, kpidPosixAttrib, VT_UI4 }, + { NULL, kpidMTime, VT_FILETIME }, + { NULL, kpidCTime, VT_FILETIME }, + { NULL, kpidATime, VT_FILETIME }, + { NULL, kpidChangeTime, VT_FILETIME }, + { "Added Time", kpidAddTime, VT_FILETIME }, + { NULL, kpidMethod, VT_BSTR }, + { NULL, kpidINode, VT_UI4 }, + { NULL, kpidLinks, VT_UI4 }, + { NULL, kpidSymLink, VT_BSTR }, + { NULL, kpidUserId, VT_UI4 }, + { NULL, kpidGroupId, VT_UI4 }, + { NULL, kpidCharacts, VT_BSTR }, + #ifdef APFS_SHOW_ALT_STREAMS + { NULL, kpidIsAltStream, VT_BOOL }, + #endif + { "Parent iNode", kpidParentINode, VT_UI4 }, + { "Primary Name", kpidPrimeName, VT_BSTR }, + { "Generation", kpidGeneration, VT_UI4 }, + { "Written Size", kpidBytesWritten, VT_UI8 }, + { "Read Size", kpidBytesRead, VT_UI8 }, + { "BSD Flags", kpidBsdFlags, VT_UI4 } + // , { "Uncompressed Size", kpidUncompressedSize, VT_UI8 } +}; + + +static const Byte kArcProps[] = +{ + kpidName, + kpidCharacts, + kpidId, + kpidClusterSize, + kpidCTime, + kpidMTime, + kpidComment +}; + +IMP_IInArchive_Props_WITH_NAME +IMP_IInArchive_ArcProps + + +static void ApfsTimeToProp(UInt64 hfsTime, NWindows::NCOM::CPropVariant &prop) +{ + if (hfsTime == 0) + return; + FILETIME ft; + UInt32 ns100; + ApfsTimeToFileTime(hfsTime, ft, ns100); + prop.SetAsTimeFrom_FT_Prec_Ns100(ft, k_PropVar_TimePrec_1ns, ns100); +} + + +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) +{ + COM_TRY_BEGIN + NWindows::NCOM::CPropVariant prop; + const CApfs *apfs = NULL; + if (Vols.Size() == 1) + apfs = &Vols[0].apfs; + switch (propID) + { + case kpidPhySize: + prop = (UInt64)sb.block_count << sb.block_size_Log; + break; + case kpidClusterSize: prop = (UInt32)(sb.block_size); break; + case kpidCharacts: NHfs::MethodsMaskToProp(MethodsMask, prop); break; + case kpidMTime: + if (apfs) + ApfsTimeToProp(apfs->modified_by[0].timestamp, prop); + break; + case kpidCTime: + if (apfs) + ApfsTimeToProp(apfs->formatted_by.timestamp, prop); + break; + case kpidIsTree: prop = true; break; + case kpidErrorFlags: + { + UInt32 flags = 0; + if (HeadersError) flags |= kpv_ErrorFlags_HeadersError; + if (flags != 0) + prop = flags; + break; + } + case kpidWarningFlags: + { + UInt32 flags = 0; + if (UnsupportedFeature) flags |= kpv_ErrorFlags_UnsupportedFeature; + if (UnsupportedMethod) flags |= kpv_ErrorFlags_UnsupportedMethod; + if (flags != 0) + prop = flags; + break; + } + + case kpidName: + { + if (apfs) + { + UString s; + AddVolInternalName_toString(s, *apfs); + s += ".apfs"; + prop = s; + } + break; + } + + case kpidId: + { + char s[32 + 4]; + sb.uuid.SetHex_To_str(s); + prop = s; + break; + } + + case kpidComment: + { + UString s; + { + AddComment_UInt64(s, "block_size", sb.block_size); + + FOR_VECTOR (i, Vols) + { + if (Vols.Size() > 1) + { + if (i != 0) + { + s += "----"; + s.Add_LF(); + } + AddComment_UInt64(s, "Volume", i + 1); + } + Vols[i].AddComment(s); + } + } + prop = s; + break; + } + + #ifdef APFS_SHOW_ALT_STREAMS + case kpidIsAltStream: + prop = ThereAreAltStreams; + // prop = false; // for debug + break; + #endif + } + prop.Detach(value); + return S_OK; + COM_TRY_END +} + + +Z7_COM7F_IMF(CHandler::GetNumRawProps(UInt32 *numProps)) +{ + *numProps = 0; + return S_OK; +} + + +Z7_COM7F_IMF(CHandler::GetRawPropInfo(UInt32 /* index */, BSTR *name, PROPID *propID)) +{ + *name = NULL; + *propID = 0; + return S_OK; +} + + +Z7_COM7F_IMF(CHandler::GetParent(UInt32 index, UInt32 *parent, UInt32 *parentType)) +{ + *parentType = NParentType::kDir; + + const CRef2 &ref2 = Refs2[index]; + const CVol &vol = Vols[ref2.VolIndex]; + UInt32 parentIndex = (UInt32)(Int32)-1; + + if (IsViDef(ref2.RefIndex)) + { + const CRef &ref = vol.Refs[ref2.RefIndex]; + #ifdef APFS_SHOW_ALT_STREAMS + if (ref.IsAltStream()) + *parentType = NParentType::kAltStream; + #endif + if (IsViDef(ref.ParentRefIndex)) + parentIndex = (UInt32)(ref.ParentRefIndex + vol.StartRef2Index); + else if (index != vol.RootRef2Index && IsViDef(vol.RootRef2Index)) + parentIndex = (UInt32)vol.RootRef2Index; + } + + *parent = parentIndex; + return S_OK; +} + + +Z7_COM7F_IMF(CHandler::GetRawProp(UInt32 index, PROPID propID, const void **data, UInt32 *dataSize, UInt32 *propType)) +{ + *data = NULL; + *dataSize = 0; + *propType = 0; + UNUSED_VAR(index) + UNUSED_VAR(propID) + return S_OK; +} + + +static void Utf8Name_to_InterName(const AString &src, UString &dest) +{ + ConvertUTF8ToUnicode(src, dest); + NItemName::NormalizeSlashes_in_FileName_for_OsPath(dest); +} + + +static void AddNodeName(UString &s, const CNode &inode, UInt64 id) +{ + s += "node"; + s.Add_UInt64(id); + if (!inode.PrimaryName.IsEmpty()) + { + s.Add_Dot(); + UString s2; + Utf8Name_to_InterName(inode.PrimaryName, s2); + s += s2; + } +} + + +void CDatabase::GetItemPath(unsigned index, const CNode *inode, NWindows::NCOM::CPropVariant &path) const +{ + const unsigned kNumLevelsMax = (1 << 10); + const unsigned kLenMax = (1 << 12); + UString s; + const CRef2 &ref2 = Refs2[index]; + const CVol &vol = Vols[ref2.VolIndex]; + + if (IsViDef(ref2.RefIndex)) + { + const CRef &ref = vol.Refs[ref2.RefIndex]; + unsigned cur = ref.ItemIndex; + UString s2; + if (IsViNotDef(cur)) + { + if (inode) + AddNodeName(s, *inode, vol.NodeIDs[ref.NodeIndex]); + } + else + { + for (unsigned i = 0;; i++) + { + if (i >= kNumLevelsMax || s.Len() > kLenMax) + { + s.Insert(0, UString("[LONG_PATH]")); + break; + } + const CItem &item = vol.Items[(unsigned)cur]; + Utf8Name_to_InterName(item.Name, s2); + // s2 += "a\\b"; // for debug + s.Insert(0, s2); + cur = item.ParentItemIndex; + if (IsViNotDef(cur)) + break; + // ParentItemIndex was not set for such items + // if (item.ParentId == ROOT_DIR_INO_NUM) break; + s.InsertAtFront(WCHAR_PATH_SEPARATOR); + } + } + + #ifdef APFS_SHOW_ALT_STREAMS + if (IsViDef(ref.AttrIndex) && inode) + { + s.Add_Colon(); + Utf8Name_to_InterName(inode->Attrs[(unsigned)ref.AttrIndex].Name, s2); + // s2 += "a\\b"; // for debug + s += s2; + } + #endif + } + + if (!vol.RootName.IsEmpty()) + { + if (IsViDef(ref2.RefIndex)) + s.InsertAtFront(WCHAR_PATH_SEPARATOR); + s.Insert(0, vol.RootName); + } + + path = s; +} + + + +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) +{ + COM_TRY_BEGIN + NWindows::NCOM::CPropVariant prop; + + const CRef2 &ref2 = Refs2[index]; + const CVol &vol = Vols[ref2.VolIndex]; + + if (IsViNotDef(ref2.RefIndex)) + { + switch (propID) + { + case kpidName: + case kpidPath: + GetItemPath(index, NULL, prop); + break; + case kpidIsDir: + prop = true; + break; + } + prop.Detach(value); + return S_OK; + } + + const CRef &ref = vol.Refs[ref2.RefIndex]; + + const CItem *item = NULL; + if (IsViDef(ref.ItemIndex)) + item = &vol.Items[ref.ItemIndex]; + + const CNode *inode = NULL; + if (IsViDef(ref.NodeIndex)) + inode = &vol.Nodes[ref.NodeIndex]; + + switch (propID) + { + case kpidPath: + GetItemPath(index, inode, prop); + break; + case kpidPrimeName: + { + #ifdef APFS_SHOW_ALT_STREAMS + if (!ref.IsAltStream()) + #endif + if (inode && !inode->PrimaryName.IsEmpty()) + { + UString s; + ConvertUTF8ToUnicode(inode->PrimaryName, s); + /* + // for debug: + if (inode.PrimaryName != item.Name) throw 123456; + */ + prop = s; + } + break; + } + + case kpidName: + { + UString s; + #ifdef APFS_SHOW_ALT_STREAMS + if (ref.IsAltStream()) + { + // if (inode) + { + const CAttr &attr = inode->Attrs[(unsigned)ref.AttrIndex]; + ConvertUTF8ToUnicode(attr.Name, s); + } + } + else + #endif + { + if (item) + ConvertUTF8ToUnicode(item->Name, s); + else if (inode) + AddNodeName(s, *inode, vol.NodeIDs[ref.NodeIndex]); + else + break; + } + // s += "s/1bs\\2"; // for debug: + prop = s; + break; + } + + case kpidSymLink: + #ifdef APFS_SHOW_ALT_STREAMS + if (!ref.IsAltStream()) + #endif + if (inode) + { + if (inode->IsSymLink() && IsViDef(inode->SymLinkIndex)) + { + const CByteBuffer &buf = inode->Attrs[(unsigned)inode->SymLinkIndex].Data; + if (buf.Size() != 0) + { + AString s; + s.SetFrom_CalcLen((const char *)(const Byte *)buf, (unsigned)buf.Size()); + if (s.Len() == buf.Size() - 1) + { + UString u; + ConvertUTF8ToUnicode(s, u); + prop = u; + } + } + } + } + break; + + case kpidSize: + if (inode) + { + UInt64 size = 0; + if (inode->GetSize(ref.GetAttrIndex(), size) || + !inode->IsDir()) + prop = size; + } + break; + + case kpidPackSize: + if (inode) + { + UInt64 size; + if (inode->GetPackSize(ref.GetAttrIndex(), size) || + !inode->IsDir()) + prop = size; + } + break; + + case kpidMethod: + #ifdef APFS_SHOW_ALT_STREAMS + if (!ref.IsAltStream()) + #endif + if (inode) + { + if (inode->CompressHeader.IsCorrect) + inode->CompressHeader.MethodToProp(prop); + else if (IsViDef(inode->DecmpfsIndex)) + prop = "decmpfs"; + else if (!inode->IsDir() && !inode->dstream_defined) + { + if (inode->IsSymLink()) + { + if (IsViDef(inode->SymLinkIndex)) + prop = "symlink"; + } + // else prop = "no_dstream"; + } + } + break; + + /* + case kpidUncompressedSize: + if (inode && inode->Has_UNCOMPRESSED_SIZE()) + prop = inode->uncompressed_size; + break; + */ + + case kpidIsDir: + { + bool isDir = false; + #ifdef APFS_SHOW_ALT_STREAMS + if (!ref.IsAltStream()) + #endif + { + if (inode) + isDir = inode->IsDir(); + else if (item) + isDir = item->Val.IsFlags_Dir(); + } + prop = isDir; + break; + } + + case kpidPosixAttrib: + { + if (inode) + { + UInt32 mode = inode->mode; + #ifdef APFS_SHOW_ALT_STREAMS + if (ref.IsAltStream()) + { + mode &= 0666; // we disable execution + mode |= MY_LIN_S_IFREG; + } + #endif + prop = (UInt32)mode; + } + else if (item && !item->Val.IsFlags_Unknown()) + prop = (UInt32)(item->Val.flags << 12); + break; + } + + case kpidCTime: if (inode) ApfsTimeToProp(inode->create_time, prop); break; + case kpidMTime: if (inode) ApfsTimeToProp(inode->mod_time, prop); break; + case kpidATime: if (inode) ApfsTimeToProp(inode->access_time, prop); break; + case kpidChangeTime: if (inode) ApfsTimeToProp(inode->change_time, prop); break; + case kpidAddTime: if (item) ApfsTimeToProp(item->Val.date_added, prop); break; + + case kpidBytesWritten: + #ifdef APFS_SHOW_ALT_STREAMS + if (!ref.IsAltStream()) + #endif + if (inode && inode->dstream_defined) + prop = inode->dstream.total_bytes_written; + break; + case kpidBytesRead: + #ifdef APFS_SHOW_ALT_STREAMS + if (!ref.IsAltStream()) + #endif + if (inode && inode->dstream_defined) + prop = inode->dstream.total_bytes_read; + break; + + #ifdef APFS_SHOW_ALT_STREAMS + case kpidIsAltStream: + prop = ref.IsAltStream(); + break; + #endif + + case kpidCharacts: + #ifdef APFS_SHOW_ALT_STREAMS + if (!ref.IsAltStream()) + #endif + if (inode) + { + FLAGS_TO_PROP(g_INODE_Flags, (UInt32)inode->internal_flags, prop); + } + break; + + case kpidBsdFlags: + #ifdef APFS_SHOW_ALT_STREAMS + if (!ref.IsAltStream()) + #endif + if (inode) + { + FLAGS_TO_PROP(g_INODE_BSD_Flags, inode->bsd_flags, prop); + } + break; + + case kpidGeneration: + #ifdef APFS_SHOW_ALT_STREAMS + // if (!ref.IsAltStream()) + #endif + if (inode) + prop = inode->write_generation_counter; + break; + + case kpidUserId: + if (inode) + prop = (UInt32)inode->owner; + break; + + case kpidGroupId: + if (inode) + prop = (UInt32)inode->group; + break; + + case kpidLinks: + #ifdef APFS_SHOW_ALT_STREAMS + if (!ref.IsAltStream()) + #endif + if (inode && !inode->IsDir()) + prop = (UInt32)inode->nlink; + break; + + case kpidINode: + #ifdef APFS_SHOW_ALT_STREAMS + // here we can disable iNode for alt stream. + if (!ref.IsAltStream()) + #endif + if (IsViDef(ref.NodeIndex)) + prop = (UInt32)vol.NodeIDs[ref.NodeIndex]; + break; + + case kpidParentINode: + #ifdef APFS_SHOW_ALT_STREAMS + if (!ref.IsAltStream()) + #endif + if (inode) + prop = (UInt32)inode->parent_id; + break; + } + prop.Detach(value); + return S_OK; + COM_TRY_END +} + + +UInt64 CDatabase::GetSize(const UInt32 index) const +{ + const CRef2 &ref2 = Refs2[index]; + const CVol &vol = Vols[ref2.VolIndex]; + if (IsViNotDef(ref2.RefIndex)) + return 0; + const CRef &ref = vol.Refs[ref2.RefIndex]; + if (IsViNotDef(ref.NodeIndex)) + return 0; + const CNode &inode = vol.Nodes[ref.NodeIndex]; + UInt64 size; + if (inode.GetSize(ref.GetAttrIndex(), size)) + return size; + return 0; +} + + +Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testMode, IArchiveExtractCallback *extractCallback)) +{ + COM_TRY_BEGIN + const bool allFilesMode = (numItems == (UInt32)(Int32)-1); + if (allFilesMode) + numItems = Refs2.Size(); + if (numItems == 0) + return S_OK; + UInt32 i; + { + UInt64 totalSize = 0; + for (i = 0; i < numItems; i++) + { + const UInt32 index = allFilesMode ? i : indices[i]; + totalSize += GetSize(index); + } + RINOK(extractCallback->SetTotal(totalSize)) + } + + UInt64 currentTotalSize = 0, currentItemSize = 0; + + CMyComPtr2_Create lps; + lps->Init(extractCallback, false); + + CMyComPtr2_Create copyCoder; + + // We don't know if zlib without Adler is allowed in APFS. + // But zlib without Adler is allowed in HFS. + // So here we allow apfs/zlib without Adler: + NHfs::CDecoder decoder(true); // IsAdlerOptional + + for (i = 0;; i++, currentTotalSize += currentItemSize) + { + lps->InSize = currentTotalSize; + lps->OutSize = currentTotalSize; + RINOK(lps->SetCur()) + + if (i >= numItems) + break; + + const UInt32 index = allFilesMode ? i : indices[i]; + const CRef2 &ref2 = Refs2[index]; + const CVol &vol = Vols[ref2.VolIndex]; + + currentItemSize = GetSize(index); + + int opRes; + { + CMyComPtr realOutStream; + const Int32 askMode = testMode ? + NExtract::NAskMode::kTest : + NExtract::NAskMode::kExtract; + RINOK(extractCallback->GetStream(index, &realOutStream, askMode)) + + if (IsViNotDef(ref2.RefIndex)) + { + RINOK(extractCallback->PrepareOperation(askMode)) + RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)) + continue; + } + + const CRef &ref = vol.Refs[ref2.RefIndex]; + bool isDir = false; + if (IsViDef(ref.NodeIndex)) + isDir = vol.Nodes[ref.NodeIndex].IsDir(); + else if (IsViDef(ref.ItemIndex)) + isDir = + #ifdef APFS_SHOW_ALT_STREAMS + !ref.IsAltStream() && + #endif + vol.Items[ref.ItemIndex].Val.IsFlags_Dir(); + + if (isDir) + { + RINOK(extractCallback->PrepareOperation(askMode)) + RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)) + continue; + } + if (!testMode && !realOutStream) + continue; + RINOK(extractCallback->PrepareOperation(askMode)) + opRes = NExtract::NOperationResult::kDataError; + + if (IsViDef(ref.NodeIndex)) + { + const CNode &inode = vol.Nodes[ref.NodeIndex]; + if ( + #ifdef APFS_SHOW_ALT_STREAMS + !ref.IsAltStream() && + #endif + !inode.dstream_defined + && inode.Extents.IsEmpty() + && inode.Has_UNCOMPRESSED_SIZE() + && inode.uncompressed_size == inode.CompressHeader.UnpackSize) + { + if (inode.CompressHeader.IsSupported) + { + CMyComPtr inStreamFork; + UInt64 forkSize = 0; + const CByteBuffer *decmpfs_Data = NULL; + + if (inode.CompressHeader.IsMethod_Resource()) + { + if (IsViDef(inode.ResourceIndex)) + { + const CAttr &attr = inode.Attrs[inode.ResourceIndex]; + forkSize = attr.GetSize(); + GetAttrStream(_stream, vol, attr, &inStreamFork); + } + } + else + { + const CAttr &attr = inode.Attrs[inode.DecmpfsIndex]; + decmpfs_Data = &attr.Data; + } + + if (inStreamFork || decmpfs_Data) + { + const HRESULT hres = decoder.Extract( + inStreamFork, realOutStream, + forkSize, + inode.CompressHeader, + decmpfs_Data, + currentTotalSize, extractCallback, + opRes); + if (hres != S_FALSE && hres != S_OK) + return hres; + } + } + else + opRes = NExtract::NOperationResult::kUnsupportedMethod; + } + else + { + CMyComPtr inStream; + if (GetStream(index, &inStream) == S_OK && inStream) + { + CMyComPtr2 hashStream; + + if (vol.integrity.Is_SHA256()) + { + const int hashIndex = FindHashIndex_for_Item(index); + if (hashIndex != -1) + { + hashStream.Create_if_Empty(); + hashStream->SetStream(realOutStream); + hashStream->Init(&(vol.Hash_Vectors[(unsigned)hashIndex]), sb.block_size_Log); + } + } + + RINOK(copyCoder.Interface()->Code(inStream, + hashStream.IsDefined() ? + hashStream.Interface() : + realOutStream.Interface(), + NULL, NULL, lps)) + opRes = NExtract::NOperationResult::kDataError; + if (copyCoder->TotalSize == currentItemSize) + { + opRes = NExtract::NOperationResult::kOK; + if (hashStream.IsDefined()) + if (!hashStream->FinalCheck()) + opRes = NExtract::NOperationResult::kCRCError; + } + else if (copyCoder->TotalSize < currentItemSize) + opRes = NExtract::NOperationResult::kUnexpectedEnd; + } + } + } + } + RINOK(extractCallback->SetOperationResult(opRes)) + } + return S_OK; + COM_TRY_END +} + + +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) +{ + *numItems = Refs2.Size(); + return S_OK; +} + + +int CHandler::FindHashIndex_for_Item(UInt32 index) +{ + const CRef2 &ref2 = Refs2[index]; + const CVol &vol = Vols[ref2.VolIndex]; + if (IsViNotDef(ref2.RefIndex)) + return -1; + + const CRef &ref = vol.Refs[ref2.RefIndex]; + if (IsViNotDef(ref.NodeIndex)) + return -1; + const CNode &inode = vol.Nodes[ref.NodeIndex]; + + unsigned attrIndex = ref.GetAttrIndex(); + + if (IsViNotDef(attrIndex) + && !inode.dstream_defined + && inode.IsSymLink()) + { + attrIndex = inode.SymLinkIndex; + if (IsViNotDef(attrIndex)) + return -1; + } + + if (IsViDef(attrIndex)) + { + /* we have seen examples, where hash available for "com.apple.ResourceFork" stream. + these hashes for "com.apple.ResourceFork" stream are for unpacked data. + but the caller here needs packed data of stream. So we don't use hashes */ + return -1; + } + else + { + if (!inode.dstream_defined) + return -1; + const UInt64 id = vol.NodeIDs[ref.NodeIndex]; + return vol.Hash_IDs.FindInSorted(id); + } +} + + +Z7_COM7F_IMF(CHandler::GetStream(UInt32 index, ISequentialInStream **stream)) +{ + *stream = NULL; + + const CRef2 &ref2 = Refs2[index]; + const CVol &vol = Vols[ref2.VolIndex]; + if (IsViNotDef(ref2.RefIndex)) + return S_FALSE; + + const CRef &ref = vol.Refs[ref2.RefIndex]; + if (IsViNotDef(ref.NodeIndex)) + return S_FALSE; + const CNode &inode = vol.Nodes[ref.NodeIndex]; + + const CRecordVector *extents; + UInt64 rem = 0; + + unsigned attrIndex = ref.GetAttrIndex(); + + if (IsViNotDef(attrIndex) + && !inode.dstream_defined + && inode.IsSymLink()) + { + attrIndex = inode.SymLinkIndex; + if (IsViNotDef(attrIndex)) + return S_FALSE; + } + + if (IsViDef(attrIndex)) + { + const CAttr &attr = inode.Attrs[(unsigned)attrIndex]; + if (!attr.dstream_defined) + { + CMyComPtr2 streamTemp; + streamTemp.Create_if_Empty(); + streamTemp->Init(attr.Data, attr.Data.Size(), (IInArchive *)this); + *stream = streamTemp.Detach(); + return S_OK; + } + const int idIndex = vol.SmallNodeIDs.FindInSorted(attr.Id); + if (idIndex != -1) + extents = &vol.SmallNodes[(unsigned)idIndex].Extents; + else + { + const int fext_Index = vol.FEXT_NodeIDs.FindInSorted(attr.Id); + if (fext_Index == -1) + return S_FALSE; + extents = &vol.FEXT_Nodes[(unsigned)fext_Index].Extents; + } + rem = attr.dstream.size; + } + else + { + if (IsViDef(ref.ItemIndex)) + if (vol.Items[ref.ItemIndex].Val.IsFlags_Dir()) + return S_FALSE; + if (inode.IsDir()) + return S_FALSE; + extents = &inode.Extents; + if (inode.dstream_defined) + { + rem = inode.dstream.size; + if (inode.Extents.Size() == 0) + { + const int fext_Index = vol.FEXT_NodeIDs.FindInSorted(vol.NodeIDs[ref.NodeIndex]); + if (fext_Index != -1) + extents = &vol.FEXT_Nodes[(unsigned)fext_Index].Extents; + } + } + else + { + // return S_FALSE; // check it !!! How zero size files are stored with dstream_defined? + } + } + return GetStream2(_stream, extents, rem, stream); +} + + + +HRESULT CDatabase::GetAttrStream(IInStream *apfsInStream, const CVol &vol, + const CAttr &attr, ISequentialInStream **stream) +{ + *stream = NULL; + if (!attr.dstream_defined) + { + CMyComPtr2 streamTemp; + streamTemp.Create_if_Empty(); + streamTemp->Init(attr.Data, attr.Data.Size(), (IInArchive *)this); + *stream = streamTemp.Detach(); + return S_OK; + } + return GetAttrStream_dstream(apfsInStream, vol, attr, stream); +} + + +HRESULT CDatabase::GetAttrStream_dstream( IInStream *apfsInStream, const CVol &vol, + const CAttr &attr, ISequentialInStream **stream) +{ + const CRecordVector *extents; + { + const int idIndex = vol.SmallNodeIDs.FindInSorted(attr.Id); + if (idIndex != -1) + extents = &vol.SmallNodes[(unsigned)idIndex].Extents; + else + { + const int fext_Index = vol.FEXT_NodeIDs.FindInSorted(attr.Id); + if (fext_Index == -1) + return S_FALSE; + extents = &vol.FEXT_Nodes[(unsigned)fext_Index].Extents; + } + } + return GetStream2(apfsInStream, extents, attr.dstream.size, stream); +} + + +HRESULT CDatabase::GetStream2( + IInStream *apfsInStream, + const CRecordVector *extents, UInt64 rem, + ISequentialInStream **stream) +{ + CMyComPtr2 extentStream; + extentStream.Create_if_Empty(); + + UInt64 virt = 0; + FOR_VECTOR (i, *extents) + { + const CExtent &e = (*extents)[i]; + if (virt != e.logical_offset) + return S_FALSE; + const UInt64 len = EXTENT_GET_LEN(e.len_and_flags); + if (len == 0) + { + return S_FALSE; + // continue; + } + if (rem == 0) + return S_FALSE; + UInt64 cur = len; + if (cur > rem) + cur = rem; + CSeekExtent se; + se.Phy = (UInt64)e.phys_block_num << sb.block_size_Log; + se.Virt = virt; + virt += cur; + rem -= cur; + extentStream->Extents.Add(se); + if (rem == 0) + if (i != extents->Size() - 1) + return S_FALSE; + } + + if (rem != 0) + return S_FALSE; + + CSeekExtent se; + se.Phy = 0; + se.Virt = virt; + extentStream->Extents.Add(se); + extentStream->Stream = apfsInStream; + extentStream->Init(); + *stream = extentStream.Detach(); + return S_OK; +} + + +REGISTER_ARC_I( + "APFS", "apfs img", NULL, 0xc3, + k_Signature, + k_SignatureOffset, + 0, + IsArc_APFS) + +}} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/ApmHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/ApmHandler.cpp index 51719d53..e88d2fee 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/ApmHandler.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/ApmHandler.cpp @@ -5,37 +5,62 @@ #include "../../../C/CpuArch.h" #include "../../Common/ComTry.h" -#include "../../Common/Defs.h" -#include "../../Common/IntToString.h" -#include "../../Windows/PropVariant.h" +#include "../../Windows/PropVariantUtils.h" #include "../Common/RegisterArc.h" #include "../Common/StreamUtils.h" #include "HandlerCont.h" -#define Get16(p) GetBe16(p) -#define Get32(p) GetBe32(p) +#define Get32(p) GetBe32a(p) using namespace NWindows; namespace NArchive { + +namespace NDmg { + const char *Find_Apple_FS_Ext(const AString &name); + bool Is_Apple_FS_Or_Unknown(const AString &name); +} + namespace NApm { static const Byte kSig0 = 'E'; static const Byte kSig1 = 'R'; +static const CUInt32PCharPair k_Flags[] = +{ + { 0, "VALID" }, + { 1, "ALLOCATED" }, + { 2, "IN_USE" }, + { 3, "BOOTABLE" }, + { 4, "READABLE" }, + { 5, "WRITABLE" }, + { 6, "OS_PIC_CODE" }, + // { 7, "OS_SPECIFIC_2" }, // "Unused" + { 8, "ChainCompatible" }, // "OS_SPECIFIC_1" + { 9, "RealDeviceDriver" }, + // { 10, "CanChainToNext" }, + { 30, "MOUNTED_AT_STARTUP" }, + { 31, "STARTUP" } +}; + +#define DPME_FLAGS_VALID (1u << 0) +#define DPME_FLAGS_ALLOCATED (1u << 1) + +static const unsigned k_Str_Size = 32; + struct CItem { UInt32 StartBlock; UInt32 NumBlocks; - char Name[32]; - char Type[32]; + UInt32 Flags; // pmPartStatus + char Name[k_Str_Size]; + char Type[k_Str_Size]; /* UInt32 DataStartBlock; UInt32 NumDataBlocks; - UInt32 Status; UInt32 BootStartBlock; UInt32 BootSize; UInt32 BootAddr; @@ -44,152 +69,212 @@ struct CItem char Processor[16]; */ - bool Parse(const Byte *p, UInt32 &numBlocksInMap) + bool Is_Valid_and_Allocated() const + { return (Flags & (DPME_FLAGS_VALID | DPME_FLAGS_ALLOCATED)) != 0; } + + bool Parse(const UInt32 *p32, UInt32 &numBlocksInMap) { - numBlocksInMap = Get32(p + 4); - StartBlock = Get32(p + 8); - NumBlocks = Get32(p + 0xC); - memcpy(Name, p + 0x10, 32); - memcpy(Type, p + 0x30, 32); - if (p[0] != 0x50 || p[1] != 0x4D || p[2] != 0 || p[3] != 0) + if (GetUi32a(p32) != 0x4d50) // "PM" return false; + numBlocksInMap = Get32(p32 + 4 / 4); + StartBlock = Get32(p32 + 8 / 4); + NumBlocks = Get32(p32 + 0xc / 4); + Flags = Get32(p32 + 0x58 / 4); + memcpy(Name, p32 + 0x10 / 4, k_Str_Size); + memcpy(Type, p32 + 0x30 / 4, k_Str_Size); /* DataStartBlock = Get32(p + 0x50); NumDataBlocks = Get32(p + 0x54); - Status = Get32(p + 0x58); - BootStartBlock = Get32(p + 0x5C); + BootStartBlock = Get32(p + 0x5c); BootSize = Get32(p + 0x60); BootAddr = Get32(p + 0x64); if (Get32(p + 0x68) != 0) return false; - BootEntry = Get32(p + 0x6C); + BootEntry = Get32(p + 0x6c); if (Get32(p + 0x70) != 0) return false; BootChecksum = Get32(p + 0x74); - memcpy(Processor, p + 0x78, 16); + memcpy(Processor, p32 + 0x78 / 4, 16); */ return true; } }; -class CHandler: public CHandlerCont + +Z7_class_CHandler_final: public CHandlerCont { + Z7_IFACE_COM7_IMP(IInArchive_Cont) + CRecordVector _items; unsigned _blockSizeLog; - UInt32 _numBlocks; - UInt64 _phySize; bool _isArc; + // UInt32 _numBlocks; + UInt64 _phySize; - HRESULT ReadTables(IInStream *stream); UInt64 BlocksToBytes(UInt32 i) const { return (UInt64)i << _blockSizeLog; } - virtual int GetItem_ExtractInfo(UInt32 index, UInt64 &pos, UInt64 &size) const + virtual int GetItem_ExtractInfo(UInt32 index, UInt64 &pos, UInt64 &size) const Z7_override { const CItem &item = _items[index]; pos = BlocksToBytes(item.StartBlock); size = BlocksToBytes(item.NumBlocks); return NExtract::NOperationResult::kOK; } - -public: - INTERFACE_IInArchive_Cont(;) }; static const UInt32 kSectorSize = 512; +// we support only 4 cluster sizes: 512, 1024, 2048, 4096 */ + API_FUNC_static_IsArc IsArc_Apm(const Byte *p, size_t size) { if (size < kSectorSize) return k_IsArc_Res_NEED_MORE; - if (p[0] != kSig0 || p[1] != kSig1) + if (GetUi32(p + 12) != 0) return k_IsArc_Res_NO; - unsigned i; - for (i = 8; i < 16; i++) - if (p[i] != 0) - return k_IsArc_Res_NO; - UInt32 blockSize = Get16(p + 2); - for (i = 9; ((UInt32)1 << i) != blockSize; i++) - if (i >= 12) - return k_IsArc_Res_NO; - return k_IsArc_Res_YES; + UInt32 v = GetUi32(p); // we read as little-endian + v ^= kSig0 | (unsigned)kSig1 << 8; + if (v & ~((UInt32)0xf << 17)) + return k_IsArc_Res_NO; + if ((0x116u >> (v >> 17)) & 1) + return k_IsArc_Res_YES; + return k_IsArc_Res_NO; } } -HRESULT CHandler::ReadTables(IInStream *stream) +Z7_COM7F_IMF(CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback * /* callback */)) { - Byte buf[kSectorSize]; + COM_TRY_BEGIN + Close(); + + UInt32 buf32[kSectorSize / 4]; + unsigned numPadSectors, blockSizeLog_from_Header; { - RINOK(ReadStream_FALSE(stream, buf, kSectorSize)); - if (buf[0] != kSig0 || buf[1] != kSig1) + // Driver Descriptor Map (DDM) + RINOK(ReadStream_FALSE(stream, buf32, kSectorSize)) + // 8: UInt16 sbDevType : =0 (usually), =1 in Apple Mac OS X 10.3.0 iso + // 10: UInt16 sbDevId : =0 (usually), =1 in Apple Mac OS X 10.3.0 iso + // 12: UInt32 sbData : =0 + if (buf32[3] != 0) + return S_FALSE; + UInt32 v = GetUi32a(buf32); // we read as little-endian + v ^= kSig0 | (unsigned)kSig1 << 8; + if (v & ~((UInt32)0xf << 17)) + return S_FALSE; + v >>= 16; + if (v == 0) return S_FALSE; - UInt32 blockSize = Get16(buf + 2); - unsigned i; - for (i = 9; ((UInt32)1 << i) != blockSize; i++) - if (i >= 12) - return S_FALSE; - _blockSizeLog = i; - _numBlocks = Get32(buf + 4); - for (i = 8; i < 16; i++) - if (buf[i] != 0) - return S_FALSE; + if (v & (v - 1)) + return S_FALSE; + // v == { 16,8,4,2 } : block size (x256 bytes) + const unsigned a = +#if 1 + (0x30210u >> v) & 3; +#else + 0; // for debug : hardcoded switch to 512-bytes mode +#endif + numPadSectors = (1u << a) - 1; + _blockSizeLog = blockSizeLog_from_Header = 9 + a; } - unsigned numSkips = (unsigned)1 << (_blockSizeLog - 9); - for (unsigned j = 1; j < numSkips; j++) +/* + some APMs (that are ".iso" macOS installation files) contain + (blockSizeLog == 11) in DDM header, + and contain 2 overlapping maps: + 1) map for 512-bytes-step + 2) map for 2048-bytes-step + 512-bytes-step map is correct. + 2048-bytes-step map can be incorrect in some cases. + + macos 8 / OSX DP2 iso: + There is shared "hfs" item in both maps. + And correct (offset/size) values for "hfs" partition + can be calculated only in 512-bytes mode (ignoring blockSizeLog == 11). + But some records (Macintosh.Apple_Driver*_) + can be correct on both modes: 512-bytes mode / 2048-bytes-step. + + macos 921 ppc / Apple Mac OS X 10.3.0 iso: + Both maps are correct. + If we use 512-bytes-step, each 4th item is (Apple_Void) with zero size. + And these zero size (Apple_Void) items will be first items in 2048-bytes-step map. +*/ + +// we define Z7_APM_SWITCH_TO_512_BYTES, because +// we want to support old MACOS APMs that contain correct value only +// for 512-bytes-step mode +#define Z7_APM_SWITCH_TO_512_BYTES + + const UInt32 numBlocks_from_Header = Get32(buf32 + 1); + UInt32 numBlocks = 0; { - RINOK(ReadStream_FALSE(stream, buf, kSectorSize)); + for (unsigned k = 0; k < numPadSectors; k++) + { + RINOK(ReadStream_FALSE(stream, buf32, kSectorSize)) +#ifdef Z7_APM_SWITCH_TO_512_BYTES + if (k == 0) + { + if (GetUi32a(buf32) == 0x4d50 // "PM" + // && (Get32(buf32 + 0x58 / 4) & 1) // Flags::VALID + // some old APMs don't use VALID flag for Apple_partition_map item + && Get32(buf32 + 8 / 4) == 1) // StartBlock + { + // we switch the mode to 512-bytes-step map reading: + numPadSectors = 0; + _blockSizeLog = 9; + break; + } + } +#endif + } } - UInt32 numBlocksInMap = 0; - for (unsigned i = 0;;) { - RINOK(ReadStream_FALSE(stream, buf, kSectorSize)); +#ifdef Z7_APM_SWITCH_TO_512_BYTES + if (i != 0 || _blockSizeLog == blockSizeLog_from_Header) +#endif + { + RINOK(ReadStream_FALSE(stream, buf32, kSectorSize)) + } CItem item; - - UInt32 numBlocksInMap2 = 0; - if (!item.Parse(buf, numBlocksInMap2)) + UInt32 numBlocksInMap = 0; + if (!item.Parse(buf32, numBlocksInMap)) return S_FALSE; - if (i == 0) - { - numBlocksInMap = numBlocksInMap2; - if (numBlocksInMap > (1 << 8)) - return S_FALSE; - } - else if (numBlocksInMap2 != numBlocksInMap) + // v24.09: we don't check that all entries have same (numBlocksInMap) values, + // because some APMs have different (numBlocksInMap) values, if (Apple_Void) is used. + if (numBlocksInMap > (1 << 8) || numBlocksInMap <= i) return S_FALSE; - UInt32 finish = item.StartBlock + item.NumBlocks; + const UInt32 finish = item.StartBlock + item.NumBlocks; if (finish < item.StartBlock) return S_FALSE; - _numBlocks = MyMax(_numBlocks, finish); + if (numBlocks < finish) + numBlocks = finish; _items.Add(item); - for (unsigned j = 1; j < numSkips; j++) + if (numPadSectors != 0) { - RINOK(ReadStream_FALSE(stream, buf, kSectorSize)); + RINOK(stream->Seek(numPadSectors << 9, STREAM_SEEK_CUR, NULL)) } if (++i == numBlocksInMap) break; } - _phySize = BlocksToBytes(_numBlocks); + _phySize = BlocksToBytes(numBlocks); + // _numBlocks = numBlocks; + const UInt64 physSize = (UInt64)numBlocks_from_Header << blockSizeLog_from_Header; + if (_phySize < physSize) + _phySize = physSize; _isArc = true; - return S_OK; -} - -STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback * /* callback */) -{ - COM_TRY_BEGIN - Close(); - RINOK(ReadTables(stream)); _stream = stream; + return S_OK; COM_TRY_END } -STDMETHODIMP CHandler::Close() + +Z7_COM7F_IMF(CHandler::Close()) { _isArc = false; _phySize = 0; @@ -198,30 +283,31 @@ STDMETHODIMP CHandler::Close() return S_OK; } + static const Byte kProps[] = { kpidPath, kpidSize, - kpidOffset + kpidOffset, + kpidCharacts + // , kpidCpu }; static const Byte kArcProps[] = { kpidClusterSize + // , kpidNumBlocks }; IMP_IInArchive_Props IMP_IInArchive_ArcProps -static AString GetString(const char *s) +static void GetString(AString &dest, const char *src) { - AString res; - for (unsigned i = 0; i < 32 && s[i] != 0; i++) - res += s[i]; - return res; + dest.SetFrom_CalcLen(src, k_Str_Size); } -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; @@ -232,24 +318,28 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) int mainIndex = -1; FOR_VECTOR (i, _items) { - AString s = GetString(_items[i].Type); - if (s != "Apple_Free" && - s != "Apple_partition_map") + const CItem &item = _items[i]; + if (!item.Is_Valid_and_Allocated()) + continue; + AString s; + GetString(s, item.Type); + if (NDmg::Is_Apple_FS_Or_Unknown(s)) { - if (mainIndex >= 0) + if (mainIndex != -1) { mainIndex = -1; break; } - mainIndex = i; + mainIndex = (int)i; } } - if (mainIndex >= 0) - prop = (UInt32)mainIndex; + if (mainIndex != -1) + prop = (UInt32)(Int32)mainIndex; break; } case kpidClusterSize: prop = (UInt32)1 << _blockSizeLog; break; case kpidPhySize: prop = _phySize; break; + // case kpidNumBlocks: prop = _numBlocks; break; case kpidErrorFlags: { @@ -264,13 +354,13 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) COM_TRY_END } -STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) { *numItems = _items.Size(); return S_OK; } -STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; @@ -279,29 +369,41 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val { case kpidPath: { - AString s = GetString(item.Name); + AString s; + GetString(s, item.Name); if (s.IsEmpty()) + s.Add_UInt32(index); + AString type; + GetString(type, item.Type); { - char s2[32]; - ConvertUInt32ToString(index, s2); - s = s2; + const char *ext = NDmg::Find_Apple_FS_Ext(type); + if (ext) + type = ext; } - AString type = GetString(item.Type); - if (type == "Apple_HFS") - type = "hfs"; if (!type.IsEmpty()) { - s += '.'; + s.Add_Dot(); s += type; } prop = s; break; } +/* + case kpidCpu: + { + AString s; + s.SetFrom_CalcLen(item.Processor, sizeof(item.Processor)); + if (!s.IsEmpty()) + prop = s; + break; + } +*/ case kpidSize: case kpidPackSize: prop = BlocksToBytes(item.NumBlocks); break; case kpidOffset: prop = BlocksToBytes(item.StartBlock); break; + case kpidCharacts: FLAGS_TO_PROP(k_Flags, item.Flags, prop); break; } prop.Detach(value); return S_OK; @@ -311,7 +413,7 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val static const Byte k_Signature[] = { kSig0, kSig1 }; REGISTER_ARC_I( - "APM", "apm", 0, 0xD4, + "APM", "apm", NULL, 0xD4, k_Signature, 0, 0, diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/ArHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/ArHandler.cpp index 83ed5a02..9f672a94 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/ArHandler.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/ArHandler.cpp @@ -56,10 +56,8 @@ BSD (Mac OS X) variant: */ static const unsigned kSignatureLen = 8; - -#define SIGNATURE { '!', '<', 'a', 'r', 'c', 'h', '>', 0x0A } - -static const Byte kSignature[kSignatureLen] = SIGNATURE; +static const Byte kSignature[kSignatureLen] = + { '!', '<', 'a', 'r', 'c', 'h', '>', 0x0A }; static const unsigned kNameSize = 16; static const unsigned kTimeSize = 12; @@ -136,16 +134,16 @@ class CInArchive HRESULT Open(IInStream *inStream); HRESULT SkipData(UInt64 dataSize) { - return m_Stream->Seek(dataSize + (dataSize & 1), STREAM_SEEK_CUR, &Position); + return m_Stream->Seek((Int64)(dataSize + (dataSize & 1)), STREAM_SEEK_CUR, &Position); } }; HRESULT CInArchive::Open(IInStream *inStream) { SubType = kSubType_None; - RINOK(inStream->Seek(0, STREAM_SEEK_CUR, &Position)); + RINOK(InStream_GetPos(inStream, Position)) char signature[kSignatureLen]; - RINOK(ReadStream_FALSE(inStream, signature, kSignatureLen)); + RINOK(ReadStream_FALSE(inStream, signature, kSignatureLen)) Position += kSignatureLen; if (memcmp(signature, kSignature, kSignatureLen) != 0) return S_FALSE; @@ -170,8 +168,8 @@ static bool OctalToNumber32(const char *s, unsigned size, UInt32 &res) res = 0; char sz[32]; size = RemoveTailSpaces(sz, s, size); - if (size == 0) - return true; // some items doesn't contaion any numbers + if (size == 0 || strcmp(sz, "-1") == 0) + return true; // some items don't contain any numbers const char *end; UInt64 res64 = ConvertOctStringToUInt64(sz, &end); if ((unsigned)(end - sz) != size) @@ -185,8 +183,8 @@ static bool DecimalToNumber(const char *s, unsigned size, UInt64 &res) res = 0; char sz[32]; size = RemoveTailSpaces(sz, s, size); - if (size == 0) - return true; // some items doesn't contaion any numbers + if (size == 0 || strcmp(sz, "-1") == 0) + return true; // some items don't contain any numbers const char *end; res = ConvertStringToUInt64(sz, &end); return ((unsigned)(end - sz) == size); @@ -215,7 +213,7 @@ HRESULT CInArchive::GetNextItem(CItem &item, bool &filled) size_t processedSize = sizeof(header); item.HeaderPos = Position; item.HeaderSize = kHeaderSize; - RINOK(ReadStream(m_Stream, header, &processedSize)); + RINOK(ReadStream(m_Stream, header, &processedSize)) if (processedSize != sizeof(header)) return S_OK; if (header[kHeaderSize - 2] != 0x60 || @@ -235,7 +233,7 @@ HRESULT CInArchive::GetNextItem(CItem &item, bool &filled) cur[3] != 0) { // BSD variant - RIF(DecimalToNumber32(cur + 3, kNameSize - 3 , longNameLen)); + RIF(DecimalToNumber32(cur + 3, kNameSize - 3 , longNameLen)) if (longNameLen >= (1 << 12)) longNameLen = 0; } @@ -247,11 +245,11 @@ HRESULT CInArchive::GetNextItem(CItem &item, bool &filled) } cur += kNameSize; - RIF(DecimalToNumber32(cur, kTimeSize, item.MTime)); cur += kTimeSize; - RIF(DecimalToNumber32(cur, kUserSize, item.User)); cur += kUserSize; - RIF(DecimalToNumber32(cur, kUserSize, item.Group)); cur += kUserSize; - RIF(OctalToNumber32(cur, kModeSize, item.Mode)); cur += kModeSize; - RIF(DecimalToNumber(cur, kSizeSize, item.Size)); cur += kSizeSize; + RIF(DecimalToNumber32(cur, kTimeSize, item.MTime)) cur += kTimeSize; + RIF(DecimalToNumber32(cur, kUserSize, item.User)) cur += kUserSize; + RIF(DecimalToNumber32(cur, kUserSize, item.Group)) cur += kUserSize; + RIF(OctalToNumber32(cur, kModeSize, item.Mode)) cur += kModeSize; + RIF(DecimalToNumber(cur, kSizeSize, item.Size)) cur += kSizeSize; if (longNameLen != 0 && longNameLen <= item.Size) { @@ -260,7 +258,7 @@ HRESULT CInArchive::GetNextItem(CItem &item, bool &filled) char *s = item.Name.GetBuf(longNameLen); HRESULT res = ReadStream(m_Stream, s, &processedSize); item.Name.ReleaseBuf_CalcLen(longNameLen); - RINOK(res); + RINOK(res) if (processedSize != longNameLen) return S_OK; item.Size -= longNameLen; @@ -272,24 +270,22 @@ HRESULT CInArchive::GetNextItem(CItem &item, bool &filled) return S_OK; } -class CHandler: - public IInArchive, - public IInArchiveGetStream, - public CMyUnknownImp -{ + +Z7_CLASS_IMP_CHandler_IInArchive_1( + IInArchiveGetStream +) + bool _isArc; CObjectVector _items; CMyComPtr _stream; - Int32 _mainSubfile; UInt64 _phySize; + Int32 _mainSubfile; EType _type; ESubType _subType; int _longNames_FileIndex; - AString _libFiles[2]; unsigned _numLibFiles; AString _errorMessage; - bool _isArc; - + AString _libFiles[2]; void UpdateErrorMessage(const char *s); @@ -298,16 +294,12 @@ class CHandler: int FindItem(UInt32 offset) const; HRESULT AddFunc(UInt32 offset, const Byte *data, size_t size, size_t &pos); HRESULT ParseLibSymbols(IInStream *stream, unsigned fileIndex); -public: - MY_UNKNOWN_IMP2(IInArchive, IInArchiveGetStream) - INTERFACE_IInArchive(;) - STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream); }; void CHandler::UpdateErrorMessage(const char *s) { if (!_errorMessage.IsEmpty()) - _errorMessage += '\n'; + _errorMessage.Add_LF(); _errorMessage += s; } @@ -322,8 +314,8 @@ static const Byte kProps[] = kpidSize, kpidMTime, kpidPosixAttrib, - kpidUser, - kpidGroup + kpidUserId, + kpidGroupId }; IMP_IInArchive_Props @@ -333,7 +325,7 @@ HRESULT CHandler::ParseLongNames(IInStream *stream) { unsigned i; for (i = 0; i < _items.Size(); i++) - if (_items[i].Name == "//") + if (_items[i].Name.IsEqualTo("//")) break; if (i == _items.Size()) return S_OK; @@ -342,11 +334,11 @@ HRESULT CHandler::ParseLongNames(IInStream *stream) const CItem &item = _items[fileIndex]; if (item.Size > ((UInt32)1 << 30)) return S_FALSE; - RINOK(stream->Seek(item.GetDataPos(), STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekSet(stream, item.GetDataPos())) const size_t size = (size_t)item.Size; CByteArr p(size); - RINOK(ReadStream_FALSE(stream, p, size)); + RINOK(ReadStream_FALSE(stream, p, size)) for (i = 0; i < _items.Size(); i++) { @@ -365,15 +357,15 @@ HRESULT CHandler::ParseLongNames(IInStream *stream) { if (pos >= size) return S_FALSE; - char c = p[pos]; + const Byte c = p[pos]; if (c == 0 || c == 0x0A) break; pos++; } - item2.Name.SetFrom((const char *)(p + start), pos - start); + item2.Name.SetFrom((const char *)(p + start), (unsigned)(pos - start)); } - _longNames_FileIndex = fileIndex; + _longNames_FileIndex = (int)fileIndex; return S_OK; } @@ -386,7 +378,7 @@ void CHandler::ChangeDuplicateNames() if (item.Name[0] == '/') continue; CItem &prev = _items[i - 1]; - if (item.Name == prev.Name) + if (item.Name.IsEqualTo(prev.Name)) { if (prev.SameNameIndex < 0) prev.SameNameIndex = 0; @@ -399,7 +391,7 @@ void CHandler::ChangeDuplicateNames() if (item.SameNameIndex < 0) continue; char sz[32]; - ConvertUInt32ToString(item.SameNameIndex + 1, sz); + ConvertUInt32ToString((unsigned)item.SameNameIndex + 1, sz); unsigned len = MyStringLen(sz); sz[len++] = '.'; sz[len] = 0; @@ -412,10 +404,10 @@ int CHandler::FindItem(UInt32 offset) const unsigned left = 0, right = _items.Size(); while (left != right) { - unsigned mid = (left + right) / 2; - UInt64 midVal = _items[mid].HeaderPos; + const unsigned mid = (left + right) / 2; + const UInt64 midVal = _items[mid].HeaderPos; if (offset == midVal) - return mid; + return (int)mid; if (offset < midVal) right = mid; else @@ -426,7 +418,7 @@ int CHandler::FindItem(UInt32 offset) const HRESULT CHandler::AddFunc(UInt32 offset, const Byte *data, size_t size, size_t &pos) { - int fileIndex = FindItem(offset); + const int fileIndex = FindItem(offset); if (fileIndex < (int)0) return S_FALSE; @@ -439,14 +431,14 @@ HRESULT CHandler::AddFunc(UInt32 offset, const Byte *data, size_t size, size_t & while (data[i++] != 0); AString &s = _libFiles[_numLibFiles]; - const AString &name = _items[fileIndex].Name; + const AString &name = _items[(unsigned)fileIndex].Name; s += name; if (!name.IsEmpty() && name.Back() == '/') s.DeleteBack(); s += " "; s += (const char *)(data + pos); - s += (char)0xD; - s += (char)0xA; + // s.Add_Char((char)0xD); + s.Add_LF(); pos = i; return S_OK; } @@ -456,42 +448,44 @@ static UInt32 Get32(const Byte *p, unsigned be) { if (be) return GetBe32(p); ret HRESULT CHandler::ParseLibSymbols(IInStream *stream, unsigned fileIndex) { CItem &item = _items[fileIndex]; - if (item.Name != "/" && - item.Name != "__.SYMDEF" && - item.Name != "__.SYMDEF SORTED") + if (!item.Name.IsEqualTo("/") && + !item.Name.IsEqualTo("__.SYMDEF") && + !item.Name.IsEqualTo("__.SYMDEF SORTED")) return S_OK; if (item.Size > ((UInt32)1 << 30) || item.Size < 4) return S_OK; - RINOK(stream->Seek(item.GetDataPos(), STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekSet(stream, item.GetDataPos())) size_t size = (size_t)item.Size; CByteArr p(size); - RINOK(ReadStream_FALSE(stream, p, size)); + RINOK(ReadStream_FALSE(stream, p, size)) size_t pos = 0; - if (item.Name != "/") + if (!item.Name.IsEqualTo("/")) { - // __.SYMDEF parsing (BSD) + // "__.SYMDEF" parsing (BSD) unsigned be; for (be = 0; be < 2; be++) { - UInt32 tableSize = Get32(p, be); + const UInt32 tableSize = Get32(p, be); pos = 4; if (size - pos < tableSize || (tableSize & 7) != 0) continue; size_t namesStart = pos + tableSize; - UInt32 namesSize = Get32(p + namesStart, be); + if (size - namesStart < 4) + continue; + const UInt32 namesSize = Get32(p.ConstData() + namesStart, be); namesStart += 4; - if (namesStart > size || namesStart + namesSize != size) + if (namesStart + namesSize != size) continue; - UInt32 numSymbols = tableSize >> 3; + const UInt32 numSymbols = tableSize >> 3; UInt32 i; for (i = 0; i < numSymbols; i++, pos += 8) { size_t namePos = Get32(p + pos, be); - UInt32 offset = Get32(p + pos + 4, be); + const UInt32 offset = Get32(p + pos + 4, be); if (AddFunc(offset, p + namesStart, namesSize, namePos) != S_OK) break; } @@ -509,7 +503,7 @@ HRESULT CHandler::ParseLibSymbols(IInStream *stream, unsigned fileIndex) else if (_numLibFiles == 0) { // archive symbol table (GNU) - UInt32 numSymbols = GetBe32(p); + const UInt32 numSymbols = GetBe32(p); pos = 4; if (numSymbols > (size - pos) / 4) return S_FALSE; @@ -517,15 +511,15 @@ HRESULT CHandler::ParseLibSymbols(IInStream *stream, unsigned fileIndex) for (UInt32 i = 0; i < numSymbols; i++) { - UInt32 offset = GetBe32(p + 4 + i * 4); - RINOK(AddFunc(offset, p, size, pos)); + const UInt32 offset = GetBe32(p + 4 + (size_t)i * 4); + RINOK(AddFunc(offset, p, size, pos)) } _type = kType_ALib; } else { // Second linker file (Microsoft .lib) - UInt32 numMembers = GetUi32(p); + const UInt32 numMembers = GetUi32(p); pos = 4; if (numMembers > (size - pos) / 4) return S_FALSE; @@ -533,7 +527,7 @@ HRESULT CHandler::ParseLibSymbols(IInStream *stream, unsigned fileIndex) if (size - pos < 4) return S_FALSE; - UInt32 numSymbols = GetUi32(p + pos); + const UInt32 numSymbols = GetUi32(p + pos); pos += 4; if (numSymbols > (size - pos) / 2) return S_FALSE; @@ -542,57 +536,56 @@ HRESULT CHandler::ParseLibSymbols(IInStream *stream, unsigned fileIndex) for (UInt32 i = 0; i < numSymbols; i++) { - // index is 1-based. So 32-bit numSymbols field works as item[0] - UInt32 index = GetUi16(p + indexStart + i * 2); + // index is 1-based. So 32-bit numMembers field works as item[0] + const UInt32 index = GetUi16(p + indexStart + (size_t)i * 2); if (index == 0 || index > numMembers) return S_FALSE; - UInt32 offset = GetUi32(p + index * 4); - RINOK(AddFunc(offset, p, size, pos)); + const UInt32 offset = GetUi32(p + (size_t)index * 4); + RINOK(AddFunc(offset, p, size, pos)) } _type = kType_Lib; } // size can be 2-byte aligned in linux files if (pos != size && pos + (pos & 1) != size) return S_FALSE; - item.TextFileIndex = _numLibFiles++; + item.TextFileIndex = (int)(_numLibFiles++); return S_OK; } -STDMETHODIMP CHandler::Open(IInStream *stream, +Z7_COM7F_IMF(CHandler::Open(IInStream *stream, const UInt64 * /* maxCheckStartPosition */, - IArchiveOpenCallback *callback) + IArchiveOpenCallback *callback)) { COM_TRY_BEGIN { Close(); - UInt64 fileSize = 0; - RINOK(stream->Seek(0, STREAM_SEEK_END, &fileSize)); - RINOK(stream->Seek(0, STREAM_SEEK_SET, NULL)); + UInt64 fileSize; + RINOK(InStream_AtBegin_GetSize(stream, fileSize)) CInArchive arc; - RINOK(arc.Open(stream)); + RINOK(arc.Open(stream)) if (callback) { - RINOK(callback->SetTotal(NULL, &fileSize)); - UInt64 numFiles = _items.Size(); - RINOK(callback->SetCompleted(&numFiles, &arc.Position)); + RINOK(callback->SetTotal(NULL, &fileSize)) + const UInt64 numFiles = _items.Size(); + RINOK(callback->SetCompleted(&numFiles, &arc.Position)) } CItem item; for (;;) { bool filled; - RINOK(arc.GetNextItem(item, filled)); + RINOK(arc.GetNextItem(item, filled)) if (!filled) break; _items.Add(item); arc.SkipData(item.Size); if (callback && (_items.Size() & 0xFF) == 0) { - UInt64 numFiles = _items.Size(); - RINOK(callback->SetCompleted(&numFiles, &arc.Position)); + const UInt64 numFiles = _items.Size(); + RINOK(callback->SetCompleted(&numFiles, &arc.Position)) } } @@ -610,21 +603,23 @@ STDMETHODIMP CHandler::Open(IInStream *stream, if (ParseLongNames(stream) != S_OK) UpdateErrorMessage("Long file names parsing error"); if (_longNames_FileIndex >= 0) - _items.Delete(_longNames_FileIndex); + _items.Delete((unsigned)_longNames_FileIndex); - if (!_items.IsEmpty() && _items[0].Name == "debian-binary") + if (!_items.IsEmpty() && _items[0].Name.IsEqualTo("debian-binary")) { _type = kType_Deb; _items.DeleteFrontal(1); for (unsigned i = 0; i < _items.Size(); i++) if (_items[i].Name.IsPrefixedBy("data.tar.")) + { if (_mainSubfile < 0) - _mainSubfile = i; + _mainSubfile = (int)i; else { _mainSubfile = -1; break; } + } } else { @@ -649,7 +644,7 @@ STDMETHODIMP CHandler::Open(IInStream *stream, COM_TRY_END } -STDMETHODIMP CHandler::Close() +Z7_COM7F_IMF(CHandler::Close()) { _isArc = false; _phySize = 0; @@ -670,13 +665,13 @@ STDMETHODIMP CHandler::Close() return S_OK; } -STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) { *numItems = _items.Size(); return S_OK; } -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; @@ -688,7 +683,7 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) case kpidShortComment: case kpidSubType: { - AString s = k_TypeExtionsions[(unsigned)_type]; + AString s (k_TypeExtionsions[(unsigned)_type]); if (_subType == kSubType_BSD) s += ":BSD"; prop = s; @@ -709,7 +704,7 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) COM_TRY_END } -STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NWindows::NCOM::CPropVariant prop; @@ -720,7 +715,7 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val if (item.TextFileIndex >= 0) prop = (item.TextFileIndex == 0) ? "1.txt" : "2.txt"; else - prop = (const wchar_t *)NItemName::GetOSName2(MultiByteToUnicodeString(item.Name, CP_OEMCP)); + prop = (const wchar_t *)NItemName::GetOsPath_Remove_TailSlash(MultiByteToUnicodeString(item.Name, CP_OEMCP)); break; case kpidSize: case kpidPackSize: @@ -732,15 +727,11 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val case kpidMTime: { if (item.MTime != 0) - { - FILETIME fileTime; - NTime::UnixTimeToFileTime(item.MTime, fileTime); - prop = fileTime; - } + PropVariant_SetFrom_UnixTime(prop, item.MTime); break; } - case kpidUser: if (item.User != 0) prop = item.User; break; - case kpidGroup: if (item.Group != 0) prop = item.Group; break; + case kpidUserId: if (item.User != 0) prop = item.User; break; + case kpidGroupId: if (item.Group != 0) prop = item.Group; break; case kpidPosixAttrib: if (item.TextFileIndex < 0) prop = item.Mode; @@ -751,11 +742,11 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val COM_TRY_END } -STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, - Int32 testMode, IArchiveExtractCallback *extractCallback) +Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testMode, IArchiveExtractCallback *extractCallback)) { COM_TRY_BEGIN - bool allFilesMode = (numItems == (UInt32)(Int32)-1); + const bool allFilesMode = (numItems == (UInt32)(Int32)-1); if (allFilesMode) numItems = _items.Size(); if (numItems == 0) @@ -773,63 +764,62 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, UInt64 currentTotalSize = 0; - NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder(); - CMyComPtr copyCoder = copyCoderSpec; - - CLocalProgress *lps = new CLocalProgress; - CMyComPtr progress = lps; + CMyComPtr2_Create copyCoder; + CMyComPtr2_Create lps; lps->Init(extractCallback, false); - CLimitedSequentialInStream *streamSpec = new CLimitedSequentialInStream; - CMyComPtr inStream(streamSpec); - streamSpec->SetStream(_stream); + CMyComPtr2_Create inStream; + inStream->SetStream(_stream); - for (i = 0; i < numItems; i++) + for (i = 0;; i++) { lps->InSize = lps->OutSize = currentTotalSize; - RINOK(lps->SetCur()); + RINOK(lps->SetCur()) + if (i >= numItems) + break; + Int32 opRes; + { CMyComPtr realOutStream; - Int32 askMode = testMode ? + const Int32 askMode = testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; - Int32 index = allFilesMode ? i : indices[i]; + const UInt32 index = allFilesMode ? i : indices[i]; const CItem &item = _items[index]; - RINOK(extractCallback->GetStream(index, &realOutStream, askMode)); + RINOK(extractCallback->GetStream(index, &realOutStream, askMode)) currentTotalSize += (item.TextFileIndex >= 0) ? (UInt64)_libFiles[(unsigned)item.TextFileIndex].Len() : item.Size; if (!testMode && !realOutStream) continue; - RINOK(extractCallback->PrepareOperation(askMode)); + RINOK(extractCallback->PrepareOperation(askMode)) if (testMode) { - RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)); + RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)) continue; } - bool isOk = true; + opRes = NExtract::NOperationResult::kOK; if (item.TextFileIndex >= 0) { const AString &f = _libFiles[(unsigned)item.TextFileIndex]; if (realOutStream) - RINOK(WriteStream(realOutStream, f, f.Len())); + RINOK(WriteStream(realOutStream, f, f.Len())) } else { - RINOK(_stream->Seek(item.GetDataPos(), STREAM_SEEK_SET, NULL)); - streamSpec->Init(item.Size); - RINOK(copyCoder->Code(inStream, realOutStream, NULL, NULL, progress)); - isOk = (copyCoderSpec->TotalSize == item.Size); + RINOK(InStream_SeekSet(_stream, item.GetDataPos())) + inStream->Init(item.Size); + RINOK(copyCoder.Interface()->Code(inStream, realOutStream, NULL, NULL, lps)) + if (copyCoder->TotalSize != item.Size) + opRes = NExtract::NOperationResult::kDataError; } - realOutStream.Release(); - RINOK(extractCallback->SetOperationResult(isOk ? - NExtract::NOperationResult::kOK: - NExtract::NOperationResult::kDataError)); + } + RINOK(extractCallback->SetOperationResult(opRes)) } return S_OK; COM_TRY_END } -STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream) +Z7_COM7F_IMF(CHandler::GetStream(UInt32 index, ISequentialInStream **stream)) { COM_TRY_BEGIN const CItem &item = _items[index]; @@ -845,7 +835,7 @@ STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream) } REGISTER_ARC_I( - "Ar", "ar a deb lib", 0, 0xEC, + "Ar", "ar a deb udeb lib", NULL, 0xEC, kSignature, 0, 0, diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Archive.def b/src/plugins/7zip/7za/cpp/7zip/Archive/Archive.def index 145516d7..b89f564d 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Archive.def +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Archive.def @@ -9,4 +9,7 @@ EXPORTS SetCodecs PRIVATE SetLargePageMode PRIVATE + SetLargePageMode2 PRIVATE SetCaseSensitive PRIVATE + + GetModuleProp PRIVATE diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Archive2.def b/src/plugins/7zip/7za/cpp/7zip/Archive/Archive2.def index c7582742..1b3cae87 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Archive2.def +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Archive2.def @@ -16,4 +16,7 @@ EXPORTS SetCodecs PRIVATE SetLargePageMode PRIVATE + SetLargePageMode2 PRIVATE SetCaseSensitive PRIVATE + + GetModuleProp PRIVATE diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/ArchiveExports.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/ArchiveExports.cpp index 28e9946d..4735fcff 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/ArchiveExports.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/ArchiveExports.cpp @@ -10,7 +10,7 @@ #include "../Common/RegisterArc.h" -static const unsigned kNumArcsMax = 64; +static const unsigned kNumArcsMax = 72; static unsigned g_NumArcs = 0; static unsigned g_DefaultArcIndex = 0; static const CArcInfo *g_Arcs[kNumArcsMax]; @@ -24,9 +24,10 @@ void RegisterArc(const CArcInfo *arcInfo) throw() g_DefaultArcIndex = g_NumArcs; g_Arcs[g_NumArcs++] = arcInfo; } + // else throw 1; } -DEFINE_GUID(CLSID_CArchiveHandler, +Z7_DEFINE_GUID(CLSID_CArchiveHandler, k_7zip_GUID_Data1, k_7zip_GUID_Data2, k_7zip_GUID_Data3_Common, @@ -36,7 +37,7 @@ DEFINE_GUID(CLSID_CArchiveHandler, static inline HRESULT SetPropStrFromBin(const char *s, unsigned size, PROPVARIANT *value) { - if ((value->bstrVal = ::SysAllocStringByteLen(s, size)) != 0) + if ((value->bstrVal = ::SysAllocStringByteLen(s, size)) != NULL) value->vt = VT_BSTR; return S_OK; } @@ -46,28 +47,29 @@ static inline HRESULT SetPropGUID(const GUID &guid, PROPVARIANT *value) return SetPropStrFromBin((const char *)&guid, sizeof(guid), value); } -int FindFormatCalssId(const GUID *clsid) +static int FindFormatCalssId(const GUID *clsid) { GUID cls = *clsid; CLS_ARC_ID_ITEM(cls) = 0; if (cls != CLSID_CArchiveHandler) return -1; - Byte id = CLS_ARC_ID_ITEM(*clsid); + const Byte id = CLS_ARC_ID_ITEM(*clsid); for (unsigned i = 0; i < g_NumArcs; i++) if (g_Arcs[i]->Id == id) return (int)i; return -1; } +STDAPI CreateArchiver(const GUID *clsid, const GUID *iid, void **outObject); STDAPI CreateArchiver(const GUID *clsid, const GUID *iid, void **outObject) { COM_TRY_BEGIN { - int needIn = (*iid == IID_IInArchive); - int needOut = (*iid == IID_IOutArchive); + const int needIn = (*iid == IID_IInArchive); + const int needOut = (*iid == IID_IOutArchive); if (!needIn && !needOut) return E_NOINTERFACE; - int formatIndex = FindFormatCalssId(clsid); + const int formatIndex = FindFormatCalssId(clsid); if (formatIndex < 0) return CLASS_E_CLASSNOTAVAILABLE; @@ -89,6 +91,7 @@ STDAPI CreateArchiver(const GUID *clsid, const GUID *iid, void **outObject) return S_OK; } +STDAPI GetHandlerProperty2(UInt32 formatIndex, PROPID propID, PROPVARIANT *value); STDAPI GetHandlerProperty2(UInt32 formatIndex, PROPID propID, PROPVARIANT *value) { COM_TRY_BEGIN @@ -113,6 +116,7 @@ STDAPI GetHandlerProperty2(UInt32 formatIndex, PROPID propID, PROPVARIANT *value case NArchive::NHandlerPropID::kAltStreams: prop = ((arc.Flags & NArcInfoFlags::kAltStreams) != 0); break; case NArchive::NHandlerPropID::kNtSecure: prop = ((arc.Flags & NArcInfoFlags::kNtSecure) != 0); break; case NArchive::NHandlerPropID::kFlags: prop = (UInt32)arc.Flags; break; + case NArchive::NHandlerPropID::kTimeFlags: prop = (UInt32)arc.TimeFlags; break; case NArchive::NHandlerPropID::kSignatureOffset: prop = (UInt32)arc.SignatureOffset; break; // case NArchive::NHandlerPropID::kVersion: prop = (UInt32)MY_VER_MIX; break; @@ -130,17 +134,20 @@ STDAPI GetHandlerProperty2(UInt32 formatIndex, PROPID propID, PROPVARIANT *value COM_TRY_END } +STDAPI GetHandlerProperty(PROPID propID, PROPVARIANT *value); STDAPI GetHandlerProperty(PROPID propID, PROPVARIANT *value) { return GetHandlerProperty2(g_DefaultArcIndex, propID, value); } +STDAPI GetNumberOfFormats(UINT32 *numFormats); STDAPI GetNumberOfFormats(UINT32 *numFormats) { *numFormats = g_NumArcs; return S_OK; } +STDAPI GetIsArc(UInt32 formatIndex, Func_IsArc *isArc); STDAPI GetIsArc(UInt32 formatIndex, Func_IsArc *isArc) { *isArc = NULL; diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/ArjHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/ArjHandler.cpp index 90819753..1d641e4b 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/ArjHandler.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/ArjHandler.cpp @@ -4,11 +4,12 @@ #include "../../../C/CpuArch.h" +#include "../../Common/AutoPtr.h" #include "../../Common/ComTry.h" -#include "../../Common/IntToString.h" #include "../../Common/StringConvert.h" #include "../../Windows/PropVariant.h" +#include "../../Windows/PropVariantUtils.h" #include "../../Windows/TimeUtils.h" #include "../Common/LimitedStreams.h" @@ -28,15 +29,13 @@ namespace NArj { namespace NDecoder { static const unsigned kMatchMinLen = 3; - static const UInt32 kWindowSize = 1 << 15; // must be >= (1 << 14) -class CCoder: - public ICompressCoder, - public CMyUnknownImp +class CCoder { CLzOutWindow _outWindow; NBitm::CDecoder _inBitStream; + // bool FinishMode; class CCoderReleaser { @@ -48,22 +47,20 @@ class CCoder: }; friend class CCoderReleaser; - HRESULT CodeReal(UInt64 outSize, ICompressProgressInfo *progress); + HRESULT CodeReal(UInt32 outSize, ICompressProgressInfo *progress); public: - MY_UNKNOWN_IMP - - bool FinishMode; - CCoder(): FinishMode(false) {} - STDMETHOD(Code)(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress); + // CCoder(): FinishMode(true) {} UInt64 GetInputProcessedSize() const { return _inBitStream.GetProcessedSize(); } + HRESULT Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, + UInt32 outSize, ICompressProgressInfo *progress); }; -HRESULT CCoder::CodeReal(UInt64 rem, ICompressProgressInfo *progress) + +HRESULT CCoder::CodeReal(UInt32 rem, ICompressProgressInfo *progress) { const UInt32 kStep = 1 << 20; - UInt64 next = 0; + UInt32 next = 0; if (rem > kStep && progress) next = rem - kStep; @@ -73,22 +70,20 @@ HRESULT CCoder::CodeReal(UInt64 rem, ICompressProgressInfo *progress) { if (_inBitStream.ExtraBitsWereRead()) return S_FALSE; - - UInt64 packSize = _inBitStream.GetProcessedSize(); - UInt64 pos = _outWindow.GetProcessedSize(); - RINOK(progress->SetRatioInfo(&packSize, &pos)); + const UInt64 packSize = _inBitStream.GetProcessedSize(); + const UInt64 pos = _outWindow.GetProcessedSize(); + RINOK(progress->SetRatioInfo(&packSize, &pos)) next = 0; if (rem > kStep) next = rem - kStep; } UInt32 len; - { const unsigned kNumBits = 7 + 7; - UInt32 val = _inBitStream.GetValue(kNumBits); + const UInt32 val = _inBitStream.GetValue(kNumBits); - if ((val & (1 << (kNumBits - 1))) == 0) + if ((val & (1u << (kNumBits - 1))) == 0) { _outWindow.PutByte((Byte)(val >> 5)); _inBitStream.MovePos(1 + 8); @@ -96,27 +91,24 @@ HRESULT CCoder::CodeReal(UInt64 rem, ICompressProgressInfo *progress) continue; } - UInt32 mask = 1 << (kNumBits - 2); unsigned w; - - for (w = 1; w < 7; w++, mask >>= 1) - if ((val & mask) == 0) - break; - - unsigned readBits = (w != 7 ? 1 : 0); - readBits += w + w; - len = (1 << w) - 1 + kMatchMinLen - 1 + - (((val >> (kNumBits - readBits)) & ((1 << w) - 1))); + { + UInt32 flag = (UInt32)1 << (kNumBits - 2); + for (w = 1; w < 7; w++, flag >>= 1) + if ((val & flag) == 0) + break; + } + const unsigned readBits = (w != 7 ? 1 : 0) + w * 2; + const UInt32 mask = ((UInt32)1 << w) - 1; + len = mask + kMatchMinLen - 1 + + ((val >> (kNumBits - readBits)) & mask); _inBitStream.MovePos(readBits); } - { const unsigned kNumBits = 4 + 13; - UInt32 val = _inBitStream.GetValue(kNumBits); - + const UInt32 val = _inBitStream.GetValue(kNumBits); unsigned readBits = 1; unsigned w; - if ((val & ((UInt32)1 << 16)) == 0) w = 9; else if ((val & ((UInt32)1 << 15)) == 0) w = 10; else if ((val & ((UInt32)1 << 14)) == 0) w = 11; @@ -124,61 +116,50 @@ HRESULT CCoder::CodeReal(UInt64 rem, ICompressProgressInfo *progress) else { w = 13; readBits = 0; } readBits += w + w - 9; - - UInt32 dist = ((UInt32)1 << w) - (1 << 9) + + const UInt32 dist = ((UInt32)1 << w) - (1 << 9) + (((val >> (kNumBits - readBits)) & ((1 << w) - 1))); _inBitStream.MovePos(readBits); - if (len > rem) - len = (UInt32)rem; - + { + // if (FinishMode) + return S_FALSE; + // else len = (UInt32)rem; + } if (!_outWindow.CopyBlock(dist, len)) return S_FALSE; rem -= len; } } - if (FinishMode) + // if (FinishMode) { if (_inBitStream.ReadAlignBits() != 0) return S_FALSE; } - if (_inBitStream.ExtraBitsWereRead()) return S_FALSE; - return S_OK; } - -STDMETHODIMP CCoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 * /* inSize */, const UInt64 *outSize, ICompressProgressInfo *progress) +HRESULT CCoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, + UInt32 outSize, ICompressProgressInfo *progress) { try { - if (!outSize) - return E_INVALIDARG; - if (!_outWindow.Create(kWindowSize)) return E_OUTOFMEMORY; if (!_inBitStream.Create(1 << 17)) return E_OUTOFMEMORY; - _outWindow.SetStream(outStream); _outWindow.Init(false); _inBitStream.SetStream(inStream); _inBitStream.Init(); - - CCoderReleaser coderReleaser(this); - HRESULT res; { - res = CodeReal(*outSize, progress); - if (res != S_OK) - return res; + CCoderReleaser coderReleaser(this); + RINOK(CodeReal(outSize, progress)) + coderReleaser.Disable(); } - - coderReleaser.Disable(); return _outWindow.Flush(); } catch(const CInBufferException &e) { return e.ErrorCode; } @@ -235,13 +216,13 @@ namespace NFileType namespace NFlags { const Byte kGarbled = 1 << 0; - const Byte kAnsiPage = 1 << 1; // or (OLD_SECURED_FLAG) obsolete + // const Byte kAnsiPage = 1 << 1; // or (OLD_SECURED_FLAG) obsolete const Byte kVolume = 1 << 2; const Byte kExtFile = 1 << 3; - const Byte kPathSym = 1 << 4; - const Byte kBackup = 1 << 5; // obsolete - const Byte kSecured = 1 << 6; - const Byte kDualName = 1 << 7; + // const Byte kPathSym = 1 << 4; + // const Byte kBackup = 1 << 5; // obsolete + // const Byte kSecured = 1 << 6; + // const Byte kDualName = 1 << 7; } namespace NHostOS @@ -367,14 +348,40 @@ HRESULT CArcHeader::Parse(const Byte *p, unsigned size) // LastChapter = p[29]; unsigned pos = headerSize; unsigned size1 = size - pos; - RINOK(ReadString(p + pos, size1, Name)); + RINOK(ReadString(p + pos, size1, Name)) pos += size1; size1 = size - pos; - RINOK(ReadString(p + pos, size1, Comment)); + RINOK(ReadString(p + pos, size1, Comment)) pos += size1; return S_OK; } + +struct CExtendedInfo +{ + UInt64 Size; + bool CrcError; + + void Clear() + { + Size = 0; + CrcError = false; + } + void ParseToPropVar(NCOM::CPropVariant &prop) const + { + if (Size != 0) + { + AString s; + s += "Extended:"; + s.Add_UInt32((UInt32)Size); + if (CrcError) + s += ":CRC_ERROR"; + prop = s; + } + } +}; + + struct CItem { AString Name; @@ -399,6 +406,8 @@ struct CItem // Byte LastChapter; UInt64 DataPosition; + + CExtendedInfo ExtendedInfo; bool IsEncrypted() const { return (Flags & NFlags::kGarbled) != 0; } bool IsDir() const { return (FileType == NFileType::kDirectory); } @@ -449,10 +458,10 @@ HRESULT CItem::Parse(const Byte *p, unsigned size) unsigned pos = headerSize; unsigned size1 = size - pos; - RINOK(ReadString(p + pos, size1, Name)); + RINOK(ReadString(p + pos, size1, Name)) pos += size1; size1 = size - pos; - RINOK(ReadString(p + pos, size1, Comment)); + RINOK(ReadString(p + pos, size1, Comment)) pos += size1; return S_OK; @@ -462,7 +471,7 @@ enum EErrorType { k_ErrorType_OK, k_ErrorType_Corrupted, - k_ErrorType_UnexpectedEnd, + k_ErrorType_UnexpectedEnd }; class CArc @@ -476,19 +485,22 @@ class CArc UInt64 NumFiles; CArcHeader Header; + CExtendedInfo ExtendedInfo; + HRESULT Open(); HRESULT GetNextItem(CItem &item, bool &filled); void Close() { IsArc = false; Error = k_ErrorType_OK; + ExtendedInfo.Clear(); } private: - UInt32 _blockSize; - Byte _block[kBlockSizeMax + 4]; + unsigned _blockSize; + CByteBuffer _block; - HRESULT ReadBlock(bool &filled, bool readSignature); - HRESULT SkipExtendedHeaders(); + HRESULT ReadBlock(bool &filled, CExtendedInfo *extendedInfo); + HRESULT SkipExtendedHeaders(CExtendedInfo &extendedInfo); HRESULT Read(void *data, size_t *size); }; @@ -503,14 +515,14 @@ HRESULT CArc::Read(void *data, size_t *size) { size_t _processed_ = (_size_); RINOK(Read(_dest_, &_processed_)); \ if (_processed_ != (_size_)) { Error = k_ErrorType_UnexpectedEnd; return S_OK; } } -HRESULT CArc::ReadBlock(bool &filled, bool readSignature) +HRESULT CArc::ReadBlock(bool &filled, CExtendedInfo *extendedInfo) { Error = k_ErrorType_OK; filled = false; Byte buf[4]; - unsigned signSize = readSignature ? 2 : 0; + const unsigned signSize = extendedInfo ? 0 : 2; READ_STREAM(buf, signSize + 2) - if (readSignature) + if (!extendedInfo) if (buf[0] != kSig0 || buf[1] != kSig1) { Error = k_ErrorType_Corrupted; @@ -519,49 +531,69 @@ HRESULT CArc::ReadBlock(bool &filled, bool readSignature) _blockSize = Get16(buf + signSize); if (_blockSize == 0) // end of archive return S_OK; - if (_blockSize < kBlockSizeMin || - _blockSize > kBlockSizeMax) + + if (!extendedInfo) + if (_blockSize < kBlockSizeMin || _blockSize > kBlockSizeMax) + { + Error = k_ErrorType_Corrupted; + return S_OK; + } + + const size_t readSize = _blockSize + 4; + if (readSize > _block.Size()) { - Error = k_ErrorType_Corrupted; - return S_OK; + // extended data size is limited by (64 KB) + // _blockSize is less than 64 KB + const size_t upSize = (_blockSize > kBlockSizeMax ? (1 << 16) : kBlockSizeMax); + _block.Alloc(upSize + 4); } - READ_STREAM(_block, _blockSize + 4); + + if (extendedInfo) + extendedInfo->Size += _blockSize; + + READ_STREAM(_block, readSize) if (Get32(_block + _blockSize) != CrcCalc(_block, _blockSize)) { - Error = k_ErrorType_Corrupted; - return S_OK; + if (extendedInfo) + extendedInfo->CrcError = true; + else + { + Error = k_ErrorType_Corrupted; + return S_OK; + } } filled = true; return S_OK; } -HRESULT CArc::SkipExtendedHeaders() +HRESULT CArc::SkipExtendedHeaders(CExtendedInfo &extendedInfo) { + extendedInfo.Clear(); for (UInt32 i = 0;; i++) { bool filled; - RINOK(ReadBlock(filled, false)); + RINOK(ReadBlock(filled, &extendedInfo)) if (!filled) return S_OK; if (Callback && (i & 0xFF) == 0) - RINOK(Callback->SetCompleted(&NumFiles, &Processed)); + RINOK(Callback->SetCompleted(&NumFiles, &Processed)) } } HRESULT CArc::Open() { bool filled; - RINOK(ReadBlock(filled, true)); + RINOK(ReadBlock(filled, NULL)) // (extendedInfo = NULL) if (!filled) return S_FALSE; - RINOK(Header.Parse(_block, _blockSize)); + RINOK(Header.Parse(_block, _blockSize)) IsArc = true; - return SkipExtendedHeaders(); + return SkipExtendedHeaders(ExtendedInfo); } HRESULT CArc::GetNextItem(CItem &item, bool &filled) { - RINOK(ReadBlock(filled, true)); + RINOK(ReadBlock(filled, NULL)) // (extendedInfo = NULL) if (!filled) return S_OK; filled = false; @@ -576,23 +608,18 @@ HRESULT CArc::GetNextItem(CItem &item, bool &filled) extraData = GetUi32(_block + pos); */ - RINOK(SkipExtendedHeaders()); + RINOK(SkipExtendedHeaders(item.ExtendedInfo)) filled = true; return S_OK; } -class CHandler: - public IInArchive, - public CMyUnknownImp -{ + +Z7_CLASS_IMP_CHandler_IInArchive_0 + CObjectVector _items; CMyComPtr _stream; UInt64 _phySize; CArc _arc; -public: - MY_UNKNOWN_IMP1(IInArchive) - - INTERFACE_IInArchive(;) HRESULT Open2(IInStream *inStream, IArchiveOpenCallback *callback); }; @@ -603,7 +630,8 @@ static const Byte kArcProps[] = kpidCTime, kpidMTime, kpidHostOS, - kpidComment + kpidComment, + kpidCharacts }; static const Byte kProps[] = @@ -619,7 +647,8 @@ static const Byte kProps[] = kpidCRC, kpidMethod, kpidHostOS, - kpidComment + kpidComment, + kpidCharacts }; IMP_IInArchive_Props @@ -629,29 +658,12 @@ static void SetTime(UInt32 dosTime, NCOM::CPropVariant &prop) { if (dosTime == 0) return; - FILETIME localFileTime, utc; - if (NTime::DosTimeToFileTime(dosTime, localFileTime)) - { - if (!LocalFileTimeToFileTime(&localFileTime, &utc)) - utc.dwHighDateTime = utc.dwLowDateTime = 0; - } - else - utc.dwHighDateTime = utc.dwLowDateTime = 0; - prop = utc; + PropVariant_SetFrom_DosTime(prop, dosTime); } static void SetHostOS(Byte hostOS, NCOM::CPropVariant &prop) { - char temp[16]; - const char *s = NULL; - if (hostOS < ARRAY_SIZE(kHostOS)) - s = kHostOS[hostOS]; - else - { - ConvertUInt32ToString(hostOS, temp); - s = temp; - } - prop = s; + TYPE_TO_PROP(kHostOS, hostOS, prop); } static void SetUnicodeString(const AString &s, NCOM::CPropVariant &prop) @@ -660,7 +672,7 @@ static void SetUnicodeString(const AString &s, NCOM::CPropVariant &prop) prop = MultiByteToUnicodeString(s, CP_OEMCP); } -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; @@ -680,30 +692,34 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) { case k_ErrorType_UnexpectedEnd: v |= kpv_ErrorFlags_UnexpectedEnd; break; case k_ErrorType_Corrupted: v |= kpv_ErrorFlags_HeadersError; break; + case k_ErrorType_OK: + // default: + break; } prop = v; break; } + case kpidCharacts: _arc.ExtendedInfo.ParseToPropVar(prop); break; } prop.Detach(value); return S_OK; COM_TRY_END } -STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) { *numItems = _items.Size(); return S_OK; } -STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; const CItem &item = _items[index]; switch (propID) { - case kpidPath: prop = NItemName::GetOSName(MultiByteToUnicodeString(item.Name, CP_OEMCP)); break; + case kpidPath: prop = NItemName::GetOsPath(MultiByteToUnicodeString(item.Name, CP_OEMCP)); break; case kpidIsDir: prop = item.IsDir(); break; case kpidSize: prop = item.Size; break; case kpidPackSize: prop = item.PackSize; break; @@ -715,6 +731,7 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val case kpidHostOS: SetHostOS(item.HostOS, prop); break; case kpidMTime: SetTime(item.MTime, prop); break; case kpidComment: SetUnicodeString(item.Comment, prop); break; + case kpidCharacts: item.ExtendedInfo.ParseToPropVar(prop); break; } prop.Detach(value); return S_OK; @@ -725,16 +742,15 @@ HRESULT CHandler::Open2(IInStream *inStream, IArchiveOpenCallback *callback) { Close(); - UInt64 endPos = 0; - RINOK(inStream->Seek(0, STREAM_SEEK_END, &endPos)); - RINOK(inStream->Seek(0, STREAM_SEEK_SET, NULL)); + UInt64 endPos; + RINOK(InStream_AtBegin_GetSize(inStream, endPos)) _arc.Stream = inStream; _arc.Callback = callback; _arc.NumFiles = 0; _arc.Processed = 0; - RINOK(_arc.Open()); + RINOK(_arc.Open()) _phySize = _arc.Processed; if (_arc.Header.ArchiveSize != 0) @@ -746,7 +762,7 @@ HRESULT CHandler::Open2(IInStream *inStream, IArchiveOpenCallback *callback) bool filled; _arc.Error = k_ErrorType_OK; - RINOK(_arc.GetNextItem(item, filled)); + RINOK(_arc.GetNextItem(item, filled)) if (_arc.Error != k_ErrorType_OK) break; @@ -770,20 +786,20 @@ HRESULT CHandler::Open2(IInStream *inStream, IArchiveOpenCallback *callback) break; } - RINOK(inStream->Seek(pos, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekSet(inStream, pos)) _arc.NumFiles = _items.Size(); _arc.Processed = pos; if (callback && (_items.Size() & 0xFF) == 0) { - RINOK(callback->SetCompleted(&_arc.NumFiles, &_arc.Processed)); + RINOK(callback->SetCompleted(&_arc.NumFiles, &_arc.Processed)) } } return S_OK; } -STDMETHODIMP CHandler::Open(IInStream *inStream, - const UInt64 * /* maxCheckStartPosition */, IArchiveOpenCallback *callback) +Z7_COM7F_IMF(CHandler::Open(IInStream *inStream, + const UInt64 * /* maxCheckStartPosition */, IArchiveOpenCallback *callback)) { COM_TRY_BEGIN HRESULT res; @@ -799,7 +815,7 @@ STDMETHODIMP CHandler::Open(IInStream *inStream, COM_TRY_END } -STDMETHODIMP CHandler::Close() +Z7_COM7F_IMF(CHandler::Close()) { _arc.Close(); _phySize = 0; @@ -808,12 +824,12 @@ STDMETHODIMP CHandler::Close() return S_OK; } -STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, - Int32 testMode, IArchiveExtractCallback *extractCallback) +Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testMode, IArchiveExtractCallback *extractCallback)) { COM_TRY_BEGIN UInt64 totalUnpacked = 0, totalPacked = 0; - bool allFilesMode = (numItems == (UInt32)(Int32)-1); + const bool allFilesMode = (numItems == (UInt32)(Int32)-1); if (allFilesMode) numItems = _items.Size(); if (numItems == 0) @@ -825,75 +841,70 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, totalUnpacked += item.Size; // totalPacked += item.PackSize; } - extractCallback->SetTotal(totalUnpacked); + RINOK(extractCallback->SetTotal(totalUnpacked)) totalUnpacked = totalPacked = 0; - UInt64 curUnpacked, curPacked; + UInt32 curUnpacked, curPacked; - NCompress::NLzh::NDecoder::CCoder *lzhDecoderSpec = NULL; - CMyComPtr lzhDecoder; - - NCompress::NArj::NDecoder::CCoder *arjDecoderSpec = NULL; - CMyComPtr arjDecoder; - - NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder(); - CMyComPtr copyCoder = copyCoderSpec; - - CLocalProgress *lps = new CLocalProgress; - CMyComPtr progress = lps; + CMyComPtr2_Create lps; lps->Init(extractCallback, false); - - CLimitedSequentialInStream *inStreamSpec = new CLimitedSequentialInStream; - CMyComPtr inStream(inStreamSpec); - inStreamSpec->SetStream(_stream); - - for (i = 0; i < numItems; i++, totalUnpacked += curUnpacked, totalPacked += curPacked) + CMyUniquePtr lzhDecoder; + CMyUniquePtr arjDecoder; + CMyComPtr2_Create copyCoder; + CMyComPtr2_Create inStream; + inStream->SetStream(_stream); + + for (i = 0;; i++, + totalUnpacked += curUnpacked, + totalPacked += curPacked) { lps->InSize = totalPacked; lps->OutSize = totalUnpacked; - RINOK(lps->SetCur()); + RINOK(lps->SetCur()) + if (i >= numItems) + break; curUnpacked = curPacked = 0; - CMyComPtr realOutStream; - Int32 askMode = testMode ? - NExtract::NAskMode::kTest : - NExtract::NAskMode::kExtract; - Int32 index = allFilesMode ? i : indices[i]; - const CItem &item = _items[index]; - RINOK(extractCallback->GetStream(index, &realOutStream, askMode)); - - if (item.IsDir()) + Int32 opRes; { - // if (!testMode) + CMyComPtr realOutStream; + const Int32 askMode = testMode ? + NExtract::NAskMode::kTest : + NExtract::NAskMode::kExtract; + const UInt32 index = allFilesMode ? i : indices[i]; + const CItem &item = _items[index]; + RINOK(extractCallback->GetStream(index, &realOutStream, askMode)) + + if (item.IsDir()) { - RINOK(extractCallback->PrepareOperation(askMode)); - RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)); + // if (!testMode) + { + RINOK(extractCallback->PrepareOperation(askMode)) + // realOutStream.Release(); + RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)) + } + continue; } - continue; - } - - if (!testMode && !realOutStream) - continue; - - RINOK(extractCallback->PrepareOperation(askMode)); - curUnpacked = item.Size; - curPacked = item.PackSize; - - { - COutStreamWithCRC *outStreamSpec = new COutStreamWithCRC; - CMyComPtr outStream(outStreamSpec); - outStreamSpec->SetStream(realOutStream); - realOutStream.Release(); - outStreamSpec->Init(); + + if (!testMode && !realOutStream) + continue; + + RINOK(extractCallback->PrepareOperation(askMode)) + curUnpacked = item.Size; + curPacked = item.PackSize; + + CMyComPtr2_Create outStream; + outStream->SetStream(realOutStream); + // realOutStream.Release(); + outStream->Init(); - inStreamSpec->Init(item.PackSize); + inStream->Init(item.PackSize); - UInt64 pos; - _stream->Seek(item.DataPosition, STREAM_SEEK_SET, &pos); + RINOK(InStream_SeekSet(_stream, item.DataPosition)) HRESULT result = S_OK; - Int32 opRes = NExtract::NOperationResult::kOK; + opRes = NExtract::NOperationResult::kOK; if (item.IsEncrypted()) opRes = NExtract::NOperationResult::kUnsupportedMethod; @@ -903,8 +914,8 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, { case NCompressionMethod::kStored: { - result = copyCoder->Code(inStream, outStream, NULL, NULL, progress); - if (result == S_OK && copyCoderSpec->TotalSize != item.PackSize) + result = copyCoder.Interface()->Code(inStream, outStream, NULL, NULL, lps); + if (result == S_OK && copyCoder->TotalSize != item.PackSize) result = S_FALSE; break; } @@ -912,29 +923,21 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, case NCompressionMethod::kCompressed1b: case NCompressionMethod::kCompressed1c: { - if (!lzhDecoder) - { - lzhDecoderSpec = new NCompress::NLzh::NDecoder::CCoder; - lzhDecoder = lzhDecoderSpec; - } - lzhDecoderSpec->FinishMode = true; + lzhDecoder.Create_if_Empty(); + // lzhDecoder->FinishMode = true; const UInt32 kHistorySize = 26624; - lzhDecoderSpec->SetDictSize(kHistorySize); - result = lzhDecoder->Code(inStream, outStream, NULL, &curUnpacked, progress); - if (result == S_OK && lzhDecoderSpec->GetInputProcessedSize() != item.PackSize) + lzhDecoder->SetDictSize(kHistorySize); + result = lzhDecoder->Code(inStream, outStream, curUnpacked, lps); + if (result == S_OK && lzhDecoder->GetInputProcessedSize() != item.PackSize) result = S_FALSE; break; } case NCompressionMethod::kCompressed2: { - if (!arjDecoder) - { - arjDecoderSpec = new NCompress::NArj::NDecoder::CCoder; - arjDecoder = arjDecoderSpec; - } - arjDecoderSpec->FinishMode = true; - result = arjDecoder->Code(inStream, outStream, NULL, &curUnpacked, progress); - if (result == S_OK && arjDecoderSpec->GetInputProcessedSize() != item.PackSize) + arjDecoder.Create_if_Empty(); + // arjDecoderSpec->FinishMode = true; + result = arjDecoder->Code(inStream, outStream, curUnpacked, lps); + if (result == S_OK && arjDecoder->GetInputProcessedSize() != item.PackSize) result = S_FALSE; break; } @@ -949,16 +952,14 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, opRes = NExtract::NOperationResult::kDataError; else { - RINOK(result); - opRes = (outStreamSpec->GetCRC() == item.FileCRC) ? + RINOK(result) + opRes = (outStream->GetCRC() == item.FileCRC) ? NExtract::NOperationResult::kOK: NExtract::NOperationResult::kCRCError; } } - - outStream.Release(); - RINOK(extractCallback->SetOperationResult(opRes)); } + RINOK(extractCallback->SetOperationResult(opRes)) } return S_OK; @@ -968,7 +969,7 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, static const Byte k_Signature[] = { kSig0, kSig1 }; REGISTER_ARC_I( - "Arj", "arj", 0, 4, + "Arj", "arj", NULL, 4, k_Signature, 0, 0, diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/AvbHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/AvbHandler.cpp new file mode 100644 index 00000000..2d20407d --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/AvbHandler.cpp @@ -0,0 +1,616 @@ +// AvbHandler.cpp + +#include "StdAfx.h" + +#include "../../../C/CpuArch.h" + +#include "../../Common/ComTry.h" +#include "../../Common/MyBuffer.h" + +#include "../../Windows/PropVariant.h" + +#include "../Common/RegisterArc.h" +#include "../Common/StreamUtils.h" + +#include "HandlerCont.h" + +#define Get32(p) GetBe32(p) +#define Get64(p) GetBe64(p) + +#define G32(_offs_, dest) dest = Get32(p + (_offs_)) +#define G64(_offs_, dest) dest = Get64(p + (_offs_)) + +using namespace NWindows; + +namespace NArchive { + +namespace NExt { +API_FUNC_IsArc IsArc_Ext_PhySize(const Byte *p, size_t size, UInt64 *phySize); +} + +namespace NAvb { + +static bool AddNameToString(AString &s, const Byte *name, unsigned size) +{ + bool strictConvert = false; + for (unsigned i = 0; i < size; i++) + { + Byte c = name[i]; + if (c == 0) + return false; + if (strictConvert && c < 32) + c = '_'; + s += (char)c; + } + return true; +} + +/* Maximum size of a vbmeta image - 64 KiB. */ +#define VBMETA_MAX_SIZE (64 * 1024) + +#define SIGNATURE { 'A', 'V', 'B', 'f', 0, 0, 0, 1 } + +static const unsigned k_SignatureSize = 8; +static const Byte k_Signature[k_SignatureSize] = SIGNATURE; + +// #define AVB_FOOTER_MAGIC "AVBf" +// #define AVB_FOOTER_MAGIC_LEN 4 +/* The current footer version used - keep in sync with avbtool. */ +#define AVB_FOOTER_VERSION_MAJOR 1 + +/* The struct used as a footer used on partitions, used to find the + * AvbVBMetaImageHeader struct. This struct is always stored at the + * end of a partition. + */ +// #define AVB_FOOTER_SIZE 64 +static const unsigned kFooterSize = 64; + +struct CFooter +{ + /* 0: Four bytes equal to "AVBf" (AVB_FOOTER_MAGIC). */ + // Byte magic[AVB_FOOTER_MAGIC_LEN]; + /* 4: The major version of the footer struct. */ + UInt32 version_major; + /* 8: The minor version of the footer struct. */ + UInt32 version_minor; + + /* 12: The original size of the image on the partition. */ + UInt64 original_image_size; + + /* 20: The offset of the |AvbVBMetaImageHeader| struct. */ + UInt64 vbmeta_offset; + + /* 28: The size of the vbmeta block (header + auth + aux blocks). */ + UInt64 vbmeta_size; + + /* 36: Padding to ensure struct is size AVB_FOOTER_SIZE bytes. This + * must be set to zeroes. + */ + Byte reserved[28]; + + void Parse(const Byte *p) + { + G32 (4, version_major); + G32 (8, version_minor); + G64 (12, original_image_size); + G64 (20, vbmeta_offset); + G64 (28, vbmeta_size); + } +}; + + +/* Size of the vbmeta image header. */ +#define AVB_VBMETA_IMAGE_HEADER_SIZE 256 + +/* Magic for the vbmeta image header. */ +// #define AVB_MAGIC "AVB0" +// #define AVB_MAGIC_LEN 4 +/* Maximum size of the release string including the terminating NUL byte. */ +#define AVB_RELEASE_STRING_SIZE 48 + +struct AvbVBMetaImageHeader +{ + /* 0: Four bytes equal to "AVB0" (AVB_MAGIC). */ + // Byte magic[AVB_MAGIC_LEN]; + + /* 4: The major version of libavb required for this header. */ + UInt32 required_libavb_version_major; + /* 8: The minor version of libavb required for this header. */ + UInt32 required_libavb_version_minor; + + /* 12: The size of the signature block. */ + UInt64 authentication_data_block_size; + /* 20: The size of the auxiliary data block. */ + UInt64 auxiliary_data_block_size; + + /* 28: The verification algorithm used, see |AvbAlgorithmType| enum. */ + UInt32 algorithm_type; + + /* 32: Offset into the "Authentication data" block of hash data. */ + UInt64 hash_offset; + /* 40: Length of the hash data. */ + UInt64 hash_size; + + /* 48: Offset into the "Authentication data" block of signature data. */ + UInt64 signature_offset; + /* 56: Length of the signature data. */ + UInt64 signature_size; + + /* 64: Offset into the "Auxiliary data" block of public key data. */ + UInt64 public_key_offset; + /* 72: Length of the public key data. */ + UInt64 public_key_size; + + /* 80: Offset into the "Auxiliary data" block of public key metadata. */ + UInt64 public_key_metadata_offset; + /* 88: Length of the public key metadata. Must be set to zero if there + * is no public key metadata. + */ + UInt64 public_key_metadata_size; + + /* 96: Offset into the "Auxiliary data" block of descriptor data. */ + UInt64 descriptors_offset; + /* 104: Length of descriptor data. */ + UInt64 descriptors_size; + + /* 112: The rollback index which can be used to prevent rollback to + * older versions. + */ + UInt64 rollback_index; + + /* 120: Flags from the AvbVBMetaImageFlags enumeration. This must be + * set to zero if the vbmeta image is not a top-level image. + */ + UInt32 flags; + + /* 124: The location of the rollback index defined in this header. + * Only valid for the main vbmeta. For chained partitions, the rollback + * index location must be specified in the AvbChainPartitionDescriptor + * and this value must be set to 0. + */ + UInt32 rollback_index_location; + + /* 128: The release string from avbtool, e.g. "avbtool 1.0.0" or + * "avbtool 1.0.0 xyz_board Git-234abde89". Is guaranteed to be NUL + * terminated. Applications must not make assumptions about how this + * string is formatted. + */ + Byte release_string[AVB_RELEASE_STRING_SIZE]; + + /* 176: Padding to ensure struct is size AVB_VBMETA_IMAGE_HEADER_SIZE + * bytes. This must be set to zeroes. + */ + // Byte reserved[80]; + bool Parse(const Byte *p); +}; + +bool AvbVBMetaImageHeader::Parse(const Byte *p) +{ + // Byte magic[AVB_MAGIC_LEN]; + if (Get32(p) != 0x41564230) // "AVB0" + return false; + G32 (4, required_libavb_version_major); + if (required_libavb_version_major != AVB_FOOTER_VERSION_MAJOR) // "AVB0" + return false; + G32 (8, required_libavb_version_minor); + G64 (12, authentication_data_block_size); + G64 (20, auxiliary_data_block_size); + G32 (28, algorithm_type); + G64 (32, hash_offset); + G64 (40, hash_size); + G64 (48, signature_offset); + G64 (56, signature_size); + G64 (64, public_key_offset); + G64 (72, public_key_size); + G64 (80, public_key_metadata_offset); + G64 (88, public_key_metadata_size); + G64 (96, descriptors_offset); + G64 (104, descriptors_size); + G64 (112, rollback_index); + G32 (120, flags); + G32 (124, rollback_index_location); + memcpy(release_string, p + 128, AVB_RELEASE_STRING_SIZE); + + /* 176: Padding to ensure struct is size AVB_VBMETA_IMAGE_HEADER_SIZE + * bytes. This must be set to zeroes. + */ + // Byte reserved[80]; + return true; +} + + +static const unsigned k_Descriptor_Size = 16; + +enum AvbDescriptorTag +{ + AVB_DESCRIPTOR_TAG_PROPERTY, + AVB_DESCRIPTOR_TAG_HASHTREE, + AVB_DESCRIPTOR_TAG_HASH, + AVB_DESCRIPTOR_TAG_KERNEL_CMDLINE, + AVB_DESCRIPTOR_TAG_CHAIN_PARTITION +}; + +struct AvbDescriptor +{ + UInt64 Tag; + UInt64 Size; + + void Parse(const Byte *p) + { + G64 (0, Tag); + G64 (8, Size); + } +}; + + +enum AvbHashtreeDescriptorFlags +{ + AVB_HASHTREE_DESCRIPTOR_FLAGS_DO_NOT_USE_AB = (1 << 0), + AVB_HASHTREE_DESCRIPTOR_FLAGS_CHECK_AT_MOST_ONCE = (1 << 1) +}; + +/* A descriptor containing information about a dm-verity hashtree. + * + * Hash-trees are used to verify large partitions typically containing + * file systems. See + * https://gitlab.com/cryptsetup/cryptsetup/wikis/DMVerity for more + * information about dm-verity. + * + * Following this struct are |partition_name_len| bytes of the + * partition name (UTF-8 encoded), |salt_len| bytes of salt, and then + * |root_digest_len| bytes of the root digest. + * + * The |reserved| field is for future expansion and must be set to NUL + * bytes. + * + * Changes in v1.1: + * - flags field is added which supports AVB_HASHTREE_DESCRIPTOR_FLAGS_USE_AB + * - digest_len may be zero, which indicates the use of a persistent digest + */ + +static const unsigned k_Hashtree_Size_Min = 164; + +struct AvbHashtreeDescriptor +{ + UInt32 dm_verity_version; + UInt64 image_size; + UInt64 tree_offset; + UInt64 tree_size; + UInt32 data_block_size; + UInt32 hash_block_size; + UInt32 fec_num_roots; + UInt64 fec_offset; + UInt64 fec_size; + Byte hash_algorithm[32]; + UInt32 partition_name_len; + UInt32 salt_len; + UInt32 root_digest_len; + UInt32 flags; + Byte reserved[60]; + void Parse(const Byte *p) + { + G32 (0, dm_verity_version); + G64 (4, image_size); + G64 (12, tree_offset); + G64 (20, tree_size); + G32 (28, data_block_size); + G32 (32, hash_block_size); + G32 (36, fec_num_roots); + G64 (40, fec_offset); + G64 (48, fec_size); + memcpy(hash_algorithm, p + 56, 32); + G32 (88, partition_name_len); + G32 (92, salt_len); + G32 (96, root_digest_len); + G32 (100, flags); + } +}; + +struct AvbPropertyDescriptor +{ + UInt64 key_num_bytes; + UInt64 value_num_bytes; + + void Parse(const Byte *p) + { + G64 (0, key_num_bytes); + G64 (8, value_num_bytes); + } +}; + +Z7_class_CHandler_final: public CHandlerCont +{ + Z7_IFACE_COM7_IMP(IInArchive_Cont) + + // UInt64 _startOffset; + UInt64 _phySize; + + CFooter Footer; + AString Name; + const char *Ext; + + HRESULT Open2(IInStream *stream); + + virtual int GetItem_ExtractInfo(UInt32 index, UInt64 &pos, UInt64 &size) const Z7_override + { + if (index != 0) + return NExtract::NOperationResult::kUnavailable; + // pos = _startOffset; + pos = 0; + size = Footer.original_image_size; + return NExtract::NOperationResult::kOK; + } +}; + + +HRESULT CHandler::Open2(IInStream *stream) +{ + UInt64 fileSize; + { + Byte buf[kFooterSize]; + RINOK(InStream_GetSize_SeekToEnd(stream, fileSize)) + if (fileSize < kFooterSize) + return S_FALSE; + RINOK(InStream_SeekSet(stream, fileSize - kFooterSize)) + RINOK(ReadStream_FALSE(stream, buf, kFooterSize)) + if (memcmp(buf, k_Signature, k_SignatureSize) != 0) + return S_FALSE; + Footer.Parse(buf); + if (Footer.vbmeta_size > VBMETA_MAX_SIZE || + Footer.vbmeta_size < AVB_VBMETA_IMAGE_HEADER_SIZE) + return S_FALSE; + for (unsigned i = 36; i < kFooterSize; i++) + if (buf[i] != 0) + return S_FALSE; + } + { + CByteBuffer buf; + buf.Alloc((size_t)Footer.vbmeta_size); + RINOK(InStream_SeekSet(stream, Footer.vbmeta_offset)) + RINOK(ReadStream_FALSE(stream, buf, (size_t)Footer.vbmeta_size)) + + AvbVBMetaImageHeader meta; + if (!meta.Parse(buf)) + return S_FALSE; + + unsigned offset = (unsigned)AVB_VBMETA_IMAGE_HEADER_SIZE; + unsigned rem = (unsigned)(Footer.vbmeta_size - offset); + + const unsigned k_BlockAlignMask = 64 - 1; + + if (meta.authentication_data_block_size) + { + if (rem < meta.authentication_data_block_size) + return S_FALSE; + const unsigned u = (unsigned)meta.authentication_data_block_size; + if (u & k_BlockAlignMask) + return S_FALSE; + offset += u; + rem -= u; + } + + if ((rem & k_BlockAlignMask) + || meta.auxiliary_data_block_size < meta.descriptors_size + || rem != meta.auxiliary_data_block_size + || rem < meta.descriptors_offset + || rem - meta.descriptors_offset < meta.descriptors_size) + return S_FALSE; + rem = (unsigned)meta.descriptors_size; + offset += (unsigned)meta.descriptors_offset; + while (rem) + { + if (rem < k_Descriptor_Size) + return S_FALSE; + AvbDescriptor desc; + desc.Parse(buf + offset); + offset += k_Descriptor_Size; + rem -= k_Descriptor_Size; + if (desc.Size > rem) + return S_FALSE; + const unsigned descSize = (unsigned)desc.Size; + if (desc.Tag == AVB_DESCRIPTOR_TAG_HASHTREE) + { + if (descSize < k_Hashtree_Size_Min) + return S_FALSE; + AvbHashtreeDescriptor ht; + ht.Parse(buf + offset); + unsigned pos = k_Hashtree_Size_Min; + + if (ht.partition_name_len > descSize - pos) + return S_FALSE; + Name.Empty(); + if (!AddNameToString(Name, buf + offset + pos, ht.partition_name_len)) + return S_FALSE; + pos += ht.partition_name_len; + + /* + if (ht.salt_len > descSize - pos) + return S_FALSE; + CByteBuffer salt; + salt.CopyFrom(buf + offset + pos, ht.salt_len); + pos += ht.salt_len; + + if (ht.root_digest_len > descSize - pos) + return S_FALSE; + CByteBuffer digest; + digest.CopyFrom(buf + offset + pos, ht.root_digest_len); + pos += ht.root_digest_len; + // what is that digest? + */ + } + /* + else if (desc.Tag == AVB_DESCRIPTOR_TAG_PROPERTY) + { + static const unsigned k_PropertyDescriptor_Size_Min = 16; + if (descSize < k_PropertyDescriptor_Size_Min + 2) + return S_FALSE; + AvbPropertyDescriptor pt; + pt.Parse(buf + offset); + unsigned pos = k_PropertyDescriptor_Size_Min; + + // Two strings contain Null character at the end, + // but key_num_bytes and value_num_bytes do not count Null character. + // so we use ( >= ) in conditions: + if (pt.key_num_bytes >= descSize - pos) + return S_FALSE; + AString key; + if (!AddNameToString(key, buf + offset + pos, (unsigned)pt.key_num_bytes)) + return false; + pos += (unsigned)pt.key_num_bytes + 1; + if (buf[offset + pos - 1]) + return S_FALSE; + + if (pt.value_num_bytes >= descSize - pos) + return S_FALSE; + AString value; + if (!AddNameToString(value, buf + offset + pos, (unsigned)pt.value_num_bytes)) + return false; + pos += (unsigned)pt.value_num_bytes + 1; + if (buf[offset + pos - 1]) + return S_FALSE; + } + */ + offset += descSize; + rem -= descSize; + } + + _phySize = fileSize; + // _startOffset = 0; + return S_OK; + } +} + + +Z7_COM7F_IMF(CHandler::Open(IInStream *stream, + const UInt64 * /* maxCheckStartPosition */, + IArchiveOpenCallback * /* openArchiveCallback */)) +{ + COM_TRY_BEGIN + Close(); + try + { + if (Open2(stream) != S_OK) + return S_FALSE; + _stream = stream; + + { + CMyComPtr parseStream; + if (GetStream(0, &parseStream) == S_OK && parseStream) + { + const size_t kParseSize = 1 << 11; + Byte buf[kParseSize]; + if (ReadStream_FAIL(parseStream, buf, kParseSize) == S_OK) + { + UInt64 extSize; + if (NExt::IsArc_Ext_PhySize(buf, kParseSize, &extSize) == k_IsArc_Res_YES) + if (extSize == Footer.original_image_size) + Ext = "ext"; + } + } + } + } + catch(...) { return S_FALSE; } + return S_OK; + COM_TRY_END +} + + +Z7_COM7F_IMF(CHandler::Close()) +{ + _stream.Release(); + // _startOffset = 0; + _phySize = 0; + Ext = NULL; + Name.Empty(); + return S_OK; +} + + +static const Byte kArcProps[] = +{ + kpidName +}; + +static const Byte kProps[] = +{ + kpidSize, + kpidPackSize, +}; + +IMP_IInArchive_Props +IMP_IInArchive_ArcProps + + +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) +{ + COM_TRY_BEGIN + NCOM::CPropVariant prop; + switch (propID) + { + case kpidMainSubfile: prop = (UInt32)0; break; + case kpidPhySize: prop = _phySize; break; + case kpidName: + { + if (!Name.IsEmpty()) + { + AString s (Name); + s += ".avb"; + prop = s; + } + break; + } + } + prop.Detach(value); + return S_OK; + COM_TRY_END +} + + +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIANT *value)) +{ + COM_TRY_BEGIN + NCOM::CPropVariant prop; + + switch (propID) + { + case kpidPath: + { + if (!Name.IsEmpty()) + { + AString s (Name); + s += '.'; + s += Ext ? Ext : "img"; + prop = s; + } + break; + } + case kpidPackSize: + case kpidSize: + prop = Footer.original_image_size; + break; + case kpidExtension: prop = (Ext ? Ext : "img"); break; + } + + prop.Detach(value); + return S_OK; + COM_TRY_END +} + + +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) +{ + *numItems = 1; + return S_OK; +} + + +REGISTER_ARC_I_NO_SIG( + "AVB", "avb img", NULL, 0xc0, + /* k_Signature, */ + 0, + /* NArcInfoFlags::kUseGlobalOffset | */ + NArcInfoFlags::kBackwardOpen + , + NULL) + +}} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Base64Handler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/Base64Handler.cpp new file mode 100644 index 00000000..4df491c1 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Base64Handler.cpp @@ -0,0 +1,488 @@ +// Base64Handler.cpp + +#include "StdAfx.h" + +#include "../../../C/CpuArch.h" + +#include "../../Common/ComTry.h" +#include "../../Common/MyBuffer.h" +#include "../../Common/IntToString.h" +#include "../../Common/MyVector.h" + +#include "../../Windows/PropVariant.h" + +#include "../Common/ProgressUtils.h" +#include "../Common/RegisterArc.h" +#include "../Common/StreamUtils.h" +#include "../Common/InBuffer.h" + +/* +spaces: + 9(TAB),10(LF),13(CR),32(SPACE) + Non-breaking space: + 0xa0 : Unicode, Windows code pages 1250-1258 + 0xff (unused): DOS code pages + +end of stream markers: '=' (0x3d): + "=" , if numBytes (% 3 == 2) + "==" , if numBytes (% 3 == 1) +*/ + + +static const Byte k_Base64Table[256] = +{ + 66,77,77,77,77,77,77,77,77,65,65,77,77,65,77,77, + 77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77, + 65,77,77,77,77,77,77,77,77,77,77,62,77,77,77,63, + 52,53,54,55,56,57,58,59,60,61,77,77,77,64,77,77, + 77, 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,77,77,77,77,77, + 77,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40, + 41,42,43,44,45,46,47,48,49,50,51,77,77,77,77,77, + 77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77, + 77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77, + 65,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77, + 77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77, + 77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77, + 77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77, + 77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77, + 77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77 +}; + +static const unsigned k_Code_Equals = 64; +static const unsigned k_Code_Space = 65; +static const unsigned k_Code_Zero = 66; + +API_FUNC_static_IsArc IsArc_Base64(const Byte *p, size_t size) +{ + size_t num = 0; + size_t firstSpace = 0; + + for (;;) + { + if (size == 0) + return k_IsArc_Res_NEED_MORE; + UInt32 c = k_Base64Table[(Byte)(*p++)]; + size--; + if (c < 64) + { + num++; + continue; + } + + if (c == k_Code_Space) + { + if (p[-1] == ' ' && firstSpace == 0) + firstSpace = num; + continue; + } + + if (c != k_Code_Equals) + return k_IsArc_Res_NO; + break; + } + + { + // we try to redece false positive detection here. + // we don't expect space character in starting base64 line + const unsigned kNumExpectedNonSpaceSyms = 20; + if (firstSpace != 0 && firstSpace < num && firstSpace < kNumExpectedNonSpaceSyms) + return k_IsArc_Res_NO; + } + + num &= 3; + + if (num <= 1) + return k_IsArc_Res_NO; + if (num != 3) + { + if (size == 0) + return k_IsArc_Res_NEED_MORE; + UInt32 c = k_Base64Table[(Byte)(*p++)]; + size--; + if (c != k_Code_Equals) + return k_IsArc_Res_NO; + } + + for (;;) + { + if (size == 0) + return k_IsArc_Res_YES; + UInt32 c = k_Base64Table[(Byte)(*p++)]; + size--; + if (c == k_Code_Space) + continue; + return k_IsArc_Res_NO; + } +} +} + + +enum EBase64Res +{ + k_Base64_RES_MaybeFinished, + k_Base64_RES_Finished, + k_Base64_RES_NeedMoreInput, + k_Base64_RES_UnexpectedChar +}; + + +static EBase64Res Base64ToBin(Byte *p, size_t size, const Byte **srcEnd, Byte **destEnd) +{ + Byte *dest = p; + UInt32 val = 1; + EBase64Res res = k_Base64_RES_NeedMoreInput; + + for (;;) + { + if (size == 0) + { + if (val == 1) + res = k_Base64_RES_MaybeFinished; + break; + } + UInt32 c = k_Base64Table[(Byte)(*p++)]; + size--; + if (c < 64) + { + val = (val << 6) | c; + if ((val & ((UInt32)1 << 24)) == 0) + continue; + dest[0] = (Byte)(val >> 16); + dest[1] = (Byte)(val >> 8); + dest[2] = (Byte)(val); + dest += 3; + val = 1; + continue; + } + + if (c == k_Code_Space) + continue; + + if (c == k_Code_Equals) + { + if (val >= (1 << 12)) + { + if (val & (1 << 18)) + { + res = k_Base64_RES_Finished; + break; + } + if (size == 0) + break; + c = k_Base64Table[(Byte)(*p++)]; + size--; + if (c == k_Code_Equals) + { + res = k_Base64_RES_Finished; + break; + } + } + } + + p--; + res = k_Base64_RES_UnexpectedChar; + break; + } + + if (val >= ((UInt32)1 << 12)) + { + if (val & (1 << 18)) + { + *dest++ = (Byte)(val >> 10); + val <<= 2; + } + *dest++ = (Byte)(val >> 4); + } + + *srcEnd = p; + *destEnd = dest; + return res; +} + + +static const Byte *Base64_SkipSpaces(const Byte *p, size_t size) +{ + for (;;) + { + if (size == 0) + return p; + const UInt32 c = k_Base64Table[(Byte)(*p++)]; + size--; + if (c == k_Code_Space) + continue; + return p - 1; + } +} + + +// the following function is used by DmgHandler.cpp + +Byte *Base64ToBin(Byte *dest, const char *src); +Byte *Base64ToBin(Byte *dest, const char *src) +{ + UInt32 val = 1; + + for (;;) + { + const UInt32 c = k_Base64Table[(Byte)(*src++)]; + + if (c < 64) + { + val = (val << 6) | c; + if ((val & ((UInt32)1 << 24)) == 0) + continue; + dest[0] = (Byte)(val >> 16); + dest[1] = (Byte)(val >> 8); + dest[2] = (Byte)(val); + dest += 3; + val = 1; + continue; + } + + if (c == k_Code_Space) + continue; + + if (c == k_Code_Equals) + break; + + if (c == k_Code_Zero && val == 1) // end of string + return dest; + + return NULL; + } + + if (val < (1 << 12)) + return NULL; + + if (val & (1 << 18)) + { + *dest++ = (Byte)(val >> 10); + val <<= 2; + } + else if (k_Base64Table[(Byte)(*src++)] != k_Code_Equals) + return NULL; + *dest++ = (Byte)(val >> 4); + + for (;;) + { + const Byte c = k_Base64Table[(Byte)(*src++)]; + if (c == k_Code_Space) + continue; + if (c == k_Code_Zero) + return dest; + return NULL; + } +} + + +namespace NArchive { +namespace NBase64 { + +Z7_CLASS_IMP_CHandler_IInArchive_0 + + bool _isArc; + UInt64 _phySize; + size_t _size; + EBase64Res _sres; + CByteBuffer _data; +}; + +static const Byte kProps[] = +{ + kpidSize, + kpidPackSize, +}; + +IMP_IInArchive_Props +IMP_IInArchive_ArcProps_NO_Table + +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) +{ + *numItems = 1; + return S_OK; +} + +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) +{ + NWindows::NCOM::CPropVariant prop; + switch (propID) + { + case kpidPhySize: if (_phySize != 0) prop = _phySize; break; + case kpidErrorFlags: + { + UInt32 v = 0; + if (!_isArc) v |= kpv_ErrorFlags_IsNotArc; + if (_sres == k_Base64_RES_NeedMoreInput) v |= kpv_ErrorFlags_UnexpectedEnd; + if (v != 0) + prop = v; + break; + } + } + prop.Detach(value); + return S_OK; +} + +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIANT *value)) +{ + // COM_TRY_BEGIN + NWindows::NCOM::CPropVariant prop; + switch (propID) + { + case kpidSize: prop = (UInt64)_size; break; + case kpidPackSize: prop = _phySize; break; + } + prop.Detach(value); + return S_OK; + // COM_TRY_END +} + + +static HRESULT ReadStream_OpenProgress(ISequentialInStream *stream, void *data, size_t size, IArchiveOpenCallback *openCallback) throw() +{ + UInt64 bytes = 0; + while (size != 0) + { + const UInt32 kBlockSize = ((UInt32)1 << 24); + const UInt32 curSize = (size < kBlockSize) ? (UInt32)size : kBlockSize; + UInt32 processedSizeLoc; + RINOK(stream->Read(data, curSize, &processedSizeLoc)) + if (processedSizeLoc == 0) + return E_FAIL; + data = (void *)((Byte *)data + processedSizeLoc); + size -= processedSizeLoc; + bytes += processedSizeLoc; + const UInt64 files = 1; + RINOK(openCallback->SetCompleted(&files, &bytes)) + } + return S_OK; +} + + +Z7_COM7F_IMF(CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *openCallback)) +{ + COM_TRY_BEGIN + { + Close(); + { + const unsigned kStartSize = 1 << 12; + _data.Alloc(kStartSize); + size_t size = kStartSize; + RINOK(ReadStream(stream, _data, &size)) + if (IsArc_Base64(_data, size) == k_IsArc_Res_NO) + return S_FALSE; + } + _isArc = true; + + UInt64 packSize64; + RINOK(InStream_GetSize_SeekToEnd(stream, packSize64)) + if (packSize64 == 0) + return S_FALSE; + size_t curSize = 1 << 16; + if (curSize > packSize64) + curSize = (size_t)packSize64; + const unsigned kLogStep = 4; + + for (;;) + { + RINOK(InStream_SeekSet(stream, 0)) + + _data.Alloc(curSize); + RINOK(ReadStream_OpenProgress(stream, _data, curSize, openCallback)) + + const Byte *srcEnd; + Byte *dest; + _sres = Base64ToBin(_data, curSize, &srcEnd, &dest); + _size = (size_t)(dest - _data); + const size_t mainSize = (size_t)(srcEnd - _data); + _phySize = mainSize; + if (_sres == k_Base64_RES_UnexpectedChar) + break; + if (curSize != mainSize) + { + const Byte *end2 = Base64_SkipSpaces(srcEnd, curSize - mainSize); + if ((size_t)(end2 - _data) != curSize) + break; + _phySize = curSize; + } + + if (curSize == packSize64) + break; + + UInt64 curSize64 = packSize64; + if (curSize < (packSize64 >> kLogStep)) + curSize64 = (UInt64)curSize << kLogStep; + curSize = (size_t)curSize64; + if (curSize != curSize64) + return E_OUTOFMEMORY; + } + if (_size == 0) + return S_FALSE; + return S_OK; + } + COM_TRY_END +} + +Z7_COM7F_IMF(CHandler::Close()) +{ + _phySize = 0; + _size = 0; + _isArc = false; + _sres = k_Base64_RES_MaybeFinished; + _data.Free(); + return S_OK; +} + + +Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testMode, IArchiveExtractCallback *extractCallback)) +{ + COM_TRY_BEGIN + if (numItems == 0) + return S_OK; + if (numItems != (UInt32)(Int32)-1 && (numItems != 1 || indices[0] != 0)) + return E_INVALIDARG; + + RINOK(extractCallback->SetTotal(_size)) + CMyComPtr2_Create lps; + lps->Init(extractCallback, false); + // RINOK(lps->SetCur()) + Int32 opRes; + { + CMyComPtr realOutStream; + const Int32 askMode = testMode ? + NExtract::NAskMode::kTest : + NExtract::NAskMode::kExtract; + RINOK(extractCallback->GetStream(0, &realOutStream, askMode)) + if (!testMode && !realOutStream) + return S_OK; + RINOK(extractCallback->PrepareOperation(askMode)) + if (realOutStream) + { + RINOK(WriteStream(realOutStream, (const Byte *)_data, _size)) + } + opRes = NExtract::NOperationResult::kOK; + if (_sres != k_Base64_RES_Finished) + { + if (_sres == k_Base64_RES_NeedMoreInput) + opRes = NExtract::NOperationResult::kUnexpectedEnd; + else if (_sres == k_Base64_RES_UnexpectedChar) + opRes = NExtract::NOperationResult::kDataError; + } + } + RINOK(extractCallback->SetOperationResult(opRes)) + lps->InSize = _phySize; + lps->OutSize = _size; + return lps->SetCur(); + COM_TRY_END +} + +REGISTER_ARC_I_NO_SIG( + "Base64", "b64", NULL, 0xC5, + 0, + NArcInfoFlags::kKeepName + | NArcInfoFlags::kStartOpen + | NArcInfoFlags::kByExtOnlyOpen, + IsArc_Base64) + +}} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Bz2Handler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/Bz2Handler.cpp index d1d5f727..02eeee6e 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Bz2Handler.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Bz2Handler.cpp @@ -20,13 +20,11 @@ using namespace NWindows; namespace NArchive { namespace NBz2 { -class CHandler: - public IInArchive, - public IArchiveOpenSeq, - public IOutArchive, - public ISetProperties, - public CMyUnknownImp -{ +Z7_CLASS_IMP_CHandler_IInArchive_3( + IArchiveOpenSeq, + IOutArchive, + ISetProperties +) CMyComPtr _stream; CMyComPtr _seqStream; @@ -46,19 +44,6 @@ class CHandler: UInt64 _numBlocks; CSingleMethodProps _props; - -public: - MY_UNKNOWN_IMP4( - IInArchive, - IArchiveOpenSeq, - IOutArchive, - ISetProperties) - INTERFACE_IInArchive(;) - INTERFACE_IOutArchive(;) - STDMETHOD(OpenSeq)(ISequentialInStream *stream); - STDMETHOD(SetProperties)(const wchar_t * const *names, const PROPVARIANT *values, UInt32 numProps); - - CHandler() { } }; static const Byte kProps[] = @@ -76,7 +61,7 @@ static const Byte kArcProps[] = IMP_IInArchive_Props IMP_IInArchive_ArcProps -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { NCOM::CPropVariant prop; switch (propID) @@ -88,29 +73,32 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) case kpidErrorFlags: { UInt32 v = 0; - if (!_isArc) v |= kpv_ErrorFlags_IsNotArc;; + if (!_isArc) v |= kpv_ErrorFlags_IsNotArc; if (_needMoreInput) v |= kpv_ErrorFlags_UnexpectedEnd; if (_dataAfterEnd) v |= kpv_ErrorFlags_DataAfterEnd; prop = v; + break; } + default: break; } prop.Detach(value); return S_OK; } -STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) { *numItems = 1; return S_OK; } -STDMETHODIMP CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIANT *value)) { NCOM::CPropVariant prop; switch (propID) { case kpidPackSize: if (_packSize_Defined) prop = _packSize; break; case kpidSize: if (_unpackSize_Defined) prop = _unpackSize; break; + default: break; } prop.Detach(value); return S_OK; @@ -133,13 +121,13 @@ API_FUNC_static_IsArc IsArc_BZip2(const Byte *p, size_t size) } } -STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *) +Z7_COM7F_IMF(CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *)) { COM_TRY_BEGIN Close(); { Byte buf[kSignatureCheckSize]; - RINOK(ReadStream_FALSE(stream, buf, kSignatureCheckSize)); + RINOK(ReadStream_FALSE(stream, buf, kSignatureCheckSize)) if (IsArc_BZip2(buf, kSignatureCheckSize) == k_IsArc_Res_NO) return S_FALSE; _isArc = true; @@ -152,7 +140,7 @@ STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallb } -STDMETHODIMP CHandler::OpenSeq(ISequentialInStream *stream) +Z7_COM7F_IMF(CHandler::OpenSeq(ISequentialInStream *stream)) { Close(); _isArc = true; @@ -160,7 +148,7 @@ STDMETHODIMP CHandler::OpenSeq(ISequentialInStream *stream) return S_OK; } -STDMETHODIMP CHandler::Close() +Z7_COM7F_IMF(CHandler::Close()) { _isArc = false; _needSeekToStart = false; @@ -179,8 +167,9 @@ STDMETHODIMP CHandler::Close() return S_OK; } -STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, - Int32 testMode, IArchiveExtractCallback *extractCallback) + +Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testMode, IArchiveExtractCallback *extractCallback)) { COM_TRY_BEGIN if (numItems == 0) @@ -189,200 +178,237 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, return E_INVALIDARG; if (_packSize_Defined) - extractCallback->SetTotal(_packSize); - - // RINOK(extractCallback->SetCompleted(&packSize)); + { + RINOK(extractCallback->SetTotal(_packSize)) + } + Int32 opRes; + { CMyComPtr realOutStream; - Int32 askMode = testMode ? + const Int32 askMode = testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; - RINOK(extractCallback->GetStream(0, &realOutStream, askMode)); + RINOK(extractCallback->GetStream(0, &realOutStream, askMode)) if (!testMode && !realOutStream) return S_OK; - extractCallback->PrepareOperation(askMode); - + RINOK(extractCallback->PrepareOperation(askMode)) if (_needSeekToStart) { if (!_stream) return E_FAIL; - RINOK(_stream->Seek(0, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekToBegin(_stream)) } else _needSeekToStart = true; - Int32 opRes; - - try - { + // try { - NCompress::NBZip2::CDecoder *decoderSpec = new NCompress::NBZip2::CDecoder; - CMyComPtr decoder = decoderSpec; - decoderSpec->SetInStream(_seqStream); + CMyComPtr2_Create decoder; - #ifndef _7ZIP_ST - RINOK(decoderSpec->SetNumberOfThreads(_props._numThreads)); + #ifndef Z7_ST + RINOK(decoder->SetNumberOfThreads(_props._numThreads)) #endif - CDummyOutStream *outStreamSpec = new CDummyOutStream; - CMyComPtr outStream(outStreamSpec); - outStreamSpec->SetStream(realOutStream); - outStreamSpec->Init(); - - realOutStream.Release(); + CMyComPtr2_Create outStream; + outStream->SetStream(realOutStream); + outStream->Init(); + // realOutStream.Release(); - CLocalProgress *lps = new CLocalProgress; - CMyComPtr progress = lps; + CMyComPtr2_Create lps; lps->Init(extractCallback, true); - UInt64 packSize = 0; - UInt64 unpackedSize = 0; - UInt64 numStreams = 0; - - decoderSpec->InitNumBlocks(); + decoder->FinishMode = true; + decoder->Base.DecodeAllStreams = true; - HRESULT result = S_OK; + _dataAfterEnd = false; + _needMoreInput = false; - for (;;) + HRESULT result = decoder.Interface()->Code(_seqStream, outStream, NULL, NULL, lps); + + if (result != S_FALSE && result != S_OK) + return result; + + if (decoder->Base.NumStreams == 0) { - lps->InSize = packSize; - lps->OutSize = unpackedSize; - - RINOK(lps->SetCur()); - - result = decoderSpec->CodeResume(outStream, progress); - - if (result != S_FALSE && result != S_OK) - return result; - - if (decoderSpec->IsBz) - numStreams++; - else if (numStreams == 0) - { - _isArc = false; - result = S_FALSE; - break; - } - - unpackedSize = outStreamSpec->GetSize(); - UInt64 streamSize = decoderSpec->GetStreamSize(); + _isArc = false; + result = S_FALSE; + } + else + { + const UInt64 inProcessedSize = decoder->GetInputProcessedSize(); + UInt64 packSize = inProcessedSize; - if (streamSize == packSize) - { - // no new bytes in input stream, So it's good end of archive. - result = S_OK; - break; - } + if (decoder->Base.NeedMoreInput) + _needMoreInput = true; - if (!decoderSpec->IsBz) - { - _dataAfterEnd = true; - result = S_FALSE; - break; - } - - if (decoderSpec->Base.BitDecoder.ExtraBitsWereRead()) + if (!decoder->Base.IsBz) { - _needMoreInput = true; - packSize = streamSize; - result = S_FALSE; - break; + packSize = decoder->Base.FinishedPackSize; + if (packSize != inProcessedSize) + _dataAfterEnd = true; } - packSize = decoderSpec->GetInputProcessedSize(); - - if (packSize > streamSize) - return E_FAIL; - - if (result != S_OK) - break; - } - - if (numStreams != 0) - { _packSize = packSize; - _unpackSize = unpackedSize; - _numStreams = numStreams; - _numBlocks = decoderSpec->GetNumBlocks(); + _unpackSize = decoder->GetOutProcessedSize(); + _numStreams = decoder->Base.NumStreams; + _numBlocks = decoder->GetNumBlocks(); _packSize_Defined = true; _unpackSize_Defined = true; _numStreams_Defined = true; _numBlocks_Defined = true; + + // RINOK( + lps.Interface()->SetRatioInfo(&packSize, &_unpackSize); } - decoderSpec->ReleaseInStream(); - outStream.Release(); + // outStream.Release(); if (!_isArc) opRes = NExtract::NOperationResult::kIsNotArc; else if (_needMoreInput) opRes = NExtract::NOperationResult::kUnexpectedEnd; - else if (decoderSpec->CrcError) + else if (decoder->GetCrcError()) opRes = NExtract::NOperationResult::kCRCError; else if (_dataAfterEnd) opRes = NExtract::NOperationResult::kDataAfterEnd; else if (result == S_FALSE) opRes = NExtract::NOperationResult::kDataError; + else if (decoder->Base.MinorError) + opRes = NExtract::NOperationResult::kDataError; else if (result == S_OK) opRes = NExtract::NOperationResult::kOK; else return result; - } - catch(const CInBufferException &e) { return e.ErrorCode; } - + } return extractCallback->SetOperationResult(opRes); + // } catch(...) { return E_FAIL; } + COM_TRY_END } + +/* +static HRESULT ReportItemProp(IArchiveUpdateCallbackArcProp *reportArcProp, PROPID propID, const PROPVARIANT *value) +{ + return reportArcProp->ReportProp(NEventIndexType::kOutArcIndex, 0, propID, value); +} + +static HRESULT ReportArcProp(IArchiveUpdateCallbackArcProp *reportArcProp, PROPID propID, const PROPVARIANT *value) +{ + return reportArcProp->ReportProp(NEventIndexType::kArcProp, 0, propID, value); +} + +static HRESULT ReportArcProps(IArchiveUpdateCallbackArcProp *reportArcProp, + const UInt64 *unpackSize, + const UInt64 *numBlocks) +{ + NCOM::CPropVariant sizeProp; + if (unpackSize) + { + sizeProp = *unpackSize; + RINOK(ReportItemProp(reportArcProp, kpidSize, &sizeProp)); + RINOK(reportArcProp->ReportFinished(NEventIndexType::kOutArcIndex, 0, NArchive::NUpdate::NOperationResult::kOK)); + } + + if (unpackSize) + { + RINOK(ReportArcProp(reportArcProp, kpidSize, &sizeProp)); + } + if (numBlocks) + { + NCOM::CPropVariant prop; + prop = *numBlocks; + RINOK(ReportArcProp(reportArcProp, kpidNumBlocks, &prop)); + } + return S_OK; +} +*/ + static HRESULT UpdateArchive( UInt64 unpackSize, ISequentialOutStream *outStream, const CProps &props, - IArchiveUpdateCallback *updateCallback) + IArchiveUpdateCallback *updateCallback + // , ArchiveUpdateCallbackArcProp *reportArcProp + ) { - RINOK(updateCallback->SetTotal(unpackSize)); - CMyComPtr fileInStream; - RINOK(updateCallback->GetStream(0, &fileInStream)); - CLocalProgress *localProgressSpec = new CLocalProgress; - CMyComPtr localProgress = localProgressSpec; - localProgressSpec->Init(updateCallback, true); - NCompress::NBZip2::CEncoder *encoderSpec = new NCompress::NBZip2::CEncoder; - CMyComPtr encoder = encoderSpec; - RINOK(props.SetCoderProps(encoderSpec, NULL)); - RINOK(encoder->Code(fileInStream, outStream, NULL, NULL, localProgress)); + { + CMyComPtr fileInStream; + RINOK(updateCallback->GetStream(0, &fileInStream)) + if (!fileInStream) + return S_FALSE; + { + Z7_DECL_CMyComPtr_QI_FROM( + IStreamGetSize, + streamGetSize, fileInStream) + if (streamGetSize) + { + UInt64 size; + if (streamGetSize->GetSize(&size) == S_OK) + unpackSize = size; + } + } + RINOK(updateCallback->SetTotal(unpackSize)) + + CMyComPtr2_Create lps; + lps->Init(updateCallback, true); + { + CMyComPtr2_Create encoder; + RINOK(props.SetCoderProps(encoder.ClsPtr(), NULL)) + RINOK(encoder.Interface()->Code(fileInStream, outStream, NULL, NULL, lps)) + /* + if (reportArcProp) + { + unpackSize = encoderSpec->GetInProcessedSize(); + RINOK(ReportArcProps(reportArcProp, &unpackSize, &encoderSpec->NumBlocks)); + } + */ + } + } return updateCallback->SetOperationResult(NArchive::NUpdate::NOperationResult::kOK); } -STDMETHODIMP CHandler::GetFileTimeType(UInt32 *type) +Z7_COM7F_IMF(CHandler::GetFileTimeType(UInt32 *timeType)) { - *type = NFileTimeType::kUnix; + *timeType = GET_FileTimeType_NotDefined_for_GetFileTimeType; + // *timeType = NFileTimeType::kUnix; return S_OK; } -STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numItems, - IArchiveUpdateCallback *updateCallback) +Z7_COM7F_IMF(CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numItems, + IArchiveUpdateCallback *updateCallback)) { COM_TRY_BEGIN if (numItems != 1) return E_INVALIDARG; + { + Z7_DECL_CMyComPtr_QI_FROM( + IStreamSetRestriction, + setRestriction, outStream) + if (setRestriction) + RINOK(setRestriction->SetRestriction(0, 0)) + } + Int32 newData, newProps; UInt32 indexInArchive; if (!updateCallback) return E_FAIL; - RINOK(updateCallback->GetUpdateItemInfo(0, &newData, &newProps, &indexInArchive)); + RINOK(updateCallback->GetUpdateItemInfo(0, &newData, &newProps, &indexInArchive)) + // Z7_DECL_CMyComPtr_QI_FROM(IArchiveUpdateCallbackArcProp, reportArcProp, updateCallback) + if (IntToBool(newProps)) { { NCOM::CPropVariant prop; - RINOK(updateCallback->GetProperty(0, kpidIsDir, &prop)); + RINOK(updateCallback->GetProperty(0, kpidIsDir, &prop)) if (prop.vt != VT_EMPTY) if (prop.vt != VT_BOOL || prop.boolVal != VARIANT_FALSE) return E_INVALIDARG; @@ -394,23 +420,33 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numIt UInt64 size; { NCOM::CPropVariant prop; - RINOK(updateCallback->GetProperty(0, kpidSize, &prop)); + RINOK(updateCallback->GetProperty(0, kpidSize, &prop)) if (prop.vt != VT_UI8) return E_INVALIDARG; size = prop.uhVal.QuadPart; } - return UpdateArchive(size, outStream, _props, updateCallback); + + CMethodProps props2 = _props; +#ifndef Z7_ST + props2.AddProp_NumThreads(_props._numThreads); +#ifdef _WIN32 + if (_props._numThreadGroups > 1) + props2.AddProp32(NCoderPropID::kNumThreadGroups, _props._numThreadGroups); +#endif +#endif + + return UpdateArchive(size, outStream, props2, updateCallback); } if (indexInArchive != 0) return E_INVALIDARG; - CLocalProgress *lps = new CLocalProgress; - CMyComPtr progress = lps; + CMyComPtr2_Create lps; lps->Init(updateCallback, true); - CMyComPtr opCallback; - updateCallback->QueryInterface(IID_IArchiveUpdateCallbackFile, (void **)&opCallback); + Z7_DECL_CMyComPtr_QI_FROM( + IArchiveUpdateCallbackFile, + opCallback, updateCallback) if (opCallback) { RINOK(opCallback->ReportOperation( @@ -419,14 +455,16 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numIt } if (_stream) - RINOK(_stream->Seek(0, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekToBegin(_stream)) + + return NCompress::CopyStream(_stream, outStream, lps); - return NCompress::CopyStream(_stream, outStream, progress); + // return ReportArcProps(reportArcProp, NULL, NULL); COM_TRY_END } -STDMETHODIMP CHandler::SetProperties(const wchar_t * const *names, const PROPVARIANT *values, UInt32 numProps) +Z7_COM7F_IMF(CHandler::SetProperties(const wchar_t * const *names, const PROPVARIANT *values, UInt32 numProps)) { return _props.SetProperties(names, values, numProps); } @@ -437,7 +475,8 @@ REGISTER_ARC_IO( "bzip2", "bz2 bzip2 tbz2 tbz", "* * .tar .tar", 2, k_Signature, 0, - NArcInfoFlags::kKeepName, - IsArc_BZip2) + NArcInfoFlags::kKeepName + , 0 + , IsArc_BZip2) }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Cab/CabBlockInStream.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/Cab/CabBlockInStream.cpp index c193434f..5e1fe8ba 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Cab/CabBlockInStream.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Cab/CabBlockInStream.cpp @@ -12,89 +12,111 @@ namespace NArchive { namespace NCab { -static const UInt32 kBlockSize = (1 << 16); +static const UInt32 kBlockSize = 1 << 16; +static const unsigned k_OverReadPadZone_Size = 32; +static const unsigned kHeaderSize = 8; +static const unsigned kReservedMax = 256; +static const unsigned kHeaderOffset = kBlockSize + k_OverReadPadZone_Size; -bool CCabBlockInStream::Create() +bool CBlockPackData::Create() throw() { if (!_buf) - _buf = (Byte *)::MyAlloc(kBlockSize); - return _buf != 0; + _buf = (Byte *)z7_AlignedAlloc(kBlockSize + k_OverReadPadZone_Size + kHeaderSize + kReservedMax); + return _buf != NULL; } -CCabBlockInStream::~CCabBlockInStream() +CBlockPackData::~CBlockPackData() throw() { - ::MyFree(_buf); + z7_AlignedFree(_buf); } -static UInt32 CheckSum(const Byte *p, UInt32 size) +static UInt32 CheckSum(const Byte *p, UInt32 size) throw() { +#ifdef MY_CPU_64BIT + + UInt64 sum64 = 0; + if (size >= 16) + { + const Byte *lim = p + (size_t)size - 16; + do + { + sum64 ^= GetUi64(p) ^ GetUi64(p + 8); + p += 16; + } + while (p <= lim); + size = (UInt32)(lim + 16 - p); + } + if (size >= 8) + { + sum64 ^= GetUi64(p); + p += 8; + size -= 8; + } + + UInt32 sum = (UInt32)(sum64 >> 32) ^ (UInt32)sum64; + +#else + UInt32 sum = 0; - - for (; size >= 8; size -= 8) + if (size >= 16) + { + const Byte *lim = p + (size_t)size - 16; + do + { + sum ^= GetUi32(p) + ^ GetUi32(p + 4) + ^ GetUi32(p + 8) + ^ GetUi32(p + 12); + p += 16; + } + while (p <= lim); + size = (UInt32)(lim + 16 - p); + } + if (size >= 8) { - sum ^= GetUi32(p) ^ GetUi32(p + 4); + sum ^= GetUi32(p + 0) ^ GetUi32(p + 4); p += 8; + size -= 8; } + +#endif if (size >= 4) { sum ^= GetUi32(p); p += 4; } - - size &= 3; - if (size > 2) sum ^= (UInt32)(*p++) << 16; - if (size > 1) sum ^= (UInt32)(*p++) << 8; - if (size > 0) sum ^= (UInt32)(*p++); - + if (size &= 3) + { + if (size >= 2) + { + if (size > 2) + sum ^= (UInt32)(*p++) << 16; + sum ^= (UInt32)(*p++) << 8; + } + sum ^= (UInt32)(*p++); + } return sum; } -HRESULT CCabBlockInStream::PreRead(ISequentialInStream *stream, UInt32 &packSize, UInt32 &unpackSize) + +HRESULT CBlockPackData::Read(ISequentialInStream *stream, Byte ReservedSize, UInt32 &packSizeRes, UInt32 &unpackSize) throw() { - const UInt32 kHeaderSize = 8; - const UInt32 kReservedMax = 256; - Byte header[kHeaderSize + kReservedMax]; - RINOK(ReadStream_FALSE(stream, header, kHeaderSize + ReservedSize)) - packSize = GetUi16(header + 4); - unpackSize = GetUi16(header + 6); + const UInt32 reserved8 = kHeaderSize + ReservedSize; + const Byte *header = _buf + kHeaderOffset; + RINOK(ReadStream_FALSE(stream, (void *)header, reserved8)) + unpackSize = GetUi16a(header + 6); + const UInt32 packSize = GetUi16a(header + 4); + packSizeRes = packSize; if (packSize > kBlockSize - _size) return S_FALSE; - RINOK(ReadStream_FALSE(stream, _buf + _size, packSize)); - - if (MsZip) - { - if (_size == 0) - { - if (packSize < 2 || _buf[0] != 0x43 || _buf[1] != 0x4B) - return S_FALSE; - _pos = 2; - } - if (_size + packSize > ((UInt32)1 << 15) + 12) /* v9.31 fix. MSZIP specification */ - return S_FALSE; - } - - if (GetUi32(header) != 0) // checkSum - if (CheckSum(header, kHeaderSize + ReservedSize) != CheckSum(_buf + _size, packSize)) + RINOK(ReadStream_FALSE(stream, _buf + _size, packSize)) + memset(_buf + _size + packSize, 0xff, k_OverReadPadZone_Size); + if (*(const UInt32 *)(const void *)header != 0) // checkSum + if (CheckSum(header, reserved8) != CheckSum(_buf + _size, packSize)) return S_FALSE; - _size += packSize; return S_OK; } -STDMETHODIMP CCabBlockInStream::Read(void *data, UInt32 size, UInt32 *processedSize) -{ - if (size != 0) - { - UInt32 rem = _size - _pos; - if (size > rem) - size = rem; - memcpy(data, _buf + _pos, size); - _pos += size; - } - if (processedSize) - *processedSize = size; - return S_OK; -} - }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Cab/CabBlockInStream.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Cab/CabBlockInStream.h index af89abb6..75687792 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Cab/CabBlockInStream.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Cab/CabBlockInStream.h @@ -1,41 +1,26 @@ // CabBlockInStream.h -#ifndef __CAB_BLOCK_IN_STREAM_H -#define __CAB_BLOCK_IN_STREAM_H +#ifndef ZIP7_INC_CAB_BLOCK_IN_STREAM_H +#define ZIP7_INC_CAB_BLOCK_IN_STREAM_H -#include "../../../Common/MyCom.h" #include "../../IStream.h" namespace NArchive { namespace NCab { -class CCabBlockInStream: - public ISequentialInStream, - public CMyUnknownImp +class CBlockPackData { Byte *_buf; UInt32 _size; - UInt32 _pos; - public: - UInt32 ReservedSize; // < 256 - bool MsZip; - - MY_UNKNOWN_IMP - - CCabBlockInStream(): _buf(0), ReservedSize(0), MsZip(false) {} - ~CCabBlockInStream(); - - bool Create(); - - void InitForNewBlock() { _size = 0; _pos = 0; } - - HRESULT PreRead(ISequentialInStream *stream, UInt32 &packSize, UInt32 &unpackSize); - - UInt32 GetPackSizeAvail() const { return _size - _pos; } - const Byte *GetData() const { return _buf + _pos; } - - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); + CBlockPackData(): _buf(NULL), _size(0) {} + ~CBlockPackData() throw(); + bool Create() throw(); + void InitForNewBlock() { _size = 0; } + HRESULT Read(ISequentialInStream *stream, Byte ReservedSize, UInt32 &packSize, UInt32 &unpackSize) throw(); + UInt32 GetPackSize() const { return _size; } + // 32 bytes of overread zone is available after PackSize: + const Byte *GetData() const { return _buf; } }; }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Cab/CabHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/Cab/CabHandler.cpp index 18f58b3d..0ad2a0ed 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Cab/CabHandler.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Cab/CabHandler.cpp @@ -5,7 +5,9 @@ // #include #include "../../../../C/Alloc.h" +#include "../../../../C/CpuArch.h" +#include "../../../Common/AutoPtr.h" #include "../../../Common/ComTry.h" #include "../../../Common/IntToString.h" #include "../../../Common/StringConvert.h" @@ -15,9 +17,9 @@ #include "../../../Windows/TimeUtils.h" #include "../../Common/ProgressUtils.h" +#include "../../Common/StreamObjects.h" #include "../../Common/StreamUtils.h" -#include "../../Compress/CopyCoder.h" #include "../../Compress/DeflateDecoder.h" #include "../../Compress/LzxDecoder.h" #include "../../Compress/QuantumDecoder.h" @@ -32,9 +34,9 @@ using namespace NWindows; namespace NArchive { namespace NCab { -// #define _CAB_DETAILS +// #define CAB_DETAILS -#ifdef _CAB_DETAILS +#ifdef CAB_DETAILS enum { kpidBlockReal = kpidUserDefined @@ -49,7 +51,7 @@ static const Byte kProps[] = kpidAttrib, kpidMethod, kpidBlock - #ifdef _CAB_DETAILS + #ifdef CAB_DETAILS , // kpidBlockReal, // L"BlockReal", kpidOffset, @@ -83,7 +85,7 @@ static const unsigned kMethodNameBufSize = 32; // "Quantum:255" static void SetMethodName(char *s, unsigned method, unsigned param) { - if (method < ARRAY_SIZE(kMethods)) + if (method < Z7_ARRAY_SIZE(kMethods)) { s = MyStpCpy(s, kMethods[method]); if (method != NHeader::NMethod::kLZX && @@ -95,7 +97,7 @@ static void SetMethodName(char *s, unsigned method, unsigned param) ConvertUInt32ToString(method, s); } -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; @@ -112,14 +114,14 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) FOR_VECTOR (i, folders) { const CFolder &folder = folders[i]; - unsigned method = folder.GetMethod(); + const unsigned method = folder.GetMethod(); mask |= ((UInt32)1 << method); if (method == NHeader::NMethod::kLZX || method == NHeader::NMethod::kQuantum) { - unsigned di = (method == NHeader::NMethod::kQuantum) ? 0 : 1; + const unsigned di = (method == NHeader::NMethod::kQuantum) ? 0 : 1; if (params[di] < folder.MethodMinor) - params[di] = folder.MethodMinor; + params[di] = folder.MethodMinor; } } } @@ -226,12 +228,9 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) if (ai.SetID != 0) { AString s; - char temp[32]; - ConvertUInt32ToString(ai.SetID, temp); - s += temp; - ConvertUInt32ToString(ai.CabinetNumber + 1, temp); - s += '_'; - s += temp; + s.Add_UInt32(ai.SetID); + s.Add_Char('_'); + s.Add_UInt32(ai.CabinetNumber + 1); s += ".cab"; prop = s; } @@ -264,20 +263,21 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) } // case kpidShortComment: + default: break; } prop.Detach(value); return S_OK; COM_TRY_END } -STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; const CMvItem &mvItem = m_Database.Items[index]; const CDatabaseEx &db = m_Database.Volumes[mvItem.VolumeIndex]; - unsigned itemIndex = mvItem.ItemIndex; + const unsigned itemIndex = mvItem.ItemIndex; const CItem &item = db.Items[itemIndex]; switch (propID) { @@ -288,7 +288,7 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val ConvertUTF8ToUnicode(item.Name, unicodeName); else unicodeName = MultiByteToUnicodeString(item.Name, CP_ACP); - prop = (const wchar_t *)NItemName::WinNameToOSName(unicodeName); + prop = (const wchar_t *)NItemName::WinPathToOsPath(unicodeName); break; } @@ -298,53 +298,51 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val case kpidMTime: { - FILETIME localFileTime, utcFileTime; - if (NTime::DosTimeToFileTime(item.Time, localFileTime)) - { - if (!LocalFileTimeToFileTime(&localFileTime, &utcFileTime)) - utcFileTime.dwHighDateTime = utcFileTime.dwLowDateTime = 0; - } - else - utcFileTime.dwHighDateTime = utcFileTime.dwLowDateTime = 0; - prop = utcFileTime; + PropVariant_SetFrom_DosTime(prop, item.Time); break; } case kpidMethod: { - UInt32 realFolderIndex = item.GetFolderIndex(db.Folders.Size()); - const CFolder &folder = db.Folders[realFolderIndex]; - char s[kMethodNameBufSize];; - SetMethodName(s, folder.GetMethod(), folder.MethodMinor); - prop = s; + const int realFolderIndex = item.GetFolderIndex(db.Folders.Size()); + if (realFolderIndex >= 0) + { + const CFolder &folder = db.Folders[(unsigned)realFolderIndex]; + char s[kMethodNameBufSize]; + SetMethodName(s, folder.GetMethod(), folder.MethodMinor); + prop = s; + } break; } - case kpidBlock: prop = (Int32)m_Database.GetFolderIndex(&mvItem); break; + case kpidBlock: prop.Set_Int32((Int32)m_Database.GetFolderIndex(&mvItem)); break; - #ifdef _CAB_DETAILS + #ifdef CAB_DETAILS // case kpidBlockReal: prop = (UInt32)item.FolderIndex; break; case kpidOffset: prop = (UInt32)item.Offset; break; case kpidVolume: prop = (UInt32)mvItem.VolumeIndex; break; #endif + + default: break; } prop.Detach(value); return S_OK; COM_TRY_END } -STDMETHODIMP CHandler::Open(IInStream *inStream, +Z7_COM7F_IMF(CHandler::Open(IInStream *inStream, const UInt64 *maxCheckStartPosition, - IArchiveOpenCallback *callback) + IArchiveOpenCallback *callback)) { COM_TRY_BEGIN Close(); CInArchive archive; CMyComPtr openVolumeCallback; - callback->QueryInterface(IID_IArchiveOpenVolumeCallback, (void **)&openVolumeCallback); + if (callback) + callback->QueryInterface(IID_IArchiveOpenVolumeCallback, (void **)&openVolumeCallback); CMyComPtr nextStream = inStream; bool prevChecked = false; @@ -368,7 +366,7 @@ STDMETHODIMP CHandler::Open(IInStream *inStream, if (res == S_OK && !m_Database.Volumes.IsEmpty()) { const CArchInfo &lastArc = m_Database.Volumes.Back().ArcInfo; - unsigned cabNumber = db.ArcInfo.CabinetNumber; + const unsigned cabNumber = db.ArcInfo.CabinetNumber; if (lastArc.SetID != db.ArcInfo.SetID) res = S_FALSE; else if (prevChecked) @@ -423,7 +421,10 @@ STDMETHODIMP CHandler::Open(IInStream *inStream, } } - RINOK(callback->SetCompleted(&numItems, NULL)); + if (callback) + { + RINOK(callback->SetCompleted(&numItems, NULL)) + } nextStream = NULL; @@ -475,7 +476,7 @@ STDMETHODIMP CHandler::Open(IInStream *inStream, startVolName_was_Requested = true; { NCOM::CPropVariant prop; - RINOK(openVolumeCallback->GetProperty(kpidName, &prop)); + RINOK(openVolumeCallback->GetProperty(kpidName, &prop)) if (prop.vt == VT_BSTR) startVolName = prop.bstrVal; } @@ -483,7 +484,7 @@ STDMETHODIMP CHandler::Open(IInStream *inStream, break; } - HRESULT result = openVolumeCallback->GetStream(fullName, &nextStream); + const HRESULT result = openVolumeCallback->GetStream(fullName, &nextStream); if (result == S_OK) break; if (result != S_FALSE) @@ -491,7 +492,7 @@ STDMETHODIMP CHandler::Open(IInStream *inStream, if (!_errorMessage.IsEmpty()) _errorMessage.Add_LF(); - _errorMessage.AddAscii("Can't open volume: "); + _errorMessage += "Can't open volume: "; _errorMessage += fullName; if (prevChecked) @@ -524,7 +525,7 @@ STDMETHODIMP CHandler::Open(IInStream *inStream, return S_OK; } -STDMETHODIMP CHandler::Close() +Z7_COM7F_IMF(CHandler::Close()) { _errorMessage.Empty(); _isArc = false; @@ -538,37 +539,35 @@ STDMETHODIMP CHandler::Close() return S_OK; } -class CFolderOutStream: - public ISequentialOutStream, - public CMyUnknownImp -{ -public: - MY_UNKNOWN_IMP - STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize); -private: +Z7_CLASS_IMP_NOQIB_1( + CFolderOutStream + , ISequentialOutStream +) + bool m_TestMode; + bool TempBufMode; + bool m_IsOk; + bool m_FileIsOpen; + const CMvDatabaseEx *m_Database; const CRecordVector *m_ExtractStatuses; Byte *TempBuf; UInt32 TempBufSize; + UInt32 TempBufWritten; unsigned NumIdenticalFiles; - bool TempBufMode; - UInt32 m_BufStartFolderOffset; unsigned m_StartIndex; unsigned m_CurrentIndex; - CMyComPtr m_ExtractCallback; - bool m_TestMode; - - CMyComPtr m_RealOutStream; - bool m_IsOk; - bool m_FileIsOpen; UInt32 m_RemainFileSize; + UInt64 m_FolderSize; UInt64 m_PosInFolder; + CMyComPtr m_ExtractCallback; + CMyComPtr m_RealOutStream; + void FreeTempBuf() { ::MyFree(TempBuf); @@ -578,7 +577,6 @@ class CFolderOutStream: HRESULT OpenFile(); HRESULT CloseFileWithResOp(Int32 resOp); HRESULT CloseFile(); - HRESULT Write2(const void *data, UInt32 size, UInt32 *processedSize, bool isOK); public: HRESULT WriteEmptyFiles(); @@ -675,21 +673,21 @@ HRESULT CFolderOutStream::OpenFile() FreeTempBuf(); TempBuf = (Byte *)MyAlloc(item.Size); TempBufSize = item.Size; - if (TempBuf == NULL) + if (!TempBuf) return E_OUTOFMEMORY; } TempBufMode = true; - m_BufStartFolderOffset = item.Offset; + TempBufWritten = 0; } else if (numExtractItems == 1) { while (NumIdenticalFiles && !(*m_ExtractStatuses)[m_CurrentIndex]) { CMyComPtr stream; - RINOK(m_ExtractCallback->GetStream(m_StartIndex + m_CurrentIndex, &stream, NExtract::NAskMode::kSkip)); + RINOK(m_ExtractCallback->GetStream(m_StartIndex + m_CurrentIndex, &stream, NExtract::NAskMode::kSkip)) if (stream) return E_FAIL; - RINOK(m_ExtractCallback->PrepareOperation(NExtract::NAskMode::kSkip)); + RINOK(m_ExtractCallback->PrepareOperation(NExtract::NAskMode::kSkip)) m_CurrentIndex++; m_FileIsOpen = true; CloseFile(); @@ -697,11 +695,11 @@ HRESULT CFolderOutStream::OpenFile() } } - Int32 askMode = (*m_ExtractStatuses)[m_CurrentIndex] ? (m_TestMode ? + Int32 askMode = (*m_ExtractStatuses)[m_CurrentIndex] ? m_TestMode ? NExtract::NAskMode::kTest : - NExtract::NAskMode::kExtract) : + NExtract::NAskMode::kExtract : NExtract::NAskMode::kSkip; - RINOK(m_ExtractCallback->GetStream(m_StartIndex + m_CurrentIndex, &m_RealOutStream, askMode)); + RINOK(m_ExtractCallback->GetStream(m_StartIndex + m_CurrentIndex, &m_RealOutStream, askMode)) if (!m_RealOutStream && !m_TestMode) askMode = NExtract::NAskMode::kSkip; return m_ExtractCallback->PrepareOperation(askMode); @@ -716,20 +714,21 @@ HRESULT CFolderOutStream::WriteEmptyFiles() { const CMvItem &mvItem = m_Database->Items[m_StartIndex + m_CurrentIndex]; const CItem &item = m_Database->Volumes[mvItem.VolumeIndex].Items[mvItem.ItemIndex]; - UInt64 fileSize = item.Size; + const UInt64 fileSize = item.Size; if (fileSize != 0) return S_OK; - HRESULT result = OpenFile(); + const HRESULT result = OpenFile(); m_RealOutStream.Release(); - RINOK(result); - RINOK(m_ExtractCallback->SetOperationResult(NExtract::NOperationResult::kOK)); + RINOK(result) + RINOK(m_ExtractCallback->SetOperationResult(NExtract::NOperationResult::kOK)) } return S_OK; } -HRESULT CFolderOutStream::Write2(const void *data, UInt32 size, UInt32 *processedSize, bool isOK) +Z7_COM7F_IMF(CFolderOutStream::Write(const void *data, UInt32 size, UInt32 *processedSize)) { + // (data == NULL) means Error_Data for solid folder flushing COM_TRY_BEGIN UInt32 realProcessed = 0; @@ -744,21 +743,34 @@ HRESULT CFolderOutStream::Write2(const void *data, UInt32 size, UInt32 *processe HRESULT res = S_OK; if (numBytesToWrite != 0) { - if (!isOK) + if (!data) m_IsOk = false; + if (m_RealOutStream) { UInt32 processedSizeLocal = 0; - res = m_RealOutStream->Write((const Byte *)data, numBytesToWrite, &processedSizeLocal); + // 18.01 : we don't want ZEROs instead of missing data + if (data) + res = m_RealOutStream->Write((const Byte *)data, numBytesToWrite, &processedSizeLocal); + else + processedSizeLocal = numBytesToWrite; numBytesToWrite = processedSizeLocal; } + if (TempBufMode && TempBuf) - memcpy(TempBuf + (m_PosInFolder - m_BufStartFolderOffset), data, numBytesToWrite); + { + if (data) + { + memcpy(TempBuf + TempBufWritten, data, numBytesToWrite); + TempBufWritten += numBytesToWrite; + } + } } realProcessed += numBytesToWrite; if (processedSize) *processedSize = realProcessed; - data = (const void *)((const Byte *)data + numBytesToWrite); + if (data) + data = (const void *)((const Byte *)data + numBytesToWrite); size -= numBytesToWrite; m_RemainFileSize -= numBytesToWrite; m_PosInFolder += numBytesToWrite; @@ -768,7 +780,7 @@ HRESULT CFolderOutStream::Write2(const void *data, UInt32 size, UInt32 *processe if (m_RemainFileSize == 0) { - RINOK(CloseFile()); + RINOK(CloseFile()) while (NumIdenticalFiles) { @@ -776,18 +788,18 @@ HRESULT CFolderOutStream::Write2(const void *data, UInt32 size, UInt32 *processe m_FileIsOpen = true; m_CurrentIndex++; if (result == S_OK && m_RealOutStream && TempBuf) - result = WriteStream(m_RealOutStream, TempBuf, (size_t)(m_PosInFolder - m_BufStartFolderOffset)); + result = WriteStream(m_RealOutStream, TempBuf, TempBufWritten); if (!TempBuf && TempBufMode && m_RealOutStream) { - RINOK(CloseFileWithResOp(NExtract::NOperationResult::kUnsupportedMethod)); + RINOK(CloseFileWithResOp(NExtract::NOperationResult::kUnsupportedMethod)) } else { - RINOK(CloseFile()); + RINOK(CloseFile()) } - RINOK(result); + RINOK(result) } TempBufMode = false; @@ -814,25 +826,26 @@ HRESULT CFolderOutStream::Write2(const void *data, UInt32 size, UInt32 *processe m_RemainFileSize = item.Size; - UInt32 fileOffset = item.Offset; + const UInt32 fileOffset = item.Offset; if (fileOffset < m_PosInFolder) return E_FAIL; if (fileOffset > m_PosInFolder) { - UInt32 numBytesToWrite = MyMin(fileOffset - (UInt32)m_PosInFolder, size); + const UInt32 numBytesToWrite = MyMin(fileOffset - (UInt32)m_PosInFolder, size); realProcessed += numBytesToWrite; if (processedSize) *processedSize = realProcessed; - data = (const void *)((const Byte *)data + numBytesToWrite); + if (data) + data = (const void *)((const Byte *)data + numBytesToWrite); size -= numBytesToWrite; m_PosInFolder += numBytesToWrite; } if (fileOffset == m_PosInFolder) { - RINOK(OpenFile()); + RINOK(OpenFile()) m_FileIsOpen = true; m_CurrentIndex++; m_IsOk = true; @@ -846,38 +859,29 @@ HRESULT CFolderOutStream::Write2(const void *data, UInt32 size, UInt32 *processe } -STDMETHODIMP CFolderOutStream::Write(const void *data, UInt32 size, UInt32 *processedSize) -{ - return Write2(data, size, processedSize, true); -} - - HRESULT CFolderOutStream::FlushCorrupted(unsigned folderIndex) { if (!NeedMoreWrite()) { - CMyComPtr callbackMessage; - m_ExtractCallback.QueryInterface(IID_IArchiveExtractCallbackMessage, &callbackMessage); + CMyComPtr callbackMessage; + m_ExtractCallback.QueryInterface(IID_IArchiveExtractCallbackMessage2, &callbackMessage); if (callbackMessage) { - RINOK(callbackMessage->ReportExtractResult(NEventIndexType::kBlockIndex, folderIndex, NExtract::NOperationResult::kDataError)); + RINOK(callbackMessage->ReportExtractResult(NEventIndexType::kBlockIndex, folderIndex, NExtract::NOperationResult::kDataError)) } return S_OK; } - const unsigned kBufSize = (1 << 12); - Byte buf[kBufSize]; - for (unsigned i = 0; i < kBufSize; i++) - buf[i] = 0; - for (;;) { if (!NeedMoreWrite()) return S_OK; - UInt64 remain = GetRemain(); - UInt32 size = (remain < kBufSize ? (UInt32)remain : (UInt32)kBufSize); + const UInt64 remain = GetRemain(); + UInt32 size = (UInt32)1 << 20; + if (size > remain) + size = (UInt32)remain; UInt32 processedSizeLocal = 0; - RINOK(Write2(buf, size, &processedSizeLocal, false)); + RINOK(Write(NULL, size, &processedSizeLocal)) } } @@ -886,28 +890,26 @@ HRESULT CFolderOutStream::Unsupported() { while (m_CurrentIndex < m_ExtractStatuses->Size()) { - HRESULT result = OpenFile(); + const HRESULT result = OpenFile(); if (result != S_FALSE && result != S_OK) return result; m_RealOutStream.Release(); - RINOK(m_ExtractCallback->SetOperationResult(NExtract::NOperationResult::kUnsupportedMethod)); + RINOK(m_ExtractCallback->SetOperationResult(NExtract::NOperationResult::kUnsupportedMethod)) m_CurrentIndex++; } return S_OK; } -STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, - Int32 testModeSpec, IArchiveExtractCallback *extractCallback) +Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testMode, IArchiveExtractCallback *extractCallback)) { COM_TRY_BEGIN - - bool allFilesMode = (numItems == (UInt32)(Int32)-1); + const bool allFilesMode = (numItems == (UInt32)(Int32)-1); if (allFilesMode) numItems = m_Database.Items.Size(); if (numItems == 0) return S_OK; - bool testMode = (testModeSpec != 0); UInt64 totalUnPacked = 0; UInt32 i; @@ -916,12 +918,12 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, for (i = 0; i < numItems; i++) { - unsigned index = allFilesMode ? i : indices[i]; + const unsigned index = allFilesMode ? i : indices[i]; const CMvItem &mvItem = m_Database.Items[index]; const CItem &item = m_Database.Volumes[mvItem.VolumeIndex].Items[mvItem.ItemIndex]; if (item.IsDir()) continue; - int folderIndex = m_Database.GetFolderIndex(&mvItem); + const int folderIndex = m_Database.GetFolderIndex(&mvItem); if (folderIndex != lastFolder) totalUnPacked += lastFolderSize; lastFolder = folderIndex; @@ -929,83 +931,72 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, } totalUnPacked += lastFolderSize; + RINOK(extractCallback->SetTotal(totalUnPacked)) - extractCallback->SetTotal(totalUnPacked); - - totalUnPacked = 0; - - UInt64 totalPacked = 0; - - CLocalProgress *lps = new CLocalProgress; - CMyComPtr progress = lps; + CMyComPtr2_Create lps; lps->Init(extractCallback, false); - NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder; - CMyComPtr copyCoder = copyCoderSpec; + CMyComPtr2 deflateDecoder; + CMyUniquePtr lzxDecoder; + CMyUniquePtr quantumDecoder; - NCompress::NDeflate::NDecoder::CCOMCoder *deflateDecoderSpec = NULL; - CMyComPtr deflateDecoder; - - NCompress::NLzx::CDecoder *lzxDecoderSpec = NULL; - CMyComPtr lzxDecoder; - - NCompress::NQuantum::CDecoder *quantumDecoderSpec = NULL; - CMyComPtr quantumDecoder; - - CCabBlockInStream *cabBlockInStreamSpec = new CCabBlockInStream(); - CMyComPtr cabBlockInStream = cabBlockInStreamSpec; - if (!cabBlockInStreamSpec->Create()) + CBlockPackData blockPackData; + if (!blockPackData.Create()) return E_OUTOFMEMORY; + CMyComPtr2_Create inBufStream; + CRecordVector extractStatuses; + + totalUnPacked = 0; + UInt64 totalPacked = 0; for (i = 0;;) { lps->OutSize = totalUnPacked; lps->InSize = totalPacked; - RINOK(lps->SetCur()); - + RINOK(lps->SetCur()) if (i >= numItems) break; - unsigned index = allFilesMode ? i : indices[i]; + const unsigned index = allFilesMode ? i : indices[i]; const CMvItem &mvItem = m_Database.Items[index]; const CDatabaseEx &db = m_Database.Volumes[mvItem.VolumeIndex]; - unsigned itemIndex = mvItem.ItemIndex; + const unsigned itemIndex = mvItem.ItemIndex; const CItem &item = db.Items[itemIndex]; i++; if (item.IsDir()) { - Int32 askMode = testMode ? + const Int32 askMode = testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; CMyComPtr realOutStream; - RINOK(extractCallback->GetStream(index, &realOutStream, askMode)); - RINOK(extractCallback->PrepareOperation(askMode)); + RINOK(extractCallback->GetStream(index, &realOutStream, askMode)) + RINOK(extractCallback->PrepareOperation(askMode)) realOutStream.Release(); - RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)); + RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)) continue; } - int folderIndex = m_Database.GetFolderIndex(&mvItem); + const int folderIndex = m_Database.GetFolderIndex(&mvItem); if (folderIndex < 0) { // If we need previous archive - Int32 askMode= testMode ? + const Int32 askMode= testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; CMyComPtr realOutStream; - RINOK(extractCallback->GetStream(index, &realOutStream, askMode)); - RINOK(extractCallback->PrepareOperation(askMode)); + RINOK(extractCallback->GetStream(index, &realOutStream, askMode)) + RINOK(extractCallback->PrepareOperation(askMode)) realOutStream.Release(); - RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kDataError)); + RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kDataError)) continue; } - unsigned startIndex2 = m_Database.FolderStartFileIndex[folderIndex]; + const unsigned startIndex2 = m_Database.FolderStartFileIndex[(unsigned)folderIndex]; unsigned startIndex = startIndex2; extractStatuses.Clear(); for (; startIndex < index; startIndex++) @@ -1016,12 +1007,12 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, for (; i < numItems; i++) { - unsigned indexNext = allFilesMode ? i : indices[i]; + const unsigned indexNext = allFilesMode ? i : indices[i]; const CMvItem &mvItem2 = m_Database.Items[indexNext]; const CItem &item2 = m_Database.Volumes[mvItem2.VolumeIndex].Items[mvItem2.ItemIndex]; if (item2.IsDir()) continue; - int newFolderIndex = m_Database.GetFolderIndex(&mvItem2); + const int newFolderIndex = m_Database.GetFolderIndex(&mvItem2); if (newFolderIndex != folderIndex) break; @@ -1032,16 +1023,16 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, curUnpack = item2.GetEndOffset(); } - CFolderOutStream *cabFolderOutStream = new CFolderOutStream; - CMyComPtr outStream(cabFolderOutStream); + CMyComPtr2_Create cabFolderOutStream; - unsigned folderIndex2 = item.GetFolderIndex(db.Folders.Size()); - const CFolder &folder = db.Folders[folderIndex2]; + const int folderIndex2 = item.GetFolderIndex(db.Folders.Size()); + if (folderIndex2 < 0) + return E_FAIL; + const CFolder &folder = db.Folders[(unsigned)folderIndex2]; cabFolderOutStream->Init(&m_Database, &extractStatuses, startIndex2, - curUnpack, extractCallback, testMode); + curUnpack, extractCallback, testMode != 0); - cabBlockInStreamSpec->MsZip = false; HRESULT res = S_OK; switch (folder.GetMethod()) @@ -1050,30 +1041,17 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, break; case NHeader::NMethod::kMSZip: - if (!deflateDecoder) - { - deflateDecoderSpec = new NCompress::NDeflate::NDecoder::CCOMCoder; - deflateDecoder = deflateDecoderSpec; - } - cabBlockInStreamSpec->MsZip = true; + deflateDecoder.Create_if_Empty(); break; case NHeader::NMethod::kLZX: - if (!lzxDecoder) - { - lzxDecoderSpec = new NCompress::NLzx::CDecoder; - lzxDecoder = lzxDecoderSpec; - } - res = lzxDecoderSpec->SetParams_and_Alloc(folder.MethodMinor); + lzxDecoder.Create_if_Empty(); + res = lzxDecoder->Set_DictBits_and_Alloc(folder.MethodMinor); break; case NHeader::NMethod::kQuantum: - if (!quantumDecoder) - { - quantumDecoderSpec = new NCompress::NQuantum::CDecoder; - quantumDecoder = quantumDecoderSpec; - } - res = quantumDecoderSpec->SetParams(folder.MethodMinor); + quantumDecoder.Create_if_Empty(); + res = quantumDecoder->SetParams(folder.MethodMinor); break; default: @@ -1083,11 +1061,11 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, if (res == E_INVALIDARG) { - RINOK(cabFolderOutStream->Unsupported()); + RINOK(cabFolderOutStream->Unsupported()) totalUnPacked += curUnpack; continue; } - RINOK(res); + RINOK(res) { unsigned volIndex = mvItem.VolumeIndex; @@ -1105,12 +1083,13 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, } const CDatabaseEx &db2 = m_Database.Volumes[volIndex]; - const CFolder &folder2 = db2.Folders[locFolderIndex]; + if (locFolderIndex < 0) + return E_FAIL; + const CFolder &folder2 = db2.Folders[(unsigned)locFolderIndex]; if (bl == 0) { - cabBlockInStreamSpec->ReservedSize = db2.ArcInfo.GetDataBlockReserveSize(); - RINOK(db2.Stream->Seek(db2.StartPosition + folder2.DataStart, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekSet(db2.Stream, db2.StartPosition + folder2.DataStart)) } if (bl == folder2.NumDataBlocks) @@ -1135,41 +1114,33 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, bl++; if (!keepInputBuffer) - cabBlockInStreamSpec->InitForNewBlock(); + blockPackData.InitForNewBlock(); UInt32 packSize, unpackSize; - res = cabBlockInStreamSpec->PreRead(db2.Stream, packSize, unpackSize); + res = blockPackData.Read(db2.Stream, db2.ArcInfo.GetDataBlockReserveSize(), packSize, unpackSize); if (res == S_FALSE) break; - RINOK(res); + RINOK(res) keepInputBuffer = (unpackSize == 0); if (keepInputBuffer) continue; - UInt64 totalUnPacked2 = totalUnPacked + cabFolderOutStream->GetPosInFolder(); + const UInt64 totalUnPacked2 = totalUnPacked + cabFolderOutStream->GetPosInFolder(); totalPacked += packSize; - lps->OutSize = totalUnPacked2; - lps->InSize = totalPacked; - RINOK(lps->SetCur()); - - const UInt32 kBlockSizeMax = (1 << 15); - - /* We don't try to reduce last block. - Note that LZX converts data with x86 filter. - and filter needs larger input data than reduced size. - It's simpler to decompress full chunk here. - also we need full block for quantum for more integrity checks */ - - if (unpackSize > kBlockSizeMax) + if (totalUnPacked2 - lps->OutSize >= (1 << 26) + || totalPacked - lps->InSize >= (1 << 24)) { - res = S_FALSE; - break; + lps->OutSize = totalUnPacked2; + lps->InSize = totalPacked; + RINOK(lps->SetCur()) } + const unsigned kBlockSizeMax = 1u << 15; + if (unpackSize != kBlockSizeMax) { - if (thereWasNotAlignedChunk) + if (unpackSize > kBlockSizeMax || thereWasNotAlignedChunk) { res = S_FALSE; break; @@ -1177,55 +1148,95 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, thereWasNotAlignedChunk = true; } - UInt64 unpackSize64 = unpackSize; - UInt32 packSizeChunk = cabBlockInStreamSpec->GetPackSizeAvail(); + /* We don't try to reduce last block. + Note that LZX converts data with x86 filter. + and filter needs larger input data than reduced size. + It's simpler to decompress full chunk here. + also we need full block for quantum for more integrity checks */ + + const UInt64 unpackSize64 = unpackSize; + const UInt32 packSizeChunk = blockPackData.GetPackSize(); switch (folder2.GetMethod()) { case NHeader::NMethod::kNone: - res = copyCoder->Code(cabBlockInStream, outStream, NULL, &unpackSize64, NULL); + if (unpackSize != packSizeChunk) + { + res = S_FALSE; + break; + } + res = WriteStream(cabFolderOutStream, blockPackData.GetData(), packSizeChunk); break; case NHeader::NMethod::kMSZip: - deflateDecoderSpec->Set_KeepHistory(keepHistory); - /* v9.31: now we follow MSZIP specification that requires to finish deflate stream at the end of each block. - But PyCabArc can create CAB archives that doesn't have finish marker at the end of block. + { + /* v24.00 : fixed : we check 2-bytes MSZIP signature only + when block was constructed from all volumes. */ + const Byte *packData = blockPackData.GetData(); + if (unpackSize > (1u << 15) + 12 /* MSZIP specification */ + || packSizeChunk < 2 || GetUi16(packData) != 0x4b43) + { + res = S_FALSE; + break; + } + const UInt32 packSizeChunk_2 = packSizeChunk - 2; + inBufStream->Init(packData + 2, packSizeChunk_2); + + deflateDecoder->Set_KeepHistory(keepHistory); + /* v9.31: now we follow MSZIP specification that requires + to finish deflate stream at the end of each block. + But PyCabArc can create CAB archives that don't have + finish marker at the end of block. Cabarc probably ignores such errors in cab archives. - Maybe we also should ignore that error? + Maybe we also should ignore such error? Or we should extract full file and show the warning? */ - deflateDecoderSpec->Set_NeedFinishInput(true); - res = deflateDecoder->Code(cabBlockInStream, outStream, NULL, &unpackSize64, NULL); + deflateDecoder->Set_NeedFinishInput(true); + res = deflateDecoder.Interface()->Code(inBufStream, cabFolderOutStream, NULL, &unpackSize64, NULL); if (res == S_OK) { - if (!deflateDecoderSpec->IsFinished()) + if (!deflateDecoder->IsFinished()) + res = S_FALSE; + if (!deflateDecoder->IsFinalBlock()) res = S_FALSE; - if (!deflateDecoderSpec->IsFinalBlock()) + if (deflateDecoder->GetInputProcessedSize() != packSizeChunk_2) res = S_FALSE; } break; + } case NHeader::NMethod::kLZX: - lzxDecoderSpec->SetKeepHistory(keepHistory); - lzxDecoderSpec->KeepHistoryForNext = true; - - res = lzxDecoderSpec->Code(cabBlockInStreamSpec->GetData(), packSizeChunk, unpackSize); - + lzxDecoder->Set_KeepHistory(keepHistory); + lzxDecoder->Set_KeepHistoryForNext(true); + res = lzxDecoder->Code_WithExceedReadWrite(blockPackData.GetData(), + packSizeChunk, unpackSize); if (res == S_OK) - res = WriteStream(outStream, - lzxDecoderSpec->GetUnpackData(), - lzxDecoderSpec->GetUnpackSize()); + res = WriteStream(cabFolderOutStream, + lzxDecoder->GetUnpackData(), + lzxDecoder->GetUnpackSize()); break; case NHeader::NMethod::kQuantum: - res = quantumDecoderSpec->Code(cabBlockInStreamSpec->GetData(), - packSizeChunk, outStream, unpackSize, keepHistory); + { + res = quantumDecoder->Code(blockPackData.GetData(), + packSizeChunk, unpackSize, keepHistory); + if (res == S_OK) + { + const UInt32 num = unpackSize; + res = WriteStream(cabFolderOutStream, + quantumDecoder->GetDataPtr() - num, num); + } + break; + } + default: + // it's unexpected case, because we checked method before + // res = E_NOTIMPL; break; } if (res != S_OK) { if (res != S_FALSE) - RINOK(res); + return res; break; } @@ -1234,13 +1245,13 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, if (res == S_OK) { - RINOK(cabFolderOutStream->WriteEmptyFiles()); + RINOK(cabFolderOutStream->WriteEmptyFiles()) } } if (res != S_OK || cabFolderOutStream->NeedMoreWrite()) { - RINOK(cabFolderOutStream->FlushCorrupted(folderIndex2)); + RINOK(cabFolderOutStream->FlushCorrupted((unsigned)folderIndex2)) } totalUnPacked += curUnpack; @@ -1252,7 +1263,7 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, } -STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) { *numItems = m_Database.Items.Size(); return S_OK; diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Cab/CabHandler.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Cab/CabHandler.h index 6f44b875..1e1811f8 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Cab/CabHandler.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Cab/CabHandler.h @@ -1,7 +1,7 @@ // CabHandler.h -#ifndef __CAB_HANDLER_H -#define __CAB_HANDLER_H +#ifndef ZIP7_INC_CAB_HANDLER_H +#define ZIP7_INC_CAB_HANDLER_H #include "../../../Common/MyCom.h" @@ -12,16 +12,8 @@ namespace NArchive { namespace NCab { -class CHandler: - public IInArchive, - public CMyUnknownImp -{ -public: - MY_UNKNOWN_IMP1(IInArchive) +Z7_CLASS_IMP_CHandler_IInArchive_0 - INTERFACE_IInArchive(;) - -private: CMvDatabaseEx m_Database; UString _errorMessage; bool _isArc; diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Cab/CabHeader.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Cab/CabHeader.h index 2f2bd109..ecb9a87b 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Cab/CabHeader.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Cab/CabHeader.h @@ -1,7 +1,7 @@ // Archive/CabHeader.h -#ifndef __ARCHIVE_CAB_HEADER_H -#define __ARCHIVE_CAB_HEADER_H +#ifndef ZIP7_INC_ARCHIVE_CAB_HEADER_H +#define ZIP7_INC_ARCHIVE_CAB_HEADER_H #include "../../../Common/MyTypes.h" diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Cab/CabIn.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/Cab/CabIn.cpp index ca0052bf..bd6969a5 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Cab/CabIn.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Cab/CabIn.cpp @@ -76,21 +76,20 @@ struct CSignatureFinder const Byte *Signature; UInt32 SignatureSize; - UInt32 _HeaderSize; - UInt32 _AlignSize; - UInt32 _BufUseCapacity; + UInt32 _headerSize; + UInt32 _alignSize; + UInt32 _bufUseCapacity; + const UInt64 *SearchLimit; ISequentialInStream *Stream; UInt64 Processed; // Global offset of start of Buf - const UInt64 *SearchLimit; - UInt32 GetTotalCapacity(UInt32 basicSize, UInt32 headerSize) { - _HeaderSize = headerSize; - for (_AlignSize = (1 << 5); _AlignSize < _HeaderSize; _AlignSize <<= 1); - _BufUseCapacity = basicSize + _AlignSize; - return _BufUseCapacity + 16; + _headerSize = headerSize; + for (_alignSize = (1 << 5); _alignSize < _headerSize; _alignSize <<= 1); + _bufUseCapacity = basicSize + _alignSize; + return _bufUseCapacity + 16; } /* @@ -108,19 +107,19 @@ HRESULT CSignatureFinder::Find() { Buf[End] = Signature[0]; // it's for fast search; - while (End - Pos >= _HeaderSize) + while (End - Pos >= _headerSize) { const Byte *p = Buf + Pos; - Byte b = Signature[0]; + const Byte b = Signature[0]; for (;;) { - if (*p == b) break; p++; - if (*p == b) break; p++; + if (*p == b) { break; } p++; + if (*p == b) { break; } p++; } Pos = (UInt32)(p - Buf); - if (End - Pos < _HeaderSize) + if (End - Pos < _headerSize) { - Pos = End - _HeaderSize + 1; + Pos = End - _headerSize + 1; break; } UInt32 i; @@ -130,28 +129,28 @@ HRESULT CSignatureFinder::Find() Pos++; } - if (Pos >= _AlignSize) + if (Pos >= _alignSize) { - UInt32 num = (Pos & ~(_AlignSize - 1)); + const UInt32 num = (Pos & ~(_alignSize - 1)); Processed += num; Pos -= num; End -= num; memmove(Buf, Buf + num, End); } - UInt32 rem = _BufUseCapacity - End; + UInt32 rem = _bufUseCapacity - End; if (SearchLimit) { if (Processed + Pos > *SearchLimit) return S_FALSE; - UInt64 rem2 = *SearchLimit - (Processed + End) + _HeaderSize; + const UInt64 rem2 = *SearchLimit - (Processed + End) + _headerSize; if (rem > rem2) rem = (UInt32)rem2; } UInt32 processedSize; - if (Processed == 0 && rem == _BufUseCapacity - _HeaderSize) - rem -= _AlignSize; // to make reads more aligned. - RINOK(Stream->Read(Buf + End, rem, &processedSize)); + if (Processed == 0 && rem == _bufUseCapacity - _headerSize) + rem -= _alignSize; // to make reads more aligned. + RINOK(Stream->Read(Buf + End, rem, &processedSize)) if (processedSize == 0) return S_FALSE; End += processedSize; @@ -189,7 +188,7 @@ HRESULT CInArchive::Open2(CDatabaseEx &db, const UInt64 *searchHeaderSizeLimit) HeaderError = false; db.Clear(); - RINOK(db.Stream->Seek(0, STREAM_SEEK_CUR, &db.StartPosition)); + RINOK(InStream_GetPos(db.Stream, db.StartPosition)) // UInt64 temp = db.StartPosition; CByteBuffer buffer; @@ -201,12 +200,12 @@ HRESULT CInArchive::Open2(CDatabaseEx &db, const UInt64 *searchHeaderSizeLimit) // for (int iii = 0; iii < 10000; iii++) { - // db.StartPosition = temp; RINOK(db.Stream->Seek(db.StartPosition, STREAM_SEEK_SET, NULL)); + // db.StartPosition = temp; RINOK(InStream_SeekSet(db.Stream, db.StartPosition)) const UInt32 kMainHeaderSize = 32; Byte header[kMainHeaderSize]; const UInt32 kBufSize = 1 << 15; - RINOK(ReadStream_FALSE(db.Stream, header, kMainHeaderSize)); + RINOK(ReadStream_FALSE(db.Stream, header, kMainHeaderSize)) if (memcmp(header, NHeader::kMarker, NHeader::kMarkerSize) == 0 && ai.Parse(header)) { limitedStreamSpec = new CLimitedSequentialInStream; @@ -216,7 +215,7 @@ HRESULT CInArchive::Open2(CDatabaseEx &db, const UInt64 *searchHeaderSizeLimit) buffer.Alloc(kBufSize); memcpy(buffer, header, kMainHeaderSize); UInt32 numProcessedBytes; - RINOK(limitedStream->Read(buffer + kMainHeaderSize, kBufSize - kMainHeaderSize, &numProcessedBytes)); + RINOK(limitedStream->Read(buffer + kMainHeaderSize, kBufSize - kMainHeaderSize, &numProcessedBytes)) _inBuffer.SetBuf(buffer, (UInt32)kBufSize, kMainHeaderSize + numProcessedBytes, kMainHeaderSize); } else @@ -241,14 +240,14 @@ HRESULT CInArchive::Open2(CDatabaseEx &db, const UInt64 *searchHeaderSizeLimit) for (;;) { - RINOK(finder.Find()); + RINOK(finder.Find()) if (ai.Parse(finder.Buf + finder.Pos)) { db.StartPosition = finder.Processed + finder.Pos; limitedStreamSpec = new CLimitedSequentialInStream; limitedStreamSpec->SetStream(db.Stream); limitedStream = limitedStreamSpec; - UInt32 remInFinder = finder.End - finder.Pos; + const UInt32 remInFinder = finder.End - finder.Pos; if (ai.Size <= remInFinder) { limitedStreamSpec->Init(0); @@ -273,7 +272,7 @@ HRESULT CInArchive::Open2(CDatabaseEx &db, const UInt64 *searchHeaderSizeLimit) _tempBuf.Alloc(1 << 12); Byte p[16]; - unsigned nextSize = 4 + (ai.ReserveBlockPresent() ? 4 : 0); + const unsigned nextSize = 4 + (ai.ReserveBlockPresent() ? 4 : 0); Read(p, nextSize); ai.SetID = Get16(p); ai.CabinetNumber = Get16(p + 2); @@ -311,7 +310,7 @@ HRESULT CInArchive::Open2(CDatabaseEx &db, const UInt64 *searchHeaderSizeLimit) { // printf("\n!!! Seek Error !!!!\n"); // fflush(stdout); - RINOK(db.Stream->Seek(db.StartPosition + ai.FileHeadersOffset, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekSet(db.Stream, db.StartPosition + ai.FileHeadersOffset)) limitedStreamSpec->Init(ai.Size - ai.FileHeadersOffset); _inBuffer.Init(); } @@ -325,8 +324,8 @@ HRESULT CInArchive::Open2(CDatabaseEx &db, const UInt64 *searchHeaderSizeLimit) item.Size = Get32(p); item.Offset = Get32(p + 4); item.FolderIndex = Get16(p + 8); - UInt16 pureDate = Get16(p + 10); - UInt16 pureTime = Get16(p + 12); + const UInt16 pureDate = Get16(p + 10); + const UInt16 pureTime = Get16(p + 12); item.Time = (((UInt32)pureDate << 16)) | pureTime; item.Attributes = Get16(p + 14); @@ -357,7 +356,7 @@ HRESULT CInArchive::Open(CDatabaseEx &db, const UInt64 *searchHeaderSizeLimit) -#define RINOZ(x) { int __tt = (x); if (__tt != 0) return __tt; } +#define RINOZ(x) { int _tt_ = (x); if (_tt_ != 0) return _tt_; } static int CompareMvItems(const CMvItem *p1, const CMvItem *p2, void *param) { @@ -365,17 +364,17 @@ static int CompareMvItems(const CMvItem *p1, const CMvItem *p2, void *param) const CDatabaseEx &db1 = mvDb.Volumes[p1->VolumeIndex]; const CDatabaseEx &db2 = mvDb.Volumes[p2->VolumeIndex]; const CItem &item1 = db1.Items[p1->ItemIndex]; - const CItem &item2 = db2.Items[p2->ItemIndex];; - bool isDir1 = item1.IsDir(); - bool isDir2 = item2.IsDir(); + const CItem &item2 = db2.Items[p2->ItemIndex]; + const bool isDir1 = item1.IsDir(); + const bool isDir2 = item2.IsDir(); if (isDir1 && !isDir2) return -1; if (isDir2 && !isDir1) return 1; - int f1 = mvDb.GetFolderIndex(p1); - int f2 = mvDb.GetFolderIndex(p2); - RINOZ(MyCompare(f1, f2)); - RINOZ(MyCompare(item1.Offset, item2.Offset)); - RINOZ(MyCompare(item1.Size, item2.Size)); - RINOZ(MyCompare(p1->VolumeIndex, p2->VolumeIndex)); + const int f1 = mvDb.GetFolderIndex(p1); + const int f2 = mvDb.GetFolderIndex(p2); + RINOZ(MyCompare(f1, f2)) + RINOZ(MyCompare(item1.Offset, item2.Offset)) + RINOZ(MyCompare(item1.Size, item2.Size)) + RINOZ(MyCompare(p1->VolumeIndex, p2->VolumeIndex)) return MyCompare(p1->ItemIndex, p2->ItemIndex); } @@ -387,7 +386,7 @@ bool CMvDatabaseEx::AreItemsEqual(unsigned i1, unsigned i2) const CDatabaseEx &db1 = Volumes[p1->VolumeIndex]; const CDatabaseEx &db2 = Volumes[p2->VolumeIndex]; const CItem &item1 = db1.Items[p1->ItemIndex]; - const CItem &item2 = db2.Items[p2->ItemIndex];; + const CItem &item2 = db2.Items[p2->ItemIndex]; return GetFolderIndex(p1) == GetFolderIndex(p2) && item1.Offset == item2.Offset && item1.Size == item2.Size @@ -434,7 +433,7 @@ void CMvDatabaseEx::FillSortAndShrink() FOR_VECTOR (i, Items) { - int folderIndex = GetFolderIndex(&Items[i]); + const int folderIndex = GetFolderIndex(&Items[i]); while (folderIndex >= (int)FolderStartFileIndex.Size()) FolderStartFileIndex.Add(i); } @@ -452,7 +451,7 @@ bool CMvDatabaseEx::Check() if (db0.Folders.IsEmpty() || db1.Folders.IsEmpty()) return false; const CFolder &f0 = db0.Folders.Back(); - const CFolder &f1 = db1.Folders.Front(); + const CFolder &f1 = db1.Folders.FrontItem(); if (f0.MethodMajor != f1.MethodMajor || f0.MethodMinor != f1.MethodMinor) return false; @@ -466,14 +465,14 @@ bool CMvDatabaseEx::Check() FOR_VECTOR (i, Items) { const CMvItem &mvItem = Items[i]; - int fIndex = GetFolderIndex(&mvItem); + const int fIndex = GetFolderIndex(&mvItem); if (fIndex >= (int)FolderStartFileIndex.Size()) return false; const CItem &item = Volumes[mvItem.VolumeIndex].Items[mvItem.ItemIndex]; if (item.IsDir()) continue; - int folderIndex = GetFolderIndex(&mvItem); + const int folderIndex = GetFolderIndex(&mvItem); if (folderIndex != prevFolder) prevFolder = folderIndex; diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Cab/CabIn.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Cab/CabIn.h index a1fc6bdc..06aa34c7 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Cab/CabIn.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Cab/CabIn.h @@ -1,7 +1,7 @@ // Archive/CabIn.h -#ifndef __ARCHIVE_CAB_IN_H -#define __ARCHIVE_CAB_IN_H +#ifndef ZIP7_INC_ARCHIVE_CAB_IN_H +#define ZIP7_INC_ARCHIVE_CAB_IN_H #include "../../../Common/MyBuffer.h" #include "../../../Common/MyCom.h" @@ -100,7 +100,7 @@ struct CDatabase int GetNumberOfNewFolders() const { - int res = Folders.Size(); + int res = (int)Folders.Size(); if (IsTherePrevFolder()) res--; return res; diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Cab/CabItem.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Cab/CabItem.h index 9b513202..b7e07d1c 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Cab/CabItem.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Cab/CabItem.h @@ -1,7 +1,7 @@ // Archive/CabItem.h -#ifndef __ARCHIVE_CAB_ITEM_H -#define __ARCHIVE_CAB_ITEM_H +#ifndef ZIP7_INC_ARCHIVE_CAB_ITEM_H +#define ZIP7_INC_ARCHIVE_CAB_ITEM_H #include "../../../Common/MyString.h" @@ -56,8 +56,8 @@ struct CItem if (ContinuedFromPrev()) return 0; if (ContinuedToNext()) - return numFolders - 1; - return FolderIndex; + return (int)numFolders - 1; + return (int)FolderIndex; } }; diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Cab/CabRegister.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/Cab/CabRegister.cpp index 0b5cc93a..8f4f323f 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Cab/CabRegister.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Cab/CabRegister.cpp @@ -10,7 +10,7 @@ namespace NArchive { namespace NCab { REGISTER_ARC_I( - "Cab", "cab", 0, 8, + "Cab", "cab", NULL, 8, NHeader::kMarker, 0, NArcInfoFlags::kFindSignature, diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Cab/StdAfx.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Cab/StdAfx.h index 2854ff3e..035267cc 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Cab/StdAfx.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Cab/StdAfx.h @@ -1,8 +1,11 @@ // StdAfx.h -#ifndef __STDAFX_H -#define __STDAFX_H +#ifndef ZIP7_INC_STDAFX_H +#define ZIP7_INC_STDAFX_H +#if defined(_MSC_VER) && _MSC_VER >= 1800 +#pragma warning(disable : 4464) // relative include path contains '..' +#endif #include "../../../Common/Common.h" #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Chm/ChmHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/Chm/ChmHandler.cpp index b890d8ff..93c3a85e 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Chm/ChmHandler.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Chm/ChmHandler.cpp @@ -2,6 +2,7 @@ #include "StdAfx.h" +#include "../../../Common/AutoPtr.h" #include "../../../Common/ComTry.h" #include "../../../Common/StringConvert.h" #include "../../../Common/UTFConvert.h" @@ -27,9 +28,9 @@ using namespace NTime; namespace NArchive { namespace NChm { -// #define _CHM_DETAILS +// #define CHM_DETAILS -#ifdef _CHM_DETAILS +#ifdef CHM_DETAILS enum { @@ -45,7 +46,7 @@ static const Byte kProps[] = kpidMethod, kpidBlock - #ifdef _CHM_DETAILS + #ifdef CHM_DETAILS , L"Section", kpidSection, kpidOffset @@ -63,7 +64,7 @@ IMP_IInArchive_Props IMP_IInArchive_ArcProps_NO_Table -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { // COM_TRY_BEGIN NCOM::CPropVariant prop; @@ -97,7 +98,7 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) // COM_TRY_END } -STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; @@ -135,7 +136,7 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val if (us.Len() > 1 && us[0] == L'/') us.Delete(0); } - NItemName::ConvertToOSName2(us); + NItemName::ReplaceToOsSlashes_Remove_TailSlash(us); prop = us; } break; @@ -145,10 +146,12 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val case kpidMethod: { if (!item.IsDir()) + { if (item.Section == 0) prop = "Copy"; else if (item.Section < m_Database.Sections.Size()) prop = m_Database.Sections[(unsigned)item.Section].GetMethodName(); + } break; } case kpidBlock: @@ -158,7 +161,7 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val prop = m_Database.GetFolder(index); break; - #ifdef _CHM_DETAILS + #ifdef CHM_DETAILS case kpidSection: prop = (UInt32)item.Section; break; case kpidOffset: prop = (UInt32)item.Offset; break; @@ -171,34 +174,10 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val COM_TRY_END } -/* -class CProgressImp: public CProgressVirt -{ - CMyComPtr _callback; -public: - STDMETHOD(SetTotal)(const UInt64 *numFiles); - STDMETHOD(SetCompleted)(const UInt64 *numFiles); - CProgressImp(IArchiveOpenCallback *callback): _callback(callback) {}; -}; - -STDMETHODIMP CProgressImp::SetTotal(const UInt64 *numFiles) -{ - if (_callback) - return _callback->SetCompleted(numFiles, NULL); - return S_OK; -} - -STDMETHODIMP CProgressImp::SetCompleted(const UInt64 *numFiles) -{ - if (_callback) - return _callback->SetCompleted(numFiles, NULL); - return S_OK; -} -*/ -STDMETHODIMP CHandler::Open(IInStream *inStream, +Z7_COM7F_IMF(CHandler::Open(IInStream *inStream, const UInt64 *maxCheckStartPosition, - IArchiveOpenCallback * /* openArchiveCallback */) + IArchiveOpenCallback * /* openArchiveCallback */)) { COM_TRY_BEGIN Close(); @@ -206,13 +185,13 @@ STDMETHODIMP CHandler::Open(IInStream *inStream, { CInArchive archive(_help2); // CProgressImp progressImp(openArchiveCallback); - HRESULT res = archive.Open(inStream, maxCheckStartPosition, m_Database); + const HRESULT res = archive.Open(inStream, maxCheckStartPosition, m_Database); if (!archive.IsArc) m_ErrorFlags |= kpv_ErrorFlags_IsNotArc; if (archive.HeadersError) m_ErrorFlags |= kpv_ErrorFlags_HeadersError; if (archive.UnexpectedEnd) m_ErrorFlags |= kpv_ErrorFlags_UnexpectedEnd; if (archive.UnsupportedFeature) m_ErrorFlags |= kpv_ErrorFlags_UnsupportedFeature; - RINOK(res); + RINOK(res) /* if (m_Database.LowLevel) return S_FALSE; @@ -227,7 +206,7 @@ STDMETHODIMP CHandler::Open(IInStream *inStream, COM_TRY_END } -STDMETHODIMP CHandler::Close() +Z7_COM7F_IMF(CHandler::Close()) { m_ErrorFlags = 0; m_Database.Clear(); @@ -235,15 +214,22 @@ STDMETHODIMP CHandler::Close() return S_OK; } -class CChmFolderOutStream: - public ISequentialOutStream, - public CMyUnknownImp -{ -public: - MY_UNKNOWN_IMP +Z7_CLASS_IMP_NOQIB_1( + CChmFolderOutStream + , ISequentialOutStream +) + bool m_TestMode; + bool m_IsOk; + bool m_FileIsOpen; + const CFilesDatabase *m_Database; + CMyComPtr m_ExtractCallback; + CMyComPtr m_RealOutStream; + UInt64 m_RemainFileSize; + HRESULT OpenFile(); + HRESULT WriteEmptyFiles(); HRESULT Write2(const void *data, UInt32 size, UInt32 *processedSize, bool isOK); - STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize); +public: UInt64 m_FolderSize; UInt64 m_PosInFolder; @@ -253,19 +239,6 @@ class CChmFolderOutStream: unsigned m_CurrentIndex; unsigned m_NumFiles; -private: - const CFilesDatabase *m_Database; - CMyComPtr m_ExtractCallback; - bool m_TestMode; - - bool m_IsOk; - bool m_FileIsOpen; - UInt64 m_RemainFileSize; - CMyComPtr m_RealOutStream; - - HRESULT OpenFile(); - HRESULT WriteEmptyFiles(); -public: void Init( const CFilesDatabase *database, IArchiveExtractCallback *extractCallback, @@ -288,12 +261,12 @@ void CChmFolderOutStream::Init( HRESULT CChmFolderOutStream::OpenFile() { - Int32 askMode = (*m_ExtractStatuses)[m_CurrentIndex] ? (m_TestMode ? + Int32 askMode = (*m_ExtractStatuses)[m_CurrentIndex] ? m_TestMode ? NExtract::NAskMode::kTest : - NExtract::NAskMode::kExtract) : + NExtract::NAskMode::kExtract : NExtract::NAskMode::kSkip; m_RealOutStream.Release(); - RINOK(m_ExtractCallback->GetStream(m_StartIndex + m_CurrentIndex, &m_RealOutStream, askMode)); + RINOK(m_ExtractCallback->GetStream(m_StartIndex + m_CurrentIndex, &m_RealOutStream, askMode)) if (!m_RealOutStream && !m_TestMode) askMode = NExtract::NAskMode::kSkip; return m_ExtractCallback->PrepareOperation(askMode); @@ -305,13 +278,13 @@ HRESULT CChmFolderOutStream::WriteEmptyFiles() return S_OK; for (; m_CurrentIndex < m_NumFiles; m_CurrentIndex++) { - UInt64 fileSize = m_Database->GetFileSize(m_StartIndex + m_CurrentIndex); + const UInt64 fileSize = m_Database->GetFileSize(m_StartIndex + m_CurrentIndex); if (fileSize != 0) return S_OK; - HRESULT result = OpenFile(); + const HRESULT result = OpenFile(); m_RealOutStream.Release(); - RINOK(result); - RINOK(m_ExtractCallback->SetOperationResult(NExtract::NOperationResult::kOK)); + RINOK(result) + RINOK(m_ExtractCallback->SetOperationResult(NExtract::NOperationResult::kOK)) } return S_OK; } @@ -356,7 +329,7 @@ HRESULT CChmFolderOutStream::Write2(const void *data, UInt32 size, UInt32 *proce RINOK(m_ExtractCallback->SetOperationResult( m_IsOk ? NExtract::NOperationResult::kOK: - NExtract::NOperationResult::kDataError)); + NExtract::NOperationResult::kDataError)) m_FileIsOpen = false; } if (realProcessed > 0) @@ -393,7 +366,7 @@ HRESULT CChmFolderOutStream::Write2(const void *data, UInt32 size, UInt32 *proce if (fileOffset == m_PosInSection) { - RINOK(OpenFile()); + RINOK(OpenFile()) m_FileIsOpen = true; m_CurrentIndex++; m_IsOk = true; @@ -404,7 +377,7 @@ HRESULT CChmFolderOutStream::Write2(const void *data, UInt32 size, UInt32 *proce return WriteEmptyFiles(); } -STDMETHODIMP CChmFolderOutStream::Write(const void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CChmFolderOutStream::Write(const void *data, UInt32 size, UInt32 *processedSize)) { return Write2(data, size, processedSize, true); } @@ -421,7 +394,7 @@ HRESULT CChmFolderOutStream::FlushCorrupted(UInt64 maxSize) { UInt32 size = (UInt32)MyMin(maxSize - m_PosInFolder, (UInt64)kBufferSize); UInt32 processedSizeLocal = 0; - RINOK(Write2(buffer, size, &processedSizeLocal, false)); + RINOK(Write2(buffer, size, &processedSizeLocal, false)) if (processedSizeLocal == 0) return S_OK; } @@ -429,11 +402,11 @@ HRESULT CChmFolderOutStream::FlushCorrupted(UInt64 maxSize) } -STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, - Int32 testModeSpec, IArchiveExtractCallback *extractCallback) +Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testModeSpec, IArchiveExtractCallback *extractCallback)) { COM_TRY_BEGIN - bool allFilesMode = (numItems == (UInt32)(Int32)-1); + const bool allFilesMode = (numItems == (UInt32)(Int32)-1); if (allFilesMode) numItems = m_Database.NewFormat ? 1: @@ -442,21 +415,18 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, m_Database.Indices.Size()); if (numItems == 0) return S_OK; - bool testMode = (testModeSpec != 0); + const bool testMode = (testModeSpec != 0); UInt64 currentTotalSize = 0; - NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder; - CMyComPtr copyCoder = copyCoderSpec; - UInt32 i; - - CLocalProgress *lps = new CLocalProgress; - CMyComPtr progress = lps; + CMyComPtr2_Create lps; lps->Init(extractCallback, false); - CLimitedSequentialInStream *streamSpec = new CLimitedSequentialInStream; - CMyComPtr inStream(streamSpec); - streamSpec->SetStream(m_Stream); + CMyComPtr2_Create copyCoder; + CMyComPtr2_Create inStream; + inStream->SetStream(m_Stream); + + UInt32 i; if (m_Database.LowLevel) { @@ -469,7 +439,7 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, for (i = 0; i < numItems; i++) totalSize += m_Database.Items[allFilesMode ? i : indices[i]].Size; - extractCallback->SetTotal(totalSize); + RINOK(extractCallback->SetTotal(totalSize)) for (i = 0; i < numItems; i++, currentTotalSize += currentItemSize) { @@ -477,13 +447,13 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, lps->InSize = currentTotalSize; // Change it lps->OutSize = currentTotalSize; - RINOK(lps->SetCur()); + RINOK(lps->SetCur()) CMyComPtr realOutStream; - Int32 askMode= testMode ? + const Int32 askMode= testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; - Int32 index = allFilesMode ? i : indices[i]; - RINOK(extractCallback->GetStream(index, &realOutStream, askMode)); + const UInt32 index = allFilesMode ? i : indices[i]; + RINOK(extractCallback->GetStream(index, &realOutStream, askMode)) if (m_Database.NewFormat) { @@ -493,10 +463,10 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, continue; if (!testMode) { - UInt32 size = m_Database.NewFormatString.Len(); - RINOK(WriteStream(realOutStream, (const char *)m_Database.NewFormatString, size)); + const unsigned size = m_Database.NewFormatString.Len(); + RINOK(WriteStream(realOutStream, (const char *)m_Database.NewFormatString, size)) } - RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)); + RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)) continue; } @@ -506,36 +476,36 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, if (!testMode && !realOutStream) continue; - RINOK(extractCallback->PrepareOperation(askMode)); + RINOK(extractCallback->PrepareOperation(askMode)) if (item.Section != 0) { - RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kUnsupportedMethod)); + RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kUnsupportedMethod)) continue; } if (testMode) { - RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)); + RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)) continue; } - RINOK(m_Stream->Seek(m_Database.ContentOffset + item.Offset, STREAM_SEEK_SET, NULL)); - streamSpec->Init(item.Size); + RINOK(InStream_SeekSet(m_Stream, m_Database.ContentOffset + item.Offset)) + inStream->Init(item.Size); - RINOK(copyCoder->Code(inStream, realOutStream, NULL, NULL, progress)); + RINOK(copyCoder.Interface()->Code(inStream, realOutStream, NULL, NULL, lps)) realOutStream.Release(); - RINOK(extractCallback->SetOperationResult((copyCoderSpec->TotalSize == item.Size) ? + RINOK(extractCallback->SetOperationResult((copyCoder->TotalSize == item.Size) ? NExtract::NOperationResult::kOK: - NExtract::NOperationResult::kDataError)); + NExtract::NOperationResult::kDataError)) } return S_OK; } - UInt64 lastFolderIndex = ((UInt64)0 - 1); + UInt64 lastFolderIndex = (UInt64)0 - 1; for (i = 0; i < numItems; i++) { - UInt32 index = allFilesMode ? i : indices[i]; + const UInt32 index = allFilesMode ? i : indices[i]; const CItem &item = m_Database.Items[m_Database.Indices[index]]; const UInt64 sectionIndex = item.Section; if (item.IsDir() || item.Size == 0) @@ -562,12 +532,10 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, } } - RINOK(extractCallback->SetTotal(currentTotalSize)); + RINOK(extractCallback->SetTotal(currentTotalSize)) - NCompress::NLzx::CDecoder *lzxDecoderSpec = NULL; - CMyComPtr lzxDecoder; - CChmFolderOutStream *chmFolderOutStream = 0; - CMyComPtr outStream; + CMyUniquePtr lzxDecoder; + CMyComPtr2_Create chmFolderOutStream; currentTotalSize = 0; @@ -577,7 +545,7 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, for (i = 0;;) { - RINOK(extractCallback->SetCompleted(¤tTotalSize)); + RINOK(extractCallback->SetCompleted(¤tTotalSize)) if (i >= numItems) break; @@ -586,17 +554,17 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, i++; const CItem &item = m_Database.Items[m_Database.Indices[index]]; const UInt64 sectionIndex = item.Section; - Int32 askMode= testMode ? + const Int32 askMode= testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; if (item.IsDir()) { CMyComPtr realOutStream; - RINOK(extractCallback->GetStream(index, &realOutStream, askMode)); - RINOK(extractCallback->PrepareOperation(askMode)); + RINOK(extractCallback->GetStream(index, &realOutStream, askMode)) + RINOK(extractCallback->PrepareOperation(askMode)) realOutStream.Release(); - RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)); + RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)) continue; } @@ -606,21 +574,21 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, if (item.Size == 0 || sectionIndex == 0) { CMyComPtr realOutStream; - RINOK(extractCallback->GetStream(index, &realOutStream, askMode)); + RINOK(extractCallback->GetStream(index, &realOutStream, askMode)) if (!testMode && !realOutStream) continue; - RINOK(extractCallback->PrepareOperation(askMode)); + RINOK(extractCallback->PrepareOperation(askMode)) Int32 opRes = NExtract::NOperationResult::kOK; if (!testMode && item.Size != 0) { - RINOK(m_Stream->Seek(m_Database.ContentOffset + item.Offset, STREAM_SEEK_SET, NULL)); - streamSpec->Init(item.Size); - RINOK(copyCoder->Code(inStream, realOutStream, NULL, NULL, progress)); - if (copyCoderSpec->TotalSize != item.Size) + RINOK(InStream_SeekSet(m_Stream, m_Database.ContentOffset + item.Offset)) + inStream->Init(item.Size); + RINOK(copyCoder.Interface()->Code(inStream, realOutStream, NULL, NULL, lps)) + if (copyCoder->TotalSize != item.Size) opRes = NExtract::NOperationResult::kDataError; } realOutStream.Release(); - RINOK(extractCallback->SetOperationResult(opRes)); + RINOK(extractCallback->SetOperationResult(opRes)) currentTotalSize += item.Size; continue; } @@ -629,11 +597,11 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, { // we must report error here; CMyComPtr realOutStream; - RINOK(extractCallback->GetStream(index, &realOutStream, askMode)); + RINOK(extractCallback->GetStream(index, &realOutStream, askMode)) if (!testMode && !realOutStream) continue; - RINOK(extractCallback->PrepareOperation(askMode)); - RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kHeadersError)); + RINOK(extractCallback->PrepareOperation(askMode)) + RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kHeadersError)) continue; } @@ -642,34 +610,24 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, if (!section.IsLzx()) { CMyComPtr realOutStream; - RINOK(extractCallback->GetStream(index, &realOutStream, askMode)); + RINOK(extractCallback->GetStream(index, &realOutStream, askMode)) if (!testMode && !realOutStream) continue; - RINOK(extractCallback->PrepareOperation(askMode)); - RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kUnsupportedMethod)); + RINOK(extractCallback->PrepareOperation(askMode)) + RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kUnsupportedMethod)) continue; } const CLzxInfo &lzxInfo = section.Methods[0].LzxInfo; - if (!chmFolderOutStream) - { - chmFolderOutStream = new CChmFolderOutStream; - outStream = chmFolderOutStream; - } - chmFolderOutStream->Init(&m_Database, extractCallback, testMode); - if (!lzxDecoderSpec) - { - lzxDecoderSpec = new NCompress::NLzx::CDecoder; - lzxDecoder = lzxDecoderSpec; - } + lzxDecoder.Create_if_Empty(); UInt64 folderIndex = m_Database.GetFolder(index); const UInt64 compressedPos = m_Database.ContentOffset + section.Offset; - RINOK(lzxDecoderSpec->SetParams_and_Alloc(lzxInfo.GetNumDictBits())); + RINOK(lzxDecoder->Set_DictBits_and_Alloc(lzxInfo.GetNumDictBits())) const CItem *lastItem = &item; extractStatuses.Clear(); @@ -677,14 +635,14 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, for (;; folderIndex++) { - RINOK(extractCallback->SetCompleted(¤tTotalSize)); + RINOK(extractCallback->SetCompleted(¤tTotalSize)) - UInt64 startPos = lzxInfo.GetFolderPos(folderIndex); + const UInt64 startPos = lzxInfo.GetFolderPos(folderIndex); UInt64 finishPos = lastItem->Offset + lastItem->Size; - UInt64 limitFolderIndex = lzxInfo.GetFolder(finishPos); + const UInt64 limitFolderIndex = lzxInfo.GetFolder(finishPos); lastFolderIndex = m_Database.GetLastFolder(index); - UInt64 folderSize = lzxInfo.GetFolderSize(); + const UInt64 folderSize = lzxInfo.GetFolderSize(); UInt64 unPackSize = folderSize; if (extractStatuses.IsEmpty()) @@ -700,7 +658,7 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, const CItem &nextItem = m_Database.Items[m_Database.Indices[nextIndex]]; if (nextItem.Section != sectionIndex) break; - UInt64 nextFolderIndex = m_Database.GetFolder(nextIndex); + const UInt64 nextFolderIndex = m_Database.GetFolder(nextIndex); if (nextFolderIndex != folderIndex) break; for (index++; index < nextIndex; index++) @@ -725,44 +683,48 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, try { - UInt64 startBlock = lzxInfo.GetBlockIndexFromFolderIndex(folderIndex); + const UInt64 startBlock = lzxInfo.GetBlockIndexFromFolderIndex(folderIndex); const CResetTable &rt = lzxInfo.ResetTable; - UInt32 numBlocks = (UInt32)rt.GetNumBlocks(unPackSize); + const UInt32 numBlocks = (UInt32)rt.GetNumBlocks(unPackSize); for (UInt32 b = 0; b < numBlocks; b++) { UInt64 completedSize = currentTotalSize + chmFolderOutStream->m_PosInSection - startPos; - RINOK(extractCallback->SetCompleted(&completedSize)); + RINOK(extractCallback->SetCompleted(&completedSize)) UInt64 bCur = startBlock + b; if (bCur >= rt.ResetOffsets.Size()) return E_FAIL; - UInt64 offset = rt.ResetOffsets[(unsigned)bCur]; + const UInt64 offset = rt.ResetOffsets[(unsigned)bCur]; UInt64 compressedSize; rt.GetCompressedSizeOfBlock(bCur, compressedSize); // chm writes full blocks. So we don't need to use reduced size for last block - RINOK(m_Stream->Seek(compressedPos + offset, STREAM_SEEK_SET, NULL)); - streamSpec->SetStream(m_Stream); - streamSpec->Init(compressedSize); + RINOK(InStream_SeekSet(m_Stream, compressedPos + offset)) + inStream->Init(compressedSize); - lzxDecoderSpec->SetKeepHistory(b > 0); + lzxDecoder->Set_KeepHistory(b > 0); - size_t compressedSizeT = (size_t)compressedSize; - if (compressedSizeT != compressedSize) - throw 2; - packBuf.AllocAtLeast(compressedSizeT); - - HRESULT res = ReadStream_FALSE(inStream, packBuf, compressedSizeT); - - if (res == S_OK) + HRESULT res = S_FALSE; + if (compressedSize <= (1u << 30)) { - lzxDecoderSpec->KeepHistoryForNext = true; - res = lzxDecoderSpec->Code(packBuf, compressedSizeT, kBlockSize); // rt.BlockSize; + const unsigned kAdditionalInputSize = 32; + const size_t compressedSizeT = (size_t)compressedSize; + const size_t allocSize = compressedSizeT + kAdditionalInputSize; + if (allocSize <= compressedSize) + throw 2; + packBuf.AllocAtLeast(allocSize); + res = ReadStream_FALSE(inStream, packBuf, compressedSizeT); if (res == S_OK) - res = WriteStream(chmFolderOutStream, - lzxDecoderSpec->GetUnpackData(), - lzxDecoderSpec->GetUnpackSize()); + { + memset(packBuf + compressedSizeT, 0xff, kAdditionalInputSize); + lzxDecoder->Set_KeepHistoryForNext(true); + res = lzxDecoder->Code_WithExceedReadWrite(packBuf, compressedSizeT, kBlockSize); // rt.BlockSize; + if (res == S_OK) + res = WriteStream(chmFolderOutStream, + lzxDecoder->GetUnpackData(), + lzxDecoder->GetUnpackSize()); + } } if (res != S_OK) @@ -775,7 +737,7 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, } catch(...) { - RINOK(chmFolderOutStream->FlushCorrupted(unPackSize)); + RINOK(chmFolderOutStream->FlushCorrupted(unPackSize)) } currentTotalSize += folderSize; @@ -788,9 +750,9 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, COM_TRY_END } -STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) { - *numItems = m_Database.NewFormat ? 1: + *numItems = m_Database.NewFormat ? 1: (m_Database.LowLevel ? m_Database.Items.Size(): m_Database.Indices.Size()); @@ -803,7 +765,7 @@ static const Byte k_Signature[] = { 'I', 'T', 'S', 'F', 3, 0, 0, 0, 0x60, 0, 0, REGISTER_ARC_I_CLS( CHandler(false), - "Chm", "chm chi chq chw", 0, 0xE9, + "Chm", "chm chi chq chw", NULL, 0xE9, k_Signature, 0, 0, @@ -817,7 +779,7 @@ static const Byte k_Signature[] = { 'I', 'T', 'O', 'L', 'I', 'T', 'L', 'S', 1, 0 REGISTER_ARC_I_CLS( CHandler(true), - "Hxs", "hxs hxi hxr hxq hxw lit", 0, 0xCE, + "Hxs", "hxs hxi hxr hxq hxw lit", NULL, 0xCE, k_Signature, 0, NArcInfoFlags::kFindSignature, diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Chm/ChmHandler.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Chm/ChmHandler.h index 884f391b..94821b21 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Chm/ChmHandler.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Chm/ChmHandler.h @@ -1,7 +1,7 @@ // ChmHandler.h -#ifndef __ARCHIVE_CHM_HANDLER_H -#define __ARCHIVE_CHM_HANDLER_H +#ifndef ZIP7_INC_ARCHIVE_CHM_HANDLER_H +#define ZIP7_INC_ARCHIVE_CHM_HANDLER_H #include "../../../Common/MyCom.h" @@ -12,22 +12,14 @@ namespace NArchive { namespace NChm { -class CHandler: - public IInArchive, - public CMyUnknownImp -{ -public: - MY_UNKNOWN_IMP1(IInArchive) - - INTERFACE_IInArchive(;) - - bool _help2; - CHandler(bool help2): _help2(help2) {} +Z7_CLASS_IMP_CHandler_IInArchive_0 -private: CFilesDatabase m_Database; CMyComPtr m_Stream; + bool _help2; UInt32 m_ErrorFlags; +public: + CHandler(bool help2): _help2(help2) {} }; }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Chm/ChmIn.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/Chm/ChmIn.cpp index 02ecfadd..08660623 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Chm/ChmIn.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Chm/ChmIn.cpp @@ -10,10 +10,10 @@ #include "../../../Common/UTFConvert.h" #include "../../Common/LimitedStreams.h" +#include "../../Common/StreamUtils.h" #include "ChmIn.h" -#define Get16(p) GetUi16(p) #define Get32(p) GetUi32(p) #define Get64(p) GetUi64(p) @@ -38,62 +38,27 @@ struct CHeaderErrorException {}; // define CHM_LOW, if you want to see low level items // #define CHM_LOW -static const GUID kChmLzxGuid = { 0x7FC28940, 0x9D31, 0x11D0, { 0x9B, 0x27, 0x00, 0xA0, 0xC9, 0x1E, 0x9C, 0x7C } }; -static const GUID kHelp2LzxGuid = { 0x0A9007C6, 0x4076, 0x11D3, { 0x87, 0x89, 0x00, 0x00, 0xF8, 0x10, 0x57, 0x54 } }; -static const GUID kDesGuid = { 0x67F6E4A2, 0x60BF, 0x11D3, { 0x85, 0x40, 0x00, 0xC0, 0x4F, 0x58, 0xC3, 0xCF } }; +static const Byte kChmLzxGuid[16] = { 0x40, 0x89, 0xC2, 0x7F, 0x31, 0x9D, 0xD0, 0x11, 0x9B, 0x27, 0x00, 0xA0, 0xC9, 0x1E, 0x9C, 0x7C }; +static const Byte kHelp2LzxGuid[16] = { 0xC6, 0x07, 0x90, 0x0A, 0x76, 0x40, 0xD3, 0x11, 0x87, 0x89, 0x00, 0x00, 0xF8, 0x10, 0x57, 0x54 }; +static const Byte kDesGuid[16] = { 0xA2, 0xE4, 0xF6, 0x67, 0xBF, 0x60, 0xD3, 0x11, 0x85, 0x40, 0x00, 0xC0, 0x4F, 0x58, 0xC3, 0xCF }; -static bool AreGuidsEqual(REFGUID g1, REFGUID g2) +static bool inline AreGuidsEqual(const Byte *g1, const Byte *g2) { - if (g1.Data1 != g2.Data1 || - g1.Data2 != g2.Data2 || - g1.Data3 != g2.Data3) - return false; - for (int i = 0; i < 8; i++) - if (g1.Data4[i] != g2.Data4[i]) - return false; - return true; -} - -static char GetHex(unsigned v) -{ - return (char)((v < 10) ? ('0' + v) : ('A' + (v - 10))); -} - -static void PrintByte(Byte b, AString &s) -{ - s += GetHex(b >> 4); - s += GetHex(b & 0xF); -} - -static void PrintUInt16(UInt16 v, AString &s) -{ - PrintByte((Byte)(v >> 8), s); - PrintByte((Byte)v, s); + return memcmp(g1, g2, 16) == 0; } -static void PrintUInt32(UInt32 v, AString &s) +static void PrintByte(unsigned b, AString &s) { - PrintUInt16((UInt16)(v >> 16), s); - PrintUInt16((UInt16)v, s); + s += (char)GET_HEX_CHAR_UPPER(b >> 4); + s += (char)GET_HEX_CHAR_LOWER(b & 0xF); } AString CMethodInfo::GetGuidString() const { - AString s; - s += '{'; - PrintUInt32(Guid.Data1, s); - s += '-'; - PrintUInt16(Guid.Data2, s); - s += '-'; - PrintUInt16(Guid.Data3, s); - s += '-'; - PrintByte(Guid.Data4[0], s); - PrintByte(Guid.Data4[1], s); - s += '-'; - for (int i = 2; i < 8; i++) - PrintByte(Guid.Data4[i], s); - s += '}'; - return s; + char s[16 * 2 + 8]; + RawLeGuidToString_Braced(Guid, s); + // MyStringUpper_Ascii(s); + return (AString)s; } bool CMethodInfo::IsLzx() const @@ -108,32 +73,30 @@ bool CMethodInfo::IsDes() const return AreGuidsEqual(Guid, kDesGuid); } -UString CMethodInfo::GetName() const +AString CMethodInfo::GetName() const { - UString s; + AString s; if (IsLzx()) { - s.SetFromAscii("LZX:"); - char temp[16]; - ConvertUInt32ToString(LzxInfo.GetNumDictBits(), temp); - s.AddAscii(temp); + s = "LZX:"; + s.Add_UInt32(LzxInfo.GetNumDictBits()); } else { - AString s2; if (IsDes()) - s2 = "DES"; + s = "DES"; else { - s2 = GetGuidString(); - if (ControlData.Size() > 0) + s = GetGuidString(); + /* + if (ControlData.Size() > 0 && ControlData.Size() <= (1 << 6)) { - s2 += ':'; + s.Add_Colon(); for (size_t i = 0; i < ControlData.Size(); i++) - PrintByte(ControlData[i], s2); + PrintByte(ControlData[i], s); } + */ } - ConvertUTF8ToUnicode(s2, s); } return s; } @@ -151,9 +114,9 @@ UString CSectionInfo::GetMethodName() const if (!IsLzx()) { UString temp; - if (ConvertUTF8ToUnicode(Name, temp)) + ConvertUTF8ToUnicode(Name, temp); s += temp; - s.AddAscii(": "); + s += ": "; } FOR_VECTOR (i, Methods) { @@ -211,7 +174,7 @@ UInt64 CInArchive::ReadEncInt() UInt64 val = 0; for (int i = 0; i < 9; i++) { - Byte b = ReadByte(); + const unsigned b = ReadByte(); val |= (b & 0x7F); if (b < 0x80) return val; @@ -220,12 +183,9 @@ UInt64 CInArchive::ReadEncInt() throw CHeaderErrorException(); } -void CInArchive::ReadGUID(GUID &g) +void CInArchive::ReadGUID(Byte *g) { - g.Data1 = ReadUInt32(); - g.Data2 = ReadUInt16(); - g.Data3 = ReadUInt16(); - ReadBytes(g.Data4, 8); + ReadBytes(g, 16); } void CInArchive::ReadString(unsigned size, AString &s) @@ -243,7 +203,7 @@ void CInArchive::ReadUString(unsigned size, UString &s) s.Empty(); while (size-- != 0) { - wchar_t c = ReadUInt16(); + const wchar_t c = ReadUInt16(); if (c == 0) { Skip(2 * size); @@ -255,7 +215,7 @@ void CInArchive::ReadUString(unsigned size, UString &s) HRESULT CInArchive::ReadChunk(IInStream *inStream, UInt64 pos, UInt64 size) { - RINOK(inStream->Seek(pos, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekSet(inStream, pos)) CLimitedSequentialInStream *streamSpec = new CLimitedSequentialInStream; CMyComPtr limitedStream(streamSpec); streamSpec->SetStream(inStream); @@ -269,7 +229,7 @@ HRESULT CInArchive::ReadChunk(IInStream *inStream, UInt64 pos, UInt64 size) HRESULT CInArchive::ReadDirEntry(CDatabase &database) { CItem item; - UInt64 nameLen = ReadEncInt(); + const UInt64 nameLen = ReadEncInt(); if (nameLen == 0 || nameLen > (1 << 13)) return S_FALSE; ReadString((unsigned)nameLen, item.Name); @@ -299,7 +259,7 @@ HRESULT CInArchive::OpenChm(IInStream *inStream, CDatabase &database) // The third and fourth bytes may contain even more fractional bits. // The 4 least significant bits in the last byte are constant. /* UInt32 lang = */ ReadUInt32(); - GUID g; + Byte g[16]; ReadGUID(g); // {7C01FD10-7BAA-11D0-9E0C-00A0-C922-E6EC} ReadGUID(g); // {7C01FD11-7BAA-11D0-9E0C-00A0-C922-E6EC} const unsigned kNumSections = 2; @@ -370,7 +330,7 @@ HRESULT CInArchive::OpenChm(IInStream *inStream, CDatabase &database) // One quickref entry exists for every n entries in the file, where n // is calculated as 1 + (1 << quickref density). So for density = 2, n = 5. - UInt32 quickrefLength = ReadUInt32(); // Len of free space and/or quickref area at end of directory chunk + const UInt32 quickrefLength = ReadUInt32(); // Len of free space and/or quickref area at end of directory chunk if (quickrefLength > dirChunkSize || quickrefLength < 2) return S_FALSE; ReadUInt32(); // Always 0 @@ -382,19 +342,19 @@ HRESULT CInArchive::OpenChm(IInStream *inStream, CDatabase &database) for (;;) { - UInt64 offset = _inBuffer.GetProcessedSize() - chunkPos; - UInt32 offsetLimit = dirChunkSize - quickrefLength; + const UInt64 offset = _inBuffer.GetProcessedSize() - chunkPos; + const UInt32 offsetLimit = dirChunkSize - quickrefLength; if (offset > offsetLimit) return S_FALSE; if (offset == offsetLimit) break; - RINOK(ReadDirEntry(database)); + RINOK(ReadDirEntry(database)) numItems++; } Skip(quickrefLength - 2); - unsigned rrr = ReadUInt16(); + const unsigned rrr = ReadUInt16(); if (rrr != numItems) { // Lazarus 9-26-2 chm contains 0 here. @@ -422,7 +382,7 @@ HRESULT CInArchive::OpenHelp2(IInStream *inStream, CDatabase &database) IsArc = true; ReadUInt32(); // Len of post-header table - GUID g; + Byte g[16]; ReadGUID(g); // {0A9007C1-4076-11D3-8789-0000F8105754} // header section table @@ -575,40 +535,46 @@ HRESULT CInArchive::OpenHelp2(IInStream *inStream, CDatabase &database) unsigned numItems = 0; for (;;) { - UInt64 offset = _inBuffer.GetProcessedSize() - chunkPos; - UInt32 offsetLimit = dirChunkSize - quickrefLength; + const UInt64 offset = _inBuffer.GetProcessedSize() - chunkPos; + const UInt32 offsetLimit = dirChunkSize - quickrefLength; if (offset > offsetLimit) return S_FALSE; if (offset == offsetLimit) break; if (database.NewFormat) { - UInt16 nameLen = ReadUInt16(); + const unsigned nameLen = ReadUInt16(); if (nameLen == 0) return S_FALSE; UString name; - ReadUString((unsigned)nameLen, name); + ReadUString(nameLen, name); AString s; ConvertUnicodeToUTF8(name, s); - Byte b = ReadByte(); - s.Add_Space(); - PrintByte(b, s); + { + const unsigned b = ReadByte(); + s.Add_Space(); + PrintByte(b, s); + } s.Add_Space(); UInt64 len = ReadEncInt(); // then number of items ? // then length ? // then some data (binary encoding?) - while (len-- != 0) + if (len > 1u << 29) // what limit here we need? + return S_FALSE; + if (len) + do { - b = ReadByte(); + const unsigned b = ReadByte(); PrintByte(b, s); } + while (--len); database.NewFormatString += s; database.NewFormatString += "\r\n"; } else { - RINOK(ReadDirEntry(database)); + RINOK(ReadDirEntry(database)) } numItems++; } @@ -637,24 +603,24 @@ HRESULT CInArchive::DecompressStream(IInStream *inStream, const CDatabase &datab #define DATA_SPACE "::DataSpace/" -static const char *kNameList = DATA_SPACE "NameList"; -static const char *kStorage = DATA_SPACE "Storage/"; -static const char *kContent = "Content"; -static const char *kControlData = "ControlData"; -static const char *kSpanInfo = "SpanInfo"; -static const char *kTransform = "Transform/"; -static const char *kResetTable = "/InstanceData/ResetTable"; -static const char *kTransformList = "List"; +#define kNameList DATA_SPACE "NameList" +#define kStorage DATA_SPACE "Storage/" +#define kContent "Content" +#define kControlData "ControlData" +#define kSpanInfo "SpanInfo" +#define kTransform "Transform/" +#define kResetTable "/InstanceData/ResetTable" +#define kTransformList "List" static AString GetSectionPrefix(const AString &name) { - AString s = kStorage; + AString s (kStorage); s += name; - s += '/'; + s.Add_Slash(); return s; } -#define RINOZ(x) { int __tt = (x); if (__tt != 0) return __tt; } +#define RINOZ(x) { int _tt_ = (x); if (_tt_ != 0) return _tt_; } static int CompareFiles(const unsigned *p1, const unsigned *p2, void *param) { @@ -672,9 +638,9 @@ static int CompareFiles(const unsigned *p1, const unsigned *p2, void *param) } else { - RINOZ(MyCompare(item1.Section, item2.Section)); - RINOZ(MyCompare(item1.Offset, item2.Offset)); - RINOZ(MyCompare(item1.Size, item2.Size)); + RINOZ(MyCompare(item1.Section, item2.Section)) + RINOZ(MyCompare(item1.Offset, item2.Offset)) + RINOZ(MyCompare(item1.Size, item2.Size)) } return MyCompare(*p1, *p2); } @@ -743,13 +709,13 @@ HRESULT CInArchive::OpenHighLevel(IInStream *inStream, CFilesDatabase &database) { { // The NameList file - RINOK(DecompressStream(inStream, database, kNameList)); + RINOK(DecompressStream(inStream, database, (AString)kNameList)) /* UInt16 length = */ ReadUInt16(); UInt16 numSections = ReadUInt16(); for (unsigned i = 0; i < numSections; i++) { CSectionInfo section; - UInt16 nameLen = ReadUInt16(); + const unsigned nameLen = ReadUInt16(); UString name; ReadUString(nameLen, name); if (ReadUInt16() != 0) @@ -764,7 +730,7 @@ HRESULT CInArchive::OpenHighLevel(IInStream *inStream, CFilesDatabase &database) for (si = 1; si < database.Sections.Size(); si++) { CSectionInfo §ion = database.Sections[si]; - AString sectionPrefix = GetSectionPrefix(section.Name); + AString sectionPrefix (GetSectionPrefix(section.Name)); { // Content int index = database.FindItem(sectionPrefix + kContent); @@ -774,11 +740,11 @@ HRESULT CInArchive::OpenHighLevel(IInStream *inStream, CFilesDatabase &database) section.Offset = item.Offset; section.CompressedSize = item.Size; } - AString transformPrefix = sectionPrefix + kTransform; + AString transformPrefix (sectionPrefix + kTransform); if (database.Help2Format) { // Transform List - RINOK(DecompressStream(inStream, database, transformPrefix + kTransformList)); + RINOK(DecompressStream(inStream, database, transformPrefix + kTransformList)) if ((_chunkSize & 0xF) != 0) return S_FALSE; unsigned numGuids = (unsigned)(_chunkSize / 0x10); @@ -794,13 +760,13 @@ HRESULT CInArchive::OpenHighLevel(IInStream *inStream, CFilesDatabase &database) else { CMethodInfo method; - method.Guid = kChmLzxGuid; + memcpy(method.Guid, kChmLzxGuid, 16); section.Methods.Add(method); } { // Control Data - RINOK(DecompressStream(inStream, database, sectionPrefix + kControlData)); + RINOK(DecompressStream(inStream, database, sectionPrefix + kControlData)) FOR_VECTOR (mi, section.Methods) { @@ -819,38 +785,43 @@ HRESULT CInArchive::OpenHighLevel(IInStream *inStream, CFilesDatabase &database) { // There is bug in VC6, if we use function call as parameter for inline function - UInt32 val32 = ReadUInt32(); - int n = GetLog(val32); + const UInt32 val32 = ReadUInt32(); + const int n = GetLog(val32); if (n < 0 || n > 16) return S_FALSE; - li.ResetIntervalBits = n; + li.ResetIntervalBits = (unsigned)n; } { - UInt32 val32 = ReadUInt32(); - int n = GetLog(val32); + const UInt32 val32 = ReadUInt32(); + const int n = GetLog(val32); if (n < 0 || n > 16) return S_FALSE; - li.WindowSizeBits = n; + li.WindowSizeBits = (unsigned)n; } li.CacheSize = ReadUInt32(); numDWORDS -= 5; - while (numDWORDS-- != 0) + if (numDWORDS) + do ReadUInt32(); + while (--numDWORDS); } else { - UInt32 numBytes = numDWORDS * 4; - method.ControlData.Alloc(numBytes); - ReadBytes(method.ControlData, numBytes); + if (numDWORDS > 1u << 27) + return S_FALSE; + const size_t numBytes = (size_t)numDWORDS * 4; + // method.ControlData.Alloc(numBytes); + // ReadBytes(method.ControlData, numBytes); + Skip(numBytes); } } } { // SpanInfo - RINOK(DecompressStream(inStream, database, sectionPrefix + kSpanInfo)); + RINOK(DecompressStream(inStream, database, sectionPrefix + kSpanInfo)) section.UncompressedSize = ReadUInt64(); } @@ -862,7 +833,7 @@ HRESULT CInArchive::OpenHighLevel(IInStream *inStream, CFilesDatabase &database) { // ResetTable; RINOK(DecompressStream(inStream, database, transformPrefix + - method.GetGuidString() + kResetTable)); + method.GetGuidString() + kResetTable)) CResetTable &rt = method.LzxInfo.ResetTable; if (_chunkSize < 4) @@ -878,10 +849,10 @@ HRESULT CInArchive::OpenHighLevel(IInStream *inStream, CFilesDatabase &database) } else { - UInt32 ver = ReadUInt32(); // 2 unknown (possibly a version number) + const UInt32 ver = ReadUInt32(); // 2 unknown (possibly a version number) if (ver != 2 && ver != 3) return S_FALSE; - UInt32 numEntries = ReadUInt32(); + const UInt32 numEntries = ReadUInt32(); const unsigned kEntrySize = 8; if (ReadUInt32() != kEntrySize) return S_FALSE; @@ -944,7 +915,7 @@ HRESULT CInArchive::Open2(IInStream *inStream, database.Help2Format = _help2; const UInt32 chmVersion = 3; - RINOK(inStream->Seek(0, STREAM_SEEK_CUR, &database.StartPosition)); + RINOK(InStream_GetPos(inStream, database.StartPosition)) if (!_inBuffer.Create(1 << 14)) return E_OUTOFMEMORY; @@ -980,7 +951,7 @@ HRESULT CInArchive::Open2(IInStream *inStream, } database.StartPosition += _inBuffer.GetProcessedSize() - kSignatureSize; - RINOK(OpenHelp2(inStream, database)); + RINOK(OpenHelp2(inStream, database)) if (database.NewFormat) return S_OK; } @@ -990,7 +961,7 @@ HRESULT CInArchive::Open2(IInStream *inStream, return S_FALSE; if (ReadUInt32() != chmVersion) return S_FALSE; - RINOK(OpenChm(inStream, database)); + RINOK(OpenChm(inStream, database)) } @@ -1007,7 +978,7 @@ HRESULT CInArchive::Open2(IInStream *inStream, database.HighLevelClear(); return S_OK; } - RINOK(res); + RINOK(res) if (!database.CheckSectionRefs()) HeadersError = true; database.LowLevel = false; diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Chm/ChmIn.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Chm/ChmIn.h index bf51616f..faf4edfd 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Chm/ChmIn.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Chm/ChmIn.h @@ -1,7 +1,7 @@ // Archive/ChmIn.h -#ifndef __ARCHIVE_CHM_IN_H -#define __ARCHIVE_CHM_IN_H +#ifndef ZIP7_INC_ARCHIVE_CHM_IN_H +#define ZIP7_INC_ARCHIVE_CHM_IN_H #include "../../../Common/MyBuffer.h" #include "../../../Common/MyString.h" @@ -45,9 +45,9 @@ struct CItem struct CDatabase { + CObjectVector Items; UInt64 StartPosition; UInt64 ContentOffset; - CObjectVector Items; AString NewFormatString; bool Help2Format; bool NewFormat; @@ -59,7 +59,7 @@ struct CDatabase { FOR_VECTOR (i, Items) if (Items[i].Name == name) - return i; + return (int)i; return -1; } @@ -84,6 +84,11 @@ struct CResetTable // unsigned BlockSizeBits; CRecordVector ResetOffsets; + CResetTable(): + UncompressedSize(0), + CompressedSize(0) + {} + bool GetCompressedSizeOfBlocks(UInt64 blockIndex, UInt32 numBlocks, UInt64 &size) const { if (blockIndex >= ResetOffsets.Size()) @@ -118,6 +123,13 @@ struct CLzxInfo CResetTable ResetTable; + CLzxInfo(): + Version(0), + ResetIntervalBits(0), + WindowSizeBits(0), + CacheSize(0) + {} + unsigned GetNumDictBits() const { if (Version == 2 || Version == 3) @@ -125,14 +137,14 @@ struct CLzxInfo return 0; } - UInt64 GetFolderSize() const { return kBlockSize << ResetIntervalBits; } + UInt64 GetFolderSize() const { return (UInt64)kBlockSize << ResetIntervalBits; } UInt64 GetFolder(UInt64 offset) const { return offset / GetFolderSize(); } UInt64 GetFolderPos(UInt64 folderIndex) const { return folderIndex * GetFolderSize(); } UInt64 GetBlockIndexFromFolderIndex(UInt64 folderIndex) const { return folderIndex << ResetIntervalBits; } bool GetOffsetOfFolder(UInt64 folderIndex, UInt64 &offset) const { - UInt64 blockIndex = GetBlockIndexFromFolderIndex(folderIndex); + const UInt64 blockIndex = GetBlockIndexFromFolderIndex(folderIndex); if (blockIndex >= ResetTable.ResetOffsets.Size()) return false; offset = ResetTable.ResetOffsets[(unsigned)blockIndex]; @@ -149,14 +161,14 @@ struct CLzxInfo struct CMethodInfo { - GUID Guid; - CByteBuffer ControlData; + Byte Guid[16]; + // CByteBuffer ControlData; CLzxInfo LzxInfo; bool IsLzx() const; bool IsDes() const; AString GetGuidString() const; - UString GetName() const; + AString GetName() const; }; @@ -176,9 +188,9 @@ struct CSectionInfo class CFilesDatabase: public CDatabase { public: - bool LowLevel; CUIntVector Indices; CObjectVector Sections; + bool LowLevel; UInt64 GetFileSize(unsigned fileIndex) const { return Items[Indices[fileIndex]].Size; } UInt64 GetFileOffset(unsigned fileIndex) const { return Items[Indices[fileIndex]].Offset; } @@ -243,7 +255,7 @@ class CInArchive UInt64 ReadEncInt(); void ReadString(unsigned size, AString &s); void ReadUString(unsigned size, UString &s); - void ReadGUID(GUID &g); + void ReadGUID(Byte *g); HRESULT ReadChunk(IInStream *inStream, UInt64 pos, UInt64 size); diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Chm/StdAfx.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Chm/StdAfx.h index 2854ff3e..035267cc 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Chm/StdAfx.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Chm/StdAfx.h @@ -1,8 +1,11 @@ // StdAfx.h -#ifndef __STDAFX_H -#define __STDAFX_H +#ifndef ZIP7_INC_STDAFX_H +#define ZIP7_INC_STDAFX_H +#if defined(_MSC_VER) && _MSC_VER >= 1800 +#pragma warning(disable : 4464) // relative include path contains '..' +#endif #include "../../../Common/Common.h" #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/ComHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/ComHandler.cpp index c6d2bd25..5bbdf07d 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/ComHandler.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/ComHandler.cpp @@ -2,41 +2,62 @@ #include "StdAfx.h" -#include "../../../C/Alloc.h" #include "../../../C/CpuArch.h" -#include "../../Common/IntToString.h" #include "../../Common/ComTry.h" -#include "../../Common/MyCom.h" -#include "../../Common/MyBuffer.h" -#include "../../Common/MyString.h" #include "../../Windows/PropVariant.h" #include "../Common/LimitedStreams.h" #include "../Common/ProgressUtils.h" #include "../Common/RegisterArc.h" +#include "../Common/StreamObjects.h" #include "../Common/StreamUtils.h" #include "../Compress/CopyCoder.h" -#define Get16(p) GetUi16(p) -#define Get32(p) GetUi32(p) +#include "Common/ItemNameUtils.h" + +#define Get16(p) GetUi16a(p) +#define Get32(p) GetUi32a(p) + +// we don't expect to get deleted files in real files +// define Z7_COMPOUND_SHOW_DELETED for debug +// #define Z7_COMPOUND_SHOW_DELETED namespace NArchive { namespace NCom { -#define SIGNATURE { 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 } -static const Byte kSignature[] = SIGNATURE; +static const unsigned k_Long_path_level_limit = 256; + +static const Byte kSignature[] = + { 0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1 }; + +// encoded "[!]MsiPatchSequence" name in "msp" file +static const Byte k_Sequence_msp[] = + { 0x40, 0x48, 0x96, 0x45, 0x6c, 0x3e, 0xe4, 0x45, + 0xe6, 0x42, 0x16, 0x42, 0x37, 0x41, 0x27, 0x41, + 0x37, 0x41, 0, 0 }; -enum EType +// encoded "MergeModule.CABinet" name in "msm" file +static const Byte k_Sequence_msm[] = + { 0x16, 0x42, 0xb5, 0x42, 0xa8, 0x3d, 0xf2, 0x41, + 0xf8, 0x43, 0xa8, 0x47, 0x8c, 0x3a, 0x0b, 0x43, + 0x31, 0x42, 0x37, 0x48, 0, 0 }; + +// static const Byte k_CLSID_AAF_V3[] = { 0x41, 0x41, 0x46, 0x42, 0x0d, 0x00, 0x4f, 0x4d, 0x06, 0x0e, 0x2b, 0x34, 0x01, 0x01, 0x01, 0xff }; +// static const Byte k_CLSID_AAF_V4[] = { 0x01, 0x02, 0x01, 0x0d, 0x00, 0x02, 0x00, 0x00, 0x06, 0x0e, 0x2b, 0x34, 0x03, 0x02, 0x01, 0x01 }; + +enum EArcType { k_Type_Common, k_Type_Msi, k_Type_Msp, + k_Type_Msm, k_Type_Doc, k_Type_Ppt, k_Type_Xls, + k_Type_Aaf }; static const char * const kExtensions[] = @@ -44,35 +65,63 @@ static const char * const kExtensions[] = "compound" , "msi" , "msp" + , "msm" , "doc" , "ppt" , "xls" + , "aaf" }; namespace NFatID { - static const UInt32 kFree = 0xFFFFFFFF; - static const UInt32 kEndOfChain = 0xFFFFFFFE; - static const UInt32 kFatSector = 0xFFFFFFFD; - static const UInt32 kMatSector = 0xFFFFFFFC; - static const UInt32 kMaxValue = 0xFFFFFFFA; + static const UInt32 kFree = 0xffffffff; + static const UInt32 kEndOfChain = 0xfffffffe; + static const UInt32 kFatSector = 0xfffffffd; + static const UInt32 k_DIF_SECT = 0xfffffffc; // double-indirect file allocation table (DIFAT) + static const UInt32 kMaxValue = 0xfffffffa; } namespace NItemType { - static const Byte kEmpty = 0; - static const Byte kStorage = 1; - static const Byte kStream = 2; - static const Byte kLockBytes = 3; - static const Byte kProperty = 4; - static const Byte kRootStorage = 5; + static const unsigned kEmpty = 0; + static const unsigned kStorage = 1; + static const unsigned kStream = 2; + // static const unsigned kLockBytes = 3; + // static const unsigned kProperty = 4; + static const unsigned kRootStorage = 5; } -static const UInt32 kNameSizeMax = 64; +static const unsigned k_MiniSectorSizeBits = 6; +static const UInt32 k_LongStreamMinSize = 1 << 12; + +static const unsigned k_Msi_NumBits = 6; +static const unsigned k_Msi_NumChars = 1 << k_Msi_NumBits; +static const unsigned k_Msi_CharMask = k_Msi_NumChars - 1; +static const unsigned k_Msi_UnicodeRange = k_Msi_NumChars * (k_Msi_NumChars + 1); +static const unsigned k_Msi_StartUnicodeChar = 0x3800; +static const unsigned k_Msi_SpecUnicodeChar = k_Msi_StartUnicodeChar + k_Msi_UnicodeRange; +// (k_Msi_SpecUnicodeChar == 0x4840) is used as special symbol that is used +// as first character in some names in dir entries +/* +static bool IsMsiName(const Byte *p) +{ + unsigned c = Get16(p); + c -= k_Msi_StartUnicodeChar; + return c <= k_Msi_UnicodeRange; +} +*/ + +Z7_FORCE_INLINE static bool IsLargeStream(UInt64 size) +{ + return size >= k_LongStreamMinSize; +} + +static const unsigned kNameSizeMax = 64; +static const UInt32 k_Item_Level_Unused = (UInt32)0 - 1; struct CItem { - Byte Name[kNameSizeMax]; + Byte Name[kNameSizeMax]; // must be aligned for 2-bytes // UInt16 NameSize; // UInt32 Flags; FILETIME CTime; @@ -82,474 +131,829 @@ struct CItem UInt32 RightDid; UInt32 SonDid; UInt32 Sid; - Byte Type; + unsigned Type; // Byte : we use unsigned instead of Byte for alignment - bool IsEmpty() const { return Type == NItemType::kEmpty; } + UInt32 Level; + + bool IsEmptyType() const { return Type == NItemType::kEmpty; } bool IsDir() const { return Type == NItemType::kStorage || Type == NItemType::kRootStorage; } + bool IsStorage() const { return Type == NItemType::kStorage; } + + bool IsLevel_Unused() const { return Level == k_Item_Level_Unused; } - void Parse(const Byte *p, bool mode64bit); + // bool IsSpecMsiName() const { return Get16(Name) == k_Msi_SpecUnicodeChar; } + bool AreMsiChars() const + { + for (unsigned i = 0; i < kNameSizeMax; i += 2) + { + unsigned c = Get16(Name + i); + if (c == 0) + break; + c -= k_Msi_StartUnicodeChar; + if (c <= k_Msi_UnicodeRange) + return true; + } + return false; + } + bool Parse(const Byte *p, bool mode64bit); }; + +static const UInt32 k_Ref_Parent_Root = 0xffffffff; + struct CRef { - int Parent; - UInt32 Did; + UInt32 Parent; // index in Refs[] + UInt32 Did; // index in Items[] }; + class CDatabase { - UInt32 NumSectorsInMiniStream; - CObjArray MiniSids; - - HRESULT AddNode(int parent, UInt32 did); public: - + CRecordVector Refs; + CObjectVector Items; CObjArray Fat; - UInt32 FatSize; - CObjArray Mat; - UInt32 MatSize; + CObjArray MiniSids; - CObjectVector Items; - CRecordVector Refs; + UInt32 FatSize; + UInt32 MatSize; + UInt32 NumSectors_in_MiniStream; - UInt32 LongStreamMinSize; + // UInt32 LongStreamMinSize; unsigned SectorSizeBits; - unsigned MiniSectorSizeBits; Int32 MainSubfile; + EArcType Type; + + bool IsArc; + bool HeadersError; + // bool HeadersWarning; + // bool IsMsi; UInt64 PhySize; - EType Type; + UInt64 PhySize_Unaligned; + // UInt64 FreeSize; - bool IsNotArcType() const + IArchiveOpenCallback *OpenCallback; + UInt32 Callback_Cur; + +private: + /* + HRESULT IncreaseOpenTotal(UInt32 numSects) { - return - Type != k_Type_Msi && - Type != k_Type_Msp; + if (!OpenCallback) + return S_OK; + const UInt64 total = (UInt64)(Callback_Cur + numSects) << SectorSizeBits; + return OpenCallback->SetTotal(NULL, &total); } + */ + HRESULT AddNodes(); + HRESULT ReadSector(IInStream *inStream, Byte *buf, UInt32 sid); + HRESULT ReadIDs(IInStream *inStream, Byte *buf, UInt32 sid, UInt32 *dest); + HRESULT Check_Item(unsigned index); - void UpdatePhySize(UInt64 val) +public: + bool IsNotArcType() const { - if (PhySize < val) - PhySize = val; + return + Type != k_Type_Msi && + Type != k_Type_Msp && + Type != k_Type_Msm; } - HRESULT ReadSector(IInStream *inStream, Byte *buf, unsigned sectorSizeBits, UInt32 sid); - HRESULT ReadIDs(IInStream *inStream, Byte *buf, unsigned sectorSizeBits, UInt32 sid, UInt32 *dest); - - HRESULT Update_PhySize_WithItem(unsigned index); void Clear(); - bool IsLargeStream(UInt64 size) const { return size >= LongStreamMinSize; } UString GetItemPath(UInt32 index) const; UInt64 GetItemPackSize(UInt64 size) const { - UInt64 mask = ((UInt64)1 << (IsLargeStream(size) ? SectorSizeBits : MiniSectorSizeBits)) - 1; - return (size + mask) & ~mask; - } - - bool GetMiniCluster(UInt32 sid, UInt64 &res) const - { - unsigned subBits = SectorSizeBits - MiniSectorSizeBits; - UInt32 fid = sid >> subBits; - if (fid >= NumSectorsInMiniStream) - return false; - res = (((UInt64)MiniSids[fid] + 1) << subBits) + (sid & ((1 << subBits) - 1)); - return true; + const UInt64 mask = ((UInt32)1 << (IsLargeStream(size) ? SectorSizeBits : k_MiniSectorSizeBits)) - 1; + return (size + mask) & ~(UInt64)mask; } HRESULT Open(IInStream *inStream); }; -HRESULT CDatabase::ReadSector(IInStream *inStream, Byte *buf, unsigned sectorSizeBits, UInt32 sid) +HRESULT CDatabase::ReadSector(IInStream *inStream, Byte *buf, UInt32 sid) { - UpdatePhySize(((UInt64)sid + 2) << sectorSizeBits); - RINOK(inStream->Seek((((UInt64)sid + 1) << sectorSizeBits), STREAM_SEEK_SET, NULL)); - return ReadStream_FALSE(inStream, buf, (size_t)1 << sectorSizeBits); + const unsigned sb = SectorSizeBits; + RINOK(InStream_SeekSet(inStream, ((UInt64)sid + 1) << sb)) + RINOK(ReadStream_FALSE(inStream, buf, (size_t)1 << sb)) + if (OpenCallback) + { + if ((++Callback_Cur & 0xfff) == 0) + { + const UInt64 processed = (UInt64)Callback_Cur << sb; + const UInt64 numFiles = Items.Size(); + RINOK(OpenCallback->SetCompleted(&numFiles, &processed)) + } + } + return S_OK; } -HRESULT CDatabase::ReadIDs(IInStream *inStream, Byte *buf, unsigned sectorSizeBits, UInt32 sid, UInt32 *dest) +HRESULT CDatabase::ReadIDs(IInStream *inStream, Byte *buf, UInt32 sid, UInt32 *dest) { - RINOK(ReadSector(inStream, buf, sectorSizeBits, sid)); - UInt32 sectorSize = (UInt32)1 << sectorSizeBits; - for (UInt32 t = 0; t < sectorSize; t += 4) + RINOK(ReadSector(inStream, buf, sid)) + const size_t sectorSize = (size_t)1 << SectorSizeBits; + for (size_t t = 0; t < sectorSize; t += 4) *dest++ = Get32(buf + t); return S_OK; } + +Z7_FORCE_INLINE static void GetFileTimeFromMem(const Byte *p, FILETIME *ft) { ft->dwLowDateTime = Get32(p); ft->dwHighDateTime = Get32(p + 4); } -void CItem::Parse(const Byte *p, bool mode64bit) +bool CItem::Parse(const Byte *p, bool mode64bit) { memcpy(Name, p, kNameSizeMax); - // NameSize = Get16(p + 64); + unsigned i; + for (i = 0; i < kNameSizeMax; i += 2) + if (*(const UInt16 *)(const void *)(p + i) == 0) + break; +#if 0 // 1 : for debug : for more strict field check + { + for (unsigned k = i; k < kNameSizeMax; k += 2) + if (*(const UInt16 *)(const void *)(p + k) != 0) + return false; + } +#endif Type = p[66]; + // DOC: names are limited to 32 UTF-16 code points, including the terminating null character. + if (!IsEmptyType()) + if (i == kNameSizeMax || i + 2 != Get16(p + 64)) // NameLength + return false; + if (p[67] >= 2) // Color: 0 (red) or 1 (black) + return false; LeftDid = Get32(p + 68); RightDid = Get32(p + 72); SonDid = Get32(p + 76); - // Flags = Get32(p + 96); + // if (Get32(p + 96) == 0) return false; // State / Flags GetFileTimeFromMem(p + 100, &CTime); GetFileTimeFromMem(p + 108, &MTime); Sid = Get32(p + 116); Size = Get32(p + 120); + /* MS DOC: it is recommended that parsers ignore the most + significant 32 bits of this field in version 3 compound files */ if (mode64bit) Size |= ((UInt64)Get32(p + 124) << 32); + return true; } + void CDatabase::Clear() { + Type = k_Type_Common; + MainSubfile = -1; + IsArc = false; + HeadersError = false; + // HeadersWarning = false; + // IsMsi = false; PhySize = 0; - + PhySize_Unaligned = 0; + // FreeSize = 0; + Callback_Cur = 0; + // OpenCallback = NULL; + + FatSize = 0; + MatSize = 0; + NumSectors_in_MiniStream = 0; + Fat.Free(); - MiniSids.Free(); Mat.Free(); + MiniSids.Free(); Items.Clear(); Refs.Clear(); } -static const UInt32 kNoDid = 0xFFFFFFFF; -HRESULT CDatabase::AddNode(int parent, UInt32 did) +static const UInt32 kNoDid = 0xffffffff; + +HRESULT CDatabase::AddNodes() { - if (did == kNoDid) + UInt32 index = Items[0].SonDid; // Items[0] is root item + if (index == kNoDid) // no files case return S_OK; - if (did >= (UInt32)Items.Size()) - return S_FALSE; - const CItem &item = Items[did]; - if (item.IsEmpty()) - return S_FALSE; - CRef ref; - ref.Parent = parent; - ref.Did = did; - int index = Refs.Add(ref); - if (Refs.Size() > Items.Size()) + if (index == 0 || index >= Items.Size()) return S_FALSE; - RINOK(AddNode(parent, item.LeftDid)); - RINOK(AddNode(parent, item.RightDid)); - if (item.IsDir()) + + CObjArray itemParents(Items.Size()); + CByteArr states(Items.Size()); + memset(itemParents, 0, (size_t)Items.Size() * sizeof(itemParents[0])); // optional + memset(states, 0, Items.Size()); + +#if 1 // 0 : for debug + const UInt32 k_exitParent = 0; + const UInt32 k_startLevel = 1; + // we don't show "Root Entry" dir + states[0] = 3; // we mark root node as processed, also we block any cycle links to root node + // itemParents[0] = 0xffffffff; // optional / unused value +#else + // we show item[0] "Root Entry" dir + const UInt32 k_exitParent = 0xffffffff; + const UInt32 k_startLevel = 0; + index = 0; +#endif + + UInt32 level = k_startLevel; // directory level + unsigned state = 0; + UInt32 parent = k_exitParent; // in Items[], itemParents[], states[] + UInt32 refParent = k_Ref_Parent_Root; // in Refs[] + + for (;;) { - RINOK(AddNode(index, item.SonDid)); - } - return S_OK; -} + if (state >= 3) + { + // we return to parent node + if (state != 3) + return E_FAIL; + index = parent; + if (index == k_exitParent) + break; + if (index >= Items.Size()) + return E_FAIL; // (index) was checked already + parent = itemParents[index]; + state = states[index]; + if (state == 0) + return E_FAIL; + if (state == 2) + { + // we return to parent Dir node + if (refParent >= Refs.Size()) + return E_FAIL; + refParent = Refs[refParent].Parent; + level--; + } + continue; + } -static const wchar_t kCharOpenBracket = L'['; -static const wchar_t kCharCloseBracket = L']'; + if (index >= Items.Size()) + return S_FALSE; + CItem &item = Items[index]; + if (item.IsEmptyType()) + return S_FALSE; + item.Level = level; + state++; + states[index] = (Byte)state; // we mark current (index) node as used node -static UString CompoundNameToFileName(const UString &s) -{ - UString res; - for (unsigned i = 0; i < s.Len(); i++) + UInt32 newIndex; + if (state != 2) + newIndex = (state < 2) ? item.LeftDid : item.RightDid; + else + { + CRef ref; + ref.Parent = refParent; + ref.Did = index; + const unsigned refIndex = Refs.Add(ref); + if (!item.IsDir()) + continue; + newIndex = item.SonDid; + if (newIndex != kNoDid) + { + level++; + refParent = refIndex; + } + } + + if (newIndex != kNoDid) + { + itemParents[index] = parent; + state = 0; + parent = index; + index = newIndex; + if (index >= Items.Size() || states[index]) + return S_FALSE; + } + } + + if (level != k_startLevel || refParent != k_Ref_Parent_Root) + return E_FAIL; +#if 1 // 1 : optional + // we check that all non-empty items were processed correctly + FOR_VECTOR(i, Items) { - wchar_t c = s[i]; - if (c < 0x20) + const unsigned st = states[i]; + if (Items[i].IsEmptyType()) { - res += kCharOpenBracket; - wchar_t buf[32]; - ConvertUInt32ToString(c, buf); - res += buf; - res += kCharCloseBracket; + if (st) + return E_FAIL; } + else if (st == 3) + continue; + else if (st) + return E_FAIL; else - res += c; + return S_FALSE; // there is unused directory item } - return res; +#endif + return S_OK; } + static const char k_Msi_Chars[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz._"; - -// static const char *k_Msi_ID = ""; // "{msi}"; -static const wchar_t k_Msi_SpecChar = L'!'; - -static const unsigned k_Msi_NumBits = 6; -static const unsigned k_Msi_NumChars = 1 << k_Msi_NumBits; -static const unsigned k_Msi_CharMask = k_Msi_NumChars - 1; -static const unsigned k_Msi_StartUnicodeChar = 0x3800; -static const unsigned k_Msi_UnicodeRange = k_Msi_NumChars * (k_Msi_NumChars + 1); - - -static bool IsMsiName(const Byte *p) -{ - UInt32 c = Get16(p); - return - c >= k_Msi_StartUnicodeChar && - c <= k_Msi_StartUnicodeChar + k_Msi_UnicodeRange; -} +static const char k_Msi_SpecChar_Replace = '!'; static bool AreEqualNames(const Byte *rawName, const char *asciiName) { - for (unsigned i = 0; i < kNameSizeMax / 2; i++) + for (;;) { - wchar_t c = Get16(rawName + i * 2); - wchar_t c2 = (Byte)asciiName[i]; + const unsigned c = Get16(rawName); + rawName += 2; + const unsigned c2 = (Byte)*asciiName; + asciiName++; if (c != c2) return false; - if (c == 0) + if (c2 == 0) return true; } - return false; } -static bool CompoundMsiNameToFileName(const UString &name, UString &res) -{ - res.Empty(); - for (unsigned i = 0; i < name.Len(); i++) - { - wchar_t c = name[i]; - if (c < k_Msi_StartUnicodeChar || c > k_Msi_StartUnicodeChar + k_Msi_UnicodeRange) - return false; - /* - if (i == 0) - res += k_Msi_ID; - */ - c -= k_Msi_StartUnicodeChar; - - unsigned c0 = (unsigned)c & k_Msi_CharMask; - unsigned c1 = (unsigned)c >> k_Msi_NumBits; - - if (c1 <= k_Msi_NumChars) - { - res += (wchar_t)(Byte)k_Msi_Chars[c0]; - if (c1 == k_Msi_NumChars) - break; - res += (wchar_t)(Byte)k_Msi_Chars[c1]; - } - else - res += k_Msi_SpecChar; - } - return true; -} -static UString ConvertName(const Byte *p, bool &isMsi) +static void MsiName_To_FileName(const Byte *p, UString &res) { - isMsi = false; - UString s; - + res.Empty(); for (unsigned i = 0; i < kNameSizeMax; i += 2) { - wchar_t c = Get16(p + i); + unsigned c = Get16(p + i); if (c == 0) break; - s += c; - } - - UString msiName; - if (CompoundMsiNameToFileName(s, msiName)) - { - isMsi = true; - return msiName; + if (c <= k_Msi_SpecUnicodeChar) + { + if (c < k_Msi_StartUnicodeChar) + { + if (c < 0x20) + { + res.Add_Char('['); + res.Add_UInt32((UInt32)c); + c = ']'; + } + } + else + { +#if 0 // 1 : for debug + if (i == 0) res += "{msi}"; +#endif + c -= k_Msi_StartUnicodeChar; + const unsigned c1 = (unsigned)c >> k_Msi_NumBits; + if (c1 <= k_Msi_NumChars) + { + res.Add_Char(k_Msi_Chars[(unsigned)c & k_Msi_CharMask]); + if (c1 == k_Msi_NumChars) + continue; + c = (Byte)k_Msi_Chars[c1]; + } + else + c = k_Msi_SpecChar_Replace; + } + } + res += (wchar_t)c; } - return CompoundNameToFileName(s); } -static UString ConvertName(const Byte *p) -{ - bool isMsi; - return ConvertName(p, isMsi); -} UString CDatabase::GetItemPath(UInt32 index) const { UString s; - while (index != kNoDid) + UString name; + unsigned level = 0; + while (index != k_Ref_Parent_Root) { const CRef &ref = Refs[index]; const CItem &item = Items[ref.Did]; if (!s.IsEmpty()) s.InsertAtFront(WCHAR_PATH_SEPARATOR); - s.Insert(0, ConvertName(item.Name)); + // if (IsMsi) + MsiName_To_FileName(item.Name, name); + // else NonMsiName_To_FileName(item.Name, name); + NItemName::NormalizeSlashes_in_FileName_for_OsPath(name); + if (name.IsEmpty()) + name = "[]"; + s.Insert(0, name); index = ref.Parent; +#ifdef Z7_COMPOUND_SHOW_DELETED + if (item.IsLevel_Unused()) + { + s.Insert(0, L"[DELETED]" WSTRING_PATH_SEPARATOR); + break; + } +#endif + if (item.Level >= k_Long_path_level_limit && level) + { + s.Insert(0, L"[LONG_PATH]" WSTRING_PATH_SEPARATOR); + break; + } + level = 1; // level++; } return s; } -HRESULT CDatabase::Update_PhySize_WithItem(unsigned index) + +HRESULT CDatabase::Check_Item(unsigned index) { const CItem &item = Items[index]; - bool isLargeStream = (index == 0 || IsLargeStream(item.Size)); - if (!isLargeStream) + if (item.IsEmptyType() || item.IsStorage()) return S_OK; - unsigned bsLog = isLargeStream ? SectorSizeBits : MiniSectorSizeBits; - // streamSpec->Size = item.Size; - - UInt32 clusterSize = (UInt32)1 << bsLog; - UInt64 numClusters64 = (item.Size + clusterSize - 1) >> bsLog; - if (numClusters64 >= ((UInt32)1 << 31)) - return S_FALSE; - UInt32 sid = item.Sid; UInt64 size = item.Size; - - if (size != 0) + const bool isLargeStream = (index == 0 || IsLargeStream(size)); + if (!isLargeStream) { - for (;; size -= clusterSize) + const unsigned bsLog = k_MiniSectorSizeBits; + const UInt32 clusterSize = (UInt32)1 << bsLog; + const UInt64 numClusters = (size + clusterSize - 1) >> bsLog; + if (numClusters > MatSize) + return S_FALSE; + UInt32 sid = item.Sid; + if (size != 0) { - // if (isLargeStream) + for (;; size -= clusterSize) + { + if (sid >= MatSize) + return S_FALSE; + const unsigned subBits = SectorSizeBits - k_MiniSectorSizeBits; + const UInt32 fid = sid >> subBits; + if (fid >= NumSectors_in_MiniStream) + return false; + sid = Mat[sid]; + if (size <= clusterSize) + break; + } + } + if (sid != NFatID::kEndOfChain) + return S_FALSE; + } + else + { + const unsigned bsLog = SectorSizeBits; + const UInt32 clusterSize = (UInt32)1 << bsLog; + const UInt64 numClusters = (size + clusterSize - 1) >> bsLog; + if (numClusters > FatSize) + return S_FALSE; + UInt32 sid = item.Sid; + if (size != 0) + { + for (;; size -= clusterSize) { if (sid >= FatSize) return S_FALSE; - UpdatePhySize(((UInt64)sid + 2) << bsLog); + const UInt32 sidPrev = sid; sid = Fat[sid]; + if (size <= clusterSize) + { + const UInt64 phySize = (((UInt64)sidPrev + 1) << SectorSizeBits) + size; + if (PhySize_Unaligned < phySize) + PhySize_Unaligned = phySize; + break; + } } - if (size <= clusterSize) - break; } + if (sid != NFatID::kEndOfChain) + return S_FALSE; } - if (sid != NFatID::kEndOfChain) - return S_FALSE; return S_OK; } -// There is name "[!]MsiPatchSequence" in msp files -static const unsigned kMspSequence_Size = 18; -static const Byte kMspSequence[kMspSequence_Size] = - { 0x40, 0x48, 0x96, 0x45, 0x6C, 0x3E, 0xE4, 0x45, - 0xE6, 0x42, 0x16, 0x42, 0x37, 0x41, 0x27, 0x41, - 0x37, 0x41 }; HRESULT CDatabase::Open(IInStream *inStream) { - MainSubfile = -1; - Type = k_Type_Common; - const UInt32 kHeaderSize = 512; - Byte p[kHeaderSize]; - PhySize = kHeaderSize; - RINOK(ReadStream_FALSE(inStream, p, kHeaderSize)); - if (memcmp(p, kSignature, ARRAY_SIZE(kSignature)) != 0) + const unsigned kHeaderSize = 512; + UInt32 p32[kHeaderSize / 4]; + RINOK(ReadStream_FALSE(inStream, p32, kHeaderSize)) + const Byte *p = (const Byte *)(const void *)p32; + if (memcmp(p, kSignature, Z7_ARRAY_SIZE(kSignature))) + return S_FALSE; + /* + if (memcmp(p + 8, k_CLSID_AAF_V3, Z7_ARRAY_SIZE(k_CLSID_AAF_V3)) == 0 || + memcmp(p + 8, k_CLSID_AAF_V4, Z7_ARRAY_SIZE(k_CLSID_AAF_V4)) == 0) + */ + if (Get32(p32 + 4) == 0x342b0e06) // simplified AAF signature check + Type = k_Type_Aaf; + if (Get16(p + 0x18) != 0x3e) // minorVer + return S_FALSE; + const unsigned ver = Get16(p + 0x1a); // majorVer + if (ver < 3 || ver > 4) return S_FALSE; - if (Get16(p + 0x1A) > 4) // majorVer + if (Get16(p + 0x1c) != 0xfffe) // Little-endian return S_FALSE; - if (Get16(p + 0x1C) != 0xFFFE) // Little-endian + const unsigned sectorSizeBits = Get16(p + 0x1e); + if (sectorSizeBits != ver * 3) // (ver == 3 ? 9 : 12) return S_FALSE; - unsigned sectorSizeBits = Get16(p + 0x1E); - bool mode64bit = (sectorSizeBits >= 12); - unsigned miniSectorSizeBits = Get16(p + 0x20); SectorSizeBits = sectorSizeBits; - MiniSectorSizeBits = miniSectorSizeBits; + if (Get16(p + 0x20) != k_MiniSectorSizeBits) + return S_FALSE; + + IsArc = true; + HeadersError = true; - if (sectorSizeBits > 24 || - sectorSizeBits < 7 || - miniSectorSizeBits > 24 || - miniSectorSizeBits < 2 || - miniSectorSizeBits > sectorSizeBits) + const bool mode64bit = (sectorSizeBits >= 12); // (ver == 4) + if (Get16(p + 0x22) || p32[9]) // reserved return S_FALSE; - UInt32 numSectorsForFAT = Get32(p + 0x2C); // SAT - LongStreamMinSize = Get32(p + 0x38); - - UInt32 sectSize = (UInt32)1 << sectorSizeBits; - CByteBuffer sect(sectSize); + const UInt32 numDirSectors = Get32(p32 + 10); + // If (ver==3), the Number of Directory Sectors MUST be zero. + if (ver != 3 + (unsigned)(numDirSectors != 0)) + return S_FALSE; + if (numDirSectors > ((1u << (32 - 2)) >> (sectorSizeBits - (7 + 2)))) + return S_FALSE; + + const UInt32 numSectorsForFAT = Get32(p32 + 11); // SAT + + // MSDOC: A 512-byte sector compound file MUST be no greater than 2 GB in size for compatibility reasons. + // but actual restriction for windows compound creation code can be more strict: + // (numSectorsForFAT < (1 << 15)) : actual restriction in win10 for compound creation code + // (numSectorsForFAT <= (1 << 15)) : relaxed restriction to allow 2 GB files. + if (sectorSizeBits == 9 && + numSectorsForFAT >= (1u << (31 - (9 + 9 - 2)))) // we use most strict check + return S_FALSE; + + // const UInt32 TransactionSignatureNumber = Get32(p32 + 13); + if (Get32(p32 + 14) != k_LongStreamMinSize) + return S_FALSE; - unsigned ssb2 = sectorSizeBits - 2; - UInt32 numSidsInSec = (UInt32)1 << ssb2; - UInt32 numFatItems = numSectorsForFAT << ssb2; - if ((numFatItems >> ssb2) != numSectorsForFAT) + const unsigned ssb2 = sectorSizeBits - 2; + const UInt32 numSidsInSec = (UInt32)1 << ssb2; + const UInt32 numFatItems = numSectorsForFAT << ssb2; + if (numFatItems == 0 || (numFatItems >> ssb2) != numSectorsForFAT) return S_FALSE; - FatSize = numFatItems; + const size_t sectSize = (size_t)1 << sectorSizeBits; + CByteArr sect(sectSize); + CByteArr used(numFatItems); + // don't change these const values. These values use same order as (0xffffffff - NFatID::const) + // const Byte k_Used_Free = 0; + const Byte k_Used_ChainTo = 1; + const Byte k_Used_FAT = 2; + const Byte k_Used_DIFAT = 3; + memset(used, 0, numFatItems); + UInt32 *fat; + + UInt64 fileSize; + RINOK(InStream_GetSize_SeekToEnd(inStream, fileSize)) +#if 0 + UInt32 numFatItems_Small = numFatItems; { - UInt32 numSectorsForBat = Get32(p + 0x48); // master sector allocation table - const UInt32 kNumHeaderBatItems = 109; - UInt32 numBatItems = kNumHeaderBatItems + (numSectorsForBat << ssb2); - if (numBatItems < kNumHeaderBatItems || ((numBatItems - kNumHeaderBatItems) >> ssb2) != numSectorsForBat) + const UInt64 numFatItems_req = ((fileSize + (sectSize - 1)) >> sectorSizeBits) - 1; + if (numFatItems_Small > numFatItems_req) + numFatItems_Small = (UInt32)numFatItems_req; + } +#endif + + { + // ========== READ FAT ========== + const UInt32 numSectorsForBat = Get32(p32 + 18); // master sector allocation table + const unsigned ssb2_m1 = ssb2 - 1; + if (numSectorsForBat > ((1u << 30) >> ssb2_m1 >> ssb2_m1)) return S_FALSE; + const unsigned kNumHeaderBatItems = 109; + UInt32 numBatItems = kNumHeaderBatItems + (numSectorsForBat << ssb2); // real size can be smaller CObjArray bat(numBatItems); - UInt32 i; + size_t i; for (i = 0; i < kNumHeaderBatItems; i++) - bat[i] = Get32(p + 0x4c + i * 4); - UInt32 sid = Get32(p + 0x44); - for (UInt32 s = 0; s < numSectorsForBat; s++) + bat[i] = Get32(p32 + 19 + i); { - RINOK(ReadIDs(inStream, sect, sectorSizeBits, sid, bat + i)); - i += numSidsInSec - 1; - sid = bat[i]; + UInt32 sid = Get32(p32 + 17); + for (UInt32 s = 0; s < numSectorsForBat; s++) + { + if (sid >= numFatItems || used[sid]) + return S_FALSE; + used[sid] = k_Used_DIFAT; + RINOK(ReadIDs(inStream, sect, sid, bat + i)) + i += numSidsInSec - 1; + sid = bat[i]; + } + if (sid != NFatID::kEndOfChain // NFatID::kEndOfChain is expected value for most files + && sid != NFatID::kFree) // NFatID::kFree is used in some AAF files + return S_FALSE; } - numBatItems = i; - + numBatItems = (UInt32)i; // corrected value + if (numSectorsForFAT > numBatItems) + return S_FALSE; + for (i = numSectorsForFAT; i < numBatItems; i++) + if (bat[i] != NFatID::kFree) + return S_FALSE; + + // RINOK(IncreaseOpenTotal(numSectorsForFAT + numDirSectors)) + Fat.Alloc(numFatItems); - UInt32 j = 0; - - for (i = 0; i < numFatItems; j++, i += numSidsInSec) + fat = Fat; + for (i = 0; i < numSectorsForFAT; i++) { - if (j >= numBatItems) + const UInt32 sectorIndex = bat[i]; + if (sectorIndex >= numFatItems) + return S_FALSE; + if (used[sectorIndex]) return S_FALSE; - RINOK(ReadIDs(inStream, sect, sectorSizeBits, bat[j], Fat + i)); + used[sectorIndex] = k_Used_FAT; + UInt32 *fat2 = fat + ((size_t)i << ssb2); + RINOK(ReadIDs(inStream, sect, sectorIndex, fat2)) } - FatSize = numFatItems = i; + +#if 0 + { + for (size_t k = numFatItems_Small; k < numFatItems; k++) + { + if (fat[k] != NFatID::kFree) + { + /* it can mean some cases: + - rare case : junk data in FAT after last used item QuickSet (selinc) file + - truncated data + - data corruption + */ + // if there is junk data in FAT, we restore correct Free items + HeadersWarning = true; + for (; k < numFatItems; k++) + fat[k] = NFatID::kFree; + break; + } + } + } +#endif + { + for (size_t k = 0; k < numFatItems; k++) + { + const UInt32 sid = fat[k]; + if (sid > NFatID::kMaxValue) + { + if (sid == NFatID::k_DIF_SECT && used[k] != k_Used_DIFAT) + return S_FALSE; + continue; + } + if (sid >= numFatItems || used[sid]) + return S_FALSE; // strict error check + used[sid] = k_Used_ChainTo; + } + } + { + for (i = 0; i < numSectorsForFAT; i++) + if (fat[bat[i]] != NFatID::kFatSector) + return S_FALSE; + } + FatSize = numFatItems; + } + + { + size_t i = numFatItems; + do + if (fat[i - 1] != NFatID::kFree) + break; + while (--i); + PhySize = ((UInt64)i + 1) << sectorSizeBits; + /* + if (i) + { + const UInt32 *lim = fat + i; + UInt32 num = 0; + do + if (*fat++ == NFatID::kFree) + num++; + while (fat != lim); + FreeSize = num << sectorSizeBits; + } + */ } UInt32 numMatItems; { - UInt32 numSectorsForMat = Get32(p + 0x40); + // ========== READ MAT ========== + const UInt32 numSectorsForMat = Get32(p32 + 16); numMatItems = (UInt32)numSectorsForMat << ssb2; if ((numMatItems >> ssb2) != numSectorsForMat) return S_FALSE; Mat.Alloc(numMatItems); - UInt32 i; - UInt32 sid = Get32(p + 0x3C); // short-sector table SID - for (i = 0; i < numMatItems; i += numSidsInSec) + UInt32 sid = Get32(p32 + 15); // short-sector table SID + if (numMatItems) + { + if (sid >= numFatItems || used[sid]) + return S_FALSE; + used[sid] = k_Used_ChainTo; + } + for (UInt32 i = 0; i < numMatItems; i += numSidsInSec) { - RINOK(ReadIDs(inStream, sect, sectorSizeBits, sid, Mat + i)); if (sid >= numFatItems) return S_FALSE; - sid = Fat[sid]; + RINOK(ReadIDs(inStream, sect, sid, Mat + i)) + sid = fat[sid]; } if (sid != NFatID::kEndOfChain) return S_FALSE; } { - CByteBuffer used(numFatItems); - for (UInt32 i = 0; i < numFatItems; i++) - used[i] = 0; - UInt32 sid = Get32(p + 0x30); // directory stream SID - for (;;) + // ========== READ DIR ITEMS ========== + UInt32 sid = Get32(p32 + 12); // directory stream SID + UInt32 numDirSectors_Processed = 0; + if (sid >= numFatItems || used[sid]) + return S_FALSE; + used[sid] = k_Used_ChainTo; + do { + // we need to check sid here becase kEndOfChain sid < numFatItems is required if (sid >= numFatItems) return S_FALSE; - if (used[sid]) + if (numDirSectors && numDirSectors_Processed >= numDirSectors) return S_FALSE; - used[sid] = 1; - RINOK(ReadSector(inStream, sect, sectorSizeBits, sid)); - for (UInt32 i = 0; i < sectSize; i += 128) + numDirSectors_Processed++; + RINOK(ReadSector(inStream, sect, sid)) + for (size_t i = 0; i < sectSize; i += (1 << 7)) { CItem item; - item.Parse(sect + i, mode64bit); + item.Level = k_Item_Level_Unused; + if (!item.Parse(sect + i, mode64bit)) + return S_FALSE; + // we use (item.Size) check here. + // so we don't need additional overflow checks for (item.Size +) in another code + if ((UInt32)(item.Size >> 32) >= sectSize) // it's because FAT size is limited by (1 << 32) items. + return S_FALSE; + + if (Items.IsEmpty()) + { + if (item.Type != NItemType::kRootStorage + || item.LeftDid != kNoDid + || item.RightDid != kNoDid + || item.SonDid == 0) + return S_FALSE; + if (item.Sid != NFatID::kEndOfChain) + { + if (item.Sid >= numFatItems || used[item.Sid]) + return S_FALSE; + used[item.Sid] = k_Used_ChainTo; + } + } + else if (item.IsStorage()) + { + if (item.Size != 0) // by specification + return S_FALSE; + if (item.Sid != 0 // by specification + && item.Sid != NFatID::kFree // NFatID::kFree is used in some AAF files + && item.Sid != NFatID::kEndOfChain) // NFatID::kEndOfChain is used in QuickSet (selinc) file + return S_FALSE; + } + // else if (item.Type == NItemType::kRootStorage) return S_FALSE; + else if (item.IsEmptyType()) + { + // kNoDid is expected in *Did fileds, but rare case MSI contains zero in all fields + if ((item.Sid != 0 // expected value + && item.Sid != NFatID::kFree // NFatID::kFree is used in some AAF files + && item.Sid != NFatID::kEndOfChain) // used by some MSI file + || (item.LeftDid != kNoDid && item.LeftDid) + || (item.RightDid != kNoDid && item.RightDid) + || (item.SonDid != kNoDid && item.SonDid) + // || item.Size != 0 // the check is disabled because some MSI file contains non zero + // || Get16(item.Name) != 0 // the check is disabled because some MSI file contains some name + ) + return S_FALSE; + } + else + { + if (item.Type != NItemType::kStream) + return S_FALSE; + // NItemType::kStream case + if (item.SonDid != kNoDid) // optional check + return S_FALSE; + if (item.Size == 0) + { + if (item.Sid != NFatID::kEndOfChain) + return S_FALSE; + } + else if (IsLargeStream(item.Size)) + { + if (item.Sid >= numFatItems || used[item.Sid]) + return S_FALSE; + used[item.Sid] = k_Used_ChainTo; + } + } + Items.Add(item); } - sid = Fat[sid]; - if (sid == NFatID::kEndOfChain) - break; + sid = fat[sid]; } + while (sid != NFatID::kEndOfChain); } - const CItem &root = Items[0]; - { + // root stream contains all data that stored with mini Sectors + const CItem &root = Items[0]; UInt32 numSectorsInMiniStream; { - UInt64 numSatSects64 = (root.Size + sectSize - 1) >> sectorSizeBits; - if (numSatSects64 > NFatID::kMaxValue) + const UInt64 numSatSects64 = (root.Size + sectSize - 1) >> sectorSizeBits; + if (numSatSects64 > NFatID::kMaxValue + 1) return S_FALSE; numSectorsInMiniStream = (UInt32)numSatSects64; } - NumSectorsInMiniStream = numSectorsInMiniStream; - MiniSids.Alloc(numSectorsInMiniStream); { - UInt64 matSize64 = (root.Size + ((UInt64)1 << miniSectorSizeBits) - 1) >> miniSectorSizeBits; - if (matSize64 > NFatID::kMaxValue) + const UInt64 matSize64 = (root.Size + (1 << k_MiniSectorSizeBits) - 1) >> k_MiniSectorSizeBits; + if (matSize64 > numMatItems) return S_FALSE; MatSize = (UInt32)matSize64; - if (numMatItems < MatSize) - return S_FALSE; } - + MiniSids.Alloc(numSectorsInMiniStream); + UInt32 * const miniSids = MiniSids; UInt32 sid = root.Sid; for (UInt32 i = 0; ; i++) { @@ -561,96 +965,189 @@ HRESULT CDatabase::Open(IInStream *inStream) } if (i >= numSectorsInMiniStream) return S_FALSE; - MiniSids[i] = sid; if (sid >= numFatItems) return S_FALSE; - sid = Fat[sid]; + miniSids[i] = sid; + sid = fat[sid]; } + NumSectors_in_MiniStream = numSectorsInMiniStream; } - RINOK(AddNode(-1, root.SonDid)); - - unsigned numCabs = 0; - - FOR_VECTOR (i, Refs) + { - const CItem &item = Items[Refs[i].Did]; - if (item.IsDir() || numCabs > 1) - continue; - bool isMsiName; - const UString msiName = ConvertName(item.Name, isMsiName); - if (isMsiName && !msiName.IsEmpty()) +/* + MS DOCs: + The range lock sector covers file offsets 0x7FFFFF00-0x7FFFFFFF. + These offsets are reserved for byte-range locking to support + concurrency, transactions, and other compound file features. + The range lock sector MUST be allocated in the FAT and marked with + ENDOFCHAIN (0xFFFFFFFE), when the compound file grows beyond 2 GB. + If the compound file is greater than 2 GB and then shrinks to below 2 GB, + the range lock sector SHOULD be marked as FREESECT (0xFFFFFFFF) in the FAT. +*/ { - // bool isThereExt = (msiName.Find(L'.') >= 0); - bool isMsiSpec = (msiName[0] == k_Msi_SpecChar); - if (msiName.Len() >= 4 && StringsAreEqualNoCase_Ascii(msiName.RightPtr(4), ".cab") - || !isMsiSpec && msiName.Len() >= 3 && StringsAreEqualNoCase_Ascii(msiName.RightPtr(3), "exe") - // || !isMsiSpec && !isThereExt - ) - + const UInt32 lockSector = (0x7fffffff >> sectorSizeBits) - 1; + if (lockSector < numFatItems) { - numCabs++; - MainSubfile = i; + if (used[lockSector]) + return S_FALSE; + const UInt32 f = fat[lockSector]; + if (f == NFatID::kEndOfChain) + used[lockSector] = k_Used_ChainTo; // we use fake state to pass the check in loop below + else if (f != NFatID::kFree) + return S_FALSE; } } + for (size_t i = 0; i < numFatItems; i++) + { + UInt32 f = fat[i]; + const UInt32 u = ~(UInt32)used[i]; // (0xffffffff - used[i]) + if (f < NFatID::kMaxValue + 1) + f = NFatID::kEndOfChain; + if (f != u) + return S_FALSE; + } } - - if (numCabs > 1) - MainSubfile = -1; { - FOR_VECTOR (t, Items) + // Don't move that code up, becase Check_Item uses Mat[] array. + FOR_VECTOR(t, Items) { - Update_PhySize_WithItem(t); + RINOK(Check_Item(t)) } } + + RINOK(AddNodes()) + + { + // some msi (in rare cases) have unaligned size of archive, + // unaligned size of compound files is also possible if we create just one stream + // where there is no padding data after payload data in last cluster of archive + if ( fileSize < PhySize + && fileSize > PhySize - sectSize + && fileSize >= PhySize_Unaligned + && PhySize_Unaligned > PhySize - sectSize) + PhySize = PhySize_Unaligned; + } + + bool isMsi = false; { - FOR_VECTOR (t, Items) + FOR_VECTOR (i, Refs) { - const CItem &item = Items[t]; + const CItem &item = Items[Refs[i].Did]; + if (item.IsDir()) + continue; + if (item.AreMsiChars()) + // if (item.IsSpecMsiName()) + { + isMsi = true; + break; + } + } + } - if (IsMsiName(item.Name)) + // IsMsi = isMsi; + if (isMsi) + { + unsigned numCabs = 0; + UString name; + FOR_VECTOR (i, Refs) + { + const CItem &item = Items[Refs[i].Did]; + if (item.IsDir() /* || item.IsSpecMsiName() */) + continue; + MsiName_To_FileName(item.Name, name); + if ( (name.Len() >= 4 && StringsAreEqualNoCase_Ascii(name.RightPtr(4), ".cab")) + || (name.Len() >= 3 && StringsAreEqualNoCase_Ascii(name.RightPtr(3), "exe")) + ) { - Type = k_Type_Msi; - if (memcmp(item.Name, kMspSequence, kMspSequence_Size) == 0) + numCabs++; + if (numCabs > 1) { - Type = k_Type_Msp; + MainSubfile = -1; break; } - continue; - } - if (AreEqualNames(item.Name, "WordDocument")) - { - Type = k_Type_Doc; - break; + MainSubfile = (int)i; } - if (AreEqualNames(item.Name, "PowerPoint Document")) + } + } + + if (isMsi) // we provide msi priority over AAF + Type = k_Type_Msi; + if (Type != k_Type_Aaf) + { + FOR_VECTOR (i, Refs) + { + const CItem &item = Items[Refs[i].Did]; + if (item.IsDir()) + continue; + const Byte *name = item.Name; + // if (IsMsiName(name)) + if (isMsi) { - Type = k_Type_Ppt; - break; + if (memcmp(name, k_Sequence_msp, sizeof(k_Sequence_msp)) == 0) + { + Type = k_Type_Msp; + break; + } + if (memcmp(name, k_Sequence_msm, sizeof(k_Sequence_msm)) == 0) + { + Type = k_Type_Msm; + break; + } } - if (AreEqualNames(item.Name, "Workbook")) + else { - Type = k_Type_Xls; - break; + if (AreEqualNames(name, "WordDocument")) + { + Type = k_Type_Doc; + break; + } + if (AreEqualNames(name, "PowerPoint Document")) + { + Type = k_Type_Ppt; + break; + } + if (AreEqualNames(name, "Workbook")) + { + Type = k_Type_Xls; + break; + } } } } +#ifdef Z7_COMPOUND_SHOW_DELETED + { + // we skip Items[0] that is root item + for (unsigned t = 1; t < Items.Size(); t++) + { + const CItem &item = Items[t]; + if ( +#if 1 // 0 for debug to show empty files + item.IsEmptyType() || +#endif + !item.IsLevel_Unused()) + continue; + CRef ref; + ref.Parent = k_Ref_Parent_Root; + ref.Did = t; + Refs.Add(ref); + } + } +#endif + + HeadersError = false; return S_OK; } -class CHandler: - public IInArchive, - public IInArchiveGetStream, - public CMyUnknownImp -{ + + +Z7_CLASS_IMP_CHandler_IInArchive_1( + IInArchiveGetStream +) CMyComPtr _stream; CDatabase _db; -public: - MY_UNKNOWN_IMP2(IInArchive, IInArchiveGetStream) - INTERFACE_IInArchive(;) - STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream); }; static const Byte kProps[] = @@ -660,19 +1157,21 @@ static const Byte kProps[] = kpidPackSize, kpidCTime, kpidMTime + // , kpidCharacts // for debug }; static const Byte kArcProps[] = { kpidExtension, - kpidClusterSize, - kpidSectorSize + kpidClusterSize + // , kpidSectorSize + // , kpidFreeSpace }; IMP_IInArchive_Props IMP_IInArchive_ArcProps -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NWindows::NCOM::CPropVariant prop; @@ -681,16 +1180,38 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) case kpidExtension: prop = kExtensions[(unsigned)_db.Type]; break; case kpidPhySize: prop = _db.PhySize; break; case kpidClusterSize: prop = (UInt32)1 << _db.SectorSizeBits; break; - case kpidSectorSize: prop = (UInt32)1 << _db.MiniSectorSizeBits; break; + // case kpidSectorSize: prop = (UInt32)1 << _db.MiniSectorSizeBits; break; case kpidMainSubfile: if (_db.MainSubfile >= 0) prop = (UInt32)_db.MainSubfile; break; + // case kpidFreeSpace: prop = _db.FreeSize; break; case kpidIsNotArcType: if (_db.IsNotArcType()) prop = true; break; + case kpidErrorFlags: + { + UInt32 v = 0; + if (!_db.IsArc) + v |= kpv_ErrorFlags_IsNotArc; + if (_db.HeadersError) + v |= kpv_ErrorFlags_HeadersError; + prop = v; + break; + } +#if 0 + case kpidWarningFlags: + { + UInt32 v = 0; + if (_db.HeadersWarning) + v |= kpv_ErrorFlags_HeadersError; + if (v) + prop = v; + break; + } +#endif } prop.Detach(value); return S_OK; COM_TRY_END } -STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NWindows::NCOM::CPropVariant prop; @@ -705,41 +1226,42 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val case kpidMTime: prop = item.MTime; break; case kpidPackSize: if (!item.IsDir()) prop = _db.GetItemPackSize(item.Size); break; case kpidSize: if (!item.IsDir()) prop = item.Size; break; + // case kpidCharacts: prop = item.Level; break; } prop.Detach(value); return S_OK; COM_TRY_END } -STDMETHODIMP CHandler::Open(IInStream *inStream, +Z7_COM7F_IMF(CHandler::Open(IInStream *inStream, const UInt64 * /* maxCheckStartPosition */, - IArchiveOpenCallback * /* openArchiveCallback */) + IArchiveOpenCallback *openArchiveCallback)) { COM_TRY_BEGIN Close(); - try + _db.OpenCallback = openArchiveCallback; + // try { - if (_db.Open(inStream) != S_OK) - return S_FALSE; + RINOK(_db.Open(inStream)) _stream = inStream; } - catch(...) { return S_FALSE; } + // catch(...) { return S_FALSE; } return S_OK; COM_TRY_END } -STDMETHODIMP CHandler::Close() +Z7_COM7F_IMF(CHandler::Close()) { _db.Clear(); _stream.Release(); return S_OK; } -STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, - Int32 testMode, IArchiveExtractCallback *extractCallback) +Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testMode, IArchiveExtractCallback *extractCallback)) { COM_TRY_BEGIN - bool allFilesMode = (numItems == (UInt32)(Int32)-1); + const bool allFilesMode = (numItems == (UInt32)(Int32)-1); if (allFilesMode) numItems = _db.Refs.Size(); if (numItems == 0) @@ -752,95 +1274,142 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, if (!item.IsDir()) totalSize += item.Size; } - RINOK(extractCallback->SetTotal(totalSize)); + RINOK(extractCallback->SetTotal(totalSize)) UInt64 totalPackSize; totalSize = totalPackSize = 0; - NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder(); - CMyComPtr copyCoder = copyCoderSpec; - - CLocalProgress *lps = new CLocalProgress; - CMyComPtr progress = lps; + CMyComPtr2_Create copyCoder; + CMyComPtr2_Create lps; lps->Init(extractCallback, false); - for (i = 0; i < numItems; i++) + for (i = 0;; i++) { lps->InSize = totalPackSize; lps->OutSize = totalSize; - RINOK(lps->SetCur()); - Int32 index = allFilesMode ? i : indices[i]; - const CItem &item = _db.Items[_db.Refs[index].Did]; - - CMyComPtr outStream; - Int32 askMode = testMode ? - NExtract::NAskMode::kTest : - NExtract::NAskMode::kExtract; - RINOK(extractCallback->GetStream(index, &outStream, askMode)); + RINOK(lps->SetCur()) + if (i >= numItems) + break; - if (item.IsDir()) + const UInt32 index = allFilesMode ? i : indices[i]; + const CItem &item = _db.Items[_db.Refs[index].Did]; + Int32 res; { - RINOK(extractCallback->PrepareOperation(askMode)); - RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)); - continue; - } - - totalPackSize += _db.GetItemPackSize(item.Size); - totalSize += item.Size; - - if (!testMode && !outStream) - continue; - RINOK(extractCallback->PrepareOperation(askMode)); - Int32 res = NExtract::NOperationResult::kDataError; - CMyComPtr inStream; - HRESULT hres = GetStream(index, &inStream); - if (hres == S_FALSE) + CMyComPtr outStream; + const Int32 askMode = testMode ? + NExtract::NAskMode::kTest : + NExtract::NAskMode::kExtract; + RINOK(extractCallback->GetStream(index, &outStream, askMode)) + + if (item.IsDir()) + { + RINOK(extractCallback->PrepareOperation(askMode)) + RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)) + continue; + } + + totalPackSize += _db.GetItemPackSize(item.Size); + totalSize += item.Size; + + if (!testMode && !outStream) + continue; + RINOK(extractCallback->PrepareOperation(askMode)) res = NExtract::NOperationResult::kDataError; - else if (hres == E_NOTIMPL) - res = NExtract::NOperationResult::kUnsupportedMethod; - else - { - RINOK(hres); - if (inStream) + CMyComPtr inStream; + const HRESULT hres = GetStream(index, &inStream); + if (hres == S_FALSE) + res = NExtract::NOperationResult::kDataError; + /* + else if (hres == E_NOTIMPL) + res = NExtract::NOperationResult::kUnsupportedMethod; + */ + else { - RINOK(copyCoder->Code(inStream, outStream, NULL, NULL, progress)); - if (copyCoderSpec->TotalSize == item.Size) - res = NExtract::NOperationResult::kOK; + RINOK(hres) + if (inStream) + { + RINOK(copyCoder.Interface()->Code(inStream, outStream, NULL, NULL, lps)) + if (copyCoder->TotalSize == item.Size) + res = NExtract::NOperationResult::kOK; + } } } - outStream.Release(); - RINOK(extractCallback->SetOperationResult(res)); + RINOK(extractCallback->SetOperationResult(res)) } return S_OK; COM_TRY_END } -STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) { *numItems = _db.Refs.Size(); return S_OK; } -STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream) +Z7_COM7F_IMF(CHandler::GetStream(UInt32 index, ISequentialInStream **stream)) { COM_TRY_BEGIN - *stream = 0; - UInt32 itemIndex = _db.Refs[index].Did; + *stream = NULL; + const UInt32 itemIndex = _db.Refs[index].Did; const CItem &item = _db.Items[itemIndex]; + if (item.IsDir()) + return S_FALSE; + const bool isLargeStream = (itemIndex == 0 || IsLargeStream(item.Size)); + if (!isLargeStream) + { + CBufferInStream *streamSpec = new CBufferInStream; + CMyComPtr streamTemp = streamSpec; + + UInt32 size = (UInt32)item.Size; + streamSpec->Buf.Alloc(size); + streamSpec->Init(); + + UInt32 sid = item.Sid; + Byte *dest = streamSpec->Buf; + + UInt64 phyPos = 0; + while (size) + { + if (sid >= _db.MatSize) + return S_FALSE; + const unsigned subBits = _db.SectorSizeBits - k_MiniSectorSizeBits; + const UInt32 fid = sid >> subBits; + if (fid >= _db.NumSectors_in_MiniStream) + return false; + const UInt64 offset = (((UInt64)_db.MiniSids[fid] + 1) << _db.SectorSizeBits) + + ((sid & ((1u << subBits) - 1)) << k_MiniSectorSizeBits); + if (phyPos != offset) + { + RINOK(InStream_SeekSet(_stream, offset)) + phyPos = offset; + } + UInt32 readSize = (UInt32)1 << k_MiniSectorSizeBits; + if (readSize > size) + readSize = size; + RINOK(ReadStream_FALSE(_stream, dest, readSize)) + phyPos += readSize; + dest += readSize; + sid = _db.Mat[sid]; + size -= readSize; + } + if (sid != NFatID::kEndOfChain) + return S_FALSE; + *stream = streamTemp.Detach(); + return S_OK; + } + CClusterInStream *streamSpec = new CClusterInStream; CMyComPtr streamTemp = streamSpec; streamSpec->Stream = _stream; streamSpec->StartOffset = 0; - - bool isLargeStream = (itemIndex == 0 || _db.IsLargeStream(item.Size)); - int bsLog = isLargeStream ? _db.SectorSizeBits : _db.MiniSectorSizeBits; + const unsigned bsLog = _db.SectorSizeBits; streamSpec->BlockSizeLog = bsLog; streamSpec->Size = item.Size; - UInt32 clusterSize = (UInt32)1 << bsLog; - UInt64 numClusters64 = (item.Size + clusterSize - 1) >> bsLog; - if (numClusters64 >= ((UInt32)1 << 31)) - return E_NOTIMPL; + const UInt32 clusterSize = (UInt32)1 << bsLog; + const UInt64 numClusters64 = (item.Size + clusterSize - 1) >> bsLog; + if (numClusters64 > _db.FatSize) + return S_FALSE; streamSpec->Vector.ClearAndReserve((unsigned)numClusters64); UInt32 sid = item.Sid; UInt64 size = item.Size; @@ -849,35 +1418,24 @@ STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream) { for (;; size -= clusterSize) { - if (isLargeStream) - { - if (sid >= _db.FatSize) - return S_FALSE; - streamSpec->Vector.AddInReserved(sid + 1); - sid = _db.Fat[sid]; - } - else - { - UInt64 val = 0; - if (sid >= _db.MatSize || !_db.GetMiniCluster(sid, val) || val >= (UInt64)1 << 32) - return S_FALSE; - streamSpec->Vector.AddInReserved((UInt32)val); - sid = _db.Mat[sid]; - } + if (sid >= _db.FatSize) + return S_FALSE; + streamSpec->Vector.AddInReserved(sid + 1); + sid = _db.Fat[sid]; if (size <= clusterSize) break; } } if (sid != NFatID::kEndOfChain) return S_FALSE; - RINOK(streamSpec->InitAndSeek()); + RINOK(streamSpec->InitAndSeek()) *stream = streamTemp.Detach(); return S_OK; COM_TRY_END } REGISTER_ARC_I( - "Compound", "msi msp doc xls ppt", 0, 0xE5, + "Compound", "msi msp msm doc xls ppt aaf", NULL, 0xe5, kSignature, 0, 0, diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Common/CoderMixer2.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/Common/CoderMixer2.cpp index 41b5805c..b1c316d1 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Common/CoderMixer2.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Common/CoderMixer2.cpp @@ -6,7 +6,7 @@ #ifdef USE_MIXER_ST -STDMETHODIMP CSequentialInStreamCalcSize::Read(void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CSequentialInStreamCalcSize::Read(void *data, UInt32 size, UInt32 *processedSize)) { UInt32 realProcessed = 0; HRESULT result = S_OK; @@ -21,7 +21,7 @@ STDMETHODIMP CSequentialInStreamCalcSize::Read(void *data, UInt32 size, UInt32 * } -STDMETHODIMP COutStreamCalcSize::Write(const void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(COutStreamCalcSize::Write(const void *data, UInt32 size, UInt32 *processedSize)) { HRESULT result = S_OK; if (_stream) @@ -32,7 +32,7 @@ STDMETHODIMP COutStreamCalcSize::Write(const void *data, UInt32 size, UInt32 *pr return result; } -STDMETHODIMP COutStreamCalcSize::OutStreamFinish() +Z7_COM7F_IMF(COutStreamCalcSize::OutStreamFinish()) { HRESULT result = S_OK; if (_stream) @@ -60,6 +60,63 @@ static void BoolVector_Fill_False(CBoolVector &v, unsigned size) p[i] = false; } + +HRESULT CCoder::CheckDataAfterEnd(bool &dataAfterEnd_Error /* , bool &InternalPackSizeError */) const +{ + if (Coder) + { + if (PackSizePointers.IsEmpty() || !PackSizePointers[0]) + return S_OK; + CMyComPtr getInStreamProcessedSize; + Coder.QueryInterface(IID_ICompressGetInStreamProcessedSize, (void **)&getInStreamProcessedSize); + // if (!getInStreamProcessedSize) return E_FAIL; + if (getInStreamProcessedSize) + { + UInt64 processed; + RINOK(getInStreamProcessedSize->GetInStreamProcessedSize(&processed)) + if (processed != (UInt64)(Int64)-1) + { + const UInt64 size = PackSizes[0]; + if (processed < size && Finish) + dataAfterEnd_Error = true; + if (processed > size) + { + // InternalPackSizeError = true; + // return S_FALSE; + } + } + } + } + else if (Coder2) + { + CMyComPtr getInStreamProcessedSize2; + Coder2.QueryInterface(IID_ICompressGetInStreamProcessedSize2, (void **)&getInStreamProcessedSize2); + if (getInStreamProcessedSize2) + FOR_VECTOR (i, PackSizePointers) + { + if (!PackSizePointers[i]) + continue; + UInt64 processed; + RINOK(getInStreamProcessedSize2->GetInStreamProcessedSize2(i, &processed)) + if (processed != (UInt64)(Int64)-1) + { + const UInt64 size = PackSizes[i]; + if (processed < size && Finish) + dataAfterEnd_Error = true; + else if (processed > size) + { + // InternalPackSizeError = true; + // return S_FALSE; + } + } + } + } + + return S_OK; +} + + + class CBondsChecks { CBoolVector _coderUsed; @@ -80,7 +137,7 @@ bool CBondsChecks::CheckCoder(unsigned coderIndex) return false; _coderUsed[coderIndex] = true; - UInt32 start = BindInfo->Coder_to_Stream[coderIndex]; + const UInt32 start = BindInfo->Coder_to_Stream[coderIndex]; for (unsigned i = 0; i < coder.NumStreams; i++) { @@ -89,10 +146,10 @@ bool CBondsChecks::CheckCoder(unsigned coderIndex) if (BindInfo->IsStream_in_PackStreams(ind)) continue; - int bond = BindInfo->FindBond_for_PackStream(ind); + const int bond = BindInfo->FindBond_for_PackStream(ind); if (bond < 0) return false; - if (!CheckCoder(BindInfo->Bonds[bond].UnpackIndex)) + if (!CheckCoder(BindInfo->Bonds[(unsigned)bond].UnpackIndex)) return false; } @@ -151,8 +208,10 @@ bool CBindInfo::CalcMapsAndCheck() } -void CCoder::SetCoderInfo(const UInt64 *unpackSize, const UInt64 * const *packSizes) +void CCoder::SetCoderInfo(const UInt64 *unpackSize, const UInt64 * const *packSizes, bool finish) { + Finish = finish; + if (unpackSize) { UnpackSize = *unpackSize; @@ -187,15 +246,15 @@ bool CMixer::Is_UnpackSize_Correct_for_Coder(UInt32 coderIndex) if (coderIndex == _bi.UnpackCoder) return true; - int bond = _bi.FindBond_for_UnpackStream(coderIndex); + const int bond = _bi.FindBond_for_UnpackStream(coderIndex); if (bond < 0) throw 20150213; /* UInt32 coderIndex, coderStreamIndex; - _bi.GetCoder_for_Stream(_bi.Bonds[bond].PackIndex, coderIndex, coderStreamIndex); + _bi.GetCoder_for_Stream(_bi.Bonds[(unsigned)bond].PackIndex, coderIndex, coderStreamIndex); */ - UInt32 nextCoder = _bi.Stream_to_Coder[_bi.Bonds[bond].PackIndex]; + const UInt32 nextCoder = _bi.Stream_to_Coder[_bi.Bonds[(unsigned)bond].PackIndex]; if (!IsFilter_Vector[nextCoder]) return false; @@ -208,11 +267,11 @@ bool CMixer::Is_PackSize_Correct_for_Stream(UInt32 streamIndex) if (_bi.IsStream_in_PackStreams(streamIndex)) return true; - int bond = _bi.FindBond_for_PackStream(streamIndex); + const int bond = _bi.FindBond_for_PackStream(streamIndex); if (bond < 0) throw 20150213; - UInt32 nextCoder = _bi.Bonds[bond].UnpackIndex; + const UInt32 nextCoder = _bi.Bonds[(unsigned)bond].UnpackIndex; if (!IsFilter_Vector[nextCoder]) return false; @@ -222,8 +281,8 @@ bool CMixer::Is_PackSize_Correct_for_Stream(UInt32 streamIndex) bool CMixer::Is_PackSize_Correct_for_Coder(UInt32 coderIndex) { - UInt32 startIndex = _bi.Coder_to_Stream[coderIndex]; - UInt32 numStreams = _bi.Coders[coderIndex].NumStreams; + const UInt32 startIndex = _bi.Coder_to_Stream[coderIndex]; + const UInt32 numStreams = _bi.Coders[coderIndex].NumStreams; for (UInt32 i = 0; i < numStreams; i++) if (!Is_PackSize_Correct_for_Stream(startIndex + i)) return false; @@ -234,19 +293,19 @@ bool CMixer::IsThere_ExternalCoder_in_PackTree(UInt32 coderIndex) { if (IsExternal_Vector[coderIndex]) return true; - UInt32 startIndex = _bi.Coder_to_Stream[coderIndex]; - UInt32 numStreams = _bi.Coders[coderIndex].NumStreams; + const UInt32 startIndex = _bi.Coder_to_Stream[coderIndex]; + const UInt32 numStreams = _bi.Coders[coderIndex].NumStreams; for (UInt32 i = 0; i < numStreams; i++) { - UInt32 si = startIndex + i; + const UInt32 si = startIndex + i; if (_bi.IsStream_in_PackStreams(si)) continue; - int bond = _bi.FindBond_for_PackStream(si); + const int bond = _bi.FindBond_for_PackStream(si); if (bond < 0) throw 20150213; - if (IsThere_ExternalCoder_in_PackTree(_bi.Bonds[bond].UnpackIndex)) + if (IsThere_ExternalCoder_in_PackTree(_bi.Bonds[(unsigned)bond].UnpackIndex)) return true; } return false; @@ -284,13 +343,11 @@ void CMixerST::AddCoder(const CCreatedCoder &cod) { IUnknown *unk = (cod.Coder ? (IUnknown *)cod.Coder : (IUnknown *)cod.Coder2); { - CMyComPtr s; - unk->QueryInterface(IID_ISequentialInStream, (void**)&s); + Z7_DECL_CMyComPtr_QI_FROM(ISequentialInStream, s, unk) c2.CanRead = (s != NULL); } { - CMyComPtr s; - unk->QueryInterface(IID_ISequentialOutStream, (void**)&s); + Z7_DECL_CMyComPtr_QI_FROM(ISequentialOutStream, s, unk) c2.CanWrite = (s != NULL); } } @@ -301,7 +358,7 @@ CCoder &CMixerST::GetCoder(unsigned index) return _coders[index]; } -void CMixerST::ReInit() {} +HRESULT CMixerST::ReInit2() { return S_OK; } HRESULT CMixerST::GetInStream2( ISequentialInStream * const *inStreams, /* const UInt64 * const *inSizes, */ @@ -323,8 +380,8 @@ HRESULT CMixerST::GetInStream2( if (!seqInStream) return E_NOTIMPL; - UInt32 numInStreams = EncodeMode ? 1 : coder.NumStreams; - UInt32 startIndex = EncodeMode ? coderIndex : _bi.Coder_to_Stream[coderIndex]; + const UInt32 numInStreams = EncodeMode ? 1 : coder.NumStreams; + const UInt32 startIndex = EncodeMode ? coderIndex : _bi.Coder_to_Stream[coderIndex]; bool isSet = false; @@ -335,8 +392,8 @@ HRESULT CMixerST::GetInStream2( if (setStream) { CMyComPtr seqInStream2; - RINOK(GetInStream(inStreams, /* inSizes, */ startIndex + 0, &seqInStream2)); - RINOK(setStream->SetInStream(seqInStream2)); + RINOK(GetInStream(inStreams, /* inSizes, */ startIndex + 0, &seqInStream2)) + RINOK(setStream->SetInStream(seqInStream2)) isSet = true; } } @@ -351,8 +408,8 @@ HRESULT CMixerST::GetInStream2( for (UInt32 i = 0; i < numInStreams; i++) { CMyComPtr seqInStream2; - RINOK(GetInStream(inStreams, /* inSizes, */ startIndex + i, &seqInStream2)); - RINOK(setStream2->SetInStream2(i, seqInStream2)); + RINOK(GetInStream(inStreams, /* inSizes, */ startIndex + i, &seqInStream2)) + RINOK(setStream2->SetInStream2(i, seqInStream2)) } } @@ -385,18 +442,18 @@ HRESULT CMixerST::GetInStream( } } - int bond = FindBond_for_Stream( + const int bond = FindBond_for_Stream( true, // forInputStream inStreamIndex); if (bond < 0) return E_INVALIDARG; RINOK(GetInStream2(inStreams, /* inSizes, */ - _bi.Bonds[bond].Get_OutIndex(EncodeMode), &seqInStream)); + _bi.Bonds[(unsigned)bond].Get_OutIndex(EncodeMode), &seqInStream)) while (_binderStreams.Size() <= (unsigned)bond) _binderStreams.AddNew(); - CStBinderStream &bs = _binderStreams[bond]; + CStBinderStream &bs = _binderStreams[(unsigned)bond]; if (bs.StreamRef || bs.InStreamSpec) return E_NOTIMPL; @@ -439,13 +496,13 @@ HRESULT CMixerST::GetOutStream( } } - int bond = FindBond_for_Stream( + const int bond = FindBond_for_Stream( false, // forInputStream outStreamIndex); if (bond < 0) return E_INVALIDARG; - UInt32 inStreamIndex = _bi.Bonds[bond].Get_InIndex(EncodeMode); + const UInt32 inStreamIndex = _bi.Bonds[(unsigned)bond].Get_InIndex(EncodeMode); UInt32 coderIndex = inStreamIndex; UInt32 coderStreamIndex = 0; @@ -464,8 +521,8 @@ HRESULT CMixerST::GetOutStream( if (!seqOutStream) return E_NOTIMPL; - UInt32 numOutStreams = EncodeMode ? coder.NumStreams : 1; - UInt32 startIndex = EncodeMode ? _bi.Coder_to_Stream[coderIndex]: coderIndex; + const UInt32 numOutStreams = EncodeMode ? coder.NumStreams : 1; + const UInt32 startIndex = EncodeMode ? _bi.Coder_to_Stream[coderIndex]: coderIndex; bool isSet = false; @@ -476,8 +533,8 @@ HRESULT CMixerST::GetOutStream( if (setOutStream) { CMyComPtr seqOutStream2; - RINOK(GetOutStream(outStreams, /* outSizes, */ startIndex + 0, &seqOutStream2)); - RINOK(setOutStream->SetOutStream(seqOutStream2)); + RINOK(GetOutStream(outStreams, /* outSizes, */ startIndex + 0, &seqOutStream2)) + RINOK(setOutStream->SetOutStream(seqOutStream2)) isSet = true; } } @@ -493,15 +550,15 @@ HRESULT CMixerST::GetOutStream( for (UInt32 i = 0; i < numOutStreams; i++) { CMyComPtr seqOutStream2; - RINOK(GetOutStream(outStreams, startIndex + i, &seqOutStream2)); - RINOK(setStream2->SetOutStream2(i, seqOutStream2)); + RINOK(GetOutStream(outStreams, startIndex + i, &seqOutStream2)) + RINOK(setStream2->SetOutStream2(i, seqOutStream2)) } */ } while (_binderStreams.Size() <= (unsigned)bond) _binderStreams.AddNew(); - CStBinderStream &bs = _binderStreams[bond]; + CStBinderStream &bs = _binderStreams[(unsigned)bond]; if (bs.StreamRef || bs.OutStreamSpec) return E_NOTIMPL; @@ -551,13 +608,13 @@ HRESULT CMixerST::FinishStream(UInt32 streamIndex) return S_OK; } - int bond = FindBond_for_Stream( + const int bond = FindBond_for_Stream( false, // forInputStream streamIndex); if (bond < 0) return E_INVALIDARG; - UInt32 inStreamIndex = _bi.Bonds[bond].Get_InIndex(EncodeMode); + const UInt32 inStreamIndex = _bi.Bonds[(unsigned)bond].Get_InIndex(EncodeMode); UInt32 coderIndex = inStreamIndex; UInt32 coderStreamIndex = 0; @@ -580,8 +637,8 @@ HRESULT CMixerST::FinishCoder(UInt32 coderIndex) { CCoder &coder = _coders[coderIndex]; - UInt32 numOutStreams = EncodeMode ? coder.NumStreams : 1; - UInt32 startIndex = EncodeMode ? _bi.Coder_to_Stream[coderIndex]: coderIndex; + const UInt32 numOutStreams = EncodeMode ? coder.NumStreams : 1; + const UInt32 startIndex = EncodeMode ? _bi.Coder_to_Stream[coderIndex]: coderIndex; HRESULT res = S_OK; for (unsigned i = 0; i < numOutStreams; i++) @@ -595,7 +652,7 @@ void CMixerST::SelectMainCoder(bool useFirst) unsigned ci = _bi.UnpackCoder; int firstNonFilter = -1; - int firstAllowed = ci; + unsigned firstAllowed = ci; for (;;) { @@ -612,10 +669,10 @@ void CMixerST::SelectMainCoder(bool useFirst) if (coder.NumStreams != 1) break; - UInt32 st = _bi.Coder_to_Stream[ci]; + const UInt32 st = _bi.Coder_to_Stream[ci]; if (_bi.IsStream_in_PackStreams(st)) break; - int bond = _bi.FindBond_for_PackStream(st); + const int bond = _bi.FindBond_for_PackStream(st); if (bond < 0) throw 20150213; @@ -623,15 +680,15 @@ void CMixerST::SelectMainCoder(bool useFirst) break; if (firstNonFilter == -1 && !IsFilter_Vector[ci]) - firstNonFilter = ci; + firstNonFilter = (int)ci; - ci = _bi.Bonds[bond].UnpackIndex; + ci = _bi.Bonds[(unsigned)bond].UnpackIndex; } if (useFirst) ci = firstAllowed; else if (firstNonFilter >= 0) - ci = firstNonFilter; + ci = (unsigned)firstNonFilter; MainCoderIndex = ci; } @@ -640,35 +697,39 @@ void CMixerST::SelectMainCoder(bool useFirst) HRESULT CMixerST::Code( ISequentialInStream * const *inStreams, ISequentialOutStream * const *outStreams, - ICompressProgressInfo *progress) + ICompressProgressInfo *progress, + bool &dataAfterEnd_Error) { + // InternalPackSizeError = false; + dataAfterEnd_Error = false; + _binderStreams.Clear(); - unsigned ci = MainCoderIndex; + const unsigned ci = MainCoderIndex; const CCoder &mainCoder = _coders[MainCoderIndex]; CObjectVector< CMyComPtr > seqInStreams; CObjectVector< CMyComPtr > seqOutStreams; - UInt32 numInStreams = EncodeMode ? 1 : mainCoder.NumStreams; - UInt32 numOutStreams = !EncodeMode ? 1 : mainCoder.NumStreams; + const UInt32 numInStreams = EncodeMode ? 1 : mainCoder.NumStreams; + const UInt32 numOutStreams = !EncodeMode ? 1 : mainCoder.NumStreams; - UInt32 startInIndex = EncodeMode ? ci : _bi.Coder_to_Stream[ci]; - UInt32 startOutIndex = !EncodeMode ? ci : _bi.Coder_to_Stream[ci]; + const UInt32 startInIndex = EncodeMode ? ci : _bi.Coder_to_Stream[ci]; + const UInt32 startOutIndex = !EncodeMode ? ci : _bi.Coder_to_Stream[ci]; UInt32 i; for (i = 0; i < numInStreams; i++) { CMyComPtr seqInStream; - RINOK(GetInStream(inStreams, /* inSizes, */ startInIndex + i, &seqInStream)); + RINOK(GetInStream(inStreams, /* inSizes, */ startInIndex + i, &seqInStream)) seqInStreams.Add(seqInStream); } for (i = 0; i < numOutStreams; i++) { CMyComPtr seqOutStream; - RINOK(GetOutStream(outStreams, /* outSizes, */ startOutIndex + i, &seqOutStream)); + RINOK(GetOutStream(outStreams, /* outSizes, */ startOutIndex + i, &seqOutStream)) seqOutStreams.Add(seqOutStream); } @@ -692,20 +753,24 @@ HRESULT CMixerST::Code( CMyComPtr initEncoder; coder.QueryInterface(IID_ICompressInitEncoder, (void **)&initEncoder); if (initEncoder) - RINOK(initEncoder->InitEncoder()); + { + RINOK(initEncoder->InitEncoder()) + } } else { CMyComPtr setOutStreamSize; coder.QueryInterface(IID_ICompressSetOutStreamSize, (void **)&setOutStreamSize); if (setOutStreamSize) + { RINOK(setOutStreamSize->SetOutStreamSize( - EncodeMode ? coder.PackSizePointers[0] : coder.UnpackSizePointer)); + EncodeMode ? coder.PackSizePointers[0] : coder.UnpackSizePointer)) + } } } - const UInt64 * const *isSizes2 = EncodeMode ? &mainCoder.UnpackSizePointer : &mainCoder.PackSizePointers.Front(); - const UInt64 * const *outSizes2 = EncodeMode ? &mainCoder.PackSizePointers.Front() : &mainCoder.UnpackSizePointer; + const UInt64 * const *isSizes2 = EncodeMode ? &mainCoder.UnpackSizePointer : mainCoder.PackSizePointers.ConstData(); + const UInt64 * const *outSizes2 = EncodeMode ? mainCoder.PackSizePointers.ConstData() : &mainCoder.UnpackSizePointer; HRESULT res; if (mainCoder.Coder) @@ -718,8 +783,8 @@ HRESULT CMixerST::Code( else { res = mainCoder.Coder2->Code( - &seqInStreamsSpec.Front(), isSizes2, numInStreams, - &seqOutStreamsSpec.Front(), outSizes2, numOutStreams, + seqInStreamsSpec.ConstData(), isSizes2, numInStreams, + seqOutStreamsSpec.ConstData(), outSizes2, numOutStreams, progress); } @@ -742,7 +807,16 @@ HRESULT CMixerST::Code( if (res == k_My_HRESULT_WritingWasCut) res = S_OK; - return res; + + if (res != S_OK) + return res; + + for (i = 0; i < _coders.Size(); i++) + { + RINOK(_coders[i].CheckDataAfterEnd(dataAfterEnd_Error /*, InternalPackSizeError */)) + } + + return S_OK; } @@ -762,7 +836,7 @@ HRESULT CMixerST::GetMainUnpackStream( coder.QueryInterface(IID_ICompressSetOutStreamSize, (void **)&setOutStreamSize); if (setOutStreamSize) { - RINOK(setOutStreamSize->SetOutStreamSize(coder.UnpackSizePointer)); + RINOK(setOutStreamSize->SetOutStreamSize(coder.UnpackSizePointer)) } } @@ -835,8 +909,8 @@ void CCoderMT::Code(ICompressProgressInfo *progress) progress); else Result = Coder2->Code( - &InStreamPointers.Front(), EncodeMode ? &UnpackSizePointer : &PackSizePointers.Front(), numInStreams, - &OutStreamPointers.Front(), EncodeMode ? &PackSizePointers.Front(): &UnpackSizePointer, numOutStreams, + InStreamPointers.ConstData(), EncodeMode ? &UnpackSizePointer : PackSizePointers.ConstData(), numInStreams, + OutStreamPointers.ConstData(), EncodeMode ? PackSizePointers.ConstData(): &UnpackSizePointer, numOutStreams, progress); } @@ -847,7 +921,8 @@ HRESULT CMixerMT::SetBindInfo(const CBindInfo &bindInfo) _streamBinders.Clear(); FOR_VECTOR (i, _bi.Bonds) { - RINOK(_streamBinders.AddNew().CreateEvents()); + // RINOK(_streamBinders.AddNew().CreateEvents()) + _streamBinders.AddNew(); } return S_OK; } @@ -869,10 +944,13 @@ CCoder &CMixerMT::GetCoder(unsigned index) return _coders[index]; } -void CMixerMT::ReInit() +HRESULT CMixerMT::ReInit2() { FOR_VECTOR (i, _streamBinders) - _streamBinders[i].ReInit(); + { + RINOK(_streamBinders[i].Create_ReInit()) + } + return S_OK; } void CMixerMT::SelectMainCoder(bool useFirst) @@ -890,10 +968,10 @@ void CMixerMT::SelectMainCoder(bool useFirst) UInt32 st = _bi.Coder_to_Stream[ci]; if (_bi.IsStream_in_PackStreams(st)) break; - int bond = _bi.FindBond_for_PackStream(st); + const int bond = _bi.FindBond_for_PackStream(st); if (bond < 0) throw 20150213; - ci = _bi.Bonds[bond].UnpackIndex; + ci = _bi.Bonds[(unsigned)bond].UnpackIndex; } MainCoderIndex = ci; @@ -910,8 +988,8 @@ HRESULT CMixerMT::Init(ISequentialInStream * const *inStreams, ISequentialOutStr UInt32 j; - unsigned numInStreams = EncodeMode ? 1 : csi.NumStreams; - unsigned numOutStreams = EncodeMode ? csi.NumStreams : 1; + const unsigned numInStreams = EncodeMode ? 1 : csi.NumStreams; + const unsigned numOutStreams = EncodeMode ? csi.NumStreams : 1; coderInfo.InStreams.Clear(); for (j = 0; j < numInStreams; j++) @@ -940,9 +1018,9 @@ HRESULT CMixerMT::Init(ISequentialInStream * const *inStreams, ISequentialOutStr outCoderStreamIndex = EncodeMode ? coderStreamIndex : 0; } - _streamBinders[i].CreateStreams( - &_coders[inCoderIndex].InStreams[inCoderStreamIndex], - &_coders[outCoderIndex].OutStreams[outCoderStreamIndex]); + _streamBinders[i].CreateStreams2( + _coders[inCoderIndex].InStreams[inCoderStreamIndex], + _coders[outCoderIndex].OutStreams[outCoderStreamIndex]); CMyComPtr inSetSize, outSetSize; _coders[inCoderIndex].QueryInterface(IID_ICompressSetBufSize, (void **)&inSetSize); @@ -988,29 +1066,46 @@ HRESULT CMixerMT::ReturnIfError(HRESULT code) HRESULT CMixerMT::Code( ISequentialInStream * const *inStreams, ISequentialOutStream * const *outStreams, - ICompressProgressInfo *progress) + ICompressProgressInfo *progress, + bool &dataAfterEnd_Error) { + // InternalPackSizeError = false; + dataAfterEnd_Error = false; + Init(inStreams, outStreams); unsigned i; for (i = 0; i < _coders.Size(); i++) if (i != MainCoderIndex) { - RINOK(_coders[i].Create()); + const WRes wres = _coders[i].Create(); + if (wres != 0) + return HRESULT_FROM_WIN32(wres); } for (i = 0; i < _coders.Size(); i++) if (i != MainCoderIndex) - _coders[i].Start(); + { + const WRes wres = _coders[i].Start(); + if (wres != 0) + return HRESULT_FROM_WIN32(wres); + } _coders[MainCoderIndex].Code(progress); + WRes wres = 0; for (i = 0; i < _coders.Size(); i++) if (i != MainCoderIndex) - _coders[i].WaitExecuteFinish(); + { + WRes wres2 = _coders[i].WaitExecuteFinish(); + if (wres == 0) + wres = wres2; + } + if (wres != 0) + return HRESULT_FROM_WIN32(wres); - RINOK(ReturnIfError(E_ABORT)); - RINOK(ReturnIfError(E_OUTOFMEMORY)); + RINOK(ReturnIfError(E_ABORT)) + RINOK(ReturnIfError(E_OUTOFMEMORY)) for (i = 0; i < _coders.Size(); i++) { @@ -1022,7 +1117,7 @@ HRESULT CMixerMT::Code( return result; } - RINOK(ReturnIfError(S_FALSE)); + RINOK(ReturnIfError(S_FALSE)) for (i = 0; i < _coders.Size(); i++) { @@ -1031,6 +1126,11 @@ HRESULT CMixerMT::Code( return result; } + for (i = 0; i < _coders.Size(); i++) + { + RINOK(_coders[i].CheckDataAfterEnd(dataAfterEnd_Error /* , InternalPackSizeError */)) + } + return S_OK; } diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Common/CoderMixer2.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Common/CoderMixer2.h index e63f2ff0..484a6089 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Common/CoderMixer2.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Common/CoderMixer2.h @@ -1,7 +1,7 @@ // CoderMixer2.h -#ifndef __CODER_MIXER2_H -#define __CODER_MIXER2_H +#ifndef ZIP7_INC_CODER_MIXER2_H +#define ZIP7_INC_CODER_MIXER2_H #include "../../../Common/MyCom.h" #include "../../../Common/MyVector.h" @@ -10,11 +10,11 @@ #include "../../Common/CreateCoder.h" -#ifdef _7ZIP_ST +#ifdef Z7_ST #define USE_MIXER_ST #else #define USE_MIXER_MT - #ifndef _SFX + #ifndef Z7_SFX #define USE_MIXER_ST #endif #endif @@ -28,18 +28,13 @@ #ifdef USE_MIXER_ST -class CSequentialInStreamCalcSize: - public ISequentialInStream, - public CMyUnknownImp -{ -public: - MY_UNKNOWN_IMP1(ISequentialInStream) - - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); -private: +Z7_CLASS_IMP_COM_1( + CSequentialInStreamCalcSize + , ISequentialInStream +) + bool _wasFinished; CMyComPtr _stream; UInt64 _size; - bool _wasFinished; public: void SetStream(ISequentialInStream *stream) { _stream = stream; } void Init() @@ -53,19 +48,14 @@ class CSequentialInStreamCalcSize: }; -class COutStreamCalcSize: - public ISequentialOutStream, - public IOutStreamFinish, - public CMyUnknownImp -{ +Z7_CLASS_IMP_COM_2( + COutStreamCalcSize + , ISequentialOutStream + , IOutStreamFinish +) CMyComPtr _stream; UInt64 _size; public: - MY_UNKNOWN_IMP2(ISequentialOutStream, IOutStreamFinish) - - STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize); - STDMETHOD(OutStreamFinish)(); - void SetStream(ISequentialOutStream *stream) { _stream = stream; } void ReleaseStream() { _stream.Release(); } void Init() { _size = 0; } @@ -107,7 +97,7 @@ struct CBindInfo { FOR_VECTOR (i, Bonds) if (Bonds[i].PackIndex == packStream) - return i; + return (int)i; return -1; } @@ -115,14 +105,14 @@ struct CBindInfo { FOR_VECTOR (i, Bonds) if (Bonds[i].UnpackIndex == unpackStream) - return i; + return (int)i; return -1; } bool SetUnpackCoder() { bool isOk = false; - FOR_VECTOR(i, Coders) + FOR_VECTOR (i, Coders) { if (FindBond_for_UnpackStream(i) < 0) { @@ -142,9 +132,9 @@ struct CBindInfo int FindStream_in_PackStreams(UInt32 streamIndex) const { - FOR_VECTOR(i, PackStreams) + FOR_VECTOR (i, PackStreams) if (PackStreams[i] == streamIndex) - return i; + return (int)i; return -1; } @@ -189,11 +179,12 @@ struct CBindInfo class CCoder { - CLASS_NO_COPY(CCoder); + Z7_CLASS_NO_COPY(CCoder) public: CMyComPtr Coder; CMyComPtr Coder2; UInt32 NumStreams; + bool Finish; UInt64 UnpackSize; const UInt64 *UnpackSizePointer; @@ -201,9 +192,11 @@ class CCoder CRecordVector PackSizes; CRecordVector PackSizePointers; - CCoder() {} + CCoder(): Finish(false) {} + + void SetCoderInfo(const UInt64 *unpackSize, const UInt64 * const *packSizes, bool finish); - void SetCoderInfo(const UInt64 *unpackSize, const UInt64 * const *packSizes); + HRESULT CheckDataAfterEnd(bool &dataAfterEnd_Error /* , bool &InternalPackSizeError */) const; IUnknown *GetUnknown() const { @@ -239,11 +232,15 @@ class CMixer public: unsigned MainCoderIndex; + // bool InternalPackSizeError; + CMixer(bool encodeMode): EncodeMode(encodeMode), MainCoderIndex(0) + // , InternalPackSizeError(false) {} + virtual ~CMixer() {} /* Sequence of calling: @@ -272,12 +269,13 @@ class CMixer virtual void AddCoder(const CCreatedCoder &cod) = 0; virtual CCoder &GetCoder(unsigned index) = 0; virtual void SelectMainCoder(bool useFirst) = 0; - virtual void ReInit() = 0; - virtual void SetCoderInfo(unsigned coderIndex, const UInt64 *unpackSize, const UInt64 * const *packSizes) = 0; + virtual HRESULT ReInit2() = 0; + virtual void SetCoderInfo(unsigned coderIndex, const UInt64 *unpackSize, const UInt64 * const *packSizes, bool finish) = 0; virtual HRESULT Code( ISequentialInStream * const *inStreams, ISequentialOutStream * const *outStreams, - ICompressProgressInfo *progress) = 0; + ICompressProgressInfo *progress, + bool &dataAfterEnd_Error) = 0; virtual UInt64 GetBondStreamSize(unsigned bondIndex) const = 0; bool Is_UnpackSize_Correct_for_Coder(UInt32 coderIndex); @@ -314,6 +312,9 @@ class CMixerST: public CMixer, public CMyUnknownImp { + Z7_COM_UNKNOWN_IMP_0 + Z7_CLASS_NO_COPY(CMixerST) + HRESULT GetInStream2(ISequentialInStream * const *inStreams, /* const UInt64 * const *inSizes, */ UInt32 outStreamIndex, ISequentialInStream **inStreamRes); HRESULT GetInStream(ISequentialInStream * const *inStreams, /* const UInt64 * const *inSizes, */ @@ -329,22 +330,21 @@ class CMixerST: CObjectVector _binderStreams; - MY_UNKNOWN_IMP - CMixerST(bool encodeMode); - ~CMixerST(); - - virtual void AddCoder(const CCreatedCoder &cod); - virtual CCoder &GetCoder(unsigned index); - virtual void SelectMainCoder(bool useFirst); - virtual void ReInit(); - virtual void SetCoderInfo(unsigned coderIndex, const UInt64 *unpackSize, const UInt64 * const *packSizes) - { _coders[coderIndex].SetCoderInfo(unpackSize, packSizes); } + ~CMixerST() Z7_DESTRUCTOR_override; + + virtual void AddCoder(const CCreatedCoder &cod) Z7_override; + virtual CCoder &GetCoder(unsigned index) Z7_override; + virtual void SelectMainCoder(bool useFirst) Z7_override; + virtual HRESULT ReInit2() Z7_override; + virtual void SetCoderInfo(unsigned coderIndex, const UInt64 *unpackSize, const UInt64 * const *packSizes, bool finish) Z7_override + { _coders[coderIndex].SetCoderInfo(unpackSize, packSizes, finish); } virtual HRESULT Code( ISequentialInStream * const *inStreams, ISequentialOutStream * const *outStreams, - ICompressProgressInfo *progress); - virtual UInt64 GetBondStreamSize(unsigned bondIndex) const; + ICompressProgressInfo *progress, + bool &dataAfterEnd_Error) Z7_override; + virtual UInt64 GetBondStreamSize(unsigned bondIndex) const Z7_override; HRESULT GetMainUnpackStream( ISequentialInStream * const *inStreams, @@ -360,12 +360,12 @@ class CMixerST: class CCoderMT: public CCoder, public CVirtThread { - CLASS_NO_COPY(CCoderMT) + Z7_CLASS_NO_COPY(CCoderMT) CRecordVector InStreamPointers; CRecordVector OutStreamPointers; private: - void Execute(); + virtual void Execute() Z7_override; public: bool EncodeMode; HRESULT Result; @@ -385,7 +385,7 @@ class CCoderMT: public CCoder, public CVirtThread class CReleaser { - CLASS_NO_COPY(CReleaser) + Z7_CLASS_NO_COPY(CReleaser) CCoderMT &_c; public: CReleaser(CCoderMT &c): _c(c) {} @@ -393,7 +393,14 @@ class CCoderMT: public CCoder, public CVirtThread }; CCoderMT(): EncodeMode(false) {} - ~CCoderMT() { CVirtThread::WaitThreadFinish(); } + ~CCoderMT() Z7_DESTRUCTOR_override + { + /* WaitThreadFinish() will be called in ~CVirtThread(). + But we need WaitThreadFinish() call before CCoder destructor, + and before destructors of this class members. + */ + CVirtThread::WaitThreadFinish(); + } void Code(ICompressProgressInfo *progress); }; @@ -404,28 +411,31 @@ class CMixerMT: public CMixer, public CMyUnknownImp { + Z7_COM_UNKNOWN_IMP_0 + Z7_CLASS_NO_COPY(CMixerMT) + CObjectVector _streamBinders; HRESULT Init(ISequentialInStream * const *inStreams, ISequentialOutStream * const *outStreams); HRESULT ReturnIfError(HRESULT code); + // virtual ~CMixerMT() {} public: CObjectVector _coders; - MY_UNKNOWN_IMP - - virtual HRESULT SetBindInfo(const CBindInfo &bindInfo); - virtual void AddCoder(const CCreatedCoder &cod); - virtual CCoder &GetCoder(unsigned index); - virtual void SelectMainCoder(bool useFirst); - virtual void ReInit(); - virtual void SetCoderInfo(unsigned coderIndex, const UInt64 *unpackSize, const UInt64 * const *packSizes) - { _coders[coderIndex].SetCoderInfo(unpackSize, packSizes); } + virtual HRESULT SetBindInfo(const CBindInfo &bindInfo) Z7_override; + virtual void AddCoder(const CCreatedCoder &cod) Z7_override; + virtual CCoder &GetCoder(unsigned index) Z7_override; + virtual void SelectMainCoder(bool useFirst) Z7_override; + virtual HRESULT ReInit2() Z7_override; + virtual void SetCoderInfo(unsigned coderIndex, const UInt64 *unpackSize, const UInt64 * const *packSizes, bool finish) Z7_override + { _coders[coderIndex].SetCoderInfo(unpackSize, packSizes, finish); } virtual HRESULT Code( ISequentialInStream * const *inStreams, ISequentialOutStream * const *outStreams, - ICompressProgressInfo *progress); - virtual UInt64 GetBondStreamSize(unsigned bondIndex) const; + ICompressProgressInfo *progress, + bool &dataAfterEnd_Error) Z7_override; + virtual UInt64 GetBondStreamSize(unsigned bondIndex) const Z7_override; CMixerMT(bool encodeMode): CMixer(encodeMode) {} }; diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Common/DummyOutStream.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/Common/DummyOutStream.cpp index 7c4f5487..f48c32f2 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Common/DummyOutStream.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Common/DummyOutStream.cpp @@ -4,7 +4,7 @@ #include "DummyOutStream.h" -STDMETHODIMP CDummyOutStream::Write(const void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CDummyOutStream::Write(const void *data, UInt32 size, UInt32 *processedSize)) { UInt32 realProcessedSize = size; HRESULT res = S_OK; diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Common/DummyOutStream.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Common/DummyOutStream.h index b5a51fc0..f884e13c 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Common/DummyOutStream.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Common/DummyOutStream.h @@ -1,24 +1,22 @@ // DummyOutStream.h -#ifndef __DUMMY_OUT_STREAM_H -#define __DUMMY_OUT_STREAM_H +#ifndef ZIP7_INC_DUMMY_OUT_STREAM_H +#define ZIP7_INC_DUMMY_OUT_STREAM_H #include "../../../Common/MyCom.h" #include "../../IStream.h" -class CDummyOutStream: - public ISequentialOutStream, - public CMyUnknownImp -{ +Z7_CLASS_IMP_NOQIB_1( + CDummyOutStream + , ISequentialOutStream +) CMyComPtr _stream; UInt64 _size; public: void SetStream(ISequentialOutStream *outStream) { _stream = outStream; } void ReleaseStream() { _stream.Release(); } void Init() { _size = 0; } - MY_UNKNOWN_IMP - STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize); UInt64 GetSize() const { return _size; } }; diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Common/FindSignature.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/Common/FindSignature.cpp index fc952fa8..4f46ee7b 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Common/FindSignature.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Common/FindSignature.cpp @@ -16,36 +16,36 @@ HRESULT FindSignatureInStream(ISequentialInStream *stream, { resPos = 0; CByteBuffer byteBuffer2(signatureSize); - RINOK(ReadStream_FALSE(stream, byteBuffer2, signatureSize)); + RINOK(ReadStream_FALSE(stream, byteBuffer2, signatureSize)) if (memcmp(byteBuffer2, signature, signatureSize) == 0) return S_OK; - const UInt32 kBufferSize = (1 << 16); + const size_t kBufferSize = 1 << 16; CByteBuffer byteBuffer(kBufferSize); Byte *buffer = byteBuffer; - UInt32 numPrevBytes = signatureSize - 1; + size_t numPrevBytes = signatureSize - 1; memcpy(buffer, (const Byte *)byteBuffer2 + 1, numPrevBytes); resPos = 1; for (;;) { - if (limit != NULL) + if (limit) if (resPos > *limit) return S_FALSE; do { - UInt32 numReadBytes = kBufferSize - numPrevBytes; + const size_t numReadBytes = kBufferSize - numPrevBytes; UInt32 processedSize; - RINOK(stream->Read(buffer + numPrevBytes, numReadBytes, &processedSize)); - numPrevBytes += processedSize; + RINOK(stream->Read(buffer + numPrevBytes, (UInt32)numReadBytes, &processedSize)) + numPrevBytes += (size_t)processedSize; if (processedSize == 0) return S_FALSE; } while (numPrevBytes < signatureSize); - UInt32 numTests = numPrevBytes - signatureSize + 1; - for (UInt32 pos = 0; pos < numTests; pos++) + const size_t numTests = numPrevBytes - signatureSize + 1; + for (size_t pos = 0; pos < numTests; pos++) { - Byte b = signature[0]; + const Byte b = signature[0]; for (; buffer[pos] != b && pos < numTests; pos++); if (pos == numTests) break; @@ -60,3 +60,31 @@ HRESULT FindSignatureInStream(ISequentialInStream *stream, memmove(buffer, buffer + numTests, numPrevBytes); } } + +namespace NArchive { +HRESULT ReadZeroTail(ISequentialInStream *stream, bool &areThereNonZeros, UInt64 &numZeros, UInt64 maxSize); +HRESULT ReadZeroTail(ISequentialInStream *stream, bool &areThereNonZeros, UInt64 &numZeros, UInt64 maxSize) +{ + areThereNonZeros = false; + numZeros = 0; + const size_t kBufSize = 1 << 11; + Byte buf[kBufSize]; + for (;;) + { + UInt32 size = 0; + RINOK(stream->Read(buf, kBufSize, &size)) + if (size == 0) + return S_OK; + for (UInt32 i = 0; i < size; i++) + if (buf[i] != 0) + { + areThereNonZeros = true; + numZeros += i; + return S_OK; + } + numZeros += size; + if (numZeros > maxSize) + return S_OK; + } +} +} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Common/FindSignature.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Common/FindSignature.h index c359b9ed..f0e6cddf 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Common/FindSignature.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Common/FindSignature.h @@ -1,7 +1,7 @@ // FindSignature.h -#ifndef __FIND_SIGNATURE_H -#define __FIND_SIGNATURE_H +#ifndef ZIP7_INC_FIND_SIGNATURE_H +#define ZIP7_INC_FIND_SIGNATURE_H #include "../../IStream.h" diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Common/HandlerOut.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/Common/HandlerOut.cpp index 30ca73bd..5a11e305 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Common/HandlerOut.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Common/HandlerOut.cpp @@ -2,54 +2,179 @@ #include "StdAfx.h" -#ifndef _7ZIP_ST -#include "../../../Windows/System.h" -#endif - -#include "../Common/ParseProperties.h" +#include "../../../Common/StringToInt.h" #include "HandlerOut.h" -using namespace NWindows; - namespace NArchive { -static void SetMethodProp32(COneMethodInfo &m, PROPID propID, UInt32 value) +bool ParseSizeString(const wchar_t *s, const PROPVARIANT &prop, UInt64 percentsBase, UInt64 &res) +{ + if (*s == 0) + { + switch (prop.vt) + { + case VT_UI4: res = prop.ulVal; return true; + case VT_UI8: res = prop.uhVal.QuadPart; return true; + case VT_BSTR: + s = prop.bstrVal; + break; + default: return false; + } + } + else if (prop.vt != VT_EMPTY) + return false; + + bool percentMode = false; + { + const wchar_t c = *s; + if (MyCharLower_Ascii(c) == 'p') + { + percentMode = true; + s++; + } + } + + const wchar_t *end; + const UInt64 v = ConvertStringToUInt64(s, &end); + if (s == end) + return false; + const wchar_t c = *end; + + if (percentMode) + { + if (c != 0) + return false; + res = Calc_From_Val_Percents(percentsBase, v); + return true; + } + + if (c == 0) + { + res = v; + return true; + } + if (end[1] != 0) + return false; + + if (c == '%') + { + res = Calc_From_Val_Percents(percentsBase, v); + return true; + } + + unsigned numBits; + switch (MyCharLower_Ascii(c)) + { + case 'b': numBits = 0; break; + case 'k': numBits = 10; break; + case 'm': numBits = 20; break; + case 'g': numBits = 30; break; + case 't': numBits = 40; break; + default: return false; + } + const UInt64 val2 = v << numBits; + if ((val2 >> numBits) != v) + return false; + res = val2; + return true; +} + + +bool CCommonMethodProps::SetCommonProperty(const UString &name, const PROPVARIANT &value, HRESULT &hres) +{ + hres = S_OK; + + if (name.IsPrefixedBy_Ascii_NoCase("mt")) + { + #ifndef Z7_ST + _numThreads = _numProcessors; + _numThreads_WasForced = false; + hres = ParseMtProp2(name.Ptr(2), value, _numThreads, _numThreads_WasForced); + // "mt" means "_numThreads_WasForced = false" here + #endif + return true; + } + + if (name.IsPrefixedBy_Ascii_NoCase("memuse")) + { + UInt64 v; + if (!ParseSizeString(name.Ptr(6), value, _memAvail, v)) + hres = E_INVALIDARG; + _memUsage_Decompress = v; + _memUsage_Compress = v; + _memUsage_WasSet = true; + return true; + } + + return false; +} + + +#ifndef Z7_EXTRACT_ONLY + +static void SetMethodProp32(CMethodProps &m, PROPID propID, UInt32 value) { if (m.FindProp(propID) < 0) m.AddProp32(propID, value); } -void CMultiMethodProps::SetGlobalLevelAndThreads(COneMethodInfo &oneMethodInfo - #ifndef _7ZIP_ST - , UInt32 numThreads - #endif - ) +void CMultiMethodProps::SetGlobalLevelTo(COneMethodInfo &oneMethodInfo) const { UInt32 level = _level; if (level != (UInt32)(Int32)-1) SetMethodProp32(oneMethodInfo, NCoderPropID::kLevel, (UInt32)level); - - #ifndef _7ZIP_ST +} + +#ifndef Z7_ST + +static void SetMethodProp32_Replace(CMethodProps &m, PROPID propID, UInt32 value) +{ + const int i = m.FindProp(propID); + if (i >= 0) + { + NWindows::NCOM::CPropVariant &val = m.Props[(unsigned)i].Value; + val = (UInt32)value; + return; + } + m.AddProp32(propID, value); +} + +void CMultiMethodProps::SetMethodThreadsTo_IfNotFinded(CMethodProps &oneMethodInfo, UInt32 numThreads) +{ SetMethodProp32(oneMethodInfo, NCoderPropID::kNumThreads, numThreads); - #endif } -void CMultiMethodProps::Init() +void CMultiMethodProps::SetMethodThreadsTo_Replace(CMethodProps &oneMethodInfo, UInt32 numThreads) +{ + SetMethodProp32_Replace(oneMethodInfo, NCoderPropID::kNumThreads, numThreads); +} + +void CMultiMethodProps::Set_Method_NumThreadGroups_IfNotFinded(CMethodProps &oneMethodInfo, UInt32 numThreadGroups) +{ + SetMethodProp32(oneMethodInfo, NCoderPropID::kNumThreadGroups, numThreadGroups); +} + +#endif // Z7_ST + + +void CMultiMethodProps::InitMulti() { - #ifndef _7ZIP_ST - _numProcessors = _numThreads = NSystem::GetNumberOfProcessors(); - #endif - _level = (UInt32)(Int32)-1; _analysisLevel = -1; - - _autoFilter = true; _crcSize = 4; - _filterMethod.Clear(); + _autoFilter = true; +} + +void CMultiMethodProps::Init() +{ + InitCommon(); + InitMulti(); _methods.Clear(); + _filterMethod.Clear(); } + HRESULT CMultiMethodProps::SetProperty(const wchar_t *nameSpec, const PROPVARIANT &value) { UString name = nameSpec; @@ -68,34 +193,32 @@ HRESULT CMultiMethodProps::SetProperty(const wchar_t *nameSpec, const PROPVARIAN { name.Delete(0, 2); UInt32 v = 9; - RINOK(ParsePropToUInt32(name, value, v)); + RINOK(ParsePropToUInt32(name, value, v)) _analysisLevel = (int)v; return S_OK; } - if (name.IsEqualTo("crc")) + if (name.IsPrefixedBy_Ascii_NoCase("crc")) { name.Delete(0, 3); _crcSize = 4; return ParsePropToUInt32(name, value, _crcSize); } + + { + HRESULT hres; + if (SetCommonProperty(name, value, hres)) + return hres; + } UInt32 number; - unsigned index = ParseStringToUInt32(name, number); - UString realName = name.Ptr(index); + const unsigned index = ParseStringToUInt32(name, number); + const UString realName = name.Ptr(index); if (index == 0) { - if (name.IsPrefixedBy_Ascii_NoCase("mt")) - { - #ifndef _7ZIP_ST - RINOK(ParseMtProp(name.Ptr(2), value, _numProcessors, _numThreads)); - #endif - - return S_OK; - } if (name.IsEqualTo("f")) { - HRESULT res = PROPVARIANT_to_bool(value, _autoFilter); + const HRESULT res = PROPVARIANT_to_bool(value, _autoFilter); if (res == S_OK) return res; if (value.vt != VT_BSTR) @@ -105,52 +228,87 @@ HRESULT CMultiMethodProps::SetProperty(const wchar_t *nameSpec, const PROPVARIAN number = 0; } if (number > 64) - return E_FAIL; - for (int j = _methods.Size(); j <= (int)number; j++) - _methods.Add(COneMethodInfo()); + return E_INVALIDARG; + for (unsigned j = _methods.Size(); j <= number; j++) + _methods.AddNew(); return _methods[number].ParseMethodFromPROPVARIANT(realName, value); } + + void CSingleMethodProps::Init() { + InitCommon(); + InitSingle(); Clear(); - _level = (UInt32)(Int32)-1; - - #ifndef _7ZIP_ST - _numProcessors = _numThreads = NWindows::NSystem::GetNumberOfProcessors(); - AddProp_NumThreads(_numThreads); - #endif } + +HRESULT CSingleMethodProps::SetProperty(const wchar_t *name2, const PROPVARIANT &value) +{ + // processed = false; + UString name = name2; + name.MakeLower_Ascii(); + if (name.IsEmpty()) + return E_INVALIDARG; + if (name.IsPrefixedBy_Ascii_NoCase("x")) + { + UInt32 a = 9; + RINOK(ParsePropToUInt32(name.Ptr(1), value, a)) + _level = a; + AddProp_Level(a); + // processed = true; + return S_OK; + } + { + HRESULT hres; + if (SetCommonProperty(name, value, hres)) + { + // processed = true; + return S_OK; + } + } + RINOK(ParseMethodFromPROPVARIANT(name, value)) + return S_OK; +} + + HRESULT CSingleMethodProps::SetProperties(const wchar_t * const *names, const PROPVARIANT *values, UInt32 numProps) { Init(); + for (UInt32 i = 0; i < numProps; i++) { - UString name = names[i]; - name.MakeLower_Ascii(); - if (name.IsEmpty()) - return E_INVALIDARG; - const PROPVARIANT &value = values[i]; - if (name[0] == L'x') - { - UInt32 a = 9; - RINOK(ParsePropToUInt32(name.Ptr(1), value, a)); - _level = a; - AddProp_Level(a); - } - else if (name.IsPrefixedBy_Ascii_NoCase("mt")) - { - #ifndef _7ZIP_ST - RINOK(ParseMtProp(name.Ptr(2), value, _numProcessors, _numThreads)); - AddProp_NumThreads(_numThreads); - #endif - } - else - { - RINOK(ParseMethodFromPROPVARIANT(names[i], value)); - } + RINOK(SetProperty(names[i], values[i])) + } + + return S_OK; +} + +#endif + + +static HRESULT PROPVARIANT_to_BoolPair(const PROPVARIANT &prop, CBoolPair &dest) +{ + RINOK(PROPVARIANT_to_bool(prop, dest.Val)) + dest.Def = true; + return S_OK; +} + +HRESULT CHandlerTimeOptions::Parse(const UString &name, const PROPVARIANT &prop, bool &processed) +{ + processed = true; + if (name.IsEqualTo_Ascii_NoCase("tm")) { return PROPVARIANT_to_BoolPair(prop, Write_MTime); } + if (name.IsEqualTo_Ascii_NoCase("ta")) { return PROPVARIANT_to_BoolPair(prop, Write_ATime); } + if (name.IsEqualTo_Ascii_NoCase("tc")) { return PROPVARIANT_to_BoolPair(prop, Write_CTime); } + if (name.IsPrefixedBy_Ascii_NoCase("tp")) + { + UInt32 v = 0; + RINOK(ParsePropToUInt32(name.Ptr(2), prop, v)) + Prec = v; + return S_OK; } + processed = false; return S_OK; } diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Common/HandlerOut.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Common/HandlerOut.h index 5a18d980..3122b05c 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Common/HandlerOut.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Common/HandlerOut.h @@ -1,32 +1,105 @@ // HandlerOut.h -#ifndef __HANDLER_OUT_H -#define __HANDLER_OUT_H +#ifndef ZIP7_INC_HANDLER_OUT_H +#define ZIP7_INC_HANDLER_OUT_H + +#include "../../../Windows/System.h" #include "../../Common/MethodProps.h" namespace NArchive { -class CMultiMethodProps +bool ParseSizeString(const wchar_t *name, const PROPVARIANT &prop, UInt64 percentsBase, UInt64 &res); + +class CCommonMethodProps { - UInt32 _level; - int _analysisLevel; +protected: + void InitCommon() + { + // _Write_MTime = true; + { +#ifndef Z7_ST + _numThreads_WasForced = false; + UInt32 numThreads; +#ifdef _WIN32 + NWindows::NSystem::CProcessAffinity aff; + numThreads = aff.Load_and_GetNumberOfThreads(); + _numThreadGroups = aff.IsGroupMode ? aff.Groups.GroupSizes.Size() : 0; +#else + numThreads = NWindows::NSystem::GetNumberOfProcessors(); +#endif // _WIN32 + _numProcessors = _numThreads = numThreads; +#endif // Z7_ST + } + + size_t memAvail = (size_t)sizeof(size_t) << 28; + _memAvail = memAvail; + _memUsage_Compress = memAvail; + _memUsage_Decompress = memAvail; + _memUsage_WasSet = NWindows::NSystem::GetRamSize(memAvail); + if (_memUsage_WasSet) + { + _memAvail = memAvail; + unsigned bits = sizeof(size_t) * 8; + if (bits == 32) + { + const UInt32 limit2 = (UInt32)7 << 28; + if (memAvail > limit2) + memAvail = limit2; + } + // 80% - is auto usage limit in handlers + // _memUsage_Compress = memAvail * 4 / 5; + // _memUsage_Compress = Calc_From_Val_Percents(memAvail, 80); + _memUsage_Compress = Calc_From_Val_Percents_Less100(memAvail, 80); + _memUsage_Decompress = memAvail / 32 * 17; + } + } + public: - #ifndef _7ZIP_ST +#ifndef Z7_ST UInt32 _numThreads; UInt32 _numProcessors; - #endif +#ifdef _WIN32 + UInt32 _numThreadGroups; +#endif + bool _numThreads_WasForced; +#endif + + bool _memUsage_WasSet; + UInt64 _memUsage_Compress; + UInt64 _memUsage_Decompress; + size_t _memAvail; + + bool SetCommonProperty(const UString &name, const PROPVARIANT &value, HRESULT &hres); + + CCommonMethodProps() { InitCommon(); } +}; + + +#ifndef Z7_EXTRACT_ONLY + +class CMultiMethodProps: public CCommonMethodProps +{ + UInt32 _level; + int _analysisLevel; + void InitMulti(); +public: UInt32 _crcSize; CObjectVector _methods; COneMethodInfo _filterMethod; bool _autoFilter; - void SetGlobalLevelAndThreads(COneMethodInfo &oneMethodInfo - #ifndef _7ZIP_ST - , UInt32 numThreads - #endif - ); + + void SetGlobalLevelTo(COneMethodInfo &oneMethodInfo) const; + +#ifndef Z7_ST + static void SetMethodThreadsTo_IfNotFinded(CMethodProps &props, UInt32 numThreads); + static void SetMethodThreadsTo_Replace(CMethodProps &props, UInt32 numThreads); + + static void Set_Method_NumThreadGroups_IfNotFinded(CMethodProps &props, UInt32 numThreadGroups); +#endif + unsigned GetNumEmptyMethods() const { @@ -41,27 +114,56 @@ class CMultiMethodProps int GetAnalysisLevel() const { return _analysisLevel; } void Init(); + CMultiMethodProps() { InitMulti(); } - CMultiMethodProps() { Init(); } HRESULT SetProperty(const wchar_t *name, const PROPVARIANT &value); }; -class CSingleMethodProps: public COneMethodInfo + +class CSingleMethodProps: public COneMethodInfo, public CCommonMethodProps { UInt32 _level; - -public: - #ifndef _7ZIP_ST - UInt32 _numThreads; - UInt32 _numProcessors; - #endif + void InitSingle() + { + _level = (UInt32)(Int32)-1; + } + +public: void Init(); - CSingleMethodProps() { Init(); } + CSingleMethodProps() { InitSingle(); } + int GetLevel() const { return _level == (UInt32)(Int32)-1 ? 5 : (int)_level; } + HRESULT SetProperty(const wchar_t *name, const PROPVARIANT &values); HRESULT SetProperties(const wchar_t * const *names, const PROPVARIANT *values, UInt32 numProps); }; +#endif + +struct CHandlerTimeOptions +{ + CBoolPair Write_MTime; + CBoolPair Write_ATime; + CBoolPair Write_CTime; + UInt32 Prec; + + void Init() + { + Write_MTime.Init(); + Write_MTime.Val = true; + Write_ATime.Init(); + Write_CTime.Init(); + Prec = (UInt32)(Int32)-1; + } + + CHandlerTimeOptions() + { + Init(); + } + + HRESULT Parse(const UString &name, const PROPVARIANT &prop, bool &processed); +}; + } #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Common/InStreamWithCRC.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/Common/InStreamWithCRC.cpp index a2d68832..735d5d1e 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Common/InStreamWithCRC.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Common/InStreamWithCRC.cpp @@ -4,22 +4,33 @@ #include "InStreamWithCRC.h" -STDMETHODIMP CSequentialInStreamWithCRC::Read(void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CSequentialInStreamWithCRC::Read(void *data, UInt32 size, UInt32 *processedSize)) { UInt32 realProcessed = 0; HRESULT result = S_OK; - if (_stream) - result = _stream->Read(data, size, &realProcessed); - _size += realProcessed; - if (size != 0 && realProcessed == 0) - _wasFinished = true; - _crc = CrcUpdate(_crc, data, realProcessed); + if (size != 0) + { + if (_stream) + result = _stream->Read(data, size, &realProcessed); + _size += realProcessed; + if (realProcessed == 0) + _wasFinished = true; + else + _crc = CrcUpdate(_crc, data, realProcessed); + } if (processedSize) *processedSize = realProcessed; return result; } -STDMETHODIMP CInStreamWithCRC::Read(void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CSequentialInStreamWithCRC::GetSize(UInt64 *size)) +{ + *size = _fullSize; + return S_OK; +} + + +Z7_COM7F_IMF(CInStreamWithCRC::Read(void *data, UInt32 size, UInt32 *processedSize)) { UInt32 realProcessed = 0; HRESULT result = S_OK; @@ -36,7 +47,7 @@ STDMETHODIMP CInStreamWithCRC::Read(void *data, UInt32 size, UInt32 *processedSi return result; } -STDMETHODIMP CInStreamWithCRC::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition) +Z7_COM7F_IMF(CInStreamWithCRC::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition)) { if (seekOrigin != STREAM_SEEK_SET || offset != 0) return E_FAIL; diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Common/InStreamWithCRC.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Common/InStreamWithCRC.h index 31b761e4..48736892 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Common/InStreamWithCRC.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Common/InStreamWithCRC.h @@ -1,7 +1,7 @@ // InStreamWithCRC.h -#ifndef __IN_STREAM_WITH_CRC_H -#define __IN_STREAM_WITH_CRC_H +#ifndef ZIP7_INC_IN_STREAM_WITH_CRC_H +#define ZIP7_INC_IN_STREAM_WITH_CRC_H #include "../../../../C/7zCrc.h" @@ -9,26 +9,29 @@ #include "../../IStream.h" -class CSequentialInStreamWithCRC: - public ISequentialInStream, - public CMyUnknownImp -{ -public: - MY_UNKNOWN_IMP - - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); -private: +Z7_CLASS_IMP_NOQIB_2( + CSequentialInStreamWithCRC + , ISequentialInStream + , IStreamGetSize +) CMyComPtr _stream; UInt64 _size; UInt32 _crc; bool _wasFinished; + UInt64 _fullSize; public: - void SetStream(ISequentialInStream *stream) { _stream = stream; } + + CSequentialInStreamWithCRC(): + _fullSize((UInt64)(Int64)-1) + {} + + void SetStream(ISequentialInStream *stream) { _stream = stream; } + void SetFullSize(UInt64 fullSize) { _fullSize = fullSize; } void Init() { _size = 0; - _wasFinished = false; _crc = CRC_INIT_VAL; + _wasFinished = false; } void ReleaseStream() { _stream.Release(); } UInt32 GetCRC() const { return CRC_GET_DIGEST(_crc); } @@ -36,22 +39,16 @@ class CSequentialInStreamWithCRC: bool WasFinished() const { return _wasFinished; } }; -class CInStreamWithCRC: - public IInStream, - public CMyUnknownImp -{ -public: - MY_UNKNOWN_IMP1(IInStream) - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); - STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition); -private: +Z7_CLASS_IMP_IInStream( + CInStreamWithCRC +) CMyComPtr _stream; UInt64 _size; UInt32 _crc; // bool _wasFinished; public: - void SetStream(IInStream *stream) { _stream = stream; } + void SetStream(IInStream *stream) { _stream = stream; } void Init() { _size = 0; diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Common/ItemNameUtils.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/Common/ItemNameUtils.cpp index 7cd3037b..150efc9b 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Common/ItemNameUtils.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Common/ItemNameUtils.cpp @@ -7,58 +7,106 @@ namespace NArchive { namespace NItemName { -static const wchar_t kOSDirDelimiter = WCHAR_PATH_SEPARATOR; -static const wchar_t kDirDelimiter = L'/'; +static const wchar_t kOsPathSepar = WCHAR_PATH_SEPARATOR; -void ReplaceToOsPathSeparator(wchar_t *s) -{ - #ifdef _WIN32 - for (;;) +#if WCHAR_PATH_SEPARATOR != L'/' +static const wchar_t kUnixPathSepar = L'/'; +#endif + +void ReplaceSlashes_OsToUnix +#if WCHAR_PATH_SEPARATOR != L'/' + (UString &name) { - wchar_t c = *s; - if (c == 0) - break; - if (c == kDirDelimiter) - *s = kOSDirDelimiter; - s++; + name.Replace(kOsPathSepar, kUnixPathSepar); } +#else + (UString &) {} +#endif + + +UString GetOsPath(const UString &name) +{ + #if WCHAR_PATH_SEPARATOR != L'/' + UString newName = name; + newName.Replace(kUnixPathSepar, kOsPathSepar); + return newName; + #else + return name; #endif } -UString MakeLegalName(const UString &name) + +UString GetOsPath_Remove_TailSlash(const UString &name) { - UString zipName = name; - zipName.Replace(kOSDirDelimiter, kDirDelimiter); - return zipName; + if (name.IsEmpty()) + return UString(); + UString newName = GetOsPath(name); + if (newName.Back() == kOsPathSepar) + newName.DeleteBack(); + return newName; } -UString GetOSName(const UString &name) + +#if WCHAR_PATH_SEPARATOR != L'/' +void ReplaceToWinSlashes(UString &name, bool useBackslashReplacement) { - UString newName = name; - newName.Replace(kDirDelimiter, kOSDirDelimiter); - return newName; + // name.Replace(kUnixPathSepar, kOsPathSepar); + const unsigned len = name.Len(); + for (unsigned i = 0; i < len; i++) + { + wchar_t c = name[i]; + if (c == L'/') + c = WCHAR_PATH_SEPARATOR; + else if (useBackslashReplacement && c == L'\\') + c = WCHAR_IN_FILE_NAME_BACKSLASH_REPLACEMENT; // WSL scheme + else + continue; + name.ReplaceOneCharAtPos(i, c); + } } +#endif -UString GetOSName2(const UString &name) +void ReplaceToOsSlashes_Remove_TailSlash(UString &name, bool + #if WCHAR_PATH_SEPARATOR != L'/' + useBackslashReplacement + #endif + ) { if (name.IsEmpty()) - return UString(); - UString newName = GetOSName(name); - if (newName.Back() == kOSDirDelimiter) - newName.DeleteBack(); - return newName; + return; + + #if WCHAR_PATH_SEPARATOR != L'/' + ReplaceToWinSlashes(name, useBackslashReplacement); + #endif + + if (name.Back() == kOsPathSepar) + name.DeleteBack(); } -void ConvertToOSName2(UString &name) + +void NormalizeSlashes_in_FileName_for_OsPath(wchar_t *name, unsigned len) { - if (!name.IsEmpty()) + for (unsigned i = 0; i < len; i++) { - name.Replace(kDirDelimiter, kOSDirDelimiter); - if (name.Back() == kOSDirDelimiter) - name.DeleteBack(); + wchar_t c = name[i]; + if (c == L'/') + c = L'_'; + #if WCHAR_PATH_SEPARATOR != L'/' + else if (c == L'\\') + c = WCHAR_IN_FILE_NAME_BACKSLASH_REPLACEMENT; // WSL scheme + #endif + else + continue; + name[i] = c; } } +void NormalizeSlashes_in_FileName_for_OsPath(UString &name) +{ + NormalizeSlashes_in_FileName_for_OsPath(name.GetBuf(), name.Len()); +} + + bool HasTailSlash(const AString &name, UINT #if defined(_WIN32) && !defined(UNDER_CE) codePage @@ -67,20 +115,24 @@ bool HasTailSlash(const AString &name, UINT { if (name.IsEmpty()) return false; - LPCSTR prev = - #if defined(_WIN32) && !defined(UNDER_CE) - CharPrevExA((WORD)codePage, name, &name[name.Len()], 0); - #else - (LPCSTR)(name) + (name.Len() - 1); - #endif - return (*prev == '/'); + char c; + #if defined(_WIN32) && !defined(UNDER_CE) + if (codePage != CP_UTF8) + c = *CharPrevExA((WORD)codePage, name, name.Ptr(name.Len()), 0); + else + #endif + { + c = name.Back(); + } + return (c == '/'); } + #ifndef _WIN32 -UString WinNameToOSName(const UString &name) +UString WinPathToOsPath(const UString &name) { UString newName = name; - newName.Replace(L'\\', kOSDirDelimiter); + newName.Replace(L'\\', WCHAR_PATH_SEPARATOR); return newName; } #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Common/ItemNameUtils.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Common/ItemNameUtils.h index d0dc76a4..cea8dcc5 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Common/ItemNameUtils.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Common/ItemNameUtils.h @@ -1,26 +1,32 @@ // Archive/Common/ItemNameUtils.h -#ifndef __ARCHIVE_ITEM_NAME_UTILS_H -#define __ARCHIVE_ITEM_NAME_UTILS_H +#ifndef ZIP7_INC_ARCHIVE_ITEM_NAME_UTILS_H +#define ZIP7_INC_ARCHIVE_ITEM_NAME_UTILS_H #include "../../../Common/MyString.h" namespace NArchive { namespace NItemName { - void ReplaceToOsPathSeparator(wchar_t *s); - - UString MakeLegalName(const UString &name); - UString GetOSName(const UString &name); - UString GetOSName2(const UString &name); - void ConvertToOSName2(UString &name); - bool HasTailSlash(const AString &name, UINT codePage); - - #ifdef _WIN32 - inline UString WinNameToOSName(const UString &name) { return name; } - #else - UString WinNameToOSName(const UString &name); - #endif +void ReplaceSlashes_OsToUnix(UString &name); + +UString GetOsPath(const UString &name); +UString GetOsPath_Remove_TailSlash(const UString &name); + +#if WCHAR_PATH_SEPARATOR != L'/' +void ReplaceToWinSlashes(UString &name, bool useBackslashReplacement); +#endif +void ReplaceToOsSlashes_Remove_TailSlash(UString &name, bool useBackslashReplacement = false); +void NormalizeSlashes_in_FileName_for_OsPath(wchar_t *s, unsigned len); +void NormalizeSlashes_in_FileName_for_OsPath(UString &name); + +bool HasTailSlash(const AString &name, UINT codePage); + +#ifdef _WIN32 + inline UString WinPathToOsPath(const UString &name) { return name; } +#else + UString WinPathToOsPath(const UString &name); +#endif }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Common/MultiStream.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/Common/MultiStream.cpp index 1de74afe..5d357af7 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Common/MultiStream.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Common/MultiStream.cpp @@ -4,7 +4,7 @@ #include "MultiStream.h" -STDMETHODIMP CMultiStream::Read(void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CMultiStream::Read(void *data, UInt32 size, UInt32 *processedSize)) { if (processedSize) *processedSize = 0; @@ -23,10 +23,7 @@ STDMETHODIMP CMultiStream::Read(void *data, UInt32 size, UInt32 *processedSize) else if (_pos >= m.GlobalOffset + m.Size) left = mid + 1; else - { - _streamIndex = mid; break; - } mid = (left + right) / 2; } _streamIndex = mid; @@ -36,12 +33,14 @@ STDMETHODIMP CMultiStream::Read(void *data, UInt32 size, UInt32 *processedSize) UInt64 localPos = _pos - s.GlobalOffset; if (localPos != s.LocalPos) { - RINOK(s.Stream->Seek(localPos, STREAM_SEEK_SET, &s.LocalPos)); + RINOK(s.Stream->Seek((Int64)localPos, STREAM_SEEK_SET, &s.LocalPos)) } - UInt64 rem = s.Size - localPos; - if (size > rem) - size = (UInt32)rem; - HRESULT result = s.Stream->Read(data, size, &size); + { + const UInt64 rem = s.Size - localPos; + if (size > rem) + size = (UInt32)rem; + } + const HRESULT result = s.Stream->Read(data, size, &size); _pos += size; s.LocalPos += size; if (processedSize) @@ -49,7 +48,7 @@ STDMETHODIMP CMultiStream::Read(void *data, UInt32 size, UInt32 *processedSize) return result; } -STDMETHODIMP CMultiStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition) +Z7_COM7F_IMF(CMultiStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition)) { switch (seekOrigin) { @@ -60,9 +59,9 @@ STDMETHODIMP CMultiStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosi } if (offset < 0) return HRESULT_WIN32_ERROR_NEGATIVE_SEEK; - _pos = offset; + _pos = (UInt64)offset; if (newPosition) - *newPosition = offset; + *newPosition = (UInt64)offset; return S_OK; } @@ -72,6 +71,9 @@ class COutVolumeStream: public ISequentialOutStream, public CMyUnknownImp { + Z7_COM_UNKNOWN_IMP_0 + Z7_IFACE_COM7_IMP(ISequentialOutStream) + unsigned _volIndex; UInt64 _volSize; UInt64 _curPos; @@ -80,8 +82,6 @@ class COutVolumeStream: CCRC _crc; public: - MY_UNKNOWN_IMP - CFileItem _file; CUpdateOptions _options; CMyComPtr VolumeCallback; @@ -98,7 +98,6 @@ class COutVolumeStream: } HRESULT Flush(); - STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize); }; HRESULT COutVolumeStream::Flush() @@ -107,7 +106,7 @@ HRESULT COutVolumeStream::Flush() { _file.UnPackSize = _curPos; _file.FileCRC = _crc.GetDigest(); - RINOK(WriteVolumeHeader(_archive, _file, _options)); + RINOK(WriteVolumeHeader(_archive, _file, _options)) _archive.Close(); _volumeStream.Release(); _file.StartPos += _file.UnPackSize; @@ -117,7 +116,10 @@ HRESULT COutVolumeStream::Flush() */ /* -STDMETHODIMP COutMultiStream::Write(const void *data, UInt32 size, UInt32 *processedSize) + +#include "../../../Common/Defs.h" + +Z7_COM7F_IMF(COutMultiStream::Write(const void *data, UInt32 size, UInt32 *processedSize)) { if (processedSize) *processedSize = 0; @@ -126,8 +128,8 @@ STDMETHODIMP COutMultiStream::Write(const void *data, UInt32 size, UInt32 *proce if (_streamIndex >= Streams.Size()) { CSubStreamInfo subStream; - RINOK(VolumeCallback->GetVolumeSize(Streams.Size(), &subStream.Size)); - RINOK(VolumeCallback->GetVolumeStream(Streams.Size(), &subStream.Stream)); + RINOK(VolumeCallback->GetVolumeSize(Streams.Size(), &subStream.Size)) + RINOK(VolumeCallback->GetVolumeStream(Streams.Size(), &subStream.Stream)) subStream.Pos = 0; Streams.Add(subStream); continue; @@ -142,15 +144,15 @@ STDMETHODIMP COutMultiStream::Write(const void *data, UInt32 size, UInt32 *proce if (_offsetPos != subStream.Pos) { CMyComPtr outStream; - RINOK(subStream.Stream.QueryInterface(IID_IOutStream, &outStream)); - RINOK(outStream->Seek(_offsetPos, STREAM_SEEK_SET, NULL)); + RINOK(subStream.Stream.QueryInterface(IID_IOutStream, &outStream)) + RINOK(outStream->Seek((Int64)_offsetPos, STREAM_SEEK_SET, NULL)) subStream.Pos = _offsetPos; } - UInt32 curSize = (UInt32)MyMin((UInt64)size, subStream.Size - subStream.Pos); + const UInt32 curSize = (UInt32)MyMin((UInt64)size, subStream.Size - subStream.Pos); UInt32 realProcessed; - RINOK(subStream.Stream->Write(data, curSize, &realProcessed)); - data = (void *)((Byte *)data + realProcessed); + RINOK(subStream.Stream->Write(data, curSize, &realProcessed)) + data = (const void *)((const Byte *)data + realProcessed); size -= realProcessed; subStream.Pos += realProcessed; _offsetPos += realProcessed; @@ -170,7 +172,7 @@ STDMETHODIMP COutMultiStream::Write(const void *data, UInt32 size, UInt32 *proce return S_OK; } -STDMETHODIMP COutMultiStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition) +Z7_COM7F_IMF(COutMultiStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition)) { switch (seekOrigin) { @@ -181,11 +183,11 @@ STDMETHODIMP COutMultiStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newP } if (offset < 0) return HRESULT_WIN32_ERROR_NEGATIVE_SEEK; - _absPos = offset; + _absPos = (UInt64)offset; _offsetPos = _absPos; _streamIndex = 0; if (newPosition) - *newPosition = offset; + *newPosition = (UInt64)offset; return S_OK; } */ diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Common/MultiStream.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Common/MultiStream.h index c10cd455..901b6ea2 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Common/MultiStream.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Common/MultiStream.h @@ -1,20 +1,21 @@ // MultiStream.h -#ifndef __MULTI_STREAM_H -#define __MULTI_STREAM_H +#ifndef ZIP7_INC_MULTI_STREAM_H +#define ZIP7_INC_MULTI_STREAM_H #include "../../../Common/MyCom.h" #include "../../../Common/MyVector.h" #include "../../IStream.h" +#include "../../Archive/IArchive.h" -class CMultiStream: - public IInStream, - public CMyUnknownImp -{ +Z7_CLASS_IMP_IInStream( + CMultiStream +) + + unsigned _streamIndex; UInt64 _pos; UInt64 _totalLength; - unsigned _streamIndex; public: @@ -24,12 +25,12 @@ class CMultiStream: UInt64 Size; UInt64 GlobalOffset; UInt64 LocalPos; - CSubStreamInfo(): Size(0), GlobalOffset(0), LocalPos(0) {} }; - + + CMyComPtr updateCallbackFile; CObjectVector Streams; - + HRESULT Init() { UInt64 total = 0; @@ -37,26 +38,27 @@ class CMultiStream: { CSubStreamInfo &s = Streams[i]; s.GlobalOffset = total; - total += Streams[i].Size; - RINOK(s.Stream->Seek(0, STREAM_SEEK_CUR, &s.LocalPos)); + total += s.Size; + s.LocalPos = 0; + { + // it was already set to start + // RINOK(InStream_GetPos(s.Stream, s.LocalPos)); + } } _totalLength = total; _pos = 0; _streamIndex = 0; return S_OK; } - - MY_UNKNOWN_IMP1(IInStream) - - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); - STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition); }; /* -class COutMultiStream: - public IOutStream, - public CMyUnknownImp -{ +Z7_CLASS_IMP_COM_1( + COutMultiStream, + IOutStream +) + Z7_IFACE_COM7_IMP(ISequentialOutStream) + unsigned _streamIndex; // required stream UInt64 _offsetPos; // offset from start of _streamIndex index UInt64 _absPos; @@ -78,11 +80,6 @@ class COutMultiStream: _absPos = 0; _length = 0; } - - MY_UNKNOWN_IMP1(IOutStream) - - STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize); - STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition); }; */ diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Common/OutStreamWithCRC.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/Common/OutStreamWithCRC.cpp index f955c225..7dcd44ac 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Common/OutStreamWithCRC.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Common/OutStreamWithCRC.cpp @@ -4,7 +4,7 @@ #include "OutStreamWithCRC.h" -STDMETHODIMP COutStreamWithCRC::Write(const void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(COutStreamWithCRC::Write(const void *data, UInt32 size, UInt32 *processedSize)) { HRESULT result = S_OK; if (_stream) @@ -12,7 +12,7 @@ STDMETHODIMP COutStreamWithCRC::Write(const void *data, UInt32 size, UInt32 *pro if (_calculate) _crc = CrcUpdate(_crc, data, size); _size += size; - if (processedSize != NULL) + if (processedSize) *processedSize = size; return result; } diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Common/OutStreamWithCRC.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Common/OutStreamWithCRC.h index 09b899bb..845146a1 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Common/OutStreamWithCRC.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Common/OutStreamWithCRC.h @@ -1,7 +1,7 @@ // OutStreamWithCRC.h -#ifndef __OUT_STREAM_WITH_CRC_H -#define __OUT_STREAM_WITH_CRC_H +#ifndef ZIP7_INC_OUT_STREAM_WITH_CRC_H +#define ZIP7_INC_OUT_STREAM_WITH_CRC_H #include "../../../../C/7zCrc.h" @@ -9,17 +9,15 @@ #include "../../IStream.h" -class COutStreamWithCRC: - public ISequentialOutStream, - public CMyUnknownImp -{ +Z7_CLASS_IMP_NOQIB_1( + COutStreamWithCRC + , ISequentialOutStream +) CMyComPtr _stream; UInt64 _size; UInt32 _crc; bool _calculate; public: - MY_UNKNOWN_IMP - STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize); void SetStream(ISequentialOutStream *stream) { _stream = stream; } void ReleaseStream() { _stream.Release(); } void Init(bool calculate = true) diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Common/OutStreamWithSha1.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/Common/OutStreamWithSha1.cpp index 77252938..2916c30b 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Common/OutStreamWithSha1.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Common/OutStreamWithSha1.cpp @@ -4,15 +4,26 @@ #include "OutStreamWithSha1.h" -STDMETHODIMP COutStreamWithSha1::Write(const void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(COutStreamWithSha1::Write(const void *data, UInt32 size, UInt32 *processedSize)) { HRESULT result = S_OK; if (_stream) result = _stream->Write(data, size, &size); if (_calculate) - Sha1_Update(&_sha, (const Byte *)data, size); + Sha1_Update(Sha(), (const Byte *)data, size); _size += size; if (processedSize) *processedSize = size; return result; } + +Z7_COM7F_IMF(CInStreamWithSha1::Read(void *data, UInt32 size, UInt32 *processedSize)) +{ + UInt32 realProcessedSize; + const HRESULT result = _stream->Read(data, size, &realProcessedSize); + _size += realProcessedSize; + Sha1_Update(Sha(), (const Byte *)data, realProcessedSize); + if (processedSize) + *processedSize = realProcessedSize; + return result; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Common/OutStreamWithSha1.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Common/OutStreamWithSha1.h index 41a84cd6..28b2587e 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Common/OutStreamWithSha1.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Common/OutStreamWithSha1.h @@ -1,36 +1,61 @@ // OutStreamWithSha1.h -#ifndef __OUT_STREAM_WITH_SHA1_H -#define __OUT_STREAM_WITH_SHA1_H +#ifndef ZIP7_INC_OUT_STREAM_WITH_SHA1_H +#define ZIP7_INC_OUT_STREAM_WITH_SHA1_H #include "../../../../C/Sha1.h" +#include "../../../Common/MyBuffer2.h" #include "../../../Common/MyCom.h" #include "../../IStream.h" -class COutStreamWithSha1: - public ISequentialOutStream, - public CMyUnknownImp -{ +Z7_CLASS_IMP_NOQIB_1( + COutStreamWithSha1 + , ISequentialOutStream +) + bool _calculate; CMyComPtr _stream; + CAlignedBuffer1 _sha; UInt64 _size; - CSha1 _sha; - bool _calculate; + + CSha1 *Sha() { return (CSha1 *)(void *)(Byte *)_sha; } public: - MY_UNKNOWN_IMP - STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize); + COutStreamWithSha1(): _sha(sizeof(CSha1)) {} void SetStream(ISequentialOutStream *stream) { _stream = stream; } void ReleaseStream() { _stream.Release(); } void Init(bool calculate = true) { - _size = 0; _calculate = calculate; - Sha1_Init(&_sha); + _size = 0; + Sha1_Init(Sha()); + } + void InitSha1() { Sha1_Init(Sha()); } + UInt64 GetSize() const { return _size; } + void Final(Byte *digest) { Sha1_Final(Sha(), digest); } +}; + + +Z7_CLASS_IMP_NOQIB_1( + CInStreamWithSha1 + , ISequentialInStream +) + CMyComPtr _stream; + CAlignedBuffer1 _sha; + UInt64 _size; + + CSha1 *Sha() { return (CSha1 *)(void *)(Byte *)_sha; } +public: + CInStreamWithSha1(): _sha(sizeof(CSha1)) {} + void SetStream(ISequentialInStream *stream) { _stream = stream; } + void Init() + { + _size = 0; + Sha1_Init(Sha()); } - void InitSha1() { Sha1_Init(&_sha); } + void ReleaseStream() { _stream.Release(); } UInt64 GetSize() const { return _size; } - void Final(Byte *digest) { Sha1_Final(&_sha, digest); } + void Final(Byte *digest) { Sha1_Final(Sha(), digest); } }; #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Common/ParseProperties.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Common/ParseProperties.h index 1038a8c0..4ec2e48e 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Common/ParseProperties.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Common/ParseProperties.h @@ -1,6 +1,6 @@ // ParseProperties.h -#ifndef __PARSE_PROPERTIES_H -#define __PARSE_PROPERTIES_H +#ifndef ZIP7_INC_PARSE_PROPERTIES_H +#define ZIP7_INC_PARSE_PROPERTIES_H #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Common/StdAfx.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Common/StdAfx.h index 2854ff3e..035267cc 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Common/StdAfx.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Common/StdAfx.h @@ -1,8 +1,11 @@ // StdAfx.h -#ifndef __STDAFX_H -#define __STDAFX_H +#ifndef ZIP7_INC_STDAFX_H +#define ZIP7_INC_STDAFX_H +#if defined(_MSC_VER) && _MSC_VER >= 1800 +#pragma warning(disable : 4464) // relative include path contains '..' +#endif #include "../../../Common/Common.h" #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/CpioHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/CpioHandler.cpp index e8898cac..e1d6d81d 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/CpioHandler.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/CpioHandler.cpp @@ -30,13 +30,11 @@ namespace NCpio { static const Byte kMagicBin0 = 0xC7; static const Byte kMagicBin1 = 0x71; -// #define MAGIC_ASCII { '0', '7', '0', '7', '0' } - static const Byte kMagicHex = '1'; // New ASCII Format static const Byte kMagicHexCrc = '2'; // New CRC Format static const Byte kMagicOct = '7'; // Portable ASCII Format -static const char *kName_TRAILER = "TRAILER!!!"; +static const char * const kName_TRAILER = "TRAILER!!!"; static const unsigned k_BinRecord_Size = 2 + 8 * 2 + 2 * 4; static const unsigned k_OctRecord_Size = 6 + 8 * 6 + 2 * 11; @@ -44,41 +42,6 @@ static const unsigned k_HexRecord_Size = 6 + 13 * 8; static const unsigned k_RecordSize_Max = k_HexRecord_Size; - /* - struct CBinRecord - { - unsigned short c_magic; - short c_dev; - unsigned short c_ino; - unsigned short c_mode; - unsigned short c_uid; - unsigned short c_gid; - unsigned short c_nlink; - short c_rdev; - unsigned short c_mtimes[2]; - unsigned short c_namesize; - unsigned short c_filesizes[2]; - }; - - struct CHexRecord - { - char Magic[6]; - char inode[8]; - char Mode[8]; - char UID[8]; - char GID[8]; - char nlink[8]; - char mtime[8]; - char Size[8]; // must be 0 for FIFOs and directories - char DevMajor[8]; - char DevMinor[8]; - char RDevMajor[8]; //only valid for chr and blk special files - char RDevMinor[8]; //only valid for chr and blk special files - char NameSize[8]; // count includes terminating NUL in pathname - char ChkSum[8]; // 0 for "new" portable format; for CRC format the sum of all the bytes in the file - }; -*/ - enum EType { k_Type_BinLe, @@ -99,114 +62,163 @@ static const char * const k_Types[] = struct CItem { - AString Name; UInt32 inode; + unsigned MainIndex_ForInode; UInt32 Mode; - UInt32 UID; - UInt32 GID; - UInt64 Size; UInt32 MTime; - - UInt32 NumLinks; UInt32 DevMajor; UInt32 DevMinor; + UInt64 Size; + AString Name; + UInt32 NumLinks; + UInt32 UID; + UInt32 GID; UInt32 RDevMajor; UInt32 RDevMinor; UInt32 ChkSum; - UInt32 Align; + UInt32 AlignMask; EType Type; UInt32 HeaderSize; UInt64 HeaderPos; + CByteBuffer Data; // for symlink + + + UInt32 GetAlignedSize(UInt32 size) const + { + return (size + AlignMask) & ~(UInt32)AlignMask; + } + + UInt64 GetPackSize() const + { + const UInt64 alignMask64 = AlignMask; + return (Size + alignMask64) & ~(UInt64)alignMask64; + } + + bool IsSame_inode_Dev(const CItem &item) const + { + return inode == item.inode + && DevMajor == item.DevMajor + && DevMinor == item.DevMinor; + } + bool IsBin() const { return Type == k_Type_BinLe || Type == k_Type_BinBe; } bool IsCrcFormat() const { return Type == k_Type_HexCrc; } bool IsDir() const { return MY_LIN_S_ISDIR(Mode); } + bool Is_SymLink() const { return MY_LIN_S_ISLNK(Mode); } bool IsTrailer() const { return strcmp(Name, kName_TRAILER) == 0; } UInt64 GetDataPosition() const { return HeaderPos + HeaderSize; } }; + enum EErrorType { k_ErrorType_OK, + k_ErrorType_BadSignature, k_ErrorType_Corrupted, - k_ErrorType_UnexpectedEnd, + k_ErrorType_UnexpectedEnd }; + struct CInArchive { + EErrorType errorType; ISequentialInStream *Stream; UInt64 Processed; + CItem item; HRESULT Read(void *data, size_t *size); - HRESULT GetNextItem(CItem &item, EErrorType &errorType); + HRESULT GetNextItem(); }; HRESULT CInArchive::Read(void *data, size_t *size) { - HRESULT res = ReadStream(Stream, data, size); + const HRESULT res = ReadStream(Stream, data, size); Processed += *size; return res; } -static bool ReadHex(const Byte *p, UInt32 &resVal) + +static bool CheckOctRecord(const Byte *p) +{ + for (unsigned i = 6; i < k_OctRecord_Size; i++) + { + const unsigned c = (unsigned)p[i] - '0'; + if (c > 7) + return false; + } + return true; +} + +static bool CheckHexRecord(const Byte *p) +{ + for (unsigned i = 6; i < k_HexRecord_Size; i++) + { + unsigned c = p[i]; + c -= '0'; + if (c > 9) + { + c -= 'A' - '0'; + c &= ~0x20u; + if (c > 5) + return false; + } + } + return true; +} + +static UInt32 ReadHex(const Byte *p) { char sz[16]; memcpy(sz, p, 8); sz[8] = 0; const char *end; - resVal = ConvertHexStringToUInt32(sz, &end); - return (unsigned)(end - sz) == 8; + return ConvertHexStringToUInt32(sz, &end); } -static bool ReadOct6(const Byte *p, UInt32 &resVal) +static UInt32 ReadOct6(const Byte *p) { char sz[16]; memcpy(sz, p, 6); sz[6] = 0; const char *end; - resVal = ConvertOctStringToUInt32(sz, &end); - return (unsigned)(end - sz) == 6; + return ConvertOctStringToUInt32(sz, &end); } -static bool ReadOct11(const Byte *p, UInt64 &resVal) +static UInt64 ReadOct11(const Byte *p) { char sz[16]; memcpy(sz, p, 11); sz[11] = 0; const char *end; - resVal = ConvertOctStringToUInt64(sz, &end); - return (unsigned)(end - sz) == 11; + return ConvertOctStringToUInt64(sz, &end); } -#define READ_HEX(y) { if (!ReadHex(p2, y)) return S_OK; p2 += 8; } -#define READ_OCT_6(y) { if (!ReadOct6(p2, y)) return S_OK; p2 += 6; } -#define READ_OCT_11(y) { if (!ReadOct11(p2, y)) return S_OK; p2 += 11; } +#define READ_HEX( y, dest) dest = ReadHex (p + 6 + (y) * 8); +#define READ_OCT_6( y, dest) dest = ReadOct6 (p + 6 + (y)); +#define READ_OCT_11( y, dest) dest = ReadOct11(p + 6 + (y)); -static UInt32 GetAlignedSize(UInt32 size, UInt32 align) -{ - while ((size & (align - 1)) != 0) - size++; - return size; -} - -static UInt16 Get16(const Byte *p, bool be) { if (be) return GetBe16(p); return GetUi16(p); } -static UInt32 Get32(const Byte *p, bool be) { return ((UInt32)Get16(p, be) << 16) + Get16(p + 2, be); } - -#define G16(offs, v) v = Get16(p + (offs), be) -#define G32(offs, v) v = Get32(p + (offs), be) +#define Get32spec(p) (((UInt32)GetUi16(p) << 16) + GetUi16(p + 2)) +#define G16(offs, v) v = GetUi16(p + (offs)) +#define G32(offs, v) v = Get32spec(p + (offs)) static const unsigned kNameSizeMax = 1 << 12; + API_FUNC_static_IsArc IsArc_Cpio(const Byte *p, size_t size) { if (size < k_BinRecord_Size) return k_IsArc_Res_NEED_MORE; + UInt32 namePos; UInt32 nameSize; - UInt32 numLinks; + UInt32 mode; + // UInt32 rDevMinor; + UInt32 rDevMajor = 0; + if (p[0] == '0') { if (p[1] != '7' || @@ -214,92 +226,123 @@ API_FUNC_static_IsArc IsArc_Cpio(const Byte *p, size_t size) p[3] != '7' || p[4] != '0') return k_IsArc_Res_NO; - if (p[5] == '7') + if (p[5] == kMagicOct) { if (size < k_OctRecord_Size) return k_IsArc_Res_NEED_MORE; - for (int i = 6; i < k_OctRecord_Size; i++) - { - char c = p[i]; - if (c < '0' || c > '7') - return k_IsArc_Res_NO; - } - ReadOct6(p + 6 * 6, numLinks); - ReadOct6(p + 8 * 6 + 11, nameSize); + if (!CheckOctRecord(p)) + return k_IsArc_Res_NO; + READ_OCT_6 (2 * 6, mode) + // READ_OCT_6 (6 * 6, rDevMinor) + READ_OCT_6 (7 * 6 + 11, nameSize) + namePos = k_OctRecord_Size; } - else if (p[5] == '1' || p[5] == '2') + else if (p[5] == kMagicHex || p[5] == kMagicHexCrc) { if (size < k_HexRecord_Size) return k_IsArc_Res_NEED_MORE; - for (int i = 6; i < k_HexRecord_Size; i++) - { - char c = p[i]; - if ((c < '0' || c > '9') && - (c < 'A' || c > 'F') && - (c < 'a' || c > 'f')) - return k_IsArc_Res_NO; - } - ReadHex(p + 6 + 4 * 8, numLinks); - ReadHex(p + 6 + 11 * 8, nameSize); + if (!CheckHexRecord(p)) + return k_IsArc_Res_NO; + READ_HEX (1, mode) + READ_HEX (9, rDevMajor) + // READ_HEX (10, rDevMinor) + READ_HEX (11, nameSize) + namePos = k_HexRecord_Size; } else return k_IsArc_Res_NO; } else { - UInt32 rDevMinor; if (p[0] == kMagicBin0 && p[1] == kMagicBin1) { - numLinks = GetUi16(p + 12); - rDevMinor = GetUi16(p + 14); + mode = GetUi16(p + 6); + // rDevMinor = GetUi16(p + 14); nameSize = GetUi16(p + 20); } else if (p[0] == kMagicBin1 && p[1] == kMagicBin0) { - numLinks = GetBe16(p + 12); - rDevMinor = GetBe16(p + 14); + mode = GetBe16(p + 6); + // rDevMinor = GetBe16(p + 14); nameSize = GetBe16(p + 20); } else return k_IsArc_Res_NO; + namePos = k_BinRecord_Size; + } - if (rDevMinor != 0) - return k_IsArc_Res_NO; - if (nameSize > (1 << 8)) + if (mode >= (1 << 16)) + return k_IsArc_Res_NO; + + /* v23.02: we have disabled rDevMinor check because real file + from Apple contains rDevMinor==255 by some unknown reason */ + if (rDevMajor != 0 + // || rDevMinor != 0 + ) + { + if (!MY_LIN_S_ISCHR(mode) && + !MY_LIN_S_ISBLK(mode)) return k_IsArc_Res_NO; } - if (numLinks == 0 || numLinks >= (1 << 10)) - return k_IsArc_Res_NO; + + // nameSize must include the null byte if (nameSize == 0 || nameSize > kNameSizeMax) return k_IsArc_Res_NO; + { + unsigned lim = namePos + nameSize - 1; + if (lim >= size) + lim = (unsigned)size; + else if (p[lim] != 0) + return k_IsArc_Res_NO; + for (unsigned i = namePos; i < lim; i++) + if (p[i] == 0) + return k_IsArc_Res_NO; + } + return k_IsArc_Res_YES; } } + #define READ_STREAM(_dest_, _size_) \ { size_t processed = (_size_); RINOK(Read(_dest_, &processed)); \ if (processed != (_size_)) { errorType = k_ErrorType_UnexpectedEnd; return S_OK; } } -HRESULT CInArchive::GetNextItem(CItem &item, EErrorType &errorType) +HRESULT CInArchive::GetNextItem() { - errorType = k_ErrorType_Corrupted; + errorType = k_ErrorType_BadSignature; Byte p[k_RecordSize_Max]; READ_STREAM(p, k_BinRecord_Size) UInt32 nameSize; + UInt32 namePos; + + /* we try to reduce probability of false detection, + so we check some fields for unuxpected values */ if (p[0] != '0') { - bool be; - if (p[0] == kMagicBin0 && p[1] == kMagicBin1) { be = false; item.Type = k_Type_BinLe; } - else if (p[0] == kMagicBin1 && p[1] == kMagicBin0) { be = true; item.Type = k_Type_BinBe; } - else return S_FALSE; + if (p[0] == kMagicBin0 && p[1] == kMagicBin1) { item.Type = k_Type_BinLe; } + else if (p[0] == kMagicBin1 && p[1] == kMagicBin0) + { + for (unsigned i = 2; i < k_BinRecord_Size; i += 2) + { + const Byte b = p[i]; + p[i] = p[i + 1]; + p[i + 1] = b; + } + item.Type = k_Type_BinBe; + } + else + return S_OK; + + errorType = k_ErrorType_Corrupted; - item.Align = 2; + item.AlignMask = 2 - 1; item.DevMajor = 0; - item.RDevMajor =0; + item.RDevMajor = 0; item.ChkSum = 0; G16(2, item.DevMinor); @@ -313,13 +356,7 @@ HRESULT CInArchive::GetNextItem(CItem &item, EErrorType &errorType) G16(20, nameSize); G32(22, item.Size); - /* - if (item.RDevMinor != 0) - return S_FALSE; - */ - - item.HeaderSize = GetAlignedSize(nameSize + k_BinRecord_Size, item.Align); - nameSize = item.HeaderSize - k_BinRecord_Size; + namePos = k_BinRecord_Size; } else { @@ -327,104 +364,142 @@ HRESULT CInArchive::GetNextItem(CItem &item, EErrorType &errorType) p[2] != '0' || p[3] != '7' || p[4] != '0') - return S_FALSE; + return S_OK; if (p[5] == kMagicOct) { + errorType = k_ErrorType_Corrupted; + item.Type = k_Type_Oct; READ_STREAM(p + k_BinRecord_Size, k_OctRecord_Size - k_BinRecord_Size) - item.Align = 1; + item.AlignMask = 1 - 1; item.DevMajor = 0; item.RDevMajor = 0; + item.ChkSum = 0; - const Byte *p2 = p + 6; - READ_OCT_6(item.DevMinor); - READ_OCT_6(item.inode); - READ_OCT_6(item.Mode); - READ_OCT_6(item.UID); - READ_OCT_6(item.GID); - READ_OCT_6(item.NumLinks); - READ_OCT_6(item.RDevMinor); + if (!CheckOctRecord(p)) + return S_OK; + + READ_OCT_6 (0, item.DevMinor) + READ_OCT_6 (1 * 6, item.inode) + READ_OCT_6 (2 * 6, item.Mode) + READ_OCT_6 (3 * 6, item.UID) + READ_OCT_6 (4 * 6, item.GID) + READ_OCT_6 (5 * 6, item.NumLinks) + READ_OCT_6 (6 * 6, item.RDevMinor) { UInt64 mTime64; - READ_OCT_11(mTime64); + READ_OCT_11 (7 * 6, mTime64) item.MTime = 0; - if (mTime64 < (UInt32)(Int32)-1) + if (mTime64 <= (UInt32)(Int32)-1) item.MTime = (UInt32)mTime64; } - READ_OCT_6(nameSize); - READ_OCT_11(item.Size); // ????? - item.HeaderSize = GetAlignedSize(nameSize + k_OctRecord_Size, item.Align); - nameSize = item.HeaderSize - k_OctRecord_Size; + READ_OCT_6 (7 * 6 + 11, nameSize) + READ_OCT_11 (8 * 6 + 11, item.Size) // ????? + + namePos = k_OctRecord_Size; } else { - if (p[5] == kMagicHex) - item.Type = k_Type_Hex; - else if (p[5] == kMagicHexCrc) - item.Type = k_Type_HexCrc; - else - return S_FALSE; + if (p[5] == kMagicHex) item.Type = k_Type_Hex; + else if (p[5] == kMagicHexCrc) item.Type = k_Type_HexCrc; + else return S_OK; + + errorType = k_ErrorType_Corrupted; READ_STREAM(p + k_BinRecord_Size, k_HexRecord_Size - k_BinRecord_Size) - item.Align = 4; + if (!CheckHexRecord(p)) + return S_OK; - const Byte *p2 = p + 6; - READ_HEX(item.inode); - READ_HEX(item.Mode); - READ_HEX(item.UID); - READ_HEX(item.GID); - READ_HEX(item.NumLinks); - READ_HEX(item.MTime); - { - UInt32 size32; - READ_HEX(size32); - item.Size = size32; - } - READ_HEX(item.DevMajor); - READ_HEX(item.DevMinor); - READ_HEX(item.RDevMajor); - READ_HEX(item.RDevMinor); - READ_HEX(nameSize); - READ_HEX(item.ChkSum); - if (nameSize >= kNameSizeMax) + item.AlignMask = 4 - 1; + READ_HEX (0, item.inode) + READ_HEX (1, item.Mode) + READ_HEX (2, item.UID) + READ_HEX (3, item.GID) + READ_HEX (4, item.NumLinks) + READ_HEX (5, item.MTime) + READ_HEX (6, item.Size) + READ_HEX (7, item.DevMajor) + READ_HEX (8, item.DevMinor) + READ_HEX (9, item.RDevMajor) + READ_HEX (10, item.RDevMinor) + READ_HEX (11, nameSize) + READ_HEX (12, item.ChkSum) + + if (item.Type == k_Type_Hex && item.ChkSum != 0) return S_OK; - item.HeaderSize = GetAlignedSize(nameSize + k_HexRecord_Size, item.Align); - nameSize = item.HeaderSize - k_HexRecord_Size; + + namePos = k_HexRecord_Size; } } - if (nameSize > kNameSizeMax) - return S_FALSE; - if (nameSize == 0 || nameSize >= kNameSizeMax) + + if (item.Mode >= (1 << 16)) return S_OK; - char *s = item.Name.GetBuf(nameSize); - size_t processedSize = nameSize; - RINOK(Read(s, &processedSize)); - item.Name.ReleaseBuf_CalcLen(nameSize); - if (processedSize != nameSize) + + /* v23.02: we have disabled rDevMinor check because real file + from Apple contains rDevMinor==255 by some unknown reason + cpio 2.13 and older versions: it copies stat::st_rdev to archive. + and stat::st_rdev can be non-zero for some old linux/filesystems cases for regular files. + cpio 2.14 (2023) copies st_rdev to archive only if (S_ISBLK (st->st_mode) || S_ISCHR (st->st_mode)) + v25.00: we have disabled RDevMajor check here to support some rare case created by cpio 2.13- with old linux. + But we still keep full check in IsArc_Cpio() to reduce false cpio detection cases. + */ +#if 0 // 0 : to disable check to support some old linux cpio archives. + if (item.RDevMajor != 0 + // || item.RDevMinor != 0 + ) + { + if (!MY_LIN_S_ISCHR(item.Mode) && + !MY_LIN_S_ISBLK(item.Mode)) + return S_OK; + } +#endif + + // Size must be 0 for FIFOs and directories + if (item.IsDir() || MY_LIN_S_ISFIFO(item.Mode)) + if (item.Size != 0) + return S_OK; + + // nameSize must include the null byte + if (nameSize == 0 || nameSize > kNameSizeMax) + return S_OK; + item.HeaderSize = item.GetAlignedSize(namePos + nameSize); + const UInt32 rem = item.HeaderSize - namePos; + char *s = item.Name.GetBuf(rem); + size_t processedSize = rem; + RINOK(Read(s, &processedSize)) + if (processedSize != rem) { + item.Name.ReleaseBuf_SetEnd(0); errorType = k_ErrorType_UnexpectedEnd; return S_OK; } + bool pad_error = false; + for (size_t i = nameSize; i < processedSize; i++) + if (s[i] != 0) + pad_error = true; + item.Name.ReleaseBuf_CalcLen(nameSize); + if (item.Name.Len() + 1 != nameSize || pad_error) + return S_OK; errorType = k_ErrorType_OK; return S_OK; } -class CHandler: - public IInArchive, - public IInArchiveGetStream, - public CMyUnknownImp -{ + + +Z7_CLASS_IMP_CHandler_IInArchive_1( + IInArchiveGetStream +) CObjectVector _items; CMyComPtr _stream; UInt64 _phySize; - EType _Type; + EType _type; EErrorType _error; bool _isArc; -public: - MY_UNKNOWN_IMP2(IInArchive, IInArchiveGetStream) - INTERFACE_IInArchive(;) - STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream); + bool _moreThanOneHardLinks_Error; + bool _numLinks_Error; + bool _pad_Error; + bool _symLink_Error; }; static const Byte kArcProps[] = @@ -437,22 +512,35 @@ static const Byte kProps[] = kpidPath, kpidIsDir, kpidSize, + kpidPackSize, kpidMTime, kpidPosixAttrib, - kpidLinks + kpidLinks, + kpidINode, + kpidUserId, + kpidGroupId, + kpidDevMajor, + kpidDevMinor, + kpidDeviceMajor, + kpidDeviceMinor, + kpidChecksum, + kpidSymLink, + kpidStreamId, // for debug + kpidOffset }; IMP_IInArchive_Props IMP_IInArchive_ArcProps -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; switch (propID) { - case kpidSubType: prop = k_Types[(unsigned)_Type]; break; + case kpidSubType: prop = k_Types[(unsigned)_type]; break; case kpidPhySize: prop = _phySize; break; + case kpidINode: prop = true; break; case kpidErrorFlags: { UInt32 v = 0; @@ -461,11 +549,28 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) switch (_error) { case k_ErrorType_UnexpectedEnd: v |= kpv_ErrorFlags_UnexpectedEnd; break; - case k_ErrorType_Corrupted: v |= kpv_ErrorFlags_HeadersError; break; + case k_ErrorType_Corrupted: v |= kpv_ErrorFlags_HeadersError; break; + case k_ErrorType_OK: + case k_ErrorType_BadSignature: + // default: + break; } prop = v; break; } + case kpidWarningFlags: + { + UInt32 v = 0; + if (_moreThanOneHardLinks_Error) + v |= kpv_ErrorFlags_UnsupportedFeature; // kpv_ErrorFlags_HeadersError + if (_numLinks_Error + || _pad_Error + || _symLink_Error) + v |= kpv_ErrorFlags_HeadersError; + if (v != 0) + prop = v; + break; + } } prop.Detach(value); return S_OK; @@ -473,22 +578,43 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) } -STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *callback) +static int CompareItems(const unsigned *p1, const unsigned *p2, void *param) +{ + const CObjectVector &items = *(const CObjectVector *)param; + const unsigned index1 = *p1; + const unsigned index2 = *p2; + const CItem &i1 = items[index1]; + const CItem &i2 = items[index2]; + if (i1.DevMajor < i2.DevMajor) return -1; + if (i1.DevMajor > i2.DevMajor) return 1; + if (i1.DevMinor < i2.DevMinor) return -1; + if (i1.DevMinor > i2.DevMinor) return 1; + if (i1.inode < i2.inode) return -1; + if (i1.inode > i2.inode) return 1; + if (i1.IsDir()) + { + if (!i2.IsDir()) + return -1; + } + else if (i2.IsDir()) + return 1; + return MyCompare(index1, index2); +} + + +Z7_COM7F_IMF(CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *callback)) { COM_TRY_BEGIN { Close(); - UInt64 endPos = 0; - - RINOK(stream->Seek(0, STREAM_SEEK_END, &endPos)); - RINOK(stream->Seek(0, STREAM_SEEK_SET, NULL)); + UInt64 endPos; + RINOK(InStream_AtBegin_GetSize(stream, endPos)) if (callback) { - RINOK(callback->SetTotal(NULL, &endPos)); + RINOK(callback->SetTotal(NULL, &endPos)) } - _items.Clear(); CInArchive arc; arc.Stream = stream; @@ -496,120 +622,256 @@ STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallb for (;;) { - CItem item; + CItem &item = arc.item; item.HeaderPos = arc.Processed; - HRESULT result = arc.GetNextItem(item, _error); - if (result == S_FALSE) - return S_FALSE; - if (result != S_OK) - return S_FALSE; + + RINOK(arc.GetNextItem()) + + _error = arc.errorType; + if (_error != k_ErrorType_OK) { - if (_error == k_ErrorType_Corrupted) + if (_error == k_ErrorType_BadSignature || + _error == k_ErrorType_Corrupted) arc.Processed = item.HeaderPos; break; } + if (_items.IsEmpty()) - _Type = item.Type; + _type = item.Type; else if (_items.Back().Type != item.Type) { _error = k_ErrorType_Corrupted; arc.Processed = item.HeaderPos; break; } + if (item.IsTrailer()) break; + item.MainIndex_ForInode = _items.Size(); _items.Add(item); + const UInt64 dataSize = item.GetPackSize(); + arc.Processed += dataSize; + if (arc.Processed > endPos) { - // archive.SkipDataRecords(item.Size, item.Align); - UInt64 dataSize = item.Size; - UInt32 align = item.Align; - while ((dataSize & (align - 1)) != 0) - dataSize++; - - // _error = k_ErrorType_UnexpectedEnd; break; - - arc.Processed += dataSize; - if (arc.Processed > endPos) + _error = k_ErrorType_UnexpectedEnd; + break; + } + + if (item.Is_SymLink() && dataSize <= (1 << 12) && item.Size != 0) + { + size_t cur = (size_t)dataSize; + CByteBuffer buf; + buf.Alloc(cur); + RINOK(ReadStream(stream, buf, &cur)) + if (cur != dataSize) { _error = k_ErrorType_UnexpectedEnd; break; } - - UInt64 newPostion; - RINOK(stream->Seek(dataSize, STREAM_SEEK_CUR, &newPostion)); - if (arc.Processed != newPostion) + size_t i; + + for (i = (size_t)item.Size; i < dataSize; i++) + if (buf[i] != 0) + break; + if (i != dataSize) + _pad_Error = true; + + for (i = 0; i < (size_t)item.Size; i++) + if (buf[i] == 0) + break; + if (i != (size_t)item.Size) + _symLink_Error = true; + else + _items.Back().Data.CopyFrom(buf, (size_t)item.Size); + } + else if (dataSize != 0) + { + UInt64 newPos; + RINOK(stream->Seek((Int64)dataSize, STREAM_SEEK_CUR, &newPos)) + if (arc.Processed != newPos) return E_FAIL; } - if (callback && (_items.Size() & 0xFF) == 0) + if (callback && (_items.Size() & 0xFFF) == 0) { - UInt64 numFiles = _items.Size(); - RINOK(callback->SetCompleted(&numFiles, &item.HeaderPos)); + const UInt64 numFiles = _items.Size(); + RINOK(callback->SetCompleted(&numFiles, &item.HeaderPos)) } } + _phySize = arc.Processed; + } + + { if (_error != k_ErrorType_OK) { + // we try to reduce probability of false detection if (_items.Size() == 0) return S_FALSE; + // bin file uses small signature. So we do additional check for single item case. if (_items.Size() == 1 && _items[0].IsBin()) - { - // probably it's false detected archive. So we return error return S_FALSE; - } } else { // Read tailing zeros. // Most of cpio files use 512-bytes aligned zeros - UInt64 pos = arc.Processed; - const UInt32 kTailSize_MAX = 1 << 9; + // rare case: 4K/8K aligment is possible also + const unsigned kTailSize_MAX = 1 << 9; Byte buf[kTailSize_MAX]; - UInt32 rem = (kTailSize_MAX - (UInt32)pos) & (kTailSize_MAX - 1); - if (rem != 0) + unsigned pos = (unsigned)_phySize & (kTailSize_MAX - 1); + if (pos != 0) // use this check to support 512 bytes alignment only + for (;;) { - rem++; // we need to see that it's end of file + const unsigned rem = kTailSize_MAX - pos; size_t processed = rem; - RINOK(ReadStream(stream, buf, &processed)); - if (processed < rem) + RINOK(ReadStream(stream, buf + pos, &processed)) + if (processed != rem) + break; + for (; pos < kTailSize_MAX && buf[pos] == 0; pos++) + {} + if (pos != kTailSize_MAX) + break; + _phySize += processed; + pos = 0; + + // use break to support 512 bytes alignment zero tail + // don't use break to support 512*n bytes alignment zero tail + break; + } + } + } + + { + /* there was such cpio archive example with hard links: + { + all hard links (same dev/inode) are stored in neighboring items, and + (item.Size == 0) for non last hard link items + (item.Size != 0) for last hard link item + } + but here we sort items by (dev/inode) to support cases + where hard links (same dev/inode) are not stored in neighboring items. + + // note: some cpio files have (numLinks == 0) ?? + */ + + CUIntVector indices; + { + const unsigned numItems = _items.Size(); + indices.ClearAndSetSize(numItems); + if (numItems != 0) + { + unsigned *vals = &indices[0]; + for (unsigned i = 0; i < numItems; i++) + vals[i] = i; + indices.Sort(CompareItems, (void *)&_items); + } + } + + /* Note: if cpio archive (maybe incorrect) contains + more then one non empty streams with identical inode number, + we want to extract all such data streams too. + + So we place items with identical inode to groups: + all items in group will have same MainIndex_ForInode, + that is index of last item in group with (Size != 0). + Another (non last) items in group have (Size == 0). + If there are another hard links with same inode number + after (Size != 0) item, we place them to another next group(s). + + Check it: maybe we should use single group for items + with identical inode instead, and ignore some extra data streams ? + */ + + for (unsigned i = 0; i < indices.Size();) + { + unsigned k; + { + const CItem &item_Base = _items[indices[i]]; + + if (item_Base.IsDir()) + { + i++; + continue; + } + + if (i != 0) + { + const CItem &item_Prev = _items[indices[i - 1]]; + if (!item_Prev.IsDir()) + if (item_Base.IsSame_inode_Dev(item_Prev)) + _moreThanOneHardLinks_Error = true; + } + + if (item_Base.Size != 0) + { + if (item_Base.NumLinks != 1) + _numLinks_Error = true; + i++; + continue; + } + + for (k = i + 1; k < indices.Size();) { - unsigned i; - for (i = 0; i < processed && buf[i] == 0; i++); - if (i == processed) - _phySize += processed; + const CItem &item = _items[indices[k]]; + if (item.IsDir()) + break; + if (!item.IsSame_inode_Dev(item_Base)) + break; + k++; + if (item.Size != 0) + break; } } + + const unsigned numLinks = k - i; + for (;;) + { + CItem &item = _items[indices[i]]; + if (item.NumLinks != numLinks) + _numLinks_Error = true; + if (++i == k) + break; + // if (item.Size == 0) + item.MainIndex_ForInode = indices[k - 1]; + } } - - _isArc = true; - _stream = stream; } + + _isArc = true; + _stream = stream; + return S_OK; COM_TRY_END } -STDMETHODIMP CHandler::Close() + +Z7_COM7F_IMF(CHandler::Close()) { _items.Clear(); _stream.Release(); _phySize = 0; - _Type = k_Type_BinLe; + _type = k_Type_BinLe; _isArc = false; + _moreThanOneHardLinks_Error = false; + _numLinks_Error = false; + _pad_Error = false; + _symLink_Error = false; _error = k_ErrorType_OK; return S_OK; } -STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) { *numItems = _items.Size(); return S_OK; } -STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; @@ -619,91 +881,137 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val { case kpidPath: { - UString res; - bool needConvert = true; - #ifdef _WIN32 - if (ConvertUTF8ToUnicode(item.Name, res)) - needConvert = false; - #endif - if (needConvert) - res = MultiByteToUnicodeString(item.Name, CP_OEMCP); - prop = NItemName::GetOSName(res); +#ifdef _WIN32 + UString u; + ConvertUTF8ToUnicode(item.Name, u); +#else + const UString u = MultiByteToUnicodeString(item.Name, CP_OEMCP); +#endif + prop = NItemName::GetOsPath(u); break; } case kpidIsDir: prop = item.IsDir(); break; + case kpidSize: + prop = (UInt64)_items[item.MainIndex_ForInode].Size; + break; + case kpidPackSize: - prop = (UInt64)item.Size; + prop = (UInt64)item.GetPackSize(); break; + case kpidMTime: { if (item.MTime != 0) - { - FILETIME utc; - NTime::UnixTimeToFileTime(item.MTime, utc); - prop = utc; - } + PropVariant_SetFrom_UnixTime(prop, item.MTime); break; } case kpidPosixAttrib: prop = item.Mode; break; + case kpidINode: prop = item.inode; break; + case kpidStreamId: + if (!item.IsDir()) + prop = (UInt32)item.MainIndex_ForInode; + break; + case kpidDevMajor: prop = (UInt32)item.DevMajor; break; + case kpidDevMinor: prop = (UInt32)item.DevMinor; break; + + case kpidUserId: prop = item.UID; break; + case kpidGroupId: prop = item.GID; break; + + case kpidSymLink: + if (item.Is_SymLink() && item.Data.Size() != 0) + { + AString s; + s.SetFrom_CalcLen((const char *)(const void *)(const Byte *)item.Data, (unsigned)item.Data.Size()); + if (s.Len() == item.Data.Size()) + { +#ifdef _WIN32 + UString u; + ConvertUTF8ToUnicode(s, u); +#else + const UString u = MultiByteToUnicodeString(s, CP_OEMCP); +#endif + prop = u; + } + } + break; + case kpidLinks: prop = item.NumLinks; break; - /* - case kpidinode: prop = item.inode; break; - case kpidiChkSum: prop = item.ChkSum; break; - */ + case kpidDeviceMajor: + // if (item.RDevMajor != 0) + prop = (UInt32)item.RDevMajor; + break; + case kpidDeviceMinor: + // if (item.RDevMinor != 0) + prop = (UInt32)item.RDevMinor; + break; + case kpidChecksum: + if (item.IsCrcFormat()) + prop = item.ChkSum; + break; + case kpidOffset: prop = item.GetDataPosition(); break; } prop.Detach(value); return S_OK; COM_TRY_END } -class COutStreamWithSum: - public ISequentialOutStream, - public CMyUnknownImp -{ + +Z7_CLASS_IMP_NOQIB_1( + COutStreamWithSum + , ISequentialOutStream +) CMyComPtr _stream; - UInt64 _size; - UInt32 _crc; + UInt32 _checksum; bool _calculate; public: - MY_UNKNOWN_IMP - STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize); void SetStream(ISequentialOutStream *stream) { _stream = stream; } void ReleaseStream() { _stream.Release(); } - void Init(bool calculate = true) + void Init(bool calculate) { - _size = 0; _calculate = calculate; - _crc = 0; + _checksum = 0; } - void EnableCalc(bool calculate) { _calculate = calculate; } - void InitCRC() { _crc = 0; } - UInt64 GetSize() const { return _size; } - UInt32 GetCRC() const { return _crc; } + UInt32 GetChecksum() const { return _checksum; } }; -STDMETHODIMP COutStreamWithSum::Write(const void *data, UInt32 size, UInt32 *processedSize) + +Z7_COM7F_IMF(COutStreamWithSum::Write(const void *data, UInt32 size, UInt32 *processedSize)) { HRESULT result = S_OK; if (_stream) result = _stream->Write(data, size, &size); + if (processedSize) + *processedSize = size; if (_calculate) { - UInt32 crc = 0; - for (UInt32 i = 0; i < size; i++) - crc += (UInt32)(((const Byte *)data)[i]); - _crc += crc; + const Byte *p = (const Byte *)data; + const Byte *lim = p + size; + UInt32 sum = _checksum; + if (size >= 4) + { + lim -= 4 - 1; + do + { + sum += p[0] + p[1] + p[2] + p[3]; + p += 4; + } + while (p < lim); + lim += 4 - 1; + } + if (p != lim) { sum += *p++; + if (p != lim) { sum += *p++; + if (p != lim) { sum += *p++; }}} + _checksum = sum; } - if (processedSize) - *processedSize = size; return result; } -STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, - Int32 testMode, IArchiveExtractCallback *extractCallback) +Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testMode, IArchiveExtractCallback *extractCallback)) { COM_TRY_BEGIN - bool allFilesMode = (numItems == (UInt32)(Int32)-1); + const bool allFilesMode = (numItems == (UInt32)(Int32)-1); if (allFilesMode) numItems = _items.Size(); if (numItems == 0) @@ -711,71 +1019,78 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, UInt64 totalSize = 0; UInt32 i; for (i = 0; i < numItems; i++) - totalSize += _items[allFilesMode ? i : indices[i]].Size; - extractCallback->SetTotal(totalSize); - - UInt64 currentTotalSize = 0; - - NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder(); - CMyComPtr copyCoder = copyCoderSpec; + { + const UInt32 index = allFilesMode ? i : indices[i]; + const CItem &item2 = _items[index]; + const CItem &item = _items[item2.MainIndex_ForInode]; + totalSize += item.Size; + } + RINOK(extractCallback->SetTotal(totalSize)) - CLocalProgress *lps = new CLocalProgress; - CMyComPtr progress = lps; + CMyComPtr2_Create copyCoder; + CMyComPtr2_Create lps; lps->Init(extractCallback, false); + CMyComPtr2_Create inStream; + inStream->SetStream(_stream); + CMyComPtr2_Create outStreamSum; - CLimitedSequentialInStream *streamSpec = new CLimitedSequentialInStream; - CMyComPtr inStream(streamSpec); - streamSpec->SetStream(_stream); - - COutStreamWithSum *outStreamSumSpec = new COutStreamWithSum; - CMyComPtr outStreamSum(outStreamSumSpec); + UInt64 total_PackSize = 0; + UInt64 total_UnpackSize = 0; - for (i = 0; i < numItems; i++) + for (i = 0;; i++) { - lps->InSize = lps->OutSize = currentTotalSize; - RINOK(lps->SetCur()); - CMyComPtr outStream; - Int32 askMode = testMode ? + lps->InSize = total_PackSize; + lps->OutSize = total_UnpackSize; + RINOK(lps->SetCur()) + if (i >= numItems) + break; + const Int32 askMode = testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; - Int32 index = allFilesMode ? i : indices[i]; - const CItem &item = _items[index]; - RINOK(extractCallback->GetStream(index, &outStream, askMode)); - currentTotalSize += item.Size; - if (item.IsDir()) + const UInt32 index = allFilesMode ? i : indices[i]; + const CItem &item2 = _items[index]; + const CItem &item = _items[item2.MainIndex_ForInode]; { - RINOK(extractCallback->PrepareOperation(askMode)); - RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)); - continue; + CMyComPtr outStream; + RINOK(extractCallback->GetStream(index, &outStream, askMode)) + + total_PackSize += item2.GetPackSize(); + total_UnpackSize += item.Size; + + if (item2.IsDir()) + { + RINOK(extractCallback->PrepareOperation(askMode)) + RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)) + continue; + } + if (!testMode && !outStream) + continue; + outStreamSum->Init(item.IsCrcFormat()); + outStreamSum->SetStream(outStream); + RINOK(extractCallback->PrepareOperation(askMode)) } - if (!testMode && !outStream) - continue; - outStreamSumSpec->Init(item.IsCrcFormat()); - outStreamSumSpec->SetStream(outStream); - outStream.Release(); - - RINOK(extractCallback->PrepareOperation(askMode)); - RINOK(_stream->Seek(item.GetDataPosition(), STREAM_SEEK_SET, NULL)); - streamSpec->Init(item.Size); - RINOK(copyCoder->Code(inStream, outStreamSum, NULL, NULL, progress)); - outStreamSumSpec->ReleaseStream(); + RINOK(InStream_SeekSet(_stream, item.GetDataPosition())) + inStream->Init(item.Size); + RINOK(copyCoder.Interface()->Code(inStream, outStreamSum, NULL, NULL, lps)) + outStreamSum->ReleaseStream(); Int32 res = NExtract::NOperationResult::kDataError; - if (copyCoderSpec->TotalSize == item.Size) + if (copyCoder->TotalSize == item.Size) { res = NExtract::NOperationResult::kOK; - if (item.IsCrcFormat() && item.ChkSum != outStreamSumSpec->GetCRC()) + if (item.IsCrcFormat() && item.ChkSum != outStreamSum->GetChecksum()) res = NExtract::NOperationResult::kCRCError; } - RINOK(extractCallback->SetOperationResult(res)); + RINOK(extractCallback->SetOperationResult(res)) } return S_OK; COM_TRY_END } -STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream) +Z7_COM7F_IMF(CHandler::GetStream(UInt32 index, ISequentialInStream **stream)) { COM_TRY_BEGIN - const CItem &item = _items[index]; + const CItem &item2 = _items[index]; + const CItem &item = _items[item2.MainIndex_ForInode]; return CreateLimitedInStream(_stream, item.GetDataPosition(), item.Size, stream); COM_TRY_END } @@ -786,7 +1101,7 @@ static const Byte k_Signature[] = { 2, kMagicBin1, kMagicBin0 }; REGISTER_ARC_I( - "Cpio", "cpio", 0, 0xED, + "Cpio", "cpio", NULL, 0xED, k_Signature, 0, NArcInfoFlags::kMultiSignature, diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/CramfsHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/CramfsHandler.cpp index 0f123321..fd83f711 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/CramfsHandler.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/CramfsHandler.cpp @@ -25,9 +25,8 @@ namespace NArchive { namespace NCramfs { -#define SIGNATURE { 'C','o','m','p','r','e','s','s','e','d',' ','R','O','M','F','S' } - -static const Byte kSignature[] = SIGNATURE; +static const Byte kSignature[] = + { 'C','o','m','p','r','e','s','s','e','d',' ','R','O','M','F','S' }; static const UInt32 kArcSizeMax = (256 + 16) << 20; static const UInt32 kNumFilesMax = (1 << 19); @@ -114,7 +113,7 @@ static UInt32 GetNameLen(const Byte *p, bool be) if (be) return (p[8] & 0xFC); else - return (p[8] & 0x3F) << 2; + return ((UInt32)p[8] & (UInt32)0x3F) << 2; } static UInt32 GetOffset(const Byte *p, bool be) @@ -145,7 +144,7 @@ struct CHeader bool Parse(const Byte *p) { - if (memcmp(p + 16, kSignature, ARRAY_SIZE(kSignature)) != 0) + if (memcmp(p + 16, kSignature, Z7_ARRAY_SIZE(kSignature)) != 0) return false; switch (GetUi32(p)) { @@ -169,11 +168,11 @@ struct CHeader unsigned GetMethod() const { return (unsigned)(Flags >> k_Flags_Method_Shift) & k_Flags_Method_Mask; } }; -class CHandler: - public IInArchive, - public IInArchiveGetStream, - public CMyUnknownImp -{ + + +Z7_CLASS_IMP_CHandler_IInArchive_1( + IInArchiveGetStream +) CRecordVector _items; CMyComPtr _stream; Byte *_data; @@ -205,8 +204,8 @@ class CHandler: HRESULT OpenDir(int parent, UInt32 baseOffsetBase, unsigned level); HRESULT Open2(IInStream *inStream); - AString GetPath(int index) const; - bool GetPackSize(int index, UInt32 &res) const; + AString GetPath(unsigned index) const; + bool GetPackSize(unsigned index, UInt32 &res) const; void Free(); UInt32 GetNumBlocks(UInt32 size) const @@ -221,11 +220,8 @@ class CHandler: } public: - CHandler(): _data(0) {} + CHandler(): _data(NULL) {} ~CHandler() { Free(); } - MY_UNKNOWN_IMP2(IInArchive, IInArchiveGetStream) - INTERFACE_IInArchive(;) - STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream); HRESULT ReadBlock(UInt64 blockIndex, Byte *dest, size_t blockSize); }; @@ -291,7 +287,7 @@ HRESULT CHandler::OpenDir(int parent, UInt32 baseOffset, unsigned level) unsigned endIndex = _items.Size(); for (unsigned i = startIndex; i < endIndex; i++) { - RINOK(OpenDir(i, _items[i].Offset, level + 1)); + RINOK(OpenDir((int)i, _items[i].Offset, level + 1)) } return S_OK; } @@ -299,7 +295,7 @@ HRESULT CHandler::OpenDir(int parent, UInt32 baseOffset, unsigned level) HRESULT CHandler::Open2(IInStream *inStream) { Byte buf[kHeaderSize]; - RINOK(ReadStream_FALSE(inStream, buf, kHeaderSize)); + RINOK(ReadStream_FALSE(inStream, buf, kHeaderSize)) if (!_h.Parse(buf)) return S_FALSE; _method = k_Flags_Method_ZLIB; @@ -319,18 +315,18 @@ HRESULT CHandler::Open2(IInStream *inStream) else { UInt64 size; - RINOK(inStream->Seek(0, STREAM_SEEK_END, &size)); + RINOK(InStream_GetSize_SeekToEnd(inStream, size)) if (size > kArcSizeMax) size = kArcSizeMax; _h.Size = (UInt32)size; - RINOK(inStream->Seek(kHeaderSize, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekSet(inStream, kHeaderSize)) } _data = (Byte *)MidAlloc(_h.Size); - if (_data == 0) + if (!_data) return E_OUTOFMEMORY; memcpy(_data, buf, kHeaderSize); size_t processed = _h.Size - kHeaderSize; - RINOK(ReadStream(inStream, _data + kHeaderSize, &processed)); + RINOK(ReadStream(inStream, _data + kHeaderSize, &processed)) if (processed < kNodeSize) return S_FALSE; _size = kHeaderSize + (UInt32)processed; @@ -340,7 +336,7 @@ HRESULT CHandler::Open2(IInStream *inStream) _errorFlags = kpv_ErrorFlags_UnexpectedEnd; else { - SetUi32(_data + 0x20, 0); + SetUi32(_data + 0x20, 0) if (CrcCalc(_data, _h.Size) != _h.Crc) { _errorFlags = kpv_ErrorFlags_HeadersError; @@ -351,7 +347,7 @@ HRESULT CHandler::Open2(IInStream *inStream) _items.ClearAndReserve(_h.NumFiles - 1); } - RINOK(OpenDir(-1, kHeaderSize, 0)); + RINOK(OpenDir(-1, kHeaderSize, 0)) if (!_h.IsVer2()) { @@ -389,22 +385,23 @@ HRESULT CHandler::Open2(IInStream *inStream) return S_OK; } -AString CHandler::GetPath(int index) const +AString CHandler::GetPath(unsigned index) const { unsigned len = 0; - int indexMem = index; - do + unsigned indexMem = index; + for (;;) { const CItem &item = _items[index]; - index = item.Parent; const Byte *p = _data + item.Offset; unsigned size = GetNameLen(p, _h.be); p += kNodeSize; unsigned i; for (i = 0; i < size && p[i]; i++); len += i + 1; + index = (unsigned)item.Parent; + if (item.Parent < 0) + break; } - while (index >= 0); len--; AString path; @@ -413,7 +410,6 @@ AString CHandler::GetPath(int index) const for (;;) { const CItem &item = _items[index]; - index = item.Parent; const Byte *p = _data + item.Offset; unsigned size = GetNameLen(p, _h.be); p += kNodeSize; @@ -421,41 +417,42 @@ AString CHandler::GetPath(int index) const for (i = 0; i < size && p[i]; i++); dest -= i; memcpy(dest, p, i); - if (index < 0) + index = (unsigned)item.Parent; + if (item.Parent < 0) break; *(--dest) = CHAR_PATH_SEPARATOR; } return path; } -bool CHandler::GetPackSize(int index, UInt32 &res) const +bool CHandler::GetPackSize(unsigned index, UInt32 &res) const { res = 0; const CItem &item = _items[index]; const Byte *p = _data + item.Offset; - bool be = _h.be; - UInt32 offset = GetOffset(p, be); + const bool be = _h.be; + const UInt32 offset = GetOffset(p, be); if (offset < kHeaderSize) return false; - UInt32 numBlocks = GetNumBlocks(GetSize(p, be)); + const UInt32 numBlocks = GetNumBlocks(GetSize(p, be)); if (numBlocks == 0) return true; - UInt32 start = offset + numBlocks * 4; + const UInt32 start = offset + numBlocks * 4; if (start > _size) return false; - UInt32 end = Get32(_data + start - 4); + const UInt32 end = Get32(_data + start - 4); if (end < start) return false; res = end - start; return true; } -STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback * /* callback */) +Z7_COM7F_IMF(CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback * /* callback */)) { COM_TRY_BEGIN { Close(); - RINOK(Open2(stream)); + RINOK(Open2(stream)) _isArc = true; _stream = stream; } @@ -466,10 +463,10 @@ STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallb void CHandler::Free() { MidFree(_data); - _data = 0; + _data = NULL; } -STDMETHODIMP CHandler::Close() +Z7_COM7F_IMF(CHandler::Close()) { _isArc = false; _phySize = 0; @@ -481,13 +478,13 @@ STDMETHODIMP CHandler::Close() return S_OK; } -STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) { *numItems = _items.Size(); return S_OK; } -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NWindows::NCOM::CPropVariant prop; @@ -523,7 +520,7 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) COM_TRY_END } -STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NWindows::NCOM::CPropVariant prop; @@ -554,7 +551,7 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val class CCramfsInStream: public CCachedInStream { - HRESULT ReadBlock(UInt64 blockIndex, Byte *dest, size_t blockSize); + HRESULT ReadBlock(UInt64 blockIndex, Byte *dest, size_t blockSize) Z7_override; public: CHandler *Handler; }; @@ -626,16 +623,16 @@ HRESULT CHandler::ReadBlock(UInt64 blockIndex, Byte *dest, size_t blockSize) } _inStreamSpec->Init(_data + start, inSize); _outStreamSpec->Init(dest, blockSize); - RINOK(_zlibDecoder->Code(_inStream, _outStream, NULL, NULL, NULL)); + RINOK(_zlibDecoder->Code(_inStream, _outStream, NULL, NULL, NULL)) return (inSize == _zlibDecoderSpec->GetInputProcessedSize() && _outStreamSpec->GetPos() == blockSize) ? S_OK : S_FALSE; } -STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, - Int32 testMode, IArchiveExtractCallback *extractCallback) +Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testMode, IArchiveExtractCallback *extractCallback)) { COM_TRY_BEGIN - bool allFilesMode = (numItems == (UInt32)(Int32)-1); + const bool allFilesMode = (numItems == (UInt32)(Int32)-1); if (allFilesMode) numItems = _items.Size(); if (numItems == 0) @@ -665,20 +662,20 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, { lps->InSize = totalPackSize; lps->OutSize = totalSize; - RINOK(lps->SetCur()); + RINOK(lps->SetCur()) CMyComPtr outStream; - Int32 askMode = testMode ? + const Int32 askMode = testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; - UInt32 index = allFilesMode ? i : indices[i]; + const UInt32 index = allFilesMode ? i : indices[i]; const CItem &item = _items[index]; - RINOK(extractCallback->GetStream(index, &outStream, askMode)); + RINOK(extractCallback->GetStream(index, &outStream, askMode)) const Byte *p = _data + item.Offset; if (IsDir(p, be)) { - RINOK(extractCallback->PrepareOperation(askMode)); - RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)); + RINOK(extractCallback->PrepareOperation(askMode)) + RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)) continue; } UInt32 curSize = GetSize(p, be); @@ -689,7 +686,7 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, if (!testMode && !outStream) continue; - RINOK(extractCallback->PrepareOperation(askMode)); + RINOK(extractCallback->PrepareOperation(askMode)) UInt32 offset = GetOffset(p, be); if (offset < kHeaderSize) @@ -705,7 +702,7 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, res = NExtract::NOperationResult::kUnsupportedMethod; else { - RINOK(hres); + RINOK(hres) { hres = copyCoder->Code(inSeqStream, outStream, NULL, NULL, progress); if (hres == S_OK) @@ -720,14 +717,14 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, } } } - RINOK(extractCallback->SetOperationResult(res)); + RINOK(extractCallback->SetOperationResult(res)) } return S_OK; COM_TRY_END } -STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream) +Z7_COM7F_IMF(CHandler::GetStream(UInt32 index, ISequentialInStream **stream)) { COM_TRY_BEGIN @@ -778,7 +775,7 @@ STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream) } REGISTER_ARC_I( - "CramFS", "cramfs", 0, 0xD3, + "CramFS", "cramfs", NULL, 0xD3, kSignature, 16, 0, diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/DeflateProps.h b/src/plugins/7zip/7za/cpp/7zip/Archive/DeflateProps.h index 9fd2c2e9..666fb39b 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/DeflateProps.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/DeflateProps.h @@ -1,6 +1,6 @@ // DeflateProps.h -#ifndef __DEFLATE_PROPS_H -#define __DEFLATE_PROPS_H +#ifndef ZIP7_INC_DEFLATE_PROPS_H +#define ZIP7_INC_DEFLATE_PROPS_H #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/DllExports.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/DllExports.cpp index 7aee235e..9def2088 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/DllExports.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/DllExports.cpp @@ -2,10 +2,11 @@ #include "StdAfx.h" -#if defined(_7ZIP_LARGE_PAGES) +#if defined(Z7_LARGE_PAGES) #include "../../../C/Alloc.h" #endif +#include "../../Common/MyWindows.h" #include "../../Common/MyInitGuid.h" #include "../../Common/ComTry.h" @@ -20,22 +21,23 @@ #include "IArchive.h" +static HINSTANCE g_hInstance; #define NT_CHECK_FAIL_ACTION return FALSE; -extern "C" -BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID /*lpReserved*/) +extern "C" BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID /*lpReserved*/); +extern "C" BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID /*lpReserved*/) { if (dwReason == DLL_PROCESS_ATTACH) { g_hInstance = hInstance; - NT_CHECK; + NT_CHECK } return TRUE; } -DEFINE_GUID(CLSID_CArchiveHandler, +Z7_DEFINE_GUID(CLSID_CArchiveHandler, k_7zip_GUID_Data1, k_7zip_GUID_Data2, k_7zip_GUID_Data3_Common, @@ -50,7 +52,7 @@ STDAPI CreateObject(const GUID *clsid, const GUID *iid, void **outObject) STDAPI SetLargePageMode() { - #if defined(_7ZIP_LARGE_PAGES) + #if defined(Z7_LARGE_PAGES) SetLargePageSize(); #endif return S_OK; @@ -64,7 +66,7 @@ STDAPI SetCaseSensitive(Int32 caseSensitive) return S_OK; } -#ifdef EXTERNAL_CODECS +#ifdef Z7_EXTERNAL_CODECS CExternalCodecs g_ExternalCodecs; diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/DllExports2.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/DllExports2.cpp index 10889e75..79be2c81 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/DllExports2.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/DllExports2.cpp @@ -3,10 +3,9 @@ #include "StdAfx.h" #include "../../Common/MyWindows.h" - #include "../../Common/MyInitGuid.h" -#if defined(_7ZIP_LARGE_PAGES) +#if defined(Z7_LARGE_PAGES) #include "../../../C/Alloc.h" #endif @@ -22,11 +21,25 @@ #include "IArchive.h" -HINSTANCE g_hInstance; +#ifdef _WIN32 + +#if defined(_UNICODE) && !defined(_WIN64) && !defined(UNDER_CE) #define NT_CHECK_FAIL_ACTION return FALSE; +#endif + +static +HINSTANCE g_hInstance; + +extern "C" +BOOL WINAPI DllMain( + #ifdef UNDER_CE + HANDLE + #else + HINSTANCE + #endif + hInstance, DWORD dwReason, LPVOID /*lpReserved*/); -#ifdef _WIN32 extern "C" BOOL WINAPI DllMain( #ifdef UNDER_CE @@ -40,7 +53,7 @@ BOOL WINAPI DllMain( { // OutputDebugStringA("7z.dll DLL_PROCESS_ATTACH"); g_hInstance = (HINSTANCE)hInstance; - NT_CHECK; + NT_CHECK } /* if (dwReason == DLL_PROCESS_DETACH) @@ -50,9 +63,24 @@ BOOL WINAPI DllMain( */ return TRUE; } -#endif -DEFINE_GUID(CLSID_CArchiveHandler, +#else // _WIN32 + +#include "../../Common/StringConvert.h" +// #include + +// STDAPI LibStartup(); +static __attribute__((constructor)) void Init_ForceToUTF8(); +static __attribute__((constructor)) void Init_ForceToUTF8() +{ + g_ForceToUTF8 = IsNativeUTF8(); + // printf("\nDLLExports2.cpp::Init_ForceToUTF8 =%d\n", g_ForceToUTF8 ? 1 : 0); +} + +#endif // _WIN32 + + +Z7_DEFINE_GUID(CLSID_CArchiveHandler, k_7zip_GUID_Data1, k_7zip_GUID_Data2, k_7zip_GUID_Data3_Common, @@ -62,10 +90,11 @@ STDAPI CreateCoder(const GUID *clsid, const GUID *iid, void **outObject); STDAPI CreateHasher(const GUID *clsid, IHasher **hasher); STDAPI CreateArchiver(const GUID *clsid, const GUID *iid, void **outObject); +STDAPI CreateObject(const GUID *clsid, const GUID *iid, void **outObject); STDAPI CreateObject(const GUID *clsid, const GUID *iid, void **outObject) { // COM_TRY_BEGIN - *outObject = 0; + *outObject = NULL; if (*iid == IID_ICompressCoder || *iid == IID_ICompressCoder2 || *iid == IID_ICompressFilter) @@ -76,26 +105,59 @@ STDAPI CreateObject(const GUID *clsid, const GUID *iid, void **outObject) // COM_TRY_END } -STDAPI SetLargePageMode() +STDAPI SetLargePageMode2(UInt32 flags, size_t pageSize, size_t threshold); +STDAPI SetLargePageMode2(UInt32 flags, size_t pageSize, size_t threshold) { - #if defined(_7ZIP_LARGE_PAGES) - SetLargePageSize(); + #ifdef Z7_LARGE_PAGES + if (pageSize & (pageSize - 1)) + return E_INVALIDARG; + z7_LargePage_Set(flags, pageSize, threshold); + #else + UNUSED_VAR(flags) + UNUSED_VAR(pageSize) + UNUSED_VAR(threshold) #endif return S_OK; } +STDAPI SetLargePageMode(); +STDAPI SetLargePageMode() +{ + return SetLargePageMode2(0, 0, 0); // default values +} + extern bool g_CaseSensitive; +STDAPI SetCaseSensitive(Int32 caseSensitive); STDAPI SetCaseSensitive(Int32 caseSensitive) { g_CaseSensitive = (caseSensitive != 0); return S_OK; } -#ifdef EXTERNAL_CODECS +/* +UInt32 g_ClientVersion; +STDAPI SetClientVersion(UInt32 version); +STDAPI SetClientVersion(UInt32 version) +{ + g_ClientVersion = version; + return S_OK; +} +*/ + +/* +STDAPI SetProperty(Int32 id, const PROPVARIANT *value); +STDAPI SetProperty(Int32 id, const PROPVARIANT *value) +{ + return S_OK; +} +*/ + +#ifdef Z7_EXTERNAL_CODECS CExternalCodecs g_ExternalCodecs; +STDAPI SetCodecs(ICompressCodecsInfo *compressCodecsInfo); STDAPI SetCodecs(ICompressCodecsInfo *compressCodecsInfo) { COM_TRY_BEGIN @@ -114,6 +176,7 @@ STDAPI SetCodecs(ICompressCodecsInfo *compressCodecsInfo) #else +STDAPI SetCodecs(ICompressCodecsInfo *); STDAPI SetCodecs(ICompressCodecsInfo *) { return S_OK; diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/DmgHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/DmgHandler.cpp index 09952ac8..9079dc7f 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/DmgHandler.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/DmgHandler.cpp @@ -2,10 +2,13 @@ #include "StdAfx.h" +#include "../../../C/Alloc.h" #include "../../../C/CpuArch.h" +#include "../../Common/AutoPtr.h" #include "../../Common/ComTry.h" #include "../../Common/IntToString.h" +#include "../../Common/MyBuffer2.h" #include "../../Common/MyXml.h" #include "../../Common/UTFConvert.h" @@ -19,130 +22,111 @@ #include "../Compress/BZip2Decoder.h" #include "../Compress/CopyCoder.h" +#include "../Compress/LzfseDecoder.h" +#include "../Compress/XzDecoder.h" #include "../Compress/ZlibDecoder.h" #include "Common/OutStreamWithCRC.h" // #define DMG_SHOW_RAW -// #include -#define PRF(x) // x +// #define SHOW_DEBUG_INFO + +/* for debug only: we can use block cache also for METHOD_COPY blocks. + It can reduce the number of Stream->Read() requests. + But it can increase memory usage. + Note: METHOD_COPY blocks are not fused usually. + But if METHOD_COPY blocks is fused in some dmg example, + block size can exceed k_Chunk_Size_MAX. + So we don't use cache for METHOD_COPY block, if (block_size > k_Chunk_Size_MAX) +*/ +// for debug only: +// #define Z7_DMG_USE_CACHE_FOR_COPY_BLOCKS + +#ifdef SHOW_DEBUG_INFO +#include +#define PRF(x) x +#else +#define PRF(x) +#endif + #define Get16(p) GetBe16(p) #define Get32(p) GetBe32(p) #define Get64(p) GetBe64(p) -static const Byte k_Base64Table[256] = -{ - 66,77,77,77,77,77,77,77,77,65,65,77,77,65,77,77, - 77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77, - 65,77,77,77,77,77,77,77,77,77,77,62,77,77,77,63, - 52,53,54,55,56,57,58,59,60,61,77,77,77,64,77,77, - 77, 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,77,77,77,77,77, - 77,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40, - 41,42,43,44,45,46,47,48,49,50,51,77,77,77,77,77, - 77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77, - 77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77, - 77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77, - 77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77, - 77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77, - 77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77, - 77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77, - 77,77,77,77,77,77,77,77,77,77,77,77,77,77,77,77 -}; - -static Byte *Base64ToBin(Byte *dest, const char *src) -{ - UInt32 val = 1; - - for (;;) - { - UInt32 c = k_Base64Table[(Byte)(*src++)]; - - if (c < 64) - { - val = (val << 6) | c; - if ((val & ((UInt32)1 << 24)) == 0) - continue; - dest[0] = (Byte)(val >> 16); - dest[1] = (Byte)(val >> 8); - dest[2] = (Byte)(val); - dest += 3; - val = 1; - continue; - } - - if (c == 65) // space - continue; - - if (c == 64) // '=' - break; - - if (c == 66 && val == 1) // end of string - return dest; - - return NULL; - } - - if (val < (1 << 12)) - return NULL; - - if (val & (1 << 18)) - { - *dest++ = (Byte)(val >> 10); - *dest++ = (Byte)(val >> 2); - } - else if (k_Base64Table[(Byte)(*src++)] != 64) // '=' - return NULL; - else - *dest++ = (Byte)(val >> 4); - - for (;;) - { - Byte c = k_Base64Table[(Byte)(*src++)]; - if (c == 65) // space - continue; - if (c == 66) // end of string - return dest; - return NULL; - } -} +#define Get32a(p) GetBe32a(p) +#define Get64a(p) GetBe64a(p) +Byte *Base64ToBin(Byte *dest, const char *src); namespace NArchive { namespace NDmg { -enum -{ - METHOD_ZERO_0 = 0, - METHOD_COPY = 1, - METHOD_ZERO_2 = 2, // without file CRC calculation - METHOD_ADC = 0x80000004, - METHOD_ZLIB = 0x80000005, - METHOD_BZIP2 = 0x80000006, - METHOD_COMMENT = 0x7FFFFFFE, // is used to comment "+beg" and "+end" in extra field. - METHOD_END = 0xFFFFFFFF -}; +// allocation limits for compressed blocks for GetStream() interface: +static const unsigned k_NumChunks_MAX = 128; +static const size_t k_Chunk_Size_MAX = (size_t)1 << 28; +// 256 MB cache for 32-bit: +// 4 GB cache for 64-bit: +static const size_t k_Chunks_TotalSize_MAX = (size_t)1 << (sizeof(size_t) + 24); + +// 2 GB limit for 32-bit: +// 4 GB limit for 64-bit: +// that limit can be increased for 64-bit mode, if there are such dmg files +static const size_t k_XmlSize_MAX = + ((size_t)1 << (sizeof(size_t) / 4 + 30)) - 256; + +static const UInt32 METHOD_ZERO_0 = 0; // sparse +static const UInt32 METHOD_COPY = 1; +static const UInt32 METHOD_ZERO_2 = 2; // sparse : without file CRC calculation +static const UInt32 METHOD_ADC = 0x80000004; +static const UInt32 METHOD_ZLIB = 0x80000005; +static const UInt32 METHOD_BZIP2 = 0x80000006; +static const UInt32 METHOD_LZFSE = 0x80000007; +static const UInt32 METHOD_XZ = 0x80000008; +static const UInt32 METHOD_COMMENT = 0x7FFFFFFE; // is used to comment "+beg" and "+end" in extra field. +static const UInt32 METHOD_END = 0xFFFFFFFF; + struct CBlock { UInt32 Type; UInt64 UnpPos; - UInt64 UnpSize; + // UInt64 UnpSize; UInt64 PackPos; UInt64 PackSize; - UInt64 GetNextPackOffset() const { return PackPos + PackSize; } - UInt64 GetNextUnpPos() const { return UnpPos + UnpSize; } + bool NeedCrc() const { return Type != METHOD_ZERO_2; } + bool IsZeroMethod() const + { + return (Type & ~(UInt32)METHOD_ZERO_2) == 0; + // return Type == METHOD_ZERO_0 || Type == METHOD_ZERO_2; + } + + bool IsClusteredMethod() const + { + // most of dmg files have non-fused COPY_METHOD blocks. + // so we don't exclude COPY_METHOD blocks when we try to detect size of cluster. + return !IsZeroMethod(); // include COPY_METHOD blocks. + // Type > METHOD_ZERO_2; // for debug: exclude COPY_METHOD blocks. + } - bool IsZeroMethod() const { return Type == METHOD_ZERO_0 || Type == METHOD_ZERO_2; } - bool ThereAreDataInBlock() const { return Type != METHOD_COMMENT && Type != METHOD_END; } + bool NeedAllocateBuffer() const + { + return +#ifdef Z7_DMG_USE_CACHE_FOR_COPY_BLOCKS + !IsZeroMethod(); +#else + Type > METHOD_ZERO_2; + // !IsZeroMethod() && Type != METHOD_COPY; +#endif + } + // we don't store non-data blocks now + // bool ThereAreDataInBlock() const { return Type != METHOD_COMMENT && Type != METHOD_END; } }; static const UInt32 kCheckSumType_CRC = 2; - -static const size_t kChecksumSize_Max = 0x80; +static const unsigned kChecksumSize_Max = 0x80; struct CChecksum { @@ -153,6 +137,11 @@ struct CChecksum bool IsCrc32() const { return Type == kCheckSumType_CRC && NumBits == 32; } UInt32 GetCrc32() const { return Get32(Data); } void Parse(const Byte *p); + + void PrintType(AString &s) const; + void Print(AString &s) const; + void Print_with_Name(AString &s) const; + void AddToComment(AString &s, const char *name) const; }; void CChecksum::Parse(const Byte *p) @@ -160,18 +149,109 @@ void CChecksum::Parse(const Byte *p) Type = Get32(p); NumBits = Get32(p + 4); memcpy(Data, p + 8, kChecksumSize_Max); -}; +} + + +void CChecksum::PrintType(AString &s) const +{ + if (NumBits == 0) + return; + if (IsCrc32()) + s += "CRC"; + else + { + s += "Checksum"; + s.Add_UInt32(Type); + s.Add_Minus(); + s.Add_UInt32(NumBits); + } +} + +void CChecksum::Print(AString &s) const +{ + if (NumBits == 0) + return; + char temp[kChecksumSize_Max * 2 + 2]; + /* + if (IsCrc32()) + ConvertUInt32ToHex8Digits(GetCrc32(), temp); + else + */ + { + UInt32 numBits = kChecksumSize_Max * 8; + if (numBits > NumBits) + numBits = NumBits; + const unsigned numBytes = (numBits + 7) >> 3; + if (numBytes <= 8) + ConvertDataToHex_Upper(temp, Data, numBytes); + else + ConvertDataToHex_Lower(temp, Data, numBytes); + } + s += temp; +} + +void CChecksum::Print_with_Name(AString &s) const +{ + if (NumBits == 0) + return; + PrintType(s); + s += ": "; + Print(s); +} + +static void AddToComment_Prop(AString &s, const char *name, const char *val) +{ + s += name; + s += ": "; + s += val; + s.Add_LF(); +} + +void CChecksum::AddToComment(AString &s, const char *name) const +{ + AString s2; + Print_with_Name(s2); + if (!s2.IsEmpty()) + AddToComment_Prop(s, name, s2); +} + struct CFile { UInt64 Size; + CRecordVector Blocks; UInt64 PackSize; - UInt64 StartPos; + UInt64 StartPackPos; + UInt64 BlockSize_MAX; + UInt64 StartUnpackSector; // unpack sector position of this file from all files + UInt64 NumUnpackSectors; + Int32 Descriptor; + bool IsCorrect; + bool FullFileChecksum; AString Name; - CRecordVector Blocks; + // AString Id; CChecksum Checksum; - bool FullFileChecksum; + UInt64 GetUnpackSize_of_Block(unsigned blockIndex) const + { + return (blockIndex == Blocks.Size() - 1 ? + Size : Blocks[blockIndex + 1].UnpPos) - Blocks[blockIndex].UnpPos; + } + + CFile(): + Size(0), + PackSize(0), + StartPackPos(0), + BlockSize_MAX(0), + StartUnpackSector(0), + NumUnpackSectors(0), + Descriptor(0), + IsCorrect(false), + FullFileChecksum(false) + { + Checksum.Type = 0; + Checksum.NumBits = 0; + } HRESULT Parse(const Byte *p, UInt32 size); }; @@ -183,93 +263,144 @@ struct CExtraFile }; #endif -class CHandler: - public IInArchive, - public IInArchiveGetStream, - public CMyUnknownImp + +static void AddToComment_UInt64(AString &s, UInt64 v, const char *name) +{ + s += name; + s += ": "; + s.Add_UInt64(v); + s.Add_LF(); +} + +struct CForkPair +{ + UInt64 Offset; + UInt64 Len; + + // (p) is aligned for 8-bytes + void Parse(const Byte *p) + { + Offset = Get64a(p); + Len = Get64a(p + 8); + } + + bool GetEndPos(UInt64 &endPos) + { + endPos = Offset + Len; + return endPos >= Offset; + } + + bool UpdateTop(UInt64 limit, UInt64 &top) + { + if (Offset > limit || Len > limit - Offset) + return false; + const UInt64 top2 = Offset + Len; + if (top <= top2) + top = top2; + return true; + } + + void Print(AString &s, const char *name) const; +}; + +void CForkPair::Print(AString &s, const char *name) const { + if (Offset != 0 || Len != 0) + { + s += name; s.Add_Minus(); AddToComment_UInt64(s, Offset, "offset"); + s += name; s.Add_Minus(); AddToComment_UInt64(s, Len, "length"); + } +} + + +Z7_CLASS_IMP_CHandler_IInArchive_1( + IInArchiveGetStream +) + bool _masterCrcError; + bool _headersError; + bool _dataForkError; + bool _rsrcMode_wasUsed; + CMyComPtr _inStream; CObjectVector _files; - bool _masterCrcError; + // UInt32 _dataStartOffset; UInt64 _startPos; UInt64 _phySize; - - #ifdef DMG_SHOW_RAW + + AString _name; + + CForkPair _dataForkPair; + CForkPair rsrcPair, xmlPair, blobPair; + + // UInt64 _runningDataForkOffset; + // UInt32 _segmentNumber; + // UInt32 _segmentCount; + UInt64 _numSectors; // total unpacked number of sectors + Byte _segmentGUID[16]; + + CChecksum _dataForkChecksum; + CChecksum _masterChecksum; + +#ifdef DMG_SHOW_RAW CObjectVector _extras; - #endif +#endif - HRESULT Open2(IInStream *stream); + HRESULT ReadData(IInStream *stream, const CForkPair &pair, CByteBuffer &buf); + bool ParseBlob(const CByteBuffer &data); + HRESULT Open2(IInStream *stream, IArchiveOpenCallback *openArchiveCallback); HRESULT Extract(IInStream *stream); -public: - MY_UNKNOWN_IMP2(IInArchive, IInArchiveGetStream) - INTERFACE_IInArchive(;) - STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream); }; -// that limit can be increased, if there are such dmg files -static const size_t kXmlSizeMax = 0xFFFF0000; // 4 GB - 64 KB; struct CMethods { CRecordVector Types; - CRecordVector ChecksumTypes; void Update(const CFile &file); - void GetString(AString &s) const; + void AddToString(AString &s) const; }; void CMethods::Update(const CFile &file) { - ChecksumTypes.AddToUniqueSorted(file.Checksum.Type); FOR_VECTOR (i, file.Blocks) + { + if (Types.Size() >= (1 << 8)) + break; Types.AddToUniqueSorted(file.Blocks[i].Type); + } } -void CMethods::GetString(AString &res) const +void CMethods::AddToString(AString &res) const { - res.Empty(); - - unsigned i; - - for (i = 0; i < Types.Size(); i++) + FOR_VECTOR (i, Types) { - UInt32 type = Types[i]; + const UInt32 type = Types[i]; + /* if (type == METHOD_COMMENT || type == METHOD_END) continue; + */ char buf[16]; const char *s; switch (type) { + // case METHOD_COMMENT: s = "Comment"; break; case METHOD_ZERO_0: s = "Zero0"; break; case METHOD_ZERO_2: s = "Zero2"; break; case METHOD_COPY: s = "Copy"; break; case METHOD_ADC: s = "ADC"; break; case METHOD_ZLIB: s = "ZLIB"; break; case METHOD_BZIP2: s = "BZip2"; break; - default: ConvertUInt32ToString(type, buf); s = buf; - } - res.Add_Space_if_NotEmpty(); - res += s; - } - - for (i = 0; i < ChecksumTypes.Size(); i++) - { - UInt32 type = ChecksumTypes[i]; - char buf[32]; - const char *s; - switch (type) - { - case kCheckSumType_CRC: s = "CRC"; break; - default: - ConvertUInt32ToString(type, MyStpCpy(buf, "Check")); - s = buf; + case METHOD_LZFSE: s = "LZFSE"; break; + case METHOD_XZ: s = "XZ"; break; + default: ConvertUInt32ToHex(type, buf); s = buf; } - res.Add_Space_if_NotEmpty(); - res += s; + res.Add_OptSpaced(s); } } + + struct CAppleName { bool IsFs; @@ -282,25 +413,78 @@ static const CAppleName k_Names[] = { true, "hfs", "Apple_HFS" }, { true, "hfsx", "Apple_HFSX" }, { true, "ufs", "Apple_UFS" }, + { true, "apfs", "Apple_APFS" }, + { true, "iso", "Apple_ISO" }, + + // efi_sys partition is FAT32, but it's not main file. So we use (IsFs = false) + { false, "efi_sys", "C12A7328-F81F-11D2-BA4B-00A0C93EC93B" }, + { false, "free", "Apple_Free" }, { false, "ddm", "DDM" }, { false, NULL, "Apple_partition_map" }, { false, NULL, " GPT " }, + /* " GPT " is substring of full name entry in dmg that contains + some small metadata GPT entry. It's not real FS entry: + "Primary GPT Header", + "Primary GPT Table" + "Backup GPT Header", + "Backup GPT Table", + */ { false, NULL, "MBR" }, { false, NULL, "Driver" }, { false, NULL, "Patches" } }; -static const unsigned kNumAppleNames = ARRAY_SIZE(k_Names); +static const unsigned kNumAppleNames = Z7_ARRAY_SIZE(k_Names); + +const char *Find_Apple_FS_Ext(const AString &name); +const char *Find_Apple_FS_Ext(const AString &name) +{ + for (unsigned i = 0; i < kNumAppleNames; i++) + { + const CAppleName &a = k_Names[i]; + if (a.Ext) + if (name.IsEqualTo(a.AppleName)) + return a.Ext; + } + return NULL; +} + + +bool Is_Apple_FS_Or_Unknown(const AString &name); +bool Is_Apple_FS_Or_Unknown(const AString &name) +{ + for (unsigned i = 0; i < kNumAppleNames; i++) + { + const CAppleName &a = k_Names[i]; + // if (name.Find(a.AppleName) >= 0) + if (strstr(name, a.AppleName)) + return a.IsFs; + } + return true; +} + + + +// define it for debug only: +// #define Z7_DMG_SINGLE_FILE_MODE static const Byte kProps[] = { kpidPath, kpidSize, kpidPackSize, - kpidCRC, kpidComment, - kpidMethod + kpidMethod, + kpidNumBlocks, + kpidClusterSize, + kpidChecksum, + kpidCRC, + kpidId + // kpidOffset +#ifdef Z7_DMG_SINGLE_FILE_MODE + , kpidPosition +#endif }; IMP_IInArchive_Props @@ -308,10 +492,13 @@ IMP_IInArchive_Props static const Byte kArcProps[] = { kpidMethod, - kpidNumBlocks + kpidNumBlocks, + kpidClusterSize, + kpidComment }; -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) + +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NWindows::NCOM::CPropVariant prop; @@ -320,10 +507,33 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) case kpidMethod: { CMethods m; - FOR_VECTOR (i, _files) - m.Update(_files[i]); + CRecordVector ChecksumTypes; + { + FOR_VECTOR (i, _files) + { + const CFile &file = _files[i]; + m.Update(file); + if (ChecksumTypes.Size() < (1 << 8)) + ChecksumTypes.AddToUniqueSorted(file.Checksum.Type); + } + } AString s; - m.GetString(s); + m.AddToString(s); + + FOR_VECTOR (i, ChecksumTypes) + { + const UInt32 type = ChecksumTypes[i]; + switch (type) + { + case kCheckSumType_CRC: + s.Add_OptSpaced("CRC"); + break; + default: + s.Add_OptSpaced("Checksum"); + s.Add_UInt32(type); + } + } + if (!s.IsEmpty()) prop = s; break; @@ -336,45 +546,90 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) prop = numBlocks; break; } + case kpidClusterSize: + { + UInt64 blockSize_MAX = 0; + FOR_VECTOR (i, _files) + { + const UInt64 a = _files[i].BlockSize_MAX; + if (blockSize_MAX < a) + blockSize_MAX = a; + } + prop = blockSize_MAX; + break; + } case kpidMainSubfile: { int mainIndex = -1; - unsigned numFS = 0; - unsigned numUnknown = 0; FOR_VECTOR (i, _files) { - const AString &name = _files[i].Name; - unsigned n; - for (n = 0; n < kNumAppleNames; n++) + if (Is_Apple_FS_Or_Unknown(_files[i].Name)) { - const CAppleName &appleName = k_Names[n]; - // if (name.Find(appleName.AppleName) >= 0) - if (strstr(name, appleName.AppleName)) + if (mainIndex != -1) { - if (appleName.IsFs) - { - numFS++; - mainIndex = i; - } + mainIndex = -1; break; } - } - if (n == kNumAppleNames) - { - mainIndex = i; - numUnknown++; + mainIndex = (int)i; } } - if (numFS + numUnknown == 1) - prop = (UInt32)mainIndex; + if (mainIndex != -1) + prop = (UInt32)(Int32)mainIndex; break; } case kpidWarning: if (_masterCrcError) prop = "Master CRC error"; break; + + case kpidWarningFlags: + { + UInt32 v = 0; + if (_headersError) v |= kpv_ErrorFlags_HeadersError; + if (_dataForkError) v |= kpv_ErrorFlags_CrcError; + if (v != 0) + prop = v; + break; + } + case kpidOffset: prop = _startPos; break; case kpidPhySize: prop = _phySize; break; + + case kpidComment: + { + AString s; + if (!_name.IsEmpty()) + AddToComment_Prop(s, "Name", _name); + AddToComment_UInt64(s, _numSectors << 9, "unpack-size"); + { + char temp[sizeof(_segmentGUID) * 2 + 2]; + ConvertDataToHex_Lower(temp, _segmentGUID, sizeof(_segmentGUID)); + AddToComment_Prop(s, "ID", temp); + } + _masterChecksum.AddToComment(s, "master-checksum"); + _dataForkChecksum.AddToComment(s, "pack-checksum"); + { + /* + if (_dataStartOffset != 0) + AddToComment_UInt64(s, _dataStartOffset, "payload-start-offset"); + */ + // if (_dataForkPair.Offset != 0) + _dataForkPair.Print(s, "pack"); + rsrcPair.Print(s, "rsrc"); + xmlPair.Print(s, "xml"); + blobPair.Print(s, "blob"); + } + if (_rsrcMode_wasUsed) + s += "RSRC_MODE\n"; + if (!s.IsEmpty()) + prop = s; + } + break; + + case kpidName: + if (!_name.IsEmpty()) + prop = _name + ".dmg"; + break; } prop.Detach(value); return S_OK; @@ -383,39 +638,64 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) IMP_IInArchive_ArcProps + + +static const UInt64 kSectorNumber_LIMIT = (UInt64)1 << (63 - 9); + HRESULT CFile::Parse(const Byte *p, UInt32 size) { - const UInt32 kHeadSize = 0xCC; + // CFile was initialized to default values: 0 in size variables and (IsCorrect == false) + const unsigned kHeadSize = 0xCC; if (size < kHeadSize) return S_FALSE; if (Get32(p) != 0x6D697368) // "mish" signature return S_FALSE; if (Get32(p + 4) != 1) // version return S_FALSE; - // UInt64 firstSectorNumber = Get64(p + 8); - UInt64 numSectors = Get64(p + 0x10); - - StartPos = Get64(p + 0x18); - - // UInt32 decompressedBufRequested = Get32(p + 0x20); // ??? - // UInt32 blocksDescriptor = Get32(p + 0x24); // number starting from -1? + + StartUnpackSector = Get64(p + 8); + NumUnpackSectors = Get64(p + 0x10); + StartPackPos = Get64(p + 0x18); + +#ifdef SHOW_DEBUG_INFO + /* the number of sectors that must be allocated. + == 0x208 for 256KB clusters + == 0x808 for 1MB clusters + == 0x1001 for 1MB clusters in some example + */ + const UInt32 decompressedBufRequested = Get32(p + 0x20); +#endif + + // Descriptor is file index. usually started from -1 + // in one dmg it was started from 0 + Descriptor = (Int32)Get32(p + 0x24); // char Reserved1[24]; Checksum.Parse(p + 0x40); - PRF(printf("\n\nChecksum Type = %2d", Checksum.Type)); - - UInt32 numBlocks = Get32(p + 0xC8); - if (numBlocks > ((UInt32)1 << 28)) + PRF(printf("\n" " Checksum Type = %2u" + "\n StartUnpackSector = %8x" + "\n NumUnpackSectors = %8x" + "\n StartPos = %8x" + "\n decompressedBufRequested=%8x" + "\n blocksDescriptor=%8x" + , (unsigned)Checksum.Type + , (unsigned)StartUnpackSector + , (unsigned)NumUnpackSectors + , (unsigned)StartPackPos + , (unsigned)decompressedBufRequested + , (unsigned)Descriptor + );) + + const UInt32 numBlocks = Get32(p + 0xC8); + const unsigned kRecordSize = 40; + if ((UInt64)numBlocks * kRecordSize + kHeadSize != size) return S_FALSE; - const UInt32 kRecordSize = 40; - if (numBlocks * kRecordSize + kHeadSize != size) - return S_FALSE; - - PackSize = 0; - Size = 0; Blocks.ClearAndReserve(numBlocks); FullFileChecksum = true; + /* IsCorrect = false; by default + So we return S_OK, if we can ignore some error in headers. + */ p += kHeadSize; UInt32 i; @@ -424,69 +704,109 @@ HRESULT CFile::Parse(const Byte *p, UInt32 size) { CBlock b; b.Type = Get32(p); - b.UnpPos = Get64(p + 0x08) << 9; - b.UnpSize = Get64(p + 0x10) << 9; + { + const UInt64 a = Get64(p + 0x08); + if (a >= kSectorNumber_LIMIT) + return S_OK; + b.UnpPos = a << 9; + } + UInt64 unpSize; + { + const UInt64 a = Get64(p + 0x10); + if (a >= kSectorNumber_LIMIT) + return S_OK; + unpSize = a << 9; + } + const UInt64 newSize = b.UnpPos + unpSize; + if (newSize >= ((UInt64)1 << 63)) + return S_OK; + b.PackPos = Get64(p + 0x18); b.PackSize = Get64(p + 0x20); - // b.PackPos can be 0 for some types. So we don't check it - if (!Blocks.IsEmpty()) - if (b.UnpPos != Blocks.Back().GetNextUnpPos()) - return S_FALSE; + if (b.UnpPos != Size) + return S_OK; - PRF(printf("\nType=%8x m[1]=%8x uPos=%8x uSize=%7x pPos=%8x pSize=%7x", - b.Type, Get32(p + 4), (UInt32)b.UnpPos, (UInt32)b.UnpSize, (UInt32)b.PackPos, (UInt32)b.PackSize)); + PRF(printf("\nType=%8x comment=%8x uPos=%8x uSize=%7x pPos=%8x pSize=%7x", + (unsigned)b.Type, Get32(p + 4), (unsigned)b.UnpPos, (unsigned)unpSize, (unsigned)b.PackPos, (unsigned)b.PackSize)); if (b.Type == METHOD_COMMENT) + { + // some files contain 2 comment block records: + // record[0] : Type=METHOD_COMMENT, comment_field = "+beg" + // record[num-2] : Type=METHOD_COMMENT, comment_field = "+end" + // we skip these useless records. continue; + } if (b.Type == METHOD_END) break; - PackSize += b.PackSize; - - if (b.UnpSize != 0) + + // we add only blocks that have non empty unpacked data: + if (unpSize != 0) { - if (b.Type == METHOD_ZERO_2) + const UInt64 k_max_pos = (UInt64)1 << 63; + if (b.PackPos >= k_max_pos || + b.PackSize >= k_max_pos - b.PackPos) + return S_OK; + + /* we don't count non-ZERO blocks here, because + ZERO blocks in dmg files are not limited by some cluster size. + note: COPY blocks also sometimes are fused to larger blocks. + */ + if (b.IsClusteredMethod()) + if (BlockSize_MAX < unpSize) + BlockSize_MAX = unpSize; + + PackSize += b.PackSize; + if (!b.NeedCrc()) FullFileChecksum = false; Blocks.AddInReserved(b); + Size = newSize; } } - + + PRF(printf("\n");) + if (i != numBlocks - 1) - return S_FALSE; - if (!Blocks.IsEmpty()) - Size = Blocks.Back().GetNextUnpPos(); - if (Size != (numSectors << 9)) - return S_FALSE; - + { + // return S_FALSE; + return S_OK; + } + + if ((Size >> 9) == NumUnpackSectors) + IsCorrect = true; return S_OK; } -static int FindKeyPair(const CXmlItem &item, const AString &key, const AString &nextTag) + +static const CXmlItem *FindKeyPair(const CXmlItem &item, const char *key, const char *nextTag) { for (unsigned i = 0; i + 1 < item.SubItems.Size(); i++) { const CXmlItem &si = item.SubItems[i]; - if (si.IsTagged("key") && si.GetSubString() == key && item.SubItems[i + 1].IsTagged(nextTag)) - return i + 1; + if (si.IsTagged("key") && si.GetSubString().IsEqualTo(key)) + { + const CXmlItem *si_1 = &item.SubItems[i + 1]; + if (si_1->IsTagged(nextTag)) + return si_1; + } } - return -1; + return NULL; } -static const AString *GetStringFromKeyPair(const CXmlItem &item, const AString &key, const AString &nextTag) +static const AString *GetStringFromKeyPair(const CXmlItem &item, const char *key, const char *nextTag) { - int index = FindKeyPair(item, key, nextTag); - if (index >= 0) - return item.SubItems[index].GetSubStringPtr(); + const CXmlItem *si_1 = FindKeyPair(item, key, nextTag); + if (si_1) + return si_1->GetSubStringPtr(); return NULL; } -static const unsigned HEADER_SIZE = 0x200; - static const Byte k_Signature[] = { 'k','o','l','y', 0, 0, 0, 4, 0, 0, 2, 0 }; static inline bool IsKoly(const Byte *p) { - return memcmp(p, k_Signature, ARRAY_SIZE(k_Signature)) == 0; + return memcmp(p, k_Signature, Z7_ARRAY_SIZE(k_Signature)) == 0; /* if (Get32(p) != 0x6B6F6C79) // "koly" signature return false; @@ -498,226 +818,457 @@ static inline bool IsKoly(const Byte *p) */ } -HRESULT CHandler::Open2(IInStream *stream) + +HRESULT CHandler::ReadData(IInStream *stream, const CForkPair &pair, CByteBuffer &buf) +{ + size_t size = (size_t)pair.Len; + if (size != pair.Len) + return E_OUTOFMEMORY; + buf.Alloc(size); + RINOK(InStream_SeekSet(stream, _startPos + pair.Offset)) + return ReadStream_FALSE(stream, buf, size); +} + + + +bool CHandler::ParseBlob(const CByteBuffer &data) +{ + const unsigned kHeaderSize = 3 * 4; + if (data.Size() < kHeaderSize) + return false; + const Byte * const p = (const Byte *)data; + if (Get32a(p) != 0xfade0cc0) // CSMAGIC_EMBEDDED_SIGNATURE + return true; + const UInt32 size = Get32a(p + 4); + if (size != data.Size()) + return false; + const UInt32 num = Get32a(p + 8); + if (num > (size - kHeaderSize) / 8) + return false; + + const UInt32 limit = num * 8 + kHeaderSize; + for (size_t i = kHeaderSize; i < limit; i += 8) + { + // type == 0 == CSSLOT_CODEDIRECTORY for CSMAGIC_CODEDIRECTORY item + // UInt32 type = Get32(p + i); + const UInt32 offset = Get32a(p + i + 4); + if (offset < limit || offset > size - 8) + return false; + // offset is not aligned for 4 here !!! + const Byte * const p2 = p + offset; + const UInt32 magic = Get32(p2); + const UInt32 len = Get32(p2 + 4); + if (size - offset < len || len < 8) + return false; + +#ifdef DMG_SHOW_RAW + CExtraFile &extra = _extras.AddNew(); + extra.Name = "_blob_"; + extra.Data.CopyFrom(p2, len); +#endif + + if (magic == 0xfade0c02) // CSMAGIC_CODEDIRECTORY + { +#ifdef DMG_SHOW_RAW + extra.Name += "codedir"; +#endif + + if (len < 11 * 4) + return false; + const UInt32 idOffset = Get32(p2 + 5 * 4); + if (idOffset >= len) + return false; + UInt32 len2 = len - idOffset; + const UInt32 kNameLenMax = 1 << 8; + if (len2 > kNameLenMax) + len2 = kNameLenMax; + _name.SetFrom_CalcLen((const char *)(p2 + idOffset), len2); + /* + // #define kSecCodeSignatureHashSHA1 1 + // #define kSecCodeSignatureHashSHA256 2 + const UInt32 hashOffset = Get32(p2 + 4 * 4); + const UInt32 nSpecialSlots = Get32(p2 + 6 * 4); + const UInt32 nCodeSlots = Get32(p2 + 7 * 4); + const unsigned hashSize = p2[36]; + const unsigned hashType = p2[37]; + // const unsigned unused = p2[38]; + const unsigned pageSize = p2[39]; + */ + } +#ifdef DMG_SHOW_RAW + else if (magic == 0xfade0c01) extra.Name += "requirements"; + else if (magic == 0xfade0b01) extra.Name += "signed"; + else + { + char temp[16]; + ConvertUInt32ToHex8Digits(magic, temp); + extra.Name += temp; + } +#endif + } + + return true; +} + + + + +HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *openArchiveCallback) { - RINOK(stream->Seek(0, STREAM_SEEK_CUR, &_startPos)); + /* + - usual dmg contains Koly Header at the end: + - rare case old dmg contains Koly Header at the begin. + */ + + // _dataStartOffset = 0; + UInt64 fileSize; + RINOK(InStream_GetPos_GetSize(stream, _startPos, fileSize)) - Byte buf[HEADER_SIZE]; - RINOK(ReadStream_FALSE(stream, buf, HEADER_SIZE)); + const unsigned HEADER_SIZE = 0x200; + UInt64 buf[HEADER_SIZE / sizeof(UInt64)]; + RINOK(ReadStream_FALSE(stream, buf, HEADER_SIZE)) UInt64 headerPos; - if (IsKoly(buf)) + bool front_Koly_Mode = false; + + /* + _dataForkChecksum.Offset == 0 for koly-at-the-end + _dataForkChecksum.Offset == 512 for koly-at-the-start + so we can use (_dataForkChecksum.Offset) to detect "koly-at-the-start" mode + */ + + if (IsKoly((const Byte *)(const void *)buf)) + { + // it can be normal koly-at-the-end or koly-at-the-start headerPos = _startPos; + /* + if (_startPos <= (1 << 8)) + { + // we want to support front_Koly_Mode, even if there is + // some data before dmg file, like 128 bytes MacBin header + _dataStartOffset = HEADER_SIZE; + front_Koly_Mode = true; + } + */ + } else { - RINOK(stream->Seek(0, STREAM_SEEK_END, &headerPos)); + /* we try to open in backward mode only for first attempt + when (_startPos == 0) */ + if (_startPos != 0) + return S_FALSE; + headerPos = fileSize; if (headerPos < HEADER_SIZE) return S_FALSE; headerPos -= HEADER_SIZE; - RINOK(stream->Seek(headerPos, STREAM_SEEK_SET, NULL)); - RINOK(ReadStream_FALSE(stream, buf, HEADER_SIZE)); - if (!IsKoly(buf)) + RINOK(InStream_SeekSet(stream, headerPos)) + RINOK(ReadStream_FALSE(stream, buf, HEADER_SIZE)) + if (!IsKoly((const Byte *)(const void *)buf)) return S_FALSE; } - // UInt32 flags = Get32(buf + 12); - // UInt64 runningDataForkOffset = Get64(buf + 0x10); - UInt64 dataForkOffset = Get64(buf + 0x18); - UInt64 dataForkLen = Get64(buf + 0x20); - UInt64 rsrcOffset = Get64(buf + 0x28); - UInt64 rsrcLen = Get64(buf + 0x30); - // UInt32 segmentNumber = Get32(buf + 0x38); - // UInt32 segmentCount = Get32(buf + 0x3C); - // Byte segmentGUID[16]; - // CChecksum dataForkChecksum; - // dataForkChecksum.Parse(buf + 0x50); - UInt64 xmlOffset = Get64(buf + 0xD8); - UInt64 xmlLen = Get64(buf + 0xE0); - - if ( headerPos < dataForkOffset - || headerPos - dataForkOffset < dataForkLen - || headerPos < rsrcOffset - || headerPos - rsrcOffset < rsrcLen - || headerPos < xmlOffset - || headerPos - xmlOffset < xmlLen) - return S_FALSE; - - UInt64 totalLen = dataForkLen + rsrcLen + xmlLen; - if (totalLen > headerPos) - return S_FALSE; - _startPos = headerPos - totalLen; - _phySize = totalLen + HEADER_SIZE; - headerPos = totalLen; + // UInt32 flags = Get32a((const Byte *)(const void *)buf + 12); + // _runningDataForkOffset = Get64a((const Byte *)(const void *)buf + 0x10); + _dataForkPair.Parse((const Byte *)(const void *)buf + 0x18); + rsrcPair.Parse((const Byte *)(const void *)buf + 0x28); + // _segmentNumber = Get32a(buf + 0x38); // 0 or 1 + // _segmentCount = Get32a(buf + 0x3C); // 0 (if not set) or 1 + memcpy(_segmentGUID, (const Byte *)(const void *)buf + 0x40, 16); + _dataForkChecksum.Parse((const Byte *)(const void *)buf + 0x50); + xmlPair.Parse((const Byte *)(const void *)buf + 0xD8); + // Byte resereved[] + blobPair.Parse((const Byte *)(const void *)buf + 0x128); + _masterChecksum.Parse((const Byte *)(const void *)buf + 0x160); + // UInt32 imageVariant = Get32a((const Byte *)(const void *)buf + 0x1E8); imageVariant = imageVariant; + _numSectors = Get64((const Byte *)(const void *)buf + 0x1EC); // it's not aligned for 8-bytes + // Byte resereved[12]; + + if (_dataForkPair.Offset == HEADER_SIZE + && headerPos + HEADER_SIZE < fileSize) + front_Koly_Mode = true; + + const UInt64 limit = front_Koly_Mode ? fileSize : headerPos; + UInt64 top = 0; + if (!_dataForkPair.UpdateTop(limit, top)) return S_FALSE; + if (!xmlPair.UpdateTop(limit, top)) return S_FALSE; + if (!rsrcPair.UpdateTop(limit, top)) return S_FALSE; + + /* Some old dmg files contain garbage data in blobPair field. + So we need to ignore such garbage case; + And we still need to detect offset of start of archive for "parser" mode. */ + const bool useBlob = blobPair.UpdateTop(limit, top); + + if (front_Koly_Mode) + _phySize = top; + else + { + _phySize = headerPos + HEADER_SIZE; + _startPos = 0; + if (top != headerPos) + { + /* + if expected absolute offset is not equal to real header offset, + 2 cases are possible: + - additional (unknown) headers + - archive with offset. + So we try to read XML with absolute offset to select from these two ways. + */ + CForkPair xmlPair2 = xmlPair; + const char *sz = " len) + xmlPair2.Len = len; + CByteBuffer buf2; + if (xmlPair2.Len < len + || ReadData(stream, xmlPair2, buf2) != S_OK + || memcmp(buf2, sz, len) != 0) + { + // if absolute offset is not OK, probably it's archive with offset + _startPos = headerPos - top; + _phySize = top + HEADER_SIZE; + } + } + } - // Byte reserved[0x78] + if (useBlob + && blobPair.Len != 0 + && blobPair.Len <= (1u << 24)) // we don't want parsing of big blobs + { +#ifdef DMG_SHOW_RAW + CExtraFile &extra = _extras.AddNew(); + extra.Name = "_blob.bin"; + CByteBuffer &blobBuf = extra.Data; +#else + CByteBuffer blobBuf; +#endif + RINOK(ReadData(stream, blobPair, blobBuf)) + if (!ParseBlob(blobBuf)) + _headersError = true; + } - CChecksum masterChecksum; - masterChecksum.Parse(buf + 0x160); - // UInt32 imageVariant = Get32(buf + 0x1E8); - // UInt64 numSectors = Get64(buf + 0x1EC); - // Byte reserved[0x12] + UInt64 openTotal = 0; + UInt64 openCur = 0; + if (_dataForkChecksum.IsCrc32()) + openTotal = _dataForkPair.Len; const UInt32 RSRC_HEAD_SIZE = 0x100; - // We don't know the size of the field "offset" in rsrc. - // We suppose that it uses 24 bits. So we use Rsrc, only if the rsrcLen < (1 << 24). - bool useRsrc = (rsrcLen > RSRC_HEAD_SIZE && rsrcLen < ((UInt32)1 << 24)); - // useRsrc = false; - - if (useRsrc) + /* we have 2 ways to read files and blocks metadata: + via Xml or via Rsrc. + But some images have no Rsrc fork. + Is it possible that there is no Xml? + Rsrc method will not work for big files. */ + // v23.02: we use xml mode by default + // if (rsrcPair.Len >= RSRC_HEAD_SIZE && rsrcPair.Len <= ((UInt32)1 << 24)) // for debug + if (xmlPair.Len == 0) { - #ifdef DMG_SHOW_RAW + // We don't know the size of the field "offset" in Rsrc. + // We suppose that it uses 24 bits. So we use Rsrc, only if the rsrcPair.Len <= (1 << 24). + const bool canUseRsrc = (rsrcPair.Len >= RSRC_HEAD_SIZE && rsrcPair.Len <= ((UInt32)1 << 24)); + if (!canUseRsrc) + return S_FALSE; + + _rsrcMode_wasUsed = true; +#ifdef DMG_SHOW_RAW CExtraFile &extra = _extras.AddNew(); extra.Name = "rsrc.bin"; CByteBuffer &rsrcBuf = extra.Data; - #else +#else CByteBuffer rsrcBuf; - #endif +#endif - size_t rsrcLenT = (size_t)rsrcLen; - rsrcBuf.Alloc(rsrcLenT); - RINOK(stream->Seek(_startPos + rsrcOffset, STREAM_SEEK_SET, NULL)); - RINOK(ReadStream_FALSE(stream, rsrcBuf, rsrcLenT)); + RINOK(ReadData(stream, rsrcPair, rsrcBuf)) const Byte *p = rsrcBuf; - UInt32 headSize = Get32(p + 0); - UInt32 footerOffset = Get32(p + 4); - UInt32 mainDataSize = Get32(p + 8); - UInt32 footerSize = Get32(p + 12); - if (headSize != RSRC_HEAD_SIZE || - footerOffset >= rsrcLenT || - mainDataSize >= rsrcLenT || - footerOffset + footerSize != rsrcLenT || - footerOffset != headSize + mainDataSize) + const UInt32 headSize = Get32a(p + 0); + const UInt32 footerOffset = Get32a(p + 4); + const UInt32 mainDataSize = Get32a(p + 8); + const UInt32 footerSize = Get32a(p + 12); + if (headSize != RSRC_HEAD_SIZE + || footerOffset >= rsrcPair.Len + || mainDataSize >= rsrcPair.Len + || footerOffset < mainDataSize + || footerOffset != headSize + mainDataSize) return S_FALSE; - if (footerSize < 16) + + { + const UInt32 footerEnd = footerOffset + footerSize; + if (footerEnd != rsrcPair.Len) + { + // there is rare case dmg example, where there are 4 additional bytes + const UInt64 rem = rsrcPair.Len - footerOffset; + if (rem < footerSize + || rem - footerSize != 4 + || Get32(p + footerEnd) != 0) + return S_FALSE; + } + } + + if (footerSize < 0x1e) return S_FALSE; if (memcmp(p, p + footerOffset, 16) != 0) return S_FALSE; p += footerOffset; - if ((UInt32)Get16(p + 0x18) != 0x1C) + if ((UInt32)Get16(p + 0x18) != 0x1c) return S_FALSE; - UInt32 namesOffset = Get16(p + 0x1A); + const UInt32 namesOffset = Get16(p + 0x1a); if (namesOffset > footerSize) return S_FALSE; - UInt32 numItems = (UInt32)Get16(p + 0x1C) + 1; - if (numItems * 8 + 0x1E > namesOffset) + const UInt32 numItems = (UInt32)Get16(p + 0x1c) + 1; + if (numItems * 8 + 0x1e > namesOffset) return S_FALSE; for (UInt32 i = 0; i < numItems; i++) { - const Byte *p2 = p + 0x1E + i * 8; - - UInt32 typeId = Get32(p2); - if (typeId != 0x626C6B78) // blkx + const Byte *p2 = p + 0x1e + (size_t)i * 8; + const UInt32 typeId = Get32(p2); + const UInt32 k_typeId_blkx = 0x626c6b78; // blkx +#ifndef DMG_SHOW_RAW + if (typeId != k_typeId_blkx) continue; - - UInt32 numFiles = (UInt32)Get16(p2 + 4) + 1; - UInt32 offs = Get16(p2 + 6); - if (0x1C + offs + 12 * numFiles > namesOffset) +#endif + const UInt32 numFiles = (UInt32)Get16(p2 + 4) + 1; + const UInt32 offs = Get16(p2 + 6); + if (0x1c + offs + 12 * numFiles > namesOffset) return S_FALSE; for (UInt32 k = 0; k < numFiles; k++) { - const Byte *p3 = p + 0x1C + offs + k * 12; + const Byte *p3 = p + 0x1c + offs + k * 12; // UInt32 id = Get16(p3); - UInt32 namePos = Get16(p3 + 2); - // Byte attributes = p3[4]; // = 0x50 for blkx + const UInt32 namePos = Get16(p3 + 2); + // Byte attributes = p3[4]; // = 0x50 for blkx #define (ATTRIBUTE_HDIUTIL) // we don't know how many bits we can use. So we use 24 bits only UInt32 blockOffset = Get32(p3 + 4); - blockOffset &= (((UInt32)1 << 24) - 1); + blockOffset &= ((UInt32)1 << 24) - 1; // UInt32 unknown2 = Get32(p3 + 8); // ??? if (blockOffset + 4 >= mainDataSize) return S_FALSE; const Byte *pBlock = rsrcBuf + headSize + blockOffset; - UInt32 blockSize = Get32(pBlock); - - #ifdef DMG_SHOW_RAW - { - CExtraFile &extra = _extras.AddNew(); - { - char extraName[16]; - ConvertUInt32ToString(_files.Size(), extraName); - extra.Name = extraName; - } - extra.Data.CopyFrom(pBlock + 4, blockSize); - } - #endif + const UInt32 blockSize = Get32(pBlock); + if (mainDataSize - (blockOffset + 4) < blockSize) + return S_FALSE; + + AString name; - CFile &file = _files.AddNew(); - if (namePos != 0xFFFF) + if (namePos != 0xffff) { - UInt32 namesBlockSize = footerSize - namesOffset; + const UInt32 namesBlockSize = footerSize - namesOffset; if (namePos >= namesBlockSize) return S_FALSE; const Byte *namePtr = p + namesOffset + namePos; - UInt32 nameLen = *namePtr; + const UInt32 nameLen = *namePtr; if (namesBlockSize - namePos <= nameLen) return S_FALSE; for (UInt32 r = 1; r <= nameLen; r++) { - Byte c = namePtr[r]; + const Byte c = namePtr[r]; if (c < 0x20 || c >= 0x80) break; - file.Name += (char)c; + name += (char)c; + } + } + + if (typeId == k_typeId_blkx) + { + CFile &file = _files.AddNew(); + file.Name = name; + RINOK(file.Parse(pBlock + 4, blockSize)) + if (!file.IsCorrect) + _headersError = true; + } + +#ifdef DMG_SHOW_RAW + { + AString name2; + name2.Add_UInt32(i); + name2 += '_'; + { + char temp[4 + 1] = { 0 }; + memcpy(temp, p2, 4); + name2 += temp; + } + name2.Trim(); + name2 += '_'; + name2.Add_UInt32(k); + if (!name.IsEmpty()) + { + name2 += '_'; + name2 += name; } + CExtraFile &extra2 = _extras.AddNew(); + extra2.Name = name2; + extra2.Data.CopyFrom(pBlock + 4, blockSize); } - RINOK(file.Parse(pBlock + 4, blockSize)); +#endif } } } else { - if (xmlLen >= kXmlSizeMax || xmlLen == 0) - return S_FALSE; - size_t size = (size_t)xmlLen; - if (size != xmlLen) + if (xmlPair.Len > k_XmlSize_MAX) return S_FALSE; - - RINOK(stream->Seek(_startPos + dataForkLen, STREAM_SEEK_SET, NULL)); - + // if (!canUseXml) return S_FALSE; + const size_t size = (size_t)xmlPair.Len; + // if (size + 1 <= xmlPair.Len) return S_FALSE; // optional check + RINOK(InStream_SeekSet(stream, _startPos + xmlPair.Offset)) CXml xml; { - CObjArray xmlStr(size + 1); - RINOK(ReadStream_FALSE(stream, xmlStr, size)); + openTotal += size; + if (openArchiveCallback) + { + RINOK(openArchiveCallback->SetTotal(NULL, &openTotal)) + } + size_t pos = 0; + CAlignedBuffer1 xmlStr(size + 1); + for (;;) + { + const size_t k_OpenStep = 1 << 24; + const size_t cur = MyMin(k_OpenStep, size - pos); + RINOK(ReadStream_FALSE(stream, xmlStr + pos, cur)) + pos += cur; + openCur += cur; + if (pos == size) + break; + if (openArchiveCallback) + { + RINOK(openArchiveCallback->SetCompleted(NULL, &openCur)) + } + } xmlStr[size] = 0; - // if (strlen(xmlStr) != size) return S_FALSE; - if (!xml.Parse(xmlStr)) + // if (strlen((const char *)(const void *)(const Byte *)xmlStr) != size) return S_FALSE; + if (!xml.Parse((char *)(void *)(Byte *)xmlStr)) return S_FALSE; - #ifdef DMG_SHOW_RAW +#ifdef DMG_SHOW_RAW CExtraFile &extra = _extras.AddNew(); extra.Name = "a.xml"; - extra.Data.CopyFrom((const Byte *)(const char *)xmlStr, size); - #endif + extra.Data.CopyFrom(xmlStr, size); +#endif } - if (xml.Root.Name != "plist") + if (!xml.Root.Name.IsEqualTo("plist")) return S_FALSE; - int dictIndex = xml.Root.FindSubTag("dict"); - if (dictIndex < 0) + const CXmlItem *dictItem = xml.Root.FindSubTag_GetPtr("dict"); + if (!dictItem) return S_FALSE; - const CXmlItem &dictItem = xml.Root.SubItems[dictIndex]; - int rfDictIndex = FindKeyPair(dictItem, "resource-fork", "dict"); - if (rfDictIndex < 0) + const CXmlItem *rfDictItem = FindKeyPair(*dictItem, "resource-fork", "dict"); + if (!rfDictItem) return S_FALSE; - const CXmlItem &rfDictItem = dictItem.SubItems[rfDictIndex]; - int arrIndex = FindKeyPair(rfDictItem, "blkx", "array"); - if (arrIndex < 0) + const CXmlItem *arrItem = FindKeyPair(*rfDictItem, "blkx", "array"); + if (!arrItem) return S_FALSE; - const CXmlItem &arrItem = rfDictItem.SubItems[arrIndex]; - - FOR_VECTOR (i, arrItem.SubItems) + FOR_VECTOR (i, arrItem->SubItems) { - const CXmlItem &item = arrItem.SubItems[i]; + const CXmlItem &item = arrItem->SubItems[i]; if (!item.IsTagged("dict")) continue; @@ -733,97 +1284,183 @@ HRESULT CHandler::Open2(IInStream *stream) const Byte *endPtr = Base64ToBin(rawBuf, *dataString); if (!endPtr) return S_FALSE; - destLen = (unsigned)(endPtr - rawBuf); + destLen = (unsigned)(endPtr - (const Byte *)rawBuf); } - #ifdef DMG_SHOW_RAW +#ifdef DMG_SHOW_RAW CExtraFile &extra = _extras.AddNew(); - { - char extraName[16]; - ConvertUInt32ToString(_files.Size(), extraName); - extra.Name = extraName; - } + extra.Name.Add_UInt32(_files.Size()); extra.Data.CopyFrom(rawBuf, destLen); - #endif +#endif } CFile &file = _files.AddNew(); { + /* xml code removes front space for such string: + (Apple_Free : 3) + maybe we shoud fix xml code and return full string with space. + */ const AString *name = GetStringFromKeyPair(item, "Name", "string"); if (!name || name->IsEmpty()) name = GetStringFromKeyPair(item, "CFName", "string"); if (name) file.Name = *name; } - RINOK(file.Parse(rawBuf, destLen)); - } - } - - if (masterChecksum.IsCrc32()) - { - UInt32 crc = CRC_INIT_VAL; - unsigned i; - for (i = 0; i < _files.Size(); i++) - { - const CChecksum &cs = _files[i].Checksum; - if ((cs.NumBits & 0x7) != 0) - break; - UInt32 len = cs.NumBits >> 3; - if (len > kChecksumSize_Max) - break; - crc = CrcUpdate(crc, cs.Data, (size_t)len); + /* + { + const AString *s = GetStringFromKeyPair(item, "ID", "string"); + if (s) + file.Id = *s; + } + */ + RINOK(file.Parse(rawBuf, destLen)) + if (!file.IsCorrect) + _headersError = true; + } + } + + if (_masterChecksum.IsCrc32()) + { + UInt32 crc = CRC_INIT_VAL; + unsigned i; + for (i = 0; i < _files.Size(); i++) + { + const CChecksum &cs = _files[i].Checksum; + if ((cs.NumBits & 0x7) != 0) + break; + const UInt32 len = cs.NumBits >> 3; + if (len > kChecksumSize_Max) + break; + crc = CrcUpdate(crc, cs.Data, (size_t)len); + } + if (i == _files.Size()) + _masterCrcError = (CRC_GET_DIGEST(crc) != _masterChecksum.GetCrc32()); + } + + { + UInt64 sec = 0; + FOR_VECTOR (i, _files) + { + const CFile &file = _files[i]; + /* + if (file.Descriptor != (Int32)i - 1) + _headersError = true; + */ + if (file.StartUnpackSector != sec) + _headersError = true; + if (file.NumUnpackSectors >= kSectorNumber_LIMIT) + _headersError = true; + sec += file.NumUnpackSectors; + if (sec >= kSectorNumber_LIMIT) + _headersError = true; + } + if (sec != _numSectors) + _headersError = true; + } + + // data checksum calculation can be slow for big dmg file + if (_dataForkChecksum.IsCrc32()) + { + UInt64 endPos; + if (!_dataForkPair.GetEndPos(endPos) + || _dataForkPair.Offset >= ((UInt64)1 << 63)) + _headersError = true; + else + { + const UInt64 seekPos = _startPos + _dataForkPair.Offset; + if (seekPos > fileSize + || endPos > fileSize - _startPos) + { + _headersError = true; + // kpv_ErrorFlags_UnexpectedEnd + } + else + { + const size_t kBufSize = 1 << 15; + CAlignedBuffer1 buf2(kBufSize); + RINOK(InStream_SeekSet(stream, seekPos)) + if (openArchiveCallback) + { + RINOK(openArchiveCallback->SetTotal(NULL, &openTotal)) + } + UInt32 crc = CRC_INIT_VAL; + UInt64 pos = 0; + for (;;) + { + const UInt64 rem = _dataForkPair.Len - pos; + size_t cur = kBufSize; + if (cur > rem) + cur = (UInt32)rem; + if (cur == 0) + break; + RINOK(ReadStream_FALSE(stream, buf2, cur)) + crc = CrcUpdate(crc, buf2, cur); + pos += cur; + openCur += cur; + if ((pos & ((1 << 24) - 1)) == 0 && openArchiveCallback) + { + RINOK(openArchiveCallback->SetCompleted(NULL, &openCur)) + } + } + if (_dataForkChecksum.GetCrc32() != CRC_GET_DIGEST(crc)) + _dataForkError = true; + } } - if (i == _files.Size()) - _masterCrcError = (CRC_GET_DIGEST(crc) != masterChecksum.GetCrc32()); } return S_OK; } -STDMETHODIMP CHandler::Open(IInStream *stream, + + +Z7_COM7F_IMF(CHandler::Open(IInStream *stream, const UInt64 * /* maxCheckStartPosition */, - IArchiveOpenCallback * /* openArchiveCallback */) + IArchiveOpenCallback *openArchiveCallback)) { COM_TRY_BEGIN - { - Close(); - if (Open2(stream) != S_OK) - return S_FALSE; - _inStream = stream; - } + Close(); + RINOK(Open2(stream, openArchiveCallback)) + _inStream = stream; return S_OK; COM_TRY_END } -STDMETHODIMP CHandler::Close() +Z7_COM7F_IMF(CHandler::Close()) { + _masterCrcError = false; + _headersError = false; + _dataForkError = false; + _rsrcMode_wasUsed = false; _phySize = 0; + _startPos = 0; + _name.Empty(); _inStream.Release(); _files.Clear(); - _masterCrcError = false; - #ifdef DMG_SHOW_RAW +#ifdef DMG_SHOW_RAW _extras.Clear(); - #endif +#endif return S_OK; } -STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) { *numItems = _files.Size() - #ifdef DMG_SHOW_RAW +#ifdef DMG_SHOW_RAW + _extras.Size() - #endif - ; +#endif + ; return S_OK; } +#ifdef DMG_SHOW_RAW #define RAW_PREFIX "raw" STRING_PATH_SEPARATOR +#endif -STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NWindows::NCOM::CPropVariant prop; - #ifdef DMG_SHOW_RAW +#ifdef DMG_SHOW_RAW if (index >= _files.Size()) { const CExtraFile &extra = _extras[index - _files.Size()]; @@ -839,7 +1476,7 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val } } else - #endif +#endif { const CFile &item = _files[index]; switch (propID) @@ -852,13 +1489,44 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val prop = item.Checksum.GetCrc32(); break; } + case kpidChecksum: + { + AString s; + item.Checksum.Print(s); + if (!s.IsEmpty()) + prop = s; + break; + } + + /* + case kpidOffset: + { + prop = item.StartPackPos; + break; + } + */ + + case kpidNumBlocks: + prop = (UInt32)item.Blocks.Size(); + break; + case kpidClusterSize: + prop = item.BlockSize_MAX; + break; case kpidMethod: { + AString s; + if (!item.IsCorrect) + s.Add_OptSpaced("CORRUPTED"); CMethods m; m.Update(item); - AString s; - m.GetString(s); + m.AddToString(s); + { + AString s2; + item.Checksum.PrintType(s2); + if (!s2.IsEmpty()) + s.Add_OptSpaced(s2); + } if (!s.IsEmpty()) prop = s; break; @@ -866,10 +1534,11 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val case kpidPath: { +#ifdef Z7_DMG_SINGLE_FILE_MODE + prop = "a.img"; +#else UString name; - wchar_t s[16]; - ConvertUInt32ToString(index, s); - name = s; + name.Add_UInt32(index); unsigned num = 10; unsigned numDigits; for (numDigits = 1; num < _files.Size(); numDigits++) @@ -882,7 +1551,7 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val if (pos1 >= 0) { pos1++; - int pos2 = item.Name.Find(')', pos1); + const int pos2 = item.Name.Find(')', pos1); if (pos2 >= 0) { subName.SetFrom(item.Name.Ptr(pos1), pos2 - pos1); @@ -891,24 +1560,17 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val subName.DeleteFrom(pos1); } } + else + subName = item.Name; // new apfs dmg can be without braces subName.Trim(); if (!subName.IsEmpty()) { - for (unsigned n = 0; n < kNumAppleNames; n++) - { - const CAppleName &appleName = k_Names[n]; - if (appleName.Ext) - { - if (subName == appleName.AppleName) - { - subName = appleName.Ext; - break; - } - } - } + const char *ext = Find_Apple_FS_Ext(subName); + if (ext) + subName = ext; UString name2; ConvertUTF8ToUnicode(subName, name2); - name += L'.'; + name.Add_Dot(); name += name2; } else @@ -916,10 +1578,12 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val UString name2; ConvertUTF8ToUnicode(item.Name, name2); if (!name2.IsEmpty()) - name.AddAscii(" - "); + name += "_"; name += name2; } prop = name; +#endif + break; } @@ -930,6 +1594,24 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val prop = name; break; } + case kpidId: + { + prop.Set_Int32((Int32)item.Descriptor); + /* + if (!item.Id.IsEmpty()) + { + UString s; + ConvertUTF8ToUnicode(item.Id, s); + prop = s; + } + */ + break; + } +#ifdef Z7_DMG_SINGLE_FILE_MODE + case kpidPosition: + prop = item.StartUnpackSector << 9; + break; +#endif } } prop.Detach(value); @@ -937,22 +1619,13 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val COM_TRY_END } -class CAdcDecoder: - public ICompressCoder, - public CMyUnknownImp + +class CAdcDecoder { CLzOutWindow m_OutWindowStream; CInBuffer m_InStream; - /* - void ReleaseStreams() - { - m_OutWindowStream.ReleaseStream(); - m_InStream.ReleaseStream(); - } - */ - - class CCoderReleaser + class CCoderReleaser Z7_final { CAdcDecoder *m_Coder; public: @@ -962,28 +1635,28 @@ class CAdcDecoder: { if (NeedFlush) m_Coder->m_OutWindowStream.Flush(); - // m_Coder->ReleaseStreams(); } }; friend class CCoderReleaser; public: - MY_UNKNOWN_IMP - - STDMETHOD(CodeReal)(ISequentialInStream *inStream, - ISequentialOutStream *outStream, const UInt64 *inSize, const UInt64 *outSize, - ICompressProgressInfo *progress); - - STDMETHOD(Code)(ISequentialInStream *inStream, - ISequentialOutStream *outStream, const UInt64 *inSize, const UInt64 *outSize, - ICompressProgressInfo *progress); + HRESULT Code(ISequentialInStream * const inStream, + ISequentialOutStream *outStream, + const UInt64 * const inSize, + const UInt64 * const outSize, + ICompressProgressInfo * const progress); }; -STDMETHODIMP CAdcDecoder::CodeReal(ISequentialInStream *inStream, - ISequentialOutStream *outStream, const UInt64 *inSize, const UInt64 *outSize, - ICompressProgressInfo *progress) + +HRESULT CAdcDecoder::Code(ISequentialInStream * const inStream, + ISequentialOutStream *outStream, + const UInt64 * const inSize, + const UInt64 * const outSizePtr, + ICompressProgressInfo * const progress) { - if (!m_OutWindowStream.Create(1 << 18)) + try { + + if (!m_OutWindowStream.Create(1 << 18)) // at least (1 << 16) is required here return E_OUTOFMEMORY; if (!m_InStream.Create(1 << 18)) return E_OUTOFMEMORY; @@ -995,86 +1668,139 @@ STDMETHODIMP CAdcDecoder::CodeReal(ISequentialInStream *inStream, CCoderReleaser coderReleaser(this); - const UInt32 kStep = (1 << 20); + const UInt32 kStep = 1 << 22; UInt64 nextLimit = kStep; - + const UInt64 outSize = *outSizePtr; UInt64 pos = 0; - while (pos < *outSize) + /* match sequences and literal sequences do not cross 64KB range + in some dmg archive examples. But is it so for any Adc stream? */ + + while (pos < outSize) { - if (pos > nextLimit && progress) + if (pos >= nextLimit && progress) { - UInt64 packSize = m_InStream.GetProcessedSize(); - RINOK(progress->SetRatioInfo(&packSize, &pos)); nextLimit += kStep; + const UInt64 packSize = m_InStream.GetProcessedSize(); + RINOK(progress->SetRatioInfo(&packSize, &pos)) } Byte b; if (!m_InStream.ReadByte(b)) return S_FALSE; - UInt64 rem = *outSize - pos; + const UInt64 rem = outSize - pos; if (b & 0x80) { - unsigned num = (b & 0x7F) + 1; + unsigned num = (unsigned)b - 0x80 + 1; if (num > rem) return S_FALSE; - for (unsigned i = 0; i < num; i++) + pos += num; + do { if (!m_InStream.ReadByte(b)) return S_FALSE; m_OutWindowStream.PutByte(b); } - pos += num; + while (--num); continue; } Byte b1; if (!m_InStream.ReadByte(b1)) return S_FALSE; - UInt32 len, distance; + UInt32 len, dist; if (b & 0x40) { - len = ((UInt32)b & 0x3F) + 4; + len = (UInt32)b - 0x40 + 4; Byte b2; if (!m_InStream.ReadByte(b2)) return S_FALSE; - distance = ((UInt32)b1 << 8) + b2; + dist = ((UInt32)b1 << 8) + b2; } else { - b &= 0x3F; len = ((UInt32)b >> 2) + 3; - distance = (((UInt32)b & 3) << 8) + b1; + dist = (((UInt32)b & 3) << 8) + b1; } - if (distance >= pos || len > rem) + if (/* dist >= pos || */ len > rem) + return S_FALSE; + if (!m_OutWindowStream.CopyBlock(dist, len)) return S_FALSE; - m_OutWindowStream.CopyBlock(distance, len); pos += len; } if (*inSize != m_InStream.GetProcessedSize()) return S_FALSE; coderReleaser.NeedFlush = false; return m_OutWindowStream.Flush(); -} -STDMETHODIMP CAdcDecoder::Code(ISequentialInStream *inStream, - ISequentialOutStream *outStream, const UInt64 *inSize, const UInt64 *outSize, - ICompressProgressInfo *progress) -{ - try { return CodeReal(inStream, outStream, inSize, outSize, progress);} + } catch(const CInBufferException &e) { return e.ErrorCode; } catch(const CLzOutWindowException &e) { return e.ErrorCode; } catch(...) { return S_FALSE; } } -STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, - Int32 testMode, IArchiveExtractCallback *extractCallback) + +struct CDecoders +{ + CMyComPtr2 zlib; + CMyComPtr2 bzip2; + CMyComPtr2 lzfse; + CMyUniquePtr xz; + CMyUniquePtr adc; + + HRESULT Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, + const CBlock &block, const UInt64 *unpSize, ICompressProgressInfo *progress); +}; + +HRESULT CDecoders::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, + const CBlock &block, const UInt64 *unpSize, ICompressProgressInfo *progress) +{ + HRESULT hres; + UInt64 processed; + switch (block.Type) + { + case METHOD_ADC: + adc.Create_if_Empty(); + return adc->Code(inStream, outStream, &block.PackSize, unpSize, progress); + case METHOD_LZFSE: + lzfse.Create_if_Empty(); + return lzfse.Interface()->Code(inStream, outStream, &block.PackSize, unpSize, progress); + case METHOD_ZLIB: + zlib.Create_if_Empty(); + hres = zlib.Interface()->Code(inStream, outStream, NULL, unpSize, progress); + processed = zlib->GetInputProcessedSize(); + break; + case METHOD_BZIP2: + bzip2.Create_if_Empty(); + hres = bzip2.Interface()->Code(inStream, outStream, NULL, unpSize, progress); + processed = bzip2->GetInputProcessedSize(); + break; + case METHOD_XZ: + xz.Create_if_Empty(); + hres = xz->Decode(inStream, outStream, unpSize, true, progress); + processed = xz->Stat.InSize; + break; + default: + return E_NOTIMPL; + } + if (hres == S_OK && processed != block.PackSize) + hres = S_FALSE; + return hres; +} + + +Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testMode, IArchiveExtractCallback *extractCallback)) { COM_TRY_BEGIN - bool allFilesMode = (numItems == (UInt32)(Int32)-1); + const bool allFilesMode = (numItems == (UInt32)(Int32)-1); if (allFilesMode) - numItems = _files.Size(); + numItems = _files.Size() +#ifdef DMG_SHOW_RAW + + _extras.Size() +#endif + ; if (numItems == 0) return S_OK; UInt64 totalSize = 0; @@ -1082,277 +1808,260 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, for (i = 0; i < numItems; i++) { - UInt32 index = (allFilesMode ? i : indices[i]); - #ifdef DMG_SHOW_RAW + const UInt32 index = allFilesMode ? i : indices[i]; +#ifdef DMG_SHOW_RAW if (index >= _files.Size()) totalSize += _extras[index - _files.Size()].Data.Size(); else - #endif +#endif totalSize += _files[index].Size; } - extractCallback->SetTotal(totalSize); + RINOK(extractCallback->SetTotal(totalSize)) - UInt64 currentPackTotal = 0; - UInt64 currentUnpTotal = 0; - UInt64 currentPackSize = 0; - UInt64 currentUnpSize = 0; - - const UInt32 kZeroBufSize = (1 << 14); - CByteBuffer zeroBuf(kZeroBufSize); + const size_t kZeroBufSize = 1 << 14; + CAlignedBuffer1 zeroBuf(kZeroBufSize); memset(zeroBuf, 0, kZeroBufSize); - NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder(); - CMyComPtr copyCoder = copyCoderSpec; - - NCompress::NBZip2::CDecoder *bzip2CoderSpec = new NCompress::NBZip2::CDecoder(); - CMyComPtr bzip2Coder = bzip2CoderSpec; - - NCompress::NZlib::CDecoder *zlibCoderSpec = new NCompress::NZlib::CDecoder(); - CMyComPtr zlibCoder = zlibCoderSpec; - - CAdcDecoder *adcCoderSpec = new CAdcDecoder(); - CMyComPtr adcCoder = adcCoderSpec; - - CLocalProgress *lps = new CLocalProgress; - CMyComPtr progress = lps; + CDecoders decoders; + CMyComPtr2_Create copyCoder; + CMyComPtr2_Create lps; lps->Init(extractCallback, false); + CMyComPtr2_Create inStream; + inStream->SetStream(_inStream); - CLimitedSequentialInStream *streamSpec = new CLimitedSequentialInStream; - CMyComPtr inStream(streamSpec); - streamSpec->SetStream(_inStream); + UInt64 total_PackSize = 0; + UInt64 total_UnpackSize = 0; + UInt64 cur_PackSize = 0; + UInt64 cur_UnpackSize = 0; - for (i = 0; i < numItems; i++, currentPackTotal += currentPackSize, currentUnpTotal += currentUnpSize) + for (i = 0;; i++, + total_PackSize += cur_PackSize, + total_UnpackSize += cur_UnpackSize) { - lps->InSize = currentPackTotal; - lps->OutSize = currentUnpTotal; - currentPackSize = 0; - currentUnpSize = 0; - RINOK(lps->SetCur()); + lps->InSize = total_PackSize; + lps->OutSize = total_UnpackSize; + cur_PackSize = 0; + cur_UnpackSize = 0; + RINOK(lps->SetCur()) + if (i >= numItems) + return S_OK; + + Int32 opRes = NExtract::NOperationResult::kOK; + { CMyComPtr realOutStream; - Int32 askMode = testMode ? + const Int32 askMode = testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; - UInt32 index = allFilesMode ? i : indices[i]; - RINOK(extractCallback->GetStream(index, &realOutStream, askMode)); + const UInt32 index = allFilesMode ? i : indices[i]; + RINOK(extractCallback->GetStream(index, &realOutStream, askMode)) if (!testMode && !realOutStream) continue; - RINOK(extractCallback->PrepareOperation(askMode)); - - - COutStreamWithCRC *outCrcStreamSpec = new COutStreamWithCRC; - CMyComPtr outCrcStream = outCrcStreamSpec; - outCrcStreamSpec->SetStream(realOutStream); - bool needCrc = false; - outCrcStreamSpec->Init(needCrc); - - CLimitedSequentialOutStream *outStreamSpec = new CLimitedSequentialOutStream; - CMyComPtr outStream(outStreamSpec); - outStreamSpec->SetStream(outCrcStream); - - realOutStream.Release(); + RINOK(extractCallback->PrepareOperation(askMode)) - Int32 opRes = NExtract::NOperationResult::kOK; - #ifdef DMG_SHOW_RAW +#ifdef DMG_SHOW_RAW if (index >= _files.Size()) { const CByteBuffer &buf = _extras[index - _files.Size()].Data; - outStreamSpec->Init(buf.Size()); - RINOK(WriteStream(outStream, buf, buf.Size())); - currentPackSize = currentUnpSize = buf.Size(); + if (realOutStream) + RINOK(WriteStream(realOutStream, buf, buf.Size())) + cur_PackSize = cur_UnpackSize = buf.Size(); } else - #endif +#endif { const CFile &item = _files[index]; - currentPackSize = item.PackSize; - currentUnpSize = item.Size; + cur_PackSize = item.PackSize; + cur_UnpackSize = item.Size; - needCrc = item.Checksum.IsCrc32(); - - UInt64 unpPos = 0; - UInt64 packPos = 0; + if (!item.IsCorrect) + opRes = NExtract::NOperationResult::kHeadersError; + else { - FOR_VECTOR (j, item.Blocks) + CMyComPtr2_Create outCrcStream; + outCrcStream->SetStream(realOutStream); + // realOutStream.Release(); + const bool needCrc = item.Checksum.IsCrc32(); + outCrcStream->Init(needCrc); + + CMyComPtr2_Create outStream; + outStream->SetStream(outCrcStream); + + UInt64 unpPos = 0; + UInt64 packPos = 0; + + FOR_VECTOR (blockIndex, item.Blocks) { - lps->InSize = currentPackTotal + packPos; - lps->OutSize = currentUnpTotal + unpPos; - RINOK(lps->SetCur()); + lps->InSize = total_PackSize + packPos; + lps->OutSize = total_UnpackSize + unpPos; + RINOK(lps->SetCur()) - const CBlock &block = item.Blocks[j]; - if (!block.ThereAreDataInBlock()) - continue; + const CBlock &block = item.Blocks[blockIndex]; + // if (!block.ThereAreDataInBlock()) continue; packPos += block.PackSize; if (block.UnpPos != unpPos) { - opRes = NExtract::NOperationResult::kDataError; + opRes = NExtract::NOperationResult::kHeadersError; break; } - RINOK(_inStream->Seek(_startPos + item.StartPos + block.PackPos, STREAM_SEEK_SET, NULL)); - streamSpec->Init(block.PackSize); - bool realMethod = true; - outStreamSpec->Init(block.UnpSize); - HRESULT res = S_OK; + RINOK(InStream_SeekSet(_inStream, _startPos + _dataForkPair.Offset + item.StartPackPos + block.PackPos)) + inStream->Init(block.PackSize); - outCrcStreamSpec->EnableCalc(needCrc); + const UInt64 unpSize = item.GetUnpackSize_of_Block(blockIndex); - switch (block.Type) - { - case METHOD_ZERO_0: - case METHOD_ZERO_2: - realMethod = false; - if (block.PackSize != 0) - opRes = NExtract::NOperationResult::kUnsupportedMethod; - outCrcStreamSpec->EnableCalc(block.Type == METHOD_ZERO_0); - break; + outStream->Init(unpSize); + HRESULT res = S_OK; - case METHOD_COPY: - if (block.UnpSize != block.PackSize) - { - opRes = NExtract::NOperationResult::kUnsupportedMethod; - break; - } - res = copyCoder->Code(inStream, outStream, NULL, NULL, progress); - break; - - case METHOD_ADC: - { - res = adcCoder->Code(inStream, outStream, &block.PackSize, &block.UnpSize, progress); - break; - } - - case METHOD_ZLIB: - { - res = zlibCoder->Code(inStream, outStream, NULL, NULL, progress); - if (res == S_OK) - if (zlibCoderSpec->GetInputProcessedSize() != block.PackSize) - opRes = NExtract::NOperationResult::kDataError; - break; - } + outCrcStream->EnableCalc(needCrc && block.NeedCrc()); - case METHOD_BZIP2: - { - res = bzip2Coder->Code(inStream, outStream, NULL, NULL, progress); - if (res == S_OK) - if (bzip2CoderSpec->GetInputProcessedSize() != block.PackSize) - opRes = NExtract::NOperationResult::kDataError; - break; - } - - default: + if (block.IsZeroMethod()) + { + if (block.PackSize != 0) opRes = NExtract::NOperationResult::kUnsupportedMethod; - break; } + else if (block.Type == METHOD_COPY) + { + if (unpSize != block.PackSize) + opRes = NExtract::NOperationResult::kUnsupportedMethod; + else + res = copyCoder.Interface()->Code(inStream, outStream, NULL, NULL, lps); + } + else + res = decoders.Code(inStream, outStream, block, &unpSize, lps); if (res != S_OK) { if (res != S_FALSE) - return res; + { + if (res != E_NOTIMPL) + return res; + opRes = NExtract::NOperationResult::kUnsupportedMethod; + } if (opRes == NExtract::NOperationResult::kOK) opRes = NExtract::NOperationResult::kDataError; } - unpPos += block.UnpSize; + unpPos += unpSize; - if (!outStreamSpec->IsFinishedOK()) + if (!outStream->IsFinishedOK()) { - if (realMethod && opRes == NExtract::NOperationResult::kOK) + if (!block.IsZeroMethod() && opRes == NExtract::NOperationResult::kOK) opRes = NExtract::NOperationResult::kDataError; - while (outStreamSpec->GetRem() != 0) + for (unsigned k = 0;;) { - UInt64 rem = outStreamSpec->GetRem(); - UInt32 size = (UInt32)MyMin(rem, (UInt64)kZeroBufSize); - RINOK(WriteStream(outStream, zeroBuf, size)); + const UInt64 rem = outStream->GetRem(); + if (rem == 0) + break; + size_t size = kZeroBufSize; + if (size > rem) + size = (size_t)rem; + RINOK(WriteStream(outStream, zeroBuf, size)) + k++; + if ((k & 0xfff) == 0) + { + lps->OutSize = total_UnpackSize + unpPos - outStream->GetRem(); + RINOK(lps->SetCur()) + } } } } - } - - if (needCrc && opRes == NExtract::NOperationResult::kOK) - { - if (outCrcStreamSpec->GetCRC() != item.Checksum.GetCrc32()) - opRes = NExtract::NOperationResult::kCRCError; + if (needCrc && opRes == NExtract::NOperationResult::kOK) + { + if (outCrcStream->GetCRC() != item.Checksum.GetCrc32()) + opRes = NExtract::NOperationResult::kCRCError; + } } } - outStream.Release(); - RINOK(extractCallback->SetOperationResult(opRes)); + } + RINOK(extractCallback->SetOperationResult(opRes)) } - return S_OK; COM_TRY_END } + + + struct CChunk { int BlockIndex; UInt64 AccessMark; - CByteBuffer Buf; + Byte *Buf; + size_t BufSize; + + void Free() + { + z7_AlignedFree(Buf); + Buf = NULL; + BufSize = 0; + } + void Alloc(size_t size) + { + Buf = (Byte *)z7_AlignedAlloc(size); + } }; -class CInStream: - public IInStream, - public CMyUnknownImp -{ + +Z7_CLASS_IMP_IInStream( + CInStream +) + bool _errorMode; UInt64 _virtPos; int _latestChunk; int _latestBlock; UInt64 _accessMark; - CObjectVector _chunks; - - NCompress::NBZip2::CDecoder *bzip2CoderSpec; - CMyComPtr bzip2Coder; - - NCompress::NZlib::CDecoder *zlibCoderSpec; - CMyComPtr zlibCoder; - - CAdcDecoder *adcCoderSpec; - CMyComPtr adcCoder; - - CBufPtrSeqOutStream *outStreamSpec; - CMyComPtr outStream; - - CLimitedSequentialInStream *limitedStreamSpec; - CMyComPtr inStream; + UInt64 _chunks_TotalSize; + CRecordVector _chunks; public: CMyComPtr Stream; - UInt64 Size; const CFile *File; + UInt64 Size; +private: UInt64 _startPos; - HRESULT InitAndSeek(UInt64 startPos) + ~CInStream(); + CMyComPtr2 outStream; + CMyComPtr2 inStream; + CDecoders decoders; +public: + + // HRESULT + void Init(UInt64 startPos) { + _errorMode = false; _startPos = startPos; _virtPos = 0; _latestChunk = -1; _latestBlock = -1; _accessMark = 0; + _chunks_TotalSize = 0; - limitedStreamSpec = new CLimitedSequentialInStream; - inStream = limitedStreamSpec; - limitedStreamSpec->SetStream(Stream); + inStream.Create_if_Empty(); + inStream->SetStream(Stream); - outStreamSpec = new CBufPtrSeqOutStream; - outStream = outStreamSpec; - return S_OK; + outStream.Create_if_Empty(); + // return S_OK; } - - MY_UNKNOWN_IMP1(IInStream) - - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); - STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition); }; -unsigned FindBlock(const CRecordVector &blocks, UInt64 pos) +CInStream::~CInStream() +{ + unsigned i = _chunks.Size(); + while (i) + _chunks[--i].Free(); +} + +static unsigned FindBlock(const CRecordVector &blocks, UInt64 pos) { unsigned left = 0, right = blocks.Size(); for (;;) { - unsigned mid = (left + right) / 2; + const unsigned mid = (left + right) / 2; if (mid == left) return left; if (pos < blocks[mid].UnpPos) @@ -1362,9 +2071,13 @@ unsigned FindBlock(const CRecordVector &blocks, UInt64 pos) } } -STDMETHODIMP CInStream::Read(void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CInStream::Read(void *data, UInt32 size, UInt32 *processedSize)) { - COM_TRY_BEGIN + // COM_TRY_BEGIN + try { + + if (_errorMode) + return E_OUTOFMEMORY; if (processedSize) *processedSize = 0; @@ -1373,151 +2086,176 @@ STDMETHODIMP CInStream::Read(void *data, UInt32 size, UInt32 *processedSize) if (_virtPos >= Size) return S_OK; // (Size == _virtPos) ? S_OK: E_FAIL; { - UInt64 rem = Size - _virtPos; + const UInt64 rem = Size - _virtPos; if (size > rem) size = (UInt32)rem; } if (_latestBlock >= 0) { - const CBlock &block = File->Blocks[_latestBlock]; - if (_virtPos < block.UnpPos || (_virtPos - block.UnpPos) >= block.UnpSize) + const CBlock &block = File->Blocks[(unsigned)_latestBlock]; + if (_virtPos < block.UnpPos || + _virtPos - block.UnpPos >= File->GetUnpackSize_of_Block((unsigned)_latestBlock)) _latestBlock = -1; } if (_latestBlock < 0) { _latestChunk = -1; - unsigned blockIndex = FindBlock(File->Blocks, _virtPos); + const unsigned blockIndex = FindBlock(File->Blocks, _virtPos); const CBlock &block = File->Blocks[blockIndex]; + const UInt64 unpSize = File->GetUnpackSize_of_Block(blockIndex); - if (!block.IsZeroMethod() && block.Type != METHOD_COPY) + if (block.NeedAllocateBuffer() + && unpSize <= k_Chunk_Size_MAX) { - unsigned i; - for (i = 0; i < _chunks.Size(); i++) - if (_chunks[i].BlockIndex == (int)blockIndex) - break; - + unsigned i = 0; + { + unsigned numChunks = _chunks.Size(); + if (numChunks) + { + const CChunk *chunk = _chunks.ConstData(); + do + { + if (chunk->BlockIndex == (int)blockIndex) + break; + chunk++; + } + while (--numChunks); + i = _chunks.Size() - numChunks; + } + } if (i != _chunks.Size()) - _latestChunk = i; + _latestChunk = (int)i; else { - const unsigned kNumChunksMax = 128; unsigned chunkIndex; - - if (_chunks.Size() != kNumChunksMax) - chunkIndex = _chunks.Add(CChunk()); - else + for (;;) { + if (_chunks.IsEmpty() || + (_chunks.Size() < k_NumChunks_MAX + && _chunks_TotalSize + unpSize <= k_Chunks_TotalSize_MAX)) + { + CChunk chunk; + chunk.Buf = NULL; + chunk.BufSize = 0; + chunk.BlockIndex = -1; + chunk.AccessMark = 0; + chunkIndex = _chunks.Add(chunk); + break; + } chunkIndex = 0; - for (i = 0; i < _chunks.Size(); i++) - if (_chunks[i].AccessMark < _chunks[chunkIndex].AccessMark) - chunkIndex = i; + if (_chunks.Size() == 1) + break; + { + const CChunk *chunks = _chunks.ConstData(); + UInt64 accessMark_min = chunks[chunkIndex].AccessMark; + const unsigned numChunks = _chunks.Size(); + for (i = 1; i < numChunks; i++) + { + if (chunks[i].AccessMark < accessMark_min) + { + chunkIndex = i; + accessMark_min = chunks[i].AccessMark; + } + } + } + { + CChunk &chunk = _chunks[chunkIndex]; + const UInt64 newTotalSize = _chunks_TotalSize - chunk.BufSize; + if (newTotalSize + unpSize <= k_Chunks_TotalSize_MAX) + break; + _chunks_TotalSize = newTotalSize; + chunk.Free(); + } + // we have called chunk.Free() before, because + // _chunks.Delete() doesn't call chunk.Free(). + _chunks.Delete(chunkIndex); + PRF(printf("\n++num_chunks=%u, _chunks_TotalSize = %u\n", (unsigned)_chunks.Size(), (unsigned)_chunks_TotalSize);) } CChunk &chunk = _chunks[chunkIndex]; chunk.BlockIndex = -1; chunk.AccessMark = 0; - if (chunk.Buf.Size() < block.UnpSize) + if (chunk.BufSize < unpSize) { - chunk.Buf.Free(); - if (block.UnpSize > ((UInt32)1 << 31)) - return E_FAIL; - chunk.Buf.Alloc((size_t)block.UnpSize); + _chunks_TotalSize -= chunk.BufSize; + chunk.Free(); + // if (unpSize > k_Chunk_Size_MAX) return E_FAIL; + chunk.Alloc((size_t)unpSize); + if (!chunk.Buf) + return E_OUTOFMEMORY; + chunk.BufSize = (size_t)unpSize; + _chunks_TotalSize += chunk.BufSize; } - outStreamSpec->Init(chunk.Buf, (size_t)block.UnpSize); - - RINOK(Stream->Seek(_startPos + File->StartPos + block.PackPos, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekSet(Stream, _startPos + File->StartPackPos + block.PackPos)) - limitedStreamSpec->Init(block.PackSize); - HRESULT res = S_OK; - - switch (block.Type) + inStream->Init(block.PackSize); +#ifdef Z7_DMG_USE_CACHE_FOR_COPY_BLOCKS + if (block.Type == METHOD_COPY) { - case METHOD_COPY: - if (block.PackSize != block.UnpSize) - return E_FAIL; - res = ReadStream_FAIL(inStream, chunk.Buf, (size_t)block.UnpSize); - break; - - case METHOD_ADC: - if (!adcCoder) - { - adcCoderSpec = new CAdcDecoder(); - adcCoder = adcCoderSpec; - } - res = adcCoder->Code(inStream, outStream, &block.PackSize, &block.UnpSize, NULL); - break; - - case METHOD_ZLIB: - if (!zlibCoder) - { - zlibCoderSpec = new NCompress::NZlib::CDecoder(); - zlibCoder = zlibCoderSpec; - } - res = zlibCoder->Code(inStream, outStream, NULL, NULL, NULL); - if (res == S_OK && zlibCoderSpec->GetInputProcessedSize() != block.PackSize) - res = S_FALSE; - break; - - case METHOD_BZIP2: - if (!bzip2Coder) - { - bzip2CoderSpec = new NCompress::NBZip2::CDecoder(); - bzip2Coder = bzip2CoderSpec; - } - res = bzip2Coder->Code(inStream, outStream, NULL, NULL, NULL); - if (res == S_OK && bzip2CoderSpec->GetInputProcessedSize() != block.PackSize) - res = S_FALSE; - break; - - default: + if (block.PackSize != unpSize) return E_FAIL; + RINOK(ReadStream_FAIL(inStream, chunk.Buf, (size_t)unpSize)) } - - if (res != S_OK) - return res; - if (block.Type != METHOD_COPY && outStreamSpec->GetPos() != block.UnpSize) - return E_FAIL; - chunk.BlockIndex = blockIndex; - _latestChunk = chunkIndex; + else +#endif + { + outStream->Init(chunk.Buf, (size_t)unpSize); + RINOK(decoders.Code(inStream, outStream, block, &unpSize, NULL)) + if (outStream->GetPos() != unpSize) + return E_FAIL; + } + chunk.BlockIndex = (int)blockIndex; + _latestChunk = (int)chunkIndex; } _chunks[_latestChunk].AccessMark = _accessMark++; } - _latestBlock = blockIndex; + _latestBlock = (int)blockIndex; } - const CBlock &block = File->Blocks[_latestBlock]; - UInt64 offset = _virtPos - block.UnpPos; - UInt64 rem = block.UnpSize - offset; - if (size > rem) - size = (UInt32)rem; - + const CBlock &block = File->Blocks[(unsigned)_latestBlock]; + const UInt64 offset = _virtPos - block.UnpPos; + { + const UInt64 rem = File->GetUnpackSize_of_Block((unsigned)_latestBlock) - offset; + if (size > rem) + size = (UInt32)rem; + if (size == 0) // it's unexpected case. but we check it. + return S_OK; + } HRESULT res = S_OK; - if (block.Type == METHOD_COPY) + if (block.IsZeroMethod()) + memset(data, 0, size); + else if (_latestChunk >= 0) + memcpy(data, _chunks[_latestChunk].Buf + (size_t)offset, size); + else { - RINOK(Stream->Seek(_startPos + File->StartPos + block.PackPos + offset, STREAM_SEEK_SET, NULL)); + if (block.Type != METHOD_COPY) + return E_FAIL; + RINOK(InStream_SeekSet(Stream, _startPos + File->StartPackPos + block.PackPos + offset)) res = Stream->Read(data, size, &size); } - else if (block.IsZeroMethod()) - memset(data, 0, size); - else if (size != 0) - memcpy(data, _chunks[_latestChunk].Buf + offset, size); _virtPos += size; if (processedSize) *processedSize = size; - return res; - COM_TRY_END + // COM_TRY_END + } + catch(...) + { + _errorMode = true; + return E_OUTOFMEMORY; + } } + -STDMETHODIMP CInStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition) +Z7_COM7F_IMF(CInStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition)) { switch (seekOrigin) { @@ -1528,38 +2266,49 @@ STDMETHODIMP CInStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPositio } if (offset < 0) return HRESULT_WIN32_ERROR_NEGATIVE_SEEK; - _virtPos = offset; + _virtPos = (UInt64)offset; if (newPosition) - *newPosition = offset; + *newPosition = (UInt64)offset; return S_OK; } -STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream) +Z7_COM7F_IMF(CHandler::GetStream(UInt32 index, ISequentialInStream **stream)) { COM_TRY_BEGIN - #ifdef DMG_SHOW_RAW - if (index >= (UInt32)_files.Size()) +#ifdef DMG_SHOW_RAW + if (index >= _files.Size()) return S_FALSE; - #endif +#endif - CInStream *spec = new CInStream; - CMyComPtr specStream = spec; + CMyComPtr2 spec; + spec.Create_if_Empty(); spec->File = &_files[index]; const CFile &file = *spec->File; + + if (!file.IsCorrect) + return S_FALSE; FOR_VECTOR (i, file.Blocks) { const CBlock &block = file.Blocks[i]; + if (!block.NeedAllocateBuffer()) + continue; + switch (block.Type) { - case METHOD_ZERO_0: - case METHOD_ZERO_2: +#ifdef Z7_DMG_USE_CACHE_FOR_COPY_BLOCKS case METHOD_COPY: + break; +#endif case METHOD_ADC: case METHOD_ZLIB: case METHOD_BZIP2: - case METHOD_END: + case METHOD_LZFSE: + case METHOD_XZ: + // case METHOD_END: + if (file.GetUnpackSize_of_Block(i) > k_Chunk_Size_MAX) + return S_FALSE; break; default: return S_FALSE; @@ -1568,15 +2317,16 @@ STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream) spec->Stream = _inStream; spec->Size = spec->File->Size; - RINOK(spec->InitAndSeek(_startPos)); - *stream = specStream.Detach(); + // RINOK( + spec->Init(_startPos + _dataForkPair.Offset); + *stream = spec.Detach(); return S_OK; COM_TRY_END } REGISTER_ARC_I( - "Dmg", "dmg", 0, 0xE4, + "Dmg", "dmg", NULL, 0xE4, k_Signature, 0, NArcInfoFlags::kBackwardOpen | diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/ElfHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/ElfHandler.cpp index 4543b067..df229950 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/ElfHandler.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/ElfHandler.cpp @@ -17,11 +17,13 @@ #include "../Compress/CopyCoder.h" +// #define Z7_ELF_SHOW_DETAILS + using namespace NWindows; -static UInt16 Get16(const Byte *p, bool be) { if (be) return GetBe16(p); return GetUi16(p); } -static UInt32 Get32(const Byte *p, bool be) { if (be) return GetBe32(p); return GetUi32(p); } -static UInt64 Get64(const Byte *p, bool be) { if (be) return GetBe64(p); return GetUi64(p); } +static UInt16 Get16(const Byte *p, bool be) { if (be) return GetBe16a(p); return GetUi16a(p); } +static UInt32 Get32(const Byte *p, bool be) { if (be) return GetBe32a(p); return GetUi32a(p); } +static UInt64 Get64(const Byte *p, bool be) { if (be) return GetBe64a(p); return GetUi64a(p); } #define G16(offs, v) v = Get16(p + (offs), be) #define G32(offs, v) v = Get32(p + (offs), be) @@ -31,14 +33,51 @@ namespace NArchive { namespace NElf { /* - ELF Structure for most files (real order can be different): - Header - Program (segment) header table (used at runtime) - Segment1 (Section ... Section) - Segment2 - ... - SegmentN - Section header table (the data for linking and relocation) +ELF Structure example: +{ + Header + Program header table (is used at runtime) (list of segment metadata records) + { + Segment (Read) + Segment : PT_PHDR : header table itself + Segment : PT_INTERP + Segment : PT_NOTE + .rela.dyn (RELA, ALLOC) + Segment (Execute/Read) + .text section (PROGBITS, SHF_ALLOC | SHF_EXECINSTR) + Segment (Read) + .rodata (PROGBITS, SHF_ALLOC | SHF_WRITE) + Segment : PT_GNU_EH_FRAME + .eh_frame_hdr + .eh_frame + .gcc_except_table + ... + Segment (Write/Read) (VaSize > Size) + Segment (Read) : PT_GNU_RELRO + Segment (Write/Read) + .data + .bss (Size == 0) (VSize != 0) + } + .comment (VA == 0) + .shstrtab (VA == 0) + Section header table (the data for linking and relocation) +} + + Last top level segment contains .bss section that requires additional VA space. + So (VaSize > Size) for that segment. + + Segments can be unsorted (by offset) in table. + Top level segments has Type=PT_LOAD : "Loadable segment". + Top level segments usually are aligned for page size (4 KB). + Another segments (non PT_LOAD segments) are inside PT_LOAD segments. + + (VA-offset == 0) is possible for some sections and segments at the beginning of file. + (VA-offset == 4KB*N) for most sections and segments where (Size != 0), + (VA-offset != 4KB*N) for .bss section (last section), because (Size == 0), + and that section is not mapped from image file. + Some files contain additional "virtual" 4 KB page in VA space after + end of data of top level segments (PT_LOAD) before new top level segments. + So (VA-offset) value can increase by 4 KB step. */ #define ELF_CLASS_32 1 @@ -47,14 +86,14 @@ namespace NElf { #define ELF_DATA_2LSB 1 #define ELF_DATA_2MSB 2 -static const UInt32 kHeaderSize32 = 0x34; -static const UInt32 kHeaderSize64 = 0x40; +static const unsigned kHeaderSize32 = 0x34; +static const unsigned kHeaderSize64 = 0x40; -static const UInt32 kSegmentSize32 = 0x20; -static const UInt32 kSegmentSize64 = 0x38; +static const unsigned kSegmentSize32 = 0x20; +static const unsigned kSegmentSize64 = 0x38; -static const UInt32 kSectionSize32 = 0x28; -static const UInt32 kSectionSize64 = 0x40; +static const unsigned kSectionSize32 = 0x28; +static const unsigned kSectionSize64 = 0x40; struct CHeader { @@ -78,9 +117,9 @@ struct CHeader UInt16 NumSections; UInt16 NamesSectIndex; - bool Parse(const Byte *buf); + bool Parse(const Byte *p); - UInt64 GetHeadersSize() const { return (UInt64)HeaderSize + + UInt32 GetHeadersSize() const { return (UInt32)HeaderSize + (UInt32)NumSegments * SegmentEntrySize + (UInt32)NumSections * SectionEntrySize; } }; @@ -104,7 +143,7 @@ bool CHeader::Parse(const Byte *p) if (p[6] != 1) // Version return false; Os = p[7]; - AbiVer = p[8]; + // AbiVer = p[8]; for (int i = 9; i < 16; i++) if (p[i] != 0) return false; @@ -117,16 +156,21 @@ bool CHeader::Parse(const Byte *p) if (Mode64) { // G64(0x18, EntryVa); - G64(0x20, ProgOffset); + G64(0x20, ProgOffset); // == kHeaderSize64 == 0x40 usually G64(0x28, SectOffset); p += 0x30; + // we expect that fields are aligned + if (ProgOffset & 7) return false; + if (SectOffset & 7) return false; } else { // G32(0x18, EntryVa); - G32(0x1C, ProgOffset); + G32(0x1C, ProgOffset); // == kHeaderSize32 == 0x34 usually G32(0x20, SectOffset); p += 0x24; + if (ProgOffset & 3) return false; + if (SectOffset & 3) return false; } G32(0, Flags); @@ -140,39 +184,43 @@ bool CHeader::Parse(const Byte *p) G16(12, NumSections); G16(14, NamesSectIndex); - if (ProgOffset < HeaderSize && (ProgOffset != 0 || NumSegments != 0)) return false; - if (SectOffset < HeaderSize && (SectOffset != 0 || NumSections != 0)) return false; + if (ProgOffset < HeaderSize && (ProgOffset || NumSegments)) return false; + if (SectOffset < HeaderSize && (SectOffset || NumSections)) return false; - if (SegmentEntrySize == 0) { if (NumSegments != 0) return false; } + if (SegmentEntrySize == 0) { if (NumSegments) return false; } else if (SegmentEntrySize != (Mode64 ? kSegmentSize64 : kSegmentSize32)) return false; - if (SectionEntrySize == 0) { if (NumSections != 0) return false; } + if (SectionEntrySize == 0) { if (NumSections) return false; } else if (SectionEntrySize != (Mode64 ? kSectionSize64 : kSectionSize32)) return false; return true; } -// The program header table itself. -#define PT_PHDR 6 +#define PT_PHDR 6 // The program header table itself. +#define PT_GNU_STACK 0x6474e551 -static const char * const g_SegnmentTypes[] = +static const CUInt32PCharPair g_SegnmentTypes[] = { - "Unused", - "Loadable segment", - "Dynamic linking tables", - "Program interpreter path name", - "Note section", - "SHLIB", - "Program header table", - "TLS" + { 0, "Unused" }, + { 1, "Loadable segment" }, + { 2, "Dynamic linking tables" }, + { 3, "Program interpreter path name" }, + { 4, "Note section" }, + { 5, "SHLIB" }, + { 6, "Program header table" }, + { 7, "TLS" }, + { 0x6474e550, "GNU_EH_FRAME" }, + { PT_GNU_STACK, "GNU_STACK" }, + { 0x6474e552, "GNU_RELRO" } }; -static const CUInt32PCharPair g_SegmentFlags[] = + +static const char * const g_SegmentFlags[] = { - { 0, "Execute" }, - { 1, "Write" }, - { 2, "Read" } + "Execute" + , "Write" + , "Read" }; struct CSegment @@ -181,16 +229,18 @@ struct CSegment UInt32 Flags; UInt64 Offset; UInt64 Va; - // UInt64 Pa; - UInt64 Size; - UInt64 VSize; - UInt64 Align; - + UInt64 Size; // size in file + UInt64 VSize; // size in memory +#ifdef Z7_ELF_SHOW_DETAILS + UInt64 Pa; // usually == Va, or == 0 + UInt64 Align; // if (Align != 0), condition must be met: + // (VSize % Align == Offset % Alig) +#endif void UpdateTotalSize(UInt64 &totalSize) { - UInt64 t = Offset + Size; + const UInt64 t = Offset + Size; if (totalSize < t) - totalSize = t; + totalSize = t; } void Parse(const Byte *p, bool mode64, bool be); }; @@ -203,20 +253,24 @@ void CSegment::Parse(const Byte *p, bool mode64, bool be) G32(4, Flags); G64(8, Offset); G64(0x10, Va); - // G64(0x18, Pa); G64(0x20, Size); G64(0x28, VSize); +#ifdef Z7_ELF_SHOW_DETAILS + G64(0x18, Pa); G64(0x30, Align); +#endif } else { G32(4, Offset); G32(8, Va); - // G32(0x0C, Pa); G32(0x10, Size); G32(0x14, VSize); G32(0x18, Flags); +#ifdef Z7_ELF_SHOW_DETAILS + G32(0x0C, Pa); G32(0x1C, Align); +#endif } } @@ -226,6 +280,7 @@ void CSegment::Parse(const Byte *p, bool mode64, bool be) // Section types +/* #define SHT_NULL 0 #define SHT_PROGBITS 1 #define SHT_SYMTAB 2 @@ -234,7 +289,9 @@ void CSegment::Parse(const Byte *p, bool mode64, bool be) #define SHT_HASH 5 #define SHT_DYNAMIC 6 #define SHT_NOTE 7 +*/ #define SHT_NOBITS 8 +/* #define SHT_REL 9 #define SHT_SHLIB 10 #define SHT_DYNSYM 11 @@ -245,7 +302,7 @@ void CSegment::Parse(const Byte *p, bool mode64, bool be) #define SHT_PREINIT_ARRAY 16 #define SHT_GROUP 17 #define SHT_SYMTAB_SHNDX 18 - +*/ static const CUInt32PCharPair g_SectTypes[] = { @@ -268,6 +325,7 @@ static const CUInt32PCharPair g_SectTypes[] = { 16, "PREINIT_ARRAY" }, { 17, "GROUP" }, { 18, "SYMTAB_SHNDX" }, + { 0x6ffffff5, "GNU_ATTRIBUTES" }, { 0x6ffffff6, "GNU_HASH" }, { 0x6ffffffd, "GNU_verdef" }, @@ -281,6 +339,8 @@ static const CUInt32PCharPair g_SectTypes[] = { 0x70000005, "ARM_OVERLAYSECTION" } }; + +// SHF_ flags static const CUInt32PCharPair g_SectionFlags[] = { { 0, "WRITE" }, @@ -294,7 +354,7 @@ static const CUInt32PCharPair g_SectionFlags[] = { 8, "OS_NONCONFORMING" }, { 9, "GROUP" }, { 10, "TLS" }, - { 11, "CP_SECTION" }, + { 11, "COMPRESSED" }, { 12, "DP_SECTION" }, { 13, "XCORE_SHF_CP_SECTION" }, { 28, "64_LARGE" }, @@ -317,9 +377,9 @@ struct CSection void UpdateTotalSize(UInt64 &totalSize) { - UInt64 t = Offset + GetSize(); + const UInt64 t = Offset + GetSize(); if (totalSize < t) - totalSize = t; + totalSize = t; } bool Parse(const Byte *p, bool mode64, bool be); }; @@ -359,174 +419,218 @@ bool CSection::Parse(const Byte *p, bool mode64, bool be) return true; } -static const CUInt32PCharPair g_Machines[] = + +static const char * const g_Machines[] = { - { 0, "None" }, - { 1, "AT&T WE 32100" }, - { 2, "SPARC" }, - { 3, "Intel 386" }, - { 4, "Motorola 68000" }, - { 5, "Motorola 88000" }, - { 6, "Intel 486" }, - { 7, "Intel i860" }, - { 8, "MIPS" }, - { 9, "IBM S/370" }, - { 10, "MIPS RS3000 LE" }, - { 11, "RS6000" }, - - { 15, "PA-RISC" }, - { 16, "nCUBE" }, - { 17, "Fujitsu VPP500" }, - { 18, "SPARC 32+" }, - { 19, "Intel i960" }, - { 20, "PowerPC" }, - { 21, "PowerPC 64-bit" }, - { 22, "IBM S/390" }, - { 23, "SPU" }, - - { 36, "NEX v800" }, - { 37, "Fujitsu FR20" }, - { 38, "TRW RH-32" }, - { 39, "Motorola RCE" }, - { 40, "ARM" }, - { 41, "Alpha" }, - { 42, "Hitachi SH" }, - { 43, "SPARC-V9" }, - { 44, "Siemens Tricore" }, - { 45, "ARC" }, - { 46, "H8/300" }, - { 47, "H8/300H" }, - { 48, "H8S" }, - { 49, "H8/500" }, - { 50, "IA-64" }, - { 51, "Stanford MIPS-X" }, - { 52, "Motorola ColdFire" }, - { 53, "M68HC12" }, - { 54, "Fujitsu MMA" }, - { 55, "Siemens PCP" }, - { 56, "Sony nCPU" }, - { 57, "Denso NDR1" }, - { 58, "Motorola StarCore" }, - { 59, "Toyota ME16" }, - { 60, "ST100" }, - { 61, "Advanced Logic TinyJ" }, - { 62, "AMD64" }, - { 63, "Sony DSP" }, - - - { 66, "Siemens FX66" }, - { 67, "ST9+" }, - { 68, "ST7" }, - { 69, "MC68HC16" }, - { 70, "MC68HC11" }, - { 71, "MC68HC08" }, - { 72, "MC68HC05" }, - { 73, "Silicon Graphics SVx" }, - { 74, "ST19" }, - { 75, "Digital VAX" }, - { 76, "Axis CRIS" }, - { 77, "Infineon JAVELIN" }, - { 78, "Element 14 FirePath" }, - { 79, "LSI ZSP" }, - { 80, "MMIX" }, - { 81, "HUANY" }, - { 82, "SiTera Prism" }, - { 83, "Atmel AVR" }, - { 84, "Fujitsu FR30" }, - { 85, "Mitsubishi D10V" }, - { 86, "Mitsubishi D30V" }, - { 87, "NEC v850" }, - { 88, "Mitsubishi M32R" }, - { 89, "Matsushita MN10300" }, - { 90, "Matsushita MN10200" }, - { 91, "picoJava" }, - { 92, "OpenRISC" }, - { 93, "ARC Tangent-A5" }, - { 94, "Tensilica Xtensa" }, - { 95, "Alphamosaic VideoCore" }, - { 96, "Thompson MM GPP" }, - { 97, "National Semiconductor 32K" }, - { 98, "Tenor Network TPC" }, - { 99, "Trebia SNP 1000" }, - { 100, "ST200" }, - { 101, "Ubicom IP2xxx" }, - { 102, "MAX" }, - { 103, "NS CompactRISC" }, - { 104, "Fujitsu F2MC16" }, - { 105, "TI msp430" }, - { 106, "Blackfin (DSP)" }, - { 107, "SE S1C33" }, - { 108, "Sharp embedded" }, - { 109, "Arca RISC" }, - { 110, "Unicore" }, - { 111, "eXcess" }, - { 112, "DXP" }, - { 113, "Altera Nios II" }, - { 114, "NS CRX" }, - { 115, "Motorola XGATE" }, - { 116, "Infineon C16x/XC16x" }, - { 117, "Renesas M16C" }, - { 118, "Microchip Technology dsPIC30F" }, - { 119, "Freescale CE" }, - { 120, "Renesas M32C" }, - - { 131, "Altium TSK3000" }, - { 132, "Freescale RS08" }, - { 133, "Analog Devices SHARC" }, - { 134, "Cyan Technology eCOG2" }, - { 135, "Sunplus S+core7 RISC" }, - { 136, "NJR 24-bit DSP" }, - { 137, "Broadcom VideoCore III" }, - { 138, "Lattice FPGA" }, - { 139, "SE C17" }, - { 140, "TI TMS320C6000" }, - { 141, "TI TMS320C2000" }, - { 142, "TI TMS320C55x" }, - - { 160, "STM 64bit VLIW Data Signal" }, - { 161, "Cypress M8C" }, - { 162, "Renesas R32C" }, - { 163, "NXP TriMedia" }, - { 164, "Qualcomm Hexagon" }, - { 165, "Intel 8051" }, - { 166, "STMicroelectronics STxP7x" }, - { 167, "Andes" }, - { 168, "Cyan Technology eCOG1X" }, - { 169, "Dallas Semiconductor MAXQ30" }, - { 170, "NJR 16-bit DSP" }, - { 171, "M2000" }, - { 172, "Cray NV2" }, - { 173, "Renesas RX" }, - { 174, "Imagination Technologies META" }, - { 175, "MCST Elbrus" }, - { 176, "Cyan Technology eCOG16" }, - { 177, "National Semiconductor CR16" }, - { 178, "Freescale ETPUnit" }, - { 179, "Infineon SLE9X" }, - { 180, "Intel L10M" }, - { 181, "Intel K10M" }, - - { 183, "ARM64" }, - - { 185, "Atmel AVR32" }, - { 186, "STM8" }, - { 187, "Tilera TILE64" }, - { 188, "Tilera TILEPro" }, - { 189, "Xilinx MicroBlaze" }, - { 190, "NVIDIA CUDA" }, - { 191, "Tilera TILE-Gx" }, - { 192, "CloudShield" }, - { 193, "KIPO-KAIST Core-A 1st" }, - { 194, "KIPO-KAIST Core-A 2nd" }, - { 195, "Synopsys ARCompact V2" }, - { 196, "Open8" }, - { 197, "Renesas RL78" }, - { 198, "Broadcom VideoCore V" }, - { 199, "Renesas 78KOR" }, - { 200, "Freescale 56800EX" }, - - { 47787, "Xilinx MicroBlaze" }, - // { 0x9026, "Alpha" } + "None" + , "AT&T WE 32100" + , "SPARC" + , "Intel 386" + , "Motorola 68000" + , "Motorola 88000" + , "Intel 486" + , "Intel i860" + , "MIPS" + , "IBM S/370" + , "MIPS RS3000 LE" + , "RS6000" + , NULL + , NULL + , NULL + , "PA-RISC" + , "nCUBE" + , "Fujitsu VPP500" + , "SPARC 32+" + , "Intel i960" + , "PowerPC" + , "PowerPC 64-bit" + , "IBM S/390" + , "SPU" + , NULL + , NULL + , NULL + , NULL + , NULL + , NULL + , NULL + , NULL + , NULL + , NULL + , NULL + , NULL + , "NEX v800" + , "Fujitsu FR20" + , "TRW RH-32" + , "Motorola RCE" + , "ARM" + , "Alpha-STD" + , "Hitachi SH" + , "SPARC-V9" + , "Siemens Tricore" + , "ARC" + , "H8/300" + , "H8/300H" + , "H8S" + , "H8/500" + , "IA-64" + , "Stanford MIPS-X" + , "Motorola ColdFire" + , "M68HC12" + , "Fujitsu MMA" + , "Siemens PCP" + , "Sony nCPU" + , "Denso NDR1" + , "Motorola StarCore" + , "Toyota ME16" + , "ST100" + , "Advanced Logic TinyJ" + , "AMD64" + , "Sony DSP" + , NULL + , NULL + , "Siemens FX66" + , "ST9+" + , "ST7" + , "MC68HC16" + , "MC68HC11" + , "MC68HC08" + , "MC68HC05" + , "Silicon Graphics SVx" + , "ST19" + , "Digital VAX" + , "Axis CRIS" + , "Infineon JAVELIN" + , "Element 14 FirePath" + , "LSI ZSP" + , "MMIX" + , "HUANY" + , "SiTera Prism" + , "Atmel AVR" + , "Fujitsu FR30" + , "Mitsubishi D10V" + , "Mitsubishi D30V" + , "NEC v850" + , "Mitsubishi M32R" + , "Matsushita MN10300" + , "Matsushita MN10200" + , "picoJava" + , "OpenRISC" + , "ARC Tangent-A5" + , "Tensilica Xtensa" + , "Alphamosaic VideoCore" + , "Thompson MM GPP" + , "National Semiconductor 32K" + , "Tenor Network TPC" + , "Trebia SNP 1000" + , "ST200" + , "Ubicom IP2xxx" + , "MAX" + , "NS CompactRISC" + , "Fujitsu F2MC16" + , "TI msp430" + , "Blackfin (DSP)" + , "SE S1C33" + , "Sharp embedded" + , "Arca RISC" + , "Unicore" + , "eXcess" + , "DXP" + , "Altera Nios II" + , "NS CRX" + , "Motorola XGATE" + , "Infineon C16x/XC16x" + , "Renesas M16C" + , "Microchip Technology dsPIC30F" + , "Freescale CE" + , "Renesas M32C" + , NULL + , NULL + , NULL + , NULL + , NULL + , NULL + , NULL + , NULL + , NULL + , NULL + , "Altium TSK3000" + , "Freescale RS08" + , "Analog Devices SHARC" + , "Cyan Technology eCOG2" + , "Sunplus S+core7 RISC" + , "NJR 24-bit DSP" + , "Broadcom VideoCore III" + , "Lattice FPGA" + , "SE C17" + , "TI TMS320C6000" + , "TI TMS320C2000" + , "TI TMS320C55x" + , NULL + , NULL + , NULL + , NULL + , NULL + , NULL + , NULL + , NULL + , NULL + , NULL + , NULL + , NULL + , NULL + , NULL + , NULL + , NULL + , NULL + , "STM 64bit VLIW Data Signal" + , "Cypress M8C" + , "Renesas R32C" + , "NXP TriMedia" + , "Qualcomm Hexagon" + , "Intel 8051" + , "STMicroelectronics STxP7x" + , "Andes" + , "Cyan Technology eCOG1X" + , "Dallas Semiconductor MAXQ30" + , "NJR 16-bit DSP" + , "M2000" + , "Cray NV2" + , "Renesas RX" + , "Imagination Technologies META" + , "MCST Elbrus" + , "Cyan Technology eCOG16" + , "National Semiconductor CR16" + , "Freescale ETPUnit" + , "Infineon SLE9X" + , "Intel L10M" + , "Intel K10M" + , NULL + , "ARM64" + , NULL + , "Atmel AVR32" + , "STM8" + , "Tilera TILE64" + , "Tilera TILEPro" + , "Xilinx MicroBlaze" + , "NVIDIA CUDA" + , "Tilera TILE-Gx" + , "CloudShield" + , "KIPO-KAIST Core-A 1st" + , "KIPO-KAIST Core-A 2nd" + , "Synopsys ARCompact V2" + , "Open8" + , "Renesas RL78" + , "Broadcom VideoCore V" + , "Renesas 78KOR" + , "Freescale 56800EX" // 200 +}; + +static const CUInt32PCharPair g_MachinePairs[] = +{ + { 243, "RISC-V" }, + { 258, "LoongArch" }, + { 0x9026, "Alpha" }, // EM_ALPHA_EXP, obsolete, (used by NetBSD/alpha) (written in the absence of an ABI) + { 0xbaab, "Xilinx MicroBlaze" } }; static const CUInt32PCharPair g_OS[] = @@ -548,13 +652,17 @@ static const CUInt32PCharPair g_OS[] = { 14, "HP NSK" }, { 15, "AROS" }, { 16, "FenixOS" }, + { 17, "CloudABI" }, + { 18, "OpenVOS" }, { 64, "Bare-metal TMS320C6000" }, { 65, "Linux TMS320C6000" }, { 97, "ARM" }, { 255, "Standalone" } }; +#define k_Machine_MIPS 8 #define k_Machine_ARM 40 +#define k_Machine_RISCV 243 /* #define EF_ARM_ABIMASK 0xFF000000 @@ -566,35 +674,59 @@ static const CUInt32PCharPair g_OS[] = static const CUInt32PCharPair g_ARM_Flags[] = { + { 1, "HasEntry" }, { 9, "SF" }, { 10, "HF" }, { 23, "BE8" } }; -#define ET_NONE 0 +static const CUInt32PCharPair g_MIPS_Flags[] = +{ + { 0, "NOREORDER" }, + { 1, "PIC" }, + { 2, "CPIC" }, + { 3, "XGOT" }, + { 4, "64BIT_WHIRL" }, + { 5, "ABI2" }, + { 6, "ABI_ON32" }, + { 10, "NAN2008" }, + { 25, "MicroMIPS" }, + { 26, "M16" }, + { 27, "MDMX" } +}; + +static const char * const g_RISCV_Flags[] = +{ + "RVC", + NULL, + NULL, + "RVE", + "TSO" +}; + + +// #define ET_NONE 0 #define ET_REL 1 -#define ET_EXEC 2 +// #define ET_EXEC 2 #define ET_DYN 3 -#define ET_CORE 4 +// #define ET_CORE 4 static const char * const g_Types[] = { - "None", - "Relocatable file", - "Executable file", - "Shared object file", - "Core file" + "None" + , "Relocatable file" + , "Executable file" + , "Shared object file" + , "Core file" }; -class CHandler: - public IInArchive, - public IArchiveAllowTail, - public CMyUnknownImp -{ +Z7_CLASS_IMP_CHandler_IInArchive_1( + IArchiveAllowTail +) CRecordVector _segments; CRecordVector _sections; CByteBuffer _namesData; @@ -603,37 +735,39 @@ class CHandler: CHeader _header; bool _headersError; bool _allowTail; + bool _stackFlags_Defined; + UInt32 _stackFlags; void GetSectionName(UInt32 index, NCOM::CPropVariant &prop, bool showNULL) const; HRESULT Open2(IInStream *stream); public: - MY_UNKNOWN_IMP2(IInArchive, IArchiveAllowTail) - INTERFACE_IInArchive(;) - STDMETHOD(AllowTail)(Int32 allowTail); - CHandler(): _allowTail(false) {} }; void CHandler::GetSectionName(UInt32 index, NCOM::CPropVariant &prop, bool showNULL) const { if (index >= _sections.Size()) - return; - const CSection §ion = _sections[index]; - UInt32 offset = section.Name; - if (index == SHN_UNDEF /* && section.Type == SHT_NULL && offset == 0 */) + prop = index; // it's possible for some file, but maybe it's ERROR case + else { - if (showNULL) - prop = "NULL"; - return; - } - const Byte *p = _namesData; - size_t size = _namesData.Size(); - for (size_t i = offset; i < size; i++) - if (p[i] == 0) + const CSection §ion = _sections[index]; + const UInt32 offset = section.Name; + if (index == SHN_UNDEF /* && section.Type == SHT_NULL && offset == 0 */) { - prop = (const char *)(p + offset); + if (showNULL) + prop = "NULL"; return; } + const Byte *p = _namesData; + const size_t size = _namesData.Size(); + for (size_t i = offset; i < size; i++) + if (p[i] == 0) + { + prop = (const char *)(p + offset); + return; + } + prop = "ERROR"; + } } static const Byte kArcProps[] = @@ -643,14 +777,21 @@ static const Byte kArcProps[] = kpidBigEndian, kpidHostOS, kpidCharacts, - kpidHeadersSize, - kpidName + kpidComment, + kpidHeadersSize }; enum { kpidLinkSection = kpidUserDefined, - kpidInfoSection + kpidInfoSection, + kpidEntrySize +#ifdef Z7_ELF_SHOW_DETAILS + // , kpidAlign + , kpidPa + , kpidDelta + , kpidOffsetEnd +#endif }; static const CStatProp kProps[] = @@ -662,6 +803,14 @@ static const CStatProp kProps[] = { NULL, kpidVa, VT_UI8 }, { NULL, kpidType, VT_BSTR }, { NULL, kpidCharacts, VT_BSTR } +#ifdef Z7_ELF_SHOW_DETAILS + // , { "Align", kpidAlign, VT_UI8 } + , { NULL, kpidClusterSize, VT_UI8 } + , { "PA", kpidPa, VT_UI8 } + , { "End offset", kpidOffsetEnd, VT_UI8 } + , { "Delta (VA-Offset)", kpidDelta, VT_UI8 } +#endif + , { "Entry Size", kpidEntrySize, VT_UI8} , { "Link Section", kpidLinkSection, VT_BSTR} , { "Info Section", kpidInfoSection, VT_BSTR} }; @@ -669,7 +818,7 @@ static const CStatProp kProps[] = IMP_IInArchive_Props_WITH_NAME IMP_IInArchive_ArcProps -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; @@ -683,21 +832,89 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) case kpidCpu: { - AString s = TypePairToString(g_Machines, ARRAY_SIZE(g_Machines), _header.Machine); + AString s; + if (_header.Machine < Z7_ARRAY_SIZE(g_Machines)) + { + const char *name = g_Machines[_header.Machine]; + if (name) + s = name; + } + if (s.IsEmpty()) + s = TypePairToString(g_MachinePairs, Z7_ARRAY_SIZE(g_MachinePairs), _header.Machine); UInt32 flags = _header.Flags; - if (flags != 0) + if (flags) { - char sz[16]; s.Add_Space(); if (_header.Machine == k_Machine_ARM) { - s += FlagsToString(g_ARM_Flags, ARRAY_SIZE(g_ARM_Flags), flags & (((UInt32)1 << 24) - 1)); + s += FlagsToString(g_ARM_Flags, Z7_ARRAY_SIZE(g_ARM_Flags), flags & (((UInt32)1 << 24) - 1)); s += " ABI:"; - ConvertUInt32ToString(flags >> 24, sz); + s.Add_UInt32(flags >> 24); } + else if (_header.Machine == k_Machine_MIPS) + { + const UInt32 ver = flags >> 28; + s.Add_Char('v'); + s.Add_UInt32(ver); + flags &= ((UInt32)1 << 28) - 1; + const UInt32 abi = (flags >> 12) & 7; + if (abi) + { + s += " ABI:"; + s.Add_UInt32(abi); + } + flags &= ~((UInt32)7 << 12); + s.Add_Space(); + s += FlagsToString(g_MIPS_Flags, Z7_ARRAY_SIZE(g_MIPS_Flags), flags); + } + else if (_header.Machine == k_Machine_RISCV) + { + s += "FLOAT_"; + const UInt32 fl = (flags >> 1) & 3; + /* + static const char * const g_RISCV_Flags_Float[] = + { "SOFT", "SINGLE", "DOUBLE", "QUAD" }; + s += g_RISCV_Flags_Float[fl]; + */ + if (fl) + s.Add_UInt32(16u << fl); + else + s += "SOFT"; + s.Add_Space(); + flags &= ~(UInt32)6; + s += FlagsToString(g_RISCV_Flags, Z7_ARRAY_SIZE(g_RISCV_Flags), flags); + } +#if 0 +#define k_Machine_LOONGARCH 258 + else if (_header.Machine == k_Machine_LOONGARCH) + { + s += "ABI:"; + s.Add_UInt32((flags >> 6) & 3); + s.Add_Dot(); + s.Add_UInt32((flags >> 3) & 7); + s.Add_Dot(); +#if 1 + s.Add_UInt32(flags & 7); +#else + static const char k_LoongArch_Float_Type[8] = { '0', 's', 'f', 'd', '4' ,'5', '6', '7' }; + s.Add_Char(k_LoongArch_Float_Type[flags & 7]); +#endif + flags &= ~(UInt32)0xff; + if (flags) + { + s.Add_Colon(); + char sz[16]; + ConvertUInt32ToHex(flags, sz); + s += sz; + } + } +#endif else + { + char sz[16]; ConvertUInt32ToHex(flags, sz); - s += sz; + s += sz; + } } prop = s; break; @@ -705,6 +922,40 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) case kpidHostOS: PAIR_TO_PROP(g_OS, _header.Os, prop); break; case kpidCharacts: TYPE_TO_PROP(g_Types, _header.Type, prop); break; + case kpidComment: + { + AString s; + if (_stackFlags_Defined) + { + s += "STACK: "; + s += FlagsToString(g_SegmentFlags, Z7_ARRAY_SIZE(g_SegmentFlags), _stackFlags); + s.Add_LF(); + /* + if (_header.EntryVa) + { + s += "Entry point: 0x"; + char temp[16 + 4]; + ConvertUInt64ToHex(_header.EntryVa, temp); + s += temp; + s.Add_LF(); + } + */ + } + if (_header.NumSegments) + { + s += "Segments: "; + s.Add_UInt32(_header.NumSegments); + s.Add_LF(); + } + if (_header.NumSections) + { + s += "Sections: "; + s.Add_UInt32(_header.NumSections); + s.Add_LF(); + } + prop = s; + break; + } case kpidExtension: { const char *s = NULL; @@ -731,7 +982,7 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) COM_TRY_END } -STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; @@ -749,12 +1000,17 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val } case kpidOffset: prop = item.Offset; break; case kpidVa: prop = item.Va; break; +#ifdef Z7_ELF_SHOW_DETAILS + case kpidDelta: if (item.Va) { prop = item.Va - item.Offset; } break; + case kpidOffsetEnd: prop = item.Offset + item.Size; break; + case kpidPa: prop = item.Pa; break; + case kpidClusterSize: prop = item.Align; break; +#endif case kpidSize: case kpidPackSize: prop = (UInt64)item.Size; break; case kpidVirtualSize: prop = (UInt64)item.VSize; break; - case kpidType: TYPE_TO_PROP(g_SegnmentTypes, item.Type, prop); break; + case kpidType: PAIR_TO_PROP(g_SegnmentTypes, item.Type, prop); break; case kpidCharacts: FLAGS_TO_PROP(g_SegmentFlags, item.Flags, prop); break; - } } else @@ -766,13 +1022,19 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val case kpidPath: GetSectionName(index, prop, true); break; case kpidOffset: prop = item.Offset; break; case kpidVa: prop = item.Va; break; +#ifdef Z7_ELF_SHOW_DETAILS + case kpidDelta: if (item.Va) { prop = item.Va - item.Offset; } break; + case kpidOffsetEnd: prop = item.Offset + item.GetSize(); break; +#endif case kpidSize: case kpidPackSize: prop = (UInt64)(item.Type == SHT_NOBITS ? 0 : item.VSize); break; case kpidVirtualSize: prop = item.GetSize(); break; case kpidType: PAIR_TO_PROP(g_SectTypes, item.Type, prop); break; case kpidCharacts: FLAGS_TO_PROP(g_SectionFlags, (UInt32)item.Flags, prop); break; + // case kpidAlign: prop = item.Align; break; case kpidLinkSection: GetSectionName(item.Link, prop, false); break; case kpidInfoSection: GetSectionName(item.Info, prop, false); break; + case kpidEntrySize: prop = (UInt64)item.EntSize; break; } } prop.Detach(value); @@ -782,71 +1044,79 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val HRESULT CHandler::Open2(IInStream *stream) { - const UInt32 kStartSize = kHeaderSize64; - Byte h[kStartSize]; - RINOK(ReadStream_FALSE(stream, h, kStartSize)); - if (h[0] != 0x7F || h[1] != 'E' || h[2] != 'L' || h[3] != 'F') - return S_FALSE; - if (!_header.Parse(h)) - return S_FALSE; + { + const UInt32 kStartSize = kHeaderSize64; + UInt64 h64[kStartSize / 8]; + RINOK(ReadStream_FALSE(stream, h64, kStartSize)) + const Byte *h = (const Byte *)(const void *)h64; + if (GetUi32a(h) != 0x464c457f) + return S_FALSE; + if (!_header.Parse(h)) + return S_FALSE; + } _totalSize = _header.HeaderSize; bool addSegments = false; bool addSections = false; - - if (_header.NumSections > 1) + // first section usually is NULL (with zero offsets and zero sizes). + if (_header.NumSegments == 0 || _header.NumSections > 1) addSections = true; else addSegments = true; +#ifdef Z7_ELF_SHOW_DETAILS + addSections = true; + addSegments = true; +#endif - if (_header.NumSegments != 0) + if (_header.NumSegments) { if (_header.ProgOffset > (UInt64)1 << 60) return S_FALSE; - RINOK(stream->Seek(_header.ProgOffset, STREAM_SEEK_SET, NULL)); - size_t size = (size_t)_header.SegmentEntrySize * _header.NumSegments; - + RINOK(InStream_SeekSet(stream, _header.ProgOffset)) + const size_t size = (size_t)_header.SegmentEntrySize * _header.NumSegments; CByteArr buf(size); - - RINOK(ReadStream_FALSE(stream, buf, size)); - - UInt64 total = _header.ProgOffset + size; - if (_totalSize < total) - _totalSize = total; - - const Byte *p = buf; - + RINOK(ReadStream_FALSE(stream, buf, size)) + { + const UInt64 total = _header.ProgOffset + size; + if (_totalSize < total) + _totalSize = total; + } if (addSegments) _segments.ClearAndReserve(_header.NumSegments); + const Byte *p = buf; for (unsigned i = 0; i < _header.NumSegments; i++, p += _header.SegmentEntrySize) { CSegment seg; seg.Parse(p, _header.Mode64, _header.Be); seg.UpdateTotalSize(_totalSize); - if (addSegments) - if (seg.Type != PT_PHDR) - _segments.AddInReserved(seg); + if (seg.Type == PT_GNU_STACK && !_stackFlags_Defined) + { + _stackFlags = seg.Flags; + _stackFlags_Defined = true; + } + if (addSegments + // we don't show program header table segment + && seg.Type != PT_PHDR + ) + _segments.AddInReserved(seg); } } - if (_header.NumSections != 0) + if (_header.NumSections) { if (_header.SectOffset > (UInt64)1 << 60) return S_FALSE; - RINOK(stream->Seek(_header.SectOffset, STREAM_SEEK_SET, NULL)); - size_t size = (size_t)_header.SectionEntrySize * _header.NumSections; - + RINOK(InStream_SeekSet(stream, _header.SectOffset)) + const size_t size = (size_t)_header.SectionEntrySize * _header.NumSections; CByteArr buf(size); - - RINOK(ReadStream_FALSE(stream, buf, size)); - - UInt64 total = _header.SectOffset + size; - if (_totalSize < total) - _totalSize = total; - - const Byte *p = buf; - + RINOK(ReadStream_FALSE(stream, buf, size)) + { + const UInt64 total = _header.SectOffset + size; + if (_totalSize < total) + _totalSize = total; + } if (addSections) _sections.ClearAndReserve(_header.NumSections); + const Byte *p = buf; for (unsigned i = 0; i < _header.NumSections; i++, p += _header.SectionEntrySize) { CSection sect; @@ -866,19 +1136,18 @@ HRESULT CHandler::Open2(IInStream *stream) if (_header.NamesSectIndex < _sections.Size()) { const CSection § = _sections[_header.NamesSectIndex]; - UInt64 size = sect.GetSize(); - if (size != 0 - && size < ((UInt64)1 << 31) - && (Int64)sect.Offset >= 0) + const UInt64 size = sect.GetSize(); + if (size && size < ((UInt64)1 << 31) + && (Int64)sect.Offset >= 0) { _namesData.Alloc((size_t)size); - RINOK(stream->Seek(sect.Offset, STREAM_SEEK_SET, NULL)); - RINOK(ReadStream_FALSE(stream, _namesData, (size_t)size)); + RINOK(InStream_SeekSet(stream, sect.Offset)) + RINOK(ReadStream_FALSE(stream, _namesData, (size_t)size)) } } - /* - // we will not delete NULL sections, since we have links to section via indexes + // we cannot delete "NULL" sections, + // because we have links to sections array via indexes for (int i = _sections.Size() - 1; i >= 0; i--) if (_sections[i].Type == SHT_NULL) _items.Delete(i); @@ -888,7 +1157,7 @@ HRESULT CHandler::Open2(IInStream *stream) if (!_allowTail) { UInt64 fileSize; - RINOK(stream->Seek(0, STREAM_SEEK_END, &fileSize)); + RINOK(InStream_GetSize_SeekToEnd(stream, fileSize)) if (fileSize > _totalSize) return S_FALSE; } @@ -896,22 +1165,23 @@ HRESULT CHandler::Open2(IInStream *stream) return S_OK; } -STDMETHODIMP CHandler::Open(IInStream *inStream, +Z7_COM7F_IMF(CHandler::Open(IInStream *inStream, const UInt64 * /* maxCheckStartPosition */, - IArchiveOpenCallback * /* openArchiveCallback */) + IArchiveOpenCallback * /* openArchiveCallback */)) { COM_TRY_BEGIN Close(); - RINOK(Open2(inStream)); + RINOK(Open2(inStream)) _inStream = inStream; return S_OK; COM_TRY_END } -STDMETHODIMP CHandler::Close() +Z7_COM7F_IMF(CHandler::Close()) { _totalSize = 0; _headersError = false; + _stackFlags_Defined = false; _inStream.Release(); _segments.Clear(); @@ -920,17 +1190,17 @@ STDMETHODIMP CHandler::Close() return S_OK; } -STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) { *numItems = _segments.Size() + _sections.Size(); return S_OK; } -STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, - Int32 testMode, IArchiveExtractCallback *extractCallback) +Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testMode, IArchiveExtractCallback *extractCallback)) { COM_TRY_BEGIN - bool allFilesMode = (numItems == (UInt32)(Int32)-1); + const bool allFilesMode = (numItems == (UInt32)(Int32)-1); if (allFilesMode) numItems = _segments.Size() + _sections.Size(); if (numItems == 0) @@ -939,35 +1209,32 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, UInt32 i; for (i = 0; i < numItems; i++) { - UInt32 index = allFilesMode ? i : indices[i]; + const UInt32 index = allFilesMode ? i : indices[i]; totalSize += (index < _segments.Size()) ? _segments[index].Size : _sections[index - _segments.Size()].GetSize(); } - extractCallback->SetTotal(totalSize); + RINOK(extractCallback->SetTotal(totalSize)) - UInt64 currentTotalSize = 0; + totalSize = 0; UInt64 currentItemSize; - NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder(); - CMyComPtr copyCoder = copyCoderSpec; - - CLocalProgress *lps = new CLocalProgress; - CMyComPtr progress = lps; + CMyComPtr2_Create copyCoder; + CMyComPtr2_Create lps; lps->Init(extractCallback, false); + CMyComPtr2_Create inStream; + inStream->SetStream(_inStream); - CLimitedSequentialInStream *streamSpec = new CLimitedSequentialInStream; - CMyComPtr inStream(streamSpec); - streamSpec->SetStream(_inStream); - - for (i = 0; i < numItems; i++, currentTotalSize += currentItemSize) + for (i = 0;; i++, totalSize += currentItemSize) { - lps->InSize = lps->OutSize = currentTotalSize; - RINOK(lps->SetCur()); - Int32 askMode = testMode ? + lps->InSize = lps->OutSize = totalSize; + RINOK(lps->SetCur()) + if (i >= numItems) + break; + const Int32 askMode = testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; - UInt32 index = allFilesMode ? i : indices[i]; + const UInt32 index = allFilesMode ? i : indices[i]; UInt64 offset; if (index < _segments.Size()) { @@ -981,26 +1248,25 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, currentItemSize = item.GetSize(); offset = item.Offset; } - - CMyComPtr outStream; - RINOK(extractCallback->GetStream(index, &outStream, askMode)); - if (!testMode && !outStream) - continue; - - RINOK(extractCallback->PrepareOperation(askMode)); - RINOK(_inStream->Seek(offset, STREAM_SEEK_SET, NULL)); - streamSpec->Init(currentItemSize); - RINOK(copyCoder->Code(inStream, outStream, NULL, NULL, progress)); - outStream.Release(); - RINOK(extractCallback->SetOperationResult(copyCoderSpec->TotalSize == currentItemSize ? + { + CMyComPtr outStream; + RINOK(extractCallback->GetStream(index, &outStream, askMode)) + if (!testMode && !outStream) + continue; + RINOK(extractCallback->PrepareOperation(askMode)) + RINOK(InStream_SeekSet(_inStream, offset)) + inStream->Init(currentItemSize); + RINOK(copyCoder.Interface()->Code(inStream, outStream, NULL, NULL, lps)) + } + RINOK(extractCallback->SetOperationResult(copyCoder->TotalSize == currentItemSize ? NExtract::NOperationResult::kOK: - NExtract::NOperationResult::kDataError)); + NExtract::NOperationResult::kDataError)) } return S_OK; COM_TRY_END } -STDMETHODIMP CHandler::AllowTail(Int32 allowTail) +Z7_COM7F_IMF(CHandler::AllowTail(Int32 allowTail)) { _allowTail = IntToBool(allowTail); return S_OK; @@ -1009,7 +1275,7 @@ STDMETHODIMP CHandler::AllowTail(Int32 allowTail) static const Byte k_Signature[] = { 0x7F, 'E', 'L', 'F' }; REGISTER_ARC_I( - "ELF", "elf", 0, 0xDE, + "ELF", "elf", NULL, 0xDE, k_Signature, 0, NArcInfoFlags::kPreArc, diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/ExtHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/ExtHandler.cpp index 6c2cd726..d3793735 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/ExtHandler.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/ExtHandler.cpp @@ -37,6 +37,8 @@ using namespace NWindows; +UInt32 LzhCrc16Update(UInt32 crc, const void *data, size_t size); + namespace NArchive { namespace NExt { @@ -87,33 +89,9 @@ static UInt32 Crc32C_Calc(Byte const *data, size_t size) */ -// CRC-16-ANSI. The poly is 0x8005 (x^16 + x^15 + x^2 + 1) -static UInt16 g_Crc16Table[256]; - -static struct CInitCrc16 -{ - CInitCrc16() - { - for (unsigned i = 0; i < 256; i++) - { - UInt32 r = i; - unsigned j; - for (j = 0; j < 8; j++) - r = (r >> 1) ^ (0xA001 & ~((r & 1) - 1)); - g_Crc16Table[i] = (UInt16)r; - } - } -} g_InitCrc16; - -#define CRC16_UPDATE_BYTE(crc, b) (g_Crc16Table[((crc) ^ (b)) & 0xFF] ^ ((crc) >> 8)) #define CRC16_INIT_VAL 0xFFFF -static UInt32 Crc16Update(UInt32 crc, Byte const *data, size_t size) -{ - for (size_t i = 0; i < size; i++) - crc = CRC16_UPDATE_BYTE(crc, data[i]); - return crc; -} +#define Crc16Update(crc, data, size) LzhCrc16Update(crc, data, size) static UInt32 Crc16Calc(Byte const *data, size_t size) { @@ -122,6 +100,8 @@ static UInt32 Crc16Calc(Byte const *data, size_t size) #define EXT4_GOOD_OLD_INODE_SIZE 128 +#define EXT_NODE_SIZE_MIN 128 + // inodes numbers @@ -161,64 +141,64 @@ static const char * const kHostOS[] = , "Lites" }; -static const CUInt32PCharPair g_FeatureCompat_Flags[] = -{ - { 0, "DIR_PREALLOC" }, - { 1, "IMAGIC_INODES" }, - { 2, "HAS_JOURNAL" }, - { 3, "EXT_ATTR" }, - { 4, "RESIZE_INODE" }, - { 5, "DIR_INDEX" }, - { 6, "LAZY_BG" }, // not in Linux - // { 7, "EXCLUDE_INODE" }, // not used - // { 8, "EXCLUDE_BITMAP" }, // not in kernel - { 9, "SPARSE_SUPER2" } +static const char * const g_FeatureCompat_Flags[] = +{ + "DIR_PREALLOC" + , "IMAGIC_INODES" + , "HAS_JOURNAL" + , "EXT_ATTR" + , "RESIZE_INODE" + , "DIR_INDEX" + , "LAZY_BG" // not in Linux + , NULL // { 7, "EXCLUDE_INODE" // not used + , NULL // { 8, "EXCLUDE_BITMAP" // not in kernel + , "SPARSE_SUPER2" }; #define EXT4_FEATURE_INCOMPAT_FILETYPE (1 << 1) #define EXT4_FEATURE_INCOMPAT_64BIT (1 << 7) -static const CUInt32PCharPair g_FeatureIncompat_Flags[] = -{ - { 0, "COMPRESSION" }, - { 1, "FILETYPE" }, - { 2, "RECOVER" }, /* Needs recovery */ - { 3, "JOURNAL_DEV" }, /* Journal device */ - { 4, "META_BG" }, - - { 6, "EXTENTS" }, /* extents support */ - { 7, "64BIT" }, - { 8, "MMP" }, - { 9, "FLEX_BG" }, - { 10, "EA_INODE" }, /* EA in inode */ - - { 12, "DIRDATA" }, /* data in dirent */ - { 13, "BG_USE_META_CSUM" }, /* use crc32c for bg */ - { 14, "LARGEDIR" }, /* >2GB or 3-lvl htree */ - { 15, "INLINE_DATA" }, /* data in inode */ - { 16, "ENCRYPT" } +static const char * const g_FeatureIncompat_Flags[] = +{ + "COMPRESSION" + , "FILETYPE" + , "RECOVER" /* Needs recovery */ + , "JOURNAL_DEV" /* Journal device */ + , "META_BG" + , NULL + , "EXTENTS" /* extents support */ + , "64BIT" + , "MMP" + , "FLEX_BG" + , "EA_INODE" /* EA in inode */ + , NULL + , "DIRDATA" /* data in dirent */ + , "BG_USE_META_CSUM" /* use crc32c for bg */ + , "LARGEDIR" /* >2GB or 3-lvl htree */ + , "INLINE_DATA" /* data in inode */ + , "ENCRYPT" // 16 }; static const UInt32 RO_COMPAT_GDT_CSUM = 1 << 4; static const UInt32 RO_COMPAT_METADATA_CSUM = 1 << 10; -static const CUInt32PCharPair g_FeatureRoCompat_Flags[] = -{ - { 0, "SPARSE_SUPER" }, - { 1, "LARGE_FILE" }, - { 2, "BTREE_DIR" }, - { 3, "HUGE_FILE" }, - { 4, "GDT_CSUM" }, - { 5, "DIR_NLINK" }, - { 6, "EXTRA_ISIZE" }, - { 7, "HAS_SNAPSHOT" }, - { 8, "QUOTA" }, - { 9, "BIGALLOC" }, - { 10, "METADATA_CSUM" }, - { 11, "REPLICA" }, - { 12, "READONLY" } +static const char * const g_FeatureRoCompat_Flags[] = +{ + "SPARSE_SUPER" + , "LARGE_FILE" + , "BTREE_DIR" + , "HUGE_FILE" + , "GDT_CSUM" + , "DIR_NLINK" + , "EXTRA_ISIZE" + , "HAS_SNAPSHOT" + , "QUOTA" + , "BIGALLOC" + , "METADATA_CSUM" + , "REPLICA" + , "READONLY" // 12 }; @@ -227,45 +207,40 @@ static const UInt32 k_NodeFlags_HUGE = (UInt32)1 << 18; static const UInt32 k_NodeFlags_EXTENTS = (UInt32)1 << 19; -static const CUInt32PCharPair g_NodeFlags[] = -{ - { 0, "SECRM" }, - { 1, "UNRM" }, - { 2, "COMPR" }, - { 3, "SYNC" }, - { 4, "IMMUTABLE" }, - { 5, "APPEND" }, - { 6, "NODUMP" }, - { 7, "NOATIME" }, - { 8, "DIRTY" }, - { 9, "COMPRBLK" }, - { 10, "NOCOMPR" }, - { 11, "ENCRYPT" }, - { 12, "INDEX" }, - { 13, "IMAGIC" }, - { 14, "JOURNAL_DATA" }, - { 15, "NOTAIL" }, - { 16, "DIRSYNC" }, - { 17, "TOPDIR" }, - { 18, "HUGE_FILE" }, - { 19, "EXTENTS" }, - - { 21, "EA_INODE" }, - { 22, "EOFBLOCKS" }, - - { 28, "INLINE_DATA" } +static const char * const g_NodeFlags[] = +{ + "SECRM" + , "UNRM" + , "COMPR" + , "SYNC" + , "IMMUTABLE" + , "APPEND" + , "NODUMP" + , "NOATIME" + , "DIRTY" + , "COMPRBLK" + , "NOCOMPR" + , "ENCRYPT" + , "INDEX" + , "IMAGIC" + , "JOURNAL_DATA" + , "NOTAIL" + , "DIRSYNC" + , "TOPDIR" + , "HUGE_FILE" + , "EXTENTS" + , NULL + , "EA_INODE" + , "EOFBLOCKS" + , NULL + , NULL + , NULL + , NULL + , NULL + , "INLINE_DATA" // 28 }; -static inline char GetHex(unsigned t) { return (char)(((t < 10) ? ('0' + t) : ('A' + (t - 10)))); } - -static inline void PrintHex(unsigned v, char *s) -{ - s[0] = GetHex((v >> 4) & 0xF); - s[1] = GetHex(v & 0xF); -} - - enum { k_Type_UNKNOWN, @@ -360,6 +335,8 @@ struct CHeader bool UseGdtChecksum() const { return (FeatureRoCompat & RO_COMPAT_GDT_CSUM) != 0; } bool UseMetadataChecksum() const { return (FeatureRoCompat & RO_COMPAT_METADATA_CSUM) != 0; } + UInt64 GetPhySize() const { return NumBlocks << BlockBits; } + bool Parse(const Byte *p); }; @@ -368,7 +345,7 @@ static int inline GetLog(UInt32 num) { for (unsigned i = 0; i < 32; i++) if (((UInt32)1 << i) == num) - return i; + return (int)i; return -1; } @@ -386,8 +363,8 @@ bool CHeader::Parse(const Byte *p) if (GetUi16(p + 0x38) != 0xEF53) return false; - LE_32 (0x18, BlockBits); - LE_32 (0x1C, ClusterBits); + LE_32 (0x18, BlockBits) + LE_32 (0x1C, ClusterBits) if (ClusterBits != 0 && BlockBits != ClusterBits) return false; @@ -396,22 +373,22 @@ bool CHeader::Parse(const Byte *p) return false; BlockBits += 10; - LE_32 (0x00, NumInodes); - LE_32 (0x04, NumBlocks); + LE_32 (0x00, NumInodes) + LE_32 (0x04, NumBlocks) // LE_32 (0x08, NumBlocksSuper); - LE_32 (0x0C, NumFreeBlocks); - LE_32 (0x10, NumFreeInodes); + LE_32 (0x0C, NumFreeBlocks) + LE_32 (0x10, NumFreeInodes) if (NumInodes < 2 || NumInodes <= NumFreeInodes) return false; UInt32 FirstDataBlock; - LE_32 (0x14, FirstDataBlock); + LE_32 (0x14, FirstDataBlock) if (FirstDataBlock != (unsigned)(BlockBits == 10 ? 1 : 0)) return false; - LE_32 (0x20, BlocksPerGroup); - LE_32 (0x24, ClustersPerGroup); + LE_32 (0x20, BlocksPerGroup) + LE_32 (0x24, ClustersPerGroup) if (BlocksPerGroup != ClustersPerGroup) return false; @@ -423,13 +400,13 @@ bool CHeader::Parse(const Byte *p) // return false; } - LE_32 (0x28, InodesPerGroup); + LE_32 (0x28, InodesPerGroup) if (InodesPerGroup < 1 || InodesPerGroup > NumInodes) return false; - LE_32 (0x2C, MountTime); - LE_32 (0x30, WriteTime); + LE_32 (0x2C, MountTime) + LE_32 (0x30, WriteTime) // LE_16 (0x34, NumMounts); // LE_16 (0x36, NumMountsMax); @@ -438,10 +415,10 @@ bool CHeader::Parse(const Byte *p) // LE_16 (0x3C, Errors); // LE_16 (0x3E, MinorRevLevel); - LE_32 (0x40, LastCheckTime); + LE_32 (0x40, LastCheckTime) // LE_32 (0x44, CheckInterval); - LE_32 (0x48, CreatorOs); - LE_32 (0x4C, RevLevel); + LE_32 (0x48, CreatorOs) + LE_32 (0x4C, RevLevel) // LE_16 (0x50, DefResUid); // LE_16 (0x52, DefResGid); @@ -451,20 +428,20 @@ bool CHeader::Parse(const Byte *p) if (!IsOldRev()) { - LE_32 (0x54, FirstInode); - LE_16 (0x58, InodeSize); + LE_32 (0x54, FirstInode) + LE_16 (0x58, InodeSize) if (FirstInode < k_INODE_GOOD_OLD_FIRST) return false; - if (InodeSize > (UInt32)1 << BlockBits) - return false; - if (GetLog(InodeSize) < 0) + if (InodeSize > ((UInt32)1 << BlockBits) + || InodeSize < EXT_NODE_SIZE_MIN + || GetLog(InodeSize) < 0) return false; } - LE_16 (0x5A, BlockGroupNr); - LE_32 (0x5C, FeatureCompat); - LE_32 (0x60, FeatureIncompat); - LE_32 (0x64, FeatureRoCompat); + LE_16 (0x5A, BlockGroupNr) + LE_32 (0x5C, FeatureCompat) + LE_32 (0x60, FeatureIncompat) + LE_32 (0x64, FeatureRoCompat) memcpy(Uuid, p + 0x68, sizeof(Uuid)); memcpy(VolName, p + 0x78, sizeof(VolName)); @@ -472,24 +449,24 @@ bool CHeader::Parse(const Byte *p) // LE_32 (0xC8, BitmapAlgo); - LE_32 (0xE0, JournalInode); + LE_32 (0xE0, JournalInode) - LE_16 (0xFE, GdSize); + LE_16 (0xFE, GdSize) - LE_32 (0x108, CTime); + LE_32 (0x108, CTime) if (Is64Bit()) { - HI_32(150, NumBlocks); - // HI_32(154, NumBlocksSuper); - HI_32(0x158, NumFreeBlocks); + HI_32(0x150, NumBlocks) + // HI_32(0x154, NumBlocksSuper); + HI_32(0x158, NumFreeBlocks) } if (NumBlocks >= (UInt64)1 << (63 - BlockBits)) return false; - LE_16(0x15C, MinExtraISize); + LE_16(0x15C, MinExtraISize) // LE_16(0x15E, WantExtraISize); // LE_32(0x160, Flags); // LE_16(0x164, RaidStride); @@ -499,7 +476,7 @@ bool CHeader::Parse(const Byte *p) // LogGroupsPerFlex = p[0x174]; // ChecksumType = p[0x175]; - LE_64 (0x178, WrittenKB); + LE_64 (0x178, WrittenKB) // LE_32(0x194, ErrorCount); // LE_32(0x198, ErrorTime); @@ -540,32 +517,32 @@ struct CGroupDescriptor void CGroupDescriptor::Parse(const Byte *p, unsigned size) { - LE_32 (0x00, BlockBitmap); - LE_32 (0x04, InodeBitmap); - LE_32 (0x08, InodeTable); - LE_16 (0x0C, NumFreeBlocks); - LE_16 (0x0E, NumFreeInodes); - LE_16 (0x10, DirCount); - LE_16 (0x12, Flags); - LE_32 (0x14, ExcludeBitmap); - LE_16 (0x18, BlockBitmap_Checksum); - LE_16 (0x1A, InodeBitmap_Checksum); - LE_16 (0x1C, UnusedCount); - LE_16 (0x1E, Checksum); + LE_32 (0x00, BlockBitmap) + LE_32 (0x04, InodeBitmap) + LE_32 (0x08, InodeTable) + LE_16 (0x0C, NumFreeBlocks) + LE_16 (0x0E, NumFreeInodes) + LE_16 (0x10, DirCount) + LE_16 (0x12, Flags) + LE_32 (0x14, ExcludeBitmap) + LE_16 (0x18, BlockBitmap_Checksum) + LE_16 (0x1A, InodeBitmap_Checksum) + LE_16 (0x1C, UnusedCount) + LE_16 (0x1E, Checksum) if (size >= 64) { p += 0x20; - HI_32 (0x00, BlockBitmap); - HI_32 (0x04, InodeBitmap); - HI_32 (0x08, InodeTable); - HI_16 (0x0C, NumFreeBlocks); - HI_16 (0x0E, NumFreeInodes); - HI_16 (0x10, DirCount); - HI_16 (0x12, UnusedCount); // instead of Flags - HI_32 (0x14, ExcludeBitmap); - HI_16 (0x18, BlockBitmap_Checksum); - HI_16 (0x1A, InodeBitmap_Checksum); + HI_32 (0x00, BlockBitmap) + HI_32 (0x04, InodeBitmap) + HI_32 (0x08, InodeTable) + HI_16 (0x0C, NumFreeBlocks) + HI_16 (0x0E, NumFreeInodes) + HI_16 (0x10, DirCount) + HI_16 (0x12, UnusedCount) // instead of Flags + HI_32 (0x14, ExcludeBitmap) + HI_16 (0x18, BlockBitmap_Checksum) + HI_16 (0x1A, InodeBitmap_Checksum) // HI_16 (0x1C, Reserved); } } @@ -582,9 +559,9 @@ struct CExtentTreeHeader bool Parse(const Byte *p) { - LE_16 (0x02, NumEntries); - LE_16 (0x04, MaxEntries); - LE_16 (0x06, Depth); + LE_16 (0x02, NumEntries) + LE_16 (0x04, MaxEntries) + LE_16 (0x06, Depth) // LE_32 (0x08, Generation); return Get16(p) == 0xF30A; // magic } @@ -597,8 +574,8 @@ struct CExtentIndexNode void Parse(const Byte *p) { - LE_32 (0x00, VirtBlock); - LE_32 (0x04, PhyLeaf); + LE_32 (0x00, VirtBlock) + LE_32 (0x04, PhyLeaf) PhyLeaf |= (((UInt64)Get16(p + 8)) << 32); // unused 16-bit field (at offset 0x0A) can be not zero in some cases. Why? } @@ -616,17 +593,17 @@ struct CExtent void Parse(const Byte *p) { - LE_32 (0x00, VirtBlock); - LE_16 (0x04, Len); + LE_32 (0x00, VirtBlock) + LE_16 (0x04, Len) IsInited = true; if (Len > (UInt32)0x8000) { IsInited = false; - Len -= (UInt32)0x8000; + Len = (UInt16)(Len - (UInt32)0x8000); } - LE_32 (0x08, PhyStart); + LE_32 (0x08, PhyStart) UInt16 hi; - LE_16 (0x06, hi); + LE_16 (0x06, hi) PhyStart |= ((UInt64)hi << 32); } }; @@ -642,37 +619,38 @@ struct CExtTime struct CNode { Int32 ParentNode; // in _refs[], -1 if not dir - int ItemIndex; // in _items[] + int ItemIndex; // in _items[] , (set as >=0 only for if (IsDir()) int SymLinkIndex; // in _symLinks[] int DirIndex; // in _dirs[] UInt16 Mode; - UInt16 Uid; - UInt16 Gid; + UInt32 Uid; // fixed 21.02 + UInt32 Gid; // fixed 21.02 // UInt16 Checksum; + UInt32 NumLinksCalced; + UInt64 FileSize; CExtTime MTime; CExtTime ATime; CExtTime CTime; - // CExtTime InodeChangeTime; + CExtTime ChangeTime; // CExtTime DTime; UInt64 NumBlocks; UInt32 NumLinks; UInt32 Flags; - UInt32 NumLinksCalced; - Byte Block[kNodeBlockFieldSize]; - CNode(): - ParentNode(-1), - ItemIndex(-1), - SymLinkIndex(-1), - DirIndex(0), - NumLinksCalced(0) - {} + void Construct() + { + ParentNode = -1; + ItemIndex = -1; + SymLinkIndex = -1; + DirIndex = -1; + NumLinksCalced = 0; + } bool IsFlags_HUGE() const { return (Flags & k_NodeFlags_HUGE) != 0; } bool IsFlags_EXTENTS() const { return (Flags & k_NodeFlags_EXTENTS) != 0; } @@ -691,21 +669,21 @@ bool CNode::Parse(const Byte *p, const CHeader &_h) ATime.Extra = 0; CTime.Extra = 0; CTime.Val = 0; - // InodeChangeTime.Extra = 0; + ChangeTime.Extra = 0; // DTime.Extra = 0; - LE_16 (0x00, Mode); - LE_16 (0x02, Uid); - LE_32 (0x04, FileSize); - LE_32 (0x08, ATime.Val); - // LE_32 (0x0C, InodeChangeTime.Val); - LE_32 (0x10, MTime.Val); + LE_16 (0x00, Mode) + LE_16 (0x02, Uid) + LE_32 (0x04, FileSize) + LE_32 (0x08, ATime.Val) + LE_32 (0x0C, ChangeTime.Val) + LE_32 (0x10, MTime.Val) // LE_32 (0x14, DTime.Val); - LE_16 (0x18, Gid); - LE_16 (0x1A, NumLinks); + LE_16 (0x18, Gid) + LE_16 (0x1A, NumLinks) - LE_32 (0x1C, NumBlocks); - LE_32 (0x20, Flags); + LE_32 (0x1C, NumBlocks) + LE_32 (0x20, Flags) // LE_32 (0x24, Union osd1); memcpy(Block, p + 0x28, kNodeBlockFieldSize); @@ -715,7 +693,7 @@ bool CNode::Parse(const Byte *p, const CHeader &_h) { UInt32 highSize; - LE_32 (0x6C, highSize); // In ext2/3 this field was named i_dir_acl + LE_32 (0x6C, highSize) // In ext2/3 this field was named i_dir_acl if (IsRegular()) // do we need that check ? FileSize |= ((UInt64)highSize << 32); @@ -733,11 +711,11 @@ bool CNode::Parse(const Byte *p, const CHeader &_h) // ext4: UInt32 numBlocksHigh; - LE_16 (0x74, numBlocksHigh); + LE_16 (0x74, numBlocksHigh) NumBlocks |= (UInt64)numBlocksHigh << 32; - HI_16 (0x74 + 4, Uid); - HI_16 (0x74 + 6, Gid); + HI_16 (0x74 + 4, Uid) + HI_16 (0x74 + 6, Gid) /* UInt32 checksum; LE_16 (0x74 + 8, checksum); @@ -749,20 +727,22 @@ bool CNode::Parse(const Byte *p, const CHeader &_h) if (_h.InodeSize > 128) { + // InodeSize is power of 2, so the following check is not required: + // if (_h.InodeSize < 128 + 2) return false; UInt16 extra_isize; - LE_16 (0x80, extra_isize); + LE_16 (0x80, extra_isize) if (128 + extra_isize > _h.InodeSize) return false; if (extra_isize >= 0x1C) { // UInt16 checksumUpper; // LE_16 (0x82, checksumUpper); - // LE_32 (0x84, InodeChangeTime.Extra); - LE_32 (0x88, MTime.Extra); - LE_32 (0x8C, ATime.Extra); - LE_32 (0x90, CTime.Val); - LE_32 (0x94, CTime.Extra); - // LE_32 (0x98, VersionHi); + LE_32 (0x84, ChangeTime.Extra) + LE_32 (0x88, MTime.Extra) + LE_32 (0x8C, ATime.Extra) + LE_32 (0x90, CTime.Val) + LE_32 (0x94, CTime.Extra) + // LE_32 (0x98, VersionHi) } } @@ -799,7 +779,6 @@ struct CItem bool IsDir() const { return Type == k_Type_DIR; } // bool IsNotDir() const { return Type != k_Type_DIR && Type != k_Type_UNKNOWN; } - }; @@ -807,14 +786,12 @@ struct CItem static const unsigned kNumTreeLevelsMax = 6; // must be >= 3 -class CHandler: - public IInArchive, - public IArchiveGetRawProps, - public IInArchiveGetStream, - public CMyUnknownImp -{ +Z7_CLASS_IMP_CHandler_IInArchive_2( + IArchiveGetRawProps, + IInArchiveGetStream +) CObjectVector _items; - CIntVector _refs; + CIntVector _refs; // iNode -> (index in _nodes). if (_refs[iNode] < 0), that node is not filled CRecordVector _nodes; CObjectVector _dirs; // each CUIntVector contains indexes in _items[] only for dir items; AStringVector _symLinks; @@ -861,7 +838,7 @@ class CHandler: } - const int GetParentAux(const CItem &item) const + int GetParentAux(const CItem &item) const { if (item.Node < _h.FirstInode && _auxSysIndex >= 0) return _auxSysIndex; @@ -884,16 +861,6 @@ class CHandler: void GetPath(unsigned index, AString &s) const; bool GetPackSize(unsigned index, UInt64 &res) const; - -public: - CHandler() {} - ~CHandler() {} - - MY_UNKNOWN_IMP3(IInArchive, IArchiveGetRawProps, IInArchiveGetStream) - - INTERFACE_IInArchive(;) - INTERFACE_IArchiveGetRawProps(;) - STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream); }; @@ -905,7 +872,7 @@ HRESULT CHandler::ParseDir(const Byte *p, size_t size, unsigned iNodeDir) PRF(printf("\n\n========= node = %5d size = %5d", (unsigned)iNodeDir, (unsigned)size)); CNode &nodeDir = _nodes[_refs[iNodeDir]]; - nodeDir.DirIndex = _dirs.Size(); + nodeDir.DirIndex = (int)_dirs.Size(); CUIntVector &dir = _dirs.AddNew(); int parentNode = -1; @@ -918,11 +885,11 @@ HRESULT CHandler::ParseDir(const Byte *p, size_t size, unsigned iNodeDir) if (size < 8) return S_FALSE; UInt32 iNode; - LE_32 (0x00, iNode); + LE_32 (0x00, iNode) unsigned recLen; - LE_16 (0x04, recLen); - unsigned nameLen = p[6]; - Byte type = p[7]; + LE_16 (0x04, recLen) + const unsigned nameLen = p[6]; + const Byte type = p[7]; if (recLen > size) return S_FALSE; @@ -939,7 +906,7 @@ HRESULT CHandler::ParseDir(const Byte *p, size_t size, unsigned iNodeDir) else if (type != 0) return S_FALSE; - item.ParentNode = iNodeDir; + item.ParentNode = (int)iNodeDir; item.Node = iNode; item.Name.SetFrom_CalcLen((const char *)(p + 8), nameLen); @@ -950,7 +917,10 @@ HRESULT CHandler::ParseDir(const Byte *p, size_t size, unsigned iNodeDir) return S_FALSE; if (_isUTF) - _isUTF = CheckUTF8(item.Name); + { + // 21.07 : we force UTF8 + // _isUTF = CheckUTF8_AString(item.Name); + } if (iNode == 0) { @@ -968,14 +938,14 @@ HRESULT CHandler::ParseDir(const Byte *p, size_t size, unsigned iNodeDir) continue; } - int nodeIndex = _refs[iNode]; + const int nodeIndex = _refs[iNode]; if (nodeIndex < 0) return S_FALSE; CNode &node = _nodes[nodeIndex]; if (_h.IsThereFileType() && type != 0) { - if (type >= ARRAY_SIZE(k_TypeToMode)) + if (type >= Z7_ARRAY_SIZE(k_TypeToMode)) return S_FALSE; if (k_TypeToMode[type] != (node.Mode & MY_LIN_S_IFMT)) return S_FALSE; @@ -1006,10 +976,10 @@ HRESULT CHandler::ParseDir(const Byte *p, size_t size, unsigned iNodeDir) if (iNode == iNodeDir && iNode != k_INODE_ROOT) return S_FALSE; - parentNode = iNode; + parentNode = (int)iNode; if (nodeDir.ParentNode < 0) - nodeDir.ParentNode = iNode; + nodeDir.ParentNode = (int)iNode; else if ((unsigned)nodeDir.ParentNode != iNode) return S_FALSE; @@ -1026,12 +996,12 @@ HRESULT CHandler::ParseDir(const Byte *p, size_t size, unsigned iNodeDir) if (node.IsDir()) { if (node.ParentNode < 0) - node.ParentNode = iNodeDir; + node.ParentNode = (int)iNodeDir; else if ((unsigned)node.ParentNode != iNodeDir) return S_FALSE; const unsigned itemIndex = _items.Size(); dir.Add(itemIndex); - node.ItemIndex = itemIndex; + node.ItemIndex = (int)itemIndex; } _items.Add(item); @@ -1044,6 +1014,13 @@ HRESULT CHandler::ParseDir(const Byte *p, size_t size, unsigned iNodeDir) } +static int CompareItemsNames(const unsigned *p1, const unsigned *p2, void *param) +{ + const CObjectVector &items = *(const CObjectVector *)param; + return strcmp(items[*p1].Name, items[*p2].Name); +} + + int CHandler::FindTargetItem_for_SymLink(unsigned iNode, const AString &path) const { unsigned pos = 0; @@ -1064,7 +1041,7 @@ int CHandler::FindTargetItem_for_SymLink(unsigned iNode, const AString &path) co while (pos != path.Len()) { const CNode &node = _nodes[_refs[iNode]]; - int slash = path.Find('/', pos); + const int slash = path.Find('/', pos); if (slash < 0) { @@ -1073,8 +1050,8 @@ int CHandler::FindTargetItem_for_SymLink(unsigned iNode, const AString &path) co } else { - s.SetFrom(path.Ptr(pos), slash - pos); - pos = slash + 1; + s.SetFrom(path.Ptr(pos), (unsigned)slash - pos); + pos = (unsigned)slash + 1; } if (s[0] == '.') @@ -1087,7 +1064,7 @@ int CHandler::FindTargetItem_for_SymLink(unsigned iNode, const AString &path) co return -1; if (iNode == k_INODE_ROOT) return -1; - iNode = node.ParentNode; + iNode = (unsigned)node.ParentNode; continue; } } @@ -1097,6 +1074,7 @@ int CHandler::FindTargetItem_for_SymLink(unsigned iNode, const AString &path) co const CUIntVector &dir = _dirs[node.DirIndex]; + /* for (unsigned i = 0;; i++) { if (i >= dir.Size()) @@ -1108,6 +1086,26 @@ int CHandler::FindTargetItem_for_SymLink(unsigned iNode, const AString &path) co break; } } + */ + + unsigned left = 0, right = dir.Size(); + for (;;) + { + if (left == right) + return -1; + const unsigned mid = (unsigned)(((size_t)left + (size_t)right) / 2); + const CItem &item = _items[dir[mid]]; + const int comp = strcmp(s, item.Name); + if (comp == 0) + { + iNode = item.Node; + break; + } + if (comp < 0) + right = mid; + else + left = mid + 1; + } } return _nodes[_refs[iNode]].ItemIndex; @@ -1120,7 +1118,7 @@ HRESULT CHandler::SeekAndRead(IInStream *inStream, UInt64 block, Byte *data, siz return S_FALSE; if (((size + ((size_t)1 << _h.BlockBits) - 1) >> _h.BlockBits) > _h.NumBlocks - block) return S_FALSE; - RINOK(inStream->Seek((UInt64)block << _h.BlockBits, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekSet(inStream, (UInt64)block << _h.BlockBits)) _totalRead += size; return ReadStream_FALSE(inStream, data, size); } @@ -1133,7 +1131,7 @@ HRESULT CHandler::Open2(IInStream *inStream) { { Byte buf[kHeaderSize]; - RINOK(ReadStream_FALSE(inStream, buf, kHeaderSize)); + RINOK(ReadStream_FALSE(inStream, buf, kHeaderSize)) if (!_h.Parse(buf + kHeaderDataOffset)) return S_FALSE; if (_h.BlockGroupNr != 0) @@ -1145,7 +1143,7 @@ HRESULT CHandler::Open2(IInStream *inStream) unsigned numGroups; { - UInt64 numGroups64 = _h.GetNumGroups(); + const UInt64 numGroups64 = _h.GetNumGroups(); if (numGroups64 > (UInt32)1 << 31) return S_FALSE; numGroups = (unsigned)numGroups64; @@ -1160,36 +1158,34 @@ HRESULT CHandler::Open2(IInStream *inStream) } _isArc = true; - _phySize = _h.NumBlocks << _h.BlockBits; + _phySize = _h.GetPhySize(); if (_openCallback) { - RINOK(_openCallback->SetTotal(NULL, &_phySize)); + RINOK(_openCallback->SetTotal(NULL, &_phySize)) } UInt64 fileSize = 0; - RINOK(inStream->Seek(0, STREAM_SEEK_END, &fileSize)); + RINOK(InStream_GetSize_SeekToEnd(inStream, fileSize)) CRecordVector groups; { // ---------- Read groups ---------- - CByteBuffer gdBuf; - size_t gdBufSize = (size_t)numGroups << gdBits; + const size_t gdBufSize = (size_t)numGroups << gdBits; if ((gdBufSize >> gdBits) != numGroups) return S_FALSE; + CByteBuffer gdBuf; gdBuf.Alloc(gdBufSize); - RINOK(SeekAndRead(inStream, (_h.BlockBits <= 10 ? 2 : 1), gdBuf, gdBufSize)); + RINOK(SeekAndRead(inStream, (_h.BlockBits <= 10 ? 2 : 1), gdBuf, gdBufSize)) - for (unsigned i = 0; i < numGroups; i++) + const unsigned gd_Size = (unsigned)1 << gdBits; + const Byte *p = gdBuf; + for (unsigned i = 0; i < numGroups; i++, p += gd_Size) { CGroupDescriptor gd; - - const Byte *p = gdBuf + ((size_t)i << gdBits); - unsigned gd_Size = (unsigned)1 << gdBits; gd.Parse(p, gd_Size); - if (_h.UseMetadataChecksum()) { // use CRC32c @@ -1198,7 +1194,7 @@ HRESULT CHandler::Open2(IInStream *inStream) { UInt32 crc = Crc16Calc(_h.Uuid, sizeof(_h.Uuid)); Byte i_le[4]; - SetUi32(i_le, i); + SetUi32(i_le, i) crc = Crc16Update(crc, i_le, 4); crc = Crc16Update(crc, p, 32 - 2); if (gd_Size != 32) @@ -1220,7 +1216,7 @@ HRESULT CHandler::Open2(IInStream *inStream) UInt32 numNodes = _h.InodesPerGroup; if (numNodes > _h.NumInodes) numNodes = _h.NumInodes; - size_t nodesDataSize = (size_t)numNodes * _h.InodeSize; + const size_t nodesDataSize = (size_t)numNodes * _h.InodeSize; if (nodesDataSize / _h.InodeSize != numNodes) return S_FALSE; @@ -1231,8 +1227,11 @@ HRESULT CHandler::Open2(IInStream *inStream) if (numNodes > (1 << 24)) return S_FALSE; } - - UInt32 numReserveInodes = _h.NumInodes - _h.NumFreeInodes + 1; + + const size_t blockSize = (size_t)1 << _h.BlockBits; + if (numNodes > blockSize * 8) + return S_FALSE; + const UInt32 numReserveInodes = _h.NumInodes - _h.NumFreeInodes + 1; // numReserveInodes = _h.NumInodes + 1; if (numReserveInodes != 0) { @@ -1244,7 +1243,6 @@ HRESULT CHandler::Open2(IInStream *inStream) nodesData.Alloc(nodesDataSize); CByteBuffer nodesMap; - const size_t blockSize = (size_t)1 << _h.BlockBits; nodesMap.Alloc(blockSize); unsigned globalNodeIndex = 0; @@ -1260,8 +1258,8 @@ HRESULT CHandler::Open2(IInStream *inStream) PRF(printf("\n\ng%6d block = %6x\n", gi, (unsigned)gd.InodeTable)); - RINOK(SeekAndRead(inStream, gd.InodeBitmap, nodesMap, blockSize)); - RINOK(SeekAndRead(inStream, gd.InodeTable, nodesData, nodesDataSize)); + RINOK(SeekAndRead(inStream, gd.InodeBitmap, nodesMap, blockSize)) + RINOK(SeekAndRead(inStream, gd.InodeTable, nodesData, nodesDataSize)) unsigned numEmpty_in_Map = 0; @@ -1285,6 +1283,7 @@ HRESULT CHandler::Open2(IInStream *inStream) } CNode node; + node.Construct(); PRF(printf("\nnode = %5d ", (unsigned)n)); @@ -1310,7 +1309,7 @@ HRESULT CHandler::Open2(IInStream *inStream) _refs.Add(-1); } - _refs.Add(_nodes.Add(node)); + _refs.Add((int)_nodes.Add(node)); } @@ -1346,7 +1345,7 @@ HRESULT CHandler::Open2(IInStream *inStream) FOR_VECTOR (i, _refs) { - int nodeIndex = _refs[i]; + const int nodeIndex = _refs[i]; { if (nodeIndex < 0) continue; @@ -1354,7 +1353,7 @@ HRESULT CHandler::Open2(IInStream *inStream) if (!node.IsDir()) continue; } - RINOK(ExtractNode(nodeIndex, dataBuf)); + RINOK(ExtractNode((unsigned)nodeIndex, dataBuf)) if (dataBuf.Size() == 0) { // _headersError = true; @@ -1362,12 +1361,13 @@ HRESULT CHandler::Open2(IInStream *inStream) } else { - RINOK(ParseDir(dataBuf, dataBuf.Size(), i)); + RINOK(ParseDir(dataBuf, dataBuf.Size(), i)) } - RINOK(CheckProgress()); + RINOK(CheckProgress()) } - if (_nodes[_refs[k_INODE_ROOT]].ParentNode != k_INODE_ROOT) + const int ref = _refs[k_INODE_ROOT]; + if (ref < 0 || _nodes[ref].ParentNode != k_INODE_ROOT) return S_FALSE; } @@ -1432,7 +1432,7 @@ HRESULT CHandler::Open2(IInStream *inStream) for (;;) { - int nodeIndex = _refs[c]; + const int nodeIndex = _refs[c]; if (nodeIndex < 0) return S_FALSE; CNode &node = _nodes[nodeIndex]; @@ -1444,12 +1444,12 @@ HRESULT CHandler::Open2(IInStream *inStream) break; } - UsedByNode[c] = i; + UsedByNode[c] = (int)i; if (node.ParentNode < 0 || node.ParentNode == k_INODE_ROOT) break; if ((unsigned)node.ParentNode == i) return S_FALSE; - c = node.ParentNode; + c = (unsigned)node.ParentNode; } } } @@ -1463,7 +1463,7 @@ HRESULT CHandler::Open2(IInStream *inStream) unsigned i; for (i = 0; i < _refs.Size(); i++) { - int nodeIndex = _refs[i]; + const int nodeIndex = _refs[i]; if (nodeIndex < 0) continue; CNode &node = _nodes[nodeIndex]; @@ -1471,32 +1471,37 @@ HRESULT CHandler::Open2(IInStream *inStream) continue; if (node.FileSize > ((UInt32)1 << 14)) continue; - if (ExtractNode(nodeIndex, data) == S_OK && data.Size() != 0) + if (ExtractNode((unsigned)nodeIndex, data) == S_OK && data.Size() != 0) { s.SetFrom_CalcLen((const char *)(const Byte *)data, (unsigned)data.Size()); if (s.Len() == data.Size()) - node.SymLinkIndex = _symLinks.Add(s); - RINOK(CheckProgress()); + node.SymLinkIndex = (int)_symLinks.Add(s); + RINOK(CheckProgress()) } } + for (i = 0; i < _dirs.Size(); i++) + { + _dirs[i].Sort(CompareItemsNames, (void *)&_items); + } + unsigned prev = 0; unsigned complex = 0; for (i = 0; i < _items.Size(); i++) { CItem &item = _items[i]; - int sym = _nodes[_refs[item.Node]].SymLinkIndex; + const int sym = _nodes[_refs[item.Node]].SymLinkIndex; if (sym >= 0 && item.ParentNode >= 0) { - item.SymLinkItemIndex = FindTargetItem_for_SymLink(item.ParentNode, _symLinks[sym]); + item.SymLinkItemIndex = FindTargetItem_for_SymLink((unsigned)item.ParentNode, _symLinks[sym]); if (_openCallback) { complex++; if (complex - prev >= (1 << 10)) { - RINOK(CheckProgress2()); prev = complex; + RINOK(CheckProgress2()) } } } @@ -1511,7 +1516,7 @@ HRESULT CHandler::Open2(IInStream *inStream) FOR_VECTOR (i, _refs) { - int nodeIndex = _refs[i]; + const int nodeIndex = _refs[i]; if (nodeIndex < 0) continue; const CNode &node = _nodes[nodeIndex]; @@ -1532,7 +1537,7 @@ HRESULT CHandler::Open2(IInStream *inStream) if (i < _h.FirstInode) { - if (item.Node < ARRAY_SIZE(k_SysInode_Names)) + if (item.Node < Z7_ARRAY_SIZE(k_SysInode_Names)) item.Name = k_SysInode_Names[item.Node]; useSys = true; } @@ -1540,27 +1545,23 @@ HRESULT CHandler::Open2(IInStream *inStream) useUnknown = true; if (item.Name.IsEmpty()) - { - char temp[16]; - ConvertUInt32ToString(item.Node, temp); - item.Name = temp; - } + item.Name.Add_UInt32(item.Node); _items.Add(item); } } if (useSys) - _auxSysIndex = _auxItems.Add("[SYS]"); + _auxSysIndex = (int)_auxItems.Add((AString)"[SYS]"); if (useUnknown) - _auxUnknownIndex = _auxItems.Add("[UNKNOWN]"); + _auxUnknownIndex = (int)_auxItems.Add((AString)"[UNKNOWN]"); } return S_OK; } -STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *callback) +Z7_COM7F_IMF(CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *callback)) { COM_TRY_BEGIN { @@ -1603,7 +1604,7 @@ void CHandler::ClearRefs() } -STDMETHODIMP CHandler::Close() +Z7_COM7F_IMF(CHandler::Close()) { _totalRead = 0; _totalReadPrev = 0; @@ -1619,6 +1620,17 @@ STDMETHODIMP CHandler::Close() } +static void ChangeSeparatorsInName(char *s, unsigned num) +{ + for (unsigned i = 0; i < num; i++) + { + char c = s[i]; + if (c == CHAR_PATH_SEPARATOR || c == '/') + s[i] = '_'; + } +} + + void CHandler::GetPath(unsigned index, AString &s) const { s.Empty(); @@ -1635,6 +1647,8 @@ void CHandler::GetPath(unsigned index, AString &s) const if (!s.IsEmpty()) s.InsertAtFront(CHAR_PATH_SEPARATOR); s.Insert(0, item.Name); + // 18.06 + ChangeSeparatorsInName(s.GetBuf(), item.Name.Len()); if (item.ParentNode == k_INODE_ROOT) return; @@ -1652,7 +1666,7 @@ void CHandler::GetPath(unsigned index, AString &s) const const CNode &node = _nodes[_refs[item.ParentNode]]; if (node.ItemIndex < 0) return; - index = node.ItemIndex; + index = (unsigned)node.ItemIndex; if (s.Len() > ((UInt32)1 << 16)) { @@ -1707,7 +1721,7 @@ bool CHandler::GetPackSize(unsigned index, UInt64 &totalPack) const } -STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) { *numItems = _items.Size() + _auxItems.Size(); return S_OK; @@ -1746,8 +1760,8 @@ static const UInt32 kProps[] = kpidLinks, kpidSymLink, kpidCharacts, - kpidUser, - kpidGroup + kpidUserId, + kpidGroupId }; @@ -1794,14 +1808,10 @@ static void StringToProp(bool isUTF, const char *s, unsigned size, NCOM::CPropVa static void UnixTimeToProp(UInt32 val, NCOM::CPropVariant &prop) { if (val != 0) - { - FILETIME ft; - NTime::UnixTimeToFileTime(val, ft); - prop = ft; - } + PropVariant_SetFrom_UnixTime(prop, val); } -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN @@ -1834,31 +1844,20 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) case kpidHostOS: { - char temp[16]; - const char *s = NULL; - if (_h.CreatorOs < ARRAY_SIZE(kHostOS)) - s = kHostOS[_h.CreatorOs]; - else - { - ConvertUInt32ToString(_h.CreatorOs, temp); - s = temp; - } - prop = s; + TYPE_TO_PROP(kHostOS, _h.CreatorOs, prop); break; } case kpidRevision: prop = _h.RevLevel; break; - case kpidINodeSize: prop = _h.InodeSize; break; + case kpidINodeSize: prop = (UInt32)_h.InodeSize; break; case kpidId: { - if (!IsEmptyData(_h.Uuid, 16)) + if (!IsEmptyData(_h.Uuid, sizeof(_h.Uuid))) { - char s[16 * 2 + 2]; - for (unsigned i = 0; i < 16; i++) - PrintHex(_h.Uuid[i], s + i * 2); - s[16 * 2] = 0; + char s[sizeof(_h.Uuid) * 2 + 2]; + ConvertDataToHex_Lower(s, _h.Uuid, sizeof(_h.Uuid)); prop = s; } break; @@ -1882,7 +1881,7 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) case kpidErrorFlags: { UInt32 v = 0; - if (!_isArc) v |= kpv_ErrorFlags_IsNotArc;; + if (!_isArc) v |= kpv_ErrorFlags_IsNotArc; if (_linksError) v |= kpv_ErrorFlags_HeadersError; if (_headersError) v |= kpv_ErrorFlags_HeadersError; if (!_stream && v == 0 && _isArc) @@ -1916,22 +1915,22 @@ static const Byte kRawProps[] = }; */ -STDMETHODIMP CHandler::GetNumRawProps(UInt32 *numProps) +Z7_COM7F_IMF(CHandler::GetNumRawProps(UInt32 *numProps)) { - // *numProps = ARRAY_SIZE(kRawProps); + // *numProps = Z7_ARRAY_SIZE(kRawProps); *numProps = 0; return S_OK; } -STDMETHODIMP CHandler::GetRawPropInfo(UInt32 /* index */, BSTR *name, PROPID *propID) +Z7_COM7F_IMF(CHandler::GetRawPropInfo(UInt32 /* index */, BSTR *name, PROPID *propID)) { // *propID = kRawProps[index]; *propID = 0; - *name = 0; + *name = NULL; return S_OK; } -STDMETHODIMP CHandler::GetParent(UInt32 index, UInt32 *parent, UInt32 *parentType) +Z7_COM7F_IMF(CHandler::GetParent(UInt32 index, UInt32 *parent, UInt32 *parentType)) { *parentType = NParentType::kDir; *parent = (UInt32)(Int32)-1; @@ -1943,22 +1942,22 @@ STDMETHODIMP CHandler::GetParent(UInt32 index, UInt32 *parent, UInt32 *parentTyp if (item.ParentNode < 0) { - int aux = GetParentAux(item); + const int aux = GetParentAux(item); if (aux >= 0) - *parent = _items.Size() + aux; + *parent = _items.Size() + (unsigned)aux; } else { - int itemIndex = _nodes[_refs[item.ParentNode]].ItemIndex; + const int itemIndex = _nodes[_refs[item.ParentNode]].ItemIndex; if (itemIndex >= 0) - *parent = itemIndex; + *parent = (unsigned)itemIndex; } return S_OK; } -STDMETHODIMP CHandler::GetRawProp(UInt32 index, PROPID propID, const void **data, UInt32 *dataSize, UInt32 *propType) +Z7_COM7F_IMF(CHandler::GetRawProp(UInt32 index, PROPID propID, const void **data, UInt32 *dataSize, UInt32 *propType)) { *data = NULL; *dataSize = 0; @@ -1995,43 +1994,42 @@ STDMETHODIMP CHandler::GetRawProp(UInt32 index, PROPID propID, const void **data static void ExtTimeToProp(const CExtTime &t, NCOM::CPropVariant &prop) { - /* - UInt32 nano = 0; - if (t.Extra != 0) - { - UInt32 mask = t.Extra & 3; - if (mask != 0) - return; - nano = t.Extra >> 2; - } - UInt64 v; - if (t.Val == 0 && nano == 0) + if (t.Val == 0 && t.Extra == 0) return; - if (!NTime::UnixTime_to_FileTime64(t.Val, v)) - return; - if (nano != 0) - v += nano / 100; FILETIME ft; - ft.dwLowDateTime = (DWORD)v; - ft.dwHighDateTime = (DWORD)(v >> 32); - prop = ft; - */ - if (t.Val == 0) - return; - if (t.Extra != 0) + unsigned low100ns = 0; + // if (t.Extra != 0) { - UInt32 mask = t.Extra & 3; - if (mask != 0) - return; + // 1901-2446 : + Int64 v = (Int64)(Int32)t.Val; + v += (UInt64)(t.Extra & 3) << 32; // 2 low bits are offset for main timestamp + UInt64 ft64 = NTime::UnixTime64_To_FileTime64(v); + const UInt32 ns = (t.Extra >> 2); + if (ns < 1000000000) + { + ft64 += ns / 100; + low100ns = (unsigned)(ns % 100); + } + ft.dwLowDateTime = (DWORD)ft64; + ft.dwHighDateTime = (DWORD)(ft64 >> 32); } - FILETIME ft; - if (NTime::UnixTime64ToFileTime(t.Val, ft)) - prop = ft; + /* + else + { + // 1901-2038 : that code is good for ext4 and compatibility with Extra + NTime::UnixTime64ToFileTime((Int32)t.Val, ft); // for + + // 1970-2106 : that code is good if timestamp is used as unsigned 32-bit + // are there such systems? + // NTime::UnixTimeToFileTime(t.Val, ft); // for + } + */ + prop.SetAsTimeFrom_FT_Prec_Ns100(ft, k_PropVar_TimePrec_1ns, low100ns); } -STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; @@ -2043,8 +2041,7 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val case kpidPath: case kpidName: { - AString s = _auxItems[index - _items.Size()]; - prop = s; + prop = _auxItems[index - _items.Size()]; break; } case kpidIsDir: prop = true; break; @@ -2109,7 +2106,7 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val case kpidPosixAttrib: { /* - if (node.Type != 0 && node.Type < ARRAY_SIZE(k_TypeToMode)) + if (node.Type != 0 && node.Type < Z7_ARRAY_SIZE(k_TypeToMode)) prop = (UInt32)(node.Mode & 0xFFF) | k_TypeToMode[node.Type]; */ prop = (UInt32)(node.Mode); @@ -2120,10 +2117,9 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val case kpidCTime: ExtTimeToProp(node.CTime, prop); break; case kpidATime: ExtTimeToProp(node.ATime, prop); break; // case kpidDTime: ExtTimeToProp(node.DTime, prop); break; - // case kpidChangeTime: ExtTimeToProp(node.InodeChangeTime, prop); break; - - case kpidUser: prop = (UInt32)node.Uid; break; - case kpidGroup: prop = (UInt32)node.Gid; break; + case kpidChangeTime: ExtTimeToProp(node.ChangeTime, prop); break; + case kpidUserId: prop = (UInt32)node.Uid; break; + case kpidGroupId: prop = (UInt32)node.Gid; break; case kpidLinks: prop = node.NumLinks; break; case kpidINode: prop = (UInt32)item.Node; break; case kpidStreamId: if (!isDir) prop = (UInt32)item.Node; break; @@ -2153,10 +2149,8 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val } -class CClusterInStream2: - public IInStream, - public CMyUnknownImp -{ +Z7_CLASS_IMP_IInStream(CClusterInStream2 +) UInt64 _virtPos; UInt64 _physPos; UInt32 _curRem; @@ -2166,7 +2160,7 @@ class CClusterInStream2: CMyComPtr Stream; CRecordVector Vector; - HRESULT SeekToPhys() { return Stream->Seek(_physPos, STREAM_SEEK_SET, NULL); } + HRESULT SeekToPhys() { return InStream_SeekSet(Stream, _physPos); } HRESULT InitAndSeek() { @@ -2180,15 +2174,10 @@ class CClusterInStream2: } return S_OK; } - - MY_UNKNOWN_IMP2(ISequentialInStream, IInStream) - - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); - STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition); }; -STDMETHODIMP CClusterInStream2::Read(void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CClusterInStream2::Read(void *data, UInt32 size, UInt32 *processedSize)) { if (processedSize) *processedSize = 0; @@ -2225,7 +2214,7 @@ STDMETHODIMP CClusterInStream2::Read(void *data, UInt32 size, UInt32 *processedS if (newPos != _physPos) { _physPos = newPos; - RINOK(SeekToPhys()); + RINOK(SeekToPhys()) } _curRem = blockSize - offsetInBlock; @@ -2245,7 +2234,7 @@ STDMETHODIMP CClusterInStream2::Read(void *data, UInt32 size, UInt32 *processedS return res; } -STDMETHODIMP CClusterInStream2::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition) +Z7_COM7F_IMF(CClusterInStream2::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition)) { switch (seekOrigin) { @@ -2258,17 +2247,16 @@ STDMETHODIMP CClusterInStream2::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *ne return HRESULT_WIN32_ERROR_NEGATIVE_SEEK; if (_virtPos != (UInt64)offset) _curRem = 0; - _virtPos = offset; + _virtPos = (UInt64)offset; if (newPosition) - *newPosition = offset; + *newPosition = (UInt64)offset; return S_OK; } -class CExtInStream: - public IInStream, - public CMyUnknownImp -{ +Z7_CLASS_IMP_IInStream( + CExtInStream +) UInt64 _virtPos; UInt64 _phyPos; public: @@ -2277,21 +2265,15 @@ class CExtInStream: CMyComPtr Stream; CRecordVector Extents; - CExtInStream() {} - HRESULT StartSeek() { _virtPos = 0; _phyPos = 0; - return Stream->Seek(_phyPos, STREAM_SEEK_SET, NULL); + return InStream_SeekSet(Stream, _phyPos); } - - MY_UNKNOWN_IMP2(ISequentialInStream, IInStream) - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); - STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition); }; -STDMETHODIMP CExtInStream::Read(void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CExtInStream::Read(void *data, UInt32 size, UInt32 *processedSize)) { if (processedSize) *processedSize = 0; @@ -2344,18 +2326,18 @@ STDMETHODIMP CExtInStream::Read(void *data, UInt32 size, UInt32 *processedSize) return S_OK; } - UInt64 phyBlock = extent.PhyStart + bo; - UInt64 phy = (phyBlock << BlockBits) + offset; + const UInt64 phyBlock = extent.PhyStart + bo; + const UInt64 phy = (phyBlock << BlockBits) + offset; if (phy != _phyPos) { - RINOK(Stream->Seek(phy, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekSet(Stream, phy)) _phyPos = phy; } UInt32 realProcessSize = 0; - HRESULT res = Stream->Read(data, size, &realProcessSize); + const HRESULT res = Stream->Read(data, size, &realProcessSize); _phyPos += realProcessSize; _virtPos += realProcessSize; @@ -2366,7 +2348,7 @@ STDMETHODIMP CExtInStream::Read(void *data, UInt32 size, UInt32 *processedSize) } -STDMETHODIMP CExtInStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition) +Z7_COM7F_IMF(CExtInStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition)) { switch (seekOrigin) { @@ -2377,9 +2359,9 @@ STDMETHODIMP CExtInStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosi } if (offset < 0) return HRESULT_WIN32_ERROR_NEGATIVE_SEEK; - _virtPos = offset; + _virtPos = (UInt64)offset; if (newPosition) - *newPosition = offset; + *newPosition = (UInt64)offset; return S_OK; } @@ -2393,7 +2375,7 @@ HRESULT CHandler::FillFileBlocks2(UInt32 block, unsigned level, unsigned numBloc PRF2(printf("\n level = %d, block = %7d", level, (unsigned)block)); - RINOK(SeekAndRead(_stream, block, tempBuf, blockSize)); + RINOK(SeekAndRead(_stream, block, tempBuf, blockSize)) const Byte *p = tempBuf; size_t num = (size_t)1 << (_h.BlockBits - 2); @@ -2424,7 +2406,7 @@ HRESULT CHandler::FillFileBlocks2(UInt32 block, unsigned level, unsigned numBloc return S_FALSE; } - RINOK(FillFileBlocks2(val, level - 1, numBlocks, blocks)); + RINOK(FillFileBlocks2(val, level - 1, numBlocks, blocks)) continue; } @@ -2480,7 +2462,7 @@ HRESULT CHandler::FillFileBlocks(const Byte *p, unsigned numBlocks, CRecordVecto return S_FALSE; } - RINOK(FillFileBlocks2(val, level, numBlocks, blocks)); + RINOK(FillFileBlocks2(val, level, numBlocks, blocks)) } return S_OK; @@ -2576,8 +2558,8 @@ HRESULT CHandler::FillExtents(const Byte *p, size_t size, CRecordVector if (!UpdateExtents(extents, e.VirtBlock)) return S_FALSE; - RINOK(SeekAndRead(_stream, e.PhyLeaf, tempBuf, blockSize)); - RINOK(FillExtents(tempBuf, blockSize, extents, eth.Depth)); + RINOK(SeekAndRead(_stream, e.PhyLeaf, tempBuf, blockSize)) + RINOK(FillExtents(tempBuf, blockSize, extents, eth.Depth)) } return S_OK; @@ -2612,7 +2594,7 @@ HRESULT CHandler::GetStream_Node(unsigned nodeIndex, ISequentialInStream **strea CMyComPtr streamTemp; - UInt64 numBlocks64 = (node.FileSize + (UInt64)(((UInt32)1 << _h.BlockBits) - 1)) >> _h.BlockBits; + const UInt64 numBlocks64 = (node.FileSize + (UInt64)(((UInt32)1 << _h.BlockBits) - 1)) >> _h.BlockBits; if (node.IsFlags_EXTENTS()) { @@ -2626,7 +2608,7 @@ HRESULT CHandler::GetStream_Node(unsigned nodeIndex, ISequentialInStream **strea streamSpec->Size = node.FileSize; streamSpec->Stream = _stream; - RINOK(FillExtents(node.Block, kNodeBlockFieldSize, streamSpec->Extents, -1)); + RINOK(FillExtents(node.Block, kNodeBlockFieldSize, streamSpec->Extents, -1)) UInt32 end = 0; if (!streamSpec->Extents.IsEmpty()) @@ -2637,7 +2619,7 @@ HRESULT CHandler::GetStream_Node(unsigned nodeIndex, ISequentialInStream **strea // return S_FALSE; } - RINOK(streamSpec->StartSeek()); + RINOK(streamSpec->StartSeek()) } else { @@ -2664,7 +2646,7 @@ HRESULT CHandler::GetStream_Node(unsigned nodeIndex, ISequentialInStream **strea } const unsigned specBits = (node.IsFlags_HUGE() ? 0 : _h.BlockBits - 9); - const UInt32 specMask = ((UInt32)1 << specBits) - 1;; + const UInt32 specMask = ((UInt32)1 << specBits) - 1; if ((node.NumBlocks & specMask) != 0) return S_FALSE; const UInt64 numBlocks64_from_header = node.NumBlocks >> specBits; @@ -2686,7 +2668,7 @@ HRESULT CHandler::GetStream_Node(unsigned nodeIndex, ISequentialInStream **strea streamSpec->Size = node.FileSize; streamSpec->Stream = _stream; - RINOK(FillFileBlocks(node.Block, numBlocks, streamSpec->Vector)); + RINOK(FillFileBlocks(node.Block, numBlocks, streamSpec->Vector)) streamSpec->InitAndSeek(); } @@ -2706,7 +2688,7 @@ HRESULT CHandler::ExtractNode(unsigned nodeIndex, CByteBuffer &data) if (size != node.FileSize) return S_FALSE; CMyComPtr inSeqStream; - RINOK(GetStream_Node(nodeIndex, &inSeqStream)); + RINOK(GetStream_Node(nodeIndex, &inSeqStream)) if (!inSeqStream) return S_FALSE; data.Alloc(size); @@ -2715,11 +2697,11 @@ HRESULT CHandler::ExtractNode(unsigned nodeIndex, CByteBuffer &data) } -STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, - Int32 testMode, IArchiveExtractCallback *extractCallback) +Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testMode, IArchiveExtractCallback *extractCallback)) { COM_TRY_BEGIN - bool allFilesMode = (numItems == (UInt32)(Int32)-1); + const bool allFilesMode = (numItems == (UInt32)(Int32)-1); if (allFilesMode) numItems = _items.Size() + _auxItems.Size(); if (numItems == 0) @@ -2730,7 +2712,7 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, for (i = 0; i < numItems; i++) { - UInt32 index = allFilesMode ? i : indices[i]; + const UInt32 index = allFilesMode ? i : indices[i]; if (index >= _items.Size()) continue; const CItem &item = _items[index]; @@ -2739,40 +2721,38 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, totalSize += node.FileSize; } - extractCallback->SetTotal(totalSize); + RINOK(extractCallback->SetTotal(totalSize)) UInt64 totalPackSize; totalSize = totalPackSize = 0; - NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder(); - CMyComPtr copyCoder = copyCoderSpec; - - CLocalProgress *lps = new CLocalProgress; - CMyComPtr progress = lps; + CMyComPtr2_Create lps; lps->Init(extractCallback, false); + CMyComPtr2_Create copyCoder; for (i = 0;; i++) { lps->InSize = totalPackSize; lps->OutSize = totalSize; - RINOK(lps->SetCur()); - - if (i == numItems) + RINOK(lps->SetCur()) + if (i >= numItems) break; + int opRes; + { CMyComPtr outStream; - Int32 askMode = testMode ? + const Int32 askMode = testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; - UInt32 index = allFilesMode ? i : indices[i]; + const UInt32 index = allFilesMode ? i : indices[i]; - RINOK(extractCallback->GetStream(index, &outStream, askMode)); + RINOK(extractCallback->GetStream(index, &outStream, askMode)) if (index >= _items.Size()) { - RINOK(extractCallback->PrepareOperation(askMode)); - RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)); + RINOK(extractCallback->PrepareOperation(askMode)) + RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)) continue; } @@ -2781,8 +2761,8 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, if (node.IsDir()) { - RINOK(extractCallback->PrepareOperation(askMode)); - RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)); + RINOK(extractCallback->PrepareOperation(askMode)) + RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)) continue; } @@ -2794,9 +2774,9 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, if (!testMode && !outStream) continue; - RINOK(extractCallback->PrepareOperation(askMode)); + RINOK(extractCallback->PrepareOperation(askMode)) - int res = NExtract::NOperationResult::kDataError; + opRes = NExtract::NOperationResult::kDataError; { CMyComPtr inSeqStream; HRESULT hres = GetStream(index, &inSeqStream); @@ -2804,30 +2784,31 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, { if (hres == E_OUTOFMEMORY) return hres; - res = NExtract::NOperationResult::kUnsupportedMethod; + opRes = NExtract::NOperationResult::kUnsupportedMethod; } else { - RINOK(hres); + RINOK(hres) { - hres = copyCoder->Code(inSeqStream, outStream, NULL, NULL, progress); + hres = copyCoder.Interface()->Code(inSeqStream, outStream, NULL, NULL, lps); if (hres == S_OK) { - if (copyCoderSpec->TotalSize == unpackSize) - res = NExtract::NOperationResult::kOK; + if (copyCoder->TotalSize == unpackSize) + opRes = NExtract::NOperationResult::kOK; } else if (hres == E_NOTIMPL) { - res = NExtract::NOperationResult::kUnsupportedMethod; + opRes = NExtract::NOperationResult::kUnsupportedMethod; } else if (hres != S_FALSE) { - RINOK(hres); + RINOK(hres) } } } } - RINOK(extractCallback->SetOperationResult(res)); + } + RINOK(extractCallback->SetOperationResult(opRes)) } return S_OK; @@ -2835,30 +2816,42 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, } -STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream) +Z7_COM7F_IMF(CHandler::GetStream(UInt32 index, ISequentialInStream **stream)) { *stream = NULL; if (index >= _items.Size()) return S_FALSE; - return GetStream_Node(_refs[_items[index].Node], stream); + return GetStream_Node((unsigned)_refs[_items[index].Node], stream); } -API_FUNC_static_IsArc IsArc_Ext(const Byte *p, size_t size) +API_FUNC_IsArc IsArc_Ext_PhySize(const Byte *p, size_t size, UInt64 *phySize); +API_FUNC_IsArc IsArc_Ext_PhySize(const Byte *p, size_t size, UInt64 *phySize) { + if (phySize) + *phySize = 0; if (size < kHeaderSize) return k_IsArc_Res_NEED_MORE; CHeader h; if (!h.Parse(p + kHeaderDataOffset)) return k_IsArc_Res_NO; + if (phySize) + *phySize = h.GetPhySize(); return k_IsArc_Res_YES; } + + +API_FUNC_IsArc IsArc_Ext(const Byte *p, size_t size); +API_FUNC_IsArc IsArc_Ext(const Byte *p, size_t size) +{ + return IsArc_Ext_PhySize(p, size, NULL); } + static const Byte k_Signature[] = { 0x53, 0xEF }; REGISTER_ARC_I( - "Ext", "ext ext2 ext3 ext4 img", 0, 0xC7, + "Ext", "ext ext2 ext3 ext4 img", NULL, 0xC7, k_Signature, 0x438, 0, diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/FatHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/FatHandler.cpp index 9c37c062..18fce91d 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/FatHandler.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/FatHandler.cpp @@ -2,13 +2,12 @@ #include "StdAfx.h" -// #include - #include "../../../C/CpuArch.h" #include "../../Common/ComTry.h" #include "../../Common/IntToString.h" #include "../../Common/MyBuffer.h" +#include "../../Common/MyBuffer2.h" #include "../../Common/MyCom.h" #include "../../Common/StringConvert.h" @@ -22,12 +21,19 @@ #include "../Compress/CopyCoder.h" -#include "Common/DummyOutStream.h" +#include "Common/ItemNameUtils.h" #define Get16(p) GetUi16(p) #define Get32(p) GetUi32(p) +#define Get16a(p) GetUi16a(p) +#define Get32a(p) GetUi32a(p) -#define PRF(x) /* x */ +#if 0 +#include +#define PRF(x) x +#else +#define PRF(x) +#endif namespace NArchive { namespace NFat { @@ -36,34 +42,34 @@ static const UInt32 kFatItemUsedByDirMask = (UInt32)1 << 31; struct CHeader { - UInt32 NumSectors; - UInt16 NumReservedSectors; + Byte NumFatBits; + Byte SectorSizeLog; + Byte SectorsPerClusterLog; + Byte ClusterSizeLog; Byte NumFats; + Byte MediaType; + + bool VolFieldsDefined; + bool HeadersWarning; + + UInt32 FatSize; + UInt32 BadCluster; + + UInt16 NumReservedSectors; + UInt32 NumSectors; UInt32 NumFatSectors; UInt32 RootDirSector; UInt32 NumRootDirSectors; UInt32 DataSector; - UInt32 FatSize; - UInt32 BadCluster; - - Byte NumFatBits; - Byte SectorSizeLog; - Byte SectorsPerClusterLog; - Byte ClusterSizeLog; - UInt16 SectorsPerTrack; UInt16 NumHeads; UInt32 NumHiddenSectors; - - bool VolFieldsDefined; UInt32 VolId; // Byte VolName[11]; // Byte FileSys[8]; - // Byte OemName[5]; - Byte MediaType; // 32-bit FAT UInt16 Flags; @@ -101,30 +107,25 @@ struct CHeader bool Parse(const Byte *p); }; -static int GetLog(UInt32 num) -{ - for (int i = 0; i < 31; i++) - if (((UInt32)1 << i) == num) - return i; - return -1; -} -static const UInt32 kHeaderSize = 512; +static const unsigned kHeaderSize = 512; -API_FUNC_static_IsArc IsArc_Fat(const Byte *p, size_t size) +API_FUNC_IsArc IsArc_Fat(const Byte *p, size_t size); +API_FUNC_IsArc IsArc_Fat(const Byte *p, size_t size) { if (size < kHeaderSize) return k_IsArc_Res_NEED_MORE; CHeader h; return h.Parse(p) ? k_IsArc_Res_YES : k_IsArc_Res_NO; } -} bool CHeader::Parse(const Byte *p) { - if (p[0x1FE] != 0x55 || p[0x1FF] != 0xAA) + if (Get16(p + 0x1FE) != 0xAA55) return false; + HeadersWarning = false; + int codeOffset = 0; switch (p[0]) { @@ -134,22 +135,40 @@ bool CHeader::Parse(const Byte *p) } { { - UInt32 val32 = Get16(p + 11); - int s = GetLog(val32); - if (s < 9 || s > 12) - return false; - SectorSizeLog = (Byte)s; + const unsigned num = Get16(p + 11); + unsigned i = 9; + unsigned m = 1 << i; + for (;;) + { + if (m == num) + break; + m <<= 1; + if (++i > 12) + return false; + } + SectorSizeLog = (Byte)i; } { - UInt32 val32 = p[13]; - int s = GetLog(val32); - if (s < 0) + const unsigned num = p[13]; + unsigned i = 0; + unsigned m = 1 << i; + for (;;) + { + if (m == num) + break; + m <<= 1; + if (++i > 7) + return false; + } + SectorsPerClusterLog = (Byte)i; + i += SectorSizeLog; + ClusterSizeLog = (Byte)i; + // (2^15 = 32 KB is safe cluster size that is suported by all system. + // (2^16 = 64 KB is supported by some systems + // (128 KB / 256 KB) can be created by some tools, but it is not supported by many tools. + if (i > 18) // 256 KB return false; - SectorsPerClusterLog = (Byte)s; } - ClusterSizeLog = (Byte)(SectorSizeLog + SectorsPerClusterLog); - if (ClusterSizeLog > 24) - return false; } NumReservedSectors = Get16(p + 14); @@ -161,9 +180,10 @@ bool CHeader::Parse(const Byte *p) return false; // we also support images that contain 0 in offset field. - bool isOkOffset = (codeOffset == 0 || (p[0] == 0xEB && p[1] == 0)); + const bool isOkOffset = (codeOffset == 0) + || (codeOffset == (p[0] == 0xEB ? 2 : 3)); - UInt16 numRootDirEntries = Get16(p + 17); + const unsigned numRootDirEntries = Get16(p + 17); if (numRootDirEntries == 0) { if (codeOffset < 90 && !isOkOffset) @@ -177,18 +197,21 @@ bool CHeader::Parse(const Byte *p) if (codeOffset < 62 - 24 && !isOkOffset) return false; NumFatBits = 0; - UInt32 mask = (1 << (SectorSizeLog - 5)) - 1; - if ((numRootDirEntries & mask) != 0) + const unsigned mask = (1u << (SectorSizeLog - 5)) - 1; + if (numRootDirEntries & mask) return false; - NumRootDirSectors = (numRootDirEntries + mask) >> (SectorSizeLog - 5); + NumRootDirSectors = (numRootDirEntries /* + mask */) >> (SectorSizeLog - 5); } NumSectors = Get16(p + 19); if (NumSectors == 0) NumSectors = Get32(p + 32); + /* + // mkfs.fat could create fat32 image with 16-bit number of sectors. + // v23: we allow 16-bit value for number of sectors in fat32. else if (IsFat32()) return false; - + */ MediaType = p[21]; NumFatSectors = Get16(p + 22); SectorsPerTrack = Get16(p + 24); @@ -212,7 +235,7 @@ bool CHeader::Parse(const Byte *p) return false; RootCluster = Get32(p + 8); FsInfoSector = Get16(p + 12); - for (int i = 16; i < 28; i++) + for (unsigned i = 16; i < 28; i++) if (p[i] != 0) return false; p += 28; @@ -240,124 +263,191 @@ bool CHeader::Parse(const Byte *p) DataSector = RootDirSector + NumRootDirSectors; if (NumSectors < DataSector) return false; - UInt32 numDataSectors = NumSectors - DataSector; - UInt32 numClusters = numDataSectors >> SectorsPerClusterLog; + const UInt32 numDataSectors = NumSectors - DataSector; + const UInt32 numClusters = numDataSectors >> SectorsPerClusterLog; BadCluster = 0x0FFFFFF7; - if (numClusters < 0xFFF5) + // v23: we support unusual (< 0xFFF5) numClusters values in fat32 systems + if (NumFatBits != 32) { - if (NumFatBits == 32) + if (numClusters >= 0xFFF5) return false; NumFatBits = (Byte)(numClusters < 0xFF5 ? 12 : 16); - BadCluster &= ((1 << NumFatBits) - 1); + BadCluster &= (((UInt32)1 << NumFatBits) - 1); } - else if (NumFatBits != 32) - return false; FatSize = numClusters + 2; - if (FatSize > BadCluster || CalcFatSizeInSectors() > NumFatSectors) + if (FatSize > BadCluster) return false; + if (CalcFatSizeInSectors() > NumFatSectors) + { + /* some third-party program can create such FAT image, where + size of FAT table (NumFatSectors from headers) is smaller than + required value that is calculated from calculated (FatSize) value. + Another FAT unpackers probably ignore that error. + v23.02: we also ignore that error, and + we recalculate (FatSize) value from (NumFatSectors). + New (FatSize) will be smaller than original "full" (FatSize) value. + So we will have some unused clusters at the end of archive. + */ + FatSize = (UInt32)(((UInt64)NumFatSectors << (3 + SectorSizeLog)) / NumFatBits); + HeadersWarning = true; + } return true; } -struct CItem + + +class CItem { - UString UName; - char DosName[11]; + Z7_CLASS_NO_COPY(CItem) +public: + UInt32 Size; + Byte Attrib; Byte CTime2; - UInt32 CTime; - UInt32 MTime; UInt16 ADate; - Byte Attrib; + CByteBuffer LongName; // if LongName.Size() == 0 : no long name + // if LongName.Size() != 0 : it's NULL terminated UTF16-LE string. + char DosName[11]; Byte Flags; - UInt32 Size; + UInt32 MTime; + UInt32 CTime; UInt32 Cluster; Int32 Parent; + CItem() {} + // NT uses Flags to store Low Case status bool NameIsLow() const { return (Flags & 0x8) != 0; } bool ExtIsLow() const { return (Flags & 0x10) != 0; } bool IsDir() const { return (Attrib & 0x10) != 0; } - UString GetShortName() const; - UString GetName() const; - UString GetVolName() const; + void GetShortName(UString &dest) const; + void GetName(UString &name) const; }; -static unsigned CopyAndTrim(char *dest, const char *src, unsigned size, bool toLower) + +static char *CopyAndTrim(char *dest, const char *src, + unsigned size, unsigned toLower) { - memcpy(dest, src, size); - if (toLower) + do { - for (unsigned i = 0; i < size; i++) + if (src[(size_t)size - 1] != ' ') { - char c = dest[i]; - if (c >= 'A' && c <= 'Z') - dest[i] = (char)(c + 0x20); + const unsigned range = toLower ? 'Z' - 'A' + 1 : 0; + do + { + unsigned c = (Byte)*src++; + if ((unsigned)(c - 'A') < range) + c += 0x20; + *dest++ = (char)c; + } + while (--size); + break; } } - - for (unsigned i = size;;) - { - if (i == 0) - return 0; - if (dest[i - 1] != ' ') - return i; - i--; - } + while (--size); + *dest = 0; + return dest; } -static UString FatStringToUnicode(const char *s) + +static void FatStringToUnicode(UString &dest, const char *s) { - return MultiByteToUnicodeString(s, CP_OEMCP); + MultiByteToUnicodeString2(dest, AString(s), CP_OEMCP); } -UString CItem::GetShortName() const +void CItem::GetShortName(UString &shortName) const { char s[16]; - unsigned i = CopyAndTrim(s, DosName, 8, NameIsLow()); - s[i++] = '.'; - unsigned j = CopyAndTrim(s + i, DosName + 8, 3, ExtIsLow()); - if (j == 0) - i--; - s[i + j] = 0; - return FatStringToUnicode(s); + char *dest = CopyAndTrim(s, DosName, 8, NameIsLow()); + *dest++ = '.'; + char *dest2 = CopyAndTrim(dest, DosName + 8, 3, ExtIsLow()); + if (dest == dest2) + dest[-1] = 0; + FatStringToUnicode(shortName, s); } -UString CItem::GetName() const + + +// numWords != 0 +static unsigned ParseLongName(UInt16 *buf, unsigned numWords) { - if (!UName.IsEmpty()) - return UName; - return GetShortName(); + unsigned i; + for (i = 0; i < numWords; i++) + { + const unsigned c = buf[i]; + if (c == 0) + break; + if (c == 0xFFFF) + return 0; + } + if (i == 0) + return 0; + buf[i] = 0; + numWords -= i; + i++; + if (numWords > 1) + { + numWords--; + buf += i; + do + if (*buf++ != 0xFFFF) + return 0; + while (--numWords); + } + return i; // it includes NULL terminator } -UString CItem::GetVolName() const + +void CItem::GetName(UString &name) const +{ + if (LongName.Size() >= 2) + { + const Byte * const p = LongName; + const unsigned numWords = ((unsigned)LongName.Size() - 2) / 2; + wchar_t *dest = name.GetBuf(numWords); + for (unsigned i = 0; i < numWords; i++) + dest[i] = (wchar_t)Get16(p + (size_t)i * 2); + name.ReleaseBuf_SetEnd(numWords); + } + else + GetShortName(name); + if (name.IsEmpty()) // it's unexpected + name = '_'; + NItemName::NormalizeSlashes_in_FileName_for_OsPath(name); +} + + +static void GetVolName(const char dosName[11], NWindows::NCOM::CPropVariant &prop) { - if (!UName.IsEmpty()) - return UName; char s[12]; - unsigned i = CopyAndTrim(s, DosName, 11, false); - s[i] = 0; - return FatStringToUnicode(s); + CopyAndTrim(s, dosName, 11, false); + UString u; + FatStringToUnicode(u, AString(s)); + prop = u; } + struct CDatabase { - CHeader Header; CObjectVector Items; UInt32 *Fat; + CHeader Header; CMyComPtr InStream; IArchiveOpenCallback *OpenCallback; + CAlignedBuffer ByteBuf; + CByteBuffer LfnBuf; UInt32 NumFreeClusters; - bool VolItemDefined; - CItem VolItem; UInt32 NumDirClusters; - CByteBuffer ByteBuf; UInt64 NumCurUsedBytes; - UInt64 PhySize; - CDatabase(): Fat(0) {} + UInt32 Vol_MTime; + char VolLabel[11]; + bool VolItem_Defined; + + CDatabase(): Fat(NULL) {} ~CDatabase() { ClearAndClose(); } void Clear(); @@ -365,7 +455,7 @@ struct CDatabase HRESULT OpenProgressFat(bool changeTotal = true); HRESULT OpenProgress(); - UString GetItemPath(Int32 index) const; + void GetItemPath(UInt32 index, UString &s) const; HRESULT Open(); HRESULT ReadDir(Int32 parent, UInt32 cluster, unsigned level); @@ -377,21 +467,22 @@ struct CDatabase HRESULT SeekToCluster(UInt32 cluster) { return SeekToSector(Header.ClusterToSector(cluster)); } }; + HRESULT CDatabase::SeekToSector(UInt32 sector) { - return InStream->Seek((UInt64)sector << Header.SectorSizeLog, STREAM_SEEK_SET, NULL); + return InStream_SeekSet(InStream, (UInt64)sector << Header.SectorSizeLog); } void CDatabase::Clear() { PhySize = 0; - VolItemDefined = false; + VolItem_Defined = false; NumDirClusters = 0; NumCurUsedBytes = 0; Items.Clear(); delete []Fat; - Fat = 0; + Fat = NULL; } void CDatabase::ClearAndClose() @@ -406,9 +497,9 @@ HRESULT CDatabase::OpenProgressFat(bool changeTotal) return S_OK; if (changeTotal) { - UInt64 numTotalBytes = (Header.CalcFatSizeInSectors() << Header.SectorSizeLog) + + const UInt64 numTotalBytes = (Header.CalcFatSizeInSectors() << Header.SectorSizeLog) + ((UInt64)(Header.FatSize - NumFreeClusters) << Header.ClusterSizeLog); - RINOK(OpenCallback->SetTotal(NULL, &numTotalBytes)); + RINOK(OpenCallback->SetTotal(NULL, &numTotalBytes)) } return OpenCallback->SetCompleted(NULL, &NumCurUsedBytes); } @@ -417,71 +508,62 @@ HRESULT CDatabase::OpenProgress() { if (!OpenCallback) return S_OK; - UInt64 numItems = Items.Size(); + const UInt64 numItems = Items.Size(); return OpenCallback->SetCompleted(&numItems, &NumCurUsedBytes); } -UString CDatabase::GetItemPath(Int32 index) const +void CDatabase::GetItemPath(UInt32 index, UString &s) const { - const CItem *item = &Items[index]; - UString name = item->GetName(); + UString name; for (;;) { - index = item->Parent; - if (index < 0) - return name; - item = &Items[index]; - name.InsertAtFront(WCHAR_PATH_SEPARATOR); - if (item->UName.IsEmpty()) - name.Insert(0, item->GetShortName()); - else - name.Insert(0, item->UName); + const CItem &item = Items[index]; + item.GetName(name); + if (item.Parent >= 0) + name.InsertAtFront(WCHAR_PATH_SEPARATOR); + s.Insert(0, name); + index = (UInt32)item.Parent; + if (item.Parent < 0) + break; } } -static wchar_t *AddSubStringToName(wchar_t *dest, const Byte *p, unsigned numChars) -{ - for (unsigned i = 0; i < numChars; i++) - { - wchar_t c = Get16(p + i * 2); - if (c != 0 && c != 0xFFFF) - *dest++ = c; - } - *dest = 0; - return dest; -} HRESULT CDatabase::ReadDir(Int32 parent, UInt32 cluster, unsigned level) { - unsigned startIndex = Items.Size(); + const unsigned startIndex = Items.Size(); if (startIndex >= (1 << 30) || level > 256) return S_FALSE; - UInt32 sectorIndex = 0; UInt32 blockSize = Header.ClusterSize(); - bool clusterMode = (Header.IsFat32() || parent >= 0); + const bool clusterMode = (Header.IsFat32() || parent >= 0); if (!clusterMode) { blockSize = Header.SectorSize(); - RINOK(SeekToSector(Header.RootDirSector)); + RINOK(SeekToSector(Header.RootDirSector)) } ByteBuf.Alloc(blockSize); - UString curName; - int checkSum = -1; - int numLongRecords = -1; + + const unsigned k_NumLfnRecords_MAX = 20; // 260 symbols limit (strict limit) + // const unsigned k_NumLfnRecords_MAX = 0x40 - 1; // 1260 symbols limit (relaxed limit) + const unsigned k_NumLfnBytes_in_Record = 13 * 2; + // we reserve 2 additional bytes for NULL terminator + LfnBuf.Alloc(k_NumLfnRecords_MAX * k_NumLfnBytes_in_Record + 2 * 1); + UInt32 curDirBytes_read = 0; + UInt32 sectorIndex = 0; + unsigned num_lfn_records = 0; + unsigned lfn_RecordIndex = 0; + int checkSum = -1; + bool is_record_error = false; + for (UInt32 pos = blockSize;; pos += 32) { if (pos == blockSize) { pos = 0; - if ((NumDirClusters & 0xFF) == 0) - { - RINOK(OpenProgress()); - } - if (clusterMode) { if (Header.IsEoc(cluster)) @@ -489,23 +571,39 @@ HRESULT CDatabase::ReadDir(Int32 parent, UInt32 cluster, unsigned level) if (!Header.IsValidCluster(cluster)) return S_FALSE; PRF(printf("\nCluster = %4X", cluster)); - RINOK(SeekToCluster(cluster)); - UInt32 newCluster = Fat[cluster]; - if ((newCluster & kFatItemUsedByDirMask) != 0) + RINOK(SeekToCluster(cluster)) + const UInt32 newCluster = Fat[cluster]; + if (newCluster & kFatItemUsedByDirMask) return S_FALSE; Fat[cluster] |= kFatItemUsedByDirMask; cluster = newCluster; NumDirClusters++; + if ((NumDirClusters & 0xFF) == 0) + { + RINOK(OpenProgress()) + } NumCurUsedBytes += Header.ClusterSize(); } else if (sectorIndex++ >= Header.NumRootDirSectors) break; - RINOK(ReadStream_FALSE(InStream, ByteBuf, blockSize)); + // if (curDirBytes_read > (1u << 28)) // do we need some relaxed limit for non-MS FATs? + if (curDirBytes_read >= (1u << 21)) // 2MB limit from FAT specification. + return S_FALSE; + RINOK(ReadStream_FALSE(InStream, ByteBuf, blockSize)) + curDirBytes_read += blockSize; } - const Byte *p = ByteBuf + pos; - + if (is_record_error) + { + Header.HeadersWarning = true; + num_lfn_records = 0; + lfn_RecordIndex = 0; + checkSum = -1; + } + + const Byte * const p = ByteBuf + pos; + if (p[0] == 0) { /* @@ -515,128 +613,194 @@ HRESULT CDatabase::ReadDir(Int32 parent, UInt32 cluster, unsigned level) */ break; } - + + is_record_error = true; + if (p[0] == 0xE5) { - if (numLongRecords > 0) - return S_FALSE; + // deleted entry + if (num_lfn_records == 0) + is_record_error = false; continue; } - - Byte attrib = p[11]; - if ((attrib & 0x3F) == 0xF) + // else { - if (p[0] > 0x7F || Get16(p + 26) != 0) - return S_FALSE; - int longIndex = p[0] & 0x3F; - if (longIndex == 0) - return S_FALSE; - bool isLast = (p[0] & 0x40) != 0; - if (numLongRecords < 0) + const Byte attrib = p[11]; + // maybe we can use more strick check : (attrib == 0xF) ? + if ((attrib & 0x3F) == 0xF) { - if (!isLast) + // long file name (LFN) entry + const unsigned longIndex = p[0] & 0x3F; + if (longIndex == 0 + || longIndex > k_NumLfnRecords_MAX + || p[0] > 0x7F + || Get16a(p + 26) != 0 // LDIR_FstClusLO + ) + { return S_FALSE; - numLongRecords = longIndex; + // break; + } + const bool isLast = (p[0] & 0x40) != 0; + if (num_lfn_records == 0) + { + if (!isLast) + continue; // orphan + num_lfn_records = longIndex; + } + else if (isLast || longIndex != lfn_RecordIndex) + { + return S_FALSE; + // break; + } + + lfn_RecordIndex = longIndex - 1; + + if (p[12] == 0) + { + Byte * const dest = LfnBuf + k_NumLfnBytes_in_Record * lfn_RecordIndex; + memcpy(dest, p + 1, 5 * 2); + memcpy(dest + 5 * 2, p + 14, 6 * 2); + memcpy(dest + 11 * 2, p + 28, 2 * 2); + if (isLast) + checkSum = p[13]; + if (checkSum == p[13]) + is_record_error = false; + // else return S_FALSE; + continue; + } + // else + checkSum = -1; // we will ignore LfnBuf in this case + continue; } - else if (isLast || numLongRecords != longIndex) - return S_FALSE; - - numLongRecords--; - if (p[12] == 0) + if (lfn_RecordIndex) { - wchar_t nameBuf[14]; - wchar_t *dest; - - dest = AddSubStringToName(nameBuf, p + 1, 5); - dest = AddSubStringToName(dest, p + 14, 6); - AddSubStringToName(dest, p + 28, 2); - curName = nameBuf + curName; - if (isLast) - checkSum = p[13]; - if (checkSum != p[13]) - return S_FALSE; + Header.HeadersWarning = true; + // return S_FALSE; } - } - else - { - if (numLongRecords > 0) - return S_FALSE; - CItem item; - memcpy(item.DosName, p, 11); + // lfn_RecordIndex = 0; - if (checkSum >= 0) + const unsigned type_in_attrib = attrib & 0x18; + if (type_in_attrib == 0x18) { - Byte sum = 0; - for (unsigned i = 0; i < 11; i++) - sum = (Byte)(((sum & 1) ? 0x80 : 0) + (sum >> 1) + (Byte)item.DosName[i]); - if (sum == checkSum) - item.UName = curName; + // invalid directory record (both flags are set: dir_flag and volume_flag) + return S_FALSE; + // break; + // continue; } - - if (item.DosName[0] == 5) - item.DosName[0] = (char)(Byte)0xE5; - item.Attrib = attrib; - item.Flags = p[12]; - item.Size = Get32(p + 28); - item.Cluster = Get16(p + 26); - if (Header.NumFatBits > 16) - item.Cluster |= ((UInt32)Get16(p + 20) << 16); - else + if (type_in_attrib == 8) // volume_flag { - // OS/2 and WinNT probably can store EA (extended atributes) in that field. + if (!VolItem_Defined && level == 0) + { + VolItem_Defined = true; + memcpy(VolLabel, p, 11); + Vol_MTime = Get32(p + 22); + is_record_error = false; + } } - - item.CTime = Get32(p + 14); - item.CTime2 = p[13]; - item.ADate = Get16(p + 18); - item.MTime = Get32(p + 22); - item.Parent = parent; - - if (attrib == 8) + else if (memcmp(p, ". ", 11) == 0 + || memcmp(p, ".. ", 11) == 0) { - VolItem = item; - VolItemDefined = true; + if (num_lfn_records == 0 && type_in_attrib == 0x10) // dir_flag + is_record_error = false; } else - if (memcmp(item.DosName, ". ", 11) != 0 && - memcmp(item.DosName, ".. ", 11) != 0) { - if (!item.IsDir()) - NumCurUsedBytes += Header.GetFilePackSize(item.Size); - Items.Add(item); - PRF(printf("\n%7d: %S", Items.Size(), GetItemPath(Items.Size() - 1))); + CItem &item = Items.AddNew(); + memcpy(item.DosName, p, 11); + if (item.DosName[0] == 5) + item.DosName[0] = (char)(Byte)0xE5; // 0xE5 is valid KANJI lead byte value. + item.Attrib = attrib; + item.Flags = p[12]; + item.Size = Get32a(p + 28); + item.Cluster = Get16a(p + 26); + if (Header.NumFatBits > 16) + item.Cluster |= ((UInt32)Get16a(p + 20) << 16); + else + { + // OS/2 and WinNT probably can store EA (extended atributes) in that field. + } + item.CTime = Get32(p + 14); + item.CTime2 = p[13]; + item.ADate = Get16a(p + 18); + item.MTime = Get32(p + 22); + item.Parent = parent; + { + if (!item.IsDir()) + NumCurUsedBytes += Header.GetFilePackSize(item.Size); + // PRF(printf("\n%7d: %S", Items.Size(), GetItemPath(Items.Size() - 1))); + PRF(printf("\n%7d" /* ": %S" */, Items.Size() /* , item.GetShortName() */ );) + } + if (num_lfn_records == 0) + is_record_error = false; + else if (checkSum >= 0 && lfn_RecordIndex == 0) + { + Byte sum = 0; + for (unsigned i = 0; i < 11; i++) + sum = (Byte)((sum << 7) + (sum >> 1) + (Byte)item.DosName[i]); + if (sum == checkSum) + { + const unsigned numWords = ParseLongName((UInt16 *)(void *)(Byte *)LfnBuf, + num_lfn_records * k_NumLfnBytes_in_Record / 2); + if (numWords > 1) + { + // numWords includes NULL terminator + item.LongName.CopyFrom(LfnBuf, numWords * 2); + is_record_error = false; + } + } + } + + if ( + // item.LongName.Size() < 20 || // for debug + item.LongName.Size() <= 2 * 1 + && memcmp(p, " ", 11) == 0) + { + char s[16 + 16]; + const size_t numChars = (size_t)(ConvertUInt32ToString( + Items.Size() - 1 - startIndex, + MyStpCpy(s, "[NONAME]-")) - s) + 1; + item.LongName.Alloc(numChars * 2); + for (size_t i = 0; i < numChars; i++) + { + SetUi16a(item.LongName + i * 2, (Byte)s[i]) + } + Header.HeadersWarning = true; + } } - numLongRecords = -1; - curName.Empty(); - checkSum = -1; + num_lfn_records = 0; } } - unsigned finishIndex = Items.Size(); + if (is_record_error) + Header.HeadersWarning = true; + + const unsigned finishIndex = Items.Size(); for (unsigned i = startIndex; i < finishIndex; i++) { const CItem &item = Items[i]; if (item.IsDir()) { - PRF(printf("\n%S", GetItemPath(i))); - RINOK(CDatabase::ReadDir(i, item.Cluster, level + 1)); + PRF(printf("\n---- %c%c%c%c%c", item.DosName[0], item.DosName[1], item.DosName[2], item.DosName[3], item.DosName[4])); + RINOK(ReadDir((int)i, item.Cluster, level + 1)) } } return S_OK; } + + HRESULT CDatabase::Open() { Clear(); - bool numFreeClustersDefined = false; + bool numFreeClusters_Defined = false; { - Byte buf[kHeaderSize]; - RINOK(ReadStream_FALSE(InStream, buf, kHeaderSize)); - if (!Header.Parse(buf)) + UInt32 buf32[kHeaderSize / 4]; + RINOK(ReadStream_FALSE(InStream, buf32, kHeaderSize)) + if (!Header.Parse((Byte *)(void *)buf32)) return S_FALSE; UInt64 fileSize; - RINOK(InStream->Seek(0, STREAM_SEEK_END, &fileSize)); + RINOK(InStream_GetSize_SeekToEnd(InStream, fileSize)) /* we comment that check to support truncated images */ /* @@ -646,61 +810,69 @@ HRESULT CDatabase::Open() if (Header.IsFat32()) { - SeekToSector(Header.FsInfoSector); - RINOK(ReadStream_FALSE(InStream, buf, kHeaderSize)); - if (buf[0x1FE] != 0x55 || buf[0x1FF] != 0xAA) - return S_FALSE; - if (Get32(buf) == 0x41615252 && Get32(buf + 484) == 0x61417272) + if (((UInt32)Header.FsInfoSector << Header.SectorSizeLog) + kHeaderSize <= fileSize + && SeekToSector(Header.FsInfoSector) == S_OK + && ReadStream_FALSE(InStream, buf32, kHeaderSize) == S_OK + && 0xaa550000 == Get32a(buf32 + 508 / 4) + && 0x41615252 == Get32a(buf32) + && 0x61417272 == Get32a(buf32 + 484 / 4)) { - NumFreeClusters = Get32(buf + 488); - numFreeClustersDefined = (NumFreeClusters <= Header.FatSize); + NumFreeClusters = Get32a(buf32 + 488 / 4); + numFreeClusters_Defined = (NumFreeClusters <= Header.FatSize); } + else + Header.HeadersWarning = true; } } - // numFreeClustersDefined = false; // to recalculate NumFreeClusters - if (!numFreeClustersDefined) + // numFreeClusters_Defined = false; // to recalculate NumFreeClusters + if (!numFreeClusters_Defined) NumFreeClusters = 0; CByteBuffer byteBuf; Fat = new UInt32[Header.FatSize]; - RINOK(OpenProgressFat()); - RINOK(SeekToSector(Header.GetFatSector())); + RINOK(OpenProgressFat()) + RINOK(SeekToSector(Header.GetFatSector())) if (Header.NumFatBits == 32) { - const UInt32 kBufSize = (1 << 15); + const UInt32 kBufSize = 1 << 15; byteBuf.Alloc(kBufSize); - for (UInt32 i = 0; i < Header.FatSize;) + for (UInt32 i = 0;;) { UInt32 size = Header.FatSize - i; + if (size == 0) + break; const UInt32 kBufSize32 = kBufSize / 4; if (size > kBufSize32) size = kBufSize32; - UInt32 readSize = Header.SizeToSectors(size * 4) << Header.SectorSizeLog; - RINOK(ReadStream_FALSE(InStream, byteBuf, readSize)); + const UInt32 readSize = Header.SizeToSectors(size * 4) << Header.SectorSizeLog; + RINOK(ReadStream_FALSE(InStream, byteBuf, readSize)) NumCurUsedBytes += readSize; - const UInt32 *src = (const UInt32 *)(const Byte *)byteBuf; + const UInt32 *src = (const UInt32 *)(const void *)(const Byte *)byteBuf; UInt32 *dest = Fat + i; - if (numFreeClustersDefined) - for (UInt32 j = 0; j < size; j++) - dest[j] = Get32(src + j) & 0x0FFFFFFF; + const UInt32 *srcLim = src + size; + if (numFreeClusters_Defined) + do + *dest++ = Get32a(src) & 0x0FFFFFFF; + while (++src != srcLim); else { UInt32 numFreeClusters = 0; - for (UInt32 j = 0; j < size; j++) + do { - UInt32 v = Get32(src + j) & 0x0FFFFFFF; + const UInt32 v = Get32a(src) & 0x0FFFFFFF; + *dest++ = v; numFreeClusters += (UInt32)(v - 1) >> 31; - dest[j] = v; } + while (++src != srcLim); NumFreeClusters += numFreeClusters; } i += size; if ((i & 0xFFFFF) == 0) { - RINOK(OpenProgressFat(!numFreeClustersDefined)); + RINOK(OpenProgressFat(!numFreeClusters_Defined)) } } } @@ -710,17 +882,17 @@ HRESULT CDatabase::Open() NumCurUsedBytes += kBufSize; byteBuf.Alloc(kBufSize); Byte *p = byteBuf; - RINOK(ReadStream_FALSE(InStream, p, kBufSize)); - UInt32 fatSize = Header.FatSize; + RINOK(ReadStream_FALSE(InStream, p, kBufSize)) + const UInt32 fatSize = Header.FatSize; UInt32 *fat = &Fat[0]; if (Header.NumFatBits == 16) for (UInt32 j = 0; j < fatSize; j++) - fat[j] = Get16(p + j * 2); + fat[j] = Get16a(p + j * 2); else for (UInt32 j = 0; j < fatSize; j++) fat[j] = (Get16(p + j * 3 / 2) >> ((j & 1) << 2)) & 0xFFF; - if (!numFreeClustersDefined) + if (!numFreeClusters_Defined) { UInt32 numFreeClusters = 0; for (UInt32 i = 0; i < fatSize; i++) @@ -729,33 +901,39 @@ HRESULT CDatabase::Open() } } - RINOK(OpenProgressFat()); + RINOK(OpenProgressFat()) if ((Fat[0] & 0xFF) != Header.MediaType) - return S_FALSE; + { + // that case can mean error in FAT, + // but xdf file: (MediaType == 0xF0 && Fat[0] == 0xFF9) + // 19.00: so we use non-strict check + if ((Fat[0] & 0xFF) < 0xF0) + return S_FALSE; + } - RINOK(ReadDir(-1, Header.RootCluster, 0)); + RINOK(ReadDir(-1, Header.RootCluster, 0)) PhySize = Header.GetPhySize(); return S_OK; } -class CHandler: + + +Z7_class_CHandler_final: public IInArchive, + public IArchiveGetRawProps, public IInArchiveGetStream, public CMyUnknownImp, CDatabase { -public: - MY_UNKNOWN_IMP2(IInArchive, IInArchiveGetStream) - INTERFACE_IInArchive(;) - STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream); + Z7_IFACES_IMP_UNK_3(IInArchive, IArchiveGetRawProps, IInArchiveGetStream) }; -STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream) +Z7_COM7F_IMF(CHandler::GetStream(UInt32 index, ISequentialInStream **stream)) { COM_TRY_BEGIN - *stream = 0; + *stream = NULL; const CItem &item = Items[index]; CClusterInStream *streamSpec = new CClusterInStream; CMyComPtr streamTemp = streamSpec; @@ -764,7 +942,7 @@ STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream) streamSpec->BlockSizeLog = Header.ClusterSizeLog; streamSpec->Size = item.Size; - UInt32 numClusters = Header.GetNumClusters(item.Size); + const UInt32 numClusters = Header.GetNumClusters(item.Size); streamSpec->Vector.ClearAndReserve(numClusters); UInt32 cluster = item.Cluster; UInt32 size = item.Size; @@ -776,7 +954,7 @@ STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream) } else { - UInt32 clusterSize = Header.ClusterSize(); + const UInt32 clusterSize = Header.ClusterSize(); for (;; size -= clusterSize) { if (!Header.IsValidCluster(cluster)) @@ -789,12 +967,14 @@ STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream) if (!Header.IsEocAndUnused(cluster)) return S_FALSE; } - RINOK(streamSpec->InitAndSeek()); + RINOK(streamSpec->InitAndSeek()) *stream = streamTemp.Detach(); return S_OK; COM_TRY_END } + + static const Byte kProps[] = { kpidPath, @@ -806,6 +986,7 @@ static const Byte kProps[] = kpidATime, kpidAttrib, kpidShortName + // , kpidCharacts }; enum @@ -839,17 +1020,18 @@ static const CStatProp kArcProps[] = IMP_IInArchive_Props IMP_IInArchive_ArcProps_WITH_NAME + static void FatTimeToProp(UInt32 dosTime, UInt32 ms10, NWindows::NCOM::CPropVariant &prop) { FILETIME localFileTime, utc; - if (NWindows::NTime::DosTimeToFileTime(dosTime, localFileTime)) + if (NWindows::NTime::DosTime_To_FileTime(dosTime, localFileTime)) if (LocalFileTimeToFileTime(&localFileTime, &utc)) { UInt64 t64 = (((UInt64)utc.dwHighDateTime) << 32) + utc.dwLowDateTime; t64 += ms10 * 100000; utc.dwLowDateTime = (DWORD)t64; utc.dwHighDateTime = (DWORD)(t64 >> 32); - prop = utc; + prop.SetAsTimeFrom_FT_Prec(utc, k_PropVar_TimePrec_Base + 2); } } @@ -865,7 +1047,7 @@ static void StringToProp(const Byte *src, unsigned size, NWindows::NCOM::CPropVa #define STRING_TO_PROP(s, p) StringToProp(s, sizeof(s), prop) */ -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NWindows::NCOM::CPropVariant prop; @@ -885,37 +1067,112 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) case kpidPhySize: prop = PhySize; break; case kpidFreeSpace: prop = (UInt64)NumFreeClusters << Header.ClusterSizeLog; break; case kpidHeadersSize: prop = GetHeadersSize(); break; - case kpidMTime: if (VolItemDefined) FatTimeToProp(VolItem.MTime, 0, prop); break; + case kpidMTime: if (VolItem_Defined) PropVariant_SetFrom_DosTime(prop, Vol_MTime); break; case kpidShortComment: - case kpidVolumeName: if (VolItemDefined) prop = VolItem.GetVolName(); break; + case kpidVolumeName: if (VolItem_Defined) GetVolName(VolLabel, prop); break; case kpidNumFats: if (Header.NumFats != 2) prop = Header.NumFats; break; case kpidSectorSize: prop = (UInt32)1 << Header.SectorSizeLog; break; // case kpidSectorsPerTrack: prop = Header.SectorsPerTrack; break; // case kpidNumHeads: prop = Header.NumHeads; break; // case kpidOemName: STRING_TO_PROP(Header.OemName, prop); break; case kpidId: if (Header.VolFieldsDefined) prop = Header.VolId; break; + case kpidIsTree: prop = true; break; // case kpidVolName: if (Header.VolFieldsDefined) STRING_TO_PROP(Header.VolName, prop); break; // case kpidFileSysType: if (Header.VolFieldsDefined) STRING_TO_PROP(Header.FileSys, prop); break; // case kpidHiddenSectors: prop = Header.NumHiddenSectors; break; + case kpidWarningFlags: + { + UInt32 v = 0; + if (Header.HeadersWarning) v |= kpv_ErrorFlags_HeadersError; + if (v != 0) + prop = v; + break; + } } prop.Detach(value); return S_OK; COM_TRY_END } -STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) + +Z7_COM7F_IMF(CHandler::GetNumRawProps(UInt32 *numProps)) +{ + *numProps = 0; + return S_OK; +} + +Z7_COM7F_IMF(CHandler::GetRawPropInfo(UInt32 /* index */ , BSTR *name, PROPID *propID)) +{ + *name = NULL; + *propID = 0; + return S_OK; +} + +Z7_COM7F_IMF(CHandler::GetParent(UInt32 index, UInt32 *parent, UInt32 *parentType)) +{ + *parentType = NParentType::kDir; + int par = -1; + if (index < Items.Size()) + par = Items[index].Parent; + *parent = (UInt32)(Int32)par; + return S_OK; +} + +Z7_COM7F_IMF(CHandler::GetRawProp(UInt32 index, PROPID propID, const void **data, UInt32 *dataSize, UInt32 *propType)) +{ + *data = NULL; + *dataSize = 0; + *propType = 0; + + if (index < Items.Size() + && propID == kpidName) + { + CByteBuffer &buf = Items[index].LongName; + const UInt32 size = (UInt32)buf.Size(); + if (size != 0) + { + *dataSize = size; + *propType = NPropDataType::kUtf16z; + *data = (const void *)(const Byte *)buf; + } + } + return S_OK; +} + + +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NWindows::NCOM::CPropVariant prop; const CItem &item = Items[index]; switch (propID) { - case kpidPath: prop = GetItemPath(index); break; - case kpidShortName: prop = item.GetShortName(); break; + case kpidPath: + case kpidName: + case kpidShortName: + { + UString s; + if (propID == kpidPath) + GetItemPath(index, s); + else if (propID == kpidName) + item.GetName(s); + else + item.GetShortName(s); + prop = s; + break; + } +/* + case kpidCharacts: + { + if (item.LongName.Size()) + prop = "LFN"; + break; + } +*/ case kpidIsDir: prop = item.IsDir(); break; - case kpidMTime: FatTimeToProp(item.MTime, 0, prop); break; + case kpidMTime: PropVariant_SetFrom_DosTime(prop, item.MTime); break; case kpidCTime: FatTimeToProp(item.CTime, item.CTime2, prop); break; - case kpidATime: FatTimeToProp(((UInt32)item.ADate << 16), 0, prop); break; + case kpidATime: PropVariant_SetFrom_DosTime(prop, ((UInt32)item.ADate << 16)); break; case kpidAttrib: prop = (UInt32)item.Attrib; break; case kpidSize: if (!item.IsDir()) prop = item.Size; break; case kpidPackSize: if (!item.IsDir()) prop = Header.GetFilePackSize(item.Size); break; @@ -925,7 +1182,7 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val COM_TRY_END } -STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *callback) +Z7_COM7F_IMF(CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *callback)) { COM_TRY_BEGIN { @@ -949,96 +1206,107 @@ STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallb COM_TRY_END } -STDMETHODIMP CHandler::Close() +Z7_COM7F_IMF(CHandler::Close()) { ClearAndClose(); return S_OK; } -STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, - Int32 testMode, IArchiveExtractCallback *extractCallback) +Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testMode, IArchiveExtractCallback *extractCallback)) { COM_TRY_BEGIN - bool allFilesMode = (numItems == (UInt32)(Int32)-1); - if (allFilesMode) + if (numItems == (UInt32)(Int32)-1) + { + indices = NULL; numItems = Items.Size(); - if (numItems == 0) - return S_OK; - UInt32 i; + if (numItems == 0) + return S_OK; + } + else + { + if (numItems == 0) + return S_OK; + if (!indices) + return E_INVALIDARG; + } UInt64 totalSize = 0; - for (i = 0; i < numItems; i++) { - const CItem &item = Items[allFilesMode ? i : indices[i]]; - if (!item.IsDir()) - totalSize += item.Size; + UInt32 i = 0; + do + { + UInt32 index = i; + if (indices) + index = indices[i]; + const CItem &item = Items[index]; + if (!item.IsDir()) + totalSize += item.Size; + } + while (++i != numItems); } - RINOK(extractCallback->SetTotal(totalSize)); - - UInt64 totalPackSize; - totalSize = totalPackSize = 0; - - NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder(); - CMyComPtr copyCoder = copyCoderSpec; + RINOK(extractCallback->SetTotal(totalSize)) - CLocalProgress *lps = new CLocalProgress; - CMyComPtr progress = lps; + CMyComPtr2_Create lps; lps->Init(extractCallback, false); + CMyComPtr2_Create copyCoder; - CDummyOutStream *outStreamSpec = new CDummyOutStream; - CMyComPtr outStream(outStreamSpec); + UInt64 totalPackSize; + totalSize = totalPackSize = 0; - for (i = 0; i < numItems; i++) + UInt32 i; + for (i = 0;; i++) { lps->InSize = totalPackSize; lps->OutSize = totalSize; - RINOK(lps->SetCur()); - CMyComPtr realOutStream; - Int32 askMode = testMode ? - NExtract::NAskMode::kTest : - NExtract::NAskMode::kExtract; - Int32 index = allFilesMode ? i : indices[i]; - const CItem &item = Items[index]; - RINOK(extractCallback->GetStream(index, &realOutStream, askMode)); - - if (item.IsDir()) - { - RINOK(extractCallback->PrepareOperation(askMode)); - RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)); - continue; - } - - totalPackSize += Header.GetFilePackSize(item.Size); - totalSize += item.Size; - - if (!testMode && !realOutStream) - continue; - RINOK(extractCallback->PrepareOperation(askMode)); - - outStreamSpec->SetStream(realOutStream); - realOutStream.Release(); - outStreamSpec->Init(); - - int res = NExtract::NOperationResult::kDataError; - CMyComPtr inStream; - HRESULT hres = GetStream(index, &inStream); - if (hres != S_FALSE) + RINOK(lps->SetCur()) + if (i == numItems) + break; + int res; { - RINOK(hres); - if (inStream) + CMyComPtr realOutStream; + const Int32 askMode = testMode ? + NExtract::NAskMode::kTest : + NExtract::NAskMode::kExtract; + UInt32 index = i; + if (indices) + index = indices[i]; + const CItem &item = Items[index]; + RINOK(extractCallback->GetStream(index, &realOutStream, askMode)) + + if (item.IsDir()) { - RINOK(copyCoder->Code(inStream, outStream, NULL, NULL, progress)); - if (copyCoderSpec->TotalSize == item.Size) - res = NExtract::NOperationResult::kOK; + RINOK(extractCallback->PrepareOperation(askMode)) + RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)) + continue; + } + + totalPackSize += Header.GetFilePackSize(item.Size); + totalSize += item.Size; + + if (!testMode && !realOutStream) + continue; + RINOK(extractCallback->PrepareOperation(askMode)) + res = NExtract::NOperationResult::kDataError; + CMyComPtr inStream; + const HRESULT hres = GetStream(index, &inStream); + if (hres != S_FALSE) + { + RINOK(hres) + if (inStream) + { + RINOK(copyCoder.Interface()->Code(inStream, realOutStream, NULL, NULL, lps)) + if (copyCoder->TotalSize == item.Size) + res = NExtract::NOperationResult::kOK; + } } } - outStreamSpec->ReleaseStream(); - RINOK(extractCallback->SetOperationResult(res)); + RINOK(extractCallback->SetOperationResult(res)) } return S_OK; COM_TRY_END } -STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) { *numItems = Items.Size(); return S_OK; @@ -1047,7 +1315,7 @@ STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) static const Byte k_Signature[] = { 0x55, 0xAA }; REGISTER_ARC_I( - "FAT", "fat img", 0, 0xDA, + "FAT", "fat img", NULL, 0xDA, k_Signature, 0x1FE, 0, diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/FlvHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/FlvHandler.cpp index 1f52f60b..1f746aa9 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/FlvHandler.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/FlvHandler.cpp @@ -23,14 +23,14 @@ ((UInt32)((const Byte *)(p))[1] << 8) | \ ((const Byte *)(p))[2] ) -#define Get16(p) GetBe16(p) +// #define Get16(p) GetBe16(p) #define Get24(p) GetBe24(p) #define Get32(p) GetBe32(p) namespace NArchive { namespace NFlv { -static const UInt32 kFileSizeMax = (UInt32)1 << 30; +// static const UInt32 kFileSizeMax = (UInt32)1 << 30; static const UInt32 kNumChunksMax = (UInt32)1 << 23; static const UInt32 kTagHeaderSize = 11; @@ -64,11 +64,10 @@ struct CItem2 bool IsAudio() const { return Type == kType_Audio; } }; -class CHandler: - public IInArchive, - public IInArchiveGetStream, - public CMyUnknownImp -{ + +Z7_CLASS_IMP_CHandler_IInArchive_1( + IInArchiveGetStream +) CMyComPtr _stream; CObjectVector _items2; CByteBuffer _metadata; @@ -77,10 +76,6 @@ class CHandler: HRESULT Open2(IInStream *stream, IArchiveOpenCallback *callback); // AString GetComment(); -public: - MY_UNKNOWN_IMP2(IInArchive, IInArchiveGetStream) - INTERFACE_IInArchive(;) - STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream); }; static const Byte kProps[] = @@ -141,7 +136,7 @@ static const char * const g_Rates[4] = , "44 kHz" }; -STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) { NWindows::NCOM::CPropVariant prop; const CItem2 &item = _items2[index]; @@ -252,7 +247,7 @@ AString CHandler::GetComment() } */ -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { // COM_TRY_BEGIN NWindows::NCOM::CPropVariant prop; @@ -271,7 +266,7 @@ HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *callback) { const UInt32 kHeaderSize = 13; Byte header[kHeaderSize]; - RINOK(ReadStream_FALSE(stream, header, kHeaderSize)); + RINOK(ReadStream_FALSE(stream, header, kHeaderSize)) if (header[0] != 'F' || header[1] != 'L' || header[2] != 'V' || @@ -358,7 +353,7 @@ HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *callback) item2.Props = props; item2.NumChunks = 1; item2.SameSubTypes = true; - lasts[item.Type] = _items2.Add(item2); + lasts[item.Type] = (int)_items2.Add(item2); } else { @@ -420,7 +415,7 @@ HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *callback) return S_OK; } -STDMETHODIMP CHandler::Open(IInStream *inStream, const UInt64 *, IArchiveOpenCallback *callback) +Z7_COM7F_IMF(CHandler::Open(IInStream *inStream, const UInt64 *, IArchiveOpenCallback *callback)) { COM_TRY_BEGIN Close(); @@ -441,7 +436,7 @@ STDMETHODIMP CHandler::Open(IInStream *inStream, const UInt64 *, IArchiveOpenCal COM_TRY_END } -STDMETHODIMP CHandler::Close() +Z7_COM7F_IMF(CHandler::Close()) { _phySize = 0; _stream.Release(); @@ -450,17 +445,17 @@ STDMETHODIMP CHandler::Close() return S_OK; } -STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) { *numItems = _items2.Size(); return S_OK; } -STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, - Int32 testMode, IArchiveExtractCallback *extractCallback) +Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testMode, IArchiveExtractCallback *extractCallback)) { COM_TRY_BEGIN - bool allFilesMode = (numItems == (UInt32)(Int32)-1); + const bool allFilesMode = (numItems == (UInt32)(Int32)-1); if (allFilesMode) numItems = _items2.Size(); if (numItems == 0) @@ -480,32 +475,32 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, for (i = 0; i < numItems; i++) { lps->InSize = lps->OutSize = totalSize; - RINOK(lps->SetCur()); + RINOK(lps->SetCur()) CMyComPtr outStream; - Int32 askMode = testMode ? + const Int32 askMode = testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; - UInt32 index = allFilesMode ? i : indices[i]; + const UInt32 index = allFilesMode ? i : indices[i]; const CItem2 &item = _items2[index]; - RINOK(extractCallback->GetStream(index, &outStream, askMode)); + RINOK(extractCallback->GetStream(index, &outStream, askMode)) totalSize += item.Size; if (!testMode && !outStream) continue; - RINOK(extractCallback->PrepareOperation(askMode)); + RINOK(extractCallback->PrepareOperation(askMode)) if (outStream) { - RINOK(WriteStream(outStream, item.BufSpec->Buf, item.BufSpec->Buf.Size())); + RINOK(WriteStream(outStream, item.BufSpec->Buf, item.BufSpec->Buf.Size())) } - RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)); + RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)) } return S_OK; COM_TRY_END } -STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream) +Z7_COM7F_IMF(CHandler::GetStream(UInt32 index, ISequentialInStream **stream)) { COM_TRY_BEGIN - *stream = 0; + *stream = NULL; CBufInStream *streamSpec = new CBufInStream; CMyComPtr streamTemp = streamSpec; streamSpec->Init(_items2[index].BufSpec); @@ -517,7 +512,7 @@ STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream) static const Byte k_Signature[] = { 'F', 'L', 'V', 1, }; REGISTER_ARC_I( - "FLV", "flv", 0, 0xD6, + "FLV", "flv", NULL, 0xD6, k_Signature, 0, 0, diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/GptHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/GptHandler.cpp index 52c7aab9..4c291c45 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/GptHandler.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/GptHandler.cpp @@ -23,14 +23,20 @@ using namespace NWindows; namespace NArchive { + +namespace NMbr { +const char *GetFileSystem(ISequentialInStream *stream, UInt64 partitionSize); +} + +namespace NFat { +API_FUNC_IsArc IsArc_Fat(const Byte *p, size_t size); +} + namespace NGpt { -#define SIGNATURE { 'E', 'F', 'I', ' ', 'P', 'A', 'R', 'T', 0, 0, 1, 0 } - static const unsigned k_SignatureSize = 12; -static const Byte k_Signature[k_SignatureSize] = SIGNATURE; - -static const UInt32 kSectorSize = 512; +static const Byte k_Signature[k_SignatureSize] = + { 'E', 'F', 'I', ' ', 'P', 'A', 'R', 'T', 0, 0, 1, 0 }; static const CUInt32PCharPair g_PartitionFlags[] = { @@ -51,6 +57,7 @@ struct CPartition UInt64 FirstLba; UInt64 LastLba; UInt64 Flags; + const char *Ext; // detected later Byte Name[kNameLen * 2]; bool IsUnused() const @@ -61,9 +68,9 @@ struct CPartition return true; } - UInt64 GetSize() const { return (LastLba - FirstLba + 1) * kSectorSize; } - UInt64 GetPos() const { return FirstLba * kSectorSize; } - UInt64 GetEnd() const { return (LastLba + 1) * kSectorSize; } + UInt64 GetSize(unsigned sectorSizeLog) const { return (LastLba - FirstLba + 1) << sectorSizeLog; } + UInt64 GetPos(unsigned sectorSizeLog) const { return FirstLba << sectorSizeLog; } + UInt64 GetEnd(unsigned sectorSizeLog) const { return (LastLba + 1) << sectorSizeLog; } void Parse(const Byte *p) { @@ -73,6 +80,7 @@ struct CPartition LastLba = Get64(p + 40); Flags = Get64(p + 48); memcpy(Name, p + 56, kNameLen * 2); + Ext = NULL; } }; @@ -86,146 +94,138 @@ struct CPartType static const CPartType kPartTypes[] = { - // { 0x0, 0, "Unused" }, + // { 0x0, NULL, "Unused" }, - { 0x21686148, 0, "BIOS Boot" }, + { 0x21686148, NULL, "BIOS Boot" }, - { 0xC12A7328, 0, "EFI System" }, - { 0x024DEE41, 0, "MBR" }, + { 0xC12A7328, NULL, "EFI System" }, + { 0x024DEE41, NULL, "MBR" }, - { 0xE3C9E316, 0, "Windows MSR" }, - { 0xEBD0A0A2, 0, "Windows BDP" }, - { 0x5808C8AA, 0, "Windows LDM Metadata" }, - { 0xAF9B60A0, 0, "Windows LDM Data" }, - { 0xDE94BBA4, 0, "Windows Recovery" }, - // { 0x37AFFC90, 0, "IBM GPFS" }, - // { 0xE75CAF8F, 0, "Windows Storage Spaces" }, - - { 0x0FC63DAF, 0, "Linux Data" }, - { 0x0657FD6D, 0, "Linux Swap" }, - - { 0x83BD6B9D, 0, "FreeBSD Boot" }, - { 0x516E7CB4, 0, "FreeBSD Data" }, - { 0x516E7CB5, 0, "FreeBSD Swap" }, + { 0xE3C9E316, NULL, "Windows MSR" }, + { 0xEBD0A0A2, NULL, "Windows BDP" }, + { 0x5808C8AA, NULL, "Windows LDM Metadata" }, + { 0xAF9B60A0, NULL, "Windows LDM Data" }, + { 0xDE94BBA4, NULL, "Windows Recovery" }, + // { 0x37AFFC90, NULL, "IBM GPFS" }, + // { 0xE75CAF8F, NULL, "Windows Storage Spaces" }, + + { 0x0FC63DAF, NULL, "Linux Data" }, + { 0x0657FD6D, NULL, "Linux Swap" }, + { 0x44479540, NULL, "Linux root (x86)" }, + { 0x4F68BCE3, NULL, "Linux root (x86-64)" }, + { 0x69DAD710, NULL, "Linux root (ARM)" }, + { 0xB921B045, NULL, "Linux root (ARM64)" }, + { 0x993D8D3D, NULL, "Linux root (IA-64)" }, + + + { 0x83BD6B9D, NULL, "FreeBSD Boot" }, + { 0x516E7CB4, NULL, "FreeBSD Data" }, + { 0x516E7CB5, NULL, "FreeBSD Swap" }, { 0x516E7CB6, "ufs", "FreeBSD UFS" }, - { 0x516E7CB8, 0, "FreeBSD Vinum" }, + { 0x516E7CB8, NULL, "FreeBSD Vinum" }, { 0x516E7CB8, "zfs", "FreeBSD ZFS" }, { 0x48465300, "hfsx", "HFS+" }, + { 0x7C3457EF, "apfs", "APFS" }, }; static int FindPartType(const Byte *guid) { - UInt32 val = Get32(guid); - for (unsigned i = 0; i < ARRAY_SIZE(kPartTypes); i++) + const UInt32 val = Get32(guid); + for (unsigned i = 0; i < Z7_ARRAY_SIZE(kPartTypes); i++) if (kPartTypes[i].Id == val) - return i; + return (int)i; return -1; } -static inline char GetHex(unsigned t) { return (char)(((t < 10) ? ('0' + t) : ('A' + (t - 10)))); } -static void PrintHex(unsigned v, char *s) +static void RawLeGuidToString_Upper(const Byte *g, char *s) { - s[0] = GetHex((v >> 4) & 0xF); - s[1] = GetHex(v & 0xF); + RawLeGuidToString(g, s); + // MyStringUpper_Ascii(s); } -static void ConvertUInt16ToHex4Digits(UInt32 val, char *s) throw() -{ - PrintHex(val >> 8, s); - PrintHex(val & 0xFF, s + 2); -} -static void GuidToString(const Byte *g, char *s) +Z7_class_CHandler_final: public CHandlerCont { - ConvertUInt32ToHex8Digits(Get32(g ), s); s += 8; *s++ = '-'; - ConvertUInt16ToHex4Digits(Get16(g + 4), s); s += 4; *s++ = '-'; - ConvertUInt16ToHex4Digits(Get16(g + 6), s); s += 4; *s++ = '-'; - for (unsigned i = 0; i < 8; i++) - { - if (i == 2) - *s++ = '-'; - PrintHex(g[8 + i], s); - s += 2; - } - *s = 0; -} - + Z7_IFACE_COM7_IMP(IInArchive_Cont) -class CHandler: public CHandlerCont -{ CRecordVector _items; UInt64 _totalSize; + unsigned _sectorSizeLog; Byte Guid[16]; CByteBuffer _buffer; HRESULT Open2(IInStream *stream); - virtual int GetItem_ExtractInfo(UInt32 index, UInt64 &pos, UInt64 &size) const + virtual int GetItem_ExtractInfo(UInt32 index, UInt64 &pos, UInt64 &size) const Z7_override { const CPartition &item = _items[index]; - pos = item.GetPos(); - size = item.GetSize(); + pos = item.GetPos(_sectorSizeLog); + size = item.GetSize(_sectorSizeLog); return NExtract::NOperationResult::kOK; } - -public: - INTERFACE_IInArchive_Cont(;) }; HRESULT CHandler::Open2(IInStream *stream) { - _buffer.Alloc(kSectorSize * 2); - RINOK(ReadStream_FALSE(stream, _buffer, kSectorSize * 2)); - + const unsigned kBufSize = 2 << 12; + _buffer.Alloc(kBufSize); + RINOK(ReadStream_FALSE(stream, _buffer, kBufSize)) const Byte *buf = _buffer; if (buf[0x1FE] != 0x55 || buf[0x1FF] != 0xAA) return S_FALSE; - - buf += kSectorSize; - if (memcmp(buf, k_Signature, k_SignatureSize) != 0) - return S_FALSE; + { + for (unsigned sectorSizeLog = 9;; sectorSizeLog += 3) + { + if (sectorSizeLog > 12) + return S_FALSE; + if (memcmp(buf + ((size_t)1 << sectorSizeLog), k_Signature, k_SignatureSize) == 0) + { + buf += ((size_t)1 << sectorSizeLog); + _sectorSizeLog = sectorSizeLog; + break; + } + } + } + const UInt32 kSectorSize = 1u << _sectorSizeLog; { // if (Get32(buf + 8) != 0x10000) return S_FALSE; // revision - UInt32 headerSize = Get32(buf + 12); // = 0x5C usually + const UInt32 headerSize = Get32(buf + 12); // = 0x5C usually if (headerSize > kSectorSize) return S_FALSE; - UInt32 crc = Get32(buf + 0x10); - SetUi32(_buffer + kSectorSize + 0x10, 0); + const UInt32 crc = Get32(buf + 0x10); + SetUi32(_buffer + kSectorSize + 0x10, 0) if (CrcCalc(_buffer + kSectorSize, headerSize) != crc) return S_FALSE; } // UInt32 reserved = Get32(buf + 0x14); - UInt64 curLba = Get64(buf + 0x18); + const UInt64 curLba = Get64(buf + 0x18); if (curLba != 1) return S_FALSE; - UInt64 backupLba = Get64(buf + 0x20); + const UInt64 backupLba = Get64(buf + 0x20); // UInt64 firstUsableLba = Get64(buf + 0x28); // UInt64 lastUsableLba = Get64(buf + 0x30); memcpy(Guid, buf + 0x38, 16); - UInt64 tableLba = Get64(buf + 0x48); - if (tableLba < 2) + const UInt64 tableLba = Get64(buf + 0x48); + if (tableLba < 2 || (tableLba >> (63 - _sectorSizeLog)) != 0) return S_FALSE; - UInt32 numEntries = Get32(buf + 0x50); - UInt32 entrySize = Get32(buf + 0x54); // = 128 usually - UInt32 entriesCrc = Get32(buf + 0x58); - - if (entrySize < 128 - || entrySize > (1 << 12) - || numEntries > (1 << 16) - || tableLba < 2 - || tableLba >= ((UInt64)1 << (64 - 10))) + const UInt32 numEntries = Get32(buf + 0x50); + if (numEntries > (1 << 16)) + return S_FALSE; + const UInt32 entrySize = Get32(buf + 0x54); // = 128 usually + if (entrySize < 128 || entrySize > (1 << 12)) return S_FALSE; + const UInt32 entriesCrc = Get32(buf + 0x58); - UInt32 tableSize = entrySize * numEntries; - UInt32 tableSizeAligned = (tableSize + kSectorSize - 1) & ~(kSectorSize - 1); + const UInt32 tableSize = entrySize * numEntries; + const UInt32 tableSizeAligned = (tableSize + kSectorSize - 1) & ~(kSectorSize - 1); _buffer.Alloc(tableSizeAligned); - UInt64 tableOffset = tableLba * kSectorSize; - RINOK(stream->Seek(tableOffset, STREAM_SEEK_SET, NULL)); - RINOK(ReadStream_FALSE(stream, _buffer, tableSizeAligned)); + const UInt64 tableOffset = tableLba * kSectorSize; + RINOK(InStream_SeekSet(stream, tableOffset)) + RINOK(ReadStream_FALSE(stream, _buffer, tableSizeAligned)) if (CrcCalc(_buffer, tableSize) != entriesCrc) return S_FALSE; @@ -238,21 +238,27 @@ HRESULT CHandler::Open2(IInStream *stream) item.Parse(_buffer + i * entrySize); if (item.IsUnused()) continue; - UInt64 endPos = item.GetEnd(); + if (item.LastLba < item.FirstLba) + return S_FALSE; + if ((item.LastLba >> (63 - _sectorSizeLog)) != 0) + return S_FALSE; + const UInt64 endPos = item.GetEnd(_sectorSizeLog); if (_totalSize < endPos) _totalSize = endPos; _items.Add(item); } - + + _buffer.Free(); { + if ((backupLba >> (63 - _sectorSizeLog)) != 0) + return S_FALSE; const UInt64 end = (backupLba + 1) * kSectorSize; if (_totalSize < end) _totalSize = end; } - { UInt64 fileEnd; - RINOK(stream->Seek(0, STREAM_SEEK_END, &fileEnd)); + RINOK(InStream_GetSize_SeekToEnd(stream, fileEnd)) if (_totalSize < fileEnd) { @@ -260,7 +266,7 @@ HRESULT CHandler::Open2(IInStream *stream) const UInt64 kRemMax = 1 << 22; if (rem <= kRemMax) { - RINOK(stream->Seek(_totalSize, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekSet(stream, _totalSize)) bool areThereNonZeros = false; UInt64 numZeros = 0; if (ReadZeroTail(stream, areThereNonZeros, numZeros, kRemMax) == S_OK) @@ -273,20 +279,49 @@ HRESULT CHandler::Open2(IInStream *stream) return S_OK; } -STDMETHODIMP CHandler::Open(IInStream *stream, + +Z7_COM7F_IMF(CHandler::Open(IInStream *stream, const UInt64 * /* maxCheckStartPosition */, - IArchiveOpenCallback * /* openArchiveCallback */) + IArchiveOpenCallback * /* openArchiveCallback */)) { COM_TRY_BEGIN Close(); - RINOK(Open2(stream)); + RINOK(Open2(stream)) _stream = stream; + + FOR_VECTOR (fileIndex, _items) + { + CPartition &item = _items[fileIndex]; + const int typeIndex = FindPartType(item.Type); + if (typeIndex < 0) + continue; + const CPartType &t = kPartTypes[(unsigned)typeIndex]; + if (t.Ext) + { + item.Ext = t.Ext; + continue; + } + if (t.Type && IsString1PrefixedByString2_NoCase_Ascii(t.Type, "Windows")) + { + CMyComPtr inStream; + if ( + // ((IInArchiveGetStream *)this)-> + GetStream(fileIndex, &inStream) == S_OK && inStream) + { + const char *fs = NMbr::GetFileSystem(inStream, item.GetSize(_sectorSizeLog)); + if (fs) + item.Ext = fs; + } + } + } + return S_OK; COM_TRY_END } -STDMETHODIMP CHandler::Close() +Z7_COM7F_IMF(CHandler::Close()) { + _sectorSizeLog = 0; _totalSize = 0; memset(Guid, 0, sizeof(Guid)); _items.Clear(); @@ -306,13 +341,14 @@ static const Byte kProps[] = static const Byte kArcProps[] = { + kpidSectorSize, kpidId }; IMP_IInArchive_Props IMP_IInArchive_ArcProps -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; @@ -325,10 +361,11 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) break; } case kpidPhySize: prop = _totalSize; break; + case kpidSectorSize: prop = (UInt32)((UInt32)1 << _sectorSizeLog); break; case kpidId: { char s[48]; - GuidToString(Guid, s); + RawLeGuidToString_Upper(Guid, s); prop = s; break; } @@ -338,13 +375,13 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) COM_TRY_END } -STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) { *numItems = _items.Size(); return S_OK; } -STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; @@ -355,46 +392,54 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val { case kpidPath: { + // Windows BDP partitions can have identical names. + // So we add the partition number at front UString s; - for (unsigned i = 0; i < kNameLen; i++) - { - wchar_t c = (wchar_t)Get16(item.Name + i * 2); - if (c == 0) - break; - s += c; - } - if (s.IsEmpty()) + s.Add_UInt32(index); { - char temp[16]; - ConvertUInt32ToString(index, temp); - s.AddAscii(temp); + UString s2; + for (unsigned i = 0; i < kNameLen; i++) + { + wchar_t c = (wchar_t)Get16(item.Name + i * 2); + if (c == 0) + break; + s2 += c; + } + if (!s2.IsEmpty()) + { + s.Add_Dot(); + s += s2; + } } { - int typeIndex = FindPartType(item.Type); - s += L'.'; - const char *ext = "img"; - if (typeIndex >= 0 && kPartTypes[(unsigned)typeIndex].Ext) - ext = kPartTypes[(unsigned)typeIndex].Ext; - s.AddAscii(ext); + s.Add_Dot(); + if (item.Ext) + { + AString fs (item.Ext); + fs.MakeLower_Ascii(); + s += fs; + } + else + s += "img"; } prop = s; break; } case kpidSize: - case kpidPackSize: prop = item.GetSize(); break; - case kpidOffset: prop = item.GetPos(); break; + case kpidPackSize: prop = item.GetSize(_sectorSizeLog); break; + case kpidOffset: prop = item.GetPos(_sectorSizeLog); break; case kpidFileSystem: { char s[48]; const char *res; - int typeIndex = FindPartType(item.Type); + const int typeIndex = FindPartType(item.Type); if (typeIndex >= 0 && kPartTypes[(unsigned)typeIndex].Type) res = kPartTypes[(unsigned)typeIndex].Type; else { - GuidToString(item.Type, s); + RawLeGuidToString_Upper(item.Type, s); res = s; } prop = res; @@ -404,7 +449,7 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val case kpidId: { char s[48]; - GuidToString(item.Id, s); + RawLeGuidToString_Upper(item.Id, s); prop = s; break; } @@ -417,10 +462,11 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val COM_TRY_END } +// we suppport signature only for 512-bytes sector. REGISTER_ARC_I( "GPT", "gpt mbr", NULL, 0xCB, k_Signature, - kSectorSize, + 1 << 9, 0, NULL) diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/GzHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/GzHandler.cpp index d8979ada..ca9d2461 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/GzHandler.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/GzHandler.cpp @@ -10,7 +10,7 @@ #include "../../Common/Defs.h" #include "../../Common/StringConvert.h" -#include "../../Windows/PropVariant.h" +#include "../../Windows/PropVariantUtils.h" #include "../../Windows/TimeUtils.h" #include "../Common/ProgressUtils.h" @@ -44,7 +44,7 @@ namespace NGz { namespace NFlags { - const Byte kIsText = 1 << 0; + // const Byte kIsText = 1 << 0; const Byte kCrc = 1 << 1; const Byte kExtra = 1 << 2; const Byte kName = 1 << 3; @@ -109,7 +109,6 @@ static const char * const kHostOSes[] = , "OS/X" }; -static const char *kUnknownOS = "Unknown"; class CItem { @@ -206,13 +205,13 @@ static HRESULT ReadString(NDecoder::CCOMCoder *stream, AString &s, size_t limit s.Empty(); for (size_t i = 0; i < limit; i++) { - Byte b = stream->ReadAlignedByte(); + const Byte b = stream->ReadAlignedByte(); if (stream->InputEofError()) return S_FALSE; // crc = CRC_UPDATE_BYTE(crc, b); if (b == 0) return S_OK; - s += (char)b; + s.Add_Char((char)b); } return S_FALSE; } @@ -229,12 +228,13 @@ static UInt32 Is_Deflate(const Byte *p, size_t size) return k_IsArc_Res_NO; if (type == 0) { - // Stored (uncompreessed data) + // Stored (uncompreessed data) if ((b >> 3) != 0) return k_IsArc_Res_NO; if (size < 4) return k_IsArc_Res_NEED_MORE; - if (GetUi16(p) != (UInt16)~GetUi16(p + 2)) + UInt16 r = (UInt16)~GetUi16(p + 2); + if (GetUi16(p) != r) return k_IsArc_Res_NO; } else if (type == 2) @@ -248,8 +248,8 @@ static UInt32 Is_Deflate(const Byte *p, size_t size) return k_IsArc_Res_YES; } -static unsigned kNameMaxLen = 1 << 12; -static unsigned kCommentMaxLen = 1 << 16; +static const unsigned kNameMaxLen = 1 << 12; +static const unsigned kCommentMaxLen = 1 << 16; API_FUNC_static_IsArc IsArc_Gz(const Byte *p, size_t size) { @@ -260,11 +260,11 @@ API_FUNC_static_IsArc IsArc_Gz(const Byte *p, size_t size) p[2] != kSignature_2) return k_IsArc_Res_NO; - Byte flags = p[3]; + const Byte flags = p[3]; if ((flags & NFlags::kReserved) != 0) return k_IsArc_Res_NO; - Byte extraFlags = p[8]; + const Byte extraFlags = p[8]; // maybe that flag can have another values for some gz archives? if (extraFlags != 0 && extraFlags != NExtraFlags::kMaximum && @@ -287,7 +287,7 @@ API_FUNC_static_IsArc IsArc_Gz(const Byte *p, size_t size) return k_IsArc_Res_NO; if (size < 4) return k_IsArc_Res_NEED_MORE; - unsigned len = GetUi16(p + 2); + const unsigned len = GetUi16(p + 2); size -= 4; xlen -= 4; p += 4; @@ -353,7 +353,7 @@ HRESULT CItem::ReadHeader(NDecoder::CCOMCoder *stream) // UInt32 crc = CRC_INIT_VAL; Byte buf[10]; - RINOK(ReadBytes(stream, buf, 10)); + RINOK(ReadBytes(stream, buf, 10)) if (buf[0] != kSignature_0 || buf[1] != kSignature_1 || @@ -373,22 +373,22 @@ HRESULT CItem::ReadHeader(NDecoder::CCOMCoder *stream) if (ExtraFieldIsPresent()) { UInt32 xlen; - RINOK(ReadUInt16(stream, xlen /* , crc */)); - RINOK(SkipBytes(stream, xlen)); + RINOK(ReadUInt16(stream, xlen /* , crc */)) + RINOK(SkipBytes(stream, xlen)) // Extra.SetCapacity(xlen); // RINOK(ReadStream_FALSE(stream, Extra, xlen)); // crc = CrcUpdate(crc, Extra, xlen); } if (NameIsPresent()) - RINOK(ReadString(stream, Name, kNameMaxLen /* , crc */)); + RINOK(ReadString(stream, Name, kNameMaxLen /* , crc */)) if (CommentIsPresent()) - RINOK(ReadString(stream, Comment, kCommentMaxLen /* , crc */)); + RINOK(ReadString(stream, Comment, kCommentMaxLen /* , crc */)) if (HeaderCrcIsPresent()) { UInt32 headerCRC; // UInt32 dummy = 0; - RINOK(ReadUInt16(stream, headerCRC /* , dummy */)); + RINOK(ReadUInt16(stream, headerCRC /* , dummy */)) /* if ((UInt16)CRC_GET_DIGEST(crc) != headerCRC) return S_FALSE; @@ -400,7 +400,7 @@ HRESULT CItem::ReadHeader(NDecoder::CCOMCoder *stream) HRESULT CItem::ReadFooter1(NDecoder::CCOMCoder *stream) { Byte buf[8]; - RINOK(ReadBytes(stream, buf, 8)); + RINOK(ReadBytes(stream, buf, 8)) Crc = Get32(buf); Size32 = Get32(buf + 4); return stream->InputEofError() ? S_FALSE : S_OK; @@ -409,7 +409,7 @@ HRESULT CItem::ReadFooter1(NDecoder::CCOMCoder *stream) HRESULT CItem::ReadFooter2(ISequentialInStream *stream) { Byte buf[8]; - RINOK(ReadStream_FALSE(stream, buf, 8)); + RINOK(ReadStream_FALSE(stream, buf, 8)) Crc = Get32(buf); Size32 = Get32(buf + 4); return S_OK; @@ -423,15 +423,15 @@ HRESULT CItem::WriteHeader(ISequentialOutStream *stream) buf[2] = kSignature_2; buf[3] = (Byte)(Flags & NFlags::kName); // buf[3] |= NFlags::kCrc; - SetUi32(buf + 4, Time); + SetUi32(buf + 4, Time) buf[8] = ExtraFlags; buf[9] = HostOS; - RINOK(WriteStream(stream, buf, 10)); + RINOK(WriteStream(stream, buf, 10)) // crc = CrcUpdate(CRC_INIT_VAL, buf, 10); if (NameIsPresent()) { // crc = CrcUpdate(crc, (const char *)Name, Name.Len() + 1); - RINOK(WriteStream(stream, (const char *)Name, Name.Len() + 1)); + RINOK(WriteStream(stream, (const char *)Name, Name.Len() + 1)) } // SetUi16(buf, (UInt16)CRC_GET_DIGEST(crc)); // RINOK(WriteStream(stream, buf, 2)); @@ -441,18 +441,16 @@ HRESULT CItem::WriteHeader(ISequentialOutStream *stream) HRESULT CItem::WriteFooter(ISequentialOutStream *stream) { Byte buf[8]; - SetUi32(buf, Crc); - SetUi32(buf + 4, Size32); + SetUi32(buf, Crc) + SetUi32(buf + 4, Size32) return WriteStream(stream, buf, 8); } -class CHandler: - public IInArchive, - public IArchiveOpenSeq, - public IOutArchive, - public ISetProperties, - public CMyUnknownImp -{ +Z7_CLASS_IMP_CHandler_IInArchive_3( + IArchiveOpenSeq, + IOutArchive, + ISetProperties +) CItem _item; bool _isArc; @@ -470,26 +468,19 @@ class CHandler: UInt64 _headerSize; // only start header (without footer) CMyComPtr _stream; - CMyComPtr _decoder; - NDecoder::CCOMCoder *_decoderSpec; + CMyComPtr2 _decoder; CSingleMethodProps _props; + CHandlerTimeOptions _timeOptions; public: - MY_UNKNOWN_IMP4( - IInArchive, - IArchiveOpenSeq, - IOutArchive, - ISetProperties) - INTERFACE_IInArchive(;) - INTERFACE_IOutArchive(;) - STDMETHOD(OpenSeq)(ISequentialInStream *stream); - STDMETHOD(SetProperties)(const wchar_t * const *names, const PROPVARIANT *values, UInt32 numProps); - - CHandler() + CHandler(): + _isArc(false) + {} + + void CreateDecoder() { - _decoderSpec = new NDecoder::CCOMCoder; - _decoder = _decoderSpec; + _decoder.Create_if_Empty(); } }; @@ -514,7 +505,7 @@ static const Byte kArcProps[] = IMP_IInArchive_Props IMP_IInArchive_ArcProps -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; @@ -527,7 +518,7 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) case kpidErrorFlags: { UInt32 v = 0; - if (!_isArc) v |= kpv_ErrorFlags_IsNotArc;; + if (!_isArc) v |= kpv_ErrorFlags_IsNotArc; if (_needMoreInput) v |= kpv_ErrorFlags_UnexpectedEnd; if (_dataAfterEnd) v |= kpv_ErrorFlags_DataAfterEnd; prop = v; @@ -537,10 +528,11 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) if (_item.NameIsPresent()) { UString s = MultiByteToUnicodeString(_item.Name, CP_ACP); - s.AddAscii(".gz"); + s += ".gz"; prop = s; } break; + default: break; } prop.Detach(value); return S_OK; @@ -548,13 +540,13 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) } -STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) { *numItems = 1; return S_OK; } -STDMETHODIMP CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; @@ -566,12 +558,13 @@ STDMETHODIMP CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIAN break; // case kpidComment: if (_item.CommentIsPresent()) prop = MultiByteToUnicodeString(_item.Comment, CP_ACP); break; case kpidMTime: + // gzip specification: MTIME = 0 means no time stamp is available. if (_item.Time != 0) - { - FILETIME utc; - NTime::UnixTimeToFileTime(_item.Time, utc); - prop = utc; - } + PropVariant_SetFrom_UnixTime(prop, _item.Time); + break; + case kpidTimeType: + if (_item.Time != 0) + prop = (UInt32)NFileTimeType::kUnix; break; case kpidSize: { @@ -587,28 +580,26 @@ STDMETHODIMP CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIAN prop = _packSize; break; } - case kpidHostOS: prop = (_item.HostOS < ARRAY_SIZE(kHostOSes)) ? - kHostOSes[_item.HostOS] : kUnknownOS; break; + case kpidHostOS: TYPE_TO_PROP(kHostOSes, _item.HostOS, prop); break; case kpidCRC: if (_stream) prop = _item.Crc; break; + default: break; } prop.Detach(value); return S_OK; COM_TRY_END } -class CCompressProgressInfoImp: - public ICompressProgressInfo, - public CMyUnknownImp -{ +Z7_CLASS_IMP_COM_1( + CCompressProgressInfoImp, + ICompressProgressInfo +) CMyComPtr Callback; public: UInt64 Offset; - MY_UNKNOWN_IMP1(ICompressProgressInfo) - STDMETHOD(SetRatioInfo)(const UInt64 *inSize, const UInt64 *outSize); void Init(IArchiveOpenCallback *callback) { Callback = callback; } }; -STDMETHODIMP CCompressProgressInfoImp::SetRatioInfo(const UInt64 *inSize, const UInt64 * /* outSize */) +Z7_COM7F_IMF(CCompressProgressInfoImp::SetRatioInfo(const UInt64 *inSize, const UInt64 * /* outSize */)) { if (Callback) { @@ -622,15 +613,15 @@ STDMETHODIMP CCompressProgressInfoImp::SetRatioInfo(const UInt64 *inSize, const /* */ -STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *) +Z7_COM7F_IMF(CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *)) { COM_TRY_BEGIN - RINOK(OpenSeq(stream)); + RINOK(OpenSeq(stream)) _isArc = false; UInt64 endPos; - RINOK(stream->Seek(-8, STREAM_SEEK_END, &endPos)); + RINOK(stream->Seek(-8, STREAM_SEEK_END, &endPos)) _packSize = endPos + 8; - RINOK(_item.ReadFooter2(stream)); + RINOK(_item.ReadFooter2(stream)) _stream = stream; _isArc = true; _needSeekToStart = true; @@ -638,18 +629,19 @@ STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallb COM_TRY_END } -STDMETHODIMP CHandler::OpenSeq(ISequentialInStream *stream) +Z7_COM7F_IMF(CHandler::OpenSeq(ISequentialInStream *stream)) { COM_TRY_BEGIN try { Close(); - _decoderSpec->SetInStream(stream); - _decoderSpec->InitInStream(true); - RINOK(_item.ReadHeader(_decoderSpec)); - if (_decoderSpec->InputEofError()) + CreateDecoder(); + _decoder->SetInStream(stream); + _decoder->InitInStream(true); + RINOK(_item.ReadHeader(_decoder.ClsPtr())) + if (_decoder->InputEofError()) return S_FALSE; - _headerSize = _decoderSpec->GetInputProcessedSize(); + _headerSize = _decoder->GetInputProcessedSize(); _isArc = true; return S_OK; } @@ -657,7 +649,7 @@ STDMETHODIMP CHandler::OpenSeq(ISequentialInStream *stream) COM_TRY_END } -STDMETHODIMP CHandler::Close() +Z7_COM7F_IMF(CHandler::Close()) { _isArc = false; _needSeekToStart = false; @@ -672,12 +664,13 @@ STDMETHODIMP CHandler::Close() _headerSize = 0; _stream.Release(); - _decoderSpec->ReleaseInStream(); + if (_decoder) + _decoder->ReleaseInStream(); return S_OK; } -STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, - Int32 testMode, IArchiveExtractCallback *extractCallback) +Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testMode, IArchiveExtractCallback *extractCallback)) { COM_TRY_BEGIN if (numItems == 0) @@ -686,27 +679,29 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, return E_INVALIDARG; if (_packSize_Defined) - extractCallback->SetTotal(_packSize); + RINOK(extractCallback->SetTotal(_packSize)) // UInt64 currentTotalPacked = 0; // RINOK(extractCallback->SetCompleted(¤tTotalPacked)); + Int32 retResult; + { CMyComPtr realOutStream; - Int32 askMode = testMode ? + const Int32 askMode = testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; - RINOK(extractCallback->GetStream(0, &realOutStream, askMode)); + RINOK(extractCallback->GetStream(0, &realOutStream, askMode)) if (!testMode && !realOutStream) return S_OK; - extractCallback->PrepareOperation(askMode); + RINOK(extractCallback->PrepareOperation(askMode)) - COutStreamWithCRC *outStreamSpec = new COutStreamWithCRC; - CMyComPtr outStream(outStreamSpec); - outStreamSpec->SetStream(realOutStream); - outStreamSpec->Init(); - realOutStream.Release(); + CreateDecoder(); - CLocalProgress *lps = new CLocalProgress; - CMyComPtr progress = lps; + CMyComPtr2_Create outStream; + outStream->SetStream(realOutStream); + outStream->Init(); + // realOutStream.Release(); + + CMyComPtr2_Create lps; lps->Init(extractCallback, true); bool needReadFirstItem = _needSeekToStart; @@ -715,8 +710,8 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, { if (!_stream) return E_FAIL; - RINOK(_stream->Seek(0, STREAM_SEEK_SET, NULL)); - _decoderSpec->InitInStream(true); + RINOK(InStream_SeekToBegin(_stream)) + _decoder->InitInStream(true); // printf("\nSeek"); } else @@ -724,7 +719,7 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, bool firstItem = true; - UInt64 packSize = _decoderSpec->GetInputProcessedSize(); + UInt64 packSize = _decoder->GetInputProcessedSize(); // printf("\npackSize = %d", (unsigned)packSize); UInt64 unpackedSize = 0; @@ -741,18 +736,18 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, lps->InSize = packSize; lps->OutSize = unpackedSize; - RINOK(lps->SetCur()); + RINOK(lps->SetCur()) CItem item; if (!firstItem || needReadFirstItem) { - result = item.ReadHeader(_decoderSpec); + result = item.ReadHeader(_decoder.ClsPtr()); if (result != S_OK && result != S_FALSE) return result; - if (_decoderSpec->InputEofError()) + if (_decoder->InputEofError()) result = S_FALSE; if (result != S_OK && firstItem) @@ -761,7 +756,7 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, break; } - if (packSize == _decoderSpec->GetStreamSize()) + if (packSize == _decoder->GetStreamSize()) { result = S_OK; break; @@ -777,20 +772,20 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, numStreams++; firstItem = false; - UInt64 startOffset = outStreamSpec->GetSize(); - outStreamSpec->InitCRC(); + const UInt64 startOffset = outStream->GetSize(); + outStream->InitCRC(); - result = _decoderSpec->CodeResume(outStream, NULL, progress); + result = _decoder->CodeResume(outStream, NULL, lps); - packSize = _decoderSpec->GetInputProcessedSize(); - unpackedSize = outStreamSpec->GetSize(); + packSize = _decoder->GetInputProcessedSize(); + unpackedSize = outStream->GetSize(); if (result != S_OK && result != S_FALSE) return result; - if (_decoderSpec->InputEofError()) + if (_decoder->InputEofError()) { - packSize = _decoderSpec->GetStreamSize(); + packSize = _decoder->GetStreamSize(); _needMoreInput = true; result = S_FALSE; } @@ -798,18 +793,18 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, if (result != S_OK) break; - _decoderSpec->AlignToByte(); + _decoder->AlignToByte(); - result = item.ReadFooter1(_decoderSpec); + result = item.ReadFooter1(_decoder.ClsPtr()); - packSize = _decoderSpec->GetInputProcessedSize(); + packSize = _decoder->GetInputProcessedSize(); if (result != S_OK && result != S_FALSE) return result; if (result != S_OK) { - if (_decoderSpec->InputEofError()) + if (_decoder->InputEofError()) { _needMoreInput = true; result = S_FALSE; @@ -817,7 +812,7 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, break; } - if (item.Crc != outStreamSpec->GetCRC() || + if (item.Crc != outStream->GetCRC() || item.Size32 != (UInt32)(unpackedSize - startOffset)) { crcError = true; @@ -841,9 +836,9 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, _numStreams_Defined = true; } - outStream.Release(); + // outStream.Release(); - Int32 retResult = NExtract::NOperationResult::kDataError; + retResult = NExtract::NOperationResult::kDataError; if (!_isArc) retResult = NExtract::NOperationResult::kIsNotArc; @@ -859,10 +854,9 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, retResult = NExtract::NOperationResult::kOK; else return result; + } return extractCallback->SetOperationResult(retResult); - - COM_TRY_END } @@ -873,28 +867,105 @@ static const Byte kHostOS = NHostOS::kUnix; #endif + +/* +static HRESULT ReportItemProp(IArchiveUpdateCallbackArcProp *reportArcProp, PROPID propID, const PROPVARIANT *value) +{ + return reportArcProp->ReportProp(NEventIndexType::kOutArcIndex, 0, propID, value); +} + +static HRESULT ReportArcProp(IArchiveUpdateCallbackArcProp *reportArcProp, PROPID propID, const PROPVARIANT *value) +{ + return reportArcProp->ReportProp(NEventIndexType::kArcProp, 0, propID, value); +} + +static HRESULT ReportArcProps(IArchiveUpdateCallbackArcProp *reportArcProp, + const CItem &item, + bool needTime, + bool needCrc, + const UInt64 *unpackSize) +{ + NCOM::CPropVariant timeProp; + NCOM::CPropVariant sizeProp; + if (needTime) + { + FILETIME ft; + NTime::UnixTimeToFileTime(item.Time, ft); + timeProp.SetAsTimeFrom_FT_Prec(ft, k_PropVar_TimePrec_Unix); + } + if (unpackSize) + { + sizeProp = *unpackSize; + RINOK(ReportItemProp(reportArcProp, kpidSize, &sizeProp)); + } + if (needCrc) + { + NCOM::CPropVariant prop; + prop = item.Crc; + RINOK(ReportItemProp(reportArcProp, kpidCRC, &prop)); + } + { + RINOK(ReportItemProp(reportArcProp, kpidMTime, &timeProp)); + } + + RINOK(reportArcProp->ReportFinished(NEventIndexType::kOutArcIndex, 0, NArchive::NUpdate::NOperationResult::kOK)); + + if (unpackSize) + { + RINOK(ReportArcProp(reportArcProp, kpidSize, &sizeProp)); + } + { + RINOK(ReportArcProp(reportArcProp, kpidComboMTime, &timeProp)); + } + return S_OK; +} +*/ + static HRESULT UpdateArchive( ISequentialOutStream *outStream, UInt64 unpackSize, CItem &item, const CSingleMethodProps &props, - IArchiveUpdateCallback *updateCallback) + const CHandlerTimeOptions &timeOptions, + IArchiveUpdateCallback *updateCallback + // , IArchiveUpdateCallbackArcProp *reportArcProp + ) { - UInt64 complexity = 0; - RINOK(updateCallback->SetTotal(unpackSize)); - RINOK(updateCallback->SetCompleted(&complexity)); - + UInt64 unpackSizeReal; + { CMyComPtr fileInStream; - RINOK(updateCallback->GetStream(0, &fileInStream)); + RINOK(updateCallback->GetStream(0, &fileInStream)) + + if (!fileInStream) + return S_FALSE; + + { + Z7_DECL_CMyComPtr_QI_FROM( + IStreamGetProps, + getProps, fileInStream) + if (getProps) + { + FILETIME mTime; + UInt64 size; + if (getProps->GetProps(&size, NULL, NULL, &mTime, NULL) == S_OK) + { + unpackSize = size; + if (timeOptions.Write_MTime.Val) + NTime::FileTime_To_UnixTime(mTime, item.Time); + } + } + } + + UInt64 complexity = 0; + RINOK(updateCallback->SetTotal(unpackSize)) + RINOK(updateCallback->SetCompleted(&complexity)) - CSequentialInStreamWithCRC *inStreamSpec = new CSequentialInStreamWithCRC; - CMyComPtr crcStream(inStreamSpec); - inStreamSpec->SetStream(fileInStream); - inStreamSpec->Init(); + CMyComPtr2_Create crcStream; + crcStream->SetStream(fileInStream); + crcStream->Init(); - CLocalProgress *lps = new CLocalProgress; - CMyComPtr progress = lps; + CMyComPtr2_Create lps; lps->Init(updateCallback, true); item.ExtraFlags = props.GetLevel() >= 7 ? @@ -903,38 +974,84 @@ static HRESULT UpdateArchive( item.HostOS = kHostOS; - RINOK(item.WriteHeader(outStream)); + RINOK(item.WriteHeader(outStream)) + + CMyComPtr2_Create deflateEncoder; - NEncoder::CCOMCoder *deflateEncoderSpec = new NEncoder::CCOMCoder; - CMyComPtr deflateEncoder = deflateEncoderSpec; - RINOK(props.SetCoderProps(deflateEncoderSpec, NULL)); - RINOK(deflateEncoder->Code(crcStream, outStream, NULL, NULL, progress)); + RINOK(props.SetCoderProps(deflateEncoder.ClsPtr(), NULL)) + RINOK(deflateEncoder.Interface()->Code(crcStream, outStream, NULL, NULL, lps)) - item.Crc = inStreamSpec->GetCRC(); - item.Size32 = (UInt32)inStreamSpec->GetSize(); - RINOK(item.WriteFooter(outStream)); + item.Crc = crcStream->GetCRC(); + unpackSizeReal = crcStream->GetSize(); + item.Size32 = (UInt32)unpackSizeReal; + RINOK(item.WriteFooter(outStream)) + } + /* + if (reportArcProp) + { + RINOK(ReportArcProps(reportArcProp, + item, + props._Write_MTime, // item.Time != 0, + true, // writeCrc + &unpackSizeReal)); + } + */ return updateCallback->SetOperationResult(NUpdate::NOperationResult::kOK); } -STDMETHODIMP CHandler::GetFileTimeType(UInt32 *timeType) + +Z7_COM7F_IMF(CHandler::GetFileTimeType(UInt32 *timeType)) { - *timeType = NFileTimeType::kUnix; + /* + if (_item.Time != 0) + { + we set NFileTimeType::kUnix in precision, + and we return NFileTimeType::kUnix in kpidTimeType + so GetFileTimeType() value is not used in any version of 7-zip. + } + else // (_item.Time == 0) + { + kpidMTime and kpidTimeType are not defined + before 22.00 : GetFileTimeType() value is used in GetUpdatePairInfoList(); + 22.00 : GetFileTimeType() value is not used + } + */ + + UInt32 t; + t = NFileTimeType::kUnix; + if (_isArc ? (_item.Time == 0) : !_timeOptions.Write_MTime.Val) + { + t = GET_FileTimeType_NotDefined_for_GetFileTimeType; + // t = k_PropVar_TimePrec_1ns; // failed in 7-Zip 21 + // t = (UInt32)(Int32)NFileTimeType::kNotDefined; // failed in 7-Zip 21 + } + *timeType = t; return S_OK; } -STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numItems, - IArchiveUpdateCallback *updateCallback) +Z7_COM7F_IMF(CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numItems, + IArchiveUpdateCallback *updateCallback)) { COM_TRY_BEGIN if (numItems != 1) return E_INVALIDARG; + { + Z7_DECL_CMyComPtr_QI_FROM( + IStreamSetRestriction, + setRestriction, outStream) + if (setRestriction) + RINOK(setRestriction->SetRestriction(0, 0)) + } + Int32 newData, newProps; UInt32 indexInArchive; if (!updateCallback) return E_FAIL; - RINOK(updateCallback->GetUpdateItemInfo(0, &newData, &newProps, &indexInArchive)); + RINOK(updateCallback->GetUpdateItemInfo(0, &newData, &newProps, &indexInArchive)) + + // Z7_DECL_CMyComPtr_QI_FROM(IArchiveUpdateCallbackArcProp, reportArcProp, updateCallback) CItem newItem; @@ -945,11 +1062,12 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numIt else { newItem.HostOS = kHostOS; + if (_timeOptions.Write_MTime.Val) { NCOM::CPropVariant prop; - RINOK(updateCallback->GetProperty(0, kpidMTime, &prop)); + RINOK(updateCallback->GetProperty(0, kpidMTime, &prop)) if (prop.vt == VT_FILETIME) - NTime::FileTimeToUnixTime(prop.filetime, newItem.Time); + NTime::FileTime_To_UnixTime(prop.filetime, newItem.Time); else if (prop.vt == VT_EMPTY) newItem.Time = 0; else @@ -957,13 +1075,13 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numIt } { NCOM::CPropVariant prop; - RINOK(updateCallback->GetProperty(0, kpidPath, &prop)); + RINOK(updateCallback->GetProperty(0, kpidPath, &prop)) if (prop.vt == VT_BSTR) { UString name = prop.bstrVal; int slashPos = name.ReverseFind_PathSepar(); if (slashPos >= 0) - name.DeleteFrontal(slashPos + 1); + name.DeleteFrontal((unsigned)(slashPos + 1)); newItem.Name = UnicodeStringToMultiByte(name, CP_ACP); if (!newItem.Name.IsEmpty()) newItem.Flags |= NFlags::kName; @@ -973,7 +1091,7 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numIt } { NCOM::CPropVariant prop; - RINOK(updateCallback->GetProperty(0, kpidIsDir, &prop)); + RINOK(updateCallback->GetProperty(0, kpidIsDir, &prop)) if (prop.vt != VT_EMPTY) if (prop.vt != VT_BOOL || prop.boolVal != VARIANT_FALSE) return E_INVALIDARG; @@ -985,12 +1103,12 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numIt UInt64 size; { NCOM::CPropVariant prop; - RINOK(updateCallback->GetProperty(0, kpidSize, &prop)); + RINOK(updateCallback->GetProperty(0, kpidSize, &prop)) if (prop.vt != VT_UI8) return E_INVALIDARG; size = prop.uhVal.QuadPart; } - return UpdateArchive(outStream, size, newItem, _props, updateCallback); + return UpdateArchive(outStream, size, newItem, _props, _timeOptions, updateCallback); } if (indexInArchive != 0) @@ -999,12 +1117,12 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numIt if (!_stream) return E_NOTIMPL; - CLocalProgress *lps = new CLocalProgress; - CMyComPtr progress = lps; + CMyComPtr2_Create lps; lps->Init(updateCallback, true); - CMyComPtr opCallback; - updateCallback->QueryInterface(IID_IArchiveUpdateCallbackFile, (void **)&opCallback); + Z7_DECL_CMyComPtr_QI_FROM( + IArchiveUpdateCallbackFile, + opCallback, updateCallback) if (opCallback) { RINOK(opCallback->ReportOperation( @@ -1020,25 +1138,65 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numIt newItem.WriteHeader(outStream); offset += _headerSize; } - RINOK(_stream->Seek(offset, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekSet(_stream, offset)) - return NCompress::CopyStream(_stream, outStream, progress); + /* + if (reportArcProp) + ReportArcProps(reportArcProp, newItem, + _props._Write_MTime, + false, // writeCrc + NULL); // unpacksize + */ + + return NCompress::CopyStream(_stream, outStream, lps); COM_TRY_END } -STDMETHODIMP CHandler::SetProperties(const wchar_t * const *names, const PROPVARIANT *values, UInt32 numProps) +Z7_COM7F_IMF(CHandler::SetProperties(const wchar_t * const *names, const PROPVARIANT *values, UInt32 numProps)) { - return _props.SetProperties(names, values, numProps); + _timeOptions.Init(); + _props.Init(); + + for (UInt32 i = 0; i < numProps; i++) + { + UString name = names[i]; + name.MakeLower_Ascii(); + if (name.IsEmpty()) + return E_INVALIDARG; + const PROPVARIANT &value = values[i]; + { + bool processed = false; + RINOK(_timeOptions.Parse(name, value, processed)) + if (processed) + { + if (_timeOptions.Write_CTime.Val || + _timeOptions.Write_ATime.Val) + return E_INVALIDARG; + if ( _timeOptions.Prec != (UInt32)(Int32)-1 + && _timeOptions.Prec != k_PropVar_TimePrec_0 + && _timeOptions.Prec != k_PropVar_TimePrec_Unix + && _timeOptions.Prec != k_PropVar_TimePrec_HighPrec + && _timeOptions.Prec != k_PropVar_TimePrec_Base) + return E_INVALIDARG; + continue; + } + } + RINOK(_props.SetProperty(name, value)) + } + return S_OK; } static const Byte k_Signature[] = { kSignature_0, kSignature_1, kSignature_2 }; REGISTER_ARC_IO( - "gzip", "gz gzip tgz tpz", "* * .tar .tar", 0xEF, - k_Signature, - 0, - NArcInfoFlags::kKeepName, - IsArc_Gz) + "gzip", "gz gzip tgz tpz apk", "* * .tar .tar .tar", 0xEF, + k_Signature, 0, + NArcInfoFlags::kKeepName + | NArcInfoFlags::kMTime + | NArcInfoFlags::kMTime_Default + , TIME_PREC_TO_ARC_FLAGS_MASK (NFileTimeType::kUnix) + | TIME_PREC_TO_ARC_FLAGS_TIME_DEFAULT (NFileTimeType::kUnix) + , IsArc_Gz) }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/HandlerCont.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/HandlerCont.cpp index c2d5c70c..6a0eee24 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/HandlerCont.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/HandlerCont.cpp @@ -14,14 +14,18 @@ namespace NArchive { -STDMETHODIMP CHandlerCont::Extract(const UInt32 *indices, UInt32 numItems, - Int32 testMode, IArchiveExtractCallback *extractCallback) +namespace NExt { +API_FUNC_IsArc IsArc_Ext(const Byte *p, size_t size); +} + +Z7_COM7F_IMF(CHandlerCont::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testMode, IArchiveExtractCallback *extractCallback)) { COM_TRY_BEGIN - bool allFilesMode = (numItems == (UInt32)(Int32)-1); + const bool allFilesMode = (numItems == (UInt32)(Int32)-1); if (allFilesMode) { - RINOK(GetNumberOfItems(&numItems)); + RINOK(GetNumberOfItems(&numItems)) } if (numItems == 0) return S_OK; @@ -33,33 +37,31 @@ STDMETHODIMP CHandlerCont::Extract(const UInt32 *indices, UInt32 numItems, GetItem_ExtractInfo(allFilesMode ? i : indices[i], pos, size); totalSize += size; } - extractCallback->SetTotal(totalSize); + RINOK(extractCallback->SetTotal(totalSize)) totalSize = 0; - NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder(); - CMyComPtr copyCoder = copyCoderSpec; - - CLocalProgress *lps = new CLocalProgress; - CMyComPtr progress = lps; + CMyComPtr2_Create lps; lps->Init(extractCallback, false); - - CLimitedSequentialInStream *streamSpec = new CLimitedSequentialInStream; - CMyComPtr inStream(streamSpec); + CMyComPtr2_Create streamSpec; streamSpec->SetStream(_stream); + CMyComPtr2_Create copyCoder; - for (i = 0; i < numItems; i++) + for (i = 0;; i++) { lps->InSize = totalSize; lps->OutSize = totalSize; - RINOK(lps->SetCur()); + RINOK(lps->SetCur()) + if (i >= numItems) + break; + CMyComPtr outStream; - Int32 askMode = testMode ? + const Int32 askMode = testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; - Int32 index = allFilesMode ? i : indices[i]; + const UInt32 index = allFilesMode ? i : indices[i]; - RINOK(extractCallback->GetStream(index, &outStream, askMode)); + RINOK(extractCallback->GetStream(index, &outStream, askMode)) UInt64 pos, size; int opRes = GetItem_ExtractInfo(index, pos, size); @@ -67,32 +69,31 @@ STDMETHODIMP CHandlerCont::Extract(const UInt32 *indices, UInt32 numItems, if (!testMode && !outStream) continue; - RINOK(extractCallback->PrepareOperation(askMode)); + RINOK(extractCallback->PrepareOperation(askMode)) if (opRes == NExtract::NOperationResult::kOK) { - RINOK(_stream->Seek(pos, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekSet(_stream, pos)) streamSpec->Init(size); - RINOK(copyCoder->Code(inStream, outStream, NULL, NULL, progress)); + RINOK(copyCoder.Interface()->Code(streamSpec, outStream, NULL, NULL, lps)) opRes = NExtract::NOperationResult::kDataError; - - if (copyCoderSpec->TotalSize == size) + if (copyCoder->TotalSize == size) opRes = NExtract::NOperationResult::kOK; - else if (copyCoderSpec->TotalSize < size) + else if (copyCoder->TotalSize < size) opRes = NExtract::NOperationResult::kUnexpectedEnd; } outStream.Release(); - RINOK(extractCallback->SetOperationResult(opRes)); + RINOK(extractCallback->SetOperationResult(opRes)) } return S_OK; COM_TRY_END } -STDMETHODIMP CHandlerCont::GetStream(UInt32 index, ISequentialInStream **stream) +Z7_COM7F_IMF(CHandlerCont::GetStream(UInt32 index, ISequentialInStream **stream)) { COM_TRY_BEGIN *stream = NULL; @@ -105,13 +106,12 @@ STDMETHODIMP CHandlerCont::GetStream(UInt32 index, ISequentialInStream **stream) -CHandlerImg::CHandlerImg(): - _imgExt(NULL) +CHandlerImg::CHandlerImg() { - ClearStreamVars(); + Clear_HandlerImg_Vars(); } -STDMETHODIMP CHandlerImg::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition) +Z7_COM7F_IMF(CHandlerImg::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition)) { switch (seekOrigin) { @@ -121,27 +121,40 @@ STDMETHODIMP CHandlerImg::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosit default: return STG_E_INVALIDFUNCTION; } if (offset < 0) + { + if (newPosition) + *newPosition = _virtPos; return HRESULT_WIN32_ERROR_NEGATIVE_SEEK; - _virtPos = offset; + } + _virtPos = (UInt64)offset; if (newPosition) - *newPosition = offset; + *newPosition = (UInt64)offset; return S_OK; } -static const Byte k_GDP_Signature[] = { 'E', 'F', 'I', ' ', 'P', 'A', 'R', 'T' }; +static const Byte k_GDP_Signature[] = + { 'E', 'F', 'I', ' ', 'P', 'A', 'R', 'T', 0, 0, 1, 0 }; +// static const Byte k_Ext_Signature[] = { 0x53, 0xEF }; +// static const unsigned k_Ext_Signature_offset = 0x438; static const char *GetImgExt(ISequentialInStream *stream) { - const size_t kHeaderSize = 1 << 10; + // const size_t kHeaderSize_for_Ext = (1 << 11); // for ext + const size_t kHeaderSize = 2 << 12; // for 4 KB sector GPT Byte buf[kHeaderSize]; - if (ReadStream_FAIL(stream, buf, kHeaderSize) == S_OK) + size_t processed = kHeaderSize; + if (ReadStream(stream, buf, &processed) == S_OK) { + if (processed >= kHeaderSize) if (buf[0x1FE] == 0x55 && buf[0x1FF] == 0xAA) { - if (memcmp(buf + 512, k_GDP_Signature, sizeof(k_GDP_Signature)) == 0) - return "gpt"; + for (unsigned k = (1 << 9); k <= (1u << 12); k <<= 3) + if (memcmp(buf + k, k_GDP_Signature, sizeof(k_GDP_Signature)) == 0) + return "gpt"; return "mbr"; } + if (NExt::IsArc_Ext(buf, processed) == k_IsArc_Res_YES) + return "ext"; } return NULL; } @@ -151,9 +164,18 @@ void CHandlerImg::CloseAtError() Stream.Release(); } -STDMETHODIMP CHandlerImg::Open(IInStream *stream, +void CHandlerImg::Clear_HandlerImg_Vars() +{ + _imgExt = NULL; + _size = 0; + ClearStreamVars(); + Reset_VirtPos(); + Reset_PosInArc(); +} + +Z7_COM7F_IMF(CHandlerImg::Open(IInStream *stream, const UInt64 * /* maxCheckStartPosition */, - IArchiveOpenCallback * openCallback) + IArchiveOpenCallback * openCallback)) { COM_TRY_BEGIN { @@ -165,9 +187,16 @@ STDMETHODIMP CHandlerImg::Open(IInStream *stream, if (res == S_OK) { CMyComPtr inStream; - HRESULT res2 = GetStream(0, &inStream); + const HRESULT res2 = GetStream(0, &inStream); if (res2 == S_OK && inStream) _imgExt = GetImgExt(inStream); + // _imgExt = GetImgExt(this); // for debug + /* we reset (_virtPos) to support cases, if some code will + call Read() from Handler object instead of GetStream() object. */ + Reset_VirtPos(); + // optional: we reset (_posInArc). if real seek position of stream will be changed in external code + Reset_PosInArc(); + // optional: here we could also reset seek positions in parent streams.. return S_OK; } } @@ -182,14 +211,36 @@ STDMETHODIMP CHandlerImg::Open(IInStream *stream, COM_TRY_END } -STDMETHODIMP CHandlerImg::GetNumberOfItems(UInt32 *numItems) +Z7_COM7F_IMF(CHandlerImg::GetNumberOfItems(UInt32 *numItems)) { *numItems = 1; return S_OK; } -STDMETHODIMP CHandlerImg::Extract(const UInt32 *indices, UInt32 numItems, - Int32 testMode, IArchiveExtractCallback *extractCallback) + +Z7_CLASS_IMP_NOQIB_1( + CHandlerImgProgress + , ICompressProgressInfo +) +public: + CHandlerImg &Handler; + CMyComPtr _ratioProgress; + + CHandlerImgProgress(CHandlerImg &handler) : Handler(handler) {} +}; + + +Z7_COM7F_IMF(CHandlerImgProgress::SetRatioInfo(const UInt64 *inSize, const UInt64 *outSize)) +{ + UInt64 inSize2; + if (Handler.Get_PackSizeProcessed(inSize2)) + inSize = &inSize2; + return _ratioProgress->SetRatioInfo(inSize, outSize); +} + + +Z7_COM7F_IMF(CHandlerImg::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testMode, IArchiveExtractCallback *extractCallback)) { COM_TRY_BEGIN if (numItems == 0) @@ -197,19 +248,15 @@ STDMETHODIMP CHandlerImg::Extract(const UInt32 *indices, UInt32 numItems, if (numItems != (UInt32)(Int32)-1 && (numItems != 1 || indices[0] != 0)) return E_INVALIDARG; - RINOK(extractCallback->SetTotal(_size)); + RINOK(extractCallback->SetTotal(_size)) CMyComPtr outStream; - Int32 askMode = testMode ? + const Int32 askMode = testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; - RINOK(extractCallback->GetStream(0, &outStream, askMode)); + RINOK(extractCallback->GetStream(0, &outStream, askMode)) if (!testMode && !outStream) return S_OK; - RINOK(extractCallback->PrepareOperation(askMode)); - - CLocalProgress *lps = new CLocalProgress; - CMyComPtr progress = lps; - lps->Init(extractCallback, false); + RINOK(extractCallback->PrepareOperation(askMode)) int opRes = NExtract::NOperationResult::kDataError; @@ -222,13 +269,25 @@ STDMETHODIMP CHandlerImg::Extract(const UInt32 *indices, UInt32 numItems, if (hres == S_OK && inStream) { - NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder(); - CMyComPtr copyCoder = copyCoderSpec; + CLocalProgress *lps = new CLocalProgress; + CMyComPtr progress = lps; + lps->Init(extractCallback, false); + + if (Init_PackSizeProcessed()) + { + CHandlerImgProgress *imgProgressSpec = new CHandlerImgProgress(*this); + CMyComPtr imgProgress = imgProgressSpec; + imgProgressSpec->_ratioProgress = progress; + progress.Release(); + progress = imgProgress; + } + + CMyComPtr2_Create copyCoder; - hres = copyCoder->Code(inStream, outStream, NULL, &_size, progress); + hres = copyCoder.Interface()->Code(inStream, outStream, NULL, &_size, progress); if (hres == S_OK) { - if (copyCoderSpec->TotalSize == _size) + if (copyCoder->TotalSize == _size) opRes = NExtract::NOperationResult::kOK; if (_stream_unavailData) @@ -237,7 +296,7 @@ STDMETHODIMP CHandlerImg::Extract(const UInt32 *indices, UInt32 numItems, opRes = NExtract::NOperationResult::kUnsupportedMethod; else if (_stream_dataError) opRes = NExtract::NOperationResult::kDataError; - else if (copyCoderSpec->TotalSize < _size) + else if (copyCoder->TotalSize < _size) opRes = NExtract::NOperationResult::kUnexpectedEnd; } } @@ -259,30 +318,4 @@ STDMETHODIMP CHandlerImg::Extract(const UInt32 *indices, UInt32 numItems, COM_TRY_END } - -HRESULT ReadZeroTail(ISequentialInStream *stream, bool &areThereNonZeros, UInt64 &numZeros, UInt64 maxSize) -{ - areThereNonZeros = false; - numZeros = 0; - const size_t kBufSize = 1 << 11; - Byte buf[kBufSize]; - for (;;) - { - UInt32 size = 0; - HRESULT(stream->Read(buf, kBufSize, &size)); - if (size == 0) - return S_OK; - for (UInt32 i = 0; i < size; i++) - if (buf[i] != 0) - { - areThereNonZeros = true; - numZeros += i; - return S_OK; - } - numZeros += size; - if (numZeros > maxSize) - return S_OK; - } -} - } diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/HandlerCont.h b/src/plugins/7zip/7za/cpp/7zip/Archive/HandlerCont.h index 50a72895..82e451aa 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/HandlerCont.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/HandlerCont.h @@ -1,7 +1,7 @@ // HandlerCont.h -#ifndef __HANDLER_CONT_H -#define __HANDLER_CONT_H +#ifndef ZIP7_INC_HANDLER_CONT_H +#define ZIP7_INC_HANDLER_CONT_H #include "../../Common/MyCom.h" @@ -9,74 +9,91 @@ namespace NArchive { -#define INTERFACE_IInArchive_Cont(x) \ - STDMETHOD(Open)(IInStream *stream, const UInt64 *maxCheckStartPosition, IArchiveOpenCallback *openCallback) MY_NO_THROW_DECL_ONLY x; \ - STDMETHOD(Close)() MY_NO_THROW_DECL_ONLY x; \ - STDMETHOD(GetNumberOfItems)(UInt32 *numItems) MY_NO_THROW_DECL_ONLY x; \ - STDMETHOD(GetProperty)(UInt32 index, PROPID propID, PROPVARIANT *value) MY_NO_THROW_DECL_ONLY x; \ - /* STDMETHOD(Extract)(const UInt32* indices, UInt32 numItems, Int32 testMode, IArchiveExtractCallback *extractCallback) MY_NO_THROW_DECL_ONLY x; */ \ - STDMETHOD(GetArchiveProperty)(PROPID propID, PROPVARIANT *value) MY_NO_THROW_DECL_ONLY x; \ - STDMETHOD(GetNumberOfProperties)(UInt32 *numProps) MY_NO_THROW_DECL_ONLY x; \ - STDMETHOD(GetPropertyInfo)(UInt32 index, BSTR *name, PROPID *propID, VARTYPE *varType) MY_NO_THROW_DECL_ONLY x; \ - STDMETHOD(GetNumberOfArchiveProperties)(UInt32 *numProps) MY_NO_THROW_DECL_ONLY x; \ - STDMETHOD(GetArchivePropertyInfo)(UInt32 index, BSTR *name, PROPID *propID, VARTYPE *varType) MY_NO_THROW_DECL_ONLY x; \ +#define Z7_IFACEM_IInArchive_Cont(x) \ + x(Open(IInStream *stream, const UInt64 *maxCheckStartPosition, IArchiveOpenCallback *openCallback)) \ + x(Close()) \ + x(GetNumberOfItems(UInt32 *numItems)) \ + x(GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) \ + /* x(Extract(const UInt32 *indices, UInt32 numItems, Int32 testMode, IArchiveExtractCallback *extractCallback)) */ \ + x(GetArchiveProperty(PROPID propID, PROPVARIANT *value)) \ + x(GetNumberOfProperties(UInt32 *numProps)) \ + x(GetPropertyInfo(UInt32 index, BSTR *name, PROPID *propID, VARTYPE *varType)) \ + x(GetNumberOfArchiveProperties(UInt32 *numProps)) \ + x(GetArchivePropertyInfo(UInt32 index, BSTR *name, PROPID *propID, VARTYPE *varType)) \ +// #define Z7_COM7F_PUREO(f) virtual Z7_COM7F_IMF(f) Z7_override =0; +// #define Z7_COM7F_PUREO2(t, f) virtual Z7_COM7F_IMF2(t, f) Z7_override =0; + class CHandlerCont: public IInArchive, public IInArchiveGetStream, public CMyUnknownImp { + Z7_COM_UNKNOWN_IMP_2( + IInArchive, + IInArchiveGetStream) + /* + Z7_IFACEM_IInArchive_Cont(Z7_COM7F_PUREO) + // Z7_IFACE_COM7_PURE(IInArchive_Cont) + */ + Z7_COM7F_IMP(Extract(const UInt32 *indices, UInt32 numItems, Int32 testMode, IArchiveExtractCallback *extractCallback)) protected: - CMyComPtr _stream; + Z7_IFACE_COM7_IMP(IInArchiveGetStream) + CMyComPtr _stream; virtual int GetItem_ExtractInfo(UInt32 index, UInt64 &pos, UInt64 &size) const = 0; - -public: - MY_UNKNOWN_IMP2(IInArchive, IInArchiveGetStream) - INTERFACE_IInArchive_Cont(PURE) - - STDMETHOD(Extract)(const UInt32* indices, UInt32 numItems, Int32 testMode, IArchiveExtractCallback *extractCallback) MY_NO_THROW_DECL_ONLY; - - STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream); - // destructor must be virtual for this class virtual ~CHandlerCont() {} }; -#define INTERFACE_IInArchive_Img(x) \ - /* STDMETHOD(Open)(IInStream *stream, const UInt64 *maxCheckStartPosition, IArchiveOpenCallback *openCallback) MY_NO_THROW_DECL_ONLY x; */ \ - STDMETHOD(Close)() MY_NO_THROW_DECL_ONLY x; \ - /* STDMETHOD(GetNumberOfItems)(UInt32 *numItems) MY_NO_THROW_DECL_ONLY x; */ \ - STDMETHOD(GetProperty)(UInt32 index, PROPID propID, PROPVARIANT *value) MY_NO_THROW_DECL_ONLY x; \ - /* STDMETHOD(Extract)(const UInt32* indices, UInt32 numItems, Int32 testMode, IArchiveExtractCallback *extractCallback) MY_NO_THROW_DECL_ONLY x; */ \ - STDMETHOD(GetArchiveProperty)(PROPID propID, PROPVARIANT *value) MY_NO_THROW_DECL_ONLY x; \ - STDMETHOD(GetNumberOfProperties)(UInt32 *numProps) MY_NO_THROW_DECL_ONLY x; \ - STDMETHOD(GetPropertyInfo)(UInt32 index, BSTR *name, PROPID *propID, VARTYPE *varType) MY_NO_THROW_DECL_ONLY x; \ - STDMETHOD(GetNumberOfArchiveProperties)(UInt32 *numProps) MY_NO_THROW_DECL_ONLY x; \ - STDMETHOD(GetArchivePropertyInfo)(UInt32 index, BSTR *name, PROPID *propID, VARTYPE *varType) MY_NO_THROW_DECL_ONLY x; \ +#define Z7_IFACEM_IInArchive_Img(x) \ + /* x(Open(IInStream *stream, const UInt64 *maxCheckStartPosition, IArchiveOpenCallback *openCallback)) */ \ + x(Close()) \ + /* x(GetNumberOfItems(UInt32 *numItems)) */ \ + x(GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) \ + /* x(Extract(const UInt32 *indices, UInt32 numItems, Int32 testMode, IArchiveExtractCallback *extractCallback)) */ \ + x(GetArchiveProperty(PROPID propID, PROPVARIANT *value)) \ + x(GetNumberOfProperties(UInt32 *numProps)) \ + x(GetPropertyInfo(UInt32 index, BSTR *name, PROPID *propID, VARTYPE *varType)) \ + x(GetNumberOfArchiveProperties(UInt32 *numProps)) \ + x(GetArchivePropertyInfo(UInt32 index, BSTR *name, PROPID *propID, VARTYPE *varType)) \ class CHandlerImg: - public IInStream, public IInArchive, public IInArchiveGetStream, + public IInStream, public CMyUnknownImp { + Z7_COM_UNKNOWN_IMP_4( + IInArchive, + IInArchiveGetStream, + ISequentialInStream, + IInStream) + + Z7_COM7F_IMP(Open(IInStream *stream, const UInt64 *maxCheckStartPosition, IArchiveOpenCallback *openCallback)) + Z7_COM7F_IMP(GetNumberOfItems(UInt32 *numItems)) + Z7_COM7F_IMP(Extract(const UInt32 *indices, UInt32 numItems, Int32 testMode, IArchiveExtractCallback *extractCallback)) + Z7_IFACE_COM7_IMP(IInStream) + // Z7_IFACEM_IInArchive_Img(Z7_COM7F_PUREO) + protected: + bool _stream_unavailData; + bool _stream_unsupportedMethod; + bool _stream_dataError; + // bool _stream_UsePackSize; + // UInt64 _stream_PackSize; UInt64 _virtPos; UInt64 _posInArc; UInt64 _size; CMyComPtr Stream; const char *_imgExt; - bool _stream_unavailData; - bool _stream_unsupportedMethod; - bool _stream_dataError; - // bool _stream_UsePackSize; - // UInt64 _stream_PackSize; + void Reset_PosInArc() { _posInArc = (UInt64)0 - 1; } + void Reset_VirtPos() { _virtPos = (UInt64)0; } void ClearStreamVars() { @@ -87,21 +104,22 @@ class CHandlerImg: // _stream_PackSize = 0; } + void Clear_HandlerImg_Vars(); // it doesn't Release (Stream) var. virtual HRESULT Open2(IInStream *stream, IArchiveOpenCallback *openCallback) = 0; virtual void CloseAtError(); -public: - MY_UNKNOWN_IMP3(IInArchive, IInArchiveGetStream, IInStream) - INTERFACE_IInArchive_Img(PURE) - - STDMETHOD(Open)(IInStream *stream, const UInt64 *maxCheckStartPosition, IArchiveOpenCallback *openCallback); - STDMETHOD(GetNumberOfItems)(UInt32 *numItems); - STDMETHOD(Extract)(const UInt32* indices, UInt32 numItems, Int32 testMode, IArchiveExtractCallback *extractCallback); - - STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream) = 0; - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize) = 0; - STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition); + // returns (true), if Get_PackSizeProcessed() is required in Extract() + virtual bool Init_PackSizeProcessed() + { + return false; + } +public: + virtual bool Get_PackSizeProcessed(UInt64 &size) + { + size = 0; + return false; + } CHandlerImg(); // destructor must be virtual for this class diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/HfsHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/HfsHandler.cpp index 185df7f0..5df00ede 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/HfsHandler.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/HfsHandler.cpp @@ -7,28 +7,31 @@ #include "../../Common/ComTry.h" #include "../../Common/MyString.h" -#include "../../Windows/PropVariant.h" +#include "../../Windows/PropVariantUtils.h" #include "../Common/LimitedStreams.h" #include "../Common/RegisterArc.h" #include "../Common/StreamObjects.h" #include "../Common/StreamUtils.h" -#include "../Compress/ZlibDecoder.h" +#include "HfsHandler.h" /* if HFS_SHOW_ALT_STREAMS is defined, the handler will show attribute files and resource forks. In most cases it looks useless. So we disable it. */ -// #define HFS_SHOW_ALT_STREAMS +#define HFS_SHOW_ALT_STREAMS #define Get16(p) GetBe16(p) #define Get32(p) GetBe32(p) #define Get64(p) GetBe64(p) +#define Get16a(p) GetBe16a(p) +#define Get32a(p) GetBe32a(p) + namespace NArchive { namespace NHfs { -static const char *kResFileName = "rsrc"; // "com.apple.ResourceFork"; +static const char * const kResFileName = "rsrc"; // "com.apple.ResourceFork"; struct CExtent { @@ -80,6 +83,8 @@ struct CFork }; static const unsigned kNumFixedExtents = 8; +static const unsigned kForkRecSize = 16 + kNumFixedExtents * 8; + void CFork::Parse(const Byte *p) { @@ -102,38 +107,36 @@ UInt32 CFork::Calc_NumBlocks_from_Extents() const { UInt32 num = 0; FOR_VECTOR (i, Extents) - { num += Extents[i].NumBlocks; - } return num; } bool CFork::Check_NumBlocks() const { - UInt32 num = 0; + UInt32 num = NumBlocks; FOR_VECTOR (i, Extents) { - UInt32 next = num + Extents[i].NumBlocks; - if (next < num) + const UInt32 cur = Extents[i].NumBlocks; + if (num < cur) return false; - num = next; + num -= cur; } - return num == NumBlocks; + return num == 0; } struct CIdIndexPair { UInt32 ID; - int Index; + unsigned Index; int Compare(const CIdIndexPair &a) const; }; -#define RINOZ(x) { int __tt = (x); if (__tt != 0) return __tt; } +#define RINOZ(x) { const int _t_ = (x); if (_t_ != 0) return _t_; } int CIdIndexPair::Compare(const CIdIndexPair &a) const { - RINOZ(MyCompare(ID, a.ID)); + RINOZ(MyCompare(ID, a.ID)) return MyCompare(Index, a.Index); } @@ -142,10 +145,10 @@ static int FindItemIndex(const CRecordVector &items, UInt32 id) unsigned left = 0, right = items.Size(); while (left != right) { - unsigned mid = (left + right) / 2; - UInt32 midVal = items[mid].ID; + const unsigned mid = (left + right) / 2; + const UInt32 midVal = items[mid].ID; if (id == midVal) - return items[mid].Index; + return (int)items[mid].Index; if (id < midVal) right = mid; else @@ -159,10 +162,10 @@ static int Find_in_IdExtents(const CObjectVector &items, UInt32 id) unsigned left = 0, right = items.Size(); while (left != right) { - unsigned mid = (left + right) / 2; - UInt32 midVal = items[mid].ID; + const unsigned mid = (left + right) / 2; + const UInt32 midVal = items[mid].ID; if (id == midVal) - return mid; + return (int)mid; if (id < midVal) right = mid; else @@ -173,7 +176,7 @@ static int Find_in_IdExtents(const CObjectVector &items, UInt32 id) bool CFork::Upgrade(const CObjectVector &items, UInt32 id) { - int index = Find_in_IdExtents(items, id); + const int index = Find_in_IdExtents(items, id); if (index < 0) return true; const CIdExtents &item = items[index]; @@ -186,8 +189,13 @@ bool CFork::Upgrade(const CObjectVector &items, UInt32 id) struct CVolHeader { - Byte Header[2]; - UInt16 Version; + unsigned BlockSizeLog; + UInt32 NumFiles; + UInt32 NumFolders; + UInt32 NumBlocks; + UInt32 NumFreeBlocks; + + bool Is_Hsfx_ver5; // UInt32 Attr; // UInt32 LastMountedVersion; // UInt32 JournalInfoBlock; @@ -197,19 +205,13 @@ struct CVolHeader // UInt32 BackupTime; // UInt32 CheckedTime; - UInt32 NumFiles; - UInt32 NumFolders; - unsigned BlockSizeLog; - UInt32 NumBlocks; - UInt32 NumFreeBlocks; - // UInt32 WriteCount; // UInt32 FinderInfo[8]; // UInt64 VolID; UInt64 GetPhySize() const { return (UInt64)NumBlocks << BlockSizeLog; } UInt64 GetFreeSize() const { return (UInt64)NumFreeBlocks << BlockSizeLog; } - bool IsHfsX() const { return Version > 4; } + bool IsHfsX() const { return Is_Hsfx_ver5; } }; inline void HfsTimeToFileTime(UInt32 hfsTime, FILETIME &ft) @@ -227,6 +229,127 @@ enum ERecordType RECORD_TYPE_FILE_THREAD }; + + +// static const UInt32 kMethod_1_NO_COMPRESSION = 1; // in xattr +static const UInt32 kMethod_ZLIB_ATTR = 3; +static const UInt32 kMethod_ZLIB_RSRC = 4; +// static const UInt32 kMethod_DEDUP = 5; // de-dup within the generation store +// macos 10.10 +static const UInt32 kMethod_LZVN_ATTR = 7; +static const UInt32 kMethod_LZVN_RSRC = 8; +static const UInt32 kMethod_COPY_ATTR = 9; +static const UInt32 kMethod_COPY_RSRC = 10; +// macos 10.11 +// static const UInt32 kMethod_LZFSE_ATTR = 11; +static const UInt32 kMethod_LZFSE_RSRC = 12; + +// static const UInt32 kMethod_ZBM_RSRC = 14; + +static const char * const g_Methods[] = +{ + NULL + , NULL + , NULL + , "ZLIB-attr" + , "ZLIB-rsrc" + , NULL + , NULL + , "LZVN-attr" + , "LZVN-rsrc" + , "COPY-attr" + , "COPY-rsrc" + , "LZFSE-attr" + , "LZFSE-rsrc" + , NULL + , "ZBM-rsrc" +}; + + +static const Byte k_COPY_Uncompressed_Marker = 0xcc; +static const Byte k_LZVN_Uncompressed_Marker = 6; + +void CCompressHeader::Parse(const Byte *p, size_t dataSize) +{ + Clear(); + + if (dataSize < k_decmpfs_HeaderSize) + return; + if (GetUi32(p) != 0x636D7066) // magic == "fpmc" + return; + Method = GetUi32(p + 4); + UnpackSize = GetUi64(p + 8); + dataSize -= k_decmpfs_HeaderSize; + IsCorrect = true; + + if ( Method == kMethod_ZLIB_RSRC + || Method == kMethod_COPY_RSRC + || Method == kMethod_LZVN_RSRC + || Method == kMethod_LZFSE_RSRC + // || Method == kMethod_ZBM_RSRC // for debug + ) + { + IsResource = true; + if (dataSize == 0) + IsSupported = ( + Method != kMethod_LZFSE_RSRC && + Method != kMethod_COPY_RSRC); + return; + } + + if ( Method == kMethod_ZLIB_ATTR + || Method == kMethod_COPY_ATTR + || Method == kMethod_LZVN_ATTR + // || Method == kMethod_LZFSE_ATTR + ) + { + if (dataSize == 0) + return; + const Byte b = p[k_decmpfs_HeaderSize]; + if ( (Method == kMethod_ZLIB_ATTR && (b & 0xf) == 0xf) + || (Method == kMethod_COPY_ATTR && b == k_COPY_Uncompressed_Marker) + || (Method == kMethod_LZVN_ATTR && b == k_LZVN_Uncompressed_Marker)) + { + dataSize--; + // if (UnpackSize > dataSize) + if (UnpackSize != dataSize) + return; + DataPos = k_decmpfs_HeaderSize + 1; + IsSupported = true; + } + else + { + if (Method != kMethod_COPY_ATTR) + IsSupported = true; + DataPos = k_decmpfs_HeaderSize; + } + } +} + + +void CCompressHeader::MethodToProp(NWindows::NCOM::CPropVariant &prop) const +{ + if (!IsCorrect) + return; + const UInt32 method = Method; + const char *p = NULL; + if (method < Z7_ARRAY_SIZE(g_Methods)) + p = g_Methods[method]; + AString s; + if (p) + s = p; + else + s.Add_UInt32(method); + // if (!IsSupported) s += "-unsuported"; + prop = s; +} + +void MethodsMaskToProp(UInt32 methodsMask, NWindows::NCOM::CPropVariant &prop) +{ + FLAGS_TO_PROP(g_Methods, methodsMask, prop); +} + + struct CItem { UString Name; @@ -240,7 +363,7 @@ struct CItem UInt32 ID; UInt32 CTime; UInt32 MTime; - // UInt32 AttrMTime; + UInt32 AttrMTime; UInt32 ATime; // UInt32 BackupDate; @@ -265,82 +388,119 @@ struct CItem CFork DataFork; CFork ResourceFork; - // for compressed attribute - UInt64 UnpackSize; - size_t DataPos; - UInt32 PackSize; - unsigned Method; - bool UseAttr; - bool UseInlineData; + // for compressed attribute (decmpfs) + int decmpfs_AttrIndex; + CCompressHeader CompressHeader; - CItem(): UseAttr(false), UseInlineData(false) {} + CItem(): + decmpfs_AttrIndex(-1) + {} bool IsDir() const { return Type == RECORD_TYPE_FOLDER; } - const CFork &GetFork(bool isResource) const { return (CFork & )*(isResource ? &ResourceFork: &DataFork ); } + // const CFork *GetFork(bool isResource) const { return (isResource ? &ResourceFork: &DataFork); } }; + struct CAttr { UInt32 ID; - UInt32 Size; - size_t Pos; + bool Fork_defined; + + // UInt32 Size; // for (Fork_defined == false) case + // size_t DataPos; // for (Fork_defined == false) case + CByteBuffer Data; + + CFork Fork; + UString Name; + + UInt64 GetSize() const + { + if (Fork_defined) + return Fork.Size; + return Data.Size(); + } + + CAttr(): + Fork_defined(false) + // Size(0), + // DataPos(0), + {} }; + +static const int kAttrIndex_Item = -1; +static const int kAttrIndex_Resource = -2; + struct CRef { unsigned ItemIndex; int AttrIndex; int Parent; - bool IsResource; - bool IsAltStream() const { return IsResource || AttrIndex >= 0; } - CRef(): AttrIndex(-1), Parent(-1), IsResource(false) {} + void Construct() { AttrIndex = kAttrIndex_Item; Parent = -1; } + bool IsResource() const { return AttrIndex == kAttrIndex_Resource; } + bool IsAltStream() const { return AttrIndex != kAttrIndex_Item; } + bool IsItem() const { return AttrIndex == kAttrIndex_Item; } }; + class CDatabase { HRESULT ReadFile(const CFork &fork, CByteBuffer &buf, IInStream *inStream); HRESULT LoadExtentFile(const CFork &fork, IInStream *inStream, CObjectVector *overflowExtentsArray); HRESULT LoadAttrs(const CFork &fork, IInStream *inStream, IArchiveOpenCallback *progress); HRESULT LoadCatalog(const CFork &fork, const CObjectVector *overflowExtentsArray, IInStream *inStream, IArchiveOpenCallback *progress); - bool Parse_decmpgfs(const CAttr &attr, CItem &item, bool &skip); + bool Parse_decmpgfs(unsigned attrIndex, CItem &item, bool &skip); public: CRecordVector Refs; CObjectVector Items; CObjectVector Attrs; - CByteBuffer AttrBuf; + // CByteBuffer AttrBuf; CVolHeader Header; bool HeadersError; + bool UnsupportedFeature; bool ThereAreAltStreams; // bool CaseSensetive; + UInt32 MethodsMask; UString ResFileName; - UInt64 PhySize; + UInt64 SpecOffset; + // UInt64 PhySize; + UInt64 PhySize2; + UInt64 ArcFileSize; void Clear() { - PhySize = 0; + SpecOffset = 0; + // PhySize = 0; + PhySize2 = 0; + ArcFileSize = 0; + MethodsMask = 0; HeadersError = false; + UnsupportedFeature = false; ThereAreAltStreams = false; // CaseSensetive = false; + Refs.Clear(); Items.Clear(); Attrs.Clear(); - AttrBuf.Free(); + // AttrBuf.Free(); } UInt64 Get_UnpackSize_of_Ref(const CRef &ref) const { if (ref.AttrIndex >= 0) - return Attrs[ref.AttrIndex].Size; + return Attrs[ref.AttrIndex].GetSize(); const CItem &item = Items[ref.ItemIndex]; + if (ref.IsResource()) + return item.ResourceFork.Size; if (item.IsDir()) return 0; - if (item.UseAttr) - return item.UnpackSize; - return item.GetFork(ref.IsResource).Size; + else if (item.CompressHeader.IsCorrect) + return item.CompressHeader.UnpackSize; + return item.DataFork.Size; } void GetItemPath(unsigned index, NWindows::NCOM::CPropVariant &path) const; @@ -366,7 +526,7 @@ void CDatabase::GetItemPath(unsigned index, NWindows::NCOM::CPropVariant &path) { unsigned len = 0; const unsigned kNumLevelsMax = (1 << 10); - int cur = index; + unsigned cur = index; unsigned i; for (i = 0; i < kNumLevelsMax; i++) @@ -374,7 +534,7 @@ void CDatabase::GetItemPath(unsigned index, NWindows::NCOM::CPropVariant &path) const CRef &ref = Refs[cur]; const UString *s; - if (ref.IsResource) + if (ref.IsResource()) s = &ResFileName; else if (ref.AttrIndex >= 0) s = &Attrs[ref.AttrIndex].Name; @@ -383,8 +543,8 @@ void CDatabase::GetItemPath(unsigned index, NWindows::NCOM::CPropVariant &path) len += s->Len(); len++; - cur = ref.Parent; - if (cur < 0) + cur = (unsigned)ref.Parent; + if (ref.Parent < 0) break; } @@ -399,7 +559,7 @@ void CDatabase::GetItemPath(unsigned index, NWindows::NCOM::CPropVariant &path) const UString *s; wchar_t delimChar = L':'; - if (ref.IsResource) + if (ref.IsResource()) s = &ResFileName; else if (ref.AttrIndex >= 0) s = &Attrs[ref.AttrIndex].Name; @@ -415,12 +575,18 @@ void CDatabase::GetItemPath(unsigned index, NWindows::NCOM::CPropVariant &path) const wchar_t *src = (const wchar_t *)*s; wchar_t *dest = p + len; for (unsigned j = 0; j < curLen; j++) - dest[j] = src[j]; + { + wchar_t c = src[j]; + // 18.06 + if (c == CHAR_PATH_SEPARATOR || c == '/') + c = '_'; + dest[j] = c; + } if (len == 0) break; p[--len] = delimChar; - cur = ref.Parent; + cur = (unsigned)ref.Parent; } } @@ -430,7 +596,10 @@ HRESULT CDatabase::ReadFile(const CFork &fork, CByteBuffer &buf, IInStream *inSt { if (fork.NumBlocks >= Header.NumBlocks) return S_FALSE; - size_t totalSize = (size_t)fork.NumBlocks << Header.BlockSizeLog; + if (((ArcFileSize - SpecOffset) >> Header.BlockSizeLog) + 1 < fork.NumBlocks) + return S_FALSE; + + const size_t totalSize = (size_t)fork.NumBlocks << Header.BlockSizeLog; if ((totalSize >> Header.BlockSizeLog) != fork.NumBlocks) return S_FALSE; buf.Alloc(totalSize); @@ -444,10 +613,10 @@ HRESULT CDatabase::ReadFile(const CFork &fork, CByteBuffer &buf, IInStream *inSt e.NumBlocks > fork.NumBlocks - curBlock || e.NumBlocks > Header.NumBlocks - e.Pos) return S_FALSE; - RINOK(inStream->Seek((UInt64)e.Pos << Header.BlockSizeLog, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekSet(inStream, SpecOffset + ((UInt64)e.Pos << Header.BlockSizeLog))) RINOK(ReadStream_FALSE(inStream, (Byte *)buf + ((size_t)curBlock << Header.BlockSizeLog), - (size_t)e.NumBlocks << Header.BlockSizeLog)); + (size_t)e.NumBlocks << Header.BlockSizeLog)) curBlock += e.NumBlocks; } return S_OK; @@ -463,20 +632,36 @@ struct CNodeDescriptor // Byte Height; unsigned NumRecords; - bool CheckNumRecords(unsigned nodeSizeLog) - { - return (kNodeDescriptor_Size + ((UInt32)NumRecords + 1) * 2 <= ((UInt32)1 << nodeSizeLog)); - } - void Parse(const Byte *p); + bool Parse(const Byte *p, unsigned nodeSizeLog); }; -void CNodeDescriptor::Parse(const Byte *p) + +bool CNodeDescriptor::Parse(const Byte *p, unsigned nodeSizeLog) { fLink = Get32(p); // bLink = Get32(p + 4); Kind = p[8]; // Height = p[9]; NumRecords = Get16(p + 10); + + const size_t nodeSize = (size_t)1 << nodeSizeLog; + if (kNodeDescriptor_Size + ((UInt32)NumRecords + 1) * 2 > nodeSize) + return false; + const size_t limit = nodeSize - ((UInt32)NumRecords + 1) * 2; + + p += nodeSize - 2; + + for (unsigned i = 0; i < NumRecords; i++) + { + const UInt32 offs = Get16(p); + p -= 2; + const UInt32 offsNext = Get16(p); + if (offs < kNodeDescriptor_Size + || offs >= offsNext + || offsNext > limit) + return false; + } + return true; } struct CHeaderRec @@ -497,11 +682,14 @@ struct CHeaderRec // UInt32 Attributes; // UInt32 Reserved3[16]; - HRESULT Parse(const Byte *p); + HRESULT Parse2(const CByteBuffer &buf); }; -HRESULT CHeaderRec::Parse(const Byte *p) +HRESULT CHeaderRec::Parse2(const CByteBuffer &buf) { + if (buf.Size() < kNodeDescriptor_Size + 0x2A + 16 * 4) + return S_FALSE; + const Byte * p = (const Byte *)buf + kNodeDescriptor_Size; // TreeDepth = Get16(p); // RootNode = Get32(p + 2); // LeafRecords = Get32(p + 6); @@ -527,6 +715,10 @@ HRESULT CHeaderRec::Parse(const Byte *p) for (int i = 0; i < 16; i++) Reserved3[i] = Get32(p + 0x2A + i * 4); */ + + if ((buf.Size() >> NodeSizeLog) < TotalNodes) + return S_FALSE; + return S_OK; } @@ -547,22 +739,21 @@ HRESULT CDatabase::LoadExtentFile(const CFork &fork, IInStream *inStream, CObjec if (fork.NumBlocks == 0) return S_OK; CByteBuffer buf; - RINOK(ReadFile(fork, buf, inStream)); + RINOK(ReadFile(fork, buf, inStream)) const Byte *p = (const Byte *)buf; // CNodeDescriptor nodeDesc; // nodeDesc.Parse(p); CHeaderRec hr; - RINOK(hr.Parse(p + kNodeDescriptor_Size)); - - if ((buf.Size() >> hr.NodeSizeLog) < hr.TotalNodes) - return S_FALSE; + RINOK(hr.Parse2(buf)) UInt32 node = hr.FirstLeafNode; if (node == 0) return S_OK; + if (hr.TotalNodes == 0) + return S_FALSE; - CByteBuffer usedBuf(hr.TotalNodes); + CByteArr usedBuf(hr.TotalNodes); memset(usedBuf, 0, hr.TotalNodes); while (node != 0) @@ -571,10 +762,9 @@ HRESULT CDatabase::LoadExtentFile(const CFork &fork, IInStream *inStream, CObjec return S_FALSE; usedBuf[node] = 1; - size_t nodeOffset = (size_t)node << hr.NodeSizeLog; + const size_t nodeOffset = (size_t)node << hr.NodeSizeLog; CNodeDescriptor desc; - desc.Parse(p + nodeOffset); - if (!desc.CheckNumRecords(hr.NodeSizeLog)) + if (!desc.Parse(p + nodeOffset, hr.NodeSizeLog)) return S_FALSE; if (desc.Kind != kNodeType_Leaf) return S_FALSE; @@ -583,22 +773,20 @@ HRESULT CDatabase::LoadExtentFile(const CFork &fork, IInStream *inStream, CObjec for (unsigned i = 0; i < desc.NumRecords; i++) { - const UInt32 nodeSize = (UInt32)1 << hr.NodeSizeLog; - const UInt32 offs = Get16(p + nodeOffset + nodeSize - (i + 1) * 2); - const UInt32 offsNext = Get16(p + nodeOffset + nodeSize - (i + 2) * 2); - if (offs > nodeSize || offsNext > nodeSize) - return S_FALSE; - UInt32 recSize = offsNext - offs; + const UInt32 nodeSize = ((UInt32)1 << hr.NodeSizeLog); + const Byte *r = p + nodeOffset + nodeSize - i * 2; + const UInt32 offs = Get16(r - 2); + UInt32 recSize = Get16(r - 4) - offs; const unsigned kKeyLen = 10; if (recSize != 2 + kKeyLen + kNumFixedExtents * 8) return S_FALSE; - const Byte *r = p + nodeOffset + offs; + r = p + nodeOffset + offs; if (Get16(r) != kKeyLen) return S_FALSE; - Byte forkType = r[2]; + const Byte forkType = r[2]; unsigned forkTypeIndex; if (forkType == kExtentForkType_Data) forkTypeIndex = 0; @@ -608,8 +796,8 @@ HRESULT CDatabase::LoadExtentFile(const CFork &fork, IInStream *inStream, CObjec continue; CObjectVector &overflowExtents = overflowExtentsArray[forkTypeIndex]; - UInt32 id = Get32(r + 4); - UInt32 startBlock = Get32(r + 8); + const UInt32 id = Get32(r + 4); + const UInt32 startBlock = Get32(r + 8); r += 2 + kKeyLen; bool needNew = true; @@ -659,7 +847,7 @@ static void LoadName(const Byte *data, unsigned len, UString &dest) unsigned i; for (i = 0; i < len; i++) { - wchar_t c = Get16(data + i * 2); + const wchar_t c = Get16(data + i * 2); if (c == 0) break; p[i] = c; @@ -672,7 +860,7 @@ static bool IsNameEqualTo(const Byte *data, const char *name) { for (unsigned i = 0;; i++) { - char c = name[i]; + const char c = name[i]; if (c == 0) return true; if (Get16(data + i * 2) != (Byte)c) @@ -681,7 +869,7 @@ static bool IsNameEqualTo(const Byte *data, const char *name) } static const UInt32 kAttrRecordType_Inline = 0x10; -// static const UInt32 kAttrRecordType_Fork = 0x20; +static const UInt32 kAttrRecordType_Fork = 0x20; // static const UInt32 kAttrRecordType_Extents = 0x30; HRESULT CDatabase::LoadAttrs(const CFork &fork, IInStream *inStream, IArchiveOpenCallback *progress) @@ -689,24 +877,24 @@ HRESULT CDatabase::LoadAttrs(const CFork &fork, IInStream *inStream, IArchiveOpe if (fork.NumBlocks == 0) return S_OK; - RINOK(ReadFile(fork, AttrBuf, inStream)); + CByteBuffer AttrBuf; + RINOK(ReadFile(fork, AttrBuf, inStream)) const Byte *p = (const Byte *)AttrBuf; // CNodeDescriptor nodeDesc; // nodeDesc.Parse(p); CHeaderRec hr; - RINOK(hr.Parse(p + kNodeDescriptor_Size)); + RINOK(hr.Parse2(AttrBuf)) // CaseSensetive = (Header.IsHfsX() && hr.KeyCompareType == 0xBC); - if ((AttrBuf.Size() >> hr.NodeSizeLog) < hr.TotalNodes) - return S_FALSE; - UInt32 node = hr.FirstLeafNode; if (node == 0) return S_OK; - - CByteBuffer usedBuf(hr.TotalNodes); + if (hr.TotalNodes == 0) + return S_FALSE; + + CByteArr usedBuf(hr.TotalNodes); memset(usedBuf, 0, hr.TotalNodes); CFork resFork; @@ -717,42 +905,36 @@ HRESULT CDatabase::LoadAttrs(const CFork &fork, IInStream *inStream, IArchiveOpe return S_FALSE; usedBuf[node] = 1; - size_t nodeOffset = (size_t)node << hr.NodeSizeLog; + const size_t nodeOffset = (size_t)node << hr.NodeSizeLog; CNodeDescriptor desc; - desc.Parse(p + nodeOffset); - if (!desc.CheckNumRecords(hr.NodeSizeLog)) + if (!desc.Parse(p + nodeOffset, hr.NodeSizeLog)) return S_FALSE; if (desc.Kind != kNodeType_Leaf) return S_FALSE; for (unsigned i = 0; i < desc.NumRecords; i++) { - const UInt32 nodeSize = (1 << hr.NodeSizeLog); - const UInt32 offs = Get16(p + nodeOffset + nodeSize - (i + 1) * 2); - const UInt32 offsNext = Get16(p + nodeOffset + nodeSize - (i + 2) * 2); - UInt32 recSize = offsNext - offs; - if (offs >= nodeSize - || offsNext > nodeSize - || offsNext < offs) - return S_FALSE; - + const UInt32 nodeSize = ((UInt32)1 << hr.NodeSizeLog); + const Byte *r = p + nodeOffset + nodeSize - i * 2; + const UInt32 offs = Get16(r - 2); + UInt32 recSize = Get16(r - 4) - offs; const unsigned kHeadSize = 14; if (recSize < kHeadSize) return S_FALSE; - const Byte *r = p + nodeOffset + offs; - UInt32 keyLen = Get16(r); + r = p + nodeOffset + offs; + const UInt32 keyLen = Get16(r); // UInt16 pad = Get16(r + 2); - UInt32 fileID = Get32(r + 4); - unsigned startBlock = Get32(r + 8); + const UInt32 fileID = Get32(r + 4); + const unsigned startBlock = Get32(r + 8); if (startBlock != 0) { // that case is still unsupported - HeadersError = true; + UnsupportedFeature = true; continue; } - unsigned nameLen = Get16(r + 12); + const unsigned nameLen = Get16(r + 12); if (keyLen + 2 > recSize || keyLen != kHeadSize - 2 + nameLen * 2) @@ -767,36 +949,56 @@ HRESULT CDatabase::LoadAttrs(const CFork &fork, IInStream *inStream, IArchiveOpe if (recSize < 4) return S_FALSE; - UInt32 recordType = Get32(r); - if (recordType != kAttrRecordType_Inline) + const UInt32 recordType = Get32(r); + + if (progress && (Attrs.Size() & 0xFFF) == 0) { - // Probably only kAttrRecordType_Inline now is used in real HFS files - HeadersError = true; - continue; + const UInt64 numFiles = 0; + RINOK(progress->SetCompleted(&numFiles, NULL)) } - const UInt32 kRecordHeaderSize = 16; - if (recSize < kRecordHeaderSize) - return S_FALSE; - UInt32 dataSize = Get32(r + 12); - - r += kRecordHeaderSize; - recSize -= kRecordHeaderSize; - - if (recSize < dataSize) + if (Attrs.Size() >= ((UInt32)1 << 31)) return S_FALSE; CAttr &attr = Attrs.AddNew(); attr.ID = fileID; - attr.Pos = nodeOffset + offs + 2 + keyLen + kRecordHeaderSize; - attr.Size = dataSize; LoadName(name, nameLen, attr.Name); - if (progress && (i & 0xFFF) == 0) + if (recordType == kAttrRecordType_Fork) { - UInt64 numFiles = 0; - RINOK(progress->SetCompleted(&numFiles, NULL)); + // 22.00 : some hfs files contain it; + /* spec: If the attribute has more than 8 extents, there will be additional + records (of type kAttrRecordType_Extents) for this attribute. */ + if (recSize != 8 + kForkRecSize) + return S_FALSE; + if (Get32(r + 4) != 0) // reserved + return S_FALSE; + attr.Fork.Parse(r + 8); + attr.Fork_defined = true; + continue; + } + else if (recordType != kAttrRecordType_Inline) + { + UnsupportedFeature = true; + continue; } + + const unsigned kRecordHeaderSize = 16; + if (recSize < kRecordHeaderSize) + return S_FALSE; + if (Get32(r + 4) != 0 || Get32(r + 8) != 0) // reserved + return S_FALSE; + const UInt32 dataSize = Get32(r + 12); + + r += kRecordHeaderSize; + recSize -= kRecordHeaderSize; + + if (recSize < dataSize) + return S_FALSE; + + attr.Data.CopyFrom(r, dataSize); + // attr.DataPos = nodeOffset + offs + 2 + keyLen + kRecordHeaderSize; + // attr.Size = dataSize; } node = desc.fLink; @@ -804,87 +1006,58 @@ HRESULT CDatabase::LoadAttrs(const CFork &fork, IInStream *inStream, IArchiveOpe return S_OK; } -static const UInt32 kMethod_Attr = 3; // data stored in attribute file -static const UInt32 kMethod_Resource = 4; // data stored in resource fork -bool CDatabase::Parse_decmpgfs(const CAttr &attr, CItem &item, bool &skip) +bool CDatabase::Parse_decmpgfs(unsigned attrIndex, CItem &item, bool &skip) { + const CAttr &attr = Attrs[attrIndex]; skip = false; - if (!attr.Name.IsEqualTo("com.apple.decmpfs")) - return true; - if (item.UseAttr || !item.DataFork.IsEmpty()) + if (item.CompressHeader.IsCorrect || !item.DataFork.IsEmpty()) return false; - const UInt32 k_decmpfs_headerSize = 16; - UInt32 dataSize = attr.Size; - if (dataSize < k_decmpfs_headerSize) - return false; - const Byte *r = AttrBuf + attr.Pos; - if (GetUi32(r) != 0x636D7066) // magic == "fpmc" - return false; - item.Method = GetUi32(r + 4); - item.UnpackSize = GetUi64(r + 8); - dataSize -= k_decmpfs_headerSize; - r += k_decmpfs_headerSize; - if (item.Method == kMethod_Resource) - { - if (dataSize != 0) - return false; - item.UseAttr = true; - } - else if (item.Method == kMethod_Attr) + item.CompressHeader.Parse(attr.Data, attr.Data.Size()); + + if (item.CompressHeader.IsCorrect) { - if (dataSize == 0) - return false; - Byte b = r[0]; - if ((b & 0xF) == 0xF) - { - dataSize--; - if (item.UnpackSize > dataSize) - return false; - item.DataPos = attr.Pos + k_decmpfs_headerSize + 1; - item.PackSize = dataSize; - item.UseAttr = true; - item.UseInlineData = true; - } - else - { - item.DataPos = attr.Pos + k_decmpfs_headerSize; - item.PackSize = dataSize; - item.UseAttr = true; - } + item.decmpfs_AttrIndex = (int)attrIndex; + skip = true; + if (item.CompressHeader.Method < sizeof(MethodsMask) * 8) + MethodsMask |= ((UInt32)1 << item.CompressHeader.Method); } - else - return false; - skip = true; + return true; } + HRESULT CDatabase::LoadCatalog(const CFork &fork, const CObjectVector *overflowExtentsArray, IInStream *inStream, IArchiveOpenCallback *progress) { - unsigned reserveSize = (unsigned)(Header.NumFolders + 1 + Header.NumFiles); - Items.ClearAndReserve(reserveSize); - Refs.ClearAndReserve(reserveSize); - - CRecordVector IdToIndexMap; - IdToIndexMap.ClearAndReserve(reserveSize); - CByteBuffer buf; - RINOK(ReadFile(fork, buf, inStream)); + RINOK(ReadFile(fork, buf, inStream)) const Byte *p = (const Byte *)buf; // CNodeDescriptor nodeDesc; // nodeDesc.Parse(p); CHeaderRec hr; - RINOK(hr.Parse(p + kNodeDescriptor_Size)); - - // CaseSensetive = (Header.IsHfsX() && hr.KeyCompareType == 0xBC); + RINOK(hr.Parse2(buf)) - if ((buf.Size() >> hr.NodeSizeLog) < hr.TotalNodes) - return S_FALSE; + CRecordVector IdToIndexMap; - CByteBuffer usedBuf(hr.TotalNodes); - memset(usedBuf, 0, hr.TotalNodes); + const unsigned reserveSize = (unsigned)(Header.NumFolders + 1 + Header.NumFiles); + + const unsigned kBasicRecSize = 0x58; + const unsigned kMinRecSize = kBasicRecSize + 10; + + if ((UInt64)reserveSize * kMinRecSize < buf.Size()) + { + Items.ClearAndReserve(reserveSize); + Refs.ClearAndReserve(reserveSize); + IdToIndexMap.ClearAndReserve(reserveSize); + } + + // CaseSensetive = (Header.IsHfsX() && hr.KeyCompareType == 0xBC); + + CByteArr usedBuf(hr.TotalNodes); + if (hr.TotalNodes != 0) + memset(usedBuf, 0, hr.TotalNodes); CFork resFork; @@ -900,8 +1073,7 @@ HRESULT CDatabase::LoadCatalog(const CFork &fork, const CObjectVector= nodeSize - || offsNext > nodeSize - || offsNext < offs - || recSize < 6) + const Byte *r = p + nodeOffset + nodeSize - i * 2; + const UInt32 offs = Get16(r - 2); + UInt32 recSize = Get16(r - 4) - offs; + if (recSize < 6) return S_FALSE; - const Byte *r = p + nodeOffset + offs; + r = p + nodeOffset + offs; UInt32 keyLen = Get16(r); UInt32 parentID = Get32(r + 2); if (keyLen < 6 || (keyLen & 1) != 0 || keyLen + 2 > recSize) @@ -944,7 +1113,6 @@ HRESULT CDatabase::LoadCatalog(const CFork &fork, const CObjectVectorSetCompleted(&numItems, NULL)); + const UInt64 numItems = Items.Size(); + RINOK(progress->SetCompleted(&numItems, NULL)) } } node = desc.fLink; @@ -1064,14 +1231,18 @@ HRESULT CDatabase::LoadCatalog(const CFork &fork, const CObjectVector= 0) { @@ -1136,15 +1308,18 @@ HRESULT CDatabase::LoadCatalog(const CFork &fork, const CObjectVector ((UInt32)1 << 29) || + h.NumFiles > ((UInt32)1 << 30)) + return S_FALSE; + if (progress) + { + UInt64 numFiles = (UInt64)h.NumFiles + h.NumFolders + 1; + RINOK(progress->SetTotal(&numFiles, NULL)) + } + h.NumFreeBlocks = Get16(p + 0x22); + */ + + // v24.09: blockSize in old HFS image can be non-power of 2. + const UInt32 blockSize = Get32a(p + 0x14); // drAlBlkSiz + if (blockSize == 0 || (blockSize & 0x1ff)) + return S_FALSE; + const unsigned numBlocks = Get16a(p + 0x12); // drNmAlBlks + // UInt16 drFreeBks = Get16a(p + 0x22); // number of unused allocation blocks + /* + we suppose that it has the following layout: + { + start data with header + blocks[h.NumBlocks] + end data with header (probably size_of_footer <= blockSize). + } + */ + // PhySize2 = ((UInt64)numBlocks + 2) * blockSize; + const unsigned sector_of_FirstBlock = Get16a(p + 0x1c); // drAlBlSt : first allocation block in volume + const UInt32 startBlock = Get16a(p + 0x7c + 2); + const UInt32 blockCount = Get16a(p + 0x7c + 4); + SpecOffset = (UInt32)sector_of_FirstBlock << 9; // it's 32-bit here + PhySize2 = SpecOffset + (UInt64)numBlocks * blockSize; + SpecOffset += (UInt64)startBlock * blockSize; + // before v24.09: // SpecOffset = (UInt64)(1 + startBlock) * blockSize; + const UInt64 phy = SpecOffset + (UInt64)blockCount * blockSize; + if (PhySize2 < phy) + PhySize2 = phy; + UInt32 tail = 1 << 10; // at least 1 KiB tail (for footer MDB) is expected. + if (tail < blockSize) + tail = blockSize; + RINOK(InStream_GetSize_SeekToEnd(inStream, ArcFileSize)) + if (ArcFileSize > PhySize2 && + ArcFileSize - PhySize2 <= tail) + { + // data after blocks[h.NumBlocks] must contain another copy of MDB. + // In example where blockSize is not power of 2, we have + // (ArcFileSize - PhySize2) < blockSize. + // We suppose that data after blocks[h.NumBlocks] is part of HFS archive. + // Maybe we should scan for footer MDB data (in last 1 KiB)? + PhySize2 = ArcFileSize; + } + RINOK(InStream_SeekSet(inStream, SpecOffset)) + RINOK(ReadStream_FALSE(inStream, buf32, kHfsHeaderSize)) } - const Byte *p = buf + kHeaderPadSize; - CVolHeader &h = Header; - - h.Header[0] = p[0]; - h.Header[1] = p[1]; - if (p[0] != 'H' || (p[1] != '+' && p[1] != 'X')) - return S_FALSE; - h.Version = Get16(p + 2); - if (h.Version < 4 || h.Version > 5) - return S_FALSE; - // h.Attr = Get32(p + 4); - // h.LastMountedVersion = Get32(p + 8); - // h.JournalInfoBlock = Get32(p + 0xC); - - h.CTime = Get32(p + 0x10); - h.MTime = Get32(p + 0x14); - // h.BackupTime = Get32(p + 0x18); - // h.CheckedTime = Get32(p + 0x1C); - - h.NumFiles = Get32(p + 0x20); - h.NumFolders = Get32(p + 0x24); - - if (h.NumFolders > ((UInt32)1 << 29) || - h.NumFiles > ((UInt32)1 << 30)) - return S_FALSE; - if (progress) + // HFS+ / HFSX volume header (starting from offset==1024): { - UInt64 numFiles = (UInt64)h.NumFiles + h.NumFolders + 1; - RINOK(progress->SetTotal(&numFiles, NULL)); + // v24.09: we use strict condition test for pair signature(Version): + // H+(4), HX(5): + const UInt32 sig = GetUi32a(p); + // h.Version = Get16(p + 2); + h.Is_Hsfx_ver5 = false; + if (sig != k_Signature_LE32_HFSP_VER4) + { + if (sig != k_Signature_LE32_HFSX_VER5) + return S_FALSE; + h.Is_Hsfx_ver5 = true; + } } - - UInt32 blockSize = Get32(p + 0x28); - { + const UInt32 blockSize = Get32a(p + 0x28); unsigned i; for (i = 9; ((UInt32)1 << i) != blockSize; i++) if (i == 31) return S_FALSE; h.BlockSizeLog = i; } +#if 1 + // HFS Plus DOCs: The first 1024 bytes are reserved for use as boot blocks + // v24.09: we don't check starting 1 KiB before old (HFS MDB) block ("BD" signture) . + // but we still check starting 1 KiB before HFS+ / HFSX volume header. + // are there HFS+ / HFSX images with non-zero data in this reserved area? + { + for (unsigned i = 0; i < kHeaderPadSize / 4; i++) + if (buf32[i] != 0) + return S_FALSE; + } +#endif + // h.Attr = Get32a(p + 4); + // h.LastMountedVersion = Get32a(p + 8); + // h.JournalInfoBlock = Get32a(p + 0xC); + h.CTime = Get32a(p + 0x10); + h.MTime = Get32a(p + 0x14); + // h.BackupTime = Get32a(p + 0x18); + // h.CheckedTime = Get32a(p + 0x1C); + h.NumFiles = Get32a(p + 0x20); + h.NumFolders = Get32a(p + 0x24); + if (h.NumFolders > ((UInt32)1 << 29) || + h.NumFiles > ((UInt32)1 << 30)) + return S_FALSE; - h.NumBlocks = Get32(p + 0x2C); - h.NumFreeBlocks = Get32(p + 0x30); + RINOK(InStream_GetSize_SeekToEnd(inStream, ArcFileSize)) + if (progress) + { + const UInt64 numFiles = (UInt64)h.NumFiles + h.NumFolders + 1; + RINOK(progress->SetTotal(&numFiles, NULL)) + } + h.NumBlocks = Get32a(p + 0x2C); + h.NumFreeBlocks = Get32a(p + 0x30); /* h.NextCalatlogNodeID = Get32(p + 0x40); h.WriteCount = Get32(p + 0x44); @@ -1221,14 +1517,7 @@ HRESULT CDatabase::Open2(IInStream *inStream, IArchiveOpenCallback *progress) h.VolID = Get64(p + 0x68); */ - /* - UInt64 endPos; - RINOK(inStream->Seek(0, STREAM_SEEK_END, &endPos)); - if ((endPos >> h.BlockSizeLog) < h.NumBlocks) - return S_FALSE; - */ - - ResFileName.SetFromAscii(kResFileName); + ResFileName = kResFileName; CFork extentsFork, catalogFork, attrFork; // allocationFork.Parse(p + 0x70 + 0x50 * 0); @@ -1242,7 +1531,7 @@ HRESULT CDatabase::Open2(IInStream *inStream, IArchiveOpenCallback *progress) HeadersError = true; else { - HRESULT res = LoadExtentFile(extentsFork, inStream, overflowExtents); + const HRESULT res = LoadExtentFile(extentsFork, inStream, overflowExtents); if (res == S_FALSE) HeadersError = true; else if (res != S_OK) @@ -1257,40 +1546,31 @@ HRESULT CDatabase::Open2(IInStream *inStream, IArchiveOpenCallback *progress) else { if (attrFork.Size != 0) - RINOK(LoadAttrs(attrFork, inStream, progress)); + RINOK(LoadAttrs(attrFork, inStream, progress)) } - RINOK(LoadCatalog(catalogFork, overflowExtents, inStream, progress)); + RINOK(LoadCatalog(catalogFork, overflowExtents, inStream, progress)) - PhySize = Header.GetPhySize(); + // PhySize = Header.GetPhySize(); return S_OK; } -class CHandler: +Z7_class_CHandler_final: public IInArchive, public IArchiveGetRawProps, public IInArchiveGetStream, public CMyUnknownImp, public CDatabase { - CMyComPtr _stream; + Z7_IFACES_IMP_UNK_3( + IInArchive, + IArchiveGetRawProps, + IInArchiveGetStream) + CMyComPtr _stream; HRESULT GetForkStream(const CFork &fork, ISequentialInStream **stream); - - HRESULT ExtractZlibFile( - ISequentialOutStream *realOutStream, - const CItem &item, - NCompress::NZlib::CDecoder *_zlibDecoderSpec, - CByteBuffer &buf, - UInt64 progressStart, - IArchiveExtractCallback *extractCallback); -public: - MY_UNKNOWN_IMP3(IInArchive, IArchiveGetRawProps, IInArchiveGetStream) - INTERFACE_IInArchive(;) - INTERFACE_IArchiveGetRawProps(;) - STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream); }; static const Byte kProps[] = @@ -1302,12 +1582,22 @@ static const Byte kProps[] = kpidCTime, kpidMTime, kpidATime, - kpidPosixAttrib + kpidChangeTime, + kpidPosixAttrib, + /* + kpidUserId, + kpidGroupId, + */ +#ifdef HFS_SHOW_ALT_STREAMS + kpidIsAltStream, +#endif + kpidMethod }; static const Byte kArcProps[] = { kpidMethod, + kpidCharacts, kpidClusterSize, kpidFreeSpace, kpidCTime, @@ -1319,12 +1609,14 @@ IMP_IInArchive_ArcProps static void HfsTimeToProp(UInt32 hfsTime, NWindows::NCOM::CPropVariant &prop) { + if (hfsTime == 0) + return; FILETIME ft; HfsTimeToFileTime(hfsTime, ft); - prop = ft; + prop.SetAsTimeFrom_FT_Prec(ft, k_PropVar_TimePrec_Base); } -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NWindows::NCOM::CPropVariant prop; @@ -1332,16 +1624,27 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) { case kpidExtension: prop = Header.IsHfsX() ? "hfsx" : "hfs"; break; case kpidMethod: prop = Header.IsHfsX() ? "HFSX" : "HFS+"; break; - case kpidPhySize: prop = PhySize; break; + case kpidCharacts: MethodsMaskToProp(MethodsMask, prop); break; + case kpidPhySize: + { + UInt64 v = SpecOffset + Header.GetPhySize(); // PhySize; + if (v < PhySize2) + v = PhySize2; + prop = v; + break; + } case kpidClusterSize: prop = (UInt32)1 << Header.BlockSizeLog; break; case kpidFreeSpace: prop = (UInt64)Header.GetFreeSize(); break; case kpidMTime: HfsTimeToProp(Header.MTime, prop); break; case kpidCTime: { - FILETIME localFt, ft; - HfsTimeToFileTime(Header.CTime, localFt); - if (LocalFileTimeToFileTime(&localFt, &ft)) - prop = ft; + if (Header.CTime != 0) + { + FILETIME localFt, ft; + HfsTimeToFileTime(Header.CTime, localFt); + if (LocalFileTimeToFileTime(&localFt, &ft)) + prop.SetAsTimeFrom_FT_Prec(ft, k_PropVar_TimePrec_Base); + } break; } case kpidIsTree: prop = true; break; @@ -1349,6 +1652,7 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) { UInt32 flags = 0; if (HeadersError) flags |= kpv_ErrorFlags_HeadersError; + if (UnsupportedFeature) flags |= kpv_ErrorFlags_UnsupportedFeature; if (flags != 0) prop = flags; break; @@ -1360,20 +1664,20 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) COM_TRY_END } -STDMETHODIMP CHandler::GetNumRawProps(UInt32 *numProps) +Z7_COM7F_IMF(CHandler::GetNumRawProps(UInt32 *numProps)) { *numProps = 0; return S_OK; } -STDMETHODIMP CHandler::GetRawPropInfo(UInt32 /* index */, BSTR *name, PROPID *propID) +Z7_COM7F_IMF(CHandler::GetRawPropInfo(UInt32 /* index */, BSTR *name, PROPID *propID)) { *name = NULL; *propID = 0; return S_OK; } -STDMETHODIMP CHandler::GetParent(UInt32 index, UInt32 *parent, UInt32 *parentType) +Z7_COM7F_IMF(CHandler::GetParent(UInt32 index, UInt32 *parent, UInt32 *parentType)) { const CRef &ref = Refs[index]; *parentType = ref.IsAltStream() ? @@ -1383,7 +1687,7 @@ STDMETHODIMP CHandler::GetParent(UInt32 index, UInt32 *parent, UInt32 *parentTyp return S_OK; } -STDMETHODIMP CHandler::GetRawProp(UInt32 index, PROPID propID, const void **data, UInt32 *dataSize, UInt32 *propType) +Z7_COM7F_IMF(CHandler::GetRawProp(UInt32 index, PROPID propID, const void **data, UInt32 *dataSize, UInt32 *propType)) { *data = NULL; *dataSize = 0; @@ -1393,22 +1697,25 @@ STDMETHODIMP CHandler::GetRawProp(UInt32 index, PROPID propID, const void **data { const CRef &ref = Refs[index]; const UString *s; - if (ref.IsResource) + if (ref.IsResource()) s = &ResFileName; else if (ref.AttrIndex >= 0) s = &Attrs[ref.AttrIndex].Name; else s = &Items[ref.ItemIndex].Name; *data = (const wchar_t *)(*s); - *dataSize = (s->Len() + 1) * sizeof(wchar_t); + *dataSize = (s->Len() + 1) * (UInt32)sizeof(wchar_t); *propType = PROP_DATA_TYPE_wchar_t_PTR_Z_LE; return S_OK; } + #else + UNUSED_VAR(index) + UNUSED_VAR(propID) #endif return S_OK; } -STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NWindows::NCOM::CPropVariant prop; @@ -1418,8 +1725,9 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val { case kpidPath: GetItemPath(index, prop); break; case kpidName: + { const UString *s; - if (ref.IsResource) + if (ref.IsResource()) s = &ResFileName; else if (ref.AttrIndex >= 0) s = &Attrs[ref.AttrIndex].Name; @@ -1427,22 +1735,31 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val s = &item.Name; prop = *s; break; + } case kpidPackSize: { UInt64 size; if (ref.AttrIndex >= 0) - size = Attrs[ref.AttrIndex].Size; + size = Attrs[ref.AttrIndex].GetSize(); + else if (ref.IsResource()) + size = (UInt64)item.ResourceFork.NumBlocks << Header.BlockSizeLog; else if (item.IsDir()) break; - else if (item.UseAttr) + else if (item.CompressHeader.IsCorrect) { - if (item.Method == kMethod_Resource) - size = item.ResourceFork.NumBlocks << Header.BlockSizeLog; + if (item.CompressHeader.IsMethod_Resource()) + size = (UInt64)item.ResourceFork.NumBlocks << Header.BlockSizeLog; + else if (item.decmpfs_AttrIndex >= 0) + { + // size = item.PackSize; + const CAttr &attr = Attrs[item.decmpfs_AttrIndex]; + size = attr.Data.Size() - item.CompressHeader.DataPos; + } else - size = item.PackSize; + size = 0; } else - size = item.GetFork(ref.IsResource).NumBlocks << Header.BlockSizeLog; + size = (UInt64)item.DataFork.NumBlocks << Header.BlockSizeLog; prop = size; break; } @@ -1450,43 +1767,53 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val { UInt64 size; if (ref.AttrIndex >= 0) - size = Attrs[ref.AttrIndex].Size; + size = Attrs[ref.AttrIndex].GetSize(); + else if (ref.IsResource()) + size = item.ResourceFork.Size; else if (item.IsDir()) break; - else if (item.UseAttr) - size = item.UnpackSize; + else if (item.CompressHeader.IsCorrect) + size = item.CompressHeader.UnpackSize; else - size = item.GetFork(ref.IsResource).Size; + size = item.DataFork.Size; prop = size; break; } - case kpidIsDir: prop = item.IsDir(); break; + case kpidIsDir: prop = (ref.IsItem() && item.IsDir()); break; case kpidIsAltStream: prop = ref.IsAltStream(); break; - case kpidCTime: HfsTimeToProp(item.CTime, prop); break; case kpidMTime: HfsTimeToProp(item.MTime, prop); break; case kpidATime: HfsTimeToProp(item.ATime, prop); break; - - case kpidPosixAttrib: if (ref.AttrIndex < 0) prop = (UInt32)item.FileMode; break; + case kpidChangeTime: HfsTimeToProp(item.AttrMTime, prop); break; + case kpidPosixAttrib: if (ref.IsItem()) prop = (UInt32)item.FileMode; break; + /* + case kpidUserId: prop = (UInt32)item.OwnerID; break; + case kpidGroupId: prop = (UInt32)item.GroupID; break; + */ + + case kpidMethod: + if (ref.IsItem()) + item.CompressHeader.MethodToProp(prop); + break; } prop.Detach(value); return S_OK; COM_TRY_END } -STDMETHODIMP CHandler::Open(IInStream *inStream, +Z7_COM7F_IMF(CHandler::Open(IInStream *inStream, const UInt64 * /* maxCheckStartPosition */, - IArchiveOpenCallback *callback) + IArchiveOpenCallback *callback)) { COM_TRY_BEGIN Close(); - RINOK(Open2(inStream, callback)); + RINOK(Open2(inStream, callback)) _stream = inStream; return S_OK; COM_TRY_END } -STDMETHODIMP CHandler::Close() +Z7_COM7F_IMF(CHandler::Close()) { _stream.Release(); Clear(); @@ -1495,151 +1822,551 @@ STDMETHODIMP CHandler::Close() static const UInt32 kCompressionBlockSize = 1 << 16; -HRESULT CHandler::ExtractZlibFile( - ISequentialOutStream *outStream, - const CItem &item, - NCompress::NZlib::CDecoder *_zlibDecoderSpec, - CByteBuffer &buf, - UInt64 progressStart, - IArchiveExtractCallback *extractCallback) +CDecoder::CDecoder(bool IsAdlerOptional) +{ + /* Some new hfs files contain zlib resource fork without Adler checksum. + We do not know how we must detect case where there is Adler + checksum or there is no Adler checksum. + */ + _zlibDecoder->IsAdlerOptional = IsAdlerOptional; + _lzfseDecoder->LzvnMode = true; +} + +HRESULT CDecoder::ExtractResourceFork_ZLIB( + ISequentialInStream *inStream, ISequentialOutStream *outStream, + UInt64 forkSize, UInt64 unpackSize, + UInt64 progressStart, IArchiveExtractCallback *extractCallback) { - CMyComPtr inStream; - const CFork &fork = item.ResourceFork; - RINOK(GetForkStream(fork, &inStream)); const unsigned kHeaderSize = 0x100 + 8; - RINOK(ReadStream_FALSE(inStream, buf, kHeaderSize)); - UInt32 dataPos = Get32(buf); - UInt32 mapPos = Get32(buf + 4); - UInt32 dataSize = Get32(buf + 8); - UInt32 mapSize = Get32(buf + 12); + + const size_t kBufSize = kCompressionBlockSize; + _buf.Alloc(kBufSize + 0x10); // we need 1 additional bytes for uncompressed chunk header + + RINOK(ReadStream_FALSE(inStream, _buf, kHeaderSize)) + Byte *buf = _buf; + const UInt32 dataPos = Get32(buf); + const UInt32 mapPos = Get32(buf + 4); + const UInt32 dataSize = Get32(buf + 8); + const UInt32 mapSize = Get32(buf + 12); const UInt32 kResMapSize = 50; if (mapSize != kResMapSize - || dataPos + dataSize != mapPos - || mapPos + mapSize != fork.Size) + || dataPos > mapPos + || dataSize != mapPos - dataPos + || mapSize > forkSize + || mapPos != forkSize - mapSize) return S_FALSE; - UInt32 dataSize2 = Get32(buf + 0x100); - if (4 + dataSize2 != dataSize || dataSize2 < 8) + const UInt32 dataSize2 = Get32(buf + 0x100); + if (4 + dataSize2 != dataSize + || dataSize2 < 8 + || dataSize2 > dataSize) return S_FALSE; - UInt32 numBlocks = GetUi32(buf + 0x100 + 4); + const UInt32 numBlocks = GetUi32(buf + 0x100 + 4); if (((dataSize2 - 4) >> 3) < numBlocks) return S_FALSE; - if (item.UnpackSize > (UInt64)numBlocks * kCompressionBlockSize) - return S_FALSE; - - if (item.UnpackSize + kCompressionBlockSize < (UInt64)numBlocks * kCompressionBlockSize) - return S_FALSE; + { + const UInt64 up = unpackSize + kCompressionBlockSize - 1; + if (up < unpackSize || up / kCompressionBlockSize != numBlocks) + return S_FALSE; + } - UInt32 tableSize = (numBlocks << 3); + const UInt32 tableSize = (numBlocks << 3); - CByteBuffer tableBuf(tableSize); + _tableBuf.AllocAtLeast(tableSize); - RINOK(ReadStream_FALSE(inStream, tableBuf, tableSize)); + RINOK(ReadStream_FALSE(inStream, _tableBuf, tableSize)) + const Byte *tableBuf = _tableBuf; UInt32 prev = 4 + tableSize; UInt32 i; for (i = 0; i < numBlocks; i++) { - UInt32 offset = GetUi32(tableBuf + i * 8); - UInt32 size = GetUi32(tableBuf + i * 8 + 4); - if (size == 0) - return S_FALSE; - if (prev != offset) + const UInt32 offs = GetUi32(tableBuf + i * 8); + const UInt32 size = GetUi32(tableBuf + i * 8 + 4); + if (size == 0 + || prev != offs + || offs > dataSize2 + || size > dataSize2 - offs) return S_FALSE; - if (offset > dataSize2 || - size > dataSize2 - offset) - return S_FALSE; - prev = offset + size; + prev = offs + size; } if (prev != dataSize2) return S_FALSE; - CBufInStream *bufInStreamSpec = new CBufInStream; - CMyComPtr bufInStream = bufInStreamSpec; + CMyComPtr2_Create bufInStream; + // bool padError = false; UInt64 outPos = 0; + for (i = 0; i < numBlocks; i++) { - UInt64 rem = item.UnpackSize - outPos; + const UInt64 rem = unpackSize - outPos; if (rem == 0) return S_FALSE; UInt32 blockSize = kCompressionBlockSize; if (rem < kCompressionBlockSize) blockSize = (UInt32)rem; - UInt32 size = GetUi32(tableBuf + i * 8 + 4); + const UInt32 size = GetUi32(tableBuf + i * 8 + 4); - if (size > buf.Size() || size > kCompressionBlockSize + 1) + if (size > kCompressionBlockSize + 1) return S_FALSE; - RINOK(ReadStream_FALSE(inStream, buf, size)); + RINOK(ReadStream_FALSE(inStream, buf, size)) if ((buf[0] & 0xF) == 0xF) { - // that code was not tested. Are there HFS archives with uncompressed block + // (buf[0] = 0xff) is marker of uncompressed block in APFS + // that code was not tested in HFS if (size - 1 != blockSize) return S_FALSE; if (outStream) { - RINOK(WriteStream(outStream, buf, blockSize)); + RINOK(WriteStream(outStream, buf + 1, blockSize)) } } else { - UInt64 blockSize64 = blockSize; - bufInStreamSpec->Init(buf, size); - RINOK(_zlibDecoderSpec->Code(bufInStream, outStream, NULL, &blockSize64, NULL)); - if (_zlibDecoderSpec->GetOutputProcessedSize() != blockSize || - _zlibDecoderSpec->GetInputProcessedSize() != size) + const UInt64 blockSize64 = blockSize; + bufInStream->Init(buf, size); + RINOK(_zlibDecoder.Interface()->Code(bufInStream, outStream, NULL, &blockSize64, NULL)) + if (_zlibDecoder->GetOutputProcessedSize() != blockSize) return S_FALSE; + const UInt64 inSize = _zlibDecoder->GetInputProcessedSize(); + if (inSize != size) + { + if (inSize > size) + return S_FALSE; + // apfs file can contain junk (non-zeros) after data block. + /* + if (!padError) + { + const Byte *p = buf + (UInt32)inSize; + const Byte *e = p + (size - (UInt32)inSize); + do + { + if (*p != 0) + { + padError = true; + break; + } + } + while (++p != e); + } + */ + } } outPos += blockSize; - UInt64 progressPos = progressStart + outPos; - RINOK(extractCallback->SetCompleted(&progressPos)); + if ((i & 0xFF) == 0) + { + const UInt64 progressPos = progressStart + outPos; + RINOK(extractCallback->SetCompleted(&progressPos)) + } } - if (outPos != item.UnpackSize) + if (outPos != unpackSize) return S_FALSE; + // if (padError) return S_FALSE; + /* We check Resource Map Are there HFS files with another values in Resource Map ??? */ - RINOK(ReadStream_FALSE(inStream, buf, mapSize)); - UInt32 types = Get16(buf + 24); - UInt32 names = Get16(buf + 26); - UInt32 numTypes = Get16(buf + 28); + RINOK(ReadStream_FALSE(inStream, buf, mapSize)) + const UInt32 types = Get16(buf + 24); + const UInt32 names = Get16(buf + 26); + const UInt32 numTypes = Get16(buf + 28); if (numTypes != 0 || types != 28 || names != kResMapSize) return S_FALSE; - UInt32 resType = Get32(buf + 30); - UInt32 numResources = Get16(buf + 34); - UInt32 resListOffset = Get16(buf + 36); + const UInt32 resType = Get32(buf + 30); + const UInt32 numResources = Get16(buf + 34); + const UInt32 resListOffset = Get16(buf + 36); if (resType != 0x636D7066) // cmpf return S_FALSE; if (numResources != 0 || resListOffset != 10) return S_FALSE; - UInt32 entryId = Get16(buf + 38); - UInt32 nameOffset = Get16(buf + 40); + const UInt32 entryId = Get16(buf + 38); + const UInt32 nameOffset = Get16(buf + 40); // Byte attrib = buf[42]; - UInt32 resourceOffset = Get32(buf + 42) & 0xFFFFFF; + const UInt32 resourceOffset = Get32(buf + 42) & 0xFFFFFF; if (entryId != 1 || nameOffset != 0xFFFF || resourceOffset != 0) return S_FALSE; return S_OK; } -STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, - Int32 testMode, IArchiveExtractCallback *extractCallback) + + +HRESULT CDecoder::ExtractResourceFork_LZFSE( + ISequentialInStream *inStream, ISequentialOutStream *outStream, + UInt64 forkSize, UInt64 unpackSize, + UInt64 progressStart, IArchiveExtractCallback *extractCallback) +{ + const UInt32 kNumBlocksMax = (UInt32)1 << 29; + if (unpackSize >= (UInt64)kNumBlocksMax * kCompressionBlockSize) + return S_FALSE; + const UInt32 numBlocks = (UInt32)((unpackSize + kCompressionBlockSize - 1) / kCompressionBlockSize); + const UInt32 numBlocks2 = numBlocks + 1; + const UInt32 tableSize = (numBlocks2 << 2); + if (tableSize > forkSize) + return S_FALSE; + _tableBuf.AllocAtLeast(tableSize); + RINOK(ReadStream_FALSE(inStream, _tableBuf, tableSize)) + const Byte *tableBuf = _tableBuf; + + { + UInt32 prev = GetUi32(tableBuf); + if (prev != tableSize) + return S_FALSE; + for (UInt32 i = 1; i < numBlocks2; i++) + { + const UInt32 offs = GetUi32(tableBuf + i * 4); + if (offs <= prev) + return S_FALSE; + prev = offs; + } + if (prev != forkSize) + return S_FALSE; + } + + const size_t kBufSize = kCompressionBlockSize; + _buf.Alloc(kBufSize + 0x10); // we need 1 additional bytes for uncompressed chunk header + + CMyComPtr2_Create bufInStream; + + UInt64 outPos = 0; + + for (UInt32 i = 0; i < numBlocks; i++) + { + const UInt64 rem = unpackSize - outPos; + if (rem == 0) + return S_FALSE; + UInt32 blockSize = kCompressionBlockSize; + if (rem < kCompressionBlockSize) + blockSize = (UInt32)rem; + + const UInt32 size = + GetUi32(tableBuf + i * 4 + 4) - + GetUi32(tableBuf + i * 4); + + if (size > kCompressionBlockSize + 1) + return S_FALSE; + + RINOK(ReadStream_FALSE(inStream, _buf, size)) + const Byte *buf = _buf; + + if (buf[0] == k_LZVN_Uncompressed_Marker) + { + if (size - 1 != blockSize) + return S_FALSE; + if (outStream) + { + RINOK(WriteStream(outStream, buf + 1, blockSize)) + } + } + else + { + const UInt64 blockSize64 = blockSize; + const UInt64 packSize64 = size; + bufInStream->Init(buf, size); + RINOK(_lzfseDecoder.Interface()->Code(bufInStream, outStream, &packSize64, &blockSize64, NULL)) + // in/out sizes were checked in Code() + } + + outPos += blockSize; + if ((i & 0xFF) == 0) + { + const UInt64 progressPos = progressStart + outPos; + RINOK(extractCallback->SetCompleted(&progressPos)) + } + } + + return S_OK; +} + + +/* +static UInt32 GetUi24(const Byte *p) +{ + return p[0] + ((UInt32)p[1] << 8) + ((UInt32)p[2] << 24); +} + +HRESULT CDecoder::ExtractResourceFork_ZBM( + ISequentialInStream *inStream, ISequentialOutStream *outStream, + UInt64 forkSize, UInt64 unpackSize, + UInt64 progressStart, IArchiveExtractCallback *extractCallback) +{ + const UInt32 kNumBlocksMax = (UInt32)1 << 29; + if (unpackSize >= (UInt64)kNumBlocksMax * kCompressionBlockSize) + return S_FALSE; + const UInt32 numBlocks = (UInt32)((unpackSize + kCompressionBlockSize - 1) / kCompressionBlockSize); + const UInt32 numBlocks2 = numBlocks + 1; + const UInt32 tableSize = (numBlocks2 << 2); + if (tableSize > forkSize) + return S_FALSE; + _tableBuf.AllocAtLeast(tableSize); + RINOK(ReadStream_FALSE(inStream, _tableBuf, tableSize)); + const Byte *tableBuf = _tableBuf; + + { + UInt32 prev = GetUi32(tableBuf); + if (prev != tableSize) + return S_FALSE; + for (UInt32 i = 1; i < numBlocks2; i++) + { + const UInt32 offs = GetUi32(tableBuf + i * 4); + if (offs <= prev) + return S_FALSE; + prev = offs; + } + if (prev != forkSize) + return S_FALSE; + } + + const size_t kBufSize = kCompressionBlockSize; + _buf.Alloc(kBufSize + 0x10); // we need 1 additional bytes for uncompressed chunk header + + CBufInStream *bufInStream = new CBufInStream; + CMyComPtr bufInStream = bufInStream; + + UInt64 outPos = 0; + + for (UInt32 i = 0; i < numBlocks; i++) + { + const UInt64 rem = unpackSize - outPos; + if (rem == 0) + return S_FALSE; + UInt32 blockSize = kCompressionBlockSize; + if (rem < kCompressionBlockSize) + blockSize = (UInt32)rem; + + const UInt32 size = + GetUi32(tableBuf + i * 4 + 4) - + GetUi32(tableBuf + i * 4); + + // if (size > kCompressionBlockSize + 1) + if (size > blockSize + 1) + return S_FALSE; // we don't expect it, because encode will use uncompressed chunk + + RINOK(ReadStream_FALSE(inStream, _buf, size)); + const Byte *buf = _buf; + + // (size != 0) + // if (size == 0) return S_FALSE; + + if (buf[0] == 0xFF) // uncompressed marker + { + if (size != blockSize + 1) + return S_FALSE; + if (outStream) + { + RINOK(WriteStream(outStream, buf + 1, blockSize)); + } + } + else + { + if (size < 4) + return S_FALSE; + if (buf[0] != 'Z' || + buf[1] != 'B' || + buf[2] != 'M' || + buf[3] != 9) + return S_FALSE; + // for debug: + unsigned packPos = 4; + unsigned unpackPos = 0; + unsigned packRem = size - packPos; + for (;;) + { + if (packRem < 6) + return S_FALSE; + const UInt32 packSize = GetUi24(buf + packPos); + const UInt32 chunkUnpackSize = GetUi24(buf + packPos + 3); + if (packSize < 6) + return S_FALSE; + if (packSize > packRem) + return S_FALSE; + if (chunkUnpackSize > blockSize - unpackPos) + return S_FALSE; + packPos += packSize; + packRem -= packSize; + unpackPos += chunkUnpackSize; + if (packSize == 6) + { + if (chunkUnpackSize != 0) + return S_FALSE; + break; + } + if (packSize >= chunkUnpackSize + 6) + { + if (packSize > chunkUnpackSize + 6) + return S_FALSE; + // uncompressed chunk; + } + else + { + // compressed chunk + const Byte *t = buf + packPos - packSize + 6; + UInt32 r = packSize - 6; + if (r < 9) + return S_FALSE; + const UInt32 v0 = GetUi24(t); + const UInt32 v1 = GetUi24(t + 3); + const UInt32 v2 = GetUi24(t + 6); + if (v0 > v1 || v1 > v2 || v2 > packSize) + return S_FALSE; + // here we need the code that will decompress ZBM chunk + } + } + + if (unpackPos != blockSize) + return S_FALSE; + + UInt32 size1 = size; + if (size1 > kCompressionBlockSize) + { + size1 = kCompressionBlockSize; + // return S_FALSE; + } + if (outStream) + { + RINOK(WriteStream(outStream, buf, size1)) + + const UInt32 kTempSize = 1 << 16; + Byte temp[kTempSize]; + memset(temp, 0, kTempSize); + + for (UInt32 k = size1; k < kCompressionBlockSize; k++) + { + UInt32 cur = kCompressionBlockSize - k; + if (cur > kTempSize) + cur = kTempSize; + RINOK(WriteStream(outStream, temp, cur)) + k += cur; + } + } + + // const UInt64 blockSize64 = blockSize; + // const UInt64 packSize64 = size; + // bufInStream->Init(buf, size); + // RINOK(_zbmDecoderSpec->Code(bufInStream, outStream, &packSize64, &blockSize64, NULL)); + // in/out sizes were checked in Code() + } + + outPos += blockSize; + if ((i & 0xFF) == 0) + { + const UInt64 progressPos = progressStart + outPos; + RINOK(extractCallback->SetCompleted(&progressPos)); + } + } + + return S_OK; +} +*/ + +HRESULT CDecoder::Extract( + ISequentialInStream *inStreamFork, ISequentialOutStream *realOutStream, + UInt64 forkSize, + const CCompressHeader &compressHeader, + const CByteBuffer *data, + UInt64 progressStart, IArchiveExtractCallback *extractCallback, + int &opRes) +{ + opRes = NExtract::NOperationResult::kDataError; + + if (compressHeader.IsMethod_Uncompressed_Inline()) + { + const size_t packSize = data->Size() - compressHeader.DataPos; + if (realOutStream) + { + RINOK(WriteStream(realOutStream, *data + compressHeader.DataPos, packSize)) + } + opRes = NExtract::NOperationResult::kOK; + return S_OK; + } + + if (compressHeader.Method == kMethod_ZLIB_ATTR || + compressHeader.Method == kMethod_LZVN_ATTR) + { + CMyComPtr2_Create bufInStream; + const size_t packSize = data->Size() - compressHeader.DataPos; + bufInStream->Init(*data + compressHeader.DataPos, packSize); + + if (compressHeader.Method == kMethod_ZLIB_ATTR) + { + const HRESULT hres = _zlibDecoder.Interface()->Code(bufInStream, realOutStream, + NULL, &compressHeader.UnpackSize, NULL); + if (hres == S_OK) + if (_zlibDecoder->GetOutputProcessedSize() == compressHeader.UnpackSize + && _zlibDecoder->GetInputProcessedSize() == packSize) + opRes = NExtract::NOperationResult::kOK; + return hres; + } + { + const UInt64 packSize64 = packSize; + const HRESULT hres = _lzfseDecoder.Interface()->Code(bufInStream, realOutStream, + &packSize64, &compressHeader.UnpackSize, NULL); + if (hres == S_OK) + { + // in/out sizes were checked in Code() + opRes = NExtract::NOperationResult::kOK; + } + return hres; + } + } + + HRESULT hres; + if (compressHeader.Method == NHfs::kMethod_ZLIB_RSRC) + { + hres = ExtractResourceFork_ZLIB( + inStreamFork, realOutStream, + forkSize, compressHeader.UnpackSize, + progressStart, extractCallback); + // for debug: + // hres = NCompress::CopyStream(inStreamFork, realOutStream, NULL); + } + else if (compressHeader.Method == NHfs::kMethod_LZVN_RSRC) + { + hres = ExtractResourceFork_LZFSE( + inStreamFork, realOutStream, + forkSize, compressHeader.UnpackSize, + progressStart, extractCallback); + } + /* + else if (compressHeader.Method == NHfs::kMethod_ZBM_RSRC) + { + hres = ExtractResourceFork_ZBM( + inStreamFork, realOutStream, + forkSize, compressHeader.UnpackSize, + progressStart, extractCallback); + } + */ + else + { + opRes = NExtract::NOperationResult::kUnsupportedMethod; + hres = S_FALSE; + } + + if (hres == S_OK) + opRes = NExtract::NOperationResult::kOK; + return hres; +} + + +Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testMode, IArchiveExtractCallback *extractCallback)) { COM_TRY_BEGIN - bool allFilesMode = (numItems == (UInt32)(Int32)-1); + const bool allFilesMode = (numItems == (UInt32)(Int32)-1); if (allFilesMode) numItems = Refs.Size(); if (numItems == 0) @@ -1651,121 +2378,127 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, const CRef &ref = Refs[allFilesMode ? i : indices[i]]; totalSize += Get_UnpackSize_of_Ref(ref); } - RINOK(extractCallback->SetTotal(totalSize)); + RINOK(extractCallback->SetTotal(totalSize)) UInt64 currentTotalSize = 0, currentItemSize = 0; const size_t kBufSize = kCompressionBlockSize; CByteBuffer buf(kBufSize + 0x10); // we need 1 additional bytes for uncompressed chunk header - NCompress::NZlib::CDecoder *_zlibDecoderSpec = NULL; - CMyComPtr _zlibDecoder; + // there are hfs without adler in zlib. + CDecoder decoder(true); // IsAdlerOptional - for (i = 0; i < numItems; i++, currentTotalSize += currentItemSize) + for (i = 0;; i++, currentTotalSize += currentItemSize) { - RINOK(extractCallback->SetCompleted(¤tTotalSize)); - UInt32 index = allFilesMode ? i : indices[i]; + RINOK(extractCallback->SetCompleted(¤tTotalSize)) + if (i >= numItems) + break; + const UInt32 index = allFilesMode ? i : indices[i]; const CRef &ref = Refs[index]; const CItem &item = Items[ref.ItemIndex]; currentItemSize = Get_UnpackSize_of_Ref(ref); + int opRes; + { CMyComPtr realOutStream; - Int32 askMode = testMode ? + const Int32 askMode = testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; - RINOK(extractCallback->GetStream(index, &realOutStream, askMode)); + RINOK(extractCallback->GetStream(index, &realOutStream, askMode)) - if (ref.AttrIndex < 0 && item.IsDir()) + if (ref.IsItem() && item.IsDir()) { - RINOK(extractCallback->PrepareOperation(askMode)); - RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)); + RINOK(extractCallback->PrepareOperation(askMode)) + RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)) continue; } if (!testMode && !realOutStream) continue; - RINOK(extractCallback->PrepareOperation(askMode)); + + RINOK(extractCallback->PrepareOperation(askMode)) + UInt64 pos = 0; - int res = NExtract::NOperationResult::kDataError; + opRes = NExtract::NOperationResult::kDataError; + const CFork *fork = NULL; + if (ref.AttrIndex >= 0) { - res = NExtract::NOperationResult::kOK; - if (realOutStream) + const CAttr &attr = Attrs[ref.AttrIndex]; + if (attr.Fork_defined && attr.Data.Size() == 0) + fork = &attr.Fork; + else { - const CAttr &attr = Attrs[ref.AttrIndex]; - RINOK(WriteStream(realOutStream, AttrBuf + attr.Pos, attr.Size)); + opRes = NExtract::NOperationResult::kOK; + if (realOutStream) + { + RINOK(WriteStream(realOutStream, + // AttrBuf + attr.Pos, attr.Size + attr.Data, attr.Data.Size() + )) + } } } - else if (item.UseAttr) + else if (ref.IsResource()) + fork = &item.ResourceFork; + else if (item.CompressHeader.IsSupported) { - if (item.UseInlineData) + CMyComPtr inStreamFork; + UInt64 forkSize = 0; + const CByteBuffer *decmpfs_Data = NULL; + + if (item.CompressHeader.IsMethod_Resource()) { - res = NExtract::NOperationResult::kOK; - if (realOutStream) - { - RINOK(WriteStream(realOutStream, AttrBuf + item.DataPos, (size_t)item.UnpackSize)); - } + const CFork &resourceFork = item.ResourceFork; + forkSize = resourceFork.Size; + GetForkStream(resourceFork, &inStreamFork); } else { - if (!_zlibDecoder) - { - _zlibDecoderSpec = new NCompress::NZlib::CDecoder(); - _zlibDecoder = _zlibDecoderSpec; - } - - if (item.Method == kMethod_Attr) - { - CBufInStream *bufInStreamSpec = new CBufInStream; - CMyComPtr bufInStream = bufInStreamSpec; - bufInStreamSpec->Init(AttrBuf + item.DataPos, item.PackSize); - - HRESULT hres = _zlibDecoder->Code(bufInStream, realOutStream, NULL, &item.UnpackSize, NULL); - if (hres != S_FALSE) - { - if (hres != S_OK) - return hres; - if (_zlibDecoderSpec->GetOutputProcessedSize() == item.UnpackSize && - _zlibDecoderSpec->GetInputProcessedSize() == item.PackSize) - res = NExtract::NOperationResult::kOK; - } - } - else - { - HRESULT hres = ExtractZlibFile(realOutStream, item, _zlibDecoderSpec, buf, - currentTotalSize, extractCallback); - if (hres != S_FALSE) - { - if (hres != S_OK) - return hres; - res = NExtract::NOperationResult::kOK; - } - } + const CAttr &attr = Attrs[item.decmpfs_AttrIndex]; + decmpfs_Data = &attr.Data; + } + + if (inStreamFork || decmpfs_Data) + { + const HRESULT hres = decoder.Extract( + inStreamFork, realOutStream, + forkSize, + item.CompressHeader, + decmpfs_Data, + currentTotalSize, extractCallback, + opRes); + if (hres != S_FALSE && hres != S_OK) + return hres; } } + else if (item.CompressHeader.IsCorrect) + opRes = NExtract::NOperationResult::kUnsupportedMethod; else + fork = &item.DataFork; + + if (fork) { - const CFork &fork = item.GetFork(ref.IsResource); - if (fork.IsOk(Header.BlockSizeLog)) + if (fork->IsOk(Header.BlockSizeLog)) { - res = NExtract::NOperationResult::kOK; + opRes = NExtract::NOperationResult::kOK; unsigned extentIndex; - for (extentIndex = 0; extentIndex < fork.Extents.Size(); extentIndex++) + for (extentIndex = 0; extentIndex < fork->Extents.Size(); extentIndex++) { - if (res != NExtract::NOperationResult::kOK) + if (opRes != NExtract::NOperationResult::kOK) break; - if (fork.Size == pos) + if (fork->Size == pos) break; - const CExtent &e = fork.Extents[extentIndex]; - RINOK(_stream->Seek((UInt64)e.Pos << Header.BlockSizeLog, STREAM_SEEK_SET, NULL)); + const CExtent &e = fork->Extents[extentIndex]; + RINOK(InStream_SeekSet(_stream, SpecOffset + ((UInt64)e.Pos << Header.BlockSizeLog))) UInt64 extentRem = (UInt64)e.NumBlocks << Header.BlockSizeLog; while (extentRem != 0) { - UInt64 rem = fork.Size - pos; + const UInt64 rem = fork->Size - pos; if (rem == 0) { // Here we check that there are no extra (empty) blocks in last extent. if (extentRem >= ((UInt64)1 << Header.BlockSizeLog)) - res = NExtract::NOperationResult::kDataError; + opRes = NExtract::NOperationResult::kDataError; break; } size_t cur = kBufSize; @@ -1773,34 +2506,34 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, cur = (size_t)rem; if (cur > extentRem) cur = (size_t)extentRem; - RINOK(ReadStream(_stream, buf, &cur)); + RINOK(ReadStream(_stream, buf, &cur)) if (cur == 0) { - res = NExtract::NOperationResult::kDataError; + opRes = NExtract::NOperationResult::kDataError; break; } if (realOutStream) { - RINOK(WriteStream(realOutStream, buf, cur)); + RINOK(WriteStream(realOutStream, buf, cur)) } pos += cur; extentRem -= cur; - UInt64 processed = currentTotalSize + pos; - RINOK(extractCallback->SetCompleted(&processed)); + const UInt64 processed = currentTotalSize + pos; + RINOK(extractCallback->SetCompleted(&processed)) } } - if (extentIndex != fork.Extents.Size() || fork.Size != pos) - res = NExtract::NOperationResult::kDataError; + if (extentIndex != fork->Extents.Size() || fork->Size != pos) + opRes = NExtract::NOperationResult::kDataError; } } - realOutStream.Release(); - RINOK(extractCallback->SetOperationResult(res)); + } + RINOK(extractCallback->SetOperationResult(opRes)) } return S_OK; COM_TRY_END } -STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) { *numItems = Refs.Size(); return S_OK; @@ -1808,13 +2541,13 @@ STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) HRESULT CHandler::GetForkStream(const CFork &fork, ISequentialInStream **stream) { - *stream = 0; + *stream = NULL; if (!fork.IsOk(Header.BlockSizeLog)) return S_FALSE; - CExtentsStream *extentStreamSpec = new CExtentsStream(); - CMyComPtr extentStream = extentStreamSpec; + CMyComPtr2 extentStream; + extentStream.Create_if_Empty(); UInt64 rem = fork.Size; UInt64 virt = 0; @@ -1832,49 +2565,64 @@ HRESULT CHandler::GetForkStream(const CFork &fork, ISequentialInStream **stream) return S_FALSE; } CSeekExtent se; - se.Phy = (UInt64)e.Pos << Header.BlockSizeLog; + se.Phy = SpecOffset + ((UInt64)e.Pos << Header.BlockSizeLog); se.Virt = virt; virt += cur; rem -= cur; - extentStreamSpec->Extents.Add(se); + extentStream->Extents.Add(se); } if (rem != 0) return S_FALSE; CSeekExtent se; - se.Phy = 0; + se.Phy = 0; // = SpecOffset ? se.Virt = virt; - extentStreamSpec->Extents.Add(se); - extentStreamSpec->Stream = _stream; - extentStreamSpec->Init(); + extentStream->Extents.Add(se); + extentStream->Stream = _stream; + extentStream->Init(); *stream = extentStream.Detach(); return S_OK; } -STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream) +Z7_COM7F_IMF(CHandler::GetStream(UInt32 index, ISequentialInStream **stream)) { - *stream = 0; + *stream = NULL; const CRef &ref = Refs[index]; + const CFork *fork = NULL; if (ref.AttrIndex >= 0) - return S_FALSE; - const CItem &item = Items[ref.ItemIndex]; - if (item.IsDir() || item.UseAttr) - return S_FALSE; - - return GetForkStream(item.GetFork(ref.IsResource), stream); + { + const CAttr &attr = Attrs[ref.AttrIndex]; + if (!attr.Fork_defined || attr.Data.Size() != 0) + return S_FALSE; + fork = &attr.Fork; + } + else + { + const CItem &item = Items[ref.ItemIndex]; + if (ref.IsResource()) + fork = &item.ResourceFork; + else if (item.IsDir()) + return S_FALSE; + else if (item.CompressHeader.IsCorrect) + return S_FALSE; + else + fork = &item.DataFork; + } + return GetForkStream(*fork, stream); } static const Byte k_Signature[] = { + 2, 'B', 'D', 4, 'H', '+', 0, 4, 4, 'H', 'X', 0, 5 }; REGISTER_ARC_I( - "HFS", "hfs hfsx", 0, 0xE3, + "HFS", "hfs hfsx", NULL, 0xE3, k_Signature, kHeaderPadSize, NArcInfoFlags::kMultiSignature, - NULL) + IsArc_HFS) }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/HfsHandler.h b/src/plugins/7zip/7za/cpp/7zip/Archive/HfsHandler.h new file mode 100644 index 00000000..6d869c24 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/HfsHandler.h @@ -0,0 +1,87 @@ +// HfsHandler.h + +#ifndef ZIP7_INC_HFS_HANDLER_H +#define ZIP7_INC_HFS_HANDLER_H + +#include "../../Windows/PropVariant.h" + +#include "../Compress/LzfseDecoder.h" +#include "../Compress/ZlibDecoder.h" + +namespace NArchive { +namespace NHfs { + +static const UInt32 k_decmpfs_HeaderSize = 16; + +struct CCompressHeader +{ + UInt64 UnpackSize; + UInt32 Method; + Byte DataPos; + bool IsCorrect; + bool IsSupported; + bool IsResource; + + bool IsMethod_Compressed_Inline() const { return DataPos == k_decmpfs_HeaderSize; } + bool IsMethod_Uncompressed_Inline() const { return DataPos == k_decmpfs_HeaderSize + 1; } + bool IsMethod_Resource() const { return IsResource; } + + void Parse(const Byte *p, size_t size); + + void Clear() + { + UnpackSize = 0; + Method = 0; + DataPos = 0; + IsCorrect = false; + IsSupported = false; + IsResource = false; + } + + CCompressHeader() { Clear(); } + + void MethodToProp(NWindows::NCOM::CPropVariant &prop) const; +}; + +void MethodsMaskToProp(UInt32 methodsMask, NWindows::NCOM::CPropVariant &prop); + + +class CDecoder +{ + CMyComPtr2_Create _zlibDecoder; + CMyComPtr2_Create _lzfseDecoder; + + CByteBuffer _tableBuf; + CByteBuffer _buf; + + HRESULT ExtractResourceFork_ZLIB( + ISequentialInStream *inStream, ISequentialOutStream *realOutStream, + UInt64 forkSize, UInt64 unpackSize, + UInt64 progressStart, IArchiveExtractCallback *extractCallback); + + HRESULT ExtractResourceFork_LZFSE( + ISequentialInStream *inStream, ISequentialOutStream *realOutStream, + UInt64 forkSize, UInt64 unpackSize, + UInt64 progressStart, IArchiveExtractCallback *extractCallback); + + HRESULT ExtractResourceFork_ZBM( + ISequentialInStream *inStream, ISequentialOutStream *realOutStream, + UInt64 forkSize, UInt64 unpackSize, + UInt64 progressStart, IArchiveExtractCallback *extractCallback); + +public: + + HRESULT Extract( + ISequentialInStream *inStreamFork, ISequentialOutStream *realOutStream, + UInt64 forkSize, + const CCompressHeader &compressHeader, + const CByteBuffer *data, + UInt64 progressStart, IArchiveExtractCallback *extractCallback, + int &opRes); + + CDecoder(bool IsAdlerOptional); +}; + +}} + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/IArchive.h b/src/plugins/7zip/7za/cpp/7zip/Archive/IArchive.h index 0028d762..9dcb2802 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/IArchive.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/IArchive.h @@ -1,22 +1,55 @@ // IArchive.h -#ifndef __IARCHIVE_H -#define __IARCHIVE_H +#ifndef ZIP7_INC_IARCHIVE_H +#define ZIP7_INC_IARCHIVE_H #include "../IProgress.h" #include "../IStream.h" #include "../PropID.h" -#define ARCHIVE_INTERFACE_SUB(i, base, x) DECL_INTERFACE_SUB(i, base, 6, x) -#define ARCHIVE_INTERFACE(i, x) ARCHIVE_INTERFACE_SUB(i, IUnknown, x) +Z7_PURE_INTERFACES_BEGIN + + +#define Z7_IFACE_CONSTR_ARCHIVE_SUB(i, base, n) \ + Z7_DECL_IFACE_7ZIP_SUB(i, base, 6, n) \ + { Z7_IFACE_COM7_PURE(i) }; + +#define Z7_IFACE_CONSTR_ARCHIVE(i, n) \ + Z7_IFACE_CONSTR_ARCHIVE_SUB(i, IUnknown, n) + +/* +How the function in 7-Zip returns object for output parameter via pointer + +1) The caller sets the value of variable before function call: + PROPVARIANT : vt = VT_EMPTY + BSTR : NULL + IUnknown* and derived interfaces : NULL + another scalar types : any non-initialized value is allowed + +2) The callee in current 7-Zip code now can free input object for output parameter: + PROPVARIANT : the callee calls VariantClear(propvaiant_ptr) for input + value stored in variable + another types : the callee ignores stored value. + +3) The callee writes new value to variable for output parameter and + returns execution to caller. + +4) The caller must free or release object returned by the callee: + PROPVARIANT : VariantClear(&propvaiant) + BSTR : SysFreeString(bstr) + IUnknown* and derived interfaces : if (ptr) ptr->Relase() +*/ + namespace NFileTimeType { enum EEnum { - kWindows, + kNotDefined = -1, + kWindows = 0, kUnix, - kDOS + kDOS, + k1ns }; } @@ -34,8 +67,33 @@ namespace NArcInfoFlags const UInt32 kPreArc = 1 << 9; // such archive can be stored before real archive (like SFX stub) const UInt32 kSymLinks = 1 << 10; // the handler supports symbolic links const UInt32 kHardLinks = 1 << 11; // the handler supports hard links + const UInt32 kByExtOnlyOpen = 1 << 12; // call handler only if file extension matches + const UInt32 kHashHandler = 1 << 13; // the handler contains the hashes (checksums) + const UInt32 kCTime = 1 << 14; + const UInt32 kCTime_Default = 1 << 15; + const UInt32 kATime = 1 << 16; + const UInt32 kATime_Default = 1 << 17; + const UInt32 kMTime = 1 << 18; + const UInt32 kMTime_Default = 1 << 19; + // const UInt32 kTTime_Reserved = 1 << 20; + // const UInt32 kTTime_Reserved_Default = 1 << 21; } +namespace NArcInfoTimeFlags +{ + const unsigned kTime_Prec_Mask_bit_index = 0; + const unsigned kTime_Prec_Mask_num_bits = 26; + + const unsigned kTime_Prec_Default_bit_index = 27; + const unsigned kTime_Prec_Default_num_bits = 5; +} + +#define TIME_PREC_TO_ARC_FLAGS_MASK(v) \ + ((UInt32)1 << (NArcInfoTimeFlags::kTime_Prec_Mask_bit_index + (v))) + +#define TIME_PREC_TO_ARC_FLAGS_TIME_DEFAULT(v) \ + ((UInt32)(v) << NArcInfoTimeFlags::kTime_Prec_Default_bit_index) + namespace NArchive { namespace NHandlerPropID @@ -53,8 +111,8 @@ namespace NArchive kSignatureOffset, // VT_UI4 kAltStreams, // VT_BOOL kNtSecure, // VT_BOOL - kFlags // VT_UI4 - // kVersion // VT_UI4 ((VER_MAJOR << 8) | VER_MINOR) + kFlags, // VT_UI4 + kTimeFlags // VT_UI4 }; } @@ -66,7 +124,8 @@ namespace NArchive { kExtract = 0, kTest, - kSkip + kSkip, + kReadExternal }; } @@ -84,6 +143,7 @@ namespace NArchive kIsNotArc, kHeadersError, kWrongPassword + // , kMemError }; } } @@ -96,6 +156,7 @@ namespace NArchive kInArcIndex, kBlockIndex, kOutArcIndex + // kArcProp }; } @@ -106,20 +167,18 @@ namespace NArchive enum { kOK = 0 - , // kError + // kError = 1, + // kError_FileChanged }; } } } -#define INTERFACE_IArchiveOpenCallback(x) \ - STDMETHOD(SetTotal)(const UInt64 *files, const UInt64 *bytes) x; \ - STDMETHOD(SetCompleted)(const UInt64 *files, const UInt64 *bytes) x; \ +#define Z7_IFACEM_IArchiveOpenCallback(x) \ + x(SetTotal(const UInt64 *files, const UInt64 *bytes)) \ + x(SetCompleted(const UInt64 *files, const UInt64 *bytes)) \ -ARCHIVE_INTERFACE(IArchiveOpenCallback, 0x10) -{ - INTERFACE_IArchiveOpenCallback(PURE); -}; +Z7_IFACE_CONSTR_ARCHIVE(IArchiveOpenCallback, 0x10) /* IArchiveExtractCallback:: @@ -137,13 +196,13 @@ IArchiveExtractCallback::GetStream() Int32 askExtractMode (Extract::NAskMode) if (askMode != NExtract::NAskMode::kExtract) { - then the callee can not real stream: (*inStream == NULL) + then the callee doesn't write data to stream: (*outStream == NULL) } Out: - (*inStream == NULL) - for directories - (*inStream == NULL) - if link (hard link or symbolic link) was created - if (*inStream == NULL && askMode == NExtract::NAskMode::kExtract) + (*outStream == NULL) - for directories + (*outStream == NULL) - if link (hard link or symbolic link) was created + if (*outStream == NULL && askMode == NExtract::NAskMode::kExtract) { then the caller must skip extracting of that file. } @@ -170,57 +229,49 @@ SetOperationResult() Int32 opRes (NExtract::NOperationResult) */ -#define INTERFACE_IArchiveExtractCallback(x) \ - INTERFACE_IProgress(x) \ - STDMETHOD(GetStream)(UInt32 index, ISequentialOutStream **outStream, Int32 askExtractMode) x; \ - STDMETHOD(PrepareOperation)(Int32 askExtractMode) x; \ - STDMETHOD(SetOperationResult)(Int32 opRes) x; \ +// INTERFACE_IProgress(x) -ARCHIVE_INTERFACE_SUB(IArchiveExtractCallback, IProgress, 0x20) -{ - INTERFACE_IArchiveExtractCallback(PURE) -}; +#define Z7_IFACEM_IArchiveExtractCallback(x) \ + x(GetStream(UInt32 index, ISequentialOutStream **outStream, Int32 askExtractMode)) \ + x(PrepareOperation(Int32 askExtractMode)) \ + x(SetOperationResult(Int32 opRes)) \ + +Z7_IFACE_CONSTR_ARCHIVE_SUB(IArchiveExtractCallback, IProgress, 0x20) /* -IArchiveExtractCallbackMessage can be requested from IArchiveExtractCallback object +v23: +IArchiveExtractCallbackMessage2 can be requested from IArchiveExtractCallback object by Extract() or UpdateItems() functions to report about extracting errors ReportExtractResult() UInt32 indexType (NEventIndexType) UInt32 index Int32 opRes (NExtract::NOperationResult) */ +/* +before v23: +#define Z7_IFACEM_IArchiveExtractCallbackMessage(x) \ + x(ReportExtractResult(UInt32 indexType, UInt32 index, Int32 opRes)) +Z7_IFACE_CONSTR_ARCHIVE_SUB(IArchiveExtractCallbackMessage, IProgress, 0x21) +*/ +#define Z7_IFACEM_IArchiveExtractCallbackMessage2(x) \ + x(ReportExtractResult(UInt32 indexType, UInt32 index, Int32 opRes)) +Z7_IFACE_CONSTR_ARCHIVE(IArchiveExtractCallbackMessage2, 0x22) -#define INTERFACE_IArchiveExtractCallbackMessage(x) \ - STDMETHOD(ReportExtractResult)(UInt32 indexType, UInt32 index, Int32 opRes) x; \ - -ARCHIVE_INTERFACE_SUB(IArchiveExtractCallbackMessage, IProgress, 0x21) -{ - INTERFACE_IArchiveExtractCallbackMessage(PURE) -}; - - -#define INTERFACE_IArchiveOpenVolumeCallback(x) \ - STDMETHOD(GetProperty)(PROPID propID, PROPVARIANT *value) x; \ - STDMETHOD(GetStream)(const wchar_t *name, IInStream **inStream) x; \ - -ARCHIVE_INTERFACE(IArchiveOpenVolumeCallback, 0x30) -{ - INTERFACE_IArchiveOpenVolumeCallback(PURE); -}; - +#define Z7_IFACEM_IArchiveOpenVolumeCallback(x) \ + x(GetProperty(PROPID propID, PROPVARIANT *value)) \ + x(GetStream(const wchar_t *name, IInStream **inStream)) +Z7_IFACE_CONSTR_ARCHIVE(IArchiveOpenVolumeCallback, 0x30) -ARCHIVE_INTERFACE(IInArchiveGetStream, 0x40) -{ - STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream) PURE; -}; +#define Z7_IFACEM_IInArchiveGetStream(x) \ + x(GetStream(UInt32 index, ISequentialInStream **stream)) +Z7_IFACE_CONSTR_ARCHIVE(IInArchiveGetStream, 0x40) -ARCHIVE_INTERFACE(IArchiveOpenSetSubArchiveName, 0x50) -{ - STDMETHOD(SetSubArchiveName)(const wchar_t *name) PURE; -}; +#define Z7_IFACEM_IArchiveOpenSetSubArchiveName(x) \ + x(SetSubArchiveName(const wchar_t *name)) +Z7_IFACE_CONSTR_ARCHIVE(IArchiveOpenSetSubArchiveName, 0x50) /* @@ -256,28 +307,25 @@ IInArchive::GetArchiveProperty: Some IInArchive handlers will work incorrectly in that case. */ -#ifdef _MSC_VER - #define MY_NO_THROW_DECL_ONLY throw() +#if defined(_MSC_VER) && !defined(__clang__) + #define MY_NO_THROW_DECL_ONLY Z7_COM7F_E #else #define MY_NO_THROW_DECL_ONLY #endif -#define INTERFACE_IInArchive(x) \ - STDMETHOD(Open)(IInStream *stream, const UInt64 *maxCheckStartPosition, IArchiveOpenCallback *openCallback) MY_NO_THROW_DECL_ONLY x; \ - STDMETHOD(Close)() MY_NO_THROW_DECL_ONLY x; \ - STDMETHOD(GetNumberOfItems)(UInt32 *numItems) MY_NO_THROW_DECL_ONLY x; \ - STDMETHOD(GetProperty)(UInt32 index, PROPID propID, PROPVARIANT *value) MY_NO_THROW_DECL_ONLY x; \ - STDMETHOD(Extract)(const UInt32* indices, UInt32 numItems, Int32 testMode, IArchiveExtractCallback *extractCallback) MY_NO_THROW_DECL_ONLY x; \ - STDMETHOD(GetArchiveProperty)(PROPID propID, PROPVARIANT *value) MY_NO_THROW_DECL_ONLY x; \ - STDMETHOD(GetNumberOfProperties)(UInt32 *numProps) MY_NO_THROW_DECL_ONLY x; \ - STDMETHOD(GetPropertyInfo)(UInt32 index, BSTR *name, PROPID *propID, VARTYPE *varType) MY_NO_THROW_DECL_ONLY x; \ - STDMETHOD(GetNumberOfArchiveProperties)(UInt32 *numProps) MY_NO_THROW_DECL_ONLY x; \ - STDMETHOD(GetArchivePropertyInfo)(UInt32 index, BSTR *name, PROPID *propID, VARTYPE *varType) MY_NO_THROW_DECL_ONLY x; \ - -ARCHIVE_INTERFACE(IInArchive, 0x60) -{ - INTERFACE_IInArchive(PURE) -}; +#define Z7_IFACEM_IInArchive(x) \ + x(Open(IInStream *stream, const UInt64 *maxCheckStartPosition, IArchiveOpenCallback *openCallback)) \ + x(Close()) \ + x(GetNumberOfItems(UInt32 *numItems)) \ + x(GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) \ + x(Extract(const UInt32 *indices, UInt32 numItems, Int32 testMode, IArchiveExtractCallback *extractCallback)) \ + x(GetArchiveProperty(PROPID propID, PROPVARIANT *value)) \ + x(GetNumberOfProperties(UInt32 *numProps)) \ + x(GetPropertyInfo(UInt32 index, BSTR *name, PROPID *propID, VARTYPE *varType)) \ + x(GetNumberOfArchiveProperties(UInt32 *numProps)) \ + x(GetArchivePropertyInfo(UInt32 index, BSTR *name, PROPID *propID, VARTYPE *varType)) \ + +Z7_IFACE_CONSTR_ARCHIVE(IInArchive, 0x60) namespace NParentType { @@ -286,7 +334,7 @@ namespace NParentType kDir = 0, kAltStream }; -}; +} namespace NPropDataType { @@ -302,41 +350,36 @@ namespace NPropDataType const UInt32 kUtf8z = kMask_Utf8 | kMask_ZeroEnd; const UInt32 kUtf16z = kMask_Utf16 | kMask_ZeroEnd; -}; +} // UTF string (pointer to wchar_t) with zero end and little-endian. #define PROP_DATA_TYPE_wchar_t_PTR_Z_LE ((NPropDataType::kMask_Utf | NPropDataType::kMask_ZeroEnd) + (sizeof(wchar_t) >> 1)) + /* GetRawProp: Result: S_OK - even if property is not set */ -#define INTERFACE_IArchiveGetRawProps(x) \ - STDMETHOD(GetParent)(UInt32 index, UInt32 *parent, UInt32 *parentType) x; \ - STDMETHOD(GetRawProp)(UInt32 index, PROPID propID, const void **data, UInt32 *dataSize, UInt32 *propType) x; \ - STDMETHOD(GetNumRawProps)(UInt32 *numProps) x; \ - STDMETHOD(GetRawPropInfo)(UInt32 index, BSTR *name, PROPID *propID) x; +#define Z7_IFACEM_IArchiveGetRawProps(x) \ + x(GetParent(UInt32 index, UInt32 *parent, UInt32 *parentType)) \ + x(GetRawProp(UInt32 index, PROPID propID, const void **data, UInt32 *dataSize, UInt32 *propType)) \ + x(GetNumRawProps(UInt32 *numProps)) \ + x(GetRawPropInfo(UInt32 index, BSTR *name, PROPID *propID)) -ARCHIVE_INTERFACE(IArchiveGetRawProps, 0x70) -{ - INTERFACE_IArchiveGetRawProps(PURE) -}; +Z7_IFACE_CONSTR_ARCHIVE(IArchiveGetRawProps, 0x70) -#define INTERFACE_IArchiveGetRootProps(x) \ - STDMETHOD(GetRootProp)(PROPID propID, PROPVARIANT *value) x; \ - STDMETHOD(GetRootRawProp)(PROPID propID, const void **data, UInt32 *dataSize, UInt32 *propType) x; \ +#define Z7_IFACEM_IArchiveGetRootProps(x) \ + x(GetRootProp(PROPID propID, PROPVARIANT *value)) \ + x(GetRootRawProp(PROPID propID, const void **data, UInt32 *dataSize, UInt32 *propType)) \ -ARCHIVE_INTERFACE(IArchiveGetRootProps, 0x71) -{ - INTERFACE_IArchiveGetRootProps(PURE) -}; +Z7_IFACE_CONSTR_ARCHIVE(IArchiveGetRootProps, 0x71) -ARCHIVE_INTERFACE(IArchiveOpenSeq, 0x61) -{ - STDMETHOD(OpenSeq)(ISequentialInStream *stream) PURE; -}; +#define Z7_IFACEM_IArchiveOpenSeq(x) \ + x(OpenSeq(ISequentialInStream *stream)) \ + +Z7_IFACE_CONSTR_ARCHIVE(IArchiveOpenSeq, 0x61) /* OpenForSize @@ -362,12 +405,10 @@ const UInt32 kOpenFlags_NoSeek = 1 << 1; the handler can return S_OK, but it doesn't check even Signature. So next Extract can be called for that sequential stream. */ - /* -ARCHIVE_INTERFACE(IArchiveOpen2, 0x62) -{ - STDMETHOD(ArcOpen2)(ISequentialInStream *stream, UInt32 flags, IArchiveOpenCallback *openCallback) PURE; -}; +#define Z7_IFACEM_IArchiveOpen2(x) \ + x(ArcOpen2(ISequentialInStream *stream, UInt32 flags, IArchiveOpenCallback *openCallback)) +Z7_IFACE_CONSTR_ARCHIVE(IArchiveOpen2, 0x62) */ // ---------- UPDATE ---------- @@ -400,27 +441,21 @@ SetOperationResult() Int32 opRes (NExtract::NOperationResult::kOK) */ -#define INTERFACE_IArchiveUpdateCallback(x) \ - INTERFACE_IProgress(x); \ - STDMETHOD(GetUpdateItemInfo)(UInt32 index, Int32 *newData, Int32 *newProps, UInt32 *indexInArchive) x; \ - STDMETHOD(GetProperty)(UInt32 index, PROPID propID, PROPVARIANT *value) x; \ - STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **inStream) x; \ - STDMETHOD(SetOperationResult)(Int32 operationResult) x; \ +// INTERFACE_IProgress(x) +#define Z7_IFACEM_IArchiveUpdateCallback(x) \ + x(GetUpdateItemInfo(UInt32 index, Int32 *newData, Int32 *newProps, UInt32 *indexInArchive)) \ + x(GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) \ + x(GetStream(UInt32 index, ISequentialInStream **inStream)) \ + x(SetOperationResult(Int32 operationResult)) \ -ARCHIVE_INTERFACE_SUB(IArchiveUpdateCallback, IProgress, 0x80) -{ - INTERFACE_IArchiveUpdateCallback(PURE); -}; +Z7_IFACE_CONSTR_ARCHIVE_SUB(IArchiveUpdateCallback, IProgress, 0x80) -#define INTERFACE_IArchiveUpdateCallback2(x) \ - INTERFACE_IArchiveUpdateCallback(x) \ - STDMETHOD(GetVolumeSize)(UInt32 index, UInt64 *size) x; \ - STDMETHOD(GetVolumeStream)(UInt32 index, ISequentialOutStream **volumeStream) x; \ +// INTERFACE_IArchiveUpdateCallback(x) +#define Z7_IFACEM_IArchiveUpdateCallback2(x) \ + x(GetVolumeSize(UInt32 index, UInt64 *size)) \ + x(GetVolumeStream(UInt32 index, ISequentialOutStream **volumeStream)) \ -ARCHIVE_INTERFACE_SUB(IArchiveUpdateCallback2, IArchiveUpdateCallback, 0x82) -{ - INTERFACE_IArchiveUpdateCallback2(PURE); -}; +Z7_IFACE_CONSTR_ARCHIVE_SUB(IArchiveUpdateCallback2, IArchiveUpdateCallback, 0x82) namespace NUpdateNotifyOp { @@ -433,11 +468,13 @@ namespace NUpdateNotifyOp kRepack, kSkip, kDelete, - kHeader - - // kNumDefined + kHeader, + kHashRead, + kInFileChanged + // , kOpFinished + // , kNumDefined }; -}; +} /* IArchiveUpdateCallbackFile::ReportOperation @@ -446,15 +483,27 @@ IArchiveUpdateCallbackFile::ReportOperation UInt32 notifyOp (NUpdateNotifyOp) */ -#define INTERFACE_IArchiveUpdateCallbackFile(x) \ - STDMETHOD(GetStream2)(UInt32 index, ISequentialInStream **inStream, UInt32 notifyOp) x; \ - STDMETHOD(ReportOperation)(UInt32 indexType, UInt32 index, UInt32 notifyOp) x; \ +#define Z7_IFACEM_IArchiveUpdateCallbackFile(x) \ + x(GetStream2(UInt32 index, ISequentialInStream **inStream, UInt32 notifyOp)) \ + x(ReportOperation(UInt32 indexType, UInt32 index, UInt32 notifyOp)) \ -ARCHIVE_INTERFACE(IArchiveUpdateCallbackFile, 0x83) -{ - INTERFACE_IArchiveUpdateCallbackFile(PURE); -}; +Z7_IFACE_CONSTR_ARCHIVE(IArchiveUpdateCallbackFile, 0x83) + + +#define Z7_IFACEM_IArchiveGetDiskProperty(x) \ + x(GetDiskProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) \ + +Z7_IFACE_CONSTR_ARCHIVE(IArchiveGetDiskProperty, 0x84) +/* +#define Z7_IFACEM_IArchiveUpdateCallbackArcProp(x) \ + x(ReportProp(UInt32 indexType, UInt32 index, PROPID propID, const PROPVARIANT *value)) \ + x(ReportRawProp(UInt32 indexType, UInt32 index, PROPID propID, const void *data, UInt32 dataSize, UInt32 propType)) \ + x(ReportFinished(UInt32 indexType, UInt32 index, Int32 opRes)) \ + x(DoNeedArcProp(PROPID propID, Int32 *answer)) \ + +Z7_IFACE_CONSTR_ARCHIVE(IArchiveUpdateCallbackArcProp, 0x85) +*/ /* UpdateItems() @@ -478,41 +527,93 @@ UpdateItems() */ -#define INTERFACE_IOutArchive(x) \ - STDMETHOD(UpdateItems)(ISequentialOutStream *outStream, UInt32 numItems, IArchiveUpdateCallback *updateCallback) x; \ - STDMETHOD(GetFileTimeType)(UInt32 *type) x; +#define Z7_IFACEM_IOutArchive(x) \ + x(UpdateItems(ISequentialOutStream *outStream, UInt32 numItems, IArchiveUpdateCallback *updateCallback)) \ + x(GetFileTimeType(UInt32 *type)) -ARCHIVE_INTERFACE(IOutArchive, 0xA0) -{ - INTERFACE_IOutArchive(PURE) -}; +Z7_IFACE_CONSTR_ARCHIVE(IOutArchive, 0xA0) -ARCHIVE_INTERFACE(ISetProperties, 0x03) -{ - STDMETHOD(SetProperties)(const wchar_t * const *names, const PROPVARIANT *values, UInt32 numProps) PURE; -}; +/* +ISetProperties::SetProperties() + PROPVARIANT values[i].vt: + VT_EMPTY + VT_BOOL + VT_UI4 - if 32-bit number + VT_UI8 - if 64-bit number + VT_BSTR +*/ -ARCHIVE_INTERFACE(IArchiveKeepModeForNextOpen, 0x04) -{ - STDMETHOD(KeepModeForNextOpen)() PURE; -}; +#define Z7_IFACEM_ISetProperties(x) \ + x(SetProperties(const wchar_t * const *names, const PROPVARIANT *values, UInt32 numProps)) + +Z7_IFACE_CONSTR_ARCHIVE(ISetProperties, 0x03) + +#define Z7_IFACEM_IArchiveKeepModeForNextOpen(x) \ + x(KeepModeForNextOpen()) \ + +Z7_IFACE_CONSTR_ARCHIVE(IArchiveKeepModeForNextOpen, 0x04) /* Exe handler: the handler for executable format (PE, ELF, Mach-O). SFX archive: executable stub + some tail data. before 9.31: exe handler didn't parse SFX archives as executable format. for 9.31+: exe handler parses SFX archives as executable format, only if AllowTail(1) was called */ -ARCHIVE_INTERFACE(IArchiveAllowTail, 0x05) +#define Z7_IFACEM_IArchiveAllowTail(x) \ + x(AllowTail(Int32 allowTail)) \ + +Z7_IFACE_CONSTR_ARCHIVE(IArchiveAllowTail, 0x05) + + +namespace NRequestMemoryUseFlags { - STDMETHOD(AllowTail)(Int32 allowTail) PURE; -}; + const UInt32 k_AllowedSize_WasForced = 1 << 0; // (*allowedSize) was forced by -mmemx or -smemx + const UInt32 k_DefaultLimit_Exceeded = 1 << 1; // default limit of archive format was exceeded + const UInt32 k_MLimit_Exceeded = 1 << 2; // -mmemx value was exceeded + const UInt32 k_SLimit_Exceeded = 1 << 3; // -smemx value was exceeded + + const UInt32 k_NoErrorMessage = 1 << 10; // do not show error message, and show only request + const UInt32 k_IsReport = 1 << 11; // only report is required, without user request + + const UInt32 k_SkipArc_IsExpected = 1 << 12; // NRequestMemoryAnswerFlags::k_SkipArc flag answer is expected + const UInt32 k_Report_SkipArc = 1 << 13; // report about SkipArc operation + // const UInt32 k_SkipBigFile_IsExpected = 1 << 14; // NRequestMemoryAnswerFlags::k_SkipBigFiles flag answer is expected (unused) + // const UInt32 k_Report_SkipBigFile = 1 << 15; // report about SkipFile operation (unused) -#define IMP_IInArchive_GetProp(k) \ - (UInt32 index, BSTR *name, PROPID *propID, VARTYPE *varType) \ - { if (index >= ARRAY_SIZE(k)) return E_INVALIDARG; \ - *propID = k[index]; *varType = k7z_PROPID_To_VARTYPE[(unsigned)*propID]; *name = 0; return S_OK; } \ + // const UInt32 k_SkipBigFiles_IsExpected = 1 << 16; // NRequestMemoryAnswerFlags::k_SkipBigFiles flag answer is expected (unused) + // const UInt32 k_Report_SkipBigFiles = 1 << 17; // report that all big files will be skipped (unused) +} + +namespace NRequestMemoryAnswerFlags +{ + const UInt32 k_Allow = 1 << 0; // allow further archive extraction + const UInt32 k_Stop = 1 << 1; // for exit (and return_code == E_ABORT is used) + const UInt32 k_SkipArc = 1 << 2; // skip current archive extraction + // const UInt32 k_SkipBigFile = 1 << 4; // skip extracting of files that exceed limit (unused) + // const UInt32 k_SkipBigFiles = 1 << 5; // skip extracting of files that exceed limit (unused) + const UInt32 k_Limit_Exceeded = 1 << 10; // limit was exceeded +} + +/* + *allowedSize is in/out: + in : default allowed memory usage size or forced size, if it was changed by switch -mmemx. + out : value specified by user or unchanged value. + + *answerFlags is in/out: + *answerFlags must be set by caller before calling for default action, + + indexType : must be set with NEventIndexType::* constant + (indexType == kNoIndex), if request for whole archive. + index : must be set for some (indexType) types (if + fileIndex , if (indexType == NEventIndexType::kInArcIndex) + 0, if if (indexType == kNoIndex) + path : NULL can be used for any indexType. +*/ +#define Z7_IFACEM_IArchiveRequestMemoryUseCallback(x) \ + x(RequestMemoryUse(UInt32 flags, UInt32 indexType, UInt32 index, const wchar_t *path, \ + UInt64 requiredSize, UInt64 *allowedSize, UInt32 *answerFlags)) +Z7_IFACE_CONSTR_ARCHIVE(IArchiveRequestMemoryUseCallback, 0x09) struct CStatProp @@ -528,46 +629,69 @@ namespace NCOM { BSTR AllocBstrFromAscii(const char *s) throw(); }} -#define IMP_IInArchive_GetProp_WITH_NAME(k) \ - (UInt32 index, BSTR *name, PROPID *propID, VARTYPE *varType) \ - { if (index >= ARRAY_SIZE(k)) return E_INVALIDARG; \ + +#define IMP_IInArchive_GetProp_Base(fn, f, k) \ + Z7_COM7F_IMF(CHandler::fn(UInt32 *numProps)) \ + { *numProps = Z7_ARRAY_SIZE(k); return S_OK; } \ + Z7_COM7F_IMF(CHandler::f(UInt32 index, BSTR *name, PROPID *propID, VARTYPE *varType)) \ + { if (index >= Z7_ARRAY_SIZE(k)) return E_INVALIDARG; \ + +#define IMP_IInArchive_GetProp_NO_NAME(fn, f, k) \ + IMP_IInArchive_GetProp_Base(fn, f, k) \ + *propID = k[index]; \ + *varType = k7z_PROPID_To_VARTYPE[(unsigned)*propID]; \ + *name = NULL; return S_OK; } \ + +#define IMP_IInArchive_GetProp_WITH_NAME(fn, f, k) \ + IMP_IInArchive_GetProp_Base(fn, f, k) \ const CStatProp &prop = k[index]; \ - *propID = (PROPID)prop.PropID; *varType = prop.vt; \ + *propID = (PROPID)prop.PropID; \ + *varType = prop.vt; \ *name = NWindows::NCOM::AllocBstrFromAscii(prop.Name); return S_OK; } \ + #define IMP_IInArchive_Props \ - STDMETHODIMP CHandler::GetNumberOfProperties(UInt32 *numProps) \ - { *numProps = ARRAY_SIZE(kProps); return S_OK; } \ - STDMETHODIMP CHandler::GetPropertyInfo IMP_IInArchive_GetProp(kProps) + IMP_IInArchive_GetProp_NO_NAME(GetNumberOfProperties, GetPropertyInfo, kProps) #define IMP_IInArchive_Props_WITH_NAME \ - STDMETHODIMP CHandler::GetNumberOfProperties(UInt32 *numProps) \ - { *numProps = ARRAY_SIZE(kProps); return S_OK; } \ - STDMETHODIMP CHandler::GetPropertyInfo IMP_IInArchive_GetProp_WITH_NAME(kProps) - + IMP_IInArchive_GetProp_WITH_NAME(GetNumberOfProperties, GetPropertyInfo, kProps) #define IMP_IInArchive_ArcProps \ - STDMETHODIMP CHandler::GetNumberOfArchiveProperties(UInt32 *numProps) \ - { *numProps = ARRAY_SIZE(kArcProps); return S_OK; } \ - STDMETHODIMP CHandler::GetArchivePropertyInfo IMP_IInArchive_GetProp(kArcProps) + IMP_IInArchive_GetProp_NO_NAME(GetNumberOfArchiveProperties, GetArchivePropertyInfo, kArcProps) #define IMP_IInArchive_ArcProps_WITH_NAME \ - STDMETHODIMP CHandler::GetNumberOfArchiveProperties(UInt32 *numProps) \ - { *numProps = ARRAY_SIZE(kArcProps); return S_OK; } \ - STDMETHODIMP CHandler::GetArchivePropertyInfo IMP_IInArchive_GetProp_WITH_NAME(kArcProps) + IMP_IInArchive_GetProp_WITH_NAME(GetNumberOfArchiveProperties, GetArchivePropertyInfo, kArcProps) #define IMP_IInArchive_ArcProps_NO_Table \ - STDMETHODIMP CHandler::GetNumberOfArchiveProperties(UInt32 *numProps) \ + Z7_COM7F_IMF(CHandler::GetNumberOfArchiveProperties(UInt32 *numProps)) \ { *numProps = 0; return S_OK; } \ - STDMETHODIMP CHandler::GetArchivePropertyInfo(UInt32, BSTR *, PROPID *, VARTYPE *) \ + Z7_COM7F_IMF(CHandler::GetArchivePropertyInfo(UInt32, BSTR *, PROPID *, VARTYPE *)) \ { return E_NOTIMPL; } \ #define IMP_IInArchive_ArcProps_NO \ IMP_IInArchive_ArcProps_NO_Table \ - STDMETHODIMP CHandler::GetArchiveProperty(PROPID, PROPVARIANT *value) \ + Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID, PROPVARIANT *value)) \ { value->vt = VT_EMPTY; return S_OK; } +#define Z7_class_CHandler_final \ + Z7_class_final(CHandler) + + +#define Z7_CLASS_IMP_CHandler_IInArchive_0 \ + Z7_CLASS_IMP_COM_1(CHandler, IInArchive) +#define Z7_CLASS_IMP_CHandler_IInArchive_1(i1) \ + Z7_CLASS_IMP_COM_2(CHandler, IInArchive, i1) +#define Z7_CLASS_IMP_CHandler_IInArchive_2(i1, i2) \ + Z7_CLASS_IMP_COM_3(CHandler, IInArchive, i1, i2) +#define Z7_CLASS_IMP_CHandler_IInArchive_3(i1, i2, i3) \ + Z7_CLASS_IMP_COM_4(CHandler, IInArchive, i1, i2, i3) +#define Z7_CLASS_IMP_CHandler_IInArchive_4(i1, i2, i3, i4) \ + Z7_CLASS_IMP_COM_5(CHandler, IInArchive, i1, i2, i3, i4) +#define Z7_CLASS_IMP_CHandler_IInArchive_5(i1, i2, i3, i4, i5) \ + Z7_CLASS_IMP_COM_6(CHandler, IInArchive, i1, i2, i3, i4, i5) + + #define k_IsArc_Res_NO 0 #define k_IsArc_Res_YES 1 @@ -590,9 +714,42 @@ extern "C" typedef HRESULT (WINAPI *Func_SetCaseSensitive)(Int32 caseSensitive); typedef HRESULT (WINAPI *Func_SetLargePageMode)(); + typedef HRESULT (WINAPI *Func_SetLargePageMode2)(UInt32 flags, size_t pageSize, size_t threshold); + // typedef HRESULT (WINAPI *Func_SetClientVersion)(UInt32 version); typedef IOutArchive * (*Func_CreateOutArchive)(); typedef IInArchive * (*Func_CreateInArchive)(); } + +/* + if there is no time in archive, external MTime of archive + will be used instead of _item.Time from archive. + For 7-zip before 22.00 we need to return some supported value. + But (kpidTimeType > kDOS) is not allowed in 7-Zip before 22.00. + So we return highest precision value supported by old 7-Zip. + new 7-Zip 22.00 doesn't use that value in usual cases. +*/ + + +#define DECLARE_AND_SET_CLIENT_VERSION_VAR +#define GET_FileTimeType_NotDefined_for_GetFileTimeType \ + NFileTimeType::kWindows + +/* +extern UInt32 g_ClientVersion; + +#define GET_CLIENT_VERSION(major, minor) \ + ((UInt32)(((UInt32)(major) << 16) | (UInt32)(minor))) + +#define DECLARE_AND_SET_CLIENT_VERSION_VAR \ + UInt32 g_ClientVersion = GET_CLIENT_VERSION(MY_VER_MAJOR, MY_VER_MINOR); + +#define GET_FileTimeType_NotDefined_for_GetFileTimeType \ + ((UInt32)(g_ClientVersion >= GET_CLIENT_VERSION(22, 0) ? \ + (UInt32)(Int32)NFileTimeType::kNotDefined : \ + NFileTimeType::kWindows)) +*/ + +Z7_PURE_INTERFACES_END #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Icons/apfs.ico b/src/plugins/7zip/7za/cpp/7zip/Archive/Icons/apfs.ico new file mode 100644 index 00000000..124eb76c Binary files /dev/null and b/src/plugins/7zip/7za/cpp/7zip/Archive/Icons/apfs.ico differ diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Icons/zst.ico b/src/plugins/7zip/7za/cpp/7zip/Archive/Icons/zst.ico new file mode 100644 index 00000000..e645e020 Binary files /dev/null and b/src/plugins/7zip/7za/cpp/7zip/Archive/Icons/zst.ico differ diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/IhexHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/IhexHandler.cpp index 00ff80a7..98ca0a37 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/IhexHandler.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/IhexHandler.cpp @@ -7,14 +7,14 @@ #include "../../Common/ComTry.h" #include "../../Common/DynamicBuffer.h" #include "../../Common/IntToString.h" -#include "../../Common/MyVector.h" +#include "../../Common/StringToInt.h" #include "../../Windows/PropVariant.h" +#include "../Common/InBuffer.h" #include "../Common/ProgressUtils.h" #include "../Common/RegisterArc.h" #include "../Common/StreamUtils.h" -#include "../Common/InBuffer.h" namespace NArchive { namespace NIhex { @@ -27,10 +27,9 @@ struct CBlock UInt32 Offset; }; -class CHandler: - public IInArchive, - public CMyUnknownImp -{ + +Z7_CLASS_IMP_CHandler_IInArchive_0 + bool _isArc; bool _needMoreInput; bool _dataError; @@ -38,9 +37,6 @@ class CHandler: UInt64 _phySize; CObjectVector _blocks; -public: - MY_UNKNOWN_IMP1(IInArchive) - INTERFACE_IInArchive(;) }; static const Byte kProps[] = @@ -53,13 +49,13 @@ static const Byte kProps[] = IMP_IInArchive_Props IMP_IInArchive_ArcProps_NO_Table -STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) { *numItems = _blocks.Size(); return S_OK; } -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { NWindows::NCOM::CPropVariant prop; switch (propID) @@ -68,17 +64,18 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) case kpidErrorFlags: { UInt32 v = 0; - if (!_isArc) v |= kpv_ErrorFlags_IsNotArc;; + if (!_isArc) v |= kpv_ErrorFlags_IsNotArc; if (_needMoreInput) v |= kpv_ErrorFlags_UnexpectedEnd; if (_dataError) v |= kpv_ErrorFlags_DataError; prop = v; + break; } } prop.Detach(value); return S_OK; } -STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NWindows::NCOM::CPropVariant prop; @@ -103,31 +100,28 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val COM_TRY_END } -static inline int HexToByte(unsigned c) -{ - if (c >= '0' && c <= '9') return c - '0'; - if (c >= 'A' && c <= 'F') return c - 'A' + 10; - if (c >= 'a' && c <= 'f') return c - 'a' + 10; - return -1; -} static int Parse(const Byte *p) { - int c1 = HexToByte(p[0]); if (c1 < 0) return -1; - int c2 = HexToByte(p[1]); if (c2 < 0) return -1; - return (c1 << 4) | c2; + unsigned v0 = p[0]; Z7_PARSE_HEX_DIGIT(v0, return -1;) + unsigned v1 = p[1]; Z7_PARSE_HEX_DIGIT(v1, return -1;) + return (int)((v0 << 4) | v1); } #define kType_Data 0 #define kType_Eof 1 #define kType_Seg 2 -#define kType_CsIp 3 +// #define kType_CsIp 3 #define kType_High 4 -#define kType_Ip32 5 +// #define kType_Ip32 5 #define kType_MAX 5 -#define IS_LINE_DELIMITER(c) ((c) == 0 || (c) == 10 || (c) == 13) +// we don't want to read files with big number of spaces between records +// it's our limitation (out of specification): +static const unsigned k_NumSpaces_LIMIT = 16 + 1; + +#define IS_LINE_DELIMITER(c) (/* (c) == 0 || */ (c) == 10 || (c) == 13) API_FUNC_static_IsArc IsArc_Ihex(const Byte *p, size_t size) { @@ -145,11 +139,11 @@ API_FUNC_static_IsArc IsArc_Ihex(const Byte *p, size_t size) if (size < 4 * 2) return k_IsArc_Res_NEED_MORE; - int num = Parse(p); + const int num = Parse(p); if (num < 0) return k_IsArc_Res_NO; - int type = Parse(p + 6); + const int type = Parse(p + 6); if (type < 0 || type > kType_MAX) return k_IsArc_Res_NO; @@ -166,7 +160,7 @@ API_FUNC_static_IsArc IsArc_Ihex(const Byte *p, size_t size) sum += (unsigned)v; } - if ((sum & 0xFF) != 0) + if (sum & 0xFF) return k_IsArc_Res_NO; if (type == kType_Data) @@ -203,17 +197,17 @@ API_FUNC_static_IsArc IsArc_Ihex(const Byte *p, size_t size) p += numChars; size -= numChars; + unsigned numSpaces = k_NumSpaces_LIMIT; for (;;) { if (size == 0) return k_IsArc_Res_NEED_MORE; - char b = *p++; + const Byte b = *p++; size--; - if (IS_LINE_DELIMITER(b)) - continue; if (b == ':') break; - return k_IsArc_Res_NO; + if (--numSpaces == 0 || !IS_LINE_DELIMITER(b)) + return k_IsArc_Res_NO; } } @@ -221,7 +215,7 @@ API_FUNC_static_IsArc IsArc_Ihex(const Byte *p, size_t size) } } -STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *) +Z7_COM7F_IMF(CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *openCallback)) { COM_TRY_BEGIN { @@ -232,8 +226,8 @@ STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallb Byte temp[kStartSize]; { size_t size = kStartSize; - RINOK(ReadStream(stream, temp, &size)); - UInt32 isArcRes = IsArc_Ihex(temp, size); + RINOK(ReadStream(stream, temp, &size)) + const UInt32 isArcRes = IsArc_Ihex(temp, size); if (isArcRes == k_IsArc_Res_NO) return S_FALSE; if (isArcRes == k_IsArc_Res_NEED_MORE && size != kStartSize) @@ -241,13 +235,12 @@ STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallb } _isArc = true; - RINOK(stream->Seek(0, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekToBegin(stream)) CInBuffer s; if (!s.Create(1 << 15)) return E_OUTOFMEMORY; s.SetStream(stream); s.Init(); - { Byte b; if (!s.ReadByte(b)) @@ -263,6 +256,8 @@ STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallb } UInt32 globalOffset = 0; + const UInt32 k_progressStep = 1 << 24; + UInt64 progressNext = k_progressStep; for (;;) { @@ -271,49 +266,47 @@ STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallb _needMoreInput = true; return S_FALSE; } - int num = Parse(temp); + const int num = Parse(temp); if (num < 0) { _dataError = true; return S_FALSE; } - { - size_t numPairs = ((unsigned)num + 4); - size_t numBytes = numPairs * 2; + const size_t numPairs = (unsigned)num + 4; + const size_t numBytes = numPairs * 2; if (s.ReadBytes(temp, numBytes) != numBytes) { _needMoreInput = true; return S_FALSE; } - - unsigned sum = num; + unsigned sum = (unsigned)num; for (size_t i = 0; i < numPairs; i++) { - int a = Parse(temp + i * 2); + const int a = Parse(temp + i * 2); if (a < 0) { _dataError = true; return S_FALSE; } temp[i] = (Byte)a; - sum += a; + sum += (unsigned)a; } - if ((sum & 0xFF) != 0) + if (sum & 0xFF) { _dataError = true; return S_FALSE; } } - unsigned type = temp[2]; + const unsigned type = temp[2]; if (type > kType_MAX) { _dataError = true; return S_FALSE; } - UInt32 a = GetBe16(temp); + const UInt32 a = GetBe16(temp); if (type == kType_Data) { @@ -326,7 +319,7 @@ STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallb } // if (num != 0) { - UInt32 offs = globalOffset + a; + const UInt32 offs = globalOffset + a; CBlock *block = NULL; if (!_blocks.IsEmpty()) { @@ -342,10 +335,21 @@ STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallb block->Data.AddData(temp + 3, (unsigned)num); } } - else if (type == kType_Eof) + else { - _phySize = s.GetProcessedSize(); + if (a != 0) // from description: the address field is typically 0. { + _dataError = true; + return S_FALSE; + } + if (type == kType_Eof) + { + if (num != 0) + { + _dataError = true; + return S_FALSE; + } + _phySize = s.GetProcessedSize(); Byte b; if (s.ReadByte(b)) { @@ -361,16 +365,9 @@ STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallb } } } + return S_OK; } - return S_OK; - } - else - { - if (a != 0) - { - _dataError = true; - return S_FALSE; - } + if (type == kType_Seg || type == kType_High) { if (num != 2) @@ -378,8 +375,8 @@ STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallb _dataError = true; return S_FALSE; } - UInt32 d = GetBe16(temp + 3); - globalOffset = d << (type == kType_Seg ? 4 : 16); + // here we use optimization trick for num shift calculation: (type == kType_Seg ? 4 : 16) + globalOffset = (UInt32)GetBe16(temp + 3) << (1u << type); } else { @@ -391,6 +388,18 @@ STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallb } } + if (openCallback) + { + const UInt64 processed = s.GetProcessedSize(); + if (processed >= progressNext) + { + progressNext = processed + k_progressStep; + const UInt64 numFiles = _blocks.Size(); + RINOK(openCallback->SetCompleted(&numFiles, &processed)) + } + } + + unsigned numSpaces = k_NumSpaces_LIMIT; for (;;) { Byte b; @@ -399,12 +408,13 @@ STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallb _needMoreInput = true; return S_FALSE; } - if (IS_LINE_DELIMITER(b)) - continue; if (b == ':') break; - _dataError = true; - return S_FALSE; + if (--numSpaces == 0 || !IS_LINE_DELIMITER(b)) + { + _dataError = true; + return S_FALSE; + } } } } @@ -413,24 +423,23 @@ STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallb COM_TRY_END } -STDMETHODIMP CHandler::Close() + +Z7_COM7F_IMF(CHandler::Close()) { _phySize = 0; - _isArc = false; _needMoreInput = false; _dataError = false; - _blocks.Clear(); return S_OK; } -STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, - Int32 testMode, IArchiveExtractCallback *extractCallback) +Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testMode, IArchiveExtractCallback *extractCallback)) { COM_TRY_BEGIN - bool allFilesMode = (numItems == (UInt32)(Int32)-1); + const bool allFilesMode = (numItems == (UInt32)(Int32)-1); if (allFilesMode) numItems = _blocks.Size(); if (numItems == 0) @@ -440,56 +449,43 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, UInt32 i; for (i = 0; i < numItems; i++) totalSize += _blocks[allFilesMode ? i : indices[i]].Data.GetPos(); - extractCallback->SetTotal(totalSize); + RINOK(extractCallback->SetTotal(totalSize)) - UInt64 currentTotalSize = 0; - UInt64 currentItemSize; - - CLocalProgress *lps = new CLocalProgress; - CMyComPtr progress = lps; + CMyComPtr2_Create lps; lps->Init(extractCallback, false); - for (i = 0; i < numItems; i++, currentTotalSize += currentItemSize) + for (i = 0;; i++) { - currentItemSize = 0; - lps->InSize = lps->OutSize = currentTotalSize; - RINOK(lps->SetCur()); - - UInt32 index = allFilesMode ? i : indices[i]; + lps->InSize = lps->OutSize; + RINOK(lps->SetCur()) + if (i >= numItems) + break; + const UInt32 index = allFilesMode ? i : indices[i]; const CByteDynamicBuffer &data = _blocks[index].Data; - currentItemSize = data.GetPos(); - - CMyComPtr realOutStream; - Int32 askMode = testMode ? - NExtract::NAskMode::kTest : - NExtract::NAskMode::kExtract; - - RINOK(extractCallback->GetStream(index, &realOutStream, askMode)); - - if (!testMode && !realOutStream) - continue; - - extractCallback->PrepareOperation(askMode); - - if (realOutStream) + lps->OutSize += data.GetPos(); { - RINOK(WriteStream(realOutStream, (const Byte *)data, data.GetPos())); + CMyComPtr realOutStream; + const Int32 askMode = testMode ? + NExtract::NAskMode::kTest : + NExtract::NAskMode::kExtract; + RINOK(extractCallback->GetStream(index, &realOutStream, askMode)) + if (!testMode && !realOutStream) + continue; + RINOK(extractCallback->PrepareOperation(askMode)) + if (realOutStream) + RINOK(WriteStream(realOutStream, (const Byte *)data, data.GetPos())) } - - realOutStream.Release(); - RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)); + RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)) } - lps->InSize = lps->OutSize = currentTotalSize; - return lps->SetCur(); - + return S_OK; COM_TRY_END } -// k_Signature: { ':', '1' } +// k_Signature: { ':' } REGISTER_ARC_I_NO_SIG( - "IHex", "ihex", 0, 0xCD, + "IHex", "ihex", NULL, 0xCD, 0, NArcInfoFlags::kStartOpen, IsArc_Ihex) diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Iso/IsoHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/Iso/IsoHandler.cpp index 5a19516c..c0853865 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Iso/IsoHandler.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Iso/IsoHandler.cpp @@ -3,14 +3,12 @@ #include "StdAfx.h" #include "../../../Common/ComTry.h" -#include "../../../Common/IntToString.h" +#include "../../../Common/MyLinux.h" #include "../../../Common/StringConvert.h" -#include "../../../Windows/PropVariant.h" -#include "../../../Windows/TimeUtils.h" - #include "../../Common/LimitedStreams.h" #include "../../Common/ProgressUtils.h" +#include "../../Common/StreamUtils.h" #include "../../Compress/CopyCoder.h" @@ -34,8 +32,8 @@ static const Byte kProps[] = // kpidCTime, // kpidATime, kpidPosixAttrib, - // kpidUser, - // kpidGroup, + // kpidUserId, + // kpidGroupId, // kpidLinks, kpidSymLink }; @@ -51,28 +49,28 @@ static const Byte kArcProps[] = IMP_IInArchive_Props IMP_IInArchive_ArcProps -STDMETHODIMP CHandler::Open(IInStream *stream, +Z7_COM7F_IMF(CHandler::Open(IInStream *stream, const UInt64 * /* maxCheckStartPosition */, - IArchiveOpenCallback * /* openArchiveCallback */) + IArchiveOpenCallback * /* openArchiveCallback */)) { COM_TRY_BEGIN Close(); { - RINOK(_archive.Open(stream)); + RINOK(_archive.Open(stream)) _stream = stream; } return S_OK; COM_TRY_END } -STDMETHODIMP CHandler::Close() +Z7_COM7F_IMF(CHandler::Close()) { _archive.Clear(); _stream.Release(); return S_OK; } -STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) { *numItems = _archive.Refs.Size() + _archive.BootEntries.Size(); return S_OK; @@ -87,13 +85,21 @@ static void AddString(AString &s, const char *name, const Byte *p, unsigned size { AString d; d.SetFrom((const char *)p, i); - s += '\n'; s += name; s += ": "; s += d; + s.Add_LF(); } } +static void AddProp_Size64(AString &s, const char *name, UInt64 size) +{ + s += name; + s += ": "; + s.Add_UInt64(size); + s.Add_LF(); +} + #define ADD_STRING(n, v) AddString(s, n, vol. v, sizeof(vol. v)) static void AddErrorMessage(AString &s, const char *message) @@ -103,7 +109,7 @@ static void AddErrorMessage(AString &s, const char *message) s += message; } -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; @@ -124,11 +130,16 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) ADD_STRING("Copyright", CopyrightFileId); ADD_STRING("Abstract", AbstractFileId); ADD_STRING("Bib", BibFileId); + // ADD_STRING("EscapeSequence", EscapeSequence); + AddProp_Size64(s, "VolumeSpaceSize", vol.Get_VolumeSpaceSize_inBytes()); + AddProp_Size64(s, "VolumeSetSize", vol.VolumeSetSize); + AddProp_Size64(s, "VolumeSequenceNumber", vol.VolumeSequenceNumber); + prop = s; break; } - case kpidCTime: { FILETIME utc; if (vol.CTime.GetFileTime(utc)) prop = utc; break; } - case kpidMTime: { FILETIME utc; if (vol.MTime.GetFileTime(utc)) prop = utc; break; } + case kpidCTime: { vol.CTime.GetFileTime(prop); break; } + case kpidMTime: { vol.MTime.GetFileTime(prop); break; } } } @@ -164,7 +175,7 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) COM_TRY_END } -STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; @@ -176,13 +187,11 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val { case kpidPath: { - AString s = "[BOOT]" STRING_PATH_SEPARATOR; + AString s ("[BOOT]" STRING_PATH_SEPARATOR); if (_archive.BootEntries.Size() != 1) { - char temp[16]; - ConvertUInt32ToString(index + 1, temp); - s += temp; - s += '-'; + s.Add_UInt32(index + 1); + s.Add_Minus(); } s += be.GetName(); prop = s; @@ -216,7 +225,7 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val if (!s.IsEmpty() && s.Back() == L'.') s.DeleteBack(); - NItemName::ConvertToOSName2(s); + NItemName::ReplaceToOsSlashes_Remove_TailSlash(s); prop = s; } break; @@ -224,16 +233,15 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val case kpidSymLink: if (_archive.IsSusp) { - UString s; UInt32 mode; if (item.GetPx(_archive.SuspSkipSize, k_Px_Mode, mode)) { - if (((mode >> 12) & 0xF) == 10) + if (MY_LIN_S_ISLNK(mode)) { AString s8; if (item.GetSymLink(_archive.SuspSkipSize, s8)) { - s = MultiByteToUnicodeString(s8, CP_OEMCP); + UString s = MultiByteToUnicodeString(s8, CP_OEMCP); prop = s; } } @@ -245,8 +253,8 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val case kpidPosixAttrib: /* case kpidLinks: - case kpidUser: - case kpidGroup: + case kpidUserId: + case kpidGroupId: */ { if (_archive.IsSusp) @@ -257,8 +265,8 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val case kpidPosixAttrib: t = k_Px_Mode; break; /* case kpidLinks: t = k_Px_Links; break; - case kpidUser: t = k_Px_User; break; - case kpidGroup: t = k_Px_Group; break; + case kpidUserId: t = k_Px_User; break; + case kpidGroupId: t = k_Px_Group; break; */ } UInt32 v; @@ -279,9 +287,8 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val // case kpidCTime: // case kpidATime: { - FILETIME utc; - if (/* propID == kpidMTime && */ item.DateTime.GetFileTime(utc)) - prop = utc; + // if + item.DateTime.GetFileTime(prop); /* else { @@ -310,11 +317,11 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val COM_TRY_END } -STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, - Int32 testMode, IArchiveExtractCallback *extractCallback) +Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testMode, IArchiveExtractCallback *extractCallback)) { COM_TRY_BEGIN - bool allFilesMode = (numItems == (UInt32)(Int32)-1); + const bool allFilesMode = (numItems == (UInt32)(Int32)-1); if (allFilesMode) numItems = _archive.Refs.Size(); if (numItems == 0) @@ -334,34 +341,33 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, else totalSize += _archive.GetBootItemSize(index - _archive.Refs.Size()); } - extractCallback->SetTotal(totalSize); + RINOK(extractCallback->SetTotal(totalSize)) UInt64 currentTotalSize = 0; UInt64 currentItemSize; - NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder(); - CMyComPtr copyCoder = copyCoderSpec; - - CLocalProgress *lps = new CLocalProgress; - CMyComPtr progress = lps; + CMyComPtr2_Create lps; lps->Init(extractCallback, false); + CMyComPtr2_Create copyCoder; + CMyComPtr2_Create inStream; + inStream->SetStream(_stream); - CLimitedSequentialInStream *streamSpec = new CLimitedSequentialInStream; - CMyComPtr inStream(streamSpec); - streamSpec->SetStream(_stream); - - for (i = 0; i < numItems; i++, currentTotalSize += currentItemSize) + for (i = 0;; i++, currentTotalSize += currentItemSize) { lps->InSize = lps->OutSize = currentTotalSize; - RINOK(lps->SetCur()); + RINOK(lps->SetCur()) + if (i >= numItems) + break; currentItemSize = 0; + Int32 opRes = NExtract::NOperationResult::kOK; + { CMyComPtr realOutStream; - Int32 askMode = testMode ? + const Int32 askMode = testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; - UInt32 index = allFilesMode ? i : indices[i]; + const UInt32 index = allFilesMode ? i : indices[i]; - RINOK(extractCallback->GetStream(index, &realOutStream, askMode)); + RINOK(extractCallback->GetStream(index, &realOutStream, askMode)) UInt64 blockIndex; if (index < (UInt32)_archive.Refs.Size()) @@ -370,8 +376,8 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, const CDir &item = ref.Dir->_subItems[ref.Index]; if (item.IsDir()) { - RINOK(extractCallback->PrepareOperation(askMode)); - RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)); + RINOK(extractCallback->PrepareOperation(askMode)) + RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)) continue; } currentItemSize = ref.TotalSize; @@ -389,9 +395,8 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, if (!testMode && !realOutStream) continue; - RINOK(extractCallback->PrepareOperation(askMode)); + RINOK(extractCallback->PrepareOperation(askMode)) - bool isOK = true; if (index < (UInt32)_archive.Refs.Size()) { const CRef &ref = _archive.Refs[index]; @@ -402,12 +407,12 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, if (item2.Size == 0) continue; lps->InSize = lps->OutSize = currentTotalSize + offset; - RINOK(_stream->Seek((UInt64)item2.ExtentLocation * kBlockSize, STREAM_SEEK_SET, NULL)); - streamSpec->Init(item2.Size); - RINOK(copyCoder->Code(inStream, realOutStream, NULL, NULL, progress)); - if (copyCoderSpec->TotalSize != item2.Size) + RINOK(InStream_SeekSet(_stream, (UInt64)item2.ExtentLocation * kBlockSize)) + inStream->Init(item2.Size); + RINOK(copyCoder.Interface()->Code(inStream, realOutStream, NULL, NULL, lps)) + if (copyCoder->TotalSize != item2.Size) { - isOK = false; + opRes = NExtract::NOperationResult::kDataError; break; } offset += item2.Size; @@ -415,25 +420,24 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, } else { - RINOK(_stream->Seek((UInt64)blockIndex * kBlockSize, STREAM_SEEK_SET, NULL)); - streamSpec->Init(currentItemSize); - RINOK(copyCoder->Code(inStream, realOutStream, NULL, NULL, progress)); - if (copyCoderSpec->TotalSize != currentItemSize) - isOK = false; + RINOK(InStream_SeekSet(_stream, (UInt64)blockIndex * kBlockSize)) + inStream->Init(currentItemSize); + RINOK(copyCoder.Interface()->Code(inStream, realOutStream, NULL, NULL, lps)) + if (copyCoder->TotalSize != currentItemSize) + opRes = NExtract::NOperationResult::kDataError; } - realOutStream.Release(); - RINOK(extractCallback->SetOperationResult(isOK ? - NExtract::NOperationResult::kOK: - NExtract::NOperationResult::kDataError)); + // realOutStream.Release(); + } + RINOK(extractCallback->SetOperationResult(opRes)) } return S_OK; COM_TRY_END } -STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream) +Z7_COM7F_IMF(CHandler::GetStream(UInt32 index, ISequentialInStream **stream)) { COM_TRY_BEGIN - *stream = 0; + *stream = NULL; UInt64 blockIndex; UInt64 currentItemSize; diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Iso/IsoHandler.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Iso/IsoHandler.h index 1923784d..507814ac 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Iso/IsoHandler.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Iso/IsoHandler.h @@ -1,29 +1,22 @@ // IsoHandler.h -#ifndef __ISO_HANDLER_H -#define __ISO_HANDLER_H +#ifndef ZIP7_INC_ISO_HANDLER_H +#define ZIP7_INC_ISO_HANDLER_H #include "../../../Common/MyCom.h" #include "../IArchive.h" #include "IsoIn.h" -#include "IsoItem.h" namespace NArchive { namespace NIso { -class CHandler: - public IInArchive, - public IInArchiveGetStream, - public CMyUnknownImp -{ +Z7_CLASS_IMP_CHandler_IInArchive_1( + IInArchiveGetStream +) CMyComPtr _stream; CInArchive _archive; -public: - MY_UNKNOWN_IMP2(IInArchive, IInArchiveGetStream) - INTERFACE_IInArchive(;) - STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream); }; }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Iso/IsoHeader.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/Iso/IsoHeader.cpp index 59c283c1..3b59060a 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Iso/IsoHeader.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Iso/IsoHeader.cpp @@ -7,6 +7,6 @@ namespace NArchive { namespace NIso { -const char *kElToritoSpec = "EL TORITO SPECIFICATION\0\0\0\0\0\0\0\0\0"; +const char * const kElToritoSpec = "EL TORITO SPECIFICATION\0\0\0\0\0\0\0\0\0"; }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Iso/IsoHeader.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Iso/IsoHeader.h index db0b1df9..0b14c9fb 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Iso/IsoHeader.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Iso/IsoHeader.h @@ -1,7 +1,7 @@ // Archive/IsoHeader.h -#ifndef __ARCHIVE_ISO_HEADER_H -#define __ARCHIVE_ISO_HEADER_H +#ifndef ZIP7_INC_ARCHIVE_ISO_HEADER_H +#define ZIP7_INC_ARCHIVE_ISO_HEADER_H #include "../../../Common/MyTypes.h" @@ -25,7 +25,7 @@ namespace NFileFlags const Byte kNonFinalExtent = 1 << 7; } -extern const char *kElToritoSpec; +extern const char * const kElToritoSpec; const UInt32 kStartPos = 0x8000; diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Iso/IsoIn.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/Iso/IsoIn.cpp index 8530a25e..0054abf0 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Iso/IsoIn.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Iso/IsoIn.cpp @@ -47,17 +47,13 @@ bool CBootInitialEntry::Parse(const Byte *p) AString CBootInitialEntry::GetName() const { - AString s = (Bootable ? "Boot" : "NotBoot"); - s += '-'; + AString s (Bootable ? "Boot" : "NotBoot"); + s.Add_Minus(); - if (BootMediaType < ARRAY_SIZE(kMediaTypes)) + if (BootMediaType < Z7_ARRAY_SIZE(kMediaTypes)) s += kMediaTypes[BootMediaType]; else - { - char name[16]; - ConvertUInt32ToString(BootMediaType, name); - s += name; - } + s.Add_UInt32(BootMediaType); if (VendorSpec[0] == 1) { @@ -69,10 +65,10 @@ AString CBootInitialEntry::GetName() const break; if (i == sizeof(VendorSpec)) { - s += '-'; + s.Add_Minus(); for (i = 1; i < sizeof(VendorSpec); i++) { - char c = VendorSpec[i]; + char c = (char)VendorSpec[i]; if (c == 0) break; if (c == '\\' || c == '/') @@ -138,7 +134,7 @@ UInt16 CInArchive::ReadUInt16() { if (b[i] != b[3 - i]) IncorrectBigEndian = true; - val |= ((UInt16)(b[i]) << (8 * i)); + val |= ((UInt32)(b[i]) << (8 * i)); } return (UInt16)val; } @@ -184,7 +180,7 @@ UInt32 CInArchive::ReadDigits(int numDigits) Byte b = ReadByte(); if (b < '0' || b > '9') { - if (b == 0) // it's bug in some CD's + if (b == 0 || b == ' ') // it's bug in some CD's b = '0'; else throw CHeaderErrorException(); @@ -291,11 +287,17 @@ void CInArchive::ReadVolumeDescriptor(CVolumeDescriptor &d) ReadDateTime(d.MTime); ReadDateTime(d.ExpirationTime); ReadDateTime(d.EffectiveTime); - d.FileStructureVersion = ReadByte(); // = 1 - SkipZeros(1); + const Byte fileStructureVersion = ReadByte(); + // d.FileStructureVersion = fileStructureVersion; + if (fileStructureVersion != 1 && // ECMA-119 + fileStructureVersion != 2) // some ISO files have fileStructureVersion == 2. + { + // v24.05: we ignore that field, because we don't know what exact values are allowed there + // throw CHeaderErrorException(); + } + SkipZeros(1); // (Reserved for future standardization) ReadBytes(d.ApplicationUse, sizeof(d.ApplicationUse)); - - // Most ISO contains zeros in the following field (reserved for future standardization). + // Most ISO contain zeros in the following field (reserved for future standardization). // But some ISO programs write some data to that area. // So we disable check for zeros. Skip(653); // SkipZeros(653); @@ -303,10 +305,12 @@ void CInArchive::ReadVolumeDescriptor(CVolumeDescriptor &d) static const Byte kSig_CD001[5] = { 'C', 'D', '0', '0', '1' }; +/* static const Byte kSig_NSR02[5] = { 'N', 'S', 'R', '0', '2' }; static const Byte kSig_NSR03[5] = { 'N', 'S', 'R', '0', '3' }; static const Byte kSig_BEA01[5] = { 'B', 'E', 'A', '0', '1' }; static const Byte kSig_TEA01[5] = { 'T', 'E', 'A', '0', '1' }; +*/ static inline bool CheckSignature(const Byte *sig, const Byte *data) { @@ -318,7 +322,9 @@ static inline bool CheckSignature(const Byte *sig, const Byte *data) void CInArchive::SeekToBlock(UInt32 blockIndex) { - HRESULT res = _stream->Seek((UInt64)blockIndex * VolDescs[MainVolDescIndex].LogicalBlockSize, STREAM_SEEK_SET, &_position); + const HRESULT res = _stream->Seek( + (Int64)((UInt64)blockIndex * VolDescs[MainVolDescIndex].LogicalBlockSize), + STREAM_SEEK_SET, &_position); if (res != S_OK) throw CSystemException(res); m_BufferPos = 0; @@ -508,10 +514,10 @@ void CInArchive::ReadBootInfo() HRESULT CInArchive::Open2() { _position = 0; - RINOK(_stream->Seek(0, STREAM_SEEK_END, &_fileSize)); + RINOK(InStream_GetSize_SeekToEnd(_stream, _fileSize)) if (_fileSize < kStartPos) return S_FALSE; - RINOK(_stream->Seek(kStartPos, STREAM_SEEK_SET, &_position)); + RINOK(_stream->Seek(kStartPos, STREAM_SEEK_SET, &_position)) PhySize = _position; m_BufferPos = 0; @@ -586,14 +592,25 @@ HRESULT CInArchive::Open2() if (VolDescs.IsEmpty()) return S_FALSE; - for (MainVolDescIndex = VolDescs.Size() - 1; MainVolDescIndex > 0; MainVolDescIndex--) + for (MainVolDescIndex = (int)VolDescs.Size() - 1; MainVolDescIndex > 0; MainVolDescIndex--) if (VolDescs[MainVolDescIndex].IsJoliet()) break; + /* FIXME: some volume can contain Rock Ridge, that is better than + Joliet volume. So we need some way to detect such case */ // MainVolDescIndex = 0; // to read primary volume const CVolumeDescriptor &vd = VolDescs[MainVolDescIndex]; if (vd.LogicalBlockSize != kBlockSize) return S_FALSE; - + + { + FOR_VECTOR (i, VolDescs) + { + const CVolumeDescriptor &vd2 = VolDescs[i]; + UpdatePhySize(0, vd2.Get_VolumeSpaceSize_inBytes()); + } + } + + IsArc = true; (CDirRecord &)_rootDir = vd.RootDirRecord; @@ -613,6 +630,21 @@ HRESULT CInArchive::Open2() } } } + + { + // find boot item for expand: + // UEFI Specification : 13.3.2.1. ISO-9660 and El Torito + _expand_BootEntries_index = -1; + FOR_VECTOR (i, BootEntries) + { + const CBootInitialEntry &be = BootEntries[i]; + if (be.SectorCount <= 1 && be.BootMediaType == NBootMediaType::kNoEmulation) + if (_expand_BootEntries_index == -1 + || be.LoadRBA >= BootEntries[_expand_BootEntries_index].LoadRBA) + _expand_BootEntries_index = (int)i; + } + } + { FOR_VECTOR (i, BootEntries) { @@ -627,10 +659,10 @@ HRESULT CInArchive::Open2() const UInt64 kRemMax = 1 << 21; if (rem <= kRemMax) { - RINOK(_stream->Seek(PhySize, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekSet(_stream, PhySize)) bool areThereNonZeros = false; UInt64 numZeros = 0; - RINOK(ReadZeroTail(_stream, areThereNonZeros, numZeros, kRemMax)); + RINOK(ReadZeroTail(_stream, areThereNonZeros, numZeros, kRemMax)) if (!areThereNonZeros) PhySize += numZeros; } @@ -668,6 +700,36 @@ void CInArchive::Clear() BootEntries.Clear(); SuspSkipSize = 0; IsSusp = false; + + _expand_BootEntries_index = -1; +} + + +UInt64 CInArchive::GetBootItemSize(unsigned index) const +{ + const CBootInitialEntry &be = BootEntries[index]; + UInt64 size = be.GetSize(); + if (be.BootMediaType == NBootMediaType::k1d2Floppy) size = 1200 << 10; + else if (be.BootMediaType == NBootMediaType::k1d44Floppy) size = 1440 << 10; + else if (be.BootMediaType == NBootMediaType::k2d88Floppy) size = 2880 << 10; + const UInt64 startPos = (UInt64)be.LoadRBA * kBlockSize; + if (startPos < _fileSize) + { + const UInt64 rem = _fileSize - startPos; + /* + UEFI modification to ISO specification: + because SectorCount is 16-bit, size is limited by (32 MB). + UEFI Specification : + 13.3.2.1. ISO-9660 and El Torito + If the value of Sector Count is set to 0 or 1, + EFI will assume the system partition consumes the space + from the beginning of the "no emulation" image to the end of the CD-ROM. + */ + // + if ((int)index == _expand_BootEntries_index || rem < size) + size = rem; + } + return size; } }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Iso/IsoIn.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Iso/IsoIn.h index 4000fc92..e7d1feec 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Iso/IsoIn.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Iso/IsoIn.h @@ -1,9 +1,8 @@ // Archive/IsoIn.h -#ifndef __ARCHIVE_ISO_IN_H -#define __ARCHIVE_ISO_IN_H +#ifndef ZIP7_INC_ARCHIVE_ISO_IN_H +#define ZIP7_INC_ARCHIVE_ISO_IN_H -#include "../../../Common/IntToString.h" #include "../../../Common/MyCom.h" #include "../../IStream.h" @@ -21,7 +20,7 @@ struct CDir: public CDirRecord void Clear() { - Parent = 0; + Parent = NULL; _subItems.Clear(); } @@ -128,17 +127,18 @@ struct CDateTime bool NotSpecified() const { return Year == 0 && Month == 0 && Day == 0 && Hour == 0 && Minute == 0 && Second == 0 && GmtOffset == 0; } - bool GetFileTime(FILETIME &ft) const + bool GetFileTime(NWindows::NCOM::CPropVariant &prop) const { - UInt64 value; - bool res = NWindows::NTime::GetSecondsSince1601(Year, Month, Day, Hour, Minute, Second, value); + UInt64 v; + const bool res = NWindows::NTime::GetSecondsSince1601(Year, Month, Day, Hour, Minute, Second, v); if (res) { - value -= (Int64)((Int32)GmtOffset * 15 * 60); - value *= 10000000; + v = (UInt64)((Int64)v - (Int64)((Int32)GmtOffset * 15 * 60)); + v *= 10000000; + if (Hundredths < 100) + v += (UInt32)Hundredths * 100000; + prop.SetAsTimeFrom_Ft64_Prec(v, k_PropVar_TimePrec_Base + 2); } - ft.dwLowDateTime = (DWORD)value; - ft.dwHighDateTime = (DWORD)(value >> 32); return res; } }; @@ -214,17 +214,19 @@ struct CVolumeDescriptor CDateTime MTime; CDateTime ExpirationTime; CDateTime EffectiveTime; - Byte FileStructureVersion; // = 1; + // Byte FileStructureVersion; // = 1; Byte ApplicationUse[512]; bool IsJoliet() const { if ((VolFlags & 1) != 0) return false; - Byte b = EscapeSequence[2]; + const Byte b = EscapeSequence[2]; return (EscapeSequence[0] == 0x25 && EscapeSequence[1] == 0x2F && (b == 0x40 || b == 0x43 || b == 0x45)); } + + UInt64 Get_VolumeSpaceSize_inBytes() const { return (UInt64)VolumeSpaceSize * LogicalBlockSize; } }; struct CRef @@ -244,10 +246,6 @@ class CInArchive UInt32 m_BufferPos; - CDir _rootDir; - bool _bootIsDefined; - CBootRecordDescriptor _bootDesc; - void Skip(size_t size); void SkipZeros(size_t size); Byte ReadByte(); @@ -285,17 +283,23 @@ class CInArchive // UInt32 BlockSize; CObjectVector BootEntries; +private: + bool _bootIsDefined; +public: bool IsArc; bool UnexpectedEnd; bool HeadersError; bool IncorrectBigEndian; bool TooDeepDirs; bool SelfLinkedDirs; - CRecordVector UniqStartLocations; + bool IsSusp; + unsigned SuspSkipSize; - Byte m_Buffer[kBlockSize]; + int _expand_BootEntries_index; - void UpdatePhySize(UInt32 blockIndex, UInt64 size) + CRecordVector UniqStartLocations; + + void UpdatePhySize(const UInt32 blockIndex, const UInt64 size) { const UInt64 alignedSize = (size + kBlockSize - 1) & ~((UInt64)kBlockSize - 1); const UInt64 end = (UInt64)blockIndex * kBlockSize + alignedSize; @@ -305,27 +309,12 @@ class CInArchive bool IsJoliet() const { return VolDescs[MainVolDescIndex].IsJoliet(); } - UInt64 GetBootItemSize(int index) const - { - const CBootInitialEntry &be = BootEntries[index]; - UInt64 size = be.GetSize(); - if (be.BootMediaType == NBootMediaType::k1d2Floppy) - size = (1200 << 10); - else if (be.BootMediaType == NBootMediaType::k1d44Floppy) - size = (1440 << 10); - else if (be.BootMediaType == NBootMediaType::k2d88Floppy) - size = (2880 << 10); - UInt64 startPos = (UInt64)be.LoadRBA * kBlockSize; - if (startPos < _fileSize) - { - if (_fileSize - startPos < size) - size = _fileSize - startPos; - } - return size; - } + UInt64 GetBootItemSize(unsigned index) const; - bool IsSusp; - unsigned SuspSkipSize; +private: + CDir _rootDir; + Byte m_Buffer[kBlockSize]; + CBootRecordDescriptor _bootDesc; }; }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Iso/IsoItem.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Iso/IsoItem.h index c4950e60..65829d05 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Iso/IsoItem.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Iso/IsoItem.h @@ -1,7 +1,7 @@ // Archive/IsoItem.h -#ifndef __ARCHIVE_ISO_ITEM_H -#define __ARCHIVE_ISO_ITEM_H +#ifndef ZIP7_INC_ARCHIVE_ISO_ITEM_H +#define ZIP7_INC_ARCHIVE_ISO_ITEM_H #include "../../../../C/CpuArch.h" @@ -25,17 +25,16 @@ struct CRecordingDateTime Byte Second; signed char GmtOffset; // min intervals from -48 (West) to +52 (East) recorded. - bool GetFileTime(FILETIME &ft) const + bool GetFileTime(NWindows::NCOM::CPropVariant &prop) const { - UInt64 value; - bool res = NWindows::NTime::GetSecondsSince1601(Year + 1900, Month, Day, Hour, Minute, Second, value); + UInt64 v; + const bool res = NWindows::NTime::GetSecondsSince1601(Year + 1900, Month, Day, Hour, Minute, Second, v); if (res) { - value -= (Int64)((Int32)GmtOffset * 15 * 60); - value *= 10000000; + v = (UInt64)((Int64)v - (Int64)((Int32)GmtOffset * 15 * 60)); + v *= 10000000; + prop.SetAsTimeFrom_Ft64_Prec(v, k_PropVar_TimePrec_Base); } - ft.dwLowDateTime = (DWORD)value; - ft.dwHighDateTime = (DWORD)(value >> 32); return res; } }; @@ -102,29 +101,29 @@ struct CDirRecord { lenRes = 0; if (SystemUse.Size() < skipSize) - return 0; + return NULL; const Byte *p = (const Byte *)SystemUse + skipSize; unsigned rem = (unsigned)(SystemUse.Size() - skipSize); while (rem >= 5) { unsigned len = p[2]; if (len < 3 || len > rem) - return 0; + return NULL; if (p[0] == id0 && p[1] == id1 && p[3] == 1) { if (len < 4) - return 0; // Check it + return NULL; // Check it lenRes = len - 4; return p + 4; } p += len; rem -= len; } - return 0; + return NULL; } - const Byte* GetNameCur(bool checkSusp, int skipSize, unsigned &nameLenRes) const + const Byte* GetNameCur(bool checkSusp, unsigned skipSize, unsigned &nameLenRes) const { const Byte *res = NULL; unsigned len = 0; @@ -149,7 +148,7 @@ struct CDirRecord } - const bool GetSymLink(int skipSize, AString &link) const + bool GetSymLink(unsigned skipSize, AString &link) const { link.Empty(); const Byte *p = NULL; @@ -180,19 +179,19 @@ struct CDirRecord if (flags & (1 << 1)) link += "./"; else if (flags & (1 << 2)) link += "../"; - else if (flags & (1 << 3)) link += '/'; + else if (flags & (1 << 3)) link.Add_Slash(); else needSlash = true; for (unsigned i = 0; i < cl; i++) { - char c = p[i]; + const Byte c = p[i]; if (c == 0) { break; // return false; } - link += c; + link += (char)c; } p += cl; @@ -202,13 +201,13 @@ struct CDirRecord break; if (needSlash) - link += '/'; + link.Add_Slash(); } return true; } - static const bool GetLe32Be32(const Byte *p, UInt32 &dest) + static bool GetLe32Be32(const Byte *p, UInt32 &dest) { UInt32 v1 = GetUi32(p); UInt32 v2 = GetBe32(p + 4); @@ -221,22 +220,23 @@ struct CDirRecord } - const bool GetPx(int skipSize, unsigned pxType, UInt32 &val) const + bool GetPx(unsigned skipSize, unsigned pxType, UInt32 &val) const { + val = 0; const Byte *p = NULL; unsigned len = 0; p = FindSuspRecord(skipSize, 'P', 'X', len); if (!p) return false; // px.Clear(); - if (len < ((unsigned)pxType + 1) * 8) + if (len < (pxType + 1) * 8) return false; return GetLe32Be32(p + pxType * 8, val); } /* - const bool GetTf(int skipSize, unsigned pxType, CRecordingDateTime &t) const + bool GetTf(int skipSize, unsigned pxType, CRecordingDateTime &t) const { const Byte *p = NULL; unsigned len = 0; @@ -302,7 +302,7 @@ struct CDirRecord bool CheckSusp(unsigned &startPos) const { const Byte *p = (const Byte *)SystemUse; - unsigned len = (int)SystemUse.Size(); + const size_t len = SystemUse.Size(); const unsigned kMinLen = 7; if (len < kMinLen) return false; diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Iso/IsoRegister.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/Iso/IsoRegister.cpp index 0205238d..41b56bac 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Iso/IsoRegister.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Iso/IsoRegister.cpp @@ -12,7 +12,7 @@ namespace NIso { static const Byte k_Signature[] = { 'C', 'D', '0', '0', '1' }; REGISTER_ARC_I( - "Iso", "iso img", 0, 0xE7, + "Iso", "iso img", NULL, 0xE7, k_Signature, NArchive::NIso::kStartPos + 1, 0, diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Iso/StdAfx.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Iso/StdAfx.h index 2854ff3e..035267cc 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Iso/StdAfx.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Iso/StdAfx.h @@ -1,8 +1,11 @@ // StdAfx.h -#ifndef __STDAFX_H -#define __STDAFX_H +#ifndef ZIP7_INC_STDAFX_H +#define ZIP7_INC_STDAFX_H +#if defined(_MSC_VER) && _MSC_VER >= 1800 +#pragma warning(disable : 4464) // relative include path contains '..' +#endif #include "../../../Common/Common.h" #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/LpHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/LpHandler.cpp new file mode 100644 index 00000000..1f44c09c --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/LpHandler.cpp @@ -0,0 +1,1172 @@ +// LpHandler.cpp + +#include "StdAfx.h" + +#include "../../../C/CpuArch.h" +#include "../../../C/Sha256.h" + +#include "../../Common/ComTry.h" +#include "../../Common/IntToString.h" +#include "../../Common/MyBuffer.h" + +#include "../../Windows/PropVariantUtils.h" + +#include "../Common/LimitedStreams.h" +#include "../Common/ProgressUtils.h" +#include "../Common/RegisterArc.h" +#include "../Common/StreamUtils.h" + +#include "../Compress/CopyCoder.h" + +#define Get16(p) GetUi16(p) +#define Get32(p) GetUi32(p) +#define Get64(p) GetUi64(p) + +#define G16(_offs_, dest) dest = Get16(p + (_offs_)); +#define G32(_offs_, dest) dest = Get32(p + (_offs_)); +#define G64(_offs_, dest) dest = Get64(p + (_offs_)); + +using namespace NWindows; + +namespace NArchive { + +namespace NExt { +API_FUNC_IsArc IsArc_Ext_PhySize(const Byte *p, size_t size, UInt64 *phySize); +} + +namespace NLp { + +/* +Android 10+ use Android's Dynamic Partitions to allow the +different read-only system partitions (e.g. system, vendor, product) +to share the same pool of storage space (as LVM in Linux). +Name for partition: "super" (for GPT) or "super.img" (for file). +Dynamic Partition Tools: lpmake +All partitions that are A/B-ed should be named as follows (slots are always named a, b, etc.): +boot_a, boot_b, system_a, system_b, vendor_a, vendor_b. +*/ + +#define LP_METADATA_MAJOR_VERSION 10 +// #define LP_METADATA_MINOR_VERSION_MIN 0 +// #define LP_METADATA_MINOR_VERSION_MAX 2 + +// #define LP_SECTOR_SIZE 512 +static const unsigned kSectorSizeLog = 9; + +/* Amount of space reserved at the start of every super partition to avoid + * creating an accidental boot sector. */ +#define LP_PARTITION_RESERVED_BYTES 4096 +#define LP_METADATA_GEOMETRY_SIZE 4096 +#define LP_METADATA_HEADER_MAGIC 0x414C5030 + +static const unsigned k_SignatureSize = 8; +static const Byte k_Signature[k_SignatureSize] = + { 0x67, 0x44, 0x6c, 0x61, 0x34, 0, 0, 0 }; + +// The length (36) is the same as the maximum length of a GPT partition name. +static const unsigned kNameLen = 36; + +static void AddName36ToString(AString &s, const char *name, bool strictConvert) +{ + for (unsigned i = 0; i < kNameLen; i++) + { + char c = name[i]; + if (c == 0) + return; + if (strictConvert && c < 32) + c = '_'; + s += c; + } +} + + +static const unsigned k_Geometry_Size = 0x34; + +// LpMetadataGeometry +struct CGeometry +{ + // UInt32 magic; + // UInt32 struct_size; + // Byte checksum[32]; /* SHA256 checksum of this struct, with this field set to 0. */ + + /* Maximum amount of space a single copy of the metadata can use, + a multiple of LP_SECTOR_SIZE. */ + UInt32 metadata_max_size; + + /* Number of copies of the metadata to keep. + For Non-A/B: 1, For A/B: 2, for A/B/C: 3. + A backup copy of each slot is kept */ + UInt32 metadata_slot_count; + + /* minimal alignment for partition and extent sizes, a multiple of LP_SECTOR_SIZE. */ + UInt32 logical_block_size; + + bool Parse(const Byte *p) + { + G32 (40, metadata_max_size) + G32 (44, metadata_slot_count) + G32 (48, logical_block_size) + if (metadata_slot_count == 0 || metadata_slot_count >= ((UInt32)1 << 20)) + return false; + if (metadata_max_size == 0) + return false; + if ((metadata_max_size & (((UInt32)1 << kSectorSizeLog) - 1)) != 0) + return false; + return true; + } + + UInt64 GetTotalMetadataSize() const + { + // there are 2 copies of GEOMETRY and METADATA slots + return LP_PARTITION_RESERVED_BYTES + + LP_METADATA_GEOMETRY_SIZE * 2 + + ((UInt64)metadata_max_size * metadata_slot_count) * 2; + } +}; + + + +// LpMetadataTableDescriptor +struct CDescriptor +{ + UInt32 offset; /* Location of the table, relative to end of the metadata header. */ + UInt32 num_entries; /* Number of entries in the table. */ + UInt32 entry_size; /* Size of each entry in the table, in bytes. */ + + void Parse(const Byte *p) + { + G32 (0, offset) + G32 (4, num_entries) + G32 (8, entry_size) + } + + bool CheckLimits(UInt32 limit) const + { + if (entry_size == 0) + return false; + const UInt32 size = num_entries * entry_size; + if (size / entry_size != num_entries) + return false; + if (offset > limit || limit - offset < size) + return false; + return true; + } +}; + + +// #define LP_PARTITION_ATTR_NONE 0x0 +// #define LP_PARTITION_ATTR_READONLY (1 << 0) + +/* This flag is only intended to be used with super_empty.img and super.img on + * retrofit devices. On these devices there are A and B super partitions, and + * we don't know ahead of time which slot the image will be applied to. + * + * If set, the partition name needs a slot suffix applied. The slot suffix is + * determined by the metadata slot number (0 = _a, 1 = _b). + */ +// #define LP_PARTITION_ATTR_SLOT_SUFFIXED (1 << 1) + +/* This flag is applied automatically when using MetadataBuilder::NewForUpdate. + * It signals that the partition was created (or modified) for a snapshot-based + * update. If this flag is not present, the partition was likely flashed via + * fastboot. + */ +// #define LP_PARTITION_ATTR_UPDATED (1 << 2) + +/* This flag marks a partition as disabled. It should not be used or mapped. */ +// #define LP_PARTITION_ATTR_DISABLED (1 << 3) + +static const char * const g_PartitionAttr[] = +{ + "READONLY" + , "SLOT_SUFFIXED" + , "UPDATED" + , "DISABLED" +}; + +static unsigned const k_MetaPartition_Size = 52; + +// LpMetadataPartition +struct CPartition +{ + /* ASCII characters: alphanumeric or _. at least one ASCII character, + (name) must be unique across all partition names. */ + char name[kNameLen]; + + UInt32 attributes; /* (LP_PARTITION_ATTR_*). */ + + /* Index of the first extent owned by this partition. The extent will + * start at logical sector 0. Gaps between extents are not allowed. */ + UInt32 first_extent_index; + + /* Number of extents in the partition. Every partition must have at least one extent. */ + UInt32 num_extents; + + /* Group this partition belongs to. */ + UInt32 group_index; + + void Parse(const Byte *p) + { + memcpy(name, p, kNameLen); + G32 (36, attributes) + G32 (40, first_extent_index) + G32 (44, num_extents) + G32 (48, group_index) + } + + // calced properties: + UInt32 MethodsMask; + UInt64 NumSectors; + UInt64 NumSectors_Pack; + const char *Ext; + + UInt64 GetSize() const { return NumSectors << kSectorSizeLog; } + UInt64 GetPackSize() const { return NumSectors_Pack << kSectorSizeLog; } + + void Construct() + { + MethodsMask = 0; + NumSectors = 0; + NumSectors_Pack = 0; + Ext = NULL; + } +}; + + + + +#define LP_TARGET_TYPE_LINEAR 0 +/* This extent is a dm-zero target. The index is ignored and must be 0. */ +#define LP_TARGET_TYPE_ZERO 1 + +static const char * const g_Methods[] = +{ + "RAW" // "LINEAR" + , "ZERO" +}; + +static unsigned const k_MetaExtent_Size = 24; + +// LpMetadataExtent +struct CExtent +{ + UInt64 num_sectors; /* Length in 512-byte sectors. */ + UInt32 target_type; /* Target type for device-mapper (LP_TARGET_TYPE_*). */ + + /* for LINEAR: The sector on the physical partition that this extent maps onto. + for ZERO: must be 0. */ + UInt64 target_data; + + /* for LINEAR: index into the block devices table. + for ZERO: must be 0. */ + UInt32 target_source; + + bool IsRAW() const { return target_type == LP_TARGET_TYPE_LINEAR; } + + void Parse(const Byte *p) + { + G64 (0, num_sectors) + G32 (8, target_type) + G64 (12, target_data) + G32 (20, target_source) + } +}; + + +/* This flag is only intended to be used with super_empty.img and super.img on + * retrofit devices. If set, the group needs a slot suffix to be interpreted + * correctly. The suffix is automatically applied by ReadMetadata(). + */ +// #define LP_GROUP_SLOT_SUFFIXED (1 << 0) +static unsigned const k_Group_Size = 48; + +// LpMetadataPartitionGroup +struct CGroup +{ + char name[kNameLen]; + UInt32 flags; /* (LP_GROUP_*). */ + UInt64 maximum_size; /* Maximum size in bytes. If 0, the group has no maximum size. */ + + void Parse(const Byte *p) + { + memcpy(name, p, kNameLen); + G32 (36, flags) + G64 (40, maximum_size) + } +}; + + + + +/* This flag is only intended to be used with super_empty.img and super.img on + * retrofit devices. On these devices there are A and B super partitions, and + * we don't know ahead of time which slot the image will be applied to. + * + * If set, the block device needs a slot suffix applied before being used with + * IPartitionOpener. The slot suffix is determined by the metadata slot number + * (0 = _a, 1 = _b). + */ +// #define LP_BLOCK_DEVICE_SLOT_SUFFIXED (1 << 0) + +static unsigned const k_Device_Size = 64; + +/* This struct defines an entry in the block_devices table. There must be at + * least one device, and the first device must represent the partition holding + * the super metadata. + */ +// LpMetadataBlockDevice +struct CDevice +{ + /* 0: First usable sector for allocating logical partitions. this will be + * the first sector after the initial geometry blocks, followed by the + * space consumed by metadata_max_size*metadata_slot_count*2. + */ + UInt64 first_logical_sector; + + /* 8: Alignment for defining partitions or partition extents. For example, + * an alignment of 1MiB will require that all partitions have a size evenly + * divisible by 1MiB, and that the smallest unit the partition can grow by + * is 1MiB. + * + * Alignment is normally determined at runtime when growing or adding + * partitions. If for some reason the alignment cannot be determined, then + * this predefined alignment in the geometry is used instead. By default + * it is set to 1MiB. + */ + UInt32 alignment; + + /* 12: Alignment offset for "stacked" devices. For example, if the "super" + * partition itself is not aligned within the parent block device's + * partition table, then we adjust for this in deciding where to place + * |first_logical_sector|. + * + * Similar to |alignment|, this will be derived from the operating system. + * If it cannot be determined, it is assumed to be 0. + */ + UInt32 alignment_offset; + + /* 16: Block device size, as specified when the metadata was created. This + * can be used to verify the geometry against a target device. + */ + UInt64 size; + + /* 24: Partition name in the GPT*/ + char partition_name[kNameLen]; + + /* 60: Flags (see LP_BLOCK_DEVICE_* flags below). */ + UInt32 flags; + + void Parse(const Byte *p) + { + memcpy(partition_name, p + 24, kNameLen); + G64 (0, first_logical_sector) + G32 (8, alignment) + G32 (12, alignment_offset) + G64 (16, size) + G32 (60, flags) + } +}; + + +/* This device uses Virtual A/B. Note that on retrofit devices, the expanded + * header may not be present. + */ +// #define LP_HEADER_FLAG_VIRTUAL_AB_DEVICE 0x1 + +static const char * const g_Header_Flags[] = +{ + "VIRTUAL_AB" +}; + + +static const unsigned k_LpMetadataHeader10_size = 128; +static const unsigned k_LpMetadataHeader12_size = 256; + +struct LpMetadataHeader +{ + /* 0: Four bytes equal to LP_METADATA_HEADER_MAGIC. */ + UInt32 magic; + + /* 4: Version number required to read this metadata. If the version is not + * equal to the library version, the metadata should be considered + * incompatible. + */ + UInt16 major_version; + + /* 6: Minor version. A library supporting newer features should be able to + * read metadata with an older minor version. However, an older library + * should not support reading metadata if its minor version is higher. + */ + UInt16 minor_version; + + /* 8: The size of this header struct. */ + UInt32 header_size; + + /* 12: SHA256 checksum of the header, up to |header_size| bytes, computed as + * if this field were set to 0. + */ + // Byte header_checksum[32]; + + /* 44: The total size of all tables. This size is contiguous; tables may not + * have gaps in between, and they immediately follow the header. + */ + UInt32 tables_size; + + /* 48: SHA256 checksum of all table contents. */ + Byte tables_checksum[32]; + + /* 80: Partition table descriptor. */ + CDescriptor partitions; + /* 92: Extent table descriptor. */ + CDescriptor extents; + /* 104: Updateable group descriptor. */ + CDescriptor groups; + /* 116: Block device table. */ + CDescriptor block_devices; + + /* Everything past here is header version 1.2+, and is only included if + * needed. When liblp supporting >= 1.2 reads a < 1.2 header, it must + * zero these additional fields. + */ + + /* 128: See LP_HEADER_FLAG_ constants for possible values. Header flags are + * independent of the version number and intended to be informational only. + * New flags can be added without bumping the version. + */ + // UInt32 flags; + + /* 132: Reserved (zero), pad to 256 bytes. */ + // Byte reserved[124]; + + void Parse128(const Byte *p) + { + G32 (0, magic) + G16 (4, major_version) + G16 (6, minor_version) + G32 (8, header_size) + // Byte header_checksum[32]; + G32 (44, tables_size) + memcpy (tables_checksum, p + 48, 32); + partitions.Parse(p + 80); + extents.Parse(p + 92); + groups.Parse(p + 104); + block_devices.Parse(p + 116); + /* Everything past here is header version 1.2+, and is only included if + * needed. When liblp supporting >= 1.2 reads a < 1.2 header, it must + * zero these additional fields. + */ + } +}; + + +static bool CheckSha256(const Byte *data, size_t size, const Byte *checksum) +{ + MY_ALIGN (16) + CSha256 sha; + Sha256_Init(&sha); + Sha256_Update(&sha, data, size); + MY_ALIGN (16) + Byte calced[32]; + Sha256_Final(&sha, calced); + return memcmp(checksum, calced, 32) == 0; +} + +static bool CheckSha256_csOffset(Byte *data, size_t size, unsigned hashOffset) +{ + MY_ALIGN (4) + Byte checksum[32]; + Byte *shaData = &data[hashOffset]; + memcpy(checksum, shaData, 32); + memset(shaData, 0, 32); + return CheckSha256(data, size, checksum); +} + + + +Z7_CLASS_IMP_CHandler_IInArchive_1( + IInArchiveGetStream +) + CRecordVector _items; + CRecordVector Extents; + + CMyComPtr _stream; + UInt64 _totalSize; + // UInt64 _usedSize; + // UInt64 _headersSize; + + CGeometry geom; + UInt16 Major_version; + UInt16 Minor_version; + UInt32 Flags; + + Int32 _mainFileIndex; + UInt32 MethodsMask; + bool _headerWarning; + AString GroupsString; + AString DevicesString; + AString DeviceArcName; + + HRESULT Open2(IInStream *stream); +}; + + +static void AddComment_UInt64(AString &s, const char *name, UInt64 val) +{ + s.Add_Space(); + s += name; + s += '='; + s.Add_UInt64(val); +} + + +static bool IsBufZero(const Byte *data, size_t size) +{ + for (size_t i = 0; i < size; i += 4) + if (*(const UInt32 *)(const void *)(data + i) != 0) + return false; + return true; +} + + +HRESULT CHandler::Open2(IInStream *stream) +{ + RINOK(InStream_SeekSet(stream, LP_PARTITION_RESERVED_BYTES)) + { + MY_ALIGN (4) + Byte buf[k_Geometry_Size]; + RINOK(ReadStream_FALSE(stream, buf, k_Geometry_Size)) + if (memcmp(buf, k_Signature, k_SignatureSize) != 0) + return S_FALSE; + if (!geom.Parse(buf)) + return S_FALSE; + if (!CheckSha256_csOffset(buf, k_Geometry_Size, 8)) + return S_FALSE; + } + + CByteBuffer buffer; + RINOK(InStream_SeekToBegin(stream)) + buffer.Alloc(LP_METADATA_GEOMETRY_SIZE * 2); + { + // buffer.Size() >= LP_PARTITION_RESERVED_BYTES + RINOK(ReadStream_FALSE(stream, buffer, LP_PARTITION_RESERVED_BYTES)) + if (!IsBufZero(buffer, LP_PARTITION_RESERVED_BYTES)) + { + _headerWarning = true; + // return S_FALSE; + } + } + + RINOK(ReadStream_FALSE(stream, buffer, LP_METADATA_GEOMETRY_SIZE * 2)) + // we check that 2 copies of GEOMETRY are identical: + if (memcmp(buffer, buffer + LP_METADATA_GEOMETRY_SIZE, LP_METADATA_GEOMETRY_SIZE) != 0 + || !IsBufZero(buffer + k_Geometry_Size, LP_METADATA_GEOMETRY_SIZE - k_Geometry_Size)) + { + _headerWarning = true; + // return S_FALSE; + } + + RINOK(ReadStream_FALSE(stream, buffer, k_LpMetadataHeader10_size)) + LpMetadataHeader header; + header.Parse128(buffer); + if (header.magic != LP_METADATA_HEADER_MAGIC || + header.major_version != LP_METADATA_MAJOR_VERSION || + header.header_size < k_LpMetadataHeader10_size) + return S_FALSE; + Flags = 0; + if (header.header_size > k_LpMetadataHeader10_size) + { + if (header.header_size != k_LpMetadataHeader12_size) + return S_FALSE; + RINOK(ReadStream_FALSE(stream, buffer + k_LpMetadataHeader10_size, + header.header_size - k_LpMetadataHeader10_size)) + Flags = Get32(buffer + k_LpMetadataHeader10_size); + } + Major_version = header.major_version; + Minor_version = header.minor_version; + + if (!CheckSha256_csOffset(buffer, header.header_size, 12)) + return S_FALSE; + + if (geom.metadata_max_size < header.tables_size || + geom.metadata_max_size - header.tables_size < header.header_size) + return S_FALSE; + + buffer.AllocAtLeast(header.tables_size); + RINOK(ReadStream_FALSE(stream, buffer, header.tables_size)) + + const UInt64 totalMetaSize = geom.GetTotalMetadataSize(); + // _headersSize = _totalSize; + _totalSize = totalMetaSize; + + if (!CheckSha256(buffer, header.tables_size, header.tables_checksum)) + return S_FALSE; + + { + const CDescriptor &d = header.partitions; + if (!d.CheckLimits(header.tables_size)) + return S_FALSE; + if (d.entry_size != k_MetaPartition_Size) + return S_FALSE; + for (UInt32 i = 0; i < d.num_entries; i++) + { + CPartition part; + part.Construct(); + part.Parse(buffer + d.offset + i * d.entry_size); + const UInt32 extLimit = part.first_extent_index + part.num_extents; + if (extLimit < part.first_extent_index || + extLimit > header.extents.num_entries || + part.group_index >= header.groups.num_entries) + return S_FALSE; + _items.Add(part); + } + } + { + const CDescriptor &d = header.extents; + if (!d.CheckLimits(header.tables_size)) + return S_FALSE; + if (d.entry_size != k_MetaExtent_Size) + return S_FALSE; + for (UInt32 i = 0; i < d.num_entries; i++) + { + CExtent e; + e.Parse(buffer + d.offset + i * d.entry_size); + // if (e.target_type > LP_TARGET_TYPE_ZERO) return S_FALSE; + if (e.IsRAW()) + { + if (e.target_source >= header.block_devices.num_entries) + return S_FALSE; + const UInt64 endSector = e.target_data + e.num_sectors; + const UInt64 endOffset = endSector << kSectorSizeLog; + if (_totalSize < endOffset) + _totalSize = endOffset; + } + MethodsMask |= (UInt32)1 << e.target_type; + Extents.Add(e); + } + } + + // _usedSize = _totalSize; + { + const CDescriptor &d = header.groups; + if (!d.CheckLimits(header.tables_size)) + return S_FALSE; + if (d.entry_size != k_Group_Size) + return S_FALSE; + AString s; + for (UInt32 i = 0; i < d.num_entries; i++) + { + CGroup g; + g.Parse(buffer + d.offset + i * d.entry_size); + if (_totalSize < g.maximum_size) + _totalSize = g.maximum_size; + s += " "; + AddName36ToString(s, g.name, true); + AddComment_UInt64(s, "maximum_size", g.maximum_size); + AddComment_UInt64(s, "flags", g.flags); + s.Add_LF(); + } + GroupsString = s; + } + + { + const CDescriptor &d = header.block_devices; + if (!d.CheckLimits(header.tables_size)) + return S_FALSE; + if (d.entry_size != k_Device_Size) + return S_FALSE; + AString s; + // CRecordVector devices; + for (UInt32 i = 0; i < d.num_entries; i++) + { + CDevice v; + v.Parse(buffer + d.offset + i * d.entry_size); + // if (i == 0) + { + // it's super_device is first device; + if (totalMetaSize > (v.first_logical_sector << kSectorSizeLog)) + return S_FALSE; + } + if (_totalSize < v.size) + _totalSize = v.size; + s += " "; + if (i == 0) + AddName36ToString(DeviceArcName, v.partition_name, true); + // devices.Add(v); + AddName36ToString(s, v.partition_name, true); + AddComment_UInt64(s, "size", v.size); + AddComment_UInt64(s, "first_logical_sector", v.first_logical_sector); + AddComment_UInt64(s, "alignment", v.alignment); + AddComment_UInt64(s, "alignment_offset", v.alignment_offset); + AddComment_UInt64(s, "flags", v.flags); + s.Add_LF(); + } + DevicesString = s; + } + + { + FOR_VECTOR (i, _items) + { + CPartition &part = _items[i]; + if (part.first_extent_index > Extents.Size() || + part.num_extents > Extents.Size() - part.first_extent_index) + return S_FALSE; + + UInt64 numSectors = 0; + UInt64 numSectors_Pack = 0; + UInt32 methods = 0; + for (UInt32 k = 0; k < part.num_extents; k++) + { + const CExtent &e = Extents[part.first_extent_index + k]; + numSectors += e.num_sectors; + if (e.IsRAW()) + numSectors_Pack += e.num_sectors; + methods |= (UInt32)1 << e.target_type; + } + part.NumSectors = numSectors; + part.NumSectors_Pack = numSectors_Pack; + part.MethodsMask = methods; + } + } + + return S_OK; +} + + +Z7_COM7F_IMF(CHandler::Open(IInStream *stream, + const UInt64 * /* maxCheckStartPosition */, + IArchiveOpenCallback * /* openArchiveCallback */)) +{ + COM_TRY_BEGIN + Close(); + RINOK(Open2(stream)) + _stream = stream; + + int mainFileIndex = -1; + unsigned numNonEmptyParts = 0; + + FOR_VECTOR (fileIndex, _items) + { + CPartition &item = _items[fileIndex]; + if (item.NumSectors != 0) + { + mainFileIndex = (int)fileIndex; + numNonEmptyParts++; + CMyComPtr parseStream; + if (GetStream(fileIndex, &parseStream) == S_OK && parseStream) + { + const size_t kParseSize = 1 << 11; + Byte buf[kParseSize]; + if (ReadStream_FAIL(parseStream, buf, kParseSize) == S_OK) + { + UInt64 extSize; + if (NExt::IsArc_Ext_PhySize(buf, kParseSize, &extSize) == k_IsArc_Res_YES) + if (extSize == item.GetSize()) + item.Ext = "ext"; + } + } + } + } + if (numNonEmptyParts == 1) + _mainFileIndex = mainFileIndex; + + return S_OK; + COM_TRY_END +} + + +Z7_COM7F_IMF(CHandler::Close()) +{ + _totalSize = 0; + // _usedSize = 0; + // _headersSize = 0; + _items.Clear(); + Extents.Clear(); + _stream.Release(); + _mainFileIndex = -1; + _headerWarning = false; + MethodsMask = 0; + GroupsString.Empty(); + DevicesString.Empty(); + DeviceArcName.Empty(); + return S_OK; +} + + +static const Byte kProps[] = +{ + kpidPath, + kpidSize, + kpidPackSize, + kpidCharacts, + kpidMethod, + kpidNumBlocks, + kpidOffset +}; + +static const Byte kArcProps[] = +{ + kpidUnpackVer, + kpidMethod, + kpidClusterSize, + // kpidHeadersSize, + // kpidFreeSpace, + kpidName, + kpidComment +}; + +IMP_IInArchive_Props +IMP_IInArchive_ArcProps + +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) +{ + COM_TRY_BEGIN + NCOM::CPropVariant prop; + switch (propID) + { + case kpidMainSubfile: + { + if (_mainFileIndex >= 0) + prop = (UInt32)_mainFileIndex; + break; + } + case kpidPhySize: prop = _totalSize; break; + + // case kpidFreeSpace: if (_usedSize != 0) prop = _totalSize - _usedSize; break; + // case kpidHeadersSize: prop = _headersSize; break; + + case kpidMethod: + { + const UInt32 m = MethodsMask; + if (m != 0) + { + FLAGS_TO_PROP(g_Methods, m, prop); + } + break; + } + + case kpidUnpackVer: + { + AString s; + s.Add_UInt32(Major_version); + s.Add_Dot(); + s.Add_UInt32(Minor_version); + prop = s; + break; + } + + case kpidClusterSize: + prop = geom.logical_block_size; + break; + + case kpidComment: + { + AString s; + + s += "metadata_slot_count: "; + s.Add_UInt32(geom.metadata_slot_count); + s.Add_LF(); + + s += "metadata_max_size: "; + s.Add_UInt32(geom.metadata_max_size); + s.Add_LF(); + + if (Flags != 0) + { + s += "flags: "; + s += FlagsToString(g_Header_Flags, Z7_ARRAY_SIZE(g_Header_Flags), Flags); + s.Add_LF(); + } + + if (!GroupsString.IsEmpty()) + { + s += "Groups:"; + s.Add_LF(); + s += GroupsString; + } + + if (!DevicesString.IsEmpty()) + { + s += "BlockDevices:"; + s.Add_LF(); + s += DevicesString; + } + + if (!s.IsEmpty()) + prop = s; + break; + } + + case kpidName: + if (!DeviceArcName.IsEmpty()) + prop = DeviceArcName + ".lpimg"; + break; + + case kpidWarningFlags: + if (_headerWarning) + { + UInt32 v = kpv_ErrorFlags_HeadersError; + prop = v; + } + break; + } + prop.Detach(value); + return S_OK; + COM_TRY_END +} + + +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) +{ + *numItems = _items.Size(); + return S_OK; +} + + +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) +{ + COM_TRY_BEGIN + NCOM::CPropVariant prop; + + const CPartition &item = _items[index]; + + switch (propID) + { + case kpidPath: + { + AString s; + AddName36ToString(s, item.name, false); + if (s.IsEmpty()) + s.Add_UInt32(index); + if (item.num_extents != 0) + { + s.Add_Dot(); + s += (item.Ext ? item.Ext : "img"); + } + prop = s; + break; + } + + case kpidSize: prop = item.GetSize(); break; + case kpidPackSize: prop = item.GetPackSize(); break; + case kpidNumBlocks: prop = item.num_extents; break; + case kpidMethod: + { + const UInt32 m = item.MethodsMask; + if (m != 0) + { + FLAGS_TO_PROP(g_Methods, m, prop); + } + break; + } + case kpidOffset: + if (item.num_extents != 0) + if (item.first_extent_index < Extents.Size()) + prop = Extents[item.first_extent_index].target_data << kSectorSizeLog; + break; + + case kpidCharacts: + { + AString s; + s += "group:"; + s.Add_UInt32(item.group_index); + s.Add_Space(); + s += FlagsToString(g_PartitionAttr, Z7_ARRAY_SIZE(g_PartitionAttr), item.attributes); + prop = s; + break; + } + } + + prop.Detach(value); + return S_OK; + COM_TRY_END +} + + + +Z7_COM7F_IMF(CHandler::GetStream(UInt32 index, ISequentialInStream **stream)) +{ + COM_TRY_BEGIN + *stream = NULL; + + const CPartition &item = _items[index]; + + if (item.first_extent_index > Extents.Size() + || item.num_extents > Extents.Size() - item.first_extent_index) + return S_FALSE; + + if (item.num_extents == 0) + return CreateLimitedInStream(_stream, 0, 0, stream); + + if (item.num_extents == 1) + { + const CExtent &e = Extents[item.first_extent_index]; + if (e.IsRAW()) + { + const UInt64 pos = e.target_data << kSectorSizeLog; + if ((pos >> kSectorSizeLog) != e.target_data) + return S_FALSE; + const UInt64 size = item.GetSize(); + if (pos + size < pos) + return S_FALSE; + return CreateLimitedInStream(_stream, pos, size, stream); + } + } + + CExtentsStream *extentStreamSpec = new CExtentsStream(); + CMyComPtr extentStream = extentStreamSpec; + + // const unsigned kNumDebugExtents = 10; + extentStreamSpec->Extents.Reserve(item.num_extents + 1 + // + kNumDebugExtents + ); + + UInt64 virt = 0; + for (UInt32 k = 0; k < item.num_extents; k++) + { + const CExtent &e = Extents[item.first_extent_index + k]; + + CSeekExtent se; + { + const UInt64 numSectors = e.num_sectors; + if (numSectors == 0) + { + continue; + // return S_FALSE; + } + const UInt64 numBytes = numSectors << kSectorSizeLog; + if ((numBytes >> kSectorSizeLog) != numSectors) + return S_FALSE; + if (numBytes >= ((UInt64)1 << 63) - virt) + return S_FALSE; + + se.Virt = virt; + virt += numBytes; + } + + const UInt64 phySector = e.target_data; + if (e.target_type == LP_TARGET_TYPE_ZERO) + { + if (phySector != 0) + return S_FALSE; + se.SetAs_ZeroFill(); + } + else if (e.target_type == LP_TARGET_TYPE_LINEAR) + { + se.Phy = phySector << kSectorSizeLog; + if ((se.Phy >> kSectorSizeLog) != phySector) + return S_FALSE; + if (se.Phy >= ((UInt64)1 << 63)) + return S_FALSE; + } + else + return S_FALSE; + + extentStreamSpec->Extents.AddInReserved(se); + + /* + { + // for debug + const UInt64 kAdd = (e.num_sectors << kSectorSizeLog) / kNumDebugExtents; + for (unsigned i = 0; i < kNumDebugExtents; i++) + { + se.Phy += kAdd; + // se.Phy += (UInt64)1 << 63; // for debug + // se.Phy += 1; // for debug + se.Virt += kAdd; + extentStreamSpec->Extents.AddInReserved(se); + } + } + */ + } + + CSeekExtent se; + se.Phy = 0; + se.Virt = virt; + extentStreamSpec->Extents.Add(se); + extentStreamSpec->Stream = _stream; + extentStreamSpec->Init(); + *stream = extentStream.Detach(); + + return S_OK; + COM_TRY_END +} + + +Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testMode, IArchiveExtractCallback *extractCallback)) +{ + COM_TRY_BEGIN + const bool allFilesMode = (numItems == (UInt32)(Int32)-1); + if (allFilesMode) + numItems = _items.Size(); + if (numItems == 0) + return S_OK; + UInt64 totalSize = 0; + UInt32 i; + for (i = 0; i < numItems; i++) + { + const UInt32 index = allFilesMode ? i : indices[i]; + totalSize += _items[index].GetSize(); + } + extractCallback->SetTotal(totalSize); + + totalSize = 0; + + NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder(); + CMyComPtr copyCoder = copyCoderSpec; + + CLocalProgress *lps = new CLocalProgress; + CMyComPtr progress = lps; + lps->Init(extractCallback, false); + + for (i = 0; i < numItems; i++) + { + lps->InSize = totalSize; + lps->OutSize = totalSize; + RINOK(lps->SetCur()) + CMyComPtr outStream; + const Int32 askMode = testMode ? + NExtract::NAskMode::kTest : + NExtract::NAskMode::kExtract; + const UInt32 index = allFilesMode ? i : indices[i]; + + RINOK(extractCallback->GetStream(index, &outStream, askMode)) + + const UInt64 size = _items[index].GetSize(); + totalSize += size; + if (!testMode && !outStream) + continue; + + RINOK(extractCallback->PrepareOperation(askMode)) + + CMyComPtr inStream; + const HRESULT hres = GetStream(index, &inStream); + int opRes = NExtract::NOperationResult::kUnsupportedMethod; + if (hres != S_FALSE) + { + if (hres != S_OK) + return hres; + RINOK(copyCoder->Code(inStream, outStream, NULL, NULL, progress)) + opRes = NExtract::NOperationResult::kDataError; + if (copyCoderSpec->TotalSize == size) + opRes = NExtract::NOperationResult::kOK; + else if (copyCoderSpec->TotalSize < size) + opRes = NExtract::NOperationResult::kUnexpectedEnd; + } + outStream.Release(); + RINOK(extractCallback->SetOperationResult(opRes)) + } + + return S_OK; + COM_TRY_END +} + + +REGISTER_ARC_I( + "LP", "lpimg img", NULL, 0xc1, + k_Signature, + LP_PARTITION_RESERVED_BYTES, + 0, + NULL) + +}} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/LvmHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/LvmHandler.cpp new file mode 100644 index 00000000..9e6fed26 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/LvmHandler.cpp @@ -0,0 +1,1108 @@ +// LvmHandler.cpp + +#include "StdAfx.h" + +#include "../../../C/7zCrc.h" +#include "../../../C/CpuArch.h" + +#include "../../Common/ComTry.h" +#include "../../Common/MyBuffer.h" +#include "../../Common/StringToInt.h" + +#include "../../Windows/PropVariantUtils.h" +#include "../../Windows/TimeUtils.h" + +#include "../Common/RegisterArc.h" +#include "../Common/StreamUtils.h" + +#include "HandlerCont.h" + +#define Get32(p) GetUi32(p) +#define Get64(p) GetUi64(p) + +#define LE_32(offs, dest) dest = Get32(p + (offs)) +#define LE_64(offs, dest) dest = Get64(p + (offs)) + +using namespace NWindows; + +namespace NArchive { +namespace NLvm { + +#define SIGNATURE { 'L', 'A', 'B', 'E', 'L', 'O', 'N', 'E' } + +static const unsigned k_SignatureSize = 8; +static const Byte k_Signature[k_SignatureSize] = SIGNATURE; + +static const unsigned k_Signature2Size = 8; +static const Byte k_Signature2[k_Signature2Size] = + { 'L', 'V', 'M', '2', ' ', '0', '0', '1' }; + +static const Byte FMTT_MAGIC[16] = + { ' ', 'L', 'V', 'M', '2', ' ', 'x', '[', '5', 'A', '%', 'r', '0', 'N', '*', '>' }; + +static const UInt32 kSectorSize = 512; + + +struct CPropVal +{ + bool IsNumber; + AString String; + UInt64 Number; + + CPropVal(): IsNumber(false), Number(0) {} +}; + + +struct CConfigProp +{ + AString Name; + + bool IsVector; + CPropVal Val; + CObjectVector Vector; + + CConfigProp(): IsVector(false) {} +}; + + +class CConfigItem +{ +public: + AString Name; + CObjectVector Props; + CObjectVector Items; + + const char *ParseItem(const char *s, int numAllowedLevels); + + int FindProp(const char *name) const throw(); + bool GetPropVal_Number(const char *name, UInt64 &val) const throw(); + bool GetPropVal_String(const char *name, AString &val) const; + + int FindSubItem(const char *tag) const throw(); +}; + +struct CConfig +{ + CConfigItem Root; + + bool Parse(const char *s); +}; + + +static bool IsSpaceChar(char c) +{ + return (c == ' ' || c == '\t' || c == 0x0D || c == 0x0A); +} + +static const char *SkipSpaces(const char * s) +{ + for (;; s++) + { + const char c = *s; + if (c == 0) + return s; + if (!IsSpaceChar(c)) + { + if (c != '#') + return s; + s++; + for (;;) + { + const char c2 = *s; + if (c2 == 0) + return s; + if (c2 == '\n') + break; + s++; + } + } + } +} + +#define SKIP_SPACES(s) s = SkipSpaces(s); + +int CConfigItem::FindProp(const char *name) const throw() +{ + FOR_VECTOR (i, Props) + if (Props[i].Name == name) + return (int)i; + return -1; +} + +bool CConfigItem::GetPropVal_Number(const char *name, UInt64 &val) const throw() +{ + val = 0; + int index = FindProp(name); + if (index < 0) + return false; + const CConfigProp &prop = Props[index]; + if (prop.IsVector) + return false; + if (!prop.Val.IsNumber) + return false; + val = prop.Val.Number; + return true; +} + +bool CConfigItem::GetPropVal_String(const char *name, AString &val) const +{ + val.Empty(); + int index = FindProp(name); + if (index < 0) + return false; + const CConfigProp &prop = Props[index]; + if (prop.IsVector) + return false; + if (prop.Val.IsNumber) + return false; + val = prop.Val.String; + return true; +} + +int CConfigItem::FindSubItem(const char *tag) const throw() +{ + FOR_VECTOR (i, Items) + if (Items[i].Name == tag) + return (int)i; + return -1; +} + +static const char *FillProp(const char *s, CPropVal &val) +{ + SKIP_SPACES(s) + const char c = *s; + if (c == 0) + return NULL; + + if (c == '\"') + { + s++; + val.IsNumber = false; + val.String.Empty(); + + for (;;) + { + const char c2 = *s; + if (c2 == 0) + return NULL; + s++; + if (c2 == '\"') + break; + val.String += c2; + } + } + else + { + const char *end; + val.IsNumber = true; + val.Number = ConvertStringToUInt64(s, &end); + if (s == end) + return NULL; + s = end; + } + + SKIP_SPACES(s) + return s; +} + + +const char *CConfigItem::ParseItem(const char *s, int numAllowedLevels) +{ + if (numAllowedLevels < 0) + return NULL; + + for (;;) + { + SKIP_SPACES(s) + const char *beg = s; + + for (;; s++) + { + char c = *s; + if (c == 0 || c == '}') + { + if (s != beg) + return NULL; + return s; + } + if (IsSpaceChar(c) || c == '=' || c == '{') + break; + } + + if (s == beg) + return NULL; + + AString name; + name.SetFrom(beg, (unsigned)(s - beg)); + + SKIP_SPACES(s) + + if (*s == 0 || *s == '}') + return NULL; + + if (*s == '{') + { + s++; + CConfigItem &item = Items.AddNew(); + item.Name = name; + s = item.ParseItem(s, numAllowedLevels - 1); + if (!s) + return NULL; + if (*s != '}') + return NULL; + s++; + continue; + } + + if (*s != '=') + continue; + + s++; + SKIP_SPACES(s) + if (*s == 0) + return NULL; + CConfigProp &prop = Props.AddNew(); + + prop.Name = name; + + if (*s == '[') + { + s++; + prop.IsVector = true; + + for (;;) + { + SKIP_SPACES(s) + char c = *s; + if (c == 0) + return NULL; + if (c == ']') + { + s++; + break; + } + + CPropVal val; + + s = FillProp(s, val); + if (!s) + return NULL; + prop.Vector.Add(val); + SKIP_SPACES(s) + + if (*s == ',') + { + s++; + continue; + } + if (*s != ']') + return NULL; + s++; + break; + } + } + else + { + prop.IsVector = false; + s = FillProp(s, prop.Val); + if (!s) + return NULL; + } + } +} + + +bool CConfig::Parse(const char *s) +{ + s = Root.ParseItem(s, 10); + if (!s) + return false; + SKIP_SPACES(s) + return *s == 0; +} + + +/* +static const CUInt32PCharPair g_PartitionFlags[] = +{ + { 0, "Sys" }, + { 1, "Ignore" }, + { 2, "Legacy" }, + { 60, "Win-Read-only" }, + { 62, "Win-Hidden" }, + { 63, "Win-Not-Automount" } +}; +*/ + +/* +static inline char GetHex(unsigned t) { return (char)(((t < 10) ? ('0' + t) : ('A' + (t - 10)))); } + +static void PrintHex(unsigned v, char *s) +{ + s[0] = GetHex((v >> 4) & 0xF); + s[1] = GetHex(v & 0xF); +} + +static void ConvertUInt16ToHex4Digits(UInt32 val, char *s) throw() +{ + PrintHex(val >> 8, s); + PrintHex(val & 0xFF, s + 2); +} + +static void GuidToString(const Byte *g, char *s) +{ + ConvertUInt32ToHex8Digits(Get32(g ), s); s += 8; *s++ = '-'; + ConvertUInt16ToHex4Digits(Get16(g + 4), s); s += 4; *s++ = '-'; + ConvertUInt16ToHex4Digits(Get16(g + 6), s); s += 4; *s++ = '-'; + for (unsigned i = 0; i < 8; i++) + { + if (i == 2) + *s++ = '-'; + PrintHex(g[8 + i], s); + s += 2; + } + *s = 0; +} + +*/ + +struct CPhyVol +{ + AString Name; + + // AString id; + // AString device; // "/dev/sda2" + // AString status; // ["ALLOCATABLE"] + // AString flags; // [] + // UInt64 dev_size; // in sectors + UInt64 pe_start; // in sectors + UInt64 pe_count; // in extents + + bool Parse(const CConfigItem &ci) + { + Name = ci.Name; + // ci.GetPropVal_String("id", id); + // ci.GetPropVal_String("device", device); + bool res = true; + // if (!ci.GetPropVal_Number("dev_size", dev_size)) res = false; + if (!ci.GetPropVal_Number("pe_start", pe_start)) res = false; + if (!ci.GetPropVal_Number("pe_count", pe_count)) res = false; + return res; + } +}; + +struct CStripe +{ + AString Name; // "pv0"; + UInt64 ExtentOffset; // ???? +}; + +struct CSegment +{ + UInt64 start_extent; + UInt64 extent_count; + AString type; + CObjectVector stripes; + + bool IsPosSizeOk() const + { + return + (start_extent < ((UInt64)1 << 63)) && + (extent_count < ((UInt64)1 << 63)); + } + + UInt64 GetEndExtent() const { return start_extent + extent_count; } + + bool Parse(const CConfigItem &si) + { + UInt64 stripe_count; + + if (!si.GetPropVal_Number("start_extent", start_extent)) return false; + if (!si.GetPropVal_Number("extent_count", extent_count)) return false; + if (!si.GetPropVal_Number("stripe_count", stripe_count)) return false; + if (!si.GetPropVal_String("type", type)) return false; + + //if (stripe_count != 1) return false; + + const int spi = si.FindProp("stripes"); + if (spi < 0) + return false; + + const CConfigProp &prop = si.Props[spi]; + if (!prop.IsVector) + return false; + + if (stripe_count > (1 << 20)) + return false; + + const unsigned numStripes = (unsigned)stripe_count; + if (prop.Vector.Size() != numStripes * 2) + return false; + + for (unsigned i = 0; i < numStripes; i++) + { + const CPropVal &v0 = prop.Vector[i * 2]; + const CPropVal &v1 = prop.Vector[i * 2 + 1]; + if (v0.IsNumber || !v1.IsNumber) + return false; + CStripe stripe; + stripe.Name = v0.String; + stripe.ExtentOffset = v1.Number; + stripes.Add(stripe); + } + + return true; + } +}; + + +struct CLogVol +{ + bool IsSupported; + + AString Name; + + AString id; + AString status; // ["READ", "WRITE", "VISIBLE"] + AString flags; // [] + + // UInt64 Pos; + // UInt64 Size; + + // UInt64 GetSize() const { return Size; } + // UInt64 GetPos() const { return Pos; } + + CObjectVector Segments; + + CLogVol(): /* Pos(0), Size(0), */ IsSupported(false) {} + + bool Parse(const CConfigItem &ci) + { + Name = ci.Name; + + UInt64 segment_count; + if (!ci.GetPropVal_Number("segment_count", segment_count)) + return false; + + if (ci.Items.Size() != segment_count) + return false; + + FOR_VECTOR (segIndex, ci.Items) + { + const CConfigItem &si = ci.Items[segIndex]; + { + AString t ("segment"); + t.Add_UInt32(segIndex + 1); + if (si.Name != t) + return false; + } + + CSegment segment; + + if (!segment.Parse(si)) + return false; + + // item.Size += (segment.extent_count * _extentSize) << 9; + + Segments.Add(segment); + } + + IsSupported = true; + return true; + } + + bool GetNumExtents(UInt64 &numExtents) const + { + numExtents = 0; + if (Segments.IsEmpty()) + return true; + unsigned i; + for (i = 1; i < Segments.Size(); i++) + if (!Segments[i].IsPosSizeOk()) + return false; + for (i = 1; i < Segments.Size(); i++) + if (Segments[i - 1].GetEndExtent() != Segments[i].start_extent) + return false; + numExtents = Segments.Back().GetEndExtent(); + return true; + } +}; + + +struct CItem +{ + int LogVol; + int PhyVol; + UInt64 Pos; + UInt64 Size; + AString Name; + bool IsSupported; + + CItem(): LogVol(-1), PhyVol(-1), Pos(0), Size(0), IsSupported(false) {} +}; + + +struct CVolGroup +{ + CObjectVector _logVols; + CObjectVector _phyVols; + AString _id; + int _extentSizeBits; + + /* + UInt64 secno; // 3 + AString status; // ["RESIZEABLE", "READ", "WRITE"] + AString flags; // [] + UInt64 max_lv; // 0 + UInt64 max_pv; // 0 + UInt64 metadata_copies; // 0 + */ + + void Clear() + { + _logVols.Clear(); + _phyVols.Clear(); + _id.Empty(); + _extentSizeBits = -1; + } +}; + + +Z7_class_CHandler_final: public CHandlerCont, public CVolGroup +{ + Z7_IFACE_COM7_IMP(IInArchive_Cont) + + CObjectVector _items; + + UInt64 _cTime; + + bool _isArc; + + UInt64 _phySize; + CByteBuffer _buffer; + + UInt64 _cfgPos; + UInt64 _cfgSize; + + HRESULT Open2(IInStream *stream); + + virtual int GetItem_ExtractInfo(UInt32 index, UInt64 &pos, UInt64 &size) const Z7_override + { + if (index >= _items.Size()) + { + pos = _cfgPos; + size = _cfgSize; + return NExtract::NOperationResult::kOK; + } + const CItem &item = _items[index]; + if (!item.IsSupported) + return NExtract::NOperationResult::kUnsupportedMethod; + pos = item.Pos; + size = item.Size; + return NExtract::NOperationResult::kOK; + } +}; + +static const UInt32 LVM_CRC_INIT_VAL = 0xf597a6cf; + +static UInt32 Z7_FASTCALL LvmCrcCalc(const void *data, size_t size) +{ + return CrcUpdate(LVM_CRC_INIT_VAL, data, size); +} + +struct CRawLocn +{ + UInt64 Offset; /* Offset in bytes to start sector */ + UInt64 Size; /* Bytes */ + UInt32 Checksum; + UInt32 Flags; + + bool IsEmpty() const { return Offset == 0 && Size == 0; } + + void Parse(const Byte *p) + { + LE_64(0x00, Offset); + LE_64(0x08, Size); + LE_32(0x10, Checksum); + LE_32(0x14, Flags); + } +}; + +// #define MDA_HEADER_SIZE 512 + +struct mda_header +{ + UInt64 Start; /* Absolute start byte of mda_header */ + UInt64 Size; /* Size of metadata area */ + + CRecordVector raw_locns; + + bool Parse(const Byte *p, size_t size) + { + if (memcmp(p + 4, FMTT_MAGIC, 16) != 0) + return false; + UInt32 version; + LE_32(0x14, version); + if (version != 1) + return false; + LE_64(0x18, Start); + LE_64(0x20, Size); + + unsigned pos = 0x28; + + for (;;) + { + if (pos + 0x18 > size) + return false; + CRawLocn locn; + locn.Parse(p + pos); + if (locn.IsEmpty()) + break; + pos += 0x18; + raw_locns.Add(locn); + } + + return true; + } +}; + + +static int inline GetLog(UInt64 num) +{ + for (unsigned i = 0; i < 64; i++) + if (((UInt64)1 << i) == num) + return (int)i; + return -1; +} + +#define ID_LEN 32 + +HRESULT CHandler::Open2(IInStream *stream) +{ + _buffer.Alloc(kSectorSize * 2); + RINOK(ReadStream_FALSE(stream, _buffer, kSectorSize * 2)) + + const Byte *buf = _buffer; + + buf += kSectorSize; + + // label_header + + if (memcmp(buf, k_Signature, k_SignatureSize) != 0) + return S_FALSE; + const UInt64 sectorNumber = Get64(buf + 8); + if (sectorNumber != 1) + return S_FALSE; + if (Get32(buf + 16) != LvmCrcCalc(buf + 20, kSectorSize - 20)) + return S_FALSE; + + const UInt32 offsetToCont = Get32(buf + 20); + if (memcmp(buf + 24, k_Signature2, k_Signature2Size) != 0) + return S_FALSE; + + if (offsetToCont != 32) + return S_FALSE; + + // pv_header + + size_t pos = offsetToCont; + const Byte *p = buf; + + /* + { + Byte id[ID_LEN]; + memcpy(id, p + pos, ID_LEN); + } + */ + + pos += ID_LEN; + const UInt64 device_size_xl = Get64(p + pos); + pos += 8; + + _phySize = device_size_xl; + _isArc = true; + + for (;;) + { + if (pos > kSectorSize - 16) + return S_FALSE; + // disk_locn (data areas) + UInt64 offset = Get64(p + pos); + UInt64 size = Get64(p + pos + 8); + pos += 16; + if (offset == 0 && size == 0) + break; + } + + CConfig cfg; + // bool isFinded = false; + + // for (;;) + { + if (pos > kSectorSize - 16) + return S_FALSE; + // disk_locn (metadata area headers) + UInt64 offset = Get64(p + pos); + UInt64 size = Get64(p + pos + 8); + pos += 16; + if (offset == 0 && size == 0) + { + // break; + return S_FALSE; + } + + CByteBuffer meta; + const size_t sizeT = (size_t)size; + if (sizeT != size) + return S_FALSE; + if (sizeT < kSectorSize) + return S_FALSE; + meta.Alloc(sizeT); + RINOK(InStream_SeekSet(stream, offset)) + RINOK(ReadStream_FALSE(stream, meta, sizeT)) + if (Get32(meta) != LvmCrcCalc(meta + 4, kSectorSize - 4)) + return S_FALSE; + mda_header mh; + if (!mh.Parse(meta, kSectorSize)) + return S_FALSE; + + if (mh.raw_locns.Size() != 1) + return S_FALSE; + unsigned g = 0; + // for (unsigned g = 0; g < mh.raw_locns.Size(); g++) + { + const CRawLocn &locn = mh.raw_locns[g]; + + CByteBuffer vgBuf; + if (locn.Size > ((UInt32)1 << 24)) + return S_FALSE; + + const size_t vgSize = (size_t)locn.Size; + if (vgSize == 0) + return S_FALSE; + + vgBuf.Alloc(vgSize); + + _cfgPos = offset + locn.Offset; + _cfgSize = vgSize; + RINOK(InStream_SeekSet(stream, _cfgPos)) + RINOK(ReadStream_FALSE(stream, vgBuf, vgSize)) + if (locn.Checksum != LvmCrcCalc(vgBuf, vgSize)) + return S_FALSE; + + { + AString s; + s.SetFrom_CalcLen((const char *)(const Byte *)vgBuf, (unsigned)vgSize); + _cfgSize = s.Len(); + if (!cfg.Parse(s)) + return S_FALSE; + // isFinded = true; + // break; + } + } + + // if (isFinded) break; + } + + // if (!isFinded) return S_FALSE; + + if (cfg.Root.Items.Size() != 1) + return S_FALSE; + const CConfigItem &volGroup = cfg.Root.Items[0]; + if (volGroup.Name != "VolGroup00") + return S_FALSE; + + volGroup.GetPropVal_String("id", _id); + + if (!cfg.Root.GetPropVal_Number("creation_time", _cTime)) + _cTime = 0; + + UInt64 extentSize; + if (!volGroup.GetPropVal_Number("extent_size", extentSize)) + return S_FALSE; + + _extentSizeBits = GetLog(extentSize); + if (_extentSizeBits < 0 || _extentSizeBits > (62 - 9)) + return S_FALSE; + + + { + int pvsIndex = volGroup.FindSubItem("physical_volumes"); + if (pvsIndex < 0) + return S_FALSE; + + const CConfigItem &phyVols = volGroup.Items[pvsIndex]; + + FOR_VECTOR (i, phyVols.Items) + { + const CConfigItem &ci = phyVols.Items[i]; + CPhyVol pv; + if (!pv.Parse(ci)) + return S_FALSE; + _phyVols.Add(pv); + } + } + + { + int lvIndex = volGroup.FindSubItem("logical_volumes"); + if (lvIndex < 0) + return S_FALSE; + + const CConfigItem &logVolumes = volGroup.Items[lvIndex]; + + FOR_VECTOR (i, logVolumes.Items) + { + const CConfigItem &ci = logVolumes.Items[i]; + CLogVol &lv = _logVols.AddNew(); + lv.Parse(ci) ; // check error + } + } + + { + FOR_VECTOR (i, _logVols) + { + CLogVol &lv = _logVols[i]; + + CItem item; + + item.LogVol = (int)i; + item.Pos = 0; + item.Size = 0; + item.Name = lv.Name; + + if (lv.IsSupported) + { + UInt64 numExtents; + lv.IsSupported = lv.GetNumExtents(numExtents); + + if (lv.IsSupported) + { + lv.IsSupported = false; + item.Size = numExtents << (_extentSizeBits + 9); + + if (lv.Segments.Size() == 1) + { + const CSegment &segment = lv.Segments[0]; + if (segment.stripes.Size() == 1) + { + const CStripe &stripe = segment.stripes[0]; + FOR_VECTOR (pvIndex, _phyVols) + { + const CPhyVol &pv = _phyVols[pvIndex]; + if (pv.Name == stripe.Name) + { + item.Pos = (pv.pe_start + (stripe.ExtentOffset << _extentSizeBits)) << 9; + lv.IsSupported = true; + item.IsSupported = true; + break; + } + } + } + } + } + } + + _items.Add(item); + } + } + + { + FOR_VECTOR (i, _phyVols) + { + const CPhyVol &pv = _phyVols[i]; + + if (pv.pe_start > (UInt64)1 << (62 - 9)) + return S_FALSE; + if (pv.pe_count > (UInt64)1 << (62 - 9 - _extentSizeBits)) + return S_FALSE; + + CItem item; + + item.PhyVol = (int)i; + item.Pos = pv.pe_start << 9; + item.Size = pv.pe_count << (_extentSizeBits + 9); + item.Name = pv.Name; + item.IsSupported = true; + + _items.Add(item); + } + } + + return S_OK; +} + + +Z7_COM7F_IMF(CHandler::Open(IInStream *stream, + const UInt64 * /* maxCheckStartPosition */, + IArchiveOpenCallback * /* openArchiveCallback */)) +{ + COM_TRY_BEGIN + Close(); + RINOK(Open2(stream)) + _stream = stream; + return S_OK; + COM_TRY_END +} + + +Z7_COM7F_IMF(CHandler::Close()) +{ + CVolGroup::Clear(); + + _cfgPos = 0; + _cfgSize = 0; + _cTime = 0; + _phySize = 0; + _isArc = false; + _items.Clear(); + + _stream.Release(); + return S_OK; +} + + +static const Byte kProps[] = +{ + kpidPath, + kpidSize, + kpidFileSystem, + kpidCharacts, + kpidOffset, + kpidId +}; + +static const Byte kArcProps[] = +{ + kpidId, + kpidCTime, + kpidClusterSize +}; + +IMP_IInArchive_Props +IMP_IInArchive_ArcProps + +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) +{ + COM_TRY_BEGIN + NCOM::CPropVariant prop; + + switch (propID) + { + case kpidMainSubfile: + { + /* + if (_items.Size() == 1) + prop = (UInt32)0; + */ + break; + } + case kpidPhySize: if (_phySize != 0) prop = _phySize; break; + case kpidId: + { + prop = _id; + break; + } + case kpidClusterSize: + { + if (_extentSizeBits >= 0) + prop = ((UInt64)1 << (_extentSizeBits + 9)); + break; + } + case kpidCTime: + { + if (_cTime != 0) + { + FILETIME ft; + NTime::UnixTime64_To_FileTime((Int64)_cTime, ft); + prop = ft; + } + break; + } + case kpidErrorFlags: + { + UInt32 v = 0; + if (!_isArc) v |= kpv_ErrorFlags_IsNotArc; + // if (_unsupported) v |= kpv_ErrorFlags_UnsupportedMethod; + // if (_headerError) v |= kpv_ErrorFlags_HeadersError; + if (v == 0 && !_stream) + v |= kpv_ErrorFlags_UnsupportedMethod; + if (v != 0) + prop = v; + break; + } + } + + prop.Detach(value); + return S_OK; + COM_TRY_END +} + + +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) +{ + *numItems = _items.Size() + (_cfgSize == 0 ? 0 : 1); + return S_OK; +} + + +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) +{ + COM_TRY_BEGIN + NCOM::CPropVariant prop; + + // const CLogVol &item = _items[index]; + + if (index >= _items.Size()) + { + switch (propID) + { + case kpidPath: + { + prop = "meta.txt"; + break; + } + + case kpidSize: + case kpidPackSize: prop = _cfgSize; break; + } + } + else + { + const CItem &item = _items[index]; + switch (propID) + { + case kpidPath: + { + AString s = item.Name; + s += ".img"; + prop = s; + break; + } + + case kpidSize: + case kpidPackSize: prop = item.Size; break; + case kpidOffset: prop = item.Pos; break; + + case kpidId: + { + // prop = item.id; + break; + } + + // case kpidCharacts: FLAGS64_TO_PROP(g_PartitionFlags, item.Flags, prop); break; + } + } + + prop.Detach(value); + return S_OK; + COM_TRY_END +} + +REGISTER_ARC_I( + "LVM", "lvm", NULL, 0xBF, + k_Signature, + kSectorSize, + 0, + NULL) + +}} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/LzhHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/LzhHandler.cpp index c8d3ff6d..89593004 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/LzhHandler.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/LzhHandler.cpp @@ -4,11 +4,13 @@ #include "../../../C/CpuArch.h" +#include "../../Common/AutoPtr.h" #include "../../Common/ComTry.h" #include "../../Common/MyBuffer.h" #include "../../Common/StringConvert.h" #include "../../Windows/PropVariant.h" +#include "../../Windows/PropVariantUtils.h" #include "../../Windows/TimeUtils.h" #include "../ICoder.h" @@ -31,10 +33,45 @@ using namespace NTime; #define Get16(p) GetUi16(p) #define Get32(p) GetUi32(p) + +// CRC-16 (-IBM, -ANSI). The poly is 0x8005 (x^16 + x^15 + x^2 + 1) + +static const UInt16 kCrc16Poly = 0xA001; + +MY_ALIGN(64) +static UInt16 g_LzhCrc16Table[256]; + +#define CRC16_UPDATE_BYTE(crc, b) (g_LzhCrc16Table[((crc) ^ (b)) & 0xFF] ^ ((crc) >> 8)) + +UInt32 LzhCrc16Update(UInt32 crc, const void *data, size_t size); +UInt32 LzhCrc16Update(UInt32 crc, const void *data, size_t size) +{ + const Byte *p = (const Byte *)data; + const Byte *pEnd = p + size; + for (; p != pEnd; p++) + crc = CRC16_UPDATE_BYTE(crc, *p); + return crc; +} + +static struct CLzhCrc16TableInit +{ + CLzhCrc16TableInit() + { + for (UInt32 i = 0; i < 256; i++) + { + UInt32 r = i; + for (unsigned j = 0; j < 8; j++) + r = (r >> 1) ^ (kCrc16Poly & ((UInt32)0 - (r & 1))); + g_LzhCrc16Table[i] = (UInt16)r; + } + } +} g_LzhCrc16TableInit; + + namespace NArchive { namespace NLzh{ -const int kMethodIdSize = 5; +const unsigned kMethodIdSize = 5; const Byte kExtIdFileName = 0x01; const Byte kExtIdDirName = 0x02; @@ -48,13 +85,7 @@ struct CExtension AString GetString() const { AString s; - for (size_t i = 0; i < Data.Size(); i++) - { - char c = (char)Data[i]; - if (c == 0) - break; - s += c; - } + s.SetFrom_CalcLen((const char *)(const Byte *)Data, (unsigned)Data.Size()); return s; } }; @@ -125,7 +156,7 @@ struct CItem return false; } - int GetNumDictBits() const + unsigned GetNumDictBits() const { if (!IsLhMethod()) return 0; @@ -146,14 +177,16 @@ struct CItem { FOR_VECTOR (i, Extensions) if (Extensions[i].Type == type) - return i; + return (int)i; return -1; } + bool GetUnixTime(UInt32 &value) const { value = 0; int index = FindExt(kExtIdUnixTime); - if (index < 0) + if (index < 0 + || Extensions[index].Data.Size() < 4) { if (Level == 2) { @@ -185,13 +218,14 @@ struct CItem AString GetName() const { - AString dirName = GetDirName(); + AString s (GetDirName()); const char kDirSeparator = '\\'; // check kDirSeparator in Linux - dirName.Replace((char)(unsigned char)0xFF, kDirSeparator); - if (!dirName.IsEmpty() && dirName.Back() != kDirSeparator) - dirName += kDirSeparator; - return dirName + GetFileName(); + s.Replace((char)(Byte)0xFF, kDirSeparator); + if (!s.IsEmpty() && s.Back() != kDirSeparator) + s += kDirSeparator; + s += GetFileName(); + return s; } }; @@ -206,10 +240,10 @@ static const Byte *ReadString(const Byte *p, size_t size, AString &s) s.Empty(); for (size_t i = 0; i < size; i++) { - char c = p[i]; + const Byte c = p[i]; if (c == 0) break; - s += c; + s += (char)c; } return p + size; } @@ -238,7 +272,7 @@ static HRESULT GetNextItem(ISequentialInStream *stream, bool &filled, CItem &ite Byte header[256]; processedSize = kBasicPartSize; - RINOK(ReadStream(stream, header, &processedSize)); + RINOK(ReadStream(stream, header, &processedSize)) if (processedSize != kBasicPartSize) return (startHeader[0] == 0) ? S_OK: S_FALSE; @@ -261,11 +295,11 @@ static HRESULT GetNextItem(ISequentialInStream *stream, bool &filled, CItem &ite headerSize = startHeader[0]; if (headerSize < kBasicPartSize) return S_FALSE; - RINOK(ReadStream_FALSE(stream, header + kBasicPartSize, headerSize - kBasicPartSize)); + RINOK(ReadStream_FALSE(stream, header + kBasicPartSize, headerSize - kBasicPartSize)) if (startHeader[1] != CalcSum(header, headerSize)) return S_FALSE; - size_t nameLength = *p++; - if ((p - header) + nameLength + 2 > headerSize) + const size_t nameLength = *p++; + if ((size_t)(p - header) + nameLength + 2 > headerSize) return S_FALSE; p = ReadString(p, nameLength, item.Name); } @@ -276,7 +310,7 @@ static HRESULT GetNextItem(ISequentialInStream *stream, bool &filled, CItem &ite { if (item.Level == 2) { - RINOK(ReadStream_FALSE(stream, header + kBasicPartSize, 2)); + RINOK(ReadStream_FALSE(stream, header + kBasicPartSize, 2)) } if ((size_t)(p - header) + 3 > headerSize) return S_FALSE; @@ -297,12 +331,12 @@ static HRESULT GetNextItem(ISequentialInStream *stream, bool &filled, CItem &ite return S_FALSE; CExtension ext; RINOK(ReadStream_FALSE(stream, &ext.Type, 1)) - nextSize -= 3; + nextSize = (UInt16)(nextSize - 3); ext.Data.Alloc(nextSize); RINOK(ReadStream_FALSE(stream, (Byte *)ext.Data, nextSize)) item.Extensions.Add(ext); Byte hdr2[2]; - RINOK(ReadStream_FALSE(stream, hdr2, 2)); + RINOK(ReadStream_FALSE(stream, hdr2, 2)) ReadUInt16(hdr2, nextSize); } } @@ -310,13 +344,8 @@ static HRESULT GetNextItem(ISequentialInStream *stream, bool &filled, CItem &ite return S_OK; } -struct COsPair -{ - Byte Id; - const char *Name; -}; -static const COsPair g_OsPairs[] = +static const CUInt32PCharPair g_OsPairs[] = { { 0, "MS-DOS" }, { 'M', "MS-DOS" }, @@ -337,15 +366,6 @@ static const COsPair g_OsPairs[] = { 'J', "Java VM" } }; -static const char *kUnknownOS = "Unknown"; - -static const char *GetOS(Byte osId) -{ - for (unsigned i = 0; i < ARRAY_SIZE(g_OsPairs); i++) - if (g_OsPairs[i].Id == osId) - return g_OsPairs[i].Name; - return kUnknownOS; -} static const Byte kProps[] = { @@ -360,109 +380,49 @@ static const Byte kProps[] = kpidHostOS }; -class CCRC -{ - UInt16 _value; -public: - static UInt16 Table[256]; - static void InitTable(); - - CCRC(): _value(0) {} - void Init() { _value = 0; } - void Update(const void *data, size_t size); - UInt16 GetDigest() const { return _value; } -}; - -static const UInt16 kCRCPoly = 0xA001; - -UInt16 CCRC::Table[256]; - -void CCRC::InitTable() -{ - for (UInt32 i = 0; i < 256; i++) - { - UInt32 r = i; - for (int j = 0; j < 8; j++) - if (r & 1) - r = (r >> 1) ^ kCRCPoly; - else - r >>= 1; - CCRC::Table[i] = (UInt16)r; - } -} - -class CCRCTableInit -{ -public: - CCRCTableInit() { CCRC::InitTable(); } -} g_CRCTableInit; - -void CCRC::Update(const void *data, size_t size) -{ - UInt16 v = _value; - const Byte *p = (const Byte *)data; - for (; size > 0; size--, p++) - v = (UInt16)(Table[((Byte)(v)) ^ *p] ^ (v >> 8)); - _value = v; -} - - -class COutStreamWithCRC: - public ISequentialOutStream, - public CMyUnknownImp -{ -public: - MY_UNKNOWN_IMP - STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize); -private: - CCRC _crc; +Z7_CLASS_IMP_NOQIB_1( + COutStreamWithCRC + , ISequentialOutStream +) + UInt32 _crc; CMyComPtr _stream; public: void Init(ISequentialOutStream *stream) { _stream = stream; - _crc.Init(); + _crc = 0; } void ReleaseStream() { _stream.Release(); } - UInt32 GetCRC() const { return _crc.GetDigest(); } - void InitCRC() { _crc.Init(); } + UInt32 GetCRC() const { return _crc; } }; -STDMETHODIMP COutStreamWithCRC::Write(const void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(COutStreamWithCRC::Write(const void *data, UInt32 size, UInt32 *processedSize)) { - UInt32 realProcessedSize; - HRESULT result; - if (!_stream) - { - realProcessedSize = size; - result = S_OK; - } - else - result = _stream->Write(data, size, &realProcessedSize); - _crc.Update(data, realProcessedSize); - if (processedSize != NULL) - *processedSize = realProcessedSize; - return result; + HRESULT res = S_OK; + if (_stream) + res = _stream->Write(data, size, &size); + _crc = LzhCrc16Update(_crc, data, size); + if (processedSize) + *processedSize = size; + return res; } + struct CItemEx: public CItem { UInt64 DataPosition; }; -class CHandler: - public IInArchive, - public CMyUnknownImp -{ + +Z7_CLASS_IMP_CHandler_IInArchive_0 + CObjectVector _items; CMyComPtr _stream; UInt64 _phySize; UInt32 _errorFlags; bool _isArc; public: - MY_UNKNOWN_IMP1(IInArchive) - INTERFACE_IInArchive(;) CHandler(); }; @@ -471,13 +431,13 @@ IMP_IInArchive_ArcProps_NO_Table CHandler::CHandler() {} -STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) { *numItems = _items.Size(); return S_OK; } -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { NCOM::CPropVariant prop; switch (propID) @@ -494,7 +454,7 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) return S_OK; } -STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; @@ -503,7 +463,7 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val { case kpidPath: { - UString s = NItemName::WinNameToOSName(MultiByteToUnicodeString(item.GetName(), CP_OEMCP)); + UString s = NItemName::WinPathToOsPath(MultiByteToUnicodeString(item.GetName(), CP_OEMCP)); if (!s.IsEmpty()) { if (s.Back() == WCHAR_PATH_SEPARATOR) @@ -513,28 +473,17 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val break; } case kpidIsDir: prop = item.IsDir(); break; - case kpidSize: prop = item.Size; break; - case kpidPackSize: prop = item.PackSize; break; + case kpidSize: prop = (UInt64)item.Size; break; + case kpidPackSize: prop = (UInt64)item.PackSize; break; case kpidCRC: prop = (UInt32)item.CRC; break; - case kpidHostOS: prop = GetOS(item.OsId); break; + case kpidHostOS: PAIR_TO_PROP(g_OsPairs, item.OsId, prop); break; case kpidMTime: { - FILETIME utc; UInt32 unixTime; if (item.GetUnixTime(unixTime)) - NTime::UnixTimeToFileTime(unixTime, utc); + PropVariant_SetFrom_UnixTime(prop, unixTime); else - { - FILETIME localFileTime; - if (DosTimeToFileTime(item.ModifiedTime, localFileTime)) - { - if (!LocalFileTimeToFileTime(&localFileTime, &utc)) - utc.dwHighDateTime = utc.dwLowDateTime = 0; - } - else - utc.dwHighDateTime = utc.dwLowDateTime = 0; - } - prop = utc; + PropVariant_SetFrom_DosTime(prop, item.ModifiedTime); break; } // case kpidAttrib: prop = (UInt32)item.Attributes; break; @@ -552,8 +501,8 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val COM_TRY_END } -STDMETHODIMP CHandler::Open(IInStream *stream, - const UInt64 * /* maxCheckStartPosition */, IArchiveOpenCallback *callback) +Z7_COM7F_IMF(CHandler::Open(IInStream *stream, + const UInt64 * /* maxCheckStartPosition */, IArchiveOpenCallback *callback)) { COM_TRY_BEGIN Close(); @@ -561,25 +510,24 @@ STDMETHODIMP CHandler::Open(IInStream *stream, { _items.Clear(); - UInt64 endPos = 0; + UInt64 endPos; bool needSetTotal = true; - RINOK(stream->Seek(0, STREAM_SEEK_END, &endPos)); - RINOK(stream->Seek(0, STREAM_SEEK_SET, NULL)); + RINOK(InStream_AtBegin_GetSize(stream, endPos)) for (;;) { CItemEx item; bool filled; - HRESULT result = GetNextItem(stream, filled, item); - RINOK(stream->Seek(0, STREAM_SEEK_CUR, &item.DataPosition)); - if (result == S_FALSE) + const HRESULT res = GetNextItem(stream, filled, item); + RINOK(InStream_GetPos(stream, item.DataPosition)) + if (res == S_FALSE) { _errorFlags = kpv_ErrorFlags_HeadersError; break; } - if (result != S_OK) + if (res != S_OK) return S_FALSE; _phySize = item.DataPosition; if (!filled) @@ -589,7 +537,7 @@ STDMETHODIMP CHandler::Open(IInStream *stream, _isArc = true; UInt64 newPostion; - RINOK(stream->Seek(item.PackSize, STREAM_SEEK_CUR, &newPostion)); + RINOK(stream->Seek(item.PackSize, STREAM_SEEK_CUR, &newPostion)) if (newPostion > endPos) { _phySize = endPos; @@ -601,14 +549,14 @@ STDMETHODIMP CHandler::Open(IInStream *stream, { if (needSetTotal) { - RINOK(callback->SetTotal(NULL, &endPos)); + RINOK(callback->SetTotal(NULL, &endPos)) needSetTotal = false; } if (_items.Size() % 100 == 0) { - UInt64 numFiles = _items.Size(); - UInt64 numBytes = item.DataPosition; - RINOK(callback->SetCompleted(&numFiles, &numBytes)); + const UInt64 numFiles = _items.Size(); + const UInt64 numBytes = item.DataPosition; + RINOK(callback->SetCompleted(&numFiles, &numBytes)) } } } @@ -625,7 +573,7 @@ STDMETHODIMP CHandler::Open(IInStream *stream, return S_OK; } -STDMETHODIMP CHandler::Close() +Z7_COM7F_IMF(CHandler::Close()) { _isArc = false; _phySize = 0; @@ -635,112 +583,96 @@ STDMETHODIMP CHandler::Close() return S_OK; } -STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, - Int32 testModeSpec, IArchiveExtractCallback *extractCallback) +Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testMode, IArchiveExtractCallback *extractCallback)) { COM_TRY_BEGIN - bool testMode = (testModeSpec != 0); - UInt64 totalUnPacked = 0, totalPacked = 0; - bool allFilesMode = (numItems == (UInt32)(Int32)-1); + const bool allFilesMode = (numItems == (UInt32)(Int32)-1); if (allFilesMode) numItems = _items.Size(); if (numItems == 0) return S_OK; + UInt64 totalUnPacked = 0 /* , totalPacked = 0 */; UInt32 i; for (i = 0; i < numItems; i++) { const CItemEx &item = _items[allFilesMode ? i : indices[i]]; totalUnPacked += item.Size; - totalPacked += item.PackSize; + // totalPacked += item.PackSize; } - RINOK(extractCallback->SetTotal(totalUnPacked)); - - UInt64 currentTotalUnPacked = 0, currentTotalPacked = 0; - UInt64 currentItemUnPacked, currentItemPacked; - - NCompress::NLzh::NDecoder::CCoder *lzhDecoderSpec = 0; - CMyComPtr lzhDecoder; - // CMyComPtr lzh1Decoder; - // CMyComPtr arj2Decoder; + RINOK(extractCallback->SetTotal(totalUnPacked)) - NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder(); - CMyComPtr copyCoder = copyCoderSpec; + UInt32 cur_Unpacked, cur_Packed; - CLocalProgress *lps = new CLocalProgress; - CMyComPtr progress = lps; + CMyComPtr2_Create lps; lps->Init(extractCallback, false); - - CLimitedSequentialInStream *streamSpec = new CLimitedSequentialInStream; - CMyComPtr inStream(streamSpec); - streamSpec->SetStream(_stream); - - for (i = 0; i < numItems; i++, currentTotalUnPacked += currentItemUnPacked, - currentTotalPacked += currentItemPacked) + CMyUniquePtr lzhDecoder; + CMyComPtr2_Create copyCoder; + CMyComPtr2_Create inStream; + inStream->SetStream(_stream); + + for (i = 0;; i++, + lps->OutSize += cur_Unpacked, + lps->InSize += cur_Packed) { - currentItemUnPacked = 0; - currentItemPacked = 0; - - lps->InSize = currentTotalPacked; - lps->OutSize = currentTotalUnPacked; - RINOK(lps->SetCur()); - - CMyComPtr realOutStream; - Int32 askMode; - askMode = testMode ? NExtract::NAskMode::kTest : - NExtract::NAskMode::kExtract; - Int32 index = allFilesMode ? i : indices[i]; - const CItemEx &item = _items[index]; - RINOK(extractCallback->GetStream(index, &realOutStream, askMode)); - - if (item.IsDir()) + cur_Unpacked = 0; + cur_Packed = 0; + RINOK(lps->SetCur()) + if (i >= numItems) + break; + + Int32 opRes; { - // if (!testMode) + CMyComPtr realOutStream; + const Int32 askMode = testMode ? + NExtract::NAskMode::kTest : + NExtract::NAskMode::kExtract; + const UInt32 index = allFilesMode ? i : indices[i]; + const CItemEx &item = _items[index]; + RINOK(extractCallback->GetStream(index, &realOutStream, askMode)) + + if (item.IsDir()) { - RINOK(extractCallback->PrepareOperation(askMode)); - RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)); + // if (!testMode) + { + RINOK(extractCallback->PrepareOperation(askMode)) + RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)) + } + continue; } - continue; - } - - if (!testMode && !realOutStream) - continue; - - RINOK(extractCallback->PrepareOperation(askMode)); - currentItemUnPacked = item.Size; - currentItemPacked = item.PackSize; + + if (!testMode && !realOutStream) + continue; + + RINOK(extractCallback->PrepareOperation(askMode)) + cur_Unpacked = item.Size; + cur_Packed = item.PackSize; - { - COutStreamWithCRC *outStreamSpec = new COutStreamWithCRC; - CMyComPtr outStream(outStreamSpec); - outStreamSpec->Init(realOutStream); + CMyComPtr2_Create outStream; + outStream->Init(realOutStream); realOutStream.Release(); - UInt64 pos; - _stream->Seek(item.DataPosition, STREAM_SEEK_SET, &pos); + RINOK(InStream_SeekSet(_stream, item.DataPosition)) - streamSpec->Init(item.PackSize); + inStream->Init(item.PackSize); - HRESULT result = S_OK; - Int32 opRes = NExtract::NOperationResult::kOK; + HRESULT res = S_OK; + opRes = NExtract::NOperationResult::kOK; if (item.IsCopyMethod()) { - result = copyCoder->Code(inStream, outStream, NULL, NULL, progress); - if (result == S_OK && copyCoderSpec->TotalSize != item.PackSize) - result = S_FALSE; + res = copyCoder.Interface()->Code(inStream, outStream, NULL, NULL, lps); + if (res == S_OK && copyCoder->TotalSize != item.PackSize) + res = S_FALSE; } else if (item.IsLh4GroupMethod()) { - if (!lzhDecoder) - { - lzhDecoderSpec = new NCompress::NLzh::NDecoder::CCoder; - lzhDecoder = lzhDecoderSpec; - } - lzhDecoderSpec->FinishMode = true; - lzhDecoderSpec->SetDictSize(1 << item.GetNumDictBits()); - result = lzhDecoder->Code(inStream, outStream, NULL, ¤tItemUnPacked, progress); - if (result == S_OK && lzhDecoderSpec->GetInputProcessedSize() != item.PackSize) - result = S_FALSE; + lzhDecoder.Create_if_Empty(); + // lzhDecoder->FinishMode = true; + lzhDecoder->SetDictSize((UInt32)1 << item.GetNumDictBits()); + res = lzhDecoder->Code(inStream, outStream, cur_Unpacked, lps); + if (res == S_OK && lzhDecoder->GetInputProcessedSize() != item.PackSize) + res = S_FALSE; } /* else if (item.IsLh1GroupMethod()) @@ -751,7 +683,7 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, lzh1Decoder = lzh1DecoderSpec; } lzh1DecoderSpec->SetDictionary(item.GetNumDictBits()); - result = lzh1Decoder->Code(inStream, outStream, NULL, ¤tItemUnPacked, progress); + res = lzh1Decoder->Code(inStream, outStream, NULL, &cur_Unpacked, progress); } */ else @@ -759,18 +691,17 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, if (opRes == NExtract::NOperationResult::kOK) { - if (result == S_FALSE) + if (res == S_FALSE) opRes = NExtract::NOperationResult::kDataError; else { - RINOK(result); - if (outStreamSpec->GetCRC() != item.CRC) + RINOK(res) + if (outStream->GetCRC() != item.CRC) opRes = NExtract::NOperationResult::kCRCError; } } - outStream.Release(); - RINOK(extractCallback->SetOperationResult(opRes)); } + RINOK(extractCallback->SetOperationResult(opRes)) } return S_OK; COM_TRY_END @@ -779,7 +710,7 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, static const Byte k_Signature[] = { '-', 'l', 'h' }; REGISTER_ARC_I( - "Lzh", "lzh lha", 0, 6, + "Lzh", "lzh lha", NULL, 6, k_Signature, 2, 0, diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/LzmaHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/LzmaHandler.cpp index 121cd67c..7cbabf49 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/LzmaHandler.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/LzmaHandler.cpp @@ -44,7 +44,8 @@ static const Byte kProps[] = static const Byte kArcProps[] = { - kpidNumStreams + kpidNumStreams, + kpidMethod }; struct CHeader @@ -53,6 +54,7 @@ struct CHeader Byte FilterID; Byte LzmaProps[5]; + Byte GetProp() const { return LzmaProps[0]; } UInt32 GetDicSize() const { return GetUi32(LzmaProps + 1); } bool HasSize() const { return (Size != (UInt64)(Int64)-1); } bool Parse(const Byte *buf, bool isThereFilter); @@ -74,35 +76,30 @@ bool CHeader::Parse(const Byte *buf, bool isThereFilter) && CheckDicSize(LzmaProps + 1); } -class CDecoder +class CDecoder Z7_final { CMyComPtr _bcjStream; CFilterCoder *_filterCoder; - CMyComPtr _lzmaDecoder; public: - NCompress::NLzma::CDecoder *_lzmaDecoderSpec; + CMyComPtr2 _lzmaDecoder; ~CDecoder(); HRESULT Create(bool filtered, ISequentialInStream *inStream); HRESULT Code(const CHeader &header, ISequentialOutStream *outStream, ICompressProgressInfo *progress); - UInt64 GetInputProcessedSize() const { return _lzmaDecoderSpec->GetInputProcessedSize(); } + UInt64 GetInputProcessedSize() const { return _lzmaDecoder->GetInputProcessedSize(); } - void ReleaseInStream() { if (_lzmaDecoder) _lzmaDecoderSpec->ReleaseInStream(); } + void ReleaseInStream() { if (_lzmaDecoder) _lzmaDecoder->ReleaseInStream(); } HRESULT ReadInput(Byte *data, UInt32 size, UInt32 *processedSize) - { return _lzmaDecoderSpec->ReadFromInputStream(data, size, processedSize); } + { return _lzmaDecoder->ReadFromInputStream(data, size, processedSize); } }; HRESULT CDecoder::Create(bool filteredMode, ISequentialInStream *inStream) { - if (!_lzmaDecoder) - { - _lzmaDecoderSpec = new NCompress::NLzma::CDecoder; - _lzmaDecoderSpec->FinishStream = true; - _lzmaDecoder = _lzmaDecoderSpec; - } + _lzmaDecoder.Create_if_Empty(); + _lzmaDecoder->FinishStream = true; if (filteredMode) { @@ -110,12 +107,12 @@ HRESULT CDecoder::Create(bool filteredMode, ISequentialInStream *inStream) { _filterCoder = new CFilterCoder(false); CMyComPtr coder = _filterCoder; - _filterCoder->Filter = new NCompress::NBcj::CCoder(false); + _filterCoder->Filter = new NCompress::NBcj::CCoder2(z7_BranchConvSt_X86_Dec); _bcjStream = _filterCoder; } } - return _lzmaDecoderSpec->SetInStream(inStream); + return _lzmaDecoder->SetInStream(inStream); } CDecoder::~CDecoder() @@ -129,25 +126,19 @@ HRESULT CDecoder::Code(const CHeader &header, ISequentialOutStream *outStream, if (header.FilterID > 1) return E_NOTIMPL; - { - CMyComPtr setDecoderProperties; - _lzmaDecoder.QueryInterface(IID_ICompressSetDecoderProperties2, &setDecoderProperties); - if (!setDecoderProperties) - return E_NOTIMPL; - RINOK(setDecoderProperties->SetDecoderProperties2(header.LzmaProps, 5)); - } + RINOK(_lzmaDecoder->SetDecoderProperties2(header.LzmaProps, 5)) bool filteredMode = (header.FilterID == 1); if (filteredMode) { - RINOK(_filterCoder->SetOutStream(outStream)); + RINOK(_filterCoder->SetOutStream(outStream)) outStream = _bcjStream; - RINOK(_filterCoder->SetOutStreamSize(NULL)); + RINOK(_filterCoder->SetOutStreamSize(NULL)) } const UInt64 *Size = header.HasSize() ? &header.Size : NULL; - HRESULT res = _lzmaDecoderSpec->CodeResume(outStream, Size, progress); + HRESULT res = _lzmaDecoder->CodeResume(outStream, Size, progress); if (filteredMode) { @@ -161,58 +152,50 @@ HRESULT CDecoder::Code(const CHeader &header, ISequentialOutStream *outStream, res = res2; } - RINOK(res); + RINOK(res) if (header.HasSize()) - if (_lzmaDecoderSpec->GetOutputProcessedSize() != header.Size) + if (_lzmaDecoder->GetOutputProcessedSize() != header.Size) return S_FALSE; return S_OK; } -class CHandler: - public IInArchive, - public IArchiveOpenSeq, - public CMyUnknownImp -{ - CHeader _header; +Z7_CLASS_IMP_CHandler_IInArchive_1( + IArchiveOpenSeq +) bool _lzma86; - CMyComPtr _stream; - CMyComPtr _seqStream; - bool _isArc; bool _needSeekToStart; bool _dataAfterEnd; bool _needMoreInput; + bool _unsupported; + bool _dataError; bool _packSize_Defined; bool _unpackSize_Defined; bool _numStreams_Defined; - bool _unsupported; - bool _dataError; - + CHeader _header; + CMyComPtr _stream; + CMyComPtr _seqStream; + UInt64 _packSize; UInt64 _unpackSize; UInt64 _numStreams; -public: - MY_UNKNOWN_IMP2(IInArchive, IArchiveOpenSeq) - - INTERFACE_IInArchive(;) - STDMETHOD(OpenSeq)(ISequentialInStream *stream); - - CHandler(bool lzma86) { _lzma86 = lzma86; } + void GetMethod(NCOM::CPropVariant &prop); unsigned GetHeaderSize() const { return 5 + 8 + (_lzma86 ? 1 : 0); } - +public: + CHandler(bool lzma86) { _lzma86 = lzma86; } }; IMP_IInArchive_Props IMP_IInArchive_ArcProps -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { NCOM::CPropVariant prop; switch (propID) @@ -220,63 +203,88 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) case kpidPhySize: if (_packSize_Defined) prop = _packSize; break; case kpidNumStreams: if (_numStreams_Defined) prop = _numStreams; break; case kpidUnpackSize: if (_unpackSize_Defined) prop = _unpackSize; break; + case kpidMethod: GetMethod(prop); break; case kpidErrorFlags: { UInt32 v = 0; - if (!_isArc) v |= kpv_ErrorFlags_IsNotArc;; + if (!_isArc) v |= kpv_ErrorFlags_IsNotArc; if (_needMoreInput) v |= kpv_ErrorFlags_UnexpectedEnd; if (_dataAfterEnd) v |= kpv_ErrorFlags_DataAfterEnd; if (_unsupported) v |= kpv_ErrorFlags_UnsupportedMethod; if (_dataError) v |= kpv_ErrorFlags_DataError; prop = v; + break; } + default: break; } prop.Detach(value); return S_OK; } -STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) { *numItems = 1; return S_OK; } -static void DictSizeToString(UInt32 value, char *s) + +static char * DictSizeToString(UInt32 val, char *s) { - for (int i = 0; i <= 31; i++) - if (((UInt32)1 << i) == value) - { - ::ConvertUInt32ToString(i, s); - return; - } + for (unsigned i = 0; i < 32; i++) + if (((UInt32)1 << i) == val) + return ::ConvertUInt32ToString(i, s); char c = 'b'; - if ((value & ((1 << 20) - 1)) == 0) { value >>= 20; c = 'm'; } - else if ((value & ((1 << 10) - 1)) == 0) { value >>= 10; c = 'k'; } - ::ConvertUInt32ToString(value, s); - s += MyStringLen(s); + if ((val & ((1 << 20) - 1)) == 0) { val >>= 20; c = 'm'; } + else if ((val & ((1 << 10) - 1)) == 0) { val >>= 10; c = 'k'; } + s = ::ConvertUInt32ToString(val, s); *s++ = c; *s = 0; + return s; +} + +static char *AddProp32(char *s, const char *name, UInt32 v) +{ + *s++ = ':'; + s = MyStpCpy(s, name); + return ::ConvertUInt32ToString(v, s); +} + +void CHandler::GetMethod(NCOM::CPropVariant &prop) +{ + if (!_stream) + return; + + char sz[64]; + char *s = sz; + if (_header.FilterID != 0) + s = MyStpCpy(s, "BCJ "); + s = MyStpCpy(s, "LZMA:"); + s = DictSizeToString(_header.GetDicSize(), s); + + UInt32 d = _header.GetProp(); + // if (d != 0x5D) + { + UInt32 lc = d % 9; + d /= 9; + UInt32 pb = d / 5; + UInt32 lp = d % 5; + if (lc != 3) s = AddProp32(s, "lc", lc); + if (lp != 0) s = AddProp32(s, "lp", lp); + if (pb != 2) s = AddProp32(s, "pb", pb); + } + prop = sz; } -STDMETHODIMP CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIANT *value) + +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIANT *value)) { NCOM::CPropVariant prop; switch (propID) { case kpidSize: if (_stream && _header.HasSize()) prop = _header.Size; break; case kpidPackSize: if (_packSize_Defined) prop = _packSize; break; - case kpidMethod: - if (_stream) - { - char sz[64]; - char *s = sz; - if (_header.FilterID != 0) - s = MyStpCpy(s, "BCJ "); - s = MyStpCpy(s, "LZMA:"); - DictSizeToString(_header.GetDicSize(), s); - prop = sz; - } - break; + case kpidMethod: GetMethod(prop); break; + default: break; } prop.Detach(value); return S_OK; @@ -289,10 +297,10 @@ API_FUNC_static_IsArc IsArc_Lzma(const Byte *p, size_t size) return k_IsArc_Res_NEED_MORE; if (p[0] >= 5 * 5 * 9) return k_IsArc_Res_NO; - UInt64 unpackSize = GetUi64(p + 1 + 4); + const UInt64 unpackSize = GetUi64(p + 1 + 4); if (unpackSize != (UInt64)(Int64)-1) { - if (size >= ((UInt64)1 << 56)) + if (unpackSize >= ((UInt64)1 << 56)) return k_IsArc_Res_NO; } if (unpackSize != 0) @@ -325,24 +333,53 @@ API_FUNC_static_IsArc IsArc_Lzma86(const Byte *p, size_t size) } } -STDMETHODIMP CHandler::Open(IInStream *inStream, const UInt64 *, IArchiveOpenCallback *) + + +Z7_COM7F_IMF(CHandler::Open(IInStream *inStream, const UInt64 *, IArchiveOpenCallback *)) { Close(); - const UInt32 kBufSize = 1 + 5 + 8 + 2; + const unsigned headerSize = GetHeaderSize(); + const UInt32 kBufSize = 1 << 7; Byte buf[kBufSize]; - - RINOK(ReadStream_FALSE(inStream, buf, kBufSize)); - + size_t processedSize = kBufSize; + RINOK(ReadStream(inStream, buf, &processedSize)) + if (processedSize < headerSize + 2) + return S_FALSE; if (!_header.Parse(buf, _lzma86)) return S_FALSE; - const Byte *start = buf + GetHeaderSize(); + const Byte *start = buf + headerSize; if (start[0] != 0 /* || (start[1] & 0x80) != 0 */ ) // empty stream with EOS is not 0x80 return S_FALSE; - - RINOK(inStream->Seek(0, STREAM_SEEK_END, &_packSize)); - if (_packSize >= 24 && _header.Size == 0 && _header.FilterID == 0 && _header.LzmaProps[0] == 0) + + RINOK(InStream_GetSize_SeekToEnd(inStream, _packSize)) + + SizeT srcLen = (SizeT)processedSize - headerSize; + + if (srcLen > 10 + && _header.Size == 0 + // && _header.FilterID == 0 + && _header.LzmaProps[0] == 0 + ) return S_FALSE; + + const UInt32 outLimit = 1 << 11; + Byte outBuf[outLimit]; + + SizeT outSize = outLimit; + if (outSize > _header.Size) + outSize = (SizeT)_header.Size; + SizeT destLen = outSize; + ELzmaStatus status; + + SRes res = LzmaDecode(outBuf, &destLen, start, &srcLen, + _header.LzmaProps, 5, LZMA_FINISH_ANY, + &status, &g_Alloc); + + if (res != SZ_OK) + if (res != SZ_ERROR_INPUT_EOF) + return S_FALSE; + _isArc = true; _stream = inStream; _seqStream = inStream; @@ -350,7 +387,7 @@ STDMETHODIMP CHandler::Open(IInStream *inStream, const UInt64 *, IArchiveOpenCal return S_OK; } -STDMETHODIMP CHandler::OpenSeq(ISequentialInStream *stream) +Z7_COM7F_IMF(CHandler::OpenSeq(ISequentialInStream *stream)) { Close(); _isArc = true; @@ -358,53 +395,50 @@ STDMETHODIMP CHandler::OpenSeq(ISequentialInStream *stream) return S_OK; } -STDMETHODIMP CHandler::Close() +Z7_COM7F_IMF(CHandler::Close()) { _isArc = false; - _packSize_Defined = false; - _unpackSize_Defined = false; - _numStreams_Defined = false; - + _needSeekToStart = false; _dataAfterEnd = false; _needMoreInput = false; _unsupported = false; _dataError = false; - _packSize = 0; + _packSize_Defined = false; + _unpackSize_Defined = false; + _numStreams_Defined = false; - _needSeekToStart = false; + _packSize = 0; _stream.Release(); _seqStream.Release(); return S_OK; } -class CCompressProgressInfoImp: - public ICompressProgressInfo, - public CMyUnknownImp -{ +Z7_CLASS_IMP_COM_1( + CCompressProgressInfoImp, + ICompressProgressInfo +) CMyComPtr Callback; public: UInt64 Offset; - - MY_UNKNOWN_IMP1(ICompressProgressInfo) - STDMETHOD(SetRatioInfo)(const UInt64 *inSize, const UInt64 *outSize); + void Init(IArchiveOpenCallback *callback) { Callback = callback; } }; -STDMETHODIMP CCompressProgressInfoImp::SetRatioInfo(const UInt64 *inSize, const UInt64 * /* outSize */) +Z7_COM7F_IMF(CCompressProgressInfoImp::SetRatioInfo(const UInt64 *inSize, const UInt64 * /* outSize */)) { if (Callback) { - UInt64 files = 0; - UInt64 value = Offset + *inSize; - return Callback->SetCompleted(&files, &value); + const UInt64 files = 0; + const UInt64 val = Offset + *inSize; + return Callback->SetCompleted(&files, &val); } return S_OK; } -STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, - Int32 testMode, IArchiveExtractCallback *extractCallback) +Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testMode, IArchiveExtractCallback *extractCallback)) { COM_TRY_BEGIN @@ -414,41 +448,39 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, return E_INVALIDARG; if (_packSize_Defined) - extractCallback->SetTotal(_packSize); + RINOK(extractCallback->SetTotal(_packSize)) - + Int32 opResult; + { CMyComPtr realOutStream; - Int32 askMode = testMode ? + const Int32 askMode = testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; - RINOK(extractCallback->GetStream(0, &realOutStream, askMode)); + RINOK(extractCallback->GetStream(0, &realOutStream, askMode)) if (!testMode && !realOutStream) return S_OK; - extractCallback->PrepareOperation(askMode); + RINOK(extractCallback->PrepareOperation(askMode)) - CDummyOutStream *outStreamSpec = new CDummyOutStream; - CMyComPtr outStream(outStreamSpec); - outStreamSpec->SetStream(realOutStream); - outStreamSpec->Init(); + CMyComPtr2_Create outStream; + outStream->SetStream(realOutStream); + outStream->Init(); realOutStream.Release(); - CLocalProgress *lps = new CLocalProgress; - CMyComPtr progress = lps; + CMyComPtr2_Create lps; lps->Init(extractCallback, true); if (_needSeekToStart) { if (!_stream) return E_FAIL; - RINOK(_stream->Seek(0, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekToBegin(_stream)) } else _needSeekToStart = true; CDecoder decoder; - HRESULT result = decoder.Create(_lzma86, _seqStream); - RINOK(result); + RINOK(decoder.Create(_lzma86, _seqStream)) bool firstItem = true; @@ -458,17 +490,19 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, bool dataAfterEnd = false; + HRESULT hres = S_OK; + for (;;) { lps->InSize = packSize; lps->OutSize = unpackSize; - RINOK(lps->SetCur()); + RINOK(lps->SetCur()) const UInt32 kBufSize = 1 + 5 + 8; Byte buf[kBufSize]; const UInt32 headerSize = GetHeaderSize(); UInt32 processed; - RINOK(decoder.ReadInput(buf, headerSize, &processed)); + RINOK(decoder.ReadInput(buf, headerSize, &processed)) if (processed != headerSize) { if (processed != 0) @@ -485,32 +519,32 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, numStreams++; firstItem = false; - result = decoder.Code(st, outStream, progress); + hres = decoder.Code(st, outStream, lps); packSize = decoder.GetInputProcessedSize(); - unpackSize = outStreamSpec->GetSize(); + unpackSize = outStream->GetSize(); - if (result == E_NOTIMPL) + if (hres == E_NOTIMPL) { _unsupported = true; - result = S_FALSE; + hres = S_FALSE; break; } - if (result == S_FALSE) + if (hres == S_FALSE) break; - RINOK(result); + RINOK(hres) } if (firstItem) { _isArc = false; - result = S_FALSE; + hres = S_FALSE; } - else if (result == S_OK || result == S_FALSE) + else if (hres == S_OK || hres == S_FALSE) { if (dataAfterEnd) _dataAfterEnd = true; - else if (decoder._lzmaDecoderSpec->NeedMoreInput) + else if (decoder._lzmaDecoder->NeedsMoreInput()) _needMoreInput = true; _packSize = packSize; @@ -522,7 +556,7 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, _numStreams_Defined = true; } - Int32 opResult = NExtract::NOperationResult::kOK; + opResult = NExtract::NOperationResult::kOK; if (!_isArc) opResult = NExtract::NOperationResult::kIsNotArc; @@ -532,14 +566,15 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, opResult = NExtract::NOperationResult::kUnsupportedMethod; else if (_dataAfterEnd) opResult = NExtract::NOperationResult::kDataAfterEnd; - else if (result == S_FALSE) + else if (hres == S_FALSE) opResult = NExtract::NOperationResult::kDataError; - else if (result == S_OK) + else if (hres == S_OK) opResult = NExtract::NOperationResult::kOK; else - return result; + return hres; - outStream.Release(); + // outStream.Release(); + } return extractCallback->SetOperationResult(opResult); COM_TRY_END @@ -551,7 +586,7 @@ namespace NLzmaAr { REGISTER_ARC_I_CLS_NO_SIG( CHandler(false), - "lzma", "lzma", 0, 0xA, + "lzma", "lzma", NULL, 0xA, 0, NArcInfoFlags::kStartOpen | NArcInfoFlags::kKeepName, @@ -563,7 +598,7 @@ namespace NLzma86Ar { REGISTER_ARC_I_CLS_NO_SIG( CHandler(true), - "lzma86", "lzma86", 0, 0xB, + "lzma86", "lzma86", NULL, 0xB, 0, NArcInfoFlags::kKeepName, IsArc_Lzma86) diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/MachoHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/MachoHandler.cpp index 26260686..4920a046 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/MachoHandler.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/MachoHandler.cpp @@ -35,10 +35,11 @@ namespace NMacho { #define CPU_SUBTYPE_I386_ALL 3 -#define CPU_TYPE_PPC64 (CPU_ARCH_ABI64 | CPU_TYPE_PPC) +// #define CPU_TYPE_PPC64 (CPU_ARCH_ABI64 | CPU_TYPE_PPC) #define CPU_TYPE_AMD64 (CPU_ARCH_ABI64 | CPU_TYPE_386) +#define CPU_TYPE_ARM64 (CPU_ARCH_ABI64 | CPU_TYPE_ARM) -#define CPU_SUBTYPE_LIB64 (1 << 31) +#define CPU_SUBTYPE_LIB64 ((UInt32)1 << 31) #define CPU_SUBTYPE_POWERPC_970 100 @@ -60,6 +61,8 @@ static const char * const k_PowerPc_SubTypes[] = static const CUInt32PCharPair g_CpuPairs[] = { + { CPU_TYPE_AMD64, "x64" }, + { CPU_TYPE_ARM64, "ARM64" }, { CPU_TYPE_386, "x86" }, { CPU_TYPE_ARM, "ARM" }, { CPU_TYPE_SPARC, "SPARC" }, @@ -92,6 +95,13 @@ static const char * const g_SectTypes[] = , "GB_ZEROFILL" , "INTERPOSING" , "16BYTE_LITERALS" + , "DTRACE_DOF" + , "LAZY_DYLIB_SYMBOL_POINTERS" + , "THREAD_LOCAL_REGULAR" + , "THREAD_LOCAL_ZEROFILL" + , "THREAD_LOCAL_VARIABLES" + , "THREAD_LOCAL_VARIABLE_POINTERS" + , "THREAD_LOCAL_INIT_FUNCTION_POINTERS" }; enum EFileType @@ -154,7 +164,7 @@ static const char * const g_ArcFlags[] = }; -static const CUInt32PCharPair g_Flags[] = +static const CUInt32PCharPair g_SectFlags[] = { { 31, "PURE_INSTRUCTIONS" }, { 30, "NO_TOC" }, @@ -168,25 +178,54 @@ static const CUInt32PCharPair g_Flags[] = { 8, "LOC_RELOC" } }; -static const int kNameSize = 16; + +// VM_PROT_* +static const char * const g_SegmentProt[] = +{ + "READ" + , "WRITE" + , "EXECUTE" + /* + , "NO_CHANGE" + , "COPY" + , "TRUSTED" + , "IS_MASK" + */ +}; + +// SG_* + +static const char * const g_SegmentFlags[] = +{ + "SG_HIGHVM" + , "SG_FVMLIB" + , "SG_NORELOC" + , "SG_PROTECTED_VERSION_1" + , "SG_READ_ONLY" +}; + +static const unsigned kNameSize = 16; struct CSegment { char Name[kNameSize]; + UInt32 MaxProt; + UInt32 InitProt; + UInt32 Flags; }; struct CSection { char Name[kNameSize]; - char SegName[kNameSize]; + // char SegName[kNameSize]; UInt64 Va; UInt64 Pa; UInt64 VSize; UInt64 PSize; + UInt32 Align; UInt32 Flags; - int SegmentIndex; - + unsigned SegmentIndex; bool IsDummy; CSection(): IsDummy(false) {} @@ -195,11 +234,9 @@ struct CSection }; -class CHandler: - public IInArchive, - public IArchiveAllowTail, - public CMyUnknownImp -{ +Z7_CLASS_IMP_CHandler_IInArchive_1( + IArchiveAllowTail +) CMyComPtr _inStream; CObjectVector _segments; CObjectVector _sections; @@ -215,9 +252,6 @@ class CHandler: HRESULT Open2(ISequentialInStream *stream); public: - MY_UNKNOWN_IMP2(IInArchive, IArchiveAllowTail) - INTERFACE_IInArchive(;) - STDMETHOD(AllowTail)(Int32 allowTail); CHandler(): _allowTail(false) {} }; @@ -237,13 +271,14 @@ static const Byte kProps[] = kpidPackSize, kpidCharacts, kpidOffset, - kpidVa + kpidVa, + kpidClusterSize // Align }; IMP_IInArchive_Props IMP_IInArchive_ArcProps -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN CPropVariant prop; @@ -253,35 +288,28 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) case kpidCpu: { AString s; - char temp[16]; - UInt32 cpu = _cpuType & ~(UInt32)CPU_ARCH_ABI64; - if (_cpuType == CPU_TYPE_AMD64) - s = "x64"; - else + const UInt32 cpu = _cpuType & ~(UInt32)CPU_ARCH_ABI64; + UInt32 flag64 = _cpuType & (UInt32)CPU_ARCH_ABI64; { - const char *n = NULL; - for (unsigned i = 0; i < ARRAY_SIZE(g_CpuPairs); i++) + s.Add_UInt32(cpu); + for (unsigned i = 0; i < Z7_ARRAY_SIZE(g_CpuPairs); i++) { const CUInt32PCharPair &pair = g_CpuPairs[i]; - if (pair.Value == cpu) + if (pair.Value == cpu || pair.Value == _cpuType) { - n = pair.Name; + if (pair.Value == _cpuType) + flag64 = 0; + s = pair.Name; break; } } - if (!n) - { - ConvertUInt32ToString(cpu, temp); - n = temp; - } - s = n; - if (_cpuType & CPU_ARCH_ABI64) - s += " 64-bit"; - else if (_cpuSubType & CPU_SUBTYPE_LIB64) - s += " 64-bit lib"; + if (flag64 != 0) + s.Add_OptSpaced("64-bit"); + else if ((_cpuSubType & CPU_SUBTYPE_LIB64) && _cpuType != CPU_TYPE_AMD64) + s.Add_OptSpaced("64-bit-lib"); } - UInt32 t = _cpuSubType & ~(UInt32)CPU_SUBTYPE_LIB64; + const UInt32 t = _cpuSubType & ~(UInt32)CPU_SUBTYPE_LIB64; if (t != 0 && (t != CPU_SUBTYPE_I386_ALL || cpu != CPU_TYPE_386)) { const char *n = NULL; @@ -289,16 +317,14 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) { if (t == CPU_SUBTYPE_POWERPC_970) n = "970"; - else if (t < ARRAY_SIZE(k_PowerPc_SubTypes)) + else if (t < Z7_ARRAY_SIZE(k_PowerPc_SubTypes)) n = k_PowerPc_SubTypes[t]; } - if (!n) - { - ConvertUInt32ToString(t, temp); - n = temp; - } s.Add_Space(); - s += n; + if (n) + s += n; + else + s.Add_UInt32(t); } prop = s; break; @@ -306,8 +332,8 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) case kpidCharacts: { // TYPE_TO_PROP(g_FileTypes, _type, prop); break; - AString res = TypeToString(g_FileTypes, ARRAY_SIZE(g_FileTypes), _type); - AString s = FlagsToString(g_ArcFlags, ARRAY_SIZE(g_ArcFlags), _flags); + AString res (TypeToString(g_FileTypes, Z7_ARRAY_SIZE(g_FileTypes), _type)); + const AString s (FlagsToString(g_ArcFlags, Z7_ARRAY_SIZE(g_ArcFlags), _flags)); if (!s.IsEmpty()) { res.Add_Space(); @@ -340,27 +366,16 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) COM_TRY_END } -static AString GetName(const char *name) +static void AddName(AString &s, const char *name) { - char res[kNameSize + 1]; - memcpy(res, name, kNameSize); - res[kNameSize] = 0; - return res; + char temp[kNameSize + 1]; + memcpy(temp, name, kNameSize); + temp[kNameSize] = 0; + s += temp; } -static AString SectFlagsToString(UInt32 flags) -{ - AString res = TypeToString(g_SectTypes, ARRAY_SIZE(g_SectTypes), flags & SECT_TYPE_MASK); - AString s = FlagsToString(g_Flags, ARRAY_SIZE(g_Flags), flags & SECT_ATTR_MASK); - if (!s.IsEmpty()) - { - res.Add_Space(); - res += s; - } - return res; -} -STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN CPropVariant prop; @@ -369,29 +384,88 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val { case kpidPath: { - AString s = GetName(_segments[item.SegmentIndex].Name); + AString s; + AddName(s, _segments[item.SegmentIndex].Name); if (!item.IsDummy) - s += GetName(item.Name); + { + // CSection::SegName and CSegment::Name are same in real cases. + // AddName(s, item.SegName); + AddName(s, item.Name); + } prop = MultiByteToUnicodeString(s); break; } case kpidSize: /* prop = (UInt64)item.VSize; break; */ case kpidPackSize: prop = (UInt64)item.GetPackSize(); break; - case kpidCharacts: if (!item.IsDummy) prop = SectFlagsToString(item.Flags); break; + case kpidCharacts: + { + AString res; + { + if (!item.IsDummy) + { + { + const AString s (TypeToString(g_SectTypes, Z7_ARRAY_SIZE(g_SectTypes), item.Flags & SECT_TYPE_MASK)); + if (!s.IsEmpty()) + { + res.Add_OptSpaced("sect_type:"); + res.Add_OptSpaced(s); + } + } + { + const AString s (FlagsToString(g_SectFlags, Z7_ARRAY_SIZE(g_SectFlags), item.Flags & SECT_ATTR_MASK)); + if (!s.IsEmpty()) + { + res.Add_OptSpaced("sect_flags:"); + res.Add_OptSpaced(s); + } + } + } + const CSegment &seg = _segments[item.SegmentIndex]; + { + const AString s (FlagsToString(g_SegmentFlags, Z7_ARRAY_SIZE(g_SegmentFlags), seg.Flags)); + if (!s.IsEmpty()) + { + res.Add_OptSpaced("seg_flags:"); + res.Add_OptSpaced(s); + } + } + { + const AString s (FlagsToString(g_SegmentProt, Z7_ARRAY_SIZE(g_SegmentProt), seg.MaxProt)); + if (!s.IsEmpty()) + { + res.Add_OptSpaced("max_prot:"); + res.Add_OptSpaced(s); + } + } + { + const AString s (FlagsToString(g_SegmentProt, Z7_ARRAY_SIZE(g_SegmentProt), seg.InitProt)); + if (!s.IsEmpty()) + { + res.Add_OptSpaced("init_prot:"); + res.Add_OptSpaced(s); + } + } + } + if (!res.IsEmpty()) + prop = res; + break; + } case kpidOffset: prop = item.Pa; break; case kpidVa: prop = item.Va; break; + case kpidClusterSize: prop = (UInt32)1 << item.Align; break; } prop.Detach(value); return S_OK; COM_TRY_END } + HRESULT CHandler::Open2(ISequentialInStream *stream) { const UInt32 kStartHeaderSize = 7 * 4; Byte header[kStartHeaderSize]; - RINOK(ReadStream_FALSE(stream, header, kStartHeaderSize)); + RINOK(ReadStream_FALSE(stream, header, kStartHeaderSize)) bool be, mode64; switch (GetUi32(header)) { @@ -402,8 +476,11 @@ HRESULT CHandler::Open2(ISequentialInStream *stream) default: return S_FALSE; } - UInt32 numCommands = Get32(header + 0x10, be); - UInt32 commandsSize = Get32(header + 0x14, be); + _mode64 = mode64; + _be = be; + + const UInt32 numCommands = Get32(header + 0x10, be); + const UInt32 commandsSize = Get32(header + 0x14, be); if (numCommands == 0) return S_FALSE; @@ -435,7 +512,7 @@ HRESULT CHandler::Open2(ISequentialInStream *stream) _headersSize = startHeaderSize + commandsSize; _totalSize = _headersSize; CByteArr buffer(_headersSize); - RINOK(ReadStream_FALSE(stream, buffer + kStartHeaderSize, _headersSize - kStartHeaderSize)); + RINOK(ReadStream_FALSE(stream, buffer + kStartHeaderSize, _headersSize - kStartHeaderSize)) const Byte *buf = buffer + startHeaderSize; size_t size = _headersSize - startHeaderSize; for (UInt32 cmdIndex = 0; cmdIndex < numCommands; cmdIndex++) @@ -481,6 +558,9 @@ HRESULT CHandler::Open2(ISequentialInStream *stream) } CSegment seg; + seg.MaxProt = Get32(buf + offs - 16, be); + seg.InitProt = Get32(buf + offs - 12, be); + seg.Flags = Get32(buf + offs - 4, be); memcpy(seg.Name, buf + 8, kNameSize); _segments.Add(seg); @@ -497,11 +577,12 @@ HRESULT CHandler::Open2(ISequentialInStream *stream) sect.PSize = phSize; sect.VSize = vmSize; sect.Pa = phAddr; + sect.Align = 0; sect.Flags = 0; } else do { - UInt32 headSize = (cmd == CMD_SEGMENT_64) ? 0x50 : 0x44; + const UInt32 headSize = (cmd == CMD_SEGMENT_64) ? 0x50 : 0x44; const Byte *p = buf + offs; if (cmdSize - offs < headSize) break; @@ -520,13 +601,16 @@ HRESULT CHandler::Open2(ISequentialInStream *stream) f32Offset = 0x28; } sect.Pa = Get32(p + f32Offset, be); - sect.Flags = Get32(p + f32Offset + 10, be); - if (sect.Flags == SECT_ATTR_ZEROFILL) + sect.Align = Get32(p + f32Offset + 4, be); + // sect.reloff = Get32(p + f32Offset + 8, be); + // sect.nreloc = Get32(p + f32Offset + 12, be); + sect.Flags = Get32(p + f32Offset + 16, be); + if ((sect.Flags & SECT_TYPE_MASK) == SECT_ATTR_ZEROFILL) sect.PSize = 0; else sect.PSize = sect.VSize; memcpy(sect.Name, p, kNameSize); - memcpy(sect.SegName, p + kNameSize, kNameSize); + // memcpy(sect.SegName, p + kNameSize, kNameSize); sect.SegmentIndex = _segments.Size() - 1; offs += headSize; } @@ -545,17 +629,17 @@ HRESULT CHandler::Open2(ISequentialInStream *stream) return S_OK; } -STDMETHODIMP CHandler::Open(IInStream *inStream, +Z7_COM7F_IMF(CHandler::Open(IInStream *inStream, const UInt64 * /* maxCheckStartPosition */, - IArchiveOpenCallback * /* openArchiveCallback */) + IArchiveOpenCallback * /* openArchiveCallback */)) { COM_TRY_BEGIN Close(); - RINOK(Open2(inStream)); + RINOK(Open2(inStream)) if (!_allowTail) { UInt64 fileSize; - RINOK(inStream->Seek(0, STREAM_SEEK_END, &fileSize)); + RINOK(InStream_GetSize_SeekToEnd(inStream, fileSize)) if (fileSize > _totalSize) return S_FALSE; } @@ -564,7 +648,7 @@ STDMETHODIMP CHandler::Open(IInStream *inStream, COM_TRY_END } -STDMETHODIMP CHandler::Close() +Z7_COM7F_IMF(CHandler::Close()) { _totalSize = 0; _inStream.Release(); @@ -573,17 +657,17 @@ STDMETHODIMP CHandler::Close() return S_OK; } -STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) { *numItems = _sections.Size(); return S_OK; } -STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, - Int32 testMode, IArchiveExtractCallback *extractCallback) +Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testMode, IArchiveExtractCallback *extractCallback)) { COM_TRY_BEGIN - bool allFilesMode = (numItems == (UInt32)(Int32)-1); + const bool allFilesMode = (numItems == (UInt32)(Int32)-1); if (allFilesMode) numItems = _sections.Size(); if (numItems == 0) @@ -611,33 +695,33 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, for (i = 0; i < numItems; i++, currentTotalSize += currentItemSize) { lps->InSize = lps->OutSize = currentTotalSize; - RINOK(lps->SetCur()); - Int32 askMode = testMode ? + RINOK(lps->SetCur()) + const Int32 askMode = testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; - UInt32 index = allFilesMode ? i : indices[i]; + const UInt32 index = allFilesMode ? i : indices[i]; const CSection &item = _sections[index]; currentItemSize = item.GetPackSize(); CMyComPtr outStream; - RINOK(extractCallback->GetStream(index, &outStream, askMode)); + RINOK(extractCallback->GetStream(index, &outStream, askMode)) if (!testMode && !outStream) continue; - RINOK(extractCallback->PrepareOperation(askMode)); - RINOK(_inStream->Seek(item.Pa, STREAM_SEEK_SET, NULL)); + RINOK(extractCallback->PrepareOperation(askMode)) + RINOK(InStream_SeekSet(_inStream, item.Pa)) streamSpec->Init(currentItemSize); - RINOK(copyCoder->Code(inStream, outStream, NULL, NULL, progress)); + RINOK(copyCoder->Code(inStream, outStream, NULL, NULL, progress)) outStream.Release(); RINOK(extractCallback->SetOperationResult(copyCoderSpec->TotalSize == currentItemSize ? NExtract::NOperationResult::kOK: - NExtract::NOperationResult::kDataError)); + NExtract::NOperationResult::kDataError)) } return S_OK; COM_TRY_END } -STDMETHODIMP CHandler::AllowTail(Int32 allowTail) +Z7_COM7F_IMF(CHandler::AllowTail(Int32 allowTail)) { _allowTail = IntToBool(allowTail); return S_OK; @@ -650,7 +734,7 @@ static const Byte k_Signature[] = { 4, 0xFE, 0xED, 0xFA, 0xCF }; REGISTER_ARC_I( - "MachO", "macho", 0, 0xDF, + "MachO", "macho", NULL, 0xDF, k_Signature, 0, NArcInfoFlags::kMultiSignature | diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/MbrHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/MbrHandler.cpp index b92755ae..53663e8e 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/MbrHandler.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/MbrHandler.cpp @@ -30,6 +30,11 @@ using namespace NWindows; namespace NArchive { + +namespace NFat { +API_FUNC_IsArc IsArc_Fat(const Byte *p, size_t size); +} + namespace NMbr { struct CChs @@ -48,13 +53,18 @@ struct CChs SectCyl = p[1]; Cyl8 = p[2]; } - bool Check() const { return GetSector() > 0; } + bool Check() const + { + if ((Head | SectCyl | Cyl8) == 0) + return true; + return GetSector() > 0; + } }; -#define RINOZ(x) { int __tt = (x); if (__tt != 0) return __tt; } // Chs in some MBRs contains only low bits of "Cyl number". So we disable check. /* +#define RINOZ(x) { int _t_ = (x); if (_t_ != 0) return _t_; } static int CompareChs(const CChs &c1, const CChs &c2) { RINOZ(MyCompare(c1.GetCyl(), c2.GetCyl())); @@ -63,21 +73,14 @@ static int CompareChs(const CChs &c1, const CChs &c2) } */ -static void AddUIntToString(UInt32 val, AString &res) -{ - char s[16]; - ConvertUInt32ToString(val, s); - res += s; -} - void CChs::ToString(NCOM::CPropVariant &prop) const { AString s; - AddUIntToString(GetCyl(), s); - s += '-'; - AddUIntToString(Head, s); - s += '-'; - AddUIntToString(GetSector(), s); + s.Add_UInt32(GetCyl()); + s.Add_Minus(); + s.Add_UInt32(Head); + s.Add_Minus(); + s.Add_UInt32(GetSector()); prop = s; } @@ -96,8 +99,8 @@ struct CPartition bool IsExtended() const { return Type == 5 || Type == 0xF; } UInt32 GetLimit() const { return Lba + NumBlocks; } // bool IsActive() const { return Status == 0x80; } - UInt64 GetPos() const { return (UInt64)Lba * 512; } - UInt64 GetSize() const { return (UInt64)NumBlocks * 512; } + UInt64 GetPos(unsigned sectorSizeLog) const { return (UInt64)Lba << sectorSizeLog; } + UInt64 GetSize(unsigned sectorSizeLog) const { return (UInt64)NumBlocks << sectorSizeLog; } bool CheckLbaLimits() const { return (UInt32)0xFFFFFFFF - Lba >= NumBlocks; } bool Parse(const Byte *p) @@ -137,42 +140,51 @@ struct CPartType const char *Name; }; -static const char *kFat = "fat"; +#define kFat "fat" + +/* +if we use "format" command in Windows 10 for existing partition: + - if we format to ExFAT, it sets type=7 + - if we format to UDF, it doesn't change type from previous value. +*/ + +static const unsigned kType_Windows_NTFS = 7; static const CPartType kPartTypes[] = { { 0x01, kFat, "FAT12" }, { 0x04, kFat, "FAT16 DOS 3.0+" }, - { 0x05, 0, "Extended" }, + { 0x05, NULL, "Extended" }, { 0x06, kFat, "FAT16 DOS 3.31+" }, { 0x07, "ntfs", "NTFS" }, { 0x0B, kFat, "FAT32" }, { 0x0C, kFat, "FAT32-LBA" }, { 0x0E, kFat, "FAT16-LBA" }, - { 0x0F, 0, "Extended-LBA" }, + { 0x0F, NULL, "Extended-LBA" }, { 0x11, kFat, "FAT12-Hidden" }, { 0x14, kFat, "FAT16-Hidden < 32 MB" }, { 0x16, kFat, "FAT16-Hidden >= 32 MB" }, { 0x1B, kFat, "FAT32-Hidden" }, { 0x1C, kFat, "FAT32-LBA-Hidden" }, { 0x1E, kFat, "FAT16-LBA-WIN95-Hidden" }, - { 0x82, 0, "Solaris x86 / Linux swap" }, - { 0x83, 0, "Linux" }, + { 0x27, "ntfs", "NTFS-WinRE" }, + { 0x82, NULL, "Solaris x86 / Linux swap" }, + { 0x83, NULL, "Linux" }, { 0x8E, "lvm", "Linux LVM" }, - { 0xA5, 0, "BSD slice" }, - { 0xBE, 0, "Solaris 8 boot" }, - { 0xBF, 0, "New Solaris x86" }, - { 0xC2, 0, "Linux-Hidden" }, - { 0xC3, 0, "Linux swap-Hidden" }, - { 0xEE, 0, "GPT" }, - { 0xEE, 0, "EFI" } + { 0xA5, NULL, "BSD slice" }, + { 0xBE, NULL, "Solaris 8 boot" }, + { 0xBF, NULL, "New Solaris x86" }, + { 0xC2, NULL, "Linux-Hidden" }, + { 0xC3, NULL, "Linux swap-Hidden" }, + { 0xEE, NULL, "GPT" }, + { 0xEE, NULL, "EFI" } }; static int FindPartType(UInt32 type) { - for (unsigned i = 0; i < ARRAY_SIZE(kPartTypes); i++) + for (unsigned i = 0; i < Z7_ARRAY_SIZE(kPartTypes); i++) if (kPartTypes[i].Id == type) - return i; + return (int)i; return -1; } @@ -180,29 +192,52 @@ struct CItem { bool IsReal; bool IsPrim; + bool WasParsed; + const char *FileSystem; UInt64 Size; CPartition Part; + + CItem(): + WasParsed(false), + FileSystem(NULL) + {} }; -class CHandler: public CHandlerCont + +Z7_class_CHandler_final: public CHandlerCont { + Z7_IFACE_COM7_IMP(IInArchive_Cont) + CObjectVector _items; UInt64 _totalSize; CByteBuffer _buffer; - virtual int GetItem_ExtractInfo(UInt32 index, UInt64 &pos, UInt64 &size) const + UInt32 _signature; + unsigned _sectorSizeLog; + + virtual int GetItem_ExtractInfo(UInt32 index, UInt64 &pos, UInt64 &size) const Z7_override Z7_final { const CItem &item = _items[index]; - pos = item.Part.GetPos(); + pos = item.Part.GetPos(_sectorSizeLog); size = item.Size; return NExtract::NOperationResult::kOK; } HRESULT ReadTables(IInStream *stream, UInt32 baseLba, UInt32 lba, unsigned level); -public: - INTERFACE_IInArchive_Cont(;) }; +/* +static bool IsEmptyBuffer(const Byte *data, size_t size) +{ + for (unsigned i = 0; i < size; i++) + if (data[i] != 0) + return false; + return true; +} +*/ + +const char *GetFileSystem(ISequentialInStream *stream, UInt64 partitionSize); + HRESULT CHandler::ReadTables(IInStream *stream, UInt32 baseLba, UInt32 lba, unsigned level) { if (level >= 128 || _items.Size() >= 128) @@ -211,24 +246,65 @@ HRESULT CHandler::ReadTables(IInStream *stream, UInt32 baseLba, UInt32 lba, unsi const unsigned kNumHeaderParts = 4; CPartition parts[kNumHeaderParts]; + if (level == 0) + _sectorSizeLog = 9; + const UInt32 kSectorSize = (UInt32)1 << _sectorSizeLog; + UInt32 bufSize = kSectorSize; + if (level == 0 && _totalSize >= (1 << 12)) + bufSize = (1 << 12); + _buffer.Alloc(bufSize); { - const UInt32 kSectorSize = 512; - _buffer.Alloc(kSectorSize); Byte *buf = _buffer; - UInt64 newPos = (UInt64)lba << 9; - if (newPos + 512 > _totalSize) + const UInt64 newPos = (UInt64)lba << _sectorSizeLog; + if (newPos + bufSize > _totalSize) return S_FALSE; - RINOK(stream->Seek(newPos, STREAM_SEEK_SET, NULL)); - RINOK(ReadStream_FALSE(stream, buf, kSectorSize)); - + RINOK(InStream_SeekSet(stream, newPos)) + RINOK(ReadStream_FALSE(stream, buf, bufSize)) if (buf[0x1FE] != 0x55 || buf[0x1FF] != 0xAA) return S_FALSE; - + if (level == 0) + _signature = GetUi32(buf + 0x1B8); for (unsigned i = 0; i < kNumHeaderParts; i++) if (!parts[i].Parse(buf + 0x1BE + 16 * i)) return S_FALSE; } + // 23.02: now we try to detect 4kn format (4 KB sectors case) + if (level == 0) + // if (_totalSize >= (1 << 28)) // we don't expect small images with 4 KB sectors + if (bufSize >= (1 << 12)) + { + UInt32 lastLim = 0; + UInt32 firstLba = 0; + UInt32 numBlocks = 0; // in first partition + for (unsigned i = 0; i < kNumHeaderParts; i++) + { + const CPartition &part = parts[i]; + if (part.IsEmpty()) + continue; + if (firstLba == 0 && part.NumBlocks != 0) + { + firstLba = part.Lba; + numBlocks = part.NumBlocks; + } + const UInt32 newLim = part.GetLimit(); + if (newLim < lastLim) + return S_FALSE; + lastLim = newLim; + } + if (lastLim != 0) + { + const UInt64 lim12 = (UInt64)lastLim << 12; + if (lim12 <= _totalSize + // && _totalSize - lim12 < (1 << 28) // we can try to exclude false positive cases + ) + // if (IsEmptyBuffer(&_buffer[(1 << 9)], (1 << 12) - (1 << 9))) + if (InStream_SeekSet(stream, (UInt64)firstLba << 12) == S_OK) + if (GetFileSystem(stream, (UInt64)numBlocks << 12)) + _sectorSizeLog = 12; + } + } + PRF(printf("\n# %8X", lba)); UInt32 limLba = lba + 1; @@ -255,7 +331,7 @@ HRESULT CHandler::ReadTables(IInStream *stream, UInt32 baseLba, UInt32 lba, unsi newLba = baseLba + part.Lba; if (newLba < limLba) return S_FALSE; - HRESULT res = ReadTables(stream, level < 1 ? newLba : baseLba, newLba, level + 1); + const HRESULT res = ReadTables(stream, level < 1 ? newLba : baseLba, newLba, level + 1); if (res != S_FALSE && res != S_OK) return res; } @@ -277,8 +353,8 @@ HRESULT CHandler::ReadTables(IInStream *stream, UInt32 baseLba, UInt32 lba, unsi else { const CItem &back = _items.Back(); - UInt32 backLimit = back.Part.GetLimit(); - UInt32 partLimit = part.GetLimit(); + const UInt32 backLimit = back.Part.GetLimit(); + const UInt32 partLimit = part.GetLimit(); if (backLimit < partLimit) { n.IsReal = false; @@ -292,39 +368,131 @@ HRESULT CHandler::ReadTables(IInStream *stream, UInt32 baseLba, UInt32 lba, unsi if (n.Part.GetLimit() < limLba) return S_FALSE; limLba = n.Part.GetLimit(); - n.Size = n.Part.GetSize(); + n.Size = n.Part.GetSize(_sectorSizeLog); _items.Add(n); } } return S_OK; } -STDMETHODIMP CHandler::Open(IInStream *stream, + +static const Byte k_Ntfs_Signature[] = { 'N', 'T', 'F', 'S', ' ', ' ', ' ', ' ', 0 }; + +static bool Is_Ntfs(const Byte *p) +{ + if (p[0x1FE] != 0x55 || p[0x1FF] != 0xAA) + return false; + switch (p[0]) + { + case 0xE9: /* codeOffset = 3 + (Int16)Get16(p + 1); */ break; + case 0xEB: if (p[2] != 0x90) return false; /* codeOffset = 2 + (int)(signed char)p[1]; */ break; + default: return false; + } + return memcmp(p + 3, k_Ntfs_Signature, Z7_ARRAY_SIZE(k_Ntfs_Signature)) == 0; +} + +static const Byte k_ExFat_Signature[] = { 'E', 'X', 'F', 'A', 'T', ' ', ' ', ' ' }; + +static bool Is_ExFat(const Byte *p) +{ + if (p[0x1FE] != 0x55 || p[0x1FF] != 0xAA) + return false; + if (p[0] != 0xEB || p[1] != 0x76 || p[2] != 0x90) + return false; + return memcmp(p + 3, k_ExFat_Signature, Z7_ARRAY_SIZE(k_ExFat_Signature)) == 0; +} + +static bool AllAreZeros(const Byte *p, size_t size) +{ + for (size_t i = 0; i < size; i++) + if (p[i] != 0) + return false; + return true; +} + +static const UInt32 k_Udf_StartPos = 0x8000; +static const Byte k_Udf_Signature[] = { 0, 'B', 'E', 'A', '0', '1', 1, 0 }; + +static bool Is_Udf(const Byte *p) +{ + return memcmp(p + k_Udf_StartPos, k_Udf_Signature, sizeof(k_Udf_Signature)) == 0; +} + + +const char *GetFileSystem(ISequentialInStream *stream, UInt64 partitionSize) +{ + const size_t kHeaderSize = 1 << 9; + if (partitionSize >= kHeaderSize) + { + Byte buf[kHeaderSize]; + if (ReadStream_FAIL(stream, buf, kHeaderSize) == S_OK) + { + // NTFS is expected default filesystem for (Type == 7) + if (Is_Ntfs(buf)) + return "NTFS"; + if (Is_ExFat(buf)) + return "exFAT"; + if (NFat::IsArc_Fat(buf, kHeaderSize)) + return "FAT"; + const size_t kHeaderSize2 = k_Udf_StartPos + (1 << 9); + if (partitionSize >= kHeaderSize2) + { + if (AllAreZeros(buf, kHeaderSize)) + { + CByteBuffer buffer(kHeaderSize2); + // memcpy(buffer, buf, kHeaderSize); + if (ReadStream_FAIL(stream, buffer + kHeaderSize, kHeaderSize2 - kHeaderSize) == S_OK) + if (Is_Udf(buffer)) + return "UDF"; + } + } + } + } + return NULL; +} + + +Z7_COM7F_IMF(CHandler::Open(IInStream *stream, const UInt64 * /* maxCheckStartPosition */, - IArchiveOpenCallback * /* openArchiveCallback */) + IArchiveOpenCallback * /* openArchiveCallback */)) { COM_TRY_BEGIN Close(); - RINOK(stream->Seek(0, STREAM_SEEK_END, &_totalSize)); - RINOK(ReadTables(stream, 0, 0, 0)); + RINOK(InStream_GetSize_SeekToEnd(stream, _totalSize)) + RINOK(ReadTables(stream, 0, 0, 0)) if (_items.IsEmpty()) return S_FALSE; - UInt32 lbaLimit = _items.Back().Part.GetLimit(); - UInt64 lim = (UInt64)lbaLimit << 9; - if (lim < _totalSize) { - CItem n; - n.Part.Lba = lbaLimit; - n.Size = _totalSize - lim; - n.IsReal = false; - _items.Add(n); + const UInt32 lbaLimit = _items.Back().Part.GetLimit(); + const UInt64 lim = (UInt64)lbaLimit << _sectorSizeLog; + if (lim < _totalSize) + { + CItem n; + n.Part.Lba = lbaLimit; + n.Size = _totalSize - lim; + n.IsReal = false; + _items.Add(n); + } + } + + FOR_VECTOR (i, _items) + { + CItem &item = _items[i]; + if (item.Part.Type != kType_Windows_NTFS) + continue; + if (InStream_SeekSet(stream, item.Part.GetPos(_sectorSizeLog)) != S_OK) + continue; + item.FileSystem = GetFileSystem(stream, item.Size); + item.WasParsed = true; } + _stream = stream; return S_OK; COM_TRY_END } -STDMETHODIMP CHandler::Close() + +Z7_COM7F_IMF(CHandler::Close()) { _totalSize = 0; _items.Clear(); @@ -332,6 +500,7 @@ STDMETHODIMP CHandler::Close() return S_OK; } + enum { kpidPrimary = kpidUserDefined, @@ -350,10 +519,17 @@ static const CStatProp kProps[] = { "End CHS", kpidEndChs, VT_BSTR} }; +static const Byte kArcProps[] = +{ + kpidSectorSize, + kpidId +}; + IMP_IInArchive_Props_WITH_NAME -IMP_IInArchive_ArcProps_NO_Table +IMP_IInArchive_ArcProps + -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { NCOM::CPropVariant prop; switch (propID) @@ -369,25 +545,27 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) mainIndex = -1; break; } - mainIndex = i; + mainIndex = (int)i; } if (mainIndex >= 0) - prop = (UInt32)mainIndex; + prop = (UInt32)(Int32)mainIndex; break; } case kpidPhySize: prop = _totalSize; break; + case kpidSectorSize: prop = (UInt32)((UInt32)1 << _sectorSizeLog); break; + case kpidId: prop = _signature; break; } prop.Detach(value); return S_OK; } -STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) { *numItems = _items.Size(); return S_OK; } -STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; @@ -399,14 +577,26 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val case kpidPath: { AString s; - AddUIntToString(index, s); + s.Add_UInt32(index); if (item.IsReal) { - int typeIndex = FindPartType(part.Type); - s += '.'; - const char *ext = "img"; - if (typeIndex >= 0 && kPartTypes[(unsigned)typeIndex].Ext) - ext = kPartTypes[(unsigned)typeIndex].Ext; + s.Add_Dot(); + const char *ext = NULL; + if (item.FileSystem) + { + ext = ""; + AString fs (item.FileSystem); + fs.MakeLower_Ascii(); + s += fs; + } + else if (!item.WasParsed) + { + const int typeIndex = FindPartType(part.Type); + if (typeIndex >= 0) + ext = kPartTypes[(unsigned)typeIndex].Ext; + } + if (!ext) + ext = "img"; s += ext; } prop = s; @@ -418,15 +608,24 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val char s[32]; ConvertUInt32ToString(part.Type, s); const char *res = s; - int typeIndex = FindPartType(part.Type); - if (typeIndex >= 0 && kPartTypes[(unsigned)typeIndex].Name) - res = kPartTypes[(unsigned)typeIndex].Name; + if (item.FileSystem) + { + // strcat(s, "-"); + // strcpy(s, item.FileSystem); + res = item.FileSystem; + } + else if (!item.WasParsed) + { + const int typeIndex = FindPartType(part.Type); + if (typeIndex >= 0 && kPartTypes[(unsigned)typeIndex].Name) + res = kPartTypes[(unsigned)typeIndex].Name; + } prop = res; } break; case kpidSize: case kpidPackSize: prop = item.Size; break; - case kpidOffset: prop = part.GetPos(); break; + case kpidOffset: prop = part.GetPos(_sectorSizeLog); break; case kpidPrimary: if (item.IsReal) prop = item.IsPrim; break; case kpidBegChs: if (item.IsReal) part.BeginChs.ToString(prop); break; case kpidEndChs: if (item.IsReal) part.EndChs.ToString(prop); break; @@ -441,7 +640,7 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val // 2, { 0x55, 0x1FF }, REGISTER_ARC_I_NO_SIG( - "MBR", "mbr", 0, 0xDB, + "MBR", "mbr", NULL, 0xDB, 0, NArcInfoFlags::kPureStartOpen, NULL) diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/MslzHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/MslzHandler.cpp index ec375733..daf58bdf 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/MslzHandler.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/MslzHandler.cpp @@ -21,11 +21,9 @@ namespace NMslz { static const UInt32 kUnpackSizeMax = 0xFFFFFFE0; -class CHandler: - public IInArchive, - public IArchiveOpenSeq, - public CMyUnknownImp -{ +Z7_CLASS_IMP_CHandler_IInArchive_1( + IArchiveOpenSeq +) CMyComPtr _inStream; CMyComPtr _seqStream; @@ -43,10 +41,6 @@ class CHandler: UString _name; void ParseName(Byte replaceByte, IArchiveOpenCallback *callback); -public: - MY_UNKNOWN_IMP2(IInArchive, IArchiveOpenSeq) - INTERFACE_IInArchive(;) - STDMETHOD(OpenSeq)(ISequentialInStream *stream); }; static const Byte kProps[] = @@ -59,13 +53,13 @@ static const Byte kProps[] = IMP_IInArchive_Props IMP_IInArchive_ArcProps_NO_Table -STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) { *numItems = 1; return S_OK; } -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NWindows::NCOM::CPropVariant prop; @@ -77,10 +71,11 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) case kpidErrorFlags: { UInt32 v = 0; - if (!_isArc) v |= kpv_ErrorFlags_IsNotArc;; + if (!_isArc) v |= kpv_ErrorFlags_IsNotArc; if (_needMoreInput) v |= kpv_ErrorFlags_UnexpectedEnd; if (_dataAfterEnd) v |= kpv_ErrorFlags_DataAfterEnd; prop = v; + break; } } prop.Detach(value); @@ -89,7 +84,7 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) } -STDMETHODIMP CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NWindows::NCOM::CPropVariant prop; @@ -125,8 +120,7 @@ void CHandler::ParseName(Byte replaceByte, IArchiveOpenCallback *callback) { if (!callback) return; - CMyComPtr volumeCallback; - callback->QueryInterface(IID_IArchiveOpenVolumeCallback, (void **)&volumeCallback); + Z7_DECL_CMyComPtr_QI_FROM(IArchiveOpenVolumeCallback, volumeCallback, callback) if (!volumeCallback) return; @@ -146,37 +140,37 @@ void CHandler::ParseName(Byte replaceByte, IArchiveOpenCallback *callback) { if (s.Len() < 3 || s[s.Len() - 3] != '.') return; - for (unsigned i = 0; i < ARRAY_SIZE(g_Exts); i++) + for (unsigned i = 0; i < Z7_ARRAY_SIZE(g_Exts); i++) { const char *ext = g_Exts[i]; if (s[s.Len() - 2] == (Byte)ext[0] && s[s.Len() - 1] == (Byte)ext[1]) { - replaceByte = ext[2]; + replaceByte = (Byte)ext[2]; break; } } } if (replaceByte >= 0x20 && replaceByte < 0x80) - _name += (wchar_t)replaceByte; + _name.Add_Char((char)replaceByte); } -STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 * /* maxCheckStartPosition */, - IArchiveOpenCallback *callback) +Z7_COM7F_IMF(CHandler::Open(IInStream *stream, const UInt64 * /* maxCheckStartPosition */, + IArchiveOpenCallback *callback)) { COM_TRY_BEGIN { Close(); _needSeekToStart = true; Byte buffer[kHeaderSize]; - RINOK(ReadStream_FALSE(stream, buffer, kHeaderSize)); + RINOK(ReadStream_FALSE(stream, buffer, kHeaderSize)) if (memcmp(buffer, kSignature, kSignatureSize) != 0) return S_FALSE; _unpackSize = GetUi32(buffer + 10); if (_unpackSize > kUnpackSizeMax) return S_FALSE; - RINOK(stream->Seek(0, STREAM_SEEK_END, &_originalFileSize)); + RINOK(InStream_GetSize_SeekToEnd(stream, _originalFileSize)) _packSize = _originalFileSize; ParseName(buffer[kSignatureSize], callback); @@ -190,7 +184,7 @@ STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 * /* maxCheckStartPo COM_TRY_END } -STDMETHODIMP CHandler::Close() +Z7_COM7F_IMF(CHandler::Close()) { _originalFileSize = 0; _packSize = 0; @@ -276,11 +270,11 @@ static HRESULT MslzDec(CInBuffer &inStream, ISequentialOutStream *outStream, UIn } if (outStream) - RINOK(WriteStream(outStream, buf, dest & kMask)); + RINOK(WriteStream(outStream, buf, dest & kMask)) return S_OK; } -STDMETHODIMP CHandler::OpenSeq(ISequentialInStream *stream) +Z7_COM7F_IMF(CHandler::OpenSeq(ISequentialInStream *stream)) { COM_TRY_BEGIN Close(); @@ -290,8 +284,8 @@ STDMETHODIMP CHandler::OpenSeq(ISequentialInStream *stream) COM_TRY_END } -STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, - Int32 testMode, IArchiveExtractCallback *extractCallback) +Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testMode, IArchiveExtractCallback *extractCallback)) { COM_TRY_BEGIN if (numItems == 0) @@ -299,38 +293,37 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, if (numItems != (UInt32)(Int32)-1 && (numItems != 1 || indices[0] != 0)) return E_INVALIDARG; - // extractCallback->SetTotal(_unpackSize); - + // RINOK(extractCallback->SetTotal(_unpackSize)) + Int32 opRes; + { CMyComPtr realOutStream; - Int32 askMode = testMode ? + const Int32 askMode = testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; - RINOK(extractCallback->GetStream(0, &realOutStream, askMode)); + RINOK(extractCallback->GetStream(0, &realOutStream, askMode)) if (!testMode && !realOutStream) return S_OK; - extractCallback->PrepareOperation(askMode); + RINOK(extractCallback->PrepareOperation(askMode)) - CDummyOutStream *outStreamSpec = new CDummyOutStream; - CMyComPtr outStream(outStreamSpec); - outStreamSpec->SetStream(realOutStream); - outStreamSpec->Init(); - realOutStream.Release(); + CMyComPtr2_Create outStream; + outStream->SetStream(realOutStream); + outStream->Init(); + // realOutStream.Release(); - CLocalProgress *lps = new CLocalProgress; - CMyComPtr progress = lps; + CMyComPtr2_Create lps; lps->Init(extractCallback, false); if (_needSeekToStart) { if (!_inStream) return E_FAIL; - RINOK(_inStream->Seek(0, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekToBegin(_inStream)) } else _needSeekToStart = true; - Int32 opRes = NExtract::NOperationResult::kDataError; + opRes = NExtract::NOperationResult::kDataError; bool isArc = false; bool needMoreInput = false; @@ -351,7 +344,7 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, unpackSize = GetUi32(buffer + 10); if (unpackSize <= kUnpackSizeMax) { - HRESULT result = MslzDec(s, outStream, unpackSize, needMoreInput, progress); + const HRESULT result = MslzDec(s, outStream, unpackSize, needMoreInput, lps); if (result == S_OK) opRes = NExtract::NOperationResult::kOK; else if (result != S_FALSE) @@ -381,14 +374,13 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, opRes = NExtract::NOperationResult::kUnexpectedEnd; else if (_dataAfterEnd) opRes = NExtract::NOperationResult::kDataAfterEnd; - - outStream.Release(); + } return extractCallback->SetOperationResult(opRes); COM_TRY_END } REGISTER_ARC_I( - "MsLZ", "mslz", 0, 0xD5, + "MsLZ", "mslz", NULL, 0xD5, kSignature, 0, 0, diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/MubHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/MubHandler.cpp index 56b8aa32..7363b1b3 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/MubHandler.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/MubHandler.cpp @@ -3,6 +3,7 @@ #include "StdAfx.h" #include "../../../C/CpuArch.h" +#include "../../../C/SwapBytes.h" #include "../../Common/ComTry.h" #include "../../Common/IntToString.h" @@ -15,15 +16,13 @@ #include "HandlerCont.h" -static UInt32 Get32(const Byte *p, bool be) { if (be) return GetBe32(p); return GetUi32(p); } - using namespace NWindows; using namespace NCOM; namespace NArchive { namespace NMub { -#define MACH_CPU_ARCH_ABI64 (1 << 24) +#define MACH_CPU_ARCH_ABI64 ((UInt32)1 << 24) #define MACH_CPU_TYPE_386 7 #define MACH_CPU_TYPE_ARM 12 #define MACH_CPU_TYPE_SPARC 14 @@ -31,8 +30,9 @@ namespace NMub { #define MACH_CPU_TYPE_PPC64 (MACH_CPU_ARCH_ABI64 | MACH_CPU_TYPE_PPC) #define MACH_CPU_TYPE_AMD64 (MACH_CPU_ARCH_ABI64 | MACH_CPU_TYPE_386) +#define MACH_CPU_TYPE_ARM64 (MACH_CPU_ARCH_ABI64 | MACH_CPU_TYPE_ARM) -#define MACH_CPU_SUBTYPE_LIB64 (1 << 31) +#define MACH_CPU_SUBTYPE_LIB64 ((UInt32)1 << 31) #define MACH_CPU_SUBTYPE_I386_ALL 3 @@ -42,13 +42,15 @@ struct CItem UInt32 SubType; UInt32 Offset; UInt32 Size; - // UInt32 Align; + UInt32 Align; }; -static const UInt32 kNumFilesMax = 10; +static const UInt32 kNumFilesMax = 6; -class CHandler: public CHandlerCont +Z7_class_CHandler_final: public CHandlerCont { + Z7_IFACE_COM7_IMP(IInArchive_Cont) + // UInt64 _startPos; UInt64 _phySize; UInt32 _numItems; @@ -57,16 +59,13 @@ class CHandler: public CHandlerCont HRESULT Open2(IInStream *stream); - virtual int GetItem_ExtractInfo(UInt32 index, UInt64 &pos, UInt64 &size) const + virtual int GetItem_ExtractInfo(UInt32 index, UInt64 &pos, UInt64 &size) const Z7_override { const CItem &item = _items[index]; pos = item.Offset; size = item.Size; return NExtract::NOperationResult::kOK; } - -public: - INTERFACE_IInArchive_Cont(;) }; static const Byte kArcProps[] = @@ -76,13 +75,16 @@ static const Byte kArcProps[] = static const Byte kProps[] = { - kpidSize + kpidPath, + kpidSize, + kpidOffset, + kpidClusterSize // Align }; IMP_IInArchive_Props IMP_IInArchive_ArcProps -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { PropVariant_Clear(value); switch (propID) @@ -93,7 +95,7 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) return S_OK; } -STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) { PropVariant_Clear(value); const CItem &item = _items[index]; @@ -102,32 +104,36 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val case kpidExtension: { char temp[32]; - const char *ext = 0; + const char *ext = NULL; switch (item.Type) { - case MACH_CPU_TYPE_386: ext = "x86"; break; + case MACH_CPU_TYPE_386: ext = "x86"; break; case MACH_CPU_TYPE_ARM: ext = "arm"; break; case MACH_CPU_TYPE_SPARC: ext = "sparc"; break; case MACH_CPU_TYPE_PPC: ext = "ppc"; break; - case MACH_CPU_TYPE_PPC64: ext = "ppc64"; break; case MACH_CPU_TYPE_AMD64: ext = "x64"; break; + case MACH_CPU_TYPE_ARM64: ext = "arm64"; break; + case MACH_CPU_TYPE_PPC64: ext = "ppc64"; break; default: temp[0] = 'c'; temp[1] = 'p'; temp[2] = 'u'; - ConvertUInt32ToString(item.Type, temp + 3); + char *p = ConvertUInt32ToString(item.Type & ~MACH_CPU_ARCH_ABI64, temp + 3); + if (item.Type & MACH_CPU_ARCH_ABI64) + MyStringCopy(p, "_64"); break; } if (ext) - strcpy(temp, ext); - if (item.SubType != 0 && ( - item.Type != MACH_CPU_TYPE_386 && - item.Type != MACH_CPU_TYPE_AMD64 || - (item.SubType & ~(UInt32)MACH_CPU_SUBTYPE_LIB64) != MACH_CPU_SUBTYPE_I386_ALL)) + MyStringCopy(temp, ext); + if (item.SubType != 0) + if ((item.Type != MACH_CPU_TYPE_386 && + item.Type != MACH_CPU_TYPE_AMD64) + || (item.SubType & ~(UInt32)MACH_CPU_SUBTYPE_LIB64) != MACH_CPU_SUBTYPE_I386_ALL + ) { unsigned pos = MyStringLen(temp); temp[pos++] = '-'; - ConvertUInt32ToString(item.SubType, temp + pos); + ConvertUInt32ToString(item.SubType, temp + pos); } return PropVarEm_Set_Str(value, temp); } @@ -135,32 +141,45 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val case kpidPackSize: PropVarEm_Set_UInt64(value, item.Size); break; + case kpidOffset: + PropVarEm_Set_UInt64(value, item.Offset); + break; + case kpidClusterSize: + PropVarEm_Set_UInt32(value, (UInt32)1 << item.Align); + break; } return S_OK; } HRESULT CHandler::Open2(IInStream *stream) { - // RINOK(stream->Seek(0, STREAM_SEEK_CUR, &_startPos)); + // RINOK(InStream_GetPos(stream, _startPos)); - const UInt32 kHeaderSize = 8; - const UInt32 kRecordSize = 5 * 4; + const UInt32 kHeaderSize = 2; + const UInt32 kRecordSize = 5; const UInt32 kBufSize = kHeaderSize + kNumFilesMax * kRecordSize; - Byte buf[kBufSize]; - size_t processed = kBufSize; - RINOK(ReadStream(stream, buf, &processed)); + UInt32 buf[kBufSize]; + size_t processed = kBufSize * 4; + RINOK(ReadStream(stream, buf, &processed)) + processed >>= 2; if (processed < kHeaderSize) return S_FALSE; bool be; - switch (GetBe32(buf)) + switch (buf[0]) { - case 0xCAFEBABE: be = true; break; - case 0xB9FAF10E: be = false; break; + case Z7_CONV_BE_TO_NATIVE_CONST32(0xCAFEBABE): be = true; break; + case Z7_CONV_BE_TO_NATIVE_CONST32(0xB9FAF10E): be = false; break; default: return S_FALSE; } _bigEndian = be; - UInt32 num = Get32(buf + 4, be); + if ( + #if defined(MY_CPU_BE) + ! + #endif + be) + z7_SwapBytes4(&buf[1], processed - 1); + const UInt32 num = buf[1]; if (num > kNumFilesMax || processed < kHeaderSize + num * kRecordSize) return S_FALSE; if (num == 0) @@ -169,13 +188,14 @@ HRESULT CHandler::Open2(IInStream *stream) for (UInt32 i = 0; i < num; i++) { - const Byte *p = buf + kHeaderSize + i * kRecordSize; + const UInt32 *p = buf + kHeaderSize + i * kRecordSize; CItem &sb = _items[i]; - sb.Type = Get32(p, be); - sb.SubType = Get32(p + 4, be); - sb.Offset = Get32(p + 8, be); - sb.Size = Get32(p + 12, be); - UInt32 align = Get32(p + 16, be); + sb.Type = p[0]; + sb.SubType = p[1]; + sb.Offset = p[2]; + sb.Size = p[3]; + const UInt32 align = p[4]; + sb.Align = align; if (align > 31) return S_FALSE; if (sb.Offset < kHeaderSize + num * kRecordSize) @@ -184,7 +204,7 @@ HRESULT CHandler::Open2(IInStream *stream) (sb.SubType & ~MACH_CPU_SUBTYPE_LIB64) >= 0x100) return S_FALSE; - UInt64 endPos = (UInt64)sb.Offset + sb.Size; + const UInt64 endPos = (UInt64)sb.Offset + sb.Size; if (endPosMax < endPos) endPosMax = endPos; } @@ -193,9 +213,9 @@ HRESULT CHandler::Open2(IInStream *stream) return S_OK; } -STDMETHODIMP CHandler::Open(IInStream *inStream, +Z7_COM7F_IMF(CHandler::Open(IInStream *inStream, const UInt64 * /* maxCheckStartPosition */, - IArchiveOpenCallback * /* openArchiveCallback */) + IArchiveOpenCallback * /* openArchiveCallback */)) { COM_TRY_BEGIN Close(); @@ -210,7 +230,7 @@ STDMETHODIMP CHandler::Open(IInStream *inStream, COM_TRY_END } -STDMETHODIMP CHandler::Close() +Z7_COM7F_IMF(CHandler::Close()) { _stream.Release(); _numItems = 0; @@ -218,7 +238,7 @@ STDMETHODIMP CHandler::Close() return S_OK; } -STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) { *numItems = _numItems; return S_OK; @@ -231,7 +251,7 @@ static const Byte k_Signature[] = { 4, 0xB9, 0xFA, 0xF1, 0x0E }; REGISTER_ARC_I( - "Mub", "mub", 0, 0xE2, + "Mub", "mub", NULL, 0xE2, k_Signature, 0, NArcInfoFlags::kMultiSignature, diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Nsis/NsisDecode.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/Nsis/NsisDecode.cpp index daf3df6e..044de37f 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Nsis/NsisDecode.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Nsis/NsisDecode.cpp @@ -11,13 +11,24 @@ #include "../../Common/MethodId.h" #include "../../Compress/BcjCoder.h" -#include "../../Compress/BZip2Decoder.h" #define Get32(p) GetUi32(p) namespace NArchive { namespace NNsis { +UInt64 CDecoder::GetInputProcessedSize() const +{ + if (_lzmaDecoder) + return _lzmaDecoder->GetInputProcessedSize(); + if (_deflateDecoder) + return _deflateDecoder->GetInputProcessedSize(); + if (_bzDecoder) + return _bzDecoder->GetInputProcessedSize(); + return 0; +} + + HRESULT CDecoder::Init(ISequentialInStream *inStream, bool &useFilter) { useFilter = false; @@ -29,14 +40,17 @@ HRESULT CDecoder::Init(ISequentialInStream *inStream, bool &useFilter) if (!_codecInStream) { - switch (Method) + switch ((int)Method) { // case NMethodType::kCopy: return E_NOTIMPL; case NMethodType::kDeflate: _deflateDecoder = new NCompress::NDeflate::NDecoder::CCOMCoder(); _codecInStream = _deflateDecoder; break; - case NMethodType::kBZip2: _codecInStream = new NCompress::NBZip2::CNsisDecoder(); break; + case NMethodType::kBZip2: + _bzDecoder = new NCompress::NBZip2::CNsisDecoder(); + _codecInStream = _bzDecoder; + break; case NMethodType::kLZMA: _lzmaDecoder = new NCompress::NLzma::CDecoder(); _codecInStream = _lzmaDecoder; @@ -51,7 +65,7 @@ HRESULT CDecoder::Init(ISequentialInStream *inStream, bool &useFilter) if (FilterFlag) { Byte flag; - RINOK(ReadStream_FALSE(inStream, &flag, 1)); + RINOK(ReadStream_FALSE(inStream, &flag, 1)) if (flag > 1) return E_NOTIMPL; useFilter = (flag != 0); @@ -65,9 +79,9 @@ HRESULT CDecoder::Init(ISequentialInStream *inStream, bool &useFilter) { _filter = new CFilterCoder(false); _filterInStream = _filter; - _filter->Filter = new NCompress::NBcj::CCoder(false); + _filter->Filter = new NCompress::NBcj::CCoder2(z7_BranchConvSt_X86_Dec); } - RINOK(_filter->SetInStream(_codecInStream)); + RINOK(_filter->SetInStream(_codecInStream)) _decoderInStream = _filterInStream; } @@ -75,8 +89,8 @@ HRESULT CDecoder::Init(ISequentialInStream *inStream, bool &useFilter) { const unsigned kPropsSize = LZMA_PROPS_SIZE; Byte props[kPropsSize]; - RINOK(ReadStream_FALSE(inStream, props, kPropsSize)); - RINOK(_lzmaDecoder->SetDecoderProperties2((const Byte *)props, kPropsSize)); + RINOK(ReadStream_FALSE(inStream, props, kPropsSize)) + RINOK(_lzmaDecoder->SetDecoderProperties2((const Byte *)props, kPropsSize)) } { @@ -84,7 +98,7 @@ HRESULT CDecoder::Init(ISequentialInStream *inStream, bool &useFilter) _codecInStream.QueryInterface(IID_ICompressSetInStream, &setInStream); if (!setInStream) return E_NOTIMPL; - RINOK(setInStream->SetInStream(inStream)); + RINOK(setInStream->SetInStream(inStream)) } { @@ -92,44 +106,43 @@ HRESULT CDecoder::Init(ISequentialInStream *inStream, bool &useFilter) _codecInStream.QueryInterface(IID_ICompressSetOutStreamSize, &setOutStreamSize); if (!setOutStreamSize) return E_NOTIMPL; - RINOK(setOutStreamSize->SetOutStreamSize(NULL)); + RINOK(setOutStreamSize->SetOutStreamSize(NULL)) } if (useFilter) { - RINOK(_filter->SetOutStreamSize(NULL)); + RINOK(_filter->SetOutStreamSize(NULL)) } return S_OK; } + static const UInt32 kMask_IsCompressed = (UInt32)1 << 31; + HRESULT CDecoder::SetToPos(UInt64 pos, ICompressProgressInfo *progress) { if (StreamPos > pos) return E_FAIL; - UInt64 inSizeStart = 0; - if (_lzmaDecoder) - inSizeStart = _lzmaDecoder->GetInputProcessedSize(); + const UInt64 inSizeStart = GetInputProcessedSize(); UInt64 offset = 0; while (StreamPos < pos) { size_t size = (size_t)MyMin(pos - StreamPos, (UInt64)Buffer.Size()); - RINOK(Read(Buffer, &size)); + RINOK(Read(Buffer, &size)) if (size == 0) return S_FALSE; StreamPos += size; offset += size; - UInt64 inSize = 0; - if (_lzmaDecoder) - inSize = _lzmaDecoder->GetInputProcessedSize() - inSizeStart; - RINOK(progress->SetRatioInfo(&inSize, &offset)); + const UInt64 inSize = GetInputProcessedSize() - inSizeStart; + RINOK(progress->SetRatioInfo(&inSize, &offset)) } return S_OK; } + HRESULT CDecoder::Decode(CByteBuffer *outBuf, bool unpackSizeDefined, UInt32 unpackSize, ISequentialOutStream *realOutStream, ICompressProgressInfo *progress, UInt32 &packSizeRes, UInt32 &unpackSizeRes) @@ -143,10 +156,10 @@ HRESULT CDecoder::Decode(CByteBuffer *outBuf, bool unpackSizeDefined, UInt32 unp { Byte temp[4]; size_t processedSize = 4; - RINOK(Read(temp, &processedSize)); + RINOK(Read(temp, &processedSize)) + StreamPos += processedSize; if (processedSize != 4) return S_FALSE; - StreamPos += processedSize; UInt32 size = Get32(temp); if (unpackSizeDefined && size != unpackSize) return S_FALSE; @@ -156,8 +169,13 @@ HRESULT CDecoder::Decode(CByteBuffer *outBuf, bool unpackSizeDefined, UInt32 unp else { Byte temp[4]; - RINOK(ReadStream_FALSE(InputStream, temp, 4)); - StreamPos += 4; + { + size_t processedSize = 4; + RINOK(ReadStream(InputStream, temp, &processedSize)) + StreamPos += processedSize; + if (processedSize != 4) + return S_FALSE; + } UInt32 size = Get32(temp); if ((size & kMask_IsCompressed) == 0) @@ -174,7 +192,7 @@ HRESULT CDecoder::Decode(CByteBuffer *outBuf, bool unpackSizeDefined, UInt32 unp { UInt32 curSize = (UInt32)MyMin((size_t)size, Buffer.Size()); UInt32 processedSize; - RINOK(InputStream->Read(Buffer, curSize, &processedSize)); + RINOK(InputStream->Read(Buffer, curSize, &processedSize)) if (processedSize == 0) return S_FALSE; if (outBuf) @@ -184,8 +202,8 @@ HRESULT CDecoder::Decode(CByteBuffer *outBuf, bool unpackSizeDefined, UInt32 unp StreamPos += processedSize; unpackSizeRes += processedSize; if (realOutStream) - RINOK(WriteStream(realOutStream, Buffer, processedSize)); - RINOK(progress->SetRatioInfo(&offset, &offset)); + RINOK(WriteStream(realOutStream, Buffer, processedSize)) + RINOK(progress->SetRatioInfo(&offset, &offset)) } return S_OK; @@ -199,7 +217,7 @@ HRESULT CDecoder::Decode(CByteBuffer *outBuf, bool unpackSizeDefined, UInt32 unp limitedStreamSpec->Init(size); { bool useFilter; - RINOK(Init(limitedStream, useFilter)); + RINOK(Init(limitedStream, useFilter)) } } @@ -209,9 +227,7 @@ HRESULT CDecoder::Decode(CByteBuffer *outBuf, bool unpackSizeDefined, UInt32 unp outBuf->Alloc(unpackSize); } - UInt64 inSizeStart = 0; - if (_lzmaDecoder) - inSizeStart = _lzmaDecoder->GetInputProcessedSize(); + const UInt64 inSizeStart = GetInputProcessedSize(); // we don't allow files larger than 4 GB; if (!unpackSizeDefined) @@ -228,7 +244,7 @@ HRESULT CDecoder::Decode(CByteBuffer *outBuf, bool unpackSizeDefined, UInt32 unp size_t size = Buffer.Size(); if (size > rem) size = rem; - RINOK(Read(Buffer, &size)); + RINOK(Read(Buffer, &size)) if (size == 0) { if (unpackSizeDefined) @@ -254,15 +270,14 @@ HRESULT CDecoder::Decode(CByteBuffer *outBuf, bool unpackSizeDefined, UInt32 unp StreamPos += size; offset += (UInt32)size; - UInt64 inSize = 0; // it can be improved: we need inSize for Deflate and BZip2 too. - if (_lzmaDecoder) - inSize = _lzmaDecoder->GetInputProcessedSize() - inSizeStart; + const UInt64 inSize = GetInputProcessedSize() - inSizeStart; + if (Solid) packSizeRes = (UInt32)inSize; unpackSizeRes += (UInt32)size; UInt64 outSize = offset; - RINOK(progress->SetRatioInfo(&inSize, &outSize)); + RINOK(progress->SetRatioInfo(&inSize, &outSize)) if (realOutStream) { res = WriteStream(realOutStream, Buffer, size); diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Nsis/NsisDecode.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Nsis/NsisDecode.h index ec713d05..1d08d018 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Nsis/NsisDecode.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Nsis/NsisDecode.h @@ -1,13 +1,14 @@ // NsisDecode.h -#ifndef __NSIS_DECODE_H -#define __NSIS_DECODE_H +#ifndef ZIP7_INC_NSIS_DECODE_H +#define ZIP7_INC_NSIS_DECODE_H #include "../../../Common/MyBuffer.h" #include "../../Common/FilterCoder.h" #include "../../Common/StreamUtils.h" +#include "../../Compress/BZip2Decoder.h" #include "../../Compress/DeflateDecoder.h" #include "../../Compress/LzmaDecoder.h" @@ -38,6 +39,7 @@ class CDecoder CMyComPtr _codecInStream; CMyComPtr _decoderInStream; + NCompress::NBZip2::CNsisDecoder *_bzDecoder; NCompress::NDeflate::NDecoder::CCOMCoder *_deflateDecoder; NCompress::NLzma::CDecoder *_lzmaDecoder; @@ -50,13 +52,17 @@ class CDecoder bool Solid; bool IsNsisDeflate; - CByteBuffer Buffer; // temp buf. + CByteBuffer Buffer; // temp buf CDecoder(): FilterFlag(false), Solid(true), IsNsisDeflate(true) - {} + { + _bzDecoder = NULL; + _deflateDecoder = NULL; + _lzmaDecoder = NULL; + } void Release() { @@ -64,13 +70,19 @@ class CDecoder _codecInStream.Release(); _decoderInStream.Release(); InputStream.Release(); + + _bzDecoder = NULL; + _deflateDecoder = NULL; _lzmaDecoder = NULL; } + + UInt64 GetInputProcessedSize() const; HRESULT Init(ISequentialInStream *inStream, bool &useFilter); + HRESULT Read(void *data, size_t *processedSize) { - return ReadStream(_decoderInStream, data, processedSize);; + return ReadStream(_decoderInStream, data, processedSize); } diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Nsis/NsisHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/Nsis/NsisHandler.cpp index 971c8464..7ce2e8e5 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Nsis/NsisHandler.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Nsis/NsisHandler.cpp @@ -23,8 +23,8 @@ using namespace NWindows; namespace NArchive { namespace NNsis { -static const char *kBcjMethod = "BCJ"; -static const char *kUnknownMethod = "Unknown"; +#define kBcjMethod "BCJ" +#define kUnknownMethod "Unknown" static const char * const kMethods[] = { @@ -50,6 +50,7 @@ static const Byte kArcProps[] = { kpidMethod, kpidSolid, + kpidBit64, kpidHeadersSize, kpidEmbeddedStubSize, kpidSubType @@ -60,22 +61,19 @@ IMP_IInArchive_Props IMP_IInArchive_ArcProps -static AString UInt32ToString(UInt32 val) +static void AddDictProp(AString &s, UInt32 val) { - char s[16]; - ConvertUInt32ToString(val, s); - return s; -} - -static AString GetStringForSizeValue(UInt32 val) -{ - for (int i = 31; i >= 0; i--) + for (unsigned i = 0; i < 32; i++) if (((UInt32)1 << i) == val) - return UInt32ToString(i); + { + s.Add_UInt32(i); + return; + } char c = 'b'; if ((val & ((1 << 20) - 1)) == 0) { val >>= 20; c = 'm'; } else if ((val & ((1 << 10) - 1)) == 0) { val >>= 10; c = 'k'; } - return UInt32ToString(val) + c; + s.Add_UInt32(val); + s.Add_Char(c); } static AString GetMethod(bool useFilter, NMethodType::EEnum method, UInt32 dict) @@ -86,11 +84,11 @@ static AString GetMethod(bool useFilter, NMethodType::EEnum method, UInt32 dict) s += kBcjMethod; s.Add_Space(); } - s += ((unsigned)method < ARRAY_SIZE(kMethods)) ? kMethods[(unsigned)method] : kUnknownMethod; + s += ((unsigned)method < Z7_ARRAY_SIZE(kMethods)) ? kMethods[(unsigned)method] : kUnknownMethod; if (method == NMethodType::kLZMA) { - s += ':'; - s += GetStringForSizeValue(dict); + s.Add_Colon(); + AddDictProp(s, dict); } return s; } @@ -104,17 +102,17 @@ AString CHandler::GetMethod(NMethodType::EEnum method, bool useItemFilter, UInt3 s += kBcjMethod; s.Add_Space(); } - s += (method < ARRAY_SIZE(kMethods)) ? kMethods[method] : kUnknownMethod; + s += (method < Z7_ARRAY_SIZE(kMethods)) ? kMethods[method] : kUnknownMethod; if (method == NMethodType::kLZMA) { - s += ':'; + s.Add_Colon(); s += GetStringForSizeValue(_archive.IsSolid ? _archive.DictionarySize: dictionary); } return s; } */ -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; @@ -123,7 +121,7 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) // case kpidCodePage: if (_archive.IsUnicode) prop = "UTF-16"; break; case kpidSubType: { - AString s = _archive.GetFormatDescription(); + AString s (_archive.GetFormatDescription()); if (!_archive.IsInstaller) { s.Add_Space_if_NotEmpty(); @@ -134,6 +132,7 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) break; } + case kpidBit64: if (_archive.Is64Bit) prop = true; break; case kpidMethod: prop = _methodString; break; case kpidSolid: prop = _archive.IsSolid; break; case kpidOffset: prop = _archive.StartOffset; break; @@ -160,7 +159,7 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) if (!_archive.IsInstaller) { if (!s.IsEmpty()) - s += '.'; + s.Add_Dot(); s += "Uninstall"; } #endif @@ -188,7 +187,7 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) } -STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *maxCheckStartPosition, IArchiveOpenCallback * /* openArchiveCallback */) +Z7_COM7F_IMF(CHandler::Open(IInStream *stream, const UInt64 *maxCheckStartPosition, IArchiveOpenCallback * /* openArchiveCallback */)) { COM_TRY_BEGIN Close(); @@ -213,18 +212,18 @@ STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *maxCheckStartPositi COM_TRY_END } -STDMETHODIMP CHandler::Close() +Z7_COM7F_IMF(CHandler::Close()) { _archive.Clear(); _archive.Release(); return S_OK; } -STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) { *numItems = _archive.Items.Size() #ifdef NSIS_SCRIPT - + 1 + _archive.LicenseFiles.Size(); + + 1 + _archive.LicenseFiles.Size() #endif ; return S_OK; @@ -238,7 +237,7 @@ bool CHandler::GetUncompressedSize(unsigned index, UInt32 &size) const size = item.Size; else if (_archive.IsSolid && item.EstimatedSize_Defined) size = item.EstimatedSize; - else + else if (!item.IsEmptyFile) return false; return true; } @@ -270,7 +269,7 @@ bool CHandler::GetCompressedSize(unsigned index, UInt32 &size) const } -STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; @@ -308,7 +307,7 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val case kpidOffset: prop = item.Pos; break; case kpidPath: { - UString s = NItemName::WinNameToOSName(_archive.GetReducedName(index)); + UString s = NItemName::WinPathToOsPath(_archive.GetReducedName(index)); if (!s.IsEmpty()) prop = (const wchar_t *)s; break; @@ -358,21 +357,21 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val } -static bool UninstallerPatch(const Byte *p, size_t size, CByteBuffer &dest) +static bool UninstallerPatch(const Byte *p, size_t size, Byte *dest, size_t destSize) { for (;;) { if (size < 4) return false; - UInt32 len = Get32(p); + const UInt32 len = Get32(p); if (len == 0) return size == 4; if (size < 8) return false; - UInt32 offs = Get32(p + 4); + const UInt32 offs = Get32(p + 4); p += 8; size -= 8; - if (size < len || offs > dest.Size() || len > dest.Size() - offs) + if (size < len || offs > destSize || len > destSize - offs) return false; memcpy(dest + offs, p, len); p += len; @@ -381,11 +380,11 @@ static bool UninstallerPatch(const Byte *p, size_t size, CByteBuffer &dest) } -STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, - Int32 testMode, IArchiveExtractCallback *extractCallback) +Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testMode, IArchiveExtractCallback *extractCallback)) { COM_TRY_BEGIN - bool allFilesMode = (numItems == (UInt32)(Int32)-1); + const bool allFilesMode = (numItems == (UInt32)(Int32)-1); if (allFilesMode) GetNumberOfItems(&numItems); if (numItems == 0) @@ -397,7 +396,7 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, UInt32 i; for (i = 0; i < numItems; i++) { - UInt32 index = (allFilesMode ? i : indices[i]); + const UInt32 index = (allFilesMode ? i : indices[i]); #ifdef NSIS_SCRIPT if (index >= _archive.Items.Size()) @@ -428,14 +427,13 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, extractCallback->SetTotal(totalSize + solidPosMax); - CLocalProgress *lps = new CLocalProgress; - CMyComPtr progress = lps; + CMyComPtr2_Create lps; lps->Init(extractCallback, !_archive.IsSolid); if (_archive.IsSolid) { - RINOK(_archive.SeekTo_DataStreamOffset()); - RINOK(_archive.InitDecoder()); + RINOK(_archive.SeekTo_DataStreamOffset()) + RINOK(_archive.InitDecoder()) _archive.Decoder.StreamPos = 0; } @@ -474,16 +472,16 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, curPacked = 0; curUnpacked = 0; - RINOK(lps->SetCur()); + RINOK(lps->SetCur()) - // RINOK(extractCallback->SetCompleted(¤tTotalSize)); + // RINOK(extractCallback->SetCompleted(¤tTotalSize)) CMyComPtr realOutStream; - Int32 askMode = testMode ? + const Int32 askMode = testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; - UInt32 index = allFilesMode ? i : indices[i]; + const UInt32 index = allFilesMode ? i : indices[i]; - RINOK(extractCallback->GetStream(index, &realOutStream, askMode)); + RINOK(extractCallback->GetStream(index, &realOutStream, askMode)) bool dataError = false; @@ -509,9 +507,9 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, curUnpacked = size; if (!testMode && !realOutStream) continue; - RINOK(extractCallback->PrepareOperation(askMode)); + RINOK(extractCallback->PrepareOperation(askMode)) if (realOutStream) - RINOK(WriteStream(realOutStream, data, size)); + RINOK(WriteStream(realOutStream, data, size)) } else #endif @@ -524,15 +522,20 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, if (!testMode && !realOutStream) continue; - RINOK(extractCallback->PrepareOperation(askMode)); + RINOK(extractCallback->PrepareOperation(askMode)) dataError = solidDataError; - bool needDecompress = !solidDataError; - if (needDecompress) + bool needDecompress = false; + + if (!item.IsEmptyFile) { - if (testMode && _archive.IsSolid && _archive.GetPosOfSolidItem(index) == prevPos) - needDecompress = false; + needDecompress = !solidDataError; + if (needDecompress) + { + if (testMode && _archive.IsSolid && _archive.GetPosOfSolidItem(index) == prevPos) + needDecompress = false; + } } if (needDecompress) @@ -542,7 +545,7 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, if (!_archive.IsSolid) { - RINOK(_archive.SeekToNonSolidItem(index)); + RINOK(_archive.SeekToNonSolidItem(index)) } else { @@ -555,7 +558,7 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, } else { - HRESULT res = _archive.Decoder.SetToPos(pos, progress); + HRESULT res = _archive.Decoder.SetToPos(pos, lps); if (res != S_OK) { if (res != S_FALSE) @@ -564,10 +567,11 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, } else if (!testMode && i + 1 < numItems) { - UInt32 next = allFilesMode ? i + 1 : indices[i + 1]; + const UInt32 next = allFilesMode ? i + 1 : indices[i + 1]; if (next < _archive.Items.Size()) { - UInt64 nextPos = _archive.GetPosOfSolidItem(next); + // next cannot be IsEmptyFile + const UInt64 nextPos = _archive.GetPosOfSolidItem(next); if (nextPos == pos) { writeToTemp = true; @@ -579,12 +583,16 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, prevPos = pos; } + /* nsis 3.08 can use (PatchSize == 0) for uninstaller without patched section */ + + const bool is_PatchedUninstaller = item.Is_PatchedUninstaller(); + if (!dataError) { // UInt32 unpackSize = 0; // bool unpackSize_Defined = false; bool writeToTemp1 = writeToTemp; - if (item.IsUninstaller) + if (is_PatchedUninstaller) { // unpackSize = item.PatchSize; // unpackSize_Defined = true; @@ -601,17 +609,17 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, if (readFromTemp) { - if (realOutStream && !item.IsUninstaller) - RINOK(WriteStream(realOutStream, tempBuf, tempBuf.Size())); + if (realOutStream && !is_PatchedUninstaller) + RINOK(WriteStream(realOutStream, tempBuf, tempBuf.Size())) } else { UInt32 curUnpacked32 = 0; - HRESULT res = _archive.Decoder.Decode( + const HRESULT res = _archive.Decoder.Decode( writeToTemp1 ? &tempBuf : NULL, - item.IsUninstaller, item.PatchSize, - item.IsUninstaller ? NULL : (ISequentialOutStream *)realOutStream, - progress, + is_PatchedUninstaller, item.PatchSize, + is_PatchedUninstaller ? NULL : (ISequentialOutStream *)realOutStream, + lps, curPacked, curUnpacked32); curUnpacked = curUnpacked32; if (_archive.IsSolid) @@ -627,31 +635,36 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, } } - if (!dataError && item.IsUninstaller) + if (!dataError && is_PatchedUninstaller) { if (_archive.ExeStub.Size() != 0) { CByteBuffer destBuf = _archive.ExeStub; - dataError = !UninstallerPatch(tempBuf, tempBuf.Size(), destBuf); - + dataError = !UninstallerPatch(tempBuf, tempBuf.Size(), destBuf, destBuf.Size()); if (realOutStream) - RINOK(WriteStream(realOutStream, destBuf, destBuf.Size())); + RINOK(WriteStream(realOutStream, destBuf, destBuf.Size())) } if (readFromTemp) { if (realOutStream) - RINOK(WriteStream(realOutStream, tempBuf2, tempBuf2.Size())); + RINOK(WriteStream(realOutStream, tempBuf2, tempBuf2.Size())) } else { UInt32 curPacked2 = 0; UInt32 curUnpacked2 = 0; - HRESULT res = _archive.Decoder.Decode( + + if (!_archive.IsSolid) + { + RINOK(_archive.SeekTo(_archive.GetPosOfNonSolidItem(index) + 4 + curPacked )) + } + + const HRESULT res = _archive.Decoder.Decode( writeToTemp ? &tempBuf2 : NULL, false, 0, realOutStream, - progress, + lps, curPacked2, curUnpacked2); curPacked += curPacked2; if (!_archive.IsSolid) @@ -671,7 +684,7 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, realOutStream.Release(); RINOK(extractCallback->SetOperationResult(dataError ? NExtract::NOperationResult::kDataError : - NExtract::NOperationResult::kOK)); + NExtract::NOperationResult::kOK)) } return S_OK; COM_TRY_END diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Nsis/NsisHandler.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Nsis/NsisHandler.h index 1eb8b731..bb90bfe0 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Nsis/NsisHandler.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Nsis/NsisHandler.h @@ -1,7 +1,7 @@ // NSisHandler.h -#ifndef __NSIS_HANDLER_H -#define __NSIS_HANDLER_H +#ifndef ZIP7_INC_NSIS_HANDLER_H +#define ZIP7_INC_NSIS_HANDLER_H #include "../../../Common/MyCom.h" @@ -14,10 +14,8 @@ namespace NArchive { namespace NNsis { -class CHandler: - public IInArchive, - public CMyUnknownImp -{ +Z7_CLASS_IMP_CHandler_IInArchive_0 + CInArchive _archive; AString _methodString; @@ -25,10 +23,6 @@ class CHandler: bool GetCompressedSize(unsigned index, UInt32 &size) const; // AString GetMethod(NMethodType::EEnum method, bool useItemFilter, UInt32 dictionary) const; -public: - MY_UNKNOWN_IMP1(IInArchive) - - INTERFACE_IInArchive(;) }; }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Nsis/NsisIn.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/Nsis/NsisIn.cpp index a3bfcaec..577062ef 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Nsis/NsisIn.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Nsis/NsisIn.cpp @@ -6,7 +6,6 @@ #include "../../../Common/StringToInt.h" #include "../../Common/LimitedStreams.h" -#include "../../Common/StreamUtils.h" #include "NsisIn.h" @@ -32,7 +31,7 @@ static const unsigned kCmdSize = 4 + kNumCommandParams * 4; static const char * const kErrorStr = "$_ERROR_STR_"; -#define RINOZ(x) { int __tt = (x); if (__tt != 0) return __tt; } +#define RINOZ(x) { int _tt_ = (x); if (_tt_ != 0) return _tt_; } /* There are several versions of NSIS: @@ -82,14 +81,14 @@ enum EW_READENVSTR, // ReadEnvStr, ExpandEnvStrings EW_INTCMP, // IntCmp, IntCmpU EW_INTOP, // IntOp - EW_INTFMT, // IntFmt + EW_INTFMT, // IntFmt/Int64Fmt EW_PUSHPOP, // Push/Pop/Exchange EW_FINDWINDOW, // FindWindow EW_SENDMESSAGE, // SendMessage EW_ISWINDOW, // IsWindow EW_GETDLGITEM, // GetDlgItem EW_SETCTLCOLORS, // SetCtlColors - EW_SETBRANDINGIMAGE, // SetBrandingImage + EW_SETBRANDINGIMAGE, // SetBrandingImage / LoadAndSetImage EW_CREATEFONT, // CreateFont EW_SHOWWINDOW, // ShowWindow, EnableWindow, HideWindow EW_SHELLEXEC, // ExecShell @@ -131,9 +130,16 @@ enum EW_SECTIONSET, // Get*, Set* EW_INSTTYPESET, // InstTypeSetText, InstTypeGetText, SetCurInstType, GetCurInstType + /* + // before v3.06 nsis it was so: // instructions not actually implemented in exehead, but used in compiler. EW_GETLABELADDR, // both of these get converted to EW_ASSIGNVAR EW_GETFUNCTIONADDR, + */ + + // v3.06 and later it was changed to: + EW_GETOSINFO, + EW_RESERVEDOPCODE, EW_LOCKWINDOW, // LockWindow @@ -141,6 +147,13 @@ enum EW_FPUTWS, // FileWriteUTF16LE, FileWriteWord EW_FGETWS, // FileReadUTF16LE, FileReadWord + /* + // since v3.06 the fllowing IDs codes was moved here: + // Opcodes listed here are not actually used in exehead. No exehead opcodes should be present after these! + EW_GETLABELADDR, // --> EW_ASSIGNVAR + EW_GETFUNCTIONADDR, // --> EW_ASSIGNVAR + */ + // The following IDs are not IDs in real order. // We just need some IDs to translate eny extended layout to main layout. @@ -155,7 +168,7 @@ enum kNumCmds }; -static const unsigned kNumAdditionalParkCmds = 3; + struct CCommandInfo { @@ -177,7 +190,7 @@ static const CCommandInfo k_Commands[kNumCmds] = { 2 }, // "SetFileAttributes" }, { 3 }, // CreateDirectory, SetOutPath { 3 }, // "IfFileExists" }, - { 3 }, // SetRebootFlag, ... + { 4 }, // SetRebootFlag, ... { 4 }, // "If" }, // IfAbort, IfSilent, IfErrors, IfRebootFlag { 2 }, // "Get" }, // GetInstDirError, GetErrorLevel { 4 }, // "Rename" }, @@ -194,20 +207,20 @@ static const CCommandInfo k_Commands[kNumCmds] = { 3 }, // ReadEnvStr, ExpandEnvStrings { 6 }, // "IntCmp" }, { 4 }, // "IntOp" }, - { 3 }, // "IntFmt" }, + { 4 }, // "IntFmt" }, EW_INTFMT { 6 }, // Push, Pop, Exch // it must be 3 params. But some multi-command write garbage. { 5 }, // "FindWindow" }, { 6 }, // "SendMessage" }, { 3 }, // "IsWindow" }, { 3 }, // "GetDlgItem" }, { 2 }, // "SetCtlColors" }, - { 3 }, // "SetBrandingImage" }, + { 4 }, // "SetBrandingImage" } // LoadAndSetImage { 5 }, // "CreateFont" }, { 4 }, // ShowWindow, EnableWindow, HideWindow { 6 }, // "ExecShell" }, { 3 }, // "Exec" }, // Exec, ExecWait { 3 }, // "GetFileTime" }, - { 3 }, // "GetDLLVersion" }, + { 4 }, // "GetDLLVersion" }, { 6 }, // RegDLL, UnRegDLL, CallInstDLL // it must be 5 params. But some multi-command write garbage. { 6 }, // "CreateShortCut" }, { 4 }, // "CopyFiles" }, @@ -229,10 +242,14 @@ static const CCommandInfo k_Commands[kNumCmds] = { 4 }, // "WriteUninstaller" }, { 5 }, // "Section" }, // *** { 4 }, // InstTypeSetText, InstTypeGetText, SetCurInstType, GetCurInstType - { 6 }, // "GetLabelAddr" }, - { 2 }, // "GetFunctionAddress" }, + + // { 6 }, // "GetLabelAddr" }, // before 3.06 + { 6 }, // "GetOsInfo" }, GetKnownFolderPath, ReadMemory, // v3.06+ + + { 2 }, // "GetFunctionAddress" }, // before 3.06 + { 1 }, // "LockWindow" }, - { 3 }, // "FileWrite" }, // FileWriteUTF16LE, FileWriteWord + { 4 }, // "FileWrite" }, // FileWriteUTF16LE, FileWriteWord { 4 }, // "FileRead" }, // FileReadUTF16LE, FileReadWord { 2 }, // "Log" }, // LogSet, LogText @@ -274,9 +291,9 @@ static const char * const k_CommandNames[kNumCmds] = , NULL // StrCpy, GetCurrentAddress , "StrCmp" , NULL // ReadEnvStr, ExpandEnvStrings - , "IntCmp" + , NULL // IntCmp / Int64Cmp / EW_INTCMP , "IntOp" - , "IntFmt" + , NULL // IntFmt / Int64Fmt / EW_INTFMT , NULL // Push, Pop, Exch // it must be 3 params. But some multi-command write garbage. , "FindWindow" , "SendMessage" @@ -311,8 +328,10 @@ static const char * const k_CommandNames[kNumCmds] = , "WriteUninstaller" , "Section" // *** , NULL // InstTypeSetText, InstTypeGetText, SetCurInstType, GetCurInstType - , "GetLabelAddr" + + , NULL // "GetOsInfo" // , "GetLabelAddr" // , "GetFunctionAddress" + , "LockWindow" , "FileWrite" // FileWriteUTF16LE, FileWriteWord , "FileRead" // FileReadUTF16LE, FileReadWord @@ -398,11 +417,9 @@ static const char * const kShellStrings[] = }; -static void UIntToString(AString &s, UInt32 v) +static inline void UIntToString(AString &s, UInt32 v) { - char sz[16]; - ConvertUInt32ToString(v, sz); - s += sz; + s.Add_UInt32(v); } #ifdef NSIS_SCRIPT @@ -448,8 +465,8 @@ void CInArchive::AddLicense(UInt32 param, Int32 langID) } strUsed[param] = 1; - UInt32 start = _stringsPos + (IsUnicode ? param * 2 : param); - UInt32 offset = start + (IsUnicode ? 2 : 1); + const UInt32 start = _stringsPos + (IsUnicode ? param * 2 : param); + const UInt32 offset = start + (IsUnicode ? 2 : 1); { FOR_VECTOR (i, LicenseFiles) { @@ -461,21 +478,21 @@ void CInArchive::AddLicense(UInt32 param, Int32 langID) } } } - AString fileName = "[LICENSE]"; + AString fileName ("[LICENSE]"); if (langID >= 0) { fileName += "\\license-"; // LangId_To_String(fileName, langID); - UIntToString(fileName, langID); + UIntToString(fileName, (UInt32)langID); } else if (++_numRootLicenses > 1) { - fileName += '-'; + fileName.Add_Minus(); UIntToString(fileName, _numRootLicenses); } const Byte *sz = (_data + start); - unsigned marker = IsUnicode ? Get16(sz) : *sz; - bool isRTF = (marker == 2); + const unsigned marker = IsUnicode ? Get16(sz) : *sz; + const bool isRTF = (marker == 2); fileName += isRTF ? ".rtf" : ".txt"; // if (*sz == 1) it's text; Script += fileName; @@ -487,7 +504,7 @@ void CInArchive::AddLicense(UInt32 param, Int32 langID) else { sz += 2; - UInt32 len = GetUi16Str_Len(sz); + const UInt32 len = GetUi16Str_Len(sz); lic.Size = len * 2; if (isRTF) { @@ -508,18 +525,40 @@ void CInArchive::AddLicense(UInt32 param, Int32 langID) #endif -#define kVar_CMDLINE 20 +#ifdef NSIS_SCRIPT +#define Z7_NSIS_WIN_GENERIC_READ ((UInt32)1 << 31) +#endif +#define Z7_NSIS_WIN_GENERIC_WRITE ((UInt32)1 << 30) +#ifdef NSIS_SCRIPT +#define Z7_NSIS_WIN_GENERIC_EXECUTE ((UInt32)1 << 29) +#define Z7_NSIS_WIN_GENERIC_ALL ((UInt32)1 << 28) +#endif + +#ifdef NSIS_SCRIPT +#define Z7_NSIS_WIN_CREATE_NEW 1 +#endif +#define Z7_NSIS_WIN_CREATE_ALWAYS 2 +#ifdef NSIS_SCRIPT +#define Z7_NSIS_WIN_OPEN_EXISTING 3 +#define Z7_NSIS_WIN_OPEN_ALWAYS 4 +#define Z7_NSIS_WIN_TRUNCATE_EXISTING 5 +#endif + + +// #define kVar_CMDLINE 20 #define kVar_INSTDIR 21 #define kVar_OUTDIR 22 #define kVar_EXEDIR 23 -#define kVar_LANGUAGE 24 +// #define kVar_LANGUAGE 24 #define kVar_TEMP 25 #define kVar_PLUGINSDIR 26 #define kVar_EXEPATH 27 // NSIS 2.26+ -#define kVar_EXEFILE 28 // NSIS 2.26+ +// #define kVar_EXEFILE 28 // NSIS 2.26+ #define kVar_HWNDPARENT_225 27 +#ifdef NSIS_SCRIPT #define kVar_HWNDPARENT 29 +#endif // #define kVar__CLICK 30 #define kVar_Spec_OUTDIR_225 29 // NSIS 2.04 - 2.25 @@ -542,9 +581,9 @@ static const char * const kVarStrings[] = , "_OUTDIR" // NSIS 2.04+ }; -static const unsigned kNumInternalVars = 20 + ARRAY_SIZE(kVarStrings); +static const unsigned kNumInternalVars = 20 + Z7_ARRAY_SIZE(kVarStrings); -#define GET_NUM_INTERNAL_VARS (IsNsis200 ? kNumInternalVars - 3 : IsNsis225 ? kNumInternalVars - 2 : kNumInternalVars); +#define GET_NUM_INTERNAL_VARS (IsNsis200 ? kNumInternalVars - 3 : IsNsis225 ? kNumInternalVars - 2 : kNumInternalVars) void CInArchive::GetVar2(AString &res, UInt32 index) { @@ -608,9 +647,9 @@ void CInArchive::AddParam_UInt(UInt32 value) #define NS_CODE_SKIP 252 #define NS_CODE_VAR 253 #define NS_CODE_SHELL 254 -#define NS_CODE_LANG 255 +// #define NS_CODE_LANG 255 -#define NS_3_CODE_LANG 1 +// #define NS_3_CODE_LANG 1 #define NS_3_CODE_SHELL 2 #define NS_3_CODE_VAR 3 #define NS_3_CODE_SKIP 4 @@ -623,7 +662,7 @@ void CInArchive::AddParam_UInt(UInt32 value) #define IS_NS_SPEC_CHAR(c) ((c) >= NS_CODE_SKIP) #define IS_PARK_SPEC_CHAR(c) ((c) >= PARK_CODE_SKIP && (c) <= PARK_CODE_LANG) -#define DECODE_NUMBER_FROM_2_CHARS(c0, c1) (((c0) & 0x7F) | (((unsigned)((c1) & 0x7F)) << 7)) +#define DECODE_NUMBER_FROM_2_CHARS(c0, c1) (((unsigned)(c0) & 0x7F) | (((unsigned)((c1) & 0x7F)) << 7)) #define CONVERT_NUMBER_NS_3_UNICODE(n) n = ((n & 0x7F) | (((n >> 8) & 0x7F) << 7)) #define CONVERT_NUMBER_PARK(n) n &= 0x7FFF @@ -718,7 +757,7 @@ void CInArchive::GetShellString(AString &s, unsigned index1, unsigned index2) } s += '$'; - if (index1 < ARRAY_SIZE(kShellStrings)) + if (index1 < Z7_ARRAY_SIZE(kShellStrings)) { const char *sz = kShellStrings[index1]; if (sz) @@ -727,7 +766,7 @@ void CInArchive::GetShellString(AString &s, unsigned index1, unsigned index2) return; } } - if (index2 < ARRAY_SIZE(kShellStrings)) + if (index2 < Z7_ARRAY_SIZE(kShellStrings)) { const char *sz = kShellStrings[index2]; if (sz) @@ -940,7 +979,7 @@ void CInArchive::GetNsisString_Unicode_Raw(const Byte *p) break; if (c < 0x80) { - Raw_UString += (wchar_t)c; + Raw_UString.Add_Char((char)c); continue; } @@ -963,7 +1002,7 @@ void CInArchive::GetNsisString_Unicode_Raw(const Byte *p) else // if (c == PARK_CODE_LANG) Add_LangStr(Raw_AString, n); } - Raw_UString.AddAscii(Raw_AString); + Raw_UString += Raw_AString.Ptr(); // check it ! continue; } c = n; @@ -1009,7 +1048,7 @@ void CInArchive::GetNsisString_Unicode_Raw(const Byte *p) else // if (c == NS_3_CODE_LANG) Add_LangStr(Raw_AString, n); } - Raw_UString.AddAscii(Raw_AString); + Raw_UString += Raw_AString.Ptr(); } } @@ -1131,7 +1170,7 @@ void CInArchive::ReadString2_Raw(UInt32 pos) Raw_AString.Empty(); Raw_UString.Empty(); if ((Int32)pos < 0) - Add_LangStr(Raw_AString, -((Int32)pos + 1)); + Add_LangStr(Raw_AString, (UInt32)-((Int32)pos + 1)); else if (pos >= NumStringChars) { Raw_AString += kErrorStr; @@ -1145,7 +1184,7 @@ void CInArchive::ReadString2_Raw(UInt32 pos) GetNsisString_Raw(_data + _stringsPos + pos); return; } - Raw_UString.SetFromAscii(Raw_AString); + Raw_UString = Raw_AString.Ptr(); } bool CInArchive::IsGoodString(UInt32 param) const @@ -1341,7 +1380,7 @@ void CInArchive::ReadString2(AString &s, UInt32 pos) { if ((Int32)pos < 0) { - Add_LangStr(s, -((Int32)pos + 1)); + Add_LangStr(s, (UInt32)-((Int32)pos + 1)); return; } @@ -1366,7 +1405,7 @@ void CInArchive::ReadString2(AString &s, UInt32 pos) #ifdef NSIS_SCRIPT -#define DEL_DIR 1 +// #define DEL_DIR 1 #define DEL_RECURSE 2 #define DEL_REBOOT 4 // #define DEL_SIMPLE 8 @@ -1447,9 +1486,11 @@ static void FlagsToString2(CDynLimBuf &s, const char * const *table, unsigned nu static bool DoesNeedQuotes(const char *s) { - char c = s[0]; - if (c == 0 || c == '#' || c == ';' || (c == '/' && s[1] == '*')) - return true; + { + char c = s[0]; + if (c == 0 || c == '#' || c == ';' || (c == '/' && s[1] == '*')) + return true; + } for (;;) { char c = *s++; @@ -1515,7 +1556,7 @@ static const UInt32 CMD_REF_Leave = (1 << 4); static const UInt32 CMD_REF_OnFunc = (1 << 5); static const UInt32 CMD_REF_Section = (1 << 6); static const UInt32 CMD_REF_InitPluginDir = (1 << 7); -// static const UInt32 CMD_REF_Creator = (1 << 5); // _Pre is used instead +// static const UInt32 CMD_REF_Creator = (1 << 5); // CMD_REF_Pre is used instead static const unsigned CMD_REF_OnFunc_NumShifts = 28; // it uses for onFunc too static const unsigned CMD_REF_Page_NumShifts = 16; // it uses for onFunc too static const UInt32 CMD_REF_Page_Mask = 0x0FFF0000; @@ -1595,7 +1636,7 @@ void CInArchive::Add_GotoVar(UInt32 param) { Space(); if ((Int32)param < 0) - Add_Var(-((Int32)param + 1)); + Add_Var((UInt32)-((Int32)param + 1)); else Add_LabelName(param - 1); } @@ -1625,8 +1666,8 @@ static bool NoLabels(const UInt32 *labels, UInt32 num) static const char * const k_REBOOTOK = " /REBOOTOK"; -#define MY__MB_ABORTRETRYIGNORE 2 -#define MY__MB_RETRYCANCEL 5 +#define Z7_NSIS_WIN_MB_ABORTRETRYIGNORE 2 +#define Z7_NSIS_WIN_MB_RETRYCANCEL 5 static const char * const k_MB_Buttons[] = { @@ -1639,7 +1680,7 @@ static const char * const k_MB_Buttons[] = , "CANCELTRYCONTINUE" }; -#define MY__MB_ICONSTOP (1 << 4) +#define Z7_NSIS_WIN_MB_ICONSTOP (1 << 4) static const char * const k_MB_Icons[] = { @@ -1662,8 +1703,8 @@ static const char * const k_MB_Flags[] = // , "SERVICE_NOTIFICATION" // unsupported. That bit is used for NSIS purposes }; -#define MY__IDCANCEL 2 -#define MY__IDIGNORE 5 +#define Z7_NSIS_WIN_IDCANCEL 2 +#define Z7_NSIS_WIN_IDIGNORE 5 static const char * const k_Button_IDs[] = { @@ -1684,7 +1725,7 @@ static const char * const k_Button_IDs[] = void CInArchive::Add_ButtonID(UInt32 buttonID) { Space(); - if (buttonID < ARRAY_SIZE(k_Button_IDs)) + if (buttonID < Z7_ARRAY_SIZE(k_Button_IDs)) Script += k_Button_IDs[buttonID]; else { @@ -1713,7 +1754,10 @@ static bool StringToUInt32(const char *s, UInt32 &res) return (*end == 0); } -static const unsigned k_CtlColors_Size = 24; +static const unsigned k_CtlColors32_Size = 24; +static const unsigned k_CtlColors64_Size = 28; + +#define GET_CtlColors_SIZE(is64) ((is64) ? k_CtlColors64_Size : k_CtlColors32_Size) struct CNsis_CtlColors { @@ -1723,34 +1767,34 @@ struct CNsis_CtlColors UInt32 bkb; // HBRUSH Int32 bkmode; Int32 flags; + UInt32 bkb_hi32; - void Parse(const Byte *p); + void Parse(const Byte *p, bool is64); }; -void CNsis_CtlColors::Parse(const Byte *p) +void CNsis_CtlColors::Parse(const Byte *p, bool is64) { text = Get32(p); bkc = Get32(p + 4); - lbStyle = Get32(p + 8); - bkb = Get32(p + 12); + if (is64) + { + bkb = Get32(p + 8); + bkb_hi32 = Get32(p + 12); + lbStyle = Get32(p + 16); + p += 4; + } + else + { + lbStyle = Get32(p + 8); + bkb = Get32(p + 12); + } bkmode = (Int32)Get32(p + 16); flags = (Int32)Get32(p + 20); } // Win32 constants -#define MY__TRANSPARENT 1 -#define MY__OPAQUE 2 - -#define MY__GENERIC_READ (1 << 31) -#define MY__GENERIC_WRITE (1 << 30) -#define MY__GENERIC_EXECUTE (1 << 29) -#define MY__GENERIC_ALL (1 << 28) - -#define MY__CREATE_NEW 1 -#define MY__CREATE_ALWAYS 2 -#define MY__OPEN_EXISTING 3 -#define MY__OPEN_ALWAYS 4 -#define MY__TRUNCATE_EXISTING 5 +#define Z7_NSIS_WIN_TRANSPARENT 1 +// #define Z7_NSIS_WIN_OPAQUE 2 // text/bg colors #define kColorsFlags_TEXT 1 @@ -1785,12 +1829,12 @@ void CInArchive::Add_Color(UInt32 v) Add_Color2(v); } -#define MY__SW_HIDE 0 -#define MY__SW_SHOWNORMAL 1 +#define Z7_NSIS_WIN_SW_HIDE 0 +#define Z7_NSIS_WIN_SW_SHOWNORMAL 1 -#define MY__SW_SHOWMINIMIZED 2 -#define MY__SW_SHOWMINNOACTIVE 7 -#define MY__SW_SHOWNA 8 +#define Z7_NSIS_WIN_SW_SHOWMINIMIZED 2 +#define Z7_NSIS_WIN_SW_SHOWMINNOACTIVE 7 +#define Z7_NSIS_WIN_SW_SHOWNA 8 static const char * const kShowWindow_Commands[] = { @@ -1810,7 +1854,7 @@ static const char * const kShowWindow_Commands[] = static void Add_ShowWindow_Cmd_2(AString &s, UInt32 cmd) { - if (cmd < ARRAY_SIZE(kShowWindow_Commands)) + if (cmd < Z7_ARRAY_SIZE(kShowWindow_Commands)) { s += "SW_"; s += kShowWindow_Commands[cmd]; @@ -1821,7 +1865,7 @@ static void Add_ShowWindow_Cmd_2(AString &s, UInt32 cmd) void CInArchive::Add_ShowWindow_Cmd(UInt32 cmd) { - if (cmd < ARRAY_SIZE(kShowWindow_Commands)) + if (cmd < Z7_ARRAY_SIZE(kShowWindow_Commands)) { Script += "SW_"; Script += kShowWindow_Commands[cmd]; @@ -1841,7 +1885,7 @@ void CInArchive::Add_TypeFromList(const char * const *table, unsigned tableSize, } } -#define ADD_TYPE_FROM_LIST(table, type) Add_TypeFromList(table, ARRAY_SIZE(table), type) +#define ADD_TYPE_FROM_LIST(table, type) Add_TypeFromList(table, Z7_ARRAY_SIZE(table), type) enum { @@ -1858,7 +1902,7 @@ enum k_ExecFlags_rtl, k_ExecFlags_ErrorLevel, k_ExecFlags_RegView, - k_ExecFlags_DetailsPrint = 13, + k_ExecFlags_DetailsPrint = 13 }; // Names for NSIS exec_flags_t structure vars @@ -2061,7 +2105,7 @@ void CSection::Parse(const Byte *p) StartCmdIndex = Get32(p + 12); NumCommands = Get32(p + 16); SizeKB = Get32(p + 20); -}; +} // used for section->flags #define SF_SELECTED (1 << 0) @@ -2070,9 +2114,11 @@ void CSection::Parse(const Byte *p) #define SF_BOLD (1 << 3) #define SF_RO (1 << 4) #define SF_EXPAND (1 << 5) +/* #define SF_PSELECTED (1 << 6) #define SF_TOGGLED (1 << 7) #define SF_NAMECHG (1 << 8) +*/ bool CInArchive::PrintSectionBegin(const CSection §, unsigned index) { @@ -2117,7 +2163,7 @@ bool CInArchive::PrintSectionBegin(const CSection §, unsigned index) Script += ' '; else */ - SmallSpaceComment(); + SmallSpaceComment(); Script += "Section_"; Add_UInt(index); @@ -2144,7 +2190,7 @@ bool CInArchive::PrintSectionBegin(const CSection §, unsigned index) { TabString("SectionIn"); UInt32 instTypes = sect.InstallTypes; - for (int i = 0; i < 32; i++, instTypes >>= 1) + for (unsigned i = 0; i < 32; i++, instTypes >>= 1) if ((instTypes & 1) != 0) { AddParam_UInt(i + 1); @@ -2232,7 +2278,7 @@ void CInArchive::MessageBox_MB_Part(UInt32 param) { UInt32 v = param & 0xF; Script += " MB_"; - if (v < ARRAY_SIZE(k_MB_Buttons)) + if (v < Z7_ARRAY_SIZE(k_MB_Buttons)) Script += k_MB_Buttons[v]; else { @@ -2245,7 +2291,7 @@ void CInArchive::MessageBox_MB_Part(UInt32 param) if (icon != 0) { Script += "|MB_"; - if (icon < ARRAY_SIZE(k_MB_Icons) && k_MB_Icons[icon] != 0) + if (icon < Z7_ARRAY_SIZE(k_MB_Icons) && k_MB_Icons[icon]) Script += k_MB_Icons[icon]; else { @@ -2270,7 +2316,7 @@ void CInArchive::MessageBox_MB_Part(UInt32 param) else if (modal == 2) Script += "|MB_TASKMODAL"; else if (modal == 3) Script += "|0x3000"; UInt32 flags = (param >> 14); - for (unsigned i = 0; i < ARRAY_SIZE(k_MB_Flags); i++) + for (unsigned i = 0; i < Z7_ARRAY_SIZE(k_MB_Flags); i++) if ((flags & (1 << i)) != 0) { Script += "|MB_"; @@ -2292,14 +2338,15 @@ bool CInArchive::CompareCommands(const Byte *rawCmds, const Byte *sequence, size return true; } -#endif static const UInt32 kSectionSize_base = 6 * 4; -static const UInt32 kSectionSize_8bit = kSectionSize_base + 1024; -static const UInt32 kSectionSize_16bit = kSectionSize_base + 1024 * 2; -static const UInt32 kSectionSize_16bit_Big = kSectionSize_base + 8196 * 2; +// static const UInt32 kSectionSize_8bit = kSectionSize_base + 1024; +// static const UInt32 kSectionSize_16bit = kSectionSize_base + 1024 * 2; +// static const UInt32 kSectionSize_16bit_Big = kSectionSize_base + 8196 * 2; // 8196 is default string length in NSIS-Unicode since 2.37.3 +#endif + static void AddString(AString &dest, const char *src) { @@ -2309,7 +2356,7 @@ static void AddString(AString &dest, const char *src) AString CInArchive::GetFormatDescription() const { - AString s = "NSIS-"; + AString s ("NSIS-"); char c; if (IsPark()) { @@ -2332,21 +2379,28 @@ AString CInArchive::GetFormatDescription() const if (IsUnicode) AddString(s, "Unicode"); + + if (Is64Bit) + AddString(s, "64-bit"); + if (LogCmdIsEnabled) AddString(s, "log"); + if (BadCmd >= 0) { AddString(s, "BadCmd="); - UIntToString(s, BadCmd); + UIntToString(s, (UInt32)BadCmd); } return s; } #ifdef NSIS_SCRIPT +static const unsigned kNumAdditionalParkCmds = 3; + unsigned CInArchive::GetNumSupportedCommands() const { - unsigned numCmds = IsPark() ? kNumCmds : kNumCmds - kNumAdditionalParkCmds; + unsigned numCmds = IsPark() ? (unsigned)kNumCmds : (unsigned)(kNumCmds) - kNumAdditionalParkCmds; if (!LogCmdIsEnabled) numCmds--; if (!IsUnicode) @@ -2409,31 +2463,42 @@ void CInArchive::FindBadCmd(const CBlockHeader &bh, const Byte *p) for (UInt32 kkk = 0; kkk < bh.Num; kkk++, p += kCmdSize) { - UInt32 id = GetCmd(Get32(p)); + const UInt32 id = GetCmd(Get32(p)); if (id >= kNumCmds) continue; if (BadCmd >= 0 && id >= (unsigned)BadCmd) continue; unsigned i; - if (id == EW_GETLABELADDR || - id == EW_GETFUNCTIONADDR) + if (IsNsis3_OrHigher()) { - BadCmd = id; - continue; + if (id == EW_RESERVEDOPCODE) + { + BadCmd = (int)id; + continue; + } + } + else + { + // if (id == EW_GETLABELADDR || id == EW_GETFUNCTIONADDR) + if (id == EW_RESERVEDOPCODE || id == EW_GETOSINFO) + { + BadCmd = (int)id; + continue; + } } for (i = 6; i != 0; i--) { - UInt32 param = Get32(p + i * 4); + const UInt32 param = Get32(p + i * 4); if (param != 0) break; } if (id == EW_FINDPROC && i == 0) { - BadCmd = id; + BadCmd = (int)id; continue; } if (k_Commands[id].NumParams < i) - BadCmd = id; + BadCmd = (int)id; } } @@ -2447,23 +2512,24 @@ void CInArchive::DetectNsisType(const CBlockHeader &bh, const Byte *p) bool strongPark = false; bool strongNsis = false; + if (NumStringChars > 2) { const Byte *strData = _data + _stringsPos; if (IsUnicode) { - UInt32 num = NumStringChars; + UInt32 num = NumStringChars - 2; for (UInt32 i = 0; i < num; i++) { if (Get16(strData + i * 2) == 0) { unsigned c2 = Get16(strData + 2 + i * 2); + // it can be TXT/RTF with marker char (1 or 2). so we must check next char // if (c2 <= NS_3_CODE_SKIP && c2 != NS_3_CODE_SHELL) if (c2 == NS_3_CODE_VAR) { - // it can be TXT/RTF string with marker char (1 or 2). so we must next char - // const wchar_t *p2 = (const wchar_t *)(strData + i * 2 + 2); - // p2 = p2; - if ((Get16(strData + 3 + i * 2) & 0x8000) != 0) + // 18.06: fixed: is it correct ? + // if ((Get16(strData + 3 + i * 2) & 0x8000) != 0) + if ((Get16(strData + 4 + i * 2) & 0x8080) == 0x8080) { NsisType = k_NsisType_Nsis3; strongNsis = true; @@ -2480,7 +2546,7 @@ void CInArchive::DetectNsisType(const CBlockHeader &bh, const Byte *p) } else { - UInt32 num = NumStringChars; + UInt32 num = NumStringChars - 2; for (UInt32 i = 0; i < num; i++) { if (strData[i] == 0) @@ -2491,7 +2557,7 @@ void CInArchive::DetectNsisType(const CBlockHeader &bh, const Byte *p) if (c2 == NS_3_CODE_VAR) // if (c2 <= NS_3_CODE_SKIP && c2 != NS_3_CODE_SHELL && c2 != 1) { - if ((strData[i+ 2] & 0x80) != 0) + if ((strData[i + 2] & 0x80) != 0) { // const char *p2 = (const char *)(strData + i + 1); // p2 = p2; @@ -2557,7 +2623,7 @@ void CInArchive::DetectNsisType(const CBlockHeader &bh, const Byte *p) for (UInt32 kkk = 0; kkk < bh.Num; kkk++, p2 += kCmdSize) { - UInt32 cmd = Get32(p2); // we use original (not converted) command + const UInt32 cmd = Get32(p2); // we use original (not converted) command if (cmd < EW_WRITEUNINSTALLER || cmd > EW_WRITEUNINSTALLER + numInsertMax) @@ -2573,7 +2639,7 @@ void CInArchive::DetectNsisType(const CBlockHeader &bh, const Byte *p) params[3] <= 1) continue; - UInt32 altParam = params[3]; + const UInt32 altParam = params[3]; if (!IsGoodString(params[0]) || !IsGoodString(altParam)) continue; @@ -2583,8 +2649,8 @@ void CInArchive::DetectNsisType(const CBlockHeader &bh, const Byte *p) continue; if (AreTwoParamStringsEqual(altParam + additional, params[0])) { - unsigned numInserts = cmd - EW_WRITEUNINSTALLER; - mask |= (1 << numInserts); + const unsigned numInserts = cmd - EW_WRITEUNINSTALLER; + mask |= ((unsigned)1 << numInserts); } } @@ -2712,13 +2778,13 @@ Int32 CInArchive::GetVarIndex(UInt32 strPos) const else if (c != NS_CODE_VAR) return -1; - unsigned c0 = p[1]; + const unsigned c0 = p[1]; if (c0 == 0) return -1; - unsigned c1 = p[2]; + const unsigned c1 = p[2]; if (c1 == 0) return -1; - return DECODE_NUMBER_FROM_2_CHARS(c0, c1); + return (Int32)DECODE_NUMBER_FROM_2_CHARS(c0, c1); } Int32 CInArchive::GetVarIndex(UInt32 strPos, UInt32 &resOffset) const @@ -2793,43 +2859,37 @@ bool CInArchive::IsAbsolutePathVar(UInt32 strPos) const return false; } -#define IS_LETTER_CHAR(c) ((c) >= 'a' && (c) <= 'z' || (c) >= 'A' && (c) <= 'Z') +#define IS_LETTER_CHAR(c) (((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z')) // We use same check as in NSIS decoder -bool IsDrivePath(const wchar_t *s) { return IS_LETTER_CHAR(s[0]) && s[1] == ':' /* && s[2] == '\\' */ ; } -bool IsDrivePath(const char *s) { return IS_LETTER_CHAR(s[0]) && s[1] == ':' /* && s[2] == '\\' */ ; } +static bool IsDrivePath(const wchar_t *s) { return IS_LETTER_CHAR(s[0]) && s[1] == ':' /* && s[2] == '\\' */ ; } +static bool IsDrivePath(const char *s) { return IS_LETTER_CHAR(s[0]) && s[1] == ':' /* && s[2] == '\\' */ ; } static bool IsAbsolutePath(const wchar_t *s) { - return - s[0] == WCHAR_PATH_SEPARATOR && - s[1] == WCHAR_PATH_SEPARATOR || - IsDrivePath(s); + return (s[0] == WCHAR_PATH_SEPARATOR && s[1] == WCHAR_PATH_SEPARATOR) || IsDrivePath(s); } static bool IsAbsolutePath(const char *s) { - return - s[0] == CHAR_PATH_SEPARATOR && - s[1] == CHAR_PATH_SEPARATOR || - IsDrivePath(s); + return (s[0] == CHAR_PATH_SEPARATOR && s[1] == CHAR_PATH_SEPARATOR) || IsDrivePath(s); } void CInArchive::SetItemName(CItem &item, UInt32 strPos) { ReadString2_Raw(strPos); - bool isAbs = IsAbsolutePathVar(strPos); + const bool isAbs = IsAbsolutePathVar(strPos); if (IsUnicode) { item.NameU = Raw_UString; if (!isAbs && !IsAbsolutePath(Raw_UString)) - item.Prefix = UPrefixes.Size() - 1; + item.Prefix = (int)UPrefixes.Size() - 1; } else { item.NameA = Raw_AString; if (!isAbs && !IsAbsolutePath(Raw_AString)) - item.Prefix = APrefixes.Size() - 1; + item.Prefix = (int)APrefixes.Size() - 1; } } @@ -2897,9 +2957,9 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) for (int i = 0; i < 5; i++) params[i] = Get32(p + 44 + 4 * i); - SET_FUNC_REF(preFunc, CMD_REF_Pre); - SET_FUNC_REF(showFunc, CMD_REF_Show); - SET_FUNC_REF(leaveFunc, CMD_REF_Leave); + SET_FUNC_REF(preFunc, CMD_REF_Pre) + SET_FUNC_REF(showFunc, CMD_REF_Show) + SET_FUNC_REF(leaveFunc, CMD_REF_Leave) if (wndProcID == PWP_COMPLETED) CommentOpen(); @@ -2917,7 +2977,7 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) else s += IsInstaller ? "Page " : "UninstPage "; - if (wndProcID < ARRAY_SIZE(kPageTypes)) + if (wndProcID < Z7_ARRAY_SIZE(kPageTypes)) s += kPageTypes[wndProcID]; else Add_UInt(wndProcID); @@ -3137,11 +3197,11 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) } */ if (IsFunc(flg) - && bh.Num - kkk >= ARRAY_SIZE(k_InitPluginDir_Commands) - && CompareCommands(p, k_InitPluginDir_Commands, ARRAY_SIZE(k_InitPluginDir_Commands))) + && bh.Num - kkk >= Z7_ARRAY_SIZE(k_InitPluginDir_Commands) + && CompareCommands(p, k_InitPluginDir_Commands, Z7_ARRAY_SIZE(k_InitPluginDir_Commands))) { - InitPluginsDir_Start = kkk; - InitPluginsDir_End = kkk + ARRAY_SIZE(k_InitPluginDir_Commands); + InitPluginsDir_Start = (int)kkk; + InitPluginsDir_End = (int)(kkk + Z7_ARRAY_SIZE(k_InitPluginDir_Commands)); labels[kkk] |= CMD_REF_InitPluginDir; break; } @@ -3185,8 +3245,8 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) UString spec_outdir_U; AString spec_outdir_A; - UPrefixes.Add(L"$INSTDIR"); - APrefixes.Add("$INSTDIR"); + UPrefixes.Add(UString("$INSTDIR")); + APrefixes.Add(AString("$INSTDIR")); p = _data + bh.Offset; @@ -3304,7 +3364,7 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) unsigned numSkipParams = 0; - if (commandId < ARRAY_SIZE(k_Commands) && commandId < numSupportedCommands) + if (commandId < Z7_ARRAY_SIZE(k_Commands) && commandId < numSupportedCommands) { numSkipParams = k_Commands[commandId].NumParams; const char *sz = k_CommandNames[commandId]; @@ -3362,7 +3422,7 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) #ifdef NSIS_SCRIPT s += isSetOutPath ? "SetOutPath" : "CreateDirectory"; AddParam(params[0]); - if (params[2] != 0) + if (params[2] != 0) // 2.51+ & 3.0b3+ { SmallSpaceComment(); s += "CreateRestrictedDirectory"; @@ -3453,9 +3513,9 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) UInt32 b1 = nsisMB >> 21; // NSIS 2.06+ UInt32 b2 = nsisMB >> 20; // NSIS old Int32 asf = (Int32)nsisMB; - if (mb == (MY__MB_ABORTRETRYIGNORE | MY__MB_ICONSTOP) && (b1 == MY__IDIGNORE || b2 == MY__IDIGNORE)) + if (mb == (Z7_NSIS_WIN_MB_ABORTRETRYIGNORE | Z7_NSIS_WIN_MB_ICONSTOP) && (b1 == Z7_NSIS_WIN_IDIGNORE || b2 == Z7_NSIS_WIN_IDIGNORE)) asf = -1; - else if (mb == (MY__MB_RETRYCANCEL | MY__MB_ICONSTOP) && (b1 == MY__IDCANCEL || b2 == MY__IDCANCEL)) + else if (mb == (Z7_NSIS_WIN_MB_RETRYCANCEL | Z7_NSIS_WIN_MB_ICONSTOP) && (b1 == Z7_NSIS_WIN_IDCANCEL || b2 == Z7_NSIS_WIN_IDCANCEL)) asf = -2; else { @@ -3539,7 +3599,7 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) #ifdef NSIS_SCRIPT AddParam(params[0]); Space(); - FlagsToString2(s, g_WinAttrib, ARRAY_SIZE(g_WinAttrib), params[1]); + FlagsToString2(s, g_WinAttrib, Z7_ARRAY_SIZE(g_WinAttrib), params[1]); #endif break; } @@ -3551,7 +3611,7 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) NSIS installer uses alternative path, if main path from params[0] is not absolute path */ - bool pathOk = (params[0] > 0) && IsGoodString(params[0]); + const bool pathOk = (params[0] > 0) && IsGoodString(params[0]); if (!pathOk) { @@ -3561,9 +3621,11 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) break; } + #ifdef NSIS_SCRIPT + bool altPathOk = true; - UInt32 altParam = params[3]; + const UInt32 altParam = params[3]; if (altParam != 0) { altPathOk = false; @@ -3572,9 +3634,6 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) altPathOk = AreTwoParamStringsEqual(altParam + additional, params[0]); } - - #ifdef NSIS_SCRIPT - AddParam(params[0]); /* @@ -3588,8 +3647,6 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) AddParam(params[3]); } - #endif - if (!altPathOk) { #ifdef NSIS_SCRIPT @@ -3597,9 +3654,11 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) #endif } + #endif + if (BadCmd >= 0 && BadCmd <= EW_WRITEUNINSTALLER) { - /* We don't cases with incorrect installer commands. + /* We don't support cases with incorrect installer commands. Such bad installer item can break unpacking for other items. */ #ifdef NSIS_SCRIPT AddError("SKIP possible BadCmd"); @@ -3607,13 +3666,23 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) break; } - CItem &item = Items.AddNew();; + CItem &item = Items.AddNew(); SetItemName(item, params[0]); item.Pos = params[1]; item.PatchSize = params[2]; item.IsUninstaller = true; + const UInt32 param3 = params[3]; + if (param3 != 0 && item.Prefix != -1) + { + /* (item.Prefix != -1) case means that param[0] path was not absolute. + So we use params[3] in that case, as original nsis */ + SetItemName(item, param3); + } + /* UNINSTALLER file doesn't use directory prefixes. + So we remove prefix: */ + item.Prefix = -1; /* // we can add second time to test the code @@ -3749,8 +3818,8 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) for (UInt32 j = i - 1; j >= kkk + 3; j--) { - const Byte *pCmd = p + kCmdSize * (j - kkk); - AddParam(GET_CMD_PARAM(pCmd, 0)); + const Byte *pCmd2 = p + kCmdSize * (j - kkk); + AddParam(GET_CMD_PARAM(pCmd2, 0)); } NewLine(); Tab(true); @@ -3782,12 +3851,12 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) s += "Call "; if ((Int32)params[0] < 0) - Add_Var(-((Int32)params[0] + 1)); + Add_Var((UInt32)-((Int32)params[0] + 1)); else if (params[0] == 0) s += '0'; else { - UInt32 val = params[0] - 1; + const UInt32 val = params[0] - 1; if (params[1] == 1) // it's Call :Label { s += ':'; @@ -3808,8 +3877,8 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) case EW_CHDETAILSVIEW: { - if (params[0] == MY__SW_SHOWNA && params[1] == MY__SW_HIDE) s += " show"; - else if (params[1] == MY__SW_SHOWNA && params[0] == MY__SW_HIDE) s += " hide"; + if (params[0] == Z7_NSIS_WIN_SW_SHOWNA && params[1] == Z7_NSIS_WIN_SW_HIDE) s += " show"; + else if (params[1] == Z7_NSIS_WIN_SW_SHOWNA && params[0] == Z7_NSIS_WIN_SW_HIDE) s += " hide"; else for (int i = 0; i < 2; i++) { @@ -3841,6 +3910,10 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) if (params[2] != 0) { s += " lastused"; + // (params[3] == (UInt32)(Int32)-1) is possible in NSIS 3.10. + // SetDetailsPrint lastused (special) + if ((Int32)params[3] < 0) + s += " ; (special)"; break; } UInt32 v; @@ -3851,13 +3924,13 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) { case k_ExecFlags_AutoClose: case k_ExecFlags_RebootFlag: - if (v < 2) s2 = (v == 0) ? "false" : "true"; break; + if (v < 2) { s2 = (v == 0) ? "false" : "true"; } break; case k_ExecFlags_ShellVarContext: - if (v < 2) s2 = (v == 0) ? "current" : "all"; break; + if (v < 2) { s2 = (v == 0) ? "current" : "all"; } break; case k_ExecFlags_Silent: - if (v < 2) s2 = (v == 0) ? "normal" : "silent"; break; + if (v < 2) { s2 = (v == 0) ? "normal" : "silent"; } break; case k_ExecFlags_RegView: - if (v == 0) s2 = "32"; + if (v == 0) s2 = "32"; else if (v == 256) s2 = "64"; break; case k_ExecFlags_DetailsPrint: @@ -3865,6 +3938,7 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) else if (v == 2) s2 = "textonly"; else if (v == 4) s2 = "listonly"; else if (v == 6) s2 = "none"; + break; } if (s2) { @@ -3882,7 +3956,7 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) Add_ExecFlags(params[2]); Add_GotoVars2(¶ms[0]); /* - static const unsigned kIfErrors = 2; + const unsigned kIfErrors = 2; if (params[2] != kIfErrors && params[3] != 0xFFFFFFFF || params[2] == kIfErrors && params[3] != 0) { @@ -3935,7 +4009,7 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) AddParam_Var(params[0]); AString temp; ReadString2(temp, params[1]); - if (temp != "$TEMP") + if (!temp.IsEqualTo("$TEMP")) SpaceQuStr(temp); break; } @@ -4009,7 +4083,12 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) case EW_INTCMP: { - if (params[5] != 0) + s += "Int"; + const UInt32 param5 = params[5]; + if (param5 & 0x8000) + s += "64"; // v3.03+ + s += "Cmp"; + if (IsNsis3_OrHigher() ? (param5 & 1) : (param5 != 0)) s += 'U'; AddParams(params, 2); Add_GotoVar1(params[2]); @@ -4021,13 +4100,13 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) case EW_INTOP: { AddParam_Var(params[0]); - const char *kOps = "+-*/|&^!|&%<>"; // NSIS 2.01+ + const char * const kOps = "+-*/|&^!|&%<>>"; // NSIS 2.01+ // "+-*/|&^!|&%"; // NSIS 2.0b4+ // "+-*/|&^~!|&%"; // NSIS old - UInt32 opIndex = params[3]; - char c = (opIndex < 13) ? kOps[opIndex] : '?'; - char c2 = (opIndex < 8 || opIndex == 10) ? (char)0 : c; - int numOps = (opIndex == 7) ? 1 : 2; + const UInt32 opIndex = params[3]; + const char c = (opIndex < 14) ? kOps[opIndex] : '?'; + const char c2 = (opIndex < 8 || opIndex == 10) ? (char)0 : c; + const int numOps = (opIndex == 7) ? 1 : 2; AddParam(params[1]); if (numOps == 2 && c == '^' && IsDirectString_Equal(params[2], "0xFFFFFFFF")) s += " ~ ;"; @@ -4035,6 +4114,8 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) s += c; if (numOps != 1) { + if (opIndex == 13) // v3.03+ : operation ">>>" + s += c; if (c2 != 0) s += c2; AddParam(params[2]); @@ -4044,6 +4125,10 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) case EW_INTFMT: { + if (params[3]) + s += "Int64Fmt"; // v3.03+ + else + s += "IntFmt"; AddParam_Var(params[0]); AddParams(params + 1, 2); break; @@ -4164,7 +4249,7 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) if (_size < bhCtlColors.Offset || _size - bhCtlColors.Offset < offset - || _size - bhCtlColors.Offset - offset < k_CtlColors_Size) + || _size - bhCtlColors.Offset - offset < GET_CtlColors_SIZE(Is64Bit)) { AddError("bad offset"); break; @@ -4172,7 +4257,7 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) const Byte *p2 = _data + bhCtlColors.Offset + offset; CNsis_CtlColors colors; - colors.Parse(p2); + colors.Parse(p2, Is64Bit); if ((colors.flags & kColorsFlags_BK_SYS) != 0 || (colors.flags & kColorsFlags_TEXT_SYS) != 0) @@ -4180,7 +4265,7 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) AString bk; bool bkc = false; - if (colors.bkmode == MY__TRANSPARENT) + if (colors.bkmode == Z7_NSIS_WIN_TRANSPARENT) bk += " transparent"; else if (colors.flags & kColorsFlags_BKB) { @@ -4206,6 +4291,7 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) break; } + // case EW_LOADANDSETIMAGE: case EW_SETBRANDINGIMAGE: { s += " /IMGID="; @@ -4240,7 +4326,7 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) bool valDefined = false; if (StringToUInt32(sw, val)) { - if (val < ARRAY_SIZE(kShowWindow_Commands)) + if (val < Z7_ARRAY_SIZE(kShowWindow_Commands)) { sw.Empty(); sw += "${"; @@ -4273,10 +4359,10 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) case EW_SHELLEXEC: { AddParams(params, 2); - if (params[2] != 0 || params[3] != MY__SW_SHOWNORMAL) + if (params[2] != 0 || params[3] != Z7_NSIS_WIN_SW_SHOWNORMAL) { AddParam(params[2]); - if (params[3] != MY__SW_SHOWNORMAL) + if (params[3] != Z7_NSIS_WIN_SW_SHOWNORMAL) { Space(); Add_ShowWindow_Cmd(params[3]); @@ -4304,6 +4390,9 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) case EW_GETFILETIME: case EW_GETDLLVERSION: { + if (commandId == EW_GETDLLVERSION) + if (params[3] == 2) + s += " /ProductVersion"; // v3.08+ AddParam(params[2]); AddParam_Var(params[0]); AddParam_Var(params[1]); @@ -4325,7 +4414,7 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) } else { - if (func == "DllUnregisterServer") + if (func.IsEqualTo("DllUnregisterServer")) { s += "UnRegDLL"; printFunc = false; @@ -4333,7 +4422,7 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) else { s += "RegDLL"; - if (func == "DllRegisterServer") + if (func.IsEqualTo("DllRegisterServer")) printFunc = false; } AddParam(params[0]); @@ -4346,11 +4435,15 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) case EW_CREATESHORTCUT: { unsigned numParams; + #define IsNsis3d0b3_OrHigher() 0 // change it + const unsigned v3_0b3 = IsNsis3d0b3_OrHigher(); for (numParams = 6; numParams > 2; numParams--) if (params[numParams - 1] != 0) break; - UInt32 spec = params[4]; + const UInt32 spec = params[4]; + const unsigned sw_shift = v3_0b3 ? 12 : 8; + const UInt32 sw_mask = v3_0b3 ? 0x7000 : 0x7F; if (spec & 0x8000) // NSIS 3.0b0 s += " /NoWorkingDir"; @@ -4358,20 +4451,20 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) if (numParams <= 4) break; - UInt32 icon = (spec & 0xFF); + UInt32 icon = (spec & (v3_0b3 ? 0xFFF : 0xFF)); Space(); if (icon != 0) Add_UInt(icon); else AddQuotes(); - if ((spec >> 8) == 0 && numParams < 6) + if ((spec >> sw_shift) == 0 && numParams < 6) break; - UInt32 sw = (spec >> 8) & 0x7F; + UInt32 sw = (spec >> sw_shift) & sw_mask; Space(); // NSIS encoder replaces these names: - if (sw == MY__SW_SHOWMINNOACTIVE) - sw = MY__SW_SHOWMINIMIZED; + if (sw == Z7_NSIS_WIN_SW_SHOWMINNOACTIVE) + sw = Z7_NSIS_WIN_SW_SHOWMINIMIZED; if (sw == 0) AddQuotes(); else @@ -4395,13 +4488,13 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) if (modKey & 4) s += "ALT|"; if (modKey & 8) s += "EXT|"; - static const unsigned kMy_VK_F1 = 0x70; + const unsigned kMy_VK_F1 = 0x70; if (key >= kMy_VK_F1 && key <= kMy_VK_F1 + 23) { s += 'F'; Add_UInt(key - kMy_VK_F1 + 1); } - else if (key >= 'A' && key <= 'Z' || key >= '0' && key <= '9') + else if ((key >= 'A' && key <= 'Z') || (key >= '0' && key <= '9')) s += (char)key; else { @@ -4477,6 +4570,7 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) s += "Key"; if (params[4] & 2) s += " /ifempty"; + // TODO: /ifnosubkeys, /ifnovalues } AddRegRoot(params[1]); AddParam(params[2]); @@ -4486,7 +4580,7 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) case EW_WRITEREG: { - const char *s2 = 0; + const char *s2 = NULL; switch (params[4]) { case 1: s2 = "Str"; break; @@ -4499,6 +4593,8 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) } if (params[4] == 1 && params[5] == 2) s2 = "ExpandStr"; + if (params[4] == 3 && params[5] == 7) + s2 = "MultiStr"; // v3.02+ if (s2) s += s2; AddRegRoot(params[0]); @@ -4567,20 +4663,42 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) break; } + #endif + case EW_FOPEN: { + /* + the pattern for empty files is following: + FileOpen $0 "file_name" w + FileClose $0 + */ + + const UInt32 acc = params[1]; // dwDesiredAccess + const UInt32 creat = params[2]; // dwCreationDisposition + if (creat == Z7_NSIS_WIN_CREATE_ALWAYS && acc == Z7_NSIS_WIN_GENERIC_WRITE) + { + if (kkk + 1 < bh.Num) + if (Get32(p + kCmdSize) == EW_FCLOSE) + if (Get32(p + kCmdSize + 4) == params[0]) + { + CItem &item = Items.AddNew(); + item.IsEmptyFile = true; + SetItemName(item, params[3]); + } + } + + #ifdef NSIS_SCRIPT + AddParam_Var(params[0]); AddParam(params[3]); - UInt32 acc = params[1]; // dwDesiredAccess - UInt32 creat = params[2]; // dwCreationDisposition if (acc == 0 && creat == 0) break; char cc = 0; - if (acc == MY__GENERIC_READ && creat == OPEN_EXISTING) + if (creat == Z7_NSIS_WIN_OPEN_EXISTING && acc == Z7_NSIS_WIN_GENERIC_READ) cc = 'r'; - else if (creat == CREATE_ALWAYS && acc == MY__GENERIC_WRITE) + else if (creat == Z7_NSIS_WIN_CREATE_ALWAYS && acc == Z7_NSIS_WIN_GENERIC_WRITE) cc = 'w'; - else if (creat == OPEN_ALWAYS && (acc == (MY__GENERIC_WRITE | MY__GENERIC_READ))) + else if (creat == Z7_NSIS_WIN_OPEN_ALWAYS && (acc == (Z7_NSIS_WIN_GENERIC_WRITE | Z7_NSIS_WIN_GENERIC_READ))) cc = 'a'; // cc = 0; if (cc != 0) @@ -4590,28 +4708,32 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) break; } - if (acc & MY__GENERIC_READ) s += " GENERIC_READ"; - if (acc & MY__GENERIC_WRITE) s += " GENERIC_WRITE"; - if (acc & MY__GENERIC_EXECUTE) s += " GENERIC_EXECUTE"; - if (acc & MY__GENERIC_ALL) s += " GENERIC_ALL"; + if (acc & Z7_NSIS_WIN_GENERIC_READ) s += " GENERIC_READ"; + if (acc & Z7_NSIS_WIN_GENERIC_WRITE) s += " GENERIC_WRITE"; + if (acc & Z7_NSIS_WIN_GENERIC_EXECUTE) s += " GENERIC_EXECUTE"; + if (acc & Z7_NSIS_WIN_GENERIC_ALL) s += " GENERIC_ALL"; const char *s2 = NULL; switch (creat) { - case MY__CREATE_NEW: s2 = "CREATE_NEW"; break; - case MY__CREATE_ALWAYS: s2 = "CREATE_ALWAYS"; break; - case MY__OPEN_EXISTING: s2 = "OPEN_EXISTING"; break; - case MY__OPEN_ALWAYS: s2 = "OPEN_ALWAYS"; break; - case MY__TRUNCATE_EXISTING: s2 = "TRUNCATE_EXISTING"; break; + case Z7_NSIS_WIN_CREATE_NEW: s2 = "CREATE_NEW"; break; + case Z7_NSIS_WIN_CREATE_ALWAYS: s2 = "CREATE_ALWAYS"; break; + case Z7_NSIS_WIN_OPEN_EXISTING: s2 = "OPEN_EXISTING"; break; + case Z7_NSIS_WIN_OPEN_ALWAYS: s2 = "OPEN_ALWAYS"; break; + case Z7_NSIS_WIN_TRUNCATE_EXISTING: s2 = "TRUNCATE_EXISTING"; break; } Space(); if (s2) s += s2; else Add_UInt(creat); + #endif + break; } + #ifdef NSIS_SCRIPT + case EW_FPUTS: case EW_FPUTWS: { @@ -4619,6 +4741,8 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) s += (params[2] == 0) ? "UTF16LE" : "Word"; else if (params[2] != 0) s += "Byte"; + if (params[2] == 0 && params[3]) + s += " /BOM"; // v3.0b3+ AddParam_Var(params[0]); AddParam(params[1]); break; @@ -4688,6 +4812,7 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) s += "Text"; AddParam(params[1]); } + break; } case EW_SECTIONSET: @@ -4702,7 +4827,7 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) else { s += "Set"; - UInt32 t = -(Int32)params[2] - 1; + const UInt32 t = (UInt32)(-(Int32)params[2] - 1); Add_SectOp(t); AddParam(params[0]); AddParam(params[t == 0 ? 4 : 1]); @@ -4715,7 +4840,7 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) case EW_INSTTYPESET: { - int numQwParams = 0; + unsigned numQwParams = 0; const char *s2; if (params[3] == 0) { @@ -4746,6 +4871,34 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) AddParam_Var(params[1]); break; } + + case EW_GETOSINFO: + { + if (IsNsis3_OrHigher()) + { + // v3.06+ + if (params[3] == 0) // GETOSINFO_KNOWNFOLDER + { + s += "GetKnownFolderPath"; + AddParam_Var(params[1]); + AddParam(params[2]); + break; + } + else if (params[3] == 1) // GETOSINFO_READMEMORY + { + s += "ReadMemory"; + AddParam_Var(params[1]); + AddParam(params[2]); + AddParam(params[4]); + // if (params[2].IsEqualTo("0")) AddCommentAndString("GetWinVer"); + } + else + s += "GetOsInfo"; + break; + } + s += "GetLabelAddr"; // before v3.06+ + break; + } case EW_LOCKWINDOW: { @@ -4830,9 +4983,6 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) } else { - if (curSectionIndex == 49) - curSectionIndex = curSectionIndex; - if (PrintSectionBegin(sect, curSectionIndex)) curSectionIndex++; else @@ -4847,9 +4997,20 @@ HRESULT CInArchive::ReadEntries(const CBlockHeader &bh) static int CompareItems(void *const *p1, void *const *p2, void *param) { - const CItem &i1 = **(CItem **)p1; - const CItem &i2 = **(CItem **)p2; - RINOZ(MyCompare(i1.Pos, i2.Pos)); + const CItem &i1 = **(const CItem *const *)p1; + const CItem &i2 = **(const CItem *const *)p2; + RINOZ(MyCompare(i1.Pos, i2.Pos)) + + /* In another code we check CItem::Pos after each solid item. + So here we place empty files before all non empty files */ + if (i1.IsEmptyFile) + { + if (!i2.IsEmptyFile) + return -1; + } + else if (i2.IsEmptyFile) + return 1; + const CInArchive *inArchive = (const CInArchive *)param; if (inArchive->IsUnicode) { @@ -4857,11 +5018,11 @@ static int CompareItems(void *const *p1, void *const *p2, void *param) { if (i1.Prefix < 0) return -1; if (i2.Prefix < 0) return 1; - RINOZ(wcscmp( - inArchive->UPrefixes[i1.Prefix], - inArchive->UPrefixes[i2.Prefix])); + RINOZ( + inArchive->UPrefixes[i1.Prefix].Compare( + inArchive->UPrefixes[i2.Prefix])) } - RINOZ(wcscmp(i1.NameU, i2.NameU)); + RINOZ(i1.NameU.Compare(i2.NameU)) } else { @@ -4871,9 +5032,9 @@ static int CompareItems(void *const *p1, void *const *p2, void *param) if (i2.Prefix < 0) return 1; RINOZ(strcmp( inArchive->APrefixes[i1.Prefix], - inArchive->APrefixes[i2.Prefix])); + inArchive->APrefixes[i2.Prefix])) } - RINOZ(strcmp(i1.NameA, i2.NameA)); + RINOZ(strcmp(i1.NameA, i2.NameA)) } return 0; } @@ -4887,6 +5048,8 @@ HRESULT CInArchive::SortItems() for (i = 0; i + 1 < Items.Size(); i++) { const CItem &i1 = Items[i]; + if (i1.IsEmptyFile) + continue; const CItem &i2 = Items[i + 1]; if (i1.Pos != i2.Pos) continue; @@ -4916,10 +5079,14 @@ HRESULT CInArchive::SortItems() for (i = 0; i < Items.Size(); i++) { CItem &item = Items[i]; - UInt32 curPos = item.Pos + 4; + if (item.IsEmptyFile) + continue; + const UInt32 curPos = item.Pos + 4; for (unsigned nextIndex = i + 1; nextIndex < Items.Size(); nextIndex++) { - UInt32 nextPos = Items[nextIndex].Pos; + const CItem &nextItem = Items[nextIndex]; + // if (nextItem.IsEmptyFile) continue; + const UInt32 nextPos = nextItem.Pos; if (curPos <= nextPos) { item.EstimatedSize_Defined = true; @@ -4934,11 +5101,13 @@ HRESULT CInArchive::SortItems() for (i = 0; i < Items.Size(); i++) { CItem &item = Items[i]; - RINOK(_stream->Seek(GetPosOfNonSolidItem(i), STREAM_SEEK_SET, NULL)); + if (item.IsEmptyFile) + continue; + RINOK(SeekToNonSolidItem(i)) const UInt32 kSigSize = 4 + 1 + 1 + 4; // size,[flag],prop,dict BYTE sig[kSigSize]; size_t processedSize = kSigSize; - RINOK(ReadStream(_stream, sig, &processedSize)); + RINOK(ReadStream(_stream, sig, &processedSize)) if (processedSize < 4) return S_FALSE; UInt32 size = Get32(sig); @@ -4971,14 +5140,15 @@ HRESULT CInArchive::SortItems() return S_OK; } +#ifdef NSIS_SCRIPT // Flags for common_header.flags -#define CH_FLAGS_DETAILS_SHOWDETAILS 1 -#define CH_FLAGS_DETAILS_NEVERSHOW 2 +// #define CH_FLAGS_DETAILS_SHOWDETAILS 1 +// #define CH_FLAGS_DETAILS_NEVERSHOW 2 #define CH_FLAGS_PROGRESS_COLORED 4 #define CH_FLAGS_SILENT 8 #define CH_FLAGS_SILENT_LOG 16 #define CH_FLAGS_AUTO_CLOSE 32 -#define CH_FLAGS_DIR_NO_SHOW 64 // unused now +// #define CH_FLAGS_DIR_NO_SHOW 64 // unused now #define CH_FLAGS_NO_ROOT_DIR 128 #define CH_FLAGS_COMP_ONLY_ON_CUSTOM 256 #define CH_FLAGS_NO_CUSTOM 512 @@ -4990,36 +5160,71 @@ static const char * const k_PostStrings[] = , "uninstcmd" // NSIS 2.25+, used by uninstaller: , "wininit" // NSIS 2.25+, used by move file on reboot }; +#endif + + +void CBlockHeader::Parse(const Byte *p, unsigned bhoSize) +{ + if (bhoSize == 12) + { + // UInt64 a = GetUi64(p); + if (GetUi32(p + 4) != 0) + throw 1; + } + Offset = GetUi32(p); + Num = GetUi32(p + bhoSize - 4); +} + +#define PARSE_BH(k, bh) bh.Parse (p1 + 4 + bhoSize * k, bhoSize) + HRESULT CInArchive::Parse() { // UInt32 offset = ReadUInt32(); // ???? offset == FirstHeader.HeaderSize - const Byte *p = _data; + const Byte * const p1 = _data; + + if (_size < 4 + 12 * 8) + Is64Bit = false; + else + { + Is64Bit = true; + // here we test high 32-bit of possible UInt64 CBlockHeader::Offset field + for (int k = 0; k < 8; k++) + if (GetUi32(p1 + 4 + 12 * k + 4) != 0) + Is64Bit = false; + } + + const unsigned bhoSize = Is64Bit ? 12 : 8; + if (_size < 4 + bhoSize * 8) + return S_FALSE; CBlockHeader bhEntries, bhStrings, bhLangTables; - bhEntries.Parse(p + 4 + 8 * 2); - bhStrings.Parse(p + 4 + 8 * 3); - bhLangTables.Parse(p + 4 + 8 * 4); + + PARSE_BH (2, bhEntries); + PARSE_BH (3, bhStrings); + PARSE_BH (4, bhLangTables); #ifdef NSIS_SCRIPT CBlockHeader bhFont; - bhPages.Parse(p + 4 + 8 * 0); - bhSections.Parse(p + 4 + 8 * 1); - bhCtlColors.Parse(p + 4 + 8 * 5); - bhFont.Parse(p + 4 + 8 * 6); - bhData.Parse(p + 4 + 8 * 7); + PARSE_BH (0, bhPages); + PARSE_BH (1, bhSections); + PARSE_BH (5, bhCtlColors); + PARSE_BH (6, bhFont); + PARSE_BH (7, bhData); #endif _stringsPos = bhStrings.Offset; - if (_stringsPos > _size) + if (_stringsPos > _size + || bhLangTables.Offset > _size + || bhEntries.Offset > _size) return S_FALSE; { if (bhLangTables.Offset < bhStrings.Offset) return S_FALSE; - UInt32 stringTableSize = bhLangTables.Offset - bhStrings.Offset; + const UInt32 stringTableSize = bhLangTables.Offset - bhStrings.Offset; if (stringTableSize < 2) return S_FALSE; const Byte *strData = _data + _stringsPos; @@ -5035,19 +5240,22 @@ HRESULT CInArchive::Parse() if (strData[stringTableSize - 2] != 0) return S_FALSE; } - } if (bhEntries.Num > (1 << 25)) return S_FALSE; - if (bhEntries.Offset > _size) - return S_FALSE; if (bhEntries.Num * kCmdSize > _size - bhEntries.Offset) return S_FALSE; DetectNsisType(bhEntries, _data + bhEntries.Offset); Decoder.IsNsisDeflate = (NsisType != k_NsisType_Nsis3); + + // some NSIS files (that are not detected as k_NsisType_Nsis3) + // use original (non-NSIS) Deflate + // How to detect these cases? + + // Decoder.IsNsisDeflate = false; #ifdef NSIS_SCRIPT @@ -5066,17 +5274,22 @@ HRESULT CInArchive::Parse() } AddLF(); - if (IsUnicode) + if (Is64Bit) + AddStringLF("Target AMD64-Unicode"); // TODO: Read PE machine type and use the correct CPU type + else if (IsUnicode) AddStringLF("Unicode true"); + else if (IsNsis3_OrHigher()) + AddStringLF("Unicode false"); // Unicode is the default in 3.07+ if (Method != NMethodType::kCopy) { const char *m = NULL; - switch (Method) + switch ((int)Method) { case NMethodType::kDeflate: m = "zlib"; break; case NMethodType::kBZip2: m = "bzip2"; break; case NMethodType::kLZMA: m = "lzma"; break; + default: break; } Script += "SetCompressor"; if (IsSolid) @@ -5160,7 +5373,7 @@ HRESULT CInArchive::Parse() memset(strUsed, 0, NumStringChars); { - UInt32 ehFlags = Get32(p); + UInt32 ehFlags = Get32(p1); UInt32 showDetails = ehFlags & 3;// CH_FLAGS_DETAILS_SHOWDETAILS & CH_FLAGS_DETAILS_NEVERSHOW; if (showDetails >= 1 && showDetails <= 2) { @@ -5201,11 +5414,16 @@ HRESULT CInArchive::Parse() } } - unsigned paramsOffset = 4 + 8 * 8; - if (bhPages.Offset == 276) - paramsOffset -= 8; + unsigned paramsOffset; + { + unsigned numBhs = 8; + // probably its for old NSIS? + if (bhoSize == 8 && bhPages.Offset == 276) + numBhs = 7; + paramsOffset = 4 + bhoSize * numBhs; + } - const Byte *p2 = p + paramsOffset; + const Byte *p2 = p1 + paramsOffset; { UInt32 rootKey = Get32(p2); // (rootKey = -1) in uninstaller by default (the bug in NSIS) @@ -5254,7 +5472,8 @@ HRESULT CInArchive::Parse() } UInt32 license_bg = Get32(p2 + 36); - if (license_bg != (UInt32)(Int32)-1 && license_bg != -15) // COLOR_BTNFACE + if (license_bg != (UInt32)(Int32)-1 && + license_bg != (UInt32)(Int32)-15) // COLOR_BTNFACE { Script += "LicenseBkColor"; if ((Int32)license_bg == -5) // COLOR_WINDOW @@ -5268,13 +5487,19 @@ HRESULT CInArchive::Parse() AddLF(); } - UInt32 langtable_size = Get32(p2 + 32); if (bhLangTables.Num > 0) { + const UInt32 langtable_size = Get32(p2 + 32); + if (langtable_size == (UInt32)(Int32)-1) return E_NOTIMPL; // maybe it's old NSIS archive() - UInt32 numStrings = (langtable_size - 10) / 4; + if (langtable_size < 10) + return S_FALSE; + if (bhLangTables.Num > (_size - bhLangTables.Offset) / langtable_size) + return S_FALSE; + + const UInt32 numStrings = (langtable_size - 10) / 4; _numLangStrings = numStrings; AddLF(); Separator(); @@ -5282,17 +5507,17 @@ HRESULT CInArchive::Parse() PrintNumComment("LANG STRINGS", numStrings); AddLF(); - if (licenseLangIndex >= 0) + if (licenseLangIndex >= 0 && (unsigned)licenseLangIndex < numStrings) { for (UInt32 i = 0; i < bhLangTables.Num; i++) { - const Byte *p = _data + bhLangTables.Offset + langtable_size * i; - LANGID langID = Get16(p); + const Byte * const p = _data + bhLangTables.Offset + langtable_size * i; + const UInt16 langID = Get16(p); UInt32 val = Get32(p + 10 + (UInt32)licenseLangIndex * 4); if (val != 0) { Script += "LicenseLangString "; - Add_LangStr_Simple(licenseLangIndex); + Add_LangStr_Simple((UInt32)licenseLangIndex); AddParam_UInt(langID); AddLicense(val, langID); noParseStringIndexes.AddToUniqueSorted(val); @@ -5302,33 +5527,24 @@ HRESULT CInArchive::Parse() AddLF(); } - UInt32 brandingText = 0; - UInt32 caption = 0; - UInt32 name = 0; + UInt32 names[3] = { 0 }; + UInt32 i; for (i = 0; i < bhLangTables.Num; i++) { - const Byte *p = _data + bhLangTables.Offset + langtable_size * i; - LANGID langID = Get16(p); + const Byte * const p = _data + bhLangTables.Offset + langtable_size * i; + const UInt16 langID = Get16(p); if (i == 0 || langID == 1033) _mainLang = p + 10; + for (unsigned k = 0; k < Z7_ARRAY_SIZE(names) && k < numStrings; k++) { - UInt32 v = Get32(p + 10 + 0 * 4); - if (v != 0 && (langID == 1033 || brandingText == 0)) - brandingText = v; - } - { - UInt32 v = Get32(p + 10 + 1 * 4); - if (v != 0 && (langID == 1033 || caption == 0)) - caption = v; - } - { - UInt32 v = Get32(p + 10 + 2 * 4); - if (v != 0 && (langID == 1033 || name == 0)) - name = v; + UInt32 v = Get32(p + 10 + k * 4); + if (v != 0 && (langID == 1033 || names[k] == 0)) + names[k] = v; } } - + + const UInt32 name = names[2]; if (name != 0) { Script += "Name"; @@ -5339,6 +5555,7 @@ HRESULT CInArchive::Parse() } /* + const UInt32 caption = names[1]; if (caption != 0) { Script += "Caption"; @@ -5347,6 +5564,7 @@ HRESULT CInArchive::Parse() } */ + const UInt32 brandingText = names[0]; if (brandingText != 0) { Script += "BrandingText"; @@ -5358,8 +5576,8 @@ HRESULT CInArchive::Parse() for (i = 0; i < bhLangTables.Num; i++) { - const Byte *p = _data + bhLangTables.Offset + langtable_size * i; - LANGID langID = Get16(p); + const Byte * const p = _data + bhLangTables.Offset + langtable_size * i; + const UInt16 langID = Get16(p); AddLF(); AddCommentAndString("LANG:"); @@ -5415,7 +5633,7 @@ HRESULT CInArchive::Parse() } onFuncOffset = paramsOffset + 40; - numOnFunc = ARRAY_SIZE(kOnFunc); + numOnFunc = Z7_ARRAY_SIZE(kOnFunc); if (bhPages.Offset == 276) numOnFunc--; p2 += 40 + numOnFunc * 4; @@ -5477,7 +5695,7 @@ HRESULT CInArchive::Parse() #endif - RINOK(ReadEntries(bhEntries)); + RINOK(ReadEntries(bhEntries)) #ifdef NSIS_SCRIPT @@ -5595,16 +5813,19 @@ HRESULT CInArchive::Open2(const Byte *sig, size_t size) if (IsSolid) { - RINOK(_stream->Seek(DataStreamOffset, STREAM_SEEK_SET, NULL)); + RINOK(SeekTo_DataStreamOffset()) } else { _headerIsCompressed = ((compressedHeaderSize & kMask_IsCompressed) != 0); compressedHeaderSize &= ~kMask_IsCompressed; _nonSolidStartOffset = compressedHeaderSize; - RINOK(_stream->Seek(DataStreamOffset + 4, STREAM_SEEK_SET, NULL)); + RINOK(SeekTo(DataStreamOffset + 4)) } + if (FirstHeader.HeaderSize == 0) + return S_FALSE; + _data.Alloc(FirstHeader.HeaderSize); _size = (size_t)FirstHeader.HeaderSize; @@ -5620,21 +5841,23 @@ HRESULT CInArchive::Open2(const Byte *sig, size_t size) if (_headerIsCompressed) { - RINOK(Decoder.Init(_stream, UseFilter)); + RINOK(Decoder.Init(_stream, UseFilter)) if (IsSolid) { size_t processedSize = 4; Byte buf[4]; - RINOK(Decoder.Read(buf, &processedSize)); + RINOK(Decoder.Read(buf, &processedSize)) if (processedSize != 4) return S_FALSE; if (Get32((const Byte *)buf) != FirstHeader.HeaderSize) return S_FALSE; } - size_t processedSize = FirstHeader.HeaderSize; - RINOK(Decoder.Read(_data, &processedSize)); - if (processedSize != FirstHeader.HeaderSize) - return S_FALSE; + { + size_t processedSize = FirstHeader.HeaderSize; + RINOK(Decoder.Read(_data, &processedSize)) + if (processedSize != FirstHeader.HeaderSize) + return S_FALSE; + } #ifdef NSIS_SCRIPT if (IsSolid) @@ -5643,7 +5866,7 @@ HRESULT CInArchive::Open2(const Byte *sig, size_t size) AfterHeaderSize = (1 << 12); _afterHeader.Alloc(AfterHeaderSize); size_t processedSize = AfterHeaderSize; - RINOK(Decoder.Read(_afterHeader, &processedSize)); + RINOK(Decoder.Read(_afterHeader, &processedSize)) AfterHeaderSize = (UInt32)processedSize; } #endif @@ -5651,7 +5874,7 @@ HRESULT CInArchive::Open2(const Byte *sig, size_t size) else { size_t processedSize = FirstHeader.HeaderSize; - RINOK(ReadStream(_stream, (Byte *)_data, &processedSize)); + RINOK(ReadStream(_stream, (Byte *)_data, &processedSize)) if (processedSize < FirstHeader.HeaderSize) return S_FALSE; } @@ -5735,7 +5958,7 @@ HRESULT CInArchive::Open(IInStream *inStream, const UInt64 *maxCheckStartPositio { Clear(); - RINOK(inStream->Seek(0, STREAM_SEEK_CUR, &StartOffset)); + RINOK(InStream_GetPos(inStream, StartOffset)) const UInt32 kStartHeaderSize = 4 * 7; const unsigned kStep = 512; // nsis start is aligned for 512 @@ -5747,7 +5970,7 @@ HRESULT CInArchive::Open(IInStream *inStream, const UInt64 *maxCheckStartPositio for (;;) { bufSize = kStep; - RINOK(ReadStream(inStream, buf, &bufSize)); + RINOK(ReadStream(inStream, buf, &bufSize)) if (bufSize < kStartHeaderSize) return S_FALSE; if (memcmp(buf + 4, kSignature, kSignatureSize) == 0) @@ -5779,8 +6002,8 @@ HRESULT CInArchive::Open(IInStream *inStream, const UInt64 *maxCheckStartPositio if (pos - posCur > (1 << 20)) break; bufSize = kStep; - RINOK(inStream->Seek(posCur, STREAM_SEEK_SET, NULL)); - RINOK(ReadStream(inStream, buf, &bufSize)); + RINOK(InStream_SeekSet(inStream, posCur)) + RINOK(ReadStream(inStream, buf, &bufSize)) if (bufSize < kStep) break; if (IsArc_Pe(buf, bufSize)) @@ -5792,8 +6015,8 @@ HRESULT CInArchive::Open(IInStream *inStream, const UInt64 *maxCheckStartPositio // restore buf to nsis header bufSize = kStep; - RINOK(inStream->Seek(pos, STREAM_SEEK_SET, NULL)); - RINOK(ReadStream(inStream, buf, &bufSize)); + RINOK(InStream_SeekSet(inStream, pos)) + RINOK(ReadStream(inStream, buf, &bufSize)) if (bufSize < kStartHeaderSize) return S_FALSE; } @@ -5814,23 +6037,34 @@ HRESULT CInArchive::Open(IInStream *inStream, const UInt64 *maxCheckStartPositio DataStreamOffset = pos + kStartHeaderSize; FirstHeader.Flags = Get32(buf); if ((FirstHeader.Flags & (~kFlagsMask)) != 0) + { + // return E_NOTIMPL; return S_FALSE; + } IsInstaller = (FirstHeader.Flags & NFlags::kUninstall) == 0; FirstHeader.HeaderSize = Get32(buf + kSignatureSize + 4); FirstHeader.ArcSize = Get32(buf + kSignatureSize + 8); if (FirstHeader.ArcSize <= kStartHeaderSize) return S_FALSE; - - RINOK(inStream->Seek(0, STREAM_SEEK_END, &_fileSize)); + + /* + if ((FirstHeader.Flags & NFlags::k_BI_ExternalFileSupport) != 0) + { + UInt32 datablock_low = Get32(buf + kSignatureSize + 12); + UInt32 datablock_high = Get32(buf + kSignatureSize + 16); + } + */ + + RINOK(InStream_GetSize_SeekToEnd(inStream, _fileSize)) IsArc = true; if (peSize != 0) { ExeStub.Alloc(peSize); - RINOK(inStream->Seek(pePos, STREAM_SEEK_SET, NULL)); - RINOK(ReadStream_FALSE(inStream, ExeStub, peSize)); + RINOK(InStream_SeekSet(inStream, pePos)) + RINOK(ReadStream_FALSE(inStream, ExeStub, peSize)) } HRESULT res = S_FALSE; @@ -5862,7 +6096,8 @@ UString CInArchive::ConvertToUnicode(const AString &s) const if (IsUnicode) { UString res; - if (ConvertUTF8ToUnicode(s, res)) + // if ( + ConvertUTF8ToUnicode(s, res); return res; } return MultiByteToUnicodeString(s); @@ -5876,6 +6111,7 @@ void CInArchive::Clear2() IsNsis200 = false; LogCmdIsEnabled = false; BadCmd = -1; + Is64Bit = false; #ifdef NSIS_SCRIPT Name.Empty(); diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Nsis/NsisIn.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Nsis/NsisIn.h index d8e6808b..1f8c9675 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Nsis/NsisIn.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Nsis/NsisIn.h @@ -1,7 +1,7 @@ // NsisIn.h -#ifndef __ARCHIVE_NSIS_IN_H -#define __ARCHIVE_NSIS_IN_H +#ifndef ZIP7_INC_ARCHIVE_NSIS_IN_H +#define ZIP7_INC_ARCHIVE_NSIS_IN_H #include "../../../../C/CpuArch.h" @@ -11,6 +11,8 @@ #include "../../../Common/StringConvert.h" #include "../../../Common/UTFConvert.h" +#include "../../Common/StreamUtils.h" + #include "NsisDecode.h" /* If NSIS_SCRIPT is defined, it will decompile NSIS script to [NSIS].nsi file. @@ -34,6 +36,11 @@ namespace NFlags const UInt32 kSilent = 2; const UInt32 kNoCrc = 4; const UInt32 kForceCrc = 8; + // NSISBI fork flags: + const UInt32 k_BI_LongOffset = 16; + const UInt32 k_BI_ExternalFileSupport = 32; + const UInt32 k_BI_ExternalFile = 64; + const UInt32 k_BI_IsStubInstaller = 128; } struct CFirstHeader @@ -58,15 +65,12 @@ struct CBlockHeader UInt32 Offset; UInt32 Num; - void Parse(const Byte *p) - { - Offset = GetUi32(p); - Num = GetUi32(p + 4); - } + void Parse(const Byte *p, unsigned bhoSize); }; struct CItem { + bool IsEmptyFile; bool IsCompressed; bool Size_Defined; bool CompressedSize_Defined; @@ -76,7 +80,7 @@ struct CItem // bool UseFilter; UInt32 Attrib; - UInt32 Pos; + UInt32 Pos; // = 0, if (IsEmptyFile == true) UInt32 Size; UInt32 CompressedSize; UInt32 EstimatedSize; @@ -88,7 +92,10 @@ struct CItem AString NameA; UString NameU; + bool Is_PatchedUninstaller() const { return PatchSize != 0; } + CItem(): + IsEmptyFile(false), IsCompressed(true), Size_Defined(false), CompressedSize_Defined(false), @@ -159,6 +166,7 @@ class CInArchive CByteBuffer _data; CObjectVector Items; bool IsUnicode; + bool Is64Bit; private: UInt32 _stringsPos; // relative to _data UInt32 NumStringChars; @@ -170,11 +178,11 @@ class CInArchive ENsisType NsisType; bool IsNsis200; // NSIS 2.03 and before bool IsNsis225; // NSIS 2.25 and before - bool LogCmdIsEnabled; int BadCmd; // -1: no bad command; in another cases lowest bad command id bool IsPark() const { return NsisType >= k_NsisType_Park1; } + bool IsNsis3_OrHigher() const { return NsisType == k_NsisType_Nsis3; } UInt64 _fileSize; @@ -350,14 +358,19 @@ class CInArchive return Decoder.Init(_stream, useFilter); } + HRESULT SeekTo(UInt64 pos) + { + return InStream_SeekSet(_stream, pos); + } + HRESULT SeekTo_DataStreamOffset() { - return _stream->Seek(DataStreamOffset, STREAM_SEEK_SET, NULL); + return SeekTo(DataStreamOffset); } HRESULT SeekToNonSolidItem(unsigned index) { - return _stream->Seek(GetPosOfNonSolidItem(index), STREAM_SEEK_SET, NULL); + return SeekTo(GetPosOfNonSolidItem(index)); } void Clear(); @@ -403,31 +416,31 @@ class CInArchive s = MultiByteToUnicodeString(APrefixes[item.Prefix]); if (s.Len() > 0) if (s.Back() != L'\\') - s += L'\\'; + s.Add_Char('\\'); } if (IsUnicode) { s += item.NameU; if (item.NameU.IsEmpty()) - s += L"file"; + s += "file"; } else { s += MultiByteToUnicodeString(item.NameA); if (item.NameA.IsEmpty()) - s += L"file"; + s += "file"; } - const char *kRemoveStr = "$INSTDIR\\"; + const char * const kRemoveStr = "$INSTDIR\\"; if (s.IsPrefixedBy_Ascii_NoCase(kRemoveStr)) { s.Delete(0, MyStringLen(kRemoveStr)); if (s[0] == L'\\') s.DeleteFrontal(1); } - if (item.IsUninstaller && ExeStub.Size() == 0) - s += L".nsis"; + if (item.Is_PatchedUninstaller() && ExeStub.Size() == 0) + s += ".nsis"; return s; } diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Nsis/NsisRegister.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/Nsis/NsisRegister.cpp index 7230c3c2..a5003817 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Nsis/NsisRegister.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Nsis/NsisRegister.cpp @@ -10,7 +10,7 @@ namespace NArchive { namespace NNsis { REGISTER_ARC_I( - "Nsis", "nsis", 0, 0x9, + "Nsis", "nsis", NULL, 0x9, kSignature, 4, NArcInfoFlags::kFindSignature | diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Nsis/StdAfx.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Nsis/StdAfx.h index 2854ff3e..035267cc 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Nsis/StdAfx.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Nsis/StdAfx.h @@ -1,8 +1,11 @@ // StdAfx.h -#ifndef __STDAFX_H -#define __STDAFX_H +#ifndef ZIP7_INC_STDAFX_H +#define ZIP7_INC_STDAFX_H +#if defined(_MSC_VER) && _MSC_VER >= 1800 +#pragma warning(disable : 4464) // relative include path contains '..' +#endif #include "../../../Common/Common.h" #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/NtfsHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/NtfsHandler.cpp index a6350944..006f0ad1 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/NtfsHandler.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/NtfsHandler.cpp @@ -47,18 +47,18 @@ #define Get32(p) GetUi32(p) #define Get64(p) GetUi64(p) -#define G16(p, dest) dest = Get16(p); -#define G32(p, dest) dest = Get32(p); -#define G64(p, dest) dest = Get64(p); +#define G16(p, dest) dest = Get16(p) +#define G32(p, dest) dest = Get32(p) +#define G64(p, dest) dest = Get64(p) using namespace NWindows; namespace NArchive { namespace Ntfs { -static const wchar_t *kVirtualFolder_System = L"[SYSTEM]"; -static const wchar_t *kVirtualFolder_Lost_Normal = L"[LOST]"; -static const wchar_t *kVirtualFolder_Lost_Deleted = L"[UNKNOWN]"; +static const wchar_t * const kVirtualFolder_System = L"[SYSTEM]"; +static const wchar_t * const kVirtualFolder_Lost_Normal = L"[LOST]"; +static const wchar_t * const kVirtualFolder_Lost_Deleted = L"[UNKNOWN]"; static const unsigned kNumSysRecs = 16; @@ -71,14 +71,15 @@ struct CHeader { unsigned SectorSizeLog; unsigned ClusterSizeLog; + unsigned MftRecordSizeLog; // Byte MediaType; - UInt32 NumHiddenSectors; + // UInt32 NumHiddenSectors; UInt64 NumSectors; UInt64 NumClusters; UInt64 MftCluster; UInt64 SerialNumber; - UInt16 SectorsPerTrack; - UInt16 NumHeads; + // UInt16 SectorsPerTrack; + // UInt16 NumHeads; UInt64 GetPhySize_Clusters() const { return NumClusters << ClusterSizeLog; } UInt64 GetPhySize_Max() const { return (NumSectors + 1) << SectorSizeLog; } @@ -111,30 +112,42 @@ bool CHeader::Parse(const Byte *p) if (memcmp(p + 3, "NTFS ", 8) != 0) return false; { - int t = GetLog(Get16(p + 11)); - if (t < 9 || t > 12) - return false; - SectorSizeLog = t; - t = GetLog(p[13]); - if (t < 0) - return false; - sectorsPerClusterLog = t; - ClusterSizeLog = SectorSizeLog + sectorsPerClusterLog; - if (ClusterSizeLog > 30) - return false; + { + const int t = GetLog(Get16(p + 11)); + if (t < 9 || t > 12) + return false; + SectorSizeLog = (unsigned)t; + } + { + const unsigned v = p[13]; + if (v <= 0x80) + { + const int t = GetLog(v); + if (t < 0) + return false; + sectorsPerClusterLog = (unsigned)t; + } + else + sectorsPerClusterLog = 0x100 - v; + ClusterSizeLog = SectorSizeLog + sectorsPerClusterLog; + if (ClusterSizeLog > 21) + return false; + } } for (int i = 14; i < 21; i++) if (p[i] != 0) return false; + // F8 : a hard disk + // F0 : high-density 3.5-inch floppy disk if (p[21] != 0xF8) // MediaType = Fixed_Disk return false; if (Get16(p + 22) != 0) // NumFatSectors return false; - G16(p + 24, SectorsPerTrack); // 63 usually - G16(p + 26, NumHeads); // 255 - G32(p + 28, NumHiddenSectors); // 63 (XP) / 2048 (Vista and win7) / (0 on media that are not partitioned ?) + // G16(p + 24, SectorsPerTrack); // 63 usually + // G16(p + 26, NumHeads); // 255 + // G32(p + 28, NumHiddenSectors); // 63 (XP) / 2048 (Vista and win7) / (0 on media that are not partitioned ?) if (Get32(p + 32) != 0) // NumSectors32 return false; @@ -156,14 +169,47 @@ bool CHeader::Parse(const Byte *p) NumClusters = NumSectors >> sectorsPerClusterLog; - G64(p + 0x30, MftCluster); + G64(p + 0x30, MftCluster); // $MFT. // G64(p + 0x38, Mft2Cluster); - G64(p + 0x48, SerialNumber); - UInt32 numClustersInMftRec; - UInt32 numClustersInIndexBlock; - G32(p + 0x40, numClustersInMftRec); // -10 means 2 ^10 = 1024 bytes. - G32(p + 0x44, numClustersInIndexBlock); - return (numClustersInMftRec < 256 && numClustersInIndexBlock < 256); + G64(p + 0x48, SerialNumber); // $MFTMirr + + /* + numClusters_per_MftRecord: + numClusters_per_IndexBlock: + only low byte from 4 bytes is used. Another 3 high bytes are zeros. + If the number is positive (number < 0x80), + then it represents the number of clusters. + If the number is negative (number >= 0x80), + then the size of the file record is 2 raised to the absolute value of this number. + example: (0xF6 == -10) means 2^10 = 1024 bytes. + */ + { + UInt32 numClusters_per_MftRecord; + G32(p + 0x40, numClusters_per_MftRecord); + if (numClusters_per_MftRecord >= 0x100 || numClusters_per_MftRecord == 0) + return false; + if (numClusters_per_MftRecord < 0x80) + { + const int t = GetLog(numClusters_per_MftRecord); + if (t < 0) + return false; + MftRecordSizeLog = (unsigned)t + ClusterSizeLog; + } + else + MftRecordSizeLog = 0x100 - numClusters_per_MftRecord; + // what exact MFT record sizes are possible and supported by Windows? + // do we need to change this limit here? + const unsigned k_MftRecordSizeLog_MAX = 12; + if (MftRecordSizeLog > k_MftRecordSizeLog_MAX) + return false; + if (MftRecordSizeLog < SectorSizeLog) + return false; + } + { + UInt32 numClusters_per_IndexBlock; + G32(p + 0x44, numClusters_per_IndexBlock); + return (numClusters_per_IndexBlock < 0x100); + } } struct CMftRef @@ -173,6 +219,8 @@ struct CMftRef UInt64 GetIndex() const { return Val & (((UInt64)1 << 48) - 1); } UInt16 GetNumber() const { return (UInt16)(Val >> 48); } bool IsBaseItself() const { return Val == 0; } + + CMftRef(): Val(0) {} }; #define ATNAME(n) ATTR_TYPE_ ## n @@ -208,7 +256,7 @@ enum Posix name can be after or before Win32 name */ -static const Byte kFileNameType_Posix = 0; // for hard links +// static const Byte kFileNameType_Posix = 0; // for hard links static const Byte kFileNameType_Win32 = 1; // after Dos name static const Byte kFileNameType_Dos = 2; // short name static const Byte kFileNameType_Win32Dos = 3; // short and full name are same @@ -233,9 +281,14 @@ struct CFileNameAttr bool IsWin32() const { return (NameType == kFileNameType_Win32); } bool Parse(const Byte *p, unsigned size); + + CFileNameAttr(): + Attrib(0), + NameType(0) + {} }; -static void GetString(const Byte *p, unsigned len, UString2 &res) +static void GetString(const Byte *p, const unsigned len, UString2 &res) { if (len == 0 && res.IsEmpty()) return; @@ -243,7 +296,7 @@ static void GetString(const Byte *p, unsigned len, UString2 &res) unsigned i; for (i = 0; i < len; i++) { - wchar_t c = Get16(p + i * 2); + const wchar_t c = Get16(p + i * 2); if (c == 0) break; s[i] = c; @@ -266,8 +319,8 @@ bool CFileNameAttr::Parse(const Byte *p, unsigned size) G32(p + 0x38, Attrib); // G16(p + 0x3C, PackedEaSize); NameType = p[0x41]; - unsigned len = p[0x40]; - if (0x42 + len > size) + const unsigned len = p[0x40]; + if (0x42 + len * 2 > size) return false; if (len != 0) GetString(p + 0x42, len, Name); @@ -278,7 +331,7 @@ struct CSiAttr { UInt64 CTime; UInt64 MTime; - // UInt64 ThisRecMTime; + UInt64 ThisRecMTime; UInt64 ATime; UInt32 Attrib; @@ -292,15 +345,25 @@ struct CSiAttr // UInt64 QuotaCharged; bool Parse(const Byte *p, unsigned size); + + CSiAttr(): + CTime(0), + MTime(0), + ThisRecMTime(0), + ATime(0), + Attrib(0), + SecurityId(0) + {} }; + bool CSiAttr::Parse(const Byte *p, unsigned size) { if (size < 0x24) return false; G64(p + 0x00, CTime); G64(p + 0x08, MTime); - // G64(p + 0x10, ThisRecMTime); + G64(p + 0x10, ThisRecMTime); G64(p + 0x18, ATime); G32(p + 0x20, Attrib); SecurityId = 0; @@ -341,15 +404,19 @@ bool CVolInfo::Parse(const Byte *p, unsigned size) struct CAttr { UInt32 Type; + + Byte NonResident; + + // Non-Resident + Byte CompressionUnit; + // UInt32 Len; UString2 Name; // UInt16 Flags; // UInt16 Instance; CByteBuffer Data; - Byte NonResident; // Non-Resident - Byte CompressionUnit; UInt64 LowVcn; UInt64 HighVcn; UInt64 AllocatedSize; @@ -378,13 +445,13 @@ struct CAttr } }; -#define RINOZ(x) { int __tt = (x); if (__tt != 0) return __tt; } +#define RINOZ(x) { int _tt_ = (x); if (_tt_ != 0) return _tt_; } static int CompareAttr(void *const *elem1, void *const *elem2, void *) { - const CAttr &a1 = *(*((const CAttr **)elem1)); - const CAttr &a2 = *(*((const CAttr **)elem2)); - RINOZ(MyCompare(a1.Type, a2.Type)); + const CAttr &a1 = *(*((const CAttr *const *)elem1)); + const CAttr &a2 = *(*((const CAttr *const *)elem2)); + RINOZ(MyCompare(a1.Type, a2.Type)) if (a1.Name.IsEmpty()) { if (!a2.Name.IsEmpty()) @@ -394,7 +461,7 @@ static int CompareAttr(void *const *elem1, void *const *elem2, void *) return 1; else { - RINOZ(wcscmp(a1.Name.GetRawPtr(), a2.Name.GetRawPtr())); + RINOZ(a1.Name.Compare(a2.Name.GetRawPtr())) } return MyCompare(a1.LowVcn, a2.LowVcn); } @@ -408,15 +475,16 @@ UInt32 CAttr::Parse(const Byte *p, unsigned size) return 8; // required size is 4, but attributes are 8 bytes aligned. So we return 8 if (size < 0x18) return 0; + PRF(printf(" T=%2X", Type)); - UInt32 len = Get32(p + 0x04); + UInt32 len = Get32(p + 4); PRF(printf(" L=%3d", len)); if (len > size) return 0; if ((len & 7) != 0) return 0; - NonResident = p[0x08]; + NonResident = p[8]; { unsigned nameLen = p[9]; UInt32 nameOffset = Get16(p + 0x0A); @@ -437,6 +505,7 @@ UInt32 CAttr::Parse(const Byte *p, unsigned size) UInt32 dataSize; UInt32 offs; + if (NonResident) { if (len < 0x40) @@ -472,16 +541,19 @@ UInt32 CAttr::Parse(const Byte *p, unsigned size) { if (len < 0x18) return 0; + G32(p + 0x10, dataSize); + G16(p + 0x14, offs); + // G16(p + 0x16, ResidentFlags); PRF(printf(" RES")); - dataSize = Get32(p + 0x10); PRF(printf(" dataSize=%3d", dataSize)); - offs = Get16(p + 0x14); - // G16(p + 0x16, ResidentFlags); // PRF(printf(" ResFlags=%4X", ResidentFlags)); } + if (offs > len || dataSize > len || len - dataSize < offs) return 0; + Data.CopyFrom(p + offs, dataSize); + #ifdef SHOW_DEBUG_INFO PRF(printf(" : ")); for (unsigned i = 0; i < Data.Size(); i++) @@ -489,16 +561,19 @@ UInt32 CAttr::Parse(const Byte *p, unsigned size) PRF(printf(" %02X", (unsigned)Data[i])); } #endif + return len; } + bool CAttr::ParseExtents(CRecordVector &extents, UInt64 numClustersMax, unsigned compressionUnit) const { const Byte *p = Data; unsigned size = (unsigned)Data.Size(); UInt64 vcn = LowVcn; UInt64 lcn = 0; - UInt64 highVcn1 = HighVcn + 1; + const UInt64 highVcn1 = HighVcn + 1; + if (LowVcn != extents.Back().Virt || highVcn1 > (UInt64)1 << 63) return false; @@ -508,11 +583,11 @@ bool CAttr::ParseExtents(CRecordVector &extents, UInt64 numClustersMax, while (size > 0) { - Byte b = *p++; + const unsigned b = *p++; size--; if (b == 0) break; - UInt32 num = b & 0xF; + unsigned num = b & 0xF; if (num == 0 || num > 8 || num > size) return false; @@ -528,15 +603,30 @@ bool CAttr::ParseExtents(CRecordVector &extents, UInt64 numClustersMax, if ((highVcn1 - vcn) < vSize) return false; - num = (b >> 4) & 0xF; - if (num > 8 || num > size) - return false; CExtent e; e.Virt = vcn; + vcn += vSize; + + num = b >> 4; + if (num > 8 || num > size) + return false; + if (num == 0) { + // Sparse + + /* if Unit is compressed, it can have many Elements for each compressed Unit: + and last Element for unit MUST be without LCN. + Element 0: numCompressedClusters2, LCN_0 + Element 1: numCompressedClusters2, LCN_1 + ... + Last Element : (16 - total_clusters_in_previous_elements), no LCN + */ + + // sparse is not allowed for (compressionUnit == 0) ? Why ? if (compressionUnit == 0) return false; + e.Phy = kEmptyExtent; } else @@ -548,14 +638,15 @@ bool CAttr::ParseExtents(CRecordVector &extents, UInt64 numClustersMax, } p += num; size -= num; - lcn += v; + lcn = (UInt64)((Int64)lcn + v); if (lcn > numClustersMax) return false; e.Phy = lcn; } + extents.Add(e); - vcn += vSize; } + CExtent e; e.Phy = kEmptyExtent; e.Virt = vcn; @@ -563,23 +654,23 @@ bool CAttr::ParseExtents(CRecordVector &extents, UInt64 numClustersMax, return (highVcn1 == vcn); } + static const UInt64 kEmptyTag = (UInt64)(Int64)-1; static const unsigned kNumCacheChunksLog = 1; -static const size_t kNumCacheChunks = (1 << kNumCacheChunksLog); +static const size_t kNumCacheChunks = (size_t)1 << kNumCacheChunksLog; -class CInStream: - public IInStream, - public CMyUnknownImp -{ +Z7_CLASS_IMP_IInStream( + CInStream +) UInt64 _virtPos; UInt64 _physPos; UInt64 _curRem; bool _sparseMode; - - +public: + bool InUse; +private: unsigned _chunkSizeLog; - UInt64 _tags[kNumCacheChunks]; CByteBuffer _inBuf; CByteBuffer _outBuf; public: @@ -588,12 +679,13 @@ class CInStream: unsigned BlockSizeLog; unsigned CompressionUnit; CRecordVector Extents; - bool InUse; CMyComPtr Stream; +private: + UInt64 _tags[kNumCacheChunks]; - HRESULT SeekToPhys() { return Stream->Seek(_physPos, STREAM_SEEK_SET, NULL); } - + HRESULT SeekToPhys() { return InStream_SeekSet(Stream, _physPos); } UInt32 GetCuSize() const { return (UInt32)1 << (BlockSizeLog + CompressionUnit); } +public: HRESULT InitAndSeek(unsigned compressionUnit) { CompressionUnit = compressionUnit; @@ -616,11 +708,6 @@ class CInStream: _physPos = e.Phy << BlockSizeLog; return SeekToPhys(); } - - MY_UNKNOWN_IMP1(IInStream) - - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); - STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition); }; static size_t Lznt1Dec(Byte *dest, size_t outBufLim, size_t destLen, const Byte *src, size_t srcLen) @@ -688,12 +775,16 @@ static size_t Lznt1Dec(Byte *dest, size_t outBufLim, size_t destLen, const Byte UInt32 dist = (v >> (16 - numDistBits)); if (dist >= sbOffset) return 0; - Int32 offs = -1 - dist; - Byte *p = dest + destSize; - for (UInt32 t = 0; t < len; t++) - p[t] = p[t + offs]; + const size_t offs = 1 + dist; + Byte *p = dest + destSize - offs; destSize += len; sbOffset += len; + const Byte *lim = p + len; + p[offs] = *p; ++p; + p[offs] = *p; ++p; + do + p[offs] = *p; + while (++p != lim); } } } @@ -704,7 +795,7 @@ static size_t Lznt1Dec(Byte *dest, size_t outBufLim, size_t destLen, const Byte return destSize; } -STDMETHODIMP CInStream::Read(void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CInStream::Read(void *data, UInt32 size, UInt32 *processedSize)) { if (processedSize) *processedSize = 0; @@ -734,24 +825,27 @@ STDMETHODIMP CInStream::Read(void *data, UInt32 size, UInt32 *processedSize) while (_curRem == 0) { - UInt64 cacheTag = _virtPos >> _chunkSizeLog; - UInt32 cacheIndex = (UInt32)cacheTag & (kNumCacheChunks - 1); + const UInt64 cacheTag = _virtPos >> _chunkSizeLog; + const size_t cacheIndex = (size_t)cacheTag & (kNumCacheChunks - 1); + if (_tags[cacheIndex] == cacheTag) { - UInt32 chunkSize = (UInt32)1 << _chunkSizeLog; - UInt32 offset = (UInt32)_virtPos & (chunkSize - 1); - UInt32 cur = MyMin(chunkSize - offset, size); + const size_t chunkSize = (size_t)1 << _chunkSizeLog; + const size_t offset = (size_t)_virtPos & (chunkSize - 1); + size_t cur = chunkSize - offset; + if (cur > size) + cur = size; memcpy(data, _outBuf + (cacheIndex << _chunkSizeLog) + offset, cur); - *processedSize = cur; + *processedSize = (UInt32)cur; _virtPos += cur; return S_OK; } PRF2(printf("\nVirtPos = %6d", _virtPos)); - UInt32 comprUnitSize = (UInt32)1 << CompressionUnit; - UInt64 virtBlock = _virtPos >> BlockSizeLog; - UInt64 virtBlock2 = virtBlock & ~((UInt64)comprUnitSize - 1); + const UInt32 comprUnitSize = (UInt32)1 << CompressionUnit; + const UInt64 virtBlock = _virtPos >> BlockSizeLog; + const UInt64 virtBlock2 = virtBlock & ~((UInt64)comprUnitSize - 1); unsigned left = 0, right = Extents.Size(); for (;;) @@ -766,7 +860,7 @@ STDMETHODIMP CInStream::Read(void *data, UInt32 size, UInt32 *processedSize) } bool isCompressed = false; - UInt64 virtBlock2End = virtBlock2 + comprUnitSize; + const UInt64 virtBlock2End = virtBlock2 + comprUnitSize; if (CompressionUnit != 0) for (unsigned i = left; i < Extents.Size(); i++) { @@ -791,7 +885,7 @@ STDMETHODIMP CInStream::Read(void *data, UInt32 size, UInt32 *processedSize) if (newPos != _physPos) { _physPos = newPos; - RINOK(SeekToPhys()); + RINOK(SeekToPhys()) } UInt64 next = Extents[i + 1].Virt; if (next > virtBlock2End) @@ -802,7 +896,9 @@ STDMETHODIMP CInStream::Read(void *data, UInt32 size, UInt32 *processedSize) _curRem = next - _virtPos; break; } + bool thereArePhy = false; + for (unsigned i2 = left; i2 < Extents.Size(); i2++) { const CExtent &e = Extents[i2]; @@ -814,6 +910,7 @@ STDMETHODIMP CInStream::Read(void *data, UInt32 size, UInt32 *processedSize) break; } } + if (!thereArePhy) { _curRem = (Extents[i + 1].Virt << BlockSizeLog) - _virtPos; @@ -823,6 +920,7 @@ STDMETHODIMP CInStream::Read(void *data, UInt32 size, UInt32 *processedSize) size_t offs = 0; UInt64 curVirt = virtBlock2; + for (i = left; i < Extents.Size(); i++) { const CExtent &e = Extents[i]; @@ -830,29 +928,30 @@ STDMETHODIMP CInStream::Read(void *data, UInt32 size, UInt32 *processedSize) break; if (e.Virt >= virtBlock2End) return S_FALSE; - UInt64 newPos = (e.Phy + (curVirt - e.Virt)) << BlockSizeLog; + const UInt64 newPos = (e.Phy + (curVirt - e.Virt)) << BlockSizeLog; if (newPos != _physPos) { _physPos = newPos; - RINOK(SeekToPhys()); + RINOK(SeekToPhys()) } UInt64 numChunks = Extents[i + 1].Virt - curVirt; if (curVirt + numChunks > virtBlock2End) numChunks = virtBlock2End - curVirt; - size_t compressed = (size_t)numChunks << BlockSizeLog; - RINOK(ReadStream_FALSE(Stream, _inBuf + offs, compressed)); + const size_t compressed = (size_t)numChunks << BlockSizeLog; + RINOK(ReadStream_FALSE(Stream, _inBuf + offs, compressed)) curVirt += numChunks; _physPos += compressed; offs += compressed; } - size_t destLenMax = GetCuSize(); + + const size_t destLenMax = GetCuSize(); size_t destLen = destLenMax; const UInt64 rem = Size - (virtBlock2 << BlockSizeLog); if (destLen > rem) destLen = (size_t)rem; Byte *dest = _outBuf + (cacheIndex << _chunkSizeLog); - size_t destSizeRes = Lznt1Dec(dest, destLenMax, destLen, _inBuf, offs); + const size_t destSizeRes = Lznt1Dec(dest, destLenMax, destLen, _inBuf, offs); _tags[cacheIndex] = cacheTag; // some files in Vista have destSize > destLen @@ -863,6 +962,7 @@ STDMETHODIMP CInStream::Read(void *data, UInt32 size, UInt32 *processedSize) return S_FALSE; } } + if (size > _curRem) size = (UInt32)_curRem; HRESULT res = S_OK; @@ -880,7 +980,7 @@ STDMETHODIMP CInStream::Read(void *data, UInt32 size, UInt32 *processedSize) return res; } -STDMETHODIMP CInStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition) +Z7_COM7F_IMF(CInStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition)) { switch (seekOrigin) { @@ -894,10 +994,10 @@ STDMETHODIMP CInStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPositio if (_virtPos != (UInt64)offset) { _curRem = 0; - _virtPos = offset; + _virtPos = (UInt64)offset; } if (newPosition) - *newPosition = offset; + *newPosition = (UInt64)offset; return S_OK; } @@ -913,6 +1013,12 @@ static HRESULT DataParseExtents(unsigned clusterSizeLog, const CObjectVector> clusterSizeLog)) + { + } + */ + if (attr0.AllocatedSize < attr0.Size || (attrs[attrIndexLim - 1].HighVcn + 1) != (attr0.AllocatedSize >> clusterSizeLog) || (attr0.AllocatedSize & ((1 << clusterSizeLog) - 1)) != 0) @@ -954,6 +1060,15 @@ struct CDataRef static const UInt32 kMagic_FILE = 0x454C4946; static const UInt32 kMagic_BAAD = 0x44414142; +// 22.02: we support some rare case magic values: +static const UInt32 kMagic_INDX = 0x58444e49; +static const UInt32 kMagic_HOLE = 0x454c4f48; +static const UInt32 kMagic_RSTR = 0x52545352; +static const UInt32 kMagic_RCRD = 0x44524352; +static const UInt32 kMagic_CHKD = 0x444b4843; +static const UInt32 kMagic_FFFFFFFF = 0xFFFFFFFF; + + struct CMftRec { UInt32 Magic; @@ -985,7 +1100,7 @@ struct CMftRec { const CFileNameAttr &next = FileNames[i]; if (next.IsWin32() && cur.ParentDirRef.Val == next.ParentDirRef.Val) - return i; + return (int)i; } return -1; } @@ -998,7 +1113,7 @@ struct CMftRec { const CFileNameAttr &next = FileNames[i]; if (next.IsDos() && cur.ParentDirRef.Val == next.ParentDirRef.Val) - return i; + return (int)i; } return -1; } @@ -1030,9 +1145,25 @@ struct CMftRec bool Parse(Byte *p, unsigned sectorSizeLog, UInt32 numSectors, UInt32 recNumber, CObjectVector *attrs); - bool IsEmpty() const { return (Magic <= 2); } - bool IsFILE() const { return (Magic == kMagic_FILE); } - bool IsBAAD() const { return (Magic == kMagic_BAAD); } + bool Is_Magic_Empty() const + { + // what exact Magic values are possible for empty and unused records? + const UInt32 k_Magic_Unused_MAX = 5; // 22.02 + return (Magic <= k_Magic_Unused_MAX); + } + bool Is_Magic_FILE() const { return (Magic == kMagic_FILE); } + // bool Is_Magic_BAAD() const { return (Magic == kMagic_BAAD); } + bool Is_Magic_CanIgnore() const + { + return Is_Magic_Empty() + || Magic == kMagic_BAAD + || Magic == kMagic_INDX + || Magic == kMagic_HOLE + || Magic == kMagic_RSTR + || Magic == kMagic_RCRD + || Magic == kMagic_CHKD + || Magic == kMagic_FFFFFFFF; + } bool InUse() const { return (Flags & 1) != 0; } bool IsDir() const { return (Flags & 2) != 0; } @@ -1044,13 +1175,17 @@ struct CMftRec UInt64 GetSize(unsigned dataIndex) const { return DataAttrs[DataRefs[dataIndex].Start].GetSize(); } - CMftRec(): MyNumNameLinks(0), MyItemIndex(-1) {} + CMftRec(): + SeqNumber(0), + Flags(0), + MyNumNameLinks(0), + MyItemIndex(-1) {} }; void CMftRec::ParseDataNames() { DataRefs.Clear(); - DataAttrs.Sort(CompareAttr, 0); + DataAttrs.Sort(CompareAttr, NULL); for (unsigned i = 0; i < DataAttrs.Size();) { @@ -1067,7 +1202,7 @@ void CMftRec::ParseDataNames() HRESULT CMftRec::GetStream(IInStream *mainStream, int dataIndex, unsigned clusterSizeLog, UInt64 numPhysClusters, IInStream **destStream) const { - *destStream = 0; + *destStream = NULL; CBufferInStream *streamSpec = new CBufferInStream; CMyComPtr streamTemp = streamSpec; @@ -1089,13 +1224,13 @@ HRESULT CMftRec::GetStream(IInStream *mainStream, int dataIndex, return S_FALSE; CInStream *ss = new CInStream; CMyComPtr streamTemp2 = ss; - RINOK(DataParseExtents(clusterSizeLog, DataAttrs, ref.Start, ref.Start + ref.Num, numPhysClusters, ss->Extents)); + RINOK(DataParseExtents(clusterSizeLog, DataAttrs, ref.Start, ref.Start + ref.Num, numPhysClusters, ss->Extents)) ss->Size = attr0.Size; ss->InitializedSize = attr0.InitializedSize; ss->Stream = mainStream; ss->BlockSizeLog = clusterSizeLog; ss->InUse = InUse(); - RINOK(ss->InitAndSeek(attr0.CompressionUnit)); + RINOK(ss->InitAndSeek(attr0.CompressionUnit)) *destStream = streamTemp2.Detach(); return S_OK; } @@ -1141,9 +1276,8 @@ bool CMftRec::Parse(Byte *p, unsigned sectorSizeLog, UInt32 numSectors, UInt32 r CObjectVector *attrs) { G32(p, Magic); - if (!IsFILE()) - return IsEmpty() || IsBAAD(); - + if (!Is_Magic_FILE()) + return Is_Magic_CanIgnore(); { UInt32 usaOffset; @@ -1180,7 +1314,7 @@ bool CMftRec::Parse(Byte *p, unsigned sectorSizeLog, UInt32 numSectors, UInt32 r void *pp = p + (i << sectorSizeLog) - 2; if (Get16(pp) != usn) return false; - SetUi16(pp, Get16(p + usaOffset + i * 2)); + SetUi16(pp, Get16(p + usaOffset + i * 2)) } } @@ -1188,12 +1322,12 @@ bool CMftRec::Parse(Byte *p, unsigned sectorSizeLog, UInt32 numSectors, UInt32 r G16(p + 0x10, SeqNumber); // G16(p + 0x12, LinkCount); // PRF(printf(" L=%d", LinkCount)); - UInt32 attrOffs = Get16(p + 0x14); + const UInt32 attrOffs = Get16(p + 0x14); G16(p + 0x16, Flags); PRF(printf(" F=%4X", Flags)); - UInt32 bytesInUse = Get32(p + 0x18); - UInt32 bytesAlloc = Get32(p + 0x1C); + const UInt32 bytesInUse = Get32(p + 0x18); + const UInt32 bytesAlloc = Get32(p + 0x1C); G64(p + 0x20, BaseMftRef.Val); if (BaseMftRef.Val != 0) { @@ -1299,7 +1433,12 @@ struct CItem int ParentHost; /* index in Items array, if it's AltStream -1: if it's not AltStream */ - CItem(): DataIndex(k_Item_DataIndex_IsDir), ParentFolder(-1), ParentHost(-1) {} + void Construct() + { + DataIndex = k_Item_DataIndex_IsDir; + ParentFolder = -1; + ParentHost = -1; + } bool IsAltStream() const { return ParentHost != -1; } bool IsDir() const { return DataIndex == k_Item_DataIndex_IsDir; } @@ -1314,7 +1453,6 @@ struct CDatabase CObjectVector Recs; CMyComPtr InStream; CHeader Header; - unsigned RecSizeLog; UInt64 PhySize; IArchiveOpenCallback *OpenCallback; @@ -1326,6 +1464,9 @@ struct CDatabase CByteBuffer SecurData; CRecordVector SecurOffsets; + // bool _headerWarning; + bool ThereAreAltStreams; + bool _showSystemFiles; bool _showDeletedFiles; CObjectVector VirtFolderNames; @@ -1335,10 +1476,6 @@ struct CDatabase int _lostFolderIndex_Normal; int _lostFolderIndex_Deleted; - // bool _headerWarning; - - bool ThereAreAltStreams; - void InitProps() { _showSystemFiles = true; @@ -1358,7 +1495,7 @@ struct CDatabase HRESULT SeekToCluster(UInt64 cluster); - int FindDirItemForMtfRec(UInt64 recIndex) const + int Find_DirItem_For_MftRec(UInt64 recIndex) const { if (recIndex >= Recs.Size()) return -1; @@ -1407,7 +1544,7 @@ struct CDatabase HRESULT CDatabase::SeekToCluster(UInt64 cluster) { - return InStream->Seek(cluster << Header.ClusterSizeLog, STREAM_SEEK_SET, NULL); + return InStream_SeekSet(InStream, cluster << Header.ClusterSizeLog); } void CDatabase::Clear() @@ -1431,6 +1568,21 @@ void CDatabase::ClearAndClose() InStream.Release(); } + +static void CopyName(wchar_t *dest, const wchar_t *src) +{ + for (;;) + { + wchar_t c = *src++; + // 18.06 + if (c == '\\' || c == '/') + c = '_'; + *dest++ = c; + if (c == 0) + return; + } +} + void CDatabase::GetItemPath(unsigned index, NCOM::CPropVariant &path) const { const CItem *item = &Items[index]; @@ -1448,7 +1600,7 @@ void CDatabase::GetItemPath(unsigned index, NCOM::CPropVariant &path) const wchar_t *s = path.AllocBstr(data.Name.Len() + 1); s[0] = L':'; if (!data.Name.IsEmpty()) - MyStringCopy(s + 1, data.Name.GetRawPtr()); + CopyName(s + 1, data.Name.GetRawPtr()); return; } @@ -1497,7 +1649,7 @@ void CDatabase::GetItemPath(unsigned index, NCOM::CPropVariant &path) const if (!name.IsEmpty()) { size -= name.Len(); - MyStringCopy(s + size, name.GetRawPtr()); + CopyName(s + size, name.GetRawPtr()); } s[--size] = ':'; needColon = true; @@ -1507,7 +1659,7 @@ void CDatabase::GetItemPath(unsigned index, NCOM::CPropVariant &path) const const UString2 &name = rec.FileNames[item->NameIndex].Name; unsigned len = name.Len(); if (len != 0) - MyStringCopy(s + size - len, name.GetRawPtr()); + CopyName(s + size - len, name.GetRawPtr()); if (needColon) s[size] = ':'; size -= len; @@ -1531,7 +1683,7 @@ void CDatabase::GetItemPath(unsigned index, NCOM::CPropVariant &path) const if (len != 0) { size -= len; - MyStringCopy(s + size, name.GetRawPtr()); + CopyName(s + size, name.GetRawPtr()); } s[size + len] = WCHAR_PATH_SEPARATOR; continue; @@ -1639,14 +1791,14 @@ HRESULT CDatabase::Open() */ { - static const UInt32 kHeaderSize = 512; + const UInt32 kHeaderSize = 512; Byte buf[kHeaderSize]; - RINOK(ReadStream_FALSE(InStream, buf, kHeaderSize)); + RINOK(ReadStream_FALSE(InStream, buf, kHeaderSize)) if (!Header.Parse(buf)) return S_FALSE; UInt64 fileSize; - RINOK(InStream->Seek(0, STREAM_SEEK_END, &fileSize)); + RINOK(InStream_GetSize_SeekToEnd(InStream, fileSize)) PhySize = Header.GetPhySize_Clusters(); if (fileSize < PhySize) return S_FALSE; @@ -1654,7 +1806,7 @@ HRESULT CDatabase::Open() UInt64 phySizeMax = Header.GetPhySize_Max(); if (fileSize >= phySizeMax) { - RINOK(InStream->Seek(Header.NumSectors << Header.SectorSizeLog, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekSet(InStream, Header.NumSectors << Header.SectorSizeLog)) Byte buf2[kHeaderSize]; if (ReadStream_FALSE(InStream, buf2, kHeaderSize) == S_OK) { @@ -1667,59 +1819,48 @@ HRESULT CDatabase::Open() SeekToCluster(Header.MftCluster); - CMftRec mftRec; - UInt32 numSectorsInRec; - + // we use ByteBuf for records reading. + // so the size of ByteBuf must be >= mftRecordSize + const size_t recSize = (size_t)1 << Header.MftRecordSizeLog; + const size_t kBufSize = MyMax((size_t)(1 << 15), recSize); + ByteBuf.Alloc(kBufSize); + RINOK(ReadStream_FALSE(InStream, ByteBuf, recSize)) + { + const UInt32 allocSize = Get32(ByteBuf + 0x1C); + if (allocSize != recSize) + return S_FALSE; + } + // MftRecordSizeLog >= SectorSizeLog + const UInt32 numSectorsInRec = 1u << (Header.MftRecordSizeLog - Header.SectorSizeLog); CMyComPtr mftStream; + CMftRec mftRec; { - UInt32 blockSize = 1 << 12; - ByteBuf.Alloc(blockSize); - RINOK(ReadStream_FALSE(InStream, ByteBuf, blockSize)); - - { - UInt32 allocSize = Get32(ByteBuf + 0x1C); - int t = GetLog(allocSize); - if (t < (int)Header.SectorSizeLog) - return S_FALSE; - RecSizeLog = t; - if (RecSizeLog > 15) - return S_FALSE; - } - - numSectorsInRec = 1 << (RecSizeLog - Header.SectorSizeLog); if (!mftRec.Parse(ByteBuf, Header.SectorSizeLog, numSectorsInRec, 0, NULL)) return S_FALSE; - if (!mftRec.IsFILE()) + if (!mftRec.Is_Magic_FILE()) return S_FALSE; mftRec.ParseDataNames(); if (mftRec.DataRefs.IsEmpty()) return S_FALSE; - RINOK(mftRec.GetStream(InStream, 0, Header.ClusterSizeLog, Header.NumClusters, &mftStream)); + RINOK(mftRec.GetStream(InStream, 0, Header.ClusterSizeLog, Header.NumClusters, &mftStream)) if (!mftStream) return S_FALSE; } // CObjectVector SecurityAttrs; - UInt64 mftSize = mftRec.DataAttrs[0].Size; + const UInt64 mftSize = mftRec.DataAttrs[0].Size; if ((mftSize >> 4) > Header.GetPhySize_Clusters()) return S_FALSE; - const size_t kBufSize = (1 << 15); - const size_t recSize = ((size_t)1 << RecSizeLog); - if (kBufSize < recSize) - return S_FALSE; - { - const UInt64 numFiles = mftSize >> RecSizeLog; + const UInt64 numFiles = mftSize >> Header.MftRecordSizeLog; if (numFiles > (1 << 30)) return S_FALSE; if (OpenCallback) { - RINOK(OpenCallback->SetTotal(&numFiles, &mftSize)); + RINOK(OpenCallback->SetTotal(&numFiles, &mftSize)) } - - ByteBuf.Alloc(kBufSize); Recs.ClearAndReserve((unsigned)numFiles); } @@ -1728,9 +1869,9 @@ HRESULT CDatabase::Open() if (OpenCallback) { const UInt64 numFiles = Recs.Size(); - if ((numFiles & 0x3FF) == 0) + if ((numFiles & 0x3FFF) == 0) { - RINOK(OpenCallback->SetCompleted(&numFiles, &pos64)); + RINOK(OpenCallback->SetCompleted(&numFiles, &pos64)) } } size_t readSize = kBufSize; @@ -1741,7 +1882,7 @@ HRESULT CDatabase::Open() } if (readSize < recSize) break; - RINOK(ReadStream_FALSE(mftStream, ByteBuf, readSize)); + RINOK(ReadStream_FALSE(mftStream, ByteBuf, readSize)) pos64 += readSize; for (size_t i = 0; readSize >= recSize; i += recSize, readSize -= recSize) @@ -1771,7 +1912,7 @@ HRESULT CDatabase::Open() for (i = 0; i < SecurityAttrs.Size(); i++) { const CAttr &attr = SecurityAttrs[i]; - if (attr.Name == L"$SII") + if (attr.Name.IsEqualTo("$SII")) { if (attr.Type == ATTR_TYPE_INDEX_ROOT) { @@ -1817,12 +1958,18 @@ HRESULT CDatabase::Open() for (i = 0; i < Recs.Size(); i++) { CMftRec &rec = Recs[i]; + if (!rec.Is_Magic_FILE()) + continue; + if (!rec.BaseMftRef.IsBaseItself()) { - UInt64 refIndex = rec.BaseMftRef.GetIndex(); - if (refIndex > (UInt32)Recs.Size()) + const UInt64 refIndex = rec.BaseMftRef.GetIndex(); + if (refIndex >= Recs.Size()) return S_FALSE; CMftRec &refRec = Recs[(unsigned)refIndex]; + if (!refRec.Is_Magic_FILE()) + continue; + bool moveAttrs = (refRec.SeqNumber == rec.BaseMftRef.GetNumber() && refRec.BaseMftRef.IsBaseItself()); if (rec.InUse() && refRec.InUse()) { @@ -1837,12 +1984,17 @@ HRESULT CDatabase::Open() } for (i = 0; i < Recs.Size(); i++) - Recs[i].ParseDataNames(); + { + CMftRec &rec = Recs[i]; + if (!rec.Is_Magic_FILE()) + continue; + rec.ParseDataNames(); + } for (i = 0; i < Recs.Size(); i++) { CMftRec &rec = Recs[i]; - if (!rec.IsFILE() || !rec.BaseMftRef.IsBaseItself()) + if (!rec.Is_Magic_FILE() || !rec.BaseMftRef.IsBaseItself()) continue; if (i < kNumSysRecs && !_showSystemFiles) continue; @@ -1864,7 +2016,7 @@ HRESULT CDatabase::Open() FOR_VECTOR (di, rec.DataRefs) if (rec.DataAttrs[rec.DataRefs[di].Start].Name.IsEmpty()) { - indexOfUnnamedStream = di; + indexOfUnnamedStream = (int)di; break; } } @@ -1913,6 +2065,7 @@ HRESULT CDatabase::Open() } CItem item; + item.Construct(); item.NameIndex = t; item.RecIndex = i; item.DataIndex = rec.IsDir() ? @@ -1922,14 +2075,14 @@ HRESULT CDatabase::Open() indexOfUnnamedStream); if (rec.MyItemIndex < 0) - rec.MyItemIndex = Items.Size(); - item.ParentHost = Items.Add(item); + rec.MyItemIndex = (int)Items.Size(); + item.ParentHost = (int)Items.Add(item); /* we can use that code to reduce the number of alt streams: it will not show how alt streams for hard links. */ // if (!isMainName) continue; isMainName = false; - unsigned numAltStreams = 0; + // unsigned numAltStreams = 0; FOR_VECTOR (di, rec.DataRefs) { @@ -1947,9 +2100,9 @@ HRESULT CDatabase::Open() continue; } - numAltStreams++; + // numAltStreams++; ThereAreAltStreams = true; - item.DataIndex = di; + item.DataIndex = (int)di; Items.Add(item); } } @@ -1964,10 +2117,10 @@ HRESULT CDatabase::Open() if (attr.Name == L"$SDS") { CMyComPtr sdsStream; - RINOK(rec.GetStream(InStream, di, Header.ClusterSizeLog, Header.NumClusters, &sdsStream)); + RINOK(rec.GetStream(InStream, (int)di, Header.ClusterSizeLog, Header.NumClusters, &sdsStream)) if (sdsStream) { - UInt64 size64 = attr.GetSize(); + const UInt64 size64 = attr.GetSize(); if (size64 < (UInt32)1 << 29) { size_t size = (size_t)size64; @@ -1997,12 +2150,12 @@ HRESULT CDatabase::Open() const CMftRec &rec = Recs[item.RecIndex]; const CFileNameAttr &fn = rec.FileNames[item.NameIndex]; const CMftRef &parentDirRef = fn.ParentDirRef; - UInt64 refIndex = parentDirRef.GetIndex(); + const UInt64 refIndex = parentDirRef.GetIndex(); if (refIndex == kRecIndex_RootDir) item.ParentFolder = -1; else { - int index = FindDirItemForMtfRec(refIndex); + int index = Find_DirItem_For_MftRec(refIndex); if (index < 0 || Recs[Items[index].RecIndex].SeqNumber != parentDirRef.GetNumber()) { @@ -2024,57 +2177,52 @@ HRESULT CDatabase::Open() unsigned virtIndex = Items.Size(); if (_showSystemFiles) { - _systemFolderIndex = virtIndex++; + _systemFolderIndex = (int)(virtIndex++); VirtFolderNames.Add(kVirtualFolder_System); } if (thereAreUnknownFolders_Normal) { - _lostFolderIndex_Normal = virtIndex++; + _lostFolderIndex_Normal = (int)(virtIndex++); VirtFolderNames.Add(kVirtualFolder_Lost_Normal); } if (thereAreUnknownFolders_Deleted) { - _lostFolderIndex_Deleted = virtIndex++; + _lostFolderIndex_Deleted = (int)(virtIndex++); VirtFolderNames.Add(kVirtualFolder_Lost_Deleted); } return S_OK; } -class CHandler: +Z7_class_CHandler_final: public IInArchive, public IArchiveGetRawProps, public IInArchiveGetStream, public ISetProperties, public CMyUnknownImp, - CDatabase + public CDatabase { -public: - MY_UNKNOWN_IMP4( + Z7_IFACES_IMP_UNK_4( IInArchive, IArchiveGetRawProps, IInArchiveGetStream, ISetProperties) - INTERFACE_IInArchive(;) - INTERFACE_IArchiveGetRawProps(;) - STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream); - STDMETHOD(SetProperties)(const wchar_t * const *names, const PROPVARIANT *values, UInt32 numProps); }; -STDMETHODIMP CHandler::GetNumRawProps(UInt32 *numProps) +Z7_COM7F_IMF(CHandler::GetNumRawProps(UInt32 *numProps)) { *numProps = 2; return S_OK; } -STDMETHODIMP CHandler::GetRawPropInfo(UInt32 index, BSTR *name, PROPID *propID) +Z7_COM7F_IMF(CHandler::GetRawPropInfo(UInt32 index, BSTR *name, PROPID *propID)) { *name = NULL; *propID = index == 0 ? kpidNtReparse : kpidNtSecure; return S_OK; } -STDMETHODIMP CHandler::GetParent(UInt32 index, UInt32 *parent, UInt32 *parentType) +Z7_COM7F_IMF(CHandler::GetParent(UInt32 index, UInt32 *parent, UInt32 *parentType)) { *parentType = NParentType::kDir; int par = -1; @@ -2104,7 +2252,7 @@ STDMETHODIMP CHandler::GetParent(UInt32 index, UInt32 *parent, UInt32 *parentTyp return S_OK; } -STDMETHODIMP CHandler::GetRawProp(UInt32 index, PROPID propID, const void **data, UInt32 *dataSize, UInt32 *propType) +Z7_COM7F_IMF(CHandler::GetRawProp(UInt32 index, PROPID propID, const void **data, UInt32 *dataSize, UInt32 *propType)) { *data = NULL; *dataSize = 0; @@ -2129,7 +2277,7 @@ STDMETHODIMP CHandler::GetRawProp(UInt32 index, PROPID propID, const void **data *data = (const wchar_t *)EmptyString; else *data = s->GetRawPtr(); - *dataSize = (s->Len() + 1) * sizeof(wchar_t); + *dataSize = (s->Len() + 1) * (UInt32)sizeof(wchar_t); *propType = PROP_DATA_TYPE_wchar_t_PTR_Z_LE; #endif return S_OK; @@ -2173,10 +2321,10 @@ STDMETHODIMP CHandler::GetRawProp(UInt32 index, PROPID propID, const void **data return S_OK; } -STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream) +Z7_COM7F_IMF(CHandler::GetStream(UInt32 index, ISequentialInStream **stream)) { COM_TRY_BEGIN - *stream = 0; + *stream = NULL; if (index >= Items.Size()) return S_OK; IInStream *stream2; @@ -2238,6 +2386,7 @@ static const Byte kProps[] = kpidMTime, kpidCTime, kpidATime, + kpidChangeTime, kpidAttrib, kpidLinks, kpidINode, @@ -2259,7 +2408,7 @@ static const CStatProp kArcProps[] = { NULL, kpidFileSystem, VT_BSTR}, { NULL, kpidClusterSize, VT_UI4}, { NULL, kpidSectorSize, VT_UI4}, - { "Record Size", kpidRecordSize, VT_UI4}, + { "MFT Record Size", kpidRecordSize, VT_UI4}, { NULL, kpidHeadersSize, VT_UI8}, { NULL, kpidCTime, VT_FILETIME}, { NULL, kpidId, VT_UI8}, @@ -2293,7 +2442,7 @@ static void NtfsTimeToProp(UInt64 t, NCOM::CPropVariant &prop) prop = ft; } -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; @@ -2340,7 +2489,7 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) } case kpidFileSystem: { - AString s = "NTFS"; + AString s ("NTFS"); FOR_VECTOR (i, VolAttrs) { const CAttr &attr = VolAttrs[i]; @@ -2350,12 +2499,9 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) if (attr.ParseVolInfo(vi)) { s.Add_Space(); - char temp[16]; - ConvertUInt32ToString(vi.MajorVer, temp); - s += temp; - s += '.'; - ConvertUInt32ToString(vi.MinorVer, temp); - s += temp; + s.Add_UInt32(vi.MajorVer); + s.Add_Dot(); + s.Add_UInt32(vi.MinorVer); } break; } @@ -2364,7 +2510,7 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) break; } case kpidSectorSize: prop = (UInt32)1 << Header.SectorSizeLog; break; - case kpidRecordSize: prop = (UInt32)1 << RecSizeLog; break; + case kpidRecordSize: prop = (UInt32)1 << Header.MftRecordSizeLog; break; case kpidId: prop = Header.SerialNumber; break; case kpidIsTree: prop = true; break; @@ -2400,7 +2546,7 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) COM_TRY_END } -STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; @@ -2468,7 +2614,7 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val { // const CMftRec &rec = Recs[item.RecIndex]; // prop = ((UInt64)rec.SeqNumber << 48) | item.RecIndex; - prop = item.RecIndex; + prop = (UInt32)item.RecIndex; break; } case kpidStreamId: @@ -2517,7 +2663,7 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val case kpidMTime: NtfsTimeToProp(rec.SiAttr.MTime, prop); break; case kpidCTime: NtfsTimeToProp(rec.SiAttr.CTime, prop); break; case kpidATime: NtfsTimeToProp(rec.SiAttr.ATime, prop); break; - // case kpidRecMTime: if (fn) NtfsTimeToProp(rec.SiAttr.ThisRecMTime, prop); break; + case kpidChangeTime: NtfsTimeToProp(rec.SiAttr.ThisRecMTime, prop); break; /* case kpidMTime2: if (fn) NtfsTimeToProp(fn->MTime, prop); break; @@ -2561,7 +2707,7 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val if (!rec.IsDir() && rec.DataAttrs[rec.DataRefs[0].Start].Name.IsEmpty()) num--; if (num > 0) - prop = num; + prop = (UInt32)num; } } break; @@ -2576,7 +2722,7 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val COM_TRY_END } -STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *callback) +Z7_COM7F_IMF(CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *callback)) { COM_TRY_BEGIN { @@ -2600,17 +2746,17 @@ STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallb COM_TRY_END } -STDMETHODIMP CHandler::Close() +Z7_COM7F_IMF(CHandler::Close()) { ClearAndClose(); return S_OK; } -STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, - Int32 testMode, IArchiveExtractCallback *extractCallback) +Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testMode, IArchiveExtractCallback *extractCallback)) { COM_TRY_BEGIN - bool allFilesMode = (numItems == (UInt32)(Int32)-1); + const bool allFilesMode = (numItems == (UInt32)(Int32)-1); if (allFilesMode) numItems = Items.Size(); if (numItems == 0) @@ -2619,15 +2765,15 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, UInt64 totalSize = 0; for (i = 0; i < numItems; i++) { - UInt32 index = allFilesMode ? i : indices[i]; + const UInt32 index = allFilesMode ? i : indices[i]; if (index >= (UInt32)Items.Size()) continue; const CItem &item = Items[allFilesMode ? i : indices[i]]; const CMftRec &rec = Recs[item.RecIndex]; if (item.DataIndex >= 0) - totalSize += rec.GetSize(item.DataIndex); + totalSize += rec.GetSize((unsigned)item.DataIndex); } - RINOK(extractCallback->SetTotal(totalSize)); + RINOK(extractCallback->SetTotal(totalSize)) UInt64 totalPackSize; totalSize = totalPackSize = 0; @@ -2635,32 +2781,30 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, UInt32 clusterSize = Header.ClusterSize(); CByteBuffer buf(clusterSize); - NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder(); - CMyComPtr copyCoder = copyCoderSpec; - - CLocalProgress *lps = new CLocalProgress; - CMyComPtr progress = lps; + CMyComPtr2_Create lps; lps->Init(extractCallback, false); + CMyComPtr2_Create copyCoder; + CMyComPtr2_Create outStream; - CDummyOutStream *outStreamSpec = new CDummyOutStream; - CMyComPtr outStream(outStreamSpec); - - for (i = 0; i < numItems; i++) + for (i = 0;; i++) { lps->InSize = totalPackSize; lps->OutSize = totalSize; - RINOK(lps->SetCur()); + RINOK(lps->SetCur()) + if (i >= numItems) + break; + CMyComPtr realOutStream; - Int32 askMode = testMode ? + const Int32 askMode = testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; - UInt32 index = allFilesMode ? i : indices[i]; - RINOK(extractCallback->GetStream(index, &realOutStream, askMode)); + const UInt32 index = allFilesMode ? i : indices[i]; + RINOK(extractCallback->GetStream(index, &realOutStream, askMode)) if (index >= (UInt32)Items.Size() || Items[index].IsDir()) { - RINOK(extractCallback->PrepareOperation(askMode)); - RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)); + RINOK(extractCallback->PrepareOperation(askMode)) + RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)) continue; } @@ -2668,11 +2812,11 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, if (!testMode && !realOutStream) continue; - RINOK(extractCallback->PrepareOperation(askMode)); + RINOK(extractCallback->PrepareOperation(askMode)) - outStreamSpec->SetStream(realOutStream); + outStream->SetStream(realOutStream); realOutStream.Release(); - outStreamSpec->Init(); + outStream->Init(); const CMftRec &rec = Recs[item.RecIndex]; @@ -2684,13 +2828,13 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, res = NExtract::NOperationResult::kUnsupportedMethod; else { - RINOK(hres); + RINOK(hres) if (inStream) { - hres = copyCoder->Code(inStream, outStream, NULL, NULL, progress); + hres = copyCoder.Interface()->Code(inStream, outStream, NULL, NULL, lps); if (hres != S_OK && hres != S_FALSE) { - RINOK(hres); + RINOK(hres) } if (/* copyCoderSpec->TotalSize == item.GetSize() && */ hres == S_OK) res = NExtract::NOperationResult::kOK; @@ -2703,39 +2847,41 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, totalPackSize += data.GetPackSize(); totalSize += data.GetSize(); } - outStreamSpec->ReleaseStream(); - RINOK(extractCallback->SetOperationResult(res)); + outStream->ReleaseStream(); + RINOK(extractCallback->SetOperationResult(res)) } return S_OK; COM_TRY_END } -STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) { *numItems = Items.Size() + VirtFolderNames.Size(); return S_OK; } -STDMETHODIMP CHandler::SetProperties(const wchar_t * const *names, const PROPVARIANT *values, UInt32 numProps) +Z7_COM7F_IMF(CHandler::SetProperties(const wchar_t * const *names, const PROPVARIANT *values, UInt32 numProps)) { InitProps(); for (UInt32 i = 0; i < numProps; i++) { - UString name = names[i]; - name.MakeLower_Ascii(); - if (name.IsEmpty()) - return E_INVALIDARG; - + const wchar_t *name = names[i]; const PROPVARIANT &prop = values[i]; - if (name.IsEqualTo("ld")) + if (StringsAreEqualNoCase_Ascii(name, "ld")) + { + RINOK(PROPVARIANT_to_bool(prop, _showDeletedFiles)) + } + else if (StringsAreEqualNoCase_Ascii(name, "ls")) + { + RINOK(PROPVARIANT_to_bool(prop, _showSystemFiles)) + } + else if (IsString1PrefixedByString2_NoCase_Ascii(name, "mt")) { - RINOK(PROPVARIANT_to_bool(prop, _showDeletedFiles)); } - else if (name.IsEqualTo("ls")) + else if (IsString1PrefixedByString2_NoCase_Ascii(name, "memuse")) { - RINOK(PROPVARIANT_to_bool(prop, _showSystemFiles)); } else return E_INVALIDARG; @@ -2746,7 +2892,7 @@ STDMETHODIMP CHandler::SetProperties(const wchar_t * const *names, const PROPVAR static const Byte k_Signature[] = { 'N', 'T', 'F', 'S', ' ', ' ', ' ', ' ', 0 }; REGISTER_ARC_I( - "NTFS", "ntfs img", 0, 0xD9, + "NTFS", "ntfs img", NULL, 0xD9, k_Signature, 3, 0, diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/PeHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/PeHandler.cpp index 7175cef3..f5f66b02 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/PeHandler.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/PeHandler.cpp @@ -28,9 +28,10 @@ #define G16(offs, v) v = Get16(p + (offs)) #define G32(offs, v) v = Get32(p + (offs)) +#define G32_signed(offs, v) v = (Int32)Get32(p + (offs)) #define G64(offs, v) v = Get64(p + (offs)) -#define RINOZ(x) { int __tt = (x); if (__tt != 0) return __tt; } +#define RINOZ(x) { int _tt_ = (x); if (_tt_ != 0) return _tt_; } using namespace NWindows; @@ -41,9 +42,8 @@ static const UInt32 k_Signature32 = 0x00004550; static HRESULT CalcCheckSum(ISequentialInStream *stream, UInt32 size, UInt32 excludePos, UInt32 &res) { - const UInt32 kBufSizeMax = (UInt32)1 << 16; - UInt32 bufSize = MyMin(kBufSizeMax, size); - bufSize += (bufSize & 1); + const UInt32 kBufSizeMax = (UInt32)1 << 15; + UInt32 bufSize = kBufSizeMax; CByteBuffer buffer(bufSize); Byte *buf = buffer; UInt32 sum = 0; @@ -56,11 +56,8 @@ static HRESULT CalcCheckSum(ISequentialInStream *stream, UInt32 size, UInt32 exc if (rem == 0) break; size_t processed = rem; - RINOK(ReadStream(stream, buf, &processed)); + RINOK(ReadStream(stream, buf, &processed)) - if ((processed & 1) != 0) - buf[processed] = 0; - for (unsigned j = 0; j < 4; j++) { UInt32 e = excludePos + j; @@ -72,11 +69,30 @@ static HRESULT CalcCheckSum(ISequentialInStream *stream, UInt32 size, UInt32 exc } } - for (size_t i = 0; i < processed; i += 2) + const unsigned kStep = (1 << 4); + { + for (size_t i = processed; (i & (kStep - 1)) != 0; i++) + buf[i] = 0; + } { - sum += Get16(buf + i); - sum = (sum + (sum >> 16)) & 0xFFFF; + const Byte *buf2 = buf; + const Byte *bufLimit = buf + processed; + UInt64 sum2 = 0; + for (; buf2 < bufLimit; buf2 += kStep) + { + UInt64 sum3 = (UInt64)Get32(buf2) + + Get32(buf2 + 4) + + Get32(buf2 + 8) + + Get32(buf2 + 12); + sum2 += sum3; + } + sum2 = (UInt32)(sum2) + (UInt64)(sum2 >> 32); + UInt32 sum3 = ((UInt32)sum2 + (UInt32)(sum2 >> 32)); + sum += (sum3 & 0xFFFF) + (sum3 >> 16); + sum = (sum & 0xFFFF) + (sum >> 16); + sum = (sum & 0xFFFF) + (sum >> 16); } + pos += (UInt32)processed; if (rem != processed) break; @@ -85,12 +101,6 @@ static HRESULT CalcCheckSum(ISequentialInStream *stream, UInt32 size, UInt32 exc return S_OK; } -static AString GetDecString(UInt32 v) -{ - char sz[16]; - ConvertUInt32ToString(v, sz); - return sz; -} struct CVersion { @@ -115,7 +125,8 @@ void CVersion::ToProp(NCOM::CPropVariant &prop) prop = sz; } -static const unsigned kHeaderSize = 4 + 20; +static const unsigned kCoffHeaderSize = 20; +static const unsigned kPeHeaderSize = 4 + kCoffHeaderSize; static const unsigned k_OptHeader32_Size_MIN = 96; static const unsigned k_OptHeader64_Size_MIN = 112; @@ -131,15 +142,14 @@ struct CHeader UInt16 OptHeaderSize; UInt16 Flags; - bool Parse(const Byte *p); + void ParseBase(const Byte *p); + bool ParseCoff(const Byte *p); + bool ParsePe(const Byte *p); bool IsDll() const { return (Flags & PE_IMAGE_FILE_DLL) != 0; } }; -bool CHeader::Parse(const Byte *p) +void CHeader::ParseBase(const Byte *p) { - if (Get32(p) != k_Signature32) - return false; - p += 4; G16( 0, Machine); G16( 2, NumSections); G32( 4, Time); @@ -147,6 +157,13 @@ bool CHeader::Parse(const Byte *p) G32(12, NumSymbols); G16(16, OptHeaderSize); G16(18, Flags); +} + +bool CHeader::ParsePe(const Byte *p) +{ + if (Get32(p) != k_Signature32) + return false; + ParseBase(p + 4); return OptHeaderSize >= k_OptHeader32_Size_MIN; } @@ -163,9 +180,32 @@ struct CDirLink } }; + +// IMAGE_DIRECTORY_ENTRY_* +static const char * const g_Dir_Names[] = +{ + "EXPORT" + , "IMPORT" + , "RESOURCE" + , "EXCEPTION" + , "SECURITY" + , "BASERELOC" + , "DEBUG" + , "ARCHITECTURE" // "COPYRIGHT" + , "GLOBALPTR" + , "TLS" + , "LOAD_CONFIG" + , "BOUND_IMPORT" + , "IAT" + , "DELAY_IMPORT" + , "COM_DESCRIPTOR" +}; + enum { + kDirLink_EXCEPTION = 3, kDirLink_Certificate = 4, + kDirLink_BASERELOC = 5, kDirLink_Debug = 6 }; @@ -212,7 +252,7 @@ struct COptHeader UInt32 UninitDataSize; // UInt32 AddressOfEntryPoint; - // UInt32 BaseOfCode; + // UInt32 BaseOfCode; // VA(.text) == 0x1000 in most cases // UInt32 BaseOfData32; UInt64 ImageBase; @@ -242,9 +282,9 @@ struct COptHeader int GetNumFileAlignBits() const { - for (unsigned i = 0; i <= 31; i++) + for (unsigned i = 0; i < 32; i++) if (((UInt32)1 << i) == FileAlign) - return i; + return (int)i; return -1; } @@ -256,6 +296,7 @@ struct COptHeader } }; +// size is 16-bit bool COptHeader::Parse(const Byte *p, UInt32 size) { if (size < k_OptHeader32_Size_MIN) @@ -317,13 +358,18 @@ bool COptHeader::Parse(const Byte *p, UInt32 size) pos = 92; } - G32(pos, NumDirItems); - if (NumDirItems > (1 << 16)) + UInt32 numDirItems; + G32(pos, numDirItems); + NumDirItems = numDirItems; + if (numDirItems > (1 << 13)) return false; pos += 4; - if (pos + 8 * NumDirItems > size) + if (pos + 8 * numDirItems > size) return false; - for (UInt32 i = 0; i < NumDirItems && i < kNumDirItemsMax; i++) + memset((void *)DirItems, 0, sizeof(DirItems)); + if (numDirItems > kNumDirItemsMax) + numDirItems = kNumDirItemsMax; + for (UInt32 i = 0; i < numDirItems; i++) DirItems[i].Parse(p + pos + i * 8); return true; } @@ -334,36 +380,50 @@ struct CSection { AString Name; + UInt32 ExtractSize; UInt32 VSize; UInt32 Va; UInt32 PSize; UInt32 Pa; UInt32 Flags; UInt32 Time; - // UInt16 NumRelocs; + // UInt16 NumRelocs; // is set to zero for executable images bool IsRealSect; bool IsDebug; bool IsAdditionalSection; - CSection(): IsRealSect(false), IsDebug(false), IsAdditionalSection(false) {} + CSection(): + ExtractSize(0), + IsRealSect(false), + IsDebug(false), + IsAdditionalSection(false) + // , NumRelocs(0) + {} + + void Set_Size_for_all(UInt32 size) + { + PSize = VSize = ExtractSize = size; + } - const UInt32 GetSizeExtract() const { return PSize; } - const UInt32 GetSizeMin() const { return MyMin(PSize, VSize); } + UInt32 GetSize_Extract() const + { + return ExtractSize; + } void UpdateTotalSize(UInt32 &totalSize) const { - UInt32 t = Pa + PSize; + const UInt32 t = Pa + PSize; if (totalSize < t) - totalSize = t; + totalSize = t; } void Parse(const Byte *p); int Compare(const CSection &s) const { - RINOZ(MyCompare(Pa, s.Pa)); - UInt32 size1 = GetSizeExtract(); - UInt32 size2 = s.GetSizeExtract(); + RINOZ(MyCompare(Pa, s.Pa)) + const UInt32 size1 = GetSize_Extract(); + const UInt32 size2 = s.GetSize_Extract(); return MyCompare(size1, size2); } }; @@ -384,8 +444,16 @@ void CSection::Parse(const Byte *p) G32(20, Pa); // G16(32, NumRelocs); G32(36, Flags); + // v24.08: we extract only useful data (without extra padding bytes). + // VSize == 0 is not expected, but we support that case too. + // return (VSize && VSize < PSize) ? VSize : PSize; + ExtractSize = (VSize && VSize < PSize) ? VSize : PSize; } + + +// IMAGE_FILE_* + static const CUInt32PCharPair g_HeaderCharacts[] = { { 1, "Executable" }, @@ -405,36 +473,65 @@ static const CUInt32PCharPair g_HeaderCharacts[] = { 15, "Big-Endian" } }; -static const CUInt32PCharPair g_DllCharacts[] = +// IMAGE_DLLCHARACTERISTICS_* + +static const char * const g_DllCharacts[] = { - { 6, "Relocated" }, - { 7, "Integrity" }, - { 8, "NX-Compatible" }, - { 9, "NoIsolation" }, - { 10, "NoSEH" }, - { 11, "NoBind" }, - { 13, "WDM" }, - { 15, "TerminalServerAware" } + NULL + , NULL + , NULL + , NULL + , NULL + , "HighEntropyVA" + , "Relocated" + , "Integrity" + , "NX-Compatible" + , "NoIsolation" + , "NoSEH" + , "NoBind" + , "AppContainer" + , "WDM" + , "GuardCF" + , "TerminalServerAware" }; -static const CUInt32PCharPair g_SectFlags[] = -{ - { 3, "NoPad" }, - { 5, "Code" }, - { 6, "InitializedData" }, - { 7, "UninitializedData" }, - { 9, "Comments" }, - { 11, "Remove" }, - { 12, "COMDAT" }, - { 15, "GP" }, - { 24, "ExtendedRelocations" }, - { 25, "Discardable" }, - { 26, "NotCached" }, - { 27, "NotPaged" }, - { 28, "Shared" }, - { 29, "Execute" }, - { 30, "Read" }, - { 31, "Write" } + +// IMAGE_SCN_* constants: + +static const char * const g_SectFlags[] = +{ + NULL + , NULL + , NULL + , "NoPad" + , NULL + , "Code" + , "InitializedData" + , "UninitializedData" + , "Other" + , "Comments" + , NULL // OVER + , "Remove" + , "COMDAT" + , NULL + , "NO_DEFER_SPEC_EXC" + , "GP" // MEM_FARDATA + , NULL // SYSHEAP + , "PURGEABLE" // 16BIT + , "LOCKED" + , "PRELOAD" + , NULL + , NULL + , NULL + , NULL + , "ExtendedRelocations" + , "Discardable" + , "NotCached" + , "NotPaged" + , "Shared" + , "Execute" + , "Read" + , "Write" }; static const CUInt32PCharPair g_MachinePairs[] = @@ -457,6 +554,7 @@ static const CUInt32PCharPair g_MachinePairs[] = { 0x01D3, "AM33" }, { 0x01F0, "PPC" }, { 0x01F1, "PPC-FP" }, + { 0x01F2, "PPC-BE" }, { 0x0200, "IA-64" }, { 0x0266, "MIPS-16" }, { 0x0284, "Alpha-64" }, @@ -465,24 +563,39 @@ static const CUInt32PCharPair g_MachinePairs[] = { 0x0520, "TriCore" }, { 0x0CEF, "CEF" }, { 0x0EBC, "EFI" }, + { 0x5032, "RISCV32" }, + { 0x5064, "RISCV64" }, +// { 0x5128, "RISCV128" }, + { 0x6232, "LOONGARCH32" }, + { 0x6264, "LOONGARCH64" }, { 0x8664, "x64" }, { 0x9041, "M32R" }, + { 0xA641, "ARM64EC" }, + { 0xA64e, "ARM64X" }, + { 0xAA64, "ARM64" }, { 0xC0EE, "CEE" } }; -static const CUInt32PCharPair g_SubSystems[] = -{ - { 0, "Unknown" }, - { 1, "Native" }, - { 2, "Windows GUI" }, - { 3, "Windows CUI" }, - { 7, "Posix" }, - { 9, "Windows CE" }, - { 10, "EFI" }, - { 11, "EFI Boot" }, - { 12, "EFI Runtime" }, - { 13, "EFI ROM" }, - { 14, "XBOX" } +static const char * const g_SubSystems[] = +{ + "Unknown" + , "Native" + , "Windows GUI" + , "Windows CUI" + , NULL // "Old Windows CE" + , "OS2" + , NULL + , "Posix" + , "Win9x" + , "Windows CE" + , "EFI" + , "EFI Boot" + , "EFI Runtime" + , "EFI ROM" + , "XBOX" + , NULL + , "Windows Boot" + , "XBOX Catalog" // 17 }; static const char * const g_ResTypes[] = @@ -556,7 +669,7 @@ struct CTextFile size_t FinalSize() const { return Buf.GetPos(); } - void AddChar(Byte c); + void AddChar(char c); void AddWChar(UInt16 c); void AddWChar_Smart(UInt16 c); void NewLine(); @@ -581,17 +694,17 @@ struct CTextFile } }; -void CTextFile::AddChar(Byte c) +void CTextFile::AddChar(char c) { Byte *p = Buf.GetCurPtrAndGrow(2); - p[0] = c; + p[0] = (Byte)c; p[1] = 0; } void CTextFile::AddWChar(UInt16 c) { Byte *p = Buf.GetCurPtrAndGrow(2); - SetUi16(p, c); + SetUi16(p, c) } void CTextFile::AddWChar_Smart(UInt16 c) @@ -645,7 +758,13 @@ struct CMixItem int StringIndex; int VersionIndex; - CMixItem(): SectionIndex(-1), ResourceIndex(-1), StringIndex(-1), VersionIndex(-1) {} + void Construct() + { + SectionIndex = -1; + ResourceIndex = -1; + StringIndex = -1; + VersionIndex = -1; + } bool IsSectionItem() const { return ResourceIndex < 0 && StringIndex < 0 && VersionIndex < 0; } }; @@ -686,15 +805,13 @@ struct CStringKeyValue UString Value; }; -class CHandler: - public IInArchive, - public IInArchiveGetStream, - public IArchiveAllowTail, - public CMyUnknownImp -{ + +Z7_CLASS_IMP_CHandler_IInArchive_2( + IInArchiveGetStream, + IArchiveAllowTail +) CMyComPtr _stream; CObjectVector _sections; - UInt32 _peOffset; CHeader _header; UInt32 _totalSize; Int32 _mainSubfile; @@ -709,15 +826,21 @@ class CHandler: CObjectVector _versionKeys; CByteBuffer _buf; + bool _oneLang; + // bool _parseResources; + bool _checksumError; + bool _sectionsError; + + bool _coffMode; + bool _allowTail; + UString _resourcesPrefix; CUsedBitmap _usedRes; - bool _parseResources; - bool _checksumError; - COptHeader _optHeader; + bool IsOpt() const { return _header.OptHeaderSize != 0; } - bool _allowTail; + COptHeader _optHeader; HRESULT LoadDebugSections(IInStream *stream, bool &thereIsSection); HRESULT Open2(IInStream *stream, IArchiveOpenCallback *callback); @@ -737,12 +860,10 @@ class CHandler: } public: - CHandler(): _allowTail(false) {} - - MY_UNKNOWN_IMP3(IInArchive, IInArchiveGetStream, IArchiveAllowTail) - INTERFACE_IInArchive(;) - STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream); - STDMETHOD(AllowTail)(Int32 allowTail); + CHandler(bool coffMode = false): + _coffMode(coffMode), + _allowTail(coffMode) + {} }; @@ -764,11 +885,11 @@ enum kpidStackReserve, kpidStackCommit, kpidHeapReserve, - kpidHeapCommit, - kpidImageBase - // kpidAddressOfEntryPoint, - // kpidBaseOfCode, - // kpidBaseOfData32, + kpidHeapCommit + // , kpidImageBase + // , kpidAddressOfEntryPoint + // , kpidBaseOfCode + // , kpidBaseOfData32 }; static const CStatProp kArcProps[] = @@ -798,14 +919,16 @@ static const CStatProp kArcProps[] = { "Stack Commit", kpidStackCommit, VT_UI8}, { "Heap Reserve", kpidHeapReserve, VT_UI8}, { "Heap Commit", kpidHeapCommit, VT_UI8}, - { "Image Base", kpidImageBase, VT_UI8}, - { NULL, kpidComment, VT_BSTR}, + { NULL, kpidVa, VT_UI8 }, // "Image Base", kpidImageBase, VT_UI8 + { NULL, kpidComment, VT_BSTR} - // { "Address Of Entry Point", kpidAddressOfEntryPoint, VT_UI8}, - // { "Base Of Code", kpidBaseOfCode, VT_UI8}, - // { "Base Of Data", kpidBaseOfData32, VT_UI8}, + // , { "Address Of Entry Point", kpidAddressOfEntryPoint, VT_UI8} + // , { "Base Of Code", kpidBaseOfCode, VT_UI8} + // , { "Base Of Data", kpidBaseOfData32, VT_UI8} }; +// #define kpid_NumRelocs 250 + static const Byte kProps[] = { kpidPath, @@ -814,7 +937,8 @@ static const Byte kProps[] = kpidVirtualSize, kpidCharacts, kpidOffset, - kpidVa, + kpidVa + // , kpid_NumRelocs }; IMP_IInArchive_Props @@ -823,19 +947,87 @@ IMP_IInArchive_ArcProps_WITH_NAME static void TimeToProp(UInt32 unixTime, NCOM::CPropVariant &prop) { if (unixTime != 0) - { - FILETIME ft; - NTime::UnixTimeToFileTime(unixTime, ft); - prop = ft; - } + PropVariant_SetFrom_UnixTime(prop, unixTime); } -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; switch (propID) { + case kpidPhySize: prop = _totalSize; break; + case kpidComment: + { + UString s (_versionFullString); + s.Add_LF(); + s += "Data Directories: "; + s.Add_UInt32(_optHeader.NumDirItems); + s.Add_LF(); + s.Add_Char('{'); + s.Add_LF(); + for (unsigned i = 0; i < _optHeader.NumDirItems + && i < Z7_ARRAY_SIZE(_optHeader.DirItems); i++) + { + const CDirLink &di = _optHeader.DirItems[i]; + if (di.Va == 0 && di.Size == 0) + continue; + s += "index="; + s.Add_UInt32(i); + + if (i < Z7_ARRAY_SIZE(g_Dir_Names)) + { + s += " name="; + s += g_Dir_Names[i]; + } + s += " VA=0x"; + char temp[16]; + ConvertUInt32ToHex(di.Va, temp); + s += temp; + s += " Size="; + s.Add_UInt32(di.Size); + s.Add_LF(); + } + s.Add_Char('}'); + s.Add_LF(); + prop = s; + break; + } + case kpidShortComment: + if (!_versionShortString.IsEmpty()) + prop = _versionShortString; + else + { + PAIR_TO_PROP(g_MachinePairs, _header.Machine, prop); + } + break; + + case kpidName: if (!_originalFilename.IsEmpty()) prop = _originalFilename; break; + + // case kpidIsSelfExe: prop = !_header.IsDll(); break; + // case kpidError: + case kpidWarning: if (_checksumError) prop = "Checksum error"; break; + + case kpidWarningFlags: + { + UInt32 v = 0; + if (_sectionsError) v |= kpv_ErrorFlags_HeadersError; + if (v != 0) + prop = v; + break; + } + + case kpidCpu: PAIR_TO_PROP(g_MachinePairs, _header.Machine, prop); break; + case kpidMTime: + case kpidCTime: TimeToProp(_header.Time, prop); break; + case kpidCharacts: FLAGS_TO_PROP(g_HeaderCharacts, _header.Flags, prop); break; + case kpidMainSubfile: if (_mainSubfile >= 0) prop = (UInt32)_mainSubfile; break; + + default: + if (IsOpt()) + switch (propID) + { + case kpidSectAlign: prop = _optHeader.SectAlign; break; case kpidFileAlign: prop = _optHeader.FileAlign; break; case kpidLinkerVer: @@ -852,49 +1044,29 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) case kpidInitDataSize: prop = _optHeader.InitDataSize; break; case kpidUnInitDataSize: prop = _optHeader.UninitDataSize; break; case kpidImageSize: prop = _optHeader.ImageSize; break; - case kpidPhySize: prop = _totalSize; break; case kpidHeadersSize: prop = _optHeader.HeadersSize; break; case kpidChecksum: prop = _optHeader.CheckSum; break; - case kpidComment: if (!_versionFullString.IsEmpty()) prop = _versionFullString; break; - case kpidShortComment: - if (!_versionShortString.IsEmpty()) - prop = _versionShortString; - else - { - PAIR_TO_PROP(g_MachinePairs, _header.Machine, prop); - } - break; - case kpidName: if (!_originalFilename.IsEmpty()) prop = _originalFilename; break; case kpidExtension: if (_header.IsDll()) - prop = _optHeader.IsSybSystem_EFI() ? "efi" : "dll"; + prop = "dll"; + else if (_optHeader.IsSybSystem_EFI()) + prop = "efi"; break; - - // case kpidIsSelfExe: prop = !_header.IsDll(); break; - - // case kpidError: - case kpidWarning: if (_checksumError) prop = "Checksum error"; break; - case kpidCpu: PAIR_TO_PROP(g_MachinePairs, _header.Machine, prop); break; case kpidBit64: if (_optHeader.Is64Bit()) prop = true; break; - case kpidSubSystem: PAIR_TO_PROP(g_SubSystems, _optHeader.SubSystem, prop); break; + case kpidSubSystem: TYPE_TO_PROP(g_SubSystems, _optHeader.SubSystem, prop); break; - case kpidMTime: - case kpidCTime: TimeToProp(_header.Time, prop); break; - case kpidCharacts: FLAGS_TO_PROP(g_HeaderCharacts, _header.Flags, prop); break; case kpidDllCharacts: FLAGS_TO_PROP(g_DllCharacts, _optHeader.DllCharacts, prop); break; case kpidStackReserve: prop = _optHeader.StackReserve; break; case kpidStackCommit: prop = _optHeader.StackCommit; break; case kpidHeapReserve: prop = _optHeader.HeapReserve; break; case kpidHeapCommit: prop = _optHeader.HeapCommit; break; - - case kpidImageBase: prop = _optHeader.ImageBase; break; + case kpidVa: prop = _optHeader.ImageBase; break; // kpidImageBase: // case kpidAddressOfEntryPoint: prop = _optHeader.AddressOfEntryPoint; break; // case kpidBaseOfCode: prop = _optHeader.BaseOfCode; break; // case kpidBaseOfData32: if (!_optHeader.Is64Bit()) prop = _optHeader.BaseOfData32; break; - - case kpidMainSubfile: if (_mainSubfile >= 0) prop = (UInt32)_mainSubfile; break; + } } prop.Detach(value); return S_OK; @@ -950,9 +1122,7 @@ void CHandler::AddResNameToString(UString &s, UInt32 id) const return; } } - wchar_t sz[16]; - ConvertUInt32ToString(id, sz); - s += sz; + s.Add_UInt32(id); } void CHandler::AddLangPrefix(UString &s, UInt32 lang) const @@ -964,7 +1134,7 @@ void CHandler::AddLangPrefix(UString &s, UInt32 lang) const } } -STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; @@ -978,7 +1148,7 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val { UString s = _resourcesPrefix; AddLangPrefix(s, item.Lang); - s.AddAscii("string.txt"); + s += "string.txt"; prop = s; break; } @@ -996,7 +1166,7 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val { UString s = _resourcesPrefix; AddLangPrefix(s, item.Lang); - s.AddAscii("version.txt"); + s += "version.txt"; prop = s; break; } @@ -1016,10 +1186,10 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val AddLangPrefix(s, item.Lang); { const char *p = NULL; - if (item.Type < ARRAY_SIZE(g_ResTypes)) + if (item.Type < Z7_ARRAY_SIZE(g_ResTypes)) p = g_ResTypes[item.Type]; if (p) - s.AddAscii(p); + s += p; else AddResNameToString(s, item.Type); } @@ -1028,9 +1198,9 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val if (item.HeaderSize != 0) { if (item.IsBmp()) - s.AddAscii(".bmp"); + s += ".bmp"; else if (item.IsIcon()) - s.AddAscii(".ico"); + s += ".ico"; } prop = s; break; @@ -1044,8 +1214,16 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val const CSection &item = _sections[mixItem.SectionIndex]; switch (propID) { - case kpidPath: prop = MultiByteToUnicodeString(item.Name); break; - case kpidSize: prop = (UInt64)item.PSize; break; + case kpidPath: + { + AString s = item.Name; + s.Replace('/', '_'); + s.Replace('\\', '_'); + prop = MultiByteToUnicodeString(s); + break; + } + case kpidSize: prop = (UInt64)item.GetSize_Extract(); break; + // case kpid_NumRelocs: prop = (UInt32)item.NumRelocs; break; case kpidPackSize: prop = (UInt64)item.PSize; break; case kpidVirtualSize: prop = (UInt64)item.VSize; break; case kpidOffset: prop = item.Pa; break; @@ -1053,7 +1231,24 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val case kpidMTime: case kpidCTime: TimeToProp(item.IsDebug ? item.Time : _header.Time, prop); break; - case kpidCharacts: if (item.IsRealSect) FLAGS_TO_PROP(g_SectFlags, item.Flags, prop); break; + case kpidCharacts: + if (item.IsRealSect) + { + UInt32 flags = item.Flags; + const UInt32 MY_IMAGE_SCN_ALIGN_MASK = 0x00F00000; + AString s = FlagsToString(g_SectFlags, Z7_ARRAY_SIZE(g_SectFlags), item.Flags & ~MY_IMAGE_SCN_ALIGN_MASK); + const UInt32 align = ((flags >> 20) & 0xF); + if (align != 0) + { + char sz[32]; + ConvertUInt32ToString(1 << (align - 1), sz); + s.Add_Space(); + s += "align_"; + s += sz; + } + prop = s; + } + break; case kpidZerosTailIsAllowed: if (!item.IsRealSect) prop = true; break; } } @@ -1070,8 +1265,17 @@ HRESULT CHandler::LoadDebugSections(IInStream *stream, bool &thereIsSection) return S_OK; const unsigned kEntrySize = 28; UInt32 numItems = debugLink.Size / kEntrySize; - if (numItems * kEntrySize != debugLink.Size || numItems > 16) + if (numItems > 16) return S_FALSE; + + // MAC's EFI file: numItems can be incorrect. Only first CDebugEntry entry is correct. + // debugLink.Size = kEntrySize + some_data, pointed by entry[0]. + if (numItems * kEntrySize != debugLink.Size) + { + // return S_FALSE; + if (numItems > 1) + numItems = 1; + } UInt64 pa = 0; unsigned i; @@ -1094,8 +1298,8 @@ HRESULT CHandler::LoadDebugSections(IInStream *stream, bool &thereIsSection) CByteBuffer buffer(debugLink.Size); Byte *buf = buffer; - RINOK(stream->Seek(pa, STREAM_SEEK_SET, NULL)); - RINOK(ReadStream_FALSE(stream, buf, debugLink.Size)); + RINOK(InStream_SeekSet(stream, pa)) + RINOK(ReadStream_FALSE(stream, buf, debugLink.Size)) for (i = 0; i < numItems; i++) { @@ -1112,12 +1316,13 @@ HRESULT CHandler::LoadDebugSections(IInStream *stream, bool &thereIsSection) thereIsSection = true; CSection § = _sections.AddNew(); - sect.Name = ".debug" + GetDecString(i); + sect.Name = ".debug"; + sect.Name.Add_UInt32(i); sect.IsDebug = true; sect.Time = de.Time; sect.Va = de.Va; sect.Pa = de.Pa; - sect.PSize = sect.VSize = de.Size; + sect.Set_Size_for_all(de.Size); } buf += kEntrySize; } @@ -1179,7 +1384,7 @@ bool CBitmapInfoHeader::Parse(const Byte *p, size_t size) if (size < kBitmapInfoHeader_Size || Get32(p) != kBitmapInfoHeader_Size) return false; G32( 4, XSize); - G32( 8, YSize); + G32_signed( 8, YSize); G16(12, Planes); G16(14, BitCount); G32(16, Compression); @@ -1199,21 +1404,24 @@ static UInt32 SetBitmapHeader(Byte *dest, const Byte *src, UInt32 size) return 0; if (h.YSize < 0) h.YSize = -h.YSize; - if (h.XSize > (1 << 26) || h.YSize > (1 << 26) || h.Planes != 1 || h.BitCount > 32) + if (h.XSize > (1 << 26) + || h.YSize > (1 << 26) + || h.YSize < 0 + || h.Planes != 1 || h.BitCount > 32) return 0; if (h.SizeImage == 0) { if (h.Compression != 0) // BI_RGB return 0; - h.SizeImage = GetImageSize(h.XSize, h.YSize, h.BitCount); + h.SizeImage = GetImageSize(h.XSize, (UInt32)h.YSize, h.BitCount); } UInt32 totalSize = kBmpHeaderSize + size; UInt32 offBits = totalSize - h.SizeImage; // BITMAPFILEHEADER - SetUi16(dest, 0x4D42); - SetUi32(dest + 2, totalSize); - SetUi32(dest + 6, 0); - SetUi32(dest + 10, offBits); + SetUi16(dest, 0x4D42) + SetUi32(dest + 2, totalSize) + SetUi32(dest + 6, 0) + SetUi32(dest + 10, offBits) return kBmpHeaderSize; } @@ -1224,11 +1432,14 @@ static UInt32 SetIconHeader(Byte *dest, const Byte *src, UInt32 size) return 0; if (h.YSize < 0) h.YSize = -h.YSize; - if (h.XSize > (1 << 26) || h.YSize > (1 << 26) || h.Planes != 1 || - h.Compression != 0) // BI_RGB + if (h.XSize > (1 << 26) + || h.YSize > (1 << 26) + || h.YSize < 0 + || h.Planes != 1 + || h.Compression != 0) // BI_RGB return 0; - UInt32 numBitCount = h.BitCount; + const UInt32 numBitCount = h.BitCount; if (numBitCount != 1 && numBitCount != 4 && numBitCount != 8 && @@ -1249,29 +1460,29 @@ static UInt32 SetIconHeader(Byte *dest, const Byte *src, UInt32 size) // UInt32 imageSize = h.SizeImage; // if (imageSize == 0) // { - UInt32 image1Size = GetImageSize(h.XSize, h.YSize, h.BitCount); - UInt32 image2Size = GetImageSize(h.XSize, h.YSize, 1); + const UInt32 image1Size = GetImageSize(h.XSize, (UInt32)h.YSize, h.BitCount); + const UInt32 image2Size = GetImageSize(h.XSize, (UInt32)h.YSize, 1); imageSize = image1Size + image2Size; // } UInt32 numColors = 0; if (numBitCount < 16) numColors = 1 << numBitCount; - SetUi16(dest, 0); // Reserved - SetUi16(dest + 2, 1); // RES_ICON - SetUi16(dest + 4, 1); // ResCount + SetUi16(dest, 0) // Reserved + SetUi16(dest + 2, 1) // RES_ICON + SetUi16(dest + 4, 1) // ResCount dest[6] = (Byte)h.XSize; // Width dest[7] = (Byte)h.YSize; // Height dest[8] = (Byte)numColors; // ColorCount dest[9] = 0; // Reserved - SetUi32(dest + 10, 0); // Reserved1 / Reserved2 + SetUi32(dest + 10, 0) // Reserved1 / Reserved2 UInt32 numQuadsBytes = numColors * 4; UInt32 BytesInRes = kBitmapInfoHeader_Size + numQuadsBytes + imageSize; - SetUi32(dest + 14, BytesInRes); - SetUi32(dest + 18, kIconHeaderSize); + SetUi32(dest + 14, BytesInRes) + SetUi32(dest + 18, kIconHeaderSize) /* Description = DWORDToString(xSize) + @@ -1384,11 +1595,9 @@ static void PrintUInt32(CTextFile &f, UInt32 v) f.AddString(s); } -static void PrintUInt32(UString &dest, UInt32 v) +static inline void PrintUInt32(UString &dest, UInt32 v) { - wchar_t s[16]; - ConvertUInt32ToString(v, s); - dest += s; + dest.Add_UInt32(v); } static void PrintHex(CTextFile &f, UInt32 val) @@ -1410,9 +1619,9 @@ static void PrintVersion(CTextFile &f, UInt32 ms, UInt32 ls) static void PrintVersion(UString &s, UInt32 ms, UInt32 ls) { - PrintUInt32(s, HIWORD(ms)); s += L'.'; - PrintUInt32(s, LOWORD(ms)); s += L'.'; - PrintUInt32(s, HIWORD(ls)); s += L'.'; + PrintUInt32(s, HIWORD(ms)); s.Add_Dot(); + PrintUInt32(s, LOWORD(ms)); s.Add_Dot(); + PrintUInt32(s, HIWORD(ls)); s.Add_Dot(); PrintUInt32(s, LOWORD(ls)); } @@ -1500,7 +1709,7 @@ static int FindKey(CObjectVector &v, const char *key) { FOR_VECTOR (i, v) if (v[i].Key.IsEqualTo(key)) - return i; + return (int)i; return -1; } @@ -1552,7 +1761,7 @@ void CMy_VS_FIXEDFILEINFO::PrintToTextFile(CTextFile &f, CObjectVector> 16; - if (high < ARRAY_SIZE(k_VS_FileOS_High)) + if (high < Z7_ARRAY_SIZE(k_VS_FileOS_High)) f.AddString(k_VS_FileOS_High[high]); else PrintHex(f, high << 16); @@ -1598,7 +1807,7 @@ void CMy_VS_FIXEDFILEINFO::PrintToTextFile(CTextFile &f, CObjectVector= size) return -1; if (Get16(p + pos) == 0) - return pos; + return (int)pos; + pos += 2; + } +} + +static int Get_Utf16Str_Len_InBytes_AllowNonZeroTail(const Byte *p, size_t size) +{ + unsigned pos = 0; + for (;;) + { + if (pos + 1 >= size) + { + if (pos == size) + return (int)pos; + return -1; + } + if (Get16(p + pos) == 0) + return (int)pos; pos += 2; } } @@ -1694,19 +1930,17 @@ bool CVersionBlock::Parse(const Byte *p, UInt32 size) return false; TotalLen = Get16(p); ValueLen = Get16(p + 2); - if (TotalLen == 0 || TotalLen > size) + if (TotalLen < k_ResoureBlockHeader_Size || TotalLen > size) + return false; + IsTextValue = Get16(p + 4); + if (IsTextValue > 1) return false; - switch (Get16(p + 4)) - { - case 0: IsTextValue = false; break; - case 1: IsTextValue = true; break; - default: return false; - } StrSize = 0; - int t = Get_Utf16Str_Len_InBytes(p + k_ResoureBlockHeader_Size, TotalLen - k_ResoureBlockHeader_Size); + const int t = Get_Utf16Str_Len_InBytes(p + k_ResoureBlockHeader_Size, + TotalLen - k_ResoureBlockHeader_Size); if (t < 0) return false; - StrSize = t; + StrSize = (unsigned)t; return true; } @@ -1743,7 +1977,7 @@ static bool ParseVersion(const Byte *p, UInt32 size, CTextFile &f, CObjectVector // if (size != vb.TotalLen) return false; */ if (size > vb.TotalLen) - size = vb.TotalLen; + size = vb.TotalLen; CMy_VS_FIXEDFILEINFO FixedFileInfo; if (!FixedFileInfo.Parse(p + pos)) return false; @@ -1764,7 +1998,7 @@ static bool ParseVersion(const Byte *p, UInt32 size, CTextFile &f, CObjectVector return false; if (vb.ValueLen != 0) return false; - UInt32 endPos = pos + vb.TotalLen; + const UInt32 endPos = pos + vb.TotalLen; pos += k_ResoureBlockHeader_Size; f.AddSpaces(2); @@ -1785,7 +2019,7 @@ static bool ParseVersion(const Byte *p, UInt32 size, CTextFile &f, CObjectVector CVersionBlock vb2; if (!vb2.Parse(p + pos, endPos - pos)) return false; - UInt32 endPos2 = pos + vb2.TotalLen; + const UInt32 endPos2 = pos + vb2.TotalLen; if (vb2.IsTextValue) return false; pos += k_ResoureBlockHeader_Size; @@ -1803,9 +2037,9 @@ static bool ParseVersion(const Byte *p, UInt32 size, CTextFile &f, CObjectVector UInt32 num = (vb2.ValueLen >> 2); for (; num != 0; num--, pos += 4) { - UInt32 dw = Get32(p + pos); - UInt32 lang = LOWORD(dw); - UInt32 codePage = HIWORD(dw); + const UInt32 dw = Get32(p + pos); + const UInt32 lang = LOWORD(dw); + const UInt32 codePage = HIWORD(dw); f.AddString(", "); PrintHex(f, lang); @@ -1820,7 +2054,6 @@ static bool ParseVersion(const Byte *p, UInt32 size, CTextFile &f, CObjectVector if (!CompareWStrStrings(p + pos, "StringFileInfo")) return false; pos += vb.StrSize + 2; - for (;;) { pos += (4 - pos) & 3; @@ -1829,7 +2062,7 @@ static bool ParseVersion(const Byte *p, UInt32 size, CTextFile &f, CObjectVector CVersionBlock vb2; if (!vb2.Parse(p + pos, endPos - pos)) return false; - UInt32 endPos2 = pos + vb2.TotalLen; + const UInt32 endPos2 = pos + vb2.TotalLen; if (vb2.ValueLen != 0) return false; pos += k_ResoureBlockHeader_Size; @@ -1851,9 +2084,8 @@ static bool ParseVersion(const Byte *p, UInt32 size, CTextFile &f, CObjectVector CVersionBlock vb3; if (!vb3.Parse(p + pos, endPos2 - pos)) return false; - // ValueLen sometimes is a number of characters (not bytes)? - // So we don't use it. - UInt32 endPos3 = pos + vb3.TotalLen; + // ValueLen is a number of 16-bit characters (usually it includes zero tail character). + const UInt32 endPos3 = pos + vb3.TotalLen; pos += k_ResoureBlockHeader_Size; // we don't write string if it's not text @@ -1868,26 +2100,35 @@ static bool ParseVersion(const Byte *p, UInt32 size, CTextFile &f, CObjectVector pos += vb3.StrSize + 2; pos += (4 - pos) & 3; - if (vb3.ValueLen > 0 && pos + 2 <= endPos3) + if (vb3.ValueLen != 0 && pos /* + 2 */ <= endPos3) { f.AddChar(','); f.AddSpaces((34 - (int)vb3.StrSize) / 2); - int sLen = Get_Utf16Str_Len_InBytes(p + pos, endPos3 - pos); + // vb3.TotalLen for some PE files (not from msvc) doesn't include tail zero at the end of Value string. + // we allow that minor error. + const int sLen = Get_Utf16Str_Len_InBytes_AllowNonZeroTail(p + pos, endPos3 - pos); if (sLen < 0) return false; + /* + if (vb3.ValueLen - 1 != (unsigned)sLen / 2 && + vb3.ValueLen != (unsigned)sLen / 2) + return false; + */ AddParamString(f, p + pos, (unsigned)sLen); - CopyToUString(p + pos, value); - pos += sLen + 2; + CopyToUString_ByLen16(p + pos, (unsigned)sLen / 2, value); + // pos += (unsigned)sLen + 2; } AddToUniqueUStringVector(keys, key, value); } pos = endPos3; f.NewLine(); } + pos = endPos2; f.CloseBlock(4); } } f.CloseBlock(2); + pos = endPos; } f.CloseBlock(0); @@ -1911,10 +2152,12 @@ HRESULT CHandler::OpenResources(unsigned sectionIndex, IInStream *stream, IArchi const UInt32 mask = ((UInt32)1 << numBits) - 1; const size_t end = (size_t)((sect.VSize + mask) & (UInt32)~mask); if (end > sect.VSize) + { if (end <= sect.PSize) fileSize = end; else fileSize = sect.PSize; + } } } @@ -1924,10 +2167,10 @@ HRESULT CHandler::OpenResources(unsigned sectionIndex, IInStream *stream, IArchi { const UInt64 fileSize64 = fileSize; if (callback) - RINOK(callback->SetTotal(NULL, &fileSize64)); + RINOK(callback->SetTotal(NULL, &fileSize64)) } - RINOK(stream->Seek(sect.Pa, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekSet(stream, sect.Pa)) _buf.Alloc(fileSize); @@ -1941,7 +2184,7 @@ HRESULT CHandler::OpenResources(unsigned sectionIndex, IInStream *stream, IArchi RINOK(callback->SetCompleted(NULL, &offset64)) } size_t rem = MyMin(fileSize - pos, (size_t)(1 << 22)); - RINOK(ReadStream(stream, _buf + pos, &rem)); + RINOK(ReadStream(stream, _buf + pos, &rem)) if (rem == 0) { if (pos < fileSizeMin) @@ -1957,7 +2200,7 @@ HRESULT CHandler::OpenResources(unsigned sectionIndex, IInStream *stream, IArchi _usedRes.Alloc(fileSize); CRecordVector specItems; - RINOK(ReadTable(0, specItems)); + RINOK(ReadTable(0, specItems)) _oneLang = true; bool stringsOk = true; @@ -1970,7 +2213,7 @@ HRESULT CHandler::OpenResources(unsigned sectionIndex, IInStream *stream, IArchi return S_FALSE; CRecordVector specItems2; - RINOK(ReadTable(item1.Offset & kMask, specItems2)); + RINOK(ReadTable(item1.Offset & kMask, specItems2)) FOR_VECTOR (j, specItems2) { @@ -1979,7 +2222,7 @@ HRESULT CHandler::OpenResources(unsigned sectionIndex, IInStream *stream, IArchi return S_FALSE; CRecordVector specItems3; - RINOK(ReadTable(item2.Offset & kMask, specItems3)); + RINOK(ReadTable(item2.Offset & kMask, specItems3)) CResItem item; item.Type = item1.ID; @@ -2034,8 +2277,9 @@ HRESULT CHandler::OpenResources(unsigned sectionIndex, IInStream *stream, IArchi if (ParseVersion((const Byte *)_buf + offset, item.Size, f, _versionKeys)) { CMixItem mixItem; - mixItem.VersionIndex = _versionFiles.Size(); - mixItem.SectionIndex = sectionIndex; // check it !!!! + mixItem.Construct(); + mixItem.VersionIndex = (int)_versionFiles.Size(); + mixItem.SectionIndex = (int)sectionIndex; // check it !!!! CByteBuffer_WithLang &vf = _versionFiles.AddNew(); vf.Lang = item.Lang; vf.CopyFrom(f.Buf, f.Buf.GetPos()); @@ -2065,8 +2309,9 @@ HRESULT CHandler::OpenResources(unsigned sectionIndex, IInStream *stream, IArchi if (_strings[i].FinalSize() == 0) continue; CMixItem mixItem; - mixItem.StringIndex = i; - mixItem.SectionIndex = sectionIndex; + mixItem.Construct(); + mixItem.StringIndex = (int)i; + mixItem.SectionIndex = (int)sectionIndex; _mixItems.Add(mixItem); } } @@ -2100,7 +2345,7 @@ HRESULT CHandler::OpenResources(unsigned sectionIndex, IInStream *stream, IArchi if (sect2.PSize != 0) { - sect2.VSize = sect2.PSize; + sect2.ExtractSize = sect2.VSize = sect2.PSize; sect2.Name = ".rsrc_1"; sect2.Time = 0; sect2.IsAdditionalSection = true; @@ -2112,9 +2357,35 @@ HRESULT CHandler::OpenResources(unsigned sectionIndex, IInStream *stream, IArchi return S_OK; } + +bool CHeader::ParseCoff(const Byte *p) +{ + ParseBase(p); + if (PointerToSymbolTable < kCoffHeaderSize) + return false; + if (NumSymbols >= (1 << 24)) + return false; + if (OptHeaderSize != 0 && OptHeaderSize < k_OptHeader32_Size_MIN) + return false; + + // 18.04: we reduce false detections + if (NumSections == 0 && OptHeaderSize == 0) + return false; + + for (unsigned i = 0; i < Z7_ARRAY_SIZE(g_MachinePairs); i++) + if (Machine == g_MachinePairs[i].Value) + return true; + if (Machine == 0) + return true; + + return false; +} + + static inline bool CheckPeOffset(UInt32 pe) { - return (pe >= 0x40 && pe <= 0x1000 && (pe & 7) == 0); + // ((pe & 7) == 0) is for most PE files. But there is unusual EFI-PE file that uses unaligned pe value. + return pe >= 0x40 && pe <= 0x1000 /* && (pe & 7) == 0 */ ; } static const unsigned kStartSize = 0x40; @@ -2130,10 +2401,10 @@ API_FUNC_static_IsArc IsArc_Pe(const Byte *p, size_t size) UInt32 pe = Get32(p + 0x3C); if (!CheckPeOffset(pe)) return k_IsArc_Res_NO; - if (pe + kHeaderSize > size) + if (pe + kPeHeaderSize > size) return k_IsArc_Res_NEED_MORE; CHeader header; - if (!header.Parse(p + pe)) + if (!header.ParsePe(p + pe)) return k_IsArc_Res_NO; return k_IsArc_Res_YES; } @@ -2141,32 +2412,48 @@ API_FUNC_static_IsArc IsArc_Pe(const Byte *p, size_t size) HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *callback) { + UInt32 coffOffset = 0; + if (_coffMode) { - Byte h[kStartSize]; - _mainSubfile = -1; - RINOK(ReadStream_FALSE(stream, h, kStartSize)); - if (h[0] != 'M' || h[1] != 'Z') - return S_FALSE; - /* most of PE files contain 0x0090 at offset 2. - But some rare PE files contain another values. So we don't use that check. - if (Get16(h + 2) != 0x90) return false; */ - _peOffset = Get32(h + 0x3C); - if (!CheckPeOffset(_peOffset)) + Byte h[kCoffHeaderSize]; + RINOK(ReadStream_FALSE(stream, h, kCoffHeaderSize)) + if (!_header.ParseCoff(h)) return S_FALSE; } + else { - Byte h[kHeaderSize]; - RINOK(stream->Seek(_peOffset, STREAM_SEEK_SET, NULL)); - RINOK(ReadStream_FALSE(stream, h, kHeaderSize)); - if (!_header.Parse(h)) - return S_FALSE; + UInt32 _peOffset; + { + Byte h[kStartSize]; + RINOK(ReadStream_FALSE(stream, h, kStartSize)) + if (h[0] != 'M' || h[1] != 'Z') + return S_FALSE; + /* most of PE files contain 0x0090 at offset 2. + But some rare PE files contain another values. So we don't use that check. + if (Get16(h + 2) != 0x90) return false; */ + _peOffset = Get32(h + 0x3C); + if (!CheckPeOffset(_peOffset)) + return S_FALSE; + coffOffset = _peOffset + 4; + } + { + Byte h[kPeHeaderSize]; + RINOK(InStream_SeekSet(stream, _peOffset)) + RINOK(ReadStream_FALSE(stream, h, kPeHeaderSize)) + if (!_header.ParsePe(h)) + return S_FALSE; + } } - UInt32 bufSize = _header.OptHeaderSize + (UInt32)_header.NumSections * kSectionSize; - _totalSize = _peOffset + kHeaderSize + bufSize; + const UInt32 optStart = coffOffset + kCoffHeaderSize; + const UInt32 bufSize = _header.OptHeaderSize + (UInt32)_header.NumSections * kSectionSize; + _totalSize = optStart + bufSize; CByteBuffer buffer(bufSize); - RINOK(ReadStream_FALSE(stream, buffer, bufSize)); + RINOK(ReadStream_FALSE(stream, buffer, bufSize)) + + // memset((void *)&_optHeader, 0, sizeof(_optHeader)); + if (_header.OptHeaderSize != 0) if (!_optHeader.Parse(buffer, _header.OptHeaderSize)) return S_FALSE; @@ -2177,35 +2464,65 @@ HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *callback) CSection § = _sections.AddNew(); sect.Parse(buffer + pos); sect.IsRealSect = true; + if (sect.Name.IsEqualTo(".reloc")) + { + const CDirLink &dl = _optHeader.DirItems[kDirLink_BASERELOC]; + if (dl.Va == sect.Va && + dl.Size <= sect.PSize) + sect.ExtractSize = dl.Size; + } + else if (sect.Name.IsEqualTo(".pdata")) + { + const CDirLink &dl = _optHeader.DirItems[kDirLink_EXCEPTION]; + if (dl.Va == sect.Va && + dl.Size <= sect.PSize) + sect.ExtractSize = dl.Size; + } /* PE pre-file in .hxs file has errors: - PSize of resource is larger tnan real size. - So it overlaps next ".its" section. - We correct it. */ + PSize of resource is larger than real size. + So it overlaps next ".its" section. + 7-zip before 22.02: we corrected it. + + 22.02: another bad case is possible in incorrect pe (exe) file: + PSize in .rsrc section is correct, + but next .reloc section has incorrect (Pa) that overlaps with .rsrc. + */ - if (i > 0) + if (i != 0) { - CSection &prev = _sections[i - 1]; - if (prev.Pa < sect.Pa && - prev.Pa + prev.PSize > sect.Pa && - sect.PSize > 0) + const CSection &prev = _sections[i - 1]; + if (prev.Pa < sect.Pa + && prev.Pa + prev.PSize > sect.Pa + && sect.PSize != 0 + && prev.PSize != 0) { - // printf("\n !!!! Section correction: %s\n ", prev.Name); - // fflush(stdout); - prev.PSize = sect.Pa - prev.Pa; + _sectionsError = true; + // PRF(printf("\n !!!! Section correction: %s\n ", prev.Name)); + + /* we corrected that case in 7-zip before 22.02: */ + // prev.PSize = sect.Pa - prev.Pa; + + /* 22.02: here we can try to change bad section position to expected postion. + but original Windows code probably will not do same things. */ + // if (prev.PSize <= sect.Va - prev.Va) sect.Pa = prev.Pa + prev.PSize; } } /* last ".its" section in hxs file has incorrect sect.PSize. - So we reduce it to real sect.VSize */ + 7-zip before 22.02: we reduced section to real sect.VSize */ + /* if (sect.VSize == 24 && sect.PSize == 512 && i == (unsigned)_header.NumSections - 1) sect.PSize = sect.VSize; + */ } for (i = 0; i < _sections.Size(); i++) _sections[i].UpdateTotalSize(_totalSize); - bool thereISDebug; - RINOK(LoadDebugSections(stream, thereISDebug)); + bool thereISDebug = false; + if (IsOpt()) + { + RINOK(LoadDebugSections(stream, thereISDebug)) const CDirLink &certLink = _optHeader.DirItems[kDirLink_Certificate]; if (certLink.Size != 0) @@ -2214,7 +2531,7 @@ HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *callback) sect.Name = "CERTIFICATE"; sect.Va = 0; sect.Pa = certLink.Va; - sect.PSize = sect.VSize = certLink.Size; + sect.Set_Size_for_all(certLink.Size); sect.UpdateTotalSize(_totalSize); } @@ -2229,11 +2546,11 @@ HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *callback) if (alignPos != 0) { UInt32 size = kAlign - alignPos; - RINOK(stream->Seek(_totalSize, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekSet(stream, _totalSize)) buffer.Alloc(kAlign); Byte *buf = buffer; size_t processed = size; - RINOK(ReadStream(stream, buf, &processed)); + RINOK(ReadStream(stream, buf, &processed)) /* if (processed != 0) @@ -2253,15 +2570,16 @@ HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *callback) _totalSize += (UInt32)k; } } + } - if (_header.NumSymbols > 0 && _header.PointerToSymbolTable >= 512) + if (_header.NumSymbols > 0 && _header.PointerToSymbolTable >= optStart) { if (_header.NumSymbols >= (1 << 24)) return S_FALSE; UInt32 size = _header.NumSymbols * 18; - RINOK(stream->Seek((UInt64)_header.PointerToSymbolTable + size, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekSet(stream, (UInt64)_header.PointerToSymbolTable + size)) Byte buf[4]; - RINOK(ReadStream_FALSE(stream, buf, 4)); + RINOK(ReadStream_FALSE(stream, buf, 4)) UInt32 size2 = Get32(buf); if (size2 >= (1 << 28)) return S_FALSE; @@ -2271,7 +2589,7 @@ HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *callback) sect.Name = "COFF_SYMBOLS"; sect.Va = 0; sect.Pa = _header.PointerToSymbolTable; - sect.PSize = sect.VSize = size; + sect.Set_Size_for_all(size); sect.UpdateTotalSize(_totalSize); } @@ -2287,11 +2605,11 @@ HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *callback) { CSection &s2 = _sections.AddNew(); s2.Pa = s2.Va = limit; - s2.PSize = s2.VSize = s.Pa - limit; + s2.Set_Size_for_all(s.Pa - limit); s2.IsAdditionalSection = true; - s2.Name = '['; - s2.Name += GetDecString(num++); - s2.Name += ']'; + s2.Name.Add_Char('['); + s2.Name.Add_UInt32(num++); + s2.Name.Add_Char(']'); limit = s.Pa; } UInt32 next = s.Pa + s.PSize; @@ -2303,11 +2621,12 @@ HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *callback) } + if (IsOpt()) if (_optHeader.CheckSum != 0) { - RINOK(stream->Seek(0, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekToBegin(stream)) UInt32 checkSum = 0; - RINOK(CalcCheckSum(stream, _totalSize, _peOffset + kHeaderSize + k_CheckSum_Field_Offset, checkSum)); + RINOK(CalcCheckSum(stream, _totalSize, optStart + k_CheckSum_Field_Offset, checkSum)) _checksumError = (checkSum != _optHeader.CheckSum); } @@ -2315,41 +2634,46 @@ HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *callback) if (!_allowTail) { UInt64 fileSize; - RINOK(stream->Seek(0, STREAM_SEEK_END, &fileSize)); + RINOK(InStream_GetSize_SeekToEnd(stream, fileSize)) if (fileSize > _totalSize) return S_FALSE; } - _parseResources = true; - // _parseResources = false; + bool _parseResources = true; + // _parseResources = false; // for debug UInt64 mainSize = 0, mainSize2 = 0; for (i = 0; i < _sections.Size(); i++) { const CSection § = _sections[i]; - CMixItem mixItem; - mixItem.SectionIndex = i; - if (_parseResources && sect.Name == ".rsrc" && _items.IsEmpty()) + if (IsOpt()) + if (_parseResources && sect.Name.IsEqualTo(".rsrc")) { + // 20.01: we try to parse only first copy of .rsrc section. + _parseResources = false; + const unsigned numMixItems = _mixItems.Size(); HRESULT res = OpenResources(i, stream, callback); if (res == S_OK) { - _resourcesPrefix.SetFromAscii(sect.Name); + _resourcesPrefix = sect.Name.Ptr(); _resourcesPrefix.Add_PathSepar(); FOR_VECTOR (j, _items) { const CResItem &item = _items[j]; if (item.Enabled) { - mixItem.ResourceIndex = j; + CMixItem mixItem; + mixItem.Construct(); + mixItem.SectionIndex = (int)i; + mixItem.ResourceIndex = (int)j; if (item.IsRcDataOrUnknown()) { if (item.Size >= mainSize) { mainSize2 = mainSize; mainSize = item.Size; - _mainSubfile = _mixItems.Size(); + _mainSubfile = (Int32)(int)_mixItems.Size(); } else if (item.Size >= mainSize2) mainSize2 = item.Size; @@ -2387,19 +2711,25 @@ HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *callback) } if (res != S_FALSE) return res; + _mixItems.DeleteFrom(numMixItems); CloseResources(); } + if (sect.IsAdditionalSection) { if (sect.PSize >= mainSize) { mainSize2 = mainSize; mainSize = sect.PSize; - _mainSubfile = _mixItems.Size(); + _mainSubfile = (Int32)(int)_mixItems.Size(); } else if (sect.PSize >= mainSize2) mainSize2 = sect.PSize; } + + CMixItem mixItem; + mixItem.Construct(); + mixItem.SectionIndex = (int)i; _mixItems.Add(mixItem); } @@ -2409,9 +2739,9 @@ HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *callback) for (i = 0; i < _mixItems.Size(); i++) { const CMixItem &mixItem = _mixItems[i]; - if (mixItem.StringIndex < 0 && mixItem.ResourceIndex < 0 && _sections[mixItem.SectionIndex].Name == "_winzip_") + if (mixItem.StringIndex < 0 && mixItem.ResourceIndex < 0 && _sections[mixItem.SectionIndex].Name.IsEqualTo("_winzip_")) { - _mainSubfile = i; + _mainSubfile = (Int32)(int)i; break; } } @@ -2422,7 +2752,7 @@ HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *callback) _versionFullString.Add_LF(); const CStringKeyValue &k = _versionKeys[i]; _versionFullString += k.Key; - _versionFullString += L": "; + _versionFullString += ": "; _versionFullString += k.Value; } @@ -2448,11 +2778,11 @@ HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *callback) return S_OK; } -STDMETHODIMP CHandler::Open(IInStream *inStream, const UInt64 *, IArchiveOpenCallback *callback) +Z7_COM7F_IMF(CHandler::Open(IInStream *inStream, const UInt64 *, IArchiveOpenCallback *callback)) { COM_TRY_BEGIN Close(); - RINOK(Open2(inStream, callback)); + RINOK(Open2(inStream, callback)) _stream = inStream; return S_OK; COM_TRY_END @@ -2471,10 +2801,13 @@ void CHandler::CloseResources() _versionKeys.Clear(); } -STDMETHODIMP CHandler::Close() +Z7_COM7F_IMF(CHandler::Close()) { _totalSize = 0; _checksumError = false; + _sectionsError = false; + _mainSubfile = -1; + _stream.Release(); _sections.Clear(); _mixItems.Clear(); @@ -2482,17 +2815,17 @@ STDMETHODIMP CHandler::Close() return S_OK; } -STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) { *numItems = _mixItems.Size(); return S_OK; } -STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, - Int32 testMode, IArchiveExtractCallback *extractCallback) +Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testMode, IArchiveExtractCallback *extractCallback)) { COM_TRY_BEGIN - bool allFilesMode = (numItems == (UInt32)(Int32)-1); + const bool allFilesMode = (numItems == (UInt32)(Int32)-1); if (allFilesMode) numItems = _mixItems.Size(); if (numItems == 0) @@ -2510,36 +2843,33 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, else if (mixItem.ResourceIndex >= 0) size = _items[mixItem.ResourceIndex].GetSize(); else - size = _sections[mixItem.SectionIndex].GetSizeExtract(); + size = _sections[mixItem.SectionIndex].GetSize_Extract(); totalSize += size; } - extractCallback->SetTotal(totalSize); + RINOK(extractCallback->SetTotal(totalSize)) - UInt64 currentTotalSize = 0; - UInt64 currentItemSize; - - NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder(); - CMyComPtr copyCoder = copyCoderSpec; - - CLocalProgress *lps = new CLocalProgress; - CMyComPtr progress = lps; + CMyComPtr2_Create copyCoder; + CMyComPtr2_Create lps; lps->Init(extractCallback, false); + CMyComPtr2_Create inStream; + inStream->SetStream(_stream); - CLimitedSequentialInStream *streamSpec = new CLimitedSequentialInStream; - CMyComPtr inStream(streamSpec); - streamSpec->SetStream(_stream); - - for (i = 0; i < numItems; i++, currentTotalSize += currentItemSize) + totalSize = 0; + UInt64 currentItemSize; + + for (i = 0;; i++, totalSize += currentItemSize) { - lps->InSize = lps->OutSize = currentTotalSize; - RINOK(lps->SetCur()); - Int32 askMode = testMode ? + lps->InSize = lps->OutSize = totalSize; + RINOK(lps->SetCur()) + if (i >= numItems) + break; + const Int32 askMode = testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; - UInt32 index = allFilesMode ? i : indices[i]; + const UInt32 index = allFilesMode ? i : indices[i]; CMyComPtr outStream; - RINOK(extractCallback->GetStream(index, &outStream, askMode)); + RINOK(extractCallback->GetStream(index, &outStream, askMode)) const CMixItem &mixItem = _mixItems[index]; const CSection § = _sections[mixItem.SectionIndex]; @@ -2551,9 +2881,9 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, if (!testMode && !outStream) continue; - RINOK(extractCallback->PrepareOperation(askMode)); + RINOK(extractCallback->PrepareOperation(askMode)) if (outStream) - RINOK(WriteStream(outStream, item.Buf, item.FinalSize())); + RINOK(WriteStream(outStream, item.Buf, item.FinalSize())) } else if (mixItem.VersionIndex >= 0) { @@ -2562,9 +2892,9 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, if (!testMode && !outStream) continue; - RINOK(extractCallback->PrepareOperation(askMode)); + RINOK(extractCallback->PrepareOperation(askMode)) if (outStream) - RINOK(WriteStream(outStream, item, item.Size())); + RINOK(WriteStream(outStream, item, item.Size())) } else if (mixItem.ResourceIndex >= 0) { @@ -2573,48 +2903,48 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, if (!testMode && !outStream) continue; - RINOK(extractCallback->PrepareOperation(askMode)); + RINOK(extractCallback->PrepareOperation(askMode)) size_t offset = item.Offset - sect.Va; if (!CheckItem(sect, item, offset)) isOk = false; else if (outStream) { if (item.HeaderSize != 0) - RINOK(WriteStream(outStream, item.Header, item.HeaderSize)); - RINOK(WriteStream(outStream, _buf + offset, item.Size)); + RINOK(WriteStream(outStream, item.Header, item.HeaderSize)) + RINOK(WriteStream(outStream, _buf + offset, item.Size)) } } else { - currentItemSize = sect.GetSizeExtract(); + currentItemSize = sect.GetSize_Extract(); if (!testMode && !outStream) continue; - RINOK(extractCallback->PrepareOperation(askMode)); - RINOK(_stream->Seek(sect.Pa, STREAM_SEEK_SET, NULL)); - streamSpec->Init(currentItemSize); - RINOK(copyCoder->Code(inStream, outStream, NULL, NULL, progress)); - isOk = (copyCoderSpec->TotalSize == currentItemSize); + RINOK(extractCallback->PrepareOperation(askMode)) + RINOK(InStream_SeekSet(_stream, sect.Pa)) + inStream->Init(currentItemSize); + RINOK(copyCoder.Interface()->Code(inStream, outStream, NULL, NULL, lps)) + isOk = (copyCoder->TotalSize == currentItemSize); } outStream.Release(); RINOK(extractCallback->SetOperationResult(isOk ? NExtract::NOperationResult::kOK : - NExtract::NOperationResult::kDataError)); + NExtract::NOperationResult::kDataError)) } return S_OK; COM_TRY_END } -STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream) +Z7_COM7F_IMF(CHandler::GetStream(UInt32 index, ISequentialInStream **stream)) { COM_TRY_BEGIN - *stream = 0; + *stream = NULL; const CMixItem &mixItem = _mixItems[index]; const CSection § = _sections[mixItem.SectionIndex]; if (mixItem.IsSectionItem()) - return CreateLimitedInStream(_stream, sect.Pa, sect.PSize, stream); + return CreateLimitedInStream(_stream, sect.Pa, sect.GetSize_Extract(), stream); CBufInStream *inStreamSpec = new CBufInStream; CMyComPtr streamTemp = inStreamSpec; @@ -2656,7 +2986,7 @@ STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream) COM_TRY_END } -STDMETHODIMP CHandler::AllowTail(Int32 allowTail) +Z7_COM7F_IMF(CHandler::AllowTail(Int32 allowTail)) { _allowTail = IntToBool(allowTail); return S_OK; @@ -2665,15 +2995,46 @@ STDMETHODIMP CHandler::AllowTail(Int32 allowTail) static const Byte k_Signature[] = { 'M', 'Z' }; REGISTER_ARC_I( - "PE", "exe dll sys", 0, 0xDD, + "PE", "exe dll sys", NULL, 0xDD, k_Signature, 0, NArcInfoFlags::kPreArc, IsArc_Pe) - + +} + +namespace NCoff { + +API_FUNC_static_IsArc IsArc_Coff(const Byte *p, size_t size) +{ + if (size < NPe::kCoffHeaderSize) + return k_IsArc_Res_NEED_MORE; + NPe::CHeader header; + if (!header.ParseCoff(p)) + return k_IsArc_Res_NO; + return k_IsArc_Res_YES; +} } +/* +static const Byte k_Signature[] = +{ + 2, 0x4C, 0x01, // x86 + 2, 0x64, 0x86, // x64 + 2, 0x64, 0xAA // ARM64 +}; +REGISTER_ARC_I_CLS( +*/ +REGISTER_ARC_I_CLS_NO_SIG( + NPe::CHandler(true), + "COFF", "obj", NULL, 0xC6, + // k_Signature, + 0, + // NArcInfoFlags::kMultiSignature | + NArcInfoFlags::kStartOpen, + IsArc_Coff) +} namespace NTe { @@ -2702,7 +3063,8 @@ static bool FindValue(const CUInt32PCharPair *pairs, unsigned num, UInt32 value) return false; } -#define MY_FIND_VALUE(pairs, value) FindValue(pairs, ARRAY_SIZE(pairs), value) +#define MY_FIND_VALUE(pairs, val) FindValue(pairs, Z7_ARRAY_SIZE(pairs), val) +#define MY_FIND_VALUE_2(strings, val) (val < Z7_ARRAY_SIZE(strings) && strings[val]) static const UInt32 kNumSection_MAX = 32; @@ -2742,7 +3104,7 @@ bool CHeader::Parse(const Byte *p) G32(12, BaseOfCode); G64(16, ImageBase); */ - for (int i = 0; i < 2; i++) + for (unsigned i = 0; i < 2; i++) { CDataDir &dd = DataDir[i]; dd.Parse(p + 24 + i * 8); @@ -2751,7 +3113,7 @@ bool CHeader::Parse(const Byte *p) } return MY_FIND_VALUE(NPe::g_MachinePairs, Machine) && - MY_FIND_VALUE(NPe::g_SubSystems, SubSystem); + MY_FIND_VALUE_2(NPe::g_SubSystems, SubSystem); } API_FUNC_static_IsArc IsArc_Te(const Byte *p, size_t size) @@ -2775,6 +3137,7 @@ struct CSection { Byte Name[NPe::kNameSize]; + UInt32 ExtractSize; UInt32 VSize; UInt32 Va; UInt32 PSize; @@ -2791,6 +3154,7 @@ struct CSection G32(20, Pa); // G32(p + 32, NumRelocs); G32(36, Flags); + ExtractSize = (VSize && VSize < PSize) ? VSize : PSize; } bool Check() const @@ -2800,20 +3164,24 @@ struct CSection PSize <= ((UInt32)1 << 30); } + UInt32 GetSize_Extract() const + { + return ExtractSize; + } + void UpdateTotalSize(UInt32 &totalSize) { - UInt32 t = Pa + PSize; - if (t > totalSize) - totalSize = t; + const UInt32 t = Pa + PSize; + if (totalSize < t) + totalSize = t; } }; -class CHandler: - public IInArchive, - public IInArchiveGetStream, - public IArchiveAllowTail, - public CMyUnknownImp -{ + +Z7_CLASS_IMP_CHandler_IInArchive_2( + IInArchiveGetStream, + IArchiveAllowTail +) CRecordVector _items; CMyComPtr _stream; UInt32 _totalSize; @@ -2822,10 +3190,6 @@ class CHandler: HRESULT Open2(IInStream *stream); public: - MY_UNKNOWN_IMP3(IInArchive, IInArchiveGetStream, IArchiveAllowTail) - INTERFACE_IInArchive(;) - STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream); - STDMETHOD(AllowTail)(Int32 allowTail); CHandler(): _allowTail(false) {} }; @@ -2833,6 +3197,7 @@ static const Byte kProps[] = { kpidPath, kpidSize, + kpidPackSize, kpidVirtualSize, kpidCharacts, kpidOffset, @@ -2841,7 +3206,7 @@ static const Byte kProps[] = enum { - kpidSubSystem = kpidUserDefined, + kpidSubSystem = kpidUserDefined // , kpidImageBase }; @@ -2856,7 +3221,7 @@ static const CStatProp kArcProps[] = IMP_IInArchive_Props IMP_IInArchive_ArcProps_WITH_NAME -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; @@ -2864,7 +3229,7 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) { case kpidPhySize: prop = _totalSize; break; case kpidCpu: PAIR_TO_PROP(NPe::g_MachinePairs, _h.Machine, prop); break; - case kpidSubSystem: PAIR_TO_PROP(NPe::g_SubSystems, _h.SubSystem, prop); break; + case kpidSubSystem: TYPE_TO_PROP(NPe::g_SubSystems, _h.SubSystem, prop); break; /* case kpidImageBase: prop = _h.ImageBase; break; case kpidAddressOfEntryPoint: prop = _h.AddressOfEntryPoint; break; @@ -2876,7 +3241,7 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) COM_TRY_END } -STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; @@ -2891,7 +3256,7 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val prop = MultiByteToUnicodeString(name); break; } - case kpidSize: + case kpidSize: prop = (UInt64)item.GetSize_Extract(); break; case kpidPackSize: prop = (UInt64)item.PSize; break; case kpidVirtualSize: prop = (UInt64)item.VSize; break; case kpidOffset: prop = item.Pa; break; @@ -2907,7 +3272,7 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val HRESULT CHandler::Open2(IInStream *stream) { Byte h[kHeaderSize]; - RINOK(ReadStream_FALSE(stream, h, kHeaderSize)); + RINOK(ReadStream_FALSE(stream, h, kHeaderSize)) if (h[0] != 'V' || h[1] != 'Z') return S_FALSE; if (!_h.Parse(h)) @@ -2915,7 +3280,7 @@ HRESULT CHandler::Open2(IInStream *stream) UInt32 headerSize = NPe::kSectionSize * (UInt32)_h.NumSections; CByteArr buf(headerSize); - RINOK(ReadStream_FALSE(stream, buf, headerSize)); + RINOK(ReadStream_FALSE(stream, buf, headerSize)) headerSize += kHeaderSize; _totalSize = headerSize; @@ -2937,7 +3302,7 @@ HRESULT CHandler::Open2(IInStream *stream) if (!_allowTail) { UInt64 fileSize; - RINOK(stream->Seek(0, STREAM_SEEK_END, &fileSize)); + RINOK(InStream_GetSize_SeekToEnd(stream, fileSize)) if (fileSize > _totalSize) return S_FALSE; } @@ -2945,24 +3310,24 @@ HRESULT CHandler::Open2(IInStream *stream) return S_OK; } -STDMETHODIMP CHandler::Open(IInStream *inStream, +Z7_COM7F_IMF(CHandler::Open(IInStream *inStream, const UInt64 * /* maxCheckStartPosition */, - IArchiveOpenCallback * /* openArchiveCallback */) + IArchiveOpenCallback * /* openArchiveCallback */)) { COM_TRY_BEGIN Close(); - try + // try { if (Open2(inStream) != S_OK) return S_FALSE; _stream = inStream; } - catch(...) { return S_FALSE; } + // catch(...) { return S_FALSE; } return S_OK; COM_TRY_END } -STDMETHODIMP CHandler::Close() +Z7_COM7F_IMF(CHandler::Close()) { _totalSize = 0; _stream.Release(); @@ -2970,17 +3335,17 @@ STDMETHODIMP CHandler::Close() return S_OK; } -STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) { *numItems = _items.Size(); return S_OK; } -STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, - Int32 testMode, IArchiveExtractCallback *extractCallback) +Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testMode, IArchiveExtractCallback *extractCallback)) { COM_TRY_BEGIN - bool allFilesMode = (numItems == (UInt32)(Int32)-1); + const bool allFilesMode = (numItems == (UInt32)(Int32)-1); if (allFilesMode) numItems = _items.Size(); if (numItems == 0) @@ -2988,62 +3353,62 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, UInt64 totalSize = 0; UInt32 i; for (i = 0; i < numItems; i++) - totalSize += _items[allFilesMode ? i : indices[i]].PSize; - extractCallback->SetTotal(totalSize); - - UInt64 currentTotalSize = 0; - - NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder(); - CMyComPtr copyCoder = copyCoderSpec; + totalSize += _items[allFilesMode ? i : indices[i]].GetSize_Extract(); + RINOK(extractCallback->SetTotal(totalSize)) - CLocalProgress *lps = new CLocalProgress; - CMyComPtr progress = lps; + CMyComPtr2_Create copyCoder; + CMyComPtr2_Create lps; lps->Init(extractCallback, false); + CMyComPtr2_Create inStream; + inStream->SetStream(_stream); - CLimitedSequentialInStream *streamSpec = new CLimitedSequentialInStream; - CMyComPtr inStream(streamSpec); - streamSpec->SetStream(_stream); + totalSize = 0; - for (i = 0; i < numItems; i++) + for (i = 0;; i++) { - lps->InSize = lps->OutSize = currentTotalSize; - RINOK(lps->SetCur()); + lps->InSize = lps->OutSize = totalSize; + RINOK(lps->SetCur()) + if (i >= numItems) + break; + int opRes; + { CMyComPtr realOutStream; - Int32 askMode = testMode ? + const Int32 askMode = testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; - UInt32 index = allFilesMode ? i : indices[i]; + const UInt32 index = allFilesMode ? i : indices[i]; const CSection &item = _items[index]; - RINOK(extractCallback->GetStream(index, &realOutStream, askMode)); - currentTotalSize += item.PSize; + RINOK(extractCallback->GetStream(index, &realOutStream, askMode)) + const UInt32 size = item.GetSize_Extract(); + totalSize += size; if (!testMode && !realOutStream) continue; - RINOK(extractCallback->PrepareOperation(askMode)); - int res = NExtract::NOperationResult::kDataError; - - RINOK(_stream->Seek(item.Pa, STREAM_SEEK_SET, NULL)); - streamSpec->Init(item.PSize); - RINOK(copyCoder->Code(inStream, realOutStream, NULL, NULL, progress)); - if (copyCoderSpec->TotalSize == item.PSize) - res = NExtract::NOperationResult::kOK; - - realOutStream.Release(); - RINOK(extractCallback->SetOperationResult(res)); + RINOK(extractCallback->PrepareOperation(askMode)) + RINOK(InStream_SeekSet(_stream, item.Pa)) + inStream->Init(size); + RINOK(copyCoder.Interface()->Code(inStream, realOutStream, NULL, NULL, lps)) + + opRes = (copyCoder->TotalSize == size) ? + NExtract::NOperationResult::kOK : (copyCoder->TotalSize < size) ? + NExtract::NOperationResult::kUnexpectedEnd : + NExtract::NOperationResult::kDataError; + } + RINOK(extractCallback->SetOperationResult(opRes)) } return S_OK; COM_TRY_END } -STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream) +Z7_COM7F_IMF(CHandler::GetStream(UInt32 index, ISequentialInStream **stream)) { COM_TRY_BEGIN const CSection &item = _items[index]; - return CreateLimitedInStream(_stream, item.Pa, item.PSize, stream); + return CreateLimitedInStream(_stream, item.Pa, item.GetSize_Extract(), stream); COM_TRY_END } -STDMETHODIMP CHandler::AllowTail(Int32 allowTail) +Z7_COM7F_IMF(CHandler::AllowTail(Int32 allowTail)) { _allowTail = IntToBool(allowTail); return S_OK; @@ -3052,7 +3417,7 @@ STDMETHODIMP CHandler::AllowTail(Int32 allowTail) static const Byte k_Signature[] = { 'V', 'Z' }; REGISTER_ARC_I( - "TE", "te", 0, 0xCF, + "TE", "te", NULL, 0xCF, k_Signature, 0, NArcInfoFlags::kPreArc, diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/PpmdHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/PpmdHandler.cpp index 528c5ceb..5f9e1c3f 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/PpmdHandler.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/PpmdHandler.cpp @@ -1,5 +1,5 @@ /* PpmdHandler.cpp -- PPMd format handler -2015-11-30 : Igor Pavlov : Public domain +2020 : Igor Pavlov : Public domain This code is based on: PPMd var.H (2001) / var.I (2002): Dmitry Shkarin : Public domain Carryless rangecoder (1999): Dmitry Subbotin : Public domain */ @@ -12,7 +12,6 @@ This code is based on: #include "../../../C/Ppmd8.h" #include "../../Common/ComTry.h" -#include "../../Common/IntToString.h" #include "../../Common/StringConvert.h" #include "../../Windows/PropVariant.h" @@ -34,13 +33,13 @@ struct CBuf { Byte *Buf; - CBuf(): Buf(0) {} + CBuf(): Buf(NULL) {} ~CBuf() { ::MidFree(Buf); } bool Alloc() { if (!Buf) Buf = (Byte *)::MidAlloc(kBufSize); - return (Buf != 0); + return (Buf != NULL); } }; @@ -60,18 +59,22 @@ struct CItem unsigned Restor; HRESULT ReadHeader(ISequentialInStream *s, UInt32 &headerSize); - bool IsSupported() const { return Ver == 7 || (Ver == 8 && Restor <= 1); } + bool IsSupported() const + { + return (Ver == 7 && Order >= PPMD7_MIN_ORDER) + || (Ver == 8 && Order >= PPMD8_MIN_ORDER && Restor < PPMD8_RESTORE_METHOD_UNSUPPPORTED); + } }; HRESULT CItem::ReadHeader(ISequentialInStream *s, UInt32 &headerSize) { Byte h[kHeaderSize]; - RINOK(ReadStream_FALSE(s, h, kHeaderSize)); + RINOK(ReadStream_FALSE(s, h, kHeaderSize)) if (GetUi32(h) != kSignature) return S_FALSE; Attrib = GetUi32(h + 4); Time = GetUi32(h + 12); - unsigned info = GetUi16(h + 8); + const unsigned info = GetUi16(h + 8); Order = (info & 0xF) + 1; MemInMB = ((info >> 4) & 0xFF) + 1; Ver = info >> 12; @@ -87,27 +90,23 @@ HRESULT CItem::ReadHeader(ISequentialInStream *s, UInt32 &headerSize) if (nameLen > (1 << 9)) return S_FALSE; char *name = Name.GetBuf(nameLen); - HRESULT res = ReadStream_FALSE(s, name, nameLen); + const HRESULT res = ReadStream_FALSE(s, name, nameLen); Name.ReleaseBuf_CalcLen(nameLen); headerSize = kHeaderSize + nameLen; return res; } -class CHandler: - public IInArchive, - public IArchiveOpenSeq, - public CMyUnknownImp -{ + +Z7_CLASS_IMP_CHandler_IInArchive_1( + IArchiveOpenSeq +) CItem _item; UInt32 _headerSize; bool _packSize_Defined; UInt64 _packSize; CMyComPtr _stream; -public: - MY_UNKNOWN_IMP2(IInArchive, IArchiveOpenSeq) - INTERFACE_IInArchive(;) - STDMETHOD(OpenSeq)(ISequentialInStream *stream); + void GetVersion(NCOM::CPropVariant &prop); }; static const Byte kProps[] = @@ -118,36 +117,51 @@ static const Byte kProps[] = kpidMethod }; +static const Byte kArcProps[] = +{ + kpidMethod +}; + IMP_IInArchive_Props -IMP_IInArchive_ArcProps_NO_Table +IMP_IInArchive_ArcProps + +void CHandler::GetVersion(NCOM::CPropVariant &prop) +{ + AString s ("PPMd"); + s.Add_Char((char)('A' + _item.Ver)); + s += ":o"; + s.Add_UInt32(_item.Order); + s += ":mem"; + s.Add_UInt32(_item.MemInMB); + s.Add_Char('m'); + if (_item.Ver >= kNewHeaderVer && _item.Restor != 0) + { + s += ":r"; + s.Add_UInt32(_item.Restor); + } + prop = s; +} -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { NCOM::CPropVariant prop; switch (propID) { case kpidPhySize: if (_packSize_Defined) prop = _packSize; break; + case kpidMethod: GetVersion(prop); break; } prop.Detach(value); return S_OK; } -STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) { *numItems = 1; return S_OK; } -static void UIntToString(AString &s, const char *prefix, unsigned value) -{ - s += prefix; - char temp[16]; - ::ConvertUInt32ToString((UInt32)value, temp); - s += temp; -} - -STDMETHODIMP CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; @@ -158,35 +172,25 @@ STDMETHODIMP CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIAN { // time can be in Unix format ??? FILETIME utc; - if (NTime::DosTimeToFileTime(_item.Time, utc)) + if (NTime::DosTime_To_FileTime(_item.Time, utc)) prop = utc; break; } case kpidAttrib: prop = _item.Attrib; break; case kpidPackSize: if (_packSize_Defined) prop = _packSize; break; - case kpidMethod: - { - AString s = "PPMd"; - s += (char)('A' + _item.Ver); - UIntToString(s, ":o", _item.Order); - UIntToString(s, ":mem", _item.MemInMB); - s += 'm'; - if (_item.Ver >= kNewHeaderVer && _item.Restor != 0) - UIntToString(s, ":r", _item.Restor); - prop = s; - } + case kpidMethod: GetVersion(prop); break; } prop.Detach(value); return S_OK; COM_TRY_END } -STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *) +Z7_COM7F_IMF(CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *)) { return OpenSeq(stream); } -STDMETHODIMP CHandler::OpenSeq(ISequentialInStream *stream) +Z7_COM7F_IMF(CHandler::OpenSeq(ISequentialInStream *stream)) { COM_TRY_BEGIN HRESULT res; @@ -204,7 +208,7 @@ STDMETHODIMP CHandler::OpenSeq(ISequentialInStream *stream) COM_TRY_END } -STDMETHODIMP CHandler::Close() +Z7_COM7F_IMF(CHandler::Close()) { _packSize = 0; _packSize_Defined = false; @@ -212,89 +216,11 @@ STDMETHODIMP CHandler::Close() return S_OK; } -static const UInt32 kTopValue = (1 << 24); -static const UInt32 kBot = (1 << 15); - -struct CRangeDecoder -{ - IPpmd7_RangeDec s; - UInt32 Range; - UInt32 Code; - UInt32 Low; - CByteInBufWrap *Stream; - -public: - bool Init() - { - Code = 0; - Low = 0; - Range = 0xFFFFFFFF; - for (int i = 0; i < 4; i++) - Code = (Code << 8) | Stream->ReadByte(); - return Code < 0xFFFFFFFF; - } - - void Normalize() - { - while ((Low ^ (Low + Range)) < kTopValue || - Range < kBot && ((Range = (0 - Low) & (kBot - 1)), 1)) - { - Code = (Code << 8) | Stream->ReadByte(); - Range <<= 8; - Low <<= 8; - } - } - - CRangeDecoder(); -}; - - -extern "C" { - -static UInt32 Range_GetThreshold(void *pp, UInt32 total) -{ - CRangeDecoder *p = (CRangeDecoder *)pp; - return p->Code / (p->Range /= total); -} - -static void Range_Decode(void *pp, UInt32 start, UInt32 size) -{ - CRangeDecoder *p = (CRangeDecoder *)pp; - start *= p->Range; - p->Low += start; - p->Code -= start; - p->Range *= size; - p->Normalize(); -} - -static UInt32 Range_DecodeBit(void *pp, UInt32 size0) -{ - CRangeDecoder *p = (CRangeDecoder *)pp; - if (p->Code / (p->Range >>= 14) < size0) - { - Range_Decode(p, 0, size0); - return 0; - } - else - { - Range_Decode(p, size0, (1 << 14) - size0); - return 1; - } -} - -} -CRangeDecoder::CRangeDecoder() -{ - s.GetThreshold = Range_GetThreshold; - s.Decode = Range_Decode; - s.DecodeBit = Range_DecodeBit; -} struct CPpmdCpp { unsigned Ver; - CRangeDecoder _rc; CPpmd7 _ppmd7; CPpmd8 _ppmd8; @@ -324,34 +250,34 @@ struct CPpmdCpp if (Ver == 7) Ppmd7_Init(&_ppmd7, order); else - Ppmd8_Init(&_ppmd8, order, restor);; + Ppmd8_Init(&_ppmd8, order, restor); } bool InitRc(CByteInBufWrap *inStream) { if (Ver == 7) { - _rc.Stream = inStream; - return _rc.Init(); + _ppmd7.rc.dec.Stream = &inStream->vt; + return (Ppmd7a_RangeDec_Init(&_ppmd7.rc.dec) != 0); } else { - _ppmd8.Stream.In = &inStream->p; - return Ppmd8_RangeDec_Init(&_ppmd8) != 0; + _ppmd8.Stream.In = &inStream->vt; + return Ppmd8_Init_RangeDec(&_ppmd8) != 0; } } bool IsFinishedOK() { if (Ver == 7) - return Ppmd7z_RangeDec_IsFinishedOK(&_rc); + return Ppmd7z_RangeDec_IsFinishedOK(&_ppmd7.rc.dec); return Ppmd8_RangeDec_IsFinishedOK(&_ppmd8); } }; -STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, - Int32 testMode, IArchiveExtractCallback *extractCallback) +Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testMode, IArchiveExtractCallback *extractCallback)) { if (numItems == 0) return S_OK; @@ -360,16 +286,18 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, // extractCallback->SetTotal(_packSize); UInt64 currentTotalPacked = 0; - RINOK(extractCallback->SetCompleted(¤tTotalPacked)); + RINOK(extractCallback->SetCompleted(¤tTotalPacked)) + Int32 opRes; +{ CMyComPtr realOutStream; - Int32 askMode = testMode ? + const Int32 askMode = testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; - RINOK(extractCallback->GetStream(0, &realOutStream, askMode)); + RINOK(extractCallback->GetStream(0, &realOutStream, askMode)) if (!testMode && !realOutStream) return S_OK; - extractCallback->PrepareOperation(askMode); + RINOK(extractCallback->PrepareOperation(askMode)) CByteInBufWrap inBuf; if (!inBuf.Alloc(1 << 20)) @@ -380,15 +308,14 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, if (!outBuf.Alloc()) return E_OUTOFMEMORY; - CLocalProgress *lps = new CLocalProgress; - CMyComPtr progress = lps; + CMyComPtr2_Create lps; lps->Init(extractCallback, true); CPpmdCpp ppmd(_item.Ver); if (!ppmd.Alloc(_item.MemInMB)) return E_OUTOFMEMORY; - Int32 opRes = NExtract::NOperationResult::kUnsupportedMethod; + opRes = NExtract::NOperationResult::kUnsupportedMethod; if (_item.IsSupported()) { @@ -403,19 +330,20 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, { lps->InSize = _packSize = inBuf.GetProcessed(); lps->OutSize = outSize; - RINOK(lps->SetCur()); + RINOK(lps->SetCur()) size_t i; int sym = 0; + Byte *buf = outBuf.Buf; if (ppmd.Ver == 7) { for (i = 0; i < kBufSize; i++) { - sym = Ppmd7_DecodeSymbol(&ppmd._ppmd7, &ppmd._rc.s); + sym = Ppmd7a_DecodeSymbol(&ppmd._ppmd7); if (inBuf.Extra || sym < 0) break; - outBuf.Buf[i] = (Byte)sym; + buf[i] = (Byte)sym; } } else @@ -425,7 +353,7 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, sym = Ppmd8_DecodeSymbol(&ppmd._ppmd8); if (inBuf.Extra || sym < 0) break; - outBuf.Buf[i] = (Byte)sym; + buf[i] = (Byte)sym; } } @@ -434,7 +362,7 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, _packSize_Defined = true; if (realOutStream) { - RINOK(WriteStream(realOutStream, outBuf.Buf, i)); + RINOK(WriteStream(realOutStream, outBuf.Buf, i)) } if (inBuf.Extra) @@ -451,10 +379,9 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, } } - RINOK(inBuf.Res); + RINOK(inBuf.Res) } - - realOutStream.Release(); +} return extractCallback->SetOperationResult(opRes); } @@ -462,7 +389,7 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, static const Byte k_Signature[] = { 0x8F, 0xAF, 0xAC, 0x84 }; REGISTER_ARC_I( - "Ppmd", "pmd", 0, 0xD, + "Ppmd", "pmd", NULL, 0xD, k_Signature, 0, 0, diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/QcowHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/QcowHandler.cpp index a84fdc9b..6edf86d9 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/QcowHandler.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/QcowHandler.cpp @@ -8,8 +8,10 @@ #include "../../Common/ComTry.h" #include "../../Common/IntToString.h" +#include "../../Common/MyBuffer2.h" #include "../../Windows/PropVariant.h" +#include "../../Windows/PropVariantUtils.h" #include "../Common/RegisterArc.h" #include "../Common/StreamObjects.h" @@ -19,80 +21,84 @@ #include "HandlerCont.h" -#define Get32(p) GetBe32(p) -#define Get64(p) GetBe64(p) +#define Get32(p) GetBe32a(p) +#define Get64(p) GetBe64a(p) using namespace NWindows; namespace NArchive { namespace NQcow { -#define SIGNATURE { 'Q', 'F', 'I', 0xFB, 0, 0, 0 } - -static const Byte k_Signature[] = SIGNATURE; +static const Byte k_Signature[] = { 'Q', 'F', 'I', 0xFB, 0, 0, 0 }; + +/* +VA to PA maps: + high bits (L1) : : index in L1 (_dir) : _dir[high_index] points to Table. + mid bits (L2) : _numMidBits : index in Table, Table[index] points to cluster start offset in arc file. + low bits : _clusterBits : offset inside cluster. +*/ -class CHandler: public CHandlerImg +Z7_class_CHandler_final: public CHandlerImg { + Z7_IFACE_COM7_IMP(IInArchive_Img) + Z7_IFACE_COM7_IMP(IInArchiveGetStream) + Z7_IFACE_COM7_IMP(ISequentialInStream) + unsigned _clusterBits; unsigned _numMidBits; UInt64 _compressedFlag; - CObjectVector _tables; - UInt64 _cacheCluster; + CObjArray2 _dir; + CAlignedBuffer _table; CByteBuffer _cache; CByteBuffer _cacheCompressed; + UInt64 _cacheCluster; UInt64 _comprPos; size_t _comprSize; - UInt64 _phySize; - - CBufInStream *_bufInStreamSpec; - CMyComPtr _bufInStream; - - CBufPtrSeqOutStream *_bufOutStreamSpec; - CMyComPtr _bufOutStream; - - NCompress::NDeflate::NDecoder::CCOMCoder *_deflateDecoderSpec; - CMyComPtr _deflateDecoder; - - bool _needDeflate; + bool _needCompression; bool _isArc; bool _unsupported; + Byte _compressionType; + + UInt64 _phySize; + + CMyComPtr2 _bufInStream; + CMyComPtr2 _bufOutStream; + CMyComPtr2 _deflateDecoder; UInt32 _version; UInt32 _cryptMethod; + UInt64 _incompatFlags; - HRESULT Seek(UInt64 offset) + HRESULT Seek2(UInt64 offset) { _posInArc = offset; - return Stream->Seek(offset, STREAM_SEEK_SET, NULL); + return InStream_SeekSet(Stream, offset); } HRESULT InitAndSeek() { _virtPos = 0; - return Seek(0); + return Seek2(0); } - HRESULT Open2(IInStream *stream, IArchiveOpenCallback *openCallback); - -public: - INTERFACE_IInArchive_Img(;) - - STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream); - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); + HRESULT Open2(IInStream *stream, IArchiveOpenCallback *openCallback) Z7_override; }; -STDMETHODIMP CHandler::Read(void *data, UInt32 size, UInt32 *processedSize) +static const UInt32 kEmptyDirItem = (UInt32)0 - 1; + +Z7_COM7F_IMF(CHandler::Read(void *data, UInt32 size, UInt32 *processedSize)) { if (processedSize) *processedSize = 0; + // printf("\nRead _virtPos = %6d size = %6d\n", (UInt32)_virtPos, size); if (_virtPos >= _size) return S_OK; { - UInt64 rem = _size - _virtPos; + const UInt64 rem = _size - _virtPos; if (size > rem) size = (UInt32)rem; if (size == 0) @@ -101,55 +107,62 @@ STDMETHODIMP CHandler::Read(void *data, UInt32 size, UInt32 *processedSize) for (;;) { - UInt64 cluster = _virtPos >> _clusterBits; - size_t clusterSize = (size_t)1 << _clusterBits; - size_t lowBits = (size_t)_virtPos & (clusterSize - 1); + const UInt64 cluster = _virtPos >> _clusterBits; + const size_t clusterSize = (size_t)1 << _clusterBits; + const size_t lowBits = (size_t)_virtPos & (clusterSize - 1); { - size_t rem = clusterSize - lowBits; + const size_t rem = clusterSize - lowBits; if (size > rem) size = (UInt32)rem; } - if (cluster == _cacheCluster) { memcpy(data, _cache + lowBits, size); - _virtPos += size; - if (processedSize) - *processedSize = size; - return S_OK; + break; } - - UInt64 high = cluster >> _numMidBits; + + const UInt64 high = cluster >> _numMidBits; - if (high < _tables.Size()) + if (high < _dir.Size()) { - const CByteBuffer &buffer = _tables[(unsigned)high]; - - if (buffer.Size() != 0) + const UInt32 tabl = _dir[(size_t)high]; + if (tabl != kEmptyDirItem) { - size_t midBits = (size_t)cluster & (((size_t)1 << _numMidBits) - 1); - const Byte *p = (const Byte *)buffer + (midBits << 3); + const size_t midBits = (size_t)cluster & (((size_t)1 << _numMidBits) - 1); + const Byte *p = _table + ((((size_t)tabl << _numMidBits) + midBits) << 3); UInt64 v = Get64(p); - if (v != 0) + if (v) { - if ((v & _compressedFlag) != 0) + if (v & _compressedFlag) { if (_version <= 1) return E_FAIL; - unsigned numOffsetBits = (62 - (_clusterBits - 8)); - UInt64 offset = v & (((UInt64)1 << 62) - 1); + /* + the example of table record for 12-bit clusters (4KB uncompressed): + 2 bits : isCompressed status + (4 == _clusterBits - 8) bits : (num_sectors - 1) + packSize = num_sectors * 512; + it uses one additional bit over unpacked cluster_bits. + (49 == 61 - _clusterBits) bits : offset of 512-byte sector + 9 bits : offset in 512-byte sector + */ + const unsigned numOffsetBits = 62 - (_clusterBits - 8); + const UInt64 offset = v & (((UInt64)1 << 62) - 1); const size_t dataSize = ((size_t)(offset >> numOffsetBits) + 1) << 9; - offset &= ((UInt64)1 << numOffsetBits) - 1; - UInt64 sectorOffset = offset >> 9 << 9; - UInt64 offset2inCache = sectorOffset - _comprPos; + UInt64 sectorOffset = offset & (((UInt64)1 << numOffsetBits) - (1 << 9)); + const UInt64 offset2inCache = sectorOffset - _comprPos; + // _comprPos is aligned for 512-bytes + // we try to use previous _cacheCompressed that contains compressed data + // that was read for previous unpacking + if (sectorOffset >= _comprPos && offset2inCache < _comprSize) { - if (offset2inCache != 0) + if (offset2inCache) { _comprSize -= (size_t)offset2inCache; - memmove(_cacheCompressed, _cacheCompressed + offset2inCache, _comprSize); + memmove(_cacheCompressed, _cacheCompressed + (size_t)offset2inCache, _comprSize); _comprPos = sectorOffset; } sectorOffset += _comprSize; @@ -160,70 +173,64 @@ STDMETHODIMP CHandler::Read(void *data, UInt32 size, UInt32 *processedSize) _comprSize = 0; } - // printf("\nDeflate"); - if (sectorOffset != _posInArc) + if (dataSize > _comprSize) { - // printf("\nDeflate %12I64x %12I64x\n", sectorOffset, sectorOffset - _posInArc); - RINOK(Seek(sectorOffset)); + if (sectorOffset != _posInArc) + { + // printf("\nDeflate-Seek %12I64x %12I64x\n", sectorOffset, sectorOffset - _posInArc); + RINOK(Seek2(sectorOffset)) + } + if (_cacheCompressed.Size() < dataSize) + return E_FAIL; + const size_t dataSize3 = dataSize - _comprSize; + size_t dataSize2 = dataSize3; + // printf("\n\n=======\nReadStream = %6d _comprPos = %6d \n", (UInt32)dataSize2, (UInt32)_comprPos); + const HRESULT hres = ReadStream(Stream, _cacheCompressed + _comprSize, &dataSize2); + _posInArc += dataSize2; + RINOK(hres) + if (dataSize2 != dataSize3) + return E_FAIL; + _comprSize += dataSize2; } - if (_cacheCompressed.Size() < dataSize) - return E_FAIL; - size_t dataSize3 = dataSize - _comprSize; - size_t dataSize2 = dataSize3; - RINOK(ReadStream(Stream, _cacheCompressed + _comprSize, &dataSize2)); - _posInArc += dataSize2; - if (dataSize2 != dataSize3) - return E_FAIL; - _comprSize += dataSize2; - const size_t kSectorMask = (1 << 9) - 1; - size_t offsetInSector = ((size_t)offset & kSectorMask); - _bufInStreamSpec->Init(_cacheCompressed + offsetInSector, dataSize - offsetInSector); - + const size_t offsetInSector = (size_t)offset & kSectorMask; + _bufInStream->Init(_cacheCompressed + offsetInSector, dataSize - offsetInSector); _cacheCluster = (UInt64)(Int64)-1; if (_cache.Size() < clusterSize) return E_FAIL; - _bufOutStreamSpec->Init(_cache, clusterSize); - + _bufOutStream->Init(_cache, clusterSize); // Do we need to use smaller block than clusterSize for last cluster? - UInt64 blockSize64 = clusterSize; - HRESULT res = _deflateDecoderSpec->Code(_bufInStream, _bufOutStream, NULL, &blockSize64, NULL); - + const UInt64 blockSize64 = clusterSize; + HRESULT res = _deflateDecoder.Interface()->Code(_bufInStream, _bufOutStream, NULL, &blockSize64, NULL); /* if (_bufOutStreamSpec->GetPos() != clusterSize) memset(_cache + _bufOutStreamSpec->GetPos(), 0, clusterSize - _bufOutStreamSpec->GetPos()); */ - if (res == S_OK) - if (!_deflateDecoderSpec->IsFinished() - || _bufOutStreamSpec->GetPos() != clusterSize) + if (!_deflateDecoder->IsFinished() + || _bufOutStream->GetPos() != clusterSize) res = S_FALSE; - - RINOK(res); + RINOK(res) _cacheCluster = cluster; - continue; /* memcpy(data, _cache + lowBits, size); - _virtPos += size; - if (processedSize) - *processedSize = size; - return S_OK; + break; */ } - // version 3 support zero clusters + // version_3 supports zero clusters if (((UInt32)v & 511) != 1) { - v &= (_compressedFlag - 1); + v &= _compressedFlag - 1; v += lowBits; if (v != _posInArc) { // printf("\n%12I64x\n", v - _posInArc); - RINOK(Seek(v)); + RINOK(Seek2(v)) } - HRESULT res = Stream->Read(data, size, &size); + const HRESULT res = Stream->Read(data, size, &size); _posInArc += size; _virtPos += size; if (processedSize) @@ -235,11 +242,13 @@ STDMETHODIMP CHandler::Read(void *data, UInt32 size, UInt32 *processedSize) } memset(data, 0, size); - _virtPos += size; - if (processedSize) - *processedSize = size; - return S_OK; + break; } + + _virtPos += size; + if (processedSize) + *processedSize = size; + return S_OK; } @@ -252,14 +261,26 @@ static const Byte kProps[] = static const Byte kArcProps[] = { kpidClusterSize, + kpidSectorSize, // actually we need variable to show table size + kpidHeadersSize, kpidUnpackVer, - kpidMethod + kpidMethod, + kpidCharacts }; IMP_IInArchive_Props IMP_IInArchive_ArcProps -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +static const CUInt32PCharPair g_IncompatFlags_Characts[] = +{ + { 0, "Dirty" }, + { 1, "Corrupt" }, + { 2, "External_Data_File" }, + { 3, "Compression" }, + { 4, "Extended_L2" } +}; + +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; @@ -268,44 +289,66 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) { case kpidMainSubfile: prop = (UInt32)0; break; case kpidClusterSize: prop = (UInt32)1 << _clusterBits; break; - case kpidPhySize: if (_phySize != 0) prop = _phySize; break; + case kpidSectorSize: prop = (UInt32)1 << (_numMidBits + 3); break; + case kpidHeadersSize: prop = _table.Size() + (UInt64)_dir.Size() * 8; break; + case kpidPhySize: if (_phySize) prop = _phySize; break; case kpidUnpackVer: prop = _version; break; - + case kpidCharacts: + { + if (_incompatFlags) + { + AString s ("incompatible: "); + // we need to show also high 32-bits. + s += FlagsToString(g_IncompatFlags_Characts, + Z7_ARRAY_SIZE(g_IncompatFlags_Characts), (UInt32)_incompatFlags); + prop = s; + } + break; + } case kpidMethod: { AString s; - if (_needDeflate) - s = "Deflate"; + if (_compressionType) + { + if (_compressionType == 1) + s += "ZSTD"; + else + { + s += "Compression:"; + s.Add_UInt32(_compressionType); + } + } + else if (_needCompression) + s.Add_OptSpaced("Deflate"); - if (_cryptMethod != 0) + if (_cryptMethod) { s.Add_Space_if_NotEmpty(); if (_cryptMethod == 1) s += "AES"; + if (_cryptMethod == 2) + s += "LUKS"; else { - char temp[16]; - ConvertUInt32ToString(_cryptMethod, temp); - s += temp; + s += "Encryption:"; + s.Add_UInt32(_cryptMethod); } } - if (!s.IsEmpty()) prop = s; - break; } case kpidErrorFlags: { UInt32 v = 0; - if (!_isArc) v |= kpv_ErrorFlags_IsNotArc;; + if (!_isArc) v |= kpv_ErrorFlags_IsNotArc; if (_unsupported) v |= kpv_ErrorFlags_UnsupportedMethod; // if (_headerError) v |= kpv_ErrorFlags_HeadersError; - if (!Stream && v == 0 && _isArc) + if (!Stream && v == 0) v = kpv_ErrorFlags_HeadersError; - if (v != 0) + if (v) prop = v; break; } @@ -317,7 +360,7 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) } -STDMETHODIMP CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; @@ -337,76 +380,91 @@ STDMETHODIMP CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIAN HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *openCallback) { - const unsigned kHeaderSize = 18 * 4; - Byte buf[kHeaderSize]; - RINOK(ReadStream_FALSE(stream, buf, kHeaderSize)); - - if (memcmp(buf, k_Signature, 4) != 0) + UInt64 buf64[0x70 / 8]; + RINOK(ReadStream_FALSE(stream, buf64, sizeof(buf64))) + const void *buf = (const void *)buf64; + // signature: { 'Q', 'F', 'I', 0xFB } + if (*(const UInt32 *)buf != Z7_CONV_BE_TO_NATIVE_CONST32(0x514649fb)) return S_FALSE; - - _version = Get32(buf + 4); + _version = Get32((const Byte *)(const void *)buf64 + 4); if (_version < 1 || _version > 3) return S_FALSE; - const UInt64 backOffset = Get64(buf + 8); - // UInt32 backSize = Get32(buf + 0x10); - - UInt64 l1Offset = 0; - UInt32 l1Size = 0; + const UInt64 k_UncompressedSize_MAX = (UInt64)1 << 60; + const UInt64 k_CompressedSize_MAX = (UInt64)1 << 60; + + _size = Get64((const Byte *)(const void *)buf64 + 0x18); + if (_size > k_UncompressedSize_MAX) + return S_FALSE; + size_t l1Size; + UInt32 headerSize; if (_version == 1) { - // _mTime = Get32(buf + 0x14); // is unused im most images - _size = Get64(buf + 0x18); - _clusterBits = buf[0x20]; - _numMidBits = buf[0x21]; + // _mTime = Get32((const Byte *)(const void *)buf64 + 0x14); // is unused in most images + _clusterBits = ((const Byte *)(const void *)buf64)[0x20]; + _numMidBits = ((const Byte *)(const void *)buf64)[0x21]; if (_clusterBits < 9 || _clusterBits > 30) return S_FALSE; if (_numMidBits < 1 || _numMidBits > 28) return S_FALSE; - _cryptMethod = Get32(buf + 0x24); - l1Offset = Get64(buf + 0x28); - if (l1Offset < 0x30) - return S_FALSE; - unsigned numBits2 = (_clusterBits + _numMidBits); - UInt64 l1Size64 = (_size + (((UInt64)1 << numBits2) - 1)) >> numBits2; + _cryptMethod = Get32((const Byte *)(const void *)buf64 + 0x24); + const unsigned numBits2 = _clusterBits + _numMidBits; + const UInt64 l1Size64 = (_size + (((UInt64)1 << numBits2) - 1)) >> numBits2; if (l1Size64 > ((UInt32)1 << 31)) return S_FALSE; - l1Size = (UInt32)l1Size64; + l1Size = (size_t)l1Size64; + headerSize = 0x30; } else { - _clusterBits = Get32(buf + 0x14); + _clusterBits = Get32((const Byte *)(const void *)buf64 + 0x14); if (_clusterBits < 9 || _clusterBits > 30) return S_FALSE; _numMidBits = _clusterBits - 3; - _size = Get64(buf + 0x18); - _cryptMethod = Get32(buf + 0x20); - l1Size = Get32(buf + 0x24); - l1Offset = Get64(buf + 0x28); // must be aligned for cluster - - UInt64 refOffset = Get64(buf + 0x30); // must be aligned for cluster - UInt32 refClusters = Get32(buf + 0x38); - - // UInt32 numSnapshots = Get32(buf + 0x3C); - // UInt64 snapshotsOffset = Get64(buf + 0x40); // must be aligned for cluster + _cryptMethod = Get32((const Byte *)(const void *)buf64 + 0x20); + l1Size = Get32((const Byte *)(const void *)buf64 + 0x24); + headerSize = 0x48; + if (_version >= 3) + { + _incompatFlags = Get64((const Byte *)(const void *)buf64 + 0x48); + // const UInt64 CompatFlags = Get64((const Byte *)(const void *)buf64 + 0x50); + // const UInt64 AutoClearFlags = Get64((const Byte *)(const void *)buf64 + 0x58); + // const UInt32 RefCountOrder = Get32((const Byte *)(const void *)buf64 + 0x60); + headerSize = 0x68; + const UInt32 headerSize2 = Get32((const Byte *)(const void *)buf64 + 0x64); + if (headerSize2 > (1u << 30)) + return S_FALSE; + if (headerSize < headerSize2) + headerSize = headerSize2; + if (headerSize2 >= 0x68 + 1) + _compressionType = ((const Byte *)(const void *)buf64)[0x68]; + } + + const UInt64 refOffset = Get64((const Byte *)(const void *)buf64 + 0x30); // must be aligned for cluster + const UInt32 refClusters = Get32((const Byte *)(const void *)buf64 + 0x38); + // UInt32 numSnapshots = Get32((const Byte *)(const void *)buf64 + 0x3C); + // UInt64 snapshotsOffset = Get64((const Byte *)(const void *)buf64 + 0x40); // must be aligned for cluster /* - if (numSnapshots != 0) + if (numSnapshots) return S_FALSE; */ - - if (refClusters != 0) + if (refClusters) { - size_t numBytes = refClusters << _clusterBits; + if (refOffset > k_CompressedSize_MAX) + return S_FALSE; + const UInt64 numBytes = (UInt64)refClusters << _clusterBits; + const UInt64 end = refOffset + numBytes; + if (end > k_CompressedSize_MAX) + return S_FALSE; /* CByteBuffer refs; refs.Alloc(numBytes); - RINOK(stream->Seek(refOffset, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekSet(stream, refOffset)) RINOK(ReadStream_FALSE(stream, refs, numBytes)); */ - UInt64 end = refOffset + numBytes; if (_phySize < end) - _phySize = end; + _phySize = end; /* for (size_t i = 0; i < numBytes; i += 2) { @@ -418,69 +476,121 @@ HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *openCallback) } } - _isArc = true; + const UInt64 l1Offset = Get64((const Byte *)(const void *)buf64 + 0x28); // must be aligned for cluster ? + if (l1Offset < headerSize || l1Offset > k_CompressedSize_MAX) + return S_FALSE; + if (_phySize < headerSize) + _phySize = headerSize; - if (backOffset != 0) - { - _unsupported = true; + // we use 32 MiB limit for L1 size, as QEMU with QCOW_MAX_L1_SIZE limit. + if (l1Size > (1u << 22)) // if (l1Size > (1u << (sizeof(size_t) * 8 - 4))) return S_FALSE; + + _isArc = true; + { + const UInt64 backOffset = Get64((const Byte *)(const void *)buf64 + 8); + // UInt32 backSize = Get32((const Byte *)(const void *)buf64 + 0x10); + if (backOffset) + { + _unsupported = true; + return S_FALSE; + } } - const size_t clusterSize = (size_t)1 << _clusterBits; + UInt64 fileSize = 0; + RINOK(InStream_GetSize_SeekToBegin(stream, fileSize)) - CByteBuffer table; + const size_t clusterSize = (size_t)1 << _clusterBits; + const size_t t1SizeBytes = (size_t)l1Size << 3; { - size_t t1SizeBytes = (size_t)l1Size << 3; - if ((t1SizeBytes >> 3) != l1Size) + const UInt64 end = l1Offset + t1SizeBytes; + if (end > k_CompressedSize_MAX) return S_FALSE; - table.Alloc(t1SizeBytes); - RINOK(stream->Seek(l1Offset, STREAM_SEEK_SET, NULL)); - RINOK(ReadStream_FALSE(stream, table, t1SizeBytes)); - - { - UInt64 end = l1Offset + t1SizeBytes; - // we need to uses align end for empty qcow files - end = (end + clusterSize - 1) >> _clusterBits << _clusterBits; - if (_phySize < end) + // we need to use align end for empty qcow files + // some files has no cluster alignment padding at the end + // but has sector alignment + // end = (end + clusterSize - 1) >> _clusterBits << _clusterBits; + if (_phySize < end) _phySize = end; + if (end > fileSize) + return S_FALSE; + if (_phySize < fileSize) + { + const UInt64 end2 = (end + 511) & ~(UInt64)511; + if (end2 == fileSize) + _phySize = end2; } } - - if (openCallback) + CObjArray table64(l1Size); { - UInt64 totalBytes = (UInt64)l1Size << (_numMidBits + 3); - RINOK(openCallback->SetTotal(NULL, &totalBytes)); + RINOK(InStream_SeekSet(stream, l1Offset)) + RINOK(ReadStream_FALSE(stream, table64, t1SizeBytes)) } _compressedFlag = (_version <= 1) ? ((UInt64)1 << 63) : ((UInt64)1 << 62); const UInt64 offsetMask = _compressedFlag - 1; + const size_t midSize = (size_t)1 << (_numMidBits + 3); + size_t numTables = 0; + size_t i; + + for (i = 0; i < l1Size; i++) + { + const UInt64 v = Get64(table64 + (size_t)i) & offsetMask; + if (!v) + continue; + numTables++; + const UInt64 end = v + midSize; + if (end > k_CompressedSize_MAX) + return S_FALSE; + if (_phySize < end) + _phySize = end; + if (end > fileSize) + return S_FALSE; + } - for (UInt32 i = 0; i < l1Size; i++) + if (numTables) { + const size_t size = (size_t)numTables << (_numMidBits + 3); + if (size >> (_numMidBits + 3) != numTables) + return E_OUTOFMEMORY; + _table.Alloc(size); + if (!_table.IsAllocated()) + return E_OUTOFMEMORY; if (openCallback) { - UInt64 numBytes = (UInt64)i << (_numMidBits + 3); - RINOK(openCallback->SetCompleted(NULL, &numBytes)); + const UInt64 totalBytes = size; + RINOK(openCallback->SetTotal(NULL, &totalBytes)) } + } - CByteBuffer &buf2 = _tables.AddNew(); - + _dir.SetSize((unsigned)l1Size); + + UInt32 curTable = 0; + + for (i = 0; i < l1Size; i++) + { + Byte *buf2; { - UInt64 v = Get64((const Byte *)table + (size_t)i * 8); - v &= offsetMask; + const UInt64 v = Get64(table64 + (size_t)i) & offsetMask; if (v == 0) + { + _dir[i] = kEmptyDirItem; continue; - - buf2.Alloc((size_t)1 << (_numMidBits + 3)); - RINOK(stream->Seek(v, STREAM_SEEK_SET, NULL)); - RINOK(ReadStream_FALSE(stream, buf2, clusterSize)); - - const UInt64 end = v + clusterSize; - if (_phySize < end) - _phySize = end; + } + _dir[i] = curTable; + const size_t tableOffset = (size_t)curTable << (_numMidBits + 3); + buf2 = (Byte *)_table + tableOffset; + curTable++; + if (openCallback && (tableOffset & 0xFFFFF) == 0) + { + const UInt64 numBytes = tableOffset; + RINOK(openCallback->SetCompleted(NULL, &numBytes)) + } + RINOK(InStream_SeekSet(stream, v)) + RINOK(ReadStream_FALSE(stream, buf2, midSize)) } - for (size_t k = 0; k < clusterSize; k += 8) + for (size_t k = 0; k < midSize; k += 8) { const UInt64 v = Get64((const Byte *)buf2 + (size_t)k); if (v == 0) @@ -488,33 +598,30 @@ HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *openCallback) UInt64 offset = v & offsetMask; size_t dataSize = clusterSize; - if ((v & _compressedFlag) != 0) + if (v & _compressedFlag) { if (_version <= 1) { - unsigned numOffsetBits = (63 - _clusterBits); + const unsigned numOffsetBits = 63 - _clusterBits; dataSize = ((size_t)(offset >> numOffsetBits) + 1) << 9; offset &= ((UInt64)1 << numOffsetBits) - 1; - dataSize = 0; - // offset >>= 9; - // offset <<= 9; + dataSize = 0; // why ? + // offset &= ~(((UInt64)1 << 9) - 1); } else { - unsigned numOffsetBits = (62 - (_clusterBits - 8)); + const unsigned numOffsetBits = 62 - (_clusterBits - 8); dataSize = ((size_t)(offset >> numOffsetBits) + 1) << 9; - offset &= ((UInt64)1 << numOffsetBits) - 1; - offset >>= 9; - offset <<= 9; + offset &= ((UInt64)1 << numOffsetBits) - (1 << 9); } - _needDeflate = true; + _needCompression = true; } else { - UInt32 low = (UInt32)v & 511; - if (low != 0) + const UInt32 low = (UInt32)v & 511; + if (low) { - // version 3 support zero clusters + // version_3 supports zero clusters if (_version < 3 || low != 1) { _unsupported = true; @@ -523,16 +630,20 @@ HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *openCallback) } } - UInt64 end = offset + dataSize; + const UInt64 end = offset + dataSize; if (_phySize < end) - _phySize = end; + _phySize = end; } } - if (_cryptMethod != 0) - _unsupported = true; + if (curTable != numTables) + return E_FAIL; - if (_needDeflate && _version <= 1) // that case was not implemented + if (_cryptMethod) + _unsupported = true; + if (_needCompression && _version <= 1) // that case was not implemented + _unsupported = true; + if (_compressionType) _unsupported = true; Stream = stream; @@ -540,65 +651,52 @@ HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *openCallback) } -STDMETHODIMP CHandler::Close() +Z7_COM7F_IMF(CHandler::Close()) { - _tables.Clear(); + _table.Free(); + _dir.Free(); + // _cache.Free(); + // _cacheCompressed.Free(); _phySize = 0; - _size = 0; _cacheCluster = (UInt64)(Int64)-1; _comprPos = 0; _comprSize = 0; - _needDeflate = false; + _needCompression = false; _isArc = false; _unsupported = false; - _imgExt = NULL; + _compressionType = 0; + _incompatFlags = 0; + + // CHandlerImg: + Clear_HandlerImg_Vars(); Stream.Release(); return S_OK; } -STDMETHODIMP CHandler::GetStream(UInt32 /* index */, ISequentialInStream **stream) +Z7_COM7F_IMF(CHandler::GetStream(UInt32 /* index */, ISequentialInStream **stream)) { COM_TRY_BEGIN *stream = NULL; - - if (_unsupported) + if (_unsupported || !Stream) return S_FALSE; - - if (_needDeflate) + if (_needCompression) { - if (_version <= 1) + if (_version <= 1 || _compressionType) return S_FALSE; - - if (!_bufInStream) - { - _bufInStreamSpec = new CBufInStream; - _bufInStream = _bufInStreamSpec; - } - - if (!_bufOutStream) - { - _bufOutStreamSpec = new CBufPtrSeqOutStream(); - _bufOutStream = _bufOutStreamSpec; - } - - if (!_deflateDecoder) - { - _deflateDecoderSpec = new NCompress::NDeflate::NDecoder::CCOMCoder(); - _deflateDecoder = _deflateDecoderSpec; - _deflateDecoderSpec->Set_NeedFinishInput(true); - } - - size_t clusterSize = (size_t)1 << _clusterBits; + _bufInStream.Create_if_Empty(); + _bufOutStream.Create_if_Empty(); + _deflateDecoder.Create_if_Empty(); + _deflateDecoder->Set_NeedFinishInput(true); + const size_t clusterSize = (size_t)1 << _clusterBits; _cache.AllocAtLeast(clusterSize); _cacheCompressed.AllocAtLeast(clusterSize * 2); } - CMyComPtr streamTemp = this; - RINOK(InitAndSeek()); + RINOK(InitAndSeek()) *stream = streamTemp.Detach(); return S_OK; COM_TRY_END diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Rar/Rar5Handler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/Rar/Rar5Handler.cpp index d70a5686..c15ff528 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Rar/Rar5Handler.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Rar/Rar5Handler.cpp @@ -7,6 +7,8 @@ #include "../../../Common/ComTry.h" #include "../../../Common/IntToString.h" +#include "../../../Common/MyBuffer2.h" +#include "../../../Common/MyLinux.h" #include "../../../Common/UTFConvert.h" #include "../../../Windows/PropVariantUtils.h" @@ -16,6 +18,7 @@ #include "../../Common/FilterCoder.h" #include "../../Common/LimitedStreams.h" +#include "../../Common/MethodProps.h" #include "../../Common/ProgressUtils.h" #include "../../Common/RegisterArc.h" #include "../../Common/StreamObjects.h" @@ -27,12 +30,13 @@ #include "../../Crypto/Rar5Aes.h" -#include "../Common/FindSignature.h" -#include "../Common/ItemNameUtils.h" +#include "../../Archive/Common/FindSignature.h" +#include "../../Archive/Common/ItemNameUtils.h" +#include "../../Archive/Common/HandlerOut.h" -#include "../HandlerCont.h" +#include "../../Archive/HandlerCont.h" -#include "RarVol.h" +#include "../../Archive/Rar/RarVol.h" #include "Rar5Handler.h" using namespace NWindows; @@ -44,11 +48,13 @@ namespace NRar5 { static const unsigned kMarkerSize = 8; -#define SIGNATURE { 0x52 , 0x61, 0x72, 0x21, 0x1a, 0x07, 0x01, 0 } +static const Byte kMarker[kMarkerSize] = + { 0x52, 0x61, 0x72, 0x21, 0x1a, 0x07, 0x01, 0 }; -static const Byte kMarker[kMarkerSize] = SIGNATURE; +// Comment length is limited to 256 KB in rar-encoder. +// So we use same limitation +static const size_t kCommentSize_Max = (size_t)1 << 18; -static const size_t kCommentSize_Max = (size_t)1 << 16; static const char * const kHostOS[] = { @@ -56,65 +62,130 @@ static const char * const kHostOS[] = , "Unix" }; -static const CUInt32PCharPair k_ArcFlags[] = + +static const char * const k_ArcFlags[] = { - { 0, "Volume" }, - { 1, "VolumeField" }, - { 2, "Solid" }, - { 3, "Recovery" }, - { 4, "Lock" } + "Volume" + , "VolumeField" + , "Solid" + , "Recovery" + , "Lock" // 4 }; +static const char * const k_FileFlags[] = +{ + "Dir" + , "UnixTime" + , "CRC" + , "UnknownSize" +}; + -template -struct CAlignedBuffer +static const char * const g_ExtraTypes[] = { - Byte *_buf; - Byte *_bufBase; - size_t _size; + "0" + , "Crypto" + , "Hash" + , "Time" + , "Version" + , "Link" + , "UnixOwner" + , "Subdata" +}; - CAlignedBuffer(): _buf(NULL), _bufBase(NULL), _size(0) {} - ~CAlignedBuffer() { ::MyFree(_bufBase); } -public: - operator Byte *() { return _buf; } - operator const Byte *() const { return _buf; } - void AllocAtLeast(size_t size) +static const char * const g_LinkTypes[] = +{ + "0" + , "UnixSymLink" + , "WinSymLink" + , "WinJunction" + , "HardLink" + , "FileCopy" +}; + + +static const char g_ExtraTimeFlags[] = { 'u', 'M', 'C', 'A', 'n' }; + + +static +Z7_NO_INLINE +unsigned ReadVarInt(const Byte *p, size_t maxSize, UInt64 *val_ptr) +{ + if (maxSize > 10) + maxSize = 10; + UInt64 val = 0; + unsigned i; + for (i = 0; i < maxSize;) { - if (_buf && _size >= size) - return; - ::MyFree(_bufBase); - _buf = NULL; - _size = 0; - _bufBase = (Byte *)::MyAlloc(size + alignMask); - - if (_bufBase) + const unsigned b = p[i]; + val |= (UInt64)(b & 0x7F) << (7 * i); + i++; + if ((b & 0x80) == 0) { - _size = size; - // _buf = (Byte *)(((uintptr_t)_bufBase + alignMask) & ~(uintptr_t)alignMask); - _buf = (Byte *)(((ptrdiff_t)_bufBase + alignMask) & ~(ptrdiff_t)alignMask); + *val_ptr = val; + return i; } } -}; + *val_ptr = 0; +#if 1 + return 0; // 7zip-unrar : strict check of error +#else + return i; // original-unrar : ignore error +#endif +} + + +#define PARSE_VAR_INT(p, size, dest) \ +{ const unsigned num_ = ReadVarInt(p, size, &dest); \ + if (num_ == 0) return false; \ + p += num_; \ + size -= num_; \ +} + -static unsigned ReadVarInt(const Byte *p, size_t maxSize, UInt64 *val) +bool CLinkInfo::Parse(const Byte *p, unsigned size) { - *val = 0; + const Byte *pStart = p; + UInt64 len; + PARSE_VAR_INT(p, size, Type) + PARSE_VAR_INT(p, size, Flags) + PARSE_VAR_INT(p, size, len) + if (size != len) + return false; + NameLen = (unsigned)len; + NameOffset = (unsigned)(size_t)(p - pStart); + return true; +} + + +static void AddHex64(AString &s, UInt64 v) +{ + char sz[32]; + sz[0] = '0'; + sz[1] = 'x'; + ConvertUInt64ToHex(v, sz + 2); + s += sz; +} - for (unsigned i = 0; i < maxSize;) + +static void PrintType(AString &s, const char * const table[], unsigned num, UInt64 val) +{ + char sz[32]; + const char *p = NULL; + if (val < num) + p = table[(unsigned)val]; + if (!p) { - Byte b = p[i]; - if (i < 10) - *val |= (UInt64)(b & 0x7F) << (7 * i++); - if ((b & 0x80) == 0) - return i; + ConvertUInt64ToString(val, sz); + p = sz; } - return 0; + s += p; } -int CItem::FindExtra(unsigned type, unsigned &recordDataSize) const +int CItem::FindExtra(unsigned extraID, unsigned &recordDataSize) const { recordDataSize = 0; size_t offset = 0; @@ -127,7 +198,7 @@ int CItem::FindExtra(unsigned type, unsigned &recordDataSize) const { UInt64 size; - unsigned num = ReadVarInt(Extra + offset, rem, &size); + const unsigned num = ReadVarInt(Extra + offset, rem, &size); if (num == 0) return -1; offset += num; @@ -137,8 +208,8 @@ int CItem::FindExtra(unsigned type, unsigned &recordDataSize) const rem = (size_t)size; } { - UInt64 type2; - unsigned num = ReadVarInt(Extra + offset, rem, &type2); + UInt64 id; + const unsigned num = ReadVarInt(Extra + offset, rem, &id); if (num == 0) return -1; offset += num; @@ -147,12 +218,12 @@ int CItem::FindExtra(unsigned type, unsigned &recordDataSize) const // There was BUG in RAR 5.21- : it stored (size-1) instead of (size) // for Subdata record in Service header. // That record always was last in bad archives, so we can fix that case. - if (type2 == NExtraRecordType::kSubdata + if (id == NExtraID::kSubdata && RecordType == NHeaderType::kService && rem + 1 == Extra.Size() - offset) rem++; - if (type2 == type) + if (id == extraID) { recordDataSize = (unsigned)rem; return (int)offset; @@ -164,19 +235,111 @@ int CItem::FindExtra(unsigned type, unsigned &recordDataSize) const } -bool CCryptoInfo::Parse(const Byte *p, size_t size) +void CItem::PrintInfo(AString &s) const { - unsigned num = ReadVarInt(p, size, &Algo); - if (num == 0) return false; p += num; size -= num; - - num = ReadVarInt(p, size, &Flags); - if (num == 0) return false; p += num; size -= num; + size_t offset = 0; - if (size != 1 + 16 + 16 + (unsigned)(IsThereCheck() ? 12 : 0)) - return false; + for (;;) + { + size_t rem = Extra.Size() - offset; + if (rem == 0) + return; + + { + UInt64 size; + unsigned num = ReadVarInt(Extra + offset, rem, &size); + if (num == 0) + return; + offset += num; + rem -= num; + if (size > rem) + break; + rem = (size_t)size; + } + { + UInt64 id; + { + unsigned num = ReadVarInt(Extra + offset, rem, &id); + if (num == 0) + break; + offset += num; + rem -= num; + } + + // There was BUG in RAR 5.21- : it stored (size-1) instead of (size) + // for Subdata record in Service header. + // That record always was last in bad archives, so we can fix that case. + if (id == NExtraID::kSubdata + && RecordType == NHeaderType::kService + && rem + 1 == Extra.Size() - offset) + rem++; + + s.Add_Space_if_NotEmpty(); + PrintType(s, g_ExtraTypes, Z7_ARRAY_SIZE(g_ExtraTypes), id); + + if (id == NExtraID::kTime) + { + const Byte *p = Extra + offset; + UInt64 flags; + const unsigned num = ReadVarInt(p, rem, &flags); + if (num != 0) + { + s.Add_Colon(); + for (unsigned i = 0; i < Z7_ARRAY_SIZE(g_ExtraTimeFlags); i++) + if ((flags & ((UInt64)1 << i)) != 0) + s.Add_Char(g_ExtraTimeFlags[i]); + flags &= ~(((UInt64)1 << Z7_ARRAY_SIZE(g_ExtraTimeFlags)) - 1); + if (flags != 0) + { + s.Add_Char('_'); + AddHex64(s, flags); + } + } + } + else if (id == NExtraID::kLink) + { + CLinkInfo linkInfo; + if (linkInfo.Parse(Extra + offset, (unsigned)rem)) + { + s.Add_Colon(); + PrintType(s, g_LinkTypes, Z7_ARRAY_SIZE(g_LinkTypes), linkInfo.Type); + UInt64 flags = linkInfo.Flags; + if (flags != 0) + { + s.Add_Colon(); + if (flags & NLinkFlags::kTargetIsDir) + { + s.Add_Char('D'); + flags &= ~((UInt64)NLinkFlags::kTargetIsDir); + } + if (flags != 0) + { + s.Add_Char('_'); + AddHex64(s, flags); + } + } + } + } + + offset += rem; + } + } + + s.Add_OptSpaced("ERROR"); +} - Cnt = p[0]; +bool CCryptoInfo::Parse(const Byte *p, size_t size) +{ + Algo = 0; + Flags = 0; + Cnt = 0; + PARSE_VAR_INT(p, size, Algo) + PARSE_VAR_INT(p, size, Flags) + if (size > 0) + Cnt = p[0]; + if (size != 1 + 16 + 16 + (unsigned)(IsThereCheck() ? 12 : 0)) + return false; return true; } @@ -184,44 +347,26 @@ bool CCryptoInfo::Parse(const Byte *p, size_t size) bool CItem::FindExtra_Version(UInt64 &version) const { unsigned size; - int offset = FindExtra(NExtraRecordType::kVersion, size); + const int offset = FindExtra(NExtraID::kVersion, size); if (offset < 0) return false; const Byte *p = Extra + (unsigned)offset; UInt64 flags; - unsigned num = ReadVarInt(p, size, &flags); - if (num == 0) return false; p += num; size -= num; - - num = ReadVarInt(p, size, &version); - if (num == 0) return false; p += num; size -= num; - + PARSE_VAR_INT(p, size, flags) + PARSE_VAR_INT(p, size, version) return size == 0; } bool CItem::FindExtra_Link(CLinkInfo &link) const { unsigned size; - int offset = FindExtra(NExtraRecordType::kLink, size); + const int offset = FindExtra(NExtraID::kLink, size); if (offset < 0) return false; - const Byte *p = Extra + (unsigned)offset; - - unsigned num = ReadVarInt(p, size, &link.Type); - if (num == 0) return false; p += num; size -= num; - - num = ReadVarInt(p, size, &link.Flags); - if (num == 0) return false; p += num; size -= num; - - UInt64 len; - num = ReadVarInt(p, size, &len); - if (num == 0) return false; p += num; size -= num; - - if (size != len) + if (!link.Parse(Extra + (unsigned)offset, size)) return false; - - link.NameLen = (unsigned)len; - link.NameOffset = (unsigned)(p - Extra); + link.NameOffset += (unsigned)offset; return true; } @@ -249,6 +394,7 @@ void CItem::Link_to_Prop(unsigned linkType, NWindows::NCOM::CPropVariant &prop) if (!FindExtra_Link(link)) return; + bool isWindows = (HostOS == kHost_Windows); if (link.Type != linkType) { if (linkType != NLinkType::kUnixSymLink) @@ -256,8 +402,11 @@ void CItem::Link_to_Prop(unsigned linkType, NWindows::NCOM::CPropVariant &prop) switch ((unsigned)link.Type) { case NLinkType::kUnixSymLink: + isWindows = false; + break; case NLinkType::kWinSymLink: case NLinkType::kWinJunction: + isWindows = true; break; default: return; } @@ -265,17 +414,22 @@ void CItem::Link_to_Prop(unsigned linkType, NWindows::NCOM::CPropVariant &prop) AString s; s.SetFrom_CalcLen((const char *)(Extra + link.NameOffset), link.NameLen); - UString unicode; - if (ConvertUTF8ToUnicode(s, unicode)) - prop = NItemName::GetOSName(unicode); + ConvertUTF8ToUnicode(s, unicode); + // rar5.0 used '\\' separator for windows symlinks and \??\ prefix for abs paths. + // rar5.1+ uses '/' separator for windows symlinks and /??/ prefix for abs paths. + // v25.00: we convert Windows slashes to Linux slashes: + if (isWindows) + unicode.Replace(L'\\', L'/'); + prop = unicode; + // prop = NItemName::GetOsPath(unicode); } bool CItem::GetAltStreamName(AString &name) const { name.Empty(); unsigned size; - int offset = FindExtra(NExtraRecordType::kSubdata, size); + const int offset = FindExtra(NExtraID::kSubdata, size); if (offset < 0) return false; name.SetFrom_CalcLen((const char *)(Extra + (unsigned)offset), size); @@ -288,8 +442,13 @@ class CHash bool _calcCRC; UInt32 _crc; int _blakeOffset; - CBlake2sp _blake; + CAlignedBuffer1 _buf; + // CBlake2sp _blake; + CBlake2sp *BlakeObj() { return (CBlake2sp *)(void *)(Byte *)_buf; } public: + CHash(): + _buf(sizeof(CBlake2sp)) + {} void Init_NoCalc() { @@ -302,17 +461,16 @@ class CHash void Update(const void *data, size_t size); UInt32 GetCRC() const { return CRC_GET_DIGEST(_crc); } - bool Check(const CItem &item, NCrypto::NRar5::CDecoder *cryptoDecoderSpec); + bool Check(const CItem &item, NCrypto::NRar5::CDecoder *cryptoDecoder); }; void CHash::Init(const CItem &item) { _crc = CRC_INIT_VAL; _calcCRC = item.Has_CRC(); - _blakeOffset = item.FindExtra_Blake(); if (_blakeOffset >= 0) - Blake2sp_Init(&_blake); + Blake2sp_Init(BlakeObj()); } void CHash::Update(const void *data, size_t size) @@ -320,52 +478,48 @@ void CHash::Update(const void *data, size_t size) if (_calcCRC) _crc = CrcUpdate(_crc, data, size); if (_blakeOffset >= 0) - Blake2sp_Update(&_blake, (const Byte *)data, size); + Blake2sp_Update(BlakeObj(), (const Byte *)data, size); } -bool CHash::Check(const CItem &item, NCrypto::NRar5::CDecoder *cryptoDecoderSpec) +bool CHash::Check(const CItem &item, NCrypto::NRar5::CDecoder *cryptoDecoder) { if (_calcCRC) { UInt32 crc = GetCRC(); - if (cryptoDecoderSpec) - crc = cryptoDecoderSpec->Hmac_Convert_Crc32(crc); + if (cryptoDecoder) + crc = cryptoDecoder->Hmac_Convert_Crc32(crc); if (crc != item.CRC) return false; } - if (_blakeOffset >= 0) { - Byte digest[BLAKE2S_DIGEST_SIZE]; - Blake2sp_Final(&_blake, digest); - if (cryptoDecoderSpec) - cryptoDecoderSpec->Hmac_Convert_32Bytes(digest); - if (memcmp(digest, &item.Extra[(unsigned)_blakeOffset], BLAKE2S_DIGEST_SIZE) != 0) + UInt32 digest[Z7_BLAKE2S_DIGEST_SIZE / sizeof(UInt32)]; + Blake2sp_Final(BlakeObj(), (Byte *)(void *)digest); + if (cryptoDecoder) + cryptoDecoder->Hmac_Convert_32Bytes((Byte *)(void *)digest); + if (memcmp(digest, item.Extra + (unsigned)_blakeOffset, Z7_BLAKE2S_DIGEST_SIZE) != 0) return false; } - return true; } -class COutStreamWithHash: - public ISequentialOutStream, - public CMyUnknownImp -{ +Z7_CLASS_IMP_NOQIB_1( + COutStreamWithHash + , ISequentialOutStream +) + bool _size_Defined; ISequentialOutStream *_stream; UInt64 _pos; UInt64 _size; - bool _size_Defined; Byte *_destBuf; public: CHash _hash; COutStreamWithHash(): _destBuf(NULL) {} - MY_UNKNOWN_IMP - STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize); void SetStream(ISequentialOutStream *stream) { _stream = stream; } - void Init(const CItem &item, Byte *destBuf) + void Init(const CItem &item, Byte *destBuf, bool needChecksumCheck) { _size_Defined = false; _size = 0; @@ -377,18 +531,21 @@ class COutStreamWithHash: _destBuf = destBuf; } _pos = 0; - _hash.Init(item); + if (needChecksumCheck) + _hash.Init(item); + else + _hash.Init_NoCalc(); } UInt64 GetPos() const { return _pos; } }; -STDMETHODIMP COutStreamWithHash::Write(const void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(COutStreamWithHash::Write(const void *data, UInt32 size, UInt32 *processedSize)) { HRESULT result = S_OK; if (_size_Defined) { - UInt64 rem = _size - _pos; + const UInt64 rem = _size - _pos; if (size > rem) size = (UInt32)rem; } @@ -409,15 +566,14 @@ STDMETHODIMP COutStreamWithHash::Write(const void *data, UInt32 size, UInt32 *pr class CInArchive { - CAlignedBuffer _buf; + CAlignedBuffer _buf; size_t _bufSize; size_t _bufPos; ISequentialInStream *_stream; - NCrypto::NRar5::CDecoder *m_CryptoDecoderSpec; - CMyComPtr m_CryptoDecoder; - + CMyComPtr2 m_CryptoDecoder; + Z7_CLASS_NO_COPY(CInArchive) HRESULT ReadStream_Check(void *data, size_t size); @@ -431,6 +587,10 @@ class CInArchive UInt64 StreamStartPosition; UInt64 Position; + size_t Get_Buf_RemainSize() const { return _bufSize - _bufPos; } + bool Is_Buf_Finished() const { return _bufPos == _bufSize; } + const Byte *Get_Buf_Data() const { return _buf + _bufPos; } + void Move_BufPos(size_t num) { _bufPos += num; } bool ReadVar(UInt64 &val); struct CHeader @@ -441,6 +601,8 @@ class CInArchive UInt64 DataSize; }; + CInArchive() {} + HRESULT ReadBlockHeader(CHeader &h); bool ReadFileHeader(const CHeader &header, CItem &item); void AddToSeekValue(UInt64 addValue) @@ -453,25 +615,26 @@ class CInArchive }; -static HRESULT MySetPassword(ICryptoGetTextPassword *getTextPassword, NCrypto::NRar5::CDecoder *cryptoDecoderSpec) +static HRESULT MySetPassword(ICryptoGetTextPassword *getTextPassword, NCrypto::NRar5::CDecoder *cryptoDecoder) { - CMyComBSTR password; - RINOK(getTextPassword->CryptoGetTextPassword(&password)); - AString utf8; + CMyComBSTR_Wipe password; + RINOK(getTextPassword->CryptoGetTextPassword(&password)) + AString_Wipe utf8; const unsigned kPasswordLen_MAX = 127; - UString unicode = (LPCOLESTR)password; + UString_Wipe unicode; + unicode.SetFromBstr(password); if (unicode.Len() > kPasswordLen_MAX) unicode.DeleteFrom(kPasswordLen_MAX); ConvertUnicodeToUTF8(unicode, utf8); - cryptoDecoderSpec->SetPassword((const Byte *)(const char *)utf8, utf8.Len()); + cryptoDecoder->SetPassword((const Byte *)(const char *)utf8, utf8.Len()); return S_OK; } bool CInArchive::ReadVar(UInt64 &val) { - unsigned offset = ReadVarInt(_buf + _bufPos, _bufSize - _bufPos, &val); - _bufPos += offset; + const unsigned offset = ReadVarInt(Get_Buf_Data(), Get_Buf_RemainSize(), &val); + Move_BufPos(offset); return (offset != 0); } @@ -479,7 +642,7 @@ bool CInArchive::ReadVar(UInt64 &val) HRESULT CInArchive::ReadStream_Check(void *data, size_t size) { size_t size2 = size; - RINOK(ReadStream(_stream, data, &size2)); + RINOK(ReadStream(_stream, data, &size2)) if (size2 == size) return S_OK; UnexpectedEnd = true; @@ -494,61 +657,70 @@ HRESULT CInArchive::ReadBlockHeader(CHeader &h) h.ExtraSize = 0; h.DataSize = 0; - const unsigned kStartSize = 4 + 3; - const unsigned kBufSize = AES_BLOCK_SIZE + AES_BLOCK_SIZE; // must be >= kStartSize; - Byte buf[kBufSize]; + Byte buf[AES_BLOCK_SIZE]; unsigned filled; if (m_CryptoMode) { - RINOK(ReadStream_Check(buf, kBufSize)); - memcpy(m_CryptoDecoderSpec->_iv, buf, AES_BLOCK_SIZE); - RINOK(m_CryptoDecoderSpec->Init()); - - _buf.AllocAtLeast(1 << 12); + _buf.AllocAtLeast(1 << 12); // at least (AES_BLOCK_SIZE * 2) if (!(Byte *)_buf) return E_OUTOFMEMORY; - - memcpy(_buf, buf + AES_BLOCK_SIZE, AES_BLOCK_SIZE); - if (m_CryptoDecoderSpec->Filter(_buf, AES_BLOCK_SIZE) != AES_BLOCK_SIZE) + RINOK(ReadStream_Check(_buf, AES_BLOCK_SIZE * 2)) + memcpy(m_CryptoDecoder->_iv, _buf, AES_BLOCK_SIZE); + RINOK(m_CryptoDecoder->Init()) + // we call RAR5_AES_Filter with: + // data_ptr == aligned_ptr + 16 + // data_size == 16 + if (m_CryptoDecoder->Filter(_buf + AES_BLOCK_SIZE, AES_BLOCK_SIZE) != AES_BLOCK_SIZE) return E_FAIL; - memcpy(buf, _buf, AES_BLOCK_SIZE); + memcpy(buf, _buf + AES_BLOCK_SIZE, AES_BLOCK_SIZE); filled = AES_BLOCK_SIZE; } else { - RINOK(ReadStream_Check(buf, kStartSize)); + const unsigned kStartSize = 4 + 3; + RINOK(ReadStream_Check(buf, kStartSize)) filled = kStartSize; } - UInt64 val; - unsigned offset = ReadVarInt(buf + 4, 3, &val); - if (offset == 0) - return S_FALSE; { + UInt64 val; + unsigned offset = ReadVarInt(buf + 4, 3, &val); + if (offset == 0) + return S_FALSE; size_t size = (size_t)val; - _bufPos = (4 + offset); - _bufSize = _bufPos + size; if (size < 2) return S_FALSE; - } - - size_t allocSize = _bufSize; - if (m_CryptoMode) - allocSize = (allocSize + AES_BLOCK_SIZE - 1) & ~(size_t)(AES_BLOCK_SIZE - 1); - _buf.AllocAtLeast(allocSize); - if (!(Byte *)_buf) - return E_OUTOFMEMORY; - - memcpy(_buf, buf, filled); - - size_t rem = allocSize - filled; - AddToSeekValue(allocSize + (m_CryptoMode ? AES_BLOCK_SIZE : 0)); - RINOK(ReadStream_Check(_buf + filled, rem)); - if (m_CryptoMode) - { - if (m_CryptoDecoderSpec->Filter(_buf + filled, (UInt32)rem) != rem) - return E_FAIL; + offset += 4; + _bufPos = offset; + size += offset; + _bufSize = size; + if (m_CryptoMode) + size = (size + AES_BLOCK_SIZE - 1) & ~(size_t)(AES_BLOCK_SIZE - 1); + _buf.AllocAtLeast(size); + if (!(Byte *)_buf) + return E_OUTOFMEMORY; + memcpy(_buf, buf, filled); + const size_t rem = size - filled; + // if (m_CryptoMode), we add AES_BLOCK_SIZE here, because _iv is not included to size. + AddToSeekValue(size + (m_CryptoMode ? AES_BLOCK_SIZE : 0)); + RINOK(ReadStream_Check(_buf + filled, rem)) + if (m_CryptoMode) + { + // we call RAR5_AES_Filter with: + // data_ptr == aligned_ptr + 16 + // (rem) can be big + if (m_CryptoDecoder->Filter(_buf + filled, (UInt32)rem) != rem) + return E_FAIL; +#if 1 + // optional 7zip-unrar check : remainder must contain zeros. + const size_t pad = size - _bufSize; + const Byte *p = _buf + _bufSize; + for (size_t i = 0; i < pad; i++) + if (p[i]) + return S_FALSE; +#endif + } } if (CrcCalc(_buf + 4, _bufSize - 4) != Get32(buf)) @@ -562,7 +734,7 @@ HRESULT CInArchive::ReadBlockHeader(CHeader &h) UInt64 extraSize; if (!ReadVar(extraSize)) return S_FALSE; - if (extraSize > _bufSize) + if (extraSize >= (1u << 21)) return S_FALSE; h.ExtraSize = (size_t)extraSize; } @@ -573,85 +745,117 @@ HRESULT CInArchive::ReadBlockHeader(CHeader &h) return S_FALSE; } + if (h.ExtraSize > Get_Buf_RemainSize()) + return S_FALSE; return S_OK; } -/* -int CInArcInfo::FindExtra(unsigned type, unsigned &recordDataSize) const +bool CInArcInfo::CLocator::Parse(const Byte *p, size_t size) { - recordDataSize = 0; - size_t offset = 0; + Flags = 0; + QuickOpen = 0; + Recovery = 0; - for (;;) + PARSE_VAR_INT(p, size, Flags) + + if (Is_QuickOpen()) { - size_t rem = Extra.Size() - offset; - if (rem == 0) - return -1; - + PARSE_VAR_INT(p, size, QuickOpen) + } + if (Is_Recovery()) + { + PARSE_VAR_INT(p, size, Recovery) + } +#if 0 + // another records are possible in future rar formats. + if (size != 0) + return false; +#endif + return true; +} + + +bool CInArcInfo::CMetadata::Parse(const Byte *p, size_t size) +{ + PARSE_VAR_INT(p, size, Flags) + if (Flags & NMetadataFlags::kArcName) + { + UInt64 nameLen; + PARSE_VAR_INT(p, size, nameLen) + if (nameLen > size) + return false; + ArcName.SetFrom_CalcLen((const char *)(const void *)p, (unsigned)nameLen); + p += (size_t)nameLen; + size -= (size_t)nameLen; + } + if (Flags & NMetadataFlags::kCTime) + { + if ((Flags & NMetadataFlags::kUnixTime) && + (Flags & NMetadataFlags::kNanoSec) == 0) { - UInt64 size; - unsigned num = ReadVarInt(Extra + offset, rem, &size); - if (num == 0) - return -1; - offset += num; - rem -= num; - if (size > rem) - return -1; - rem = (size_t)size; + if (size < 4) + return false; + CTime = GetUi32(p); + p += 4; + size -= 4; } + else { - UInt64 type2; - unsigned num = ReadVarInt(Extra + offset, rem, &type2); - if (num == 0) - return -1; - offset += num; - rem -= num; - - if (type2 == type) - { - recordDataSize = (unsigned)rem; - return (int)offset; - } - - offset += rem; + if (size < 8) + return false; + CTime = GetUi64(p); + p += 8; + size -= 8; } } +#if 0 + // another records are possible in future rar formats. + if (size != 0) + return false; +#endif + return true; } -bool CInArcInfo::FindExtra_Locator(CLocator &locator) const +bool CInArcInfo::ParseExtra(const Byte *p, size_t size) { - locator.Flags = 0; - locator.QuickOpen = 0; - locator.Recovery = 0; - - unsigned size; - int offset = FindExtra(kArcExtraRecordType_Locator, size); - if (offset < 0) - return false; - const Byte *p = Extra + (unsigned)offset; - - unsigned num; - - num = ReadVarInt(p, size, &locator.Flags); - if (num == 0) return false; p += num; size -= num; - - if (locator.Is_QuickOpen()) - { - num = ReadVarInt(p, size, &locator.QuickOpen); - if (num == 0) return false; p += num; size -= num; - } - - if (locator.Is_Recovery()) + for (;;) { - num = ReadVarInt(p, size, &locator.Recovery); - if (num == 0) return false; p += num; size -= num; + if (size == 0) + return true; + UInt64 recSize64, id; + PARSE_VAR_INT(p, size, recSize64) + if (recSize64 > size) + return false; + size_t recSize = (size_t)recSize64; + size -= recSize; + // READ_VAR_INT(p, recSize, recSize) + { + const unsigned num = ReadVarInt(p, recSize, &id); + if (num == 0) + return false; + p += num; + recSize -= num; + } + if (id == kArcExtraRecordType_Metadata) + { + Metadata_Defined = true; + if (!Metadata.Parse(p, recSize)) + Metadata_Error = true; + } + else if (id == kArcExtraRecordType_Locator) + { + Locator_Defined = true; + if (!Locator.Parse(p, recSize)) + Locator_Error = true; + } + else + UnknownExtraRecord = true; + p += recSize; } - - return true; } -*/ + HRESULT CInArchive::Open(IInStream *stream, const UInt64 *searchHeaderSizeLimit, ICryptoGetTextPassword *getTextPassword, @@ -668,19 +872,19 @@ HRESULT CInArchive::Open(IInStream *stream, const UInt64 *searchHeaderSizeLimit, UInt64 arcStartPos = StreamStartPosition; { Byte marker[kMarkerSize]; - RINOK(ReadStream_FALSE(stream, marker, kMarkerSize)); + RINOK(ReadStream_FALSE(stream, marker, kMarkerSize)) if (memcmp(marker, kMarker, kMarkerSize) == 0) Position += kMarkerSize; else { if (searchHeaderSizeLimit && *searchHeaderSizeLimit == 0) return S_FALSE; - RINOK(stream->Seek(StreamStartPosition, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekSet(stream, StreamStartPosition)) RINOK(FindSignatureInStream(stream, kMarker, kMarkerSize, - searchHeaderSizeLimit, arcStartPos)); + searchHeaderSizeLimit, arcStartPos)) arcStartPos += StreamStartPosition; Position = arcStartPos + kMarkerSize; - RINOK(stream->Seek(Position, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekSet(stream, Position)) } } @@ -688,7 +892,7 @@ HRESULT CInArchive::Open(IInStream *stream, const UInt64 *searchHeaderSizeLimit, _stream = stream; CHeader h; - RINOK(ReadBlockHeader(h)); + RINOK(ReadBlockHeader(h)) info.IsEncrypted = false; if (h.Type == NHeaderType::kArcEncrypt) @@ -697,27 +901,17 @@ HRESULT CInArchive::Open(IInStream *stream, const UInt64 *searchHeaderSizeLimit, IsArc = true; if (!getTextPassword) return E_NOTIMPL; - m_CryptoMode = true; - - if (!m_CryptoDecoder) - { - m_CryptoDecoderSpec = new NCrypto::NRar5::CDecoder; - m_CryptoDecoder = m_CryptoDecoderSpec; - } - - RINOK(m_CryptoDecoderSpec->SetDecoderProps( - _buf + _bufPos, (unsigned)(_bufSize - _bufPos), false, false)); - - RINOK(MySetPassword(getTextPassword, m_CryptoDecoderSpec)); - - if (!m_CryptoDecoderSpec->CalcKey_and_CheckPassword()) + m_CryptoDecoder.Create_if_Empty(); + RINOK(m_CryptoDecoder->SetDecoderProps( + Get_Buf_Data(), (unsigned)Get_Buf_RemainSize(), false, false)) + RINOK(MySetPassword(getTextPassword, m_CryptoDecoder.ClsPtr())) + if (!m_CryptoDecoder->CalcKey_and_CheckPassword()) { WrongPassword = True; return S_FALSE; } - - RINOK(ReadBlockHeader(h)); + RINOK(ReadBlockHeader(h)) } if (h.Type != NHeaderType::kArc) @@ -733,42 +927,29 @@ HRESULT CInArchive::Open(IInStream *stream, const UInt64 *searchHeaderSizeLimit, if (!ReadVar(info.VolNumber)) return S_FALSE; - if (h.ExtraSize != 0) + if (h.ExtraSize != Get_Buf_RemainSize()) + return S_FALSE; + if (h.ExtraSize) { - if (_bufSize - _bufPos < h.ExtraSize) - return S_FALSE; - /* - info.Extra.Alloc(h.ExtraSize); - memcpy(info.Extra, _buf + _bufPos, h.ExtraSize); - */ - _bufPos += h.ExtraSize; - - /* - CInArcInfo::CLocator locator; - if (info.FindExtra_Locator(locator)) - locator.Flags = locator.Flags; - */ + if (!info.ParseExtra(Get_Buf_Data(), h.ExtraSize)) + info.Extra_Error = true; } - - if (_bufPos != _bufSize) - return S_FALSE; - return S_OK; } bool CInArchive::ReadFileHeader(const CHeader &header, CItem &item) { - item.UnixMTime = 0; - item.CRC = 0; - item.Flags = 0; - item.CommonFlags = (UInt32)header.Flags; item.PackSize = header.DataSize; + item.UnixMTime = 0; + item.CRC = 0; - UInt64 flags64; - if (!ReadVar(flags64)) return false; - item.Flags = (UInt32)flags64; + { + UInt64 flags64; + if (!ReadVar(flags64)) return false; + item.Flags = (UInt32)flags64; + } if (!ReadVar(item.Size)) return false; @@ -777,23 +958,20 @@ bool CInArchive::ReadFileHeader(const CHeader &header, CItem &item) if (!ReadVar(attrib)) return false; item.Attrib = (UInt32)attrib; } - if (item.Has_UnixMTime()) { - if (_bufSize - _bufPos < 4) + if (Get_Buf_RemainSize() < 4) return false; - item.UnixMTime = Get32(_buf + _bufPos); - _bufPos += 4; + item.UnixMTime = Get32(Get_Buf_Data()); + Move_BufPos(4); } - if (item.Has_CRC()) { - if (_bufSize - _bufPos < 4) + if (Get_Buf_RemainSize() < 4) return false; - item.CRC = Get32(_buf + _bufPos); - _bufPos += 4; + item.CRC = Get32(Get_Buf_Data()); + Move_BufPos(4); } - { UInt64 method; if (!ReadVar(method)) return false; @@ -805,25 +983,24 @@ bool CInArchive::ReadFileHeader(const CHeader &header, CItem &item) { UInt64 len; if (!ReadVar(len)) return false; - if (len > _bufSize - _bufPos) + if (len > Get_Buf_RemainSize()) return false; - item.Name.SetFrom_CalcLen((const char *)(_buf + _bufPos), (unsigned)len); - _bufPos += (unsigned)len; + item.Name.SetFrom_CalcLen((const char *)Get_Buf_Data(), (unsigned)len); + Move_BufPos((size_t)len); } item.Extra.Free(); - size_t extraSize = header.ExtraSize; + const size_t extraSize = header.ExtraSize; if (extraSize != 0) { - if (_bufSize - _bufPos < extraSize) + if (Get_Buf_RemainSize() < extraSize) return false; item.Extra.Alloc(extraSize); - memcpy(item.Extra, _buf + _bufPos, extraSize); - _bufPos += extraSize; + memcpy(item.Extra, Get_Buf_Data(), extraSize); + Move_BufPos(extraSize); } - - return (_bufPos == _bufSize); + return Is_Buf_Finished(); } @@ -831,7 +1008,7 @@ bool CInArchive::ReadFileHeader(const CHeader &header, CItem &item) struct CLinkFile { unsigned Index; - unsigned NumLinks; + unsigned NumLinks; // the number of links to Data CByteBuffer Data; HRESULT Res; bool crcOK; @@ -842,94 +1019,84 @@ struct CLinkFile struct CUnpacker { - NCompress::CCopyCoder *copyCoderSpec; - CMyComPtr copyCoder; - + CMyComPtr2 copyCoder; CMyComPtr LzCoders[2]; - bool NeedClearSolid[2]; - + bool SolidAllowed; + bool NeedCrc; CFilterCoder *filterStreamSpec; CMyComPtr filterStream; - - NCrypto::NRar5::CDecoder *cryptoDecoderSpec; - CMyComPtr cryptoDecoder; - + CMyComPtr2 cryptoDecoder; CMyComPtr getTextPassword; - - COutStreamWithHash *outStreamSpec; - CMyComPtr outStream; + CMyComPtr2 outStream; CByteBuffer _tempBuf; - CLinkFile *linkFile; - CUnpacker(): linkFile(NULL) { NeedClearSolid[0] = NeedClearSolid[1] = true; } + CUnpacker(): linkFile(NULL) { SolidAllowed = false; NeedCrc = true; } - HRESULT Create(DECL_EXTERNAL_CODECS_LOC_VARS const CItem &item, bool isSolid, bool &wrongPassword); + HRESULT Create(DECL_EXTERNAL_CODECS_LOC_VARS + const CItem &item, bool isSolid, bool &wrongPassword); HRESULT Code(const CItem &item, const CItem &lastItem, UInt64 packSize, ISequentialInStream *inStream, ISequentialOutStream *outStream, ICompressProgressInfo *progress, bool &isCrcOK); - HRESULT DecodeToBuf(DECL_EXTERNAL_CODECS_LOC_VARS const CItem &item, UInt64 packSize, ISequentialInStream *inStream, CByteBuffer &buffer); + HRESULT DecodeToBuf(DECL_EXTERNAL_CODECS_LOC_VARS + const CItem &item, UInt64 packSize, ISequentialInStream *inStream, CByteBuffer &buffer); }; static const unsigned kLzMethodMax = 5; -HRESULT CUnpacker::Create(DECL_EXTERNAL_CODECS_LOC_VARS const CItem &item, bool isSolid, bool &wrongPassword) +HRESULT CUnpacker::Create(DECL_EXTERNAL_CODECS_LOC_VARS + const CItem &item, bool isSolid, bool &wrongPassword) { wrongPassword = false; - if (item.GetAlgoVersion() != 0) + if (item.Get_AlgoVersion_RawBits() > 1) return E_NOTIMPL; - if (!outStream) - { - outStreamSpec = new COutStreamWithHash; - outStream = outStreamSpec; - } + outStream.Create_if_Empty(); - unsigned method = item.GetMethod(); + const unsigned method = item.Get_Method(); if (method == 0) - { - if (!copyCoder) - { - copyCoderSpec = new NCompress::CCopyCoder; - copyCoder = copyCoderSpec; - } - } + copyCoder.Create_if_Empty(); else { if (method > kLzMethodMax) return E_NOTIMPL; - /* if (item.IsSplitBefore()) return S_FALSE; */ - - int lzIndex = item.IsService() ? 1 : 0; + const unsigned lzIndex = item.IsService() ? 1 : 0; CMyComPtr &lzCoder = LzCoders[lzIndex]; - if (!lzCoder) { const UInt32 methodID = 0x40305; - RINOK(CreateCoder(EXTERNAL_CODECS_LOC_VARS methodID, false, lzCoder)); + RINOK(CreateCoder_Id(EXTERNAL_CODECS_LOC_VARS methodID, false, lzCoder)) if (!lzCoder) return E_NOTIMPL; } CMyComPtr csdp; - RINOK(lzCoder.QueryInterface(IID_ICompressSetDecoderProperties2, &csdp)); - - Byte props[2] = { (Byte)(item.GetDictSize()), (Byte)(isSolid ? 1 : 0) }; - RINOK(csdp->SetDecoderProperties2(props, 2)); + RINOK(lzCoder.QueryInterface(IID_ICompressSetDecoderProperties2, &csdp)) + if (!csdp) + return E_NOTIMPL; + const unsigned ver = item.Get_AlgoVersion_HuffRev(); + if (ver > 1) + return E_NOTIMPL; + const Byte props[2] = + { + (Byte)item.Get_DictSize_Main(), + (Byte)((item.Get_DictSize_Frac() << 3) + (ver << 1) + (isSolid ? 1 : 0)) + }; + RINOK(csdp->SetDecoderProperties2(props, 2)) } unsigned cryptoSize = 0; - int cryptoOffset = item.FindExtra(NExtraRecordType::kCrypto, cryptoSize); + const int cryptoOffset = item.FindExtra(NExtraID::kCrypto, cryptoSize); if (cryptoOffset >= 0) { @@ -939,13 +1106,9 @@ HRESULT CUnpacker::Create(DECL_EXTERNAL_CODECS_LOC_VARS const CItem &item, bool filterStream = filterStreamSpec; } - if (!cryptoDecoder) - { - cryptoDecoderSpec = new NCrypto::NRar5::CDecoder; - cryptoDecoder = cryptoDecoderSpec; - } + cryptoDecoder.Create_if_Empty(); - RINOK(cryptoDecoderSpec->SetDecoderProps(item.Extra + (unsigned)cryptoOffset, cryptoSize, true, item.IsService())); + RINOK(cryptoDecoder->SetDecoderProps(item.Extra + (unsigned)cryptoOffset, cryptoSize, true, item.IsService())) if (!getTextPassword) { @@ -953,9 +1116,9 @@ HRESULT CUnpacker::Create(DECL_EXTERNAL_CODECS_LOC_VARS const CItem &item, bool return E_NOTIMPL; } - RINOK(MySetPassword(getTextPassword, cryptoDecoderSpec)); + RINOK(MySetPassword(getTextPassword, cryptoDecoder.ClsPtr())) - if (!cryptoDecoderSpec->CalcKey_and_CheckPassword()) + if (!cryptoDecoder->CalcKey_and_CheckPassword()) wrongPassword = True; } @@ -969,13 +1132,15 @@ HRESULT CUnpacker::Code(const CItem &item, const CItem &lastItem, UInt64 packSiz { isCrcOK = true; - unsigned method = item.GetMethod(); + const unsigned method = item.Get_Method(); if (method > kLzMethodMax) return E_NOTIMPL; - if (linkFile && !lastItem.Is_UnknownSize()) + const bool needBuf = (linkFile && linkFile->NumLinks != 0); + + if (needBuf && !lastItem.Is_UnknownSize()) { - size_t dataSize = (size_t)lastItem.Size; + const size_t dataSize = (size_t)lastItem.Size; if (dataSize != lastItem.Size) return E_NOTIMPL; linkFile->Data.Alloc(dataSize); @@ -995,53 +1160,64 @@ HRESULT CUnpacker::Code(const CItem &item, const CItem &lastItem, UInt64 packSiz else inStream = volsInStream; - ICompressCoder *commonCoder = (method == 0) ? copyCoder : LzCoders[item.IsService() ? 1 : 0]; - - outStreamSpec->SetStream(realOutStream); - outStreamSpec->Init(lastItem, (linkFile ? (Byte *)linkFile->Data : NULL)); + ICompressCoder *commonCoder = (method == 0) ? + copyCoder.Interface() : + LzCoders[item.IsService() ? 1 : 0].Interface(); - NeedClearSolid[item.IsService() ? 1 : 0] = false; + outStream->SetStream(realOutStream); + outStream->Init(lastItem, (needBuf ? (Byte *)linkFile->Data : NULL), NeedCrc); HRESULT res = S_OK; if (packSize != 0 || lastItem.Is_UnknownSize() || lastItem.Size != 0) { res = commonCoder->Code(inStream, outStream, &packSize, lastItem.Is_UnknownSize() ? NULL : &lastItem.Size, progress); + if (!item.IsService()) + SolidAllowed = true; } else { - res = res; + // res = res; } if (isCryptoMode) filterStreamSpec->ReleaseInStream(); - UInt64 processedSize = outStreamSpec->GetPos(); + const UInt64 processedSize = outStream->GetPos(); if (res == S_OK && !lastItem.Is_UnknownSize() && processedSize != lastItem.Size) - res = S_FALSE; + { + // rar_v7.13-: linux archive contains symLink with (packSize == 0 && lastItem.Size != 0) + // v25.02: we ignore such record in rar headers: + if (packSize != 0 + || method != 0 + || lastItem.HostOS != kHost_Unix + || !MY_LIN_S_ISLNK(lastItem.Attrib)) + res = S_FALSE; + } // if (res == S_OK) { unsigned cryptoSize = 0; - int cryptoOffset = lastItem.FindExtra(NExtraRecordType::kCrypto, cryptoSize); + const int cryptoOffset = lastItem.FindExtra(NExtraID::kCrypto, cryptoSize); NCrypto::NRar5::CDecoder *crypto = NULL; - if (cryptoOffset >= 0) { CCryptoInfo cryptoInfo; if (cryptoInfo.Parse(lastItem.Extra + (unsigned)cryptoOffset, cryptoSize)) if (cryptoInfo.UseMAC()) - crypto = cryptoDecoderSpec; + crypto = cryptoDecoder.ClsPtr(); } - - isCrcOK = outStreamSpec->_hash.Check(lastItem, crypto); + if (NeedCrc) + isCrcOK = outStream->_hash.Check(lastItem, crypto); } if (linkFile) { linkFile->Res = res; linkFile->crcOK = isCrcOK; - if (!lastItem.Is_UnknownSize() && processedSize != lastItem.Size) + if (needBuf + && !lastItem.Is_UnknownSize() + && processedSize != lastItem.Size) linkFile->Data.ChangeSize_KeepData((size_t)processedSize, (size_t)processedSize); } @@ -1049,12 +1225,14 @@ HRESULT CUnpacker::Code(const CItem &item, const CItem &lastItem, UInt64 packSiz } -HRESULT CUnpacker::DecodeToBuf(DECL_EXTERNAL_CODECS_LOC_VARS const CItem &item, UInt64 packSize, ISequentialInStream *inStream, CByteBuffer &buffer) +HRESULT CUnpacker::DecodeToBuf(DECL_EXTERNAL_CODECS_LOC_VARS + const CItem &item, UInt64 packSize, + ISequentialInStream *inStream, + CByteBuffer &buffer) { - CBufPtrSeqOutStream *outSpec = new CBufPtrSeqOutStream; - CMyComPtr out = outSpec; + CMyComPtr2_Create out; _tempBuf.AllocAtLeast((size_t)item.Size); - outSpec->Init(_tempBuf, (size_t)item.Size); + out->Init(_tempBuf, (size_t)item.Size); bool wrongPassword; @@ -1068,16 +1246,15 @@ HRESULT CUnpacker::DecodeToBuf(DECL_EXTERNAL_CODECS_LOC_VARS const CItem &item, if (wrongPassword) return S_FALSE; - CLimitedSequentialInStream *limitedStreamSpec = new CLimitedSequentialInStream; - CMyComPtr limitedStream(limitedStreamSpec); - limitedStreamSpec->SetStream(inStream); - limitedStreamSpec->Init(packSize); + CMyComPtr2_Create limitedStream; + limitedStream->SetStream(inStream); + limitedStream->Init(packSize); bool crcOK = true; res = Code(item, item, packSize, limitedStream, out, NULL, crcOK); if (res == S_OK) { - if (!crcOK || outSpec->GetPos() != item.Size) + if (!crcOK || out->GetPos() != item.Size) res = S_FALSE; else buffer.CopyFrom(_tempBuf, (size_t)item.Size); @@ -1104,7 +1281,9 @@ struct CTempBuf HRESULT Decode(DECL_EXTERNAL_CODECS_LOC_VARS const CItem &item, - ISequentialInStream *inStream, CUnpacker &unpacker, CByteBuffer &destBuf); + ISequentialInStream *inStream, + CUnpacker &unpacker, + CByteBuffer &destBuf); }; @@ -1133,7 +1312,7 @@ HRESULT CTempBuf::Decode(DECL_EXTERNAL_CODECS_LOC_VARS _buf.ChangeSize_KeepData(newSize, _offset); Byte *data = (Byte *)_buf + _offset; - RINOK(ReadStream_FALSE(inStream, data, packSize)); + RINOK(ReadStream_FALSE(inStream, data, packSize)) _offset += packSize; @@ -1153,15 +1332,14 @@ HRESULT CTempBuf::Decode(DECL_EXTERNAL_CODECS_LOC_VARS if (_offset == 0) { RINOK(unpacker.DecodeToBuf(EXTERNAL_CODECS_LOC_VARS - item, item.PackSize, inStream, destBuf)); + item, item.PackSize, inStream, destBuf)) } else { - CBufInStream *bufInStreamSpec = new CBufInStream; - CMyComPtr bufInStream = bufInStreamSpec; - bufInStreamSpec->Init(_buf, _offset); + CMyComPtr2_Create bufInStream; + bufInStream->Init(_buf, _offset); RINOK(unpacker.DecodeToBuf(EXTERNAL_CODECS_LOC_VARS - item, _offset, bufInStream, destBuf)); + item, _offset, bufInStream, destBuf)) } } } @@ -1181,6 +1359,7 @@ static const Byte kProps[] = kpidCTime, kpidATime, kpidAttrib, + // kpidPosixAttrib, // for debug kpidIsAltStream, kpidEncrypted, @@ -1190,10 +1369,12 @@ static const Byte kProps[] = kpidCRC, kpidHostOS, kpidMethod, - + kpidCharacts, kpidSymLink, kpidHardLink, kpidCopyLink, + + kpidVolumeIndex }; @@ -1201,12 +1382,15 @@ static const Byte kArcProps[] = { kpidTotalPhySize, kpidCharacts, + kpidEncrypted, kpidSolid, kpidNumBlocks, - kpidEncrypted, + kpidMethod, kpidIsVolume, kpidVolumeIndex, kpidNumVolumes, + kpidName, + kpidCTime, kpidComment }; @@ -1225,12 +1409,23 @@ UInt64 CHandler::GetPackSize(unsigned refIndex) const size += item.PackSize; if (item.NextItem < 0) return size; - index = item.NextItem; + index = (unsigned)item.NextItem; } } +static char *PrintDictSize(char *s, UInt64 w) +{ + char c = 'K'; w >>= 10; + if ((w & ((1 << 10) - 1)) == 0) { c = 'M'; w >>= 10; + if ((w & ((1 << 10) - 1)) == 0) { c = 'G'; w >>= 10; }} + s = ConvertUInt64ToString(w, s); + *s++ = c; + *s = 0; + return s; +} -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) + +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN @@ -1246,10 +1441,69 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) case kpidSolid: if (arcInfo) prop = arcInfo->IsSolid(); break; case kpidCharacts: { - if (!_arcs.IsEmpty()) + AString s; + if (arcInfo) + { + s = FlagsToString(k_ArcFlags, Z7_ARRAY_SIZE(k_ArcFlags), (UInt32)arcInfo->Flags); + if (arcInfo->Extra_Error) + s.Add_OptSpaced("Extra-ERROR"); + if (arcInfo->UnsupportedFeature) + s.Add_OptSpaced("unsupported-feature"); + if (arcInfo->Metadata_Defined) + { + s.Add_OptSpaced("Metadata"); + if (arcInfo->Metadata_Error) + s += "-ERROR"; + else + { + if (arcInfo->Metadata.Flags & NMetadataFlags::kArcName) + s.Add_OptSpaced("arc-name"); + if (arcInfo->Metadata.Flags & NMetadataFlags::kCTime) + { + s.Add_OptSpaced("ctime-"); + s += + (arcInfo->Metadata.Flags & NMetadataFlags::kUnixTime) ? + (arcInfo->Metadata.Flags & NMetadataFlags::kNanoSec) ? + "1ns" : "1s" : "win"; + } + } + } + if (arcInfo->Locator_Defined) + { + s.Add_OptSpaced("Locator"); + if (arcInfo->Locator_Error) + s += "-ERROR"; + else + { + if (arcInfo->Locator.Is_QuickOpen()) + { + s.Add_OptSpaced("QuickOpen:"); + s.Add_UInt64(arcInfo->Locator.QuickOpen); + } + if (arcInfo->Locator.Is_Recovery()) + { + s.Add_OptSpaced("Recovery:"); + s.Add_UInt64(arcInfo->Locator.Recovery); + } + } + } + if (arcInfo->UnknownExtraRecord) + s.Add_OptSpaced("Unknown-Extra-Record"); + + } + if (_comment_WasUsedInArc) { - FLAGS_TO_PROP(k_ArcFlags, (UInt32)arcInfo->Flags, prop); + s.Add_OptSpaced("Comment"); + // s.Add_UInt32((UInt32)_comment.Size()); } + // + if (_acls.Size() != 0) + { + s.Add_OptSpaced("ACL"); + // s.Add_UInt32(_acls.Size()); + } + if (!s.IsEmpty()) + prop = s; break; } case kpidEncrypted: if (arcInfo) prop = arcInfo->IsEncrypted; break; // it's for encrypted names. @@ -1276,19 +1530,61 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) break; } + case kpidName: + if (arcInfo) + if (!arcInfo->Metadata_Error + && !arcInfo->Metadata.ArcName.IsEmpty()) + { + UString s; + if (ConvertUTF8ToUnicode(arcInfo->Metadata.ArcName, s)) + prop = s; + } + break; + + case kpidCTime: + if (arcInfo) + if (!arcInfo->Metadata_Error + && (arcInfo->Metadata.Flags & NMetadataFlags::kCTime)) + { + const UInt64 ct = arcInfo->Metadata.CTime; + if (arcInfo->Metadata.Flags & NMetadataFlags::kUnixTime) + { + if (arcInfo->Metadata.Flags & NMetadataFlags::kNanoSec) + { + const UInt64 sec = ct / 1000000000; + const UInt64 ns = ct % 1000000000; + UInt64 wt = NTime::UnixTime64_To_FileTime64((Int64)sec); + wt += ns / 100; + const unsigned ns100 = (unsigned)(ns % 100); + FILETIME ft; + ft.dwLowDateTime = (DWORD)(UInt32)wt; + ft.dwHighDateTime = (DWORD)(UInt32)(wt >> 32); + prop.SetAsTimeFrom_FT_Prec_Ns100(ft, k_PropVar_TimePrec_1ns, ns100); + } + else + { + const UInt64 wt = NTime::UnixTime64_To_FileTime64((Int64)ct); + prop.SetAsTimeFrom_Ft64_Prec(wt, k_PropVar_TimePrec_Unix); + } + } + else + prop.SetAsTimeFrom_Ft64_Prec(ct, k_PropVar_TimePrec_100ns); + } + break; + case kpidComment: { // if (!_arcs.IsEmpty()) { // const CArc &arc = _arcs[0]; const CByteBuffer &cmt = _comment; - if (cmt.Size() != 0 && cmt.Size() < (1 << 16)) + if (cmt.Size() != 0 /* && cmt.Size() < (1 << 16) */) { AString s; s.SetFrom_CalcLen((const char *)(const Byte *)cmt, (unsigned)cmt.Size()); UString unicode; - if (ConvertUTF8ToUnicode(s, unicode)) - prop = unicode; + ConvertUTF8ToUnicode(s, unicode); + prop = unicode; } } break; @@ -1296,11 +1592,48 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) case kpidNumBlocks: { - UInt32 numBlocks = 0; - FOR_VECTOR (i, _refs) - if (!_items[_refs[i].Item].IsSolid()) - numBlocks++; - prop = (UInt32)numBlocks; + prop = (UInt32)_numBlocks; + break; + } + + case kpidMethod: + { + AString s; + + UInt64 algo = _algo_Mask; + for (unsigned v = 0; algo != 0; v++, algo >>= 1) + { + if ((algo & 1) == 0) + continue; + s.Add_OptSpaced("v"); + s.Add_UInt32(v + 6); + if (v < Z7_ARRAY_SIZE(_methodMasks)) + { + const UInt64 dict = _dictMaxSizes[v]; + if (dict) + { + char temp[24]; + temp[0] = ':'; + PrintDictSize(temp + 1, dict); + s += temp; + } + unsigned method = _methodMasks[v]; + for (unsigned m = 0; method; m++, method >>= 1) + { + if ((method & 1) == 0) + continue; + s += ":m"; + s.Add_UInt32(m); + } + } + } + if (_rar5comapt_mask & 2) + { + s += ":c"; + if (_rar5comapt_mask & 1) + s.Add_Char('n'); + } + prop = s; break; } @@ -1308,8 +1641,7 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) { if (/* &_missingVol || */ !_missingVolName.IsEmpty()) { - UString s; - s.SetFromAscii("Missing volume : "); + UString s ("Missing volume : "); s += _missingVolName; prop = s; } @@ -1321,6 +1653,10 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) UInt32 v = _errorFlags; if (!_isArc) v |= kpv_ErrorFlags_IsNotArc; + if (_error_in_ACL) + v |= kpv_ErrorFlags_HeadersError; + if (_split_Error) + v |= kpv_ErrorFlags_HeadersError; prop = v; break; } @@ -1339,13 +1675,11 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) { if (arcInfo->IsVolume()) { - char sz[32]; - ConvertUInt64ToString(arcInfo->GetVolIndex() + 1, sz); - unsigned len = MyStringLen(sz); - AString s = "part"; - for (; len < 2; len++) - s += '0'; - s += sz; + AString s ("part"); + UInt32 v = (UInt32)arcInfo->GetVolIndex() + 1; + if (v < 10) + s.Add_Char('0'); + s.Add_UInt32(v); s += ".rar"; prop = s; } @@ -1362,9 +1696,9 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) } -STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) { - *numItems = _refs.Size(); + *numItems = (UInt32)_refs.Size(); return S_OK; } @@ -1376,20 +1710,20 @@ static const Byte kRawProps[] = }; -STDMETHODIMP CHandler::GetNumRawProps(UInt32 *numProps) +Z7_COM7F_IMF(CHandler::GetNumRawProps(UInt32 *numProps)) { - *numProps = ARRAY_SIZE(kRawProps); + *numProps = Z7_ARRAY_SIZE(kRawProps); return S_OK; } -STDMETHODIMP CHandler::GetRawPropInfo(UInt32 index, BSTR *name, PROPID *propID) +Z7_COM7F_IMF(CHandler::GetRawPropInfo(UInt32 index, BSTR *name, PROPID *propID)) { *propID = kRawProps[index]; - *name = 0; + *name = NULL; return S_OK; } -STDMETHODIMP CHandler::GetParent(UInt32 index, UInt32 *parent, UInt32 *parentType) +Z7_COM7F_IMF(CHandler::GetParent(UInt32 index, UInt32 *parent, UInt32 *parentType)) { *parentType = NParentType::kDir; *parent = (UInt32)(Int32)-1; @@ -1410,7 +1744,7 @@ STDMETHODIMP CHandler::GetParent(UInt32 index, UInt32 *parent, UInt32 *parentTyp } -STDMETHODIMP CHandler::GetRawProp(UInt32 index, PROPID propID, const void **data, UInt32 *dataSize, UInt32 *propType) +Z7_COM7F_IMF(CHandler::GetRawProp(UInt32 index, PROPID propID, const void **data, UInt32 *dataSize, UInt32 *propType)) { *data = NULL; *dataSize = 0; @@ -1435,13 +1769,21 @@ STDMETHODIMP CHandler::GetRawProp(UInt32 index, PROPID propID, const void **data if (propID == kpidChecksum) { - int hashRecOffset = item.FindExtra_Blake(); + const int hashRecOffset = item.FindExtra_Blake(); if (hashRecOffset >= 0) { - *dataSize = BLAKE2S_DIGEST_SIZE; + *dataSize = Z7_BLAKE2S_DIGEST_SIZE; + *propType = NPropDataType::kRaw; + *data = item.Extra + (unsigned)hashRecOffset; + } + /* + else if (item.Has_CRC() && item.IsEncrypted()) + { + *dataSize = 4; *propType = NPropDataType::kRaw; - *data = &item.Extra[hashRecOffset]; + *data = &item->CRC; // we must show same value for big/little endian here } + */ return S_OK; } @@ -1452,14 +1794,15 @@ STDMETHODIMP CHandler::GetRawProp(UInt32 index, PROPID propID, const void **data static void TimeRecordToProp(const CItem &item, unsigned stampIndex, NCOM::CPropVariant &prop) { unsigned size; - int offset = item.FindExtra(NExtraRecordType::kTime, size); + const int offset = item.FindExtra(NExtraID::kTime, size); if (offset < 0) return; const Byte *p = item.Extra + (unsigned)offset; UInt64 flags; + // PARSE_VAR_INT(p, size, flags) { - unsigned num = ReadVarInt(p, size, &flags); + const unsigned num = ReadVarInt(p, size, &flags); if (num == 0) return; p += num; @@ -1470,36 +1813,58 @@ static void TimeRecordToProp(const CItem &item, unsigned stampIndex, NCOM::CProp return; unsigned numStamps = 0; - unsigned i; - for (i = 0; i < 3; i++) - if ((flags & (NTimeRecord::NFlags::kMTime << i)) != 0) - numStamps++; - unsigned stampSizeLog = ((flags & NTimeRecord::NFlags::kUnixTime) != 0) ? 2 : 3; - - if ((numStamps << stampSizeLog) != size) - return; - - numStamps = 0; - for (i = 0; i < stampIndex; i++) + unsigned curStamp = 0; + + for (unsigned i = 0; i < 3; i++) if ((flags & (NTimeRecord::NFlags::kMTime << i)) != 0) + { + if (i == stampIndex) + curStamp = numStamps; numStamps++; - - p += (numStamps << stampSizeLog); + } FILETIME ft; + + unsigned timePrec = 0; + unsigned ns100 = 0; + if ((flags & NTimeRecord::NFlags::kUnixTime) != 0) - NWindows::NTime::UnixTimeToFileTime(Get32(p), ft); + { + curStamp *= 4; + if (curStamp + 4 > size) + return; + p += curStamp; + UInt64 val = NTime::UnixTime_To_FileTime64(Get32(p)); + numStamps *= 4; + timePrec = k_PropVar_TimePrec_Unix; + if ((flags & NTimeRecord::NFlags::kUnixNs) != 0 && numStamps * 2 <= size) + { + const UInt32 ns = Get32(p + numStamps) & 0x3FFFFFFF; + if (ns < 1000000000) + { + val += ns / 100; + ns100 = (unsigned)(ns % 100); + timePrec = k_PropVar_TimePrec_1ns; + } + } + ft.dwLowDateTime = (DWORD)val; + ft.dwHighDateTime = (DWORD)(val >> 32); + } else { + curStamp *= 8; + if (curStamp + 8 > size) + return; + p += curStamp; ft.dwLowDateTime = Get32(p); ft.dwHighDateTime = Get32(p + 4); } - prop = ft; + prop.SetAsTimeFrom_FT_Prec_Ns100(ft, timePrec, ns100); } -STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN @@ -1519,37 +1884,36 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val AString s; if (ref.Parent >= 0) { - CItem &mainItem = _items[_refs[ref.Parent].Item]; + const CItem &mainItem = _items[_refs[ref.Parent].Item]; s = mainItem.Name; } AString name; item.GetAltStreamName(name); if (name[0] != ':') - s += ':'; + s.Add_Colon(); s += name; - if (!ConvertUTF8ToUnicode(s, unicodeName)) - break; + ConvertUTF8ToUnicode(s, unicodeName); } else { - if (!ConvertUTF8ToUnicode(item.Name, unicodeName)) - break; + ConvertUTF8ToUnicode(item.Name, unicodeName); + if (item.Version_Defined) { - wchar_t temp[32]; + char temp[32]; // temp[0] = ';'; // ConvertUInt64ToString(item.Version, temp + 1); // unicodeName += temp; ConvertUInt64ToString(item.Version, temp); - UString s2 = L"[VER]" WSTRING_PATH_SEPARATOR; + UString s2 ("[VER]" STRING_PATH_SEPARATOR); s2 += temp; s2.Add_PathSepar(); unicodeName.Insert(0, s2); } } - NItemName::ConvertToOSName2(unicodeName); + NItemName::ReplaceToOsSlashes_Remove_TailSlash(unicodeName); prop = unicodeName; break; @@ -1557,27 +1921,19 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val case kpidIsDir: prop = item.IsDir(); break; case kpidSize: if (!lastItem.Is_UnknownSize()) prop = lastItem.Size; break; - case kpidPackSize: prop = GetPackSize(index); break; + case kpidPackSize: prop = GetPackSize((unsigned)index); break; case kpidMTime: { TimeRecordToProp(item, NTimeRecord::k_Index_MTime, prop); if (prop.vt == VT_EMPTY && item.Has_UnixMTime()) - { - FILETIME ft; - NWindows::NTime::UnixTimeToFileTime(item.UnixMTime, ft); - prop = ft; - } + PropVariant_SetFrom_UnixTime(prop, item.UnixMTime); if (prop.vt == VT_EMPTY && ref.Parent >= 0) { const CItem &baseItem = _items[_refs[ref.Parent].Item]; TimeRecordToProp(baseItem, NTimeRecord::k_Index_MTime, prop); if (prop.vt == VT_EMPTY && baseItem.Has_UnixMTime()) - { - FILETIME ft; - NWindows::NTime::UnixTimeToFileTime(baseItem.UnixMTime, ft); - prop = ft; - } + PropVariant_SetFrom_UnixTime(prop, baseItem.UnixMTime); } break; } @@ -1594,8 +1950,8 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val { name.DeleteFrontal(1); UString unicodeName; - if (ConvertUTF8ToUnicode(name, unicodeName)) - prop = unicodeName; + ConvertUTF8ToUnicode(name, unicodeName); + prop = unicodeName; } } break; @@ -1608,66 +1964,125 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val case kpidCopyLink: item.Link_to_Prop(NLinkType::kFileCopy, prop); break; case kpidAttrib: prop = item.GetWinAttrib(); break; + case kpidPosixAttrib: + if (item.HostOS == kHost_Unix) + prop = (UInt32)item.Attrib; + break; + case kpidEncrypted: prop = item.IsEncrypted(); break; case kpidSolid: prop = item.IsSolid(); break; case kpidSplitBefore: prop = item.IsSplitBefore(); break; case kpidSplitAfter: prop = lastItem.IsSplitAfter(); break; + + case kpidVolumeIndex: + { + if (item.VolIndex < _arcs.Size()) + { + const CInArcInfo &arcInfo = _arcs[item.VolIndex].Info; + if (arcInfo.IsVolume()) + prop = (UInt64)arcInfo.GetVolIndex(); + } + break; + } + case kpidCRC: { const CItem *item2 = (lastItem.IsSplitAfter() ? &item : &lastItem); - if (item2->Has_CRC()) + // we don't want to show crc for encrypted file here, + // because crc is also encrrypted. + if (item2->Has_CRC() && !item2->IsEncrypted()) prop = item2->CRC; break; } case kpidMethod: { - char temp[64]; - unsigned algo = item.GetAlgoVersion(); + char temp[128]; + const unsigned algo = item.Get_AlgoVersion_RawBits(); char *s = temp; - if (algo != 0) + // if (algo != 0) { - ConvertUInt32ToString(algo, s); - s += MyStringLen(s); + *s++ = 'v'; + s = ConvertUInt32ToString((UInt32)algo + 6, s); + if (item.Is_Rar5_Compat()) + *s++ = 'c'; *s++ = ':'; } - unsigned m = item.GetMethod(); { - s[0] = 'm'; - s[1] = (char)(m + '0'); - s[2] = 0; + const unsigned m = item.Get_Method(); + *s++ = 'm'; + *s++ = (char)(m + '0'); if (!item.IsDir()) { - s[2] = ':'; - ConvertUInt32ToString(item.GetDictSize() + 17, s + 3); + *s++ = ':'; + const unsigned dictMain = item.Get_DictSize_Main(); + const unsigned frac = item.Get_DictSize_Frac(); + /* + if (frac == 0 && algo == 0) + s = ConvertUInt32ToString(dictMain + 17, s); + else + */ + s = PrintDictSize(s, (UInt64)(32 + frac) << (12 + dictMain)); + if (item.Is_Rar5_Compat()) + { + *s++ = ':'; + *s++ = 'c'; + } } } - unsigned cryptoSize = 0; - int cryptoOffset = item.FindExtra(NExtraRecordType::kCrypto, cryptoSize); + const int cryptoOffset = item.FindExtra(NExtraID::kCrypto, cryptoSize); if (cryptoOffset >= 0) { - s = temp + strlen(temp); *s++ = ' '; - strcpy(s, "AES:"); CCryptoInfo cryptoInfo; - if (cryptoInfo.Parse(item.Extra + (unsigned)cryptoOffset, cryptoSize)) + const bool isOK = cryptoInfo.Parse(item.Extra + (unsigned)cryptoOffset, cryptoSize); + if (cryptoInfo.Algo == 0) + s = MyStpCpy(s, "AES"); + else + { + s = MyStpCpy(s, "Crypto_"); + s = ConvertUInt64ToString(cryptoInfo.Algo, s); + } + if (isOK) { - s += strlen(s); - ConvertUInt32ToString(cryptoInfo.Cnt, s); - s += strlen(s); *s++ = ':'; - ConvertUInt64ToString(cryptoInfo.Flags, s); + s = ConvertUInt32ToString(cryptoInfo.Cnt, s); + *s++ = ':'; + s = ConvertUInt64ToString(cryptoInfo.Flags, s); } } - + *s = 0; prop = temp; break; } + case kpidCharacts: + { + AString s; + + if (item.ACL >= 0) + s.Add_OptSpaced("ACL"); + + const UInt32 flags = item.Flags; + if (flags != 0) + { + const AString s2 = FlagsToString(k_FileFlags, Z7_ARRAY_SIZE(k_FileFlags), flags); + if (!s2.IsEmpty()) + s.Add_OptSpaced(s2); + } + + item.PrintInfo(s); + + if (!s.IsEmpty()) + prop = s; + break; + } + + case kpidHostOS: - if (item.HostOS < ARRAY_SIZE(kHostOS)) + if (item.HostOS < Z7_ARRAY_SIZE(kHostOS)) prop = kHostOS[(size_t)item.HostOS]; else prop = (UInt64)item.HostOS; @@ -1693,7 +2108,7 @@ static int CompareItemsPaths(const CHandler &handler, unsigned p1, unsigned p2, { if (!item2.Version_Defined) return -1; - int res = MyCompare(item1.Version, item2.Version); + const int res = MyCompare(item1.Version, item2.Version); if (res != 0) return res; } @@ -1707,7 +2122,7 @@ static int CompareItemsPaths(const CHandler &handler, unsigned p1, unsigned p2, static int CompareItemsPaths2(const CHandler &handler, unsigned p1, unsigned p2, const AString *name1) { - int res = CompareItemsPaths(handler, p1, p2, name1); + const int res = CompareItemsPaths(handler, p1, p2, name1); if (res != 0) return res; return MyCompare(p1, p2); @@ -1728,24 +2143,24 @@ static int FindLink(const CHandler &handler, const CUIntVector &sorted, { if (left > 0) { - unsigned refIndex = sorted[left - 1]; + const unsigned refIndex = sorted[left - 1]; if (CompareItemsPaths(handler, index, refIndex, &s) == 0) - return refIndex; + return (int)refIndex; } if (right < sorted.Size()) { - unsigned refIndex = sorted[right]; + const unsigned refIndex = sorted[right]; if (CompareItemsPaths(handler, index, refIndex, &s) == 0) - return refIndex; + return (int)refIndex; } return -1; } - unsigned mid = (left + right) / 2; - unsigned refIndex = sorted[mid]; - int compare = CompareItemsPaths2(handler, index, refIndex, &s); + const unsigned mid = (left + right) / 2; + const unsigned refIndex = sorted[mid]; + const int compare = CompareItemsPaths2(handler, index, refIndex, &s); if (compare == 0) - return refIndex; + return (int)refIndex; if (compare < 0) right = mid; else @@ -1756,15 +2171,34 @@ static int FindLink(const CHandler &handler, const CUIntVector &sorted, void CHandler::FillLinks() { unsigned i; - + + bool need_FillLinks = false; + for (i = 0; i < _refs.Size(); i++) { const CItem &item = _items[_refs[i].Item]; - if (!item.IsDir() && !item.IsService() && item.NeedUse_as_CopyLink()) - break; + if (!item.IsDir() + && !item.IsService() + && item.NeedUse_as_CopyLink()) + need_FillLinks = true; + + if (!item.IsSolid()) + _numBlocks++; + + const unsigned algo = item.Get_AlgoVersion_RawBits(); + _algo_Mask |= (UInt64)1 << algo; + _rar5comapt_mask |= 1u << item.Get_Rar5_CompatBit(); + if (!item.IsDir() && algo < Z7_ARRAY_SIZE(_methodMasks)) + { + _methodMasks[algo] |= 1u << (item.Get_Method()); + UInt64 d = 32 + item.Get_DictSize_Frac(); + d <<= (12 + item.Get_DictSize_Main()); + if (_dictMaxSizes[algo] < d) + _dictMaxSizes[algo] = d; + } } - if (i == _refs.Size()) + if (!need_FillLinks) return; CUIntVector sorted; @@ -1788,11 +2222,11 @@ void CHandler::FillLinks() const CItem &item = _items[ref.Item]; if (item.IsDir() || item.IsService() || item.PackSize != 0) continue; - CItem::CLinkInfo linkInfo; + CLinkInfo linkInfo; if (!item.FindExtra_Link(linkInfo) || linkInfo.Type != NLinkType::kFileCopy) continue; link.SetFrom_CalcLen((const char *)(item.Extra + linkInfo.NameOffset), linkInfo.NameLen); - int linkIndex = FindLink(*this, sorted, link, i); + const int linkIndex = FindLink(*this, sorted, link, i); if (linkIndex < 0) continue; if ((unsigned)linkIndex >= i) @@ -1816,30 +2250,24 @@ HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *openCallback) { CMyComPtr openVolumeCallback; - CMyComPtr getTextPassword; - + // CMyComPtr getTextPassword; NRar::CVolumeName seqName; - - UInt64 totalBytes = 0; - UInt64 curBytes = 0; + CTempBuf tempBuf; + CUnpacker unpacker; if (openCallback) { openCallback->QueryInterface(IID_IArchiveOpenVolumeCallback, (void **)&openVolumeCallback); - openCallback->QueryInterface(IID_ICryptoGetTextPassword, (void **)&getTextPassword); + openCallback->QueryInterface(IID_ICryptoGetTextPassword, (void **)&unpacker.getTextPassword); } + // unpacker.getTextPassword = getTextPassword; - CTempBuf tempBuf; - - CUnpacker unpacker; - unpacker.getTextPassword = getTextPassword; - + CInArchive arch; int prevSplitFile = -1; int prevMainFile = -1; - + UInt64 totalBytes = 0; + UInt64 curBytes = 0; bool nextVol_is_Required = false; - - CInArchive arch; for (;;) { @@ -1851,13 +2279,12 @@ HRESULT CHandler::Open2(IInStream *stream, { if (!openVolumeCallback) break; - if (_arcs.Size() == 1) { UString baseName; { NCOM::CPropVariant prop; - RINOK(openVolumeCallback->GetProperty(kpidName, &prop)); + RINOK(openVolumeCallback->GetProperty(kpidName, &prop)) if (prop.vt != VT_BSTR) break; baseName = prop.bstrVal; @@ -1865,14 +2292,10 @@ HRESULT CHandler::Open2(IInStream *stream, if (!seqName.InitName(baseName)) break; } - const UString volName = seqName.GetNextName(); - - HRESULT result = openVolumeCallback->GetStream(volName, &inStream); - + const HRESULT result = openVolumeCallback->GetStream(volName, &inStream); if (result != S_OK && result != S_FALSE) return result; - if (!inStream || result != S_OK) { if (nextVol_is_Required) @@ -1882,39 +2305,34 @@ HRESULT CHandler::Open2(IInStream *stream, } UInt64 endPos = 0; - RINOK(inStream->Seek(0, STREAM_SEEK_CUR, &arch.StreamStartPosition)); - RINOK(inStream->Seek(0, STREAM_SEEK_END, &endPos)); - RINOK(inStream->Seek(arch.StreamStartPosition, STREAM_SEEK_SET, NULL)); + RINOK(InStream_GetPos_GetSize(inStream, arch.StreamStartPosition, endPos)) if (openCallback) { totalBytes += endPos; - RINOK(openCallback->SetTotal(NULL, &totalBytes)); - } - - CInArcInfo arcInfoOpen; - { - HRESULT res = arch.Open(inStream, maxCheckStartPosition, getTextPassword, arcInfoOpen); - if (arch.IsArc && arch.UnexpectedEnd) - _errorFlags |= kpv_ErrorFlags_UnexpectedEnd; - if (_arcs.IsEmpty()) - { - _isArc = arch.IsArc; + RINOK(openCallback->SetTotal(NULL, &totalBytes)) } - if (res != S_OK) + CInArcInfo arcInfo_Open; { - if (res != S_FALSE) - return res; + const HRESULT res = arch.Open(inStream, maxCheckStartPosition, unpacker.getTextPassword, arcInfo_Open); + if (arch.IsArc && arch.UnexpectedEnd) + _errorFlags |= kpv_ErrorFlags_UnexpectedEnd; if (_arcs.IsEmpty()) - return res; - break; - } + _isArc = arch.IsArc; + if (res != S_OK) + { + if (res != S_FALSE) + return res; + if (_arcs.IsEmpty()) + return res; + break; + } } CArc &arc = _arcs.AddNew(); CInArcInfo &arcInfo = arc.Info; - arcInfo = arcInfoOpen; + arcInfo = arcInfo_Open; arc.Stream = inStream; CItem item; @@ -1924,18 +2342,16 @@ HRESULT CHandler::Open2(IInStream *stream, item.Clear(); arcInfo.EndPos = arch.Position; - if (arch.Position > endPos) { _errorFlags |= kpv_ErrorFlags_UnexpectedEnd; break; } - - RINOK(inStream->Seek(arch.Position, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekSet(inStream, arch.Position)) { CInArchive::CHeader h; - HRESULT res = arch.ReadBlockHeader(h); + const HRESULT res = arch.ReadBlockHeader(h); if (res != S_OK) { if (res != S_FALSE) @@ -1944,9 +2360,9 @@ HRESULT CHandler::Open2(IInStream *stream, { _errorFlags |= kpv_ErrorFlags_UnexpectedEnd; if (arcInfo.EndPos < arch.Position) - arcInfo.EndPos = arch.Position; + arcInfo.EndPos = arch.Position; if (arcInfo.EndPos < endPos) - arcInfo.EndPos = endPos; + arcInfo.EndPos = endPos; } else _errorFlags |= kpv_ErrorFlags_HeadersError; @@ -1959,15 +2375,17 @@ HRESULT CHandler::Open2(IInStream *stream, arcInfo.EndOfArchive_was_Read = true; if (!arch.ReadVar(arcInfo.EndFlags)) _errorFlags |= kpv_ErrorFlags_HeadersError; + if (!arch.Is_Buf_Finished() || h.ExtraSize || h.DataSize) + arcInfo.UnsupportedFeature = true; if (arcInfo.IsVolume()) { // for multivolume archives RAR can add ZERO bytes at the end for alignment. // We must skip these bytes to prevent phySize warning. - RINOK(inStream->Seek(arcInfo.EndPos, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekSet(inStream, arcInfo.EndPos)) bool areThereNonZeros; UInt64 numZeros; const UInt64 maxSize = 1 << 12; - RINOK(ReadZeroTail(inStream, areThereNonZeros, numZeros, maxSize)); + RINOK(ReadZeroTail(inStream, areThereNonZeros, numZeros, maxSize)) if (!areThereNonZeros && numZeros != 0 && numZeros <= maxSize) arcInfo.EndPos += numZeros; } @@ -1982,14 +2400,11 @@ HRESULT CHandler::Open2(IInStream *stream, } item.RecordType = (Byte)h.Type; - if (!arch.ReadFileHeader(h, item)) { _errorFlags |= kpv_ErrorFlags_HeadersError; break; } - - // item.MainPartSize = (UInt32)(Position - item.Position); item.DataPos = arch.Position; } @@ -2001,7 +2416,7 @@ HRESULT CHandler::Open2(IInStream *stream, isOk_packSize = false; _errorFlags |= kpv_ErrorFlags_HeadersError; if (arcInfo.EndPos < endPos) - arcInfo.EndPos = endPos; + arcInfo.EndPos = endPos; } else { @@ -2011,29 +2426,32 @@ HRESULT CHandler::Open2(IInStream *stream, } bool needAdd = true; - + + if (!_comment_WasUsedInArc + && _comment.Size() == 0 + && item.Is_CMT()) { - if (_comment.Size() == 0 - && item.Is_CMT() - && item.PackSize < kCommentSize_Max + _comment_WasUsedInArc = true; + if ( item.PackSize <= kCommentSize_Max && item.PackSize == item.Size && item.PackSize != 0 - && item.GetMethod() == 0 + && item.Get_Method() == 0 && !item.IsSplit()) { - RINOK(unpacker.DecodeToBuf(EXTERNAL_CODECS_VARS item, item.PackSize, inStream, _comment)); + RINOK(unpacker.DecodeToBuf(EXTERNAL_CODECS_VARS item, item.PackSize, inStream, _comment)) needAdd = false; + // item.RecordType = (Byte)NHeaderType::kFile; // for debug } } + + CRefItem ref; + ref.Item = _items.Size(); + ref.Last = ref.Item; + ref.Parent = -1; + ref.Link = -1; if (needAdd) { - CRefItem ref; - ref.Item = _items.Size(); - ref.Last = ref.Item; - ref.Parent = -1; - ref.Link = -1; - if (item.IsService()) { if (item.Is_STM()) @@ -2044,8 +2462,16 @@ HRESULT CHandler::Open2(IInStream *stream, else { needAdd = false; - if (item.Is_ACL() && (!item.IsEncrypted() || arch.m_CryptoMode)) + if (item.Is_ACL()) { + _acl_Used = true; + if (item.IsEncrypted() && !arch.m_CryptoMode) + _error_in_ACL = true; + else if (item.IsSolid() + || prevMainFile < 0 + || item.Size >= (1 << 24) + || item.Size == 0) + _error_in_ACL = true; if (prevMainFile >= 0 && item.Size < (1 << 24) && item.Size != 0) { CItem &mainItem = _items[_refs[prevMainFile].Item]; @@ -2053,7 +2479,7 @@ HRESULT CHandler::Open2(IInStream *stream, if (mainItem.ACL < 0) { CByteBuffer acl; - HRESULT res = tempBuf.Decode(EXTERNAL_CODECS_VARS item, inStream, unpacker, acl); + const HRESULT res = tempBuf.Decode(EXTERNAL_CODECS_VARS item, inStream, unpacker, acl); if (!item.IsSplitAfter()) tempBuf.Clear(); if (res != S_OK) @@ -2061,20 +2487,19 @@ HRESULT CHandler::Open2(IInStream *stream, tempBuf.Clear(); if (res != S_FALSE && res != E_NOTIMPL) return res; + _error_in_ACL = true; } - // RINOK(); - - if (res == S_OK && acl.Size() != 0) + else if (acl.Size() != 0) { if (_acls.IsEmpty() || acl != _acls.Back()) _acls.Add(acl); - mainItem.ACL = _acls.Size() - 1; + mainItem.ACL = (int)_acls.Size() - 1; } } } } } - } + } // item.IsService() if (needAdd) { @@ -2087,20 +2512,21 @@ HRESULT CHandler::Open2(IInStream *stream, if (item.IsNextForItem(prevItem)) { ref2.Last = _items.Size(); - prevItem.NextItem = ref2.Last; + prevItem.NextItem = (int)ref2.Last; needAdd = false; } } + else + _split_Error = true; } } if (needAdd) { if (item.IsSplitAfter()) - prevSplitFile = _refs.Size(); + prevSplitFile = (int)_refs.Size(); if (!item.IsService()) - prevMainFile = _refs.Size(); - _refs.Add(ref); + prevMainFile = (int)_refs.Size(); } } @@ -2115,12 +2541,14 @@ HRESULT CHandler::Open2(IInStream *stream, item.VolIndex = _arcs.Size() - 1; _items.Add(item); + if (needAdd) + _refs.Add(ref); if (openCallback && (_items.Size() & 0xFF) == 0) { - UInt64 numFiles = _items.Size(); - UInt64 numBytes = curBytes + item.DataPos; - RINOK(openCallback->SetCompleted(&numFiles, &numBytes)); + const UInt64 numFiles = _refs.Size(); // _items.Size() + const UInt64 numBytes = curBytes + item.DataPos; + RINOK(openCallback->SetCompleted(&numFiles, &numBytes)) } if (!isOk_packSize) @@ -2143,14 +2571,14 @@ HRESULT CHandler::Open2(IInStream *stream, } FillLinks(); - return S_OK; } -STDMETHODIMP CHandler::Open(IInStream *stream, + +Z7_COM7F_IMF(CHandler::Open(IInStream *stream, const UInt64 *maxCheckStartPosition, - IArchiveOpenCallback *openCallback) + IArchiveOpenCallback *openCallback)) { COM_TRY_BEGIN Close(); @@ -2158,13 +2586,26 @@ STDMETHODIMP CHandler::Open(IInStream *stream, COM_TRY_END } -STDMETHODIMP CHandler::Close() +Z7_COM7F_IMF(CHandler::Close()) { COM_TRY_BEGIN _missingVolName.Empty(); _errorFlags = 0; // _warningFlags = 0; _isArc = false; + _comment_WasUsedInArc = false; + _acl_Used = false; + _error_in_ACL = false; + _split_Error = false; + _numBlocks = 0; + _rar5comapt_mask = 0; + _algo_Mask = 0; // (UInt64)0u - 1; + for (unsigned i = 0; i < Z7_ARRAY_SIZE(_methodMasks); i++) + { + _methodMasks[i] = 0; + _dictMaxSizes[i] = 0; + } + _refs.Clear(); _items.Clear(); _arcs.Clear(); @@ -2175,10 +2616,10 @@ STDMETHODIMP CHandler::Close() } -class CVolsInStream: - public ISequentialInStream, - public CMyUnknownImp -{ +Z7_CLASS_IMP_NOQIB_1( + CVolsInStream + , ISequentialInStream +) UInt64 _rem; ISequentialInStream *_stream; const CObjectVector *_arcs; @@ -2189,22 +2630,19 @@ class CVolsInStream: private: CHash _hash; public: - MY_UNKNOWN_IMP void Init(const CObjectVector *arcs, const CObjectVector *items, unsigned itemIndex) { _arcs = arcs; _items = items; - _itemIndex = itemIndex; + _itemIndex = (int)itemIndex; _stream = NULL; CrcIsOK = true; } - - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); }; -STDMETHODIMP CVolsInStream::Read(void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CVolsInStream::Read(void *data, UInt32 size, UInt32 *processedSize)) { if (processedSize) *processedSize = 0; @@ -2218,7 +2656,7 @@ STDMETHODIMP CVolsInStream::Read(void *data, UInt32 size, UInt32 *processedSize) break; const CItem &item = (*_items)[_itemIndex]; IInStream *s = (*_arcs)[item.VolIndex].Stream; - RINOK(s->Seek(item.GetDataPosition(), STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekSet(s, item.GetDataPosition())) _stream = s; if (CrcIsOK && item.IsSplitAfter()) _hash.Init(item); @@ -2230,7 +2668,7 @@ STDMETHODIMP CVolsInStream::Read(void *data, UInt32 size, UInt32 *processedSize) UInt32 cur = size; if (cur > _rem) cur = (UInt32)_rem; - UInt32 num = cur; + const UInt32 num = cur; HRESULT res = _stream->Read(data, cur, &cur); _hash.Update(data, cur); realProcessedSize += cur; @@ -2267,10 +2705,10 @@ static int FindLinkBuf(CObjectVector &linkFiles, unsigned index) { if (left == right) return -1; - unsigned mid = (left + right) / 2; - unsigned linkIndex = linkFiles[mid].Index; + const unsigned mid = (left + right) / 2; + const unsigned linkIndex = linkFiles[mid].Index; if (index == linkIndex) - return mid; + return (int)mid; if (index < linkIndex) right = mid; else @@ -2295,41 +2733,35 @@ static inline int DecoderRes_to_OpRes(HRESULT res, bool crcOK) static HRESULT CopyData_with_Progress(const Byte *data, size_t size, ISequentialOutStream *outStream, ICompressProgressInfo *progress) { - size_t pos = 0; - - while (pos < size) + UInt64 pos64 = 0; + while (size) { - const UInt32 kStepSize = ((UInt32)1 << 24); - UInt32 cur32; - { - size_t cur = size - pos; - if (cur > kStepSize) - cur = kStepSize; - cur32 = (UInt32)cur; - } - RINOK(outStream->Write(data + pos, cur32, &cur32)); - if (cur32 == 0) + const UInt32 kStepSize = (UInt32)1 << 24; + UInt32 cur = kStepSize; + if (cur > size) + cur = (UInt32)size; + RINOK(outStream->Write(data, cur, &cur)) + if (cur == 0) return E_FAIL; - pos += cur32; + size -= cur; + data += cur; + pos64 += cur; if (progress) { - UInt64 pos64 = pos; - RINOK(progress->SetRatioInfo(&pos64, &pos64)); + RINOK(progress->SetRatioInfo(&pos64, &pos64)) } } - return S_OK; } -STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, - Int32 testMode, IArchiveExtractCallback *extractCallback) +Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testMode, IArchiveExtractCallback *extractCallback)) { COM_TRY_BEGIN - - bool allFilesMode = (numItems == (UInt32)(Int32)-1); + const bool allFilesMode = (numItems == (UInt32)(Int32)-1); if (allFilesMode) - numItems = _refs.Size(); + numItems = (UInt32)_refs.Size(); if (numItems == 0) return S_OK; @@ -2343,18 +2775,26 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, const Byte kStatus_Skip = 1 << 1; const Byte kStatus_Link = 1 << 2; + /* + In original RAR: + 1) service streams are not allowed to be solid, + and solid flag must be ignored for service streams. + 2) If RAR creates new solid block and first file in solid block is Link file, + then it can clear solid flag for Link file and + clear solid flag for first non-Link file after Link file. + */ + CObjectVector linkFiles; { UInt64 total = 0; bool isThereUndefinedSize = false; bool thereAreLinks = false; - { unsigned solidLimit = 0; for (UInt32 t = 0; t < numItems; t++) { - unsigned index = allFilesMode ? t : indices[t]; + const unsigned index = (unsigned)(allFilesMode ? t : indices[t]); const CRefItem &ref = _refs[index]; const CItem &item = _items[ref.Item]; const CItem &lastItem = _items[ref.Last]; @@ -2368,13 +2808,15 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, if (ref.Link >= 0) { - if (!testMode) + // 18.06 fixed: we use links for Test mode too + // if (!testMode) { if ((unsigned)ref.Link < index) { const CRefItem &linkRef = _refs[(unsigned)ref.Link]; const CItem &linkItem = _items[linkRef.Item]; - if (linkItem.IsSolid() && linkItem.Size <= k_CopyLinkFile_MaxSize) + if (linkItem.IsSolid()) + if (testMode || linkItem.Size <= k_CopyLinkFile_MaxSize) { if (extractStatuses[(unsigned)ref.Link] == 0) { @@ -2429,19 +2871,25 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, { unsigned solidLimit = 0; - FOR_VECTOR(i, _refs) + FOR_VECTOR (i, _refs) { if ((extractStatuses[i] & kStatus_Link) == 0) continue; + + // We use CLinkFile for testMode too. + // So we can show errors for copy files. + // if (!testMode) + { + CLinkFile &linkFile = linkFiles.AddNew(); + linkFile.Index = i; + } + const CItem &item = _items[_refs[i].Item]; /* if (item.IsService()) continue; */ - CLinkFile &linkFile = linkFiles.AddNew(); - linkFile.Index = i; - if (item.IsSolid()) { unsigned j = i; @@ -2472,18 +2920,19 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, solidLimit = i + 1; } + if (!testMode) for (UInt32 t = 0; t < numItems; t++) { - unsigned index = allFilesMode ? t : indices[t]; + const unsigned index = (unsigned)(allFilesMode ? t : indices[t]); const CRefItem &ref = _refs[index]; - int linkIndex = ref.Link; + const int linkIndex = ref.Link; if (linkIndex < 0 || (unsigned)linkIndex >= index) continue; const CItem &linkItem = _items[_refs[(unsigned)linkIndex].Item]; if (!linkItem.IsSolid() || linkItem.Size > k_CopyLinkFile_MaxSize) continue; - int bufIndex = FindLinkBuf(linkFiles, linkIndex); + const int bufIndex = FindLinkBuf(linkFiles, (unsigned)linkIndex); if (bufIndex < 0) return E_FAIL; linkFiles[bufIndex].NumLinks++; @@ -2492,58 +2941,162 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, if (total != 0 || !isThereUndefinedSize) { - RINOK(extractCallback->SetTotal(total)); + RINOK(extractCallback->SetTotal(total)) + } + } + + + + // ---------- MEMORY REQUEST ---------- + { + UInt64 dictMaxSize = 0; + for (UInt32 i = 0; i < _refs.Size(); i++) + { + if (extractStatuses[i] == 0) + continue; + const CRefItem &ref = _refs[i]; + const CItem &item = _items[ref.Item]; +/* + if (!item.IsDir() && !item.IsService() && item.NeedUse_as_CopyLink()) + { + } +*/ + const unsigned algo = item.Get_AlgoVersion_RawBits(); + if (!item.IsDir() && algo < Z7_ARRAY_SIZE(_methodMasks)) + { + const UInt64 d = item.Get_DictSize64(); + if (dictMaxSize < d) + dictMaxSize = d; + } + } + // we use callback, if dict exceeds (1 GB), because + // client code can set low limit (1 GB) for allowed memory usage. + const UInt64 k_MemLimit_for_Callback = (UInt64)1 << 30; + if (dictMaxSize > (_memUsage_WasSet ? + _memUsage_Decompress : k_MemLimit_for_Callback)) + { + { + CMyComPtr requestMem; + extractCallback->QueryInterface(IID_IArchiveRequestMemoryUseCallback, (void **)&requestMem); + if (!requestMem) + { + if (_memUsage_WasSet) + return E_OUTOFMEMORY; + } + else + { + UInt64 allowedSize = _memUsage_WasSet ? + _memUsage_Decompress : + (UInt64)1 << 32; // 4 GB is default allowed limit for RAR7 + + const UInt32 flags = (_memUsage_WasSet ? + NRequestMemoryUseFlags::k_AllowedSize_WasForced | + NRequestMemoryUseFlags::k_MLimit_Exceeded : + (dictMaxSize > allowedSize) ? + NRequestMemoryUseFlags::k_DefaultLimit_Exceeded: + 0) + | NRequestMemoryUseFlags::k_SkipArc_IsExpected + // | NRequestMemoryUseFlags::k_NoErrorMessage // for debug + ; + + // we set "Allow" for default case, if requestMem doesn't process anything. + UInt32 answerFlags = + (_memUsage_WasSet && dictMaxSize > allowedSize) ? + NRequestMemoryAnswerFlags::k_Limit_Exceeded + | NRequestMemoryAnswerFlags::k_SkipArc + : NRequestMemoryAnswerFlags::k_Allow; + + RINOK(requestMem->RequestMemoryUse( + flags, + NEventIndexType::kNoIndex, + // NEventIndexType::kInArcIndex, // for debug + 0, // index + NULL, // path + dictMaxSize, &allowedSize, &answerFlags)) + if ( (answerFlags & NRequestMemoryAnswerFlags::k_Allow) == 0 + || (answerFlags & NRequestMemoryAnswerFlags::k_Stop) + || (answerFlags & NRequestMemoryAnswerFlags::k_SkipArc) + ) + { + return E_OUTOFMEMORY; + } +/* + if ((answerFlags & NRequestMemoryAnswerFlags::k_AskForBigFile) == 0 && + (answerFlags & NRequestMemoryAnswerFlags::k_ReportForBigFile) == 0) + { + // requestMem.Release(); + } +*/ + } + } } } + + // ---------- UNPACK ---------- + UInt64 totalUnpacked = 0; UInt64 totalPacked = 0; - UInt64 curUnpackSize = 0; - UInt64 curPackSize = 0; + UInt64 curUnpackSize; + UInt64 curPackSize; CUnpacker unpacker; - - CVolsInStream *volsInStreamSpec = new CVolsInStream; - CMyComPtr volsInStream = volsInStreamSpec; - - CLocalProgress *lps = new CLocalProgress; - CMyComPtr progress = lps; + unpacker.NeedCrc = _needChecksumCheck; + CMyComPtr2_Create volsInStream; + CMyComPtr2_Create lps; lps->Init(extractCallback, false); - // bool needClearSolid = true; +/* + bool prevSolidWasSkipped = false; + UInt64 solidDictSize_Skip = 0; +*/ - FOR_VECTOR(i, _refs) + for (unsigned i = 0;; i++, + totalUnpacked += curUnpackSize, + totalPacked += curPackSize) { - if (extractStatuses[i] == 0) - continue; - - totalUnpacked += curUnpackSize; - totalPacked += curPackSize; lps->InSize = totalPacked; lps->OutSize = totalUnpacked; - RINOK(lps->SetCur()); + RINOK(lps->SetCur()) + { + const unsigned num = _refs.Size(); + if (i >= num) + break; + for (;;) + { + if (extractStatuses[i] != 0) + break; + i++; + if (i >= num) + break; + } + if (i >= num) + break; + } + curUnpackSize = 0; + curPackSize = 0; - CMyComPtr realOutStream; - + // isExtract means that we don't skip that item. So we need read data. + const bool isExtract = ((extractStatuses[i] & kStatus_Extract) != 0); Int32 askMode = - ((extractStatuses[i] & kStatus_Extract) != 0) ? (testMode ? + isExtract ? (testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract) : NExtract::NAskMode::kSkip; unpacker.linkFile = NULL; - if (((extractStatuses[i] & kStatus_Link) != 0)) + // if (!testMode) + if ((extractStatuses[i] & kStatus_Link) != 0) { - int bufIndex = FindLinkBuf(linkFiles, i); + const int bufIndex = FindLinkBuf(linkFiles, i); if (bufIndex < 0) return E_FAIL; unpacker.linkFile = &linkFiles[bufIndex]; } - UInt32 index = i; - + const unsigned index = i; const CRefItem *ref = &_refs[index]; const CItem *item = &_items[ref->Item]; const CItem &lastItem = _items[ref->Last]; @@ -2554,25 +3107,68 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, curPackSize = GetPackSize(index); - RINOK(extractCallback->GetStream(index, &realOutStream, askMode)); + bool isSolid = false; + if (!item->IsService()) + { + if (item->IsSolid()) + isSolid = unpacker.SolidAllowed; + unpacker.SolidAllowed = isSolid; + } + - bool isSolid; + // ----- request mem ----- +/* + // link files are complicated cases. (ref->Link >= 0) + // link file can refer to non-solid file that can have big dictionary + // link file can refer to solid files that requres buffer + if (!item->IsDir() && requestMem && ref->Link < 0) { - bool &needClearSolid = unpacker.NeedClearSolid[item->IsService() ? 1 : 0]; - isSolid = (item->IsSolid() && !needClearSolid); - if (item->IsService()) - isSolid = false; - needClearSolid = !item->IsSolid(); + bool needSkip = false; + if (isSolid) + needSkip = prevSolidWasSkipped; + else + { + // isSolid == false + const unsigned algo = item->Get_AlgoVersion_RawBits(); + // const unsigned m = item.Get_Method(); + if (algo < Z7_ARRAY_SIZE(_methodMasks)) + { + solidDictSize_Skip = item->Get_DictSize64(); + if (solidDictSize_Skip > allowedSize) + needSkip = true; + } + } + if (needSkip) + { + UInt32 answerFlags = 0; + UInt64 allowedSize_File = allowedSize; + RINOK(requestMem->RequestMemoryUse( + NRequestMemoryUseFlags::k_Limit_Exceeded | + NRequestMemoryUseFlags::k_IsReport, + NEventIndexType::kInArcIndex, + index, + NULL, // path + solidDictSize_Skip, &allowedSize_File, &answerFlags)) + if (!item->IsService()) + prevSolidWasSkipped = true; + continue; + } } + if (!item->IsService() && item->IsDir()) + prevSolidWasSkipped = false; +*/ + + CMyComPtr realOutStream; + RINOK(extractCallback->GetStream((UInt32)index, &realOutStream, askMode)) if (item->IsDir()) { - RINOK(extractCallback->PrepareOperation(askMode)); - RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)); + RINOK(extractCallback->PrepareOperation(askMode)) + RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)) continue; } - int index2 = ref->Link; + const int index2 = ref->Link; int bufIndex = -1; @@ -2589,20 +3185,32 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, curUnpackSize = lastItem2.Size; else curUnpackSize = 0; - curPackSize = GetPackSize(index2); + curPackSize = GetPackSize((unsigned)index2); + } + else + { + if ((unsigned)index2 < index) + bufIndex = FindLinkBuf(linkFiles, (unsigned)index2); } - else if ((unsigned)index2 < index) - bufIndex = FindLinkBuf(linkFiles, index2); } + bool needCallback = true; + if (!realOutStream) { if (testMode) { if (item->NeedUse_as_CopyLink_or_HardLink()) { - RINOK(extractCallback->PrepareOperation(askMode)); - RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)); + Int32 opRes = NExtract::NOperationResult::kOK; + if (bufIndex >= 0) + { + const CLinkFile &linkFile = linkFiles[bufIndex]; + opRes = DecoderRes_to_OpRes(linkFile.Res, linkFile.crcOK); + } + + RINOK(extractCallback->PrepareOperation(askMode)) + RINOK(extractCallback->SetOperationResult(opRes)) continue; } } @@ -2611,10 +3219,10 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, if (item->IsService()) continue; - if (item->NeedUse_as_HardLink()) - continue; + needCallback = false; - bool needDecode = false; + if (!item->NeedUse_as_HardLink()) + if (index2 < 0) for (unsigned n = i + 1; n < _refs.Size(); n++) { @@ -2625,47 +3233,62 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, break; if (extractStatuses[i] != 0) { - needDecode = true; + needCallback = true; break; } } - if (!needDecode) - continue; - askMode = NExtract::NAskMode::kSkip; } } - RINOK(extractCallback->PrepareOperation(askMode)); + if (needCallback) + { + RINOK(extractCallback->PrepareOperation(askMode)) + } if (bufIndex >= 0) { CLinkFile &linkFile = linkFiles[bufIndex]; - if (linkFile.NumLinks == 0) - return E_FAIL; - if (realOutStream) + + if (isExtract) + { + if (linkFile.NumLinks == 0) + return E_FAIL; + + if (needCallback) + if (realOutStream) + { + RINOK(CopyData_with_Progress(linkFile.Data, linkFile.Data.Size(), realOutStream, lps)) + } + + if (--linkFile.NumLinks == 0) + linkFile.Data.Free(); + } + + if (needCallback) { - RINOK(CopyData_with_Progress(linkFile.Data, linkFile.Data.Size(), realOutStream, progress)); + RINOK(extractCallback->SetOperationResult(DecoderRes_to_OpRes(linkFile.Res, linkFile.crcOK))) } - if (--linkFile.NumLinks == 0) - linkFile.Data.Free(); - RINOK(extractCallback->SetOperationResult(DecoderRes_to_OpRes(linkFile.Res, linkFile.crcOK))); continue; } + if (!needCallback) + continue; + if (item->NeedUse_as_CopyLink()) { - RINOK(extractCallback->SetOperationResult( - realOutStream ? - NExtract::NOperationResult::kUnsupportedMethod: - NExtract::NOperationResult::kOK)); + const int opRes = realOutStream ? + NExtract::NOperationResult::kUnsupportedMethod: + NExtract::NOperationResult::kOK; + realOutStream.Release(); + RINOK(extractCallback->SetOperationResult(opRes)) continue; } - volsInStreamSpec->Init(&_arcs, &_items, ref->Item); + volsInStream->Init(&_arcs, &_items, ref->Item); - UInt64 packSize = curPackSize; + const UInt64 packSize = curPackSize; if (item->IsEncrypted()) if (!unpacker.getTextPassword) @@ -2677,15 +3300,15 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, if (wrongPassword) { realOutStream.Release(); - RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kWrongPassword)); + RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kWrongPassword)) continue; } bool crcOK = true; if (result == S_OK) - result = unpacker.Code(*item, _items[ref->Last], packSize, volsInStream, realOutStream, progress, crcOK); + result = unpacker.Code(*item, _items[ref->Last], packSize, volsInStream, realOutStream, lps, crcOK); realOutStream.Release(); - if (!volsInStreamSpec->CrcIsOK) + if (!volsInStream->CrcIsOK) crcOK = false; int opRes = crcOK ? @@ -2702,25 +3325,79 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, return result; } - RINOK(extractCallback->SetOperationResult(opRes)); + RINOK(extractCallback->SetOperationResult(opRes)) } { - FOR_VECTOR(i, linkFiles) - if (linkFiles[i].NumLinks != 0) + FOR_VECTOR (k, linkFiles) + if (linkFiles[k].NumLinks != 0) return E_FAIL; } return S_OK; - COM_TRY_END } +CHandler::CHandler() +{ + InitDefaults(); +} + +void CHandler::InitDefaults() +{ + _needChecksumCheck = true; + _memUsage_WasSet = false; + _memUsage_Decompress = (UInt64)1 << 32; +} + +Z7_COM7F_IMF(CHandler::SetProperties(const wchar_t * const *names, const PROPVARIANT *values, UInt32 numProps)) +{ + InitDefaults(); + + for (UInt32 i = 0; i < numProps; i++) + { + UString name = names[i]; + name.MakeLower_Ascii(); + if (name.IsEmpty()) + return E_INVALIDARG; + + const PROPVARIANT &prop = values[i]; + + if (name.IsPrefixedBy_Ascii_NoCase("mt")) + { + } + else if (name.IsPrefixedBy_Ascii_NoCase("memx")) + { + size_t memAvail; + if (!NWindows::NSystem::GetRamSize(memAvail)) + memAvail = (size_t)sizeof(size_t) << 28; + UInt64 v; + if (!ParseSizeString(name.Ptr(4), prop, memAvail, v)) + return E_INVALIDARG; + _memUsage_Decompress = v; + _memUsage_WasSet = true; + } + else if (name.IsPrefixedBy_Ascii_NoCase("crc")) + { + name.Delete(0, 3); + UInt32 crcSize = 1; + RINOK(ParsePropToUInt32(name, prop, crcSize)) + _needChecksumCheck = (crcSize != 0); + } + else + { + return E_INVALIDARG; + } + } + return S_OK; +} + + IMPL_ISetCompressCodecsInfo REGISTER_ARC_I( - "Rar5", "rar r00", 0, 0xCC, + "Rar5", "rar r00", NULL, 0xCC, kMarker, 0, NArcInfoFlags::kFindSignature, @@ -2729,33 +3406,78 @@ REGISTER_ARC_I( }} -class CBlake2spHasher: - public IHasher, - public CMyUnknownImp -{ - CBlake2sp _blake; - Byte mtDummy[1 << 7]; - +Z7_CLASS_IMP_COM_2( + CBlake2spHasher + , IHasher + , ICompressSetCoderProperties +) + CAlignedBuffer1 _buf; + // CBlake2sp _blake; + #define Z7_BLACK2S_ALIGN_OBJECT_OFFSET 0 + CBlake2sp *Obj() { return (CBlake2sp *)(void *)((Byte *)_buf + Z7_BLACK2S_ALIGN_OBJECT_OFFSET); } public: - CBlake2spHasher() { Init(); } - - MY_UNKNOWN_IMP - INTERFACE_IHasher(;) + Byte _mtDummy[1 << 7]; // it's public to eliminate clang warning: unused private field + CBlake2spHasher(): + _buf(sizeof(CBlake2sp) + Z7_BLACK2S_ALIGN_OBJECT_OFFSET) + { + Blake2sp_SetFunction(Obj(), 0); + Blake2sp_InitState(Obj()); + } }; -STDMETHODIMP_(void) CBlake2spHasher::Init() throw() +Z7_COM7F_IMF2(void, CBlake2spHasher::Init()) +{ + Blake2sp_InitState(Obj()); +} + +Z7_COM7F_IMF2(void, CBlake2spHasher::Update(const void *data, UInt32 size)) { - Blake2sp_Init(&_blake); +#if 1 + Blake2sp_Update(Obj(), (const Byte *)data, (size_t)size); +#else + // for debug: + for (;;) + { + if (size == 0) + return; + UInt32 size2 = (size * 0x85EBCA87) % size / 800; + // UInt32 size2 = size / 2; + if (size2 == 0) + size2 = 1; + Blake2sp_Update(Obj(), (const Byte *)data, size2); + data = (const void *)((const Byte *)data + size2); + size -= size2; + } +#endif } -STDMETHODIMP_(void) CBlake2spHasher::Update(const void *data, UInt32 size) throw() +Z7_COM7F_IMF2(void, CBlake2spHasher::Final(Byte *digest)) { - Blake2sp_Update(&_blake, (const Byte *)data, size); + Blake2sp_Final(Obj(), digest); } -STDMETHODIMP_(void) CBlake2spHasher::Final(Byte *digest) throw() +Z7_COM7F_IMF(CBlake2spHasher::SetCoderProperties(const PROPID *propIDs, const PROPVARIANT *coderProps, UInt32 numProps)) { - Blake2sp_Final(&_blake, digest); + unsigned algo = 0; + for (UInt32 i = 0; i < numProps; i++) + { + if (propIDs[i] == NCoderPropID::kDefaultProp) + { + const PROPVARIANT &prop = coderProps[i]; + if (prop.vt != VT_UI4) + return E_INVALIDARG; + /* + if (prop.ulVal > Z7_BLAKE2S_ALGO_MAX) + return E_NOTIMPL; + */ + algo = (unsigned)prop.ulVal; + } + } + if (!Blake2sp_SetFunction(Obj(), algo)) + return E_NOTIMPL; + return S_OK; } -REGISTER_HASHER(CBlake2spHasher, 0x202, "BLAKE2sp", BLAKE2S_DIGEST_SIZE) +REGISTER_HASHER(CBlake2spHasher, 0x202, "BLAKE2sp", Z7_BLAKE2S_DIGEST_SIZE) + +static struct CBlake2sp_Prepare { CBlake2sp_Prepare() { z7_Black2sp_Prepare(); } } g_Blake2sp_Prepare; diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Rar/Rar5Handler.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Rar/Rar5Handler.h index 27e937de..913ba85c 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Rar/Rar5Handler.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Rar/Rar5Handler.h @@ -1,7 +1,7 @@ // Rar5Handler.h -#ifndef __RAR5_HANDLER_H -#define __RAR5_HANDLER_H +#ifndef ZIP7_INC_RAR5_HANDLER_H +#define ZIP7_INC_RAR5_HANDLER_H #include "../../../../C/Blake2.h" @@ -48,7 +48,8 @@ namespace NArcFlags // const unsigned kLocked = 1 << 4; } -const unsigned kArcExtraRecordType_Locator = 1; +const unsigned kArcExtraRecordType_Locator = 1; +const unsigned kArcExtraRecordType_Metadata = 2; namespace NLocatorFlags { @@ -56,6 +57,14 @@ namespace NLocatorFlags const unsigned kRecovery = 1 << 1; } +namespace NMetadataFlags +{ + const unsigned kArcName = 1 << 0; + const unsigned kCTime = 1 << 1; + const unsigned kUnixTime = 1 << 2; + const unsigned kNanoSec = 1 << 3; +} + namespace NFileFlags { const unsigned kIsDir = 1 << 0; @@ -68,6 +77,7 @@ namespace NMethodFlags { // const unsigned kVersionMask = 0x3F; const unsigned kSolid = 1 << 6; + const unsigned kRar5_Compat = 1u << 20; } namespace NArcEndFlags @@ -85,7 +95,7 @@ enum EHostOS // ---------- Extra ---------- -namespace NExtraRecordType +namespace NExtraID { enum { @@ -99,7 +109,7 @@ namespace NExtraRecordType }; } -// const unsigned kCryptoAlgo_AES = 0; +const unsigned kCryptoAlgo_AES = 0; namespace NCryptoFlags { @@ -133,8 +143,9 @@ namespace NTimeRecord { const unsigned kUnixTime = 1 << 0; const unsigned kMTime = 1 << 1; - // const unsigned kCTime = 1 << 2; - // const unsigned kATime = 1 << 3; + const unsigned kCTime = 1 << 2; + const unsigned kATime = 1 << 3; + const unsigned kUnixNs = 1 << 4; } } @@ -156,6 +167,17 @@ namespace NLinkFlags } +struct CLinkInfo +{ + UInt64 Type; + UInt64 Flags; + unsigned NameOffset; + unsigned NameLen; + + bool Parse(const Byte *p, unsigned size); +}; + + struct CItem { UInt32 CommonFlags; @@ -168,8 +190,8 @@ struct CItem AString Name; - int VolIndex; - int NextItem; + unsigned VolIndex; + int NextItem; // in _items{} UInt32 UnixMTime; UInt32 CRC; @@ -189,9 +211,12 @@ struct CItem void Clear() { - CommonFlags = 0; - Flags = 0; + // CommonFlags = 0; + // Flags = 0; + // UnixMTime = 0; + // CRC = 0; + VolIndex = 0; NextItem = -1; @@ -218,32 +243,70 @@ struct CItem // && false; } - bool IsSolid() const { return ((UInt32)Method & NMethodFlags::kSolid) != 0; } - unsigned GetAlgoVersion() const { return (unsigned)Method & 0x3F; } - unsigned GetMethod() const { return ((unsigned)Method >> 7) & 0x7; } - UInt32 GetDictSize() const { return (((UInt32)Method >> 10) & 0xF); } + // rar docs: Solid flag can be set only for file headers and is never set for service headers. + bool IsSolid() const { return ((UInt32)Method & NMethodFlags::kSolid) != 0; } + bool Is_Rar5_Compat() const { return ((UInt32)Method & NMethodFlags::kRar5_Compat) != 0; } + unsigned Get_Rar5_CompatBit() const { return ((UInt32)Method >> 20) & 1; } + + unsigned Get_AlgoVersion_RawBits() const { return (unsigned)Method & 0x3F; } + unsigned Get_AlgoVersion_HuffRev() const + { + unsigned w = (unsigned)Method & 0x3F; + if (w == 1 && Is_Rar5_Compat()) + w = 0; + return w; + } + unsigned Get_Method() const { return ((unsigned)Method >> 7) & 0x7; } + + unsigned Get_DictSize_Main() const + { return ((UInt32)Method >> 10) & (Get_AlgoVersion_RawBits() == 0 ? 0xf : 0x1f); } + unsigned Get_DictSize_Frac() const + { + // original-unrar ignores Frac, if (algo==0) (rar5): + if (Get_AlgoVersion_RawBits() == 0) + return 0; + return ((UInt32)Method >> 15) & 0x1f; + } + UInt64 Get_DictSize64() const + { + // ver 6.* check + // return (((UInt32)Method >> 10) & 0xF); + UInt64 winSize = 0; + const unsigned algo = Get_AlgoVersion_RawBits(); + if (algo <= 1) + { + UInt32 w = 32; + if (algo == 1) + w += Get_DictSize_Frac(); + winSize = (UInt64)w << (12 + Get_DictSize_Main()); + } + return winSize; + } + bool IsService() const { return RecordType == NHeaderType::kService; } - bool Is_STM() const { return IsService() && Name == "STM"; } - bool Is_CMT() const { return IsService() && Name == "CMT"; } - bool Is_ACL() const { return IsService() && Name == "ACL"; } - // bool Is_QO() const { return IsService() && Name == "QO"; } + bool Is_STM() const { return IsService() && Name.IsEqualTo("STM"); } + bool Is_CMT() const { return IsService() && Name.IsEqualTo("CMT"); } + bool Is_ACL() const { return IsService() && Name.IsEqualTo("ACL"); } + // bool Is_QO() const { return IsService() && Name.IsEqualTo("QO"); } + + int FindExtra(unsigned extraID, unsigned &recordDataSize) const; + void PrintInfo(AString &s) const; - int FindExtra(unsigned type, unsigned &recordDataSize) const; bool IsEncrypted() const { unsigned size; - return FindExtra(NExtraRecordType::kCrypto, size) >= 0; + return FindExtra(NExtraID::kCrypto, size) >= 0; } int FindExtra_Blake() const { unsigned size = 0; - int offset = FindExtra(NExtraRecordType::kHash, size); + const int offset = FindExtra(NExtraID::kHash, size); if (offset >= 0 - && size == BLAKE2S_DIGEST_SIZE + 1 + && size == Z7_BLAKE2S_DIGEST_SIZE + 1 && Extra[(unsigned)offset] == kHashID_Blake2sp) return offset + 1; return -1; @@ -251,14 +314,6 @@ struct CItem bool FindExtra_Version(UInt64 &version) const; - struct CLinkInfo - { - UInt64 Type; - UInt64 Flags; - unsigned NameOffset; - unsigned NameLen; - }; - bool FindExtra_Link(CLinkInfo &link) const; void Link_to_Prop(unsigned linkType, NWindows::NCOM::CPropVariant &prop) const; bool Is_CopyLink() const; @@ -276,11 +331,17 @@ struct CItem UInt32 a; switch (HostOS) { - case kHost_Windows: a = Attrib; break; - case kHost_Unix: a = (Attrib << 16); break; - default: a = 0; + case kHost_Windows: + a = Attrib; + break; + case kHost_Unix: + a = Attrib << 16; + a |= 0x8000; // add posix mode marker + break; + default: + a = 0; } - // if (IsDir()) a |= FILE_ATTRIBUTE_DIRECTORY; + if (IsDir()) a |= FILE_ATTRIBUTE_DIRECTORY; return a; } @@ -299,10 +360,14 @@ struct CInArcInfo bool EndOfArchive_was_Read; bool IsEncrypted; + bool Locator_Defined; + bool Locator_Error; + bool Metadata_Defined; + bool Metadata_Error; + bool UnknownExtraRecord; + bool Extra_Error; + bool UnsupportedFeature; - // CByteBuffer Extra; - - /* struct CLocator { UInt64 Flags; @@ -311,11 +376,32 @@ struct CInArcInfo bool Is_QuickOpen() const { return (Flags & NLocatorFlags::kQuickOpen) != 0; } bool Is_Recovery() const { return (Flags & NLocatorFlags::kRecovery) != 0; } + + bool Parse(const Byte *p, size_t size); + CLocator(): + Flags(0), + QuickOpen(0), + Recovery(0) + {} }; - int FindExtra(unsigned type, unsigned &recordDataSize) const; - bool FindExtra_Locator(CLocator &locator) const; - */ + struct CMetadata + { + UInt64 Flags; + UInt64 CTime; + AString ArcName; + + bool Parse(const Byte *p, size_t size); + CMetadata(): + Flags(0), + CTime(0) + {} + }; + + CLocator Locator; + CMetadata Metadata; + + bool ParseExtra(const Byte *p, size_t size); CInArcInfo(): Flags(0), @@ -324,7 +410,14 @@ struct CInArcInfo EndPos(0), EndFlags(0), EndOfArchive_was_Read(false), - IsEncrypted(false) + IsEncrypted(false), + Locator_Defined(false), + Locator_Error(false), + Metadata_Defined(false), + Metadata_Error(false), + UnknownExtraRecord(false), + Extra_Error(false), + UnsupportedFeature(false) {} /* @@ -336,7 +429,6 @@ struct CInArcInfo EndPos = 0; EndFlags = 0; EndOfArchive_was_Read = false; - Extra.Free(); } */ @@ -354,10 +446,10 @@ struct CInArcInfo struct CRefItem { - unsigned Item; - unsigned Last; - int Parent; - int Link; + unsigned Item; // First item in _items[] + unsigned Last; // Last item in _items[] + int Parent; // in _refs[], if alternate stream + int Link; // in _refs[] }; @@ -368,25 +460,57 @@ struct CArc }; -class CHandler: +class CHandler Z7_final: public IInArchive, public IArchiveGetRawProps, - PUBLIC_ISetCompressCodecsInfo + public ISetProperties, + Z7_PUBLIC_ISetCompressCodecsInfo_IFEC public CMyUnknownImp { + Z7_COM_QI_BEGIN2(IInArchive) + Z7_COM_QI_ENTRY(IArchiveGetRawProps) + Z7_COM_QI_ENTRY(ISetProperties) + Z7_COM_QI_ENTRY_ISetCompressCodecsInfo_IFEC + Z7_COM_QI_END + Z7_COM_ADDREF_RELEASE + + Z7_IFACE_COM7_IMP(IInArchive) + Z7_IFACE_COM7_IMP(IArchiveGetRawProps) + Z7_IFACE_COM7_IMP(ISetProperties) + DECL_ISetCompressCodecsInfo + + void InitDefaults(); + + bool _isArc; + bool _needChecksumCheck; + bool _memUsage_WasSet; + bool _comment_WasUsedInArc; + bool _acl_Used; + bool _error_in_ACL; + bool _split_Error; public: CRecordVector _refs; CObjectVector _items; + + CHandler(); private: CObjectVector _arcs; CObjectVector _acls; UInt32 _errorFlags; // UInt32 _warningFlags; - bool _isArc; + + UInt32 _numBlocks; + unsigned _rar5comapt_mask; + unsigned _methodMasks[2]; + UInt64 _algo_Mask; + UInt64 _dictMaxSizes[2]; + CByteBuffer _comment; UString _missingVolName; + UInt64 _memUsage_Decompress; + DECL_EXTERNAL_CODECS_VARS UInt64 GetPackSize(unsigned refIndex) const; @@ -396,18 +520,6 @@ class CHandler: HRESULT Open2(IInStream *stream, const UInt64 *maxCheckStartPosition, IArchiveOpenCallback *openCallback); - -public: - MY_QUERYINTERFACE_BEGIN2(IInArchive) - MY_QUERYINTERFACE_ENTRY(IArchiveGetRawProps) - QUERY_ENTRY_ISetCompressCodecsInfo - MY_QUERYINTERFACE_END - MY_ADDREF_RELEASE - - INTERFACE_IInArchive(;) - INTERFACE_IArchiveGetRawProps(;) - - DECL_ISetCompressCodecsInfo }; }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Rar/RarHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/Rar/RarHandler.cpp index e858c1ea..6c53847b 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Rar/RarHandler.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Rar/RarHandler.cpp @@ -6,6 +6,8 @@ #include "../../../Common/ComTry.h" #include "../../../Common/IntToString.h" +#include "../../../Common/MyBuffer2.h" +#include "../../../Common/MyLinux.h" #include "../../../Common/UTFConvert.h" #include "../../../Windows/PropVariantUtils.h" @@ -43,9 +45,8 @@ using namespace NWindows; namespace NArchive { namespace NRar { -#define SIGNATURE { 0x52 , 0x61, 0x72, 0x21, 0x1a, 0x07, 0x00 } - -static const Byte kMarker[NHeader::kMarkerSize] = SIGNATURE; +static const Byte kMarker[NHeader::kMarkerSize] = + { 0x52, 0x61, 0x72, 0x21, 0x1a, 0x07, 0x00 }; const unsigned kPasswordLen_MAX = 127; @@ -70,8 +71,14 @@ bool CItem::IsDir() const case NHeader::NFile::kHostMSDOS: case NHeader::NFile::kHostOS2: case NHeader::NFile::kHostWin32: - if ((Attrib & FILE_ATTRIBUTE_DIRECTORY) != 0) + if (Attrib & FILE_ATTRIBUTE_DIRECTORY) + return true; + break; + case NHeader::NFile::kHostUnix: + case NHeader::NFile::kHostBeOS: + if (MY_LIN_S_ISDIR(Attrib)) return true; + break; } return false; } @@ -86,11 +93,20 @@ UInt32 CItem::GetWinAttrib() const case NHeader::NFile::kHostWin32: a = Attrib; break; + case NHeader::NFile::kHostUnix: + case NHeader::NFile::kHostBeOS: + a = Attrib << 16; + a |= 0x8000; // add posix mode marker + break; + // case NHeader::NFile::kHostMacOS: + // kHostMacOS was used only by some very old rare case rar. + // New rar4-rar7 for macos probably uses kHostUnix. + // So we process kHostMacOS without attribute parsing: default: - a = 0; // must be converted from unix value; + a = 0; } if (IsDir()) - a |= NHeader::NFile::kWinFileDirectoryAttributeMask; + a |= FILE_ATTRIBUTE_DIRECTORY; return a; } @@ -104,20 +120,18 @@ static const char * const kHostOS[] = , "BeOS" }; -static const char *kUnknownOS = "Unknown"; - -static const CUInt32PCharPair k_Flags[] = +static const char * const k_Flags[] = { - { 0, "Volume" }, - { 1, "Comment" }, - { 2, "Lock" }, - { 3, "Solid" }, - { 4, "NewVolName" }, // pack_comment in old versuons - { 5, "Authenticity" }, - { 6, "Recovery" }, - { 7, "BlockEncryption" }, - { 8, "FirstVolume" }, - { 9, "EncryptVer" } + "Volume" + , "Comment" + , "Lock" + , "Solid" + , "NewVolName" // pack_comment in old versuons + , "Authenticity" + , "Recovery" + , "BlockEncryption" + , "FirstVolume" + , "EncryptVer" // 9 }; enum EErrorType @@ -132,14 +146,13 @@ class CInArchive { IInStream *m_Stream; UInt64 m_StreamStartPosition; - CBuffer _unicodeNameBuffer; + UString _unicodeNameBuffer; CByteBuffer _comment; CByteBuffer m_FileHeaderData; NHeader::NBlock::CBlock m_BlockHeader; NCrypto::NRar3::CDecoder *m_RarAESSpec; CMyComPtr m_RarAES; - CBuffer m_DecryptedData; - Byte *m_DecryptedDataAligned; + CAlignedBuffer m_DecryptedDataAligned; UInt32 m_DecryptedDataSize; bool m_CryptoMode; UInt32 m_CryptoPos; @@ -186,35 +199,33 @@ HRESULT CInArchive::Open(IInStream *stream, const UInt64 *searchHeaderSizeLimit) { HeaderErrorWarning = false; m_CryptoMode = false; - RINOK(stream->Seek(0, STREAM_SEEK_CUR, &m_StreamStartPosition)); - RINOK(stream->Seek(0, STREAM_SEEK_END, &ArcInfo.FileSize)); - RINOK(stream->Seek(m_StreamStartPosition, STREAM_SEEK_SET, NULL)); + RINOK(InStream_GetPos_GetSize(stream, m_StreamStartPosition, ArcInfo.FileSize)) m_Position = m_StreamStartPosition; UInt64 arcStartPos = m_StreamStartPosition; { Byte marker[NHeader::kMarkerSize]; - RINOK(ReadStream_FALSE(stream, marker, NHeader::kMarkerSize)); + RINOK(ReadStream_FALSE(stream, marker, NHeader::kMarkerSize)) if (memcmp(marker, kMarker, NHeader::kMarkerSize) == 0) m_Position += NHeader::kMarkerSize; else { if (searchHeaderSizeLimit && *searchHeaderSizeLimit == 0) return S_FALSE; - RINOK(stream->Seek(m_StreamStartPosition, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekSet(stream, m_StreamStartPosition)) RINOK(FindSignatureInStream(stream, kMarker, NHeader::kMarkerSize, - searchHeaderSizeLimit, arcStartPos)); + searchHeaderSizeLimit, arcStartPos)) m_Position = arcStartPos + NHeader::kMarkerSize; - RINOK(stream->Seek(m_Position, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekSet(stream, m_Position)) } } Byte buf[NHeader::NArchive::kArchiveHeaderSize + 1]; - RINOK(ReadStream_FALSE(stream, buf, NHeader::NArchive::kArchiveHeaderSize)); + RINOK(ReadStream_FALSE(stream, buf, NHeader::NArchive::kArchiveHeaderSize)) AddToSeekValue(NHeader::NArchive::kArchiveHeaderSize); - UInt32 blockSize = Get16(buf + 5); + const UInt32 blockSize = Get16(buf + 5); ArcInfo.EncryptVersion = 0; ArcInfo.Flags = Get16(buf + 3); @@ -240,7 +251,7 @@ HRESULT CInArchive::Open(IInStream *stream, const UInt64 *searchHeaderSizeLimit) size_t commentSize = blockSize - headerSize; _comment.Alloc(commentSize); - RINOK(ReadStream_FALSE(stream, _comment, commentSize)); + RINOK(ReadStream_FALSE(stream, _comment, commentSize)) AddToSeekValue(commentSize); m_Stream = stream; ArcInfo.StartPos = arcStartPos; @@ -272,14 +283,19 @@ bool CInArchive::ReadBytesAndTestSize(void *data, UInt32 size) return processed == size; } -static void DecodeUnicodeFileName(const Byte *name, const Byte *encName, + +static unsigned DecodeUnicodeFileName(const Byte *name, const Byte *encName, unsigned encSize, wchar_t *unicodeName, unsigned maxDecSize) { unsigned encPos = 0; unsigned decPos = 0; unsigned flagBits = 0; Byte flags = 0; - Byte highByte = encName[encPos++]; + + if (encPos >= encSize) + return 0; // error + const unsigned highBits = ((unsigned)encName[encPos++]) << 8; + while (encPos < encSize && decPos < maxDecSize) { if (flagBits == 0) @@ -287,40 +303,46 @@ static void DecodeUnicodeFileName(const Byte *name, const Byte *encName, flags = encName[encPos++]; flagBits = 8; } - switch (flags >> 6) + + if (encPos >= encSize) + break; // error + unsigned len = encName[encPos++]; + + flagBits -= 2; + const unsigned mode = (flags >> flagBits) & 3; + + if (mode != 3) { - case 0: - unicodeName[decPos++] = encName[encPos++]; - break; - case 1: - unicodeName[decPos++] = (wchar_t)(encName[encPos++] + (highByte << 8)); - break; - case 2: - unicodeName[decPos++] = (wchar_t)(encName[encPos] + (encName[encPos + 1] << 8)); - encPos += 2; - break; - case 3: - { - unsigned len = encName[encPos++]; - if (len & 0x80) - { - Byte correction = encName[encPos++]; - for (len = (len & 0x7f) + 2; - len > 0 && decPos < maxDecSize; len--, decPos++) - unicodeName[decPos] = (wchar_t)(((name[decPos] + correction) & 0xff) + (highByte << 8)); - } - else - for (len += 2; len > 0 && decPos < maxDecSize; len--, decPos++) - unicodeName[decPos] = name[decPos]; - } - break; + if (mode == 1) + len += highBits; + else if (mode == 2) + { + if (encPos >= encSize) + break; // error + len += ((unsigned)encName[encPos++] << 8); + } + unicodeName[decPos++] = (wchar_t)len; + } + else + { + if (len & 0x80) + { + if (encPos >= encSize) + break; // error + Byte correction = encName[encPos++]; + for (len = (len & 0x7f) + 2; len > 0 && decPos < maxDecSize; len--, decPos++) + unicodeName[decPos] = (wchar_t)(((name[decPos] + correction) & 0xff) + highBits); + } + else + for (len += 2; len > 0 && decPos < maxDecSize; len--, decPos++) + unicodeName[decPos] = name[decPos]; } - flags <<= 2; - flagBits -= 2; } - unicodeName[decPos < maxDecSize ? decPos : maxDecSize - 1] = 0; + + return decPos < maxDecSize ? decPos : maxDecSize - 1; } + void CInArchive::ReadName(const Byte *p, unsigned nameSize, CItem &item) { item.UnicodeName.Empty(); @@ -336,8 +358,8 @@ void CInArchive::ReadName(const Byte *p, unsigned nameSize, CItem &item) { i++; unsigned uNameSizeMax = MyMin(nameSize, (unsigned)0x400); - _unicodeNameBuffer.AllocAtLeast(uNameSizeMax + 1); - DecodeUnicodeFileName(p, p + i, nameSize - i, _unicodeNameBuffer, uNameSizeMax); + unsigned len = DecodeUnicodeFileName(p, p + i, nameSize - i, _unicodeNameBuffer.GetBuf(uNameSizeMax), uNameSizeMax); + _unicodeNameBuffer.ReleaseBuf_SetEnd(len); item.UnicodeName = _unicodeNameBuffer; } else if (!ConvertUTF8ToUnicode(item.Name, item.UnicodeName)) @@ -351,7 +373,7 @@ void CInArchive::ReadName(const Byte *p, unsigned nameSize, CItem &item) static int ReadTime(const Byte *p, unsigned size, Byte mask, CRarTime &rarTime) { rarTime.LowSecond = (Byte)(((mask & 4) != 0) ? 1 : 0); - unsigned numDigits = (mask & 3); + const unsigned numDigits = (mask & 3); rarTime.SubTime[0] = rarTime.SubTime[1] = rarTime.SubTime[2] = 0; @@ -359,7 +381,7 @@ static int ReadTime(const Byte *p, unsigned size, Byte mask, CRarTime &rarTime) return -1; for (unsigned i = 0; i < numDigits; i++) rarTime.SubTime[3 - numDigits + i] = p[i]; - return numDigits; + return (int)numDigits; } #define READ_TIME(_mask_, _ttt_) \ @@ -396,8 +418,8 @@ bool CInArchive::ReadHeaderReal(const Byte *p, unsigned size, CItem &item) item.MTime.LowSecond = 0; item.MTime.SubTime[0] = - item.MTime.SubTime[1] = - item.MTime.SubTime[2] = 0; + item.MTime.SubTime[1] = + item.MTime.SubTime[2] = 0; p += kFileHeaderSize; size -= kFileHeaderSize; @@ -406,6 +428,8 @@ bool CInArchive::ReadHeaderReal(const Byte *p, unsigned size, CItem &item) if (size < 8) return false; item.PackSize |= ((UInt64)Get32(p) << 32); + if (item.PackSize >= ((UInt64)1 << 63)) + return false; item.Size |= ((UInt64)Get32(p + 4) << 32); p += 8; size -= 8; @@ -427,13 +451,13 @@ bool CInArchive::ReadHeaderReal(const Byte *p, unsigned size, CItem &item) size -= sizeof(item.Salt); p += sizeof(item.Salt); } - if (item.Name == "ACL" && size == 0) + if (item.Name.IsEqualTo("ACL") && size == 0) { item.IsAltStream = true; item.Name.Empty(); item.UnicodeName.SetFromAscii(".ACL"); } - else if (item.Name == "STM" && size != 0 && (size & 1) == 0) + else if (item.Name.IsEqualTo("STM") && size != 0 && (size & 1) == 0) { item.IsAltStream = true; item.Name.Empty(); @@ -455,7 +479,7 @@ bool CInArchive::ReadHeaderReal(const Byte *p, unsigned size, CItem &item) for (unsigned i = 0; i < sizeof(item.Salt); i++) item.Salt[i] = p[i]; p += sizeof(item.Salt); - size -= sizeof(item.Salt); + size -= (unsigned)sizeof(item.Salt); } // some rar archives have HasExtTime flag without field. @@ -469,10 +493,10 @@ bool CInArchive::ReadHeaderReal(const Byte *p, unsigned size, CItem &item) Byte cMask = (Byte)(b & 0xF); if ((mMask & 8) != 0) { - READ_TIME(mMask, item.MTime); + READ_TIME(mMask, item.MTime) } - READ_TIME_2(cMask, item.CTimeDefined, item.CTime); - READ_TIME_2(aMask, item.ATimeDefined, item.ATime); + READ_TIME_2(cMask, item.CTimeDefined, item.CTime) + READ_TIME_2(aMask, item.ATimeDefined, item.ATime) } unsigned fileHeaderWithNameSize = 7 + (unsigned)(p - pStart); @@ -497,13 +521,13 @@ HRESULT CInArchive::GetNextItem(CItem &item, ICryptoGetTextPassword *getTextPass error = k_ErrorType_OK; for (;;) { - m_Stream->Seek(m_Position, STREAM_SEEK_SET, NULL); + RINOK(InStream_SeekSet(m_Stream, m_Position)) ArcInfo.EndPos = m_Position; if (!m_CryptoMode && (ArcInfo.Flags & NHeader::NArchive::kBlockHeadersAreEncrypted) != 0) { m_CryptoMode = false; - if (getTextPassword == 0) + if (!getTextPassword) { error = k_ErrorType_DecryptionError; return S_OK; // return S_FALSE; @@ -515,42 +539,48 @@ HRESULT CInArchive::GetNextItem(CItem &item, ICryptoGetTextPassword *getTextPass } // m_RarAESSpec->SetRar350Mode(ArcInfo.IsEncryptOld()); - // Salt - const UInt32 kSaltSize = 8; - Byte salt[kSaltSize]; - if (!ReadBytesAndTestSize(salt, kSaltSize)) - return S_FALSE; - m_Position += kSaltSize; - RINOK(m_RarAESSpec->SetDecoderProperties2(salt, kSaltSize)) - // Password - CMyComBSTR password; - RINOK(getTextPassword->CryptoGetTextPassword(&password)) - unsigned len = 0; - if (password) - len = MyStringLen(password); - if (len > kPasswordLen_MAX) - len = kPasswordLen_MAX; - - CByteArr buffer(len * 2); - for (unsigned i = 0; i < len; i++) { - wchar_t c = password[i]; - ((Byte *)buffer)[i * 2] = (Byte)c; - ((Byte *)buffer)[i * 2 + 1] = (Byte)(c >> 8); + // Salt + const UInt32 kSaltSize = 8; + Byte salt[kSaltSize]; + if (!ReadBytesAndTestSize(salt, kSaltSize)) + return S_FALSE; + m_Position += kSaltSize; + RINOK(m_RarAESSpec->SetDecoderProperties2(salt, kSaltSize)) } - m_RarAESSpec->SetPassword((const Byte *)buffer, len * 2); + { + // Password + CMyComBSTR_Wipe password; + RINOK(getTextPassword->CryptoGetTextPassword(&password)) + unsigned len = 0; + if (password) + len = MyStringLen(password); + if (len > kPasswordLen_MAX) + len = kPasswordLen_MAX; + + CByteBuffer_Wipe buffer(len * 2); + for (unsigned i = 0; i < len; i++) + { + wchar_t c = password[i]; + ((Byte *)buffer)[i * 2] = (Byte)c; + ((Byte *)buffer)[i * 2 + 1] = (Byte)(c >> 8); + } + + m_RarAESSpec->SetPassword((const Byte *)buffer, len * 2); + } const UInt32 kDecryptedBufferSize = (1 << 12); - if (m_DecryptedData.Size() == 0) + if (m_DecryptedDataAligned.Size() == 0) { - const UInt32 kAlign = 16; - m_DecryptedData.Alloc(kDecryptedBufferSize + kAlign); - m_DecryptedDataAligned = (Byte *)((ptrdiff_t)((Byte *)m_DecryptedData + kAlign - 1) & ~(ptrdiff_t)(kAlign - 1)); + // const UInt32 kAlign = 16; + m_DecryptedDataAligned.AllocAtLeast(kDecryptedBufferSize); + if (!m_DecryptedDataAligned.IsAllocated()) + return E_OUTOFMEMORY; } - RINOK(m_RarAES->Init()); + RINOK(m_RarAES->Init()) size_t decryptedDataSizeT = kDecryptedBufferSize; - RINOK(ReadStream(m_Stream, m_DecryptedDataAligned, &decryptedDataSizeT)); + RINOK(ReadStream(m_Stream, m_DecryptedDataAligned, &decryptedDataSizeT)) m_DecryptedDataSize = (UInt32)decryptedDataSizeT; m_DecryptedDataSize = m_RarAES->Filter(m_DecryptedDataAligned, m_DecryptedDataSize); @@ -560,7 +590,7 @@ HRESULT CInArchive::GetNextItem(CItem &item, ICryptoGetTextPassword *getTextPass m_FileHeaderData.AllocAtLeast(7); size_t processed = 7; - RINOK(ReadBytesSpec((Byte *)m_FileHeaderData, &processed)); + RINOK(ReadBytesSpec((Byte *)m_FileHeaderData, &processed)) if (processed != 7) { if (processed != 0) @@ -658,7 +688,8 @@ HRESULT CInArchive::GetNextItem(CItem &item, ICryptoGetTextPassword *getTextPass { if (processed < offset + 2) error = k_ErrorType_Corrupted; - ArcInfo.VolNumber = (UInt32)Get16(m_FileHeaderData + offset); + else + ArcInfo.VolNumber = (UInt32)Get16(m_FileHeaderData + offset); } ArcInfo.EndOfArchive_was_Read = true; @@ -697,7 +728,7 @@ HRESULT CInArchive::GetNextItem(CItem &item, ICryptoGetTextPassword *getTextPass FinishCryptoBlock(); m_CryptoMode = false; // Move Position to compressed Data; - m_Stream->Seek(m_Position, STREAM_SEEK_SET, NULL); + RINOK(InStream_SeekSet(m_Stream, m_Position)) AddToSeekValue(item.PackSize); // m_Position points to next header; // if (okItem) return S_OK; @@ -768,7 +799,9 @@ static const Byte kProps[] = kpidCRC, kpidHostOS, kpidMethod, - kpidUnpackVer + kpidUnpackVer, + + kpidVolumeIndex }; static const Byte kArcProps[] = @@ -808,7 +841,7 @@ bool CHandler::IsSolid(unsigned refIndex) const return item.IsSolid(); } -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; @@ -818,7 +851,7 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) case kpidSolid: prop = _arcInfo.IsSolid(); break; case kpidCharacts: { - AString s = FlagsToString(k_Flags, ARRAY_SIZE(k_Flags), _arcInfo.Flags); + AString s (FlagsToString(k_Flags, Z7_ARRAY_SIZE(k_Flags), _arcInfo.Flags)); // FLAGS_TO_PROP(k_Flags, _arcInfo.Flags, prop); if (_arcInfo.Is_DataCRC_Defined()) { @@ -871,8 +904,7 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) if (/* &_missingVol || */ !_missingVolName.IsEmpty()) { - UString s; - s.SetFromAscii("Missing volume : "); + UString s ("Missing volume : "); s += _missingVolName; prop = s; } @@ -900,13 +932,11 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) { if (_arcInfo.Is_VolNumber_Defined()) { - char sz[16]; - ConvertUInt32ToString((UInt32)_arcInfo.VolNumber + 1, sz); - unsigned len = MyStringLen(sz); - AString s = "part"; - for (; len < 2; len++) + AString s ("part"); + UInt32 v = (UInt32)_arcInfo.VolNumber + 1; + if (v < 10) s += '0'; - s += sz; + s.Add_UInt32(v); s += ".rar"; prop = s; } @@ -918,40 +948,41 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) COM_TRY_END } -STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) { *numItems = _refItems.Size(); return S_OK; } -static bool RarTimeToFileTime(const CRarTime &rarTime, FILETIME &result) +static bool RarTimeToFileTime(const CRarTime &rarTime, FILETIME &ft) { - if (!NTime::DosTimeToFileTime(rarTime.DosTime, result)) + if (!NTime::DosTime_To_FileTime(rarTime.DosTime, ft)) return false; - UInt64 value = (((UInt64)result.dwHighDateTime) << 32) + result.dwLowDateTime; - value += (UInt64)rarTime.LowSecond * 10000000; - value += ((UInt64)rarTime.SubTime[2] << 16) + - ((UInt64)rarTime.SubTime[1] << 8) + - ((UInt64)rarTime.SubTime[0]); - result.dwLowDateTime = (DWORD)value; - result.dwHighDateTime = DWORD(value >> 32); + UInt64 v = (((UInt64)ft.dwHighDateTime) << 32) + ft.dwLowDateTime; + v += (UInt32)rarTime.LowSecond * 10000000; + v += + ((UInt32)rarTime.SubTime[2] << 16) + + ((UInt32)rarTime.SubTime[1] << 8) + + ((UInt32)rarTime.SubTime[0]); + ft.dwLowDateTime = (DWORD)v; + ft.dwHighDateTime = (DWORD)(v >> 32); return true; } static void RarTimeToProp(const CRarTime &rarTime, NCOM::CPropVariant &prop) { - FILETIME localFileTime, utcFileTime; - if (RarTimeToFileTime(rarTime, localFileTime)) - { - if (!LocalFileTimeToFileTime(&localFileTime, &utcFileTime)) - utcFileTime.dwHighDateTime = utcFileTime.dwLowDateTime = 0; - } + FILETIME localFileTime, utc; + if (RarTimeToFileTime(rarTime, localFileTime) + && LocalFileTimeToFileTime(&localFileTime, &utc)) + prop.SetAsTimeFrom_FT_Prec(utc, k_PropVar_TimePrec_100ns); + /* else - utcFileTime.dwHighDateTime = utcFileTime.dwLowDateTime = 0; - prop = utcFileTime; + utc.dwHighDateTime = utc.dwLowDateTime = 0; + // prop.SetAsTimeFrom_FT_Prec(utc, k_PropVar_TimePrec_100ns); + */ } -STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; @@ -974,7 +1005,7 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val u = mainItem->GetName(); u += item.GetName(); */ - prop = (const wchar_t *)NItemName::WinNameToOSName(item.GetName()); + prop = (const wchar_t *)NItemName::WinPathToOsPath(item.GetName()); break; } case kpidIsDir: prop = item.IsDir(); break; @@ -989,6 +1020,12 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val case kpidCommented: prop = item.IsCommented(); break; case kpidSplitBefore: prop = item.IsSplitBefore(); break; case kpidSplitAfter: prop = _items[refItem.ItemIndex + refItem.NumItems - 1].IsSplitAfter(); break; + + case kpidVolumeIndex: + if (_arcInfo.Is_VolNumber_Defined()) + prop = (UInt32)(_arcInfo.VolNumber + refItem.VolumeIndex); + break; + case kpidCRC: { prop = ((lastItem.IsSplitAfter()) ? item.FileCRC : lastItem.FileCRC); @@ -1015,7 +1052,9 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val prop = s; break; } - case kpidHostOS: prop = (item.HostOS < ARRAY_SIZE(kHostOS)) ? kHostOS[item.HostOS] : kUnknownOS; break; + case kpidHostOS: + TYPE_TO_PROP(kHostOS, item.HostOS, prop); + break; } prop.Detach(value); return S_OK; @@ -1061,7 +1100,7 @@ HRESULT CHandler::Open2(IInStream *stream, UString baseName; { NCOM::CPropVariant prop; - RINOK(openVolumeCallback->GetProperty(kpidName, &prop)); + RINOK(openVolumeCallback->GetProperty(kpidName, &prop)) if (prop.vt != VT_BSTR) break; baseName = prop.bstrVal; @@ -1093,16 +1132,15 @@ HRESULT CHandler::Open2(IInStream *stream, else inStream = stream; - UInt64 endPos = 0; - RINOK(inStream->Seek(0, STREAM_SEEK_END, &endPos)); - RINOK(inStream->Seek(0, STREAM_SEEK_SET, NULL)); + UInt64 endPos; + RINOK(InStream_AtBegin_GetSize(inStream, endPos)) if (openCallback) { totalBytes += endPos; - RINOK(openCallback->SetTotal(NULL, &totalBytes)); + RINOK(openCallback->SetTotal(NULL, &totalBytes)) } - RINOK(archive.Open(inStream, maxCheckStartPosition)); + RINOK(archive.Open(inStream, maxCheckStartPosition)) _isArc = true; CItem item; @@ -1131,7 +1169,7 @@ HRESULT CHandler::Open2(IInStream *stream, // AddErrorMessage(errorMessageLoc); } - RINOK(result); + RINOK(result) if (!filled) { @@ -1143,11 +1181,11 @@ HRESULT CHandler::Open2(IInStream *stream, /* if there is recovery record for multivolume archive, RAR adds 18 bytes (ZERO bytes) at the end for alignment. We must skip these bytes to prevent phySize warning. */ - RINOK(inStream->Seek(archive.ArcInfo.EndPos, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekSet(inStream, archive.ArcInfo.EndPos)) bool areThereNonZeros; UInt64 numZeros; const UInt64 maxSize = 1 << 12; - RINOK(ReadZeroTail(inStream, areThereNonZeros, numZeros, maxSize)); + RINOK(ReadZeroTail(inStream, areThereNonZeros, numZeros, maxSize)) if (!areThereNonZeros && numZeros != 0 && numZeros <= maxSize) archive.ArcInfo.EndPos += numZeros; } @@ -1184,7 +1222,7 @@ HRESULT CHandler::Open2(IInStream *stream, { UInt64 numFiles = _items.Size(); UInt64 numBytes = curBytes + item.Position; - RINOK(openCallback->SetCompleted(&numFiles, &numBytes)); + RINOK(openCallback->SetCompleted(&numFiles, &numBytes)) } } @@ -1234,9 +1272,9 @@ HRESULT CHandler::Open2(IInStream *stream, return S_OK; } -STDMETHODIMP CHandler::Open(IInStream *stream, +Z7_COM7F_IMF(CHandler::Open(IInStream *stream, const UInt64 *maxCheckStartPosition, - IArchiveOpenCallback *openCallback) + IArchiveOpenCallback *openCallback)) { COM_TRY_BEGIN Close(); @@ -1255,7 +1293,7 @@ STDMETHODIMP CHandler::Open(IInStream *stream, COM_TRY_END } -STDMETHODIMP CHandler::Close() +Z7_COM7F_IMF(CHandler::Close()) { COM_TRY_BEGIN // _errorMessage.Empty(); @@ -1277,10 +1315,10 @@ struct CMethodItem }; -class CVolsInStream: - public ISequentialInStream, - public CMyUnknownImp -{ +Z7_CLASS_IMP_NOQIB_1( + CVolsInStream + , ISequentialInStream +) UInt64 _rem; ISequentialInStream *_stream; const CObjectVector *_arcs; @@ -1291,10 +1329,6 @@ class CVolsInStream: bool _calcCrc; public: - MY_UNKNOWN_IMP - - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); - void Init(const CObjectVector *arcs, const CObjectVector *items, const CRefItem &refItem) @@ -1311,7 +1345,7 @@ class CVolsInStream: }; -STDMETHODIMP CVolsInStream::Read(void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CVolsInStream::Read(void *data, UInt32 size, UInt32 *processedSize)) { if (processedSize) *processedSize = 0; @@ -1324,8 +1358,14 @@ STDMETHODIMP CVolsInStream::Read(void *data, UInt32 size, UInt32 *processedSize) if (_curIndex >= _refItem.NumItems) break; const CItem &item = (*_items)[_refItem.ItemIndex + _curIndex]; - IInStream *s = (*_arcs)[_refItem.VolumeIndex + _curIndex].Stream; - RINOK(s->Seek(item.GetDataPosition(), STREAM_SEEK_SET, NULL)); + unsigned volIndex = _refItem.VolumeIndex + _curIndex; + if (volIndex >= _arcs->Size()) + { + return S_OK; + // return S_FALSE; + } + IInStream *s = (*_arcs)[volIndex].Stream; + RINOK(InStream_SeekSet(s, item.GetDataPosition())) _stream = s; _calcCrc = (CrcIsOK && item.IsSplitAfter()); _crc = CRC_INIT_VAL; @@ -1365,16 +1405,16 @@ STDMETHODIMP CVolsInStream::Read(void *data, UInt32 size, UInt32 *processedSize) return S_OK; } -STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, - Int32 testMode, IArchiveExtractCallback *extractCallback) +Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testMode, IArchiveExtractCallback *extractCallback)) { COM_TRY_BEGIN CMyComPtr getTextPassword; - UInt64 censoredTotalUnPacked = 0, + UInt64 // censoredTotalUnPacked = 0, // censoredTotalPacked = 0, importantTotalUnPacked = 0; // importantTotalPacked = 0; - bool allFilesMode = (numItems == (UInt32)(Int32)-1); + const bool allFilesMode = (numItems == (UInt32)(Int32)-1); if (allFilesMode) numItems = _refItems.Size(); if (numItems == 0) @@ -1394,7 +1434,9 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, const CItem &item = _items[refItem.ItemIndex + refItem.NumItems - 1]; if (item.Is_Size_Defined()) - censoredTotalUnPacked += item.Size; + { + // censoredTotalUnPacked += item.Size; + } else isThereUndefinedSize = true; @@ -1426,7 +1468,7 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, if (importantTotalUnPacked != 0 || !isThereUndefinedSize) { - RINOK(extractCallback->SetTotal(importantTotalUnPacked)); + RINOK(extractCallback->SetTotal(importantTotalUnPacked)) } UInt64 currentImportantTotalUnPacked = 0; @@ -1462,7 +1504,7 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, { lps->InSize = currentImportantTotalPacked; lps->OutSize = currentImportantTotalUnPacked; - RINOK(lps->SetCur()); + RINOK(lps->SetCur()) if (i >= importantIndexes.Size()) break; @@ -1496,14 +1538,14 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, if (item.IgnoreItem()) continue; - RINOK(extractCallback->GetStream(index, &realOutStream, askMode)); + RINOK(extractCallback->GetStream(index, &realOutStream, askMode)) if (!IsSolid(index)) solidStart = true; if (item.IsDir()) { - RINOK(extractCallback->PrepareOperation(askMode)); - RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)); + RINOK(extractCallback->PrepareOperation(askMode)) + RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)) continue; } @@ -1522,7 +1564,7 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, if (!realOutStream && !testMode) askMode = NExtract::NAskMode::kSkip; - RINOK(extractCallback->PrepareOperation(askMode)); + RINOK(extractCallback->PrepareOperation(askMode)) COutStreamWithCRC *outStreamSpec = new COutStreamWithCRC; CMyComPtr outStream(outStreamSpec); @@ -1562,7 +1604,7 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, RINOK(rar3CryptoDecoder.QueryInterface(IID_ICompressSetDecoderProperties2, &cryptoProperties)); */ - RINOK(rar3CryptoDecoderSpec->SetDecoderProperties2(item.Salt, item.HasSalt() ? sizeof(item.Salt) : 0)); + RINOK(rar3CryptoDecoderSpec->SetDecoderProperties2(item.Salt, item.HasSalt() ? sizeof(item.Salt) : 0)) filterStreamSpec->Filter = rar3CryptoDecoder; } else if (item.UnPackVersion >= 20) @@ -1577,7 +1619,7 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, else { outStream.Release(); - RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kUnsupportedMethod)); + RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kUnsupportedMethod)) continue; } @@ -1589,14 +1631,14 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, if (!getTextPassword) { outStream.Release(); - RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kUnsupportedMethod)); + RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kUnsupportedMethod)) continue; } // if (getTextPassword) { - CMyComBSTR password; - RINOK(getTextPassword->CryptoGetTextPassword(&password)); + CMyComBSTR_Wipe password; + RINOK(getTextPassword->CryptoGetTextPassword(&password)) if (item.UnPackVersion >= 29) { @@ -1605,7 +1647,7 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, len = MyStringLen(password); if (len > kPasswordLen_MAX) len = kPasswordLen_MAX; - CByteArr buffer(len * 2); + CByteBuffer_Wipe buffer(len * 2); for (unsigned k = 0; k < len; k++) { wchar_t c = password[k]; @@ -1616,13 +1658,14 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, } else { - AString oemPassword; + AString_Wipe oemPassword; if (password) { - UString unicode = (LPCOLESTR)password; + UString_Wipe unicode; + unicode.SetFromBstr(password); if (unicode.Len() > kPasswordLen_MAX) unicode.DeleteFrom(kPasswordLen_MAX); - oemPassword = UnicodeStringToMultiByte(unicode, CP_OEMCP); + UnicodeStringToMultiByte2(oemPassword, unicode, CP_OEMCP); } rar20CryptoDecoderSpec->SetPassword((const Byte *)(const char *)oemPassword, oemPassword.Len()); } @@ -1677,13 +1720,13 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, methodID += 2; else methodID += 3; - RINOK(CreateCoder(EXTERNAL_CODECS_VARS methodID, false, mi.Coder)); + RINOK(CreateCoder_Id(EXTERNAL_CODECS_VARS methodID, false, mi.Coder)) } - if (mi.Coder == 0) + if (!mi.Coder) { outStream.Release(); - RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kUnsupportedMethod)); + RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kUnsupportedMethod)) continue; } @@ -1693,7 +1736,7 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, CMyComPtr compressSetDecoderProperties; RINOK(decoder.QueryInterface(IID_ICompressSetDecoderProperties2, - &compressSetDecoderProperties)); + &compressSetDecoderProperties)) Byte isSolid = (Byte)((IsSolid(index) || item.IsSplitBefore()) ? 1: 0); if (solidStart) @@ -1703,14 +1746,14 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, } - RINOK(compressSetDecoderProperties->SetDecoderProperties2(&isSolid, 1)); + RINOK(compressSetDecoderProperties->SetDecoderProperties2(&isSolid, 1)) commonCoder = decoder; break; } default: outStream.Release(); - RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kUnsupportedMethod)); + RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kUnsupportedMethod)) continue; } @@ -1736,7 +1779,7 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, else return result; } - RINOK(extractCallback->SetOperationResult(opRes)); + RINOK(extractCallback->SetOperationResult(opRes)) } return S_OK; @@ -1746,7 +1789,7 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, IMPL_ISetCompressCodecsInfo REGISTER_ARC_I( - "Rar", "rar r00", 0, 3, + "Rar", "rar r00", NULL, 3, kMarker, 0, NArcInfoFlags::kFindSignature, diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Rar/RarHandler.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Rar/RarHandler.h index 901406d4..6a1da4f0 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Rar/RarHandler.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Rar/RarHandler.h @@ -1,7 +1,7 @@ // RarHandler.h -#ifndef __RAR_HANDLER_H -#define __RAR_HANDLER_H +#ifndef ZIP7_INC_RAR_HANDLER_H +#define ZIP7_INC_RAR_HANDLER_H #include "../IArchive.h" @@ -26,7 +26,7 @@ struct CInArcInfo UInt32 DataCRC; bool EndOfArchive_was_Read; - CInArcInfo(): EndFlags(0), EndOfArchive_was_Read(false) {} + CInArcInfo(): EndFlags(0), VolNumber(0), EndOfArchive_was_Read(false) {} UInt64 GetPhySize() const { return EndPos - StartPos; } @@ -67,11 +67,21 @@ struct CRefItem unsigned NumItems; }; -class CHandler: +class CHandler Z7_final: public IInArchive, - PUBLIC_ISetCompressCodecsInfo + Z7_PUBLIC_ISetCompressCodecsInfo_IFEC public CMyUnknownImp { + Z7_COM_QI_BEGIN2(IInArchive) + Z7_COM_QI_ENTRY_ISetCompressCodecsInfo_IFEC + Z7_COM_QI_END + Z7_COM_ADDREF_RELEASE + + Z7_IFACE_COM7_IMP(IInArchive) + DECL_ISetCompressCodecsInfo + + bool _isArc; + CRecordVector _refItems; CObjectVector _items; CObjectVector _arcs; @@ -79,7 +89,6 @@ class CHandler: // AString _errorMessage; UInt32 _errorFlags; UInt32 _warningFlags; - bool _isArc; UString _missingVolName; DECL_EXTERNAL_CODECS_VARS @@ -91,7 +100,7 @@ class CHandler: void AddErrorMessage(const AString &s) { if (!_errorMessage.IsEmpty()) - _errorMessage += '\n'; + _errorMessage.Add_LF(); _errorMessage += s; } */ @@ -99,16 +108,6 @@ class CHandler: HRESULT Open2(IInStream *stream, const UInt64 *maxCheckStartPosition, IArchiveOpenCallback *openCallback); - -public: - MY_QUERYINTERFACE_BEGIN2(IInArchive) - QUERY_ENTRY_ISetCompressCodecsInfo - MY_QUERYINTERFACE_END - MY_ADDREF_RELEASE - - INTERFACE_IInArchive(;) - - DECL_ISetCompressCodecsInfo }; }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Rar/RarHeader.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Rar/RarHeader.h index 30c53ec9..031fea62 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Rar/RarHeader.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Rar/RarHeader.h @@ -1,7 +1,7 @@ // Archive/RarHeader.h -#ifndef __ARCHIVE_RAR_HEADER_H -#define __ARCHIVE_RAR_HEADER_H +#ifndef ZIP7_INC_ARCHIVE_RAR_HEADER_H +#define ZIP7_INC_ARCHIVE_RAR_HEADER_H #include "../../../Common/MyTypes.h" diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Rar/RarItem.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Rar/RarItem.h index 10e21c57..e1028ba4 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Rar/RarItem.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Rar/RarItem.h @@ -1,7 +1,7 @@ // RarItem.h -#ifndef __ARCHIVE_RAR_ITEM_H -#define __ARCHIVE_RAR_ITEM_H +#ifndef ZIP7_INC_ARCHIVE_RAR_ITEM_H +#define ZIP7_INC_ARCHIVE_RAR_ITEM_H #include "../../../Common/StringConvert.h" diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Rar/RarVol.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Rar/RarVol.h index 2d2ce473..9ee8e86c 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Rar/RarVol.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Rar/RarVol.h @@ -1,7 +1,7 @@ // RarVol.h -#ifndef __ARCHIVE_RAR_VOL_H -#define __ARCHIVE_RAR_VOL_H +#ifndef ZIP7_INC_ARCHIVE_RAR_VOL_H +#define ZIP7_INC_ARCHIVE_RAR_VOL_H #include "../../../Common/StringConvert.h" @@ -22,18 +22,18 @@ class CVolumeName UString _changed; UString _after; public: - CVolumeName(): _needChangeForNext(true) {}; + CVolumeName(): _needChangeForNext(true) {} bool InitName(const UString &name, bool newStyle = true) { _needChangeForNext = true; _after.Empty(); - UString base = name; - int dotPos = name.ReverseFind_Dot(); + UString base (name); + const int dotPos = name.ReverseFind_Dot(); if (dotPos >= 0) { - const UString ext = name.Ptr(dotPos + 1); + const UString ext (name.Ptr(dotPos + 1)); if (ext.IsEqualTo_Ascii_NoCase("rar")) { _after = name.Ptr(dotPos); @@ -41,7 +41,7 @@ class CVolumeName } else if (ext.IsEqualTo_Ascii_NoCase("exe")) { - _after.SetFromAscii(".rar"); + _after = ".rar"; base.DeleteFrom(dotPos); } else if (!newStyle) @@ -52,7 +52,7 @@ class CVolumeName ext.IsEqualTo_Ascii_NoCase("r01")) { _changed = ext; - _before = name.Left(dotPos + 1); + _before.SetFrom(name.Ptr(), (unsigned)dotPos + 1); return true; } } @@ -60,24 +60,31 @@ class CVolumeName if (newStyle) { - unsigned i = base.Len(); + unsigned k = base.Len(); + + for (; k != 0; k--) + if (IsDigit(base[k - 1])) + break; + + unsigned i = k; for (; i != 0; i--) if (!IsDigit(base[i - 1])) break; - if (i != base.Len()) + if (i != k) { - _before = base.Left(i); - _changed = base.Ptr(i); + _before.SetFrom(base.Ptr(), i); + _changed.SetFrom(base.Ptr(i), k - i); + _after.Insert(0, base.Ptr(k)); return true; } } _after.Empty(); _before = base; - _before += L'.'; - _changed.SetFromAscii("r00"); + _before.Add_Dot(); + _changed = "r00"; _needChangeForNext = false; return true; } diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Rar/StdAfx.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Rar/StdAfx.h index 2854ff3e..035267cc 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Rar/StdAfx.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Rar/StdAfx.h @@ -1,8 +1,11 @@ // StdAfx.h -#ifndef __STDAFX_H -#define __STDAFX_H +#ifndef ZIP7_INC_STDAFX_H +#define ZIP7_INC_STDAFX_H +#if defined(_MSC_VER) && _MSC_VER >= 1800 +#pragma warning(disable : 4464) // relative include path contains '..' +#endif #include "../../../Common/Common.h" #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/RpmHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/RpmHandler.cpp index 08df1ae7..4f8aaaa0 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/RpmHandler.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/RpmHandler.cpp @@ -11,6 +11,7 @@ #include "../../Common/UTFConvert.h" #include "../../Windows/PropVariant.h" +#include "../../Windows/PropVariantUtils.h" #include "../../Windows/TimeUtils.h" #include "../Common/RegisterArc.h" @@ -18,7 +19,7 @@ #include "HandlerCont.h" -// #define _SHOW_RPM_METADATA +// #define Z7_RPM_SHOW_METADATA using namespace NWindows; @@ -96,7 +97,15 @@ static const char * const k_CPUs[] = , "ppc64" , "sh" , "xtensa" - , "aarch64" // 19 + , "aarch64" // 19 + , "mipsr6" // 20 + , "mips64r6" // 21 + , "riscv64" // 22 + , "loongarch64" // 23 + // , "24" + // , "25" + // , "loongarch64" // 26 : why 23 and 26 for loongarch64? + // 255 for some non specified arch }; static const char * const k_OS[] = @@ -127,8 +136,8 @@ static const char * const k_OS[] = struct CLead { - unsigned char Major; - unsigned char Minor; + Byte Major; + // Byte Minor; UInt16 Type; UInt16 Cpu; UInt16 Os; @@ -139,7 +148,7 @@ struct CLead void Parse(const Byte *p) { Major = p[4]; - Minor = p[5]; + // Minor = p[5]; Type = Get16(p + 6); Cpu= Get16(p + 8); memcpy(Name, p + 10, kNameSize); @@ -167,8 +176,20 @@ struct CEntry } }; -class CHandler: public CHandlerCont + +#ifdef Z7_RPM_SHOW_METADATA +struct CMetaFile { + UInt32 Tag; + UInt32 Offset; + UInt32 Size; +}; +#endif + +Z7_class_CHandler_final: public CHandlerCont +{ + Z7_IFACE_COM7_IMP(IInArchive_Cont) + UInt64 _headersSize; // is equal to start offset of payload data UInt64 _payloadSize; UInt64 _size; @@ -192,22 +213,19 @@ class CHandler: public CHandlerCont AString _os; // linux AString _format; // cpio - AString _compressor; // xz, gzip, bzip2 + AString _compressor; // xz, gzip, bzip2, lzma, zstd CLead _lead; - #ifdef _SHOW_RPM_METADATA + #ifdef Z7_RPM_SHOW_METADATA AString _metadata; + CRecordVector _metaFiles; #endif void SetTime(NCOM::CPropVariant &prop) const { if (_time_Defined && _buildTime != 0) - { - FILETIME ft; - if (NTime::UnixTime64ToFileTime(_buildTime, ft)) - prop = ft; - } + PropVariant_SetFrom_UnixTime(prop, _buildTime); } void SetStringProp(const AString &s, NCOM::CPropVariant &prop) const @@ -226,15 +244,12 @@ class CHandler: public CHandlerCont HRESULT ReadHeader(ISequentialInStream *stream, bool isMainHeader); HRESULT Open2(ISequentialInStream *stream); - virtual int GetItem_ExtractInfo(UInt32 /* index */, UInt64 &pos, UInt64 &size) const + virtual int GetItem_ExtractInfo(UInt32 /* index */, UInt64 &pos, UInt64 &size) const Z7_override { pos = _headersSize; size = _size; return NExtract::NOperationResult::kOK; } - -public: - INTERFACE_IInArchive_Cont(;) }; static const Byte kArcProps[] = @@ -243,7 +258,7 @@ static const Byte kArcProps[] = kpidCpu, kpidHostOS, kpidCTime - #ifdef _SHOW_RPM_METADATA + #ifdef Z7_RPM_SHOW_METADATA , kpidComment #endif }; @@ -266,16 +281,10 @@ void CHandler::AddCPU(AString &s) const { if (_lead.Type == kRpmType_Bin) { - char temp[16]; - const char *p; - if (_lead.Cpu < ARRAY_SIZE(k_CPUs)) - p = k_CPUs[_lead.Cpu]; + if (_lead.Cpu < Z7_ARRAY_SIZE(k_CPUs)) + s += k_CPUs[_lead.Cpu]; else - { - ConvertUInt32ToString(_lead.Cpu, temp); - p = temp; - } - s += p; + s.Add_UInt32(_lead.Cpu); } } } @@ -288,19 +297,19 @@ AString CHandler::GetBaseName() const s = _name; if (!_version.IsEmpty()) { - s += '-'; + s.Add_Minus(); s += _version; } if (!_release.IsEmpty()) { - s += '-'; + s.Add_Minus(); s += _release; } } else s.SetFrom_CalcLen(_lead.Name, kNameSize); - s += '.'; + s.Add_Dot(); if (_lead.Type == kRpmType_Src) s += "src"; else @@ -314,27 +323,31 @@ void CHandler::AddSubFileExtension(AString &res) const res += _format; else res += "cpio"; - res += '.'; + res.Add_Dot(); const char *s; if (!_compressor.IsEmpty()) { s = _compressor; - if (_compressor == "bzip2") + if (_compressor.IsEqualTo("bzip2")) s = "bz2"; - else if (_compressor == "gzip") + else if (_compressor.IsEqualTo("gzip")) s = "gz"; + else if (_compressor.IsEqualTo("zstd")) + s = "zst"; } else { const Byte *p = _payloadSig; - if (p[0] == 0x1F && p[1] == 0x8B) + if (p[0] == 0x1F && p[1] == 0x8B && p[2] == 8) s = "gz"; else if (p[0] == 0xFD && p[1] == '7' && p[2] == 'z' && p[3] == 'X' && p[4] == 'Z' && p[5] == 0) s = "xz"; else if (p[0] == 'B' && p[1] == 'Z' && p[2] == 'h' && p[3] >= '1' && p[3] <= '9') s = "bz2"; + else if (p[0] == 0x28 && p[1] == 0xb5 && p[2] == 0x2f && p[3] == 0xfd) + s = "zst"; else s = "lzma"; } @@ -342,7 +355,7 @@ void CHandler::AddSubFileExtension(AString &res) const res += s; } -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; @@ -376,29 +389,18 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) SetStringProp(_os, prop); else { - char temp[16]; - const char *p; - if (_lead.Os < ARRAY_SIZE(k_OS)) - p = k_OS[_lead.Os]; - else - { - ConvertUInt32ToString(_lead.Os, temp); - p = temp; - } - prop = p; + TYPE_TO_PROP(k_OS, _lead.Os, prop); } break; } - #ifdef _SHOW_RPM_METADATA - case kpidComment: SetStringProp(_metadata, prop); break; + #ifdef Z7_RPM_SHOW_METADATA + // case kpidComment: SetStringProp(_metadata, prop); break; #endif case kpidName: { - AString s = GetBaseName(); - s += ".rpm"; - SetStringProp(s, prop); + SetStringProp(GetBaseName() + ".rpm", prop); break; } } @@ -408,9 +410,10 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) } -STDMETHODIMP CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) { NWindows::NCOM::CPropVariant prop; + if (index == 0) switch (propID) { case kpidSize: @@ -425,8 +428,8 @@ STDMETHODIMP CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIAN case kpidPath: { - AString s = GetBaseName(); - s += '.'; + AString s (GetBaseName()); + s.Add_Dot(); AddSubFileExtension(s); SetStringProp(s, prop); break; @@ -440,16 +443,41 @@ STDMETHODIMP CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIAN } */ } + #ifdef Z7_RPM_SHOW_METADATA + else + { + index--; + if (index > _metaFiles.Size()) + return E_INVALIDARG; + const CMetaFile &meta = _metaFiles[index]; + switch (propID) + { + case kpidSize: + case kpidPackSize: + prop = meta.Size; + break; + + case kpidMTime: + case kpidCTime: + SetTime(prop); + break; + + case kpidPath: + { + AString s ("[META]"); + s.Add_PathSepar(); + s.Add_UInt32(meta.Tag); + prop = s; + break; + } + } + } + #endif + prop.Detach(value); return S_OK; } -#ifdef _SHOW_RPM_METADATA -static inline char GetHex(unsigned value) -{ - return (char)((value < 10) ? ('0' + value) : ('A' + (value - 10))); -} -#endif HRESULT CHandler::ReadHeader(ISequentialInStream *stream, bool isMainHeader) { @@ -457,7 +485,7 @@ HRESULT CHandler::ReadHeader(ISequentialInStream *stream, bool isMainHeader) UInt32 dataLen; { char buf[k_HeaderSig_Size]; - RINOK(ReadStream_FALSE(stream, buf, k_HeaderSig_Size)); + RINOK(ReadStream_FALSE(stream, buf, k_HeaderSig_Size)) if (Get32(buf) != 0x8EADE801) // buf[3] = 0x01 - is version return S_FALSE; // reserved = Get32(buf + 4); @@ -471,7 +499,7 @@ HRESULT CHandler::ReadHeader(ISequentialInStream *stream, bool isMainHeader) if (headerSize < dataLen) return S_FALSE; CByteBuffer buffer(headerSize); - RINOK(ReadStream_FALSE(stream, buffer, headerSize)); + RINOK(ReadStream_FALSE(stream, buffer, headerSize)) for (UInt32 i = 0; i < numEntries; i++) { @@ -497,12 +525,9 @@ HRESULT CHandler::ReadHeader(ISequentialInStream *stream, bool isMainHeader) } else { - #ifdef _SHOW_RPM_METADATA + #ifdef Z7_RPM_SHOW_METADATA { - char temp[16]; - ConvertUInt32ToString(entry.Tag, temp); - - _metadata += temp; + _metadata.Add_UInt32(entry.Tag); _metadata += ": "; } #endif @@ -515,7 +540,7 @@ HRESULT CHandler::ReadHeader(ISequentialInStream *stream, bool isMainHeader) for (j = 0; j < rem && p[j] != 0; j++); if (j == rem) return S_FALSE; - AString s = (const char *)p; + AString s((const char *)p); switch (entry.Tag) { case RPMTAG_NAME: _name = s; break; @@ -527,7 +552,7 @@ HRESULT CHandler::ReadHeader(ISequentialInStream *stream, bool isMainHeader) case RPMTAG_PAYLOADCOMPRESSOR: _compressor = s; break; } - #ifdef _SHOW_RPM_METADATA + #ifdef Z7_RPM_SHOW_METADATA _metadata += s; #endif } @@ -543,19 +568,17 @@ HRESULT CHandler::ReadHeader(ISequentialInStream *stream, bool isMainHeader) _time_Defined = true; } - #ifdef _SHOW_RPM_METADATA + #ifdef Z7_RPM_SHOW_METADATA for (UInt32 t = 0; t < entry.Count; t++) { if (t != 0) _metadata.Add_Space(); - char temp[16]; - ConvertUInt32ToString(Get32(p + t * 4), temp); - _metadata += temp; + _metadata.Add_UInt32(Get32(p + t * 4)); } #endif } - #ifdef _SHOW_RPM_METADATA + #ifdef Z7_RPM_SHOW_METADATA else if ( entry.Type == k_EntryType_STRING_ARRAY || @@ -568,7 +591,7 @@ HRESULT CHandler::ReadHeader(ISequentialInStream *stream, bool isMainHeader) if (rem2 == 0) return S_FALSE; if (t != 0) - _metadata += '\n'; + _metadata.Add_LF(); size_t j; for (j = 0; j < rem2 && p2[j] != 0; j++); if (j == rem2) @@ -587,9 +610,7 @@ HRESULT CHandler::ReadHeader(ISequentialInStream *stream, bool isMainHeader) { if (t != 0) _metadata.Add_Space(); - char temp[16]; - ConvertUInt32ToString(Get16(p + t * 2), temp); - _metadata += temp; + _metadata.Add_UInt32(Get16(p + t * 2)); } } else if (entry.Type == k_EntryType_BIN) @@ -599,17 +620,26 @@ HRESULT CHandler::ReadHeader(ISequentialInStream *stream, bool isMainHeader) for (UInt32 t = 0; t < entry.Count; t++) { const unsigned b = p[t]; - _metadata += GetHex((b >> 4) & 0xF); - _metadata += GetHex(b & 0xF); + _metadata += GET_HEX_CHAR_UPPER(b >> 4); + _metadata += GET_HEX_CHAR_UPPER(b & 0xF); } } else { // p = p; } - _metadata += '\n'; + + _metadata.Add_LF(); #endif } + + #ifdef Z7_RPM_SHOW_METADATA + CMetaFile meta; + meta.Offset = entry.Offset; + meta.Tag = entry.Tag; + meta.Size = entry.Count; + _metaFiles.Add(meta); + #endif } headerSize += k_HeaderSig_Size; @@ -631,7 +661,7 @@ HRESULT CHandler::Open2(ISequentialInStream *stream) { { Byte buf[kLeadSize]; - RINOK(ReadStream_FALSE(stream, buf, kLeadSize)); + RINOK(ReadStream_FALSE(stream, buf, kLeadSize)) if (Get32(buf) != 0xEDABEEDB) return S_FALSE; _lead.Parse(buf); @@ -643,22 +673,22 @@ HRESULT CHandler::Open2(ISequentialInStream *stream) if (_lead.SignatureType == RPMSIG_NONE) { - ; + } else if (_lead.SignatureType == RPMSIG_PGP262_1024) { Byte temp[256]; - RINOK(ReadStream_FALSE(stream, temp, sizeof(temp))); + RINOK(ReadStream_FALSE(stream, temp, sizeof(temp))) } else if (_lead.SignatureType == RPMSIG_HEADERSIG) { - RINOK(ReadHeader(stream, false)); + RINOK(ReadHeader(stream, false)) unsigned pos = (unsigned)_headersSize & 7; if (pos != 0) { Byte temp[8]; unsigned num = 8 - pos; - RINOK(ReadStream_FALSE(stream, temp, num)); + RINOK(ReadStream_FALSE(stream, temp, num)) _headersSize += num; } } @@ -669,20 +699,20 @@ HRESULT CHandler::Open2(ISequentialInStream *stream) } -STDMETHODIMP CHandler::Open(IInStream *inStream, const UInt64 *, IArchiveOpenCallback *) +Z7_COM7F_IMF(CHandler::Open(IInStream *inStream, const UInt64 *, IArchiveOpenCallback *)) { COM_TRY_BEGIN { Close(); - RINOK(Open2(inStream)); + RINOK(Open2(inStream)) // start of payload is allowed to be unaligned - RINOK(ReadStream_FALSE(inStream, _payloadSig, sizeof(_payloadSig))); + RINOK(ReadStream_FALSE(inStream, _payloadSig, sizeof(_payloadSig))) if (!_payloadSize_Defined) { UInt64 endPos; - RINOK(inStream->Seek(0, STREAM_SEEK_END, &endPos)); + RINOK(InStream_GetSize_SeekToEnd(inStream, endPos)) _size = endPos - _headersSize; } _stream = inStream; @@ -691,7 +721,7 @@ STDMETHODIMP CHandler::Open(IInStream *inStream, const UInt64 *, IArchiveOpenCal COM_TRY_END } -STDMETHODIMP CHandler::Close() +Z7_COM7F_IMF(CHandler::Close()) { _headersSize = 0; _payloadSize = 0; @@ -713,24 +743,30 @@ STDMETHODIMP CHandler::Close() _format.Empty(); _compressor.Empty(); - #ifdef _SHOW_RPM_METADATA + #ifdef Z7_RPM_SHOW_METADATA _metadata.Empty(); + _metaFiles.Size(); #endif _stream.Release(); return S_OK; } -STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) { - *numItems = 1; + *numItems = 1 + #ifdef Z7_RPM_SHOW_METADATA + + _metaFiles.Size() + #endif + ; + return S_OK; } static const Byte k_Signature[] = { 0xED, 0xAB, 0xEE, 0xDB}; REGISTER_ARC_I( - "Rpm", "rpm", 0, 0xEB, + "Rpm", "rpm", NULL, 0xEB, k_Signature, 0, 0, diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/SparseHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/SparseHandler.cpp new file mode 100644 index 00000000..70bcb6fc --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/SparseHandler.cpp @@ -0,0 +1,558 @@ +// SparseHandler.cpp + +#include "StdAfx.h" + +#include "../../../C/CpuArch.h" + +#include "../../Common/ComTry.h" + +#include "../../Windows/PropVariantUtils.h" + +#include "../Common/RegisterArc.h" +#include "../Common/StreamUtils.h" + +#include "HandlerCont.h" + +#define Get16(p) GetUi16(p) +#define Get32(p) GetUi32(p) + +#define G16(_offs_, dest) dest = Get16(p + (_offs_)); +#define G32(_offs_, dest) dest = Get32(p + (_offs_)); + +using namespace NWindows; + +namespace NArchive { +namespace NSparse { + +// libsparse and simg2img + +struct CHeader +{ + // UInt32 magic; /* 0xed26ff3a */ + // UInt16 major_version; /* (0x1) - reject images with higher major versions */ + // UInt16 minor_version; /* (0x0) - allow images with higer minor versions */ + UInt16 file_hdr_sz; /* 28 bytes for first revision of the file format */ + UInt16 chunk_hdr_sz; /* 12 bytes for first revision of the file format */ + UInt32 BlockSize; /* block size in bytes, must be a multiple of 4 (4096) */ + UInt32 NumBlocks; /* total blocks in the non-sparse output image */ + UInt32 NumChunks; /* total chunks in the sparse input image */ + // UInt32 image_checksum; /* CRC32 checksum of the original data, counting "don't care" as 0. */ + + void Parse(const Byte *p) + { + // G16 (4, major_version); + // G16 (6, minor_version); + G16 (8, file_hdr_sz) + G16 (10, chunk_hdr_sz) + G32 (12, BlockSize) + G32 (16, NumBlocks) + G32 (20, NumChunks) + // G32 (24, image_checksum); + } +}; + +// #define SPARSE_HEADER_MAGIC 0xed26ff3a + +#define CHUNK_TYPE_RAW 0xCAC1 +#define CHUNK_TYPE_FILL 0xCAC2 +#define CHUNK_TYPE_DONT_CARE 0xCAC3 +#define CHUNK_TYPE_CRC32 0xCAC4 + +#define MY_CHUNK_TYPE_FILL 0 +#define MY_CHUNK_TYPE_DONT_CARE 1 +#define MY_CHUNK_TYPE_RAW_START 2 + +static const char * const g_Methods[] = +{ + "RAW" + , "FILL" + , "SPARSE" // "DONT_CARE" + , "CRC32" +}; + +static const unsigned kFillSize = 4; + +struct CChunk +{ + UInt32 VirtBlock; + Byte Fill [kFillSize]; + UInt64 PhyOffset; + + void Construct() + { + Fill[0] = + Fill[1] = + Fill[2] = + Fill[3] = + 0; + } +}; + +static const Byte k_Signature[] = { 0x3a, 0xff, 0x26, 0xed, 1, 0 }; + + +Z7_class_CHandler_final: public CHandlerImg +{ + Z7_IFACE_COM7_IMP(IInArchive_Img) + + Z7_IFACE_COM7_IMP(IInArchiveGetStream) + Z7_IFACE_COM7_IMP(ISequentialInStream) + + CRecordVector Chunks; + UInt64 _virtSize_fromChunks; + unsigned _blockSizeLog; + UInt32 _chunkIndexPrev; + + UInt64 _packSizeProcessed; + UInt64 _phySize; + UInt32 _methodFlags; + bool _isArc; + bool _headersError; + bool _unexpectedEnd; + // bool _unsupported; + UInt32 NumChunks; // from header + + HRESULT Seek2(UInt64 offset) + { + _posInArc = offset; + return InStream_SeekSet(Stream, offset); + } + + void InitSeekPositions() + { + /* (_virtPos) and (_posInArc) is used only in Read() (that calls ReadPhy()). + So we must reset these variables before first call of Read() */ + Reset_VirtPos(); + Reset_PosInArc(); + _chunkIndexPrev = 0; + _packSizeProcessed = 0; + } + + // virtual functions + bool Init_PackSizeProcessed() Z7_override + { + _packSizeProcessed = 0; + return true; + } + bool Get_PackSizeProcessed(UInt64 &size) Z7_override + { + size = _packSizeProcessed; + return true; + } + + HRESULT Open2(IInStream *stream, IArchiveOpenCallback *openCallback) Z7_override; + HRESULT ReadPhy(UInt64 offset, void *data, UInt32 size, UInt32 &processed); +}; + + + +static const Byte kProps[] = +{ + kpidSize, + kpidPackSize +}; + +static const Byte kArcProps[] = +{ + kpidClusterSize, + kpidNumBlocks, + kpidMethod +}; + +IMP_IInArchive_Props +IMP_IInArchive_ArcProps + +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) +{ + COM_TRY_BEGIN + NCOM::CPropVariant prop; + + switch (propID) + { + case kpidMainSubfile: prop = (UInt32)0; break; + case kpidClusterSize: prop = (UInt32)((UInt32)1 << _blockSizeLog); break; + case kpidNumBlocks: prop = (UInt32)NumChunks; break; + case kpidPhySize: if (_phySize != 0) prop = _phySize; break; + + case kpidMethod: + { + FLAGS_TO_PROP(g_Methods, _methodFlags, prop); + break; + } + + case kpidErrorFlags: + { + UInt32 v = 0; + if (!_isArc) v |= kpv_ErrorFlags_IsNotArc; + if (_headersError) v |= kpv_ErrorFlags_HeadersError; + if (_unexpectedEnd) v |= kpv_ErrorFlags_UnexpectedEnd; + // if (_unsupported) v |= kpv_ErrorFlags_UnsupportedMethod; + if (!Stream && v == 0 && _isArc) + v = kpv_ErrorFlags_HeadersError; + if (v != 0) + prop = v; + break; + } + } + + prop.Detach(value); + return S_OK; + COM_TRY_END +} + + +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIANT *value)) +{ + COM_TRY_BEGIN + NCOM::CPropVariant prop; + + switch (propID) + { + case kpidSize: prop = _size; break; + case kpidPackSize: prop = _phySize; break; + case kpidExtension: prop = (_imgExt ? _imgExt : "img"); break; + } + + prop.Detach(value); + return S_OK; + COM_TRY_END +} + + +static unsigned GetLogSize(UInt32 size) +{ + unsigned k; + for (k = 0; k < 32; k++) + if (((UInt32)1 << k) == size) + return k; + return k; +} + + +HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *openCallback) +{ + const unsigned kHeaderSize = 28; + const unsigned kChunkHeaderSize = 12; + CHeader h; + { + Byte buf[kHeaderSize]; + RINOK(ReadStream_FALSE(stream, buf, kHeaderSize)) + if (memcmp(buf, k_Signature, 6) != 0) + return S_FALSE; + h.Parse(buf); + } + + if (h.file_hdr_sz != kHeaderSize || + h.chunk_hdr_sz != kChunkHeaderSize) + return S_FALSE; + + NumChunks = h.NumChunks; + + const unsigned logSize = GetLogSize(h.BlockSize); + if (logSize < 2 || logSize >= 32) + return S_FALSE; + _blockSizeLog = logSize; + + _size = (UInt64)h.NumBlocks << logSize; + + if (h.NumChunks >= (UInt32)(Int32)-2) // it's our limit + return S_FALSE; + + _isArc = true; + Chunks.Reserve(h.NumChunks + 1); + UInt64 offset = kHeaderSize; + UInt32 virtBlock = 0; + UInt32 i; + + for (i = 0; i < h.NumChunks; i++) + { + { + const UInt32 mask = ((UInt32)1 << 16) - 1; + if ((i & mask) == mask && openCallback) + { + RINOK(openCallback->SetCompleted(NULL, &offset)) + } + } + Byte buf[kChunkHeaderSize]; + { + size_t processed = kChunkHeaderSize; + RINOK(ReadStream(stream, buf, &processed)) + if (kChunkHeaderSize != processed) + { + offset += kChunkHeaderSize; + break; + } + } + const UInt32 type = Get32(&buf[0]); + const UInt32 numBlocks = Get32(&buf[4]); + UInt32 size = Get32(&buf[8]); + + if (type < CHUNK_TYPE_RAW || + type > CHUNK_TYPE_CRC32) + return S_FALSE; + if (size < kChunkHeaderSize) + return S_FALSE; + CChunk c; + c.Construct(); + c.PhyOffset = offset + kChunkHeaderSize; + c.VirtBlock = virtBlock; + offset += size; + size -= kChunkHeaderSize; + _methodFlags |= ((UInt32)1 << (type - CHUNK_TYPE_RAW)); + + if (numBlocks > h.NumBlocks - virtBlock) + return S_FALSE; + + if (type == CHUNK_TYPE_CRC32) + { + // crc chunk must be last chunk (i == h.NumChunks -1); + if (size != kFillSize || numBlocks != 0) + return S_FALSE; + { + size_t processed = kFillSize; + RINOK(ReadStream(stream, c.Fill, &processed)) + if (kFillSize != processed) + break; + } + continue; + } + // else + { + if (numBlocks == 0) + return S_FALSE; + + if (type == CHUNK_TYPE_DONT_CARE) + { + if (size != 0) + return S_FALSE; + c.PhyOffset = MY_CHUNK_TYPE_DONT_CARE; + } + else if (type == CHUNK_TYPE_FILL) + { + if (size != kFillSize) + return S_FALSE; + c.PhyOffset = MY_CHUNK_TYPE_FILL; + size_t processed = kFillSize; + RINOK(ReadStream(stream, c.Fill, &processed)) + if (kFillSize != processed) + break; + } + else if (type == CHUNK_TYPE_RAW) + { + /* Here we require (size == virtSize). + Probably original decoder also requires it. + But maybe size of last chunk can be non-aligned with blockSize ? */ + const UInt32 virtSize = (numBlocks << _blockSizeLog); + if (size != virtSize || numBlocks != (virtSize >> _blockSizeLog)) + return S_FALSE; + } + else + return S_FALSE; + + virtBlock += numBlocks; + Chunks.AddInReserved(c); + if (type == CHUNK_TYPE_RAW) + RINOK(InStream_SeekSet(stream, offset)) + } + } + + if (i != h.NumChunks) + _unexpectedEnd = true; + else if (virtBlock != h.NumBlocks) + _headersError = true; + + _phySize = offset; + + { + CChunk c; + c.Construct(); + c.VirtBlock = virtBlock; + c.PhyOffset = offset; + Chunks.AddInReserved(c); + } + _virtSize_fromChunks = (UInt64)virtBlock << _blockSizeLog; + + Stream = stream; + return S_OK; +} + + +Z7_COM7F_IMF(CHandler::Close()) +{ + Chunks.Clear(); + _isArc = false; + _virtSize_fromChunks = 0; + // _unsupported = false; + _headersError = false; + _unexpectedEnd = false; + _phySize = 0; + _methodFlags = 0; + + _chunkIndexPrev = 0; + _packSizeProcessed = 0; + + // CHandlerImg: + Clear_HandlerImg_Vars(); + Stream.Release(); + return S_OK; +} + + +Z7_COM7F_IMF(CHandler::GetStream(UInt32 /* index */, ISequentialInStream **stream)) +{ + COM_TRY_BEGIN + *stream = NULL; + if (Chunks.Size() < 1) + return S_FALSE; + if (Chunks.Size() < 2 && _virtSize_fromChunks != 0) + return S_FALSE; + // if (_unsupported) return S_FALSE; + InitSeekPositions(); + CMyComPtr streamTemp = this; + *stream = streamTemp.Detach(); + return S_OK; + COM_TRY_END +} + + + +HRESULT CHandler::ReadPhy(UInt64 offset, void *data, UInt32 size, UInt32 &processed) +{ + processed = 0; + if (offset > _phySize || offset + size > _phySize) + { + // we don't expect these cases, if (_phySize) was set correctly. + return S_FALSE; + } + if (offset != _posInArc) + { + const HRESULT res = Seek2(offset); + if (res != S_OK) + { + Reset_PosInArc(); // we don't trust seek_pos in case of error + return res; + } + } + { + size_t size2 = size; + const HRESULT res = ReadStream(Stream, data, &size2); + processed = (UInt32)size2; + _packSizeProcessed += size2; + _posInArc += size2; + if (res != S_OK) + Reset_PosInArc(); // we don't trust seek_pos in case of reading error + return res; + } +} + + +Z7_COM7F_IMF(CHandler::Read(void *data, UInt32 size, UInt32 *processedSize)) +{ + if (processedSize) + *processedSize = 0; + // const unsigned kLimit = (1 << 16) + 1; if (size > kLimit) size = kLimit; // for debug + if (_virtPos >= _virtSize_fromChunks) + return S_OK; + { + const UInt64 rem = _virtSize_fromChunks - _virtPos; + if (size > rem) + size = (UInt32)rem; + if (size == 0) + return S_OK; + } + + UInt32 chunkIndex = _chunkIndexPrev; + if (chunkIndex + 1 >= Chunks.Size()) + return S_FALSE; + { + const UInt32 blockIndex = (UInt32)(_virtPos >> _blockSizeLog); + if (blockIndex < Chunks[chunkIndex ].VirtBlock || + blockIndex >= Chunks[chunkIndex + 1].VirtBlock) + { + unsigned left = 0, right = Chunks.Size() - 1; + for (;;) + { + const unsigned mid = (unsigned)(((size_t)left + (size_t)right) / 2); + if (mid == left) + break; + if (blockIndex < Chunks[mid].VirtBlock) + right = mid; + else + left = mid; + } + chunkIndex = left; + _chunkIndexPrev = chunkIndex; + } + } + + const CChunk &c = Chunks[chunkIndex]; + const UInt64 offset = _virtPos - ((UInt64)c.VirtBlock << _blockSizeLog); + { + const UInt32 numBlocks = Chunks[chunkIndex + 1].VirtBlock - c.VirtBlock; + const UInt64 rem = ((UInt64)numBlocks << _blockSizeLog) - offset; + if (size > rem) + size = (UInt32)rem; + } + + const UInt64 phyOffset = c.PhyOffset; + + if (phyOffset >= MY_CHUNK_TYPE_RAW_START) + { + UInt32 processed = 0; + const HRESULT res = ReadPhy(phyOffset + offset, data, size, processed); + if (processedSize) + *processedSize = processed; + _virtPos += processed; + return res; + } + + Byte b = 0; + + if (phyOffset == MY_CHUNK_TYPE_FILL) + { + const Byte b0 = c.Fill [0]; + const Byte b1 = c.Fill [1]; + const Byte b2 = c.Fill [2]; + const Byte b3 = c.Fill [3]; + if (b0 != b1 || + b0 != b2 || + b0 != b3) + { + if (processedSize) + *processedSize = size; + _virtPos += size; + Byte *dest = (Byte *)data; + while (size >= 4) + { + dest[0] = b0; + dest[1] = b1; + dest[2] = b2; + dest[3] = b3; + dest += 4; + size -= 4; + } + if (size > 0) dest[0] = b0; + if (size > 1) dest[1] = b1; + if (size > 2) dest[2] = b2; + return S_OK; + } + b = b0; + } + else if (phyOffset != MY_CHUNK_TYPE_DONT_CARE) + return S_FALSE; + + memset(data, b, size); + _virtPos += size; + if (processedSize) + *processedSize = size; + return S_OK; +} + +REGISTER_ARC_I( + "Sparse", "simg img", NULL, 0xc2, + k_Signature, + 0, + 0, + NULL) + +}} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/SplitHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/SplitHandler.cpp index 72b52fe7..bf89303b 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/SplitHandler.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/SplitHandler.cpp @@ -9,6 +9,7 @@ #include "../Common/ProgressUtils.h" #include "../Common/RegisterArc.h" +#include "../Common/StreamUtils.h" #include "../Compress/CopyCoder.h" @@ -31,27 +32,22 @@ static const Byte kArcProps[] = kpidTotalPhySize }; -class CHandler: - public IInArchive, - public IInArchiveGetStream, - public CMyUnknownImp -{ + +Z7_CLASS_IMP_CHandler_IInArchive_1( + IInArchiveGetStream +) CObjectVector > _streams; CRecordVector _sizes; UString _subName; UInt64 _totalSize; HRESULT Open2(IInStream *stream, IArchiveOpenCallback *callback); -public: - MY_UNKNOWN_IMP2(IInArchive, IInArchiveGetStream) - INTERFACE_IInArchive(;) - STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream); }; IMP_IInArchive_Props IMP_IInArchive_ArcProps -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { NCOM::CPropVariant prop; switch (propID) @@ -60,6 +56,7 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) case kpidPhySize: if (!_sizes.IsEmpty()) prop = _sizes[0]; break; case kpidTotalPhySize: prop = _totalSize; break; case kpidNumVolumes: prop = (UInt32)_streams.Size(); break; + default: break; } prop.Detach(value); return S_OK; @@ -121,29 +118,31 @@ struct CSeqName } }; + HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *callback) { Close(); if (!callback) return S_FALSE; - CMyComPtr volumeCallback; - callback->QueryInterface(IID_IArchiveOpenVolumeCallback, (void **)&volumeCallback); + Z7_DECL_CMyComPtr_QI_FROM( + IArchiveOpenVolumeCallback, + volumeCallback, callback) if (!volumeCallback) return S_FALSE; UString name; { NCOM::CPropVariant prop; - RINOK(volumeCallback->GetProperty(kpidName, &prop)); + RINOK(volumeCallback->GetProperty(kpidName, &prop)) if (prop.vt != VT_BSTR) return S_FALSE; name = prop.bstrVal; } - int dotPos = name.ReverseFind_Dot(); - const UString prefix = name.Left(dotPos + 1); - const UString ext = name.Ptr(dotPos + 1); + const int dotPos = name.ReverseFind_Dot(); + const UString prefix = name.Left((unsigned)(dotPos + 1)); + const UString ext = name.Ptr((unsigned)(dotPos + 1)); UString ext2 = ext; ext2.MakeLower_Ascii(); @@ -162,7 +161,10 @@ HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *callback) numLetters++; } } - else if (ext.Len() >= 2 && StringsAreEqual_Ascii(ext2.RightPtr(2), "01")) + else if (ext2.Len() >= 2 && ( + StringsAreEqual_Ascii(ext2.RightPtr(2), "01") + || StringsAreEqual_Ascii(ext2.RightPtr(2), "00") + )) { while (numLetters < ext2.Len()) { @@ -170,7 +172,7 @@ HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *callback) break; numLetters++; } - if (numLetters != ext.Len()) + if (numLetters != ext2.Len()) return S_FALSE; } else @@ -181,7 +183,7 @@ HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *callback) seqName._splitStyle = splitStyle; if (prefix.Len() < 1) - _subName.SetFromAscii("file"); + _subName = "file"; else _subName.SetFrom(prefix, prefix.Len() - 1); @@ -189,14 +191,13 @@ HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *callback) { /* NCOM::CPropVariant prop; - RINOK(volumeCallback->GetProperty(kpidSize, &prop)); + RINOK(volumeCallback->GetProperty(kpidSize, &prop)) if (prop.vt != VT_UI8) return E_INVALIDARG; size = prop.uhVal.QuadPart; */ - RINOK(stream->Seek(0, STREAM_SEEK_END, &size)); - RINOK(stream->Seek(0, STREAM_SEEK_SET, NULL)); } + RINOK(InStream_AtBegin_GetSize(stream, size)) _totalSize += size; _sizes.Add(size); @@ -204,7 +205,7 @@ HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *callback) { const UInt64 numFiles = _streams.Size(); - RINOK(callback->SetCompleted(&numFiles, NULL)); + RINOK(callback->SetCompleted(&numFiles, NULL)) } for (;;) @@ -213,30 +214,20 @@ HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *callback) if (!seqName.GetNextName(fullName)) break; CMyComPtr nextStream; - HRESULT result = volumeCallback->GetStream(fullName, &nextStream); + const HRESULT result = volumeCallback->GetStream(fullName, &nextStream); if (result == S_FALSE) break; if (result != S_OK) return result; if (!nextStream) break; - { - /* - NCOM::CPropVariant prop; - RINOK(volumeCallback->GetProperty(kpidSize, &prop)); - if (prop.vt != VT_UI8) - return E_INVALIDARG; - size = prop.uhVal.QuadPart; - */ - RINOK(nextStream->Seek(0, STREAM_SEEK_END, &size)); - RINOK(nextStream->Seek(0, STREAM_SEEK_SET, NULL)); - } + RINOK(InStream_AtBegin_GetSize(nextStream, size)) _totalSize += size; _sizes.Add(size); _streams.Add(nextStream); { const UInt64 numFiles = _streams.Size(); - RINOK(callback->SetCompleted(&numFiles, NULL)); + RINOK(callback->SetCompleted(&numFiles, NULL)) } } @@ -248,17 +239,17 @@ HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *callback) return S_OK; } -STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *callback) +Z7_COM7F_IMF(CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *callback)) { COM_TRY_BEGIN - HRESULT res = Open2(stream, callback); + const HRESULT res = Open2(stream, callback); if (res != S_OK) Close(); return res; COM_TRY_END } -STDMETHODIMP CHandler::Close() +Z7_COM7F_IMF(CHandler::Close()) { _totalSize = 0; _subName.Empty(); @@ -267,13 +258,13 @@ STDMETHODIMP CHandler::Close() return S_OK; } -STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) { *numItems = _streams.IsEmpty() ? 0 : 1; return S_OK; } -STDMETHODIMP CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIANT *value)) { NCOM::CPropVariant prop; switch (propID) @@ -283,13 +274,14 @@ STDMETHODIMP CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIAN case kpidPackSize: prop = _totalSize; break; + default: break; } prop.Detach(value); return S_OK; } -STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, - Int32 testMode, IArchiveExtractCallback *extractCallback) +Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testMode, IArchiveExtractCallback *extractCallback)) { COM_TRY_BEGIN if (numItems == 0) @@ -298,15 +290,15 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, return E_INVALIDARG; UInt64 currentTotalSize = 0; - RINOK(extractCallback->SetTotal(_totalSize)); + RINOK(extractCallback->SetTotal(_totalSize)) CMyComPtr outStream; - Int32 askMode = testMode ? + const Int32 askMode = testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; - RINOK(extractCallback->GetStream(0, &outStream, askMode)); + RINOK(extractCallback->GetStream(0, &outStream, askMode)) if (!testMode && !outStream) return S_OK; - RINOK(extractCallback->PrepareOperation(askMode)); + RINOK(extractCallback->PrepareOperation(askMode)) NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder; CMyComPtr copyCoder = copyCoderSpec; @@ -315,13 +307,15 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, CMyComPtr progress = lps; lps->Init(extractCallback, false); - FOR_VECTOR (i, _streams) + for (unsigned i = 0;; i++) { lps->InSize = lps->OutSize = currentTotalSize; - RINOK(lps->SetCur()); + RINOK(lps->SetCur()) + if (i == _streams.Size()) + break; IInStream *inStream = _streams[i]; - RINOK(inStream->Seek(0, STREAM_SEEK_SET, NULL)); - RINOK(copyCoder->Code(inStream, outStream, NULL, NULL, progress)); + RINOK(InStream_SeekToBegin(inStream)) + RINOK(copyCoder->Code(inStream, outStream, NULL, NULL, progress)) currentTotalSize += copyCoderSpec->TotalSize; } outStream.Release(); @@ -329,12 +323,12 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, COM_TRY_END } -STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream) +Z7_COM7F_IMF(CHandler::GetStream(UInt32 index, ISequentialInStream **stream)) { COM_TRY_BEGIN if (index != 0) return E_INVALIDARG; - *stream = 0; + *stream = NULL; CMultiStream *streamSpec = new CMultiStream; CMyComPtr streamTemp = streamSpec; FOR_VECTOR (i, _streams) @@ -351,7 +345,7 @@ STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream) } REGISTER_ARC_I_NO_SIG( - "Split", "001", 0, 0xEA, + "Split", "001", NULL, 0xEA, 0, 0, NULL) diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/SquashfsHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/SquashfsHandler.cpp index 3bcb2862..83cb1cfd 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/SquashfsHandler.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/SquashfsHandler.cpp @@ -2,15 +2,17 @@ #include "StdAfx.h" -#include "../../../C/7zCrc.h" #include "../../../C/Alloc.h" -#include "../../../C/CpuArch.h" +#include "../../../C/LzmaDec.h" #include "../../../C/Xz.h" +#include "../../../C/ZstdDec.h" +#include "../../../C/CpuArch.h" #include "../../Common/ComTry.h" #include "../../Common/MyLinux.h" #include "../../Common/IntToString.h" #include "../../Common/StringConvert.h" +#include "../../Common/UTFConvert.h" #include "../../Windows/PropVariantUtils.h" #include "../../Windows/TimeUtils.h" @@ -24,13 +26,13 @@ #include "../Compress/CopyCoder.h" #include "../Compress/ZlibDecoder.h" -#include "../Compress/LzmaDecoder.h" +// #include "../Compress/LzmaDecoder.h" namespace NArchive { namespace NSquashfs { -static const UInt32 kNumFilesMax = (1 << 28); -static const unsigned kNumDirLevelsMax = (1 << 10); +static const UInt32 kNumFilesMax = 1 << 28; +static const unsigned kNumDirLevelsMax = 1 << 10; // Layout: Header, Data, inodes, Directories, Fragments, UIDs, GIDs @@ -40,41 +42,46 @@ static const unsigned kNumDirLevelsMax = (1 << 10); #define Get64(p) (be ? GetBe64(p) : GetUi64(p)) */ -UInt16 Get16b(const Byte *p, bool be) { return be ? GetBe16(p) : GetUi16(p); } -UInt32 Get32b(const Byte *p, bool be) { return be ? GetBe32(p) : GetUi32(p); } -UInt64 Get64b(const Byte *p, bool be) { return be ? GetBe64(p) : GetUi64(p); } +static UInt16 Get16b(const Byte *p, bool be) { return be ? GetBe16(p) : GetUi16(p); } +static UInt32 Get32b(const Byte *p, bool be) { return be ? GetBe32(p) : GetUi32(p); } +static UInt64 Get64b(const Byte *p, bool be) { return be ? GetBe64(p) : GetUi64(p); } #define Get16(p) Get16b(p, be) #define Get32(p) Get32b(p, be) #define Get64(p) Get64b(p, be) -#define LE_16(offs, dest) dest = GetUi16(p + (offs)); -#define LE_32(offs, dest) dest = GetUi32(p + (offs)); -#define LE_64(offs, dest) dest = GetUi64(p + (offs)); +#define LE_16(offs, dest) dest = GetUi16(p + (offs)) +#define LE_32(offs, dest) dest = GetUi32(p + (offs)) +#define LE_64(offs, dest) dest = GetUi64(p + (offs)) -#define GET_16(offs, dest) dest = Get16(p + (offs)); -#define GET_32(offs, dest) dest = Get32(p + (offs)); -#define GET_64(offs, dest) dest = Get64(p + (offs)); +#define GET_16(offs, dest) dest = Get16(p + (offs)) +#define GET_32(offs, dest) dest = Get32(p + (offs)) +#define GET_64(offs, dest) dest = Get64(p + (offs)) static const UInt32 kSignature32_LE = 0x73717368; static const UInt32 kSignature32_BE = 0x68737173; static const UInt32 kSignature32_LZ = 0x71736873; +static const UInt32 kSignature32_B2 = 0x73687371; #define kMethod_ZLIB 1 #define kMethod_LZMA 2 #define kMethod_LZO 3 #define kMethod_XZ 4 +// #define kMethod_LZ4 5 +#define kMethod_ZSTD 6 static const char * const k_Methods[] = { - "Unknown" + "0" , "ZLIB" , "LZMA" , "LZO" , "XZ" + , "LZ4" + , "ZSTD" }; -static const UInt32 kMetadataBlockSizeLog = 13; +static const unsigned kMetadataBlockSizeLog = 13; static const UInt32 kMetadataBlockSize = (1 << kMetadataBlockSizeLog); enum @@ -109,28 +116,32 @@ enum kFlag_EXPORT }; -static const CUInt32PCharPair k_Flags[] = +static const char * const k_Flags[] = { - { kFlag_UNC_INODES, "UNCOMPRESSED_INODES" }, - { kFlag_UNC_DATA, "UNCOMPRESSED_DATA" }, - { kFlag_CHECK, "CHECK" }, - { kFlag_UNC_FRAGS, "UNCOMPRESSED_FRAGMENTS" }, - { kFlag_NO_FRAGS, "NO_FRAGMENTS" }, - { kFlag_ALWAYS_FRAG, "ALWAYS_FRAGMENTS" }, - { kFlag_DUPLICATE, "DUPLICATES_REMOVED" }, - { kFlag_EXPORT, "EXPORTABLE" } + "UNCOMPRESSED_INODES" + , "UNCOMPRESSED_DATA" + , "CHECK" + , "UNCOMPRESSED_FRAGMENTS" + , "NO_FRAGMENTS" + , "ALWAYS_FRAGMENTS" + , "DUPLICATES_REMOVED" + , "EXPORTABLE" + , "UNCOMPRESSED_XATTRS" + , "NO_XATTRS" + , "COMPRESSOR_OPTIONS" + , "UNCOMPRESSED_IDS" }; -static const UInt32 kNotCompressedBit16 = (1 << 15); -static const UInt32 kNotCompressedBit32 = (1 << 24); +static const UInt32 kNotCompressedBit16 = 1 << 15; +static const UInt32 kNotCompressedBit32 = 1 << 24; #define GET_COMPRESSED_BLOCK_SIZE(size) ((size) & ~kNotCompressedBit32) #define IS_COMPRESSED_BLOCK(size) (((size) & kNotCompressedBit32) == 0) -static const UInt32 kHeaderSize1 = 0x33; -static const UInt32 kHeaderSize2 = 0x3F; +// static const UInt32 kHeaderSize1 = 0x33; +// static const UInt32 kHeaderSize2 = 0x3F; static const UInt32 kHeaderSize3 = 0x77; -static const UInt32 kHeaderSize4 = 0x60; +// static const UInt32 kHeaderSize4 = 0x60; struct CHeader { @@ -224,6 +235,7 @@ struct CHeader case kSignature32_LE: break; case kSignature32_BE: be = true; break; case kSignature32_LZ: SeveralMethods = true; break; + case kSignature32_B2: SeveralMethods = true; be = true; break; default: return false; } GET_32 (4, NumInodes); @@ -401,7 +413,7 @@ UInt32 CNode::Parse1(const Byte *p, UInt32 size, const CHeader &_h) UInt32 CNode::Parse2(const Byte *p, UInt32 size, const CHeader &_h) { - bool be = _h.be; + const bool be = _h.be; if (size < 4) return 0; { @@ -534,7 +546,7 @@ UInt32 CNode::Parse2(const Byte *p, UInt32 size, const CHeader &_h) UInt32 CNode::Parse3(const Byte *p, UInt32 size, const CHeader &_h) { - bool be = _h.be; + const bool be = _h.be; if (size < 12) return 0; @@ -800,14 +812,14 @@ struct CItem int Parent; UInt32 Ptr; - CItem(): Node(-1), Parent(-1), Ptr(0) {} + void Construct() { Node = -1; Parent = -1; Ptr = 0; } }; struct CData { CByteBuffer Data; CRecordVector PackPos; - CRecordVector UnpackPos; // additional item at the end contains TotalUnpackSize + CRecordVector UnpackPos; UInt32 GetNumBlocks() const { return PackPos.Size(); } void Clear() @@ -824,29 +836,29 @@ struct CFrag UInt32 Size; }; -class CHandler: - public IInArchive, - public IInArchiveGetStream, - public CMyUnknownImp -{ + +Z7_CLASS_IMP_CHandler_IInArchive_1( + IInArchiveGetStream +) + bool _noPropsLZMA; + bool _needCheckLzma; + CRecordVector _items; CRecordVector _nodes; CRecordVector _nodesPos; - CRecordVector _blockToNode; CData _inodesData; CData _dirs; CRecordVector _frags; - // CByteBuffer _uids; - // CByteBuffer _gids; + CByteBuffer _uids; + CByteBuffer _gids; CHeader _h; - bool _noPropsLZMA; - bool _needCheckLzma; - - CMyComPtr _stream; + UInt64 _sizeCalculated; + CMyComPtr _stream; IArchiveOpenCallback *_openCallback; + UInt32 _openCodePage; int _nodeIndex; CRecordVector _blockCompressed; CRecordVector _blockOffsets; @@ -856,25 +868,18 @@ class CHandler: UInt32 _cachedPackBlockSize; UInt32 _cachedUnpackBlockSize; - CLimitedSequentialInStream *_limitedInStreamSpec; - CMyComPtr _limitedInStream; - - CBufPtrSeqOutStream *_outStreamSpec; - CMyComPtr _outStream; + CMyComPtr2_Create _limitedInStream; + CMyComPtr2_Create _outStream; + CMyComPtr2_Create _dynOutStream; - NCompress::NLzma::CDecoder *_lzmaDecoderSpec; - CMyComPtr _lzmaDecoder; - - NCompress::NZlib::CDecoder *_zlibDecoderSpec; - CMyComPtr _zlibDecoder; + // CMyComPtr2 _lzmaDecoder; + CMyComPtr2 _zlibDecoder; CXzUnpacker _xz; + CZstdDecHandle _zstd; CByteBuffer _inputBuffer; - CDynBufSeqOutStream *_dynOutStreamSpec; - CMyComPtr _dynOutStream; - void ClearCache() { _cachedBlockStartPos = 0; @@ -882,44 +887,41 @@ class CHandler: _cachedUnpackBlockSize = 0; } + HRESULT Seek2(UInt64 offset) + { + return InStream_SeekSet(_stream, offset); + } + HRESULT Decompress(ISequentialOutStream *outStream, Byte *outBuf, bool *outBufWasWritten, UInt32 *outBufWasWrittenSize, UInt32 inSize, UInt32 outSizeMax); HRESULT ReadMetadataBlock(UInt32 &packSize); + HRESULT ReadMetadataBlock2(); HRESULT ReadData(CData &data, UInt64 start, UInt64 end); HRESULT OpenDir(int parent, UInt32 startBlock, UInt32 offset, unsigned level, int &nodeIndex); HRESULT ScanInodes(UInt64 ptr); - // HRESULT ReadUids(UInt64 start, UInt32 num, CByteBuffer &ids); + HRESULT ReadUids(UInt64 start, UInt32 num, CByteBuffer &ids); HRESULT Open2(IInStream *inStream); - AString GetPath(int index) const; - bool GetPackSize(int index, UInt64 &res, bool fillOffsets); + AString GetPath(unsigned index) const; + bool GetPackSize(unsigned index, UInt64 &res, bool fillOffsets); public: CHandler(); ~CHandler() { XzUnpacker_Free(&_xz); + if (_zstd) + ZstdDec_Destroy(_zstd); } - MY_UNKNOWN_IMP2(IInArchive, IInArchiveGetStream) - INTERFACE_IInArchive(;) - STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream); - HRESULT ReadBlock(UInt64 blockIndex, Byte *dest, size_t blockSize); }; -CHandler::CHandler() + +CHandler::CHandler(): + _zstd(NULL) { XzUnpacker_Construct(&_xz, &g_Alloc); - - _limitedInStreamSpec = new CLimitedSequentialInStream; - _limitedInStream = _limitedInStreamSpec; - - _outStreamSpec = new CBufPtrSeqOutStream(); - _outStream = _outStreamSpec; - - _dynOutStreamSpec = new CDynBufSeqOutStream; - _dynOutStream = _dynOutStreamSpec; } static const Byte kProps[] = @@ -929,9 +931,9 @@ static const Byte kProps[] = kpidSize, kpidPackSize, kpidMTime, - kpidPosixAttrib - // kpidUser, - // kpidGroup, + kpidPosixAttrib, + kpidUserId, + kpidGroupId // kpidLinks, // kpidOffset }; @@ -944,7 +946,8 @@ static const Byte kArcProps[] = kpidClusterSize, kpidBigEndian, kpidCTime, - kpidCharacts + kpidCharacts, + kpidCodePage // kpidNumBlocks }; @@ -1028,7 +1031,7 @@ static HRESULT LzoDecode(Byte *dest, SizeT *destLen, const Byte *src, SizeT *src } srcRem--; - back = (b >> 2) + (*src++ << 2); + back = (b >> 2) + ((UInt32)*src++ << 2); len = 2; if (mode == 4) { @@ -1070,8 +1073,8 @@ static HRESULT LzoDecode(Byte *dest, SizeT *destLen, const Byte *src, SizeT *src back += ((bOld & 8) << 11); if (back == 0) { - *destLen = dest - destStart; - *srcLen = src - srcStart; + *destLen = (size_t)(dest - destStart); + *srcLen = (size_t)(src - srcStart); return S_OK; } back += (1 << 14) - 1; @@ -1121,16 +1124,16 @@ HRESULT CHandler::Decompress(ISequentialOutStream *outStream, Byte *outBuf, bool if (_h.SeveralMethods) { Byte b; - RINOK(ReadStream_FALSE(_stream, &b, 1)); - RINOK(_stream->Seek(-1, STREAM_SEEK_CUR, NULL)); + RINOK(ReadStream_FALSE(_stream, &b, 1)) + RINOK(_stream->Seek(-1, STREAM_SEEK_CUR, NULL)) method = (b == 0x5D ? kMethod_LZMA : kMethod_ZLIB); } if (method == kMethod_ZLIB && _needCheckLzma) { Byte b; - RINOK(ReadStream_FALSE(_stream, &b, 1)); - RINOK(_stream->Seek(-1, STREAM_SEEK_CUR, NULL)); + RINOK(ReadStream_FALSE(_stream, &b, 1)) + RINOK(_stream->Seek(-1, STREAM_SEEK_CUR, NULL)) if (b == 0) { _noPropsLZMA = true; @@ -1141,23 +1144,16 @@ HRESULT CHandler::Decompress(ISequentialOutStream *outStream, Byte *outBuf, bool if (method == kMethod_ZLIB) { - if (!_zlibDecoder) - { - _zlibDecoderSpec = new NCompress::NZlib::CDecoder(); - _zlibDecoder = _zlibDecoderSpec; - } - RINOK(_zlibDecoder->Code(_limitedInStream, outStream, NULL, NULL, NULL)); - if (inSize != _zlibDecoderSpec->GetInputProcessedSize()) + _zlibDecoder.Create_if_Empty(); + RINOK(_zlibDecoder.Interface()->Code(_limitedInStream, outStream, NULL, NULL, NULL)) + if (inSize != _zlibDecoder->GetInputProcessedSize()) return S_FALSE; } + /* else if (method == kMethod_LZMA) { - if (!_lzmaDecoder) - { - _lzmaDecoderSpec = new NCompress::NLzma::CDecoder(); - _lzmaDecoderSpec->FinishStream = true; - _lzmaDecoder = _lzmaDecoderSpec; - } + _lzmaDecoder.Create_if_Empty(); + // _lzmaDecoder->FinishStream = true; const UInt32 kPropsSize = LZMA_PROPS_SIZE + 8; Byte props[kPropsSize]; UInt32 propsSize; @@ -1182,34 +1178,134 @@ HRESULT CHandler::Decompress(ISequentialOutStream *outStream, Byte *outBuf, bool if (inSize != propsSize + _lzmaDecoderSpec->GetInputProcessedSize()) return S_FALSE; } + */ else { if (_inputBuffer.Size() < inSize) _inputBuffer.Alloc(inSize); - RINOK(ReadStream_FALSE(_stream, _inputBuffer, inSize)); + RINOK(ReadStream_FALSE(_stream, _inputBuffer, inSize)) Byte *dest = outBuf; if (!outBuf) { - dest = _dynOutStreamSpec->GetBufPtrForWriting(outSizeMax); + dest = _dynOutStream->GetBufPtrForWriting(outSizeMax); if (!dest) return E_OUTOFMEMORY; } + SizeT destLen = outSizeMax, srcLen = inSize; + if (method == kMethod_LZO) { - RINOK(LzoDecode(dest, &destLen, _inputBuffer, &srcLen)); + RINOK(LzoDecode(dest, &destLen, _inputBuffer, &srcLen)) + } + else if (method == kMethod_LZMA) + { + Byte props[5]; + const Byte *src = _inputBuffer; + + if (_noPropsLZMA) + { + props[0] = 0x5D; + SetUi32(&props[1], _h.BlockSize) + } + else + { + const UInt32 kPropsSize = LZMA_PROPS_SIZE + 8; + if (inSize < kPropsSize) + return S_FALSE; + memcpy(props, src, LZMA_PROPS_SIZE); + UInt64 outSize = GetUi64(src + LZMA_PROPS_SIZE); + if (outSize > outSizeMax) + return S_FALSE; + destLen = (SizeT)outSize; + src += kPropsSize; + inSize -= kPropsSize; + srcLen = inSize; + } + + ELzmaStatus status; + SRes res = LzmaDecode(dest, &destLen, + src, &srcLen, + props, LZMA_PROPS_SIZE, + LZMA_FINISH_END, + &status, &g_Alloc); + if (res != 0) + return SResToHRESULT(res); + if (status != LZMA_STATUS_FINISHED_WITH_MARK + && status != LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK) + return S_FALSE; + } + else if (method == kMethod_ZSTD) + { + const Byte *src = _inputBuffer; + + if (!_zstd) + { + _zstd = ZstdDec_Create(&g_AlignedAlloc, &g_AlignedAlloc); + if (!_zstd) + return E_OUTOFMEMORY; + } + + CZstdDecState state; + ZstdDecState_Clear(&state); + + state.inBuf = src; + state.inLim = srcLen; // + 1; for debug + // state.outStep = outSizeMax; + + state.outBuf_fromCaller = dest; + state.outBufSize_fromCaller = outSizeMax; + // state.mustBeFinished = True; + + ZstdDec_Init(_zstd); + SRes sres; + for (;;) + { + sres = ZstdDec_Decode(_zstd, &state); + if (sres != SZ_OK) + break; + if (state.inLim == state.inPos + && (state.status == ZSTD_STATUS_NEEDS_MORE_INPUT || + state.status == ZSTD_STATUS_FINISHED_FRAME)) + break; + // sres = sres; + // break; // for debug + } + + CZstdDecResInfo info; + // ZstdDecInfo_Clear(&stat); + // stat->InSize = state.inPos; + ZstdDec_GetResInfo(_zstd, &state, sres, &info); + sres = info.decode_SRes; + if (sres == SZ_OK) + { + if (state.status != ZSTD_STATUS_FINISHED_FRAME + // ||stat.UnexpededEnd + || info.extraSize != 0 + || state.inLim != state.inPos) + sres = SZ_ERROR_DATA; + } + if (sres != SZ_OK) + return SResToHRESULT(sres); + if (state.winPos > outSizeMax) + return E_FAIL; + // memcpy(dest, state.dic, state.dicPos); + destLen = state.winPos; } else { ECoderStatus status; - XzUnpacker_Init(&_xz); - SRes res = XzUnpacker_Code(&_xz, dest, &destLen, _inputBuffer, &srcLen, CODER_FINISH_END, &status); + const SRes res = XzUnpacker_CodeFull(&_xz, + dest, &destLen, + _inputBuffer, &srcLen, + CODER_FINISH_END, &status); if (res != 0) return SResToHRESULT(res); if (status != CODER_STATUS_NEEDS_MORE_INPUT || !XzUnpacker_IsStreamWasFinished(&_xz)) return S_FALSE; } + if (inSize != srcLen) return S_FALSE; if (outBuf) @@ -1218,67 +1314,83 @@ HRESULT CHandler::Decompress(ISequentialOutStream *outStream, Byte *outBuf, bool *outBufWasWrittenSize = (UInt32)destLen; } else - _dynOutStreamSpec->UpdateSize(destLen); + _dynOutStream->UpdateSize(destLen); } return S_OK; } +// in : packSize : allowed packSize limit +// out : packSize : real packSize in block +// out(packSize) <= in(packSize) HRESULT CHandler::ReadMetadataBlock(UInt32 &packSize) { Byte temp[3]; - unsigned offset = _h.NeedCheckData() ? 3 : 2; + const unsigned offset = _h.NeedCheckData() ? 3 : 2; if (offset > packSize) return S_FALSE; - RINOK(ReadStream_FALSE(_stream, temp, offset)); + RINOK(ReadStream_FALSE(_stream, temp, offset)) // if (NeedCheckData && Major < 4) checkByte must be = 0xFF - bool be = _h.be; + const bool be = _h.be; UInt32 size = Get16(temp); - bool isCompressed = ((size & kNotCompressedBit16) == 0); + const bool isCompressed = ((size & kNotCompressedBit16) == 0); if (size != kNotCompressedBit16) size &= ~kNotCompressedBit16; - - if (size > kMetadataBlockSize || offset + size > packSize) + if (size > kMetadataBlockSize) + return S_FALSE; + const UInt32 packSize2 = offset + size; + if (packSize2 > packSize) return S_FALSE; - packSize = offset + size; + packSize = packSize2; if (isCompressed) { - _limitedInStreamSpec->Init(size); - RINOK(Decompress(_dynOutStream, NULL, NULL, NULL, size, kMetadataBlockSize)); + _limitedInStream->Init(size); + RINOK(Decompress(_dynOutStream, NULL, NULL, NULL, size, kMetadataBlockSize)) } else { // size != 0 here - Byte *buf = _dynOutStreamSpec->GetBufPtrForWriting(size); + Byte *buf = _dynOutStream->GetBufPtrForWriting(size); if (!buf) return E_OUTOFMEMORY; - RINOK(ReadStream_FALSE(_stream, buf, size)); - _dynOutStreamSpec->UpdateSize(size); + RINOK(ReadStream_FALSE(_stream, buf, size)) + _dynOutStream->UpdateSize(size); } return S_OK; } + +HRESULT CHandler::ReadMetadataBlock2() +{ + _dynOutStream->Init(); + UInt32 packSize = kMetadataBlockSize + 3; // check it + return ReadMetadataBlock(packSize); +} + HRESULT CHandler::ReadData(CData &data, UInt64 start, UInt64 end) { - if (end < start || end - start >= ((UInt64)1 << 32)) + if (end < start) + return S_FALSE; + const UInt64 size64 = end - start; + if (size64 >= ((UInt64)1 << 32)) return S_FALSE; - UInt32 size = (UInt32)(end - start); - RINOK(_stream->Seek(start, STREAM_SEEK_SET, NULL)); - _dynOutStreamSpec->Init(); + const UInt32 size = (UInt32)size64; + RINOK(Seek2(start)) + _dynOutStream->Init(); UInt32 packPos = 0; while (packPos != size) { data.PackPos.Add(packPos); - data.UnpackPos.Add((UInt32)_dynOutStreamSpec->GetSize()); - if (packPos > size) - return S_FALSE; + data.UnpackPos.Add((UInt32)_dynOutStream->GetSize()); UInt32 packSize = size - packPos; - RINOK(ReadMetadataBlock(packSize)); - if (_dynOutStreamSpec->GetSize() >= ((UInt64)1 << 32)) - return S_FALSE; + RINOK(ReadMetadataBlock(packSize)) + { + const size_t tSize = _dynOutStream->GetSize(); + if (tSize != (UInt32)tSize) + return S_FALSE; + } packPos += packSize; } - data.UnpackPos.Add((UInt32)_dynOutStreamSpec->GetSize()); - _dynOutStreamSpec->CopyToBuffer(data.Data); + _dynOutStream->CopyToBuffer(data.Data); return S_OK; } @@ -1303,14 +1415,15 @@ HRESULT CHandler::OpenDir(int parent, UInt32 startBlock, UInt32 offset, unsigned if (unpackPos < offset) return S_FALSE; - nodeIndex = _nodesPos.FindInSorted(unpackPos, _blockToNode[blockIndex], _blockToNode[blockIndex + 1]); - // nodeIndex = _nodesPos.FindInSorted(unpackPos); + nodeIndex = _nodesPos.FindInSorted(unpackPos); if (nodeIndex < 0) return S_FALSE; const CNode &n = _nodes[nodeIndex]; if (!n.IsDir()) return S_OK; + if ((UInt32)n.StartBlock != n.StartBlock) + return S_FALSE; blockIndex = _dirs.PackPos.FindInSorted((UInt32)n.StartBlock); if (blockIndex < 0) return S_FALSE; @@ -1333,10 +1446,12 @@ HRESULT CHandler::OpenDir(int parent, UInt32 startBlock, UInt32 offset, unsigned return S_FALSE; rem = fileSize; + AString tempString; + CRecordVector tempItems; while (rem != 0) { - bool be = _h.be; + const bool be = _h.be; UInt32 count; CTempItem tempItem; if (_h.Major <= 2) @@ -1382,23 +1497,24 @@ HRESULT CHandler::OpenDir(int parent, UInt32 startBlock, UInt32 offset, unsigned if (rem == 0) return S_FALSE; - UInt32 nameOffset = _h.GetFileNameOffset(); + const UInt32 nameOffset = _h.GetFileNameOffset(); if (rem < nameOffset) return S_FALSE; - if ((UInt32)_items.Size() >= kNumFilesMax) + if (_items.Size() >= kNumFilesMax) return S_FALSE; if (_openCallback) { - UInt64 numFiles = _items.Size(); + const UInt64 numFiles = _items.Size(); if ((numFiles & 0xFFFF) == 0) { - RINOK(_openCallback->SetCompleted(&numFiles, NULL)); + RINOK(_openCallback->SetCompleted(&numFiles, NULL)) } } CItem item; - item.Ptr = (UInt32)(p - _dirs.Data); + item.Construct(); + item.Ptr = (UInt32)(p - (const Byte *)_dirs.Data); UInt32 size; if (_h.IsOldVersion()) @@ -1432,6 +1548,14 @@ HRESULT CHandler::OpenDir(int parent, UInt32 startBlock, UInt32 offset, unsigned size++; if (rem < size) return S_FALSE; + + if (_openCodePage == CP_UTF8) + { + tempString.SetFrom_CalcLen((const char *)p, size); + if (!CheckUTF8_AString(tempString)) + _openCodePage = CP_OEMCP; + } + p += size; rem -= size; item.Parent = parent; @@ -1440,33 +1564,33 @@ HRESULT CHandler::OpenDir(int parent, UInt32 startBlock, UInt32 offset, unsigned } } - int startItemIndex = _items.Size() - tempItems.Size(); + const unsigned startItemIndex = _items.Size() - tempItems.Size(); FOR_VECTOR (i, tempItems) { const CTempItem &tempItem = tempItems[i]; - int index = startItemIndex + i; + const unsigned index = startItemIndex + i; CItem &item = _items[index]; - RINOK(OpenDir(index, tempItem.StartBlock, tempItem.Offset, level + 1, item.Node)); + RINOK(OpenDir((int)index, tempItem.StartBlock, tempItem.Offset, level + 1, item.Node)) } return S_OK; } -/* HRESULT CHandler::ReadUids(UInt64 start, UInt32 num, CByteBuffer &ids) { - size_t size = num * 4; - ids.SetCapacity(size); - RINOK(_stream->Seek(start, STREAM_SEEK_SET, NULL)); + const size_t size = (size_t)num * 4; + ids.Alloc(size); + if (num == 0) + return S_OK; + RINOK(Seek2(start)) return ReadStream_FALSE(_stream, ids, size); } -*/ HRESULT CHandler::Open2(IInStream *inStream) { { Byte buf[kHeaderSize3]; - RINOK(ReadStream_FALSE(inStream, buf, kHeaderSize3)); + RINOK(ReadStream_FALSE(inStream, buf, kHeaderSize3)) if (!_h.Parse(buf)) return S_FALSE; if (!_h.IsSupported()) @@ -1480,6 +1604,7 @@ HRESULT CHandler::Open2(IInStream *inStream) case kMethod_LZMA: case kMethod_LZO: case kMethod_XZ: + case kMethod_ZSTD: break; default: return E_NOTIMPL; @@ -1493,28 +1618,26 @@ HRESULT CHandler::Open2(IInStream *inStream) if (_h.NumFrags > kNumFilesMax) return S_FALSE; _frags.ClearAndReserve(_h.NumFrags); - unsigned bigFrag = (_h.Major > 2); + const unsigned bigFrag = (_h.Major > 2); - unsigned fragPtrsInBlockLog = kMetadataBlockSizeLog - (3 + bigFrag); - UInt32 numBlocks = (_h.NumFrags + (1 << fragPtrsInBlockLog) - 1) >> fragPtrsInBlockLog; - size_t numBlocksBytes = (size_t)numBlocks << (2 + bigFrag); + const unsigned fragPtrsInBlockLog = kMetadataBlockSizeLog - (3 + bigFrag); + const UInt32 numBlocks = (_h.NumFrags + (1 << fragPtrsInBlockLog) - 1) >> fragPtrsInBlockLog; + const size_t numBlocksBytes = (size_t)numBlocks << (2 + bigFrag); CByteBuffer data(numBlocksBytes); - RINOK(inStream->Seek(_h.FragTable, STREAM_SEEK_SET, NULL)); - RINOK(ReadStream_FALSE(inStream, data, numBlocksBytes)); - bool be = _h.be; + RINOK(Seek2(_h.FragTable)) + RINOK(ReadStream_FALSE(inStream, data, numBlocksBytes)) + const bool be = _h.be; for (UInt32 i = 0; i < numBlocks; i++) { - UInt64 offset = bigFrag ? Get64(data + i * 8) : Get32(data + i * 4); - RINOK(_stream->Seek(offset, STREAM_SEEK_SET, NULL)); - _dynOutStreamSpec->Init(); - UInt32 packSize = kMetadataBlockSize + 3; - RINOK(ReadMetadataBlock(packSize)); - UInt32 unpackSize = (UInt32)_dynOutStreamSpec->GetSize(); + const UInt64 offset = bigFrag ? Get64(data + i * 8) : Get32(data + i * 4); + RINOK(Seek2(offset)) + RINOK(ReadMetadataBlock2()) + const UInt32 unpackSize = (UInt32)_dynOutStream->GetSize(); if (unpackSize != kMetadataBlockSize) if (i != numBlocks - 1 || unpackSize != ((_h.NumFrags << (3 + bigFrag)) & (kMetadataBlockSize - 1))) return S_FALSE; - const Byte *buf = _dynOutStreamSpec->GetBuffer(); + const Byte *buf = _dynOutStream->GetBuffer(); for (UInt32 j = 0; j < kMetadataBlockSize && j < unpackSize;) { CFrag frag; @@ -1538,22 +1661,20 @@ HRESULT CHandler::Open2(IInStream *inStream) return S_FALSE; } - // RINOK(inStream->Seek(_h.InodeTable, STREAM_SEEK_SET, NULL)); - - RINOK(ReadData(_inodesData, _h.InodeTable, _h.DirTable)); - RINOK(ReadData(_dirs, _h.DirTable, _h.FragTable)); + RINOK(ReadData(_inodesData, _h.InodeTable, _h.DirTable)) + RINOK(ReadData(_dirs, _h.DirTable, _h.FragTable)) - UInt64 absOffset = _h.RootInode >> 16; + const UInt64 absOffset = _h.RootInode >> 16; if (absOffset >= ((UInt64)1 << 32)) return S_FALSE; { - UInt32 pos = 0; - UInt32 totalSize = (UInt32)_inodesData.Data.Size(); + const UInt32 totalSize = (UInt32)_inodesData.Data.Size(); + const unsigned kMinNodeParseSize = 4; + if (_h.NumInodes > totalSize / kMinNodeParseSize) + return S_FALSE; _nodesPos.ClearAndReserve(_h.NumInodes); _nodes.ClearAndReserve(_h.NumInodes); - // we use _blockToNode for binary search seed optimizations - _blockToNode.ClearAndReserve(_inodesData.GetNumBlocks() + 1); - int curBlock = 0; + UInt32 pos = 0; for (UInt32 i = 0; i < _h.NumInodes; i++) { CNode n; @@ -1569,57 +1690,51 @@ HRESULT CHandler::Open2(IInStream *inStream) } if (size == 0) return S_FALSE; - while (pos >= _inodesData.UnpackPos[curBlock]) - { - _blockToNode.Add(_nodesPos.Size()); - curBlock++; - } _nodesPos.AddInReserved(pos); _nodes.AddInReserved(n); pos += size; } - _blockToNode.Add(_nodesPos.Size()); if (pos != totalSize) return S_FALSE; } int rootNodeIndex; - RINOK(OpenDir(-1, (UInt32)absOffset, (UInt32)_h.RootInode & 0xFFFF, 0, rootNodeIndex)); + RINOK(OpenDir(-1, (UInt32)absOffset, (UInt32)_h.RootInode & 0xFFFF, 0, rootNodeIndex)) - /* if (_h.Major < 4) { - RINOK(ReadUids(_h.UidTable, _h.NumUids, _uids)); - RINOK(ReadUids(_h.GidTable, _h.NumGids, _gids)); + RINOK(ReadUids(_h.UidTable, _h.NumUids, _uids)) + RINOK(ReadUids(_h.GidTable, _h.NumGids, _gids)) } else { - UInt32 size = _h.NumIDs * 4; - _uids.SetCapacity(size); + const UInt32 size = (UInt32)_h.NumIDs * 4; + _uids.Alloc(size); - UInt32 numBlocks = (size + kMetadataBlockSize - 1) / kMetadataBlockSize; - UInt32 numBlocksBytes = numBlocks << 3; - CByteBuffer data; - data.SetCapacity(numBlocksBytes); - RINOK(inStream->Seek(_h.UidTable, STREAM_SEEK_SET, NULL)); - RINOK(ReadStream_FALSE(inStream, data, numBlocksBytes)); + const UInt32 numBlocks = (size + kMetadataBlockSize - 1) / kMetadataBlockSize; + const UInt32 numBlocksBytes = numBlocks << 3; + CByteBuffer data(numBlocksBytes); + RINOK(Seek2(_h.UidTable)) + RINOK(ReadStream_FALSE(inStream, data, numBlocksBytes)) for (UInt32 i = 0; i < numBlocks; i++) { - UInt64 offset = GetUi64(data + i * 8); - UInt32 unpackSize, packSize; - RINOK(_stream->Seek(offset, STREAM_SEEK_SET, NULL)); - RINOK(ReadMetadataBlock(NULL, _uids + kMetadataBlockSize * i, packSize, unpackSize)); - if (unpackSize != kMetadataBlockSize) - if (i != numBlocks - 1 || unpackSize != (size & (kMetadataBlockSize - 1))) - return S_FALSE; + const UInt64 offset = GetUi64(data + i * 8); + RINOK(Seek2(offset)) + // RINOK(ReadMetadataBlock(NULL, _uids + kMetadataBlockSize * i, packSize, unpackSize)); + RINOK(ReadMetadataBlock2()) + const size_t unpackSize = _dynOutStream->GetSize(); + const UInt32 remSize = (i == numBlocks - 1) ? + (size & (kMetadataBlockSize - 1)) : kMetadataBlockSize; + if (unpackSize != remSize) + return S_FALSE; + memcpy(_uids + kMetadataBlockSize * i, _dynOutStream->GetBuffer(), remSize); } } - */ { const UInt32 alignSize = 1 << 12; Byte buf[alignSize]; - RINOK(inStream->Seek(_h.Size, STREAM_SEEK_SET, NULL)); + RINOK(Seek2(_h.Size)) UInt32 rem = (UInt32)(0 - _h.Size) & (alignSize - 1); _sizeCalculated = _h.Size; if (rem != 0) @@ -1636,23 +1751,24 @@ HRESULT CHandler::Open2(IInStream *inStream) return S_OK; } -AString CHandler::GetPath(int index) const +AString CHandler::GetPath(unsigned index) const { unsigned len = 0; - int indexMem = index; - bool be = _h.be; - do + const unsigned indexMem = index; + const bool be = _h.be; + for (;;) { const CItem &item = _items[index]; - index = item.Parent; const Byte *p = _dirs.Data + item.Ptr; - unsigned size = (_h.IsOldVersion() ? (unsigned)p[2] : (unsigned)Get16(p + 6)) + 1; + const unsigned size = (_h.IsOldVersion() ? (unsigned)p[2] : (unsigned)Get16(p + 6)) + 1; p += _h.GetFileNameOffset(); unsigned i; for (i = 0; i < size && p[i]; i++); len += i + 1; + index = (unsigned)item.Parent; + if (item.Parent < 0) + break; } - while (index >= 0); len--; AString path; @@ -1661,27 +1777,27 @@ AString CHandler::GetPath(int index) const for (;;) { const CItem &item = _items[index]; - index = item.Parent; const Byte *p = _dirs.Data + item.Ptr; - unsigned size = (_h.IsOldVersion() ? (unsigned)p[2] : (unsigned)Get16(p + 6)) + 1; + const unsigned size = (_h.IsOldVersion() ? (unsigned)p[2] : (unsigned)Get16(p + 6)) + 1; p += _h.GetFileNameOffset(); unsigned i; for (i = 0; i < size && p[i]; i++); dest -= i; memcpy(dest, p, i); - if (index < 0) + index = (unsigned)item.Parent; + if (item.Parent < 0) break; *(--dest) = CHAR_PATH_SEPARATOR; } return path; } -STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *callback) +Z7_COM7F_IMF(CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *callback)) { COM_TRY_BEGIN { Close(); - _limitedInStreamSpec->SetStream(stream); + _limitedInStream->SetStream(stream); HRESULT res; try { @@ -1704,23 +1820,23 @@ STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallb COM_TRY_END } -STDMETHODIMP CHandler::Close() +Z7_COM7F_IMF(CHandler::Close()) { + _openCodePage = CP_UTF8; _sizeCalculated = 0; - _limitedInStreamSpec->ReleaseStream(); + _limitedInStream->ReleaseStream(); _stream.Release(); _items.Clear(); _nodes.Clear(); _nodesPos.Clear(); - _blockToNode.Clear(); _frags.Clear(); _inodesData.Clear(); _dirs.Clear(); - // _uids.Free(); - // _gids.Free();; + _uids.Free(); + _gids.Free(); _cachedBlock.Free(); ClearCache(); @@ -1728,16 +1844,16 @@ STDMETHODIMP CHandler::Close() return S_OK; } -bool CHandler::GetPackSize(int index, UInt64 &totalPack, bool fillOffsets) +bool CHandler::GetPackSize(unsigned index, UInt64 &totalPack, bool fillOffsets) { totalPack = 0; const CItem &item = _items[index]; const CNode &node = _nodes[item.Node]; - UInt32 ptr = _nodesPos[item.Node]; + const UInt32 ptr = _nodesPos[item.Node]; const Byte *p = _inodesData.Data + ptr; - bool be = _h.be; + const bool be = _h.be; - UInt32 type = node.Type; + const UInt32 type = node.Type; UInt32 offset; if (node.IsLink() || node.FileSize == 0) { @@ -1745,7 +1861,7 @@ bool CHandler::GetPackSize(int index, UInt64 &totalPack, bool fillOffsets) return true; } - UInt32 numBlocks = (UInt32)node.GetNumBlocks(_h); + const UInt32 numBlocks = (UInt32)node.GetNumBlocks(_h); if (fillOffsets) { @@ -1815,13 +1931,13 @@ bool CHandler::GetPackSize(int index, UInt64 &totalPack, bool fillOffsets) } -STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) { *numItems = _items.Size(); return S_OK; } -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NWindows::NCOM::CPropVariant prop; @@ -1829,6 +1945,7 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) { case kpidMethod: { + char sz[16]; const char *s; if (_noPropsLZMA) s = "LZMA Spec"; @@ -1836,25 +1953,27 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) s = "LZMA ZLIB"; else { - s = k_Methods[0]; - if (_h.Method < ARRAY_SIZE(k_Methods)) + s = NULL; + if (_h.Method < Z7_ARRAY_SIZE(k_Methods)) s = k_Methods[_h.Method]; + if (!s) + { + ConvertUInt32ToString(_h.Method, sz); + s = sz; + } } prop = s; break; } case kpidFileSystem: { - AString res = "SquashFS"; + AString res ("SquashFS"); if (_h.SeveralMethods) res += "-LZMA"; res.Add_Space(); - char s[16]; - ConvertUInt32ToString(_h.Major, s); - res += s; - res += '.'; - ConvertUInt32ToString(_h.Minor, s); - res += s; + res.Add_UInt32(_h.Major); + res.Add_Dot(); + res.Add_UInt32(_h.Minor); prop = res; break; } @@ -1862,11 +1981,7 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) case kpidBigEndian: prop = _h.be; break; case kpidCTime: if (_h.CTime != 0) - { - FILETIME ft; - NWindows::NTime::UnixTimeToFileTime(_h.CTime, ft); - prop = ft; - } + PropVariant_SetFrom_UnixTime(prop, _h.CTime); break; case kpidCharacts: FLAGS_TO_PROP(k_Flags, _h.Flags, prop); break; // case kpidNumBlocks: prop = _h.NumFrags; break; @@ -1875,24 +1990,52 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) if (_sizeCalculated >= _h.InodeTable) prop = _sizeCalculated - _h.InodeTable; break; + + case kpidCodePage: + { + char sz[16]; + const char *name = NULL; + switch (_openCodePage) + { + case CP_OEMCP: name = "OEM"; break; + case CP_UTF8: name = "UTF-8"; break; + } + if (!name) + { + ConvertUInt32ToString(_openCodePage, sz); + name = sz; + } + prop = name; + break; + } } prop.Detach(value); return S_OK; COM_TRY_END } -STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NWindows::NCOM::CPropVariant prop; const CItem &item = _items[index]; const CNode &node = _nodes[item.Node]; - bool isDir = node.IsDir(); - bool be = _h.be; + const bool isDir = node.IsDir(); + const bool be = _h.be; switch (propID) { - case kpidPath: prop = MultiByteToUnicodeString(GetPath(index), CP_OEMCP); break; + case kpidPath: + { + AString path (GetPath(index)); + UString s; + if (_openCodePage == CP_UTF8) + ConvertUTF8ToUnicode(path, s); + else + MultiByteToUnicodeString2(s, path, _openCodePage); + prop = s; + break; + } case kpidIsDir: prop = isDir; break; // case kpidOffset: if (!node.IsLink()) prop = (UInt64)node.StartBlock; break; case kpidSize: if (!isDir) prop = node.GetSize(); break; @@ -1929,43 +2072,37 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val if (offset != 0) { const Byte *p = _inodesData.Data + _nodesPos[item.Node] + offset; - FILETIME ft; - NWindows::NTime::UnixTimeToFileTime(Get32(p), ft); - prop = ft; + PropVariant_SetFrom_UnixTime(prop, Get32(p)); } break; } case kpidPosixAttrib: { - if (node.Type != 0 && node.Type < ARRAY_SIZE(k_TypeToMode)) + if (node.Type != 0 && node.Type < Z7_ARRAY_SIZE(k_TypeToMode)) prop = (UInt32)(node.Mode & 0xFFF) | k_TypeToMode[node.Type]; break; } - /* - case kpidUser: + case kpidUserId: + case kpidGroupId: { - UInt32 offset = node.Uid * 4; - if (offset < _uids.Size()) - prop = (UInt32)Get32(_uids + offset); - break; - } - case kpidGroup: - { - if (_h.Major == 4 || node.Gid == _h.GetSpecGuidIndex()) - { - UInt32 offset = node.Uid * 4; - if (offset < _uids.Size()) - prop = (UInt32)Get32(_uids + offset); - } - else + UInt32 id = node.Uid; + const CByteBuffer *ids = &_uids; + if (propID == kpidGroupId) { - UInt32 offset = node.Gid * 4; - if (offset < _gids.Size()) - prop = (UInt32)Get32(_gids + offset); + id = node.Gid; + if (_h.Major < 4) + { + if (id == _h.GetSpecGuidIndex()) + id = node.Uid; + else + ids = &_gids; + } } + const UInt32 offset = (UInt32)id * 4; + if (offset < ids->Size()) + prop = (UInt32)Get32(*ids + offset); break; } - */ /* case kpidLinks: if (_h.Major >= 3 && node.Type != kType_FILE) @@ -1980,7 +2117,7 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val class CSquashfsInStream: public CCachedInStream { - HRESULT ReadBlock(UInt64 blockIndex, Byte *dest, size_t blockSize); + HRESULT ReadBlock(UInt64 blockIndex, Byte *dest, size_t blockSize) Z7_override; public: CHandler *Handler; }; @@ -1999,9 +2136,9 @@ HRESULT CHandler::ReadBlock(UInt64 blockIndex, Byte *dest, size_t blockSize) bool compressed; if (blockIndex < _blockCompressed.Size()) { - compressed = _blockCompressed[(int)blockIndex]; - blockOffset = _blockOffsets[(int)blockIndex]; - packBlockSize = (UInt32)(_blockOffsets[(int)blockIndex + 1] - blockOffset); + compressed = _blockCompressed[(unsigned)blockIndex]; + blockOffset = _blockOffsets[(unsigned)blockIndex]; + packBlockSize = (UInt32)(_blockOffsets[(unsigned)blockIndex + 1] - blockOffset); blockOffset += node.StartBlock; } else @@ -2010,6 +2147,8 @@ HRESULT CHandler::ReadBlock(UInt64 blockIndex, Byte *dest, size_t blockSize) return S_FALSE; const CFrag &frag = _frags[node.Frag]; offsetInBlock = node.Offset; + if (offsetInBlock > _h.BlockSize) + return S_FALSE; blockOffset = frag.StartBlock; packBlockSize = GET_COMPRESSED_BLOCK_SIZE(frag.Size); compressed = IS_COMPRESSED_BLOCK(frag.Size); @@ -2026,41 +2165,45 @@ HRESULT CHandler::ReadBlock(UInt64 blockIndex, Byte *dest, size_t blockSize) packBlockSize != _cachedPackBlockSize) { ClearCache(); - RINOK(_stream->Seek(blockOffset, STREAM_SEEK_SET, NULL)); - _limitedInStreamSpec->Init(packBlockSize); + RINOK(Seek2(blockOffset)) + _limitedInStream->Init(packBlockSize); if (compressed) { - _outStreamSpec->Init((Byte *)_cachedBlock, _h.BlockSize); + _outStream->Init((Byte *)_cachedBlock, _h.BlockSize); bool outBufWasWritten; UInt32 outBufWasWrittenSize; HRESULT res = Decompress(_outStream, _cachedBlock, &outBufWasWritten, &outBufWasWrittenSize, packBlockSize, _h.BlockSize); + RINOK(res) if (outBufWasWritten) _cachedUnpackBlockSize = outBufWasWrittenSize; else - _cachedUnpackBlockSize = (UInt32)_outStreamSpec->GetPos(); - RINOK(res); + _cachedUnpackBlockSize = (UInt32)_outStream->GetPos(); } else { - RINOK(ReadStream_FALSE(_limitedInStream, _cachedBlock, packBlockSize)); + if (packBlockSize > _h.BlockSize) + return S_FALSE; + RINOK(ReadStream_FALSE(_limitedInStream, _cachedBlock, packBlockSize)) _cachedUnpackBlockSize = packBlockSize; } _cachedBlockStartPos = blockOffset; _cachedPackBlockSize = packBlockSize; } - if (offsetInBlock + blockSize > _cachedUnpackBlockSize) + + if (_cachedUnpackBlockSize < offsetInBlock || + _cachedUnpackBlockSize - offsetInBlock < blockSize) return S_FALSE; if (blockSize != 0) memcpy(dest, _cachedBlock + offsetInBlock, blockSize); return S_OK; } -STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, - Int32 testMode, IArchiveExtractCallback *extractCallback) +Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testMode, IArchiveExtractCallback *extractCallback)) { COM_TRY_BEGIN - bool allFilesMode = (numItems == (UInt32)(Int32)-1); + const bool allFilesMode = (numItems == (UInt32)(Int32)-1); if (allFilesMode) numItems = _items.Size(); if (numItems == 0) @@ -2073,40 +2216,42 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, const CNode &node = _nodes[item.Node]; totalSize += node.GetSize(); } - extractCallback->SetTotal(totalSize); + RINOK(extractCallback->SetTotal(totalSize)) UInt64 totalPackSize; totalSize = totalPackSize = 0; - NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder(); - CMyComPtr copyCoder = copyCoderSpec; - - CLocalProgress *lps = new CLocalProgress; - CMyComPtr progress = lps; + CMyComPtr2_Create lps; lps->Init(extractCallback, false); + CMyComPtr2_Create copyCoder; - for (i = 0; i < numItems; i++) + for (i = 0;; i++) { lps->InSize = totalPackSize; lps->OutSize = totalSize; - RINOK(lps->SetCur()); + RINOK(lps->SetCur()) + if (i >= numItems) + break; + + int res; + { CMyComPtr outStream; - Int32 askMode = testMode ? + const Int32 askMode = testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; - UInt32 index = allFilesMode ? i : indices[i]; + const UInt32 index = allFilesMode ? i : indices[i]; const CItem &item = _items[index]; const CNode &node = _nodes[item.Node]; - RINOK(extractCallback->GetStream(index, &outStream, askMode)); + RINOK(extractCallback->GetStream(index, &outStream, askMode)) // const Byte *p = _data + item.Offset; if (node.IsDir()) { - RINOK(extractCallback->PrepareOperation(askMode)); - RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)); + RINOK(extractCallback->PrepareOperation(askMode)) + RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)) continue; } - UInt64 unpackSize = node.GetSize(); + const UInt64 unpackSize = node.GetSize(); totalSize += unpackSize; UInt64 packSize; if (GetPackSize(index, packSize, false)) @@ -2114,9 +2259,9 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, if (!testMode && !outStream) continue; - RINOK(extractCallback->PrepareOperation(askMode)); + RINOK(extractCallback->PrepareOperation(askMode)) - int res = NExtract::NOperationResult::kDataError; + res = NExtract::NOperationResult::kDataError; { CMyComPtr inSeqStream; HRESULT hres = GetStream(index, &inSeqStream); @@ -2128,12 +2273,12 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, } else { - RINOK(hres); + RINOK(hres) { - hres = copyCoder->Code(inSeqStream, outStream, NULL, NULL, progress); + hres = copyCoder.Interface()->Code(inSeqStream, outStream, NULL, NULL, lps); if (hres == S_OK) { - if (copyCoderSpec->TotalSize == unpackSize) + if (copyCoder->TotalSize == unpackSize) res = NExtract::NOperationResult::kOK; } else if (hres == E_NOTIMPL) @@ -2142,13 +2287,13 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, } else if (hres != S_FALSE) { - RINOK(hres); + RINOK(hres) } } } } - - RINOK(extractCallback->SetOperationResult(res)); + } + RINOK(extractCallback->SetOperationResult(res)) } return S_OK; @@ -2156,7 +2301,7 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, } -STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream) +Z7_COM7F_IMF(CHandler::GetStream(UInt32 index, ISequentialInStream **stream)) { COM_TRY_BEGIN @@ -2212,10 +2357,11 @@ STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream) static const Byte k_Signature[] = { 4, 'h', 's', 'q', 's', 4, 's', 'q', 's', 'h', - 4, 's', 'h', 's', 'q' }; + 4, 's', 'h', 's', 'q', + 4, 'q', 's', 'h', 's' }; REGISTER_ARC_I( - "SquashFS", "squashfs", 0, 0xD2, + "SquashFS", "squashfs", NULL, 0xD2, k_Signature, 0, NArcInfoFlags::kMultiSignature, diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/StdAfx.h b/src/plugins/7zip/7za/cpp/7zip/Archive/StdAfx.h index 1cbd7fea..80866550 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/StdAfx.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/StdAfx.h @@ -1,8 +1,11 @@ // StdAfx.h -#ifndef __STDAFX_H -#define __STDAFX_H +#ifndef ZIP7_INC_STDAFX_H +#define ZIP7_INC_STDAFX_H +#if defined(_MSC_VER) && _MSC_VER >= 1800 +#pragma warning(disable : 4464) // relative include path contains '..' +#endif #include "../../Common/Common.h" #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/SwfHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/SwfHandler.cpp index 06ed2106..b143d9a9 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/SwfHandler.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/SwfHandler.cpp @@ -10,6 +10,7 @@ #include "../../Common/MyString.h" #include "../../Windows/PropVariant.h" +#include "../../Windows/PropVariantUtils.h" #include "../Common/InBuffer.h" #include "../Common/LimitedStreams.h" @@ -20,12 +21,20 @@ #include "../Compress/CopyCoder.h" #include "../Compress/LzmaDecoder.h" -#include "../Compress/LzmaEncoder.h" #include "../Compress/ZlibDecoder.h" -#include "../Compress/ZlibEncoder.h" #include "Common/DummyOutStream.h" + +// #define Z7_SWF_UPDATE + +#ifdef Z7_SWF_UPDATE + +#include "../Compress/LzmaEncoder.h" +#include "../Compress/ZlibEncoder.h" + #include "Common/HandlerOut.h" + +#endif using namespace NWindows; @@ -45,7 +54,7 @@ static const Byte SWF_COMPRESSED_LZMA = 'Z'; static const Byte SWF_MIN_COMPRESSED_ZLIB_VER = 6; static const Byte SWF_MIN_COMPRESSED_LZMA_VER = 13; -static const Byte kVerLim = 20; +static const Byte kVerLim = 64; API_FUNC_static_IsArc IsArc_Swf(const Byte *p, size_t size) { @@ -133,7 +142,7 @@ struct CItem Buf[0] = SWF_COMPRESSED_LZMA; if (Buf[3] < SWF_MIN_COMPRESSED_LZMA_VER) Buf[3] = SWF_MIN_COMPRESSED_LZMA_VER; - SetUi32(Buf + 8, packSize); + SetUi32(Buf + 8, packSize) HeaderSize = kHeaderLzmaSize; } @@ -148,29 +157,37 @@ struct CItem } }; -class CHandler: + +Z7_class_CHandler_final: public IInArchive, public IArchiveOpenSeq, + #ifdef Z7_SWF_UPDATE public IOutArchive, public ISetProperties, + #endif public CMyUnknownImp { + #ifdef Z7_SWF_UPDATE + Z7_IFACES_IMP_UNK_4(IInArchive, IArchiveOpenSeq, IOutArchive, ISetProperties) + #else + Z7_IFACES_IMP_UNK_2(IInArchive, IArchiveOpenSeq) + #endif + CItem _item; UInt64 _packSize; bool _packSizeDefined; CMyComPtr _seqStream; CMyComPtr _stream; +#ifdef Z7_SWF_UPDATE CSingleMethodProps _props; bool _lzmaMode; +#endif public: + #ifdef Z7_SWF_UPDATE CHandler(): _lzmaMode(false) {} - MY_UNKNOWN_IMP4(IInArchive, IArchiveOpenSeq, IOutArchive, ISetProperties) - INTERFACE_IInArchive(;) - INTERFACE_IOutArchive(;) - STDMETHOD(OpenSeq)(ISequentialInStream *stream); - STDMETHOD(SetProperties)(const wchar_t * const *names, const PROPVARIANT *values, UInt32 numProps); + #endif }; static const Byte kProps[] = @@ -183,7 +200,7 @@ static const Byte kProps[] = IMP_IInArchive_Props IMP_IInArchive_ArcProps_NO_Table -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { NCOM::CPropVariant prop; switch (propID) @@ -195,7 +212,7 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) return S_OK; } -STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) { *numItems = 1; return S_OK; @@ -205,7 +222,7 @@ static void DicSizeToString(char *s, UInt32 val) { char c = 0; unsigned i; - for (i = 0; i <= 31; i++) + for (i = 0; i < 32; i++) if (((UInt32)1 << i) == val) { val = i; @@ -223,7 +240,7 @@ static void DicSizeToString(char *s, UInt32 val) s[pos] = 0; } -STDMETHODIMP CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIANT *value)) { NWindows::NCOM::CPropVariant prop; switch (propID) @@ -248,22 +265,22 @@ STDMETHODIMP CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIAN return S_OK; } -STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *) +Z7_COM7F_IMF(CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *)) { - RINOK(OpenSeq(stream)); + RINOK(OpenSeq(stream)) _stream = stream; return S_OK; } -STDMETHODIMP CHandler::OpenSeq(ISequentialInStream *stream) +Z7_COM7F_IMF(CHandler::OpenSeq(ISequentialInStream *stream)) { Close(); - RINOK(_item.ReadHeader(stream)); + RINOK(_item.ReadHeader(stream)) if (!_item.IsSwf()) return S_FALSE; if (_item.IsLzma()) { - RINOK(ReadStream_FALSE(stream, _item.Buf + kHeaderBaseSize, kHeaderLzmaSize - kHeaderBaseSize)); + RINOK(ReadStream_FALSE(stream, _item.Buf + kHeaderBaseSize, kHeaderLzmaSize - kHeaderBaseSize)) _item.HeaderSize = kHeaderLzmaSize; _packSize = _item.GetLzmaPackSize(); _packSizeDefined = true; @@ -276,7 +293,7 @@ STDMETHODIMP CHandler::OpenSeq(ISequentialInStream *stream) return S_OK; } -STDMETHODIMP CHandler::Close() +Z7_COM7F_IMF(CHandler::Close()) { _packSize = 0; _packSizeDefined = false; @@ -285,31 +302,29 @@ STDMETHODIMP CHandler::Close() return S_OK; } -class CCompressProgressInfoImp: - public ICompressProgressInfo, - public CMyUnknownImp -{ +Z7_CLASS_IMP_COM_1( + CCompressProgressInfoImp, + ICompressProgressInfo +) CMyComPtr Callback; public: UInt64 Offset; - MY_UNKNOWN_IMP1(ICompressProgressInfo) - STDMETHOD(SetRatioInfo)(const UInt64 *inSize, const UInt64 *outSize); void Init(IArchiveOpenCallback *callback) { Callback = callback; } }; -STDMETHODIMP CCompressProgressInfoImp::SetRatioInfo(const UInt64 *inSize, const UInt64 * /* outSize */) +Z7_COM7F_IMF(CCompressProgressInfoImp::SetRatioInfo(const UInt64 *inSize, const UInt64 * /* outSize */)) { if (Callback) { - UInt64 files = 0; - UInt64 value = Offset + *inSize; + const UInt64 files = 0; + const UInt64 value = Offset + *inSize; return Callback->SetCompleted(&files, &value); } return S_OK; } -STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, - Int32 testMode, IArchiveExtractCallback *extractCallback) +Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testMode, IArchiveExtractCallback *extractCallback)) { COM_TRY_BEGIN if (numItems == 0) @@ -317,42 +332,42 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, if (numItems != (UInt32)(Int32)-1 && (numItems != 1 || indices[0] != 0)) return E_INVALIDARG; - extractCallback->SetTotal(_item.GetSize()); + RINOK(extractCallback->SetTotal(_item.GetSize())) + Int32 opRes; +{ CMyComPtr realOutStream; - Int32 askMode = testMode ? + const Int32 askMode = testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; - RINOK(extractCallback->GetStream(0, &realOutStream, askMode)); + RINOK(extractCallback->GetStream(0, &realOutStream, askMode)) if (!testMode && !realOutStream) return S_OK; - extractCallback->PrepareOperation(askMode); + RINOK(extractCallback->PrepareOperation(askMode)) - CDummyOutStream *outStreamSpec = new CDummyOutStream; - CMyComPtr outStream(outStreamSpec); - outStreamSpec->SetStream(realOutStream); - outStreamSpec->Init(); - realOutStream.Release(); + CMyComPtr2_Create outStream; + outStream->SetStream(realOutStream); + outStream->Init(); + // realOutStream.Release(); - CLocalProgress *lps = new CLocalProgress; - CMyComPtr progress = lps; + CMyComPtr2_Create lps; lps->Init(extractCallback, false); lps->InSize = _item.HeaderSize; - lps->OutSize = outStreamSpec->GetSize(); - RINOK(lps->SetCur()); + lps->OutSize = outStream->GetSize(); + RINOK(lps->SetCur()) CItem item = _item; item.MakeUncompressed(); if (_stream) - RINOK(_stream->Seek(_item.HeaderSize, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekSet(_stream, _item.HeaderSize)) NCompress::NZlib::CDecoder *_decoderZlibSpec = NULL; NCompress::NLzma::CDecoder *_decoderLzmaSpec = NULL; CMyComPtr _decoder; CMyComPtr inStream2; - UInt64 unpackSize = _item.GetSize() - (UInt32)8; + const UInt64 unpackSize = _item.GetSize() - (UInt32)8; if (_item.IsZlib()) { _decoderZlibSpec = new NCompress::NZlib::CDecoder; @@ -383,16 +398,16 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, if (dicSize > (UInt32)unpackSize) { dicSize = (UInt32)unpackSize; - SetUi32(props + 1, dicSize); + SetUi32(props + 1, dicSize) } - RINOK(_decoderLzmaSpec->SetDecoderProperties2(props, 5)); + RINOK(_decoderLzmaSpec->SetDecoderProperties2(props, 5)) } - RINOK(item.WriteHeader(outStream)); - HRESULT result = _decoder->Code(inStream2, outStream, NULL, &unpackSize, progress); - Int32 opRes = NExtract::NOperationResult::kDataError; + RINOK(item.WriteHeader(outStream)) + const HRESULT result = _decoder->Code(inStream2, outStream, NULL, &unpackSize, lps); + opRes = NExtract::NOperationResult::kDataError; if (result == S_OK) { - if (item.GetSize() == outStreamSpec->GetSize()) + if (item.GetSize() == outStream->GetSize()) { if (_item.IsZlib()) { @@ -410,21 +425,25 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, else if (result != S_FALSE) return result; - outStream.Release(); + // outStream.Release(); + } return extractCallback->SetOperationResult(opRes); COM_TRY_END } + +#ifdef Z7_SWF_UPDATE + static HRESULT UpdateArchive(ISequentialOutStream *outStream, UInt64 size, bool lzmaMode, const CSingleMethodProps &props, IArchiveUpdateCallback *updateCallback) { UInt64 complexity = 0; - RINOK(updateCallback->SetTotal(size)); - RINOK(updateCallback->SetCompleted(&complexity)); + RINOK(updateCallback->SetTotal(size)) + RINOK(updateCallback->SetCompleted(&complexity)) CMyComPtr fileInStream; - RINOK(updateCallback->GetStream(0, &fileInStream)); + RINOK(updateCallback->GetStream(0, &fileInStream)) /* CDummyOutStream *outStreamSpec = new CDummyOutStream; @@ -435,10 +454,10 @@ static HRESULT UpdateArchive(ISequentialOutStream *outStream, UInt64 size, */ CItem item; - HRESULT res = item.ReadHeader(fileInStream); + const HRESULT res = item.ReadHeader(fileInStream); if (res == S_FALSE) return E_INVALIDARG; - RINOK(res); + RINOK(res) if (!item.IsSwf() || !item.IsUncompressed() || size != item.GetSize()) return E_INVALIDARG; @@ -453,39 +472,38 @@ static HRESULT UpdateArchive(ISequentialOutStream *outStream, UInt64 size, return E_NOTIMPL; encoderLzmaSpec = new NCompress::NLzma::CEncoder; encoder = encoderLzmaSpec; - RINOK(props.SetCoderProps(encoderLzmaSpec, &size)); + RINOK(props.SetCoderProps(encoderLzmaSpec, &size)) item.MakeLzma((UInt32)0xFFFFFFFF); CBufPtrSeqOutStream *propStreamSpec = new CBufPtrSeqOutStream; CMyComPtr propStream = propStreamSpec; propStreamSpec->Init(item.Buf + 12, 5); - RINOK(encoderLzmaSpec->WriteCoderProperties(propStream)); + RINOK(encoderLzmaSpec->WriteCoderProperties(propStream)) } else { encoderZlibSpec = new NCompress::NZlib::CEncoder; encoder = encoderZlibSpec; encoderZlibSpec->Create(); - RINOK(props.SetCoderProps(encoderZlibSpec->DeflateEncoderSpec, NULL)); + RINOK(props.SetCoderProps(encoderZlibSpec->DeflateEncoderSpec, NULL)) item.MakeZlib(); } - RINOK(item.WriteHeader(outStream)); + RINOK(item.WriteHeader(outStream)) - CLocalProgress *lps = new CLocalProgress; - CMyComPtr progress = lps; + CMyComPtr2_Create lps; lps->Init(updateCallback, true); - RINOK(encoder->Code(fileInStream, outStream, NULL, NULL, progress)); + RINOK(encoder->Code(fileInStream, outStream, NULL, NULL, lps)) UInt64 inputProcessed; if (lzmaMode) { UInt64 curPos = 0; - RINOK(outSeekStream->Seek(0, STREAM_SEEK_CUR, &curPos)); - UInt64 packSize = curPos - kHeaderLzmaSize; + RINOK(outSeekStream->Seek(0, STREAM_SEEK_CUR, &curPos)) + const UInt64 packSize = curPos - kHeaderLzmaSize; if (packSize > (UInt32)0xFFFFFFFF) return E_INVALIDARG; item.MakeLzma((UInt32)packSize); - RINOK(outSeekStream->Seek(0, STREAM_SEEK_SET, NULL)); - item.WriteHeader(outStream); + RINOK(outSeekStream->Seek(0, STREAM_SEEK_SET, NULL)) + RINOK(item.WriteHeader(outStream)) inputProcessed = encoderLzmaSpec->GetInputProcessedSize(); } else @@ -497,14 +515,14 @@ static HRESULT UpdateArchive(ISequentialOutStream *outStream, UInt64 size, return updateCallback->SetOperationResult(NUpdate::NOperationResult::kOK); } -STDMETHODIMP CHandler::GetFileTimeType(UInt32 *timeType) +Z7_COM7F_IMF(CHandler::GetFileTimeType(UInt32 *timeType)) { *timeType = NFileTimeType::kUnix; return S_OK; } -STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numItems, - IArchiveUpdateCallback *updateCallback) +Z7_COM7F_IMF(CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numItems, + IArchiveUpdateCallback *updateCallback)) { if (numItems != 1) return E_INVALIDARG; @@ -513,13 +531,13 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numIt UInt32 indexInArchive; if (!updateCallback) return E_FAIL; - RINOK(updateCallback->GetUpdateItemInfo(0, &newData, &newProps, &indexInArchive)); + RINOK(updateCallback->GetUpdateItemInfo(0, &newData, &newProps, &indexInArchive)) if (IntToBool(newProps)) { { NCOM::CPropVariant prop; - RINOK(updateCallback->GetProperty(0, kpidIsDir, &prop)); + RINOK(updateCallback->GetProperty(0, kpidIsDir, &prop)) if (prop.vt == VT_BOOL) { if (prop.boolVal != VARIANT_FALSE) @@ -535,7 +553,7 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numIt UInt64 size; { NCOM::CPropVariant prop; - RINOK(updateCallback->GetProperty(0, kpidSize, &prop)); + RINOK(updateCallback->GetProperty(0, kpidSize, &prop)) if (prop.vt != VT_UI8) return E_INVALIDARG; size = prop.uhVal.QuadPart; @@ -551,36 +569,38 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numIt if (_stream) { - RINOK(_stream->Seek(0, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekToBegin(_stream)) } else _item.WriteHeader(outStream); return NCompress::CopyStream(_seqStream, outStream, NULL); } -STDMETHODIMP CHandler::SetProperties(const wchar_t * const *names, const PROPVARIANT *values, UInt32 numProps) +Z7_COM7F_IMF(CHandler::SetProperties(const wchar_t * const *names, const PROPVARIANT *values, UInt32 numProps)) { _lzmaMode = false; - RINOK(_props.SetProperties(names, values, numProps)); - AString m = _props.MethodName; - m.MakeLower_Ascii(); - if (m.IsEqualTo("lzma")) + RINOK(_props.SetProperties(names, values, numProps)) + const AString &m = _props.MethodName; + if (m.IsEqualTo_Ascii_NoCase("lzma")) { return E_NOTIMPL; // _lzmaMode = true; } - else if (m.IsEqualTo("deflate") || m.IsEmpty()) + else if (m.IsEqualTo_Ascii_NoCase("Deflate") || m.IsEmpty()) _lzmaMode = false; else return E_INVALIDARG; return S_OK; } +#endif + + static const Byte k_Signature[] = { 3, 'C', 'W', 'S', 3, 'Z', 'W', 'S' }; -REGISTER_ARC_IO( +REGISTER_ARC_I( "SWFc", "swf", "~.swf", 0xD8, k_Signature, 0, @@ -599,22 +619,16 @@ struct CTag CByteBuffer Buf; }; -class CHandler: - public IInArchive, - public IArchiveOpenSeq, - public CMyUnknownImp -{ + +Z7_CLASS_IMP_CHandler_IInArchive_1( + IArchiveOpenSeq +) CObjectVector _tags; NSwfc::CItem _item; UInt64 _phySize; HRESULT OpenSeq3(ISequentialInStream *stream, IArchiveOpenCallback *callback); HRESULT OpenSeq2(ISequentialInStream *stream, IArchiveOpenCallback *callback); -public: - MY_UNKNOWN_IMP2(IInArchive, IArchiveOpenSeq) - INTERFACE_IInArchive(;) - - STDMETHOD(OpenSeq)(ISequentialInStream *stream); }; static const Byte kProps[] = @@ -627,7 +641,7 @@ static const Byte kProps[] = IMP_IInArchive_Props IMP_IInArchive_ArcProps_NO_Table -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { NCOM::CPropVariant prop; switch (propID) @@ -640,7 +654,7 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) } -STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) { *numItems = _tags.Size(); return S_OK; @@ -742,7 +756,7 @@ static const char * const g_TagDesc[92] = , "DefineFont4" }; -STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) { NWindows::NCOM::CPropVariant prop; const CTag &tag = _tags[index]; @@ -762,40 +776,35 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val case kpidPackSize: prop = (UInt64)tag.Buf.Size(); break; case kpidComment: - if (tag.Type < ARRAY_SIZE(g_TagDesc)) - { - const char *s = g_TagDesc[tag.Type]; - if (s != NULL) - prop = s; - } + TYPE_TO_PROP(g_TagDesc, tag.Type, prop); break; } prop.Detach(value); return S_OK; } -STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *callback) +Z7_COM7F_IMF(CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *callback)) { return OpenSeq2(stream, callback); } static UInt16 Read16(CInBuffer &stream) { - UInt16 res = 0; - for (int i = 0; i < 2; i++) + UInt32 res = 0; + for (unsigned i = 0; i < 2; i++) { Byte b; if (!stream.ReadByte(b)) throw 1; - res |= (UInt16)b << (i * 8); + res |= (UInt32)b << (i * 8); } - return res; + return (UInt16)res; } static UInt32 Read32(CInBuffer &stream) { UInt32 res = 0; - for (int i = 0; i < 4; i++) + for (unsigned i = 0; i < 4; i++) { Byte b; if (!stream.ReadByte(b)) @@ -831,7 +840,7 @@ UInt32 CBitReader::ReadBits(unsigned numBits) res <<= numBits; NumBits -= numBits; res |= (Val >> NumBits); - Val &= (1 << NumBits) - 1; + Val = (Byte)(Val & (((unsigned)1 << NumBits) - 1)); break; } else @@ -850,7 +859,7 @@ HRESULT CHandler::OpenSeq3(ISequentialInStream *stream, IArchiveOpenCallback *ca RINOK(_item.ReadHeader(stream)) if (!_item.IsSwf() || !_item.IsUncompressed()) return S_FALSE; - UInt32 uncompressedSize = _item.GetSize(); + const UInt32 uncompressedSize = _item.GetSize(); if (uncompressedSize > kFileSizeMax) return S_FALSE; @@ -863,7 +872,7 @@ HRESULT CHandler::OpenSeq3(ISequentialInStream *stream, IArchiveOpenCallback *ca { CBitReader br; br.stream = &s; - unsigned numBits = br.ReadBits(5); + const unsigned numBits = br.ReadBits(5); /* UInt32 xMin = */ br.ReadBits(numBits); /* UInt32 xMax = */ br.ReadBits(numBits); /* UInt32 yMin = */ br.ReadBits(numBits); @@ -876,14 +885,14 @@ HRESULT CHandler::OpenSeq3(ISequentialInStream *stream, IArchiveOpenCallback *ca UInt64 offsetPrev = 0; for (;;) { - UInt32 pair = Read16(s); - UInt32 type = pair >> 6; + const UInt32 pair = Read16(s); + const UInt32 type = pair >> 6; UInt32 length = pair & 0x3F; if (length == 0x3F) length = Read32(s); if (type == 0) break; - UInt64 offset = s.GetProcessedSize() + NSwfc::kHeaderBaseSize + length; + const UInt64 offset = s.GetProcessedSize() + NSwfc::kHeaderBaseSize + length; if (offset > uncompressedSize || _tags.Size() >= kNumTagsMax) return S_FALSE; CTag &tag = _tags.AddNew(); @@ -893,8 +902,8 @@ HRESULT CHandler::OpenSeq3(ISequentialInStream *stream, IArchiveOpenCallback *ca return S_FALSE; if (callback && offset >= offsetPrev + (1 << 20)) { - UInt64 numItems = _tags.Size(); - RINOK(callback->SetCompleted(&numItems, &offset)); + const UInt64 numItems = _tags.Size(); + RINOK(callback->SetCompleted(&numItems, &offset)) offsetPrev = offset; } } @@ -915,22 +924,22 @@ HRESULT CHandler::OpenSeq2(ISequentialInStream *stream, IArchiveOpenCallback *ca return res; } -STDMETHODIMP CHandler::OpenSeq(ISequentialInStream *stream) +Z7_COM7F_IMF(CHandler::OpenSeq(ISequentialInStream *stream)) { return OpenSeq2(stream, NULL); } -STDMETHODIMP CHandler::Close() +Z7_COM7F_IMF(CHandler::Close()) { _phySize = 0; return S_OK; } -STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, - Int32 testMode, IArchiveExtractCallback *extractCallback) +Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testMode, IArchiveExtractCallback *extractCallback)) { COM_TRY_BEGIN - bool allFilesMode = (numItems == (UInt32)(Int32)-1); + const bool allFilesMode = (numItems == (UInt32)(Int32)-1); if (allFilesMode) numItems = _tags.Size(); if (numItems == 0) @@ -939,7 +948,7 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, UInt32 i; for (i = 0; i < numItems; i++) totalSize += _tags[allFilesMode ? i : indices[i]].Buf.Size(); - extractCallback->SetTotal(totalSize); + RINOK(extractCallback->SetTotal(totalSize)) CLocalProgress *lps = new CLocalProgress; CMyComPtr progress = lps; @@ -950,24 +959,24 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, for (i = 0; i < numItems; i++) { lps->InSize = lps->OutSize = totalSize; - RINOK(lps->SetCur()); - Int32 askMode = testMode ? + RINOK(lps->SetCur()) + const Int32 askMode = testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; - UInt32 index = allFilesMode ? i : indices[i]; + const UInt32 index = allFilesMode ? i : indices[i]; const CByteBuffer &buf = _tags[index].Buf; totalSize += buf.Size(); CMyComPtr outStream; - RINOK(extractCallback->GetStream(index, &outStream, askMode)); + RINOK(extractCallback->GetStream(index, &outStream, askMode)) if (!testMode && !outStream) continue; - RINOK(extractCallback->PrepareOperation(askMode)); + RINOK(extractCallback->PrepareOperation(askMode)) if (outStream) - RINOK(WriteStream(outStream, buf, buf.Size())); + RINOK(WriteStream(outStream, buf, buf.Size())) outStream.Release(); - RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)); + RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)) } return S_OK; COM_TRY_END @@ -976,7 +985,7 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, static const Byte k_Signature[] = { 'F', 'W', 'S' }; REGISTER_ARC_I( - "SWF", "swf", 0, 0xD7, + "SWF", "swf", NULL, 0xD7, k_Signature, 0, NArcInfoFlags::kKeepName, diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/StdAfx.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/StdAfx.h index 2854ff3e..035267cc 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/StdAfx.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/StdAfx.h @@ -1,8 +1,11 @@ // StdAfx.h -#ifndef __STDAFX_H -#define __STDAFX_H +#ifndef ZIP7_INC_STDAFX_H +#define ZIP7_INC_STDAFX_H +#if defined(_MSC_VER) && _MSC_VER >= 1800 +#pragma warning(disable : 4464) // relative include path contains '..' +#endif #include "../../../Common/Common.h" #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarHandler.cpp index baa43c79..5761ea37 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarHandler.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarHandler.cpp @@ -24,7 +24,9 @@ using namespace NWindows; namespace NArchive { namespace NTar { -static const UINT k_DefaultCodePage = CP_OEMCP; // it uses it if UTF8 check in names shows error +// 21.02: we use UTF8 code page by default, even if some files show error +// before 21.02 : CP_OEMCP; +// static const UINT k_DefaultCodePage = CP_UTF8; static const Byte kProps[] = @@ -34,126 +36,207 @@ static const Byte kProps[] = kpidSize, kpidPackSize, kpidMTime, + kpidCTime, + kpidATime, kpidPosixAttrib, +#if 0 + kpidAttrib, +#endif kpidUser, kpidGroup, + kpidUserId, + kpidGroupId, kpidSymLink, kpidHardLink, - // kpidLinkType + kpidCharacts, + kpidComment + , kpidDeviceMajor + , kpidDeviceMinor + // , kpidDevice + // , kpidHeadersSize // for debug + // , kpidOffset // for debug }; static const Byte kArcProps[] = { kpidHeadersSize, - kpidCodePage + kpidCodePage, + kpidCharacts, + kpidComment }; +static const char * const k_Characts_Prefix = "PREFIX"; + IMP_IInArchive_Props IMP_IInArchive_ArcProps -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { NCOM::CPropVariant prop; switch (propID) { - case kpidPhySize: if (_phySizeDefined) prop = _phySize; break; - case kpidHeadersSize: if (_phySizeDefined) prop = _headersSize; break; + case kpidPhySize: if (_arc._phySize_Defined) prop = _arc._phySize; break; + case kpidHeadersSize: if (_arc._phySize_Defined) prop = _arc._headersSize; break; case kpidErrorFlags: { UInt32 flags = 0; if (!_isArc) flags |= kpv_ErrorFlags_IsNotArc; - else switch (_error) + else switch ((int)_arc._error) { case k_ErrorType_UnexpectedEnd: flags = kpv_ErrorFlags_UnexpectedEnd; break; case k_ErrorType_Corrupted: flags = kpv_ErrorFlags_HeadersError; break; + // case k_ErrorType_OK: break; + // case k_ErrorType_Warning: break; + // case k_ErrorType_OK: + default: break; } - prop = flags; + if (flags != 0) + prop = flags; break; } case kpidWarningFlags: { - if (_warning) + if (_arc._is_Warning) prop = kpv_ErrorFlags_HeadersError; break; } case kpidCodePage: { + char sz[16]; const char *name = NULL; switch (_openCodePage) { case CP_OEMCP: name = "OEM"; break; - case CP_UTF8: name = "UTF-8"; break; + case CP_UTF8: name = "UTF-8"; break; + default: break; } - if (name != NULL) - prop = name; - else + if (!name) { - char sz[16]; ConvertUInt32ToString(_openCodePage, sz); - prop = sz; - }; + name = sz; + } + prop = name; + break; + } + + case kpidCharacts: + { + AString s; + if (_arc._are_Gnu) s.Add_OptSpaced("GNU"); + if (_arc._are_Posix) s.Add_OptSpaced("POSIX"); + if (_arc._are_Pax_Items) s.Add_OptSpaced("PAX_ITEM"); + if (_arc._pathPrefix_WasUsed) s.Add_OptSpaced(k_Characts_Prefix); + if (_arc._are_LongName) s.Add_OptSpaced("LongName"); + if (_arc._are_LongLink) s.Add_OptSpaced("LongLink"); + if (_arc._are_Pax) s.Add_OptSpaced("PAX"); + if (_arc._are_pax_path) s.Add_OptSpaced("path"); + if (_arc._are_pax_link) s.Add_OptSpaced("linkpath"); + if (_arc._are_mtime) s.Add_OptSpaced("mtime"); + if (_arc._are_atime) s.Add_OptSpaced("atime"); + if (_arc._are_ctime) s.Add_OptSpaced("ctime"); + if (_arc._are_SCHILY_fflags) s.Add_OptSpaced("SCHILY.fflags"); + if (_arc._is_PaxGlobal_Error) s.Add_OptSpaced("PAX_GLOBAL_ERROR"); + s.Add_OptSpaced(_encodingCharacts.GetCharactsString()); + prop = s; + break; + } + + case kpidComment: + { + if (_arc.PaxGlobal_Defined) + { + AString s; + _arc.PaxGlobal.Print_To_String(s); + if (!s.IsEmpty()) + prop = s; + } break; } + default: break; } prop.Detach(value); return S_OK; } -HRESULT CHandler::ReadItem2(ISequentialInStream *stream, bool &filled, CItemEx &item) + +void CEncodingCharacts::Check(const AString &s) { - item.HeaderPos = _phySize; - EErrorType error; - HRESULT res = ReadItem(stream, filled, item, error); - if (error == k_ErrorType_Warning) - _warning = true; - else if (error != k_ErrorType_OK) - _error = error; - RINOK(res); - if (filled) + IsAscii = s.IsAscii(); + if (!IsAscii) { /* - if (item.IsSparse()) - _isSparse = true; + { + Oem_Checked = true; + UString u; + MultiByteToUnicodeString2(u, s, CP_OEMCP); + Oem_Ok = (u.Find((wchar_t)0xfffd) <= 0); + } + Utf_Checked = true; */ - if (item.IsPaxExtendedHeader()) - _thereIsPaxExtendedHeader = true; + UtfCheck.Check_AString(s); } - _phySize += item.HeaderSize; - _headersSize += item.HeaderSize; - return S_OK; } + +AString CEncodingCharacts::GetCharactsString() const +{ + AString s; + if (IsAscii) + { + s += "ASCII"; + } + /* + if (Oem_Checked) + { + s.Add_Space_if_NotEmpty(); + s += (Oem_Ok ? "oem-ok" : "oem-error"); + } + if (Utf_Checked) + */ + else + { + s.Add_Space_if_NotEmpty(); + s += (UtfCheck.IsOK() ? "UTF8" : "UTF8-ERROR"); // "UTF8-error" + { + AString s2; + UtfCheck.PrintStatus(s2); + s.Add_Space_if_NotEmpty(); + s += s2; + } + } + return s; +} + + HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *callback) { - UInt64 endPos = 0; + UInt64 endPos; { - RINOK(stream->Seek(0, STREAM_SEEK_END, &endPos)); - RINOK(stream->Seek(0, STREAM_SEEK_SET, NULL)); + RINOK(InStream_AtBegin_GetSize(stream, endPos)) } - _phySizeDefined = true; + _arc._phySize_Defined = true; - bool utf8_OK = true; - if (!_forceCodePage) - { - if (!utf8_OK) - _curCodePage = k_DefaultCodePage; - } + // bool utf8_OK = true; + _arc.SeqStream = stream; + _arc.InStream = stream; + _arc.OpenCallback = callback; + + CItemEx item; for (;;) { - CItemEx item; - bool filled; - RINOK(ReadItem2(stream, filled, item)); - if (!filled) + _arc.NumFiles = _items.Size(); + RINOK(_arc.ReadItem(item)) + if (!_arc.filled) break; _isArc = true; - _items.Add(item); + /* if (!_forceCodePage) { if (utf8_OK) utf8_OK = CheckUTF8(item.Name, item.NameCouldBeReduced); @@ -161,11 +244,17 @@ HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *callback) if (utf8_OK) utf8_OK = CheckUTF8(item.User); if (utf8_OK) utf8_OK = CheckUTF8(item.Group); } - - RINOK(stream->Seek(item.GetPackSizeAligned(), STREAM_SEEK_CUR, &_phySize)); - if (_phySize > endPos) + */ + + item.EncodingCharacts.Check(item.Name); + _encodingCharacts.Update(item.EncodingCharacts); + + _items.Add(item); + + RINOK(stream->Seek((Int64)item.Get_PackSize_Aligned(), STREAM_SEEK_CUR, &_arc._phySize)) + if (_arc._phySize > endPos) { - _error = k_ErrorType_UnexpectedEnd; + _arc._error = k_ErrorType_UnexpectedEnd; break; } /* @@ -175,6 +264,7 @@ HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *callback) break; } */ + /* if (callback) { if (_items.Size() == 1) @@ -183,30 +273,34 @@ HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *callback) } if ((_items.Size() & 0x3FF) == 0) { - UInt64 numFiles = _items.Size(); + const UInt64 numFiles = _items.Size(); RINOK(callback->SetCompleted(&numFiles, &_phySize)); } } + */ } + /* if (!_forceCodePage) { if (!utf8_OK) _curCodePage = k_DefaultCodePage; } + */ _openCodePage = _curCodePage; if (_items.Size() == 0) { - if (_error != k_ErrorType_OK) + if (_arc._error != k_ErrorType_OK) { _isArc = false; return S_FALSE; } - CMyComPtr openVolumeCallback; if (!callback) return S_FALSE; - callback->QueryInterface(IID_IArchiveOpenVolumeCallback, (void **)&openVolumeCallback); + Z7_DECL_CMyComPtr_QI_FROM( + IArchiveOpenVolumeCallback, + openVolumeCallback, callback) if (!openVolumeCallback) return S_FALSE; NCOM::CPropVariant prop; @@ -223,19 +317,20 @@ HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *callback) return S_OK; } -STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *openArchiveCallback) +Z7_COM7F_IMF(CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *openArchiveCallback)) { COM_TRY_BEGIN + // for (int i = 0; i < 10; i++) // for debug { Close(); - RINOK(Open2(stream, openArchiveCallback)); + RINOK(Open2(stream, openArchiveCallback)) _stream = stream; } return S_OK; COM_TRY_END } -STDMETHODIMP CHandler::OpenSeq(ISequentialInStream *stream) +Z7_COM7F_IMF(CHandler::OpenSeq(ISequentialInStream *stream)) { Close(); _seqStream = stream; @@ -243,26 +338,22 @@ STDMETHODIMP CHandler::OpenSeq(ISequentialInStream *stream) return S_OK; } -STDMETHODIMP CHandler::Close() +Z7_COM7F_IMF(CHandler::Close()) { _isArc = false; - _warning = false; - _error = k_ErrorType_OK; - _phySizeDefined = false; - _phySize = 0; - _headersSize = 0; + _arc.Clear(); + _curIndex = 0; _latestIsRead = false; - // _isSparse = false; - _thereIsPaxExtendedHeader = false; + _encodingCharacts.Clear(); _items.Clear(); _seqStream.Release(); _stream.Release(); return S_OK; } -STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) { *numItems = (_stream ? _items.Size() : (UInt32)(Int32)-1); return S_OK; @@ -270,8 +361,8 @@ STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) CHandler::CHandler() { - copyCoderSpec = new NCompress::CCopyCoder(); - copyCoder = copyCoderSpec; + // copyCoder = new NCompress::CCopyCoder(); + // copyCoder = copyCoder; _openCodePage = CP_UTF8; Init(); } @@ -282,12 +373,12 @@ HRESULT CHandler::SkipTo(UInt32 index) { if (_latestIsRead) { - UInt64 packSize = _latestItem.GetPackSizeAligned(); - RINOK(copyCoderSpec->Code(_seqStream, NULL, &packSize, &packSize, NULL)); - _phySize += copyCoderSpec->TotalSize; - if (copyCoderSpec->TotalSize != packSize) + const UInt64 packSize = _latestItem.Get_PackSize_Aligned(); + RINOK(copyCoder.Interface()->Code(_seqStream, NULL, &packSize, &packSize, NULL)) + _arc._phySize += copyCoder->TotalSize; + if (copyCoder->TotalSize != packSize) { - _error = k_ErrorType_UnexpectedEnd; + _arc._error = k_ErrorType_UnexpectedEnd; return S_FALSE; } _latestIsRead = false; @@ -295,11 +386,12 @@ HRESULT CHandler::SkipTo(UInt32 index) } else { - bool filled; - RINOK(ReadItem2(_seqStream, filled, _latestItem)); - if (!filled) + _arc.SeqStream = _seqStream; + _arc.InStream = NULL; + RINOK(_arc.ReadItem(_latestItem)) + if (!_arc.filled) { - _phySizeDefined = true; + _arc._phySize_Defined = true; return E_INVALIDARG; } _latestIsRead = true; @@ -316,11 +408,91 @@ void CHandler::TarStringToUnicode(const AString &s, NWindows::NCOM::CPropVariant else MultiByteToUnicodeString2(dest, s, _curCodePage); if (toOs) - NItemName::ConvertToOSName2(dest); + NItemName::ReplaceToOsSlashes_Remove_TailSlash(dest, + true); // useBackslashReplacement prop = dest; } -STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) + +// CPaxTime is defined (NumDigits >= 0) +static void PaxTimeToProp(const CPaxTime &pt, NWindows::NCOM::CPropVariant &prop) +{ + UInt64 v; + if (!NTime::UnixTime64_To_FileTime64(pt.Sec, v)) + return; + if (pt.Ns != 0) + v += pt.Ns / 100; + FILETIME ft; + ft.dwLowDateTime = (DWORD)v; + ft.dwHighDateTime = (DWORD)(v >> 32); + prop.SetAsTimeFrom_FT_Prec_Ns100(ft, + k_PropVar_TimePrec_Base + (unsigned)pt.NumDigits, pt.Ns % 100); +} + + +static void AddSpecCharToString(const char c, AString &s) +{ + if ((Byte)c <= 0x20 || (Byte)c > 127) + { + s.Add_Char('['); + s.Add_Char(GET_HEX_CHAR_LOWER((Byte)c >> 4)); + s.Add_Char(GET_HEX_CHAR_LOWER(c & 15)); + s.Add_Char(']'); + } + else + s.Add_Char(c); +} + +static void AddSpecUInt64(AString &s, const char *name, UInt64 v) +{ + if (v != 0) + { + s.Add_OptSpaced(name); + if (v > 1) + { + s.Add_Colon(); + s.Add_UInt64(v); + } + } +} + +static void AddSpecBools(AString &s, const char *name, bool b1, bool b2) +{ + if (b1) + { + s.Add_OptSpaced(name); + if (b2) + s.Add_Char('*'); + } +} + + +#if 0 +static bool Parse_Attrib_from_SCHILY_fflags(const AString &s, UInt32 &attribRes) +{ + UInt32 attrib = 0; + attribRes = attrib; + unsigned pos = 0; + while (pos < s.Len()) + { + int pos2 = s.Find(',', pos); + if (pos2 < 0) + pos2 = (int)s.Len(); + const AString str = s.Mid(pos, (unsigned)pos2 - pos); + if (str.IsEqualTo("hidden")) attrib |= FILE_ATTRIBUTE_HIDDEN; + else if (str.IsEqualTo("rdonly")) attrib |= FILE_ATTRIBUTE_READONLY; + else if (str.IsEqualTo("system")) attrib |= FILE_ATTRIBUTE_SYSTEM; + else + return false; + pos = (unsigned)pos2 + 1; + } + attribRes = attrib; + return true; +} +#endif + + +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; @@ -334,7 +506,7 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val return E_INVALIDARG; else { - RINOK(SkipTo(index)); + RINOK(SkipTo(index)) item = &_latestItem; } } @@ -343,38 +515,221 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val { case kpidPath: TarStringToUnicode(item->Name, prop, true); break; case kpidIsDir: prop = item->IsDir(); break; - case kpidSize: prop = item->GetUnpackSize(); break; - case kpidPackSize: prop = item->GetPackSizeAligned(); break; + case kpidSize: prop = item->Get_UnpackSize(); break; + case kpidPackSize: prop = item->Get_PackSize_Aligned(); break; case kpidMTime: - if (item->MTime != 0) + { + /* + // for debug: + PropVariant_SetFrom_UnixTime(prop, 1 << 30); + prop.wReserved1 = k_PropVar_TimePrec_Base + 1; + prop.wReserved2 = 12; + break; + */ + + if (item->PaxTimes.MTime.IsDefined()) + PaxTimeToProp(item->PaxTimes.MTime, prop); + else + // if (item->MTime != 0) { + // we allow (item->MTime == 0) FILETIME ft; - if (NTime::UnixTime64ToFileTime(item->MTime, ft)) - prop = ft; + if (NTime::UnixTime64_To_FileTime(item->MTime, ft)) + { + unsigned prec = k_PropVar_TimePrec_Unix; + if (item->MTime_IsBin) + { + /* we report here that it's Int64-UnixTime + instead of basic UInt32-UnixTime range */ + prec = k_PropVar_TimePrec_Base; + } + prop.SetAsTimeFrom_FT_Prec(ft, prec); + } + } + break; + } + case kpidATime: + if (item->PaxTimes.ATime.IsDefined()) + PaxTimeToProp(item->PaxTimes.ATime, prop); + break; + case kpidCTime: + if (item->PaxTimes.CTime.IsDefined()) + PaxTimeToProp(item->PaxTimes.CTime, prop); + break; + case kpidPosixAttrib: prop = item->Get_Combined_Mode(); break; + + // kpidAttrib has priority over kpidPosixAttrib in 7-Zip. + // but if we want kpidPosixAttrib priority for TAR, we disable kpidAttrib. +#if 0 + case kpidAttrib: + { + if (!item->SCHILY_fflags.IsEmpty()) + { + UInt32 attrib = 0; + if (Parse_Attrib_from_SCHILY_fflags(item->SCHILY_fflags, attrib)) + { + if (attrib != 0) + { + if (item->IsDir()) + attrib |= FILE_ATTRIBUTE_DIRECTORY; + attrib |= ((UInt32)item->Get_Combined_Mode() << 16) | 0x8000; // FILE_ATTRIBUTE_UNIX_EXTENSION; + prop = attrib; + } + } } break; - case kpidPosixAttrib: prop = item->Mode; break; - case kpidUser: TarStringToUnicode(item->User, prop); break; - case kpidGroup: TarStringToUnicode(item->Group, prop); break; - case kpidSymLink: if (item->LinkFlag == NFileHeader::NLinkFlag::kSymLink && !item->LinkName.IsEmpty()) TarStringToUnicode(item->LinkName, prop); break; - case kpidHardLink: if (item->LinkFlag == NFileHeader::NLinkFlag::kHardLink && !item->LinkName.IsEmpty()) TarStringToUnicode(item->LinkName, prop); break; - // case kpidLinkType: prop = (int)item->LinkFlag; break; + } +#endif + + case kpidUser: + if (!item->User.IsEmpty()) + TarStringToUnicode(item->User, prop); + break; + case kpidGroup: + if (!item->Group.IsEmpty()) + TarStringToUnicode(item->Group, prop); + break; + + case kpidUserId: + // if (item->UID != 0) + prop = (UInt32)item->UID; + break; + case kpidGroupId: + // if (item->GID != 0) + prop = (UInt32)item->GID; + break; + + case kpidDeviceMajor: + if (item->DeviceMajor_Defined) + // if (item->DeviceMajor != 0) + prop = (UInt32)item->DeviceMajor; + break; + + case kpidDeviceMinor: + if (item->DeviceMinor_Defined) + // if (item->DeviceMinor != 0) + prop = (UInt32)item->DeviceMinor; + break; + /* + case kpidDevice: + if (item->DeviceMajor_Defined) + if (item->DeviceMinor_Defined) + prop = (UInt64)MY_dev_makedev(item->DeviceMajor, item->DeviceMinor); + break; + */ + + case kpidSymLink: + if (item->Is_SymLink()) + if (!item->LinkName.IsEmpty()) + TarStringToUnicode(item->LinkName, prop); + break; + case kpidHardLink: + if (item->Is_HardLink()) + if (!item->LinkName.IsEmpty()) + TarStringToUnicode(item->LinkName, prop); + break; + + case kpidCharacts: + { + AString s; + { + s.Add_Space_if_NotEmpty(); + AddSpecCharToString(item->LinkFlag, s); + } + if (item->IsMagic_GNU()) + s.Add_OptSpaced("GNU"); + else if (item->IsMagic_Posix_ustar_00()) + s.Add_OptSpaced("POSIX"); + else + { + s.Add_Space_if_NotEmpty(); + for (unsigned i = 0; i < sizeof(item->Magic); i++) + AddSpecCharToString(item->Magic[i], s); + } + + if (item->IsSignedChecksum) + s.Add_OptSpaced("SignedChecksum"); + + if (item->Prefix_WasUsed) + s.Add_OptSpaced(k_Characts_Prefix); + + s.Add_OptSpaced(item->EncodingCharacts.GetCharactsString()); + + // AddSpecUInt64(s, "LongName", item->Num_LongName_Records); + // AddSpecUInt64(s, "LongLink", item->Num_LongLink_Records); + AddSpecBools(s, "LongName", item->LongName_WasUsed, item->LongName_WasUsed_2); + AddSpecBools(s, "LongLink", item->LongLink_WasUsed, item->LongLink_WasUsed_2); + + if (item->MTime_IsBin) + s.Add_OptSpaced("bin_mtime"); + if (item->PackSize_IsBin) + s.Add_OptSpaced("bin_psize"); + if (item->Size_IsBin) + s.Add_OptSpaced("bin_size"); + + AddSpecUInt64(s, "PAX", item->Num_Pax_Records); + + if (item->PaxTimes.MTime.IsDefined()) s.Add_OptSpaced("mtime"); + if (item->PaxTimes.ATime.IsDefined()) s.Add_OptSpaced("atime"); + if (item->PaxTimes.CTime.IsDefined()) s.Add_OptSpaced("ctime"); + + if (item->pax_path_WasUsed) + s.Add_OptSpaced("pax_path"); + if (item->pax_link_WasUsed) + s.Add_OptSpaced("pax_linkpath"); + if (item->pax_size_WasUsed) + s.Add_OptSpaced("pax_size"); + if (!item->SCHILY_fflags.IsEmpty()) + { + s.Add_OptSpaced("SCHILY.fflags="); + s += item->SCHILY_fflags; + } + if (item->Is_Sparse()) + s.Add_OptSpaced("SPARSE"); + if (item->IsThereWarning()) + s.Add_OptSpaced("WARNING"); + if (item->HeaderError) + s.Add_OptSpaced("ERROR"); + if (item->Method_Error) + s.Add_OptSpaced("METHOD_ERROR"); + if (item->Pax_Error) + s.Add_OptSpaced("PAX_error"); + if (!item->PaxExtra.RawLines.IsEmpty()) + s.Add_OptSpaced("PAX_unsupported_line"); + if (item->Pax_Overflow) + s.Add_OptSpaced("PAX_overflow"); + if (!s.IsEmpty()) + prop = s; + break; + } + case kpidComment: + { + AString s; + item->PaxExtra.Print_To_String(s); + if (!s.IsEmpty()) + prop = s; + break; + } + // case kpidHeadersSize: prop = item->HeaderSize; break; // for debug + // case kpidOffset: prop = item->HeaderPos; break; // for debug + default: break; } prop.Detach(value); return S_OK; COM_TRY_END } -HRESULT CHandler::Extract(const UInt32 *indices, UInt32 numItems, - Int32 testMode, IArchiveExtractCallback *extractCallback) + +Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testMode, IArchiveExtractCallback *extractCallback)) { COM_TRY_BEGIN ISequentialInStream *stream = _seqStream; - bool seqMode = (_stream == NULL); + const bool seqMode = (_stream == NULL); if (!seqMode) stream = _stream; - bool allFilesMode = (numItems == (UInt32)(Int32)-1); + const bool allFilesMode = (numItems == (UInt32)(Int32)-1); if (allFilesMode) numItems = _items.Size(); if (_stream && numItems == 0) @@ -382,53 +737,51 @@ HRESULT CHandler::Extract(const UInt32 *indices, UInt32 numItems, UInt64 totalSize = 0; UInt32 i; for (i = 0; i < numItems; i++) - totalSize += _items[allFilesMode ? i : indices[i]].GetUnpackSize(); - extractCallback->SetTotal(totalSize); + totalSize += _items[allFilesMode ? i : indices[i]].Get_UnpackSize(); + RINOK(extractCallback->SetTotal(totalSize)) UInt64 totalPackSize; totalSize = totalPackSize = 0; - CLocalProgress *lps = new CLocalProgress; - CMyComPtr progress = lps; + CMyComPtr2_Create lps; lps->Init(extractCallback, false); + CMyComPtr2_Create inStream; + inStream->SetStream(stream); + CMyComPtr2_Create outStreamSpec; - CLimitedSequentialInStream *streamSpec = new CLimitedSequentialInStream; - CMyComPtr inStream(streamSpec); - streamSpec->SetStream(stream); - - CLimitedSequentialOutStream *outStreamSpec = new CLimitedSequentialOutStream; - CMyComPtr outStream(outStreamSpec); - - for (i = 0; i < numItems || seqMode; i++) + for (i = 0; ; i++) { lps->InSize = totalPackSize; lps->OutSize = totalSize; - RINOK(lps->SetCur()); - CMyComPtr realOutStream; - Int32 askMode = testMode ? - NExtract::NAskMode::kTest : - NExtract::NAskMode::kExtract; - Int32 index = allFilesMode ? i : indices[i]; + RINOK(lps->SetCur()) + if (i >= numItems && !seqMode) + break; + const UInt32 index = allFilesMode ? i : indices[i]; const CItemEx *item; if (seqMode) { - HRESULT res = SkipTo(index); + const HRESULT res = SkipTo(index); if (res == E_INVALIDARG) break; - RINOK(res); + RINOK(res) item = &_latestItem; } else item = &_items[index]; - RINOK(extractCallback->GetStream(index, &realOutStream, askMode)); - UInt64 unpackSize = item->GetUnpackSize(); + Int32 askMode = testMode ? + NExtract::NAskMode::kTest : + NExtract::NAskMode::kExtract; + CMyComPtr realOutStream; + RINOK(extractCallback->GetStream(index, &realOutStream, askMode)) + const UInt64 unpackSize = item->Get_UnpackSize(); totalSize += unpackSize; - totalPackSize += item->GetPackSizeAligned(); + totalPackSize += item->Get_PackSize_Aligned(); if (item->IsDir()) { - RINOK(extractCallback->PrepareOperation(askMode)); - RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)); + RINOK(extractCallback->PrepareOperation(askMode)) + // realOutStream.Release(); + RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)) continue; } bool skipMode = false; @@ -437,12 +790,13 @@ HRESULT CHandler::Extract(const UInt32 *indices, UInt32 numItems, if (!seqMode) { /* - // probably we must show extracting info it callback handler instead - if (item->IsHardLink() || - item->IsSymLink()) + // GetStream() creates link. + // so we can show extracting info in GetStream() instead + if (item->Is_HardLink() || + item->Is_SymLink()) { - RINOK(extractCallback->PrepareOperation(askMode)); - RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)); + RINOK(extractCallback->PrepareOperation(askMode)) + RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)) } */ continue; @@ -450,7 +804,7 @@ HRESULT CHandler::Extract(const UInt32 *indices, UInt32 numItems, skipMode = true; askMode = NExtract::NAskMode::kSkip; } - RINOK(extractCallback->PrepareOperation(askMode)); + RINOK(extractCallback->PrepareOperation(askMode)) outStreamSpec->SetStream(realOutStream); realOutStream.Release(); @@ -458,28 +812,33 @@ HRESULT CHandler::Extract(const UInt32 *indices, UInt32 numItems, Int32 opRes = NExtract::NOperationResult::kOK; CMyComPtr inStream2; - if (!item->IsSparse()) + if (!item->Is_Sparse()) inStream2 = inStream; else { - GetStream(index, &inStream2); - if (!inStream2) - return E_FAIL; + const HRESULT hres = GetStream(index, &inStream2); + if (hres == E_NOTIMPL) + opRes = NExtract::NOperationResult::kHeadersError; // kUnsupportedMethod + else if (!inStream2) + { + opRes = NExtract::NOperationResult::kDataError; + // return E_FAIL; + } } - + if (opRes == NExtract::NOperationResult::kOK) { - if (item->IsSymLink()) + if (item->Is_SymLink()) { - RINOK(WriteStream(outStreamSpec, (const char *)item->LinkName, item->LinkName.Len())); + RINOK(WriteStream(outStreamSpec, (const char *)item->LinkName, item->LinkName.Len())) } else { if (!seqMode) { - RINOK(_stream->Seek(item->GetDataPosition(), STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekSet(_stream, item->Get_DataPos())) } - streamSpec->Init(item->GetPackSizeAligned()); - RINOK(copyCoder->Code(inStream2, outStream, NULL, NULL, progress)); + inStream->Init(item->Get_PackSize_Aligned()); + RINOK(copyCoder.Interface()->Code(inStream2, outStreamSpec, NULL, NULL, lps)) } if (outStreamSpec->GetRem() != 0) opRes = NExtract::NOperationResult::kDataError; @@ -490,30 +849,26 @@ HRESULT CHandler::Extract(const UInt32 *indices, UInt32 numItems, _curIndex++; } outStreamSpec->ReleaseStream(); - RINOK(extractCallback->SetOperationResult(opRes)); + RINOK(extractCallback->SetOperationResult(opRes)) } return S_OK; COM_TRY_END } -class CSparseStream: - public IInStream, - public CMyUnknownImp -{ + +Z7_CLASS_IMP_IInStream( + CSparseStream +) UInt64 _phyPos; UInt64 _virtPos; bool _needStartSeek; public: + unsigned ItemIndex; CHandler *Handler; CMyComPtr HandlerRef; - unsigned ItemIndex; CRecordVector PhyOffsets; - MY_UNKNOWN_IMP2(ISequentialInStream, IInStream) - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); - STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition); - void Init() { _virtPos = 0; @@ -523,7 +878,7 @@ class CSparseStream: }; -STDMETHODIMP CSparseStream::Read(void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CSparseStream::Read(void *data, UInt32 size, UInt32 *processedSize)) { if (processedSize) *processedSize = 0; @@ -533,7 +888,7 @@ STDMETHODIMP CSparseStream::Read(void *data, UInt32 size, UInt32 *processedSize) if (_virtPos >= item.Size) return S_OK; { - UInt64 rem = item.Size - _virtPos; + const UInt64 rem = item.Size - _virtPos; if (size > rem) size = (UInt32)rem; } @@ -547,7 +902,7 @@ STDMETHODIMP CSparseStream::Read(void *data, UInt32 size, UInt32 *processedSize) unsigned left = 0, right = item.SparseBlocks.Size(); for (;;) { - unsigned mid = (left + right) / 2; + const unsigned mid = (unsigned)(((size_t)left + (size_t)right) / 2); if (mid == left) break; if (_virtPos < item.SparseBlocks[mid].Offset) @@ -557,17 +912,17 @@ STDMETHODIMP CSparseStream::Read(void *data, UInt32 size, UInt32 *processedSize) } const CSparseBlock &sb = item.SparseBlocks[left]; - UInt64 relat = _virtPos - sb.Offset; + const UInt64 relat = _virtPos - sb.Offset; if (_virtPos >= sb.Offset && relat < sb.Size) { - UInt64 rem = sb.Size - relat; + const UInt64 rem = sb.Size - relat; if (size > rem) size = (UInt32)rem; - UInt64 phyPos = PhyOffsets[left] + relat; + const UInt64 phyPos = PhyOffsets[left] + relat; if (_needStartSeek || _phyPos != phyPos) { - RINOK(Handler->_stream->Seek(item.GetDataPosition() + phyPos, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekSet(Handler->_stream, item.Get_DataPos() + phyPos)) _needStartSeek = false; _phyPos = phyPos; } @@ -581,7 +936,7 @@ STDMETHODIMP CSparseStream::Read(void *data, UInt32 size, UInt32 *processedSize) next = sb.Offset; else if (left + 1 < item.SparseBlocks.Size()) next = item.SparseBlocks[left + 1].Offset; - UInt64 rem = next - _virtPos; + const UInt64 rem = next - _virtPos; if (size > rem) size = (UInt32)rem; memset(data, 0, size); @@ -594,7 +949,7 @@ STDMETHODIMP CSparseStream::Read(void *data, UInt32 size, UInt32 *processedSize) return res; } -STDMETHODIMP CSparseStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition) +Z7_COM7F_IMF(CSparseStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition)) { switch (seekOrigin) { @@ -605,20 +960,22 @@ STDMETHODIMP CSparseStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPos } if (offset < 0) return HRESULT_WIN32_ERROR_NEGATIVE_SEEK; - _virtPos = offset; + _virtPos = (UInt64)offset; if (newPosition) *newPosition = _virtPos; return S_OK; } -STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream) +Z7_COM7F_IMF(CHandler::GetStream(UInt32 index, ISequentialInStream **stream)) { COM_TRY_BEGIN const CItemEx &item = _items[index]; - if (item.IsSparse()) + if (item.Is_Sparse()) { + if (item.Method_Error) + return E_NOTIMPL; // S_FALSE CSparseStream *streamSpec = new CSparseStream; CMyComPtr streamTemp = streamSpec; streamSpec->Init(); @@ -637,26 +994,31 @@ STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream) return S_OK; } - if (item.IsSymLink()) + if (item.Is_SymLink()) { Create_BufInStream_WithReference((const Byte *)(const char *)item.LinkName, item.LinkName.Len(), (IInArchive *)this, stream); return S_OK; } - return CreateLimitedInStream(_stream, item.GetDataPosition(), item.PackSize, stream); + return CreateLimitedInStream(_stream, item.Get_DataPos(), item.PackSize, stream); COM_TRY_END } + void CHandler::Init() { _forceCodePage = false; - // _codePage = CP_OEMCP; _curCodePage = _specifiedCodePage = CP_UTF8; // CP_OEMCP; - _thereIsPaxExtendedHeader = false; + _posixMode = false; + _posixMode_WasForced = false; + // TimeOptions.Clear(); + _handlerTimeOptions.Init(); + // _handlerTimeOptions.Write_MTime.Val = true; // it's default already } -STDMETHODIMP CHandler::SetProperties(const wchar_t * const *names, const PROPVARIANT *values, UInt32 numProps) + +Z7_COM7F_IMF(CHandler::SetProperties(const wchar_t * const *names, const PROPVARIANT *values, UInt32 numProps)) { Init(); @@ -673,17 +1035,69 @@ STDMETHODIMP CHandler::SetProperties(const wchar_t * const *names, const PROPVAR { // some clients write 'x' property. So we support it UInt32 level = 0; - RINOK(ParsePropToUInt32(name.Ptr(1), prop, level)); + RINOK(ParsePropToUInt32(name.Ptr(1), prop, level)) } else if (name.IsEqualTo("cp")) { UInt32 cp = CP_OEMCP; - RINOK(ParsePropToUInt32(L"", prop, cp)); + RINOK(ParsePropToUInt32(L"", prop, cp)) _forceCodePage = true; _curCodePage = _specifiedCodePage = cp; } + else if (name.IsPrefixedBy_Ascii_NoCase("mt")) + { + } + else if (name.IsPrefixedBy_Ascii_NoCase("memuse")) + { + } + else if (name.IsEqualTo("m")) + { + if (prop.vt != VT_BSTR) + return E_INVALIDARG; + const UString s = prop.bstrVal; + if (s.IsEqualTo_Ascii_NoCase("pax") || + s.IsEqualTo_Ascii_NoCase("posix")) + _posixMode = true; + else if (s.IsEqualTo_Ascii_NoCase("gnu")) + _posixMode = false; + else + return E_INVALIDARG; + _posixMode_WasForced = true; + } else + { + /* + if (name.IsPrefixedBy_Ascii_NoCase("td")) + { + name.Delete(0, 3); + if (prop.vt == VT_EMPTY) + { + if (name.IsEqualTo_Ascii_NoCase("n")) + { + // TimeOptions.UseNativeDigits = true; + } + else if (name.IsEqualTo_Ascii_NoCase("r")) + { + // TimeOptions.RemoveZeroDigits = true; + } + else + return E_INVALIDARG; + } + else + { + UInt32 numTimeDigits = 0; + RINOK(ParsePropToUInt32(name, prop, numTimeDigits)); + TimeOptions.NumDigits_WasForced = true; + TimeOptions.NumDigits = numTimeDigits; + } + } + */ + bool processed = false; + RINOK(_handlerTimeOptions.Parse(name, prop, processed)) + if (processed) + continue; return E_INVALIDARG; + } } return S_OK; } diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarHandler.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarHandler.h index eb9c049e..f1ef3b69 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarHandler.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarHandler.h @@ -1,75 +1,53 @@ // TarHandler.h -#ifndef __TAR_HANDLER_H -#define __TAR_HANDLER_H +#ifndef ZIP7_INC_TAR_HANDLER_H +#define ZIP7_INC_TAR_HANDLER_H #include "../../../Common/MyCom.h" -#include "../../../Windows/PropVariant.h" - #include "../../Compress/CopyCoder.h" -#include "../IArchive.h" +#include "../Common/HandlerOut.h" #include "TarIn.h" namespace NArchive { namespace NTar { -class CHandler: - public IInArchive, - public IArchiveOpenSeq, - public IInArchiveGetStream, - public ISetProperties, - public IOutArchive, - public CMyUnknownImp -{ +Z7_CLASS_IMP_CHandler_IInArchive_4( + IArchiveOpenSeq + , IInArchiveGetStream + , ISetProperties + , IOutArchive +) public: CObjectVector _items; CMyComPtr _stream; CMyComPtr _seqStream; private: - UInt32 _curIndex; - bool _latestIsRead; - CItemEx _latestItem; - - UInt64 _phySize; - UInt64 _headersSize; - bool _phySizeDefined; - EErrorType _error; - bool _warning; bool _isArc; - - // bool _isSparse; - bool _thereIsPaxExtendedHeader; - + bool _posixMode_WasForced; + bool _posixMode; bool _forceCodePage; UInt32 _specifiedCodePage; UInt32 _curCodePage; UInt32 _openCodePage; + // CTimeOptions TimeOptions; + CHandlerTimeOptions _handlerTimeOptions; + CEncodingCharacts _encodingCharacts; - NCompress::CCopyCoder *copyCoderSpec; - CMyComPtr copyCoder; + UInt32 _curIndex; + bool _latestIsRead; + CItemEx _latestItem; + + CArchive _arc; + + CMyComPtr2_Create copyCoder; - HRESULT ReadItem2(ISequentialInStream *stream, bool &filled, CItemEx &itemInfo); HRESULT Open2(IInStream *stream, IArchiveOpenCallback *callback); HRESULT SkipTo(UInt32 index); void TarStringToUnicode(const AString &s, NWindows::NCOM::CPropVariant &prop, bool toOs = false) const; public: - MY_UNKNOWN_IMP5( - IInArchive, - IArchiveOpenSeq, - IInArchiveGetStream, - ISetProperties, - IOutArchive - ) - - INTERFACE_IInArchive(;) - INTERFACE_IOutArchive(;) - STDMETHOD(OpenSeq)(ISequentialInStream *stream); - STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream); - STDMETHOD(SetProperties)(const wchar_t * const *names, const PROPVARIANT *values, UInt32 numProps); - void Init(); CHandler(); }; diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarHandlerOut.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarHandlerOut.cpp index 0b67a285..f7134ced 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarHandlerOut.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarHandlerOut.cpp @@ -2,15 +2,16 @@ #include "StdAfx.h" +// #include + #include "../../../Common/ComTry.h" -#include "../../../Common/Defs.h" #include "../../../Common/MyLinux.h" #include "../../../Common/StringConvert.h" -#include "../../../Common/UTFConvert.h" -#include "../../../Windows/PropVariant.h" #include "../../../Windows/TimeUtils.h" +#include "../Common/ItemNameUtils.h" + #include "TarHandler.h" #include "TarUpdate.h" @@ -19,31 +20,49 @@ using namespace NWindows; namespace NArchive { namespace NTar { -STDMETHODIMP CHandler::GetFileTimeType(UInt32 *type) +Z7_COM7F_IMF(CHandler::GetFileTimeType(UInt32 *type)) { - *type = NFileTimeType::kUnix; + UInt32 t = NFileTimeType::kUnix; + const UInt32 prec = _handlerTimeOptions.Prec; + if (prec != (UInt32)(Int32)-1) + { + t = NFileTimeType::kWindows; + if (prec == k_PropVar_TimePrec_0 || + prec == k_PropVar_TimePrec_100ns) + t = NFileTimeType::kWindows; + else if (prec == k_PropVar_TimePrec_HighPrec) + t = k_PropVar_TimePrec_1ns; + else if (prec >= k_PropVar_TimePrec_Base) + t = prec; + } + // 7-Zip before 22.00 fails, if unknown typeType. + *type = t; return S_OK; } -HRESULT GetPropString(IArchiveUpdateCallback *callback, UInt32 index, PROPID propId, - AString &res, UINT codePage, bool convertSlash = false) + +void Get_AString_From_UString(const UString &s, AString &res, + UINT codePage, unsigned utfFlags) +{ + if (codePage == CP_UTF8) + ConvertUnicodeToUTF8_Flags(s, res, utfFlags); + else + UnicodeStringToMultiByte2(res, s, codePage); +} + + +HRESULT GetPropString(IArchiveUpdateCallback *callback, UInt32 index, PROPID propId, AString &res, + UINT codePage, unsigned utfFlags, bool convertSlash) { NCOM::CPropVariant prop; - RINOK(callback->GetProperty(index, propId, &prop)); + RINOK(callback->GetProperty(index, propId, &prop)) if (prop.vt == VT_BSTR) { UString s = prop.bstrVal; if (convertSlash) - s = NItemName::MakeLegalName(s); - - if (codePage == CP_UTF8) - { - ConvertUnicodeToUTF8(s, res); - // if (!ConvertUnicodeToUTF8(s, res)) // return E_INVALIDARG; - } - else - UnicodeStringToMultiByte2(res, s, codePage); + NItemName::ReplaceSlashes_OsToUnix(s); + Get_AString_From_UString(s, res, codePage, utfFlags); } else if (prop.vt != VT_EMPTY) return E_INVALIDARG; @@ -56,8 +75,8 @@ HRESULT GetPropString(IArchiveUpdateCallback *callback, UInt32 index, PROPID pro static int CompareUpdateItems(void *const *p1, void *const *p2, void *) { - const CUpdateItem &u1 = *(*((const CUpdateItem **)p1)); - const CUpdateItem &u2 = *(*((const CUpdateItem **)p2)); + const CUpdateItem &u1 = *(*((const CUpdateItem *const *)p1)); + const CUpdateItem &u2 = *(*((const CUpdateItem *const *)p2)); if (!u1.NewProps) { if (u2.NewProps) @@ -70,16 +89,117 @@ static int CompareUpdateItems(void *const *p1, void *const *p2, void *) } -STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numItems, - IArchiveUpdateCallback *callback) +static HRESULT GetTime(UInt32 i, UInt32 pid, IArchiveUpdateCallback *callback, + CPaxTime &pt) +{ + pt.Clear(); + NCOM::CPropVariant prop; + RINOK(callback->GetProperty(i, pid, &prop)) + return Prop_To_PaxTime(prop, pt); +} + + +/* +static HRESULT GetDevice(IArchiveUpdateCallback *callback, UInt32 i, + UInt32 &majo, UInt32 &mino, bool &majo_defined, bool &mino_defined) +{ + NWindows::NCOM::CPropVariant prop; + RINOK(callback->GetProperty(i, kpidDevice, &prop)); + if (prop.vt == VT_EMPTY) + return S_OK; + if (prop.vt != VT_UI8) + return E_INVALIDARG; + { + const UInt64 v = prop.uhVal.QuadPart; + majo = MY_dev_major(v); + mino = MY_dev_minor(v); + majo_defined = true; + mino_defined = true; + } + return S_OK; +} +*/ + +static HRESULT GetDevice(IArchiveUpdateCallback *callback, UInt32 i, + UInt32 pid, UInt32 &id, bool &defined) +{ + defined = false; + NWindows::NCOM::CPropVariant prop; + RINOK(callback->GetProperty(i, pid, &prop)) + if (prop.vt == VT_EMPTY) + return S_OK; + if (prop.vt == VT_UI4) + { + id = prop.ulVal; + defined = true; + return S_OK; + } + return E_INVALIDARG; +} + + +static HRESULT GetUser(IArchiveUpdateCallback *callback, UInt32 i, + UInt32 pidName, UInt32 pidId, AString &name, UInt32 &id, + UINT codePage, unsigned utfFlags) +{ + // printf("\ncallback->GetProperty(i, pidId, &prop))\n"); + + bool isSet = false; + { + NWindows::NCOM::CPropVariant prop; + RINOK(callback->GetProperty(i, pidId, &prop)) + if (prop.vt == VT_UI4) + { + isSet = true; + id = prop.ulVal; + // printf("\ncallback->GetProperty(i, pidId, &prop)); = %d \n", (unsigned)id); + name.Empty(); + } + else if (prop.vt != VT_EMPTY) + return E_INVALIDARG; + } + { + NWindows::NCOM::CPropVariant prop; + RINOK(callback->GetProperty(i, pidName, &prop)) + if (prop.vt == VT_BSTR) + { + const UString s = prop.bstrVal; + Get_AString_From_UString(s, name, codePage, utfFlags); + if (!isSet) + id = 0; + } + else if (prop.vt == VT_UI4) + { + id = prop.ulVal; + name.Empty(); + } + else if (prop.vt != VT_EMPTY) + return E_INVALIDARG; + } + return S_OK; +} + + + +Z7_COM7F_IMF(CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numItems, + IArchiveUpdateCallback *callback)) { COM_TRY_BEGIN - if ((_stream && (_error != k_ErrorType_OK || _warning /* || _isSparse */)) || _seqStream) + if ((_stream && (_arc._error != k_ErrorType_OK || _arc._is_Warning + /* || _isSparse */ + )) || _seqStream) return E_NOTIMPL; CObjectVector updateItems; - UINT codePage = (_forceCodePage ? _specifiedCodePage : _openCodePage); - + const UINT codePage = (_forceCodePage ? _specifiedCodePage : _openCodePage); + const unsigned utfFlags = g_Unicode_To_UTF8_Flags; + /* + // for debug only: + unsigned utfFlags = 0; + utfFlags |= Z7_UTF_FLAG_TO_UTF8_EXTRACT_BMP_ESCAPE; + utfFlags |= Z7_UTF_FLAG_TO_UTF8_SURROGATE_ERROR; + */ + for (UInt32 i = 0; i < numItems; i++) { CUpdateItem ui; @@ -90,18 +210,18 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numIt if (!callback) return E_FAIL; - RINOK(callback->GetUpdateItemInfo(i, &newData, &newProps, &indexInArc)); + RINOK(callback->GetUpdateItemInfo(i, &newData, &newProps, &indexInArc)) ui.NewProps = IntToBool(newProps); ui.NewData = IntToBool(newData); - ui.IndexInArc = indexInArc; + ui.IndexInArc = (int)indexInArc; ui.IndexInClient = i; if (IntToBool(newProps)) { { NCOM::CPropVariant prop; - RINOK(callback->GetProperty(i, kpidIsDir, &prop)); + RINOK(callback->GetProperty(i, kpidIsDir, &prop)) if (prop.vt == VT_EMPTY) ui.IsDir = false; else if (prop.vt != VT_BOOL) @@ -112,7 +232,7 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numIt { NCOM::CPropVariant prop; - RINOK(callback->GetProperty(i, kpidPosixAttrib, &prop)); + RINOK(callback->GetProperty(i, kpidPosixAttrib, &prop)) if (prop.vt == VT_EMPTY) ui.Mode = MY_LIN_S_IRWXO @@ -123,30 +243,37 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numIt return E_INVALIDARG; else ui.Mode = prop.ulVal; + // 21.07 : we clear high file type bits as GNU TAR. + // we will clear it later + // ui.Mode &= ~(UInt32)MY_LIN_S_IFMT; } + if (_handlerTimeOptions.Write_MTime.Val) + RINOK(GetTime(i, kpidMTime, callback, ui.PaxTimes.MTime)) + if (_handlerTimeOptions.Write_ATime.Val) + RINOK(GetTime(i, kpidATime, callback, ui.PaxTimes.ATime)) + if (_handlerTimeOptions.Write_CTime.Val) + RINOK(GetTime(i, kpidCTime, callback, ui.PaxTimes.CTime)) + + RINOK(GetPropString(callback, i, kpidPath, ui.Name, codePage, utfFlags, true)) + if (ui.IsDir && !ui.Name.IsEmpty() && ui.Name.Back() != '/') + ui.Name.Add_Slash(); + // ui.Name.Add_Slash(); // for debug + + if (_posixMode) { - NCOM::CPropVariant prop; - RINOK(callback->GetProperty(i, kpidMTime, &prop)); - if (prop.vt == VT_EMPTY) - ui.MTime = 0; - else if (prop.vt != VT_FILETIME) - return E_INVALIDARG; - else - ui.MTime = NTime::FileTimeToUnixTime64(prop.filetime); + RINOK(GetDevice(callback, i, kpidDeviceMajor, ui.DeviceMajor, ui.DeviceMajor_Defined)) + RINOK(GetDevice(callback, i, kpidDeviceMinor, ui.DeviceMinor, ui.DeviceMinor_Defined)) } - - RINOK(GetPropString(callback, i, kpidPath, ui.Name, codePage, true)); - if (ui.IsDir && !ui.Name.IsEmpty() && ui.Name.Back() != '/') - ui.Name += '/'; - RINOK(GetPropString(callback, i, kpidUser, ui.User, codePage)); - RINOK(GetPropString(callback, i, kpidGroup, ui.Group, codePage)); + + RINOK(GetUser(callback, i, kpidUser, kpidUserId, ui.User, ui.UID, codePage, utfFlags)) + RINOK(GetUser(callback, i, kpidGroup, kpidGroupId, ui.Group, ui.GID, codePage, utfFlags)) } if (IntToBool(newData)) { NCOM::CPropVariant prop; - RINOK(callback->GetProperty(i, kpidSize, &prop)); + RINOK(callback->GetProperty(i, kpidSize, &prop)) if (prop.vt != VT_UI8) return E_INVALIDARG; ui.Size = prop.uhVal.QuadPart; @@ -160,13 +287,44 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numIt updateItems.Add(ui); } - if (_thereIsPaxExtendedHeader) + if (_stream && _arc._are_Pax_Items) { - // we restore original order of files, if there is pax header block + // we restore original order of files, if there are pax items updateItems.Sort(CompareUpdateItems, NULL); } + + CUpdateOptions options; + + options.CodePage = codePage; + options.UtfFlags = utfFlags; + options.PosixMode = _posixMode; + + options.Write_MTime = _handlerTimeOptions.Write_MTime; + options.Write_ATime = _handlerTimeOptions.Write_ATime; + options.Write_CTime = _handlerTimeOptions.Write_CTime; - return UpdateArchive(_stream, outStream, _items, updateItems, codePage, callback); + // options.TimeOptions = TimeOptions; + + const UInt32 prec = _handlerTimeOptions.Prec; + if (prec != (UInt32)(Int32)-1) + { + unsigned numDigits = 0; + if (prec == 0) + numDigits = 7; + else if (prec == k_PropVar_TimePrec_HighPrec + || prec >= k_PropVar_TimePrec_1ns) + numDigits = 9; + else if (prec >= k_PropVar_TimePrec_Base) + numDigits = prec - k_PropVar_TimePrec_Base; + options.TimeOptions.NumDigitsMax = numDigits; + // options.TimeOptions.RemoveZeroMode = + // k_PaxTimeMode_DontRemoveZero; // pure for debug + // k_PaxTimeMode_RemoveZero_if_PureSecondOnly; // optimized code + // k_PaxTimeMode_RemoveZero_Always; // original pax code + } + + return UpdateArchive(_stream, outStream, _items, updateItems, + options, callback); COM_TRY_END } diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarHeader.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarHeader.cpp index 70be09f3..deae357e 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarHeader.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarHeader.cpp @@ -8,16 +8,92 @@ namespace NArchive { namespace NTar { namespace NFileHeader { - const char *kLongLink = "././@LongLink"; - const char *kLongLink2 = "@LongLink"; + const char * const kLongLink = "././@LongLink"; + const char * const kLongLink2 = "@LongLink"; // The magic field is filled with this if uname and gname are valid. namespace NMagic { - // const char *kUsTar = "ustar"; // 5 chars - // const char *kGNUTar = "GNUtar "; // 7 chars and a null - // const char *kEmpty = "\0\0\0\0\0\0\0\0"; - const char kUsTar_00[8] = { 'u', 's', 't', 'a', 'r', 0, '0', '0' } ; + // const char * const kUsTar = "ustar"; // 5 chars + // const char * const kGNUTar = "GNUtar "; // 7 chars and a null + // const char * const kEmpty = "\0\0\0\0\0\0\0\0"; + // 7-Zip used kUsTar_00 before 21.07: + const char k_Posix_ustar_00[8] = { 'u', 's', 't', 'a', 'r', 0, '0', '0' } ; + // GNU TAR uses such header: + const char k_GNU_ustar[8] = { 'u', 's', 't', 'a', 'r', ' ', ' ', 0 } ; } +/* +pre-POSIX.1-1988 (i.e. v7) tar header: +----- +Link indicator: +'0' or 0 : Normal file +'1' : Hard link +'2' : Symbolic link +Some pre-POSIX.1-1988 tar implementations indicated a directory by having +a trailing slash (/) in the name. + +Numeric values : octal with leading zeroes. +For historical reasons, a final NUL or space character should also be used. +Thus only 11 octal digits can be stored from 12 bytes field. + +2001 star : introduced a base-256 coding that is indicated by +setting the high-order bit of the leftmost byte of a numeric field. +GNU-tar and BSD-tar followed this idea. + +versions of tar from before the first POSIX standard from 1988 +pad the values with spaces instead of zeroes. + +UStar +----- +UStar (Unix Standard TAR) : POSIX IEEE P1003.1 : 1988. + 257 signature: "ustar", 0, "00" + 265 32 Owner user name + 297 32 Owner group name + 329 8 Device major number + 337 8 Device minor number + 345 155 Filename prefix + +POSIX.1-2001/pax +---- +format is known as extended tar format or pax format +vendor-tagged vendor-specific enhancements. +tags Defined by the POSIX standard: + atime, mtime, path, linkpath, uname, gname, size, uid, gid, ... + + +PAX EXTENSION +----------- +Hard links +A further difference from the ustar header block is that data blocks +for files of typeflag 1 (hard link) may be included, +which means that the size field may be greater than zero. +Archives created by pax -o linkdata shall include these data +blocks with the hard links. +* + +compatiblity +------------ + 7-Zip 16.03 supports "PaxHeader/" + 7-Zip 20.01 supports "PaxHeaders.X/" with optional "./" + 7-Zip 21.02 supports "@PaxHeader" with optional "./" "./" + + GNU tar --format=posix uses "PaxHeaders/" in folder of file + + +GNU TAR format +============== +v7 - Unix V7 +oldgnu - GNU tar <=1.12 : writes zero in last character in name +gnu - GNU tar 1.13 : doesn't write zero in last character in name + as 7-zip 21.07 +ustar - POSIX.1-1988 +posix (pax) - POSIX.1-2001 + + gnu tar: + if (S_ISCHR (st->stat.st_mode) || S_ISBLK (st->stat.st_mode)) { + major_t devmajor = major (st->stat.st_rdev); + minor_t devminor = minor (st->stat.st_rdev); } +*/ + }}} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarHeader.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarHeader.h index df594d81..aeccd286 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarHeader.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarHeader.h @@ -1,7 +1,7 @@ // Archive/TarHeader.h -#ifndef __ARCHIVE_TAR_HEADER_H -#define __ARCHIVE_TAR_HEADER_H +#ifndef ZIP7_INC_ARCHIVE_TAR_HEADER_H +#define ZIP7_INC_ARCHIVE_TAR_HEADER_H #include "../../../Common/MyTypes.h" @@ -58,6 +58,10 @@ namespace NFileHeader const char kGnu_LongLink = 'K'; const char kGnu_LongName = 'L'; const char kSparse = 'S'; + const char kLabel = 'V'; + const char kPax = 'x'; // Extended header with meta data for the next file in the archive (POSIX.1-2001) + const char kPax_2 = 'X'; + const char kGlobal = 'g'; // Global extended header with meta data (POSIX.1-2001) const char kDumpDir = 'D'; /* GNUTYPE_DUMPDIR. data: list of files created by the --incremental (-G) option Each file name is preceded by either @@ -65,17 +69,19 @@ namespace NFileHeader - 'N' (file is a directory, or is not stored in the archive.) Each file name is terminated by a null + an additional null after the last file name. */ + // 'A'-'Z' Vendor specific extensions (POSIX.1-1988) } - extern const char *kLongLink; // = "././@LongLink"; - extern const char *kLongLink2; // = "@LongLink"; + extern const char * const kLongLink; // = "././@LongLink"; + extern const char * const kLongLink2; // = "@LongLink"; namespace NMagic { - // extern const char *kUsTar; // = "ustar"; // 5 chars - // extern const char *kGNUTar; // = "GNUtar "; // 7 chars and a null - // extern const char *kEmpty; // = "\0\0\0\0\0\0\0\0" - extern const char kUsTar_00[]; + // extern const char * const kUsTar; // = "ustar"; // 5 chars + // extern const char * const kGNUTar; // = "GNUtar "; // 7 chars and a null + // extern const char * const kEmpty; // = "\0\0\0\0\0\0\0\0" + extern const char k_Posix_ustar_00[8]; + extern const char k_GNU_ustar[8]; } } diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarIn.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarIn.cpp index 57e18ac0..303eda05 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarIn.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarIn.cpp @@ -12,6 +12,45 @@ #include "TarIn.h" +#define NUM_UNROLL_BYTES (8 * 4) + +Z7_NO_INLINE static bool IsBufNonZero(const void *data, size_t size); +Z7_NO_INLINE static bool IsBufNonZero(const void *data, size_t size) +{ + const Byte *p = (const Byte *)data; + + for (; size != 0 && ((unsigned)(ptrdiff_t)p & (NUM_UNROLL_BYTES - 1)) != 0; size--) + if (*p++ != 0) + return true; + + if (size >= NUM_UNROLL_BYTES) + { + const Byte *lim = p + size; + size &= (NUM_UNROLL_BYTES - 1); + lim -= size; + do + { + if (*(const UInt64 *)(const void *)(p ) != 0) return true; + if (*(const UInt64 *)(const void *)(p + 8 * 1) != 0) return true; + if (*(const UInt64 *)(const void *)(p + 8 * 2) != 0) return true; + if (*(const UInt64 *)(const void *)(p + 8 * 3) != 0) return true; + // if (*(const UInt32 *)(const void *)(p ) != 0) return true; + // if (*(const UInt32 *)(const void *)(p + 4 * 1) != 0) return true; + // if (*(const UInt32 *)(const void *)(p + 4 * 2) != 0) return true; + // if (*(const UInt32 *)(const void *)(p + 4 * 3) != 0) return true; + p += NUM_UNROLL_BYTES; + } + while (p != lim); + } + + for (; size != 0; size--) + if (*p++ != 0) + return true; + + return false; +} + + namespace NArchive { namespace NTar { @@ -26,24 +65,26 @@ static void MyStrNCpy(char *dest, const char *src, unsigned size) } } -static bool OctalToNumber(const char *srcString, unsigned size, UInt64 &res) +static bool OctalToNumber(const char *srcString, unsigned size, UInt64 &res, bool allowEmpty = false) { + res = 0; char sz[32]; MyStrNCpy(sz, srcString, size); sz[size] = 0; const char *end; unsigned i; for (i = 0; sz[i] == ' '; i++); + if (sz[i] == 0) + return allowEmpty; res = ConvertOctStringToUInt64(sz + i, &end); - if (end == sz + i) - return false; return (*end == ' ' || *end == 0); } -static bool OctalToNumber32(const char *srcString, unsigned size, UInt32 &res) +static bool OctalToNumber32(const char *srcString, UInt32 &res, bool allowEmpty = false) { + const unsigned kSize = 8; UInt64 res64; - if (!OctalToNumber(srcString, size, res64)) + if (!OctalToNumber(srcString, kSize, res64, allowEmpty)) return false; res = (UInt32)res64; return (res64 <= 0xFFFFFFFF); @@ -51,55 +92,61 @@ static bool OctalToNumber32(const char *srcString, unsigned size, UInt32 &res) #define RIF(x) { if (!(x)) return S_OK; } -/* -static bool IsEmptyData(const char *buf, size_t size) -{ - for (unsigned i = 0; i < size; i++) - if (buf[i] != 0) - return false; - return true; -} -*/ - -static bool IsRecordLast(const char *buf) -{ - for (unsigned i = 0; i < NFileHeader::kRecordSize; i++) - if (buf[i] != 0) - return false; - return true; -} - static void ReadString(const char *s, unsigned size, AString &result) { - char temp[NFileHeader::kRecordSize + 1]; - MyStrNCpy(temp, s, size); - temp[size] = '\0'; - result = temp; + result.SetFrom_CalcLen(s, size); } -static bool ParseInt64(const char *p, Int64 &val) +static bool ParseInt64(const char *p, Int64 &val, bool &isBin) { - UInt32 h = GetBe32(p); - val = GetBe64(p + 4); + const UInt32 h = GetBe32(p); + val = (Int64)GetBe64(p + 4); + isBin = true; if (h == (UInt32)1 << 31) return ((val >> 63) & 1) == 0; if (h == (UInt32)(Int32)-1) return ((val >> 63) & 1) != 0; - UInt64 uv; - bool res = OctalToNumber(p, 12, uv); - val = uv; + isBin = false; + UInt64 u; + const bool res = OctalToNumber(p, 12, u); + val = (Int64)u; return res; } -static bool ParseSize(const char *p, UInt64 &val) +static bool ParseInt64_MTime(const char *p, Int64 &val, bool &isBin) +{ + // rare case tar : ZEROs in Docker-Windows TARs + // rare case tar : pax record : single ZERO byte followed by junk data + // rare case tar : spaces + isBin = false; + // if (GetUi32(p)) + if (p[0]) + for (unsigned i = 0; i < 12; i++) + if (p[i] != ' ') + return ParseInt64(p, val, isBin); + val = 0; + return true; +} + +static bool ParseSize(const char *p, UInt64 &val, bool &isBin) { if (GetBe32(p) == (UInt32)1 << 31) { // GNU extension + isBin = true; val = GetBe64(p + 4); return ((val >> 63) & 1) == 0; } - return OctalToNumber(p, 12, val); + isBin = false; + return OctalToNumber(p, 12, val, + true // 20.03: allow empty size for 'V' Label entry + ); +} + +static bool ParseSize(const char *p, UInt64 &val) +{ + bool isBin; + return ParseSize(p, val, isBin); } #define CHECK(x) { if (!(x)) return k_IsArc_Res_NO; } @@ -113,27 +160,30 @@ API_FUNC_IsArc IsArc_Tar(const Byte *p2, size_t size) p += NFileHeader::kNameSize; UInt32 mode; - CHECK(OctalToNumber32(p, 8, mode)); p += 8; + // we allow empty Mode value for LongName prefix items + CHECK(OctalToNumber32(p, mode, true)) p += 8; - // if (!OctalToNumber32(p, 8, item.UID)) item.UID = 0; + // if (!OctalToNumber32(p, item.UID)) item.UID = 0; p += 8; - // if (!OctalToNumber32(p, 8, item.GID)) item.GID = 0; + // if (!OctalToNumber32(p, item.GID)) item.GID = 0; p += 8; UInt64 packSize; Int64 time; UInt32 checkSum; - CHECK(ParseSize(p, packSize)); p += 12; - CHECK(ParseInt64(p, time)); p += 12; - CHECK(OctalToNumber32(p, 8, checkSum)); + bool isBin; + CHECK(ParseSize(p, packSize, isBin)) p += 12; + CHECK(ParseInt64_MTime(p, time, isBin)) p += 12; + CHECK(OctalToNumber32(p, checkSum)) return k_IsArc_Res_YES; } -static HRESULT GetNextItemReal(ISequentialInStream *stream, bool &filled, CItemEx &item, EErrorType &error) + +HRESULT CArchive::GetNextItemReal(CItemEx &item) { char buf[NFileHeader::kRecordSize]; - char *p = buf; + item.Method_Error = false; error = k_ErrorType_OK; filled = false; @@ -141,7 +191,7 @@ static HRESULT GetNextItemReal(ISequentialInStream *stream, bool &filled, CItemE for (;;) { size_t processedSize = NFileHeader::kRecordSize; - RINOK(ReadStream(stream, buf, &processedSize)); + RINOK(ReadStream(SeqStream, buf, &processedSize)) if (processedSize == 0) { if (!thereAreEmptyRecords) @@ -167,10 +217,11 @@ static HRESULT GetNextItemReal(ISequentialInStream *stream, bool &filled, CItemE return S_OK; } - if (!IsRecordLast(buf)) + if (IsBufNonZero(buf, NFileHeader::kRecordSize)) break; item.HeaderSize += NFileHeader::kRecordSize; thereAreEmptyRecords = true; + RINOK(Progress(item, 0)) } if (thereAreEmptyRecords) { @@ -178,51 +229,69 @@ static HRESULT GetNextItemReal(ISequentialInStream *stream, bool &filled, CItemE return S_OK; } + char *p = buf; + error = k_ErrorType_Corrupted; - ReadString(p, NFileHeader::kNameSize, item.Name); p += NFileHeader::kNameSize; - item.NameCouldBeReduced = + + // ReadString(p, NFileHeader::kNameSize, item.Name); + p += NFileHeader::kNameSize; + + /* + item.Name_CouldBeReduced = (item.Name.Len() == NFileHeader::kNameSize || item.Name.Len() == NFileHeader::kNameSize - 1); + */ - RIF(OctalToNumber32(p, 8, item.Mode)); p += 8; + // we allow empty Mode value for LongName prefix items + RIF(OctalToNumber32(p, item.Mode, true)) p += 8; - if (!OctalToNumber32(p, 8, item.UID)) item.UID = 0; p += 8; - if (!OctalToNumber32(p, 8, item.GID)) item.GID = 0; p += 8; + if (!OctalToNumber32(p, item.UID)) { item.UID = 0; } p += 8; + if (!OctalToNumber32(p, item.GID)) { item.GID = 0; } p += 8; - RIF(ParseSize(p, item.PackSize)); + RIF(ParseSize(p, item.PackSize, item.PackSize_IsBin)) item.Size = item.PackSize; + item.Size_IsBin = item.PackSize_IsBin; p += 12; - RIF(ParseInt64(p, item.MTime)); p += 12; + RIF(ParseInt64_MTime(p, item.MTime, item.MTime_IsBin)) p += 12; UInt32 checkSum; - RIF(OctalToNumber32(p, 8, checkSum)); + RIF(OctalToNumber32(p, checkSum)) memset(p, ' ', 8); p += 8; item.LinkFlag = *p++; ReadString(p, NFileHeader::kNameSize, item.LinkName); p += NFileHeader::kNameSize; - item.LinkNameCouldBeReduced = + + /* + item.LinkName_CouldBeReduced = (item.LinkName.Len() == NFileHeader::kNameSize || item.LinkName.Len() == NFileHeader::kNameSize - 1); + */ memcpy(item.Magic, p, 8); p += 8; ReadString(p, NFileHeader::kUserNameSize, item.User); p += NFileHeader::kUserNameSize; ReadString(p, NFileHeader::kGroupNameSize, item.Group); p += NFileHeader::kGroupNameSize; - item.DeviceMajorDefined = (p[0] != 0); if (item.DeviceMajorDefined) { RIF(OctalToNumber32(p, 8, item.DeviceMajor)); } p += 8; - item.DeviceMinorDefined = (p[0] != 0); if (item.DeviceMinorDefined) { RIF(OctalToNumber32(p, 8, item.DeviceMinor)); } p += 8; + item.DeviceMajor_Defined = (p[0] != 0); if (item.DeviceMajor_Defined) { RIF(OctalToNumber32(p, item.DeviceMajor)) } p += 8; + item.DeviceMinor_Defined = (p[0] != 0); if (item.DeviceMinor_Defined) { RIF(OctalToNumber32(p, item.DeviceMinor)) } p += 8; - if (p[0] != 0) + if (p[0] != 0 + && item.IsMagic_ustar_5chars() + && (item.LinkFlag != 'L' )) { - AString prefix; - ReadString(p, NFileHeader::kPrefixSize, prefix); - if (!prefix.IsEmpty() - && item.IsUstarMagic() - && (item.LinkFlag != 'L' /* || prefix != "00000000000" */ )) - item.Name = prefix + '/' + item.Name; + item.Prefix_WasUsed = true; + ReadString(p, NFileHeader::kPrefixSize, item.Name); + item.Name.Add_Slash(); + unsigned i; + for (i = 0; i < NFileHeader::kNameSize; i++) + if (buf[i] == 0) + break; + item.Name.AddFrom(buf, i); } - + else + ReadString(buf, NFileHeader::kNameSize, item.Name); + p += NFileHeader::kPrefixSize; if (item.LinkFlag == NFileHeader::NLinkFlag::kHardLink) @@ -230,254 +299,853 @@ static HRESULT GetNextItemReal(ISequentialInStream *stream, bool &filled, CItemE item.PackSize = 0; item.Size = 0; } + + if (item.LinkFlag == NFileHeader::NLinkFlag::kDirectory) + { + // GNU tar ignores Size field, if LinkFlag is kDirectory + // 21.02 : we set PackSize = 0 to be more compatible with GNU tar + item.PackSize = 0; + // item.Size = 0; + } + /* TAR standard requires sum of unsigned byte values. - But some TAR programs use sum of signed byte values. + But some old TAR programs use sum of signed byte values. So we check both values. */ - UInt32 checkSumReal = 0; - Int32 checkSumReal_Signed = 0; - for (unsigned i = 0; i < NFileHeader::kRecordSize; i++) - { - char c = buf[i]; - checkSumReal_Signed += (signed char)c; - checkSumReal += (Byte)buf[i]; - } - - if (checkSumReal != checkSum) + // for (int y = 0; y < 100; y++) // for debug { - if ((UInt32)checkSumReal_Signed != checkSum) - return S_OK; + UInt32 sum0 = 0; + { + for (unsigned i = 0; i < NFileHeader::kRecordSize; i++) + sum0 += (Byte)buf[i]; + } + if (sum0 != checkSum) + { + Int32 sum = 0; + for (unsigned i = 0; i < NFileHeader::kRecordSize; i++) + sum += (signed char)buf[i]; + if ((UInt32)sum != checkSum) + return S_OK; + item.IsSignedChecksum = true; + } } item.HeaderSize += NFileHeader::kRecordSize; if (item.LinkFlag == NFileHeader::NLinkFlag::kSparse) { - Byte isExtended = buf[482]; - if (isExtended != 0 && isExtended != 1) - return S_OK; - RIF(ParseSize(buf + 483, item.Size)); - UInt64 min = 0; - for (unsigned i = 0; i < 4; i++) - { - p = buf + 386 + 24 * i; - if (GetBe32(p) == 0) - { - if (isExtended != 0) - return S_OK; - break; - } - CSparseBlock sb; - RIF(ParseSize(p, sb.Offset)); - RIF(ParseSize(p + 12, sb.Size)); - item.SparseBlocks.Add(sb); - if (sb.Offset < min || sb.Offset > item.Size) - return S_OK; - if ((sb.Offset & 0x1FF) != 0 || (sb.Size & 0x1FF) != 0) - return S_OK; - min = sb.Offset + sb.Size; - if (min < sb.Offset) - return S_OK; - } - if (min > item.Size) + // OLD GNU format: parse sparse file information: + // PackSize = cumulative size of all non-empty blocks of the file. + // We read actual file size from 'realsize' member of oldgnu_header: + RIF(ParseSize(buf + 483, item.Size, item.Size_IsBin)) + if (item.Size < item.PackSize) // additional check return S_OK; - while (isExtended != 0) - { - size_t processedSize = NFileHeader::kRecordSize; - RINOK(ReadStream(stream, buf, &processedSize)); - if (processedSize != NFileHeader::kRecordSize) - { - error = k_ErrorType_UnexpectedEnd; - return S_OK; - } + p = buf + 386; + + UInt64 end = 0, packSum = 0; + unsigned numRecords = 4; + unsigned isExtended = (Byte)p[4 * 24]; // (Byte)p[numRecords * 24]; + // the list of blocks contains non-empty blocks. All another data is empty. - item.HeaderSize += NFileHeader::kRecordSize; - isExtended = buf[21 * 24]; - if (isExtended != 0 && isExtended != 1) + for (;;) + { + // const unsigned isExtended = (Byte)p[numRecords * 24]; + if (isExtended > 1) return S_OK; - for (unsigned i = 0; i < 21; i++) + do { - p = buf + 24 * i; if (GetBe32(p) == 0) { - if (isExtended != 0) + if (isExtended) return S_OK; break; } CSparseBlock sb; - RIF(ParseSize(p, sb.Offset)); - RIF(ParseSize(p + 12, sb.Size)); - item.SparseBlocks.Add(sb); - if (sb.Offset < min || sb.Offset > item.Size) - return S_OK; - if ((sb.Offset & 0x1FF) != 0 || (sb.Size & 0x1FF) != 0) - return S_OK; - min = sb.Offset + sb.Size; - if (min < sb.Offset) + RIF(ParseSize(p, sb.Offset)) + RIF(ParseSize(p + 12, sb.Size)) + p += 24; + /* for all non-last blocks we expect : + ((sb.Size & 0x1ff) == 0) && ((sb.Offset & 0x1ff) == 0) + for last block : (sb.Size == 0) is possible. + */ + if (sb.Offset < end + || item.Size < sb.Offset + || item.Size - sb.Offset < sb.Size) return S_OK; + // optional check: + if (sb.Size && ((end & 0x1ff) || (sb.Offset & 0x1ff))) + { + item.Method_Error = true; // relaxed check + // return S_OK; + } + end = sb.Offset + sb.Size; + packSum += sb.Size; + item.SparseBlocks.Add(sb); } + while (--numRecords); + + if (!isExtended) + break; + + size_t processedSize = NFileHeader::kRecordSize; + RINOK(ReadStream(SeqStream, buf, &processedSize)) + if (processedSize != NFileHeader::kRecordSize) + { + error = k_ErrorType_UnexpectedEnd; + return S_OK; + } + item.HeaderSize += NFileHeader::kRecordSize; + RINOK(Progress(item, 0)) + p = buf; + numRecords = 21; + isExtended = (Byte)p[21 * 24]; // (Byte)p[numRecords * 24]; + } + // optional checks for strict size consistency: + if (end != item.Size || packSum != item.PackSize) + { + item.Method_Error = true; // relaxed check + // return S_OK; } - if (min > item.Size) - return S_OK; } + if (item.PackSize >= (UInt64)1 << 63) // optional check. It was checked in ParseSize() already + return S_OK; filled = true; error = k_ErrorType_OK; return S_OK; } -static HRESULT ReadDataToString(ISequentialInStream *stream, CItemEx &item, AString &s, EErrorType &error) +HRESULT CArchive::Progress(const CItemEx &item, UInt64 posOffset) { - const unsigned packSize = (unsigned)item.GetPackSizeAligned(); - size_t processedSize = packSize; - HRESULT res = ReadStream(stream, s.GetBuf(packSize), &processedSize); - item.HeaderSize += (unsigned)processedSize; - s.ReleaseBuf_CalcLen((unsigned)item.PackSize); - RINOK(res); - if (processedSize != packSize) - error = k_ErrorType_UnexpectedEnd; + if (!OpenCallback) + return S_OK; + const UInt64 pos = item.Get_DataPos() + posOffset; + if (NumFiles - NumFiles_Prev < (1 << 16) + // && NumRecords - NumRecords_Prev < (1 << 16) + && pos - Pos_Prev < ((UInt32)1 << 28)) + return S_OK; + { + Pos_Prev = pos; + NumFiles_Prev = NumFiles; + // NumRecords_Prev = NumRecords; + // Sleep(100); // for debug + return OpenCallback->SetCompleted(&NumFiles, &pos); + } +} + + +HRESULT CArchive::ReadDataToBuffer(const CItemEx &item, + CTempBuffer &tb, size_t stringLimit) +{ + tb.Init(); + UInt64 packSize = item.Get_PackSize_Aligned(); + if (packSize == 0) + return S_OK; + + UInt64 pos; + + { + size_t size = stringLimit; + if (size > packSize) + size = (size_t)packSize; + tb.Buffer.AllocAtLeast(size); + size_t processedSize = size; + const HRESULT res = ReadStream(SeqStream, tb.Buffer, &processedSize); + pos = processedSize; + if (processedSize != size) + { + error = k_ErrorType_UnexpectedEnd; + return res; + } + RINOK(res) + + packSize -= size; + + size_t i; + const Byte *p = tb.Buffer; + for (i = 0; i < size; i++) + if (p[i] == 0) + break; + if (i >= item.PackSize) + tb.StringSize_IsConfirmed = true; + if (i > item.PackSize) + { + tb.StringSize = (size_t)item.PackSize; + tb.IsNonZeroTail = true; + } + else + { + tb.StringSize = i; + if (i != size) + { + tb.StringSize_IsConfirmed = true; + if (IsBufNonZero(p + i, size - i)) + tb.IsNonZeroTail = true; + } + } + + if (packSize == 0) + return S_OK; + } + + if (InStream) + { + RINOK(InStream->Seek((Int64)packSize, STREAM_SEEK_CUR, NULL)) + return S_OK; + } + const unsigned kBufSize = 1 << 15; + Buffer.AllocAtLeast(kBufSize); + + do + { + RINOK(Progress(item, pos)) + + unsigned size = kBufSize; + if (size > packSize) + size = (unsigned)packSize; + size_t processedSize = size; + const HRESULT res = ReadStream(SeqStream, Buffer, &processedSize); + if (processedSize != size) + { + error = k_ErrorType_UnexpectedEnd; + return res; + } + if (!tb.IsNonZeroTail) + { + if (IsBufNonZero(Buffer, size)) + tb.IsNonZeroTail = true; + } + packSize -= size; + pos += size; + } + while (packSize != 0); return S_OK; } + + -static bool ParsePaxLongName(const AString &src, AString &dest) +struct CPaxInfo: public CPaxTimes { - dest.Empty(); - for (unsigned pos = 0;;) + bool DoubleTagError; + bool TagParsingError; + bool UnknownLines_Overflow; + bool Size_Defined; + bool UID_Defined; + bool GID_Defined; + bool Path_Defined; + bool Link_Defined; + bool User_Defined; + bool Group_Defined; + bool SCHILY_fflags_Defined; + + UInt64 Size; + UInt32 UID; + UInt32 GID; + + AString Path; + AString Link; + AString User; + AString Group; + AString UnknownLines; + AString SCHILY_fflags; + + bool ParseID(const AString &val, bool &defined, UInt32 &res) { - if (pos >= src.Len()) + if (defined) + DoubleTagError = true; + if (val.IsEmpty()) return false; - const char *start = src.Ptr(pos); - const char *end; - const UInt32 lineLen = ConvertStringToUInt32(start, &end); - if (end == start) + const char *end2; + res = ConvertStringToUInt32(val.Ptr(), &end2); + if (*end2 != 0) return false; - if (*end != ' ') + defined = true; + return true; + } + + bool ParsePax(const CTempBuffer &tb, bool isFile); +}; + + +static bool ParsePaxTime(const AString &src, CPaxTime &pt, bool &doubleTagError) +{ + if (pt.IsDefined()) + doubleTagError = true; + pt.Clear(); + const char *s = src.Ptr(); + bool isNegative = false; + if (*s == '-') + { + isNegative = true; + s++; + } + const char *end; + { + UInt64 sec = ConvertStringToUInt64(s, &end); + if (s == end) return false; - if (lineLen > src.Len() - pos) + if (sec >= ((UInt64)1 << 63)) return false; - unsigned offset = (unsigned)(end - start) + 1; - if (lineLen < offset) + if (isNegative) + sec = (UInt64)-(Int64)sec; + pt.Sec = (Int64)sec; + } + if (*end == 0) + { + pt.Ns = 0; + pt.NumDigits = 0; + return true; + } + if (*end != '.') + return false; + s = end + 1; + + UInt32 ns = 0; + unsigned i; + const unsigned kNsDigits = 9; + for (i = 0;; i++) + { + const char c = s[i]; + if (c == 0) + break; + if (c < '0' || c > '9') return false; - if (IsString1PrefixedByString2(src.Ptr(pos + offset), "path=")) + // we ignore digits after 9 digits as GNU TAR + if (i < kNsDigits) + { + ns *= 10; + ns += (unsigned)(c - '0'); + } + } + pt.NumDigits = (int)(i < kNsDigits ? i : kNsDigits); + while (i < kNsDigits) + { + ns *= 10; + i++; + } + if (isNegative && ns != 0) + { + pt.Sec--; + ns = (UInt32)1000 * 1000 * 1000 - ns; + } + pt.Ns = ns; + return true; +} + + +bool CPaxInfo::ParsePax(const CTempBuffer &tb, bool isFile) +{ + DoubleTagError = false; + TagParsingError = false; + UnknownLines_Overflow = false; + Size_Defined = false; + UID_Defined = false; + GID_Defined = false; + Path_Defined = false; + Link_Defined = false; + User_Defined = false; + Group_Defined = false; + SCHILY_fflags_Defined = false; + + // CPaxTimes::Clear(); + + const char *s = (const char *)(const void *)(const Byte *)tb.Buffer; + size_t rem = tb.StringSize; + + Clear(); + + AString name, val; + + while (rem != 0) + { + unsigned i; + for (i = 0;; i++) { - offset += 5; // "path=" - dest = src.Mid(pos + offset, lineLen - offset); - if (dest.IsEmpty()) + if (i > 24 || i >= rem) // we use limitation for size of (size) field return false; - if (dest.Back() != '\n') + if (s[i] == ' ') + break; + } + if (i == 0) + return false; + const char *end; + const UInt32 size = ConvertStringToUInt32(s, &end); + const unsigned offset = (unsigned)(end - s) + 1; + if (size > rem + || size <= offset + 1 + || offset != i + 1 + || s[size - 1] != '\n') + return false; + + for (i = offset; i < size; i++) + if (s[i] == 0) return false; - dest.DeleteBack(); - return true; + + for (i = offset; i < size - 1; i++) + if (s[i] == '=') + break; + if (i == size - 1) + return false; + + name.SetFrom(s + offset, i - offset); + val.SetFrom(s + i + 1, (unsigned)(size - 1 - (i + 1))); + + bool parsed = false; + if (isFile) + { + bool isDetectedName = true; + // only lower case (name) is supported + if (name.IsEqualTo("path")) + { + if (Path_Defined) + DoubleTagError = true; + Path = val; + Path_Defined = true; + parsed = true; + } + else if (name.IsEqualTo("linkpath")) + { + if (Link_Defined) + DoubleTagError = true; + Link = val; + Link_Defined = true; + parsed = true; + } + else if (name.IsEqualTo("uname")) + { + if (User_Defined) + DoubleTagError = true; + User = val; + User_Defined = true; + parsed = true; + } + else if (name.IsEqualTo("gname")) + { + if (Group_Defined) + DoubleTagError = true; + Group = val; + Group_Defined = true; + parsed = true; + } + else if (name.IsEqualTo("uid")) + { + parsed = ParseID(val, UID_Defined, UID); + } + else if (name.IsEqualTo("gid")) + { + parsed = ParseID(val, GID_Defined, GID); + } + else if (name.IsEqualTo("size")) + { + if (Size_Defined) + DoubleTagError = true; + Size_Defined = false; + if (!val.IsEmpty()) + { + const char *end2; + Size = ConvertStringToUInt64(val.Ptr(), &end2); + if (*end2 == 0) + { + Size_Defined = true; + parsed = true; + } + } + } + else if (name.IsEqualTo("mtime")) + { parsed = ParsePaxTime(val, MTime, DoubleTagError); } + else if (name.IsEqualTo("atime")) + { parsed = ParsePaxTime(val, ATime, DoubleTagError); } + else if (name.IsEqualTo("ctime")) + { parsed = ParsePaxTime(val, CTime, DoubleTagError); } + else if (name.IsEqualTo("SCHILY.fflags")) + { + if (SCHILY_fflags_Defined) + DoubleTagError = true; + SCHILY_fflags = val; + SCHILY_fflags_Defined = true; + parsed = true; + } + else + isDetectedName = false; + if (isDetectedName && !parsed) + TagParsingError = true; } - pos += lineLen; + if (!parsed) + { + if (!UnknownLines_Overflow) + { + const unsigned addSize = size - offset; + if (UnknownLines.Len() + addSize < (1 << 16)) + UnknownLines.AddFrom(s + offset, addSize); + else + UnknownLines_Overflow = true; + } + } + + s += size; + rem -= size; } + return true; } -HRESULT ReadItem(ISequentialInStream *stream, bool &filled, CItemEx &item, EErrorType &error) + +HRESULT CArchive::ReadItem2(CItemEx &item) { + // CItem + + item.SparseBlocks.Clear(); + item.PaxTimes.Clear(); + + // CItemEx + item.HeaderSize = 0; + item.Num_Pax_Records = 0; + + item.LongName_WasUsed = false; + item.LongName_WasUsed_2 = false; + + item.LongLink_WasUsed = false; + item.LongLink_WasUsed_2 = false; + + item.HeaderError = false; + item.Method_Error = false; + item.IsSignedChecksum = false; + item.Prefix_WasUsed = false; + + item.Pax_Error = false; + item.Pax_Overflow = false; + item.pax_path_WasUsed = false; + item.pax_link_WasUsed = false; + item.pax_size_WasUsed = false; + + item.PaxExtra.Clear(); + item.SCHILY_fflags.Empty(); + + item.EncodingCharacts.Clear(); + + // CArchive temp variable - bool flagL = false; - bool flagK = false; - AString nameL; - AString nameK; - AString pax; + NameBuf.Init(); + LinkBuf.Init(); + PaxBuf.Init(); + PaxBuf_global.Init(); + UInt64 numExtraRecords = 0; + for (;;) { - RINOK(GetNextItemReal(stream, filled, item, error)); + RINOK(Progress(item, 0)) + RINOK(GetNextItemReal(item)) + // NumRecords++; + if (!filled) { - if (error == k_ErrorType_OK && (flagL || flagK)) + if (error == k_ErrorType_OK) + if (numExtraRecords != 0 + || item.LongName_WasUsed + || item.LongLink_WasUsed + || item.Num_Pax_Records != 0) error = k_ErrorType_Corrupted; return S_OK; } - if (error != k_ErrorType_OK) return S_OK; - - if (item.LinkFlag == NFileHeader::NLinkFlag::kGnu_LongName || // file contains a long name - item.LinkFlag == NFileHeader::NLinkFlag::kGnu_LongLink) // file contains a long linkname - { - AString *name; - if (item.LinkFlag == NFileHeader::NLinkFlag::kGnu_LongName) - { if (flagL) return S_OK; flagL = true; name = &nameL; } - else - { if (flagK) return S_OK; flagK = true; name = &nameK; } + numExtraRecords++; + + const char lf = item.LinkFlag; + if (lf == NFileHeader::NLinkFlag::kGnu_LongName || + lf == NFileHeader::NLinkFlag::kGnu_LongLink) + { + // GNU tar ignores item.Name after LinkFlag test + // 22.00 : now we also ignore item.Name here + /* if (item.Name != NFileHeader::kLongLink && item.Name != NFileHeader::kLongLink2) + { + break; + // return S_OK; + } + */ + + CTempBuffer *tb = + lf == NFileHeader::NLinkFlag::kGnu_LongName ? + &NameBuf : + &LinkBuf; + + /* + if (item.PackSize > (1 << 29)) + { + // break; return S_OK; - if (item.PackSize > (1 << 14)) - return S_OK; + } + */ - RINOK(ReadDataToString(stream, item, *name, error)); + const unsigned kLongNameSizeMax = (unsigned)1 << 14; + RINOK(ReadDataToBuffer(item, *tb, kLongNameSizeMax)) if (error != k_ErrorType_OK) return S_OK; + if (lf == NFileHeader::NLinkFlag::kGnu_LongName) + { + item.LongName_WasUsed_2 = + item.LongName_WasUsed; + item.LongName_WasUsed = true; + } + else + { + item.LongLink_WasUsed_2 = + item.LongLink_WasUsed; + item.LongLink_WasUsed = true; + } + + if (!tb->StringSize_IsConfirmed) + tb->StringSize = 0; + item.HeaderSize += item.Get_PackSize_Aligned(); + if (tb->StringSize == 0 || + tb->StringSize + 1 != item.PackSize) + item.HeaderError = true; + if (tb->IsNonZeroTail) + item.HeaderError = true; continue; } - switch (item.LinkFlag) + if (lf == NFileHeader::NLinkFlag::kGlobal || + lf == NFileHeader::NLinkFlag::kPax || + lf == NFileHeader::NLinkFlag::kPax_2) { - case 'g': - case 'x': - case 'X': + // GNU tar ignores item.Name after LinkFlag test + // 22.00 : now we also ignore item.Name here + /* + if (item.PackSize > (UInt32)1 << 26) { - // pax Extended Header - if (item.Name.IsPrefixedBy("PaxHeader/")) - { - RINOK(ReadDataToString(stream, item, pax, error)); - if (error != k_ErrorType_OK) - return S_OK; - continue; - } - - break; + break; // we don't want big PaxBuf files + // return S_OK; } - case NFileHeader::NLinkFlag::kDumpDir: + */ + const unsigned kParsingPaxSizeMax = (unsigned)1 << 26; + + const bool isStartHeader = (item.HeaderSize == NFileHeader::kRecordSize); + + CTempBuffer *tb = (lf == NFileHeader::NLinkFlag::kGlobal ? &PaxBuf_global : &PaxBuf); + + RINOK(ReadDataToBuffer(item, *tb, kParsingPaxSizeMax)) + if (error != k_ErrorType_OK) + return S_OK; + + item.HeaderSize += item.Get_PackSize_Aligned(); + + if (tb->StringSize != item.PackSize + || tb->StringSize == 0 + || tb->IsNonZeroTail) + item.Pax_Error = true; + + item.Num_Pax_Records++; + if (lf != NFileHeader::NLinkFlag::kGlobal) { - break; - // GNU Extensions to the Archive Format + item.PaxExtra.RecordPath = item.Name; + continue; } - case NFileHeader::NLinkFlag::kSparse: + // break; // for debug { - break; - // GNU Extensions to the Archive Format + if (PaxGlobal_Defined) + _is_PaxGlobal_Error = true; + CPaxInfo paxInfo; + if (paxInfo.ParsePax(PaxBuf_global, false)) + { + PaxGlobal.RawLines = paxInfo.UnknownLines; + PaxGlobal.RecordPath = item.Name; + PaxGlobal_Defined = true; + } + else + _is_PaxGlobal_Error = true; + + if (isStartHeader + && item.Num_Pax_Records == 1 + && numExtraRecords == 1) + { + // we skip global pax header info after parsing + item.HeaderPos += item.HeaderSize; + item.HeaderSize = 0; + item.Num_Pax_Records = 0; + numExtraRecords = 0; + } + else + _is_PaxGlobal_Error = true; } - default: - if (item.LinkFlag > '7' || (item.LinkFlag < '0' && item.LinkFlag != 0)) - return S_OK; + continue; } - if (flagL) + /* + if (lf == NFileHeader::NLinkFlag::kDumpDir || + lf == NFileHeader::NLinkFlag::kSparse) { - item.Name = nameL; - item.NameCouldBeReduced = false; + // GNU Extensions to the Archive Format + break; } + if (lf > '7' || (lf < '0' && lf != 0)) + { + break; + // return S_OK; + } + */ + break; + } - if (flagK) + // we still use name from main header, if long_name is bad + if (item.LongName_WasUsed && NameBuf.StringSize != 0) + { + NameBuf.CopyToString(item.Name); + // item.Name_CouldBeReduced = false; + } + + if (item.LongLink_WasUsed) + { + // we use empty link, if long_link is bad + LinkBuf.CopyToString(item.LinkName); + // item.LinkName_CouldBeReduced = false; + } + + error = k_ErrorType_OK; + + if (PaxBuf.StringSize != 0) + { + CPaxInfo paxInfo; + if (!paxInfo.ParsePax(PaxBuf, true)) + item.Pax_Error = true; + else { - item.LinkName = nameK; - item.LinkNameCouldBeReduced = false; + if (paxInfo.Path_Defined) // if (!paxInfo.Path.IsEmpty()) + { + item.Name = paxInfo.Path; + item.pax_path_WasUsed = true; + } + if (paxInfo.Link_Defined) // (!paxInfo.Link.IsEmpty()) + { + item.LinkName = paxInfo.Link; + item.pax_link_WasUsed = true; + } + if (paxInfo.User_Defined) + { + item.User = paxInfo.User; + // item.pax_uname_WasUsed = true; + } + if (paxInfo.Group_Defined) + { + item.Group = paxInfo.Group; + // item.pax_gname_WasUsed = true; + } + if (paxInfo.SCHILY_fflags_Defined) + { + item.SCHILY_fflags = paxInfo.SCHILY_fflags; + // item.SCHILY_fflags_WasUsed = true; + } + if (paxInfo.UID_Defined) + { + item.UID = (UInt32)paxInfo.UID; + } + if (paxInfo.GID_Defined) + { + item.GID = (UInt32)paxInfo.GID; + } + + if (paxInfo.Size_Defined) + { + const UInt64 piSize = paxInfo.Size; + // GNU TAR ignores (item.Size) in that case + if (item.Size != 0 && item.Size != piSize) + item.Pax_Error = true; + if (piSize >= ((UInt64)1 << 63)) + item.Pax_Error = true; + else + { + item.Size = piSize; + item.PackSize = piSize; + item.pax_size_WasUsed = true; + } + } + + item.PaxTimes = paxInfo; + item.PaxExtra.RawLines = paxInfo.UnknownLines; + if (paxInfo.UnknownLines_Overflow) + item.Pax_Overflow = true; + if (paxInfo.TagParsingError) + item.Pax_Error = true; + if (paxInfo.DoubleTagError) + item.Pax_Error = true; } + } - error = k_ErrorType_OK; + return S_OK; +} - if (!pax.IsEmpty()) - { - AString name; - if (ParsePaxLongName(pax, name)) - item.Name = name; - else - error = k_ErrorType_Warning; - } - return S_OK; + +HRESULT CArchive::ReadItem(CItemEx &item) +{ + error = k_ErrorType_OK; + filled = false; + item.HeaderPos = _phySize; + + const HRESULT res = ReadItem2(item); + + /* + if (error == k_ErrorType_Warning) + _is_Warning = true; + else + */ + + if (error != k_ErrorType_OK) + _error = error; + + RINOK(res) + + if (filled) + { + if (item.IsMagic_GNU()) + _are_Gnu = true; + else if (item.IsMagic_Posix_ustar_00()) + _are_Posix = true; + + if (item.Num_Pax_Records != 0) + _are_Pax = true; + + if (item.PaxTimes.MTime.IsDefined()) _are_mtime = true; + if (item.PaxTimes.ATime.IsDefined()) _are_atime = true; + if (item.PaxTimes.CTime.IsDefined()) _are_ctime = true; + if (!item.SCHILY_fflags.IsEmpty()) _are_SCHILY_fflags = true; + + if (item.pax_path_WasUsed) + _are_pax_path = true; + if (item.pax_link_WasUsed) + _are_pax_link = true; + if (item.LongName_WasUsed) + _are_LongName = true; + if (item.LongLink_WasUsed) + _are_LongLink = true; + if (item.Prefix_WasUsed) + _pathPrefix_WasUsed = true; + /* + if (item.IsSparse()) + _isSparse = true; + */ + if (item.Is_PaxExtendedHeader()) + _are_Pax_Items = true; + if (item.IsThereWarning() + || item.HeaderError + || item.Pax_Error) + _is_Warning = true; } + + const UInt64 headerEnd = item.HeaderPos + item.HeaderSize; + // _headersSize += headerEnd - _phySize; + // we don't count skipped records + _headersSize += item.HeaderSize; + _phySize = headerEnd; + return S_OK; } }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarIn.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarIn.h index 1c508bcc..edfc94a0 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarIn.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarIn.h @@ -1,9 +1,9 @@ // TarIn.h -#ifndef __ARCHIVE_TAR_IN_H -#define __ARCHIVE_TAR_IN_H +#ifndef ZIP7_INC_ARCHIVE_TAR_IN_H +#define ZIP7_INC_ARCHIVE_TAR_IN_H -#include "../../IStream.h" +#include "../IArchive.h" #include "TarItem.h" @@ -14,11 +14,135 @@ enum EErrorType { k_ErrorType_OK, k_ErrorType_Corrupted, - k_ErrorType_UnexpectedEnd, - k_ErrorType_Warning + k_ErrorType_UnexpectedEnd + // , k_ErrorType_Warning +}; + + +struct CTempBuffer +{ + CByteBuffer Buffer; + size_t StringSize; // num characters before zero Byte (StringSize <= item.PackSize) + bool IsNonZeroTail; + bool StringSize_IsConfirmed; + + void CopyToString(AString &s) + { + s.Empty(); + if (StringSize != 0) + s.SetFrom((const char *)(const void *)(const Byte *)Buffer, (unsigned)StringSize); + } + + void Init() + { + StringSize = 0; + IsNonZeroTail = false; + StringSize_IsConfirmed = false; + } +}; + + +class CArchive +{ +public: + bool _phySize_Defined; + bool _is_Warning; + bool PaxGlobal_Defined; + bool _is_PaxGlobal_Error; + bool _are_Pax_Items; + bool _are_Gnu; + bool _are_Posix; + bool _are_Pax; + bool _are_mtime; + bool _are_atime; + bool _are_ctime; + bool _are_pax_path; + bool _are_pax_link; + bool _are_LongName; + bool _are_LongLink; + bool _pathPrefix_WasUsed; + bool _are_SCHILY_fflags; + // bool _isSparse; + + // temp internal vars for ReadItem(): + bool filled; +private: + EErrorType error; + +public: + UInt64 _phySize; + UInt64 _headersSize; + EErrorType _error; + + ISequentialInStream *SeqStream; + IInStream *InStream; + IArchiveOpenCallback *OpenCallback; + UInt64 NumFiles; + UInt64 NumFiles_Prev; + UInt64 Pos_Prev; + // UInt64 NumRecords; + // UInt64 NumRecords_Prev; + + CPaxExtra PaxGlobal; + + void Clear() + { + SeqStream = NULL; + InStream = NULL; + OpenCallback = NULL; + NumFiles = 0; + NumFiles_Prev = 0; + Pos_Prev = 0; + // NumRecords = 0; + // NumRecords_Prev = 0; + + PaxGlobal.Clear(); + PaxGlobal_Defined = false; + _is_PaxGlobal_Error = false; + _are_Pax_Items = false; // if there are final paxItems + _are_Gnu = false; + _are_Posix = false; + _are_Pax = false; + _are_mtime = false; + _are_atime = false; + _are_ctime = false; + _are_pax_path = false; + _are_pax_link = false; + _are_LongName = false; + _are_LongLink = false; + _pathPrefix_WasUsed = false; + _are_SCHILY_fflags = false; + // _isSparse = false; + + _is_Warning = false; + _error = k_ErrorType_OK; + + _phySize_Defined = false; + _phySize = 0; + _headersSize = 0; + } + +private: + CTempBuffer NameBuf; + CTempBuffer LinkBuf; + CTempBuffer PaxBuf; + CTempBuffer PaxBuf_global; + + CByteBuffer Buffer; + + HRESULT ReadDataToBuffer(const CItemEx &item, CTempBuffer &tb, size_t stringLimit); + HRESULT Progress(const CItemEx &item, UInt64 posOffset); + HRESULT GetNextItemReal(CItemEx &item); + HRESULT ReadItem2(CItemEx &itemInfo); +public: + CArchive() + { + // we will call Clear() in CHandler::Close(). + // Clear(); // it's not required here + } + HRESULT ReadItem(CItemEx &itemInfo); }; -HRESULT ReadItem(ISequentialInStream *stream, bool &filled, CItemEx &itemInfo, EErrorType &error); API_FUNC_IsArc IsArc_Tar(const Byte *p, size_t size); diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarItem.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarItem.h index 5245aaa4..d4e2ea55 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarItem.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarItem.h @@ -1,9 +1,10 @@ // TarItem.h -#ifndef __ARCHIVE_TAR_ITEM_H -#define __ARCHIVE_TAR_ITEM_H +#ifndef ZIP7_INC_ARCHIVE_TAR_ITEM_H +#define ZIP7_INC_ARCHIVE_TAR_ITEM_H -#include "../Common/ItemNameUtils.h" +#include "../../../Common/MyLinux.h" +#include "../../../Common/UTFConvert.h" #include "TarHeader.h" @@ -16,46 +17,193 @@ struct CSparseBlock UInt64 Size; }; + +enum EPaxTimeRemoveZeroMode +{ + k_PaxTimeMode_DontRemoveZero, + k_PaxTimeMode_RemoveZero_if_PureSecondOnly, + k_PaxTimeMode_RemoveZero_Always +}; + +struct CTimeOptions +{ + EPaxTimeRemoveZeroMode RemoveZeroMode; + unsigned NumDigitsMax; + + void Init() + { + RemoveZeroMode = k_PaxTimeMode_RemoveZero_if_PureSecondOnly; + NumDigitsMax = 0; + } + CTimeOptions() { Init(); } +}; + + +struct CPaxTime +{ + Int32 NumDigits; // -1 means undefined + UInt32 Ns; // it's smaller than 1G. Even if (Sec < 0), larger (Ns) value means newer files. + Int64 Sec; // can be negative + + Int64 GetSec() const { return NumDigits != -1 ? Sec : 0; } + + bool IsDefined() const { return NumDigits != -1; } + // bool IsDefined_And_nonZero() const { return NumDigits != -1 && (Sec != 0 || Ns != 0); } + + void Clear() + { + NumDigits = -1; + Ns = 0; + Sec = 0; + } + CPaxTime() { Clear(); } + + /* + void ReducePrecison(int numDigits) + { + // we don't use this->NumDigits here + if (numDigits > 0) + { + if (numDigits >= 9) + return; + UInt32 r = 1; + for (unsigned i = numDigits; i < 9; i++) + r *= 10; + Ns /= r; + Ns *= r; + return; + } + Ns = 0; + if (numDigits == 0) + return; + UInt32 r; + if (numDigits == -1) r = 60; + else if (numDigits == -2) r = 60 * 60; + else if (numDigits == -3) r = 60 * 60 * 24; + else return; + Sec /= r; + Sec *= r; + } + */ +}; + + +struct CPaxTimes +{ + CPaxTime MTime; + CPaxTime ATime; + CPaxTime CTime; + + void Clear() + { + MTime.Clear(); + ATime.Clear(); + CTime.Clear(); + } + + /* + void ReducePrecison(int numDigits) + { + MTime.ReducePrecison(numDigits); + CTime.ReducePrecison(numDigits); + ATime.ReducePrecison(numDigits); + } + */ +}; + + struct CItem { - AString Name; UInt64 PackSize; UInt64 Size; Int64 MTime; + char LinkFlag; + bool DeviceMajor_Defined; + bool DeviceMinor_Defined; + UInt32 Mode; UInt32 UID; UInt32 GID; UInt32 DeviceMajor; UInt32 DeviceMinor; + AString Name; AString LinkName; AString User; AString Group; char Magic[8]; - char LinkFlag; - bool DeviceMajorDefined; - bool DeviceMinorDefined; + + CPaxTimes PaxTimes; CRecordVector SparseBlocks; - bool IsSymLink() const { return LinkFlag == NFileHeader::NLinkFlag::kSymLink && (Size == 0); } - bool IsHardLink() const { return LinkFlag == NFileHeader::NLinkFlag::kHardLink; } - bool IsSparse() const { return LinkFlag == NFileHeader::NLinkFlag::kSparse; } - UInt64 GetUnpackSize() const { return IsSymLink() ? LinkName.Len() : Size; } - bool IsPaxExtendedHeader() const + void SetMagic_Posix(bool posixMode) + { + memcpy(Magic, posixMode ? + NFileHeader::NMagic::k_Posix_ustar_00 : + NFileHeader::NMagic::k_GNU_ustar, + 8); + } + + bool Is_SymLink() const { return LinkFlag == NFileHeader::NLinkFlag::kSymLink && (Size == 0); } + bool Is_HardLink() const { return LinkFlag == NFileHeader::NLinkFlag::kHardLink; } + bool Is_Sparse() const { return LinkFlag == NFileHeader::NLinkFlag::kSparse; } + + UInt64 Get_UnpackSize() const { return Is_SymLink() ? LinkName.Len() : Size; } + + bool Is_PaxExtendedHeader() const { switch (LinkFlag) { - case 'g': - case 'x': - case 'X': // Check it + case NFileHeader::NLinkFlag::kPax: + case NFileHeader::NLinkFlag::kPax_2: + case NFileHeader::NLinkFlag::kGlobal: return true; + default: break; } return false; } + UInt32 Get_Combined_Mode() const + { + return (Mode & ~(UInt32)MY_LIN_S_IFMT) | Get_FileTypeMode_from_LinkFlag(); + } + + void Set_LinkFlag_for_File(UInt32 mode) + { + char lf = NFileHeader::NLinkFlag::kNormal; + if (MY_LIN_S_ISCHR(mode)) lf = NFileHeader::NLinkFlag::kCharacter; + else if (MY_LIN_S_ISBLK(mode)) lf = NFileHeader::NLinkFlag::kBlock; + else if (MY_LIN_S_ISFIFO(mode)) lf = NFileHeader::NLinkFlag::kFIFO; + // else if (MY_LIN_S_ISDIR(mode)) lf = NFileHeader::NLinkFlag::kDirectory; + // else if (MY_LIN_S_ISLNK(mode)) lf = NFileHeader::NLinkFlag::kSymLink; + LinkFlag = lf; + } + + UInt32 Get_FileTypeMode_from_LinkFlag() const + { + switch (LinkFlag) + { + /* + case NFileHeader::NLinkFlag::kDirectory: + case NFileHeader::NLinkFlag::kDumpDir: + return MY_LIN_S_IFDIR; + */ + case NFileHeader::NLinkFlag::kSymLink: return MY_LIN_S_IFLNK; + case NFileHeader::NLinkFlag::kBlock: return MY_LIN_S_IFBLK; + case NFileHeader::NLinkFlag::kCharacter: return MY_LIN_S_IFCHR; + case NFileHeader::NLinkFlag::kFIFO: return MY_LIN_S_IFIFO; + // case return MY_LIN_S_IFSOCK; + default: break; + } + + if (IsDir()) + return MY_LIN_S_IFDIR; + return MY_LIN_S_IFREG; + } + bool IsDir() const { switch (LinkFlag) @@ -66,31 +214,150 @@ struct CItem case NFileHeader::NLinkFlag::kOldNormal: case NFileHeader::NLinkFlag::kNormal: case NFileHeader::NLinkFlag::kSymLink: - return NItemName::HasTailSlash(Name, CP_OEMCP); + if (Name.IsEmpty()) + return false; + // GNU TAR uses last character as directory marker + // we also do it + return Name.Back() == '/'; + // return NItemName::HasTailSlash(Name, CP_OEMCP); + default: break; } return false; } - bool IsUstarMagic() const + bool IsMagic_ustar_5chars() const + { + for (unsigned i = 0; i < 5; i++) + if (Magic[i] != NFileHeader::NMagic::k_GNU_ustar[i]) + return false; + return true; + } + + bool IsMagic_Posix_ustar_00() const + { + for (unsigned i = 0; i < 8; i++) + if (Magic[i] != NFileHeader::NMagic::k_Posix_ustar_00[i]) + return false; + return true; + } + + bool IsMagic_GNU() const { - for (int i = 0; i < 5; i++) - if (Magic[i] != NFileHeader::NMagic::kUsTar_00[i]) + for (unsigned i = 0; i < 8; i++) + if (Magic[i] != NFileHeader::NMagic::k_GNU_ustar[i]) return false; return true; } - UInt64 GetPackSizeAligned() const { return (PackSize + 0x1FF) & (~((UInt64)0x1FF)); } + UInt64 Get_PackSize_Aligned() const { return (PackSize + 0x1FF) & (~((UInt64)0x1FF)); } + + bool IsThereWarning() const + { + // that Header Warning is possible if (Size != 0) for dir item + return (PackSize < Size) && (LinkFlag == NFileHeader::NLinkFlag::kDirectory); + } }; + + +struct CEncodingCharacts +{ + bool IsAscii; + // bool Oem_Checked; + // bool Oem_Ok; + // bool Utf_Checked; + CUtf8Check UtfCheck; + + void Clear() + { + IsAscii = true; + // Oem_Checked = false; + // Oem_Ok = false; + // Utf_Checked = false; + UtfCheck.Clear(); + } + + void Update(const CEncodingCharacts &ec) + { + if (!ec.IsAscii) + IsAscii = false; + + // if (ec.Utf_Checked) + { + UtfCheck.Update(ec.UtfCheck); + // Utf_Checked = true; + } + } + + CEncodingCharacts() { Clear(); } + void Check(const AString &s); + AString GetCharactsString() const; +}; + + +struct CPaxExtra +{ + AString RecordPath; + AString RawLines; + + void Clear() + { + RecordPath.Empty(); + RawLines.Empty(); + } + + void Print_To_String(AString &s) const + { + if (!RecordPath.IsEmpty()) + { + s += RecordPath; + s.Add_LF(); + } + if (!RawLines.IsEmpty()) + s += RawLines; + } +}; + + struct CItemEx: public CItem { + bool HeaderError; + bool Method_Error; + + bool IsSignedChecksum; + bool Prefix_WasUsed; + + bool Pax_Error; + bool Pax_Overflow; + bool pax_path_WasUsed; + bool pax_link_WasUsed; + bool pax_size_WasUsed; + + bool MTime_IsBin; + bool PackSize_IsBin; + bool Size_IsBin; + + bool LongName_WasUsed; + bool LongName_WasUsed_2; + + bool LongLink_WasUsed; + bool LongLink_WasUsed_2; + + // bool Name_CouldBeReduced; + // bool LinkName_CouldBeReduced; + UInt64 HeaderPos; - unsigned HeaderSize; - bool NameCouldBeReduced; - bool LinkNameCouldBeReduced; + UInt64 HeaderSize; + + UInt64 Num_Pax_Records; + CPaxExtra PaxExtra; + AString SCHILY_fflags; + + CEncodingCharacts EncodingCharacts; - UInt64 GetDataPosition() const { return HeaderPos + HeaderSize; } - UInt64 GetFullSize() const { return HeaderSize + PackSize; } + UInt64 Get_DataPos() const { return HeaderPos + HeaderSize; } + // UInt64 GetFullSize() const { return HeaderSize + PackSize; } + UInt64 Get_FullSize_Aligned() const { return HeaderSize + Get_PackSize_Aligned(); } }; }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarOut.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarOut.cpp index 51081e8b..8a80c0c2 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarOut.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarOut.cpp @@ -2,6 +2,10 @@ #include "StdAfx.h" +#include "../../../../C/7zCrc.h" + +#include "../../../Common/IntToString.h" + #include "../../Common/StreamUtils.h" #include "TarOut.h" @@ -9,34 +13,33 @@ namespace NArchive { namespace NTar { -HRESULT COutArchive::WriteBytes(const void *data, unsigned size) -{ - Pos += size; - return WriteStream(m_Stream, data, size); -} +using namespace NFileHeader; -static void MyStrNCpy(char *dest, const char *src, unsigned size) -{ - for (unsigned i = 0; i < size; i++) - { - char c = src[i]; - dest[i] = c; - if (c == 0) - break; - } -} +// it's path prefix assigned by 7-Zip to show that file path was cut +#define K_PREFIX_PATH_CUT "@PathCut" -static bool WriteOctal_8(char *s, UInt32 val) +static const UInt32 k_7_oct_digits_Val_Max = ((UInt32)1 << (7 * 3)) - 1; + +static void WriteOctal_8(char *s, UInt32 val) { const unsigned kNumDigits = 8 - 1; if (val >= ((UInt32)1 << (kNumDigits * 3))) - return false; + { + val = 0; + // return false; + } for (unsigned i = 0; i < kNumDigits; i++) { s[kNumDigits - 1 - i] = (char)('0' + (val & 7)); val >>= 3; } - return true; + // return true; +} + +static void WriteBin_64bit(char *s, UInt64 val) +{ + for (unsigned i = 0; i < 8; i++, val <<= 8) + s[i] = (char)(val >> 56); } static void WriteOctal_12(char *s, UInt64 val) @@ -47,8 +50,7 @@ static void WriteOctal_12(char *s, UInt64 val) // GNU extension; s[0] = (char)(Byte)0x80; s[1] = s[2] = s[3] = 0; - for (unsigned i = 0; i < 8; i++, val <<= 8) - s[4 + i] = (char)(val >> 56); + WriteBin_64bit(s + 4, val); return; } for (unsigned i = 0; i < kNumDigits; i++) @@ -58,67 +60,109 @@ static void WriteOctal_12(char *s, UInt64 val) } } -static void WriteOctal_12_Signed(char *s, Int64 val) +static void WriteOctal_12_Signed(char *s, const Int64 val) { if (val >= 0) { - WriteOctal_12(s, val); + WriteOctal_12(s, (UInt64)val); return; } s[0] = s[1] = s[2] = s[3] = (char)(Byte)0xFF; - for (unsigned i = 0; i < 8; i++, val <<= 8) - s[4 + i] = (char)(val >> 56); + WriteBin_64bit(s + 4, (UInt64)val); } -static bool CopyString(char *dest, const AString &src, unsigned maxSize) +static void CopyString(char *dest, const AString &src, const unsigned maxSize) { - if (src.Len() >= maxSize) - return false; - MyStringCopy(dest, (const char *)src); - return true; + unsigned len = src.Len(); + if (len == 0) + return; + // 21.07: new gnu : we don't require additional 0 character at the end + // if (len >= maxSize) + if (len > maxSize) + { + len = maxSize; + /* + // oldgnu needs 0 character at the end + len = maxSize - 1; + dest[len] = 0; + */ + } + memcpy(dest, src.Ptr(), len); } -#define RETURN_IF_NOT_TRUE(x) { if (!(x)) return E_FAIL; } +// #define RETURN_IF_NOT_TRUE(x) { if (!(x)) return E_INVALIDARG; } +#define RETURN_IF_NOT_TRUE(x) { x; } + +#define COPY_STRING_CHECK(dest, src, size) \ + CopyString(dest, src, size); dest += (size); -HRESULT COutArchive::WriteHeaderReal(const CItem &item) +#define WRITE_OCTAL_8_CHECK(dest, src) \ + RETURN_IF_NOT_TRUE(WriteOctal_8(dest, src)) + + +HRESULT COutArchive::WriteHeaderReal(const CItem &item, bool isPax + // , bool zero_PackSize + // , bool zero_MTime + ) { - char record[NFileHeader::kRecordSize]; - memset(record, 0, NFileHeader::kRecordSize); + /* + if (isPax) { we don't use Glob_Name and Prefix } + if (!isPax) + { + we use Glob_Name if it's not empty + we use Prefix if it's not empty + } + */ + char record[kRecordSize]; + memset(record, 0, kRecordSize); char *cur = record; - if (item.Name.Len() > NFileHeader::kNameSize) - return E_FAIL; - MyStrNCpy(cur, item.Name, NFileHeader::kNameSize); - cur += NFileHeader::kNameSize; + COPY_STRING_CHECK (cur, + (!isPax && !Glob_Name.IsEmpty()) ? Glob_Name : item.Name, + kNameSize) - RETURN_IF_NOT_TRUE(WriteOctal_8(cur, item.Mode)); cur += 8; - RETURN_IF_NOT_TRUE(WriteOctal_8(cur, item.UID)); cur += 8; - RETURN_IF_NOT_TRUE(WriteOctal_8(cur, item.GID)); cur += 8; + WRITE_OCTAL_8_CHECK (cur, item.Mode) cur += 8; // & k_7_oct_digits_Val_Max + WRITE_OCTAL_8_CHECK (cur, item.UID) cur += 8; + WRITE_OCTAL_8_CHECK (cur, item.GID) cur += 8; - WriteOctal_12(cur, item.PackSize); cur += 12; - WriteOctal_12_Signed(cur, item.MTime); cur += 12; + WriteOctal_12 (cur, /* zero_PackSize ? 0 : */ item.PackSize); cur += 12; + WriteOctal_12_Signed (cur, /* zero_MTime ? 0 : */ item.MTime); cur += 12; - memset(cur, ' ', 8); + // we will use binary init for checksum instead of memset + // checksum field: + // memset(cur, ' ', 8); cur += 8; *cur++ = item.LinkFlag; - RETURN_IF_NOT_TRUE(CopyString(cur, item.LinkName, NFileHeader::kNameSize)); - cur += NFileHeader::kNameSize; + COPY_STRING_CHECK (cur, item.LinkName, kNameSize) memcpy(cur, item.Magic, 8); cur += 8; - RETURN_IF_NOT_TRUE(CopyString(cur, item.User, NFileHeader::kUserNameSize)); - cur += NFileHeader::kUserNameSize; - RETURN_IF_NOT_TRUE(CopyString(cur, item.Group, NFileHeader::kGroupNameSize)); - cur += NFileHeader::kGroupNameSize; + COPY_STRING_CHECK (cur, item.User, kUserNameSize) + COPY_STRING_CHECK (cur, item.Group, kGroupNameSize) + const bool needDevice = (IsPosixMode && !isPax); + + if (item.DeviceMajor_Defined) + WRITE_OCTAL_8_CHECK (cur, item.DeviceMajor) + else if (needDevice) + WRITE_OCTAL_8_CHECK (cur, 0) + cur += 8; + + if (item.DeviceMinor_Defined) + WRITE_OCTAL_8_CHECK (cur, item.DeviceMinor) + else if (needDevice) + WRITE_OCTAL_8_CHECK (cur, 0) + cur += 8; - if (item.DeviceMajorDefined) RETURN_IF_NOT_TRUE(WriteOctal_8(cur, item.DeviceMajor)); cur += 8; - if (item.DeviceMinorDefined) RETURN_IF_NOT_TRUE(WriteOctal_8(cur, item.DeviceMinor)); cur += 8; + if (!isPax && !Prefix.IsEmpty()) + { + COPY_STRING_CHECK (cur, Prefix, kPrefixSize) + } - if (item.IsSparse()) + if (item.Is_Sparse()) { record[482] = (char)(item.SparseBlocks.Size() > 4 ? 1 : 0); WriteOctal_12(record + 483, item.Size); @@ -132,31 +176,31 @@ HRESULT COutArchive::WriteHeaderReal(const CItem &item) } { - UInt32 checkSum = 0; + UInt32 sum = (unsigned)(' ') * 8; // we use binary init { - for (unsigned i = 0; i < NFileHeader::kRecordSize; i++) - checkSum += (Byte)record[i]; + for (unsigned i = 0; i < kRecordSize; i++) + sum += (Byte)record[i]; } - /* we use GNU TAR scheme: - checksum field is formatted differently from the + /* checksum field is formatted differently from the other fields: it has [6] digits, a null, then a space. */ - // RETURN_IF_NOT_TRUE(WriteOctal_8(record + 148, checkSum)); + // WRITE_OCTAL_8_CHECK(record + 148, sum); const unsigned kNumDigits = 6; for (unsigned i = 0; i < kNumDigits; i++) { - record[148 + kNumDigits - 1 - i] = (char)('0' + (checkSum & 7)); - checkSum >>= 3; + record[148 + kNumDigits - 1 - i] = (char)('0' + (sum & 7)); + sum >>= 3; } - record[148 + 6] = 0; + // record[148 + 6] = 0; // we need it, if we use memset(' ') init + record[148 + 7] = ' '; // we need it, if we use binary init } - RINOK(WriteBytes(record, NFileHeader::kRecordSize)); + RINOK(Write_Data(record, kRecordSize)) - if (item.IsSparse()) + if (item.Is_Sparse()) { for (unsigned i = 4; i < item.SparseBlocks.Size();) { - memset(record, 0, NFileHeader::kRecordSize); + memset(record, 0, kRecordSize); for (unsigned t = 0; t < 21 && i < item.SparseBlocks.Size(); t++, i++) { const CSparseBlock &sb = item.SparseBlocks[i]; @@ -165,79 +209,434 @@ HRESULT COutArchive::WriteHeaderReal(const CItem &item) WriteOctal_12(p + 12, sb.Size); } record[21 * 24] = (char)(i < item.SparseBlocks.Size() ? 1 : 0); - RINOK(WriteBytes(record, NFileHeader::kRecordSize)); + RINOK(Write_Data(record, kRecordSize)) } } return S_OK; } + +static void AddPaxLine(AString &s, const char *name, const AString &val) +{ + // s.Add_LF(); // for debug + const unsigned len = 3 + (unsigned)strlen(name) + val.Len(); + AString n; + for (unsigned numDigits = 1;; numDigits++) + { + n.Empty(); + n.Add_UInt32(numDigits + len); + if (numDigits == n.Len()) + break; + } + s += n; + s.Add_Space(); + s += name; + s.Add_Char('='); + s += val; + s.Add_LF(); +} + +// pt is defined : (pt.NumDigits >= 0) +static void AddPaxTime(AString &s, const char *name, const CPaxTime &pt, + const CTimeOptions &options) +{ + unsigned numDigits = (unsigned)pt.NumDigits; + if (numDigits > options.NumDigitsMax) + numDigits = options.NumDigitsMax; + + bool needNs = false; + UInt32 ns = 0; + if (numDigits != 0) + { + ns = pt.Ns; + // if (ns != 0) before reduction, we show all digits after digits reduction + needNs = (ns != 0 || options.RemoveZeroMode == k_PaxTimeMode_DontRemoveZero); + UInt32 d = 1; + for (unsigned k = numDigits; k < 9; k++) + d *= 10; + ns /= d; + ns *= d; + } + + AString v; + { + Int64 sec = pt.Sec; + if (pt.Sec < 0) + { + sec = -sec; + v.Add_Minus(); + if (ns != 0) + { + ns = 1000*1000*1000 - ns; + sec--; + } + } + v.Add_UInt64((UInt64)sec); + } + + if (needNs) + { + AString d; + d.Add_UInt32(ns); + while (d.Len() < 9) + d.InsertAtFront('0'); + // here we have precision + while (d.Len() > (unsigned)numDigits) + d.DeleteBack(); + // GNU TAR reduces '0' digits. + if (options.RemoveZeroMode == k_PaxTimeMode_RemoveZero_Always) + while (!d.IsEmpty() && d.Back() == '0') + d.DeleteBack(); + + if (!d.IsEmpty()) + { + v.Add_Dot(); + v += d; + // v += "1234567009999"; // for debug + // for (int y = 0; y < 1000; y++) v += '8'; // for debug + } + } + + AddPaxLine(s, name, v); +} + + +static void AddPax_UInt32_ifBig(AString &s, const char *name, const UInt32 &v) +{ + if (v > k_7_oct_digits_Val_Max) + { + AString s2; + s2.Add_UInt32(v); + AddPaxLine(s, name, s2); + } +} + + +/* OLD_GNU_TAR: writes name with zero at the end + NEW_GNU_TAR: can write name filled with all kNameSize characters */ + +static const unsigned kNameSize_Max = + kNameSize; // NEW_GNU_TAR / 7-Zip 21.07 + // kNameSize - 1; // OLD_GNU_TAR / old 7-Zip + +#define DOES_NAME_FIT_IN_FIELD(name) ((name).Len() <= kNameSize_Max) + + HRESULT COutArchive::WriteHeader(const CItem &item) { - unsigned nameSize = item.Name.Len(); - unsigned linkSize = item.LinkName.Len(); - - /* There two versions of GNU tar: - OLDGNU_FORMAT: it writes short name and zero at the end - GNU_FORMAT: it writes only short name without zero at the end - we write it as OLDGNU_FORMAT with zero at the end */ - - if (nameSize < NFileHeader::kNameSize && - linkSize < NFileHeader::kNameSize) - return WriteHeaderReal(item); - - CItem mi = item; - mi.Name = NFileHeader::kLongLink; - mi.LinkName.Empty(); - for (int i = 0; i < 2; i++) - { - const AString *name; - // We suppose that GNU tar also writes item for long link before item for LongName? - if (i == 0) + Glob_Name.Empty(); + Prefix.Empty(); + + unsigned namePos = 0; + bool needPathCut = false; + bool allowPrefix = false; + + if (!DOES_NAME_FIT_IN_FIELD(item.Name)) + { + const char *s = item.Name; + const char *p = s + item.Name.Len() - 1; + for (; *p == '/' && p != s; p--) + {} + for (; p != s && p[-1] != '/'; p--) + {} + namePos = (unsigned)(p - s); + needPathCut = true; + } + + if (IsPosixMode) + { + AString s; + + if (needPathCut) + { + const unsigned nameLen = item.Name.Len() - namePos; + if ( item.LinkFlag >= NLinkFlag::kNormal + && item.LinkFlag <= NLinkFlag::kDirectory + && namePos > 1 + && nameLen != 0 + // && IsPrefixAllowed + && item.IsMagic_Posix_ustar_00()) + { + /* GNU TAR decoder supports prefix field, only if (magic) + signature matches 6-bytes "ustar\0". + so here we use prefix field only in posix mode with posix signature */ + + allowPrefix = true; + // allowPrefix = false; // for debug + if (namePos <= kPrefixSize + 1 && nameLen <= kNameSize_Max) + { + needPathCut = false; + /* we will set Prefix and Glob_Name later, for such conditions: + if (!DOES_NAME_FIT_IN_FIELD(item.Name) && !needPathCut) */ + } + } + + if (needPathCut) + AddPaxLine(s, "path", item.Name); + } + + // AddPaxLine(s, "testname", AString("testval")); // for debug + + if (item.LinkName.Len() > kNameSize_Max) + AddPaxLine(s, "linkpath", item.LinkName); + + const UInt64 kPaxSize_Limit = ((UInt64)1 << 33); + // const UInt64 kPaxSize_Limit = ((UInt64)1 << 1); // for debug + // bool zero_PackSize = false; + if (item.PackSize >= kPaxSize_Limit) + { + /* GNU TAR in pax mode sets PackSize = 0 in main record, if pack_size >= 8 GiB + But old 7-Zip doesn't detect "size" property from pax header. + So we write real size (>= 8 GiB) to main record in binary format, + and old 7-Zip can decode size correctly */ + // zero_PackSize = true; + AString v; + v.Add_UInt64(item.PackSize); + AddPaxLine(s, "size", v); + } + + /* GNU TAR encoder can set "devmajor" / "devminor" attributes, + but GNU TAR decoder doesn't parse "devmajor" / "devminor" */ + if (item.DeviceMajor_Defined) + AddPax_UInt32_ifBig(s, "devmajor", item.DeviceMajor); + if (item.DeviceMinor_Defined) + AddPax_UInt32_ifBig(s, "devminor", item.DeviceMinor); + + AddPax_UInt32_ifBig(s, "uid", item.UID); + AddPax_UInt32_ifBig(s, "gid", item.GID); + + const UInt64 kPax_MTime_Limit = ((UInt64)1 << 33); + const bool zero_MTime = ( + item.MTime < 0 || + item.MTime >= (Int64)kPax_MTime_Limit); + + const CPaxTime &mtime = item.PaxTimes.MTime; + if (mtime.IsDefined()) + { + bool needPax = false; + if (zero_MTime) + needPax = true; + else if (TimeOptions.NumDigitsMax > 0) + if (mtime.Ns != 0 || + (mtime.NumDigits != 0 && + TimeOptions.RemoveZeroMode == k_PaxTimeMode_DontRemoveZero)) + needPax = true; + if (needPax) + AddPaxTime(s, "mtime", mtime, TimeOptions); + } + + if (item.PaxTimes.ATime.IsDefined()) + AddPaxTime(s, "atime", item.PaxTimes.ATime, TimeOptions); + if (item.PaxTimes.CTime.IsDefined()) + AddPaxTime(s, "ctime", item.PaxTimes.CTime, TimeOptions); + + if (item.User.Len() > kUserNameSize) + AddPaxLine(s, "uname", item.User); + if (item.Group.Len() > kGroupNameSize) + AddPaxLine(s, "gname", item.Group); + + /* + // for debug + AString a ("11"); for (int y = 0; y < (1 << 24); y++) AddPaxLine(s, "temp", a); + */ + + const unsigned paxSize = s.Len(); + if (paxSize != 0) { - mi.LinkFlag = NFileHeader::NLinkFlag::kGnu_LongLink; - name = &item.LinkName; + CItem mi = item; + mi.LinkName.Empty(); + // SparseBlocks will be ignored by Is_Sparse() + // mi.SparseBlocks.Clear(); + // we use "PaxHeader/*" for compatibility with previous 7-Zip decoder + + // GNU TAR writes empty for these fields; + mi.User.Empty(); + mi.Group.Empty(); + mi.UID = 0; + mi.GID = 0; + + mi.DeviceMajor_Defined = false; + mi.DeviceMinor_Defined = false; + + mi.Name = "PaxHeader/@PaxHeader"; + mi.Mode = 0644; // octal + if (zero_MTime) + mi.MTime = 0; + mi.LinkFlag = NLinkFlag::kPax; + // mi.LinkFlag = 'Z'; // for debug + mi.PackSize = paxSize; + // for (unsigned y = 0; y < 1; y++) { // for debug + RINOK(WriteHeaderReal(mi, true)) // isPax + RINOK(Write_Data_And_Residual(s, paxSize)) + // } // for debug + /* + we can send (zero_MTime) for compatibility with gnu tar output. + we can send (zero_MTime = false) for better compatibility with old 7-Zip + */ + // return WriteHeaderReal(item); + /* + false, // isPax + false, // zero_PackSize + false); // zero_MTime + */ } + } + else // !PosixMode + if (!DOES_NAME_FIT_IN_FIELD(item.Name) || + !DOES_NAME_FIT_IN_FIELD(item.LinkName)) + { + // here we can get all fields from main (item) or create new empty item + /* + CItem mi; + mi.SetDefaultWriteFields(); + */ + CItem mi = item; + mi.LinkName.Empty(); + // SparseBlocks will be ignored by Is_Sparse() + // mi.SparseBlocks.Clear(); + mi.Name = kLongLink; + // mi.Name = "././@BAD_LONG_LINK_TEST"; // for debug + // 21.07 : we set Mode and MTime props as in GNU TAR: + mi.Mode = 0644; // octal + mi.MTime = 0; + + mi.User.Empty(); + mi.Group.Empty(); + /* + gnu tar sets "root" for such items: + uid_to_uname (0, &uname); + gid_to_gname (0, &gname); + */ + /* + mi.User = "root"; + mi.Group = "root"; + */ + mi.UID = 0; + mi.GID = 0; + mi.DeviceMajor_Defined = false; + mi.DeviceMinor_Defined = false; + + + for (unsigned i = 0; i < 2; i++) + { + const AString *name; + // We suppose that GNU TAR also writes item for long link before item for LongName? + if (i == 0) + { + mi.LinkFlag = NLinkFlag::kGnu_LongLink; + name = &item.LinkName; + } + else + { + mi.LinkFlag = NLinkFlag::kGnu_LongName; + name = &item.Name; + } + if (DOES_NAME_FIT_IN_FIELD(*name)) + continue; + // GNU TAR writes null character after NAME to file. We do same here: + const unsigned nameStreamSize = name->Len() + 1; + mi.PackSize = nameStreamSize; + // for (unsigned y = 0; y < 3; y++) { // for debug + RINOK(WriteHeaderReal(mi)) + RINOK(Write_Data_And_Residual(name->Ptr(), nameStreamSize)) + // } + + // for debug + /* + const unsigned kSize = (1 << 29) + 16; + CByteBuffer buf; + buf.Alloc(kSize); + memset(buf, 0, kSize); + memcpy(buf, name->Ptr(), name->Len()); + const unsigned nameStreamSize = kSize; + mi.PackSize = nameStreamSize; + // for (unsigned y = 0; y < 3; y++) { // for debug + RINOK(WriteHeaderReal(mi)); + RINOK(WriteBytes(buf, nameStreamSize)); + RINOK(FillDataResidual(nameStreamSize)); + */ + } + } + + // bool fals = false; if (fals) // for debug: for bit-to-bit output compatibility with GNU TAR + + if (!DOES_NAME_FIT_IN_FIELD(item.Name)) + { + const unsigned nameLen = item.Name.Len() - namePos; + if (!needPathCut) + Prefix.SetFrom(item.Name, namePos - 1); else { - mi.LinkFlag = NFileHeader::NLinkFlag::kGnu_LongName; - name = &item.Name; + Glob_Name = K_PREFIX_PATH_CUT "/_pc_"; + + if (namePos == 0) + Glob_Name += "root"; + else + { + Glob_Name += "crc32/"; + char temp[12]; + ConvertUInt32ToHex8Digits(CrcCalc(item.Name, namePos - 1), temp); + Glob_Name += temp; + } + + if (!allowPrefix || Glob_Name.Len() + 1 + nameLen <= kNameSize_Max) + Glob_Name.Add_Slash(); + else + { + Prefix = Glob_Name; + Glob_Name.Empty(); + } } - if (name->Len() < NFileHeader::kNameSize) - continue; - unsigned nameStreamSize = name->Len() + 1; - mi.PackSize = nameStreamSize; - RINOK(WriteHeaderReal(mi)); - RINOK(WriteBytes((const char *)*name, nameStreamSize)); - RINOK(FillDataResidual(nameStreamSize)); - } - - mi = item; - if (mi.Name.Len() >= NFileHeader::kNameSize) - mi.Name.SetFrom(item.Name, NFileHeader::kNameSize - 1); - if (mi.LinkName.Len() >= NFileHeader::kNameSize) - mi.LinkName.SetFrom(item.LinkName, NFileHeader::kNameSize - 1); - return WriteHeaderReal(mi); + Glob_Name.AddFrom(item.Name.Ptr(namePos), nameLen); + } + + return WriteHeaderReal(item); } -HRESULT COutArchive::FillDataResidual(UInt64 dataSize) + +HRESULT COutArchive::Write_Data(const void *data, unsigned size) { - unsigned lastRecordSize = ((unsigned)dataSize & (NFileHeader::kRecordSize - 1)); - if (lastRecordSize == 0) + Pos += size; + return WriteStream(Stream, data, size); +} + +HRESULT COutArchive::Write_AfterDataResidual(UInt64 dataSize) +{ + const unsigned v = ((unsigned)dataSize & (kRecordSize - 1)); + if (v == 0) return S_OK; - unsigned rem = NFileHeader::kRecordSize - lastRecordSize; - Byte buf[NFileHeader::kRecordSize]; + const unsigned rem = kRecordSize - v; + Byte buf[kRecordSize]; memset(buf, 0, rem); - return WriteBytes(buf, rem); + return Write_Data(buf, rem); +} + + +HRESULT COutArchive::Write_Data_And_Residual(const void *data, unsigned size) +{ + RINOK(Write_Data(data, size)) + return Write_AfterDataResidual(size); } + HRESULT COutArchive::WriteFinishHeader() { - Byte record[NFileHeader::kRecordSize]; - memset(record, 0, NFileHeader::kRecordSize); - for (unsigned i = 0; i < 2; i++) + Byte record[kRecordSize]; + memset(record, 0, kRecordSize); + + const unsigned kNumFinishRecords = 2; + + /* GNU TAR by default uses --blocking-factor=20 (512 * 20 = 10 KiB) + we also can use cluster alignment: + const unsigned numBlocks = (unsigned)(Pos / kRecordSize) + kNumFinishRecords; + const unsigned kNumClusterBlocks = (1 << 3); // 8 blocks = 4 KiB + const unsigned numFinishRecords = kNumFinishRecords + ((kNumClusterBlocks - numBlocks) & (kNumClusterBlocks - 1)); + */ + + for (unsigned i = 0; i < kNumFinishRecords; i++) { - RINOK(WriteBytes(record, NFileHeader::kRecordSize)); + RINOK(Write_Data(record, kRecordSize)) } return S_OK; } diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarOut.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarOut.h index ee9b965e..7b99c268 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarOut.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarOut.h @@ -1,7 +1,7 @@ // Archive/TarOut.h -#ifndef __ARCHIVE_TAR_OUT_H -#define __ARCHIVE_TAR_OUT_H +#ifndef ZIP7_INC_ARCHIVE_TAR_OUT_H +#define ZIP7_INC_ARCHIVE_TAR_OUT_H #include "../../../Common/MyCom.h" @@ -14,21 +14,38 @@ namespace NTar { class COutArchive { - CMyComPtr m_Stream; + CMyComPtr Stream; + + AString Glob_Name; + AString Prefix; + + HRESULT WriteHeaderReal(const CItem &item, bool isPax = false + // , bool zero_PackSize = false + // , bool zero_MTime = false + ); + + HRESULT Write_Data(const void *data, unsigned size); + HRESULT Write_Data_And_Residual(const void *data, unsigned size); - HRESULT WriteBytes(const void *data, unsigned size); - HRESULT WriteHeaderReal(const CItem &item); public: UInt64 Pos; - + bool IsPosixMode; + // bool IsPrefixAllowed; // it's used only if (IsPosixMode == true) + CTimeOptions TimeOptions; + void Create(ISequentialOutStream *outStream) { - m_Stream = outStream; + Stream = outStream; } - HRESULT WriteHeader(const CItem &item); - HRESULT FillDataResidual(UInt64 dataSize); + HRESULT Write_AfterDataResidual(UInt64 dataSize); HRESULT WriteFinishHeader(); + + COutArchive(): + Pos(0), + IsPosixMode(false) + // , IsPrefixAllowed(true) + {} }; }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarRegister.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarRegister.cpp index 5014f04d..709c1917 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarRegister.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarRegister.cpp @@ -12,12 +12,20 @@ namespace NTar { static const Byte k_Signature[] = { 'u', 's', 't', 'a', 'r' }; REGISTER_ARC_IO( - "tar", "tar ova", 0, 0xEE, + "tar", "tar ova", NULL, 0xEE, k_Signature, NFileHeader::kUstarMagic_Offset, - NArcInfoFlags::kStartOpen | - NArcInfoFlags::kSymLinks | - NArcInfoFlags::kHardLinks, - IsArc_Tar) + NArcInfoFlags::kStartOpen + | NArcInfoFlags::kSymLinks + | NArcInfoFlags::kHardLinks + | NArcInfoFlags::kMTime + | NArcInfoFlags::kMTime_Default + // | NArcInfoTimeFlags::kCTime + // | NArcInfoTimeFlags::kATime + , TIME_PREC_TO_ARC_FLAGS_MASK (NFileTimeType::kWindows) + | TIME_PREC_TO_ARC_FLAGS_MASK (NFileTimeType::kUnix) + | TIME_PREC_TO_ARC_FLAGS_MASK (NFileTimeType::k1ns) + | TIME_PREC_TO_ARC_FLAGS_TIME_DEFAULT (NFileTimeType::kUnix) + , IsArc_Tar) }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarUpdate.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarUpdate.cpp index 0cdb30d1..d4ed7721 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarUpdate.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarUpdate.cpp @@ -2,10 +2,13 @@ #include "StdAfx.h" +// #include + #include "../../../Windows/TimeUtils.h" #include "../../Common/LimitedStreams.h" #include "../../Common/ProgressUtils.h" +#include "../../Common/StreamUtils.h" #include "../../Compress/CopyCoder.h" @@ -15,24 +18,164 @@ namespace NArchive { namespace NTar { -HRESULT GetPropString(IArchiveUpdateCallback *callback, UInt32 index, PROPID propId, - AString &res, UINT codePage, bool convertSlash = false); +static void FILETIME_To_PaxTime(const FILETIME &ft, CPaxTime &pt) +{ + UInt32 ns; + pt.Sec = NWindows::NTime::FileTime_To_UnixTime64_and_Quantums(ft, ns); + pt.Ns = ns * 100; + pt.NumDigits = 7; +} + + +HRESULT Prop_To_PaxTime(const NWindows::NCOM::CPropVariant &prop, CPaxTime &pt) +{ + pt.Clear(); + if (prop.vt == VT_EMPTY) + { + // pt.Sec = 0; + return S_OK; + } + if (prop.vt != VT_FILETIME) + return E_INVALIDARG; + { + UInt32 ns; + pt.Sec = NWindows::NTime::FileTime_To_UnixTime64_and_Quantums(prop.filetime, ns); + ns *= 100; + pt.NumDigits = 7; + const unsigned prec = prop.wReserved1; + if (prec >= k_PropVar_TimePrec_Base) + { + pt.NumDigits = (int)(prec - k_PropVar_TimePrec_Base); + if (prop.wReserved2 < 100) + ns += prop.wReserved2; + } + pt.Ns = ns; + return S_OK; + } +} + + +static HRESULT GetTime(IStreamGetProp *getProp, UInt32 pid, CPaxTime &pt) +{ + pt.Clear(); + NWindows::NCOM::CPropVariant prop; + RINOK(getProp->GetProperty(pid, &prop)) + return Prop_To_PaxTime(prop, pt); +} + + +static HRESULT GetUser(IStreamGetProp *getProp, + UInt32 pidName, UInt32 pidId, AString &name, UInt32 &id, + UINT codePage, unsigned utfFlags) +{ + // printf("\nGetUser\n"); + // we keep old values, if both GetProperty() return VT_EMPTY + // we clear old values, if any of GetProperty() returns non-VT_EMPTY; + bool isSet = false; + { + NWindows::NCOM::CPropVariant prop; + RINOK(getProp->GetProperty(pidId, &prop)) + if (prop.vt == VT_UI4) + { + isSet = true; + id = prop.ulVal; + name.Empty(); + } + else if (prop.vt != VT_EMPTY) + return E_INVALIDARG; + } + { + NWindows::NCOM::CPropVariant prop; + RINOK(getProp->GetProperty(pidName, &prop)) + if (prop.vt == VT_BSTR) + { + const UString s = prop.bstrVal; + Get_AString_From_UString(s, name, codePage, utfFlags); + // printf("\ngetProp->GetProperty(pidName, &prop) : %s" , name.Ptr()); + if (!isSet) + id = 0; + } + else if (prop.vt == VT_UI4) + { + id = prop.ulVal; + name.Empty(); + } + else if (prop.vt != VT_EMPTY) + return E_INVALIDARG; + } + return S_OK; +} + + +/* +static HRESULT GetDevice(IStreamGetProp *getProp, + UInt32 &majo, UInt32 &mino, bool &majo_defined, bool &mino_defined) +{ + NWindows::NCOM::CPropVariant prop; + RINOK(getProp->GetProperty(kpidDevice, &prop)); + if (prop.vt == VT_EMPTY) + return S_OK; + if (prop.vt != VT_UI8) + return E_INVALIDARG; + { + printf("\nTarUpdate.cpp :: GetDevice()\n"); + const UInt64 v = prop.uhVal.QuadPart; + majo = MY_dev_major(v); + mino = MY_dev_minor(v); + majo_defined = true; + mino_defined = true; + } + return S_OK; +} +*/ + +static HRESULT GetDevice(IStreamGetProp *getProp, + UInt32 pid, UInt32 &id, bool &defined) +{ + defined = false; + NWindows::NCOM::CPropVariant prop; + RINOK(getProp->GetProperty(pid, &prop)) + if (prop.vt == VT_EMPTY) + return S_OK; + if (prop.vt == VT_UI4) + { + id = prop.ulVal; + defined = true; + return S_OK; + } + return E_INVALIDARG; +} + HRESULT UpdateArchive(IInStream *inStream, ISequentialOutStream *outStream, const CObjectVector &inputItems, const CObjectVector &updateItems, - UINT codePage, + const CUpdateOptions &options, IArchiveUpdateCallback *updateCallback) { COutArchive outArchive; outArchive.Create(outStream); outArchive.Pos = 0; + outArchive.IsPosixMode = options.PosixMode; + outArchive.TimeOptions = options.TimeOptions; - CMyComPtr outSeekStream; - outStream->QueryInterface(IID_IOutStream, (void **)&outSeekStream); + Z7_DECL_CMyComPtr_QI_FROM(IOutStream, outSeekStream, outStream) + Z7_DECL_CMyComPtr_QI_FROM(IStreamSetRestriction, setRestriction, outStream) + Z7_DECL_CMyComPtr_QI_FROM(IArchiveUpdateCallbackFile, opCallback, outStream) - CMyComPtr opCallback; - updateCallback->QueryInterface(IID_IArchiveUpdateCallbackFile, (void **)&opCallback); + if (outSeekStream) + { + /* + // for debug + Byte buf[1 << 14]; + memset (buf, 0, sizeof(buf)); + RINOK(outStream->Write(buf, sizeof(buf), NULL)); + */ + // we need real outArchive.Pos, if outSeekStream->SetSize() will be used. + RINOK(outSeekStream->Seek(0, STREAM_SEEK_CUR, &outArchive.Pos)) + } + if (setRestriction) + RINOK(setRestriction->SetRestriction(0, 0)) UInt64 complexity = 0; @@ -41,40 +184,55 @@ HRESULT UpdateArchive(IInStream *inStream, ISequentialOutStream *outStream, { const CUpdateItem &ui = updateItems[i]; if (ui.NewData) + { + if (ui.Size == (UInt64)(Int64)-1) + break; complexity += ui.Size; + } else - complexity += inputItems[ui.IndexInArc].GetFullSize(); + complexity += inputItems[(unsigned)ui.IndexInArc].Get_FullSize_Aligned(); } - RINOK(updateCallback->SetTotal(complexity)); - - NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder; - CMyComPtr copyCoder = copyCoderSpec; + if (i == updateItems.Size()) + RINOK(updateCallback->SetTotal(complexity)) - CLocalProgress *lps = new CLocalProgress; - CMyComPtr progress = lps; + CMyComPtr2_Create lps; lps->Init(updateCallback, true); - - CLimitedSequentialInStream *streamSpec = new CLimitedSequentialInStream; - CMyComPtr inStreamLimited(streamSpec); - streamSpec->SetStream(inStream); + CMyComPtr2_Create copyCoder; + CMyComPtr2_Create inStreamLimited; + inStreamLimited->SetStream(inStream); complexity = 0; - for (i = 0; i < updateItems.Size(); i++) + // const int kNumReduceDigits = -1; // for debug + + for (i = 0;; i++) { lps->InSize = lps->OutSize = complexity; - RINOK(lps->SetCur()); + RINOK(lps->SetCur()) + + if (i == updateItems.Size()) + { + if (outSeekStream && setRestriction) + RINOK(setRestriction->SetRestriction(0, 0)) + return outArchive.WriteFinishHeader(); + } const CUpdateItem &ui = updateItems[i]; CItem item; if (ui.NewProps) { - item.Mode = ui.Mode; + item.SetMagic_Posix(options.PosixMode); item.Name = ui.Name; item.User = ui.User; item.Group = ui.Group; + item.UID = ui.UID; + item.GID = ui.GID; + item.DeviceMajor = ui.DeviceMajor; + item.DeviceMinor = ui.DeviceMinor; + item.DeviceMajor_Defined = ui.DeviceMajor_Defined; + item.DeviceMinor_Defined = ui.DeviceMinor_Defined; if (ui.IsDir) { @@ -83,24 +241,24 @@ HRESULT UpdateArchive(IInStream *inStream, ISequentialOutStream *outStream, } else { - item.LinkFlag = NFileHeader::NLinkFlag::kNormal; item.PackSize = ui.Size; + item.Set_LinkFlag_for_File(ui.Mode); } - - item.MTime = ui.MTime; - item.DeviceMajorDefined = false; - item.DeviceMinorDefined = false; - item.UID = 0; - item.GID = 0; - memcpy(item.Magic, NFileHeader::NMagic::kUsTar_00, 8); + + // 22.00 + item.Mode = ui.Mode & ~(UInt32)MY_LIN_S_IFMT; + item.PaxTimes = ui.PaxTimes; + // item.PaxTimes.ReducePrecison(kNumReduceDigits); // for debug + item.MTime = ui.PaxTimes.MTime.GetSec(); } else - item = inputItems[ui.IndexInArc]; + item = inputItems[(unsigned)ui.IndexInArc]; AString symLink; if (ui.NewData || ui.NewProps) { - RINOK(GetPropString(updateCallback, ui.IndexInClient, kpidSymLink, symLink, codePage, true)); + RINOK(GetPropString(updateCallback, ui.IndexInClient, kpidSymLink, symLink, + options.CodePage, options.UtfFlags, true)) if (!symLink.IsEmpty()) { item.LinkFlag = NFileHeader::NLinkFlag::kSymLink; @@ -113,9 +271,10 @@ HRESULT UpdateArchive(IInStream *inStream, ISequentialOutStream *outStream, item.SparseBlocks.Clear(); item.PackSize = ui.Size; item.Size = ui.Size; +#if 0 if (ui.Size == (UInt64)(Int64)-1) return E_INVALIDARG; - +#endif CMyComPtr fileInStream; bool needWrite = true; @@ -127,39 +286,112 @@ HRESULT UpdateArchive(IInStream *inStream, ISequentialOutStream *outStream, } else { - HRESULT res = updateCallback->GetStream(ui.IndexInClient, &fileInStream); + const HRESULT res = updateCallback->GetStream(ui.IndexInClient, &fileInStream); if (res == S_FALSE) needWrite = false; else { - RINOK(res); + RINOK(res) - if (fileInStream) + if (!fileInStream) + { + item.PackSize = 0; + item.Size = 0; + } + else { - CMyComPtr getProps; - fileInStream->QueryInterface(IID_IStreamGetProps, (void **)&getProps); - if (getProps) + Z7_DECL_CMyComPtr_QI_FROM(IStreamGetProp, getProp, fileInStream) + if (getProp) + { + if (options.Write_MTime.Val) RINOK(GetTime(getProp, kpidMTime, item.PaxTimes.MTime)) + if (options.Write_ATime.Val) RINOK(GetTime(getProp, kpidATime, item.PaxTimes.ATime)) + if (options.Write_CTime.Val) RINOK(GetTime(getProp, kpidCTime, item.PaxTimes.CTime)) + + if (options.PosixMode) + { + /* + RINOK(GetDevice(getProp, item.DeviceMajor, item.DeviceMinor, + item.DeviceMajor_Defined, item.DeviceMinor_Defined)); + */ + bool defined = false; + UInt32 val = 0; + RINOK(GetDevice(getProp, kpidDeviceMajor, val, defined)) + if (defined) + { + item.DeviceMajor = val; + item.DeviceMajor_Defined = true; + item.DeviceMinor = 0; + item.DeviceMinor_Defined = false; + RINOK(GetDevice(getProp, kpidDeviceMinor, item.DeviceMinor, item.DeviceMinor_Defined)) + } + } + + RINOK(GetUser(getProp, kpidUser, kpidUserId, item.User, item.UID, options.CodePage, options.UtfFlags)) + RINOK(GetUser(getProp, kpidGroup, kpidGroupId, item.Group, item.GID, options.CodePage, options.UtfFlags)) + + { + NWindows::NCOM::CPropVariant prop; + RINOK(getProp->GetProperty(kpidPosixAttrib, &prop)) + if (prop.vt == VT_EMPTY) + item.Mode = + MY_LIN_S_IRWXO + | MY_LIN_S_IRWXG + | MY_LIN_S_IRWXU + | (ui.IsDir ? MY_LIN_S_IFDIR : MY_LIN_S_IFREG); + else if (prop.vt != VT_UI4) + return E_INVALIDARG; + else + item.Mode = prop.ulVal; + // 21.07 : we clear high file type bits as GNU TAR. + item.Set_LinkFlag_for_File(item.Mode); + item.Mode &= ~(UInt32)MY_LIN_S_IFMT; + } + + { + NWindows::NCOM::CPropVariant prop; + RINOK(getProp->GetProperty(kpidSize, &prop)) + if (prop.vt != VT_UI8) + return E_INVALIDARG; + const UInt64 size = prop.uhVal.QuadPart; + // printf("\nTAR after GetProperty(kpidSize size = %8d\n", (unsigned)size); + item.PackSize = size; + item.Size = size; + } + /* + printf("\nNum digits = %d %d\n", + (int)item.PaxTimes.MTime.NumDigits, + (int)item.PaxTimes.MTime.Ns); + */ + } + else { - FILETIME mTime; - UInt64 size2; - if (getProps->GetProps(&size2, NULL, NULL, &mTime, NULL) == S_OK) + Z7_DECL_CMyComPtr_QI_FROM(IStreamGetProps, getProps, fileInStream) + if (getProps) { - item.PackSize = size2; - item.Size = size2; - item.MTime = NWindows::NTime::FileTimeToUnixTime64(mTime);; + FILETIME mTime, aTime, cTime; + UInt64 size2; + if (getProps->GetProps(&size2, + options.Write_CTime.Val ? &cTime : NULL, + options.Write_ATime.Val ? &aTime : NULL, + options.Write_MTime.Val ? &mTime : NULL, + NULL) == S_OK) + { + item.PackSize = size2; + item.Size = size2; + if (options.Write_MTime.Val) FILETIME_To_PaxTime(mTime, item.PaxTimes.MTime); + if (options.Write_ATime.Val) FILETIME_To_PaxTime(aTime, item.PaxTimes.ATime); + if (options.Write_CTime.Val) FILETIME_To_PaxTime(cTime, item.PaxTimes.CTime); + } } } } - else - { - item.PackSize = 0; - item.Size = 0; - } { + // we must request kpidHardLink after updateCallback->GetStream() AString hardLink; - RINOK(GetPropString(updateCallback, ui.IndexInClient, kpidHardLink, hardLink, codePage, true)); + RINOK(GetPropString(updateCallback, ui.IndexInClient, kpidHardLink, hardLink, + options.CodePage, options.UtfFlags, true)) if (!hardLink.IsEmpty()) { item.LinkFlag = NFileHeader::NLinkFlag::kHardLink; @@ -172,37 +404,107 @@ HRESULT UpdateArchive(IInStream *inStream, ISequentialOutStream *outStream, } } + // item.PaxTimes.ReducePrecison(kNumReduceDigits); // for debug + + if (ui.NewProps) + item.MTime = item.PaxTimes.MTime.GetSec(); + if (needWrite) { - UInt64 fileHeaderStartPos = outArchive.Pos; - RINOK(outArchive.WriteHeader(item)); + if (fileInStream) + // if (item.PackSize == (UInt64)(Int64)-1) + if (item.Size == (UInt64)(Int64)-1) + return E_INVALIDARG; + + const UInt64 headerPos = outArchive.Pos; + // item.PackSize = ((UInt64)1 << 33); // for debug + + if (outSeekStream && setRestriction) + RINOK(setRestriction->SetRestriction(outArchive.Pos, (UInt64)(Int64)-1)) + + RINOK(outArchive.WriteHeader(item)) if (fileInStream) { - RINOK(copyCoder->Code(fileInStream, outStream, NULL, NULL, progress)); - outArchive.Pos += copyCoderSpec->TotalSize; - if (copyCoderSpec->TotalSize != item.PackSize) + for (unsigned numPasses = 0;; numPasses++) { + // printf("\nTAR numPasses = %d" " old size = %8d\n", numPasses, (unsigned)item.PackSize); + /* we support 2 attempts to write header: + pass-0: main pass: + pass-1: additional pass, if size_of_file and size_of_header are changed */ + if (numPasses >= 2) + { + // opRes = NArchive::NUpdate::NOperationResult::kError_FileChanged; + // break; + return E_FAIL; + } + + const UInt64 dataPos = outArchive.Pos; + RINOK(copyCoder.Interface()->Code(fileInStream, outStream, NULL, NULL, lps)) + outArchive.Pos += copyCoder->TotalSize; + RINOK(outArchive.Write_AfterDataResidual(copyCoder->TotalSize)) + // printf("\nTAR after Code old size = %8d copyCoder->TotalSize = %8d \n", (unsigned)item.PackSize, (unsigned)copyCoder->TotalSize); + // if (numPasses >= 10) // for debug + if (copyCoder->TotalSize == item.PackSize) + break; + + if (opCallback) + { + RINOK(opCallback->ReportOperation( + NEventIndexType::kOutArcIndex, (UInt32)ui.IndexInClient, + NUpdateNotifyOp::kInFileChanged)) + } + if (!outSeekStream) return E_FAIL; - UInt64 backOffset = outArchive.Pos - fileHeaderStartPos; - RINOK(outSeekStream->Seek(-(Int64)backOffset, STREAM_SEEK_CUR, NULL)); - outArchive.Pos = fileHeaderStartPos; - item.PackSize = copyCoderSpec->TotalSize; - RINOK(outArchive.WriteHeader(item)); - RINOK(outSeekStream->Seek(item.PackSize, STREAM_SEEK_CUR, NULL)); - outArchive.Pos += item.PackSize; + const UInt64 nextPos = outArchive.Pos; + RINOK(outSeekStream->Seek(-(Int64)(nextPos - headerPos), STREAM_SEEK_CUR, NULL)) + outArchive.Pos = headerPos; + item.PackSize = copyCoder->TotalSize; + + RINOK(outArchive.WriteHeader(item)) + + // if (numPasses >= 10) // for debug + if (outArchive.Pos == dataPos) + { + const UInt64 alignedSize = nextPos - dataPos; + if (alignedSize != 0) + { + RINOK(outSeekStream->Seek((Int64)alignedSize, STREAM_SEEK_CUR, NULL)) + outArchive.Pos += alignedSize; + } + break; + } + + // size of header was changed. + // we remove data after header and try new attempt, if required + Z7_DECL_CMyComPtr_QI_FROM(IInStream, fileSeekStream, fileInStream) + if (!fileSeekStream) + return E_FAIL; + RINOK(InStream_SeekToBegin(fileSeekStream)) + RINOK(outSeekStream->SetSize(outArchive.Pos)) + if (item.PackSize == 0) + break; } - RINOK(outArchive.FillDataResidual(item.PackSize)); } } complexity += item.PackSize; - RINOK(updateCallback->SetOperationResult(NArchive::NUpdate::NOperationResult::kOK)); + fileInStream.Release(); + RINOK(updateCallback->SetOperationResult(NArchive::NUpdate::NOperationResult::kOK)) } else { - const CItemEx &existItem = inputItems[ui.IndexInArc]; - UInt64 size; + // (ui.NewData == false) + + if (opCallback) + { + RINOK(opCallback->ReportOperation( + NEventIndexType::kInArcIndex, (UInt32)ui.IndexInArc, + NUpdateNotifyOp::kReplicate)) + } + + const CItemEx &existItem = inputItems[(unsigned)ui.IndexInArc]; + UInt64 size, pos; if (ui.NewProps) { @@ -223,44 +525,39 @@ HRESULT UpdateArchive(IInStream *inStream, ISequentialOutStream *outStream, item.PackSize = existItem.PackSize; } - item.DeviceMajorDefined = existItem.DeviceMajorDefined; - item.DeviceMinorDefined = existItem.DeviceMinorDefined; + item.DeviceMajor_Defined = existItem.DeviceMajor_Defined; + item.DeviceMinor_Defined = existItem.DeviceMinor_Defined; item.DeviceMajor = existItem.DeviceMajor; item.DeviceMinor = existItem.DeviceMinor; item.UID = existItem.UID; item.GID = existItem.GID; - RINOK(outArchive.WriteHeader(item)); - RINOK(inStream->Seek(existItem.GetDataPosition(), STREAM_SEEK_SET, NULL)); - size = existItem.PackSize; + RINOK(outArchive.WriteHeader(item)) + size = existItem.Get_PackSize_Aligned(); + pos = existItem.Get_DataPos(); } else { - RINOK(inStream->Seek(existItem.HeaderPos, STREAM_SEEK_SET, NULL)); - size = existItem.GetFullSize(); + size = existItem.Get_FullSize_Aligned(); + pos = existItem.HeaderPos; } - - streamSpec->Init(size); - if (opCallback) + if (size != 0) { - RINOK(opCallback->ReportOperation( - NEventIndexType::kInArcIndex, (UInt32)ui.IndexInArc, - NUpdateNotifyOp::kReplicate)) + RINOK(InStream_SeekSet(inStream, pos)) + inStreamLimited->Init(size); + if (outSeekStream && setRestriction) + RINOK(setRestriction->SetRestriction(0, 0)) + // 22.00 : we copy Residual data from old archive to new archive instead of zeroing + RINOK(copyCoder.Interface()->Code(inStreamLimited, outStream, NULL, NULL, lps)) + if (copyCoder->TotalSize != size) + return E_FAIL; + outArchive.Pos += size; + // RINOK(outArchive.Write_AfterDataResidual(existItem.PackSize)); + complexity += size; } - - RINOK(copyCoder->Code(inStreamLimited, outStream, NULL, NULL, progress)); - if (copyCoderSpec->TotalSize != size) - return E_FAIL; - outArchive.Pos += size; - RINOK(outArchive.FillDataResidual(existItem.PackSize)); - complexity += size; } } - - lps->InSize = lps->OutSize = complexity; - RINOK(lps->SetCur()); - return outArchive.WriteFinishHeader(); } }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarUpdate.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarUpdate.h index b758635f..f6c2e779 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarUpdate.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Tar/TarUpdate.h @@ -1,7 +1,7 @@ // TarUpdate.h -#ifndef __TAR_UPDATE_H -#define __TAR_UPDATE_H +#ifndef ZIP7_INC_TAR_UPDATE_H +#define ZIP7_INC_TAR_UPDATE_H #include "../IArchive.h" @@ -13,26 +13,62 @@ namespace NTar { struct CUpdateItem { int IndexInArc; - int IndexInClient; + unsigned IndexInClient; UInt64 Size; - Int64 MTime; + // Int64 MTime; UInt32 Mode; bool NewData; bool NewProps; bool IsDir; + bool DeviceMajor_Defined; + bool DeviceMinor_Defined; + UInt32 UID; + UInt32 GID; + UInt32 DeviceMajor; + UInt32 DeviceMinor; AString Name; AString User; AString Group; - CUpdateItem(): Size(0), IsDir(false) {} + CPaxTimes PaxTimes; + + CUpdateItem(): + Size(0), + IsDir(false), + DeviceMajor_Defined(false), + DeviceMinor_Defined(false), + UID(0), + GID(0) + {} +}; + + +struct CUpdateOptions +{ + UINT CodePage; + unsigned UtfFlags; + bool PosixMode; + CBoolPair Write_MTime; + CBoolPair Write_ATime; + CBoolPair Write_CTime; + CTimeOptions TimeOptions; }; + HRESULT UpdateArchive(IInStream *inStream, ISequentialOutStream *outStream, const CObjectVector &inputItems, const CObjectVector &updateItems, - UINT codePage, + const CUpdateOptions &options, IArchiveUpdateCallback *updateCallback); +HRESULT GetPropString(IArchiveUpdateCallback *callback, UInt32 index, PROPID propId, AString &res, + UINT codePage, unsigned utfFlags, bool convertSlash); + +HRESULT Prop_To_PaxTime(const NWindows::NCOM::CPropVariant &prop, CPaxTime &pt); + +void Get_AString_From_UString(const UString &s, AString &res, + UINT codePage, unsigned utfFlags); + }} #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Udf/StdAfx.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Udf/StdAfx.h index 2854ff3e..035267cc 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Udf/StdAfx.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Udf/StdAfx.h @@ -1,8 +1,11 @@ // StdAfx.h -#ifndef __STDAFX_H -#define __STDAFX_H +#ifndef ZIP7_INC_STDAFX_H +#define ZIP7_INC_STDAFX_H +#if defined(_MSC_VER) && _MSC_VER >= 1800 +#pragma warning(disable : 4464) // relative include path contains '..' +#endif #include "../../../Common/Common.h" #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Udf/UdfHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/Udf/UdfHandler.cpp index 74ec0beb..7c74c805 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Udf/UdfHandler.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Udf/UdfHandler.cpp @@ -26,12 +26,18 @@ static void UdfTimeToFileTime(const CTime &t, NWindows::NCOM::CPropVariant &prop if (!NWindows::NTime::GetSecondsSince1601(t.GetYear(), d[4], d[5], d[6], d[7], d[8], numSecs)) return; if (t.IsLocal()) - numSecs -= (Int64)((Int32)t.GetMinutesOffset() * 60); - FILETIME ft; - UInt64 v = (((numSecs * 100 + d[9]) * 100 + d[10]) * 100 + d[11]) * 10; - ft.dwLowDateTime = (UInt32)v; - ft.dwHighDateTime = (UInt32)(v >> 32); - prop = ft; + numSecs = (UInt64)((Int64)numSecs - (Int64)((Int32)t.GetMinutesOffset() * 60)); + const UInt32 m0 = d[9]; + const UInt32 m1 = d[10]; + const UInt32 m2 = d[11]; + unsigned numDigits = 0; + UInt64 v = numSecs * 10000000; + if (m0 < 100 && m1 < 100 && m2 < 100) + { + v += m0 * 100000 + m1 * 1000 + m2 * 10; + numDigits = 6; + } + prop.SetAsTimeFrom_Ft64_Prec(v, k_PropVar_TimePrec_Base + numDigits); } static const Byte kProps[] = @@ -41,20 +47,29 @@ static const Byte kProps[] = kpidSize, kpidPackSize, kpidMTime, - kpidATime + kpidATime, + kpidCTime, + kpidChangeTime, + // kpidUserId, + // kpidGroupId, + // kpidPosixAttrib, + kpidLinks }; static const Byte kArcProps[] = { - kpidComment, + kpidUnpackVer, kpidClusterSize, - kpidCTime + kpidSectorSize, + kpidCTime, + kpidMTime, + kpidComment }; IMP_IInArchive_Props IMP_IInArchive_ArcProps -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NWindows::NCOM::CPropVariant prop; @@ -62,6 +77,18 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) { case kpidPhySize: prop = _archive.PhySize; break; + case kpidUnpackVer: + { + if (_archive.LogVols.Size() == 1) + { + UString s; + const CLogVol &vol = _archive.LogVols[0]; + vol.DomainId.AddUdfVersionTo(s); + if (!s.IsEmpty()) + prop = s; + } + break; + } case kpidComment: { UString comment = _archive.GetComment(); @@ -83,12 +110,21 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) } break; + case kpidSectorSize: prop = ((UInt32)1 << _archive.SecLogSize); break; + case kpidCTime: if (_archive.LogVols.Size() == 1) { const CLogVol &vol = _archive.LogVols[0]; if (vol.FileSets.Size() >= 1) - UdfTimeToFileTime(vol.FileSets[0].RecodringTime, prop); + UdfTimeToFileTime(vol.FileSets[0].RecordingTime, prop); + } + break; + case kpidMTime: + if (_archive.PrimeVols.Size() == 1) + { + const CPrimeVol &pv = _archive.PrimeVols[0]; + UdfTimeToFileTime(pv.RecordingTime, prop); } break; @@ -108,15 +144,15 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) COM_TRY_END } -class CProgressImp: public CProgressVirt +class CProgressImp Z7_final: public CProgressVirt { CMyComPtr _callback; UInt64 _numFiles; UInt64 _numBytes; public: - HRESULT SetTotal(UInt64 numBytes); - HRESULT SetCompleted(UInt64 numFiles, UInt64 numBytes); - HRESULT SetCompleted(); + HRESULT SetTotal(UInt64 numBytes) Z7_override; + HRESULT SetCompleted(UInt64 numFiles, UInt64 numBytes) Z7_override; + HRESULT SetCompleted() Z7_override; CProgressImp(IArchiveOpenCallback *callback): _callback(callback), _numFiles(0), _numBytes(0) {} }; @@ -141,18 +177,19 @@ HRESULT CProgressImp::SetCompleted() return S_OK; } -STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *callback) +Z7_COM7F_IMF(CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *callback)) { COM_TRY_BEGIN { Close(); CProgressImp progressImp(callback); - RINOK(_archive.Open(stream, &progressImp)); + RINOK(_archive.Open(stream, &progressImp)) bool showVolName = (_archive.LogVols.Size() > 1); FOR_VECTOR (volIndex, _archive.LogVols) { const CLogVol &vol = _archive.LogVols[volIndex]; bool showFileSetName = (vol.FileSets.Size() > 1); + // showFileSetName = true; // for debug FOR_VECTOR (fsIndex, vol.FileSets) { const CFileSet &fs = vol.FileSets[fsIndex]; @@ -172,7 +209,7 @@ STDMETHODIMP CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallb COM_TRY_END } -STDMETHODIMP CHandler::Close() +Z7_COM7F_IMF(CHandler::Close()) { _inStream.Release(); _archive.Clear(); @@ -180,13 +217,13 @@ STDMETHODIMP CHandler::Close() return S_OK; } -STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) { *numItems = _refs2.Size(); return S_OK; } -STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NWindows::NCOM::CPropVariant prop; @@ -205,6 +242,15 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val case kpidPackSize: if (!item.IsDir()) prop = (UInt64)item.NumLogBlockRecorded * vol.BlockSize; break; case kpidMTime: UdfTimeToFileTime(item.MTime, prop); break; case kpidATime: UdfTimeToFileTime(item.ATime, prop); break; + case kpidCTime: + if (item.IsExtended) + UdfTimeToFileTime(item.CreateTime, prop); + break; + case kpidChangeTime: UdfTimeToFileTime(item.AttribTime, prop); break; + // case kpidUserId: prop = item.Uid; break; + // case kpidGroupId: prop = item.Gid; break; + // case kpidPosixAttrib: prop = (UInt32)item.Permissions; break; + case kpidLinks: prop = (UInt32)item.FileLinkCount; break; } } prop.Detach(value); @@ -212,9 +258,9 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val COM_TRY_END } -STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream) +Z7_COM7F_IMF(CHandler::GetStream(UInt32 index, ISequentialInStream **stream)) { - *stream = 0; + *stream = NULL; const CRef2 &ref2 = _refs2[index]; const CLogVol &vol = _archive.LogVols[ref2.Vol]; @@ -247,7 +293,7 @@ STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream) if (size < len) return S_FALSE; - int partitionIndex = vol.PartitionMaps[extent.PartitionRef].PartitionIndex; + const unsigned partitionIndex = vol.PartitionMaps[extent.PartitionRef].PartitionIndex; UInt32 logBlockNumber = extent.Pos; const CPartition &partition = _archive.Partitions[partitionIndex]; UInt64 offset = ((UInt64)partition.Pos << _archive.SecLogSize) + @@ -272,11 +318,11 @@ STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream) return S_OK; } -STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, - Int32 testMode, IArchiveExtractCallback *extractCallback) +Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testMode, IArchiveExtractCallback *extractCallback)) { COM_TRY_BEGIN - bool allFilesMode = (numItems == (UInt32)(Int32)-1); + const bool allFilesMode = (numItems == (UInt32)(Int32)-1); if (allFilesMode) numItems = _refs2.Size(); if (numItems == 0) @@ -286,7 +332,7 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, for (i = 0; i < numItems; i++) { - UInt32 index = (allFilesMode ? i : indices[i]); + const UInt32 index = (allFilesMode ? i : indices[i]); const CRef2 &ref2 = _refs2[index]; const CRef &ref = _archive.LogVols[ref2.Vol].FileSets[ref2.Fs].Refs[ref2.Ref]; const CFile &file = _archive.Files[ref.FileIndex]; @@ -294,31 +340,28 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, if (!item.IsDir()) totalSize += item.Size; } - extractCallback->SetTotal(totalSize); + RINOK(extractCallback->SetTotal(totalSize)) UInt64 currentTotalSize = 0; - NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder(); - CMyComPtr copyCoder = copyCoderSpec; - - CLocalProgress *lps = new CLocalProgress; - CMyComPtr progress = lps; + CMyComPtr2_Create lps; lps->Init(extractCallback, false); + CMyComPtr2_Create copyCoder; + CMyComPtr2_Create outStream; - CLimitedSequentialOutStream *outStreamSpec = new CLimitedSequentialOutStream; - CMyComPtr outStream(outStreamSpec); - - for (i = 0; i < numItems; i++) + for (i = 0;; i++) { lps->InSize = lps->OutSize = currentTotalSize; - RINOK(lps->SetCur()); + RINOK(lps->SetCur()) + if (i >= numItems) + break; CMyComPtr realOutStream; - Int32 askMode = testMode ? + const Int32 askMode = testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; - UInt32 index = allFilesMode ? i : indices[i]; + const UInt32 index = allFilesMode ? i : indices[i]; - RINOK(extractCallback->GetStream(index, &realOutStream, askMode)); + RINOK(extractCallback->GetStream(index, &realOutStream, askMode)) const CRef2 &ref2 = _refs2[index]; const CRef &ref = _archive.LogVols[ref2.Vol].FileSets[ref2.Fs].Refs[ref2.Ref]; @@ -327,8 +370,8 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, if (item.IsDir()) { - RINOK(extractCallback->PrepareOperation(askMode)); - RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)); + RINOK(extractCallback->PrepareOperation(askMode)) + RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)) continue; } currentTotalSize += item.Size; @@ -336,26 +379,28 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, if (!testMode && !realOutStream) continue; - RINOK(extractCallback->PrepareOperation(askMode)); - outStreamSpec->SetStream(realOutStream); + RINOK(extractCallback->PrepareOperation(askMode)) + outStream->SetStream(realOutStream); realOutStream.Release(); - outStreamSpec->Init(item.Size); + outStream->Init(item.Size); Int32 opRes; - CMyComPtr udfInStream; - HRESULT res = GetStream(index, &udfInStream); - if (res == E_NOTIMPL) - opRes = NExtract::NOperationResult::kUnsupportedMethod; - else if (res != S_OK) - opRes = NExtract::NOperationResult::kDataError; - else { - RINOK(copyCoder->Code(udfInStream, outStream, NULL, NULL, progress)); - opRes = outStreamSpec->IsFinishedOK() ? - NExtract::NOperationResult::kOK: - NExtract::NOperationResult::kDataError; + CMyComPtr udfInStream; + const HRESULT res = GetStream(index, &udfInStream); + if (res == E_NOTIMPL) + opRes = NExtract::NOperationResult::kUnsupportedMethod; + else if (res != S_OK) + opRes = NExtract::NOperationResult::kDataError; + else + { + RINOK(copyCoder.Interface()->Code(udfInStream, outStream, NULL, NULL, lps)) + opRes = outStream->IsFinishedOK() ? + NExtract::NOperationResult::kOK: + NExtract::NOperationResult::kDataError; + } } - outStreamSpec->ReleaseStream(); - RINOK(extractCallback->SetOperationResult(opRes)); + outStream->ReleaseStream(); + RINOK(extractCallback->SetOperationResult(opRes)) } return S_OK; COM_TRY_END @@ -364,12 +409,18 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, static const UInt32 kIsoStartPos = 0x8000; // 5, { 0, 'N', 'S', 'R', '0' }, -static const Byte k_Signature[] = { 1, 'C', 'D', '0', '0', '1' }; + +static const Byte k_Signature[] = +{ + 8, 0, 'B', 'E', 'A', '0', '1', 1, 0, + 6, 1, 'C', 'D', '0', '0', '1' +}; REGISTER_ARC_I( - "Udf", "udf iso img", 0, 0xE0, + "Udf", "udf iso img", NULL, 0xE0, k_Signature, kIsoStartPos, + NArcInfoFlags::kMultiSignature | NArcInfoFlags::kStartOpen, IsArc_Udf) diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Udf/UdfHandler.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Udf/UdfHandler.h index da44b232..5426d064 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Udf/UdfHandler.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Udf/UdfHandler.h @@ -1,7 +1,7 @@ // UdfHandler.h -#ifndef __UDF_HANDLER_H -#define __UDF_HANDLER_H +#ifndef ZIP7_INC_UDF_HANDLER_H +#define ZIP7_INC_UDF_HANDLER_H #include "../../../Common/MyCom.h" @@ -19,18 +19,12 @@ struct CRef2 unsigned Ref; }; -class CHandler: - public IInArchive, - public IInArchiveGetStream, - public CMyUnknownImp -{ +Z7_CLASS_IMP_CHandler_IInArchive_1( + IInArchiveGetStream +) + CRecordVector _refs2; CMyComPtr _inStream; CInArchive _archive; - CRecordVector _refs2; -public: - MY_UNKNOWN_IMP2(IInArchive, IInArchiveGetStream) - INTERFACE_IInArchive(;) - STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream); }; }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Udf/UdfIn.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/Udf/UdfIn.cpp index d52db9f4..e5332b53 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Udf/UdfIn.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Udf/UdfIn.cpp @@ -10,6 +10,8 @@ #include "../../../../C/CpuArch.h" +#include "../../../Windows/PropVariantUtils.h" + #include "../../Common/RegisterArc.h" #include "../../Common/StreamUtils.h" @@ -25,6 +27,10 @@ #define Get32(p) GetUi32(p) #define Get64(p) GetUi64(p) +#define G16(_offs_, dest) dest = Get16(p + (_offs_)) +#define G32(_offs_, dest) dest = Get32(p + (_offs_)) +#define G64(_offs_, dest) dest = Get64(p + (_offs_)) + namespace NArchive { namespace NUdf { @@ -39,40 +45,37 @@ static const UInt64 kFileNameLengthTotalMax = (UInt64)1 << 33; static const UInt64 kInlineExtentsSizeMax = (UInt64)1 << 33; #define CRC16_INIT_VAL 0 -#define CRC16_GET_DIGEST(crc) (crc) #define CRC16_UPDATE_BYTE(crc, b) ((UInt16)(g_Crc16Table[(((crc) >> 8) ^ (b)) & 0xFF] ^ ((crc) << 8))) #define kCrc16Poly 0x1021 static UInt16 g_Crc16Table[256]; -void MY_FAST_CALL Crc16GenerateTable(void) +static void Z7_FASTCALL Crc16GenerateTable(void) { UInt32 i; for (i = 0; i < 256; i++) { UInt32 r = (i << 8); - for (int j = 8; j > 0; j--) - r = ((r & 0x8000) ? ((r << 1) ^ kCrc16Poly) : (r << 1)) & 0xFFFF; + for (unsigned j = 0; j < 8; j++) + r = ((r << 1) ^ (kCrc16Poly & ((UInt32)0 - (r >> 15)))) & 0xFFFF; g_Crc16Table[i] = (UInt16)r; } } -UInt16 MY_FAST_CALL Crc16_Update(UInt16 v, const void *data, size_t size) +static UInt32 Z7_FASTCALL Crc16Calc(const void *data, size_t size) { + UInt32 v = CRC16_INIT_VAL; const Byte *p = (const Byte *)data; - for (; size > 0 ; size--, p++) + const Byte *pEnd = p + size; + for (; p != pEnd; p++) v = CRC16_UPDATE_BYTE(v, *p); return v; } -UInt16 MY_FAST_CALL Crc16Calc(const void *data, size_t size) -{ - return Crc16_Update(CRC16_INIT_VAL, data, size); -} - -struct CCrc16TableInit { CCrc16TableInit() { Crc16GenerateTable(); } } g_Crc16TableInit; +static struct CCrc16TableInit { CCrc16TableInit() { Crc16GenerateTable(); } } g_Crc16TableInit; +// ---------- ECMA Part 1 ---------- void CDString::Parse(const Byte *p, unsigned size) { @@ -82,16 +85,17 @@ void CDString::Parse(const Byte *p, unsigned size) static UString ParseDString(const Byte *data, unsigned size) { UString res; - if (size > 0) + if (size != 0) { wchar_t *p; - Byte type = data[0]; + const Byte type = *data++; + size--; if (type == 8) { p = res.GetBuf(size); - for (unsigned i = 1; i < size; i++) + for (unsigned i = 0; i < size; i++) { - wchar_t c = data[i]; + const wchar_t c = data[i]; if (c == 0) break; *p++ = c; @@ -99,92 +103,138 @@ static UString ParseDString(const Byte *data, unsigned size) } else if (type == 16) { + size &= ~(unsigned)1; p = res.GetBuf(size / 2); - for (unsigned i = 1; i + 2 <= size; i += 2) + for (unsigned i = 0; i < size; i += 2) { - wchar_t c = GetBe16(data + i); + const wchar_t c = GetBe16(data + i); if (c == 0) break; *p++ = c; } } else - return L"[unknow]"; + { + res = "[unknown]"; + return res; + } *p = 0; res.ReleaseBuf_SetLen((unsigned)(p - (const wchar_t *)res)); } return res; } +UString CDString32::GetString() const +{ + const unsigned size = Data[sizeof(Data) - 1]; + return ParseDString(Data, MyMin(size, (unsigned)(sizeof(Data) - 1))); +} + UString CDString128::GetString() const { - unsigned size = Data[sizeof(Data) - 1]; + const unsigned size = Data[sizeof(Data) - 1]; return ParseDString(Data, MyMin(size, (unsigned)(sizeof(Data) - 1))); } UString CDString::GetString() const { return ParseDString(Data, (unsigned)Data.Size()); } -void CTime::Parse(const Byte *buf) { memcpy(Data, buf, sizeof(Data)); } +void CTime::Parse(const Byte *p) { memcpy(Data, p, sizeof(Data)); } -/* -void CRegId::Parse(const Byte *buf) + +static void AddCommentChars(UString &dest, const char *s, size_t size) { - Flags = buf[0]; - memcpy(Id, buf + 1, sizeof(Id)); - memcpy(Suffix, buf + 24, sizeof(Suffix)); + for (size_t i = 0; i < size; i++) + { + char c = s[i]; + if (c == 0) + break; + if (c < 0x20) + c = '_'; + dest += (wchar_t)c; + } } -*/ -// ECMA 3/7.1 -struct CExtent +void CRegId::Parse(const Byte *p) { - UInt32 Len; - UInt32 Pos; + Flags = p[0]; + memcpy(Id, p + 1, sizeof(Id)); + memcpy(Suffix, p + 24, sizeof(Suffix)); +} + +void CRegId::AddCommentTo(UString &s) const +{ + AddCommentChars(s, Id, sizeof(Id)); +} + +void CRegId::AddUdfVersionTo(UString &s) const +{ + // use it only for "Domain Identifier Suffix" and "UDF Identifier Suffix" + // UDF 2.1.5.3 + // Revision in hex (3 digits) + const Byte minor = Suffix[0]; + const Byte major = Suffix[1]; + if (major != 0 || minor != 0) + { + char temp[16]; + ConvertUInt32ToHex(major, temp); + s += temp; + s.Add_Dot(); + ConvertUInt32ToHex8Digits(minor, temp); + s += &temp[8 - 2]; + } +} - void Parse(const Byte *buf); -}; -void CExtent::Parse(const Byte *buf) +// ---------- ECMA Part 3: Volume Structure ---------- + +void CExtent::Parse(const Byte *p) { - Len = Get32(buf); - Pos = Get32(buf + 4); + /* Len shall be less than < 2^30. + Unless otherwise specified, the length shall be an integral multiple of the logical sector size. + If (Len == 0), no extent is specified and (Pos) shall contain 0 */ + G32 (0, Len); + G32 (4, Pos); } + // ECMA 3/7.2 struct CTag { UInt16 Id; - UInt16 Version; + // UInt16 Version; // Byte Checksum; // UInt16 SerialNumber; // UInt16 Crc; - // UInt16 CrcLen; - // UInt32 TagLocation; + UInt16 CrcLen; + // UInt32 TagLocation; // the number of the logical sector - HRESULT Parse(const Byte *buf, size_t size); + HRESULT Parse(const Byte *p, size_t size); }; -HRESULT CTag::Parse(const Byte *buf, size_t size) +HRESULT CTag::Parse(const Byte *p, size_t size) { if (size < 16) return S_FALSE; - Byte sum = 0; - int i; - for (i = 0; i < 4; i++) sum = (Byte)(sum + buf[i]); - for (i = 5; i < 16; i++) sum = (Byte)(sum + buf[i]); - if (sum != buf[4] || buf[5] != 0) return S_FALSE; - - Id = Get16(buf); - Version = Get16(buf + 2); - // SerialNumber = Get16(buf + 6); - UInt16 crc = Get16(buf + 8); - UInt16 crcLen = Get16(buf + 10); - // TagLocation = Get32(buf + 12); - - if (size >= 16 + (size_t)crcLen) - if (crc == Crc16Calc(buf + 16, crcLen)) + { + unsigned sum = 0; + for (unsigned i = 0; i < 16; i++) + sum = sum + p[i]; + if ((Byte)(sum - p[4]) != p[4] || p[5] != 0) + return S_FALSE; + } + Id = Get16(p); + const UInt16 Version = Get16(p + 2); + if (Version != 2 && Version != 3) + return S_FALSE; + // SerialNumber = Get16(p + 6); + const UInt32 crc = Get16(p + 8); + CrcLen = Get16(p + 10); + // TagLocation = Get32(p + 12); + + if (size >= 16 + (size_t)CrcLen) + if (crc == Crc16Calc(p + 16, (size_t)CrcLen)) return S_OK; return S_FALSE; } @@ -210,52 +260,78 @@ enum EDescriptorType DESC_TYPE_Terminal = 260, DESC_TYPE_File = 261, DESC_TYPE_ExtendedAttrHeader = 262, - DESC_TYPE_UnallocatedSpace = 263, + DESC_TYPE_UnallocatedSpaceEntry = 263, DESC_TYPE_SpaceBitmap = 264, DESC_TYPE_PartitionIntegrity = 265, DESC_TYPE_ExtendedFile = 266 }; -void CLogBlockAddr::Parse(const Byte *buf) +void CLogBlockAddr::Parse(const Byte *p) { - Pos = Get32(buf); - PartitionRef = Get16(buf + 4); + G32 (0, Pos); + G16 (4, PartitionRef); } -void CShortAllocDesc::Parse(const Byte *buf) +void CShortAllocDesc::Parse(const Byte *p) { - Len = Get32(buf); - Pos = Get32(buf + 4); + G32 (0, Len); + G32 (4, Pos); } /* -void CADImpUse::Parse(const Byte *buf) +void CADImpUse::Parse(const Byte *p) { - Flags = Get16(buf); - UdfUniqueId = Get32(buf + 2); + G16 (0, Flags); + G32 (2, UdfUniqueId); } */ -void CLongAllocDesc::Parse(const Byte *buf) +void CLongAllocDesc::Parse(const Byte *p) { - Len = Get32(buf); - Location.Parse(buf + 4); - // memcpy(ImplUse, buf + 10, sizeof(ImplUse)); + G32 (0, Len); + Location.Parse(p + 4); + // memcpy(ImplUse, p + 10, sizeof(ImplUse)); // adImpUse.Parse(ImplUse); } -bool CInArchive::CheckExtent(int volIndex, int partitionRef, UInt32 blockPos, UInt32 len) const + +void CPrimeVol::Parse(const Byte *p) +{ + // G32 (16, VolumeDescriptorSequenceNumber); + G32 (20, PrimaryVolumeDescriptorNumber); + VolumeId.Parse(p + 24); + G16 (56, VolumeSequenceNumber); + G16 (58, MaximumVolumeSequenceNumber); + // G16 (60, InterchangeLevel); + // G16 (62, MaximumInterchangeLevel); + // G32 (64, CharacterSetList) + // G32 (68, MaximumCharacterSetList) + VolumeSetId.Parse(p + 72); + // 200 64 Descriptor Character Set charspec (1/7.2.1) + // 264 64 Explanatory Character Set charspec (1/7.2.1) + // VolumeAbstract.Parse(p + 328); + // VolumeCopyrightNotice.Parse(p + 336); + ApplicationId.Parse(p + 344); + RecordingTime.Parse(p + 376); + ImplId.Parse(p + 388); + // 420 64 Implementation Use bytes + // G32 (484, PredecessorVolumeDescriptorSequenceLocation); + // G16 (488, Flags); +} + + + +bool CInArchive::CheckExtent(unsigned volIndex, unsigned partitionRef, UInt32 blockPos, UInt32 len) const { const CLogVol &vol = LogVols[volIndex]; - if (partitionRef >= (int)vol.PartitionMaps.Size()) + if (partitionRef >= vol.PartitionMaps.Size()) return false; const CPartition &partition = Partitions[vol.PartitionMaps[partitionRef].PartitionIndex]; - UInt64 offset = ((UInt64)partition.Pos << SecLogSize) + (UInt64)blockPos * vol.BlockSize; - return (offset + len) <= (((UInt64)partition.Pos + partition.Len) << SecLogSize); + return ((UInt64)blockPos * vol.BlockSize + len) <= ((UInt64)partition.Len << SecLogSize); } -bool CInArchive::CheckItemExtents(int volIndex, const CItem &item) const +bool CInArchive::CheckItemExtents(unsigned volIndex, const CItem &item) const { FOR_VECTOR (i, item.Extents) { @@ -266,28 +342,28 @@ bool CInArchive::CheckItemExtents(int volIndex, const CItem &item) const return true; } -HRESULT CInArchive::Read(int volIndex, int partitionRef, UInt32 blockPos, UInt32 len, Byte *buf) +HRESULT CInArchive::Read(unsigned volIndex, unsigned partitionRef, UInt32 blockPos, UInt32 len, Byte *buf) { if (!CheckExtent(volIndex, partitionRef, blockPos, len)) return S_FALSE; const CLogVol &vol = LogVols[volIndex]; const CPartition &partition = Partitions[vol.PartitionMaps[partitionRef].PartitionIndex]; UInt64 offset = ((UInt64)partition.Pos << SecLogSize) + (UInt64)blockPos * vol.BlockSize; - RINOK(_stream->Seek(offset, STREAM_SEEK_SET, NULL)); - HRESULT res = ReadStream_FALSE(_stream, buf, len); - if (res == S_FALSE && offset + len > FileSize) + RINOK(InStream_SeekSet(_stream, offset)) + offset += len; + UpdatePhySize(offset); + const HRESULT res = ReadStream_FALSE(_stream, buf, len); + if (res == S_FALSE && offset > FileSize) UnexpectedEnd = true; - RINOK(res); - UpdatePhySize(offset + len); - return S_OK; + return res; } -HRESULT CInArchive::Read(int volIndex, const CLongAllocDesc &lad, Byte *buf) +HRESULT CInArchive::ReadLad(unsigned volIndex, const CLongAllocDesc &lad, Byte *buf) { return Read(volIndex, lad.Location.PartitionRef, lad.Location.Pos, lad.GetLen(), (Byte *)buf); } -HRESULT CInArchive::ReadFromFile(int volIndex, const CItem &item, CByteBuffer &buf) +HRESULT CInArchive::ReadFromFile(unsigned volIndex, const CItem &item, CByteBuffer &buf) { if (item.Size >= (UInt32)1 << 30) return S_FALSE; @@ -301,8 +377,8 @@ HRESULT CInArchive::ReadFromFile(int volIndex, const CItem &item, CByteBuffer &b FOR_VECTOR (i, item.Extents) { const CMyExtent &e = item.Extents[i]; - UInt32 len = e.GetLen(); - RINOK(Read(volIndex, e.PartitionRef, e.Pos, len, (Byte *)buf + pos)); + const UInt32 len = e.GetLen(); + RINOK(Read(volIndex, e.PartitionRef, e.Pos, len, (Byte *)buf + pos)) pos += len; } return S_OK; @@ -311,36 +387,83 @@ HRESULT CInArchive::ReadFromFile(int volIndex, const CItem &item, CByteBuffer &b void CIcbTag::Parse(const Byte *p) { - // PriorDirectNum = Get32(p); - // StrategyType = Get16(p + 4); - // StrategyParam = Get16(p + 6); - // MaxNumOfEntries = Get16(p + 8); + // G32 (0, PriorDirectNum); + // G16 (4, StrategyType); + // G16 (6, StrategyParam); + // G16 (8, MaxNumOfEntries); FileType = p[11]; // ParentIcb.Parse(p + 12); - Flags = Get16(p + 18); + G16 (18, Flags); } + +// ECMA 4/14.9 File Entry +// UDF FileEntry 2.3.6 + +// ECMA 4/14.17 Extended File Entry + void CItem::Parse(const Byte *p) { - // Uid = Get32(p + 36); - // Gid = Get32(p + 40); - // Permissions = Get32(p + 44); - // FileLinkCount = Get16(p + 48); + // (-1) can be stored in Uid/Gid. + // G32 (36, Uid); + // G32 (40, Gid); + // G32 (44, Permissions); + G16 (48, FileLinkCount); // RecordFormat = p[50]; // RecordDisplayAttr = p[51]; - // RecordLen = Get32(p + 52); - Size = Get64(p + 56); - NumLogBlockRecorded = Get64(p + 64); + // G32 (52, RecordLen); + G64 (56, Size); + if (IsExtended) + { + // The sum of all Information Length fields for all streams of a file (including the default stream). If this file has no + // streams, the Object Size shall be equal to the Information Length. + // G64 (64, ObjectSize); + p += 8; + } + G64 (64, NumLogBlockRecorded); ATime.Parse(p + 72); MTime.Parse(p + 84); - // AttrtTime.Parse(p + 96); - // CheckPoint = Get32(p + 108); + if (IsExtended) + { + CreateTime.Parse(p + 96); + p += 12; + } + AttribTime.Parse(p + 96); + // G32 (108, CheckPoint); + /* + if (IsExtended) + { + // Get32(p + 112); // reserved + p += 4; + } // ExtendedAttrIcb.Parse(p + 112); + if (IsExtended) + { + StreamDirectoryIcb.Parse(p + 128); + p += 16; + } + */ + // ImplId.Parse(p + 128); - // UniqueId = Get64(p + 160); + // G64 (160, UniqueId); } -// 4/14.4 + +// ECMA 4/14.4 +// UDF 2.3.4 + +/* +File Characteristics: +Deleted bit: + ECMA: If set to ONE, shall mean this File Identifier Descriptor + identifies a file that has been deleted; + UDF: If the space for the file or directory is deallocated, + the implementation shall set the ICB field to zero. + ECMA 167 4/8.6 requires that the File Identifiers of all FIDs in a directory shall be unique. + The implementations shall follow these rules when a Deleted bit is set: + rewrire the compression ID of the File Identifier: 8 -> 254, 16 -> 255. +*/ + struct CFileId { // UInt16 FileVersion; @@ -349,112 +472,145 @@ struct CFileId CDString Id; CLongAllocDesc Icb; - bool IsItLinkParent() const { return (FileCharacteristics & FILEID_CHARACS_Parent) != 0; } - HRESULT Parse(const Byte *p, size_t size, size_t &processed); + bool IsItLink_Dir () const { return (FileCharacteristics & FILEID_CHARACS_Dir) != 0; } + bool IsItLink_Deleted() const { return (FileCharacteristics & FILEID_CHARACS_Deleted) != 0; } + bool IsItLink_Parent () const { return (FileCharacteristics & FILEID_CHARACS_Parent) != 0; } + + size_t Parse(const Byte *p, size_t size); }; -HRESULT CFileId::Parse(const Byte *p, size_t size, size_t &processed) +size_t CFileId::Parse(const Byte *p, size_t size) { - processed = 0; - if (size < 38) - return S_FALSE; + const unsigned k_FileId_FixedSize = 38; + if (size < k_FileId_FixedSize) + return 0; CTag tag; - RINOK(tag.Parse(p, size)); + if (tag.Parse(p, size) != S_OK) + return 0; if (tag.Id != DESC_TYPE_FileId) - return S_FALSE; + return 0; // FileVersion = Get16(p + 16); + // UDF: There shall be only one version of a file as specified below with the value being set to 1. + FileCharacteristics = p[18]; - unsigned idLen = p[19]; + const unsigned idLen = p[19]; Icb.Parse(p + 20); - unsigned impLen = Get16(p + 36); - if (size < 38 + idLen + impLen) - return S_FALSE; - // ImplUse.SetCapacity(impLen); - processed = 38; - // memcpy(ImplUse, p + processed, impLen); + const unsigned impLen = Get16(p + 36); + if (size < k_FileId_FixedSize + idLen + impLen) + return 0; + size_t processed = k_FileId_FixedSize; + // ImplUse.CopyFrom(p + processed, impLen); processed += impLen; Id.Parse(p + processed, idLen); processed += idLen; - for (;(processed & 3) != 0; processed++) - if (p[processed] != 0) - return S_FALSE; - return (processed <= size) ? S_OK : S_FALSE; + // const size_t processed2 = processed; + for (; processed & 3; processed++) + if (processed >= size || p[processed]) + return 0; + // some program can create non-standard UDF file where CrcLen doesn't include Padding data + if ((size_t)tag.CrcLen + 16 != processed + // && (size_t)tag.CrcLen + 16 != processed2 // we can enable this check to support non-standard UDF + ) + return 0; + return processed; } -HRESULT CInArchive::ReadFileItem(int volIndex, int fsIndex, const CLongAllocDesc &lad, int numRecurseAllowed) + + +HRESULT CInArchive::ReadFileItem(unsigned volIndex, unsigned fsIndex, const CLongAllocDesc &lad, bool isDir, int numRecurseAllowed) { if (Files.Size() % 100 == 0) - RINOK(_progress->SetCompleted(Files.Size(), _processedProgressBytes)); + RINOK(_progress->SetCompleted(Files.Size(), _processedProgressBytes)) if (numRecurseAllowed-- == 0) return S_FALSE; CFile &file = Files.Back(); const CLogVol &vol = LogVols[volIndex]; - unsigned partitionRef = lad.Location.PartitionRef; + const unsigned partitionRef = lad.Location.PartitionRef; if (partitionRef >= vol.PartitionMaps.Size()) return S_FALSE; CPartition &partition = Partitions[vol.PartitionMaps[partitionRef].PartitionIndex]; - UInt32 key = lad.Location.Pos; + const UInt32 key = lad.Location.Pos; UInt32 value; const UInt32 kRecursedErrorValue = (UInt32)(Int32)-1; if (partition.Map.Find(key, value)) { if (value == kRecursedErrorValue) return S_FALSE; - file.ItemIndex = value; + file.ItemIndex = (int)(Int32)value; } else { value = Items.Size(); - file.ItemIndex = (int)value; + file.ItemIndex = (int)(Int32)value; if (partition.Map.Set(key, kRecursedErrorValue)) return S_FALSE; - RINOK(ReadItem(volIndex, fsIndex, lad, numRecurseAllowed)); + RINOK(ReadItem(volIndex, (int)fsIndex, lad, isDir, numRecurseAllowed)) if (!partition.Map.Set(key, value)) return S_FALSE; } return S_OK; } -HRESULT CInArchive::ReadItem(int volIndex, int fsIndex, const CLongAllocDesc &lad, int numRecurseAllowed) + +// (fsIndex = -1) means that it's metadata file + +HRESULT CInArchive::ReadItem(unsigned volIndex, int fsIndex, const CLongAllocDesc &lad, bool isDir, int numRecurseAllowed) { - if (Items.Size() > kNumItemsMax) + if (Items.Size() >= kNumItemsMax) return S_FALSE; - Items.Add(CItem()); - CItem &item = Items.Back(); + CItem &item = Items.AddNew(); const CLogVol &vol = LogVols[volIndex]; - if (lad.GetLen() != vol.BlockSize) + const size_t size = lad.GetLen(); + if (size != vol.BlockSize) return S_FALSE; - const size_t size = lad.GetLen(); CByteBuffer buf(size); - RINOK(Read(volIndex, lad, buf)); + RINOK(ReadLad(volIndex, lad, buf)) CTag tag; const Byte *p = buf; - RINOK(tag.Parse(p, size)); - if (size < 176) + RINOK(tag.Parse(p, size)) + + item.IsExtended = (tag.Id == DESC_TYPE_ExtendedFile); + const size_t kExtendOffset = item.IsExtended ? 40 : 0; + + if (size < kExtendOffset + 176) return S_FALSE; - if (tag.Id != DESC_TYPE_File) + if (tag.Id != DESC_TYPE_File && + tag.Id != DESC_TYPE_ExtendedFile) return S_FALSE; item.IcbTag.Parse(p + 16); - if (item.IcbTag.FileType != ICB_FILE_TYPE_DIR && - item.IcbTag.FileType != ICB_FILE_TYPE_FILE) + + // maybe another FileType values are possible in rare cases. + // Shoud we ignore FileType here? + if (fsIndex < 0) + { + // if (item.IcbTag.FileType == ICB_FILE_TYPE_DIR) return S_FALSE; + if (item.IcbTag.FileType != ICB_FILE_TYPE_METADATA && + item.IcbTag.FileType != ICB_FILE_TYPE_METADATA_MIRROR && + item.IcbTag.FileType != ICB_FILE_TYPE_METADATA_BITMAP) + return S_FALSE; + } + else if ( + item.IcbTag.FileType != ICB_FILE_TYPE_DIR && + item.IcbTag.FileType != ICB_FILE_TYPE_FILE && + item.IcbTag.FileType != ICB_FILE_TYPE_REAL_TIME_FILE) // M2TS files in /BDMV/STREAM/ in Blu-ray movie return S_FALSE; item.Parse(p); _processedProgressBytes += (UInt64)item.NumLogBlockRecorded * vol.BlockSize + size; - UInt32 extendedAttrLen = Get32(p + 168); - UInt32 allocDescriptorsLen = Get32(p + 172); + const UInt32 extendedAttrLen = Get32(p + 168 + kExtendOffset); + const UInt32 allocDescriptorsLen = Get32(p + 172 + kExtendOffset); if ((extendedAttrLen & 3) != 0) return S_FALSE; - size_t pos = 176; + size_t pos = 176 + kExtendOffset; if (extendedAttrLen > size - pos) return S_FALSE; /* @@ -472,10 +628,10 @@ HRESULT CInArchive::ReadItem(int volIndex, int fsIndex, const CLongAllocDesc &la */ pos += extendedAttrLen; - int desctType = item.IcbTag.GetDescriptorType(); + const int descType = item.IcbTag.GetDescriptorType(); if (allocDescriptorsLen > size - pos) return S_FALSE; - if (desctType == ICB_DESC_TYPE_INLINE) + if (descType == ICB_DESC_TYPE_INLINE) { item.IsInline = true; item.InlineData.CopyFrom(p + pos, allocDescriptorsLen); @@ -483,12 +639,12 @@ HRESULT CInArchive::ReadItem(int volIndex, int fsIndex, const CLongAllocDesc &la else { item.IsInline = false; - if (desctType != ICB_DESC_TYPE_SHORT && desctType != ICB_DESC_TYPE_LONG) + if (descType != ICB_DESC_TYPE_SHORT && descType != ICB_DESC_TYPE_LONG) return S_FALSE; for (UInt32 i = 0; i < allocDescriptorsLen;) { CMyExtent e; - if (desctType == ICB_DESC_TYPE_SHORT) + if (descType == ICB_DESC_TYPE_SHORT) { if (i + 8 > allocDescriptorsLen) return S_FALSE; @@ -514,25 +670,38 @@ HRESULT CInArchive::ReadItem(int volIndex, int fsIndex, const CLongAllocDesc &la } } + if (isDir != item.IcbTag.IsDir()) + return S_FALSE; + if (item.IcbTag.IsDir()) { + if (fsIndex < 0) + return S_FALSE; + if (!item.CheckChunkSizes() || !CheckItemExtents(volIndex, item)) return S_FALSE; CByteBuffer buf2; - RINOK(ReadFromFile(volIndex, item, buf2)); + RINOK(ReadFromFile(volIndex, item, buf2)) item.Size = 0; item.Extents.ClearAndFree(); item.InlineData.Free(); const Byte *p2 = buf2; - const size_t size2 = buf2.Size(); - size_t processedTotal = 0; - for (; processedTotal < size2;) + size_t size2 = buf2.Size(); + while (size2 != 0) { - size_t processedCur; CFileId fileId; - RINOK(fileId.Parse(p2 + processedTotal, size2 - processedTotal, processedCur)); - if (!fileId.IsItLinkParent()) + { + const size_t cur = fileId.Parse(p2, size2); + if (cur == 0) + return S_FALSE; + p2 += cur; + size2 -= cur; + } + if (fileId.IsItLink_Parent()) + continue; + if (fileId.IsItLink_Deleted()) + continue; { CFile file; // file.FileVersion = fileId.FileVersion; @@ -545,12 +714,12 @@ HRESULT CInArchive::ReadItem(int volIndex, int fsIndex, const CLongAllocDesc &la return S_FALSE; item.SubFiles.Add(Files.Size()); - if (Files.Size() > kNumFilesMax) + if (Files.Size() >= kNumFilesMax) return S_FALSE; Files.Add(file); - RINOK(ReadFileItem(volIndex, fsIndex, fileId.Icb, numRecurseAllowed)); + RINOK(ReadFileItem(volIndex, (unsigned)fsIndex, fileId.Icb, + fileId.IsItLink_Dir(), numRecurseAllowed)) } - processedTotal += processedCur; } } else @@ -567,11 +736,12 @@ HRESULT CInArchive::ReadItem(int volIndex, int fsIndex, const CLongAllocDesc &la return S_OK; } + HRESULT CInArchive::FillRefs(CFileSet &fs, unsigned fileIndex, int parent, int numRecurseAllowed) { if ((_numRefs & 0xFFF) == 0) { - RINOK(_progress->SetCompleted()); + RINOK(_progress->SetCompleted()) } if (numRecurseAllowed-- == 0) return S_FALSE; @@ -581,23 +751,24 @@ HRESULT CInArchive::FillRefs(CFileSet &fs, unsigned fileIndex, int parent, int n CRef ref; ref.FileIndex = fileIndex; ref.Parent = parent; - parent = fs.Refs.Size(); + parent = (int)fs.Refs.Size(); fs.Refs.Add(ref); const CItem &item = Items[Files[fileIndex].ItemIndex]; FOR_VECTOR (i, item.SubFiles) { - RINOK(FillRefs(fs, item.SubFiles[i], parent, numRecurseAllowed)); + RINOK(FillRefs(fs, item.SubFiles[i], parent, numRecurseAllowed)) } return S_OK; } + API_FUNC_IsArc IsArc_Udf(const Byte *p, size_t size) { UInt32 res = k_IsArc_Res_NO; unsigned SecLogSize; - for (SecLogSize = 11;; SecLogSize -= 3) + for (SecLogSize = 11;; SecLogSize -= 2) { - if (SecLogSize < 8) + if (SecLogSize < 9) return res; const UInt32 offset = (UInt32)256 << SecLogSize; const UInt32 bufSize = (UInt32)1 << SecLogSize; @@ -608,7 +779,11 @@ API_FUNC_IsArc IsArc_Udf(const Byte *p, size_t size) CTag tag; if (tag.Parse(p + offset, bufSize) == S_OK) if (tag.Id == DESC_TYPE_AnchorVolPtr) - return k_IsArc_Res_YES; + { + if (Get32(p + offset + 12) == 256 && // TagLocation + tag.CrcLen >= 16) + return k_IsArc_Res_YES; + } } } } @@ -618,7 +793,7 @@ HRESULT CInArchive::Open2() { Clear(); UInt64 fileSize; - RINOK(_stream->Seek(0, STREAM_SEEK_END, &fileSize)); + RINOK(InStream_GetSize_SeekToEnd(_stream, fileSize)) FileSize = fileSize; // Some UDFs contain additional pad zeros (2 KB). @@ -629,7 +804,7 @@ HRESULT CInArchive::Open2() const size_t kBufSize = 1 << 14; Byte buf[kBufSize]; size_t readSize = (fileSize < kBufSize) ? (size_t)fileSize : kBufSize; - RINOK(_stream->Seek(fileSize - readSize, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekSet(_stream, fileSize - readSize)) RINOK(ReadStream(_stream, buf, &readSize)); size_t i = readSize; for (;;) @@ -649,90 +824,114 @@ HRESULT CInArchive::Open2() extentVDS.Parse(buf + i + 16); */ + /* + An Anchor Volume Descriptor Pointer structure shall be recorded in at + least 2 of the following 3 locations on the media: + Logical Sector 256. + Logical Sector (N - 256). + N + */ + const size_t kBufSize = 1 << 11; Byte buf[kBufSize]; - for (SecLogSize = 11;; SecLogSize -= 3) + for (SecLogSize = 11;; SecLogSize -= 2) { - if (SecLogSize < 8) + // Windows 10 uses unusual (SecLogSize = 9) + if (SecLogSize < 9) return S_FALSE; - UInt32 offset = (UInt32)256 << SecLogSize; + const UInt32 offset = (UInt32)256 << SecLogSize; if (offset >= fileSize) continue; - RINOK(_stream->Seek(offset, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekSet(_stream, offset)) const size_t bufSize = (size_t)1 << SecLogSize; size_t readSize = bufSize; - RINOK(ReadStream(_stream, buf, &readSize)); + RINOK(ReadStream(_stream, buf, &readSize)) if (readSize == bufSize) { CTag tag; if (tag.Parse(buf, readSize) == S_OK) if (tag.Id == DESC_TYPE_AnchorVolPtr) - break; + { + if (Get32(buf + 12) == 256 && + tag.CrcLen >= 16) // TagLocation + break; + } } } PhySize = (UInt32)(256 + 1) << SecLogSize; IsArc = true; + // UDF 2.2.3 AnchorVolumeDescriptorPointer + CExtent extentVDS; extentVDS.Parse(buf + 16); { CExtent extentVDS2; extentVDS2.Parse(buf + 24); - UpdatePhySize(((UInt64)extentVDS.Pos << SecLogSize) + extentVDS.Len); - UpdatePhySize(((UInt64)extentVDS2.Pos << SecLogSize) + extentVDS2.Len); + UpdatePhySize(extentVDS); + UpdatePhySize(extentVDS2); } for (UInt32 location = 0; ; location++) { - const size_t bufSize = (size_t)1 << SecLogSize; - if (((UInt64)(location + 1) << SecLogSize) > extentVDS.Len) + if (location >= (extentVDS.Len >> SecLogSize)) return S_FALSE; - UInt64 offs = (UInt64)(extentVDS.Pos + location) << SecLogSize; - RINOK(_stream->Seek(offs, STREAM_SEEK_SET, NULL)); - HRESULT res = ReadStream_FALSE(_stream, buf, bufSize); - if (res == S_FALSE && offs + bufSize > FileSize) - UnexpectedEnd = true; - RINOK(res); - - - CTag tag; + const size_t bufSize = (size_t)1 << SecLogSize; { - const size_t pos = 0; - RINOK(tag.Parse(buf + pos, bufSize - pos)); + const UInt64 offs = ((UInt64)extentVDS.Pos + location) << SecLogSize; + RINOK(InStream_SeekSet(_stream, offs)) + const HRESULT res = ReadStream_FALSE(_stream, buf, bufSize); + if (res == S_FALSE && offs + bufSize > FileSize) + UnexpectedEnd = true; + RINOK(res) } + + CTag tag; + RINOK(tag.Parse(buf, bufSize)) + if (tag.Id == DESC_TYPE_Terminating) break; - + + if (tag.Id == DESC_TYPE_PrimVol) + { + CPrimeVol &pm = PrimeVols.AddNew(); + pm.Parse(buf); + continue; + } + if (tag.Id == DESC_TYPE_Partition) { // Partition Descriptor - // ECMA 167 3/10.5 - // UDF / 2.2.14 - + // ECMA 3/10.5 + // UDF 2.2.14 if (Partitions.Size() >= kNumPartitionsMax) return S_FALSE; CPartition partition; - // UInt32 volDescSeqNumer = Get32(buf + 16); - // partition.Flags = Get16(buf + 20); + // const UInt32 volDescSeqNumer = Get32(buf + 16); + partition.Flags = Get16(buf + 20); partition.Number = Get16(buf + 22); - // partition.ContentsId.Parse(buf + 24); + partition.ContentsId.Parse(buf + 24); // memcpy(partition.ContentsUse, buf + 56, sizeof(partition.ContentsUse)); - // ContentsUse is Partition Header Description. + // ContentsUse contains Partition Header Description. + // ECMA 4/14.3 + // UDF PartitionHeaderDescriptor 2.3.3 - // partition.AccessType = Get32(buf + 184); + partition.AccessType = Get32(buf + 184); partition.Pos = Get32(buf + 188); partition.Len = Get32(buf + 192); - // partition.ImplId.Parse(buf + 196); + partition.ImplId.Parse(buf + 196); // memcpy(partition.ImplUse, buf + 228, sizeof(partition.ImplUse)); PRF(printf("\nPartition number = %2d pos = %d len = %d", partition.Number, partition.Pos, partition.Len)); Partitions.Add(partition); + continue; } - else if (tag.Id == DESC_TYPE_LogicalVol) + + if (tag.Id == DESC_TYPE_LogicalVol) { /* Logical Volume Descriptor ECMA 3/10.6 @@ -740,46 +939,65 @@ HRESULT CInArchive::Open2() if (LogVols.Size() >= kNumLogVolumesMax) return S_FALSE; - CLogVol vol; + CLogVol &vol = LogVols.AddNew(); + vol.Id.Parse(buf + 84); vol.BlockSize = Get32(buf + 212); - // vol.DomainId.Parse(buf + 216); - + if (vol.BlockSize != ((UInt32)1 << SecLogSize)) + { + // UDF 2.2.4.2 LogicalBlockSize + // UDF probably doesn't allow different sizes + return S_FALSE; + } + /* if (vol.BlockSize < 512 || vol.BlockSize > ((UInt32)1 << 30)) return S_FALSE; - - // memcpy(vol.ContentsUse, buf + 248, sizeof(vol.ContentsUse)); - vol.FileSetLocation.Parse(buf + 248); + */ + + vol.DomainId.Parse(buf + 216); + + // ECMA 4/3.1 + // UDF 2.2.4.4 LogicalVolumeContentsUse /* the extent in which the first File Set Descriptor Sequence of the logical volume is recorded */ + vol.FileSetLocation.Parse(buf + 248); + // memcpy(vol.ContentsUse, buf + 248, sizeof(vol.ContentsUse)); - // UInt32 mapTableLength = Get32(buf + 264); - UInt32 numPartitionMaps = Get32(buf + 268); + vol.ImplId.Parse(buf + 272); + // memcpy(vol.ImplUse, buf + 304, sizeof(vol.ImplUse)); + // vol.IntegritySequenceExtent.Parse(buf + 432); + + const UInt32 mapTableLen = Get32(buf + 264); + const UInt32 numPartitionMaps = Get32(buf + 268); if (numPartitionMaps > kNumPartitionsMax) return S_FALSE; - // vol.ImplId.Parse(buf + 272); - // memcpy(vol.ImplUse, buf + 128, sizeof(vol.ImplUse)); PRF(printf("\nLogicalVol numPartitionMaps = %2d", numPartitionMaps)); + size_t pos = 440; + if (mapTableLen > bufSize - pos) + return S_FALSE; + const size_t posLimit = pos + mapTableLen; + for (UInt32 i = 0; i < numPartitionMaps; i++) { - if (pos + 2 > bufSize) + // ECMA 3/10.7 Partition maps + if (pos + 2 > posLimit) return S_FALSE; CPartitionMap pm; - pm.Type = buf[pos]; + pm.Type = buf[pos + 0]; // pm.Length = buf[pos + 1]; - Byte len = buf[pos + 1]; - - if (pos + len > bufSize) + const Byte len = buf[pos + 1]; + if (pos + len > posLimit) return S_FALSE; // memcpy(pm.Data, buf + pos + 2, pm.Length - 2); if (pm.Type == 1) { - if (len != 6) // < 6 + // ECMA 3/10.7.2 + if (len != 6) return S_FALSE; - // pm.VolSeqNumber = Get16(buf + pos + 2); + pm.VolumeSequenceNumber = Get16(buf + pos + 2); pm.PartitionNumber = Get16(buf + pos + 4); PRF(printf("\nPartitionMap type 1 PartitionNumber = %2d", pm.PartitionNumber)); } @@ -790,28 +1008,60 @@ HRESULT CInArchive::Open2() /* ECMA 10.7.3 / Type 2 Partition Map 62 bytes: Partition Identifier. */ - /* UDF 2.6 - 2.2.8 Virtual Partition Map - This is an extension of ECMA 167 to expand its scope to include - sequentially written media (eg. CD-R). This extension is for a - Partition Map entry to describe a virtual space. */ + /* UDF + 2.2.8 "*UDF Virtual Partition" + 2.2.9 "*UDF Sparable Partition" + 2.2.10 "*UDF Metadata Partition" + */ - // It's not implemented still. - if (Get16(buf + pos + 2) != 0) + if (Get16(buf + pos + 2) != 0) // reserved return S_FALSE; - // pm.VolSeqNumber = Get16(buf + pos + 36); + + pm.PartitionTypeId.Parse(buf + pos + 4); + pm.VolumeSequenceNumber = Get16(buf + pos + 36); pm.PartitionNumber = Get16(buf + pos + 38); + + if (memcmp(pm.PartitionTypeId.Id, "*UDF Metadata Partition", 23) != 0) + return S_FALSE; + + // UDF 2.2.10 Metadata Partition Map + pm.MetadataFileLocation = Get32(buf + pos + 40); + // pm.MetadataMirrorFileLocation = Get32(buf + pos + 44); + // pm.MetadataBitmapFileLocation = Get32(buf + pos + 48); + // pm.AllocationUnitSize = Get32(buf + pos + 52); + // pm.AlignmentUnitSize = Get16(buf + pos + 56); + // pm.Flags = buf[pos + 58]; + PRF(printf("\nPartitionMap type 2 PartitionNumber = %2d", pm.PartitionNumber)); // Unsupported = true; - return S_FALSE; + // return S_FALSE; } else return S_FALSE; pos += len; vol.PartitionMaps.Add(pm); } - LogVols.Add(vol); + continue; + } + + /* + if (tag.Id == DESC_TYPE_UnallocSpace) + { + // UInt32 volDescSeqNumer = Get32(buf + 16); + const UInt32 numAlocDescs = Get32(buf + 20); + // we need examples for (numAlocDescs != 0) case + if (numAlocDescs > (bufSize - 24) / 8) + return S_FALSE; + for (UInt32 i = 0; i < numAlocDescs; i++) + { + CExtent e; + e.Parse(buf + 24 + i * 8); + } + continue; } + else + continue; + */ } UInt64 totalSize = 0; @@ -823,12 +1073,18 @@ HRESULT CInArchive::Open2() FOR_VECTOR (pmIndex, vol.PartitionMaps) { CPartitionMap &pm = vol.PartitionMaps[pmIndex]; - unsigned i; - for (i = 0; i < Partitions.Size(); i++) + for (unsigned i = 0;; i++) { + if (i == Partitions.Size()) + return S_FALSE; CPartition &part = Partitions[i]; if (part.Number == pm.PartitionNumber) { + pm.PartitionIndex = i; + if (pm.Type == 2) + break; + + /* if (part.VolIndex >= 0) { // it's for 2.60. Fix it @@ -836,19 +1092,101 @@ HRESULT CInArchive::Open2() return S_FALSE; // return S_FALSE; } - pm.PartitionIndex = i; part.VolIndex = volIndex; + */ totalSize += (UInt64)part.Len << SecLogSize; break; } } - if (i == Partitions.Size()) + } + } + + for (volIndex = 0; volIndex < LogVols.Size(); volIndex++) + { + CLogVol &vol = LogVols[volIndex]; + FOR_VECTOR (pmIndex, vol.PartitionMaps) + { + CPartitionMap &pm = vol.PartitionMaps[pmIndex]; + if (pm.Type != 2) + continue; + + { + CLongAllocDesc lad; + lad.Len = vol.BlockSize; + lad.Location.Pos = pm.MetadataFileLocation; + // lad.Location.Pos = pm.MetadataMirrorFileLocation; + + lad.Location.PartitionRef = (UInt16)pmIndex; + + /* we need correct PartitionMaps[lad.Location.PartitionRef].PartitionIndex. + so we can use pmIndex or find (Type==1) PartitionMap */ + FOR_VECTOR (pmIndex2, vol.PartitionMaps) + { + const CPartitionMap &pm2 = vol.PartitionMaps[pmIndex2]; + if (pm2.PartitionNumber == pm.PartitionNumber && pm2.Type == 1) + { + lad.Location.PartitionRef = (UInt16)pmIndex2; + break; + } + } + + RINOK(ReadItem(volIndex, + -1, // (fsIndex = -1) means that it's metadata + lad, + false, // isDir + 1)) // numRecurseAllowed + } + { + const CItem &item = Items.Back(); + if (!CheckItemExtents(volIndex, item)) + return S_FALSE; + if (item.Extents.Size() != 1) + { + if (item.Extents.Size() < 1) + return S_FALSE; + /* Windows 10 writes empty record item.Extents[1]. + we ignore such extent here */ + for (unsigned k = 1; k < item.Extents.Size(); k++) + { + const CMyExtent &e = item.Extents[k]; + if (e.GetLen() != 0) + return S_FALSE; + } + } + + const CMyExtent &e = item.Extents[0]; + const CPartition &part = Partitions[pm.PartitionIndex]; + CPartition mp = part; + mp.IsMetadata = true; + // mp.Number = part.Number; + mp.Pos = part.Pos + e.Pos; + mp.Len = e.Len >> SecLogSize; + pm.PartitionIndex = Partitions.Add(mp); + } + // Items.DeleteBack(); // we can delete that metadata item + + /* + // short version of code to read metadata file. + RINOK(CInArchive::Read(volIndex, pmIndex, pm.MetadataFileLocation, 224, buf)); + CTag tag; + RINOK(tag.Parse(buf, 224)); + if (tag.Id != DESC_TYPE_ExtendedFile) return S_FALSE; + CShortAllocDesc sad; + sad.Parse(buf + 216); + const CPartition &part = Partitions[pm.PartitionIndex]; + CPartition mp = part; + mp.IsMetadata = true; + // mp.Number = part.Number; + mp.Pos = part.Pos + sad.Pos; + mp.Len = sad.Len >> SecLogSize; + pm.PartitionIndex = Partitions.Add(mp); + */ } } - RINOK(_progress->SetTotal(totalSize)); + RINOK(_progress->SetTotal(totalSize)) PRF(printf("\n Read files")); @@ -865,37 +1203,41 @@ HRESULT CInArchive::Open2() if (nextExtent.GetLen() < 512) return S_FALSE; CByteBuffer buf2(nextExtent.GetLen()); - RINOK(Read(volIndex, nextExtent, buf2)); + RINOK(ReadLad(volIndex, nextExtent, buf2)) const Byte *p = buf2; - size_t size = nextExtent.GetLen(); + const size_t size = nextExtent.GetLen(); CTag tag; - RINOK(tag.Parse(p, size)); + RINOK(tag.Parse(p, size)) + /* + // commented in 22.01 if (tag.Id == DESC_TYPE_ExtendedFile) { // ECMA 4 / 14.17 // 2.60 ?? return S_FALSE; } + */ if (tag.Id != DESC_TYPE_FileSet) return S_FALSE; - PRF(printf("\n FileSet", volIndex)); + PRF(printf("\n FileSet")); CFileSet fs; - fs.RecodringTime.Parse(p + 16); + fs.RecordingTime.Parse(p + 16); // fs.InterchangeLevel = Get16(p + 18); // fs.MaxInterchangeLevel = Get16(p + 20); - // fs.FileSetNumber = Get32(p + 40); - // fs.FileSetDescNumber = Get32(p + 44); + fs.FileSetNumber = Get32(p + 40); + fs.FileSetDescNumber = Get32(p + 44); - // fs.Id.Parse(p + 304); - // fs.CopyrightId.Parse(p + 336); - // fs.AbstractId.Parse(p + 368); + fs.LogicalVolumeId.Parse(p + 112); + fs.Id.Parse(p + 304); + fs.CopyrightId.Parse(p + 336); + fs.AbstractId.Parse(p + 368); fs.RootDirICB.Parse(p + 400); - // fs.DomainId.Parse(p + 416); + fs.DomainId.Parse(p + 416); // fs.SystemStreamDirICB.Parse(p + 464); @@ -907,10 +1249,12 @@ HRESULT CInArchive::Open2() FOR_VECTOR (fsIndex, vol.FileSets) { CFileSet &fs = vol.FileSets[fsIndex]; - unsigned fileIndex = Files.Size(); + const unsigned fileIndex = Files.Size(); Files.AddNew(); - RINOK(ReadFileItem(volIndex, fsIndex, fs.RootDirICB, kNumRecursionLevelsMax)); - RINOK(FillRefs(fs, fileIndex, -1, kNumRecursionLevelsMax)); + RINOK(ReadFileItem(volIndex, fsIndex, fs.RootDirICB, + true, // isDir + kNumRecursionLevelsMax)) + RINOK(FillRefs(fs, fileIndex, -1, kNumRecursionLevelsMax)) } } @@ -937,16 +1281,16 @@ HRESULT CInArchive::Open2() FOR_VECTOR (extentIndex, item.Extents) { const CMyExtent &extent = item.Extents[extentIndex]; - UInt32 len = extent.GetLen(); + const UInt32 len = extent.GetLen(); if (len == 0) continue; if (size < len) break; - int partitionIndex = vol.PartitionMaps[extent.PartitionRef].PartitionIndex; - UInt32 logBlockNumber = extent.Pos; + const unsigned partitionIndex = vol.PartitionMaps[extent.PartitionRef].PartitionIndex; + const UInt32 logBlockNumber = extent.Pos; const CPartition &partition = Partitions[partitionIndex]; - UInt64 offset = ((UInt64)partition.Pos << SecLogSize) + + const UInt64 offset = ((UInt64)partition.Pos << SecLogSize) + (UInt64)logBlockNumber * vol.BlockSize; UpdatePhySize(offset + len); } @@ -966,7 +1310,7 @@ HRESULT CInArchive::Open2() UInt64 rem = fileSize - PhySize; const size_t secSize = (size_t)1 << SecLogSize; - RINOK(_stream->Seek(PhySize, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekSet(_stream, PhySize)) // some UDF images contain ZEROs before "Anchor Volume Descriptor Pointer" at the end @@ -979,16 +1323,18 @@ HRESULT CInArchive::Open2() if (readSize > rem) readSize = (size_t)rem; - RINOK(ReadStream(_stream, buf, &readSize)); + RINOK(ReadStream(_stream, buf, &readSize)) if (readSize == 0) break; - if (readSize == secSize && NoEndAnchor) + // some udf contain many EndAnchors + if (readSize == secSize /* && NoEndAnchor */) { CTag tag; - if (tag.Parse(buf, readSize) == S_OK && - tag.Id == DESC_TYPE_AnchorVolPtr) + if (tag.Parse(buf, readSize) == S_OK + && tag.Id == DESC_TYPE_AnchorVolPtr + && Get32(buf + 12) == (UInt32)((fileSize - rem) >> SecLogSize)) { NoEndAnchor = false; rem -= readSize; @@ -1051,6 +1397,7 @@ void CInArchive::Clear() Partitions.Clear(); LogVols.Clear(); + PrimeVols.Clear(); Items.Clear(); Files.Clear(); _fileNameLengthTotal = 0; @@ -1060,16 +1407,291 @@ void CInArchive::Clear() _processedProgressBytes = 0; } + +static const char * const g_PartitionTypes[] = +{ + "Pseudo-Overwritable" // UDF + , "Read-Only" + , "Write-Once" + , "Rewritable" + , "Overwritable" +}; + + +static void AddComment_Align(UString &s) +{ + s += " "; +} + +static void AddComment_PropName(UString &s, const char *name) +{ + AddComment_Align(s); + s += name; + s += ": "; +} + +static void AddComment_UInt32(UString &s, const char *name, UInt32 val) +{ + AddComment_PropName(s, name); + s.Add_UInt32(val); + s.Add_LF(); +} + +static void AddComment_UInt32_2(UString &s, const char *name, UInt32 val) +{ + AddComment_Align(s); + AddComment_UInt32(s, name, val); +} + + +static void AddComment_UInt64(UString &s, const char *name, UInt64 val) +{ + AddComment_PropName(s, name); + s.Add_UInt64(val); + s.Add_LF(); +} + +static void AddComment_RegId(UString &s, const char *name, const CRegId &ri) +{ + AddComment_PropName(s, name); + ri.AddCommentTo(s); + s.Add_LF(); +} + +static void AddComment_RegId_Domain(UString &s, const char *name, const CRegId &ri) +{ + AddComment_PropName(s, name); + ri.AddCommentTo(s); + { + UString s2; + ri.AddUdfVersionTo(s2); + if (!s2.IsEmpty()) + { + s += "::"; + s += s2; + } + } + s.Add_LF(); +} + + +// UDF 6.3.1 OS Class + +static const char * const g_OsClasses[] = +{ + NULL + , "DOS" + , "OS/2" + , "Macintosh OS" + , "UNIX" + , "Windows 9x" + , "Windows NT" + , "OS/400" + , "BeOS" + , "Windows CE" +}; + +// UDF 6.3.2 OS Identifier + +static const char * const g_OsIds_Unix[] = +{ + NULL // "Generic" + , "AIX" + , "SUN OS / Solaris" + , "HP/UX" + , "Silicon Graphics Irix" + , "Linux" + , "MKLinux" + , "FreeBSD" + , "NetBSD" +}; + +static void AddOs_Class_Id(UString &s, const Byte *p) +{ + // UDF 2.1.5.3 Implementation Identifier Suffix + // Appendix 6.3 Operating System Identifiers. + const Byte osClass = p[0]; + if (osClass != 0) + { + s += "::"; + s += TypeToString(g_OsClasses, Z7_ARRAY_SIZE(g_OsClasses), osClass); + } + const Byte osId = p[1]; + if (osId != 0) + { + s += "::"; + if (osClass == 4) // unix + { + s += TypeToString(g_OsIds_Unix, Z7_ARRAY_SIZE(g_OsIds_Unix), osId); + } + else + s.Add_UInt32(osId); + } +} + + +static void AddComment_RegId_Impl(UString &s, const char *name, const CRegId &ri) +{ + AddComment_PropName(s, name); + ri.AddCommentTo(s); + { + AddOs_Class_Id(s, ri.Suffix); + } + s.Add_LF(); +} + + +static void AddComment_RegId_UdfId(UString &s, const char *name, const CRegId &ri) +{ + AddComment_PropName(s, name); + ri.AddCommentTo(s); + { + // UDF 2.1.5.3 + // UDF Identifier Suffix format + UString s2; + ri.AddUdfVersionTo(s2); + if (!s2.IsEmpty()) + { + s += "::"; + s += s2; + } + AddOs_Class_Id(s, &ri.Suffix[2]); + } + s.Add_LF(); +} + +static void AddComment_DString32(UString &s, const char *name, const CDString32 &d) +{ + AddComment_Align(s); + AddComment_PropName(s, name); + s += d.GetString(); + s.Add_LF(); +} + UString CInArchive::GetComment() const { - UString res; - FOR_VECTOR (i, LogVols) + UString s; + { + s += "Primary Volumes:"; + s.Add_LF(); + FOR_VECTOR (i, PrimeVols) + { + if (i != 0) + s.Add_LF(); + const CPrimeVol &pv = PrimeVols[i]; + // AddComment_UInt32(s, "VolumeDescriptorSequenceNumber", pv.VolumeDescriptorSequenceNumber); + // if (PrimeVols.Size() != 1 || pv.PrimaryVolumeDescriptorNumber != 0) + AddComment_UInt32(s, "PrimaryVolumeDescriptorNumber", pv.PrimaryVolumeDescriptorNumber); + // if (pv.MaximumVolumeSequenceNumber != 1 || pv.VolumeSequenceNumber != 1) + AddComment_UInt32(s, "VolumeSequenceNumber", pv.VolumeSequenceNumber); + if (pv.MaximumVolumeSequenceNumber != 1) + AddComment_UInt32(s, "MaximumVolumeSequenceNumber", pv.MaximumVolumeSequenceNumber); + AddComment_PropName(s, "VolumeId"); + s += pv.VolumeId.GetString(); + s.Add_LF(); + AddComment_PropName(s, "VolumeSetId"); + s += pv.VolumeSetId.GetString(); + s.Add_LF(); + // AddComment_UInt32(s, "InterchangeLevel", pv.InterchangeLevel); + // AddComment_UInt32(s, "MaximumInterchangeLevel", pv.MaximumInterchangeLevel); + AddComment_RegId(s, "ApplicationId", pv.ApplicationId); + AddComment_RegId_Impl(s, "ImplementationId", pv.ImplId); + } + } { - if (i != 0) - res.Add_Space(); - res += LogVols[i].GetName(); + s += "Partitions:"; + s.Add_LF(); + FOR_VECTOR (i, Partitions) + { + if (i != 0) + s.Add_LF(); + const CPartition &part = Partitions[i]; + AddComment_UInt32(s, "PartitionIndex", i); + AddComment_UInt32(s, "PartitionNumber", part.Number); + if (part.IsMetadata) + AddComment_UInt32(s, "IsMetadata", 1); + else + { + AddComment_RegId(s, "ContentsId", part.ContentsId); + AddComment_RegId_Impl(s, "ImplementationId", part.ImplId); + AddComment_PropName(s, "AccessType"); + s += TypeToString(g_PartitionTypes, Z7_ARRAY_SIZE(g_PartitionTypes), part.AccessType); + s.Add_LF(); + } + AddComment_UInt64(s, "Size", (UInt64)part.Len << SecLogSize); + AddComment_UInt64(s, "Pos", (UInt64)part.Pos << SecLogSize); + } } - return res; + s += "Logical Volumes:"; + s.Add_LF(); + { + FOR_VECTOR (i, LogVols) + { + if (i != 0) + s.Add_LF(); + const CLogVol &vol = LogVols[i]; + if (LogVols.Size() != 1) + AddComment_UInt32(s, "Number", i); + AddComment_PropName(s, "Id"); + s += vol.Id.GetString(); + s.Add_LF(); + AddComment_UInt32(s, "BlockSize", vol.BlockSize); + AddComment_RegId_Domain(s, "DomainId", vol.DomainId); + AddComment_RegId_Impl(s, "ImplementationId", vol.ImplId); + // AddComment_UInt64(s, "IntegritySequenceExtent_Len", vol.IntegritySequenceExtent.Len); + // AddComment_UInt64(s, "IntegritySequenceExtent_Pos", (UInt64)vol.IntegritySequenceExtent.Pos << SecLogSize); + + s += " Partition Maps:"; + s.Add_LF(); + { + FOR_VECTOR (j, vol.PartitionMaps) + { + if (j != 0) + s.Add_LF(); + const CPartitionMap &pm = vol.PartitionMaps[j]; + AddComment_UInt32_2(s, "PartitionMap", j); + AddComment_UInt32_2(s, "Type", pm.Type); + AddComment_UInt32_2(s, "VolumeSequenceNumber", pm.VolumeSequenceNumber); + AddComment_UInt32_2(s, "PartitionNumber", pm.PartitionNumber); + if (pm.Type == 2) + { + AddComment_UInt32_2(s, "MetadataFileLocation", pm.MetadataFileLocation); + // AddComment_UInt32_2(s, "MetadataMirrorFileLocation", pm.MetadataMirrorFileLocation); + // AddComment_UInt32_2(s, "MetadataBitmapFileLocation", pm.MetadataBitmapFileLocation); + // AddComment_UInt32_2(s, "AllocationUnitSize", pm.AllocationUnitSize); + // AddComment_UInt32_2(s, "AlignmentUnitSize", pm.AlignmentUnitSize); + // AddComment_UInt32_2(s, "Flags", pm.Flags); + AddComment_Align(s); AddComment_RegId_UdfId(s, "PartitionTypeId", pm.PartitionTypeId); + } + } + } + s += " File Sets:"; + s.Add_LF(); + { + FOR_VECTOR (j, vol.FileSets) + { + if (j != 0) + s.Add_LF(); + const CFileSet &fs = vol.FileSets[j]; + AddComment_Align(s); AddComment_UInt32(s, "FileSetNumber", fs.FileSetNumber); + AddComment_Align(s); AddComment_UInt32(s, "FileSetDescNumber", fs.FileSetDescNumber); + + AddComment_Align(s); + AddComment_PropName(s, "LogicalVolumeId"); + s += fs.LogicalVolumeId.GetString(); + s.Add_LF(); + + AddComment_DString32(s, "Id", fs.Id); + AddComment_DString32(s, "CopyrightId", fs.CopyrightId); + AddComment_DString32(s, "AbstractId", fs.AbstractId); + + AddComment_Align(s); + AddComment_RegId_Domain(s, "DomainId", fs.DomainId); + } + } + } + } + return s; } static UString GetSpecName(const UString &name) @@ -1077,14 +1699,7 @@ static UString GetSpecName(const UString &name) UString name2 = name; name2.Trim(); if (name2.IsEmpty()) - { - /* - wchar_t s[32]; - ConvertUInt64ToString(id, s); - return L"[" + (UString)s + L"]"; - */ - return L"[]"; - } + return UString("[]"); return name; } @@ -1096,7 +1711,7 @@ static void UpdateWithName(UString &res, const UString &addString) res.Insert(0, addString + WCHAR_PATH_SEPARATOR); } -UString CInArchive::GetItemPath(int volIndex, int fsIndex, int refIndex, +UString CInArchive::GetItemPath(unsigned volIndex, unsigned fsIndex, unsigned refIndex, bool showVolName, bool showFsName) const { // showVolName = true; @@ -1108,30 +1723,28 @@ UString CInArchive::GetItemPath(int volIndex, int fsIndex, int refIndex, for (;;) { const CRef &ref = fs.Refs[refIndex]; - refIndex = ref.Parent; - if (refIndex < 0) + // we break on root file (that probably has empty name) + if (ref.Parent < 0) break; + refIndex = (unsigned)ref.Parent; UpdateWithName(name, GetSpecName(Files[ref.FileIndex].GetName())); } if (showFsName) { - wchar_t s[32]; - ConvertUInt32ToString(fsIndex, s); - UString newName = L"File Set "; - newName += s; + UString newName ("File Set "); + newName.Add_UInt32(fsIndex); UpdateWithName(name, newName); } if (showVolName) { - wchar_t s[32]; - ConvertUInt32ToString(volIndex, s); - UString newName = s; + UString newName; + newName.Add_UInt32(volIndex); UString newName2 = vol.GetName(); if (newName2.IsEmpty()) - newName2 = L"Volume"; - newName += L'-'; + newName2 = "Volume"; + newName.Add_Minus(); newName += newName2; UpdateWithName(name, newName); } diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Udf/UdfIn.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Udf/UdfIn.h index f7379401..cbe1a273 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Udf/UdfIn.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Udf/UdfIn.h @@ -1,7 +1,7 @@ // Archive/UdfIn.h -- UDF / ECMA-167 -#ifndef __ARCHIVE_UDF_IN_H -#define __ARCHIVE_UDF_IN_H +#ifndef ZIP7_INC_ARCHIVE_UDF_IN_H +#define ZIP7_INC_ARCHIVE_UDF_IN_H #include "../../../Common/IntToString.h" #include "../../../Common/MyBuffer.h" @@ -17,16 +17,15 @@ namespace NUdf { // ---------- ECMA Part 1 ---------- // ECMA 1/7.2.12 +// UDF 2.1.3 -/* struct CDString32 { Byte Data[32]; - void Parse(const Byte *buf); - // UString GetString() const; + void Parse(const Byte *buf) { memcpy(Data, buf, sizeof(Data)); } + UString GetString() const; }; -*/ struct CDString128 { @@ -46,6 +45,7 @@ struct CDString // ECMA 1/7.3 +// UDF 2.1.4 timestamp struct CTime { @@ -65,54 +65,107 @@ struct CTime }; -// ECMA 1/7.4 +// ECMA 1/7.4 regid +// UDF 2.1.5 EntityID -/* struct CRegId { Byte Flags; char Id[23]; - char Suffix[8]; + Byte Suffix[8]; void Parse(const Byte *buf); + void AddCommentTo(UString &s) const; + void AddUdfVersionTo(UString &s) const; }; -*/ + + // ---------- ECMA Part 3: Volume Structure ---------- -// ECMA 3/10.5 +// ECMA 3/7.1 -struct CPartition +struct CExtent +{ + UInt32 Len; + UInt32 Pos; // logical sector number + + void Parse(const Byte *p); +}; + + +// ECMA 3/10.1 +// UDF 2.2.2 PrimaryVolumeDescriptor + +struct CPrimeVol { + // UInt32 VolumeDescriptorSequenceNumber; + UInt32 PrimaryVolumeDescriptorNumber; + CDString32 VolumeId; + UInt16 VolumeSequenceNumber; + UInt16 MaximumVolumeSequenceNumber; + // UInt16 InterchangeLevel; + // UInt16 MaximumInterchangeLevel; + // UInt32 CharacterSetList; + // UInt32 MaximumCharacterSetList; + CDString128 VolumeSetId; + // charspec DescriptorCharacterSet; // (1/7.2.1) + // charspec ExplanatoryCharacterSet; // (1/7.2.1) + // CExtent VolumeAbstract; + // CExtent VolumeCopyrightNotice; + CRegId ApplicationId; + CTime RecordingTime; + CRegId ImplId; + // bytes ImplementationUse + // UInt32 PredecessorVolumeDescriptorSequenceLocation; // UInt16 Flags; - UInt16 Number; - // CRegId ContentsId; - // Byte ContentsUse[128]; - // UInt32 AccessType; + void Parse(const Byte *p); +}; + + +// ECMA 3/10.5 +// UDF 2.2.14 PartitionDescriptor + +struct CPartition +{ UInt32 Pos; UInt32 Len; - // CRegId ImplId; + UInt16 Flags; + UInt16 Number; + CRegId ContentsId; + // Byte ContentsUse[128]; + UInt32 AccessType; + + CRegId ImplId; // Byte ImplUse[128]; - int VolIndex; + // int VolIndex; CMap32 Map; - CPartition(): VolIndex(-1) {} + bool IsMetadata; + + CPartition(): + // VolIndex(-1), + IsMetadata(false) {} // bool IsNsr() const { return (strncmp(ContentsId.Id, "+NSR0", 5) == 0); } // bool IsAllocated() const { return ((Flags & 1) != 0); } }; + +// ECMA 4/7.1 lb_addr + struct CLogBlockAddr { UInt32 Pos; UInt16 PartitionRef; - void Parse(const Byte *buf); + void Parse(const Byte *p); }; + enum EShortAllocDescType { SHORT_ALLOC_DESC_TYPE_RecordedAndAllocated = 0, @@ -121,16 +174,18 @@ enum EShortAllocDescType SHORT_ALLOC_DESC_TYPE_NextExtent = 3 }; + +// ECMA 4/14.14.1 short_ad + struct CShortAllocDesc { UInt32 Len; UInt32 Pos; - // 4/14.14.1 // UInt32 GetLen() const { return Len & 0x3FFFFFFF; } // UInt32 GetType() const { return Len >> 30; } // bool IsRecAndAlloc() const { return GetType() == SHORT_ALLOC_DESC_TYPE_RecordedAndAllocated; } - void Parse(const Byte *buf); + void Parse(const Byte *p); }; /* @@ -138,10 +193,13 @@ struct CADImpUse { UInt16 Flags; UInt32 UdfUniqueId; - void Parse(const Byte *buf); + void Parse(const Byte *p); }; */ +// ECMA 4/14.14.2 long_ad +// UDF 2.3.10.1 + struct CLongAllocDesc { UInt32 Len; @@ -153,29 +211,49 @@ struct CLongAllocDesc UInt32 GetLen() const { return Len & 0x3FFFFFFF; } UInt32 GetType() const { return Len >> 30; } bool IsRecAndAlloc() const { return GetType() == SHORT_ALLOC_DESC_TYPE_RecordedAndAllocated; } - void Parse(const Byte *buf); + void Parse(const Byte *p); }; + +// ECMA 3/10.7 Partition maps +// UDF 2.2.8-2.2.10 Partition Maps + struct CPartitionMap { + unsigned PartitionIndex; + Byte Type; // Byte Len; - // Type - 1 - // UInt16 VolSeqNumber; + // ECMA 10.7.2 + UInt16 VolumeSequenceNumber; UInt16 PartitionNumber; + + CRegId PartitionTypeId; - // Byte Data[256]; + // UDF 2.2.10 Metadata Partition Map + UInt32 MetadataFileLocation; + // UInt32 MetadataMirrorFileLocation; + // UInt32 MetadataBitmapFileLocation; + // UInt32 AllocationUnitSize; // (Blocks) + // UInt16 AlignmentUnitSize; // (Blocks) + // Byte Flags; - int PartitionIndex; + // Byte Data[256]; + // CPartitionMap(): PartitionIndex(-1) {} }; -// ECMA 4/14.6 + +// ECMA 4/14.6.6 enum EIcbFileType { ICB_FILE_TYPE_DIR = 4, - ICB_FILE_TYPE_FILE = 5 + ICB_FILE_TYPE_FILE = 5, + ICB_FILE_TYPE_REAL_TIME_FILE = 249, // 2.3.5.2.1 + ICB_FILE_TYPE_METADATA = 250, // 2.2.13.1 + ICB_FILE_TYPE_METADATA_MIRROR = 251, // 2.2.13.1 + ICB_FILE_TYPE_METADATA_BITMAP = 252 // 2.2.13.2 }; enum EIcbDescriptorType @@ -186,6 +264,9 @@ enum EIcbDescriptorType ICB_DESC_TYPE_INLINE = 3 }; +// ECMA 4/14.6 +// UDF 3.3.2 + struct CIcbTag { // UInt32 PriorDirectNum; @@ -201,33 +282,41 @@ struct CIcbTag void Parse(const Byte *p); }; + +// ECMA 4/14.4.3 +// UDF 2.3.4.2 FileCharacteristics + // const Byte FILEID_CHARACS_Existance = (1 << 0); -const Byte FILEID_CHARACS_Parent = (1 << 3); +const Byte FILEID_CHARACS_Dir = (1 << 1); +const Byte FILEID_CHARACS_Deleted = (1 << 2); +const Byte FILEID_CHARACS_Parent = (1 << 3); +// const Byte FILEID_CHARACS_Metadata = (1 << 4); struct CFile { + int ItemIndex; // UInt16 FileVersion; // Byte FileCharacteristics; // CByteBuffer ImplUse; CDString Id; - int ItemIndex; - CFile(): /* FileVersion(0), FileCharacteristics(0), */ ItemIndex(-1) {} UString GetName() const { return Id.GetString(); } }; + struct CMyExtent { UInt32 Pos; UInt32 Len; - unsigned PartitionRef; + unsigned PartitionRef; // index in CLogVol::PartitionMaps UInt32 GetLen() const { return Len & 0x3FFFFFFF; } UInt32 GetType() const { return Len >> 30; } bool IsRecAndAlloc() const { return GetType() == SHORT_ALLOC_DESC_TYPE_RecordedAndAllocated; } }; + struct CItem { CIcbTag IcbTag; @@ -235,26 +324,30 @@ struct CItem // UInt32 Uid; // UInt32 Gid; // UInt32 Permissions; - // UInt16 FileLinkCount; + UInt16 FileLinkCount; // Byte RecordFormat; // Byte RecordDisplayAttr; // UInt32 RecordLen; UInt64 Size; UInt64 NumLogBlockRecorded; + // UInt64 ObjectSize; + CTime ATime; CTime MTime; - // CTime AttrtTime; + CTime AttribTime; // Attribute time : most recent date and time of the day of file creation or modification of the attributes of. + CTime CreateTime; // UInt32 CheckPoint; // CLongAllocDesc ExtendedAttrIcb; // CRegId ImplId; // UInt64 UniqueId; + bool IsExtended; bool IsInline; CByteBuffer InlineData; CRecordVector Extents; CUIntVector SubFiles; - void Parse(const Byte *buf); + void Parse(const Byte *p); bool IsRecAndAlloc() const { @@ -279,89 +372,80 @@ struct CItem bool IsDir() const { return IcbTag.IsDir(); } }; + struct CRef { - int Parent; unsigned FileIndex; + int Parent; }; // ECMA 4 / 14.1 struct CFileSet { - CTime RecodringTime; + CRecordVector Refs; + + CTime RecordingTime; // UInt16 InterchangeLevel; // UInt16 MaxInterchangeLevel; - // UInt32 FileSetNumber; - // UInt32 FileSetDescNumber; - // CDString32 Id; - // CDString32 CopyrightId; - // CDString32 AbstractId; + UInt32 FileSetNumber; + UInt32 FileSetDescNumber; + CDString128 LogicalVolumeId; + CDString32 Id; + CDString32 CopyrightId; + CDString32 AbstractId; CLongAllocDesc RootDirICB; - // CRegId DomainId; + CRegId DomainId; // CLongAllocDesc SystemStreamDirICB; - - CRecordVector Refs; }; +/* 8.3 Volume descriptors +8.4 +A Volume Descriptor Sequence: + shall contain one or more Primary Volume Descriptors. +*/ + // ECMA 3/10.6 +// UDF 2.2.4 LogicalVolumeDescriptor struct CLogVol { - CDString128 Id; + CObjectVector PartitionMaps; + CObjectVector FileSets; + UInt32 BlockSize; - // CRegId DomainId; + CDString128 Id; + CRegId DomainId; // Byte ContentsUse[16]; CLongAllocDesc FileSetLocation; // UDF - // CRegId ImplId; + CRegId ImplId; // Byte ImplUse[128]; - - CObjectVector PartitionMaps; - CObjectVector FileSets; + // CExtent IntegritySequenceExtent; UString GetName() const { return Id.GetString(); } }; -struct CProgressVirt + +Z7_PURE_INTERFACES_BEGIN +struct Z7_DECLSPEC_NOVTABLE CProgressVirt { - virtual HRESULT SetTotal(UInt64 numBytes) PURE; - virtual HRESULT SetCompleted(UInt64 numFiles, UInt64 numBytes) PURE; - virtual HRESULT SetCompleted() PURE; + virtual HRESULT SetTotal(UInt64 numBytes) =0; \ + virtual HRESULT SetCompleted(UInt64 numFiles, UInt64 numBytes) =0; \ + virtual HRESULT SetCompleted() =0; \ }; +Z7_PURE_INTERFACES_END class CInArchive { - IInStream *_stream; - CProgressVirt *_progress; - - HRESULT Read(int volIndex, int partitionRef, UInt32 blockPos, UInt32 len, Byte *buf); - HRESULT Read(int volIndex, const CLongAllocDesc &lad, Byte *buf); - HRESULT ReadFromFile(int volIndex, const CItem &item, CByteBuffer &buf); - - HRESULT ReadFileItem(int volIndex, int fsIndex, const CLongAllocDesc &lad, int numRecurseAllowed); - HRESULT ReadItem(int volIndex, int fsIndex, const CLongAllocDesc &lad, int numRecurseAllowed); - - HRESULT Open2(); - HRESULT FillRefs(CFileSet &fs, unsigned fileIndex, int parent, int numRecurseAllowed); - - UInt64 _processedProgressBytes; - - UInt64 _fileNameLengthTotal; - int _numRefs; - UInt32 _numExtents; - UInt64 _inlineExtentsSize; - bool CheckExtent(int volIndex, int partitionRef, UInt32 blockPos, UInt32 len) const; - public: - CObjectVector Partitions; CObjectVector LogVols; - CObjectVector Items; CObjectVector Files; + CObjectVector Partitions; unsigned SecLogSize; UInt64 PhySize; @@ -372,19 +456,49 @@ class CInArchive bool UnexpectedEnd; bool NoEndAnchor; - void UpdatePhySize(UInt64 val) - { - if (PhySize < val) - PhySize = val; - } + CObjectVector PrimeVols; + HRESULT Open(IInStream *inStream, CProgressVirt *progress); void Clear(); UString GetComment() const; - UString GetItemPath(int volIndex, int fsIndex, int refIndex, + UString GetItemPath(unsigned volIndex, unsigned fsIndex, unsigned refIndex, bool showVolName, bool showFsName) const; - bool CheckItemExtents(int volIndex, const CItem &item) const; + bool CheckItemExtents(unsigned volIndex, const CItem &item) const; + +private: + IInStream *_stream; + CProgressVirt *_progress; + + HRESULT Read(unsigned volIndex, unsigned partitionRef, UInt32 blockPos, UInt32 len, Byte *buf); + HRESULT ReadLad(unsigned volIndex, const CLongAllocDesc &lad, Byte *buf); + HRESULT ReadFromFile(unsigned volIndex, const CItem &item, CByteBuffer &buf); + + HRESULT ReadFileItem(unsigned volIndex, unsigned fsIndex, const CLongAllocDesc &lad, bool isDir, int numRecurseAllowed); + HRESULT ReadItem(unsigned volIndex, int fsIndex, const CLongAllocDesc &lad, bool isDir, int numRecurseAllowed); + + HRESULT Open2(); + HRESULT FillRefs(CFileSet &fs, unsigned fileIndex, int parent, int numRecurseAllowed); + + UInt64 _processedProgressBytes; + + UInt64 _fileNameLengthTotal; + unsigned _numRefs; + UInt32 _numExtents; + UInt64 _inlineExtentsSize; + bool CheckExtent(unsigned volIndex, unsigned partitionRef, UInt32 blockPos, UInt32 len) const; + + void UpdatePhySize(UInt64 val) + { + if (PhySize < val) + PhySize = val; + } + + void UpdatePhySize(const CExtent &e) + { + UpdatePhySize(((UInt64)e.Pos << SecLogSize) + e.Len); + } }; API_FUNC_IsArc IsArc_Udf(const Byte *p, size_t size); diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/UefiHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/UefiHandler.cpp index ff0737a0..d53fb447 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/UefiHandler.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/UefiHandler.cpp @@ -10,9 +10,10 @@ #include "../../../C/7zCrc.h" #include "../../../C/Alloc.h" -#include "../../../C/CpuArch.h" #include "../../../C/LzmaDec.h" +#include "../../../C/CpuArch.h" +#include "../../Common/AutoPtr.h" #include "../../Common/ComTry.h" #include "../../Common/IntToString.h" #include "../../Common/MyBuffer.h" @@ -42,24 +43,62 @@ namespace NArchive { namespace NUefi { -static const size_t kBufTotalSizeMax = (1 << 29); -static const unsigned kNumFilesMax = (1 << 18); +static const size_t kBufTotalSizeMax = 1 << 29; +static const unsigned kNumFilesMax = 1 << 18; static const unsigned kLevelMax = 64; +static const Byte k_IntelMeSignature[] = +{ + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0x5A, 0xA5, 0xF0, 0x0F +}; + +static bool IsIntelMe(const Byte *p) +{ + return memcmp(p, k_IntelMeSignature, sizeof(k_IntelMeSignature)) == 0; +} + static const unsigned kFvHeaderSize = 0x38; static const unsigned kGuidSize = 16; -#define CAPSULE_SIGNATURE \ - { 0xBD,0x86,0x66,0x3B,0x76,0x0D,0x30,0x40,0xB7,0x0E,0xB5,0x51,0x9E,0x2F,0xC5,0xA0 } -static const Byte kCapsuleSig[kGuidSize] = CAPSULE_SIGNATURE; + +#define CAPSULE_SIGNATURE 0xBD,0x86,0x66,0x3B,0x76,0x0D,0x30,0x40,0xB7,0x0E,0xB5,0x51,0x9E,0x2F,0xC5,0xA0 +#define CAPSULE2_SIGNATURE 0x8B,0xA6,0x3C,0x4A,0x23,0x77,0xFB,0x48,0x80,0x3D,0x57,0x8C,0xC1,0xFE,0xC4,0x4D +#define CAPSULE_UEFI_SIGNATURE 0xB9,0x82,0x91,0x53,0xB5,0xAB,0x91,0x43,0xB6,0x9A,0xE3,0xA9,0x43,0xF7,0x2F,0xCC +/* + 6dcbd5ed-e82d-4c44-bda1-7194199ad92a : Firmware Management ` +*/ + +static const Byte k_Guids_Capsules[][kGuidSize] = +{ + { CAPSULE_SIGNATURE }, + { CAPSULE2_SIGNATURE }, + { CAPSULE_UEFI_SIGNATURE } +}; + static const unsigned kFfsGuidOffset = 16; -#define FFS_SIGNATURE \ - { 0xD9,0x54,0x93,0x7A,0x68,0x04,0x4A,0x44,0x81,0xCE,0x0B,0xF6,0x17,0xD8,0x90,0xDF } -static const Byte k_FFS_Guid[kGuidSize] = FFS_SIGNATURE; -static const Byte k_MacFS_Guid[kGuidSize] = - { 0xAD,0xEE,0xAD,0x04,0xFF,0x61,0x31,0x4D,0xB6,0xBA,0x64,0xF8,0xBF,0x90,0x1F,0x5A }; +#define FFS1_SIGNATURE 0xD9,0x54,0x93,0x7A,0x68,0x04,0x4A,0x44,0x81,0xCE,0x0B,0xF6,0x17,0xD8,0x90,0xDF +#define FFS2_SIGNATURE 0x78,0xE5,0x8C,0x8C,0x3D,0x8A,0x1C,0x4F,0x99,0x35,0x89,0x61,0x85,0xC3,0x2D,0xD3 +#define MACFS_SIGNATURE 0xAD,0xEE,0xAD,0x04,0xFF,0x61,0x31,0x4D,0xB6,0xBA,0x64,0xF8,0xBF,0x90,0x1F,0x5A +// APPLE_BOOT +/* + "FFS3": "5473c07a-3dcb-4dca-bd6f-1e9689e7349a", + "NVRAM_EVSA": "fff12b8d-7696-4c8b-a985-2747075b4f50", + "NVRAM_NVAR": "cef5b9a3-476d-497f-9fdc-e98143e0422c", + "NVRAM_EVSA2": "00504624-8a59-4eeb-bd0f-6b36e96128e0", +static const Byte k_NVRAM_NVAR_Guid[kGuidSize] = + { 0xA3,0xB9,0xF5,0xCE,0x6D,0x47,0x7F,0x49,0x9F,0xDC,0xE9,0x81,0x43,0xE0,0x42,0x2C }; +*/ + +static const Byte k_Guids_FS[][kGuidSize] = +{ + { FFS1_SIGNATURE }, + { FFS2_SIGNATURE }, + { MACFS_SIGNATURE } +}; + static const UInt32 kFvSignature = 0x4856465F; // "_FVH" @@ -80,6 +119,8 @@ static const Byte kGuids[][kGuidSize] = { 0x18,0x88,0x53,0x4A,0xE0,0x5A,0xB2,0x4E,0xB2,0xEB,0x48,0x8B,0x23,0x65,0x70,0x22 } }; +static const Byte k_Guid_LZMA_COMPRESSED[kGuidSize] = + { 0x98,0x58,0x4E,0xEE,0x14,0x39,0x59,0x42,0x9D,0x6E,0xDC,0x7B,0xD7,0x94,0x03,0xCF }; static const char * const kGuidNames[] = { @@ -131,14 +172,14 @@ enum static const char *FindExt(const Byte *p, size_t size) { unsigned i; - for (i = 0; i < ARRAY_SIZE(g_Sigs); i++) + for (i = 0; i < Z7_ARRAY_SIZE(g_Sigs); i++) { const CSigExtPair &pair = g_Sigs[i]; if (size >= pair.sigSize) if (memcmp(p, pair.sig, pair.sigSize) == 0) break; } - if (i == ARRAY_SIZE(g_Sigs)) + if (i == Z7_ARRAY_SIZE(g_Sigs)) return NULL; switch (i) { @@ -172,15 +213,20 @@ static bool AreGuidsEq(const Byte *p1, const Byte *p2) static int FindGuid(const Byte *p) { - for (unsigned i = 0; i < ARRAY_SIZE(kGuids); i++) + for (unsigned i = 0; i < Z7_ARRAY_SIZE(kGuids); i++) if (AreGuidsEq(p, kGuids[i])) - return i; + return (int)i; return -1; } static bool IsFfs(const Byte *p) { - return (Get32(p + 0x28) == kFvSignature && AreGuidsEq(p + kFfsGuidOffset, k_FFS_Guid)); + if (Get32(p + 0x28) != kFvSignature) + return false; + for (unsigned i = 0; i < Z7_ARRAY_SIZE(k_Guids_FS); i++) + if (AreGuidsEq(p + kFfsGuidOffset, k_Guids_FS[i])) + return true; + return false; } #define FVB_ERASE_POLARITY (1 << 11) @@ -273,19 +319,19 @@ static const CUInt32PCharPair g_FFS_FILE_ATTRIBUTES[] = // SECTION_TYPE -#define SECTION_ALL 0x00 +// #define SECTION_ALL 0x00 #define SECTION_COMPRESSION 0x01 #define SECTION_GUID_DEFINED 0x02 // Leaf section Type values -#define SECTION_PE32 0x10 -#define SECTION_PIC 0x11 -#define SECTION_TE 0x12 +// #define SECTION_PE32 0x10 +// #define SECTION_PIC 0x11 +// #define SECTION_TE 0x12 #define SECTION_DXE_DEPEX 0x13 #define SECTION_VERSION 0x14 #define SECTION_USER_INTERFACE 0x15 -#define SECTION_COMPATIBILITY16 0x16 +// #define SECTION_COMPATIBILITY16 0x16 #define SECTION_FIRMWARE_VOLUME_IMAGE 0x17 #define SECTION_FREEFORM_SUBTYPE_GUID 0x18 #define SECTION_RAW 0x19 @@ -329,39 +375,15 @@ static const char * const g_Methods[] = , "LZMA" }; -static AString UInt32ToString(UInt32 val) -{ - char sz[16]; - ConvertUInt32ToString(val, sz); - return sz; -} -static void ConvertByteToHex(unsigned value, char *s) +static void AddGuid(AString &dest, const Byte *p, bool full) { - for (int i = 0; i < 2; i++) - { - unsigned t = value & 0xF; - value >>= 4; - s[1 - i] = (char)((t < 10) ? ('0' + t) : ('A' + (t - 10))); - } -} - -static AString GuidToString(const Byte *p, bool full) -{ - char s[16 * 2 + 8]; - int i; - for (i = 0; i < 4; i++) - ConvertByteToHex(p[3 - i], s + i * 2); - s[8] = 0; - - if (full) - { - s[8] = '-'; - for (i = 4; i < kGuidSize; i++) - ConvertByteToHex(p[i], s + 1 + i * 2); - s[32 + 1] = 0; - } - return s; + char s[64]; + ::RawLeGuidToString(p, s); + // MyStringUpper_Ascii(s); + if (!full) + s[8] = 0; + dest += s; } static const char * const kExpressionCommands[] = @@ -374,8 +396,8 @@ static bool ParseDepedencyExpression(const Byte *p, UInt32 size, AString &res) res.Empty(); for (UInt32 i = 0; i < size;) { - unsigned command = p[i++]; - if (command > ARRAY_SIZE(kExpressionCommands)) + const unsigned command = p[i++]; + if (command >= Z7_ARRAY_SIZE(kExpressionCommands)) return false; res += kExpressionCommands[command]; if (command < 3) @@ -383,7 +405,7 @@ static bool ParseDepedencyExpression(const Byte *p, UInt32 size, AString &res) if (i + kGuidSize > size) return false; res.Add_Space(); - res += GuidToString(p + i, false); + AddGuid(res, p + i, false); i += kGuidSize; } res += "; "; @@ -416,9 +438,9 @@ static bool ParseUtf16zString2(const Byte *p, UInt32 size, AString &res) return true; } -#define FLAGS_TO_STRING(pairs, value) FlagsToString(pairs, ARRAY_SIZE(pairs), value) -#define TYPE_TO_STRING(table, value) TypeToString(table, ARRAY_SIZE(table), value) -#define TYPE_PAIR_TO_STRING(table, value) TypePairToString(table, ARRAY_SIZE(table), value) +#define FLAGS_TO_STRING(pairs, value) FlagsToString(pairs, Z7_ARRAY_SIZE(pairs), value) +#define TYPE_TO_STRING(table, value) TypeToString(table, Z7_ARRAY_SIZE(table), value) +#define TYPE_PAIR_TO_STRING(table, value) TypePairToString(table, Z7_ARRAY_SIZE(table), value) static const UInt32 kFileHeaderSize = 24; @@ -433,6 +455,7 @@ static void AddSpaceAndString(AString &res, const AString &newString) class CFfsFileHeader { +PRF(public:) Byte CheckHeader; Byte CheckFile; Byte Attrib; @@ -449,7 +472,7 @@ class CFfsFileHeader bool Parse(const Byte *p) { - int i; + unsigned i; for (i = 0; i < kFileHeaderSize; i++) if (p[i] != 0xFF) break; @@ -530,14 +553,15 @@ class CFfsFileHeader if (align != 0) { s += " Align:"; - s += UInt32ToString((UInt32)1 << g_Allignment[align]); + s.Add_UInt32((UInt32)1 << g_Allignment[align]); } */ return s; } }; -#define G32(_offs_, dest) dest = Get32(p + (_offs_)); +#define G32(_offs_, dest) dest = Get32(p + (_offs_)) +#define G16(_offs_, dest) dest = Get16(p + (_offs_)) struct CCapsuleHeader { @@ -557,23 +581,51 @@ struct CCapsuleHeader void Clear() { memset(this, 0, sizeof(*this)); } - void Parse(const Byte *p) + bool Parse(const Byte *p) { + Clear(); G32(0x10, HeaderSize); G32(0x14, Flags); G32(0x18, CapsuleImageSize); - G32(0x1C, SequenceNumber); - G32(0x30, OffsetToSplitInformation); - G32(0x34, OffsetToCapsuleBody); - G32(0x38, OffsetToOemDefinedHeader); - G32(0x3C, OffsetToAuthorInformation); - G32(0x40, OffsetToRevisionInformation); - G32(0x44, OffsetToShortDescription); - G32(0x48, OffsetToLongDescription); - G32(0x4C, OffsetToApplicableDevices); + if (HeaderSize < 0x1C) + return false; + if (AreGuidsEq(p, k_Guids_Capsules[0])) + { + const unsigned kHeaderSize = 80; + if (HeaderSize != kHeaderSize) + return false; + G32(0x1C, SequenceNumber); + G32(0x30, OffsetToSplitInformation); + G32(0x34, OffsetToCapsuleBody); + G32(0x38, OffsetToOemDefinedHeader); + G32(0x3C, OffsetToAuthorInformation); + G32(0x40, OffsetToRevisionInformation); + G32(0x44, OffsetToShortDescription); + G32(0x48, OffsetToLongDescription); + G32(0x4C, OffsetToApplicableDevices); + return true; + } + else if (AreGuidsEq(p, k_Guids_Capsules[1])) + { + // capsule v2 + G16(0x1C, OffsetToCapsuleBody); + G16(0x1E, OffsetToOemDefinedHeader); + return true; + } + else if (AreGuidsEq(p, k_Guids_Capsules[2])) + { + OffsetToCapsuleBody = HeaderSize; + return true; + } + else + { + // here we must check for another capsule types + return false; + } } }; + struct CItem { AString Name; @@ -581,14 +633,14 @@ struct CItem int Parent; int Method; int NameIndex; - int NumChilds; + unsigned NumChilds; bool IsDir; bool Skip; bool ThereAreSubDirs; bool ThereIsUniqueName; bool KeepName; - int BufIndex; + unsigned BufIndex; UInt32 Offset; UInt32 Size; @@ -605,7 +657,10 @@ void CItem::SetGuid(const Byte *guidName, bool full) if (index >= 0) Name = kGuidNames[(unsigned)index]; else - Name = GuidToString(guidName, full); + { + Name.Empty(); + AddGuid(Name, guidName, full); + } } AString CItem::GetName(int numChildsInParent) const @@ -614,66 +669,79 @@ AString CItem::GetName(int numChildsInParent) const return Name; char sz[32]; char sz2[32]; - ConvertUInt32ToString(NameIndex, sz); - ConvertUInt32ToString(numChildsInParent - 1, sz2); - unsigned numZeros = (unsigned)strlen(sz2) - (unsigned)strlen(sz); + ConvertUInt32ToString((unsigned)NameIndex, sz); + ConvertUInt32ToString((unsigned)numChildsInParent - 1, sz2); + const int numZeros = (int)strlen(sz2) - (int)strlen(sz); AString res; - for (unsigned i = 0; i < numZeros; i++) - res += '0'; - return res + (AString)sz + '.' + Name; + for (int i = 0; i < numZeros; i++) + res.Add_Char('0'); + res += sz; + res.Add_Dot(); + res += Name; + return res; } struct CItem2 { AString Name; AString Characts; - int MainIndex; + unsigned MainIndex; int Parent; CItem2(): Parent(-1) {} }; -class CHandler: - public IInArchive, - public IInArchiveGetStream, - public CMyUnknownImp -{ + +Z7_CLASS_IMP_CHandler_IInArchive_1( + IInArchiveGetStream +) CObjectVector _items; CObjectVector _items2; CObjectVector _bufs; UString _comment; UInt32 _methodsMask; bool _capsuleMode; + bool _headersError; size_t _totalBufsSize; CCapsuleHeader _h; UInt64 _phySize; - void AddCommentString(const wchar_t *name, UInt32 pos); - int AddItem(const CItem &item); - int AddFileItemWithIndex(CItem &item); - int AddDirItem(CItem &item); + void AddCommentString(const char *name, UInt32 pos); + unsigned AddItem(const CItem &item); + unsigned AddFileItemWithIndex(CItem &item); + unsigned AddDirItem(CItem &item); unsigned AddBuf(size_t size); - HRESULT ParseSections(int bufIndex, UInt32 pos, UInt32 size, int parent, int method, unsigned level); - HRESULT ParseVolume(int bufIndex, UInt32 posBase, + HRESULT DecodeLzma(const Byte *data, size_t inputSize); + + HRESULT ParseSections(unsigned bufIndex, UInt32 pos, + UInt32 size, + int parent, int method, unsigned level, + bool &error); + + HRESULT ParseIntelMe(unsigned bufIndex, UInt32 posBase, + UInt32 exactSize, UInt32 limitSize, + int parent, int method, unsigned level); + + HRESULT ParseVolume(unsigned bufIndex, UInt32 posBase, UInt32 exactSize, UInt32 limitSize, - int parent, int method, int level); + int parent, int method, unsigned level); + HRESULT OpenCapsule(IInStream *stream); HRESULT OpenFv(IInStream *stream, const UInt64 *maxCheckStartPosition, IArchiveOpenCallback *callback); HRESULT Open2(IInStream *stream, const UInt64 *maxCheckStartPosition, IArchiveOpenCallback *callback); public: CHandler(bool capsuleMode): _capsuleMode(capsuleMode) {} - MY_UNKNOWN_IMP2(IInArchive, IInArchiveGetStream) - INTERFACE_IInArchive(;) - STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream); }; + static const Byte kProps[] = { kpidPath, kpidIsDir, kpidSize, + // kpidOffset, kpidMethod, kpidCharacts }; @@ -688,7 +756,7 @@ static const Byte kArcProps[] = IMP_IInArchive_Props IMP_IInArchive_ArcProps -STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NWindows::NCOM::CPropVariant prop; @@ -698,7 +766,7 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val { case kpidPath: { - AString path = item2.Name; + AString path (item2.Name); int cur = item2.Parent; while (cur >= 0) { @@ -714,29 +782,34 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val case kpidMethod: if (item.Method >= 0) prop = g_Methods[(unsigned)item.Method]; break; case kpidCharacts: if (!item2.Characts.IsEmpty()) prop = item2.Characts; break; case kpidSize: if (!item.IsDir) prop = (UInt64)item.Size; break; + // case kpidOffset: if (!item.IsDir) prop = item.Offset; break; } prop.Detach(value); return S_OK; COM_TRY_END } -void CHandler::AddCommentString(const wchar_t *name, UInt32 pos) +void CHandler::AddCommentString(const char *name, UInt32 pos) { UString s; - const Byte *buf = _bufs[0]; if (pos < _h.HeaderSize) return; - for (UInt32 i = pos;; i += 2) + if (pos >= _h.OffsetToCapsuleBody) + return; + UInt32 limit = (_h.OffsetToCapsuleBody - pos) & ~(UInt32)1; + const Byte *buf = _bufs[0] + pos; + for (UInt32 i = 0;;) { - if (s.Len() > (1 << 16) || i >= _h.OffsetToCapsuleBody) + if (s.Len() > (1 << 16) || i >= limit) return; wchar_t c = Get16(buf + i); + i += 2; if (c == 0) { - i += 2; - if (i >= _h.OffsetToCapsuleBody) + if (i >= limit) return; c = Get16(buf + i); + i += 2; if (c == 0) break; s.Add_LF(); @@ -747,11 +820,11 @@ void CHandler::AddCommentString(const wchar_t *name, UInt32 pos) return; _comment.Add_LF(); _comment += name; - _comment.AddAscii(": "); + _comment += ": "; _comment += s; } -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NWindows::NCOM::CPropVariant prop; @@ -762,13 +835,22 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) AString s; for (unsigned i = 0; i < 32; i++) if ((_methodsMask & ((UInt32)1 << i)) != 0) - AddSpaceAndString(s, g_Methods[i]); + AddSpaceAndString(s, (AString)g_Methods[i]); if (!s.IsEmpty()) prop = s; break; } case kpidComment: if (!_comment.IsEmpty()) prop = _comment; break; case kpidPhySize: prop = (UInt64)_phySize; break; + + case kpidErrorFlags: + { + UInt32 v = 0; + if (_headersError) v |= kpv_ErrorFlags_HeadersError; + if (v != 0) + prop = v; + break; + } } prop.Detach(value); return S_OK; @@ -776,16 +858,16 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) } #ifdef SHOW_DEBUG_INFO -static void PrintLevel(int level) +static void PrintLevel(unsigned level) { PRF(printf("\n")); - for (int i = 0; i < level; i++) + for (unsigned i = 0; i < level; i++) PRF(printf(" ")); } -static void MyPrint(UInt32 posBase, UInt32 size, int level, const char *name) +static void MyPrint(UInt32 posBase, UInt32 size, unsigned level, const char *name) { PrintLevel(level); - PRF(printf("%s, pos = %6x, size = %6d", name, posBase, size)); + PRF(printf("%s, pos = %6x, size = %6x", name, posBase, size)); } #else #define PrintLevel(level) @@ -794,23 +876,23 @@ static void MyPrint(UInt32 posBase, UInt32 size, int level, const char *name) -int CHandler::AddItem(const CItem &item) +unsigned CHandler::AddItem(const CItem &item) { if (_items.Size() >= kNumFilesMax) throw 2; return _items.Add(item); } -int CHandler::AddFileItemWithIndex(CItem &item) +unsigned CHandler::AddFileItemWithIndex(CItem &item) { - int nameIndex = _items.Size(); + unsigned nameIndex = _items.Size(); if (item.Parent >= 0) nameIndex = _items[item.Parent].NumChilds++; - item.NameIndex = nameIndex; + item.NameIndex = (int)nameIndex; return AddItem(item); } -int CHandler::AddDirItem(CItem &item) +unsigned CHandler::AddDirItem(CItem &item) { if (item.Parent >= 0) _items[item.Parent].ThereAreSubDirs = true; @@ -829,8 +911,36 @@ unsigned CHandler::AddBuf(size_t size) return index; } -HRESULT CHandler::ParseSections(int bufIndex, UInt32 posBase, UInt32 size, int parent, int method, unsigned level) + +HRESULT CHandler::DecodeLzma(const Byte *data, size_t inputSize) { + if (inputSize < 5 + 8) + return S_FALSE; + const UInt64 unpackSize = Get64(data + 5); + if (unpackSize > ((UInt32)1 << 30)) + return S_FALSE; + SizeT destLen = (SizeT)unpackSize; + const unsigned newBufIndex = AddBuf((size_t)unpackSize); + CByteBuffer &buf = _bufs[newBufIndex]; + ELzmaStatus status; + SizeT srcLen = inputSize - (5 + 8); + const SizeT srcLen2 = srcLen; + SRes res = LzmaDecode(buf, &destLen, data + 13, &srcLen, + data, 5, LZMA_FINISH_END, &status, &g_Alloc); + if (res != 0) + return S_FALSE; + if (srcLen != srcLen2 || destLen != unpackSize || ( + status != LZMA_STATUS_FINISHED_WITH_MARK && + status != LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK)) + return S_FALSE; + return S_OK; +} + + +HRESULT CHandler::ParseSections(unsigned bufIndex, UInt32 posBase, UInt32 size, int parent, int method, unsigned level, bool &error) +{ + error = false; + if (level > kLevelMax) return S_FALSE; MyPrint(posBase, size, level, "Sections"); @@ -842,29 +952,38 @@ HRESULT CHandler::ParseSections(int bufIndex, UInt32 posBase, UInt32 size, int p if (size == pos) return S_OK; PrintLevel(level); - PRF(printf("%s, pos = %6x", "Sect", pos)); + PRF(printf("%s, abs = %6x, relat = %6x", "Sect", posBase + pos, pos)); pos = (pos + 3) & ~(UInt32)3; if (pos > size) return S_FALSE; - UInt32 rem = size - pos; + const UInt32 rem = size - pos; if (rem == 0) return S_OK; if (rem < 4) return S_FALSE; + const Byte *p = bufData + posBase + pos; - UInt32 sectSize = Get24(p); - if (sectSize > rem || sectSize < 4) - return S_FALSE; - Byte type = p[3]; - PrintLevel(level); - PRF(printf("%s, type = %2x, pos = %6x, size = %6d", "Sect", type, pos, sectSize)); + const UInt32 sectSize = Get24(p); + const Byte type = p[3]; + + // PrintLevel(level); + PRF(printf(" type = %2x, sectSize = %6x", type, sectSize)); + + if (sectSize > rem || sectSize < 4) + { + _headersError = true; + error = true; + return S_OK; + // return S_FALSE; + } + CItem item; item.Method = method; item.BufIndex = bufIndex; item.Parent = parent; item.Offset = posBase + pos + 4; - UInt32 sectDataSize = sectSize - 4; + const UInt32 sectDataSize = sectSize - 4; item.Size = sectDataSize; item.Name = TYPE_PAIR_TO_STRING(g_SECTION_TYPE, type); @@ -872,8 +991,8 @@ HRESULT CHandler::ParseSections(int bufIndex, UInt32 posBase, UInt32 size, int p { if (sectSize < 4 + 5) return S_FALSE; - UInt32 uncompressedSize = Get32(p + 4); - Byte compressionType = p[8]; + const UInt32 uncompressedSize = Get32(p + 4); + const Byte compressionType = p[8]; UInt32 newSectSize = sectSize - 9; UInt32 newOffset = posBase + pos + 9; @@ -891,25 +1010,24 @@ HRESULT CHandler::ParseSections(int bufIndex, UInt32 posBase, UInt32 size, int p // int parent = AddDirItem(item); if (compressionType == COMPRESSION_TYPE_NONE) { - RINOK(ParseSections(bufIndex, newOffset, newSectSize, parent, method, level)); + bool error2; + RINOK(ParseSections(bufIndex, newOffset, newSectSize, parent, method, level, error2)) } else if (compressionType == COMPRESSION_TYPE_LZH) { - unsigned newBufIndex = AddBuf(uncompressedSize); + const unsigned newBufIndex = AddBuf(uncompressedSize); CByteBuffer &buf = _bufs[newBufIndex]; - - NCompress::NLzh::NDecoder::CCoder *lzhDecoderSpec = 0; - CMyComPtr lzhDecoder; - - lzhDecoderSpec = new NCompress::NLzh::NDecoder::CCoder; - lzhDecoder = lzhDecoderSpec; - + CMyUniquePtr lzhDecoder; + lzhDecoder.Create_if_Empty(); { const Byte *src = pStart; if (newSectSize < 8) return S_FALSE; UInt32 packSize = Get32(src); - UInt32 unpackSize = Get32(src + 4); + const UInt32 unpackSize = Get32(src + 4); + + PRF(printf(" LZH packSize = %6x, unpackSize = %6x", packSize, unpackSize)); + if (uncompressedSize != unpackSize || newSectSize - 8 != packSize) return S_FALSE; if (packSize < 1) @@ -919,32 +1037,30 @@ HRESULT CHandler::ParseSections(int bufIndex, UInt32 posBase, UInt32 size, int p if (src[packSize] != 0) return S_FALSE; - CBufInStream *inStreamSpec = new CBufInStream; - CMyComPtr inStream = inStreamSpec; - inStreamSpec->Init(src, packSize); - - CBufPtrSeqOutStream *outStreamSpec = new CBufPtrSeqOutStream; - CMyComPtr outStream = outStreamSpec; - outStreamSpec->Init(buf, uncompressedSize); - - UInt64 uncompressedSize64 = uncompressedSize; - lzhDecoderSpec->FinishMode = true; + CMyComPtr2_Create inStream; + CMyComPtr2_Create outStream; + // const UInt64 uncompressedSize64 = uncompressedSize; + // lzhDecoder->FinishMode = true; /* - EFI 1.1 probably used small dictionary and (pbit = 4) in LZH. We don't support such archives. + EFI 1.1 probably used LZH with small dictionary and (pbit = 4). It was named "Efi compression". New version of compression code (named Tiano) uses LZH with (1 << 19) dictionary. But maybe LZH decoder in UEFI decoder supports larger than (1 << 19) dictionary. + We check both LZH versions: Tiano and then Efi. */ - lzhDecoderSpec->SetDictSize(1 << 19); - - HRESULT res = lzhDecoder->Code(inStream, outStream, NULL, &uncompressedSize64, NULL); - if (res != S_OK) - return res; - - if (lzhDecoderSpec->GetInputProcessedSize() != packSize) - return S_FALSE; + HRESULT res = S_FALSE; + for (unsigned m = 0 ; m < 2; m++) + { + inStream->Init(src, packSize); + outStream->Init(buf, uncompressedSize); + lzhDecoder->SetDictSize(m == 0 ? ((UInt32)1 << 19) : ((UInt32)1 << 14)); + res = lzhDecoder->Code(inStream, outStream, uncompressedSize, NULL); + if (res == S_OK) + break; + } + RINOK(res) } - - RINOK(ParseSections(newBufIndex, 0, uncompressedSize, parent, compressionType, level)); + bool error2; + RINOK(ParseSections(newBufIndex, 0, uncompressedSize, parent, compressionType, level, error2)) } else { @@ -964,26 +1080,14 @@ HRESULT CHandler::ParseSections(int bufIndex, UInt32 posBase, UInt32 size, int p // firstSectType can be 0 in some archives } pStart += addSize; - UInt64 lzmaUncompressedSize = Get64(pStart + 5); - if (lzmaUncompressedSize > (1 << 30)) - return S_FALSE; + + RINOK(DecodeLzma(pStart, newSectSize - addSize)) + const size_t lzmaUncompressedSize = _bufs.Back().Size(); + // if (lzmaUncompressedSize != uncompressedSize) if (lzmaUncompressedSize < uncompressedSize) return S_FALSE; - SizeT destLen = (SizeT)lzmaUncompressedSize; - unsigned newBufIndex = AddBuf((size_t)lzmaUncompressedSize); - CByteBuffer &buf = _bufs[newBufIndex]; - ELzmaStatus status; - SizeT srcLen = newSectSize - (addSize + 5 + 8); - SizeT srcLen2 = srcLen; - SRes res = LzmaDecode(buf, &destLen, pStart + 13, &srcLen, - pStart, 5, LZMA_FINISH_END, &status, &g_Alloc); - if (res != 0) - return S_FALSE; - if (srcLen != srcLen2 || destLen != lzmaUncompressedSize || ( - status != LZMA_STATUS_FINISHED_WITH_MARK && - status != LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK)) - return S_FALSE; - RINOK(ParseSections(newBufIndex, 0, (UInt32)lzmaUncompressedSize, parent, compressionType, level)); + bool error2; + RINOK(ParseSections(_bufs.Size() - 1, 0, (UInt32)lzmaUncompressedSize, parent, compressionType, level, error2)) } _methodsMask |= (1 << compressionType); } @@ -994,18 +1098,35 @@ HRESULT CHandler::ParseSections(int bufIndex, UInt32 posBase, UInt32 size, int p if (sectSize < kHeaderSize) return S_FALSE; item.SetGuid(p + 4); - UInt32 dataOffset = Get16(p + 4 + kGuidSize); - UInt32 attrib = Get16(p + 4 + kGuidSize + 2); + const UInt32 dataOffset = Get16(p + 4 + kGuidSize); + const UInt32 attrib = Get16(p + 4 + kGuidSize + 2); if (dataOffset > sectSize || dataOffset < kHeaderSize) return S_FALSE; UInt32 newSectSize = sectSize - dataOffset; item.Size = newSectSize; UInt32 newOffset = posBase + pos + dataOffset; item.Offset = newOffset; - UInt32 propsSize = dataOffset - kHeaderSize; - bool needDir = true; + const UInt32 propsSize = dataOffset - kHeaderSize; AddSpaceAndString(item.Characts, FLAGS_TO_STRING(g_GUIDED_SECTION_ATTRIBUTES, attrib)); - if (AreGuidsEq(p + 0x4, kGuids[kGuidIndex_CRC]) && propsSize == 4) + + bool needDir = true; + unsigned newBufIndex = bufIndex; + int newMethod = method; + + if (AreGuidsEq(p + 0x4, k_Guid_LZMA_COMPRESSED)) + { + // item.Name = "guid.lzma"; + // AddItem(item); + const Byte *pStart = bufData + newOffset; + // do we need correct pStart here for lzma steram offset? + RINOK(DecodeLzma(pStart, newSectSize)) + _methodsMask |= (1 << COMPRESSION_TYPE_LZMA); + newBufIndex = _bufs.Size() - 1; + newOffset = 0; + newSectSize = (UInt32)_bufs.Back().Size(); + newMethod = COMPRESSION_TYPE_LZMA; + } + else if (AreGuidsEq(p + 0x4, kGuids[kGuidIndex_CRC]) && propsSize == 4) { needDir = false; item.KeepName = false; @@ -1023,19 +1144,21 @@ HRESULT CHandler::ParseSections(int bufIndex, UInt32 posBase, UInt32 size, int p AddItem(item2); } } + int newParent = parent; if (needDir) - newParent = AddDirItem(item); - RINOK(ParseSections(bufIndex, newOffset, newSectSize, newParent, method, level)); + newParent = (int)AddDirItem(item); + bool error2; + RINOK(ParseSections(newBufIndex, newOffset, newSectSize, newParent, newMethod, level, error2)) } else if (type == SECTION_FIRMWARE_VOLUME_IMAGE) { item.KeepName = false; - int newParent = AddDirItem(item); + const int newParent = (int)AddDirItem(item); RINOK(ParseVolume(bufIndex, posBase + pos + 4, sectSize - 4, sectSize - 4, - newParent, method, level)); + newParent, method, level)) } else { @@ -1052,11 +1175,11 @@ HRESULT CHandler::ParseSections(int bufIndex, UInt32 posBase, UInt32 size, int p { needAdd = false; item.Name = "vol"; - int newParent = AddDirItem(item); + const unsigned newParent = AddDirItem(item); RINOK(ParseVolume(bufIndex, posBase + pos + 4 + kInsydeOffset, sectDataSize - kInsydeOffset, sectDataSize - kInsydeOffset, - newParent, method, level)); + (int)newParent, method, level)) } if (needAdd) @@ -1077,7 +1200,7 @@ HRESULT CHandler::ParseSections(int bufIndex, UInt32 posBase, UInt32 size, int p if (s.Len() < (1 << 9)) { s.InsertAtFront('['); - s += ']'; + s.Add_Char(']'); AddSpaceAndString(_items[item.Parent].Characts, s); needAdd = false; } @@ -1100,8 +1223,8 @@ HRESULT CHandler::ParseSections(int bufIndex, UInt32 posBase, UInt32 size, int p AString s; if (ParseUtf16zString2(p + 6, sectDataSize - 2, s)) { - AString s2 = "ver:"; - s2 += UInt32ToString(Get16(p + 4)); + AString s2 ("ver:"); + s2.Add_UInt32(Get16(p + 4)); s2.Add_Space(); s2 += s; AddSpaceAndString(_items[item.Parent].Characts, s2); @@ -1135,6 +1258,7 @@ HRESULT CHandler::ParseSections(int bufIndex, UInt32 posBase, UInt32 size, int p if (needAdd) AddFileItemWithIndex(item); } + pos += sectSize; } } @@ -1172,23 +1296,23 @@ bool CVolFfsHeader::Parse(const Byte *p) if (HeaderLen < kFvHeaderSize || (HeaderLen & 0x7) != 0 || VolSize < HeaderLen) return false; return true; -}; +} + HRESULT CHandler::ParseVolume( - int bufIndex, UInt32 posBase, + unsigned bufIndex, UInt32 posBase, UInt32 exactSize, UInt32 limitSize, - int parent, int method, int level) + int parent, int method, unsigned level) { if (level > kLevelMax) return S_FALSE; - MyPrint(posBase, size, level, "Volume"); + MyPrint(posBase, exactSize, level, "Volume"); level++; if (exactSize < kFvHeaderSize) return S_FALSE; const Byte *p = _bufs[bufIndex] + posBase; // first 16 bytes must be zeros, but they are not zeros sometimes. - if (!AreGuidsEq(p + kFfsGuidOffset, k_FFS_Guid) && - !AreGuidsEq(p + kFfsGuidOffset, k_MacFS_Guid)) + if (!IsFfs(p)) { CItem item; item.Method = method; @@ -1196,8 +1320,10 @@ HRESULT CHandler::ParseVolume( item.Parent = parent; item.Offset = posBase; item.Size = exactSize; - item.SetGuid(p + kFfsGuidOffset); - item.Name += " [VOLUME]"; + if (!Is_FF_Stream(p + kFfsGuidOffset, 16)) + item.SetGuid(p + kFfsGuidOffset); + // if (item.Name.IsEmpty()) + item.Name += "[VOL]"; AddItem(item); return S_OK; } @@ -1246,7 +1372,7 @@ HRESULT CHandler::ParseVolume( UInt32 rem = (UInt32)ffsHeader.VolSize - pos; if (rem < kFileHeaderSize) break; - pos = (pos + 7) & ~7; + pos = (pos + 7) & ~7u; rem = (UInt32)ffsHeader.VolSize - pos; if (rem < kFileHeaderSize) break; @@ -1268,9 +1394,13 @@ HRESULT CHandler::ParseVolume( item.Size = rem - num_FF_bytes; AddItem(item); } + PrintLevel(level); PRF(printf("== FF FF reminder")); + break; } - PrintLevel(level); PRF(printf("%s, pos = %6x, size = %6d", "FILE", posBase + pos, fh.Size)); + + PrintLevel(level); PRF(printf("%s, type = %3d, pos = %7x, size = %7x", "FILE", fh.Type, posBase + pos, fh.Size)); + if (!fh.Check(pFile, rem)) return S_FALSE; @@ -1295,9 +1425,16 @@ HRESULT CHandler::ParseVolume( item.SetGuid(fh.GuidName, full); item.Characts = fh.GetCharacts(); - PrintLevel(level); - PRF(printf("%s", item.Characts)); - + // PrintLevel(level); + PRF(printf(" : %s", item.Characts.Ptr())); + + { + PRF(printf(" attrib = %2u State = %3u ", (unsigned)fh.Attrib, (unsigned)fh.State)); + PRF(char s[64]); + PRF(RawLeGuidToString(fh.GuidName, s)); + PRF(printf(" : %s ", s)); + } + if (fh.Type == FV_FILETYPE_FFS_PAD || fh.Type == FV_FILETYPE_RAW) { @@ -1310,34 +1447,141 @@ HRESULT CHandler::ParseVolume( } if (isVolume) { - int newParent = AddDirItem(item); - UInt32 limSize = fh.GetDataSize2(rem); + const unsigned newParent = AddDirItem(item); + const UInt32 limSize = fh.GetDataSize2(rem); // volume.VolSize > fh.Size for some UEFI archives (is it correct UEFI?) // so we will check VolSize for limitSize instead. - RINOK(ParseVolume(bufIndex, offset, sectSize, limSize, newParent, method, level)); + RINOK(ParseVolume(bufIndex, offset, sectSize, limSize, (int)newParent, method, level)) } else AddItem(item); } else { - int newParent = AddDirItem(item); - RINOK(ParseSections(bufIndex, offset, sectSize, newParent, method, level)); + /* + if (fh.Type == FV_FILETYPE_FREEFORM) + { + // in intel bio example: one FV_FILETYPE_FREEFORM file is wav file (not sections) + // AddItem(item); + } + else + */ + { + const unsigned newParent = AddDirItem(item); + bool error2; + RINOK(ParseSections(bufIndex, offset, sectSize, (int)newParent, method, level + 1, error2)) + if (error2) + { + // in intel bio example: one FV_FILETYPE_FREEFORM file is wav file (not sections) + item.IsDir = false; + item.Size = sectSize; + item.Name.Insert(0, "[ERROR]"); + AddItem(item); + } + } } } + + return S_OK; +} + + +static const char * const kRegionName[] = +{ + "Descriptor" + , "BIOS" + , "ME" + , "GbE" + , "PDR" + , "Region5" + , "Region6" + , "Region7" +}; + + +HRESULT CHandler::ParseIntelMe( + unsigned bufIndex, UInt32 posBase, + UInt32 exactSize, UInt32 limitSize, + int parent, int method, unsigned /* level */) +{ + UNUSED_VAR(limitSize) + // level++; + + const Byte *p = _bufs[bufIndex] + posBase; + if (exactSize < 16 + 16) + return S_FALSE; + if (!IsIntelMe(p)) + return S_FALSE; + + UInt32 v0 = GetUi32(p + 20); + // UInt32 numRegions = (v0 >> 24) & 0x7; + UInt32 regAddr = (v0 >> 12) & 0xFF0; + // UInt32 numComps = (v0 >> 8) & 0x3; + // UInt32 fcba = (v0 << 4) & 0xFF0; + + // (numRegions == 0) in header in some new images. + // So we don't use the value from header + UInt32 numRegions = 7; + + for (unsigned i = 0; i <= numRegions; i++) + { + UInt32 offset = regAddr + i * 4; + if (offset + 4 > exactSize) + break; + UInt32 val = GetUi32(p + offset); + + // only 12 bits probably are OK. + // How does it work for files larger than 16 MB? + const UInt32 kMask = 0xFFF; + // const UInt32 kMask = 0xFFFF; // 16-bit is more logical + const UInt32 lim = (val >> 16) & kMask; + const UInt32 base = (val & kMask); + + /* + strange combinations: + PDR: base = 0x1FFF lim = 0; + empty: base = 0xFFFF lim = 0xFFFF; + + PDR: base = 0x7FFF lim = 0; + empty: base = 0x7FFF lim = 0; + */ + if (base == kMask && lim == 0) + continue; // unused + + if (lim < base) + continue; // unused + + CItem item; + item.Name = kRegionName[i]; + item.Method = method; + item.BufIndex = bufIndex; + item.Parent = parent; + item.Offset = posBase + (base << 12); + if (item.Offset > exactSize) + continue; + item.Size = (lim + 1 - base) << 12; + // item.SetGuid(p + kFfsGuidOffset); + // item.Name += " [VOLUME]"; + AddItem(item); + } return S_OK; } + HRESULT CHandler::OpenCapsule(IInStream *stream) { const unsigned kHeaderSize = 80; Byte buf[kHeaderSize]; - RINOK(ReadStream_FALSE(stream, buf, kHeaderSize)); - _h.Parse(buf); - if (_h.HeaderSize != kHeaderSize || - _h.CapsuleImageSize < kHeaderSize || - _h.OffsetToCapsuleBody < kHeaderSize || - _h.OffsetToCapsuleBody > _h.CapsuleImageSize) + RINOK(ReadStream_FALSE(stream, buf, kHeaderSize)) + if (!_h.Parse(buf)) + return S_FALSE; + if (_h.CapsuleImageSize < kHeaderSize + || _h.CapsuleImageSize < _h.HeaderSize + || _h.OffsetToCapsuleBody < _h.HeaderSize + || _h.OffsetToCapsuleBody > _h.CapsuleImageSize + || _h.CapsuleImageSize > (1u << 30) // to reduce false detection + || _h.HeaderSize > (1u << 28) // to reduce false detection + ) return S_FALSE; _phySize = _h.CapsuleImageSize; @@ -1345,26 +1589,30 @@ HRESULT CHandler::OpenCapsule(IInStream *stream) _h.OffsetToSplitInformation != 0 ) return E_NOTIMPL; - unsigned bufIndex = AddBuf(_h.CapsuleImageSize); + const unsigned bufIndex = AddBuf(_h.CapsuleImageSize); CByteBuffer &buf0 = _bufs[bufIndex]; memcpy(buf0, buf, kHeaderSize); - ReadStream_FALSE(stream, buf0 + kHeaderSize, _h.CapsuleImageSize - kHeaderSize); + RINOK(ReadStream_FALSE(stream, buf0 + kHeaderSize, _h.CapsuleImageSize - kHeaderSize)) + + AddCommentString("Author", _h.OffsetToAuthorInformation); + AddCommentString("Revision", _h.OffsetToRevisionInformation); + AddCommentString("Short Description", _h.OffsetToShortDescription); + AddCommentString("Long Description", _h.OffsetToLongDescription); - AddCommentString(L"Author", _h.OffsetToAuthorInformation); - AddCommentString(L"Revision", _h.OffsetToRevisionInformation); - AddCommentString(L"Short Description", _h.OffsetToShortDescription); - AddCommentString(L"Long Description", _h.OffsetToLongDescription); - return ParseVolume(bufIndex, _h.OffsetToCapsuleBody, - _h.CapsuleImageSize - _h.OffsetToCapsuleBody, - _h.CapsuleImageSize - _h.OffsetToCapsuleBody, - -1, -1, 0); + const UInt32 size = _h.CapsuleImageSize - _h.OffsetToCapsuleBody; + + if (size >= 32 && IsIntelMe(buf0 + _h.OffsetToCapsuleBody)) + return ParseIntelMe(bufIndex, _h.OffsetToCapsuleBody, size, size, -1, -1, 0); + + return ParseVolume(bufIndex, _h.OffsetToCapsuleBody, size, size, -1, -1, 0); } + HRESULT CHandler::OpenFv(IInStream *stream, const UInt64 * /* maxCheckStartPosition */, IArchiveOpenCallback * /* callback */) { Byte buf[kFvHeaderSize]; - RINOK(ReadStream_FALSE(stream, buf, kFvHeaderSize)); + RINOK(ReadStream_FALSE(stream, buf, kFvHeaderSize)) if (!IsFfs(buf)) return S_FALSE; CVolFfsHeader ffsHeader; @@ -1373,25 +1621,26 @@ HRESULT CHandler::OpenFv(IInStream *stream, const UInt64 * /* maxCheckStartPosit if (ffsHeader.VolSize > ((UInt32)1 << 30)) return S_FALSE; _phySize = ffsHeader.VolSize; - RINOK(stream->Seek(0, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekToBegin(stream)) UInt32 fvSize32 = (UInt32)ffsHeader.VolSize; unsigned bufIndex = AddBuf(fvSize32); - RINOK(ReadStream_FALSE(stream, _bufs[bufIndex], fvSize32)); + RINOK(ReadStream_FALSE(stream, _bufs[bufIndex], fvSize32)) return ParseVolume(bufIndex, 0, fvSize32, fvSize32, -1, -1, 0); } + HRESULT CHandler::Open2(IInStream *stream, const UInt64 *maxCheckStartPosition, IArchiveOpenCallback *callback) { if (_capsuleMode) { - RINOK(OpenCapsule(stream)); + RINOK(OpenCapsule(stream)) } else { - RINOK(OpenFv(stream, maxCheckStartPosition, callback)); + RINOK(OpenFv(stream, maxCheckStartPosition, callback)) } - unsigned num = _items.Size(); + const unsigned num = _items.Size(); CIntArr numChilds(num); unsigned i; @@ -1401,7 +1650,7 @@ HRESULT CHandler::Open2(IInStream *stream, const UInt64 *maxCheckStartPosition, for (i = 0; i < num; i++) { - int parent = _items[i].Parent; + const int parent = _items[i].Parent; if (parent >= 0) numChilds[(unsigned)parent]++; } @@ -1409,7 +1658,7 @@ HRESULT CHandler::Open2(IInStream *stream, const UInt64 *maxCheckStartPosition, for (i = 0; i < num; i++) { const CItem &item = _items[i]; - int parent = item.Parent; + const int parent = item.Parent; if (parent >= 0) { CItem &parentItem = _items[(unsigned)parent]; @@ -1432,8 +1681,8 @@ HRESULT CHandler::Open2(IInStream *stream, const UInt64 *maxCheckStartPosition, int parent = item.Parent; if (parent >= 0) numItems = numChilds[(unsigned)parent]; - AString name2 = item.GetName(numItems); - AString characts2 = item.Characts; + AString name2 (item.GetName(numItems)); + AString characts2 (item.Characts); if (item.KeepName) name = name2; @@ -1444,7 +1693,7 @@ HRESULT CHandler::Open2(IInStream *stream, const UInt64 *maxCheckStartPosition, break; if (item3.KeepName) { - AString name3 = item3.GetName(-1); + AString name3 (item3.GetName(-1)); if (name.IsEmpty()) name = name3; else @@ -1462,7 +1711,7 @@ HRESULT CHandler::Open2(IInStream *stream, const UInt64 *maxCheckStartPosition, item2.Name = name; item2.Characts = characts2; if (parent >= 0) - item2.Parent = mainToReduced[(unsigned)parent]; + item2.Parent = (int)mainToReduced[(unsigned)parent]; _items2.Add(item2); /* CItem2 item2; @@ -1476,9 +1725,9 @@ HRESULT CHandler::Open2(IInStream *stream, const UInt64 *maxCheckStartPosition, return S_OK; } -STDMETHODIMP CHandler::Open(IInStream *inStream, +Z7_COM7F_IMF(CHandler::Open(IInStream *inStream, const UInt64 *maxCheckStartPosition, - IArchiveOpenCallback *callback) + IArchiveOpenCallback *callback)) { COM_TRY_BEGIN Close(); @@ -1491,7 +1740,7 @@ STDMETHODIMP CHandler::Open(IInStream *inStream, COM_TRY_END } -STDMETHODIMP CHandler::Close() +Z7_COM7F_IMF(CHandler::Close()) { _phySize = 0; _totalBufsSize = 0; @@ -1500,21 +1749,22 @@ STDMETHODIMP CHandler::Close() _items2.Clear(); _bufs.Clear(); _comment.Empty(); + _headersError = false; _h.Clear(); return S_OK; } -STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) { *numItems = _items2.Size(); return S_OK; } -STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, - Int32 testMode, IArchiveExtractCallback *extractCallback) +Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testMode, IArchiveExtractCallback *extractCallback)) { COM_TRY_BEGIN - bool allFilesMode = (numItems == (UInt32)(Int32)-1); + const bool allFilesMode = (numItems == (UInt32)(Int32)-1); if (allFilesMode) numItems = _items2.Size(); if (numItems == 0) @@ -1523,55 +1773,54 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, UInt32 i; for (i = 0; i < numItems; i++) totalSize += _items[_items2[allFilesMode ? i : indices[i]].MainIndex].Size; - extractCallback->SetTotal(totalSize); - - UInt64 currentTotalSize = 0; + RINOK(extractCallback->SetTotal(totalSize)) + totalSize = 0; - NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder(); - CMyComPtr copyCoder = copyCoderSpec; - - CLocalProgress *lps = new CLocalProgress; - CMyComPtr progress = lps; + CMyComPtr2_Create lps; lps->Init(extractCallback, false); + CMyComPtr2_Create copyCoder; - for (i = 0; i < numItems; i++) + for (i = 0;; i++) { - lps->InSize = lps->OutSize = currentTotalSize; - RINOK(lps->SetCur()); - CMyComPtr realOutStream; - Int32 askMode = testMode ? - NExtract::NAskMode::kTest : - NExtract::NAskMode::kExtract; - UInt32 index = allFilesMode ? i : indices[i]; - const CItem &item = _items[_items2[index].MainIndex]; - RINOK(extractCallback->GetStream(index, &realOutStream, askMode)); - currentTotalSize += item.Size; - - if (!testMode && !realOutStream) - continue; - RINOK(extractCallback->PrepareOperation(askMode)); - if (testMode || item.IsDir) - { - RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)); - continue; - } - int res = NExtract::NOperationResult::kDataError; - CMyComPtr inStream; - GetStream(index, &inStream); - if (inStream) + lps->InSize = lps->OutSize = totalSize; + RINOK(lps->SetCur()) + if (i >= numItems) + break; + Int32 opRes; { - RINOK(copyCoder->Code(inStream, realOutStream, NULL, NULL, progress)); - if (copyCoderSpec->TotalSize == item.Size) - res = NExtract::NOperationResult::kOK; + CMyComPtr realOutStream; + const Int32 askMode = testMode ? + NExtract::NAskMode::kTest : + NExtract::NAskMode::kExtract; + const UInt32 index = allFilesMode ? i : indices[i]; + const CItem &item = _items[_items2[index].MainIndex]; + RINOK(extractCallback->GetStream(index, &realOutStream, askMode)) + totalSize += item.Size; + if (!testMode && !realOutStream) + continue; + RINOK(extractCallback->PrepareOperation(askMode)) + if (testMode || item.IsDir) + opRes = NExtract::NOperationResult::kOK; + else + { + opRes = NExtract::NOperationResult::kDataError; + CMyComPtr inStream; + GetStream(index, &inStream); + if (inStream) + { + RINOK(copyCoder.Interface()->Code(inStream, realOutStream, NULL, NULL, lps)) + if (copyCoder->TotalSize == item.Size) + opRes = NExtract::NOperationResult::kOK; + } + } } - realOutStream.Release(); - RINOK(extractCallback->SetOperationResult(res)); + RINOK(extractCallback->SetOperationResult(opRes)) } return S_OK; COM_TRY_END } -STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream) +Z7_COM7F_IMF(CHandler::GetStream(UInt32 index, ISequentialInStream **stream)) { COM_TRY_BEGIN const CItem &item = _items[_items2[index].MainIndex]; @@ -1580,11 +1829,12 @@ STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream) CBufInStream *streamSpec = new CBufInStream; CMyComPtr streamTemp = streamSpec; const CByteBuffer &buf = _bufs[item.BufIndex]; - /* - if (item.Offset + item.Size > buf.GetCapacity()) + if (item.Offset > buf.Size()) return S_FALSE; - */ - streamSpec->Init(buf + item.Offset, item.Size, (IInArchive *)this); + size_t size = buf.Size() - item.Offset; + if (size > item.Size) + size = item.Size; + streamSpec->Init(buf + item.Offset, size, (IInArchive *)this); *stream = streamTemp.Detach(); return S_OK; COM_TRY_END @@ -1593,11 +1843,19 @@ STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream) namespace UEFIc { +static const Byte k_Capsule_Signatures[] = +{ + 16, CAPSULE_SIGNATURE, + 16, CAPSULE2_SIGNATURE, + 16, CAPSULE_UEFI_SIGNATURE +}; + REGISTER_ARC_I_CLS( CHandler(true), - "UEFIc", "scap", 0, 0xD0, - kCapsuleSig, + "UEFIc", "scap", NULL, 0xD0, + k_Capsule_Signatures, 0, + NArcInfoFlags::kMultiSignature | NArcInfoFlags::kFindSignature, NULL) @@ -1605,11 +1863,19 @@ REGISTER_ARC_I_CLS( namespace UEFIf { +static const Byte k_FFS_Signatures[] = +{ + 16, FFS1_SIGNATURE, + 16, FFS2_SIGNATURE +}; + + REGISTER_ARC_I_CLS( CHandler(false), - "UEFIf", "uefif", 0, 0xD1, - k_FFS_Guid, + "UEFIf", "uefif", NULL, 0xD1, + k_FFS_Signatures, kFfsGuidOffset, + NArcInfoFlags::kMultiSignature | NArcInfoFlags::kFindSignature, NULL) diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/VdiHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/VdiHandler.cpp index a8d3fe36..a5a9332b 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/VdiHandler.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/VdiHandler.cpp @@ -11,6 +11,7 @@ #include "../../Common/MyBuffer.h" #include "../../Windows/PropVariant.h" +#include "../../Windows/PropVariantUtils.h" #include "../Common/RegisterArc.h" #include "../Common/StreamUtils.h" @@ -25,13 +26,22 @@ using namespace NWindows; namespace NArchive { namespace NVdi { -#define SIGNATURE { 0x7F, 0x10, 0xDA, 0xBE } - -static const Byte k_Signature[] = SIGNATURE; +static const Byte k_Signature[] = { 0x7F, 0x10, 0xDA, 0xBE }; static const unsigned k_ClusterBits = 20; static const UInt32 k_ClusterSize = (UInt32)1 << k_ClusterBits; -static const UInt32 k_UnusedCluster = 0xFFFFFFFF; + + +/* +VDI_IMAGE_BLOCK_FREE = (~0) // returns any random data +VDI_IMAGE_BLOCK_ZERO = (~1) // returns zeros +*/ + +// static const UInt32 k_ClusterType_Free = 0xffffffff; +static const UInt32 k_ClusterType_Zero = 0xfffffffe; + +#define IS_CLUSTER_ALLOCATED(v) ((UInt32)(v) < k_ClusterType_Zero) + // static const UInt32 kDiskType_Dynamic = 1; // static const UInt32 kDiskType_Static = 2; @@ -41,9 +51,39 @@ static const char * const kDiskTypes[] = "0" , "Dynamic" , "Static" + , "Undo" + , "Diff" }; -class CHandler: public CHandlerImg + +enum EGuidType +{ + k_GuidType_Creat, + k_GuidType_Modif, + k_GuidType_Link, + k_GuidType_PModif +}; + +static const unsigned kNumGuids = 4; +static const char * const kGuidNames[kNumGuids] = +{ + "Creat " + , "Modif " + , "Link " + , "PModif" +}; + +static bool IsEmptyGuid(const Byte *data) +{ + for (unsigned i = 0; i < 16; i++) + if (data[i] != 0) + return false; + return true; +} + + + +Z7_class_CHandler_final: public CHandlerImg { UInt32 _dataOffset; CByteBuffer _table; @@ -52,29 +92,31 @@ class CHandler: public CHandlerImg bool _isArc; bool _unsupported; - HRESULT Seek(UInt64 offset) + Byte Guids[kNumGuids][16]; + + HRESULT Seek2(UInt64 offset) { _posInArc = offset; - return Stream->Seek(offset, STREAM_SEEK_SET, NULL); + return InStream_SeekSet(Stream, offset); } HRESULT InitAndSeek() { _virtPos = 0; - return Seek(0); + return Seek2(0); } - HRESULT Open2(IInStream *stream, IArchiveOpenCallback *openCallback); + HRESULT Open2(IInStream *stream, IArchiveOpenCallback *openCallback) Z7_override; public: - INTERFACE_IInArchive_Img(;) + Z7_IFACE_COM7_IMP(IInArchive_Img) - STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream); - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); + Z7_IFACE_COM7_IMP(IInArchiveGetStream) + Z7_IFACE_COM7_IMP(ISequentialInStream) }; -STDMETHODIMP CHandler::Read(void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CHandler::Read(void *data, UInt32 size, UInt32 *processedSize)) { if (processedSize) *processedSize = 0; @@ -101,14 +143,14 @@ STDMETHODIMP CHandler::Read(void *data, UInt32 size, UInt32 *processedSize) if (cluster < _table.Size()) { const Byte *p = (const Byte *)_table + (size_t)cluster; - UInt32 v = Get32(p); - if (v != k_UnusedCluster) + const UInt32 v = Get32(p); + if (IS_CLUSTER_ALLOCATED(v)) { UInt64 offset = _dataOffset + ((UInt64)v << k_ClusterBits); offset += lowBits; if (offset != _posInArc) { - RINOK(Seek(offset)); + RINOK(Seek2(offset)) } HRESULT res = Stream->Read(data, size, &size); _posInArc += size; @@ -137,13 +179,15 @@ static const Byte kProps[] = static const Byte kArcProps[] = { kpidHeadersSize, - kpidMethod + kpidMethod, + kpidComment, + kpidName }; IMP_IInArchive_Props IMP_IInArchive_ArcProps -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; @@ -156,23 +200,14 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) case kpidMethod: { - char s[16]; - const char *ptr; - if (_imageType < ARRAY_SIZE(kDiskTypes)) - ptr = kDiskTypes[_imageType]; - else - { - ConvertUInt32ToString(_imageType, s); - ptr = s; - } - prop = ptr; + TYPE_TO_PROP(kDiskTypes, _imageType, prop); break; } case kpidErrorFlags: { UInt32 v = 0; - if (!_isArc) v |= kpv_ErrorFlags_IsNotArc;; + if (!_isArc) v |= kpv_ErrorFlags_IsNotArc; if (_unsupported) v |= kpv_ErrorFlags_UnsupportedMethod; // if (_headerError) v |= kpv_ErrorFlags_HeadersError; if (!Stream && v == 0 && _isArc) @@ -181,6 +216,42 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) prop = v; break; } + + case kpidComment: + { + AString s; + for (unsigned i = 0; i < kNumGuids; i++) + { + const Byte *guid = Guids[i]; + if (!IsEmptyGuid(guid)) + { + s.Add_LF(); + s += kGuidNames[i]; + s += " : "; + char temp[64]; + RawLeGuidToString_Braced(guid, temp); + MyStringLower_Ascii(temp); + s += temp; + } + } + if (!s.IsEmpty()) + prop = s; + break; + } + + case kpidName: + { + const Byte *guid = Guids[k_GuidType_Creat]; + if (!IsEmptyGuid(guid)) + { + char temp[64]; + RawLeGuidToString_Braced(guid, temp); + MyStringLower_Ascii(temp); + MyStringCat(temp, ".vdi"); + prop = temp; + } + break; + } } prop.Detach(value); @@ -189,7 +260,7 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) } -STDMETHODIMP CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; @@ -207,63 +278,74 @@ STDMETHODIMP CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIAN } -static bool IsEmptyGuid(const Byte *data) -{ - for (unsigned i = 0; i < 16; i++) - if (data[i] != 0) - return false; - return true; -} - - HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback * /* openCallback */) { const unsigned kHeaderSize = 512; Byte buf[kHeaderSize]; - RINOK(ReadStream_FALSE(stream, buf, kHeaderSize)); + RINOK(ReadStream_FALSE(stream, buf, kHeaderSize)) if (memcmp(buf + 0x40, k_Signature, sizeof(k_Signature)) != 0) return S_FALSE; - UInt32 version = Get32(buf + 0x44); + const UInt32 version = Get32(buf + 0x44); if (version >= 0x20000) return S_FALSE; + if (version < 0x10000) + { + _unsupported = true; + return S_FALSE; + } - UInt32 headerSize = Get32(buf + 0x48); - if (headerSize < 0x140 || headerSize > 0x1B8) + const unsigned kHeaderOffset = 0x48; + const unsigned kGuidsOffsets = 0x188; + const UInt32 headerSize = Get32(buf + kHeaderOffset); + if (headerSize < kGuidsOffsets - kHeaderOffset || headerSize > 0x200 - kHeaderOffset) return S_FALSE; _imageType = Get32(buf + 0x4C); - _dataOffset = Get32(buf + 0x158); + // Int32 flags = Get32(buf + 0x50); + // Byte Comment[0x100] - UInt32 tableOffset = Get32(buf + 0x154); + const UInt32 tableOffset = Get32(buf + 0x154); if (tableOffset < 0x200) return S_FALSE; + + _dataOffset = Get32(buf + 0x158); + + // UInt32 geometry[3]; - UInt32 sectorSize = Get32(buf + 0x168); + const UInt32 sectorSize = Get32(buf + 0x168); if (sectorSize != 0x200) return S_FALSE; _size = Get64(buf + 0x170); + const UInt32 blockSize = Get32(buf + 0x178); + const UInt32 totalBlocks = Get32(buf + 0x180); + const UInt32 numAllocatedBlocks = Get32(buf + 0x184); + _isArc = true; - if (_imageType > 2) - { - _unsupported = true; - return S_FALSE; - } - if (_dataOffset < tableOffset) return S_FALSE; - UInt32 blockSize = Get32(buf + 0x178); - if (blockSize != ((UInt32)1 << k_ClusterBits)) + if (_imageType > 4) + _unsupported = true; + + if (blockSize != k_ClusterSize) { _unsupported = true; return S_FALSE; } - UInt32 totalBlocks = Get32(buf + 0x180); + if (headerSize >= kGuidsOffsets + kNumGuids * 16 - kHeaderOffset) + { + for (unsigned i = 0; i < kNumGuids; i++) + memcpy(Guids[i], buf + kGuidsOffsets + 16 * i, 16); + + if (!IsEmptyGuid(Guids[k_GuidType_Link]) || + !IsEmptyGuid(Guids[k_GuidType_PModif])) + _unsupported = true; + } { UInt64 size2 = (UInt64)totalBlocks << k_ClusterBits; @@ -278,18 +360,6 @@ HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback * /* openCallbac */ } - if (headerSize >= 0x180) - { - if (!IsEmptyGuid(buf + 0x1A8) || - !IsEmptyGuid(buf + 0x1B8)) - { - _unsupported = true; - return S_FALSE; - } - } - - UInt32 numAllocatedBlocks = Get32(buf + 0x184); - { UInt32 tableReserved = _dataOffset - tableOffset; if ((tableReserved >> 2) < totalBlocks) @@ -298,25 +368,28 @@ HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback * /* openCallbac _phySize = _dataOffset + ((UInt64)numAllocatedBlocks << k_ClusterBits); - size_t numBytes = (size_t)totalBlocks * 4; + const size_t numBytes = (size_t)totalBlocks * 4; if ((numBytes >> 2) != totalBlocks) { _unsupported = true; - return S_FALSE; + return E_OUTOFMEMORY; } _table.Alloc(numBytes); - RINOK(stream->Seek(tableOffset, STREAM_SEEK_SET, NULL)); - RINOK(ReadStream_FALSE(stream, _table, numBytes)); + RINOK(InStream_SeekSet(stream, tableOffset)) + RINOK(ReadStream_FALSE(stream, _table, numBytes)) const Byte *data = _table; for (UInt32 i = 0; i < totalBlocks; i++) { - UInt32 v = Get32(data + (size_t)i * 4); - if (v == k_UnusedCluster) + const UInt32 v = Get32(data + (size_t)i * 4); + if (!IS_CLUSTER_ALLOCATED(v)) continue; if (v >= numAllocatedBlocks) + { + _unsupported = true; return S_FALSE; + } } Stream = stream; @@ -324,28 +397,31 @@ HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback * /* openCallbac } -STDMETHODIMP CHandler::Close() +Z7_COM7F_IMF(CHandler::Close()) { _table.Free(); _phySize = 0; - _size = 0; _isArc = false; _unsupported = false; - _imgExt = NULL; + for (unsigned i = 0; i < kNumGuids; i++) + memset(Guids[i], 0, 16); + + // CHandlerImg: + Clear_HandlerImg_Vars(); Stream.Release(); return S_OK; } -STDMETHODIMP CHandler::GetStream(UInt32 /* index */, ISequentialInStream **stream) +Z7_COM7F_IMF(CHandler::GetStream(UInt32 /* index */, ISequentialInStream **stream)) { COM_TRY_BEGIN *stream = NULL; if (_unsupported) return S_FALSE; CMyComPtr streamTemp = this; - RINOK(InitAndSeek()); + RINOK(InitAndSeek()) *stream = streamTemp.Detach(); return S_OK; COM_TRY_END diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/VhdHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/VhdHandler.cpp index 12cbfa04..2fef079c 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/VhdHandler.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/VhdHandler.cpp @@ -19,18 +19,17 @@ #define Get32(p) GetBe32(p) #define Get64(p) GetBe64(p) -#define G32(_offs_, dest) dest = Get32(p + (_offs_)); -#define G64(_offs_, dest) dest = Get64(p + (_offs_)); +#define G32(_offs_, dest) dest = Get32(p + (_offs_)) +#define G64(_offs_, dest) dest = Get64(p + (_offs_)) using namespace NWindows; namespace NArchive { namespace NVhd { -#define SIGNATURE { 'c', 'o', 'n', 'e', 'c', 't', 'i', 'x', 0, 0 } - static const unsigned kSignatureSize = 10; -static const Byte kSignature[kSignatureSize] = SIGNATURE; +static const Byte kSignature[kSignatureSize] = + { 'c', 'o', 'n', 'e', 'c', 't', 'i', 'x', 0, 0 }; static const UInt32 kUnusedBlock = 0xFFFFFFFF; @@ -69,17 +68,16 @@ struct CFooter UInt32 NumCyls() const { return DiskGeometry >> 16; } UInt32 NumHeads() const { return (DiskGeometry >> 8) & 0xFF; } UInt32 NumSectorsPerTrack() const { return DiskGeometry & 0xFF; } - AString GetTypeString() const; + void AddTypeString(AString &s) const; bool Parse(const Byte *p); }; -AString CFooter::GetTypeString() const +void CFooter::AddTypeString(AString &s) const { - if (Type < ARRAY_SIZE(kDiskTypes)) - return kDiskTypes[Type]; - char s[16]; - ConvertUInt32ToString(Type, s); - return s; + if (Type < Z7_ARRAY_SIZE(kDiskTypes)) + s += kDiskTypes[Type]; + else + s.Add_UInt32(Type); } static bool CheckBlock(const Byte *p, unsigned size, unsigned checkSumOffset, unsigned zeroOffset) @@ -216,7 +214,7 @@ bool CDynHeader::Parse(const Byte *p) return CheckBlock(p, 1024, 0x24, 0x240 + 8 * 24); } -class CHandler: public CHandlerImg +Z7_class_CHandler_final: public CHandlerImg { UInt64 _posInArcLimit; UInt64 _startOffset; @@ -228,26 +226,28 @@ class CHandler: public CHandlerImg CByteBuffer BitMap; UInt32 BitMapTag; UInt32 NumUsedBlocks; - // CMyComPtr Stream; CMyComPtr ParentStream; CHandler *Parent; + UInt64 NumLevels; UString _errorMessage; // bool _unexpectedEnd; - void AddErrorMessage(const wchar_t *s) + void AddErrorMessage(const char *message, const wchar_t *name = NULL) { if (!_errorMessage.IsEmpty()) _errorMessage.Add_LF(); - _errorMessage += s; + _errorMessage += message; + if (name) + _errorMessage += name; } + void UpdatePhySize(UInt64 value) { if (_phySize < value) _phySize = value; } - void Reset_PosInArc() { _posInArc = (UInt64)0 - 1; } - HRESULT Seek(UInt64 offset); + HRESULT Seek2(UInt64 offset); HRESULT InitAndSeek(); HRESULT ReadPhy(UInt64 offset, void *data, UInt32 size); @@ -262,7 +262,7 @@ class CHandler: public CHandlerImg while (p && p->NeedParent()) { if (!res.IsEmpty()) - res.AddAscii(" -> "); + res += " -> "; UString mainName; UString anotherName; if (Dyn.RelativeNameWasUsed) @@ -279,9 +279,9 @@ class CHandler: public CHandlerImg if (mainName != anotherName && !anotherName.IsEmpty()) { res.Add_Space(); - res += L'('; + res.Add_Char('('); res += anotherName; - res += L')'; + res.Add_Char(')'); } p = p->Parent; } @@ -302,31 +302,31 @@ class CHandler: public CHandlerImg HRESULT Open3(); HRESULT Open2(IInStream *stream, CHandler *child, IArchiveOpenCallback *openArchiveCallback, unsigned level); - HRESULT Open2(IInStream *stream, IArchiveOpenCallback *openArchiveCallback) + HRESULT Open2(IInStream *stream, IArchiveOpenCallback *openArchiveCallback) Z7_override { return Open2(stream, NULL, openArchiveCallback, 0); } - void CloseAtError(); + void CloseAtError() Z7_override; public: - INTERFACE_IInArchive_Img(;) + Z7_IFACE_COM7_IMP(IInArchive_Img) - STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream); - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); + Z7_IFACE_COM7_IMP(IInArchiveGetStream) + Z7_IFACE_COM7_IMP(ISequentialInStream) }; -HRESULT CHandler::Seek(UInt64 offset) { return Stream->Seek(_startOffset + offset, STREAM_SEEK_SET, NULL); } +HRESULT CHandler::Seek2(UInt64 offset) { return InStream_SeekSet(Stream, _startOffset + offset); } HRESULT CHandler::InitAndSeek() { if (ParentStream) { - RINOK(Parent->InitAndSeek()); + RINOK(Parent->InitAndSeek()) } _virtPos = _posInArc = 0; BitMapTag = kUnusedBlock; BitMap.Alloc(Dyn.NumBitMapSectors() << kSectorSize_Log); - return Seek(0); + return Seek2(0); } HRESULT CHandler::ReadPhy(UInt64 offset, void *data, UInt32 size) @@ -336,7 +336,7 @@ HRESULT CHandler::ReadPhy(UInt64 offset, void *data, UInt32 size) if (offset != _posInArc) { _posInArc = offset; - RINOK(Seek(offset)); + RINOK(Seek2(offset)) } HRESULT res = ReadStream_FALSE(Stream, data, size); if (res == S_OK) @@ -351,10 +351,10 @@ HRESULT CHandler::Open3() // Fixed archive uses only footer UInt64 startPos; - RINOK(Stream->Seek(0, STREAM_SEEK_CUR, &startPos)); + RINOK(InStream_GetPos(Stream, startPos)) _startOffset = startPos; Byte header[kHeaderSize]; - RINOK(ReadStream_FALSE(Stream, header, kHeaderSize)); + RINOK(ReadStream_FALSE(Stream, header, kHeaderSize)) bool headerIsOK = Footer.Parse(header); _size = Footer.CurrentSize; @@ -371,15 +371,15 @@ HRESULT CHandler::Open3() } UInt64 fileSize; - RINOK(Stream->Seek(0, STREAM_SEEK_END, &fileSize)); + RINOK(InStream_GetSize_SeekToEnd(Stream, fileSize)) if (fileSize < kHeaderSize) return S_FALSE; const UInt32 kDynSize = 1024; Byte buf[kDynSize]; - RINOK(Stream->Seek(fileSize - kHeaderSize, STREAM_SEEK_SET, NULL)); - RINOK(ReadStream_FALSE(Stream, buf, kHeaderSize)); + RINOK(InStream_SeekSet(Stream, fileSize - kHeaderSize)) + RINOK(ReadStream_FALSE(Stream, buf, kHeaderSize)) if (!headerIsOK) { @@ -388,6 +388,7 @@ HRESULT CHandler::Open3() _size = Footer.CurrentSize; if (Footer.ThereIsDynamic()) return S_FALSE; // we can't open Dynamic Archive backward. + // fixed archive _posInArcLimit = Footer.CurrentSize; _phySize = Footer.CurrentSize + kHeaderSize; _startOffset = fileSize - kHeaderSize - Footer.CurrentSize; @@ -406,7 +407,7 @@ HRESULT CHandler::Open3() _phySize = fileSize - _startOffset; } - RINOK(ReadPhy(Footer.DataOffset, buf, kDynSize)); + RINOK(ReadPhy(Footer.DataOffset, buf, kDynSize)) if (!Dyn.Parse(buf)) return S_FALSE; @@ -429,7 +430,7 @@ HRESULT CHandler::Open3() unsigned len = (locator.DataLen >> 1); { wchar_t *s = tempString.GetBuf(len); - RINOK(ReadPhy(locator.DataOffset, nameBuf, locator.DataLen)); + RINOK(ReadPhy(locator.DataOffset, nameBuf, locator.DataLen)) unsigned j; for (j = 0; j < len; j++) { @@ -466,7 +467,7 @@ HRESULT CHandler::Open3() while ((UInt32)Bat.Size() < Dyn.NumBlocks) { - RINOK(ReadPhy(Dyn.TableOffset + (UInt64)Bat.Size() * 4, buf, kSectorSize)); + RINOK(ReadPhy(Dyn.TableOffset + (UInt64)Bat.Size() * 4, buf, kSectorSize)) UpdatePhySize(Dyn.TableOffset + kSectorSize); for (UInt32 j = 0; j < kSectorSize; j += 4) { @@ -494,7 +495,7 @@ HRESULT CHandler::Open3() return S_OK; } - RINOK(ReadPhy(_phySize, buf, kHeaderSize)); + RINOK(ReadPhy(_phySize, buf, kHeaderSize)) if (memcmp(header, buf, kHeaderSize) == 0) { _posInArcLimit = _phySize; @@ -510,7 +511,7 @@ HRESULT CHandler::Open3() for (i = 0; i < kSectorSize && buf[i] == 0; i++); if (i == kSectorSize) { - RINOK(ReadPhy(_phySize + kSectorSize, buf, kHeaderSize)); + RINOK(ReadPhy(_phySize + kSectorSize, buf, kHeaderSize)) if (memcmp(header, buf, kHeaderSize) == 0) { _phySize += kSectorSize; @@ -522,11 +523,11 @@ HRESULT CHandler::Open3() } _posInArcLimit = _phySize; _phySize += kHeaderSize; - AddErrorMessage(L"Can't find footer"); + AddErrorMessage("Can't find footer"); return S_OK; } -STDMETHODIMP CHandler::Read(void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CHandler::Read(void *data, UInt32 size, UInt32 *processedSize)) { if (processedSize) *processedSize = 0; @@ -539,9 +540,40 @@ STDMETHODIMP CHandler::Read(void *data, UInt32 size, UInt32 *processedSize) } if (size == 0) return S_OK; - UInt32 blockIndex = (UInt32)(_virtPos >> Dyn.BlockSizeLog); - UInt32 blockSectIndex = Bat[blockIndex]; - UInt32 blockSize = (UInt32)1 << Dyn.BlockSizeLog; + + if (Footer.IsFixed()) + { + if (_virtPos > _posInArcLimit) + return S_FALSE; + { + const UInt64 rem = _posInArcLimit - _virtPos; + if (size > rem) + size = (UInt32)rem; + } + HRESULT res = S_OK; + if (_virtPos != _posInArc) + { + _posInArc = _virtPos; + res = Seek2(_virtPos); + } + if (res == S_OK) + { + UInt32 processedSize2 = 0; + res = Stream->Read(data, size, &processedSize2); + if (processedSize) + *processedSize = processedSize2; + _posInArc += processedSize2; + } + if (res != S_OK) + Reset_PosInArc(); + return res; + } + + const UInt32 blockIndex = (UInt32)(_virtPos >> Dyn.BlockSizeLog); + if (blockIndex >= Bat.Size()) + return E_FAIL; // it's some unexpected case + const UInt32 blockSectIndex = Bat[blockIndex]; + const UInt32 blockSize = (UInt32)1 << Dyn.BlockSizeLog; UInt32 offsetInBlock = (UInt32)_virtPos & (blockSize - 1); size = MyMin(blockSize - offsetInBlock, size); @@ -550,7 +582,7 @@ STDMETHODIMP CHandler::Read(void *data, UInt32 size, UInt32 *processedSize) { if (ParentStream) { - RINOK(ParentStream->Seek(_virtPos, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekSet(ParentStream, _virtPos)) res = ParentStream->Read(data, size, &size); } else @@ -558,23 +590,23 @@ STDMETHODIMP CHandler::Read(void *data, UInt32 size, UInt32 *processedSize) } else { - UInt64 newPos = (UInt64)blockSectIndex << kSectorSize_Log; + const UInt64 newPos = (UInt64)blockSectIndex << kSectorSize_Log; if (BitMapTag != blockIndex) { - RINOK(ReadPhy(newPos, BitMap, (UInt32)BitMap.Size())); + RINOK(ReadPhy(newPos, BitMap, (UInt32)BitMap.Size())) BitMapTag = blockIndex; } - RINOK(ReadPhy(newPos + BitMap.Size() + offsetInBlock, data, size)); + RINOK(ReadPhy(newPos + BitMap.Size() + offsetInBlock, data, size)) for (UInt32 cur = 0; cur < size;) { const UInt32 rem = MyMin(0x200 - (offsetInBlock & 0x1FF), size - cur); - UInt32 bmi = offsetInBlock >> kSectorSize_Log; + const UInt32 bmi = offsetInBlock >> kSectorSize_Log; if (((BitMap[bmi >> 3] >> (7 - (bmi & 7))) & 1) == 0) { if (ParentStream) { - RINOK(ParentStream->Seek(_virtPos + cur, STREAM_SEEK_SET, NULL)); - RINOK(ReadStream_FALSE(ParentStream, (Byte *)data + cur, rem)); + RINOK(InStream_SeekSet(ParentStream, _virtPos + cur)) + RINOK(ReadStream_FALSE(ParentStream, (Byte *)data + cur, rem)) } else { @@ -603,11 +635,12 @@ enum static const CStatProp kArcProps[] = { - { NULL, kpidSize, VT_UI8}, { NULL, kpidOffset, VT_UI8}, { NULL, kpidCTime, VT_FILETIME}, { NULL, kpidClusterSize, VT_UI8}, { NULL, kpidMethod, VT_BSTR}, + { NULL, kpidNumVolumes, VT_UI4}, + { NULL, kpidTotalPhySize, VT_UI8}, { "Parent", kpidParent, VT_BSTR}, { NULL, kpidCreatorApp, VT_BSTR}, { NULL, kpidHostOS, VT_BSTR}, @@ -649,25 +682,15 @@ static void StringToAString(char *dest, UInt32 val) { for (int i = 24; i >= 0; i -= 8) { - Byte b = (Byte)((val >> i) & 0xFF); + const Byte b = (Byte)((val >> i) & 0xFF); if (b < 0x20 || b > 0x7F) break; - *dest++ = b; + *dest++ = (char)b; } *dest = 0; } -static void ConvertByteToHex(unsigned value, char *s) -{ - for (int i = 0; i < 2; i++) - { - unsigned t = value & 0xF; - value >>= 4; - s[1 - i] = (char)((t < 10) ? ('0' + t) : ('A' + (t - 10))); - } -} - -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; @@ -679,7 +702,8 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) case kpidShortComment: case kpidMethod: { - AString s = Footer.GetTypeString(); + AString s; + Footer.AddTypeString(s); if (NeedParent()) { s += " -> "; @@ -689,7 +713,7 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) if (!p) s += '?'; else - s += p->Footer.GetTypeString(); + p->Footer.AddTypeString(s); } prop = s; break; @@ -698,14 +722,12 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) { char s[16]; StringToAString(s, Footer.CreatorApp); - AString res = s; + AString res (s); res.Trim(); - ConvertUInt32ToString(Footer.CreatorVersion >> 16, s); res.Add_Space(); - res += s; - res += '.'; - ConvertUInt32ToString(Footer.CreatorVersion & 0xFFFF, s); - res += s; + res.Add_UInt32(Footer.CreatorVersion >> 16); + res.Add_Dot(); + res.Add_UInt32(Footer.CreatorVersion & 0xFFFF); prop = res; break; } @@ -723,10 +745,8 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) } case kpidId: { - char s[32 + 4]; - for (int i = 0; i < 16; i++) - ConvertByteToHex(Footer.Id[i], s + i * 2); - s[32] = 0; + char s[sizeof(Footer.Id) * 2 + 2]; + ConvertDataToHex_Upper(s, Footer.Id, sizeof(Footer.Id)); prop = s; break; } @@ -734,6 +754,21 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) case kpidParent: if (NeedParent()) prop = GetParentSequence(); break; case kpidOffset: prop = _startOffset; break; case kpidPhySize: prop = _phySize; break; + case kpidTotalPhySize: + { + const CHandler *p = this; + UInt64 sum = 0; + do + { + sum += p->_phySize; + p = p->Parent; + } + while (p); + prop = sum; + break; + } + case kpidNumVolumes: if (NumLevels != 1) prop = (UInt32)NumLevels; break; + /* case kpidErrorFlags: { @@ -760,8 +795,9 @@ HRESULT CHandler::Open2(IInStream *stream, CHandler *child, IArchiveOpenCallback if (level > (1 << 12)) // Maybe we need to increase that limit return S_FALSE; - RINOK(Open3()); + RINOK(Open3()) + NumLevels = 1; if (child && memcmp(child->Dyn.ParentId, Footer.Id, 16) != 0) return S_FALSE; if (Footer.Type != kDiskType_Diff) @@ -783,9 +819,10 @@ HRESULT CHandler::Open2(IInStream *stream, CHandler *child, IArchiveOpenCallback Dyn.RelativeNameWasUsed = useRelative; - CMyComPtr openVolumeCallback; - openArchiveCallback->QueryInterface(IID_IArchiveOpenVolumeCallback, (void **)&openVolumeCallback); - + Z7_DECL_CMyComPtr_QI_FROM( + IArchiveOpenVolumeCallback, + openVolumeCallback, openArchiveCallback) + if (openVolumeCallback) { CMyComPtr nextStream; @@ -806,10 +843,7 @@ HRESULT CHandler::Open2(IInStream *stream, CHandler *child, IArchiveOpenCallback if (res == S_FALSE || !nextStream) { - UString s; - s.SetFromAscii("Missing volume : "); - s += name; - AddErrorMessage(s); + AddErrorMessage("Missing volume : ", name); return S_OK; } @@ -829,6 +863,10 @@ HRESULT CHandler::Open2(IInStream *stream, CHandler *child, IArchiveOpenCallback // we must show that error code } } + if (res == S_OK) + { + NumLevels = Parent->NumLevels + 1; + } } { const CHandler *p = this; @@ -837,8 +875,7 @@ HRESULT CHandler::Open2(IInStream *stream, CHandler *child, IArchiveOpenCallback p = p->Parent; if (!p) { - AddErrorMessage(L"Can't open parent VHD file:"); - AddErrorMessage(Dyn.ParentName); + AddErrorMessage("Can't open parent VHD file : ", Dyn.ParentName); break; } } @@ -849,25 +886,28 @@ HRESULT CHandler::Open2(IInStream *stream, CHandler *child, IArchiveOpenCallback void CHandler::CloseAtError() { + // CHandlerImg: + Stream.Release(); + Clear_HandlerImg_Vars(); + _phySize = 0; + NumLevels = 0; Bat.Clear(); NumUsedBlocks = 0; Parent = NULL; - Stream.Release(); ParentStream.Release(); Dyn.Clear(); _errorMessage.Empty(); // _unexpectedEnd = false; - _imgExt = NULL; } -STDMETHODIMP CHandler::Close() +Z7_COM7F_IMF(CHandler::Close()) { CloseAtError(); return S_OK; } -STDMETHODIMP CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; @@ -892,24 +932,25 @@ STDMETHODIMP CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIAN } -STDMETHODIMP CHandler::GetStream(UInt32 /* index */, ISequentialInStream **stream) +Z7_COM7F_IMF(CHandler::GetStream(UInt32 /* index */, ISequentialInStream **stream)) { COM_TRY_BEGIN - *stream = 0; + *stream = NULL; if (Footer.IsFixed()) { - CLimitedInStream *streamSpec = new CLimitedInStream; - CMyComPtr streamTemp = streamSpec; + CMyComPtr2 streamSpec; + streamSpec.Create_if_Empty(); streamSpec->SetStream(Stream); - streamSpec->InitAndSeek(0, Footer.CurrentSize); - RINOK(streamSpec->SeekToStart()); - *stream = streamTemp.Detach(); + // fixme : check (startOffset = 0) + streamSpec->InitAndSeek(_startOffset, Footer.CurrentSize); + RINOK(streamSpec->SeekToStart()) + *stream = streamSpec.Detach(); return S_OK; } if (!Footer.ThereIsDynamic() || !AreParentsOK()) return S_FALSE; CMyComPtr streamTemp = this; - RINOK(InitAndSeek()); + RINOK(InitAndSeek()) *stream = streamTemp.Detach(); return S_OK; COM_TRY_END diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/VhdxHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/VhdxHandler.cpp new file mode 100644 index 00000000..ca450e5d --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/VhdxHandler.cpp @@ -0,0 +1,2083 @@ +// VhdxHandler.cpp + +#include "StdAfx.h" + +// #include + +#include "../../../C/CpuArch.h" + +#include "../../Common/ComTry.h" +#include "../../Common/IntToString.h" +#include "../../Common/StringToInt.h" +#include "../../Common/MyBuffer.h" + +#include "../../Windows/PropVariant.h" + +#include "../Common/RegisterArc.h" +#include "../Common/StreamUtils.h" + +#include "HandlerCont.h" + +#define Get16(p) GetUi16(p) +#define Get32(p) GetUi32(p) +#define Get64(p) GetUi64(p) + +#define G32(_offs_, dest) dest = Get32(p + (_offs_)) +#define G64(_offs_, dest) dest = Get64(p + (_offs_)) + +using namespace NWindows; + + +EXTERN_C_BEGIN + +// CRC-32C (Castagnoli) : reversed for poly 0x1EDC6F41 +#define k_Crc32c_Poly 0x82f63b78 + +MY_ALIGN(64) +static UInt32 g_Crc32c_Table[256]; + +static void Z7_FASTCALL Crc32c_GenerateTable() +{ + UInt32 i; + for (i = 0; i < 256; i++) + { + UInt32 r = i; + unsigned j; + for (j = 0; j < 8; j++) + r = (r >> 1) ^ (k_Crc32c_Poly & ((UInt32)0 - (r & 1))); + g_Crc32c_Table[i] = r; + } +} + + +#define CRC32C_INIT_VAL 0xFFFFFFFF + +#define CRC_UPDATE_BYTE_2(crc, b) (table[((crc) ^ (b)) & 0xFF] ^ ((crc) >> 8)) + +// UInt32 Z7_FASTCALL CrcUpdateT1(UInt32 v, const void *data, size_t size, const UInt32 *table); +static UInt32 Z7_FASTCALL CrcUpdateT1_vhdx(UInt32 v, const void *data, size_t size, const UInt32 *table) +{ + const Byte *p = (const Byte *)data; + const Byte *pEnd = p + size; + for (; p != pEnd; p++) + v = CRC_UPDATE_BYTE_2(v, *p); + return v; +} + +static UInt32 Z7_FASTCALL Crc32c_Calc(const void *data, size_t size) +{ + return CrcUpdateT1_vhdx(CRC32C_INIT_VAL, data, size, g_Crc32c_Table) ^ CRC32C_INIT_VAL; +} + +EXTERN_C_END + + +namespace NArchive { +namespace NVhdx { + +static struct C_CRC32c_TableInit { C_CRC32c_TableInit() { Crc32c_GenerateTable(); } } g_CRC32c_TableInit; + +static const unsigned kSignatureSize = 8; +static const Byte kSignature[kSignatureSize] = + { 'v', 'h', 'd', 'x', 'f', 'i', 'l', 'e' }; + +static const unsigned kBitmapSize_Log = 20; +static const size_t kBitmapSize = (size_t)1 << kBitmapSize_Log; + + +static bool IsZeroArr(const Byte *p, size_t size) +{ + for (size_t i = 0; i < size; i++) + if (p[i] != 0) + return false; + return true; +} + + + +Z7_FORCE_INLINE +static int DecodeFrom2HexChars(const wchar_t *s) +{ + unsigned v0 = (unsigned)s[0]; Z7_PARSE_HEX_DIGIT(v0, return -1;) + unsigned v1 = (unsigned)s[1]; Z7_PARSE_HEX_DIGIT(v1, return -1;) + return (int)((v0 << 4) | v1); +} + + +struct CGuid +{ + Byte Data[16]; + + bool IsZero() const { return IsZeroArr(Data, 16); } + bool IsEqualTo(const Byte *a) const { return memcmp(Data, a, 16) == 0; } + bool IsEqualTo(const CGuid &g) const { return IsEqualTo(g.Data); } + void AddHexToString(UString &s) const; + + void SetFrom(const Byte *p) { memcpy(Data, p, 16); } + + bool ParseFromFormatedHexString(const UString &s) + { + const unsigned kLen = 16 * 2 + 4 + 2; + if (s.Len() != kLen || s[0] != '{' || s[kLen - 1] != '}') + return false; + unsigned pos = 0; + for (unsigned i = 1; i < kLen - 1;) + { + if (i == 9 || i == 14 || i == 19 || i == 24) + { + if (s[i] != '-') + return false; + i++; + continue; + } + const int v = DecodeFrom2HexChars(s.Ptr(i)); + if (v < 0) + return false; + unsigned pos2 = pos; + if (pos < 8) + pos2 ^= (pos < 4 ? 3 : 1); + Data[pos2] = (Byte)v; + pos++; + i += 2; + } + return true; // pos == 16; + } +}; + +void CGuid::AddHexToString(UString &s) const +{ + char temp[sizeof(Data) * 2 + 2]; + ConvertDataToHex_Lower(temp, Data, sizeof(Data)); + s += temp; +} + + +#define IS_NON_ALIGNED(v) (((v) & 0xFFFFF) != 0) + +static const unsigned kHeader_GUID_Index_FileWriteGuid = 0; +static const unsigned kHeader_GUID_Index_DataWriteGuid = 1; +static const unsigned kHeader_GUID_Index_LogGuid = 2; + +struct CHeader +{ + UInt64 SequenceNumber; + // UInt16 LogVersion; + // UInt16 Version; + UInt32 LogLength; + UInt64 LogOffset; + CGuid Guids[3]; + + bool IsEqualTo(const CHeader &h) const + { + if (SequenceNumber != h.SequenceNumber) + return false; + if (LogLength != h.LogLength) + return false; + if (LogOffset != h.LogOffset) + return false; + for (unsigned i = 0; i < 3; i++) + if (!Guids[i].IsEqualTo(h.Guids[i])) + return false; + return true; + } + + bool Parse(Byte *p); +}; + +static const unsigned kHeader2Size = 1 << 12; + +bool CHeader::Parse(Byte *p) +{ + if (Get32(p) != 0x64616568) // "head" + return false; + const UInt32 crc = Get32(p + 4); + SetUi32(p + 4, 0) + if (Crc32c_Calc(p, kHeader2Size) != crc) + return false; + G64(8, SequenceNumber); + for (unsigned i = 0; i < 3; i++) + Guids[i].SetFrom(p + 0x10 + 0x10 * i); + // LogVersion = Get16(p + 0x40); + /* LogVersion MUST be set to zero, for known log format + but we don't parse log so we ignore it */ + G32(0x44, LogLength); + G64(0x48, LogOffset); + if (Get16(p + 0x42) != 1) // Header format Version + return false; + if (IS_NON_ALIGNED(LogLength)) + return false; + if (IS_NON_ALIGNED(LogOffset)) + return false; + return true; + // return IsZeroArr(p + 0x50, kHeader2Size - 0x50); +} + + + +static const Byte kBat[16] = + { 0x66,0x77,0xC2,0x2D,0x23,0xF6,0x00,0x42,0x9D,0x64,0x11,0x5E,0x9B,0xFD,0x4A,0x08 }; +static const Byte kMetadataRegion[16] = + { 0x06,0xA2,0x7C,0x8B,0x90,0x47,0x9A,0x4B,0xB8,0xFE,0x57,0x5F,0x05,0x0F,0x88,0x6E }; + +struct CRegionEntry +{ + // CGuid Guid; + UInt64 Offset; + UInt32 Len; + UInt32 Required; + + UInt64 GetEndPos() const { return Offset + Len; } + bool Parse(const Byte *p); +}; + +bool CRegionEntry::Parse(const Byte *p) +{ + // Guid.SetFrom(p); + G64(0x10, Offset); + G32(0x18, Len); + G32(0x1c, Required); + if (IS_NON_ALIGNED(Offset)) + return false; + if (IS_NON_ALIGNED(Len)) + return false; + if (Offset + Len < Offset) + return false; + return true; +} + + +struct CRegion +{ + bool Bat_Defined; + bool Meta_Defined; + UInt64 EndPos; + UInt64 DataSize; + + CRegionEntry BatEntry; + CRegionEntry MetaEntry; + + bool Parse(Byte *p); +}; + + +static const size_t kRegionSize = 1 << 16; +static const unsigned kNumRegionEntriesMax = (1 << 11) - 1; + +bool CRegion::Parse(Byte *p) +{ + Bat_Defined = false; + Meta_Defined = false; + EndPos = 0; + DataSize = 0; + + if (Get32(p) != 0x69676572) // "regi" + return false; + const UInt32 crc = Get32(p + 4); + SetUi32(p + 4, 0) + const UInt32 crc_calced = Crc32c_Calc(p, kRegionSize); + if (crc_calced != crc) + return false; + + const UInt32 EntryCount = Get32(p + 8); + if (Get32(p + 12) != 0) // reserved field must be set to 0. + return false; + if (EntryCount > kNumRegionEntriesMax) + return false; + for (UInt32 i = 0; i < EntryCount; i++) + { + CRegionEntry e; + const Byte *p2 = p + 0x10 + 0x20 * (size_t)i; + if (!e.Parse(p2)) + return false; + DataSize += e.Len; + const UInt64 endPos = e.GetEndPos(); + if (EndPos < endPos) + EndPos = endPos; + CGuid Guid; + Guid.SetFrom(p2); + if (Guid.IsEqualTo(kBat)) + { + if (Bat_Defined) + return false; + BatEntry = e; + Bat_Defined = true; + } + else if (Guid.IsEqualTo(kMetadataRegion)) + { + if (Meta_Defined) + return false; + MetaEntry = e; + Meta_Defined = true; + } + else + { + if (e.Required != 0) + return false; + // it's allowed to ignore unknown non-required region entries + } + } + /* + const size_t k = 0x10 + 0x20 * EntryCount; + return IsZeroArr(p + k, kRegionSize - k); + */ + return true; +} + + + + +struct CMetaEntry +{ + CGuid Guid; + UInt32 Offset; + UInt32 Len; + UInt32 Flags0; + // UInt32 Flags1; + + bool IsUser() const { return (Flags0 & 1) != 0; } + bool IsVirtualDisk() const { return (Flags0 & 2) != 0; } + bool IsRequired() const { return (Flags0 & 4) != 0; } + + bool CheckLimit(size_t regionSize) const + { + return Offset <= regionSize && Len <= regionSize - Offset; + } + + bool Parse(const Byte *p); +}; + + +bool CMetaEntry::Parse(const Byte *p) +{ + Guid.SetFrom(p); + + G32(0x10, Offset); + G32(0x14, Len); + G32(0x18, Flags0); + UInt32 Flags1; + G32(0x1C, Flags1); + + if (Offset != 0 && Offset < (1 << 16)) + return false; + if (Len > (1 << 20)) + return false; + if (Len == 0 && Offset != 0) + return false; + if ((Flags0 >> 3) != 0) // Reserved + return false; + if ((Flags1 & 3) != 0) // Reserved2 + return false; + return true; +} + + +struct CParentPair +{ + UString Key; + UString Value; +}; + + +struct CMetaHeader +{ + // UInt16 EntryCount; + bool Guid_Defined; + bool VirtualDiskSize_Defined; + bool Locator_Defined; + + unsigned BlockSize_Log; + unsigned LogicalSectorSize_Log; + unsigned PhysicalSectorSize_Log; + + UInt32 Flags; + UInt64 VirtualDiskSize; + CGuid Guid; + // CGuid LocatorType; + + CObjectVector ParentPairs; + + int FindParentKey(const char *name) const + { + FOR_VECTOR (i, ParentPairs) + { + const CParentPair &pair = ParentPairs[i]; + if (pair.Key.IsEqualTo(name)) + return (int)i; + } + return -1; + } + + bool Is_LeaveBlockAllocated() const { return (Flags & 1) != 0; } + bool Is_HasParent() const { return (Flags & 2) != 0; } + + void Clear() + { + Guid_Defined = false; + VirtualDiskSize_Defined = false; + Locator_Defined = false; + BlockSize_Log = 0; + LogicalSectorSize_Log = 0; + PhysicalSectorSize_Log = 0; + Flags = 0; + VirtualDiskSize = 0; + ParentPairs.Clear(); + } + + bool Parse(const Byte *p, size_t size); +}; + + +static unsigned GetLogSize(UInt32 size) +{ + unsigned k; + for (k = 0; k < 32; k++) + if (((UInt32)1 << k) == size) + return k; + return k; +} + + +static const unsigned kMetadataSize = 8; +static const Byte kMetadata[kMetadataSize] = + { 'm','e','t','a','d','a','t','a' }; + +static const unsigned k_Num_MetaEntries_Max = (1 << 11) - 1; + +static const Byte kFileParameters[16] = + { 0x37,0x67,0xa1,0xca,0x36,0xfa,0x43,0x4d,0xb3,0xb6,0x33,0xf0,0xaa,0x44,0xe7,0x6b }; +static const Byte kVirtualDiskSize[16] = + { 0x24,0x42,0xa5,0x2f,0x1b,0xcd,0x76,0x48,0xb2,0x11,0x5d,0xbe,0xd8,0x3b,0xf4,0xb8 }; +static const Byte kVirtualDiskID[16] = + { 0xab,0x12,0xca,0xbe,0xe6,0xb2,0x23,0x45,0x93,0xef,0xc3,0x09,0xe0,0x00,0xc7,0x46 }; +static const Byte kLogicalSectorSize[16] = + { 0x1d,0xbf,0x41,0x81,0x6f,0xa9,0x09,0x47,0xba,0x47,0xf2,0x33,0xa8,0xfa,0xab,0x5f }; +static const Byte kPhysicalSectorSize[16] = + { 0xc7,0x48,0xa3,0xcd,0x5d,0x44,0x71,0x44,0x9c,0xc9,0xe9,0x88,0x52,0x51,0xc5,0x56 }; +static const Byte kParentLocator[16] = + { 0x2d,0x5f,0xd3,0xa8,0x0b,0xb3,0x4d,0x45,0xab,0xf7,0xd3,0xd8,0x48,0x34,0xab,0x0c }; + +static bool GetString16(UString &s, const Byte *p, size_t size) +{ + s.Empty(); + if (size & 1) + return false; + for (size_t i = 0; i < size; i += 2) + { + const wchar_t c = Get16(p + i); + if (c == 0) + return false; + s += c; + } + return true; +} + + +bool CMetaHeader::Parse(const Byte *p, size_t size) +{ + if (memcmp(p, kMetadata, kMetadataSize) != 0) + return false; + if (Get16(p + 8) != 0) // Reserved + return false; + const UInt32 EntryCount = Get16(p + 10); + if (EntryCount > k_Num_MetaEntries_Max) + return false; + if (!IsZeroArr(p + 12, 20)) // Reserved + return false; + + for (unsigned i = 0; i < EntryCount; i++) + { + CMetaEntry e; + if (!e.Parse(p + 32 + 32 * (size_t)i)) + return false; + if (!e.CheckLimit(size)) + return false; + const Byte *p2 = p + e.Offset; + + if (e.Guid.IsEqualTo(kFileParameters)) + { + if (BlockSize_Log != 0) + return false; + if (e.Len != 8) + return false; + const UInt32 v = Get32(p2); + Flags = Get32(p2 + 4); + BlockSize_Log = GetLogSize(v); + if (BlockSize_Log < 20 || BlockSize_Log > 28) // specification from 1 MB to 256 MB + return false; + if ((Flags >> 2) != 0) // reserved + return false; + } + else if (e.Guid.IsEqualTo(kVirtualDiskSize)) + { + if (VirtualDiskSize_Defined) + return false; + if (e.Len != 8) + return false; + VirtualDiskSize = Get64(p2); + VirtualDiskSize_Defined = true; + } + else if (e.Guid.IsEqualTo(kVirtualDiskID)) + { + if (e.Len != 16) + return false; + Guid.SetFrom(p2); + Guid_Defined = true; + } + else if (e.Guid.IsEqualTo(kLogicalSectorSize)) + { + if (LogicalSectorSize_Log != 0) + return false; + if (e.Len != 4) + return false; + const UInt32 v = Get32(p2); + LogicalSectorSize_Log = GetLogSize(v); + if (LogicalSectorSize_Log != 9 && LogicalSectorSize_Log != 12) + return false; + } + else if (e.Guid.IsEqualTo(kPhysicalSectorSize)) + { + if (PhysicalSectorSize_Log != 0) + return false; + if (e.Len != 4) + return false; + const UInt32 v = Get32(p2); + PhysicalSectorSize_Log = GetLogSize(v); + if (PhysicalSectorSize_Log != 9 && PhysicalSectorSize_Log != 12) + return false; + } + else if (e.Guid.IsEqualTo(kParentLocator)) + { + if (Locator_Defined) + return false; + if (e.Len < 20) + return false; + // LocatorType.SetFrom(p2); + /* Specifies the type of the parent virtual disk. + is different for each type: VHDX, VHD or iSCSI. + only "B04AEFB7-D19E-4A81-B789-25B8E9445913" (for VHDX) is supported now + */ + Locator_Defined = true; + if (Get16(p2 + 16) != 0) // reserved + return false; + const UInt32 KeyValueCount = Get16(p2 + 18); + if (20 + (UInt32)KeyValueCount * 12 > e.Len) + return false; + for (unsigned k = 0; k < KeyValueCount; k++) + { + const Byte *p3 = p2 + 20 + (size_t)k * 12; + const UInt32 KeyOffset = Get32(p3); + const UInt32 ValueOffset = Get32(p3 + 4); + const UInt32 KeyLength = Get16(p3 + 8); + const UInt32 ValueLength = Get16(p3 + 10); + if (KeyOffset > e.Len || KeyLength > e.Len - KeyOffset) + return false; + if (ValueOffset > e.Len || ValueLength > e.Len - ValueOffset) + return false; + CParentPair pair; + if (!GetString16(pair.Key, p2 + KeyOffset, KeyLength)) + return false; + if (!GetString16(pair.Value, p2 + ValueOffset, ValueLength)) + return false; + ParentPairs.Add(pair); + } + } + else + { + if (e.IsRequired()) + return false; + // return false; // unknown metadata; + } + } + + // some properties are required for correct processing + + if (BlockSize_Log == 0) + return false; + if (LogicalSectorSize_Log == 0) + return false; + if (!VirtualDiskSize_Defined) + return false; + if (((UInt32)VirtualDiskSize & ((UInt32)1 << LogicalSectorSize_Log)) != 0) + return false; + + // vhdx specification sets limit for 64 TB. + // do we need to check over same limit ? + const UInt64 kVirtualDiskSize_Max = (UInt64)1 << 46; + if (VirtualDiskSize > kVirtualDiskSize_Max) + return false; + + return true; +} + + + +struct CBat +{ + CByteBuffer Data; + + void Clear() { Data.Free(); } + UInt64 GetItem(size_t n) const + { + return Get64(Data + n * 8); + } +}; + + + +Z7_class_CHandler_final: public CHandlerImg +{ + UInt64 _phySize; + + CBat Bat; + CObjectVector BitMaps; + + unsigned ChunkRatio_Log; + size_t ChunkRatio; + size_t TotalBatEntries; + + CMetaHeader Meta; + CHeader Header; + + UInt32 NumUsedBlocks; + UInt32 NumUsedBitMaps; + UInt64 HeadersSize; + + UInt32 NumLevels; + UInt64 PackSize_Total; + + /* + UInt64 NumUsed_1MB_Blocks; // data and bitmaps + bool NumUsed_1MB_Blocks_Defined; + */ + + CMyComPtr ParentStream; + CHandler *Parent; + UString _errorMessage; + UString _creator; + + bool _nonEmptyLog; + bool _isDataContiguous; + // bool _batOverlap; + + CGuid _parentGuid; + bool _parentGuid_IsDefined; + UStringVector ParentNames; + UString ParentName_Used; + + const CHandler *_child; + unsigned _level; + bool _isCyclic; + bool _isCyclic_or_CyclicParent; + + void AddErrorMessage(const char *message); + void AddErrorMessage(const char *message, const wchar_t *name); + + void UpdatePhySize(UInt64 value) + { + if (_phySize < value) + _phySize = value; + } + + HRESULT Seek2(UInt64 offset); + HRESULT Read_FALSE(Byte *data, size_t size) + { + return ReadStream_FALSE(Stream, data, size); + } + HRESULT ReadToBuf_FALSE(CByteBuffer &buf, size_t size) + { + buf.Alloc(size); + return ReadStream_FALSE(Stream, buf, size); + } + + void InitSeekPositions(); + HRESULT ReadPhy(UInt64 offset, void *data, UInt32 size, UInt32 &processed); + + bool IsDiff() const + { + // here we suppose that only HasParent() flag is mandatory for Diff archive type + return Meta.Is_HasParent(); + // return _parentGuid_IsDefined; + } + + void AddTypeString(AString &s) const + { + if (IsDiff()) + s += "Differencing"; + else + { + if (Meta.Is_LeaveBlockAllocated()) + s += _isDataContiguous ? "fixed" : "fixed-non-cont"; + else + s += "dynamic"; + } + } + + void AddComment(UString &s) const; + + UInt64 GetPackSize() const + { + return (UInt64)NumUsedBlocks << Meta.BlockSize_Log; + } + + UString GetParentSequence() const + { + const CHandler *p = this; + UString res; + while (p && p->IsDiff()) + { + if (!res.IsEmpty()) + res += " -> "; + res += ParentName_Used; + p = p->Parent; + } + return res; + } + + bool AreParentsOK() const + { + if (_isCyclic_or_CyclicParent) + return false; + const CHandler *p = this; + while (p->IsDiff()) + { + p = p->Parent; + if (!p) + return false; + } + return true; + } + + // bool ParseLog(CByteBuffer &log); + bool ParseBat(); + bool CheckBat(); + + HRESULT Open3(); + HRESULT Open2(IInStream *stream, IArchiveOpenCallback *openArchiveCallback) Z7_override; + HRESULT OpenParent(IArchiveOpenCallback *openArchiveCallback, bool &_parentFileWasOpen); + virtual void CloseAtError() Z7_override; + +public: + Z7_IFACE_COM7_IMP(IInArchive_Img) + + Z7_IFACE_COM7_IMP(IInArchiveGetStream) + Z7_IFACE_COM7_IMP(ISequentialInStream) + + CHandler(): + _child(NULL), + _level(0), + _isCyclic(false), + _isCyclic_or_CyclicParent(false) + {} +}; + + +HRESULT CHandler::Seek2(UInt64 offset) +{ + return InStream_SeekSet(Stream, offset); +} + + +void CHandler::InitSeekPositions() +{ + /* (_virtPos) and (_posInArc) is used only in Read() (that calls ReadPhy()). + So we must reset these variables before first call of Read() */ + Reset_VirtPos(); + Reset_PosInArc(); + if (ParentStream) + Parent->InitSeekPositions(); +} + + +HRESULT CHandler::ReadPhy(UInt64 offset, void *data, UInt32 size, UInt32 &processed) +{ + processed = 0; + if (offset > _phySize + || offset + size > _phySize) + { + // we don't expect these cases, if (_phySize) was set correctly. + return S_FALSE; + } + if (offset != _posInArc) + { + const HRESULT res = Seek2(offset); + if (res != S_OK) + { + Reset_PosInArc(); // we don't trust seek_pos in case of error + return res; + } + _posInArc = offset; + } + { + size_t size2 = size; + const HRESULT res = ReadStream(Stream, data, &size2); + processed = (UInt32)size2; + _posInArc += size2; + if (res != S_OK) + Reset_PosInArc(); // we don't trust seek_pos in case of reading error + return res; + } +} + + +#define PAYLOAD_BLOCK_NOT_PRESENT 0 +#define PAYLOAD_BLOCK_UNDEFINED 1 +#define PAYLOAD_BLOCK_ZERO 2 +#define PAYLOAD_BLOCK_UNMAPPED 3 +#define PAYLOAD_BLOCK_FULLY_PRESENT 6 +#define PAYLOAD_BLOCK_PARTIALLY_PRESENT 7 + +#define SB_BLOCK_NOT_PRESENT 0 +#define SB_BLOCK_PRESENT 6 + +#define BAT_GET_OFFSET(v) ((v) & ~(UInt64)0xFFFFF) +#define BAT_GET_STATE(v) ((UInt32)(v) & 7) + +/* The log contains only updates to metadata, bat and region tables + The log doesn't contain updates to start header, and 2 headers (first 192 KB of file). + The log is array of 4 KB blocks and each block has 4-byte signature. + So it's possible to scan whole log to find the latest entry sequence (and header for replay). +*/ + +/* +struct CLogEntry +{ + UInt32 EntryLength; + UInt32 Tail; + UInt64 SequenceNumber; + CGuid LogGuid; + UInt32 DescriptorCount; + UInt64 FlushedFileOffset; + UInt64 LastFileOffset; + + bool Parse(const Byte *p); +}; + +bool CLogEntry::Parse(const Byte *p) +{ + G32 (8, EntryLength); + G32 (12,Tail); + G64 (16, SequenceNumber); + G32 (24, DescriptorCount); // it's 32-bit, but specification says 64-bit + if (Get32(p + 28) != 0) // reserved + return false; + LogGuid.SetFrom(p + 32); + G64 (48, FlushedFileOffset); + G64 (56, LastFileOffset); + + if (SequenceNumber == 0) + return false; + if ((Tail & 0xfff) != 0) + return false; + if (IS_NON_ALIGNED(FlushedFileOffset)) + return false; + if (IS_NON_ALIGNED(LastFileOffset)) + return false; + return true; +} + + +bool CHandler::ParseLog(CByteBuffer &log) +{ + CLogEntry lastEntry; + lastEntry.SequenceNumber = 0; + bool lastEntry_found = false; + size_t lastEntry_Offset = 0; + for (size_t i = 0; i < log.Size(); i += 1 << 12) + { + Byte *p = (Byte *)(log + i); + + if (Get32(p) != 0x65676F6C) // "loge" + continue; + const UInt32 crc = Get32(p + 4); + + CLogEntry e; + if (!e.Parse(p)) + { + return false; + continue; + } + const UInt32 entryLength = Get32(p + 8); + if (e.EntryLength > log.Size() || (e.EntryLength & 0xFFF) != 0 || e.EntryLength == 0) + { + return false; + continue; + } + SetUi32(p + 4, 0); + const UInt32 crc_calced = Crc32c_Calc(p, entryLength); + SetUi32(p + 4, crc); // we must restore crc if we want same data in log + if (crc_calced != crc) + continue; + if (!lastEntry_found || lastEntry.SequenceNumber < e.SequenceNumber) + { + lastEntry = e; + lastEntry_found = true; + lastEntry_Offset = i; + } + } + + return true; +} +*/ + + +bool CHandler::ParseBat() +{ + ChunkRatio_Log = kBitmapSize_Log + 3 + Meta.LogicalSectorSize_Log - Meta.BlockSize_Log; + ChunkRatio = (size_t)1 << (ChunkRatio_Log); + + UInt64 totalBatEntries64; + const bool isDiff = IsDiff(); + const UInt32 blockSize = (UInt32)1 << Meta.BlockSize_Log; + { + const UInt64 up = Meta.VirtualDiskSize + blockSize - 1; + if (up < Meta.VirtualDiskSize) + return false; + const UInt64 numDataBlocks = up >> Meta.BlockSize_Log; + + if (isDiff) + { + // differencing table must be finished with bitmap entry + const UInt64 numBitmaps = (numDataBlocks + ChunkRatio - 1) >> ChunkRatio_Log; + totalBatEntries64 = numBitmaps * (ChunkRatio + 1); + } + else + { + // we don't need last Bitmap entry + totalBatEntries64 = numDataBlocks + ((numDataBlocks - 1) >> ChunkRatio_Log); + } + } + + if (totalBatEntries64 > Bat.Data.Size() / 8) + return false; + + const size_t totalBatEntries = (size_t)totalBatEntries64; + TotalBatEntries = totalBatEntries; + + bool isCont = (!isDiff && Meta.Is_LeaveBlockAllocated()); + UInt64 prevBlockOffset = 0; + UInt64 maxBlockOffset = 0; + + size_t remEntries = ChunkRatio + 1; + + size_t i; + for (i = 0; i < totalBatEntries; i++) + { + const UInt64 v = Bat.GetItem(i); + if ((v & 0xFFFF8) != 0) + return false; + const UInt64 offset = BAT_GET_OFFSET(v); + const unsigned state = BAT_GET_STATE(v); + + /* + UInt64 index64 = v >> 20; + printf("\n%7d", i); + printf("%10d, ", (unsigned)index64); + printf("%4x, ", (unsigned)state); + */ + + remEntries--; + if (remEntries == 0) + { + // printf(" ========"); + // printf("\n"); + remEntries = ChunkRatio + 1; + if (state == SB_BLOCK_PRESENT) + { + isCont = false; + if (!isDiff) + return false; + if (offset == 0) + return false; + const UInt64 lim = offset + kBitmapSize; + if (lim < offset) + return false; + if (_phySize < lim) + _phySize = lim; + NumUsedBitMaps++; + } + else if (state != SB_BLOCK_NOT_PRESENT) + return false; + } + else + { + if (state == PAYLOAD_BLOCK_FULLY_PRESENT + || state == PAYLOAD_BLOCK_PARTIALLY_PRESENT) + { + if (offset == 0) + return false; + if (maxBlockOffset < offset) + maxBlockOffset = offset; + + if (state == PAYLOAD_BLOCK_PARTIALLY_PRESENT) + { + isCont = false; + if (!isDiff) + return false; + } + else if (isCont) + { + if (prevBlockOffset != 0 && prevBlockOffset + blockSize != offset) + isCont = false; + else + prevBlockOffset = offset; + } + + NumUsedBlocks++; + } + else if (state == PAYLOAD_BLOCK_UNMAPPED) + { + isCont = false; + // non-empty (offset) is allowed + } + else if (state == PAYLOAD_BLOCK_NOT_PRESENT + || state == PAYLOAD_BLOCK_UNDEFINED + || state == PAYLOAD_BLOCK_ZERO) + { + isCont = false; + /* (offset) is reserved and (offset == 0) is expected here, + but we ignore (offset) here */ + // if (offset != 0) return false; + } + else + return false; + } + } + + _isDataContiguous = isCont; + + if (maxBlockOffset != 0) + { + const UInt64 lim = maxBlockOffset + blockSize; + if (lim < maxBlockOffset) + return false; + if (_phySize < lim) + _phySize = lim; + const UInt64 kPhyLimit = (UInt64)1 << 62; + if (maxBlockOffset >= kPhyLimit) + return false; + } + return true; +} + + +bool CHandler::CheckBat() +{ + const UInt64 upSize = _phySize + kBitmapSize * 8 - 1; + if (upSize < _phySize) + return false; + const UInt64 useMapSize64 = upSize >> (kBitmapSize_Log + 3); + const size_t useMapSize = (size_t)useMapSize64; + + const UInt32 blockSizeMB = (UInt32)1 << (Meta.BlockSize_Log - kBitmapSize_Log); + + // we don't check useMap, if it's too big. + if (useMapSize != useMapSize64) + return true; + if (useMapSize == 0 || useMapSize > ((size_t)1 << 28)) + return true; + + CByteArr useMap; + useMap.Alloc(useMapSize); + memset(useMap, 0, useMapSize); + // useMap[0] = (Byte)(1 << 0); // first 1 MB is used by headers + // we can also update useMap for log, and region data. + + const size_t totalBatEntries = TotalBatEntries; + size_t remEntries = ChunkRatio + 1; + + size_t i; + for (i = 0; i < totalBatEntries; i++) + { + const UInt64 v = Bat.GetItem(i); + const UInt64 offset = BAT_GET_OFFSET(v); + const unsigned state = BAT_GET_STATE(v); + const UInt64 index = offset >> kBitmapSize_Log; + UInt32 numBlocks = 1; + remEntries--; + if (remEntries == 0) + { + remEntries = ChunkRatio + 1; + if (state != SB_BLOCK_PRESENT) + continue; + } + else + { + if (state != PAYLOAD_BLOCK_FULLY_PRESENT && + state != PAYLOAD_BLOCK_PARTIALLY_PRESENT) + continue; + numBlocks = blockSizeMB; + } + + for (unsigned k = 0; k < numBlocks; k++) + { + const UInt64 index2 = index + k; + const unsigned flag = (unsigned)1 << ((unsigned)index2 & 7); + const size_t byteIndex = (size_t)(index2 >> 3); + if (byteIndex >= useMapSize) + return false; + const unsigned m = useMap[byteIndex]; + if (m & flag) + return false; + useMap[byteIndex] = (Byte)(m | flag); + } + } + + /* + UInt64 num = 0; + for (i = 0; i < useMapSize; i++) + { + Byte b = useMap[i]; + unsigned t = 0; + t += (b & 1); b >>= 1; + t += (b & 1); b >>= 1; + t += (b & 1); b >>= 1; + t += (b & 1); b >>= 1; + t += (b & 1); b >>= 1; + t += (b & 1); b >>= 1; + t += (b & 1); b >>= 1; + t += (b & 1); + num += t; + } + NumUsed_1MB_Blocks = num; + NumUsed_1MB_Blocks_Defined = true; + */ + + return true; +} + + + +HRESULT CHandler::Open3() +{ + { + const unsigned kHeaderSize = 512; // + 8 + Byte header[kHeaderSize]; + + RINOK(Read_FALSE(header, kHeaderSize)) + + if (memcmp(header, kSignature, kSignatureSize) != 0) + return S_FALSE; + + const Byte *p = &header[0]; + for (unsigned i = kSignatureSize; i < kHeaderSize; i += 2) + { + const wchar_t c = Get16(p + i); + if (c < 0x20 || c > 0x7F) + break; + _creator += c; + } + } + + HeadersSize = (UInt32)1 << 20; + CHeader headers[2]; + { + Byte header[kHeader2Size]; + for (unsigned i = 0; i < 2; i++) + { + RINOK(Seek2((1 << 16) * (1 + i))) + RINOK(Read_FALSE(header, kHeader2Size)) + bool headerIsOK = headers[i].Parse(header); + if (!headerIsOK) + return S_FALSE; + } + } + unsigned mainIndex; + if (headers[0].SequenceNumber > headers[1].SequenceNumber) mainIndex = 0; + else if (headers[0].SequenceNumber < headers[1].SequenceNumber) mainIndex = 1; + else + { + /* Disk2vhd v2.02 can create image with 2 full copies of headers. + It's violation of VHDX specification: + "A header is current if it is the only valid header + or if it is valid and its SequenceNumber field is + greater than the other header's SequenceNumber". + but we support such Disk2vhd archives. */ + if (!headers[0].IsEqualTo(headers[1])) + return S_FALSE; + mainIndex = 0; + } + + const CHeader &h = headers[mainIndex]; + Header = h; + if (h.LogLength != 0) + { + HeadersSize += h.LogLength; + UpdatePhySize(h.LogOffset + h.LogLength); + if (!h.Guids[kHeader_GUID_Index_LogGuid].IsZero()) + { + _nonEmptyLog = true; + AddErrorMessage("non-empty LOG was not replayed"); + /* + if (h.LogVersion != 0) + AddErrorMessage("unknown LogVresion"); + else + { + CByteBuffer log; + RINOK(Seek2(h.LogOffset)); + RINOK(ReadToBuf_FALSE(log, h.LogLength)); + if (!ParseLog(log)) + { + return S_FALSE; + } + } + */ + } + } + CRegion regions[2]; + int correctRegionIndex = -1; + + { + CByteBuffer temp; + temp.Alloc(kRegionSize * 2); + RINOK(Seek2((1 << 16) * 3)) + RINOK(Read_FALSE(temp, kRegionSize * 2)) + unsigned numTables = 1; + if (memcmp(temp, temp + kRegionSize, kRegionSize) != 0) + { + AddErrorMessage("Region tables mismatch"); + numTables = 2; + } + + for (unsigned i = 0; i < numTables; i++) + { + // RINOK(Seek2((1 << 16) * (3 + i))); + // RINOK(Read_FALSE(temp, kRegionSize)); + if (regions[i].Parse(temp)) + { + if (correctRegionIndex < 0) + correctRegionIndex = (int)i; + } + else + { + AddErrorMessage("Incorrect region table"); + } + } + if (correctRegionIndex < 0) + return S_FALSE; + /* + if (!regions[0].IsEqualTo(regions[1])) + return S_FALSE; + */ + } + + // UpdatePhySize((1 << 16) * 5); + UpdatePhySize(1 << 20); + + { + const CRegion ®ion = regions[correctRegionIndex]; + HeadersSize += region.DataSize; + UpdatePhySize(region.EndPos); + { + if (!region.Meta_Defined) + return S_FALSE; + const CRegionEntry &e = region.MetaEntry; + if (e.Len == 0) + return S_FALSE; + { + // static const kMetaTableSize = 1 << 16; + CByteBuffer temp; + { + RINOK(Seek2(e.Offset)) + RINOK(ReadToBuf_FALSE(temp, e.Len)) + } + if (!Meta.Parse(temp, temp.Size())) + return S_FALSE; + } + // UpdatePhySize(e.GetEndPos()); + } + { + if (!region.Bat_Defined) + return S_FALSE; + const CRegionEntry &e = region.BatEntry; + if (e.Len == 0) + return S_FALSE; + // UpdatePhySize(e.GetEndPos()); + { + RINOK(Seek2(e.Offset)) + RINOK(ReadToBuf_FALSE(Bat.Data, e.Len)) + } + if (!ParseBat()) + return S_FALSE; + if (!CheckBat()) + { + AddErrorMessage("BAT overlap"); + // _batOverlap = true; + // return S_FALSE; + } + } + } + + { + // do we need to check "parent_linkage2" also? + FOR_VECTOR (i, Meta.ParentPairs) + { + const CParentPair &pair = Meta.ParentPairs[i]; + if (pair.Key.IsEqualTo("parent_linkage")) + { + _parentGuid_IsDefined = _parentGuid.ParseFromFormatedHexString(pair.Value); + break; + } + } + } + + { + // absolute paths for parent stream can be rejected later in client callback + // the order of check by specification: + const char * const g_ParentKeys[] = + { + "relative_path" // "..\..\path2\sub3\parent.vhdx" + , "volume_path" // "\\?\Volume{26A21BDA-A627-11D7-9931-806E6F6E6963}\path2\sub3\parent.vhdx") + , "absolute_win32_path" // "d:\path2\sub3\parent.vhdx" + }; + for (unsigned i = 0; i < Z7_ARRAY_SIZE(g_ParentKeys); i++) + { + const int index = Meta.FindParentKey(g_ParentKeys[i]); + if (index < 0) + continue; + ParentNames.Add(Meta.ParentPairs[index].Value); + } + } + + if (Meta.Is_HasParent()) + { + if (!Meta.Locator_Defined) + AddErrorMessage("Parent locator is not defined"); + else + { + if (!_parentGuid_IsDefined) + AddErrorMessage("Parent GUID is not defined"); + if (ParentNames.IsEmpty()) + AddErrorMessage("Parent VHDX file name is not defined"); + } + } + else + { + if (Meta.Locator_Defined) + AddErrorMessage("Unexpected parent locator"); + } + + // here we suppose that and locator can be used only with HasParent flag + + // return S_FALSE; + + _size = Meta.VirtualDiskSize; // CHandlerImg + + // _posInArc = 0; + // Reset_PosInArc(); + // RINOK(InStream_SeekToBegin(Stream)) + + return S_OK; +} + + +/* +static UInt32 g_NumCalls = 0; +static UInt32 g_NumCalls2 = 0; +static struct CCounter { ~CCounter() +{ + printf("\nNumCalls = %10u\n", g_NumCalls); + printf("NumCalls2 = %10u\n", g_NumCalls2); +} } g_Counter; +*/ + +Z7_COM7F_IMF(CHandler::Read(void *data, UInt32 size, UInt32 *processedSize)) +{ + // g_NumCalls++; + if (processedSize) + *processedSize = 0; + if (_virtPos >= Meta.VirtualDiskSize) + return S_OK; + { + const UInt64 rem = Meta.VirtualDiskSize - _virtPos; + if (size > rem) + size = (UInt32)rem; + } + if (size == 0) + return S_OK; + const size_t blockIndex = (size_t)(_virtPos >> Meta.BlockSize_Log); + const size_t chunkIndex = blockIndex >> ChunkRatio_Log; + const size_t chunkRatio = (size_t)1 << ChunkRatio_Log; + const size_t blockIndex2 = chunkIndex * (chunkRatio + 1) + (blockIndex & (chunkRatio - 1)); + const UInt64 blockSectVal = Bat.GetItem(blockIndex2); + const UInt64 blockOffset = BAT_GET_OFFSET(blockSectVal); + const UInt32 blockState = BAT_GET_STATE(blockSectVal); + + const UInt32 blockSize = (UInt32)1 << Meta.BlockSize_Log; + const UInt32 offsetInBlock = (UInt32)_virtPos & (blockSize - 1); + size = MyMin(blockSize - offsetInBlock, size); + + bool needParent = false; + bool needRead = false; + + if (blockState == PAYLOAD_BLOCK_FULLY_PRESENT) + needRead = true; + else if (blockState == PAYLOAD_BLOCK_NOT_PRESENT) + { + /* for a differencing VHDX: parent virtual disk SHOULD be + inspected to determine the associated contents (SPECIFICATION). + we suppose that we should not check BitMap. + for fixed or dynamic VHDX files: the block contents are undefined and + can contain arbitrary data (SPECIFICATION). NTFS::pagefile.sys can use such state. */ + if (IsDiff()) + needParent = true; + } + else if (blockState == PAYLOAD_BLOCK_PARTIALLY_PRESENT) + { + // only allowed for differencing VHDX files. + // associated sector bitmap block MUST be valid + if (chunkIndex >= BitMaps.Size()) + return S_FALSE; + // else + { + const CByteBuffer &bitmap = BitMaps[(unsigned)chunkIndex]; + const Byte *p = (const Byte *)bitmap; + if (!p) + return S_FALSE; + // else + { + // g_NumCalls2++; + const UInt64 sectorIndex = _virtPos >> Meta.LogicalSectorSize_Log; + + #define BIT_MAP_UNIT_LOG 3 // it's for small block (4 KB) + // #define BIT_MAP_UNIT_LOG 5 // speed optimization for large blocks (16 KB) + + const size_t offs = (size_t)(sectorIndex >> 3) & + ( + (kBitmapSize - 1) + & ~(((UInt32)1 << (BIT_MAP_UNIT_LOG - 3)) - 1) + ); + + unsigned sector2 = (unsigned)sectorIndex & ((1 << BIT_MAP_UNIT_LOG) - 1); + #if BIT_MAP_UNIT_LOG == 5 + UInt32 v = GetUi32(p + offs) >> sector2; + #else + unsigned v = (unsigned)p[offs] >> sector2; + #endif + // UInt32 v = GetUi32(p + offs) >> sector2; + const UInt32 sectorSize = (UInt32)1 << Meta.LogicalSectorSize_Log; + const UInt32 offsetInSector = (UInt32)_virtPos & (sectorSize - 1); + const unsigned bit = (unsigned)(v & 1); + if (bit) + needRead = true; + else + needParent = true; // zero - from the parent VHDX file + UInt32 rem = sectorSize - offsetInSector; + for (sector2++; sector2 < (1 << BIT_MAP_UNIT_LOG); sector2++) + { + v >>= 1; + if (bit != (v & 1)) + break; + rem += sectorSize; + } + if (size > rem) + size = rem; + } + } + } + + bool needZero = true; + + HRESULT res = S_OK; + + if (needParent) + { + if (!ParentStream) + return S_FALSE; + // if (ParentStream) + { + RINOK(InStream_SeekSet(ParentStream, _virtPos)) + size_t processed = size; + res = ReadStream(ParentStream, (Byte *)data, &processed); + size = (UInt32)processed; + needZero = false; + } + } + else if (needRead) + { + UInt32 processed = 0; + res = ReadPhy(blockOffset + offsetInBlock, data, size, processed); + size = processed; + needZero = false; + } + + if (needZero) + memset(data, 0, size); + + if (processedSize) + *processedSize = size; + + _virtPos += size; + return res; +} + + +enum +{ + kpidParent = kpidUserDefined +}; + +static const CStatProp kArcProps[] = +{ + { NULL, kpidClusterSize, VT_UI4}, + { NULL, kpidSectorSize, VT_UI4}, + { NULL, kpidMethod, VT_BSTR}, + { NULL, kpidNumVolumes, VT_UI4}, + { NULL, kpidTotalPhySize, VT_UI8}, + { "Parent", kpidParent, VT_BSTR}, + { NULL, kpidCreatorApp, VT_BSTR}, + { NULL, kpidComment, VT_BSTR}, + { NULL, kpidId, VT_BSTR} + }; + +static const Byte kProps[] = +{ + kpidSize, + kpidPackSize +}; + +IMP_IInArchive_Props +IMP_IInArchive_ArcProps_WITH_NAME + + +void CHandler::AddErrorMessage(const char *message) +{ + if (!_errorMessage.IsEmpty()) + _errorMessage.Add_LF(); + _errorMessage += message; +} + +void CHandler::AddErrorMessage(const char *message, const wchar_t *name) +{ + AddErrorMessage(message); + _errorMessage += name; +} + + +static void AddComment_Name(UString &s, const char *name) +{ + s += name; + s += ": "; +} + +static void AddComment_Bool(UString &s, const char *name, bool val) +{ + AddComment_Name(s, name); + s.Add_Char(val ? '+' : '-'); + s.Add_LF(); +} + +static void AddComment_UInt64(UString &s, const char *name, UInt64 v, bool showMB = false) +{ + AddComment_Name(s, name); + s.Add_UInt64(v); + if (showMB) + { + s += " ("; + s.Add_UInt64(v >> 20); + s += " MiB)"; + } + s.Add_LF(); +} + +static void AddComment_BlockSize(UString &s, const char *name, unsigned logSize) +{ + if (logSize != 0) + AddComment_UInt64(s, name, ((UInt64)1 << logSize)); +} + + +void CHandler::AddComment(UString &s) const +{ + AddComment_UInt64(s, "VirtualDiskSize", Meta.VirtualDiskSize); + AddComment_UInt64(s, "PhysicalSize", _phySize); + + if (!_errorMessage.IsEmpty()) + { + AddComment_Name(s, "Error"); + s += _errorMessage; + s.Add_LF(); + } + + if (Meta.Guid_Defined) + { + AddComment_Name(s, "Id"); + Meta.Guid.AddHexToString(s); + s.Add_LF(); + } + + AddComment_UInt64(s, "SequenceNumber", Header.SequenceNumber); + AddComment_UInt64(s, "LogLength", Header.LogLength, true); + + for (unsigned i = 0; i < 3; i++) + { + const CGuid &g = Header.Guids[i]; + if (g.IsZero()) + continue; + if (i == 0) + s += "FileWrite"; + else if (i == 1) + s += "DataWrite"; + else + s += "Log"; + AddComment_Name(s, "Guid"); + g.AddHexToString(s); + s.Add_LF(); + } + + AddComment_Bool(s, "HasParent", Meta.Is_HasParent()); + AddComment_Bool(s, "Fixed", Meta.Is_LeaveBlockAllocated()); + if (Meta.Is_LeaveBlockAllocated()) + AddComment_Bool(s, "DataContiguous", _isDataContiguous); + + AddComment_BlockSize(s, "BlockSize", Meta.BlockSize_Log); + AddComment_BlockSize(s, "LogicalSectorSize", Meta.LogicalSectorSize_Log); + AddComment_BlockSize(s, "PhysicalSectorSize", Meta.PhysicalSectorSize_Log); + + { + const UInt64 packSize = GetPackSize(); + AddComment_UInt64(s, "PackSize", packSize, true); + const UInt64 headersSize = HeadersSize + ((UInt64)NumUsedBitMaps << kBitmapSize_Log); + AddComment_UInt64(s, "HeadersSize", headersSize, true); + AddComment_UInt64(s, "FreeSpace", _phySize - packSize - headersSize, true); + /* + if (NumUsed_1MB_Blocks_Defined) + AddComment_UInt64(s, "used2", (NumUsed_1MB_Blocks << 20)); + */ + } + + if (Meta.ParentPairs.Size() != 0) + { + s += "Parent:"; + s.Add_LF(); + FOR_VECTOR(i, Meta.ParentPairs) + { + const CParentPair &pair = Meta.ParentPairs[i]; + s += " "; + s += pair.Key; + s += ": "; + s += pair.Value; + s.Add_LF(); + } + s.Add_LF(); + } +} + + + +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) +{ + COM_TRY_BEGIN + NCOM::CPropVariant prop; + switch (propID) + { + case kpidMainSubfile: prop = (UInt32)0; break; + case kpidClusterSize: prop = (UInt32)1 << Meta.BlockSize_Log; break; + case kpidSectorSize: prop = (UInt32)1 << Meta.LogicalSectorSize_Log; break; + case kpidShortComment: + case kpidMethod: + { + AString s; + AddTypeString(s); + if (IsDiff()) + { + s += " -> "; + const CHandler *p = this; + while (p && p->IsDiff()) + p = p->Parent; + if (!p) + s += '?'; + else + p->AddTypeString(s); + } + prop = s; + break; + } + case kpidComment: + { + UString s; + { + if (NumLevels > 1) + { + AddComment_UInt64(s, "NumVolumeLevels", NumLevels); + AddComment_UInt64(s, "PackSizeTotal", PackSize_Total, true); + s += "----"; + s.Add_LF(); + } + + const CHandler *p = this; + for (;;) + { + if (p->_level != 0 || p->Parent) + AddComment_UInt64(s, "VolumeLevel", p->_level + 1); + p->AddComment(s); + if (!p->Parent) + break; + s += "----"; + s.Add_LF(); + { + s.Add_LF(); + if (!p->ParentName_Used.IsEmpty()) + { + AddComment_Name(s, "Name"); + s += p->ParentName_Used; + s.Add_LF(); + } + } + p = p->Parent; + } + } + prop = s; + break; + } + case kpidCreatorApp: + { + if (!_creator.IsEmpty()) + prop = _creator; + break; + } + case kpidId: + { + if (Meta.Guid_Defined) + { + UString s; + Meta.Guid.AddHexToString(s); + prop = s; + } + break; + } + case kpidName: + { + if (Meta.Guid_Defined) + { + UString s; + Meta.Guid.AddHexToString(s); + s += ".vhdx"; + prop = s; + } + break; + } + case kpidParent: if (IsDiff()) prop = GetParentSequence(); break; + case kpidPhySize: prop = _phySize; break; + case kpidTotalPhySize: + { + const CHandler *p = this; + UInt64 sum = 0; + do + { + sum += p->_phySize; + p = p->Parent; + } + while (p); + prop = sum; + break; + } + case kpidNumVolumes: if (NumLevels != 1) prop = (UInt32)NumLevels; break; + case kpidError: + { + UString s; + const CHandler *p = this; + do + { + if (!p->_errorMessage.IsEmpty()) + { + if (!s.IsEmpty()) + s.Add_LF(); + s += p->_errorMessage; + } + p = p->Parent; + } + while (p); + if (!s.IsEmpty()) + prop = s; + break; + } + } + prop.Detach(value); + return S_OK; + COM_TRY_END +} + + +HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *openArchiveCallback) +{ + Stream = stream; + if (_level >= (1 << 20)) + return S_FALSE; + + RINOK(Open3()) + + NumLevels = 1; + PackSize_Total = GetPackSize(); + + if (_child) + { + if (!_child->_parentGuid.IsEqualTo(Header.Guids[kHeader_GUID_Index_DataWriteGuid])) + return S_FALSE; + const CHandler *child = _child; + do + { + /* We suppose that only FileWriteGuid is unique. + Another IDs must be identical in in difference and parent archives. */ + if (Header.Guids[kHeader_GUID_Index_FileWriteGuid].IsEqualTo( + child->Header.Guids[kHeader_GUID_Index_FileWriteGuid]) + && _phySize == child->_phySize) + { + _isCyclic = true; + _isCyclic_or_CyclicParent = true; + AddErrorMessage("Cyclic parent archive was blocked"); + return S_OK; + } + child = child->_child; + } + while (child); + } + + if (!Meta.Is_HasParent()) + return S_OK; + + if (!Meta.Locator_Defined + || !_parentGuid_IsDefined + || ParentNames.IsEmpty()) + { + return S_OK; + } + + ParentName_Used = ParentNames.Front(); + + HRESULT res; + const unsigned kNumLevelsMax = (1 << 8); // Maybe we need to increase that limit + if (_level >= kNumLevelsMax - 1) + { + AddErrorMessage("Too many parent levels"); + return S_OK; + } + + bool _parentFileWasOpen = false; + + if (!openArchiveCallback) + res = S_FALSE; + else + res = OpenParent(openArchiveCallback, _parentFileWasOpen); + + if (res != S_OK) + { + if (res != S_FALSE) + return res; + + if (_parentFileWasOpen) + AddErrorMessage("Can't parse parent VHDX file : ", ParentName_Used); + else + AddErrorMessage("Missing parent VHDX file : ", ParentName_Used); + } + + + return S_OK; +} + + +HRESULT CHandler::OpenParent(IArchiveOpenCallback *openArchiveCallback, bool &_parentFileWasOpen) +{ + _parentFileWasOpen = false; + Z7_DECL_CMyComPtr_QI_FROM( + IArchiveOpenVolumeCallback, + openVolumeCallback, openArchiveCallback) + if (!openVolumeCallback) + return S_FALSE; + { + CMyComPtr nextStream; + HRESULT res = S_FALSE; + UString name; + + FOR_VECTOR (i, ParentNames) + { + name = ParentNames[i]; + + // we remove prefix ".\\', but client already can support any variant + if (name[0] == L'.' && name[1] == L'\\') + name.DeleteFrontal(2); + + res = openVolumeCallback->GetStream(name, &nextStream); + + if (res == S_OK && nextStream) + break; + + if (res != S_OK && res != S_FALSE) + return res; + } + + if (res == S_FALSE || !nextStream) + return S_FALSE; + + ParentName_Used = name; + _parentFileWasOpen = true; + + Parent = new CHandler; + ParentStream = Parent; + + try + { + Parent->_level = _level + 1; + Parent->_child = this; + /* we could call CHandlerImg::Open() here. + but we don't need (_imgExt) in (Parent). So we call Open2() here */ + Parent->Close(); + res = Parent->Open2(nextStream, openArchiveCallback); + } + catch(...) + { + Parent = NULL; + ParentStream.Release(); + res = S_FALSE; + throw; + } + + if (res != S_OK) + { + Parent = NULL; + ParentStream.Release(); + if (res == E_ABORT) + return res; + if (res != S_FALSE) + { + // we must show that error code + } + } + + if (res == S_OK) + { + if (Parent->_isCyclic_or_CyclicParent) + _isCyclic_or_CyclicParent = true; + + NumLevels = Parent->NumLevels + 1; + PackSize_Total += Parent->GetPackSize(); + + // we read BitMaps only if Parent was open + + UInt64 numBytes = (UInt64)NumUsedBitMaps << kBitmapSize_Log; + if (openArchiveCallback && numBytes != 0) + { + RINOK(openArchiveCallback->SetTotal(NULL, &numBytes)) + } + numBytes = 0; + for (size_t i = ChunkRatio; i < TotalBatEntries; i += ChunkRatio + 1) + { + const UInt64 v = Bat.GetItem(i); + const UInt64 offset = BAT_GET_OFFSET(v); + const unsigned state = BAT_GET_STATE(v); + + CByteBuffer &buf = BitMaps.AddNew(); + if (state == SB_BLOCK_PRESENT) + { + if (openArchiveCallback) + { + RINOK(openArchiveCallback->SetCompleted(NULL, &numBytes)) + } + numBytes += kBitmapSize; + buf.Alloc(kBitmapSize); + RINOK(Seek2(offset)) + RINOK(Read_FALSE(buf, kBitmapSize)) + /* + for (unsigned i = 0; i < (1 << 20); i+=4) + { + UInt32 v = GetUi32(buf + i); + if (v != 0 && v != (UInt32)(Int32)-1) + printf("\n%7d %8x", i, v); + } + */ + } + } + } + } + + return S_OK; +} + + +void CHandler::CloseAtError() +{ + // CHandlerImg + Clear_HandlerImg_Vars(); + Stream.Release(); + + _phySize = 0; + Bat.Clear(); + BitMaps.Clear(); + NumUsedBlocks = 0; + NumUsedBitMaps = 0; + HeadersSize = 0; + /* + NumUsed_1MB_Blocks = 0; + NumUsed_1MB_Blocks_Defined = false; + */ + + Parent = NULL; + ParentStream.Release(); + _errorMessage.Empty(); + _creator.Empty(); + _nonEmptyLog = false; + _parentGuid_IsDefined = false; + _isDataContiguous = false; + // _batOverlap = false; + + ParentNames.Clear(); + ParentName_Used.Empty(); + + Meta.Clear(); + + ChunkRatio_Log = 0; + ChunkRatio = 0; + TotalBatEntries = 0; + NumLevels = 0; + PackSize_Total = 0; + + _isCyclic = false; + _isCyclic_or_CyclicParent = false; +} + +Z7_COM7F_IMF(CHandler::Close()) +{ + CloseAtError(); + return S_OK; +} + + +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIANT *value)) +{ + COM_TRY_BEGIN + NCOM::CPropVariant prop; + + switch (propID) + { + case kpidSize: prop = Meta.VirtualDiskSize; break; + case kpidPackSize: prop = PackSize_Total; break; + case kpidExtension: prop = (_imgExt ? _imgExt : "img"); break; + } + + prop.Detach(value); + return S_OK; + COM_TRY_END +} + + +Z7_COM7F_IMF(CHandler::GetStream(UInt32 /* index */, ISequentialInStream **stream)) +{ + COM_TRY_BEGIN + *stream = NULL; + // if some prarent is not OK, we don't create stream + if (!AreParentsOK()) + return S_FALSE; + InitSeekPositions(); + CMyComPtr streamTemp = this; + *stream = streamTemp.Detach(); + return S_OK; + COM_TRY_END +} + +REGISTER_ARC_I( + "VHDX", "vhdx avhdx", NULL, 0xc4, + kSignature, + 0, + 0, + NULL) + +}} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/VmdkHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/VmdkHandler.cpp index 5f93c738..221af21f 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/VmdkHandler.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/VmdkHandler.cpp @@ -31,17 +31,15 @@ namespace NVmdk { #define Get32(p) GetUi32(p) #define Get64(p) GetUi64(p) -#define LE_16(offs, dest) dest = Get16(p + (offs)); -#define LE_32(offs, dest) dest = Get32(p + (offs)); -#define LE_64(offs, dest) dest = Get64(p + (offs)); +#define LE_16(offs, dest) dest = Get16(p + (offs)) +#define LE_32(offs, dest) dest = Get32(p + (offs)) +#define LE_64(offs, dest) dest = Get64(p + (offs)) -#define SIGNATURE { 'K', 'D', 'M', 'V' } - -static const Byte k_Signature[] = SIGNATURE; +static const Byte k_Signature[] = { 'K', 'D', 'M', 'V' }; static const UInt32 k_Flags_NL = (UInt32)1 << 0; -static const UInt32 k_Flags_RGD = (UInt32)1 << 1; +// static const UInt32 k_Flags_RGD = (UInt32)1 << 1; static const UInt32 k_Flags_ZeroGrain = (UInt32)1 << 2; static const UInt32 k_Flags_Compressed = (UInt32)1 << 16; static const UInt32 k_Flags_Marker = (UInt32)1 << 17; @@ -65,10 +63,10 @@ struct CHeader UInt64 gdOffset; UInt64 overHead; - bool Is_NL() const { return (flags & k_Flags_NL) != 0; }; - bool Is_ZeroGrain() const { return (flags & k_Flags_ZeroGrain) != 0; }; - bool Is_Compressed() const { return (flags & k_Flags_Compressed) != 0; }; - bool Is_Marker() const { return (flags & k_Flags_Marker) != 0; }; + bool Is_NL() const { return (flags & k_Flags_NL) != 0; } + bool Is_ZeroGrain() const { return (flags & k_Flags_ZeroGrain) != 0; } + bool Is_Compressed() const { return (flags & k_Flags_Compressed) != 0; } + bool Is_Marker() const { return (flags & k_Flags_Marker) != 0; } bool Parse(const Byte *p); @@ -138,7 +136,7 @@ static bool Str_to_ValName(const AString &s, AString &name, AString &val) int eq = s.Find('='); if (eq < 0 || (qu >= 0 && eq > qu)) return false; - name = s.Left(eq); + name.SetFrom(s.Ptr(), eq); name.Trim(); val = s.Ptr(eq + 1); val.Trim(); @@ -165,7 +163,7 @@ static const char *SkipSpaces(const char *s) static const char *GetNextWord(const char *s, AString &dest) { dest.Empty(); - SKIP_SPACES(s); + SKIP_SPACES(s) const char *start = s; for (;; s++) { @@ -180,7 +178,7 @@ static const char *GetNextWord(const char *s, AString &dest) static const char *GetNextNumber(const char *s, UInt64 &val) { - SKIP_SPACES(s); + SKIP_SPACES(s) if (*s == 0) return s; const char *end; @@ -204,9 +202,12 @@ struct CExtentInfo // PartitionUUID // DeviceIdentifier - bool IsType_ZERO() const { return Type == "ZERO"; } - // bool IsType_FLAT() const { return Type == "FLAT"; } - bool IsType_Flat() const { return Type == "FLAT" || Type == "VMFS" || Type == "VMFSRAW"; } + bool IsType_ZERO() const { return Type.IsEqualTo("ZERO"); } + // bool IsType_FLAT() const { return Type.IsEqualTo("FLAT"); } + bool IsType_Flat() const + { return Type.IsEqualTo("FLAT") + || Type.IsEqualTo("VMFS") + || Type.IsEqualTo("VMFSRAW"); } bool Parse(const char *s); }; @@ -228,7 +229,7 @@ bool CExtentInfo::Parse(const char *s) if (Type.IsEmpty()) return false; - SKIP_SPACES(s); + SKIP_SPACES(s) if (IsType_ZERO()) return (*s == 0); @@ -243,7 +244,7 @@ bool CExtentInfo::Parse(const char *s) FileName.SetFrom(s, (unsigned)(s2 - s)); s = s2 + 1; } - SKIP_SPACES(s); + SKIP_SPACES(s) if (*s == 0) return true; @@ -296,10 +297,15 @@ bool CDescriptor::Parse(const Byte *p, size_t size) AString name; AString val; - for (size_t i = 0;; i++) + for (;;) { - const char c = p[i]; - if (i == size || c == 0 || c == 0xA || c == 0xD) + Byte c = 0; + if (size != 0) + { + size--; + c = *p++; + } + if (c == 0 || c == 0xA || c == 0xD) { if (!s.IsEmpty() && s[0] != '#') { @@ -322,14 +328,12 @@ bool CDescriptor::Parse(const Byte *p, size_t size) } s.Empty(); - if (c == 0 || i >= size) - break; + if (c == 0) + return true; } else s += (char)c; } - - return true; } @@ -366,7 +370,7 @@ struct CExtent UInt64 GetEndOffset() const { return StartOffset + NumBytes; } - bool IsVmdk() const { return !IsZero && !IsFlat; }; + bool IsVmdk() const { return !IsZero && !IsFlat; } // if (IsOK && IsVmdk()), then VMDK header of this extent was read CExtent(): @@ -400,7 +404,7 @@ struct CExtent HRESULT Seek(UInt64 offset) { PosInArc = offset; - return Stream->Seek(offset, STREAM_SEEK_SET, NULL); + return InStream_SeekSet(Stream, offset); } HRESULT InitAndSeek() @@ -419,7 +423,7 @@ struct CExtent }; -class CHandler: public CHandlerImg +Z7_class_CHandler_final: public CHandlerImg { bool _isArc; bool _unsupported; @@ -458,17 +462,17 @@ class CHandler: public CHandlerImg _virtPos = 0; } - virtual HRESULT Open2(IInStream *stream, IArchiveOpenCallback *openCallback); - virtual void CloseAtError(); + virtual HRESULT Open2(IInStream *stream, IArchiveOpenCallback *openCallback) Z7_override; + virtual void CloseAtError() Z7_override; public: - INTERFACE_IInArchive_Img(;) + Z7_IFACE_COM7_IMP(IInArchive_Img) - STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream); - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); + Z7_IFACE_COM7_IMP(IInArchiveGetStream) + Z7_IFACE_COM7_IMP(ISequentialInStream) }; -STDMETHODIMP CHandler::Read(void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CHandler::Read(void *data, UInt32 size, UInt32 *processedSize)) { if (processedSize) *processedSize = 0; @@ -566,7 +570,7 @@ STDMETHODIMP CHandler::Read(void *data, UInt32 size, UInt32 *processedSize) UInt64 offset = extent.FlatOffset + vir; if (offset != extent.PosInArc) { - RINOK(extent.Seek(offset)); + RINOK(extent.Seek(offset)) } UInt32 size2 = 0; HRESULT res = extent.Stream->Read(data, size, &size2); @@ -633,13 +637,13 @@ STDMETHODIMP CHandler::Read(void *data, UInt32 size, UInt32 *processedSize) if (offset != extent.PosInArc) { // printf("\n%12x %12x\n", (unsigned)offset, (unsigned)(offset - extent.PosInArc)); - RINOK(extent.Seek(offset)); + RINOK(extent.Seek(offset)) } const size_t kStartSize = 1 << 9; { size_t curSize = kStartSize; - RINOK(extent.Read(_cacheCompressed, &curSize)); + RINOK(extent.Read(_cacheCompressed, &curSize)) // _stream_PackSize += curSize; if (curSize != kStartSize) return S_FALSE; @@ -661,7 +665,7 @@ STDMETHODIMP CHandler::Read(void *data, UInt32 size, UInt32 *processedSize) return S_FALSE; size_t curSize = dataSize2 - kStartSize; const size_t curSize2 = curSize; - RINOK(extent.Read(_cacheCompressed + kStartSize, &curSize)); + RINOK(extent.Read(_cacheCompressed + kStartSize, &curSize)) // _stream_PackSize += curSize; if (curSize != curSize2) return S_FALSE; @@ -677,8 +681,8 @@ STDMETHODIMP CHandler::Read(void *data, UInt32 size, UInt32 *processedSize) _bufOutStreamSpec->Init(_cache, clusterSize); // Do we need to use smaller block than clusterSize for last cluster? - UInt64 blockSize64 = clusterSize; - HRESULT res = _zlibDecoderSpec->Code(_bufInStream, _bufOutStream, NULL, &blockSize64, NULL); + const UInt64 blockSize64 = clusterSize; + HRESULT res = _zlibDecoder->Code(_bufInStream, _bufOutStream, NULL, &blockSize64, NULL); /* if (_bufOutStreamSpec->GetPos() != clusterSize) @@ -696,7 +700,7 @@ STDMETHODIMP CHandler::Read(void *data, UInt32 size, UInt32 *processedSize) res = S_FALSE; } - RINOK(res); + RINOK(res) _cacheCluster = cluster; _cacheExtent = extentIndex; @@ -715,7 +719,7 @@ STDMETHODIMP CHandler::Read(void *data, UInt32 size, UInt32 *processedSize) if (offset != extent.PosInArc) { // printf("\n%12x %12x\n", (unsigned)offset, (unsigned)(offset - extent.PosInArc)); - RINOK(extent.Seek(offset)); + RINOK(extent.Seek(offset)) } UInt32 size2 = 0; HRESULT res = extent.Stream->Read(data, size, &size2); @@ -759,6 +763,7 @@ static const Byte kProps[] = static const Byte kArcProps[] = { kpidNumVolumes, + kpidTotalPhySize, kpidMethod, kpidClusterSize, kpidHeadersSize, @@ -771,7 +776,7 @@ IMP_IInArchive_Props IMP_IInArchive_ArcProps -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; @@ -791,6 +796,17 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) { case kpidMainSubfile: prop = (UInt32)0; break; case kpidPhySize: if (_phySize != 0) prop = _phySize; break; + case kpidTotalPhySize: + { + UInt64 sum = _phySize; + if (_isMultiVol) + { + FOR_VECTOR (i, _extents) + sum += _extents[i].PhySize; + } + prop = sum; + break; + } case kpidClusterSize: prop = (UInt32)((UInt32)1 << _clusterBitsMax); break; case kpidHeadersSize: if (e) prop = (e->h.overHead << 9); break; case kpidMethod: @@ -802,7 +818,7 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) bool zlib = false; bool marker = false; - int algo = -1; + Int32 algo = -1; FOR_VECTOR (i, _extents) { @@ -816,12 +832,10 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) { if (h.algo == 1) zlib = true; - else if (algo != (int)h.algo) + else if (algo != h.algo) { s.Add_Space_if_NotEmpty(); - char temp[16]; - ConvertUInt32ToString(h.algo, temp); - s += temp; + s.Add_UInt32(h.algo); algo = h.algo; } } @@ -831,16 +845,10 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) } if (zlib) - { - s.Add_Space_if_NotEmpty(); - s += "zlib"; - } + s.Add_OptSpaced("zlib"); if (marker) - { - s.Add_Space_if_NotEmpty(); - s += "Marker"; - } + s.Add_OptSpaced("Marker"); if (!s.IsEmpty()) prop = s; @@ -860,11 +868,13 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) } case kpidId: + { if (desc && !desc->CID.IsEmpty()) { prop = desc->CID; - break; } + break; + } case kpidName: { @@ -888,8 +898,7 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) { if (_missingVol || !_missingVolName.IsEmpty()) { - UString s; - s.SetFromAscii("Missing volume : "); + UString s ("Missing volume : "); if (!_missingVolName.IsEmpty()) s += _missingVolName; prop = s; @@ -900,7 +909,7 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) case kpidErrorFlags: { UInt32 v = 0; - if (!_isArc) v |= kpv_ErrorFlags_IsNotArc;; + if (!_isArc) v |= kpv_ErrorFlags_IsNotArc; if (_unsupported) v |= kpv_ErrorFlags_UnsupportedMethod; if (_unsupportedSome) v |= kpv_ErrorFlags_UnsupportedMethod; if (_headerError) v |= kpv_ErrorFlags_HeadersError; @@ -917,7 +926,7 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) } -STDMETHODIMP CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; @@ -966,9 +975,9 @@ static int inline GetLog(UInt64 num) HRESULT CExtent::ReadForHeader(IInStream *stream, UInt64 sector, void *data, size_t numSectors) { sector <<= 9; - RINOK(stream->Seek(sector, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekSet(stream, sector)) size_t size = numSectors << 9; - RINOK(ReadStream_FALSE(stream, data, size)); + RINOK(ReadStream_FALSE(stream, data, size)) UInt64 end = sector + size; if (PhySize < end) PhySize = end; @@ -983,12 +992,15 @@ void CHandler::CloseAtError() } +static const char * const kSignature_Descriptor = "# Disk DescriptorFile"; + + HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *openCallback) { const unsigned kSectoreSize = 512; Byte buf[kSectoreSize]; size_t headerSize = kSectoreSize; - RINOK(ReadStream(stream, buf, &headerSize)); + RINOK(ReadStream(stream, buf, &headerSize)) if (headerSize < sizeof(k_Signature)) return S_FALSE; @@ -997,7 +1009,6 @@ HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *openCallback) if (memcmp(buf, k_Signature, sizeof(k_Signature)) != 0) { - const char *kSignature_Descriptor = "# Disk DescriptorFile"; const size_t k_SigDesc_Size = strlen(kSignature_Descriptor); if (headerSize < k_SigDesc_Size) return S_FALSE; @@ -1005,13 +1016,13 @@ HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *openCallback) return S_FALSE; UInt64 endPos; - RINOK(stream->Seek(0, STREAM_SEEK_END, &endPos)); + RINOK(InStream_GetSize_SeekToEnd(stream, endPos)) if (endPos > (1 << 20)) return S_FALSE; const size_t numBytes = (size_t)endPos; _descriptorBuf.Alloc(numBytes); - RINOK(stream->Seek(0, STREAM_SEEK_SET, NULL)); - RINOK(ReadStream_FALSE(stream, _descriptorBuf, numBytes)); + RINOK(InStream_SeekToBegin(stream)) + RINOK(ReadStream_FALSE(stream, _descriptorBuf, numBytes)) if (!_descriptor.Parse(_descriptorBuf, _descriptorBuf.Size())) return S_FALSE; @@ -1048,7 +1059,7 @@ HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *openCallback) if (_descriptor.Extents.Size() > 1) { const UInt64 numFiles = _descriptor.Extents.Size(); - RINOK(openCallback->SetTotal(&numFiles, NULL)); + RINOK(openCallback->SetTotal(&numFiles, NULL)) } } @@ -1119,7 +1130,7 @@ HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *openCallback) stream = nextStream; headerSize = kSectoreSize; - RINOK(ReadStream(stream, buf, &headerSize)); + RINOK(ReadStream(stream, buf, &headerSize)) if (headerSize != kSectoreSize) continue; @@ -1178,7 +1189,7 @@ HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *openCallback) _needDeflate = false; _clusterBitsMax = 0; - unsigned numOKs = 0; + // unsigned numOKs = 0; unsigned numUnsupported = 0; FOR_VECTOR (i, _extents) @@ -1188,7 +1199,7 @@ HRESULT CHandler::Open2(IInStream *stream, IArchiveOpenCallback *openCallback) numUnsupported++; if (!e.IsOK) continue; - numOKs++; + // numOKs++; if (e.IsVmdk()) { if (e.NeedDeflate) @@ -1214,7 +1225,7 @@ HRESULT CExtent::Open3(IInStream *stream, IArchiveOpenCallback *openCallback, h.descriptorSize > (1 << 10)) return S_FALSE; DescriptorBuf.Alloc((size_t)h.descriptorSize << 9); - RINOK(ReadForHeader(stream, h.descriptorOffset, DescriptorBuf, (size_t)h.descriptorSize)); + RINOK(ReadForHeader(stream, h.descriptorOffset, DescriptorBuf, (size_t)h.descriptorSize)) if (h.descriptorOffset == 1 && h.Is_Marker() && Get64(DescriptorBuf) == 0) { // We check data as end marker. @@ -1233,7 +1244,7 @@ HRESULT CExtent::Open3(IInStream *stream, IArchiveOpenCallback *openCallback, { // Grain Dir is at end of file UInt64 endPos; - RINOK(stream->Seek(0, STREAM_SEEK_END, &endPos)); + RINOK(InStream_GetSize_SeekToEnd(stream, endPos)) if ((endPos & 511) != 0) return S_FALSE; @@ -1241,8 +1252,8 @@ HRESULT CExtent::Open3(IInStream *stream, IArchiveOpenCallback *openCallback, Byte buf2[kEndSize]; if (endPos < kEndSize) return S_FALSE; - RINOK(stream->Seek(endPos - kEndSize, STREAM_SEEK_SET, NULL)); - RINOK(ReadStream_FALSE(stream, buf2, kEndSize)); + RINOK(InStream_SeekSet(stream, endPos - kEndSize)) + RINOK(ReadStream_FALSE(stream, buf2, kEndSize)) CHeader h2; if (!h2.Parse(buf2 + 512)) @@ -1262,7 +1273,7 @@ HRESULT CExtent::Open3(IInStream *stream, IArchiveOpenCallback *openCallback, PhySize = endPos; } - int grainSize_Log = GetLog(h.grainSize); + const int grainSize_Log = GetLog(h.grainSize); if (grainSize_Log < 3 || grainSize_Log > 30 - 9) // grain size must be >= 4 KB return S_FALSE; if (h.capacity >= ((UInt64)1 << (63 - 9))) @@ -1271,7 +1282,7 @@ HRESULT CExtent::Open3(IInStream *stream, IArchiveOpenCallback *openCallback, return S_FALSE; IsArc = true; - ClusterBits = (9 + grainSize_Log); + ClusterBits = (9 + (unsigned)grainSize_Log); VirtSize = h.capacity << 9; NeedDeflate = (h.algo >= 1); @@ -1283,7 +1294,7 @@ HRESULT CExtent::Open3(IInStream *stream, IArchiveOpenCallback *openCallback, } { - UInt64 overHeadBytes = h.overHead << 9; + const UInt64 overHeadBytes = h.overHead << 9; if (PhySize < overHeadBytes) PhySize = overHeadBytes; } @@ -1292,8 +1303,8 @@ HRESULT CExtent::Open3(IInStream *stream, IArchiveOpenCallback *openCallback, if (h.Is_ZeroGrain()) ZeroSector = 1; - const UInt64 numSectorsPerGde = (UInt64)1 << (grainSize_Log + k_NumMidBits); - const UInt64 numGdeEntries = (h.capacity + numSectorsPerGde - 1) >> (grainSize_Log + k_NumMidBits); + const UInt64 numSectorsPerGde = (UInt64)1 << ((unsigned)grainSize_Log + k_NumMidBits); + const UInt64 numGdeEntries = (h.capacity + numSectorsPerGde - 1) >> ((unsigned)grainSize_Log + k_NumMidBits); CByteBuffer table; if (numGdeEntries != 0) @@ -1322,7 +1333,7 @@ HRESULT CExtent::Open3(IInStream *stream, IArchiveOpenCallback *openCallback, } } - RINOK(ReadForHeader(stream, h.gdOffset, table, numSectors)); + RINOK(ReadForHeader(stream, h.gdOffset, table, numSectors)) } const size_t clusterSize = (size_t)1 << ClusterBits; @@ -1334,12 +1345,12 @@ HRESULT CExtent::Open3(IInStream *stream, IArchiveOpenCallback *openCallback, complexity += (UInt64)numGdeEntries << (k_NumMidBits + 2); { const UInt64 numVols2 = numVols; - RINOK(openCallback->SetTotal((numVols == 1) ? NULL : &numVols2, &complexity)); + RINOK(openCallback->SetTotal((numVols == 1) ? NULL : &numVols2, &complexity)) } if (numVols != 1) { const UInt64 volIndex2 = volIndex; - RINOK(openCallback->SetCompleted(numVols == 1 ? NULL : &volIndex2, &complexityStart)); + RINOK(openCallback->SetCompleted(numVols == 1 ? NULL : &volIndex2, &complexityStart)) } } @@ -1362,7 +1373,7 @@ HRESULT CExtent::Open3(IInStream *stream, IArchiveOpenCallback *openCallback, { const UInt64 comp = complexityStart + ((UInt64)i << (k_NumMidBits + 2)); const UInt64 volIndex2 = volIndex; - RINOK(openCallback->SetCompleted(numVols == 1 ? NULL : &volIndex2, &comp)); + RINOK(openCallback->SetCompleted(numVols == 1 ? NULL : &volIndex2, &comp)) numProcessed_Prev = i; } @@ -1382,7 +1393,7 @@ HRESULT CExtent::Open3(IInStream *stream, IArchiveOpenCallback *openCallback, } buf.Alloc(k_NumMidItems * 4); - RINOK(ReadForHeader(stream, v, buf, k_NumSectors)); + RINOK(ReadForHeader(stream, v, buf, k_NumSectors)) } for (size_t k = 0; k < k_NumMidItems; k++) @@ -1429,10 +1440,9 @@ HRESULT CExtent::Open3(IInStream *stream, IArchiveOpenCallback *openCallback, } -STDMETHODIMP CHandler::Close() +Z7_COM7F_IMF(CHandler::Close()) { _phySize = 0; - _size = 0; _cacheCluster = (UInt64)(Int64)-1; _cacheExtent = (unsigned)(int)-1; @@ -1452,17 +1462,19 @@ STDMETHODIMP CHandler::Close() _descriptorBuf.Free(); _descriptor.Clear(); - _imgExt = NULL; - Stream.Release(); // Stream vriable is unused + // CHandlerImg: + Clear_HandlerImg_Vars(); + Stream.Release(); + _extents.Clear(); return S_OK; } -STDMETHODIMP CHandler::GetStream(UInt32 /* index */, ISequentialInStream **stream) +Z7_COM7F_IMF(CHandler::GetStream(UInt32 /* index */, ISequentialInStream **stream)) { COM_TRY_BEGIN - *stream = 0; + *stream = NULL; if (_unsupported) return S_FALSE; @@ -1497,7 +1509,7 @@ STDMETHODIMP CHandler::GetStream(UInt32 /* index */, ISequentialInStream **strea FOR_VECTOR (i, _extents) { - RINOK(_extents[i].InitAndSeek()); + RINOK(_extents[i].InitAndSeek()) } CMyComPtr streamTemp = this; diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Wim/StdAfx.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Wim/StdAfx.h index 2854ff3e..035267cc 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Wim/StdAfx.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Wim/StdAfx.h @@ -1,8 +1,11 @@ // StdAfx.h -#ifndef __STDAFX_H -#define __STDAFX_H +#ifndef ZIP7_INC_STDAFX_H +#define ZIP7_INC_STDAFX_H +#if defined(_MSC_VER) && _MSC_VER >= 1800 +#pragma warning(disable : 4464) // relative include path contains '..' +#endif #include "../../../Common/Common.h" #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Wim/WimHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/Wim/WimHandler.cpp index 234780dd..cc246975 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Wim/WimHandler.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Wim/WimHandler.cpp @@ -94,17 +94,8 @@ static void AddErrorMessage(AString &s, const char *message) s += message; } -static void ConvertByteToHex(unsigned value, char *s) -{ - for (int i = 0; i < 2; i++) - { - unsigned t = value & 0xF; - value >>= 4; - s[1 - i] = (char)((t < 10) ? ('0' + t) : ('A' + (t - 10))); - } -} -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; @@ -133,7 +124,7 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) const CImageInfo &image2 = xml.Images[i]; if (image2.CTimeDefined) if (index < 0 || ::CompareFileTime(&image2.CTime, &xml.Images[index].CTime) < 0) - index = i; + index = (int)i; } if (index >= 0) prop = xml.Images[index].CTime; @@ -150,7 +141,7 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) const CImageInfo &image2 = xml.Images[i]; if (image2.MTimeDefined) if (index < 0 || ::CompareFileTime(&image2.MTime, &xml.Images[index].MTime) > 0) - index = i; + index = (int)i; } if (index >= 0) prop = xml.Images[index].MTime; @@ -177,17 +168,14 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) UInt32 ver2 = (_version >> 8) & 0xFF; UInt32 ver3 = (_version) & 0xFF; - char s[16]; - ConvertUInt32ToString(ver1, s); - AString res = s; - res += '.'; - ConvertUInt32ToString(ver2, s); - res += s; + AString res; + res.Add_UInt32(ver1); + res.Add_Dot(); + res.Add_UInt32(ver2); if (ver3 != 0) { - res += '.'; - ConvertUInt32ToString(ver3, s); - res += s; + res.Add_Dot(); + res.Add_UInt32(ver3); } prop = res; break; @@ -229,22 +217,16 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) const CHeader &h = _volumes[_firstVolumeIndex].Header; if (GetUi32(h.Guid) != 0) { - char temp[16 * 2 + 4]; - int i; - for (i = 0; i < 4; i++) - ConvertByteToHex(h.Guid[i], temp + i * 2); - temp[i * 2] = 0; - AString s = temp; + char temp[64]; + RawLeGuidToString(h.Guid, temp); + temp[8] = 0; // for reduced GUID + AString s (temp); const char *ext = ".wim"; if (h.NumParts != 1) { s += '_'; if (h.PartNumber != 1) - { - char sz[16]; - ConvertUInt32ToString(h.PartNumber, sz); - s += sz; - } + s.Add_UInt32(h.PartNumber); ext = ".swm"; } s += ext; @@ -262,10 +244,8 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) AString s; if (h.PartNumber != 1) { - char sz[16]; - ConvertUInt32ToString(h.PartNumber, sz); - s = sz; - s += '.'; + s.Add_UInt32(h.PartNumber); + s.Add_Dot(); } s += "swm"; prop = s; @@ -287,7 +267,7 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) { const CHeader &header = _volumes[_xmls[i].VolIndex].Header; unsigned method = header.GetMethod(); - if (method < ARRAY_SIZE(k_Methods)) + if (method < Z7_ARRAY_SIZE(k_Methods)) methodMask |= ((UInt32)1 << method); else methodUnknown = method; @@ -300,7 +280,7 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) unsigned numMethods = 0; - for (unsigned i = 0; i < ARRAY_SIZE(k_Methods); i++) + for (unsigned i = 0; i < Z7_ARRAY_SIZE(k_Methods); i++) { if (methodMask & ((UInt32)1 << i)) { @@ -312,19 +292,15 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) if (methodUnknown != 0) { - char temp[32]; - ConvertUInt32ToString(methodUnknown, temp); res.Add_Space_if_NotEmpty(); - res += temp; + res.Add_UInt32(methodUnknown); numMethods++; } if (numMethods == 1 && chunkSizeBits != 0) { - char temp[32]; - temp[0] = ':'; - ConvertUInt32ToString((UInt32)chunkSizeBits, temp + 1); - res += temp; + res.Add_Colon(); + res.Add_UInt32((UInt32)chunkSizeBits); } prop = res; @@ -374,11 +350,12 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) COM_TRY_END } -void GetFileTime(const Byte *p, NCOM::CPropVariant &prop) +static void GetFileTime(const Byte *p, NCOM::CPropVariant &prop) { prop.vt = VT_FILETIME; prop.filetime.dwLowDateTime = Get32(p); prop.filetime.dwHighDateTime = Get32(p + 4); + prop.Set_FtPrec(k_PropVar_TimePrec_100ns); } @@ -388,10 +365,10 @@ static void MethodToProp(int method, int chunksSizeBits, NCOM::CPropVariant &pro { char temp[32]; - if ((unsigned)method < ARRAY_SIZE(k_Methods)) - strcpy(temp, k_Methods[(unsigned)method]); + if ((unsigned)method < Z7_ARRAY_SIZE(k_Methods)) + MyStringCopy(temp, k_Methods[(unsigned)method]); else - ConvertUInt32ToString((unsigned)method, temp); + ConvertUInt32ToString((UInt32)(unsigned)method, temp); if (chunksSizeBits >= 0) { @@ -405,7 +382,7 @@ static void MethodToProp(int method, int chunksSizeBits, NCOM::CPropVariant &pro } -STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; @@ -436,9 +413,6 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val _db.GetItemPath(realIndex, _showImageNumber, prop); else { - char sz[16]; - ConvertUInt32ToString(item.StreamIndex, sz); - AString s = sz; /* while (s.Len() < _nameLenForStreams) s = '0' + s; @@ -448,7 +422,8 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val s = (AString)("[Free]" STRING_PATH_SEPARATOR) + sz; else */ - s = (AString)(FILES_DIR_NAME STRING_PATH_SEPARATOR) + sz; + AString s (FILES_DIR_NAME STRING_PATH_SEPARATOR); + s.Add_UInt32((UInt32)(Int32)item.StreamIndex); prop = s; } break; @@ -459,7 +434,7 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val else { char sz[16]; - ConvertUInt32ToString(item.StreamIndex, sz); + ConvertUInt32ToString((UInt32)(Int32)item.StreamIndex, sz); /* AString s = sz; while (s.Len() < _nameLenForStreams) @@ -583,7 +558,7 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val if (r.SolidIndex >= 0) { CSolid &ss = _db.Solids[r.SolidIndex]; - MethodToProp(ss.Method, ss.ChunkSizeBits, prop); + MethodToProp(ss.Method, (int)ss.ChunkSizeBits, prop); } } else @@ -592,8 +567,8 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val int chunkSizeBits = -1; if (r.IsCompressed()) { - method = vol->Header.GetMethod(); - chunkSizeBits = vol->Header.ChunkSizeBits; + method = (int)vol->Header.GetMethod(); + chunkSizeBits = (int)vol->Header.ChunkSizeBits; } MethodToProp(method, chunkSizeBits, prop); } @@ -645,7 +620,7 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val COM_TRY_END } -STDMETHODIMP CHandler::GetRootProp(PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetRootProp(PROPID propID, PROPVARIANT *value)) { // COM_TRY_BEGIN NCOM::CPropVariant prop; @@ -678,13 +653,13 @@ HRESULT CHandler::GetSecurity(UInt32 realIndex, const void **data, UInt32 *dataS return S_OK; const CImage &image = _db.Images[item.ImageIndex]; const Byte *metadata = image.Meta + item.Offset; - UInt32 securityId = Get32(metadata + 0xC); - if (securityId == (UInt32)(Int32)-1) + const UInt32 securId = Get32(metadata + 0xC); + if (// securId == (UInt32)(Int32)-1 || + securId >= image.SecurOffsets.Size() + || securId + 1 >= image.SecurOffsets.Size()) return S_OK; - if (securityId >= (UInt32)image.SecurOffsets.Size()) - return E_FAIL; - UInt32 offs = image.SecurOffsets[securityId]; - UInt32 len = image.SecurOffsets[securityId + 1] - offs; + const UInt32 offs = image.SecurOffsets[securId]; + const UInt32 len = image.SecurOffsets[securId + 1] - offs; const CByteBuffer &buf = image.Meta; if (offs <= buf.Size() && buf.Size() - offs >= len) { @@ -695,9 +670,9 @@ HRESULT CHandler::GetSecurity(UInt32 realIndex, const void **data, UInt32 *dataS return S_OK; } -STDMETHODIMP CHandler::GetRootRawProp(PROPID propID, const void **data, UInt32 *dataSize, UInt32 *propType) +Z7_COM7F_IMF(CHandler::GetRootRawProp(PROPID propID, const void **data, UInt32 *dataSize, UInt32 *propType)) { - *data = 0; + *data = NULL; *dataSize = 0; *propType = 0; if (propID == kpidNtSecure && _db.Images.Size() != 0 && _db.NumExcludededItems != 0) @@ -705,7 +680,7 @@ STDMETHODIMP CHandler::GetRootRawProp(PROPID propID, const void **data, UInt32 * const CImage &image = _db.Images[_db.IndexOfUserImage]; const CItem &item = _db.Items[image.StartItem]; if (!item.IsDir || item.ImageIndex != _db.IndexOfUserImage) - return E_FAIL; + return S_OK; // E_FAIL; return GetSecurity(image.StartItem, data, dataSize, propType); } return S_OK; @@ -719,20 +694,20 @@ static const Byte kRawProps[] = }; -STDMETHODIMP CHandler::GetNumRawProps(UInt32 *numProps) +Z7_COM7F_IMF(CHandler::GetNumRawProps(UInt32 *numProps)) { - *numProps = ARRAY_SIZE(kRawProps); + *numProps = Z7_ARRAY_SIZE(kRawProps); return S_OK; } -STDMETHODIMP CHandler::GetRawPropInfo(UInt32 index, BSTR *name, PROPID *propID) +Z7_COM7F_IMF(CHandler::GetRawPropInfo(UInt32 index, BSTR *name, PROPID *propID)) { *propID = kRawProps[index]; - *name = 0; + *name = NULL; return S_OK; } -STDMETHODIMP CHandler::GetParent(UInt32 index, UInt32 *parent, UInt32 *parentType) +Z7_COM7F_IMF(CHandler::GetParent(UInt32 index, UInt32 *parent, UInt32 *parentType)) { *parentType = NParentType::kDir; *parent = (UInt32)(Int32)-1; @@ -747,13 +722,13 @@ STDMETHODIMP CHandler::GetParent(UInt32 index, UInt32 *parent, UInt32 *parentTyp if (item.Parent >= 0) { if (_db.ExludedItem != item.Parent) - *parent = _db.Items[item.Parent].IndexInSorted; + *parent = (unsigned)_db.Items[item.Parent].IndexInSorted; } else { CImage &image = _db.Images[item.ImageIndex]; if (image.VirtualRootIndex >= 0) - *parent = _db.SortedItems.Size() + _numXmlItems + image.VirtualRootIndex; + *parent = _db.SortedItems.Size() + _numXmlItems + (unsigned)image.VirtualRootIndex; } } else @@ -761,7 +736,7 @@ STDMETHODIMP CHandler::GetParent(UInt32 index, UInt32 *parent, UInt32 *parentTyp return S_OK; } -STDMETHODIMP CHandler::GetRawProp(UInt32 index, PROPID propID, const void **data, UInt32 *dataSize, UInt32 *propType) +Z7_COM7F_IMF(CHandler::GetRawProp(UInt32 index, PROPID propID, const void **data, UInt32 *dataSize, UInt32 *propType)) { *data = NULL; *dataSize = 0; @@ -867,20 +842,21 @@ class CVolumeName { int dotPos = name.ReverseFind_Dot(); if (dotPos < 0) - dotPos = name.Len(); - _before = name.Left(dotPos); + dotPos = (int)name.Len(); + _before.SetFrom(name.Ptr(), (unsigned)dotPos); _after = name.Ptr(dotPos); } UString GetNextName(UInt32 index) const { - wchar_t s[16]; - ConvertUInt32ToString(index, s); - return _before + (UString)s + _after; + UString s = _before; + s.Add_UInt32(index); + s += _after; + return s; } }; -STDMETHODIMP CHandler::Open(IInStream *inStream, const UInt64 *, IArchiveOpenCallback *callback) +Z7_COM7F_IMF(CHandler::Open(IInStream *inStream, const UInt64 *, IArchiveOpenCallback *callback)) { COM_TRY_BEGIN @@ -902,8 +878,10 @@ STDMETHODIMP CHandler::Open(IInStream *inStream, const UInt64 *, IArchiveOpenCal curStream = inStream; else { - UString fullName = seqName.GetNextName(i); - HRESULT result = openVolumeCallback->GetStream(fullName, &curStream); + if (!openVolumeCallback) + continue; + const UString fullName = seqName.GetNextName(i); + const HRESULT result = openVolumeCallback->GetStream(fullName, &curStream); if (result == S_FALSE) continue; if (result != S_OK) @@ -970,11 +948,9 @@ STDMETHODIMP CHandler::Open(IInStream *inStream, const UInt64 *, IArchiveOpenCal if (_xmls.IsEmpty() || xml.Data != _xmls[0].Data) { - char sz[16]; - ConvertUInt32ToString(xml.VolIndex, sz); - xml.FileName = L'['; - xml.FileName.AddAscii(sz); - xml.FileName.AddAscii("].xml"); + xml.FileName = '['; + xml.FileName.Add_UInt32(xml.VolIndex); + xml.FileName += "].xml"; _xmls.Add(xml); } @@ -987,7 +963,7 @@ STDMETHODIMP CHandler::Open(IInStream *inStream, const UInt64 *, IArchiveOpenCal numVolumes = header.NumParts; { NCOM::CPropVariant prop; - RINOK(openVolumeCallback->GetProperty(kpidName, &prop)); + RINOK(openVolumeCallback->GetProperty(kpidName, &prop)) if (prop.vt != VT_BSTR) break; seqName.InitName(prop.bstrVal); @@ -995,7 +971,7 @@ STDMETHODIMP CHandler::Open(IInStream *inStream, const UInt64 *, IArchiveOpenCal } } - RINOK(_db.FillAndCheck(_volumes)); + RINOK(_db.FillAndCheck(_volumes)) int defaultImageIndex = (int)_defaultImageNumber - 1; bool showImageNumber = (_db.Images.Size() != 1 && defaultImageIndex < 0); @@ -1007,8 +983,8 @@ STDMETHODIMP CHandler::Open(IInStream *inStream, const UInt64 *, IArchiveOpenCal _showImageNumber = showImageNumber; - RINOK(_db.GenerateSortedItems(defaultImageIndex, showImageNumber)); - RINOK(_db.ExtractReparseStreams(_volumes, callback)); + RINOK(_db.GenerateSortedItems(defaultImageIndex, showImageNumber)) + RINOK(_db.ExtractReparseStreams(_volumes, callback)) /* wchar_t sz[16]; @@ -1025,7 +1001,7 @@ STDMETHODIMP CHandler::Open(IInStream *inStream, const UInt64 *, IArchiveOpenCal } -STDMETHODIMP CHandler::Close() +Z7_COM7F_IMF(CHandler::Close()) { _firstVolumeIndex = -1; _phySize = 0; @@ -1043,11 +1019,11 @@ STDMETHODIMP CHandler::Close() } -STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, - Int32 testMode, IArchiveExtractCallback *extractCallback) +Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testMode, IArchiveExtractCallback *extractCallback)) { COM_TRY_BEGIN - bool allFilesMode = (numItems == (UInt32)(Int32)-1); + const bool allFilesMode = (numItems == (UInt32)(Int32)-1); if (allFilesMode) numItems = _db.SortedItems.Size() + _numXmlItems + _db.VirtualRoots.Size() + _numIgnoreItems; @@ -1072,77 +1048,73 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, else { index -= _db.SortedItems.Size(); - if (index < (UInt32)_numXmlItems) + if (index < _numXmlItems) totalSize += _xmls[index].Data.Size(); } } - RINOK(extractCallback->SetTotal(totalSize)); + RINOK(extractCallback->SetTotal(totalSize)) - UInt64 currentTotalUnPacked = 0; + totalSize = 0; UInt64 currentItemUnPacked; int prevSuccessStreamIndex = -1; CUnpacker unpacker; - CLocalProgress *lps = new CLocalProgress; - CMyComPtr progress = lps; + CMyComPtr2_Create lps; lps->Init(extractCallback, false); for (i = 0;; i++, - currentTotalUnPacked += currentItemUnPacked) + totalSize += currentItemUnPacked) { currentItemUnPacked = 0; - lps->InSize = unpacker.TotalPacked; - lps->OutSize = currentTotalUnPacked; - - RINOK(lps->SetCur()); - + lps->OutSize = totalSize; + RINOK(lps->SetCur()) if (i >= numItems) break; UInt32 index = allFilesMode ? i : indices[i]; - Int32 askMode = testMode ? + const Int32 askMode = testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; CMyComPtr realOutStream; - RINOK(extractCallback->GetStream(index, &realOutStream, askMode)); + RINOK(extractCallback->GetStream(index, &realOutStream, askMode)) if (index >= _db.SortedItems.Size()) { if (!testMode && !realOutStream) continue; - RINOK(extractCallback->PrepareOperation(askMode)); + RINOK(extractCallback->PrepareOperation(askMode)) index -= _db.SortedItems.Size(); - if (index < (UInt32)_numXmlItems) + if (index < _numXmlItems) { const CByteBuffer &data = _xmls[index].Data; currentItemUnPacked = data.Size(); if (realOutStream) { - RINOK(WriteStream(realOutStream, (const Byte *)data, data.Size())); + RINOK(WriteStream(realOutStream, (const Byte *)data, data.Size())) realOutStream.Release(); } } - RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)); + RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)) continue; } const CItem &item = _db.Items[_db.SortedItems[index]]; - int streamIndex = item.StreamIndex; + const int streamIndex = item.StreamIndex; if (streamIndex < 0) { if (!item.IsDir) if (!testMode && !realOutStream) continue; - RINOK(extractCallback->PrepareOperation(askMode)); + RINOK(extractCallback->PrepareOperation(askMode)) realOutStream.Release(); RINOK(extractCallback->SetOperationResult(!item.IsDir && _db.ItemHasStream(item) ? NExtract::NOperationResult::kDataError : - NExtract::NOperationResult::kOK)); + NExtract::NOperationResult::kOK)) continue; } @@ -1152,17 +1124,16 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, if (!testMode && !realOutStream) continue; - RINOK(extractCallback->PrepareOperation(askMode)); + RINOK(extractCallback->PrepareOperation(askMode)) Int32 opRes = NExtract::NOperationResult::kOK; if (streamIndex != prevSuccessStreamIndex || realOutStream) { Byte digest[kHashSize]; const CVolume &vol = _volumes[si.PartNumber]; - bool needDigest = !si.IsEmptyHash(); - - HRESULT res = unpacker.Unpack(vol.Stream, si.Resource, vol.Header, &_db, - realOutStream, progress, needDigest ? digest : NULL); + const bool needDigest = !si.IsEmptyHash() && !_disable_Sha1Check; + const HRESULT res = unpacker.Unpack(vol.Stream, si.Resource, vol.Header, &_db, + realOutStream, lps, needDigest ? digest : NULL); if (res == S_OK) { @@ -1180,7 +1151,7 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, } realOutStream.Release(); - RINOK(extractCallback->SetOperationResult(opRes)); + RINOK(extractCallback->SetOperationResult(opRes)) } return S_OK; @@ -1188,7 +1159,7 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, } -STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) { *numItems = _db.SortedItems.Size() + _numXmlItems + @@ -1204,7 +1175,7 @@ CHandler::CHandler() _xmlError = false; } -STDMETHODIMP CHandler::SetProperties(const wchar_t * const *names, const PROPVARIANT *values, UInt32 numProps) +Z7_COM7F_IMF(CHandler::SetProperties(const wchar_t * const *names, const PROPVARIANT *values, UInt32 numProps)) { InitDefaults(); @@ -1221,26 +1192,44 @@ STDMETHODIMP CHandler::SetProperties(const wchar_t * const *names, const PROPVAR { // some clients write 'x' property. So we support it UInt32 level = 0; - RINOK(ParsePropToUInt32(name.Ptr(1), prop, level)); + RINOK(ParsePropToUInt32(name.Ptr(1), prop, level)) } else if (name.IsEqualTo("is")) { - RINOK(PROPVARIANT_to_bool(prop, _set_showImageNumber)); + RINOK(PROPVARIANT_to_bool(prop, _set_showImageNumber)) _set_use_ShowImageNumber = true; } else if (name.IsEqualTo("im")) { UInt32 image = 9; - RINOK(ParsePropToUInt32(L"", prop, image)); - _defaultImageNumber = image; + RINOK(ParsePropToUInt32(L"", prop, image)) + _defaultImageNumber = (int)image; + } + else if (name.IsPrefixedBy_Ascii_NoCase("mt")) + { + } + else if (name.IsPrefixedBy_Ascii_NoCase("memuse")) + { + } + else if (name.IsPrefixedBy_Ascii_NoCase("crc")) + { + name.Delete(0, 3); + UInt32 crcSize = 1; + RINOK(ParsePropToUInt32(name, prop, crcSize)) + _disable_Sha1Check = (crcSize == 0); } else - return E_INVALIDARG; + { + bool processed = false; + RINOK(_timeOptions.Parse(name, prop, processed)) + if (!processed) + return E_INVALIDARG; + } } return S_OK; } -STDMETHODIMP CHandler::KeepModeForNextOpen() +Z7_COM7F_IMF(CHandler::KeepModeForNextOpen()) { _keepMode_ShowImageNumber = _showImageNumber; return S_OK; diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Wim/WimHandler.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Wim/WimHandler.h index 3ba88d2b..126d233c 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Wim/WimHandler.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Wim/WimHandler.h @@ -1,39 +1,40 @@ // WimHandler.h -#ifndef __ARCHIVE_WIM_HANDLER_H -#define __ARCHIVE_WIM_HANDLER_H +#ifndef ZIP7_INC_ARCHIVE_WIM_HANDLER_H +#define ZIP7_INC_ARCHIVE_WIM_HANDLER_H #include "../../../Common/MyCom.h" +#include "../Common/HandlerOut.h" + #include "WimIn.h" namespace NArchive { namespace NWim { -static const Int32 kNumImagesMaxUpdate = (1 << 10); - -class CHandler: - public IInArchive, - public IArchiveGetRawProps, - public IArchiveGetRootProps, - public IArchiveKeepModeForNextOpen, - public ISetProperties, - public IOutArchive, - public CMyUnknownImp -{ +const Int32 kNumImagesMaxUpdate = 1 << 10; + +Z7_CLASS_IMP_CHandler_IInArchive_5( + IArchiveGetRawProps + , IArchiveGetRootProps + , IArchiveKeepModeForNextOpen + , ISetProperties + , IOutArchive +) CDatabase _db; UInt32 _version; - bool _isOldVersion; UInt32 _bootIndex; CObjectVector _volumes; CObjectVector _xmls; // unsigned _nameLenForStreams; - bool _xmlInComments; - + unsigned _numXmlItems; unsigned _numIgnoreItems; + bool _isOldVersion; + bool _xmlInComments; + bool _xmlError; bool _isArc; bool _unsupported; @@ -43,17 +44,21 @@ class CHandler: int _defaultImageNumber; bool _showImageNumber; - bool _keepMode_ShowImageNumber; + bool _disable_Sha1Check; UInt64 _phySize; - int _firstVolumeIndex; + Int32 _firstVolumeIndex; + + CHandlerTimeOptions _timeOptions; void InitDefaults() { + _disable_Sha1Check = false; _set_use_ShowImageNumber = false; _set_showImageNumber = false; _defaultImageNumber = -1; + _timeOptions.Init(); } bool IsUpdateSupported() const @@ -83,19 +88,6 @@ class CHandler: HRESULT GetTime(IArchiveUpdateCallback *callback, UInt32 callbackIndex, Int32 arcIndex, PROPID propID, FILETIME &ft); public: CHandler(); - MY_UNKNOWN_IMP6( - IInArchive, - IArchiveGetRawProps, - IArchiveGetRootProps, - IArchiveKeepModeForNextOpen, - ISetProperties, - IOutArchive) - INTERFACE_IInArchive(;) - INTERFACE_IArchiveGetRawProps(;) - INTERFACE_IArchiveGetRootProps(;) - STDMETHOD(SetProperties)(const wchar_t * const *names, const PROPVARIANT *values, UInt32 numProps); - STDMETHOD(KeepModeForNextOpen)(); - INTERFACE_IOutArchive(;) }; }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Wim/WimHandlerOut.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/Wim/WimHandlerOut.cpp index 1d198df0..0fd7d239 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Wim/WimHandlerOut.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Wim/WimHandlerOut.cpp @@ -4,6 +4,7 @@ #include "../../../Common/ComTry.h" #include "../../../Common/IntToString.h" +#include "../../../Common/MyBuffer2.h" #include "../../../Common/StringToInt.h" #include "../../../Common/UTFConvert.h" #include "../../../Common/Wildcard.h" @@ -19,6 +20,8 @@ #include "../../Crypto/RandGen.h" #include "../../Crypto/Sha1Cls.h" +#include "../Common/OutStreamWithSha1.h" + #include "WimHandler.h" using namespace NWindows; @@ -26,13 +29,30 @@ using namespace NWindows; namespace NArchive { namespace NWim { -static int AddUniqHash(const CStreamInfo *streams, CUIntVector &sorted, const Byte *h, int streamIndexForInsert) +static const unsigned k_NumSubVectors_Bits = 12; // must be <= 16 + +struct CSortedIndex +{ + CObjectVector Vectors; + + CSortedIndex() + { + const unsigned k_NumSubVectors = 1 << k_NumSubVectors_Bits; + Vectors.ClearAndReserve(k_NumSubVectors); + for (unsigned i = 0; i < k_NumSubVectors; i++) + Vectors.AddNew(); + } +}; + +static int AddUniqHash(const CStreamInfo *streams, CSortedIndex &sorted2, const Byte *h, int streamIndexForInsert) { + const unsigned hash = (((unsigned)h[0] << 8) | (unsigned)h[1]) >> (16 - k_NumSubVectors_Bits); + CUIntVector &sorted = sorted2.Vectors[hash]; unsigned left = 0, right = sorted.Size(); while (left != right) { - unsigned mid = (left + right) / 2; - unsigned index = sorted[mid]; + const unsigned mid = (unsigned)(((size_t)left + (size_t)right) / 2); + const unsigned index = sorted[mid]; const Byte *hash2 = streams[index].Hash; unsigned i; @@ -41,7 +61,7 @@ static int AddUniqHash(const CStreamInfo *streams, CUIntVector &sorted, const By break; if (i == kHashSize) - return index; + return (int)index; if (h[i] < hash2[i]) right = mid; @@ -49,8 +69,8 @@ static int AddUniqHash(const CStreamInfo *streams, CUIntVector &sorted, const By left = mid + 1; } - if (streamIndexForInsert >= 0) - sorted.Insert(left, streamIndexForInsert); + if (streamIndexForInsert != -1) + sorted.Insert(left, (unsigned)streamIndexForInsert); return -1; } @@ -77,13 +97,13 @@ struct CMetaItem FILETIME CTime; FILETIME ATime; FILETIME MTime; - UInt32 Attrib; UInt64 FileID; UInt64 VolID; UString Name; UString ShortName; + UInt32 Attrib; int SecurityId; // -1: means no secutity ID bool IsDir; bool Skip; @@ -93,9 +113,22 @@ struct CMetaItem CByteBuffer Reparse; unsigned GetNumAltStreams() const { return AltStreams.Size() - NumSkipAltStreams; } - CMetaItem(): UpdateIndex(-1), HashIndex(-1), SecurityId(-1), - FileID(0), VolID(0), - Skip(false), NumSkipAltStreams(0) {} + CMetaItem(): + UpdateIndex(-1) + , HashIndex(-1) + , Size(0) + , FileID(0) + , VolID(0) + , Attrib(0) + , SecurityId(-1) + , IsDir(false) + , Skip(false) + , NumSkipAltStreams(0) + { + FILETIME_Clear(CTime); + FILETIME_Clear(ATime); + FILETIME_Clear(MTime); + } }; @@ -117,11 +150,11 @@ static int AddToHardLinkList(const CObjectVector &metaItems, unsigned unsigned left = 0, right = indexes.Size(); while (left != right) { - unsigned mid = (left + right) / 2; - unsigned index = indexes[mid]; - int comp = Compare_HardLink_MetaItems(mi, metaItems[index]); + const unsigned mid = (unsigned)(((size_t)left + (size_t)right) / 2); + const unsigned index = indexes[mid]; + const int comp = Compare_HardLink_MetaItems(mi, metaItems[index]); if (comp == 0) - return index; + return (int)index; if (comp < 0) right = mid; else @@ -144,7 +177,12 @@ struct CUpdateItem int InArcIndex; // >= 0, if we use OLD Data // -1, if we use NEW Data - CUpdateItem(): MetaIndex(-1), AltStreamIndex(-1), InArcIndex(-1) {} + void Construct() + { + MetaIndex = -1; + AltStreamIndex = -1; + InArcIndex = -1; + } }; @@ -196,8 +234,8 @@ bool CDir::FindDir(const CObjectVector &items, const UString &name, u unsigned left = 0, right = Dirs.Size(); while (left != right) { - unsigned mid = (left + right) / 2; - int comp = CompareFileNames(name, items[Dirs[mid].MetaIndex].Name); + const unsigned mid = (unsigned)(((size_t)left + (size_t)right) / 2); + const int comp = CompareFileNames(name, items[Dirs[mid].MetaIndex].Name); if (comp == 0) { index = mid; @@ -213,7 +251,7 @@ bool CDir::FindDir(const CObjectVector &items, const UString &name, u } -STDMETHODIMP CHandler::GetFileTimeType(UInt32 *type) +Z7_COM7F_IMF(CHandler::GetFileTimeType(UInt32 *type)) { *type = NFileTimeType::kWindows; return S_OK; @@ -222,8 +260,8 @@ STDMETHODIMP CHandler::GetFileTimeType(UInt32 *type) HRESULT CHandler::GetOutProperty(IArchiveUpdateCallback *callback, UInt32 callbackIndex, Int32 arcIndex, PROPID propID, PROPVARIANT *value) { - if (arcIndex >= 0) - return GetProperty(arcIndex, propID, value); + if (arcIndex != -1) + return GetProperty((UInt32)arcIndex, propID, value); return callback->GetProperty(callbackIndex, propID, value); } @@ -232,7 +270,7 @@ HRESULT CHandler::GetTime(IArchiveUpdateCallback *callback, UInt32 callbackIndex { ft.dwLowDateTime = ft.dwHighDateTime = 0; NCOM::CPropVariant prop; - RINOK(GetOutProperty(callback, callbackIndex, arcIndex, propID, &prop)); + RINOK(GetOutProperty(callback, callbackIndex, arcIndex, propID, &prop)) if (prop.vt == VT_FILETIME) ft = prop.filetime; else if (prop.vt != VT_EMPTY) @@ -249,7 +287,7 @@ static HRESULT GetRootTime( NCOM::CPropVariant prop; if (callback) { - RINOK(callback->GetRootProp(propID, &prop)); + RINOK(callback->GetRootProp(propID, &prop)) if (prop.vt == VT_FILETIME) { ft = prop.filetime; @@ -260,7 +298,7 @@ static HRESULT GetRootTime( } if (arcRoot) { - RINOK(arcRoot->GetRootProp(propID, &prop)); + RINOK(arcRoot->GetRootProp(propID, &prop)) if (prop.vt == VT_FILETIME) { ft = prop.filetime; @@ -278,29 +316,29 @@ static HRESULT GetRootTime( void CResource::WriteTo(Byte *p) const { - Set64(p, PackSize); + Set64(p, PackSize) p[7] = Flags; - Set64(p + 8, Offset); - Set64(p + 16, UnpackSize); + Set64(p + 8, Offset) + Set64(p + 16, UnpackSize) } void CHeader::WriteTo(Byte *p) const { memcpy(p, kSignature, kSignatureSize); - Set32(p + 8, kHeaderSizeMax); - Set32(p + 0xC, Version); - Set32(p + 0x10, Flags); - Set32(p + 0x14, ChunkSize); + Set32(p + 8, kHeaderSizeMax) + Set32(p + 0xC, Version) + Set32(p + 0x10, Flags) + Set32(p + 0x14, ChunkSize) memcpy(p + 0x18, Guid, 16); - Set16(p + 0x28, PartNumber); - Set16(p + 0x2A, NumParts); - Set32(p + 0x2C, NumImages); + Set16(p + 0x28, PartNumber) + Set16(p + 0x2A, NumParts) + Set32(p + 0x2C, NumImages) OffsetResource.WriteTo(p + 0x30); XmlResource.WriteTo(p + 0x48); MetadataResource.WriteTo(p + 0x60); IntegrityResource.WriteTo(p + 0x7C); - Set32(p + 0x78, BootIndex); + Set32(p + 0x78, BootIndex) memset(p + 0x94, 0, 60); } @@ -308,50 +346,16 @@ void CHeader::WriteTo(Byte *p) const void CStreamInfo::WriteTo(Byte *p) const { Resource.WriteTo(p); - Set16(p + 0x18, PartNumber); - Set32(p + 0x1A, RefCount); + Set16(p + 0x18, PartNumber) + Set32(p + 0x1A, RefCount) memcpy(p + 0x1E, Hash, kHashSize); } -class CInStreamWithSha1: - public ISequentialInStream, - public CMyUnknownImp -{ - CMyComPtr _stream; - UInt64 _size; - NCrypto::NSha1::CContext _sha; -public: - MY_UNKNOWN_IMP1(IInStream) - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); - - void SetStream(ISequentialInStream *stream) { _stream = stream; } - void Init() - { - _size = 0; - _sha.Init(); - } - void ReleaseStream() { _stream.Release(); } - UInt64 GetSize() const { return _size; } - void Final(Byte *digest) { _sha.Final(digest); } -}; - -STDMETHODIMP CInStreamWithSha1::Read(void *data, UInt32 size, UInt32 *processedSize) -{ - UInt32 realProcessedSize; - HRESULT result = _stream->Read(data, size, &realProcessedSize); - _size += realProcessedSize; - _sha.Update((const Byte *)data, realProcessedSize); - if (processedSize) - *processedSize = realProcessedSize; - return result; -} - - static void SetFileTimeToMem(Byte *p, const FILETIME &ft) { - Set32(p, ft.dwLowDateTime); - Set32(p + 4, ft.dwHighDateTime); + Set32(p, ft.dwLowDateTime) + Set32(p + 4, ft.dwHighDateTime) } static size_t WriteItem_Dummy(const CMetaItem &item) @@ -362,15 +366,15 @@ static size_t WriteItem_Dummy(const CMetaItem &item) // we write fileNameLen + 2 + 2 to be same as original WIM. unsigned fileNameLen2 = (fileNameLen == 0 ? 0 : fileNameLen + 2); - unsigned shortNameLen = item.ShortName.Len() * 2; - unsigned shortNameLen2 = (shortNameLen == 0 ? 2 : shortNameLen + 4); + const unsigned shortNameLen = item.ShortName.Len() * 2; + const unsigned shortNameLen2 = (shortNameLen == 0 ? 2 : shortNameLen + 4); - size_t totalLen = ((kDirRecordSize + fileNameLen2 + shortNameLen2 + 6) & ~7); + size_t totalLen = ((kDirRecordSize + fileNameLen2 + shortNameLen2 + 6) & ~(unsigned)7); if (item.GetNumAltStreams() != 0) { if (!item.IsDir) { - UInt32 curLen = (((0x26 + 0) + 6) & ~7); + const UInt32 curLen = (((0x26 + 0) + 6) & ~(unsigned)7); totalLen += curLen; } FOR_VECTOR (i, item.AltStreams) @@ -380,7 +384,7 @@ static size_t WriteItem_Dummy(const CMetaItem &item) continue; fileNameLen = ss.Name.Len() * 2; fileNameLen2 = (fileNameLen == 0 ? 0 : fileNameLen + 2 + 2); - UInt32 curLen = (((0x26 + fileNameLen2) + 6) & ~7); + const UInt32 curLen = (((0x26 + fileNameLen2) + 6) & ~(unsigned)7); totalLen += curLen; } } @@ -397,12 +401,12 @@ static size_t WriteItem(const CStreamInfo *streams, const CMetaItem &item, Byte unsigned shortNameLen = item.ShortName.Len() * 2; unsigned shortNameLen2 = (shortNameLen == 0 ? 2 : shortNameLen + 4); - size_t totalLen = ((kDirRecordSize + fileNameLen2 + shortNameLen2 + 6) & ~7); + size_t totalLen = ((kDirRecordSize + fileNameLen2 + shortNameLen2 + 6) & ~(unsigned)7); memset(p, 0, totalLen); - Set64(p, totalLen); - Set64(p + 8, item.Attrib); - Set32(p + 0xC, (Int32)item.SecurityId); + Set64(p, totalLen) + Set64(p + 8, item.Attrib) + Set32(p + 0xC, (UInt32)(Int32)item.SecurityId) SetFileTimeToMem(p + 0x28, item.CTime); SetFileTimeToMem(p + 0x30, item.ATime); SetFileTimeToMem(p + 0x38, item.MTime); @@ -415,21 +419,21 @@ static size_t WriteItem(const CStreamInfo *streams, const CMetaItem &item, Byte if (item.Reparse.Size() != 0) { UInt32 tag = GetUi32(item.Reparse); - Set32(p + 0x58, tag); + Set32(p + 0x58, tag) // Set32(p + 0x5C, 0); // probably it's always ZERO } else if (item.FileID != 0) { - Set64(p + 0x58, item.FileID); + Set64(p + 0x58, item.FileID) } - Set16(p + 0x62, (UInt16)shortNameLen); - Set16(p + 0x64, (UInt16)fileNameLen); + Set16(p + 0x62, (UInt16)shortNameLen) + Set16(p + 0x64, (UInt16)fileNameLen) unsigned i; for (i = 0; i * 2 < fileNameLen; i++) - Set16(p + kDirRecordSize + i * 2, item.Name[i]); + Set16(p + kDirRecordSize + i * 2, (UInt16)item.Name[i]) for (i = 0; i * 2 < shortNameLen; i++) - Set16(p + kDirRecordSize + fileNameLen2 + i * 2, item.ShortName[i]); + Set16(p + kDirRecordSize + fileNameLen2 + i * 2, (UInt16)item.ShortName[i]) if (item.GetNumAltStreams() == 0) { @@ -438,14 +442,14 @@ static size_t WriteItem(const CStreamInfo *streams, const CMetaItem &item, Byte } else { - Set16(p + 0x60, (UInt16)(item.GetNumAltStreams() + (item.IsDir ? 0 : 1))); + Set16(p + 0x60, (UInt16)(item.GetNumAltStreams() + (item.IsDir ? 0 : 1))) p += totalLen; if (!item.IsDir) { - UInt32 curLen = (((0x26 + 0) + 6) & ~7); + const UInt32 curLen = (((0x26 + 0) + 6) & ~(unsigned)7); memset(p, 0, curLen); - Set64(p, curLen); + Set64(p, curLen) if (item.HashIndex >= 0) memcpy(p + 0x10, streams[item.HashIndex].Hash, kHashSize); totalLen += curLen; @@ -460,15 +464,15 @@ static size_t WriteItem(const CStreamInfo *streams, const CMetaItem &item, Byte fileNameLen = ss.Name.Len() * 2; fileNameLen2 = (fileNameLen == 0 ? 0 : fileNameLen + 2 + 2); - UInt32 curLen = (((0x26 + fileNameLen2) + 6) & ~7); + UInt32 curLen = (((0x26 + fileNameLen2) + 6) & ~(unsigned)7); memset(p, 0, curLen); - Set64(p, curLen); + Set64(p, curLen) if (ss.HashIndex >= 0) memcpy(p + 0x10, streams[ss.HashIndex].Hash, kHashSize); - Set16(p + 0x24, (UInt16)fileNameLen); + Set16(p + 0x24, (UInt16)fileNameLen) for (i = 0; i * 2 < fileNameLen; i++) - Set16(p + 0x26 + i * 2, ss.Name[i]); + Set16(p + 0x26 + i * 2, (UInt16)ss.Name[i]) totalLen += curLen; p += curLen; } @@ -519,7 +523,7 @@ void CDb::WriteTree(const CDir &tree, Byte *dest, size_t &pos) const for (i = 0; i < tree.Dirs.Size(); i++) pos += WriteItem_Dummy(MetaItems[tree.Dirs[i].MetaIndex]); - Set64(dest + pos, 0); + Set64(dest + pos, 0) pos += 8; @@ -534,7 +538,7 @@ void CDb::WriteTree(const CDir &tree, Byte *dest, size_t &pos) const posStart += len; if (needCreateTree) { - Set64(dest + posStart - len + 0x10, pos); // subdirOffset + Set64(dest + posStart - len + 0x10, pos) // subdirOffset WriteTree(subDir, dest, pos); } } @@ -547,18 +551,18 @@ void CDb::WriteOrderList(const CDir &tree) { const CMetaItem &mi = MetaItems[tree.MetaIndex]; if (mi.UpdateIndex >= 0) - UpdateIndexes.Add(mi.UpdateIndex); + UpdateIndexes.Add((unsigned)mi.UpdateIndex); FOR_VECTOR (si, mi.AltStreams) - UpdateIndexes.Add(mi.AltStreams[si].UpdateIndex); + UpdateIndexes.Add((unsigned)mi.AltStreams[si].UpdateIndex); } unsigned i; for (i = 0; i < tree.Files.Size(); i++) { const CMetaItem &mi = MetaItems[tree.Files[i]]; - UpdateIndexes.Add(mi.UpdateIndex); + UpdateIndexes.Add((unsigned)mi.UpdateIndex); FOR_VECTOR (si, mi.AltStreams) - UpdateIndexes.Add(mi.AltStreams[si].UpdateIndex); + UpdateIndexes.Add((unsigned)mi.AltStreams[si].UpdateIndex); } for (i = 0; i < tree.Dirs.Size(); i++) @@ -568,14 +572,14 @@ void CDb::WriteOrderList(const CDir &tree) static void AddTag_ToString(AString &s, const char *name, const char *value) { - s += '<'; + s.Add_Char('<'); s += name; - s += '>'; + s.Add_Char('>'); s += value; - s += '<'; - s += '/'; + s.Add_Char('<'); + s.Add_Slash(); s += name; - s += '>'; + s.Add_Char('>'); } @@ -589,7 +593,7 @@ static void AddTagUInt64_ToString(AString &s, const char *name, UInt64 value) static CXmlItem &AddUniqueTag(CXmlItem &parentItem, const char *name) { - int index = parentItem.FindSubTag(name); + const int index = parentItem.FindSubTag(name); if (index < 0) { CXmlItem &subItem = parentItem.SubItems.AddNew(); @@ -648,8 +652,7 @@ static void AddTag_Time(CXmlItem &parentItem, const char *name, const FILETIME & static void AddTag_String_IfEmpty(CXmlItem &parentItem, const char *name, const char *value) { - int index = parentItem.FindSubTag(name); - if (index >= 0) + if (parentItem.FindSubTag(name) >= 0) return; CXmlItem &tag = parentItem.SubItems.AddNew(); tag.IsTag = true; @@ -671,7 +674,7 @@ void CHeader::SetDefaultFields(bool useLZX) ChunkSize = kChunkSize; ChunkSizeBits = kChunkSizeBits; } - g_RandomGenerator.Generate(Guid, 16); + MY_RAND_GEN(Guid, 16); PartNumber = 1; NumParts = 1; NumImages = 1; @@ -686,15 +689,15 @@ void CHeader::SetDefaultFields(bool useLZX) static void AddTrees(CObjectVector &trees, CObjectVector &metaItems, const CMetaItem &ri, int curTreeIndex) { while (curTreeIndex >= (int)trees.Size()) - trees.AddNew().Dirs.AddNew().MetaIndex = metaItems.Add(ri); + trees.AddNew().Dirs.AddNew().MetaIndex = (int)metaItems.Add(ri); } -#define IS_LETTER_CHAR(c) ((c) >= 'a' && (c) <= 'z' || (c) >= 'A' && (c) <= 'Z') +#define IS_LETTER_CHAR(c) (((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z')) -STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 numItems, IArchiveUpdateCallback *callback) +Z7_COM7F_IMF(CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 numItems, IArchiveUpdateCallback *callback)) { COM_TRY_BEGIN @@ -722,7 +725,7 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 nu return E_NOTIMPL; CMyComPtr outStream; - RINOK(outSeqStream->QueryInterface(IID_IOutStream, (void **)&outStream)); + RINOK(outSeqStream->QueryInterface(IID_IOutStream, (void **)&outStream)) if (!outStream) return E_NOTIMPL; if (!callback) @@ -734,7 +737,7 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 nu CMetaItem ri; // default DIR item FILETIME ftCur; NTime::GetCurUtcFileTime(ftCur); - ri.MTime = ri.ATime = ri.CTime = ftCur; + // ri.MTime = ri.ATime = ri.CTime = ftCur; ri.Attrib = FILE_ATTRIBUTE_DIRECTORY; ri.IsDir = true; @@ -755,7 +758,7 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 nu { UInt32 indexInArchive; Int32 newData, newProps; - RINOK(callback->GetUpdateItemInfo(i, &newData, &newProps, &indexInArchive)); + RINOK(callback->GetUpdateItemInfo(i, &newData, &newProps, &indexInArchive)) if (newProps == 0) { if (indexInArchive >= _db.SortedItems.Size()) @@ -781,7 +784,7 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 nu else { NCOM::CPropVariant prop; - RINOK(callback->GetProperty(i, kpidPath, &prop)); + RINOK(callback->GetProperty(i, kpidPath, &prop)) if (prop.vt != VT_BSTR) return E_INVALIDARG; @@ -841,11 +844,11 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 nu UInt32 propType = 0; if (getRootProps) { - RINOK(getRootProps->GetRootRawProp(kpidNtSecure, &data, &dataSize, &propType)); + RINOK(getRootProps->GetRootRawProp(kpidNtSecure, &data, &dataSize, &propType)) } if (dataSize == 0 && isUpdate) { - RINOK(GetRootRawProp(kpidNtSecure, &data, &dataSize, &propType)); + RINOK(GetRootRawProp(kpidNtSecure, &data, &dataSize, &propType)) } if (dataSize != 0) { @@ -854,21 +857,21 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 nu while (defaultImageIndex >= (int)secureBlocks.Size()) secureBlocks.AddNew(); CUniqBlocks &secUniqBlocks = secureBlocks[defaultImageIndex]; - rootItem.SecurityId = secUniqBlocks.AddUniq((const Byte *)data, dataSize); + rootItem.SecurityId = (int)secUniqBlocks.AddUniq((const Byte *)data, dataSize); } } IArchiveGetRootProps *thisGetRoot = isUpdate ? this : NULL; - RINOK(GetRootTime(getRootProps, thisGetRoot, kpidCTime, rootItem.CTime)); - RINOK(GetRootTime(getRootProps, thisGetRoot, kpidATime, rootItem.ATime)); - RINOK(GetRootTime(getRootProps, thisGetRoot, kpidMTime, rootItem.MTime)); + if (_timeOptions.Write_CTime.Val) RINOK(GetRootTime(getRootProps, thisGetRoot, kpidCTime, rootItem.CTime)) + if (_timeOptions.Write_ATime.Val) RINOK(GetRootTime(getRootProps, thisGetRoot, kpidATime, rootItem.ATime)) + if (_timeOptions.Write_MTime.Val) RINOK(GetRootTime(getRootProps, thisGetRoot, kpidMTime, rootItem.MTime)) { NCOM::CPropVariant prop; if (getRootProps) { - RINOK(getRootProps->GetRootProp(kpidAttrib, &prop)); + RINOK(getRootProps->GetRootProp(kpidAttrib, &prop)) if (prop.vt == VT_UI4) rootItem.Attrib = prop.ulVal; else if (prop.vt != VT_EMPTY) @@ -876,7 +879,7 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 nu } if (prop.vt == VT_EMPTY && thisGetRoot) { - RINOK(GetRootProp(kpidAttrib, &prop)); + RINOK(GetRootProp(kpidAttrib, &prop)) if (prop.vt == VT_UI4) rootItem.Attrib = prop.ulVal; else if (prop.vt != VT_EMPTY) @@ -896,9 +899,10 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 nu for (i = 0; i < numItems; i++) { CUpdateItem ui; + ui.Construct(); UInt32 indexInArchive; Int32 newData, newProps; - RINOK(callback->GetUpdateItemInfo(i, &newData, &newProps, &indexInArchive)); + RINOK(callback->GetUpdateItemInfo(i, &newData, &newProps, &indexInArchive)) if (newData == 0 || newProps == 0) { @@ -930,16 +934,16 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 nu } if (newData == 0) - ui.InArcIndex = indexInArchive; + ui.InArcIndex = (Int32)indexInArchive; } // we set arcIndex only if we must use old props - Int32 arcIndex = (newProps ? -1 : indexInArchive); + const Int32 arcIndex = (newProps ? -1 : (Int32)indexInArchive); bool isDir = false; { NCOM::CPropVariant prop; - RINOK(GetOutProperty(callback, i, arcIndex, kpidIsDir, &prop)); + RINOK(GetOutProperty(callback, i, arcIndex, kpidIsDir, &prop)) if (prop.vt == VT_BOOL) isDir = (prop.boolVal != VARIANT_FALSE); else if (prop.vt != VT_EMPTY) @@ -949,7 +953,7 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 nu bool isAltStream = false; { NCOM::CPropVariant prop; - RINOK(GetOutProperty(callback, i, arcIndex, kpidIsAltStream, &prop)); + RINOK(GetOutProperty(callback, i, arcIndex, kpidIsAltStream, &prop)) if (prop.vt == VT_BOOL) isAltStream = (prop.boolVal != VARIANT_FALSE); else if (prop.vt != VT_EMPTY) @@ -976,11 +980,11 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 nu if (newData) { - RINOK(callback->GetProperty(i, kpidSize, &prop)); + RINOK(callback->GetProperty(i, kpidSize, &prop)) } else { - RINOK(GetProperty(indexInArchive, kpidSize, &prop)); + RINOK(GetProperty(indexInArchive, kpidSize, &prop)) } if (prop.vt == VT_UI8) @@ -992,7 +996,7 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 nu { NCOM::CPropVariant propPath; const wchar_t *path = NULL; - RINOK(GetOutProperty(callback, i, arcIndex, kpidPath, &propPath)); + RINOK(GetOutProperty(callback, i, arcIndex, kpidPath, &propPath)) if (propPath.vt == VT_BSTR) path = propPath.bstrVal; else if (propPath.vt != VT_EMPTY) @@ -1024,7 +1028,7 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 nu imageIndex = (int)val - 1; if (imageIndex < (int)isChangedImage.Size()) - if (!isChangedImage[imageIndex]) + if (!isChangedImage[imageIndex]) return E_FAIL; AddTrees(trees, db.MetaItems, ri, imageIndex); @@ -1046,8 +1050,8 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 nu CAltStream ss; ss.Size = size; ss.Name = end + 1; - ss.UpdateIndex = db.UpdateItems.Size(); - ui.AltStreamIndex = db.MetaItems[ui.MetaIndex].AltStreams.Add(ss); + ss.UpdateIndex = (int)db.UpdateItems.Size(); + ui.AltStreamIndex = (int)db.MetaItems[ui.MetaIndex].AltStreams.Add(ss); } else if (c == WCHAR_PATH_SEPARATOR || c == L'/') { @@ -1063,7 +1067,7 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 nu { for (;;) { - wchar_t c = *path++; + const wchar_t c = *path++; if (c == 0) break; if (c == WCHAR_PATH_SEPARATOR || c == L'/') @@ -1072,14 +1076,34 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 nu if (!curItem->FindDir(db.MetaItems, fileName, indexOfDir)) { CDir &dir = curItem->Dirs.InsertNew(indexOfDir); - dir.MetaIndex = db.MetaItems.Add(ri); + dir.MetaIndex = (int)db.MetaItems.Add(ri); db.MetaItems.Back().Name = fileName; } curItem = &curItem->Dirs[indexOfDir]; fileName.Empty(); } else + { + /* + #if WCHAR_MAX > 0xffff + if (c >= 0x10000) + { + c -= 0x10000; + + if (c < (1 << 20)) + { + wchar_t c0 = 0xd800 + ((c >> 10) & 0x3FF); + fileName += c0; + c = 0xdc00 + (c & 0x3FF); + } + else + c = '_'; // we change character unsupported by UTF16 + } + #endif + */ + fileName += c; + } } if (isAltStream) @@ -1091,7 +1115,7 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 nu // we want to support cases of c::substream, where c: is drive name if (colonPos == 1 && fileName[2] == L':' && IS_LETTER_CHAR(fileName[0])) colonPos = 2; - const UString mainName = fileName.Left(colonPos); + const UString mainName = fileName.Left((unsigned)colonPos); unsigned indexOfDir; if (mainName.IsEmpty()) @@ -1102,11 +1126,11 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 nu { for (int j = (int)curItem->Files.Size() - 1; j >= 0; j--) { - int metaIndex = curItem->Files[j]; + const unsigned metaIndex = curItem->Files[j]; const CMetaItem &mi = db.MetaItems[metaIndex]; if (CompareFileNames(mainName, mi.Name) == 0) { - ui.MetaIndex = metaIndex; + ui.MetaIndex = (int)metaIndex; break; } } @@ -1117,8 +1141,8 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 nu CAltStream ss; ss.Size = size; ss.Name = fileName.Ptr(colonPos + 1); - ss.UpdateIndex = db.UpdateItems.Size(); - ui.AltStreamIndex = db.MetaItems[ui.MetaIndex].AltStreams.Add(ss); + ss.UpdateIndex = (int)db.UpdateItems.Size(); + ui.AltStreamIndex = (int)db.MetaItems[ui.MetaIndex].AltStreams.Add(ss); } } } @@ -1128,7 +1152,7 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 nu { if (!isRootImageDir) { - ui.MetaIndex = db.MetaItems.Size(); + ui.MetaIndex = (int)db.MetaItems.Size(); db.MetaItems.AddNew(); } @@ -1136,10 +1160,10 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 nu mi.Size = size; mi.IsDir = isDir; mi.Name = fileName; - mi.UpdateIndex = db.UpdateItems.Size(); + mi.UpdateIndex = (int)db.UpdateItems.Size(); { NCOM::CPropVariant prop; - RINOK(GetOutProperty(callback, i, arcIndex, kpidAttrib, &prop)); + RINOK(GetOutProperty(callback, i, arcIndex, kpidAttrib, &prop)) if (prop.vt == VT_EMPTY) mi.Attrib = 0; else if (prop.vt == VT_UI4) @@ -1149,13 +1173,17 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 nu if (isDir) mi.Attrib |= FILE_ATTRIBUTE_DIRECTORY; } - RINOK(GetTime(callback, i, arcIndex, kpidCTime, mi.CTime)); - RINOK(GetTime(callback, i, arcIndex, kpidATime, mi.ATime)); - RINOK(GetTime(callback, i, arcIndex, kpidMTime, mi.MTime)); + + if (arcIndex != -1 || _timeOptions.Write_CTime.Val) + RINOK(GetTime(callback, i, arcIndex, kpidCTime, mi.CTime)) + if (arcIndex != -1 || _timeOptions.Write_ATime.Val) + RINOK(GetTime(callback, i, arcIndex, kpidATime, mi.ATime)) + if (arcIndex != -1 || _timeOptions.Write_MTime.Val) + RINOK(GetTime(callback, i, arcIndex, kpidMTime, mi.MTime)) { NCOM::CPropVariant prop; - RINOK(GetOutProperty(callback, i, arcIndex, kpidShortName, &prop)); + RINOK(GetOutProperty(callback, i, arcIndex, kpidShortName, &prop)) if (prop.vt == VT_BSTR) mi.ShortName.SetFromBstr(prop.bstrVal); else if (prop.vt != VT_EMPTY) @@ -1178,7 +1206,7 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 nu if (arcIndex >= 0) { - GetRawProp(arcIndex, kpidNtSecure, &data, &dataSize, &propType); + GetRawProp((UInt32)arcIndex, kpidNtSecure, &data, &dataSize, &propType); } else { @@ -1189,7 +1217,7 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 nu { if (propType != NPropDataType::kRaw) return E_FAIL; - mi.SecurityId = secUniqBlocks.AddUniq((const Byte *)data, dataSize); + mi.SecurityId = (int)secUniqBlocks.AddUniq((const Byte *)data, dataSize); } data = NULL; @@ -1198,7 +1226,7 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 nu if (arcIndex >= 0) { - GetRawProp(arcIndex, kpidNtReparse, &data, &dataSize, &propType); + GetRawProp((UInt32)arcIndex, kpidNtReparse, &data, &dataSize, &propType); } else { @@ -1224,7 +1252,7 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 nu curItem->Dirs.InsertNew(indexOfDir).MetaIndex = ui.MetaIndex; } else - curItem->Files.Add(ui.MetaIndex); + curItem->Files.Add((unsigned)ui.MetaIndex); } } @@ -1242,7 +1270,7 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 nu if (!isChangedImage[i]) numNewImages = i + 1; - AddTrees(trees, db.MetaItems, ri, numNewImages - 1); + AddTrees(trees, db.MetaItems, ri, (int)numNewImages - 1); for (i = 0; i < trees.Size(); i++) if (i >= isChangedImage.Size() || isChangedImage[i]) @@ -1324,15 +1352,12 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 nu complexity += rs.PackSize; } - RINOK(callback->SetTotal(complexity)); + RINOK(callback->SetTotal(complexity)) UInt64 totalComplexity = complexity; - NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder; - CMyComPtr copyCoder = copyCoderSpec; - - CLocalProgress *lps = new CLocalProgress; - CMyComPtr progress = lps; + CMyComPtr2_Create lps; lps->Init(callback, true); + CMyComPtr2_Create copyCoder; complexity = 0; @@ -1351,19 +1376,23 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 nu header.ChunkSizeBits = srcHeader.ChunkSizeBits; } + CMyComPtr setRestriction; + outSeqStream->QueryInterface(IID_IStreamSetRestriction, (void **)&setRestriction); + if (setRestriction) + RINOK(setRestriction->SetRestriction(0, kHeaderSizeMax)) + { Byte buf[kHeaderSizeMax]; header.WriteTo(buf); - RINOK(WriteStream(outStream, buf, kHeaderSizeMax)); + RINOK(WriteStream(outStream, buf, kHeaderSizeMax)) } UInt64 curPos = kHeaderSizeMax; - CInStreamWithSha1 *inShaStreamSpec = new CInStreamWithSha1; - CMyComPtr inShaStream = inShaStreamSpec; + CMyComPtr2_Create inShaStream; CLimitedSequentialInStream *inStreamLimitedSpec = NULL; - CMyComPtr inStreamLimited; + CMyComPtr inStreamLimited; if (_volumes.Size() == 2) { inStreamLimitedSpec = new CLimitedSequentialInStream; @@ -1373,7 +1402,7 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 nu CRecordVector streams; - CUIntVector sortedHashes; // indexes to streams, sorted by SHA1 + CSortedIndex sortedHashes; // indexes to streams, sorted by SHA1 // ---------- Copy unchanged data streams ---------- @@ -1385,7 +1414,7 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 nu const CStreamInfo &siOld = _db.DataStreams[i]; const CResource &rs = siOld.Resource; - unsigned numRefs = streamsRefs[i]; + const unsigned numRefs = streamsRefs[i]; if (numRefs == 0) { @@ -1396,9 +1425,9 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 nu } lps->InSize = lps->OutSize = complexity; - RINOK(lps->SetCur()); + RINOK(lps->SetCur()) - int streamIndex = streams.Size(); + const unsigned streamIndex = streams.Size(); CStreamInfo s; s.Resource = rs; s.PartNumber = 1; @@ -1432,17 +1461,17 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 nu if (!rs.IsSolid() || rs.IsSolidSmall()) { - int find = AddUniqHash(&streams.Front(), sortedHashes, siOld.Hash, streamIndex); - if (find >= 0) + const int find = AddUniqHash(streams.ConstData(), sortedHashes, siOld.Hash, (int)streamIndex); + if (find != -1) return E_FAIL; // two streams with same SHA-1 } if (!rs.IsSolid() || rs.IsSolidBig()) { - RINOK(_volumes[siOld.PartNumber].Stream->Seek(rs.Offset, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekSet(_volumes[siOld.PartNumber].Stream, rs.Offset)) inStreamLimitedSpec->Init(rs.PackSize); - RINOK(copyCoder->Code(inStreamLimited, outStream, NULL, NULL, progress)); - if (copyCoderSpec->TotalSize != rs.PackSize) + RINOK(copyCoder.Interface()->Code(inStreamLimited, outStream, NULL, NULL, lps)) + if (copyCoder->TotalSize != rs.PackSize) return E_FAIL; s.Resource.Offset = curPos; curPos += rs.PackSize; @@ -1460,7 +1489,7 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 nu for (i = 0; i < db.UpdateIndexes.Size(); i++) { lps->InSize = lps->OutSize = complexity; - RINOK(lps->SetCur()); + RINOK(lps->SetCur()) const CUpdateItem &ui = db.UpdateItems[db.UpdateIndexes[i]]; CMetaItem &mi = db.MetaItems[ui.MetaIndex]; UInt64 size = 0; @@ -1504,9 +1533,9 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 nu const CStreamInfo &siOld = _db.DataStreams[item.StreamIndex]; - int index = AddUniqHash(&streams.Front(), sortedHashes, siOld.Hash, -1); + const int index = AddUniqHash(streams.ConstData(), sortedHashes, siOld.Hash, -1); // we must have written that stream already - if (index < 0) + if (index == -1) return E_FAIL; if (ui.AltStreamIndex < 0) @@ -1532,7 +1561,7 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 nu } else { - RINOK(res); + RINOK(res) int miIndex = -1; @@ -1551,23 +1580,23 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 nu if (getProps2->GetProps2(&props) == S_OK) { mi.Attrib = props.Attrib; - mi.CTime = props.CTime; - mi.ATime = props.ATime; - mi.MTime = props.MTime; + if (_timeOptions.Write_CTime.Val) mi.CTime = props.CTime; + if (_timeOptions.Write_ATime.Val) mi.ATime = props.ATime; + if (_timeOptions.Write_MTime.Val) mi.MTime = props.MTime; mi.FileID = props.FileID_Low; if (props.NumLinks <= 1) mi.FileID = 0; mi.VolID = props.VolID; if (mi.FileID != 0) - miIndex = AddToHardLinkList(db.MetaItems, ui.MetaIndex, hlIndexes); + miIndex = AddToHardLinkList(db.MetaItems, (unsigned)ui.MetaIndex, hlIndexes); if (props.Size != size && props.Size != (UInt64)(Int64)-1) { - Int64 delta = (Int64)props.Size - (Int64)size; - Int64 newComplexity = totalComplexity + delta; + const Int64 delta = (Int64)props.Size - (Int64)size; + const Int64 newComplexity = (Int64)totalComplexity + delta; if (newComplexity > 0) { - totalComplexity = newComplexity; + totalComplexity = (UInt64)newComplexity; callback->SetTotal(totalComplexity); } mi.Size = props.Size; @@ -1590,19 +1619,19 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 nu return E_FAIL; NCrypto::NSha1::CContext sha1; sha1.Init(); - size_t packSize = mi.Reparse.Size() - 8; + const size_t packSize = mi.Reparse.Size() - 8; sha1.Update((const Byte *)mi.Reparse + 8, packSize); Byte hash[kHashSize]; sha1.Final(hash); - int index = AddUniqHash(&streams.Front(), sortedHashes, hash, streams.Size()); + int index = AddUniqHash(streams.ConstData(), sortedHashes, hash, (int)streams.Size()); - if (index >= 0) + if (index != -1) streams[index].RefCount++; else { - index = streams.Size(); - RINOK(WriteStream(outStream, (const Byte *)mi.Reparse + 8, packSize)); + index = (int)streams.Size(); + RINOK(WriteStream(outStream, (const Byte *)mi.Reparse + 8, packSize)) CStreamInfo s; s.Resource.PackSize = packSize; s.Resource.Offset = curPos; @@ -1624,9 +1653,13 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 nu } else { - inShaStreamSpec->SetStream(fileInStream); + inShaStream->SetStream(fileInStream); + + CMyComPtr inSeekStream; + fileInStream.QueryInterface(IID_IInStream, (void **)&inSeekStream); + fileInStream.Release(); - inShaStreamSpec->Init(); + inShaStream->Init(); UInt64 offsetBlockSize = 0; /* if (useResourceCompression) @@ -1640,54 +1673,88 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 nu } } */ + + // 22.02: we use additional read-only pass to calculate SHA-1 + bool needWritePass = true; + int index = -1; - RINOK(copyCoder->Code(inShaStream, outStream, NULL, NULL, progress)); - size = copyCoderSpec->TotalSize; - - if (size != 0) + if (inSeekStream /* && !sortedHashes.IsEmpty() */) { - Byte hash[kHashSize]; - UInt64 packSize = offsetBlockSize + size; - inShaStreamSpec->Final(hash); - - int index = AddUniqHash(&streams.Front(), sortedHashes, hash, streams.Size()); - - if (index >= 0) - { - streams[index].RefCount++; - outStream->Seek(-(Int64)packSize, STREAM_SEEK_CUR, &curPos); - outStream->SetSize(curPos); - } + RINOK(copyCoder.Interface()->Code(inShaStream, NULL, NULL, NULL, lps)) + size = copyCoder->TotalSize; + if (size == 0) + needWritePass = false; else { - index = streams.Size(); - CStreamInfo s; - s.Resource.PackSize = packSize; - s.Resource.Offset = curPos; - s.Resource.UnpackSize = size; - s.Resource.Flags = 0; - /* - if (useResourceCompression) - s.Resource.Flags = NResourceFlags::Compressed; - */ - s.PartNumber = 1; - s.RefCount = 1; - memcpy(s.Hash, hash, kHashSize); - curPos += packSize; - - streams.Add(s); + Byte hash[kHashSize]; + inShaStream->Final(hash); + + index = AddUniqHash(streams.ConstData(), sortedHashes, hash, -1); + if (index != -1) + { + streams[index].RefCount++; + needWritePass = false; + } + else + { + RINOK(InStream_SeekToBegin(inSeekStream)) + inShaStream->Init(); + } } - + } + + if (needWritePass) + { + RINOK(copyCoder.Interface()->Code(inShaStream, outStream, NULL, NULL, lps)) + size = copyCoder->TotalSize; + } + + if (size != 0) + { + if (needWritePass) + { + Byte hash[kHashSize]; + const UInt64 packSize = offsetBlockSize + size; + inShaStream->Final(hash); + + index = AddUniqHash(streams.ConstData(), sortedHashes, hash, (int)streams.Size()); + + if (index != -1) + { + streams[index].RefCount++; + outStream->Seek(-(Int64)packSize, STREAM_SEEK_CUR, &curPos); + outStream->SetSize(curPos); + } + else + { + index = (int)streams.Size(); + CStreamInfo s; + s.Resource.PackSize = packSize; + s.Resource.Offset = curPos; + s.Resource.UnpackSize = size; + s.Resource.Flags = 0; + /* + if (useResourceCompression) + s.Resource.Flags = NResourceFlags::Compressed; + */ + s.PartNumber = 1; + s.RefCount = 1; + memcpy(s.Hash, hash, kHashSize); + curPos += packSize; + + streams.Add(s); + } + } // needWritePass if (ui.AltStreamIndex < 0) mi.HashIndex = index; else mi.AltStreams[ui.AltStreamIndex].HashIndex = index; - } + } // (size != 0) } } fileInStream.Release(); complexity += size; - RINOK(callback->SetOperationResult(NArchive::NUpdate::NOperationResult::kOK)); + RINOK(callback->SetOperationResult(NArchive::NUpdate::NOperationResult::kOK)) } while (secureBlocks.Size() < numNewImages) @@ -1700,15 +1767,15 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 nu for (i = 0; i < numNewImages; i++) { lps->InSize = lps->OutSize = complexity; - RINOK(lps->SetCur()); + RINOK(lps->SetCur()) if (i < isChangedImage.Size() && !isChangedImage[i]) { CStreamInfo s = _db.MetaStreams[i]; - RINOK(_volumes[1].Stream->Seek(s.Resource.Offset, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekSet(_volumes[1].Stream, s.Resource.Offset)) inStreamLimitedSpec->Init(s.Resource.PackSize); - RINOK(copyCoder->Code(inStreamLimited, outStream, NULL, NULL, progress)); - if (copyCoderSpec->TotalSize != s.Resource.PackSize) + RINOK(copyCoder.Interface()->Code(inStreamLimited, outStream, NULL, NULL, lps)) + if (copyCoder->TotalSize != s.Resource.PackSize) return E_FAIL; s.Resource.Offset = curPos; @@ -1744,14 +1811,14 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 nu CByteArr meta(pos); - Set32((Byte *)meta + 4, secBufs.Size()); // num security entries + Set32((Byte *)meta + 4, secBufs.Size()) // num security entries pos = kSecuritySize; if (secBufs.Size() == 0) { // we can write 0 here only if there is no security data, imageX does it, // but some programs expect size = 8 - Set32((Byte *)meta, 8); // size of security data + Set32((Byte *)meta, 8) // size of security data // Set32((Byte *)meta, 0); } else @@ -1759,7 +1826,7 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 nu unsigned k; for (k = 0; k < secBufs.Size(); k++, pos += 8) { - Set64(meta + pos, secBufs[k].Size()); + Set64(meta + pos, secBufs[k].Size()) } for (k = 0; k < secBufs.Size(); k++) { @@ -1773,10 +1840,10 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 nu } while ((pos & 7) != 0) meta[pos++] = 0; - Set32((Byte *)meta, (UInt32)pos); // size of security data + Set32((Byte *)meta, (UInt32)pos) // size of security data } - db.Hashes = &streams.Front(); + db.Hashes = streams.ConstData(); db.WriteTree(tree, (Byte *)meta, pos); { @@ -1803,14 +1870,14 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 nu header.BootIndex = _bootIndex; } - RINOK(WriteStream(outStream, (const Byte *)meta, pos)); + RINOK(WriteStream(outStream, (const Byte *)meta, pos)) meta.Free(); curPos += pos; } } lps->InSize = lps->OutSize = complexity; - RINOK(lps->SetCur()); + RINOK(lps->SetCur()) header.OffsetResource.UnpackSize = header.OffsetResource.PackSize = (UInt64)streams.Size() * kStreamInfoSize; header.OffsetResource.Offset = curPos; @@ -1824,21 +1891,21 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 nu { Byte buf[kStreamInfoSize]; streams[i].WriteTo(buf); - RINOK(WriteStream(outStream, buf, kStreamInfoSize)); + RINOK(WriteStream(outStream, buf, kStreamInfoSize)) curPos += kStreamInfoSize; } - AString xml = ""; + AString xml (""); AddTagUInt64_ToString(xml, "TOTALBYTES", curPos); for (i = 0; i < trees.Size(); i++) { - CDir &tree = trees[i]; + const CDir &tree = trees[i]; CXmlItem item; if (_xmls.Size() == 1) { const CWimXml &_oldXml = _xmls[0]; - if ((int)i < _oldXml.Images.Size()) + if (i < _oldXml.Images.Size()) { // int ttt = _oldXml.Images[i].ItemIndexInXml; item = _oldXml.Xml.Root.SubItems[_oldXml.Images[i].ItemIndexInXml]; @@ -1875,16 +1942,19 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 nu UString utf16; if (!ConvertUTF8ToUnicode(xml, utf16)) return S_FALSE; - xmlSize = (utf16.Len() + 1) * 2; + xmlSize = ((size_t)utf16.Len() + 1) * 2; CByteArr xmlBuf(xmlSize); - Set16((Byte *)xmlBuf, 0xFEFF); + Set16((Byte *)xmlBuf, 0xFEFF) for (i = 0; i < (unsigned)utf16.Len(); i++) - Set16((Byte *)xmlBuf + 2 + i * 2, utf16[i]); - RINOK(WriteStream(outStream, (const Byte *)xmlBuf, xmlSize)); + { + Set16((Byte *)xmlBuf + 2 + (size_t)i * 2, (UInt16)utf16[i]) + } + RINOK(WriteStream(outStream, (const Byte *)xmlBuf, xmlSize)) } - header.XmlResource.UnpackSize = header.XmlResource.PackSize = xmlSize; + header.XmlResource.UnpackSize = + header.XmlResource.PackSize = xmlSize; header.XmlResource.Offset = curPos; header.XmlResource.Flags = NResourceFlags::kMetadata; @@ -1893,9 +1963,14 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outSeqStream, UInt32 nu { Byte buf[kHeaderSizeMax]; header.WriteTo(buf); - return WriteStream(outStream, buf, kHeaderSizeMax); + RINOK(WriteStream(outStream, buf, kHeaderSizeMax)) } + if (setRestriction) + RINOK(setRestriction->SetRestriction(0, 0)) + + return S_OK; + COM_TRY_END } diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Wim/WimIn.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/Wim/WimIn.cpp index f35c2d50..33fbe821 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Wim/WimIn.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Wim/WimIn.cpp @@ -34,19 +34,19 @@ namespace NArchive { namespace NWim { -static int inline GetLog(UInt32 num) +static bool inline GetLog_val_min_dest(const UInt32 val, unsigned i, unsigned &dest) { - for (int i = 0; i < 32; i++) - if (((UInt32)1 << i) == num) - return i; - return -1; -} - - -CUnpacker::~CUnpacker() -{ - if (lzmsDecoder) - delete lzmsDecoder; + UInt32 v = (UInt32)1 << i; + for (; i < 32; i++) + { + if (v == val) + { + dest = i; + return true; + } + v += v; + } + return false; } @@ -64,25 +64,27 @@ HRESULT CUnpacker::UnpackChunk( } else if (method == NMethod::kLZX) { - if (!lzxDecoder) - { - lzxDecoderSpec = new NCompress::NLzx::CDecoder(true); - lzxDecoder = lzxDecoderSpec; - } + lzxDecoder.Create_if_Empty(); + lzxDecoder->Set_WimMode(true); } else if (method == NMethod::kLZMS) { - if (!lzmsDecoder) - lzmsDecoder = new NCompress::NLzms::CDecoder(); + lzmsDecoder.Create_if_Empty(); } else return E_NOTIMPL; const size_t chunkSize = (size_t)1 << chunkSizeBits; - - unpackBuf.EnsureCapacity(chunkSize); - if (!unpackBuf.Data) - return E_OUTOFMEMORY; + + { + const unsigned + kAdditionalOutputBufSize = MyMax(NCompress::NLzx:: + kAdditionalOutputBufSize, NCompress::NXpress:: + kAdditionalOutputBufSize); + unpackBuf.EnsureCapacity(chunkSize + kAdditionalOutputBufSize); + if (!unpackBuf.Data) + return E_OUTOFMEMORY; + } HRESULT res = S_FALSE; size_t unpackedSize = 0; @@ -95,34 +97,38 @@ HRESULT CUnpacker::UnpackChunk( } else if (inSize < chunkSize) { - packBuf.EnsureCapacity(chunkSize); + const unsigned kAdditionalInputSize = 32; + packBuf.EnsureCapacity(chunkSize + kAdditionalInputSize); if (!packBuf.Data) return E_OUTOFMEMORY; - RINOK(ReadStream_FALSE(inStream, packBuf.Data, inSize)); + RINOK(ReadStream_FALSE(inStream, packBuf.Data, inSize)) + memset(packBuf.Data + inSize, 0xff, kAdditionalInputSize); TotalPacked += inSize; if (method == NMethod::kXPRESS) { - res = NCompress::NXpress::Decode(packBuf.Data, inSize, unpackBuf.Data, outSize); + res = NCompress::NXpress::Decode_WithExceedWrite(packBuf.Data, inSize, unpackBuf.Data, outSize); if (res == S_OK) unpackedSize = outSize; } else if (method == NMethod::kLZX) { - lzxDecoderSpec->SetExternalWindow(unpackBuf.Data, chunkSizeBits); - lzxDecoderSpec->KeepHistoryForNext = false; - lzxDecoderSpec->SetKeepHistory(false); - res = lzxDecoderSpec->Code(packBuf.Data, inSize, (UInt32)outSize); - unpackedSize = lzxDecoderSpec->GetUnpackSize(); - if (res == S_OK && !lzxDecoderSpec->WasBlockFinished()) + res = lzxDecoder->Set_ExternalWindow_DictBits(unpackBuf.Data, chunkSizeBits); + if (res != S_OK) + return E_NOTIMPL; + lzxDecoder->Set_KeepHistoryForNext(false); + lzxDecoder->Set_KeepHistory(false); + res = lzxDecoder->Code_WithExceedReadWrite(packBuf.Data, inSize, (UInt32)outSize); + unpackedSize = lzxDecoder->GetUnpackSize(); + if (res == S_OK && !lzxDecoder->WasBlockFinished()) res = S_FALSE; } else { res = lzmsDecoder->Code(packBuf.Data, inSize, unpackBuf.Data, outSize); - unpackedSize = lzmsDecoder->GetUnpackSize();; + unpackedSize = lzmsDecoder->GetUnpackSize(); } } @@ -139,7 +145,7 @@ HRESULT CUnpacker::UnpackChunk( if (outStream) { - RINOK(WriteStream(outStream, unpackBuf.Data, outSize)); + RINOK(WriteStream(outStream, unpackBuf.Data, outSize)) } return res; @@ -156,26 +162,21 @@ HRESULT CUnpacker::Unpack2( { if (!resource.IsCompressed() && !resource.IsSolid()) { - if (!copyCoder) - { - copyCoderSpec = new NCompress::CCopyCoder; - copyCoder = copyCoderSpec; - } + copyCoder.Create_if_Empty(); - CLimitedSequentialInStream *limitedStreamSpec = new CLimitedSequentialInStream(); - CMyComPtr limitedStream = limitedStreamSpec; - limitedStreamSpec->SetStream(inStream); + CMyComPtr2_Create limitedStream; + limitedStream->SetStream(inStream); - RINOK(inStream->Seek(resource.Offset, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekSet(inStream, resource.Offset)) if (resource.PackSize != resource.UnpackSize) return S_FALSE; - limitedStreamSpec->Init(resource.PackSize); + limitedStream->Init(resource.PackSize); TotalPacked += resource.PackSize; - HRESULT res = copyCoder->Code(limitedStream, outStream, NULL, NULL, progress); + HRESULT res = copyCoder.Interface()->Code(limitedStream, outStream, NULL, NULL, progress); - if (res == S_OK && copyCoderSpec->TotalSize != resource.UnpackSize) + if (res == S_OK && copyCoder->TotalSize != resource.UnpackSize) res = S_FALSE; return res; } @@ -219,7 +220,7 @@ HRESULT CUnpacker::Unpack2( size_t cur = chunkSize - offsetInChunk; if (cur > rem) cur = (size_t)rem; - RINOK(WriteStream(outStream, unpackBuf.Data + offsetInChunk, cur)); + RINOK(WriteStream(outStream, unpackBuf.Data + offsetInChunk, cur)) outProcessed += cur; rem -= cur; offsetInChunk = 0; @@ -231,20 +232,20 @@ HRESULT CUnpacker::Unpack2( if (rem == 0) return S_OK; - UInt64 offset = ss.Chunks[chunkIndex]; - UInt64 packSize = ss.GetChunkPackSize(chunkIndex); + const UInt64 offset = ss.Chunks[chunkIndex]; + const UInt64 packSize = ss.GetChunkPackSize(chunkIndex); const CResource &rs = db->DataStreams[ss.StreamIndex].Resource; - RINOK(inStream->Seek(rs.Offset + ss.HeadersSize + offset, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekSet(inStream, rs.Offset + ss.HeadersSize + offset)) size_t cur = chunkSize; - UInt64 unpackRem = ss.UnpackSize - ((UInt64)chunkIndex << chunkSizeBits); + const UInt64 unpackRem = ss.UnpackSize - ((UInt64)chunkIndex << chunkSizeBits); if (cur > unpackRem) cur = (size_t)unpackRem; _solidIndex = -1; _unpackedChunkIndex = 0; - HRESULT res = UnpackChunk(inStream, ss.Method, chunkSizeBits, (size_t)packSize, cur, NULL); + const HRESULT res = UnpackChunk(inStream, (unsigned)ss.Method, chunkSizeBits, (size_t)packSize, cur, NULL); if (res != S_OK) { @@ -264,11 +265,11 @@ HRESULT CUnpacker::Unpack2( if (cur > rem) cur = (size_t)rem; - RINOK(WriteStream(outStream, unpackBuf.Data + offsetInChunk, cur)); + RINOK(WriteStream(outStream, unpackBuf.Data + offsetInChunk, cur)) if (progress) { - RINOK(progress->SetRatioInfo(&packProcessed, &outProcessed)); + RINOK(progress->SetRatioInfo(&packProcessed, &outProcessed)) packProcessed += packSize; outProcessed += cur; } @@ -300,17 +301,17 @@ HRESULT CUnpacker::Unpack2( UInt64 packDataSize; size_t numChunks; { - UInt64 numChunks64 = (unpackSize + (((UInt32)1 << chunkSizeBits) - 1)) >> chunkSizeBits; - UInt64 sizesBufSize64 = (numChunks64 - 1) << entrySizeShifts; + const UInt64 numChunks64 = (unpackSize + (((UInt32)1 << chunkSizeBits) - 1)) >> chunkSizeBits; + const UInt64 sizesBufSize64 = (numChunks64 - 1) << entrySizeShifts; if (sizesBufSize64 > resource.PackSize) return S_FALSE; packDataSize = resource.PackSize - sizesBufSize64; - size_t sizesBufSize = (size_t)sizesBufSize64; + const size_t sizesBufSize = (size_t)sizesBufSize64; if (sizesBufSize != sizesBufSize64) return E_OUTOFMEMORY; sizesBuf.AllocAtLeast(sizesBufSize); - RINOK(inStream->Seek(baseOffset, STREAM_SEEK_SET, NULL)); - RINOK(ReadStream_FALSE(inStream, sizesBuf, sizesBufSize)); + RINOK(InStream_SeekSet(inStream, baseOffset)) + RINOK(ReadStream_FALSE(inStream, sizesBuf, sizesBufSize)) baseOffset += sizesBufSize64; numChunks = (size_t)numChunks64; } @@ -339,11 +340,11 @@ HRESULT CUnpacker::Unpack2( if (inSize != inSize64) return S_FALSE; - RINOK(inStream->Seek(baseOffset + offset, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekSet(inStream, baseOffset + offset)) if (progress) { - RINOK(progress->SetRatioInfo(&offset, &outProcessed)); + RINOK(progress->SetRatioInfo(&offset, &outProcessed)) } size_t outSize = (size_t)1 << chunkSizeBits; @@ -351,7 +352,7 @@ HRESULT CUnpacker::Unpack2( if (outSize > rem) outSize = (size_t)rem; - RINOK(UnpackChunk(inStream, header.GetMethod(), chunkSizeBits, inSize, outSize, outStream)); + RINOK(UnpackChunk(inStream, header.GetMethod(), chunkSizeBits, inSize, outSize, outStream)) outProcessed += outSize; offset = nextOffset; @@ -364,24 +365,13 @@ HRESULT CUnpacker::Unpack2( HRESULT CUnpacker::Unpack(IInStream *inStream, const CResource &resource, const CHeader &header, const CDatabase *db, ISequentialOutStream *outStream, ICompressProgressInfo *progress, Byte *digest) { - COutStreamWithSha1 *shaStreamSpec = NULL; - CMyComPtr shaStream; - + CMyComPtr2_Create shaStream; // outStream can be NULL, so we use COutStreamWithSha1 even if sha1 is not required - // if (digest) - { - shaStreamSpec = new COutStreamWithSha1(); - shaStream = shaStreamSpec; - shaStreamSpec->SetStream(outStream); - shaStreamSpec->Init(digest != NULL); - outStream = shaStream; - } - - HRESULT res = Unpack2(inStream, resource, header, db, outStream, progress); - + shaStream->SetStream(outStream); + shaStream->Init(digest != NULL); + const HRESULT res = Unpack2(inStream, resource, header, db, shaStream, progress); if (digest) - shaStreamSpec->Final(digest); - + shaStream->Final(digest); return res; } @@ -392,21 +382,16 @@ HRESULT CUnpacker::UnpackData(IInStream *inStream, CByteBuffer &buf, Byte *digest) { // if (resource.IsSolid()) return E_NOTIMPL; - UInt64 unpackSize64 = resource.UnpackSize; if (db) unpackSize64 = db->Get_UnpackSize_of_Resource(resource); - - size_t size = (size_t)unpackSize64; + const size_t size = (size_t)unpackSize64; if (size != unpackSize64) return E_OUTOFMEMORY; - buf.Alloc(size); - CBufPtrSeqOutStream *outStreamSpec = new CBufPtrSeqOutStream(); - CMyComPtr outStream = outStreamSpec; - outStreamSpec->Init((Byte *)buf, size); - + CMyComPtr2_Create outStream; + outStream->Init((Byte *)buf, size); return Unpack(inStream, resource, header, db, outStream, NULL, digest); } @@ -442,7 +427,7 @@ static inline void ParseStream(bool oldVersion, const Byte *p, CStreamInfo &s) } -static const char *kLongPath = "[LongPath]"; +#define kLongPath "[LONG_PATH]" STRING_PATH_SEPARATOR "[LONG_PATH_ITEM]" void CDatabase::GetShortName(unsigned index, NWindows::NCOM::CPropVariant &name) const { @@ -492,8 +477,8 @@ void CDatabase::GetItemName(unsigned index, NWindows::NCOM::CPropVariant &name) void CDatabase::GetItemPath(unsigned index1, bool showImageNumber, NWindows::NCOM::CPropVariant &path) const { unsigned size = 0; - int index = index1; - int imageIndex = Items[index].ImageIndex; + int index = (int)index1; + const int imageIndex = Items[index].ImageIndex; const CImage &image = Images[imageIndex]; unsigned newLevel = 0; @@ -502,6 +487,11 @@ void CDatabase::GetItemPath(unsigned index1, bool showImageNumber, NWindows::NCO for (;;) { const CItem &item = Items[index]; + if (item.DirLevel > (1 << 12)) + { + path = kLongPath; + return; + } index = item.Parent; if (index >= 0 || image.NumEmptyRootItems == 0) { @@ -543,7 +533,7 @@ void CDatabase::GetItemPath(unsigned index1, bool showImageNumber, NWindows::NCO else if (needColon) s[0] = L':'; - index = index1; + index = (int)index1; wchar_t separator = 0; for (;;) @@ -563,7 +553,16 @@ void CDatabase::GetItemPath(unsigned index1, bool showImageNumber, NWindows::NCO wchar_t *dest = s + size; meta += 2; for (unsigned i = 0; i < len; i++) - dest[i] = Get16(meta + i * 2); + { + wchar_t c = Get16(meta + i * 2); + if (c == L'/') + c = L'_'; + #if WCHAR_PATH_SEPARATOR != L'/' + else if (c == L'\\') + c = WCHAR_IN_FILE_NAME_BACKSLASH_REPLACEMENT; // 22.00 : WSL scheme + #endif + dest[i] = c; + } } if (index < 0) return; @@ -575,53 +574,62 @@ void CDatabase::GetItemPath(unsigned index1, bool showImageNumber, NWindows::NCO // if (ver <= 1.10), root folder contains real items. // if (ver >= 1.12), root folder contains only one folder with empty name. -HRESULT CDatabase::ParseDirItem(size_t pos, int parent) +HRESULT CDatabase::ParseDirItem(size_t pos, int parent, unsigned dirLevel) { + // if (++level > (1 << 10)) return S_FALSE; + CImage &image = Images.Back(); const unsigned align = GetDirAlignMask(); - if ((pos & align) != 0) + if (pos & align) return S_FALSE; for (unsigned numItems = 0;; numItems++) { if (OpenCallback && (Items.Size() & 0xFFFF) == 0) { - UInt64 numFiles = Items.Size(); - RINOK(OpenCallback->SetCompleted(&numFiles, NULL)); + const UInt64 numFiles = Items.Size(); + RINOK(OpenCallback->SetCompleted(&numFiles, NULL)) } const size_t rem = DirSize - pos; - if (pos < DirStartOffset || pos > DirSize || rem < 8) + if (pos < DirStartOffset || pos > DirSize || rem < 8 || DirSize - DirProcessed < 8) return S_FALSE; - const Byte *p = DirData + pos; - - UInt64 len = Get64(p); + const UInt64 len = Get64(p); if (len == 0) { DirProcessed += 8; return S_OK; } - - if ((len & align) != 0 || rem < len) + if ((len & align) || rem < len || DirSize - DirProcessed < len) return S_FALSE; - DirProcessed += (size_t)len; - if (DirProcessed > DirSize) - return S_FALSE; const unsigned dirRecordSize = IsOldVersion ? kDirRecordSizeOld : kDirRecordSize; if (len < dirRecordSize) return S_FALSE; CItem item; - UInt32 attrib = Get32(p + 8); + item.Construct(); + const UInt32 attrib = Get32(p + 8); item.IsDir = ((attrib & 0x10) != 0); - UInt64 subdirOffset = Get64(p + 0x10); - + { + const UInt32 securId = Get32(p + 0xC); + if (securId != (UInt32)(Int32)-1) + if (securId >= image.SecurOffsets.Size() || + securId + 1 >= image.SecurOffsets.Size()) + HeadersError = true; + } + size_t subdirOffset; + { + const UInt64 subdirOffset64 = Get64(p + 0x10); + if (subdirOffset64 > DirSize) + return S_FALSE; + subdirOffset = (size_t)subdirOffset64; + } const UInt32 numAltStreams = Get16(p + dirRecordSize - 6); const UInt32 shortNameLen = Get16(p + dirRecordSize - 4); const UInt32 fileNameLen = Get16(p + dirRecordSize - 2); - if ((shortNameLen & 1) != 0 || (fileNameLen & 1) != 0) + if ((shortNameLen & 1) || (fileNameLen & 1)) return S_FALSE; const UInt32 shortNameLen2 = (shortNameLen == 0 ? shortNameLen : shortNameLen + 2); const UInt32 fileNameLen2 = (fileNameLen == 0 ? fileNameLen : fileNameLen + 2); @@ -629,31 +637,30 @@ HRESULT CDatabase::ParseDirItem(size_t pos, int parent) return S_FALSE; p += dirRecordSize; - { - if (*(const UInt16 *)(p + fileNameLen) != 0) + if (*(const UInt16 *)(const void *)(p + fileNameLen)) return S_FALSE; for (UInt32 j = 0; j < fileNameLen; j += 2) - if (*(const UInt16 *)(p + j) == 0) + if (*(const UInt16 *)(const void *)(p + j) == 0) return S_FALSE; } - // PRF(printf("\n%S", p)); - if (shortNameLen != 0) + if (shortNameLen) { // empty shortName has no ZERO at the end ? const Byte *p2 = p + fileNameLen2; - if (*(const UInt16 *)(p2 + shortNameLen) != 0) + if (*(const UInt16 *)(const void *)(p2 + shortNameLen)) return S_FALSE; for (UInt32 j = 0; j < shortNameLen; j += 2) - if (*(const UInt16 *)(p2 + j) == 0) + if (*(const UInt16 *)(const void *)(p2 + j) == 0) return S_FALSE; } item.Offset = pos; item.Parent = parent; - item.ImageIndex = Images.Size() - 1; + item.DirLevel = dirLevel; + item.ImageIndex = (int)Images.Size() - 1; const unsigned prevIndex = Items.Add(item); @@ -666,46 +673,40 @@ HRESULT CDatabase::ParseDirItem(size_t pos, int parent) return S_FALSE; const Byte *p2 = DirData + pos; const UInt64 len2 = Get64(p2); - if ((len2 & align) != 0 || rem2 < len2 || len2 < (IsOldVersion ? 0x18 : 0x28)) + if ((len2 & align) || rem2 < len2 + || DirSize - DirProcessed < len2 + || len2 < (unsigned)(IsOldVersion ? 0x18 : 0x28)) return S_FALSE; - DirProcessed += (size_t)len2; - if (DirProcessed > DirSize) - return S_FALSE; unsigned extraOffset = 0; - if (IsOldVersion) extraOffset = 0x10; else { - if (Get64(p2 + 8) != 0) + if (Get64(p2 + 8)) return S_FALSE; extraOffset = 0x24; } const UInt32 fileNameLen111 = Get16(p2 + extraOffset); - if ((fileNameLen111 & 1) != 0) + if (fileNameLen111 & 1) return S_FALSE; /* Probably different versions of ImageX can use different number of additional ZEROs. So we don't use exact check. */ const UInt32 fileNameLen222 = (fileNameLen111 == 0 ? fileNameLen111 : fileNameLen111 + 2); if (((extraOffset + 2 + fileNameLen222 + align) & ~align) > len2) return S_FALSE; - { const Byte *p3 = p2 + extraOffset + 2; - if (*(const UInt16 *)(p3 + fileNameLen111) != 0) + if (*(const UInt16 *)(const void *)(p3 + fileNameLen111)) return S_FALSE; for (UInt32 j = 0; j < fileNameLen111; j += 2) - if (*(const UInt16 *)(p3 + j) == 0) + if (*(const UInt16 *)(const void *)(p3 + j) == 0) return S_FALSE; - // PRF(printf("\n %S", p3)); } - - - /* wim uses alt sreams list, if there is at least one alt stream. + /* wim uses alt streams list, if there is at least one alt stream. And alt stream without name is main stream. */ // Why wimlib writes two alt streams for REPARSE_POINT, with empty second alt stream? @@ -729,10 +730,12 @@ HRESULT CDatabase::ParseDirItem(size_t pos, int parent) { ThereAreAltStreams = true; CItem item2; + item2.Construct(); item2.Offset = pos; item2.IsAltStream = true; - item2.Parent = prevIndex; - item2.ImageIndex = Images.Size() - 1; + item2.Parent = (int)prevIndex; + item2.DirLevel = dirLevel; + item2.ImageIndex = (int)Images.Size() - 1; Items.Add(item2); } @@ -744,28 +747,32 @@ HRESULT CDatabase::ParseDirItem(size_t pos, int parent) const Byte *p2 = DirData + pos; if (DirSize - pos >= 8 && Get64(p2) == 0) { - CImage &image = Images.Back(); image.NumEmptyRootItems = 1; - if (subdirOffset != 0 + if (pos + 8 < subdirOffset && DirSize - pos >= 16 - && Get64(p2 + 8) != 0 - && pos + 8 < subdirOffset) + && Get64(p2 + 8)) { // Longhorn.4093 contains hidden files after empty root folder and before items of next folder. Why? // That code shows them. If we want to ignore them, we need to update DirProcessed. - // DirProcessed += (size_t)(subdirOffset - (pos + 8)); - // printf("\ndirOffset = %5d hiddenOffset = %5d\n", (int)subdirOffset, (int)pos + 8); +#if 1 // 0 : for debug : to ignore hidden files + // we parse hidden files and then parse main files: subdirOffset = pos + 8; +#else // ignore hidden files + DirProcessed += subdirOffset - (pos + 8); +#endif + // printf("\ndirOffset = %5d hiddenOffset = %5d\n", (int)subdirOffset, (int)pos + 8); // return S_FALSE; } } } - - if (item.IsDir && subdirOffset != 0) + Items[prevIndex].SubDirOffset = subdirOffset; + /* + if (item.IsDir && subdirOffset) { - RINOK(ParseDirItem((size_t)subdirOffset, prevIndex)); + RINOK(ParseDirItem(subdirOffset, (int)prevIndex)) } + */ } } @@ -776,30 +783,26 @@ HRESULT CDatabase::ParseImageDirs(CByteBuffer &buf, int parent) DirSize = buf.Size(); if (DirSize < 8) return S_FALSE; - const Byte *p = DirData; size_t pos = 0; CImage &image = Images.Back(); + const Byte * const p = DirData; if (IsOldVersion) { - UInt32 numEntries = Get32(p + 4); - - if (numEntries > (1 << 28) || + const UInt32 numEntries = Get32(p + 4); + if (numEntries >= (1 << 28) || numEntries > (DirSize >> 3)) return S_FALSE; - UInt32 sum = 8; - if (numEntries != 0) + if (numEntries) sum = numEntries * 8; - image.SecurOffsets.ClearAndReserve(numEntries + 1); image.SecurOffsets.AddInReserved(sum); - for (UInt32 i = 0; i < numEntries; i++) { const Byte *pp = p + (size_t)i * 8; - UInt32 len = Get32(pp); - if (i != 0 && Get32(pp + 4) != 0) + const UInt32 len = Get32(pp); + if (i && Get32(pp + 4)) return S_FALSE; if (len > DirSize - sum) return S_FALSE; @@ -808,38 +811,35 @@ HRESULT CDatabase::ParseImageDirs(CByteBuffer &buf, int parent) return S_FALSE; image.SecurOffsets.AddInReserved(sum); } - pos = sum; - const size_t align = GetDirAlignMask(); pos = (pos + align) & ~(size_t)align; } else { - UInt32 totalLen = Get32(p); - if (totalLen == 0) - pos = 8; - else + const UInt32 totalLen = Get32(p); + pos = 8; + if (totalLen) { if (totalLen < 8) return S_FALSE; UInt32 numEntries = Get32(p + 4); - pos = 8; if (totalLen > DirSize || numEntries > ((totalLen - 8) >> 3)) return S_FALSE; UInt32 sum = (UInt32)pos + numEntries * 8; - image.SecurOffsets.ClearAndReserve(numEntries + 1); - image.SecurOffsets.AddInReserved(sum); - - for (UInt32 i = 0; i < numEntries; i++, pos += 8) + numEntries++; + image.SecurOffsets.ClearAndReserve(numEntries); + for (;;) { - UInt64 len = Get64(p + pos); + image.SecurOffsets.AddInReserved(sum); + if (--numEntries == 0) + break; + const UInt64 len = Get64(p + pos); + pos += 8; if (len > totalLen - sum) return S_FALSE; sum += (UInt32)len; - image.SecurOffsets.AddInReserved(sum); } - pos = sum; pos = (pos + 7) & ~(size_t)7; if (pos != (((size_t)totalLen + 7) & ~(size_t)7)) @@ -849,24 +849,35 @@ HRESULT CDatabase::ParseImageDirs(CByteBuffer &buf, int parent) if (pos > DirSize) return S_FALSE; - DirStartOffset = DirProcessed = pos; image.StartItem = Items.Size(); - RINOK(ParseDirItem(pos, parent)); + RINOK(ParseDirItem(pos, parent, 0)) // dirLevel = 0 + { + for (unsigned i = image.StartItem; i < Items.Size(); i++) + { + const CItem &item = Items[i]; + if (item.IsDir && item.SubDirOffset) + { + RINOK(ParseDirItem(item.SubDirOffset, (int)i, item.DirLevel + 1)) + } + } + } image.NumItems = Items.Size() - image.StartItem; if (DirProcessed == DirSize) return S_OK; - /* Original program writes additional 8 bytes (END_OF_ROOT_FOLDER), but the reference to that folder is empty */ - // we can't use DirProcessed - DirStartOffset == 112 check if there is alt stream in root if (DirProcessed == DirSize - 8 && Get64(p + DirSize - 8) != 0) return S_OK; - return S_FALSE; + // 18.06: we support cases, when some old dism can capture images + // where DirProcessed much smaller than DirSize + HeadersError = true; + return S_OK; + // return S_FALSE; } @@ -884,27 +895,25 @@ HRESULT CHeader::Parse(const Byte *p, UInt64 &phySize) ChunkSizeBits = kChunkSizeBits; if (ChunkSize != 0) { - int log = GetLog(ChunkSize); - if (log < 12) + if (!GetLog_val_min_dest(ChunkSize, 12, ChunkSizeBits)) return S_FALSE; - ChunkSizeBits = log; } } - _IsOldVersion = false; - _IsNewVersion = false; + _isOldVersion = false; + _isNewVersion = false; if (IsSolidVersion()) - _IsNewVersion = true; + _isNewVersion = true; else { if (Version < 0x010900) return S_FALSE; - _IsOldVersion = (Version <= 0x010A00); + _isOldVersion = (Version <= 0x010A00); // We don't know details about 1.11 version. So we use headerSize to guess exact features. if (Version == 0x010B00 && headerSize == 0x60) - _IsOldVersion = true; - _IsNewVersion = (Version >= 0x010D00); + _isOldVersion = true; + _isNewVersion = (Version >= 0x010D00); } unsigned offset; @@ -958,7 +967,7 @@ const Byte kSignature[kSignatureSize] = { 'M', 'S', 'W', 'I', 'M', 0, 0, 0 }; HRESULT ReadHeader(IInStream *inStream, CHeader &h, UInt64 &phySize) { Byte p[kHeaderSizeMax]; - RINOK(ReadStream_FALSE(inStream, p, kHeaderSizeMax)); + RINOK(ReadStream_FALSE(inStream, p, kHeaderSizeMax)) if (memcmp(p, kSignature, kSignatureSize) != 0) return S_FALSE; return h.Parse(p, phySize); @@ -970,7 +979,7 @@ static HRESULT ReadStreams(IInStream *inStream, const CHeader &h, CDatabase &db) CByteBuffer offsetBuf; CUnpacker unpacker; - RINOK(unpacker.UnpackData(inStream, h.OffsetResource, h, NULL, offsetBuf, NULL)); + RINOK(unpacker.UnpackData(inStream, h.OffsetResource, h, NULL, offsetBuf, NULL)) const size_t streamInfoSize = h.IsOldVersion() ? kStreamInfoSize + 2 : kStreamInfoSize; { @@ -1072,7 +1081,7 @@ HRESULT CDatabase::Open(IInStream *inStream, const CHeader &h, unsigned numItems IsOldVersion = h.IsOldVersion(); IsOldVersion9 = (h.Version == 0x10900); - RINOK(ReadStreams(inStream, h, *this)); + RINOK(ReadStreams(inStream, h, *this)) bool needBootMetadata = !h.MetadataResource.IsEmpty(); unsigned numNonDeletedImages = 0; @@ -1086,14 +1095,14 @@ HRESULT CDatabase::Open(IInStream *inStream, const CHeader &h, unsigned numItems if (h.PartNumber != 1 || si.PartNumber != h.PartNumber) continue; - const int userImage = Images.Size() + GetStartImageIndex(); + const unsigned userImage = Images.Size() + GetStartImageIndex(); CImage &image = Images.AddNew(); SetRootNames(image, userImage); CByteBuffer &metadata = image.Meta; Byte hash[kHashSize]; - RINOK(unpacker.UnpackData(inStream, si.Resource, h, this, metadata, hash)); + RINOK(unpacker.UnpackData(inStream, si.Resource, h, this, metadata, hash)) if (memcmp(hash, si.Hash, kHashSize) != 0 && !(h.IsOldVersion() && IsEmptySha(si.Hash))) @@ -1104,7 +1113,7 @@ HRESULT CDatabase::Open(IInStream *inStream, const CHeader &h, unsigned numItems if (Items.IsEmpty()) Items.ClearAndReserve(numItemsReserve); - RINOK(ParseImageDirs(metadata, -1)); + RINOK(ParseImageDirs(metadata, -1)) if (needBootMetadata) { @@ -1151,12 +1160,12 @@ bool CDatabase::ItemHasStream(const CItem &item) const } -#define RINOZ(x) { int __tt = (x); if (__tt != 0) return __tt; } +#define RINOZ(x) { int _tt_ = (x); if (_tt_ != 0) return _tt_; } static int CompareStreamsByPos(const CStreamInfo *p1, const CStreamInfo *p2, void * /* param */) { - RINOZ(MyCompare(p1->PartNumber, p2->PartNumber)); - RINOZ(MyCompare(p1->Resource.Offset, p2->Resource.Offset)); + RINOZ(MyCompare(p1->PartNumber, p2->PartNumber)) + RINOZ(MyCompare(p1->Resource.Offset, p2->Resource.Offset)) return MyCompare(p1->Resource.PackSize, p2->Resource.PackSize); } @@ -1177,11 +1186,11 @@ static int FindId(const CStreamInfo *streams, const CUIntVector &sorted, UInt32 unsigned left = 0, right = sorted.Size(); while (left != right) { - unsigned mid = (left + right) / 2; - unsigned streamIndex = sorted[mid]; - UInt32 id2 = streams[streamIndex].Id; + const unsigned mid = (left + right) / 2; + const unsigned streamIndex = sorted[mid]; + const UInt32 id2 = streams[streamIndex].Id; if (id == id2) - return streamIndex; + return (int)streamIndex; if (id < id2) right = mid; else @@ -1195,15 +1204,15 @@ static int FindHash(const CStreamInfo *streams, const CUIntVector &sorted, const unsigned left = 0, right = sorted.Size(); while (left != right) { - unsigned mid = (left + right) / 2; - unsigned streamIndex = sorted[mid]; + const unsigned mid = (left + right) / 2; + const unsigned streamIndex = sorted[mid]; const Byte *hash2 = streams[streamIndex].Hash; unsigned i; for (i = 0; i < kHashSize; i++) if (hash[i] != hash2[i]) break; if (i == kHashSize) - return streamIndex; + return (int)streamIndex; if (hash[i] < hash2[i]) right = mid; else @@ -1222,8 +1231,8 @@ static int CompareItems(const unsigned *a1, const unsigned *a2, void *param) return i1.IsDir ? -1 : 1; if (i1.IsAltStream != i2.IsAltStream) return i1.IsAltStream ? 1 : -1; - RINOZ(MyCompare(i1.StreamIndex, i2.StreamIndex)); - RINOZ(MyCompare(i1.ImageIndex, i2.ImageIndex)); + RINOZ(MyCompare(i1.StreamIndex, i2.StreamIndex)) + RINOZ(MyCompare(i1.ImageIndex, i2.ImageIndex)) return MyCompare(i1.Offset, i2.Offset); } @@ -1271,7 +1280,7 @@ HRESULT CDatabase::FillAndCheck(const CObjectVector &volumes) if (si.RefCount != 1) return S_FALSE; - r.SolidIndex = Solids.Size(); + r.SolidIndex = (int)Solids.Size(); CSolid &ss = Solids.AddNew(); ss.StreamIndex = k; @@ -1285,8 +1294,8 @@ HRESULT CDatabase::FillAndCheck(const CObjectVector &volumes) const CVolume &vol = volumes[si.PartNumber]; IInStream *inStream = vol.Stream; - RINOK(inStream->Seek(r.Offset, STREAM_SEEK_SET, NULL)); - RINOK(ReadStream_FALSE(inStream, (Byte *)header, kSolidHeaderSize)); + RINOK(InStream_SeekSet(inStream, r.Offset)) + RINOK(ReadStream_FALSE(inStream, (Byte *)header, kSolidHeaderSize)) ss.UnpackSize = GetUi64(header); @@ -1298,23 +1307,21 @@ HRESULT CDatabase::FillAndCheck(const CObjectVector &volumes) return S_FALSE; const UInt32 solidChunkSize = GetUi32(header + 8); - int log = GetLog(solidChunkSize); - if (log < 8 || log > 31) + if (!GetLog_val_min_dest(solidChunkSize, 8, ss.ChunkSizeBits)) return S_FALSE; - ss.ChunkSizeBits = log; - ss.Method = GetUi32(header + 12); + ss.Method = (Int32)GetUi32(header + 12); - UInt64 numChunks64 = (ss.UnpackSize + (((UInt32)1 << ss.ChunkSizeBits) - 1)) >> ss.ChunkSizeBits; - UInt64 sizesBufSize64 = 4 * numChunks64; + const UInt64 numChunks64 = (ss.UnpackSize + (((UInt32)1 << ss.ChunkSizeBits) - 1)) >> ss.ChunkSizeBits; + const UInt64 sizesBufSize64 = 4 * numChunks64; ss.HeadersSize = kSolidHeaderSize + sizesBufSize64; - size_t sizesBufSize = (size_t)sizesBufSize64; + const size_t sizesBufSize = (size_t)sizesBufSize64; if (sizesBufSize != sizesBufSize64) return E_OUTOFMEMORY; sizesBuf.AllocAtLeast(sizesBufSize); - RINOK(ReadStream_FALSE(inStream, sizesBuf, sizesBufSize)); + RINOK(ReadStream_FALSE(inStream, sizesBuf, sizesBufSize)) - size_t numChunks = (size_t)numChunks64; + const size_t numChunks = (size_t)numChunks64; ss.Chunks.Alloc(numChunks + 1); UInt64 offset = 0; @@ -1366,14 +1373,14 @@ HRESULT CDatabase::FillAndCheck(const CObjectVector &volumes) CSolid &ss = Solids[solidIndex]; if (r.Offset < ss.SolidOffset) return S_FALSE; - UInt64 relat = r.Offset - ss.SolidOffset; + const UInt64 relat = r.Offset - ss.SolidOffset; if (relat > ss.UnpackSize) return S_FALSE; if (r.PackSize > ss.UnpackSize - relat) return S_FALSE; - r.SolidIndex = solidIndex; + r.SolidIndex = (int)solidIndex; if (ss.FirstSmallStream < 0) - ss.FirstSmallStream = k; + ss.FirstSmallStream = (int)k; sortedByHash.AddInReserved(k); // ss.NumRefs++; @@ -1423,7 +1430,7 @@ HRESULT CDatabase::FillAndCheck(const CObjectVector &volumes) { { - const CStreamInfo *streams = &DataStreams.Front(); + const CStreamInfo *streams = DataStreams.ConstData(); if (IsOldVersion) { @@ -1465,7 +1472,7 @@ HRESULT CDatabase::FillAndCheck(const CObjectVector &volumes) hash += (item.IsAltStream ? 0x8 : 0x10); UInt32 id = GetUi32(hash); if (id != 0) - item.StreamIndex = FindId(&DataStreams.Front(), sortedByHash, id); + item.StreamIndex = FindId(DataStreams.ConstData(), sortedByHash, id); } } /* @@ -1479,7 +1486,7 @@ HRESULT CDatabase::FillAndCheck(const CObjectVector &volumes) hash += (item.IsAltStream ? 0x10 : 0x40); if (!IsEmptySha(hash)) { - item.StreamIndex = FindHash(&DataStreams.Front(), sortedByHash, hash); + item.StreamIndex = FindHash(DataStreams.ConstData(), sortedByHash, hash); } } } @@ -1502,7 +1509,7 @@ HRESULT CDatabase::FillAndCheck(const CObjectVector &volumes) for (i = 0; i < Items.Size(); i++) { - int streamIndex = Items[i].StreamIndex; + const int streamIndex = Items[i].StreamIndex; if (streamIndex >= 0) refCounts[streamIndex]++; } @@ -1526,8 +1533,9 @@ HRESULT CDatabase::FillAndCheck(const CObjectVector &volumes) if (!r.IsSolidBig() || Solids[r.SolidIndex].FirstSmallStream < 0) { CItem item; + item.Construct(); item.Offset = 0; - item.StreamIndex = i; + item.StreamIndex = (int)i; item.ImageIndex = -1; Items.Add(item); ThereAreDeletedStreams = true; @@ -1576,7 +1584,7 @@ HRESULT CDatabase::GenerateSortedItems(int imageIndex, bool showImageNumber) if (NumExcludededItems != 0) { - ExludedItem = startItem; + ExludedItem = (int)startItem; startItem += NumExcludededItems; } @@ -1588,7 +1596,7 @@ HRESULT CDatabase::GenerateSortedItems(int imageIndex, bool showImageNumber) SortedItems.Sort(CompareItems, this); for (i = 0; i < SortedItems.Size(); i++) - Items[SortedItems[i]].IndexInSorted = i; + Items[SortedItems[i]].IndexInSorted = (int)i; if (showImageNumber) for (i = 0; i < Images.Size(); i++) @@ -1596,7 +1604,7 @@ HRESULT CDatabase::GenerateSortedItems(int imageIndex, bool showImageNumber) CImage &image = Images[i]; if (image.NumEmptyRootItems != 0) continue; - image.VirtualRootIndex = VirtualRoots.Size(); + image.VirtualRootIndex = (int)VirtualRoots.Size(); VirtualRoots.Add(i); } @@ -1666,7 +1674,7 @@ HRESULT CDatabase::ExtractReparseStreams(const CObjectVector &volumes, if ((unpacker.TotalPacked - totalPackedPrev) >= ((UInt32)1 << 16)) { UInt64 numFiles = Items.Size(); - RINOK(openCallback->SetCompleted(&numFiles, &unpacker.TotalPacked)); + RINOK(openCallback->SetCompleted(&numFiles, &unpacker.TotalPacked)) totalPackedPrev = unpacker.TotalPacked; } } @@ -1700,7 +1708,7 @@ HRESULT CDatabase::ExtractReparseStreams(const CObjectVector &volumes, if (res == S_FALSE) continue; - RINOK(res); + RINOK(res) if (memcmp(digest, si.Hash, kHashSize) != 0 // && !(h.IsOldVersion() && IsEmptySha(si.Hash)) @@ -1714,11 +1722,11 @@ HRESULT CDatabase::ExtractReparseStreams(const CObjectVector &volumes, CByteBuffer &reparse = ReparseItems.AddNew(); reparse.Alloc(8 + buf.Size()); Byte *dest = (Byte *)reparse; - SetUi32(dest, tag); - SetUi32(dest + 4, (UInt32)buf.Size()); + SetUi32(dest, tag) + SetUi32(dest + 4, (UInt32)buf.Size()) if (buf.Size() != 0) memcpy(dest + 8, buf, buf.Size()); - ItemToReparse[itemIndex] = ReparseItems.Size() - 1; + ItemToReparse[itemIndex] = (int)ReparseItems.Size() - 1; } return S_OK; @@ -1757,13 +1765,12 @@ static bool ParseNumber32(const AString &s, UInt32 &res) static bool ParseTime(const CXmlItem &item, FILETIME &ft, const char *tag) { - int index = item.FindSubTag(tag); - if (index >= 0) + const CXmlItem *timeItem = item.FindSubTag_GetPtr(tag); + if (timeItem) { - const CXmlItem &timeItem = item.SubItems[index]; UInt32 low = 0, high = 0; - if (ParseNumber32(timeItem.GetSubStringForTag("LOWPART"), low) && - ParseNumber32(timeItem.GetSubStringForTag("HIGHPART"), high)) + if (ParseNumber32(timeItem->GetSubStringForTag("LOWPART"), low) && + ParseNumber32(timeItem->GetSubStringForTag("HIGHPART"), high)) { ft.dwLowDateTime = low; ft.dwHighDateTime = high; @@ -1778,7 +1785,8 @@ void CImageInfo::Parse(const CXmlItem &item) { CTimeDefined = ParseTime(item, CTime, "CREATIONTIME"); MTimeDefined = ParseTime(item, MTime, "LASTMODIFICATIONTIME"); - NameDefined = ConvertUTF8ToUnicode(item.GetSubStringForTag("NAME"), Name); + NameDefined = true; + ConvertUTF8ToUnicode(item.GetSubStringForTag("NAME"), Name); ParseNumber64(item.GetSubStringForTag("DIRCOUNT"), DirCount); ParseNumber64(item.GetSubStringForTag("FILECOUNT"), FileCount); @@ -1819,7 +1827,7 @@ bool CWimXml::Parse() if (!Xml.Parse(utf)) return false; - if (Xml.Root.Name != "WIM") + if (!Xml.Root.Name.IsEqualTo("WIM")) return false; FOR_VECTOR (i, Xml.Root.SubItems) @@ -1840,7 +1848,7 @@ bool CWimXml::Parse() return false; } - imageInfo.ItemIndexInXml = i; + imageInfo.ItemIndexInXml = (int)i; Images.Add(imageInfo); } diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Wim/WimIn.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Wim/WimIn.h index 6a387212..ae7cd6b6 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Wim/WimIn.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Wim/WimIn.h @@ -1,10 +1,11 @@ // Archive/WimIn.h -#ifndef __ARCHIVE_WIM_IN_H -#define __ARCHIVE_WIM_IN_H +#ifndef ZIP7_INC_ARCHIVE_WIM_IN_H +#define ZIP7_INC_ARCHIVE_WIM_IN_H #include "../../../../C/Alloc.h" +#include "../../../Common/AutoPtr.h" #include "../../../Common/MyBuffer.h" #include "../../../Common/MyXml.h" @@ -192,7 +193,7 @@ struct CSolid UInt64 UnpackSize; int Method; - int ChunkSizeBits; + unsigned ChunkSizeBits; UInt64 HeadersSize; // size_t NumChunks; @@ -258,8 +259,8 @@ struct CHeader UInt32 NumImages; UInt32 BootIndex; - bool _IsOldVersion; // 1.10- - bool _IsNewVersion; // 1.13+ or 0.14 + bool _isOldVersion; // 1.10- + bool _isNewVersion; // 1.13+ or 0.14 CResource OffsetResource; CResource XmlResource; @@ -295,8 +296,8 @@ struct CHeader return mask; } - bool IsOldVersion() const { return _IsOldVersion; } - bool IsNewVersion() const { return _IsNewVersion; } + bool IsOldVersion() const { return _isOldVersion; } + bool IsNewVersion() const { return _isNewVersion; } bool IsSolidVersion() const { return (Version == k_Version_Solid); } bool AreFromOnArchive(const CHeader &h) @@ -341,16 +342,21 @@ struct CItem int ImageIndex; // -1 means that file is unreferenced in Images (deleted item?) bool IsDir; bool IsAltStream; + unsigned DirLevel; + size_t SubDirOffset; bool HasMetadata() const { return ImageIndex >= 0; } - CItem(): - IndexInSorted(-1), - StreamIndex(-1), - Parent(-1), - IsDir(false), - IsAltStream(false) - {} + void Construct() + { + IndexInSorted = -1; + StreamIndex = -1; + Parent = -1; + IsDir = false; + IsAltStream = false; + DirLevel = 0; + SubDirOffset = 0; + } }; struct CImage @@ -434,7 +440,7 @@ class CDatabase size_t DirStartOffset; IArchiveOpenCallback *OpenCallback; - HRESULT ParseDirItem(size_t pos, int parent); + HRESULT ParseDirItem(size_t pos, int parent, unsigned dirLevel); HRESULT ParseImageDirs(CByteBuffer &buf, int parent); public: @@ -457,7 +463,7 @@ class CDatabase bool RefCountError; bool HeadersError; - bool GetStartImageIndex() const { return IsOldVersion9 ? 0 : 1; } + unsigned GetStartImageIndex() const { return IsOldVersion9 ? 0 : 1; } unsigned GetDirAlignMask() const { return IsOldVersion9 ? 3 : 7; } // User Items can contain all images or just one image from all. @@ -468,7 +474,7 @@ class CDatabase int ExludedItem; // -1 : if there are no exclude items CUIntVector VirtualRoots; // we use them for old 1.10 WIM archives - bool ThereIsError() const { return RefCountError; } + bool ThereIsError() const { return RefCountError || HeadersError; } unsigned GetNumUserItemsInImage(unsigned imageIndex) const { @@ -544,7 +550,10 @@ class CDatabase HeadersError = false; } - CDatabase(): RefCountError(false) {} + CDatabase(): + RefCountError(false), + HeadersError(false) + {} void GetShortName(unsigned index, NWindows::NCOM::CPropVariant &res) const; void GetItemName(unsigned index1, NWindows::NCOM::CPropVariant &res) const; @@ -580,27 +589,23 @@ struct CMidBuf { if (size > _size) { - ::MidFree(Data); + ::z7_AlignedFree(Data); _size = 0; - Data = (Byte *)::MidAlloc(size); + Data = (Byte *)::z7_AlignedAlloc(size); if (Data) _size = size; } } - ~CMidBuf() { ::MidFree(Data); } + ~CMidBuf() { ::z7_AlignedFree(Data); } }; class CUnpacker { - NCompress::CCopyCoder *copyCoderSpec; - CMyComPtr copyCoder; - - NCompress::NLzx::CDecoder *lzxDecoderSpec; - CMyComPtr lzxDecoder; - - NCompress::NLzms::CDecoder *lzmsDecoder; + CMyComPtr2 copyCoder; + CMyUniquePtr lzxDecoder; + CMyUniquePtr lzmsDecoder; CByteBuffer sizesBuf; @@ -634,7 +639,6 @@ class CUnpacker _unpackedChunkIndex(0), TotalPacked(0) {} - ~CUnpacker(); HRESULT Unpack( IInStream *inStream, diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Wim/WimRegister.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/Wim/WimRegister.cpp index 6ad28acc..e143f91c 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Wim/WimRegister.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Wim/WimRegister.cpp @@ -10,13 +10,20 @@ namespace NArchive { namespace NWim { REGISTER_ARC_IO( - "wim", "wim swm esd", 0, 0xE6, - kSignature, - 0, - NArcInfoFlags::kAltStreams | - NArcInfoFlags::kNtSecure | - NArcInfoFlags::kSymLinks | - NArcInfoFlags::kHardLinks + "wim", "wim swm esd ppkg", NULL, 0xE6 + , kSignature, 0 + , NArcInfoFlags::kAltStreams + | NArcInfoFlags::kNtSecure + | NArcInfoFlags::kSymLinks + | NArcInfoFlags::kHardLinks + | NArcInfoFlags::kCTime + // | NArcInfoFlags::kCTime_Default + | NArcInfoFlags::kATime + // | NArcInfoFlags::kATime_Default + | NArcInfoFlags::kMTime + | NArcInfoFlags::kMTime_Default + , TIME_PREC_TO_ARC_FLAGS_MASK (NFileTimeType::kWindows) + | TIME_PREC_TO_ARC_FLAGS_TIME_DEFAULT (NFileTimeType::kWindows) , NULL) }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/XarHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/XarHandler.cpp index 96386809..cba546ef 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/XarHandler.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/XarHandler.cpp @@ -2,6 +2,8 @@ #include "StdAfx.h" +#include "../../../C/Sha256.h" +#include "../../../C/Sha512.h" #include "../../../C/CpuArch.h" #include "../../Common/ComTry.h" @@ -36,100 +38,279 @@ using namespace NWindows; namespace NArchive { namespace NXar { -static const size_t kXmlSizeMax = ((size_t )1 << 30) - (1 << 14); -static const size_t kXmlPackSizeMax = kXmlSizeMax; +Z7_CLASS_IMP_NOQIB_1( + CInStreamWithSha256 + , ISequentialInStream +) + bool _sha512Mode; + CMyComPtr _stream; + CAlignedBuffer1 _sha256; + CAlignedBuffer1 _sha512; + UInt64 _size; + + CSha256 *Sha256() { return (CSha256 *)(void *)(Byte *)_sha256; } + CSha512 *Sha512() { return (CSha512 *)(void *)(Byte *)_sha512; } +public: + CInStreamWithSha256(): + _sha256(sizeof(CSha256)), + _sha512(sizeof(CSha512)) + {} + void SetStream(ISequentialInStream *stream) { _stream = stream; } + void Init(bool sha512Mode) + { + _sha512Mode = sha512Mode; + _size = 0; + if (sha512Mode) + Sha512_Init(Sha512(), SHA512_DIGEST_SIZE); + else + Sha256_Init(Sha256()); + } + void ReleaseStream() { _stream.Release(); } + UInt64 GetSize() const { return _size; } + void Final256(Byte *digest) { Sha256_Final(Sha256(), digest); } + void Final512(Byte *digest) { Sha512_Final(Sha512(), digest, SHA512_DIGEST_SIZE); } +}; -/* -#define XAR_CKSUM_NONE 0 -#define XAR_CKSUM_SHA1 1 -#define XAR_CKSUM_MD5 2 +Z7_COM7F_IMF(CInStreamWithSha256::Read(void *data, UInt32 size, UInt32 *processedSize)) +{ + UInt32 realProcessedSize; + const HRESULT result = _stream->Read(data, size, &realProcessedSize); + _size += realProcessedSize; + if (_sha512Mode) + Sha512_Update(Sha512(), (const Byte *)data, realProcessedSize); + else + Sha256_Update(Sha256(), (const Byte *)data, realProcessedSize); + if (processedSize) + *processedSize = realProcessedSize; + return result; +} -static const char * const k_ChecksumAlgos[] = + +Z7_CLASS_IMP_NOQIB_1( + COutStreamWithSha256 + , ISequentialOutStream +) + bool _sha512Mode; + CMyComPtr _stream; + CAlignedBuffer1 _sha256; + CAlignedBuffer1 _sha512; + UInt64 _size; + + CSha256 *Sha256() { return (CSha256 *)(void *)(Byte *)_sha256; } + CSha512 *Sha512() { return (CSha512 *)(void *)(Byte *)_sha512; } +public: + COutStreamWithSha256(): + _sha256(sizeof(CSha256)), + _sha512(sizeof(CSha512)) + {} + void SetStream(ISequentialOutStream *stream) { _stream = stream; } + void ReleaseStream() { _stream.Release(); } + void Init(bool sha512Mode) + { + _sha512Mode = sha512Mode; + _size = 0; + if (sha512Mode) + Sha512_Init(Sha512(), SHA512_DIGEST_SIZE); + else + Sha256_Init(Sha256()); + } + UInt64 GetSize() const { return _size; } + void Final256(Byte *digest) { Sha256_Final(Sha256(), digest); } + void Final512(Byte *digest) { Sha512_Final(Sha512(), digest, SHA512_DIGEST_SIZE); } +}; + +Z7_COM7F_IMF(COutStreamWithSha256::Write(const void *data, UInt32 size, UInt32 *processedSize)) { - "None" - , "SHA-1" + HRESULT result = S_OK; + if (_stream) + result = _stream->Write(data, size, &size); + // if (_calculate) + if (_sha512Mode) + Sha512_Update(Sha512(), (const Byte *)data, size); + else + Sha256_Update(Sha256(), (const Byte *)data, size); + _size += size; + if (processedSize) + *processedSize = size; + return result; +} + +// we limit supported xml sizes: +// ((size_t)1 << (sizeof(size_t) / 2 + 28)) - (1u << 14); +static const size_t kXmlSizeMax = ((size_t)1 << 30) - (1u << 14); +static const size_t kXmlPackSizeMax = kXmlSizeMax; + +#define XAR_CKSUM_NONE 0 +#define XAR_CKSUM_SHA1 1 +#define XAR_CKSUM_MD5 2 +#define XAR_CKSUM_SHA256 3 +#define XAR_CKSUM_SHA512 4 +// #define XAR_CKSUM_OTHER 3 +// fork version of xar can use (3) as special case, +// where name of hash is stored as string at the end of header +// we do not support such hash still. + +static const char * const k_ChecksumNames[] = +{ + "NONE" + , "SHA1" , "MD5" + , "SHA256" + , "SHA512" }; -*/ -#define METHOD_NAME_ZLIB "zlib" +static unsigned GetHashSize(int algo) +{ + if (algo <= XAR_CKSUM_NONE || algo > XAR_CKSUM_SHA512) + return 0; + if (algo == XAR_CKSUM_SHA1) + return SHA1_DIGEST_SIZE; + return (16u >> XAR_CKSUM_MD5) << algo; +} + +#define METHOD_NAME_ZLIB "zlib" + +static int Find_ChecksumId_for_Name(const AString &style) +{ + for (unsigned i = 0; i < Z7_ARRAY_SIZE(k_ChecksumNames); i++) + { + // old xars used upper case in "style" + // new xars use lower case in "style" + if (style.IsEqualTo_Ascii_NoCase(k_ChecksumNames[i])) + return (int)i; + } + return -1; +} + + +struct CCheckSum +{ + int AlgoNumber; + bool Error; + CByteBuffer Data; + AString Style; + + CCheckSum(): AlgoNumber(-1), Error(false) {} + void AddNameToString(AString &s) const; +}; + +void CCheckSum::AddNameToString(AString &s) const +{ + if (Style.IsEmpty()) + s.Add_OptSpaced("NO-CHECKSUM"); + else + { + s.Add_OptSpaced(Style); + if (Error) + s += "-ERROR"; + } +} struct CFile { - AString Name; - AString Method; + bool IsDir; + bool Is_SymLink; + bool HasData; + bool Mode_Defined; + bool INode_Defined; + bool UserId_Defined; + bool GroupId_Defined; + // bool Device_Defined; + bool Id_Defined; + + int Parent; + UInt32 Mode; + UInt64 Size; UInt64 PackSize; UInt64 Offset; - - UInt64 CTime; UInt64 MTime; + UInt64 CTime; UInt64 ATime; - UInt32 Mode; + UInt64 INode; + UInt64 UserId; + UInt64 GroupId; + // UInt64 Device; + AString Name; + AString Method; AString User; AString Group; + // AString Id; + AString Type; + AString Link; + // AString LinkType; + // AString LinkFrom; - bool IsDir; - bool HasData; - bool ModeDefined; - bool Sha1IsDefined; - // bool packSha1IsDefined; - - Byte Sha1[SHA1_DIGEST_SIZE]; - // Byte packSha1[SHA1_DIGEST_SIZE]; - - int Parent; - - CFile(): IsDir(false), HasData(false), ModeDefined(false), Sha1IsDefined(false), - /* packSha1IsDefined(false), */ - Parent(-1), + UInt64 Id; + CCheckSum extracted_checksum; + CCheckSum archived_checksum; + + CFile(int parent): + IsDir(false), + Is_SymLink(false), + HasData(false), + Mode_Defined(false), + INode_Defined(false), + UserId_Defined(false), + GroupId_Defined(false), + // Device_Defined(false), + Id_Defined(false), + Parent(parent), + Mode(0), Size(0), PackSize(0), Offset(0), - CTime(0), MTime(0), ATime(0), Mode(0) {} + MTime(0), CTime(0), ATime(0), + INode(0) + {} bool IsCopyMethod() const { - return Method.IsEmpty() || Method == "octet-stream"; + return Method.IsEmpty() || Method.IsEqualTo("octet-stream"); } void UpdateTotalPackSize(UInt64 &totalSize) const { - UInt64 t = Offset + PackSize; + const UInt64 t = Offset + PackSize; + if (t >= Offset) if (totalSize < t) - totalSize = t; + totalSize = t; } }; -class CHandler: - public IInArchive, - public IInArchiveGetStream, - public CMyUnknownImp -{ - UInt64 _dataStartPos; - CMyComPtr _inStream; - CByteArr _xml; - size_t _xmlLen; + +Z7_CLASS_IMP_CHandler_IInArchive_2( + IArchiveGetRawProps, + IInArchiveGetStream +) + bool _is_pkg; + bool _toc_CrcError; CObjectVector _files; - // UInt32 _checkSumAlgo; + CMyComPtr _inStream; + UInt64 _dataStartPos; UInt64 _phySize; + CAlignedBuffer _xmlBuf; + size_t _xmlLen; + // UInt64 CreationTime; + AString CreationTime_String; + UInt32 _checkSumAlgo; Int32 _mainSubfile; - bool _is_pkg; HRESULT Open2(IInStream *stream); - HRESULT Extract(IInStream *stream); -public: - MY_UNKNOWN_IMP2(IInArchive, IInArchiveGetStream) - INTERFACE_IInArchive(;) - STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **stream); }; + static const Byte kArcProps[] = { kpidSubType, - kpidHeadersSize + // kpidHeadersSize, + kpidMethod, + kpidCTime }; +// #define kpidLinkType 250 +// #define kpidLinkFrom 251 + static const Byte kProps[] = { kpidPath, @@ -139,21 +320,28 @@ static const Byte kProps[] = kpidCTime, kpidATime, kpidPosixAttrib, + kpidType, kpidUser, kpidGroup, - kpidMethod + kpidUserId, + kpidGroupId, + kpidINode, + // kpidDeviceMajor, + // kpidDeviceMinor, + kpidSymLink, + // kpidLinkType, + // kpidLinkFrom, + kpidMethod, + kpidId, + kpidOffset }; IMP_IInArchive_Props IMP_IInArchive_ArcProps -#define PARSE_NUM(_num_, _dest_) \ - { const char *end; _dest_ = ConvertStringToUInt32(p, &end); \ - if ((unsigned)(end - p) != _num_) return 0; p += _num_ + 1; } - static bool ParseUInt64(const CXmlItem &item, const char *name, UInt64 &res) { - const AString s = item.GetSubStringForTag(name); + const AString s (item.GetSubStringForTag(name)); if (s.IsEmpty()) return false; const char *end; @@ -161,14 +349,26 @@ static bool ParseUInt64(const CXmlItem &item, const char *name, UInt64 &res) return *end == 0; } -static UInt64 ParseTime(const CXmlItem &item, const char *name) + +#define PARSE_NUM(_num_, _dest_) \ + { const char *end; _dest_ = ConvertStringToUInt32(p, &end); \ + if ((unsigned)(end - p) != _num_) return 0; \ + p += _num_ + 1; } + +static UInt64 ParseTime(const CXmlItem &item, const char *name /* , bool z_isRequired */ ) { - const AString s = item.GetSubStringForTag(name); - if (s.Len() < 20) + const AString s (item.GetSubStringForTag(name)); + if (s.Len() < 20 /* (z_isRequired ? 20u : 19u) */) return 0; const char *p = s; - if (p[ 4] != '-' || p[ 7] != '-' || p[10] != 'T' || - p[13] != ':' || p[16] != ':' || p[19] != 'Z') + if (p[ 4] != '-' || + p[ 7] != '-' || + p[10] != 'T' || + p[13] != ':' || + p[16] != ':') + return 0; + // if (z_isRequired) + if (p[19] != 'Z') return 0; UInt32 year, month, day, hour, min, sec; PARSE_NUM(4, year) @@ -177,177 +377,269 @@ static UInt64 ParseTime(const CXmlItem &item, const char *name) PARSE_NUM(2, hour) PARSE_NUM(2, min) PARSE_NUM(2, sec) - UInt64 numSecs; if (!NTime::GetSecondsSince1601(year, month, day, hour, min, sec, numSecs)) return 0; return numSecs * 10000000; } -static int HexToByte(unsigned char c) -{ - if (c >= '0' && c <= '9') return c - '0'; - if (c >= 'A' && c <= 'F') return c - 'A' + 10; - if (c >= 'a' && c <= 'f') return c - 'a' + 10; - return -1; -} -static bool ParseSha1(const CXmlItem &item, const char *name, Byte *digest) +static void ParseChecksum(const CXmlItem &item, const char *name, CCheckSum &checksum) { - int index = item.FindSubTag(name); - if (index < 0) - return false; - const CXmlItem &checkItem = item.SubItems[index]; - const AString style = checkItem.GetPropVal("style"); - if (style == "SHA1") + const CXmlItem *checkItem = item.FindSubTag_GetPtr(name); + if (!checkItem) + return; // false; + checksum.Style = checkItem->GetPropVal("style"); + const AString s (checkItem->GetSubString()); + if ((s.Len() & 1) == 0 && s.Len() <= (2u << 7)) // 1024-bit max { - const AString s = checkItem.GetSubString(); - if (s.Len() != SHA1_DIGEST_SIZE * 2) - return false; - for (unsigned i = 0; i < s.Len(); i += 2) + const size_t size = s.Len() / 2; + CByteBuffer temp(size); + if ((size_t)(ParseHexString(s, temp) - temp) == size) { - int b0 = HexToByte(s[i]); - int b1 = HexToByte(s[i + 1]); - if (b0 < 0 || b1 < 0) - return false; - digest[i / 2] = (Byte)((b0 << 4) | b1); + checksum.Data = temp; + const int index = Find_ChecksumId_for_Name(checksum.Style); + if (index >= 0 && checksum.Data.Size() == GetHashSize(index)) + { + checksum.AlgoNumber = index; + return; + } } - return true; } - return false; + checksum.Error = true; } -static bool AddItem(const CXmlItem &item, CObjectVector &files, int parent) + +static bool AddItem(const CXmlItem &item, CObjectVector &files, int parent, int level) { if (!item.IsTag) return true; - if (item.Name == "file") + if (level >= 1024) + return false; + if (item.Name.IsEqualTo("file")) { - CFile file; - file.Parent = parent; - parent = files.Size(); + CFile file(parent); + parent = (int)files.Size(); + { + const AString id = item.GetPropVal("id"); + const char *end; + file.Id = ConvertStringToUInt64(id, &end); + if (*end == 0) + file.Id_Defined = true; + } file.Name = item.GetSubStringForTag("name"); - AString type = item.GetSubStringForTag("type"); - if (type == "directory") - file.IsDir = true; - else if (type == "file") - file.IsDir = false; - else - return false; + z7_xml_DecodeString(file.Name); + { + const CXmlItem *typeItem = item.FindSubTag_GetPtr("type"); + if (typeItem) + { + file.Type = typeItem->GetSubString(); + // file.LinkFrom = typeItem->GetPropVal("link"); + if (file.Type.IsEqualTo("directory")) + file.IsDir = true; + else + { + // file.IsDir = false; + /* + else if (file.Type.IsEqualTo("file")) + {} + else if (file.Type.IsEqualTo("hardlink")) + {} + else + */ + if (file.Type.IsEqualTo("symlink")) + file.Is_SymLink = true; + // file.IsDir = false; + } + } + } + { + const CXmlItem *linkItem = item.FindSubTag_GetPtr("link"); + if (linkItem) + { + // file.LinkType = linkItem->GetPropVal("type"); + file.Link = linkItem->GetSubString(); + z7_xml_DecodeString(file.Link); + } + } - int dataIndex = item.FindSubTag("data"); - if (dataIndex >= 0 && !file.IsDir) + const CXmlItem *dataItem = item.FindSubTag_GetPtr("data"); + if (dataItem && !file.IsDir) { file.HasData = true; - const CXmlItem &dataItem = item.SubItems[dataIndex]; - if (!ParseUInt64(dataItem, "size", file.Size)) + if (!ParseUInt64(*dataItem, "size", file.Size)) return false; - if (!ParseUInt64(dataItem, "length", file.PackSize)) + if (!ParseUInt64(*dataItem, "length", file.PackSize)) return false; - if (!ParseUInt64(dataItem, "offset", file.Offset)) + if (!ParseUInt64(*dataItem, "offset", file.Offset)) return false; - file.Sha1IsDefined = ParseSha1(dataItem, "extracted-checksum", file.Sha1); - // file.packSha1IsDefined = ParseSha1(dataItem, "archived-checksum", file.packSha1); - int encodingIndex = dataItem.FindSubTag("encoding"); - if (encodingIndex >= 0) + ParseChecksum(*dataItem, "extracted-checksum", file.extracted_checksum); + ParseChecksum(*dataItem, "archived-checksum", file.archived_checksum); + const CXmlItem *encodingItem = dataItem->FindSubTag_GetPtr("encoding"); + if (encodingItem) { - const CXmlItem &encodingItem = dataItem.SubItems[encodingIndex]; - if (encodingItem.IsTag) + AString s (encodingItem->GetPropVal("style")); + if (!s.IsEmpty()) { - AString s = encodingItem.GetPropVal("style"); - if (!s.IsEmpty()) + const AString appl ("application/"); + if (s.IsPrefixedBy(appl)) { - const AString appl = "application/"; - if (s.IsPrefixedBy(appl)) + s.DeleteFrontal(appl.Len()); + const AString xx ("x-"); + if (s.IsPrefixedBy(xx)) { - s.DeleteFrontal(appl.Len()); - const AString xx = "x-"; - if (s.IsPrefixedBy(xx)) - { - s.DeleteFrontal(xx.Len()); - if (s == "gzip") - s = METHOD_NAME_ZLIB; - } + s.DeleteFrontal(xx.Len()); + if (s.IsEqualTo("gzip")) + s = METHOD_NAME_ZLIB; } - file.Method = s; } + file.Method = s; } } } + file.INode_Defined = ParseUInt64(item, "inode", file.INode); + file.UserId_Defined = ParseUInt64(item, "uid", file.UserId); + file.GroupId_Defined = ParseUInt64(item, "gid", file.GroupId); + // file.Device_Defined = ParseUInt64(item, "deviceno", file.Device); + file.MTime = ParseTime(item, "mtime"); // z_IsRequied = true file.CTime = ParseTime(item, "ctime"); - file.MTime = ParseTime(item, "mtime"); file.ATime = ParseTime(item, "atime"); - { - const AString s = item.GetSubStringForTag("mode"); + const AString s (item.GetSubStringForTag("mode")); if (s[0] == '0') { const char *end; file.Mode = ConvertOctStringToUInt32(s, &end); - file.ModeDefined = (*end == 0); + file.Mode_Defined = (*end == 0); } } - file.User = item.GetSubStringForTag("user"); file.Group = item.GetSubStringForTag("group"); files.Add(file); } + FOR_VECTOR (i, item.SubItems) - if (!AddItem(item.SubItems[i], files, parent)) + if (!AddItem(item.SubItems[i], files, parent, level + 1)) return false; return true; } -HRESULT CHandler::Open2(IInStream *stream) + + +struct CInStreamWithHash { - const UInt32 kHeaderSize = 0x1C; - Byte buf[kHeaderSize]; - RINOK(ReadStream_FALSE(stream, buf, kHeaderSize)); + CMyComPtr2_Create inStreamSha1; + CMyComPtr2_Create inStreamSha256; + CMyComPtr2_Create inStreamLim; - UInt32 size = Get16(buf + 4); - // UInt32 ver = Get16(buf + 6); // == 1 - if (Get32(buf) != 0x78617221 || size != kHeaderSize) - return S_FALSE; + void SetStreamAndInit(ISequentialInStream *stream, int algo); + bool CheckHash(int algo, const Byte *digest_from_arc) const; +}; - UInt64 packSize = Get64(buf + 8); - UInt64 unpackSize = Get64(buf + 0x10); - // _checkSumAlgo = Get32(buf + 0x18); +void CInStreamWithHash::SetStreamAndInit(ISequentialInStream *stream, int algo) +{ + if (algo == XAR_CKSUM_SHA1) + { + inStreamSha1->SetStream(stream); + inStreamSha1->Init(); + stream = inStreamSha1; + } + else if (algo == XAR_CKSUM_SHA256 + || algo == XAR_CKSUM_SHA512) + { + inStreamSha256->SetStream(stream); + inStreamSha256->Init(algo == XAR_CKSUM_SHA512); + stream = inStreamSha256; + } + inStreamLim->SetStream(stream); +} + +bool CInStreamWithHash::CheckHash(int algo, const Byte *digest_from_arc) const +{ + if (algo == XAR_CKSUM_SHA1) + { + Byte digest[SHA1_DIGEST_SIZE]; + inStreamSha1->Final(digest); + if (memcmp(digest, digest_from_arc, sizeof(digest)) != 0) + return false; + } + else if (algo == XAR_CKSUM_SHA256) + { + Byte digest[SHA256_DIGEST_SIZE]; + inStreamSha256->Final256(digest); + if (memcmp(digest, digest_from_arc, sizeof(digest)) != 0) + return false; + } + else if (algo == XAR_CKSUM_SHA512) + { + Byte digest[SHA512_DIGEST_SIZE]; + inStreamSha256->Final512(digest); + if (memcmp(digest, digest_from_arc, sizeof(digest)) != 0) + return false; + } + return true; +} + +HRESULT CHandler::Open2(IInStream *stream) +{ + const unsigned kHeaderSize = 28; + UInt32 buf32[kHeaderSize / sizeof(UInt32)]; + RINOK(ReadStream_FALSE(stream, buf32, kHeaderSize)) + const unsigned headerSize = Get16((const Byte *)(const void *)buf32 + 4); + // xar library now writes 1 to version field. + // some old xars could have version == 0 ? + // specification allows (headerSize != 28), + // but we don't expect big value in (headerSize). + // so we restrict (headerSize) with 64 bytes to reduce false open. + const unsigned kHeaderSize_MAX = 64; + if (Get32(buf32) != 0x78617221 // signature: "xar!" + || headerSize < kHeaderSize + || headerSize > kHeaderSize_MAX + || Get16((const Byte *)(const void *)buf32 + 6) > 1 // version + ) + return S_FALSE; + _checkSumAlgo = Get32(buf32 + 6); + const UInt64 packSize = Get64(buf32 + 2); + const UInt64 unpackSize = Get64(buf32 + 4); if (packSize >= kXmlPackSizeMax || unpackSize >= kXmlSizeMax) return S_FALSE; - - _dataStartPos = kHeaderSize + packSize; + /* some xar archives can have padding bytes at offset 28, + or checksum algorithm name at offset 28 (in xar fork, if cksum_alg==3) + But we didn't see such xar archives. + */ + if (headerSize != kHeaderSize) + { + RINOK(InStream_SeekSet(stream, headerSize)) + } + _dataStartPos = headerSize + packSize; _phySize = _dataStartPos; - _xml.Alloc((size_t)unpackSize + 1); + _xmlBuf.Alloc((size_t)unpackSize + 1); + if (!_xmlBuf.IsAllocated()) + return E_OUTOFMEMORY; _xmlLen = (size_t)unpackSize; - NCompress::NZlib::CDecoder *zlibCoderSpec = new NCompress::NZlib::CDecoder(); - CMyComPtr zlibCoder = zlibCoderSpec; - - CLimitedSequentialInStream *inStreamLimSpec = new CLimitedSequentialInStream; - CMyComPtr inStreamLim(inStreamLimSpec); - inStreamLimSpec->SetStream(stream); - inStreamLimSpec->Init(packSize); - - CBufPtrSeqOutStream *outStreamLimSpec = new CBufPtrSeqOutStream; - CMyComPtr outStreamLim(outStreamLimSpec); - outStreamLimSpec->Init(_xml, (size_t)unpackSize); - - RINOK(zlibCoder->Code(inStreamLim, outStreamLim, NULL, NULL, NULL)); - - if (outStreamLimSpec->GetPos() != (size_t)unpackSize) + CInStreamWithHash hashStream; + { + CMyComPtr2_Create zlibCoder; + hashStream.SetStreamAndInit(stream, (int)(unsigned)_checkSumAlgo); + hashStream.inStreamLim->Init(packSize); + CMyComPtr2_Create outStreamLim; + outStreamLim->Init(_xmlBuf, (size_t)unpackSize); + RINOK(zlibCoder.Interface()->Code(hashStream.inStreamLim, outStreamLim, NULL, &unpackSize, NULL)) + if (outStreamLim->GetPos() != (size_t)unpackSize) + return S_FALSE; + } + _xmlBuf[(size_t)unpackSize] = 0; + if (strlen((const char *)(const Byte *)_xmlBuf) != (size_t)unpackSize) return S_FALSE; - - _xml[(size_t)unpackSize] = 0; - if (strlen((const char *)(const Byte *)_xml) != unpackSize) return S_FALSE; - CXml xml; - if (!xml.Parse((const char *)(const Byte *)_xml)) + if (!xml.Parse((const char *)(const Byte *)_xmlBuf)) return S_FALSE; if (!xml.Root.IsTagged("xar") || xml.Root.SubItems.Size() != 1) @@ -355,7 +647,40 @@ HRESULT CHandler::Open2(IInStream *stream) const CXmlItem &toc = xml.Root.SubItems[0]; if (!toc.IsTagged("toc")) return S_FALSE; - if (!AddItem(toc, _files, -1)) + + // CreationTime = ParseTime(toc, "creation-time", false); // z_IsRequied + CreationTime_String = toc.GetSubStringForTag("creation-time"); + { + // we suppose that offset of checksum is always 0; + // but [TOC].xml contains exact offset value in block. + const UInt64 offset = 0; + const unsigned hashSize = GetHashSize((int)(unsigned)_checkSumAlgo); + if (hashSize) + { + /* + const CXmlItem *csItem = toc.FindSubTag_GetPtr("checksum"); + if (csItem) + { + const int checkSumAlgo2 = Find_ChecksumId_for_Name(csItem->GetPropVal("style")); + UInt64 csSize, csOffset; + if (ParseUInt64(*csItem, "size", csSize) && + ParseUInt64(*csItem, "offset", csOffset) && + csSize == hashSize && + (unsigned)checkSumAlgo2 == _checkSumAlgo) + offset = csOffset; + } + */ + CByteBuffer digest_from_arc(hashSize); + RINOK(InStream_SeekSet(stream, _dataStartPos + offset)) + RINOK(ReadStream_FALSE(stream, digest_from_arc, hashSize)) + if (!hashStream.CheckHash((int)(unsigned)_checkSumAlgo, digest_from_arc)) + _toc_CrcError = true; + } + } + + if (!AddItem(toc, _files, + -1, // parent + 0)) // level return S_FALSE; UInt64 totalPackSize = 0; @@ -365,56 +690,67 @@ HRESULT CHandler::Open2(IInStream *stream) { const CFile &file = _files[i]; file.UpdateTotalPackSize(totalPackSize); - if (file.Name == "Payload" || file.Name == "Content") + if (file.Parent == -1) { - _mainSubfile = i; - numMainFiles++; + if (file.Name.IsEqualTo("Payload") || + file.Name.IsEqualTo("Content")) + { + _mainSubfile = (Int32)(int)i; + numMainFiles++; + } + else if (file.Name.IsEqualTo("PackageInfo")) + _is_pkg = true; } - else if (file.Name == "PackageInfo") - _is_pkg = true; } if (numMainFiles > 1) _mainSubfile = -1; - - _phySize = _dataStartPos + totalPackSize; + + const UInt64 k_PhySizeLim = (UInt64)1 << 62; + _phySize = (totalPackSize > k_PhySizeLim - _dataStartPos) ? + k_PhySizeLim : + _dataStartPos + totalPackSize; return S_OK; } -STDMETHODIMP CHandler::Open(IInStream *stream, +Z7_COM7F_IMF(CHandler::Open(IInStream *stream, const UInt64 * /* maxCheckStartPosition */, - IArchiveOpenCallback * /* openArchiveCallback */) + IArchiveOpenCallback * /* openArchiveCallback */)) { COM_TRY_BEGIN { Close(); - if (Open2(stream) != S_OK) - return S_FALSE; + RINOK(Open2(stream)) _inStream = stream; } return S_OK; COM_TRY_END } -STDMETHODIMP CHandler::Close() +Z7_COM7F_IMF(CHandler::Close()) { _phySize = 0; + _dataStartPos = 0; _inStream.Release(); _files.Clear(); _xmlLen = 0; - _xml.Free(); + _xmlBuf.Free(); _mainSubfile = -1; _is_pkg = false; + _toc_CrcError = false; + _checkSumAlgo = 0; + // CreationTime = 0; + CreationTime_String.Empty(); return S_OK; } -STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) { *numItems = _files.Size() - #ifdef XAR_SHOW_RAW +#ifdef XAR_SHOW_RAW + 1 - #endif +#endif ; return S_OK; } @@ -435,90 +771,194 @@ static void Utf8StringToProp(const AString &s, NCOM::CPropVariant &prop) if (!s.IsEmpty()) { UString us; - if (ConvertUTF8ToUnicode(s, us)) - prop = us; + ConvertUTF8ToUnicode(s, us); + prop = us; } } -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; switch (propID) { - case kpidHeadersSize: prop = _dataStartPos; break; + // case kpidHeadersSize: prop = _dataStartPos; break; case kpidPhySize: prop = _phySize; break; case kpidMainSubfile: if (_mainSubfile >= 0) prop = (UInt32)_mainSubfile; break; case kpidSubType: if (_is_pkg) prop = "pkg"; break; case kpidExtension: prop = _is_pkg ? "pkg" : "xar"; break; + case kpidCTime: + { + // it's local time. We can transfer it to UTC time, if we use FILETIME. + // TimeToProp(CreationTime, prop); break; + if (!CreationTime_String.IsEmpty()) + prop = CreationTime_String; + break; + } + case kpidMethod: + { + AString s; + if (_checkSumAlgo < Z7_ARRAY_SIZE(k_ChecksumNames)) + s = k_ChecksumNames[_checkSumAlgo]; + else + { + s += "Checksum"; + s.Add_UInt32(_checkSumAlgo); + } + prop = s; + break; + } + case kpidWarningFlags: + { + UInt32 v = 0; + if (_toc_CrcError) v |= kpv_ErrorFlags_CrcError; + prop = v; + break; + } + case kpidINode: prop = true; break; + case kpidIsTree: prop = true; break; } prop.Detach(value); return S_OK; COM_TRY_END } -STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) + +/* +inline UInt32 MY_dev_major(UInt64 dev) +{ + return ((UInt32)(dev >> 8) & (UInt32)0xfff) | ((UInt32)(dev >> 32) & ~(UInt32)0xfff); +} +inline UInt32 MY_dev_minor(UInt64 dev) +{ + return ((UInt32)(dev) & 0xff) | ((UInt32)(dev >> 12) & ~(UInt32)0xff); +} +*/ + +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; - #ifdef XAR_SHOW_RAW - if (index == _files.Size()) +#ifdef XAR_SHOW_RAW + if (index >= _files.Size()) { switch (propID) { - case kpidPath: prop = "[TOC].xml"; break; + case kpidName: + case kpidPath: + prop = "[TOC].xml"; break; case kpidSize: case kpidPackSize: prop = (UInt64)_xmlLen; break; } } else - #endif +#endif { const CFile &item = _files[index]; switch (propID) { - case kpidMethod: Utf8StringToProp(item.Method, prop); break; - case kpidPath: { AString path; - int cur = index; - do + unsigned cur = index; + for (;;) { const CFile &item2 = _files[cur]; if (!path.IsEmpty()) path.InsertAtFront(CHAR_PATH_SEPARATOR); +// #define XAR_EMPTY_NAME_REPLACEMENT "[]" if (item2.Name.IsEmpty()) - path.Insert(0, "unknown"); + { + AString s('['); + s.Add_UInt32(cur); + s.Add_Char(']'); + path.Insert(0, s); + } else path.Insert(0, item2.Name); - cur = item2.Parent; + if (item2.Parent < 0) + break; + cur = (unsigned)item2.Parent; } - while (cur >= 0); - Utf8StringToProp(path, prop); break; } - + + case kpidName: + { + if (item.Name.IsEmpty()) + { + AString s('['); + s.Add_UInt32(index); + s.Add_Char(']'); + prop = s; + } + else + Utf8StringToProp(item.Name, prop); + break; + } + case kpidIsDir: prop = item.IsDir; break; - case kpidSize: if (!item.IsDir) prop = item.Size; break; - case kpidPackSize: if (!item.IsDir) prop = item.PackSize; break; + case kpidSize: if (item.HasData && !item.IsDir) prop = item.Size; break; + case kpidPackSize: if (item.HasData && !item.IsDir) prop = item.PackSize; break; + + case kpidMethod: + { + if (item.HasData) + { + AString s = item.Method; + item.extracted_checksum.AddNameToString(s); + item.archived_checksum.AddNameToString(s); + Utf8StringToProp(s, prop); + } + break; + } + case kpidMTime: TimeToProp(item.MTime, prop); break; case kpidCTime: TimeToProp(item.CTime, prop); break; case kpidATime: TimeToProp(item.ATime, prop); break; + case kpidPosixAttrib: - if (item.ModeDefined) + if (item.Mode_Defined) { UInt32 mode = item.Mode; if ((mode & MY_LIN_S_IFMT) == 0) - mode |= (item.IsDir ? MY_LIN_S_IFDIR : MY_LIN_S_IFREG); + mode |= ( + item.Is_SymLink ? MY_LIN_S_IFLNK : + item.IsDir ? MY_LIN_S_IFDIR : + MY_LIN_S_IFREG); prop = mode; } break; - case kpidUser: Utf8StringToProp(item.User, prop); break; + + case kpidType: Utf8StringToProp(item.Type, prop); break; + case kpidUser: Utf8StringToProp(item.User, prop); break; case kpidGroup: Utf8StringToProp(item.Group, prop); break; + case kpidSymLink: if (item.Is_SymLink) Utf8StringToProp(item.Link, prop); break; + + case kpidUserId: if (item.UserId_Defined) prop = item.UserId; break; + case kpidGroupId: if (item.GroupId_Defined) prop = item.GroupId; break; + case kpidINode: if (item.INode_Defined) prop = item.INode; break; + case kpidId: if (item.Id_Defined) prop = item.Id; break; + // Utf8StringToProp(item.Id, prop); + /* + case kpidDeviceMajor: if (item.Device_Defined) prop = (UInt32)MY_dev_major(item.Device); break; + case kpidDeviceMinor: if (item.Device_Defined) prop = (UInt32)MY_dev_minor(item.Device); break; + case kpidLinkType: + if (!item.LinkType.IsEmpty()) + Utf8StringToProp(item.LinkType, prop); + break; + case kpidLinkFrom: + if (!item.LinkFrom.IsEmpty()) + Utf8StringToProp(item.LinkFrom, prop); + break; + */ + case kpidOffset: + if (item.HasData) + prop = _dataStartPos + item.Offset; + break; } } prop.Detach(value); @@ -526,143 +966,264 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val COM_TRY_END } -STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, - Int32 testMode, IArchiveExtractCallback *extractCallback) + +// for debug: +// #define Z7_XAR_SHOW_CHECKSUM_PACK + +#ifdef Z7_XAR_SHOW_CHECKSUM_PACK +enum +{ + kpidChecksumPack = kpidUserDefined +}; +#endif + +static const Byte kRawProps[] = +{ + kpidChecksum +#ifdef Z7_XAR_SHOW_CHECKSUM_PACK + , kpidCRC // instead of kpidUserDefined / kpidCRC +#endif +}; + +Z7_COM7F_IMF(CHandler::GetNumRawProps(UInt32 *numProps)) +{ + *numProps = Z7_ARRAY_SIZE(kRawProps); + return S_OK; +} + +Z7_COM7F_IMF(CHandler::GetRawPropInfo(UInt32 index, BSTR *name, PROPID *propID)) +{ + *propID = kRawProps[index]; + *name = NULL; + +#ifdef Z7_XAR_SHOW_CHECKSUM_PACK + if (index != 0) + { + *propID = kpidChecksumPack; + *name = NWindows::NCOM::AllocBstrFromAscii("archived-checksum"); + } +#endif + return S_OK; +} + +Z7_COM7F_IMF(CHandler::GetParent(UInt32 index, UInt32 *parent, UInt32 *parentType)) +{ + *parentType = NParentType::kDir; + *parent = (UInt32)(Int32)-1; +#ifdef XAR_SHOW_RAW + if (index >= _files.Size()) + return S_OK; +#endif + { + const CFile &item = _files[index]; + *parent = (UInt32)(Int32)item.Parent; + } + return S_OK; +} + + +Z7_COM7F_IMF(CHandler::GetRawProp(UInt32 index, PROPID propID, const void **data, UInt32 *dataSize, UInt32 *propType)) +{ + *data = NULL; + *dataSize = 0; + *propType = 0; + + // COM_TRY_BEGIN + NCOM::CPropVariant prop; + + if (propID == kpidChecksum) + { +#ifdef XAR_SHOW_RAW + if (index >= _files.Size()) + { + // case kpidPath: prop = "[TOC].xml"; break; + } + else +#endif + { + const CFile &item = _files[index]; + const size_t size = item.extracted_checksum.Data.Size(); + if (size != 0) + { + *dataSize = (UInt32)size; + *propType = NPropDataType::kRaw; + *data = item.extracted_checksum.Data; + } + } + } + +#ifdef Z7_XAR_SHOW_CHECKSUM_PACK + if (propID == kpidChecksumPack) + { +#ifdef XAR_SHOW_RAW + if (index >= _files.Size()) + { + // we can show digest check sum here + } + else +#endif + { + const CFile &item = _files[index]; + const size_t size = (UInt32)item.archived_checksum.Data.Size(); + if (size != 0) + { + *dataSize = (UInt32)size; + *propType = NPropDataType::kRaw; + *data = item.archived_checksum.Data; + } + } + } +#endif + return S_OK; +} + + + + +Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testMode, IArchiveExtractCallback *extractCallback)) { COM_TRY_BEGIN - bool allFilesMode = (numItems == (UInt32)(Int32)-1); + const bool allFilesMode = (numItems == (UInt32)(Int32)-1); if (allFilesMode) - numItems = _files.Size(); + numItems = _files.Size() +#ifdef XAR_SHOW_RAW + + 1 +#endif + ; if (numItems == 0) return S_OK; UInt64 totalSize = 0; UInt32 i; for (i = 0; i < numItems; i++) { - UInt32 index = (allFilesMode ? i : indices[i]); - #ifdef XAR_SHOW_RAW - if (index == _files.Size()) + const UInt32 index = allFilesMode ? i : indices[i]; +#ifdef XAR_SHOW_RAW + if (index >= _files.Size()) totalSize += _xmlLen; else - #endif +#endif totalSize += _files[index].Size; } - extractCallback->SetTotal(totalSize); - - UInt64 currentPackTotal = 0; - UInt64 currentUnpTotal = 0; - UInt64 currentPackSize = 0; - UInt64 currentUnpSize = 0; - - const UInt32 kZeroBufSize = (1 << 14); - CByteBuffer zeroBuf(kZeroBufSize); - memset(zeroBuf, 0, kZeroBufSize); - - NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder(); - CMyComPtr copyCoder = copyCoderSpec; + RINOK(extractCallback->SetTotal(totalSize)) - NCompress::NZlib::CDecoder *zlibCoderSpec = new NCompress::NZlib::CDecoder(); - CMyComPtr zlibCoder = zlibCoderSpec; - - NCompress::NBZip2::CDecoder *bzip2CoderSpec = new NCompress::NBZip2::CDecoder(); - CMyComPtr bzip2Coder = bzip2CoderSpec; - - NCompress::NDeflate::NDecoder::CCOMCoder *deflateCoderSpec = new NCompress::NDeflate::NDecoder::CCOMCoder(); - CMyComPtr deflateCoder = deflateCoderSpec; - - CLocalProgress *lps = new CLocalProgress; - CMyComPtr progress = lps; + CMyComPtr2_Create lps; lps->Init(extractCallback, false); - - CLimitedSequentialInStream *inStreamSpec = new CLimitedSequentialInStream; - CMyComPtr inStream(inStreamSpec); - inStreamSpec->SetStream(_inStream); - + CInStreamWithHash inHashStream; + CMyComPtr2_Create outStreamSha1; + CMyComPtr2_Create outStreamSha256; + CMyComPtr2_Create outStreamLim; + CMyComPtr2_Create copyCoder; + CMyComPtr2_Create zlibCoder; + CMyComPtr2_Create bzip2Coder; + bzip2Coder->FinishMode = true; - CLimitedSequentialOutStream *outStreamLimSpec = new CLimitedSequentialOutStream; - CMyComPtr outStream(outStreamLimSpec); + UInt64 cur_PackSize, cur_UnpSize; - COutStreamWithSha1 *outStreamSha1Spec = new COutStreamWithSha1; + for (i = 0;; i++, + lps->InSize += cur_PackSize, + lps->OutSize += cur_UnpSize) { - CMyComPtr outStreamSha1(outStreamSha1Spec); - outStreamLimSpec->SetStream(outStreamSha1); - } + cur_PackSize = 0; + cur_UnpSize = 0; + RINOK(lps->SetCur()) + if (i >= numItems) + break; - for (i = 0; i < numItems; i++, currentPackTotal += currentPackSize, currentUnpTotal += currentUnpSize) - { - lps->InSize = currentPackTotal; - lps->OutSize = currentUnpTotal; - currentPackSize = 0; - currentUnpSize = 0; - RINOK(lps->SetCur()); CMyComPtr realOutStream; - Int32 askMode = testMode ? + const Int32 askMode = testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; - UInt32 index = allFilesMode ? i : indices[i]; - RINOK(extractCallback->GetStream(index, &realOutStream, askMode)); + const UInt32 index = allFilesMode ? i : indices[i]; + RINOK(extractCallback->GetStream(index, &realOutStream, askMode)) if (index < _files.Size()) { const CFile &item = _files[index]; if (item.IsDir) { - RINOK(extractCallback->PrepareOperation(askMode)); - RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)); + RINOK(extractCallback->PrepareOperation(askMode)) + realOutStream.Release(); + RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)) continue; } } if (!testMode && !realOutStream) continue; - RINOK(extractCallback->PrepareOperation(askMode)); - - outStreamSha1Spec->SetStream(realOutStream); - realOutStream.Release(); + RINOK(extractCallback->PrepareOperation(askMode)) Int32 opRes = NExtract::NOperationResult::kOK; - #ifdef XAR_SHOW_RAW - if (index == _files.Size()) + +#ifdef XAR_SHOW_RAW + if (index >= _files.Size()) { - outStreamSha1Spec->Init(false); - outStreamLimSpec->Init(_xmlLen); - RINOK(WriteStream(outStream, _xml, _xmlLen)); - currentPackSize = currentUnpSize = _xmlLen; + cur_PackSize = cur_UnpSize = _xmlLen; + if (realOutStream) + RINOK(WriteStream(realOutStream, _xmlBuf, _xmlLen)) + realOutStream.Release(); } else - #endif +#endif { const CFile &item = _files[index]; - if (item.HasData) + if (!item.HasData) + realOutStream.Release(); + else { - currentPackSize = item.PackSize; - currentUnpSize = item.Size; + cur_PackSize = item.PackSize; + cur_UnpSize = item.Size; - RINOK(_inStream->Seek(_dataStartPos + item.Offset, STREAM_SEEK_SET, NULL)); - inStreamSpec->Init(item.PackSize); - outStreamSha1Spec->Init(item.Sha1IsDefined); - outStreamLimSpec->Init(item.Size); + RINOK(InStream_SeekSet(_inStream, _dataStartPos + item.Offset)) + + inHashStream.SetStreamAndInit(_inStream, item.archived_checksum.AlgoNumber); + inHashStream.inStreamLim->Init(item.PackSize); + + const int checksum_method = item.extracted_checksum.AlgoNumber; + if (checksum_method == XAR_CKSUM_SHA1) + { + outStreamLim->SetStream(outStreamSha1); + outStreamSha1->SetStream(realOutStream); + outStreamSha1->Init(); + } + else if (checksum_method == XAR_CKSUM_SHA256 + || checksum_method == XAR_CKSUM_SHA512) + { + outStreamLim->SetStream(outStreamSha256); + outStreamSha256->SetStream(realOutStream); + outStreamSha256->Init(checksum_method == XAR_CKSUM_SHA512); + } + else + outStreamLim->SetStream(realOutStream); + + realOutStream.Release(); + + // outStreamSha1->Init(item.Sha1IsDefined); + + outStreamLim->Init(item.Size); HRESULT res = S_OK; ICompressCoder *coder = NULL; if (item.IsCopyMethod()) + { if (item.PackSize == item.Size) coder = copyCoder; else opRes = NExtract::NOperationResult::kUnsupportedMethod; - else if (item.Method == METHOD_NAME_ZLIB) + } + else if (item.Method.IsEqualTo(METHOD_NAME_ZLIB)) coder = zlibCoder; - else if (item.Method == "bzip2") + else if (item.Method.IsEqualTo("bzip2")) coder = bzip2Coder; else opRes = NExtract::NOperationResult::kUnsupportedMethod; if (coder) - res = coder->Code(inStream, outStream, NULL, NULL, progress); + res = coder->Code(inHashStream.inStreamLim, outStreamLim, NULL, &item.Size, lps); if (res != S_OK) { - if (!outStreamLimSpec->IsFinishedOK()) + if (!outStreamLim->IsFinishedOK()) opRes = NExtract::NOperationResult::kDataError; else if (res != S_FALSE) return res; @@ -672,45 +1233,63 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, if (opRes == NExtract::NOperationResult::kOK) { - if (outStreamLimSpec->IsFinishedOK() && - outStreamSha1Spec->GetSize() == item.Size) + if (outStreamLim->IsFinishedOK()) { - if (!outStreamLimSpec->IsFinishedOK()) + if (checksum_method == XAR_CKSUM_SHA1) { - opRes = NExtract::NOperationResult::kDataError; + Byte digest[SHA1_DIGEST_SIZE]; + outStreamSha1->Final(digest); + if (memcmp(digest, item.extracted_checksum.Data, SHA1_DIGEST_SIZE) != 0) + opRes = NExtract::NOperationResult::kCRCError; } - else if (item.Sha1IsDefined) + else if (checksum_method == XAR_CKSUM_SHA256) { - Byte digest[SHA1_DIGEST_SIZE]; - outStreamSha1Spec->Final(digest); - if (memcmp(digest, item.Sha1, SHA1_DIGEST_SIZE) != 0) + Byte digest[SHA256_DIGEST_SIZE]; + outStreamSha256->Final256(digest); + if (memcmp(digest, item.extracted_checksum.Data, sizeof(digest)) != 0) opRes = NExtract::NOperationResult::kCRCError; } + else if (checksum_method == XAR_CKSUM_SHA512) + { + Byte digest[SHA512_DIGEST_SIZE]; + outStreamSha256->Final512(digest); + if (memcmp(digest, item.extracted_checksum.Data, sizeof(digest)) != 0) + opRes = NExtract::NOperationResult::kCRCError; + } + if (opRes == NExtract::NOperationResult::kOK) + if (!inHashStream.CheckHash( + item.archived_checksum.AlgoNumber, + item.archived_checksum.Data)) + opRes = NExtract::NOperationResult::kCRCError; } else opRes = NExtract::NOperationResult::kDataError; } + if (checksum_method == XAR_CKSUM_SHA1) + outStreamSha1->ReleaseStream(); + else if (checksum_method == XAR_CKSUM_SHA256) + outStreamSha256->ReleaseStream(); } + outStreamLim->ReleaseStream(); } - outStreamSha1Spec->ReleaseStream(); - RINOK(extractCallback->SetOperationResult(opRes)); + RINOK(extractCallback->SetOperationResult(opRes)) } return S_OK; COM_TRY_END } -STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream) +Z7_COM7F_IMF(CHandler::GetStream(UInt32 index, ISequentialInStream **stream)) { *stream = NULL; COM_TRY_BEGIN - #ifdef XAR_SHOW_RAW - if (index == _files.Size()) +#ifdef XAR_SHOW_RAW + if (index >= _files.Size()) { - Create_BufInStream_WithNewBuffer(_xml, _xmlLen, stream); + Create_BufInStream_WithNewBuffer(_xmlBuf, _xmlLen, stream); return S_OK; } else - #endif +#endif { const CFile &item = _files[index]; if (item.HasData && item.IsCopyMethod() && item.PackSize == item.Size) @@ -720,10 +1299,15 @@ STDMETHODIMP CHandler::GetStream(UInt32 index, ISequentialInStream **stream) COM_TRY_END } -static const Byte k_Signature[] = { 'x', 'a', 'r', '!', 0, 0x1C }; +// 0x1c == 28 is expected header size value for most archives. +// but we want to support another (rare case) headers sizes. +// so we must reduce signature to 4 or 5 bytes. +static const Byte k_Signature[] = +// { 'x', 'a', 'r', '!', 0, 0x1C, 0 }; + { 'x', 'a', 'r', '!', 0 }; REGISTER_ARC_I( - "Xar", "xar pkg xip", 0, 0xE1, + "Xar", "xar pkg xip", NULL, 0xE1, k_Signature, 0, 0, diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/XzHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/XzHandler.cpp index 318be190..5aaa4057 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/XzHandler.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/XzHandler.cpp @@ -3,16 +3,15 @@ #include "StdAfx.h" #include "../../../C/Alloc.h" -#include "../../../C/XzCrc64.h" -#include "../../../C/XzEnc.h" #include "../../Common/ComTry.h" #include "../../Common/Defs.h" #include "../../Common/IntToString.h" +#include "../../Common/MyBuffer.h" +#include "../../Common/StringToInt.h" #include "../../Windows/PropVariant.h" - -#include "../ICoder.h" +#include "../../Windows/System.h" #include "../Common/CWrappers.h" #include "../Common/ProgressUtils.h" @@ -20,126 +19,179 @@ #include "../Common/StreamUtils.h" #include "../Compress/CopyCoder.h" +#include "../Compress/XzDecoder.h" +#include "../Compress/XzEncoder.h" #include "IArchive.h" -#ifndef EXTRACT_ONLY #include "Common/HandlerOut.h" -#endif - -#include "XzHandler.h" using namespace NWindows; -namespace NCompress { -namespace NLzma2 { - -HRESULT SetLzma2Prop(PROPID propID, const PROPVARIANT &prop, CLzma2EncProps &lzma2Props); - -}} - namespace NArchive { namespace NXz { -struct CCrc64Gen { CCrc64Gen() { Crc64GenerateTable(); } } g_Crc64TableInit; +#define k_LZMA2_Name "LZMA2" -static const char *k_LZMA2_Name = "LZMA2"; -void CStatInfo::Clear() +struct CBlockInfo { - InSize = 0; - OutSize = 0; - PhySize = 0; - - NumStreams = 0; - NumBlocks = 0; - - UnpackSize_Defined = false; - - NumStreams_Defined = false; - NumBlocks_Defined = false; - - IsArc = false; - UnexpectedEnd = false; - DataAfterEnd = false; - Unsupported = false; - HeadersError = false; - DataError = false; - CrcError = false; -} + unsigned StreamFlags; + UInt64 PackPos; + UInt64 PackSize; // pure value from Index record, it doesn't include pad zeros + UInt64 UnpackPos; +}; + -class CHandler: +Z7_class_CHandler_final: public IInArchive, public IArchiveOpenSeq, - #ifndef EXTRACT_ONLY - public IOutArchive, + public IInArchiveGetStream, public ISetProperties, - public CMultiMethodProps, - #endif - public CMyUnknownImp + #ifndef Z7_EXTRACT_ONLY + public IOutArchive, + #endif + public CMyUnknownImp, + #ifndef Z7_EXTRACT_ONLY + public CMultiMethodProps + #else + public CCommonMethodProps + #endif { - CStatInfo _stat; - + Z7_COM_QI_BEGIN2(IInArchive) + Z7_COM_QI_ENTRY(IArchiveOpenSeq) + Z7_COM_QI_ENTRY(IInArchiveGetStream) + Z7_COM_QI_ENTRY(ISetProperties) + #ifndef Z7_EXTRACT_ONLY + Z7_COM_QI_ENTRY(IOutArchive) + #endif + Z7_COM_QI_END + Z7_COM_ADDREF_RELEASE + + Z7_IFACE_COM7_IMP(IInArchive) + Z7_IFACE_COM7_IMP(IArchiveOpenSeq) + Z7_IFACE_COM7_IMP(IInArchiveGetStream) + Z7_IFACE_COM7_IMP(ISetProperties) + #ifndef Z7_EXTRACT_ONLY + Z7_IFACE_COM7_IMP(IOutArchive) + #endif + + bool _stat_defined; + bool _stat2_defined; bool _isArc; bool _needSeekToStart; - bool _phySize_Defined; - - CMyComPtr _stream; - CMyComPtr _seqStream; + bool _firstBlockWasRead; + SRes _stat2_decode_SRes; + + CXzStatInfo _stat; // it's stat from backward parsing + CXzStatInfo _stat2; // it's data from forward parsing, if the decoder was called + const CXzStatInfo *GetStat() const + { + if (_stat_defined) return &_stat; + if (_stat2_defined) return &_stat2; + return NULL; + } + AString _methodsString; - #ifndef EXTRACT_ONLY + + #ifndef Z7_EXTRACT_ONLY UInt32 _filterId; + UInt64 _numSolidBytes; - void Init() + void InitXz() { _filterId = 0; - CMultiMethodProps::Init(); + _numSolidBytes = XZ_PROPS_BLOCK_SIZE_AUTO; } - + #endif + + void Init() + { + #ifndef Z7_EXTRACT_ONLY + InitXz(); + CMultiMethodProps::Init(); + #else + CCommonMethodProps::InitCommon(); + #endif + } + + HRESULT SetProperty(const wchar_t *name, const PROPVARIANT &value); + HRESULT Open2(IInStream *inStream, /* UInt32 flags, */ IArchiveOpenCallback *callback); - HRESULT Decode2(ISequentialInStream *seqInStream, ISequentialOutStream *outStream, - CDecoder &decoder, ICompressProgressInfo *progress) + HRESULT Decode(NCompress::NXz::CDecoder &decoder, + ISequentialInStream *seqInStream, + ISequentialOutStream *outStream, + ICompressProgressInfo *progress) { - RINOK(decoder.Decode(seqInStream, outStream, progress)); - _stat = decoder; - _phySize_Defined = true; - return S_OK; + #ifndef Z7_ST + decoder._numThreads = _numThreads; + #endif + decoder._memUsage = _memUsage_Decompress; + + const HRESULT hres = decoder.Decode(seqInStream, outStream, + NULL, // *outSizeLimit + true, // finishStream + progress); + + if (decoder.MainDecodeSRes_wasUsed + && decoder.MainDecodeSRes != SZ_ERROR_MEM + && decoder.MainDecodeSRes != SZ_ERROR_UNSUPPORTED) + { + // if (!_stat2_defined) + { + _stat2_decode_SRes = decoder.MainDecodeSRes; + _stat2 = decoder.Stat; + _stat2_defined = true; + } + } + + if (hres == S_OK && progress) + { + // RINOK( + progress->SetRatioInfo(&decoder.Stat.InSize, &decoder.Stat.OutSize); + } + return hres; } public: - MY_QUERYINTERFACE_BEGIN2(IInArchive) - MY_QUERYINTERFACE_ENTRY(IArchiveOpenSeq) - #ifndef EXTRACT_ONLY - MY_QUERYINTERFACE_ENTRY(IOutArchive) - MY_QUERYINTERFACE_ENTRY(ISetProperties) - #endif - MY_QUERYINTERFACE_END - MY_ADDREF_RELEASE - - INTERFACE_IInArchive(;) - STDMETHOD(OpenSeq)(ISequentialInStream *stream); + CBlockInfo *_blocks; + size_t _blocksArraySize; + UInt64 _maxBlocksSize; + CMyComPtr _stream; + CMyComPtr _seqStream; - #ifndef EXTRACT_ONLY - INTERFACE_IOutArchive(;) - STDMETHOD(SetProperties)(const wchar_t * const *names, const PROPVARIANT *values, UInt32 numProps); - #endif + CXzBlock _firstBlock; CHandler(); + ~CHandler(); + + HRESULT SeekToPackPos(UInt64 pos) + { + return InStream_SeekSet(_stream, pos); + } }; -CHandler::CHandler() + +CHandler::CHandler(): + _blocks(NULL), + _blocksArraySize(0) { - #ifndef EXTRACT_ONLY - Init(); + #ifndef Z7_EXTRACT_ONLY + InitXz(); #endif } +CHandler::~CHandler() +{ + MyFree(_blocks); +} + static const Byte kProps[] = { @@ -152,30 +204,14 @@ static const Byte kArcProps[] = { kpidMethod, kpidNumStreams, - kpidNumBlocks + kpidNumBlocks, + kpidClusterSize, + kpidCharacts }; IMP_IInArchive_Props IMP_IInArchive_ArcProps -static inline char GetHex(unsigned value) -{ - return (char)((value < 10) ? ('0' + value) : ('A' + (value - 10))); -} - -static inline void AddHexToString(AString &s, Byte value) -{ - s += GetHex(value >> 4); - s += GetHex(value & 0xF); -} - -static void AddUInt32ToString(AString &s, UInt32 value) -{ - char temp[16]; - ConvertUInt32ToString(value, temp); - s += temp; -} - static void Lzma2PropToString(AString &s, unsigned prop) { char c = 0; @@ -192,9 +228,9 @@ static void Lzma2PropToString(AString &s, unsigned prop) c = 'm'; } } - AddUInt32ToString(s, size); + s.Add_UInt32(size); if (c != 0) - s += c; + s.Add_Char(c); } struct CMethodNamePair @@ -213,13 +249,15 @@ static const CMethodNamePair g_NamePairs[] = { XZ_ID_ARM, "ARM" }, { XZ_ID_ARMT, "ARMT" }, { XZ_ID_SPARC, "SPARC" }, + { XZ_ID_ARM64, "ARM64" }, + { XZ_ID_RISCV, "RISCV" }, { XZ_ID_LZMA2, "LZMA2" } }; -static AString GetMethodString(const CXzFilter &f) +static void AddMethodString(AString &s, const CXzFilter &f) { const char *p = NULL; - for (unsigned i = 0; i < ARRAY_SIZE(g_NamePairs); i++) + for (unsigned i = 0; i < Z7_ARRAY_SIZE(g_NamePairs); i++) if (g_NamePairs[i].Id == f.id) { p = g_NamePairs[i].Name; @@ -232,30 +270,29 @@ static AString GetMethodString(const CXzFilter &f) p = temp; } - AString s = p; + s += p; if (f.propsSize > 0) { - s += ':'; + s.Add_Colon(); if (f.id == XZ_ID_LZMA2 && f.propsSize == 1) Lzma2PropToString(s, f.props[0]); else if (f.id == XZ_ID_Delta && f.propsSize == 1) - AddUInt32ToString(s, (UInt32)f.props[0] + 1); + s.Add_UInt32((UInt32)f.props[0] + 1); + else if (f.id == XZ_ID_ARM64 && f.propsSize == 1) + s.Add_UInt32((UInt32)f.props[0] + 16 + 2); else { - s += '['; + s.Add_Char('['); for (UInt32 bi = 0; bi < f.propsSize; bi++) - AddHexToString(s, f.props[bi]); - s += ']'; + { + const unsigned v = f.props[bi]; + s.Add_Char(GET_HEX_CHAR_UPPER(v >> 4)); + s.Add_Char(GET_HEX_CHAR_UPPER(v & 15)); + } + s.Add_Char(']'); } } - return s; -} - -static void AddString(AString &dest, const AString &src) -{ - dest.Add_Space_if_NotEmpty(); - dest += src; } static const char * const kChecks[] = @@ -278,73 +315,101 @@ static const char * const kChecks[] = , NULL }; -static AString GetCheckString(const CXzs &xzs) +static void AddCheckString(AString &s, const CXzs &xzs) { size_t i; UInt32 mask = 0; for (i = 0; i < xzs.num; i++) mask |= ((UInt32)1 << XzFlags_GetCheckType(xzs.streams[i].flags)); - AString s; for (i = 0; i <= XZ_CHECK_MASK; i++) if (((mask >> i) & 1) != 0) { - AString s2; + s.Add_Space_if_NotEmpty(); if (kChecks[i]) - s2 = kChecks[i]; + s += kChecks[i]; else { - s2 = "Check-"; - AddUInt32ToString(s2, (UInt32)i); + s += "Check-"; + s.Add_UInt32((UInt32)i); } - AddString(s, s2); } - return s; } -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NCOM::CPropVariant prop; + + const CXzStatInfo *stat = GetStat(); + switch (propID) { - case kpidPhySize: if (_phySize_Defined) prop = _stat.PhySize; break; - case kpidNumStreams: if (_stat.NumStreams_Defined) prop = _stat.NumStreams; break; - case kpidNumBlocks: if (_stat.NumBlocks_Defined) prop = _stat.NumBlocks; break; - case kpidUnpackSize: if (_stat.UnpackSize_Defined) prop = _stat.OutSize; break; + case kpidPhySize: if (stat) prop = stat->InSize; break; + case kpidNumStreams: if (stat && stat->NumStreams_Defined) prop = stat->NumStreams; break; + case kpidNumBlocks: if (stat && stat->NumBlocks_Defined) prop = stat->NumBlocks; break; + case kpidUnpackSize: if (stat && stat->UnpackSize_Defined) prop = stat->OutSize; break; + case kpidClusterSize: if (_stat_defined && _stat.NumBlocks_Defined && stat->NumBlocks > 1) prop = _maxBlocksSize; break; + case kpidCharacts: + if (_firstBlockWasRead) + { + AString s; + if (XzBlock_HasPackSize(&_firstBlock)) + s.Add_OptSpaced("BlockPackSize"); + if (XzBlock_HasUnpackSize(&_firstBlock)) + s.Add_OptSpaced("BlockUnpackSize"); + if (!s.IsEmpty()) + prop = s; + } + break; + + case kpidMethod: if (!_methodsString.IsEmpty()) prop = _methodsString; break; case kpidErrorFlags: { UInt32 v = 0; - if (!_isArc) v |= kpv_ErrorFlags_IsNotArc;; - if (_stat.UnexpectedEnd) v |= kpv_ErrorFlags_UnexpectedEnd; - if (_stat.DataAfterEnd) v |= kpv_ErrorFlags_DataAfterEnd; - if (_stat.HeadersError) v |= kpv_ErrorFlags_HeadersError; - if (_stat.Unsupported) v |= kpv_ErrorFlags_UnsupportedMethod; - if (_stat.DataError) v |= kpv_ErrorFlags_DataError; - if (_stat.CrcError) v |= kpv_ErrorFlags_CrcError; - prop = v; + SRes sres = _stat2_decode_SRes; + if (!_isArc) v |= kpv_ErrorFlags_IsNotArc; + if (sres == SZ_ERROR_INPUT_EOF) v |= kpv_ErrorFlags_UnexpectedEnd; + if (_stat2_defined && _stat2.DataAfterEnd) v |= kpv_ErrorFlags_DataAfterEnd; + if (sres == SZ_ERROR_ARCHIVE) v |= kpv_ErrorFlags_HeadersError; + if (sres == SZ_ERROR_UNSUPPORTED) v |= kpv_ErrorFlags_UnsupportedMethod; + if (sres == SZ_ERROR_DATA) v |= kpv_ErrorFlags_DataError; + if (sres == SZ_ERROR_CRC) v |= kpv_ErrorFlags_CrcError; + if (v != 0) + prop = v; + break; } + + case kpidMainSubfile: + { + // debug only, comment it: + // if (_blocks) prop = (UInt32)0; + break; + } + default: break; } prop.Detach(value); return S_OK; COM_TRY_END } -STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) { *numItems = 1; return S_OK; } -STDMETHODIMP CHandler::GetProperty(UInt32, PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetProperty(UInt32, PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN + const CXzStatInfo *stat = GetStat(); NCOM::CPropVariant prop; switch (propID) { - case kpidSize: if (_stat.UnpackSize_Defined) prop = _stat.OutSize; break; - case kpidPackSize: if (_phySize_Defined) prop = _stat.PhySize; break; + case kpidSize: if (stat && stat->UnpackSize_Defined) prop = stat->OutSize; break; + case kpidPackSize: if (stat) prop = stat->InSize; break; case kpidMethod: if (!_methodsString.IsEmpty()) prop = _methodsString; break; + default: break; } prop.Detach(value); return S_OK; @@ -354,34 +419,61 @@ STDMETHODIMP CHandler::GetProperty(UInt32, PROPID propID, PROPVARIANT *value) struct COpenCallbackWrap { - ICompressProgress p; + ICompressProgress vt; IArchiveOpenCallback *OpenCallback; HRESULT Res; - COpenCallbackWrap(IArchiveOpenCallback *progress); + + // new clang shows "non-POD" warning for offsetof(), if we use constructor instead of Init() + void Init(IArchiveOpenCallback *progress); }; -static SRes OpenCallbackProgress(void *pp, UInt64 inSize, UInt64 /* outSize */) +static SRes OpenCallbackProgress(ICompressProgressPtr pp, UInt64 inSize, UInt64 /* outSize */) { - COpenCallbackWrap *p = (COpenCallbackWrap *)pp; + Z7_CONTAINER_FROM_VTBL_TO_DECL_VAR_pp_vt_p(COpenCallbackWrap) if (p->OpenCallback) p->Res = p->OpenCallback->SetCompleted(NULL, &inSize); - return (SRes)p->Res; + return HRESULT_To_SRes(p->Res, SZ_ERROR_PROGRESS); } -COpenCallbackWrap::COpenCallbackWrap(IArchiveOpenCallback *callback) +void COpenCallbackWrap::Init(IArchiveOpenCallback *callback) { - p.Progress = OpenCallbackProgress; + vt.Progress = OpenCallbackProgress; OpenCallback = callback; Res = SZ_OK; } + struct CXzsCPP { CXzs p; - CXzsCPP() { Xzs_Construct(&p); } + CXzsCPP() { Xzs_CONSTRUCT(&p) } ~CXzsCPP() { Xzs_Free(&p, &g_Alloc); } }; +#define kInputBufSize ((size_t)1 << 10) + +struct CLookToRead2_CPP: public CLookToRead2 +{ + CLookToRead2_CPP() + { + buf = NULL; + LookToRead2_CreateVTable(this, + True // Lookahead ? + ); + } + void Alloc(size_t allocSize) + { + buf = (Byte *)MyAlloc(allocSize); + if (buf) + this->bufSize = allocSize; + } + ~CLookToRead2_CPP() + { + MyFree(buf); + } +}; + + static HRESULT SRes_to_Open_HRESULT(SRes res) { switch (res) @@ -397,53 +489,98 @@ static HRESULT SRes_to_Open_HRESULT(SRes res) case SZ_ERROR_NO_ARCHIVE: return S_FALSE; */ + default: break; } return S_FALSE; } + + HRESULT CHandler::Open2(IInStream *inStream, /* UInt32 flags, */ IArchiveOpenCallback *callback) { _needSeekToStart = true; { CXzStreamFlags st; - CSeqInStreamWrap inStreamWrap(inStream); - SRes res = Xz_ReadHeader(&st, &inStreamWrap.p); + CSeqInStreamWrap inStreamWrap; + + inStreamWrap.Init(inStream); + + SRes res = Xz_ReadHeader(&st, &inStreamWrap.vt); + + if (inStreamWrap.Res != S_OK) + return inStreamWrap.Res; if (res != SZ_OK) return SRes_to_Open_HRESULT(res); { CXzBlock block; - Bool isIndex; + BoolInt isIndex; UInt32 headerSizeRes; - SRes res2 = XzBlock_ReadHeader(&block, &inStreamWrap.p, &isIndex, &headerSizeRes); - if (res2 == SZ_OK && !isIndex) + + SRes res2 = XzBlock_ReadHeader(&block, &inStreamWrap.vt, &isIndex, &headerSizeRes); + + if (inStreamWrap.Res != S_OK) + return inStreamWrap.Res; + + if (res2 != SZ_OK) { + if (res2 == SZ_ERROR_INPUT_EOF) + { + _stat2_decode_SRes = res2; + _stream = inStream; + _seqStream = inStream; + _isArc = true; + return S_OK; + } + + if (res2 == SZ_ERROR_ARCHIVE) + return S_FALSE; + // what codes are possible here ? + // ?? res2 == SZ_ERROR_MEM : is possible here + // ?? res2 == SZ_ERROR_UNSUPPORTED : is possible here + } + else if (!isIndex) + { + _firstBlockWasRead = true; + _firstBlock = block; + unsigned numFilters = XzBlock_GetNumFilters(&block); for (unsigned i = 0; i < numFilters; i++) - AddString(_methodsString, GetMethodString(block.filters[i])); + { + _methodsString.Add_Space_if_NotEmpty(); + AddMethodString(_methodsString, block.filters[i]); + } } } } - RINOK(inStream->Seek(0, STREAM_SEEK_END, &_stat.PhySize)); + RINOK(InStream_GetSize_SeekToEnd(inStream, _stat.InSize)) if (callback) { - RINOK(callback->SetTotal(NULL, &_stat.PhySize)); + RINOK(callback->SetTotal(NULL, &_stat.InSize)) } - CSeekInStreamWrap inStreamImp(inStream); + CSeekInStreamWrap inStreamImp; + + inStreamImp.Init(inStream); - CLookToRead lookStream; - LookToRead_CreateVTable(&lookStream, True); - lookStream.realStream = &inStreamImp.p; - LookToRead_Init(&lookStream); + CLookToRead2_CPP lookStream; - COpenCallbackWrap openWrap(callback); + lookStream.Alloc(kInputBufSize); + + if (!lookStream.buf) + return E_OUTOFMEMORY; + + lookStream.realStream = &inStreamImp.vt; + LookToRead2_INIT(&lookStream) + + COpenCallbackWrap openWrap; + openWrap.Init(callback); CXzsCPP xzs; Int64 startPosition; - SRes res = Xzs_ReadBackward(&xzs.p, &lookStream.s, &startPosition, &openWrap.p, &g_Alloc); + SRes res = Xzs_ReadBackward(&xzs.p, &lookStream.vt, &startPosition, &openWrap.vt, &g_Alloc); if (res == SZ_ERROR_PROGRESS) return (openWrap.Res == S_OK) ? E_FAIL : openWrap.Res; /* @@ -452,7 +589,7 @@ HRESULT CHandler::Open2(IInStream *inStream, /* UInt32 flags, */ IArchiveOpenCal */ if (res == SZ_OK && startPosition == 0) { - _phySize_Defined = true; + _stat_defined = true; _stat.OutSize = Xzs_GetUnpackSize(&xzs.p); _stat.UnpackSize_Defined = true; @@ -463,21 +600,81 @@ HRESULT CHandler::Open2(IInStream *inStream, /* UInt32 flags, */ IArchiveOpenCal _stat.NumBlocks = Xzs_GetNumBlocks(&xzs.p); _stat.NumBlocks_Defined = true; - AddString(_methodsString, GetCheckString(xzs.p)); + AddCheckString(_methodsString, xzs.p); + + const size_t numBlocks = (size_t)_stat.NumBlocks + 1; + const size_t bytesAlloc = numBlocks * sizeof(CBlockInfo); + + if (bytesAlloc / sizeof(CBlockInfo) == _stat.NumBlocks + 1) + { + _blocks = (CBlockInfo *)MyAlloc(bytesAlloc); + if (_blocks) + { + unsigned blockIndex = 0; + UInt64 unpackPos = 0; + + for (size_t si = xzs.p.num; si != 0;) + { + si--; + const CXzStream &str = xzs.p.streams[si]; + UInt64 packPos = str.startOffset + XZ_STREAM_HEADER_SIZE; + + for (size_t bi = 0; bi < str.numBlocks; bi++) + { + const CXzBlockSizes &bs = str.blocks[bi]; + const UInt64 packSizeAligned = bs.totalSize + ((0 - (unsigned)bs.totalSize) & 3); + + if (bs.unpackSize != 0) + { + if (blockIndex >= _stat.NumBlocks) + return E_FAIL; + + CBlockInfo &block = _blocks[blockIndex++]; + block.StreamFlags = str.flags; + block.PackSize = bs.totalSize; // packSizeAligned; + block.PackPos = packPos; + block.UnpackPos = unpackPos; + } + packPos += packSizeAligned; + unpackPos += bs.unpackSize; + if (_maxBlocksSize < bs.unpackSize) + _maxBlocksSize = bs.unpackSize; + } + } + + /* + if (blockIndex != _stat.NumBlocks) + { + // there are Empty blocks; + } + */ + if (_stat.OutSize != unpackPos) + return E_FAIL; + CBlockInfo &block = _blocks[blockIndex++]; + block.StreamFlags = 0; + block.PackSize = 0; + block.PackPos = 0; + block.UnpackPos = unpackPos; + _blocksArraySize = blockIndex; + } + } } else { res = SZ_OK; } - RINOK(SRes_to_Open_HRESULT(res)); + RINOK(SRes_to_Open_HRESULT(res)) + _stream = inStream; _seqStream = inStream; _isArc = true; return S_OK; } -STDMETHODIMP CHandler::Open(IInStream *inStream, const UInt64 *, IArchiveOpenCallback *callback) + + +Z7_COM7F_IMF(CHandler::Open(IInStream *inStream, const UInt64 *, IArchiveOpenCallback *callback)) { COM_TRY_BEGIN { @@ -487,7 +684,7 @@ STDMETHODIMP CHandler::Open(IInStream *inStream, const UInt64 *, IArchiveOpenCal COM_TRY_END } -STDMETHODIMP CHandler::OpenSeq(ISequentialInStream *stream) +Z7_COM7F_IMF(CHandler::OpenSeq(ISequentialInStream *stream)) { Close(); _seqStream = stream; @@ -496,206 +693,336 @@ STDMETHODIMP CHandler::OpenSeq(ISequentialInStream *stream) return S_OK; } -STDMETHODIMP CHandler::Close() +Z7_COM7F_IMF(CHandler::Close()) { - _stat.Clear(); + XzStatInfo_Clear(&_stat); + XzStatInfo_Clear(&_stat2); + _stat_defined = false; + _stat2_defined = false; + _stat2_decode_SRes = SZ_OK; _isArc = false; _needSeekToStart = false; + _firstBlockWasRead = false; - _phySize_Defined = false; - _methodsString.Empty(); _stream.Release(); _seqStream.Release(); + + MyFree(_blocks); + _blocks = NULL; + _blocksArraySize = 0; + _maxBlocksSize = 0; + return S_OK; } -class CSeekToSeqStream: - public IInStream, - public CMyUnknownImp -{ -public: - CMyComPtr Stream; - MY_UNKNOWN_IMP1(IInStream) - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); - STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition); +struct CXzUnpackerCPP2 +{ + Byte *InBuf; + // Byte *OutBuf; + CXzUnpacker p; + + CXzUnpackerCPP2(); + ~CXzUnpackerCPP2(); }; -STDMETHODIMP CSeekToSeqStream::Read(void *data, UInt32 size, UInt32 *processedSize) +CXzUnpackerCPP2::CXzUnpackerCPP2(): InBuf(NULL) + // , OutBuf(NULL) { - return Stream->Read(data, size, processedSize); + XzUnpacker_Construct(&p, &g_Alloc); } -STDMETHODIMP CSeekToSeqStream::Seek(Int64, UInt32, UInt64 *) { return E_NOTIMPL; } - -CXzUnpackerCPP::CXzUnpackerCPP(): InBuf(0), OutBuf(0) +CXzUnpackerCPP2::~CXzUnpackerCPP2() { - XzUnpacker_Construct(&p, &g_Alloc); + XzUnpacker_Free(&p); + MidFree(InBuf); + // MidFree(OutBuf); } -CXzUnpackerCPP::~CXzUnpackerCPP() + +Z7_CLASS_IMP_IInStream( + CInStream +) + + UInt64 _virtPos; +public: + UInt64 Size; + UInt64 _cacheStartPos; + size_t _cacheSize; + CByteBuffer _cache; + // UInt64 _startPos; + CXzUnpackerCPP2 xz; + + void InitAndSeek() + { + _virtPos = 0; + _cacheStartPos = 0; + _cacheSize = 0; + // _startPos = startPos; + } + + CMyComPtr2 _handlerSpec; + // ~CInStream(); +}; + +/* +CInStream::~CInStream() { - XzUnpacker_Free(&p); - MyFree(InBuf); - MyFree(OutBuf); + // _cache.Free(); } +*/ -HRESULT CDecoder::Decode(ISequentialInStream *seqInStream, ISequentialOutStream *outStream, ICompressProgressInfo *progress) +static size_t FindBlock(const CBlockInfo *blocks, size_t numBlocks, UInt64 pos) { - const size_t kInBufSize = 1 << 15; - const size_t kOutBufSize = 1 << 21; + size_t left = 0, right = numBlocks; + for (;;) + { + size_t mid = (left + right) / 2; + if (mid == left) + return left; + if (pos < blocks[mid].UnpackPos) + right = mid; + else + left = mid; + } +} + - Clear(); - DecodeRes = SZ_OK; + +static HRESULT DecodeBlock(CXzUnpackerCPP2 &xzu, + ISequentialInStream *seqInStream, + unsigned streamFlags, + UInt64 packSize, // pure size from Index record, it doesn't include pad zeros + size_t unpackSize, Byte *dest + // , ICompressProgressInfo *progress + ) +{ + const size_t kInBufSize = (size_t)1 << 16; XzUnpacker_Init(&xzu.p); + if (!xzu.InBuf) - xzu.InBuf = (Byte *)MyAlloc(kInBufSize); - if (!xzu.OutBuf) - xzu.OutBuf = (Byte *)MyAlloc(kOutBufSize); + { + xzu.InBuf = (Byte *)MidAlloc(kInBufSize); + if (!xzu.InBuf) + return E_OUTOFMEMORY; + } + xzu.p.streamFlags = (UInt16)streamFlags; + XzUnpacker_PrepareToRandomBlockDecoding(&xzu.p); + + XzUnpacker_SetOutBuf(&xzu.p, dest, unpackSize); + + const UInt64 packSizeAligned = packSize + ((0 - (unsigned)packSize) & 3); + UInt64 packRem = packSizeAligned; + UInt32 inSize = 0; SizeT inPos = 0; SizeT outPos = 0; + HRESULT readRes = S_OK; + for (;;) { - if (inPos == inSize) + if (inPos == inSize && readRes == S_OK) { - inPos = inSize = 0; - RINOK(seqInStream->Read(xzu.InBuf, kInBufSize, &inSize)); + inPos = 0; + inSize = 0; + UInt32 rem = kInBufSize; + if (rem > packRem) + rem = (UInt32)packRem; + if (rem != 0) + readRes = seqInStream->Read(xzu.InBuf, rem, &inSize); } SizeT inLen = inSize - inPos; - SizeT outLen = kOutBufSize - outPos; - ECoderStatus status; + SizeT outLen = unpackSize - outPos; - SRes res = XzUnpacker_Code(&xzu.p, - xzu.OutBuf + outPos, &outLen, + ECoderStatus status; + + const SRes res = XzUnpacker_Code(&xzu.p, + // dest + outPos, + NULL, + &outLen, xzu.InBuf + inPos, &inLen, - (inSize == 0 ? CODER_FINISH_END : CODER_FINISH_ANY), &status); + (inLen == 0), // srcFinished + CODER_FINISH_END, &status); - inPos += inLen; - outPos += outLen; + // return E_OUTOFMEMORY; + // res = SZ_ERROR_CRC; - InSize += inLen; - OutSize += outLen; + if (res != SZ_OK) + { + if (res == SZ_ERROR_CRC) + return S_FALSE; + return SResToHRESULT(res); + } - DecodeRes = res; + inPos += inLen; + outPos += outLen; - bool finished = ((inLen == 0 && outLen == 0) || res != SZ_OK); + packRem -= inLen; + + const BoolInt blockFinished = XzUnpacker_IsBlockFinished(&xzu.p); - if (outStream) + if ((inLen == 0 && outLen == 0) || blockFinished) { - if (outPos == kOutBufSize || finished) - { - if (outPos != 0) - { - RINOK(WriteStream(outStream, xzu.OutBuf, outPos)); - outPos = 0; - } - } + if (packRem != 0 || !blockFinished || unpackSize != outPos) + return S_FALSE; + if (XzUnpacker_GetPackSizeForIndex(&xzu.p) != packSize) + return S_FALSE; + return S_OK; } - else - outPos = 0; - - if (progress) + } +} + + +Z7_COM7F_IMF(CInStream::Read(void *data, UInt32 size, UInt32 *processedSize)) +{ + COM_TRY_BEGIN + + if (processedSize) + *processedSize = 0; + if (size == 0) + return S_OK; + + { + if (_virtPos >= Size) + return S_OK; // (Size == _virtPos) ? S_OK: E_FAIL; { - RINOK(progress->SetRatioInfo(&InSize, &OutSize)); + UInt64 rem = Size - _virtPos; + if (size > rem) + size = (UInt32)rem; } - - if (finished) - { - PhySize = InSize; - NumStreams = xzu.p.numStartedStreams; - if (NumStreams > 0) - IsArc = true; - NumBlocks = xzu.p.numTotalBlocks; + } - UnpackSize_Defined = true; - NumStreams_Defined = true; - NumBlocks_Defined = true; + if (size == 0) + return S_OK; - UInt64 extraSize = XzUnpacker_GetExtraSize(&xzu.p); + if (_virtPos < _cacheStartPos || _virtPos >= _cacheStartPos + _cacheSize) + { + const size_t bi = FindBlock(_handlerSpec->_blocks, _handlerSpec->_blocksArraySize, _virtPos); + const CBlockInfo &block = _handlerSpec->_blocks[bi]; + const UInt64 unpackSize = _handlerSpec->_blocks[bi + 1].UnpackPos - block.UnpackPos; + if (_cache.Size() < unpackSize) + return E_FAIL; - if (res == SZ_OK) - { - if (status == CODER_STATUS_NEEDS_MORE_INPUT) - { - extraSize = 0; - if (!XzUnpacker_IsStreamWasFinished(&xzu.p)) - { - // finished at padding bytes, but padding is not aligned for 4 - UnexpectedEnd = true; - res = SZ_ERROR_DATA; - } - } - else // status == CODER_STATUS_NOT_FINISHED - res = SZ_ERROR_DATA; - } - else if (res == SZ_ERROR_NO_ARCHIVE) - { - if (InSize == extraSize) - IsArc = false; - else - { - if (extraSize != 0 || inPos != inSize) - { - DataAfterEnd = true; - res = SZ_OK; - } - } - } + _cacheSize = 0; - DecodeRes = res; - PhySize -= extraSize; + RINOK(_handlerSpec->SeekToPackPos(block.PackPos)) + RINOK(DecodeBlock(xz, _handlerSpec->_seqStream, block.StreamFlags, block.PackSize, + (size_t)unpackSize, _cache)) + _cacheStartPos = block.UnpackPos; + _cacheSize = (size_t)unpackSize; + } - switch (res) - { - case SZ_OK: break; - case SZ_ERROR_NO_ARCHIVE: IsArc = false; break; - case SZ_ERROR_ARCHIVE: HeadersError = true; break; - case SZ_ERROR_UNSUPPORTED: Unsupported = true; break; - case SZ_ERROR_CRC: CrcError = true; break; - case SZ_ERROR_DATA: DataError = true; break; - default: DataError = true; break; - } + { + const size_t offset = (size_t)(_virtPos - _cacheStartPos); + const size_t rem = _cacheSize - offset; + if (size > rem) + size = (UInt32)rem; + memcpy(data, _cache.ConstData() + offset, size); + _virtPos += size; + if (processedSize) + *processedSize = size; + return S_OK; + } - break; - } + COM_TRY_END +} + + +Z7_COM7F_IMF(CInStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition)) +{ + switch (seekOrigin) + { + case STREAM_SEEK_SET: break; + case STREAM_SEEK_CUR: offset += _virtPos; break; + case STREAM_SEEK_END: offset += Size; break; + default: return STG_E_INVALIDFUNCTION; } + if (offset < 0) + return HRESULT_WIN32_ERROR_NEGATIVE_SEEK; + _virtPos = (UInt64)offset; + if (newPosition) + *newPosition = (UInt64)offset; + return S_OK; +} + + + +static const UInt64 kMaxBlockSize_for_GetStream = (UInt64)1 << 40; + +Z7_COM7F_IMF(CHandler::GetStream(UInt32 index, ISequentialInStream **stream)) +{ + COM_TRY_BEGIN + + *stream = NULL; + + if (index != 0) + return E_INVALIDARG; + if (!_stat.UnpackSize_Defined + || _maxBlocksSize == 0 // 18.02 + || _maxBlocksSize > kMaxBlockSize_for_GetStream + || _maxBlocksSize != (size_t)_maxBlocksSize) + return S_FALSE; + + size_t memSize; + if (!NSystem::GetRamSize(memSize)) + memSize = (size_t)sizeof(size_t) << 28; + { + if (_maxBlocksSize > memSize / 4) + return S_FALSE; + } + + CMyComPtr2 spec; + spec.Create_if_Empty(); + spec->_cache.Alloc((size_t)_maxBlocksSize); + spec->_handlerSpec.SetFromCls(this); + // spec->_handler = (IInArchive *)this; + spec->Size = _stat.OutSize; + spec->InitAndSeek(); + + *stream = spec.Detach(); return S_OK; + + COM_TRY_END } -Int32 CDecoder::Get_Extract_OperationResult() const + +static Int32 Get_Extract_OperationResult(const NCompress::NXz::CDecoder &decoder) { Int32 opRes; - if (!IsArc) + SRes sres = decoder.MainDecodeSRes; + if (sres == SZ_ERROR_NO_ARCHIVE) // (!IsArc) opRes = NExtract::NOperationResult::kIsNotArc; - else if (UnexpectedEnd) + else if (sres == SZ_ERROR_INPUT_EOF) // (UnexpectedEnd) opRes = NExtract::NOperationResult::kUnexpectedEnd; - else if (DataAfterEnd) + else if (decoder.Stat.DataAfterEnd) opRes = NExtract::NOperationResult::kDataAfterEnd; - else if (CrcError) + else if (sres == SZ_ERROR_CRC) // (CrcError) opRes = NExtract::NOperationResult::kCRCError; - else if (Unsupported) + else if (sres == SZ_ERROR_UNSUPPORTED) // (Unsupported) opRes = NExtract::NOperationResult::kUnsupportedMethod; - else if (HeadersError) + else if (sres == SZ_ERROR_ARCHIVE) // (HeadersError) opRes = NExtract::NOperationResult::kDataError; - else if (DataError) + else if (sres == SZ_ERROR_DATA) // (DataError) opRes = NExtract::NOperationResult::kDataError; - else if (DecodeRes != SZ_OK) + else if (sres != SZ_OK) opRes = NExtract::NOperationResult::kDataError; else opRes = NExtract::NOperationResult::kOK; return opRes; } -STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, - Int32 testMode, IArchiveExtractCallback *extractCallback) + + + +Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testMode, IArchiveExtractCallback *extractCallback)) { COM_TRY_BEGIN if (numItems == 0) @@ -703,80 +1030,105 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, if (numItems != (UInt32)(Int32)-1 && (numItems != 1 || indices[0] != 0)) return E_INVALIDARG; - if (_phySize_Defined) - extractCallback->SetTotal(_stat.PhySize); + const CXzStatInfo *stat = GetStat(); + + if (stat) + RINOK(extractCallback->SetTotal(stat->InSize)) UInt64 currentTotalPacked = 0; - RINOK(extractCallback->SetCompleted(¤tTotalPacked)); + RINOK(extractCallback->SetCompleted(¤tTotalPacked)) + Int32 opRes; + { CMyComPtr realOutStream; - Int32 askMode = testMode ? + const Int32 askMode = testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; - RINOK(extractCallback->GetStream(0, &realOutStream, askMode)); + RINOK(extractCallback->GetStream(0, &realOutStream, askMode)) if (!testMode && !realOutStream) return S_OK; - extractCallback->PrepareOperation(askMode); + RINOK(extractCallback->PrepareOperation(askMode)) - CLocalProgress *lps = new CLocalProgress; - CMyComPtr lpsRef = lps; + CMyComPtr2_Create lps; lps->Init(extractCallback, true); if (_needSeekToStart) { if (!_stream) return E_FAIL; - RINOK(_stream->Seek(0, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekToBegin(_stream)) } else _needSeekToStart = true; - CDecoder decoder; - RINOK(Decode2(_seqStream, realOutStream, decoder, lpsRef)); - Int32 opRes = decoder.Get_Extract_OperationResult(); - realOutStream.Release(); + NCompress::NXz::CDecoder decoder; + + const HRESULT hres = Decode(decoder, _seqStream, realOutStream, lps); + + if (!decoder.MainDecodeSRes_wasUsed) + return hres == S_OK ? E_FAIL : hres; + + opRes = Get_Extract_OperationResult(decoder); + if (opRes == NExtract::NOperationResult::kOK + && hres != S_OK) + opRes = NExtract::NOperationResult::kDataError; + + // realOutStream.Release(); + } return extractCallback->SetOperationResult(opRes); COM_TRY_END } -#ifndef EXTRACT_ONLY -STDMETHODIMP CHandler::GetFileTimeType(UInt32 *timeType) + +#ifndef Z7_EXTRACT_ONLY + +Z7_COM7F_IMF(CHandler::GetFileTimeType(UInt32 *timeType)) { - *timeType = NFileTimeType::kUnix; + *timeType = GET_FileTimeType_NotDefined_for_GetFileTimeType; + // *timeType = NFileTimeType::kUnix; return S_OK; } -STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numItems, - IArchiveUpdateCallback *updateCallback) + +Z7_COM7F_IMF(CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numItems, + IArchiveUpdateCallback *updateCallback)) { COM_TRY_BEGIN - CSeqOutStreamWrap seqOutStream(outStream); - if (numItems == 0) { - SRes res = Xz_EncodeEmpty(&seqOutStream.p); + CSeqOutStreamWrap seqOutStream; + seqOutStream.Init(outStream); + SRes res = Xz_EncodeEmpty(&seqOutStream.vt); return SResToHRESULT(res); } if (numItems != 1) return E_INVALIDARG; + { + Z7_DECL_CMyComPtr_QI_FROM( + IStreamSetRestriction, + setRestriction, outStream) + if (setRestriction) + RINOK(setRestriction->SetRestriction(0, 0)) + } + Int32 newData, newProps; UInt32 indexInArchive; if (!updateCallback) return E_FAIL; - RINOK(updateCallback->GetUpdateItemInfo(0, &newData, &newProps, &indexInArchive)); + RINOK(updateCallback->GetUpdateItemInfo(0, &newData, &newProps, &indexInArchive)) if (IntToBool(newProps)) { { NCOM::CPropVariant prop; - RINOK(updateCallback->GetProperty(0, kpidIsDir, &prop)); + RINOK(updateCallback->GetProperty(0, kpidIsDir, &prop)) if (prop.vt != VT_EMPTY) if (prop.vt != VT_BOOL || prop.boolVal != VARIANT_FALSE) return E_INVALIDARG; @@ -785,101 +1137,180 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numIt if (IntToBool(newData)) { - UInt64 size; + UInt64 dataSize; { NCOM::CPropVariant prop; - RINOK(updateCallback->GetProperty(0, kpidSize, &prop)); + RINOK(updateCallback->GetProperty(0, kpidSize, &prop)) if (prop.vt != VT_UI8) return E_INVALIDARG; - size = prop.uhVal.QuadPart; - RINOK(updateCallback->SetTotal(size)); + dataSize = prop.uhVal.QuadPart; } - CLzma2EncProps lzma2Props; - Lzma2EncProps_Init(&lzma2Props); + CMyComPtr2_Create encoder; - lzma2Props.lzmaProps.level = GetLevel(); + CXzProps &xzProps = encoder->xzProps; + CLzma2EncProps &lzma2Props = xzProps.lzma2Props; - CMyComPtr fileInStream; - RINOK(updateCallback->GetStream(0, &fileInStream)); - - CSeqInStreamWrap seqInStream(fileInStream); + lzma2Props.lzmaProps.level = GetLevel(); + xzProps.reduceSize = dataSize; + /* { - NCOM::CPropVariant prop = (UInt64)size; - RINOK(NCompress::NLzma2::SetLzma2Prop(NCoderPropID::kReduceSize, prop, lzma2Props)); + NCOM::CPropVariant prop = (UInt64)dataSize; + RINOK(encoder->SetCoderProp(NCoderPropID::kReduceSize, prop)) } + */ - FOR_VECTOR (i, _methods) + #ifndef Z7_ST + +#ifdef _WIN32 + // we don't use chunk multithreading inside lzma2 stream. + // so we don't set xzProps.lzma2Props.numThreadGroups. + if (_numThreadGroups > 1) + xzProps.numThreadGroups = _numThreadGroups; +#endif + + UInt32 numThreads = _numThreads; + + const UInt32 kNumThreads_Max = 1024; + if (numThreads > kNumThreads_Max) + numThreads = kNumThreads_Max; + + if (!_numThreads_WasForced + && _numThreads >= 1 + && _memUsage_WasSet) { - COneMethodInfo &m = _methods[i]; - SetGlobalLevelAndThreads(m - #ifndef _7ZIP_ST - , _numThreads - #endif - ); + COneMethodInfo oneMethodInfo; + if (!_methods.IsEmpty()) + oneMethodInfo = _methods[0]; + + SetGlobalLevelTo(oneMethodInfo); + + const bool numThreads_WasSpecifiedInMethod = (oneMethodInfo.Get_NumThreads() >= 0); + if (!numThreads_WasSpecifiedInMethod) { - FOR_VECTOR (j, m.Props) + // here we set the (NCoderPropID::kNumThreads) property in each method, only if there is no such property already + CMultiMethodProps::SetMethodThreadsTo_IfNotFinded(oneMethodInfo, numThreads); + } + + // printf("\n====== GetProcessGroupAffinity : \n"); + + UInt64 cs = _numSolidBytes; + if (cs != XZ_PROPS_BLOCK_SIZE_AUTO) + oneMethodInfo.AddProp_BlockSize2(cs); + cs = oneMethodInfo.Get_Xz_BlockSize(); + + if (cs != XZ_PROPS_BLOCK_SIZE_AUTO && + cs != XZ_PROPS_BLOCK_SIZE_SOLID) + { + const UInt32 lzmaThreads = oneMethodInfo.Get_Lzma_NumThreads(); + const UInt32 numBlockThreads_Original = numThreads / lzmaThreads; + + if (numBlockThreads_Original > 1) { - const CProp &prop = m.Props[j]; - RINOK(NCompress::NLzma2::SetLzma2Prop(prop.Id, prop.Value, lzma2Props)); + UInt32 numBlockThreads = numBlockThreads_Original; + { + const UInt64 lzmaMemUsage = oneMethodInfo.Get_Lzma_MemUsage(false); + for (; numBlockThreads > 1; numBlockThreads--) + { + UInt64 size = numBlockThreads * (lzmaMemUsage + cs); + UInt32 numPackChunks = numBlockThreads + (numBlockThreads / 8) + 1; + if (cs < ((UInt32)1 << 26)) numPackChunks++; + if (cs < ((UInt32)1 << 24)) numPackChunks++; + if (cs < ((UInt32)1 << 22)) numPackChunks++; + size += numPackChunks * cs; + // printf("\nnumBlockThreads = %d, size = %d\n", (unsigned)(numBlockThreads), (unsigned)(size >> 20)); + if (size <= _memUsage_Compress) + break; + } + } + if (numBlockThreads == 0) + numBlockThreads = 1; + if (numBlockThreads != numBlockThreads_Original) + numThreads = numBlockThreads * lzmaThreads; } } } + xzProps.numTotalThreads = (int)numThreads; - #ifndef _7ZIP_ST - lzma2Props.numTotalThreads = _numThreads; - #endif + #endif // Z7_ST - CLocalProgress *lps = new CLocalProgress; - CMyComPtr progress = lps; - lps->Init(updateCallback, true); - - CCompressProgressWrap progressWrap(progress); - CXzProps xzProps; - CXzFilterProps filter; - XzProps_Init(&xzProps); - XzFilterProps_Init(&filter); - xzProps.lzma2Props = &lzma2Props; - xzProps.filterProps = (_filterId != 0 ? &filter : NULL); - switch (_crcSize) + + xzProps.blockSize = _numSolidBytes; + if (_numSolidBytes == XZ_PROPS_BLOCK_SIZE_SOLID) { - case 0: xzProps.checkId = XZ_CHECK_NO; break; - case 4: xzProps.checkId = XZ_CHECK_CRC32; break; - case 8: xzProps.checkId = XZ_CHECK_CRC64; break; - case 32: xzProps.checkId = XZ_CHECK_SHA256; break; - default: return E_INVALIDARG; + xzProps.lzma2Props.blockSize = LZMA2_ENC_PROPS_BLOCK_SIZE_SOLID; } - filter.id = _filterId; - if (_filterId == XZ_ID_Delta) + + RINOK(encoder->SetCheckSize(_crcSize)) + { - bool deltaDefined = false; - FOR_VECTOR (j, _filterMethod.Props) + CXzFilterProps &filter = xzProps.filterProps; + + if (_filterId == XZ_ID_Delta) { - const CProp &prop = _filterMethod.Props[j]; - if (prop.Id == NCoderPropID::kDefaultProp && prop.Value.vt == VT_UI4) + bool deltaDefined = false; + FOR_VECTOR (j, _filterMethod.Props) { - UInt32 delta = (UInt32)prop.Value.ulVal; - if (delta < 1 || delta > 256) + const CProp &prop = _filterMethod.Props[j]; + if (prop.Id == NCoderPropID::kDefaultProp && prop.Value.vt == VT_UI4) + { + UInt32 delta = (UInt32)prop.Value.ulVal; + if (delta < 1 || delta > 256) + return E_INVALIDARG; + filter.delta = delta; + deltaDefined = true; + } + else return E_INVALIDARG; - filter.delta = delta; - deltaDefined = true; } + if (!deltaDefined) + return E_INVALIDARG; } - if (!deltaDefined) - return E_INVALIDARG; + filter.id = _filterId; } - SRes res = Xz_Encode(&seqOutStream.p, &seqInStream.p, &xzProps, &progressWrap.p); - if (res == SZ_OK) - return updateCallback->SetOperationResult(NArchive::NUpdate::NOperationResult::kOK); - return SResToHRESULT(res); + + FOR_VECTOR (i, _methods) + { + COneMethodInfo &m = _methods[i]; + + FOR_VECTOR (j, m.Props) + { + const CProp &prop = m.Props[j]; + RINOK(encoder->SetCoderProp(prop.Id, prop.Value)) + } + } + + { + CMyComPtr fileInStream; + RINOK(updateCallback->GetStream(0, &fileInStream)) + if (!fileInStream) + return S_FALSE; + { + CMyComPtr streamGetSize; + fileInStream.QueryInterface(IID_IStreamGetSize, &streamGetSize); + if (streamGetSize) + { + UInt64 size; + if (streamGetSize->GetSize(&size) == S_OK) + dataSize = size; + } + } + RINOK(updateCallback->SetTotal(dataSize)) + CMyComPtr2_Create lps; + lps->Init(updateCallback, true); + RINOK(encoder.Interface()->Code(fileInStream, outStream, NULL, NULL, lps)) + } + + return updateCallback->SetOperationResult(NArchive::NUpdate::NOperationResult::kOK); } if (indexInArchive != 0) return E_INVALIDARG; - CMyComPtr opCallback; - updateCallback->QueryInterface(IID_IArchiveUpdateCallbackFile, (void **)&opCallback); + Z7_DECL_CMyComPtr_QI_FROM( + IArchiveUpdateCallbackFile, + opCallback, updateCallback) if (opCallback) { RINOK(opCallback->ReportOperation(NEventIndexType::kInArcIndex, 0, NUpdateNotifyOp::kReplicate)) @@ -887,34 +1318,96 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numIt if (_stream) { - if (_phySize_Defined) - RINOK(updateCallback->SetTotal(_stat.PhySize)); - RINOK(_stream->Seek(0, STREAM_SEEK_SET, NULL)); + const CXzStatInfo *stat = GetStat(); + if (stat) + { + RINOK(updateCallback->SetTotal(stat->InSize)) + } + RINOK(InStream_SeekToBegin(_stream)) } - CLocalProgress *lps = new CLocalProgress; - CMyComPtr progress = lps; + CMyComPtr2_Create lps; lps->Init(updateCallback, true); - return NCompress::CopyStream(_stream, outStream, progress); + return NCompress::CopyStream(_stream, outStream, lps); COM_TRY_END } -STDMETHODIMP CHandler::SetProperties(const wchar_t * const *names, const PROPVARIANT *values, UInt32 numProps) +#endif + + +HRESULT CHandler::SetProperty(const wchar_t *nameSpec, const PROPVARIANT &value) +{ + UString name = nameSpec; + name.MakeLower_Ascii(); + if (name.IsEmpty()) + return E_INVALIDARG; + + #ifndef Z7_EXTRACT_ONLY + + if (name[0] == L's') + { + const wchar_t *s = name.Ptr(1); + if (*s == 0) + { + bool useStr = false; + bool isSolid; + switch (value.vt) + { + case VT_EMPTY: isSolid = true; break; + case VT_BOOL: isSolid = (value.boolVal != VARIANT_FALSE); break; + case VT_BSTR: + if (!StringToBool(value.bstrVal, isSolid)) + useStr = true; + break; + default: return E_INVALIDARG; + } + if (!useStr) + { + _numSolidBytes = (isSolid ? XZ_PROPS_BLOCK_SIZE_SOLID : XZ_PROPS_BLOCK_SIZE_AUTO); + return S_OK; + } + } + return ParseSizeString(s, value, + 0, // percentsBase + _numSolidBytes) ? S_OK: E_INVALIDARG; + } + + return CMultiMethodProps::SetProperty(name, value); + + #else + + { + HRESULT hres; + if (SetCommonProperty(name, value, hres)) + return hres; + } + + return E_INVALIDARG; + + #endif +} + + + +Z7_COM7F_IMF(CHandler::SetProperties(const wchar_t * const *names, const PROPVARIANT *values, UInt32 numProps)) { COM_TRY_BEGIN Init(); + for (UInt32 i = 0; i < numProps; i++) { - RINOK(SetProperty(names[i], values[i])); + RINOK(SetProperty(names[i], values[i])) } + #ifndef Z7_EXTRACT_ONLY + if (!_filterMethod.MethodName.IsEmpty()) { unsigned k; - for (k = 0; k < ARRAY_SIZE(g_NamePairs); k++) + for (k = 0; k < Z7_ARRAY_SIZE(g_NamePairs); k++) { const CMethodNamePair &pair = g_NamePairs[k]; if (StringsAreEqualNoCase_Ascii(_filterMethod.MethodName, pair.Name)) @@ -923,7 +1416,7 @@ STDMETHODIMP CHandler::SetProperties(const wchar_t * const *names, const PROPVAR break; } } - if (k == ARRAY_SIZE(g_NamePairs)) + if (k == Z7_ARRAY_SIZE(g_NamePairs)) return E_INVALIDARG; } @@ -935,22 +1428,25 @@ STDMETHODIMP CHandler::SetProperties(const wchar_t * const *names, const PROPVAR AString &methodName = _methods[0].MethodName; if (methodName.IsEmpty()) methodName = k_LZMA2_Name; - else if (!methodName.IsEqualTo_Ascii_NoCase(k_LZMA2_Name)) + else if ( + !methodName.IsEqualTo_Ascii_NoCase(k_LZMA2_Name) + && !methodName.IsEqualTo_Ascii_NoCase("xz")) return E_INVALIDARG; } + #endif + return S_OK; COM_TRY_END } -#endif REGISTER_ARC_IO( "xz", "xz txz", "* .tar", 0xC, - XZ_SIG, - 0, - NArcInfoFlags::kKeepName, - NULL) + XZ_SIG, 0 + , NArcInfoFlags::kKeepName + , 0 + , NULL) }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/XzHandler.h b/src/plugins/7zip/7za/cpp/7zip/Archive/XzHandler.h index 4a59e356..4d099548 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/XzHandler.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/XzHandler.h @@ -1,65 +1,11 @@ // XzHandler.h -#ifndef __XZ_HANDLER_H -#define __XZ_HANDLER_H - -#include "../../../C/Xz.h" - -#include "../ICoder.h" +#ifndef ZIP7_INC_XZ_HANDLER_H +#define ZIP7_INC_XZ_HANDLER_H namespace NArchive { namespace NXz { -struct CXzUnpackerCPP -{ - Byte *InBuf; - Byte *OutBuf; - CXzUnpacker p; - - CXzUnpackerCPP(); - ~CXzUnpackerCPP(); -}; - -struct CStatInfo -{ - UInt64 InSize; - UInt64 OutSize; - UInt64 PhySize; - - UInt64 NumStreams; - UInt64 NumBlocks; - - bool UnpackSize_Defined; - - bool NumStreams_Defined; - bool NumBlocks_Defined; - - bool IsArc; - bool UnexpectedEnd; - bool DataAfterEnd; - bool Unsupported; - bool HeadersError; - bool DataError; - bool CrcError; - - CStatInfo() { Clear(); } - - void Clear(); -}; - -struct CDecoder: public CStatInfo -{ - CXzUnpackerCPP xzu; - SRes DecodeRes; // it's not HRESULT - - CDecoder(): DecodeRes(SZ_OK) {} - - /* Decode() can return ERROR code only if there is progress or stream error. - Decode() returns S_OK in case of xz decoding error, but DecodeRes and CStatInfo contain error information */ - HRESULT Decode(ISequentialInStream *seqInStream, ISequentialOutStream *outStream, ICompressProgressInfo *compressProgress); - Int32 Get_Extract_OperationResult() const; -}; - }} #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/ZHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/ZHandler.cpp index 29934367..1caef345 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/ZHandler.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/ZHandler.cpp @@ -17,17 +17,12 @@ namespace NArchive { namespace NZ { -class CHandler: - public IInArchive, - public CMyUnknownImp -{ +Z7_CLASS_IMP_CHandler_IInArchive_0 + CMyComPtr _stream; UInt64 _packSize; // UInt64 _unpackSize; // bool _unpackSize_Defined; -public: - MY_UNKNOWN_IMP1(IInArchive) - INTERFACE_IInArchive(;) }; static const Byte kProps[] = @@ -38,13 +33,13 @@ static const Byte kProps[] = IMP_IInArchive_Props IMP_IInArchive_ArcProps_NO_Table -STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) { *numItems = 1; return S_OK; } -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { NWindows::NCOM::CPropVariant prop; switch (propID) @@ -55,7 +50,7 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) return S_OK; } -STDMETHODIMP CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIANT *value)) { NWindows::NCOM::CPropVariant prop; switch (propID) @@ -68,22 +63,21 @@ STDMETHODIMP CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIAN } /* -class CCompressProgressInfoImp: - public ICompressProgressInfo, - public CMyUnknownImp -{ +Z7_CLASS_IMP_COM_1( + CCompressProgressInfoImp + , ICompressProgressInfo +) CMyComPtr Callback; public: - MY_UNKNOWN_IMP1(ICompressProgressInfo) - STDMETHOD(SetRatioInfo)(const UInt64 *inSize, const UInt64 *outSize); void Init(IArchiveOpenCallback *callback) { Callback = callback; } }; -STDMETHODIMP CCompressProgressInfoImp::SetRatioInfo(const UInt64 *inSize, const UInt64 *outSize) +Z7_COM7F_IMF(CCompressProgressInfoImp::SetRatioInfo(const UInt64 *inSize, const UInt64 *outSize)) { + outSize = outSize; if (Callback) { - UInt64 files = 1; + const UInt64 files = 1; return Callback->SetCompleted(&files, inSize); } return S_OK; @@ -95,30 +89,30 @@ API_FUNC_static_IsArc IsArc_Z(const Byte *p, size_t size) if (size < 3) return k_IsArc_Res_NEED_MORE; if (size > NCompress::NZ::kRecommendedCheckSize) - size = NCompress::NZ::kRecommendedCheckSize; + size = NCompress::NZ::kRecommendedCheckSize; if (!NCompress::NZ::CheckStream(p, size)) return k_IsArc_Res_NO; return k_IsArc_Res_YES; } } -STDMETHODIMP CHandler::Open(IInStream *stream, +Z7_COM7F_IMF(CHandler::Open(IInStream *stream, const UInt64 * /* maxCheckStartPosition */, - IArchiveOpenCallback * /* openCallback */) + IArchiveOpenCallback * /* openCallback */)) { COM_TRY_BEGIN { - // RINOK(stream->Seek(0, STREAM_SEEK_CUR, &_streamStartPosition)); + // RINOK(InStream_GetPos(stream, _streamStartPosition)); Byte buffer[NCompress::NZ::kRecommendedCheckSize]; // Byte buffer[1500]; size_t size = NCompress::NZ::kRecommendedCheckSize; // size = 700; - RINOK(ReadStream(stream, buffer, &size)); + RINOK(ReadStream(stream, buffer, &size)) if (!NCompress::NZ::CheckStream(buffer, size)) return S_FALSE; UInt64 endPos; - RINOK(stream->Seek(0, STREAM_SEEK_END, &endPos)); + RINOK(InStream_GetSize_SeekToEnd(stream, endPos)) _packSize = endPos; /* @@ -142,7 +136,7 @@ STDMETHODIMP CHandler::Open(IInStream *stream, UInt64 files = 1; RINOK(openCallback->SetTotal(&files, &endPos)); } - RINOK(stream->Seek(_streamStartPosition + kSignatureSize, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekSet(stream, _streamStartPosition + kSignatureSize)) HRESULT res = decoder->Code(stream, outStream, NULL, NULL, openCallback ? compressProgress : NULL); if (res != S_OK) return S_FALSE; @@ -155,7 +149,7 @@ STDMETHODIMP CHandler::Open(IInStream *stream, COM_TRY_END } -STDMETHODIMP CHandler::Close() +Z7_COM7F_IMF(CHandler::Close()) { _packSize = 0; // _unpackSize_Defined = false; @@ -164,62 +158,57 @@ STDMETHODIMP CHandler::Close() } -STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, - Int32 testMode, IArchiveExtractCallback *extractCallback) +Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testMode, IArchiveExtractCallback *extractCallback)) { COM_TRY_BEGIN if (numItems == 0) return S_OK; if (numItems != (UInt32)(Int32)-1 && (numItems != 1 || indices[0] != 0)) return E_INVALIDARG; - - extractCallback->SetTotal(_packSize); - + RINOK(extractCallback->SetTotal(_packSize)) UInt64 currentTotalPacked = 0; + RINOK(extractCallback->SetCompleted(¤tTotalPacked)) - RINOK(extractCallback->SetCompleted(¤tTotalPacked)); - + int opRes; + { CMyComPtr realOutStream; - Int32 askMode = testMode ? + const Int32 askMode = testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; - RINOK(extractCallback->GetStream(0, &realOutStream, askMode)); + RINOK(extractCallback->GetStream(0, &realOutStream, askMode)) if (!testMode && !realOutStream) return S_OK; - extractCallback->PrepareOperation(askMode); + RINOK(extractCallback->PrepareOperation(askMode)) - CDummyOutStream *outStreamSpec = new CDummyOutStream; - CMyComPtr outStream(outStreamSpec); - outStreamSpec->SetStream(realOutStream); - outStreamSpec->Init(); - realOutStream.Release(); + CMyComPtr2_Create outStream; + outStream->SetStream(realOutStream); + outStream->Init(); + // realOutStream.Release(); - CLocalProgress *lps = new CLocalProgress; - CMyComPtr progress = lps; + CMyComPtr2_Create lps; lps->Init(extractCallback, true); - RINOK(_stream->Seek(0, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekToBegin(_stream)) - NCompress::NZ::CDecoder *decoderSpec = new NCompress::NZ::CDecoder; - CMyComPtr decoder = decoderSpec; - - int opRes; + NCompress::NZ::CDecoder decoder; { - HRESULT result = decoder->Code(_stream, outStream, NULL, NULL, progress); - if (result == S_FALSE) + const HRESULT hres = decoder.Code(_stream, outStream, lps); + if (hres == S_FALSE) opRes = NExtract::NOperationResult::kDataError; else { - RINOK(result); + RINOK(hres) opRes = NExtract::NOperationResult::kOK; } } // _unpackSize = outStreamSpec->GetSize(); // _unpackSize_Defined = true; - outStream.Release(); + // outStream.Release(); + } return extractCallback->SetOperationResult(opRes); COM_TRY_END } diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/StdAfx.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/StdAfx.h index 2854ff3e..035267cc 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/StdAfx.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/StdAfx.h @@ -1,8 +1,11 @@ // StdAfx.h -#ifndef __STDAFX_H -#define __STDAFX_H +#ifndef ZIP7_INC_STDAFX_H +#define ZIP7_INC_STDAFX_H +#if defined(_MSC_VER) && _MSC_VER >= 1800 +#pragma warning(disable : 4464) // relative include path contains '..' +#endif #include "../../../Common/Common.h" #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipAddCommon.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipAddCommon.cpp index 06fbe22f..43d68c73 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipAddCommon.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipAddCommon.cpp @@ -17,6 +17,7 @@ #include "../../Compress/LzmaEncoder.h" #include "../../Compress/PpmdZip.h" +#include "../../Compress/XzEncoder.h" #include "../Common/InStreamWithCRC.h" @@ -26,41 +27,31 @@ namespace NArchive { namespace NZip { -static const CMethodId kMethodId_ZipBase = 0x040100; -static const CMethodId kMethodId_BZip2 = 0x040202; +using namespace NFileHeader; -static const UInt32 kLzmaPropsSize = 5; -static const UInt32 kLzmaHeaderSize = 4 + kLzmaPropsSize; -class CLzmaEncoder: - public ICompressCoder, - public ICompressSetCoderProperties, - public CMyUnknownImp -{ - NCompress::NLzma::CEncoder *EncoderSpec; - CMyComPtr Encoder; - Byte Header[kLzmaHeaderSize]; -public: - STDMETHOD(Code)(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress); - STDMETHOD(SetCoderProperties)(const PROPID *propIDs, const PROPVARIANT *props, UInt32 numProps); +static const unsigned kLzmaPropsSize = 5; +static const unsigned kLzmaHeaderSize = 4 + kLzmaPropsSize; - MY_UNKNOWN_IMP1(ICompressSetCoderProperties) +Z7_CLASS_IMP_NOQIB_3( + CLzmaEncoder + , ICompressCoder + , ICompressSetCoderProperties + , ICompressSetCoderPropertiesOpt +) +public: + CMyComPtr2 Encoder; + Byte Header[kLzmaHeaderSize]; }; -STDMETHODIMP CLzmaEncoder::SetCoderProperties(const PROPID *propIDs, const PROPVARIANT *props, UInt32 numProps) +Z7_COM7F_IMF(CLzmaEncoder::SetCoderProperties(const PROPID *propIDs, const PROPVARIANT *props, UInt32 numProps)) { - if (!Encoder) - { - EncoderSpec = new NCompress::NLzma::CEncoder; - Encoder = EncoderSpec; - } - CBufPtrSeqOutStream *outStreamSpec = new CBufPtrSeqOutStream; - CMyComPtr outStream(outStreamSpec); - outStreamSpec->Init(Header + 4, kLzmaPropsSize); - RINOK(EncoderSpec->SetCoderProperties(propIDs, props, numProps)); - RINOK(EncoderSpec->WriteCoderProperties(outStream)); - if (outStreamSpec->GetPos() != kLzmaPropsSize) + Encoder.Create_if_Empty(); + CMyComPtr2_Create outStream; + outStream->Init(Header + 4, kLzmaPropsSize); + RINOK(Encoder->SetCoderProperties(propIDs, props, numProps)) + RINOK(Encoder->WriteCoderProperties(outStream)) + if (outStream->GetPos() != kLzmaPropsSize) return E_FAIL; Header[0] = MY_VER_MAJOR; Header[1] = MY_VER_MINOR; @@ -69,21 +60,29 @@ STDMETHODIMP CLzmaEncoder::SetCoderProperties(const PROPID *propIDs, const PROPV return S_OK; } -STDMETHODIMP CLzmaEncoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress) +Z7_COM7F_IMF(CLzmaEncoder::SetCoderPropertiesOpt(const PROPID *propIDs, const PROPVARIANT *props, UInt32 numProps)) { - RINOK(WriteStream(outStream, Header, kLzmaHeaderSize)); - return Encoder->Code(inStream, outStream, inSize, outSize, progress); + return Encoder->SetCoderPropertiesOpt(propIDs, props, numProps); } +Z7_COM7F_IMF(CLzmaEncoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, + const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress)) +{ + RINOK(WriteStream(outStream, Header, kLzmaHeaderSize)) + return Encoder.Interface()->Code(inStream, outStream, inSize, outSize, progress); +} -CAddCommon::CAddCommon(const CCompressionMethodMode &options): - _options(options), - _copyCoderSpec(NULL), - _cryptoStreamSpec(NULL), + +CAddCommon::CAddCommon(): + _isLzmaEos(false), _buf(NULL) {} +void CAddCommon::SetOptions(const CCompressionMethodMode &options) +{ + _options = options; +} + CAddCommon::~CAddCommon() { MidFree(_buf); @@ -104,7 +103,7 @@ HRESULT CAddCommon::CalcStreamCRC(ISequentialInStream *inStream, UInt32 &resultC for (;;) { UInt32 processed; - RINOK(inStream->Read(_buf, kBufSize, &processed)); + RINOK(inStream->Read(_buf, kBufSize, &processed)) if (processed == 0) { resultCRC = CRC_GET_DIGEST(crc); @@ -114,141 +113,233 @@ HRESULT CAddCommon::CalcStreamCRC(ISequentialInStream *inStream, UInt32 &resultC } } + +HRESULT CAddCommon::Set_Pre_CompressionResult(bool inSeqMode, bool outSeqMode, UInt64 unpackSize, + CCompressingResult &opRes) const +{ + // We use Zip64, if unPackSize size is larger than 0xF8000000 to support + // cases when compressed size can be about 3% larger than uncompressed size + + const UInt32 kUnpackZip64Limit = 0xF8000000; + + opRes.UnpackSize = unpackSize; + opRes.PackSize = (UInt64)1 << 60; // we use big value to force Zip64 mode. + + if (unpackSize < kUnpackZip64Limit) + opRes.PackSize = (UInt32)0xFFFFFFFF - 1; // it will not use Zip64 for that size + + if (opRes.PackSize < unpackSize) + opRes.PackSize = unpackSize; + + const Byte method = _options.MethodSequence[0]; + + if (method == NCompressionMethod::kStore && !_options.Password_Defined) + opRes.PackSize = unpackSize; + + opRes.CRC = 0; + + opRes.LzmaEos = false; + + opRes.ExtractVersion = NCompressionMethod::kExtractVersion_Default; + opRes.DescriptorMode = outSeqMode; + + if (_options.Password_Defined) + { + opRes.ExtractVersion = NCompressionMethod::kExtractVersion_ZipCrypto; + if (_options.IsAesMode) + opRes.ExtractVersion = NCompressionMethod::kExtractVersion_Aes; + else + { + if (inSeqMode) + opRes.DescriptorMode = true; + } + } + + opRes.Method = method; + Byte ver = 0; + + switch (method) + { + case NCompressionMethod::kStore: break; + case NCompressionMethod::kDeflate: ver = NCompressionMethod::kExtractVersion_Deflate; break; + case NCompressionMethod::kDeflate64: ver = NCompressionMethod::kExtractVersion_Deflate64; break; + case NCompressionMethod::kXz : ver = NCompressionMethod::kExtractVersion_Xz; break; + case NCompressionMethod::kPPMd : ver = NCompressionMethod::kExtractVersion_PPMd; break; + case NCompressionMethod::kBZip2: ver = NCompressionMethod::kExtractVersion_BZip2; break; + case NCompressionMethod::kLZMA : + { + ver = NCompressionMethod::kExtractVersion_LZMA; + const COneMethodInfo *oneMethodMain = &_options._methods[0]; + opRes.LzmaEos = oneMethodMain->Get_Lzma_Eos(); + break; + } + default: break; + } + if (opRes.ExtractVersion < ver) + opRes.ExtractVersion = ver; + + return S_OK; +} + + HRESULT CAddCommon::Compress( DECL_EXTERNAL_CODECS_LOC_VARS ISequentialInStream *inStream, IOutStream *outStream, - UInt32 /* fileTime */, + bool inSeqMode, bool outSeqMode, + UInt32 fileTime, + UInt64 expectedDataSize, bool expectedDataSize_IsConfirmed, ICompressProgressInfo *progress, CCompressingResult &opRes) { + // opRes.LzmaEos = false; + if (!inStream) { // We can create empty stream here. But it was already implemented in caller code in 9.33+ return E_INVALIDARG; } - // CSequentialInStreamWithCRC *inSecCrcStreamSpec = NULL; - CInStreamWithCRC *inCrcStreamSpec = NULL; - CMyComPtr inCrcStream; + CMyComPtr2_Create inCrcStream; + + CMyComPtr inStream2; + if (!inSeqMode) { - CMyComPtr inStream2; - inStream->QueryInterface(IID_IInStream, (void **)&inStream2); - - if (inStream2) + if (!inStream2) { - inCrcStreamSpec = new CInStreamWithCRC; - inCrcStream = inCrcStreamSpec; - inCrcStreamSpec->SetStream(inStream2); - inCrcStreamSpec->Init(); - } - else - { - // we don't support stdin, since stream from stdin can require 64-bit size header - return E_NOTIMPL; - /* - inSecCrcStreamSpec = new CSequentialInStreamWithCRC; - inCrcStream = inSecCrcStreamSpec; - inSecCrcStreamSpec->SetStream(inStream); - inSecCrcStreamSpec->Init(); - */ + // inSeqMode = true; + // inSeqMode must be correct before + return E_FAIL; } } + inCrcStream->SetStream(inStream); + inCrcStream->SetFullSize(expectedDataSize_IsConfirmed ? expectedDataSize : (UInt64)(Int64)-1); + // inCrcStream->Init(); + unsigned numTestMethods = _options.MethodSequence.Size(); + // numTestMethods != 0 + + bool descriptorMode = outSeqMode; - if (numTestMethods > 1 && !inCrcStreamSpec) - numTestMethods = 1; + // ZipCrypto without descriptor requires additional reading pass for + // inStream to calculate CRC for password check field. + // The descriptor allows to use ZipCrypto check field without CRC (InfoZip's modification). + + if (!outSeqMode) + if (inSeqMode && _options.Password_Defined && !_options.IsAesMode) + descriptorMode = true; + opRes.DescriptorMode = descriptorMode; + + if (numTestMethods > 1) + if (inSeqMode || outSeqMode || !inStream2) + numTestMethods = 1; UInt32 crc = 0; bool crc_IsCalculated = false; - Byte method = 0; CFilterCoder::C_OutStream_Releaser outStreamReleaser; - opRes.ExtractVersion = NFileHeader::NCompressionMethod::kExtractVersion_Default; - opRes.FileTimeWasUsed = false; + // opRes.ExtractVersion = NCompressionMethod::kExtractVersion_Default; for (unsigned i = 0; i < numTestMethods; i++) { - opRes.ExtractVersion = NFileHeader::NCompressionMethod::kExtractVersion_Default; - if (inCrcStreamSpec) - RINOK(inCrcStreamSpec->Seek(0, STREAM_SEEK_SET, NULL)); - RINOK(outStream->SetSize(0)); - RINOK(outStream->Seek(0, STREAM_SEEK_SET, NULL)); - - if (_options.PasswordIsDefined) - { - opRes.ExtractVersion = NFileHeader::NCompressionMethod::kExtractVersion_ZipCrypto; + inCrcStream->Init(); - if (!_cryptoStream) + if (i != 0) + { + // if (inStream2) { - _cryptoStreamSpec = new CFilterCoder(true); - _cryptoStream = _cryptoStreamSpec; + RINOK(InStream_SeekToBegin(inStream2)) } + RINOK(outStream->Seek(0, STREAM_SEEK_SET, NULL)) + RINOK(outStream->SetSize(0)) + } + + opRes.LzmaEos = false; + opRes.ExtractVersion = NCompressionMethod::kExtractVersion_Default; + + const Byte method = _options.MethodSequence[i]; + if (method == NCompressionMethod::kStore && descriptorMode) + { + // we still can create descriptor_mode archives with "Store" method, but they are not good for 100% + return E_NOTIMPL; + } + + bool needCode = true; + + if (_options.Password_Defined) + { + opRes.ExtractVersion = NCompressionMethod::kExtractVersion_ZipCrypto; + + if (!_cryptoStream.IsDefined()) + _cryptoStream.SetFromCls(new CFilterCoder(true)); if (_options.IsAesMode) { - opRes.ExtractVersion = NFileHeader::NCompressionMethod::kExtractVersion_Aes; - if (!_cryptoStreamSpec->Filter) + opRes.ExtractVersion = NCompressionMethod::kExtractVersion_Aes; + if (!_cryptoStream->Filter) { - _cryptoStreamSpec->Filter = _filterAesSpec = new NCrypto::NWzAes::CEncoder; + _cryptoStream->Filter = _filterAesSpec = new NCrypto::NWzAes::CEncoder; _filterAesSpec->SetKeyMode(_options.AesKeyMode); - RINOK(_filterAesSpec->CryptoSetPassword((const Byte *)(const char *)_options.Password, _options.Password.Len())); + RINOK(_filterAesSpec->CryptoSetPassword((const Byte *)(const char *)_options.Password, _options.Password.Len())) } - RINOK(_filterAesSpec->WriteHeader(outStream)); + RINOK(_filterAesSpec->WriteHeader(outStream)) } else { - if (!_cryptoStreamSpec->Filter) + if (!_cryptoStream->Filter) { - _cryptoStreamSpec->Filter = _filterSpec = new NCrypto::NZip::CEncoder; + _cryptoStream->Filter = _filterSpec = new NCrypto::NZip::CEncoder; _filterSpec->CryptoSetPassword((const Byte *)(const char *)_options.Password, _options.Password.Len()); } UInt32 check; - // if (inCrcStreamSpec) + if (descriptorMode) + { + // it's Info-ZIP modification for stream_mode descriptor_mode (bit 3 of the general purpose bit flag is set) + check = (fileTime & 0xFFFF); + } + else { if (!crc_IsCalculated) { - RINOK(CalcStreamCRC(inStream, crc)); + RINOK(CalcStreamCRC(inStream, crc)) crc_IsCalculated = true; - RINOK(inCrcStreamSpec->Seek(0, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekToBegin(inStream2)) + inCrcStream->Init(); } check = (crc >> 16); } - /* - else - { - opRes.FileTimeWasUsed = true; - check = (fileTime & 0xFFFF); - } - */ - RINOK(_filterSpec->WriteHeader_Check16(outStream, (UInt16)check)); + RINOK(_filterSpec->WriteHeader_Check16(outStream, (UInt16)check)) } - RINOK(_cryptoStreamSpec->SetOutStream(outStream)); - RINOK(_cryptoStreamSpec->InitEncoder()); - outStreamReleaser.FilterCoder = _cryptoStreamSpec; + if (method == NCompressionMethod::kStore) + { + needCode = false; + RINOK(_cryptoStream->Code(inCrcStream, outStream, NULL, NULL, progress)) + } + else + { + RINOK(_cryptoStream->SetOutStream(outStream)) + RINOK(_cryptoStream->InitEncoder()) + outStreamReleaser.FilterCoder = _cryptoStream.ClsPtr(); + } } - method = _options.MethodSequence[i]; - - switch (method) + if (needCode) { - case NFileHeader::NCompressionMethod::kStored: + switch (method) { - if (_copyCoderSpec == NULL) - { - _copyCoderSpec = new NCompress::CCopyCoder; - _copyCoder = _copyCoderSpec; - } + case NCompressionMethod::kStore: + { + _copyCoder.Create_if_Empty(); CMyComPtr outStreamNew; - if (_options.PasswordIsDefined) + if (_options.Password_Defined) outStreamNew = _cryptoStream; else outStreamNew = outStream; - RINOK(_copyCoder->Code(inCrcStream, outStreamNew, NULL, NULL, progress)); + RINOK(_copyCoder.Interface()->Code(inCrcStream, outStreamNew, NULL, NULL, progress)) break; } @@ -256,15 +347,22 @@ HRESULT CAddCommon::Compress( { if (!_compressEncoder) { - if (method == NFileHeader::NCompressionMethod::kLZMA) + CLzmaEncoder *_lzmaEncoder = NULL; + if (method == NCompressionMethod::kLZMA) { - _compressExtractVersion = NFileHeader::NCompressionMethod::kExtractVersion_LZMA; - CLzmaEncoder *_lzmaEncoder = new CLzmaEncoder(); + _compressExtractVersion = NCompressionMethod::kExtractVersion_LZMA; + _lzmaEncoder = new CLzmaEncoder(); _compressEncoder = _lzmaEncoder; } - else if (method == NFileHeader::NCompressionMethod::kPPMd) + else if (method == NCompressionMethod::kXz) + { + _compressExtractVersion = NCompressionMethod::kExtractVersion_Xz; + NCompress::NXz::CEncoder *encoder = new NCompress::NXz::CEncoder(); + _compressEncoder = encoder; + } + else if (method == NCompressionMethod::kPPMd) { - _compressExtractVersion = NFileHeader::NCompressionMethod::kExtractVersion_PPMd; + _compressExtractVersion = NCompressionMethod::kExtractVersion_PPMd; NCompress::NPpmdZip::CEncoder *encoder = new NCompress::NPpmdZip::CEncoder(); _compressEncoder = encoder; } @@ -273,28 +371,28 @@ HRESULT CAddCommon::Compress( CMethodId methodId; switch (method) { - case NFileHeader::NCompressionMethod::kBZip2: + case NCompressionMethod::kBZip2: methodId = kMethodId_BZip2; - _compressExtractVersion = NFileHeader::NCompressionMethod::kExtractVersion_BZip2; + _compressExtractVersion = NCompressionMethod::kExtractVersion_BZip2; break; default: - _compressExtractVersion = ((method == NFileHeader::NCompressionMethod::kDeflated64) ? - NFileHeader::NCompressionMethod::kExtractVersion_Deflate64 : - NFileHeader::NCompressionMethod::kExtractVersion_Deflate); + _compressExtractVersion = ((method == NCompressionMethod::kDeflate64) ? + NCompressionMethod::kExtractVersion_Deflate64 : + NCompressionMethod::kExtractVersion_Deflate); methodId = kMethodId_ZipBase + method; break; } - RINOK(CreateCoder( + RINOK(CreateCoder_Id( EXTERNAL_CODECS_LOC_VARS - methodId, true, _compressEncoder)); + methodId, true, _compressEncoder)) if (!_compressEncoder) return E_NOTIMPL; - if (method == NFileHeader::NCompressionMethod::kDeflated || - method == NFileHeader::NCompressionMethod::kDeflated64) + if (method == NCompressionMethod::kDeflate || + method == NCompressionMethod::kDeflate64) { } - else if (method == NFileHeader::NCompressionMethod::kBZip2) + else if (method == NCompressionMethod::kBZip2) { } } @@ -303,49 +401,74 @@ HRESULT CAddCommon::Compress( _compressEncoder.QueryInterface(IID_ICompressSetCoderProperties, &setCoderProps); if (setCoderProps) { - RINOK(_options.MethodInfo.SetCoderProps(setCoderProps, - _options._dataSizeReduceDefined ? &_options._dataSizeReduce : NULL)); + if (!_options._methods.IsEmpty()) + { + COneMethodInfo *oneMethodMain = &_options._methods[0]; + + RINOK(oneMethodMain->SetCoderProps(setCoderProps, + _options.DataSizeReduce_Defined ? &_options.DataSizeReduce : NULL)) + } } } + if (method == NCompressionMethod::kLZMA) + _isLzmaEos = _lzmaEncoder->Encoder->IsWriteEndMark(); } + + if (method == NCompressionMethod::kLZMA) + opRes.LzmaEos = _isLzmaEos; + CMyComPtr outStreamNew; - if (_options.PasswordIsDefined) + if (_options.Password_Defined) outStreamNew = _cryptoStream; else outStreamNew = outStream; if (_compressExtractVersion > opRes.ExtractVersion) opRes.ExtractVersion = _compressExtractVersion; - RINOK(_compressEncoder->Code(inCrcStream, outStreamNew, NULL, NULL, progress)); + + { + CMyComPtr optProps; + _compressEncoder->QueryInterface(IID_ICompressSetCoderPropertiesOpt, (void **)&optProps); + if (optProps) + { + const PROPID propID = NCoderPropID::kExpectedDataSize; + NWindows::NCOM::CPropVariant prop = (UInt64)expectedDataSize; + RINOK(optProps->SetCoderPropertiesOpt(&propID, &prop, 1)) + } + } + + try { + RINOK(_compressEncoder->Code(inCrcStream, outStreamNew, NULL, NULL, progress)) + } catch (...) { return E_FAIL; } break; } + } // switch end + + if (_options.Password_Defined) + { + RINOK(_cryptoStream->OutStreamFinish()) + } } - if (_options.PasswordIsDefined) + if (_options.Password_Defined) { - RINOK(_cryptoStreamSpec->OutStreamFinish()); - if (_options.IsAesMode) { - RINOK(_filterAesSpec->WriteFooter(outStream)); + RINOK(_filterAesSpec->WriteFooter(outStream)) } } - RINOK(outStream->Seek(0, STREAM_SEEK_CUR, &opRes.PackSize)); + RINOK(outStream->Seek(0, STREAM_SEEK_CUR, &opRes.PackSize)) - // if (inCrcStreamSpec) { - opRes.CRC = inCrcStreamSpec->GetCRC(); - opRes.UnpackSize = inCrcStreamSpec->GetSize(); + opRes.CRC = inCrcStream->GetCRC(); + opRes.UnpackSize = inCrcStream->GetSize(); + opRes.Method = method; } - /* - else - { - opRes.CRC = inSecCrcStreamSpec->GetCRC(); - opRes.UnpackSize = inSecCrcStreamSpec->GetSize(); - } - */ - if (_options.PasswordIsDefined) + if (!inCrcStream->WasFinished()) + return E_FAIL; + + if (_options.Password_Defined) { if (opRes.PackSize < opRes.UnpackSize + (_options.IsAesMode ? _filterAesSpec->GetAddPackSize() : NCrypto::NZip::kHeaderSize)) @@ -355,8 +478,6 @@ HRESULT CAddCommon::Compress( break; } - - opRes.Method = method; return S_OK; } diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipAddCommon.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipAddCommon.h index 1e0c3bfa..e72ec7bc 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipAddCommon.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipAddCommon.h @@ -1,7 +1,7 @@ // ZipAddCommon.h -#ifndef __ZIP_ADD_COMMON_H -#define __ZIP_ADD_COMMON_H +#ifndef ZIP7_INC_ZIP_ADD_COMMON_H +#define ZIP7_INC_ZIP_ADD_COMMON_H #include "../../ICoder.h" #include "../../IProgress.h" @@ -26,20 +26,26 @@ struct CCompressingResult UInt32 CRC; UInt16 Method; Byte ExtractVersion; - bool FileTimeWasUsed; + bool DescriptorMode; + bool LzmaEos; + + CCompressingResult() + { + // for GCC: + UnpackSize = 0; + } }; -class CAddCommon +class CAddCommon MY_UNCOPYABLE { CCompressionMethodMode _options; - NCompress::CCopyCoder *_copyCoderSpec; - CMyComPtr _copyCoder; + CMyComPtr2 _copyCoder; CMyComPtr _compressEncoder; Byte _compressExtractVersion; + bool _isLzmaEos; - CFilterCoder *_cryptoStreamSpec; - CMyComPtr _cryptoStream; + CMyComPtr2 _cryptoStream; NCrypto::NZip::CEncoder *_filterSpec; NCrypto::NWzAes::CEncoder *_filterAesSpec; @@ -48,13 +54,21 @@ class CAddCommon HRESULT CalcStreamCRC(ISequentialInStream *inStream, UInt32 &resultCRC); public: - CAddCommon(const CCompressionMethodMode &options); + // CAddCommon(const CCompressionMethodMode &options); + CAddCommon(); + void SetOptions(const CCompressionMethodMode &options); ~CAddCommon(); + + HRESULT Set_Pre_CompressionResult(bool inSeqMode, bool outSeqMode, UInt64 unpackSize, + CCompressingResult &opRes) const; + HRESULT Compress( DECL_EXTERNAL_CODECS_LOC_VARS ISequentialInStream *inStream, IOutStream *outStream, + bool inSeqMode, bool outSeqMode, UInt32 fileTime, - ICompressProgressInfo *progress, CCompressingResult &operationResult); + UInt64 expectedDataSize, bool expectedDataSize_IsConfirmed, + ICompressProgressInfo *progress, CCompressingResult &opRes); }; }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipCompressionMode.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipCompressionMode.h index 86548d95..89742615 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipCompressionMode.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipCompressionMode.h @@ -1,11 +1,11 @@ // CompressionMode.h -#ifndef __ZIP_COMPRESSION_MODE_H -#define __ZIP_COMPRESSION_MODE_H +#ifndef ZIP7_INC_ZIP_COMPRESSION_MODE_H +#define ZIP7_INC_ZIP_COMPRESSION_MODE_H #include "../../../Common/MyString.h" -#ifndef _7ZIP_ST +#ifndef Z7_ST #include "../../../Windows/System.h" #endif @@ -14,26 +14,18 @@ namespace NArchive { namespace NZip { -struct CBaseProps -{ - CMethodProps MethodInfo; - Int32 Level; +const CMethodId kMethodId_ZipBase = 0x040100; +const CMethodId kMethodId_BZip2 = 0x040202; - #ifndef _7ZIP_ST - UInt32 NumThreads; - bool NumThreadsWasChanged; - #endif +struct CBaseProps: public CMultiMethodProps +{ bool IsAesMode; Byte AesKeyMode; void Init() { - MethodInfo.Clear(); - Level = -1; - #ifndef _7ZIP_ST - NumThreads = NWindows::NSystem::GetNumberOfProcessors();; - NumThreadsWasChanged = false; - #endif + CMultiMethodProps::Init(); + IsAesMode = false; AesKeyMode = 3; } @@ -42,19 +34,27 @@ struct CBaseProps struct CCompressionMethodMode: public CBaseProps { CRecordVector MethodSequence; - bool PasswordIsDefined; - AString Password; + AString Password; // _Wipe + bool Password_Defined; + bool Force_SeqOutMode; + bool DataSizeReduce_Defined; + UInt64 DataSizeReduce; - UInt64 _dataSizeReduce; - bool _dataSizeReduceDefined; - - bool IsRealAesMode() const { return PasswordIsDefined && IsAesMode; } + bool IsRealAesMode() const { return Password_Defined && IsAesMode; } - CCompressionMethodMode(): PasswordIsDefined(false) + CCompressionMethodMode() { - _dataSizeReduceDefined = false; - _dataSizeReduce = 0; + Password_Defined = false; + Force_SeqOutMode = false; + DataSizeReduce_Defined = false; + DataSizeReduce = 0; } + +#ifdef Z7_CPP_IS_SUPPORTED_default + CCompressionMethodMode(const CCompressionMethodMode &) = default; + CCompressionMethodMode& operator =(const CCompressionMethodMode &) = default; +#endif + ~CCompressionMethodMode() { Password.Wipe_and_Empty(); } }; }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipHandler.cpp index 75034de0..c6b6e394 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipHandler.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipHandler.cpp @@ -3,10 +3,10 @@ #include "StdAfx.h" #include "../../../Common/ComTry.h" -#include "../../../Common/IntToString.h" #include "../../../Common/StringConvert.h" #include "../../../Windows/PropVariant.h" +#include "../../../Windows/PropVariantUtils.h" #include "../../../Windows/TimeUtils.h" #include "../../IPassword.h" @@ -18,10 +18,15 @@ #include "../../Common/StreamUtils.h" #include "../../Compress/CopyCoder.h" +#ifndef Z7_ZIP_LZFSE_DISABLE +#include "../../Compress/LzfseDecoder.h" +#endif #include "../../Compress/LzmaDecoder.h" #include "../../Compress/ImplodeDecoder.h" #include "../../Compress/PpmdZip.h" #include "../../Compress/ShrinkDecoder.h" +#include "../../Compress/XzDecoder.h" +#include "../../Compress/ZstdDecoder.h" #include "../../Crypto/WzAes.h" #include "../../Crypto/ZipCrypto.h" @@ -30,7 +35,6 @@ #include "../Common/ItemNameUtils.h" #include "../Common/OutStreamWithCRC.h" -#include "../XzHandler.h" #include "ZipHandler.h" @@ -39,9 +43,6 @@ using namespace NWindows; namespace NArchive { namespace NZip { -static const CMethodId kMethodId_ZipBase = 0x040100; -static const CMethodId kMethodId_BZip2 = 0x040202; - static const char * const kHostOS[] = { "FAT" @@ -66,24 +67,69 @@ static const char * const kHostOS[] = , "OS/X" }; -static const char * const kMethods[] = + +const char * const kMethodNames1[kNumMethodNames1] = { "Store" , "Shrink" - , "Reduced1" - , "Reduced2" - , "Reduced3" - , "Reduced4" + , "Reduce1" + , "Reduce2" + , "Reduce3" + , "Reduce4" , "Implode" - , "Tokenizing" + , NULL // "Tokenize" , "Deflate" , "Deflate64" , "PKImploding" + , NULL + , "BZip2" + , NULL + , "LZMA" + /* + , NULL + , NULL + , NULL + , NULL + , NULL + , "zstd-pk" // deprecated + */ +}; + + +const char * const kMethodNames2[kNumMethodNames2] = +{ + "zstd" + , "MP3" + , "xz" + , "Jpeg" + , "WavPack" + , "PPMd" + , "LZFSE" // , "WzAES" }; -static const char *kMethod_AES = "AES"; -static const char *kMethod_ZipCrypto = "ZipCrypto"; -static const char *kMethod_StrongCrypto = "StrongCrypto"; +#define kMethod_AES "AES" +#define kMethod_ZipCrypto "ZipCrypto" +#define kMethod_StrongCrypto "StrongCrypto" + +static const char * const kDeflateLevels[4] = +{ + "Normal" + , "Maximum" + , "Fast" + , "Fastest" +}; + + +static const CUInt32PCharPair g_HeaderCharacts[] = +{ + { 0, "Encrypt" }, + { 3, "Descriptor" }, + // { 4, "Enhanced" }, + // { 5, "Patched" }, + { 6, kMethod_StrongCrypto }, + { 11, "UTF8" }, + { 14, "Alt" } +}; struct CIdToNamePair { @@ -91,15 +137,6 @@ struct CIdToNamePair const char *Name; }; -static const CIdToNamePair k_MethodIdNamePairs[] = -{ - { NFileHeader::NCompressionMethod::kBZip2, "BZip2" }, - { NFileHeader::NCompressionMethod::kLZMA, "LZMA" }, - { NFileHeader::NCompressionMethod::kXz, "xz" }, - { NFileHeader::NCompressionMethod::kJpeg, "Jpeg" }, - { NFileHeader::NCompressionMethod::kWavPack, "WavPack" }, - { NFileHeader::NCompressionMethod::kPPMd, "PPMd" } -}; static const CIdToNamePair k_StrongCryptoPairs[] = { @@ -116,7 +153,7 @@ static const CIdToNamePair k_StrongCryptoPairs[] = { NStrongCrypto_AlgId::kRC4, "RC4" } }; -const char *FindNameForId(const CIdToNamePair *pairs, unsigned num, unsigned id) +static const char *FindNameForId(const CIdToNamePair *pairs, unsigned num, unsigned id) { for (unsigned i = 0; i < num; i++) { @@ -127,6 +164,7 @@ const char *FindNameForId(const CIdToNamePair *pairs, unsigned num, unsigned id) return NULL; } + static const Byte kProps[] = { kpidPath, @@ -142,9 +180,14 @@ static const Byte kProps[] = kpidComment, kpidCRC, kpidMethod, + kpidCharacts, kpidHostOS, kpidUnpackVer, - kpidVolumeIndex + kpidVolumeIndex, + kpidOffset + // kpidIsAltStream + // , kpidChangeTime // for debug + // , 255 // for debug }; static const Byte kArcProps[] = @@ -152,6 +195,7 @@ static const Byte kArcProps[] = kpidEmbeddedStubSize, kpidBit64, kpidComment, + kpidCharacts, kpidTotalPhySize, kpidIsVolume, kpidVolumeIndex, @@ -173,7 +217,7 @@ static AString BytesToString(const CByteBuffer &data) IMP_IInArchive_Props IMP_IInArchive_ArcProps -STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NWindows::NCOM::CPropVariant prop; @@ -193,11 +237,40 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) break; } - case kpidTotalPhySize: if (m_Archive.IsMultiVol) prop = m_Archive.Vols.GetTotalSize(); break; + case kpidTotalPhySize: if (m_Archive.IsMultiVol) prop = m_Archive.Vols.TotalBytesSize; break; case kpidVolumeIndex: if (m_Archive.IsMultiVol) prop = (UInt32)m_Archive.Vols.StartVolIndex; break; case kpidIsVolume: if (m_Archive.IsMultiVol) prop = true; break; case kpidNumVolumes: if (m_Archive.IsMultiVol) prop = (UInt32)m_Archive.Vols.Streams.Size(); break; + case kpidCharacts: + { + AString s; + + if (m_Archive.LocalsWereRead) + { + s.Add_OptSpaced("Local"); + + if (m_Archive.LocalsCenterMerged) + s.Add_OptSpaced("Central"); + } + + if (m_Archive.IsZip64) + s.Add_OptSpaced("Zip64"); + + if (m_Archive.IsCdUnsorted) + s.Add_OptSpaced("Unsorted_CD"); + + if (m_Archive.IsApk) + s.Add_OptSpaced("apk"); + + if (m_Archive.ExtraMinorError) + s.Add_OptSpaced("Minor_Extra_ERROR"); + + if (!s.IsEmpty()) + prop = s; + break; + } + case kpidWarningFlags: { UInt32 v = 0; @@ -208,12 +281,23 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) break; } + case kpidWarning: + { + AString s; + if (m_Archive.Overflow32bit) + s.Add_OptSpaced("32-bit overflow in headers"); + if (m_Archive.Cd_NumEntries_Overflow_16bit) + s.Add_OptSpaced("16-bit overflow for number of files in headers"); + if (!s.IsEmpty()) + prop = s; + break; + } + case kpidError: { if (!m_Archive.Vols.MissingName.IsEmpty()) { - UString s; - s.SetFromAscii("Missing volume : "); + UString s("Missing volume : "); s += m_Archive.Vols.MissingName; prop = s; } @@ -248,19 +332,49 @@ STDMETHODIMP CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value) prop = true; break; } + + // case kpidIsAltStream: prop = true; break; + default: break; } - prop.Detach(value); + return prop.Detach(value); COM_TRY_END - return S_OK; } -STDMETHODIMP CHandler::GetNumberOfItems(UInt32 *numItems) +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) { *numItems = m_Items.Size(); return S_OK; } -STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) + +static bool NtfsUnixTimeToProp(bool fromCentral, + const CExtraBlock &extra, + unsigned ntfsIndex, unsigned unixIndex, NWindows::NCOM::CPropVariant &prop) +{ + { + FILETIME ft; + if (extra.GetNtfsTime(ntfsIndex, ft)) + { + PropVariant_SetFrom_NtfsTime(prop, ft); + return true; + } + } + { + UInt32 unixTime = 0; + if (!extra.GetUnixTime(fromCentral, unixIndex, unixTime)) + return false; + /* + // we allow unixTime == 0 + if (unixTime == 0) + return false; + */ + PropVariant_SetFrom_UnixTime(prop, unixTime); + return true; + } +} + + +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) { COM_TRY_BEGIN NWindows::NCOM::CPropVariant prop; @@ -273,15 +387,62 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val { UString res; item.GetUnicodeString(res, item.Name, false, _forceCodePage, _specifiedCodePage); - NItemName::ConvertToOSName2(res); + NItemName::ReplaceToOsSlashes_Remove_TailSlash(res, + item.Is_MadeBy_Unix() // useBackslashReplacement + ); + /* + if (item.ParentOfAltStream >= 0) + { + const CItemEx &prevItem = m_Items[item.ParentOfAltStream]; + UString prevName; + prevItem.GetUnicodeString(prevName, prevItem.Name, false, _forceCodePage, _specifiedCodePage); + NItemName::ReplaceToOsSlashes_Remove_TailSlash(prevName); + if (res.IsPrefixedBy(prevName)) + if (IsString1PrefixedByString2(res.Ptr(prevName.Len()), k_SpecName_NTFS_STREAM)) + { + res.Delete(prevName.Len(), (unsigned)strlen(k_SpecName_NTFS_STREAM)); + res.Insert(prevName.Len(), L":"); + } + } + */ prop = res; break; } case kpidIsDir: prop = item.IsDir(); break; - case kpidSize: prop = item.Size; break; + case kpidSize: + { + if (!item.IsBadDescriptor()) + prop = item.Size; + break; + } + case kpidPackSize: prop = item.PackSize; break; + case kpidCTime: + NtfsUnixTimeToProp(item.FromCentral, extra, + NFileHeader::NNtfsExtra::kCTime, + NFileHeader::NUnixTime::kCTime, prop); + break; + + case kpidATime: + NtfsUnixTimeToProp(item.FromCentral, extra, + NFileHeader::NNtfsExtra::kATime, + NFileHeader::NUnixTime::kATime, prop); + break; + + case kpidMTime: + { + if (!NtfsUnixTimeToProp(item.FromCentral, extra, + NFileHeader::NNtfsExtra::kMTime, + NFileHeader::NUnixTime::kMTime, prop)) + { + if (item.Time != 0) + PropVariant_SetFrom_DosTime(prop, item.Time); + } + break; + } + case kpidTimeType: { FILETIME ft; @@ -289,7 +450,7 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val UInt32 type; if (extra.GetNtfsTime(NFileHeader::NNtfsExtra::kMTime, ft)) type = NFileTimeType::kWindows; - else if (extra.GetUnixTime(true, NFileHeader::NUnixTime::kMTime, unixTime)) + else if (extra.GetUnixTime(item.FromCentral, NFileHeader::NUnixTime::kMTime, unixTime)) type = NFileTimeType::kUnix; else type = NFileTimeType::kDOS; @@ -297,45 +458,28 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val break; } - case kpidCTime: - { - FILETIME ft; - if (extra.GetNtfsTime(NFileHeader::NNtfsExtra::kCTime, ft)) - prop = ft; - break; - } - - case kpidATime: - { - FILETIME ft; - if (extra.GetNtfsTime(NFileHeader::NNtfsExtra::kATime, ft)) - prop = ft; - break; - } - - case kpidMTime: + /* + // for debug to get Dos time values: + case kpidChangeTime: if (item.Time != 0) PropVariant_SetFrom_DosTime(prop, item.Time); break; + // for debug + // time difference (dos - utc) + case 255: { - FILETIME utc; - bool defined = true; - if (!extra.GetNtfsTime(NFileHeader::NNtfsExtra::kMTime, utc)) + if (NtfsUnixTimeToProp(item.FromCentral, extra, + NFileHeader::NNtfsExtra::kMTime, + NFileHeader::NUnixTime::kMTime, prop)) { - UInt32 unixTime = 0; - if (extra.GetUnixTime(true, NFileHeader::NUnixTime::kMTime, unixTime)) - NTime::UnixTimeToFileTime(unixTime, utc); - else + FILETIME localFileTime; + if (item.Time != 0 && NTime::DosTime_To_FileTime(item.Time, localFileTime)) { - FILETIME localFileTime; - if (item.Time == 0) - defined = false; - else if (!NTime::DosTimeToFileTime(item.Time, localFileTime) || - !LocalFileTimeToFileTime(&localFileTime, &utc)) - utc.dwHighDateTime = utc.dwLowDateTime = 0; + UInt64 t1 = FILETIME_To_UInt64(prop.filetime); + UInt64 t2 = FILETIME_To_UInt64(localFileTime); + prop.Set_Int64(t2 - t1); } } - if (defined) - prop = utc; break; } + */ case kpidAttrib: prop = item.GetWinAttrib(); break; @@ -364,40 +508,40 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val case kpidMethod: { - unsigned id = item.Method; AString m; - - if (item.IsEncrypted()) + bool isWzAes = false; + unsigned id = item.Method; + + if (id == NFileHeader::NCompressionMethod::kWzAES) { - if (id == NFileHeader::NCompressionMethod::kWzAES) + CWzAesExtra aesField; + if (extra.GetWzAes(aesField)) { m += kMethod_AES; - CWzAesExtra aesField; - if (extra.GetWzAes(aesField)) - { - char s[16]; - s[0] = '-'; - ConvertUInt32ToString(((unsigned)aesField.Strength + 1) * 64 , s + 1); - m += s; - id = aesField.Method; - } + m.Add_Minus(); + m.Add_UInt32(((unsigned)aesField.Strength + 1) * 64); + id = aesField.Method; + isWzAes = true; } - else if (item.IsStrongEncrypted()) + } + + if (item.IsEncrypted()) + if (!isWzAes) + { + if (item.IsStrongEncrypted()) { CStrongCryptoExtra f; f.AlgId = 0; if (extra.GetStrongCrypto(f)) { - const char *s = FindNameForId(k_StrongCryptoPairs, ARRAY_SIZE(k_StrongCryptoPairs), f.AlgId); + const char *s = FindNameForId(k_StrongCryptoPairs, Z7_ARRAY_SIZE(k_StrongCryptoPairs), f.AlgId); if (s) m += s; else { m += kMethod_StrongCrypto; - char temp[16]; - temp[0] = ':'; - ConvertUInt32ToString(f.AlgId, temp + 1); - m += temp; + m.Add_Colon(); + m.Add_UInt32(f.AlgId); } if (f.CertificateIsUsed()) m += "-Cert"; @@ -407,45 +551,106 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val } else m += kMethod_ZipCrypto; - m += ' '; } + + m.Add_Space_if_NotEmpty(); { - char temp[16]; const char *s = NULL; - if (id < ARRAY_SIZE(kMethods)) - s = kMethods[id]; + if (id < kNumMethodNames1) + s = kMethodNames1[id]; else { - s = FindNameForId(k_MethodIdNamePairs, ARRAY_SIZE(k_MethodIdNamePairs), id); - if (!s) + const int id2 = (int)id - (int)kMethodNames2Start; + if (id2 >= 0 && (unsigned)id2 < kNumMethodNames2) + s = kMethodNames2[id2]; + } + if (s) + m += s; + else + m.Add_UInt32(id); + } + { + unsigned level = item.GetDeflateLevel(); + if (level != 0) + { + if (id == NFileHeader::NCompressionMethod::kLZMA) { - ConvertUInt32ToString(id, temp); - s = temp; + if (level & 1) + m += ":eos"; + level &= ~(unsigned)1; + } + else if (id == NFileHeader::NCompressionMethod::kDeflate) + { + m.Add_Colon(); + m += kDeflateLevels[level]; + level = 0; + } + + if (level != 0) + { + m += ":v"; + m.Add_UInt32(level); } } - m += s; - if (id == NFileHeader::NCompressionMethod::kLZMA && item.IsLzmaEOS()) - m += ":EOS"; } prop = m; break; } + case kpidCharacts: + { + AString s; + + if (item.FromLocal) + { + s.Add_OptSpaced("Local"); + + item.LocalExtra.PrintInfo(s); + + if (item.FromCentral) + { + s.Add_OptSpaced(":"); + s.Add_OptSpaced("Central"); + } + } + + if (item.FromCentral) + { + item.CentralExtra.PrintInfo(s); + } + + UInt32 flags = item.Flags; + flags &= ~(unsigned)6; // we don't need compression related bits here. + + if (flags != 0) + { + const AString s2 = FlagsToString(g_HeaderCharacts, Z7_ARRAY_SIZE(g_HeaderCharacts), flags); + if (!s2.IsEmpty()) + { + if (!s.IsEmpty()) + s.Add_OptSpaced(":"); + s.Add_OptSpaced(s2); + } + } + + if (item.IsBadDescriptor()) + s.Add_OptSpaced("Descriptor_ERROR"); + + if (!s.IsEmpty()) + prop = s; + break; + } + case kpidHostOS: { - Byte hostOS = item.GetHostOS(); - char temp[16]; - const char *s = NULL; - if (hostOS < ARRAY_SIZE(kHostOS)) - s = kHostOS[hostOS]; - else + if (item.FromCentral) { - ConvertUInt32ToString(hostOS, temp); - s = temp; + // 18.06: now we use HostOS only from Central::MadeByVersion + const Byte hostOS = item.MadeByVersion.HostOS; + TYPE_TO_PROP(kHostOS, hostOS, prop); } - prop = s; break; } @@ -456,34 +661,134 @@ STDMETHODIMP CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *val case kpidVolumeIndex: prop = item.Disk; break; + + case kpidOffset: + prop = item.LocalHeaderPos; + break; + + /* + case kpidIsAltStream: + prop = (bool)(item.ParentOfAltStream >= 0); // item.IsAltStream(); + break; + + case kpidName: + if (item.ParentOfAltStream >= 0) + { + // extract name of stream here + } + break; + */ + default: break; } - prop.Detach(value); - return S_OK; + return prop.Detach(value); COM_TRY_END } -STDMETHODIMP CHandler::Open(IInStream *inStream, - const UInt64 *maxCheckStartPosition, IArchiveOpenCallback *callback) + +/* +Z7_COM7F_IMF(CHandler::GetNumRawProps(UInt32 *numProps) +{ + *numProps = 0; + return S_OK; +} + +Z7_COM7F_IMF(CHandler::GetRawPropInfo(UInt32 index, BSTR *name, PROPID *propID) +{ + UNUSED_VAR(index); + *propID = 0; + *name = 0; + return S_OK; +} + +Z7_COM7F_IMF(CHandler::GetParent(UInt32 index, UInt32 *parent, UInt32 *parentType) +{ + *parentType = NParentType::kDir; + *parent = (UInt32)(Int32)-1; + if (index >= m_Items.Size()) + return S_OK; + const CItemEx &item = m_Items[index]; + + if (item.ParentOfAltStream >= 0) + { + *parentType = NParentType::kAltStream; + *parent = item.ParentOfAltStream; + } + return S_OK; +} + +Z7_COM7F_IMF(CHandler::GetRawProp(UInt32 index, PROPID propID, const void **data, UInt32 *dataSize, UInt32 *propType) +{ + UNUSED_VAR(index); + UNUSED_VAR(propID); + *data = NULL; + *dataSize = 0; + *propType = 0; + return S_OK; +} + + +void CHandler::MarkAltStreams(CObjectVector &items) +{ + int prevIndex = -1; + UString prevName; + UString name; + + for (unsigned i = 0; i < items.Size(); i++) + { + CItemEx &item = m_Items[i]; + if (item.IsAltStream()) + { + if (prevIndex == -1) + continue; + if (prevName.IsEmpty()) + { + const CItemEx &prevItem = m_Items[prevIndex]; + prevItem.GetUnicodeString(prevName, prevItem.Name, false, _forceCodePage, _specifiedCodePage); + NItemName::ReplaceToOsSlashes_Remove_TailSlash(prevName); + } + name.Empty(); + item.GetUnicodeString(name, item.Name, false, _forceCodePage, _specifiedCodePage); + NItemName::ReplaceToOsSlashes_Remove_TailSlash(name); + + if (name.IsPrefixedBy(prevName)) + if (IsString1PrefixedByString2(name.Ptr(prevName.Len()), k_SpecName_NTFS_STREAM)) + item.ParentOfAltStream = prevIndex; + } + else + { + prevIndex = i; + prevName.Empty(); + } + } +} +*/ + +Z7_COM7F_IMF(CHandler::Open(IInStream *inStream, + const UInt64 *maxCheckStartPosition, IArchiveOpenCallback *callback)) { COM_TRY_BEGIN try { Close(); + m_Archive.Force_ReadLocals_Mode = _force_OpenSeq; + // m_Archive.Disable_VolsRead = _force_OpenSeq; + // m_Archive.Disable_FindMarker = _force_OpenSeq; HRESULT res = m_Archive.Open(inStream, maxCheckStartPosition, callback, m_Items); if (res != S_OK) { m_Items.Clear(); - m_Archive.ClearRefs(); + m_Archive.ClearRefs(); // we don't want to clear error flags } + // MarkAltStreams(m_Items); return res; } catch(...) { Close(); throw; } COM_TRY_END } -STDMETHODIMP CHandler::Close() +Z7_COM7F_IMF(CHandler::Close()) { m_Items.Clear(); m_Archive.Close(); @@ -491,64 +796,55 @@ STDMETHODIMP CHandler::Close() } -class CLzmaDecoder: - public ICompressCoder, - public CMyUnknownImp -{ - NCompress::NLzma::CDecoder *DecoderSpec; - CMyComPtr Decoder; +Z7_CLASS_IMP_NOQIB_3( + CLzmaDecoder + , ICompressCoder + , ICompressSetFinishMode + , ICompressGetInStreamProcessedSize +) public: - CLzmaDecoder(); - STDMETHOD(Code)(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress); - - MY_UNKNOWN_IMP + CMyComPtr2_Create Decoder; }; -CLzmaDecoder::CLzmaDecoder() -{ - DecoderSpec = new NCompress::NLzma::CDecoder; - Decoder = DecoderSpec; -} +static const unsigned kZipLzmaPropsSize = 4 + LZMA_PROPS_SIZE; -HRESULT CLzmaDecoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 * /* inSize */, const UInt64 *outSize, ICompressProgressInfo *progress) +Z7_COM7F_IMF(CLzmaDecoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, + const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress)) { - Byte buf[9]; - RINOK(ReadStream_FALSE(inStream, buf, 9)); - if (buf[2] != 5 || buf[3] != 0) + Byte buf[kZipLzmaPropsSize]; + RINOK(ReadStream_FALSE(inStream, buf, kZipLzmaPropsSize)) + if (buf[2] != LZMA_PROPS_SIZE || buf[3] != 0) return E_NOTIMPL; - RINOK(DecoderSpec->SetDecoderProperties2(buf + 4, 5)); - return Decoder->Code(inStream, outStream, NULL, outSize, progress); + RINOK(Decoder->SetDecoderProperties2(buf + 4, LZMA_PROPS_SIZE)) + UInt64 inSize2 = 0; + if (inSize) + { + inSize2 = *inSize; + if (inSize2 < kZipLzmaPropsSize) + return S_FALSE; + inSize2 -= kZipLzmaPropsSize; + } + return Decoder.Interface()->Code(inStream, outStream, inSize ? &inSize2 : NULL, outSize, progress); } - -class CXzDecoder: - public ICompressCoder, - public CMyUnknownImp +Z7_COM7F_IMF(CLzmaDecoder::SetFinishMode(UInt32 finishMode)) { - NArchive::NXz::CDecoder _decoder; -public: - - STDMETHOD(Code)(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress); - - MY_UNKNOWN_IMP -}; + Decoder->FinishStream = (finishMode != 0); + return S_OK; +} -HRESULT CXzDecoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 * /* inSize */, const UInt64 * /* outSize */, ICompressProgressInfo *progress) +Z7_COM7F_IMF(CLzmaDecoder::GetInStreamProcessedSize(UInt64 *value)) { - RINOK(_decoder.Decode(inStream, outStream, progress)); - Int32 opRes = _decoder.Get_Extract_OperationResult(); - if (opRes == NExtract::NOperationResult::kUnsupportedMethod) - return E_NOTIMPL; - if (opRes != NExtract::NOperationResult::kOK) - return S_FALSE; + *value = Decoder->GetInputProcessedSize() + kZipLzmaPropsSize; return S_OK; } + + + + + struct CMethodItem { unsigned ZipMethod; @@ -559,25 +855,19 @@ struct CMethodItem class CZipDecoder { - NCrypto::NZip::CDecoder *_zipCryptoDecoderSpec; - NCrypto::NZipStrong::CDecoder *_pkAesDecoderSpec; - NCrypto::NWzAes::CDecoder *_wzAesDecoderSpec; - - CMyComPtr _zipCryptoDecoder; - CMyComPtr _pkAesDecoder; - CMyComPtr _wzAesDecoder; + CMyComPtr2 _zipCryptoDecoder; + CMyComPtr2 _pkAesDecoder; + CMyComPtr2 _wzAesDecoder; - CFilterCoder *filterStreamSpec; - CMyComPtr filterStream; + CMyComPtr2 filterStream; CMyComPtr getTextPassword; CObjectVector methodItems; + CLzmaDecoder *lzmaDecoderSpec; public: CZipDecoder(): - _zipCryptoDecoderSpec(0), - _pkAesDecoderSpec(0), - _wzAesDecoderSpec(0), - filterStreamSpec(0) {} + lzmaDecoderSpec(NULL) + {} HRESULT Decode( DECL_EXTERNAL_CODECS_LOC_VARS @@ -585,84 +875,165 @@ class CZipDecoder ISequentialOutStream *realOutStream, IArchiveExtractCallback *extractCallback, ICompressProgressInfo *compressProgress, - #ifndef _7ZIP_ST - UInt32 numThreads, + #ifndef Z7_ST + UInt32 numThreads, UInt64 memUsage, #endif Int32 &res); }; -static HRESULT SkipStreamData(ISequentialInStream *stream, UInt64 size) +static HRESULT SkipStreamData(ISequentialInStream *stream, + ICompressProgressInfo *progress, UInt64 packSize, UInt64 unpackSize, + bool &thereAreData) { + thereAreData = false; const size_t kBufSize = 1 << 12; Byte buf[kBufSize]; + UInt64 prev = packSize; for (;;) { + size_t size = kBufSize; + RINOK(ReadStream(stream, buf, &size)) if (size == 0) return S_OK; - size_t curSize = kBufSize; - if (curSize > size) - curSize = (size_t)size; - RINOK(ReadStream_FALSE(stream, buf, curSize)); - size -= curSize; + thereAreData = true; + packSize += size; + if ((packSize - prev) >= (1 << 22)) + { + prev = packSize; + RINOK(progress->SetRatioInfo(&packSize, &unpackSize)) + } } } + +Z7_CLASS_IMP_NOQIB_1( + COutStreamWithPadPKCS7 + , ISequentialOutStream +) + CMyComPtr _stream; + UInt64 _size; + UInt64 _padPos; + UInt32 _padSize; + bool _padFailure; +public: + void SetStream(ISequentialOutStream *stream) { _stream = stream; } + void ReleaseStream() { _stream.Release(); } + + // padSize == 0 means (no_pad Mode) + void Init(UInt64 padPos, UInt32 padSize) + { + _padPos = padPos; + _padSize = padSize; + _size = 0; + _padFailure = false; + } + UInt64 GetSize() const { return _size; } + bool WasPadFailure() const { return _padFailure; } +}; + + +Z7_COM7F_IMF(COutStreamWithPadPKCS7::Write(const void *data, UInt32 size, UInt32 *processedSize)) +{ + UInt32 written = 0; + HRESULT result = S_OK; + if (_size < _padPos) + { + const UInt64 rem = _padPos - _size; + UInt32 num = size; + if (num > rem) + num = (UInt32)rem; + result = _stream->Write(data, num, &written); + _size += written; + if (processedSize) + *processedSize = written; + if (_size != _padPos || result != S_OK) + return result; + size -= written; + data = ((const Byte *)data) + written; + } + _size += size; + written += size; + if (processedSize) + *processedSize = written; + if (_padSize != 0) + for (; size != 0; size--) + { + if (*(const Byte *)data != _padSize) + _padFailure = true; + data = ((const Byte *)data) + 1; + } + return result; +} + + + HRESULT CZipDecoder::Decode( DECL_EXTERNAL_CODECS_LOC_VARS CInArchive &archive, const CItemEx &item, ISequentialOutStream *realOutStream, IArchiveExtractCallback *extractCallback, ICompressProgressInfo *compressProgress, - #ifndef _7ZIP_ST - UInt32 numThreads, + #ifndef Z7_ST + UInt32 numThreads, UInt64 memUsage, #endif Int32 &res) { - res = NExtract::NOperationResult::kDataError; + res = NExtract::NOperationResult::kHeadersError; + CFilterCoder::C_InStream_Releaser inStreamReleaser; + CFilterCoder::C_Filter_Releaser filterReleaser; bool needCRC = true; bool wzAesMode = false; bool pkAesMode = false; + + bool badDescriptor = item.IsBadDescriptor(); + if (badDescriptor) + needCRC = false; + + unsigned id = item.Method; - if (item.IsEncrypted()) + CWzAesExtra aesField; + // LZFSE and WinZip's AES use same id - kWzAES. + + if (id == NFileHeader::NCompressionMethod::kWzAES) { - if (item.IsStrongEncrypted()) + if (item.GetMainExtra().GetWzAes(aesField)) { - CStrongCryptoExtra f; - if (item.CentralExtra.GetStrongCrypto(f)) - { - pkAesMode = true; - } - if (!pkAesMode) + if (!item.IsEncrypted()) { res = NExtract::NOperationResult::kUnsupportedMethod; return S_OK; } + wzAesMode = true; + needCRC = aesField.NeedCrc(); } - if (!pkAesMode && id == NFileHeader::NCompressionMethod::kWzAES) + } + + if (!wzAesMode) + if (item.IsEncrypted()) + { + if (item.IsStrongEncrypted()) { - CWzAesExtra aesField; - if (item.GetMainExtra().GetWzAes(aesField)) + CStrongCryptoExtra f; + if (!item.CentralExtra.GetStrongCrypto(f)) { - wzAesMode = true; - needCRC = aesField.NeedCrc(); + res = NExtract::NOperationResult::kUnsupportedMethod; + return S_OK; } + pkAesMode = true; } } - - COutStreamWithCRC *outStreamSpec = new COutStreamWithCRC; - CMyComPtr outStream = outStreamSpec; - outStreamSpec->SetStream(realOutStream); - outStreamSpec->Init(needCRC); + + CMyComPtr2_Create outStream; + outStream->SetStream(realOutStream); + outStream->Init(needCRC); CMyComPtr packStream; - - CLimitedSequentialInStream *limitedStreamSpec = new CLimitedSequentialInStream; - CMyComPtr inStream(limitedStreamSpec); + CMyComPtr2_Create inStream; { UInt64 packSize = item.PackSize; @@ -672,15 +1043,18 @@ HRESULT CZipDecoder::Decode( return S_OK; packSize -= NCrypto::NWzAes::kMacSize; } - RINOK(archive.GetItemStream(item, true, packStream)); + RINOK(archive.GetItemStream(item, true, packStream)) if (!packStream) { res = NExtract::NOperationResult::kUnavailable; return S_OK; } - limitedStreamSpec->SetStream(packStream); - limitedStreamSpec->Init(packSize); + inStream->SetStream(packStream); + inStream->Init(packSize); } + + + res = NExtract::NOperationResult::kDataError; CMyComPtr cryptoFilter; @@ -688,17 +1062,10 @@ HRESULT CZipDecoder::Decode( { if (wzAesMode) { - CWzAesExtra aesField; - if (!item.GetMainExtra().GetWzAes(aesField)) - return S_OK; id = aesField.Method; - if (!_wzAesDecoder) - { - _wzAesDecoderSpec = new NCrypto::NWzAes::CDecoder; - _wzAesDecoder = _wzAesDecoderSpec; - } + _wzAesDecoder.Create_if_Empty(); cryptoFilter = _wzAesDecoder; - if (!_wzAesDecoderSpec->SetKeyMode(aesField.Strength)) + if (!_wzAesDecoder->SetKeyMode(aesField.Strength)) { res = NExtract::NOperationResult::kUnsupportedMethod; return S_OK; @@ -706,69 +1073,72 @@ HRESULT CZipDecoder::Decode( } else if (pkAesMode) { - if (!_pkAesDecoder) - { - _pkAesDecoderSpec = new NCrypto::NZipStrong::CDecoder; - _pkAesDecoder = _pkAesDecoderSpec; - } + _pkAesDecoder.Create_if_Empty(); cryptoFilter = _pkAesDecoder; } else { - if (!_zipCryptoDecoder) - { - _zipCryptoDecoderSpec = new NCrypto::NZip::CDecoder; - _zipCryptoDecoder = _zipCryptoDecoderSpec; - } + _zipCryptoDecoder.Create_if_Empty(); cryptoFilter = _zipCryptoDecoder; } CMyComPtr cryptoSetPassword; - RINOK(cryptoFilter.QueryInterface(IID_ICryptoSetPassword, &cryptoSetPassword)); + RINOK(cryptoFilter.QueryInterface(IID_ICryptoSetPassword, &cryptoSetPassword)) + if (!cryptoSetPassword) + return E_FAIL; if (!getTextPassword) extractCallback->QueryInterface(IID_ICryptoGetTextPassword, (void **)&getTextPassword); if (getTextPassword) { - CMyComBSTR password; - RINOK(getTextPassword->CryptoGetTextPassword(&password)); - AString charPassword; + CMyComBSTR_Wipe password; + RINOK(getTextPassword->CryptoGetTextPassword(&password)) + AString_Wipe charPassword; if (password) { +#if 0 && defined(_WIN32) + // do we need UTF-8 passwords here ? + if (item.GetHostOS() == NFileHeader::NHostOS::kUnix // 24.05 + // || item.IsUtf8() // 22.00 + ) + { + // throw 1; + ConvertUnicodeToUTF8((LPCOLESTR)password, charPassword); + } + else +#endif + { + UnicodeStringToMultiByte2(charPassword, (LPCOLESTR)password, CP_ACP); + } + /* if (wzAesMode || pkAesMode) { - charPassword = UnicodeStringToMultiByte((const wchar_t *)password, CP_ACP); - /* - for (unsigned i = 0;; i++) - { - wchar_t c = password[i]; - if (c == 0) - break; - if (c >= 0x80) - { - res = NExtract::NOperationResult::kDataError; - return S_OK; - } - charPassword += (char)c; - } - */ } else { - /* pkzip25 / WinZip / Windows probably use ANSI for some files - We use OEM for compatibility with previous versions of 7-Zip? */ - charPassword = UnicodeStringToMultiByte((const wchar_t *)password, CP_OEMCP); + // PASSWORD encoding for ZipCrypto: + // pkzip25 / WinZip / Windows probably use ANSI + // 7-Zip < 4.43 creates ZIP archives with OEM encoding in password + // 7-Zip >= 4.43 creates ZIP archives only with ASCII characters in password + // 7-Zip < 17.00 uses CP_OEMCP for password decoding + // 7-Zip >= 17.00 uses CP_ACP for password decoding } + */ } HRESULT result = cryptoSetPassword->CryptoSetPassword( (const Byte *)(const char *)charPassword, charPassword.Len()); if (result != S_OK) + { + res = NExtract::NOperationResult::kWrongPassword; return S_OK; + } } else { - RINOK(cryptoSetPassword->CryptoSetPassword(0, 0)); + res = NExtract::NOperationResult::kWrongPassword; + return S_OK; + // RINOK(cryptoSetPassword->CryptoSetPassword(NULL, 0)); } } @@ -781,18 +1151,27 @@ HRESULT CZipDecoder::Decode( { CMethodItem mi; mi.ZipMethod = id; - if (id == NFileHeader::NCompressionMethod::kStored) + if (id == NFileHeader::NCompressionMethod::kStore) mi.Coder = new NCompress::CCopyCoder; - else if (id == NFileHeader::NCompressionMethod::kShrunk) + else if (id == NFileHeader::NCompressionMethod::kShrink) mi.Coder = new NCompress::NShrink::CDecoder; - else if (id == NFileHeader::NCompressionMethod::kImploded) + else if (id == NFileHeader::NCompressionMethod::kImplode) mi.Coder = new NCompress::NImplode::NDecoder::CCoder; else if (id == NFileHeader::NCompressionMethod::kLZMA) - mi.Coder = new CLzmaDecoder; + { + lzmaDecoderSpec = new CLzmaDecoder; + mi.Coder = lzmaDecoderSpec; + } else if (id == NFileHeader::NCompressionMethod::kXz) - mi.Coder = new CXzDecoder; + mi.Coder = new NCompress::NXz::CComDecoder; else if (id == NFileHeader::NCompressionMethod::kPPMd) mi.Coder = new NCompress::NPpmdZip::CDecoder(true); + else if (id == NFileHeader::NCompressionMethod::kZstdWz) + mi.Coder = new NCompress::NZstd::CDecoder(); +#ifndef Z7_ZIP_LZFSE_DISABLE + else if (id == NFileHeader::NCompressionMethod::kWzAES) + mi.Coder = new NCompress::NLzfse::CDecoder; +#endif else { CMethodId szMethodID; @@ -808,9 +1187,9 @@ HRESULT CZipDecoder::Decode( szMethodID = kMethodId_ZipBase + (Byte)id; } - RINOK(CreateCoder(EXTERNAL_CODECS_LOC_VARS szMethodID, false, mi.Coder)); + RINOK(CreateCoder_Id(EXTERNAL_CODECS_LOC_VARS szMethodID, false, mi.Coder)) - if (mi.Coder == 0) + if (!mi.Coder) { res = NExtract::NOperationResult::kUnsupportedMethod; return S_OK; @@ -819,48 +1198,74 @@ HRESULT CZipDecoder::Decode( m = methodItems.Add(mi); } - ICompressCoder *coder = methodItems[m].Coder; + const CMethodItem &mi = methodItems[m]; + ICompressCoder *coder = mi.Coder; + + #ifndef Z7_ST { - CMyComPtr setDecoderProperties; - coder->QueryInterface(IID_ICompressSetDecoderProperties2, (void **)&setDecoderProperties); - if (setDecoderProperties) + CMyComPtr setCoderMt; + coder->QueryInterface(IID_ICompressSetCoderMt, (void **)&setCoderMt); + if (setCoderMt) { - Byte properties = (Byte)item.Flags; - RINOK(setDecoderProperties->SetDecoderProperties2(&properties, 1)); + RINOK(setCoderMt->SetNumberOfThreads(numThreads)) } } - - #ifndef _7ZIP_ST + // if (memUsage != 0) { - CMyComPtr setCoderMt; - coder->QueryInterface(IID_ICompressSetCoderMt, (void **)&setCoderMt); - if (setCoderMt) + CMyComPtr setMemLimit; + coder->QueryInterface(IID_ICompressSetMemLimit, (void **)&setMemLimit); + if (setMemLimit) { - RINOK(setCoderMt->SetNumberOfThreads(numThreads)); + RINOK(setMemLimit->SetMemLimit(memUsage)) } } #endif + + { + CMyComPtr setDecoderProperties; + coder->QueryInterface(IID_ICompressSetDecoderProperties2, (void **)&setDecoderProperties); + if (setDecoderProperties) + { + Byte properties = (Byte)item.Flags; + RINOK(setDecoderProperties->SetDecoderProperties2(&properties, 1)) + } + } + + bool isFullStreamExpected = (!item.HasDescriptor() || item.PackSize != 0); + bool needReminderCheck = false; + + bool dataAfterEnd = false; + bool truncatedError = false; + bool lzmaEosError = false; + bool headersError = false; + bool padError = false; + bool readFromFilter = false; + + const bool useUnpackLimit = (id == NFileHeader::NCompressionMethod::kStore + || !item.HasDescriptor() + || item.Size >= ((UInt64)1 << 32) + || item.LocalExtra.IsZip64 + || item.CentralExtra.IsZip64 + ); + { HRESULT result = S_OK; - CMyComPtr inStreamNew; if (item.IsEncrypted()) { - if (!filterStream) - { - filterStreamSpec = new CFilterCoder(false); - filterStream = filterStreamSpec; - } + if (!filterStream.IsDefined()) + filterStream.SetFromCls(new CFilterCoder(false)); - filterStreamSpec->Filter = cryptoFilter; + filterReleaser.FilterCoder = filterStream.ClsPtr(); + filterStream->Filter = cryptoFilter; if (wzAesMode) { - result = _wzAesDecoderSpec->ReadHeader(inStream); + result = _wzAesDecoder->ReadHeader(inStream); if (result == S_OK) { - if (!_wzAesDecoderSpec->Init_and_CheckPassword()) + if (!_wzAesDecoder->Init_and_CheckPassword()) { res = NExtract::NOperationResult::kWrongPassword; return S_OK; @@ -869,11 +1274,12 @@ HRESULT CZipDecoder::Decode( } else if (pkAesMode) { - result =_pkAesDecoderSpec->ReadHeader(inStream, item.Crc, item.Size); + isFullStreamExpected = false; + result = _pkAesDecoder->ReadHeader(inStream, item.Crc, item.Size); if (result == S_OK) { bool passwOK; - result = _pkAesDecoderSpec->Init_and_CheckPassword(passwOK); + result = _pkAesDecoder->Init_and_CheckPassword(passwOK); if (result == S_OK && !passwOK) { res = NExtract::NOperationResult::kWrongPassword; @@ -883,10 +1289,10 @@ HRESULT CZipDecoder::Decode( } else { - result = _zipCryptoDecoderSpec->ReadHeader(inStream); + result = _zipCryptoDecoder->ReadHeader(inStream); if (result == S_OK) { - _zipCryptoDecoderSpec->Init_BeforeDecode(); + _zipCryptoDecoder->Init_BeforeDecode(); /* Info-ZIP modification to ZipCrypto format: if bit 3 of the general purpose bit flag is set, @@ -894,10 +1300,10 @@ HRESULT CZipDecoder::Decode( Info-ZIP code probably writes 2 bytes of File Time. We check only 1 byte. */ - // UInt32 v1 = GetUi16(_zipCryptoDecoderSpec->_header + NCrypto::NZip::kHeaderSize - 2); + // UInt32 v1 = GetUi16(_zipCryptoDecoder->_header + NCrypto::NZip::kHeaderSize - 2); // UInt32 v2 = (item.HasDescriptor() ? (item.Time & 0xFFFF) : (item.Crc >> 16)); - Byte v1 = _zipCryptoDecoderSpec->_header[NCrypto::NZip::kHeaderSize - 1]; + Byte v1 = _zipCryptoDecoder->_header[NCrypto::NZip::kHeaderSize - 1]; Byte v2 = (Byte)(item.HasDescriptor() ? (item.Time >> 8) : (item.Crc >> 24)); if (v1 != v2) @@ -907,26 +1313,173 @@ HRESULT CZipDecoder::Decode( } } } + } - if (result == S_OK) + if (result == S_OK) + { + CMyComPtr setFinishMode; + coder->QueryInterface(IID_ICompressSetFinishMode, (void **)&setFinishMode); + if (setFinishMode) { - inStreamReleaser.FilterCoder = filterStreamSpec; - RINOK(filterStreamSpec->SetInStream(inStream)); + RINOK(setFinishMode->SetFinishMode(BoolToUInt(true))) + } + + const UInt64 coderPackSize = inStream->GetRem(); + + if (id == NFileHeader::NCompressionMethod::kStore && item.IsEncrypted()) + { + // for debug : we can disable this code (kStore + 50), if we want to test CopyCoder+Filter + // here we use filter without CopyCoder + readFromFilter = false; + + COutStreamWithPadPKCS7 *padStreamSpec = NULL; + CMyComPtr padStream; + UInt32 padSize = 0; - /* IFilter::Init() does nothing in all zip crypto filters. - So we can call any Initialize function in CFilterCoder. */ + if (pkAesMode) + { + padStreamSpec = new COutStreamWithPadPKCS7; + padStream = padStreamSpec; + padSize = _pkAesDecoder->GetPadSize((UInt32)item.Size); + padStreamSpec->SetStream(outStream); + padStreamSpec->Init(item.Size, padSize); + } - RINOK(filterStreamSpec->Init_NoSubFilterInit()); - // RINOK(filterStreamSpec->SetOutStreamSize(NULL)); - - inStreamNew = filterStream; + // Here we decode minimal required size, including padding + const UInt64 expectedSize = item.Size + padSize; + UInt64 size = coderPackSize; + if (item.Size > coderPackSize) + headersError = true; + else if (expectedSize != coderPackSize) + { + headersError = true; + if (coderPackSize > expectedSize) + size = expectedSize; + } + + result = filterStream->Code(inStream, padStream ? + padStream.Interface() : + outStream.Interface(), + NULL, &size, compressProgress); + + if (outStream->GetSize() != item.Size) + truncatedError = true; + + if (pkAesMode) + { + if (padStreamSpec->GetSize() != size) + truncatedError = true; + if (padStreamSpec->WasPadFailure()) + padError = true; + } } - } - else - inStreamNew = inStream; + else + { + if (item.IsEncrypted()) + { + readFromFilter = true; + inStreamReleaser.FilterCoder = filterStream.ClsPtr(); + RINOK(filterStream->SetInStream(inStream)) + + /* IFilter::Init() does nothing in all zip crypto filters. + So we can call any Initialize function in CFilterCoder. */ + + RINOK(filterStream->Init_NoSubFilterInit()) + // RINOK(filterStream->SetOutStreamSize(NULL)); + } - if (result == S_OK) - result = coder->Code(inStreamNew, outStream, NULL, &item.Size, compressProgress); + try { + result = coder->Code(readFromFilter ? + filterStream.Interface() : + inStream.Interface(), + outStream, + isFullStreamExpected ? &coderPackSize : NULL, + // NULL, + useUnpackLimit ? &item.Size : NULL, + compressProgress); + } catch (...) { return E_FAIL; } + + if (result == S_OK) + { + CMyComPtr getInStreamProcessedSize; + coder->QueryInterface(IID_ICompressGetInStreamProcessedSize, (void **)&getInStreamProcessedSize); + if (getInStreamProcessedSize && setFinishMode) + { + UInt64 processed; + RINOK(getInStreamProcessedSize->GetInStreamProcessedSize(&processed)) + if (processed != (UInt64)(Int64)-1) + { + if (pkAesMode) + { + const UInt32 padSize = _pkAesDecoder->GetPadSize((UInt32)processed); + if (processed + padSize > coderPackSize) + truncatedError = true; + else if (processed + padSize < coderPackSize) + dataAfterEnd = true; + else + { + { + // here we check PKCS7 padding data from reminder (it can be inside stream buffer in coder). + CMyComPtr readInStream; + coder->QueryInterface(IID_ICompressReadUnusedFromInBuf, (void **)&readInStream); + // CCopyCoder() for kStore doesn't read data outside of (item.Size) + if (readInStream || id == NFileHeader::NCompressionMethod::kStore) + { + // change pad size, if we support another block size in ZipStrong. + // here we request more data to detect error with data after end. + const UInt32 kBufSize = NCrypto::NZipStrong::kAesPadAllign + 16; + Byte buf[kBufSize]; + UInt32 processedSize = 0; + if (readInStream) + { + RINOK(readInStream->ReadUnusedFromInBuf(buf, kBufSize, &processedSize)) + } + if (processedSize > padSize) + dataAfterEnd = true; + else + { + size_t processedSize2 = kBufSize - processedSize; + result = ReadStream(filterStream, buf + processedSize, &processedSize2); + if (result == S_OK) + { + processedSize2 += processedSize; + if (processedSize2 > padSize) + dataAfterEnd = true; + else if (processedSize2 < padSize) + truncatedError = true; + else + for (unsigned i = 0; i < padSize; i++) + if (buf[i] != padSize) + padError = true; + } + } + } + } + } + } + else + { + if (processed < coderPackSize) + { + if (isFullStreamExpected) + dataAfterEnd = true; + } + else if (processed > coderPackSize) + { + // that case is additional check, that can show the bugs in code (coder) + truncatedError = true; + } + needReminderCheck = isFullStreamExpected; + } + } + } + } + } + + if (result == S_OK && id == NFileHeader::NCompressionMethod::kLZMA) + if (!lzmaDecoderSpec->Decoder->CheckFinishStatus(item.IsLzmaEOS())) + lzmaEosError = true; + } if (result == S_FALSE) return S_OK; @@ -937,123 +1490,160 @@ HRESULT CZipDecoder::Decode( return S_OK; } - RINOK(result); + RINOK(result) } bool crcOK = true; bool authOk = true; if (needCRC) - crcOK = (outStreamSpec->GetCRC() == item.Crc); + crcOK = (outStream->GetCRC() == item.Crc); + + if (useUnpackLimit) + if (outStream->GetSize() != item.Size) + truncatedError = true; if (wzAesMode) { - const UInt64 rem = limitedStreamSpec->GetRem(); - if (rem != 0) - if (SkipStreamData(inStream, rem) != S_OK) + const UInt64 unpackSize = outStream->GetSize(); + const UInt64 packSize = inStream->GetSize(); + bool thereAreData = false; + // read to the end from filter or from packed stream + if (SkipStreamData(readFromFilter ? + filterStream.Interface() : + inStream.Interface(), + compressProgress, packSize, unpackSize, thereAreData) != S_OK) + authOk = false; + if (needReminderCheck && thereAreData) + dataAfterEnd = true; + + if (inStream->GetRem() != 0) + truncatedError = true; + else + { + inStream->Init(NCrypto::NWzAes::kMacSize); + if (_wzAesDecoder->CheckMac(inStream, authOk) != S_OK) authOk = false; + } + } - limitedStreamSpec->Init(NCrypto::NWzAes::kMacSize); - if (_wzAesDecoderSpec->CheckMac(inStream, authOk) != S_OK) - authOk = false; + res = NExtract::NOperationResult::kCRCError; + + if (crcOK && authOk) + { + res = NExtract::NOperationResult::kOK; + + if (dataAfterEnd) + res = NExtract::NOperationResult::kDataAfterEnd; + else if (padError) + res = NExtract::NOperationResult::kCRCError; + else if (truncatedError) + res = NExtract::NOperationResult::kUnexpectedEnd; + else if (headersError) + res = NExtract::NOperationResult::kHeadersError; + else if (lzmaEosError) + res = NExtract::NOperationResult::kHeadersError; + else if (badDescriptor) + res = NExtract::NOperationResult::kUnexpectedEnd; + + // CheckDescriptor() supports only data descriptor with signature and + // it doesn't support "old" pkzip's data descriptor without signature. + // So we disable that check. + /* + if (item.HasDescriptor() && archive.CheckDescriptor(item) != S_OK) + res = NExtract::NOperationResult::kHeadersError; + */ } - - res = ((crcOK && authOk) ? - NExtract::NOperationResult::kOK : - NExtract::NOperationResult::kCRCError); + return S_OK; } -STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, - Int32 testMode, IArchiveExtractCallback *extractCallback) +Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testMode, IArchiveExtractCallback *extractCallback)) { COM_TRY_BEGIN - CZipDecoder myDecoder; - UInt64 totalUnPacked = 0, totalPacked = 0; - bool allFilesMode = (numItems == (UInt32)(Int32)-1); + const bool allFilesMode = (numItems == (UInt32)(Int32)-1); if (allFilesMode) numItems = m_Items.Size(); if (numItems == 0) return S_OK; + UInt64 total = 0; // , totalPacked = 0; UInt32 i; for (i = 0; i < numItems; i++) { const CItemEx &item = m_Items[allFilesMode ? i : indices[i]]; - totalUnPacked += item.Size; - totalPacked += item.PackSize; + total += item.Size; + // totalPacked += item.PackSize; } - RINOK(extractCallback->SetTotal(totalUnPacked)); + RINOK(extractCallback->SetTotal(total)) - UInt64 currentTotalUnPacked = 0, currentTotalPacked = 0; - UInt64 currentItemUnPacked, currentItemPacked; + CZipDecoder myDecoder; + UInt64 cur_Unpacked, cur_Packed; - CLocalProgress *lps = new CLocalProgress; - CMyComPtr progress = lps; + CMyComPtr2_Create lps; lps->Init(extractCallback, false); - for (i = 0; i < numItems; i++, - currentTotalUnPacked += currentItemUnPacked, - currentTotalPacked += currentItemPacked) + for (i = 0;; i++, + lps->OutSize += cur_Unpacked, + lps->InSize += cur_Packed) { - currentItemUnPacked = 0; - currentItemPacked = 0; - - lps->InSize = currentTotalPacked; - lps->OutSize = currentTotalUnPacked; - RINOK(lps->SetCur()); + RINOK(lps->SetCur()) + if (i >= numItems) + return S_OK; + const UInt32 index = allFilesMode ? i : indices[i]; + CItemEx item = m_Items[index]; + cur_Unpacked = item.Size; + cur_Packed = item.PackSize; - CMyComPtr realOutStream; - Int32 askMode = testMode ? + const bool isLocalOffsetOK = m_Archive.IsLocalOffsetOK(item); + const bool skip = !isLocalOffsetOK && !item.IsDir(); + const Int32 askMode = skip ? + NExtract::NAskMode::kSkip : testMode ? NExtract::NAskMode::kTest : NExtract::NAskMode::kExtract; - UInt32 index = allFilesMode ? i : indices[i]; - - CItemEx item = m_Items[index]; - bool isLocalOffsetOK = m_Archive.IsLocalOffsetOK(item); - bool skip = !isLocalOffsetOK && !item.IsDir(); - if (skip) - askMode = NExtract::NAskMode::kSkip; - currentItemUnPacked = item.Size; - currentItemPacked = item.PackSize; - - RINOK(extractCallback->GetStream(index, &realOutStream, askMode)); + Int32 opRes; + { + CMyComPtr realOutStream; + RINOK(extractCallback->GetStream(index, &realOutStream, askMode)) if (!isLocalOffsetOK) { - RINOK(extractCallback->PrepareOperation(askMode)); + RINOK(extractCallback->PrepareOperation(askMode)) realOutStream.Release(); - RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kUnavailable)); + RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kUnavailable)) continue; } + + bool headersError = false; if (!item.FromLocal) { bool isAvail = true; - HRESULT res = m_Archive.ReadLocalItemAfterCdItem(item, isAvail); - if (res == S_FALSE) + const HRESULT hres = m_Archive.Read_LocalItem_After_CdItem(item, isAvail, headersError); + if (hres == S_FALSE) { if (item.IsDir() || realOutStream || testMode) { - RINOK(extractCallback->PrepareOperation(askMode)); + RINOK(extractCallback->PrepareOperation(askMode)) realOutStream.Release(); RINOK(extractCallback->SetOperationResult( isAvail ? NExtract::NOperationResult::kHeadersError : - NExtract::NOperationResult::kUnavailable)); + NExtract::NOperationResult::kUnavailable)) } continue; } - RINOK(res); + RINOK(hres) } if (item.IsDir()) { // if (!testMode) { - RINOK(extractCallback->PrepareOperation(askMode)); + RINOK(extractCallback->PrepareOperation(askMode)) realOutStream.Release(); - RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)); + RINOK(extractCallback->SetOperationResult(NExtract::NOperationResult::kOK)) } continue; } @@ -1061,26 +1651,26 @@ STDMETHODIMP CHandler::Extract(const UInt32 *indices, UInt32 numItems, if (!testMode && !realOutStream) continue; - RINOK(extractCallback->PrepareOperation(askMode)); + RINOK(extractCallback->PrepareOperation(askMode)) - Int32 res; - HRESULT hres = myDecoder.Decode( + const HRESULT hres = myDecoder.Decode( EXTERNAL_CODECS_VARS m_Archive, item, realOutStream, extractCallback, - progress, - #ifndef _7ZIP_ST - _props.NumThreads, + lps, + #ifndef Z7_ST + _props._numThreads, _props._memUsage_Decompress, #endif - res); - RINOK(hres); - realOutStream.Release(); + opRes); + + RINOK(hres) + // realOutStream.Release(); - RINOK(extractCallback->SetOperationResult(res)) + if (opRes == NExtract::NOperationResult::kOK && headersError) + opRes = NExtract::NOperationResult::kHeadersError; + } + RINOK(extractCallback->SetOperationResult(opRes)) } - - lps->InSize = currentTotalPacked; - lps->OutSize = currentTotalUnPacked; - return lps->SetCur(); + COM_TRY_END } diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipHandler.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipHandler.h index c2a362a7..1f38bfff 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipHandler.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipHandler.h @@ -1,7 +1,7 @@ // Zip/Handler.h -#ifndef __ZIP_HANDLER_H -#define __ZIP_HANDLER_H +#ifndef ZIP7_INC_ZIP_HANDLER_H +#define ZIP7_INC_ZIP_HANDLER_H #include "../../../Common/DynamicBuffer.h" #include "../../ICoder.h" @@ -9,47 +9,57 @@ #include "../../Common/CreateCoder.h" -#include "ZipIn.h" #include "ZipCompressionMode.h" +#include "ZipIn.h" namespace NArchive { namespace NZip { -class CHandler: +const unsigned kNumMethodNames1 = NFileHeader::NCompressionMethod::kZstdPk + 1; +const unsigned kMethodNames2Start = NFileHeader::NCompressionMethod::kZstdWz; +const unsigned kNumMethodNames2 = NFileHeader::NCompressionMethod::kWzAES + 1 - kMethodNames2Start; + +extern const char * const kMethodNames1[kNumMethodNames1]; +extern const char * const kMethodNames2[kNumMethodNames2]; + + +class CHandler Z7_final: public IInArchive, + // public IArchiveGetRawProps, public IOutArchive, public ISetProperties, - PUBLIC_ISetCompressCodecsInfo + Z7_PUBLIC_ISetCompressCodecsInfo_IFEC public CMyUnknownImp { -public: - MY_QUERYINTERFACE_BEGIN2(IInArchive) - MY_QUERYINTERFACE_ENTRY(IOutArchive) - MY_QUERYINTERFACE_ENTRY(ISetProperties) - QUERY_ENTRY_ISetCompressCodecsInfo - MY_QUERYINTERFACE_END - MY_ADDREF_RELEASE - - INTERFACE_IInArchive(;) - INTERFACE_IOutArchive(;) - - STDMETHOD(SetProperties)(const wchar_t * const *names, const PROPVARIANT *values, UInt32 numProps); - + Z7_COM_QI_BEGIN2(IInArchive) + // Z7_COM_QI_ENTRY(IArchiveGetRawProps) + Z7_COM_QI_ENTRY(IOutArchive) + Z7_COM_QI_ENTRY(ISetProperties) + Z7_COM_QI_ENTRY_ISetCompressCodecsInfo_IFEC + Z7_COM_QI_END + Z7_COM_ADDREF_RELEASE + + Z7_IFACE_COM7_IMP(IInArchive) + // Z7_IFACE_COM7_IMP(IArchiveGetRawProps) + Z7_IFACE_COM7_IMP(IOutArchive) + Z7_IFACE_COM7_IMP(ISetProperties) DECL_ISetCompressCodecsInfo - CHandler(); private: CObjectVector m_Items; CInArchive m_Archive; CBaseProps _props; + CHandlerTimeOptions TimeOptions; int m_MainMethod; bool m_ForceAesMode; - bool m_WriteNtfsTimeExtra; + bool _removeSfxBlock; bool m_ForceLocal; bool m_ForceUtf8; + bool _force_SeqOutMode; // for creation + bool _force_OpenSeq; bool _forceCodePage; UInt32 _specifiedCodePage; @@ -58,15 +68,25 @@ class CHandler: void InitMethodProps() { _props.Init(); + TimeOptions.Init(); + TimeOptions.Prec = k_PropVar_TimePrec_0; m_MainMethod = -1; m_ForceAesMode = false; - m_WriteNtfsTimeExtra = true; _removeSfxBlock = false; m_ForceLocal = false; m_ForceUtf8 = false; + _force_SeqOutMode = false; + _force_OpenSeq = false; _forceCodePage = false; _specifiedCodePage = CP_OEMCP; } + + // void MarkAltStreams(CObjectVector &items); + + HRESULT GetOutProperty(IArchiveUpdateCallback *callback, UInt32 callbackIndex, Int32 arcIndex, PROPID propID, PROPVARIANT *value); + +public: + CHandler(); }; }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipHandlerOut.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipHandlerOut.cpp index 8a8de511..46ccf3d8 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipHandlerOut.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipHandlerOut.cpp @@ -28,9 +28,9 @@ using namespace NTime; namespace NArchive { namespace NZip { -STDMETHODIMP CHandler::GetFileTimeType(UInt32 *timeType) +Z7_COM7F_IMF(CHandler::GetFileTimeType(UInt32 *timeType)) { - *timeType = NFileTimeType::kDOS; + *timeType = TimeOptions.Prec; return S_OK; } @@ -46,16 +46,40 @@ static bool IsSimpleAsciiString(const wchar_t *s) } } + +static int FindZipMethod(const char *s, const char * const *names, unsigned num) +{ + for (unsigned i = 0; i < num; i++) + { + const char *name = names[i]; + if (name && StringsAreEqualNoCase_Ascii(s, name)) + return (int)i; + } + return -1; +} + +static int FindZipMethod(const char *s) +{ + int k = FindZipMethod(s, kMethodNames1, kNumMethodNames1); + if (k >= 0) + return k; + k = FindZipMethod(s, kMethodNames2, kNumMethodNames2); + if (k >= 0) + return (int)kMethodNames2Start + k; + return -1; +} + + #define COM_TRY_BEGIN2 try { #define COM_TRY_END2 } \ catch(const CSystemException &e) { return e.ErrorCode; } \ catch(...) { return E_OUTOFMEMORY; } -static HRESULT GetTime(IArchiveUpdateCallback *callback, int index, PROPID propID, FILETIME &filetime) +static HRESULT GetTime(IArchiveUpdateCallback *callback, unsigned index, PROPID propID, FILETIME &filetime) { filetime.dwHighDateTime = filetime.dwLowDateTime = 0; NCOM::CPropVariant prop; - RINOK(callback->GetProperty(index, propID, &prop)); + RINOK(callback->GetProperty(index, propID, &prop)) if (prop.vt == VT_FILETIME) filetime = prop.filetime; else if (prop.vt != VT_EMPTY) @@ -63,8 +87,9 @@ static HRESULT GetTime(IArchiveUpdateCallback *callback, int index, PROPID propI return S_OK; } -STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numItems, - IArchiveUpdateCallback *callback) + +Z7_COM7F_IMF(CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numItems, + IArchiveUpdateCallback *callback)) { COM_TRY_BEGIN2 @@ -75,34 +100,54 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numIt } CObjectVector updateItems; + updateItems.ClearAndReserve(numItems); + bool thereAreAesUpdates = false; UInt64 largestSize = 0; bool largestSizeDefined = false; + #ifdef _WIN32 + const UINT oemCP = GetOEMCP(); + #endif + + UString name; + CUpdateItem ui; + for (UInt32 i = 0; i < numItems; i++) { - CUpdateItem ui; Int32 newData; Int32 newProps; - UInt32 indexInArchive; + UInt32 indexInArc; + if (!callback) return E_FAIL; - RINOK(callback->GetUpdateItemInfo(i, &newData, &newProps, &indexInArchive)); + + RINOK(callback->GetUpdateItemInfo(i, &newData, &newProps, &indexInArc)) + + name.Empty(); + ui.Clear(); + ui.NewProps = IntToBool(newProps); ui.NewData = IntToBool(newData); - ui.IndexInArc = indexInArchive; + ui.IndexInArc = (int)indexInArc; ui.IndexInClient = i; - bool existInArchive = (indexInArchive != (UInt32)(Int32)-1); - if (existInArchive && newData) - if (m_Items[indexInArchive].IsAesEncrypted()) + + bool existInArchive = (indexInArc != (UInt32)(Int32)-1); + if (existInArchive) + { + const CItemEx &inputItem = m_Items[indexInArc]; + if (inputItem.IsAesEncrypted()) thereAreAesUpdates = true; + if (!IntToBool(newProps)) + ui.IsDir = inputItem.IsDir(); + // ui.IsAltStream = inputItem.IsAltStream(); + } if (IntToBool(newProps)) { - UString name; { NCOM::CPropVariant prop; - RINOK(callback->GetProperty(i, kpidAttrib, &prop)); + RINOK(callback->GetProperty(i, kpidAttrib, &prop)) if (prop.vt == VT_EMPTY) ui.Attrib = 0; else if (prop.vt != VT_UI4) @@ -113,17 +158,20 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numIt { NCOM::CPropVariant prop; - RINOK(callback->GetProperty(i, kpidPath, &prop)); + RINOK(callback->GetProperty(i, kpidPath, &prop)) if (prop.vt == VT_EMPTY) - name.Empty(); + { + // name.Empty(); + } else if (prop.vt != VT_BSTR) return E_INVALIDARG; else name = prop.bstrVal; } + { NCOM::CPropVariant prop; - RINOK(callback->GetProperty(i, kpidIsDir, &prop)); + RINOK(callback->GetProperty(i, kpidIsDir, &prop)) if (prop.vt == VT_EMPTY) ui.IsDir = false; else if (prop.vt != VT_BOOL) @@ -132,28 +180,87 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numIt ui.IsDir = (prop.boolVal != VARIANT_FALSE); } + /* + { + bool isAltStream = false; + { + NCOM::CPropVariant prop; + RINOK(callback->GetProperty(i, kpidIsAltStream, &prop)); + if (prop.vt == VT_BOOL) + isAltStream = (prop.boolVal != VARIANT_FALSE); + else if (prop.vt != VT_EMPTY) + return E_INVALIDARG; + } + + if (isAltStream) + { + if (ui.IsDir) + return E_INVALIDARG; + int delim = name.ReverseFind(L':'); + if (delim >= 0) + { + name.Delete(delim, 1); + name.Insert(delim, UString(k_SpecName_NTFS_STREAM)); + ui.IsAltStream = true; + } + } + } + */ + + // 22.00 : kpidTimeType is useless here : the code was disabled + /* { CPropVariant prop; RINOK(callback->GetProperty(i, kpidTimeType, &prop)); if (prop.vt == VT_UI4) - ui.NtfsTimeIsDefined = (prop.ulVal == NFileTimeType::kWindows); + ui.NtfsTime_IsDefined = (prop.ulVal == NFileTimeType::kWindows); else - ui.NtfsTimeIsDefined = m_WriteNtfsTimeExtra; + ui.NtfsTime_IsDefined = _Write_NtfsTime; } - RINOK(GetTime(callback, i, kpidMTime, ui.Ntfs_MTime)); - RINOK(GetTime(callback, i, kpidATime, ui.Ntfs_ATime)); - RINOK(GetTime(callback, i, kpidCTime, ui.Ntfs_CTime)); + */ + + if (TimeOptions.Write_MTime.Val) RINOK (GetTime (callback, i, kpidMTime, ui.Ntfs_MTime)) + if (TimeOptions.Write_ATime.Val) RINOK (GetTime (callback, i, kpidATime, ui.Ntfs_ATime)) + if (TimeOptions.Write_CTime.Val) RINOK (GetTime (callback, i, kpidCTime, ui.Ntfs_CTime)) + if (TimeOptions.Prec != k_PropVar_TimePrec_DOS) { - FILETIME localFileTime = { 0, 0 }; - if (ui.Ntfs_MTime.dwHighDateTime != 0 || - ui.Ntfs_MTime.dwLowDateTime != 0) - if (!FileTimeToLocalFileTime(&ui.Ntfs_MTime, &localFileTime)) - return E_INVALIDARG; - FileTimeToDosTime(localFileTime, ui.Time); + if (TimeOptions.Prec == k_PropVar_TimePrec_Unix || + TimeOptions.Prec == k_PropVar_TimePrec_Base) + ui.Write_UnixTime = ! FILETIME_IsZero (ui.Ntfs_MTime); + else + { + /* + // if we want to store zero timestamps as zero timestamp, use the following: + ui.Write_NtfsTime = + _Write_MTime || + _Write_ATime || + _Write_CTime; + */ + + // We treat zero timestamp as no timestamp + ui.Write_NtfsTime = + ! FILETIME_IsZero (ui.Ntfs_MTime) || + ! FILETIME_IsZero (ui.Ntfs_ATime) || + ! FILETIME_IsZero (ui.Ntfs_CTime); + } } - name = NItemName::MakeLegalName(name); + /* + how 0 in dos time works: + win10 explorer extract : some random date 1601-04-25. + winrar 6.10 : write time. + 7zip : MTime of archive is used + how 0 in tar works: + winrar 6.10 : 1970 + 0 in dos field can show that there is no timestamp. + we write correct 1970-01-01 in dos field, to support correct extraction in Win10. + */ + + UtcFileTime_To_LocalDosTime(ui.Ntfs_MTime, ui.Time); + + NItemName::ReplaceSlashes_OsToUnix(name); + bool needSlash = ui.IsDir; const wchar_t kSlash = L'/'; if (!name.IsEmpty()) @@ -168,10 +275,25 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numIt if (needSlash) name += kSlash; - UINT codePage = _forceCodePage ? _specifiedCodePage : CP_OEMCP; - + const UINT codePage = _forceCodePage ? _specifiedCodePage : CP_OEMCP; bool tryUtf8 = true; - if ((m_ForceLocal || !m_ForceUtf8) && codePage != CP_UTF8) + + /* + Windows 10 allows users to set UTF-8 in Region Settings via option: + "Beta: Use Unicode UTF-8 for worldwide language support" + In that case Windows uses CP_UTF8 when we use CP_OEMCP. + 21.02 fixed: + we set UTF-8 mark for non-latin files for such UTF-8 mode in Windows. + we write additional Info-Zip Utf-8 FileName Extra for non-latin names/ + */ + + if ((codePage != CP_UTF8) && + #ifdef _WIN32 + (m_ForceLocal || !m_ForceUtf8) && (oemCP != CP_UTF8) + #else + (m_ForceLocal && !m_ForceUtf8) + #endif + ) { bool defaultCharWasUsed; ui.Name = UnicodeStringToMultiByte(name, codePage, '_', defaultCharWasUsed); @@ -179,20 +301,59 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numIt MultiByteToUnicodeString(ui.Name, codePage) != name)); } + const bool isNonLatin = !name.IsAscii(); + if (tryUtf8) { - ui.IsUtf8 = !name.IsAscii(); + ui.IsUtf8 = isNonLatin; ConvertUnicodeToUTF8(name, ui.Name); + + #ifndef _WIN32 + if (ui.IsUtf8 && !CheckUTF8_AString(ui.Name)) + { + // if it's non-Windows and there are non-UTF8 characters we clear UTF8-flag + ui.IsUtf8 = false; + } + #endif } + else if (isNonLatin) + Convert_Unicode_To_UTF8_Buf(name, ui.Name_Utf); - if (ui.Name.Len() >= (1 << 16)) + if (ui.Name.Len() >= (1 << 16) + || ui.Name_Utf.Size() >= (1 << 16) - 128) return E_INVALIDARG; - ui.IndexInClient = i; + { + NCOM::CPropVariant prop; + RINOK(callback->GetProperty(i, kpidComment, &prop)) + if (prop.vt == VT_EMPTY) + { + // ui.Comment.Free(); + } + else if (prop.vt != VT_BSTR) + return E_INVALIDARG; + else + { + UString s = prop.bstrVal; + AString a; + if (ui.IsUtf8) + ConvertUnicodeToUTF8(s, a); + else + { + bool defaultCharWasUsed; + a = UnicodeStringToMultiByte(s, codePage, '_', defaultCharWasUsed); + } + if (a.Len() >= (1 << 16)) + return E_INVALIDARG; + ui.Comment.CopyFrom((const Byte *)(const char *)a, a.Len()); + } + } + + /* if (existInArchive) { - const CItemEx &itemInfo = m_Items[indexInArchive]; + const CItemEx &itemInfo = m_Items[indexInArc]; // ui.Commented = itemInfo.IsCommented(); ui.Commented = false; if (ui.Commented) @@ -205,13 +366,15 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numIt ui.Commented = false; */ } + + if (IntToBool(newData)) { UInt64 size = 0; if (!ui.IsDir) { NCOM::CPropVariant prop; - RINOK(callback->GetProperty(i, kpidSize, &prop)); + RINOK(callback->GetProperty(i, kpidSize, &prop)) if (prop.vt != VT_UI8) return E_INVALIDARG; size = prop.uhVal.QuadPart; @@ -220,12 +383,12 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numIt largestSizeDefined = true; } ui.Size = size; - - // ui.Size -= ui.Size / 2; } + updateItems.Add(ui); } + CMyComPtr getTextPassword; { CMyComPtr udateCallBack2(callback); @@ -233,18 +396,18 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numIt } CCompressionMethodMode options; (CBaseProps &)options = _props; - options._dataSizeReduce = largestSize; - options._dataSizeReduceDefined = largestSizeDefined; + options.DataSizeReduce = largestSize; + options.DataSizeReduce_Defined = largestSizeDefined; - options.PasswordIsDefined = false; - options.Password.Empty(); + options.Password_Defined = false; + options.Password.Wipe_and_Empty(); if (getTextPassword) { - CMyComBSTR password; + CMyComBSTR_Wipe password; Int32 passwordIsDefined; - RINOK(getTextPassword->CryptoGetTextPassword2(&passwordIsDefined, &password)); - options.PasswordIsDefined = IntToBool(passwordIsDefined); - if (options.PasswordIsDefined) + RINOK(getTextPassword->CryptoGetTextPassword2(&passwordIsDefined, &password)) + options.Password_Defined = IntToBool(passwordIsDefined); + if (options.Password_Defined) { if (!m_ForceAesMode) options.IsAesMode = thereAreAesUpdates; @@ -252,7 +415,7 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numIt if (!IsSimpleAsciiString(password)) return E_INVALIDARG; if (password) - options.Password = UnicodeStringToMultiByte((LPCOLESTR)password, CP_OEMCP); + UnicodeStringToMultiByte2(options.Password, (LPCOLESTR)password, CP_OEMCP); if (options.IsAesMode) { if (options.Password.Len() > NCrypto::NWzAes::kPasswordSizeMax) @@ -261,48 +424,81 @@ STDMETHODIMP CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numIt } } - Byte mainMethod; - if (m_MainMethod < 0) - mainMethod = (Byte)(((_props.Level == 0) ? - NFileHeader::NCompressionMethod::kStored : - NFileHeader::NCompressionMethod::kDeflated)); + + int mainMethod = m_MainMethod; + + if (mainMethod < 0) + { + if (!_props._methods.IsEmpty()) + { + const AString &methodName = _props._methods.Front().MethodName; + if (!methodName.IsEmpty()) + { + mainMethod = FindZipMethod(methodName); + if (mainMethod < 0) + { + CMethodId methodId; + UInt32 numStreams; + bool isFilter; + if (FindMethod_Index(EXTERNAL_CODECS_VARS methodName, true, + methodId, numStreams, isFilter) < 0) + return E_NOTIMPL; + if (numStreams != 1) + return E_NOTIMPL; + if (methodId == kMethodId_BZip2) + mainMethod = NFileHeader::NCompressionMethod::kBZip2; + else + { + if (methodId < kMethodId_ZipBase) + return E_NOTIMPL; + methodId -= kMethodId_ZipBase; + if (methodId > 0xFF) + return E_NOTIMPL; + mainMethod = (int)methodId; + } + } + } + } + } + + if (mainMethod < 0) + mainMethod = (Byte)(((_props.GetLevel() == 0) ? + NFileHeader::NCompressionMethod::kStore : + NFileHeader::NCompressionMethod::kDeflate)); else - mainMethod = (Byte)m_MainMethod; - options.MethodSequence.Add(mainMethod); - if (mainMethod != NFileHeader::NCompressionMethod::kStored) - options.MethodSequence.Add(NFileHeader::NCompressionMethod::kStored); + mainMethod = (Byte)mainMethod; + + options.MethodSequence.Add((Byte)mainMethod); + + if (mainMethod != NFileHeader::NCompressionMethod::kStore) + options.MethodSequence.Add(NFileHeader::NCompressionMethod::kStore); + + options.Force_SeqOutMode = _force_SeqOutMode; + + CUpdateOptions uo; + uo.Write_MTime = TimeOptions.Write_MTime.Val; + uo.Write_ATime = TimeOptions.Write_ATime.Val; + uo.Write_CTime = TimeOptions.Write_CTime.Val; + /* + uo.Write_NtfsTime = _Write_NtfsTime && + (_Write_MTime || _Write_ATime || _Write_CTime); + uo.Write_UnixTime = _Write_UnixTime; + */ return Update( EXTERNAL_CODECS_VARS m_Items, updateItems, outStream, m_Archive.IsOpen() ? &m_Archive : NULL, _removeSfxBlock, - options, callback); + uo, options, callback); COM_TRY_END2 } -struct CMethodIndexToName -{ - unsigned Method; - const char *Name; -}; -static const CMethodIndexToName k_SupportedMethods[] = -{ - { NFileHeader::NCompressionMethod::kStored, "copy" }, - { NFileHeader::NCompressionMethod::kDeflated, "deflate" }, - { NFileHeader::NCompressionMethod::kDeflated64, "deflate64" }, - { NFileHeader::NCompressionMethod::kBZip2, "bzip2" }, - { NFileHeader::NCompressionMethod::kLZMA, "lzma" }, - { NFileHeader::NCompressionMethod::kPPMd, "ppmd" } -}; - -STDMETHODIMP CHandler::SetProperties(const wchar_t * const *names, const PROPVARIANT *values, UInt32 numProps) + +Z7_COM7F_IMF(CHandler::SetProperties(const wchar_t * const *names, const PROPVARIANT *values, UInt32 numProps)) { InitMethodProps(); - #ifndef _7ZIP_ST - const UInt32 numProcessors = _props.NumThreads; - #endif for (UInt32 i = 0; i < numProps; i++) { @@ -313,82 +509,35 @@ STDMETHODIMP CHandler::SetProperties(const wchar_t * const *names, const PROPVAR const PROPVARIANT &prop = values[i]; - if (name[0] == L'x') - { - UInt32 level = 9; - RINOK(ParsePropToUInt32(name.Ptr(1), prop, level)); - _props.Level = level; - _props.MethodInfo.AddProp_Level(level); - } - else if (name == L"m") - { - if (prop.vt == VT_BSTR) - { - UString m = prop.bstrVal, m2; - m.MakeLower_Ascii(); - int colonPos = m.Find(L':'); - if (colonPos >= 0) - { - m2 = m.Ptr(colonPos + 1); - m.DeleteFrom(colonPos); - } - unsigned k; - for (k = 0; k < ARRAY_SIZE(k_SupportedMethods); k++) - { - const CMethodIndexToName &pair = k_SupportedMethods[k]; - if (m.IsEqualTo(pair.Name)) - { - if (!m2.IsEmpty()) - { - RINOK(_props.MethodInfo.ParseParamsFromString(m2)); - } - m_MainMethod = pair.Method; - break; - } - } - if (k == ARRAY_SIZE(k_SupportedMethods)) - return E_INVALIDARG; - } - else if (prop.vt == VT_UI4) - { - unsigned k; - for (k = 0; k < ARRAY_SIZE(k_SupportedMethods); k++) - { - unsigned method = k_SupportedMethods[k].Method; - if (prop.ulVal == method) - { - m_MainMethod = method; - break; - } - } - if (k == ARRAY_SIZE(k_SupportedMethods)) - return E_INVALIDARG; - } - else - return E_INVALIDARG; - } - else if (name.IsPrefixedBy(L"em")) + if (name.IsEqualTo_Ascii_NoCase("em")) { if (prop.vt != VT_BSTR) return E_INVALIDARG; { - UString m = prop.bstrVal; - m.MakeLower_Ascii(); - if (m.IsPrefixedBy(L"aes")) + const wchar_t *m = prop.bstrVal; + if (IsString1PrefixedByString2_NoCase_Ascii(m, "AES")) { - m.DeleteFrontal(3); - if (m == L"128") - _props.AesKeyMode = 1; - else if (m == L"192") - _props.AesKeyMode = 2; - else if (m == L"256" || m.IsEmpty()) - _props.AesKeyMode = 3; - else - return E_INVALIDARG; + m += 3; + UInt32 v = 3; + if (*m != 0) + { + if (*m == '-') + m++; + const wchar_t *end; + v = ConvertStringToUInt32(m, &end); + if (*end != 0 || v % 64 != 0) + return E_INVALIDARG; + v /= 64; + v -= 2; + if (v >= 3) + return E_INVALIDARG; + v++; + } + _props.AesKeyMode = (Byte)v; _props.IsAesMode = true; m_ForceAesMode = true; } - else if (m == L"zipcrypto") + else if (StringsAreEqualNoCase_Ascii(m, "ZipCrypto")) { _props.IsAesMode = false; m_ForceAesMode = true; @@ -397,45 +546,80 @@ STDMETHODIMP CHandler::SetProperties(const wchar_t * const *names, const PROPVAR return E_INVALIDARG; } } - else if (name.IsPrefixedBy(L"mt")) - { - #ifndef _7ZIP_ST - RINOK(ParseMtProp(name.Ptr(2), prop, numProcessors, _props.NumThreads)); - _props.NumThreadsWasChanged = true; - #endif - } - else if (name.IsEqualTo("tc")) - { - RINOK(PROPVARIANT_to_bool(prop, m_WriteNtfsTimeExtra)); - } + + + else if (name.IsEqualTo("cl")) { - RINOK(PROPVARIANT_to_bool(prop, m_ForceLocal)); + RINOK(PROPVARIANT_to_bool(prop, m_ForceLocal)) if (m_ForceLocal) m_ForceUtf8 = false; } else if (name.IsEqualTo("cu")) { - RINOK(PROPVARIANT_to_bool(prop, m_ForceUtf8)); + RINOK(PROPVARIANT_to_bool(prop, m_ForceUtf8)) if (m_ForceUtf8) m_ForceLocal = false; } else if (name.IsEqualTo("cp")) { UInt32 cp = CP_OEMCP; - RINOK(ParsePropToUInt32(L"", prop, cp)); + RINOK(ParsePropToUInt32(L"", prop, cp)) _forceCodePage = true; _specifiedCodePage = cp; } else if (name.IsEqualTo("rsfx")) { - RINOK(PROPVARIANT_to_bool(prop, _removeSfxBlock)); + RINOK(PROPVARIANT_to_bool(prop, _removeSfxBlock)) + } + else if (name.IsEqualTo("rws")) + { + RINOK(PROPVARIANT_to_bool(prop, _force_SeqOutMode)) + } + else if (name.IsEqualTo("ros")) + { + RINOK(PROPVARIANT_to_bool(prop, _force_OpenSeq)) } else { - RINOK(_props.MethodInfo.ParseParamsFromPROPVARIANT(name, prop)); + if (name.IsEqualTo_Ascii_NoCase("m") && prop.vt == VT_UI4) + { + UInt32 id = prop.ulVal; + if (id > 0xFF) + return E_INVALIDARG; + m_MainMethod = (int)id; + } + else + { + bool processed = false; + RINOK(TimeOptions.Parse(name, prop, processed)) + if (!processed) + { + RINOK(_props.SetProperty(name, prop)) + } + } + // RINOK(_props.MethodInfo.ParseParamsFromPROPVARIANT(name, prop)); + } + } + + _props._methods.DeleteFrontal(_props.GetNumEmptyMethods()); + if (_props._methods.Size() > 1) + return E_INVALIDARG; + if (_props._methods.Size() == 1) + { + const AString &methodName = _props._methods[0].MethodName; + + if (!methodName.IsEmpty()) + { + const char *end; + UInt32 id = ConvertStringToUInt32(methodName, &end); + if (*end == 0 && id <= 0xFF) + m_MainMethod = (int)id; + else if (methodName.IsEqualTo_Ascii_NoCase("Copy")) // it's alias for "Store" + m_MainMethod = 0; } } + return S_OK; } diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipHeader.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipHeader.h index fead0192..038d49d2 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipHeader.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipHeader.h @@ -1,7 +1,7 @@ // ZipHeader.h -#ifndef __ARCHIVE_ZIP_HEADER_H -#define __ARCHIVE_ZIP_HEADER_H +#ifndef ZIP7_INC_ARCHIVE_ZIP_HEADER_H +#define ZIP7_INC_ARCHIVE_ZIP_HEADER_H #include "../../../Common/MyTypes.h" @@ -23,7 +23,8 @@ namespace NSignature } const unsigned kLocalHeaderSize = 4 + 26; // including signature -const unsigned kDataDescriptorSize = 4 + 12; // including signature +const unsigned kDataDescriptorSize32 = 4 + 4 + 4 * 2; // including signature +const unsigned kDataDescriptorSize64 = 4 + 4 + 8 * 2; // including signature const unsigned kCentralHeaderSize = 4 + 42; // including signature const unsigned kEcdSize = 22; // including signature @@ -37,28 +38,33 @@ namespace NFileHeader { enum EType { - kStored = 0, - kShrunk = 1, - kReduced1 = 2, - kReduced2 = 3, - kReduced3 = 4, - kReduced4 = 5, - kImploded = 6, - kReservedTokenizing = 7, // reserved for tokenizing - kDeflated = 8, - kDeflated64 = 9, + kStore = 0, + kShrink = 1, + kReduce1 = 2, + kReduce2 = 3, + kReduce3 = 4, + kReduce4 = 5, + kImplode = 6, + kTokenize = 7, + kDeflate = 8, + kDeflate64 = 9, kPKImploding = 10, kBZip2 = 12, + kLZMA = 14, + kTerse = 18, kLz77 = 19, + kZstdPk = 20, - kXz = 0x5F, - kJpeg = 0x60, - kWavPack = 0x61, - kPPMd = 0x62, - kWzAES = 0x63 + kZstdWz = 93, + kMP3 = 94, + kXz = 95, + kJpeg = 96, + kWavPack = 97, + kPPMd = 98, + kWzAES = 99 }; const Byte kMadeByProgramVersion = 63; @@ -73,6 +79,7 @@ namespace NFileHeader const Byte kExtractVersion_Aes = 51; const Byte kExtractVersion_LZMA = 63; const Byte kExtractVersion_PPMd = 63; + const Byte kExtractVersion_Xz = 20; // test it } namespace NExtraID @@ -81,11 +88,17 @@ namespace NFileHeader { kZip64 = 0x01, kNTFS = 0x0A, + kUnix0 = 0x0D, // Info-ZIP : (UNIX) PK kStrongEncrypt = 0x17, - kUnixTime = 0x5455, + kIzNtSecurityDescriptor = 0x4453, + kUnixTime = 0x5455, // "UT" (time) Info-ZIP + kUnix1 = 0x5855, // Info-ZIP kIzUnicodeComment = 0x6375, kIzUnicodeName = 0x7075, - kWzAES = 0x9901 + kUnix2 = 0x7855, // Info-ZIP + kUnixN = 0x7875, // Info-ZIP + kWzAES = 0x9901, + kApkAlign = 0xD935 }; } @@ -110,6 +123,15 @@ namespace NFileHeader }; } + namespace NUnixExtra + { + enum + { + kATime = 0, + kMTime + }; + } + namespace NFlags { const unsigned kEncrypted = 1 << 0; @@ -117,14 +139,17 @@ namespace NFileHeader const unsigned kDescriptorUsedMask = 1 << 3; const unsigned kStrongEncrypted = 1 << 6; const unsigned kUtf8 = 1 << 11; + const unsigned kAltStream = 1 << 14; const unsigned kImplodeDictionarySizeMask = 1 << 1; const unsigned kImplodeLiteralsOnMask = 1 << 2; + /* const unsigned kDeflateTypeBitStart = 1; const unsigned kNumDeflateTypeBits = 2; const unsigned kNumDeflateTypes = (1 << kNumDeflateTypeBits); const unsigned kDeflateTypeMask = (1 << kNumDeflateTypeBits) - 1; + */ } namespace NHostOS diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipIn.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipIn.cpp index 6361dc5c..9d77e87f 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipIn.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipIn.cpp @@ -6,12 +6,11 @@ #include "../../../Common/DynamicBuffer.h" #include "../../../Common/IntToString.h" +#include "../../../Common/MyException.h" #include "../../../Common/StringToInt.h" #include "../../../Windows/PropVariant.h" -#include "../../Common/StreamUtils.h" - #include "../IArchive.h" #include "ZipIn.h" @@ -27,6 +26,48 @@ namespace NArchive { namespace NZip { +/* we try to use same size of Buffer (1 << 17) for all tasks. + it allow to avoid reallocations and cache clearing. */ + +static const size_t kSeqBufferSize = (size_t)1 << 17; + +/* +Open() +{ + _inBufMode = false; + ReadVols() + FindCd(); + TryEcd64() + SeekToVol() + FindMarker() + _inBufMode = true; + ReadHeaders() + _inBufMode = false; + ReadCd() + FindCd() + TryEcd64() + TryReadCd() + { + SeekToVol(); + _inBufMode = true; + } + _inBufMode = true; + ReadLocals() + ReadCdItem() + .... +} +FindCd() writes to Buffer without touching (_inBufMode) +*/ + +/* + if (not defined ZIP_SELF_CHECK) : it reads CD and if error in first pass CD reading, it reads LOCALS-CD-MODE + if ( defined ZIP_SELF_CHECK) : it always reads CD and LOCALS-CD-MODE + use ZIP_SELF_CHECK to check LOCALS-CD-MODE for any zip archive +*/ + +// #define ZIP_SELF_CHECK + + struct CEcd { UInt16 ThisDisk; @@ -66,6 +107,7 @@ void CEcd::Parse(const Byte *p) void CCdInfo::ParseEcd32(const Byte *p) { + IsFromEcd64 = false; // (p) includes signature p += 4; G16(0, ThisDisk); @@ -79,6 +121,7 @@ void CCdInfo::ParseEcd32(const Byte *p) void CCdInfo::ParseEcd64e(const Byte *p) { + IsFromEcd64 = true; // (p) exclude signature G16(0, VersionMade); G16(2, VersionNeedExtract); @@ -106,9 +149,14 @@ struct CLocator G64(4, Ecd64Offset); G32(12, NumDisks); } -}; + bool IsEmptyArc() const + { + return Ecd64Disk == 0 && NumDisks == 0 && Ecd64Offset == 0; + } +}; + void CInArchive::ClearRefs() @@ -123,27 +171,208 @@ void CInArchive::ClearRefs() void CInArchive::Close() { - _processedCnt = 0; - IsArc = false; + _cnt = 0; + DisableBufMode(); + IsArcOpen = false; - IsMultiVol = false; - UseDisk_in_SingleVol = false; - EcdVolIndex = 0; + + IsArc = false; + IsZip64 = false; + + IsApk = false; + IsCdUnsorted = false; + HeadersError = false; HeadersWarning = false; ExtraMinorError = false; + UnexpectedEnd = false; + LocalsWereRead = false; + LocalsCenterMerged = false; NoCentralDir = false; - IsZip64 = false; - MarkerIsFound = false; + Overflow32bit = false; + Cd_NumEntries_Overflow_16bit = false; + MarkerIsFound = false; + MarkerIsSafe = false; + + IsMultiVol = false; + UseDisk_in_SingleVol = false; + EcdVolIndex = 0; + + ArcInfo.Clear(); + ClearRefs(); } -HRESULT CInArchive::Seek(UInt64 offset) + +HRESULT CInArchive::Seek_SavePos(UInt64 offset) +{ + // InitBuf(); + // if (!Stream) return S_FALSE; + return Stream->Seek((Int64)offset, STREAM_SEEK_SET, &_streamPos); +} + + +/* SeekToVol() will keep the cached mode, if new volIndex is + same Vols.StreamIndex volume, and offset doesn't go out of cached region */ + +HRESULT CInArchive::SeekToVol(int volIndex, UInt64 offset) +{ + if (volIndex != Vols.StreamIndex) + { + if (IsMultiVol && volIndex >= 0) + { + if ((unsigned)volIndex >= Vols.Streams.Size()) + return S_FALSE; + if (!Vols.Streams[(unsigned)volIndex].Stream) + return S_FALSE; + Stream = Vols.Streams[(unsigned)volIndex].Stream; + } + else if (volIndex == -2) + { + if (!Vols.ZipStream) + return S_FALSE; + Stream = Vols.ZipStream; + } + else + Stream = StartStream; + Vols.StreamIndex = volIndex; + } + else + { + if (offset <= _streamPos) + { + const UInt64 back = _streamPos - offset; + if (back <= _bufCached) + { + _bufPos = _bufCached - (size_t)back; + return S_OK; + } + } + } + InitBuf(); + return Seek_SavePos(offset); +} + + +HRESULT CInArchive::AllocateBuffer(size_t size) +{ + if (size <= Buffer.Size()) + return S_OK; + /* in cached mode virtual_pos is not equal to phy_pos (_streamPos) + so we change _streamPos and do Seek() to virtual_pos before cache clearing */ + if (_bufPos != _bufCached) + { + RINOK(Seek_SavePos(GetVirtStreamPos())) + } + InitBuf(); + Buffer.AllocAtLeast(size); + if (!Buffer.IsAllocated()) + return E_OUTOFMEMORY; + return S_OK; +} + +// ---------- ReadFromCache ---------- +// reads from cache and from Stream +// move to next volume can be allowed if (CanStartNewVol) and only before first byte reading + +HRESULT CInArchive::ReadFromCache(Byte *data, unsigned size, unsigned &processed) +{ + HRESULT result = S_OK; + processed = 0; + + for (;;) + { + if (size == 0) + return S_OK; + + const size_t avail = GetAvail(); + + if (avail != 0) + { + unsigned cur = size; + if (cur > avail) + cur = (unsigned)avail; + memcpy(data, (const Byte *)Buffer + _bufPos, cur); + + data += cur; + size -= cur; + processed += cur; + + _bufPos += cur; + _cnt += cur; + + CanStartNewVol = false; + + continue; + } + + InitBuf(); + + if (_inBufMode) + { + UInt32 cur = 0; + result = Stream->Read(Buffer, (UInt32)Buffer.Size(), &cur); + _bufPos = 0; + _bufCached = cur; + _streamPos += cur; + if (cur != 0) + CanStartNewVol = false; + if (result != S_OK) + break; + if (cur != 0) + continue; + } + else + { + size_t cur = size; + result = ReadStream(Stream, data, &cur); + data += cur; + size -= (unsigned)cur; + processed += (unsigned)cur; + _streamPos += cur; + _cnt += cur; + if (cur != 0) + { + CanStartNewVol = false; + break; + } + if (result != S_OK) + break; + } + + if ( !IsMultiVol + || !CanStartNewVol + || Vols.StreamIndex < 0 + || (unsigned)Vols.StreamIndex + 1 >= Vols.Streams.Size()) + break; + + const CVols::CSubStreamInfo &s = Vols.Streams[(unsigned)Vols.StreamIndex + 1]; + if (!s.Stream) + break; + result = s.SeekToStart(); + if (result != S_OK) + break; + Vols.StreamIndex++; + _streamPos = 0; + // Vols.NeedSeek = false; + + Stream = s.Stream; + } + + return result; +} + + +HRESULT CInArchive::ReadFromCache_FALSE(Byte *data, unsigned size) { - return Stream->Seek(offset, STREAM_SEEK_SET, NULL); + unsigned processed; + HRESULT res = ReadFromCache(data, size, processed); + if (res == S_OK && size != processed) + return S_FALSE; + return res; } @@ -168,18 +397,33 @@ API_FUNC_IsArc IsArc_Zip(const Byte *p, size_t size) if (p[0] != 'P') return k_IsArc_Res_NO; - UInt32 value = Get32(p); + UInt32 sig = Get32(p); - if (value == NSignature::kNoSpan - || value == NSignature::kSpan) + if (sig == NSignature::kNoSpan || sig == NSignature::kSpan) { p += 4; size -= 4; } - value = Get32(p); + sig = Get32(p); + + if (sig == NSignature::kEcd64) + { + if (size < kEcd64_FullSize) + return k_IsArc_Res_NEED_MORE; - if (value == NSignature::kEcd) + const UInt64 recordSize = Get64(p + 4); + if ( recordSize < kEcd64_MainSize + || recordSize > kEcd64_MainSize + (1 << 20)) + return k_IsArc_Res_NO; + CCdInfo cdInfo; + cdInfo.ParseEcd64e(p + 12); + if (!cdInfo.IsEmptyArc()) + return k_IsArc_Res_NO; + return k_IsArc_Res_YES; // k_IsArc_Res_YES_2; + } + + if (sig == NSignature::kEcd) { if (size < kEcdSize) return k_IsArc_Res_NEED_MORE; @@ -190,8 +434,8 @@ API_FUNC_IsArc IsArc_Zip(const Byte *p, size_t size) return k_IsArc_Res_NO; return k_IsArc_Res_YES; // k_IsArc_Res_YES_2; } - - if (value != NSignature::kLocalFileHeader) + + if (sig != NSignature::kLocalFileHeader) return k_IsArc_Res_NO; if (size < kLocalHeaderSize) @@ -228,8 +472,12 @@ API_FUNC_IsArc IsArc_Zip(const Byte *p, size_t size) const unsigned nameSize = Get16(p + 22); unsigned extraSize = Get16(p + 24); const UInt32 extraOffset = kLocalHeaderSize + (UInt32)nameSize; + + /* + // 21.02: fixed. we don't use the following check if (extraOffset + extraSize > (1 << 16)) return k_IsArc_Res_NO; + */ p -= 4; @@ -240,8 +488,17 @@ API_FUNC_IsArc IsArc_Zip(const Byte *p, size_t size) const Byte *p2 = p + kLocalHeaderSize; for (size_t i = 0; i < rem; i++) if (p2[i] == 0) + { + // we support some "bad" zip archives that contain zeros after name + for (size_t k = i + 1; k < rem; k++) + if (p2[k] != 0) + return k_IsArc_Res_NO; + break; + /* if (i != nameSize - 1) return k_IsArc_Res_NO; + */ + } } if (size < extraOffset) @@ -255,7 +512,7 @@ API_FUNC_IsArc IsArc_Zip(const Byte *p, size_t size) { if (extraSize < 4) { - // 7-Zip before 9.31 created incorrect WsAES Extra in folder's local headers. + // 7-Zip before 9.31 created incorrect WzAES Extra in folder's local headers. // so we return k_IsArc_Res_YES to support such archives. // return k_IsArc_Res_NO; // do we need to support such extra ? return k_IsArc_Res_YES; @@ -267,7 +524,16 @@ API_FUNC_IsArc IsArc_Zip(const Byte *p, size_t size) extraSize -= 4; p += 4; if (dataSize > extraSize) - return k_IsArc_Res_NO; + { + // It can be error on header. + // We want to support such rare case bad archives. + // We use additional checks to reduce false-positive probability. + if (nameSize == 0 + || nameSize > (1 << 9) + || extraSize > (1 << 9)) + return k_IsArc_Res_NO; + return k_IsArc_Res_YES; + } if (dataSize > size) return k_IsArc_Res_NEED_MORE; size -= dataSize; @@ -288,398 +554,629 @@ static UInt32 IsArc_Zip_2(const Byte *p, size_t size, bool isFinal) } + +/* FindPK_4() is allowed to access data up to and including &limit[3]. + limit[4] access is not allowed. + return: + (return_ptr < limit) : "PK" was found at (return_ptr) + (return_ptr >= limit) : limit was reached or crossed. So no "PK" found before limit +*/ +Z7_NO_INLINE +static const Byte *FindPK_4(const Byte *p, const Byte *limit) +{ + for (;;) + { + for (;;) + { + if (p >= limit) + return limit; + Byte b = p[1]; + if (b == 0x4B) { if (p[0] == 0x50) { return p; } p += 1; break; } + if (b == 0x50) { if (p[2] == 0x4B) { return p + 1; } p += 2; break; } + b = p[3]; + p += 4; + if (b == 0x4B) { if (p[-2]== 0x50) { return p - 2; } p -= 1; break; } + if (b == 0x50) { if (p[0] == 0x4B) { return p - 1; } break; } + } + } + /* + for (;;) + { + for (;;) + { + if (p >= limit) + return limit; + if (*p++ == 0x50) break; + if (*p++ == 0x50) break; + if (*p++ == 0x50) break; + if (*p++ == 0x50) break; + } + if (*p == 0x4B) + return p - 1; + } + */ +} + + +/* +---------- FindMarker ---------- +returns: + S_OK: + ArcInfo.MarkerVolIndex : volume of marker + ArcInfo.MarkerPos : Pos of first signature + ArcInfo.MarkerPos2 : Pos of main signature (local item signature in most cases) + _streamPos : stream pos + _cnt : The number of virtal Bytes after start of search to offset after signature + _signature : main signature + + S_FALSE: can't find marker, or there is some non-zip data after marker -HRESULT CInArchive::FindMarker(IInStream *stream, const UInt64 *searchLimit) + Error code: stream reading error. +*/ + +HRESULT CInArchive::FindMarker(const UInt64 *searchLimit) { - ArcInfo.MarkerPos = m_Position; - ArcInfo.MarkerPos2 = m_Position; + ArcInfo.MarkerPos = GetVirtStreamPos(); + ArcInfo.MarkerPos2 = ArcInfo.MarkerPos; + ArcInfo.MarkerVolIndex = Vols.StreamIndex; + + _cnt = 0; + + CanStartNewVol = false; if (searchLimit && *searchLimit == 0) { Byte startBuf[kMarkerSize]; - { - size_t processed = kMarkerSize; - RINOK(ReadStream(stream, startBuf, &processed)); - m_Position += processed; - if (processed != kMarkerSize) - return S_FALSE; - } + RINOK(ReadFromCache_FALSE(startBuf, kMarkerSize)) - m_Signature = Get32(startBuf); + UInt32 marker = Get32(startBuf); + _signature = marker; - if (m_Signature != NSignature::kEcd && - m_Signature != NSignature::kLocalFileHeader) + if ( marker == NSignature::kNoSpan + || marker == NSignature::kSpan) { - if (m_Signature != NSignature::kNoSpan) - { - if (m_Signature != NSignature::kSpan) - return S_FALSE; - if (m_Position != 4) // we don't support multivol archives with sfx stub - return S_FALSE; - ArcInfo.IsSpanMode = true; - } - size_t processed = kMarkerSize; - RINOK(ReadStream(stream, startBuf, &processed)); - m_Position += processed; - if (processed != kMarkerSize) - return S_FALSE; - m_Signature = Get32(startBuf); - if (m_Signature != NSignature::kEcd && - m_Signature != NSignature::kLocalFileHeader) - return S_FALSE; - ArcInfo.MarkerPos2 += 4; + RINOK(ReadFromCache_FALSE(startBuf, kMarkerSize)) + _signature = Get32(startBuf); } + + if ( _signature != NSignature::kEcd + && _signature != NSignature::kEcd64 + && _signature != NSignature::kLocalFileHeader) + return S_FALSE; + + ArcInfo.MarkerPos2 = GetVirtStreamPos() - 4; + ArcInfo.IsSpanMode = (marker == NSignature::kSpan); // we use weak test in case of (*searchLimit == 0) // since error will be detected later in Open function - return S_OK; // maybe we need to search backward. + return S_OK; } - const size_t kBufSize = (size_t)1 << 18; // must be larger than kCheckSize - const size_t kCheckSize = (size_t)1 << 16; // must be smaller than kBufSize - CByteArr buffer(kBufSize); - - size_t numBytesInBuffer = 0; - UInt64 curScanPos = 0; + // zip specification: (_zip_header_size < (1 << 16)) + // so we need such size to check header + const size_t kCheckSize = (size_t)1 << 16; + const size_t kBufSize = (size_t)1 << 17; // (kBufSize must be > kCheckSize) + + RINOK(AllocateBuffer(kBufSize)) + + _inBufMode = true; + + UInt64 progressPrev = 0; for (;;) { - size_t numReadBytes = kBufSize - numBytesInBuffer; - RINOK(ReadStream(stream, buffer + numBytesInBuffer, &numReadBytes)); - m_Position += numReadBytes; - numBytesInBuffer += numReadBytes; - const bool isFinished = (numBytesInBuffer != kBufSize); + RINOK(LookAhead(kBufSize)) + + const size_t avail = GetAvail(); - size_t limit = numBytesInBuffer;; + size_t limitPos; + // (avail > kBufSize) is possible, if (Buffer.Size() > kBufSize) + const bool isFinished = (avail < kBufSize); if (isFinished) { - if (limit == 0) - break; - limit--; + const unsigned kMinAllowed = 4; + if (avail <= kMinAllowed) + { + if ( !IsMultiVol + || Vols.StreamIndex < 0 + || (unsigned)Vols.StreamIndex + 1 >= Vols.Streams.Size()) + break; + + SkipLookahed(avail); + + const CVols::CSubStreamInfo &s = Vols.Streams[(unsigned)Vols.StreamIndex + 1]; + if (!s.Stream) + break; + + RINOK(s.SeekToStart()) + + InitBuf(); + Vols.StreamIndex++; + _streamPos = 0; + Stream = s.Stream; + continue; + } + limitPos = avail - kMinAllowed; } else - limit -= kCheckSize; + limitPos = (avail - kCheckSize); - if (searchLimit && curScanPos + limit > *searchLimit) - limit = (size_t)(*searchLimit - curScanPos + 1); + // we don't check at (limitPos) for good fast aligned operations - if (limit < 1) + if (searchLimit) + { + if (_cnt > *searchLimit) + break; + UInt64 rem = *searchLimit - _cnt; + if (limitPos > rem) + limitPos = (size_t)rem + 1; + } + + if (limitPos == 0) break; - const Byte *buf = buffer; - for (size_t pos = 0; pos < limit; pos++) + const Byte * const pStart = Buffer + _bufPos; + const Byte * p = pStart; + const Byte * const limit = pStart + limitPos; + + for (;; p++) { - if (buf[pos] != 0x50) - continue; - if (buf[pos + 1] != 0x4B) - continue; - size_t rem = numBytesInBuffer - pos; - UInt32 res = IsArc_Zip_2(buf + pos, rem, isFinished); + p = FindPK_4(p, limit); + if (p >= limit) + break; + size_t rem = (size_t)(pStart + avail - p); + /* 22.02 : we limit check size with kCheckSize to be consistent for + any different combination of _bufPos in Buffer and size of Buffer. */ + if (rem > kCheckSize) + rem = kCheckSize; + const UInt32 res = IsArc_Zip_2(p, rem, isFinished); if (res != k_IsArc_Res_NO) { if (rem < kMarkerSize) return S_FALSE; - m_Signature = Get32(buf + pos); - ArcInfo.MarkerPos += curScanPos + pos; + _signature = Get32(p); + SkipLookahed((size_t)(p - pStart)); + ArcInfo.MarkerVolIndex = Vols.StreamIndex; + ArcInfo.MarkerPos = GetVirtStreamPos(); ArcInfo.MarkerPos2 = ArcInfo.MarkerPos; - if (m_Signature == NSignature::kNoSpan - || m_Signature == NSignature::kSpan) + SkipLookahed(4); + if ( _signature == NSignature::kNoSpan + || _signature == NSignature::kSpan) { - m_Signature = Get32(buf + pos + 4); + if (rem < kMarkerSize * 2) + return S_FALSE; + ArcInfo.IsSpanMode = (_signature == NSignature::kSpan); + _signature = Get32(p + 4); ArcInfo.MarkerPos2 += 4; + SkipLookahed(4); } - m_Position = ArcInfo.MarkerPos2 + kMarkerSize; return S_OK; } } - if (isFinished) + if (!IsMultiVol && isFinished) break; - curScanPos += limit; - numBytesInBuffer -= limit; - memmove(buffer, buffer + limit, numBytesInBuffer); + SkipLookahed((size_t)(p - pStart)); + + if (Callback && (_cnt - progressPrev) >= ((UInt32)1 << 23)) + { + progressPrev = _cnt; + // const UInt64 numFiles64 = 0; + RINOK(Callback->SetCompleted(NULL, &_cnt)) + } } return S_FALSE; } -HRESULT CInArchive::IncreaseRealPosition(Int64 addValue, bool &isFinished) +/* +---------- IncreaseRealPosition ---------- +moves virtual offset in virtual stream. +changing to new volumes is allowed +*/ + +HRESULT CInArchive::IncreaseRealPosition(UInt64 offset, bool &isFinished) { isFinished = false; + + for (;;) + { + const size_t avail = GetAvail(); + + if (offset <= avail) + { + _bufPos += (size_t)offset; + _cnt += offset; + return S_OK; + } + + _cnt += avail; + offset -= avail; + + _bufCached = 0; + _bufPos = 0; + + if (!_inBufMode) + break; + + CanStartNewVol = true; + LookAhead(1); + + if (GetAvail() == 0) + return S_OK; + } + + // cache is empty + if (!IsMultiVol) - return Stream->Seek(addValue, STREAM_SEEK_CUR, &m_Position); + { + _cnt += offset; + return Stream->Seek((Int64)offset, STREAM_SEEK_CUR, &_streamPos); + } for (;;) { - if (addValue == 0) + if (offset == 0) return S_OK; - if (addValue > 0) + + if (Vols.StreamIndex < 0) + return S_FALSE; + if ((unsigned)Vols.StreamIndex >= Vols.Streams.Size()) { - if (Vols.StreamIndex < 0) - return S_FALSE; - if ((unsigned)Vols.StreamIndex >= Vols.Streams.Size()) + isFinished = true; + return S_OK; + } + { + const CVols::CSubStreamInfo &s = Vols.Streams[(unsigned)Vols.StreamIndex]; + if (!s.Stream) { isFinished = true; return S_OK; } + if (_streamPos > s.Size) + return S_FALSE; + const UInt64 rem = s.Size - _streamPos; + if ((UInt64)offset <= rem) { - const CVols::CSubStreamInfo &s = Vols.Streams[Vols.StreamIndex]; - if (!s.Stream) - { - isFinished = true; - return S_OK; - } - if (m_Position > s.Size) - return S_FALSE; - UInt64 rem = s.Size - m_Position; - if ((UInt64)addValue <= rem) - return Stream->Seek(addValue, STREAM_SEEK_CUR, &m_Position); - RINOK(Stream->Seek(s.Size, STREAM_SEEK_SET, &m_Position)); - addValue -= rem; - Stream = NULL; - Vols.StreamIndex++; - if ((unsigned)Vols.StreamIndex >= Vols.Streams.Size()) - { - isFinished = true; - return S_OK; - } - } - const CVols::CSubStreamInfo &s2 = Vols.Streams[Vols.StreamIndex]; - if (!s2.Stream) - { - isFinished = true; - return S_OK; + _cnt += offset; + return Stream->Seek((Int64)offset, STREAM_SEEK_CUR, &_streamPos); } - Stream = s2.Stream; - m_Position = 0; - RINOK(Stream->Seek(0, STREAM_SEEK_SET, &m_Position)); + RINOK(Seek_SavePos(s.Size)) + offset -= rem; + _cnt += rem; } - else + + Stream = NULL; + _streamPos = 0; + Vols.StreamIndex++; + if ((unsigned)Vols.StreamIndex >= Vols.Streams.Size()) { - if (!Stream) - return S_FALSE; - { - if (m_Position >= (UInt64)(-addValue)) - return Stream->Seek(addValue, STREAM_SEEK_CUR, &m_Position); - addValue += m_Position; - RINOK(Stream->Seek(0, STREAM_SEEK_SET, &m_Position)); - m_Position = 0; - Stream = NULL; - if (--Vols.StreamIndex < 0) - return S_FALSE; - } - const CVols::CSubStreamInfo &s2 = Vols.Streams[Vols.StreamIndex]; - if (!s2.Stream) - return S_FALSE; - Stream = s2.Stream; - m_Position = s2.Size; - RINOK(Stream->Seek(s2.Size, STREAM_SEEK_SET, &m_Position)); + isFinished = true; + return S_OK; } + const CVols::CSubStreamInfo &s2 = Vols.Streams[(unsigned)Vols.StreamIndex]; + if (!s2.Stream) + { + isFinished = true; + return S_OK; + } + Stream = s2.Stream; + RINOK(Seek_SavePos(0)) } } -class CUnexpectEnd {}; +/* +---------- LookAhead ---------- +Reads data to buffer, if required. -HRESULT CInArchive::ReadBytes(void *data, UInt32 size, UInt32 *processedSize) -{ - size_t realProcessedSize = size; - HRESULT result = S_OK; - if (_inBufMode) - { - try { realProcessedSize = _inBuffer.ReadBytes((Byte *)data, size); } - catch (const CInBufferException &e) { return e.ErrorCode; } - } - else - result = ReadStream(Stream, data, &realProcessedSize); - if (processedSize) - *processedSize = (UInt32)realProcessedSize; - m_Position += realProcessedSize; - return result; -} +It can read from volumes as long as Buffer.Size(). +But it moves to new volume, only if it's required to provide minRequired bytes in buffer. -void CInArchive::SafeReadBytes(void *data, unsigned size) -{ - size_t processed = size; - - HRESULT result = S_OK; +in: + (minRequired <= Buffer.Size()) - if (!_inBufMode) - result = ReadStream(Stream, data, &processed); - else +return: + S_OK : if (GetAvail() < minRequired) after function return, it's end of stream(s) data, or no new volume stream. + Error codes: IInStream::Read() error or IInStream::Seek() error for multivol +*/ + +HRESULT CInArchive::LookAhead(size_t minRequired) +{ + for (;;) { - for (;;) + const size_t avail = GetAvail(); + + if (minRequired <= avail) + return S_OK; + + if (_bufPos != 0) { - processed = _inBuffer.ReadBytes((Byte *)data, size); - if (processed != 0 - || IsMultiVol - || !CanStartNewVol - || Vols.StreamIndex < 0 - || (unsigned)Vols.StreamIndex >= Vols.Streams.Size()) - break; - Vols.StreamIndex++; - const CVols::CSubStreamInfo &s = Vols.Streams[Vols.StreamIndex]; - if (!s.Stream) - break; - // if (Vols.NeedSeek) - { - result = s.Stream->Seek(0, STREAM_SEEK_SET, NULL); - m_Position = 0; - if (result != S_OK) - break; - Vols.NeedSeek = false; - } - _inBuffer.SetStream(s.Stream); - _inBuffer.Init(); + if (avail != 0) + memmove(Buffer, Buffer + _bufPos, avail); + _bufPos = 0; + _bufCached = avail; } - CanStartNewVol = false; + + const size_t pos = _bufCached; + UInt32 processed = 0; + HRESULT res = Stream->Read(Buffer + pos, (UInt32)(Buffer.Size() - pos), &processed); + _streamPos += processed; + _bufCached += processed; + + if (res != S_OK) + return res; + + if (processed != 0) + continue; + + if ( !IsMultiVol + || !CanStartNewVol + || Vols.StreamIndex < 0 + || (unsigned)Vols.StreamIndex + 1 >= Vols.Streams.Size()) + return S_OK; + + const CVols::CSubStreamInfo &s = Vols.Streams[(unsigned)Vols.StreamIndex + 1]; + if (!s.Stream) + return S_OK; + + RINOK(s.SeekToStart()) + + Vols.StreamIndex++; + _streamPos = 0; + Stream = s.Stream; + // Vols.NeedSeek = false; } +} + + +class CUnexpectEnd {}; + + +/* +---------- SafeRead ---------- + +reads data of exact size from stream(s) - m_Position += processed; - _processedCnt += processed; +in: + _inBufMode + if (CanStartNewVol) it can go to next volume before first byte reading, if there is end of volume data. +in, out: + _streamPos : position in Stream + Stream + Vols : if (IsMultiVol) + _cnt + +out: + (CanStartNewVol == false), if some data was read + +return: + S_OK : success reading of requested data + +exceptions: + CSystemException() - stream reading error + CUnexpectEnd() : could not read data of requested size +*/ + +void CInArchive::SafeRead(Byte *data, unsigned size) +{ + unsigned processed; + HRESULT result = ReadFromCache(data, size, processed); if (result != S_OK) throw CSystemException(result); - - if (processed != size) + if (size != processed) throw CUnexpectEnd(); } void CInArchive::ReadBuffer(CByteBuffer &buffer, unsigned size) { buffer.Alloc(size); - if (size > 0) - SafeReadBytes(buffer, size); + if (size != 0) + SafeRead(buffer, size); } -Byte CInArchive::ReadByte() +// Byte CInArchive::ReadByte () { Byte b; SafeRead(&b, 1); return b; } +// UInt16 CInArchive::ReadUInt16() { Byte buf[2]; SafeRead(buf, 2); return Get16(buf); } +UInt32 CInArchive::ReadUInt32() { Byte buf[4]; SafeRead(buf, 4); return Get32(buf); } +UInt64 CInArchive::ReadUInt64() { Byte buf[8]; SafeRead(buf, 8); return Get64(buf); } + +void CInArchive::ReadSignature() { - Byte b; - SafeReadBytes(&b, 1); - return b; + CanStartNewVol = true; + _signature = ReadUInt32(); + // CanStartNewVol = false; // it's already changed in SafeRead } -UInt16 CInArchive::ReadUInt16() { Byte buf[2]; SafeReadBytes(buf, 2); return Get16(buf); } -UInt32 CInArchive::ReadUInt32() { Byte buf[4]; SafeReadBytes(buf, 4); return Get32(buf); } -UInt64 CInArchive::ReadUInt64() { Byte buf[8]; SafeReadBytes(buf, 8); return Get64(buf); } -// we use Skip() inside headers only, so no need for stream change in multivol. +// we Skip() inside headers only, so no need for stream change in multivol. -void CInArchive::Skip(unsigned num) +void CInArchive::Skip(size_t num) { - if (_inBufMode) - { - size_t skip = _inBuffer.Skip(num); - m_Position += skip; - _processedCnt += skip; - if (skip != num) - throw CUnexpectEnd(); - } - else + while (num != 0) { - for (unsigned i = 0; i < num; i++) - ReadByte(); + const unsigned kBufSize = (size_t)1 << 10; + Byte buf[kBufSize]; + unsigned step = kBufSize; + if (step > num) + step = (unsigned)num; + SafeRead(buf, step); + num -= step; } } -void CInArchive::Skip64(UInt64 num) +/* +HRESULT CInArchive::Callback_Completed(unsigned numFiles) +{ + const UInt64 numFiles64 = numFiles; + return Callback->SetCompleted(&numFiles64, &_cnt); +} +*/ + +HRESULT CInArchive::Skip64(UInt64 num, unsigned numFiles) { - for (UInt64 i = 0; i < num; i++) - ReadByte(); + if (num == 0) + return S_OK; + + for (;;) + { + size_t step = (size_t)1 << 24; + if (step > num) + step = (size_t)num; + Skip(step); + num -= step; + if (num == 0) + return S_OK; + if (Callback) + { + const UInt64 numFiles64 = numFiles; + RINOK(Callback->SetCompleted(&numFiles64, &_cnt)) + } + } } -void CInArchive::ReadFileName(unsigned size, AString &s) +bool CInArchive::ReadFileName(unsigned size, AString &s) { if (size == 0) { s.Empty(); - return; + return true; + } + char *p = s.GetBuf(size); + SafeRead((Byte *)p, size); + unsigned i = size; + do + { + if (p[i - 1] != 0) + break; } - SafeReadBytes(s.GetBuf(size), size); + while (--i); s.ReleaseBuf_CalcLen(size); + return s.Len() == i; } -bool CInArchive::ReadExtra(unsigned extraSize, CExtraBlock &extraBlock, - UInt64 &unpackSize, UInt64 &packSize, UInt64 &localHeaderOffset, UInt32 &diskStartNumber) +#define ZIP64_IS_32_MAX(n) ((n) == 0xFFFFFFFF) +#define ZIP64_IS_16_MAX(n) ((n) == 0xFFFF) + + +bool CInArchive::ReadExtra(const CLocalItem &item, unsigned extraSize, CExtraBlock &extra, + UInt64 &unpackSize, UInt64 &packSize, + CItem *cdItem) { - extraBlock.Clear(); - - UInt32 remain = extraSize; + extra.Clear(); - while (remain >= 4) + while (extraSize >= 4) { CExtraSubBlock subBlock; - subBlock.ID = ReadUInt16(); - unsigned dataSize = ReadUInt16(); - remain -= 4; - if (dataSize > remain) // it's bug + const UInt32 pair = ReadUInt32(); + subBlock.ID = (pair & 0xFFFF); + unsigned size = (unsigned)(pair >> 16); + // const unsigned origSize = size; + + extraSize -= 4; + + if (size > extraSize) { + // it's error in extra HeadersWarning = true; - Skip(remain); + extra.Error = true; + Skip(extraSize); return false; } + + extraSize -= size; + if (subBlock.ID == NFileHeader::NExtraID::kZip64) { - if (unpackSize == 0xFFFFFFFF) + extra.IsZip64 = true; + bool isOK = true; + + if (!cdItem + && size == 16 + && !ZIP64_IS_32_MAX(unpackSize) + && !ZIP64_IS_32_MAX(packSize)) + { + /* Win10 Explorer's "Send to Zip" for big (3500 MiB) files + creates Zip64 Extra in local file header. + But if both uncompressed and compressed sizes are smaller than 4 GiB, + Win10 doesn't store 0xFFFFFFFF in 32-bit fields as expected by zip specification. + 21.04: we ignore these minor errors in Win10 zip archives. */ + if (ReadUInt64() != unpackSize) + isOK = false; + if (ReadUInt64() != packSize) + isOK = false; + size = 0; + } + else { - if (dataSize < 8) + if (ZIP64_IS_32_MAX(unpackSize)) + { if (size < 8) isOK = false; else { size -= 8; unpackSize = ReadUInt64(); }} + + if (isOK && ZIP64_IS_32_MAX(packSize)) + { if (size < 8) isOK = false; else { size -= 8; packSize = ReadUInt64(); }} + + if (cdItem) { - HeadersWarning = true; - Skip(remain); - return false; + if (isOK) + { + if (ZIP64_IS_32_MAX(cdItem->LocalHeaderPos)) + { if (size < 8) isOK = false; else { size -= 8; cdItem->LocalHeaderPos = ReadUInt64(); }} + /* + else if (size == 8) + { + size -= 8; + const UInt64 v = ReadUInt64(); + // soong_zip, an AOSP tool (written in the Go) writes incorrect value. + // we can ignore that minor error here + if (v != cdItem->LocalHeaderPos) + isOK = false; // ignore error + // isOK = false; // force error + } + */ + } + + if (isOK && ZIP64_IS_16_MAX(cdItem->Disk)) + { if (size < 4) isOK = false; else { size -= 4; cdItem->Disk = ReadUInt32(); }} } - unpackSize = ReadUInt64(); - remain -= 8; - dataSize -= 8; - } - if (packSize == 0xFFFFFFFF) - { - if (dataSize < 8) - break; - packSize = ReadUInt64(); - remain -= 8; - dataSize -= 8; } - if (localHeaderOffset == 0xFFFFFFFF) - { - if (dataSize < 8) - break; - localHeaderOffset = ReadUInt64(); - remain -= 8; - dataSize -= 8; - } - if (diskStartNumber == 0xFFFF) + + // we can ignore errors, when some zip archiver still write all fields to zip64 extra in local header + // if (&& (cdItem || !isOK || origSize != 8 * 3 + 4 || size != 8 * 1 + 4)) + if (!isOK || size != 0) { - if (dataSize < 4) - break; - diskStartNumber = ReadUInt32(); - remain -= 4; - dataSize -= 4; + HeadersWarning = true; + extra.Error = true; + extra.IsZip64_Error = true; } - Skip(dataSize); + Skip(size); } else { - ReadBuffer(subBlock.Data, dataSize); - extraBlock.SubBlocks.Add(subBlock); + ReadBuffer(subBlock.Data, size); + extra.SubBlocks.Add(subBlock); + if (subBlock.ID == NFileHeader::NExtraID::kIzUnicodeName) + { + if (!subBlock.CheckIzUnicode(item.Name)) + extra.Error = true; + } } - remain -= dataSize; } - if (remain != 0) + if (extraSize != 0) { ExtraMinorError = true; - // 7-Zip before 9.31 created incorrect WsAES Extra in folder's local headers. + extra.MinorError = true; + // 7-Zip before 9.31 created incorrect WzAES Extra in folder's local headers. // so we don't return false, but just set warning flag // return false; + Skip(extraSize); } - - Skip(remain); + return true; } @@ -688,10 +1185,10 @@ bool CInArchive::ReadLocalItem(CItemEx &item) { item.Disk = 0; if (IsMultiVol && Vols.StreamIndex >= 0) - item.Disk = Vols.StreamIndex; + item.Disk = (UInt32)Vols.StreamIndex; const unsigned kPureHeaderSize = kLocalHeaderSize - 4; Byte p[kPureHeaderSize]; - SafeReadBytes(p, kPureHeaderSize); + SafeRead(p, kPureHeaderSize); { unsigned i; for (i = 0; i < kPureHeaderSize && p[i] == 0; i++); @@ -709,8 +1206,9 @@ bool CInArchive::ReadLocalItem(CItemEx &item) G32(18, item.Size); const unsigned nameSize = Get16(p + 22); const unsigned extraSize = Get16(p + 24); - ReadFileName(nameSize, item.Name); + bool isOkName = ReadFileName(nameSize, item.Name); item.LocalFullHeaderSize = kLocalHeaderSize + (UInt32)nameSize + extraSize; + item.DescriptorWasRead = false; /* if (item.IsDir()) @@ -719,10 +1217,7 @@ bool CInArchive::ReadLocalItem(CItemEx &item) if (extraSize > 0) { - UInt64 localHeaderOffset = 0; - UInt32 diskStartNumber = 0; - if (!ReadExtra(extraSize, item.LocalExtra, item.Size, item.PackSize, - localHeaderOffset, diskStartNumber)) + if (!ReadExtra(item, extraSize, item.LocalExtra, item.Size, item.PackSize, NULL)) { /* Most of archives are OK for Extra. But there are some rare cases that have error. And if error in first item, it can't open archive. @@ -739,39 +1234,46 @@ bool CInArchive::ReadLocalItem(CItemEx &item) if (item.Name.Len() != nameSize) { - // we support "bad" archives with null-terminated name. - if (item.Name.Len() + 1 != nameSize) + // we support some "bad" zip archives that contain zeros after name + if (!isOkName) return false; HeadersWarning = true; } - return item.LocalFullHeaderSize <= ((UInt32)1 << 16); + // return item.LocalFullHeaderSize <= ((UInt32)1 << 16); + return true; } -static bool FlagsAreSame(const CItem &i1, const CItem &i2) +static bool FlagsAreSame(const CItem &i1, const CItem &i2_cd) { - if (i1.Method != i2.Method) + if (i1.Method != i2_cd.Method) return false; - if (i1.Flags == i2.Flags) + + UInt32 mask = i1.Flags ^ i2_cd.Flags; + if (mask == 0) return true; - UInt32 mask = 0xFFFF; switch (i1.Method) { - case NFileHeader::NCompressionMethod::kDeflated: - mask = 0x7FF9; + case NFileHeader::NCompressionMethod::kDeflate: + mask &= 0x7FF9; break; default: - if (i1.Method <= NFileHeader::NCompressionMethod::kImploded) - mask = 0x7FFF; + if (i1.Method <= NFileHeader::NCompressionMethod::kImplode) + mask &= 0x7FFF; } - // we can ignore utf8 flag, if name is ascii - if ((i1.Flags ^ i2.Flags) & NFileHeader::NFlags::kUtf8) - if (i1.Name.IsAscii() && i2.Name.IsAscii()) + // we can ignore utf8 flag, if name is ascii, or if only cdItem has utf8 flag + if (mask & NFileHeader::NFlags::kUtf8) + if ((i1.Name.IsAscii() && i2_cd.Name.IsAscii()) + || (i2_cd.Flags & NFileHeader::NFlags::kUtf8)) mask &= ~NFileHeader::NFlags::kUtf8; + + // some bad archive in rare case can use descriptor without descriptor flag in Central Dir + // if (i1.HasDescriptor()) + mask &= ~NFileHeader::NFlags::kDescriptorUsedMask; - return ((i1.Flags & mask) == (i2.Flags & mask)); + return (mask == 0); } @@ -801,13 +1303,13 @@ static bool AreEqualPaths_IgnoreSlashes(const char *s1, const char *s2) static bool AreItemsEqual(const CItemEx &localItem, const CItemEx &cdItem) { - if (!FlagsAreSame(cdItem, localItem)) + if (!FlagsAreSame(localItem, cdItem)) return false; if (!localItem.HasDescriptor()) { - if (cdItem.Crc != localItem.Crc || - cdItem.PackSize != localItem.PackSize || - cdItem.Size != localItem.Size) + if (cdItem.PackSize != localItem.PackSize + || cdItem.Size != localItem.Size + || (cdItem.Crc != localItem.Crc && cdItem.Crc != 0)) // some program writes 0 to crc field in central directory return false; } /* pkzip 2.50 creates incorrect archives. It uses @@ -833,7 +1335,8 @@ static bool AreItemsEqual(const CItemEx &localItem, const CItemEx &cdItem) // pkzip 2.50 uses DOS encoding in central dir and WIN encoding in local header. // so we ignore that error if (hostOs != NFileHeader::NHostOS::kFAT - || cdItem.MadeByVersion.Version != 25) + || cdItem.MadeByVersion.Version < 25 + || cdItem.MadeByVersion.Version > 40) return false; } } @@ -847,9 +1350,10 @@ static bool AreItemsEqual(const CItemEx &localItem, const CItemEx &cdItem) } -HRESULT CInArchive::ReadLocalItemAfterCdItem(CItemEx &item, bool &isAvail) +HRESULT CInArchive::Read_LocalItem_After_CdItem(CItemEx &item, bool &isAvail, bool &headersError) { isAvail = true; + headersError = false; if (item.FromLocal) return S_OK; try @@ -863,15 +1367,13 @@ HRESULT CInArchive::ReadLocalItemAfterCdItem(CItemEx &item, bool &isAvail) isAvail = false; return S_FALSE; } - IInStream *str2 = Vols.Streams[item.Disk].Stream; - if (!str2) + Stream = Vols.Streams[item.Disk].Stream; + Vols.StreamIndex = (int)item.Disk; + if (!Stream) { isAvail = false; return S_FALSE; } - RINOK(str2->Seek(offset, STREAM_SEEK_SET, NULL)); - Stream = str2; - Vols.StreamIndex = item.Disk; } else { @@ -882,15 +1384,25 @@ HRESULT CInArchive::ReadLocalItemAfterCdItem(CItemEx &item, bool &isAvail) } Stream = StreamRef; - offset += ArcInfo.Base; + offset = (UInt64)((Int64)offset + ArcInfo.Base); if (ArcInfo.Base < 0 && (Int64)offset < 0) { isAvail = false; return S_FALSE; } - RINOK(Seek(offset)); } + _inBufMode = false; + RINOK(Seek_SavePos(offset)) + InitBuf(); + /* + // we can use buf mode with small buffer to reduce + // the number of Read() calls in ReadLocalItem() + _inBufMode = true; + Buffer.Alloc(1 << 10); + if (!Buffer.IsAllocated()) + return E_OUTOFMEMORY; + */ CItemEx localItem; if (ReadUInt32() != NSignature::kLocalFileHeader) @@ -900,6 +1412,16 @@ HRESULT CInArchive::ReadLocalItemAfterCdItem(CItemEx &item, bool &isAvail) return S_FALSE; item.LocalFullHeaderSize = localItem.LocalFullHeaderSize; item.LocalExtra = localItem.LocalExtra; + if (item.Crc != localItem.Crc && !localItem.HasDescriptor()) + { + item.Crc = localItem.Crc; + headersError = true; + } + if ((item.Flags ^ localItem.Flags) & NFileHeader::NFlags::kDescriptorUsedMask) + { + item.Flags = (UInt16)(item.Flags ^ NFileHeader::NFlags::kDescriptorUsedMask); + headersError = true; + } item.FromLocal = true; } catch(...) { return S_FALSE; } @@ -907,86 +1429,218 @@ HRESULT CInArchive::ReadLocalItemAfterCdItem(CItemEx &item, bool &isAvail) } -HRESULT CInArchive::ReadLocalItemDescriptor(CItemEx &item) +/* +---------- FindDescriptor ---------- + +in: + _streamPos : position in Stream + Stream : + Vols : if (IsMultiVol) + +action: + searches descriptor in input stream(s). + sets + item.DescriptorWasRead = true; + item.Size + item.PackSize + item.Crc + if descriptor was found + +out: + S_OK: + if ( item.DescriptorWasRead) : if descriptor was found + if (!item.DescriptorWasRead) : if descriptor was not found : unexpected end of stream(s) + + S_FALSE: if no items or there is just one item with strange properies that doesn't look like real archive. + + another error code: Callback error. + +exceptions : + CSystemException() : stream reading error +*/ + +HRESULT CInArchive::FindDescriptor(CItemEx &item, unsigned numFiles) { - const unsigned kBufSize = (1 << 12); - Byte buf[kBufSize]; + // const size_t kBufSize = (size_t)1 << 5; // don't increase it too much. It reads data look ahead. + + // Buffer.Alloc(kBufSize); + // Byte *buf = Buffer; - UInt32 numBytesInBuffer = 0; - UInt32 packedSize = 0; + UInt64 packedSize = 0; + UInt64 progressPrev = _cnt; + for (;;) { - UInt32 processedSize; - RINOK(ReadBytes(buf + numBytesInBuffer, kBufSize - numBytesInBuffer, &processedSize)); - numBytesInBuffer += processedSize; - if (numBytesInBuffer < kDataDescriptorSize) - return S_FALSE; + /* appnote specification claims that we must use 64-bit descriptor, if there is zip64 extra. + But some old third-party xps archives used 64-bit descriptor without zip64 extra. */ + // unsigned descriptorSize = kDataDescriptorSize64 + kNextSignatureSize; + + // const unsigned kNextSignatureSize = 0; // we can disable check for next signatuire + const unsigned kNextSignatureSize = 4; // we check also for signature for next File headear + + const unsigned descriptorSize4 = item.GetDescriptorSize() + kNextSignatureSize; + + if (descriptorSize4 > Buffer.Size()) return E_FAIL; + + // size_t processedSize; + CanStartNewVol = true; + RINOK(LookAhead(descriptorSize4)) + const size_t avail = GetAvail(); + + if (avail < descriptorSize4) + { + // we write to packSize all these available bytes. + // later it's simpler to work with such value than with 0 + // if (item.PackSize == 0) + item.PackSize = packedSize + avail; + if (item.Method == 0) + item.Size = item.PackSize; + SkipLookahed(avail); + return S_OK; + } + + const Byte * const pStart = Buffer + _bufPos; + const Byte * p = pStart; + const Byte * const limit = pStart + (avail - descriptorSize4); - UInt32 i; - for (i = 0; i <= numBytesInBuffer - kDataDescriptorSize; i++) + for (; p <= limit; p++) { // descriptor signature field is Info-ZIP's extension to pkware Zip specification. // New ZIP specification also allows descriptorSignature. - if (buf[i] != 0x50) + + p = FindPK_4(p, limit + 1); + if (p > limit) + break; + + /* + if (*p != 0x50) + continue; + */ + + if (Get32(p) != NSignature::kDataDescriptor) continue; - // !!!! It must be fixed for Zip64 archives - if (Get32(buf + i) == NSignature::kDataDescriptor) + + // we check next signatuire after descriptor + // maybe we need check only 2 bytes "PK" instead of 4 bytes, if some another type of header is possible after descriptor + const UInt32 sig = Get32(p + descriptorSize4 - kNextSignatureSize); + if ( sig != NSignature::kLocalFileHeader + && sig != NSignature::kCentralFileHeader) + continue; + + const UInt64 packSizeCur = packedSize + (size_t)(p - pStart); + if (descriptorSize4 == kDataDescriptorSize64 + kNextSignatureSize) // if (item.LocalExtra.IsZip64) { - UInt32 descriptorPackSize = Get32(buf + i + 8); - if (descriptorPackSize == packedSize + i) - { - item.Crc = Get32(buf + i + 4); - item.PackSize = descriptorPackSize; - item.Size = Get32(buf + i + 12); - bool isFinished; - return IncreaseRealPosition((Int64)(Int32)(0 - (numBytesInBuffer - i - kDataDescriptorSize)), isFinished); - } + const UInt64 descriptorPackSize = Get64(p + 8); + if (descriptorPackSize != packSizeCur) + continue; + item.Size = Get64(p + 16); + } + else + { + const UInt32 descriptorPackSize = Get32(p + 8); + if (descriptorPackSize != (UInt32)packSizeCur) + continue; + item.Size = Get32(p + 12); + // that item.Size can be truncated to 32-bit value here } + // We write calculated 64-bit packSize, even if descriptor64 was not used + item.PackSize = packSizeCur; + + item.DescriptorWasRead = true; + item.Crc = Get32(p + 4); + + const size_t skip = (size_t)(p - pStart) + descriptorSize4 - kNextSignatureSize; + + SkipLookahed(skip); + + return S_OK; } - packedSize += i; - unsigned j; - for (j = 0; i < numBytesInBuffer; i++, j++) - buf[j] = buf[i]; - numBytesInBuffer = j; + const size_t skip = (size_t)(p - pStart); + SkipLookahed(skip); + + packedSize += skip; + + if (Callback) + if (_cnt - progressPrev >= ((UInt32)1 << 22)) + { + progressPrev = _cnt; + const UInt64 numFiles64 = numFiles; + RINOK(Callback->SetCompleted(&numFiles64, &_cnt)) + } + } +} + + +HRESULT CInArchive::CheckDescriptor(const CItemEx &item) +{ + if (!item.HasDescriptor()) + return S_OK; + + // pkzip's version without descriptor signature is not supported + + bool isFinished = false; + RINOK(IncreaseRealPosition(item.PackSize, isFinished)) + if (isFinished) + return S_FALSE; + + /* + if (!IsMultiVol) + { + RINOK(Seek_SavePos(ArcInfo.Base + item.GetDataPosition() + item.PackSize)); + } + */ + + Byte buf[kDataDescriptorSize64]; + try + { + CanStartNewVol = true; + SafeRead(buf, item.GetDescriptorSize()); + } + catch (const CSystemException &e) { return e.ErrorCode; } + // catch (const CUnexpectEnd &) + catch(...) + { + return S_FALSE; + } + // RINOK(ReadStream_FALSE(Stream, buf, item.GetDescriptorSize())); + + if (Get32(buf) != NSignature::kDataDescriptor) + return S_FALSE; + UInt32 crc = Get32(buf + 4); + UInt64 packSize, unpackSize; + + if (item.LocalExtra.IsZip64) + { + packSize = Get64(buf + 8); + unpackSize = Get64(buf + 16); } + else + { + packSize = Get32(buf + 8); + unpackSize = Get32(buf + 12); + } + + if (crc != item.Crc || item.PackSize != packSize || item.Size != unpackSize) + return S_FALSE; + return S_OK; } -HRESULT CInArchive::ReadLocalItemAfterCdItemFull(CItemEx &item) +HRESULT CInArchive::Read_LocalItem_After_CdItem_Full(CItemEx &item) { if (item.FromLocal) return S_OK; try { bool isAvail = true; - RINOK(ReadLocalItemAfterCdItem(item, isAvail)); + bool headersError = false; + RINOK(Read_LocalItem_After_CdItem(item, isAvail, headersError)) + if (headersError) + return S_FALSE; if (item.HasDescriptor()) - { - // pkzip's version without descriptor is not supported - RINOK(Seek(ArcInfo.Base + item.GetDataPosition() + item.PackSize)); - if (ReadUInt32() != NSignature::kDataDescriptor) - return S_FALSE; - UInt32 crc = ReadUInt32(); - UInt64 packSize, unpackSize; - - /* - if (IsZip64) - { - packSize = ReadUInt64(); - unpackSize = ReadUInt64(); - } - else - */ - { - packSize = ReadUInt32(); - unpackSize = ReadUInt32(); - } - - if (crc != item.Crc || item.PackSize != packSize || item.Size != unpackSize) - return S_FALSE; - } + return CheckDescriptor(item); } catch(...) { return S_FALSE; } return S_OK; @@ -997,7 +1651,7 @@ HRESULT CInArchive::ReadCdItem(CItemEx &item) { item.FromCentral = true; Byte p[kCentralHeaderSize - 4]; - SafeReadBytes(p, kCentralHeaderSize - 4); + SafeRead(p, kCentralHeaderSize - 4); item.MadeByVersion.Version = p[0]; item.MadeByVersion.HostOS = p[1]; @@ -1019,7 +1673,7 @@ HRESULT CInArchive::ReadCdItem(CItemEx &item) ReadFileName(nameSize, item.Name); if (extraSize > 0) - ReadExtra(extraSize, item.CentralExtra, item.Size, item.PackSize, item.LocalHeaderPos, item.Disk); + ReadExtra(item, extraSize, item.CentralExtra, item.Size, item.PackSize, &item); // May be these strings must be deleted /* @@ -1032,103 +1686,119 @@ HRESULT CInArchive::ReadCdItem(CItemEx &item) } +/* +TryEcd64() + (_inBufMode == false) is expected here + so TryEcd64() can't change the Buffer. + if (Ecd64 is not covered by cached region), + TryEcd64() can change cached region ranges (_bufCached, _bufPos) and _streamPos. +*/ + HRESULT CInArchive::TryEcd64(UInt64 offset, CCdInfo &cdInfo) { if (offset >= ((UInt64)1 << 63)) return S_FALSE; - RINOK(Seek(offset)); Byte buf[kEcd64_FullSize]; - RINOK(ReadStream_FALSE(Stream, buf, kEcd64_FullSize)); + RINOK(SeekToVol(Vols.StreamIndex, offset)) + RINOK(ReadFromCache_FALSE(buf, kEcd64_FullSize)) if (Get32(buf) != NSignature::kEcd64) return S_FALSE; UInt64 mainSize = Get64(buf + 4); - if (mainSize < kEcd64_MainSize || mainSize > ((UInt64)1 << 32)) + if (mainSize < kEcd64_MainSize || mainSize > ((UInt64)1 << 40)) return S_FALSE; cdInfo.ParseEcd64e(buf + 12); return S_OK; } +/* FindCd() doesn't use previous cached region, + but it uses Buffer. So it sets new cached region */ + HRESULT CInArchive::FindCd(bool checkOffsetMode) { - CCdInfo &cdInfo = Vols.ecd; - + // There are no useful data in cache in most cases here. + // So here we don't use cache data from previous operations. + InitBuf(); UInt64 endPos; - - RINOK(Stream->Seek(0, STREAM_SEEK_END, &endPos)); - - const UInt32 kBufSizeMax = ((UInt32)1 << 16) + kEcdSize + kEcd64Locator_Size + kEcd64_FullSize; - const UInt32 bufSize = (endPos < kBufSizeMax) ? (UInt32)endPos : kBufSizeMax; + RINOK(InStream_GetSize_SeekToEnd(Stream, endPos)) + _streamPos = endPos; + const size_t kBufSizeMax = (size_t)1 << 17; // must be larger than + // (1 << 16) + kEcdSize + kEcd64Locator_Size + kEcd64_FullSize + const size_t bufSize = (endPos < kBufSizeMax) ? (size_t)endPos : kBufSizeMax; if (bufSize < kEcdSize) return S_FALSE; - CByteArr byteBuffer(bufSize); + RINOK(AllocateBuffer(kBufSizeMax)) + { + RINOK(Seek_SavePos(endPos - bufSize)) + size_t processed = bufSize; + const HRESULT res = ReadStream(Stream, Buffer, &processed); + _streamPos += processed; + _bufCached = processed; + _bufPos = 0; + _cnt += processed; + if (res != S_OK) + return res; + if (processed != bufSize) + return S_FALSE; + } - const UInt64 startPos = endPos - bufSize; - RINOK(Stream->Seek(startPos, STREAM_SEEK_SET, &m_Position)); - if (m_Position != startPos) - return S_FALSE; - - RINOK(ReadStream_FALSE(Stream, byteBuffer, bufSize)); + CCdInfo &cdInfo = Vols.ecd; - for (UInt32 i = bufSize - kEcdSize + 1;;) + for (size_t i = bufSize - kEcdSize + 1;;) { - if (i == 0) - return S_FALSE; - - const Byte *buf = byteBuffer; - - for (;;) + const Byte *buf = Buffer; { - i--; - if (buf[i] == 0x50) - break; - if (i == 0) - return S_FALSE; - } - - if (Get32(buf + i) != NSignature::kEcd) - continue; + const Byte *p = buf + i; + do + if (p == buf) + return S_FALSE; + while (*(--p) != 0x50); - cdInfo.ParseEcd32(buf + i); + i = (size_t)(p - buf); + if (Get32(p) != NSignature::kEcd) + continue; + cdInfo.ParseEcd32(p); + } if (i >= kEcd64Locator_Size) { - const Byte *locatorPtr = buf + i - kEcd64Locator_Size; - if (Get32(locatorPtr) == NSignature::kEcd64Locator) + const size_t locatorIndex = i - kEcd64Locator_Size; + if (Get32(buf + locatorIndex) == NSignature::kEcd64Locator) { CLocator locator; - locator.Parse(locatorPtr + 4); - if ((cdInfo.ThisDisk == locator.NumDisks - 1 || cdInfo.ThisDisk == 0xFFFF) - && locator.Ecd64Disk < locator.NumDisks) + locator.Parse(buf + locatorIndex + 4); + UInt32 numDisks = locator.NumDisks; + // we ignore the error, where some zip creators use (NumDisks == 0) + if (numDisks == 0) + numDisks = 1; + if ((cdInfo.ThisDisk == numDisks - 1 || ZIP64_IS_16_MAX(cdInfo.ThisDisk)) + && locator.Ecd64Disk < numDisks) { - if (locator.Ecd64Disk != cdInfo.ThisDisk && cdInfo.ThisDisk != 0xFFFF) + if (locator.Ecd64Disk != cdInfo.ThisDisk && !ZIP64_IS_16_MAX(cdInfo.ThisDisk)) return E_NOTIMPL; // Most of the zip64 use fixed size Zip64 ECD // we try relative backward reading. - - UInt64 absEcd64 = endPos - bufSize + i - (kEcd64Locator_Size + kEcd64_FullSize); + const UInt64 absEcd64 = endPos - bufSize + i - (kEcd64Locator_Size + kEcd64_FullSize); + + if (locatorIndex >= kEcd64_FullSize) if (checkOffsetMode || absEcd64 == locator.Ecd64Offset) { - const Byte *ecd64 = locatorPtr - kEcd64_FullSize; - if (Get32(ecd64) == NSignature::kEcd64) + const Byte *ecd64 = buf + locatorIndex - kEcd64_FullSize; + if (Get32(ecd64) == NSignature::kEcd64 && + Get64(ecd64 + 4) == kEcd64_MainSize) { - UInt64 mainEcd64Size = Get64(ecd64 + 4); - if (mainEcd64Size == kEcd64_MainSize) - { - cdInfo.ParseEcd64e(ecd64 + 12); - ArcInfo.Base = absEcd64 - locator.Ecd64Offset; - // ArcInfo.BaseVolIndex = cdInfo.ThisDisk; - return S_OK; - } + cdInfo.ParseEcd64e(ecd64 + 12); + ArcInfo.Base = (Int64)(absEcd64 - locator.Ecd64Offset); + // ArcInfo.BaseVolIndex = cdInfo.ThisDisk; + return S_OK; } } // some zip64 use variable size Zip64 ECD. // we try to use absolute offset from locator. - if (absEcd64 != locator.Ecd64Offset) { if (TryEcd64(locator.Ecd64Offset, cdInfo) == S_OK) @@ -1147,7 +1817,7 @@ HRESULT CInArchive::FindCd(bool checkOffsetMode) { if (TryEcd64(ArcInfo.MarkerPos + locator.Ecd64Offset, cdInfo) == S_OK) { - ArcInfo.Base = ArcInfo.MarkerPos; + ArcInfo.Base = (Int64)ArcInfo.MarkerPos; // ArcInfo.BaseVolIndex = cdInfo.ThisDisk; return S_OK; } @@ -1181,7 +1851,7 @@ HRESULT CInArchive::FindCd(bool checkOffsetMode) } else */ - ArcInfo.Base = absEcdPos - cdEnd; + ArcInfo.Base = (Int64)(absEcdPos - cdEnd); } return S_OK; } @@ -1192,61 +1862,98 @@ HRESULT CInArchive::FindCd(bool checkOffsetMode) HRESULT CInArchive::TryReadCd(CObjectVector &items, const CCdInfo &cdInfo, UInt64 cdOffset, UInt64 cdSize) { items.Clear(); - - ISequentialInStream *stream; + IsCdUnsorted = false; - if (!IsMultiVol) - { - stream = this->StartStream; - Vols.StreamIndex = -1; - RINOK(this->StartStream->Seek(cdOffset, STREAM_SEEK_SET, &m_Position)); - if (m_Position != cdOffset) - return S_FALSE; - } - else - { - if (cdInfo.CdDisk >= Vols.Streams.Size()) - return S_FALSE; - IInStream *str2 = Vols.Streams[cdInfo.CdDisk].Stream; - if (!str2) - return S_FALSE; - RINOK(str2->Seek(cdOffset, STREAM_SEEK_SET, NULL)); - stream = str2; - Vols.NeedSeek = false; - Vols.StreamIndex = cdInfo.CdDisk; - m_Position = cdOffset; - } + if ((Int64)cdOffset < 0) + return S_FALSE; + + // _startLocalFromCd_Disk = (UInt32)(Int32)-1; + // _startLocalFromCd_Offset = (UInt64)(Int64)-1; - _inBuffer.SetStream(stream); + RINOK(SeekToVol(IsMultiVol ? (int)cdInfo.CdDisk : -1, cdOffset)) - _inBuffer.Init(); _inBufMode = true; + _cnt = 0; - _processedCnt = 0; + if (Callback) + { + RINOK(Callback->SetTotal(&cdInfo.NumEntries, IsMultiVol ? &Vols.TotalBytesSize : NULL)) + } + UInt64 numFileExpected = cdInfo.NumEntries; + const UInt64 *totalFilesPtr = &numFileExpected; + bool isCorrect_NumEntries = (cdInfo.IsFromEcd64 || numFileExpected >= ((UInt32)1 << 16)); - while (_processedCnt < cdSize) + while (_cnt < cdSize) { CanStartNewVol = true; if (ReadUInt32() != NSignature::kCentralFileHeader) return S_FALSE; + CanStartNewVol = false; { CItemEx cdItem; - RINOK(ReadCdItem(cdItem)); + RINOK(ReadCdItem(cdItem)) + + /* + if (cdItem.Disk < _startLocalFromCd_Disk || + cdItem.Disk == _startLocalFromCd_Disk && + cdItem.LocalHeaderPos < _startLocalFromCd_Offset) + { + _startLocalFromCd_Disk = cdItem.Disk; + _startLocalFromCd_Offset = cdItem.LocalHeaderPos; + } + */ + + if (items.Size() > 0 && !IsCdUnsorted) + { + const CItemEx &prev = items.Back(); + if (cdItem.Disk < prev.Disk + || (cdItem.Disk == prev.Disk && + cdItem.LocalHeaderPos < prev.LocalHeaderPos)) + IsCdUnsorted = true; + } + items.Add(cdItem); } if (Callback && (items.Size() & 0xFFF) == 0) { const UInt64 numFiles = items.Size(); - RINOK(Callback->SetCompleted(&numFiles, NULL)); + + if (numFiles > numFileExpected && totalFilesPtr) + { + if (isCorrect_NumEntries) + totalFilesPtr = NULL; + else + while (numFiles > numFileExpected) + numFileExpected += (UInt32)1 << 16; + RINOK(Callback->SetTotal(totalFilesPtr, NULL)) + } + + RINOK(Callback->SetCompleted(&numFiles, &_cnt)) } } CanStartNewVol = true; - return (_processedCnt == cdSize) ? S_OK : S_FALSE; + return (_cnt == cdSize) ? S_OK : S_FALSE; } +/* +static int CompareCdItems(void *const *elem1, void *const *elem2, void *) +{ + const CItemEx *i1 = *(const CItemEx **)elem1; + const CItemEx *i2 = *(const CItemEx **)elem2; + + if (i1->Disk < i2->Disk) return -1; + if (i1->Disk > i2->Disk) return 1; + if (i1->LocalHeaderPos < i2->LocalHeaderPos) return -1; + if (i1->LocalHeaderPos > i2->LocalHeaderPos) return 1; + if (i1 < i2) return -1; + if (i1 > i2) return 1; + return 0; +} +*/ + HRESULT CInArchive::ReadCd(CObjectVector &items, UInt32 &cdDisk, UInt64 &cdOffset, UInt64 &cdSize) { bool checkOffsetMode = true; @@ -1255,7 +1962,7 @@ HRESULT CInArchive::ReadCd(CObjectVector &items, UInt32 &cdDisk, UInt64 { if (Vols.EndVolIndex == -1) return S_FALSE; - Stream = Vols.Streams[Vols.EndVolIndex].Stream; + Stream = Vols.Streams[(unsigned)Vols.EndVolIndex].Stream; if (!Vols.StartIsZip) checkOffsetMode = false; } @@ -1264,7 +1971,7 @@ HRESULT CInArchive::ReadCd(CObjectVector &items, UInt32 &cdDisk, UInt64 if (!Vols.ecd_wasRead) { - RINOK(FindCd(checkOffsetMode)); + RINOK(FindCd(checkOffsetMode)) } CCdInfo &cdInfo = Vols.ecd; @@ -1275,12 +1982,13 @@ HRESULT CInArchive::ReadCd(CObjectVector &items, UInt32 &cdDisk, UInt64 cdOffset = cdInfo.Offset; cdDisk = cdInfo.CdDisk; - if (Callback) + if (!IsMultiVol) { - RINOK(Callback->SetTotal(&cdInfo.NumEntries, NULL)); + if (cdInfo.ThisDisk != cdInfo.CdDisk) + return S_FALSE; } - - const UInt64 base = (IsMultiVol ? 0 : ArcInfo.Base); + + const UInt64 base = (IsMultiVol ? 0 : (UInt64)ArcInfo.Base); res = TryReadCd(items, cdInfo, base + cdOffset, cdSize); if (res == S_FALSE && !IsMultiVol && base != ArcInfo.MarkerPos) @@ -1288,9 +1996,11 @@ HRESULT CInArchive::ReadCd(CObjectVector &items, UInt32 &cdDisk, UInt64 // do we need that additional attempt to read cd? res = TryReadCd(items, cdInfo, ArcInfo.MarkerPos + cdOffset, cdSize); if (res == S_OK) - ArcInfo.Base = ArcInfo.MarkerPos; + ArcInfo.Base = (Int64)ArcInfo.MarkerPos; } + // Some rare case files are unsorted + // items.Sort(CompareCdItems, NULL); return res; } @@ -1302,14 +2012,14 @@ static int FindItem(const CObjectVector &items, const CItemEx &item) { if (left >= right) return -1; - unsigned index = (left + right) / 2; + const unsigned index = (unsigned)(((size_t)left + (size_t)right) / 2); const CItemEx &item2 = items[index]; if (item.Disk < item2.Disk) right = index; else if (item.Disk > item2.Disk) left = index + 1; else if (item.LocalHeaderPos == item2.LocalHeaderPos) - return index; + return (int)index; else if (item.LocalHeaderPos < item2.LocalHeaderPos) right = index; else @@ -1323,66 +2033,103 @@ static bool IsStrangeItem(const CItem &item) } + +/* + ---------- ReadLocals ---------- + +in: + (_signature == NSignature::kLocalFileHeader) + VirtStreamPos : after _signature : position in Stream + Stream : + Vols : if (IsMultiVol) + (_inBufMode == false) + +action: + it parses local items. + + if ( IsMultiVol) it writes absolute offsets to CItemEx::LocalHeaderPos + if (!IsMultiVol) it writes relative (from ArcInfo.Base) offsets to CItemEx::LocalHeaderPos + later we can correct CItemEx::LocalHeaderPos values, if + some new value for ArcInfo.Base will be detected +out: + S_OK: + (_signature != NSignature::kLocalFileHeade) + _streamPos : after _signature + + S_FALSE: if no items or there is just one item with strange properies that doesn't look like real archive. + + another error code: stream reading error or Callback error. + + CUnexpectEnd() exception : it's not fatal exception here. + It means that reading was interrupted by unexpected end of input stream, + but some CItemEx items were parsed OK. + We can stop further archive parsing. + But we can use all filled CItemEx items. +*/ + HRESULT CInArchive::ReadLocals(CObjectVector &items) { items.Clear(); + + UInt64 progressPrev = _cnt; - while (m_Signature == NSignature::kLocalFileHeader) + if (Callback) + { + RINOK(Callback->SetTotal(NULL, IsMultiVol ? &Vols.TotalBytesSize : NULL)) + } + + while (_signature == NSignature::kLocalFileHeader) { CItemEx item; - item.LocalHeaderPos = m_Position - 4; - if (!IsMultiVol) - item.LocalHeaderPos -= ArcInfo.MarkerPos; - // we write ralative LocalHeaderPos here. Later we can correct it to real Base. + item.LocalHeaderPos = GetVirtStreamPos() - 4; + if (!IsMultiVol) + item.LocalHeaderPos = (UInt64)((Int64)item.LocalHeaderPos - ArcInfo.Base); try { ReadLocalItem(item); item.FromLocal = true; bool isFinished = false; - + if (item.HasDescriptor()) - ReadLocalItemDescriptor(item); + { + RINOK(FindDescriptor(item, items.Size())) + isFinished = !item.DescriptorWasRead; + } else { - /* - if (IsMultiVol) - { - const int kStep = 10000; - RINOK(IncreaseRealPosition(-kStep, isFinished)); - RINOK(IncreaseRealPosition(item.PackSize + kStep, isFinished)); - } - else - */ - RINOK(IncreaseRealPosition(item.PackSize, isFinished)); + if (item.PackSize >= ((UInt64)1 << 62)) + throw CUnexpectEnd(); + RINOK(IncreaseRealPosition(item.PackSize, isFinished)) } - + items.Add(item); if (isFinished) throw CUnexpectEnd(); - - m_Signature = ReadUInt32(); + + ReadSignature(); } catch (CUnexpectEnd &) { - if (items.IsEmpty() || items.Size() == 1 && IsStrangeItem(items[0])) + if (items.IsEmpty() || (items.Size() == 1 && IsStrangeItem(items[0]))) return S_FALSE; throw; } - if (Callback && (items.Size() & 0xFF) == 0) + + if (Callback) + if ((items.Size() & 0xFF) == 0 + || _cnt - progressPrev >= ((UInt32)1 << 22)) { + progressPrev = _cnt; const UInt64 numFiles = items.Size(); - UInt64 numBytes = 0; - // if (!sMultiVol) - numBytes = item.LocalHeaderPos; - RINOK(Callback->SetCompleted(&numFiles, &numBytes)); + RINOK(Callback->SetCompleted(&numFiles, &_cnt)) } } - if (items.Size() == 1 && m_Signature != NSignature::kCentralFileHeader) + if (items.Size() == 1 && _signature != NSignature::kCentralFileHeader) if (IsStrangeItem(items[0])) return S_FALSE; @@ -1396,41 +2143,64 @@ HRESULT CVols::ParseArcName(IArchiveOpenVolumeCallback *volCallback) UString name; { NWindows::NCOM::CPropVariant prop; - RINOK(volCallback->GetProperty(kpidName, &prop)); + RINOK(volCallback->GetProperty(kpidName, &prop)) if (prop.vt != VT_BSTR) return S_OK; name = prop.bstrVal; } - UString base = name; - int dotPos = name.ReverseFind_Dot(); - + const int dotPos = name.ReverseFind_Dot(); if (dotPos < 0) return S_OK; + const UString ext = name.Ptr((unsigned)(dotPos + 1)); + name.DeleteFrom((unsigned)(dotPos + 1)); - base.DeleteFrom(dotPos + 1); - - const UString ext = name.Ptr(dotPos + 1); StartVolIndex = (Int32)(-1); if (ext.IsEmpty()) return S_OK; - else { wchar_t c = ext[0]; IsUpperCase = (c >= 'A' && c <= 'Z'); if (ext.IsEqualTo_Ascii_NoCase("zip")) { - BaseName = base; + BaseName = name; StartIsZ = true; StartIsZip = true; return S_OK; } else if (ext.IsEqualTo_Ascii_NoCase("exe")) { + /* possible cases: + - exe with zip inside + - sfx: a.exe, a.z02, a.z03,... , a.zip + a.exe is start volume. + - zip renamed to exe + */ + StartIsExe = true; - BaseName = base; + BaseName = name; StartVolIndex = 0; + /* sfx-zip can use both arc.exe and arc.zip + We can open arc.zip, if it was requesed to open arc.exe. + But it's possible that arc.exe and arc.zip are not parts of same archive. + So we can disable such operation */ + + // 18.04: we still want to open zip renamed to exe. + /* + { + UString volName = name; + volName += IsUpperCase ? "Z01" : "z01"; + { + CMyComPtr stream; + HRESULT res2 = volCallback->GetStream(volName, &stream); + if (res2 == S_OK) + DisableVolsSearch = true; + } + } + */ + DisableVolsSearch = true; + return S_OK; } else if (ext[0] == 'z' || ext[0] == 'Z') { @@ -1440,8 +2210,8 @@ HRESULT CVols::ParseArcName(IArchiveOpenVolumeCallback *volCallback) UInt32 volNum = ConvertStringToUInt32(ext.Ptr(1), &end); if (*end != 0 || volNum < 1 || volNum > ((UInt32)1 << 30)) return S_OK; - StartVolIndex = volNum - 1; - BaseName = base; + StartVolIndex = (Int32)(volNum - 1); + BaseName = name; StartIsZ = true; } else @@ -1449,9 +2219,11 @@ HRESULT CVols::ParseArcName(IArchiveOpenVolumeCallback *volCallback) } UString volName = BaseName; - volName.AddAscii(IsUpperCase ? "ZIP" : "zip"); - HRESULT result = volCallback->GetStream(volName, &ZipStream); - if (result == S_FALSE || !ZipStream) + volName += (IsUpperCase ? "ZIP" : "zip"); + + HRESULT res = volCallback->GetStream(volName, &ZipStream); + + if (res == S_FALSE || !ZipStream) { if (MissingName.IsEmpty()) { @@ -1461,13 +2233,16 @@ HRESULT CVols::ParseArcName(IArchiveOpenVolumeCallback *volCallback) return S_OK; } - return result; + return res; } HRESULT CInArchive::ReadVols2(IArchiveOpenVolumeCallback *volCallback, unsigned start, int lastDisk, int zipDisk, unsigned numMissingVolsMax, unsigned &numMissingVols) { + if (Vols.DisableVolsSearch) + return S_OK; + numMissingVols = 0; for (unsigned i = start;; i++) @@ -1493,25 +2268,33 @@ HRESULT CInArchive::ReadVols2(IArchiveOpenVolumeCallback *volCallback, { UString volName = Vols.BaseName; { - volName += (wchar_t)(Vols.IsUpperCase ? 'Z' : 'z'); + volName.Add_Char(Vols.IsUpperCase ? 'Z' : 'z'); + const unsigned v = i + 1; + if (v < 10) + volName.Add_Char('0'); + volName.Add_UInt32(v); + } + + HRESULT res = volCallback->GetStream(volName, &stream); + if (res != S_OK && res != S_FALSE) + return res; + if (res == S_FALSE || !stream) + { + if (i == 0) { - char s[32]; - ConvertUInt32ToString(i + 1, s); - unsigned len = (unsigned)strlen(s); - while (len < 2) - { - volName += (wchar_t)'0'; - len++; - } - volName.AddAscii(s); + UString volName_exe = Vols.BaseName; + volName_exe += (Vols.IsUpperCase ? "EXE" : "exe"); + + HRESULT res2 = volCallback->GetStream(volName_exe, &stream); + if (res2 != S_OK && res2 != S_FALSE) + return res2; + res = res2; } } - - HRESULT result = volCallback->GetStream(volName, &stream); - if (result != S_OK && result != S_FALSE) - return result; - if (result == S_FALSE || !stream) + if (res == S_FALSE || !stream) { + if (i == 1 && Vols.StartIsExe) + return S_OK; if (Vols.MissingName.IsEmpty()) Vols.MissingName = volName; numMissingVols++; @@ -1523,24 +2306,22 @@ HRESULT CInArchive::ReadVols2(IArchiveOpenVolumeCallback *volCallback, } } - UInt64 size; - - UInt64 pos; - RINOK(stream->Seek(0, STREAM_SEEK_CUR, &pos)); - RINOK(stream->Seek(0, STREAM_SEEK_END, &size)); - RINOK(stream->Seek(pos, STREAM_SEEK_SET, NULL)); + UInt64 pos, size; + RINOK(InStream_GetPos_GetSize(stream, pos, size)) while (i >= Vols.Streams.Size()) Vols.Streams.AddNew(); CVols::CSubStreamInfo &ss = Vols.Streams[i]; Vols.NumVols++; + Vols.TotalBytesSize += size; + ss.Stream = stream; ss.Size = size; if ((int)i == zipDisk) { - Vols.EndVolIndex = Vols.Streams.Size() - 1; + Vols.EndVolIndex = (int)(Vols.Streams.Size() - 1); break; } } @@ -1557,13 +2338,13 @@ HRESULT CInArchive::ReadVols() if (!volCallback) return S_OK; - RINOK(Vols.ParseArcName(volCallback)); + RINOK(Vols.ParseArcName(volCallback)) - int startZIndex = Vols.StartVolIndex; + // const int startZIndex = Vols.StartVolIndex; if (!Vols.StartIsZ) { - // if (!Vols.StartIsExe) + if (!Vols.StartIsExe) return S_OK; } @@ -1573,35 +2354,46 @@ HRESULT CInArchive::ReadVols() if (Vols.StartIsZip) Vols.ZipStream = StartStream; - // bool cdOK = false; - if (Vols.ZipStream) { Stream = Vols.ZipStream; + + if (Vols.StartIsZip) + Vols.StreamIndex = -1; + else + { + Vols.StreamIndex = -2; + InitBuf(); + } + HRESULT res = FindCd(true); + CCdInfo &ecd = Vols.ecd; if (res == S_OK) { - zipDisk = ecd.ThisDisk; + zipDisk = (int)ecd.ThisDisk; Vols.ecd_wasRead = true; + + // if is not multivol or bad multivol, we return to main single stream code if (ecd.ThisDisk == 0 || ecd.ThisDisk >= ((UInt32)1 << 30) || ecd.ThisDisk < ecd.CdDisk) return S_OK; - cdDisk = ecd.CdDisk; + + cdDisk = (int)ecd.CdDisk; if (Vols.StartVolIndex < 0) - Vols.StartVolIndex = ecd.ThisDisk; + Vols.StartVolIndex = (Int32)ecd.ThisDisk; + else if ((UInt32)Vols.StartVolIndex >= ecd.ThisDisk) + return S_OK; + // Vols.StartVolIndex = ecd.ThisDisk; // Vols.EndVolIndex = ecd.ThisDisk; unsigned numMissingVols; - if (cdDisk == zipDisk) - { - // cdOK = true; - } - else + if (cdDisk != zipDisk) { - RINOK(ReadVols2(volCallback, cdDisk, zipDisk, zipDisk, 0, numMissingVols)); - if (numMissingVols == 0) + // get volumes required for cd. + RINOK(ReadVols2(volCallback, (unsigned)cdDisk, zipDisk, zipDisk, 0, numMissingVols)) + if (numMissingVols != 0) { // cdOK = false; } @@ -1611,25 +2403,50 @@ HRESULT CInArchive::ReadVols() return res; } - if (Vols.Streams.Size() > 0) - IsMultiVol = true; - if (Vols.StartVolIndex < 0) + { + // is not mutivol; return S_OK; + } + /* + if (!Vols.Streams.IsEmpty()) + IsMultiVol = true; + */ + unsigned numMissingVols; if (cdDisk != 0) { - RINOK(ReadVols2(volCallback, 0, cdDisk < 0 ? -1 : cdDisk, zipDisk, 1 << 10, numMissingVols)); + // get volumes that were no requested still + const unsigned kNumMissingVolsMax = 1 << 12; + RINOK(ReadVols2(volCallback, 0, cdDisk < 0 ? -1 : cdDisk, zipDisk, kNumMissingVolsMax, numMissingVols)) + } + + // if (Vols.StartVolIndex >= 0) + { + if (Vols.Streams.IsEmpty()) + if (Vols.StartVolIndex > (1 << 20)) + return S_OK; + if ((unsigned)Vols.StartVolIndex >= Vols.Streams.Size() + || !Vols.Streams[(unsigned)Vols.StartVolIndex].Stream) + { + // we get volumes starting from StartVolIndex, if they we not requested before know the volume index (if FindCd() was ok) + RINOK(ReadVols2(volCallback, (unsigned)Vols.StartVolIndex, zipDisk, zipDisk, 0, numMissingVols)) + } } if (Vols.ZipStream) { + // if there is no another volumes and volumeIndex is too big, we don't use multivol mode if (Vols.Streams.IsEmpty()) if (zipDisk > (1 << 10)) return S_OK; - RINOK(ReadVols2(volCallback, zipDisk, zipDisk + 1, zipDisk, 0, numMissingVols)); + if (zipDisk >= 0) + { + // we create item in Streams for ZipStream, if we know the volume index (if FindCd() was ok) + RINOK(ReadVols2(volCallback, (unsigned)zipDisk, zipDisk + 1, zipDisk, 0, numMissingVols)) + } } if (!Vols.Streams.IsEmpty()) @@ -1639,11 +2456,14 @@ HRESULT CInArchive::ReadVols() if (cdDisk) IsMultiVol = true; */ + const int startZIndex = Vols.StartVolIndex; if (startZIndex >= 0) { - if (Vols.Streams.Size() >= (unsigned)startZIndex) + // if all volumes before start volume are OK, we can start parsing from 0 + // if there are missing volumes before startZIndex, we start parsing in current startZIndex + if ((unsigned)startZIndex < Vols.Streams.Size()) { - for (unsigned i = 0; i < (unsigned)startZIndex; i++) + for (unsigned i = 0; i <= (unsigned)startZIndex; i++) if (!Vols.Streams[i].Stream) { Vols.StartParsingVol = startZIndex; @@ -1658,10 +2478,6 @@ HRESULT CInArchive::ReadVols() - - - - HRESULT CVols::Read(void *data, UInt32 size, UInt32 *processedSize) { if (processedSize) @@ -1675,12 +2491,12 @@ HRESULT CVols::Read(void *data, UInt32 size, UInt32 *processedSize) return S_OK; if ((unsigned)StreamIndex >= Streams.Size()) return S_OK; - const CVols::CSubStreamInfo &s = Streams[StreamIndex]; + const CVols::CSubStreamInfo &s = Streams[(unsigned)StreamIndex]; if (!s.Stream) return S_FALSE; if (NeedSeek) { - RINOK(s.Stream->Seek(0, STREAM_SEEK_SET, NULL)); + RINOK(s.SeekToStart()) NeedSeek = false; } UInt32 realProcessedSize = 0; @@ -1696,7 +2512,7 @@ HRESULT CVols::Read(void *data, UInt32 size, UInt32 *processedSize) } } -STDMETHODIMP CVolStream::Read(void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CVolStream::Read(void *data, UInt32 size, UInt32 *processedSize)) { return Vols->Read(data, size, processedSize); } @@ -1704,47 +2520,115 @@ STDMETHODIMP CVolStream::Read(void *data, UInt32 size, UInt32 *processedSize) -#define COPY_ECD_ITEM_16(n) if (!isZip64 || ecd. n != 0xFFFF) ecd64. n = ecd. n; -#define COPY_ECD_ITEM_32(n) if (!isZip64 || ecd. n != 0xFFFFFFFF) ecd64. n = ecd. n; +#define COPY_ECD_ITEM_16(n) if (!isZip64 || !ZIP64_IS_16_MAX(ecd. n)) cdInfo. n = ecd. n; +#define COPY_ECD_ITEM_32(n) if (!isZip64 || !ZIP64_IS_32_MAX(ecd. n)) cdInfo. n = ecd. n; -HRESULT CInArchive::ReadHeaders2(CObjectVector &items) +HRESULT CInArchive::ReadHeaders(CObjectVector &items) { + // buffer that can be used for cd reading + RINOK(AllocateBuffer(kSeqBufferSize)) + + // here we can read small records. So we switch off _inBufMode. + _inBufMode = false; + HRESULT res = S_OK; bool localsWereRead = false; - UInt64 cdSize = 0, cdRelatOffset = 0, cdAbsOffset = 0; + + /* we try to open archive with the following modes: + 1) CD-MODE : fast mode : we read backward ECD and CD, compare CD items with first Local item. + 2) LOCALS-CD-MODE : slow mode, if CD-MODE fails : we sequentially read all Locals and then CD. + Then we read sequentially ECD64, Locator, ECD again at the end. + + - in LOCALS-CD-MODE we use use the following + variables (with real cd properties) to set Base archive offset + and check real cd properties with values from ECD/ECD64. + */ + + UInt64 cdSize = 0; + UInt64 cdRelatOffset = 0; UInt32 cdDisk = 0; - if (!_inBuffer.Create(1 << 15)) - return E_OUTOFMEMORY; + UInt64 cdAbsOffset = 0; // absolute cd offset, for LOCALS-CD-MODE only. - if (!MarkerIsFound) +if (Force_ReadLocals_Mode) +{ + IsArc = true; + res = S_FALSE; // we will use LOCALS-CD-MODE mode +} +else +{ + if (!MarkerIsFound || !MarkerIsSafe) { IsArc = true; res = ReadCd(items, cdDisk, cdRelatOffset, cdSize); if (res == S_OK) - m_Signature = ReadUInt32(); + ReadSignature(); + else if (res != S_FALSE) + return res; } - else + else // (MarkerIsFound && MarkerIsSafe) { - // m_Signature must be kLocalFileHeader or kEcd - // m_Position points to next byte after signature - RINOK(Stream->Seek(m_Position, STREAM_SEEK_SET, NULL)); + // _signature must be kLocalFileHeader or kEcd or kEcd64 + + SeekToVol(ArcInfo.MarkerVolIndex, ArcInfo.MarkerPos2 + 4); + + CanStartNewVol = false; + + if (_signature == NSignature::kEcd64) + { + // UInt64 ecd64Offset = GetVirtStreamPos() - 4; + IsZip64 = true; + + { + const UInt64 recordSize = ReadUInt64(); + if (recordSize < kEcd64_MainSize) + return S_FALSE; + if (recordSize >= ((UInt64)1 << 62)) + return S_FALSE; + + { + const unsigned kBufSize = kEcd64_MainSize; + Byte buf[kBufSize]; + SafeRead(buf, kBufSize); + CCdInfo cdInfo; + cdInfo.ParseEcd64e(buf); + if (!cdInfo.IsEmptyArc()) + return S_FALSE; + } + + RINOK(Skip64(recordSize - kEcd64_MainSize, 0)) + } - _inBuffer.SetStream(Stream); + ReadSignature(); + if (_signature != NSignature::kEcd64Locator) + return S_FALSE; - bool needReadCd = true; + { + const unsigned kBufSize = 16; + Byte buf[kBufSize]; + SafeRead(buf, kBufSize); + CLocator locator; + locator.Parse(buf); + if (!locator.IsEmptyArc()) + return S_FALSE; + } - if (m_Signature == NSignature::kEcd) + ReadSignature(); + if (_signature != NSignature::kEcd) + return S_FALSE; + } + + if (_signature == NSignature::kEcd) { // It must be empty archive or backware archive // we don't support backware archive still const unsigned kBufSize = kEcdSize - 4; Byte buf[kBufSize]; - SafeReadBytes(buf, kBufSize); + SafeRead(buf, kBufSize); CEcd ecd; ecd.Parse(buf); // if (ecd.cdSize != 0) @@ -1752,16 +2636,16 @@ HRESULT CInArchive::ReadHeaders2(CObjectVector &items) if (!ecd.IsEmptyArc()) return S_FALSE; - ArcInfo.Base = ArcInfo.MarkerPos; - needReadCd = false; + ArcInfo.Base = (Int64)ArcInfo.MarkerPos; IsArc = true; // check it: we need more tests? - RINOK(Stream->Seek(ArcInfo.MarkerPos2 + 4, STREAM_SEEK_SET, &m_Position)); - } - if (needReadCd) + RINOK(SeekToVol(ArcInfo.MarkerVolIndex, ArcInfo.MarkerPos2)) + ReadSignature(); + } + else { CItemEx firstItem; - // try + try { try { @@ -1776,9 +2660,10 @@ HRESULT CInArchive::ReadHeaders2(CObjectVector &items) IsArc = true; res = ReadCd(items, cdDisk, cdRelatOffset, cdSize); if (res == S_OK) - m_Signature = ReadUInt32(); + ReadSignature(); } - // catch() { res = S_FALSE; } + catch(CUnexpectEnd &) { res = S_FALSE; } + if (res != S_FALSE && res != S_OK) return res; @@ -1792,90 +2677,207 @@ HRESULT CInArchive::ReadHeaders2(CObjectVector &items) res = S_FALSE; else { - firstItem.LocalHeaderPos = ArcInfo.MarkerPos2 - ArcInfo.Base; - int index = FindItem(items, firstItem); + firstItem.LocalHeaderPos = (UInt64)((Int64)ArcInfo.MarkerPos2 - ArcInfo.Base); + int index = -1; + + UInt32 min_Disk = (UInt32)(Int32)-1; + UInt64 min_LocalHeaderPos = (UInt64)(Int64)-1; + + if (!IsCdUnsorted) + index = FindItem(items, firstItem); + else + { + FOR_VECTOR (i, items) + { + const CItemEx &cdItem = items[i]; + if (cdItem.Disk == firstItem.Disk + && (cdItem.LocalHeaderPos == firstItem.LocalHeaderPos)) + index = (int)i; + + if (i == 0 + || cdItem.Disk < min_Disk + || (cdItem.Disk == min_Disk && cdItem.LocalHeaderPos < min_LocalHeaderPos)) + { + min_Disk = cdItem.Disk; + min_LocalHeaderPos = cdItem.LocalHeaderPos; + } + } + } + if (index == -1) res = S_FALSE; - else if (!AreItemsEqual(firstItem, items[index])) + else if (!AreItemsEqual(firstItem, items[(unsigned)index])) res = S_FALSE; else { ArcInfo.CdWasRead = true; - ArcInfo.FirstItemRelatOffset = items[0].LocalHeaderPos; + if (IsCdUnsorted) + ArcInfo.FirstItemRelatOffset = min_LocalHeaderPos; + else + ArcInfo.FirstItemRelatOffset = items[0].LocalHeaderPos; + + // ArcInfo.FirstItemRelatOffset = _startLocalFromCd_Offset; } } } } - } + } // (MarkerIsFound && MarkerIsSafe) +} // (!onlyLocalsMode) CObjectVector cdItems; - bool needSetBase = false; + bool needSetBase = false; // we set needSetBase only for LOCALS_CD_MODE unsigned numCdItems = items.Size(); - if (res == S_FALSE) + #ifdef ZIP_SELF_CHECK + res = S_FALSE; // if uncommented, it uses additional LOCALS-CD-MODE mode to check the code + #endif + + if (res != S_OK) { + // ---------- LOCALS-CD-MODE ---------- // CD doesn't match firstItem, - // so we clear items and read Locals. + // so we clear items and read Locals and CD. + items.Clear(); localsWereRead = true; - _inBufMode = false; - ArcInfo.Base = ArcInfo.MarkerPos; + + HeadersError = false; + HeadersWarning = false; + ExtraMinorError = false; + + /* we can use any mode: with buffer and without buffer + without buffer : skips packed data : fast for big files : slow for small files + with buffer : reads packed data : slow for big files : fast for small files + Buffer mode is more effective. */ + // _inBufMode = false; + _inBufMode = true; + // we could change the buffer size here, if we want smaller Buffer. + // RINOK(ReAllocateBuffer(kSeqBufferSize)); + // InitBuf() + + ArcInfo.Base = 0; - if (IsMultiVol) + if (!Disable_FindMarker) + { + if (!MarkerIsFound) { - Vols.StreamIndex = Vols.StartParsingVol; - if (Vols.StartParsingVol >= (int)Vols.Streams.Size()) + if (!IsMultiVol) return S_FALSE; - Stream = Vols.Streams[Vols.StartParsingVol].Stream; - if (!Stream) + if (Vols.StartParsingVol != 0) return S_FALSE; + // if (StartParsingVol == 0) and we didn't find marker, we use default zero marker. + // so we suppose that there is no sfx stub + RINOK(SeekToVol(0, ArcInfo.MarkerPos2)) + } + else + { + if (ArcInfo.MarkerPos != 0) + { + /* + If multi-vol or there is (No)Span-marker at start of stream, we set (Base) as 0. + In another caes: + (No)Span-marker is supposed as false positive. So we set (Base) as main marker (MarkerPos2). + The (Base) can be corrected later after ECD reading. + But sfx volume with stub and (No)Span-marker in (!IsMultiVol) mode will have incorrect (Base) here. + */ + ArcInfo.Base = (Int64)ArcInfo.MarkerPos2; + } + RINOK(SeekToVol(ArcInfo.MarkerVolIndex, ArcInfo.MarkerPos2)) } + } + _cnt = 0; - RINOK(Stream->Seek(ArcInfo.MarkerPos2, STREAM_SEEK_SET, &m_Position)); - m_Signature = ReadUInt32(); + ReadSignature(); - RINOK(ReadLocals(items)); + LocalsWereRead = true; - if (m_Signature != NSignature::kCentralFileHeader) + RINOK(ReadLocals(items)) + + if (_signature != NSignature::kCentralFileHeader) { - // if (!UnexpectedEnd) - m_Position -= 4; - NoCentralDir = true; - HeadersError = true; - return S_OK; + // GetVirtStreamPos() - 4 + if (items.IsEmpty()) + return S_FALSE; + + bool isError = true; + + const UInt32 apkSize = _signature; + const unsigned kApkFooterSize = 16 + 8; + if (apkSize >= kApkFooterSize && apkSize <= (1 << 20)) + { + if (ReadUInt32() == 0) + { + CByteBuffer apk; + apk.Alloc(apkSize); + SafeRead(apk, apkSize); + ReadSignature(); + const Byte *footer = apk + apkSize - kApkFooterSize; + if (_signature == NSignature::kCentralFileHeader) + if (GetUi64(footer) == apkSize) + if (memcmp(footer + 8, "APK Sig Block 42", 16) == 0) + { + isError = false; + IsApk = true; + } + } + } + + if (isError) + { + NoCentralDir = true; + HeadersError = true; + return S_OK; + } } _inBufMode = true; - _inBuffer.Init(); - - cdAbsOffset = m_Position - 4; - cdDisk = Vols.StreamIndex; + + cdAbsOffset = GetVirtStreamPos() - 4; + cdDisk = (UInt32)Vols.StreamIndex; + + #ifdef ZIP_SELF_CHECK + if (!IsMultiVol && _cnt != GetVirtStreamPos() - ArcInfo.MarkerPos2) + return E_FAIL; + #endif + + const UInt64 processedCnt_start = _cnt; for (;;) { CItemEx cdItem; - CanStartNewVol = true; - RINOK(ReadCdItem(cdItem)); + RINOK(ReadCdItem(cdItem)) cdItems.Add(cdItem); if (Callback && (cdItems.Size() & 0xFFF) == 0) { const UInt64 numFiles = items.Size(); - RINOK(Callback->SetCompleted(&numFiles, NULL)); + const UInt64 numBytes = _cnt; + RINOK(Callback->SetCompleted(&numFiles, &numBytes)) } - CanStartNewVol = true; - m_Signature = ReadUInt32(); - if (m_Signature != NSignature::kCentralFileHeader) + ReadSignature(); + if (_signature != NSignature::kCentralFileHeader) break; } - cdSize = (m_Position - 4) - cdAbsOffset; + cdSize = _cnt - processedCnt_start; + + #ifdef ZIP_SELF_CHECK + if (!IsMultiVol) + { + if (_cnt != GetVirtStreamPos() - ArcInfo.MarkerPos2) + return E_FAIL; + if (cdSize != (GetVirtStreamPos() - 4) - cdAbsOffset) + return E_FAIL; + } + #endif + needSetBase = true; numCdItems = cdItems.Size(); + cdRelatOffset = (UInt64)((Int64)cdAbsOffset - ArcInfo.Base); if (!cdItems.IsEmpty()) { @@ -1886,13 +2888,13 @@ HRESULT CInArchive::ReadHeaders2(CObjectVector &items) - CCdInfo ecd64; + CCdInfo cdInfo; CLocator locator; bool isZip64 = false; - const UInt64 ecd64AbsOffset = m_Position - 4; + const UInt64 ecd64AbsOffset = GetVirtStreamPos() - 4; int ecd64Disk = -1; - if (m_Signature == NSignature::kEcd64) + if (_signature == NSignature::kEcd64) { ecd64Disk = Vols.StreamIndex; @@ -1900,26 +2902,27 @@ HRESULT CInArchive::ReadHeaders2(CObjectVector &items) { const UInt64 recordSize = ReadUInt64(); - if (recordSize < kEcd64_MainSize) + if (recordSize < kEcd64_MainSize + || recordSize >= ((UInt64)1 << 62)) { HeadersError = true; return S_OK; } - + { const unsigned kBufSize = kEcd64_MainSize; Byte buf[kBufSize]; - SafeReadBytes(buf, kBufSize); - ecd64.ParseEcd64e(buf); + SafeRead(buf, kBufSize); + cdInfo.ParseEcd64e(buf); } - Skip64(recordSize - kEcd64_MainSize); + RINOK(Skip64(recordSize - kEcd64_MainSize, items.Size())) } - m_Signature = ReadUInt32(); + ReadSignature(); - if (m_Signature != NSignature::kEcd64Locator) + if (_signature != NSignature::kEcd64Locator) { HeadersError = true; return S_OK; @@ -1928,66 +2931,139 @@ HRESULT CInArchive::ReadHeaders2(CObjectVector &items) { const unsigned kBufSize = 16; Byte buf[kBufSize]; - SafeReadBytes(buf, kBufSize); + SafeRead(buf, kBufSize); locator.Parse(buf); + // we ignore the error, where some zip creators use (NumDisks == 0) + // if (locator.NumDisks == 0) HeadersWarning = true; } - m_Signature = ReadUInt32(); + ReadSignature(); } - if (m_Signature != NSignature::kEcd) + if (_signature != NSignature::kEcd) { HeadersError = true; return S_OK; } + CanStartNewVol = false; + // ---------- ECD ---------- CEcd ecd; { const unsigned kBufSize = kEcdSize - 4; Byte buf[kBufSize]; - SafeReadBytes(buf, kBufSize); + SafeRead(buf, kBufSize); ecd.Parse(buf); } - COPY_ECD_ITEM_16(ThisDisk); - COPY_ECD_ITEM_16(CdDisk); - COPY_ECD_ITEM_16(NumEntries_in_ThisDisk); - COPY_ECD_ITEM_16(NumEntries); - COPY_ECD_ITEM_32(Size); - COPY_ECD_ITEM_32(Offset); + COPY_ECD_ITEM_16(ThisDisk) + COPY_ECD_ITEM_16(CdDisk) + COPY_ECD_ITEM_16(NumEntries_in_ThisDisk) + COPY_ECD_ITEM_16(NumEntries) + COPY_ECD_ITEM_32(Size) + COPY_ECD_ITEM_32(Offset) + + bool cdOK = true; + + if ((UInt32)cdInfo.Size != (UInt32)cdSize) + { + // return S_FALSE; + cdOK = false; + } + + if (isZip64) + { + if (cdInfo.NumEntries != numCdItems + || cdInfo.Size != cdSize) + { + cdOK = false; + } + } + if (IsMultiVol) { - if (cdDisk != (int)ecd64.CdDisk) + if (cdDisk != cdInfo.CdDisk) HeadersError = true; } - else if (needSetBase) + else if (needSetBase && cdOK) { + const UInt64 oldBase = (UInt64)ArcInfo.Base; + // localsWereRead == true + // ArcInfo.Base == ArcInfo.MarkerPos2 + // cdRelatOffset == (cdAbsOffset - ArcInfo.Base) + if (isZip64) { if (ecd64Disk == Vols.StartVolIndex) { - ArcInfo.Base = ecd64AbsOffset - locator.Ecd64Offset; - // cdRelatOffset = ecd64.Offset; - needSetBase = false; + const Int64 newBase = (Int64)ecd64AbsOffset - (Int64)locator.Ecd64Offset; + if (newBase <= (Int64)ecd64AbsOffset) + { + if (!localsWereRead || newBase <= (Int64)ArcInfo.MarkerPos2) + { + ArcInfo.Base = newBase; + cdRelatOffset = (UInt64)((Int64)cdAbsOffset - newBase); + } + else + cdOK = false; + } } } - else + else if (numCdItems != 0) // we can't use ecd.Offset in empty archive? { if ((int)cdDisk == Vols.StartVolIndex) { - ArcInfo.Base = cdAbsOffset - ecd64.Offset; - cdRelatOffset = ecd64.Offset; - needSetBase = false; + const Int64 newBase = (Int64)cdAbsOffset - (Int64)cdInfo.Offset; + if (newBase <= (Int64)cdAbsOffset) + { + if (!localsWereRead || newBase <= (Int64)ArcInfo.MarkerPos2) + { + // cd can be more accurate, when it points before Locals + // so we change Base and cdRelatOffset + ArcInfo.Base = newBase; + cdRelatOffset = cdInfo.Offset; + } + else + { + // const UInt64 delta = ((UInt64)cdRelatOffset - cdInfo.Offset); + const UInt64 delta = ((UInt64)(newBase - ArcInfo.Base)); + if ((UInt32)delta == 0) + { + // we set Overflow32bit mode, only if there is (x<<32) offset + // between real_CD_offset_from_MarkerPos and CD_Offset_in_ECD. + // Base and cdRelatOffset unchanged + Overflow32bit = true; + } + else + cdOK = false; + } + } + else + cdOK = false; + } + } + // cdRelatOffset = cdAbsOffset - ArcInfo.Base; + + if (localsWereRead) + { + const UInt64 delta = (UInt64)((Int64)oldBase - ArcInfo.Base); + if (delta != 0) + { + FOR_VECTOR (i, items) + items[i].LocalHeaderPos += delta; } } } - EcdVolIndex = ecd64.ThisDisk; + if (!cdOK) + HeadersError = true; + + EcdVolIndex = cdInfo.ThisDisk; if (!IsMultiVol) { @@ -1997,55 +3073,81 @@ HRESULT CInArchive::ReadHeaders2(CObjectVector &items) Vols.MissingZip = false; } - UseDisk_in_SingleVol = true; - if (localsWereRead) { - if ((UInt64)ArcInfo.Base != ArcInfo.MarkerPos) - { - const UInt64 delta = ArcInfo.MarkerPos - ArcInfo.Base; - FOR_VECTOR (i, items) - items[i].LocalHeaderPos += delta; - } - if (EcdVolIndex != 0) { FOR_VECTOR (i, items) items[i].Disk = EcdVolIndex; } } + + UseDisk_in_SingleVol = true; } if (isZip64) { - if (ecd64.ThisDisk == 0 && ecd64AbsOffset != ArcInfo.Base + locator.Ecd64Offset - // || ecd64.NumEntries_in_ThisDisk != numCdItems - || ecd64.NumEntries != numCdItems - || ecd64.Size != cdSize - || (ecd64.Offset != cdRelatOffset && !items.IsEmpty())) + if ((cdInfo.ThisDisk == 0 && ecd64AbsOffset != (UInt64)(ArcInfo.Base + (Int64)locator.Ecd64Offset)) + // || cdInfo.NumEntries_in_ThisDisk != numCdItems + || cdInfo.NumEntries != numCdItems + || cdInfo.Size != cdSize + || (cdInfo.Offset != cdRelatOffset && !items.IsEmpty())) { HeadersError = true; return S_OK; } } - // ---------- merge Central Directory Items ---------- - - if (!cdItems.IsEmpty()) + if (cdOK && !cdItems.IsEmpty()) { - CObjectVector items2; + // ---------- merge Central Directory Items ---------- + + CRecordVector items2; + + int nextLocalIndex = 0; + + LocalsCenterMerged = true; FOR_VECTOR (i, cdItems) { + if (Callback) + if ((i & 0x3FFF) == 0) + { + const UInt64 numFiles64 = items.Size() + items2.Size(); + RINOK(Callback->SetCompleted(&numFiles64, &_cnt)) + } + const CItemEx &cdItem = cdItems[i]; - int index = FindItem(items, cdItem); + + int index = -1; + + if (nextLocalIndex != -1) + { + if ((unsigned)nextLocalIndex < items.Size()) + { + CItemEx &item = items[(unsigned)nextLocalIndex]; + if (item.Disk == cdItem.Disk && + (item.LocalHeaderPos == cdItem.LocalHeaderPos + || (Overflow32bit && (UInt32)item.LocalHeaderPos == cdItem.LocalHeaderPos))) + index = nextLocalIndex++; + else + nextLocalIndex = -1; + } + } + + if (index == -1) + index = FindItem(items, cdItem); + + // index = -1; + if (index == -1) { - items2.Add(cdItem); + items2.Add(i); HeadersError = true; continue; } - CItemEx &item = items[index]; + + CItemEx &item = items[(unsigned)index]; if (item.Name != cdItem.Name // || item.Name.Len() != cdItem.Name.Len() || item.PackSize != cdItem.PackSize @@ -2065,12 +3167,15 @@ HRESULT CInArchive::ReadHeaders2(CObjectVector &items) item.ExternalAttrib = cdItem.ExternalAttrib; item.Comment = cdItem.Comment; item.FromCentral = cdItem.FromCentral; + // 22.02: we force utf8 flag, if central header has utf8 flag + if (cdItem.Flags & NFileHeader::NFlags::kUtf8) + item.Flags |= NFileHeader::NFlags::kUtf8; } - items += items2; + FOR_VECTOR (k, items2) + items.Add(cdItems[items2[k]]); } - if (ecd.NumEntries < ecd.NumEntries_in_ThisDisk) HeadersError = true; @@ -2083,35 +3188,56 @@ HRESULT CInArchive::ReadHeaders2(CObjectVector &items) } } - if (ecd.NumEntries > items.Size()) - HeadersError = true; - if (isZip64) { - if (ecd64.NumEntries != items.Size()) + if (cdInfo.NumEntries != items.Size() + || (ecd.NumEntries != items.Size() && ecd.NumEntries != 0xFFFF)) HeadersError = true; } else { // old 7-zip could store 32-bit number of CD items to 16-bit field. - /* - if ((UInt16)ecd64.NumEntries == (UInt16)items.Size()) + // if (ecd.NumEntries != items.Size()) + if (ecd.NumEntries > items.Size()) HeadersError = true; - */ + + if (cdInfo.NumEntries != numCdItems) + { + if ((UInt16)cdInfo.NumEntries != (UInt16)numCdItems) + HeadersError = true; + else + Cd_NumEntries_Overflow_16bit = true; + } } ReadBuffer(ArcInfo.Comment, ecd.CommentSize); + _inBufMode = false; - _inBuffer.Free(); - if ((UInt16)ecd64.NumEntries != (UInt16)numCdItems - || (UInt32)ecd64.Size != (UInt32)cdSize - || ((UInt32)ecd64.Offset != (UInt32)cdRelatOffset && !items.IsEmpty())) + // DisableBufMode(); + // Buffer.Free(); + /* we can't clear buf varibles. we need them to calculate PhySize of archive */ + + if ((UInt16)cdInfo.NumEntries != (UInt16)numCdItems + || (UInt32)cdInfo.Size != (UInt32)cdSize + || ((UInt32)cdInfo.Offset != (UInt32)cdRelatOffset && !items.IsEmpty())) { // return S_FALSE; HeadersError = true; } - + + #ifdef ZIP_SELF_CHECK + if (localsWereRead) + { + const UInt64 endPos = ArcInfo.MarkerPos2 + _cnt; + if (endPos != (IsMultiVol ? Vols.TotalBytesSize : ArcInfo.FileEndPos)) + { + // there are some data after the end of archive or error in code; + return E_FAIL; + } + } + #endif + // printf("\nOpen OK"); return S_OK; } @@ -2121,40 +3247,59 @@ HRESULT CInArchive::ReadHeaders2(CObjectVector &items) HRESULT CInArchive::Open(IInStream *stream, const UInt64 *searchLimit, IArchiveOpenCallback *callback, CObjectVector &items) { - _inBufMode = false; items.Clear(); Close(); - ArcInfo.Clear(); UInt64 startPos; - RINOK(stream->Seek(0, STREAM_SEEK_CUR, &startPos)); - RINOK(stream->Seek(0, STREAM_SEEK_END, &ArcInfo.FileEndPos)); - m_Position = ArcInfo.FileEndPos; + RINOK(InStream_GetPos(stream, startPos)) + RINOK(InStream_GetSize_SeekToEnd(stream, ArcInfo.FileEndPos)) + _streamPos = ArcInfo.FileEndPos; StartStream = stream; + Stream = stream; Callback = callback; + + DisableBufMode(); bool volWasRequested = false; + if (!Disable_VolsRead) if (callback && (startPos == 0 || !searchLimit || *searchLimit != 0)) { + // we try to read volumes only if it's first call (offset == 0) or scan is allowed. volWasRequested = true; - RINOK(ReadVols()); + RINOK(ReadVols()) } - if (IsMultiVol && Vols.StartVolIndex != 0) + if (Disable_FindMarker) + { + RINOK(SeekToVol(-1, startPos)) + StreamRef = stream; + Stream = stream; + MarkerIsFound = true; + MarkerIsSafe = true; + ArcInfo.MarkerPos = startPos; + ArcInfo.MarkerPos2 = startPos; + } + else + if (IsMultiVol && Vols.StartParsingVol == 0 && (unsigned)Vols.StartParsingVol < Vols.Streams.Size()) { - Stream = Vols.Streams[0].Stream; - if (Stream) + // only StartParsingVol = 0 is safe search. + RINOK(SeekToVol(0, 0)) + // if (Stream) { - m_Position = 0; - RINOK(Stream->Seek(0, STREAM_SEEK_SET, NULL)); - UInt64 limit = 0; - HRESULT res = FindMarker(Stream, &limit); + // UInt64 limit = 1 << 22; // for sfx + UInt64 limit = 0; // without sfx + + HRESULT res = FindMarker(&limit); + if (res == S_OK) + { MarkerIsFound = true; + MarkerIsSafe = true; + } else if (res != S_FALSE) return res; } @@ -2162,56 +3307,95 @@ HRESULT CInArchive::Open(IInStream *stream, const UInt64 *searchLimit, else { // printf("\nOpen offset = %u\n", (unsigned)startPos); - RINOK(stream->Seek(startPos, STREAM_SEEK_SET, NULL)); - m_Position = startPos; - HRESULT res = FindMarker(stream, searchLimit); - UInt64 curPos = m_Position; + if (IsMultiVol + && (unsigned)Vols.StartParsingVol < Vols.Streams.Size() + && Vols.Streams[(unsigned)Vols.StartParsingVol].Stream) + { + RINOK(SeekToVol(Vols.StartParsingVol, Vols.StreamIndex == Vols.StartVolIndex ? startPos : 0)) + } + else + { + RINOK(SeekToVol(-1, startPos)) + } + + // UInt64 limit = 1 << 22; + // HRESULT res = FindMarker(&limit); + + HRESULT res = FindMarker(searchLimit); + + // const UInt64 curPos = GetVirtStreamPos(); + const UInt64 curPos = ArcInfo.MarkerPos2 + 4; + if (res == S_OK) MarkerIsFound = true; - else + else if (!IsMultiVol) { - // if (res != S_FALSE) + /* + // if (startPos != 0), probably CD could be already tested with another call with (startPos == 0). + // so we don't want to try to open CD again in that case. + if (startPos != 0) + return res; + // we can try to open CD, if there is no Marker and (startPos == 0). + // is it OK to open such files as ZIP, or big number of false positive, when CD can be find in end of file ? + */ return res; } - - MarkerIsFound = true; if (ArcInfo.IsSpanMode && !volWasRequested) { - RINOK(ReadVols()); + RINOK(ReadVols()) + if (IsMultiVol && MarkerIsFound && ArcInfo.MarkerVolIndex < 0) + ArcInfo.MarkerVolIndex = Vols.StartVolIndex; } + + MarkerIsSafe = !IsMultiVol + || (ArcInfo.MarkerVolIndex == 0 && ArcInfo.MarkerPos == 0) + ; - if (IsMultiVol && (unsigned)Vols.StartVolIndex < Vols.Streams.Size()) + + if (IsMultiVol) { - Stream = Vols.Streams[Vols.StartVolIndex].Stream; - if (!Stream) - IsMultiVol = false; - else + if ((unsigned)Vols.StartVolIndex < Vols.Streams.Size()) { - RINOK(Stream->Seek(curPos, STREAM_SEEK_SET, NULL)); - m_Position = curPos; + Stream = Vols.Streams[(unsigned)Vols.StartVolIndex].Stream; + if (Stream) + { + RINOK(Seek_SavePos(curPos)) + } + else + IsMultiVol = false; } + else + IsMultiVol = false; } - else - IsMultiVol = false; if (!IsMultiVol) { - RINOK(stream->Seek(curPos, STREAM_SEEK_SET, NULL)); - m_Position = curPos; + if (Vols.StreamIndex != -1) + { + Stream = StartStream; + Vols.StreamIndex = -1; + InitBuf(); + RINOK(Seek_SavePos(curPos)) + } + + ArcInfo.MarkerVolIndex = -1; StreamRef = stream; Stream = stream; } } + if (!IsMultiVol) + Vols.ClearRefs(); + { HRESULT res; try { - res = ReadHeaders2(items); + res = ReadHeaders(items); } - catch (const CInBufferException &e) { res = e.ErrorCode; } + catch (const CSystemException &e) { res = e.ErrorCode; } catch (const CUnexpectEnd &) { if (items.IsEmpty()) @@ -2221,7 +3405,7 @@ HRESULT CInArchive::Open(IInStream *stream, const UInt64 *searchLimit, } catch (...) { - _inBufMode = false; + DisableBufMode(); throw; } @@ -2229,16 +3413,17 @@ HRESULT CInArchive::Open(IInStream *stream, const UInt64 *searchLimit, { ArcInfo.FinishPos = ArcInfo.FileEndPos; if ((unsigned)Vols.StreamIndex < Vols.Streams.Size()) - if (m_Position < Vols.Streams[Vols.StreamIndex].Size) + if (GetVirtStreamPos() < Vols.Streams[(unsigned)Vols.StreamIndex].Size) ArcInfo.ThereIsTail = true; } else { - ArcInfo.FinishPos = m_Position; - ArcInfo.ThereIsTail = (ArcInfo.FileEndPos > m_Position); + ArcInfo.FinishPos = GetVirtStreamPos(); + ArcInfo.ThereIsTail = (ArcInfo.FileEndPos > ArcInfo.FinishPos); } - _inBufMode = false; + DisableBufMode(); + IsArcOpen = true; if (!IsMultiVol) Vols.Streams.Clear(); @@ -2259,8 +3444,8 @@ HRESULT CInArchive::GetItemStream(const CItemEx &item, bool seekPackData, CMyCom { if (UseDisk_in_SingleVol && item.Disk != EcdVolIndex) return S_OK; - pos += ArcInfo.Base; - RINOK(StreamRef->Seek(pos, STREAM_SEEK_SET, NULL)); + pos = (UInt64)((Int64)pos + ArcInfo.Base); + RINOK(InStream_SeekSet(StreamRef, pos)) stream = StreamRef; return S_OK; } @@ -2271,10 +3456,10 @@ HRESULT CInArchive::GetItemStream(const CItemEx &item, bool seekPackData, CMyCom IInStream *str2 = Vols.Streams[item.Disk].Stream; if (!str2) return S_OK; - RINOK(str2->Seek(pos, STREAM_SEEK_SET, NULL)); + RINOK(InStream_SeekSet(str2, pos)) Vols.NeedSeek = false; - Vols.StreamIndex = item.Disk; + Vols.StreamIndex = (int)item.Disk; CVolStream *volsStreamSpec = new CVolStream; volsStreamSpec->Vols = &Vols; diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipIn.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipIn.h index 9b0afe28..bea26dc9 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipIn.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipIn.h @@ -1,14 +1,14 @@ // Archive/ZipIn.h -#ifndef __ZIP_IN_H -#define __ZIP_IN_H +#ifndef ZIP7_INC_ZIP_IN_H +#define ZIP7_INC_ZIP_IN_H +#include "../../../Common/MyBuffer2.h" #include "../../../Common/MyCom.h" +#include "../../Common/StreamUtils.h" #include "../../IStream.h" -#include "../../Common/InBuffer.h" - #include "ZipHeader.h" #include "ZipItem.h" @@ -21,11 +21,23 @@ class CItemEx: public CItem { public: UInt32 LocalFullHeaderSize; // including Name and Extra + // int ParentOfAltStream; // -1, if not AltStream + bool DescriptorWasRead; + + CItemEx(): + // ParentOfAltStream(-1), + DescriptorWasRead(false) {} + UInt64 GetLocalFullSize() const - { return LocalFullHeaderSize + PackSize + (HasDescriptor() ? kDataDescriptorSize : 0); } + { return LocalFullHeaderSize + GetPackSizeWithDescriptor(); } UInt64 GetDataPosition() const { return LocalHeaderPos + LocalFullHeaderSize; } + + bool IsBadDescriptor() const + { + return !FromCentral && FromLocal && HasDescriptor() && !DescriptorWasRead; + } }; @@ -52,6 +64,10 @@ struct CInArchiveInfo UInt64 FirstItemRelatOffset; /* Relative offset of first local (read from cd) (relative to Base). = 0 in most archives = size of stub for some SFXs */ + + + int MarkerVolIndex; + bool CdWasRead; bool IsSpanMode; bool ThereIsTail; @@ -68,6 +84,7 @@ struct CInArchiveInfo FinishPos(0), FileEndPos(0), FirstItemRelatOffset(0), + MarkerVolIndex(-1), CdWasRead(false), IsSpanMode(false), ThereIsTail(false) @@ -82,6 +99,7 @@ struct CInArchiveInfo MarkerPos2 = 0; FinishPos = 0; FileEndPos = 0; + MarkerVolIndex = -1; ThereIsTail = false; FirstItemRelatOffset = 0; @@ -96,6 +114,10 @@ struct CInArchiveInfo struct CCdInfo { + bool IsFromEcd64; + + UInt16 CommentSize; + // 64 UInt16 VersionMade; UInt16 VersionNeedExtract; @@ -108,39 +130,56 @@ struct CCdInfo UInt64 Size; UInt64 Offset; - UInt16 CommentSize; - - CCdInfo() { memset(this, 0, sizeof(*this)); } + CCdInfo() { memset(this, 0, sizeof(*this)); IsFromEcd64 = false; } void ParseEcd32(const Byte *p); // (p) includes signature void ParseEcd64e(const Byte *p); // (p) exclude signature + + bool IsEmptyArc() const + { + return ThisDisk == 0 + && CdDisk == 0 + && NumEntries_in_ThisDisk == 0 + && NumEntries == 0 + && Size == 0 + && Offset == 0 // test it + ; + } }; -class CVols +struct CVols { -public: - struct CSubStreamInfo { CMyComPtr Stream; UInt64 Size; + HRESULT SeekToStart() const { return InStream_SeekToBegin(Stream); } + CSubStreamInfo(): Size(0) {} }; CObjectVector Streams; - int StreamIndex; + + int StreamIndex; // -1 for StartStream + // -2 for ZipStream at multivol detection code + // >=0 volume index in multivol + bool NeedSeek; - CMyComPtr ZipStream; - + bool DisableVolsSearch; bool StartIsExe; // is .exe bool StartIsZ; // is .zip or .zNN bool StartIsZip; // is .zip bool IsUpperCase; bool MissingZip; - Int32 StartVolIndex; // = (NN - 1), if StartStream is .zNN + + bool ecd_wasRead; + + Int32 StartVolIndex; // -1, if unknown vol index + // = (NN - 1), if StartStream is .zNN + // = 0, if start vol is exe Int32 StartParsingVol; // if we need local parsing, we must use that stream unsigned NumVols; @@ -148,19 +187,28 @@ class CVols int EndVolIndex; // index of last volume (ecd volume), // -1, if is not multivol - UString BaseName; // including '.' - + UString BaseName; // name of archive including '.' UString MissingName; + CMyComPtr ZipStream; + CCdInfo ecd; - bool ecd_wasRead; + + UInt64 TotalBytesSize; // for MultiVol only + + void ClearRefs() + { + Streams.Clear(); + ZipStream.Release(); + TotalBytesSize = 0; + } void Clear() { StreamIndex = -1; NeedSeek = false; - + DisableVolsSearch = false; StartIsExe = false; StartIsZ = false; StartIsZip = false; @@ -177,77 +225,95 @@ class CVols MissingZip = false; ecd_wasRead = false; - Streams.Clear(); - ZipStream.Release(); + ClearRefs(); } HRESULT ParseArcName(IArchiveOpenVolumeCallback *volCallback); HRESULT Read(void *data, UInt32 size, UInt32 *processedSize); - - UInt64 GetTotalSize() const - { - UInt64 total = 0; - FOR_VECTOR (i, Streams) - total += Streams[i].Size; - return total; - } }; -class CVolStream: - public ISequentialInStream, - public CMyUnknownImp -{ +Z7_CLASS_IMP_COM_1( + CVolStream + , ISequentialInStream +) public: CVols *Vols; - - MY_UNKNOWN_IMP1(ISequentialInStream) - - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); }; class CInArchive { - CInBuffer _inBuffer; + CMidBuffer Buffer; + size_t _bufPos; + size_t _bufCached; + + UInt64 _streamPos; + UInt64 _cnt; + + // UInt32 _startLocalFromCd_Disk; + // UInt64 _startLocalFromCd_Offset; + + size_t GetAvail() const { return _bufCached - _bufPos; } + + void InitBuf() { _bufPos = 0; _bufCached = 0; } + void DisableBufMode() { InitBuf(); _inBufMode = false; } + + void SkipLookahed(size_t skip) + { + _bufPos += skip; + _cnt += skip; + } + + HRESULT AllocateBuffer(size_t size); + + UInt64 GetVirtStreamPos() { return _streamPos - _bufCached + _bufPos; } + bool _inBufMode; - UInt32 m_Signature; - UInt64 m_Position; - UInt64 _processedCnt; - + bool IsArcOpen; bool CanStartNewVol; + UInt32 _signature; + CMyComPtr StreamRef; IInStream *Stream; IInStream *StartStream; + IArchiveOpenCallback *Callback; - bool IsArcOpen; + HRESULT Seek_SavePos(UInt64 offset); + HRESULT SeekToVol(int volIndex, UInt64 offset); + + HRESULT ReadFromCache(Byte *data, unsigned size, unsigned &processed); + HRESULT ReadFromCache_FALSE(Byte *data, unsigned size); HRESULT ReadVols2(IArchiveOpenVolumeCallback *volCallback, unsigned start, int lastDisk, int zipDisk, unsigned numMissingVolsMax, unsigned &numMissingVols); HRESULT ReadVols(); - HRESULT Seek(UInt64 offset); - HRESULT FindMarker(IInStream *stream, const UInt64 *searchLimit); - HRESULT IncreaseRealPosition(Int64 addValue, bool &isFinished); + HRESULT FindMarker(const UInt64 *searchLimit); + HRESULT IncreaseRealPosition(UInt64 addValue, bool &isFinished); - HRESULT ReadBytes(void *data, UInt32 size, UInt32 *processedSize); - void SafeReadBytes(void *data, unsigned size); + HRESULT LookAhead(size_t minRequiredInBuffer); + void SafeRead(Byte *data, unsigned size); void ReadBuffer(CByteBuffer &buffer, unsigned size); - Byte ReadByte(); - UInt16 ReadUInt16(); + // Byte ReadByte(); + // UInt16 ReadUInt16(); UInt32 ReadUInt32(); UInt64 ReadUInt64(); - void Skip(unsigned num); - void Skip64(UInt64 num); - void ReadFileName(unsigned nameSize, AString &dest); - bool ReadExtra(unsigned extraSize, CExtraBlock &extraBlock, - UInt64 &unpackSize, UInt64 &packSize, UInt64 &localHeaderOffset, UInt32 &diskStartNumber); + void ReadSignature(); + + void Skip(size_t num); + HRESULT Skip64(UInt64 num, unsigned numFiles); + + bool ReadFileName(unsigned nameSize, AString &dest); + + bool ReadExtra(const CLocalItem &item, unsigned extraSize, CExtraBlock &extra, + UInt64 &unpackSize, UInt64 &packSize, CItem *cdItem); bool ReadLocalItem(CItemEx &item); - HRESULT ReadLocalItemDescriptor(CItemEx &item); + HRESULT FindDescriptor(CItemEx &item, unsigned numFiles); HRESULT ReadCdItem(CItemEx &item); HRESULT TryEcd64(UInt64 offset, CCdInfo &cdInfo); HRESULT FindCd(bool checkOffsetMode); @@ -255,38 +321,58 @@ class CInArchive HRESULT ReadCd(CObjectVector &items, UInt32 &cdDisk, UInt64 &cdOffset, UInt64 &cdSize); HRESULT ReadLocals(CObjectVector &localItems); - HRESULT ReadHeaders2(CObjectVector &items); + HRESULT ReadHeaders(CObjectVector &items); HRESULT GetVolStream(unsigned vol, UInt64 pos, CMyComPtr &stream); + public: CInArchiveInfo ArcInfo; bool IsArc; bool IsZip64; + + bool IsApk; + bool IsCdUnsorted; + bool HeadersError; bool HeadersWarning; bool ExtraMinorError; bool UnexpectedEnd; + bool LocalsWereRead; + bool LocalsCenterMerged; bool NoCentralDir; + bool Overflow32bit; // = true, if zip without Zip64 extension support and it has some fields values truncated to 32-bits. + bool Cd_NumEntries_Overflow_16bit; // = true, if no Zip64 and 16-bit ecd:NumEntries was overflowed. bool MarkerIsFound; + bool MarkerIsSafe; bool IsMultiVol; bool UseDisk_in_SingleVol; UInt32 EcdVolIndex; CVols Vols; - - IArchiveOpenCallback *Callback; - CInArchive(): Stream(NULL), Callback(NULL), IsArcOpen(false) {} + bool Force_ReadLocals_Mode; + bool Disable_VolsRead; + bool Disable_FindMarker; + + CInArchive(): + IsArcOpen(false), + Stream(NULL), + StartStream(NULL), + Callback(NULL), + Force_ReadLocals_Mode(false), + Disable_VolsRead(false), + Disable_FindMarker(false) + {} UInt64 GetPhySize() const { if (IsMultiVol) return ArcInfo.FinishPos; else - return ArcInfo.FinishPos - ArcInfo.Base; + return (UInt64)((Int64)ArcInfo.FinishPos - ArcInfo.Base); } UInt64 GetOffset() const @@ -294,14 +380,13 @@ class CInArchive if (IsMultiVol) return 0; else - return ArcInfo.Base; + return (UInt64)ArcInfo.Base; } void ClearRefs(); void Close(); HRESULT Open(IInStream *stream, const UInt64 *searchLimit, IArchiveOpenCallback *callback, CObjectVector &items); - HRESULT ReadHeaders(CObjectVector &items); bool IsOpen() const { return IsArcOpen; } @@ -321,16 +406,20 @@ class CInArchive UInt64 GetEmbeddedStubSize() const { + // it's possible that first item in CD doesn refers to first local item + // so FirstItemRelatOffset is not first local item + if (ArcInfo.CdWasRead) return ArcInfo.FirstItemRelatOffset; if (IsMultiVol) return 0; - return ArcInfo.MarkerPos2 - ArcInfo.Base; + return (UInt64)((Int64)ArcInfo.MarkerPos2 - ArcInfo.Base); } - HRESULT ReadLocalItemAfterCdItem(CItemEx &item, bool &isAvail); - HRESULT ReadLocalItemAfterCdItemFull(CItemEx &item); + HRESULT CheckDescriptor(const CItemEx &item); + HRESULT Read_LocalItem_After_CdItem(CItemEx &item, bool &isAvail, bool &headersError); + HRESULT Read_LocalItem_After_CdItem_Full(CItemEx &item); HRESULT GetItemStream(const CItemEx &item, bool seekPackData, CMyComPtr &stream); @@ -343,7 +432,9 @@ class CInArchive || ArcInfo.Base < 0 || (Int64)ArcInfo.MarkerPos2 < ArcInfo.Base || ArcInfo.ThereIsTail - || GetEmbeddedStubSize() != 0) + || GetEmbeddedStubSize() != 0 + || IsApk + || IsCdUnsorted) return false; // 7-zip probably can update archives with embedded stubs. diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipItem.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipItem.cpp index e732df7c..5684cac0 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipItem.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipItem.cpp @@ -5,9 +5,12 @@ #include "../../../../C/CpuArch.h" #include "../../../../C/7zCrc.h" +#include "../../../Common/IntToString.h" #include "../../../Common/MyLinux.h" #include "../../../Common/StringConvert.h" +#include "../../../Windows/PropVariantUtils.h" + #include "../Common/ItemNameUtils.h" #include "ZipItem.h" @@ -17,6 +20,106 @@ namespace NZip { using namespace NFileHeader; + +/* +const char *k_SpecName_NTFS_STREAM = "@@NTFS@STREAM@"; +const char *k_SpecName_MAC_RESOURCE_FORK = "@@MAC@RESOURCE-FORK@"; +*/ + +static const CUInt32PCharPair g_ExtraTypes[] = +{ + { NExtraID::kZip64, "Zip64" }, + { NExtraID::kNTFS, "NTFS" }, + { NExtraID::kUnix0, "UNIX" }, + { NExtraID::kStrongEncrypt, "StrongCrypto" }, + { NExtraID::kUnixTime, "UT" }, + { NExtraID::kUnix1, "UX" }, + { NExtraID::kUnix2, "Ux" }, + { NExtraID::kUnixN, "ux" }, + { NExtraID::kIzUnicodeComment, "uc" }, + { NExtraID::kIzUnicodeName, "up" }, + { NExtraID::kIzNtSecurityDescriptor, "SD" }, + { NExtraID::kWzAES, "WzAES" }, + { NExtraID::kApkAlign, "ApkAlign" } +}; + +void CExtraSubBlock::PrintInfo(AString &s) const +{ + for (unsigned i = 0; i < Z7_ARRAY_SIZE(g_ExtraTypes); i++) + { + const CUInt32PCharPair &pair = g_ExtraTypes[i]; + if (pair.Value == ID) + { + s += pair.Name; + if (ID == NExtraID::kUnixTime) + { + if (Data.Size() >= 1) + { + s.Add_Colon(); + const Byte flags = Data[0]; + if (flags & 1) s.Add_Char('M'); + if (flags & 2) s.Add_Char('A'); + if (flags & 4) s.Add_Char('C'); + const UInt32 size = (UInt32)(Data.Size()) - 1; + if (size % 4 == 0) + { + s.Add_Colon(); + s.Add_UInt32(size / 4); + } + } + } + /* + if (ID == NExtraID::kApkAlign && Data.Size() >= 2) + { + char sz[32]; + sz[0] = ':'; + ConvertUInt32ToHex(GetUi16(Data), sz + 1); + s += sz; + for (unsigned j = 2; j < Data.Size(); j++) + { + char sz[32]; + sz[0] = '-'; + ConvertUInt32ToHex(Data[j], sz + 1); + s += sz; + } + } + */ + return; + } + } + { + char sz[16]; + sz[0] = '0'; + sz[1] = 'x'; + ConvertUInt32ToHex(ID, sz + 2); + s += sz; + } +} + + +void CExtraBlock::PrintInfo(AString &s) const +{ + if (Error) + s.Add_OptSpaced("Extra_ERROR"); + + if (MinorError) + s.Add_OptSpaced("Minor_Extra_ERROR"); + + if (IsZip64 || IsZip64_Error) + { + s.Add_OptSpaced("Zip64"); + if (IsZip64_Error) + s += "_ERROR"; + } + + FOR_VECTOR (i, SubBlocks) + { + s.Add_Space_if_NotEmpty(); + SubBlocks[i].PrintInfo(s); + } +} + + bool CExtraSubBlock::ExtractNtfsTime(unsigned index, FILETIME &ft) const { ft.dwHighDateTime = ft.dwLowDateTime = 0; @@ -48,14 +151,22 @@ bool CExtraSubBlock::ExtractNtfsTime(unsigned index, FILETIME &ft) const return false; } -bool CExtraSubBlock::ExtractUnixTime(bool isCentral, unsigned index, UInt32 &res) const +bool CExtraSubBlock::Extract_UnixTime(bool isCentral, unsigned index, UInt32 &res) const { + /* Info-Zip : + The central-header extra field contains the modification + time only, or no timestamp at all. + Size of Data is used to flag its presence or absence + If "Flags" indicates that Modtime is present in the local header + field, it MUST be present in the central header field, too + */ + res = 0; UInt32 size = (UInt32)Data.Size(); if (ID != NExtraID::kUnixTime || size < 5) return false; const Byte *p = (const Byte *)Data; - Byte flags = *p++; + const Byte flags = *p++; size--; if (isCentral) { @@ -83,6 +194,36 @@ bool CExtraSubBlock::ExtractUnixTime(bool isCentral, unsigned index, UInt32 &res } +// Info-ZIP's abandoned "Unix1 timestamps & owner ID info" + +bool CExtraSubBlock::Extract_Unix01_Time(unsigned index, UInt32 &res) const +{ + res = 0; + const unsigned offset = index * 4; + if (Data.Size() < offset + 4) + return false; + if (ID != NExtraID::kUnix0 && + ID != NExtraID::kUnix1) + return false; + const Byte *p = (const Byte *)Data + offset; + res = GetUi32(p); + return true; +} + +/* +// PKWARE's Unix "extra" is similar to Info-ZIP's abandoned "Unix1 timestamps" +bool CExtraSubBlock::Extract_Unix_Time(unsigned index, UInt32 &res) const +{ + res = 0; + const unsigned offset = index * 4; + if (ID != NExtraID::kUnix0 || Data.Size() < offset) + return false; + const Byte *p = (const Byte *)Data + offset; + res = GetUi32(p); + return true; +} +*/ + bool CExtraBlock::GetNtfsTime(unsigned index, FILETIME &ft) const { FOR_VECTOR (i, SubBlocks) @@ -96,11 +237,30 @@ bool CExtraBlock::GetNtfsTime(unsigned index, FILETIME &ft) const bool CExtraBlock::GetUnixTime(bool isCentral, unsigned index, UInt32 &res) const { - FOR_VECTOR (i, SubBlocks) { - const CExtraSubBlock &sb = SubBlocks[i]; - if (sb.ID == NFileHeader::NExtraID::kUnixTime) - return sb.ExtractUnixTime(isCentral, index, res); + FOR_VECTOR (i, SubBlocks) + { + const CExtraSubBlock &sb = SubBlocks[i]; + if (sb.ID == NFileHeader::NExtraID::kUnixTime) + return sb.Extract_UnixTime(isCentral, index, res); + } + } + + switch (index) + { + case NUnixTime::kMTime: index = NUnixExtra::kMTime; break; + case NUnixTime::kATime: index = NUnixExtra::kATime; break; + default: return false; + } + + { + FOR_VECTOR (i, SubBlocks) + { + const CExtraSubBlock &sb = SubBlocks[i]; + if (sb.ID == NFileHeader::NExtraID::kUnix0 || + sb.ID == NFileHeader::NExtraID::kUnix1) + return sb.Extract_Unix01_Time(index, res); + } } return false; } @@ -113,6 +273,7 @@ bool CLocalItem::IsDir() const bool CItem::IsDir() const { + // FIXME: we can check InfoZip UTF-8 name at first. if (NItemName::HasTailSlash(Name, GetCodePage())) return true; @@ -130,6 +291,7 @@ bool CItem::IsDir() const case NHostOS::kHPFS: case NHostOS::kVFAT: return true; + default: break; } } @@ -179,8 +341,27 @@ UInt32 CItem::GetWinAttrib() const case NHostOS::kUnix: // do we need to clear 16 low bits in this case? if (FromCentral) + { + /* + Some programs write posix attributes in high 16 bits of ExternalAttrib + Also some programs can write additional marker flag: + 0x8000 - p7zip + 0x4000 - Zip in MacOS + no marker - Info-Zip + + Client code has two options to detect posix field: + 1) check 0x8000 marker. In that case we must add 0x8000 marker here. + 2) check that high 4 bits (file type bits in posix field) of attributes are not zero. + */ + winAttrib = ExternalAttrib & 0xFFFF0000; + + // #ifndef _WIN32 + winAttrib |= 0x8000; // add posix mode marker + // #endif + } break; + default: break; } if (IsDir()) // test it; winAttrib |= FILE_ATTRIBUTE_DIRECTORY; @@ -201,10 +382,30 @@ bool CItem::GetPosixAttrib(UInt32 &attrib) const return false; } + +bool CExtraSubBlock::CheckIzUnicode(const AString &s) const +{ + size_t size = Data.Size(); + if (size < 1 + 4) + return false; + const Byte *p = (const Byte *)Data; + if (p[0] > 1) + return false; + if (CrcCalc(s, s.Len()) != GetUi32(p + 1)) + return false; + size -= 5; + p += 5; + for (size_t i = 0; i < size; i++) + if (p[i] == 0) + return false; + return Check_UTF8_Buf((const char *)(const void *)p, size, false); +} + + void CItem::GetUnicodeString(UString &res, const AString &s, bool isComment, bool useSpecifiedCodePage, UINT codePage) const { bool isUtf8 = IsUtf8(); - bool ignore_Utf8_Errors = true; + // bool ignore_Utf8_Errors = true; if (!isUtf8) { @@ -219,10 +420,14 @@ void CItem::GetUnicodeString(UString &res, const AString &s, bool isComment, boo const CExtraSubBlock &sb = subBlocks[i]; if (sb.ID == id) { - AString utf; - if (sb.ExtractIzUnicode(CrcCalc(s, s.Len()), utf)) - if (ConvertUTF8ToUnicode(utf, res)) + if (sb.CheckIzUnicode(s)) + { + // const unsigned kIzUnicodeHeaderSize = 5; + if (Convert_UTF8_Buf_To_Unicode( + (const char *)(const void *)(const Byte *)sb.Data + 5, + sb.Data.Size() - 5, res)) return; + } break; } } @@ -237,15 +442,21 @@ void CItem::GetUnicodeString(UString &res, const AString &s, bool isComment, boo We try to get name as UTF-8. Do we need to do it in POSIX version also? */ isUtf8 = true; - ignore_Utf8_Errors = false; + + /* 21.02: we want to ignore UTF-8 errors to support file paths that are mixed + of UTF-8 and non-UTF-8 characters. */ + // ignore_Utf8_Errors = false; + // ignore_Utf8_Errors = true; } #endif } if (isUtf8) - if (ConvertUTF8ToUnicode(s, res) || ignore_Utf8_Errors) - return; + { + ConvertUTF8ToUnicode(s, res); + return; + } MultiByteToUnicodeString2(res, s, useSpecifiedCodePage ? codePage : GetCodePage()); } diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipItem.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipItem.h index c134ec79..4a25de3d 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipItem.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipItem.h @@ -1,7 +1,7 @@ // Archive/ZipItem.h -#ifndef __ARCHIVE_ZIP_ITEM_H -#define __ARCHIVE_ZIP_ITEM_H +#ifndef ZIP7_INC_ARCHIVE_ZIP_ITEM_H +#define ZIP7_INC_ARCHIVE_ZIP_ITEM_H #include "../../../../C/CpuArch.h" @@ -14,6 +14,11 @@ namespace NArchive { namespace NZip { +/* +extern const char *k_SpecName_NTFS_STREAM; +extern const char *k_SpecName_MAC_RESOURCE_FORK; +*/ + struct CVersion { Byte Version; @@ -22,28 +27,17 @@ struct CVersion struct CExtraSubBlock { - UInt16 ID; + UInt32 ID; CByteBuffer Data; bool ExtractNtfsTime(unsigned index, FILETIME &ft) const; - bool ExtractUnixTime(bool isCentral, unsigned index, UInt32 &res) const; - - bool ExtractIzUnicode(UInt32 crc, AString &name) const - { - unsigned size = (unsigned)Data.Size(); - if (size < 1 + 4) - return false; - const Byte *p = (const Byte *)Data; - if (p[0] > 1) - return false; - if (crc != GetUi32(p + 1)) - return false; - size -= 5; - name.SetFrom_CalcLen((const char *)p + 5, size); - if (size != name.Len()) - return false; - return CheckUTF8(name, false); - } + bool Extract_UnixTime(bool isCentral, unsigned index, UInt32 &res) const; + bool Extract_Unix01_Time(unsigned index, UInt32 &res) const; + // bool Extract_Unix_Time(unsigned index, UInt32 &res) const; + + bool CheckIzUnicode(const AString &s) const; + + void PrintInfo(AString &s) const; }; const unsigned k_WzAesExtra_Size = 7; @@ -129,11 +123,22 @@ struct CStrongCryptoExtra bool CertificateIsUsed() const { return (Flags > 0x0001); } }; + struct CExtraBlock { CObjectVector SubBlocks; + bool Error; + bool MinorError; + bool IsZip64; + bool IsZip64_Error; - void Clear() { SubBlocks.Clear(); } + CExtraBlock(): Error(false), MinorError(false), IsZip64(false), IsZip64_Error(false) {} + + void Clear() + { + SubBlocks.Clear(); + IsZip64 = false; + } size_t GetSize() const { @@ -176,13 +181,21 @@ struct CExtraBlock bool GetNtfsTime(unsigned index, FILETIME &ft) const; bool GetUnixTime(bool isCentral, unsigned index, UInt32 &res) const; + void PrintInfo(AString &s) const; + void RemoveUnknownSubBlocks() { for (unsigned i = SubBlocks.Size(); i != 0;) { i--; - if (SubBlocks[i].ID != NFileHeader::NExtraID::kWzAES) - SubBlocks.Delete(i); + switch (SubBlocks[i].ID) + { + case NFileHeader::NExtraID::kStrongEncrypt: + case NFileHeader::NExtraID::kWzAES: + break; + default: + SubBlocks.Delete(i); + } } } }; @@ -193,6 +206,12 @@ class CLocalItem public: UInt16 Flags; UInt16 Method; + + /* + Zip specification doesn't mention that ExtractVersion field uses HostOS subfield. + 18.06: 7-Zip now doesn't use ExtractVersion::HostOS to detect codePage + */ + CVersion ExtractVersion; UInt64 Size; @@ -206,12 +225,20 @@ class CLocalItem CExtraBlock LocalExtra; + unsigned GetDescriptorSize() const { return LocalExtra.IsZip64 ? kDataDescriptorSize64 : kDataDescriptorSize32; } + + UInt64 GetPackSizeWithDescriptor() const + { return PackSize + (HasDescriptor() ? GetDescriptorSize() : 0); } + bool IsUtf8() const { return (Flags & NFileHeader::NFlags::kUtf8) != 0; } bool IsEncrypted() const { return (Flags & NFileHeader::NFlags::kEncrypted) != 0; } bool IsStrongEncrypted() const { return IsEncrypted() && (Flags & NFileHeader::NFlags::kStrongEncrypted) != 0; } bool IsAesEncrypted() const { return IsEncrypted() && (IsStrongEncrypted() || Method == NFileHeader::NCompressionMethod::kWzAES); } bool IsLzmaEOS() const { return (Flags & NFileHeader::NFlags::kLzmaEOS) != 0; } bool HasDescriptor() const { return (Flags & NFileHeader::NFlags::kDescriptorUsedMask) != 0; } + // bool IsAltStream() const { return (Flags & NFileHeader::NFlags::kAltStream) != 0; } + + unsigned GetDeflateLevel() const { return (Flags >> 1) & 3; } bool IsDir() const; @@ -231,9 +258,9 @@ class CLocalItem void SetFlag(unsigned bitMask, bool enable) { if (enable) - Flags |= bitMask; + Flags = (UInt16)(Flags | bitMask); else - Flags &= ~bitMask; + Flags = (UInt16)(Flags & ~bitMask); } public: @@ -241,9 +268,15 @@ class CLocalItem void ClearFlags() { Flags = 0; } void SetEncrypted(bool encrypted) { SetFlag(NFileHeader::NFlags::kEncrypted, encrypted); } void SetUtf8(bool isUtf8) { SetFlag(NFileHeader::NFlags::kUtf8, isUtf8); } + // void SetFlag_AltStream(bool isAltStream) { SetFlag(NFileHeader::NFlags::kAltStream, isAltStream); } void SetDescriptorMode(bool useDescriptor) { SetFlag(NFileHeader::NFlags::kDescriptorUsedMask, useDescriptor); } - UINT GetCodePage() const { return CP_OEMCP; } + UINT GetCodePage() const + { + if (IsUtf8()) + return CP_UTF8; + return CP_OEMCP; + } }; @@ -279,7 +312,8 @@ class CItem: public CLocalItem UInt32 GetWinAttrib() const; bool GetPosixAttrib(UInt32 &attrib) const; - Byte GetHostOS() const { return FromCentral ? MadeByVersion.HostOS : ExtractVersion.HostOS; } + // 18.06: 0 instead of ExtractVersion.HostOS for local item + Byte GetHostOS() const { return FromCentral ? MadeByVersion.HostOS : (Byte)0; } void GetUnicodeString(UString &res, const AString &s, bool isComment, bool useSpecifiedCodePage, UINT codePage) const; @@ -293,10 +327,22 @@ class CItem: public CLocalItem } return (Crc != 0 || !IsDir()); } + + bool Is_MadeBy_Unix() const + { + if (!FromCentral) + return false; + return (MadeByVersion.HostOS == NFileHeader::NHostOS::kUnix); + } UINT GetCodePage() const { - Byte hostOS = GetHostOS(); + // 18.06: now we use HostOS only from Central::MadeByVersion + if (IsUtf8()) + return CP_UTF8; + if (!FromCentral) + return CP_OEMCP; + Byte hostOS = MadeByVersion.HostOS; return (UINT)(( hostOS == NFileHeader::NHostOS::kFAT || hostOS == NFileHeader::NHostOS::kNTFS diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipOut.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipOut.cpp index 2a1ba2c4..e8a21c24 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipOut.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipOut.cpp @@ -2,6 +2,9 @@ #include "StdAfx.h" +#include "../../../../C/7zCrc.h" + +#include "../../../Windows/TimeUtils.h" #include "../../Common/OffsetStream.h" #include "ZipOut.h" @@ -9,6 +12,20 @@ namespace NArchive { namespace NZip { +HRESULT COutArchive::ClearRestriction() +{ + if (SetRestriction) + return SetRestriction->SetRestriction(0, 0); + return S_OK; +} + +HRESULT COutArchive::SetRestrictionFromCurrent() +{ + if (SetRestriction) + return SetRestriction->SetRestriction(m_Base + m_CurPos, (UInt64)(Int64)-1); + return S_OK; +} + HRESULT COutArchive::Create(IOutStream *outStream) { m_CurPos = 0; @@ -21,95 +38,73 @@ HRESULT COutArchive::Create(IOutStream *outStream) return m_Stream->Seek(0, STREAM_SEEK_CUR, &m_Base); } -void COutArchive::MoveCurPos(UInt64 distanceToMove) -{ - m_CurPos += distanceToMove; // test overflow -} - -void COutArchive::SeekToRelatPos(UInt64 offset) +void COutArchive::SeekToCurPos() { - HRESULT res = m_Stream->Seek(m_Base + offset, STREAM_SEEK_SET, NULL); + HRESULT res = m_Stream->Seek((Int64)(m_Base + m_CurPos), STREAM_SEEK_SET, NULL); if (res != S_OK) throw CSystemException(res); } -void COutArchive::PrepareWriteCompressedDataZip64(unsigned fileNameLen, bool isZip64, bool aesEncryption) -{ - m_IsZip64 = isZip64; - m_ExtraSize = isZip64 ? (4 + 8 + 8) : 0; - if (aesEncryption) - m_ExtraSize += 4 + k_WzAesExtra_Size; - m_LocalFileHeaderSize = kLocalHeaderSize + fileNameLen + m_ExtraSize; -} - -void COutArchive::PrepareWriteCompressedData(unsigned fileNameLen, UInt64 unPackSize, bool aesEncryption) -{ - // We use Zip64, if unPackSize size is larger than 0xF8000000 to support - // cases when compressed size can be about 3% larger than uncompressed size - - PrepareWriteCompressedDataZip64(fileNameLen, unPackSize >= (UInt32)0xF8000000, aesEncryption); -} - #define DOES_NEED_ZIP64(v) (v >= (UInt32)0xFFFFFFFF) +// #define DOES_NEED_ZIP64(v) (v >= 0) -void COutArchive::PrepareWriteCompressedData2(unsigned fileNameLen, UInt64 unPackSize, UInt64 packSize, bool aesEncryption) -{ - bool isZip64 = - DOES_NEED_ZIP64(unPackSize) || - DOES_NEED_ZIP64(packSize); - PrepareWriteCompressedDataZip64(fileNameLen, isZip64, aesEncryption); -} -void COutArchive::WriteBytes(const void *buffer, UInt32 size) +Z7_NO_INLINE +void COutArchive::WriteBytes(const void *data, size_t size) { - m_OutBuffer.WriteBytes(buffer, size); + m_OutBuffer.WriteBytes(data, size); m_CurPos += size; } +Z7_NO_INLINE void COutArchive::Write8(Byte b) { m_OutBuffer.WriteByte(b); m_CurPos++; } +Z7_NO_INLINE void COutArchive::Write16(UInt16 val) { - for (int i = 0; i < 2; i++) - { - Write8((Byte)val); - val >>= 8; - } + Write8((Byte)val); + Write8((Byte)(val >> 8)); } +Z7_NO_INLINE void COutArchive::Write32(UInt32 val) { for (int i = 0; i < 4; i++) { - Write8((Byte)val); + // Write8((Byte)val); + m_OutBuffer.WriteByte((Byte)val); val >>= 8; } + m_CurPos += 4; } +#define WRITE_CONST_PAIR_16_16(a, b) { Write32((a) | ((UInt32)(b) << 16)); } + +Z7_NO_INLINE void COutArchive::Write64(UInt64 val) { for (int i = 0; i < 8; i++) { - Write8((Byte)val); + // Write8((Byte)val); + m_OutBuffer.WriteByte((Byte)val); val >>= 8; } + m_CurPos += 8; } +Z7_NO_INLINE void COutArchive::WriteExtra(const CExtraBlock &extra) { - if (extra.SubBlocks.Size() != 0) + FOR_VECTOR (i, extra.SubBlocks) { - FOR_VECTOR (i, extra.SubBlocks) - { - const CExtraSubBlock &subBlock = extra.SubBlocks[i]; - Write16(subBlock.ID); - Write16((UInt16)subBlock.Data.Size()); - WriteBytes(subBlock.Data, (UInt32)subBlock.Data.Size()); - } + const CExtraSubBlock &subBlock = extra.SubBlocks[i]; + Write16((UInt16)subBlock.ID); + Write16((UInt16)subBlock.Data.Size()); + WriteBytes(subBlock.Data, (UInt16)subBlock.Data.Size()); } } @@ -125,82 +120,218 @@ void COutArchive::WriteCommonItemInfo(const CLocalItem &item, bool isZip64) Write16(item.Flags); Write16(item.Method); Write32(item.Time); - Write32(item.Crc); } -#define WRITE_32_VAL_SPEC(__v, __isZip64) Write32((__isZip64) ? 0xFFFFFFFF : (UInt32)(__v)); -void COutArchive::WriteLocalHeader(const CLocalItem &item) +#define WRITE_32_VAL_SPEC(_v_, _isZip64_) Write32((_isZip64_) ? 0xFFFFFFFF : (UInt32)(_v_)); + + +void COutArchive::WriteUtfName(const CItemOut &item) +{ + if (item.Name_Utf.Size() == 0) + return; + Write16(NFileHeader::NExtraID::kIzUnicodeName); + Write16((UInt16)(5 + item.Name_Utf.Size())); + Write8(1); // (1 = version) of that extra field + Write32(CrcCalc(item.Name.Ptr(), item.Name.Len())); + WriteBytes(item.Name_Utf, (UInt16)item.Name_Utf.Size()); +} + + +static const unsigned k_Ntfs_ExtraSize = 4 + 2 + 2 + (3 * 8); +static const unsigned k_UnixTime_ExtraSize = 1 + (1 * 4); + +void COutArchive::WriteTimeExtra(const CItemOut &item, bool writeNtfs) { - SeekToCurPos(); + if (writeNtfs) + { + // windows explorer ignores that extra + WRITE_CONST_PAIR_16_16(NFileHeader::NExtraID::kNTFS, k_Ntfs_ExtraSize) + Write32(0); // reserved + WRITE_CONST_PAIR_16_16(NFileHeader::NNtfsExtra::kTagTime, 8 * 3) + WriteNtfsTime(item.Ntfs_MTime); + WriteNtfsTime(item.Ntfs_ATime); + WriteNtfsTime(item.Ntfs_CTime); + } + + if (item.Write_UnixTime) + { + // windows explorer ignores that extra + // by specification : should we write to local header also? + WRITE_CONST_PAIR_16_16(NFileHeader::NExtraID::kUnixTime, k_UnixTime_ExtraSize) + const Byte flags = (Byte)((unsigned)1 << NFileHeader::NUnixTime::kMTime); + Write8(flags); + UInt32 unixTime; + NWindows::NTime::FileTime_To_UnixTime(item.Ntfs_MTime, unixTime); + Write32(unixTime); + } +} + + +void COutArchive::WriteLocalHeader(CItemOut &item, bool needCheck) +{ + m_LocalHeaderPos = m_CurPos; + item.LocalHeaderPos = m_CurPos; - bool isZip64 = m_IsZip64 || + bool isZip64 = DOES_NEED_ZIP64(item.PackSize) || DOES_NEED_ZIP64(item.Size); - + + if (needCheck && m_IsZip64) + isZip64 = true; + + // Why don't we write NTFS timestamps to local header? + // Probably we want to reduce size of archive? + const bool writeNtfs = false; // do not write NTFS timestamp to local header + // const bool writeNtfs = item.Write_NtfsTime; // write NTFS time to local header + const UInt32 localExtraSize = (UInt32)( + (isZip64 ? (4 + 8 + 8): 0) + + (writeNtfs ? 4 + k_Ntfs_ExtraSize : 0) + + (item.Write_UnixTime ? 4 + k_UnixTime_ExtraSize : 0) + + item.Get_UtfName_ExtraSize() + + item.LocalExtra.GetSize()); + if ((UInt16)localExtraSize != localExtraSize) + throw CSystemException(E_FAIL); + if (needCheck && m_ExtraSize != localExtraSize) + throw CSystemException(E_FAIL); + + m_IsZip64 = isZip64; + m_ExtraSize = localExtraSize; + + item.LocalExtra.IsZip64 = isZip64; + Write32(NSignature::kLocalFileHeader); + WriteCommonItemInfo(item, isZip64); + + Write32(item.HasDescriptor() ? 0 : item.Crc); - WRITE_32_VAL_SPEC(item.PackSize, isZip64); - WRITE_32_VAL_SPEC(item.Size, isZip64); - - Write16((UInt16)item.Name.Len()); + UInt64 packSize = item.PackSize; + UInt64 size = item.Size; + + if (item.HasDescriptor()) { - UInt16 localExtraSize = (UInt16)((isZip64 ? (4 + 8 + 8): 0) + item.LocalExtra.GetSize()); - if (localExtraSize != m_ExtraSize) - throw CSystemException(E_FAIL); + packSize = 0; + size = 0; } - Write16((UInt16)m_ExtraSize); - WriteBytes((const char *)item.Name, item.Name.Len()); + + WRITE_32_VAL_SPEC(packSize, isZip64) + WRITE_32_VAL_SPEC(size, isZip64) + + Write16((UInt16)item.Name.Len()); + + Write16((UInt16)localExtraSize); + + WriteBytes((const char *)item.Name, (UInt16)item.Name.Len()); if (isZip64) { - Write16(NFileHeader::NExtraID::kZip64); - Write16(8 + 8); - Write64(item.Size); - Write64(item.PackSize); + WRITE_CONST_PAIR_16_16(NFileHeader::NExtraID::kZip64, 8 + 8) + Write64(size); + Write64(packSize); } + WriteTimeExtra(item, writeNtfs); + + WriteUtfName(item); + WriteExtra(item.LocalExtra); - // Why don't we write NTFS timestamps to local header? - // Probably we want to reduce size of archive? + const UInt32 localFileHeaderSize = (UInt32)(m_CurPos - m_LocalHeaderPos); + if (needCheck && m_LocalFileHeaderSize != localFileHeaderSize) + throw CSystemException(E_FAIL); + m_LocalFileHeaderSize = localFileHeaderSize; m_OutBuffer.FlushWithCheck(); - MoveCurPos(item.PackSize); } + +void COutArchive::WriteLocalHeader_Replace(CItemOut &item) +{ + m_CurPos = m_LocalHeaderPos + m_LocalFileHeaderSize + item.PackSize; + + if (item.HasDescriptor()) + { + WriteDescriptor(item); + m_OutBuffer.FlushWithCheck(); + return; + // we don't replace local header, if we write Descriptor. + // so local header with Descriptor flag must be written to local header before. + } + + const UInt64 nextPos = m_CurPos; + m_CurPos = m_LocalHeaderPos; + SeekToCurPos(); + WriteLocalHeader(item, true); + m_CurPos = nextPos; + SeekToCurPos(); +} + + +void COutArchive::WriteDescriptor(const CItemOut &item) +{ + Byte buf[kDataDescriptorSize64]; + SetUi32(buf, NSignature::kDataDescriptor) + SetUi32(buf + 4, item.Crc) + unsigned descriptorSize; + if (m_IsZip64) + { + SetUi64(buf + 8, item.PackSize) + SetUi64(buf + 16, item.Size) + descriptorSize = kDataDescriptorSize64; + } + else + { + SetUi32(buf + 8, (UInt32)item.PackSize) + SetUi32(buf + 12, (UInt32)item.Size) + descriptorSize = kDataDescriptorSize32; + } + WriteBytes(buf, descriptorSize); +} + + + void COutArchive::WriteCentralHeader(const CItemOut &item) { - bool isUnPack64 = DOES_NEED_ZIP64(item.Size); - bool isPack64 = DOES_NEED_ZIP64(item.PackSize); - bool isPosition64 = DOES_NEED_ZIP64(item.LocalHeaderPos); - bool isZip64 = isPack64 || isUnPack64 || isPosition64; + const bool isUnPack64 = DOES_NEED_ZIP64(item.Size); + const bool isPack64 = DOES_NEED_ZIP64(item.PackSize); + const bool isPosition64 = DOES_NEED_ZIP64(item.LocalHeaderPos); + const bool isZip64 = isPack64 || isUnPack64 || isPosition64; Write32(NSignature::kCentralFileHeader); Write8(item.MadeByVersion.Version); Write8(item.MadeByVersion.HostOS); WriteCommonItemInfo(item, isZip64); + Write32(item.Crc); - WRITE_32_VAL_SPEC(item.PackSize, isPack64); - WRITE_32_VAL_SPEC(item.Size, isUnPack64); + WRITE_32_VAL_SPEC(item.PackSize, isPack64) + WRITE_32_VAL_SPEC(item.Size, isUnPack64) Write16((UInt16)item.Name.Len()); - UInt16 zip64ExtraSize = (UInt16)((isUnPack64 ? 8: 0) + (isPack64 ? 8: 0) + (isPosition64 ? 8: 0)); - const UInt16 kNtfsExtraSize = 4 + 2 + 2 + (3 * 8); - const UInt16 centralExtraSize = (UInt16)( - (isZip64 ? 4 + zip64ExtraSize : 0) + - (item.NtfsTimeIsDefined ? 4 + kNtfsExtraSize : 0) + - item.CentralExtra.GetSize()); - - Write16(centralExtraSize); // test it; - Write16((UInt16)item.Comment.Size()); - Write16(0); // DiskNumberStart; + const UInt16 zip64ExtraSize = (UInt16)((isUnPack64 ? 8: 0) + (isPack64 ? 8: 0) + (isPosition64 ? 8: 0)); + const bool writeNtfs = item.Write_NtfsTime; + const size_t centralExtraSize = + (isZip64 ? 4 + zip64ExtraSize : 0) + + (writeNtfs ? 4 + k_Ntfs_ExtraSize : 0) + + (item.Write_UnixTime ? 4 + k_UnixTime_ExtraSize : 0) + + item.Get_UtfName_ExtraSize() + + item.CentralExtra.GetSize(); + + const UInt16 centralExtraSize16 = (UInt16)centralExtraSize; + if (centralExtraSize16 != centralExtraSize) + throw CSystemException(E_FAIL); + + Write16(centralExtraSize16); + + const UInt16 commentSize = (UInt16)item.Comment.Size(); + + Write16(commentSize); + Write16(0); // DiskNumberStart Write16(item.InternalAttrib); Write32(item.ExternalAttrib); - WRITE_32_VAL_SPEC(item.LocalHeaderPos, isPosition64); + WRITE_32_VAL_SPEC(item.LocalHeaderPos, isPosition64) WriteBytes((const char *)item.Name, item.Name.Len()); if (isZip64) @@ -215,36 +346,28 @@ void COutArchive::WriteCentralHeader(const CItemOut &item) Write64(item.LocalHeaderPos); } - if (item.NtfsTimeIsDefined) - { - Write16(NFileHeader::NExtraID::kNTFS); - Write16(kNtfsExtraSize); - Write32(0); // reserved - Write16(NFileHeader::NNtfsExtra::kTagTime); - Write16(8 * 3); - WriteNtfsTime(item.Ntfs_MTime); - WriteNtfsTime(item.Ntfs_ATime); - WriteNtfsTime(item.Ntfs_CTime); - } + WriteTimeExtra(item, writeNtfs); + WriteUtfName(item); WriteExtra(item.CentralExtra); - if (item.Comment.Size() > 0) - WriteBytes(item.Comment, (UInt32)item.Comment.Size()); + if (commentSize != 0) + WriteBytes(item.Comment, commentSize); } -void COutArchive::WriteCentralDir(const CObjectVector &items, const CByteBuffer *comment) +HRESULT COutArchive::WriteCentralDir(const CObjectVector &items, const CByteBuffer *comment) { - SeekToCurPos(); + RINOK(ClearRestriction()) - UInt64 cdOffset = GetCurPos(); + const UInt64 cdOffset = GetCurPos(); FOR_VECTOR (i, items) WriteCentralHeader(items[i]); - UInt64 cd64EndOffset = GetCurPos(); - UInt64 cdSize = cd64EndOffset - cdOffset; - bool cdOffset64 = DOES_NEED_ZIP64(cdOffset); - bool cdSize64 = DOES_NEED_ZIP64(cdSize); - bool items64 = items.Size() >= 0xFFFF; - bool isZip64 = (cdOffset64 || cdSize64 || items64); + const UInt64 cd64EndOffset = GetCurPos(); + const UInt64 cdSize = cd64EndOffset - cdOffset; + const bool cdOffset64 = DOES_NEED_ZIP64(cdOffset); + const bool cdSize64 = DOES_NEED_ZIP64(cdSize); + const bool need_Items_64 = items.Size() >= 0xFFFF; + const unsigned items16 = (UInt16)(need_Items_64 ? 0xFFFF: items.Size()); + const bool isZip64 = (cdOffset64 || cdSize64 || need_Items_64); // isZip64 = true; // to test Zip64 @@ -252,15 +375,22 @@ void COutArchive::WriteCentralDir(const CObjectVector &items, const CB { Write32(NSignature::kEcd64); Write64(kEcd64_MainSize); - Write16(45); // made by version - Write16(45); // extract version - Write32(0); // ThisDiskNumber = 0; - Write32(0); // StartCentralDirectoryDiskNumber;; + + // to test extra block: + // const UInt32 extraSize = 1 << 26; + // Write64(kEcd64_MainSize + extraSize); + + WRITE_CONST_PAIR_16_16(45, // made by version + 45) // extract version + Write32(0); // ThisDiskNumber + Write32(0); // StartCentralDirectoryDiskNumber Write64((UInt64)items.Size()); Write64((UInt64)items.Size()); Write64((UInt64)cdSize); Write64((UInt64)cdOffset); + // for (UInt32 iii = 0; iii < extraSize; iii++) Write8(1); + Write32(NSignature::kEcd64Locator); Write32(0); // number of the disk with the start of the zip64 end of central directory Write64(cd64EndOffset); @@ -268,45 +398,31 @@ void COutArchive::WriteCentralDir(const CObjectVector &items, const CB } Write32(NSignature::kEcd); - Write16(0); // ThisDiskNumber = 0; - Write16(0); // StartCentralDirectoryDiskNumber; - Write16((UInt16)(items64 ? 0xFFFF: items.Size())); - Write16((UInt16)(items64 ? 0xFFFF: items.Size())); + WRITE_CONST_PAIR_16_16(0, 0) // ThisDiskNumber, StartCentralDirectoryDiskNumber + Write16((UInt16)items16); + Write16((UInt16)items16); - WRITE_32_VAL_SPEC(cdSize, cdSize64); - WRITE_32_VAL_SPEC(cdOffset, cdOffset64); + WRITE_32_VAL_SPEC(cdSize, cdSize64) + WRITE_32_VAL_SPEC(cdOffset, cdOffset64) - UInt32 commentSize = (UInt32)(comment ? comment->Size() : 0); + const UInt16 commentSize = (UInt16)(comment ? comment->Size() : 0); Write16((UInt16)commentSize); - if (commentSize > 0) + if (commentSize != 0) WriteBytes((const Byte *)*comment, commentSize); m_OutBuffer.FlushWithCheck(); + return S_OK; } -void COutArchive::CreateStreamForCompressing(IOutStream **outStream) +void COutArchive::CreateStreamForCompressing(CMyComPtr &outStream) { COffsetOutStream *streamSpec = new COffsetOutStream; - CMyComPtr tempStream(streamSpec); - streamSpec->Init(m_Stream, m_Base + m_CurPos + m_LocalFileHeaderSize); - *outStream = tempStream.Detach(); -} - -/* -void COutArchive::SeekToPackedDataPosition() -{ - SeekTo(m_BasePosition + m_LocalFileHeaderSize); -} -*/ - -void COutArchive::SeekToCurPos() -{ - SeekToRelatPos(m_CurPos); + outStream = streamSpec; + streamSpec->Init(m_Stream, m_Base + m_CurPos); } -void COutArchive::CreateStreamForCopying(ISequentialOutStream **outStream) +void COutArchive::CreateStreamForCopying(CMyComPtr &outStream) { - CMyComPtr tempStream(m_Stream); - *outStream = tempStream.Detach(); + outStream = m_Stream; } }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipOut.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipOut.h index 056d0d09..e9b2abbc 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipOut.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipOut.h @@ -1,11 +1,10 @@ // ZipOut.h -#ifndef __ZIP_OUT_H -#define __ZIP_OUT_H +#ifndef ZIP7_INC_ZIP_OUT_H +#define ZIP7_INC_ZIP_OUT_H #include "../../../Common/MyCom.h" -#include "../../IStream.h" #include "../../Common/OutBuffer.h" #include "ZipItem.h" @@ -13,36 +12,50 @@ namespace NArchive { namespace NZip { -// can throw CSystemException and COutBufferException - class CItemOut: public CItem { public: FILETIME Ntfs_MTime; FILETIME Ntfs_ATime; FILETIME Ntfs_CTime; - bool NtfsTimeIsDefined; + bool Write_NtfsTime; + bool Write_UnixTime; // It's possible that NtfsTime is not defined, but there is NtfsTime in Extra. + + CByteBuffer Name_Utf; // for Info-Zip (kIzUnicodeName) Extra + + size_t Get_UtfName_ExtraSize() const + { + const size_t size = Name_Utf.Size(); + if (size == 0) + return 0; + return 4 + 5 + size; + } - CItemOut(): NtfsTimeIsDefined(false) {} + CItemOut(): + Write_NtfsTime(false), + Write_UnixTime(false) + {} }; + +// COutArchive can throw CSystemException and COutBufferException + class COutArchive { - CMyComPtr m_Stream; COutBuffer m_OutBuffer; + CMyComPtr m_Stream; - UInt64 m_Base; // Base of arc (offset in output Stream) + UInt64 m_Base; // Base of archive (offset in output Stream) UInt64 m_CurPos; // Curent position in archive (relative from m_Base) + UInt64 m_LocalHeaderPos; // LocalHeaderPos (relative from m_Base) for last WriteLocalHeader() call UInt32 m_LocalFileHeaderSize; UInt32 m_ExtraSize; bool m_IsZip64; - void SeekToRelatPos(UInt64 offset); - - void WriteBytes(const void *buffer, UInt32 size); + void WriteBytes(const void *data, size_t size); void Write8(Byte b); void Write16(UInt16 val); void Write32(UInt32 val); @@ -53,34 +66,36 @@ class COutArchive Write32(ft.dwHighDateTime); } + void WriteTimeExtra(const CItemOut &item, bool writeNtfs); + void WriteUtfName(const CItemOut &item); void WriteExtra(const CExtraBlock &extra); void WriteCommonItemInfo(const CLocalItem &item, bool isZip64); void WriteCentralHeader(const CItemOut &item); - void PrepareWriteCompressedDataZip64(unsigned fileNameLen, bool isZip64, bool aesEncryption); - + void SeekToCurPos(); public: + CMyComPtr SetRestriction; + + HRESULT ClearRestriction(); + HRESULT SetRestrictionFromCurrent(); HRESULT Create(IOutStream *outStream); - void MoveCurPos(UInt64 distanceToMove); UInt64 GetCurPos() const { return m_CurPos; } - void SeekToCurPos(); - - void PrepareWriteCompressedData(unsigned fileNameLen, UInt64 unPackSize, bool aesEncryption); - void PrepareWriteCompressedData2(unsigned fileNameLen, UInt64 unPackSize, UInt64 packSize, bool aesEncryption); - void WriteLocalHeader(const CLocalItem &item); - - void WriteLocalHeader_And_SeekToNextFile(const CLocalItem &item) + void MoveCurPos(UInt64 distanceToMove) { - WriteLocalHeader(item); - SeekToCurPos(); + m_CurPos += distanceToMove; } - void WriteCentralDir(const CObjectVector &items, const CByteBuffer *comment); + void WriteLocalHeader(CItemOut &item, bool needCheck = false); + void WriteLocalHeader_Replace(CItemOut &item); + + void WriteDescriptor(const CItemOut &item); + + HRESULT WriteCentralDir(const CObjectVector &items, const CByteBuffer *comment); - void CreateStreamForCompressing(IOutStream **outStream); - void CreateStreamForCopying(ISequentialOutStream **outStream); + void CreateStreamForCompressing(CMyComPtr &outStream); + void CreateStreamForCopying(CMyComPtr &outStream); }; }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipRegister.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipRegister.cpp index 6674189f..c17a1a3d 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipRegister.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipRegister.cpp @@ -10,18 +10,29 @@ namespace NArchive { namespace NZip { static const Byte k_Signature[] = { - 4, 0x50, 0x4B, 0x03, 0x04, - 4, 0x50, 0x4B, 0x05, 0x06, - 6, 0x50, 0x4B, 0x07, 0x08, 0x50, 0x4B, - 6, 0x50, 0x4B, 0x30, 0x30, 0x50, 0x4B }; + 4, 0x50, 0x4B, 0x03, 0x04, // Local + 4, 0x50, 0x4B, 0x05, 0x06, // Ecd + 4, 0x50, 0x4B, 0x06, 0x06, // Ecd64 + 6, 0x50, 0x4B, 0x07, 0x08, 0x50, 0x4B, // Span / Descriptor + 6, 0x50, 0x4B, 0x30, 0x30, 0x50, 0x4B }; // NoSpan REGISTER_ARC_IO( - "zip", "zip z01 zipx jar xpi odt ods docx xlsx epub", 0, 1, + "zip", "zip z01 zipx jar xpi odt ods docx xlsx epub ipa apk appx", NULL, 1, k_Signature, 0, - NArcInfoFlags::kFindSignature | - NArcInfoFlags::kMultiSignature | - NArcInfoFlags::kUseGlobalOffset, - IsArc_Zip) + NArcInfoFlags::kFindSignature + | NArcInfoFlags::kMultiSignature + | NArcInfoFlags::kUseGlobalOffset + | NArcInfoFlags::kCTime + // | NArcInfoFlags::kCTime_Default + | NArcInfoFlags::kATime + // | NArcInfoFlags::kATime_Default + | NArcInfoFlags::kMTime + | NArcInfoFlags::kMTime_Default + , TIME_PREC_TO_ARC_FLAGS_MASK (NFileTimeType::kWindows) + | TIME_PREC_TO_ARC_FLAGS_MASK (NFileTimeType::kUnix) + | TIME_PREC_TO_ARC_FLAGS_MASK (NFileTimeType::kDOS) + | TIME_PREC_TO_ARC_FLAGS_TIME_DEFAULT (NFileTimeType::kWindows) + , IsArc_Zip) }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipUpdate.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipUpdate.cpp index bc50c1d7..b2684dcb 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipUpdate.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipUpdate.cpp @@ -2,6 +2,15 @@ #include "StdAfx.h" +// #define DEBUG_CACHE + +#ifdef DEBUG_CACHE +#include + #define PRF(x) x +#else + #define PRF(x) +#endif + #include "../../../../C/Alloc.h" #include "../../../Common/AutoPtr.h" @@ -15,12 +24,13 @@ #include "../../Common/LimitedStreams.h" #include "../../Common/OutMemStream.h" #include "../../Common/ProgressUtils.h" -#ifndef _7ZIP_ST +#ifndef Z7_ST #include "../../Common/ProgressMt.h" #endif #include "../../Common/StreamUtils.h" #include "../../Compress/CopyCoder.h" +// #include "../../Compress/ZstdEncoderProps.h" #include "ZipAddCommon.h" #include "ZipOut.h" @@ -40,54 +50,87 @@ static const Byte kHostOS = #endif static const Byte kMadeByHostOS = kHostOS; -static const Byte kExtractHostOS = kHostOS; -static const Byte kMethodForDirectory = NFileHeader::NCompressionMethod::kStored; +// 18.06: now we always write zero to high byte of ExtractVersion field. +// Previous versions of p7zip wrote (NFileHeader::NHostOS::kUnix) there, that is not correct +static const Byte kExtractHostOS = 0; + +static const Byte kMethodForDirectory = NFileHeader::NCompressionMethod::kStore; -static HRESULT CopyBlockToArchive(ISequentialInStream *inStream, UInt64 size, - COutArchive &outArchive, ICompressProgressInfo *progress) + +static void AddAesExtra(CItem &item, Byte aesKeyMode, UInt16 method) { - CMyComPtr outStream; - outArchive.CreateStreamForCopying(&outStream); - return NCompress::CopyStream_ExactSize(inStream, outStream, size, progress); + CWzAesExtra wzAesField; + wzAesField.Strength = aesKeyMode; + wzAesField.Method = method; + item.Method = NFileHeader::NCompressionMethod::kWzAES; + item.Crc = 0; + CExtraSubBlock sb; + wzAesField.SetSubBlock(sb); + item.LocalExtra.SubBlocks.Add(sb); + item.CentralExtra.SubBlocks.Add(sb); +} + + +static void Copy_From_UpdateItem_To_ItemOut(const CUpdateItem &ui, CItemOut &item) +{ + item.Name = ui.Name; + item.Name_Utf = ui.Name_Utf; + item.Comment = ui.Comment; + item.SetUtf8(ui.IsUtf8); + // item.SetFlag_AltStream(ui.IsAltStream); + // item.ExternalAttrib = ui.Attrib; + item.Time = ui.Time; + item.Ntfs_MTime = ui.Ntfs_MTime; + item.Ntfs_ATime = ui.Ntfs_ATime; + item.Ntfs_CTime = ui.Ntfs_CTime; + + item.Write_UnixTime = ui.Write_UnixTime; + item.Write_NtfsTime = ui.Write_NtfsTime; } static void SetFileHeader( - COutArchive &archive, const CCompressionMethodMode &options, const CUpdateItem &ui, - // bool isSeqMode, + bool useDescriptor, CItemOut &item) { item.Size = ui.Size; - bool isDir; + const bool isDir = ui.IsDir; item.ClearFlags(); if (ui.NewProps) { - isDir = ui.IsDir; - item.Name = ui.Name; - item.SetUtf8(ui.IsUtf8); + Copy_From_UpdateItem_To_ItemOut(ui, item); + // item.SetFlag_AltStream(ui.IsAltStream); item.ExternalAttrib = ui.Attrib; - item.Time = ui.Time; - item.Ntfs_MTime = ui.Ntfs_MTime; - item.Ntfs_ATime = ui.Ntfs_ATime; - item.Ntfs_CTime = ui.Ntfs_CTime; - item.NtfsTimeIsDefined = ui.NtfsTimeIsDefined; } + /* else isDir = item.IsDir(); + */ - item.LocalHeaderPos = archive.GetCurPos(); item.MadeByVersion.HostOS = kMadeByHostOS; item.MadeByVersion.Version = NFileHeader::NCompressionMethod::kMadeByProgramVersion; item.ExtractVersion.HostOS = kExtractHostOS; item.InternalAttrib = 0; // test it - item.SetEncrypted(!isDir && options.PasswordIsDefined); - // item.SetDescriptorMode(isSeqMode); + item.SetEncrypted(!isDir && options.Password_Defined); + item.SetDescriptorMode(useDescriptor); + + if (isDir) + { + item.ExtractVersion.Version = NFileHeader::NCompressionMethod::kExtractVersion_Dir; + item.Method = kMethodForDirectory; + item.PackSize = 0; + item.Size = 0; + item.Crc = 0; + } + + item.LocalExtra.Clear(); + item.CentralExtra.Clear(); if (isDir) { @@ -97,14 +140,20 @@ static void SetFileHeader( item.Size = 0; item.Crc = 0; } + else if (options.IsRealAesMode()) + AddAesExtra(item, options.AesKeyMode, (Byte)(options.MethodSequence.IsEmpty() ? 8 : options.MethodSequence[0])); } +// we call SetItemInfoFromCompressingResult() after SetFileHeader() + static void SetItemInfoFromCompressingResult(const CCompressingResult &compressingResult, bool isAesMode, Byte aesKeyMode, CItem &item) { item.ExtractVersion.Version = compressingResult.ExtractVersion; item.Method = compressingResult.Method; + if (compressingResult.Method == NFileHeader::NCompressionMethod::kLZMA && compressingResult.LzmaEos) + item.Flags |= NFileHeader::NFlags::kLzmaEOS; item.Crc = compressingResult.CRC; item.Size = compressingResult.UnpackSize; item.PackSize = compressingResult.PackSize; @@ -113,31 +162,52 @@ static void SetItemInfoFromCompressingResult(const CCompressingResult &compressi item.CentralExtra.Clear(); if (isAesMode) - { - CWzAesExtra wzAesField; - wzAesField.Strength = aesKeyMode; - wzAesField.Method = compressingResult.Method; - item.Method = NFileHeader::NCompressionMethod::kWzAES; - item.Crc = 0; - CExtraSubBlock sb; - wzAesField.SetSubBlock(sb); - item.LocalExtra.SubBlocks.Add(sb); - item.CentralExtra.SubBlocks.Add(sb); - } + AddAesExtra(item, aesKeyMode, compressingResult.Method); } -#ifndef _7ZIP_ST +#ifndef Z7_ST + +struct CMtSem +{ + NWindows::NSynchronization::CSemaphore Semaphore; + NWindows::NSynchronization::CCriticalSection CS; + CIntVector Indexes; + int Head; + + void ReleaseItem(unsigned index) + { + { + CCriticalSectionLock lock(CS); + Indexes[index] = Head; + Head = (int)index; + } + Semaphore.Release(); + } + + int GetFreeItem() + { + int i; + { + CCriticalSectionLock lock(CS); + i = Head; + Head = Indexes[(unsigned)i]; + } + return i; + } +}; static THREAD_FUNC_DECL CoderThread(void *threadCoderInfo); struct CThreadInfo { - DECL_EXTERNAL_CODECS_LOC_VARS2; + DECL_EXTERNAL_CODECS_LOC_VARS_DECL NWindows::CThread Thread; NWindows::NSynchronization::CAutoResetEvent CompressEvent; - NWindows::NSynchronization::CAutoResetEvent CompressionCompletedEvent; + CMtSem *MtSem; + unsigned ThreadIndex; + bool ExitThread; CMtCompressProgress *ProgressSpec; @@ -152,34 +222,67 @@ struct CThreadInfo CCompressingResult CompressingResult; bool IsFree; + bool InSeqMode; + bool OutSeqMode; + bool ExpectedDataSize_IsConfirmed; + UInt32 UpdateIndex; UInt32 FileTime; + UInt64 ExpectedDataSize; - CThreadInfo(const CCompressionMethodMode &options): + CThreadInfo(): + MtSem(NULL), ExitThread(false), - ProgressSpec(0), - OutStreamSpec(0), - Coder(options), - FileTime(0) + ProgressSpec(NULL), + OutStreamSpec(NULL), + IsFree(true), + InSeqMode(false), + OutSeqMode(false), + ExpectedDataSize_IsConfirmed(false), + FileTime(0), + ExpectedDataSize((UInt64)(Int64)-1) {} + + void SetOptions(const CCompressionMethodMode &options) + { + Coder.SetOptions(options); + } HRESULT CreateEvents() { - RINOK(CompressEvent.CreateIfNotCreated()); - return CompressionCompletedEvent.CreateIfNotCreated(); + const WRes wres = CompressEvent.CreateIfNotCreated_Reset(); + return HRESULT_FROM_WIN32(wres); + } + + // (group < 0) means no_group. + HRESULT CreateThread_with_group( +#ifdef _WIN32 + int group +#endif + ) + { + // tested in win10: If thread is created by another thread, + // child thread probably uses same group as parent thread. + // So we don't need to send (group) to encoder in created thread. + const WRes wres = +#ifdef _WIN32 + group >= 0 ? + Thread.Create_With_Group(CoderThread, this, (unsigned)group) : +#endif + Thread.Create(CoderThread, this); + return HRESULT_FROM_WIN32(wres); } - HRes CreateThread() { return Thread.Create(CoderThread, this); } void WaitAndCode(); - void StopWaitClose() + + void StopWait_Close() { ExitThread = true; - if (OutStreamSpec != 0) + if (OutStreamSpec) OutStreamSpec->StopWriting(E_ABORT); if (CompressEvent.IsCreated()) CompressEvent.Set(); - Thread.Wait(); - Thread.Close(); + Thread.Wait_Close(); } }; @@ -190,14 +293,18 @@ void CThreadInfo::WaitAndCode() CompressEvent.Lock(); if (ExitThread) return; - + Result = Coder.Compress( EXTERNAL_CODECS_LOC_VARS - InStream, OutStream, FileTime, Progress, CompressingResult); + InStream, OutStream, + InSeqMode, OutSeqMode, FileTime, ExpectedDataSize, + ExpectedDataSize_IsConfirmed, + Progress, CompressingResult); if (Result == S_OK && Progress) Result = Progress->SetRatioInfo(&CompressingResult.UnpackSize, &CompressingResult.PackSize); - CompressionCompletedEvent.Set(); + + MtSem->ReleaseItem(ThreadIndex); } } @@ -214,16 +321,20 @@ class CThreads ~CThreads() { FOR_VECTOR (i, Threads) - Threads[i].StopWaitClose(); + Threads[i].StopWait_Close(); } }; struct CMemBlocks2: public CMemLockBlocks { - CCompressingResult CompressingResult; - bool Defined; bool Skip; - CMemBlocks2(): Defined(false), Skip(false) {} + bool InSeqMode; + bool PreDescriptorMode; + bool Finished; + CCompressingResult CompressingResult; + + CMemBlocks2(): Skip(false), InSeqMode(false), PreDescriptorMode(false), Finished(false), + CompressingResult() {} }; class CMemRefs @@ -231,7 +342,7 @@ class CMemRefs public: CMemBlockManagerMt *Manager; CObjectVector Refs; - CMemRefs(CMemBlockManagerMt *manager): Manager(manager) {} ; + CMemRefs(CMemBlockManagerMt *manager): Manager(manager) {} ~CMemRefs() { FOR_VECTOR (i, Refs) @@ -239,10 +350,11 @@ class CMemRefs } }; -class CMtProgressMixer2: - public ICompressProgressInfo, - public CMyUnknownImp -{ + +Z7_CLASS_IMP_NOQIB_1( + CMtProgressMixer2 + , ICompressProgressInfo +) UInt64 ProgressOffset; UInt64 InSizes[2]; UInt64 OutSizes[2]; @@ -251,11 +363,10 @@ class CMtProgressMixer2: bool _inSizeIsMain; public: NWindows::NSynchronization::CCriticalSection CriticalSection; - MY_UNKNOWN_IMP void Create(IProgress *progress, bool inSizeIsMain); void SetProgressOffset(UInt64 progressOffset); + void SetProgressOffset_NoLock(UInt64 progressOffset); HRESULT SetRatioInfo(unsigned index, const UInt64 *inSize, const UInt64 *outSize); - STDMETHOD(SetRatioInfo)(const UInt64 *inSize, const UInt64 *outSize); }; void CMtProgressMixer2::Create(IProgress *progress, bool inSizeIsMain) @@ -266,11 +377,16 @@ void CMtProgressMixer2::Create(IProgress *progress, bool inSizeIsMain) ProgressOffset = InSizes[0] = InSizes[1] = OutSizes[0] = OutSizes[1] = 0; } -void CMtProgressMixer2::SetProgressOffset(UInt64 progressOffset) +void CMtProgressMixer2::SetProgressOffset_NoLock(UInt64 progressOffset) { - CriticalSection.Enter(); InSizes[1] = OutSizes[1] = 0; ProgressOffset = progressOffset; +} + +void CMtProgressMixer2::SetProgressOffset(UInt64 progressOffset) +{ + CriticalSection.Enter(); + SetProgressOffset_NoLock(progressOffset); CriticalSection.Leave(); } @@ -279,7 +395,7 @@ HRESULT CMtProgressMixer2::SetRatioInfo(unsigned index, const UInt64 *inSize, co NWindows::NSynchronization::CCriticalSectionLock lock(CriticalSection); if (index == 0 && RatioProgress) { - RINOK(RatioProgress->SetRatioInfo(inSize, outSize)); + RINOK(RatioProgress->SetRatioInfo(inSize, outSize)) } if (inSize) InSizes[index] = *inSize; @@ -291,21 +407,20 @@ HRESULT CMtProgressMixer2::SetRatioInfo(unsigned index, const UInt64 *inSize, co return Progress->SetCompleted(&v); } -STDMETHODIMP CMtProgressMixer2::SetRatioInfo(const UInt64 *inSize, const UInt64 *outSize) +Z7_COM7F_IMF(CMtProgressMixer2::SetRatioInfo(const UInt64 *inSize, const UInt64 *outSize)) { return SetRatioInfo(0, inSize, outSize); } -class CMtProgressMixer: - public ICompressProgressInfo, - public CMyUnknownImp -{ + +Z7_CLASS_IMP_NOQIB_1( + CMtProgressMixer + , ICompressProgressInfo +) public: CMtProgressMixer2 *Mixer2; CMyComPtr RatioProgress; void Create(IProgress *progress, bool inSizeIsMain); - MY_UNKNOWN_IMP - STDMETHOD(SetRatioInfo)(const UInt64 *inSize, const UInt64 *outSize); }; void CMtProgressMixer::Create(IProgress *progress, bool inSizeIsMain) @@ -315,7 +430,7 @@ void CMtProgressMixer::Create(IProgress *progress, bool inSizeIsMain) Mixer2->Create(progress, inSizeIsMain); } -STDMETHODIMP CMtProgressMixer::SetRatioInfo(const UInt64 *inSize, const UInt64 *outSize) +Z7_COM7F_IMF(CMtProgressMixer::SetRatioInfo(const UInt64 *inSize, const UInt64 *outSize)) { return Mixer2->SetRatioInfo(1, inSize, outSize); } @@ -323,7 +438,6 @@ STDMETHODIMP CMtProgressMixer::SetRatioInfo(const UInt64 *inSize, const UInt64 * #endif - static HRESULT UpdateItemOldData( COutArchive &archive, CInArchive *inArchive, @@ -342,120 +456,185 @@ static HRESULT UpdateItemOldData( NUpdateNotifyOp::kReplicate)) } + UInt64 rangeSize; + + RINOK(archive.ClearRestriction()) + if (ui.NewProps) { if (item.HasDescriptor()) - return E_NOTIMPL; - - // use old name size. - - CMyComPtr packStream; - RINOK(inArchive->GetItemStream(itemEx, true, packStream)); - if (!packStream) - return E_NOTIMPL; - + { + // we know compressed / uncompressed sizes and crc. + // so we remove descriptor here + item.Flags = (UInt16)(item.Flags & ~NFileHeader::NFlags::kDescriptorUsedMask); + // return E_NOTIMPL; + } // we keep ExternalAttrib and some another properties from old archive // item.ExternalAttrib = ui.Attrib; - - item.Name = ui.Name; - item.SetUtf8(ui.IsUtf8); - item.Time = ui.Time; - item.Ntfs_MTime = ui.Ntfs_MTime; - item.Ntfs_ATime = ui.Ntfs_ATime; - item.Ntfs_CTime = ui.Ntfs_CTime; - item.NtfsTimeIsDefined = ui.NtfsTimeIsDefined; + // if we don't change Comment, we keep Comment from OldProperties + Copy_From_UpdateItem_To_ItemOut(ui, item); + // item.SetFlag_AltStream(ui.IsAltStream); item.CentralExtra.RemoveUnknownSubBlocks(); item.LocalExtra.RemoveUnknownSubBlocks(); - item.LocalHeaderPos = archive.GetCurPos(); - archive.PrepareWriteCompressedData2(item.Name.Len(), item.Size, item.PackSize, item.LocalExtra.HasWzAes()); archive.WriteLocalHeader(item); - - RINOK(CopyBlockToArchive(packStream, itemEx.PackSize, archive, progress)); - - complexity += itemEx.PackSize; + rangeSize = item.GetPackSizeWithDescriptor(); } else { - CMyComPtr packStream; - RINOK(inArchive->GetItemStream(itemEx, false, packStream)); - if (!packStream) - return E_NOTIMPL; - - // set new header position item.LocalHeaderPos = archive.GetCurPos(); - - const UInt64 rangeSize = itemEx.GetLocalFullSize(); - - RINOK(CopyBlockToArchive(packStream, rangeSize, archive, progress)); - - complexity += rangeSize; - archive.MoveCurPos(rangeSize); + rangeSize = itemEx.GetLocalFullSize(); } - return S_OK; -} + CMyComPtr packStream; + RINOK(inArchive->GetItemStream(itemEx, ui.NewProps, packStream)) + if (!packStream) + return E_NOTIMPL; -static void WriteDirHeader(COutArchive &archive, const CCompressionMethodMode *options, - const CUpdateItem &ui, CItemOut &item) -{ - SetFileHeader(archive, *options, ui, item); - archive.PrepareWriteCompressedData(item.Name.Len(), ui.Size, - // options->IsRealAesMode() - false // fixed 9.31 - ); - archive.WriteLocalHeader_And_SeekToNextFile(item); + complexity += rangeSize; + + CMyComPtr outStream; + archive.CreateStreamForCopying(outStream); + HRESULT res = NCompress::CopyStream_ExactSize(packStream, outStream, rangeSize, progress); + archive.MoveCurPos(rangeSize); + return res; } -static inline bool IsZero_FILETIME(const FILETIME &ft) +static HRESULT WriteDirHeader(COutArchive &archive, const CCompressionMethodMode *options, + const CUpdateItem &ui, CItemOut &item) { - return (ft.dwHighDateTime == 0 && ft.dwLowDateTime == 0); + SetFileHeader(*options, ui, false, item); + RINOK(archive.ClearRestriction()) + archive.WriteLocalHeader(item); + return S_OK; } -static void UpdatePropsFromStream(CUpdateItem &item, ISequentialInStream *fileInStream, + +static void UpdatePropsFromStream( + const CUpdateOptions &options, + CUpdateItem &item, ISequentialInStream *fileInStream, IArchiveUpdateCallback *updateCallback, UInt64 &totalComplexity) { CMyComPtr getProps; fileInStream->QueryInterface(IID_IStreamGetProps, (void **)&getProps); - if (!getProps) - return; - - FILETIME cTime, aTime, mTime; - UInt64 size; - // UInt32 attrib; - if (getProps->GetProps(&size, &cTime, &aTime, &mTime, NULL) != S_OK) - return; + UInt64 size = (UInt64)(Int64)-1; + bool size_WasSet = false; - if (size != item.Size && size != (UInt64)(Int64)-1) + if (getProps) { - Int64 newComplexity = totalComplexity + ((Int64)size - (Int64)item.Size); - if (newComplexity > 0) + FILETIME cTime, aTime, mTime; + UInt32 attrib; + if (getProps->GetProps(&size, &cTime, &aTime, &mTime, &attrib) == S_OK) { - totalComplexity = newComplexity; - updateCallback->SetTotal(totalComplexity); + if (options.Write_MTime) + if (!FILETIME_IsZero(mTime)) + { + item.Ntfs_MTime = mTime; + NTime::UtcFileTime_To_LocalDosTime(mTime, item.Time); + } + + if (options.Write_CTime) if (!FILETIME_IsZero(cTime)) item.Ntfs_CTime = cTime; + if (options.Write_ATime) if (!FILETIME_IsZero(aTime)) item.Ntfs_ATime = aTime; + + item.Attrib = attrib; + size_WasSet = true; } - item.Size = size; } - - if (!IsZero_FILETIME(mTime)) + + if (!size_WasSet) + { + CMyComPtr streamGetSize; + fileInStream->QueryInterface(IID_IStreamGetSize, (void **)&streamGetSize); + if (streamGetSize) + { + if (streamGetSize->GetSize(&size) == S_OK) + size_WasSet = true; + } + } + + if (size_WasSet && size != (UInt64)(Int64)-1) { - item.Ntfs_MTime = mTime; - FILETIME loc = { 0, 0 }; - if (FileTimeToLocalFileTime(&mTime, &loc)) + item.Size_WasSetFromStream = true; + if (size != item.Size) { - item.Time = 0; - NTime::FileTimeToDosTime(loc, item.Time); + const Int64 newComplexity = (Int64)totalComplexity + ((Int64)size - (Int64)item.Size); + if (newComplexity > 0) + { + totalComplexity = (UInt64)newComplexity; + updateCallback->SetTotal(totalComplexity); + } + item.Size = size; } } +} - if (!IsZero_FILETIME(cTime)) item.Ntfs_CTime = cTime; - if (!IsZero_FILETIME(aTime)) item.Ntfs_ATime = aTime; - // item.Attrib = attrib; +/* +static HRESULT ReportProps( + IArchiveUpdateCallbackArcProp *reportArcProp, + UInt32 index, + const CItemOut &item, + bool isAesMode) +{ + PROPVARIANT prop; + prop.vt = VT_EMPTY; + prop.wReserved1 = 0; + + NCOM::PropVarEm_Set_UInt64(&prop, item.Size); + RINOK(reportArcProp->ReportProp(NEventIndexType::kOutArcIndex, index, kpidSize, &prop)); + + NCOM::PropVarEm_Set_UInt64(&prop, item.PackSize); + RINOK(reportArcProp->ReportProp(NEventIndexType::kOutArcIndex, index, kpidPackSize, &prop)); + + if (!isAesMode) + { + NCOM::PropVarEm_Set_UInt32(&prop, item.Crc); + RINOK(reportArcProp->ReportProp(NEventIndexType::kOutArcIndex, index, kpidCRC, &prop)); + } + + RINOK(reportArcProp->ReportFinished(NEventIndexType::kOutArcIndex, index, NUpdate::NOperationResult::kOK)); + + // if (opCallback) RINOK(opCallback->ReportOperation(NEventIndexType::kOutArcIndex, index, NUpdateNotifyOp::kOpFinished)) + + return S_OK; } +*/ + +/* +struct CTotalStats +{ + UInt64 Size; + UInt64 PackSize; + + void UpdateWithItem(const CItemOut &item) + { + Size += item.Size; + PackSize += item.PackSize; + } +}; + +static HRESULT ReportArcProps(IArchiveUpdateCallbackArcProp *reportArcProp, + CTotalStats &st) +{ + PROPVARIANT prop; + prop.vt = VT_EMPTY; + prop.wReserved1 = 0; + { + NWindows::NCOM::PropVarEm_Set_UInt64(&prop, st.Size); + RINOK(reportArcProp->ReportProp( + NEventIndexType::kArcProp, 0, kpidSize, &prop)); + } + { + NWindows::NCOM::PropVarEm_Set_UInt64(&prop, st.PackSize); + RINOK(reportArcProp->ReportProp( + NEventIndexType::kArcProp, 0, kpidPackSize, &prop)); + } + return S_OK; +} +*/ static HRESULT Update2St( @@ -464,17 +643,21 @@ static HRESULT Update2St( CInArchive *inArchive, const CObjectVector &inputItems, CObjectVector &updateItems, - const CCompressionMethodMode *options, + const CUpdateOptions &updateOptions, + const CCompressionMethodMode *options, bool outSeqMode, const CByteBuffer *comment, IArchiveUpdateCallback *updateCallback, UInt64 &totalComplexity, - IArchiveUpdateCallbackFile *opCallback) + IArchiveUpdateCallbackFile *opCallback + // , IArchiveUpdateCallbackArcProp *reportArcProp + ) { CLocalProgress *lps = new CLocalProgress; CMyComPtr progress = lps; lps->Init(updateCallback, true); - CAddCommon compressor(*options); + CAddCommon compressor; + compressor.SetOptions(*options); CObjectVector items; UInt64 unpackSizeTotal = 0, packSizeTotal = 0; @@ -483,85 +666,102 @@ static HRESULT Update2St( { lps->InSize = unpackSizeTotal; lps->OutSize = packSizeTotal; - RINOK(lps->SetCur()); + RINOK(lps->SetCur()) CUpdateItem &ui = updateItems[itemIndex]; CItemEx itemEx; CItemOut item; if (!ui.NewProps || !ui.NewData) { - itemEx = inputItems[ui.IndexInArc]; - if (inArchive->ReadLocalItemAfterCdItemFull(itemEx) != S_OK) + // Note: for (ui.NewProps && !ui.NewData) it copies Props from old archive, + // But we will rewrite all important properties later. But we can keep some properties like Comment + itemEx = inputItems[(unsigned)ui.IndexInArc]; + if (inArchive->Read_LocalItem_After_CdItem_Full(itemEx) != S_OK) return E_NOTIMPL; (CItem &)item = itemEx; } if (ui.NewData) { - bool isDir = ((ui.NewProps) ? ui.IsDir : item.IsDir()); + // bool isDir = ((ui.NewProps) ? ui.IsDir : item.IsDir()); + bool isDir = ui.IsDir; if (isDir) { - WriteDirHeader(archive, options, ui, item); + RINOK(WriteDirHeader(archive, options, ui, item)) } else { - CMyComPtr fileInStream; + CMyComPtr fileInStream; + { HRESULT res = updateCallback->GetStream(ui.IndexInClient, &fileInStream); if (res == S_FALSE) { lps->ProgressOffset += ui.Size; - RINOK(updateCallback->SetOperationResult(NArchive::NUpdate::NOperationResult::kOK)); + RINOK(updateCallback->SetOperationResult(NArchive::NUpdate::NOperationResult::kOK)) continue; } - RINOK(res); + RINOK(res) if (!fileInStream) return E_INVALIDARG; - // bool isSeqMode = false; - /* + bool inSeqMode = false; + if (!inSeqMode) { CMyComPtr inStream2; fileInStream->QueryInterface(IID_IInStream, (void **)&inStream2); - isSeqMode = (inStream2 == NULL); + inSeqMode = (inStream2 == NULL); } - */ + // seqMode = true; // to test seqMode - UpdatePropsFromStream(ui, fileInStream, updateCallback, totalComplexity); - SetFileHeader(archive, *options, ui, item); + UpdatePropsFromStream(updateOptions, ui, fileInStream, updateCallback, totalComplexity); - // file Size can be 64-bit !!! - archive.PrepareWriteCompressedData(item.Name.Len(), ui.Size, options->IsRealAesMode()); CCompressingResult compressingResult; + + RINOK(compressor.Set_Pre_CompressionResult( + inSeqMode, outSeqMode, + ui.Size, + compressingResult)) + + SetFileHeader(*options, ui, compressingResult.DescriptorMode, item); + + // file Size can be 64-bit !!! + + SetItemInfoFromCompressingResult(compressingResult, options->IsRealAesMode(), options->AesKeyMode, item); + + RINOK(archive.SetRestrictionFromCurrent()) + archive.WriteLocalHeader(item); + CMyComPtr outStream; - archive.CreateStreamForCompressing(&outStream); + archive.CreateStreamForCompressing(outStream); RINOK(compressor.Compress( EXTERNAL_CODECS_LOC_VARS fileInStream, outStream, + inSeqMode, outSeqMode, ui.Time, - progress, compressingResult)); + ui.Size, ui.Size_WasSetFromStream, + progress, compressingResult)) - if (compressingResult.FileTimeWasUsed) - { - /* - if (!item.HasDescriptor()) - return E_FAIL; - */ - item.SetDescriptorMode(true); - } + if (item.HasDescriptor() != compressingResult.DescriptorMode) + return E_FAIL; SetItemInfoFromCompressingResult(compressingResult, options->IsRealAesMode(), options->AesKeyMode, item); - archive.WriteLocalHeader_And_SeekToNextFile(item); - RINOK(updateCallback->SetOperationResult(NArchive::NUpdate::NOperationResult::kOK)); - unpackSizeTotal += item.Size; - packSizeTotal += item.PackSize; + + archive.WriteLocalHeader_Replace(item); + } + // if (reportArcProp) RINOK(ReportProps(reportArcProp, ui.IndexInClient, item, options->IsRealAesMode())) + RINOK(updateCallback->SetOperationResult(NArchive::NUpdate::NOperationResult::kOK)) + unpackSizeTotal += item.Size; + packSizeTotal += item.PackSize; } } else { UInt64 complexity = 0; lps->SendRatio = false; - RINOK(UpdateItemOldData(archive, inArchive, itemEx, ui, item, progress, opCallback, complexity)); + + RINOK(UpdateItemOldData(archive, inArchive, itemEx, ui, item, progress, opCallback, complexity)) + lps->SendRatio = true; lps->ProgressOffset += complexity; } @@ -572,11 +772,146 @@ static HRESULT Update2St( lps->InSize = unpackSizeTotal; lps->OutSize = packSizeTotal; - RINOK(lps->SetCur()); - archive.WriteCentralDir(items, comment); - return S_OK; + RINOK(lps->SetCur()) + + RINOK(archive.WriteCentralDir(items, comment)) + + /* + CTotalStats stat; + stat.Size = unpackSizeTotal; + stat.PackSize = packSizeTotal; + if (reportArcProp) + RINOK(ReportArcProps(reportArcProp, stat)) + */ + + lps->ProgressOffset += kCentralHeaderSize * updateItems.Size() + 1; + return lps->SetCur(); } +#ifndef Z7_ST + + +static const size_t kBlockSize = 1 << 16; +// kMemPerThread must be >= kBlockSize +// +static const size_t kMemPerThread = (size_t)sizeof(size_t) << 23; +// static const size_t kMemPerThread = (size_t)sizeof(size_t) << 16; // for debug +// static const size_t kMemPerThread = (size_t)1 << 16; // for debug + +/* +in: + nt_Zip >= 1: the starting maximum number of ZIP threads for search +out: + nt_Zip: calculated number of ZIP threads + returns: calculated number of ZSTD threads +*/ +/* +static UInt32 CalcThreads_for_ZipZstd(CZstdEncProps *zstdProps, + UInt64 memLimit, UInt32 totalThreads, + UInt32 &nt_Zip) +{ + for (; nt_Zip > 1; nt_Zip--) + { + UInt64 mem1 = memLimit / nt_Zip; + if (mem1 <= kMemPerThread) + continue; + mem1 -= kMemPerThread; + UInt32 n_ZSTD = ZstdEncProps_GetNumThreads_for_MemUsageLimit( + zstdProps, mem1, totalThreads / nt_Zip); + // we don't allow (nbWorkers == 1) here + if (n_ZSTD <= 1) + n_ZSTD = 0; + zstdProps->nbWorkers = n_ZSTD; + mem1 = ZstdEncProps_GetMemUsage(zstdProps); + if ((mem1 + kMemPerThread) * nt_Zip <= memLimit) + return n_ZSTD; + } + return ZstdEncProps_GetNumThreads_for_MemUsageLimit( + zstdProps, memLimit, totalThreads); +} + + +static UInt32 SetZstdThreads( + const CCompressionMethodMode &options, + COneMethodInfo *oneMethodMain, + UInt32 numThreads, + UInt32 numZipThreads_limit, + UInt64 numFilesToCompress, + UInt64 numBytesToCompress) +{ + NCompress::NZstd::CEncoderProps encoderProps; + RINOK(encoderProps.SetFromMethodProps(*oneMethodMain)); + CZstdEncProps &zstdProps = encoderProps.EncProps; + ZstdEncProps_NormalizeFull(&zstdProps); + if (oneMethodMain->FindProp(NCoderPropID::kNumThreads) >= 0) + { + // threads for ZSTD are fixed + if (zstdProps.nbWorkers > 1) + numThreads /= zstdProps.nbWorkers; + if (numThreads > numZipThreads_limit) + numThreads = numZipThreads_limit; + if (options._memUsage_WasSet + && !options._numThreads_WasForced) + { + const UInt64 mem1 = ZstdEncProps_GetMemUsage(&zstdProps); + const UInt64 numZipThreads = options._memUsage_Compress / (mem1 + kMemPerThread); + if (numThreads > numZipThreads) + numThreads = (UInt32)numZipThreads; + } + return numThreads; + } + { + // threads for ZSTD are not fixed + + // calculate estimated required number of ZST threads per file size statistics + UInt32 t = MY_ZSTDMT_NBWORKERS_MAX; + { + UInt64 averageNumberOfBlocks = 0; + const UInt64 averageSize = numBytesToCompress / numFilesToCompress; + const UInt64 jobSize = zstdProps.jobSize; + if (jobSize != 0) + averageNumberOfBlocks = averageSize / jobSize + 0; + if (t > averageNumberOfBlocks) + t = (UInt32)averageNumberOfBlocks; + } + if (t > numThreads) + t = numThreads; + + // calculate the nuber of zip threads + UInt32 numZipThreads = numThreads; + if (t > 1) + numZipThreads = numThreads / t; + if (numZipThreads > numZipThreads_limit) + numZipThreads = numZipThreads_limit; + if (numZipThreads < 1) + numZipThreads = 1; + { + // recalculate the number of ZSTD threads via the number of ZIP threads + const UInt32 t2 = numThreads / numZipThreads; + if (t < t2) + t = t2; + } + + if (options._memUsage_WasSet + && !options._numThreads_WasForced) + { + t = CalcThreads_for_ZipZstd(&zstdProps, + options._memUsage_Compress, numThreads, numZipThreads); + numThreads = numZipThreads; + } + // we don't use (nbWorkers = 1) here + if (t <= 1) + t = 0; + oneMethodMain->AddProp_NumThreads(t); + return numThreads; + } +} +*/ + +#endif + + + static HRESULT Update2( DECL_EXTERNAL_CODECS_LOC_VARS @@ -584,16 +919,25 @@ static HRESULT Update2( CInArchive *inArchive, const CObjectVector &inputItems, CObjectVector &updateItems, - const CCompressionMethodMode &options, + const CUpdateOptions &updateOptions, + const CCompressionMethodMode &options, bool outSeqMode, const CByteBuffer *comment, IArchiveUpdateCallback *updateCallback) { CMyComPtr opCallback; updateCallback->QueryInterface(IID_IArchiveUpdateCallbackFile, (void **)&opCallback); + /* + CMyComPtr reportArcProp; + updateCallback->QueryInterface(IID_IArchiveUpdateCallbackArcProp, (void **)&reportArcProp); + */ + + bool unknownComplexity = false; UInt64 complexity = 0; + #ifndef Z7_ST UInt64 numFilesToCompress = 0; UInt64 numBytesToCompress = 0; + #endif unsigned i; @@ -602,9 +946,14 @@ static HRESULT Update2( const CUpdateItem &ui = updateItems[i]; if (ui.NewData) { - complexity += ui.Size; + if (ui.Size == (UInt64)(Int64)-1) + unknownComplexity = true; + else + complexity += ui.Size; + #ifndef Z7_ST numBytesToCompress += ui.Size; numFilesToCompress++; + #endif /* if (ui.Commented) complexity += ui.CommentRange.Size; @@ -612,8 +961,8 @@ static HRESULT Update2( } else { - CItemEx inputItem = inputItems[ui.IndexInArc]; - if (inArchive->ReadLocalItemAfterCdItemFull(inputItem) != S_OK) + CItemEx inputItem = inputItems[(unsigned)ui.IndexInArc]; + if (inArchive->Read_LocalItem_After_CdItem_Full(inputItem) != S_OK) return E_NOTIMPL; complexity += inputItem.GetLocalFullSize(); // complexity += inputItem.GetCentralExtraPlusCommentSize(); @@ -625,89 +974,262 @@ static HRESULT Update2( if (comment) complexity += comment->Size(); complexity++; // end of central - updateCallback->SetTotal(complexity); + + if (!unknownComplexity) + updateCallback->SetTotal(complexity); UInt64 totalComplexity = complexity; - CAddCommon compressor(options); + CCompressionMethodMode options2 = options; + + if (options2._methods.IsEmpty()) + { + // we need method item, if default method was used + options2._methods.AddNew(); + } + + CAddCommon compressor; + compressor.SetOptions(options2); complexity = 0; - CCompressionMethodMode options2 = options; + const Byte method = options.MethodSequence.FrontItem(); + + COneMethodInfo *oneMethodMain = NULL; + if (!options2._methods.IsEmpty()) + oneMethodMain = &options2._methods[0]; - #ifndef _7ZIP_ST + { + FOR_VECTOR (mi, options2._methods) + { + options2.SetGlobalLevelTo(options2._methods[mi]); + } + } + + if (oneMethodMain) + { + // appnote recommends to use EOS marker for LZMA. + if (method == NFileHeader::NCompressionMethod::kLZMA) + oneMethodMain->AddProp_EndMarker_if_NotFound(true); + } - UInt32 numThreads = options.NumThreads; - const UInt32 kNumMaxThreads = 64; - if (numThreads > kNumMaxThreads) - numThreads = kNumMaxThreads; - if (numThreads > MAXIMUM_WAIT_OBJECTS) // is 64 in Windows (is it 64 in all versions?) + + #ifndef Z7_ST + + UInt32 numThreads = options._numThreads; +#ifdef _WIN32 + const UInt32 numThreadGroups = options._numThreadGroups; +#endif + + UInt32 numZipThreads_limit = numThreads; + if (numZipThreads_limit > numFilesToCompress) + numZipThreads_limit = (UInt32)numFilesToCompress; + + if (numZipThreads_limit > 1) + { + const unsigned numFiles_OPEN_MAX = NSystem::Get_File_OPEN_MAX_Reduced_for_3_tasks(); + // printf("\nzip:numFiles_OPEN_MAX =%d\n", (unsigned)numFiles_OPEN_MAX); + if (numZipThreads_limit > numFiles_OPEN_MAX) + numZipThreads_limit = (UInt32)numFiles_OPEN_MAX; + } + + { + // we reduce number of threads for 32-bit to reduce memory usege to 256 MB + const UInt32 kNumMaxThreads = + // _WIN32 (64-bit) supports only 64 threads in one group. + 8 << (sizeof(size_t) / 2); // 32 threads for 32-bit : 128 threads for 64-bit + if (numThreads > kNumMaxThreads) + numThreads = kNumMaxThreads; + } + /* + if (numThreads > MAXIMUM_WAIT_OBJECTS) // is 64 in Windows numThreads = MAXIMUM_WAIT_OBJECTS; + */ + + + /* + // zstd supports (numThreads == 0); if (numThreads < 1) numThreads = 1; - - - const size_t kMemPerThread = (1 << 25); - const size_t kBlockSize = 1 << 16; + */ bool mtMode = (numThreads > 1); if (numFilesToCompress <= 1) mtMode = false; - Byte method = options.MethodSequence.Front(); + // mtMode = true; // debug: to test mtMode if (!mtMode) { - if (options2.MethodInfo.FindProp(NCoderPropID::kNumThreads) < 0) + // if (oneMethodMain) { + /* + if (method == NFileHeader::NCompressionMethod::kZstdWz) { - // fixed for 9.31. bzip2 default is just one thread. - if (options2.NumThreadsWasChanged || method == NFileHeader::NCompressionMethod::kBZip2) - options2.MethodInfo.AddProp_NumThreads(numThreads); + if (oneMethodMain->FindProp(NCoderPropID::kNumThreads) < 0) + { + // numZstdThreads was not forced in oneMethodMain + if (numThreads >= 1 + && options._memUsage_WasSet + && !options._numThreads_WasForced) + { + NCompress::NZstd::CEncoderProps encoderProps; + RINOK(encoderProps.SetFromMethodProps(*oneMethodMain)) + CZstdEncProps &zstdProps = encoderProps.EncProps; + ZstdEncProps_NormalizeFull(&zstdProps); + numThreads = ZstdEncProps_GetNumThreads_for_MemUsageLimit( + &zstdProps, options._memUsage_Compress, numThreads); + // we allow (nbWorkers = 1) here. + } + oneMethodMain->AddProp_NumThreads(numThreads); + } + } // kZstdWz + */ + // } // oneMethodMain + + FOR_VECTOR (mi, options2._methods) + { + COneMethodInfo &onem = options2._methods[mi]; + + if (onem.FindProp(NCoderPropID::kNumThreads) < 0) + { + // fixme: we should check the number of threads for xz method also + // fixed for 9.31. bzip2 default is just one thread. + onem.AddProp_NumThreads(numThreads); + } } } - else + else // mtMode { - if (method == NFileHeader::NCompressionMethod::kStored && !options.PasswordIsDefined) + if (method == NFileHeader::NCompressionMethod::kStore && !options.Password_Defined) numThreads = 1; + + if (oneMethodMain) + { + if (method == NFileHeader::NCompressionMethod::kBZip2) { bool fixedNumber; - UInt32 numBZip2Threads = options2.MethodInfo.Get_BZip2_NumThreads(fixedNumber); + UInt32 numBZip2Threads = oneMethodMain->Get_BZip2_NumThreads(fixedNumber); if (!fixedNumber) { - UInt64 averageSize = numBytesToCompress / numFilesToCompress; - UInt32 blockSize = options2.MethodInfo.Get_BZip2_BlockSize(); - UInt64 averageNumberOfBlocks = averageSize / blockSize + 1; - numBZip2Threads = 32; - if (averageNumberOfBlocks < numBZip2Threads) + const UInt64 averageSize = numBytesToCompress / numFilesToCompress; + const UInt32 blockSize = oneMethodMain->Get_BZip2_BlockSize(); + const UInt64 averageNumberOfBlocks = averageSize / blockSize + 1; + numBZip2Threads = 64; + if (numBZip2Threads > averageNumberOfBlocks) numBZip2Threads = (UInt32)averageNumberOfBlocks; - options2.MethodInfo.AddProp_NumThreads(numBZip2Threads); + if (numBZip2Threads > numThreads) + numBZip2Threads = numThreads; + oneMethodMain->AddProp_NumThreads(numBZip2Threads); } numThreads /= numBZip2Threads; } - if (method == NFileHeader::NCompressionMethod::kLZMA) + else if (method == NFileHeader::NCompressionMethod::kXz) + { + UInt32 numLzmaThreads = 1; + int numXzThreads = oneMethodMain->Get_Xz_NumThreads(numLzmaThreads); + if (numXzThreads < 0) + { + // numXzThreads is unknown + const UInt64 averageSize = numBytesToCompress / numFilesToCompress; + const UInt64 blockSize = oneMethodMain->Get_Xz_BlockSize(); + UInt64 averageNumberOfBlocks = 1; + if (blockSize != (UInt64)(Int64)-1) + averageNumberOfBlocks = averageSize / blockSize + 1; + UInt32 t = 256; + if (t > averageNumberOfBlocks) + t = (UInt32)averageNumberOfBlocks; + t *= numLzmaThreads; + if (t > numThreads) + t = numThreads; + oneMethodMain->AddProp_NumThreads(t); + numXzThreads = (int)t; + } + numThreads /= (unsigned)numXzThreads; + } + /* + else if (method == NFileHeader::NCompressionMethod::kZstdWz) + { + numThreads = SetZstdThreads(options, + oneMethodMain, numThreads, + numZipThreads_limit, + numFilesToCompress, numBytesToCompress); + } + */ + else if ( + method == NFileHeader::NCompressionMethod::kDeflate + || method == NFileHeader::NCompressionMethod::kDeflate64 + || method == NFileHeader::NCompressionMethod::kPPMd) + { + if (numThreads > 1 + && options._memUsage_WasSet + && !options._numThreads_WasForced) + { + UInt64 methodMemUsage; + if (method == NFileHeader::NCompressionMethod::kPPMd) + methodMemUsage = oneMethodMain->Get_Ppmd_MemSize(); + else + methodMemUsage = (4 << 20); // for deflate + const UInt64 threadMemUsage = kMemPerThread + methodMemUsage; + const UInt64 numThreads64 = options._memUsage_Compress / threadMemUsage; + if (numThreads64 < numThreads) + numThreads = (UInt32)numThreads64; + } + } + else if (method == NFileHeader::NCompressionMethod::kLZMA) { - bool fixedNumber; // we suppose that default LZMA is 2 thread. So we don't change it - UInt32 numLZMAThreads = options2.MethodInfo.Get_Lzma_NumThreads(fixedNumber); + const UInt32 numLZMAThreads = oneMethodMain->Get_Lzma_NumThreads(); numThreads /= numLZMAThreads; + + if (numThreads > 1 + && options._memUsage_WasSet + && !options._numThreads_WasForced) + { + const UInt64 methodMemUsage = oneMethodMain->Get_Lzma_MemUsage(true); + const UInt64 threadMemUsage = kMemPerThread + methodMemUsage; + const UInt64 numThreads64 = options._memUsage_Compress / threadMemUsage; + if (numThreads64 < numThreads) + numThreads = (UInt32)numThreads64; + } } - if (numThreads > numFilesToCompress) - numThreads = (UInt32)numFilesToCompress; + } // (oneMethodMain) + + if (numThreads > numZipThreads_limit) + numThreads = numZipThreads_limit; if (numThreads <= 1) + { mtMode = false; + numThreads = 1; + } } + // mtMode = true; // to test mtMode for seqMode + if (!mtMode) #endif return Update2St( EXTERNAL_CODECS_LOC_VARS archive, inArchive, - inputItems, updateItems, &options2, comment, updateCallback, totalComplexity, opCallback); - - - #ifndef _7ZIP_ST + inputItems, updateItems, + updateOptions, + &options2, outSeqMode, + comment, updateCallback, totalComplexity, + opCallback + // , reportArcProp + ); + + + #ifndef Z7_ST + + /* + CTotalStats stat; + stat.Size = 0; + stat.PackSize = 0; + */ + if (numThreads < 1) + numThreads = 1; CObjectVector items; @@ -721,34 +1243,53 @@ static HRESULT Update2( CMemBlockManagerMt memManager(kBlockSize); CMemRefs refs(&memManager); + CMtSem mtSem; CThreads threads; - CRecordVector compressingCompletedEvents; + mtSem.Head = -1; + mtSem.Indexes.ClearAndSetSize(numThreads); + { + WRes wres = mtSem.Semaphore.Create(0, numThreads); + if (wres != 0) + return HRESULT_FROM_WIN32(wres); + } + CUIntVector threadIndices; // list threads in order of updateItems { - RINOK(memManager.AllocateSpaceAlways((size_t)numThreads * (kMemPerThread / kBlockSize))); + RINOK(memManager.AllocateSpaceAlways((size_t)numThreads * (kMemPerThread / kBlockSize))) for (i = 0; i < updateItems.Size(); i++) refs.Refs.Add(CMemBlocks2()); for (i = 0; i < numThreads; i++) - threads.Threads.Add(CThreadInfo(options2)); + { + threads.Threads.AddNew(); + // mtSem.Indexes[i] = -1; // actually we don't use these values + } for (i = 0; i < numThreads; i++) { CThreadInfo &threadInfo = threads.Threads[i]; - #ifdef EXTERNAL_CODECS - threadInfo.__externalCodecs = __externalCodecs; + threadInfo.ThreadIndex = i; + threadInfo.SetOptions(options2); + #ifdef Z7_EXTERNAL_CODECS + threadInfo._externalCodecs = _externalCodecs; #endif - RINOK(threadInfo.CreateEvents()); + RINOK(threadInfo.CreateEvents()) threadInfo.OutStreamSpec = new COutMemStream(&memManager); - RINOK(threadInfo.OutStreamSpec->CreateEvents()); + RINOK(threadInfo.OutStreamSpec->CreateEvents(SYNC_WFMO(&memManager.Synchro))) threadInfo.OutStream = threadInfo.OutStreamSpec; - threadInfo.IsFree = true; threadInfo.ProgressSpec = new CMtCompressProgress(); threadInfo.Progress = threadInfo.ProgressSpec; - threadInfo.ProgressSpec->Init(&mtCompressProgressMixer, (int)i); - threadInfo.FileTime = 0; // fix it ! - RINOK(threadInfo.CreateThread()); + threadInfo.ProgressSpec->Init(&mtCompressProgressMixer, i); + threadInfo.MtSem = &mtSem; + const HRESULT hres = + threadInfo.CreateThread_with_group( +#ifdef _WIN32 + (numThreadGroups > 1 && numThreads > 1) ? + (int)(i % numThreadGroups) : -1 +#endif + ); + RINOK(hres) } } @@ -756,10 +1297,15 @@ static HRESULT Update2( unsigned itemIndex = 0; int lastRealStreamItemIndex = -1; + while (itemIndex < updateItems.Size()) { if (threadIndices.Size() < numThreads && mtItemIndex < updateItems.Size()) { + // we start ahead the threads for compressing + // also we set refs.Refs[itemIndex].SeqMode that is used later + // don't move that code block + CUpdateItem &ui = updateItems[mtItemIndex++]; if (!ui.NewData) continue; @@ -773,55 +1319,79 @@ static HRESULT Update2( } else { - itemEx = inputItems[ui.IndexInArc]; - if (inArchive->ReadLocalItemAfterCdItemFull(itemEx) != S_OK) + itemEx = inputItems[(unsigned)ui.IndexInArc]; + if (inArchive->Read_LocalItem_After_CdItem_Full(itemEx) != S_OK) return E_NOTIMPL; (CItem &)item = itemEx; - if (item.IsDir()) + if (item.IsDir() != ui.IsDir) + return E_NOTIMPL; + if (ui.IsDir) continue; } CMyComPtr fileInStream; + CMemBlocks2 &memRef2 = refs.Refs[mtItemIndex - 1]; + { NWindows::NSynchronization::CCriticalSectionLock lock(mtProgressMixerSpec->Mixer2->CriticalSection); - HRESULT res = updateCallback->GetStream(ui.IndexInClient, &fileInStream); + const HRESULT res = updateCallback->GetStream(ui.IndexInClient, &fileInStream); if (res == S_FALSE) { complexity += ui.Size; complexity += kLocalHeaderSize; - mtProgressMixerSpec->Mixer2->SetProgressOffset(complexity); - RINOK(updateCallback->SetOperationResult(NArchive::NUpdate::NOperationResult::kOK)); - refs.Refs[mtItemIndex - 1].Skip = true; + mtProgressMixerSpec->Mixer2->SetProgressOffset_NoLock(complexity); + RINOK(updateCallback->SetOperationResult(NArchive::NUpdate::NOperationResult::kOK)) + memRef2.Skip = true; continue; } - RINOK(res); + RINOK(res) if (!fileInStream) return E_INVALIDARG; - UpdatePropsFromStream(ui, fileInStream, updateCallback, totalComplexity); - RINOK(updateCallback->SetOperationResult(NArchive::NUpdate::NOperationResult::kOK)); + UpdatePropsFromStream(updateOptions, ui, fileInStream, updateCallback, totalComplexity); + RINOK(updateCallback->SetOperationResult(NArchive::NUpdate::NOperationResult::kOK)) } - for (UInt32 k = 0; k < numThreads; k++) + UInt32 k; + for (k = 0; k < numThreads; k++) + if (threads.Threads[k].IsFree) + break; + + if (k == numThreads) + return E_FAIL; { - CThreadInfo &threadInfo = threads.Threads[k]; - if (threadInfo.IsFree) { + CThreadInfo &threadInfo = threads.Threads[k]; threadInfo.IsFree = false; threadInfo.InStream = fileInStream; + bool inSeqMode = false; + + if (!inSeqMode) + { + CMyComPtr inStream2; + fileInStream->QueryInterface(IID_IInStream, (void **)&inStream2); + inSeqMode = (inStream2 == NULL); + } + memRef2.InSeqMode = inSeqMode; + // !!!!! we must release ref before sending event // BUG was here in v4.43 and v4.44. It could change ref counter in two threads in same time fileInStream.Release(); threadInfo.OutStreamSpec->Init(); threadInfo.ProgressSpec->Reinit(); - threadInfo.CompressEvent.Set(); + threadInfo.UpdateIndex = mtItemIndex - 1; + threadInfo.InSeqMode = inSeqMode; + threadInfo.OutSeqMode = outSeqMode; + threadInfo.FileTime = ui.Time; // FileTime is used for ZipCrypto only in seqMode + threadInfo.ExpectedDataSize = ui.Size; + threadInfo.ExpectedDataSize_IsConfirmed = ui.Size_WasSetFromStream; - compressingCompletedEvents.Add(threadInfo.CompressionCompletedEvent); + threadInfo.CompressEvent.Set(); + threadIndices.Add(k); - break; } } @@ -841,94 +1411,159 @@ static HRESULT Update2( if (!ui.NewProps || !ui.NewData) { - itemEx = inputItems[ui.IndexInArc]; - if (inArchive->ReadLocalItemAfterCdItemFull(itemEx) != S_OK) + itemEx = inputItems[(unsigned)ui.IndexInArc]; + if (inArchive->Read_LocalItem_After_CdItem_Full(itemEx) != S_OK) return E_NOTIMPL; (CItem &)item = itemEx; } if (ui.NewData) { - bool isDir = ((ui.NewProps) ? ui.IsDir : item.IsDir()); + // bool isDir = ((ui.NewProps) ? ui.IsDir : item.IsDir()); + const bool isDir = ui.IsDir; if (isDir) { - WriteDirHeader(archive, &options, ui, item); + RINOK(WriteDirHeader(archive, &options, ui, item)) } else { - if (lastRealStreamItemIndex < (int)itemIndex) - { - lastRealStreamItemIndex = itemIndex; - SetFileHeader(archive, options, ui, item); - // file Size can be 64-bit !!! - archive.PrepareWriteCompressedData(item.Name.Len(), ui.Size, options.IsRealAesMode()); - } - CMemBlocks2 &memRef = refs.Refs[itemIndex]; - if (memRef.Defined) + if (memRef.Finished) { - CMyComPtr outStream; - archive.CreateStreamForCompressing(&outStream); - memRef.WriteToStream(memManager.GetBlockSize(), outStream); - SetFileHeader(archive, options, ui, item); + if (lastRealStreamItemIndex < (int)itemIndex) + lastRealStreamItemIndex = (int)itemIndex; + + SetFileHeader(options, ui, memRef.CompressingResult.DescriptorMode, item); + // the BUG was fixed in 9.26: // SetItemInfoFromCompressingResult must be after SetFileHeader // to write correct Size. + SetItemInfoFromCompressingResult(memRef.CompressingResult, options.IsRealAesMode(), options.AesKeyMode, item); - archive.WriteLocalHeader_And_SeekToNextFile(item); + RINOK(archive.ClearRestriction()) + archive.WriteLocalHeader(item); // RINOK(updateCallback->SetOperationResult(NArchive::NUpdate::NOperationResult::kOK)); + CMyComPtr outStream; + archive.CreateStreamForCopying(outStream); + memRef.WriteToStream(memManager.GetBlockSize(), outStream); + // v23: we fixed the bug: we need to write descriptor also + if (item.HasDescriptor()) + { + /* that function doesn't rewrite local header, if item.HasDescriptor(). + it just writes descriptor */ + archive.WriteLocalHeader_Replace(item); + } + else + archive.MoveCurPos(item.PackSize); memRef.FreeOpt(&memManager); + /* + if (reportArcProp) + { + stat.UpdateWithItem(item); + RINOK(ReportProps(reportArcProp, ui.IndexInClient, item, options.IsRealAesMode())); + } + */ } else { + // current file was not finished + + if (lastRealStreamItemIndex < (int)itemIndex) { - CThreadInfo &thread = threads.Threads[threadIndices.Front()]; + // LocalHeader was not written for current itemIndex still + + lastRealStreamItemIndex = (int)itemIndex; + + // thread was started before for that item already, and memRef.SeqMode was set + + CCompressingResult compressingResult; + RINOK(compressor.Set_Pre_CompressionResult( + memRef.InSeqMode, outSeqMode, + ui.Size, + compressingResult)) + + memRef.PreDescriptorMode = compressingResult.DescriptorMode; + SetFileHeader(options, ui, compressingResult.DescriptorMode, item); + + SetItemInfoFromCompressingResult(compressingResult, options.IsRealAesMode(), options.AesKeyMode, item); + + // file Size can be 64-bit !!! + RINOK(archive.SetRestrictionFromCurrent()) + archive.WriteLocalHeader(item); + } + + { + CThreadInfo &thread = threads.Threads[threadIndices.FrontItem()]; if (!thread.OutStreamSpec->WasUnlockEventSent()) { CMyComPtr outStream; - archive.CreateStreamForCompressing(&outStream); + archive.CreateStreamForCompressing(outStream); thread.OutStreamSpec->SetOutStream(outStream); thread.OutStreamSpec->SetRealStreamMode(); } } - DWORD result = ::WaitForMultipleObjects(compressingCompletedEvents.Size(), - &compressingCompletedEvents.Front(), FALSE, INFINITE); - if (result == WAIT_FAILED) - { - DWORD lastError = GetLastError(); - return lastError != 0 ? lastError : E_FAIL; - } - - unsigned t = (unsigned)(result - WAIT_OBJECT_0); - if (t >= compressingCompletedEvents.Size()) + const WRes wres = mtSem.Semaphore.Lock(); + if (wres != 0) + return HRESULT_FROM_WIN32(wres); + + const int ti = mtSem.GetFreeItem(); + if (ti < 0) return E_FAIL; - CThreadInfo &threadInfo = threads.Threads[threadIndices[t]]; + CThreadInfo &threadInfo = threads.Threads[(unsigned)ti]; threadInfo.InStream.Release(); threadInfo.IsFree = true; - RINOK(threadInfo.Result); + RINOK(threadInfo.Result) + + unsigned t = 0; + + for (;;) + { + if (t == threadIndices.Size()) + return E_FAIL; + if (threadIndices[t] == (unsigned)ti) + break; + t++; + } threadIndices.Delete(t); - compressingCompletedEvents.Delete(t); if (t == 0) { - RINOK(threadInfo.OutStreamSpec->WriteToRealStream()); + // if thread for current file was finished. + if (threadInfo.UpdateIndex != itemIndex) + return E_FAIL; + + if (memRef.PreDescriptorMode != threadInfo.CompressingResult.DescriptorMode) + return E_FAIL; + + RINOK(threadInfo.OutStreamSpec->WriteToRealStream()) threadInfo.OutStreamSpec->ReleaseOutStream(); - SetFileHeader(archive, options, ui, item); + SetFileHeader(options, ui, threadInfo.CompressingResult.DescriptorMode, item); SetItemInfoFromCompressingResult(threadInfo.CompressingResult, options.IsRealAesMode(), options.AesKeyMode, item); - archive.WriteLocalHeader_And_SeekToNextFile(item); + + archive.WriteLocalHeader_Replace(item); + + /* + if (reportArcProp) + { + stat.UpdateWithItem(item); + RINOK(ReportProps(reportArcProp, ui.IndexInClient, item, options.IsRealAesMode())); + } + */ } else { + // it's not current file. So we must store information in array CMemBlocks2 &memRef2 = refs.Refs[threadInfo.UpdateIndex]; threadInfo.OutStreamSpec->DetachData(memRef2); memRef2.CompressingResult = threadInfo.CompressingResult; - memRef2.Defined = true; + // memRef2.SeqMode = threadInfo.SeqMode; // it was set before + memRef2.Finished = true; continue; } } @@ -936,7 +1571,7 @@ static HRESULT Update2( } else { - RINOK(UpdateItemOldData(archive, inArchive, itemEx, ui, item, progress, opCallback, complexity)); + RINOK(UpdateItemOldData(archive, inArchive, itemEx, ui, item, progress, opCallback, complexity)) } items.Add(item); @@ -945,192 +1580,379 @@ static HRESULT Update2( itemIndex++; } - RINOK(mtCompressProgressMixer.SetRatioInfo(0, NULL, NULL)); - archive.WriteCentralDir(items, comment); - return S_OK; + RINOK(mtCompressProgressMixer.SetRatioInfo(0, NULL, NULL)) + + RINOK(archive.WriteCentralDir(items, comment)) + + /* + if (reportArcProp) + { + RINOK(ReportArcProps(reportArcProp, stat)); + } + */ + + complexity += kCentralHeaderSize * updateItems.Size() + 1; + mtProgressMixerSpec->Mixer2->SetProgressOffset(complexity); + return mtCompressProgressMixer.SetRatioInfo(0, NULL, NULL); + #endif } +/* +// we need CSeekOutStream, if we need Seek(0, STREAM_SEEK_CUR) for seqential stream +Z7_CLASS_IMP_COM_1( + CSeekOutStream + , IOutStream +) + Z7_IFACE_COM7_IMP(ISequentialOutStream) -static const size_t kCacheBlockSize = (1 << 20); -static const size_t kCacheSize = (kCacheBlockSize << 2); -static const size_t kCacheMask = (kCacheSize - 1); + CMyComPtr _seqStream; + UInt64 _size; +public: + void Init(ISequentialOutStream *seqStream) + { + _size = 0; + _seqStream = seqStream; + } +}; -class CCacheOutStream: - public IOutStream, - public CMyUnknownImp +Z7_COM7F_IMF(CSeekOutStream::Write(const void *data, UInt32 size, UInt32 *processedSize)) { + UInt32 realProcessedSize; + const HRESULT result = _seqStream->Write(data, size, &realProcessedSize); + _size += realProcessedSize; + if (processedSize) + *processedSize = realProcessedSize; + return result; +} + +Z7_COM7F_IMF(CSeekOutStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition)) +{ + if (seekOrigin != STREAM_SEEK_CUR || offset != 0) + return E_NOTIMPL; + if (newPosition) + *newPosition = (UInt64)_size; + return S_OK; +} + +Z7_COM7F_IMF(CSeekOutStream::SetSize(UInt64 newSize)) +{ + UNUSED_VAR(newSize) + return E_NOTIMPL; +} +*/ + +static const size_t kCacheBlockSize = 1 << 20; +static const size_t kCacheSize = kCacheBlockSize << 2; +static const size_t kCacheMask = kCacheSize - 1; + +Z7_CLASS_IMP_NOQIB_2( + CCacheOutStream + , IOutStream + , IStreamSetRestriction +) + Z7_IFACE_COM7_IMP(ISequentialOutStream) + + HRESULT _hres; + CMyComPtr _seqStream; CMyComPtr _stream; + CMyComPtr _setRestriction; Byte *_cache; + size_t _cachedSize; + UInt64 _cachedPos; UInt64 _virtPos; UInt64 _virtSize; UInt64 _phyPos; - UInt64 _phySize; // <= _virtSize - UInt64 _cachedPos; // (_cachedPos + _cachedSize) <= _virtSize - size_t _cachedSize; + UInt64 _phySize; + UInt64 _restrict_begin; + UInt64 _restrict_end; + + HRESULT FlushFromCache(size_t size); + HRESULT FlushNonRestrictedBlocks(); + HRESULT FlushCache(); + HRESULT SetRestriction_ForWrite(size_t writeSize) const; - HRESULT MyWrite(size_t size); - HRESULT MyWriteBlock() + HRESULT SeekPhy(UInt64 pos) { - return MyWrite(kCacheBlockSize - ((size_t)_cachedPos & (kCacheBlockSize - 1))); + if (pos == _phyPos) + return S_OK; + if (!_stream) + return E_NOTIMPL; + _hres = _stream->Seek((Int64)pos, STREAM_SEEK_SET, &_phyPos); + if (_hres == S_OK && _phyPos != pos) + _hres = E_FAIL; + return _hres; } - HRESULT FlushCache(); + public: - CCacheOutStream(): _cache(0) {} + CCacheOutStream(): _cache(NULL) {} ~CCacheOutStream(); - bool Allocate(); - HRESULT Init(IOutStream *stream); - - MY_UNKNOWN_IMP - - STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize); - STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition); - STDMETHOD(SetSize)(UInt64 newSize); + bool Allocate() + { + if (!_cache) + _cache = (Byte *)::MidAlloc(kCacheSize); + return _cache != NULL; + } + HRESULT Init(ISequentialOutStream *seqStream, IOutStream *stream, IStreamSetRestriction *setRestriction); + HRESULT FinalFlush(); }; -bool CCacheOutStream::Allocate() +CCacheOutStream::~CCacheOutStream() { - if (!_cache) - _cache = (Byte *)::MidAlloc(kCacheSize); - return (_cache != NULL); + ::MidFree(_cache); } -HRESULT CCacheOutStream::Init(IOutStream *stream) + +HRESULT CCacheOutStream::Init(ISequentialOutStream *seqStream, IOutStream *stream, IStreamSetRestriction *setRestriction) { - _virtPos = _phyPos = 0; + _hres = S_OK; + _cachedSize = 0; + _cachedPos = 0; + _virtPos = 0; + _virtSize = 0; + // by default we have no restriction + _restrict_begin = 0; + _restrict_end = 0; + _seqStream = seqStream; _stream = stream; - RINOK(_stream->Seek(0, STREAM_SEEK_CUR, &_virtPos)); - RINOK(_stream->Seek(0, STREAM_SEEK_END, &_virtSize)); - RINOK(_stream->Seek(_virtPos, STREAM_SEEK_SET, &_virtPos)); + _setRestriction = setRestriction; + if (_stream) + { + RINOK(_stream->Seek(0, STREAM_SEEK_CUR, &_virtPos)) + RINOK(_stream->Seek(0, STREAM_SEEK_END, &_virtSize)) + RINOK(_stream->Seek((Int64)_virtPos, STREAM_SEEK_SET, &_virtPos)) + } _phyPos = _virtPos; _phySize = _virtSize; - _cachedPos = 0; - _cachedSize = 0; return S_OK; } -HRESULT CCacheOutStream::MyWrite(size_t size) + +/* we call SetRestriction_ForWrite() just before Write() from cache. + (_phyPos == _cachedPos) + (writeSize != 0) +*/ +HRESULT CCacheOutStream::SetRestriction_ForWrite(size_t writeSize) const { - while (size != 0 && _cachedSize != 0) + if (!_setRestriction) + return S_OK; + PRF(printf("\n-- CCacheOutStream::SetRestriction_ForWrite _cachedPos = 0x%x, writeSize = %d\n", (unsigned)_cachedPos, (unsigned)writeSize)); + UInt64 begin = _restrict_begin; + UInt64 end = _restrict_end; + const UInt64 phyPos = _phyPos; + if (phyPos != _cachedPos) return E_FAIL; + if (phyPos == _phySize) { - if (_phyPos != _cachedPos) + // The writing will be to the end of phy stream. + // So we will try to use non-restricted write, if possible. + if (begin == end) + begin = _virtPos; // _virtSize; // it's supposed that (_virtSize == _virtPos) + if (phyPos + writeSize <= begin) { - RINOK(_stream->Seek(_cachedPos, STREAM_SEEK_SET, &_phyPos)); + // the write is not restricted + PRF(printf("\n+++ write is not restricted \n")); + begin = 0; + end = 0; } - size_t pos = (size_t)_cachedPos & kCacheMask; - size_t curSize = MyMin(kCacheSize - pos, _cachedSize); - curSize = MyMin(curSize, size); - RINOK(WriteStream(_stream, _cache + pos, curSize)); - _phyPos += curSize; + else + { + if (begin > phyPos) + begin = phyPos; + end = (UInt64)(Int64)-1; + } + } + else + { + // (phyPos != _phySize) + if (begin == end || begin > phyPos) + begin = phyPos; + end = (UInt64)(Int64)-1; + } + return _setRestriction->SetRestriction(begin, end); +} + + +/* it writes up to (size) bytes from cache. + (size > _cachedSize) is allowed +*/ +HRESULT CCacheOutStream::FlushFromCache(size_t size) +{ + PRF(printf("\n-- CCacheOutStream::FlushFromCache %u\n", (unsigned)size)); + if (_hres != S_OK) + return _hres; + if (size > _cachedSize) + size = _cachedSize; + // (size <= _cachedSize) + if (size == 0) + return S_OK; + RINOK(SeekPhy(_cachedPos)) + for (;;) + { + // (_phyPos == _cachedPos) + const size_t pos = (size_t)_cachedPos & kCacheMask; + const size_t cur = MyMin(kCacheSize - pos, size); + _hres = SetRestriction_ForWrite(cur); + RINOK(_hres) + PRF(printf("\n-- CCacheOutStream::WriteFromCache _phyPos = 0x%x, size = %d\n", (unsigned)_phyPos, (unsigned)cur)); + _hres = WriteStream(_seqStream, _cache + pos, cur); + RINOK(_hres) + _phyPos += cur; if (_phySize < _phyPos) _phySize = _phyPos; - _cachedPos += curSize; - _cachedSize -= curSize; - size -= curSize; + _cachedPos += cur; + _cachedSize -= cur; + size -= cur; + if (size == 0) + return S_OK; + } +} + + +HRESULT CCacheOutStream::FlushNonRestrictedBlocks() +{ + for (;;) + { + const size_t size = kCacheBlockSize - ((size_t)_cachedPos & (kCacheBlockSize - 1)); + if (_cachedSize < size) + break; + UInt64 begin = _restrict_begin; + if (begin == _restrict_end) + begin = _virtPos; + // we don't flush the data to restricted area + if (_cachedPos + size > begin) + break; + RINOK(FlushFromCache(size)) } return S_OK; } + HRESULT CCacheOutStream::FlushCache() { - return MyWrite(_cachedSize); + return FlushFromCache(_cachedSize); } -CCacheOutStream::~CCacheOutStream() +HRESULT CCacheOutStream::FinalFlush() { - FlushCache(); - if (_virtSize != _phySize) - _stream->SetSize(_virtSize); - if (_virtPos != _phyPos) - _stream->Seek(_virtPos, STREAM_SEEK_SET, NULL); - ::MidFree(_cache); + _restrict_begin = 0; + _restrict_end = 0; + RINOK(FlushCache()) + if (_stream && _hres == S_OK) + { + if (_virtSize != _phySize) + { + // it's unexpected + RINOK(_stream->SetSize(_virtSize)) + _phySize = _virtSize; + } + _hres = SeekPhy(_virtPos); + } + return _hres; } -STDMETHODIMP CCacheOutStream::Write(const void *data, UInt32 size, UInt32 *processedSize) + +Z7_COM7F_IMF(CCacheOutStream::Write(const void *data, UInt32 size, UInt32 *processedSize)) { + PRF(printf("\n==== CCacheOutStream::Write virtPos=0x%x, %u\n", (unsigned)_virtPos, (unsigned)size)); + if (processedSize) *processedSize = 0; if (size == 0) return S_OK; + if (_hres != S_OK) + return _hres; - UInt64 zerosStart = _virtPos; if (_cachedSize != 0) + if (_virtPos < _cachedPos || + _virtPos > _cachedPos + _cachedSize) { - if (_virtPos < _cachedPos) - { - RINOK(FlushCache()); - } - else - { - UInt64 cachedEnd = _cachedPos + _cachedSize; - if (cachedEnd < _virtPos) - { - if (cachedEnd < _phySize) - { - RINOK(FlushCache()); - } - else - zerosStart = cachedEnd; - } - } - } - - if (_cachedSize == 0 && _phySize < _virtPos) - _cachedPos = zerosStart = _phySize; - - if (zerosStart != _virtPos) - { - // write zeros to [cachedEnd ... _virtPos) - - for (;;) - { - UInt64 cachedEnd = _cachedPos + _cachedSize; - size_t endPos = (size_t)cachedEnd & kCacheMask; - size_t curSize = kCacheSize - endPos; - if (curSize > _virtPos - cachedEnd) - curSize = (size_t)(_virtPos - cachedEnd); - if (curSize == 0) - break; - while (curSize > (kCacheSize - _cachedSize)) - { - RINOK(MyWriteBlock()); - } - memset(_cache + endPos, 0, curSize); - _cachedSize += curSize; - } + RINOK(FlushCache()) } if (_cachedSize == 0) _cachedPos = _virtPos; - size_t pos = (size_t)_virtPos & kCacheMask; - size = (UInt32)MyMin((size_t)size, kCacheSize - pos); - UInt64 cachedEnd = _cachedPos + _cachedSize; - if (_virtPos != cachedEnd) // _virtPos < cachedEnd - size = (UInt32)MyMin((size_t)size, (size_t)(cachedEnd - _virtPos)); + const size_t pos = (size_t)_virtPos & kCacheMask; + { + const size_t blockRem = kCacheBlockSize - ((size_t)_virtPos & (kCacheBlockSize - 1)); + if (size > blockRem) + size = (UInt32)blockRem; + } + // _cachedPos <= _virtPos <= _cachedPos + _cachedSize + const UInt64 cachedRem = _cachedPos + _cachedSize - _virtPos; + if (cachedRem) + { + // _virtPos < _cachedPos + _cachedSize + // we rewrite only existing data in cache. So _cachedSize will be not changed + if (size > cachedRem) + size = (UInt32)cachedRem; + } else { - // _virtPos == cachedEnd + // _virtPos == _cachedPos + _cachedSize + // so we need to add new data to the end of cache if (_cachedSize == kCacheSize) { - RINOK(MyWriteBlock()); + // cache is full. So we need to flush some part of cache. + // we flush only one block, but we are allowed to flush any size here + RINOK(FlushFromCache(kCacheBlockSize - ((size_t)_cachedPos & (kCacheBlockSize - 1)))) } - size_t startPos = (size_t)_cachedPos & kCacheMask; - if (startPos > pos) - size = (UInt32)MyMin((size_t)size, (size_t)(startPos - pos)); + // _cachedSize != kCacheSize + // so we have some space for new data in cache + if (_cachedSize == 0) + { + /* this code is optional (for optimization): + we write data directly without cache, + if there is no restriction and we have full block. */ + if (_restrict_begin == _restrict_end + && size == kCacheBlockSize) + { + RINOK(SeekPhy(_virtPos)) + if (_setRestriction) + { + _hres = _setRestriction->SetRestriction(_restrict_begin, _restrict_end); + RINOK(_hres) + } + PRF(printf("\n-- CCacheOutStream::WriteDirectly _phyPos = 0x%x, size = %d\n", (unsigned)_phyPos, (unsigned)size)); + _hres = WriteStream(_seqStream, data, size); + RINOK(_hres) + if (processedSize) + *processedSize = size; + _virtPos += size; + if (_virtSize < _virtPos) + _virtSize = _virtPos; + _phyPos += size; + if (_phySize < _phyPos) + _phySize = _phyPos; + return S_OK; + } + } + else // (_cachedSize != 0) + { + const size_t startPos = (size_t)_cachedPos & kCacheMask; + // we don't allow new data to overwrite old start data in cache. + // (startPos == pos) here means that cache is empty. + // (startPos == pos) is not possible here. + if (startPos > pos) + size = (UInt32)MyMin((size_t)size, (size_t)(startPos - pos)); + } + // _virtPos == (_cachedPos + _cachedSize) still _cachedSize += size; } + memcpy(_cache + pos, data, size); if (processedSize) *processedSize = size; _virtPos += size; if (_virtSize < _virtPos) _virtSize = _virtPos; - return S_OK; + return FlushNonRestrictedBlocks(); } -STDMETHODIMP CCacheOutStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition) + +Z7_COM7F_IMF(CCacheOutStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition)) { + PRF(printf("\n==== CCacheOutStream::Seek seekOrigin=%d Seek =%u\n", seekOrigin, (unsigned)offset)); switch (seekOrigin) { case STREAM_SEEK_SET: break; @@ -1140,93 +1962,204 @@ STDMETHODIMP CCacheOutStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newP } if (offset < 0) return HRESULT_WIN32_ERROR_NEGATIVE_SEEK; - _virtPos = offset; + _virtPos = (UInt64)offset; if (newPosition) - *newPosition = offset; + *newPosition = (UInt64)offset; return S_OK; } -STDMETHODIMP CCacheOutStream::SetSize(UInt64 newSize) + +Z7_COM7F_IMF(CCacheOutStream::SetSize(UInt64 newSize)) { - _virtSize = newSize; - if (newSize < _phySize) - { - RINOK(_stream->SetSize(newSize)); - _phySize = newSize; - } - if (newSize <= _cachedPos) + if (_hres != S_OK) + return _hres; + + if (newSize <= _cachedPos || _cachedSize == 0) { _cachedSize = 0; _cachedPos = newSize; } - if (newSize < _cachedPos + _cachedSize) - _cachedSize = (size_t)(newSize - _cachedPos); + else + { + // _cachedSize != 0 + // newSize > _cachedPos + const UInt64 offset = newSize - _cachedPos; + if (offset <= _cachedSize) + { + // newSize is inside cached block or is touching cached block. + // so we reduce cache + _cachedSize = (size_t)offset; + if (_phySize <= newSize) + { + // _phySize will be restored later after cache flush + _virtSize = newSize; + return S_OK; + } + // (_phySize > newSize) + // so we must reduce phyStream size to (newSize) or to (_cachedPos) + // newPhySize = _cachedPos; // optional reduce to _cachedPos + } + else + { + // newSize > _cachedPos + _cachedSize + /* It's possible that we need to write zeros, + if new size is larger than old size. + We don't optimize for possible cases here. + So we just flush the cache. */ + _hres = FlushCache(); + } + } + + _virtSize = newSize; + + if (_hres != S_OK) + return _hres; + + if (newSize != _phySize) + { + if (!_stream) + return E_NOTIMPL; + // if (_phyPos > newSize) + RINOK(SeekPhy(newSize)) + if (_setRestriction) + { + UInt64 begin = _restrict_begin; + UInt64 end = _restrict_end; + if (_cachedSize != 0) + { + if (begin > _cachedPos) + begin = _cachedPos; + end = (UInt64)(Int64)-1; + } + _hres = _setRestriction->SetRestriction(begin, end); + RINOK(_hres) + } + _hres = _stream->SetSize(newSize); + RINOK(_hres) + _phySize = newSize; + } return S_OK; } +Z7_COM7F_IMF(CCacheOutStream::SetRestriction(UInt64 begin, UInt64 end)) +{ + PRF(printf("\n============ CCacheOutStream::SetRestriction 0x%x, %u\n", (unsigned)begin, (unsigned)end)); + _restrict_begin = begin; + _restrict_end = end; + return FlushNonRestrictedBlocks(); +} + + + HRESULT Update( DECL_EXTERNAL_CODECS_LOC_VARS const CObjectVector &inputItems, CObjectVector &updateItems, ISequentialOutStream *seqOutStream, CInArchive *inArchive, bool removeSfx, + const CUpdateOptions &updateOptions, const CCompressionMethodMode &compressionMethodMode, IArchiveUpdateCallback *updateCallback) { + /* + // it was tested before if (inArchive) { if (!inArchive->CanUpdate()) return E_NOTIMPL; } + */ + CMyComPtr setRestriction; + seqOutStream->QueryInterface(IID_IStreamSetRestriction, (void **)&setRestriction); + if (setRestriction) + { + RINOK(setRestriction->SetRestriction(0, 0)) + } CMyComPtr outStream; + CCacheOutStream *cacheStream; + bool outSeqMode; + { CMyComPtr outStreamReal; - seqOutStream->QueryInterface(IID_IOutStream, (void **)&outStreamReal); - if (!outStreamReal) - return E_NOTIMPL; + + if (!compressionMethodMode.Force_SeqOutMode) + { + seqOutStream->QueryInterface(IID_IOutStream, (void **)&outStreamReal); + /* + if (!outStreamReal) + return E_NOTIMPL; + */ + } if (inArchive) { if (!inArchive->IsMultiVol && inArchive->ArcInfo.Base > 0 && !removeSfx) { IInStream *baseStream = inArchive->GetBaseStream(); - RINOK(baseStream->Seek(0, STREAM_SEEK_SET, NULL)); - RINOK(NCompress::CopyStream_ExactSize(baseStream, outStreamReal, inArchive->ArcInfo.Base, NULL)); + RINOK(InStream_SeekToBegin(baseStream)) + RINOK(NCompress::CopyStream_ExactSize(baseStream, seqOutStream, (UInt64)inArchive->ArcInfo.Base, NULL)) } } - CCacheOutStream *cacheStream = new CCacheOutStream(); - outStream = cacheStream; - if (!cacheStream->Allocate()) - return E_OUTOFMEMORY; - RINOK(cacheStream->Init(outStreamReal)); + outSeqMode = (outStreamReal == NULL); + if (outSeqMode) + setRestriction.Release(); + /* CCacheOutStream works as non-restricted by default. + So we use (setRestriction == NULL) for outSeqMode */ + // bool use_cacheStream = true; + // if (use_cacheStream) + { + cacheStream = new CCacheOutStream(); + outStream = cacheStream; + if (!cacheStream->Allocate()) + return E_OUTOFMEMORY; + RINOK(cacheStream->Init(seqOutStream, outStreamReal, setRestriction)) + setRestriction.Release(); + if (!outSeqMode) + setRestriction = cacheStream; + } + /* + else if (!outStreamReal) + { + CSeekOutStream *seekOutStream = new CSeekOutStream(); + outStream = seekOutStream; + seekOutStream->Init(seqOutStream); + } + else + outStream = outStreamReal; + */ } COutArchive outArchive; - RINOK(outArchive.Create(outStream)); + outArchive.SetRestriction = setRestriction; + + RINOK(outArchive.Create(outStream)) if (inArchive) { if (!inArchive->IsMultiVol && (Int64)inArchive->ArcInfo.MarkerPos2 > inArchive->ArcInfo.Base) { IInStream *baseStream = inArchive->GetBaseStream(); - RINOK(baseStream->Seek(inArchive->ArcInfo.Base, STREAM_SEEK_SET, NULL)); - UInt64 embStubSize = inArchive->ArcInfo.MarkerPos2 - inArchive->ArcInfo.Base; - RINOK(NCompress::CopyStream_ExactSize(baseStream, outStream, embStubSize, NULL)); + RINOK(InStream_SeekSet(baseStream, (UInt64)inArchive->ArcInfo.Base)) + const UInt64 embStubSize = (UInt64)((Int64)inArchive->ArcInfo.MarkerPos2 - inArchive->ArcInfo.Base); + RINOK(NCompress::CopyStream_ExactSize(baseStream, outStream, embStubSize, NULL)) outArchive.MoveCurPos(embStubSize); } } - return Update2( + RINOK (Update2( EXTERNAL_CODECS_LOC_VARS outArchive, inArchive, inputItems, updateItems, - compressionMethodMode, + updateOptions, + compressionMethodMode, outSeqMode, inArchive ? &inArchive->ArcInfo.Comment : NULL, - updateCallback); + updateCallback)) + + return cacheStream->FinalFlush(); } }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipUpdate.h b/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipUpdate.h index 15cbf69d..13ecd9cb 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipUpdate.h +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/Zip/ZipUpdate.h @@ -1,7 +1,7 @@ // ZipUpdate.h -#ifndef __ZIP_UPDATE_H -#define __ZIP_UPDATE_H +#ifndef ZIP7_INC_ZIP_UPDATE_H +#define ZIP7_INC_ZIP_UPDATE_H #include "../../ICoder.h" #include "../IArchive.h" @@ -14,43 +14,91 @@ namespace NArchive { namespace NZip { +/* struct CUpdateRange { UInt64 Position; UInt64 Size; - // CUpdateRange() {}; - CUpdateRange(UInt64 position, UInt64 size): Position(position), Size(size) {}; + // CUpdateRange() {} + CUpdateRange(UInt64 position, UInt64 size): Position(position), Size(size) {} }; +*/ struct CUpdateItem { bool NewData; bool NewProps; bool IsDir; - bool NtfsTimeIsDefined; + bool Write_NtfsTime; + bool Write_UnixTime; + // bool Write_UnixTime_ATime; bool IsUtf8; + bool Size_WasSetFromStream; + // bool IsAltStream; int IndexInArc; - int IndexInClient; + unsigned IndexInClient; UInt32 Attrib; UInt32 Time; UInt64 Size; AString Name; + CByteBuffer Name_Utf; // for Info-Zip (kIzUnicodeName) Extra + CByteBuffer Comment; // bool Commented; // CUpdateRange CommentRange; FILETIME Ntfs_MTime; FILETIME Ntfs_ATime; FILETIME Ntfs_CTime; - CUpdateItem(): NtfsTimeIsDefined(false), IsUtf8(false), Size(0) {} + void Clear() + { + IsDir = false; + + Write_NtfsTime = false; + Write_UnixTime = false; + + IsUtf8 = false; + Size_WasSetFromStream = false; + // IsAltStream = false; + Time = 0; + Size = 0; + Name.Empty(); + Name_Utf.Free(); + Comment.Free(); + + FILETIME_Clear(Ntfs_MTime); + FILETIME_Clear(Ntfs_ATime); + FILETIME_Clear(Ntfs_CTime); + } + + CUpdateItem(): + IsDir(false), + Write_NtfsTime(false), + Write_UnixTime(false), + IsUtf8(false), + Size_WasSetFromStream(false), + // IsAltStream(false), + Time(0), + Size(0) + {} +}; + + +struct CUpdateOptions +{ + bool Write_MTime; + bool Write_ATime; + bool Write_CTime; }; + HRESULT Update( DECL_EXTERNAL_CODECS_LOC_VARS const CObjectVector &inputItems, CObjectVector &updateItems, ISequentialOutStream *seqOutStream, CInArchive *inArchive, bool removeSfx, + const CUpdateOptions &updateOptions, const CCompressionMethodMode &compressionMethodMode, IArchiveUpdateCallback *updateCallback); diff --git a/src/plugins/7zip/7za/cpp/7zip/Archive/ZstdHandler.cpp b/src/plugins/7zip/7za/cpp/7zip/Archive/ZstdHandler.cpp new file mode 100644 index 00000000..b9c0fb7b --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Archive/ZstdHandler.cpp @@ -0,0 +1,1147 @@ +// ZstdHandler.cpp + +#include "StdAfx.h" + +// #define Z7_USE_ZSTD_ORIG_DECODER +// #define Z7_USE_ZSTD_COMPRESSION + +#include "../../Common/ComTry.h" + +#include "../Common/MethodProps.h" +#include "../Common/ProgressUtils.h" +#include "../Common/RegisterArc.h" +#include "../Common/StreamUtils.h" + +#include "../Compress/CopyCoder.h" +#include "../Compress/ZstdDecoder.h" +#ifdef Z7_USE_ZSTD_ORIG_DECODER +#include "../Compress/Zstd2Decoder.h" +#endif + +#ifdef Z7_USE_ZSTD_COMPRESSION +#include "../Compress/ZstdEncoder.h" +#include "../Compress/ZstdEncoderProps.h" +#include "Common/HandlerOut.h" +#endif + +#include "Common/DummyOutStream.h" + +#include "../../../C/CpuArch.h" + +using namespace NWindows; + +namespace NArchive { +namespace NZstd { + +#define DESCRIPTOR_Get_DictionaryId_Flag(d) ((d) & 3) +#define DESCRIPTOR_FLAG_CHECKSUM (1 << 2) +#define DESCRIPTOR_FLAG_RESERVED (1 << 3) +#define DESCRIPTOR_FLAG_UNUSED (1 << 4) +#define DESCRIPTOR_FLAG_SINGLE (1 << 5) +#define DESCRIPTOR_Get_ContentSize_Flag3(d) ((d) >> 5) +#define DESCRIPTOR_Is_ContentSize_Defined(d) (((d) & 0xe0) != 0) + +struct CFrameHeader +{ + Byte Descriptor; + Byte WindowDescriptor; + UInt32 DictionaryId; + UInt64 ContentSize; + + /* by zstd specification: + the decoder must check that (Is_Reserved() == false) + the decoder must ignore Unused_bit */ + bool Is_Reserved() const { return (Descriptor & DESCRIPTOR_FLAG_RESERVED) != 0; } + bool Is_Checksum() const { return (Descriptor & DESCRIPTOR_FLAG_CHECKSUM) != 0; } + bool Is_SingleSegment() const { return (Descriptor & DESCRIPTOR_FLAG_SINGLE) != 0; } + bool Is_ContentSize_Defined() const { return DESCRIPTOR_Is_ContentSize_Defined(Descriptor); } + unsigned Get_DictionaryId_Flag() const { return DESCRIPTOR_Get_DictionaryId_Flag(Descriptor); } + unsigned Get_ContentSize_Flag3() const { return DESCRIPTOR_Get_ContentSize_Flag3(Descriptor); } + + const Byte *Parse(const Byte *p, int size) + { + if ((unsigned)size < 2) + return NULL; + Descriptor = *p++; + size--; + { + Byte w = 0; + if (!Is_SingleSegment()) + { + w = *p++; + size--; + } + WindowDescriptor = w; + } + { + unsigned n = Get_DictionaryId_Flag(); + UInt32 d = 0; + if (n) + { + n = (unsigned)1 << (n - 1); + if ((size -= (int)n) < 0) + return NULL; + d = GetUi32(p) & ((UInt32)(Int32)-1 >> (32 - 8u * n)); + p += n; + } + DictionaryId = d; + } + { + unsigned n = Get_ContentSize_Flag3(); + UInt64 v = 0; + if (n) + { + n >>= 1; + if (n == 1) + v = 256; + n = (unsigned)1 << n; + if ((size -= (int)n) < 0) + return NULL; + v += GetUi64(p) & ((UInt64)(Int64)-1 >> (64 - 8u * n)); + p += n; + } + ContentSize = v; + } + return p; + } +}; + + + +class CHandler Z7_final: + public IInArchive, + public IArchiveOpenSeq, + public ISetProperties, +#ifdef Z7_USE_ZSTD_COMPRESSION + public IOutArchive, +#endif + public CMyUnknownImp +{ + Z7_COM_QI_BEGIN2(IInArchive) + Z7_COM_QI_ENTRY(IArchiveOpenSeq) + Z7_COM_QI_ENTRY(ISetProperties) +#ifdef Z7_USE_ZSTD_COMPRESSION + Z7_COM_QI_ENTRY(IOutArchive) +#endif + Z7_COM_QI_END + Z7_COM_ADDREF_RELEASE + + Z7_IFACE_COM7_IMP(IInArchive) + Z7_IFACE_COM7_IMP(IArchiveOpenSeq) + Z7_IFACE_COM7_IMP(ISetProperties) +#ifdef Z7_USE_ZSTD_COMPRESSION + Z7_IFACE_COM7_IMP(IOutArchive) +#endif + + bool _isArc; + bool _needSeekToStart; + // bool _dataAfterEnd; + // bool _needMoreInput; + bool _unsupportedBlock; + + bool _wasParsed; + bool _phySize_Decoded_Defined; + bool _unpackSize_Defined; // decoded + bool _decoded_Info_Defined; + + bool _parseMode; + bool _disableHash; + // bool _smallMode; + + UInt64 _phySize; + UInt64 _phySize_Decoded; + UInt64 _unpackSize; + + CZstdDecInfo _parsed_Info; + CZstdDecInfo _decoded_Info; + + CMyComPtr _stream; + CMyComPtr _seqStream; + +#ifdef Z7_USE_ZSTD_COMPRESSION + CSingleMethodProps _props; +#endif + +public: + CHandler(): + _parseMode(false), + _disableHash(false) + // _smallMode(false) + {} +}; + + +static const Byte kProps[] = +{ + kpidSize, + kpidPackSize +}; + +static const Byte kArcProps[] = +{ + kpidNumStreams, + kpidNumBlocks, + kpidMethod, + // kpidChecksum + kpidCRC +}; + +IMP_IInArchive_Props +IMP_IInArchive_ArcProps + + +// static const unsigned kBlockType_Raw = 0; +static const unsigned kBlockType_RLE = 1; +// static const unsigned kBlockType_Compressed = 2; +static const unsigned kBlockType_Reserved = 3; +/* +static const char * const kNames[] = +{ + "RAW" + , "RLE" + , "Compressed" + , "Reserved" +}; +*/ + +static void Add_UInt64(AString &s, const char *name, UInt64 v) +{ + s.Add_OptSpaced(name); + s.Add_Colon(); + s.Add_UInt64(v); +} + + +static void PrintSize(AString &s, UInt64 w) +{ + char c = 0; + if ((w & ((1 << 30) - 1)) == 0) { c = 'G'; w >>= 30; } + else if ((w & ((1 << 20) - 1)) == 0) { c = 'M'; w >>= 20; } + else if ((w & ((1 << 10) - 1)) == 0) { c = 'K'; w >>= 10; } + s.Add_UInt64(w); + if (c) + { + s.Add_Char(c); + s += "iB"; + } +} + + +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) +{ + NCOM::CPropVariant prop; + + CZstdDecInfo *p = NULL; + if (_wasParsed || !_decoded_Info_Defined) + p = &_parsed_Info; + else if (_decoded_Info_Defined) + p = &_decoded_Info; + + switch (propID) + { + case kpidPhySize: + if (_wasParsed) + prop = _phySize; + else if (_phySize_Decoded_Defined) + prop = _phySize_Decoded; + break; + + case kpidUnpackSize: + if (_unpackSize_Defined) + prop = _unpackSize; + break; + + case kpidNumStreams: + if (p) + if (_wasParsed || _decoded_Info_Defined) + prop = p->num_DataFrames; + break; + + case kpidNumBlocks: + if (p) + if (_wasParsed || _decoded_Info_Defined) + prop = p->num_Blocks; + break; + + // case kpidChecksum: + case kpidCRC: + if (p) + if (p->checksum_Defined && p->num_DataFrames == 1) + prop = p->checksum; // it's checksum from last frame + break; + + case kpidMethod: + { + AString s; + s.Add_OptSpaced(p == &_decoded_Info ? + "decoded:" : _wasParsed ? + "parsed:" : + "header-open-only:"); + + if (p->dictionaryId != 0) + { + if (p->are_DictionaryId_Different) + s.Add_OptSpaced("different-dictionary-IDs"); + s.Add_OptSpaced("dictionary-ID:"); + s.Add_UInt32(p->dictionaryId); + } + /* + if (ContentSize_Defined) + { + s.Add_OptSpaced("ContentSize="); + s.Add_UInt64(ContentSize_Total); + } + */ + // if (p->are_Checksums) + if (p->descriptor_OR & DESCRIPTOR_FLAG_CHECKSUM) + s.Add_OptSpaced("XXH64"); + if (p->descriptor_NOT_OR & DESCRIPTOR_FLAG_CHECKSUM) + s.Add_OptSpaced("NO-XXH64"); + + if (p->descriptor_OR & DESCRIPTOR_FLAG_UNUSED) + s.Add_OptSpaced("unused_bit"); + + if (p->descriptor_OR & DESCRIPTOR_FLAG_SINGLE) + s.Add_OptSpaced("single-segments"); + + if (p->descriptor_NOT_OR & DESCRIPTOR_FLAG_SINGLE) + { + // Add_UInt64(s, "wnd-descriptors", p->num_WindowDescriptors); + s.Add_OptSpaced("wnd-desc-log-MAX:"); + // WindowDescriptor_MAX = 16 << 3; // for debug + const unsigned e = p->windowDescriptor_MAX >> 3; + s.Add_UInt32(e + 10); + const unsigned m = p->windowDescriptor_MAX & 7; + if (m != 0) + { + s.Add_Dot(); + s.Add_UInt32(m); + } + } + + if (DESCRIPTOR_Is_ContentSize_Defined(p->descriptor_OR) || + (p->descriptor_NOT_OR & DESCRIPTOR_FLAG_SINGLE)) + /* + if (p->are_ContentSize_Known || + p->are_WindowDescriptors) + */ + { + s.Add_OptSpaced("wnd-MAX:"); + PrintSize(s, p->windowSize_MAX); + if (p->windowSize_MAX != p->windowSize_Allocate_MAX) + { + s.Add_OptSpaced("wnd-use-MAX:"); + PrintSize(s, p->windowSize_Allocate_MAX); + } + } + + if (p->num_DataFrames != 1) + Add_UInt64(s, "data-frames", p->num_DataFrames); + if (p->num_SkipFrames != 0) + { + Add_UInt64(s, "skip-frames", p->num_SkipFrames); + Add_UInt64(s, "skip-frames-size-total", p->skipFrames_Size); + } + + if (p->are_ContentSize_Unknown) + s.Add_OptSpaced("unknown-content-size"); + + if (DESCRIPTOR_Is_ContentSize_Defined(p->descriptor_OR)) + { + Add_UInt64(s, "content-size-frame-max", p->contentSize_MAX); + Add_UInt64(s, "content-size-total", p->contentSize_Total); + } + + /* + for (unsigned i = 0; i < 4; i++) + { + const UInt64 n = p->num_Blocks_forType[i]; + if (n) + { + s.Add_OptSpaced(kNames[i]); + s += "-blocks:"; + s.Add_UInt64(n); + + s.Add_OptSpaced(kNames[i]); + s += "-block-bytes:"; + s.Add_UInt64(p->num_BlockBytes_forType[i]); + } + } + */ + prop = s; + break; + } + + case kpidErrorFlags: + { + UInt32 v = 0; + if (!_isArc) v |= kpv_ErrorFlags_IsNotArc; + // if (_needMoreInput) v |= kpv_ErrorFlags_UnexpectedEnd; + // if (_dataAfterEnd) v |= kpv_ErrorFlags_DataAfterEnd; + if (_unsupportedBlock) v |= kpv_ErrorFlags_UnsupportedMethod; + /* + if (_parsed_Info.numBlocks_forType[kBlockType_Reserved]) + v |= kpv_ErrorFlags_UnsupportedMethod; + */ + prop = v; + break; + } + + default: break; + } + prop.Detach(value); + return S_OK; +} + + +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) +{ + *numItems = 1; + return S_OK; +} + +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 /* index */, PROPID propID, PROPVARIANT *value)) +{ + NCOM::CPropVariant prop; + switch (propID) + { + case kpidPackSize: + if (_wasParsed) + prop = _phySize; + else if (_phySize_Decoded_Defined) + prop = _phySize_Decoded; + break; + + case kpidSize: + if (_wasParsed && !_parsed_Info.are_ContentSize_Unknown) + prop = _parsed_Info.contentSize_Total; + else if (_unpackSize_Defined) + prop = _unpackSize; + break; + + default: break; + } + prop.Detach(value); + return S_OK; +} + +static const unsigned kSignatureSize = 4; +static const Byte k_Signature[kSignatureSize] = { 0x28, 0xb5, 0x2f, 0xfd } ; + +static const UInt32 kDataFrameSignature32 = 0xfd2fb528; +static const UInt32 kSkipFrameSignature = 0x184d2a50; +static const UInt32 kSkipFrameSignature_Mask = 0xfffffff0; + +/* +API_FUNC_static_IsArc IsArc_Zstd(const Byte *p, size_t size) +{ + if (size < kSignatureSize) + return k_IsArc_Res_NEED_MORE; + if (memcmp(p, k_Signature, kSignatureSize) != 0) + { + const UInt32 v = GetUi32(p); + if ((v & kSkipFrameSignature_Mask) != kSkipFrameSignature) + return k_IsArc_Res_NO; + return k_IsArc_Res_YES; + } + p += 4; + // return k_IsArc_Res_YES; +} +} +*/ + +// kBufSize must be >= (ZSTD_FRAMEHEADERSIZE_MAX = 18) +// we use big buffer for fast parsing of worst case small blocks. +static const unsigned kBufSize = + 1 << 9; + // 1 << 14; // fastest in real file + +struct CStreamBuffer +{ + unsigned pos; + unsigned lim; + IInStream *Stream; + UInt64 StreamOffset; + Byte buf[kBufSize]; + + CStreamBuffer(): + pos(0), + lim(0), + StreamOffset(0) + {} + unsigned Avail() const { return lim - pos; } + const Byte *GetPtr() const { return &buf[pos]; } + UInt64 GetCurOffset() const { return StreamOffset - Avail(); } + void SkipInBuf(UInt32 size) { pos += size; } + HRESULT Skip(UInt32 size); + HRESULT Read(unsigned num); +}; + +HRESULT CStreamBuffer::Skip(UInt32 size) +{ + unsigned rem = lim - pos; + if (rem != 0) + { + if (rem > size) + rem = size; + pos += rem; + size -= rem; + if (pos != lim) + return S_OK; + } + if (size == 0) + return S_OK; + return Stream->Seek(size, STREAM_SEEK_CUR, &StreamOffset); +} + +HRESULT CStreamBuffer::Read(unsigned num) +{ + if (lim - pos >= num) + return S_OK; + if (pos != 0) + { + lim -= pos; + memmove(buf, buf + pos, lim); + pos = 0; + } + size_t processed = kBufSize - ((unsigned)StreamOffset & (kBufSize - 1)); + const unsigned avail = kBufSize - lim; + num -= lim; + if (avail < processed || processed < num) + processed = avail; + const HRESULT res = ReadStream(Stream, buf + lim, &processed); + StreamOffset += processed; + lim += (unsigned)processed; + return res; +} + + +static const unsigned k_ZSTD_FRAMEHEADERSIZE_MAX = 4 + 14; + +Z7_COM7F_IMF(CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *callback)) +{ + COM_TRY_BEGIN + Close(); + + CZstdDecInfo *p = &_parsed_Info; + // p->are_ContentSize_Unknown = False; + CStreamBuffer sb; + sb.Stream = stream; + + for (;;) + { + RINOK(sb.Read(k_ZSTD_FRAMEHEADERSIZE_MAX)) + if (sb.Avail() < kSignatureSize) + break; + + if (callback && (ZstdDecInfo_GET_NUM_FRAMES(p) & 0xFFF) == 2) + { + const UInt64 numBytes = sb.GetCurOffset(); + RINOK(callback->SetCompleted(NULL, &numBytes)) + } + + const UInt32 v = GetUi32(sb.GetPtr()); + if (v != kDataFrameSignature32) + { + if ((v & kSkipFrameSignature_Mask) != kSkipFrameSignature) + break; + _phySize = sb.GetCurOffset() + 8; + p->num_SkipFrames++; + sb.SkipInBuf(4); + if (sb.Avail() < 4) + break; + const UInt32 size = GetUi32(sb.GetPtr()); + p->skipFrames_Size += size; + sb.SkipInBuf(4); + _phySize = sb.GetCurOffset() + size; + RINOK(sb.Skip(size)) + continue; + } + + p->num_DataFrames++; + // _numStreams_Defined = true; + sb.SkipInBuf(4); + CFrameHeader fh; + { + const Byte *data = fh.Parse(sb.GetPtr(), (int)sb.Avail()); + if (!data) + { + // _needMoreInput = true; + // we set size for one byte more to show that stream was truncated + _phySize = sb.StreamOffset + 1; + break; + } + if (fh.Is_Reserved()) + { + // we don't want false detection + if (ZstdDecInfo_GET_NUM_FRAMES(p) == 1) + return S_FALSE; + // _phySize = sb.GetCurOffset(); + break; + } + sb.SkipInBuf((unsigned)(data - sb.GetPtr())); + } + + p->descriptor_OR = (Byte)(p->descriptor_OR | fh.Descriptor); + p->descriptor_NOT_OR = (Byte)(p->descriptor_NOT_OR | ~fh.Descriptor); + + // _numBlocks_Defined = true; + // if (fh.Get_DictionaryId_Flag()) + // p->dictionaryId_Cur = fh.DictionaryId; + if (fh.DictionaryId != 0) + { + if (p->dictionaryId == 0) + p->dictionaryId = fh.DictionaryId; + else if (p->dictionaryId != fh.DictionaryId) + p->are_DictionaryId_Different = True; + } + + UInt32 blockSizeAllowedMax = (UInt32)1 << 17; + { + UInt64 winSize = fh.ContentSize; + UInt64 winSize_forAllocate = fh.ContentSize; + if (!fh.Is_SingleSegment()) + { + if (p->windowDescriptor_MAX < fh.WindowDescriptor) + p->windowDescriptor_MAX = fh.WindowDescriptor; + const unsigned e = (fh.WindowDescriptor >> 3); + const unsigned m = (fh.WindowDescriptor & 7); + winSize = (UInt64)(8 + m) << (e + 10 - 3); + if (!fh.Is_ContentSize_Defined() + || fh.DictionaryId != 0 + || winSize_forAllocate > winSize) + winSize_forAllocate = winSize; + // p->are_WindowDescriptors = true; + } + else + { + // p->are_SingleSegments = True; + } + if (blockSizeAllowedMax > winSize) + blockSizeAllowedMax = (UInt32)winSize; + if (p->windowSize_MAX < winSize) + p->windowSize_MAX = winSize; + if (p->windowSize_Allocate_MAX < winSize_forAllocate) + p->windowSize_Allocate_MAX = winSize_forAllocate; + } + + if (fh.Is_ContentSize_Defined()) + { + // p->are_ContentSize_Known = True; + p->contentSize_Total += fh.ContentSize; + if (p->contentSize_MAX < fh.ContentSize) + p->contentSize_MAX = fh.ContentSize; + } + else + { + p->are_ContentSize_Unknown = True; + } + + p->checksum_Defined = false; + + // p->numBlocks_forType[3] += 99; // for debug + + if (!_parseMode) + { + if (ZstdDecInfo_GET_NUM_FRAMES(p) == 1) + break; + } + + _wasParsed = true; + + bool blocksWereParsed = false; + + for (;;) + { + if (callback && (p->num_Blocks & 0xFFF) == 2) + { + // Sleep(10); + const UInt64 numBytes = sb.GetCurOffset(); + RINOK(callback->SetCompleted(NULL, &numBytes)) + } + _phySize = sb.GetCurOffset() + 3; + RINOK(sb.Read(3)) + if (sb.Avail() < 3) + { + // _needMoreInput = true; + // return S_FALSE; + break; // change it + } + const unsigned pos = sb.pos; + sb.pos = pos + 3; + UInt32 b = 0; + b += (UInt32)sb.buf[pos]; + b += (UInt32)sb.buf[pos + 1] << (8 * 1); + b += (UInt32)sb.buf[pos + 2] << (8 * 2); + p->num_Blocks++; + const unsigned blockType = (b >> 1) & 3; + UInt32 size = b >> 3; + // p->num_Blocks_forType[blockType]++; + // p->num_BlockBytes_forType[blockType] += size; + if (size > blockSizeAllowedMax + || blockType == kBlockType_Reserved) + { + _unsupportedBlock = true; + if (ZstdDecInfo_GET_NUM_FRAMES(p) == 1 && p->num_Blocks == 1) + return S_FALSE; + break; + } + if (blockType == kBlockType_RLE) + size = 1; + _phySize = sb.GetCurOffset() + size; + RINOK(sb.Skip(size)) + if (b & 1) + { + // it's last block + blocksWereParsed = true; + break; + } + } + + if (!blocksWereParsed) + break; + + if (fh.Is_Checksum()) + { + _phySize = sb.GetCurOffset() + 4; + RINOK(sb.Read(4)) + if (sb.Avail() < 4) + break; + p->checksum_Defined = true; + // if (p->num_DataFrames == 1) + p->checksum = GetUi32(sb.GetPtr()); + sb.SkipInBuf(4); + } + } + + if (ZstdDecInfo_GET_NUM_FRAMES(p) == 0) + return S_FALSE; + + _needSeekToStart = true; + // } // _parseMode + _isArc = true; + _stream = stream; + _seqStream = stream; + + return S_OK; + COM_TRY_END +} + + +Z7_COM7F_IMF(CHandler::OpenSeq(ISequentialInStream *stream)) +{ + Close(); + _isArc = true; + _seqStream = stream; + return S_OK; +} + + +Z7_COM7F_IMF(CHandler::Close()) +{ + _isArc = false; + _needSeekToStart = false; + // _dataAfterEnd = false; + // _needMoreInput = false; + _unsupportedBlock = false; + + _wasParsed = false; + _phySize_Decoded_Defined = false; + _unpackSize_Defined = false; + _decoded_Info_Defined = false; + + ZstdDecInfo_CLEAR(&_parsed_Info) + ZstdDecInfo_CLEAR(&_decoded_Info) + + _phySize = 0; + _phySize_Decoded = 0; + _unpackSize = 0; + + _seqStream.Release(); + _stream.Release(); + return S_OK; +} + + +Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testMode, IArchiveExtractCallback *extractCallback)) +{ + COM_TRY_BEGIN + if (numItems == 0) + return S_OK; + if (numItems != (UInt32)(Int32)-1 && (numItems != 1 || indices[0] != 0)) + return E_INVALIDARG; + if (_wasParsed) + { + RINOK(extractCallback->SetTotal(_phySize)) + } + + Int32 opRes; + { + CMyComPtr realOutStream; + const Int32 askMode = testMode ? + NExtract::NAskMode::kTest : + NExtract::NAskMode::kExtract; + RINOK(extractCallback->GetStream(0, &realOutStream, askMode)) + if (!testMode && !realOutStream) + return S_OK; + + extractCallback->PrepareOperation(askMode); + + if (_needSeekToStart) + { + if (!_stream) + return E_FAIL; + RINOK(_stream->Seek(0, STREAM_SEEK_SET, NULL)) + } + else + _needSeekToStart = true; + + CMyComPtr2_Create lps; + lps->Init(extractCallback, true); + +#ifdef Z7_USE_ZSTD_ORIG_DECODER + CMyComPtr2_Create decoder; +#else + CMyComPtr2_Create decoder; +#endif + + CMyComPtr2_Create outStreamSpec; + outStreamSpec->SetStream(realOutStream); + outStreamSpec->Init(); + // realOutStream.Release(); + + decoder->FinishMode = true; +#ifndef Z7_USE_ZSTD_ORIG_DECODER + decoder->DisableHash = _disableHash; +#endif + + // _dataAfterEnd = false; + // _needMoreInput = false; + const HRESULT hres = decoder.Interface()->Code(_seqStream, outStreamSpec, NULL, NULL, lps); + /* + { + UInt64 t1 = decoder->GetInputProcessedSize(); + // for debug + const UInt32 kTempSize = 64; + Byte buf[kTempSize]; + UInt32 processedSize = 0; + RINOK(decoder->ReadUnusedFromInBuf(buf, kTempSize, &processedSize)) + processedSize -= processedSize; + UInt64 t2 = decoder->GetInputProcessedSize(); + t2 = t2; + t1 = t1; + } + */ + const UInt64 outSize = outStreamSpec->GetSize(); + // } + + // if (hres == E_ABORT) return hres; + opRes = NExtract::NOperationResult::kDataError; + + if (hres == E_OUTOFMEMORY) + { + return hres; + // opRes = NExtract::NOperationResult::kMemError; + } + else if (hres == S_OK || hres == S_FALSE) + { +#ifndef Z7_USE_ZSTD_ORIG_DECODER + _decoded_Info_Defined = true; + _decoded_Info = decoder->_state.info; + // NumDataFrames_Decoded = decoder->_state.info.num_DataFrames; + // NumSkipFrames_Decoded = decoder->_state.info.num_SkipFrames; + const UInt64 inSize = decoder->_inProcessed; +#else + const UInt64 inSize = decoder->GetInputProcessedSize(); +#endif + _phySize_Decoded = inSize; + _phySize_Decoded_Defined = true; + + _unpackSize_Defined = true; + _unpackSize = outSize; + + // RINOK( + lps.Interface()->SetRatioInfo(&inSize, &outSize); + +#ifdef Z7_USE_ZSTD_ORIG_DECODER + if (hres == S_OK) + opRes = NExtract::NOperationResult::kOK; +#else + if (decoder->ResInfo.decode_SRes == SZ_ERROR_CRC) + { + opRes = NExtract::NOperationResult::kCRCError; + } + else if (decoder->ResInfo.decode_SRes == SZ_ERROR_NO_ARCHIVE) + { + _isArc = false; + opRes = NExtract::NOperationResult::kIsNotArc; + } + else if (decoder->ResInfo.decode_SRes == SZ_ERROR_INPUT_EOF) + opRes = NExtract::NOperationResult::kUnexpectedEnd; + else + { + if (hres == S_OK && decoder->ResInfo.decode_SRes == SZ_OK) + opRes = NExtract::NOperationResult::kOK; + if (decoder->ResInfo.extraSize) + { + // if (inSize == 0) _isArc = false; + opRes = NExtract::NOperationResult::kDataAfterEnd; + } + /* + if (decoder->ResInfo.unexpededEnd) + opRes = NExtract::NOperationResult::kUnexpectedEnd; + */ + } +#endif + } + else if (hres == E_NOTIMPL) + { + opRes = NExtract::NOperationResult::kUnsupportedMethod; + } + else + return hres; + } + + return extractCallback->SetOperationResult(opRes); + + COM_TRY_END +} + + + +Z7_COM7F_IMF(CHandler::SetProperties(const wchar_t * const *names, const PROPVARIANT *values, UInt32 numProps)) +{ + // return _props.SetProperties(names, values, numProps); + // _smallMode = false; + _disableHash = false; + _parseMode = false; + // _parseMode = true; // for debug +#ifdef Z7_USE_ZSTD_COMPRESSION + _props.Init(); +#endif + + for (UInt32 i = 0; i < numProps; i++) + { + UString name = names[i]; + const PROPVARIANT &value = values[i]; + + if (name.IsEqualTo("parse")) + { + bool parseMode = true; + RINOK(PROPVARIANT_to_bool(value, parseMode)) + _parseMode = parseMode; + continue; + } + if (name.IsPrefixedBy_Ascii_NoCase("crc")) + { + name.Delete(0, 3); + UInt32 crcSize = 4; + RINOK(ParsePropToUInt32(name, value, crcSize)) + if (crcSize == 0) + _disableHash = true; + else if (crcSize == 4) + _disableHash = false; + else + return E_INVALIDARG; + continue; + } +#ifdef Z7_USE_ZSTD_COMPRESSION + /* + if (name.IsEqualTo("small")) + { + bool smallMode = true; + RINOK(PROPVARIANT_to_bool(value, smallMode)) + _smallMode = smallMode; + continue; + } + */ + RINOK(_props.SetProperty(names[i], value)) +#endif + } + return S_OK; +} + + + + +#ifdef Z7_USE_ZSTD_COMPRESSION + +Z7_COM7F_IMF(CHandler::GetFileTimeType(UInt32 *timeType)) +{ + *timeType = GET_FileTimeType_NotDefined_for_GetFileTimeType; + // *timeType = NFileTimeType::kUnix; + return S_OK; +} + + +Z7_COM7F_IMF(CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numItems, + IArchiveUpdateCallback *updateCallback)) +{ + COM_TRY_BEGIN + + if (numItems != 1) + return E_INVALIDARG; + { + CMyComPtr setRestriction; + outStream->QueryInterface(IID_IStreamSetRestriction, (void **)&setRestriction); + if (setRestriction) + RINOK(setRestriction->SetRestriction(0, 0)) + } + Int32 newData, newProps; + UInt32 indexInArchive; + if (!updateCallback) + return E_FAIL; + RINOK(updateCallback->GetUpdateItemInfo(0, &newData, &newProps, &indexInArchive)) + + if (IntToBool(newProps)) + { + { + NCOM::CPropVariant prop; + RINOK(updateCallback->GetProperty(0, kpidIsDir, &prop)) + if (prop.vt != VT_EMPTY) + if (prop.vt != VT_BOOL || prop.boolVal != VARIANT_FALSE) + return E_INVALIDARG; + } + } + + if (IntToBool(newData)) + { + UInt64 size; + { + NCOM::CPropVariant prop; + RINOK(updateCallback->GetProperty(0, kpidSize, &prop)) + if (prop.vt != VT_UI8) + return E_INVALIDARG; + size = prop.uhVal.QuadPart; + } + + if (!_props.MethodName.IsEmpty() + && !_props.MethodName.IsEqualTo_Ascii_NoCase("zstd")) + return E_INVALIDARG; + + { + CMyComPtr fileInStream; + RINOK(updateCallback->GetStream(0, &fileInStream)) + if (!fileInStream) + return S_FALSE; + { + CMyComPtr streamGetSize; + fileInStream.QueryInterface(IID_IStreamGetSize, &streamGetSize); + if (streamGetSize) + { + UInt64 size2; + if (streamGetSize->GetSize(&size2) == S_OK) + size = size2; + } + } + RINOK(updateCallback->SetTotal(size)) + + CMethodProps props2 = _props; + +#ifndef Z7_ST + /* + CSingleMethodProps (_props) + derives from + CMethodProps (props2) + So we transfer additional variable (num Threads) to CMethodProps list of properties + */ + + UInt32 numThreads = _props._numThreads; + + if (numThreads > Z7_ZSTDMT_NBWORKERS_MAX) + numThreads = Z7_ZSTDMT_NBWORKERS_MAX; + + if (_props.FindProp(NCoderPropID::kNumThreads) < 0) + { + if (!_props._numThreads_WasForced + && numThreads >= 1 + && _props._memUsage_WasSet) + { + NCompress::NZstd::CEncoderProps zstdProps; + RINOK(zstdProps.SetFromMethodProps(_props)) + ZstdEncProps_NormalizeFull(&zstdProps.EncProps); + numThreads = ZstdEncProps_GetNumThreads_for_MemUsageLimit( + &zstdProps.EncProps, _props._memUsage_Compress, numThreads); + } + props2.AddProp_NumThreads(numThreads); + } + +#endif // Z7_ST + + CMyComPtr2_Create lps; + lps->Init(updateCallback, true); + { + CMyComPtr2_Create encoder; + // size = 1 << 24; // for debug + RINOK(props2.SetCoderProps(encoder.ClsPtr(), size != (UInt64)(Int64)-1 ? &size : NULL)) + // encoderSpec->_props.SmallFileOpt = _smallMode; + // we must set kExpectedDataSize just before Code(). + encoder->SrcSizeHint64 = size; + /* + CMyComPtr optProps; + _compressEncoder->QueryInterface(IID_ICompressSetCoderPropertiesOpt, (void**)&optProps); + if (optProps) + { + PROPID propID = NCoderPropID::kExpectedDataSize; + NWindows::NCOM::CPropVariant prop = (UInt64)size; + // RINOK(optProps->SetCoderPropertiesOpt(&propID, &prop, 1)) + RINOK(encoderSpec->SetCoderPropertiesOpt(&propID, &prop, 1)) + } + */ + RINOK(encoder.Interface()->Code(fileInStream, outStream, NULL, NULL, lps)) + } + } + return updateCallback->SetOperationResult(NArchive::NUpdate::NOperationResult::kOK); + } + + if (indexInArchive != 0) + return E_INVALIDARG; + + CMyComPtr2_Create lps; + lps->Init(updateCallback, true); + + CMyComPtr opCallback; + updateCallback->QueryInterface(IID_IArchiveUpdateCallbackFile, (void **)&opCallback); + if (opCallback) + { + RINOK(opCallback->ReportOperation( + NEventIndexType::kInArcIndex, 0, + NUpdateNotifyOp::kReplicate)) + } + + if (_stream) + RINOK(_stream->Seek(0, STREAM_SEEK_SET, NULL)) + + return NCompress::CopyStream(_stream, outStream, lps); + + COM_TRY_END +} +#endif + + + +#ifndef Z7_USE_ZSTD_COMPRESSION +#undef IMP_CreateArcOut +#define IMP_CreateArcOut +#undef CreateArcOut +#define CreateArcOut NULL +#endif + +#ifdef Z7_USE_ZSTD_COMPRESSION +REGISTER_ARC_IO( + "zstd2", "zst tzst", "* .tar", 0xe + 1, + k_Signature, 0 + , NArcInfoFlags::kKeepName + , 0 + , NULL) +#else +REGISTER_ARC_IO( + "zstd", "zst tzst", "* .tar", 0xe, + k_Signature, 0 + , NArcInfoFlags::kKeepName + , 0 + , NULL) +#endif + +}} diff --git a/src/plugins/7zip/7za/cpp/7zip/Asm.mak b/src/plugins/7zip/7za/cpp/7zip/Asm.mak new file mode 100644 index 00000000..4ee0823c --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Asm.mak @@ -0,0 +1,12 @@ +!IFDEF ASM_OBJS +!IF "$(PLATFORM)" == "arm64" +$(ASM_OBJS): ../../../../Asm/arm64/$(*B).S + $(COMPL_ASM_CLANG) +!ELSEIF "$(PLATFORM)" == "arm" +$(ASM_OBJS): ../../../../Asm/arm/$(*B).asm + $(COMPL_ASM) +!ELSEIF "$(PLATFORM)" != "ia64" && "$(PLATFORM)" != "mips" +$(ASM_OBJS): ../../../../Asm/x86/$(*B).asm + $(COMPL_ASM) +!ENDIF +!ENDIF diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone/Alone.dsp b/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone/Alone.dsp new file mode 100644 index 00000000..6512e409 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone/Alone.dsp @@ -0,0 +1,3324 @@ +# Microsoft Developer Studio Project File - Name="Alone" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=Alone - Win32 DebugU +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "Alone.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "Alone.mak" CFG="Alone - Win32 DebugU" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "Alone - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "Alone - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE "Alone - Win32 ReleaseU" (based on "Win32 (x86) Console Application") +!MESSAGE "Alone - Win32 DebugU" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "Alone - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /Gr /MT /W4 /WX /GX /O1 /I "..\..\..\\" /D "NDEBUG" /D "_MBCS" /D "WIN32" /D "_CONSOLE" /D "Z7_LONG_PATH" /D "Z7_DEVICE_FILE" /FAcs /Yu"StdAfx.h" /FD /c +# ADD BASE RSC /l 0x419 /d "NDEBUG" +# ADD RSC /l 0x419 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 /out:"c:\UTIL\7za.exe" /opt:NOWIN98 +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /Gz /MDd /W4 /WX /Gm /GX /ZI /Od /I "..\..\..\\" /D "_DEBUG" /D "_MBCS" /D "WIN32" /D "_CONSOLE" /D "Z7_LONG_PATH" /D "Z7_DEVICE_FILE" /Yu"StdAfx.h" /FD /GZ /c +# ADD BASE RSC /l 0x419 /d "_DEBUG" +# ADD RSC /l 0x419 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /out:"c:\UTIL\7za.exe" /pdbtype:sept + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "ReleaseU" +# PROP BASE Intermediate_Dir "ReleaseU" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "ReleaseU" +# PROP Intermediate_Dir "ReleaseU" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MD /W3 /GX /O2 /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /Yu"StdAfx.h" /FD /c +# ADD CPP /nologo /Gz /MD /W4 /WX /GX /O1 /I "..\..\..\\" /D "NDEBUG" /D "UNICODE" /D "_UNICODE" /D "WIN32" /D "_CONSOLE" /D "Z7_LONG_PATH" /D "Z7_DEVICE_FILE" /Yu"StdAfx.h" /FD /c +# ADD BASE RSC /l 0x419 /d "NDEBUG" +# ADD RSC /l 0x419 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 /out:"c:\UTIL\7za.exe" /opt:NOWIN98 +# SUBTRACT BASE LINK32 /pdb:none +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 /out:"c:\UTIL\7zan.exe" /opt:NOWIN98 +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "DebugU" +# PROP BASE Intermediate_Dir "DebugU" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "DebugU" +# PROP Intermediate_Dir "DebugU" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "_DEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /Yu"StdAfx.h" /FD /GZ /c +# ADD CPP /nologo /Gz /MDd /W4 /WX /Gm /GX /ZI /Od /I "..\..\..\\" /D "_DEBUG" /D "_UNICODE" /D "UNICODE" /D "WIN32" /D "_CONSOLE" /D "Z7_LONG_PATH" /D "Z7_DEVICE_FILE" /Yu"StdAfx.h" /FD /GZ /c +# ADD BASE RSC /l 0x419 /d "_DEBUG" +# ADD RSC /l 0x419 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /out:"c:\UTIL\7za.exe" /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /out:"c:\UTIL\7zan.exe" /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "Alone - Win32 Release" +# Name "Alone - Win32 Debug" +# Name "Alone - Win32 ReleaseU" +# Name "Alone - Win32 DebugU" +# Begin Group "Console" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\UI\Console\ArError.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\BenchCon.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\BenchCon.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\CompressionMode.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\ConsoleClose.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\ConsoleClose.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\ExtractCallbackConsole.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\ExtractCallbackConsole.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\HashCon.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\HashCon.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\List.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\List.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\Main.cpp +# ADD CPP /D "Z7_PROG_VARIANT_A" +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\MainAr.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\OpenCallbackConsole.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\OpenCallbackConsole.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\PercentPrinter.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\PercentPrinter.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\UpdateCallbackConsole.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\UpdateCallbackConsole.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\UserInputUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\UserInputUtils.h +# End Source File +# End Group +# Begin Group "Spec" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=.\resource.rc +# End Source File +# Begin Source File + +SOURCE=.\StdAfx.cpp +# ADD CPP /Yc"StdAfx.h" +# End Source File +# Begin Source File + +SOURCE=.\StdAfx.h +# End Source File +# End Group +# Begin Group "Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\Common\AutoPtr.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Buffer.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\CommandLineParser.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\CommandLineParser.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Common.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Common0.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\ComTry.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\CRC.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\CrcReg.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Defs.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\DynamicBuffer.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\DynLimBuf.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\DynLimBuf.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\IntToString.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\IntToString.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\ListFileUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\ListFileUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\LzFindPrepare.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyBuffer.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyBuffer2.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyCom.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyException.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyGuidDef.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyInitGuid.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyLinux.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyString.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyString.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyTypes.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyUnknown.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyVector.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyVector.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyWindows.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\NewHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\NewHandler.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Sha1Prepare.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Sha1Reg.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Sha256Prepare.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Sha256Reg.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StdInStream.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StdInStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StdOutStream.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StdOutStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StringConvert.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StringConvert.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StringToInt.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StringToInt.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\UTFConvert.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\UTFConvert.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Wildcard.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Wildcard.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Xxh64Reg.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\XzCrc64Init.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\XzCrc64Reg.cpp +# End Source File +# End Group +# Begin Group "Windows" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\Windows\Defs.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Device.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\DLL.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\DLL.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\ErrorMsg.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\ErrorMsg.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileDir.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileDir.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileFind.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileFind.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileIO.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileIO.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileLink.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileMapping.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileName.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileName.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileSystem.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileSystem.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Handle.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\MemoryLock.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\MemoryLock.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\NtCheck.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\PropVariant.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\PropVariant.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\PropVariantConv.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\PropVariantConv.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\PropVariantUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\PropVariantUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Registry.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Registry.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\SecurityUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Synchronization.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Synchronization.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\System.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\System.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\SystemInfo.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\SystemInfo.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Thread.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\TimeUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\TimeUtils.h +# End Source File +# End Group +# Begin Group "7zip Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Common\CreateCoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\CreateCoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\CWrappers.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\CWrappers.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FilePathAutoRename.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FilePathAutoRename.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FileStreams.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FileStreams.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FilterCoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FilterCoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\InBuffer.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\InBuffer.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\InOutTempBuffer.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\InOutTempBuffer.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\LimitedStreams.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\LimitedStreams.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\LockedStream.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\LockedStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\MemBlocks.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\MemBlocks.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\MethodId.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\MethodId.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\MethodProps.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\MethodProps.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\MultiOutStream.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\MultiOutStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\OffsetStream.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\OffsetStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\OutBuffer.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\OutBuffer.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\OutMemStream.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\OutMemStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\ProgressMt.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\ProgressMt.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\ProgressUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\ProgressUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\PropId.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\RegisterArc.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\RegisterCodec.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamBinder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamBinder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamObjects.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamObjects.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\UniqBlocks.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\UniqBlocks.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\VirtThread.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\VirtThread.h +# End Source File +# End Group +# Begin Group "Compress" + +# PROP Default_Filter "" +# Begin Group "BZip2" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Compress\BZip2Const.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BZip2Crc.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BZip2Crc.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BZip2Decoder.cpp + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BZip2Decoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BZip2Encoder.cpp + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BZip2Encoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BZip2Register.cpp +# End Source File +# End Group +# Begin Group "Copy" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Compress\CopyCoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\CopyCoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\CopyRegister.cpp +# End Source File +# End Group +# Begin Group "Deflate" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Compress\Deflate64Register.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\DeflateConst.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\DeflateDecoder.cpp + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\DeflateDecoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\DeflateEncoder.cpp + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\DeflateEncoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\DeflateRegister.cpp +# End Source File +# End Group +# Begin Group "Huffman" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Compress\HuffmanDecoder.h +# End Source File +# End Group +# Begin Group "Implode" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Compress\ImplodeDecoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\ImplodeDecoder.h +# End Source File +# End Group +# Begin Group "LZMA" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Compress\Lzma2Decoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Lzma2Decoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Lzma2Encoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Lzma2Encoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Lzma2Register.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\LzmaDecoder.cpp + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\LzmaDecoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\LzmaEncoder.cpp + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\LzmaEncoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\LzmaRegister.cpp +# End Source File +# End Group +# Begin Group "PPMd" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Compress\PpmdDecoder.cpp + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\PpmdDecoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\PpmdEncoder.cpp + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\PpmdEncoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\PpmdRegister.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\PpmdZip.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\PpmdZip.h +# End Source File +# End Group +# Begin Group "Shrink" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Compress\ShrinkDecoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\ShrinkDecoder.h +# End Source File +# End Group +# Begin Group "BWT" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Compress\Mtf8.h +# End Source File +# End Group +# Begin Group "LZX" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Compress\Lzx.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\LzxDecoder.cpp + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\LzxDecoder.h +# End Source File +# End Group +# Begin Group "Quantum" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Compress\QuantumDecoder.cpp + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\QuantumDecoder.h +# End Source File +# End Group +# Begin Source File + +SOURCE=..\..\Compress\Bcj2Coder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Bcj2Coder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Bcj2Register.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BcjCoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BcjCoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BcjRegister.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BitlDecoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BitlDecoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BitlEncoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BitmDecoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BitmEncoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BranchMisc.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BranchMisc.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BranchRegister.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\ByteSwap.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\ByteSwap.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\DeltaFilter.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\LzOutWindow.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\LzOutWindow.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\XzDecoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\XzDecoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\XzEncoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\XzEncoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\ZstdDecoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\ZstdDecoder.h +# End Source File +# End Group +# Begin Group "Archive" + +# PROP Default_Filter "" +# Begin Group "7z" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Archive\7z\7zCompressionMode.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zCompressionMode.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zDecode.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zDecode.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zEncode.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zEncode.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zExtract.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zFolderInStream.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zFolderInStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zHandler.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zHandlerOut.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zHeader.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zHeader.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zIn.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zIn.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zItem.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zOut.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zOut.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zProperties.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zProperties.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zRegister.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zSpecStream.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zSpecStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zUpdate.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zUpdate.h +# End Source File +# End Group +# Begin Group "tar" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Archive\Tar\TarHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Tar\TarHandler.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Tar\TarHandlerOut.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Tar\TarHeader.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Tar\TarHeader.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Tar\TarIn.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Tar\TarIn.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Tar\TarItem.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Tar\TarOut.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Tar\TarOut.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Tar\TarRegister.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Tar\TarUpdate.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Tar\TarUpdate.h +# End Source File +# End Group +# Begin Group "zip" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Archive\Zip\ZipAddCommon.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Zip\ZipAddCommon.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Zip\ZipCompressionMode.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Zip\ZipHandler.cpp +# ADD CPP /D "Z7_ZIP_LZFSE_DISABLE" +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Zip\ZipHandler.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Zip\ZipHandlerOut.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Zip\ZipHeader.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Zip\ZipIn.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Zip\ZipIn.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Zip\ZipItem.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Zip\ZipItem.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Zip\ZipOut.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Zip\ZipOut.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Zip\ZipRegister.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Zip\ZipUpdate.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Zip\ZipUpdate.h +# End Source File +# End Group +# Begin Group "Archive Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Archive\Common\CoderLoader.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\CoderMixer2.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\CoderMixer2.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\DummyOutStream.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\DummyOutStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\FindSignature.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\FindSignature.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\HandlerOut.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\HandlerOut.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\InStreamWithCRC.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\InStreamWithCRC.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\ItemNameUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\ItemNameUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\MultiStream.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\MultiStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\OutStreamWithCRC.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\OutStreamWithCRC.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\ParseProperties.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\ParseProperties.h +# End Source File +# End Group +# Begin Group "cab" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Archive\Cab\CabBlockInStream.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Cab\CabBlockInStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Cab\CabHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Cab\CabHandler.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Cab\CabHeader.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Cab\CabHeader.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Cab\CabIn.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Cab\CabIn.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Cab\CabItem.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Cab\CabRegister.cpp +# End Source File +# End Group +# Begin Source File + +SOURCE=..\..\Archive\Bz2Handler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\DeflateProps.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\DeflateProps.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\GzHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\IArchive.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\LzmaHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\SplitHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\XzHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\ZstdHandler.cpp +# End Source File +# End Group +# Begin Group "UI Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\UI\Common\ArchiveCommandLine.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\ArchiveCommandLine.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\ArchiveExtractCallback.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\ArchiveExtractCallback.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\ArchiveOpenCallback.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\ArchiveOpenCallback.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\Bench.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\Bench.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\DefaultName.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\DefaultName.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\DirItem.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\EnumDirItems.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\EnumDirItems.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\ExitCode.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\Extract.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\Extract.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\ExtractingFilePath.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\ExtractingFilePath.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\ExtractMode.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\HashCalc.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\HashCalc.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\IFileExtractCallback.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\LoadCodecs.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\LoadCodecs.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\OpenArchive.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\OpenArchive.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\Property.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\PropIDUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\PropIDUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\SetProperties.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\SetProperties.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\SortUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\SortUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\TempFiles.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\TempFiles.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\Update.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\Update.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\UpdateAction.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\UpdateAction.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\UpdateCallback.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\UpdateCallback.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\UpdatePair.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\UpdatePair.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\UpdateProduce.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\UpdateProduce.h +# End Source File +# End Group +# Begin Group "Crypto" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Crypto\7zAes.cpp + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\7zAes.h +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\7zAesRegister.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\HmacSha1.cpp + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\HmacSha1.h +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\MyAes.cpp + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\MyAes.h +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\MyAesReg.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\Pbkdf2HmacSha1.cpp + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\Pbkdf2HmacSha1.h +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\RandGen.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\RandGen.h +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\Sha1Cls.h +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\WzAes.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\WzAes.h +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\ZipCrypto.cpp + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O1 + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# ADD CPP /O1 + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\ZipCrypto.h +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\ZipStrong.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\ZipStrong.h +# End Source File +# End Group +# Begin Group "7-zip" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\ICoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\IDecl.h +# End Source File +# Begin Source File + +SOURCE=..\..\IMyUnknown.h +# End Source File +# Begin Source File + +SOURCE=..\..\IPassword.h +# End Source File +# Begin Source File + +SOURCE=..\..\IProgress.h +# End Source File +# Begin Source File + +SOURCE=..\..\IStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\MyVersion.h +# End Source File +# Begin Source File + +SOURCE=..\..\PropID.h +# End Source File +# End Group +# Begin Group "C" + +# PROP Default_Filter "" +# Begin Group "Xz" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\..\C\Xz.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Xz.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\XzCrc64.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\XzCrc64.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\XzCrc64Opt.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\XzDec.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\XzEnc.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\XzEnc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\XzIn.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# End Group +# Begin Source File + +SOURCE=..\..\..\..\C\7zCrc.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zCrc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zCrcOpt.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zStream.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zTypes.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zVersion.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zWindows.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Aes.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Aes.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\AesOpt.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Alloc.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Alloc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Bcj2.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Bcj2.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Bcj2Enc.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Bra.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Bra.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Bra86.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\BraIA64.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\BwtSort.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\BwtSort.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Compiler.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\CpuArch.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\CpuArch.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Delta.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Delta.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\DllSecur.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\DllSecur.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\HuffEnc.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\HuffEnc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\IStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzFind.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzFind.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzFindMt.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzFindMt.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzFindOpt.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzHash.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Lzma2Dec.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Lzma2Dec.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Lzma2DecMt.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Lzma2DecMt.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Lzma2Enc.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Lzma2Enc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzmaDec.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzmaDec.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzmaEnc.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzmaEnc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\MtCoder.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\MtCoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\MtDec.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\MtDec.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Ppmd.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Ppmd7.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Ppmd7.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Ppmd7Dec.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Ppmd7Enc.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Ppmd8.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Ppmd8.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Ppmd8Dec.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Ppmd8Enc.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Precomp.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\RotateDefs.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Sha1.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Sha1.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Sha1Opt.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Sha256.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Sha256.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Sha256Opt.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Sort.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Sort.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\SwapBytes.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\SwapBytes.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Threads.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Threads.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Xxh64.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Xxh64.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\ZstdDec.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\ZstdDec.h +# End Source File +# End Group +# End Target +# End Project diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone/Alone.dsw b/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone/Alone.dsw new file mode 100644 index 00000000..65eca43f --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone/Alone.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "Alone"=.\Alone.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone/StdAfx.cpp b/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone/StdAfx.cpp new file mode 100644 index 00000000..d0feea85 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone/StdAfx.cpp @@ -0,0 +1,3 @@ +// StdAfx.cpp + +#include "StdAfx.h" diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone/StdAfx.h b/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone/StdAfx.h new file mode 100644 index 00000000..035267cc --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone/StdAfx.h @@ -0,0 +1,11 @@ +// StdAfx.h + +#ifndef ZIP7_INC_STDAFX_H +#define ZIP7_INC_STDAFX_H + +#if defined(_MSC_VER) && _MSC_VER >= 1800 +#pragma warning(disable : 4464) // relative include path contains '..' +#endif +#include "../../../Common/Common.h" + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone/makefile b/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone/makefile new file mode 100644 index 00000000..9f81f9e3 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone/makefile @@ -0,0 +1,245 @@ +PROG = 7za.exe + +CFLAGS = $(CFLAGS) -DZ7_ZIP_LZFSE_DISABLE +# -DZ7_PROG_VARIANT_A +# CONSOLE_VARIANT_FLAGS=-DZ7_PROG_VARIANT_A +# ZIP_FLAGS=-DZ7_ZIP_LZFSE_DISABLE + +# USE_C_SORT=1 +# USE_C_AES = 1 +# USE_C_SHA = 1 +# USE_C_LZFINDOPT = 1 + +COMMON_OBJS = \ + $O\CommandLineParser.obj \ + $O\CRC.obj \ + $O\CrcReg.obj \ + $O\DynLimBuf.obj \ + $O\IntToString.obj \ + $O\ListFileUtils.obj \ + $O\LzFindPrepare.obj \ + $O\NewHandler.obj \ + $O\StdInStream.obj \ + $O\StdOutStream.obj \ + $O\MyString.obj \ + $O\StringConvert.obj \ + $O\StringToInt.obj \ + $O\UTFConvert.obj \ + $O\MyVector.obj \ + $O\Wildcard.obj \ + $O\Sha1Reg.obj \ + $O\Sha256Reg.obj \ + $O\Xxh64Reg.obj \ + $O\XzCrc64Init.obj \ + $O\XzCrc64Reg.obj \ + +WIN_OBJS = \ + $O\DLL.obj \ + $O\ErrorMsg.obj \ + $O\FileDir.obj \ + $O\FileFind.obj \ + $O\FileIO.obj \ + $O\FileLink.obj \ + $O\FileName.obj \ + $O\FileSystem.obj \ + $O\MemoryLock.obj \ + $O\PropVariant.obj \ + $O\PropVariantConv.obj \ + $O\PropVariantUtils.obj \ + $O\Registry.obj \ + $O\Synchronization.obj \ + $O\System.obj \ + $O\SystemInfo.obj \ + $O\TimeUtils.obj \ + +7ZIP_COMMON_OBJS = \ + $O\CreateCoder.obj \ + $O\CWrappers.obj \ + $O\FilePathAutoRename.obj \ + $O\FileStreams.obj \ + $O\FilterCoder.obj \ + $O\InBuffer.obj \ + $O\InOutTempBuffer.obj \ + $O\LimitedStreams.obj \ + $O\MemBlocks.obj \ + $O\MethodId.obj \ + $O\MethodProps.obj \ + $O\MultiOutStream.obj \ + $O\OffsetStream.obj \ + $O\OutBuffer.obj \ + $O\OutMemStream.obj \ + $O\ProgressMt.obj \ + $O\ProgressUtils.obj \ + $O\PropId.obj \ + $O\StreamBinder.obj \ + $O\StreamObjects.obj \ + $O\StreamUtils.obj \ + $O\UniqBlocks.obj \ + $O\VirtThread.obj \ + +AR_OBJS = \ + $O\Bz2Handler.obj \ + $O\DeflateProps.obj \ + $O\GzHandler.obj \ + $O\LzmaHandler.obj \ + $O\SplitHandler.obj \ + $O\XzHandler.obj \ + $O\ZstdHandler.obj \ + +AR_COMMON_OBJS = \ + $O\CoderMixer2.obj \ + $O\DummyOutStream.obj \ + $O\FindSignature.obj \ + $O\HandlerOut.obj \ + $O\InStreamWithCRC.obj \ + $O\ItemNameUtils.obj \ + $O\MultiStream.obj \ + $O\OutStreamWithCRC.obj \ + $O\ParseProperties.obj \ + + +7Z_OBJS = \ + $O\7zCompressionMode.obj \ + $O\7zDecode.obj \ + $O\7zEncode.obj \ + $O\7zExtract.obj \ + $O\7zFolderInStream.obj \ + $O\7zHandler.obj \ + $O\7zHandlerOut.obj \ + $O\7zHeader.obj \ + $O\7zIn.obj \ + $O\7zOut.obj \ + $O\7zProperties.obj \ + $O\7zSpecStream.obj \ + $O\7zUpdate.obj \ + $O\7zRegister.obj \ + +CAB_OBJS = \ + $O\CabBlockInStream.obj \ + $O\CabHandler.obj \ + $O\CabHeader.obj \ + $O\CabIn.obj \ + $O\CabRegister.obj \ + +TAR_OBJS = \ + $O\TarHandler.obj \ + $O\TarHandlerOut.obj \ + $O\TarHeader.obj \ + $O\TarIn.obj \ + $O\TarOut.obj \ + $O\TarUpdate.obj \ + $O\TarRegister.obj \ + +ZIP_OBJS = \ + $O\ZipAddCommon.obj \ + $O\ZipHandler.obj \ + $O\ZipHandlerOut.obj \ + $O\ZipIn.obj \ + $O\ZipItem.obj \ + $O\ZipOut.obj \ + $O\ZipUpdate.obj \ + $O\ZipRegister.obj \ + + +COMPRESS_OBJS = \ + $O\Bcj2Coder.obj \ + $O\Bcj2Register.obj \ + $O\BcjCoder.obj \ + $O\BcjRegister.obj \ + $O\BitlDecoder.obj \ + $O\BranchMisc.obj \ + $O\BranchRegister.obj \ + $O\ByteSwap.obj \ + $O\BZip2Crc.obj \ + $O\BZip2Decoder.obj \ + $O\BZip2Encoder.obj \ + $O\BZip2Register.obj \ + $O\CopyCoder.obj \ + $O\CopyRegister.obj \ + $O\Deflate64Register.obj \ + $O\DeflateDecoder.obj \ + $O\DeflateEncoder.obj \ + $O\DeflateRegister.obj \ + $O\DeltaFilter.obj \ + $O\ImplodeDecoder.obj \ + $O\Lzma2Decoder.obj \ + $O\Lzma2Encoder.obj \ + $O\Lzma2Register.obj \ + $O\LzmaDecoder.obj \ + $O\LzmaEncoder.obj \ + $O\LzmaRegister.obj \ + $O\LzOutWindow.obj \ + $O\LzxDecoder.obj \ + $O\PpmdDecoder.obj \ + $O\PpmdEncoder.obj \ + $O\PpmdRegister.obj \ + $O\PpmdZip.obj \ + $O\QuantumDecoder.obj \ + $O\ShrinkDecoder.obj \ + $O\XzDecoder.obj \ + $O\XzEncoder.obj \ + $O\ZstdDecoder.obj \ + +# $O\LzfseDecoder.obj \ +# $O\ZstdRegister.obj \ + +CRYPTO_OBJS = \ + $O\7zAes.obj \ + $O\7zAesRegister.obj \ + $O\HmacSha1.obj \ + $O\MyAes.obj \ + $O\MyAesReg.obj \ + $O\Pbkdf2HmacSha1.obj \ + $O\RandGen.obj \ + $O\WzAes.obj \ + $O\ZipCrypto.obj \ + $O\ZipStrong.obj \ + +C_OBJS = \ + $O\7zStream.obj \ + $O\Alloc.obj \ + $O\Bcj2.obj \ + $O\Bcj2Enc.obj \ + $O\Bra.obj \ + $O\Bra86.obj \ + $O\BraIA64.obj \ + $O\BwtSort.obj \ + $O\CpuArch.obj \ + $O\Delta.obj \ + $O\HuffEnc.obj \ + $O\LzFind.obj \ + $O\LzFindMt.obj \ + $O\Lzma2Dec.obj \ + $O\Lzma2DecMt.obj \ + $O\Lzma2Enc.obj \ + $O\LzmaDec.obj \ + $O\LzmaEnc.obj \ + $O\MtCoder.obj \ + $O\MtDec.obj \ + $O\Ppmd7.obj \ + $O\Ppmd7Dec.obj \ + $O\Ppmd7Enc.obj \ + $O\Ppmd8.obj \ + $O\Ppmd8Dec.obj \ + $O\Ppmd8Enc.obj \ + $O\SwapBytes.obj \ + $O\Threads.obj \ + $O\Xxh64.obj \ + $O\Xz.obj \ + $O\XzDec.obj \ + $O\XzEnc.obj \ + $O\XzIn.obj \ + $O\ZstdDec.obj \ + +!include "../../UI/Console/Console.mak" + +!include "../../Aes.mak" +!include "../../Crc.mak" +!include "../../Crc64.mak" +!include "../../LzFindOpt.mak" +!include "../../LzmaDec.mak" +!include "../../Sha1.mak" +!include "../../Sha256.mak" +!include "../../Sort.mak" + +!include "../../7zip.mak" diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone/makefile.gcc b/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone/makefile.gcc new file mode 100644 index 00000000..822d2526 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone/makefile.gcc @@ -0,0 +1,354 @@ +PROG = 7za + +CONSOLE_VARIANT_FLAGS=-DZ7_PROG_VARIANT_A +ZIP_FLAGS=-DZ7_ZIP_LZFSE_DISABLE + + +# IS_X64 = 1 +# USE_ASM = 1 +# ST_MODE = 1 + +ifdef SystemDrive +IS_MINGW = 1 +else +ifdef SYSTEMDRIVE +# ifdef OS +IS_MINGW = 1 +endif +endif + +include ../../LzmaDec_gcc.mak + + +LOCAL_FLAGS_ST = +MT_OBJS = + + +ifdef ST_MODE + +LOCAL_FLAGS_ST = -DZ7_ST + +ifdef IS_MINGW +MT_OBJS = \ + $O/Threads.o \ + +endif + +else + +MT_OBJS = \ + $O/LzFindMt.o \ + $O/LzFindOpt.o \ + $O/Threads.o \ + $O/MemBlocks.o \ + $O/OutMemStream.o \ + $O/ProgressMt.o \ + $O/StreamBinder.o \ + $O/Synchronization.o \ + $O/VirtThread.o \ + +endif + + + +LOCAL_FLAGS_SYS = + +ifdef IS_MINGW + +LOCAL_FLAGS_SYS = \ + -DZ7_DEVICE_FILE \ + +SYS_OBJS = \ + $O/FileSystem.o \ + $O/Registry.o \ + $O/MemoryLock.o \ + $O/DLL.o \ + $O/DllSecur.o \ + $O/resource.o \ + +else + +SYS_OBJS = \ + $O/MyWindows.o \ + +endif + +LOCAL_FLAGS = \ + $(LOCAL_FLAGS_ST) \ + $(LOCAL_FLAGS_SYS) \ + + +CONSOLE_OBJS = \ + $O/BenchCon.o \ + $O/ConsoleClose.o \ + $O/ExtractCallbackConsole.o \ + $O/HashCon.o \ + $O/List.o \ + $O/Main.o \ + $O/MainAr.o \ + $O/OpenCallbackConsole.o \ + $O/PercentPrinter.o \ + $O/UpdateCallbackConsole.o \ + $O/UserInputUtils.o \ + +UI_COMMON_OBJS = \ + $O/ArchiveCommandLine.o \ + $O/ArchiveExtractCallback.o \ + $O/ArchiveOpenCallback.o \ + $O/Bench.o \ + $O/DefaultName.o \ + $O/EnumDirItems.o \ + $O/Extract.o \ + $O/ExtractingFilePath.o \ + $O/HashCalc.o \ + $O/LoadCodecs.o \ + $O/OpenArchive.o \ + $O/PropIDUtils.o \ + $O/SetProperties.o \ + $O/SortUtils.o \ + $O/TempFiles.o \ + $O/Update.o \ + $O/UpdateAction.o \ + $O/UpdateCallback.o \ + $O/UpdatePair.o \ + $O/UpdateProduce.o \ + +COMMON_OBJS = \ + $O/CommandLineParser.o \ + $O/CRC.o \ + $O/CrcReg.o \ + $O/DynLimBuf.o \ + $O/IntToString.o \ + $O/ListFileUtils.o \ + $O/LzFindPrepare.o \ + $O/MyString.o \ + $O/MyVector.o \ + $O/NewHandler.o \ + $O/Sha1Prepare.o \ + $O/Sha1Reg.o \ + $O/Sha256Prepare.o \ + $O/Sha256Reg.o \ + $O/StdInStream.o \ + $O/StdOutStream.o \ + $O/StringConvert.o \ + $O/StringToInt.o \ + $O/UTFConvert.o \ + $O/Wildcard.o \ + $O/Xxh64Reg.o \ + $O/XzCrc64Init.o \ + $O/XzCrc64Reg.o \ + +WIN_OBJS = \ + $O/ErrorMsg.o \ + $O/FileDir.o \ + $O/FileFind.o \ + $O/FileIO.o \ + $O/FileLink.o \ + $O/FileName.o \ + $O/PropVariant.o \ + $O/PropVariantConv.o \ + $O/PropVariantUtils.o \ + $O/System.o \ + $O/SystemInfo.o \ + $O/TimeUtils.o \ + +7ZIP_COMMON_OBJS = \ + $O/CreateCoder.o \ + $O/CWrappers.o \ + $O/FilePathAutoRename.o \ + $O/FileStreams.o \ + $O/InBuffer.o \ + $O/InOutTempBuffer.o \ + $O/FilterCoder.o \ + $O/LimitedStreams.o \ + $O/MethodId.o \ + $O/MethodProps.o \ + $O/MultiOutStream.o \ + $O/OffsetStream.o \ + $O/OutBuffer.o \ + $O/ProgressUtils.o \ + $O/PropId.o \ + $O/StreamObjects.o \ + $O/StreamUtils.o \ + $O/UniqBlocks.o \ + +AR_OBJS = \ + $O/Bz2Handler.o \ + $O/GzHandler.o \ + $O/LzmaHandler.o \ + $O/SplitHandler.o \ + $O/XzHandler.o \ + $O/ZstdHandler.o \ + +AR_COMMON_OBJS = \ + $O/CoderMixer2.o \ + $O/DummyOutStream.o \ + $O/HandlerOut.o \ + $O/InStreamWithCRC.o \ + $O/ItemNameUtils.o \ + $O/MultiStream.o \ + $O/OutStreamWithCRC.o \ + $O/ParseProperties.o \ + +7Z_OBJS = \ + $O/7zCompressionMode.o \ + $O/7zDecode.o \ + $O/7zEncode.o \ + $O/7zExtract.o \ + $O/7zFolderInStream.o \ + $O/7zHandler.o \ + $O/7zHandlerOut.o \ + $O/7zHeader.o \ + $O/7zIn.o \ + $O/7zOut.o \ + $O/7zProperties.o \ + $O/7zRegister.o \ + $O/7zSpecStream.o \ + $O/7zUpdate.o \ + +CAB_OBJS = \ + $O/CabBlockInStream.o \ + $O/CabHandler.o \ + $O/CabHeader.o \ + $O/CabIn.o \ + $O/CabRegister.o \ + +TAR_OBJS = \ + $O/TarHandler.o \ + $O/TarHandlerOut.o \ + $O/TarHeader.o \ + $O/TarIn.o \ + $O/TarOut.o \ + $O/TarUpdate.o \ + $O/TarRegister.o \ + +ZIP_OBJS = \ + $O/ZipAddCommon.o \ + $O/ZipHandler.o \ + $O/ZipHandlerOut.o \ + $O/ZipIn.o \ + $O/ZipItem.o \ + $O/ZipOut.o \ + $O/ZipUpdate.o \ + $O/ZipRegister.o \ + +COMPRESS_OBJS = \ + $O/Bcj2Coder.o \ + $O/Bcj2Register.o \ + $O/BcjCoder.o \ + $O/BcjRegister.o \ + $O/BitlDecoder.o \ + $O/BranchMisc.o \ + $O/BranchRegister.o \ + $O/ByteSwap.o \ + $O/BZip2Crc.o \ + $O/BZip2Decoder.o \ + $O/BZip2Encoder.o \ + $O/BZip2Register.o \ + $O/CopyCoder.o \ + $O/CopyRegister.o \ + $O/Deflate64Register.o \ + $O/DeflateDecoder.o \ + $O/DeflateEncoder.o \ + $O/DeflateRegister.o \ + $O/DeltaFilter.o \ + $O/ImplodeDecoder.o \ + $O/Lzma2Decoder.o \ + $O/Lzma2Encoder.o \ + $O/Lzma2Register.o \ + $O/LzmaDecoder.o \ + $O/LzmaEncoder.o \ + $O/LzmaRegister.o \ + $O/LzOutWindow.o \ + $O/LzxDecoder.o \ + $O/PpmdDecoder.o \ + $O/PpmdEncoder.o \ + $O/PpmdRegister.o \ + $O/PpmdZip.o \ + $O/QuantumDecoder.o \ + $O/ShrinkDecoder.o \ + $O/XzDecoder.o \ + $O/XzEncoder.o \ + $O/ZstdDecoder.o \ + +# $O/LzfseDecoder.o \ +# $O/ZstdRegister.o + +CRYPTO_OBJS = \ + $O/7zAes.o \ + $O/7zAesRegister.o \ + $O/HmacSha1.o \ + $O/MyAes.o \ + $O/MyAesReg.o \ + $O/Pbkdf2HmacSha1.o \ + $O/RandGen.o \ + $O/WzAes.o \ + $O/ZipCrypto.o \ + $O/ZipStrong.o \ + +C_OBJS = \ + $O/7zCrc.o \ + $O/7zCrcOpt.o \ + $O/7zStream.o \ + $O/Aes.o \ + $O/AesOpt.o \ + $O/Alloc.o \ + $O/Bcj2.o \ + $O/Bcj2Enc.o \ + $O/Bra.o \ + $O/Bra86.o \ + $O/BraIA64.o \ + $O/BwtSort.o \ + $O/CpuArch.o \ + $O/Delta.o \ + $O/HuffEnc.o \ + $O/LzFind.o \ + $O/Lzma2Dec.o \ + $O/Lzma2DecMt.o \ + $O/Lzma2Enc.o \ + $O/LzmaDec.o \ + $O/LzmaEnc.o \ + $O/MtCoder.o \ + $O/MtDec.o \ + $O/Ppmd7.o \ + $O/Ppmd7Dec.o \ + $O/Ppmd7Enc.o \ + $O/Ppmd8.o \ + $O/Ppmd8Dec.o \ + $O/Ppmd8Enc.o \ + $O/Sha1.o \ + $O/Sha1Opt.o \ + $O/Sha256.o \ + $O/Sha256Opt.o \ + $O/Sort.o \ + $O/SwapBytes.o \ + $O/Xxh64.o \ + $O/Xz.o \ + $O/XzDec.o \ + $O/XzEnc.o \ + $O/XzIn.o \ + $O/XzCrc64.o \ + $O/XzCrc64Opt.o \ + $O/ZstdDec.o \ + + +OBJS = \ + $(LZMA_DEC_OPT_OBJS) \ + $(C_OBJS) \ + $(MT_OBJS) \ + $(SYS_OBJS) \ + $(COMMON_OBJS) \ + $(WIN_OBJS) \ + $(COMPRESS_OBJS) \ + $(CRYPTO_OBJS) \ + $(7ZIP_COMMON_OBJS) \ + $(AR_OBJS) \ + $(AR_COMMON_OBJS) \ + $(7Z_OBJS) \ + $(CAB_OBJS) \ + $(TAR_OBJS) \ + $(ZIP_OBJS) \ + $(UI_COMMON_OBJS) \ + $(CONSOLE_OBJS) \ + +include ../../7zip_gcc.mak diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone/resource.rc b/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone/resource.rc new file mode 100644 index 00000000..c85acaa9 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone/resource.rc @@ -0,0 +1,7 @@ +#include "../../MyVersionInfo.rc" + +MY_VERSION_INFO_APP("7-Zip Standalone Console", "7za") + +#ifndef UNDER_CE +1 24 MOVEABLE PURE "../../UI/Console/Console.manifest" +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone2/StdAfx.cpp b/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone2/StdAfx.cpp new file mode 100644 index 00000000..d0feea85 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone2/StdAfx.cpp @@ -0,0 +1,3 @@ +// StdAfx.cpp + +#include "StdAfx.h" diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone2/StdAfx.h b/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone2/StdAfx.h new file mode 100644 index 00000000..035267cc --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone2/StdAfx.h @@ -0,0 +1,11 @@ +// StdAfx.h + +#ifndef ZIP7_INC_STDAFX_H +#define ZIP7_INC_STDAFX_H + +#if defined(_MSC_VER) && _MSC_VER >= 1800 +#pragma warning(disable : 4464) // relative include path contains '..' +#endif +#include "../../../Common/Common.h" + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone2/makefile b/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone2/makefile new file mode 100644 index 00000000..af89d664 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone2/makefile @@ -0,0 +1,31 @@ +PROG = 7zz.exe +# USE_C_AES = 1 +# USE_C_SHA = 1 + +CFLAGS = $(CFLAGS) -DZ7_PROG_VARIANT_Z +# CONSOLE_VARIANT_FLAGS=-DZ7_PROG_VARIANT_Z + +!include "../Format7zF/Arc.mak" +!include "../../UI/Console/Console.mak" + +COMMON_OBJS = $(COMMON_OBJS) \ + $O\CommandLineParser.obj \ + $O\ListFileUtils.obj \ + $O\StdInStream.obj \ + $O\StdOutStream.obj \ + +WIN_OBJS = $(WIN_OBJS) \ + $O\DLL.obj \ + $O\ErrorMsg.obj \ + $O\FileLink.obj \ + $O\FileSystem.obj \ + $O\MemoryLock.obj \ + $O\Registry.obj \ + $O\SystemInfo.obj \ + +7ZIP_COMMON_OBJS = $(7ZIP_COMMON_OBJS) \ + $O\FilePathAutoRename.obj \ + $O\FileStreams.obj \ + $O\MultiOutStream.obj \ + +!include "../../7zip.mak" diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone2/makefile.gcc b/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone2/makefile.gcc new file mode 100644 index 00000000..15e3a35c --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone2/makefile.gcc @@ -0,0 +1,108 @@ +PROG = 7zz + +CONSOLE_VARIANT_FLAGS=-DZ7_PROG_VARIANT_Z + +# IS_X64 = 1 +# USE_ASM = 1 +# ST_MODE = 1 + +include ../Format7zF/Arc_gcc.mak + +ifdef SystemDrive +IS_MINGW = 1 +else +ifdef SYSTEMDRIVE +# ifdef OS +IS_MINGW = 1 +endif +endif + +LOCAL_FLAGS_SYS = + +ifdef IS_MINGW + +LOCAL_FLAGS_SYS = \ + -DZ7_DEVICE_FILE \ + +SYS_OBJS = \ + $O/FileSystem.o \ + $O/Registry.o \ + $O/MemoryLock.o \ + $O/DLL.o \ + $O/DllSecur.o \ + $O/resource.o \ + +else + +SYS_OBJS = \ + $O/MyWindows.o \ + +endif + + +LOCAL_FLAGS = \ + $(LOCAL_FLAGS_SYS) \ + $(LOCAL_FLAGS_ST) \ + + +UI_COMMON_OBJS = \ + $O/ArchiveCommandLine.o \ + $O/ArchiveExtractCallback.o \ + $O/ArchiveOpenCallback.o \ + $O/Bench.o \ + $O/DefaultName.o \ + $O/EnumDirItems.o \ + $O/Extract.o \ + $O/ExtractingFilePath.o \ + $O/HashCalc.o \ + $O/LoadCodecs.o \ + $O/OpenArchive.o \ + $O/PropIDUtils.o \ + $O/SetProperties.o \ + $O/SortUtils.o \ + $O/TempFiles.o \ + $O/Update.o \ + $O/UpdateAction.o \ + $O/UpdateCallback.o \ + $O/UpdatePair.o \ + $O/UpdateProduce.o \ + +CONSOLE_OBJS = \ + $O/BenchCon.o \ + $O/ConsoleClose.o \ + $O/ExtractCallbackConsole.o \ + $O/HashCon.o \ + $O/List.o \ + $O/Main.o \ + $O/MainAr.o \ + $O/OpenCallbackConsole.o \ + $O/PercentPrinter.o \ + $O/UpdateCallbackConsole.o \ + $O/UserInputUtils.o \ + +COMMON_OBJS_2 = \ + $O/CommandLineParser.o \ + $O/ListFileUtils.o \ + $O/StdInStream.o \ + $O/StdOutStream.o \ + +WIN_OBJS_2 = \ + $O/ErrorMsg.o \ + $O/FileLink.o \ + $O/SystemInfo.o \ + +7ZIP_COMMON_OBJS_2 = \ + $O/FilePathAutoRename.o \ + $O/FileStreams.o \ + $O/MultiOutStream.o \ + +OBJS = \ + $(ARC_OBJS) \ + $(SYS_OBJS) \ + $(COMMON_OBJS_2) \ + $(WIN_OBJS_2) \ + $(7ZIP_COMMON_OBJS_2) \ + $(UI_COMMON_OBJS) \ + $(CONSOLE_OBJS) \ + +include ../../7zip_gcc.mak diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone2/resource.rc b/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone2/resource.rc new file mode 100644 index 00000000..af24c175 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone2/resource.rc @@ -0,0 +1,7 @@ +#include "../../MyVersionInfo.rc" + +MY_VERSION_INFO_APP("7-Zip Standalone 2 Console", "7zz") + +#ifndef UNDER_CE +1 24 MOVEABLE PURE "../../UI/Console/Console.manifest" +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone7z/Alone.dsp b/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone7z/Alone.dsp new file mode 100644 index 00000000..c5c149fd --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone7z/Alone.dsp @@ -0,0 +1,2090 @@ +# Microsoft Developer Studio Project File - Name="Alone" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=Alone - Win32 DebugU +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "Alone.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "Alone.mak" CFG="Alone - Win32 DebugU" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "Alone - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "Alone - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE "Alone - Win32 ReleaseU" (based on "Win32 (x86) Console Application") +!MESSAGE "Alone - Win32 DebugU" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "Alone - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /Gr /MT /W4 /WX /GX /O1 /I "..\..\..\\" /D "NDEBUG" /D "_MBCS" /D "WIN32" /D "_CONSOLE" /D "Z7_DEVICE_FILE" /FAcs /Yu"StdAfx.h" /FD /c +# ADD BASE RSC /l 0x419 /d "NDEBUG" +# ADD RSC /l 0x419 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 /out:"c:\UTIL\7zr.exe" /opt:NOWIN98 +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /Gr /MDd /W4 /WX /Gm /GX /ZI /Od /I "..\..\..\\" /D "_DEBUG" /D "_MBCS" /D "WIN32" /D "_CONSOLE" /D "Z7_DEVICE_FILE" /Yu"StdAfx.h" /FD /GZ /c +# ADD BASE RSC /l 0x419 /d "_DEBUG" +# ADD RSC /l 0x419 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /out:"c:\UTIL\7zr.exe" /pdbtype:sept + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "ReleaseU" +# PROP BASE Intermediate_Dir "ReleaseU" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "ReleaseU" +# PROP Intermediate_Dir "ReleaseU" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MD /W3 /GX /O2 /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /Yu"StdAfx.h" /FD /c +# ADD CPP /nologo /Gr /MD /W4 /GX /O1 /I "..\..\..\\" /D "NDEBUG" /D "UNICODE" /D "_UNICODE" /D "WIN32" /D "_CONSOLE" /D "Z7_DEVICE_FILE" /Yu"StdAfx.h" /FD /c +# ADD BASE RSC /l 0x419 /d "NDEBUG" +# ADD RSC /l 0x419 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 /out:"c:\UTIL\7za.exe" /opt:NOWIN98 +# SUBTRACT BASE LINK32 /pdb:none +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 /out:"c:\UTIL\7zr.exe" /opt:NOWIN98 +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "DebugU" +# PROP BASE Intermediate_Dir "DebugU" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "DebugU" +# PROP Intermediate_Dir "DebugU" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "_DEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /Yu"StdAfx.h" /FD /GZ /c +# ADD CPP /nologo /Gr /MDd /W4 /Gm /GX /ZI /Od /I "..\..\..\\" /D "_DEBUG" /D "_UNICODE" /D "UNICODE" /D "WIN32" /D "_CONSOLE" /D "Z7_DEVICE_FILE" /Yu"StdAfx.h" /FD /GZ /c +# ADD BASE RSC /l 0x419 /d "_DEBUG" +# ADD RSC /l 0x419 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /out:"c:\UTIL\7za.exe" /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /out:"c:\UTIL\7zr.exe" /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "Alone - Win32 Release" +# Name "Alone - Win32 Debug" +# Name "Alone - Win32 ReleaseU" +# Name "Alone - Win32 DebugU" +# Begin Group "Console" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\UI\Console\BenchCon.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\BenchCon.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\CompressionMode.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\ConsoleClose.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\ConsoleClose.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\ExtractCallbackConsole.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\ExtractCallbackConsole.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\HashCon.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\HashCon.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\List.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\List.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\Main.cpp +# ADD CPP /D "PROG_VARIANT_R" +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\MainAr.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\OpenCallbackConsole.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\OpenCallbackConsole.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\PercentPrinter.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\PercentPrinter.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\UpdateCallbackConsole.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\UpdateCallbackConsole.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\UserInputUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\UserInputUtils.h +# End Source File +# End Group +# Begin Group "Spec" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=.\resource.rc +# End Source File +# Begin Source File + +SOURCE=.\StdAfx.cpp +# ADD CPP /Yc"StdAfx.h" +# End Source File +# Begin Source File + +SOURCE=.\StdAfx.h +# End Source File +# End Group +# Begin Group "Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\Common\AutoPtr.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Buffer.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\CommandLineParser.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\CommandLineParser.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Common.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Common0.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\ComTry.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\CRC.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\CrcReg.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Defs.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\DynamicBuffer.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\DynLimBuf.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\DynLimBuf.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\IntToString.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\IntToString.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\ListFileUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\ListFileUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\LzFindPrepare.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyBuffer.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyBuffer2.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyCom.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyException.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyGuidDef.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyInitGuid.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyLinux.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyString.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyString.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyTypes.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyUnknown.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyVector.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyVector.h +# End Source File +# Begin Source File + +SOURCE=..\..\MyVersion.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyWindows.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyWindows.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\NewHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\NewHandler.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Sha256Prepare.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Sha256Reg.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StdInStream.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StdInStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StdOutStream.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StdOutStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StringConvert.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StringConvert.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StringToInt.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StringToInt.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Types.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\UTFConvert.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\UTFConvert.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Wildcard.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Wildcard.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\XzCrc64Init.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\XzCrc64Reg.cpp +# End Source File +# End Group +# Begin Group "Windows" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\Windows\Defs.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Device.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\DLL.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\DLL.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\ErrorMsg.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\ErrorMsg.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileDir.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileDir.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileFind.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileFind.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileIO.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileIO.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileLink.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileMapping.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileName.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileName.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileSystem.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileSystem.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Handle.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\MemoryLock.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\MemoryLock.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\NtCheck.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\PropVariant.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\PropVariant.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\PropVariantConv.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\PropVariantConv.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Registry.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Registry.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\SecurityUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Synchronization.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\System.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\System.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\SystemInfo.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\SystemInfo.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Thread.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\TimeUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\TimeUtils.h +# End Source File +# End Group +# Begin Group "7zip Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Common\CreateCoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\CreateCoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\CWrappers.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\CWrappers.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FilePathAutoRename.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FilePathAutoRename.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FileStreams.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FileStreams.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FilterCoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FilterCoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\InBuffer.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\InBuffer.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\InOutTempBuffer.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\InOutTempBuffer.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\LimitedStreams.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\LimitedStreams.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\LockedStream.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\LockedStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\MethodId.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\MethodId.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\MethodProps.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\MethodProps.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\MultiOutStream.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\MultiOutStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\OffsetStream.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\OffsetStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\OutBuffer.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\OutBuffer.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\ProgressUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\ProgressUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\PropId.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\RegisterArc.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\RegisterCodec.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamBinder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamBinder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamObjects.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamObjects.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\UniqBlocks.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\UniqBlocks.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\VirtThread.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\VirtThread.h +# End Source File +# End Group +# Begin Group "Compress" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Compress\Bcj2Coder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Bcj2Coder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Bcj2Register.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BcjCoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BcjCoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BcjRegister.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BranchMisc.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BranchMisc.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BranchRegister.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\ByteSwap.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\CopyCoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\CopyCoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\CopyRegister.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\DeltaFilter.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Lzma2Decoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Lzma2Decoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Lzma2Encoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Lzma2Encoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Lzma2Register.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\LzmaDecoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\LzmaDecoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\LzmaEncoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\LzmaEncoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\LzmaRegister.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\XzDecoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\XzDecoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\XzEncoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\XzEncoder.h +# End Source File +# End Group +# Begin Group "Archive" + +# PROP Default_Filter "" +# Begin Group "7z" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Archive\7z\7zCompressionMode.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zCompressionMode.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zDecode.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zDecode.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zEncode.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zEncode.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zExtract.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zFolderInStream.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zFolderInStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zHandler.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zHandlerOut.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zHeader.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zHeader.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zIn.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zIn.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zItem.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zOut.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zOut.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zProperties.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zProperties.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zRegister.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zSpecStream.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zSpecStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zUpdate.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zUpdate.h +# End Source File +# End Group +# Begin Group "Archive Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Archive\Common\CoderMixer2.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\CoderMixer2.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\DummyOutStream.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\DummyOutStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\HandlerOut.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\HandlerOut.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\InStreamWithCRC.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\InStreamWithCRC.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\ItemNameUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\ItemNameUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\MultiStream.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\MultiStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\OutStreamWithCRC.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\OutStreamWithCRC.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\ParseProperties.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\ParseProperties.h +# End Source File +# End Group +# Begin Source File + +SOURCE=..\..\Archive\IArchive.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\LzmaHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\SplitHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\XzHandler.cpp +# End Source File +# End Group +# Begin Group "UI Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\UI\Common\ArchiveCommandLine.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\ArchiveCommandLine.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\ArchiveExtractCallback.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\ArchiveExtractCallback.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\ArchiveOpenCallback.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\ArchiveOpenCallback.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\Bench.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\Bench.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\DefaultName.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\DefaultName.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\DirItem.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\EnumDirItems.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\EnumDirItems.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\ExitCode.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\Extract.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\Extract.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\ExtractingFilePath.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\ExtractingFilePath.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\HashCalc.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\HashCalc.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\LoadCodecs.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\LoadCodecs.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\OpenArchive.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\OpenArchive.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\Property.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\PropIDUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\PropIDUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\SetProperties.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\SetProperties.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\SortUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\SortUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\TempFiles.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\TempFiles.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\Update.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\Update.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\UpdateAction.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\UpdateAction.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\UpdateCallback.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\UpdateCallback.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\UpdatePair.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\UpdatePair.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\UpdateProduce.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\UpdateProduce.h +# End Source File +# End Group +# Begin Group "7-zip" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\ICoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\IDecl.h +# End Source File +# Begin Source File + +SOURCE=..\..\IPassword.h +# End Source File +# Begin Source File + +SOURCE=..\..\IProgress.h +# End Source File +# Begin Source File + +SOURCE=..\..\IStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\PropID.h +# End Source File +# End Group +# Begin Group "C" + +# PROP Default_Filter "" +# Begin Group "Xz" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\..\C\Xz.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Xz.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\XzCrc64.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\XzCrc64.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\XzCrc64Opt.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\XzDec.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\XzEnc.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\XzEnc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\XzIn.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# End Group +# Begin Source File + +SOURCE=..\..\..\..\C\7zCrc.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zCrc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zCrcOpt.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zStream.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zTypes.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zVersion.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zWindows.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Aes.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Aes.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\AesOpt.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Alloc.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Alloc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Bcj2.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Bcj2.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Bcj2Enc.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Bra.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Bra.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Bra86.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\BraIA64.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Compiler.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\CpuArch.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\CpuArch.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Delta.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Delta.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\DllSecur.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\DllSecur.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\IStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzFind.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzFind.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzFindMt.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzFindMt.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzFindOpt.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Compress\Lz\LzHash.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzHash.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Lzma2Dec.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Lzma2Dec.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Lzma2DecMt.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Lzma2DecMt.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Lzma2Enc.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Lzma2Enc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzmaDec.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzmaDec.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzmaEnc.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzmaEnc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\MtCoder.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\MtCoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\MtDec.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\MtDec.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Precomp.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\RotateDefs.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Sha256.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Sha256.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Sha256Opt.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\SwapBytes.c + +!IF "$(CFG)" == "Alone - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Alone - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\SwapBytes.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Threads.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Threads.h +# End Source File +# End Group +# Begin Group "Crypto" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Crypto\7zAes.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\7zAes.h +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\7zAesRegister.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\MyAes.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\MyAes.h +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\MyAesReg.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\RandGen.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\RandGen.h +# End Source File +# End Group +# End Target +# End Project diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone7z/Alone.dsw b/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone7z/Alone.dsw new file mode 100644 index 00000000..65eca43f --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone7z/Alone.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "Alone"=.\Alone.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone7z/StdAfx.cpp b/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone7z/StdAfx.cpp new file mode 100644 index 00000000..d0feea85 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone7z/StdAfx.cpp @@ -0,0 +1,3 @@ +// StdAfx.cpp + +#include "StdAfx.h" diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone7z/StdAfx.h b/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone7z/StdAfx.h new file mode 100644 index 00000000..035267cc --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone7z/StdAfx.h @@ -0,0 +1,11 @@ +// StdAfx.h + +#ifndef ZIP7_INC_STDAFX_H +#define ZIP7_INC_STDAFX_H + +#if defined(_MSC_VER) && _MSC_VER >= 1800 +#pragma warning(disable : 4464) // relative include path contains '..' +#endif +#include "../../../Common/Common.h" + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone7z/makefile b/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone7z/makefile new file mode 100644 index 00000000..f0a813ac --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone7z/makefile @@ -0,0 +1,168 @@ +PROG = 7zr.exe + +# USE_C_AES = 1 +# USE_C_SHA = 1 +# USE_C_CRC64 = 1 +# USE_C_CRC = 1 +# NO_ASM_GNU=1 +# NO_ASM=1 + +CFLAGS = $(CFLAGS) -DZ7_PROG_VARIANT_R +# CONSOLE_VARIANT_FLAGS=-DZ7_PROG_VARIANT_R + +COMMON_OBJS = \ + $O\CommandLineParser.obj \ + $O\CRC.obj \ + $O\CrcReg.obj \ + $O\DynLimBuf.obj \ + $O\IntToString.obj \ + $O\ListFileUtils.obj \ + $O\LzFindPrepare.obj \ + $O\NewHandler.obj \ + $O\StdInStream.obj \ + $O\StdOutStream.obj \ + $O\MyString.obj \ + $O\Sha256Reg.obj \ + $O\StringConvert.obj \ + $O\StringToInt.obj \ + $O\UTFConvert.obj \ + $O\MyVector.obj \ + $O\Wildcard.obj \ + $O\XzCrc64Init.obj \ + $O\XzCrc64Reg.obj \ + +WIN_OBJS = \ + $O\DLL.obj \ + $O\ErrorMsg.obj \ + $O\FileDir.obj \ + $O\FileFind.obj \ + $O\FileIO.obj \ + $O\FileLink.obj \ + $O\FileName.obj \ + $O\FileSystem.obj \ + $O\MemoryLock.obj \ + $O\PropVariant.obj \ + $O\PropVariantConv.obj \ + $O\Registry.obj \ + $O\System.obj \ + $O\SystemInfo.obj \ + $O\TimeUtils.obj \ + +7ZIP_COMMON_OBJS = \ + $O\CreateCoder.obj \ + $O\CWrappers.obj \ + $O\FilePathAutoRename.obj \ + $O\FileStreams.obj \ + $O\InBuffer.obj \ + $O\InOutTempBuffer.obj \ + $O\FilterCoder.obj \ + $O\LimitedStreams.obj \ + $O\MethodId.obj \ + $O\MethodProps.obj \ + $O\MultiOutStream.obj \ + $O\OffsetStream.obj \ + $O\OutBuffer.obj \ + $O\ProgressUtils.obj \ + $O\PropId.obj \ + $O\StreamBinder.obj \ + $O\StreamObjects.obj \ + $O\StreamUtils.obj \ + $O\UniqBlocks.obj \ + $O\VirtThread.obj \ + +AR_OBJS = \ + $O\LzmaHandler.obj \ + $O\SplitHandler.obj \ + $O\XzHandler.obj \ + +AR_COMMON_OBJS = \ + $O\CoderMixer2.obj \ + $O\DummyOutStream.obj \ + $O\HandlerOut.obj \ + $O\InStreamWithCRC.obj \ + $O\ItemNameUtils.obj \ + $O\MultiStream.obj \ + $O\OutStreamWithCRC.obj \ + $O\ParseProperties.obj \ + + +7Z_OBJS = \ + $O\7zCompressionMode.obj \ + $O\7zDecode.obj \ + $O\7zEncode.obj \ + $O\7zExtract.obj \ + $O\7zFolderInStream.obj \ + $O\7zHandler.obj \ + $O\7zHandlerOut.obj \ + $O\7zHeader.obj \ + $O\7zIn.obj \ + $O\7zOut.obj \ + $O\7zProperties.obj \ + $O\7zRegister.obj \ + $O\7zSpecStream.obj \ + $O\7zUpdate.obj \ + +COMPRESS_OBJS = \ + $O\Bcj2Coder.obj \ + $O\Bcj2Register.obj \ + $O\BcjCoder.obj \ + $O\BcjRegister.obj \ + $O\BranchMisc.obj \ + $O\BranchRegister.obj \ + $O\ByteSwap.obj \ + $O\CopyCoder.obj \ + $O\CopyRegister.obj \ + $O\DeltaFilter.obj \ + $O\Lzma2Decoder.obj \ + $O\Lzma2Encoder.obj \ + $O\Lzma2Register.obj \ + $O\LzmaDecoder.obj \ + $O\LzmaEncoder.obj \ + $O\LzmaRegister.obj \ + $O\XzDecoder.obj \ + $O\XzEncoder.obj \ + +CRYPTO_OBJS = \ + $O\7zAes.obj \ + $O\7zAesRegister.obj \ + $O\MyAes.obj \ + $O\MyAesReg.obj \ + $O\RandGen.obj \ + +C_OBJS = \ + $O\7zStream.obj \ + $O\Alloc.obj \ + $O\Bcj2.obj \ + $O\Bcj2Enc.obj \ + $O\Bra.obj \ + $O\Bra86.obj \ + $O\BraIA64.obj \ + $O\CpuArch.obj \ + $O\Delta.obj \ + $O\LzFind.obj \ + $O\LzFindMt.obj \ + $O\Lzma2Dec.obj \ + $O\Lzma2DecMt.obj \ + $O\Lzma2Enc.obj \ + $O\LzmaDec.obj \ + $O\LzmaEnc.obj \ + $O\MtCoder.obj \ + $O\MtDec.obj \ + $O\SwapBytes.obj \ + $O\Threads.obj \ + $O\Xz.obj \ + $O\XzDec.obj \ + $O\XzEnc.obj \ + $O\XzIn.obj \ + +!include "../../UI/Console/Console.mak" + +!include "../../Aes.mak" +!include "../../Crc.mak" +!include "../../Crc64.mak" +!include "../../LzFindOpt.mak" +!include "../../LzmaDec.mak" +!include "../../Sha256.mak" +!include "../../Sort.mak" + +!include "../../7zip.mak" diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone7z/makefile.gcc b/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone7z/makefile.gcc new file mode 100644 index 00000000..179bfef5 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone7z/makefile.gcc @@ -0,0 +1,276 @@ +PROG = 7zr + +CONSOLE_VARIANT_FLAGS=-DZ7_PROG_VARIANT_R + +# IS_X64 = 1 +# USE_ASM = 1 +# ST_MODE = 1 + +ifdef SystemDrive +IS_MINGW = 1 +else +ifdef SYSTEMDRIVE +# ifdef OS +IS_MINGW = 1 +endif +endif + +include ../../LzmaDec_gcc.mak + + +LOCAL_FLAGS_ST = +MT_OBJS = + + +ifdef ST_MODE + +LOCAL_FLAGS_ST = -DZ7_ST + +ifdef IS_MINGW +MT_OBJS = \ + $O/Threads.o \ + +endif + +else + +MT_OBJS = \ + $O/LzFindMt.o \ + $O/LzFindOpt.o \ + $O/Threads.o \ + $O/StreamBinder.o \ + $O/VirtThread.o \ + + + + + +endif + + + +LOCAL_FLAGS_SYS = + +ifdef IS_MINGW + +LOCAL_FLAGS_SYS = \ + -DZ7_DEVICE_FILE \ + +SYS_OBJS = \ + $O/FileSystem.o \ + $O/Registry.o \ + $O/MemoryLock.o \ + $O/DLL.o \ + $O/DllSecur.o \ + $O/resource.o \ + +else + +SYS_OBJS = \ + $O/MyWindows.o \ + +endif + +LOCAL_FLAGS = \ + $(LOCAL_FLAGS_ST) \ + $(LOCAL_FLAGS_SYS) \ + + +CONSOLE_OBJS = \ + $O/BenchCon.o \ + $O/ConsoleClose.o \ + $O/ExtractCallbackConsole.o \ + $O/HashCon.o \ + $O/List.o \ + $O/Main.o \ + $O/MainAr.o \ + $O/OpenCallbackConsole.o \ + $O/PercentPrinter.o \ + $O/UpdateCallbackConsole.o \ + $O/UserInputUtils.o \ + +UI_COMMON_OBJS = \ + $O/ArchiveCommandLine.o \ + $O/ArchiveExtractCallback.o \ + $O/ArchiveOpenCallback.o \ + $O/Bench.o \ + $O/DefaultName.o \ + $O/EnumDirItems.o \ + $O/Extract.o \ + $O/ExtractingFilePath.o \ + $O/HashCalc.o \ + $O/LoadCodecs.o \ + $O/OpenArchive.o \ + $O/PropIDUtils.o \ + $O/SetProperties.o \ + $O/SortUtils.o \ + $O/TempFiles.o \ + $O/Update.o \ + $O/UpdateAction.o \ + $O/UpdateCallback.o \ + $O/UpdatePair.o \ + $O/UpdateProduce.o \ + +COMMON_OBJS = \ + $O/CommandLineParser.o \ + $O/CRC.o \ + $O/CrcReg.o \ + $O/DynLimBuf.o \ + $O/IntToString.o \ + $O/ListFileUtils.o \ + $O/LzFindPrepare.o \ + $O/MyString.o \ + $O/MyVector.o \ + $O/NewHandler.o \ + $O/Sha256Prepare.o \ + $O/Sha256Reg.o \ + $O/StdInStream.o \ + $O/StdOutStream.o \ + $O/StringConvert.o \ + $O/StringToInt.o \ + $O/UTFConvert.o \ + $O/Wildcard.o \ + $O/XzCrc64Init.o \ + $O/XzCrc64Reg.o \ + +WIN_OBJS = \ + $O/ErrorMsg.o \ + $O/FileDir.o \ + $O/FileFind.o \ + $O/FileIO.o \ + $O/FileLink.o \ + $O/FileName.o \ + $O/PropVariant.o \ + $O/PropVariantConv.o \ + $O/System.o \ + $O/SystemInfo.o \ + $O/TimeUtils.o \ + +7ZIP_COMMON_OBJS = \ + $O/CreateCoder.o \ + $O/CWrappers.o \ + $O/FilePathAutoRename.o \ + $O/FileStreams.o \ + $O/InBuffer.o \ + $O/InOutTempBuffer.o \ + $O/FilterCoder.o \ + $O/LimitedStreams.o \ + $O/MethodId.o \ + $O/MethodProps.o \ + $O/MultiOutStream.o \ + $O/OffsetStream.o \ + $O/OutBuffer.o \ + $O/ProgressUtils.o \ + $O/PropId.o \ + $O/StreamObjects.o \ + $O/StreamUtils.o \ + $O/UniqBlocks.o \ + +AR_OBJS = \ + $O/LzmaHandler.o \ + $O/SplitHandler.o \ + $O/XzHandler.o \ + +AR_COMMON_OBJS = \ + $O/CoderMixer2.o \ + $O/DummyOutStream.o \ + $O/HandlerOut.o \ + $O/InStreamWithCRC.o \ + $O/ItemNameUtils.o \ + $O/MultiStream.o \ + $O/OutStreamWithCRC.o \ + $O/ParseProperties.o \ + +7Z_OBJS = \ + $O/7zCompressionMode.o \ + $O/7zDecode.o \ + $O/7zEncode.o \ + $O/7zExtract.o \ + $O/7zFolderInStream.o \ + $O/7zHandler.o \ + $O/7zHandlerOut.o \ + $O/7zHeader.o \ + $O/7zIn.o \ + $O/7zOut.o \ + $O/7zProperties.o \ + $O/7zRegister.o \ + $O/7zSpecStream.o \ + $O/7zUpdate.o \ + +COMPRESS_OBJS = \ + $O/Bcj2Coder.o \ + $O/Bcj2Register.o \ + $O/BcjCoder.o \ + $O/BcjRegister.o \ + $O/BranchMisc.o \ + $O/BranchRegister.o \ + $O/ByteSwap.o \ + $O/CopyCoder.o \ + $O/CopyRegister.o \ + $O/DeltaFilter.o \ + $O/Lzma2Decoder.o \ + $O/Lzma2Encoder.o \ + $O/Lzma2Register.o \ + $O/LzmaDecoder.o \ + $O/LzmaEncoder.o \ + $O/LzmaRegister.o \ + $O/XzDecoder.o \ + $O/XzEncoder.o \ + +CRYPTO_OBJS = \ + $O/7zAes.o \ + $O/7zAesRegister.o \ + $O/MyAes.o \ + $O/MyAesReg.o \ + $O/RandGen.o \ + +C_OBJS = \ + $O/7zCrc.o \ + $O/7zCrcOpt.o \ + $O/7zStream.o \ + $O/Aes.o \ + $O/AesOpt.o \ + $O/Alloc.o \ + $O/Bcj2.o \ + $O/Bcj2Enc.o \ + $O/Bra.o \ + $O/Bra86.o \ + $O/BraIA64.o \ + $O/CpuArch.o \ + $O/Delta.o \ + $O/LzFind.o \ + $O/Lzma2Dec.o \ + $O/Lzma2DecMt.o \ + $O/Lzma2Enc.o \ + $O/LzmaDec.o \ + $O/LzmaEnc.o \ + $O/MtCoder.o \ + $O/MtDec.o \ + $O/Sha256.o \ + $O/Sha256Opt.o \ + $O/SwapBytes.o \ + $O/Xz.o \ + $O/XzDec.o \ + $O/XzEnc.o \ + $O/XzIn.o \ + $O/XzCrc64.o \ + $O/XzCrc64Opt.o \ + + +OBJS = \ + $(LZMA_DEC_OPT_OBJS) \ + $(C_OBJS) \ + $(MT_OBJS) \ + $(SYS_OBJS) \ + $(COMMON_OBJS) \ + $(WIN_OBJS) \ + $(COMPRESS_OBJS) \ + $(CRYPTO_OBJS) \ + $(7ZIP_COMMON_OBJS) \ + $(AR_OBJS) \ + $(AR_COMMON_OBJS) \ + $(7Z_OBJS) \ + $(UI_COMMON_OBJS) \ + $(CONSOLE_OBJS) \ + +include ../../7zip_gcc.mak diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone7z/resource.rc b/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone7z/resource.rc new file mode 100644 index 00000000..59378500 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Alone7z/resource.rc @@ -0,0 +1,7 @@ +#include "../../../../C/7zVersion.rc" + +MY_VERSION_INFO_APP("7-Zip Reduced Standalone Console", "7zr") + +#ifndef UNDER_CE +1 24 MOVEABLE PURE "../../UI/Console/Console.manifest" +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Fm/FM.dsp b/src/plugins/7zip/7za/cpp/7zip/Bundles/Fm/FM.dsp new file mode 100644 index 00000000..6e55d926 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Fm/FM.dsp @@ -0,0 +1,2275 @@ +# Microsoft Developer Studio Project File - Name="FM" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Application" 0x0101 + +CFG=FM - Win32 DebugU +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "FM.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "FM.mak" CFG="FM - Win32 DebugU" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "FM - Win32 Release" (based on "Win32 (x86) Application") +!MESSAGE "FM - Win32 Debug" (based on "Win32 (x86) Application") +!MESSAGE "FM - Win32 ReleaseU" (based on "Win32 (x86) Application") +!MESSAGE "FM - Win32 DebugU" (based on "Win32 (x86) Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "FM - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /Gz /MD /W4 /WX /GX /O1 /D "NDEBUG" /D "_MBCS" /D "WIN32" /D "_WINDOWS" /D "Z7_LANG" /D "Z7_LONG_PATH" /D "Z7_EXTERNAL_CODECS_2" /D "Z7_DEVICE_FILE" /Yu"StdAfx.h" /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib comctl32.lib Mpr.lib htmlhelp.lib Urlmon.lib /nologo /subsystem:windows /machine:I386 /out:"C:\Util\7zFM.exe" /opt:NOWIN98 +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "FM - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /Gz /MDd /W4 /WX /Gm /GX /ZI /Od /D "_DEBUG" /D "_MBCS" /D "WIN32" /D "_WINDOWS" /D "Z7_LANG" /D "Z7_LONG_PATH" /D "Z7_EXTERNAL_CODECS_2" /D "Z7_DEVICE_FILE" /Yu"StdAfx.h" /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib comctl32.lib Mpr.lib htmlhelp.lib Urlmon.lib /nologo /subsystem:windows /debug /machine:I386 /out:"C:\Util\7zFM.exe" /pdbtype:sept + +!ELSEIF "$(CFG)" == "FM - Win32 ReleaseU" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "ReleaseU" +# PROP BASE Intermediate_Dir "ReleaseU" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "ReleaseU" +# PROP Intermediate_Dir "ReleaseU" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MD /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /Yu"StdAfx.h" /FD /c +# ADD CPP /nologo /Gz /MD /W4 /WX /GX /O1 /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "Z7_LANG" /D "Z7_LONG_PATH" /D "Z7_EXTERNAL_CODECS_2" /D "Z7_DEVICE_FILE" /Yu"StdAfx.h" /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib comctl32.lib /nologo /subsystem:windows /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib comctl32.lib Mpr.lib htmlhelp.lib Urlmon.lib /nologo /subsystem:windows /machine:I386 /out:"C:\Util\7zFM.exe" /opt:NOWIN98 +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "FM - Win32 DebugU" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "DebugU" +# PROP BASE Intermediate_Dir "DebugU" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "DebugU" +# PROP Intermediate_Dir "DebugU" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /Yu"StdAfx.h" /FD /GZ /c +# ADD CPP /nologo /Gz /MDd /W4 /WX /Gm /GX /ZI /Od /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "Z7_LANG" /D "Z7_LONG_PATH" /D "Z7_EXTERNAL_CODECS_2" /D "Z7_DEVICE_FILE" /Yu"StdAfx.h" /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib comctl32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib comctl32.lib Mpr.lib htmlhelp.lib Urlmon.lib /nologo /subsystem:windows /debug /machine:I386 /out:"C:\Util\7zFM.exe" /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "FM - Win32 Release" +# Name "FM - Win32 Debug" +# Name "FM - Win32 ReleaseU" +# Name "FM - Win32 DebugU" +# Begin Group "Spec" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\UI\FileManager\7zipLogo.ico +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\add.bmp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\ClassDefs.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\Copy.bmp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\Delete.bmp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\Extract.bmp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\FM.ico +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\Move.bmp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\Parent.bmp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\Properties.bmp +# End Source File +# Begin Source File + +SOURCE=.\resource.rc +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\StdAfx.cpp +# ADD CPP /Yc"StdAfx.h" +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\StdAfx.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\Test.bmp +# End Source File +# End Group +# Begin Group "Archive" + +# PROP Default_Filter "" +# Begin Group "Archive Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Archive\Common\CoderMixer2.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\CoderMixer2.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\DummyOutStream.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\DummyOutStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\FindSignature.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\FindSignature.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\HandlerOut.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\HandlerOut.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\InStreamWithCRC.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\InStreamWithCRC.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\ItemNameUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\ItemNameUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\MultiStream.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\MultiStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\OutStreamWithCRC.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\OutStreamWithCRC.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\ParseProperties.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\ParseProperties.h +# End Source File +# End Group +# Begin Group "7z" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Archive\7z\7zCompressionMode.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zCompressionMode.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zDecode.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zDecode.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zEncode.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zEncode.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zExtract.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zFolderInStream.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zFolderInStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zHandler.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zHandlerOut.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zHeader.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zHeader.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zIn.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zIn.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zItem.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zOut.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zOut.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zProperties.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zProperties.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zRegister.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zSpecStream.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zSpecStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zUpdate.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zUpdate.h +# End Source File +# End Group +# Begin Source File + +SOURCE=..\..\Archive\IArchive.h +# End Source File +# End Group +# Begin Group "Folders" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\UI\FileManager\AltStreamsFolder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\AltStreamsFolder.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\FSDrives.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\FSDrives.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\FSFolder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\FSFolder.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\FSFolderCopy.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\IFolder.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\NetFolder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\NetFolder.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\RootFolder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\RootFolder.h +# End Source File +# End Group +# Begin Group "Registry" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\UI\FileManager\RegistryAssociations.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\RegistryAssociations.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\RegistryPlugins.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\RegistryPlugins.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\RegistryUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\RegistryUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\ViewSettings.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\ViewSettings.h +# End Source File +# End Group +# Begin Group "Panel" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\UI\FileManager\App.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\App.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\AppState.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\EnumFormatEtc.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\EnumFormatEtc.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\FileFolderPluginOpen.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\FileFolderPluginOpen.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\Panel.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\Panel.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\PanelCopy.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\PanelCrc.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\PanelDrag.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\PanelFolderChange.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\PanelItemOpen.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\PanelItems.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\PanelKey.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\PanelListNotify.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\PanelMenu.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\PanelOperations.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\PanelSelect.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\PanelSort.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\PanelSplitFile.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\VerCtrl.cpp +# End Source File +# End Group +# Begin Group "Dialog" + +# PROP Default_Filter "" +# Begin Group "Options" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\UI\FileManager\EditPage.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\EditPage.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\FoldersPage.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\FoldersPage.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\LangPage.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\LangPage.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\MenuPage.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\MenuPage.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\OptionsDialog.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\SettingsPage.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\SettingsPage.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\SystemPage.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\SystemPage.h +# End Source File +# End Group +# Begin Source File + +SOURCE=..\..\UI\FileManager\AboutDialog.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\AboutDialog.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\BrowseDialog.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\BrowseDialog.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\BrowseDialog2.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\BrowseDialog2.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\BrowseDialog2Res.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\ComboDialog.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\ComboDialog.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\CopyDialog.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\CopyDialog.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\DialogSize.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\EditDialog.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\EditDialog.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\LinkDialog.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\LinkDialog.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\ListViewDialog.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\ListViewDialog.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\MemDialog.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\MemDialog.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\MemDialogRes.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\MessagesDialog.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\MessagesDialog.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\OverwriteDialog.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\OverwriteDialog.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\PasswordDialog.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\PasswordDialog.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\ProgressDialog2.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\ProgressDialog2.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\SplitDialog.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\SplitDialog.h +# End Source File +# End Group +# Begin Group "FM Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\UI\FileManager\ExtractCallback.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\ExtractCallback.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\FormatUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\FormatUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\HelpUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\HelpUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\LangUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\LangUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\ProgramLocation.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\ProgramLocation.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\UpdateCallback100.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\UpdateCallback100.h +# End Source File +# End Group +# Begin Group "7-Zip Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Common\CreateCoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\CreateCoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\CWrappers.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\CWrappers.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FilePathAutoRename.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FilePathAutoRename.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FileStreams.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FileStreams.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\InOutTempBuffer.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\InOutTempBuffer.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\MethodProps.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\MethodProps.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\MultiOutStream.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\MultiOutStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\OutBuffer.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\OutBuffer.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\ProgressUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\ProgressUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\PropId.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamBinder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamBinder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamObjects.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamObjects.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\UniqBlocks.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\UniqBlocks.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\VirtThread.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\VirtThread.h +# End Source File +# End Group +# Begin Group "C" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\..\C\7zCrc.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zCrc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zCrcOpt.c + +!IF "$(CFG)" == "FM - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "FM - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "FM - Win32 ReleaseU" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "FM - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zStream.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zTypes.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zWindows.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Alloc.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Alloc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Compiler.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\CpuArch.c + +!IF "$(CFG)" == "FM - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "FM - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "FM - Win32 ReleaseU" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "FM - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\CpuArch.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\DllSecur.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\DllSecur.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzFind.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzFind.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzFindMt.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzFindMt.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzFindOpt.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Lzma2Dec.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Lzma2Dec.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Lzma2DecMt.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Lzma2DecMt.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Lzma2Enc.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Lzma2Enc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzmaDec.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzmaDec.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzmaEnc.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzmaEnc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\MtCoder.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\MtCoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\MtDec.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\MtDec.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Sha1.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Sha1.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Sha1Opt.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Sha256.c + +!IF "$(CFG)" == "FM - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "FM - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "FM - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "FM - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Sha256.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Sha256Opt.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Sort.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Sort.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Threads.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Threads.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\XzCrc64.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\XzCrc64Opt.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# End Group +# Begin Group "Windows" + +# PROP Default_Filter "" +# Begin Group "Control" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\Windows\Control\ComboBox.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\ComboBox.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\CommandBar.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\Dialog.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\Dialog.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\Edit.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\ImageList.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\ListView.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\ListView.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\ProgressBar.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\PropertyPage.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\PropertyPage.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\ReBar.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\Static.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\StatusBar.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\ToolBar.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\Trackbar.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\Window2.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\Window2.h +# End Source File +# End Group +# Begin Source File + +SOURCE=..\..\..\Windows\Clipboard.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Clipboard.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\COM.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\CommonDialog.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\CommonDialog.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Defs.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Device.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\DLL.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\DLL.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\ErrorMsg.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\ErrorMsg.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileDir.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileDir.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileFind.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileFind.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileIO.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileIO.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileLink.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileMapping.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileName.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileName.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileSystem.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileSystem.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Handle.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\MemoryGlobal.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\MemoryGlobal.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\MemoryLock.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\MemoryLock.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Menu.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Menu.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Net.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Net.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\ProcessUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\ProcessUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\PropVariant.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\PropVariant.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\PropVariantConv.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\PropVariantConv.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Registry.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Registry.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\ResourceString.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\ResourceString.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\SecurityUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\SecurityUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Shell.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Shell.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Synchronization.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Synchronization.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\System.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\System.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\SystemInfo.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\SystemInfo.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Thread.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\TimeUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\TimeUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Window.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Window.h +# End Source File +# End Group +# Begin Group "Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\Common\CommandLineParser.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\CommandLineParser.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Common.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\ComTry.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\CRC.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\CrcReg.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Defs.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\DynamicBuffer.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\DynLimBuf.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\DynLimBuf.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Exception.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\IntToString.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\IntToString.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Lang.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Lang.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\LimitedStreams.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\LimitedStreams.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\ListFileUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\ListFileUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\LzFindPrepare.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyBuffer.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyCom.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyString.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyString.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyVector.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyVector.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\NewHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\NewHandler.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Random.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Random.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Sha1Prepare.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Sha1Reg.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Sha256Prepare.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Sha256Reg.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StringConvert.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StringConvert.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StringToInt.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StringToInt.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Types.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\UTFConvert.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\UTFConvert.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Wildcard.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Wildcard.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\XzCrc64Init.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\XzCrc64Reg.cpp +# End Source File +# End Group +# Begin Group "UI" + +# PROP Default_Filter "" +# Begin Group "UI Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\UI\Common\ArchiveExtractCallback.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\ArchiveExtractCallback.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\ArchiveName.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\ArchiveName.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\ArchiveOpenCallback.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\ArchiveOpenCallback.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\Bench.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\Bench.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\CompressCall.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\CompressCall2.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\DefaultName.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\DefaultName.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\DirItem.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\EnumDirItems.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\EnumDirItems.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\ExitCode.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\Extract.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\Extract.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\ExtractingFilePath.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\ExtractingFilePath.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\ExtractMode.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FilterCoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FilterCoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\HashCalc.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\HashCalc.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\IFileExtractCallback.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\LoadCodecs.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\LoadCodecs.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\OpenArchive.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\OpenArchive.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\Property.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\PropIDUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\PropIDUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\SetProperties.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\SetProperties.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\SortUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\SortUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\StdAfx.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\TempFiles.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\TempFiles.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\Update.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\Update.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\UpdateAction.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\UpdateAction.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\UpdateCallback.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\UpdateCallback.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\UpdatePair.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\UpdatePair.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\UpdateProduce.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\UpdateProduce.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\WorkDir.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\WorkDir.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\ZipRegistry.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\ZipRegistry.h +# End Source File +# End Group +# Begin Group "Agent" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\UI\Agent\Agent.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Agent\Agent.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Agent\AgentOut.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Agent\AgentProxy.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Agent\AgentProxy.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Agent\ArchiveFolder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Agent\ArchiveFolderOpen.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Agent\ArchiveFolderOut.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Agent\IFolderArchive.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Agent\UpdateCallbackAgent.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Agent\UpdateCallbackAgent.h +# End Source File +# End Group +# Begin Group "Explorer" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\UI\Explorer\ContextMenu.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Explorer\ContextMenu.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Explorer\MyMessages.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Explorer\MyMessages.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Explorer\RegistryContextMenu.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Explorer\RegistryContextMenu.h +# End Source File +# End Group +# Begin Group "GUI" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\UI\GUI\BenchmarkDialog.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\GUI\BenchmarkDialog.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\GUI\BenchmarkDialogRes.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\GUI\CompressDialog.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\GUI\CompressDialog.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\GUI\ExtractDialog.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\GUI\ExtractDialog.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\GUI\ExtractGUI.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\GUI\ExtractGUI.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\GUI\HashGUI.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\GUI\HashGUI.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\GUI\UpdateCallbackGUI.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\GUI\UpdateCallbackGUI.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\GUI\UpdateCallbackGUI2.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\GUI\UpdateCallbackGUI2.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\GUI\UpdateGUI.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\GUI\UpdateGUI.h +# End Source File +# End Group +# End Group +# Begin Group "Compress" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Compress\CopyCoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\CopyCoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\CopyRegister.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Lzma2Decoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Lzma2Decoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Lzma2Encoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Lzma2Encoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Lzma2Register.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\LzmaDecoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\LzmaDecoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\LzmaEncoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\LzmaEncoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\LzmaRegister.cpp +# End Source File +# End Group +# Begin Group "Interface" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\ICoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\IDecl.h +# End Source File +# Begin Source File + +SOURCE=..\..\IPassword.h +# End Source File +# Begin Source File + +SOURCE=..\..\IProgress.h +# End Source File +# End Group +# Begin Source File + +SOURCE=..\..\UI\FileManager\7zFM.exe.manifest +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\7zipLogo.ico +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\Add2.bmp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\Copy2.bmp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\Delete2.bmp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\Extract2.bmp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\FilePlugins.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\FilePlugins.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\FM.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\Info.bmp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\Info2.bmp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\Move2.bmp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\MyCom2.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\MyLoadMenu.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\MyLoadMenu.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\OpenCallback.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\OpenCallback.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\PluginInterface.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\PluginLoader.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\PropertyName.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\PropertyName.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\resource.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\SplitUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\SplitUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\StringUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\StringUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\SysIconUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\SysIconUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\Test2.bmp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\TextPairs.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\TextPairs.h +# End Source File +# End Target +# End Project diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Fm/FM.dsw b/src/plugins/7zip/7za/cpp/7zip/Bundles/Fm/FM.dsw new file mode 100644 index 00000000..1c955d95 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Fm/FM.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "FM"=.\FM.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Fm/StdAfx.cpp b/src/plugins/7zip/7za/cpp/7zip/Bundles/Fm/StdAfx.cpp new file mode 100644 index 00000000..d0feea85 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Fm/StdAfx.cpp @@ -0,0 +1,3 @@ +// StdAfx.cpp + +#include "StdAfx.h" diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Fm/StdAfx.h b/src/plugins/7zip/7za/cpp/7zip/Bundles/Fm/StdAfx.h new file mode 100644 index 00000000..4f27255c --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Fm/StdAfx.h @@ -0,0 +1,6 @@ +// StdAfx.h + +#if _MSC_VER >= 1800 +#pragma warning(disable : 4464) // relative include path contains '..' +#endif +#include "../../UI/FileManager/StdAfx.h" diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Fm/makefile b/src/plugins/7zip/7za/cpp/7zip/Bundles/Fm/makefile new file mode 100644 index 00000000..b7fa247a --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Fm/makefile @@ -0,0 +1,82 @@ +PROG = 7zFM.exe + +!include "../Format7zF/Arc.mak" + +!include "../../UI/FileManager/FM.mak" + +COMMON_OBJS = $(COMMON_OBJS) \ + $O\CommandLineParser.obj \ + $O\Lang.obj \ + $O\ListFileUtils.obj \ + $O\Random.obj \ + +WIN_OBJS = $(WIN_OBJS) \ + $O\Clipboard.obj \ + $O\CommonDialog.obj \ + $O\DLL.obj \ + $O\ErrorMsg.obj \ + $O\FileLink.obj \ + $O\MemoryGlobal.obj \ + $O\MemoryLock.obj \ + $O\Menu.obj \ + $O\ProcessUtils.obj \ + $O\Registry.obj \ + $O\ResourceString.obj \ + $O\SystemInfo.obj \ + $O\Shell.obj \ + $O\Window.obj \ + +WIN_CTRL_OBJS = \ + $O\ComboBox.obj \ + $O\Dialog.obj \ + $O\ListView.obj \ + $O\PropertyPage.obj \ + $O\Window2.obj \ + +7ZIP_COMMON_OBJS = $(7ZIP_COMMON_OBJS) \ + $O\FilePathAutoRename.obj \ + $O\FileStreams.obj \ + $O\MultiOutStream.obj \ + +UI_COMMON_OBJS = \ + $O\ArchiveExtractCallback.obj \ + $O\ArchiveName.obj \ + $O\ArchiveOpenCallback.obj \ + $O\Bench.obj \ + $O\CompressCall2.obj \ + $O\DefaultName.obj \ + $O\EnumDirItems.obj \ + $O\Extract.obj \ + $O\ExtractingFilePath.obj \ + $O\HashCalc.obj \ + $O\LoadCodecs.obj \ + $O\OpenArchive.obj \ + $O\PropIDUtils.obj \ + $O\SetProperties.obj \ + $O\SortUtils.obj \ + $O\TempFiles.obj \ + $O\Update.obj \ + $O\UpdateAction.obj \ + $O\UpdateCallback.obj \ + $O\UpdatePair.obj \ + $O\UpdateProduce.obj \ + $O\WorkDir.obj \ + $O\ZipRegistry.obj \ + +EXPLORER_OBJS = \ + $O\ContextMenu.obj \ + $O\MyMessages.obj \ + $O\RegistryContextMenu.obj \ + +GUI_OBJS = \ + $O\BenchmarkDialog.obj \ + $O\CompressDialog.obj \ + $O\ExtractDialog.obj \ + $O\ExtractGUI.obj \ + $O\HashGUI.obj \ + $O\UpdateCallbackGUI.obj \ + $O\UpdateCallbackGUI2.obj \ + $O\UpdateGUI.obj \ + + +!include "../../7zip.mak" diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Fm/resource.rc b/src/plugins/7zip/7za/cpp/7zip/Bundles/Fm/resource.rc new file mode 100644 index 00000000..ebc2f74b --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Fm/resource.rc @@ -0,0 +1,7 @@ +#include "../../UI/FileManager/resource.rc" +#include "../../UI/GUI/resource2.rc" + +STRINGTABLE +BEGIN + 100 "7z zip rar 001 cab iso xz txz lzma tar cpio bz2 bzip2 tbz2 tbz gz gzip tgz tpz z taz lzh lha rpm deb arj vhd vhdx wim swm esd fat ntfs dmg hfs xar squashfs apfs" +END diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7z/StdAfx.cpp b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7z/StdAfx.cpp new file mode 100644 index 00000000..d0feea85 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7z/StdAfx.cpp @@ -0,0 +1,3 @@ +// StdAfx.cpp + +#include "StdAfx.h" diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7z/StdAfx.h b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7z/StdAfx.h new file mode 100644 index 00000000..035267cc --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7z/StdAfx.h @@ -0,0 +1,11 @@ +// StdAfx.h + +#ifndef ZIP7_INC_STDAFX_H +#define ZIP7_INC_STDAFX_H + +#if defined(_MSC_VER) && _MSC_VER >= 1800 +#pragma warning(disable : 4464) // relative include path contains '..' +#endif +#include "../../../Common/Common.h" + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7z/makefile b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7z/makefile new file mode 100644 index 00000000..3d4754ca --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7z/makefile @@ -0,0 +1,148 @@ +PROG = 7za.dll +DEF_FILE = ../../Archive/Archive2.def +CFLAGS = $(CFLAGS) \ + -DZ7_DEFLATE_EXTRACT_ONLY \ + -DZ7_BZIP2_EXTRACT_ONLY \ + +COMMON_OBJS = \ + $O\CRC.obj \ + $O\CrcReg.obj \ + $O\IntToString.obj \ + $O\LzFindPrepare.obj \ + $O\NewHandler.obj \ + $O\MyString.obj \ + $O\Sha256Reg.obj \ + $O\StringConvert.obj \ + $O\StringToInt.obj \ + $O\MyVector.obj \ + $O\Wildcard.obj \ + +WIN_OBJS = \ + $O\FileDir.obj \ + $O\FileFind.obj \ + $O\FileIO.obj \ + $O\FileName.obj \ + $O\PropVariant.obj \ + $O\Synchronization.obj \ + $O\System.obj \ + $O\TimeUtils.obj \ + +7ZIP_COMMON_OBJS = \ + $O\CreateCoder.obj \ + $O\CWrappers.obj \ + $O\InBuffer.obj \ + $O\InOutTempBuffer.obj \ + $O\FilterCoder.obj \ + $O\LimitedStreams.obj \ + $O\MethodId.obj \ + $O\MethodProps.obj \ + $O\OutBuffer.obj \ + $O\ProgressUtils.obj \ + $O\PropId.obj \ + $O\StreamBinder.obj \ + $O\StreamObjects.obj \ + $O\StreamUtils.obj \ + $O\UniqBlocks.obj \ + $O\VirtThread.obj \ + +AR_OBJS = \ + $O\ArchiveExports.obj \ + $O\DllExports2.obj \ + +AR_COMMON_OBJS = \ + $O\CoderMixer2.obj \ + $O\HandlerOut.obj \ + $O\InStreamWithCRC.obj \ + $O\ItemNameUtils.obj \ + $O\OutStreamWithCRC.obj \ + $O\ParseProperties.obj \ + + +7Z_OBJS = \ + $O\7zCompressionMode.obj \ + $O\7zDecode.obj \ + $O\7zEncode.obj \ + $O\7zExtract.obj \ + $O\7zFolderInStream.obj \ + $O\7zHandler.obj \ + $O\7zHandlerOut.obj \ + $O\7zHeader.obj \ + $O\7zIn.obj \ + $O\7zOut.obj \ + $O\7zProperties.obj \ + $O\7zSpecStream.obj \ + $O\7zUpdate.obj \ + $O\7zRegister.obj \ + + +COMPRESS_OBJS = \ + $O\CodecExports.obj \ + $O\Bcj2Coder.obj \ + $O\Bcj2Register.obj \ + $O\BcjCoder.obj \ + $O\BcjRegister.obj \ + $O\BitlDecoder.obj \ + $O\BranchMisc.obj \ + $O\BranchRegister.obj \ + $O\ByteSwap.obj \ + $O\BZip2Crc.obj \ + $O\BZip2Decoder.obj \ + $O\BZip2Register.obj \ + $O\CopyCoder.obj \ + $O\CopyRegister.obj \ + $O\DeflateDecoder.obj \ + $O\DeflateRegister.obj \ + $O\DeltaFilter.obj \ + $O\Lzma2Decoder.obj \ + $O\Lzma2Encoder.obj \ + $O\Lzma2Register.obj \ + $O\LzmaDecoder.obj \ + $O\LzmaEncoder.obj \ + $O\LzmaRegister.obj \ + $O\LzOutWindow.obj \ + $O\PpmdDecoder.obj \ + $O\PpmdEncoder.obj \ + $O\PpmdRegister.obj \ + +CRYPTO_OBJS = \ + $O\7zAes.obj \ + $O\7zAesRegister.obj \ + $O\MyAes.obj \ + $O\MyAesReg.obj \ + $O\RandGen.obj \ + +C_OBJS = \ + $O\7zStream.obj \ + $O\Alloc.obj \ + $O\Bcj2.obj \ + $O\Bcj2Enc.obj \ + $O\Bra.obj \ + $O\Bra86.obj \ + $O\BraIA64.obj \ + $O\BwtSort.obj \ + $O\CpuArch.obj \ + $O\Delta.obj \ + $O\HuffEnc.obj \ + $O\LzFind.obj \ + $O\LzFindMt.obj \ + $O\Lzma2Dec.obj \ + $O\Lzma2DecMt.obj \ + $O\Lzma2Enc.obj \ + $O\LzmaDec.obj \ + $O\LzmaEnc.obj \ + $O\MtCoder.obj \ + $O\MtDec.obj \ + $O\Ppmd7.obj \ + $O\Ppmd7Dec.obj \ + $O\Ppmd7Enc.obj \ + $O\SwapBytes.obj \ + $O\Threads.obj \ + +!include "../../Aes.mak" +!include "../../Crc.mak" +!include "../../LzFindOpt.mak" +!include "../../LzmaDec.mak" +!include "../../Sha256.mak" +!include "../../Sort.mak" + +!include "../../7zip.mak" diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7z/resource.rc b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7z/resource.rc new file mode 100644 index 00000000..2f2b42ae --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7z/resource.rc @@ -0,0 +1,5 @@ +#include "../../MyVersionInfo.rc" + +MY_VERSION_INFO_DLL("7z Standalone Plugin", "7za") + +101 ICON "../../Archive/Icons/7z.ico" diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zExtract/StdAfx.cpp b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zExtract/StdAfx.cpp new file mode 100644 index 00000000..d0feea85 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zExtract/StdAfx.cpp @@ -0,0 +1,3 @@ +// StdAfx.cpp + +#include "StdAfx.h" diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zExtract/StdAfx.h b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zExtract/StdAfx.h new file mode 100644 index 00000000..035267cc --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zExtract/StdAfx.h @@ -0,0 +1,11 @@ +// StdAfx.h + +#ifndef ZIP7_INC_STDAFX_H +#define ZIP7_INC_STDAFX_H + +#if defined(_MSC_VER) && _MSC_VER >= 1800 +#pragma warning(disable : 4464) // relative include path contains '..' +#endif +#include "../../../Common/Common.h" + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zExtract/makefile b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zExtract/makefile new file mode 100644 index 00000000..4e2ed3e8 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zExtract/makefile @@ -0,0 +1,116 @@ +PROG = 7zxa.dll +DEF_FILE = ../../Archive/Archive2.def +CFLAGS = $(CFLAGS) \ + -DZ7_EXTRACT_ONLY \ + +COMMON_OBJS = \ + $O\CRC.obj \ + $O\CrcReg.obj \ + $O\IntToString.obj \ + $O\MyString.obj \ + $O\MyVector.obj \ + $O\NewHandler.obj \ + $O\Sha256Reg.obj \ + $O\StringConvert.obj \ + $O\StringToInt.obj \ + $O\Wildcard.obj \ + +WIN_OBJS = \ + $O\PropVariant.obj \ + $O\Synchronization.obj \ + $O\System.obj \ + +7ZIP_COMMON_OBJS = \ + $O\CreateCoder.obj \ + $O\CWrappers.obj \ + $O\InBuffer.obj \ + $O\FilterCoder.obj \ + $O\LimitedStreams.obj \ + $O\MethodId.obj \ + $O\MethodProps.obj \ + $O\OutBuffer.obj \ + $O\ProgressUtils.obj \ + $O\PropId.obj \ + $O\StreamBinder.obj \ + $O\StreamObjects.obj \ + $O\StreamUtils.obj \ + $O\VirtThread.obj \ + +AR_OBJS = \ + $O\ArchiveExports.obj \ + $O\DllExports2.obj \ + +AR_COMMON_OBJS = \ + $O\CoderMixer2.obj \ + $O\HandlerOut.obj \ + $O\ItemNameUtils.obj \ + $O\OutStreamWithCRC.obj \ + $O\ParseProperties.obj \ + +7Z_OBJS = \ + $O\7zCompressionMode.obj \ + $O\7zDecode.obj \ + $O\7zExtract.obj \ + $O\7zHandler.obj \ + $O\7zHeader.obj \ + $O\7zIn.obj \ + $O\7zProperties.obj \ + $O\7zRegister.obj \ + + +COMPRESS_OBJS = \ + $O\CodecExports.obj \ + $O\Bcj2Coder.obj \ + $O\Bcj2Register.obj \ + $O\BcjCoder.obj \ + $O\BcjRegister.obj \ + $O\BitlDecoder.obj \ + $O\BranchMisc.obj \ + $O\BranchRegister.obj \ + $O\ByteSwap.obj \ + $O\BZip2Crc.obj \ + $O\BZip2Decoder.obj \ + $O\BZip2Register.obj \ + $O\CopyCoder.obj \ + $O\CopyRegister.obj \ + $O\DeflateDecoder.obj \ + $O\DeflateRegister.obj \ + $O\DeltaFilter.obj \ + $O\Lzma2Decoder.obj \ + $O\Lzma2Register.obj \ + $O\LzmaDecoder.obj \ + $O\LzmaRegister.obj \ + $O\LzOutWindow.obj \ + $O\PpmdDecoder.obj \ + $O\PpmdRegister.obj \ + +CRYPTO_OBJS = \ + $O\7zAes.obj \ + $O\7zAesRegister.obj \ + $O\MyAes.obj \ + $O\MyAesReg.obj \ + +C_OBJS = \ + $O\7zStream.obj \ + $O\Alloc.obj \ + $O\Bcj2.obj \ + $O\Bra.obj \ + $O\Bra86.obj \ + $O\BraIA64.obj \ + $O\CpuArch.obj \ + $O\Delta.obj \ + $O\Lzma2Dec.obj \ + $O\Lzma2DecMt.obj \ + $O\LzmaDec.obj \ + $O\MtDec.obj \ + $O\Ppmd7.obj \ + $O\Ppmd7Dec.obj \ + $O\SwapBytes.obj \ + $O\Threads.obj \ + +!include "../../Aes.mak" +!include "../../Crc.mak" +!include "../../LzmaDec.mak" +!include "../../Sha256.mak" + +!include "../../7zip.mak" diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zExtract/resource.rc b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zExtract/resource.rc new file mode 100644 index 00000000..6a654615 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zExtract/resource.rc @@ -0,0 +1,5 @@ +#include "../../MyVersionInfo.rc" + +MY_VERSION_INFO_DLL("7z Standalone Extracting Plugin", "7zxa") + +101 ICON "../../Archive/Icons/7z.ico" diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zExtractR/StdAfx.cpp b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zExtractR/StdAfx.cpp new file mode 100644 index 00000000..d0feea85 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zExtractR/StdAfx.cpp @@ -0,0 +1,3 @@ +// StdAfx.cpp + +#include "StdAfx.h" diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zExtractR/StdAfx.h b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zExtractR/StdAfx.h new file mode 100644 index 00000000..035267cc --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zExtractR/StdAfx.h @@ -0,0 +1,11 @@ +// StdAfx.h + +#ifndef ZIP7_INC_STDAFX_H +#define ZIP7_INC_STDAFX_H + +#if defined(_MSC_VER) && _MSC_VER >= 1800 +#pragma warning(disable : 4464) // relative include path contains '..' +#endif +#include "../../../Common/Common.h" + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zExtractR/makefile b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zExtractR/makefile new file mode 100644 index 00000000..50b84d87 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zExtractR/makefile @@ -0,0 +1,98 @@ +PROG = 7zxr.dll +DEF_FILE = ../../Archive/Archive2.def +CFLAGS = $(CFLAGS) \ + -DZ7_EXTRACT_ONLY \ + -DZ7_NO_CRYPTO + +COMMON_OBJS = \ + $O\CRC.obj \ + $O\CrcReg.obj \ + $O\IntToString.obj \ + $O\NewHandler.obj \ + $O\MyString.obj \ + $O\StringConvert.obj \ + $O\StringToInt.obj \ + $O\MyVector.obj \ + $O\Wildcard.obj \ + +WIN_OBJS = \ + $O\PropVariant.obj \ + $O\Synchronization.obj \ + $O\System.obj \ + +7ZIP_COMMON_OBJS = \ + $O\CreateCoder.obj \ + $O\CWrappers.obj \ + $O\InBuffer.obj \ + $O\FilterCoder.obj \ + $O\LimitedStreams.obj \ + $O\MethodId.obj \ + $O\MethodProps.obj \ + $O\OutBuffer.obj \ + $O\ProgressUtils.obj \ + $O\PropId.obj \ + $O\StreamBinder.obj \ + $O\StreamObjects.obj \ + $O\StreamUtils.obj \ + $O\VirtThread.obj \ + +AR_OBJS = \ + $O\ArchiveExports.obj \ + $O\DllExports2.obj \ + +AR_COMMON_OBJS = \ + $O\CoderMixer2.obj \ + $O\HandlerOut.obj \ + $O\ItemNameUtils.obj \ + $O\OutStreamWithCRC.obj \ + $O\ParseProperties.obj \ + + +7Z_OBJS = \ + $O\7zCompressionMode.obj \ + $O\7zDecode.obj \ + $O\7zExtract.obj \ + $O\7zHandler.obj \ + $O\7zHeader.obj \ + $O\7zIn.obj \ + $O\7zProperties.obj \ + $O\7zRegister.obj \ + + +COMPRESS_OBJS = \ + $O\CodecExports.obj \ + $O\Bcj2Coder.obj \ + $O\Bcj2Register.obj \ + $O\BcjCoder.obj \ + $O\BcjRegister.obj \ + $O\BranchMisc.obj \ + $O\BranchRegister.obj \ + $O\ByteSwap.obj \ + $O\CopyCoder.obj \ + $O\CopyRegister.obj \ + $O\DeltaFilter.obj \ + $O\Lzma2Decoder.obj \ + $O\Lzma2Register.obj \ + $O\LzmaDecoder.obj \ + $O\LzmaRegister.obj \ + +C_OBJS = \ + $O\7zStream.obj \ + $O\Alloc.obj \ + $O\Bcj2.obj \ + $O\Bra.obj \ + $O\Bra86.obj \ + $O\BraIA64.obj \ + $O\CpuArch.obj \ + $O\Delta.obj \ + $O\Lzma2Dec.obj \ + $O\Lzma2DecMt.obj \ + $O\LzmaDec.obj \ + $O\MtDec.obj \ + $O\SwapBytes.obj \ + $O\Threads.obj \ + +!include "../../Crc.mak" +!include "../../LzmaDec.mak" + +!include "../../7zip.mak" diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zExtractR/resource.rc b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zExtractR/resource.rc new file mode 100644 index 00000000..149f58c4 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zExtractR/resource.rc @@ -0,0 +1,5 @@ +#include "../../../../C/7zVersion.rc" + +MY_VERSION_INFO_DLL("7z Extracting Reduced Standalone Plugin", "7zxr") + +101 ICON "../../Archive/Icons/7z.ico" diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zF/Arc.mak b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zF/Arc.mak new file mode 100644 index 00000000..b1c6fe22 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zF/Arc.mak @@ -0,0 +1,310 @@ +COMMON_OBJS = \ + $O\CRC.obj \ + $O\CrcReg.obj \ + $O\DynLimBuf.obj \ + $O\IntToString.obj \ + $O\LzFindPrepare.obj \ + $O\Md5Reg.obj \ + $O\MyMap.obj \ + $O\MyString.obj \ + $O\MyVector.obj \ + $O\MyXml.obj \ + $O\NewHandler.obj \ + $O\Sha1Reg.obj \ + $O\Sha256Reg.obj \ + $O\Sha3Reg.obj \ + $O\Sha512Reg.obj \ + $O\Sha512Prepare.obj \ + $O\StringConvert.obj \ + $O\StringToInt.obj \ + $O\UTFConvert.obj \ + $O\Wildcard.obj \ + $O\Xxh64Reg.obj \ + $O\XzCrc64Init.obj \ + $O\XzCrc64Reg.obj \ + +WIN_OBJS = \ + $O\FileDir.obj \ + $O\FileFind.obj \ + $O\FileIO.obj \ + $O\FileName.obj \ + $O\PropVariant.obj \ + $O\PropVariantConv.obj \ + $O\PropVariantUtils.obj \ + $O\Synchronization.obj \ + $O\System.obj \ + $O\TimeUtils.obj \ + +7ZIP_COMMON_OBJS = \ + $O\CreateCoder.obj \ + $O\CWrappers.obj \ + $O\InBuffer.obj \ + $O\InOutTempBuffer.obj \ + $O\FilterCoder.obj \ + $O\LimitedStreams.obj \ + $O\LockedStream.obj \ + $O\MemBlocks.obj \ + $O\MethodId.obj \ + $O\MethodProps.obj \ + $O\OffsetStream.obj \ + $O\OutBuffer.obj \ + $O\OutMemStream.obj \ + $O\ProgressMt.obj \ + $O\ProgressUtils.obj \ + $O\PropId.obj \ + $O\StreamBinder.obj \ + $O\StreamObjects.obj \ + $O\StreamUtils.obj \ + $O\UniqBlocks.obj \ + $O\VirtThread.obj \ + +AR_OBJS = \ + $O\ApfsHandler.obj \ + $O\ApmHandler.obj \ + $O\ArHandler.obj \ + $O\ArjHandler.obj \ + $O\Base64Handler.obj \ + $O\Bz2Handler.obj \ + $O\ComHandler.obj \ + $O\CpioHandler.obj \ + $O\CramfsHandler.obj \ + $O\DeflateProps.obj \ + $O\DmgHandler.obj \ + $O\ElfHandler.obj \ + $O\ExtHandler.obj \ + $O\FatHandler.obj \ + $O\FlvHandler.obj \ + $O\GzHandler.obj \ + $O\GptHandler.obj \ + $O\HandlerCont.obj \ + $O\HfsHandler.obj \ + $O\IhexHandler.obj \ + $O\LpHandler.obj \ + $O\LzhHandler.obj \ + $O\LzmaHandler.obj \ + $O\MachoHandler.obj \ + $O\MbrHandler.obj \ + $O\MslzHandler.obj \ + $O\MubHandler.obj \ + $O\NtfsHandler.obj \ + $O\PeHandler.obj \ + $O\PpmdHandler.obj \ + $O\QcowHandler.obj \ + $O\RpmHandler.obj \ + $O\SparseHandler.obj \ + $O\SplitHandler.obj \ + $O\SquashfsHandler.obj \ + $O\SwfHandler.obj \ + $O\UefiHandler.obj \ + $O\VdiHandler.obj \ + $O\VhdHandler.obj \ + $O\VhdxHandler.obj \ + $O\VmdkHandler.obj \ + $O\XarHandler.obj \ + $O\XzHandler.obj \ + $O\ZHandler.obj \ + $O\ZstdHandler.obj \ + +AR_COMMON_OBJS = \ + $O\CoderMixer2.obj \ + $O\DummyOutStream.obj \ + $O\FindSignature.obj \ + $O\InStreamWithCRC.obj \ + $O\ItemNameUtils.obj \ + $O\MultiStream.obj \ + $O\OutStreamWithCRC.obj \ + $O\OutStreamWithSha1.obj \ + $O\HandlerOut.obj \ + $O\ParseProperties.obj \ + +7Z_OBJS = \ + $O\7zCompressionMode.obj \ + $O\7zDecode.obj \ + $O\7zEncode.obj \ + $O\7zExtract.obj \ + $O\7zFolderInStream.obj \ + $O\7zHandler.obj \ + $O\7zHandlerOut.obj \ + $O\7zHeader.obj \ + $O\7zIn.obj \ + $O\7zOut.obj \ + $O\7zProperties.obj \ + $O\7zSpecStream.obj \ + $O\7zUpdate.obj \ + $O\7zRegister.obj \ + +CAB_OBJS = \ + $O\CabBlockInStream.obj \ + $O\CabHandler.obj \ + $O\CabHeader.obj \ + $O\CabIn.obj \ + $O\CabRegister.obj \ + +CHM_OBJS = \ + $O\ChmHandler.obj \ + $O\ChmIn.obj \ + +ISO_OBJS = \ + $O\IsoHandler.obj \ + $O\IsoHeader.obj \ + $O\IsoIn.obj \ + $O\IsoRegister.obj \ + +NSIS_OBJS = \ + $O\NsisDecode.obj \ + $O\NsisHandler.obj \ + $O\NsisIn.obj \ + $O\NsisRegister.obj \ + +RAR_OBJS = \ + $O\RarHandler.obj \ + $O\Rar5Handler.obj \ + +TAR_OBJS = \ + $O\TarHandler.obj \ + $O\TarHandlerOut.obj \ + $O\TarHeader.obj \ + $O\TarIn.obj \ + $O\TarOut.obj \ + $O\TarUpdate.obj \ + $O\TarRegister.obj \ + +UDF_OBJS = \ + $O\UdfHandler.obj \ + $O\UdfIn.obj \ + +WIM_OBJS = \ + $O\WimHandler.obj \ + $O\WimHandlerOut.obj \ + $O\WimIn.obj \ + $O\WimRegister.obj \ + +ZIP_OBJS = \ + $O\ZipAddCommon.obj \ + $O\ZipHandler.obj \ + $O\ZipHandlerOut.obj \ + $O\ZipIn.obj \ + $O\ZipItem.obj \ + $O\ZipOut.obj \ + $O\ZipUpdate.obj \ + $O\ZipRegister.obj \ + +COMPRESS_OBJS = \ + $O\Bcj2Coder.obj \ + $O\Bcj2Register.obj \ + $O\BcjCoder.obj \ + $O\BcjRegister.obj \ + $O\BitlDecoder.obj \ + $O\BranchMisc.obj \ + $O\BranchRegister.obj \ + $O\ByteSwap.obj \ + $O\BZip2Crc.obj \ + $O\BZip2Decoder.obj \ + $O\BZip2Encoder.obj \ + $O\BZip2Register.obj \ + $O\CopyCoder.obj \ + $O\CopyRegister.obj \ + $O\Deflate64Register.obj \ + $O\DeflateDecoder.obj \ + $O\DeflateEncoder.obj \ + $O\DeflateRegister.obj \ + $O\DeltaFilter.obj \ + $O\ImplodeDecoder.obj \ + $O\LzfseDecoder.obj \ + $O\LzhDecoder.obj \ + $O\Lzma2Decoder.obj \ + $O\Lzma2Encoder.obj \ + $O\Lzma2Register.obj \ + $O\LzmaDecoder.obj \ + $O\LzmaEncoder.obj \ + $O\LzmaRegister.obj \ + $O\LzmsDecoder.obj \ + $O\LzOutWindow.obj \ + $O\LzxDecoder.obj \ + $O\PpmdDecoder.obj \ + $O\PpmdEncoder.obj \ + $O\PpmdRegister.obj \ + $O\PpmdZip.obj \ + $O\QuantumDecoder.obj \ + $O\Rar1Decoder.obj \ + $O\Rar2Decoder.obj \ + $O\Rar3Decoder.obj \ + $O\Rar3Vm.obj \ + $O\Rar5Decoder.obj \ + $O\RarCodecsRegister.obj \ + $O\ShrinkDecoder.obj \ + $O\XpressDecoder.obj \ + $O\XzDecoder.obj \ + $O\XzEncoder.obj \ + $O\ZlibDecoder.obj \ + $O\ZlibEncoder.obj \ + $O\ZDecoder.obj \ + $O\ZstdDecoder.obj \ + +CRYPTO_OBJS = \ + $O\7zAes.obj \ + $O\7zAesRegister.obj \ + $O\HmacSha1.obj \ + $O\HmacSha256.obj \ + $O\MyAes.obj \ + $O\MyAesReg.obj \ + $O\Pbkdf2HmacSha1.obj \ + $O\RandGen.obj \ + $O\Rar20Crypto.obj \ + $O\Rar5Aes.obj \ + $O\RarAes.obj \ + $O\WzAes.obj \ + $O\ZipCrypto.obj \ + $O\ZipStrong.obj \ + +C_OBJS = \ + $O\7zBuf2.obj \ + $O\7zStream.obj \ + $O\Alloc.obj \ + $O\Bcj2.obj \ + $O\Bcj2Enc.obj \ + $O\Blake2s.obj \ + $O\Bra.obj \ + $O\Bra86.obj \ + $O\BraIA64.obj \ + $O\BwtSort.obj \ + $O\CpuArch.obj \ + $O\Delta.obj \ + $O\HuffEnc.obj \ + $O\LzFind.obj \ + $O\LzFindMt.obj \ + $O\Lzma2Dec.obj \ + $O\Lzma2DecMt.obj \ + $O\Lzma2Enc.obj \ + $O\LzmaDec.obj \ + $O\LzmaEnc.obj \ + $O\Md5.obj \ + $O\MtCoder.obj \ + $O\MtDec.obj \ + $O\Ppmd7.obj \ + $O\Ppmd7Dec.obj \ + $O\Ppmd7aDec.obj \ + $O\Ppmd7Enc.obj \ + $O\Ppmd8.obj \ + $O\Ppmd8Dec.obj \ + $O\Ppmd8Enc.obj \ + $O\Sha3.obj \ + $O\Sha512.obj \ + $O\Sha512Opt.obj \ + $O\SwapBytes.obj \ + $O\Threads.obj \ + $O\Xxh64.obj \ + $O\Xz.obj \ + $O\XzDec.obj \ + $O\XzEnc.obj \ + $O\XzIn.obj \ + $O\ZstdDec.obj \ + +!include "../../Aes.mak" +!include "../../Crc.mak" +!include "../../Crc64.mak" +!include "../../LzFindOpt.mak" +!include "../../LzmaDec.mak" +!include "../../Sha1.mak" +!include "../../Sha256.mak" +!include "../../Sort.mak" diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zF/Arc_gcc.mak b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zF/Arc_gcc.mak new file mode 100644 index 00000000..746aaff2 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zF/Arc_gcc.mak @@ -0,0 +1,395 @@ +include ../../LzmaDec_gcc.mak + +LOCAL_FLAGS_ST = +MT_OBJS = + +ifdef SystemDrive +IS_MINGW = 1 +else +ifdef SYSTEMDRIVE +# ifdef OS +IS_MINGW = 1 +endif +endif + +ifdef ST_MODE + +LOCAL_FLAGS_ST = -DZ7_ST + +ifdef IS_MINGW +MT_OBJS = \ + $O/Threads.o \ + +endif + +else + +MT_OBJS = \ + $O/LzFindMt.o \ + $O/LzFindOpt.o \ + $O/Threads.o \ + $O/MemBlocks.o \ + $O/OutMemStream.o \ + $O/ProgressMt.o \ + $O/StreamBinder.o \ + $O/Synchronization.o \ + $O/VirtThread.o \ + +endif + + + +COMMON_OBJS = \ + $O/CRC.o \ + $O/CrcReg.o \ + $O/DynLimBuf.o \ + $O/IntToString.o \ + $O/LzFindPrepare.o \ + $O/Md5Reg.o \ + $O/MyMap.o \ + $O/MyString.o \ + $O/MyVector.o \ + $O/MyXml.o \ + $O/NewHandler.o \ + $O/Sha1Prepare.o \ + $O/Sha1Reg.o \ + $O/Sha256Prepare.o \ + $O/Sha256Reg.o \ + $O/Sha3Reg.o \ + $O/Sha512Prepare.o \ + $O/Sha512Reg.o \ + $O/StringConvert.o \ + $O/StringToInt.o \ + $O/UTFConvert.o \ + $O/Wildcard.o \ + $O/Xxh64Reg.o \ + $O/XzCrc64Init.o \ + $O/XzCrc64Reg.o \ + +WIN_OBJS = \ + $O/FileDir.o \ + $O/FileFind.o \ + $O/FileIO.o \ + $O/FileName.o \ + $O/PropVariant.o \ + $O/PropVariantConv.o \ + $O/PropVariantUtils.o \ + $O/System.o \ + $O/TimeUtils.o \ + +7ZIP_COMMON_OBJS = \ + $O/CreateCoder.o \ + $O/CWrappers.o \ + $O/InBuffer.o \ + $O/InOutTempBuffer.o \ + $O/FilterCoder.o \ + $O/LimitedStreams.o \ + $O/LockedStream.o \ + $O/MethodId.o \ + $O/MethodProps.o \ + $O/OffsetStream.o \ + $O/OutBuffer.o \ + $O/ProgressUtils.o \ + $O/PropId.o \ + $O/StreamObjects.o \ + $O/StreamUtils.o \ + $O/UniqBlocks.o \ + +AR_OBJS = \ + $O/ApfsHandler.o \ + $O/ApmHandler.o \ + $O/ArHandler.o \ + $O/ArjHandler.o \ + $O/Base64Handler.o \ + $O/Bz2Handler.o \ + $O/ComHandler.o \ + $O/CpioHandler.o \ + $O/CramfsHandler.o \ + $O/DeflateProps.o \ + $O/DmgHandler.o \ + $O/ElfHandler.o \ + $O/ExtHandler.o \ + $O/FatHandler.o \ + $O/FlvHandler.o \ + $O/GzHandler.o \ + $O/GptHandler.o \ + $O/HandlerCont.o \ + $O/HfsHandler.o \ + $O/IhexHandler.o \ + $O/LpHandler.o \ + $O/LzhHandler.o \ + $O/LzmaHandler.o \ + $O/MachoHandler.o \ + $O/MbrHandler.o \ + $O/MslzHandler.o \ + $O/MubHandler.o \ + $O/NtfsHandler.o \ + $O/PeHandler.o \ + $O/PpmdHandler.o \ + $O/QcowHandler.o \ + $O/RpmHandler.o \ + $O/SparseHandler.o \ + $O/SplitHandler.o \ + $O/SquashfsHandler.o \ + $O/SwfHandler.o \ + $O/UefiHandler.o \ + $O/VdiHandler.o \ + $O/VhdHandler.o \ + $O/VhdxHandler.o \ + $O/VmdkHandler.o \ + $O/XarHandler.o \ + $O/XzHandler.o \ + $O/ZHandler.o \ + $O/ZstdHandler.o \ + +# $O/AvbHandler.o +# $O/LvmHandler.o + +AR_COMMON_OBJS = \ + $O/CoderMixer2.o \ + $O/DummyOutStream.o \ + $O/FindSignature.o \ + $O/InStreamWithCRC.o \ + $O/ItemNameUtils.o \ + $O/MultiStream.o \ + $O/OutStreamWithCRC.o \ + $O/OutStreamWithSha1.o \ + $O/HandlerOut.o \ + $O/ParseProperties.o \ + + +7Z_OBJS = \ + $O/7zCompressionMode.o \ + $O/7zDecode.o \ + $O/7zEncode.o \ + $O/7zExtract.o \ + $O/7zFolderInStream.o \ + $O/7zHandler.o \ + $O/7zHandlerOut.o \ + $O/7zHeader.o \ + $O/7zIn.o \ + $O/7zOut.o \ + $O/7zProperties.o \ + $O/7zSpecStream.o \ + $O/7zUpdate.o \ + $O/7zRegister.o \ + +CAB_OBJS = \ + $O/CabBlockInStream.o \ + $O/CabHandler.o \ + $O/CabHeader.o \ + $O/CabIn.o \ + $O/CabRegister.o \ + +CHM_OBJS = \ + $O/ChmHandler.o \ + $O/ChmIn.o \ + +ISO_OBJS = \ + $O/IsoHandler.o \ + $O/IsoHeader.o \ + $O/IsoIn.o \ + $O/IsoRegister.o \ + +NSIS_OBJS = \ + $O/NsisDecode.o \ + $O/NsisHandler.o \ + $O/NsisIn.o \ + $O/NsisRegister.o \ + +ifndef DISABLE_RAR +RAR_OBJS = \ + $O/RarHandler.o \ + $O/Rar5Handler.o \ + +endif + + +TAR_OBJS = \ + $O/TarHandler.o \ + $O/TarHandlerOut.o \ + $O/TarHeader.o \ + $O/TarIn.o \ + $O/TarOut.o \ + $O/TarUpdate.o \ + $O/TarRegister.o \ + +UDF_OBJS = \ + $O/UdfHandler.o \ + $O/UdfIn.o \ + +WIM_OBJS = \ + $O/WimHandler.o \ + $O/WimHandlerOut.o \ + $O/WimIn.o \ + $O/WimRegister.o \ + +ZIP_OBJS = \ + $O/ZipAddCommon.o \ + $O/ZipHandler.o \ + $O/ZipHandlerOut.o \ + $O/ZipIn.o \ + $O/ZipItem.o \ + $O/ZipOut.o \ + $O/ZipUpdate.o \ + $O/ZipRegister.o \ + +COMPRESS_OBJS = \ + $O/Bcj2Coder.o \ + $O/Bcj2Register.o \ + $O/BcjCoder.o \ + $O/BcjRegister.o \ + $O/BitlDecoder.o \ + $O/BranchMisc.o \ + $O/BranchRegister.o \ + $O/ByteSwap.o \ + $O/BZip2Crc.o \ + $O/BZip2Decoder.o \ + $O/BZip2Encoder.o \ + $O/BZip2Register.o \ + $O/CopyCoder.o \ + $O/CopyRegister.o \ + $O/Deflate64Register.o \ + $O/DeflateDecoder.o \ + $O/DeflateEncoder.o \ + $O/DeflateRegister.o \ + $O/DeltaFilter.o \ + $O/ImplodeDecoder.o \ + $O/LzfseDecoder.o \ + $O/LzhDecoder.o \ + $O/Lzma2Decoder.o \ + $O/Lzma2Encoder.o \ + $O/Lzma2Register.o \ + $O/LzmaDecoder.o \ + $O/LzmaEncoder.o \ + $O/LzmaRegister.o \ + $O/LzmsDecoder.o \ + $O/LzOutWindow.o \ + $O/LzxDecoder.o \ + $O/PpmdDecoder.o \ + $O/PpmdEncoder.o \ + $O/PpmdRegister.o \ + $O/PpmdZip.o \ + $O/QuantumDecoder.o \ + $O/ShrinkDecoder.o \ + $O/XpressDecoder.o \ + $O/XzDecoder.o \ + $O/XzEncoder.o \ + $O/ZlibDecoder.o \ + $O/ZlibEncoder.o \ + $O/ZDecoder.o \ + $O/ZstdDecoder.o \ + +ifdef DISABLE_RAR +DISABLE_RAR_COMPRESS=1 +endif + +ifndef DISABLE_RAR_COMPRESS +COMPRESS_OBJS += \ + $O/Rar1Decoder.o \ + $O/Rar2Decoder.o \ + $O/Rar3Decoder.o \ + $O/Rar3Vm.o \ + $O/Rar5Decoder.o \ + $O/RarCodecsRegister.o \ + +endif + +CRYPTO_OBJS = \ + $O/7zAes.o \ + $O/7zAesRegister.o \ + $O/HmacSha1.o \ + $O/HmacSha256.o \ + $O/MyAes.o \ + $O/MyAesReg.o \ + $O/Pbkdf2HmacSha1.o \ + $O/RandGen.o \ + $O/WzAes.o \ + $O/ZipCrypto.o \ + $O/ZipStrong.o \ + +ifndef DISABLE_RAR +CRYPTO_OBJS += \ + $O/Rar20Crypto.o \ + $O/Rar5Aes.o \ + $O/RarAes.o \ + +endif + + +C_OBJS = \ + $O/7zBuf2.o \ + $O/7zCrc.o \ + $O/7zCrcOpt.o \ + $O/7zStream.o \ + $O/Aes.o \ + $O/AesOpt.o \ + $O/Alloc.o \ + $O/Bcj2.o \ + $O/Bcj2Enc.o \ + $O/Blake2s.o \ + $O/Bra.o \ + $O/Bra86.o \ + $O/BraIA64.o \ + $O/BwtSort.o \ + $O/CpuArch.o \ + $O/Delta.o \ + $O/HuffEnc.o \ + $O/LzFind.o \ + $O/Lzma2Dec.o \ + $O/Lzma2DecMt.o \ + $O/Lzma2Enc.o \ + $O/LzmaDec.o \ + $O/LzmaEnc.o \ + $O/Md5.o \ + $O/MtCoder.o \ + $O/MtDec.o \ + $O/Ppmd7.o \ + $O/Ppmd7Dec.o \ + $O/Ppmd7aDec.o \ + $O/Ppmd7Enc.o \ + $O/Ppmd8.o \ + $O/Ppmd8Dec.o \ + $O/Ppmd8Enc.o \ + $O/Sha1.o \ + $O/Sha1Opt.o \ + $O/Sha256.o \ + $O/Sha256Opt.o \ + $O/Sha3.o \ + $O/Sha512.o \ + $O/Sha512Opt.o \ + $O/Sort.o \ + $O/SwapBytes.o \ + $O/Xxh64.o \ + $O/Xz.o \ + $O/XzDec.o \ + $O/XzEnc.o \ + $O/XzIn.o \ + $O/XzCrc64.o \ + $O/XzCrc64Opt.o \ + $O/ZstdDec.o \ + +ARC_OBJS = \ + $(LZMA_DEC_OPT_OBJS) \ + $(C_OBJS) \ + $(MT_OBJS) \ + $(COMMON_OBJS) \ + $(WIN_OBJS) \ + $(7ZIP_COMMON_OBJS) \ + $(AR_OBJS) \ + $(AR_COMMON_OBJS) \ + $(7Z_OBJS) \ + $(CAB_OBJS) \ + $(CHM_OBJS) \ + $(COM_OBJS) \ + $(ISO_OBJS) \ + $(NSIS_OBJS) \ + $(RAR_OBJS) \ + $(TAR_OBJS) \ + $(UDF_OBJS) \ + $(WIM_OBJS) \ + $(ZIP_OBJS) \ + $(COMPRESS_OBJS) \ + $(CRYPTO_OBJS) \ + +# we need empty line after last line above diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zF/Format7z.dsp b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zF/Format7z.dsp new file mode 100644 index 00000000..cf91341d --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zF/Format7z.dsp @@ -0,0 +1,3426 @@ +# Microsoft Developer Studio Project File - Name="7z" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=7z - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "Format7z.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "Format7z.mak" CFG="7z - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "7z - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "7z - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "7z - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 1 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "MY7Z_EXPORTS" /YX /FD /c +# ADD CPP /nologo /Gr /MD /W4 /WX /GX /O1 /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "MY7Z_EXPORTS" /D "Z7_EXTERNAL_CODECS" /FAcs /Yu"StdAfx.h" /FD /GF /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "NDEBUG" +# ADD RSC /l 0x419 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 /out:"C:\Util\7z.dll" /opt:NOWIN98 +# SUBTRACT LINK32 /pdb:none /debug + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 1 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "MY7Z_EXPORTS" /YX /FD /GZ /c +# ADD CPP /nologo /Gr /MTd /W4 /WX /Gm /GX /ZI /Od /I "..\..\..\..\SDK" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "MY7Z_EXPORTS" /D "Z7_EXTERNAL_CODECS" /Yu"StdAfx.h" /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "_DEBUG" +# ADD RSC /l 0x419 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /out:"C:\Util\7z.dll" /pdbtype:sept /ignore:4033 +# SUBTRACT LINK32 /pdb:none + +!ENDIF + +# Begin Target + +# Name "7z - Win32 Release" +# Name "7z - Win32 Debug" +# Begin Group "Spec" + +# PROP Default_Filter "" +# Begin Group "Icons" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Archive\Icons\7z.ico +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Icons\arj.ico +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Icons\bz2.ico +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Icons\cab.ico +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Icons\cpio.ico +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Icons\deb.ico +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Icons\dmg.ico +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Icons\fat.ico +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Icons\gz.ico +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Icons\hfs.ico +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Icons\iso.ico +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Icons\lzh.ico +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Icons\lzma.ico +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Icons\ntfs.ico +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Icons\rar.ico +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Icons\rpm.ico +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Icons\split.ico +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Icons\squashfs.ico +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Icons\tar.ico +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Icons\vhd.ico +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Icons\wim.ico +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Icons\xar.ico +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Icons\xz.ico +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Icons\z.ico +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Icons\zip.ico +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Icons\zst.ico +# End Source File +# End Group +# Begin Source File + +SOURCE=..\..\Archive\Archive2.def +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\ArchiveExports.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\CodecExports.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\DllExports2.cpp +# End Source File +# Begin Source File + +SOURCE=.\resource.rc +# End Source File +# Begin Source File + +SOURCE=.\StdAfx.cpp +# ADD CPP /Yc"StdAfx.h" +# End Source File +# Begin Source File + +SOURCE=.\StdAfx.h +# End Source File +# End Group +# Begin Group "Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\Common\AutoPtr.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Common.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Common0.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\ComTry.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\CRC.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\CrcReg.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Defs.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\DynamicBuffer.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\DynLimBuf.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\DynLimBuf.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\IntToString.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\IntToString.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\LzFindPrepare.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Md5Reg.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyBuffer.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyBuffer2.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyCom.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyException.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyInitGuid.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyLinux.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyMap.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyMap.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyString.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyString.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyTypes.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyUnknown.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyVector.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyVector.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyWindows.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyWindows.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyXml.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyXml.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\NewHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\NewHandler.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Sha1Prepare.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Sha1Reg.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Sha256Prepare.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Sha256Reg.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Sha3Reg.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Sha512Prepare.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Sha512Reg.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StringConvert.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StringConvert.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StringToInt.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StringToInt.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\UTFConvert.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\UTFConvert.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Wildcard.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Wildcard.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Xxh64Reg.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\XzCrc64Init.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\XzCrc64Reg.cpp +# End Source File +# End Group +# Begin Group "Compress" + +# PROP Default_Filter "" +# Begin Group "Bit Coder" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Compress\BitlDecoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BitlDecoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BitlEncoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BitmDecoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BitmEncoder.h +# End Source File +# End Group +# Begin Group "Rar Compress" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Compress\Rar1Decoder.cpp + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Rar1Decoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Rar2Decoder.cpp + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Rar2Decoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Rar3Decoder.cpp + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Rar3Decoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Rar3Vm.cpp + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Rar3Vm.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Rar5Decoder.cpp + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Rar5Decoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\RarCodecsRegister.cpp + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +!ENDIF + +# End Source File +# End Group +# Begin Group "BZip2 Compress" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Compress\BZip2Const.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BZip2Crc.cpp + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BZip2Crc.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BZip2Decoder.cpp + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BZip2Decoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BZip2Encoder.cpp + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BZip2Encoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BZip2Register.cpp + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Mtf8.h +# End Source File +# End Group +# Begin Group "Zip Compress" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Compress\Deflate64Register.cpp + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\DeflateConst.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\DeflateDecoder.cpp + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\DeflateDecoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\DeflateEncoder.cpp + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\DeflateEncoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\DeflateRegister.cpp + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\ImplodeDecoder.cpp + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\ImplodeDecoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\PpmdZip.cpp + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\PpmdZip.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\ShrinkDecoder.cpp + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\ShrinkDecoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\ZlibDecoder.cpp + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\ZlibDecoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\ZlibEncoder.cpp + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\ZlibEncoder.h +# End Source File +# End Group +# Begin Group "7z Compress" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Compress\Bcj2Coder.cpp + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Bcj2Coder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Bcj2Register.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BcjCoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BcjCoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BcjRegister.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BranchMisc.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BranchMisc.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BranchRegister.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\ByteSwap.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\CopyCoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\CopyCoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\CopyRegister.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\DeltaFilter.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Lzma2Decoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Lzma2Decoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Lzma2Encoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Lzma2Encoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Lzma2Register.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\LzmaDecoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\LzmaDecoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\LzmaEncoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\LzmaEncoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\LzmaRegister.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\PpmdDecoder.cpp + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\PpmdDecoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\PpmdEncoder.cpp + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\PpmdEncoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\PpmdRegister.cpp +# End Source File +# End Group +# Begin Group "Cab Compress" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Compress\Lzx.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\LzxDecoder.cpp + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\LzxDecoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\QuantumDecoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\QuantumDecoder.h +# End Source File +# End Group +# Begin Source File + +SOURCE=..\..\Compress\HuffmanDecoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\LzfseDecoder.cpp + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\LzfseDecoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\LzhDecoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\LzhDecoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\LzmsDecoder.cpp + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\LzmsDecoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\LzOutWindow.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\LzOutWindow.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\XpressDecoder.cpp + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\XpressDecoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\XzDecoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\XzDecoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\XzEncoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\XzEncoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\ZDecoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\ZDecoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\ZstdDecoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\ZstdDecoder.h +# End Source File +# End Group +# Begin Group "Crypto" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Crypto\7zAes.cpp + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\7zAes.h +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\7zAesRegister.cpp + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\HmacSha1.cpp + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\HmacSha1.h +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\HmacSha256.cpp + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\HmacSha256.h +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\MyAes.cpp + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\MyAes.h +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\MyAesReg.cpp + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\Pbkdf2HmacSha1.cpp + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\Pbkdf2HmacSha1.h +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\RandGen.cpp + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\RandGen.h +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\Rar20Crypto.cpp + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\Rar20Crypto.h +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\Rar5Aes.cpp + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\Rar5Aes.h +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\RarAes.cpp + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\RarAes.h +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\Sha1Cls.h +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\WzAes.cpp + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\WzAes.h +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\ZipCrypto.cpp + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\ZipCrypto.h +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\ZipStrong.cpp + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\ZipStrong.h +# End Source File +# End Group +# Begin Group "7zip Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Common\CreateCoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\CreateCoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\CWrappers.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\CWrappers.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FilterCoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FilterCoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\InBuffer.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\InBuffer.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\InOutTempBuffer.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\InOutTempBuffer.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\LimitedStreams.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\LimitedStreams.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\MemBlocks.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\MemBlocks.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\MethodId.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\MethodId.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\MethodProps.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\MethodProps.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\OffsetStream.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\OffsetStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\OutBuffer.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\OutBuffer.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\OutMemStream.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\OutMemStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\ProgressMt.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\ProgressMt.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\ProgressUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\ProgressUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\PropId.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\RegisterArc.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\RegisterCodec.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamBinder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamBinder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamObjects.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamObjects.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\UniqBlocks.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\UniqBlocks.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\VirtThread.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\VirtThread.h +# End Source File +# End Group +# Begin Group "C" + +# PROP Default_Filter "" +# Begin Group "xz" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\..\C\Xz.c + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /W4 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +# ADD CPP /W4 /WX +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Xz.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\XzCrc64.c + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /W4 /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +# ADD CPP /W4 /WX +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\XzCrc64.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\XzCrc64Opt.c + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\XzDec.c + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /W4 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +# ADD CPP /W4 /WX +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\XzEnc.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\XzEnc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\XzIn.c + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /W4 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +# ADD CPP /W4 /WX +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# End Group +# Begin Source File + +SOURCE=..\..\..\..\C\7zBuf.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zBuf2.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zCrc.c + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zCrc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zStream.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zTypes.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zVersion.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zWindows.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Aes.c + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Aes.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Alloc.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Alloc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Bcj2.c + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Bcj2.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Bcj2Enc.c + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Blake2.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Blake2s.c + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Bra.c + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Bra.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Bra86.c + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\BraIA64.c + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\BwtSort.c + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\BwtSort.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Compiler.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\CpuArch.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\CpuArch.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Delta.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Delta.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\HuffEnc.c + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\HuffEnc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzFind.c + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzFind.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzFindMt.c + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzFindMt.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzFindOpt.c + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzHash.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Lzma2Dec.c + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Lzma2Dec.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Lzma2DecMt.c + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Lzma2DecMt.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Lzma2Enc.c + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Lzma2Enc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzmaDec.c + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzmaDec.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzmaEnc.c + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzmaEnc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Md5.c + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Md5.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\MtCoder.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\MtCoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\MtDec.c + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\MtDec.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Ppmd.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Ppmd7.c + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Ppmd7.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Ppmd7aDec.c + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Ppmd7Dec.c + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Ppmd7Enc.c + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Ppmd8.c + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Ppmd8.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Ppmd8Dec.c + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Ppmd8Enc.c + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Precomp.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\RotateDefs.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Sha1.c + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Sha1.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Sha256.c + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Sha256.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Sha3.c + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Sha3.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Sha512.c + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Sha512.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Sha512Opt.c + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Sort.c + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Sort.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\SwapBytes.c + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\SwapBytes.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Threads.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Threads.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Xxh64.c + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Xxh64.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\ZstdDec.c + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\ZstdDec.h +# End Source File +# End Group +# Begin Group "Archive" + +# PROP Default_Filter "" +# Begin Group "7z" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Archive\7z\7zCompressionMode.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zCompressionMode.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zDecode.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zDecode.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zEncode.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zEncode.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zExtract.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zFolderInStream.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zFolderInStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zHandler.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zHandlerOut.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zHeader.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zHeader.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zIn.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zIn.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zItem.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zOut.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zOut.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zProperties.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zProperties.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zRegister.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zSpecStream.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zSpecStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zUpdate.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zUpdate.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zUpdateItem.h +# End Source File +# End Group +# Begin Group "Rar" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Archive\Rar\Rar5Handler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Rar\Rar5Handler.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Rar\RarHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Rar\RarHandler.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Rar\RarHeader.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Rar\RarItem.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Rar\RarVol.h +# End Source File +# End Group +# Begin Group "Cab" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Archive\Cab\CabBlockInStream.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Cab\CabBlockInStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Cab\CabHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Cab\CabHandler.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Cab\CabHeader.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Cab\CabHeader.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Cab\CabIn.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Cab\CabIn.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Cab\CabItem.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Cab\CabRegister.cpp +# End Source File +# End Group +# Begin Group "Chm" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Archive\Chm\ChmHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Chm\ChmHandler.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Chm\ChmIn.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Chm\ChmIn.h +# End Source File +# End Group +# Begin Group "Archive common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Archive\Common\CoderMixer2.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\CoderMixer2.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\DummyOutStream.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\DummyOutStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\FindSignature.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\FindSignature.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\HandlerOut.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\HandlerOut.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\InStreamWithCRC.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\InStreamWithCRC.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\ItemNameUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\ItemNameUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\MultiStream.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\MultiStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\OutStreamWithCRC.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\OutStreamWithCRC.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\OutStreamWithSha1.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\OutStreamWithSha1.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\ParseProperties.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\ParseProperties.h +# End Source File +# End Group +# Begin Group "Iso" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Archive\Iso\IsoHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Iso\IsoHandler.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Iso\IsoHeader.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Iso\IsoHeader.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Iso\IsoIn.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Iso\IsoIn.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Iso\IsoItem.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Iso\IsoRegister.cpp +# End Source File +# End Group +# Begin Group "Nsis" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Archive\Nsis\NsisDecode.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Nsis\NsisDecode.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Nsis\NsisHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Nsis\NsisHandler.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Nsis\NsisIn.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Nsis\NsisIn.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Nsis\NsisRegister.cpp +# End Source File +# End Group +# Begin Group "Tar" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Archive\Tar\TarHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Tar\TarHandler.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Tar\TarHandlerOut.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Tar\TarHeader.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Tar\TarHeader.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Tar\TarIn.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Tar\TarIn.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Tar\TarItem.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Tar\TarOut.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Tar\TarOut.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Tar\TarRegister.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Tar\TarUpdate.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Tar\TarUpdate.h +# End Source File +# End Group +# Begin Group "Zip" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Archive\Zip\ZipAddCommon.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Zip\ZipAddCommon.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Zip\ZipCompressionMode.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Zip\ZipHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Zip\ZipHandler.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Zip\ZipHandlerOut.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Zip\ZipHeader.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Zip\ZipIn.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Zip\ZipIn.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Zip\ZipItem.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Zip\ZipItem.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Zip\ZipOut.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Zip\ZipOut.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Zip\ZipRegister.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Zip\ZipUpdate.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Zip\ZipUpdate.h +# End Source File +# End Group +# Begin Group "Wim" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Archive\Wim\WimHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Wim\WimHandler.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Wim\WimHandlerOut.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Wim\WimIn.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Wim\WimIn.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Wim\WimRegister.cpp +# End Source File +# End Group +# Begin Group "Udf" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Archive\Udf\UdfHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Udf\UdfHandler.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Udf\UdfIn.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Udf\UdfIn.h +# End Source File +# End Group +# Begin Source File + +SOURCE=..\..\Archive\ApfsHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\ApmHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\ArHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\ArjHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Base64Handler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Bz2Handler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\ComHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\CpioHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\CramfsHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\DeflateProps.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\DeflateProps.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\DmgHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\ElfHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\ExtHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\FatHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\FlvHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\GptHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\GzHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\HandlerCont.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\HandlerCont.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\HfsHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\HfsHandler.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\IArchive.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\IhexHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\LpHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\LzhHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\LzmaHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\MachoHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\MbrHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\MslzHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\MubHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\NtfsHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\PeHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\PpmdHandler.cpp + +!IF "$(CFG)" == "7z - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\QcowHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\RpmHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\SparseHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\SplitHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\SquashfsHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\SwfHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\UefiHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\VdiHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\VhdHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\VhdxHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\VmdkHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\XarHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\XzHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\XzHandler.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\ZHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\ZstdHandler.cpp +# End Source File +# End Group +# Begin Group "7zip" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\ICoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\IDecl.h +# End Source File +# Begin Source File + +SOURCE=..\..\IPassword.h +# End Source File +# Begin Source File + +SOURCE=..\..\IProgress.h +# End Source File +# Begin Source File + +SOURCE=..\..\IStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\MyVersion.h +# End Source File +# Begin Source File + +SOURCE=..\..\PropID.h +# End Source File +# End Group +# Begin Group "Windows" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\Windows\Defs.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileDir.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileDir.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileFind.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileFind.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileIO.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileIO.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileName.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileName.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Handle.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\NtCheck.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\PropVariant.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\PropVariant.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\PropVariantConv.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\PropVariantConv.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\PropVariantUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\PropVariantUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Synchronization.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Synchronization.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\System.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\System.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Thread.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\TimeUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\TimeUtils.h +# End Source File +# End Group +# Begin Group "Asm" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\..\Asm\x86\7zAsm.asm +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\Asm\x86\7zCrcOpt.asm + +!IF "$(CFG)" == "7z - Win32 Release" + +# Begin Custom Build +OutDir=.\Release +InputPath=..\..\..\..\Asm\x86\7zCrcOpt.asm +InputName=7zCrcOpt + +"$(OutDir)\$(InputName).obj" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + ml.exe -c -Fo$(OutDir)\$(InputName).obj $(InputPath) + +# End Custom Build + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +# Begin Custom Build +OutDir=.\Debug +InputPath=..\..\..\..\Asm\x86\7zCrcOpt.asm +InputName=7zCrcOpt + +"$(OutDir)\$(InputName).obj" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + ml.exe -c -omf -Fo$(OutDir)\$(InputName).obj $(InputPath) + +# End Custom Build + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\Asm\x86\AesOpt.asm + +!IF "$(CFG)" == "7z - Win32 Release" + +# PROP Ignore_Default_Tool 1 +# Begin Custom Build +OutDir=.\Release +InputPath=..\..\..\..\Asm\x86\AesOpt.asm +InputName=AesOpt + +"$(OutDir)\$(InputName).obj" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + ml.exe -c -Fo$(OutDir)\$(InputName).obj $(InputPath) + +# End Custom Build + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +# PROP Ignore_Default_Tool 1 +# Begin Custom Build +OutDir=.\Debug +InputPath=..\..\..\..\Asm\x86\AesOpt.asm +InputName=AesOpt + +"$(OutDir)\$(InputName).obj" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + ml.exe -c -omf -WX -W3 -Fo$(OutDir)\$(InputName).obj $(InputPath) + +# End Custom Build + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\Asm\x86\Sha1Opt.asm + +!IF "$(CFG)" == "7z - Win32 Release" + +# Begin Custom Build +OutDir=.\Release +InputPath=..\..\..\..\Asm\x86\Sha1Opt.asm +InputName=Sha1Opt + +"$(OutDir)\$(InputName).obj" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + ml.exe -c -Fo$(OutDir)\$(InputName).obj $(InputPath) + +# End Custom Build + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +# Begin Custom Build +OutDir=.\Debug +InputPath=..\..\..\..\Asm\x86\Sha1Opt.asm +InputName=Sha1Opt + +"$(OutDir)\$(InputName).obj" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + ml.exe -c -omf -Fo$(OutDir)\$(InputName).obj $(InputPath) + +# End Custom Build + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\Asm\x86\Sha256Opt.asm + +!IF "$(CFG)" == "7z - Win32 Release" + +# Begin Custom Build +OutDir=.\Release +InputPath=..\..\..\..\Asm\x86\Sha256Opt.asm +InputName=Sha256Opt + +"$(OutDir)\$(InputName).obj" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + ml.exe -c -Fo$(OutDir)\$(InputName).obj $(InputPath) + +# End Custom Build + +!ELSEIF "$(CFG)" == "7z - Win32 Debug" + +# Begin Custom Build +OutDir=.\Debug +InputPath=..\..\..\..\Asm\x86\Sha256Opt.asm +InputName=Sha256Opt + +"$(OutDir)\$(InputName).obj" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + ml.exe -c -omf -Fo$(OutDir)\$(InputName).obj $(InputPath) + +# End Custom Build + +!ENDIF + +# End Source File +# End Group +# End Target +# End Project diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zF/Format7z.dsw b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zF/Format7z.dsw new file mode 100644 index 00000000..324dab1f --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zF/Format7z.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "7z"=.\Format7z.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zF/StdAfx.cpp b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zF/StdAfx.cpp new file mode 100644 index 00000000..d0feea85 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zF/StdAfx.cpp @@ -0,0 +1,3 @@ +// StdAfx.cpp + +#include "StdAfx.h" diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zF/StdAfx.h b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zF/StdAfx.h new file mode 100644 index 00000000..035267cc --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zF/StdAfx.h @@ -0,0 +1,11 @@ +// StdAfx.h + +#ifndef ZIP7_INC_STDAFX_H +#define ZIP7_INC_STDAFX_H + +#if defined(_MSC_VER) && _MSC_VER >= 1800 +#pragma warning(disable : 4464) // relative include path contains '..' +#endif +#include "../../../Common/Common.h" + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zF/makefile b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zF/makefile new file mode 100644 index 00000000..170d35b3 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zF/makefile @@ -0,0 +1,20 @@ +PROG = 7z.dll +# USE_C_LZFINDOPT = 1 +DEF_FILE = ../../Archive/Archive2.def +CFLAGS = $(CFLAGS) \ + -DZ7_EXTERNAL_CODECS \ + +!IFNDEF UNDER_CE +!ENDIF + +!include "Arc.mak" + +COMPRESS_OBJS = $(COMPRESS_OBJS) \ + $O\CodecExports.obj \ + +AR_OBJS = $(AR_OBJS) \ + $O\ArchiveExports.obj \ + $O\DllExports2.obj \ + + +!include "../../7zip.mak" diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zF/makefile.gcc b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zF/makefile.gcc new file mode 100644 index 00000000..dd2e9149 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zF/makefile.gcc @@ -0,0 +1,55 @@ +PROG = 7z +DEF_FILE = ../../Archive/Archive2.def + +# IS_X64 = 1 +# USE_ASM = 1 +# ST_MODE = 1 + +include Arc_gcc.mak + +ifdef SystemDrive +IS_MINGW = 1 +else +ifdef SYSTEMDRIVE +# ifdef OS +IS_MINGW = 1 +endif +endif + +LOCAL_FLAGS_SYS = + +ifdef IS_MINGW + +LOCAL_FLAGS_SYS = \ + $(LOCAL_FLAGS_ST) \ + +SYS_OBJS = \ + $O/resource.o \ + +else + +SYS_OBJS = \ + $O/MyWindows.o \ + +endif + +LOCAL_FLAGS = \ + -DZ7_EXTERNAL_CODECS \ + $(LOCAL_FLAGS_SYS) \ + $(LOCAL_FLAGS_ST) \ + + +COMPRESS_OBJS_2 = \ + $O/CodecExports.o \ + +AR_OBJS_2 = \ + $O/ArchiveExports.o \ + $O/DllExports2.o \ + +OBJS = \ + $(ARC_OBJS) \ + $(AR_OBJS_2) \ + $(COMPRESS_OBJS_2) \ + $(SYS_OBJS) \ + +include ../../7zip_gcc.mak diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zF/resource.rc b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zF/resource.rc new file mode 100644 index 00000000..f2950c01 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zF/resource.rc @@ -0,0 +1,39 @@ +#include "../../MyVersionInfo.rc" + +MY_VERSION_INFO_DLL("7z Plugin" , "7z") + + +0 ICON "../../Archive/Icons/7z.ico" +1 ICON "../../Archive/Icons/zip.ico" +2 ICON "../../Archive/Icons/bz2.ico" +3 ICON "../../Archive/Icons/rar.ico" +4 ICON "../../Archive/Icons/arj.ico" +5 ICON "../../Archive/Icons/z.ico" +6 ICON "../../Archive/Icons/lzh.ico" +7 ICON "../../Archive/Icons/cab.ico" +8 ICON "../../Archive/Icons/iso.ico" +9 ICON "../../Archive/Icons/split.ico" +10 ICON "../../Archive/Icons/rpm.ico" +11 ICON "../../Archive/Icons/deb.ico" +12 ICON "../../Archive/Icons/cpio.ico" +13 ICON "../../Archive/Icons/tar.ico" +14 ICON "../../Archive/Icons/gz.ico" +15 ICON "../../Archive/Icons/wim.ico" +16 ICON "../../Archive/Icons/lzma.ico" +17 ICON "../../Archive/Icons/dmg.ico" +18 ICON "../../Archive/Icons/hfs.ico" +19 ICON "../../Archive/Icons/xar.ico" +20 ICON "../../Archive/Icons/vhd.ico" +21 ICON "../../Archive/Icons/fat.ico" +22 ICON "../../Archive/Icons/ntfs.ico" +23 ICON "../../Archive/Icons/xz.ico" +24 ICON "../../Archive/Icons/squashfs.ico" +25 ICON "../../Archive/Icons/apfs.ico" +26 ICON "../../Archive/Icons/zst.ico" + + + +STRINGTABLE +BEGIN + 100 "7z:0 zip:1 rar:3 001:9 cab:7 iso:8 xz:23 txz:23 lzma:16 tar:13 cpio:12 bz2:2 bzip2:2 tbz2:2 tbz:2 gz:14 gzip:14 tgz:14 tpz:14 zst:26 tzst:26 z:5 taz:5 lzh:6 lha:6 rpm:10 deb:11 arj:4 vhd:20 vhdx:20 wim:15 swm:15 esd:15 fat:21 ntfs:22 dmg:17 hfs:18 xar:19 squashfs:24 apfs:25" +END diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zR/StdAfx.cpp b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zR/StdAfx.cpp new file mode 100644 index 00000000..d0feea85 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zR/StdAfx.cpp @@ -0,0 +1,3 @@ +// StdAfx.cpp + +#include "StdAfx.h" diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zR/StdAfx.h b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zR/StdAfx.h new file mode 100644 index 00000000..035267cc --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zR/StdAfx.h @@ -0,0 +1,11 @@ +// StdAfx.h + +#ifndef ZIP7_INC_STDAFX_H +#define ZIP7_INC_STDAFX_H + +#if defined(_MSC_VER) && _MSC_VER >= 1800 +#pragma warning(disable : 4464) // relative include path contains '..' +#endif +#include "../../../Common/Common.h" + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zR/makefile b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zR/makefile new file mode 100644 index 00000000..326d043d --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zR/makefile @@ -0,0 +1,121 @@ +PROG = 7zra.dll +DEF_FILE = ../../Archive/Archive2.def +CFLAGS = $(CFLAGS) \ + -DZ7_NO_CRYPTO + +COMMON_OBJS = \ + $O\CRC.obj \ + $O\CrcReg.obj \ + $O\IntToString.obj \ + $O\LzFindPrepare.obj \ + $O\NewHandler.obj \ + $O\MyString.obj \ + $O\StringConvert.obj \ + $O\StringToInt.obj \ + $O\MyVector.obj \ + $O\Wildcard.obj \ + +WIN_OBJS = \ + $O\FileDir.obj \ + $O\FileFind.obj \ + $O\FileIO.obj \ + $O\FileName.obj \ + $O\PropVariant.obj \ + $O\Synchronization.obj \ + $O\System.obj \ + $O\TimeUtils.obj \ + +7ZIP_COMMON_OBJS = \ + $O\CreateCoder.obj \ + $O\CWrappers.obj \ + $O\InBuffer.obj \ + $O\InOutTempBuffer.obj \ + $O\FilterCoder.obj \ + $O\LimitedStreams.obj \ + $O\MethodId.obj \ + $O\MethodProps.obj \ + $O\OutBuffer.obj \ + $O\ProgressUtils.obj \ + $O\PropId.obj \ + $O\StreamBinder.obj \ + $O\StreamObjects.obj \ + $O\StreamUtils.obj \ + $O\UniqBlocks.obj \ + $O\VirtThread.obj \ + +AR_OBJS = \ + $O\ArchiveExports.obj \ + $O\DllExports2.obj \ + +AR_COMMON_OBJS = \ + $O\CoderMixer2.obj \ + $O\HandlerOut.obj \ + $O\InStreamWithCRC.obj \ + $O\ItemNameUtils.obj \ + $O\OutStreamWithCRC.obj \ + $O\ParseProperties.obj \ + + +7Z_OBJS = \ + $O\7zCompressionMode.obj \ + $O\7zDecode.obj \ + $O\7zEncode.obj \ + $O\7zExtract.obj \ + $O\7zFolderInStream.obj \ + $O\7zHandler.obj \ + $O\7zHandlerOut.obj \ + $O\7zHeader.obj \ + $O\7zIn.obj \ + $O\7zOut.obj \ + $O\7zProperties.obj \ + $O\7zSpecStream.obj \ + $O\7zUpdate.obj \ + $O\7zRegister.obj \ + + +COMPRESS_OBJS = \ + $O\CodecExports.obj \ + $O\Bcj2Coder.obj \ + $O\Bcj2Register.obj \ + $O\BcjCoder.obj \ + $O\BcjRegister.obj \ + $O\BranchMisc.obj \ + $O\BranchRegister.obj \ + $O\ByteSwap.obj \ + $O\CopyCoder.obj \ + $O\CopyRegister.obj \ + $O\DeltaFilter.obj \ + $O\Lzma2Decoder.obj \ + $O\Lzma2Encoder.obj \ + $O\Lzma2Register.obj \ + $O\LzmaDecoder.obj \ + $O\LzmaEncoder.obj \ + $O\LzmaRegister.obj \ + +C_OBJS = \ + $O\7zStream.obj \ + $O\Alloc.obj \ + $O\Bcj2.obj \ + $O\Bcj2Enc.obj \ + $O\Bra.obj \ + $O\Bra86.obj \ + $O\BraIA64.obj \ + $O\CpuArch.obj \ + $O\Delta.obj \ + $O\LzFind.obj \ + $O\LzFindMt.obj \ + $O\Lzma2Dec.obj \ + $O\Lzma2DecMt.obj \ + $O\Lzma2Enc.obj \ + $O\LzmaDec.obj \ + $O\LzmaEnc.obj \ + $O\MtCoder.obj \ + $O\MtDec.obj \ + $O\SwapBytes.obj \ + $O\Threads.obj \ + +!include "../../Crc.mak" +!include "../../LzFindOpt.mak" +!include "../../LzmaDec.mak" + +!include "../../7zip.mak" diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zR/resource.rc b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zR/resource.rc new file mode 100644 index 00000000..a8baa596 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/Format7zR/resource.rc @@ -0,0 +1,5 @@ +#include "../../../../C/7zVersion.rc" + +MY_VERSION_INFO_DLL("7z Reduced Standalone Plugin", "7zr") + +101 ICON "../../Archive/Icons/7z.ico" diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/LzmaCon/LzmaAlone.cpp b/src/plugins/7zip/7za/cpp/7zip/Bundles/LzmaCon/LzmaAlone.cpp new file mode 100644 index 00000000..e43e8b1a --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/LzmaCon/LzmaAlone.cpp @@ -0,0 +1,817 @@ + // LzmaAlone.cpp + +#include "StdAfx.h" + +// #include + +#if (defined(_WIN32) || defined(OS2) || defined(MSDOS)) && !defined(UNDER_CE) +#include +#include +#define MY_SET_BINARY_MODE(file) _setmode(_fileno(file), O_BINARY) +#else +#define MY_SET_BINARY_MODE(file) +#endif + +#include "../../../../C/CpuArch.h" +#include "../../../../C/7zVersion.h" +#include "../../../../C/Alloc.h" +#include "../../../../C/Lzma86.h" + +#include "../../../Common/MyWindows.h" +#include "../../../Common/MyInitGuid.h" + +#include "../../../Windows/NtCheck.h" + +#ifndef Z7_ST +#include "../../../Windows/System.h" +#endif + +#include "../../../Common/IntToString.h" +#include "../../../Common/CommandLineParser.h" +#include "../../../Common/StringConvert.h" +#include "../../../Common/StringToInt.h" + +#include "../../Common/FileStreams.h" +#include "../../Common/StreamUtils.h" + +#include "../../Compress/LzmaDecoder.h" +#include "../../Compress/LzmaEncoder.h" + +#include "../../UI/Console/BenchCon.h" +#include "../../UI/Console/ConsoleClose.h" + +extern +bool g_LargePagesMode; +bool g_LargePagesMode = false; + +using namespace NCommandLineParser; + +static const unsigned kDictSizeLog = 24; + +#define kCopyrightString "\nLZMA " MY_VERSION_CPU " : " MY_COPYRIGHT_DATE "\n\n" + +static const char * const kHelpString = + "Usage: lzma [inputFile] [outputFile] [...]\n" + "\n" + "\n" + " e : Encode file\n" + " d : Decode file\n" + " b : Benchmark\n" + "\n" + " -a{N} : set compression mode : [0, 1] : default = 1 (max)\n" + " -d{N} : set dictionary size : [12, 31] : default = 24 (16 MiB)\n" + " -fb{N} : set number of fast bytes : [5, 273] : default = 128\n" + " -mc{N} : set number of cycles for match finder\n" + " -lc{N} : set number of literal context bits : [0, 8] : default = 3\n" + " -lp{N} : set number of literal pos bits : [0, 4] : default = 0\n" + " -pb{N} : set number of pos bits : [0, 4] : default = 2\n" + " -mf{M} : set match finder: [hc4, hc5, bt2, bt3, bt4, bt5] : default = bt4\n" + " -mt{N} : set number of CPU threads\n" + " -eos : write end of stream marker\n" + " -si : read data from stdin\n" + " -so : write data to stdout\n"; + + +static const char * const kCantAllocate = "Cannot allocate memory"; +static const char * const kReadError = "Read error"; +static const char * const kWriteError = "Write error"; + + +namespace NKey { +enum Enum +{ + kHelp1 = 0, + kHelp2, + kMethod, + kLevel, + kAlgo, + kDict, + kFb, + kMc, + kLc, + kLp, + kPb, + kMatchFinder, + kMultiThread, + kEOS, + kStdIn, + kStdOut, + kFilter86 +}; +} + +#define SWFRM_3(t, mu, mi) t, mu, mi, NULL + +#define SWFRM_1(t) SWFRM_3(t, false, 0) +#define SWFRM_SIMPLE SWFRM_1(NSwitchType::kSimple) +#define SWFRM_STRING SWFRM_1(NSwitchType::kString) + +#define SWFRM_STRING_SINGL(mi) SWFRM_3(NSwitchType::kString, false, mi) + +static const CSwitchForm kSwitchForms[] = +{ + { "?", SWFRM_SIMPLE }, + { "H", SWFRM_SIMPLE }, + { "MM", SWFRM_STRING_SINGL(1) }, + { "X", SWFRM_STRING_SINGL(1) }, + { "A", SWFRM_STRING_SINGL(1) }, + { "D", SWFRM_STRING_SINGL(1) }, + { "FB", SWFRM_STRING_SINGL(1) }, + { "MC", SWFRM_STRING_SINGL(1) }, + { "LC", SWFRM_STRING_SINGL(1) }, + { "LP", SWFRM_STRING_SINGL(1) }, + { "PB", SWFRM_STRING_SINGL(1) }, + { "MF", SWFRM_STRING_SINGL(1) }, + { "MT", SWFRM_STRING }, + { "EOS", SWFRM_SIMPLE }, + { "SI", SWFRM_SIMPLE }, + { "SO", SWFRM_SIMPLE }, + { "F86", NSwitchType::kChar, false, 0, "+" } +}; + + +static void Convert_UString_to_AString(const UString &s, AString &temp) +{ + int codePage = CP_OEMCP; + /* + int g_CodePage = -1; + int codePage = g_CodePage; + if (codePage == -1) + codePage = CP_OEMCP; + if (codePage == CP_UTF8) + ConvertUnicodeToUTF8(s, temp); + else + */ + UnicodeStringToMultiByte2(temp, s, (UINT)codePage); +} + +static void PrintErr(const char *s) +{ + fputs(s, stderr); +} + +static void PrintErr_LF(const char *s) +{ + PrintErr(s); + fputc('\n', stderr); +} + + +static void PrintError(const char *s) +{ + PrintErr("\nERROR: "); + PrintErr_LF(s); +} + +static void PrintError2(const char *s1, const UString &s2) +{ + PrintError(s1); + AString a; + Convert_UString_to_AString(s2, a); + PrintErr_LF(a); +} + +static void PrintError_int(const char *s, int code) +{ + PrintError(s); + char temp[32]; + ConvertInt64ToString(code, temp); + PrintErr("Error code = "); + PrintErr_LF(temp); +} + + + +static void Print(const char *s) +{ + fputs(s, stdout); +} + +static void Print_UInt64(UInt64 v) +{ + char temp[32]; + ConvertUInt64ToString(v, temp); + Print(temp); +} + +static void Print_MB(UInt64 v) +{ + Print_UInt64(v); + Print(" MiB"); +} + +static void Print_Size(const char *s, UInt64 v) +{ + Print(s); + Print_UInt64(v); + Print(" ("); + Print_MB(v >> 20); + Print(")\n"); +} + +static void PrintTitle() +{ + Print(kCopyrightString); +} + +static void PrintHelp() +{ + PrintTitle(); + Print(kHelpString); +} + + +Z7_CLASS_IMP_COM_1( + CProgressPrint, + ICompressProgressInfo +) + UInt64 _size1; + UInt64 _size2; +public: + CProgressPrint(): _size1(0), _size2(0) {} + + void ClosePrint(); +}; + +#define BACK_STR \ +"\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b" +static const char * const kBackSpaces = +BACK_STR +" " +BACK_STR; + + +void CProgressPrint::ClosePrint() +{ + Print(kBackSpaces); +} + +Z7_COM7F_IMF(CProgressPrint::SetRatioInfo(const UInt64 *inSize, const UInt64 *outSize)) +{ + if (NConsoleClose::TestBreakSignal()) + return E_ABORT; + if (inSize) + { + UInt64 v1 = *inSize >> 20; + UInt64 v2 = _size2; + if (outSize) + v2 = *outSize >> 20; + if (v1 != _size1 || v2 != _size2) + { + _size1 = v1; + _size2 = v2; + ClosePrint(); + Print_MB(_size1); + Print(" -> "); + Print_MB(_size2); + } + } + return S_OK; +} + + +Z7_ATTR_NORETURN +static void IncorrectCommand() +{ + throw "Incorrect command"; +} + +static UInt32 GetNumber(const wchar_t *s) +{ + const wchar_t *end; + UInt32 v = ConvertStringToUInt32(s, &end); + if (*end != 0) + IncorrectCommand(); + return v; +} + +static void ParseUInt32(const CParser &parser, unsigned index, UInt32 &res) +{ + if (parser[index].ThereIs) + res = GetNumber(parser[index].PostStrings[0]); +} + + +static int Error_HRESULT(const char *s, HRESULT res) +{ + if (res == E_ABORT) + { + Print("\n\nBreak signaled\n"); + return 255; + } + + PrintError(s); + + if (res == E_OUTOFMEMORY) + { + PrintErr_LF(kCantAllocate); + return 8; + } + if (res == E_INVALIDARG) + { + PrintErr_LF("Ununsupported parameter"); + } + else + { + char temp[32]; + ConvertUInt32ToHex((UInt32)res, temp); + PrintErr("Error code = 0x"); + PrintErr_LF(temp); + } + return 1; +} + +#if defined(_UNICODE) && !defined(_WIN64) && !defined(UNDER_CE) +#define NT_CHECK_FAIL_ACTION PrintError("Unsupported Windows version"); return 1; +#endif + +static void AddProp(CObjectVector &props2, const char *name, const wchar_t *val) +{ + CProperty &prop = props2.AddNew(); + prop.Name = name; + prop.Value = val; +} + +static int main2(int numArgs, const char *args[]) +{ + NT_CHECK + + if (numArgs == 1) + { + PrintHelp(); + return 0; + } + + /* + bool unsupportedTypes = (sizeof(Byte) != 1 || sizeof(UInt32) < 4 || sizeof(UInt64) < 8); + if (unsupportedTypes) + throw "Unsupported base types. Edit Common/Types.h and recompile"; + */ + + UStringVector commandStrings; + for (int i = 1; i < numArgs; i++) + commandStrings.Add(MultiByteToUnicodeString(args[i])); + + CParser parser; + try + { + if (!parser.ParseStrings(kSwitchForms, Z7_ARRAY_SIZE(kSwitchForms), commandStrings)) + { + PrintError2(parser.ErrorMessage, parser.ErrorLine); + return 1; + } + } + catch(...) + { + IncorrectCommand(); + } + + if (parser[NKey::kHelp1].ThereIs || parser[NKey::kHelp2].ThereIs) + { + PrintHelp(); + return 0; + } + + const bool stdInMode = parser[NKey::kStdIn].ThereIs; + const bool stdOutMode = parser[NKey::kStdOut].ThereIs; + + if (!stdOutMode) + PrintTitle(); + + const UStringVector ¶ms = parser.NonSwitchStrings; + + unsigned paramIndex = 0; + if (paramIndex >= params.Size()) + IncorrectCommand(); + const UString &command = params[paramIndex++]; + + CObjectVector props2; + bool dictDefined = false; + UInt32 dict = (UInt32)(Int32)-1; + + if (parser[NKey::kDict].ThereIs) + { + UInt32 dictLog; + const UString &s = parser[NKey::kDict].PostStrings[0]; + dictLog = GetNumber(s); + if (dictLog >= 32) + throw "unsupported dictionary size"; + // we only want to use dictionary sizes that are powers of 2, + // because 7-zip only recognizes such dictionary sizes in the lzma header.#if 0 +#if 0 + if (dictLog == 32) + dict = (UInt32)3840 << 20; + else +#endif + dict = (UInt32)1 << dictLog; + dictDefined = true; + AddProp(props2, "d", s); + } + + if (parser[NKey::kLevel].ThereIs) + { + const UString &s = parser[NKey::kLevel].PostStrings[0]; + /* UInt32 level = */ GetNumber(s); + AddProp(props2, "x", s); + } + + UString mf ("BT4"); + if (parser[NKey::kMatchFinder].ThereIs) + mf = parser[NKey::kMatchFinder].PostStrings[0]; + + UInt32 numThreads = (UInt32)(Int32)-1; + + #ifndef Z7_ST + + if (parser[NKey::kMultiThread].ThereIs) + { + const UString &s = parser[NKey::kMultiThread].PostStrings[0]; + if (s.IsEmpty()) + numThreads = NWindows::NSystem::GetNumberOfProcessors(); + else + numThreads = GetNumber(s); + AddProp(props2, "mt", s); + } + + #endif + + + if (parser[NKey::kMethod].ThereIs) + { + const UString &s = parser[NKey::kMethod].PostStrings[0]; + if (s.IsEmpty() || s[0] != '=') + IncorrectCommand(); + AddProp(props2, "m", s.Ptr(1)); + } + + if (StringsAreEqualNoCase_Ascii(command, "b")) + { + UInt32 numIterations = 1; + if (paramIndex < params.Size()) + numIterations = GetNumber(params[paramIndex++]); + if (params.Size() != paramIndex) + IncorrectCommand(); + + HRESULT res = BenchCon(props2, numIterations, stdout); + + if (res == S_OK) + return 0; + return Error_HRESULT("Benchmark error", res); + } + + { + UInt32 needParams = 3; + if (stdInMode) needParams--; + if (stdOutMode) needParams--; + if (needParams != params.Size()) + IncorrectCommand(); + } + + if (numThreads == (UInt32)(Int32)-1) + numThreads = 1; + + bool encodeMode = false; + + if (StringsAreEqualNoCase_Ascii(command, "e")) + encodeMode = true; + else if (!StringsAreEqualNoCase_Ascii(command, "d")) + IncorrectCommand(); + + CMyComPtr inStream; + CInFileStream *inStreamSpec = NULL; + + if (stdInMode) + { + inStream = new CStdInFileStream; + MY_SET_BINARY_MODE(stdin); + } + else + { + const UString &inputName = params[paramIndex++]; + inStreamSpec = new CInFileStream; + inStream = inStreamSpec; + if (!inStreamSpec->Open(us2fs(inputName))) + { + PrintError2("Cannot open input file", inputName); + return 1; + } + } + + CMyComPtr outStream; + COutFileStream *outStreamSpec = NULL; + + if (stdOutMode) + { + outStream = new CStdOutFileStream; + MY_SET_BINARY_MODE(stdout); + } + else + { + const UString &outputName = params[paramIndex++]; + outStreamSpec = new COutFileStream; + outStream = outStreamSpec; + if (!outStreamSpec->Create_ALWAYS(us2fs(outputName))) + { + PrintError2("Cannot open output file", outputName); + return 1; + } + } + + bool fileSizeDefined = false; + UInt64 fileSize = 0; + + if (inStreamSpec) + { + if (!inStreamSpec->GetLength(fileSize)) + throw "Cannot get file length"; + fileSizeDefined = true; + if (!stdOutMode) + Print_Size("Input size: ", fileSize); + } + + if (encodeMode && !dictDefined) + { + dict = (UInt32)1 << kDictSizeLog; + if (fileSizeDefined) + { + unsigned i; + for (i = 16; i < kDictSizeLog; i++) + if ((UInt32)((UInt32)1 << i) >= fileSize) + break; + dict = (UInt32)1 << i; + } + } + + if (parser[NKey::kFilter86].ThereIs) + { + /* -f86 switch is for x86 filtered mode: BCJ + LZMA. + It uses modified header format. + It's not recommended to use -f86 mode now. + You can use xz format instead, if you want to use filters */ + + if (parser[NKey::kEOS].ThereIs || stdInMode) + throw "Cannot use stdin in this mode"; + + size_t inSize = (size_t)fileSize; + + if (inSize != fileSize) + throw "File is too big"; + + Byte *inBuffer = NULL; + + if (inSize != 0) + { + inBuffer = (Byte *)MyAlloc((size_t)inSize); + if (!inBuffer) + throw kCantAllocate; + } + + if (ReadStream_FAIL(inStream, inBuffer, inSize) != S_OK) + throw "Cannot read"; + + Byte *outBuffer = NULL; + size_t outSize; + + if (encodeMode) + { + // we allocate 105% of original size for output buffer + UInt64 outSize64 = fileSize / 20 * 21 + (1 << 16); + + outSize = (size_t)outSize64; + + if (outSize != outSize64) + throw "File is too big"; + + if (outSize != 0) + { + outBuffer = (Byte *)MyAlloc((size_t)outSize); + if (!outBuffer) + throw kCantAllocate; + } + + int res = Lzma86_Encode(outBuffer, &outSize, inBuffer, inSize, + 5, dict, parser[NKey::kFilter86].PostCharIndex == 0 ? SZ_FILTER_YES : SZ_FILTER_AUTO); + + if (res != 0) + { + PrintError_int("Encode error", (int)res); + return 1; + } + } + else + { + UInt64 outSize64; + + if (Lzma86_GetUnpackSize(inBuffer, inSize, &outSize64) != 0) + throw "data error"; + + outSize = (size_t)outSize64; + if (outSize != outSize64) + throw "Unpack size is too big"; + if (outSize != 0) + { + outBuffer = (Byte *)MyAlloc(outSize); + if (!outBuffer) + throw kCantAllocate; + } + + int res = Lzma86_Decode(outBuffer, &outSize, inBuffer, &inSize); + + if (inSize != (size_t)fileSize) + throw "incorrect processed size"; + if (res != 0) + { + PrintError_int("Decode error", (int)res); + return 1; + } + } + + if (WriteStream(outStream, outBuffer, outSize) != S_OK) + throw kWriteError; + + MyFree(outBuffer); + MyFree(inBuffer); + } + else + { + + CProgressPrint *progressSpec = NULL; + CMyComPtr progress; + + if (!stdOutMode) + { + progressSpec = new CProgressPrint; + progress = progressSpec; + } + + if (encodeMode) + { + NCompress::NLzma::CEncoder *encoderSpec = new NCompress::NLzma::CEncoder; + CMyComPtr encoder = encoderSpec; + + UInt32 pb = 2; + UInt32 lc = 3; // = 0; for 32-bit data + UInt32 lp = 0; // = 2; for 32-bit data + UInt32 algo = 1; + UInt32 fb = 128; + UInt32 mc = 16 + fb / 2; + bool mcDefined = false; + + bool eos = parser[NKey::kEOS].ThereIs || stdInMode; + + ParseUInt32(parser, NKey::kAlgo, algo); + ParseUInt32(parser, NKey::kFb, fb); + ParseUInt32(parser, NKey::kLc, lc); + ParseUInt32(parser, NKey::kLp, lp); + ParseUInt32(parser, NKey::kPb, pb); + + mcDefined = parser[NKey::kMc].ThereIs; + if (mcDefined) + mc = GetNumber(parser[NKey::kMc].PostStrings[0]); + + const PROPID propIDs[] = + { + NCoderPropID::kDictionarySize, + NCoderPropID::kPosStateBits, + NCoderPropID::kLitContextBits, + NCoderPropID::kLitPosBits, + NCoderPropID::kAlgorithm, + NCoderPropID::kNumFastBytes, + NCoderPropID::kMatchFinder, + NCoderPropID::kEndMarker, + NCoderPropID::kNumThreads, + NCoderPropID::kMatchFinderCycles, + }; + + const unsigned kNumPropsMax = Z7_ARRAY_SIZE(propIDs); + + PROPVARIANT props[kNumPropsMax]; + for (int p = 0; p < 6; p++) + props[p].vt = VT_UI4; + + props[0].ulVal = (UInt32)dict; + props[1].ulVal = (UInt32)pb; + props[2].ulVal = (UInt32)lc; + props[3].ulVal = (UInt32)lp; + props[4].ulVal = (UInt32)algo; + props[5].ulVal = (UInt32)fb; + + props[6].vt = VT_BSTR; + props[6].bstrVal = const_cast((const wchar_t *)mf); + + props[7].vt = VT_BOOL; + props[7].boolVal = eos ? VARIANT_TRUE : VARIANT_FALSE; + + props[8].vt = VT_UI4; + props[8].ulVal = (UInt32)numThreads; + + // it must be last in property list + props[9].vt = VT_UI4; + props[9].ulVal = (UInt32)mc; + + unsigned numProps = kNumPropsMax; + if (!mcDefined) + numProps--; + + HRESULT res = encoderSpec->SetCoderProperties(propIDs, props, numProps); + if (res != S_OK) + return Error_HRESULT("incorrect encoder properties", res); + + if (encoderSpec->WriteCoderProperties(outStream) != S_OK) + throw kWriteError; + + bool fileSizeWasUsed = true; + if (eos || stdInMode) + { + fileSize = (UInt64)(Int64)-1; + fileSizeWasUsed = false; + } + + { + Byte temp[8]; + for (int i = 0; i < 8; i++) + temp[i]= (Byte)(fileSize >> (8 * i)); + if (WriteStream(outStream, temp, 8) != S_OK) + throw kWriteError; + } + + res = encoder->Code(inStream, outStream, NULL, NULL, progress); + if (progressSpec) + progressSpec->ClosePrint(); + + if (res != S_OK) + return Error_HRESULT("Encoding error", res); + + UInt64 processedSize = encoderSpec->GetInputProcessedSize(); + + if (fileSizeWasUsed && processedSize != fileSize) + throw "Incorrect size of processed data"; + } + else + { + NCompress::NLzma::CDecoder *decoderSpec = new NCompress::NLzma::CDecoder; + CMyComPtr decoder = decoderSpec; + + decoderSpec->FinishStream = true; + + const unsigned kPropertiesSize = 5; + Byte header[kPropertiesSize + 8]; + + if (ReadStream_FALSE(inStream, header, kPropertiesSize + 8) != S_OK) + throw kReadError; + + if (decoderSpec->SetDecoderProperties2(header, kPropertiesSize) != S_OK) + throw "SetDecoderProperties error"; + + UInt64 unpackSize = 0; + for (unsigned i = 0; i < 8; i++) + unpackSize |= ((UInt64)header[kPropertiesSize + i]) << (8 * i); + + bool unpackSizeDefined = (unpackSize != (UInt64)(Int64)-1); + + HRESULT res = decoder->Code(inStream, outStream, NULL, unpackSizeDefined ? &unpackSize : NULL, progress); + if (progressSpec) + progressSpec->ClosePrint(); + + if (res != S_OK) + { + if (res == S_FALSE) + { + PrintError("Decoding error"); + return 1; + } + return Error_HRESULT("Decoding error", res); + } + + if (unpackSizeDefined && unpackSize != decoderSpec->GetOutputProcessedSize()) + throw "incorrect uncompressed size in header"; + } + } + + if (outStreamSpec) + { + if (!stdOutMode) + Print_Size("Output size: ", outStreamSpec->ProcessedSize); + if (outStreamSpec->Close() != S_OK) + throw "File closing error"; + } + + return 0; +} + +int Z7_CDECL main(int numArgs, const char *args[]) +{ + NConsoleClose::CCtrlHandlerSetter ctrlHandlerSetter; + + try { return main2(numArgs, args); } + catch (const char *s) + { + PrintError(s); + return 1; + } + catch(...) + { + PrintError("Unknown Error"); + return 1; + } +} diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/LzmaCon/LzmaCon.dsp b/src/plugins/7zip/7za/cpp/7zip/Bundles/LzmaCon/LzmaCon.dsp new file mode 100644 index 00000000..258bb6d7 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/LzmaCon/LzmaCon.dsp @@ -0,0 +1,540 @@ +# Microsoft Developer Studio Project File - Name="LzmaCon" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=LzmaCon - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "LzmaCon.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "LzmaCon.mak" CFG="LzmaCon - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "LzmaCon - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "LzmaCon - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "LzmaCon - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /Gr /MD /W4 /WX /GX /O2 /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "Z7_NO_LONG_PATH" /Yu"StdAfx.h" /FD /c +# ADD BASE RSC /l 0x419 /d "NDEBUG" +# ADD RSC /l 0x419 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 /out:"C:\Util\lzma.exe" + +!ELSEIF "$(CFG)" == "LzmaCon - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /MDd /W4 /WX /Gm /GX /ZI /Od /D "_DEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "Z7_NO_LONG_PATH" /Yu"StdAfx.h" /FD /GZ /c +# ADD BASE RSC /l 0x419 /d "_DEBUG" +# ADD RSC /l 0x419 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /out:"C:\Util\lzma.exe" /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "LzmaCon - Win32 Release" +# Name "LzmaCon - Win32 Debug" +# Begin Group "Spec" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=.\StdAfx.cpp +# ADD CPP /Yc"StdAfx.h" +# End Source File +# Begin Source File + +SOURCE=.\StdAfx.h +# End Source File +# End Group +# Begin Group "Compress" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Compress\LzmaDecoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\LzmaDecoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\LzmaEncoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\LzmaEncoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\LzmaRegister.cpp +# End Source File +# End Group +# Begin Group "Windows" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\Windows\FileIO.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileIO.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\NtCheck.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\PropVariant.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\PropVariant.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Registry.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Registry.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\System.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\System.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\SystemInfo.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\SystemInfo.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Thread.h +# End Source File +# End Group +# Begin Group "Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\Common\CommandLineParser.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\CommandLineParser.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Common.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\ComTry.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\CRC.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\CrcReg.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Defs.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\IntToString.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\IntToString.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\LzFindPrepare.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyBuffer.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyBuffer2.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyCom.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyInitGuid.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyLinux.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyString.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyString.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyTypes.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyUnknown.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyVector.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyVector.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyWindows.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyWindows.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\NewHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\NewHandler.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StringConvert.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StringConvert.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StringToInt.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StringToInt.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Types.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Wildcard.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Wildcard.h +# End Source File +# End Group +# Begin Group "7zip Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Common\CreateCoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\CreateCoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\CWrappers.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\CWrappers.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FileStreams.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FileStreams.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FilterCoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FilterCoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\MethodProps.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\MethodProps.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamObjects.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamObjects.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamUtils.h +# End Source File +# End Group +# Begin Group "UI Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\UI\Common\Bench.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\Bench.h +# End Source File +# End Group +# Begin Group "Console" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\UI\Console\BenchCon.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\BenchCon.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\ConsoleClose.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\ConsoleClose.h +# End Source File +# End Group +# Begin Group "C" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\..\C\7zCrc.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zCrc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zCrcOpt.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zTypes.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zWindows.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Alloc.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Alloc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Bra86.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Compiler.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\CpuArch.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\CpuArch.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzFind.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzFind.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzFindMt.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzFindMt.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzFindOpt.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzHash.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Lzma86.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Lzma86Dec.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Lzma86Enc.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzmaDec.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzmaDec.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzmaEnc.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzmaEnc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Threads.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Threads.h +# End Source File +# End Group +# Begin Group "7zip" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\ICoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\IDecl.h +# End Source File +# Begin Source File + +SOURCE=..\..\IProgress.h +# End Source File +# End Group +# Begin Source File + +SOURCE=.\LzmaAlone.cpp +# End Source File +# End Target +# End Project diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/LzmaCon/LzmaCon.dsw b/src/plugins/7zip/7za/cpp/7zip/Bundles/LzmaCon/LzmaCon.dsw new file mode 100644 index 00000000..e62c9d2f --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/LzmaCon/LzmaCon.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "LzmaCon"=.\LzmaCon.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/LzmaCon/StdAfx.cpp b/src/plugins/7zip/7za/cpp/7zip/Bundles/LzmaCon/StdAfx.cpp new file mode 100644 index 00000000..d0feea85 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/LzmaCon/StdAfx.cpp @@ -0,0 +1,3 @@ +// StdAfx.cpp + +#include "StdAfx.h" diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/LzmaCon/StdAfx.h b/src/plugins/7zip/7za/cpp/7zip/Bundles/LzmaCon/StdAfx.h new file mode 100644 index 00000000..035267cc --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/LzmaCon/StdAfx.h @@ -0,0 +1,11 @@ +// StdAfx.h + +#ifndef ZIP7_INC_STDAFX_H +#define ZIP7_INC_STDAFX_H + +#if defined(_MSC_VER) && _MSC_VER >= 1800 +#pragma warning(disable : 4464) // relative include path contains '..' +#endif +#include "../../../Common/Common.h" + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/LzmaCon/makefile b/src/plugins/7zip/7za/cpp/7zip/Bundles/LzmaCon/makefile new file mode 100644 index 00000000..4aef2524 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/LzmaCon/makefile @@ -0,0 +1,68 @@ +PROG = lzma.exe +MY_CONSOLE = 1 + +CFLAGS = $(CFLAGS) -DZ7_NO_LONG_PATH +# CFLAGS = $(CFLAGS) -DZ7_ST + + +CURRENT_OBJS = \ + $O\LzmaAlone.obj \ + +COMPRESS_OBJS = \ + $O\LzmaDecoder.obj \ + $O\LzmaEncoder.obj \ + $O\LzmaRegister.obj \ + +COMMON_OBJS = \ + $O\CommandLineParser.obj \ + $O\CRC.obj \ + $O\CrcReg.obj \ + $O\IntToString.obj \ + $O\LzFindPrepare.obj \ + $O\MyString.obj \ + $O\MyVector.obj \ + $O\NewHandler.obj \ + $O\StringConvert.obj \ + $O\StringToInt.obj \ + $O\Wildcard.obj \ + +WIN_OBJS = \ + $O\FileIO.obj \ + $O\PropVariant.obj \ + $O\Registry.obj \ + $O\System.obj \ + $O\SystemInfo.obj \ + +7ZIP_COMMON_OBJS = \ + $O\CreateCoder.obj \ + $O\CWrappers.obj \ + $O\FileStreams.obj \ + $O\FilterCoder.obj \ + $O\MethodProps.obj \ + $O\StreamObjects.obj \ + $O\StreamUtils.obj \ + +UI_COMMON_OBJS = \ + $O\Bench.obj \ + +CONSOLE_OBJS = \ + $O\ConsoleClose.obj \ + $O\BenchCon.obj \ + +C_OBJS = \ + $O\Alloc.obj \ + $O\Bra86.obj \ + $O\CpuArch.obj \ + $O\LzFind.obj \ + $O\LzFindMt.obj \ + $O\Lzma86Dec.obj \ + $O\Lzma86Enc.obj \ + $O\LzmaDec.obj \ + $O\LzmaEnc.obj \ + $O\Threads.obj \ + +!include "../../Crc.mak" +!include "../../LzFindOpt.mak" +!include "../../LzmaDec.mak" + +!include "../../7zip.mak" diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/LzmaCon/makefile.gcc b/src/plugins/7zip/7za/cpp/7zip/Bundles/LzmaCon/makefile.gcc new file mode 100644 index 00000000..67c78927 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/LzmaCon/makefile.gcc @@ -0,0 +1,131 @@ +PROG = lzma + +# IS_X64 = 1 +# USE_ASM = 1 +# ST_MODE = 1 + +ifdef SystemDrive +IS_MINGW = 1 +else +ifdef SYSTEMDRIVE +# ifdef OS +IS_MINGW = 1 +endif +endif + +include ../../LzmaDec_gcc.mak + + +LOCAL_FLAGS_ST = +MT_OBJS = + + +ifdef ST_MODE + +LOCAL_FLAGS_ST = -DZ7_ST + +else + +MT_OBJS = \ + $O/LzFindMt.o \ + $O/LzFindOpt.o \ + $O/Threads.o \ + $O/Synchronization.o \ + + + +endif + + + +LOCAL_FLAGS_SYS = + +ifdef IS_MINGW + +SYS_OBJS = \ + $O/Registry.o \ + $O/resource.o \ + +LOCAL_FLAGS_SYS = \ + -DZ7_NO_LONG_PATH \ + +else + +SYS_OBJS = \ + $O/FileDir.o \ + $O/FileFind.o \ + $O/FileName.o \ + $O/MyWindows.o \ + $O/TimeUtils.o \ + +endif + +LOCAL_FLAGS = \ + $(LOCAL_FLAGS_ST) \ + $(LOCAL_FLAGS_SYS) \ + + +COMMON_OBJS = \ + $O/CommandLineParser.o \ + $O/CRC.o \ + $O/CrcReg.o \ + $O/IntToString.o \ + $O/LzFindPrepare.o \ + $O/MyString.o \ + $O/MyVector.o \ + $O/NewHandler.o \ + $O/StringConvert.o \ + $O/StringToInt.o \ + $O/UTFConvert.o \ + $O/Wildcard.o \ + +WIN_OBJS = \ + $O/FileIO.o \ + $O/PropVariant.o \ + $O/System.o \ + $O/SystemInfo.o \ + +COMPRESS_OBJS = \ + $O/LzmaDecoder.o \ + $O/LzmaEncoder.o \ + $O/LzmaRegister.o \ + +CONSOLE_OBJS = \ + $O/BenchCon.o \ + $O/ConsoleClose.o \ + +7ZIP_COMMON_OBJS = \ + $O/CreateCoder.o \ + $O/CWrappers.o \ + $O/FileStreams.o \ + $O/FilterCoder.o \ + $O/MethodProps.o \ + $O/StreamObjects.o \ + $O/StreamUtils.o \ + +C_OBJS = \ + $O/7zCrc.o \ + $O/7zCrcOpt.o \ + $O/Alloc.o \ + $O/Bra86.o \ + $O/CpuArch.o \ + $O/LzFind.o \ + $O/LzmaDec.o \ + $O/LzmaEnc.o \ + $O/Lzma86Dec.o \ + $O/Lzma86Enc.o \ + +OBJS = \ + $(LZMA_DEC_OPT_OBJS) \ + $(C_OBJS) \ + $(MT_OBJS) \ + $(SYS_OBJS) \ + $(COMMON_OBJS) \ + $(WIN_OBJS) \ + $(COMPRESS_OBJS) \ + $(7ZIP_COMMON_OBJS) \ + $(CONSOLE_OBJS) \ + $O/LzmaAlone.o \ + $O/Bench.o \ + +include ../../7zip_gcc.mak diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/LzmaCon/resource.rc b/src/plugins/7zip/7za/cpp/7zip/Bundles/LzmaCon/resource.rc new file mode 100644 index 00000000..43b50738 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/LzmaCon/resource.rc @@ -0,0 +1,3 @@ +#include "../../MyVersionInfo.rc" + +MY_VERSION_INFO_APP("LZMA", "lzma") diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXCon/7z.ico b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXCon/7z.ico new file mode 100644 index 00000000..47ffb781 Binary files /dev/null and b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXCon/7z.ico differ diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXCon/SFXCon.dsp b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXCon/SFXCon.dsp new file mode 100644 index 00000000..d4909326 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXCon/SFXCon.dsp @@ -0,0 +1,1017 @@ +# Microsoft Developer Studio Project File - Name="SFXCon" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=SFXCon - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "SFXCon.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "SFXCon.mak" CFG="SFXCon - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "SFXCon - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "SFXCon - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "SFXCon - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /Gr /MD /W4 /WX /GX /O1 /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "Z7_EXTRACT_ONLY" /D "Z7_SFX" /D "Z7_NO_READ_FROM_CODER" /D "Z7_NO_LONG_PATH" /D "Z7_NO_LARGE_PAGES" /FAcs /Yu"StdAfx.h" /FD /c +# ADD BASE RSC /l 0x419 /d "NDEBUG" +# ADD RSC /l 0x419 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 /out:"C:\Util\7zCon.exe" /opt:NOWIN98 +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "SFXCon - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /Gz /MTd /W4 /WX /Gm /GX /ZI /Od /I "..\..\..\..\\" /D "_DEBUG" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "Z7_EXTRACT_ONLY" /D "Z7_SFX" /D "Z7_NO_READ_FROM_CODER" /D "Z7_NO_LONG_PATH" /D "Z7_NO_LARGE_PAGES" /Yu"StdAfx.h" /FD /GZ /c +# ADD BASE RSC /l 0x419 /d "_DEBUG" +# ADD RSC /l 0x419 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /out:"C:\Util\7zCon.exe" /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "SFXCon - Win32 Release" +# Name "SFXCon - Win32 Debug" +# Begin Group "Spec" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=.\resource.h +# End Source File +# Begin Source File + +SOURCE=.\resource.rc +# End Source File +# Begin Source File + +SOURCE=.\StdAfx.cpp +# ADD CPP /Yc"StdAfx.h" +# End Source File +# Begin Source File + +SOURCE=.\StdAfx.h +# End Source File +# End Group +# Begin Group "Archive Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Archive\Common\CoderMixer2.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\CoderMixer2.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\HandlerOut.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\ItemNameUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\ItemNameUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\MultiStream.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\MultiStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\OutStreamWithCRC.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\OutStreamWithCRC.h +# End Source File +# End Group +# Begin Group "Console" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\UI\Console\ConsoleClose.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\ConsoleClose.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\ExtractCallbackConsole.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\ExtractCallbackConsole.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\List.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\List.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\MainAr.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\OpenCallbackConsole.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\OpenCallbackConsole.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\PercentPrinter.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\PercentPrinter.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\UserInputUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Console\UserInputUtils.h +# End Source File +# End Group +# Begin Group "7z" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Archive\7z\7zDecode.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zDecode.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zExtract.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zHandler.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zHeader.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zIn.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zIn.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zItem.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zRegister.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\SplitHandler.cpp +# End Source File +# End Group +# Begin Group "Compress" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Compress\Bcj2Coder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Bcj2Coder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Bcj2Register.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BcjCoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BcjCoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BcjRegister.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BranchMisc.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BranchMisc.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BranchRegister.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\CopyCoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\CopyCoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\CopyRegister.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\DeltaFilter.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Lzma2Decoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Lzma2Decoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Lzma2Register.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\LzmaDecoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\LzmaDecoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\LzmaRegister.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\PpmdDecoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\PpmdDecoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\PpmdRegister.cpp +# End Source File +# End Group +# Begin Group "Crypto" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Crypto\7zAes.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\7zAes.h +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\7zAesRegister.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\MyAes.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\MyAes.h +# End Source File +# End Group +# Begin Group "Windows" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\Windows\DLL.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\DLL.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\ErrorMsg.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\ErrorMsg.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileDir.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileDir.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileFind.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileFind.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileIO.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileIO.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileName.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileName.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\PropVariant.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\PropVariant.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\PropVariantConv.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\PropVariantConv.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Synchronization.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Synchronization.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\System.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\System.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\TimeUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\TimeUtils.h +# End Source File +# End Group +# Begin Group "Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\Common\CommandLineParser.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\CommandLineParser.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Common.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\CRC.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\CRC.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\IntToString.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\IntToString.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyBuffer.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyBuffer2.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyCom.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyString.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyString.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyVector.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyVector.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\NewHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\NewHandler.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Sha256Prepare.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StdInStream.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StdInStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StdOutStream.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StdOutStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StringConvert.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StringConvert.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\UTFConvert.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\UTFConvert.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Wildcard.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Wildcard.h +# End Source File +# End Group +# Begin Group "7zip Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Common\CreateCoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\CreateCoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\CWrappers.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\CWrappers.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FilePathAutoRename.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FilePathAutoRename.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FileStreams.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FileStreams.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FilterCoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FilterCoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\InBuffer.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\InBuffer.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\LimitedStreams.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\LimitedStreams.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\LockedStream.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\LockedStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\OffsetStream.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\OffsetStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\OutBuffer.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\OutBuffer.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\ProgressUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\ProgressUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\PropId.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\RegisterArc.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\RegisterCodec.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamBinder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamBinder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamObjects.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamObjects.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\VirtThread.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\VirtThread.h +# End Source File +# End Group +# Begin Group "UI" + +# PROP Default_Filter "" +# Begin Group "UI Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\UI\Common\ArchiveExtractCallback.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\ArchiveExtractCallback.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\ArchiveOpenCallback.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\ArchiveOpenCallback.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\DefaultName.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\DefaultName.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\ExitCode.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\Extract.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\Extract.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\ExtractingFilePath.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\ExtractingFilePath.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\ExtractMode.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\LoadCodecs.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\LoadCodecs.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\OpenArchive.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\OpenArchive.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\PropIDUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\PropIDUtils.h +# End Source File +# End Group +# End Group +# Begin Group "C" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\..\C\7zCrc.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zCrc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zCrcOpt.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zStream.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zTypes.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zWindows.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Aes.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Aes.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\AesOpt.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Alloc.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Alloc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Bcj2.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Bcj2.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Bra.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Bra.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Bra86.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\BraIA64.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Compiler.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\CpuArch.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\CpuArch.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Delta.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Delta.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\DllSecur.c + +!IF "$(CFG)" == "SFXCon - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "SFXCon - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\DllSecur.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Lzma2Dec.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Lzma2Dec.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Lzma2DecMt.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Lzma2DecMt.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzmaDec.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzmaDec.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\MtDec.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\MtDec.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Ppmd7.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Ppmd7.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Ppmd7Dec.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Sha256.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Sha256.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Sha256Opt.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Threads.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Threads.h +# End Source File +# End Group +# Begin Group "7zip" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Archive\IArchive.h +# End Source File +# Begin Source File + +SOURCE=..\..\ICoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\IDecl.h +# End Source File +# Begin Source File + +SOURCE=..\..\IPassword.h +# End Source File +# Begin Source File + +SOURCE=..\..\IProgress.h +# End Source File +# End Group +# Begin Source File + +SOURCE=.\7z.ico +# End Source File +# Begin Source File + +SOURCE=.\SfxCon.cpp +# End Source File +# End Target +# End Project diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXCon/SFXCon.dsw b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXCon/SFXCon.dsw new file mode 100644 index 00000000..27bf7e6d --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXCon/SFXCon.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "SFXCon"=.\SFXCon.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXCon/SfxCon.cpp b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXCon/SfxCon.cpp new file mode 100644 index 00000000..9e2d13d6 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXCon/SfxCon.cpp @@ -0,0 +1,521 @@ +// Main.cpp + +#include "StdAfx.h" + +#include "../../../../C/CpuArch.h" +#include "../../../../C/DllSecur.h" + +#include "../../../Common/MyWindows.h" +#include "../../../Common/MyInitGuid.h" + +#include "../../../Common/CommandLineParser.h" +#include "../../../Common/MyException.h" + +#ifdef _WIN32 +#include "../../../Windows/DLL.h" +#else +#include "../../../Common/StringConvert.h" +#endif +#include "../../../Windows/FileDir.h" +#include "../../../Windows/FileName.h" + +#include "../../UI/Common/ExitCode.h" +#include "../../UI/Common/Extract.h" + +#include "../../UI/Console/ExtractCallbackConsole.h" +#include "../../UI/Console/List.h" +#include "../../UI/Console/OpenCallbackConsole.h" + +#include "../../MyVersion.h" + + +using namespace NWindows; +using namespace NFile; +using namespace NDir; +using namespace NCommandLineParser; + +#ifdef _WIN32 +extern +HINSTANCE g_hInstance; +HINSTANCE g_hInstance = NULL; +#endif +extern +int g_CodePage; +int g_CodePage = -1; +extern CStdOutStream *g_StdStream; + +static const char * const kCopyrightString = +"\n7-Zip SFX " MY_VERSION_CPU " : " MY_COPYRIGHT_DATE "\n"; + +static const int kNumSwitches = 6; + +namespace NKey { +enum Enum +{ + kHelp1 = 0, + kHelp2, + kDisablePercents, + kYes, + kPassword, + kOutputDir +}; + +} + +namespace NRecursedType { +enum EEnum +{ + kRecursed, + kWildcardOnlyRecursed, + kNonRecursed +}; +} +/* +static const char kRecursedIDChar = 'R'; + +namespace NRecursedPostCharIndex { + enum EEnum + { + kWildcardRecursionOnly = 0, + kNoRecursion = 1 + }; +} + +static const char kFileListID = '@'; +static const char kImmediateNameID = '!'; + +static const char kSomeCludePostStringMinSize = 2; // at least <@|!>ame must be +static const char kSomeCludeAfterRecursedPostStringMinSize = 2; // at least <@|!>ame must be +*/ + +#define SWFRM_3(t, mu, mi) t, mu, mi, NULL +#define SWFRM_1(t) SWFRM_3(t, false, 0) +#define SWFRM_SIMPLE SWFRM_1(NSwitchType::kSimple) +#define SWFRM_STRING_SINGL(mi) SWFRM_3(NSwitchType::kString, false, mi) + +static const CSwitchForm kSwitchForms[kNumSwitches] = +{ + { "?", SWFRM_SIMPLE }, + { "H", SWFRM_SIMPLE }, + { "BD", SWFRM_SIMPLE }, + { "Y", SWFRM_SIMPLE }, + { "P", SWFRM_STRING_SINGL(1) }, + { "O", SWFRM_STRING_SINGL(1) }, +}; + +static const int kNumCommandForms = 3; + +static const NRecursedType::EEnum kCommandRecursedDefault[kNumCommandForms] = +{ + NRecursedType::kRecursed +}; + +// static const bool kTestExtractRecursedDefault = true; +// static const bool kAddRecursedDefault = false; + +static const char * const kUniversalWildcard = "*"; + +static const char * const kHelpString = + "\nUsage: 7zSFX [] [...] [...]\n" + "\n" + "\n" + // " l: List contents of archive\n" + " t: Test integrity of archive\n" + " x: eXtract files with full pathname (default)\n" + "\n" + // " -bd Disable percentage indicator\n" + " -o{Directory}: set Output directory\n" + " -p{Password}: set Password\n" + " -y: assume Yes on all queries\n"; + + +// --------------------------- +// exception messages + +static const char * const kUserErrorMessage = "Incorrect command line"; // NExitCode::kUserError +// static const char * const kIncorrectListFile = "Incorrect wildcard in listfile"; +static const char * const kIncorrectWildcardInCommandLine = "Incorrect wildcard in command line"; + +// static const CSysString kFileIsNotArchiveMessageBefore = "File \""; +// static const CSysString kFileIsNotArchiveMessageAfter = "\" is not archive"; + +// static const char * const kProcessArchiveMessage = " archive: "; + +static const char * const kCantFindSFX = " cannot find sfx"; + +namespace NCommandType +{ + enum EEnum + { + kTest = 0, + kFullExtract, + kList + }; +} + +static const char * const g_Commands = "txl"; + +struct CArchiveCommand +{ + NCommandType::EEnum CommandType; + + NRecursedType::EEnum DefaultRecursedType() const; +}; + +static bool ParseArchiveCommand(const UString &commandString, CArchiveCommand &command) +{ + UString s = commandString; + s.MakeLower_Ascii(); + if (s.Len() != 1) + return false; + if (s[0] >= 0x80) + return false; + int index = FindCharPosInString(g_Commands, (char)s[0]); + if (index < 0) + return false; + command.CommandType = (NCommandType::EEnum)index; + return true; +} + +NRecursedType::EEnum CArchiveCommand::DefaultRecursedType() const +{ + return kCommandRecursedDefault[CommandType]; +} + +static void PrintHelp(void) +{ + g_StdOut << kHelpString; +} + +Z7_ATTR_NORETURN +static void ShowMessageAndThrowException(const char *message, NExitCode::EEnum code) +{ + g_StdOut << message << endl; + throw code; +} + +Z7_ATTR_NORETURN +static void PrintHelpAndExit() // yyy +{ + PrintHelp(); + ShowMessageAndThrowException(kUserErrorMessage, NExitCode::kUserError); +} + +// ------------------------------------------------------------------ +// filenames functions + +static bool AddNameToCensor(NWildcard::CCensor &wildcardCensor, + const UString &name, bool include, NRecursedType::EEnum type) +{ + /* + if (!IsWildcardFilePathLegal(name)) + return false; + */ + const bool isWildcard = DoesNameContainWildcard(name); + bool recursed = false; + + switch (type) + { + case NRecursedType::kWildcardOnlyRecursed: + recursed = isWildcard; + break; + case NRecursedType::kRecursed: + recursed = true; + break; + case NRecursedType::kNonRecursed: + recursed = false; + break; + } + + NWildcard::CCensorPathProps props; + props.Recursive = recursed; + wildcardCensor.AddPreItem(include, name, props); + return true; +} + +static void AddCommandLineWildcardToCensor(NWildcard::CCensor &wildcardCensor, + const UString &name, bool include, NRecursedType::EEnum type) +{ + if (!AddNameToCensor(wildcardCensor, name, include, type)) + ShowMessageAndThrowException(kIncorrectWildcardInCommandLine, NExitCode::kUserError); +} + + +#ifndef _WIN32 +static void GetArguments(int numArgs, char *args[], UStringVector &parts) +{ + parts.Clear(); + for (int i = 0; i < numArgs; i++) + { + UString s = MultiByteToUnicodeString(args[i]); + parts.Add(s); + } +} +#endif + + +int Main2( + #ifndef _WIN32 + int numArgs, char *args[] + #endif +); +int Main2( + #ifndef _WIN32 + int numArgs, char *args[] + #endif +) +{ + #ifdef _WIN32 + // do we need load Security DLLs for console program? + LoadSecurityDlls(); + #endif + + #if defined(_WIN32) && !defined(UNDER_CE) + SetFileApisToOEM(); + #endif + + #ifdef ENV_HAVE_LOCALE + MY_SetLocale(); + #endif + + g_StdOut << kCopyrightString; + + UStringVector commandStrings; + #ifdef _WIN32 + NCommandLineParser::SplitCommandLine(GetCommandLineW(), commandStrings); + #else + GetArguments(numArgs, args, commandStrings); + #endif + + #ifdef _WIN32 + + FString arcPath; + { + FString path; + NDLL::MyGetModuleFileName(path); + if (!MyGetFullPathName(path, arcPath)) + { + g_StdOut << "GetFullPathName Error"; + return NExitCode::kFatalError; + } + } + + #else + + if (commandStrings.IsEmpty()) + return NExitCode::kFatalError; + + const FString arcPath = us2fs(commandStrings.Front()); + + #endif + + #ifndef UNDER_CE + if (commandStrings.Size() > 0) + commandStrings.Delete(0); + #endif + + NCommandLineParser::CParser parser; + + try + { + if (!parser.ParseStrings(kSwitchForms, kNumSwitches, commandStrings)) + { + g_StdOut << "Command line error:" << endl + << parser.ErrorMessage << endl + << parser.ErrorLine << endl; + return NExitCode::kUserError; + } + } + catch(...) + { + PrintHelpAndExit(); + } + + if (parser[NKey::kHelp1].ThereIs || parser[NKey::kHelp2].ThereIs) + { + PrintHelp(); + return 0; + } + + const UStringVector &nonSwitchStrings = parser.NonSwitchStrings; + + unsigned curCommandIndex = 0; + + CArchiveCommand command; + if (nonSwitchStrings.IsEmpty()) + command.CommandType = NCommandType::kFullExtract; + else + { + const UString &cmd = nonSwitchStrings[curCommandIndex]; + if (!ParseArchiveCommand(cmd, command)) + { + g_StdOut << "ERROR: Unknown command:" << endl << cmd << endl; + return NExitCode::kUserError; + } + curCommandIndex = 1; + } + + + NRecursedType::EEnum recursedType; + recursedType = command.DefaultRecursedType(); + + NWildcard::CCensor wildcardCensor; + + { + if (nonSwitchStrings.Size() == curCommandIndex) + AddCommandLineWildcardToCensor(wildcardCensor, (UString)kUniversalWildcard, true, recursedType); + for (; curCommandIndex < nonSwitchStrings.Size(); curCommandIndex++) + { + const UString &s = nonSwitchStrings[curCommandIndex]; + if (s.IsEmpty()) + throw "Empty file path"; + AddCommandLineWildcardToCensor(wildcardCensor, s, true, recursedType); + } + } + + const bool yesToAll = parser[NKey::kYes].ThereIs; + + // NExtractMode::EEnum extractMode; + // bool isExtractGroupCommand = command.IsFromExtractGroup(extractMode); + + const bool passwordEnabled = parser[NKey::kPassword].ThereIs; + + UString password; + if (passwordEnabled) + password = parser[NKey::kPassword].PostStrings[0]; + + if (!NFind::DoesFileExist_FollowLink(arcPath)) + throw kCantFindSFX; + + FString outputDir; + if (parser[NKey::kOutputDir].ThereIs) + { + outputDir = us2fs(parser[NKey::kOutputDir].PostStrings[0]); + NName::NormalizeDirPathPrefix(outputDir); + } + + + wildcardCensor.AddPathsToCensor(NWildcard::k_RelatPath); + + { + UStringVector v1, v2; + v1.Add(fs2us(arcPath)); + v2.Add(fs2us(arcPath)); + const NWildcard::CCensorNode &wildcardCensorHead = + wildcardCensor.Pairs.Front().Head; + + CCodecs *codecs = new CCodecs; + CMyComPtr< + #ifdef Z7_EXTERNAL_CODECS + ICompressCodecsInfo + #else + IUnknown + #endif + > compressCodecsInfo = codecs; + { + HRESULT result = codecs->Load(); + if (result != S_OK) + throw CSystemException(result); + } + + if (command.CommandType != NCommandType::kList) + { + CExtractCallbackConsole *ecs = new CExtractCallbackConsole; + CMyComPtr extractCallback = ecs; + ecs->Init(g_StdStream, &g_StdErr, g_StdStream, false); + + #ifndef Z7_NO_CRYPTO + ecs->PasswordIsDefined = passwordEnabled; + ecs->Password = password; + #endif + + /* + COpenCallbackConsole openCallback; + openCallback.Init(g_StdStream, g_StdStream); + + #ifndef Z7_NO_CRYPTO + openCallback.PasswordIsDefined = passwordEnabled; + openCallback.Password = password; + #endif + */ + + CExtractOptions eo; + eo.StdOutMode = false; + eo.YesToAll = yesToAll; + eo.TestMode = command.CommandType == NCommandType::kTest; + eo.PathMode = NExtract::NPathMode::kFullPaths; + eo.OverwriteMode = yesToAll ? + NExtract::NOverwriteMode::kOverwrite : + NExtract::NOverwriteMode::kAsk; + eo.OutputDir = outputDir; + + UString errorMessage; + CDecompressStat stat; + HRESULT result = Extract( + codecs, CObjectVector(), CIntVector(), + v1, v2, + wildcardCensorHead, + eo, + ecs, ecs, ecs, + // NULL, // hash + errorMessage, stat); + + ecs->ClosePercents(); + + if (!errorMessage.IsEmpty()) + { + (*g_StdStream) << endl << "Error: " << errorMessage; + if (result == S_OK) + result = E_FAIL; + } + + if ( 0 != ecs->NumCantOpenArcs + || 0 != ecs->NumArcsWithError + || 0 != ecs->NumFileErrors + || 0 != ecs->NumOpenArcErrors) + { + if (ecs->NumCantOpenArcs != 0) + (*g_StdStream) << endl << "Can't open as archive" << endl; + if (ecs->NumArcsWithError != 0) + (*g_StdStream) << endl << "Archive Errors" << endl; + if (ecs->NumFileErrors != 0) + (*g_StdStream) << endl << "Sub items Errors: " << ecs->NumFileErrors << endl; + if (ecs->NumOpenArcErrors != 0) + (*g_StdStream) << endl << "Open Errors: " << ecs->NumOpenArcErrors << endl; + return NExitCode::kFatalError; + } + if (result != S_OK) + throw CSystemException(result); + } + else + { + throw CSystemException(E_NOTIMPL); + + /* + UInt64 numErrors = 0; + UInt64 numWarnings = 0; + HRESULT result = ListArchives( + codecs, CObjectVector(), CIntVector(), + false, // stdInMode + v1, v2, + true, // processAltStreams + false, // showAltStreams + wildcardCensorHead, + true, // enableHeaders + false, // techMode + #ifndef Z7_NO_CRYPTO + passwordEnabled, password, + #endif + numErrors, numWarnings); + if (numErrors > 0) + { + g_StdOut << endl << "Errors: " << numErrors; + return NExitCode::kFatalError; + } + if (result != S_OK) + throw CSystemException(result); + */ + } + } + return 0; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXCon/StdAfx.cpp b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXCon/StdAfx.cpp new file mode 100644 index 00000000..d0feea85 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXCon/StdAfx.cpp @@ -0,0 +1,3 @@ +// StdAfx.cpp + +#include "StdAfx.h" diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXCon/StdAfx.h b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXCon/StdAfx.h new file mode 100644 index 00000000..035267cc --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXCon/StdAfx.h @@ -0,0 +1,11 @@ +// StdAfx.h + +#ifndef ZIP7_INC_STDAFX_H +#define ZIP7_INC_STDAFX_H + +#if defined(_MSC_VER) && _MSC_VER >= 1800 +#pragma warning(disable : 4464) // relative include path contains '..' +#endif +#include "../../../Common/Common.h" + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXCon/makefile b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXCon/makefile new file mode 100644 index 00000000..a72e3f8f --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXCon/makefile @@ -0,0 +1,137 @@ +PROG = 7zCon.sfx +MY_CONSOLE = 1 +MY_FIXED = 1 + +CFLAGS = $(CFLAGS) \ + -DZ7_EXTRACT_ONLY \ + -DZ7_NO_READ_FROM_CODER \ + -DZ7_SFX \ + -DZ7_NO_LONG_PATH \ + -DZ7_NO_LARGE_PAGES \ + +CURRENT_OBJS = \ + $O\SfxCon.obj \ + +CONSOLE_OBJS = \ + $O\ConsoleClose.obj \ + $O\ExtractCallbackConsole.obj \ + $O\List.obj \ + $O\MainAr.obj \ + $O\OpenCallbackConsole.obj \ + $O\PercentPrinter.obj \ + $O\UserInputUtils.obj \ + +COMMON_OBJS = \ + $O\CommandLineParser.obj \ + $O\CRC.obj \ + $O\IntToString.obj \ + $O\MyString.obj \ + $O\MyVector.obj \ + $O\NewHandler.obj \ + $O\StdInStream.obj \ + $O\StdOutStream.obj \ + $O\StringConvert.obj \ + $O\UTFConvert.obj \ + $O\Wildcard.obj \ + +WIN_OBJS = \ + $O\DLL.obj \ + $O\ErrorMsg.obj \ + $O\FileDir.obj \ + $O\FileFind.obj \ + $O\FileIO.obj \ + $O\FileName.obj \ + $O\PropVariant.obj \ + $O\PropVariantConv.obj \ + $O\Synchronization.obj \ + $O\System.obj \ + $O\TimeUtils.obj \ + +7ZIP_COMMON_OBJS = \ + $O\CreateCoder.obj \ + $O\CWrappers.obj \ + $O\FilePathAutoRename.obj \ + $O\FileStreams.obj \ + $O\InBuffer.obj \ + $O\FilterCoder.obj \ + $O\LimitedStreams.obj \ + $O\OutBuffer.obj \ + $O\ProgressUtils.obj \ + $O\PropId.obj \ + $O\StreamBinder.obj \ + $O\StreamObjects.obj \ + $O\StreamUtils.obj \ + $O\VirtThread.obj \ + +UI_COMMON_OBJS = \ + $O\ArchiveExtractCallback.obj \ + $O\ArchiveOpenCallback.obj \ + $O\DefaultName.obj \ + $O\Extract.obj \ + $O\ExtractingFilePath.obj \ + $O\LoadCodecs.obj \ + $O\OpenArchive.obj \ + $O\PropIDUtils.obj \ + +AR_OBJS = \ + $O\SplitHandler.obj \ + +AR_COMMON_OBJS = \ + $O\CoderMixer2.obj \ + $O\ItemNameUtils.obj \ + $O\MultiStream.obj \ + $O\OutStreamWithCRC.obj \ + +7Z_OBJS = \ + $O\7zDecode.obj \ + $O\7zExtract.obj \ + $O\7zHandler.obj \ + $O\7zIn.obj \ + $O\7zRegister.obj \ + +COMPRESS_OBJS = \ + $O\Bcj2Coder.obj \ + $O\Bcj2Register.obj \ + $O\BcjCoder.obj \ + $O\BcjRegister.obj \ + $O\BranchMisc.obj \ + $O\BranchRegister.obj \ + $O\CopyCoder.obj \ + $O\CopyRegister.obj \ + $O\DeltaFilter.obj \ + $O\Lzma2Decoder.obj \ + $O\Lzma2Register.obj \ + $O\LzmaDecoder.obj \ + $O\LzmaRegister.obj \ + $O\PpmdDecoder.obj \ + $O\PpmdRegister.obj \ + +CRYPTO_OBJS = \ + $O\7zAes.obj \ + $O\7zAesRegister.obj \ + $O\MyAes.obj \ + +C_OBJS = \ + $O\7zStream.obj \ + $O\Alloc.obj \ + $O\Bcj2.obj \ + $O\Bra.obj \ + $O\Bra86.obj \ + $O\BraIA64.obj \ + $O\CpuArch.obj \ + $O\Delta.obj \ + $O\DllSecur.obj \ + $O\Lzma2Dec.obj \ + $O\Lzma2DecMt.obj \ + $O\LzmaDec.obj \ + $O\MtDec.obj \ + $O\Ppmd7.obj \ + $O\Ppmd7Dec.obj \ + $O\Threads.obj \ + +!include "../../Aes.mak" +!include "../../Crc.mak" +!include "../../LzmaDec.mak" +!include "../../Sha256.mak" + +!include "../../7zip.mak" diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXCon/makefile.gcc b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXCon/makefile.gcc new file mode 100644 index 00000000..28abdf74 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXCon/makefile.gcc @@ -0,0 +1,215 @@ +PROG = 7zCon + +# IS_X64 = 1 +# USE_ASM = 1 +# ST_MODE = 1 + +include ../../LzmaDec_gcc.mak + + +LOCAL_FLAGS_ST = +MT_OBJS = + + +ifdef SystemDrive +IS_MINGW = 1 +else +ifdef SYSTEMDRIVE +# ifdef OS +IS_MINGW = 1 +endif +endif + +ifdef ST_MODE + +LOCAL_FLAGS_ST = -DZ7_ST + +ifdef IS_MINGW +MT_OBJS = \ + $O/Threads.o \ + +endif + +else + +MT_OBJS = \ + $O/StreamBinder.o \ + $O/Synchronization.o \ + $O/VirtThread.o \ + $O/Threads.o \ + +endif + + + +LOCAL_FLAGS_SYS = + +ifdef IS_MINGW + +LOCAL_FLAGS_SYS = \ + -DZ7_NO_LONG_PATH \ + +SYS_OBJS = \ + $O/DLL.o \ + $O/DllSecur.o \ + $O/resource.o \ + +else + +SYS_OBJS = \ + $O/MyWindows.o \ + +endif + +LOCAL_FLAGS = \ + -DZ7_EXTRACT_ONLY \ + -DZ7_NO_READ_FROM_CODER \ + -DZ7_SFX \ + -DZ7_NO_LARGE_PAGES \ + $(LOCAL_FLAGS_ST) \ + $(LOCAL_FLAGS_SYS) \ + + +CURRENT_OBJS = \ + $O/SfxCon.o \ + +CONSOLE_OBJS = \ + $O/ConsoleClose.o \ + $O/ExtractCallbackConsole.o \ + $O/List.o \ + $O/MainAr.o \ + $O/OpenCallbackConsole.o \ + $O/PercentPrinter.o \ + $O/UserInputUtils.o \ + +COMMON_OBJS = \ + $O/CommandLineParser.o \ + $O/CRC.o \ + $O/IntToString.o \ + $O/MyString.o \ + $O/MyVector.o \ + $O/NewHandler.o \ + $O/Sha256Prepare.o \ + $O/StdInStream.o \ + $O/StdOutStream.o \ + $O/StringConvert.o \ + $O/UTFConvert.o \ + $O/Wildcard.o \ + +WIN_OBJS = \ + \ + $O/ErrorMsg.o \ + $O/FileDir.o \ + $O/FileFind.o \ + $O/FileIO.o \ + $O/FileName.o \ + $O/PropVariant.o \ + $O/PropVariantConv.o \ + \ + $O/System.o \ + $O/TimeUtils.o \ + +7ZIP_COMMON_OBJS = \ + $O/CreateCoder.o \ + $O/CWrappers.o \ + $O/FilePathAutoRename.o \ + $O/FileStreams.o \ + $O/InBuffer.o \ + $O/FilterCoder.o \ + $O/LimitedStreams.o \ + $O/OutBuffer.o \ + $O/ProgressUtils.o \ + $O/PropId.o \ + \ + $O/StreamObjects.o \ + $O/StreamUtils.o \ + \ + +UI_COMMON_OBJS = \ + $O/ArchiveExtractCallback.o \ + $O/ArchiveOpenCallback.o \ + $O/DefaultName.o \ + $O/Extract.o \ + $O/ExtractingFilePath.o \ + $O/LoadCodecs.o \ + $O/OpenArchive.o \ + $O/PropIDUtils.o \ + +AR_OBJS = \ + $O/SplitHandler.o \ + +AR_COMMON_OBJS = \ + $O/CoderMixer2.o \ + $O/ItemNameUtils.o \ + $O/MultiStream.o \ + $O/OutStreamWithCRC.o \ + +7Z_OBJS = \ + $O/7zDecode.o \ + $O/7zExtract.o \ + $O/7zHandler.o \ + $O/7zIn.o \ + $O/7zRegister.o \ + +COMPRESS_OBJS = \ + $O/Bcj2Coder.o \ + $O/Bcj2Register.o \ + $O/BcjCoder.o \ + $O/BcjRegister.o \ + $O/BranchMisc.o \ + $O/BranchRegister.o \ + $O/CopyCoder.o \ + $O/CopyRegister.o \ + $O/DeltaFilter.o \ + $O/Lzma2Decoder.o \ + $O/Lzma2Register.o \ + $O/LzmaDecoder.o \ + $O/LzmaRegister.o \ + $O/PpmdDecoder.o \ + $O/PpmdRegister.o \ + +CRYPTO_OBJS = \ + $O/7zAes.o \ + $O/7zAesRegister.o \ + $O/MyAes.o \ + +C_OBJS = \ + $O/7zStream.o \ + $O/Alloc.o \ + $O/Bcj2.o \ + $O/Bra.o \ + $O/Bra86.o \ + $O/BraIA64.o \ + $O/CpuArch.o \ + $O/Delta.o \ + $O/Lzma2Dec.o \ + $O/Lzma2DecMt.o \ + $O/LzmaDec.o \ + $O/MtDec.o \ + $O/Ppmd7.o \ + $O/Ppmd7Dec.o \ + $O/Sha256.o \ + $O/Sha256Opt.o \ + $O/7zCrc.o \ + $O/7zCrcOpt.o \ + $O/Aes.o \ + $O/AesOpt.o \ + +OBJS = \ + $(LZMA_DEC_OPT_OBJS) \ + $(C_OBJS) \ + $(MT_OBJS) \ + $(SYS_OBJS) \ + $(COMMON_OBJS) \ + $(WIN_OBJS) \ + $(COMPRESS_OBJS) \ + $(CRYPTO_OBJS) \ + $(7ZIP_COMMON_OBJS) \ + $(AR_OBJS) \ + $(AR_COMMON_OBJS) \ + $(7Z_OBJS) \ + $(UI_COMMON_OBJS) \ + $(CONSOLE_OBJS) \ + $(CURRENT_OBJS) \ + +include ../../7zip_gcc.mak diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXCon/resource.rc b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXCon/resource.rc new file mode 100644 index 00000000..65762125 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXCon/resource.rc @@ -0,0 +1,9 @@ +#include "../../MyVersionInfo.rc" + +MY_VERSION_INFO_APP("7z Console SFX", "7z.sfx") + +101 ICON "7z.ico" + +#ifndef UNDER_CE +1 24 MOVEABLE PURE "../../UI/Console/Console.manifest" +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXSetup/ExtractCallbackSfx.cpp b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXSetup/ExtractCallbackSfx.cpp new file mode 100644 index 00000000..14dd2ecd --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXSetup/ExtractCallbackSfx.cpp @@ -0,0 +1,246 @@ +// ExtractCallbackSfx.h + +#include "StdAfx.h" + +#include "../../../Common/Wildcard.h" + +#include "../../../Windows/FileDir.h" +#include "../../../Windows/FileFind.h" +#include "../../../Windows/FileName.h" +#include "../../../Windows/PropVariant.h" + +#include "ExtractCallbackSfx.h" + +using namespace NWindows; +using namespace NFile; +using namespace NDir; + +static LPCSTR const kCantDeleteFile = "Cannot delete output file"; +static LPCSTR const kCantOpenFile = "Cannot open output file"; +static LPCSTR const kUnsupportedMethod = "Unsupported Method"; + +void CExtractCallbackImp::Init(IInArchive *archiveHandler, + const FString &directoryPath, + const UString &itemDefaultName, + const FILETIME &defaultMTime, + UInt32 defaultAttributes) +{ + _message.Empty(); + _isCorrupt = false; + _itemDefaultName = itemDefaultName; + _defaultMTime = defaultMTime; + _defaultAttributes = defaultAttributes; + _archiveHandler = archiveHandler; + _directoryPath = directoryPath; + NName::NormalizeDirPathPrefix(_directoryPath); +} + +HRESULT CExtractCallbackImp::Open_CheckBreak() +{ + #ifndef _NO_PROGRESS + return ProgressDialog.Sync.ProcessStopAndPause(); + #else + return S_OK; + #endif +} + +HRESULT CExtractCallbackImp::Open_SetTotal(const UInt64 * /* numFiles */, const UInt64 * /* numBytes */) +{ + return S_OK; +} + +HRESULT CExtractCallbackImp::Open_SetCompleted(const UInt64 * /* numFiles */, const UInt64 * /* numBytes */) +{ + #ifndef _NO_PROGRESS + return ProgressDialog.Sync.ProcessStopAndPause(); + #else + return S_OK; + #endif +} + +HRESULT CExtractCallbackImp::Open_Finished() +{ + return S_OK; +} + +Z7_COM7F_IMF(CExtractCallbackImp::SetTotal(UInt64 size)) +{ + #ifndef _NO_PROGRESS + ProgressDialog.Sync.SetProgress(size, 0); + #endif + return S_OK; +} + +Z7_COM7F_IMF(CExtractCallbackImp::SetCompleted(const UInt64 *completeValue)) +{ + #ifndef _NO_PROGRESS + RINOK(ProgressDialog.Sync.ProcessStopAndPause()) + if (completeValue != NULL) + ProgressDialog.Sync.SetPos(*completeValue); + #endif + return S_OK; +} + +void CExtractCallbackImp::CreateComplexDirectory(const UStringVector &dirPathParts) +{ + FString fullPath = _directoryPath; + FOR_VECTOR (i, dirPathParts) + { + fullPath += us2fs(dirPathParts[i]); + CreateDir(fullPath); + fullPath.Add_PathSepar(); + } +} + +Z7_COM7F_IMF(CExtractCallbackImp::GetStream(UInt32 index, + ISequentialOutStream **outStream, Int32 askExtractMode)) +{ + #ifndef _NO_PROGRESS + if (ProgressDialog.Sync.GetStopped()) + return E_ABORT; + #endif + _outFileStream.Release(); + + UString fullPath; + { + NCOM::CPropVariant prop; + RINOK(_archiveHandler->GetProperty(index, kpidPath, &prop)) + if (prop.vt == VT_EMPTY) + fullPath = _itemDefaultName; + else + { + if (prop.vt != VT_BSTR) + return E_FAIL; + fullPath.SetFromBstr(prop.bstrVal); + } + _filePath = fullPath; + } + + if (askExtractMode == NArchive::NExtract::NAskMode::kExtract) + { + NCOM::CPropVariant prop; + RINOK(_archiveHandler->GetProperty(index, kpidAttrib, &prop)) + if (prop.vt == VT_EMPTY) + _processedFileInfo.Attributes = _defaultAttributes; + else + { + if (prop.vt != VT_UI4) + return E_FAIL; + _processedFileInfo.Attributes = prop.ulVal; + } + + RINOK(_archiveHandler->GetProperty(index, kpidIsDir, &prop)) + _processedFileInfo.IsDir = VARIANT_BOOLToBool(prop.boolVal); + + bool isAnti = false; + { + NCOM::CPropVariant propTemp; + RINOK(_archiveHandler->GetProperty(index, kpidIsAnti, &propTemp)) + if (propTemp.vt == VT_BOOL) + isAnti = VARIANT_BOOLToBool(propTemp.boolVal); + } + + RINOK(_archiveHandler->GetProperty(index, kpidMTime, &prop)) + switch (prop.vt) + { + case VT_EMPTY: _processedFileInfo.MTime = _defaultMTime; break; + case VT_FILETIME: _processedFileInfo.MTime = prop.filetime; break; + default: return E_FAIL; + } + + UStringVector pathParts; + SplitPathToParts(fullPath, pathParts); + if (pathParts.IsEmpty()) + return E_FAIL; + + UString processedPath = fullPath; + + if (!_processedFileInfo.IsDir) + pathParts.DeleteBack(); + if (!pathParts.IsEmpty()) + { + if (!isAnti) + CreateComplexDirectory(pathParts); + } + + FString fullProcessedPath = _directoryPath + us2fs(processedPath); + + if (_processedFileInfo.IsDir) + { + _diskFilePath = fullProcessedPath; + + if (isAnti) + RemoveDir(_diskFilePath); + else + SetDirTime(_diskFilePath, NULL, NULL, &_processedFileInfo.MTime); + return S_OK; + } + + NFind::CFileInfo fileInfo; + if (fileInfo.Find(fullProcessedPath)) + { + if (!DeleteFileAlways(fullProcessedPath)) + { + _message = kCantDeleteFile; + return E_FAIL; + } + } + + if (!isAnti) + { + _outFileStreamSpec = new COutFileStream; + CMyComPtr outStreamLoc(_outFileStreamSpec); + if (!_outFileStreamSpec->Create_ALWAYS(fullProcessedPath)) + { + _message = kCantOpenFile; + return E_FAIL; + } + _outFileStream = outStreamLoc; + *outStream = outStreamLoc.Detach(); + } + _diskFilePath = fullProcessedPath; + } + else + { + *outStream = NULL; + } + return S_OK; +} + +Z7_COM7F_IMF(CExtractCallbackImp::PrepareOperation(Int32 askExtractMode)) +{ + _extractMode = (askExtractMode == NArchive::NExtract::NAskMode::kExtract); + return S_OK; +} + +Z7_COM7F_IMF(CExtractCallbackImp::SetOperationResult(Int32 resultEOperationResult)) +{ + switch (resultEOperationResult) + { + case NArchive::NExtract::NOperationResult::kOK: + break; + + default: + { + _outFileStream.Release(); + switch (resultEOperationResult) + { + case NArchive::NExtract::NOperationResult::kUnsupportedMethod: + _message = kUnsupportedMethod; + break; + default: + _isCorrupt = true; + } + return E_FAIL; + } + } + if (_outFileStream != NULL) + { + _outFileStreamSpec->SetMTime(&_processedFileInfo.MTime); + RINOK(_outFileStreamSpec->Close()) + } + _outFileStream.Release(); + if (_extractMode) + SetFileAttrib(_diskFilePath, _processedFileInfo.Attributes); + return S_OK; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXSetup/ExtractCallbackSfx.h b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXSetup/ExtractCallbackSfx.h new file mode 100644 index 00000000..b9377ae6 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXSetup/ExtractCallbackSfx.h @@ -0,0 +1,83 @@ +// ExtractCallbackSfx.h + +#ifndef ZIP7_INC_EXTRACT_CALLBACK_SFX_H +#define ZIP7_INC_EXTRACT_CALLBACK_SFX_H + +#include "resource.h" + +#include "../../../Windows/ResourceString.h" + +#include "../../Archive/IArchive.h" + +#include "../../Common/FileStreams.h" +#include "../../ICoder.h" + +#include "../../UI/FileManager/LangUtils.h" + +#ifndef _NO_PROGRESS +#include "../../UI/FileManager/ProgressDialog.h" +#endif +#include "../../UI/Common/ArchiveOpenCallback.h" + +class CExtractCallbackImp Z7_final: + public IArchiveExtractCallback, + public IOpenCallbackUI, + public CMyUnknownImp +{ + Z7_COM_UNKNOWN_IMP_0 + Z7_IFACE_COM7_IMP(IProgress) + Z7_IFACE_COM7_IMP(IArchiveExtractCallback) + Z7_IFACE_IMP(IOpenCallbackUI) + + CMyComPtr _archiveHandler; + FString _directoryPath; + UString _filePath; + FString _diskFilePath; + + bool _extractMode; + struct CProcessedFileInfo + { + FILETIME MTime; + bool IsDir; + UInt32 Attributes; + } _processedFileInfo; + + COutFileStream *_outFileStreamSpec; + CMyComPtr _outFileStream; + + UString _itemDefaultName; + FILETIME _defaultMTime; + UInt32 _defaultAttributes; + + void CreateComplexDirectory(const UStringVector &dirPathParts); +public: + #ifndef _NO_PROGRESS + CProgressDialog ProgressDialog; + #endif + + bool _isCorrupt; + UString _message; + + void Init(IInArchive *archiveHandler, + const FString &directoryPath, + const UString &itemDefaultName, + const FILETIME &defaultMTime, + UInt32 defaultAttributes); + + #ifndef _NO_PROGRESS + HRESULT StartProgressDialog(const UString &title, NWindows::CThread &thread) + { + ProgressDialog.Create(title, thread, NULL); + { + ProgressDialog.SetText(LangString(IDS_PROGRESS_EXTRACTING)); + } + + ProgressDialog.Show(SW_SHOWNORMAL); + return S_OK; + } + ~CExtractCallbackImp() { ProgressDialog.Destroy(); } + #endif + +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXSetup/ExtractEngine.cpp b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXSetup/ExtractEngine.cpp new file mode 100644 index 00000000..73ccff1b --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXSetup/ExtractEngine.cpp @@ -0,0 +1,135 @@ +// ExtractEngine.cpp + +#include "StdAfx.h" + +#include "../../../Windows/FileDir.h" +#include "../../../Windows/FileName.h" +#include "../../../Windows/Thread.h" + +#include "../../UI/Common/OpenArchive.h" + +#include "../../UI/FileManager/FormatUtils.h" +#include "../../UI/FileManager/LangUtils.h" + +#include "ExtractCallbackSfx.h" +#include "ExtractEngine.h" + +using namespace NWindows; +using namespace NFile; +using namespace NDir; + +static LPCSTR const kCantFindArchive = "Cannot find archive file"; +static LPCSTR const kCantOpenArchive = "Cannot open the file as archive"; + +struct CThreadExtracting +{ + CCodecs *Codecs; + FString FileName; + FString DestFolder; + + CExtractCallbackImp *ExtractCallbackSpec; + CMyComPtr ExtractCallback; + + CArchiveLink ArchiveLink; + HRESULT Result; + UString ErrorMessage; + + void Process2() + { + NFind::CFileInfo fi; + if (!fi.Find(FileName)) + { + ErrorMessage = kCantFindArchive; + Result = E_FAIL; + return; + } + + CObjectVector incl; + CIntVector excl; + COpenOptions options; + options.codecs = Codecs; + options.types = &incl; + options.excludedFormats = ! + options.filePath = fs2us(FileName); + + Result = ArchiveLink.Open2(options, ExtractCallbackSpec); + if (Result != S_OK) + { + ErrorMessage = kCantOpenArchive; + return; + } + + FString dirPath = DestFolder; + NName::NormalizeDirPathPrefix(dirPath); + + if (!CreateComplexDir(dirPath)) + { + ErrorMessage = MyFormatNew(IDS_CANNOT_CREATE_FOLDER, fs2us(dirPath)); + Result = E_FAIL; + return; + } + + ExtractCallbackSpec->Init(ArchiveLink.GetArchive(), dirPath, (UString)"Default", fi.MTime, 0); + + Result = ArchiveLink.GetArchive()->Extract(NULL, (UInt32)(Int32)-1 , BoolToInt(false), ExtractCallback); + } + + void Process() + { + try + { + #ifndef _NO_PROGRESS + CProgressCloser closer(ExtractCallbackSpec->ProgressDialog); + #endif + Process2(); + } + catch(...) { Result = E_FAIL; } + } + + static THREAD_FUNC_DECL MyThreadFunction(void *param) + { + ((CThreadExtracting *)param)->Process(); + return 0; + } +}; + +HRESULT ExtractArchive(CCodecs *codecs, const FString &fileName, const FString &destFolder, + bool showProgress, bool &isCorrupt, UString &errorMessage) +{ + isCorrupt = false; + CThreadExtracting t; + + t.Codecs = codecs; + t.FileName = fileName; + t.DestFolder = destFolder; + + t.ExtractCallbackSpec = new CExtractCallbackImp; + t.ExtractCallback = t.ExtractCallbackSpec; + + #ifndef _NO_PROGRESS + + if (showProgress) + { + t.ExtractCallbackSpec->ProgressDialog.IconID = IDI_ICON; + NWindows::CThread thread; + const WRes wres = thread.Create(CThreadExtracting::MyThreadFunction, &t); + if (wres != 0) + return HRESULT_FROM_WIN32(wres); + + UString title; + LangString(IDS_PROGRESS_EXTRACTING, title); + t.ExtractCallbackSpec->StartProgressDialog(title, thread); + } + else + + #endif + { + t.Process2(); + } + + errorMessage = t.ErrorMessage; + if (errorMessage.IsEmpty()) + errorMessage = t.ExtractCallbackSpec->_message; + isCorrupt = t.ExtractCallbackSpec->_isCorrupt; + return t.Result; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXSetup/ExtractEngine.h b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXSetup/ExtractEngine.h new file mode 100644 index 00000000..b7f79657 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXSetup/ExtractEngine.h @@ -0,0 +1,11 @@ +// ExtractEngine.h + +#ifndef ZIP7_INC_EXTRACT_ENGINE_H +#define ZIP7_INC_EXTRACT_ENGINE_H + +#include "../../UI/Common/LoadCodecs.h" + +HRESULT ExtractArchive(CCodecs *codecs, const FString &fileName, const FString &destFolder, + bool showProgress, bool &isCorrupt, UString &errorMessage); + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXSetup/SFXSetup.dsp b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXSetup/SFXSetup.dsp new file mode 100644 index 00000000..09683e28 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXSetup/SFXSetup.dsp @@ -0,0 +1,872 @@ +# Microsoft Developer Studio Project File - Name="SFXSetup" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Application" 0x0101 + +CFG=SFXSetup - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "SFXSetup.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "SFXSetup.mak" CFG="SFXSetup - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "SFXSetup - Win32 Release" (based on "Win32 (x86) Application") +!MESSAGE "SFXSetup - Win32 Debug" (based on "Win32 (x86) Application") +!MESSAGE "SFXSetup - Win32 ReleaseD" (based on "Win32 (x86) Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "SFXSetup - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /Gz /MT /W4 /WX /GX /O1 /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /D "Z7_EXTRACT_ONLY" /D "Z7_NO_REGISTRY" /D "Z7_SFX" /D "Z7_NO_CRYPTO" /D "Z7_NO_LONG_PATH" /D "Z7_NO_LARGE_PAGES" /Yu"StdAfx.h" /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "NDEBUG" +# ADD RSC /l 0x419 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386 +# ADD LINK32 comctl32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386 /out:"C:\Util\7zS.sfx" /opt:NOWIN98 +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "SFXSetup - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /Gz /MTd /W4 /WX /Gm /GX /ZI /Od /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /D "Z7_EXTRACT_ONLY" /D "Z7_NO_REGISTRY" /D "Z7_SFX" /D "Z7_NO_CRYPTO" /D "Z7_NO_LONG_PATH" /D "Z7_NO_LARGE_PAGES" /Yu"StdAfx.h" /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "_DEBUG" +# ADD RSC /l 0x419 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib comctl32.lib /nologo /subsystem:windows /debug /machine:I386 /out:"C:\UTIL\7zSfxS.exe" /pdbtype:sept + +!ELSEIF "$(CFG)" == "SFXSetup - Win32 ReleaseD" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "ReleaseD" +# PROP BASE Intermediate_Dir "ReleaseD" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "ReleaseD" +# PROP Intermediate_Dir "ReleaseD" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MD /W3 /GX /O1 /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /D "Z7_EXTRACT_ONLY" /D "Z7_NO_REGISTRY" /D "Z7_SFX" /Yu"StdAfx.h" /FD /c +# ADD CPP /nologo /Gz /MD /W4 /WX /GX /O1 /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /D "Z7_EXTRACT_ONLY" /D "Z7_NO_REGISTRY" /D "Z7_SFX" /D "Z7_NO_CRYPTO" /D "Z7_NO_LONG_PATH" /D "Z7_NO_LARGE_PAGES" /Yu"StdAfx.h" /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "NDEBUG" +# ADD RSC /l 0x419 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 comctl32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386 /out:"C:\UTIL\7zWinSR.exe" +# SUBTRACT BASE LINK32 /debug /nodefaultlib +# ADD LINK32 comctl32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386 /out:"C:\Util\7zSD.sfx" /opt:NOWIN98 +# SUBTRACT LINK32 /pdb:none + +!ENDIF + +# Begin Target + +# Name "SFXSetup - Win32 Release" +# Name "SFXSetup - Win32 Debug" +# Name "SFXSetup - Win32 ReleaseD" +# Begin Group "Spec" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=.\resource.rc +# End Source File +# Begin Source File + +SOURCE=.\StdAfx.cpp +# ADD CPP /Yc"StdAfx.h" +# End Source File +# Begin Source File + +SOURCE=.\StdAfx.h +# End Source File +# End Group +# Begin Group "Interface" + +# PROP Default_Filter "" +# End Group +# Begin Group "7z" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Archive\7z\7zDecode.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zDecode.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zExtract.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zHandler.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zHeader.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zIn.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zIn.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zItem.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zRegister.cpp +# End Source File +# End Group +# Begin Group "Archive Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Archive\Common\CoderMixer2.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\CoderMixer2.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\ItemNameUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\ItemNameUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\OutStreamWithCRC.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\OutStreamWithCRC.h +# End Source File +# End Group +# Begin Group "Compress" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Compress\Bcj2Coder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Bcj2Coder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Bcj2Register.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BcjCoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BcjCoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BcjRegister.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BranchMisc.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BranchMisc.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BranchRegister.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\CopyCoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\CopyCoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\CopyRegister.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\DeltaFilter.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Lzma2Decoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Lzma2Decoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Lzma2Register.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\LzmaDecoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\LzmaRegister.cpp +# End Source File +# End Group +# Begin Group "Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\Common\CommandLineParser.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\CommandLineParser.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Common.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\CRC.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\CRC.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\IntToString.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\IntToString.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyCom.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyString.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyString.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyVector.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyVector.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\NewHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\NewHandler.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StringConvert.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StringConvert.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\TextConfig.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\TextConfig.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\UTFConvert.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\UTFConvert.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Wildcard.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Wildcard.h +# End Source File +# End Group +# Begin Group "Windows" + +# PROP Default_Filter "" +# Begin Group "Control" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\Windows\Control\Dialog.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\Dialog.h +# End Source File +# End Group +# Begin Source File + +SOURCE=..\..\..\Windows\DLL.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\DLL.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\ErrorMsg.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\ErrorMsg.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileDir.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileDir.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileFind.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileFind.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileIO.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileIO.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileName.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileName.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\PropVariant.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\PropVariant.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\ResourceString.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\ResourceString.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Synchronization.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Synchronization.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\System.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\System.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\TimeUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\TimeUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Window.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Window.h +# End Source File +# End Group +# Begin Group "7zip Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Common\CreateCoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\CreateCoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\CWrappers.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\CWrappers.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FileStreams.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FileStreams.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FilterCoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FilterCoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\InBuffer.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\InBuffer.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\LimitedStreams.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\LimitedStreams.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\OutBuffer.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\OutBuffer.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\ProgressUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\ProgressUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\PropId.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamBinder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamBinder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamObjects.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamObjects.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\VirtThread.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\VirtThread.h +# End Source File +# End Group +# Begin Group "UI" + +# PROP Default_Filter "" +# Begin Group "Explorer" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\UI\Explorer\MyMessages.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Explorer\MyMessages.h +# End Source File +# End Group +# Begin Group "UI Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\UI\Common\ArchiveOpenCallback.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\ArchiveOpenCallback.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\DefaultName.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\DefaultName.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\ExtractMode.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\LoadCodecs.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\LoadCodecs.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\OpenArchive.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\OpenArchive.h +# End Source File +# End Group +# End Group +# Begin Group "File Manager" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\UI\FileManager\FormatUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\FormatUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\LangUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\ProgressDialog.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\ProgressDialog.h +# End Source File +# End Group +# Begin Group "C" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\..\C\7zCrc.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zCrc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zCrcOpt.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zStream.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zTypes.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zWindows.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Alloc.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Alloc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Bcj2.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Bcj2.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Bra.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Bra.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Bra86.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\BraIA64.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Compiler.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\CpuArch.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\CpuArch.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Delta.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Delta.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\DllSecur.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\DllSecur.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Lzma2Dec.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Lzma2Dec.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Lzma2DecMt.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Lzma2DecMt.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzmaDec.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzmaDec.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\MtDec.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\MtDec.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Threads.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Threads.h +# End Source File +# End Group +# Begin Group "7zip" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Archive\IArchive.h +# End Source File +# Begin Source File + +SOURCE=..\..\ICoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\IDecl.h +# End Source File +# Begin Source File + +SOURCE=..\..\IPassword.h +# End Source File +# Begin Source File + +SOURCE=..\..\IProgress.h +# End Source File +# End Group +# Begin Source File + +SOURCE=.\ExtractCallbackSfx.cpp +# End Source File +# Begin Source File + +SOURCE=.\ExtractCallbackSfx.h +# End Source File +# Begin Source File + +SOURCE=.\ExtractEngine.cpp +# End Source File +# Begin Source File + +SOURCE=.\ExtractEngine.h +# End Source File +# Begin Source File + +SOURCE=.\setup.ico +# End Source File +# Begin Source File + +SOURCE=.\SfxSetup.cpp +# End Source File +# End Target +# End Project diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXSetup/SFXSetup.dsw b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXSetup/SFXSetup.dsw new file mode 100644 index 00000000..f563b21f --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXSetup/SFXSetup.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "SFXSetup"=.\SFXSetup.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXSetup/SfxSetup.cpp b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXSetup/SfxSetup.cpp new file mode 100644 index 00000000..20fa952b --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXSetup/SfxSetup.cpp @@ -0,0 +1,359 @@ +// Main.cpp + +#include "StdAfx.h" + +#include "../../../../C/DllSecur.h" + +#include "../../../Common/MyWindows.h" +#include "../../../Common/MyInitGuid.h" + +#include "../../../Common/CommandLineParser.h" +#include "../../../Common/StringConvert.h" +#include "../../../Common/TextConfig.h" + +#include "../../../Windows/DLL.h" +#include "../../../Windows/ErrorMsg.h" +#include "../../../Windows/FileDir.h" +#include "../../../Windows/FileFind.h" +#include "../../../Windows/FileIO.h" +#include "../../../Windows/FileName.h" +#include "../../../Windows/NtCheck.h" +#include "../../../Windows/ResourceString.h" + +#include "../../UI/Explorer/MyMessages.h" + +#include "ExtractEngine.h" + +#include "resource.h" + +using namespace NWindows; +using namespace NFile; +using namespace NDir; + +extern +HINSTANCE g_hInstance; +HINSTANCE g_hInstance; +extern +bool g_DisableUserQuestions; +bool g_DisableUserQuestions; + +static CFSTR const kTempDirPrefix = FTEXT("7zS"); + +#define MY_SHELL_EXECUTE + +static const char kStartID[] = { ',','!','@','I','n','s','t','a','l','l','@','!','U','T','F','-','8','!' }; +static const char kEndID[] = { ',','!','@','I','n','s','t','a','l','l','E','n','d','@','!' }; + +static bool ReadDataString(CFSTR fileName, AString &stringResult) +{ + // stringResult.Empty(); // (stringResult) is empty already + NIO::CInFile inFile; + if (!inFile.Open(fileName)) + return false; + const size_t kBufferSize = 1 << 12; + + Byte buffer[kBufferSize]; + const size_t startSize1 = sizeof(kStartID) - 1; + const size_t endSize1 = sizeof(kEndID) - 1; + + size_t numBytesPrev = 0; + bool scanMode = true; + UInt32 posTotal = 0; + for (;;) + { + const size_t numReadBytes = kBufferSize - numBytesPrev; + size_t processedSize; + if (!inFile.ReadFull(buffer + numBytesPrev, numReadBytes, processedSize)) + return false; + if (processedSize == 0) + break; + numBytesPrev += processedSize; + size_t pos = 0; + for (;;) + { + if (!scanMode) + { + if (pos + endSize1 >= numBytesPrev) + break; + const Byte b = buffer[pos++]; + if (b == 0) + return false; + if (b == ';' && memcmp(buffer + pos, kEndID + 1, endSize1) == 0) + return true; + stringResult += (char)b; + } + else + { + if (pos + startSize1 >= numBytesPrev) + break; + const Byte b = buffer[pos++]; + if (b == ';' && memcmp(buffer + pos, kStartID + 1, startSize1) == 0) + { + scanMode = false; + pos += startSize1; + } + } + } + posTotal += (UInt32)pos; + if (posTotal > (1 << 21)) + break; + numBytesPrev -= pos; + memmove(buffer, buffer + pos, numBytesPrev); + } + return scanMode; +} + +#if defined(_WIN32) && defined(_UNICODE) && !defined(_WIN64) && !defined(UNDER_CE) +#define NT_CHECK_FAIL_ACTION ShowErrorMessage(L"Unsupported Windows version"); return 1; +#endif + +static void ShowErrorMessageSpec(const UString &name) +{ + UString message = NError::MyFormatMessage(::GetLastError()); + const int pos = message.Find(L"%1"); + if (pos >= 0) + { + message.Delete((unsigned)pos, 2); + message.Insert((unsigned)pos, name); + } + ShowErrorMessage(NULL, message); +} + +int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE /* hPrevInstance */, + #ifdef UNDER_CE + LPWSTR + #else + LPSTR + #endif + /* lpCmdLine */,int /* nCmdShow */) +{ + g_hInstance = (HINSTANCE)hInstance; + + NT_CHECK + + #ifdef _WIN32 + LoadSecurityDlls(); + #endif + + // InitCommonControls(); + + UString archiveName, switches; + #ifdef MY_SHELL_EXECUTE + UString executeFile, executeParameters; + #endif + NCommandLineParser::SplitCommandLine(GetCommandLineW(), archiveName, switches); + + FString fullPath; + NDLL::MyGetModuleFileName(fullPath); + + switches.Trim(); + bool assumeYes = false; + if (switches.IsPrefixedBy_Ascii_NoCase("-y")) + { + assumeYes = true; + switches = switches.Ptr(2); + switches.Trim(); + } + + AString config; + if (!ReadDataString(fullPath, config)) + { + if (!assumeYes) + ShowErrorMessage(L"Can't load config info"); + return 1; + } + + UString dirPrefix ("." STRING_PATH_SEPARATOR); + UString appLaunched; + bool showProgress = true; + if (!config.IsEmpty()) + { + CObjectVector pairs; + if (!GetTextConfig(config, pairs)) + { + if (!assumeYes) + ShowErrorMessage(L"Config failed"); + return 1; + } + const UString friendlyName = GetTextConfigValue(pairs, "Title"); + const UString installPrompt = GetTextConfigValue(pairs, "BeginPrompt"); + const UString progress = GetTextConfigValue(pairs, "Progress"); + if (progress.IsEqualTo_Ascii_NoCase("no")) + showProgress = false; + const int index = FindTextConfigItem(pairs, "Directory"); + if (index >= 0) + dirPrefix = pairs[index].String; + if (!installPrompt.IsEmpty() && !assumeYes) + { + if (MessageBoxW(NULL, installPrompt, friendlyName, MB_YESNO | + MB_ICONQUESTION) != IDYES) + return 0; + } + appLaunched = GetTextConfigValue(pairs, "RunProgram"); + + #ifdef MY_SHELL_EXECUTE + executeFile = GetTextConfigValue(pairs, "ExecuteFile"); + executeParameters = GetTextConfigValue(pairs, "ExecuteParameters"); + #endif + } + + CTempDir tempDir; + if (!tempDir.Create(kTempDirPrefix)) + { + if (!assumeYes) + ShowErrorMessage(L"Cannot create temp folder archive"); + return 1; + } + + CCodecs *codecs = new CCodecs; + CMyComPtr compressCodecsInfo = codecs; + { + const HRESULT result = codecs->Load(); + if (result != S_OK) + { + ShowErrorMessage(L"Cannot load codecs"); + return 1; + } + } + + const FString tempDirPath = tempDir.GetPath(); + // tempDirPath = "M:\\1\\"; // to test low disk space + { + bool isCorrupt = false; + UString errorMessage; + HRESULT result = ExtractArchive(codecs, fullPath, tempDirPath, showProgress, + isCorrupt, errorMessage); + + if (result != S_OK) + { + if (!assumeYes) + { + if (result == S_FALSE || isCorrupt) + { + NWindows::MyLoadString(IDS_EXTRACTION_ERROR_MESSAGE, errorMessage); + result = E_FAIL; + } + if (result != E_ABORT) + { + if (errorMessage.IsEmpty()) + errorMessage = NError::MyFormatMessage(result); + ::MessageBoxW(NULL, errorMessage, NWindows::MyLoadString(IDS_EXTRACTION_ERROR_TITLE), MB_ICONERROR); + } + } + return 1; + } + } + + #ifndef UNDER_CE + CCurrentDirRestorer currentDirRestorer; + if (!SetCurrentDir(tempDirPath)) + return 1; + #endif + + HANDLE hProcess = NULL; +#ifdef MY_SHELL_EXECUTE + if (!executeFile.IsEmpty()) + { + CSysString filePath (GetSystemString(executeFile)); + SHELLEXECUTEINFO execInfo; + execInfo.cbSize = sizeof(execInfo); + execInfo.fMask = SEE_MASK_NOCLOSEPROCESS + #ifndef UNDER_CE + | SEE_MASK_FLAG_DDEWAIT + #endif + ; + execInfo.hwnd = NULL; + execInfo.lpVerb = NULL; + execInfo.lpFile = filePath; + + if (!switches.IsEmpty()) + { + executeParameters.Add_Space_if_NotEmpty(); + executeParameters += switches; + } + + const CSysString parametersSys (GetSystemString(executeParameters)); + if (parametersSys.IsEmpty()) + execInfo.lpParameters = NULL; + else + execInfo.lpParameters = parametersSys; + + execInfo.lpDirectory = NULL; + execInfo.nShow = SW_SHOWNORMAL; + execInfo.hProcess = NULL; + /* BOOL success = */ ::ShellExecuteEx(&execInfo); + UINT32 result = (UINT32)(UINT_PTR)execInfo.hInstApp; + if (result <= 32) + { + if (!assumeYes) + ShowErrorMessage(L"Cannot open file"); + return 1; + } + hProcess = execInfo.hProcess; + } + else +#endif + { + if (appLaunched.IsEmpty()) + { + appLaunched = "setup.exe"; + if (!NFind::DoesFileExist_FollowLink(us2fs(appLaunched))) + { + if (!assumeYes) + ShowErrorMessage(L"Cannot find setup.exe"); + return 1; + } + } + + { + FString s2 = tempDirPath; + NName::NormalizeDirPathPrefix(s2); + appLaunched.Replace(L"%%T" WSTRING_PATH_SEPARATOR, fs2us(s2)); + } + + const UString appNameForError = appLaunched; // actually we need to rtemove parameters also + + appLaunched.Replace(L"%%T", fs2us(tempDirPath)); + + if (!switches.IsEmpty()) + { + appLaunched.Add_Space(); + appLaunched += switches; + } + STARTUPINFO startupInfo; + startupInfo.cb = sizeof(startupInfo); + startupInfo.lpReserved = NULL; + startupInfo.lpDesktop = NULL; + startupInfo.lpTitle = NULL; + startupInfo.dwFlags = 0; + startupInfo.cbReserved2 = 0; + startupInfo.lpReserved2 = NULL; + + PROCESS_INFORMATION processInformation; + + const CSysString appLaunchedSys (GetSystemString(dirPrefix + appLaunched)); + + const BOOL createResult = CreateProcess(NULL, + appLaunchedSys.Ptr_non_const(), + NULL, NULL, FALSE, 0, NULL, NULL /*tempDir.GetPath() */, + &startupInfo, &processInformation); + if (createResult == 0) + { + if (!assumeYes) + { + // we print name of exe file, if error message is + // ERROR_BAD_EXE_FORMAT: "%1 is not a valid Win32 application". + ShowErrorMessageSpec(appNameForError); + } + return 1; + } + ::CloseHandle(processInformation.hThread); + hProcess = processInformation.hProcess; + } + if (hProcess) + { + WaitForSingleObject(hProcess, INFINITE); + ::CloseHandle(hProcess); + } + return 0; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXSetup/StdAfx.cpp b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXSetup/StdAfx.cpp new file mode 100644 index 00000000..d0feea85 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXSetup/StdAfx.cpp @@ -0,0 +1,3 @@ +// StdAfx.cpp + +#include "StdAfx.h" diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXSetup/StdAfx.h b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXSetup/StdAfx.h new file mode 100644 index 00000000..4f27255c --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXSetup/StdAfx.h @@ -0,0 +1,6 @@ +// StdAfx.h + +#if _MSC_VER >= 1800 +#pragma warning(disable : 4464) // relative include path contains '..' +#endif +#include "../../UI/FileManager/StdAfx.h" diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXSetup/makefile b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXSetup/makefile new file mode 100644 index 00000000..4f6c5fcc --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXSetup/makefile @@ -0,0 +1,121 @@ +PROG = 7zS.sfx +MY_FIXED = 1 + +CFLAGS = $(CFLAGS) \ + -DZ7_NO_REGISTRY \ + -DZ7_EXTRACT_ONLY \ + -DZ7_NO_READ_FROM_CODER \ + -DZ7_SFX \ + -DZ7_NO_CRYPTO \ + -DZ7_NO_LONG_PATH \ + -DZ7_NO_LARGE_PAGES \ + +CURRENT_OBJS = \ + $O\SfxSetup.obj \ + $O\ExtractCallbackSfx.obj \ + $O\ExtractEngine.obj \ + +COMMON_OBJS = \ + $O\CommandLineParser.obj \ + $O\CRC.obj \ + $O\IntToString.obj \ + $O\NewHandler.obj \ + $O\MyString.obj \ + $O\StringConvert.obj \ + $O\TextConfig.obj \ + $O\UTFConvert.obj \ + $O\MyVector.obj \ + $O\Wildcard.obj \ + +WIN_OBJS = \ + $O\DLL.obj \ + $O\ErrorMsg.obj \ + $O\FileDir.obj \ + $O\FileFind.obj \ + $O\FileIO.obj \ + $O\FileName.obj \ + $O\PropVariant.obj \ + $O\ResourceString.obj \ + $O\Synchronization.obj \ + $O\System.obj \ + $O\TimeUtils.obj \ + $O\Window.obj \ + +WIN_CTRL_OBJS = \ + $O\Dialog.obj \ + +7ZIP_COMMON_OBJS = \ + $O\CreateCoder.obj \ + $O\CWrappers.obj \ + $O\FileStreams.obj \ + $O\InBuffer.obj \ + $O\FilterCoder.obj \ + $O\LimitedStreams.obj \ + $O\OutBuffer.obj \ + $O\ProgressUtils.obj \ + $O\PropId.obj \ + $O\StreamBinder.obj \ + $O\StreamObjects.obj \ + $O\StreamUtils.obj \ + $O\VirtThread.obj \ + +UI_COMMON_OBJS = \ + $O\ArchiveOpenCallback.obj \ + $O\DefaultName.obj \ + $O\LoadCodecs.obj \ + $O\OpenArchive.obj \ + +EXPLORER_OBJS = \ + $O\MyMessages.obj \ + +FM_OBJS = \ + $O\FormatUtils.obj \ + $O\ProgressDialog.obj \ + +AR_COMMON_OBJS = \ + $O\CoderMixer2.obj \ + $O\ItemNameUtils.obj \ + $O\OutStreamWithCRC.obj \ + +7Z_OBJS = \ + $O\7zDecode.obj \ + $O\7zExtract.obj \ + $O\7zHandler.obj \ + $O\7zIn.obj \ + $O\7zRegister.obj \ + +COMPRESS_OBJS = \ + $O\Bcj2Coder.obj \ + $O\Bcj2Register.obj \ + $O\BcjCoder.obj \ + $O\BcjRegister.obj \ + $O\BranchMisc.obj \ + $O\BranchRegister.obj \ + $O\CopyCoder.obj \ + $O\CopyRegister.obj \ + $O\DeltaFilter.obj \ + $O\Lzma2Decoder.obj \ + $O\Lzma2Register.obj \ + $O\LzmaDecoder.obj \ + $O\LzmaRegister.obj \ + +C_OBJS = \ + $O\7zStream.obj \ + $O\Alloc.obj \ + $O\Bcj2.obj \ + $O\Bra.obj \ + $O\Bra86.obj \ + $O\BraIA64.obj \ + $O\CpuArch.obj \ + $O\Delta.obj \ + $O\DllSecur.obj \ + $O\Lzma2Dec.obj \ + $O\Lzma2DecMt.obj \ + $O\LzmaDec.obj \ + $O\MtDec.obj \ + $O\Threads.obj \ + +!include "../../Crc.mak" +!include "../../LzmaDec.mak" + +!include "../../7zip.mak" diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXSetup/resource.h b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXSetup/resource.h new file mode 100644 index 00000000..d5f440bb --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXSetup/resource.h @@ -0,0 +1,6 @@ +#define IDI_ICON 1 + +#define IDS_EXTRACTION_ERROR_TITLE 7 +#define IDS_EXTRACTION_ERROR_MESSAGE 8 +#define IDS_CANNOT_CREATE_FOLDER 3003 +#define IDS_PROGRESS_EXTRACTING 3300 diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXSetup/resource.rc b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXSetup/resource.rc new file mode 100644 index 00000000..47e1b762 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXSetup/resource.rc @@ -0,0 +1,16 @@ +#include "../../MyVersionInfo.rc" +#include "resource.h" + +MY_VERSION_INFO_APP("7z Setup SFX", "7zS.sfx") + +IDI_ICON ICON "setup.ico" + +STRINGTABLE +BEGIN + IDS_EXTRACTION_ERROR_TITLE "Extraction Failed" + IDS_EXTRACTION_ERROR_MESSAGE "File is corrupt" + IDS_CANNOT_CREATE_FOLDER "Cannot create folder '{0}'" + IDS_PROGRESS_EXTRACTING "Extracting" +END + +#include "../../UI/FileManager/ProgressDialog.rc" diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXSetup/setup.ico b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXSetup/setup.ico new file mode 100644 index 00000000..bb455be1 Binary files /dev/null and b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXSetup/setup.ico differ diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXWin/7z.ico b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXWin/7z.ico new file mode 100644 index 00000000..47ffb781 Binary files /dev/null and b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXWin/7z.ico differ diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXWin/SFXWin.dsp b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXWin/SFXWin.dsp new file mode 100644 index 00000000..18db7f83 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXWin/SFXWin.dsp @@ -0,0 +1,1074 @@ +# Microsoft Developer Studio Project File - Name="SFXWin" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Application" 0x0101 + +CFG=SFXWin - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "SFXWin.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "SFXWin.mak" CFG="SFXWin - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "SFXWin - Win32 Release" (based on "Win32 (x86) Application") +!MESSAGE "SFXWin - Win32 Debug" (based on "Win32 (x86) Application") +!MESSAGE "SFXWin - Win32 ReleaseD" (based on "Win32 (x86) Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "SFXWin - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /Gz /MD /W4 /WX /GX /O1 /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /D "Z7_EXTRACT_ONLY" /D "Z7_NO_REGISTRY" /D "Z7_NO_READ_FROM_CODER" /D "Z7_SFX" /D "Z7_NO_LONG_PATH" /D "Z7_NO_LARGE_PAGES" /Yu"StdAfx.h" /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "NDEBUG" +# ADD RSC /l 0x419 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386 /out:"C:\Util\7z.sfx" /opt:NOWIN98 +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "SFXWin - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /Gz /MTd /W4 /WX /Gm /GX /ZI /Od /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /D "Z7_EXTRACT_ONLY" /D "Z7_NO_REGISTRY" /D "Z7_NO_READ_FROM_CODER" /D "Z7_SFX" /D "Z7_NO_LONG_PATH" /D "Z7_NO_LARGE_PAGES" /Yu"StdAfx.h" /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "_DEBUG" +# ADD RSC /l 0x419 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /out:"C:\Util\7zsfx.exe" /pdbtype:sept + +!ELSEIF "$(CFG)" == "SFXWin - Win32 ReleaseD" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "SFXWin___Win32_ReleaseD" +# PROP BASE Intermediate_Dir "SFXWin___Win32_ReleaseD" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "SFXWin___Win32_ReleaseD" +# PROP Intermediate_Dir "SFXWin___Win32_ReleaseD" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /Gz /MT /W3 /GX /O1 /I "..\..\..\\" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /D "Z7_EXTRACT_ONLY" /D "Z7_NO_REGISTRY" /D "Z7_SFX" /Yu"StdAfx.h" /FD /c +# ADD CPP /nologo /Gz /MD /W4 /WX /GX /O1 /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /D "Z7_EXTRACT_ONLY" /D "Z7_NO_REGISTRY" /D "Z7_NO_READ_FROM_CODER" /D "Z7_SFX" /D "Z7_NO_LONG_PATH" /D "Z7_NO_LARGE_PAGES" /Yu"StdAfx.h" /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "NDEBUG" +# ADD RSC /l 0x419 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386 /out:"C:\Util\7z.sfx" /opt:NOWIN98 +# SUBTRACT BASE LINK32 /pdb:none +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386 /out:"C:\Util\7zD.sfx" /opt:NOWIN98 +# SUBTRACT LINK32 /pdb:none + +!ENDIF + +# Begin Target + +# Name "SFXWin - Win32 Release" +# Name "SFXWin - Win32 Debug" +# Name "SFXWin - Win32 ReleaseD" +# Begin Group "Spec" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=.\StdAfx.cpp +# ADD CPP /Yc"StdAfx.h" +# End Source File +# Begin Source File + +SOURCE=.\StdAfx.h +# End Source File +# End Group +# Begin Group "7z" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Archive\7z\7zDecode.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zDecode.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zExtract.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zHandler.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zHeader.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zIn.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zIn.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\7z\7zRegister.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\SplitHandler.cpp +# End Source File +# End Group +# Begin Group "Archive Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Archive\Common\CoderMixer2.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\CoderMixer2.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\ItemNameUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\ItemNameUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\MultiStream.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\MultiStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\OutStreamWithCRC.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\OutStreamWithCRC.h +# End Source File +# End Group +# Begin Group "Compress" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Compress\Bcj2Coder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Bcj2Coder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Bcj2Register.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BcjCoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BcjCoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BcjRegister.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BranchMisc.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BranchMisc.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\BranchRegister.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\CopyCoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\CopyCoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\CopyRegister.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\DeltaFilter.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Lzma2Decoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\Lzma2Register.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\LzmaDecoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\LzmaRegister.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\PpmdDecoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\PpmdDecoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\PpmdRegister.cpp +# End Source File +# End Group +# Begin Group "Crypto" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Crypto\7zAes.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\7zAes.h +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\7zAesRegister.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\MyAes.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Crypto\MyAes.h +# End Source File +# End Group +# Begin Group "Dialogs" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\UI\FileManager\BrowseDialog.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\BrowseDialog.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\ComboDialog.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\ComboDialog.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\OverwriteDialog.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\OverwriteDialog.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\PasswordDialog.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\PasswordDialog.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\ProgressDialog2.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\ProgressDialog2.h +# End Source File +# End Group +# Begin Group "7zip Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Common\CreateCoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\CreateCoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\CWrappers.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\CWrappers.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FilePathAutoRename.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FilePathAutoRename.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FileStreams.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FileStreams.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FilterCoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FilterCoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\InBuffer.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\InBuffer.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\LimitedStreams.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\LimitedStreams.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\LockedStream.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\LockedStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\OutBuffer.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\OutBuffer.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\ProgressUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\ProgressUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\PropId.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamBinder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamBinder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamObjects.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamObjects.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\VirtThread.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\VirtThread.h +# End Source File +# End Group +# Begin Group "File Manager" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\UI\FileManager\ExtractCallback.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\ExtractCallback.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\FormatUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\FormatUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\PropertyName.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\PropertyName.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\SysIconUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\FileManager\SysIconUtils.h +# End Source File +# End Group +# Begin Group "Windows" + +# PROP Default_Filter "" +# Begin Group "Control" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\Windows\Control\ComboBox.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\ComboBox.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\Dialog.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\Dialog.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\ListView.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\ListView.h +# End Source File +# End Group +# Begin Source File + +SOURCE=..\..\..\Windows\Clipboard.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Clipboard.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\CommonDialog.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\CommonDialog.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\DLL.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\DLL.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\ErrorMsg.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\ErrorMsg.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileDir.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileDir.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileFind.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileFind.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileIO.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileIO.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileName.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileName.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\MemoryGlobal.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\MemoryGlobal.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\PropVariant.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\PropVariant.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\PropVariantConv.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\PropVariantConv.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\ResourceString.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\ResourceString.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Shell.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Shell.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Synchronization.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Synchronization.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\System.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\System.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\TimeUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\TimeUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Window.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Window.h +# End Source File +# End Group +# Begin Group "Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\Common\CommandLineParser.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\CommandLineParser.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Common.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\CRC.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\CRC.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\IntToString.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\IntToString.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyString.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyString.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyVector.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyVector.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\NewHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\NewHandler.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Sha256Prepare.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StringConvert.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StringConvert.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Wildcard.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Wildcard.h +# End Source File +# End Group +# Begin Group "UI" + +# PROP Default_Filter "" +# Begin Group "UI Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\UI\Common\ArchiveExtractCallback.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\ArchiveExtractCallback.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\ArchiveOpenCallback.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\ArchiveOpenCallback.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\DefaultName.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\DefaultName.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\Extract.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\Extract.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\ExtractingFilePath.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\ExtractingFilePath.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\LoadCodecs.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\LoadCodecs.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\OpenArchive.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Common\OpenArchive.h +# End Source File +# End Group +# Begin Group "GUI" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\UI\GUI\ExtractDialog.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\GUI\ExtractDialog.h +# End Source File +# Begin Source File + +SOURCE=..\..\UI\GUI\ExtractGUI.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\GUI\ExtractGUI.h +# End Source File +# End Group +# Begin Group "Explorer" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\UI\Explorer\MyMessages.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\UI\Explorer\MyMessages.h +# End Source File +# End Group +# End Group +# Begin Group "C" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\..\C\7zCrc.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zCrc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zCrcOpt.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zStream.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zWindows.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Aes.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Aes.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\AesOpt.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Alloc.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Alloc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Bcj2.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Bcj2.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Bra.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Bra.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Bra86.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\BraIA64.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Compiler.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\CpuArch.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Delta.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Delta.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\DllSecur.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\DllSecur.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Lzma2Dec.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Lzma2Dec.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Lzma2DecMt.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Lzma2DecMt.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzmaDec.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\LzmaDec.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\MtDec.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\MtDec.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Ppmd7.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Ppmd7.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Ppmd7Dec.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Sha256.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Sha256Opt.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Threads.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Threads.h +# End Source File +# End Group +# Begin Group "7zip" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Archive\IArchive.h +# End Source File +# Begin Source File + +SOURCE=..\..\ICoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\IDecl.h +# End Source File +# Begin Source File + +SOURCE=..\..\IPassword.h +# End Source File +# Begin Source File + +SOURCE=..\..\IProgress.h +# End Source File +# End Group +# Begin Source File + +SOURCE=.\7z.ico +# End Source File +# Begin Source File + +SOURCE=.\resource.rc +# End Source File +# Begin Source File + +SOURCE=.\SfxWin.cpp +# End Source File +# End Target +# End Project diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXWin/SFXWin.dsw b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXWin/SFXWin.dsw new file mode 100644 index 00000000..a9926c71 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXWin/SFXWin.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "SFXWin"=.\SFXWin.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXWin/SfxWin.cpp b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXWin/SfxWin.cpp new file mode 100644 index 00000000..de2ffa4b --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXWin/SfxWin.cpp @@ -0,0 +1,268 @@ +// Main.cpp + +#include "StdAfx.h" + +#include "../../../Common/MyWindows.h" + +#if defined(__MINGW32__) || defined(__MINGW64__) +#include +#else +#include +#endif + +#include "../../../../C/DllSecur.h" + +#include "../../../Common/MyInitGuid.h" + +#include "../../../Common/CommandLineParser.h" +#include "../../../Common/StringConvert.h" + +#include "../../../Windows/DLL.h" +#include "../../../Windows/ErrorMsg.h" +#include "../../../Windows/FileDir.h" +#include "../../../Windows/FileName.h" +#include "../../../Windows/NtCheck.h" +#include "../../../Windows/ResourceString.h" + +#include "../../ICoder.h" +#include "../../IPassword.h" +#include "../../Archive/IArchive.h" +#include "../../UI/Common/Extract.h" +#include "../../UI/Common/ExitCode.h" +#include "../../UI/Explorer/MyMessages.h" +#include "../../UI/FileManager/MyWindowsNew.h" +#include "../../UI/GUI/ExtractGUI.h" +#include "../../UI/GUI/ExtractRes.h" + +using namespace NWindows; +using namespace NFile; +using namespace NDir; + +extern +HINSTANCE g_hInstance; +HINSTANCE g_hInstance; +extern +bool g_DisableUserQuestions; +bool g_DisableUserQuestions; + +#ifndef UNDER_CE + +#if !defined(Z7_WIN32_WINNT_MIN) || Z7_WIN32_WINNT_MIN < 0x0500 // win2000 +#define Z7_USE_DYN_ComCtl32Version +#endif + +#ifdef Z7_USE_DYN_ComCtl32Version +Z7_DIAGNOSTIC_IGNORE_CAST_FUNCTION + +static DWORD GetDllVersion(LPCTSTR dllName) +{ + DWORD dwVersion = 0; + const HINSTANCE hinstDll = LoadLibrary(dllName); + if (hinstDll) + { + const + DLLGETVERSIONPROC func_DllGetVersion = Z7_GET_PROC_ADDRESS( + DLLGETVERSIONPROC, hinstDll, "DllGetVersion"); + if (func_DllGetVersion) + { + DLLVERSIONINFO dvi; + ZeroMemory(&dvi, sizeof(dvi)); + dvi.cbSize = sizeof(dvi); + const HRESULT hr = func_DllGetVersion(&dvi); + if (SUCCEEDED(hr)) + dwVersion = (DWORD)MAKELONG(dvi.dwMinorVersion, dvi.dwMajorVersion); + } + FreeLibrary(hinstDll); + } + return dwVersion; +} + +#endif +#endif + +extern +bool g_LVN_ITEMACTIVATE_Support; +bool g_LVN_ITEMACTIVATE_Support = true; + +static const wchar_t * const kUnknownExceptionMessage = L"ERROR: Unknown Error!"; + +static void ErrorMessageForHRESULT(HRESULT res) +{ + ShowErrorMessage(HResultToMessage(res)); +} + +static int APIENTRY WinMain2() +{ + // OleInitialize is required for ProgressBar in TaskBar. +#ifndef UNDER_CE + OleInitialize(NULL); +#endif + +#ifndef UNDER_CE +#ifdef Z7_USE_DYN_ComCtl32Version + { + const DWORD g_ComCtl32Version = ::GetDllVersion(TEXT("comctl32.dll")); + g_LVN_ITEMACTIVATE_Support = (g_ComCtl32Version >= MAKELONG(71, 4)); + } +#endif +#endif + + UString password; + bool assumeYes = false; + bool outputFolderDefined = false; + FString outputFolder; + UStringVector commandStrings; + NCommandLineParser::SplitCommandLine(GetCommandLineW(), commandStrings); + + #ifndef UNDER_CE + if (commandStrings.Size() > 0) + commandStrings.Delete(0); + #endif + + FOR_VECTOR (i, commandStrings) + { + const UString &s = commandStrings[i]; + if (s.Len() > 1 && s[0] == '-') + { + const wchar_t c = MyCharLower_Ascii(s[1]); + if (c == 'y') + { + assumeYes = true; + if (s.Len() != 2) + { + ShowErrorMessage(L"Bad command"); + return 1; + } + } + else if (c == 'o') + { + outputFolder = us2fs(s.Ptr(2)); + NName::NormalizeDirPathPrefix(outputFolder); + outputFolderDefined = !outputFolder.IsEmpty(); + } + else if (c == 'p') + { + password = s.Ptr(2); + } + } + } + + g_DisableUserQuestions = assumeYes; + + FString path; + NDLL::MyGetModuleFileName(path); + + FString fullPath; + if (!MyGetFullPathName(path, fullPath)) + { + ShowErrorMessage(L"Error 1329484"); + return 1; + } + + CCodecs *codecs = new CCodecs; + CMyComPtr compressCodecsInfo = codecs; + HRESULT result = codecs->Load(); + if (result != S_OK) + { + ErrorMessageForHRESULT(result); + return 1; + } + + // COpenCallbackGUI openCallback; + + // openCallback.PasswordIsDefined = !password.IsEmpty(); + // openCallback.Password = password; + + CExtractCallbackImp *ecs = new CExtractCallbackImp; + CMyComPtr extractCallback = ecs; + ecs->Init(); + + #ifndef Z7_NO_CRYPTO + ecs->PasswordIsDefined = !password.IsEmpty(); + ecs->Password = password; + #endif + + CExtractOptions eo; + + FString dirPrefix; + if (!GetOnlyDirPrefix(path, dirPrefix)) + { + ShowErrorMessage(L"Error 1329485"); + return 1; + } + + eo.OutputDir = outputFolderDefined ? outputFolder : dirPrefix; + eo.YesToAll = assumeYes; + eo.OverwriteMode = assumeYes ? + NExtract::NOverwriteMode::kOverwrite : + NExtract::NOverwriteMode::kAsk; + eo.PathMode = NExtract::NPathMode::kFullPaths; + eo.TestMode = false; + + UStringVector v1, v2; + v1.Add(fs2us(fullPath)); + v2.Add(fs2us(fullPath)); + NWildcard::CCensorNode wildcardCensor; + wildcardCensor.Add_Wildcard(); + + bool messageWasDisplayed = false; + result = ExtractGUI(codecs, + CObjectVector(), CIntVector(), + v1, v2, + wildcardCensor, eo, (assumeYes ? false: true), messageWasDisplayed, ecs); + + if (result == S_OK) + { + if (!ecs->IsOK()) + return NExitCode::kFatalError; + return 0; + } + if (result == E_ABORT) + return NExitCode::kUserBreak; + if (!messageWasDisplayed) + { + if (result == S_FALSE) + ShowErrorMessage(L"Error in archive"); + else + ErrorMessageForHRESULT(result); + } + if (result == E_OUTOFMEMORY) + return NExitCode::kMemoryError; + return NExitCode::kFatalError; +} + +#if defined(_WIN32) && defined(_UNICODE) && !defined(_WIN64) && !defined(UNDER_CE) +#define NT_CHECK_FAIL_ACTION ShowErrorMessage(L"Unsupported Windows version"); return NExitCode::kFatalError; +#endif + +int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE /* hPrevInstance */, + #ifdef UNDER_CE + LPWSTR + #else + LPSTR + #endif + /* lpCmdLine */, int /* nCmdShow */) +{ + g_hInstance = (HINSTANCE)hInstance; + + NT_CHECK + + try + { + #ifdef _WIN32 + LoadSecurityDlls(); + #endif + + return WinMain2(); + } + catch(const CNewException &) + { + ErrorMessageForHRESULT(E_OUTOFMEMORY); + return NExitCode::kMemoryError; + } + catch(...) + { + ShowErrorMessage(kUnknownExceptionMessage); + return NExitCode::kFatalError; + } +} diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXWin/StdAfx.cpp b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXWin/StdAfx.cpp new file mode 100644 index 00000000..d0feea85 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXWin/StdAfx.cpp @@ -0,0 +1,3 @@ +// StdAfx.cpp + +#include "StdAfx.h" diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXWin/StdAfx.h b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXWin/StdAfx.h new file mode 100644 index 00000000..4f27255c --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXWin/StdAfx.h @@ -0,0 +1,6 @@ +// StdAfx.h + +#if _MSC_VER >= 1800 +#pragma warning(disable : 4464) // relative include path contains '..' +#endif +#include "../../UI/FileManager/StdAfx.h" diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXWin/makefile b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXWin/makefile new file mode 100644 index 00000000..806bd073 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXWin/makefile @@ -0,0 +1,159 @@ +PROG = 7z.sfx +MY_FIXED = 1 + +CFLAGS = $(CFLAGS) \ + -DZ7_NO_REGISTRY \ + -DZ7_EXTRACT_ONLY \ + -DZ7_NO_READ_FROM_CODER \ + -DZ7_SFX \ + -DZ7_NO_LONG_PATH \ + -DZ7_NO_LARGE_PAGES \ + +!IFDEF UNDER_CE +LIBS = $(LIBS) ceshell.lib Commctrl.lib +!ELSE +LIBS = $(LIBS) comctl32.lib comdlg32.lib +!ENDIF + +CURRENT_OBJS = \ + $O\SfxWin.obj \ + +GUI_OBJS = \ + $O\ExtractDialog.obj \ + $O\ExtractGUI.obj \ + +COMMON_OBJS = \ + $O\CRC.obj \ + $O\CommandLineParser.obj \ + $O\IntToString.obj \ + $O\NewHandler.obj \ + $O\MyString.obj \ + $O\StringConvert.obj \ + $O\MyVector.obj \ + $O\Wildcard.obj \ + +WIN_OBJS = \ + $O\Clipboard.obj \ + $O\CommonDialog.obj \ + $O\DLL.obj \ + $O\ErrorMsg.obj \ + $O\FileDir.obj \ + $O\FileFind.obj \ + $O\FileIO.obj \ + $O\FileName.obj \ + $O\MemoryGlobal.obj \ + $O\PropVariant.obj \ + $O\PropVariantConv.obj \ + $O\ResourceString.obj \ + $O\Shell.obj \ + $O\Synchronization.obj \ + $O\System.obj \ + $O\TimeUtils.obj \ + $O\Window.obj \ + +WIN_CTRL_OBJS = \ + $O\ComboBox.obj \ + $O\Dialog.obj \ + $O\ListView.obj \ + +7ZIP_COMMON_OBJS = \ + $O\CreateCoder.obj \ + $O\CWrappers.obj \ + $O\FilePathAutoRename.obj \ + $O\FileStreams.obj \ + $O\InBuffer.obj \ + $O\FilterCoder.obj \ + $O\LimitedStreams.obj \ + $O\OutBuffer.obj \ + $O\ProgressUtils.obj \ + $O\PropId.obj \ + $O\StreamBinder.obj \ + $O\StreamObjects.obj \ + $O\StreamUtils.obj \ + $O\VirtThread.obj \ + +UI_COMMON_OBJS = \ + $O\ArchiveExtractCallback.obj \ + $O\ArchiveOpenCallback.obj \ + $O\DefaultName.obj \ + $O\Extract.obj \ + $O\ExtractingFilePath.obj \ + $O\LoadCodecs.obj \ + $O\OpenArchive.obj \ + +EXPLORER_OBJS = \ + $O\MyMessages.obj \ + +FM_OBJS = \ + $O\BrowseDialog.obj \ + $O\ComboDialog.obj \ + $O\ExtractCallback.obj \ + $O\FormatUtils.obj \ + $O\OverwriteDialog.obj \ + $O\PasswordDialog.obj \ + $O\ProgressDialog2.obj \ + $O\PropertyName.obj \ + $O\SysIconUtils.obj \ + +AR_OBJS = \ + $O\SplitHandler.obj \ + +AR_COMMON_OBJS = \ + $O\CoderMixer2.obj \ + $O\ItemNameUtils.obj \ + $O\MultiStream.obj \ + $O\OutStreamWithCRC.obj \ + +7Z_OBJS = \ + $O\7zDecode.obj \ + $O\7zExtract.obj \ + $O\7zHandler.obj \ + $O\7zIn.obj \ + $O\7zRegister.obj \ + +COMPRESS_OBJS = \ + $O\Bcj2Coder.obj \ + $O\Bcj2Register.obj \ + $O\BcjCoder.obj \ + $O\BcjRegister.obj \ + $O\BranchMisc.obj \ + $O\BranchRegister.obj \ + $O\CopyCoder.obj \ + $O\CopyRegister.obj \ + $O\DeltaFilter.obj \ + $O\Lzma2Decoder.obj \ + $O\Lzma2Register.obj \ + $O\LzmaDecoder.obj \ + $O\LzmaRegister.obj \ + $O\PpmdDecoder.obj \ + $O\PpmdRegister.obj \ + +CRYPTO_OBJS = \ + $O\7zAes.obj \ + $O\7zAesRegister.obj \ + $O\MyAes.obj \ + +C_OBJS = \ + $O\7zStream.obj \ + $O\Alloc.obj \ + $O\Bcj2.obj \ + $O\Bra.obj \ + $O\Bra86.obj \ + $O\BraIA64.obj \ + $O\CpuArch.obj \ + $O\Delta.obj \ + $O\DllSecur.obj \ + $O\Lzma2Dec.obj \ + $O\Lzma2DecMt.obj \ + $O\LzmaDec.obj \ + $O\MtDec.obj \ + $O\Ppmd7.obj \ + $O\Ppmd7Dec.obj \ + $O\Threads.obj \ + +!include "../../Aes.mak" +!include "../../Crc.mak" +!include "../../LzmaDec.mak" +!include "../../Sha256.mak" + +!include "../../7zip.mak" diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXWin/resource.h b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXWin/resource.h new file mode 100644 index 00000000..30fe2030 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXWin/resource.h @@ -0,0 +1 @@ +#define IDI_ICON 1 diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXWin/resource.rc b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXWin/resource.rc new file mode 100644 index 00000000..3b2217a5 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/SFXWin/resource.rc @@ -0,0 +1,55 @@ +#include "../../MyVersionInfo.rc" +#include "../../GuiCommon.rc" +#include "../../UI/GUI/ExtractDialogRes.h" +#include "../../UI/FileManager/PropertyNameRes.h" + +#include "resource.h" + +MY_VERSION_INFO_APP("7z SFX", "7z.sfx") + +#define xc 240 +#define yc 64 + +IDI_ICON ICON "7z.ico" + +IDD_EXTRACT DIALOG 0, 0, xs, ys MY_MODAL_DIALOG_STYLE MY_FONT +CAPTION "7-Zip self-extracting archive" +BEGIN + LTEXT "E&xtract to:", IDT_EXTRACT_EXTRACT_TO, m, m, xc, 8 + EDITTEXT IDC_EXTRACT_PATH, m, 21, xc - bxsDots - 12, 14, ES_AUTOHSCROLL + PUSHBUTTON "...", IDB_EXTRACT_SET_PATH, xs - m - bxsDots, 20, bxsDots, bys, WS_GROUP + DEFPUSHBUTTON "Extract", IDOK, bx2, by, bxs, bys, WS_GROUP + PUSHBUTTON "Cancel", IDCANCEL, bx1, by, bxs, bys +END + +#ifdef UNDER_CE + +#undef xc +#define xc 144 + +IDD_EXTRACT_2 DIALOG 0, 0, xs, ys MY_MODAL_DIALOG_STYLE MY_FONT +CAPTION "7-Zip self-extracting archive" +BEGIN + LTEXT "E&xtract to:", IDT_EXTRACT_EXTRACT_TO, m, m, xc - bxsDots - 12, 8 + EDITTEXT IDC_EXTRACT_PATH, m, m + bys + 4, xc, 14, ES_AUTOHSCROLL + PUSHBUTTON "...", IDB_EXTRACT_SET_PATH, xs - m - bxsDots, m, bxsDots, bys, WS_GROUP + DEFPUSHBUTTON "Extract", IDOK, bx2, by, bxs, bys, WS_GROUP + PUSHBUTTON "Cancel", IDCANCEL, bx1, by, bxs, bys +END + +#endif + +#include "../../UI/FileManager/OverwriteDialog.rc" +#include "../../UI/FileManager/PasswordDialog.rc" +#include "../../UI/FileManager/ProgressDialog2.rc" +#include "../../UI/GUI/Extract.rc" + +STRINGTABLE DISCARDABLE +BEGIN + IDS_PROP_MTIME "Modified" +END + + +#ifndef UNDER_CE +1 24 MOVEABLE PURE "../../UI/GUI/7zG.exe.manifest" +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Bundles/makefile b/src/plugins/7zip/7za/cpp/7zip/Bundles/makefile new file mode 100644 index 00000000..0651da54 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Bundles/makefile @@ -0,0 +1,19 @@ +DIRS = \ + Alone\~ \ + Alone2\~ \ + Alone7z\~ \ + Fm\~ \ + Format7z\~ \ + Format7zF\~ \ + Format7zR\~ \ + Format7zExtract\~ \ + Format7zExtractR\~ \ + LzmaCon\~ \ + SFXCon\~ \ + SFXSetup\~ \ + SFXWin\~ \ + +all: $(DIRS) + +$(DIRS): +!include "../SubBuild.mak" diff --git a/src/plugins/7zip/7za/cpp/7zip/Common/CWrappers.cpp b/src/plugins/7zip/7za/cpp/7zip/Common/CWrappers.cpp index a77b67e8..d3676b72 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Common/CWrappers.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Common/CWrappers.cpp @@ -1,4 +1,4 @@ -// CWrappers.h +// CWrappers.c #include "StdAfx.h" @@ -8,43 +8,78 @@ #include "StreamUtils.h" +SRes HRESULT_To_SRes(HRESULT res, SRes defaultRes) throw() +{ + switch (res) + { + case S_OK: return SZ_OK; + case E_OUTOFMEMORY: return SZ_ERROR_MEM; + case E_INVALIDARG: return SZ_ERROR_PARAM; + case E_ABORT: return SZ_ERROR_PROGRESS; + case S_FALSE: return SZ_ERROR_DATA; + case E_NOTIMPL: return SZ_ERROR_UNSUPPORTED; + default: break; + } + return defaultRes; +} + + +HRESULT SResToHRESULT(SRes res) throw() +{ + switch (res) + { + case SZ_OK: return S_OK; + + case SZ_ERROR_DATA: + case SZ_ERROR_CRC: + case SZ_ERROR_INPUT_EOF: + case SZ_ERROR_ARCHIVE: + case SZ_ERROR_NO_ARCHIVE: + return S_FALSE; + + case SZ_ERROR_MEM: return E_OUTOFMEMORY; + case SZ_ERROR_PARAM: return E_INVALIDARG; + case SZ_ERROR_PROGRESS: return E_ABORT; + case SZ_ERROR_UNSUPPORTED: return E_NOTIMPL; + // case SZ_ERROR_OUTPUT_EOF: + // case SZ_ERROR_READ: + // case SZ_ERROR_WRITE: + // case SZ_ERROR_THREAD: + // case SZ_ERROR_ARCHIVE: + // case SZ_ERROR_NO_ARCHIVE: + // return E_FAIL; + default: break; + } + if (res < 0) + return res; + return E_FAIL; +} + + #define PROGRESS_UNKNOWN_VALUE ((UInt64)(Int64)-1) #define CONVERT_PR_VAL(x) (x == PROGRESS_UNKNOWN_VALUE ? NULL : &x) -static SRes CompressProgress(void *pp, UInt64 inSize, UInt64 outSize) throw() + +static SRes CompressProgress(ICompressProgressPtr pp, UInt64 inSize, UInt64 outSize) throw() { - CCompressProgressWrap *p = (CCompressProgressWrap *)pp; + Z7_CONTAINER_FROM_VTBL_TO_DECL_VAR_pp_vt_p(CCompressProgressWrap) p->Res = p->Progress->SetRatioInfo(CONVERT_PR_VAL(inSize), CONVERT_PR_VAL(outSize)); - return (SRes)p->Res; + return HRESULT_To_SRes(p->Res, SZ_ERROR_PROGRESS); } -CCompressProgressWrap::CCompressProgressWrap(ICompressProgressInfo *progress) throw() +void CCompressProgressWrap::Init(ICompressProgressInfo *progress) throw() { - p.Progress = CompressProgress; + vt.Progress = CompressProgress; Progress = progress; Res = SZ_OK; } static const UInt32 kStreamStepSize = (UInt32)1 << 31; -SRes HRESULT_To_SRes(HRESULT res, SRes defaultRes) +static SRes MyRead(ISeqInStreamPtr pp, void *data, size_t *size) throw() { - switch (res) - { - case S_OK: return SZ_OK; - case E_OUTOFMEMORY: return SZ_ERROR_MEM; - case E_INVALIDARG: return SZ_ERROR_PARAM; - case E_ABORT: return SZ_ERROR_PROGRESS; - case S_FALSE: return SZ_ERROR_DATA; - case E_NOTIMPL: return SZ_ERROR_UNSUPPORTED; - } - return defaultRes; -} - -static SRes MyRead(void *object, void *data, size_t *size) throw() -{ - CSeqInStreamWrap *p = (CSeqInStreamWrap *)object; + Z7_CONTAINER_FROM_VTBL_TO_DECL_VAR_pp_vt_p(CSeqInStreamWrap) UInt32 curSize = ((*size < kStreamStepSize) ? (UInt32)*size : kStreamStepSize); p->Res = (p->Stream->Read(data, curSize, &curSize)); *size = curSize; @@ -54,9 +89,9 @@ static SRes MyRead(void *object, void *data, size_t *size) throw() return HRESULT_To_SRes(p->Res, SZ_ERROR_READ); } -static size_t MyWrite(void *object, const void *data, size_t size) throw() +static size_t MyWrite(ISeqOutStreamPtr pp, const void *data, size_t size) throw() { - CSeqOutStreamWrap *p = (CSeqOutStreamWrap *)object; + Z7_CONTAINER_FROM_VTBL_TO_DECL_VAR_pp_vt_p(CSeqOutStreamWrap) if (p->Stream) { p->Res = WriteStream(p->Stream, data, size); @@ -69,49 +104,41 @@ static size_t MyWrite(void *object, const void *data, size_t size) throw() return size; } -CSeqInStreamWrap::CSeqInStreamWrap(ISequentialInStream *stream) throw() + +void CSeqInStreamWrap::Init(ISequentialInStream *stream) throw() { - p.Read = MyRead; + vt.Read = MyRead; Stream = stream; Processed = 0; + Res = S_OK; } -CSeqOutStreamWrap::CSeqOutStreamWrap(ISequentialOutStream *stream) throw() +void CSeqOutStreamWrap::Init(ISequentialOutStream *stream) throw() { - p.Write = MyWrite; + vt.Write = MyWrite; Stream = stream; Res = SZ_OK; Processed = 0; } -HRESULT SResToHRESULT(SRes res) throw() -{ - switch (res) - { - case SZ_OK: return S_OK; - case SZ_ERROR_MEM: return E_OUTOFMEMORY; - case SZ_ERROR_PARAM: return E_INVALIDARG; - case SZ_ERROR_PROGRESS: return E_ABORT; - case SZ_ERROR_DATA: return S_FALSE; - case SZ_ERROR_UNSUPPORTED: return E_NOTIMPL; - } - return E_FAIL; -} -static SRes InStreamWrap_Read(void *pp, void *data, size_t *size) throw() +static SRes InStreamWrap_Read(ISeekInStreamPtr pp, void *data, size_t *size) throw() { - CSeekInStreamWrap *p = (CSeekInStreamWrap *)pp; + Z7_CONTAINER_FROM_VTBL_TO_DECL_VAR_pp_vt_p(CSeekInStreamWrap) UInt32 curSize = ((*size < kStreamStepSize) ? (UInt32)*size : kStreamStepSize); p->Res = p->Stream->Read(data, curSize, &curSize); *size = curSize; return (p->Res == S_OK) ? SZ_OK : SZ_ERROR_READ; } -static SRes InStreamWrap_Seek(void *pp, Int64 *offset, ESzSeek origin) throw() +static SRes InStreamWrap_Seek(ISeekInStreamPtr pp, Int64 *offset, ESzSeek origin) throw() { - CSeekInStreamWrap *p = (CSeekInStreamWrap *)pp; + Z7_CONTAINER_FROM_VTBL_TO_DECL_VAR_pp_vt_p(CSeekInStreamWrap) UInt32 moveMethod; - switch (origin) + /* we need (int)origin to eliminate the clang warning: + default label in switch which covers all enumeration values + [-Wcovered-switch-default */ + switch ((int)origin) { case SZ_SEEK_SET: moveMethod = STREAM_SEEK_SET; break; case SZ_SEEK_CUR: moveMethod = STREAM_SEEK_CUR; break; @@ -124,11 +151,11 @@ static SRes InStreamWrap_Seek(void *pp, Int64 *offset, ESzSeek origin) throw() return (p->Res == S_OK) ? SZ_OK : SZ_ERROR_READ; } -CSeekInStreamWrap::CSeekInStreamWrap(IInStream *stream) throw() +void CSeekInStreamWrap::Init(IInStream *stream) throw() { Stream = stream; - p.Read = InStreamWrap_Read; - p.Seek = InStreamWrap_Seek; + vt.Read = InStreamWrap_Read; + vt.Seek = InStreamWrap_Seek; Res = S_OK; } @@ -138,27 +165,27 @@ CSeekInStreamWrap::CSeekInStreamWrap(IInStream *stream) throw() void CByteInBufWrap::Free() throw() { ::MidFree(Buf); - Buf = 0; + Buf = NULL; } bool CByteInBufWrap::Alloc(UInt32 size) throw() { - if (Buf == 0 || size != Size) + if (!Buf || size != Size) { Free(); Lim = Cur = Buf = (Byte *)::MidAlloc((size_t)size); Size = size; } - return (Buf != 0); + return (Buf != NULL); } Byte CByteInBufWrap::ReadByteFromNewBlock() throw() { - if (Res == S_OK) + if (!Extra && Res == S_OK) { UInt32 avail; - Processed += (Cur - Buf); Res = Stream->Read(Buf, Size, &avail); + Processed += (size_t)(Cur - Buf); Cur = Buf; Lim = Buf + avail; if (avail != 0) @@ -168,55 +195,106 @@ Byte CByteInBufWrap::ReadByteFromNewBlock() throw() return 0; } -static Byte Wrap_ReadByte(void *pp) throw() +// #pragma GCC diagnostic ignored "-Winvalid-offsetof" + +static Byte Wrap_ReadByte(IByteInPtr pp) throw() { - CByteInBufWrap *p = (CByteInBufWrap *)pp; + CByteInBufWrap *p = Z7_CONTAINER_FROM_VTBL_CLS(pp, CByteInBufWrap, vt); + // Z7_CONTAINER_FROM_VTBL_TO_DECL_VAR_pp_vt_p(CByteInBufWrap) if (p->Cur != p->Lim) return *p->Cur++; return p->ReadByteFromNewBlock(); } -CByteInBufWrap::CByteInBufWrap(): Buf(0) +CByteInBufWrap::CByteInBufWrap() throw(): Buf(NULL) +{ + vt.Read = Wrap_ReadByte; +} + + + +/* ---------- CByteOutBufWrap ---------- */ + +/* +void CLookToSequentialWrap::Free() throw() { - p.Read = Wrap_ReadByte; + ::MidFree(BufBase); + BufBase = NULL; } +bool CLookToSequentialWrap::Alloc(UInt32 size) throw() +{ + if (!BufBase || size != Size) + { + Free(); + BufBase = (Byte *)::MidAlloc((size_t)size); + Size = size; + } + return (BufBase != NULL); +} +*/ + +/* +EXTERN_C_BEGIN + +void CLookToSequentialWrap_Look(ILookInSeqStreamPtr pp) +{ + CLookToSequentialWrap *p = (CLookToSequentialWrap *)pp->Obj; + + if (p->Extra || p->Res != S_OK) + return; + { + UInt32 avail; + p->Res = p->Stream->Read(p->BufBase, p->Size, &avail); + p->Processed += avail; + pp->Buf = p->BufBase; + pp->Limit = pp->Buf + avail; + if (avail == 0) + p->Extra = true; + } +} + +EXTERN_C_END +*/ + /* ---------- CByteOutBufWrap ---------- */ void CByteOutBufWrap::Free() throw() { ::MidFree(Buf); - Buf = 0; + Buf = NULL; } bool CByteOutBufWrap::Alloc(size_t size) throw() { - if (Buf == 0 || size != Size) + if (!Buf || size != Size) { Free(); Buf = (Byte *)::MidAlloc(size); Size = size; } - return (Buf != 0); + return (Buf != NULL); } HRESULT CByteOutBufWrap::Flush() throw() { if (Res == S_OK) { - size_t size = (Cur - Buf); + const size_t size = (size_t)(Cur - Buf); Res = WriteStream(Stream, Buf, size); if (Res == S_OK) Processed += size; - Cur = Buf; + // else throw 11; } + Cur = Buf; // reset pointer for later Wrap_WriteByte() return Res; } -static void Wrap_WriteByte(void *pp, Byte b) throw() +static void Wrap_WriteByte(IByteOutPtr pp, Byte b) throw() { - CByteOutBufWrap *p = (CByteOutBufWrap *)pp; + CByteOutBufWrap *p = Z7_CONTAINER_FROM_VTBL_CLS(pp, CByteOutBufWrap, vt); + // Z7_CONTAINER_FROM_VTBL_TO_DECL_VAR_pp_vt_p(CByteOutBufWrap) Byte *dest = p->Cur; *dest = b; p->Cur = ++dest; @@ -224,7 +302,57 @@ static void Wrap_WriteByte(void *pp, Byte b) throw() p->Flush(); } -CByteOutBufWrap::CByteOutBufWrap() throw(): Buf(0) +CByteOutBufWrap::CByteOutBufWrap() throw(): Buf(NULL), Size(0) +{ + vt.Write = Wrap_WriteByte; +} + + +/* ---------- CLookOutWrap ---------- */ + +/* +void CLookOutWrap::Free() throw() +{ + ::MidFree(Buf); + Buf = NULL; +} + +bool CLookOutWrap::Alloc(size_t size) throw() +{ + if (!Buf || size != Size) + { + Free(); + Buf = (Byte *)::MidAlloc(size); + Size = size; + } + return (Buf != NULL); +} + +static size_t LookOutWrap_GetOutBuf(ILookOutStreamPtr pp, void **buf) throw() +{ + CLookOutWrap *p = Z7_CONTAINER_FROM_VTBL_CLS(pp, CLookOutWrap, vt); + *buf = p->Buf; + return p->Size; +} + +static size_t LookOutWrap_Write(ILookOutStreamPtr pp, size_t size) throw() +{ + CLookOutWrap *p = Z7_CONTAINER_FROM_VTBL_CLS(pp, CLookOutWrap, vt); + if (p->Res == S_OK && size != 0) + { + p->Res = WriteStream(p->Stream, p->Buf, size); + if (p->Res == S_OK) + { + p->Processed += size; + return size; + } + } + return 0; +} + +CLookOutWrap::CLookOutWrap() throw(): Buf(NULL), Size(0) { - p.Write = Wrap_WriteByte; + vt.GetOutBuf = LookOutWrap_GetOutBuf; + vt.Write = LookOutWrap_Write; } +*/ diff --git a/src/plugins/7zip/7za/cpp/7zip/Common/CWrappers.h b/src/plugins/7zip/7za/cpp/7zip/Common/CWrappers.h index acadc170..6c10a5c2 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Common/CWrappers.h +++ b/src/plugins/7zip/7za/cpp/7zip/Common/CWrappers.h @@ -1,54 +1,59 @@ // CWrappers.h -#ifndef __C_WRAPPERS_H -#define __C_WRAPPERS_H +#ifndef ZIP7_INC_C_WRAPPERS_H +#define ZIP7_INC_C_WRAPPERS_H #include "../ICoder.h" #include "../../Common/MyCom.h" +SRes HRESULT_To_SRes(HRESULT res, SRes defaultRes) throw(); +HRESULT SResToHRESULT(SRes res) throw(); + struct CCompressProgressWrap { - ICompressProgress p; + ICompressProgress vt; ICompressProgressInfo *Progress; HRESULT Res; - CCompressProgressWrap(ICompressProgressInfo *progress) throw(); + void Init(ICompressProgressInfo *progress) throw(); }; + struct CSeqInStreamWrap { - ISeqInStream p; + ISeqInStream vt; ISequentialInStream *Stream; HRESULT Res; UInt64 Processed; - CSeqInStreamWrap(ISequentialInStream *stream) throw(); + void Init(ISequentialInStream *stream) throw(); }; + struct CSeekInStreamWrap { - ISeekInStream p; + ISeekInStream vt; IInStream *Stream; HRESULT Res; - CSeekInStreamWrap(IInStream *stream) throw(); + void Init(IInStream *stream) throw(); }; + struct CSeqOutStreamWrap { - ISeqOutStream p; + ISeqOutStream vt; ISequentialOutStream *Stream; HRESULT Res; UInt64 Processed; - CSeqOutStreamWrap(ISequentialOutStream *stream) throw(); + void Init(ISequentialOutStream *stream) throw(); }; -HRESULT SResToHRESULT(SRes res) throw(); struct CByteInBufWrap { - IByteIn p; + IByteIn vt; const Byte *Cur; const Byte *Lim; Byte *Buf; @@ -58,7 +63,7 @@ struct CByteInBufWrap bool Extra; HRESULT Res; - CByteInBufWrap(); + CByteInBufWrap() throw(); ~CByteInBufWrap() { Free(); } void Free() throw(); bool Alloc(UInt32 size) throw(); @@ -69,7 +74,40 @@ struct CByteInBufWrap Extra = false; Res = S_OK; } - UInt64 GetProcessed() const { return Processed + (Cur - Buf); } + UInt64 GetProcessed() const { return Processed + (size_t)(Cur - Buf); } + Byte ReadByteFromNewBlock() throw(); + Byte ReadByte() + { + if (Cur != Lim) + return *Cur++; + return ReadByteFromNewBlock(); + } +}; + + +/* +struct CLookToSequentialWrap +{ + Byte *BufBase; + UInt32 Size; + ISequentialInStream *Stream; + UInt64 Processed; + bool Extra; + HRESULT Res; + + CLookToSequentialWrap(): BufBase(NULL) {} + ~CLookToSequentialWrap() { Free(); } + void Free() throw(); + bool Alloc(UInt32 size) throw(); + void Init() + { + // Lim = Cur = Buf; + Processed = 0; + Extra = false; + Res = S_OK; + } + // UInt64 GetProcessed() const { return Processed + (Cur - Buf); } + Byte ReadByteFromNewBlock() throw(); Byte ReadByte() { @@ -79,9 +117,16 @@ struct CByteInBufWrap } }; +EXTERN_C_BEGIN +// void CLookToSequentialWrap_Look(ILookInSeqStream *pp); +EXTERN_C_END +*/ + + + struct CByteOutBufWrap { - IByteOut p; + IByteOut vt; Byte *Cur; const Byte *Lim; Byte *Buf; @@ -101,7 +146,7 @@ struct CByteOutBufWrap Processed = 0; Res = S_OK; } - UInt64 GetProcessed() const { return Processed + (Cur - Buf); } + UInt64 GetProcessed() const { return Processed + (size_t)(Cur - Buf); } HRESULT Flush() throw(); void WriteByte(Byte b) { @@ -111,4 +156,27 @@ struct CByteOutBufWrap } }; + +/* +struct CLookOutWrap +{ + ILookOutStream vt; + Byte *Buf; + size_t Size; + ISequentialOutStream *Stream; + UInt64 Processed; + HRESULT Res; + + CLookOutWrap() throw(); + ~CLookOutWrap() { Free(); } + void Free() throw(); + bool Alloc(size_t size) throw(); + void Init() + { + Processed = 0; + Res = S_OK; + } +}; +*/ + #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Common/CreateCoder.cpp b/src/plugins/7zip/7za/cpp/7zip/Common/CreateCoder.cpp index 75074ad8..67873d14 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Common/CreateCoder.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Common/CreateCoder.cpp @@ -2,7 +2,7 @@ #include "StdAfx.h" -#include "../../Windows/Defs.h" +#include "../../Windows/WinDefs.h" #include "../../Windows/PropVariant.h" #include "CreateCoder.h" @@ -11,19 +11,23 @@ #include "RegisterCodec.h" static const unsigned kNumCodecsMax = 64; +extern +unsigned g_NumCodecs; unsigned g_NumCodecs = 0; +extern +const CCodecInfo *g_Codecs[]; const CCodecInfo *g_Codecs[kNumCodecsMax]; // We use g_ExternalCodecs in other stages. +#ifdef Z7_EXTERNAL_CODECS /* -#ifdef EXTERNAL_CODECS extern CExternalCodecs g_ExternalCodecs; #define CHECK_GLOBAL_CODECS \ - if (!__externalCodecs || !__externalCodecs->IsSet()) __externalCodecs = &g_ExternalCodecs; -#endif + if (!_externalCodecs || !_externalCodecs->IsSet()) _externalCodecs = &g_ExternalCodecs; */ - #define CHECK_GLOBAL_CODECS +#endif + void RegisterCodec(const CCodecInfo *codecInfo) throw() { @@ -31,8 +35,12 @@ void RegisterCodec(const CCodecInfo *codecInfo) throw() g_Codecs[g_NumCodecs++] = codecInfo; } -static const unsigned kNumHashersMax = 16; +static const unsigned kNumHashersMax = 32; +extern +unsigned g_NumHashers; unsigned g_NumHashers = 0; +extern +const CHasherInfo *g_Hashers[]; const CHasherInfo *g_Hashers[kNumHashersMax]; void RegisterHasher(const CHasherInfo *hashInfo) throw() @@ -42,12 +50,12 @@ void RegisterHasher(const CHasherInfo *hashInfo) throw() } -#ifdef EXTERNAL_CODECS +#ifdef Z7_EXTERNAL_CODECS static HRESULT ReadNumberOfStreams(ICompressCodecsInfo *codecsInfo, UInt32 index, PROPID propID, UInt32 &res) { NWindows::NCOM::CPropVariant prop; - RINOK(codecsInfo->GetProperty(index, propID, &prop)); + RINOK(codecsInfo->GetProperty(index, propID, &prop)) if (prop.vt == VT_EMPTY) res = 1; else if (prop.vt == VT_UI4) @@ -60,7 +68,7 @@ static HRESULT ReadNumberOfStreams(ICompressCodecsInfo *codecsInfo, UInt32 index static HRESULT ReadIsAssignedProp(ICompressCodecsInfo *codecsInfo, UInt32 index, PROPID propID, bool &res) { NWindows::NCOM::CPropVariant prop; - RINOK(codecsInfo->GetProperty(index, propID, &prop)); + RINOK(codecsInfo->GetProperty(index, propID, &prop)) if (prop.vt == VT_EMPTY) res = true; else if (prop.vt == VT_BOOL) @@ -81,13 +89,13 @@ HRESULT CExternalCodecs::Load() UString s; UInt32 num; - RINOK(GetCodecs->GetNumMethods(&num)); + RINOK(GetCodecs->GetNumMethods(&num)) for (UInt32 i = 0; i < num; i++) { NWindows::NCOM::CPropVariant prop; - RINOK(GetCodecs->GetProperty(i, NMethodPropID::kID, &prop)); + RINOK(GetCodecs->GetProperty(i, NMethodPropID::kID, &prop)) if (prop.vt != VT_UI8) continue; // old Interface info.Id = prop.uhVal.QuadPart; @@ -95,21 +103,22 @@ HRESULT CExternalCodecs::Load() prop.Clear(); info.Name.Empty(); - RINOK(GetCodecs->GetProperty(i, NMethodPropID::kName, &prop)); + RINOK(GetCodecs->GetProperty(i, NMethodPropID::kName, &prop)) if (prop.vt == VT_BSTR) info.Name.SetFromWStr_if_Ascii(prop.bstrVal); else if (prop.vt != VT_EMPTY) continue; - RINOK(ReadNumberOfStreams(GetCodecs, i, NMethodPropID::kPackStreams, info.NumStreams)); + RINOK(ReadNumberOfStreams(GetCodecs, i, NMethodPropID::kPackStreams, info.NumStreams)) { UInt32 numUnpackStreams = 1; - RINOK(ReadNumberOfStreams(GetCodecs, i, NMethodPropID::kUnpackStreams, numUnpackStreams)); + RINOK(ReadNumberOfStreams(GetCodecs, i, NMethodPropID::kUnpackStreams, numUnpackStreams)) if (numUnpackStreams != 1) continue; } - RINOK(ReadIsAssignedProp(GetCodecs, i, NMethodPropID::kEncoderIsAssigned, info.EncoderIsAssigned)); - RINOK(ReadIsAssignedProp(GetCodecs, i, NMethodPropID::kDecoderIsAssigned, info.DecoderIsAssigned)); + RINOK(ReadIsAssignedProp(GetCodecs, i, NMethodPropID::kEncoderIsAssigned, info.EncoderIsAssigned)) + RINOK(ReadIsAssignedProp(GetCodecs, i, NMethodPropID::kDecoderIsAssigned, info.DecoderIsAssigned)) + RINOK(ReadIsAssignedProp(GetCodecs, i, NMethodPropID::kIsFilter, info.IsFilter)) Codecs.Add(info); } @@ -124,7 +133,7 @@ HRESULT CExternalCodecs::Load() { NWindows::NCOM::CPropVariant prop; - RINOK(GetHashers->GetHasherProp(i, NMethodPropID::kID, &prop)); + RINOK(GetHashers->GetHasherProp(i, NMethodPropID::kID, &prop)) if (prop.vt != VT_UI8) continue; info.Id = prop.uhVal.QuadPart; @@ -132,7 +141,7 @@ HRESULT CExternalCodecs::Load() prop.Clear(); info.Name.Empty(); - RINOK(GetHashers->GetHasherProp(i, NMethodPropID::kName, &prop)); + RINOK(GetHashers->GetHasherProp(i, NMethodPropID::kName, &prop)) if (prop.vt == VT_BSTR) info.Name.SetFromWStr_if_Ascii(prop.bstrVal); else if (prop.vt != VT_EMPTY) @@ -148,44 +157,82 @@ HRESULT CExternalCodecs::Load() #endif -bool FindMethod( +int FindMethod_Index( DECL_EXTERNAL_CODECS_LOC_VARS const AString &name, - CMethodId &methodId, UInt32 &numStreams) + bool encode, + CMethodId &methodId, + UInt32 &numStreams, + bool &isFilter) { unsigned i; for (i = 0; i < g_NumCodecs; i++) { const CCodecInfo &codec = *g_Codecs[i]; - if (StringsAreEqualNoCase_Ascii(name, codec.Name)) + if ((encode ? codec.CreateEncoder : codec.CreateDecoder) + && StringsAreEqualNoCase_Ascii(name, codec.Name)) { methodId = codec.Id; numStreams = codec.NumStreams; - return true; + isFilter = codec.IsFilter; + return (int)i; } } - #ifdef EXTERNAL_CODECS + #ifdef Z7_EXTERNAL_CODECS CHECK_GLOBAL_CODECS - if (__externalCodecs) - for (i = 0; i < __externalCodecs->Codecs.Size(); i++) + if (_externalCodecs) + for (i = 0; i < _externalCodecs->Codecs.Size(); i++) { - const CCodecInfoEx &codec = __externalCodecs->Codecs[i]; - if (StringsAreEqualNoCase_Ascii(name, codec.Name)) + const CCodecInfoEx &codec = _externalCodecs->Codecs[i]; + if ((encode ? codec.EncoderIsAssigned : codec.DecoderIsAssigned) + && StringsAreEqualNoCase_Ascii(name, codec.Name)) { methodId = codec.Id; numStreams = codec.NumStreams; - return true; + isFilter = codec.IsFilter; + return (int)(g_NumCodecs + i); } } #endif - return false; + return -1; } + +static int FindMethod_Index( + DECL_EXTERNAL_CODECS_LOC_VARS + CMethodId methodId, bool encode) +{ + unsigned i; + for (i = 0; i < g_NumCodecs; i++) + { + const CCodecInfo &codec = *g_Codecs[i]; + if (codec.Id == methodId && (encode ? codec.CreateEncoder : codec.CreateDecoder)) + return (int)i; + } + + #ifdef Z7_EXTERNAL_CODECS + + CHECK_GLOBAL_CODECS + + if (_externalCodecs) + for (i = 0; i < _externalCodecs->Codecs.Size(); i++) + { + const CCodecInfoEx &codec = _externalCodecs->Codecs[i]; + if (codec.Id == methodId && (encode ? codec.EncoderIsAssigned : codec.DecoderIsAssigned)) + return (int)(g_NumCodecs + i); + } + + #endif + + return -1; +} + + bool FindMethod( DECL_EXTERNAL_CODECS_LOC_VARS CMethodId methodId, @@ -204,14 +251,14 @@ bool FindMethod( } } - #ifdef EXTERNAL_CODECS + #ifdef Z7_EXTERNAL_CODECS CHECK_GLOBAL_CODECS - if (__externalCodecs) - for (i = 0; i < __externalCodecs->Codecs.Size(); i++) + if (_externalCodecs) + for (i = 0; i < _externalCodecs->Codecs.Size(); i++) { - const CCodecInfoEx &codec = __externalCodecs->Codecs[i]; + const CCodecInfoEx &codec = _externalCodecs->Codecs[i]; if (methodId == codec.Id) { name = codec.Name; @@ -240,14 +287,14 @@ bool FindHashMethod( } } - #ifdef EXTERNAL_CODECS + #ifdef Z7_EXTERNAL_CODECS CHECK_GLOBAL_CODECS - if (__externalCodecs) - for (i = 0; i < __externalCodecs->Hashers.Size(); i++) + if (_externalCodecs) + for (i = 0; i < _externalCodecs->Hashers.Size(); i++) { - const CHasherInfoEx &codec = __externalCodecs->Hashers[i]; + const CHasherInfoEx &codec = _externalCodecs->Hashers[i]; if (StringsAreEqualNoCase_Ascii(name, codec.Name)) { methodId = codec.Id; @@ -269,20 +316,22 @@ void GetHashMethods( for (i = 0; i < g_NumHashers; i++) methods[i] = (*g_Hashers[i]).Id; - #ifdef EXTERNAL_CODECS + #ifdef Z7_EXTERNAL_CODECS CHECK_GLOBAL_CODECS - if (__externalCodecs) - for (i = 0; i < __externalCodecs->Hashers.Size(); i++) - methods.Add(__externalCodecs->Hashers[i].Id); + if (_externalCodecs) + for (i = 0; i < _externalCodecs->Hashers.Size(); i++) + methods.Add(_externalCodecs->Hashers[i].Id); #endif } -HRESULT CreateCoder( + + +HRESULT CreateCoder_Index( DECL_EXTERNAL_CODECS_LOC_VARS - CMethodId methodId, bool encode, + unsigned i, bool encode, CMyComPtr &filter, CCreatedCoder &cod) { @@ -290,11 +339,10 @@ HRESULT CreateCoder( cod.IsFilter = false; cod.NumStreams = 1; - unsigned i; - for (i = 0; i < g_NumCodecs; i++) + if (i < g_NumCodecs) { const CCodecInfo &codec = *g_Codecs[i]; - if (codec.Id == methodId) + // if (codec.Id == methodId) { if (encode) { @@ -319,17 +367,18 @@ HRESULT CreateCoder( } } - #ifdef EXTERNAL_CODECS + #ifdef Z7_EXTERNAL_CODECS CHECK_GLOBAL_CODECS - if (__externalCodecs) + if (_externalCodecs) { + i -= g_NumCodecs; cod.IsExternal = true; - for (i = 0; i < __externalCodecs->Codecs.Size(); i++) + if (i < _externalCodecs->Codecs.Size()) { - const CCodecInfoEx &codec = __externalCodecs->Codecs[i]; - if (codec.Id == methodId) + const CCodecInfoEx &codec = _externalCodecs->Codecs[i]; + // if (codec.Id == methodId) { if (encode) { @@ -337,15 +386,15 @@ HRESULT CreateCoder( { if (codec.NumStreams == 1) { - HRESULT res = __externalCodecs->GetCodecs->CreateEncoder(i, &IID_ICompressCoder, (void **)&cod.Coder); + const HRESULT res = _externalCodecs->GetCodecs->CreateEncoder(i, &IID_ICompressCoder, (void **)&cod.Coder); if (res != S_OK && res != E_NOINTERFACE && res != CLASS_E_CLASSNOTAVAILABLE) return res; if (cod.Coder) return res; - return __externalCodecs->GetCodecs->CreateEncoder(i, &IID_ICompressFilter, (void **)&filter); + return _externalCodecs->GetCodecs->CreateEncoder(i, &IID_ICompressFilter, (void **)&filter); } cod.NumStreams = codec.NumStreams; - return __externalCodecs->GetCodecs->CreateEncoder(i, &IID_ICompressCoder2, (void **)&cod.Coder2); + return _externalCodecs->GetCodecs->CreateEncoder(i, &IID_ICompressCoder2, (void **)&cod.Coder2); } } else @@ -353,15 +402,15 @@ HRESULT CreateCoder( { if (codec.NumStreams == 1) { - HRESULT res = __externalCodecs->GetCodecs->CreateDecoder(i, &IID_ICompressCoder, (void **)&cod.Coder); + const HRESULT res = _externalCodecs->GetCodecs->CreateDecoder(i, &IID_ICompressCoder, (void **)&cod.Coder); if (res != S_OK && res != E_NOINTERFACE && res != CLASS_E_CLASSNOTAVAILABLE) return res; if (cod.Coder) return res; - return __externalCodecs->GetCodecs->CreateDecoder(i, &IID_ICompressFilter, (void **)&filter); + return _externalCodecs->GetCodecs->CreateDecoder(i, &IID_ICompressFilter, (void **)&filter); } cod.NumStreams = codec.NumStreams; - return __externalCodecs->GetCodecs->CreateDecoder(i, &IID_ICompressCoder2, (void **)&cod.Coder2); + return _externalCodecs->GetCodecs->CreateDecoder(i, &IID_ICompressCoder2, (void **)&cod.Coder2); } } } @@ -371,13 +420,50 @@ HRESULT CreateCoder( return S_OK; } -HRESULT CreateCoder( + +HRESULT CreateCoder_Index( + DECL_EXTERNAL_CODECS_LOC_VARS + unsigned index, bool encode, + CCreatedCoder &cod) +{ + CMyComPtr filter; + const HRESULT res = CreateCoder_Index( + EXTERNAL_CODECS_LOC_VARS + index, encode, + filter, cod); + + if (filter) + { + cod.IsFilter = true; + CFilterCoder *coderSpec = new CFilterCoder(encode); + cod.Coder = coderSpec; + coderSpec->Filter = filter; + } + + return res; +} + + +HRESULT CreateCoder_Id( + DECL_EXTERNAL_CODECS_LOC_VARS + CMethodId methodId, bool encode, + CMyComPtr &filter, + CCreatedCoder &cod) +{ + const int index = FindMethod_Index(EXTERNAL_CODECS_LOC_VARS methodId, encode); + if (index < 0) + return S_OK; + return CreateCoder_Index(EXTERNAL_CODECS_LOC_VARS (unsigned)index, encode, filter, cod); +} + + +HRESULT CreateCoder_Id( DECL_EXTERNAL_CODECS_LOC_VARS CMethodId methodId, bool encode, CCreatedCoder &cod) { CMyComPtr filter; - HRESULT res = CreateCoder( + const HRESULT res = CreateCoder_Id( EXTERNAL_CODECS_LOC_VARS methodId, encode, filter, cod); @@ -393,13 +479,14 @@ HRESULT CreateCoder( return res; } -HRESULT CreateCoder( + +HRESULT CreateCoder_Id( DECL_EXTERNAL_CODECS_LOC_VARS CMethodId methodId, bool encode, CMyComPtr &coder) { CCreatedCoder cod; - HRESULT res = CreateCoder( + const HRESULT res = CreateCoder_Id( EXTERNAL_CODECS_LOC_VARS methodId, encode, cod); @@ -413,7 +500,7 @@ HRESULT CreateFilter( CMyComPtr &filter) { CCreatedCoder cod; - return CreateCoder( + return CreateCoder_Id( EXTERNAL_CODECS_LOC_VARS methodId, encode, filter, cod); @@ -440,18 +527,18 @@ HRESULT CreateHasher( } } - #ifdef EXTERNAL_CODECS + #ifdef Z7_EXTERNAL_CODECS CHECK_GLOBAL_CODECS - if (!hasher && __externalCodecs) - for (i = 0; i < __externalCodecs->Hashers.Size(); i++) + if (!hasher && _externalCodecs) + for (i = 0; i < _externalCodecs->Hashers.Size(); i++) { - const CHasherInfoEx &codec = __externalCodecs->Hashers[i]; + const CHasherInfoEx &codec = _externalCodecs->Hashers[i]; if (codec.Id == methodId) { name = codec.Name; - return __externalCodecs->GetHashers->CreateHasher((UInt32)i, &hasher); + return _externalCodecs->GetHashers->CreateHasher((UInt32)i, &hasher); } } diff --git a/src/plugins/7zip/7za/cpp/7zip/Common/CreateCoder.h b/src/plugins/7zip/7za/cpp/7zip/Common/CreateCoder.h index f06064b6..709fe83c 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Common/CreateCoder.h +++ b/src/plugins/7zip/7za/cpp/7zip/Common/CreateCoder.h @@ -1,7 +1,7 @@ // CreateCoder.h -#ifndef __CREATE_CODER_H -#define __CREATE_CODER_H +#ifndef ZIP7_INC_CREATE_CODER_H +#define ZIP7_INC_CREATE_CODER_H #include "../../Common/MyCom.h" #include "../../Common/MyString.h" @@ -11,10 +11,10 @@ #include "MethodId.h" /* - if EXTERNAL_CODECS is not defined, the code supports only codecs that + if Z7_EXTERNAL_CODECS is not defined, the code supports only codecs that are statically linked at compile-time and link-time. - if EXTERNAL_CODECS is defined, the code supports also codecs from another + if Z7_EXTERNAL_CODECS is defined, the code supports also codecs from another executable modules, that can be linked dynamically at run-time: - EXE module can use codecs from external DLL files. - DLL module can use codecs from external EXE and DLL files. @@ -26,7 +26,7 @@ 2) External codecs */ -#ifdef EXTERNAL_CODECS +#ifdef Z7_EXTERNAL_CODECS struct CCodecInfoEx { @@ -35,8 +35,9 @@ struct CCodecInfoEx UInt32 NumStreams; bool EncoderIsAssigned; bool DecoderIsAssigned; + bool IsFilter; // it's unused - CCodecInfoEx(): EncoderIsAssigned(false), DecoderIsAssigned(false) {} + CCodecInfoEx(): EncoderIsAssigned(false), DecoderIsAssigned(false), IsFilter(false) {} }; struct CHasherInfoEx @@ -45,13 +46,17 @@ struct CHasherInfoEx AString Name; }; -#define PUBLIC_ISetCompressCodecsInfo public ISetCompressCodecsInfo, -#define QUERY_ENTRY_ISetCompressCodecsInfo MY_QUERYINTERFACE_ENTRY(ISetCompressCodecsInfo) -#define DECL_ISetCompressCodecsInfo STDMETHOD(SetCompressCodecsInfo)(ICompressCodecsInfo *compressCodecsInfo); -#define IMPL_ISetCompressCodecsInfo2(x) \ -STDMETHODIMP x::SetCompressCodecsInfo(ICompressCodecsInfo *compressCodecsInfo) { \ - COM_TRY_BEGIN __externalCodecs.GetCodecs = compressCodecsInfo; return __externalCodecs.Load(); COM_TRY_END } -#define IMPL_ISetCompressCodecsInfo IMPL_ISetCompressCodecsInfo2(CHandler) +#define Z7_PUBLIC_ISetCompressCodecsInfo_IFEC \ + public ISetCompressCodecsInfo, +#define Z7_COM_QI_ENTRY_ISetCompressCodecsInfo_IFEC \ + Z7_COM_QI_ENTRY(ISetCompressCodecsInfo) +#define DECL_ISetCompressCodecsInfo \ + Z7_COM7F_IMP(SetCompressCodecsInfo(ICompressCodecsInfo *compressCodecsInfo)) +#define IMPL_ISetCompressCodecsInfo2(cls) \ + Z7_COM7F_IMF(cls::SetCompressCodecsInfo(ICompressCodecsInfo *compressCodecsInfo)) \ + { COM_TRY_BEGIN _externalCodecs.GetCodecs = compressCodecsInfo; \ + return _externalCodecs.Load(); COM_TRY_END } +#define IMPL_ISetCompressCodecsInfo IMPL_ISetCompressCodecsInfo2(CHandler) struct CExternalCodecs { @@ -82,26 +87,27 @@ struct CExternalCodecs extern CExternalCodecs g_ExternalCodecs; -#define EXTERNAL_CODECS_VARS2 (__externalCodecs.IsSet() ? &__externalCodecs : &g_ExternalCodecs) -#define EXTERNAL_CODECS_VARS2_L (&__externalCodecs) +#define EXTERNAL_CODECS_VARS2 (_externalCodecs.IsSet() ? &_externalCodecs : &g_ExternalCodecs) +#define EXTERNAL_CODECS_VARS2_L (&_externalCodecs) #define EXTERNAL_CODECS_VARS2_G (&g_ExternalCodecs) -#define DECL_EXTERNAL_CODECS_VARS CExternalCodecs __externalCodecs; +#define DECL_EXTERNAL_CODECS_VARS CExternalCodecs _externalCodecs; #define EXTERNAL_CODECS_VARS EXTERNAL_CODECS_VARS2, #define EXTERNAL_CODECS_VARS_L EXTERNAL_CODECS_VARS2_L, #define EXTERNAL_CODECS_VARS_G EXTERNAL_CODECS_VARS2_G, -#define DECL_EXTERNAL_CODECS_LOC_VARS2 const CExternalCodecs *__externalCodecs -#define EXTERNAL_CODECS_LOC_VARS2 __externalCodecs +#define DECL_EXTERNAL_CODECS_LOC_VARS2 const CExternalCodecs *_externalCodecs +#define DECL_EXTERNAL_CODECS_LOC_VARS DECL_EXTERNAL_CODECS_LOC_VARS2, +#define DECL_EXTERNAL_CODECS_LOC_VARS_DECL DECL_EXTERNAL_CODECS_LOC_VARS2; -#define DECL_EXTERNAL_CODECS_LOC_VARS DECL_EXTERNAL_CODECS_LOC_VARS2, -#define EXTERNAL_CODECS_LOC_VARS EXTERNAL_CODECS_LOC_VARS2, +#define EXTERNAL_CODECS_LOC_VARS2 _externalCodecs +#define EXTERNAL_CODECS_LOC_VARS EXTERNAL_CODECS_LOC_VARS2, #else -#define PUBLIC_ISetCompressCodecsInfo -#define QUERY_ENTRY_ISetCompressCodecsInfo +#define Z7_PUBLIC_ISetCompressCodecsInfo_IFEC +#define Z7_COM_QI_ENTRY_ISetCompressCodecsInfo_IFEC #define DECL_ISetCompressCodecsInfo #define IMPL_ISetCompressCodecsInfo #define EXTERNAL_CODECS_VARS2 @@ -110,19 +116,20 @@ extern CExternalCodecs g_ExternalCodecs; #define EXTERNAL_CODECS_VARS_L #define EXTERNAL_CODECS_VARS_G #define DECL_EXTERNAL_CODECS_LOC_VARS2 -#define EXTERNAL_CODECS_LOC_VARS2 #define DECL_EXTERNAL_CODECS_LOC_VARS +#define DECL_EXTERNAL_CODECS_LOC_VARS_DECL +#define EXTERNAL_CODECS_LOC_VARS2 #define EXTERNAL_CODECS_LOC_VARS #endif - - - -bool FindMethod( +int FindMethod_Index( DECL_EXTERNAL_CODECS_LOC_VARS const AString &name, - CMethodId &methodId, UInt32 &numStreams); + bool encode, + CMethodId &methodId, + UInt32 &numStreams, + bool &isFilter); bool FindMethod( DECL_EXTERNAL_CODECS_LOC_VARS @@ -152,18 +159,29 @@ struct CCreatedCoder }; -HRESULT CreateCoder( +HRESULT CreateCoder_Index( + DECL_EXTERNAL_CODECS_LOC_VARS + unsigned codecIndex, bool encode, + CMyComPtr &filter, + CCreatedCoder &cod); + +HRESULT CreateCoder_Index( + DECL_EXTERNAL_CODECS_LOC_VARS + unsigned index, bool encode, + CCreatedCoder &cod); + +HRESULT CreateCoder_Id( DECL_EXTERNAL_CODECS_LOC_VARS CMethodId methodId, bool encode, CMyComPtr &filter, CCreatedCoder &cod); -HRESULT CreateCoder( +HRESULT CreateCoder_Id( DECL_EXTERNAL_CODECS_LOC_VARS CMethodId methodId, bool encode, CCreatedCoder &cod); -HRESULT CreateCoder( +HRESULT CreateCoder_Id( DECL_EXTERNAL_CODECS_LOC_VARS CMethodId methodId, bool encode, CMyComPtr &coder); diff --git a/src/plugins/7zip/7za/cpp/7zip/Common/FilePathAutoRename.cpp b/src/plugins/7zip/7za/cpp/7zip/Common/FilePathAutoRename.cpp index d186e599..81172d56 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Common/FilePathAutoRename.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Common/FilePathAutoRename.cpp @@ -2,9 +2,6 @@ #include "StdAfx.h" -#include "../../Common/Defs.h" -#include "../../Common/IntToString.h" - #include "../../Windows/FileFind.h" #include "FilePathAutoRename.h" @@ -14,34 +11,32 @@ using namespace NWindows; static bool MakeAutoName(const FString &name, const FString &extension, UInt32 value, FString &path) { - char temp[16]; - ConvertUInt32ToString(value, temp); path = name; - path.AddAscii(temp); + path.Add_UInt32(value); path += extension; return NFile::NFind::DoesFileOrDirExist(path); } bool AutoRenamePath(FString &path) { - int dotPos = path.ReverseFind_Dot(); - int slashPos = path.ReverseFind_PathSepar(); + const int dotPos = path.ReverseFind_Dot(); + const int slashPos = path.ReverseFind_PathSepar(); FString name = path; FString extension; if (dotPos > slashPos + 1) { - name.DeleteFrom(dotPos); - extension = path.Ptr(dotPos); + name.DeleteFrom((unsigned)dotPos); + extension = path.Ptr((unsigned)dotPos); } - name += FTEXT('_'); + name.Add_Char('_'); FString temp; - UInt32 left = 1, right = ((UInt32)1 << 30); + UInt32 left = 1, right = (UInt32)1 << 30; while (left != right) { - UInt32 mid = (left + right) / 2; + const UInt32 mid = (left + right) / 2; if (MakeAutoName(name, extension, mid, temp)) left = mid + 1; else diff --git a/src/plugins/7zip/7za/cpp/7zip/Common/FilePathAutoRename.h b/src/plugins/7zip/7za/cpp/7zip/Common/FilePathAutoRename.h index 7b576591..ac368823 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Common/FilePathAutoRename.h +++ b/src/plugins/7zip/7za/cpp/7zip/Common/FilePathAutoRename.h @@ -1,7 +1,7 @@ // FilePathAutoRename.h -#ifndef __FILE_PATH_AUTO_RENAME_H -#define __FILE_PATH_AUTO_RENAME_H +#ifndef ZIP7_INC_FILE_PATH_AUTO_RENAME_H +#define ZIP7_INC_FILE_PATH_AUTO_RENAME_H #include "../../Common/MyString.h" diff --git a/src/plugins/7zip/7za/cpp/7zip/Common/FileStreams.cpp b/src/plugins/7zip/7za/cpp/7zip/Common/FileStreams.cpp index f3a322fc..b7e4fbe2 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Common/FileStreams.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Common/FileStreams.cpp @@ -2,42 +2,83 @@ #include "StdAfx.h" +// #include + #ifndef _WIN32 #include #include #include +#include +#include + +/* +inclusion of by is deprecated since glibc 2.25. +Since glibc 2.3.3, macros have been aliases for three GNU-specific +functions: gnu_dev_makedev(), gnu_dev_major(), and gnu_dev_minor() + +Warning in GCC: +In the GNU C Library, "major" is defined by . +For historical compatibility, it is currently defined by + as well, but we plan to remove this soon. +To use "major", include directly. +If you did not intend to use a system-defined macro "major", +you should undefine it after including +*/ +// for major()/minor(): +#if defined(__APPLE__) || defined(__DragonFly__) || \ + defined(BSD) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) +#include +#else +#include #endif -#ifdef SUPPORT_DEVICE_FILE +#endif // _WIN32 + +#include "../../Windows/FileFind.h" + +#ifdef Z7_DEVICE_FILE #include "../../../C/Alloc.h" #include "../../Common/Defs.h" #endif +#include "../PropID.h" + #include "FileStreams.h" -static inline HRESULT ConvertBoolToHRESULT(bool result) +static inline HRESULT GetLastError_HRESULT() { - #ifdef _WIN32 - if (result) - return S_OK; DWORD lastError = ::GetLastError(); if (lastError == 0) return E_FAIL; return HRESULT_FROM_WIN32(lastError); - #else - return result ? S_OK: E_FAIL; - #endif } +static inline HRESULT ConvertBoolToHRESULT(bool result) +{ + if (result) + return S_OK; + return GetLastError_HRESULT(); +} + +#ifdef Z7_DEVICE_FILE static const UInt32 kClusterSize = 1 << 18; +#endif + CInFileStream::CInFileStream(): - #ifdef SUPPORT_DEVICE_FILE + #ifdef Z7_DEVICE_FILE VirtPos(0), PhyPos(0), - Buf(0), + Buf(NULL), BufSize(0), - #endif + #endif + #ifndef _WIN32 + _uid(0), + _gid(0), + StoreOwnerId(false), + StoreOwnerName(false), + #endif + _info_WasLoaded(false), SupportHardLinks(false), Callback(NULL), CallbackRef(0) @@ -46,19 +87,21 @@ CInFileStream::CInFileStream(): CInFileStream::~CInFileStream() { - #ifdef SUPPORT_DEVICE_FILE + #ifdef Z7_DEVICE_FILE MidFree(Buf); #endif if (Callback) - Callback->InFileStream_On_Destroy(CallbackRef); + Callback->InFileStream_On_Destroy(this, CallbackRef); } -STDMETHODIMP CInFileStream::Read(void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CInFileStream::Read(void *data, UInt32 size, UInt32 *processedSize)) { - #ifdef USE_WIN_FILE + // printf("\nCInFileStream::Read size=%d, VirtPos=%8d\n", (unsigned)size, (int)VirtPos); + + #ifdef Z7_FILE_STREAMS_USE_WIN_FILE - #ifdef SUPPORT_DEVICE_FILE + #ifdef Z7_DEVICE_FILE if (processedSize) *processedSize = 0; if (size == 0) @@ -69,7 +112,7 @@ STDMETHODIMP CInFileStream::Read(void *data, UInt32 size, UInt32 *processedSize) { if (VirtPos >= File.Size) return VirtPos == File.Size ? S_OK : E_FAIL; - UInt64 rem = File.Size - VirtPos; + const UInt64 rem = File.Size - VirtPos; if (size > rem) size = (UInt32)rem; } @@ -77,13 +120,13 @@ STDMETHODIMP CInFileStream::Read(void *data, UInt32 size, UInt32 *processedSize) { const UInt32 mask = kClusterSize - 1; const UInt64 mask2 = ~(UInt64)mask; - UInt64 alignedPos = VirtPos & mask2; + const UInt64 alignedPos = VirtPos & mask2; if (BufSize > 0 && BufStartPos == alignedPos) { - UInt32 pos = (UInt32)VirtPos & mask; + const UInt32 pos = (UInt32)VirtPos & mask; if (pos >= BufSize) return S_OK; - UInt32 rem = MyMin(BufSize - pos, size); + const UInt32 rem = MyMin(BufSize - pos, size); memcpy(data, Buf + pos, rem); VirtPos += rem; if (processedSize) @@ -92,7 +135,7 @@ STDMETHODIMP CInFileStream::Read(void *data, UInt32 size, UInt32 *processedSize) } bool useBuf = false; - if ((VirtPos & mask) != 0 || ((ptrdiff_t)data & mask) != 0 ) + if ((VirtPos & mask) != 0 || ((size_t)(ptrdiff_t)data & mask) != 0 ) useBuf = true; else { @@ -111,7 +154,7 @@ STDMETHODIMP CInFileStream::Read(void *data, UInt32 size, UInt32 *processedSize) if (alignedPos != PhyPos) { UInt64 realNewPosition; - bool result = File.Seek(alignedPos, FILE_BEGIN, realNewPosition); + const bool result = File.Seek((Int64)alignedPos, FILE_BEGIN, realNewPosition); if (!result) return ConvertBoolToHRESULT(result); PhyPos = realNewPosition; @@ -128,7 +171,7 @@ STDMETHODIMP CInFileStream::Read(void *data, UInt32 size, UInt32 *processedSize) if (!Buf) return E_OUTOFMEMORY; } - bool result = File.Read1(Buf, readSize, BufSize); + const bool result = File.Read1(Buf, readSize, BufSize); if (!result) return ConvertBoolToHRESULT(result); @@ -140,7 +183,7 @@ STDMETHODIMP CInFileStream::Read(void *data, UInt32 size, UInt32 *processedSize) if (VirtPos != PhyPos) { UInt64 realNewPosition; - bool result = File.Seek(VirtPos, FILE_BEGIN, realNewPosition); + bool result = File.Seek((Int64)VirtPos, FILE_BEGIN, realNewPosition); if (!result) return ConvertBoolToHRESULT(result); PhyPos = VirtPos = realNewPosition; @@ -149,11 +192,11 @@ STDMETHODIMP CInFileStream::Read(void *data, UInt32 size, UInt32 *processedSize) #endif UInt32 realProcessedSize; - bool result = File.ReadPart(data, size, realProcessedSize); + const bool result = File.ReadPart(data, size, realProcessedSize); if (processedSize) *processedSize = realProcessedSize; - #ifdef SUPPORT_DEVICE_FILE + #ifdef Z7_DEVICE_FILE VirtPos += realProcessedSize; PhyPos += realProcessedSize; #endif @@ -161,37 +204,35 @@ STDMETHODIMP CInFileStream::Read(void *data, UInt32 size, UInt32 *processedSize) if (result) return S_OK; + #else // Z7_FILE_STREAMS_USE_WIN_FILE + + if (processedSize) + *processedSize = 0; + const ssize_t res = File.read_part(data, (size_t)size); + if (res != -1) { - DWORD error = ::GetLastError(); + if (processedSize) + *processedSize = (UInt32)res; + return S_OK; + } + #endif // Z7_FILE_STREAMS_USE_WIN_FILE + { + const DWORD error = ::GetLastError(); +#if 0 + if (File.IsStdStream && error == ERROR_BROKEN_PIPE) + return S_OK; // end of stream +#endif if (Callback) return Callback->InFileStream_On_Error(CallbackRef, error); if (error == 0) return E_FAIL; - return HRESULT_FROM_WIN32(error); } - - #else - - if (processedSize) - *processedSize = 0; - ssize_t res = File.Read(data, (size_t)size); - if (res == -1) - { - if (Callback) - return Callback->InFileStream_On_Error(CallbackRef, E_FAIL); - return E_FAIL; - } - if (processedSize) - *processedSize = (UInt32)res; - return S_OK; - - #endif } #ifdef UNDER_CE -STDMETHODIMP CStdInFileStream::Read(void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CStdInFileStream::Read(void *data, UInt32 size, UInt32 *processedSize)) { size_t s2 = fread(data, 1, size, stdin); int error = ferror(stdin); @@ -202,15 +243,37 @@ STDMETHODIMP CStdInFileStream::Read(void *data, UInt32 size, UInt32 *processedSi return E_FAIL; } #else -STDMETHODIMP CStdInFileStream::Read(void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CStdInFileStream::Read(void *data, UInt32 size, UInt32 *processedSize)) { + // printf("\nCStdInFileStream::Read size = %d\n", (unsigned)size); #ifdef _WIN32 DWORD realProcessedSize; UInt32 sizeTemp = (1 << 20); if (sizeTemp > size) sizeTemp = size; + /* in GUI mode : GetStdHandle(STD_INPUT_HANDLE) returns NULL, + and it doesn't set LastError. */ + /* + SetLastError(0); + const HANDLE h = GetStdHandle(STD_INPUT_HANDLE); + if (!h || h == INVALID_HANDLE_VALUE) + { + if (processedSize) + *processedSize = 0; + if (GetLastError() == 0) + SetLastError(ERROR_INVALID_HANDLE); + return GetLastError_noZero_HRESULT(); + } + */ BOOL res = ::ReadFile(GetStdHandle(STD_INPUT_HANDLE), data, sizeTemp, &realProcessedSize, NULL); + + /* + printf("\nCInFileStream::Read: size=%d, processed=%8d res=%d 4rror=%3d\n", + (unsigned)size, (int)realProcessedSize, + (int)res, GetLastError()); + */ + if (processedSize) *processedSize = realProcessedSize; if (res == FALSE && GetLastError() == ERROR_BROKEN_PIPE) @@ -228,7 +291,7 @@ STDMETHODIMP CStdInFileStream::Read(void *data, UInt32 size, UInt32 *processedSi } while (res < 0 && (errno == EINTR)); if (res == -1) - return E_FAIL; + return GetLastError_HRESULT(); if (processedSize) *processedSize = (UInt32)res; return S_OK; @@ -238,14 +301,68 @@ STDMETHODIMP CStdInFileStream::Read(void *data, UInt32 size, UInt32 *processedSi #endif -STDMETHODIMP CInFileStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition) + +/* +bool CreateStdInStream(CMyComPtr &str) { +#if 0 + CInFileStream *inStreamSpec = new CInFileStream; + CMyComPtr inStreamLoc(inStreamSpec);; + if (!inStreamSpec->OpenStdIn()) + return false; + if (!inStreamSpec->File.IsStdPipeStream) + str = inStreamLoc.Detach(); + else +#endif + str = new CStdInFileStream; + return true; +} +*/ + +#if 0 +bool CInFileStream::OpenStdIn() +{ + _info_WasLoaded = false; + // Sleep(100); + bool res = File.AttachStdIn(); + if (!res) + return false; +#if 1 + CStreamFileProps props; + if (GetProps2(&props) != S_OK) + { + // we can ignore that error + return false; + } + // we can't use Size, because Size can be set for pipe streams for some value. + // Seek() sees only current chunk in pipe buffer. + // So Seek() can move across only current unread chunk. + // But after reading that chunk. it can't move position back. + // We need safe check that shows that we can use seek (non-pipe mode) + // Is it safe check that shows that pipe mode was used? + File.IsStdPipeStream = (props.VolID == 0); + // && FILETIME_IsZero(props.CTime) + // && FILETIME_IsZero(props.ATime) + // && FILETIME_IsZero(props.MTime); +#endif + // printf("\n######## pipe=%d", (unsigned)File.IsStdPipeStream); + return true; +} +#endif + + +Z7_COM7F_IMF(CInFileStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition)) +{ + /* + printf("\nCInFileStream::Seek seekOrigin=%d, offset=%8d, VirtPos=%8d\n", + (unsigned)seekOrigin, (int)offset, (int)VirtPos); + */ if (seekOrigin >= 3) return STG_E_INVALIDFUNCTION; - #ifdef USE_WIN_FILE + #ifdef Z7_FILE_STREAMS_USE_WIN_FILE - #ifdef SUPPORT_DEVICE_FILE + #ifdef Z7_DEVICE_FILE if (File.IsDeviceFile && (File.SizeDefined || seekOrigin != STREAM_SEEK_END)) { switch (seekOrigin) @@ -257,29 +374,40 @@ STDMETHODIMP CInFileStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPos } if (offset < 0) return HRESULT_WIN32_ERROR_NEGATIVE_SEEK; - VirtPos = offset; + VirtPos = (UInt64)offset; if (newPosition) - *newPosition = offset; + *newPosition = (UInt64)offset; return S_OK; } #endif - UInt64 realNewPosition; - bool result = File.Seek(offset, seekOrigin, realNewPosition); + UInt64 realNewPosition = 0; + const bool result = File.Seek(offset, seekOrigin, realNewPosition); + const HRESULT hres = ConvertBoolToHRESULT(result); + + /* 21.07: new File.Seek() in 21.07 already returns correct (realNewPosition) + in case of error. So we don't need additional code below */ + // if (!result) { realNewPosition = 0; File.GetPosition(realNewPosition); } - #ifdef SUPPORT_DEVICE_FILE + #ifdef Z7_DEVICE_FILE PhyPos = VirtPos = realNewPosition; #endif if (newPosition) *newPosition = realNewPosition; - return ConvertBoolToHRESULT(result); + + return hres; #else - off_t res = File.Seek((off_t)offset, seekOrigin); + const off_t res = File.seek((off_t)offset, (int)seekOrigin); if (res == -1) - return E_FAIL; + { + const HRESULT hres = GetLastError_HRESULT(); + if (newPosition) + *newPosition = (UInt64)File.seekToCur(); + return hres; + } if (newPosition) *newPosition = (UInt64)res; return S_OK; @@ -287,17 +415,25 @@ STDMETHODIMP CInFileStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPos #endif } -STDMETHODIMP CInFileStream::GetSize(UInt64 *size) +Z7_COM7F_IMF(CInFileStream::GetSize(UInt64 *size)) { return ConvertBoolToHRESULT(File.GetLength(*size)); } -#ifdef USE_WIN_FILE +#ifdef Z7_FILE_STREAMS_USE_WIN_FILE -STDMETHODIMP CInFileStream::GetProps(UInt64 *size, FILETIME *cTime, FILETIME *aTime, FILETIME *mTime, UInt32 *attrib) +Z7_COM7F_IMF(CInFileStream::GetProps(UInt64 *size, FILETIME *cTime, FILETIME *aTime, FILETIME *mTime, UInt32 *attrib)) { + if (!_info_WasLoaded) + { + RINOK(ReloadProps()) + } + const BY_HANDLE_FILE_INFORMATION &info = _info; + /* BY_HANDLE_FILE_INFORMATION info; - if (File.GetFileInformation(&info)) + if (!File.GetFileInformation(&info)) + return GetLastError_HRESULT(); + */ { if (size) *size = (((UInt64)info.nFileSizeHigh) << 32) + info.nFileSizeLow; if (cTime) *cTime = info.ftCreationTime; @@ -306,13 +442,20 @@ STDMETHODIMP CInFileStream::GetProps(UInt64 *size, FILETIME *cTime, FILETIME *aT if (attrib) *attrib = info.dwFileAttributes; return S_OK; } - return GetLastError(); } -STDMETHODIMP CInFileStream::GetProps2(CStreamFileProps *props) +Z7_COM7F_IMF(CInFileStream::GetProps2(CStreamFileProps *props)) { + if (!_info_WasLoaded) + { + RINOK(ReloadProps()) + } + const BY_HANDLE_FILE_INFORMATION &info = _info; + /* BY_HANDLE_FILE_INFORMATION info; - if (File.GetFileInformation(&info)) + if (!File.GetFileInformation(&info)) + return GetLastError_HRESULT(); + */ { props->Size = (((UInt64)info.nFileSizeHigh) << 32) + info.nFileSizeLow; props->VolID = info.dwVolumeSerialNumber; @@ -325,11 +468,329 @@ STDMETHODIMP CInFileStream::GetProps2(CStreamFileProps *props) props->MTime = info.ftLastWriteTime; return S_OK; } - return GetLastError(); +} + +Z7_COM7F_IMF(CInFileStream::GetProperty(PROPID propID, PROPVARIANT *value)) +{ + if (!_info_WasLoaded) + { + RINOK(ReloadProps()) + } + + if (!_info_WasLoaded) + return S_OK; + + NWindows::NCOM::CPropVariant prop; + + #ifdef Z7_DEVICE_FILE + if (File.IsDeviceFile) + { + switch (propID) + { + case kpidSize: + if (File.SizeDefined) + prop = File.Size; + break; + // case kpidAttrib: prop = (UInt32)0; break; + case kpidPosixAttrib: + { + prop = (UInt32)NWindows::NFile::NFind::NAttributes:: + Get_PosixMode_From_WinAttrib(0); + /* GNU TAR by default can't extract file with MY_LIN_S_IFBLK attribute + so we don't use MY_LIN_S_IFBLK here */ + // prop = (UInt32)(MY_LIN_S_IFBLK | 0600); // for debug + break; + } + /* + case kpidDeviceMajor: + prop = (UInt32)8; // id for SCSI type device (sda) + break; + case kpidDeviceMinor: + prop = (UInt32)0; + break; + */ + } + } + else + #endif + { + switch (propID) + { + case kpidSize: + { + const UInt64 size = (((UInt64)_info.nFileSizeHigh) << 32) + _info.nFileSizeLow; + prop = size; + break; + } + case kpidAttrib: prop = (UInt32)_info.dwFileAttributes; break; + case kpidCTime: PropVariant_SetFrom_FiTime(prop, _info.ftCreationTime); break; + case kpidATime: PropVariant_SetFrom_FiTime(prop, _info.ftLastAccessTime); break; + case kpidMTime: PropVariant_SetFrom_FiTime(prop, _info.ftLastWriteTime); break; + case kpidPosixAttrib: + prop = (UInt32)NWindows::NFile::NFind::NAttributes:: + Get_PosixMode_From_WinAttrib(_info.dwFileAttributes); + // | (UInt32)(1 << 21); // for debug + break; + } + } + prop.Detach(value); + return S_OK; +} + + +Z7_COM7F_IMF(CInFileStream::ReloadProps()) +{ + #ifdef Z7_DEVICE_FILE + if (File.IsDeviceFile) + { + memset(&_info, 0, sizeof(_info)); + if (File.SizeDefined) + { + _info.nFileSizeHigh = (DWORD)(File.Size >> 32); + _info.nFileSizeLow = (DWORD)(File.Size); + } + _info.nNumberOfLinks = 1; + _info_WasLoaded = true; + return S_OK; + } + #endif + _info_WasLoaded = File.GetFileInformation(&_info); + if (!_info_WasLoaded) + return GetLastError_HRESULT(); +#ifdef _WIN32 +#if 0 + printf( + "\ndwFileAttributes = %8x" + "\nftCreationTime = %8x" + "\nftLastAccessTime = %8x" + "\nftLastWriteTime = %8x" + "\ndwVolumeSerialNumber = %8x" + "\nnFileSizeHigh = %8x" + "\nnFileSizeLow = %8x" + "\nnNumberOfLinks = %8x" + "\nnFileIndexHigh = %8x" + "\nnFileIndexLow = %8x \n", + (unsigned)_info.dwFileAttributes, + (unsigned)_info.ftCreationTime.dwHighDateTime, + (unsigned)_info.ftLastAccessTime.dwHighDateTime, + (unsigned)_info.ftLastWriteTime.dwHighDateTime, + (unsigned)_info.dwVolumeSerialNumber, + (unsigned)_info.nFileSizeHigh, + (unsigned)_info.nFileSizeLow, + (unsigned)_info.nNumberOfLinks, + (unsigned)_info.nFileIndexHigh, + (unsigned)_info.nFileIndexLow); +#endif +#endif + return S_OK; +} + + +#elif !defined(_WIN32) + +Z7_COM7F_IMF(CInFileStream::GetProps(UInt64 *size, FILETIME *cTime, FILETIME *aTime, FILETIME *mTime, UInt32 *attrib)) +{ + // printf("\nCInFileStream::GetProps VirtPos = %8d\n", (int)VirtPos); + if (!_info_WasLoaded) + { + RINOK(ReloadProps()) + } + const struct stat &st = _info; + /* + struct stat st; + if (File.my_fstat(&st) != 0) + return GetLastError_HRESULT(); + */ + + if (size) *size = (UInt64)st.st_size; + if (cTime) FiTime_To_FILETIME (ST_CTIME(st), *cTime); + if (aTime) FiTime_To_FILETIME (ST_ATIME(st), *aTime); + if (mTime) FiTime_To_FILETIME (ST_MTIME(st), *mTime); + if (attrib) *attrib = NWindows::NFile::NFind::Get_WinAttribPosix_From_PosixMode(st.st_mode); + + return S_OK; +} + +// #include + +Z7_COM7F_IMF(CInFileStream::GetProps2(CStreamFileProps *props)) +{ + // printf("\nCInFileStream::GetProps2 VirtPos = %8d\n", (int)VirtPos); + if (!_info_WasLoaded) + { + RINOK(ReloadProps()) + } + const struct stat &st = _info; + /* + struct stat st; + if (File.my_fstat(&st) != 0) + return GetLastError_HRESULT(); + */ + + props->Size = (UInt64)st.st_size; + /* + dev_t stat::st_dev: + GCC:Linux long unsigned int : __dev_t + Mac: int + */ + props->VolID = (UInt64)(Int64)st.st_dev; + props->FileID_Low = st.st_ino; + props->FileID_High = 0; + props->NumLinks = (UInt32)st.st_nlink; // we reduce to UInt32 from (nlink_t) that is (unsigned long) + props->Attrib = NWindows::NFile::NFind::Get_WinAttribPosix_From_PosixMode(st.st_mode); + + FiTime_To_FILETIME (ST_CTIME(st), props->CTime); + FiTime_To_FILETIME (ST_ATIME(st), props->ATime); + FiTime_To_FILETIME (ST_MTIME(st), props->MTime); + + /* + printf("\nGetProps2() NumLinks=%d = st_dev=%d st_ino = %d\n" + , (unsigned)(props->NumLinks) + , (unsigned)(st.st_dev) + , (unsigned)(st.st_ino) + ); + */ + + return S_OK; +} + +Z7_COM7F_IMF(CInFileStream::GetProperty(PROPID propID, PROPVARIANT *value)) +{ + // printf("\nCInFileStream::GetProperty VirtPos = %8d propID = %3d\n", (int)VirtPos, propID); + if (!_info_WasLoaded) + { + RINOK(ReloadProps()) + } + + if (!_info_WasLoaded) + return S_OK; + + const struct stat &st = _info; + + NWindows::NCOM::CPropVariant prop; + { + switch (propID) + { + case kpidSize: prop = (UInt64)st.st_size; break; + case kpidAttrib: + prop = (UInt32)NWindows::NFile::NFind::Get_WinAttribPosix_From_PosixMode(st.st_mode); + break; + case kpidCTime: PropVariant_SetFrom_FiTime(prop, ST_CTIME(st)); break; + case kpidATime: PropVariant_SetFrom_FiTime(prop, ST_ATIME(st)); break; + case kpidMTime: PropVariant_SetFrom_FiTime(prop, ST_MTIME(st)); break; + case kpidPosixAttrib: prop = (UInt32)st.st_mode; break; + + #if defined(__APPLE__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wsign-conversion" + #endif + + case kpidDeviceMajor: + { + // printf("\nst.st_rdev = %d\n", st.st_rdev); + if (S_ISCHR(st.st_mode) || + S_ISBLK(st.st_mode)) + prop = (UInt32)(major(st.st_rdev)); // + 1000); + // prop = (UInt32)12345678; // for debug + break; + } + + case kpidDeviceMinor: + if (S_ISCHR(st.st_mode) || + S_ISBLK(st.st_mode)) + prop = (UInt32)(minor(st.st_rdev)); // + 100); + // prop = (UInt32)(st.st_rdev); // for debug + // printf("\nst.st_rdev = %d\n", st.st_rdev); + // prop = (UInt32)123456789; // for debug + break; + + #if defined(__APPLE__) + #pragma GCC diagnostic pop + #endif + + /* + case kpidDevice: + if (S_ISCHR(st.st_mode) || + S_ISBLK(st.st_mode)) + prop = (UInt64)(st.st_rdev); + break; + */ + + case kpidUserId: + { + if (StoreOwnerId) + prop = (UInt32)st.st_uid; + break; + } + case kpidGroupId: + { + if (StoreOwnerId) + prop = (UInt32)st.st_gid; + break; + } + case kpidUser: + { + if (StoreOwnerName) + { + const uid_t uid = st.st_uid; + { + if (!OwnerName.IsEmpty() && _uid == uid) + prop = OwnerName; + else + { + const passwd *pw = getpwuid(uid); + if (pw) + { + // we can use utf-8 here. + // prop = pw->pw_name; + } + } + } + } + break; + } + case kpidGroup: + { + if (StoreOwnerName) + { + const gid_t gid = st.st_gid; + { + if (!OwnerGroup.IsEmpty() && _gid == gid) + prop = OwnerGroup; + else + { + const group *gr = getgrgid(gid); + if (gr) + { + // we can use utf-8 here. + // prop = gr->gr_name; + } + } + } + } + break; + } + default: break; + } + } + prop.Detach(value); + return S_OK; +} + + +Z7_COM7F_IMF(CInFileStream::ReloadProps()) +{ + _info_WasLoaded = (File.my_fstat(&_info) == 0); + if (!_info_WasLoaded) + return GetLastError_HRESULT(); + return S_OK; } #endif + + + ////////////////////////// // COutFileStream @@ -338,12 +799,12 @@ HRESULT COutFileStream::Close() return ConvertBoolToHRESULT(File.Close()); } -STDMETHODIMP COutFileStream::Write(const void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(COutFileStream::Write(const void *data, UInt32 size, UInt32 *processedSize)) { - #ifdef USE_WIN_FILE + #ifdef Z7_FILE_STREAMS_USE_WIN_FILE UInt32 realProcessedSize; - bool result = File.Write(data, size, realProcessedSize); + const bool result = File.Write(data, size, realProcessedSize); ProcessedSize += realProcessedSize; if (processedSize) *processedSize = realProcessedSize; @@ -353,35 +814,36 @@ STDMETHODIMP COutFileStream::Write(const void *data, UInt32 size, UInt32 *proces if (processedSize) *processedSize = 0; - ssize_t res = File.Write(data, (size_t)size); - if (res == -1) - return E_FAIL; + size_t realProcessedSize; + const ssize_t res = File.write_full(data, (size_t)size, realProcessedSize); + ProcessedSize += realProcessedSize; if (processedSize) - *processedSize = (UInt32)res; - ProcessedSize += res; + *processedSize = (UInt32)realProcessedSize; + if (res == -1) + return GetLastError_HRESULT(); return S_OK; #endif } -STDMETHODIMP COutFileStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition) +Z7_COM7F_IMF(COutFileStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition)) { if (seekOrigin >= 3) return STG_E_INVALIDFUNCTION; - #ifdef USE_WIN_FILE + #ifdef Z7_FILE_STREAMS_USE_WIN_FILE - UInt64 realNewPosition; - bool result = File.Seek(offset, seekOrigin, realNewPosition); + UInt64 realNewPosition = 0; + const bool result = File.Seek(offset, seekOrigin, realNewPosition); if (newPosition) *newPosition = realNewPosition; return ConvertBoolToHRESULT(result); #else - off_t res = File.Seek((off_t)offset, seekOrigin); + const off_t res = File.seek((off_t)offset, (int)seekOrigin); if (res == -1) - return E_FAIL; + return GetLastError_HRESULT(); if (newPosition) *newPosition = (UInt64)res; return S_OK; @@ -389,23 +851,9 @@ STDMETHODIMP COutFileStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPo #endif } -STDMETHODIMP COutFileStream::SetSize(UInt64 newSize) +Z7_COM7F_IMF(COutFileStream::SetSize(UInt64 newSize)) { - #ifdef USE_WIN_FILE - - UInt64 currentPos; - if (!File.Seek(0, FILE_CURRENT, currentPos)) - return E_FAIL; - bool result = File.SetLength(newSize); - UInt64 currentPos2; - result = result && File.Seek(currentPos, currentPos2); - return result ? S_OK : E_FAIL; - - #else - - return E_FAIL; - - #endif + return ConvertBoolToHRESULT(File.SetLength_KeepPosition(newSize)); } HRESULT COutFileStream::GetSize(UInt64 *size) @@ -415,7 +863,7 @@ HRESULT COutFileStream::GetSize(UInt64 *size) #ifdef UNDER_CE -STDMETHODIMP CStdOutFileStream::Write(const void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CStdOutFileStream::Write(const void *data, UInt32 size, UInt32 *processedSize)) { size_t s2 = fwrite(data, 1, size, stdout); if (processedSize) @@ -425,7 +873,7 @@ STDMETHODIMP CStdOutFileStream::Write(const void *data, UInt32 size, UInt32 *pro #else -STDMETHODIMP CStdOutFileStream::Write(const void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CStdOutFileStream::Write(const void *data, UInt32 size, UInt32 *processedSize)) { if (processedSize) *processedSize = 0; @@ -462,7 +910,7 @@ STDMETHODIMP CStdOutFileStream::Write(const void *data, UInt32 size, UInt32 *pro while (res < 0 && (errno == EINTR)); if (res == -1) - return E_FAIL; + return GetLastError_HRESULT(); _size += (size_t)res; if (processedSize) diff --git a/src/plugins/7zip/7za/cpp/7zip/Common/FileStreams.h b/src/plugins/7zip/7za/cpp/7zip/Common/FileStreams.h index ef2986fd..7f465cf6 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Common/FileStreams.h +++ b/src/plugins/7zip/7za/cpp/7zip/Common/FileStreams.h @@ -1,50 +1,76 @@ // FileStreams.h -#ifndef __FILE_STREAMS_H -#define __FILE_STREAMS_H +#ifndef ZIP7_INC_FILE_STREAMS_H +#define ZIP7_INC_FILE_STREAMS_H #ifdef _WIN32 -#define USE_WIN_FILE +#define Z7_FILE_STREAMS_USE_WIN_FILE #endif +#include "../../Common/MyCom.h" #include "../../Common/MyString.h" -#ifdef USE_WIN_FILE #include "../../Windows/FileIO.h" -#else -#include "../../Common/C_FileIO.h" -#endif - -#include "../../Common/MyCom.h" #include "../IStream.h" -#ifdef _WIN32 -typedef UINT_PTR My_UINT_PTR; -#else -typedef UINT My_UINT_PTR; -#endif +#include "UniqBlocks.h" + -struct IInFileStream_Callback +class CInFileStream; + +Z7_PURE_INTERFACES_BEGIN +DECLARE_INTERFACE(IInFileStream_Callback) { - virtual HRESULT InFileStream_On_Error(My_UINT_PTR val, DWORD error) = 0; - virtual void InFileStream_On_Destroy(My_UINT_PTR val) = 0; + virtual HRESULT InFileStream_On_Error(UINT_PTR val, DWORD error) = 0; + virtual void InFileStream_On_Destroy(CInFileStream *stream, UINT_PTR val) = 0; }; - -class CInFileStream: +Z7_PURE_INTERFACES_END + + +/* +Z7_CLASS_IMP_COM_5( + CInFileStream + , IInStream + , IStreamGetSize + , IStreamGetProps + , IStreamGetProps2 + , IStreamGetProp +) +*/ +Z7_class_final(CInFileStream) : public IInStream, public IStreamGetSize, - #ifdef USE_WIN_FILE public IStreamGetProps, public IStreamGetProps2, - #endif + public IStreamGetProp, public CMyUnknownImp { + Z7_COM_UNKNOWN_IMP_6( + IInStream, + ISequentialInStream, + IStreamGetSize, + IStreamGetProps, + IStreamGetProps2, + IStreamGetProp) + + Z7_IFACE_COM7_IMP(ISequentialInStream) + Z7_IFACE_COM7_IMP(IInStream) +public: + Z7_IFACE_COM7_IMP(IStreamGetSize) +private: + Z7_IFACE_COM7_IMP(IStreamGetProps) public: - #ifdef USE_WIN_FILE + Z7_IFACE_COM7_IMP(IStreamGetProps2) + Z7_IFACE_COM7_IMP(IStreamGetProp) + +private: NWindows::NFile::NIO::CInFile File; +public: + + #ifdef Z7_FILE_STREAMS_USE_WIN_FILE - #ifdef SUPPORT_DEVICE_FILE + #ifdef Z7_DEVICE_FILE UInt64 VirtPos; UInt64 PhyPos; UInt64 BufStartPos; @@ -52,115 +78,128 @@ class CInFileStream: UInt32 BufSize; #endif - #else - NC::NFile::NIO::CInFile File; #endif + #ifdef _WIN32 + BY_HANDLE_FILE_INFORMATION _info; + #else + struct stat _info; + uid_t _uid; // uid_t can be unsigned or signed int + gid_t _gid; + UString OwnerName; + UString OwnerGroup; + bool StoreOwnerId; + bool StoreOwnerName; + #endif + + bool _info_WasLoaded; bool SupportHardLinks; - IInFileStream_Callback *Callback; - My_UINT_PTR CallbackRef; - - virtual ~CInFileStream(); + UINT_PTR CallbackRef; CInFileStream(); + ~CInFileStream(); + + void Set_PreserveATime(bool v) + { + File.PreserveATime = v; + } + + bool GetLength(UInt64 &length) const throw() + { + return File.GetLength(length); + } + +#if 0 + bool OpenStdIn(); +#endif bool Open(CFSTR fileName) { + _info_WasLoaded = false; return File.Open(fileName); } bool OpenShared(CFSTR fileName, bool shareForWrite) { + _info_WasLoaded = false; return File.OpenShared(fileName, shareForWrite); } - - MY_QUERYINTERFACE_BEGIN2(IInStream) - MY_QUERYINTERFACE_ENTRY(IStreamGetSize) - #ifdef USE_WIN_FILE - MY_QUERYINTERFACE_ENTRY(IStreamGetProps) - MY_QUERYINTERFACE_ENTRY(IStreamGetProps2) - #endif - MY_QUERYINTERFACE_END - MY_ADDREF_RELEASE - - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); - STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition); - - STDMETHOD(GetSize)(UInt64 *size); - #ifdef USE_WIN_FILE - STDMETHOD(GetProps)(UInt64 *size, FILETIME *cTime, FILETIME *aTime, FILETIME *mTime, UInt32 *attrib); - STDMETHOD(GetProps2)(CStreamFileProps *props); - #endif }; -class CStdInFileStream: - public ISequentialInStream, - public CMyUnknownImp -{ -public: - MY_UNKNOWN_IMP +// bool CreateStdInStream(CMyComPtr &str); - virtual ~CStdInFileStream() {} - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); +Z7_CLASS_IMP_NOQIB_1( + CStdInFileStream + , ISequentialInStream +) }; -class COutFileStream: - public IOutStream, - public CMyUnknownImp -{ + +Z7_CLASS_IMP_COM_1( + COutFileStream + , IOutStream +) + Z7_IFACE_COM7_IMP(ISequentialOutStream) public: - #ifdef USE_WIN_FILE + NWindows::NFile::NIO::COutFile File; - #else - NC::NFile::NIO::COutFile File; - #endif - virtual ~COutFileStream() {} - bool Create(CFSTR fileName, bool createAlways) + + bool Create_NEW(CFSTR fileName) { ProcessedSize = 0; - return File.Create(fileName, createAlways); + return File.Create_NEW(fileName); } - bool Open(CFSTR fileName, DWORD creationDisposition) + + bool Create_ALWAYS(CFSTR fileName) + { + ProcessedSize = 0; + return File.Create_ALWAYS(fileName); + } + + bool Open_EXISTING(CFSTR fileName) { ProcessedSize = 0; - return File.Open(fileName, creationDisposition); + return File.Open_EXISTING(fileName); + } + + bool Create_ALWAYS_or_Open_ALWAYS(CFSTR fileName, bool createAlways) + { + ProcessedSize = 0; + return File.Create_ALWAYS_or_Open_ALWAYS(fileName, createAlways); } HRESULT Close(); UInt64 ProcessedSize; - #ifdef USE_WIN_FILE - bool SetTime(const FILETIME *cTime, const FILETIME *aTime, const FILETIME *mTime) + bool SetTime(const CFiTime *cTime, const CFiTime *aTime, const CFiTime *mTime) { return File.SetTime(cTime, aTime, mTime); } - bool SetMTime(const FILETIME *mTime) { return File.SetMTime(mTime); } - #endif - - - MY_UNKNOWN_IMP1(IOutStream) + bool SetMTime(const CFiTime *mTime) { return File.SetMTime(mTime); } - STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize); - STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition); - STDMETHOD(SetSize)(UInt64 newSize); + bool SeekToBegin_bool() + { + #ifdef Z7_FILE_STREAMS_USE_WIN_FILE + return File.SeekToBegin(); + #else + return File.seekToBegin() == 0; + #endif + } HRESULT GetSize(UInt64 *size); }; -class CStdOutFileStream: - public ISequentialOutStream, - public CMyUnknownImp -{ + +Z7_CLASS_IMP_NOQIB_1( + CStdOutFileStream + , ISequentialOutStream +) UInt64 _size; public: - MY_UNKNOWN_IMP - UInt64 GetSize() const { return _size; } CStdOutFileStream(): _size(0) {} - virtual ~CStdOutFileStream() {} - STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize); }; #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Common/FilterCoder.cpp b/src/plugins/7zip/7za/cpp/7zip/Common/FilterCoder.cpp index 4f3ae4e7..8d7e0dcc 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Common/FilterCoder.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Common/FilterCoder.cpp @@ -2,11 +2,30 @@ #include "StdAfx.h" +// #include + #include "../../Common/Defs.h" #include "FilterCoder.h" #include "StreamUtils.h" +#ifdef _WIN32 + #define alignedMidBuffer_Alloc g_MidAlloc +#else + #define alignedMidBuffer_Alloc g_AlignedAlloc +#endif + +CAlignedMidBuffer::~CAlignedMidBuffer() +{ + ISzAlloc_Free(&alignedMidBuffer_Alloc, _buf); +} + +void CAlignedMidBuffer::AllocAligned(size_t size) +{ + ISzAlloc_Free(&alignedMidBuffer_Alloc, _buf); + _buf = (Byte *)ISzAlloc_Alloc(&alignedMidBuffer_Alloc, size); +} + /* AES filters need 16-bytes alignment for HARDWARE-AES instructions. So we call IFilter::Filter(, size), where (size != 16 * N) only for last data block. @@ -16,13 +35,17 @@ Some filters (BCJ and others) don't process data at the end of stream in some cases. So the encoder and decoder write such last bytes without change. + + Most filters process all data, if we send aligned size to filter. + But BCJ filter can process up 4 bytes less than sent size. + And ARMT filter can process 2 bytes less than sent size. */ -static const UInt32 kBufSize = 1 << 20; +static const UInt32 kBufSize = 1 << 21; -STDMETHODIMP CFilterCoder::SetInBufSize(UInt32 , UInt32 size) { _inBufSize = size; return S_OK; } -STDMETHODIMP CFilterCoder::SetOutBufSize(UInt32 , UInt32 size) { _outBufSize = size; return S_OK; } +Z7_COM7F_IMF(CFilterCoder::SetInBufSize(UInt32 , UInt32 size)) { _inBufSize = size; return S_OK; } +Z7_COM7F_IMF(CFilterCoder::SetOutBufSize(UInt32 , UInt32 size)) { _outBufSize = size; return S_OK; } HRESULT CFilterCoder::Alloc() { @@ -34,9 +57,10 @@ HRESULT CFilterCoder::Alloc() size &= ~(UInt32)(kMinSize - 1); if (size < kMinSize) size = kMinSize; + // size = (1 << 12); // + 117; // for debug if (!_buf || _bufSize != size) { - AllocAlignedMask(size, 16 - 1); + AllocAligned(size); if (!_buf) return E_OUTOFMEMORY; _bufSize = size; @@ -46,7 +70,7 @@ HRESULT CFilterCoder::Alloc() HRESULT CFilterCoder::Init_and_Alloc() { - RINOK(Filter->Init()); + RINOK(Filter->Init()) return Alloc(); } @@ -55,78 +79,198 @@ CFilterCoder::CFilterCoder(bool encodeMode): _inBufSize(kBufSize), _outBufSize(kBufSize), _encodeMode(encodeMode), - _outSizeIsDefined(false), + _outSize_Defined(false), _outSize(0), _nowPos64(0) {} -CFilterCoder::~CFilterCoder() -{ -} -STDMETHODIMP CFilterCoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 * /* inSize */, const UInt64 *outSize, ICompressProgressInfo *progress) +Z7_COM7F_IMF(CFilterCoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, + const UInt64 * /* inSize */, const UInt64 *outSize, ICompressProgressInfo *progress)) { - RINOK(Init_and_Alloc()); - + RINOK(Init_and_Alloc()) + + /* + It's expected that BCJ/ARMT filter can process up to 4 bytes less + than sent data size. For such BCJ/ARMT cases with non-filtered data we: + - write some filtered data to output stream + - move non-written data (filtered and non-filtered data) to start of buffer + - read more new data from input stream to position after end of non-filtered data + - call Filter() for concatenated data in buffer. + + For all cases, even for cases with partial filtering (BCJ/ARMT), + we try to keep real/virtual alignment for all operations + (memmove, Read(), Filter(), Write()). + We use (kAlignSize=64) alignmnent that is larger than (16-bytes) + required for AES filter alignment. + + AES-CBC uses 16-bytes blocks, that is simple case for processing here, + if we call Filter() for aligned size for all calls except of last call (last block). + And now there are no filters that use blocks with non-power2 size, + but we try to support such non-power2 filters too here at Code(). + */ + + UInt64 prev = 0; UInt64 nowPos64 = 0; bool inputFinished = false; - UInt32 pos = 0; + UInt32 readPos = 0; + UInt32 filterPos = 0; while (!outSize || nowPos64 < *outSize) { - UInt32 endPos = pos; - + HRESULT hres = S_OK; if (!inputFinished) { - size_t processedSize = _bufSize - pos; - RINOK(ReadStream(inStream, _buf + pos, &processedSize)); - endPos = pos + (UInt32)processedSize; - inputFinished = (endPos != _bufSize); + size_t processedSize = _bufSize - readPos; + /* for AES filters we need at least max(16, kAlignSize) bytes in buffer. + But we try to read full buffer to reduce the number of Filter() and Write() calls. + */ + hres = ReadStream(inStream, _buf + readPos, &processedSize); + readPos += (UInt32)processedSize; + inputFinished = (readPos != _bufSize); + if (hres != S_OK) + { + // do we need to stop encoding after reading error? + // if (_encodeMode) return hres; + inputFinished = true; + } } - pos = Filter->Filter(_buf, endPos); - - if (pos > endPos) + if (readPos == 0) + return hres; + + /* we set (needMoreInput = true), if it's block-filter (like AES-CBC) + that needs more data for current block filtering: + We read full input buffer with Read(), and _bufSize is aligned, + So the possible cases when we set (needMoreInput = true) are: + 1) decode : filter needs more data after the end of input stream. + another cases are possible for non-power2-block-filter, + because buffer size is not aligned for filter_non_power2_block_size: + 2) decode/encode : filter needs more data from non-finished input stream + 3) encode : filter needs more space for zeros after the end of input stream + */ + bool needMoreInput = false; + + while (readPos != filterPos) { - // AES - if (!inputFinished || pos > _bufSize) - return E_FAIL; - if (!_encodeMode) - return S_FALSE; - - do - _buf[endPos] = 0; - while (++endPos != pos); - - if (pos != Filter->Filter(_buf, pos)) - return E_FAIL; + /* Filter() is allowed to process part of data. + Here we use the loop to filter as max as possible. + when we call Filter(data, size): + if (size < 16), AES-CTR filter uses internal 16-byte buffer. + new (since v23.00) AES-CTR filter allows (size < 16) for non-last block, + but it will work less efficiently than calls with aligned (size). + We still support old (before v23.00) AES-CTR filters here. + We have aligned (size) for AES-CTR, if it's not last block. + We have aligned (readPos) for any filter, if (!inputFinished). + We also meet the requirements for (data) pointer in Filter() call: + { + (virtual_stream_offset % aligment_size) == (data_ptr % aligment_size) + (aligment_size == 2^N) + (aligment_size >= 16) + } + */ + const UInt32 cur = Filter->Filter(_buf + filterPos, readPos - filterPos); + if (cur == 0) + break; + const UInt32 f = filterPos + cur; + if (cur > readPos - filterPos) + { + // AES-CBC + if (hres != S_OK) + break; + + if (!_encodeMode + || cur > _bufSize - filterPos + || !inputFinished) + { + /* (cur > _bufSize - filterPos) is unexpected for AES filter, if _bufSize is multiply of 16. + But we support this case, if some future filter will use block with non-power2-size. + */ + needMoreInput = true; + break; + } + + /* (_encodeMode && inputFinished). + We add zero bytes as pad in current block after the end of read data. */ + Byte *buf = _buf; + do + buf[readPos] = 0; + while (++readPos != f); + // (readPos) now is (size_of_real_input_data + size_of_zero_pad) + if (cur != Filter->Filter(buf + filterPos, cur)) + return E_FAIL; + } + filterPos = f; } - if (endPos == 0) - return S_OK; + UInt32 size = filterPos; + if (hres == S_OK) + { + /* If we need more Read() or Filter() calls, then we need to Write() + some data and move unwritten data to get additional space in buffer. + We try to keep alignment for data moves, Read(), Filter() and Write() calls. + */ + const UInt32 kAlignSize = 1 << 6; + const UInt32 alignedFiltered = filterPos & ~(kAlignSize - 1); + if (inputFinished) + { + if (!needMoreInput) + size = readPos; // for risc/bcj filters in last block we write data after filterPos. + else if (_encodeMode) + size = alignedFiltered; // for non-power2-block-encode-filter + } + else + size = alignedFiltered; + } - UInt32 size = (pos != 0 ? pos : endPos); - if (outSize) { - UInt64 remSize = *outSize - nowPos64; - if (size > remSize) - size = (UInt32)remSize; + UInt32 writeSize = size; + if (outSize) + { + const UInt64 rem = *outSize - nowPos64; + if (writeSize > rem) + writeSize = (UInt32)rem; + } + RINOK(WriteStream(outStream, _buf, writeSize)) + nowPos64 += writeSize; } - - RINOK(WriteStream(outStream, _buf, size)); - nowPos64 += size; - if (pos == 0) - return S_OK; + if (hres != S_OK) + return hres; - if (progress) - RINOK(progress->SetRatioInfo(&nowPos64, &nowPos64)); + if (inputFinished) + { + if (readPos == size) + return hres; + if (!_encodeMode) + { + // block-decode-filter (AES-CBS) has non-full last block + // we don't want unaligned data move for more iterations with this error case. + return S_FALSE; + } + } - UInt32 i = 0; - while (pos < endPos) - _buf[i++] = _buf[pos++]; - pos = i; + if (size == 0) + { + // it's unexpected that we have no any move in this iteration. + return E_FAIL; + } + // if (size != 0) + { + if (filterPos < size) + return E_FAIL; // filterPos = 0; else + filterPos -= size; + readPos -= size; + if (readPos != 0) + memmove(_buf, _buf + size, readPos); + } + // printf("\nnowPos64=%x, readPos=%x, filterPos=%x\n", (unsigned)nowPos64, (unsigned)readPos, (unsigned)filterPos); + + if (progress && (nowPos64 - prev) >= (1 << 22)) + { + prev = nowPos64; + RINOK(progress->SetRatioInfo(&nowPos64, &nowPos64)) + } } return S_OK; @@ -136,13 +280,13 @@ STDMETHODIMP CFilterCoder::Code(ISequentialInStream *inStream, ISequentialOutStr // ---------- Write to Filter ---------- -STDMETHODIMP CFilterCoder::SetOutStream(ISequentialOutStream *outStream) +Z7_COM7F_IMF(CFilterCoder::SetOutStream(ISequentialOutStream *outStream)) { _outStream = outStream; return S_OK; } -STDMETHODIMP CFilterCoder::ReleaseOutStream() +Z7_COM7F_IMF(CFilterCoder::ReleaseOutStream()) { _outStream.Release(); return S_OK; @@ -153,9 +297,9 @@ HRESULT CFilterCoder::Flush2() while (_convSize != 0) { UInt32 num = _convSize; - if (_outSizeIsDefined) + if (_outSize_Defined) { - UInt64 rem = _outSize - _nowPos64; + const UInt64 rem = _outSize - _nowPos64; if (num > rem) num = (UInt32)rem; if (num == 0) @@ -163,21 +307,23 @@ HRESULT CFilterCoder::Flush2() } UInt32 processed = 0; - HRESULT res = _outStream->Write(_buf + _convPos, num, &processed); + const HRESULT res = _outStream->Write(_buf + _convPos, num, &processed); if (processed == 0) return res != S_OK ? res : E_FAIL; _convPos += processed; _convSize -= processed; _nowPos64 += processed; - RINOK(res); + RINOK(res) } - if (_convPos != 0) + const UInt32 convPos = _convPos; + if (convPos != 0) { - UInt32 num = _bufPos - _convPos; + const UInt32 num = _bufPos - convPos; + Byte *buf = _buf; for (UInt32 i = 0; i < num; i++) - _buf[i] = _buf[_convPos + i]; + buf[i] = buf[convPos + i]; _bufPos = num; _convPos = 0; } @@ -185,14 +331,14 @@ HRESULT CFilterCoder::Flush2() return S_OK; } -STDMETHODIMP CFilterCoder::Write(const void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CFilterCoder::Write(const void *data, UInt32 size, UInt32 *processedSize)) { if (processedSize) *processedSize = 0; while (size != 0) { - RINOK(Flush2()); + RINOK(Flush2()) // _convSize is 0 // _convPos is 0 @@ -227,20 +373,22 @@ STDMETHODIMP CFilterCoder::Write(const void *data, UInt32 size, UInt32 *processe return S_OK; } -STDMETHODIMP CFilterCoder::OutStreamFinish() +Z7_COM7F_IMF(CFilterCoder::OutStreamFinish()) { for (;;) { - RINOK(Flush2()); + RINOK(Flush2()) if (_bufPos == 0) break; - _convSize = Filter->Filter(_buf, _bufPos); - if (_convSize == 0) - _convSize = _bufPos; - else if (_convSize > _bufPos) + const UInt32 convSize = Filter->Filter(_buf, _bufPos); + _convSize = convSize; + UInt32 bufPos = _bufPos; + if (convSize == 0) + _convSize = bufPos; + else if (convSize > bufPos) { // AES - if (_convSize > _bufSize) + if (convSize > _bufSize) { _convSize = 0; return E_FAIL; @@ -250,9 +398,11 @@ STDMETHODIMP CFilterCoder::OutStreamFinish() _convSize = 0; return S_FALSE; } - for (; _bufPos < _convSize; _bufPos++) - _buf[_bufPos] = 0; - _convSize = Filter->Filter(_buf, _bufPos); + Byte *buf = _buf; + for (; bufPos < convSize; bufPos++) + buf[bufPos] = 0; + _bufPos = bufPos; + _convSize = Filter->Filter(_buf, bufPos); if (_convSize != _bufPos) return E_FAIL; } @@ -267,7 +417,7 @@ STDMETHODIMP CFilterCoder::OutStreamFinish() // ---------- Init functions ---------- -STDMETHODIMP CFilterCoder::InitEncoder() +Z7_COM7F_IMF(CFilterCoder::InitEncoder()) { InitSpecVars(); return Init_and_Alloc(); @@ -279,33 +429,33 @@ HRESULT CFilterCoder::Init_NoSubFilterInit() return Alloc(); } -STDMETHODIMP CFilterCoder::SetOutStreamSize(const UInt64 *outSize) +Z7_COM7F_IMF(CFilterCoder::SetOutStreamSize(const UInt64 *outSize)) { InitSpecVars(); if (outSize) { _outSize = *outSize; - _outSizeIsDefined = true; + _outSize_Defined = true; } return Init_and_Alloc(); } // ---------- Read from Filter ---------- -STDMETHODIMP CFilterCoder::SetInStream(ISequentialInStream *inStream) +Z7_COM7F_IMF(CFilterCoder::SetInStream(ISequentialInStream *inStream)) { _inStream = inStream; return S_OK; } -STDMETHODIMP CFilterCoder::ReleaseInStream() +Z7_COM7F_IMF(CFilterCoder::ReleaseInStream()) { _inStream.Release(); return S_OK; } -STDMETHODIMP CFilterCoder::Read(void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CFilterCoder::Read(void *data, UInt32 size, UInt32 *processedSize)) { if (processedSize) *processedSize = 0; @@ -316,9 +466,9 @@ STDMETHODIMP CFilterCoder::Read(void *data, UInt32 size, UInt32 *processedSize) { if (size > _convSize) size = _convSize; - if (_outSizeIsDefined) + if (_outSize_Defined) { - UInt64 rem = _outSize - _nowPos64; + const UInt64 rem = _outSize - _nowPos64; if (size > rem) size = (UInt32)rem; } @@ -331,46 +481,51 @@ STDMETHODIMP CFilterCoder::Read(void *data, UInt32 size, UInt32 *processedSize) break; } - if (_convPos != 0) + const UInt32 convPos = _convPos; + if (convPos != 0) { - UInt32 num = _bufPos - _convPos; + const UInt32 num = _bufPos - convPos; + Byte *buf = _buf; for (UInt32 i = 0; i < num; i++) - _buf[i] = _buf[_convPos + i]; + buf[i] = buf[convPos + i]; _bufPos = num; _convPos = 0; } { size_t readSize = _bufSize - _bufPos; - HRESULT res = ReadStream(_inStream, _buf + _bufPos, &readSize); + const HRESULT res = ReadStream(_inStream, _buf + _bufPos, &readSize); _bufPos += (UInt32)readSize; - RINOK(res); + RINOK(res) } - _convSize = Filter->Filter(_buf, _bufPos); + const UInt32 convSize = Filter->Filter(_buf, _bufPos); + _convSize = convSize; - if (_convSize == 0) + UInt32 bufPos = _bufPos; + + if (convSize == 0) { - if (_bufPos == 0) + if (bufPos == 0) break; // BCJ - _convSize = _bufPos; + _convSize = bufPos; continue; } - if (_convSize > _bufPos) + if (convSize > bufPos) { // AES - if (_convSize > _bufSize) + if (convSize > _bufSize) return E_FAIL; if (!_encodeMode) return S_FALSE; - + Byte *buf = _buf; do - _buf[_bufPos] = 0; - while (++_bufPos != _convSize); - - _convSize = Filter->Filter(_buf, _convSize); + buf[bufPos] = 0; + while (++bufPos != convSize); + _bufPos = bufPos; + _convSize = Filter->Filter(_buf, convSize); if (_convSize != _bufPos) return E_FAIL; } @@ -380,39 +535,43 @@ STDMETHODIMP CFilterCoder::Read(void *data, UInt32 size, UInt32 *processedSize) } -#ifndef _NO_CRYPTO +#ifndef Z7_NO_CRYPTO -STDMETHODIMP CFilterCoder::CryptoSetPassword(const Byte *data, UInt32 size) - { return _SetPassword->CryptoSetPassword(data, size); } +Z7_COM7F_IMF(CFilterCoder::CryptoSetPassword(const Byte *data, UInt32 size)) + { return _setPassword->CryptoSetPassword(data, size); } -STDMETHODIMP CFilterCoder::SetKey(const Byte *data, UInt32 size) - { return _CryptoProperties->SetKey(data, size); } +Z7_COM7F_IMF(CFilterCoder::SetKey(const Byte *data, UInt32 size)) + { return _cryptoProperties->SetKey(data, size); } -STDMETHODIMP CFilterCoder::SetInitVector(const Byte *data, UInt32 size) - { return _CryptoProperties->SetInitVector(data, size); } +Z7_COM7F_IMF(CFilterCoder::SetInitVector(const Byte *data, UInt32 size)) + { return _cryptoProperties->SetInitVector(data, size); } #endif -#ifndef EXTRACT_ONLY +#ifndef Z7_EXTRACT_ONLY + +Z7_COM7F_IMF(CFilterCoder::SetCoderProperties(const PROPID *propIDs, + const PROPVARIANT *properties, UInt32 numProperties)) + { return _setCoderProperties->SetCoderProperties(propIDs, properties, numProperties); } -STDMETHODIMP CFilterCoder::SetCoderProperties(const PROPID *propIDs, - const PROPVARIANT *properties, UInt32 numProperties) - { return _SetCoderProperties->SetCoderProperties(propIDs, properties, numProperties); } +Z7_COM7F_IMF(CFilterCoder::WriteCoderProperties(ISequentialOutStream *outStream)) + { return _writeCoderProperties->WriteCoderProperties(outStream); } -STDMETHODIMP CFilterCoder::WriteCoderProperties(ISequentialOutStream *outStream) - { return _WriteCoderProperties->WriteCoderProperties(outStream); } +Z7_COM7F_IMF(CFilterCoder::SetCoderPropertiesOpt(const PROPID *propIDs, + const PROPVARIANT *properties, UInt32 numProperties)) + { return _setCoderPropertiesOpt->SetCoderPropertiesOpt(propIDs, properties, numProperties); } /* -STDMETHODIMP CFilterCoder::ResetSalt() - { return _CryptoResetSalt->ResetSalt(); } +Z7_COM7F_IMF(CFilterCoder::ResetSalt() + { return _cryptoResetSalt->ResetSalt(); } */ -STDMETHODIMP CFilterCoder::ResetInitVector() - { return _CryptoResetInitVector->ResetInitVector(); } +Z7_COM7F_IMF(CFilterCoder::ResetInitVector()) + { return _cryptoResetInitVector->ResetInitVector(); } #endif -STDMETHODIMP CFilterCoder::SetDecoderProperties2(const Byte *data, UInt32 size) - { return _SetDecoderProperties2->SetDecoderProperties2(data, size); } +Z7_COM7F_IMF(CFilterCoder::SetDecoderProperties2(const Byte *data, UInt32 size)) + { return _setDecoderProperties2->SetDecoderProperties2(data, size); } diff --git a/src/plugins/7zip/7za/cpp/7zip/Common/FilterCoder.h b/src/plugins/7zip/7za/cpp/7zip/Common/FilterCoder.h index 224ff2ca..3a588fd5 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Common/FilterCoder.h +++ b/src/plugins/7zip/7za/cpp/7zip/Common/FilterCoder.h @@ -1,62 +1,33 @@ // FilterCoder.h -#ifndef __FILTER_CODER_H -#define __FILTER_CODER_H +#ifndef ZIP7_INC_FILTER_CODER_H +#define ZIP7_INC_FILTER_CODER_H #include "../../../C/Alloc.h" #include "../../Common/MyCom.h" #include "../ICoder.h" -#ifndef _NO_CRYPTO +#ifndef Z7_NO_CRYPTO #include "../IPassword.h" #endif -#define MY_QUERYINTERFACE_ENTRY_AG(i, sub0, sub) else if (iid == IID_ ## i) \ +#define Z7_COM_QI_ENTRY_AG(i, sub0, sub) else if (iid == IID_ ## i) \ { if (!sub) RINOK(sub0->QueryInterface(IID_ ## i, (void **)&sub)) \ *outObject = (void *)(i *)this; } struct CAlignedMidBuffer { - #ifdef _WIN32 - Byte *_buf; CAlignedMidBuffer(): _buf(NULL) {} - ~CAlignedMidBuffer() { ::MidFree(_buf); } - - void AllocAlignedMask(size_t size, size_t) - { - ::MidFree(_buf); - _buf = (Byte *)::MidAlloc(size); - } - - #else - - Byte *_bufBase; - Byte *_buf; - - CAlignedMidBuffer(): _bufBase(NULL), _buf(NULL) {} - ~CAlignedMidBuffer() { ::MidFree(_bufBase); } - - void AllocAlignedMask(size_t size, size_t alignMask) - { - ::MidFree(_bufBase); - _buf = NULL; - _bufBase = (Byte *)::MidAlloc(size + alignMask); - - if (_bufBase) - { - // _buf = (Byte *)(((uintptr_t)_bufBase + alignMask) & ~(uintptr_t)alignMask); - _buf = (Byte *)(((ptrdiff_t)_bufBase + alignMask) & ~(ptrdiff_t)alignMask); - } - } - - #endif + ~CAlignedMidBuffer(); + void AllocAligned(size_t size); }; -class CFilterCoder: + +class CFilterCoder Z7_final : public ICompressCoder, public ICompressSetOutStreamSize, @@ -71,14 +42,15 @@ class CFilterCoder: public ICompressSetBufSize, - #ifndef _NO_CRYPTO + #ifndef Z7_NO_CRYPTO public ICryptoSetPassword, public ICryptoProperties, #endif - #ifndef EXTRACT_ONLY + #ifndef Z7_EXTRACT_ONLY public ICompressSetCoderProperties, public ICompressWriteCoderProperties, + public ICompressSetCoderPropertiesOpt, // public ICryptoResetSalt, public ICryptoResetInitVector, #endif @@ -92,7 +64,7 @@ class CFilterCoder: UInt32 _outBufSize; bool _encodeMode; - bool _outSizeIsDefined; + bool _outSize_Defined; UInt64 _outSize; UInt64 _nowPos64; @@ -108,7 +80,7 @@ class CFilterCoder: _convPos = 0; _convSize = 0; - _outSizeIsDefined = false; + _outSize_Defined = false; _outSize = 0; _nowPos64 = 0; } @@ -117,108 +89,111 @@ class CFilterCoder: HRESULT Init_and_Alloc(); HRESULT Flush2(); - #ifndef _NO_CRYPTO - CMyComPtr _SetPassword; - CMyComPtr _CryptoProperties; + #ifndef Z7_NO_CRYPTO + CMyComPtr _setPassword; + CMyComPtr _cryptoProperties; #endif - #ifndef EXTRACT_ONLY - CMyComPtr _SetCoderProperties; - CMyComPtr _WriteCoderProperties; - // CMyComPtr _CryptoResetSalt; - CMyComPtr _CryptoResetInitVector; + #ifndef Z7_EXTRACT_ONLY + CMyComPtr _setCoderProperties; + CMyComPtr _writeCoderProperties; + CMyComPtr _setCoderPropertiesOpt; + // CMyComPtr _cryptoResetSalt; + CMyComPtr _cryptoResetInitVector; #endif - CMyComPtr _SetDecoderProperties2; + CMyComPtr _setDecoderProperties2; public: CMyComPtr Filter; CFilterCoder(bool encodeMode); - ~CFilterCoder(); - class C_InStream_Releaser + struct C_InStream_Releaser { - public: CFilterCoder *FilterCoder; C_InStream_Releaser(): FilterCoder(NULL) {} ~C_InStream_Releaser() { if (FilterCoder) FilterCoder->ReleaseInStream(); } }; - class C_OutStream_Releaser + struct C_OutStream_Releaser { - public: CFilterCoder *FilterCoder; C_OutStream_Releaser(): FilterCoder(NULL) {} ~C_OutStream_Releaser() { if (FilterCoder) FilterCoder->ReleaseOutStream(); } }; - MY_QUERYINTERFACE_BEGIN2(ICompressCoder) + struct C_Filter_Releaser + { + CFilterCoder *FilterCoder; + C_Filter_Releaser(): FilterCoder(NULL) {} + ~C_Filter_Releaser() { if (FilterCoder) FilterCoder->Filter.Release(); } + }; + +private: + Z7_COM_QI_BEGIN2(ICompressCoder) - MY_QUERYINTERFACE_ENTRY(ICompressSetOutStreamSize) - MY_QUERYINTERFACE_ENTRY(ICompressInitEncoder) + Z7_COM_QI_ENTRY(ICompressSetOutStreamSize) + Z7_COM_QI_ENTRY(ICompressInitEncoder) - MY_QUERYINTERFACE_ENTRY(ICompressSetInStream) - MY_QUERYINTERFACE_ENTRY(ISequentialInStream) + Z7_COM_QI_ENTRY(ICompressSetInStream) + Z7_COM_QI_ENTRY(ISequentialInStream) - MY_QUERYINTERFACE_ENTRY(ICompressSetOutStream) - MY_QUERYINTERFACE_ENTRY(ISequentialOutStream) - MY_QUERYINTERFACE_ENTRY(IOutStreamFinish) + Z7_COM_QI_ENTRY(ICompressSetOutStream) + Z7_COM_QI_ENTRY(ISequentialOutStream) + Z7_COM_QI_ENTRY(IOutStreamFinish) - MY_QUERYINTERFACE_ENTRY(ICompressSetBufSize) + Z7_COM_QI_ENTRY(ICompressSetBufSize) - #ifndef _NO_CRYPTO - MY_QUERYINTERFACE_ENTRY_AG(ICryptoSetPassword, Filter, _SetPassword) - MY_QUERYINTERFACE_ENTRY_AG(ICryptoProperties, Filter, _CryptoProperties) + #ifndef Z7_NO_CRYPTO + Z7_COM_QI_ENTRY_AG(ICryptoSetPassword, Filter, _setPassword) + Z7_COM_QI_ENTRY_AG(ICryptoProperties, Filter, _cryptoProperties) #endif - #ifndef EXTRACT_ONLY - MY_QUERYINTERFACE_ENTRY_AG(ICompressSetCoderProperties, Filter, _SetCoderProperties) - MY_QUERYINTERFACE_ENTRY_AG(ICompressWriteCoderProperties, Filter, _WriteCoderProperties) - // MY_QUERYINTERFACE_ENTRY_AG(ICryptoResetSalt, Filter, _CryptoResetSalt) - MY_QUERYINTERFACE_ENTRY_AG(ICryptoResetInitVector, Filter, _CryptoResetInitVector) + #ifndef Z7_EXTRACT_ONLY + Z7_COM_QI_ENTRY_AG(ICompressSetCoderProperties, Filter, _setCoderProperties) + Z7_COM_QI_ENTRY_AG(ICompressWriteCoderProperties, Filter, _writeCoderProperties) + Z7_COM_QI_ENTRY_AG(ICompressSetCoderPropertiesOpt, Filter, _setCoderPropertiesOpt) + // Z7_COM_QI_ENTRY_AG(ICryptoResetSalt, Filter, _cryptoResetSalt) + Z7_COM_QI_ENTRY_AG(ICryptoResetInitVector, Filter, _cryptoResetInitVector) #endif - MY_QUERYINTERFACE_ENTRY_AG(ICompressSetDecoderProperties2, Filter, _SetDecoderProperties2) - MY_QUERYINTERFACE_END - MY_ADDREF_RELEASE + Z7_COM_QI_ENTRY_AG(ICompressSetDecoderProperties2, Filter, _setDecoderProperties2) + Z7_COM_QI_END + Z7_COM_ADDREF_RELEASE +public: + Z7_IFACE_COM7_IMP(ICompressCoder) + Z7_IFACE_COM7_IMP(ICompressSetOutStreamSize) + Z7_IFACE_COM7_IMP(ICompressInitEncoder) + Z7_IFACE_COM7_IMP(ICompressSetInStream) +private: + Z7_IFACE_COM7_IMP(ISequentialInStream) +public: + Z7_IFACE_COM7_IMP(ICompressSetOutStream) +private: + Z7_IFACE_COM7_IMP(ISequentialOutStream) +public: + Z7_IFACE_COM7_IMP(IOutStreamFinish) +private: - STDMETHOD(Code)(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress); - - STDMETHOD(SetOutStreamSize)(const UInt64 *outSize); - STDMETHOD(InitEncoder)(); - - STDMETHOD(SetInStream)(ISequentialInStream *inStream); - STDMETHOD(ReleaseInStream)(); - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); - - STDMETHOD(SetOutStream)(ISequentialOutStream *outStream); - STDMETHOD(ReleaseOutStream)(); - STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize); - STDMETHOD(OutStreamFinish)(); - - STDMETHOD(SetInBufSize)(UInt32 streamIndex, UInt32 size); - STDMETHOD(SetOutBufSize)(UInt32 streamIndex, UInt32 size); + Z7_IFACE_COM7_IMP(ICompressSetBufSize) - #ifndef _NO_CRYPTO - STDMETHOD(CryptoSetPassword)(const Byte *data, UInt32 size); - - STDMETHOD(SetKey)(const Byte *data, UInt32 size); - STDMETHOD(SetInitVector)(const Byte *data, UInt32 size); + #ifndef Z7_NO_CRYPTO + Z7_IFACE_COM7_IMP(ICryptoSetPassword) + Z7_IFACE_COM7_IMP(ICryptoProperties) #endif - #ifndef EXTRACT_ONLY - STDMETHOD(SetCoderProperties)(const PROPID *propIDs, - const PROPVARIANT *properties, UInt32 numProperties); - STDMETHOD(WriteCoderProperties)(ISequentialOutStream *outStream); - // STDMETHOD(ResetSalt)(); - STDMETHOD(ResetInitVector)(); + #ifndef Z7_EXTRACT_ONLY + Z7_IFACE_COM7_IMP(ICompressSetCoderProperties) + Z7_IFACE_COM7_IMP(ICompressWriteCoderProperties) + Z7_IFACE_COM7_IMP(ICompressSetCoderPropertiesOpt) + // Z7_IFACE_COM7_IMP(ICryptoResetSalt) + Z7_IFACE_COM7_IMP(ICryptoResetInitVector) #endif - STDMETHOD(SetDecoderProperties2)(const Byte *data, UInt32 size); - +public: + Z7_IFACE_COM7_IMP(ICompressSetDecoderProperties2) HRESULT Init_NoSubFilterInit(); }; diff --git a/src/plugins/7zip/7za/cpp/7zip/Common/InBuffer.cpp b/src/plugins/7zip/7za/cpp/7zip/Common/InBuffer.cpp index 133d95b3..ae004fbd 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Common/InBuffer.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Common/InBuffer.cpp @@ -7,10 +7,10 @@ #include "InBuffer.h" CInBufferBase::CInBufferBase() throw(): - _buf(0), - _bufLim(0), - _bufBase(0), - _stream(0), + _buf(NULL), + _bufLim(NULL), + _bufBase(NULL), + _stream(NULL), _processedSize(0), _bufSize(0), _wasFinished(false), @@ -22,18 +22,18 @@ bool CInBuffer::Create(size_t bufSize) throw() const unsigned kMinBlockSize = 1; if (bufSize < kMinBlockSize) bufSize = kMinBlockSize; - if (_bufBase != 0 && _bufSize == bufSize) + if (_bufBase != NULL && _bufSize == bufSize) return true; Free(); _bufSize = bufSize; _bufBase = (Byte *)::MidAlloc(bufSize); - return (_bufBase != 0); + return (_bufBase != NULL); } void CInBuffer::Free() throw() { ::MidFree(_bufBase); - _bufBase = 0; + _bufBase = NULL; } void CInBufferBase::Init() throw() @@ -42,7 +42,7 @@ void CInBufferBase::Init() throw() _buf = _bufBase; _bufLim = _buf; _wasFinished = false; - #ifdef _NO_EXCEPTIONS + #ifdef Z7_NO_EXCEPTIONS ErrorCode = S_OK; #endif NumExtraBytes = 0; @@ -50,19 +50,19 @@ void CInBufferBase::Init() throw() bool CInBufferBase::ReadBlock() { - #ifdef _NO_EXCEPTIONS + #ifdef Z7_NO_EXCEPTIONS if (ErrorCode != S_OK) return false; #endif if (_wasFinished) return false; - _processedSize += (_buf - _bufBase); + _processedSize += (size_t)(_buf - _bufBase); _buf = _bufBase; _bufLim = _bufBase; UInt32 processed; // FIX_ME: we can improve it to support (_bufSize >= (1 << 32)) - HRESULT result = _stream->Read(_bufBase, (UInt32)_bufSize, &processed); - #ifdef _NO_EXCEPTIONS + const HRESULT result = _stream->Read(_bufBase, (UInt32)_bufSize, &processed); + #ifdef Z7_NO_EXCEPTIONS ErrorCode = result; #else if (result != S_OK) @@ -77,7 +77,8 @@ bool CInBufferBase::ReadByte_FromNewBlock(Byte &b) { if (!ReadBlock()) { - NumExtraBytes++; + // 22.00: we don't increment (NumExtraBytes) here + // NumExtraBytes++; b = 0xFF; return false; } @@ -95,8 +96,53 @@ Byte CInBufferBase::ReadByte_FromNewBlock() return *_buf++; } +size_t CInBufferBase::ReadBytesPart(Byte *buf, size_t size) +{ + if (size == 0) + return 0; + size_t rem = (size_t)(_bufLim - _buf); + if (rem == 0) + { + if (!ReadBlock()) + return 0; + rem = (size_t)(_bufLim - _buf); + } + if (size > rem) + size = rem; + memcpy(buf, _buf, size); + _buf += size; + return size; +} + size_t CInBufferBase::ReadBytes(Byte *buf, size_t size) { + size_t num = 0; + for (;;) + { + const size_t rem = (size_t)(_bufLim - _buf); + if (size <= rem) + { + if (size != 0) + { + memcpy(buf, _buf, size); + _buf += size; + num += size; + } + return num; + } + if (rem != 0) + { + memcpy(buf, _buf, rem); + _buf += rem; + buf += rem; + num += rem; + size -= rem; + } + if (!ReadBlock()) + return num; + } + + /* if ((size_t)(_bufLim - _buf) >= size) { const Byte *src = _buf; @@ -113,6 +159,7 @@ size_t CInBufferBase::ReadBytes(Byte *buf, size_t size) buf[i] = *_buf++; } return size; + */ } size_t CInBufferBase::Skip(size_t size) @@ -120,7 +167,7 @@ size_t CInBufferBase::Skip(size_t size) size_t processed = 0; for (;;) { - size_t rem = (_bufLim - _buf); + const size_t rem = (size_t)(_bufLim - _buf); if (rem >= size) { _buf += size; diff --git a/src/plugins/7zip/7za/cpp/7zip/Common/InBuffer.h b/src/plugins/7zip/7za/cpp/7zip/Common/InBuffer.h index 4b8662bb..13ec088f 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Common/InBuffer.h +++ b/src/plugins/7zip/7za/cpp/7zip/Common/InBuffer.h @@ -1,12 +1,12 @@ // InBuffer.h -#ifndef __IN_BUFFER_H -#define __IN_BUFFER_H +#ifndef ZIP7_INC_IN_BUFFER_H +#define ZIP7_INC_IN_BUFFER_H #include "../../Common/MyException.h" #include "../IStream.h" -#ifndef _NO_EXCEPTIONS +#ifndef Z7_NO_EXCEPTIONS struct CInBufferException: public CSystemException { CInBufferException(HRESULT errorCode): CSystemException(errorCode) {} @@ -31,18 +31,27 @@ class CInBufferBase Byte ReadByte_FromNewBlock(); public: - #ifdef _NO_EXCEPTIONS + #ifdef Z7_NO_EXCEPTIONS HRESULT ErrorCode; #endif UInt32 NumExtraBytes; CInBufferBase() throw(); - UInt64 GetStreamSize() const { return _processedSize + (_buf - _bufBase); } - UInt64 GetProcessedSize() const { return _processedSize + NumExtraBytes + (_buf - _bufBase); } + // the size of portion of data in real stream that was already read from this object + // it doesn't include unused data in buffer + // it doesn't include virtual Extra bytes after the end of real stream data + UInt64 GetStreamSize() const { return _processedSize + (size_t)(_buf - _bufBase); } + + // the size of virtual data that was read from this object + // it doesn't include unused data in buffers + // it includes any virtual Extra bytes after the end of real data + UInt64 GetProcessedSize() const { return _processedSize + NumExtraBytes + (size_t)(_buf - _bufBase); } + bool WasFinished() const { return _wasFinished; } void SetStream(ISequentialInStream *stream) { _stream = stream; } + void ClearStreamPtr() { _stream = NULL; } void SetBuf(Byte *buf, size_t bufSize, size_t end, size_t pos) { @@ -52,14 +61,15 @@ class CInBufferBase _buf = buf + pos; _bufLim = buf + end; _wasFinished = false; - #ifdef _NO_EXCEPTIONS + #ifdef Z7_NO_EXCEPTIONS ErrorCode = S_OK; #endif NumExtraBytes = 0; } void Init() throw(); - + + Z7_FORCE_INLINE bool ReadByte(Byte &b) { if (_buf >= _bufLim) @@ -67,7 +77,17 @@ class CInBufferBase b = *_buf++; return true; } + + Z7_FORCE_INLINE + bool ReadByte_FromBuf(Byte &b) + { + if (_buf >= _bufLim) + return false; + b = *_buf++; + return true; + } + Z7_FORCE_INLINE Byte ReadByte() { if (_buf >= _bufLim) @@ -75,7 +95,18 @@ class CInBufferBase return *_buf++; } + size_t ReadBytesPart(Byte *buf, size_t size); size_t ReadBytes(Byte *buf, size_t size); + const Byte *Lookahead(size_t &rem) + { + rem = (size_t)(_bufLim - _buf); + if (!rem) + { + ReadBlock(); + rem = (size_t)(_bufLim - _buf); + } + return _buf; + } size_t Skip(size_t size); }; diff --git a/src/plugins/7zip/7za/cpp/7zip/Common/InOutTempBuffer.cpp b/src/plugins/7zip/7za/cpp/7zip/Common/InOutTempBuffer.cpp index 85d6a1aa..3f7272ef 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Common/InOutTempBuffer.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Common/InOutTempBuffer.cpp @@ -2,126 +2,236 @@ #include "StdAfx.h" -#include "../../../C/7zCrc.h" - -#include "../../Common/Defs.h" +#include "../../../C/Alloc.h" #include "InOutTempBuffer.h" + #include "StreamUtils.h" -using namespace NWindows; -using namespace NFile; -using namespace NDir; +#ifdef USE_InOutTempBuffer_FILE + +#include "../../../C/7zCrc.h" + +#define kTempFilePrefixString FTEXT("7zt") +/* + Total buffer size limit, if we use temp file scheme: + 32-bit: 16 MiB = 1 MiB * 16 buffers + 64-bit: 4 GiB = 1 MiB * 4096 buffers +*/ +static const size_t kNumBufsMax = (size_t)1 << (sizeof(size_t) * 2 - 4); -static const size_t kTempBufSize = (1 << 20); +#endif -static CFSTR kTempFilePrefixString = FTEXT("7zt"); +static const size_t kBufSize = (size_t)1 << 20; -CInOutTempBuffer::CInOutTempBuffer(): _buf(NULL) { } -void CInOutTempBuffer::Create() +CInOutTempBuffer::CInOutTempBuffer(): + _size(0), + _bufs(NULL), + _numBufs(0), + _numFilled(0) { - if (!_buf) - _buf = new Byte[kTempBufSize]; + #ifdef USE_InOutTempBuffer_FILE + _tempFile_Created = false; + _useMemOnly = false; + _crc = CRC_INIT_VAL; + #endif } CInOutTempBuffer::~CInOutTempBuffer() { - delete []_buf; + for (size_t i = 0; i < _numBufs; i++) + MyFree(_bufs[i]); + MyFree(_bufs); } -void CInOutTempBuffer::InitWriting() -{ - _bufPos = 0; - _tempFileCreated = false; - _size = 0; - _crc = CRC_INIT_VAL; -} -bool CInOutTempBuffer::WriteToFile(const void *data, UInt32 size) +void *CInOutTempBuffer::GetBuf(size_t index) { - if (size == 0) - return true; - if (!_tempFileCreated) + if (index >= _numBufs) + { + const size_t num = (_numBufs == 0 ? 16 : _numBufs * 2); + void **p = (void **)MyRealloc(_bufs, num * sizeof(void *)); + if (!p) + return NULL; + _bufs = p; + memset(p + _numBufs, 0, (num - _numBufs) * sizeof(void *)); + _numBufs = num; + } + + void *buf = _bufs[index]; + if (!buf) { - if (!_tempFile.CreateRandomInTempFolder(kTempFilePrefixString, &_outFile)) - return false; - _tempFileCreated = true; + buf = MyAlloc(kBufSize); + if (buf) + _bufs[index] = buf; } - UInt32 processed; - if (!_outFile.Write(data, size, processed)) - return false; - _crc = CrcUpdate(_crc, data, processed); - _size += processed; - return (processed == size); + return buf; } -bool CInOutTempBuffer::Write(const void *data, UInt32 size) + +HRESULT CInOutTempBuffer::Write_HRESULT(const void *data, UInt32 size) { if (size == 0) - return true; - size_t cur = kTempBufSize - _bufPos; - if (cur != 0) + return S_OK; + + #ifdef USE_InOutTempBuffer_FILE + if (!_tempFile_Created) + #endif + for (;;) // loop for additional attemp to allocate memory after file creation error { - if (cur > size) - cur = size; - memcpy(_buf + _bufPos, data, cur); - _crc = CrcUpdate(_crc, data, cur); - _bufPos += cur; - _size += cur; - size -= (UInt32)cur; - data = ((const Byte *)data) + cur; + #ifdef USE_InOutTempBuffer_FILE + bool allocError = false; + #endif + + for (;;) // loop for writing to buffers + { + const size_t index = (size_t)(_size / kBufSize); + + #ifdef USE_InOutTempBuffer_FILE + if (index >= kNumBufsMax && !_useMemOnly) + break; + #endif + + void *buf = GetBuf(index); + if (!buf) + { + #ifdef USE_InOutTempBuffer_FILE + if (!_useMemOnly) + { + allocError = true; + break; + } + #endif + return E_OUTOFMEMORY; + } + + const size_t offset = (size_t)(_size) & (kBufSize - 1); + size_t cur = kBufSize - offset; + if (cur > size) + cur = size; + memcpy((Byte *)buf + offset, data, cur); + _size += cur; + if (index >= _numFilled) + _numFilled = index + 1; + data = (const void *)((const Byte *)data + cur); + size -= (UInt32)cur; + if (size == 0) + return S_OK; + } + + #ifdef USE_InOutTempBuffer_FILE + #ifndef _WIN32 + _outFile.mode_for_Create = 0600; // only owner will have the rights to access this file + #endif + if (_tempFile.CreateRandomInTempFolder(kTempFilePrefixString, &_outFile)) + { + _tempFile_Created = true; + break; + } + _useMemOnly = true; + if (allocError) + return GetLastError_noZero_HRESULT(); + #endif } - return WriteToFile(data, size); + + #ifdef USE_InOutTempBuffer_FILE + if (!_outFile.WriteFull(data, size)) + return GetLastError_noZero_HRESULT(); + _crc = CrcUpdate(_crc, data, size); + _size += size; + return S_OK; + #endif } + HRESULT CInOutTempBuffer::WriteToStream(ISequentialOutStream *stream) { - if (!_outFile.Close()) - return E_FAIL; + UInt64 rem = _size; + // if (rem == 0) return S_OK; - UInt64 size = 0; - UInt32 crc = CRC_INIT_VAL; + const size_t numFilled = _numFilled; + _numFilled = 0; - if (_bufPos != 0) + for (size_t i = 0; i < numFilled; i++) { - RINOK(WriteStream(stream, _buf, _bufPos)); - crc = CrcUpdate(crc, _buf, _bufPos); - size += _bufPos; - } - - if (_tempFileCreated) - { - NIO::CInFile inFile; - if (!inFile.Open(_tempFile.GetPath())) + if (rem == 0) return E_FAIL; - while (size < _size) + size_t cur = kBufSize; + if (cur > rem) + cur = (size_t)rem; + RINOK(WriteStream(stream, _bufs[i], cur)) + rem -= cur; + #ifdef USE_InOutTempBuffer_FILE + // we will use _bufs[0] later for writing from temp file + if (i != 0 || !_tempFile_Created) + #endif { - UInt32 processed; - if (!inFile.ReadPart(_buf, kTempBufSize, processed)) - return E_FAIL; - if (processed == 0) - break; - RINOK(WriteStream(stream, _buf, processed)); - crc = CrcUpdate(crc, _buf, processed); - size += processed; + MyFree(_bufs[i]); + _bufs[i] = NULL; } } - - return (_crc == crc && size == _size) ? S_OK : E_FAIL; -} -/* -STDMETHODIMP CSequentialOutTempBufferImp::Write(const void *data, UInt32 size, UInt32 *processed) -{ - if (!_buf->Write(data, size)) - { - if (processed) - *processed = 0; + + #ifdef USE_InOutTempBuffer_FILE + + if (rem == 0) + return _tempFile_Created ? E_FAIL : S_OK; + + if (!_tempFile_Created) return E_FAIL; + + if (!_outFile.Close()) + return GetLastError_noZero_HRESULT(); + + HRESULT hres; + void *buf = GetBuf(0); // index + if (!buf) + hres = E_OUTOFMEMORY; + else + { + NWindows::NFile::NIO::CInFile inFile; + if (!inFile.Open(_tempFile.GetPath())) + hres = GetLastError_noZero_HRESULT(); + else + { + UInt32 crc = CRC_INIT_VAL; + for (;;) + { + size_t processed; + if (!inFile.ReadFull(buf, kBufSize, processed)) + { + hres = GetLastError_noZero_HRESULT(); + break; + } + if (processed == 0) + { + // we compare crc without CRC_GET_DIGEST + hres = (_crc == crc ? S_OK : E_FAIL); + break; + } + size_t n = processed; + if (n > rem) + n = (size_t)rem; + hres = WriteStream(stream, buf, n); + if (hres != S_OK) + break; + crc = CrcUpdate(crc, buf, n); + rem -= n; + if (n != processed) + { + hres = E_FAIL; + break; + } + } + } } - if (processed) - *processed = size; - return S_OK; + + // _tempFile.DisableDeleting(); // for debug + _tempFile.Remove(); + RINOK(hres) + + #endif + + return rem == 0 ? S_OK : E_FAIL; } -*/ diff --git a/src/plugins/7zip/7za/cpp/7zip/Common/InOutTempBuffer.h b/src/plugins/7zip/7za/cpp/7zip/Common/InOutTempBuffer.h index 204a105f..345c3863 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Common/InOutTempBuffer.h +++ b/src/plugins/7zip/7za/cpp/7zip/Common/InOutTempBuffer.h @@ -1,48 +1,45 @@ // InOutTempBuffer.h -#ifndef __IN_OUT_TEMP_BUFFER_H -#define __IN_OUT_TEMP_BUFFER_H +#ifndef ZIP7_INC_IN_OUT_TEMP_BUFFER_H +#define ZIP7_INC_IN_OUT_TEMP_BUFFER_H -#include "../../Common/MyCom.h" +// #ifdef _WIN32 +#define USE_InOutTempBuffer_FILE +// #endif + +#ifdef USE_InOutTempBuffer_FILE #include "../../Windows/FileDir.h" +#endif #include "../IStream.h" class CInOutTempBuffer { - NWindows::NFile::NDir::CTempFile _tempFile; - NWindows::NFile::NIO::COutFile _outFile; - Byte *_buf; - size_t _bufPos; UInt64 _size; + void **_bufs; + size_t _numBufs; + size_t _numFilled; + + #ifdef USE_InOutTempBuffer_FILE + + bool _tempFile_Created; + bool _useMemOnly; UInt32 _crc; - bool _tempFileCreated; + // COutFile object must be declared after CTempFile object for correct destructor order + NWindows::NFile::NDir::CTempFile _tempFile; + NWindows::NFile::NIO::COutFile _outFile; + + #endif + + void *GetBuf(size_t index); - bool WriteToFile(const void *data, UInt32 size); + Z7_CLASS_NO_COPY(CInOutTempBuffer) public: CInOutTempBuffer(); ~CInOutTempBuffer(); - void Create(); - - void InitWriting(); - bool Write(const void *data, UInt32 size); - + HRESULT Write_HRESULT(const void *data, UInt32 size); HRESULT WriteToStream(ISequentialOutStream *stream); UInt64 GetDataSize() const { return _size; } }; -/* -class CSequentialOutTempBufferImp: - public ISequentialOutStream, - public CMyUnknownImp -{ - CInOutTempBuffer *_buf; -public: - void Init(CInOutTempBuffer *buffer) { _buf = buffer; } - MY_UNKNOWN_IMP - - STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize); -}; -*/ - #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Common/LimitedStreams.cpp b/src/plugins/7zip/7za/cpp/7zip/Common/LimitedStreams.cpp index 8e032561..664cd0c4 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Common/LimitedStreams.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Common/LimitedStreams.cpp @@ -6,7 +6,7 @@ #include "LimitedStreams.h" -STDMETHODIMP CLimitedSequentialInStream::Read(void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CLimitedSequentialInStream::Read(void *data, UInt32 size, UInt32 *processedSize)) { UInt32 realProcessedSize = 0; { @@ -27,7 +27,7 @@ STDMETHODIMP CLimitedSequentialInStream::Read(void *data, UInt32 size, UInt32 *p return result; } -STDMETHODIMP CLimitedInStream::Read(void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CLimitedInStream::Read(void *data, UInt32 size, UInt32 *processedSize)) { if (processedSize) *processedSize = 0; @@ -46,7 +46,7 @@ STDMETHODIMP CLimitedInStream::Read(void *data, UInt32 size, UInt32 *processedSi if (newPos != _physPos) { _physPos = newPos; - RINOK(SeekToPhys()); + RINOK(SeekToPhys()) } HRESULT res = _stream->Read(data, size, &size); if (processedSize) @@ -56,7 +56,7 @@ STDMETHODIMP CLimitedInStream::Read(void *data, UInt32 size, UInt32 *processedSi return res; } -STDMETHODIMP CLimitedInStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition) +Z7_COM7F_IMF(CLimitedInStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition)) { switch (seekOrigin) { @@ -67,7 +67,7 @@ STDMETHODIMP CLimitedInStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *new } if (offset < 0) return HRESULT_WIN32_ERROR_NEGATIVE_SEEK; - _virtPos = offset; + _virtPos = (UInt64)offset; if (newPosition) *newPosition = _virtPos; return S_OK; @@ -75,17 +75,17 @@ STDMETHODIMP CLimitedInStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *new HRESULT CreateLimitedInStream(IInStream *inStream, UInt64 pos, UInt64 size, ISequentialInStream **resStream) { - *resStream = 0; + *resStream = NULL; CLimitedInStream *streamSpec = new CLimitedInStream; CMyComPtr streamTemp = streamSpec; streamSpec->SetStream(inStream); - RINOK(streamSpec->InitAndSeek(pos, size)); + RINOK(streamSpec->InitAndSeek(pos, size)) streamSpec->SeekToStart(); *resStream = streamTemp.Detach(); return S_OK; } -STDMETHODIMP CClusterInStream::Read(void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CClusterInStream::Read(void *data, UInt32 size, UInt32 *processedSize)) { if (processedSize) *processedSize = 0; @@ -110,12 +110,12 @@ STDMETHODIMP CClusterInStream::Read(void *data, UInt32 size, UInt32 *processedSi if (newPos != _physPos) { _physPos = newPos; - RINOK(SeekToPhys()); + RINOK(SeekToPhys()) } _curRem = blockSize - offsetInBlock; - for (int i = 1; i < 64 && (virtBlock + i) < (UInt32)Vector.Size() && phyBlock + i == Vector[virtBlock + i]; i++) + for (unsigned i = 1; i < 64 && (virtBlock + i) < (UInt32)Vector.Size() && phyBlock + i == Vector[virtBlock + i]; i++) _curRem += (UInt32)1 << BlockSizeLog; } @@ -130,7 +130,7 @@ STDMETHODIMP CClusterInStream::Read(void *data, UInt32 size, UInt32 *processedSi return res; } -STDMETHODIMP CClusterInStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition) +Z7_COM7F_IMF(CClusterInStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition)) { switch (seekOrigin) { @@ -143,56 +143,82 @@ STDMETHODIMP CClusterInStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *new return HRESULT_WIN32_ERROR_NEGATIVE_SEEK; if (_virtPos != (UInt64)offset) _curRem = 0; - _virtPos = offset; + _virtPos = (UInt64)offset; if (newPosition) - *newPosition = offset; + *newPosition = (UInt64)offset; return S_OK; } -STDMETHODIMP CExtentsStream::Read(void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CExtentsStream::Read(void *data, UInt32 size, UInt32 *processedSize)) { if (processedSize) *processedSize = 0; - if (_virtPos >= Extents.Back().Virt) + const UInt64 virt = _virtPos; + if (virt >= Extents.Back().Virt) return S_OK; if (size == 0) return S_OK; - unsigned left = 0, right = Extents.Size() - 1; - for (;;) + unsigned left = _prevExtentIndex; + if (virt < Extents[left].Virt || + virt >= Extents[left + 1].Virt) { - unsigned mid = (left + right) / 2; - if (mid == left) - break; - if (_virtPos < Extents[mid].Virt) - right = mid; - else - left = mid; + left = 0; + unsigned right = Extents.Size() - 1; + for (;;) + { + const unsigned mid = (unsigned)(((size_t)left + (size_t)right) / 2); + if (mid == left) + break; + if (virt < Extents[mid].Virt) + right = mid; + else + left = mid; + } + _prevExtentIndex = left; } - const CSeekExtent &extent = Extents[left]; - UInt64 phyPos = extent.Phy + (_virtPos - extent.Virt); - if (_needStartSeek || _phyPos != phyPos) { - _needStartSeek = false; - _phyPos = phyPos; - RINOK(SeekToPhys()); + const UInt64 rem = Extents[left + 1].Virt - virt; + if (size > rem) + size = (UInt32)rem; } - UInt64 rem = Extents[left + 1].Virt - _virtPos; - if (size > rem) - size = (UInt32)rem; + const CSeekExtent &extent = Extents[left]; - HRESULT res = Stream->Read(data, size, &size); - _phyPos += size; + if (extent.Is_ZeroFill()) + { + memset(data, 0, size); + _virtPos += size; + if (processedSize) + *processedSize = size; + return S_OK; + } + + { + const UInt64 phy = extent.Phy + (virt - extent.Virt); + if (_phyPos != phy) + { + _phyPos = (UInt64)0 - 1; // we don't trust seek_pos in case of error + RINOK(InStream_SeekSet(Stream, phy)) + _phyPos = phy; + } + } + + const HRESULT res = Stream->Read(data, size, &size); _virtPos += size; + if (res == S_OK) + _phyPos += size; + else + _phyPos = (UInt64)0 - 1; // we don't trust seek_pos in case of error if (processedSize) *processedSize = size; return res; } -STDMETHODIMP CExtentsStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition) + +Z7_COM7F_IMF(CExtentsStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition)) { switch (seekOrigin) { @@ -203,14 +229,14 @@ STDMETHODIMP CExtentsStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPo } if (offset < 0) return HRESULT_WIN32_ERROR_NEGATIVE_SEEK; - _virtPos = offset; + _virtPos = (UInt64)offset; if (newPosition) *newPosition = _virtPos; return S_OK; } -STDMETHODIMP CLimitedSequentialOutStream::Write(const void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CLimitedSequentialOutStream::Write(const void *data, UInt32 size, UInt32 *processedSize)) { HRESULT result = S_OK; if (processedSize) @@ -237,7 +263,7 @@ STDMETHODIMP CLimitedSequentialOutStream::Write(const void *data, UInt32 size, U } -STDMETHODIMP CTailInStream::Read(void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CTailInStream::Read(void *data, UInt32 size, UInt32 *processedSize)) { UInt32 cur; HRESULT res = Stream->Read(data, size, &cur); @@ -247,7 +273,7 @@ STDMETHODIMP CTailInStream::Read(void *data, UInt32 size, UInt32 *processedSize) return res; } -STDMETHODIMP CTailInStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition) +Z7_COM7F_IMF(CTailInStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition)) { switch (seekOrigin) { @@ -256,7 +282,7 @@ STDMETHODIMP CTailInStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPos case STREAM_SEEK_END: { UInt64 pos = 0; - RINOK(Stream->Seek(offset, STREAM_SEEK_END, &pos)); + RINOK(Stream->Seek(offset, STREAM_SEEK_END, &pos)) if (pos < Offset) return HRESULT_WIN32_ERROR_NEGATIVE_SEEK; _virtPos = pos - Offset; @@ -268,13 +294,13 @@ STDMETHODIMP CTailInStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPos } if (offset < 0) return HRESULT_WIN32_ERROR_NEGATIVE_SEEK; - _virtPos = offset; + _virtPos = (UInt64)offset; if (newPosition) *newPosition = _virtPos; - return Stream->Seek(Offset + _virtPos, STREAM_SEEK_SET, NULL); + return InStream_SeekSet(Stream, Offset + _virtPos); } -STDMETHODIMP CLimitedCachedInStream::Read(void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CLimitedCachedInStream::Read(void *data, UInt32 size, UInt32 *processedSize)) { if (processedSize) *processedSize = 0; @@ -303,7 +329,7 @@ STDMETHODIMP CLimitedCachedInStream::Read(void *data, UInt32 size, UInt32 *proce if (newPos != _physPos) { _physPos = newPos; - RINOK(SeekToPhys()); + RINOK(SeekToPhys()) } res = _stream->Read(data, size, &size); _physPos += size; @@ -314,7 +340,7 @@ STDMETHODIMP CLimitedCachedInStream::Read(void *data, UInt32 size, UInt32 *proce return res; } -STDMETHODIMP CLimitedCachedInStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition) +Z7_COM7F_IMF(CLimitedCachedInStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition)) { switch (seekOrigin) { @@ -325,13 +351,13 @@ STDMETHODIMP CLimitedCachedInStream::Seek(Int64 offset, UInt32 seekOrigin, UInt6 } if (offset < 0) return HRESULT_WIN32_ERROR_NEGATIVE_SEEK; - _virtPos = offset; + _virtPos = (UInt64)offset; if (newPosition) *newPosition = _virtPos; return S_OK; } -STDMETHODIMP CTailOutStream::Write(const void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CTailOutStream::Write(const void *data, UInt32 size, UInt32 *processedSize)) { UInt32 cur; HRESULT res = Stream->Write(data, size, &cur); @@ -343,7 +369,7 @@ STDMETHODIMP CTailOutStream::Write(const void *data, UInt32 size, UInt32 *proces return res; } -STDMETHODIMP CTailOutStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition) +Z7_COM7F_IMF(CTailOutStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition)) { switch (seekOrigin) { @@ -354,13 +380,13 @@ STDMETHODIMP CTailOutStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPo } if (offset < 0) return HRESULT_WIN32_ERROR_NEGATIVE_SEEK; - _virtPos = offset; + _virtPos = (UInt64)offset; if (newPosition) *newPosition = _virtPos; - return Stream->Seek(Offset + _virtPos, STREAM_SEEK_SET, NULL); + return Stream->Seek((Int64)(Offset + _virtPos), STREAM_SEEK_SET, NULL); } -STDMETHODIMP CTailOutStream::SetSize(UInt64 newSize) +Z7_COM7F_IMF(CTailOutStream::SetSize(UInt64 newSize)) { _virtSize = newSize; return Stream->SetSize(Offset + newSize); diff --git a/src/plugins/7zip/7za/cpp/7zip/Common/LimitedStreams.h b/src/plugins/7zip/7za/cpp/7zip/Common/LimitedStreams.h index fb1ac3cd..69fcdcdb 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Common/LimitedStreams.h +++ b/src/plugins/7zip/7za/cpp/7zip/Common/LimitedStreams.h @@ -1,17 +1,19 @@ // LimitedStreams.h -#ifndef __LIMITED_STREAMS_H -#define __LIMITED_STREAMS_H +#ifndef ZIP7_INC_LIMITED_STREAMS_H +#define ZIP7_INC_LIMITED_STREAMS_H #include "../../Common/MyBuffer.h" #include "../../Common/MyCom.h" #include "../../Common/MyVector.h" #include "../IStream.h" -class CLimitedSequentialInStream: - public ISequentialInStream, - public CMyUnknownImp -{ +#include "StreamUtils.h" + +Z7_CLASS_IMP_COM_1( + CLimitedSequentialInStream + , ISequentialInStream +) CMyComPtr _stream; UInt64 _size; UInt64 _pos; @@ -25,26 +27,22 @@ class CLimitedSequentialInStream: _pos = 0; _wasFinished = false; } - - MY_UNKNOWN_IMP1(ISequentialInStream) - - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); UInt64 GetSize() const { return _pos; } UInt64 GetRem() const { return _size - _pos; } bool WasFinished() const { return _wasFinished; } }; -class CLimitedInStream: - public IInStream, - public CMyUnknownImp -{ + +Z7_CLASS_IMP_IInStream( + CLimitedInStream +) CMyComPtr _stream; UInt64 _virtPos; UInt64 _physPos; UInt64 _size; UInt64 _startOffset; - HRESULT SeekToPhys() { return _stream->Seek(_physPos, STREAM_SEEK_SET, NULL); } + HRESULT SeekToPhys() { return InStream_SeekSet(_stream, _physPos); } public: void SetStream(IInStream *stream) { _stream = stream; } HRESULT InitAndSeek(UInt64 startOffset, UInt64 size) @@ -55,21 +53,15 @@ class CLimitedInStream: _size = size; return SeekToPhys(); } - - MY_UNKNOWN_IMP2(ISequentialInStream, IInStream) - - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); - STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition); - HRESULT SeekToStart() { return Seek(0, STREAM_SEEK_SET, NULL); } }; HRESULT CreateLimitedInStream(IInStream *inStream, UInt64 pos, UInt64 size, ISequentialInStream **resStream); -class CClusterInStream: - public IInStream, - public CMyUnknownImp -{ + +Z7_CLASS_IMP_IInStream( + CClusterInStream +) UInt64 _virtPos; UInt64 _physPos; UInt32 _curRem; @@ -80,7 +72,7 @@ class CClusterInStream: CRecordVector Vector; UInt64 StartOffset; - HRESULT SeekToPhys() { return Stream->Seek(_physPos, STREAM_SEEK_SET, NULL); } + HRESULT SeekToPhys() { return InStream_SeekSet(Stream, _physPos); } HRESULT InitAndSeek() { @@ -94,57 +86,52 @@ class CClusterInStream: } return S_OK; } +}; - MY_UNKNOWN_IMP2(ISequentialInStream, IInStream) - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); - STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition); -}; + +const UInt64 k_SeekExtent_Phy_Type_ZeroFill = (UInt64)(Int64)-1; struct CSeekExtent { - UInt64 Phy; UInt64 Virt; -}; + UInt64 Phy; -class CExtentsStream: - public IInStream, - public CMyUnknownImp -{ - UInt64 _phyPos; - UInt64 _virtPos; - bool _needStartSeek; + void SetAs_ZeroFill() { Phy = k_SeekExtent_Phy_Type_ZeroFill; } + bool Is_ZeroFill() const { return Phy == k_SeekExtent_Phy_Type_ZeroFill; } +}; - HRESULT SeekToPhys() { return Stream->Seek(_phyPos, STREAM_SEEK_SET, NULL); } +Z7_CLASS_IMP_IInStream( + CExtentsStream +) + UInt64 _virtPos; + UInt64 _phyPos; + unsigned _prevExtentIndex; public: CMyComPtr Stream; CRecordVector Extents; - MY_UNKNOWN_IMP2(ISequentialInStream, IInStream) - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); - STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition); void ReleaseStream() { Stream.Release(); } - void Init() { _virtPos = 0; - _phyPos = 0; - _needStartSeek = true; + _phyPos = (UInt64)0 - 1; // we need Seek() for Stream + _prevExtentIndex = 0; } }; -class CLimitedSequentialOutStream: - public ISequentialOutStream, - public CMyUnknownImp -{ + + +Z7_CLASS_IMP_COM_1( + CLimitedSequentialOutStream + , ISequentialOutStream +) CMyComPtr _stream; UInt64 _size; bool _overflow; bool _overflowIsAllowed; public: - MY_UNKNOWN_IMP1(ISequentialOutStream) - STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize); void SetStream(ISequentialOutStream *stream) { _stream = stream; } void ReleaseStream() { _stream.Release(); } void Init(UInt64 size, bool overflowIsAllowed = false) @@ -158,10 +145,9 @@ class CLimitedSequentialOutStream: }; -class CTailInStream: - public IInStream, - public CMyUnknownImp -{ +Z7_CLASS_IMP_IInStream( + CTailInStream +) UInt64 _virtPos; public: CMyComPtr Stream; @@ -171,19 +157,13 @@ class CTailInStream: { _virtPos = 0; } - - MY_UNKNOWN_IMP2(ISequentialInStream, IInStream) - - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); - STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition); - - HRESULT SeekToStart() { return Stream->Seek(Offset, STREAM_SEEK_SET, NULL); } + HRESULT SeekToStart() { return InStream_SeekSet(Stream, Offset); } }; -class CLimitedCachedInStream: - public IInStream, - public CMyUnknownImp -{ + +Z7_CLASS_IMP_IInStream( + CLimitedCachedInStream +) CMyComPtr _stream; UInt64 _virtPos; UInt64 _physPos; @@ -194,8 +174,7 @@ class CLimitedCachedInStream: size_t _cacheSize; size_t _cachePhyPos; - - HRESULT SeekToPhys() { return _stream->Seek(_physPos, STREAM_SEEK_SET, NULL); } + HRESULT SeekToPhys() { return InStream_SeekSet(_stream, _physPos); } public: CByteBuffer Buffer; @@ -216,37 +195,27 @@ class CLimitedCachedInStream: return SeekToPhys(); } - MY_UNKNOWN_IMP2(ISequentialInStream, IInStream) - - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); - STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition); - HRESULT SeekToStart() { return Seek(0, STREAM_SEEK_SET, NULL); } }; -class CTailOutStream: + +class CTailOutStream Z7_final : public IOutStream, public CMyUnknownImp { + Z7_IFACES_IMP_UNK_2(ISequentialOutStream, IOutStream) + UInt64 _virtPos; UInt64 _virtSize; public: CMyComPtr Stream; UInt64 Offset; - virtual ~CTailOutStream() {} - - MY_UNKNOWN_IMP2(ISequentialOutStream, IOutStream) - void Init() { _virtPos = 0; _virtSize = 0; } - - STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize); - STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition); - STDMETHOD(SetSize)(UInt64 newSize); }; #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Common/LockedStream.h b/src/plugins/7zip/7za/cpp/7zip/Common/LockedStream.h index efebf197..99ee8055 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Common/LockedStream.h +++ b/src/plugins/7zip/7za/cpp/7zip/Common/LockedStream.h @@ -1,6 +1,6 @@ // LockedStream.h -#ifndef __LOCKED_STREAM_H -#define __LOCKED_STREAM_H +#ifndef ZIP7_INC_LOCKED_STREAM_H +#define ZIP7_INC_LOCKED_STREAM_H #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Common/MemBlocks.cpp b/src/plugins/7zip/7za/cpp/7zip/Common/MemBlocks.cpp index 5aba046b..6165c7e5 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Common/MemBlocks.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Common/MemBlocks.cpp @@ -7,21 +7,26 @@ #include "MemBlocks.h" #include "StreamUtils.h" -bool CMemBlockManager::AllocateSpace(size_t numBlocks) +bool CMemBlockManager::AllocateSpace_bool(size_t numBlocks) { FreeSpace(); - if (_blockSize < sizeof(void *) || numBlocks < 1) + if (numBlocks == 0) + { + return true; + // return false; + } + if (_blockSize < sizeof(void *)) return false; - size_t totalSize = numBlocks * _blockSize; + const size_t totalSize = numBlocks * _blockSize; if (totalSize / _blockSize != numBlocks) return false; _data = ::MidAlloc(totalSize); - if (_data == 0) + if (!_data) return false; Byte *p = (Byte *)_data; for (size_t i = 0; i + 1 < numBlocks; i++, p += _blockSize) - *(Byte **)p = (p + _blockSize); - *(Byte **)p = 0; + *(Byte **)(void *)p = (p + _blockSize); + *(Byte **)(void *)p = NULL; _headFree = _data; return true; } @@ -29,47 +34,70 @@ bool CMemBlockManager::AllocateSpace(size_t numBlocks) void CMemBlockManager::FreeSpace() { ::MidFree(_data); - _data = 0; - _headFree= 0; + _data = NULL; + _headFree= NULL; } void *CMemBlockManager::AllocateBlock() { - if (_headFree == 0) - return 0; void *p = _headFree; - _headFree = *(void **)_headFree; + if (p) + _headFree = *(void **)p; return p; } void CMemBlockManager::FreeBlock(void *p) { - if (p == 0) + if (!p) return; *(void **)p = _headFree; _headFree = p; } -HRes CMemBlockManagerMt::AllocateSpace(size_t numBlocks, size_t numNoLockBlocks) +// #include + +HRESULT CMemBlockManagerMt::AllocateSpace(size_t numBlocks, size_t numNoLockBlocks) { if (numNoLockBlocks > numBlocks) return E_INVALIDARG; - if (!CMemBlockManager::AllocateSpace(numBlocks)) + const size_t numLockBlocks = numBlocks - numNoLockBlocks; + UInt32 maxCount = (UInt32)numLockBlocks; + if (maxCount != numLockBlocks) + return E_OUTOFMEMORY; + if (!CMemBlockManager::AllocateSpace_bool(numBlocks)) return E_OUTOFMEMORY; - size_t numLockBlocks = numBlocks - numNoLockBlocks; + // we need (maxCount = 1), if we want to create non-use empty Semaphore + if (maxCount == 0) + maxCount = 1; + + // printf("\n Synchro.Create() \n"); + WRes wres; + #ifndef _WIN32 Semaphore.Close(); - return Semaphore.Create((LONG)numLockBlocks, (LONG)numLockBlocks); + wres = Synchro.Create(); + if (wres != 0) + return HRESULT_FROM_WIN32(wres); + wres = Semaphore.Create(&Synchro, (UInt32)numLockBlocks, maxCount); + #else + wres = Semaphore.OptCreateInit((UInt32)numLockBlocks, maxCount); + #endif + + return HRESULT_FROM_WIN32(wres); } -HRes CMemBlockManagerMt::AllocateSpaceAlways(size_t desiredNumberOfBlocks, size_t numNoLockBlocks) + +HRESULT CMemBlockManagerMt::AllocateSpaceAlways(size_t desiredNumberOfBlocks, size_t numNoLockBlocks) { + // desiredNumberOfBlocks = 0; // for debug if (numNoLockBlocks > desiredNumberOfBlocks) return E_INVALIDARG; for (;;) { - if (AllocateSpace(desiredNumberOfBlocks, numNoLockBlocks) == 0) - return 0; + // if (desiredNumberOfBlocks == 0) return E_OUTOFMEMORY; + const HRESULT hres = AllocateSpace(desiredNumberOfBlocks, numNoLockBlocks); + if (hres != E_OUTOFMEMORY) + return hres; if (desiredNumberOfBlocks == numNoLockBlocks) return E_OUTOFMEMORY; desiredNumberOfBlocks = numNoLockBlocks + ((desiredNumberOfBlocks - numNoLockBlocks) >> 1); @@ -91,7 +119,7 @@ void *CMemBlockManagerMt::AllocateBlock() void CMemBlockManagerMt::FreeBlock(void *p, bool lockMode) { - if (p == 0) + if (!p) return; { NWindows::NSynchronization::CCriticalSectionLock lock(_criticalSection); @@ -101,6 +129,8 @@ void CMemBlockManagerMt::FreeBlock(void *p, bool lockMode) Semaphore.Release(); } + + void CMemBlocks::Free(CMemBlockManagerMt *manager) { while (Blocks.Size() > 0) @@ -122,22 +152,22 @@ HRESULT CMemBlocks::WriteToStream(size_t blockSize, ISequentialOutStream *outStr UInt64 totalSize = TotalSize; for (unsigned blockIndex = 0; totalSize > 0; blockIndex++) { - UInt32 curSize = (UInt32)blockSize; - if (totalSize < curSize) - curSize = (UInt32)totalSize; + size_t curSize = blockSize; + if (curSize > totalSize) + curSize = (size_t)totalSize; if (blockIndex >= Blocks.Size()) return E_FAIL; - RINOK(WriteStream(outStream, Blocks[blockIndex], curSize)); + RINOK(WriteStream(outStream, Blocks[blockIndex], curSize)) totalSize -= curSize; } return S_OK; } -void CMemLockBlocks::FreeBlock(int index, CMemBlockManagerMt *memManager) +void CMemLockBlocks::FreeBlock(unsigned index, CMemBlockManagerMt *memManager) { memManager->FreeBlock(Blocks[index], LockMode); - Blocks[index] = 0; + Blocks[index] = NULL; } void CMemLockBlocks::Free(CMemBlockManagerMt *memManager) @@ -150,6 +180,7 @@ void CMemLockBlocks::Free(CMemBlockManagerMt *memManager) TotalSize = 0; } +/* HRes CMemLockBlocks::SwitchToNoLockMode(CMemBlockManagerMt *memManager) { if (LockMode) @@ -162,20 +193,21 @@ HRes CMemLockBlocks::SwitchToNoLockMode(CMemBlockManagerMt *memManager) } return 0; } +*/ void CMemLockBlocks::Detach(CMemLockBlocks &blocks, CMemBlockManagerMt *memManager) { blocks.Free(memManager); blocks.LockMode = LockMode; UInt64 totalSize = 0; - size_t blockSize = memManager->GetBlockSize(); + const size_t blockSize = memManager->GetBlockSize(); FOR_VECTOR (i, Blocks) { if (totalSize < TotalSize) blocks.Blocks.Add(Blocks[i]); else FreeBlock(i, memManager); - Blocks[i] = 0; + Blocks[i] = NULL; totalSize += blockSize; } blocks.TotalSize = TotalSize; diff --git a/src/plugins/7zip/7za/cpp/7zip/Common/MemBlocks.h b/src/plugins/7zip/7za/cpp/7zip/Common/MemBlocks.h index ec56c14d..201f3bc7 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Common/MemBlocks.h +++ b/src/plugins/7zip/7za/cpp/7zip/Common/MemBlocks.h @@ -1,7 +1,7 @@ // MemBlocks.h -#ifndef __MEM_BLOCKS_H -#define __MEM_BLOCKS_H +#ifndef ZIP7_INC_MEM_BLOCKS_H +#define ZIP7_INC_MEM_BLOCKS_H #include "../../Common/MyVector.h" @@ -15,10 +15,10 @@ class CMemBlockManager size_t _blockSize; void *_headFree; public: - CMemBlockManager(size_t blockSize = (1 << 20)): _data(0), _blockSize(blockSize), _headFree(0) {} + CMemBlockManager(size_t blockSize = (1 << 20)): _data(NULL), _blockSize(blockSize), _headFree(NULL) {} ~CMemBlockManager() { FreeSpace(); } - bool AllocateSpace(size_t numBlocks); + bool AllocateSpace_bool(size_t numBlocks); void FreeSpace(); size_t GetBlockSize() const { return _blockSize; } void *AllocateBlock(); @@ -30,17 +30,18 @@ class CMemBlockManagerMt: public CMemBlockManager { NWindows::NSynchronization::CCriticalSection _criticalSection; public: - NWindows::NSynchronization::CSemaphore Semaphore; + SYNC_OBJ_DECL(Synchro) + NWindows::NSynchronization::CSemaphore_WFMO Semaphore; CMemBlockManagerMt(size_t blockSize = (1 << 20)): CMemBlockManager(blockSize) {} ~CMemBlockManagerMt() { FreeSpace(); } - HRes AllocateSpace(size_t numBlocks, size_t numNoLockBlocks = 0); - HRes AllocateSpaceAlways(size_t desiredNumberOfBlocks, size_t numNoLockBlocks = 0); + HRESULT AllocateSpace(size_t numBlocks, size_t numNoLockBlocks); + HRESULT AllocateSpaceAlways(size_t desiredNumberOfBlocks, size_t numNoLockBlocks = 0); void FreeSpace(); void *AllocateBlock(); void FreeBlock(void *p, bool lockMode = true); - HRes ReleaseLockedBlocks(int number) { return Semaphore.Release(number); } + // WRes ReleaseLockedBlocks_WRes(unsigned number) { return Semaphore.Release(number); } }; @@ -61,10 +62,10 @@ struct CMemLockBlocks: public CMemBlocks { bool LockMode; - CMemLockBlocks(): LockMode(true) {}; + CMemLockBlocks(): LockMode(true) {} void Free(CMemBlockManagerMt *memManager); - void FreeBlock(int index, CMemBlockManagerMt *memManager); - HRes SwitchToNoLockMode(CMemBlockManagerMt *memManager); + void FreeBlock(unsigned index, CMemBlockManagerMt *memManager); + // HRESULT SwitchToNoLockMode(CMemBlockManagerMt *memManager); void Detach(CMemLockBlocks &blocks, CMemBlockManagerMt *memManager); }; diff --git a/src/plugins/7zip/7za/cpp/7zip/Common/MethodId.h b/src/plugins/7zip/7za/cpp/7zip/Common/MethodId.h index 28b615fc..19b1f10d 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Common/MethodId.h +++ b/src/plugins/7zip/7za/cpp/7zip/Common/MethodId.h @@ -1,7 +1,7 @@ // MethodId.h -#ifndef __7Z_METHOD_ID_H -#define __7Z_METHOD_ID_H +#ifndef ZIP7_INC_7Z_METHOD_ID_H +#define ZIP7_INC_7Z_METHOD_ID_H #include "../../Common/MyTypes.h" diff --git a/src/plugins/7zip/7za/cpp/7zip/Common/MethodProps.cpp b/src/plugins/7zip/7za/cpp/7zip/Common/MethodProps.cpp index 09f7b29c..a5d90cfe 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Common/MethodProps.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Common/MethodProps.cpp @@ -8,9 +8,39 @@ using namespace NWindows; -bool StringToBool(const UString &s, bool &res) +UInt64 Calc_From_Val_Percents(UInt64 val, UInt64 percents) { - if (s.IsEmpty() || (s[0] == '+' && s[1] == 0) || StringsAreEqualNoCase_Ascii(s, "ON")) + // if (percents == 0) return 0; + const UInt64 q = percents / 100; + const UInt32 r = (UInt32)(percents % 100); + UInt64 res = 0; + + if (q != 0) + { + if (val > (UInt64)(Int64)-1 / q) + return (UInt64)(Int64)-1; + res = val * q; + } + + if (r != 0) + { + UInt64 v2; + if (val <= (UInt64)(Int64)-1 / r) + v2 = val * r / 100; + else + v2 = val / 100 * r; + res += v2; + if (res < v2) + return (UInt64)(Int64)-1; + } + + return res; +} + + +bool StringToBool(const wchar_t *s, bool &res) +{ + if (s[0] == 0 || (s[0] == '+' && s[1] == 0) || StringsAreEqualNoCase_Ascii(s, "ON")) { res = true; return true; @@ -30,6 +60,7 @@ HRESULT PROPVARIANT_to_bool(const PROPVARIANT &prop, bool &dest) case VT_EMPTY: dest = true; return S_OK; case VT_BOOL: dest = (prop.boolVal != VARIANT_FALSE); return S_OK; case VT_BSTR: return StringToBool(prop.bstrVal, dest) ? S_OK : E_INVALIDARG; + default: break; } return E_INVALIDARG; } @@ -42,10 +73,18 @@ unsigned ParseStringToUInt32(const UString &srcString, UInt32 &number) return (unsigned)(end - start); } +static unsigned ParseStringToUInt64(const UString &srcString, UInt64 &number) +{ + const wchar_t *start = srcString; + const wchar_t *end; + number = ConvertStringToUInt64(start, &end); + return (unsigned)(end - start); +} + HRESULT ParsePropToUInt32(const UString &name, const PROPVARIANT &prop, UInt32 &resValue) { // =VT_UI4 - // =VT_EMPTY + // =VT_EMPTY : it doesn't change (resValue), and returns S_OK // {stringUInt32}=VT_EMPTY if (prop.vt == VT_UI4) @@ -66,66 +105,151 @@ HRESULT ParsePropToUInt32(const UString &name, const PROPVARIANT &prop, UInt32 & return S_OK; } -HRESULT ParseMtProp(const UString &name, const PROPVARIANT &prop, UInt32 defaultNumThreads, UInt32 &numThreads) + + +HRESULT ParseMtProp2(const UString &name, const PROPVARIANT &prop, UInt32 &numThreads, bool &force) { + force = false; + UString s; if (name.IsEmpty()) { - switch (prop.vt) + if (prop.vt == VT_UI4) + { + numThreads = prop.ulVal; + force = true; + return S_OK; + } + bool val; + HRESULT res = PROPVARIANT_to_bool(prop, val); + if (res == S_OK) { - case VT_UI4: - numThreads = prop.ulVal; - break; - default: + if (!val) { - bool val; - RINOK(PROPVARIANT_to_bool(prop, val)); - numThreads = (val ? defaultNumThreads : 1); - break; + numThreads = 1; + force = true; } + // force = true; for debug + // "(VT_BOOL = VARIANT_TRUE)" set "force = false" and doesn't change numThreads + return S_OK; } - return S_OK; + if (prop.vt != VT_BSTR) + return res; + s.SetFromBstr(prop.bstrVal); + if (s.IsEmpty()) + return E_INVALIDARG; } - if (prop.vt != VT_EMPTY) + else + { + if (prop.vt != VT_EMPTY) + return E_INVALIDARG; + s = name; + } + + s.MakeLower_Ascii(); + const wchar_t *start = s; + UInt32 v = numThreads; + + /* we force up, if threads number specified + only `d` will force it down */ + bool force_loc = true; + for (;;) + { + const wchar_t c = *start; + if (!c) + break; + if (c == 'd') + { + force_loc = false; // force down + start++; + continue; + } + if (c == 'u') + { + force_loc = true; // force up + start++; + continue; + } + bool isPercent = false; + if (c == 'p') + { + isPercent = true; + start++; + } + const wchar_t *end; + v = ConvertStringToUInt32(start, &end); + if (end == start) + return E_INVALIDARG; + if (isPercent) + v = numThreads * v / 100; + start = end; + } + + numThreads = v; + force = force_loc; + return S_OK; +} + + + +static HRESULT SetLogSizeProp(UInt64 number, NCOM::CPropVariant &destProp) +{ + if (number >= 64) return E_INVALIDARG; - return ParsePropToUInt32(name, prop, numThreads); + UInt32 val32; + if (number < 32) + val32 = (UInt32)1 << (unsigned)number; + /* + else if (number == 32 && reduce_4GB_to_32bits) + val32 = (UInt32)(Int32)-1; + */ + else + { + destProp = (UInt64)((UInt64)1 << (unsigned)number); + return S_OK; + } + destProp = (UInt32)val32; + return S_OK; } static HRESULT StringToDictSize(const UString &s, NCOM::CPropVariant &destProp) { + /* if (reduce_4GB_to_32bits) we can reduce (4 GiB) property to (4 GiB - 1). + to fit the value to UInt32 for clients that do not support 64-bit values */ + const wchar_t *end; - UInt32 number = ConvertStringToUInt32(s, &end); - unsigned numDigits = (unsigned)(end - s); + const UInt64 number = ConvertStringToUInt64(s, &end); + const unsigned numDigits = (unsigned)(end - s.Ptr()); if (numDigits == 0 || s.Len() > numDigits + 1) return E_INVALIDARG; if (s.Len() == numDigits) - { - if (number >= 64) - return E_INVALIDARG; - if (number < 32) - destProp = (UInt32)((UInt32)1 << (unsigned)number); - else - destProp = (UInt64)((UInt64)1 << (unsigned)number); - return S_OK; - } + return SetLogSizeProp(number, destProp); unsigned numBits; switch (MyCharLower_Ascii(s[numDigits])) { - case 'b': destProp = number; return S_OK; + case 'b': numBits = 0; break; case 'k': numBits = 10; break; case 'm': numBits = 20; break; case 'g': numBits = 30; break; default: return E_INVALIDARG; } - if (number < ((UInt32)1 << (32 - numBits))) - destProp = (UInt32)(number << numBits); + const UInt64 range4g = ((UInt64)1 << (32 - numBits)); + if (number < range4g) + destProp = (UInt32)((UInt32)number << numBits); + /* + else if (number == range4g && reduce_4GB_to_32bits) + destProp = (UInt32)(Int32)-1; + */ + else if (numBits == 0) + destProp = (UInt64)number; + else if (number >= ((UInt64)1 << (64 - numBits))) + return E_INVALIDARG; else destProp = (UInt64)((UInt64)number << numBits); - return S_OK; } @@ -133,28 +257,32 @@ static HRESULT StringToDictSize(const UString &s, NCOM::CPropVariant &destProp) static HRESULT PROPVARIANT_to_DictSize(const PROPVARIANT &prop, NCOM::CPropVariant &destProp) { if (prop.vt == VT_UI4) + return SetLogSizeProp(prop.ulVal, destProp); + + if (prop.vt == VT_BSTR) { - UInt32 v = prop.ulVal; - if (v >= 64) - return E_INVALIDARG; - if (v < 32) - destProp = (UInt32)((UInt32)1 << (unsigned)v); - else - destProp = (UInt64)((UInt64)1 << (unsigned)v); - return S_OK; + UString s; + s = prop.bstrVal; + return StringToDictSize(s, destProp); } - if (prop.vt == VT_BSTR) - return StringToDictSize(prop.bstrVal, destProp); return E_INVALIDARG; } -void CProps::AddProp32(PROPID propid, UInt32 level) +void CProps::AddProp32(PROPID propid, UInt32 val) { CProp &prop = Props.AddNew(); prop.IsOptional = true; prop.Id = propid; - prop.Value = (UInt32)level; + prop.Value = (UInt32)val; +} + +void CProps::AddPropBool(PROPID propid, bool val) +{ + CProp &prop = Props.AddNew(); + prop.IsOptional = true; + prop.Id = propid; + prop.Value = val; } class CCoderProps @@ -164,10 +292,12 @@ class CCoderProps unsigned _numProps; unsigned _numPropsMax; public: - CCoderProps(unsigned numPropsMax) + CCoderProps(unsigned numPropsMax): + _propIDs(NULL), + _props(NULL), + _numProps(0), + _numPropsMax(numPropsMax) { - _numPropsMax = numPropsMax; - _numProps = 0; _propIDs = new PROPID[numPropsMax]; _props = new NCOM::CPropVariant[numPropsMax]; } @@ -194,7 +324,22 @@ void CCoderProps::AddProp(const CProp &prop) HRESULT CProps::SetCoderProps(ICompressSetCoderProperties *scp, const UInt64 *dataSizeReduce) const { - CCoderProps coderProps(Props.Size() + (dataSizeReduce ? 1 : 0)); + return SetCoderProps_DSReduce_Aff(scp, dataSizeReduce, NULL, NULL, NULL); +} + +HRESULT CProps::SetCoderProps_DSReduce_Aff( + ICompressSetCoderProperties *scp, + const UInt64 *dataSizeReduce, + const UInt64 *affinity, + const UInt32 *affinityGroup, + const UInt64 *affinityInGroup) const +{ + CCoderProps coderProps(Props.Size() + + (dataSizeReduce ? 1 : 0) + + (affinity ? 1 : 0) + + (affinityGroup ? 1 : 0) + + (affinityInGroup ? 1 : 0) + ); FOR_VECTOR (i, Props) coderProps.AddProp(Props[i]); if (dataSizeReduce) @@ -204,27 +349,48 @@ HRESULT CProps::SetCoderProps(ICompressSetCoderProperties *scp, const UInt64 *da prop.Value = *dataSizeReduce; coderProps.AddProp(prop); } + if (affinity) + { + CProp prop; + prop.Id = NCoderPropID::kAffinity; + prop.Value = *affinity; + coderProps.AddProp(prop); + } + if (affinityGroup) + { + CProp prop; + prop.Id = NCoderPropID::kThreadGroup; + prop.Value = *affinityGroup; + coderProps.AddProp(prop); + } + if (affinityInGroup) + { + CProp prop; + prop.Id = NCoderPropID::kAffinityInGroup; + prop.Value = *affinityInGroup; + coderProps.AddProp(prop); + } return coderProps.SetProps(scp); } int CMethodProps::FindProp(PROPID id) const { - for (int i = Props.Size() - 1; i >= 0; i--) - if (Props[i].Id == id) - return i; + for (unsigned i = Props.Size(); i != 0;) + if (Props[--i].Id == id) + return (int)i; return -1; } -int CMethodProps::GetLevel() const +unsigned CMethodProps::GetLevel() const { int i = FindProp(NCoderPropID::kLevel); if (i < 0) return 5; - if (Props[i].Value.vt != VT_UI4) + if (Props[(unsigned)i].Value.vt != VT_UI4) return 9; - UInt32 level = Props[i].Value.ulVal; - return level > 9 ? 9 : (int)level; + UInt32 level = Props[(unsigned)i].Value.ulVal; + return level > 9 ? 9 : (unsigned)level; } struct CNameToPropID @@ -233,13 +399,16 @@ struct CNameToPropID const char *Name; }; + +// the following are related to NCoderPropID::EEnum values +// NCoderPropID::k_NUM_DEFINED static const CNameToPropID g_NameToPropID[] = { { VT_UI4, "" }, { VT_UI4, "d" }, { VT_UI4, "mem" }, { VT_UI4, "o" }, - { VT_UI4, "c" }, + { VT_UI8, "c" }, { VT_UI4, "pb" }, { VT_UI4, "lc" }, { VT_UI4, "lp" }, @@ -251,14 +420,66 @@ static const CNameToPropID g_NameToPropID[] = { VT_UI4, "mt" }, { VT_BOOL, "eos" }, { VT_UI4, "x" }, - { VT_UI4, "reduceSize" } + { VT_UI8, "reduce" }, + { VT_UI8, "expect" }, + { VT_UI8, "cc" }, // "cc" in v23, "b" in v22.01 + { VT_UI4, "check" }, + { VT_BSTR, "filter" }, + { VT_UI8, "memuse" }, + { VT_UI8, "aff" }, + { VT_UI4, "offset" }, + { VT_UI4, "zhb" } + /* + , { VT_UI4, "tgn" }, // kNumThreadGroups + , { VT_UI4, "tgi" }, // kThreadGroup + , { VT_UI8, "tga" }, // kAffinityInGroup + */ + /* + , + // { VT_UI4, "zhc" }, + // { VT_UI4, "zhd" }, + // { VT_UI4, "zcb" }, + { VT_UI4, "dc" }, + { VT_UI4, "zx" }, + { VT_UI4, "zf" }, + { VT_UI4, "zmml" }, + { VT_UI4, "zov" }, + { VT_BOOL, "zmfr" }, + { VT_BOOL, "zle" }, // long enable + // { VT_UI4, "zldb" }, + { VT_UI4, "zld" }, + { VT_UI4, "zlhb" }, + { VT_UI4, "zlmml" }, + { VT_UI4, "zlbb" }, + { VT_UI4, "zlhrb" }, + { VT_BOOL, "zwus" }, + { VT_BOOL, "zshp" }, + { VT_BOOL, "zshs" }, + { VT_BOOL, "zshe" }, + { VT_BOOL, "zshg" }, + { VT_UI4, "zpsm" } + */ + // { VT_UI4, "mcb" }, // mc log version + // { VT_UI4, "ztlen" }, // fb ? }; +/* +#if defined(static_assert) || (defined(__cplusplus) && __cplusplus >= 200410L) || (defined(_MSC_VER) && _MSC_VER >= 1600) + +#if (defined(__cplusplus) && __cplusplus < 201103L) \ + && defined(__clang__) && __clang_major__ >= 4 +#pragma GCC diagnostic ignored "-Wc11-extensions" +#endif + static_assert(Z7_ARRAY_SIZE(g_NameToPropID) == NCoderPropID::k_NUM_DEFINED, + "g_NameToPropID doesn't match NCoderPropID enum"); +#endif +*/ + static int FindPropIdExact(const UString &name) { - for (unsigned i = 0; i < ARRAY_SIZE(g_NameToPropID); i++) + for (unsigned i = 0; i < Z7_ARRAY_SIZE(g_NameToPropID); i++) if (StringsAreEqualNoCase_Ascii(name, g_NameToPropID[i].Name)) - return i; + return (int)i; return -1; } @@ -269,6 +490,13 @@ static bool ConvertProperty(const PROPVARIANT &srcProp, VARTYPE varType, NCOM::C destProp = srcProp; return true; } + + if (varType == VT_UI8 && srcProp.vt == VT_UI4) + { + destProp = (UInt64)srcProp.ulVal; + return true; + } + if (varType == VT_BOOL) { bool res; @@ -311,8 +539,8 @@ static void SplitParam(const UString ¶m, UString &name, UString &value) int eqPos = param.Find(L'='); if (eqPos >= 0) { - name.SetFrom(param, eqPos); - value = param.Ptr(eqPos + 1); + name.SetFrom(param, (unsigned)eqPos); + value = param.Ptr((unsigned)(eqPos + 1)); return; } unsigned i; @@ -333,8 +561,14 @@ static bool IsLogSizeProp(PROPID propid) case NCoderPropID::kDictionarySize: case NCoderPropID::kUsedMemorySize: case NCoderPropID::kBlockSize: - case NCoderPropID::kReduceSize: + case NCoderPropID::kBlockSize2: + /* + case NCoderPropID::kChainSize: + case NCoderPropID::kLdmWindowSize: + */ + // case NCoderPropID::kReduceSize: return true; + default: break; } return false; } @@ -343,14 +577,19 @@ HRESULT CMethodProps::SetParam(const UString &name, const UString &value) { int index = FindPropIdExact(name); if (index < 0) - return E_INVALIDARG; + { + // 'b' was used as NCoderPropID::kBlockSize2 before v23 + if (!name.IsEqualTo_Ascii_NoCase("b") || value.Find(L':') >= 0) + return E_INVALIDARG; + index = NCoderPropID::kBlockSize2; + } const CNameToPropID &nameToPropID = g_NameToPropID[(unsigned)index]; CProp prop; - prop.Id = index; + prop.Id = (unsigned)index; if (IsLogSizeProp(prop.Id)) { - RINOK(StringToDictSize(value, prop.Value)); + RINOK(StringToDictSize(value, prop.Value)) } else { @@ -366,9 +605,22 @@ HRESULT CMethodProps::SetParam(const UString &name, const UString &value) } else if (!value.IsEmpty()) { - UInt32 number; - if (ParseStringToUInt32(value, number) == value.Len()) - propValue = number; + if (nameToPropID.VarType == VT_UI4) + { + UInt32 number; + if (ParseStringToUInt32(value, number) == value.Len()) + propValue = number; + else + propValue = value; + } + else if (nameToPropID.VarType == VT_UI8) + { + UInt64 number; + if (ParseStringToUInt64(value, number) == value.Len()) + propValue = number; + else + propValue = value; + } else propValue = value; } @@ -388,7 +640,7 @@ HRESULT CMethodProps::ParseParamsFromString(const UString &srcString) const UString ¶m = params[i]; UString name, value; SplitParam(param, name, value); - RINOK(SetParam(name, value)); + RINOK(SetParam(name, value)) } return S_OK; } @@ -409,16 +661,16 @@ HRESULT CMethodProps::ParseParamsFromPROPVARIANT(const UString &realName, const } // {realName}=value - int index = FindPropIdExact(realName); + const int index = FindPropIdExact(realName); if (index < 0) return E_INVALIDARG; const CNameToPropID &nameToPropID = g_NameToPropID[(unsigned)index]; CProp prop; - prop.Id = index; + prop.Id = (unsigned)index; if (IsLogSizeProp(prop.Id)) { - RINOK(PROPVARIANT_to_DictSize(value, prop.Value)); + RINOK(PROPVARIANT_to_DictSize(value, prop.Value)) } else { @@ -429,6 +681,59 @@ HRESULT CMethodProps::ParseParamsFromPROPVARIANT(const UString &realName, const return S_OK; } + +static UInt64 GetMemoryUsage_LZMA(UInt32 dict, bool isBt, UInt32 numThreads) +{ + UInt32 hs = dict - 1; + hs |= (hs >> 1); + hs |= (hs >> 2); + hs |= (hs >> 4); + hs |= (hs >> 8); + hs >>= 1; + if (hs >= (1 << 24)) + hs >>= 1; + hs |= (1 << 16) - 1; + // if (numHashBytes >= 5) + if (!isBt) + hs |= (256 << 10) - 1; + hs++; + UInt64 size1 = (UInt64)hs * 4; + size1 += (UInt64)dict * 4; + if (isBt) + size1 += (UInt64)dict * 4; + size1 += (2 << 20); + + if (numThreads > 1 && isBt) + size1 += (2 << 20) + (4 << 20); + return size1; +} + +static const UInt32 kLzmaMaxDictSize = (UInt32)15 << 28; + +UInt64 CMethodProps::Get_Lzma_MemUsage(bool addSlidingWindowSize) const +{ + const UInt64 dicSize = Get_Lzma_DicSize(); + const bool isBt = Get_Lzma_MatchFinder_IsBt(); + const UInt32 dict32 = (dicSize >= kLzmaMaxDictSize ? kLzmaMaxDictSize : (UInt32)dicSize); + const UInt32 numThreads = Get_Lzma_NumThreads(); + UInt64 size = GetMemoryUsage_LZMA(dict32, isBt, numThreads); + + if (addSlidingWindowSize) + { + const UInt32 kBlockSizeMax = (UInt32)0 - (UInt32)(1 << 16); + UInt64 blockSize = (UInt64)dict32 + (1 << 16) + + (numThreads > 1 ? (1 << 20) : 0); + blockSize += (blockSize >> (blockSize < ((UInt32)1 << 30) ? 1 : 2)); + if (blockSize >= kBlockSizeMax) + blockSize = kBlockSizeMax; + size += blockSize; + } + return size; +} + + + + HRESULT COneMethodInfo::ParseMethodFromString(const UString &s) { MethodName.Empty(); @@ -436,14 +741,14 @@ HRESULT COneMethodInfo::ParseMethodFromString(const UString &s) { UString temp = s; if (splitPos >= 0) - temp.DeleteFrom(splitPos); + temp.DeleteFrom((unsigned)splitPos); if (!temp.IsAscii()) return E_INVALIDARG; MethodName.SetFromWStr_if_Ascii(temp); } if (splitPos < 0) return S_OK; - PropsString = s.Ptr(splitPos + 1); + PropsString = s.Ptr((unsigned)(splitPos + 1)); return ParseParamsFromString(PropsString); } @@ -454,5 +759,7 @@ HRESULT COneMethodInfo::ParseMethodFromPROPVARIANT(const UString &realName, cons // -m{N}=method if (value.vt != VT_BSTR) return E_INVALIDARG; - return ParseMethodFromString(value.bstrVal); + UString s; + s = value.bstrVal; + return ParseMethodFromString(s); } diff --git a/src/plugins/7zip/7za/cpp/7zip/Common/MethodProps.h b/src/plugins/7zip/7za/cpp/7zip/Common/MethodProps.h index 765e425d..f7ba5690 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Common/MethodProps.h +++ b/src/plugins/7zip/7za/cpp/7zip/Common/MethodProps.h @@ -1,20 +1,48 @@ // MethodProps.h -#ifndef __7Z_METHOD_PROPS_H -#define __7Z_METHOD_PROPS_H +#ifndef ZIP7_INC_7Z_METHOD_PROPS_H +#define ZIP7_INC_7Z_METHOD_PROPS_H #include "../../Common/MyString.h" +#include "../../Common/Defs.h" +#include "../../Windows/WinDefs.h" #include "../../Windows/PropVariant.h" #include "../ICoder.h" -bool StringToBool(const UString &s, bool &res); +// UInt64 GetMemoryUsage_LZMA(UInt32 dict, bool isBt, UInt32 numThreads); + +inline UInt64 Calc_From_Val_Percents_Less100(UInt64 val, UInt64 percents) +{ + if (percents == 0) + return 0; + if (val <= (UInt64)(Int64)-1 / percents) + return val * percents / 100; + return val / 100 * percents; +} + +UInt64 Calc_From_Val_Percents(UInt64 val, UInt64 percents); + +bool StringToBool(const wchar_t *s, bool &res); HRESULT PROPVARIANT_to_bool(const PROPVARIANT &prop, bool &dest); unsigned ParseStringToUInt32(const UString &srcString, UInt32 &number); + +/* +if (name.IsEmpty() && prop.vt == VT_EMPTY), it doesn't change (resValue) and returns S_OK. + So you must set (resValue) for default value before calling */ HRESULT ParsePropToUInt32(const UString &name, const PROPVARIANT &prop, UInt32 &resValue); -HRESULT ParseMtProp(const UString &name, const PROPVARIANT &prop, UInt32 defaultNumThreads, UInt32 &numThreads); +/* input: (numThreads = the_number_of_processors) */ +HRESULT ParseMtProp2(const UString &name, const PROPVARIANT &prop, UInt32 &numThreads, bool &force); + +inline HRESULT ParseMtProp(const UString &name, const PROPVARIANT &prop, UInt32 numCPUs, UInt32 &numThreads) +{ + bool forced = false; + numThreads = numCPUs; + return ParseMtProp2(name, prop, numThreads, forced); +} + struct CProp { @@ -38,7 +66,9 @@ struct CProps return false; } - void AddProp32(PROPID propid, UInt32 level); + void AddProp32(PROPID propid, UInt32 val); + + void AddPropBool(PROPID propid, bool val); void AddProp_Ascii(PROPID propid, const char *s) { @@ -48,33 +78,49 @@ struct CProps prop.Value = s; } - HRESULT SetCoderProps(ICompressSetCoderProperties *scp, const UInt64 *dataSizeReduce) const; + HRESULT SetCoderProps(ICompressSetCoderProperties *scp, const UInt64 *dataSizeReduce = NULL) const; + HRESULT SetCoderProps_DSReduce_Aff(ICompressSetCoderProperties *scp, + const UInt64 *dataSizeReduce, + const UInt64 *affinity, + const UInt32 *affinityGroup, + const UInt64 *affinityInGroup) const; }; class CMethodProps: public CProps { HRESULT SetParam(const UString &name, const UString &value); public: - int GetLevel() const; + unsigned GetLevel() const; int Get_NumThreads() const { - int i = FindProp(NCoderPropID::kNumThreads); + const int i = FindProp(NCoderPropID::kNumThreads); if (i >= 0) - if (Props[i].Value.vt == VT_UI4) - return (int)Props[i].Value.ulVal; + { + const NWindows::NCOM::CPropVariant &val = Props[(unsigned)i].Value; + if (val.vt == VT_UI4) + return (int)val.ulVal; + } return -1; } - bool Get_DicSize(UInt32 &res) const + bool Get_DicSize(UInt64 &res) const { res = 0; - int i = FindProp(NCoderPropID::kDictionarySize); + const int i = FindProp(NCoderPropID::kDictionarySize); if (i >= 0) - if (Props[i].Value.vt == VT_UI4) + { + const NWindows::NCOM::CPropVariant &val = Props[(unsigned)i].Value; + if (val.vt == VT_UI4) { - res = Props[i].Value.ulVal; + res = val.ulVal; return true; } + if (val.vt == VT_UI8) + { + res = val.uhVal.QuadPart; + return true; + } + } return false; } @@ -82,21 +128,52 @@ class CMethodProps: public CProps UInt32 Get_Lzma_Algo() const { - int i = FindProp(NCoderPropID::kAlgorithm); + const int i = FindProp(NCoderPropID::kAlgorithm); if (i >= 0) - if (Props[i].Value.vt == VT_UI4) - return Props[i].Value.ulVal; + { + const NWindows::NCOM::CPropVariant &val = Props[(unsigned)i].Value; + if (val.vt == VT_UI4) + return val.ulVal; + } return GetLevel() >= 5 ? 1 : 0; } - UInt32 Get_Lzma_DicSize() const + UInt64 Get_Lzma_DicSize() const + { + UInt64 v; + if (Get_DicSize(v)) + return v; + const unsigned level = GetLevel(); + const UInt32 dictSize = level <= 4 ? + (UInt32)1 << (level * 2 + 16) : + level <= sizeof(size_t) / 2 + 4 ? + (UInt32)1 << (level + 20) : + (UInt32)1 << (sizeof(size_t) / 2 + 24); + return dictSize; + } + + bool Get_Lzma_MatchFinder_IsBt() const { - int i = FindProp(NCoderPropID::kDictionarySize); + const int i = FindProp(NCoderPropID::kMatchFinder); if (i >= 0) - if (Props[i].Value.vt == VT_UI4) - return Props[i].Value.ulVal; - int level = GetLevel(); - return level <= 5 ? (1 << (level * 2 + 14)) : (level == 6 ? (1 << 25) : (1 << 26)); + { + const NWindows::NCOM::CPropVariant &val = Props[(unsigned)i].Value; + if (val.vt == VT_BSTR) + return ((val.bstrVal[0] | 0x20) != 'h'); // check for "hc" + } + return GetLevel() >= 5; + } + + bool Get_Lzma_Eos() const + { + const int i = FindProp(NCoderPropID::kEndMarker); + if (i >= 0) + { + const NWindows::NCOM::CPropVariant &val = Props[(unsigned)i].Value; + if (val.vt == VT_BOOL) + return VARIANT_BOOLToBool(val.boolVal); + } + return false; } bool Are_Lzma_Model_Props_Defined() const @@ -107,18 +184,68 @@ class CMethodProps: public CProps return false; } - UInt32 Get_Lzma_NumThreads(bool &fixedNumber) const + UInt32 Get_Lzma_NumThreads() const { - fixedNumber = false; + if (Get_Lzma_Algo() == 0) + return 1; int numThreads = Get_NumThreads(); if (numThreads >= 0) - { - fixedNumber = true; return numThreads < 2 ? 1 : 2; + return 2; + } + + UInt64 Get_Lzma_MemUsage(bool addSlidingWindowSize) const; + + /* returns -1, if numThreads is unknown */ + int Get_Xz_NumThreads(UInt32 &lzmaThreads) const + { + lzmaThreads = 1; + int numThreads = Get_NumThreads(); + if (numThreads >= 0 && numThreads <= 1) + return 1; + if (Get_Lzma_Algo() != 0) + lzmaThreads = 2; + return numThreads; + } + + UInt64 GetProp_BlockSize(PROPID id) const + { + const int i = FindProp(id); + if (i >= 0) + { + const NWindows::NCOM::CPropVariant &val = Props[(unsigned)i].Value; + if (val.vt == VT_UI4) { return val.ulVal; } + if (val.vt == VT_UI8) { return val.uhVal.QuadPart; } + } + return 0; + } + + UInt64 Get_Xz_BlockSize() const + { + { + UInt64 blockSize1 = GetProp_BlockSize(NCoderPropID::kBlockSize); + UInt64 blockSize2 = GetProp_BlockSize(NCoderPropID::kBlockSize2); + UInt64 minSize = MyMin(blockSize1, blockSize2); + if (minSize != 0) + return minSize; + UInt64 maxSize = MyMax(blockSize1, blockSize2); + if (maxSize != 0) + return maxSize; } - return Get_Lzma_Algo() == 0 ? 1 : 2; + const UInt32 kMinSize = (UInt32)1 << 20; + const UInt32 kMaxSize = (UInt32)1 << 28; + const UInt64 dictSize = Get_Lzma_DicSize(); + /* lzma2 code uses fake 4 GiB to calculate ChunkSize. So we do same */ + UInt64 blockSize = (UInt64)dictSize << 2; + if (blockSize < kMinSize) blockSize = kMinSize; + if (blockSize > kMaxSize) blockSize = kMaxSize; + if (blockSize < dictSize) blockSize = dictSize; + blockSize += (kMinSize - 1); + blockSize &= ~(UInt64)(kMinSize - 1); + return blockSize; } + UInt32 Get_BZip2_NumThreads(bool &fixedNumber) const { fixedNumber = false; @@ -127,37 +254,47 @@ class CMethodProps: public CProps { fixedNumber = true; if (numThreads < 1) return 1; - if (numThreads > 64) return 64; - return numThreads; + const unsigned kNumBZip2ThreadsMax = 64; + if ((unsigned)numThreads > kNumBZip2ThreadsMax) return kNumBZip2ThreadsMax; + return (unsigned)numThreads; } return 1; } UInt32 Get_BZip2_BlockSize() const { - int i = FindProp(NCoderPropID::kDictionarySize); + const int i = FindProp(NCoderPropID::kDictionarySize); if (i >= 0) - if (Props[i].Value.vt == VT_UI4) + { + const NWindows::NCOM::CPropVariant &val = Props[(unsigned)i].Value; + if (val.vt == VT_UI4) { - UInt32 blockSize = Props[i].Value.ulVal; + UInt32 blockSize = val.ulVal; const UInt32 kDicSizeMin = 100000; const UInt32 kDicSizeMax = 900000; if (blockSize < kDicSizeMin) blockSize = kDicSizeMin; if (blockSize > kDicSizeMax) blockSize = kDicSizeMax; return blockSize; } - int level = GetLevel(); + } + const unsigned level = GetLevel(); return 100000 * (level >= 5 ? 9 : (level >= 1 ? level * 2 - 1: 1)); } - UInt32 Get_Ppmd_MemSize() const + UInt64 Get_Ppmd_MemSize() const { - int i = FindProp(NCoderPropID::kUsedMemorySize); + const int i = FindProp(NCoderPropID::kUsedMemorySize); if (i >= 0) - if (Props[i].Value.vt == VT_UI4) - return Props[i].Value.ulVal; - int level = GetLevel(); - return level >= 9 ? (192 << 20) : ((UInt32)1 << (level + 19)); + { + const NWindows::NCOM::CPropVariant &val = Props[(unsigned)i].Value; + if (val.vt == VT_UI4) + return val.ulVal; + if (val.vt == VT_UI8) + return val.uhVal.QuadPart; + } + const unsigned level = GetLevel(); + const UInt32 mem = (UInt32)1 << (level + 19); + return mem; } void AddProp_Level(UInt32 level) @@ -170,6 +307,23 @@ class CMethodProps: public CProps AddProp32(NCoderPropID::kNumThreads, numThreads); } + void AddProp_EndMarker_if_NotFound(bool eos) + { + if (FindProp(NCoderPropID::kEndMarker) < 0) + AddPropBool(NCoderPropID::kEndMarker, eos); + } + + void AddProp_BlockSize2(UInt64 blockSize2) + { + if (FindProp(NCoderPropID::kBlockSize2) < 0) + { + CProp &prop = Props.AddNew(); + prop.IsOptional = true; + prop.Id = NCoderPropID::kBlockSize2; + prop.Value = blockSize2; + } + } + HRESULT ParseParamsFromString(const UString &srcString); HRESULT ParseParamsFromPROPVARIANT(const UString &realName, const PROPVARIANT &value); }; diff --git a/src/plugins/7zip/7za/cpp/7zip/Common/MultiOutStream.cpp b/src/plugins/7zip/7za/cpp/7zip/Common/MultiOutStream.cpp new file mode 100644 index 00000000..19543119 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Common/MultiOutStream.cpp @@ -0,0 +1,855 @@ +// MultiOutStream.cpp + +#include "StdAfx.h" + +// #define DEBUG_VOLUMES + +#ifdef DEBUG_VOLUMES +#include + #define PRF(x) x; +#else + #define PRF(x) +#endif + +#include "../../Common/ComTry.h" + +#include "../../Windows/FileDir.h" +#include "../../Windows/FileFind.h" +#include "../../Windows/System.h" + +#include "MultiOutStream.h" + +using namespace NWindows; +using namespace NFile; +using namespace NDir; + +static const unsigned k_NumVols_MAX = k_VectorSizeMax - 1; + // 2; // for debug + +/* +#define UPDATE_HRES(hres, x) \ + { const HRESULT res2 = (x); if (hres == SZ_OK) hres = res2; } +*/ + +HRESULT CMultiOutStream::Destruct() +{ + COM_TRY_BEGIN + HRESULT hres = S_OK; + HRESULT hres3 = S_OK; + + while (!Streams.IsEmpty()) + { + try + { + HRESULT hres2; + if (NeedDelete) + { + /* we could call OptReOpen_and_SetSize() to test that we try to delete correct file, + but we cannot guarantee that (RealSize) will be correct after Write() or another failures. + And we still want to delete files even for such cases. + So we don't check for OptReOpen_and_SetSize() here: */ + // if (OptReOpen_and_SetSize(Streams.Size() - 1, 0) == S_OK) + hres2 = CloseStream_and_DeleteFile(Streams.Size() - 1); + } + else + { + hres2 = CloseStream(Streams.Size() - 1); + } + if (hres == S_OK) + hres = hres2; + } + catch(...) + { + hres3 = E_OUTOFMEMORY; + } + + { + /* Stream was released in CloseStream_*() above already, and it was removed from linked list + it's some unexpected case, if Stream is still attached here. + So the following code is optional: */ + CVolStream &s = Streams.Back(); + if (s.Stream) + { + if (hres3 == S_OK) + hres3 = E_FAIL; + s.Stream.Detach(); + /* it will be not failure, even if we call RemoveFromLinkedList() + twice for same CVolStream in this Destruct() function */ + RemoveFromLinkedList(Streams.Size() - 1); + } + } + Streams.DeleteBack(); + // Delete_LastStream_Records(); + } + + if (hres == S_OK) + hres = hres3; + if (hres == S_OK && NumListItems != 0) + hres = E_FAIL; + return hres; + COM_TRY_END +} + + +CMultiOutStream::~CMultiOutStream() +{ + // we try to avoid exception in destructors + Destruct(); +} + + +void CMultiOutStream::Init(const CRecordVector &sizes) +{ + Streams.Clear(); + InitLinkedList(); + Sizes = sizes; + NeedDelete = true; + MTime_Defined = false; + FinalVol_WasReopen = false; + NumOpenFiles_AllowedMax = NSystem::Get_File_OPEN_MAX_Reduced_for_3_tasks(); + + _streamIndex = 0; + _offsetPos = 0; + _absPos = 0; + _length = 0; + _absLimit = (UInt64)(Int64)-1; + + _restrict_Begin = 0; + _restrict_End = (UInt64)(Int64)-1; + _restrict_Global = 0; + + UInt64 sum = 0; + unsigned i = 0; + for (i = 0; i < Sizes.Size(); i++) + { + if (i >= k_NumVols_MAX) + { + _absLimit = sum; + break; + } + const UInt64 size = Sizes[i]; + const UInt64 next = sum + size; + if (next < sum) + break; + sum = next; + } + + // if (Sizes.IsEmpty()) throw "no volume sizes"; + const UInt64 size = Sizes.Back(); + if (size == 0) + throw "zero size last volume"; + + if (i == Sizes.Size()) + if ((_absLimit - sum) / size >= (k_NumVols_MAX - i)) + _absLimit = sum + (k_NumVols_MAX - i) * size; +} + + +/* IsRestricted(): + we must call only if volume is full (s.RealSize==VolSize) or finished. + the function doesn't use VolSize and it uses s.RealSize instead. + it returns true : if stream is restricted, and we can't close that stream + it returns false : if there is no restriction, and we can close that stream + Note: (RealSize == 0) (empty volume) on restriction bounds are supposed as non-restricted +*/ +bool CMultiOutStream::IsRestricted(const CVolStream &s) const +{ + if (s.Start < _restrict_Global) + return true; + if (_restrict_Begin == _restrict_End) + return false; + if (_restrict_Begin <= s.Start) + return _restrict_End > s.Start; + return _restrict_Begin < s.Start + s.RealSize; +} + +/* +// this function check also _length and volSize +bool CMultiOutStream::IsRestricted_for_Close(unsigned index) const +{ + const CVolStream &s = Streams[index]; + if (_length <= s.Start) // we don't close streams after the end, because we still can write them later + return true; + // (_length > s.Start) + const UInt64 volSize = GetVolSize_for_Stream(index); + if (volSize == 0) + return IsRestricted_Empty(s); + if (_length - s.Start < volSize) + return true; + return IsRestricted(s); +} +*/ + +FString CMultiOutStream::GetFilePath(unsigned index) +{ + FString name; + name.Add_UInt32((UInt32)(index + 1)); + while (name.Len() < 3) + name.InsertAtFront(FTEXT('0')); + name.Insert(0, Prefix); + return name; +} + + +// we close stream, but we still keep item in Streams[] vector +HRESULT CMultiOutStream::CloseStream(unsigned index) +{ + CVolStream &s = Streams[index]; + if (s.Stream) + { + RINOK(s.StreamSpec->Close()) + // the following two commands must be called together: + s.Stream.Release(); + RemoveFromLinkedList(index); + } + return S_OK; +} + + +// we close stream and delete file, but we still keep item in Streams[] vector +HRESULT CMultiOutStream::CloseStream_and_DeleteFile(unsigned index) +{ + PRF(printf("\n====== %u, CloseStream_AndDelete \n", index)) + RINOK(CloseStream(index)) + FString path = GetFilePath(index); + path += Streams[index].Postfix; + // we can checki that file exist + // if (NFind::DoesFileExist_Raw(path)) + if (!DeleteFileAlways(path)) + return GetLastError_noZero_HRESULT(); + return S_OK; +} + + +HRESULT CMultiOutStream::CloseStream_and_FinalRename(unsigned index) +{ + PRF(printf("\n====== %u, CloseStream_and_FinalRename \n", index)) + CVolStream &s = Streams[index]; + // HRESULT res = S_OK; + bool mtime_WasSet = false; + if (MTime_Defined && s.Stream) + { + if (s.StreamSpec->SetMTime(&MTime)) + mtime_WasSet = true; + // else res = GetLastError_noZero_HRESULT(); + } + + RINOK(CloseStream(index)) + if (s.Postfix.IsEmpty()) // if Postfix is empty, the path is already final + return S_OK; + const FString path = GetFilePath(index); + FString tempPath = path; + tempPath += s.Postfix; + + if (MTime_Defined && !mtime_WasSet) + { + if (!SetDirTime(tempPath, NULL, NULL, &MTime)) + { + // res = GetLastError_noZero_HRESULT(); + } + } + if (!MyMoveFile(tempPath, path)) + return GetLastError_noZero_HRESULT(); + /* we clear CVolStream::Postfix. So we will not use Temp path + anymore for this stream, and we will work only with final path */ + s.Postfix.Empty(); + // we can ignore set_mtime error or we can return it + return S_OK; + // return res; +} + + +HRESULT CMultiOutStream::PrepareToOpenNew() +{ + PRF(printf("PrepareToOpenNew NumListItems =%u, NumOpenFiles_AllowedMax = %u \n", NumListItems, NumOpenFiles_AllowedMax)) + + if (NumListItems < NumOpenFiles_AllowedMax) + return S_OK; + /* when we create zip archive: in most cases we need only starting + data of restricted region for rewriting zip's local header. + So here we close latest created volume (from Head), and we try to + keep oldest volumes that will be used for header rewriting later. */ + const int index = Head; + if (index == -1) + return E_FAIL; + PRF(printf("\n== %u, PrepareToOpenNew::CloseStream, NumListItems =%u \n", index, NumListItems)) + /* we don't expect non-restricted stream here in normal cases (if _restrict_Global was not changed). + if there was non-restricted stream, it should be closed before */ + // if (!IsRestricted_for_Close(index)) return CloseStream_and_FinalRename(index); + return CloseStream((unsigned)index); +} + + +HRESULT CMultiOutStream::CreateNewStream(UInt64 newSize) +{ + PRF(printf("\n== %u, CreateNewStream, size =%u \n", Streams.Size(), (unsigned)newSize)) + + if (Streams.Size() >= k_NumVols_MAX) + return E_INVALIDARG; // E_OUTOFMEMORY + + RINOK(PrepareToOpenNew()) + CVolStream s; + s.StreamSpec = new COutFileStream; + s.Stream = s.StreamSpec; + const FString path = GetFilePath(Streams.Size()); + + if (NFind::DoesFileExist_Raw(path)) + return HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS); + if (!CreateTempFile2(path, false, s.Postfix, &s.StreamSpec->File)) + return GetLastError_noZero_HRESULT(); + + s.Start = GetGlobalOffset_for_NewStream(); + s.Pos = 0; + s.RealSize = 0; + + const unsigned index = Streams.Add(s); + InsertToLinkedList(index); + + if (newSize != 0) + return s.SetSize2(newSize); + return S_OK; +} + + +HRESULT CMultiOutStream::CreateStreams_If_Required(unsigned streamIndex) +{ + // UInt64 lastStreamSize = 0; + for (;;) + { + const unsigned numStreamsBefore = Streams.Size(); + if (streamIndex < numStreamsBefore) + return S_OK; + UInt64 newSize; + if (streamIndex == numStreamsBefore) + { + // it's final volume that will be used for real writing. + /* SetSize(_offsetPos) is not required, + because the file Size will be set later by calling Seek() with Write() */ + newSize = 0; // lastStreamSize; + } + else + { + // it's intermediate volume. So we need full volume size + newSize = GetVolSize_for_Stream(numStreamsBefore); + } + + RINOK(CreateNewStream(newSize)) + + // optional check + if (numStreamsBefore + 1 != Streams.Size()) return E_FAIL; + + if (streamIndex != numStreamsBefore) + { + // it's intermediate volume. So we can close it, if it's non-restricted + bool isRestricted; + { + const CVolStream &s = Streams[numStreamsBefore]; + if (newSize == 0) + isRestricted = IsRestricted_Empty(s); + else + isRestricted = IsRestricted(s); + } + if (!isRestricted) + { + RINOK(CloseStream_and_FinalRename(numStreamsBefore)) + } + } + } +} + + +HRESULT CMultiOutStream::ReOpenStream(unsigned streamIndex) +{ + PRF(printf("\n====== %u, ReOpenStream \n", streamIndex)) + RINOK(PrepareToOpenNew()) + CVolStream &s = Streams[streamIndex]; + + FString path = GetFilePath(streamIndex); + path += s.Postfix; + + s.StreamSpec = new COutFileStream; + s.Stream = s.StreamSpec; + s.Pos = 0; + + HRESULT hres; + if (s.StreamSpec->Open_EXISTING(path)) + { + if (s.Postfix.IsEmpty()) + { + /* it's unexpected case that we open finished volume. + It can mean that the code for restriction is incorrect */ + FinalVol_WasReopen = true; + } + UInt64 realSize = 0; + hres = s.StreamSpec->GetSize(&realSize); + if (hres == S_OK) + { + if (realSize == s.RealSize) + { + PRF(printf("\n ReOpenStream OK realSize = %u\n", (unsigned)realSize)) + InsertToLinkedList(streamIndex); + return S_OK; + } + // file size was changed between Close() and ReOpen() + // we must release Stream to be consistent with linked list + hres = E_FAIL; + } + } + else + hres = GetLastError_noZero_HRESULT(); + s.Stream.Release(); + s.StreamSpec = NULL; + return hres; +} + + +/* Sets size of stream, if new size is not equal to old size (RealSize). + If stream was closed and size change is required, it reopens the stream. */ + +HRESULT CMultiOutStream::OptReOpen_and_SetSize(unsigned index, UInt64 size) +{ + CVolStream &s = Streams[index]; + if (size == s.RealSize) + return S_OK; + if (!s.Stream) + { + RINOK(ReOpenStream(index)) + } + PRF(printf("\n== %u, OptReOpen_and_SetSize, size =%u RealSize = %u\n", index, (unsigned)size, (unsigned)s.RealSize)) + // comment it to debug tail after data + return s.SetSize2(size); +} + + +/* +call Normalize_finalMode(false), if _length was changed. + for all streams starting after _length: + - it sets zero size + - it still keeps file open + Note: after _length reducing with CMultiOutStream::SetSize() we can + have very big number of empty streams at the end of Streams[] list. + And Normalize_finalMode() will runs all these empty streams of Streams[] vector. + So it can be ineffective, if we call Normalize_finalMode() many + times after big reducing of (_length). + +call Normalize_finalMode(true) to set final presentations of all streams + for all streams starting after _length: + - it sets zero size + - it removes file + - it removes CVolStream object from Streams[] vector + +Note: we don't remove zero sized first volume, if (_length == 0) +*/ + +HRESULT CMultiOutStream::Normalize_finalMode(bool finalMode) +{ + PRF(printf("\n== Normalize_finalMode: _length =%d \n", (unsigned)_length)) + + unsigned i = Streams.Size(); + + UInt64 offset = 0; + + /* At first we normalize (reduce or increase) the sizes of all existing + streams in Streams[] that can be affected by changed _length. + And we remove tailing zero-size streams, if (finalMode == true) */ + while (i != 0) + { + offset = Streams[--i].Start; // it's last item in Streams[] + // we don't want to remove first volume + if (offset < _length || i == 0) + { + const UInt64 volSize = GetVolSize_for_Stream(i); + UInt64 size = _length - offset; // (size != 0) here + if (size > volSize) + size = volSize; + RINOK(OptReOpen_and_SetSize(i, size)) + if (_length - offset <= volSize) + return S_OK; + // _length - offset > volSize + offset += volSize; + // _length > offset + break; + // UPDATE_HRES(res, OptReOpen_and_SetSize(i, size)); + } + + /* we Set Size of stream to zero even for (finalMode==true), although + that stream will be deleted in next commands */ + // UPDATE_HRES(res, OptReOpen_and_SetSize(i, 0)); + RINOK(OptReOpen_and_SetSize(i, 0)) + if (finalMode) + { + RINOK(CloseStream_and_DeleteFile(i)) + /* CVolStream::Stream was released above already, and it was + removed from linked list. So we don't need to update linked list + structure, when we delete last item in Streams[] */ + Streams.DeleteBack(); + // Delete_LastStream_Records(); + } + } + + /* now we create new zero-filled streams to cover all data up to _length */ + + if (_length == 0) + return S_OK; + + // (offset) is start offset of next stream after existing Streams[] + + for (;;) + { + // _length > offset + const UInt64 volSize = GetVolSize_for_Stream(Streams.Size()); + UInt64 size = _length - offset; // (size != 0) here + if (size > volSize) + size = volSize; + RINOK(CreateNewStream(size)) + if (_length - offset <= volSize) + return S_OK; + // _length - offset > volSize) + offset += volSize; + // _length > offset + } +} + + +HRESULT CMultiOutStream::FinalFlush_and_CloseFiles(unsigned &numTotalVolumesRes) +{ + // at first we remove unused zero-sized streams after _length + HRESULT res = Normalize_finalMode(true); + numTotalVolumesRes = Streams.Size(); + FOR_VECTOR (i, Streams) + { + const HRESULT res2 = CloseStream_and_FinalRename(i); + if (res == S_OK) + res = res2; + } + if (NumListItems != 0 && res == S_OK) + res = E_FAIL; + return res; +} + + +bool CMultiOutStream::SetMTime_Final(const CFiTime &mTime) +{ + // we will set mtime only if new value differs from previous + if (!FinalVol_WasReopen && MTime_Defined && Compare_FiTime(&MTime, &mTime) == 0) + return true; + bool res = true; + FOR_VECTOR (i, Streams) + { + CVolStream &s = Streams[i]; + if (s.Stream) + { + if (!s.StreamSpec->SetMTime(&mTime)) + res = false; + } + else + { + if (!SetDirTime(GetFilePath(i), NULL, NULL, &mTime)) + res = false; + } + } + return res; +} + + +Z7_COM7F_IMF(CMultiOutStream::SetSize(UInt64 newSize)) +{ + COM_TRY_BEGIN + if ((Int64)newSize < 0) + return HRESULT_WIN32_ERROR_NEGATIVE_SEEK; + if (newSize > _absLimit) + { + /* big seek value was sent to SetSize() or to Seek()+Write(). + It can mean one of two situations: + 1) some incorrect code called it with big seek value. + 2) volume size was small, and we have too big number of volumes + */ + /* in Windows SetEndOfFile() can return: + ERROR_NEGATIVE_SEEK: for >= (1 << 63) + ERROR_INVALID_PARAMETER: for > (16 TiB - 64 KiB) + ERROR_DISK_FULL: for <= (16 TiB - 64 KiB) + */ + // return E_FAIL; + // return E_OUTOFMEMORY; + return E_INVALIDARG; + } + + if (newSize > _length) + { + // we don't expect such case. So we just define global restriction */ + _restrict_Global = newSize; + } + else if (newSize < _restrict_Global) + _restrict_Global = newSize; + + PRF(printf("\n== CMultiOutStream::SetSize, size =%u \n", (unsigned)newSize)) + + _length = newSize; + return Normalize_finalMode(false); + + COM_TRY_END +} + + +Z7_COM7F_IMF(CMultiOutStream::Write(const void *data, UInt32 size, UInt32 *processedSize)) +{ + COM_TRY_BEGIN + if (processedSize) + *processedSize = 0; + if (size == 0) + return S_OK; + + PRF(printf("\n -- CMultiOutStream::Write() : _absPos = %6u, size =%6u \n", + (unsigned)_absPos, (unsigned)size)) + + if (_absPos > _length) + { + // it create data only up to _absPos. + // but we still can need additional new streams, if _absPos at range of volume + RINOK(SetSize(_absPos)) + } + + while (size != 0) + { + UInt64 volSize; + { + if (_streamIndex < Sizes.Size() - 1) + { + volSize = Sizes[_streamIndex]; + if (_offsetPos >= volSize) + { + _offsetPos -= volSize; + _streamIndex++; + continue; + } + } + else + { + volSize = Sizes[Sizes.Size() - 1]; + if (_offsetPos >= volSize) + { + const UInt64 v = _offsetPos / volSize; + if (v >= ((UInt32)(Int32)-1) - _streamIndex) + return E_INVALIDARG; + // throw 202208; + _streamIndex += (unsigned)v; + _offsetPos -= (unsigned)v * volSize; + } + if (_streamIndex >= k_NumVols_MAX) + return E_INVALIDARG; + } + } + + // (_offsetPos < volSize) here + + /* we can need to create one or more streams here, + vol_size for some streams is allowed to be 0. + Also we close some new created streams, if they are non-restricted */ + // file Size will be set later by calling Seek() with Write() + + /* the case (_absPos > _length) was processed above with SetSize(_absPos), + so here it's expected. that we can create optional zero-size streams and then _streamIndex */ + RINOK(CreateStreams_If_Required(_streamIndex)) + + CVolStream &s = Streams[_streamIndex]; + + PRF(printf("\n%d, == Write : Pos = %u, RealSize = %u size =%u \n", + _streamIndex, (unsigned)s.Pos, (unsigned)s.RealSize, size)) + + if (!s.Stream) + { + RINOK(ReOpenStream(_streamIndex)) + } + if (_offsetPos != s.Pos) + { + RINOK(s.Stream->Seek((Int64)_offsetPos, STREAM_SEEK_SET, NULL)) + s.Pos = _offsetPos; + } + + UInt32 curSize = size; + { + const UInt64 rem = volSize - _offsetPos; + if (curSize > rem) + curSize = (UInt32)rem; + } + // curSize != 0 + UInt32 realProcessed = 0; + + HRESULT hres = s.Stream->Write(data, curSize, &realProcessed); + + data = (const void *)((const Byte *)data + realProcessed); + size -= realProcessed; + s.Pos += realProcessed; + _offsetPos += realProcessed; + _absPos += realProcessed; + if (_length < _absPos) + _length = _absPos; + if (s.RealSize < _offsetPos) + s.RealSize = _offsetPos; + if (processedSize) + *processedSize += realProcessed; + + if (s.Pos == volSize) + { + bool isRestricted; + if (volSize == 0) + isRestricted = IsRestricted_Empty(s); + else + isRestricted = IsRestricted(s); + if (!isRestricted) + { + const HRESULT res2 = CloseStream_and_FinalRename(_streamIndex); + if (hres == S_OK) + hres = res2; + } + _streamIndex++; + _offsetPos = 0; + } + + RINOK(hres) + if (realProcessed == 0 && curSize != 0) + return E_FAIL; + // break; + } + return S_OK; + COM_TRY_END +} + + +Z7_COM7F_IMF(CMultiOutStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition)) +{ + PRF(printf("\n-- CMultiOutStream::Seek seekOrigin=%u Seek =%u\n", seekOrigin, (unsigned)offset)) + + switch (seekOrigin) + { + case STREAM_SEEK_SET: break; + case STREAM_SEEK_CUR: offset += _absPos; break; + case STREAM_SEEK_END: offset += _length; break; + default: return STG_E_INVALIDFUNCTION; + } + if (offset < 0) + return HRESULT_WIN32_ERROR_NEGATIVE_SEEK; + if ((UInt64)offset != _absPos) + { + _absPos = (UInt64)offset; + _offsetPos = (UInt64)offset; + _streamIndex = 0; + } + if (newPosition) + *newPosition = (UInt64)offset; + return S_OK; +} + + +// result value will be saturated to (UInt32)(Int32)-1 + +unsigned CMultiOutStream::GetStreamIndex_for_Offset(UInt64 offset, UInt64 &relOffset) const +{ + const unsigned last = Sizes.Size() - 1; + for (unsigned i = 0; i < last; i++) + { + const UInt64 size = Sizes[i]; + if (offset < size) + { + relOffset = offset; + return i; + } + offset -= size; + } + const UInt64 size = Sizes[last]; + const UInt64 v = offset / size; + if (v >= ((UInt32)(Int32)-1) - last) + return (unsigned)(int)-1; // saturation + relOffset = offset - (unsigned)v * size; + return last + (unsigned)(v); +} + + +Z7_COM7F_IMF(CMultiOutStream::SetRestriction(UInt64 begin, UInt64 end)) +{ + COM_TRY_BEGIN + + // begin = end = 0; // for debug + + PRF(printf("\n==================== CMultiOutStream::SetRestriction %u, %u\n", (unsigned)begin, (unsigned)end)) + if (begin > end) + { + // these value are FAILED values. + return E_FAIL; + // return E_INVALIDARG; + /* + // or we can ignore error with 3 ways: no change, non-restricted, saturation: + end = begin; // non-restricted + end = (UInt64)(Int64)-1; // saturation: + return S_OK; + */ + } + UInt64 b = _restrict_Begin; + UInt64 e = _restrict_End; + _restrict_Begin = begin; + _restrict_End = end; + + if (b == e) // if there were no restriction before + return S_OK; // no work to derestrict now. + + /* [b, e) is previous restricted region. So all volumes that + intersect that [b, e) region are candidats for derestriction */ + + if (begin != end) // if there is new non-empty restricted region + { + /* Now we will try to reduce or change (b) and (e) bounds + to reduce main loop that checks volumes for derestriction. + We still use one big derestriction region in main loop, although + in some cases we could have two smaller derestriction regions. + Also usually restriction region cannot move back from previous start position, + so (b <= begin) is expected here for normal cases */ + if (b == begin) // if same low bounds + b = end; // we need to derestrict only after the end of new restricted region + if (e == end) // if same high bounds + e = begin; // we need to derestrict only before the begin of new restricted region + } + + if (b > e) // || b == (UInt64)(Int64)-1 + return S_OK; + + /* Here we close finished volumes that are not restricted anymore. + We close (low number) volumes at first. */ + + UInt64 offset; + unsigned index = GetStreamIndex_for_Offset(b, offset); + + for (; index < Streams.Size(); index++) + { + { + const CVolStream &s = Streams[index]; + if (_length <= s.Start) + break; // we don't close streams after _length + // (_length > s.Start) + const UInt64 volSize = GetVolSize_for_Stream(index); + if (volSize == 0) + { + if (e < s.Start) + break; + // we don't close empty stream, if next byte [s.Start, s.Start] is restricted + if (IsRestricted_Empty(s)) + continue; + } + else + { + if (e <= s.Start) + break; + // we don't close non full streams + if (_length - s.Start < volSize) + break; + // (volSize == s.RealSize) is expected here. So no need to check it + // if (volSize != s.RealSize) break; + if (IsRestricted(s)) + continue; + } + } + RINOK(CloseStream_and_FinalRename(index)) + } + + return S_OK; + COM_TRY_END +} diff --git a/src/plugins/7zip/7za/cpp/7zip/Common/MultiOutStream.h b/src/plugins/7zip/7za/cpp/7zip/Common/MultiOutStream.h new file mode 100644 index 00000000..2fb78112 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Common/MultiOutStream.h @@ -0,0 +1,160 @@ +// MultiOutStream.h + +#ifndef ZIP7_INC_MULTI_OUT_STREAM_H +#define ZIP7_INC_MULTI_OUT_STREAM_H + +#include "FileStreams.h" + +Z7_CLASS_IMP_COM_2( + CMultiOutStream + , IOutStream + , IStreamSetRestriction +) + Z7_IFACE_COM7_IMP(ISequentialOutStream) + + Z7_CLASS_NO_COPY(CMultiOutStream) + + struct CVolStream + { + COutFileStream *StreamSpec; + CMyComPtr Stream; + UInt64 Start; // start pos of current Stream in global stream + UInt64 Pos; // pos in current Stream + UInt64 RealSize; + int Next; // next older + int Prev; // prev newer + AString Postfix; + + HRESULT SetSize2(UInt64 size) + { + const HRESULT res = Stream->SetSize(size); + if (res == SZ_OK) + RealSize = size; + return res; + } + }; + + unsigned _streamIndex; // (_streamIndex >= Stream.Size()) is allowed in some internal code + UInt64 _offsetPos; // offset relative to Streams[_streamIndex] volume. (_offsetPos >= volSize is allowed) + UInt64 _absPos; + UInt64 _length; // virtual Length + UInt64 _absLimit; + + CObjectVector Streams; + CRecordVector Sizes; + + UInt64 _restrict_Begin; + UInt64 _restrict_End; + UInt64 _restrict_Global; + + unsigned NumOpenFiles_AllowedMax; + + // ----- Double Linked List ----- + + unsigned NumListItems; + int Head; // newest + int Tail; // oldest + + void InitLinkedList() + { + Head = -1; + Tail = -1; + NumListItems = 0; + } + + void InsertToLinkedList(unsigned index) + { + { + CVolStream &node = Streams[index]; + node.Next = Head; + node.Prev = -1; + } + if (Head != -1) + Streams[(unsigned)Head].Prev = (int)index; + else + { + // if (Tail != -1) throw 1; + Tail = (int)index; + } + Head = (int)index; + NumListItems++; + } + + void RemoveFromLinkedList(unsigned index) + { + CVolStream &s = Streams[index]; + if (s.Next != -1) Streams[(unsigned)s.Next].Prev = s.Prev; else Tail = s.Prev; + if (s.Prev != -1) Streams[(unsigned)s.Prev].Next = s.Next; else Head = s.Next; + s.Next = -1; // optional + s.Prev = -1; // optional + NumListItems--; + } + + /* + void Delete_LastStream_Records() + { + if (Streams.Back().Stream) + RemoveFromLinkedList(Streams.Size() - 1); + Streams.DeleteBack(); + } + */ + + UInt64 GetVolSize_for_Stream(unsigned i) const + { + const unsigned last = Sizes.Size() - 1; + return Sizes[i < last ? i : last]; + } + UInt64 GetGlobalOffset_for_NewStream() const + { + return Streams.Size() == 0 ? 0: + Streams.Back().Start + + GetVolSize_for_Stream(Streams.Size() - 1); + } + unsigned GetStreamIndex_for_Offset(UInt64 offset, UInt64 &relOffset) const; + bool IsRestricted(const CVolStream &s) const; + bool IsRestricted_Empty(const CVolStream &s) const + { + // (s) must be stream that has (VolSize == 0). + // we treat empty stream as restricted, if next byte is restricted. + if (s.Start < _restrict_Global) + return true; + return + (_restrict_Begin != _restrict_End) + && (_restrict_Begin <= s.Start) + && (_restrict_Begin == s.Start || _restrict_End > s.Start); + } + // bool IsRestricted_for_Close(unsigned index) const; + FString GetFilePath(unsigned index); + + HRESULT CloseStream(unsigned index); + HRESULT CloseStream_and_DeleteFile(unsigned index); + HRESULT CloseStream_and_FinalRename(unsigned index); + + HRESULT PrepareToOpenNew(); + HRESULT CreateNewStream(UInt64 newSize); + HRESULT CreateStreams_If_Required(unsigned streamIndex); + HRESULT ReOpenStream(unsigned streamIndex); + HRESULT OptReOpen_and_SetSize(unsigned index, UInt64 size); + + HRESULT Normalize_finalMode(bool finalMode); +public: + FString Prefix; + CFiTime MTime; + bool MTime_Defined; + bool FinalVol_WasReopen; + bool NeedDelete; + + CMultiOutStream() {} + ~CMultiOutStream(); + void Init(const CRecordVector &sizes); + bool SetMTime_Final(const CFiTime &mTime); + UInt64 GetSize() const { return _length; } + /* it makes final flushing, closes open files and renames to final name if required + but it still keeps Streams array of all closed files. + So we still can delete all files later, if required */ + HRESULT FinalFlush_and_CloseFiles(unsigned &numTotalVolumesRes); + // Destruct object without exceptions + HRESULT Destruct(); +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Common/OffsetStream.cpp b/src/plugins/7zip/7za/cpp/7zip/Common/OffsetStream.cpp index 368d39b6..a6b005e4 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Common/OffsetStream.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Common/OffsetStream.cpp @@ -2,38 +2,36 @@ #include "StdAfx.h" -#include "../../Common/Defs.h" - #include "OffsetStream.h" HRESULT COffsetOutStream::Init(IOutStream *stream, UInt64 offset) { _offset = offset; _stream = stream; - return _stream->Seek(offset, STREAM_SEEK_SET, NULL); + return _stream->Seek((Int64)offset, STREAM_SEEK_SET, NULL); } -STDMETHODIMP COffsetOutStream::Write(const void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(COffsetOutStream::Write(const void *data, UInt32 size, UInt32 *processedSize)) { return _stream->Write(data, size, processedSize); } -STDMETHODIMP COffsetOutStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition) +Z7_COM7F_IMF(COffsetOutStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition)) { - UInt64 absoluteNewPosition; if (seekOrigin == STREAM_SEEK_SET) { if (offset < 0) return HRESULT_WIN32_ERROR_NEGATIVE_SEEK; offset += _offset; } - HRESULT result = _stream->Seek(offset, seekOrigin, &absoluteNewPosition); + UInt64 absoluteNewPosition = 0; // =0 for gcc-10 + const HRESULT result = _stream->Seek(offset, seekOrigin, &absoluteNewPosition); if (newPosition) *newPosition = absoluteNewPosition - _offset; return result; } -STDMETHODIMP COffsetOutStream::SetSize(UInt64 newSize) +Z7_COM7F_IMF(COffsetOutStream::SetSize(UInt64 newSize)) { return _stream->SetSize(_offset + newSize); } diff --git a/src/plugins/7zip/7za/cpp/7zip/Common/OffsetStream.h b/src/plugins/7zip/7za/cpp/7zip/Common/OffsetStream.h index 9074a24e..9bd554cc 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Common/OffsetStream.h +++ b/src/plugins/7zip/7za/cpp/7zip/Common/OffsetStream.h @@ -1,26 +1,22 @@ // OffsetStream.h -#ifndef __OFFSET_STREAM_H -#define __OFFSET_STREAM_H +#ifndef ZIP7_INC_OFFSET_STREAM_H +#define ZIP7_INC_OFFSET_STREAM_H #include "../../Common/MyCom.h" #include "../IStream.h" -class COffsetOutStream: - public IOutStream, - public CMyUnknownImp -{ - UInt64 _offset; +Z7_CLASS_IMP_NOQIB_1( + COffsetOutStream + , IOutStream +) + Z7_IFACE_COM7_IMP(ISequentialOutStream) + CMyComPtr _stream; + UInt64 _offset; public: HRESULT Init(IOutStream *stream, UInt64 offset); - - MY_UNKNOWN_IMP - - STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize); - STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition); - STDMETHOD(SetSize)(UInt64 newSize); }; #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Common/OutBuffer.cpp b/src/plugins/7zip/7za/cpp/7zip/Common/OutBuffer.cpp index 4ba34a05..197b3767 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Common/OutBuffer.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Common/OutBuffer.cpp @@ -11,18 +11,18 @@ bool COutBuffer::Create(UInt32 bufSize) throw() const UInt32 kMinBlockSize = 1; if (bufSize < kMinBlockSize) bufSize = kMinBlockSize; - if (_buf != 0 && _bufSize == bufSize) + if (_buf && _bufSize == bufSize) return true; Free(); _bufSize = bufSize; _buf = (Byte *)::MidAlloc(bufSize); - return (_buf != 0); + return (_buf != NULL); } void COutBuffer::Free() throw() { ::MidFree(_buf); - _buf = 0; + _buf = NULL; } void COutBuffer::Init() throw() @@ -32,7 +32,7 @@ void COutBuffer::Init() throw() _pos = 0; _processedSize = 0; _overDict = false; - #ifdef _NO_EXCEPTIONS + #ifdef Z7_NO_EXCEPTIONS ErrorCode = S_OK; #endif } @@ -51,17 +51,17 @@ HRESULT COutBuffer::FlushPart() throw() // _streamPos < _bufSize UInt32 size = (_streamPos >= _pos) ? (_bufSize - _streamPos) : (_pos - _streamPos); HRESULT result = S_OK; - #ifdef _NO_EXCEPTIONS + #ifdef Z7_NO_EXCEPTIONS result = ErrorCode; #endif - if (_buf2 != 0) + if (_buf2) { memcpy(_buf2, _buf + _streamPos, size); _buf2 += size; } - if (_stream != 0 - #ifdef _NO_EXCEPTIONS + if (_stream + #ifdef Z7_NO_EXCEPTIONS && (ErrorCode == S_OK) #endif ) @@ -85,14 +85,14 @@ HRESULT COutBuffer::FlushPart() throw() HRESULT COutBuffer::Flush() throw() { - #ifdef _NO_EXCEPTIONS + #ifdef Z7_NO_EXCEPTIONS if (ErrorCode != S_OK) return ErrorCode; #endif while (_streamPos != _pos) { - HRESULT result = FlushPart(); + const HRESULT result = FlushPart(); if (result != S_OK) return result; } @@ -101,8 +101,8 @@ HRESULT COutBuffer::Flush() throw() void COutBuffer::FlushWithCheck() { - HRESULT result = Flush(); - #ifdef _NO_EXCEPTIONS + const HRESULT result = Flush(); + #ifdef Z7_NO_EXCEPTIONS ErrorCode = result; #else if (result != S_OK) diff --git a/src/plugins/7zip/7za/cpp/7zip/Common/OutBuffer.h b/src/plugins/7zip/7za/cpp/7zip/Common/OutBuffer.h index 3bdfb87c..af78c4f0 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Common/OutBuffer.h +++ b/src/plugins/7zip/7za/cpp/7zip/Common/OutBuffer.h @@ -1,13 +1,13 @@ // OutBuffer.h -#ifndef __OUT_BUFFER_H -#define __OUT_BUFFER_H +#ifndef ZIP7_INC_OUT_BUFFER_H +#define ZIP7_INC_OUT_BUFFER_H #include "../IStream.h" #include "../../Common/MyCom.h" #include "../../Common/MyException.h" -#ifndef _NO_EXCEPTIONS +#ifndef Z7_NO_EXCEPTIONS struct COutBufferException: public CSystemException { COutBufferException(HRESULT errorCode): CSystemException(errorCode) {} @@ -29,11 +29,11 @@ class COutBuffer HRESULT FlushPart() throw(); public: - #ifdef _NO_EXCEPTIONS + #ifdef Z7_NO_EXCEPTIONS HRESULT ErrorCode; #endif - COutBuffer(): _buf(0), _pos(0), _stream(0), _buf2(0) {} + COutBuffer(): _buf(NULL), _pos(0), _stream(NULL), _buf2(NULL) {} ~COutBuffer() { Free(); } bool Create(UInt32 bufSize) throw(); @@ -45,17 +45,87 @@ class COutBuffer HRESULT Flush() throw(); void FlushWithCheck(); + Z7_FORCE_INLINE void WriteByte(Byte b) { - _buf[_pos++] = b; - if (_pos == _limitPos) + UInt32 pos = _pos; + _buf[pos] = b; + pos++; + _pos = pos; + if (pos == _limitPos) FlushWithCheck(); } + void WriteBytes(const void *data, size_t size) { - for (size_t i = 0; i < size; i++) - WriteByte(((const Byte *)data)[i]); + while (size) + { + UInt32 pos = _pos; + size_t cur = (size_t)(_limitPos - pos); + if (cur >= size) + cur = size; + size -= cur; + Byte *dest = _buf + pos; + pos += (UInt32)cur; + _pos = pos; +#if 0 + memcpy(dest, data, cur); + data = (const void *)((const Byte *)data + cur); +#else + const Byte * const lim = (const Byte *)data + cur; + do + { + *dest++ = *(const Byte *)data; + data = (const void *)((const Byte *)data + 1); + } + while (data != lim); +#endif + if (pos == _limitPos) + FlushWithCheck(); + } + } + + Byte *GetOutBuffer(size_t &avail) + { + const UInt32 pos = _pos; + avail = (size_t)(_limitPos - pos); + return _buf + pos; + } + + void SkipWrittenBytes(size_t num) + { + const UInt32 pos = _pos; + const UInt32 rem = _limitPos - pos; + if (rem > num) + { + _pos = pos + (UInt32)num; + return; + } + // (rem <= num) + // the caller must not call it with (rem < num) + // so (rem == num) + _pos = _limitPos; + FlushWithCheck(); + } + /* + void WriteBytesBig(const void *data, size_t size) + { + while (size) + { + UInt32 pos = _pos; + UInt32 rem = _limitPos - pos; + if (rem > size) + { + _pos = pos + size; + memcpy(_buf + pos, data, size); + return; + } + memcpy(_buf + pos, data, rem); + _pos = pos + rem; + FlushWithCheck(); + } } + */ UInt64 GetProcessedSize() const throw(); }; diff --git a/src/plugins/7zip/7za/cpp/7zip/Common/OutMemStream.cpp b/src/plugins/7zip/7za/cpp/7zip/Common/OutMemStream.cpp index 768c2d45..29a23941 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Common/OutMemStream.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Common/OutMemStream.cpp @@ -2,6 +2,8 @@ #include "StdAfx.h" +// #include + #include "OutMemStream.h" void COutMemStream::Free() @@ -29,16 +31,17 @@ void COutMemStream::DetachData(CMemLockBlocks &blocks) HRESULT COutMemStream::WriteToRealStream() { - RINOK(Blocks.WriteToStream(_memManager->GetBlockSize(), OutSeqStream)); + RINOK(Blocks.WriteToStream(_memManager->GetBlockSize(), OutSeqStream)) Blocks.Free(_memManager); return S_OK; } -STDMETHODIMP COutMemStream::Write(const void *data, UInt32 size, UInt32 *processedSize) + +Z7_COM7F_IMF(COutMemStream::Write(const void *data, UInt32 size, UInt32 *processedSize)) { if (_realStreamMode) return OutSeqStream->Write(data, size, processedSize); - if (processedSize != 0) + if (processedSize) *processedSize = 0; while (size != 0) { @@ -49,13 +52,13 @@ STDMETHODIMP COutMemStream::Write(const void *data, UInt32 size, UInt32 *process if (size < curSize) curSize = size; memcpy(p, data, curSize); - if (processedSize != 0) + if (processedSize) *processedSize += (UInt32)curSize; data = (const void *)((const Byte *)data + curSize); size -= (UInt32)curSize; _curBlockPos += curSize; - UInt64 pos64 = GetPos(); + const UInt64 pos64 = GetPos(); if (pos64 > Blocks.TotalSize) Blocks.TotalSize = pos64; if (_curBlockPos == _memManager->GetBlockSize()) @@ -65,8 +68,14 @@ STDMETHODIMP COutMemStream::Write(const void *data, UInt32 size, UInt32 *process } continue; } - HANDLE events[3] = { StopWritingEvent, WriteToRealStreamEvent, /* NoLockEvent, */ _memManager->Semaphore }; - DWORD waitResult = ::WaitForMultipleObjects((Blocks.LockMode ? 3 : 2), events, FALSE, INFINITE); + + const NWindows::NSynchronization::CHandle_WFMO events[3] = + { StopWritingEvent, WriteToRealStreamEvent, /* NoLockEvent, */ _memManager->Semaphore }; + const DWORD waitResult = NWindows::NSynchronization::WaitForMultiObj_Any_Infinite( + ((Blocks.LockMode /* && _memManager->Semaphore.IsCreated() */) ? 3 : 2), events); + + // printf("\n 1- outMemStream %d\n", waitResult - WAIT_OBJECT_0); + switch (waitResult) { case (WAIT_OBJECT_0 + 0): @@ -74,35 +83,42 @@ STDMETHODIMP COutMemStream::Write(const void *data, UInt32 size, UInt32 *process case (WAIT_OBJECT_0 + 1): { _realStreamMode = true; - RINOK(WriteToRealStream()); + RINOK(WriteToRealStream()) UInt32 processedSize2; - HRESULT res = OutSeqStream->Write(data, size, &processedSize2); - if (processedSize != 0) + const HRESULT res = OutSeqStream->Write(data, size, &processedSize2); + if (processedSize) *processedSize += processedSize2; return res; } - /* case (WAIT_OBJECT_0 + 2): { // it has bug: no write. + /* if (!Blocks.SwitchToNoLockMode(_memManager)) return E_FAIL; + */ break; } - */ - case (WAIT_OBJECT_0 + 2): - break; default: + { + if (waitResult == WAIT_FAILED) + { + const DWORD res = ::GetLastError(); + if (res != 0) + return HRESULT_FROM_WIN32(res); + } return E_FAIL; + } } - Blocks.Blocks.Add(_memManager->AllocateBlock()); - if (Blocks.Blocks.Back() == 0) + void *p = _memManager->AllocateBlock(); + if (!p) return E_FAIL; + Blocks.Blocks.Add(p); } return S_OK; } -STDMETHODIMP COutMemStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition) +Z7_COM7F_IMF(COutMemStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition)) { if (_realStreamMode) { @@ -129,7 +145,7 @@ STDMETHODIMP COutMemStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPos return S_OK; } -STDMETHODIMP COutMemStream::SetSize(UInt64 newSize) +Z7_COM7F_IMF(COutMemStream::SetSize(UInt64 newSize)) { if (_realStreamMode) { diff --git a/src/plugins/7zip/7za/cpp/7zip/Common/OutMemStream.h b/src/plugins/7zip/7za/cpp/7zip/Common/OutMemStream.h index 0a892c52..a58fb91b 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Common/OutMemStream.h +++ b/src/plugins/7zip/7za/cpp/7zip/Common/OutMemStream.h @@ -1,40 +1,44 @@ // OutMemStream.h -#ifndef __OUT_MEM_STREAM_H -#define __OUT_MEM_STREAM_H +#ifndef ZIP7_INC_OUT_MEM_STREAM_H +#define ZIP7_INC_OUT_MEM_STREAM_H #include "../../Common/MyCom.h" #include "MemBlocks.h" -class COutMemStream: - public IOutStream, - public CMyUnknownImp -{ +Z7_CLASS_IMP_NOQIB_1( + COutMemStream + , IOutStream +) + Z7_IFACE_COM7_IMP(ISequentialOutStream) + CMemBlockManagerMt *_memManager; - unsigned _curBlockIndex; size_t _curBlockPos; + unsigned _curBlockIndex; bool _realStreamMode; bool _unlockEventWasSent; - NWindows::NSynchronization::CAutoResetEvent StopWritingEvent; - NWindows::NSynchronization::CAutoResetEvent WriteToRealStreamEvent; + NWindows::NSynchronization::CAutoResetEvent_WFMO StopWritingEvent; + NWindows::NSynchronization::CAutoResetEvent_WFMO WriteToRealStreamEvent; // NWindows::NSynchronization::CAutoResetEvent NoLockEvent; HRESULT StopWriteResult; CMemLockBlocks Blocks; - UInt64 GetPos() const { return (UInt64)_curBlockIndex * _memManager->GetBlockSize() + _curBlockPos; } - CMyComPtr OutSeqStream; CMyComPtr OutStream; + UInt64 GetPos() const { return (UInt64)_curBlockIndex * _memManager->GetBlockSize() + _curBlockPos; } + public: - HRes CreateEvents() + HRESULT CreateEvents(SYNC_PARAM_DECL(synchro)) { - RINOK(StopWritingEvent.CreateIfNotCreated()); - return WriteToRealStreamEvent.CreateIfNotCreated(); + WRes wres = StopWritingEvent.CreateIfNotCreated_Reset(SYNC_WFMO(synchro)); + if (wres == 0) + wres = WriteToRealStreamEvent.CreateIfNotCreated_Reset(SYNC_WFMO(synchro)); + return HRESULT_FROM_WIN32(wres); } void SetOutStream(IOutStream *outStream) @@ -55,7 +59,16 @@ class COutMemStream: OutSeqStream.Release(); } - COutMemStream(CMemBlockManagerMt *memManager): _memManager(memManager) { } + COutMemStream(CMemBlockManagerMt *memManager): + _memManager(memManager) + { + /* + #ifndef _WIN32 + StopWritingEvent._sync = + WriteToRealStreamEvent._sync = &memManager->Synchro; + #endif + */ + } ~COutMemStream() { Free(); } void Free(); @@ -86,12 +99,6 @@ class COutMemStream: StopWriteResult = res; StopWritingEvent.Set(); } - - MY_UNKNOWN_IMP - - STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize); - STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition); - STDMETHOD(SetSize)(UInt64 newSize); }; #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Common/ProgressMt.cpp b/src/plugins/7zip/7za/cpp/7zip/Common/ProgressMt.cpp index 319bd241..ee21ab3e 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Common/ProgressMt.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Common/ProgressMt.cpp @@ -4,12 +4,12 @@ #include "ProgressMt.h" -void CMtCompressProgressMixer::Init(int numItems, ICompressProgressInfo *progress) +void CMtCompressProgressMixer::Init(unsigned numItems, ICompressProgressInfo *progress) { NWindows::NSynchronization::CCriticalSectionLock lock(CriticalSection); InSizes.Clear(); OutSizes.Clear(); - for (int i = 0; i < numItems; i++) + for (unsigned i = 0; i < numItems; i++) { InSizes.Add(0); OutSizes.Add(0); @@ -19,25 +19,25 @@ void CMtCompressProgressMixer::Init(int numItems, ICompressProgressInfo *progres _progress = progress; } -void CMtCompressProgressMixer::Reinit(int index) +void CMtCompressProgressMixer::Reinit(unsigned index) { NWindows::NSynchronization::CCriticalSectionLock lock(CriticalSection); InSizes[index] = 0; OutSizes[index] = 0; } -HRESULT CMtCompressProgressMixer::SetRatioInfo(int index, const UInt64 *inSize, const UInt64 *outSize) +HRESULT CMtCompressProgressMixer::SetRatioInfo(unsigned index, const UInt64 *inSize, const UInt64 *outSize) { NWindows::NSynchronization::CCriticalSectionLock lock(CriticalSection); - if (inSize != 0) + if (inSize) { - UInt64 diff = *inSize - InSizes[index]; + const UInt64 diff = *inSize - InSizes[index]; InSizes[index] = *inSize; TotalInSize += diff; } - if (outSize != 0) + if (outSize) { - UInt64 diff = *outSize - OutSizes[index]; + const UInt64 diff = *outSize - OutSizes[index]; OutSizes[index] = *outSize; TotalOutSize += diff; } @@ -47,7 +47,7 @@ HRESULT CMtCompressProgressMixer::SetRatioInfo(int index, const UInt64 *inSize, } -STDMETHODIMP CMtCompressProgress::SetRatioInfo(const UInt64 *inSize, const UInt64 *outSize) +Z7_COM7F_IMF(CMtCompressProgress::SetRatioInfo(const UInt64 *inSize, const UInt64 *outSize)) { return _progress->SetRatioInfo(_index, inSize, outSize); } diff --git a/src/plugins/7zip/7za/cpp/7zip/Common/ProgressMt.h b/src/plugins/7zip/7za/cpp/7zip/Common/ProgressMt.h index 26079d4e..78c806c5 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Common/ProgressMt.h +++ b/src/plugins/7zip/7za/cpp/7zip/Common/ProgressMt.h @@ -1,7 +1,7 @@ // ProgressMt.h -#ifndef __PROGRESSMT_H -#define __PROGRESSMT_H +#ifndef ZIP7_INC_PROGRESSMT_H +#define ZIP7_INC_PROGRESSMT_H #include "../../Common/MyCom.h" #include "../../Common/MyVector.h" @@ -19,28 +19,25 @@ class CMtCompressProgressMixer UInt64 TotalOutSize; public: NWindows::NSynchronization::CCriticalSection CriticalSection; - void Init(int numItems, ICompressProgressInfo *progress); - void Reinit(int index); - HRESULT SetRatioInfo(int index, const UInt64 *inSize, const UInt64 *outSize); + void Init(unsigned numItems, ICompressProgressInfo *progress); + void Reinit(unsigned index); + HRESULT SetRatioInfo(unsigned index, const UInt64 *inSize, const UInt64 *outSize); }; -class CMtCompressProgress: - public ICompressProgressInfo, - public CMyUnknownImp -{ + +Z7_CLASS_IMP_NOQIB_1( + CMtCompressProgress + , ICompressProgressInfo +) + unsigned _index; CMtCompressProgressMixer *_progress; - int _index; public: - void Init(CMtCompressProgressMixer *progress, int index) + void Init(CMtCompressProgressMixer *progress, unsigned index) { _progress = progress; _index = index; } void Reinit() { _progress->Reinit(_index); } - - MY_UNKNOWN_IMP - - STDMETHOD(SetRatioInfo)(const UInt64 *inSize, const UInt64 *outSize); }; #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Common/ProgressUtils.cpp b/src/plugins/7zip/7za/cpp/7zip/Common/ProgressUtils.cpp index 41385ccb..fb81f29b 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Common/ProgressUtils.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Common/ProgressUtils.cpp @@ -5,11 +5,11 @@ #include "ProgressUtils.h" CLocalProgress::CLocalProgress(): + SendRatio(true), + SendProgress(true), ProgressOffset(0), InSize(0), - OutSize(0), - SendRatio(true), - SendProgress(true) + OutSize(0) {} void CLocalProgress::Init(IProgress *progress, bool inSizeIsMain) @@ -20,7 +20,7 @@ void CLocalProgress::Init(IProgress *progress, bool inSizeIsMain) _inSizeIsMain = inSizeIsMain; } -STDMETHODIMP CLocalProgress::SetRatioInfo(const UInt64 *inSize, const UInt64 *outSize) +Z7_COM7F_IMF(CLocalProgress::SetRatioInfo(const UInt64 *inSize, const UInt64 *outSize)) { UInt64 inSize2 = InSize; UInt64 outSize2 = OutSize; @@ -32,7 +32,7 @@ STDMETHODIMP CLocalProgress::SetRatioInfo(const UInt64 *inSize, const UInt64 *ou if (SendRatio && _ratioProgress) { - RINOK(_ratioProgress->SetRatioInfo(&inSize2, &outSize2)); + RINOK(_ratioProgress->SetRatioInfo(&inSize2, &outSize2)) } if (SendProgress) diff --git a/src/plugins/7zip/7za/cpp/7zip/Common/ProgressUtils.h b/src/plugins/7zip/7za/cpp/7zip/Common/ProgressUtils.h index e94265ba..dad5fccf 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Common/ProgressUtils.h +++ b/src/plugins/7zip/7za/cpp/7zip/Common/ProgressUtils.h @@ -1,35 +1,33 @@ // ProgressUtils.h -#ifndef __PROGRESS_UTILS_H -#define __PROGRESS_UTILS_H +#ifndef ZIP7_INC_PROGRESS_UTILS_H +#define ZIP7_INC_PROGRESS_UTILS_H #include "../../Common/MyCom.h" #include "../ICoder.h" #include "../IProgress.h" -class CLocalProgress: - public ICompressProgressInfo, - public CMyUnknownImp -{ +Z7_CLASS_IMP_COM_1( + CLocalProgress + , ICompressProgressInfo +) +public: + bool SendRatio; + bool SendProgress; +private: + bool _inSizeIsMain; CMyComPtr _progress; CMyComPtr _ratioProgress; - bool _inSizeIsMain; public: UInt64 ProgressOffset; UInt64 InSize; UInt64 OutSize; - bool SendRatio; - bool SendProgress; CLocalProgress(); void Init(IProgress *progress, bool inSizeIsMain); HRESULT SetCur(); - - MY_UNKNOWN_IMP1(ICompressProgressInfo) - - STDMETHOD(SetRatioInfo)(const UInt64 *inSize, const UInt64 *outSize); }; #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Common/PropId.cpp b/src/plugins/7zip/7za/cpp/7zip/Common/PropId.cpp index 11d20d55..b00ad813 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Common/PropId.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Common/PropId.cpp @@ -3,10 +3,8 @@ #include "StdAfx.h" #include "../../Common/MyWindows.h" - #include "../PropID.h" -// VARTYPE const Byte k7z_PROPID_To_VARTYPE[kpid_NUM_DEFINED] = { VT_EMPTY, @@ -84,7 +82,7 @@ const Byte k7z_PROPID_To_VARTYPE[kpid_NUM_DEFINED] = VT_UI4, VT_BSTR, VT_UI8, - VT_UI8, + VT_UI4, // kpidNumAltStreams VT_UI8, VT_UI8, VT_UI8, @@ -104,5 +102,14 @@ const Byte k7z_PROPID_To_VARTYPE[kpid_NUM_DEFINED] = VT_UI8, VT_BOOL, VT_BSTR, - VT_BSTR + VT_BSTR, + VT_BSTR, + VT_BOOL, + VT_FILETIME, // kpidChangeTime + VT_UI4, + VT_UI4, + VT_UI4, + VT_UI4, + VT_UI4, + VT_UI4 // kpidDevMinor }; diff --git a/src/plugins/7zip/7za/cpp/7zip/Common/RegisterArc.h b/src/plugins/7zip/7za/cpp/7zip/Common/RegisterArc.h index 3421ba1b..55c1483f 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Common/RegisterArc.h +++ b/src/plugins/7zip/7za/cpp/7zip/Common/RegisterArc.h @@ -1,13 +1,13 @@ // RegisterArc.h -#ifndef __REGISTER_ARC_H -#define __REGISTER_ARC_H +#ifndef ZIP7_INC_REGISTER_ARC_H +#define ZIP7_INC_REGISTER_ARC_H #include "../Archive/IArchive.h" struct CArcInfo { - UInt16 Flags; + UInt32 Flags; Byte Id; Byte SignatureSize; UInt16 SignatureOffset; @@ -17,6 +17,8 @@ struct CArcInfo const char *Ext; const char *AddExt; + UInt32 TimeFlags; + Func_CreateInArchive CreateInArchive; Func_CreateOutArchive CreateOutArchive; Func_IsArc IsArc; @@ -32,29 +34,29 @@ void RegisterArc(const CArcInfo *arcInfo) throw(); #define IMP_CreateArcIn IMP_CreateArcIn_2(CHandler()) -#ifdef EXTRACT_ONLY +#ifdef Z7_EXTRACT_ONLY #define IMP_CreateArcOut #define CreateArcOut NULL #else #define IMP_CreateArcOut static IOutArchive *CreateArcOut() { return new CHandler(); } #endif -#define REGISTER_ARC_V(n, e, ae, id, sigSize, sig, offs, flags, crIn, crOut, isArc) \ - static const CArcInfo g_ArcInfo = { flags, id, sigSize, offs, sig, n, e, ae, crIn, crOut, isArc } ; \ +#define REGISTER_ARC_V(n, e, ae, id, sigSize, sig, offs, flags, tf, crIn, crOut, isArc) \ + static const CArcInfo g_ArcInfo = { flags, id, sigSize, offs, sig, n, e, ae, tf, crIn, crOut, isArc } ; \ -#define REGISTER_ARC_R(n, e, ae, id, sigSize, sig, offs, flags, crIn, crOut, isArc) \ - REGISTER_ARC_V(n, e, ae, id, sigSize, sig, offs, flags, crIn, crOut, isArc) \ +#define REGISTER_ARC_R(n, e, ae, id, sigSize, sig, offs, flags, tf, crIn, crOut, isArc) \ + REGISTER_ARC_V (n, e, ae, id, sigSize, sig, offs, flags, tf, crIn, crOut, isArc) \ struct CRegisterArc { CRegisterArc() { RegisterArc(&g_ArcInfo); }}; \ static CRegisterArc g_RegisterArc; #define REGISTER_ARC_I_CLS(cls, n, e, ae, id, sig, offs, flags, isArc) \ IMP_CreateArcIn_2(cls) \ - REGISTER_ARC_R(n, e, ae, id, ARRAY_SIZE(sig), sig, offs, flags, CreateArc, NULL, isArc) + REGISTER_ARC_R(n, e, ae, id, Z7_ARRAY_SIZE(sig), sig, offs, flags, 0, CreateArc, NULL, isArc) #define REGISTER_ARC_I_CLS_NO_SIG(cls, n, e, ae, id, offs, flags, isArc) \ IMP_CreateArcIn_2(cls) \ - REGISTER_ARC_R(n, e, ae, id, 0, NULL, offs, flags, CreateArc, NULL, isArc) + REGISTER_ARC_R(n, e, ae, id, 0, NULL, offs, flags, 0, CreateArc, NULL, isArc) #define REGISTER_ARC_I(n, e, ae, id, sig, offs, flags, isArc) \ REGISTER_ARC_I_CLS(CHandler(), n, e, ae, id, sig, offs, flags, isArc) @@ -63,15 +65,15 @@ void RegisterArc(const CArcInfo *arcInfo) throw(); REGISTER_ARC_I_CLS_NO_SIG(CHandler(), n, e, ae, id, offs, flags, isArc) -#define REGISTER_ARC_IO(n, e, ae, id, sig, offs, flags, isArc) \ +#define REGISTER_ARC_IO(n, e, ae, id, sig, offs, flags, tf, isArc) \ IMP_CreateArcIn \ IMP_CreateArcOut \ - REGISTER_ARC_R(n, e, ae, id, ARRAY_SIZE(sig), sig, offs, flags, CreateArc, CreateArcOut, isArc) + REGISTER_ARC_R(n, e, ae, id, Z7_ARRAY_SIZE(sig), sig, offs, flags, tf, CreateArc, CreateArcOut, isArc) -#define REGISTER_ARC_IO_DECREMENT_SIG(n, e, ae, id, sig, offs, flags, isArc) \ +#define REGISTER_ARC_IO_DECREMENT_SIG(n, e, ae, id, sig, offs, flags, tf, isArc) \ IMP_CreateArcIn \ IMP_CreateArcOut \ - REGISTER_ARC_V(n, e, ae, id, ARRAY_SIZE(sig), sig, offs, flags, CreateArc, CreateArcOut, isArc) \ + REGISTER_ARC_V(n, e, ae, id, Z7_ARRAY_SIZE(sig), sig, offs, flags, tf, CreateArc, CreateArcOut, isArc) \ struct CRegisterArcDecSig { CRegisterArcDecSig() { sig[0]--; RegisterArc(&g_ArcInfo); }}; \ static CRegisterArcDecSig g_RegisterArc; diff --git a/src/plugins/7zip/7za/cpp/7zip/Common/RegisterCodec.h b/src/plugins/7zip/7za/cpp/7zip/Common/RegisterCodec.h index 7ddb7604..cf94998a 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Common/RegisterCodec.h +++ b/src/plugins/7zip/7za/cpp/7zip/Common/RegisterCodec.h @@ -1,7 +1,7 @@ // RegisterCodec.h -#ifndef __REGISTER_CODEC_H -#define __REGISTER_CODEC_H +#ifndef ZIP7_INC_REGISTER_CODEC_H +#define ZIP7_INC_REGISTER_CODEC_H #include "../Common/MethodId.h" @@ -26,29 +26,29 @@ void RegisterCodec(const CCodecInfo *codecInfo) throw(); #define REGISTER_CODEC_CREATE(name, cls) REGISTER_CODEC_CREATE_2(name, cls, ICompressCoder) #define REGISTER_CODEC_NAME(x) CRegisterCodec ## x -#define REGISTER_CODEC_VAR static const CCodecInfo g_CodecInfo = +#define REGISTER_CODEC_VAR(x) static const CCodecInfo g_CodecInfo_ ## x = #define REGISTER_CODEC(x) struct REGISTER_CODEC_NAME(x) { \ - REGISTER_CODEC_NAME(x)() { RegisterCodec(&g_CodecInfo); }}; \ - static REGISTER_CODEC_NAME(x) g_RegisterCodec; + REGISTER_CODEC_NAME(x)() { RegisterCodec(&g_CodecInfo_ ## x); }}; \ + static REGISTER_CODEC_NAME(x) g_RegisterCodec_ ## x; #define REGISTER_CODECS_NAME(x) CRegisterCodecs ## x #define REGISTER_CODECS_VAR static const CCodecInfo g_CodecsInfo[] = #define REGISTER_CODECS(x) struct REGISTER_CODECS_NAME(x) { \ - REGISTER_CODECS_NAME(x)() { for (unsigned i = 0; i < ARRAY_SIZE(g_CodecsInfo); i++) \ + REGISTER_CODECS_NAME(x)() { for (unsigned i = 0; i < Z7_ARRAY_SIZE(g_CodecsInfo); i++) \ RegisterCodec(&g_CodecsInfo[i]); }}; \ static REGISTER_CODECS_NAME(x) g_RegisterCodecs; #define REGISTER_CODEC_2(x, crDec, crEnc, id, name) \ - REGISTER_CODEC_VAR \ + REGISTER_CODEC_VAR(x) \ { crDec, crEnc, id, name, 1, false }; \ REGISTER_CODEC(x) -#ifdef EXTRACT_ONLY +#ifdef Z7_EXTRACT_ONLY #define REGISTER_CODEC_E(x, clsDec, clsEnc, id, name) \ REGISTER_CODEC_CREATE(CreateDec, clsDec) \ REGISTER_CODEC_2(x, CreateDec, NULL, id, name) @@ -67,19 +67,19 @@ void RegisterCodec(const CCodecInfo *codecInfo) throw(); { crDec, crEnc, id, name, 1, true } #define REGISTER_FILTER(x, crDec, crEnc, id, name) \ - REGISTER_CODEC_VAR \ + REGISTER_CODEC_VAR(x) \ REGISTER_FILTER_ITEM(crDec, crEnc, id, name); \ REGISTER_CODEC(x) -#ifdef EXTRACT_ONLY +#ifdef Z7_EXTRACT_ONLY #define REGISTER_FILTER_E(x, clsDec, clsEnc, id, name) \ - REGISTER_FILTER_CREATE(CreateDec, clsDec) \ - REGISTER_FILTER(x, CreateDec, NULL, id, name) + REGISTER_FILTER_CREATE(x ## _CreateDec, clsDec) \ + REGISTER_FILTER(x, x ## _CreateDec, NULL, id, name) #else #define REGISTER_FILTER_E(x, clsDec, clsEnc, id, name) \ - REGISTER_FILTER_CREATE(CreateDec, clsDec) \ - REGISTER_FILTER_CREATE(CreateEnc, clsEnc) \ - REGISTER_FILTER(x, CreateDec, CreateEnc, id, name) + REGISTER_FILTER_CREATE(x ## _CreateDec, clsDec) \ + REGISTER_FILTER_CREATE(x ## _CreateEnc, clsEnc) \ + REGISTER_FILTER(x, x ## _CreateDec, x ## _CreateEnc, id, name) #endif @@ -97,7 +97,7 @@ void RegisterHasher(const CHasherInfo *hasher) throw(); #define REGISTER_HASHER_NAME(x) CRegHasher_ ## x #define REGISTER_HASHER(cls, id, name, size) \ - STDMETHODIMP_(UInt32) cls::GetDigestSize() throw() { return size; } \ + Z7_COM7F_IMF2(UInt32, cls::GetDigestSize()) { return size; } \ static IHasher *CreateHasherSpec() { return new cls(); } \ static const CHasherInfo g_HasherInfo = { CreateHasherSpec, id, name, size }; \ struct REGISTER_HASHER_NAME(cls) { REGISTER_HASHER_NAME(cls)() { RegisterHasher(&g_HasherInfo); }}; \ diff --git a/src/plugins/7zip/7za/cpp/7zip/Common/StdAfx.h b/src/plugins/7zip/7za/cpp/7zip/Common/StdAfx.h index 1cbd7fea..80866550 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Common/StdAfx.h +++ b/src/plugins/7zip/7za/cpp/7zip/Common/StdAfx.h @@ -1,8 +1,11 @@ // StdAfx.h -#ifndef __STDAFX_H -#define __STDAFX_H +#ifndef ZIP7_INC_STDAFX_H +#define ZIP7_INC_STDAFX_H +#if defined(_MSC_VER) && _MSC_VER >= 1800 +#pragma warning(disable : 4464) // relative include path contains '..' +#endif #include "../../Common/Common.h" #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Common/StreamBinder.cpp b/src/plugins/7zip/7za/cpp/7zip/Common/StreamBinder.cpp index 435440c6..38334af9 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Common/StreamBinder.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Common/StreamBinder.cpp @@ -6,50 +6,48 @@ #include "StreamBinder.h" -class CBinderInStream: - public ISequentialInStream, - public CMyUnknownImp -{ +Z7_CLASS_IMP_COM_1( + CBinderInStream + , ISequentialInStream +) CStreamBinder *_binder; public: - MY_UNKNOWN_IMP1(ISequentialInStream) - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); - ~CBinderInStream() { _binder->CloseRead(); } + ~CBinderInStream() { _binder->CloseRead_CallOnce(); } CBinderInStream(CStreamBinder *binder): _binder(binder) {} }; -STDMETHODIMP CBinderInStream::Read(void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CBinderInStream::Read(void *data, UInt32 size, UInt32 *processedSize)) { return _binder->Read(data, size, processedSize); } -class CBinderOutStream: - public ISequentialOutStream, - public CMyUnknownImp -{ + +Z7_CLASS_IMP_COM_1( + CBinderOutStream + , ISequentialOutStream +) CStreamBinder *_binder; public: - MY_UNKNOWN_IMP1(ISequentialOutStream) - STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize); ~CBinderOutStream() { _binder->CloseWrite(); } CBinderOutStream(CStreamBinder *binder): _binder(binder) {} }; -STDMETHODIMP CBinderOutStream::Write(const void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CBinderOutStream::Write(const void *data, UInt32 size, UInt32 *processedSize)) { return _binder->Write(data, size, processedSize); } - -WRes CStreamBinder::CreateEvents() +static HRESULT Event_Create_or_Reset(NWindows::NSynchronization::CAutoResetEvent &event) { - RINOK(_canWrite_Event.Create()); - RINOK(_canRead_Event.Create()); - return _readingWasClosed_Event.Create(); + const WRes wres = event.CreateIfNotCreated_Reset(); + return HRESULT_FROM_WIN32(wres); } -void CStreamBinder::ReInit() +HRESULT CStreamBinder::Create_ReInit() { - _canWrite_Event.Reset(); - _canRead_Event.Reset(); - _readingWasClosed_Event.Reset(); + RINOK(Event_Create_or_Reset(_canRead_Event)) + // RINOK(Event_Create_or_Reset(_canWrite_Event)) + + // _canWrite_Semaphore.Close(); + // we need at least 3 items of maxCount: 1 for normal unlock in Read(), 2 items for unlock in CloseRead_CallOnce() + _canWrite_Semaphore.OptCreateInit(0, 3); // _readingWasClosed = false; _readingWasClosed2 = false; @@ -59,27 +57,14 @@ void CStreamBinder::ReInit() _buf = NULL; ProcessedSize = 0; // WritingWasCut = false; + return S_OK; } -void CStreamBinder::CreateStreams(ISequentialInStream **inStream, ISequentialOutStream **outStream) +void CStreamBinder::CreateStreams2(CMyComPtr &inStream, CMyComPtr &outStream) { - // _readingWasClosed = false; - _readingWasClosed2 = false; - - _waitWrite = true; - _bufSize = 0; - _buf = NULL; - ProcessedSize = 0; - // WritingWasCut = false; - - CBinderInStream *inStreamSpec = new CBinderInStream(this); - CMyComPtr inStreamLoc(inStreamSpec); - *inStream = inStreamLoc.Detach(); - - CBinderOutStream *outStreamSpec = new CBinderOutStream(this); - CMyComPtr outStreamLoc(outStreamSpec); - *outStream = outStreamLoc.Detach(); + inStream = new CBinderInStream(this); + outStream = new CBinderOutStream(this); } // (_canRead_Event && _bufSize == 0) means that stream is finished. @@ -92,7 +77,9 @@ HRESULT CStreamBinder::Read(void *data, UInt32 size, UInt32 *processedSize) { if (_waitWrite) { - RINOK(_canRead_Event.Lock()); + WRes wres = _canRead_Event.Lock(); + if (wres != 0) + return HRESULT_FROM_WIN32(wres); _waitWrite = false; } if (size > _bufSize) @@ -105,17 +92,25 @@ HRESULT CStreamBinder::Read(void *data, UInt32 size, UInt32 *processedSize) if (processedSize) *processedSize = size; _bufSize -= size; + + /* + if (_bufSize == 0), then we have read whole buffer + we have two ways here: + - if we check (_bufSize == 0) here, we unlock Write only after full data Reading - it reduces the number of syncs + - if we don't check (_bufSize == 0) here, we unlock Write after partial data Reading + */ if (_bufSize == 0) { _waitWrite = true; - _canRead_Event.Reset(); - _canWrite_Event.Set(); + // _canWrite_Event.Set(); + _canWrite_Semaphore.Release(); } } } return S_OK; } + HRESULT CStreamBinder::Write(const void *data, UInt32 size, UInt32 *processedSize) { if (processedSize) @@ -135,20 +130,20 @@ HRESULT CStreamBinder::Write(const void *data, UInt32 size, UInt32 *processedSiz _readingWasClosed2 = true; */ - HANDLE events[2] = { _canWrite_Event, _readingWasClosed_Event }; - DWORD waitResult = ::WaitForMultipleObjects(2, events, FALSE, INFINITE); - if (waitResult >= WAIT_OBJECT_0 + 2) - return E_FAIL; + _canWrite_Semaphore.Lock(); + // _bufSize : is remain size that was not read size -= _bufSize; + + // size : is size of data that was read if (size != 0) { + // if some data was read, then we report that size and return if (processedSize) *processedSize = size; return S_OK; } - // if (waitResult == WAIT_OBJECT_0 + 1) - _readingWasClosed2 = true; + _readingWasClosed2 = true; } // WritingWasCut = true; diff --git a/src/plugins/7zip/7za/cpp/7zip/Common/StreamBinder.h b/src/plugins/7zip/7za/cpp/7zip/Common/StreamBinder.h index 12088a94..c0a70793 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Common/StreamBinder.h +++ b/src/plugins/7zip/7za/cpp/7zip/Common/StreamBinder.h @@ -1,52 +1,70 @@ // StreamBinder.h -#ifndef __STREAM_BINDER_H -#define __STREAM_BINDER_H +#ifndef ZIP7_INC_STREAM_BINDER_H +#define ZIP7_INC_STREAM_BINDER_H #include "../../Windows/Synchronization.h" #include "../IStream.h" /* -We don't use probably UNSAFE version: -reader thread: +We can use one from two code versions here: with Event or with Semaphore to unlock Writer thread +The difference for cases where Reading must be closed before Writing closing + +1) Event Version: _canWrite_Event + We call _canWrite_Event.Set() without waiting _canRead_Event in CloseRead() function. + The writer thread can get (_readingWasClosed) status in one from two iterations. + It's ambiguity of processing flow. But probably it's SAFE to use, if Event functions provide memory barriers. + reader thread: _canWrite_Event.Set(); - _readingWasClosed = true + _readingWasClosed = true; _canWrite_Event.Set(); -writer thread: + writer thread: _canWrite_Event.Wait() if (_readingWasClosed) -Can second call of _canWrite_Event.Set() be executed without memory barrier, if event is already set? + +2) Semaphore Version: _canWrite_Semaphore + writer thread always will detect closing of reading in latest iteration after all data processing iterations */ class CStreamBinder { - NWindows::NSynchronization::CAutoResetEvent _canWrite_Event; - NWindows::NSynchronization::CManualResetEvent _canRead_Event; - NWindows::NSynchronization::CManualResetEvent _readingWasClosed_Event; + NWindows::NSynchronization::CAutoResetEvent _canRead_Event; + // NWindows::NSynchronization::CAutoResetEvent _canWrite_Event; + NWindows::NSynchronization::CSemaphore _canWrite_Semaphore; - // bool _readingWasClosed; - bool _readingWasClosed2; + // bool _readingWasClosed; // set it in reader thread and check it in write thread + bool _readingWasClosed2; // use it in writer thread // bool WritingWasCut; - bool _waitWrite; + bool _waitWrite; // use it in reader thread UInt32 _bufSize; const void *_buf; public: - UInt64 ProcessedSize; + UInt64 ProcessedSize; // the size that was read by reader thread - WRes CreateEvents(); - void CreateStreams(ISequentialInStream **inStream, ISequentialOutStream **outStream); + void CreateStreams2(CMyComPtr &inStream, CMyComPtr &outStream); - void ReInit(); + HRESULT Create_ReInit(); HRESULT Read(void *data, UInt32 size, UInt32 *processedSize); HRESULT Write(const void *data, UInt32 size, UInt32 *processedSize); - void CloseRead() + void CloseRead_CallOnce() { - _readingWasClosed_Event.Set(); - // _readingWasClosed = true; - // _canWrite_Event.Set(); + // call it only once: for example, in destructor + + /* + _readingWasClosed = true; + _canWrite_Event.Set(); + */ + + /* + We must relase Semaphore only once !!! + we must release at least 2 items of Semaphore: + one item to unlock partial Write(), if Read() have read some items + then additional item to stop writing (_bufSize will be 0) + */ + _canWrite_Semaphore.Release(2); } void CloseWrite() diff --git a/src/plugins/7zip/7za/cpp/7zip/Common/StreamObjects.cpp b/src/plugins/7zip/7za/cpp/7zip/Common/StreamObjects.cpp index 8136716d..b54f4237 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Common/StreamObjects.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Common/StreamObjects.cpp @@ -2,13 +2,11 @@ #include "StdAfx.h" -#include - #include "../../../C/Alloc.h" #include "StreamObjects.h" -STDMETHODIMP CBufferInStream::Read(void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CBufferInStream::Read(void *data, UInt32 size, UInt32 *processedSize)) { if (processedSize) *processedSize = 0; @@ -26,7 +24,7 @@ STDMETHODIMP CBufferInStream::Read(void *data, UInt32 size, UInt32 *processedSiz return S_OK; } -STDMETHODIMP CBufferInStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition) +Z7_COM7F_IMF(CBufferInStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition)) { switch (seekOrigin) { @@ -37,13 +35,13 @@ STDMETHODIMP CBufferInStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newP } if (offset < 0) return HRESULT_WIN32_ERROR_NEGATIVE_SEEK; - _pos = offset; + _pos = (UInt64)offset; if (newPosition) - *newPosition = offset; + *newPosition = (UInt64)offset; return S_OK; } -STDMETHODIMP CBufInStream::Read(void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CBufInStream::Read(void *data, UInt32 size, UInt32 *processedSize)) { if (processedSize) *processedSize = 0; @@ -61,7 +59,7 @@ STDMETHODIMP CBufInStream::Read(void *data, UInt32 size, UInt32 *processedSize) return S_OK; } -STDMETHODIMP CBufInStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition) +Z7_COM7F_IMF(CBufInStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition)) { switch (seekOrigin) { @@ -72,9 +70,9 @@ STDMETHODIMP CBufInStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosi } if (offset < 0) return HRESULT_WIN32_ERROR_NEGATIVE_SEEK; - _pos = offset; + _pos = (UInt64)offset; if (newPosition) - *newPosition = offset; + *newPosition = (UInt64)offset; return S_OK; } @@ -99,8 +97,8 @@ void Create_BufInStream_WithNewBuffer(const void *data, size_t size, ISequential void CByteDynBuffer::Free() throw() { - free(_buf); - _buf = 0; + MyFree(_buf); + _buf = NULL; _capacity = 0; } @@ -108,15 +106,10 @@ bool CByteDynBuffer::EnsureCapacity(size_t cap) throw() { if (cap <= _capacity) return true; - size_t delta; - if (_capacity > 64) - delta = _capacity / 4; - else if (_capacity > 8) - delta = 16; - else - delta = 4; - cap = MyMax(_capacity + delta, cap); - Byte *buf = (Byte *)realloc(_buf, cap); + const size_t cap2 = _capacity + _capacity / 4; + if (cap < cap2) + cap = cap2; + Byte *buf = (Byte *)MyRealloc(_buf, cap); if (!buf) return false; _buf = buf; @@ -139,7 +132,7 @@ void CDynBufSeqOutStream::CopyToBuffer(CByteBuffer &dest) const dest.CopyFrom((const Byte *)_buffer, _size); } -STDMETHODIMP CDynBufSeqOutStream::Write(const void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CDynBufSeqOutStream::Write(const void *data, UInt32 size, UInt32 *processedSize)) { if (processedSize) *processedSize = 0; @@ -155,7 +148,7 @@ STDMETHODIMP CDynBufSeqOutStream::Write(const void *data, UInt32 size, UInt32 *p return S_OK; } -STDMETHODIMP CBufPtrSeqOutStream::Write(const void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CBufPtrSeqOutStream::Write(const void *data, UInt32 size, UInt32 *processedSize)) { size_t rem = _size - _pos; if (rem > size) @@ -170,7 +163,7 @@ STDMETHODIMP CBufPtrSeqOutStream::Write(const void *data, UInt32 size, UInt32 *p return (rem != 0 || size == 0) ? S_OK : E_FAIL; } -STDMETHODIMP CSequentialOutStreamSizeCount::Write(const void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CSequentialOutStreamSizeCount::Write(const void *data, UInt32 size, UInt32 *processedSize)) { UInt32 realProcessedSize; HRESULT result = _stream->Write(data, size, &realProcessedSize); @@ -185,9 +178,9 @@ static const UInt64 kEmptyTag = (UInt64)(Int64)-1; void CCachedInStream::Free() throw() { MyFree(_tags); - _tags = 0; + _tags = NULL; MidFree(_data); - _data = 0; + _data = NULL; } bool CCachedInStream::Alloc(unsigned blockSizeLog, unsigned numBlocksLog) throw() @@ -196,19 +189,19 @@ bool CCachedInStream::Alloc(unsigned blockSizeLog, unsigned numBlocksLog) throw( if (sizeLog >= sizeof(size_t) * 8) return false; size_t dataSize = (size_t)1 << sizeLog; - if (_data == 0 || dataSize != _dataSize) + if (!_data || dataSize != _dataSize) { MidFree(_data); _data = (Byte *)MidAlloc(dataSize); - if (_data == 0) + if (!_data) return false; _dataSize = dataSize; } - if (_tags == 0 || numBlocksLog != _numBlocksLog) + if (!_tags || numBlocksLog != _numBlocksLog) { MyFree(_tags); _tags = (UInt64 *)MyAlloc(sizeof(UInt64) << numBlocksLog); - if (_tags == 0) + if (!_tags) return false; _numBlocksLog = numBlocksLog; } @@ -220,12 +213,12 @@ void CCachedInStream::Init(UInt64 size) throw() { _size = size; _pos = 0; - size_t numBlocks = (size_t)1 << _numBlocksLog; + const size_t numBlocks = (size_t)1 << _numBlocksLog; for (size_t i = 0; i < numBlocks; i++) _tags[i] = kEmptyTag; } -STDMETHODIMP CCachedInStream::Read(void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CCachedInStream::Read(void *data, UInt32 size, UInt32 *processedSize)) { if (processedSize) *processedSize = 0; @@ -235,28 +228,39 @@ STDMETHODIMP CCachedInStream::Read(void *data, UInt32 size, UInt32 *processedSiz return S_OK; { - UInt64 rem = _size - _pos; + const UInt64 rem = _size - _pos; if (size > rem) size = (UInt32)rem; } while (size != 0) { - UInt64 cacheTag = _pos >> _blockSizeLog; - size_t cacheIndex = (size_t)cacheTag & (((size_t)1 << _numBlocksLog) - 1); + const UInt64 cacheTag = _pos >> _blockSizeLog; + const size_t cacheIndex = (size_t)cacheTag & (((size_t)1 << _numBlocksLog) - 1); Byte *p = _data + (cacheIndex << _blockSizeLog); + if (_tags[cacheIndex] != cacheTag) { - UInt64 remInBlock = _size - (cacheTag << _blockSizeLog); + _tags[cacheIndex] = kEmptyTag; + const UInt64 remInBlock = _size - (cacheTag << _blockSizeLog); size_t blockSize = (size_t)1 << _blockSizeLog; if (blockSize > remInBlock) blockSize = (size_t)remInBlock; - RINOK(ReadBlock(cacheTag, p, blockSize)); + + RINOK(ReadBlock(cacheTag, p, blockSize)) + _tags[cacheIndex] = cacheTag; } - size_t offset = (size_t)_pos & (((size_t)1 << _blockSizeLog) - 1); - UInt32 cur = (UInt32)MyMin(((size_t)1 << _blockSizeLog) - offset, (size_t)size); + + const size_t kBlockSize = (size_t)1 << _blockSizeLog; + const size_t offset = (size_t)_pos & (kBlockSize - 1); + UInt32 cur = size; + const size_t rem = kBlockSize - offset; + if (cur > rem) + cur = (UInt32)rem; + memcpy(data, p + offset, cur); + if (processedSize) *processedSize += cur; data = (void *)((const Byte *)data + cur); @@ -266,8 +270,9 @@ STDMETHODIMP CCachedInStream::Read(void *data, UInt32 size, UInt32 *processedSiz return S_OK; } + -STDMETHODIMP CCachedInStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition) +Z7_COM7F_IMF(CCachedInStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition)) { switch (seekOrigin) { @@ -278,8 +283,8 @@ STDMETHODIMP CCachedInStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newP } if (offset < 0) return HRESULT_WIN32_ERROR_NEGATIVE_SEEK; - _pos = offset; + _pos = (UInt64)offset; if (newPosition) - *newPosition = offset; + *newPosition = (UInt64)offset; return S_OK; } diff --git a/src/plugins/7zip/7za/cpp/7zip/Common/StreamObjects.h b/src/plugins/7zip/7za/cpp/7zip/Common/StreamObjects.h index e20e9bd8..2c0ebea9 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Common/StreamObjects.h +++ b/src/plugins/7zip/7za/cpp/7zip/Common/StreamObjects.h @@ -1,7 +1,7 @@ // StreamObjects.h -#ifndef __STREAM_OBJECTS_H -#define __STREAM_OBJECTS_H +#ifndef ZIP7_INC_STREAM_OBJECTS_H +#define ZIP7_INC_STREAM_OBJECTS_H #include "../../Common/MyBuffer.h" #include "../../Common/MyCom.h" @@ -9,39 +9,33 @@ #include "../IStream.h" -class CBufferInStream: - public IInStream, - public CMyUnknownImp -{ +Z7_CLASS_IMP_IInStream( + CBufferInStream +) UInt64 _pos; public: CByteBuffer Buf; void Init() { _pos = 0; } - - MY_UNKNOWN_IMP2(ISequentialInStream, IInStream) - - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); - STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition); }; -struct CReferenceBuf: - public IUnknown, - public CMyUnknownImp -{ + +Z7_CLASS_IMP_COM_0( + CReferenceBuf +) +public: CByteBuffer Buf; - MY_UNKNOWN_IMP }; -class CBufInStream: - public IInStream, - public CMyUnknownImp -{ + +Z7_CLASS_IMP_IInStream( + CBufInStream +) const Byte *_data; UInt64 _pos; size_t _size; CMyComPtr _ref; public: - void Init(const Byte *data, size_t size, IUnknown *ref = 0) + void Init(const Byte *data, size_t size, IUnknown *ref = NULL) { _data = data; _size = size; @@ -50,22 +44,24 @@ class CBufInStream: } void Init(CReferenceBuf *ref) { Init(ref->Buf, ref->Buf.Size(), ref); } - MY_UNKNOWN_IMP2(ISequentialInStream, IInStream) - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); - STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition); + // Seek() is allowed here. So reading order could be changed + bool WasFinished() const { return _pos == _size; } }; + void Create_BufInStream_WithReference(const void *data, size_t size, IUnknown *ref, ISequentialInStream **stream); void Create_BufInStream_WithNewBuffer(const void *data, size_t size, ISequentialInStream **stream); inline void Create_BufInStream_WithNewBuffer(const CByteBuffer &buf, ISequentialInStream **stream) { Create_BufInStream_WithNewBuffer(buf, buf.Size(), stream); } -class CByteDynBuffer + +class CByteDynBuffer Z7_final { size_t _capacity; Byte *_buf; + Z7_CLASS_NO_COPY(CByteDynBuffer) public: - CByteDynBuffer(): _capacity(0), _buf(0) {}; + CByteDynBuffer(): _capacity(0), _buf(NULL) {} // there is no copy constructor. So don't copy this object. ~CByteDynBuffer() { Free(); } void Free() throw(); @@ -75,10 +71,11 @@ class CByteDynBuffer bool EnsureCapacity(size_t capacity) throw(); }; -class CDynBufSeqOutStream: - public ISequentialOutStream, - public CMyUnknownImp -{ + +Z7_CLASS_IMP_COM_1( + CDynBufSeqOutStream + , ISequentialOutStream +) CByteDynBuffer _buffer; size_t _size; public: @@ -89,15 +86,13 @@ class CDynBufSeqOutStream: void CopyToBuffer(CByteBuffer &dest) const; Byte *GetBufPtrForWriting(size_t addSize); void UpdateSize(size_t addSize) { _size += addSize; } - - MY_UNKNOWN_IMP1(ISequentialOutStream) - STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize); }; -class CBufPtrSeqOutStream: - public ISequentialOutStream, - public CMyUnknownImp -{ + +Z7_CLASS_IMP_COM_1( + CBufPtrSeqOutStream + , ISequentialOutStream +) Byte *_buffer; size_t _size; size_t _pos; @@ -109,30 +104,28 @@ class CBufPtrSeqOutStream: _size = size; } size_t GetPos() const { return _pos; } - - MY_UNKNOWN_IMP1(ISequentialOutStream) - STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize); }; -class CSequentialOutStreamSizeCount: - public ISequentialOutStream, - public CMyUnknownImp -{ + +Z7_CLASS_IMP_COM_1( + CSequentialOutStreamSizeCount + , ISequentialOutStream +) CMyComPtr _stream; UInt64 _size; public: void SetStream(ISequentialOutStream *stream) { _stream = stream; } void Init() { _size = 0; } UInt64 GetSize() const { return _size; } - - MY_UNKNOWN_IMP1(ISequentialOutStream) - STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize); }; + class CCachedInStream: public IInStream, public CMyUnknownImp { + Z7_IFACES_IMP_UNK_2(ISequentialInStream, IInStream) + UInt64 *_tags; Byte *_data; size_t _dataSize; @@ -143,15 +136,11 @@ class CCachedInStream: protected: virtual HRESULT ReadBlock(UInt64 blockIndex, Byte *dest, size_t blockSize) = 0; public: - CCachedInStream(): _tags(0), _data(0) {} - virtual ~CCachedInStream() { Free(); } // the destructor must be virtual (release calls it) !!! + CCachedInStream(): _tags(NULL), _data(NULL) {} + virtual ~CCachedInStream() { Free(); } // the destructor must be virtual (Release() calls it) !!! void Free() throw(); bool Alloc(unsigned blockSizeLog, unsigned numBlocksLog) throw(); void Init(UInt64 size) throw(); - - MY_UNKNOWN_IMP2(ISequentialInStream, IInStream) - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); - STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition); }; #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Common/StreamUtils.cpp b/src/plugins/7zip/7za/cpp/7zip/Common/StreamUtils.cpp index 1402f420..24eed36c 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Common/StreamUtils.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Common/StreamUtils.cpp @@ -2,10 +2,55 @@ #include "StdAfx.h" +#include "../../Common/MyCom.h" + #include "StreamUtils.h" static const UInt32 kBlockSize = ((UInt32)1 << 31); + +HRESULT InStream_SeekToBegin(IInStream *stream) throw() +{ + return InStream_SeekSet(stream, 0); +} + + +HRESULT InStream_AtBegin_GetSize(IInStream *stream, UInt64 &sizeRes) throw() +{ +#ifdef _WIN32 + { + Z7_DECL_CMyComPtr_QI_FROM( + IStreamGetSize, + streamGetSize, stream) + if (streamGetSize && streamGetSize->GetSize(&sizeRes) == S_OK) + return S_OK; + } +#endif + const HRESULT hres = InStream_GetSize_SeekToEnd(stream, sizeRes); + const HRESULT hres2 = InStream_SeekToBegin(stream); + return hres != S_OK ? hres : hres2; +} + + +HRESULT InStream_GetPos_GetSize(IInStream *stream, UInt64 &curPosRes, UInt64 &sizeRes) throw() +{ + RINOK(InStream_GetPos(stream, curPosRes)) +#ifdef _WIN32 + { + Z7_DECL_CMyComPtr_QI_FROM( + IStreamGetSize, + streamGetSize, stream) + if (streamGetSize && streamGetSize->GetSize(&sizeRes) == S_OK) + return S_OK; + } +#endif + const HRESULT hres = InStream_GetSize_SeekToEnd(stream, sizeRes); + const HRESULT hres2 = InStream_SeekSet(stream, curPosRes); + return hres != S_OK ? hres : hres2; +} + + + HRESULT ReadStream(ISequentialInStream *stream, void *data, size_t *processedSize) throw() { size_t size = *processedSize; @@ -18,7 +63,7 @@ HRESULT ReadStream(ISequentialInStream *stream, void *data, size_t *processedSiz *processedSize += processedSizeLoc; data = (void *)((Byte *)data + processedSizeLoc); size -= processedSizeLoc; - RINOK(res); + RINOK(res) if (processedSizeLoc == 0) return S_OK; } @@ -28,14 +73,14 @@ HRESULT ReadStream(ISequentialInStream *stream, void *data, size_t *processedSiz HRESULT ReadStream_FALSE(ISequentialInStream *stream, void *data, size_t size) throw() { size_t processedSize = size; - RINOK(ReadStream(stream, data, &processedSize)); + RINOK(ReadStream(stream, data, &processedSize)) return (size == processedSize) ? S_OK : S_FALSE; } HRESULT ReadStream_FAIL(ISequentialInStream *stream, void *data, size_t size) throw() { size_t processedSize = size; - RINOK(ReadStream(stream, data, &processedSize)); + RINOK(ReadStream(stream, data, &processedSize)) return (size == processedSize) ? S_OK : E_FAIL; } @@ -48,7 +93,7 @@ HRESULT WriteStream(ISequentialOutStream *stream, const void *data, size_t size) HRESULT res = stream->Write(data, curSize, &processedSizeLoc); data = (const void *)((const Byte *)data + processedSizeLoc); size -= processedSizeLoc; - RINOK(res); + RINOK(res) if (processedSizeLoc == 0) return E_FAIL; } diff --git a/src/plugins/7zip/7za/cpp/7zip/Common/StreamUtils.h b/src/plugins/7zip/7za/cpp/7zip/Common/StreamUtils.h index ae914c00..35a62ed3 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Common/StreamUtils.h +++ b/src/plugins/7zip/7za/cpp/7zip/Common/StreamUtils.h @@ -1,10 +1,28 @@ // StreamUtils.h -#ifndef __STREAM_UTILS_H -#define __STREAM_UTILS_H +#ifndef ZIP7_INC_STREAM_UTILS_H +#define ZIP7_INC_STREAM_UTILS_H #include "../IStream.h" +inline HRESULT InStream_SeekSet(IInStream *stream, UInt64 offset) throw() + { return stream->Seek((Int64)offset, STREAM_SEEK_SET, NULL); } +inline HRESULT InStream_GetPos(IInStream *stream, UInt64 &curPosRes) throw() + { return stream->Seek(0, STREAM_SEEK_CUR, &curPosRes); } +inline HRESULT InStream_GetSize_SeekToEnd(IInStream *stream, UInt64 &sizeRes) throw() + { return stream->Seek(0, STREAM_SEEK_END, &sizeRes); } + +HRESULT InStream_SeekToBegin(IInStream *stream) throw(); +HRESULT InStream_AtBegin_GetSize(IInStream *stream, UInt64 &size) throw(); +HRESULT InStream_GetPos_GetSize(IInStream *stream, UInt64 &curPosRes, UInt64 &sizeRes) throw(); + +inline HRESULT InStream_GetSize_SeekToBegin(IInStream *stream, UInt64 &sizeRes) throw() +{ + RINOK(InStream_SeekToBegin(stream)) + return InStream_AtBegin_GetSize(stream, sizeRes); +} + + HRESULT ReadStream(ISequentialInStream *stream, void *data, size_t *size) throw(); HRESULT ReadStream_FALSE(ISequentialInStream *stream, void *data, size_t size) throw(); HRESULT ReadStream_FAIL(ISequentialInStream *stream, void *data, size_t size) throw(); diff --git a/src/plugins/7zip/7za/cpp/7zip/Common/UniqBlocks.cpp b/src/plugins/7zip/7za/cpp/7zip/Common/UniqBlocks.cpp index 8f754e17..32dc2762 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Common/UniqBlocks.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Common/UniqBlocks.cpp @@ -11,10 +11,10 @@ unsigned CUniqBlocks::AddUniq(const Byte *data, size_t size) unsigned left = 0, right = Sorted.Size(); while (left != right) { - unsigned mid = (left + right) / 2; - unsigned index = Sorted[mid]; + const unsigned mid = (unsigned)(((size_t)left + (size_t)right) / 2); + const unsigned index = Sorted[mid]; const CByteBuffer &buf = Bufs[index]; - size_t sizeMid = buf.Size(); + const size_t sizeMid = buf.Size(); if (size < sizeMid) right = mid; else if (size > sizeMid) @@ -23,7 +23,7 @@ unsigned CUniqBlocks::AddUniq(const Byte *data, size_t size) { if (size == 0) return index; - int cmp = memcmp(data, buf, size); + const int cmp = memcmp(data, buf, size); if (cmp == 0) return index; if (cmp < 0) diff --git a/src/plugins/7zip/7za/cpp/7zip/Common/UniqBlocks.h b/src/plugins/7zip/7za/cpp/7zip/Common/UniqBlocks.h index a376024e..66c7fa21 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Common/UniqBlocks.h +++ b/src/plugins/7zip/7za/cpp/7zip/Common/UniqBlocks.h @@ -1,11 +1,26 @@ // UniqBlocks.h -#ifndef __UNIQ_BLOCKS_H -#define __UNIQ_BLOCKS_H +#ifndef ZIP7_INC_UNIQ_BLOCKS_H +#define ZIP7_INC_UNIQ_BLOCKS_H -#include "../../Common/MyTypes.h" #include "../../Common/MyBuffer.h" -#include "../../Common/MyVector.h" +#include "../../Common/MyString.h" + +struct C_UInt32_UString_Map +{ + CRecordVector Numbers; + UStringVector Strings; + + void Add_UInt32(const UInt32 n) + { + Numbers.AddToUniqueSorted(n); + } + int Find(const UInt32 n) + { + return Numbers.FindInSorted(n); + } +}; + struct CUniqBlocks { @@ -19,7 +34,7 @@ struct CUniqBlocks bool IsOnlyEmpty() const { - return (Bufs.Size() == 0 || Bufs.Size() == 1 && Bufs[0].Size() == 0); + return (Bufs.Size() == 0 || (Bufs.Size() == 1 && Bufs[0].Size() == 0)); } }; diff --git a/src/plugins/7zip/7za/cpp/7zip/Common/VirtThread.cpp b/src/plugins/7zip/7za/cpp/7zip/Common/VirtThread.cpp index ead69f51..4b03f7c3 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Common/VirtThread.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Common/VirtThread.cpp @@ -11,39 +11,36 @@ static THREAD_FUNC_DECL CoderThread(void *p) CVirtThread *t = (CVirtThread *)p; t->StartEvent.Lock(); if (t->Exit) - return 0; + return THREAD_FUNC_RET_ZERO; t->Execute(); t->FinishedEvent.Set(); } } -// OPENSAL_7ZIP_PATCH BEGIN -extern "C" { - DWORD - RunThreadWithCallStackObject(LPTHREAD_START_ROUTINE startAddress, LPVOID parameter); -} +// Keep 7-Zip worker stacks visible to the host's crash diagnostics. +extern "C" DWORD RunThreadWithCallStackObject(LPTHREAD_START_ROUTINE startAddress, LPVOID parameter); -static THREAD_FUNC_DECL CoderThreadWrap(void *p) { - return (THREAD_FUNC_RET_TYPE) RunThreadWithCallStackObject((LPTHREAD_START_ROUTINE) CoderThread, p); +static THREAD_FUNC_DECL CoderThreadWithSalamanderStack(void *p) +{ + return (THREAD_FUNC_RET_TYPE)RunThreadWithCallStackObject((LPTHREAD_START_ROUTINE)CoderThread, p); } WRes CVirtThread::Create() { - RINOK(StartEvent.CreateIfNotCreated()); - RINOK(FinishedEvent.CreateIfNotCreated()); - StartEvent.Reset(); - FinishedEvent.Reset(); + RINOK_WRes(StartEvent.CreateIfNotCreated_Reset()) + RINOK_WRes(FinishedEvent.CreateIfNotCreated_Reset()) + // StartEvent.Reset(); + // FinishedEvent.Reset(); Exit = false; if (Thread.IsCreated()) return S_OK; - return Thread.Create(CoderThreadWrap, this); + return Thread.Create(CoderThreadWithSalamanderStack, this); } -// OPENSAL_7ZIP_PATCH END -void CVirtThread::Start() +WRes CVirtThread::Start() { Exit = false; - StartEvent.Set(); + return StartEvent.Set(); } void CVirtThread::WaitThreadFinish() @@ -53,7 +50,6 @@ void CVirtThread::WaitThreadFinish() StartEvent.Set(); if (Thread.IsCreated()) { - Thread.Wait(); - Thread.Close(); + Thread.Wait_Close(); } } diff --git a/src/plugins/7zip/7za/cpp/7zip/Common/VirtThread.h b/src/plugins/7zip/7za/cpp/7zip/Common/VirtThread.h index ebee158c..809b3568 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Common/VirtThread.h +++ b/src/plugins/7zip/7za/cpp/7zip/Common/VirtThread.h @@ -1,7 +1,7 @@ // VirtThread.h -#ifndef __VIRT_THREAD_H -#define __VIRT_THREAD_H +#ifndef ZIP7_INC_VIRT_THREAD_H +#define ZIP7_INC_VIRT_THREAD_H #include "../../Windows/Synchronization.h" #include "../../Windows/Thread.h" @@ -13,12 +13,12 @@ struct CVirtThread NWindows::CThread Thread; bool Exit; - ~CVirtThread() { WaitThreadFinish(); } + virtual ~CVirtThread() { WaitThreadFinish(); } void WaitThreadFinish(); // call it in destructor of child class ! WRes Create(); - void Start(); + WRes Start(); virtual void Execute() = 0; - void WaitExecuteFinish() { FinishedEvent.Lock(); } + WRes WaitExecuteFinish() { return FinishedEvent.Lock(); } }; #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/BZip2Const.h b/src/plugins/7zip/7za/cpp/7zip/Compress/BZip2Const.h index 588f5ae0..3380aafb 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/BZip2Const.h +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/BZip2Const.h @@ -1,7 +1,7 @@ // Compress/BZip2Const.h -#ifndef __COMPRESS_BZIP2_CONST_H -#define __COMPRESS_BZIP2_CONST_H +#ifndef ZIP7_INC_COMPRESS_BZIP2_CONST_H +#define ZIP7_INC_COMPRESS_BZIP2_CONST_H namespace NCompress { namespace NBZip2 { @@ -46,10 +46,26 @@ const UInt32 kBlockSizeStep = 100000; const UInt32 kBlockSizeMax = kBlockSizeMultMax * kBlockSizeStep; const unsigned kNumSelectorsBits = 15; -const UInt32 kNumSelectorsMax = (2 + (kBlockSizeMax / kGroupSize)); +const unsigned kNumSelectorsMax = 2 + kBlockSizeMax / kGroupSize; const unsigned kRleModeRepSize = 4; +/* +The number of selectors stored in bzip2 block: +(numSelectors <= 18001) - must work with any decoder. +(numSelectors == 18002) - works with bzip2 1.0.6 decoder and all derived decoders. +(numSelectors > 18002) + lbzip2 2.5: encoder can write up to (18001 + 7) selectors. + + 7-Zip before 19.03: decoder doesn't support it. + 7-Zip 19.03: decoder allows 8 additional selector records for lbzip2 compatibility. + + bzip2 1.0.6: decoder can overflow selector[18002] arrays. But there are another + arrays after selector arrays. So the compiled code works. + bzip2 1.0.7: decoder doesn't support it. + bzip2 1.0.8: decoder allows additional selector records for lbzip2 compatibility. +*/ + }} #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/BZip2Crc.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/BZip2Crc.cpp index 4e4741f4..1c62253f 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/BZip2Crc.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/BZip2Crc.cpp @@ -4,6 +4,7 @@ #include "BZip2Crc.h" +MY_ALIGN(64) UInt32 CBZip2Crc::Table[256]; static const UInt32 kBZip2CrcPoly = 0x04c11db7; /* AUTODIN II, Ethernet, & FDDI */ @@ -12,13 +13,14 @@ void CBZip2Crc::InitTable() { for (UInt32 i = 0; i < 256; i++) { - UInt32 r = (i << 24); - for (int j = 8; j > 0; j--) - r = (r & 0x80000000) ? ((r << 1) ^ kBZip2CrcPoly) : (r << 1); + UInt32 r = i << 24; + for (unsigned j = 0; j < 8; j++) + r = (r << 1) ^ (kBZip2CrcPoly & ((UInt32)0 - (r >> 31))); Table[i] = r; } } +static class CBZip2CrcTableInit { public: diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/BZip2Crc.h b/src/plugins/7zip/7za/cpp/7zip/Compress/BZip2Crc.h index 1b5755b5..10e147bc 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/BZip2Crc.h +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/BZip2Crc.h @@ -1,7 +1,7 @@ // BZip2Crc.h -#ifndef __BZIP2_CRC_H -#define __BZIP2_CRC_H +#ifndef ZIP7_INC_BZIP2_CRC_H +#define ZIP7_INC_BZIP2_CRC_H #include "../../Common/MyTypes.h" @@ -11,10 +11,10 @@ class CBZip2Crc static UInt32 Table[256]; public: static void InitTable(); - CBZip2Crc(): _value(0xFFFFFFFF) {}; - void Init() { _value = 0xFFFFFFFF; } + CBZip2Crc(UInt32 initVal = 0xFFFFFFFF): _value(initVal) {} + void Init(UInt32 initVal = 0xFFFFFFFF) { _value = initVal; } void UpdateByte(Byte b) { _value = Table[(_value >> 24) ^ b] ^ (_value << 8); } - void UpdateByte(unsigned int b) { _value = Table[(_value >> 24) ^ b] ^ (_value << 8); } + void UpdateByte(unsigned b) { _value = Table[(_value >> 24) ^ b] ^ (_value << 8); } UInt32 GetDigest() const { return _value ^ 0xFFFFFFFF; } }; @@ -22,7 +22,7 @@ class CBZip2CombinedCrc { UInt32 _value; public: - CBZip2CombinedCrc(): _value(0){}; + CBZip2CombinedCrc(): _value(0) {} void Init() { _value = 0; } void Update(UInt32 v) { _value = ((_value << 1) | (_value >> 31)) ^ v; } UInt32 GetDigest() const { return _value ; } diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/BZip2Decoder.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/BZip2Decoder.cpp index 56f1b02f..f44faa2e 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/BZip2Decoder.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/BZip2Decoder.cpp @@ -2,21 +2,47 @@ #include "StdAfx.h" +// #include "CopyCoder.h" + +/* +#include +#include "../../../C/CpuTicks.h" +*/ +#define TICKS_START +#define TICKS_UPDATE(n) + + +/* +#define PRIN(s) printf(s "\n"); fflush(stdout); +#define PRIN_VAL(s, val) printf(s " = %u \n", val); fflush(stdout); +*/ + +#define PRIN(s) +#define PRIN_VAL(s, val) + + #include "../../../C/Alloc.h" +#include "../Common/StreamUtils.h" + #include "BZip2Decoder.h" -#include "Mtf8.h" + namespace NCompress { namespace NBZip2 { -#undef NO_INLINE -#define NO_INLINE - -static const UInt32 kNumThreadsMax = 4; +// #undef NO_INLINE +#define NO_INLINE Z7_NO_INLINE + +#define BZIP2_BYTE_MODE + + +static const UInt32 kInBufSize = (UInt32)1 << 17; +static const size_t kOutBufSize = (size_t)1 << 20; -static const UInt32 kBufferSize = (1 << 17); +static const UInt32 kProgressStep = (UInt32)1 << 16; +MY_ALIGN(64) static const UInt16 kRandNums[512] = { 619, 720, 127, 481, 931, 816, 813, 233, 566, 247, 985, 724, 205, 454, 863, 491, 741, 242, 949, 214, @@ -72,416 +98,112 @@ static const UInt16 kRandNums[512] = { 936, 638 }; -bool CState::Alloc() -{ - if (!Counters) - Counters = (UInt32 *)::BigAlloc((256 + kBlockSizeMax) * sizeof(UInt32)); - return (Counters != 0); -} - -void CState::Free() -{ - ::BigFree(Counters); - Counters = 0; -} - -Byte CDecoder::ReadByte() { return (Byte)Base.ReadBits(8); } -UInt32 CBase::ReadBits(unsigned numBits) { return BitDecoder.ReadBits(numBits); } -unsigned CBase::ReadBit() { return (unsigned)BitDecoder.ReadBits(1); } -HRESULT CBase::ReadBlock(UInt32 *charCounters, UInt32 blockSizeMax, CBlockProps *props) +enum EState { - NumBlocks++; - - if (props->randMode) - props->randMode = ReadBit() ? true : false; - props->origPtr = ReadBits(kNumOrigBits); - - // in original code it compares OrigPtr to (UInt32)(10 + blockSizeMax)) : why ? - if (props->origPtr >= blockSizeMax) - return S_FALSE; - - CMtf8Decoder mtf; - mtf.StartInit(); - - unsigned numInUse = 0; - { - Byte inUse16[16]; - unsigned i; - for (i = 0; i < 16; i++) - inUse16[i] = (Byte)ReadBit(); - for (i = 0; i < 256; i++) - if (inUse16[i >> 4]) - { - if (ReadBit()) - mtf.Add(numInUse++, (Byte)i); - } - if (numInUse == 0) - return S_FALSE; - // mtf.Init(numInUse); - } - unsigned alphaSize = numInUse + 2; - - unsigned numTables = ReadBits(kNumTablesBits); - if (numTables < kNumTablesMin || numTables > kNumTablesMax) - return S_FALSE; + STATE_STREAM_SIGNATURE, + STATE_BLOCK_SIGNATURE, + + STATE_BLOCK_START, + STATE_ORIG_BITS, + STATE_IN_USE, + STATE_IN_USE2, + STATE_NUM_TABLES, + STATE_NUM_SELECTORS, + STATE_SELECTORS, + STATE_LEVELS, - UInt32 numSelectors = ReadBits(kNumSelectorsBits); - if (numSelectors < 1 || numSelectors > kNumSelectorsMax) - return S_FALSE; + STATE_BLOCK_SYMBOLS, - { - Byte mtfPos[kNumTablesMax]; - unsigned t = 0; - do - mtfPos[t] = (Byte)t; - while (++t < numTables); - UInt32 i = 0; - do - { - unsigned j = 0; - while (ReadBit()) - if (++j >= numTables) - return S_FALSE; - Byte tmp = mtfPos[j]; - for (;j > 0; j--) - mtfPos[j] = mtfPos[j - 1]; - m_Selectors[i] = mtfPos[0] = tmp; - } - while (++i < numSelectors); - } + STATE_STREAM_FINISHED +}; - unsigned t = 0; - do - { - Byte lens[kMaxAlphaSize]; - unsigned len = (unsigned)ReadBits(kNumLevelsBits); - unsigned i; - for (i = 0; i < alphaSize; i++) - { - for (;;) - { - if (len < 1 || len > kMaxHuffmanLen) - return S_FALSE; - if (!ReadBit()) - break; - len++; - len -= (ReadBit() << 1); - } - lens[i] = (Byte)len; - } - for (; i < kMaxAlphaSize; i++) - lens[i] = 0; - if (!m_HuffmanDecoders[t].Build(lens)) - return S_FALSE; - } - while (++t < numTables); - { - for (unsigned i = 0; i < 256; i++) - charCounters[i] = 0; - } - - UInt32 blockSize = 0; - { - UInt32 groupIndex = 0; - UInt32 groupSize = 0; - CHuffmanDecoder *huffmanDecoder = 0; - unsigned runPower = 0; - UInt32 runCounter = 0; - - for (;;) - { - if (groupSize == 0) - { - if (groupIndex >= numSelectors) - return S_FALSE; - groupSize = kGroupSize; - huffmanDecoder = &m_HuffmanDecoders[m_Selectors[groupIndex++]]; - } - groupSize--; - - if (BitDecoder.ExtraBitsWereRead_Fast()) - break; +#define UPDATE_VAL_2(val, num_bits) { \ + val |= (UInt32)(*_buf) << (24 - num_bits); \ + num_bits += 8; \ + _buf++; \ +} - UInt32 nextSym = huffmanDecoder->Decode(&BitDecoder); - - if (nextSym < 2) - { - runCounter += ((UInt32)(nextSym + 1) << runPower++); - if (blockSizeMax - blockSize < runCounter) - return S_FALSE; - continue; - } - if (runCounter != 0) - { - UInt32 b = (UInt32)mtf.GetHead(); - charCounters[b] += runCounter; - do - charCounters[256 + blockSize++] = b; - while (--runCounter != 0); - runPower = 0; - } - if (nextSym <= (UInt32)numInUse) - { - UInt32 b = (UInt32)mtf.GetAndMove((unsigned)nextSym - 1); - if (blockSize >= blockSizeMax) - return S_FALSE; - charCounters[b]++; - charCounters[256 + blockSize++] = b; - } - else if (nextSym == (UInt32)numInUse + 1) - break; - else - return S_FALSE; - } +#define UPDATE_VAL UPDATE_VAL_2(VAL, NUM_BITS) - if (BitDecoder.ExtraBitsWereRead()) - return S_FALSE; - } - props->blockSize = blockSize; - return (props->origPtr < props->blockSize) ? S_OK : S_FALSE; +#define READ_BITS(res, num) { \ + while (_numBits < num) { \ + if (_buf == _lim) return SZ_OK; \ + UPDATE_VAL_2(_value, _numBits) } \ + res = _value >> (32 - num); \ + _value <<= num; \ + _numBits -= num; \ } -static void NO_INLINE DecodeBlock1(UInt32 *charCounters, UInt32 blockSize) -{ - { - UInt32 sum = 0; - for (UInt32 i = 0; i < 256; i++) - { - sum += charCounters[i]; - charCounters[i] = sum - charCounters[i]; - } - } - - UInt32 *tt = charCounters + 256; - // Compute the T^(-1) vector - UInt32 i = 0; - do - tt[charCounters[tt[i] & 0xFF]++] |= (i << 8); - while (++i < blockSize); +#define READ_BITS_8(res, num) { \ + if (_numBits < num) { \ + if (_buf == _lim) return SZ_OK; \ + UPDATE_VAL_2(_value, _numBits) } \ + res = _value >> (32 - num); \ + _value <<= num; \ + _numBits -= num; \ } -static UInt32 NO_INLINE DecodeBlock2(const UInt32 *tt, UInt32 blockSize, UInt32 OrigPtr, COutBuffer &m_OutStream) -{ - CBZip2Crc crc; - - // it's for speed optimization: prefetch & prevByte_init; - UInt32 tPos = tt[tt[OrigPtr] >> 8]; - unsigned prevByte = (unsigned)(tPos & 0xFF); - - unsigned numReps = 0; +#define READ_BIT(res) READ_BITS_8(res, 1) - do - { - unsigned b = (unsigned)(tPos & 0xFF); - tPos = tt[tPos >> 8]; - - if (numReps == kRleModeRepSize) - { - for (; b > 0; b--) - { - crc.UpdateByte(prevByte); - m_OutStream.WriteByte((Byte)prevByte); - } - numReps = 0; - continue; - } - if (b != prevByte) - numReps = 0; - numReps++; - prevByte = b; - crc.UpdateByte(b); - m_OutStream.WriteByte((Byte)b); - - /* - prevByte = b; - crc.UpdateByte(b); - m_OutStream.WriteByte((Byte)b); - for (; --blockSize != 0;) - { - b = (unsigned)(tPos & 0xFF); - tPos = tt[tPos >> 8]; - crc.UpdateByte(b); - m_OutStream.WriteByte((Byte)b); - if (b != prevByte) - { - prevByte = b; - continue; - } - if (--blockSize == 0) - break; - - b = (unsigned)(tPos & 0xFF); - tPos = tt[tPos >> 8]; - crc.UpdateByte(b); - m_OutStream.WriteByte((Byte)b); - if (b != prevByte) - { - prevByte = b; - continue; - } - if (--blockSize == 0) - break; - - b = (unsigned)(tPos & 0xFF); - tPos = tt[tPos >> 8]; - crc.UpdateByte(b); - m_OutStream.WriteByte((Byte)b); - if (b != prevByte) - { - prevByte = b; - continue; - } - --blockSize; - break; - } - if (blockSize == 0) - break; - b = (unsigned)(tPos & 0xFF); - tPos = tt[tPos >> 8]; - - for (; b > 0; b--) - { - crc.UpdateByte(prevByte); - m_OutStream.WriteByte((Byte)prevByte); - } - */ - } - while (--blockSize != 0); - return crc.GetDigest(); -} -static UInt32 NO_INLINE DecodeBlock2Rand(const UInt32 *tt, UInt32 blockSize, UInt32 OrigPtr, COutBuffer &m_OutStream) -{ - CBZip2Crc crc; - - UInt32 randIndex = 1; - UInt32 randToGo = kRandNums[0] - 2; - - unsigned numReps = 0; +#define VAL _value2 +// #define NUM_BITS _numBits2 +#define NUM_BITS _numBits +#define BLOCK_SIZE blockSize2 +#define RUN_COUNTER runCounter2 - // it's for speed optimization: prefetch & prevByte_init; - UInt32 tPos = tt[tt[OrigPtr] >> 8]; - unsigned prevByte = (unsigned)(tPos & 0xFF); - - do - { - unsigned b = (unsigned)(tPos & 0xFF); - tPos = tt[tPos >> 8]; - - { - if (randToGo == 0) - { - b ^= 1; - randToGo = kRandNums[randIndex++]; - randIndex &= 0x1FF; - } - randToGo--; - } - - if (numReps == kRleModeRepSize) - { - for (; b > 0; b--) - { - crc.UpdateByte(prevByte); - m_OutStream.WriteByte((Byte)prevByte); - } - numReps = 0; - continue; - } - if (b != prevByte) - numReps = 0; - numReps++; - prevByte = b; - crc.UpdateByte(b); - m_OutStream.WriteByte((Byte)b); - } - while (--blockSize != 0); - return crc.GetDigest(); -} +#define LOAD_LOCAL \ + UInt32 VAL = this->_value; \ + /* unsigned NUM_BITS = this->_numBits; */ \ + UInt32 BLOCK_SIZE = this->blockSize; \ + UInt32 RUN_COUNTER = this->runCounter; \ -static UInt32 NO_INLINE DecodeBlock(const CBlockProps &props, UInt32 *tt, COutBuffer &m_OutStream) -{ - if (props.randMode) - return DecodeBlock2Rand(tt, props.blockSize, props.origPtr, m_OutStream); - else - return DecodeBlock2 (tt, props.blockSize, props.origPtr, m_OutStream); -} +#define SAVE_LOCAL \ + this->_value = VAL; \ + /* this->_numBits = NUM_BITS; */ \ + this->blockSize = BLOCK_SIZE; \ + this->runCounter = RUN_COUNTER; \ -CDecoder::CDecoder() -{ - #ifndef _7ZIP_ST - m_States = 0; - m_NumThreadsPrev = 0; - NumThreads = 1; - #endif - _needInStreamInit = true; -} -#ifndef _7ZIP_ST -CDecoder::~CDecoder() +SRes CBitDecoder::ReadByte(int &b) { - Free(); + b = -1; + READ_BITS_8(b, 8) + return SZ_OK; } -#define RINOK_THREAD(x) { WRes __result_ = (x); if (__result_ != 0) return __result_; } -HRESULT CDecoder::Create() +NO_INLINE +SRes CBase::ReadStreamSignature2() { - RINOK_THREAD(CanProcessEvent.CreateIfNotCreated()); - RINOK_THREAD(CanStartWaitingEvent.CreateIfNotCreated()); - if (m_States != 0 && m_NumThreadsPrev == NumThreads) - return S_OK; - Free(); - MtMode = (NumThreads > 1); - m_NumThreadsPrev = NumThreads; - try - { - m_States = new CState[NumThreads]; - if (!m_States) - return E_OUTOFMEMORY; - } - catch(...) { return E_OUTOFMEMORY; } - for (UInt32 t = 0; t < NumThreads; t++) + for (;;) { - CState &ti = m_States[t]; - ti.Decoder = this; - if (MtMode) + unsigned b; + READ_BITS_8(b, 8) + + if ( (state2 == 0 && b != kArSig0) + || (state2 == 1 && b != kArSig1) + || (state2 == 2 && b != kArSig2) + || (state2 == 3 && (b <= kArSig3 || b > kArSig3 + kBlockSizeMultMax))) + return SZ_ERROR_DATA; + state2++; + + if (state2 == 4) { - HRESULT res = ti.Create(); - if (res != S_OK) - { - NumThreads = t; - Free(); - return res; - } + blockSizeMax = (UInt32)(b - kArSig3) * kBlockSizeStep; + CombinedCrc.Init(); + state = STATE_BLOCK_SIGNATURE; + state2 = 0; + return SZ_OK; } } - return S_OK; -} - -void CDecoder::Free() -{ - if (!m_States) - return; - CloseThreads = true; - CanProcessEvent.Set(); - for (UInt32 t = 0; t < NumThreads; t++) - { - CState &s = m_States[t]; - if (MtMode) - s.Thread.Wait(); - s.Free(); - } - delete []m_States; - m_States = 0; } -#endif bool IsEndSig(const Byte *p) throw() { @@ -505,490 +227,1551 @@ bool IsBlockSig(const Byte *p) throw() p[5] == kBlockSig5; } -HRESULT CDecoder::ReadSignature(UInt32 &crc) -{ - BzWasFinished = false; - crc = 0; - - Byte s[10]; - unsigned i; - for (i = 0; i < 10; i++) - s[i] = ReadByte(); - if (Base.BitDecoder.ExtraBitsWereRead()) - return S_FALSE; - - UInt32 v = 0; - for (i = 0; i < 4; i++) +NO_INLINE +SRes CBase::ReadBlockSignature2() +{ + while (state2 < 10) { - v <<= 8; - v |= s[6 + i]; + unsigned b; + READ_BITS_8(b, 8) + temp[state2] = (Byte)b; + state2++; } - crc = v; + crc = 0; + for (unsigned i = 0; i < 4; i++) + { + crc <<= 8; + crc |= temp[6 + i]; + } - if (IsBlockSig(s)) + if (IsBlockSig(temp)) { + if (!IsBz) + NumStreams++; + NumBlocks++; IsBz = true; CombinedCrc.Update(crc); - return S_OK; + state = STATE_BLOCK_START; + return SZ_OK; } + + if (!IsEndSig(temp)) + return SZ_ERROR_DATA; - if (!IsEndSig(s)) - return S_FALSE; - + if (!IsBz) + NumStreams++; IsBz = true; - BzWasFinished = true; + + if (_value != 0) + MinorError = true; + + AlignToByte(); + + state = STATE_STREAM_FINISHED; if (crc != CombinedCrc.GetDigest()) { - CrcError = true; - return S_FALSE; + StreamCrcError = true; + return SZ_ERROR_DATA; } - return S_OK; + return SZ_OK; } -HRESULT CDecoder::DecodeFile(ICompressProgressInfo *progress) + +NO_INLINE +SRes CBase::ReadBlock2() { - Progress = progress; - #ifndef _7ZIP_ST - RINOK(Create()); - for (UInt32 t = 0; t < NumThreads; t++) + if (state != STATE_BLOCK_SYMBOLS) { + PRIN("ReadBlock2") + + if (state == STATE_BLOCK_START) { - CState &s = m_States[t]; - if (!s.Alloc()) - return E_OUTOFMEMORY; - if (MtMode) + if (Props.randMode) { - RINOK(s.StreamWasFinishedEvent.Reset()); - RINOK(s.WaitingWasStartedEvent.Reset()); - RINOK(s.CanWriteEvent.Reset()); + READ_BIT(Props.randMode) } + state = STATE_ORIG_BITS; + // g_Tick = GetCpuTicks(); } - #else - if (!m_States[0].Alloc()) - return E_OUTOFMEMORY; - #endif - IsBz = false; + if (state == STATE_ORIG_BITS) + { + READ_BITS(Props.origPtr, kNumOrigBits) + if (Props.origPtr >= blockSizeMax) + return SZ_ERROR_DATA; + state = STATE_IN_USE; + } + + // why original code compares origPtr to (UInt32)(10 + blockSizeMax)) ? - /* - if (Base.BitDecoder.ExtraBitsWereRead()) - return E_FAIL; - */ + if (state == STATE_IN_USE) + { + READ_BITS(state2, 16) + state = STATE_IN_USE2; + state3 = 0; + numInUse = 0; + mtf.StartInit(); + } - Byte s[4]; - unsigned i; - for (i = 0; i < 4; i++) - s[i] = ReadByte(); - if (Base.BitDecoder.ExtraBitsWereRead()) - return S_FALSE; - - if (s[0] != kArSig0 || - s[1] != kArSig1 || - s[2] != kArSig2 || - s[3] <= kArSig3 || - s[3] > kArSig3 + kBlockSizeMultMax) - return S_FALSE; - - UInt32 dicSize = (UInt32)(s[3] - kArSig3) * kBlockSizeStep; - - CombinedCrc.Init(); - #ifndef _7ZIP_ST - if (MtMode) - { - NextBlockIndex = 0; - StreamWasFinished1 = StreamWasFinished2 = false; - CloseThreads = false; - CanStartWaitingEvent.Reset(); - m_States[0].CanWriteEvent.Set(); - BlockSizeMax = dicSize; - Result1 = Result2 = S_OK; - CanProcessEvent.Set(); - UInt32 t; - for (t = 0; t < NumThreads; t++) - m_States[t].StreamWasFinishedEvent.Lock(); - CanProcessEvent.Reset(); - CanStartWaitingEvent.Set(); - for (t = 0; t < NumThreads; t++) - m_States[t].WaitingWasStartedEvent.Lock(); - CanStartWaitingEvent.Reset(); - RINOK(Result2); - RINOK(Result1); - } - else - #endif + if (state == STATE_IN_USE2) { - CState &state = m_States[0]; - for (;;) - { - RINOK(SetRatioProgress(Base.BitDecoder.GetProcessedSize())); - UInt32 crc; - RINOK(ReadSignature(crc)); - if (BzWasFinished) - return S_OK; - - CBlockProps props; - props.randMode = true; - RINOK(Base.ReadBlock(state.Counters, dicSize, &props)); - DecodeBlock1(state.Counters, props.blockSize); - if (DecodeBlock(props, state.Counters + 256, m_OutStream) != crc) + for (; state3 < 256; state3++) + if (state2 & ((UInt32)0x8000 >> (state3 >> 4))) { - CrcError = true; - return S_FALSE; + unsigned b; + READ_BIT(b) + if (b) + mtf.Add(numInUse++, (Byte)state3); } - } + if (numInUse == 0) + return SZ_ERROR_DATA; + state = STATE_NUM_TABLES; } - return SetRatioProgress(Base.BitDecoder.GetProcessedSize()); -} - -HRESULT CDecoder::CodeReal(ISequentialInStream *inStream, ISequentialOutStream *outStream, - ICompressProgressInfo *progress) -{ - IsBz = false; - BzWasFinished = false; - CrcError = false; - try + + if (state == STATE_NUM_TABLES) + { + READ_BITS_8(numTables, kNumTablesBits) + state = STATE_NUM_SELECTORS; + if (numTables < kNumTablesMin || numTables > kNumTablesMax) + return SZ_ERROR_DATA; + } + + if (state == STATE_NUM_SELECTORS) { + READ_BITS(numSelectors, kNumSelectorsBits) + state = STATE_SELECTORS; + state2 = 0x543210; + state3 = 0; + state4 = 0; + // lbzip2 can write small number of additional selectors, + // 20.01: we allow big number of selectors here like bzip2-1.0.8 + if (numSelectors == 0 + // || numSelectors > kNumSelectorsMax_Decoder + ) + return SZ_ERROR_DATA; + } - if (!Base.BitDecoder.Create(kBufferSize)) - return E_OUTOFMEMORY; - if (!m_OutStream.Create(kBufferSize)) - return E_OUTOFMEMORY; + if (state == STATE_SELECTORS) + { + const unsigned kMtfBits = 4; + const UInt32 kMtfMask = (1 << kMtfBits) - 1; + do + { + for (;;) + { + unsigned b; + READ_BIT(b) + if (!b) + break; + if (++state4 >= numTables) + return SZ_ERROR_DATA; + } + const UInt32 tmp = (state2 >> (kMtfBits * state4)) & kMtfMask; + const UInt32 mask = ((UInt32)1 << ((state4 + 1) * kMtfBits)) - 1; + state4 = 0; + state2 = ((state2 << kMtfBits) & mask) | (state2 & ~mask) | tmp; + // 20.01: here we keep compatibility with bzip2-1.0.8 decoder: + if (state3 < kNumSelectorsMax) + selectors[state3] = (Byte)tmp; + } + while (++state3 < numSelectors); - if (inStream) - Base.BitDecoder.SetStream(inStream); + // we allowed additional dummy selector records filled above to support lbzip2's archives. + // but we still don't allow to use these additional dummy selectors in the code bellow + // bzip2 1.0.8 decoder also has similar restriction. - CDecoderFlusher flusher(this); + if (numSelectors > kNumSelectorsMax) + numSelectors = kNumSelectorsMax; - if (_needInStreamInit) - { - Base.BitDecoder.Init(); - _needInStreamInit = false; + state = STATE_LEVELS; + state2 = 0; + state3 = 0; } - _inStart = Base.BitDecoder.GetProcessedSize(); - Base.BitDecoder.AlignToByte(); + if (state == STATE_LEVELS) + { + do + { + if (state3 == 0) + { + READ_BITS_8(state3, kNumLevelsBits) + state4 = 0; + state5 = 0; + } + const unsigned alphaSize = numInUse + 2; + for (; state4 < alphaSize; state4++) + { + for (;;) + { + if (state3 < 1 || state3 > kMaxHuffmanLen) + return SZ_ERROR_DATA; + + if (state5 == 0) + { + unsigned b; + READ_BIT(b) + if (!b) + break; + } + + state5 = 1; + unsigned b; + READ_BIT(b) + + state5 = 0; + state3++; + state3 -= (b << 1); + } + lens[state4] = (Byte)state3; + state5 = 0; + } + + // 19.03: we use non-full Build() to support lbzip2 archives. + // lbzip2 2.5 can produce dummy tree, where lens[i] = kMaxHuffmanLen + for (unsigned i = state4; i < kMaxAlphaSize; i++) + lens[i] = 0; + if (!huffs[state2].Build(lens)) // k_BuildMode_Partial + return SZ_ERROR_DATA; + state3 = 0; + } + while (++state2 < numTables); - m_OutStream.SetStream(outStream); - m_OutStream.Init(); + { + UInt32 *counters = this->Counters; + for (unsigned i = 0; i < 256; i++) + counters[i] = 0; + } - RINOK(DecodeFile(progress)); - flusher.NeedFlush = false; - return Flush(); + state = STATE_BLOCK_SYMBOLS; + groupIndex = 0; + groupSize = kGroupSize; + runPower = 0; + runCounter = 0; + blockSize = 0; } - catch(const CInBufferException &e) { return e.ErrorCode; } - catch(const COutBufferException &e) { return e.ErrorCode; } - catch(...) { return E_FAIL; } -} + + if (state != STATE_BLOCK_SYMBOLS) + return SZ_ERROR_DATA; -STDMETHODIMP CDecoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 * /* inSize */, const UInt64 * /* outSize */, ICompressProgressInfo *progress) -{ - _needInStreamInit = true; - return CodeReal(inStream, outStream, progress); -} + // g_Ticks[3] += GetCpuTicks() - g_Tick; -HRESULT CDecoder::CodeResume(ISequentialOutStream *outStream, ICompressProgressInfo *progress) -{ - return CodeReal(NULL, outStream, progress); -} + } -STDMETHODIMP CDecoder::SetInStream(ISequentialInStream *inStream) -{ - Base.InStreamRef = inStream; - Base.BitDecoder.SetStream(inStream); - return S_OK; -} + { + LOAD_LOCAL + const CHuffmanDecoder *huf = &huffs[selectors[groupIndex]]; -STDMETHODIMP CDecoder::ReleaseInStream() -{ - Base.InStreamRef.Release(); - return S_OK; -} + for (;;) + { + if (groupSize == 0) + { + if (++groupIndex >= numSelectors) + return SZ_ERROR_DATA; + huf = &huffs[selectors[groupIndex]]; + groupSize = kGroupSize; + } -#ifndef _7ZIP_ST + if (NUM_BITS < kMaxHuffmanLen && _buf != _lim) { UPDATE_VAL + if (NUM_BITS < kMaxHuffmanLen && _buf != _lim) { UPDATE_VAL + if (NUM_BITS < kMaxHuffmanLen && _buf != _lim) { UPDATE_VAL }}} + + unsigned sym; + + #define MOV_POS(bs, len) \ + { \ + if (NUM_BITS < len) \ + { \ + SAVE_LOCAL \ + return SZ_OK; \ + } \ + VAL <<= len; \ + NUM_BITS -= (unsigned)len; \ + } -static THREAD_FUNC_DECL MFThread(void *p) { ((CState *)p)->ThreadFunc(); return 0; } + Z7_HUFF_DECODE_VAL_IN_HIGH32(sym, huf, kMaxHuffmanLen, kNumTableBits, + VAL, + Z7_HUFF_DECODE_ERROR_SYM_CHECK_YES, + { return SZ_ERROR_DATA; }, + MOV_POS, {}, bs) -HRESULT CState::Create() -{ - RINOK_THREAD(StreamWasFinishedEvent.CreateIfNotCreated()); - RINOK_THREAD(WaitingWasStartedEvent.CreateIfNotCreated()); - RINOK_THREAD(CanWriteEvent.CreateIfNotCreated()); - RINOK_THREAD(Thread.Create(MFThread, this)); - return S_OK; + groupSize--; + + if (sym < 2) + { + RUN_COUNTER += (UInt32)(sym + 1) << runPower; + runPower++; + if (blockSizeMax - BLOCK_SIZE < RUN_COUNTER) + return SZ_ERROR_DATA; + continue; + } + + UInt32 *counters = this->Counters; + if (RUN_COUNTER != 0) + { + UInt32 b = (UInt32)(mtf.Buf[0] & 0xFF); + counters[b] += RUN_COUNTER; + runPower = 0; + #ifdef BZIP2_BYTE_MODE + Byte *dest = (Byte *)(&counters[256 + kBlockSizeMax]) + BLOCK_SIZE; + const Byte *limit = dest + RUN_COUNTER; + BLOCK_SIZE += RUN_COUNTER; + RUN_COUNTER = 0; + do + { + dest[0] = (Byte)b; + dest[1] = (Byte)b; + dest[2] = (Byte)b; + dest[3] = (Byte)b; + dest += 4; + } + while (dest < limit); + #else + UInt32 *dest = &counters[256 + BLOCK_SIZE]; + const UInt32 *limit = dest + RUN_COUNTER; + BLOCK_SIZE += RUN_COUNTER; + RUN_COUNTER = 0; + do + { + dest[0] = b; + dest[1] = b; + dest[2] = b; + dest[3] = b; + dest += 4; + } + while (dest < limit); + #endif + } + + sym -= 1; + if (sym < numInUse) + { + if (BLOCK_SIZE >= blockSizeMax) + return SZ_ERROR_DATA; + + // UInt32 b = (UInt32)mtf.GetAndMove((unsigned)sym); + + const unsigned lim = sym >> Z7_MTF_MOVS; + const unsigned pos = (sym & Z7_MTF_MASK) << 3; + CMtfVar next = mtf.Buf[lim]; + CMtfVar prev = (next >> pos) & 0xFF; + + #ifdef BZIP2_BYTE_MODE + ((Byte *)(counters + 256 + kBlockSizeMax))[BLOCK_SIZE++] = (Byte)prev; + #else + (counters + 256)[BLOCK_SIZE++] = (UInt32)prev; + #endif + counters[prev]++; + + CMtfVar *m = mtf.Buf; + CMtfVar *mLim = m + lim; + if (lim != 0) + { + do + { + CMtfVar n0 = *m; + *m = (n0 << 8) | prev; + prev = (n0 >> (Z7_MTF_MASK << 3)); + } + while (++m != mLim); + } + + CMtfVar mask = (((CMtfVar)0x100 << pos) - 1); + *mLim = (next & ~mask) | (((next << 8) | prev) & mask); + continue; + } + + if (sym != numInUse) + return SZ_ERROR_DATA; + break; + } + + // we write additional item that will be read in DecodeBlock1 for prefetching + #ifdef BZIP2_BYTE_MODE + ((Byte *)(Counters + 256 + kBlockSizeMax))[BLOCK_SIZE] = 0; + #else + (counters + 256)[BLOCK_SIZE] = 0; + #endif + + SAVE_LOCAL + Props.blockSize = blockSize; + state = STATE_BLOCK_SIGNATURE; + state2 = 0; + + PRIN_VAL("origPtr", Props.origPtr); + PRIN_VAL("blockSize", Props.blockSize); + + return (Props.origPtr < Props.blockSize) ? SZ_OK : SZ_ERROR_DATA; + } +} + + +NO_INLINE +static void DecodeBlock1(UInt32 *counters, UInt32 blockSize) +{ + { + UInt32 sum = 0; + for (UInt32 i = 0; i < 256; i++) + { + const UInt32 v = counters[i]; + counters[i] = sum; + sum += v; + } + } + + UInt32 *tt = counters + 256; + // Compute the T^(-1) vector + + // blockSize--; + + #ifdef BZIP2_BYTE_MODE + + unsigned c = ((const Byte *)(tt + kBlockSizeMax))[0]; + + for (UInt32 i = 0; i < blockSize; i++) + { + unsigned c1 = c; + const UInt32 pos = counters[c]; + c = ((const Byte *)(tt + kBlockSizeMax))[(size_t)i + 1]; + counters[c1] = pos + 1; + tt[pos] = (i << 8) | ((const Byte *)(tt + kBlockSizeMax))[pos]; + } + + /* + // last iteration without next character prefetching + { + const UInt32 pos = counters[c]; + counters[c] = pos + 1; + tt[pos] = (blockSize << 8) | ((const Byte *)(tt + kBlockSizeMax))[pos]; + } + */ + + #else + + unsigned c = (unsigned)(tt[0] & 0xFF); + + for (UInt32 i = 0; i < blockSize; i++) + { + unsigned c1 = c; + const UInt32 pos = counters[c]; + c = (unsigned)(tt[(size_t)i + 1] & 0xFF); + counters[c1] = pos + 1; + tt[pos] |= (i << 8); + } + + /* + { + const UInt32 pos = counters[c]; + counters[c] = pos + 1; + tt[pos] |= (blockSize << 8); + } + */ + + #endif + + + /* + for (UInt32 i = 0; i < blockSize; i++) + { + #ifdef BZIP2_BYTE_MODE + const unsigned c = ((const Byte *)(tt + kBlockSizeMax))[i]; + const UInt32 pos = counters[c]++; + tt[pos] = (i << 8) | ((const Byte *)(tt + kBlockSizeMax))[pos]; + #else + const unsigned c = (unsigned)(tt[i] & 0xFF); + const UInt32 pos = counters[c]++; + tt[pos] |= (i << 8); + #endif + } + */ } -void CState::FinishStream() + +void CSpecState::Init(UInt32 origPtr, unsigned randMode) throw() { - Decoder->StreamWasFinished1 = true; - StreamWasFinishedEvent.Set(); - Decoder->CS.Leave(); - Decoder->CanStartWaitingEvent.Lock(); - WaitingWasStartedEvent.Set(); + _tPos = _tt[_tt[origPtr] >> 8]; + _prevByte = (unsigned)(_tPos & 0xFF); + _reps = 0; + _randIndex = 0; + _randToGo = -1; + if (randMode) + { + _randIndex = 1; + _randToGo = kRandNums[0] - 2; + } + _crc.Init(); } -void CState::ThreadFunc() + + +NO_INLINE +Byte * CSpecState::Decode(Byte *data, size_t size) throw() { + if (size == 0) + return data; + + unsigned prevByte = _prevByte; + int reps = _reps; + CBZip2Crc crc = _crc; + const Byte *lim = data + size; + + while (reps > 0) + { + reps--; + *data++ = (Byte)prevByte; + crc.UpdateByte(prevByte); + if (data == lim) + break; + } + + UInt32 tPos = _tPos; + UInt32 blockSize = _blockSize; + const UInt32 *tt = _tt; + + if (data != lim && blockSize) + for (;;) { - Decoder->CanProcessEvent.Lock(); - Decoder->CS.Enter(); - if (Decoder->CloseThreads) + unsigned b = (unsigned)(tPos & 0xFF); + tPos = tt[tPos >> 8]; + blockSize--; + + if (_randToGo >= 0) { - Decoder->CS.Leave(); - return; + if (_randToGo == 0) + { + b ^= 1; + _randToGo = kRandNums[_randIndex]; + _randIndex++; + _randIndex &= 0x1FF; + } + _randToGo--; } - if (Decoder->StreamWasFinished1) + + if (reps != -(int)kRleModeRepSize) { - FinishStream(); + if (b != prevByte) + reps = 0; + reps--; + prevByte = b; + *data++ = (Byte)b; + crc.UpdateByte(b); + if (data == lim || blockSize == 0) + break; continue; } - HRESULT res = S_OK; - UInt32 blockIndex = Decoder->NextBlockIndex; - UInt32 nextBlockIndex = blockIndex + 1; - if (nextBlockIndex == Decoder->NumThreads) - nextBlockIndex = 0; - Decoder->NextBlockIndex = nextBlockIndex; - UInt32 crc; - UInt64 packSize = 0; + reps = (int)b; + while (reps) + { + reps--; + *data++ = (Byte)prevByte; + crc.UpdateByte(prevByte); + if (data == lim) + break; + } + if (data == lim) + break; + if (blockSize == 0) + break; + } + + if (blockSize == 1 && reps == -(int)kRleModeRepSize) + { + unsigned b = (unsigned)(tPos & 0xFF); + tPos = tt[tPos >> 8]; + blockSize--; + + if (_randToGo >= 0) + { + if (_randToGo == 0) + { + b ^= 1; + _randToGo = kRandNums[_randIndex]; + _randIndex++; + _randIndex &= 0x1FF; + } + _randToGo--; + } + + reps = (int)b; + } + + _tPos = tPos; + _prevByte = prevByte; + _reps = reps; + _crc = crc; + _blockSize = blockSize; + + return data; +} + + +HRESULT CDecoder::Flush() +{ + if (_writeRes == S_OK) + { + _writeRes = WriteStream(_outStream, _outBuf, _outPos); + _outWritten += _outPos; + _outPos = 0; + } + return _writeRes; +} + + +NO_INLINE +HRESULT CDecoder::DecodeBlock(const CBlockProps &props) +{ + _calcedBlockCrc = 0; + _blockFinished = false; + + CSpecState block; + + block._blockSize = props.blockSize; + block._tt = _counters + 256; + + block.Init(props.origPtr, props.randMode); + + for (;;) + { + Byte *data = _outBuf + _outPos; + size_t size = kOutBufSize - _outPos; + + if (_outSizeDefined) + { + const UInt64 rem = _outSize - _outPosTotal; + if (size >= rem) + { + size = (size_t)rem; + if (size == 0) + return FinishMode ? S_FALSE : S_OK; + } + } + + TICKS_START + const size_t processed = (size_t)(block.Decode(data, size) - data); + TICKS_UPDATE(2) + + _outPosTotal += processed; + _outPos += processed; + + if (processed >= size) + { + RINOK(Flush()) + } + + if (block.Finished()) + { + _blockFinished = true; + _calcedBlockCrc = block._crc.GetDigest(); + return S_OK; + } + } +} + + +CDecoder::CDecoder(): + _outBuf(NULL), + FinishMode(false), + _outSizeDefined(false), + _counters(NULL), + _inBuf(NULL), + _inProcessed(0) +{ + #ifndef Z7_ST + MtMode = false; + NeedWaitScout = false; + // ScoutRes = S_OK; + #endif +} + + +CDecoder::~CDecoder() +{ + PRIN("\n~CDecoder()"); + + #ifndef Z7_ST + + if (Thread.IsCreated()) + { + WaitScout(); + + _block.StopScout = true; + + PRIN("\nScoutEvent.Set()"); + ScoutEvent.Set(); + + PRIN("\nThread.Wait()()"); + Thread.Wait_Close(); + PRIN("\n after Thread.Wait()()"); + + // if (ScoutRes != S_OK) throw ScoutRes; + } + + #endif + + BigFree(_counters); + MidFree(_outBuf); + MidFree(_inBuf); +} + + +HRESULT CDecoder::ReadInput() +{ + if (Base._buf != Base._lim || _inputFinished || _inputRes != S_OK) + return _inputRes; + + _inProcessed += (size_t)(Base._buf - _inBuf); + Base._buf = _inBuf; + Base._lim = _inBuf; + UInt32 size = 0; + _inputRes = Base.InStream->Read(_inBuf, kInBufSize, &size); + _inputFinished = (size == 0); + Base._lim = _inBuf + size; + return _inputRes; +} + + +void CDecoder::StartNewStream() +{ + Base.state = STATE_STREAM_SIGNATURE; + Base.state2 = 0; + Base.IsBz = false; +} + + +HRESULT CDecoder::ReadStreamSignature() +{ + for (;;) + { + RINOK(ReadInput()) + SRes res = Base.ReadStreamSignature2(); + if (res != SZ_OK) + return S_FALSE; + if (Base.state == STATE_BLOCK_SIGNATURE) + return S_OK; + if (_inputFinished) + { + Base.NeedMoreInput = true; + return S_FALSE; + } + } +} + + +HRESULT CDecoder::StartRead() +{ + StartNewStream(); + return ReadStreamSignature(); +} + + +HRESULT CDecoder::ReadBlockSignature() +{ + for (;;) + { + RINOK(ReadInput()) + + SRes res = Base.ReadBlockSignature2(); + + if (Base.state == STATE_STREAM_FINISHED) + Base.FinishedPackSize = GetInputProcessedSize(); + if (res != SZ_OK) + return S_FALSE; + if (Base.state != STATE_BLOCK_SIGNATURE) + return S_OK; + if (_inputFinished) + { + Base.NeedMoreInput = true; + return S_FALSE; + } + } +} + + +HRESULT CDecoder::ReadBlock() +{ + for (;;) + { + RINOK(ReadInput()) + + SRes res = Base.ReadBlock2(); + + if (res != SZ_OK) + return S_FALSE; + if (Base.state == STATE_BLOCK_SIGNATURE) + return S_OK; + if (_inputFinished) + { + Base.NeedMoreInput = true; + return S_FALSE; + } + } +} + + + +HRESULT CDecoder::DecodeStreams(ICompressProgressInfo *progress) +{ + { + #ifndef Z7_ST + _block.StopScout = false; + #endif + } + + RINOK(StartRead()) + + UInt64 inPrev = 0; + UInt64 outPrev = 0; + + { + #ifndef Z7_ST + CWaitScout_Releaser waitScout_Releaser(this); + + bool useMt = false; + #endif + + bool wasFinished = false; + + UInt32 crc = 0; + UInt32 nextCrc = 0; + HRESULT nextRes = S_OK; + + UInt64 packPos = 0; + CBlockProps props; - try + props.blockSize = 0; + + for (;;) { - res = Decoder->ReadSignature(crc); - if (res != S_OK) + if (progress) { - Decoder->Result1 = res; - FinishStream(); - continue; + const UInt64 outCur = GetOutProcessedSize(); + if (packPos - inPrev >= kProgressStep || outCur - outPrev >= kProgressStep) + { + RINOK(progress->SetRatioInfo(&packPos, &outCur)) + inPrev = packPos; + outPrev = outCur; + } + } + + if (props.blockSize == 0) + if (wasFinished || nextRes != S_OK) + return nextRes; + + if ( + #ifndef Z7_ST + !useMt && + #endif + !wasFinished && Base.state == STATE_BLOCK_SIGNATURE) + { + nextRes = ReadBlockSignature(); + nextCrc = Base.crc; + packPos = GetInputProcessedSize(); + + wasFinished = true; + + if (nextRes != S_OK) + continue; + + if (Base.state == STATE_STREAM_FINISHED) + { + if (!Base.DecodeAllStreams) + { + wasFinished = true; + continue; + } + + nextRes = StartRead(); + + if (Base.NeedMoreInput) + { + if (Base.state2 == 0) + Base.NeedMoreInput = false; + wasFinished = true; + nextRes = S_OK; + continue; + } + + if (nextRes != S_OK) + continue; + + wasFinished = false; + continue; + } + + wasFinished = false; + + #ifndef Z7_ST + if (MtMode) + if (props.blockSize != 0) + { + // we start multithreading, if next block is big enough. + const UInt32 k_Mt_BlockSize_Threshold = (1 << 12); // (1 << 13) + if (props.blockSize > k_Mt_BlockSize_Threshold) + { + if (!Thread.IsCreated()) + { + PRIN("=== MT_MODE"); + RINOK(CreateThread()) + } + useMt = true; + } + } + #endif } - if (Decoder->BzWasFinished) + + if (props.blockSize == 0) { - Decoder->Result1 = res; - FinishStream(); - continue; + crc = nextCrc; + + #ifndef Z7_ST + if (useMt) + { + PRIN("DecoderEvent.Lock()"); + { + WRes wres = DecoderEvent.Lock(); + if (wres != 0) + return HRESULT_FROM_WIN32(wres); + } + NeedWaitScout = false; + PRIN("-- DecoderEvent.Lock()"); + props = _block.Props; + nextCrc = _block.NextCrc; + if (_block.Crc_Defined) + crc = _block.Crc; + packPos = _block.PackPos; + wasFinished = _block.WasFinished; + RINOK(_block.Res) + } + else + #endif + { + if (Base.state != STATE_BLOCK_START) + return E_FAIL; + + TICKS_START + Base.Props.randMode = 1; + RINOK(ReadBlock()) + TICKS_UPDATE(0) + + props = Base.Props; + continue; + } } - props.randMode = true; - res = Decoder->Base.ReadBlock(Counters, Decoder->BlockSizeMax, &props); - if (res != S_OK) + if (props.blockSize != 0) + { + TICKS_START + DecodeBlock1(_counters, props.blockSize); + TICKS_UPDATE(1) + } + + #ifndef Z7_ST + if (useMt && !wasFinished) { - Decoder->Result1 = res; - FinishStream(); + /* + if (props.blockSize == 0) + { + // this codes switches back to single-threadMode + useMt = false; + PRIN("=== ST_MODE"); + continue; + } + */ + + PRIN("ScoutEvent.Set()"); + { + WRes wres = ScoutEvent.Set(); + if (wres != 0) + return HRESULT_FROM_WIN32(wres); + } + NeedWaitScout = true; + } + #endif + + if (props.blockSize == 0) continue; + + RINOK(DecodeBlock(props)) + + if (!_blockFinished) + return nextRes; + + props.blockSize = 0; + if (_calcedBlockCrc != crc) + { + BlockCrcError = true; + return S_FALSE; } - packSize = Decoder->Base.BitDecoder.GetProcessedSize(); } - catch(const CInBufferException &e) { res = e.ErrorCode; if (res == S_OK) res = E_FAIL; } - catch(...) { res = E_FAIL; } - if (res != S_OK) + } +} + + + + +bool CDecoder::CreateInputBufer() +{ + if (!_inBuf) + { + _inBuf = (Byte *)MidAlloc(kInBufSize); + if (!_inBuf) + return false; + Base._buf = _inBuf; + Base._lim = _inBuf; + } + if (!_counters) + { + const size_t size = (256 + kBlockSizeMax) * sizeof(UInt32) + #ifdef BZIP2_BYTE_MODE + + kBlockSizeMax + #endif + + 256; + _counters = (UInt32 *)::BigAlloc(size); + if (!_counters) + return false; + Base.Counters = _counters; + } + return true; +} + + +void CDecoder::InitOutSize(const UInt64 *outSize) +{ + _outPosTotal = 0; + + _outSizeDefined = false; + _outSize = 0; + if (outSize) + { + _outSize = *outSize; + _outSizeDefined = true; + } + + BlockCrcError = false; + + Base.InitNumStreams2(); +} + + +Z7_COM7F_IMF(CDecoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, + const UInt64 * /* inSize */, const UInt64 *outSize, ICompressProgressInfo *progress)) +{ + /* + { + RINOK(SetInStream(inStream)); + RINOK(SetOutStreamSize(outSize)); + + RINOK(CopyStream(this, outStream, progress)); + return ReleaseInStream(); + } + */ + + _inputFinished = false; + _inputRes = S_OK; + _writeRes = S_OK; + + try { + + InitOutSize(outSize); + + // we can request data from InputBuffer after Code(). + // so we init InputBuffer before any function return. + + InitInputBuffer(); + + if (!CreateInputBufer()) + return E_OUTOFMEMORY; + + if (!_outBuf) + { + _outBuf = (Byte *)MidAlloc(kOutBufSize); + if (!_outBuf) + return E_OUTOFMEMORY; + } + + Base.InStream = inStream; + + // InitInputBuffer(); + + _outStream = outStream; + _outWritten = 0; + _outPos = 0; + + HRESULT res = DecodeStreams(progress); + + Flush(); + + Base.InStream = NULL; + _outStream = NULL; + + /* + if (res == S_OK) + if (FinishMode && inSize && *inSize != GetInputProcessedSize()) + res = S_FALSE; + */ + + if (res != S_OK) + return res; + + } catch(...) { return E_FAIL; } + + return _writeRes; +} + + +Z7_COM7F_IMF(CDecoder::SetFinishMode(UInt32 finishMode)) +{ + FinishMode = (finishMode != 0); + return S_OK; +} + + +Z7_COM7F_IMF(CDecoder::GetInStreamProcessedSize(UInt64 *value)) +{ + *value = GetInStreamSize(); + return S_OK; +} + + +Z7_COM7F_IMF(CDecoder::ReadUnusedFromInBuf(void *data, UInt32 size, UInt32 *processedSize)) +{ + Base.AlignToByte(); + UInt32 i; + for (i = 0; i < size; i++) + { + int b; + Base.ReadByte(b); + if (b < 0) + break; + ((Byte *)data)[i] = (Byte)b; + } + if (processedSize) + *processedSize = i; + return S_OK; +} + + +#ifndef Z7_ST + +#define PRIN_MT(s) PRIN(" " s) + +// #define RINOK_THREAD(x) { WRes __result_ = (x); if (__result_ != 0) return __result_; } + +static THREAD_FUNC_DECL RunScout2(void *p) { ((CDecoder *)p)->RunScout(); return 0; } + +HRESULT CDecoder::CreateThread() +{ + WRes wres = DecoderEvent.CreateIfNotCreated_Reset(); + if (wres == 0) { wres = ScoutEvent.CreateIfNotCreated_Reset(); + if (wres == 0) { wres = Thread.Create(RunScout2, this); }} + return HRESULT_FROM_WIN32(wres); +} + +void CDecoder::RunScout() +{ + for (;;) + { { - Decoder->Result1 = res; - FinishStream(); - continue; + PRIN_MT("ScoutEvent.Lock()") + WRes wres = ScoutEvent.Lock(); + PRIN_MT("-- ScoutEvent.Lock()") + if (wres != 0) + { + // ScoutRes = wres; + return; + } } - Decoder->CS.Leave(); + CBlock &block = _block; - DecodeBlock1(Counters, props.blockSize); + if (block.StopScout) + { + // ScoutRes = S_OK; + return; + } + + block.Res = S_OK; + block.WasFinished = false; + + HRESULT res = S_OK; - bool needFinish = true; try { - Decoder->m_States[blockIndex].CanWriteEvent.Lock(); - needFinish = Decoder->StreamWasFinished2; - if (!needFinish) + UInt64 packPos = GetInputProcessedSize(); + + block.Props.blockSize = 0; + block.Crc_Defined = false; + // block.NextCrc_Defined = false; + block.NextCrc = 0; + + for (;;) { - if (DecodeBlock(props, Counters + 256, Decoder->m_OutStream) == crc) - res = Decoder->SetRatioProgress(packSize); - else - res = S_FALSE; + if (Base.state == STATE_BLOCK_SIGNATURE) + { + res = ReadBlockSignature(); + + if (res != S_OK) + break; + + if (block.Props.blockSize == 0) + { + block.Crc = Base.crc; + block.Crc_Defined = true; + } + else + { + block.NextCrc = Base.crc; + // block.NextCrc_Defined = true; + } + + continue; + } + + if (Base.state == STATE_BLOCK_START) + { + if (block.Props.blockSize != 0) + break; + + Base.Props.randMode = 1; + + res = ReadBlock(); + + PRIN_MT("-- Base.ReadBlock") + if (res != S_OK) + break; + block.Props = Base.Props; + continue; + } + + if (Base.state == STATE_STREAM_FINISHED) + { + if (!Base.DecodeAllStreams) + { + block.WasFinished = true; + break; + } + + res = StartRead(); + + if (Base.NeedMoreInput) + { + if (Base.state2 == 0) + Base.NeedMoreInput = false; + block.WasFinished = true; + res = S_OK; + break; + } + + if (res != S_OK) + break; + + if (GetInputProcessedSize() - packPos > 0) // kProgressStep + break; + continue; + } + + // throw 1; + res = E_FAIL; + break; } } - catch(const COutBufferException &e) { res = e.ErrorCode; if (res == S_OK) res = E_FAIL; } - catch(...) { res = E_FAIL; } + + catch (...) { res = E_FAIL; } + if (res != S_OK) { - Decoder->Result2 = res; - Decoder->StreamWasFinished2 = true; + PRIN_MT("error") + block.Res = res; + block.WasFinished = true; } - Decoder->m_States[nextBlockIndex].CanWriteEvent.Set(); - if (res != S_OK || needFinish) + + block.PackPos = GetInputProcessedSize(); + PRIN_MT("DecoderEvent.Set()") + WRes wres = DecoderEvent.Set(); + if (wres != 0) { - StreamWasFinishedEvent.Set(); - Decoder->CanStartWaitingEvent.Lock(); - WaitingWasStartedEvent.Set(); + // ScoutRes = wres; + return; } } } -STDMETHODIMP CDecoder::SetNumberOfThreads(UInt32 numThreads) + +Z7_COM7F_IMF(CDecoder::SetNumberOfThreads(UInt32 numThreads)) { - NumThreads = numThreads; - if (NumThreads < 1) - NumThreads = 1; - if (NumThreads > kNumThreadsMax) - NumThreads = kNumThreadsMax; + MtMode = (numThreads > 1); + + #ifndef BZIP2_BYTE_MODE + MtMode = false; + #endif + + // MtMode = false; return S_OK; } #endif -HRESULT CDecoder::SetRatioProgress(UInt64 packSize) -{ - if (!Progress) - return S_OK; - packSize -= _inStart; - UInt64 unpackSize = m_OutStream.GetProcessedSize(); - return Progress->SetRatioInfo(&packSize, &unpackSize); -} -// ---------- NSIS ---------- +#ifndef Z7_NO_READ_FROM_CODER -enum -{ - NSIS_STATE_INIT, - NSIS_STATE_NEW_BLOCK, - NSIS_STATE_DATA, - NSIS_STATE_FINISHED, - NSIS_STATE_ERROR -}; -STDMETHODIMP CNsisDecoder::SetInStream(ISequentialInStream *inStream) +Z7_COM7F_IMF(CDecoder::SetInStream(ISequentialInStream *inStream)) { Base.InStreamRef = inStream; - Base.BitDecoder.SetStream(inStream); + Base.InStream = inStream; return S_OK; } -STDMETHODIMP CNsisDecoder::ReleaseInStream() + + +Z7_COM7F_IMF(CDecoder::ReleaseInStream()) { Base.InStreamRef.Release(); + Base.InStream = NULL; return S_OK; } -STDMETHODIMP CNsisDecoder::SetOutStreamSize(const UInt64 * /* outSize */) + + +Z7_COM7F_IMF(CDecoder::SetOutStreamSize(const UInt64 *outSize)) { - _nsisState = NSIS_STATE_INIT; + InitOutSize(outSize); + + InitInputBuffer(); + + if (!CreateInputBufer()) + return E_OUTOFMEMORY; + + // InitInputBuffer(); + + StartNewStream(); + + _blockFinished = true; + + ErrorResult = S_OK; + + _inputFinished = false; + _inputRes = S_OK; + return S_OK; } -STDMETHODIMP CNsisDecoder::Read(void *data, UInt32 size, UInt32 *processedSize) -{ - try { + +Z7_COM7F_IMF(CDecoder::Read(void *data, UInt32 size, UInt32 *processedSize)) +{ *processedSize = 0; - if (_nsisState == NSIS_STATE_FINISHED) - return S_OK; - if (_nsisState == NSIS_STATE_ERROR) - return S_FALSE; - if (size == 0) - return S_OK; - CState &state = m_State; + try { - if (_nsisState == NSIS_STATE_INIT) - { - if (!Base.BitDecoder.Create(kBufferSize)) - return E_OUTOFMEMORY; - if (!state.Alloc()) - return E_OUTOFMEMORY; - Base.BitDecoder.Init(); - _nsisState = NSIS_STATE_NEW_BLOCK; - } + if (ErrorResult != S_OK) + return ErrorResult; - if (_nsisState == NSIS_STATE_NEW_BLOCK) + for (;;) { - Byte b = (Byte)Base.ReadBits(8); - if (b == kFinSig0) + if (Base.state == STATE_STREAM_FINISHED) + { + if (!Base.DecodeAllStreams) + return ErrorResult; + StartNewStream(); + continue; + } + + if (Base.state == STATE_STREAM_SIGNATURE) + { + ErrorResult = ReadStreamSignature(); + + if (Base.NeedMoreInput) + if (Base.state2 == 0 && Base.NumStreams != 0) + { + Base.NeedMoreInput = false; + ErrorResult = S_OK; + return S_OK; + } + if (ErrorResult != S_OK) + return ErrorResult; + continue; + } + + if (_blockFinished && Base.state == STATE_BLOCK_SIGNATURE) { - _nsisState = NSIS_STATE_FINISHED; + ErrorResult = ReadBlockSignature(); + + if (ErrorResult != S_OK) + return ErrorResult; + + continue; + } + + if (_outSizeDefined) + { + const UInt64 rem = _outSize - _outPosTotal; + if (size >= rem) + size = (UInt32)rem; + } + if (size == 0) return S_OK; + + if (_blockFinished) + { + if (Base.state != STATE_BLOCK_START) + { + ErrorResult = E_FAIL; + return ErrorResult; + } + + Base.Props.randMode = 1; + ErrorResult = ReadBlock(); + + if (ErrorResult != S_OK) + return ErrorResult; + + DecodeBlock1(_counters, Base.Props.blockSize); + + _spec._blockSize = Base.Props.blockSize; + _spec._tt = _counters + 256; + _spec.Init(Base.Props.origPtr, Base.Props.randMode); + + _blockFinished = false; } - if (b != kBlockSig0) + { - _nsisState = NSIS_STATE_ERROR; - return S_FALSE; + Byte *ptr = _spec.Decode((Byte *)data, size); + + const UInt32 processed = (UInt32)(ptr - (Byte *)data); + data = ptr; + size -= processed; + (*processedSize) += processed; + _outPosTotal += processed; + + if (_spec.Finished()) + { + _blockFinished = true; + if (Base.crc != _spec._crc.GetDigest()) + { + BlockCrcError = true; + ErrorResult = S_FALSE; + return ErrorResult; + } + } } - CBlockProps props; - props.randMode = false; - RINOK(Base.ReadBlock(state.Counters, 9 * kBlockSizeStep, &props)); - _blockSize = props.blockSize; - DecodeBlock1(state.Counters, props.blockSize); - const UInt32 *tt = state.Counters + 256; - _tPos = tt[tt[props.origPtr] >> 8]; - _prevByte = (unsigned)(_tPos & 0xFF); - _numReps = 0; - _repRem = 0; - _nsisState = NSIS_STATE_DATA; } - UInt32 tPos = _tPos; - unsigned prevByte = _prevByte; - unsigned numReps = _numReps; - UInt32 blockSize = _blockSize; - const UInt32 *tt = state.Counters + 256; + } catch(...) { ErrorResult = S_FALSE; return S_FALSE; } +} - while (_repRem) - { - _repRem--; - *(Byte *)data = (Byte)prevByte; - data = (Byte *)data + 1; - (*processedSize)++; - if (--size == 0) - return S_OK; - } - if (blockSize == 0) - { - _nsisState = NSIS_STATE_NEW_BLOCK; + +// ---------- NSIS ---------- + +Z7_COM7F_IMF(CNsisDecoder::Read(void *data, UInt32 size, UInt32 *processedSize)) +{ + *processedSize = 0; + + try { + + if (ErrorResult != S_OK) + return ErrorResult; + + if (Base.state == STATE_STREAM_FINISHED) return S_OK; + + if (Base.state == STATE_STREAM_SIGNATURE) + { + Base.blockSizeMax = 9 * kBlockSizeStep; + Base.state = STATE_BLOCK_SIGNATURE; + // Base.state2 = 0; } - do + for (;;) { - unsigned b = (unsigned)(tPos & 0xFF); - tPos = tt[tPos >> 8]; - blockSize--; + if (_blockFinished && Base.state == STATE_BLOCK_SIGNATURE) + { + ErrorResult = ReadInput(); + if (ErrorResult != S_OK) + return ErrorResult; + + int b; + Base.ReadByte(b); + if (b < 0) + { + ErrorResult = S_FALSE; + return ErrorResult; + } + + if (b == kFinSig0) + { + /* + if (!Base.AreRemainByteBitsEmpty()) + ErrorResult = S_FALSE; + */ + Base.state = STATE_STREAM_FINISHED; + return ErrorResult; + } + + if (b != kBlockSig0) + { + ErrorResult = S_FALSE; + return ErrorResult; + } + + Base.state = STATE_BLOCK_START; + } + + if (_outSizeDefined) + { + const UInt64 rem = _outSize - _outPosTotal; + if (size >= rem) + size = (UInt32)rem; + } + if (size == 0) + return S_OK; - if (numReps == kRleModeRepSize) + if (_blockFinished) { - numReps = 0; - while (b) + if (Base.state != STATE_BLOCK_START) { - b--; - *(Byte *)data = (Byte)prevByte; - data = (Byte *)data + 1; - (*processedSize)++; - if (--size == 0) - break; + ErrorResult = E_FAIL; + return ErrorResult; } - _repRem = b; - continue; + + Base.Props.randMode = 0; + ErrorResult = ReadBlock(); + + if (ErrorResult != S_OK) + return ErrorResult; + + DecodeBlock1(_counters, Base.Props.blockSize); + + _spec._blockSize = Base.Props.blockSize; + _spec._tt = _counters + 256; + _spec.Init(Base.Props.origPtr, Base.Props.randMode); + + _blockFinished = false; + } + + { + Byte *ptr = _spec.Decode((Byte *)data, size); + + const UInt32 processed = (UInt32)(ptr - (Byte *)data); + data = ptr; + size -= processed; + (*processedSize) += processed; + _outPosTotal += processed; + + if (_spec.Finished()) + _blockFinished = true; } - if (b != prevByte) - numReps = 0; - numReps++; - prevByte = b; - *(Byte *)data = (Byte)b; - data = (Byte *)data + 1; - (*processedSize)++; - size--; } - while (size && blockSize); - _tPos = tPos; - _prevByte = prevByte; - _numReps = numReps; - _blockSize = blockSize; - return S_OK; - } - catch(const CInBufferException &e) { return e.ErrorCode; } - catch(...) { return S_FALSE; } + } catch(...) { ErrorResult = S_FALSE; return S_FALSE; } } +#endif + }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/BZip2Decoder.h b/src/plugins/7zip/7za/cpp/7zip/Compress/BZip2Decoder.h index 8703d572..4adc564c 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/BZip2Decoder.h +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/BZip2Decoder.h @@ -1,24 +1,24 @@ // Compress/BZip2Decoder.h -#ifndef __COMPRESS_BZIP2_DECODER_H -#define __COMPRESS_BZIP2_DECODER_H +#ifndef ZIP7_INC_COMPRESS_BZIP2_DECODER_H +#define ZIP7_INC_COMPRESS_BZIP2_DECODER_H #include "../../Common/MyCom.h" -#ifndef _7ZIP_ST +// #define Z7_NO_READ_FROM_CODER +// #define Z7_ST + +#ifndef Z7_ST #include "../../Windows/Synchronization.h" #include "../../Windows/Thread.h" #endif #include "../ICoder.h" -#include "../Common/InBuffer.h" -#include "../Common/OutBuffer.h" - -#include "BitmDecoder.h" #include "BZip2Const.h" #include "BZip2Crc.h" #include "HuffmanDecoder.h" +#include "Mtf8.h" namespace NCompress { namespace NBZip2 { @@ -26,213 +26,363 @@ namespace NBZip2 { bool IsEndSig(const Byte *p) throw(); bool IsBlockSig(const Byte *p) throw(); -typedef NCompress::NHuffman::CDecoder CHuffmanDecoder; +const unsigned kNumTableBits = 9; -class CDecoder; +typedef NHuffman::CDecoder CHuffmanDecoder; -struct CState -{ - UInt32 *Counters; - #ifndef _7ZIP_ST +struct CBlockProps +{ + UInt32 blockSize; + UInt32 origPtr; + unsigned randMode; + + CBlockProps(): blockSize(0), origPtr(0), randMode(0) {} +}; - CDecoder *Decoder; - NWindows::CThread Thread; - bool m_OptimizeNumTables; - NWindows::NSynchronization::CAutoResetEvent StreamWasFinishedEvent; - NWindows::NSynchronization::CAutoResetEvent WaitingWasStartedEvent; +struct CBitDecoder +{ + unsigned _numBits; + UInt32 _value; + const Byte *_buf; + const Byte *_lim; - // it's not member of this thread. We just need one event per thread - NWindows::NSynchronization::CAutoResetEvent CanWriteEvent; + void InitBitDecoder() + { + _numBits = 0; + _value = 0; + } - Byte MtPad[1 << 8]; // It's pad for Multi-Threading. Must be >= Cache_Line_Size. + void AlignToByte() + { + unsigned bits = _numBits & 7; + _numBits -= bits; + _value <<= bits; + } - HRESULT Create(); - void FinishStream(); - void ThreadFunc(); + /* + bool AreRemainByteBitsEmpty() const + { + unsigned bits = _numBits & 7; + if (bits != 0) + return (_value >> (32 - bits)) == 0; + return true; + } + */ - #endif + SRes ReadByte(int &b); - CState(): Counters(0) {} - ~CState() { Free(); } - bool Alloc(); - void Free(); + CBitDecoder(): + _buf(NULL), + _lim(NULL) + { + InitBitDecoder(); + } }; -struct CBlockProps + +// 19.03: we allow additional 8 selectors to support files created by lbzip2. +const UInt32 kNumSelectorsMax_Decoder = kNumSelectorsMax + 8; + +struct CBase: public CBitDecoder { + unsigned numInUse; + UInt32 groupIndex; + UInt32 groupSize; + unsigned runPower; + UInt32 runCounter; UInt32 blockSize; - UInt32 origPtr; - bool randMode; - - CBlockProps(): blockSize(0), origPtr(0), randMode(false) {} -}; -struct CBase -{ - CMyComPtr InStreamRef; - NBitm::CDecoder BitDecoder; + UInt32 *Counters; + UInt32 blockSizeMax; + + unsigned state; + UInt32 state2; + unsigned state3; + unsigned state4; + unsigned state5; + unsigned numTables; + UInt32 numSelectors; + + CBlockProps Props; private: - Byte m_Selectors[kNumSelectorsMax]; - CHuffmanDecoder m_HuffmanDecoders[kNumTablesMax]; + CMtf8Decoder mtf; + Byte selectors[kNumSelectorsMax_Decoder]; + CHuffmanDecoder huffs[kNumTablesMax]; + + Byte lens[kMaxAlphaSize]; + + Byte temp[10]; public: + UInt32 crc; + CBZip2CombinedCrc CombinedCrc; + + bool IsBz; + bool StreamCrcError; + bool MinorError; + bool NeedMoreInput; + + bool DecodeAllStreams; + + UInt64 NumStreams; UInt64 NumBlocks; + UInt64 FinishedPackSize; - CBase(): NumBlocks(0) {} - UInt32 ReadBits(unsigned numBits); - unsigned ReadBit(); - void InitNumBlocks() { NumBlocks = 0; } + ISequentialInStream *InStream; - /* - ReadBlock() props->randMode: - in: need read randMode bit, - out: randMode status - */ - HRESULT ReadBlock(UInt32 *charCounters, UInt32 blockSizeMax, CBlockProps *props); + #ifndef Z7_NO_READ_FROM_CODER + CMyComPtr InStreamRef; + #endif + + CBase(): + StreamCrcError(false), + MinorError(false), + NeedMoreInput(false), + + DecodeAllStreams(false), + + NumStreams(0), + NumBlocks(0), + FinishedPackSize(0) + {} + + void InitNumStreams2() + { + StreamCrcError = false; + MinorError = false; + NeedMoreInput = 0; + NumStreams = 0; + NumBlocks = 0; + FinishedPackSize = 0; + } + + SRes ReadStreamSignature2(); + SRes ReadBlockSignature2(); + + /* ReadBlock2() : Props->randMode: + in: need read randMode bit + out: randMode status */ + SRes ReadBlock2(); }; -class CDecoder : + +class CSpecState +{ + UInt32 _tPos; + unsigned _prevByte; + int _reps; + +public: + CBZip2Crc _crc; + UInt32 _blockSize; + UInt32 *_tt; + + int _randToGo; + unsigned _randIndex; + + void Init(UInt32 origPtr, unsigned randMode) throw(); + + bool Finished() const { return _reps <= 0 && _blockSize == 0; } + + Byte *Decode(Byte *data, size_t size) throw(); +}; + + + + +class CDecoder: public ICompressCoder, - #ifndef _7ZIP_ST + public ICompressSetFinishMode, + public ICompressGetInStreamProcessedSize, + public ICompressReadUnusedFromInBuf, +#ifndef Z7_NO_READ_FROM_CODER + public ICompressSetInStream, + public ICompressSetOutStreamSize, + public ISequentialInStream, +#endif +#ifndef Z7_ST public ICompressSetCoderMt, - #endif +#endif public CMyUnknownImp { + Z7_COM_QI_BEGIN2(ICompressCoder) + Z7_COM_QI_ENTRY(ICompressSetFinishMode) + Z7_COM_QI_ENTRY(ICompressGetInStreamProcessedSize) + Z7_COM_QI_ENTRY(ICompressReadUnusedFromInBuf) +#ifndef Z7_NO_READ_FROM_CODER + Z7_COM_QI_ENTRY(ICompressSetInStream) + Z7_COM_QI_ENTRY(ICompressSetOutStreamSize) + Z7_COM_QI_ENTRY(ISequentialInStream) +#endif +#ifndef Z7_ST + Z7_COM_QI_ENTRY(ICompressSetCoderMt) +#endif + Z7_COM_QI_END + Z7_COM_ADDREF_RELEASE + + Z7_IFACE_COM7_IMP(ICompressCoder) + Z7_IFACE_COM7_IMP(ICompressSetFinishMode) + Z7_IFACE_COM7_IMP(ICompressGetInStreamProcessedSize) + Z7_IFACE_COM7_IMP(ICompressReadUnusedFromInBuf) +#ifndef Z7_NO_READ_FROM_CODER + Z7_IFACE_COM7_IMP(ICompressSetInStream) + Z7_IFACE_COM7_IMP(ICompressSetOutStreamSize) + Z7_IFACE_COM7_IMP_NONFINAL(ISequentialInStream) +#endif public: - COutBuffer m_OutStream; - Byte MtPad[1 << 8]; // It's pad for Multi-Threading. Must be >= Cache_Line_Size. +#ifndef Z7_ST + Z7_IFACE_COM7_IMP(ICompressSetCoderMt) +#endif - CBase Base; +private: + Byte *_outBuf; + size_t _outPos; + UInt64 _outWritten; + ISequentialOutStream *_outStream; + HRESULT _writeRes; - UInt64 _inStart; +protected: + HRESULT ErrorResult; // for ISequentialInStream::Read mode only -private: +public: - bool _needInStreamInit; + UInt32 _calcedBlockCrc; + bool _blockFinished; + bool BlockCrcError; - Byte ReadByte(); + bool FinishMode; + bool _outSizeDefined; + UInt64 _outSize; + UInt64 _outPosTotal; - HRESULT DecodeFile(ICompressProgressInfo *progress); - HRESULT CodeReal(ISequentialInStream *inStream, ISequentialOutStream *outStream, ICompressProgressInfo *progress); - - class CDecoderFlusher + CSpecState _spec; + UInt32 *_counters; + + #ifndef Z7_ST + + struct CBlock { - CDecoder *_decoder; - public: - bool NeedFlush; - CDecoderFlusher(CDecoder *decoder): _decoder(decoder), NeedFlush(true) {} - ~CDecoderFlusher() - { - if (NeedFlush) - _decoder->Flush(); - } + bool StopScout; + + bool WasFinished; + bool Crc_Defined; + // bool NextCrc_Defined; + + UInt32 Crc; + UInt32 NextCrc; + HRESULT Res; + UInt64 PackPos; + + CBlockProps Props; }; -public: - CBZip2CombinedCrc CombinedCrc; - ICompressProgressInfo *Progress; - - #ifndef _7ZIP_ST - CState *m_States; - UInt32 m_NumThreadsPrev; + CBlock _block; - NWindows::NSynchronization::CManualResetEvent CanProcessEvent; - NWindows::NSynchronization::CCriticalSection CS; - UInt32 NumThreads; + bool NeedWaitScout; bool MtMode; - UInt32 NextBlockIndex; - bool CloseThreads; - bool StreamWasFinished1; - bool StreamWasFinished2; - NWindows::NSynchronization::CManualResetEvent CanStartWaitingEvent; - HRESULT Result1; - HRESULT Result2; + NWindows::CThread Thread; + NWindows::NSynchronization::CAutoResetEvent DecoderEvent; + NWindows::NSynchronization::CAutoResetEvent ScoutEvent; + // HRESULT ScoutRes; + + Byte MtPad[1 << 7]; // It's pad for Multi-Threading. Must be >= Cache_Line_Size. - UInt32 BlockSizeMax; - ~CDecoder(); - HRESULT Create(); - void Free(); + void RunScout(); - #else - CState m_States[1]; - #endif + void WaitScout() + { + if (NeedWaitScout) + { + DecoderEvent.Lock(); + NeedWaitScout = false; + } + } - bool IsBz; - bool BzWasFinished; // bzip stream was finished with end signature - bool CrcError; // it can CRC error of block or CRC error of whole stream. + class CWaitScout_Releaser + { + CDecoder *_decoder; + public: + CWaitScout_Releaser(CDecoder *decoder): _decoder(decoder) {} + ~CWaitScout_Releaser() { _decoder->WaitScout(); } + }; - CDecoder(); + HRESULT CreateThread(); - HRESULT SetRatioProgress(UInt64 packSize); - HRESULT ReadSignature(UInt32 &crc); + #endif - HRESULT Flush() { return m_OutStream.Flush(); } + Byte *_inBuf; + UInt64 _inProcessed; + bool _inputFinished; + HRESULT _inputRes; - MY_QUERYINTERFACE_BEGIN2(ICompressCoder) - #ifndef _7ZIP_ST - MY_QUERYINTERFACE_ENTRY(ICompressSetCoderMt) - #endif + CBase Base; - MY_QUERYINTERFACE_END - MY_ADDREF_RELEASE + bool GetCrcError() const { return BlockCrcError || Base.StreamCrcError; } + void InitOutSize(const UInt64 *outSize); - STDMETHOD(Code)(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress); + bool CreateInputBufer(); + + void InitInputBuffer() + { + // We use InitInputBuffer() before stream init. + // So don't read from stream here + _inProcessed = 0; + Base._buf = _inBuf; + Base._lim = _inBuf; + Base.InitBitDecoder(); + } + + UInt64 GetInputProcessedSize() const + { + // for NSIS case : we need also look the number of bits in bitDecoder + return _inProcessed + (size_t)(Base._buf - _inBuf); + } - STDMETHOD(SetInStream)(ISequentialInStream *inStream); - STDMETHOD(ReleaseInStream)(); + UInt64 GetInStreamSize() const + { + return _inProcessed + (size_t)(Base._buf - _inBuf) - (Base._numBits >> 3); + } - HRESULT CodeResume(ISequentialOutStream *outStream, ICompressProgressInfo *progress); + UInt64 GetOutProcessedSize() const { return _outWritten + _outPos; } - UInt64 GetStreamSize() const { return Base.BitDecoder.GetStreamSize(); } - UInt64 GetInputProcessedSize() const { return Base.BitDecoder.GetProcessedSize(); } + HRESULT ReadInput(); - void InitNumBlocks() { Base.InitNumBlocks(); } - UInt64 GetNumBlocks() const { return Base.NumBlocks; } + void StartNewStream(); - #ifndef _7ZIP_ST - STDMETHOD(SetNumberOfThreads)(UInt32 numThreads); - #endif -}; + HRESULT ReadStreamSignature(); + HRESULT StartRead(); + HRESULT ReadBlockSignature(); + HRESULT ReadBlock(); -class CNsisDecoder : - public ISequentialInStream, - public ICompressSetInStream, - public ICompressSetOutStreamSize, - public CMyUnknownImp -{ - CBase Base; + HRESULT Flush(); + HRESULT DecodeBlock(const CBlockProps &props); + HRESULT DecodeStreams(ICompressProgressInfo *progress); + + UInt64 GetNumStreams() const { return Base.NumStreams; } + UInt64 GetNumBlocks() const { return Base.NumBlocks; } + + CDecoder(); + virtual ~CDecoder(); +}; - CState m_State; - - int _nsisState; - UInt32 _tPos; - unsigned _prevByte; - unsigned _repRem; - unsigned _numReps; - UInt32 _blockSize; -public: - MY_QUERYINTERFACE_BEGIN2(ISequentialInStream) - MY_QUERYINTERFACE_ENTRY(ICompressSetInStream) - MY_QUERYINTERFACE_ENTRY(ICompressSetOutStreamSize) - MY_QUERYINTERFACE_END - MY_ADDREF_RELEASE +#ifndef Z7_NO_READ_FROM_CODER - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); - STDMETHOD(SetInStream)(ISequentialInStream *inStream); - STDMETHOD(ReleaseInStream)(); - STDMETHOD(SetOutStreamSize)(const UInt64 *outSize); +class CNsisDecoder Z7_final: public CDecoder +{ + Z7_IFACE_COM7_IMP(ISequentialInStream) }; +#endif + }} #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/BZip2Encoder.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/BZip2Encoder.cpp index e2225134..af0b3124 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/BZip2Encoder.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/BZip2Encoder.cpp @@ -6,32 +6,38 @@ #include "../../../C/BwtSort.h" #include "../../../C/HuffEnc.h" -#include "BZip2Crc.h" #include "BZip2Encoder.h" -#include "Mtf8.h" namespace NCompress { namespace NBZip2 { -const unsigned kMaxHuffmanLenForEncoding = 16; // it must be < kMaxHuffmanLen = 20 - -static const UInt32 kBufferSize = (1 << 17); +#define HUFFMAN_LEN 16 +#if HUFFMAN_LEN > Z7_HUFFMAN_LEN_MAX + #error Stop_Compiling_Bad_HUFFMAN_LEN_BZip2Encoder +#endif + +static const size_t kBufferSize = 1 << 17; static const unsigned kNumHuffPasses = 4; + bool CThreadInfo::Alloc() { - if (m_BlockSorterIndex == 0) + if (!m_BlockSorterIndex) { m_BlockSorterIndex = (UInt32 *)::BigAlloc(BLOCK_SORT_BUF_SIZE(kBlockSizeMax) * sizeof(UInt32)); - if (m_BlockSorterIndex == 0) + if (!m_BlockSorterIndex) return false; } - if (m_Block == 0) + if (!m_Block_Base) { - m_Block = (Byte *)::MidAlloc(kBlockSizeMax * 5 + kBlockSizeMax / 10 + (20 << 10)); - if (m_Block == 0) + const unsigned kPadSize = 1 << 7; // we need at least 1 byte backward padding, becuase we use (m_Block - 1) pointer; + m_Block_Base = (Byte *)::MidAlloc(kBlockSizeMax * 5 + + kBlockSizeMax / 10 + (20 << 10) + + kPadSize); + if (!m_Block_Base) return false; + m_Block = m_Block_Base + kPadSize; m_MtfArray = m_Block + kBlockSizeMax; m_TempArray = m_MtfArray + kBlockSizeMax * 2 + 2; } @@ -41,27 +47,35 @@ bool CThreadInfo::Alloc() void CThreadInfo::Free() { ::BigFree(m_BlockSorterIndex); - m_BlockSorterIndex = 0; - ::MidFree(m_Block); - m_Block = 0; + m_BlockSorterIndex = NULL; + ::MidFree(m_Block_Base); + m_Block_Base = NULL; } -#ifndef _7ZIP_ST +#ifndef Z7_ST static THREAD_FUNC_DECL MFThread(void *threadCoderInfo) { return ((CThreadInfo *)threadCoderInfo)->ThreadFunc(); } -#define RINOK_THREAD(x) { WRes __result_ = (x); if (__result_ != 0) return __result_; } - HRESULT CThreadInfo::Create() { - RINOK_THREAD(StreamWasFinishedEvent.Create()); - RINOK_THREAD(WaitingWasStartedEvent.Create()); - RINOK_THREAD(CanWriteEvent.Create()); - RINOK_THREAD(Thread.Create(MFThread, this)); - return S_OK; + WRes wres = StreamWasFinishedEvent.Create(); + if (wres == 0) { wres = WaitingWasStartedEvent.Create(); + if (wres == 0) { wres = CanWriteEvent.Create(); + if (wres == 0) + { + wres = +#ifdef _WIN32 + Encoder->_props.NumThreadGroups > 1 ? + Thread.Create_With_Group(MFThread, this, ThreadNextGroup_GetNext(&Encoder->ThreadNextGroup), 0) : // affinity +#endif + Encoder->_props.Affinity != 0 ? + Thread.Create_With_Affinity(MFThread, this, (CAffinityMask)Encoder->_props.Affinity) : + Thread.Create(MFThread, this); + }}} + return HRESULT_FROM_WIN32(wres); } void CThreadInfo::FinishStream(bool needLeave) @@ -74,7 +88,7 @@ void CThreadInfo::FinishStream(bool needLeave) WaitingWasStartedEvent.Set(); } -DWORD CThreadInfo::ThreadFunc() +THREAD_FUNC_RET_TYPE CThreadInfo::ThreadFunc() { for (;;) { @@ -94,8 +108,8 @@ DWORD CThreadInfo::ThreadFunc() bool needLeave = true; try { - UInt32 blockSize = Encoder->ReadRleBlock(m_Block); - m_PackSize = Encoder->m_InStream.GetProcessedSize(); + const UInt32 blockSize = Encoder->ReadRleBlock(m_Block); + m_UnpackSize = Encoder->m_InStream.GetProcessedSize(); m_BlockIndex = Encoder->NextBlockIndex; if (++Encoder->NextBlockIndex == Encoder->NumThreads) Encoder->NextBlockIndex = 0; @@ -133,7 +147,7 @@ void CEncProps::Normalize(int level) if (NumPasses > kNumPassesMax) NumPasses = kNumPassesMax; if (BlockSizeMult == (UInt32)(Int32)-1) - BlockSizeMult = (level >= 5 ? 9 : (level >= 1 ? level * 2 - 1: 1)); + BlockSizeMult = (level >= 5 ? 9 : (level >= 1 ? (unsigned)level * 2 - 1: 1)); if (BlockSizeMult < kBlockSizeMultMin) BlockSizeMult = kBlockSizeMultMin; if (BlockSizeMult > kBlockSizeMultMax) BlockSizeMult = kBlockSizeMultMax; } @@ -142,14 +156,14 @@ CEncoder::CEncoder() { _props.Normalize(-1); - #ifndef _7ZIP_ST - ThreadsInfo = 0; + #ifndef Z7_ST + ThreadsInfo = NULL; m_NumThreadsPrev = 0; NumThreads = 1; #endif } -#ifndef _7ZIP_ST +#ifndef Z7_ST CEncoder::~CEncoder() { Free(); @@ -157,9 +171,14 @@ CEncoder::~CEncoder() HRESULT CEncoder::Create() { - RINOK_THREAD(CanProcessEvent.CreateIfNotCreated()); - RINOK_THREAD(CanStartWaitingEvent.CreateIfNotCreated()); - if (ThreadsInfo != 0 && m_NumThreadsPrev == NumThreads) + { + WRes wres = CanProcessEvent.CreateIfNotCreated_Reset(); + if (wres == 0) { wres = CanStartWaitingEvent.CreateIfNotCreated_Reset(); } + if (wres != 0) + return HRESULT_FROM_WIN32(wres); + } + + if (ThreadsInfo && m_NumThreadsPrev == NumThreads) return S_OK; try { @@ -167,7 +186,7 @@ HRESULT CEncoder::Create() MtMode = (NumThreads > 1); m_NumThreadsPrev = NumThreads; ThreadsInfo = new CThreadInfo[NumThreads]; - if (ThreadsInfo == 0) + if (!ThreadsInfo) return E_OUTOFMEMORY; } catch(...) { return E_OUTOFMEMORY; } @@ -199,101 +218,259 @@ void CEncoder::Free() { CThreadInfo &ti = ThreadsInfo[t]; if (MtMode) - ti.Thread.Wait(); + ti.Thread.Wait_Close(); ti.Free(); } delete []ThreadsInfo; - ThreadsInfo = 0; + ThreadsInfo = NULL; } #endif +struct CRleEncoder +{ + const Byte *_src; + const Byte *_srcLim; + Byte *_dest; + const Byte *_destLim; + Byte _prevByte; + unsigned _numReps; + + void Encode(); +}; + +Z7_NO_INLINE +void CRleEncoder::Encode() +{ + const Byte *src = _src; + const Byte * const srcLim = _srcLim; + Byte *dest = _dest; + const Byte * const destLim = _destLim; + Byte prev = _prevByte; + unsigned numReps = _numReps; + // (dest < destLim) + // src = srcLim; // for debug + while (dest < destLim) + { + if (src == srcLim) + break; + const Byte b = *src++; + if (b != prev) + { + if (numReps >= kRleModeRepSize) + *dest++ = (Byte)(numReps - kRleModeRepSize); + *dest++ = b; + numReps = 1; + prev = b; + /* + { // speed optimization code: + if (dest >= destLim || src == srcLim) + break; + const Byte b2 = *src++; + *dest++ = b2; + numReps += (prev == b2); + prev = b2; + } + */ + continue; + } + numReps++; + if (numReps <= kRleModeRepSize) + *dest++ = b; + else if (numReps == kRleModeRepSize + 255) + { + *dest++ = (Byte)(numReps - kRleModeRepSize); + numReps = 0; + } + } + _src = src; + _dest = dest; + _prevByte = prev; + _numReps = numReps; + // (dest <= destLim + 1) +} + + +// out: return value is blockSize: size of data filled in buffer[]: +// (returned_blockSize <= _props.BlockSizeMult * kBlockSizeStep) UInt32 CEncoder::ReadRleBlock(Byte *buffer) { + CRleEncoder rle; UInt32 i = 0; - Byte prevByte; - if (m_InStream.ReadByte(prevByte)) + if (m_InStream.ReadByte(rle._prevByte)) { - UInt32 blockSize = _props.BlockSizeMult * kBlockSizeStep - 1; - unsigned numReps = 1; - buffer[i++] = prevByte; - while (i < blockSize) // "- 1" to support RLE + NumBlocks++; + const UInt32 blockSize = _props.BlockSizeMult * kBlockSizeStep - 1; // -1 for RLE + rle._destLim = buffer + blockSize; + rle._numReps = 1; + buffer[i++] = rle._prevByte; + while (i < blockSize) { - Byte b; - if (!m_InStream.ReadByte(b)) + rle._dest = buffer + i; + size_t rem; + const Byte * const ptr = m_InStream.Lookahead(rem); + if (rem == 0) break; - if (b != prevByte) - { - if (numReps >= kRleModeRepSize) - buffer[i++] = (Byte)(numReps - kRleModeRepSize); - buffer[i++] = b; - numReps = 1; - prevByte = b; - continue; - } - numReps++; - if (numReps <= kRleModeRepSize) - buffer[i++] = b; - else if (numReps == kRleModeRepSize + 255) - { - buffer[i++] = (Byte)(numReps - kRleModeRepSize); - numReps = 0; - } + rle._src = ptr; + rle._srcLim = ptr + rem; + rle.Encode(); + m_InStream.Skip((size_t)(rle._src - ptr)); + i = (UInt32)(size_t)(rle._dest - buffer); + // (i <= blockSize + 1) } - // it's to support original BZip2 decoder - if (numReps >= kRleModeRepSize) - buffer[i++] = (Byte)(numReps - kRleModeRepSize); + const int n = (int)rle._numReps - (int)kRleModeRepSize; + if (n >= 0) + buffer[i++] = (Byte)n; } return i; } -void CThreadInfo::WriteBits2(UInt32 value, unsigned numBits) { m_OutStreamCurrent->WriteBits(value, numBits); } -void CThreadInfo::WriteByte2(Byte b) { WriteBits2(b, 8); } -void CThreadInfo::WriteBit2(Byte v) { WriteBits2(v, 1); } -void CThreadInfo::WriteCrc2(UInt32 v) -{ - for (unsigned i = 0; i < 4; i++) - WriteByte2(((Byte)(v >> (24 - i * 8)))); + + +Z7_NO_INLINE +void CThreadInfo::WriteBits2(UInt32 value, unsigned numBits) + { m_OutStreamCurrent.WriteBits(value, numBits); } +/* +Z7_NO_INLINE +void CThreadInfo::WriteByte2(unsigned b) + { m_OutStreamCurrent.WriteByte(b); } +*/ +// void CEncoder::WriteBits(UInt32 value, unsigned numBits) { m_OutStream.WriteBits(value, numBits); } +Z7_NO_INLINE +void CEncoder::WriteByte(Byte b) { m_OutStream.WriteByte(b); } + + +#define WRITE_BITS_UPDATE(value, numBits) \ +{ \ + numBits -= _bitPos; \ + const UInt32 hi = value >> numBits; \ + *_buf++ = (Byte)(_curByte | hi); \ + value -= hi << numBits; \ + _bitPos = 8; \ + _curByte = 0; \ } -void CEncoder::WriteBits(UInt32 value, unsigned numBits) { m_OutStream.WriteBits(value, numBits); } -void CEncoder::WriteByte(Byte b) { WriteBits(b, 8); } -// void CEncoder::WriteBit(Byte v) { WriteBits(v, 1); } -void CEncoder::WriteCrc(UInt32 v) -{ - for (unsigned i = 0; i < 4; i++) - WriteByte(((Byte)(v >> (24 - i * 8)))); +#if HUFFMAN_LEN > 16 + +#define WRITE_BITS_HUFF(value2, numBits2) \ +{ \ + UInt32 value = value2; \ + unsigned numBits = numBits2; \ + while (numBits >= _bitPos) { \ + WRITE_BITS_UPDATE(value, numBits) \ + } \ + _bitPos -= numBits; \ + _curByte |= (value << _bitPos); \ +} + +#else // HUFFMAN_LEN <= 16 + +// numBits2 <= 16 is supported +#define WRITE_BITS_HUFF(value2, numBits2) \ +{ \ + UInt32 value = value2; \ + unsigned numBits = numBits2; \ + if (numBits >= _bitPos) \ + { \ + WRITE_BITS_UPDATE(value, numBits) \ + if (numBits >= _bitPos) \ + { \ + numBits -= _bitPos; \ + const UInt32 hi = value >> numBits; \ + *_buf++ = (Byte)hi; \ + value -= hi << numBits; \ + } \ + } \ + _bitPos -= numBits; \ + _curByte |= (value << _bitPos); \ +} + +#endif + +#define WRITE_BITS_8(value2, numBits2) \ +{ \ + UInt32 value = value2; \ + unsigned numBits = numBits2; \ + if (numBits >= _bitPos) \ + { \ + WRITE_BITS_UPDATE(value, numBits) \ + } \ + _bitPos -= numBits; \ + _curByte |= (value << _bitPos); \ +} + +#define WRITE_BIT_PRE \ + { _bitPos--; } + +#define WRITE_BIT_POST \ +{ \ + if (_bitPos == 0) \ + { \ + *_buf++ = (Byte)_curByte; \ + _curByte = 0; \ + _bitPos = 8; \ + } \ +} + +#define WRITE_BIT_0 \ +{ \ + WRITE_BIT_PRE \ + WRITE_BIT_POST \ +} + +#define WRITE_BIT_1 \ +{ \ + WRITE_BIT_PRE \ + _curByte |= 1u << _bitPos; \ + WRITE_BIT_POST \ } // blockSize > 0 void CThreadInfo::EncodeBlock(const Byte *block, UInt32 blockSize) { - WriteBit2(0); // Randomised = false - + // WriteBit2(0); // Randomised = false { - UInt32 origPtr = BlockSort(m_BlockSorterIndex, block, blockSize); + const UInt32 origPtr = BlockSort(m_BlockSorterIndex, block, blockSize); // if (m_BlockSorterIndex[origPtr] != 0) throw 1; m_BlockSorterIndex[origPtr] = blockSize; - WriteBits2(origPtr, kNumOrigBits); + WriteBits2(origPtr, kNumOrigBits + 1); // + 1 for additional high bit flag (Randomised = false) } - - CMtf8Encoder mtf; - unsigned numInUse = 0; + Byte mtfBuf[256]; + // memset(mtfBuf, 0, sizeof(mtfBuf)); // to disable MSVC warning + unsigned numInUse; { Byte inUse[256]; Byte inUse16[16]; - UInt32 i; + unsigned i; for (i = 0; i < 256; i++) inUse[i] = 0; for (i = 0; i < 16; i++) inUse16[i] = 0; - for (i = 0; i < blockSize; i++) - inUse[block[i]] = 1; + { + const Byte * cur = block; + block = block + (size_t)blockSize - 1; + if (cur != block) + { + do + { + const unsigned b0 = cur[0]; + const unsigned b1 = cur[1]; + cur += 2; + inUse[b0] = 1; + inUse[b1] = 1; + } + while (cur < block); + } + if (cur == block) + inUse[cur[0]] = 1; + block -= blockSize; // block pointer is (original_block - 1) + } + numInUse = 0; for (i = 0; i < 256; i++) if (inUse[i]) { inUse16[i >> 4] = 1; - mtf.Buf[numInUse++] = (Byte)i; + mtfBuf[numInUse++] = (Byte)i; } for (i = 0; i < 16; i++) WriteBit2(inUse16[i]); @@ -301,65 +478,88 @@ void CThreadInfo::EncodeBlock(const Byte *block, UInt32 blockSize) if (inUse16[i >> 4]) WriteBit2(inUse[i]); } - unsigned alphaSize = numInUse + 2; + const unsigned alphaSize = numInUse + 2; - Byte *mtfs = m_MtfArray; - UInt32 mtfArraySize = 0; UInt32 symbolCounts[kMaxAlphaSize]; { for (unsigned i = 0; i < kMaxAlphaSize; i++) symbolCounts[i] = 0; + symbolCounts[(size_t)alphaSize - 1] = 1; } + Byte *mtfs = m_MtfArray; { - UInt32 rleSize = 0; - UInt32 i = 0; const UInt32 *bsIndex = m_BlockSorterIndex; - block--; + const UInt32 *bsIndex_rle = bsIndex; + const UInt32 * const bsIndex_end = bsIndex + blockSize; + // block--; // backward fix + // block pointer is (original_block - 1) do { - unsigned pos = mtf.FindAndMove(block[bsIndex[i]]); - if (pos == 0) - rleSize++; - else + const Byte v = block[*bsIndex++]; + Byte a = mtfBuf[0]; + if (v != a) { - while (rleSize != 0) + mtfBuf[0] = v; { - rleSize--; - mtfs[mtfArraySize++] = (Byte)(rleSize & 1); - symbolCounts[rleSize & 1]++; - rleSize >>= 1; + UInt32 rleSize = (UInt32)(size_t)(bsIndex - bsIndex_rle) - 1; + bsIndex_rle = bsIndex; + while (rleSize) + { + const unsigned sym = (unsigned)(--rleSize & 1); + *mtfs++ = (Byte)sym; + symbolCounts[sym]++; + rleSize >>= 1; + } } - if (pos >= 0xFE) + unsigned pos1 = 2; // = real_pos + 1 + Byte b; + b = mtfBuf[1]; mtfBuf[1] = a; if (v != b) + { a = mtfBuf[2]; mtfBuf[2] = b; if (v == a) pos1 = 3; + else { b = mtfBuf[3]; mtfBuf[3] = a; if (v == b) pos1 = 4; + else + { + Byte *m = mtfBuf + 7; + for (;;) + { + a = m[-3]; m[-3] = b; if (v == a) { pos1 = (unsigned)(size_t)(m - (mtfBuf + 2)); break; } + b = m[-2]; m[-2] = a; if (v == b) { pos1 = (unsigned)(size_t)(m - (mtfBuf + 1)); break; } + a = m[-1]; m[-1] = b; if (v == a) { pos1 = (unsigned)(size_t)(m - (mtfBuf )); break; } + b = m[ 0]; m[ 0] = a; m += 4; if (v == b) { pos1 = (unsigned)(size_t)(m - (mtfBuf + 3)); break; } + } + }}} + symbolCounts[pos1]++; + if (pos1 >= 0xff) { - mtfs[mtfArraySize++] = 0xFF; - mtfs[mtfArraySize++] = (Byte)(pos - 0xFE); + *mtfs++ = 0xff; + // pos1 -= 0xff; + pos1++; // we need only low byte } - else - mtfs[mtfArraySize++] = (Byte)(pos + 1); - symbolCounts[pos + 1]++; + *mtfs++ = (Byte)pos1; } } - while (++i < blockSize); + while (bsIndex < bsIndex_end); - while (rleSize != 0) + UInt32 rleSize = (UInt32)(size_t)(bsIndex - bsIndex_rle); + while (rleSize) { - rleSize--; - mtfs[mtfArraySize++] = (Byte)(rleSize & 1); - symbolCounts[rleSize & 1]++; + const unsigned sym = (unsigned)(--rleSize & 1); + *mtfs++ = (Byte)sym; + symbolCounts[sym]++; rleSize >>= 1; } - - if (alphaSize < 256) - mtfs[mtfArraySize++] = (Byte)(alphaSize - 1); - else + + unsigned d = alphaSize - 1; + if (alphaSize >= 256) { - mtfs[mtfArraySize++] = 0xFF; - mtfs[mtfArraySize++] = (Byte)(alphaSize - 256); + *mtfs++ = 0xff; + d = alphaSize; // (-256) } - symbolCounts[alphaSize - 1]++; + *mtfs++ = (Byte)d; } + const Byte * const mtf_lim = mtfs; + UInt32 numSymbols = 0; { for (unsigned i = 0; i < kMaxAlphaSize; i++) @@ -368,34 +568,30 @@ void CThreadInfo::EncodeBlock(const Byte *block, UInt32 blockSize) unsigned bestNumTables = kNumTablesMin; UInt32 bestPrice = 0xFFFFFFFF; - UInt32 startPos = m_OutStreamCurrent->GetPos(); - Byte startCurByte = m_OutStreamCurrent->GetCurByte(); + const UInt32 startPos = m_OutStreamCurrent.GetPos(); + const unsigned startCurByte = m_OutStreamCurrent.GetCurByte(); for (unsigned nt = kNumTablesMin; nt <= kNumTablesMax + 1; nt++) { unsigned numTables; if (m_OptimizeNumTables) { - m_OutStreamCurrent->SetPos(startPos); - m_OutStreamCurrent->SetCurState((startPos & 7), startCurByte); - if (nt <= kNumTablesMax) - numTables = nt; - else - numTables = bestNumTables; + m_OutStreamCurrent.SetPos(startPos); + m_OutStreamCurrent.SetCurState(startPos & 7, startCurByte); + numTables = (nt <= kNumTablesMax ? nt : bestNumTables); } else { - if (numSymbols < 200) numTables = 2; - else if (numSymbols < 600) numTables = 3; + if (numSymbols < 200) numTables = 2; + else if (numSymbols < 600) numTables = 3; else if (numSymbols < 1200) numTables = 4; else if (numSymbols < 2400) numTables = 5; - else numTables = 6; + else numTables = 6; } WriteBits2(numTables, kNumTablesBits); - - UInt32 numSelectors = (numSymbols + kGroupSize - 1) / kGroupSize; - WriteBits2(numSelectors, kNumSelectorsBits); + const unsigned numSelectors = (numSymbols + kGroupSize - 1) / kGroupSize; + WriteBits2((UInt32)numSelectors, kNumSelectorsBits); { UInt32 remFreq = numSymbols; @@ -412,7 +608,7 @@ void CThreadInfo::EncodeBlock(const Byte *block, UInt32 blockSize) if (ge > gs + 1 && t != numTables && t != 1 && (((numTables - t) & 1) == 1)) aFreq -= symbolCounts[--ge]; - Byte *lens = Lens[t - 1]; + Byte *lens = Lens[(size_t)t - 1]; unsigned i = 0; do lens[i] = (Byte)((i >= gs && i < ge) ? 0 : 1); @@ -426,28 +622,23 @@ void CThreadInfo::EncodeBlock(const Byte *block, UInt32 blockSize) for (unsigned pass = 0; pass < kNumHuffPasses; pass++) { + memset(Freqs, 0, sizeof(Freqs[0]) * numTables); + // memset(Freqs, 0, sizeof(Freqs)); { - unsigned t = 0; - do - memset(Freqs[t], 0, sizeof(Freqs[t])); - while (++t < numTables); - } - - { - UInt32 mtfPos = 0; + mtfs = m_MtfArray; UInt32 g = 0; do { - UInt32 symbols[kGroupSize]; + unsigned symbols[kGroupSize]; unsigned i = 0; do { - UInt32 symbol = mtfs[mtfPos++]; + UInt32 symbol = *mtfs++; if (symbol >= 0xFF) - symbol += mtfs[mtfPos++]; + symbol += *mtfs++; symbols[i] = symbol; } - while (++i < kGroupSize && mtfPos < mtfArraySize); + while (++i < kGroupSize && mtfs < mtf_lim); UInt32 bestPrice2 = 0xFFFFFFFF; unsigned t = 0; @@ -472,7 +663,7 @@ void CThreadInfo::EncodeBlock(const Byte *block, UInt32 blockSize) freqs[symbols[j]]++; while (++j < i); } - while (mtfPos < mtfArraySize); + while (mtfs < mtf_lim); } unsigned t = 0; @@ -484,11 +675,15 @@ void CThreadInfo::EncodeBlock(const Byte *block, UInt32 blockSize) if (freqs[i] == 0) freqs[i] = 1; while (++i < alphaSize); - Huffman_Generate(freqs, Codes[t], Lens[t], kMaxAlphaSize, kMaxHuffmanLenForEncoding); + Huffman_Generate(freqs, Codes[t], Lens[t], kMaxAlphaSize, HUFFMAN_LEN); } while (++t < numTables); } + unsigned _bitPos; // 0 < _bitPos <= 8 : number of non-filled low bits in _curByte + unsigned _curByte; // low (_bitPos) bits are zeros + // high (8 - _bitPos) bits are filled + Byte *_buf; { Byte mtfSel[kNumTablesMax]; { @@ -497,81 +692,97 @@ void CThreadInfo::EncodeBlock(const Byte *block, UInt32 blockSize) mtfSel[t] = (Byte)t; while (++t < numTables); } + + _bitPos = m_OutStreamCurrent._bitPos; + _curByte = m_OutStreamCurrent._curByte; + _buf = m_OutStreamCurrent._buf; + // stream.Init_from_Global(m_OutStreamCurrent); - UInt32 i = 0; + const Byte *selectors = m_Selectors; + const Byte * const selectors_lim = selectors + numSelectors; + Byte prev = 0; // mtfSel[0]; do { - Byte sel = m_Selectors[i]; - unsigned pos; - for (pos = 0; mtfSel[pos] != sel; pos++) - WriteBit2(1); - WriteBit2(0); - for (; pos > 0; pos--) - mtfSel[pos] = mtfSel[pos - 1]; - mtfSel[0] = sel; + const Byte sel = *selectors++; + if (prev != sel) + { + Byte *mtfSel_cur = &mtfSel[1]; + for (;;) + { + WRITE_BIT_1 + const Byte next = *mtfSel_cur; + *mtfSel_cur++ = prev; + prev = next; + if (next == sel) + break; + } + // mtfSel[0] = sel; + } + WRITE_BIT_0 } - while (++i < numSelectors); + while (selectors != selectors_lim); } - { unsigned t = 0; do { const Byte *lens = Lens[t]; - UInt32 len = lens[0]; - WriteBits2(len, kNumLevelsBits); + unsigned len = lens[0]; + WRITE_BITS_8(len, kNumLevelsBits) unsigned i = 0; do { - UInt32 level = lens[i]; + const unsigned level = lens[i]; while (len != level) { - WriteBit2(1); + WRITE_BIT_1 if (len < level) { - WriteBit2(0); len++; + WRITE_BIT_0 } else { - WriteBit2(1); len--; + WRITE_BIT_1 } } - WriteBit2(0); + WRITE_BIT_0 } while (++i < alphaSize); } while (++t < numTables); } - { - UInt32 groupSize = 0; - UInt32 groupIndex = 0; - const Byte *lens = 0; - const UInt32 *codes = 0; - UInt32 mtfPos = 0; + UInt32 groupSize = 1; + const Byte *selectors = m_Selectors; + const Byte *lens = NULL; + const UInt32 *codes = NULL; + mtfs = m_MtfArray; do { - UInt32 symbol = mtfs[mtfPos++]; + unsigned symbol = *mtfs++; if (symbol >= 0xFF) - symbol += mtfs[mtfPos++]; - if (groupSize == 0) + symbol += *mtfs++; + if (--groupSize == 0) { groupSize = kGroupSize; - unsigned t = m_Selectors[groupIndex++]; + const unsigned t = *selectors++; lens = Lens[t]; codes = Codes[t]; } - groupSize--; - m_OutStreamCurrent->WriteBits(codes[symbol], lens[symbol]); + WRITE_BITS_HUFF(codes[symbol], lens[symbol]) } - while (mtfPos < mtfArraySize); + while (mtfs < mtf_lim); } + // Restore_from_Local: + m_OutStreamCurrent._bitPos = _bitPos; + m_OutStreamCurrent._curByte = _curByte; + m_OutStreamCurrent._buf = _buf; if (!m_OptimizeNumTables) break; - UInt32 price = m_OutStreamCurrent->GetPos() - startPos; + const UInt32 price = m_OutStreamCurrent.GetPos() - startPos; if (price <= bestPrice) { if (nt == kNumTablesMax) @@ -582,6 +793,7 @@ void CThreadInfo::EncodeBlock(const Byte *block, UInt32 blockSize) } } + // blockSize > 0 UInt32 CThreadInfo::EncodeBlockWithHeaders(const Byte *block, UInt32 blockSize) { @@ -593,158 +805,149 @@ UInt32 CThreadInfo::EncodeBlockWithHeaders(const Byte *block, UInt32 blockSize) WriteByte2(kBlockSig5); CBZip2Crc crc; - unsigned numReps = 0; - Byte prevByte = block[0]; - UInt32 i = 0; - do + const Byte * const lim = block + blockSize; + unsigned b = *block++; + crc.UpdateByte(b); + for (;;) { - Byte b = block[i]; - if (numReps == kRleModeRepSize) - { - for (; b > 0; b--) - crc.UpdateByte(prevByte); - numReps = 0; - continue; - } - if (prevByte == b) - numReps++; - else - { - numReps = 1; - prevByte = b; - } - crc.UpdateByte(b); + const unsigned prev = b; + if (block >= lim) { break; } b = *block++; crc.UpdateByte(b); if (prev != b) continue; + if (block >= lim) { break; } b = *block++; crc.UpdateByte(b); if (prev != b) continue; + if (block >= lim) { break; } b = *block++; crc.UpdateByte(b); if (prev != b) continue; + if (block >= lim) { break; } b = *block++; if (b) do crc.UpdateByte(prev); while (--b); + if (block >= lim) { break; } b = *block++; crc.UpdateByte(b); } - while (++i < blockSize); - UInt32 crcRes = crc.GetDigest(); - WriteCrc2(crcRes); - EncodeBlock(block, blockSize); + const UInt32 crcRes = crc.GetDigest(); + for (int i = 24; i >= 0; i -= 8) + WriteByte2((Byte)(crcRes >> i)); + EncodeBlock(lim - blockSize, blockSize); return crcRes; } + void CThreadInfo::EncodeBlock2(const Byte *block, UInt32 blockSize, UInt32 numPasses) { - UInt32 numCrcs = m_NumCrcs; - bool needCompare = false; - - UInt32 startBytePos = m_OutStreamCurrent->GetBytePos(); - UInt32 startPos = m_OutStreamCurrent->GetPos(); - Byte startCurByte = m_OutStreamCurrent->GetCurByte(); - Byte endCurByte = 0; - UInt32 endPos = 0; + const UInt32 numCrcs = m_NumCrcs; + + const UInt32 startBytePos = m_OutStreamCurrent.GetBytePos(); + const UInt32 startPos = m_OutStreamCurrent.GetPos(); + const unsigned startCurByte = m_OutStreamCurrent.GetCurByte(); + unsigned endCurByte = 0; + UInt32 endPos = 0; // 0 means no no additional passes if (numPasses > 1 && blockSize >= (1 << 10)) { - UInt32 blockSize0 = blockSize / 2; - for (;(block[blockSize0] == block[blockSize0 - 1] || - block[blockSize0 - 1] == block[blockSize0 - 2]) && - blockSize0 < blockSize; blockSize0++); - if (blockSize0 < blockSize) + UInt32 bs0 = blockSize / 2; + for (; bs0 < blockSize && + (block[ bs0 ] == + block[(size_t)bs0 - 1] || + block[(size_t)bs0 - 1] == + block[(size_t)bs0 - 2]); + bs0++) + {} + + if (bs0 < blockSize) { - EncodeBlock2(block, blockSize0, numPasses - 1); - EncodeBlock2(block + blockSize0, blockSize - blockSize0, numPasses - 1); - endPos = m_OutStreamCurrent->GetPos(); - endCurByte = m_OutStreamCurrent->GetCurByte(); - if ((endPos & 7) > 0) + EncodeBlock2(block, bs0, numPasses - 1); + EncodeBlock2(block + bs0, blockSize - bs0, numPasses - 1); + endPos = m_OutStreamCurrent.GetPos(); + endCurByte = m_OutStreamCurrent.GetCurByte(); + // we prepare next byte as identical byte to starting byte for main encoding attempt: + if (endPos & 7) WriteBits2(0, 8 - (endPos & 7)); - m_OutStreamCurrent->SetCurState((startPos & 7), startCurByte); - needCompare = true; + m_OutStreamCurrent.SetCurState((startPos & 7), startCurByte); } } - UInt32 startBytePos2 = m_OutStreamCurrent->GetBytePos(); - UInt32 startPos2 = m_OutStreamCurrent->GetPos(); - UInt32 crcVal = EncodeBlockWithHeaders(block, blockSize); - UInt32 endPos2 = m_OutStreamCurrent->GetPos(); + const UInt32 startBytePos2 = m_OutStreamCurrent.GetBytePos(); + const UInt32 startPos2 = m_OutStreamCurrent.GetPos(); + const UInt32 crcVal = EncodeBlockWithHeaders(block, blockSize); - if (needCompare) + if (endPos) { - UInt32 size2 = endPos2 - startPos2; - if (size2 < endPos - startPos) - { - UInt32 numBytes = m_OutStreamCurrent->GetBytePos() - startBytePos2; - Byte *buffer = m_OutStreamCurrent->GetStream(); - for (UInt32 i = 0; i < numBytes; i++) - buffer[startBytePos + i] = buffer[startBytePos2 + i]; - m_OutStreamCurrent->SetPos(startPos + endPos2 - startPos2); - m_NumCrcs = numCrcs; - m_CRCs[m_NumCrcs++] = crcVal; - } - else + const UInt32 size2 = m_OutStreamCurrent.GetPos() - startPos2; + if (size2 >= endPos - startPos) { - m_OutStreamCurrent->SetPos(endPos); - m_OutStreamCurrent->SetCurState((endPos & 7), endCurByte); + m_OutStreamCurrent.SetPos(endPos); + m_OutStreamCurrent.SetCurState((endPos & 7), endCurByte); + return; } + const UInt32 numBytes = m_OutStreamCurrent.GetBytePos() - startBytePos2; + Byte * const buffer = m_OutStreamCurrent.GetStream(); + memmove(buffer + startBytePos, buffer + startBytePos2, numBytes); + m_OutStreamCurrent.SetPos(startPos + size2); + // we don't call m_OutStreamCurrent.SetCurState() here because + // m_OutStreamCurrent._curByte is correct already } - else - { - m_NumCrcs = numCrcs; - m_CRCs[m_NumCrcs++] = crcVal; - } + m_CRCs[numCrcs] = crcVal; + m_NumCrcs = numCrcs + 1; } + HRESULT CThreadInfo::EncodeBlock3(UInt32 blockSize) { - CMsbfEncoderTemp outStreamTemp; + CMsbfEncoderTemp &outStreamTemp = m_OutStreamCurrent; outStreamTemp.SetStream(m_TempArray); outStreamTemp.Init(); - m_OutStreamCurrent = &outStreamTemp; - m_NumCrcs = 0; EncodeBlock2(m_Block, blockSize, Encoder->_props.NumPasses); - #ifndef _7ZIP_ST +#ifndef Z7_ST if (Encoder->MtMode) Encoder->ThreadsInfo[m_BlockIndex].CanWriteEvent.Lock(); - #endif +#endif + for (UInt32 i = 0; i < m_NumCrcs; i++) Encoder->CombinedCrc.Update(m_CRCs[i]); - Encoder->WriteBytes(m_TempArray, outStreamTemp.GetPos(), outStreamTemp.GetCurByte()); + Encoder->WriteBytes(m_TempArray, outStreamTemp.GetPos(), outStreamTemp.GetNonFlushedByteBits()); HRESULT res = S_OK; - #ifndef _7ZIP_ST + +#ifndef Z7_ST if (Encoder->MtMode) { UInt32 blockIndex = m_BlockIndex + 1; if (blockIndex == Encoder->NumThreads) blockIndex = 0; - if (Encoder->Progress) { - UInt64 unpackSize = Encoder->m_OutStream.GetProcessedSize(); - res = Encoder->Progress->SetRatioInfo(&m_PackSize, &unpackSize); + const UInt64 packSize = Encoder->m_OutStream.GetProcessedSize(); + res = Encoder->Progress->SetRatioInfo(&m_UnpackSize, &packSize); } - Encoder->ThreadsInfo[blockIndex].CanWriteEvent.Set(); } - #endif +#endif return res; } -void CEncoder::WriteBytes(const Byte *data, UInt32 sizeInBits, Byte lastByte) +void CEncoder::WriteBytes(const Byte *data, UInt32 sizeInBits, unsigned lastByteBits) { - UInt32 bytesSize = (sizeInBits >> 3); - for (UInt32 i = 0; i < bytesSize; i++) - m_OutStream.WriteBits(data[i], 8); - WriteBits(lastByte, (sizeInBits & 7)); + m_OutStream.WriteBytes(data, sizeInBits >> 3); + sizeInBits &= 7; + if (sizeInBits) + m_OutStream.WriteBits(lastByteBits, sizeInBits); } HRESULT CEncoder::CodeReal(ISequentialInStream *inStream, ISequentialOutStream *outStream, const UInt64 * /* inSize */, const UInt64 * /* outSize */, ICompressProgressInfo *progress) { - #ifndef _7ZIP_ST + NumBlocks = 0; +#ifndef Z7_ST Progress = progress; - RINOK(Create()); + ThreadNextGroup_Init(&ThreadNextGroup, _props.NumThreadGroups, 0); // startGroup + RINOK(Create()) for (UInt32 t = 0; t < NumThreads; t++) - #endif +#endif { - #ifndef _7ZIP_ST + #ifndef Z7_ST CThreadInfo &ti = ThreadsInfo[t]; if (MtMode) { - RINOK(ti.StreamWasFinishedEvent.Reset()); - RINOK(ti.WaitingWasStartedEvent.Reset()); - RINOK(ti.CanWriteEvent.Reset()); + WRes wres = ti.StreamWasFinishedEvent.Reset(); + if (wres == 0) { wres = ti.WaitingWasStartedEvent.Reset(); + if (wres == 0) { wres = ti.CanWriteEvent.Reset(); }} + if (wres != 0) + return HRESULT_FROM_WIN32(wres); } #else CThreadInfo &ti = ThreadsInfo; @@ -771,7 +974,7 @@ HRESULT CEncoder::CodeReal(ISequentialInStream *inStream, ISequentialOutStream * m_OutStream.Init(); CombinedCrc.Init(); - #ifndef _7ZIP_ST + #ifndef Z7_ST NextBlockIndex = 0; StreamWasFinished = false; CloseThreads = false; @@ -783,7 +986,7 @@ HRESULT CEncoder::CodeReal(ISequentialInStream *inStream, ISequentialOutStream * WriteByte(kArSig2); WriteByte((Byte)(kArSig3 + _props.BlockSizeMult)); - #ifndef _7ZIP_ST + #ifndef Z7_ST if (MtMode) { @@ -798,7 +1001,7 @@ HRESULT CEncoder::CodeReal(ISequentialInStream *inStream, ISequentialOutStream * for (t = 0; t < NumThreads; t++) ThreadsInfo[t].WaitingWasStartedEvent.Lock(); CanStartWaitingEvent.Reset(); - RINOK(Result); + RINOK(Result) } else #endif @@ -806,20 +1009,20 @@ HRESULT CEncoder::CodeReal(ISequentialInStream *inStream, ISequentialOutStream * for (;;) { CThreadInfo &ti = - #ifndef _7ZIP_ST - ThreadsInfo[0]; + #ifndef Z7_ST + ThreadsInfo[0]; #else - ThreadsInfo; + ThreadsInfo; #endif - UInt32 blockSize = ReadRleBlock(ti.m_Block); + const UInt32 blockSize = ReadRleBlock(ti.m_Block); if (blockSize == 0) break; - RINOK(ti.EncodeBlock3(blockSize)); + RINOK(ti.EncodeBlock3(blockSize)) if (progress) { - UInt64 packSize = m_InStream.GetProcessedSize(); - UInt64 unpackSize = m_OutStream.GetProcessedSize(); - RINOK(progress->SetRatioInfo(&packSize, &unpackSize)); + const UInt64 unpackSize = m_InStream.GetProcessedSize(); + const UInt64 packSize = m_OutStream.GetProcessedSize(); + RINOK(progress->SetRatioInfo(&unpackSize, &packSize)) } } } @@ -829,13 +1032,19 @@ HRESULT CEncoder::CodeReal(ISequentialInStream *inStream, ISequentialOutStream * WriteByte(kFinSig3); WriteByte(kFinSig4); WriteByte(kFinSig5); - - WriteCrc(CombinedCrc.GetDigest()); - return Flush(); + { + const UInt32 v = CombinedCrc.GetDigest(); + for (int i = 24; i >= 0; i -= 8) + WriteByte((Byte)(v >> i)); + } + RINOK(Flush()) + if (!m_InStream.WasFinished()) + return E_FAIL; + return S_OK; } -STDMETHODIMP CEncoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress) +Z7_COM7F_IMF(CEncoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, + const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress)) { try { return CodeReal(inStream, outStream, inSize, outSize, progress); } catch(const CInBufferException &e) { return e.ErrorCode; } @@ -843,27 +1052,44 @@ STDMETHODIMP CEncoder::Code(ISequentialInStream *inStream, ISequentialOutStream catch(...) { return S_FALSE; } } -HRESULT CEncoder::SetCoderProperties(const PROPID *propIDs, const PROPVARIANT *coderProps, UInt32 numProps) +Z7_COM7F_IMF(CEncoder::SetCoderProperties(const PROPID *propIDs, const PROPVARIANT *coderProps, UInt32 numProps)) { int level = -1; CEncProps props; for (UInt32 i = 0; i < numProps; i++) { const PROPVARIANT &prop = coderProps[i]; - PROPID propID = propIDs[i]; + const PROPID propID = propIDs[i]; + + if (propID == NCoderPropID::kAffinity) + { + if (prop.vt != VT_UI8) + return E_INVALIDARG; + props.Affinity = prop.uhVal.QuadPart; + continue; + } + + if (propID == NCoderPropID::kNumThreadGroups) + { + if (prop.vt != VT_UI4) + return E_INVALIDARG; + props.NumThreadGroups = (UInt32)prop.ulVal; + continue; + } + if (propID >= NCoderPropID::kReduceSize) continue; if (prop.vt != VT_UI4) return E_INVALIDARG; - UInt32 v = (UInt32)prop.ulVal; + const UInt32 v = (UInt32)prop.ulVal; switch (propID) { case NCoderPropID::kNumPasses: props.NumPasses = v; break; case NCoderPropID::kDictionarySize: props.BlockSizeMult = v / kBlockSizeStep; break; - case NCoderPropID::kLevel: level = v; break; + case NCoderPropID::kLevel: level = (int)v; break; case NCoderPropID::kNumThreads: { - #ifndef _7ZIP_ST + #ifndef Z7_ST SetNumberOfThreads(v); #endif break; @@ -876,8 +1102,8 @@ HRESULT CEncoder::SetCoderProperties(const PROPID *propIDs, const PROPVARIANT *c return S_OK; } -#ifndef _7ZIP_ST -STDMETHODIMP CEncoder::SetNumberOfThreads(UInt32 numThreads) +#ifndef Z7_ST +Z7_COM7F_IMF(CEncoder::SetNumberOfThreads(UInt32 numThreads)) { const UInt32 kNumThreadsMax = 64; if (numThreads < 1) numThreads = 1; diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/BZip2Encoder.h b/src/plugins/7zip/7za/cpp/7zip/Compress/BZip2Encoder.h index 05d6fa16..bcb4025a 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/BZip2Encoder.h +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/BZip2Encoder.h @@ -1,12 +1,11 @@ // BZip2Encoder.h -#ifndef __COMPRESS_BZIP2_ENCODER_H -#define __COMPRESS_BZIP2_ENCODER_H +#ifndef ZIP7_INC_COMPRESS_BZIP2_ENCODER_H +#define ZIP7_INC_COMPRESS_BZIP2_ENCODER_H -#include "../../Common/Defs.h" #include "../../Common/MyCom.h" -#ifndef _7ZIP_ST +#ifndef Z7_ST #include "../../Windows/Synchronization.h" #include "../../Windows/Thread.h" #endif @@ -23,80 +22,114 @@ namespace NCompress { namespace NBZip2 { -class CMsbfEncoderTemp +const unsigned kNumPassesMax = 10; + +struct CMsbfEncoderTemp { - UInt32 _pos; - unsigned _bitPos; - Byte _curByte; + unsigned _bitPos; // 0 < _bitPos <= 8 : number of non-filled low bits in _curByte + unsigned _curByte; // low (_bitPos) bits are zeros + // high (8 - _bitPos) bits are filled Byte *_buf; -public: - void SetStream(Byte *buf) { _buf = buf; } - Byte *GetStream() const { return _buf; } + Byte *_buf_base; + void SetStream(Byte *buf) { _buf_base = _buf = buf; } + Byte *GetStream() const { return _buf_base; } void Init() { - _pos = 0; _bitPos = 8; _curByte = 0; + _buf = _buf_base; } - void Flush() - { - if (_bitPos < 8) - WriteBits(0, _bitPos); - } - + // required condition: (value >> numBits) == 0 + // numBits == 0 is allowed void WriteBits(UInt32 value, unsigned numBits) { - while (numBits > 0) + do { - unsigned numNewBits = MyMin(numBits, _bitPos); - numBits -= numNewBits; - - _curByte <<= numNewBits; - UInt32 newBits = value >> numBits; - _curByte |= Byte(newBits); - value -= (newBits << numBits); - - _bitPos -= numNewBits; - - if (_bitPos == 0) + unsigned bp = _bitPos; + unsigned curByte = _curByte; + if (numBits < bp) { - _buf[_pos++] = _curByte; - _bitPos = 8; + bp -= numBits; + _curByte = curByte | (value << bp); + _bitPos = bp; + return; } + numBits -= bp; + const UInt32 hi = value >> numBits; + value -= (hi << numBits); + Byte *buf = _buf; + _bitPos = 8; + _curByte = 0; + *buf++ = (Byte)(curByte | hi); + _buf = buf; } + while (numBits); } - - UInt32 GetBytePos() const { return _pos ; } - UInt32 GetPos() const { return _pos * 8 + (8 - _bitPos); } - Byte GetCurByte() const { return _curByte; } + + void WriteBit(unsigned value) + { + const unsigned bp = _bitPos - 1; + const unsigned curByte = _curByte | (value << bp); + _curByte = curByte; + _bitPos = bp; + if (bp == 0) + { + *_buf++ = (Byte)curByte; + _curByte = 0; + _bitPos = 8; + } + } + + void WriteByte(unsigned b) + { + const unsigned bp = _bitPos; + const unsigned a = _curByte | (b >> (8 - bp)); + _curByte = b << bp; + Byte *buf = _buf; + *buf++ = (Byte)a; + _buf = buf; + } + + UInt32 GetBytePos() const { return (UInt32)(size_t)(_buf - _buf_base); } + UInt32 GetPos() const { return GetBytePos() * 8 + 8 - _bitPos; } + unsigned GetCurByte() const { return _curByte; } + unsigned GetNonFlushedByteBits() const { return _curByte >> _bitPos; } void SetPos(UInt32 bitPos) { - _pos = bitPos >> 3; + _buf = _buf_base + (bitPos >> 3); _bitPos = 8 - ((unsigned)bitPos & 7); } - void SetCurState(unsigned bitPos, Byte curByte) + void SetCurState(unsigned bitPos, unsigned curByte) { _bitPos = 8 - bitPos; _curByte = curByte; } }; -class CEncoder; -const unsigned kNumPassesMax = 10; +class CEncoder; class CThreadInfo { +private: + CMsbfEncoderTemp m_OutStreamCurrent; public: + CEncoder *Encoder; Byte *m_Block; private: Byte *m_MtfArray; Byte *m_TempArray; UInt32 *m_BlockSorterIndex; - CMsbfEncoderTemp *m_OutStreamCurrent; +public: + bool m_OptimizeNumTables; + UInt32 m_NumCrcs; + UInt32 m_BlockIndex; + UInt64 m_UnpackSize; + + Byte *m_Block_Base; Byte Lens[kNumTablesMax][kMaxAlphaSize]; UInt32 Freqs[kNumTablesMax][kMaxAlphaSize]; @@ -105,22 +138,16 @@ class CThreadInfo Byte m_Selectors[kNumSelectorsMax]; UInt32 m_CRCs[1 << kNumPassesMax]; - UInt32 m_NumCrcs; - - UInt32 m_BlockIndex; void WriteBits2(UInt32 value, unsigned numBits); - void WriteByte2(Byte b); - void WriteBit2(Byte v); - void WriteCrc2(UInt32 v); + void WriteByte2(unsigned b) { WriteBits2(b, 8); } + void WriteBit2(unsigned v) { m_OutStreamCurrent.WriteBit(v); } void EncodeBlock(const Byte *block, UInt32 blockSize); UInt32 EncodeBlockWithHeaders(const Byte *block, UInt32 blockSize); void EncodeBlock2(const Byte *block, UInt32 blockSize, UInt32 numPasses); public: - bool m_OptimizeNumTables; - CEncoder *Encoder; - #ifndef _7ZIP_ST +#ifndef Z7_ST NWindows::CThread Thread; NWindows::NSynchronization::CAutoResetEvent StreamWasFinishedEvent; @@ -129,15 +156,14 @@ class CThreadInfo // it's not member of this thread. We just need one event per thread NWindows::NSynchronization::CAutoResetEvent CanWriteEvent; - UInt64 m_PackSize; - +public: Byte MtPad[1 << 8]; // It's pad for Multi-Threading. Must be >= Cache_Line_Size. HRESULT Create(); void FinishStream(bool needLeave); - DWORD ThreadFunc(); - #endif + THREAD_FUNC_RET_TYPE ThreadFunc(); +#endif - CThreadInfo(): m_BlockSorterIndex(0), m_Block(0) {} + CThreadInfo(): m_BlockSorterIndex(NULL), m_Block_Base(NULL) {} ~CThreadInfo() { Free(); } bool Alloc(); void Free(); @@ -145,37 +171,60 @@ class CThreadInfo HRESULT EncodeBlock3(UInt32 blockSize); }; + struct CEncProps { UInt32 BlockSizeMult; UInt32 NumPasses; + UInt32 NumThreadGroups; + UInt64 Affinity; CEncProps() { BlockSizeMult = (UInt32)(Int32)-1; NumPasses = (UInt32)(Int32)-1; + NumThreadGroups = 0; + Affinity = 0; } void Normalize(int level); bool DoOptimizeNumTables() const { return NumPasses > 1; } }; -class CEncoder : +class CEncoder Z7_final: public ICompressCoder, public ICompressSetCoderProperties, - #ifndef _7ZIP_ST + #ifndef Z7_ST public ICompressSetCoderMt, - #endif + #endif public CMyUnknownImp { + Z7_COM_QI_BEGIN2(ICompressCoder) + Z7_COM_QI_ENTRY(ICompressSetCoderProperties) + #ifndef Z7_ST + Z7_COM_QI_ENTRY(ICompressSetCoderMt) + #endif + Z7_COM_QI_END + Z7_COM_ADDREF_RELEASE + + Z7_IFACE_COM7_IMP(ICompressCoder) + Z7_IFACE_COM7_IMP(ICompressSetCoderProperties) + #ifndef Z7_ST + Z7_IFACE_COM7_IMP(ICompressSetCoderMt) + #endif + + #ifndef Z7_ST UInt32 m_NumThreadsPrev; + #endif public: CInBuffer m_InStream; + #ifndef Z7_ST Byte MtPad[1 << 8]; // It's pad for Multi-Threading. Must be >= Cache_Line_Size. + #endif CBitmEncoder m_OutStream; CEncProps _props; CBZip2CombinedCrc CombinedCrc; - #ifndef _7ZIP_ST + #ifndef Z7_ST CThreadInfo *ThreadsInfo; NWindows::NSynchronization::CManualResetEvent CanProcessEvent; NWindows::NSynchronization::CCriticalSection CS; @@ -186,52 +235,37 @@ class CEncoder : bool CloseThreads; bool StreamWasFinished; NWindows::NSynchronization::CManualResetEvent CanStartWaitingEvent; + CThreadNextGroup ThreadNextGroup; HRESULT Result; ICompressProgressInfo *Progress; - #else + #else CThreadInfo ThreadsInfo; - #endif + #endif - UInt32 ReadRleBlock(Byte *buf); - void WriteBytes(const Byte *data, UInt32 sizeInBits, Byte lastByte); + UInt64 NumBlocks; + + UInt64 GetInProcessedSize() const { return m_InStream.GetProcessedSize(); } - void WriteBits(UInt32 value, unsigned numBits); + UInt32 ReadRleBlock(Byte *buf); + void WriteBytes(const Byte *data, UInt32 sizeInBits, unsigned lastByteBits); void WriteByte(Byte b); - // void WriteBit(Byte v); - void WriteCrc(UInt32 v); - #ifndef _7ZIP_ST + #ifndef Z7_ST HRESULT Create(); void Free(); - #endif + #endif public: CEncoder(); - #ifndef _7ZIP_ST + #ifndef Z7_ST ~CEncoder(); - #endif + #endif HRESULT Flush() { return m_OutStream.Flush(); } - MY_QUERYINTERFACE_BEGIN2(ICompressCoder) - #ifndef _7ZIP_ST - MY_QUERYINTERFACE_ENTRY(ICompressSetCoderMt) - #endif - MY_QUERYINTERFACE_ENTRY(ICompressSetCoderProperties) - MY_QUERYINTERFACE_END - MY_ADDREF_RELEASE - HRESULT CodeReal(ISequentialInStream *inStream, ISequentialOutStream *outStream, const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress); - - STDMETHOD(Code)(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress); - STDMETHOD(SetCoderProperties)(const PROPID *propIDs, const PROPVARIANT *props, UInt32 numProps); - - #ifndef _7ZIP_ST - STDMETHOD(SetNumberOfThreads)(UInt32 numThreads); - #endif }; }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/BZip2Register.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/BZip2Register.cpp index 6e960366..83b911de 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/BZip2Register.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/BZip2Register.cpp @@ -5,7 +5,7 @@ #include "../Common/RegisterCodec.h" #include "BZip2Decoder.h" -#if !defined(EXTRACT_ONLY) && !defined(BZIP2_EXTRACT_ONLY) +#if !defined(Z7_EXTRACT_ONLY) && !defined(Z7_BZIP2_EXTRACT_ONLY) #include "BZip2Encoder.h" #endif @@ -14,7 +14,7 @@ namespace NBZip2 { REGISTER_CODEC_CREATE(CreateDec, CDecoder) -#if !defined(EXTRACT_ONLY) && !defined(BZIP2_EXTRACT_ONLY) +#if !defined(Z7_EXTRACT_ONLY) && !defined(Z7_BZIP2_EXTRACT_ONLY) REGISTER_CODEC_CREATE(CreateEnc, CEncoder) #else #define CreateEnc NULL diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/Bcj2Coder.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/Bcj2Coder.cpp index 96150c5f..4fa42a5a 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/Bcj2Coder.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/Bcj2Coder.cpp @@ -2,6 +2,8 @@ #include "StdAfx.h" +// #include + #include "../../../C/Alloc.h" #include "../Common/StreamUtils.h" @@ -13,42 +15,47 @@ namespace NBcj2 { CBaseCoder::CBaseCoder() { - for (int i = 0; i < BCJ2_NUM_STREAMS + 1; i++) + for (unsigned i = 0; i < BCJ2_NUM_STREAMS + 1; i++) { _bufs[i] = NULL; - _bufsCurSizes[i] = 0; - _bufsNewSizes[i] = (1 << 18); + _bufsSizes[i] = 0; + _bufsSizes_New[i] = (1 << 18); } } CBaseCoder::~CBaseCoder() { - for (int i = 0; i < BCJ2_NUM_STREAMS + 1; i++) + for (unsigned i = 0; i < BCJ2_NUM_STREAMS + 1; i++) ::MidFree(_bufs[i]); } HRESULT CBaseCoder::Alloc(bool allocForOrig) { - unsigned num = allocForOrig ? BCJ2_NUM_STREAMS + 1 : BCJ2_NUM_STREAMS; + const unsigned num = allocForOrig ? BCJ2_NUM_STREAMS + 1 : BCJ2_NUM_STREAMS; for (unsigned i = 0; i < num; i++) { - UInt32 newSize = _bufsNewSizes[i]; - const UInt32 kMinBufSize = 1; - if (newSize < kMinBufSize) - newSize = kMinBufSize; - if (!_bufs[i] || newSize != _bufsCurSizes[i]) + UInt32 size = _bufsSizes_New[i]; + /* buffer sizes for BCJ2_STREAM_CALL and BCJ2_STREAM_JUMP streams + must be aligned for 4 */ + size &= ~(UInt32)3; + const UInt32 kMinBufSize = 4; + if (size < kMinBufSize) + size = kMinBufSize; + // size = 4 * 100; // for debug + // if (BCJ2_IS_32BIT_STREAM(i) == 1) size = 4 * 1; // for debug + if (!_bufs[i] || size != _bufsSizes[i]) { if (_bufs[i]) { ::MidFree(_bufs[i]); - _bufs[i] = 0; + _bufs[i] = NULL; } - _bufsCurSizes[i] = 0; - Byte *buf = (Byte *)::MidAlloc(newSize); - _bufs[i] = buf; + _bufsSizes[i] = 0; + Byte *buf = (Byte *)::MidAlloc(size); if (!buf) return E_OUTOFMEMORY; - _bufsCurSizes[i] = newSize; + _bufs[i] = buf; + _bufsSizes[i] = size; } } return S_OK; @@ -56,23 +63,30 @@ HRESULT CBaseCoder::Alloc(bool allocForOrig) -#ifndef EXTRACT_ONLY +#ifndef Z7_EXTRACT_ONLY -CEncoder::CEncoder(): _relatLim(BCJ2_RELAT_LIMIT) {} +CEncoder::CEncoder(): + _relatLim(BCJ2_ENC_RELAT_LIMIT_DEFAULT) + // , _excludeRangeBits(BCJ2_RELAT_EXCLUDE_NUM_BITS) + {} CEncoder::~CEncoder() {} -STDMETHODIMP CEncoder::SetInBufSize(UInt32, UInt32 size) { _bufsNewSizes[BCJ2_NUM_STREAMS] = size; return S_OK; } -STDMETHODIMP CEncoder::SetOutBufSize(UInt32 streamIndex, UInt32 size) { _bufsNewSizes[streamIndex] = size; return S_OK; } +Z7_COM7F_IMF(CEncoder::SetInBufSize(UInt32, UInt32 size)) + { _bufsSizes_New[BCJ2_NUM_STREAMS] = size; return S_OK; } +Z7_COM7F_IMF(CEncoder::SetOutBufSize(UInt32 streamIndex, UInt32 size)) + { _bufsSizes_New[streamIndex] = size; return S_OK; } -STDMETHODIMP CEncoder::SetCoderProperties(const PROPID *propIDs, const PROPVARIANT *props, UInt32 numProps) +Z7_COM7F_IMF(CEncoder::SetCoderProperties(const PROPID *propIDs, const PROPVARIANT *props, UInt32 numProps)) { - UInt32 relatLim = BCJ2_RELAT_LIMIT; - + UInt32 relatLim = BCJ2_ENC_RELAT_LIMIT_DEFAULT; + // UInt32 excludeRangeBits = BCJ2_RELAT_EXCLUDE_NUM_BITS; for (UInt32 i = 0; i < numProps; i++) { const PROPVARIANT &prop = props[i]; - PROPID propID = propIDs[i]; - if (propID >= NCoderPropID::kReduceSize) + const PROPID propID = propIDs[i]; + if (propID >= NCoderPropID::kReduceSize + // && propID != NCoderPropID::kHashBits + ) continue; switch (propID) { @@ -87,225 +101,310 @@ STDMETHODIMP CEncoder::SetCoderProperties(const PROPID *propIDs, const PROPVARIA relatLim = (UInt32)1 << v; break; } + case NCoderPropID::kHashBits: + { + if (prop.vt != VT_UI4) + return E_INVALIDARG; + UInt32 v = prop.ulVal; + if (v > 31) + return E_INVALIDARG; + excludeRangeBits = v; + break; + } */ case NCoderPropID::kDictionarySize: { if (prop.vt != VT_UI4) return E_INVALIDARG; relatLim = prop.ulVal; - if (relatLim > ((UInt32)1 << 31)) + if (relatLim > BCJ2_ENC_RELAT_LIMIT_MAX) return E_INVALIDARG; break; } - case NCoderPropID::kNumThreads: - continue; case NCoderPropID::kLevel: continue; - default: return E_INVALIDARG; } } - _relatLim = relatLim; - + // _excludeRangeBits = excludeRangeBits; return S_OK; } -HRESULT CEncoder::CodeReal(ISequentialInStream * const *inStreams, const UInt64 * const *inSizes, UInt32 numInStreams, +HRESULT CEncoder::CodeReal( + ISequentialInStream * const *inStreams, const UInt64 * const *inSizes, UInt32 numInStreams, ISequentialOutStream * const *outStreams, const UInt64 * const * /* outSizes */, UInt32 numOutStreams, ICompressProgressInfo *progress) { if (numInStreams != 1 || numOutStreams != BCJ2_NUM_STREAMS) return E_INVALIDARG; - RINOK(Alloc()); + RINOK(Alloc()) - UInt32 fileSize_for_Conv = 0; + CBcj2Enc_ip_unsigned fileSize_minus1 = BCJ2_ENC_FileSizeField_UNLIMITED; if (inSizes && inSizes[0]) { - UInt64 inSize = *inSizes[0]; - if (inSize <= BCJ2_FileSize_MAX) - fileSize_for_Conv = (UInt32)inSize; + const UInt64 inSize = *inSizes[0]; + #ifdef BCJ2_ENC_FileSize_MAX + if (inSize <= BCJ2_ENC_FileSize_MAX) + #endif + fileSize_minus1 = BCJ2_ENC_GET_FileSizeField_VAL_FROM_FileSize(inSize); } - CMyComPtr getSubStreamSize; - inStreams[0]->QueryInterface(IID_ICompressGetSubStreamSize, (void **)&getSubStreamSize); + Z7_DECL_CMyComPtr_QI_FROM(ICompressGetSubStreamSize, getSubStreamSize, inStreams[0]) CBcj2Enc enc; - enc.src = _bufs[BCJ2_NUM_STREAMS]; enc.srcLim = enc.src; - { - for (int i = 0; i < BCJ2_NUM_STREAMS; i++) + for (unsigned i = 0; i < BCJ2_NUM_STREAMS; i++) { enc.bufs[i] = _bufs[i]; - enc.lims[i] = _bufs[i] + _bufsCurSizes[i]; + enc.lims[i] = _bufs[i] + _bufsSizes[i]; } } - - size_t numBytes_in_ReadBuf = 0; - UInt64 prevProgress = 0; - UInt64 totalStreamRead = 0; // size read from InputStream - UInt64 currentInPos = 0; // data that was processed, it doesn't include data in input buffer and data in enc.temp - UInt64 outSizeRc = 0; - Bcj2Enc_Init(&enc); - - enc.fileIp = 0; - enc.fileSize = fileSize_for_Conv; - + enc.fileIp64 = 0; + enc.fileSize64_minus1 = fileSize_minus1; enc.relatLimit = _relatLim; - + // enc.relatExcludeBits = _excludeRangeBits; enc.finishMode = BCJ2_ENC_FINISH_MODE_CONTINUE; - bool needSubSize = false; - UInt64 subStreamIndex = 0; - UInt64 subStreamStartPos = 0; + // Varibales that correspond processed data in input stream: + UInt64 inPos_without_Temp = 0; // it doesn't include data in enc.temp[] + UInt64 inPos_with_Temp = 0; // it includes data in enc.temp[] + + UInt64 prevProgress = 0; + UInt64 totalRead = 0; // size read from input stream + UInt64 outSizeRc = 0; + UInt64 subStream_Index = 0; + UInt64 subStream_StartPos = 0; // global start offset of subStreams[subStream_Index] + UInt64 subStream_Size = 0; + const Byte *srcLim_Read = _bufs[BCJ2_NUM_STREAMS]; bool readWasFinished = false; + bool isAccurate = false; + bool wasUnknownSize = false; for (;;) { - if (needSubSize && getSubStreamSize) - { - enc.fileIp = 0; - enc.fileSize = fileSize_for_Conv; - enc.finishMode = BCJ2_ENC_FINISH_MODE_CONTINUE; - - for (;;) - { - UInt64 subStreamSize = 0; - HRESULT result = getSubStreamSize->GetSubStreamSize(subStreamIndex, &subStreamSize); - needSubSize = false; - - if (result == S_OK) - { - UInt64 newEndPos = subStreamStartPos + subStreamSize; - - bool isAccurateEnd = (newEndPos < totalStreamRead || - (newEndPos <= totalStreamRead && readWasFinished)); - - if (newEndPos <= currentInPos && isAccurateEnd) - { - subStreamStartPos = newEndPos; - subStreamIndex++; - continue; - } - - enc.srcLim = _bufs[BCJ2_NUM_STREAMS] + numBytes_in_ReadBuf; - - if (isAccurateEnd) - { - // data in enc.temp is possible here - size_t rem = (size_t)(totalStreamRead - newEndPos); - - /* Pos_of(enc.src) <= old newEndPos <= newEndPos - in another case, it's fail in some code */ - if ((size_t)(enc.srcLim - enc.src) < rem) - return E_FAIL; - - enc.srcLim -= rem; - enc.finishMode = BCJ2_ENC_FINISH_MODE_END_BLOCK; - } - - if (subStreamSize <= BCJ2_FileSize_MAX) - { - enc.fileIp = enc.ip + (UInt32)(subStreamStartPos - currentInPos); - enc.fileSize = (UInt32)subStreamSize; - } - break; - } - - if (result == S_FALSE) - break; - if (result == E_NOTIMPL) - { - getSubStreamSize.Release(); - break; - } - return result; - } - } - - if (readWasFinished && totalStreamRead - currentInPos == Bcj2Enc_Get_InputData_Size(&enc)) + if (readWasFinished && enc.srcLim == srcLim_Read) enc.finishMode = BCJ2_ENC_FINISH_MODE_END_STREAM; + // for debug: + // for (int y=0;y<100;y++) { CBcj2Enc enc2 = enc; Bcj2Enc_Encode(&enc2); } + Bcj2Enc_Encode(&enc); - currentInPos = totalStreamRead - numBytes_in_ReadBuf + (enc.src - _bufs[BCJ2_NUM_STREAMS]) - enc.tempPos; + inPos_with_Temp = totalRead - (size_t)(srcLim_Read - enc.src); + inPos_without_Temp = inPos_with_Temp - Bcj2Enc_Get_AvailInputSize_in_Temp(&enc); + // if (inPos_without_Temp != enc.ip64) return E_FAIL; + if (Bcj2Enc_IsFinished(&enc)) break; if (enc.state < BCJ2_NUM_STREAMS) { - size_t curSize = enc.bufs[enc.state] - _bufs[enc.state]; + if (enc.bufs[enc.state] != enc.lims[enc.state]) + return E_FAIL; + const size_t curSize = (size_t)(enc.bufs[enc.state] - _bufs[enc.state]); // printf("Write stream = %2d %6d\n", enc.state, curSize); - RINOK(WriteStream(outStreams[enc.state], _bufs[enc.state], curSize)); + RINOK(WriteStream(outStreams[enc.state], _bufs[enc.state], curSize)) if (enc.state == BCJ2_STREAM_RC) outSizeRc += curSize; - enc.bufs[enc.state] = _bufs[enc.state]; - enc.lims[enc.state] = _bufs[enc.state] + _bufsCurSizes[enc.state]; + enc.lims[enc.state] = _bufs[enc.state] + _bufsSizes[enc.state]; } - else if (enc.state != BCJ2_ENC_STATE_ORIG) - return E_FAIL; else { - needSubSize = true; - - if (numBytes_in_ReadBuf != (size_t)(enc.src - _bufs[BCJ2_NUM_STREAMS])) + if (enc.state != BCJ2_ENC_STATE_ORIG) + return E_FAIL; + // (enc.state == BCJ2_ENC_STATE_ORIG) + if (enc.src != enc.srcLim) + return E_FAIL; + if (enc.finishMode != BCJ2_ENC_FINISH_MODE_CONTINUE + && Bcj2Enc_Get_AvailInputSize_in_Temp(&enc) != 0) + return E_FAIL; + + if (enc.src == srcLim_Read) { - enc.srcLim = _bufs[BCJ2_NUM_STREAMS] + numBytes_in_ReadBuf; - continue; + if (readWasFinished) + return E_FAIL; + UInt32 curSize = _bufsSizes[BCJ2_NUM_STREAMS]; + RINOK(inStreams[0]->Read(_bufs[BCJ2_NUM_STREAMS], curSize, &curSize)) + // printf("Read %6u bytes\n", curSize); + if (curSize == 0) + readWasFinished = true; + totalRead += curSize; + enc.src = _bufs[BCJ2_NUM_STREAMS]; + srcLim_Read = _bufs[BCJ2_NUM_STREAMS] + curSize; } + enc.srcLim = srcLim_Read; - if (readWasFinished) - continue; - - numBytes_in_ReadBuf = 0; - enc.src = _bufs[BCJ2_NUM_STREAMS]; - enc.srcLim = _bufs[BCJ2_NUM_STREAMS]; - - UInt32 curSize = _bufsCurSizes[BCJ2_NUM_STREAMS]; - RINOK(inStreams[0]->Read(_bufs[BCJ2_NUM_STREAMS], curSize, &curSize)); - - // printf("Read %6d bytes\n", curSize); - if (curSize == 0) + if (getSubStreamSize) { - readWasFinished = true; - continue; - } + /* we set base default conversions options that will be used, + if subStream related options will be not OK */ + enc.fileIp64 = 0; + enc.fileSize64_minus1 = fileSize_minus1; + for (;;) + { + UInt64 nextPos; + if (isAccurate) + nextPos = subStream_StartPos + subStream_Size; + else + { + const HRESULT hres = getSubStreamSize->GetSubStreamSize(subStream_Index, &subStream_Size); + if (hres != S_OK) + { + enc.finishMode = BCJ2_ENC_FINISH_MODE_CONTINUE; + /* if sub-stream size is unknown, we use default settings. + We still can recover to normal mode for next sub-stream, + if GetSubStreamSize() will return S_OK, when current + sub-stream will be finished. + */ + if (hres == S_FALSE) + { + wasUnknownSize = true; + break; + } + if (hres == E_NOTIMPL) + { + getSubStreamSize.Release(); + break; + } + return hres; + } + // printf("GetSubStreamSize %6u : %6u \n", (unsigned)subStream_Index, (unsigned)subStream_Size); + nextPos = subStream_StartPos + subStream_Size; + if ((Int64)subStream_Size == -1) + { + /* it's not expected, but (-1) can mean unknown size. */ + enc.finishMode = BCJ2_ENC_FINISH_MODE_CONTINUE; + wasUnknownSize = true; + break; + } + if (nextPos < subStream_StartPos) + return E_FAIL; + isAccurate = + (nextPos < totalRead + || (nextPos <= totalRead && readWasFinished)); + } + + /* (nextPos) is estimated end position of current sub_stream. + But only (totalRead) and (readWasFinished) values + can confirm that this estimated end position is accurate. + That end position is accurate, if it can't be changed in + further calls of GetSubStreamSize() */ + + /* (nextPos < inPos_with_Temp) is unexpected case here, that we + can get if from some incorrect ICompressGetSubStreamSize object, + where new GetSubStreamSize() call returns smaller size than + confirmed by Read() size from previous GetSubStreamSize() call. + */ + if (nextPos < inPos_with_Temp) + { + if (wasUnknownSize) + { + /* that case can be complicated for recovering. + so we disable sub-streams requesting. */ + enc.finishMode = BCJ2_ENC_FINISH_MODE_CONTINUE; + getSubStreamSize.Release(); + break; + } + return E_FAIL; // to stop after failure + } + + if (nextPos <= inPos_with_Temp) + { + // (nextPos == inPos_with_Temp) + /* CBcj2Enc encoder requires to finish each [non-empty] block (sub-stream) + with BCJ2_ENC_FINISH_MODE_END_BLOCK + or with BCJ2_ENC_FINISH_MODE_END_STREAM for last block: + And we send data of new block to CBcj2Enc, only if previous block was finished. + So we switch to next sub-stream if after Bcj2Enc_Encode() call we have + && (enc.finishMode != BCJ2_ENC_FINISH_MODE_CONTINUE) + && (nextPos == inPos_with_Temp) + && (enc.state == BCJ2_ENC_STATE_ORIG) + */ + if (enc.finishMode != BCJ2_ENC_FINISH_MODE_CONTINUE) + { + /* subStream_StartPos is increased only here. + (subStream_StartPos == inPos_with_Temp) : at start + (subStream_StartPos <= inPos_with_Temp) : will be later + */ + subStream_StartPos = nextPos; + subStream_Size = 0; + wasUnknownSize = false; + subStream_Index++; + isAccurate = false; + // we don't change finishMode here + continue; + } + } + + enc.finishMode = BCJ2_ENC_FINISH_MODE_CONTINUE; + /* for (!isAccurate) case: + (totalRead <= real_end_of_subStream) + so we can use BCJ2_ENC_FINISH_MODE_CONTINUE up to (totalRead) + // we don't change settings at the end of substream, if settings were unknown, + */ + + /* if (wasUnknownSize) then we can't trust size of that sub-stream. + so we use default settings instead */ + if (!wasUnknownSize) + #ifdef BCJ2_ENC_FileSize_MAX + if (subStream_Size <= BCJ2_ENC_FileSize_MAX) + #endif + { + enc.fileIp64 = + (CBcj2Enc_ip_unsigned)( + (CBcj2Enc_ip_signed)enc.ip64 + + (CBcj2Enc_ip_signed)(subStream_StartPos - inPos_without_Temp)); + Bcj2Enc_SET_FileSize(&enc, subStream_Size) + } - numBytes_in_ReadBuf = curSize; - totalStreamRead += numBytes_in_ReadBuf; - enc.srcLim = _bufs[BCJ2_NUM_STREAMS] + numBytes_in_ReadBuf; + if (isAccurate) + { + /* (real_end_of_subStream == nextPos <= totalRead) + So we can use BCJ2_ENC_FINISH_MODE_END_BLOCK up to (nextPos). */ + const size_t rem = (size_t)(totalRead - nextPos); + if ((size_t)(enc.srcLim - enc.src) < rem) + return E_FAIL; + enc.srcLim -= rem; + enc.finishMode = BCJ2_ENC_FINISH_MODE_END_BLOCK; + } + + break; + } // for() loop + } // getSubStreamSize } - if (progress && currentInPos - prevProgress >= (1 << 20)) + if (progress && inPos_without_Temp - prevProgress >= (1 << 22)) { - UInt64 outSize2 = currentInPos + outSizeRc + enc.bufs[BCJ2_STREAM_RC] - enc.bufs[BCJ2_STREAM_RC]; - prevProgress = currentInPos; - // printf("progress %8d, %8d\n", (int)inSize2, (int)outSize2); - RINOK(progress->SetRatioInfo(¤tInPos, &outSize2)); + prevProgress = inPos_without_Temp; + const UInt64 outSize2 = inPos_without_Temp + outSizeRc + + (size_t)(enc.bufs[BCJ2_STREAM_RC] - _bufs[BCJ2_STREAM_RC]); + // printf("progress %8u, %8u\n", (unsigned)inSize2, (unsigned)outSize2); + RINOK(progress->SetRatioInfo(&inPos_without_Temp, &outSize2)) } } - for (int i = 0; i < BCJ2_NUM_STREAMS; i++) + for (unsigned i = 0; i < BCJ2_NUM_STREAMS; i++) { - RINOK(WriteStream(outStreams[i], _bufs[i], enc.bufs[i] - _bufs[i])); + RINOK(WriteStream(outStreams[i], _bufs[i], (size_t)(enc.bufs[i] - _bufs[i]))) } - - // if (currentInPos != subStreamStartPos + subStreamSize) return E_FAIL; - + // if (inPos_without_Temp != subStream_StartPos + subStream_Size) return E_FAIL; return S_OK; } -STDMETHODIMP CEncoder::Code(ISequentialInStream * const *inStreams, const UInt64 * const *inSizes, UInt32 numInStreams, + +Z7_COM7F_IMF(CEncoder::Code( + ISequentialInStream * const *inStreams, const UInt64 * const *inSizes, UInt32 numInStreams, ISequentialOutStream * const *outStreams, const UInt64 * const *outSizes, UInt32 numOutStreams, - ICompressProgressInfo *progress) + ICompressProgressInfo *progress)) { try { @@ -321,141 +420,170 @@ STDMETHODIMP CEncoder::Code(ISequentialInStream * const *inStreams, const UInt64 -STDMETHODIMP CDecoder::SetInBufSize(UInt32 streamIndex, UInt32 size) { _bufsNewSizes[streamIndex] = size; return S_OK; } -STDMETHODIMP CDecoder::SetOutBufSize(UInt32 , UInt32 size) { _bufsNewSizes[BCJ2_NUM_STREAMS] = size; return S_OK; } - -CDecoder::CDecoder(): _finishMode(false), _outSizeDefined(false), _outSize(0) +CDecoder::CDecoder(): + _finishMode(false) +#ifndef Z7_NO_READ_FROM_CODER + , _outSizeDefined(false) + , _outSize(0) + , _outSize_Processed(0) +#endif {} -STDMETHODIMP CDecoder::SetFinishMode(UInt32 finishMode) +Z7_COM7F_IMF(CDecoder::SetInBufSize(UInt32 streamIndex, UInt32 size)) + { _bufsSizes_New[streamIndex] = size; return S_OK; } +Z7_COM7F_IMF(CDecoder::SetOutBufSize(UInt32, UInt32 size)) + { _bufsSizes_New[BCJ2_NUM_STREAMS] = size; return S_OK; } + +Z7_COM7F_IMF(CDecoder::SetFinishMode(UInt32 finishMode)) { _finishMode = (finishMode != 0); return S_OK; } -void CDecoder::InitCommon() +void CBaseDecoder::InitCommon() { + for (unsigned i = 0; i < BCJ2_NUM_STREAMS; i++) { - for (int i = 0; i < BCJ2_NUM_STREAMS; i++) - dec.lims[i] = dec.bufs[i] = _bufs[i]; + dec.lims[i] = dec.bufs[i] = _bufs[i]; + _readRes[i] = S_OK; + _extraSizes[i] = 0; + _readSizes[i] = 0; } + Bcj2Dec_Init(&dec); +} + +/* call ReadInStream() only after Bcj2Dec_Decode(). + input requirement: + (dec.state < BCJ2_NUM_STREAMS) +*/ +void CBaseDecoder::ReadInStream(ISequentialInStream *inStream) +{ + const unsigned state = dec.state; + UInt32 total; + { + Byte *buf = _bufs[state]; + const Byte *cur = dec.bufs[state]; + // if (cur != dec.lims[state]) throw 1; // unexpected case + dec.lims[state] = + dec.bufs[state] = buf; + total = (UInt32)_extraSizes[state]; + for (UInt32 i = 0; i < total; i++) + buf[i] = cur[i]; + } + + if (_readRes[state] != S_OK) + return; + + do { - for (int i = 0; i < BCJ2_NUM_STREAMS; i++) + UInt32 curSize = _bufsSizes[state] - total; + // if (state == 0) curSize = 0; // for debug + // curSize = 7; // for debug + /* even if we have reached provided inSizes[state] limit, + we call Read() with (curSize != 0), because + we want the called handler of stream->Read() could + execute required Init/Flushing code even for empty stream. + In another way we could call Read() with (curSize == 0) for + finished streams, but some Read() handlers can ignore Read(size=0) calls. + */ + const HRESULT hres = inStream->Read(_bufs[state] + total, curSize, &curSize); + _readRes[state] = hres; + if (curSize == 0) + break; + _readSizes[state] += curSize; + total += curSize; + if (hres != S_OK) + break; + } + while (total < 4 && BCJ2_IS_32BIT_STREAM(state)); + + /* we exit from decoding loop here, if we can't + provide new data for input stream. + Usually it's normal exit after full stream decoding. */ + if (total == 0) + return; + + if (BCJ2_IS_32BIT_STREAM(state)) + { + const unsigned extra = (unsigned)total & 3; + _extraSizes[state] = extra; + if (total < 4) { - _extraReadSizes[i] = 0; - _inStreamsProcessed[i] = 0; - _readRes[i] = S_OK; + if (_readRes[state] == S_OK) + _readRes[state] = S_FALSE; // actually it's stream error. So maybe we need another error code. + return; } + total -= (UInt32)extra; } - - Bcj2Dec_Init(&dec); + + dec.lims[state] += total; // = _bufs[state] + total; } -HRESULT CDecoder::Code(ISequentialInStream * const *inStreams, const UInt64 * const *inSizes, UInt32 numInStreams, + +Z7_COM7F_IMF(CDecoder::Code( + ISequentialInStream * const *inStreams, const UInt64 * const *inSizes, UInt32 numInStreams, ISequentialOutStream * const *outStreams, const UInt64 * const *outSizes, UInt32 numOutStreams, - ICompressProgressInfo *progress) + ICompressProgressInfo *progress)) { if (numInStreams != BCJ2_NUM_STREAMS || numOutStreams != 1) return E_INVALIDARG; - RINOK(Alloc()); - + RINOK(Alloc()) InitCommon(); dec.destLim = dec.dest = _bufs[BCJ2_NUM_STREAMS]; - UInt64 outSizeProcessed = 0; + UInt64 outSizeWritten = 0; UInt64 prevProgress = 0; - HRESULT res = S_OK; + HRESULT hres_Crit = S_OK; // critical hres status (mostly from input stream reading) + HRESULT hres_Weak = S_OK; // first non-critical error code from input stream reading for (;;) { if (Bcj2Dec_Decode(&dec) != SZ_OK) - return S_FALSE; - + { + /* it's possible only at start (first 5 bytes in RC stream) */ + hres_Crit = S_FALSE; + break; + } if (dec.state < BCJ2_NUM_STREAMS) { - size_t totalRead = _extraReadSizes[dec.state]; - { - Byte *buf = _bufs[dec.state]; - for (size_t i = 0; i < totalRead; i++) - buf[i] = dec.bufs[dec.state][i]; - dec.lims[dec.state] = - dec.bufs[dec.state] = buf; - } - - if (_readRes[dec.state] != S_OK) + ReadInStream(inStreams[dec.state]); + const unsigned state = dec.state; + const HRESULT hres = _readRes[state]; + if (dec.lims[state] == _bufs[state]) { - res = _readRes[dec.state]; - break; - } - - do - { - UInt32 curSize = _bufsCurSizes[dec.state] - (UInt32)totalRead; - /* - we want to call Read even even if size is 0 - if (inSizes && inSizes[dec.state]) - { - UInt64 rem = *inSizes[dec.state] - _inStreamsProcessed[dec.state]; - if (curSize > rem) - curSize = (UInt32)rem; - } - */ - - HRESULT res2 = inStreams[dec.state]->Read(_bufs[dec.state] + totalRead, curSize, &curSize); - _readRes[dec.state] = res2; - if (curSize == 0) - break; - _inStreamsProcessed[dec.state] += curSize; - totalRead += curSize; - if (res2 != S_OK) - break; - } - while (totalRead < 4 && BCJ2_IS_32BIT_STREAM(dec.state)); - - if (_readRes[dec.state] != S_OK) - res = _readRes[dec.state]; - - if (totalRead == 0) + // we break decoding, if there are no new data in input stream + hres_Crit = hres; break; - - // res == S_OK; - - if (BCJ2_IS_32BIT_STREAM(dec.state)) - { - unsigned extraSize = ((unsigned)totalRead & 3); - _extraReadSizes[dec.state] = extraSize; - if (totalRead < 4) - { - res = (_readRes[dec.state] != S_OK) ? _readRes[dec.state] : S_FALSE; - break; - } - totalRead -= extraSize; } - - dec.lims[dec.state] = _bufs[dec.state] + totalRead; + if (hres != S_OK && hres_Weak == S_OK) + hres_Weak = hres; } - else // if (dec.state <= BCJ2_STATE_ORIG) + else // (BCJ2_DEC_STATE_ORIG_0 <= state <= BCJ2_STATE_ORIG) { - size_t curSize = dec.dest - _bufs[BCJ2_NUM_STREAMS]; - if (curSize != 0) { - outSizeProcessed += curSize; - RINOK(WriteStream(outStreams[0], _bufs[BCJ2_NUM_STREAMS], curSize)); + const size_t curSize = (size_t)(dec.dest - _bufs[BCJ2_NUM_STREAMS]); + if (curSize != 0) + { + outSizeWritten += curSize; + RINOK(WriteStream(outStreams[0], _bufs[BCJ2_NUM_STREAMS], curSize)) + } } - dec.dest = _bufs[BCJ2_NUM_STREAMS]; { - size_t rem = _bufsCurSizes[BCJ2_NUM_STREAMS]; + UInt32 rem = _bufsSizes[BCJ2_NUM_STREAMS]; if (outSizes && outSizes[0]) { - UInt64 outSize = *outSizes[0] - outSizeProcessed; + const UInt64 outSize = *outSizes[0] - outSizeWritten; if (rem > outSize) - rem = (size_t)outSize; + rem = (UInt32)outSize; } + dec.dest = _bufs[BCJ2_NUM_STREAMS]; dec.destLim = dec.dest + rem; + /* we exit from decoding loop here, + if (outSizes[0]) limit for output stream was reached */ if (rem == 0) break; } @@ -463,98 +591,148 @@ HRESULT CDecoder::Code(ISequentialInStream * const *inStreams, const UInt64 * co if (progress) { - UInt64 outSize2 = outSizeProcessed + (dec.dest - _bufs[BCJ2_NUM_STREAMS]); - if (outSize2 - prevProgress >= (1 << 22)) + // here we don't count additional data in dec.temp (up to 4 bytes for output stream) + const UInt64 processed = outSizeWritten + (size_t)(dec.dest - _bufs[BCJ2_NUM_STREAMS]); + if (processed - prevProgress >= (1 << 24)) { - UInt64 inSize2 = outSize2 + _inStreamsProcessed[BCJ2_STREAM_RC] - (dec.lims[BCJ2_STREAM_RC] - dec.bufs[BCJ2_STREAM_RC]); - RINOK(progress->SetRatioInfo(&inSize2, &outSize2)); - prevProgress = outSize2; + prevProgress = processed; + const UInt64 inSize = processed + + _readSizes[BCJ2_STREAM_RC] - (size_t)( + dec.lims[BCJ2_STREAM_RC] - + dec.bufs[BCJ2_STREAM_RC]); + RINOK(progress->SetRatioInfo(&inSize, &prevProgress)) } } } - size_t curSize = dec.dest - _bufs[BCJ2_NUM_STREAMS]; - if (curSize != 0) { - outSizeProcessed += curSize; - RINOK(WriteStream(outStreams[0], _bufs[BCJ2_NUM_STREAMS], curSize)); + const size_t curSize = (size_t)(dec.dest - _bufs[BCJ2_NUM_STREAMS]); + if (curSize != 0) + { + outSizeWritten += curSize; + RINOK(WriteStream(outStreams[0], _bufs[BCJ2_NUM_STREAMS], curSize)) + } } - if (res != S_OK) - return res; + if (hres_Crit == S_OK) hres_Crit = hres_Weak; + if (hres_Crit != S_OK) return hres_Crit; if (_finishMode) { - if (!Bcj2Dec_IsFinished(&dec)) + if (!Bcj2Dec_IsMaybeFinished_code(&dec)) return S_FALSE; - // we still allow the cases when input streams are larger than required for decoding. - // so the case (dec.state == BCJ2_STATE_ORIG) is also allowed, if MAIN stream is larger than required. - if (dec.state != BCJ2_STREAM_MAIN && - dec.state != BCJ2_DEC_STATE_ORIG) + /* here we support two correct ways to finish full stream decoding + with one of the following conditions: + - the end of input stream MAIN was reached + - the end of output stream ORIG was reached + Currently 7-Zip/7z code ends with (state == BCJ2_STREAM_MAIN), + because the sizes of MAIN and ORIG streams are known and these + sizes are stored in 7z archive headers. + And Bcj2Dec_Decode() exits with (state == BCJ2_STREAM_MAIN), + if both MAIN and ORIG streams have reached buffers limits. + But if the size of MAIN stream is not known or if the + size of MAIN stream includes some padding after payload data, + then we still can correctly finish decoding with + (state == BCJ2_DEC_STATE_ORIG), if we know the exact size + of output ORIG stream. + */ + if (dec.state != BCJ2_STREAM_MAIN) + if (dec.state != BCJ2_DEC_STATE_ORIG) + return S_FALSE; + + /* the caller also will know written size. + So the following check is optional: */ + if (outSizes && outSizes[0] && *outSizes[0] != outSizeWritten) return S_FALSE; if (inSizes) { - for (int i = 0; i < BCJ2_NUM_STREAMS; i++) + for (unsigned i = 0; i < BCJ2_NUM_STREAMS; i++) { - size_t rem = dec.lims[i] - dec.bufs[i] + _extraReadSizes[i]; - /* - if (rem != 0) - return S_FALSE; - */ - if (inSizes[i] && *inSizes[i] != _inStreamsProcessed[i] - rem) + /* if (inSizes[i]) is defined, we do full check for processed stream size. */ + if (inSizes[i] && *inSizes[i] != GetProcessedSize_ForInStream(i)) return S_FALSE; } } + + /* v23.02: we call Read(0) for BCJ2_STREAM_CALL and BCJ2_STREAM_JUMP streams, + if there were no Read() calls for such stream. + So the handlers of these input streams objects can do + Init/Flushing even for case when stream is empty: + */ + for (unsigned i = BCJ2_STREAM_CALL; i < BCJ2_STREAM_CALL + 2; i++) + { + if (_readSizes[i]) + continue; + Byte b; + UInt32 processed; + RINOK(inStreams[i]->Read(&b, 0, &processed)) + } } return S_OK; } -STDMETHODIMP CDecoder::SetInStream2(UInt32 streamIndex, ISequentialInStream *inStream) + +Z7_COM7F_IMF(CDecoder::GetInStreamProcessedSize2(UInt32 streamIndex, UInt64 *value)) +{ + *value = GetProcessedSize_ForInStream(streamIndex); + return S_OK; +} + + +#ifndef Z7_NO_READ_FROM_CODER + +Z7_COM7F_IMF(CDecoder::SetInStream2(UInt32 streamIndex, ISequentialInStream *inStream)) { _inStreams[streamIndex] = inStream; return S_OK; } -STDMETHODIMP CDecoder::ReleaseInStream2(UInt32 streamIndex) +Z7_COM7F_IMF(CDecoder::ReleaseInStream2(UInt32 streamIndex)) { _inStreams[streamIndex].Release(); return S_OK; } -STDMETHODIMP CDecoder::SetOutStreamSize(const UInt64 *outSize) +Z7_COM7F_IMF(CDecoder::SetOutStreamSize(const UInt64 *outSize)) { _outSizeDefined = (outSize != NULL); _outSize = 0; if (_outSizeDefined) _outSize = *outSize; - _outSize_Processed = 0; - HRESULT res = Alloc(false); - + const HRESULT res = Alloc(false); // allocForOrig InitCommon(); dec.destLim = dec.dest = NULL; - return res; } -STDMETHODIMP CDecoder::Read(void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CDecoder::Read(void *data, UInt32 size, UInt32 *processedSize)) { if (processedSize) *processedSize = 0; - if (size == 0) - return S_OK; + /* Note the case: + The output (ORIG) stream can be empty. + But BCJ2_STREAM_RC stream always is not empty. + And we want to support full data processing for all streams. + We disable check (size == 0) here. + So if the caller calls this CDecoder::Read() with (size == 0), + we execute required Init/Flushing code in this CDecoder object. + Also this CDecoder::Read() function will call Read() for input streams. + So the handlers of input streams objects also can do Init/Flushing. + */ + // if (size == 0) return S_OK; // disabled to allow (size == 0) processing UInt32 totalProcessed = 0; if (_outSizeDefined) { - UInt64 rem = _outSize - _outSize_Processed; + const UInt64 rem = _outSize - _outSize_Processed; if (size > rem) size = (UInt32)rem; } @@ -565,94 +743,125 @@ STDMETHODIMP CDecoder::Read(void *data, UInt32 size, UInt32 *processedSize) for (;;) { - SRes sres = Bcj2Dec_Decode(&dec); - if (sres != SZ_OK) - return S_FALSE; - + if (Bcj2Dec_Decode(&dec) != SZ_OK) + return S_FALSE; // this error can be only at start of stream { - UInt32 curSize = (UInt32)(dec.dest - (Byte *)data); + const UInt32 curSize = (UInt32)(size_t)(dec.dest - (Byte *)data); if (curSize != 0) { - totalProcessed += curSize; - if (processedSize) - *processedSize = totalProcessed; data = (void *)((Byte *)data + curSize); size -= curSize; _outSize_Processed += curSize; + totalProcessed += curSize; + if (processedSize) + *processedSize = totalProcessed; } } - if (dec.state >= BCJ2_NUM_STREAMS) break; - + ReadInStream(_inStreams[dec.state]); + if (dec.lims[dec.state] == _bufs[dec.state]) { - size_t totalRead = _extraReadSizes[dec.state]; - { - Byte *buf = _bufs[dec.state]; - for (size_t i = 0; i < totalRead; i++) - buf[i] = dec.bufs[dec.state][i]; - dec.lims[dec.state] = - dec.bufs[dec.state] = buf; - } - - if (_readRes[dec.state] != S_OK) - return _readRes[dec.state]; - - do - { - UInt32 curSize = _bufsCurSizes[dec.state] - (UInt32)totalRead; - HRESULT res2 = _inStreams[dec.state]->Read(_bufs[dec.state] + totalRead, curSize, &curSize); - _readRes[dec.state] = res2; - if (curSize == 0) - break; - _inStreamsProcessed[dec.state] += curSize; - totalRead += curSize; - if (res2 != S_OK) - break; - } - while (totalRead < 4 && BCJ2_IS_32BIT_STREAM(dec.state)); - - if (totalRead == 0) - { - if (totalProcessed == 0) - res = _readRes[dec.state]; - break; - } - - if (BCJ2_IS_32BIT_STREAM(dec.state)) - { - unsigned extraSize = ((unsigned)totalRead & 3); - _extraReadSizes[dec.state] = extraSize; - if (totalRead < 4) - { - if (totalProcessed != 0) - return S_OK; - return (_readRes[dec.state] != S_OK) ? _readRes[dec.state] : S_FALSE; - } - totalRead -= extraSize; - } - - dec.lims[dec.state] = _bufs[dec.state] + totalRead; + /* we break decoding, if there are no new data in input stream. + and we ignore error code, if some data were written to output buffer. */ + if (totalProcessed == 0) + res = _readRes[dec.state]; + break; } } + if (res == S_OK) if (_finishMode && _outSizeDefined && _outSize == _outSize_Processed) { - if (!Bcj2Dec_IsFinished(&dec)) + if (!Bcj2Dec_IsMaybeFinished_code(&dec)) return S_FALSE; - - if (dec.state != BCJ2_STREAM_MAIN && - dec.state != BCJ2_DEC_STATE_ORIG) + if (dec.state != BCJ2_STREAM_MAIN) + if (dec.state != BCJ2_DEC_STATE_ORIG) return S_FALSE; - - /* - for (int i = 0; i < BCJ2_NUM_STREAMS; i++) - if (dec.bufs[i] != dec.lims[i] || _extraReadSizes[i] != 0) - return S_FALSE; - */ } return res; } +#endif + }} + + +/* +extern "C" +{ +extern UInt32 bcj2_stats[256 + 2][2]; +} + +static class CBcj2Stat +{ +public: + ~CBcj2Stat() + { + printf("\nBCJ2 stat:"); + unsigned sums[2] = { 0, 0 }; + int i; + for (i = 2; i < 256 + 2; i++) + { + sums[0] += bcj2_stats[i][0]; + sums[1] += bcj2_stats[i][1]; + } + const unsigned sums2 = sums[0] + sums[1]; + for (int vi = 0; vi < 256 + 3; vi++) + { + printf("\n"); + UInt32 n0, n1; + if (vi < 4) + printf("\n"); + + if (vi < 2) + i = vi; + else if (vi == 2) + i = -1; + else + i = vi - 1; + + if (i < 0) + { + n0 = sums[0]; + n1 = sums[1]; + printf("calls :"); + } + else + { + if (i == 0) + printf("jcc :"); + else if (i == 1) + printf("jump :"); + else + printf("call %02x :", i - 2); + n0 = bcj2_stats[i][0]; + n1 = bcj2_stats[i][1]; + } + + const UInt32 sum = n0 + n1; + printf(" %10u", sum); + + #define PRINT_PERC(val, sum) \ + { UInt32 _sum = sum; if (_sum == 0) _sum = 1; \ + printf(" %7.3f %%", (double)((double)val * (double)100 / (double)_sum )); } + + if (i >= 2 || i < 0) + { + PRINT_PERC(sum, sums2); + } + else + printf("%10s", ""); + + printf(" :%10u", n0); + PRINT_PERC(n0, sum); + + printf(" :%10u", n1); + PRINT_PERC(n1, sum); + } + printf("\n\n"); + fflush(stdout); + } +} g_CBcjStat; +*/ diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/Bcj2Coder.h b/src/plugins/7zip/7za/cpp/7zip/Compress/Bcj2Coder.h index 381b9f54..2ab91bc8 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/Bcj2Coder.h +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/Bcj2Coder.h @@ -1,7 +1,7 @@ // Bcj2Coder.h -#ifndef __COMPRESS_BCJ2_CODER_H -#define __COMPRESS_BCJ2_CODER_H +#ifndef ZIP7_INC_COMPRESS_BCJ2_CODER_H +#define ZIP7_INC_COMPRESS_BCJ2_CODER_H #include "../../../C/Bcj2.h" @@ -16,8 +16,8 @@ class CBaseCoder { protected: Byte *_bufs[BCJ2_NUM_STREAMS + 1]; - UInt32 _bufsCurSizes[BCJ2_NUM_STREAMS + 1]; - UInt32 _bufsNewSizes[BCJ2_NUM_STREAMS + 1]; + UInt32 _bufsSizes[BCJ2_NUM_STREAMS + 1]; + UInt32 _bufsSizes_New[BCJ2_NUM_STREAMS + 1]; HRESULT Alloc(bool allocForOrig = true); public: @@ -26,89 +26,99 @@ class CBaseCoder }; -#ifndef EXTRACT_ONLY +#ifndef Z7_EXTRACT_ONLY -class CEncoder: +class CEncoder Z7_final: public ICompressCoder2, public ICompressSetCoderProperties, public ICompressSetBufSize, public CMyUnknownImp, public CBaseCoder { + Z7_IFACES_IMP_UNK_3( + ICompressCoder2, + ICompressSetCoderProperties, + ICompressSetBufSize) + UInt32 _relatLim; + // UInt32 _excludeRangeBits; - HRESULT CodeReal(ISequentialInStream * const *inStreams, const UInt64 * const *inSizes, UInt32 numInStreams, + HRESULT CodeReal( + ISequentialInStream * const *inStreams, const UInt64 * const *inSizes, UInt32 numInStreams, ISequentialOutStream * const *outStreams, const UInt64 * const *outSizes, UInt32 numOutStreams, ICompressProgressInfo *progress); - public: - MY_UNKNOWN_IMP3(ICompressCoder2, ICompressSetCoderProperties, ICompressSetBufSize) - - STDMETHOD(Code)(ISequentialInStream * const *inStreams, const UInt64 * const *inSizes, UInt32 numInStreams, - ISequentialOutStream * const *outStreams, const UInt64 * const *outSizes, UInt32 numOutStreams, - ICompressProgressInfo *progress); - - STDMETHOD(SetCoderProperties)(const PROPID *propIDs, const PROPVARIANT *props, UInt32 numProps); - - STDMETHOD(SetInBufSize)(UInt32 streamIndex, UInt32 size); - STDMETHOD(SetOutBufSize)(UInt32 streamIndex, UInt32 size); - CEncoder(); ~CEncoder(); }; #endif -class CDecoder: + + +class CBaseDecoder: public CBaseCoder +{ +protected: + HRESULT _readRes[BCJ2_NUM_STREAMS]; + unsigned _extraSizes[BCJ2_NUM_STREAMS]; + UInt64 _readSizes[BCJ2_NUM_STREAMS]; + + CBcj2Dec dec; + + UInt64 GetProcessedSize_ForInStream(unsigned i) const + { + return _readSizes[i] - ((size_t)(dec.lims[i] - dec.bufs[i]) + _extraSizes[i]); + } + void InitCommon(); + void ReadInStream(ISequentialInStream *inStream); +}; + + +class CDecoder Z7_final: public ICompressCoder2, public ICompressSetFinishMode, + public ICompressGetInStreamProcessedSize2, + public ICompressSetBufSize, +#ifndef Z7_NO_READ_FROM_CODER public ICompressSetInStream2, - public ISequentialInStream, public ICompressSetOutStreamSize, - public ICompressSetBufSize, + public ISequentialInStream, +#endif public CMyUnknownImp, - public CBaseCoder + public CBaseDecoder { - unsigned _extraReadSizes[BCJ2_NUM_STREAMS]; - UInt64 _inStreamsProcessed[BCJ2_NUM_STREAMS]; - HRESULT _readRes[BCJ2_NUM_STREAMS]; - CMyComPtr _inStreams[BCJ2_NUM_STREAMS]; + Z7_COM_QI_BEGIN2(ICompressCoder2) + Z7_COM_QI_ENTRY(ICompressSetFinishMode) + Z7_COM_QI_ENTRY(ICompressGetInStreamProcessedSize2) + Z7_COM_QI_ENTRY(ICompressSetBufSize) + #ifndef Z7_NO_READ_FROM_CODER + Z7_COM_QI_ENTRY(ICompressSetInStream2) + Z7_COM_QI_ENTRY(ICompressSetOutStreamSize) + Z7_COM_QI_ENTRY(ISequentialInStream) + #endif + Z7_COM_QI_END + Z7_COM_ADDREF_RELEASE + + Z7_IFACE_COM7_IMP(ICompressCoder2) + Z7_IFACE_COM7_IMP(ICompressSetFinishMode) + Z7_IFACE_COM7_IMP(ICompressGetInStreamProcessedSize2) + Z7_IFACE_COM7_IMP(ICompressSetBufSize) +#ifndef Z7_NO_READ_FROM_CODER + Z7_IFACE_COM7_IMP(ICompressSetInStream2) + Z7_IFACE_COM7_IMP(ICompressSetOutStreamSize) + Z7_IFACE_COM7_IMP(ISequentialInStream) +#endif bool _finishMode; + +#ifndef Z7_NO_READ_FROM_CODER bool _outSizeDefined; UInt64 _outSize; UInt64 _outSize_Processed; - CBcj2Dec dec; - - void InitCommon(); - // HRESULT ReadSpec(); - + CMyComPtr _inStreams[BCJ2_NUM_STREAMS]; +#endif + public: - MY_UNKNOWN_IMP6( - ICompressCoder2, - ICompressSetFinishMode, - ICompressSetInStream2, - ISequentialInStream, - ICompressSetOutStreamSize, - ICompressSetBufSize - ); - - STDMETHOD(Code)(ISequentialInStream * const *inStreams, const UInt64 * const *inSizes, UInt32 numInStreams, - ISequentialOutStream * const *outStreams, const UInt64 * const *outSizes, UInt32 numOutStreams, - ICompressProgressInfo *progress); - - STDMETHOD(SetFinishMode)(UInt32 finishMode); - - STDMETHOD(SetInStream2)(UInt32 streamIndex, ISequentialInStream *inStream); - STDMETHOD(ReleaseInStream2)(UInt32 streamIndex); - - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); - - STDMETHOD(SetOutStreamSize)(const UInt64 *outSize); - - STDMETHOD(SetInBufSize)(UInt32 streamIndex, UInt32 size); - STDMETHOD(SetOutBufSize)(UInt32 streamIndex, UInt32 size); - CDecoder(); }; diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/Bcj2Register.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/Bcj2Register.cpp index 7a48f91c..1ce4decb 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/Bcj2Register.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/Bcj2Register.cpp @@ -10,13 +10,13 @@ namespace NCompress { namespace NBcj2 { REGISTER_CODEC_CREATE_2(CreateCodec, CDecoder(), ICompressCoder2) -#ifndef EXTRACT_ONLY +#ifndef Z7_EXTRACT_ONLY REGISTER_CODEC_CREATE_2(CreateCodecOut, CEncoder(), ICompressCoder2) #else #define CreateCodecOut NULL #endif -REGISTER_CODEC_VAR +REGISTER_CODEC_VAR(BCJ2) { CreateCodec, CreateCodecOut, 0x303011B, "BCJ2", 4, false }; REGISTER_CODEC(BCJ2) diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/BcjCoder.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/BcjCoder.cpp index 32aa1762..00478747 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/BcjCoder.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/BcjCoder.cpp @@ -7,17 +7,17 @@ namespace NCompress { namespace NBcj { -STDMETHODIMP CCoder::Init() +Z7_COM7F_IMF(CCoder2::Init()) { - _bufferPos = 0; - x86_Convert_Init(_prevMask); + _pc = 0; + _state = Z7_BRANCH_CONV_ST_X86_STATE_INIT_VAL; return S_OK; } -STDMETHODIMP_(UInt32) CCoder::Filter(Byte *data, UInt32 size) +Z7_COM7F_IMF2(UInt32, CCoder2::Filter(Byte *data, UInt32 size)) { - UInt32 processed = (UInt32)::x86_Convert(data, size, _bufferPos, &_prevMask, _encode); - _bufferPos += processed; + const UInt32 processed = (UInt32)(size_t)(_convFunc(data, size, _pc, &_state) - data); + _pc += processed; return processed; } diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/BcjCoder.h b/src/plugins/7zip/7za/cpp/7zip/Compress/BcjCoder.h index 7883906e..734a2786 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/BcjCoder.h +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/BcjCoder.h @@ -1,7 +1,7 @@ // BcjCoder.h -#ifndef __COMPRESS_BCJ_CODER_H -#define __COMPRESS_BCJ_CODER_H +#ifndef ZIP7_INC_COMPRESS_BCJ_CODER_H +#define ZIP7_INC_COMPRESS_BCJ_CODER_H #include "../../../C/Bra.h" @@ -12,18 +12,24 @@ namespace NCompress { namespace NBcj { -class CCoder: - public ICompressFilter, - public CMyUnknownImp -{ - UInt32 _bufferPos; - UInt32 _prevMask; - int _encode; +/* CCoder in old versions used another constructor parameter CCoder(int encode). + And some code called it as CCoder(0). + We have changed constructor parameter type. + So we have changed the name of class also to CCoder2. */ + +Z7_CLASS_IMP_COM_1( + CCoder2 + , ICompressFilter +) + UInt32 _pc; + UInt32 _state; + z7_Func_BranchConvSt _convFunc; public: - MY_UNKNOWN_IMP1(ICompressFilter); - INTERFACE_ICompressFilter(;) - - CCoder(int encode): _bufferPos(0), _encode(encode) { x86_Convert_Init(_prevMask); } + CCoder2(z7_Func_BranchConvSt convFunc): + _pc(0), + _state(Z7_BRANCH_CONV_ST_X86_STATE_INIT_VAL), + _convFunc(convFunc) + {} }; }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/BcjRegister.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/BcjRegister.cpp index f06dcfe9..0348f641 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/BcjRegister.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/BcjRegister.cpp @@ -10,8 +10,8 @@ namespace NCompress { namespace NBcj { REGISTER_FILTER_E(BCJ, - CCoder(false), - CCoder(true), + CCoder2(z7_BranchConvSt_X86_Dec), + CCoder2(z7_BranchConvSt_X86_Enc), 0x3030103, "BCJ") }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/BitlDecoder.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/BitlDecoder.cpp index 516b0932..bf1e5ba3 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/BitlDecoder.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/BitlDecoder.cpp @@ -6,19 +6,27 @@ namespace NBitl { -Byte kInvertTable[256]; +#if defined(Z7_BITL_USE_REVERSE_BITS_TABLE) -struct CInverterTableInitializer +MY_ALIGN(64) +Byte kReverseTable[256]; + +static +struct CReverseerTableInitializer { - CInverterTableInitializer() + CReverseerTableInitializer() { for (unsigned i = 0; i < 256; i++) { - unsigned x = ((i & 0x55) << 1) | ((i & 0xAA) >> 1); - x = ((x & 0x33) << 2) | ((x & 0xCC) >> 2); - kInvertTable[i] = (Byte)(((x & 0x0F) << 4) | ((x & 0xF0) >> 4)); + unsigned + x = ((i & 0x55) << 1) | ((i >> 1) & 0x55); + x = ((x & 0x33) << 2) | ((x >> 2) & 0x33); + kReverseTable[i] = (Byte)((x << 4) | (x >> 4)); } } -} g_InverterTableInitializer; +} g_ReverseerTableInitializer; +#elif 0 +unsigned ReverseBits8test(unsigned i) { return ReverseBits8(i); } +#endif } diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/BitlDecoder.h b/src/plugins/7zip/7za/cpp/7zip/Compress/BitlDecoder.h index 41e22fe7..465c4d3d 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/BitlDecoder.h +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/BitlDecoder.h @@ -1,7 +1,9 @@ // BitlDecoder.h -- the Least Significant Bit of byte is First -#ifndef __BITL_DECODER_H -#define __BITL_DECODER_H +#ifndef ZIP7_INC_BITL_DECODER_H +#define ZIP7_INC_BITL_DECODER_H + +#include "../../../C/CpuArch.h" #include "../IStream.h" @@ -10,10 +12,50 @@ namespace NBitl { const unsigned kNumBigValueBits = 8 * 4; const unsigned kNumValueBytes = 3; const unsigned kNumValueBits = 8 * kNumValueBytes; - const UInt32 kMask = (1 << kNumValueBits) - 1; -extern Byte kInvertTable[256]; +#if !defined(Z7_BITL_USE_REVERSE_BITS_TABLE) +#if 1 && defined(MY_CPU_ARM_OR_ARM64) \ + && (defined(MY_CPU_ARM64) || defined(__ARM_ARCH_6T2__) \ + || defined(__ARM_ARCH) && (__ARM_ARCH >= 7)) \ + && (defined(__GNUC__) && (__GNUC__ >= 4) \ + || defined(__clang__) && (__clang_major__ >= 4)) + #define Z7_BITL_USE_REVERSE_BITS_INSTRUCTION +#elif 1 + #define Z7_BITL_USE_REVERSE_BITS_TABLE +#endif +#endif + +#if defined(Z7_BITL_USE_REVERSE_BITS_TABLE) +extern Byte kReverseTable[256]; +#endif + +inline unsigned ReverseBits8(unsigned i) +{ +#if defined(Z7_BITL_USE_REVERSE_BITS_TABLE) + return kReverseTable[i]; +#elif defined(Z7_BITL_USE_REVERSE_BITS_INSTRUCTION) + // rbit is available in ARMv6T2 and above + asm ("rbit " +#if defined(MY_CPU_ARM) + "%0,%0" // it uses default register size, + // but we need 32-bit register here. + // we must use it only if default register size is 32-bit. + // it will work incorrectly for ARM64. +#else + "%w0,%w0" // it uses 32-bit registers in ARM64. + // compiler for (MY_CPU_ARM) can't compile it. +#endif + : "+r" (i)); + return i >> 24; +#else + unsigned + x = ((i & 0x55) << 1) | ((i >> 1) & 0x55); + x = ((x & 0x33) << 2) | ((x >> 2) & 0x33); + return ((x & 0x0f) << 4) | (x >> 4); +#endif +} + /* TInByte must support "Extra Bytes" (bytes that can be read after the end of stream TInByte::ReadByte() returns 0xFF after the end of stream @@ -31,6 +73,7 @@ class CBaseDecoder public: bool Create(UInt32 bufSize) { return _stream.Create(bufSize); } void SetStream(ISequentialInStream *inStream) { _stream.SetStream(inStream); } + void ClearStreamPtr() { _stream.ClearStreamPtr(); } void Init() { _stream.Init(); @@ -38,17 +81,30 @@ class CBaseDecoder _value = 0; } - UInt64 GetStreamSize() const { return _stream.GetStreamSize(); } + // the size of portion data in real stream that was already read from this object. + // it doesn't include unused data in BitStream object buffer (up to 4 bytes) + // it doesn't include unused data in TInByte buffers + // it doesn't include virtual Extra bytes after the end of real stream data + UInt64 GetStreamSize() const + { + return ExtraBitsWereRead() ? + _stream.GetStreamSize(): + GetProcessedSize(); + } + + // the size of virtual data that was read from this object. UInt64 GetProcessedSize() const { return _stream.GetProcessedSize() - ((kNumBigValueBits - _bitPos) >> 3); } bool ThereAreDataInBitsBuffer() const { return this->_bitPos != kNumBigValueBits; } - + + Z7_FORCE_INLINE void Normalize() { for (; _bitPos >= 8; _bitPos -= 8) _value = ((UInt32)_stream.ReadByte() << (kNumBigValueBits - _bitPos)) | _value; } + Z7_FORCE_INLINE UInt32 ReadBits(unsigned numBits) { Normalize(); @@ -88,29 +144,40 @@ class CDecoder: public CBaseDecoder CBaseDecoder::Init(); _normalValue = 0; } - + + Z7_FORCE_INLINE void Normalize() { for (; this->_bitPos >= 8; this->_bitPos -= 8) { - Byte b = this->_stream.ReadByte(); + const unsigned b = this->_stream.ReadByte(); _normalValue = ((UInt32)b << (kNumBigValueBits - this->_bitPos)) | _normalValue; - this->_value = (this->_value << 8) | kInvertTable[b]; + this->_value = (this->_value << 8) | ReverseBits8(b); } } + Z7_FORCE_INLINE UInt32 GetValue(unsigned numBits) { Normalize(); return ((this->_value >> (8 - this->_bitPos)) & kMask) >> (kNumValueBits - numBits); } - void MovePos(unsigned numBits) + Z7_FORCE_INLINE + UInt32 GetValue_InHigh32bits() + { + Normalize(); + return this->_value << this->_bitPos; + } + + Z7_FORCE_INLINE + void MovePos(size_t numBits) { - this->_bitPos += numBits; + this->_bitPos += (unsigned)numBits; _normalValue >>= numBits; } + Z7_FORCE_INLINE UInt32 ReadBits(unsigned numBits) { Normalize(); @@ -120,9 +187,14 @@ class CDecoder: public CBaseDecoder } void AlignToByte() { MovePos((32 - this->_bitPos) & 7); } - + + Z7_FORCE_INLINE Byte ReadDirectByte() { return this->_stream.ReadByte(); } + Z7_FORCE_INLINE + size_t ReadDirectBytesPart(Byte *buf, size_t size) { return this->_stream.ReadBytesPart(buf, size); } + + Z7_FORCE_INLINE Byte ReadAlignedByte() { if (this->_bitPos == kNumBigValueBits) @@ -131,6 +203,21 @@ class CDecoder: public CBaseDecoder MovePos(8); return b; } + + // call it only if the object is aligned for byte. + Z7_FORCE_INLINE + bool ReadAlignedByte_FromBuf(Byte &b) + { + if (this->_stream.NumExtraBytes != 0) + if (this->_stream.NumExtraBytes >= 4 + || kNumBigValueBits - this->_bitPos <= (this->_stream.NumExtraBytes << 3)) + return false; + if (this->_bitPos == kNumBigValueBits) + return this->_stream.ReadByte_FromBuf(b); + b = (Byte)(_normalValue & 0xFF); + MovePos(8); + return true; + } }; } diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/BitlEncoder.h b/src/plugins/7zip/7za/cpp/7zip/Compress/BitlEncoder.h index 22b83545..364f84df 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/BitlEncoder.h +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/BitlEncoder.h @@ -1,7 +1,7 @@ // BitlEncoder.h -- the Least Significant Bit of byte is First -#ifndef __BITL_ENCODER_H -#define __BITL_ENCODER_H +#ifndef ZIP7_INC_BITL_ENCODER_H +#define ZIP7_INC_BITL_ENCODER_H #include "../Common/OutBuffer.h" @@ -33,13 +33,14 @@ class CBitlEncoder _bitPos = 8; _curByte = 0; } + Z7_FORCE_INLINE void WriteBits(UInt32 value, unsigned numBits) { while (numBits > 0) { if (numBits < _bitPos) { - _curByte |= (value & ((1 << numBits) - 1)) << (8 - _bitPos); + _curByte |= (Byte)((value & ((1 << numBits) - 1)) << (8 - _bitPos)); _bitPos -= numBits; return; } diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/BitmDecoder.h b/src/plugins/7zip/7za/cpp/7zip/Compress/BitmDecoder.h index 9b1c540e..21c83dbd 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/BitmDecoder.h +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/BitmDecoder.h @@ -1,7 +1,7 @@ // BitmDecoder.h -- the Most Significant Bit of byte is First -#ifndef __BITM_DECODER_H -#define __BITM_DECODER_H +#ifndef ZIP7_INC_BITM_DECODER_H +#define ZIP7_INC_BITM_DECODER_H #include "../IStream.h" @@ -46,25 +46,35 @@ class CDecoder { return (_stream.NumExtraBytes > 4); } - + + Z7_FORCE_INLINE void Normalize() { for (; _bitPos >= 8; _bitPos -= 8) _value = (_value << 8) | _stream.ReadByte(); } + Z7_FORCE_INLINE UInt32 GetValue(unsigned numBits) const { // return (_value << _bitPos) >> (kNumBigValueBits - numBits); return ((_value >> (8 - _bitPos)) & kMask) >> (kNumValueBits - numBits); } - + + Z7_FORCE_INLINE + UInt32 GetValue_InHigh32bits() const + { + return this->_value << this->_bitPos; + } + + Z7_FORCE_INLINE void MovePos(unsigned numBits) { _bitPos += numBits; Normalize(); } - + + Z7_FORCE_INLINE UInt32 ReadBits(unsigned numBits) { UInt32 res = GetValue(numBits); @@ -87,6 +97,7 @@ class CDecoder void AlignToByte() { MovePos((kNumBigValueBits - _bitPos) & 7); } + Z7_FORCE_INLINE UInt32 ReadAlignBits() { return ReadBits((kNumBigValueBits - _bitPos) & 7); } }; diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/BitmEncoder.h b/src/plugins/7zip/7za/cpp/7zip/Compress/BitmEncoder.h index 05079ace..f7448cd6 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/BitmEncoder.h +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/BitmEncoder.h @@ -1,15 +1,16 @@ // BitmEncoder.h -- the Most Significant Bit of byte is First -#ifndef __BITM_ENCODER_H -#define __BITM_ENCODER_H +#ifndef ZIP7_INC_BITM_ENCODER_H +#define ZIP7_INC_BITM_ENCODER_H #include "../IStream.h" template class CBitmEncoder { - unsigned _bitPos; - Byte _curByte; + unsigned _bitPos; // 0 < _bitPos <= 8 : number of non-filled low bits in _curByte + unsigned _curByte; // low (_bitPos) bits are zeros + // high (8 - _bitPos) bits are filled TOutByte _stream; public: bool Create(UInt32 bufferSize) { return _stream.Create(bufferSize); } @@ -24,25 +25,65 @@ class CBitmEncoder HRESULT Flush() { if (_bitPos < 8) - WriteBits(0, _bitPos); + { + _stream.WriteByte((Byte)_curByte); + _bitPos = 8; + _curByte = 0; + } return _stream.Flush(); } + + // required condition: (value >> numBits) == 0 + // numBits == 0 is allowed void WriteBits(UInt32 value, unsigned numBits) { - while (numBits > 0) + do { - if (numBits < _bitPos) + unsigned bp = _bitPos; + unsigned curByte = _curByte; + if (numBits < bp) { - _curByte |= ((Byte)value << (_bitPos -= numBits)); + bp -= numBits; + _curByte = curByte | (value << bp); + _bitPos = bp; return; } - numBits -= _bitPos; - UInt32 newBits = (value >> numBits); - value -= (newBits << numBits); - _stream.WriteByte((Byte)(_curByte | newBits)); + numBits -= bp; + const UInt32 hi = (value >> numBits); + value -= (hi << numBits); + _stream.WriteByte((Byte)(curByte | hi)); _bitPos = 8; _curByte = 0; } + while (numBits); + } + void WriteByte(unsigned b) + { + const unsigned bp = _bitPos; + const unsigned a = _curByte | (b >> (8 - bp)); + _curByte = b << bp; + _stream.WriteByte((Byte)a); + } + + void WriteBytes(const Byte *data, size_t num) + { + const unsigned bp = _bitPos; +#if 1 // 1 for optional speed-optimized code branch + if (bp == 8) + { + _stream.WriteBytes(data, num); + return; + } +#endif + unsigned c = _curByte; + const unsigned bp_rev = 8 - bp; + for (size_t i = 0; i < num; i++) + { + const unsigned b = data[i]; + _stream.WriteByte((Byte)(c | (b >> bp_rev))); + c = b << bp; + } + _curByte = c; } }; diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/BranchMisc.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/BranchMisc.cpp index d0e75e83..2098bebb 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/BranchMisc.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/BranchMisc.cpp @@ -2,22 +2,105 @@ #include "StdAfx.h" +#include "../../../C/CpuArch.h" + +#include "../Common/StreamUtils.h" + #include "BranchMisc.h" namespace NCompress { namespace NBranch { -STDMETHODIMP CCoder::Init() +Z7_COM7F_IMF(CCoder::Init()) +{ + _pc = 0; + return S_OK; +} + + +Z7_COM7F_IMF2(UInt32, CCoder::Filter(Byte *data, UInt32 size)) +{ + const UInt32 processed = (UInt32)(size_t)(BraFunc(data, size, _pc) - data); + _pc += processed; + return processed; +} + + +#ifndef Z7_EXTRACT_ONLY + +Z7_COM7F_IMF(CEncoder::Init()) { - _bufferPos = 0; + _pc = _pc_Init; return S_OK; } -STDMETHODIMP_(UInt32) CCoder::Filter(Byte *data, UInt32 size) +Z7_COM7F_IMF2(UInt32, CEncoder::Filter(Byte *data, UInt32 size)) { - UInt32 processed = (UInt32)BraFunc(data, size, _bufferPos, _encode); - _bufferPos += processed; + const UInt32 processed = (UInt32)(size_t)(BraFunc(data, size, _pc) - data); + _pc += processed; return processed; } +Z7_COM7F_IMF(CEncoder::SetCoderProperties(const PROPID *propIDs, const PROPVARIANT *props, UInt32 numProps)) +{ + UInt32 pc = 0; + for (UInt32 i = 0; i < numProps; i++) + { + const PROPID propID = propIDs[i]; + if (propID == NCoderPropID::kDefaultProp || + propID == NCoderPropID::kBranchOffset) + { + const PROPVARIANT &prop = props[i]; + if (prop.vt != VT_UI4) + return E_INVALIDARG; + pc = prop.ulVal; + if (pc & _alignment) + return E_INVALIDARG; + } + } + _pc_Init = pc; + return S_OK; +} + + +Z7_COM7F_IMF(CEncoder::WriteCoderProperties(ISequentialOutStream *outStream)) +{ + if (_pc_Init == 0) + return S_OK; + UInt32 buf32[1]; + SetUi32(buf32, _pc_Init) + return WriteStream(outStream, buf32, 4); +} + +#endif + + +Z7_COM7F_IMF(CDecoder::Init()) +{ + _pc = _pc_Init; + return S_OK; +} + +Z7_COM7F_IMF2(UInt32, CDecoder::Filter(Byte *data, UInt32 size)) +{ + const UInt32 processed = (UInt32)(size_t)(BraFunc(data, size, _pc) - data); + _pc += processed; + return processed; +} + +Z7_COM7F_IMF(CDecoder::SetDecoderProperties2(const Byte *props, UInt32 size)) +{ + UInt32 val = 0; + if (size != 0) + { + if (size != 4) + return E_NOTIMPL; + val = GetUi32(props); + if (val & _alignment) + return E_NOTIMPL; + } + _pc_Init = val; + return S_OK; +} + }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/BranchMisc.h b/src/plugins/7zip/7za/cpp/7zip/Compress/BranchMisc.h index 66fc23d2..2b5cc420 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/BranchMisc.h +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/BranchMisc.h @@ -1,33 +1,57 @@ // BranchMisc.h -#ifndef __COMPRESS_BRANCH_MISC_H -#define __COMPRESS_BRANCH_MISC_H +#ifndef ZIP7_INC_COMPRESS_BRANCH_MISC_H +#define ZIP7_INC_COMPRESS_BRANCH_MISC_H +#include "../../../C/Bra.h" #include "../../Common/MyCom.h" #include "../ICoder.h" -EXTERN_C_BEGIN - -typedef SizeT (*Func_Bra)(Byte *data, SizeT size, UInt32 ip, int encoding); - -EXTERN_C_END - namespace NCompress { namespace NBranch { -class CCoder: - public ICompressFilter, - public CMyUnknownImp -{ - UInt32 _bufferPos; - int _encode; - Func_Bra BraFunc; +Z7_CLASS_IMP_COM_1( + CCoder + , ICompressFilter +) + UInt32 _pc; + z7_Func_BranchConv BraFunc; +public: + CCoder(z7_Func_BranchConv bra): _pc(0), BraFunc(bra) {} +}; + +#ifndef Z7_EXTRACT_ONLY + +Z7_CLASS_IMP_COM_3( + CEncoder + , ICompressFilter + , ICompressSetCoderProperties + , ICompressWriteCoderProperties +) + UInt32 _pc; + UInt32 _pc_Init; + UInt32 _alignment; + z7_Func_BranchConv BraFunc; public: - MY_UNKNOWN_IMP1(ICompressFilter); - INTERFACE_ICompressFilter(;) + CEncoder(z7_Func_BranchConv bra, UInt32 alignment): + _pc(0), _pc_Init(0), _alignment(alignment), BraFunc(bra) {} +}; - CCoder(Func_Bra bra, int encode): _bufferPos(0), _encode(encode), BraFunc(bra) {} +#endif + +Z7_CLASS_IMP_COM_2( + CDecoder + , ICompressFilter + , ICompressSetDecoderProperties2 +) + UInt32 _pc; + UInt32 _pc_Init; + UInt32 _alignment; + z7_Func_BranchConv BraFunc; +public: + CDecoder(z7_Func_BranchConv bra, UInt32 alignment): + _pc(0), _pc_Init(0), _alignment(alignment), BraFunc(bra) {} }; }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/BranchRegister.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/BranchRegister.cpp index 6331c1b9..20385ffd 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/BranchRegister.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/BranchRegister.cpp @@ -2,8 +2,6 @@ #include "StdAfx.h" -#include "../../../C/Bra.h" - #include "../Common/RegisterCodec.h" #include "BranchMisc.h" @@ -11,31 +9,50 @@ namespace NCompress { namespace NBranch { +#ifdef Z7_EXTRACT_ONLY +#define GET_CREATE_FUNC(x) NULL +#define CREATE_BRA_E(n) +#else +#define GET_CREATE_FUNC(x) x +#define CREATE_BRA_E(n) \ + REGISTER_FILTER_CREATE(CreateBra_Encoder_ ## n, CCoder(Z7_BRANCH_CONV_ENC_2(n))) +#endif + #define CREATE_BRA(n) \ - REGISTER_FILTER_CREATE(CreateBra_Decoder_ ## n, CCoder(n ## _Convert, false)) \ - REGISTER_FILTER_CREATE(CreateBra_Encoder_ ## n, CCoder(n ## _Convert, true)) \ + REGISTER_FILTER_CREATE(CreateBra_Decoder_ ## n, CCoder(Z7_BRANCH_CONV_DEC_2(n))) \ + CREATE_BRA_E(n) -CREATE_BRA(PPC) -CREATE_BRA(IA64) -CREATE_BRA(ARM) -CREATE_BRA(ARMT) -CREATE_BRA(SPARC) +CREATE_BRA(BranchConv_PPC) +CREATE_BRA(BranchConv_IA64) +CREATE_BRA(BranchConv_ARM) +CREATE_BRA(BranchConv_ARMT) +CREATE_BRA(BranchConv_SPARC) #define METHOD_ITEM(n, id, name) \ REGISTER_FILTER_ITEM( \ - CreateBra_Decoder_ ## n, \ - CreateBra_Encoder_ ## n, \ + CreateBra_Decoder_ ## n, GET_CREATE_FUNC( \ + CreateBra_Encoder_ ## n), \ 0x3030000 + id, name) REGISTER_CODECS_VAR { - METHOD_ITEM(PPC, 0x205, "PPC"), - METHOD_ITEM(IA64, 0x401, "IA64"), - METHOD_ITEM(ARM, 0x501, "ARM"), - METHOD_ITEM(ARMT, 0x701, "ARMT"), - METHOD_ITEM(SPARC, 0x805, "SPARC") + METHOD_ITEM(BranchConv_PPC, 0x205, "PPC"), + METHOD_ITEM(BranchConv_IA64, 0x401, "IA64"), + METHOD_ITEM(BranchConv_ARM, 0x501, "ARM"), + METHOD_ITEM(BranchConv_ARMT, 0x701, "ARMT"), + METHOD_ITEM(BranchConv_SPARC, 0x805, "SPARC") }; REGISTER_CODECS(Branch) + +#define REGISTER_FILTER_E_BRANCH(id, n, name, alignment) \ + REGISTER_FILTER_E(n, \ + CDecoder(Z7_BRANCH_CONV_DEC(n), alignment), \ + CEncoder(Z7_BRANCH_CONV_ENC(n), alignment), \ + id, name) + +REGISTER_FILTER_E_BRANCH(0xa, ARM64, "ARM64", 3) +REGISTER_FILTER_E_BRANCH(0xb, RISCV, "RISCV", 1) + }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/ByteSwap.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/ByteSwap.cpp index 4c11806e..cc28d8b9 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/ByteSwap.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/ByteSwap.cpp @@ -2,6 +2,8 @@ #include "StdAfx.h" +#include "../../../C/SwapBytes.h" + #include "../../Common/MyCom.h" #include "../ICoder.h" @@ -11,80 +13,77 @@ namespace NCompress { namespace NByteSwap { -class CByteSwap2: - public ICompressFilter, - public CMyUnknownImp -{ -public: - MY_UNKNOWN_IMP1(ICompressFilter); - INTERFACE_ICompressFilter(;) -}; - -class CByteSwap4: - public ICompressFilter, - public CMyUnknownImp -{ -public: - MY_UNKNOWN_IMP1(ICompressFilter); - INTERFACE_ICompressFilter(;) -}; +Z7_CLASS_IMP_COM_1(CByteSwap2, ICompressFilter) }; +Z7_CLASS_IMP_COM_1(CByteSwap4, ICompressFilter) }; -STDMETHODIMP CByteSwap2::Init() { return S_OK; } +Z7_COM7F_IMF(CByteSwap2::Init()) { return S_OK; } -STDMETHODIMP_(UInt32) CByteSwap2::Filter(Byte *data, UInt32 size) +Z7_COM7F_IMF2(UInt32, CByteSwap2::Filter(Byte *data, UInt32 size)) { - const UInt32 kStep = 2; - if (size < kStep) - return 0; - size &= ~(kStep - 1); - - const Byte *end = data + (size_t)size; - - do + const UInt32 kMask = 2 - 1; + size &= ~kMask; + /* + if ((unsigned)(ptrdiff_t)data & kMask) { - Byte b0 = data[0]; - data[0] = data[1]; - data[1] = b0; - data += kStep; + if (size == 0) + return 0; + const Byte *end = data + (size_t)size; + do + { + const Byte b0 = data[0]; + data[0] = data[1]; + data[1] = b0; + data += kStep; + } + while (data != end); } - while (data != end); - + else + */ + z7_SwapBytes2((UInt16 *)(void *)data, size >> 1); return size; } -STDMETHODIMP CByteSwap4::Init() { return S_OK; } -STDMETHODIMP_(UInt32) CByteSwap4::Filter(Byte *data, UInt32 size) +Z7_COM7F_IMF(CByteSwap4::Init()) { return S_OK; } + +Z7_COM7F_IMF2(UInt32, CByteSwap4::Filter(Byte *data, UInt32 size)) { - const UInt32 kStep = 4; - if (size < kStep) - return 0; - size &= ~(kStep - 1); - - const Byte *end = data + (size_t)size; - - do + const UInt32 kMask = 4 - 1; + size &= ~kMask; + /* + if ((unsigned)(ptrdiff_t)data & kMask) { - Byte b0 = data[0]; - Byte b1 = data[1]; - data[0] = data[3]; - data[1] = data[2]; - data[2] = b1; - data[3] = b0; - data += kStep; + if (size == 0) + return 0; + const Byte *end = data + (size_t)size; + do + { + const Byte b0 = data[0]; + const Byte b1 = data[1]; + data[0] = data[3]; + data[1] = data[2]; + data[2] = b1; + data[3] = b0; + data += kStep; + } + while (data != end); } - while (data != end); - + else + */ + z7_SwapBytes4((UInt32 *)(void *)data, size >> 2); return size; } +static struct C_SwapBytesPrepare { C_SwapBytesPrepare() { z7_SwapBytesPrepare(); } } g_SwapBytesPrepare; + + REGISTER_FILTER_CREATE(CreateFilter2, CByteSwap2()) REGISTER_FILTER_CREATE(CreateFilter4, CByteSwap4()) REGISTER_CODECS_VAR { REGISTER_FILTER_ITEM(CreateFilter2, CreateFilter2, 0x20302, "Swap2"), - REGISTER_FILTER_ITEM(CreateFilter4, CreateFilter4, 0x20304, "Swap4") + REGISTER_FILTER_ITEM(CreateFilter4, CreateFilter4, 0x20304, "Swap4"), }; REGISTER_CODECS(ByteSwap) diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/Codec.def b/src/plugins/7zip/7za/cpp/7zip/Compress/Codec.def index f55b2d57..e2bc0dfd 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/Codec.def +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/Codec.def @@ -4,3 +4,4 @@ EXPORTS GetMethodProperty PRIVATE CreateDecoder PRIVATE CreateEncoder PRIVATE + GetModuleProp PRIVATE diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/CodecExports.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/CodecExports.cpp index 99085040..70be7602 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/CodecExports.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/CodecExports.cpp @@ -3,11 +3,13 @@ #include "StdAfx.h" #include "../../../C/CpuArch.h" +#include "../../../C/7zVersion.h" #include "../../Common/ComTry.h" #include "../../Common/MyCom.h" -#include "../../Windows/Defs.h" +#include "../../Windows/WinDefs.h" +#include "../../Windows/PropVariant.h" #include "../ICoder.h" @@ -21,7 +23,7 @@ extern const CHasherInfo *g_Hashers[]; static void SetPropFromAscii(const char *s, PROPVARIANT *prop) throw() { - UINT len = (UINT)strlen(s); + const UINT len = (UINT)strlen(s); BSTR dest = ::SysAllocStringLen(NULL, len); if (dest) { @@ -45,7 +47,7 @@ static HRESULT MethodToClassID(UInt16 typeId, CMethodId id, PROPVARIANT *value) clsId.Data1 = k_7zip_GUID_Data1; clsId.Data2 = k_7zip_GUID_Data2; clsId.Data3 = typeId; - SetUi64(clsId.Data4, id); + SetUi64(clsId.Data4, id) return SetPropGUID(clsId, value); } @@ -61,7 +63,7 @@ static HRESULT FindCodecClassId(const GUID *clsid, bool isCoder2, bool isFilter, if (clsid->Data3 == k_7zip_GUID_Data3_Decoder) encode = false; else if (clsid->Data3 != k_7zip_GUID_Data3_Encoder) return S_OK; - UInt64 id = GetUi64(clsid->Data4); + const UInt64 id = GetUi64(clsid->Data4); for (unsigned i = 0; i < g_NumCodecs; i++) { @@ -75,13 +77,21 @@ static HRESULT FindCodecClassId(const GUID *clsid, bool isCoder2, bool isFilter, if (codec.NumStreams == 1 ? isCoder2 : !isCoder2) return E_NOINTERFACE; - index = i; + index = (int)i; return S_OK; } return S_OK; } +/* +#ifdef __GNUC__ +#ifndef __clang__ +#pragma GCC diagnostic ignored "-Wduplicated-branches" +#endif +#endif +*/ + static HRESULT CreateCoderMain(unsigned index, bool encode, void **coder) { COM_TRY_BEGIN @@ -97,12 +107,15 @@ static HRESULT CreateCoderMain(unsigned index, bool encode, void **coder) if (c) { IUnknown *unk; + unk = (IUnknown *)c; + /* if (codec.IsFilter) unk = (IUnknown *)(ICompressFilter *)c; else if (codec.NumStreams != 1) unk = (IUnknown *)(ICompressCoder2 *)c; else unk = (IUnknown *)(ICompressCoder *)c; + */ unk->AddRef(); *coder = c; } @@ -136,23 +149,29 @@ static HRESULT CreateCoder2(bool encode, UInt32 index, const GUID *iid, void **o return CreateCoderMain(index, encode, outObject); } + +STDAPI CreateDecoder(UInt32 index, const GUID *iid, void **outObject); STDAPI CreateDecoder(UInt32 index, const GUID *iid, void **outObject) { return CreateCoder2(false, index, iid, outObject); } + +STDAPI CreateEncoder(UInt32 index, const GUID *iid, void **outObject); STDAPI CreateEncoder(UInt32 index, const GUID *iid, void **outObject) { return CreateCoder2(true, index, iid, outObject); } + +STDAPI CreateCoder(const GUID *clsid, const GUID *iid, void **outObject); STDAPI CreateCoder(const GUID *clsid, const GUID *iid, void **outObject) { *outObject = NULL; bool isFilter = false; bool isCoder2 = false; - bool isCoder = (*iid == IID_ICompressCoder) != 0; + const bool isCoder = (*iid == IID_ICompressCoder) != 0; if (!isCoder) { isFilter = (*iid == IID_ICompressFilter) != 0; @@ -166,15 +185,17 @@ STDAPI CreateCoder(const GUID *clsid, const GUID *iid, void **outObject) bool encode; int codecIndex; - HRESULT res = FindCodecClassId(clsid, isCoder2, isFilter, encode, codecIndex); + const HRESULT res = FindCodecClassId(clsid, isCoder2, isFilter, encode, codecIndex); if (res != S_OK) return res; if (codecIndex < 0) return CLASS_E_CLASSNOTAVAILABLE; - return CreateCoderMain(codecIndex, encode, outObject); + return CreateCoderMain((unsigned)codecIndex, encode, outObject); } + +STDAPI GetMethodProperty(UInt32 codecIndex, PROPID propID, PROPVARIANT *value); STDAPI GetMethodProperty(UInt32 codecIndex, PROPID propID, PROPVARIANT *value) { ::VariantClear((VARIANTARG *)value); @@ -211,15 +232,12 @@ STDAPI GetMethodProperty(UInt32 codecIndex, PROPID propID, PROPVARIANT *value) value->ulVal = (ULONG)codec.NumStreams; } break; - /* case NMethodPropID::kIsFilter: - // if (codec.IsFilter) { value->vt = VT_BOOL; value->boolVal = BoolToVARIANT_BOOL(codec.IsFilter); } break; - */ /* case NMethodPropID::kDecoderFlags: { @@ -238,7 +256,9 @@ STDAPI GetMethodProperty(UInt32 codecIndex, PROPID propID, PROPVARIANT *value) return S_OK; } -STDAPI GetNumberOfMethods(UINT32 *numCodecs) + +STDAPI GetNumberOfMethods(UInt32 *numCodecs); +STDAPI GetNumberOfMethods(UInt32 *numCodecs) { *numCodecs = g_NumCodecs; return S_OK; @@ -253,10 +273,10 @@ static int FindHasherClassId(const GUID *clsid) throw() clsid->Data2 != k_7zip_GUID_Data2 || clsid->Data3 != k_7zip_GUID_Data3_Hasher) return -1; - UInt64 id = GetUi64(clsid->Data4); + const UInt64 id = GetUi64(clsid->Data4); for (unsigned i = 0; i < g_NumCodecs; i++) if (id == g_Hashers[i]->Id) - return i; + return (int)i; return -1; } @@ -270,17 +290,19 @@ static HRESULT CreateHasher2(UInt32 index, IHasher **hasher) COM_TRY_END } +STDAPI CreateHasher(const GUID *clsid, IHasher **outObject); STDAPI CreateHasher(const GUID *clsid, IHasher **outObject) { COM_TRY_BEGIN - *outObject = 0; - int index = FindHasherClassId(clsid); + *outObject = NULL; + const int index = FindHasherClassId(clsid); if (index < 0) return CLASS_E_CLASSNOTAVAILABLE; - return CreateHasher2(index, outObject); + return CreateHasher2((UInt32)(unsigned)index, outObject); COM_TRY_END } +STDAPI GetHasherProp(UInt32 codecIndex, PROPID propID, PROPVARIANT *value); STDAPI GetHasherProp(UInt32 codecIndex, PROPID propID, PROPVARIANT *value) { ::VariantClear((VARIANTARG *)value); @@ -306,18 +328,9 @@ STDAPI GetHasherProp(UInt32 codecIndex, PROPID propID, PROPVARIANT *value) return S_OK; } -class CHashers: - public IHashers, - public CMyUnknownImp -{ -public: - MY_UNKNOWN_IMP1(IHashers) - - STDMETHOD_(UInt32, GetNumHashers)(); - STDMETHOD(GetHasherProp)(UInt32 index, PROPID propID, PROPVARIANT *value); - STDMETHOD(CreateHasher)(UInt32 index, IHasher **hasher); -}; +Z7_CLASS_IMP_COM_1(CHashers, IHashers) }; +STDAPI GetHashers(IHashers **hashers); STDAPI GetHashers(IHashers **hashers) { COM_TRY_BEGIN @@ -328,17 +341,38 @@ STDAPI GetHashers(IHashers **hashers) COM_TRY_END } -STDMETHODIMP_(UInt32) CHashers::GetNumHashers() +Z7_COM7F_IMF2(UInt32, CHashers::GetNumHashers()) { return g_NumHashers; } -STDMETHODIMP CHashers::GetHasherProp(UInt32 index, PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CHashers::GetHasherProp(UInt32 index, PROPID propID, PROPVARIANT *value)) { return ::GetHasherProp(index, propID, value); } -STDMETHODIMP CHashers::CreateHasher(UInt32 index, IHasher **hasher) +Z7_COM7F_IMF(CHashers::CreateHasher(UInt32 index, IHasher **hasher)) { return ::CreateHasher2(index, hasher); } + + +STDAPI GetModuleProp(PROPID propID, PROPVARIANT *value); +STDAPI GetModuleProp(PROPID propID, PROPVARIANT *value) +{ + ::VariantClear((VARIANTARG *)value); + switch (propID) + { + case NModulePropID::kInterfaceType: + { + NWindows::NCOM::PropVarEm_Set_UInt32(value, NModuleInterfaceType::k_IUnknown_VirtDestructor_ThisModule); + break; + } + case NModulePropID::kVersion: + { + NWindows::NCOM::PropVarEm_Set_UInt32(value, (MY_VER_MAJOR << 16) | MY_VER_MINOR); + break; + } + } + return S_OK; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/CopyCoder.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/CopyCoder.cpp index ac6ab2e7..b779e08b 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/CopyCoder.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/CopyCoder.cpp @@ -15,10 +15,15 @@ CCopyCoder::~CCopyCoder() ::MidFree(_buf); } -STDMETHODIMP CCopyCoder::Code(ISequentialInStream *inStream, +Z7_COM7F_IMF(CCopyCoder::SetFinishMode(UInt32 /* finishMode */)) +{ + return S_OK; +} + +Z7_COM7F_IMF(CCopyCoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, const UInt64 * /* inSize */, const UInt64 *outSize, - ICompressProgressInfo *progress) + ICompressProgressInfo *progress)) { if (!_buf) { @@ -32,12 +37,39 @@ STDMETHODIMP CCopyCoder::Code(ISequentialInStream *inStream, for (;;) { UInt32 size = kBufSize; - if (outSize && size > *outSize - TotalSize) - size = (UInt32)(*outSize - TotalSize); - if (size == 0) - return S_OK; + if (outSize) + { + const UInt64 rem = *outSize - TotalSize; + if (size > rem) + { + size = (UInt32)rem; + if (size == 0) + { + /* if we enable the following check, + we will make one call of Read(_buf, 0) for empty stream */ + // if (TotalSize != 0) + return S_OK; + } + } + } - HRESULT readRes = inStream->Read(_buf, size, &size); + HRESULT readRes; + { + UInt32 pos = 0; + do + { + const UInt32 curSize = size - pos; + UInt32 processed = 0; + readRes = inStream->Read(_buf + pos, curSize, &processed); + if (processed > curSize) + return E_FAIL; // internal code failure + pos += processed; + if (readRes != S_OK || processed == 0) + break; + } + while (pos < kBufSize); + size = pos; + } if (size == 0) return readRes; @@ -47,12 +79,15 @@ STDMETHODIMP CCopyCoder::Code(ISequentialInStream *inStream, UInt32 pos = 0; do { - UInt32 curSize = size - pos; - HRESULT res = outStream->Write(_buf + pos, curSize, &curSize); - pos += curSize; - TotalSize += curSize; - RINOK(res); - if (curSize == 0) + const UInt32 curSize = size - pos; + UInt32 processed = 0; + const HRESULT res = outStream->Write(_buf + pos, curSize, &processed); + if (processed > curSize) + return E_FAIL; // internal code failure + pos += processed; + TotalSize += processed; + RINOK(res) + if (processed == 0) return E_FAIL; } while (pos < size); @@ -60,29 +95,32 @@ STDMETHODIMP CCopyCoder::Code(ISequentialInStream *inStream, else TotalSize += size; - RINOK(readRes); + RINOK(readRes) + + if (size != kBufSize) + return S_OK; - if (progress) + if (progress && (TotalSize & (((UInt32)1 << 22) - 1)) == 0) { - RINOK(progress->SetRatioInfo(&TotalSize, &TotalSize)); + RINOK(progress->SetRatioInfo(&TotalSize, &TotalSize)) } } } -STDMETHODIMP CCopyCoder::SetInStream(ISequentialInStream *inStream) +Z7_COM7F_IMF(CCopyCoder::SetInStream(ISequentialInStream *inStream)) { _inStream = inStream; TotalSize = 0; return S_OK; } -STDMETHODIMP CCopyCoder::ReleaseInStream() +Z7_COM7F_IMF(CCopyCoder::ReleaseInStream()) { _inStream.Release(); return S_OK; } -STDMETHODIMP CCopyCoder::Read(void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CCopyCoder::Read(void *data, UInt32 size, UInt32 *processedSize)) { UInt32 realProcessedSize = 0; HRESULT res = _inStream->Read(data, size, &realProcessedSize); @@ -92,7 +130,7 @@ STDMETHODIMP CCopyCoder::Read(void *data, UInt32 size, UInt32 *processedSize) return res; } -STDMETHODIMP CCopyCoder::GetInStreamProcessedSize(UInt64 *value) +Z7_COM7F_IMF(CCopyCoder::GetInStreamProcessedSize(UInt64 *value)) { *value = TotalSize; return S_OK; @@ -108,7 +146,7 @@ HRESULT CopyStream_ExactSize(ISequentialInStream *inStream, ISequentialOutStream { NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder; CMyComPtr copyCoder = copyCoderSpec; - RINOK(copyCoder->Code(inStream, outStream, NULL, &size, progress)); + RINOK(copyCoder->Code(inStream, outStream, NULL, &size, progress)) return copyCoderSpec->TotalSize == size ? S_OK : E_FAIL; } diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/CopyCoder.h b/src/plugins/7zip/7za/cpp/7zip/Compress/CopyCoder.h index a21c0988..cb93e4ee 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/CopyCoder.h +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/CopyCoder.h @@ -1,7 +1,7 @@ // Compress/CopyCoder.h -#ifndef __COMPRESS_COPY_CODER_H -#define __COMPRESS_COPY_CODER_H +#ifndef ZIP7_INC_COMPRESS_COPY_CODER_H +#define ZIP7_INC_COMPRESS_COPY_CODER_H #include "../../Common/MyCom.h" @@ -9,33 +9,21 @@ namespace NCompress { -class CCopyCoder: - public ICompressCoder, - public ICompressSetInStream, - public ISequentialInStream, - public ICompressGetInStreamProcessedSize, - public CMyUnknownImp -{ +Z7_CLASS_IMP_COM_5( + CCopyCoder + , ICompressCoder + , ICompressSetInStream + , ISequentialInStream + , ICompressSetFinishMode + , ICompressGetInStreamProcessedSize +) Byte *_buf; CMyComPtr _inStream; public: UInt64 TotalSize; - CCopyCoder(): _buf(0), TotalSize(0) {}; + CCopyCoder(): _buf(NULL), TotalSize(0) {} ~CCopyCoder(); - - MY_UNKNOWN_IMP4( - ICompressCoder, - ICompressSetInStream, - ISequentialInStream, - ICompressGetInStreamProcessedSize) - - STDMETHOD(Code)(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress); - STDMETHOD(SetInStream)(ISequentialInStream *inStream); - STDMETHOD(ReleaseInStream)(); - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); - STDMETHOD(GetInStreamProcessedSize)(UInt64 *value); }; HRESULT CopyStream(ISequentialInStream *inStream, ISequentialOutStream *outStream, ICompressProgressInfo *progress); diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/Deflate64Register.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/Deflate64Register.cpp index 14d20bb3..7f6bae0e 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/Deflate64Register.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/Deflate64Register.cpp @@ -5,8 +5,7 @@ #include "../Common/RegisterCodec.h" #include "DeflateDecoder.h" - -#if !defined(EXTRACT_ONLY) && !defined(DEFLATE_EXTRACT_ONLY) +#if !defined(Z7_EXTRACT_ONLY) && !defined(Z7_DEFLATE_EXTRACT_ONLY) #include "DeflateEncoder.h" #endif @@ -15,7 +14,7 @@ namespace NDeflate { REGISTER_CODEC_CREATE(CreateDec, NDecoder::CCOMCoder64()) -#if !defined(EXTRACT_ONLY) && !defined(DEFLATE_EXTRACT_ONLY) +#if !defined(Z7_EXTRACT_ONLY) && !defined(Z7_DEFLATE_EXTRACT_ONLY) REGISTER_CODEC_CREATE(CreateEnc, NEncoder::CCOMCoder64()) #else #define CreateEnc NULL diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/DeflateConst.h b/src/plugins/7zip/7za/cpp/7zip/Compress/DeflateConst.h index cfbbf886..a73d8ff0 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/DeflateConst.h +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/DeflateConst.h @@ -1,7 +1,7 @@ // DeflateConst.h -#ifndef __DEFLATE_CONST_H -#define __DEFLATE_CONST_H +#ifndef ZIP7_INC_DEFLATE_CONST_H +#define ZIP7_INC_DEFLATE_CONST_H namespace NCompress { namespace NDeflate { diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/DeflateDecoder.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/DeflateDecoder.cpp index 60c9aea6..79931764 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/DeflateDecoder.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/DeflateDecoder.cpp @@ -9,12 +9,14 @@ namespace NDeflate { namespace NDecoder { CCoder::CCoder(bool deflate64Mode): - _deflate64Mode(deflate64Mode), _deflateNSIS(false), + _deflate64Mode(deflate64Mode), _keepHistory(false), _needFinishInput(false), _needInitInStream(true), - ZlibMode(false) {} + _outSizeDefined(false), + _outStartPos(0) + {} UInt32 CCoder::ReadBits(unsigned numBits) { @@ -32,7 +34,7 @@ bool CCoder::DecodeLevels(Byte *levels, unsigned numSymbols) do { - UInt32 sym = m_LevelDecoder.Decode(&m_InBitStream); + unsigned sym = m_LevelDecoder.Decode(&m_InBitStream); if (sym < kTableDirectLevels) levels[i++] = (Byte)sym; else @@ -50,7 +52,7 @@ bool CCoder::DecodeLevels(Byte *levels, unsigned numSymbols) return false; numBits = 2; num = 0; - symbol = levels[i - 1]; + symbol = levels[(size_t)i - 1]; } else { @@ -81,7 +83,7 @@ bool CCoder::ReadTables(void) m_FinalBlock = (ReadBits(kFinalBlockFieldSize) == NFinalBlockField::kFinalBlock); if (m_InBitStream.ExtraBitsWereRead()) return false; - UInt32 blockType = ReadBits(kBlockTypeFieldSize); + const UInt32 blockType = ReadBits(kBlockTypeFieldSize); if (blockType > NBlockType::kDynamicHuffman) return false; if (m_InBitStream.ExtraBitsWereRead()) @@ -107,28 +109,26 @@ bool CCoder::ReadTables(void) } else { - unsigned numLitLenLevels = ReadBits(kNumLenCodesFieldSize) + kNumLitLenCodesMin; - _numDistLevels = ReadBits(kNumDistCodesFieldSize) + kNumDistCodesMin; - unsigned numLevelCodes = ReadBits(kNumLevelCodesFieldSize) + kNumLevelCodesMin; + const unsigned numLitLenLevels = ReadBits(kNumLenCodesFieldSize) + kNumLitLenCodesMin; + _numDistLevels = (unsigned)ReadBits(kNumDistCodesFieldSize) + kNumDistCodesMin; + const unsigned numLevelCodes = ReadBits(kNumLevelCodesFieldSize) + kNumLevelCodesMin; if (!_deflate64Mode) if (_numDistLevels > kDistTableSize32) return false; - Byte levelLevels[kLevelTableSize]; - for (unsigned i = 0; i < kLevelTableSize; i++) - { - unsigned position = kCodeLengthAlphabetOrder[i]; - if (i < numLevelCodes) - levelLevels[position] = (Byte)ReadBits(kLevelFieldSize); - else - levelLevels[position] = 0; - } + const unsigned kLevelTableSize_aligned4 = kLevelTableSize + 1; + Byte levelLevels[kLevelTableSize_aligned4]; + memset (levelLevels, 0, sizeof(levelLevels)); + unsigned i = 0; + do + levelLevels[kCodeLengthAlphabetOrder[i++]] = (Byte)ReadBits(kLevelFieldSize); + while (i != numLevelCodes); if (m_InBitStream.ExtraBitsWereRead()) return false; - RIF(m_LevelDecoder.Build(levelLevels)); + RIF(m_LevelDecoder.Build(levelLevels, false)) // full Byte tmpLevels[kFixedMainTableSize + kFixedDistTableSize]; if (!DecodeLevels(tmpLevels, numLitLenLevels + _numDistLevels)) @@ -141,35 +141,59 @@ bool CCoder::ReadTables(void) memcpy(levels.litLenLevels, tmpLevels, numLitLenLevels); memcpy(levels.distLevels, tmpLevels + numLitLenLevels, _numDistLevels); } - RIF(m_MainDecoder.Build(levels.litLenLevels)); + RIF(m_MainDecoder.Build(levels.litLenLevels)) return m_DistDecoder.Build(levels.distLevels); } -HRESULT CCoder::CodeSpec(UInt32 curSize, bool finishInputStream) + +HRESULT CCoder::InitInStream(bool needInit) +{ + if (needInit) + { + // for HDD-Windows: + // (1 << 15) - best for reading only prefetch + // (1 << 22) - best for real reading / writing + if (!m_InBitStream.Create(1 << 20)) + return E_OUTOFMEMORY; + m_InBitStream.Init(); + _needInitInStream = false; + } + return S_OK; +} + + +HRESULT CCoder::CodeSpec(UInt32 curSize, bool finishInputStream, UInt32 inputProgressLimit) { if (_remainLen == kLenIdFinished) return S_OK; + if (_remainLen == kLenIdNeedInit) { if (!_keepHistory) if (!m_OutWindowStream.Create(_deflate64Mode ? kHistorySize64: kHistorySize32)) return E_OUTOFMEMORY; - RINOK(InitInStream(_needInitInStream)); + RINOK(InitInStream(_needInitInStream)) m_OutWindowStream.Init(_keepHistory); + m_FinalBlock = false; _remainLen = 0; _needReadTable = true; } - while (_remainLen > 0 && curSize > 0) + // _remainLen >= 0 + while (_remainLen && curSize) { _remainLen--; - Byte b = m_OutWindowStream.GetByte(_rep0); + const Byte b = m_OutWindowStream.GetByte(_rep0); m_OutWindowStream.PutByte(b); curSize--; } - while (curSize > 0 || finishInputStream) + UInt64 inputStart = 0; + if (inputProgressLimit != 0) + inputStart = m_InBitStream.GetProcessedSize(); + + while (curSize || finishInputStream) { if (m_InBitStream.ExtraBitsWereRead()) return S_FALSE; @@ -181,6 +205,11 @@ HRESULT CCoder::CodeSpec(UInt32 curSize, bool finishInputStream) _remainLen = kLenIdFinished; break; } + + if (inputProgressLimit != 0) + if (m_InBitStream.GetProcessedSize() - inputStart >= inputProgressLimit) + return S_OK; + if (!ReadTables()) return S_FALSE; if (m_InBitStream.ExtraBitsWereRead()) @@ -194,20 +223,53 @@ HRESULT CCoder::CodeSpec(UInt32 curSize, bool finishInputStream) return S_FALSE; /* NSIS version contains some bits in bitl bits buffer. So we must read some first bytes via ReadAlignedByte */ - for (; m_StoredBlockSize > 0 && curSize > 0 && m_InBitStream.ThereAreDataInBitsBuffer(); m_StoredBlockSize--, curSize--) + UInt32 num = m_StoredBlockSize; + if (num > curSize) + num = curSize; + m_StoredBlockSize -= num; + curSize -= num; + for (; num && m_InBitStream.ThereAreDataInBitsBuffer(); num--) m_OutWindowStream.PutByte(ReadAlignedByte()); - for (; m_StoredBlockSize > 0 && curSize > 0; m_StoredBlockSize--, curSize--) - m_OutWindowStream.PutByte(m_InBitStream.ReadDirectByte()); + if (num) + { +#if 1 + // fast code + do + { + size_t a; + Byte *buf = m_OutWindowStream.GetOutBuffer(a); + // a != 0 + if (a > num) + a = num; + // a != 0 + a = m_InBitStream.ReadDirectBytesPart(buf, a); + if (a == 0) + return S_FALSE; + m_OutWindowStream.SkipWrittenBytes(a); + num -= (UInt32)a; + } + while (num); +#else + // slow code: + do + m_OutWindowStream.PutByte(m_InBitStream.ReadDirectByte()); + while (--num); +#endif + } _needReadTable = (m_StoredBlockSize == 0); continue; } - while (curSize > 0) + while (curSize) { if (m_InBitStream.ExtraBitsWereRead_Fast()) return S_FALSE; - - UInt32 sym = m_MainDecoder.Decode(&m_InBitStream); + unsigned sym; +#if 0 + sym = m_MainDecoder.Decode(&m_InBitStream); +#else + Z7_HUFF_DECODE_CHECK(sym, &m_MainDecoder, kNumHuffmanBits, kNumTableBits_Main, &m_InBitStream, { return S_FALSE; }) +#endif if (sym < 0x100) { @@ -215,12 +277,15 @@ HRESULT CCoder::CodeSpec(UInt32 curSize, bool finishInputStream) curSize--; continue; } - else if (sym == kSymbolEndOfBlock) + if (sym == kSymbolEndOfBlock) { _needReadTable = true; break; } - else if (sym < kMainTableSize) +#if 0 + if (sym >= kMainTableSize) + return S_FALSE; +#endif { sym -= kSymbolMatch; UInt32 len; @@ -238,26 +303,40 @@ HRESULT CCoder::CodeSpec(UInt32 curSize, bool finishInputStream) } len += kMatchMinLen + m_InBitStream.ReadBits(numBits); } - UInt32 locLen = len; - if (locLen > curSize) - locLen = (UInt32)curSize; + +#if 0 sym = m_DistDecoder.Decode(&m_InBitStream); if (sym >= _numDistLevels) return S_FALSE; - UInt32 distance = kDistStart[sym] + m_InBitStream.ReadBits(kDistDirectBits[sym]); - if (!m_OutWindowStream.CopyBlock(distance, locLen)) +#else + Z7_HUFF_DECODE_CHECK(sym, &m_DistDecoder, kNumHuffmanBits, kNumTableBits_Dist, &m_InBitStream, { return S_FALSE; }) +#endif + +#if 1 + sym = kDistStart[sym] + m_InBitStream.ReadBits(kDistDirectBits[sym]); +#else + if (sym >= 4) + { + // sym &= 31; + const unsigned numDirectBits = (sym - 2) >> 1; + sym = (2u | (sym & 1)) << numDirectBits; + sym += m_InBitStream.ReadBits(numDirectBits); + } +#endif + UInt32 locLen = len; + if (locLen > curSize) + locLen = (UInt32)curSize; + if (!m_OutWindowStream.CopyBlock(sym, locLen)) return S_FALSE; curSize -= locLen; len -= locLen; if (len != 0) { _remainLen = (Int32)len; - _rep0 = distance; + _rep0 = sym; break; } } - else - return S_FALSE; } if (finishInputStream && curSize == 0) @@ -275,7 +354,7 @@ HRESULT CCoder::CodeSpec(UInt32 curSize, bool finishInputStream) } -#ifdef _NO_EXCEPTIONS +#ifdef Z7_NO_EXCEPTIONS #define DEFLATE_TRY_BEGIN #define DEFLATE_TRY_END(res) @@ -284,134 +363,204 @@ HRESULT CCoder::CodeSpec(UInt32 curSize, bool finishInputStream) #define DEFLATE_TRY_BEGIN try { #define DEFLATE_TRY_END(res) } \ - catch(const CInBufferException &e) { res = e.ErrorCode; } \ - catch(const CLzOutWindowException &e) { res = e.ErrorCode; } \ + catch(const CSystemException &e) { res = e.ErrorCode; } \ catch(...) { res = S_FALSE; } + // catch(const CInBufferException &e) { res = e.ErrorCode; } + // catch(const CLzOutWindowException &e) { res = e.ErrorCode; } + #endif -HRESULT CCoder::CodeReal(ISequentialOutStream *outStream, - const UInt64 *outSize, ICompressProgressInfo *progress) +HRESULT CCoder::CodeReal(ISequentialOutStream *outStream, ICompressProgressInfo *progress) { HRESULT res; + DEFLATE_TRY_BEGIN + m_OutWindowStream.SetStream(outStream); CCoderReleaser flusher(this); const UInt64 inStart = _needInitInStream ? 0 : m_InBitStream.GetProcessedSize(); - const UInt64 start = m_OutWindowStream.GetProcessedSize(); for (;;) { - UInt32 curSize = 1 << 18; + const UInt32 kInputProgressLimit = 1 << 21; + UInt32 curSize = 1 << 20; bool finishInputStream = false; - if (outSize) + if (_outSizeDefined) { - const UInt64 rem = *outSize - (m_OutWindowStream.GetProcessedSize() - start); + const UInt64 rem = _outSize - GetOutProcessedCur(); if (curSize >= rem) { curSize = (UInt32)rem; - if (ZlibMode || _needFinishInput) + if (_needFinishInput) finishInputStream = true; + else if (curSize == 0) + break; } } - if (!finishInputStream && curSize == 0) - break; - RINOK(CodeSpec(curSize, finishInputStream)); + + RINOK(CodeSpec(curSize, finishInputStream, progress ? kInputProgressLimit : 0)) + if (_remainLen == kLenIdFinished) break; + if (progress) { const UInt64 inSize = m_InBitStream.GetProcessedSize() - inStart; - const UInt64 nowPos64 = m_OutWindowStream.GetProcessedSize() - start; - RINOK(progress->SetRatioInfo(&inSize, &nowPos64)); + const UInt64 nowPos64 = GetOutProcessedCur(); + RINOK(progress->SetRatioInfo(&inSize, &nowPos64)) } } - if (_remainLen == kLenIdFinished && ZlibMode) - { - m_InBitStream.AlignToByte(); - for (unsigned i = 0; i < 4; i++) - ZlibFooter[i] = ReadAlignedByte(); - } - flusher.NeedFlush = false; res = Flush(); if (res == S_OK && _remainLen != kLenIdNeedInit && InputEofError()) return S_FALSE; + DEFLATE_TRY_END(res) + return res; } -HRESULT CCoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 * /* inSize */, const UInt64 *outSize, ICompressProgressInfo *progress) + +Z7_COM7F_IMF(CCoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, + const UInt64 * /* inSize */, const UInt64 *outSize, ICompressProgressInfo *progress)) { SetInStream(inStream); SetOutStreamSize(outSize); - HRESULT res = CodeReal(outStream, outSize, progress); + const HRESULT res = CodeReal(outStream, progress); ReleaseInStream(); + /* + if (res == S_OK) + if (_needFinishInput && inSize && *inSize != m_InBitStream.GetProcessedSize()) + res = S_FALSE; + */ return res; } -STDMETHODIMP CCoder::GetInStreamProcessedSize(UInt64 *value) + +Z7_COM7F_IMF(CCoder::SetFinishMode(UInt32 finishMode)) +{ + Set_NeedFinishInput(finishMode != 0); + return S_OK; +} + + +Z7_COM7F_IMF(CCoder::GetInStreamProcessedSize(UInt64 *value)) +{ + *value = m_InBitStream.GetStreamSize(); + return S_OK; +} + + +Z7_COM7F_IMF(CCoder::ReadUnusedFromInBuf(void *data, UInt32 size, UInt32 *processedSize)) { - if (!value) - return E_INVALIDARG; - *value = m_InBitStream.GetProcessedSize(); + AlignToByte(); + UInt32 i = 0; + { + for (i = 0; i < size; i++) + { + if (!m_InBitStream.ReadAlignedByte_FromBuf(((Byte *)data)[i])) + break; + } + } + if (processedSize) + *processedSize = i; return S_OK; } -STDMETHODIMP CCoder::SetInStream(ISequentialInStream *inStream) + +Z7_COM7F_IMF(CCoder::SetInStream(ISequentialInStream *inStream)) { m_InStreamRef = inStream; m_InBitStream.SetStream(inStream); return S_OK; } -STDMETHODIMP CCoder::ReleaseInStream() + +Z7_COM7F_IMF(CCoder::ReleaseInStream()) { m_InStreamRef.Release(); + m_InBitStream.ClearStreamPtr(); return S_OK; } -STDMETHODIMP CCoder::SetOutStreamSize(const UInt64 * /* outSize */) + +void CCoder::SetOutStreamSizeResume(const UInt64 *outSize) { + _outSizeDefined = (outSize != NULL); + _outSize = 0; + if (_outSizeDefined) + _outSize = *outSize; + m_OutWindowStream.Init(_keepHistory); + _outStartPos = m_OutWindowStream.GetProcessedSize(); _remainLen = kLenIdNeedInit; +} + + +Z7_COM7F_IMF(CCoder::SetOutStreamSize(const UInt64 *outSize)) +{ + /* + 18.06: + We want to support GetInputProcessedSize() before CCoder::Read() + So we call m_InBitStream.Init() even before buffer allocations + m_InBitStream.Init() just sets variables to default values + But later we will call m_InBitStream.Init() again with real buffer pointers + */ + m_InBitStream.Init(); _needInitInStream = true; - m_OutWindowStream.Init(_keepHistory); + SetOutStreamSizeResume(outSize); return S_OK; } -#ifndef NO_READ_FROM_CODER -STDMETHODIMP CCoder::Read(void *data, UInt32 size, UInt32 *processedSize) +#ifndef Z7_NO_READ_FROM_CODER + +Z7_COM7F_IMF(CCoder::Read(void *data, UInt32 size, UInt32 *processedSize)) { - HRESULT res; - DEFLATE_TRY_BEGIN if (processedSize) *processedSize = 0; - const UInt64 startPos = m_OutWindowStream.GetProcessedSize(); - m_OutWindowStream.SetMemStream((Byte *)data); - res = CodeSpec(size, false); - if (res == S_OK) + const UInt64 outPos = GetOutProcessedCur(); + + bool finishInputStream = false; + if (_outSizeDefined) { - res = Flush(); - if (processedSize) - *processedSize = (UInt32)(m_OutWindowStream.GetProcessedSize() - startPos); + const UInt64 rem = _outSize - outPos; + if (size >= rem) + { + size = (UInt32)rem; + if (_needFinishInput) + finishInputStream = true; + } } + if (!finishInputStream && size == 0) + return S_OK; + + HRESULT res; + DEFLATE_TRY_BEGIN + m_OutWindowStream.SetMemStream((Byte *)data); + res = CodeSpec(size, finishInputStream); DEFLATE_TRY_END(res) + { + const HRESULT res2 = Flush(); + if (res2 != S_OK) + res = res2; + } + if (processedSize) + *processedSize = (UInt32)(GetOutProcessedCur() - outPos); m_OutWindowStream.SetMemStream(NULL); return res; } #endif -STDMETHODIMP CCoder::CodeResume(ISequentialOutStream *outStream, const UInt64 *outSize, ICompressProgressInfo *progress) + +HRESULT CCoder::CodeResume(ISequentialOutStream *outStream, const UInt64 *outSize, ICompressProgressInfo *progress) { - _remainLen = kLenIdNeedInit; - m_OutWindowStream.Init(_keepHistory); - return CodeReal(outStream, outSize, progress); + SetOutStreamSizeResume(outSize); + return CodeReal(outStream, progress); } }}} diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/DeflateDecoder.h b/src/plugins/7zip/7za/cpp/7zip/Compress/DeflateDecoder.h index 09f84eb4..4e0ae616 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/DeflateDecoder.h +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/DeflateDecoder.h @@ -1,7 +1,7 @@ // DeflateDecoder.h -#ifndef __DEFLATE_DECODER_H -#define __DEFLATE_DECODER_H +#ifndef ZIP7_INC_DEFLATE_DECODER_H +#define ZIP7_INC_DEFLATE_DECODER_H #include "../../Common/MyCom.h" @@ -21,26 +21,54 @@ namespace NDecoder { const int kLenIdFinished = -1; const int kLenIdNeedInit = -2; +const unsigned kNumTableBits_Main = 10; +const unsigned kNumTableBits_Dist = 6; + class CCoder: public ICompressCoder, + public ICompressSetFinishMode, public ICompressGetInStreamProcessedSize, - #ifndef NO_READ_FROM_CODER + public ICompressReadUnusedFromInBuf, public ICompressSetInStream, public ICompressSetOutStreamSize, +#ifndef Z7_NO_READ_FROM_CODER public ISequentialInStream, - #endif +#endif public CMyUnknownImp { + Z7_COM_QI_BEGIN2(ICompressCoder) + Z7_COM_QI_ENTRY(ICompressSetFinishMode) + Z7_COM_QI_ENTRY(ICompressGetInStreamProcessedSize) + Z7_COM_QI_ENTRY(ICompressReadUnusedFromInBuf) + Z7_COM_QI_ENTRY(ICompressSetInStream) + Z7_COM_QI_ENTRY(ICompressSetOutStreamSize) +#ifndef Z7_NO_READ_FROM_CODER + Z7_COM_QI_ENTRY(ISequentialInStream) +#endif + Z7_COM_QI_END + Z7_COM_ADDREF_RELEASE + + Z7_IFACE_COM7_IMP(ICompressCoder) + Z7_IFACE_COM7_IMP(ICompressSetFinishMode) + Z7_IFACE_COM7_IMP(ICompressGetInStreamProcessedSize) +public: + Z7_IFACE_COM7_IMP(ICompressReadUnusedFromInBuf) + Z7_IFACE_COM7_IMP(ICompressSetInStream) +private: + Z7_IFACE_COM7_IMP(ICompressSetOutStreamSize) +#ifndef Z7_NO_READ_FROM_CODER + Z7_IFACE_COM7_IMP(ISequentialInStream) +#endif + CLzOutWindow m_OutWindowStream; - CMyComPtr m_InStreamRef; NBitl::CDecoder m_InBitStream; - NCompress::NHuffman::CDecoder m_MainDecoder; - NCompress::NHuffman::CDecoder m_DistDecoder; + NCompress::NHuffman::CDecoder m_MainDecoder; + NCompress::NHuffman::CDecoder256 m_DistDecoder; NCompress::NHuffman::CDecoder7b m_LevelDecoder; UInt32 m_StoredBlockSize; - UInt32 _numDistLevels; + unsigned _numDistLevels; bool m_FinalBlock; bool m_StoredMode; @@ -54,6 +82,14 @@ class CCoder: Int32 _remainLen; UInt32 _rep0; + bool _outSizeDefined; + CMyComPtr m_InStreamRef; + UInt64 _outSize; + UInt64 _outStartPos; + + void SetOutStreamSizeResume(const UInt64 *outSize); + UInt64 GetOutProcessedCur() const { return m_OutWindowStream.GetProcessedSize() - _outStartPos; } + UInt32 ReadBits(unsigned numBits); bool DecodeLevels(Byte *levels, unsigned numSymbols); @@ -74,78 +110,40 @@ class CCoder: }; friend class CCoderReleaser; - HRESULT CodeSpec(UInt32 curSize, bool finishInputStream); + HRESULT CodeSpec(UInt32 curSize, bool finishInputStream, UInt32 inputProgressLimit = 0); public: - bool ZlibMode; - Byte ZlibFooter[4]; CCoder(bool deflate64Mode); - virtual ~CCoder() {}; + virtual ~CCoder() {} void SetNsisMode(bool nsisMode) { _deflateNSIS = nsisMode; } void Set_KeepHistory(bool keepHistory) { _keepHistory = keepHistory; } void Set_NeedFinishInput(bool needFinishInput) { _needFinishInput = needFinishInput; } - bool IsFinished() const { return _remainLen == kLenIdFinished;; } + bool IsFinished() const { return _remainLen == kLenIdFinished; } bool IsFinalBlock() const { return m_FinalBlock; } - HRESULT CodeReal(ISequentialOutStream *outStream, - const UInt64 *outSize, ICompressProgressInfo *progress); - - #ifndef NO_READ_FROM_CODER - MY_UNKNOWN_IMP5( - ICompressCoder, - ICompressGetInStreamProcessedSize, - ICompressSetInStream, - ICompressSetOutStreamSize, - ISequentialInStream - ) - #else - MY_UNKNOWN_IMP2( - ICompressCoder, - ICompressGetInStreamProcessedSize) - #endif - - STDMETHOD(Code)(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress); - - STDMETHOD(SetInStream)(ISequentialInStream *inStream); - STDMETHOD(ReleaseInStream)(); - STDMETHOD(SetOutStreamSize)(const UInt64 *outSize); - - #ifndef NO_READ_FROM_CODER - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); - #endif - - STDMETHOD(CodeResume)(ISequentialOutStream *outStream, const UInt64 *outSize, ICompressProgressInfo *progress); + HRESULT CodeReal(ISequentialOutStream *outStream, ICompressProgressInfo *progress); - HRESULT InitInStream(bool needInit) - { - if (!m_InBitStream.Create(1 << 17)) - return E_OUTOFMEMORY; - if (needInit) - { - m_InBitStream.Init(); - _needInitInStream = false; - } - return S_OK; - } +public: + HRESULT CodeResume(ISequentialOutStream *outStream, const UInt64 *outSize, ICompressProgressInfo *progress); + HRESULT InitInStream(bool needInit); void AlignToByte() { m_InBitStream.AlignToByte(); } Byte ReadAlignedByte(); UInt32 ReadAligned_UInt16() // aligned for Byte range { - UInt32 v = m_InBitStream.ReadAlignedByte(); + const UInt32 v = m_InBitStream.ReadAlignedByte(); return v | ((UInt32)m_InBitStream.ReadAlignedByte() << 8); } bool InputEofError() const { return m_InBitStream.ExtraBitsWereRead(); } + // size of used real data from input stream UInt64 GetStreamSize() const { return m_InBitStream.GetStreamSize(); } - UInt64 GetInputProcessedSize() const { return m_InBitStream.GetProcessedSize(); } - // IGetInStreamProcessedSize - STDMETHOD(GetInStreamProcessedSize)(UInt64 *value); + // size of virtual input stream processed + UInt64 GetInputProcessedSize() const { return m_InBitStream.GetProcessedSize(); } }; class CCOMCoder : public CCoder { public: CCOMCoder(): CCoder(false) {} }; diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/DeflateEncoder.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/DeflateEncoder.cpp index 538e7b5e..afc5f125 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/DeflateEncoder.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/DeflateEncoder.cpp @@ -7,22 +7,28 @@ #include "../../Common/ComTry.h" +#include "../Common/CWrappers.h" + #include "DeflateEncoder.h" #undef NO_INLINE #ifdef _MSC_VER -#define NO_INLINE MY_NO_INLINE +#define NO_INLINE Z7_NO_INLINE #else #define NO_INLINE #endif +#define MAX_HUF_LEN_12 12 + namespace NCompress { namespace NDeflate { namespace NEncoder { +static const unsigned k_CodeValue_Len_Is_Literal_Flag = 1u << 15; + static const unsigned kNumDivPassesMax = 10; // [0, 16); ratio/speed/ram tradeoff; use big value for better compression ratio. -static const UInt32 kNumTables = (1 << kNumDivPassesMax); +static const unsigned kNumTables = 1u << kNumDivPassesMax; static const UInt32 kFixedHuffmanCodeBlockSizeMax = (1 << 8); // [0, (1 << 32)); ratio/speed tradeoff; use big value for better compression ratio. static const UInt32 kDivideCodeBlockSizeMin = (1 << 7); // [1, (1 << 32)); ratio/speed tradeoff; use small value for better compression ratio. @@ -34,15 +40,19 @@ static const UInt32 kMatchArrayLimit = kMatchArraySize - kMatchMaxLen * 4 * size static const UInt32 kBlockUncompressedSizeThreshold = kMaxUncompressedBlockSize - kMatchMaxLen - kNumOpts; -static const unsigned kMaxCodeBitLength = 11; +// static const unsigned kMaxCodeBitLength = 11; static const unsigned kMaxLevelBitLength = 7; static const Byte kNoLiteralStatPrice = 11; static const Byte kNoLenStatPrice = 11; static const Byte kNoPosStatPrice = 6; +MY_ALIGN(64) static Byte g_LenSlots[kNumLenSymbolsMax]; -static Byte g_FastPos[1 << 9]; + +#define kNumLogBits 9 // do not change it +MY_ALIGN(64) +static Byte g_FastPos[1 << kNumLogBits]; class CFastPosInit { @@ -53,17 +63,17 @@ class CFastPosInit for (i = 0; i < kNumLenSlots; i++) { unsigned c = kLenStart32[i]; - unsigned j = 1 << kLenDirectBits32[i]; + const unsigned j = 1u << kLenDirectBits32[i]; for (unsigned k = 0; k < j; k++, c++) g_LenSlots[c] = (Byte)i; } - const unsigned kFastSlots = 18; + const unsigned kFastSlots = kNumLogBits * 2; unsigned c = 0; for (Byte slotFast = 0; slotFast < kFastSlots; slotFast++) { - UInt32 k = (1 << kDistDirectBits[slotFast]); - for (UInt32 j = 0; j < k; j++, c++) + const unsigned k = 1u << kDistDirectBits[slotFast]; + for (unsigned j = 0; j < k; j++, c++) g_FastPos[c] = slotFast; } } @@ -71,14 +81,24 @@ class CFastPosInit static CFastPosInit g_FastPosInit; - -inline UInt32 GetPosSlot(UInt32 pos) +inline unsigned GetPosSlot(UInt32 pos) { + /* if (pos < 0x200) return g_FastPos[pos]; return g_FastPos[pos >> 8] + 16; + */ + // const unsigned zz = (pos < ((UInt32)1 << (kNumLogBits))) ? 0 : 8; + /* + const unsigned zz = (kNumLogBits - 1) & + ((UInt32)0 - (((((UInt32)1 << kNumLogBits) - 1) - pos) >> 31)); + */ + const unsigned zz = (kNumLogBits - 1) & + (((((UInt32)1 << kNumLogBits) - 1) - pos) >> (31 - 3)); + return g_FastPos[pos >> zz] + (zz * 2); } + void CEncProps::Normalize() { int level = Level; @@ -87,7 +107,7 @@ void CEncProps::Normalize() if (algo < 0) algo = (level < 5 ? 0 : 1); if (fb < 0) fb = (level < 7 ? 32 : (level < 9 ? 64 : 128)); if (btMode < 0) btMode = (algo == 0 ? 0 : 1); - if (mc == 0) mc = (16 + (fb >> 1)); + if (mc == 0) mc = (16 + ((unsigned)fb >> 1)); if (numPasses == (UInt32)(Int32)-1) numPasses = (level < 7 ? 1 : (level < 9 ? 3 : 10)); } @@ -98,7 +118,7 @@ void CCoder::SetProps(const CEncProps *props2) m_MatchFinderCycles = props.mc; { - unsigned fb = props.fb; + unsigned fb = (unsigned)props.fb; if (fb < kMatchMinLen) fb = kMatchMinLen; if (fb > m_MatchMaxLen) @@ -123,12 +143,12 @@ void CCoder::SetProps(const CEncProps *props2) } CCoder::CCoder(bool deflate64Mode): - m_Deflate64Mode(deflate64Mode), - m_OnePosMatchesMemory(0), - m_DistanceMemory(0), + m_Values(NULL), + m_OnePosMatchesMemory(NULL), + m_DistanceMemory(NULL), m_Created(false), - m_Values(0), - m_Tables(0) + m_Deflate64Mode(deflate64Mode), + m_Tables(NULL) { m_MatchMaxLen = deflate64Mode ? kMatchMaxLen64 : kMatchMaxLen32; m_NumLenCombinations = deflate64Mode ? kNumLenSymbols64 : kNumLenSymbols32; @@ -144,34 +164,34 @@ CCoder::CCoder(bool deflate64Mode): HRESULT CCoder::Create() { // COM_TRY_BEGIN - if (m_Values == 0) + if (!m_Values) { - m_Values = (CCodeValue *)MyAlloc((kMaxUncompressedBlockSize) * sizeof(CCodeValue)); - if (m_Values == 0) + m_Values = (CCodeValue *)MyAlloc(kMaxUncompressedBlockSize * sizeof(CCodeValue)); + if (!m_Values) return E_OUTOFMEMORY; } - if (m_Tables == 0) + if (!m_Tables) { - m_Tables = (CTables *)MyAlloc((kNumTables) * sizeof(CTables)); - if (m_Tables == 0) + m_Tables = (CTables *)MyAlloc(kNumTables * sizeof(CTables)); + if (!m_Tables) return E_OUTOFMEMORY; } if (m_IsMultiPass) { - if (m_OnePosMatchesMemory == 0) + if (!m_OnePosMatchesMemory) { m_OnePosMatchesMemory = (UInt16 *)::MidAlloc(kMatchArraySize * sizeof(UInt16)); - if (m_OnePosMatchesMemory == 0) + if (!m_OnePosMatchesMemory) return E_OUTOFMEMORY; } } else { - if (m_DistanceMemory == 0) + if (!m_DistanceMemory) { m_DistanceMemory = (UInt16 *)MyAlloc((kMatchMaxLen + 2) * 2 * sizeof(UInt16)); - if (m_DistanceMemory == 0) + if (!m_DistanceMemory) return E_OUTOFMEMORY; m_MatchDistances = m_DistanceMemory; } @@ -181,10 +201,11 @@ HRESULT CCoder::Create() { _lzInWindow.btMode = (Byte)(_btMode ? 1 : 0); _lzInWindow.numHashBytes = 3; + _lzInWindow.numHashBytes_Min = 3; if (!MatchFinder_Create(&_lzInWindow, m_Deflate64Mode ? kHistorySize64 : kHistorySize32, kNumOpts + kMaxUncompressedBlockSize, - m_NumFastBytes, m_MatchMaxLen - m_NumFastBytes, &g_Alloc)) + m_NumFastBytes, m_MatchMaxLen - m_NumFastBytes, &g_AlignedAlloc)) return E_OUTOFMEMORY; if (!m_OutStream.Create(1 << 20)) return E_OUTOFMEMORY; @@ -211,10 +232,10 @@ HRESULT CCoder::BaseSetEncoderProperties2(const PROPID *propIDs, const PROPVARIA switch (propID) { case NCoderPropID::kNumPasses: props.numPasses = v; break; - case NCoderPropID::kNumFastBytes: props.fb = v; break; + case NCoderPropID::kNumFastBytes: props.fb = (int)v; break; case NCoderPropID::kMatchFinderCycles: props.mc = v; break; - case NCoderPropID::kAlgorithm: props.algo = v; break; - case NCoderPropID::kLevel: props.Level = v; break; + case NCoderPropID::kAlgorithm: props.algo = (int)v; break; + case NCoderPropID::kLevel: props.Level = (int)v; break; case NCoderPropID::kNumThreads: break; default: return E_INVALIDARG; } @@ -225,16 +246,16 @@ HRESULT CCoder::BaseSetEncoderProperties2(const PROPID *propIDs, const PROPVARIA void CCoder::Free() { - ::MidFree(m_OnePosMatchesMemory); m_OnePosMatchesMemory = 0; - ::MyFree(m_DistanceMemory); m_DistanceMemory = 0; - ::MyFree(m_Values); m_Values = 0; - ::MyFree(m_Tables); m_Tables = 0; + ::MidFree(m_OnePosMatchesMemory); m_OnePosMatchesMemory = NULL; + ::MyFree(m_DistanceMemory); m_DistanceMemory = NULL; + ::MyFree(m_Values); m_Values = NULL; + ::MyFree(m_Tables); m_Tables = NULL; } CCoder::~CCoder() { Free(); - MatchFinder_Free(&_lzInWindow, &g_Alloc); + MatchFinder_Free(&_lzInWindow, &g_AlignedAlloc); } NO_INLINE void CCoder::GetMatches() @@ -251,34 +272,36 @@ NO_INLINE void CCoder::GetMatches() UInt32 distanceTmp[kMatchMaxLen * 2 + 3]; - UInt32 numPairs = (_btMode) ? + const size_t numPairs = (size_t)((_btMode ? Bt3Zip_MatchFinder_GetMatches(&_lzInWindow, distanceTmp): - Hc3Zip_MatchFinder_GetMatches(&_lzInWindow, distanceTmp); + Hc3Zip_MatchFinder_GetMatches(&_lzInWindow, distanceTmp)) - distanceTmp); - *m_MatchDistances = (UInt16)numPairs; + UInt16 *matchDistances = m_MatchDistances; + *matchDistances++ = (UInt16)numPairs; - if (numPairs > 0) + if (numPairs != 0) { - UInt32 i; + size_t i; for (i = 0; i < numPairs; i += 2) { - m_MatchDistances[i + 1] = (UInt16)distanceTmp[i]; - m_MatchDistances[i + 2] = (UInt16)distanceTmp[i + 1]; + matchDistances[0] = (UInt16)distanceTmp[i]; + matchDistances[1] = (UInt16)distanceTmp[(size_t)i + 1]; + matchDistances += 2; } - UInt32 len = distanceTmp[numPairs - 2]; + UInt32 len = distanceTmp[(size_t)numPairs - 2]; if (len == m_NumFastBytes && m_NumFastBytes != m_MatchMaxLen) { UInt32 numAvail = Inline_MatchFinder_GetNumAvailableBytes(&_lzInWindow) + 1; const Byte *pby = Inline_MatchFinder_GetPointerToCurrentPos(&_lzInWindow) - 1; - const Byte *pby2 = pby - (distanceTmp[numPairs - 1] + 1); + const Byte *pby2 = pby - (distanceTmp[(size_t)numPairs - 1] + 1); if (numAvail > m_MatchMaxLen) numAvail = m_MatchMaxLen; for (; len < numAvail && pby[len] == pby2[len]; len++); - m_MatchDistances[i - 1] = (UInt16)len; + matchDistances[-2] = (UInt16)len; } } if (m_IsMultiPass) - m_Pos += numPairs + 1; + m_Pos += (UInt32)numPairs + 1; if (!m_SecondPass) m_AdditionalOffset++; } @@ -337,11 +360,11 @@ NO_INLINE UInt32 CCoder::GetOptimal(UInt32 &backRes) if (numDistancePairs == 0) return 1; const UInt16 *matchDistances = m_MatchDistances + 1; - lenEnd = matchDistances[numDistancePairs - 2]; + lenEnd = matchDistances[(size_t)numDistancePairs - 2]; if (lenEnd > m_NumFastBytes) { - backRes = matchDistances[numDistancePairs - 1]; + backRes = matchDistances[(size_t)numDistancePairs - 1]; MovePos(lenEnd - 1); return lenEnd; } @@ -356,10 +379,10 @@ NO_INLINE UInt32 CCoder::GetOptimal(UInt32 &backRes) for (UInt32 i = kMatchMinLen; i <= lenEnd; i++) { - UInt32 distance = matchDistances[offs + 1]; + UInt32 distance = matchDistances[(size_t)offs + 1]; m_Optimum[i].PosPrev = 0; m_Optimum[i].BackPrev = (UInt16)distance; - m_Optimum[i].Price = m_LenPrices[i - kMatchMinLen] + m_PosPrices[GetPosSlot(distance)]; + m_Optimum[i].Price = m_LenPrices[(size_t)i - kMatchMinLen] + m_PosPrices[GetPosSlot(distance)]; if (i == matchDistances[offs]) offs += 2; } @@ -378,11 +401,11 @@ NO_INLINE UInt32 CCoder::GetOptimal(UInt32 &backRes) UInt32 newLen = 0; if (numDistancePairs != 0) { - newLen = matchDistances[numDistancePairs - 2]; + newLen = matchDistances[(size_t)numDistancePairs - 2]; if (newLen > m_NumFastBytes) { UInt32 len = Backward(backRes, cur); - m_Optimum[cur].BackPrev = matchDistances[numDistancePairs - 1]; + m_Optimum[cur].BackPrev = matchDistances[(size_t)numDistancePairs - 1]; m_OptimumEndIndex = cur + newLen; m_Optimum[cur].PosPrev = (UInt16)m_OptimumEndIndex; MovePos(newLen - 1); @@ -392,7 +415,7 @@ NO_INLINE UInt32 CCoder::GetOptimal(UInt32 &backRes) UInt32 curPrice = m_Optimum[cur].Price; { const UInt32 curAnd1Price = curPrice + m_LiteralPrices[*(Inline_MatchFinder_GetPointerToCurrentPos(&_lzInWindow) + cur - m_AdditionalOffset)]; - COptimal &optimum = m_Optimum[cur + 1]; + COptimal &optimum = m_Optimum[(size_t)cur + 1]; if (curAnd1Price < optimum.Price) { optimum.Price = curAnd1Price; @@ -404,11 +427,11 @@ NO_INLINE UInt32 CCoder::GetOptimal(UInt32 &backRes) while (lenEnd < cur + newLen) m_Optimum[++lenEnd].Price = kIfinityPrice; UInt32 offs = 0; - UInt32 distance = matchDistances[offs + 1]; + UInt32 distance = matchDistances[(size_t)offs + 1]; curPrice += m_PosPrices[GetPosSlot(distance)]; for (UInt32 lenTest = kMatchMinLen; ; lenTest++) { - UInt32 curAndLenPrice = curPrice + m_LenPrices[lenTest - kMatchMinLen]; + UInt32 curAndLenPrice = curPrice + m_LenPrices[(size_t)lenTest - kMatchMinLen]; COptimal &optimum = m_Optimum[cur + lenTest]; if (curAndLenPrice < optimum.Price) { @@ -422,7 +445,7 @@ NO_INLINE UInt32 CCoder::GetOptimal(UInt32 &backRes) if (offs == numDistancePairs) break; curPrice -= m_PosPrices[GetPosSlot(distance)]; - distance = matchDistances[offs + 1]; + distance = matchDistances[(size_t)offs + 1]; curPrice += m_PosPrices[GetPosSlot(distance)]; } } @@ -435,7 +458,7 @@ UInt32 CCoder::GetOptimalFast(UInt32 &backRes) UInt32 numDistancePairs = m_MatchDistances[0]; if (numDistancePairs == 0) return 1; - UInt32 lenMain = m_MatchDistances[numDistancePairs - 1]; + UInt32 lenMain = m_MatchDistances[(size_t)numDistancePairs - 1]; backRes = m_MatchDistances[numDistancePairs]; MovePos(lenMain - 1); return lenMain; @@ -470,7 +493,7 @@ NO_INLINE void CCoder::LevelTableDummy(const Byte *levels, unsigned numLevels, U for (unsigned n = 0; n < numLevels; n++) { unsigned curLen = nextLen; - nextLen = (n < numLevels - 1) ? levels[n + 1] : 0xFF; + nextLen = (n < numLevels - 1) ? levels[(size_t)n + 1] : 0xFF; count++; if (count < maxCount && curLen == nextLen) continue; @@ -518,6 +541,7 @@ NO_INLINE void CCoder::WriteBits(UInt32 value, unsigned numBits) } #define WRITE_HF2(codes, lens, i) m_OutStream.WriteBits(codes[i], lens[i]) +#define WRITE_HF2_NO_INLINE(codes, lens, i) WriteBits(codes[i], lens[i]) #define WRITE_HF(i) WriteBits(codes[i], lens[i]) NO_INLINE void CCoder::LevelTableCode(const Byte *levels, unsigned numLevels, const Byte *lens, const UInt32 *codes) @@ -537,7 +561,7 @@ NO_INLINE void CCoder::LevelTableCode(const Byte *levels, unsigned numLevels, co for (unsigned n = 0; n < numLevels; n++) { unsigned curLen = nextLen; - nextLen = (n < numLevels - 1) ? levels[n + 1] : 0xFF; + nextLen = (n < numLevels - 1) ? levels[(size_t)n + 1] : 0xFF; count++; if (count < maxCount && curLen == nextLen) continue; @@ -593,7 +617,7 @@ NO_INLINE void CCoder::MakeTables(unsigned maxHuffLen) Huffman_Generate(distFreqs, distCodes, m_NewLevels.distLevels, kDistTableSize64, maxHuffLen); } -NO_INLINE UInt32 Huffman_GetPrice(const UInt32 *freqs, const Byte *lens, UInt32 num) +static NO_INLINE UInt32 Huffman_GetPrice(const UInt32 *freqs, const Byte *lens, UInt32 num) { UInt32 price = 0; UInt32 i; @@ -602,17 +626,22 @@ NO_INLINE UInt32 Huffman_GetPrice(const UInt32 *freqs, const Byte *lens, UInt32 return price; } -NO_INLINE UInt32 Huffman_GetPrice_Spec(const UInt32 *freqs, const Byte *lens, UInt32 num, const Byte *extraBits, UInt32 extraBase) +static NO_INLINE UInt32 Huffman_GetPrice_Spec( + const UInt32 *freqs, const Byte *lens, UInt32 num, + const Byte *extraBits, UInt32 extraBase) { - return Huffman_GetPrice(freqs, lens, num) + + return + Huffman_GetPrice(freqs, lens, num) + Huffman_GetPrice(freqs + extraBase, extraBits, num - extraBase); } NO_INLINE UInt32 CCoder::GetLzBlockPrice() const { return - Huffman_GetPrice_Spec(mainFreqs, m_NewLevels.litLenLevels, kFixedMainTableSize, m_LenDirectBits, kSymbolMatch) + - Huffman_GetPrice_Spec(distFreqs, m_NewLevels.distLevels, kDistTableSize64, kDistDirectBits, 0); + Huffman_GetPrice_Spec(mainFreqs, m_NewLevels.litLenLevels, + kFixedMainTableSize, m_LenDirectBits, kSymbolMatch) + + Huffman_GetPrice_Spec(distFreqs, m_NewLevels.distLevels, + kDistTableSize64, kDistDirectBits, 0); } NO_INLINE void CCoder::TryBlock() @@ -627,8 +656,9 @@ NO_INLINE void CCoder::TryBlock() { if (m_OptimumCurrentIndex == m_OptimumEndIndex) { - if (m_Pos >= kMatchArrayLimit || BlockSizeRes >= blockSize || !m_SecondPass && - ((Inline_MatchFinder_GetNumAvailableBytes(&_lzInWindow) == 0) || m_ValueIndex >= m_ValueBlockSize)) + if (m_Pos >= kMatchArrayLimit + || BlockSizeRes >= blockSize + || (!m_SecondPass && ((Inline_MatchFinder_GetNumAvailableBytes(&_lzInWindow) == 0) || m_ValueIndex >= m_ValueBlockSize))) break; } UInt32 pos; @@ -640,18 +670,18 @@ NO_INLINE void CCoder::TryBlock() CCodeValue &codeValue = m_Values[m_ValueIndex++]; if (len >= kMatchMinLen) { - UInt32 newLen = len - kMatchMinLen; + const UInt32 newLen = len - kMatchMinLen; codeValue.Len = (UInt16)newLen; - mainFreqs[kSymbolMatch + g_LenSlots[newLen]]++; + mainFreqs[kSymbolMatch + (size_t)g_LenSlots[newLen]]++; codeValue.Pos = (UInt16)pos; distFreqs[GetPosSlot(pos)]++; } else { - Byte b = *(Inline_MatchFinder_GetPointerToCurrentPos(&_lzInWindow) - m_AdditionalOffset); + const unsigned b = *(Inline_MatchFinder_GetPointerToCurrentPos(&_lzInWindow) - m_AdditionalOffset); mainFreqs[b]++; - codeValue.SetAsLiteral(); - codeValue.Pos = b; + codeValue.Len = k_CodeValue_Len_Is_Literal_Flag; + codeValue.Pos = (UInt16)b; } m_AdditionalOffset -= len; BlockSizeRes += len; @@ -675,7 +705,7 @@ NO_INLINE void CCoder::SetPrices(const CLevels &levels) for (i = 0; i < m_NumLenCombinations; i++) { UInt32 slot = g_LenSlots[i]; - Byte price = levels.litLenLevels[kSymbolMatch + slot]; + Byte price = levels.litLenLevels[kSymbolMatch + (size_t)slot]; m_LenPrices[i] = (Byte)(((price != 0) ? price : kNoLenStatPrice) + m_LenDirectBits[slot]); } @@ -686,16 +716,24 @@ NO_INLINE void CCoder::SetPrices(const CLevels &levels) } } -NO_INLINE void Huffman_ReverseBits(UInt32 *codes, const Byte *lens, UInt32 num) +#if MAX_HUF_LEN_12 > 12 +// Huffman_ReverseBits() now supports 12-bits values only. +#error Stop_Compiling_Bad_MAX_HUF_LEN_12 +#endif +static NO_INLINE void Huffman_ReverseBits(UInt32 *codes, const Byte *lens, UInt32 num) { - for (UInt32 i = 0; i < num; i++) + const Byte * const lens_lim = lens + num; + do { - UInt32 x = codes[i]; - x = ((x & 0x5555) << 1) | ((x & 0xAAAA) >> 1); - x = ((x & 0x3333) << 2) | ((x & 0xCCCC) >> 2); - x = ((x & 0x0F0F) << 4) | ((x & 0xF0F0) >> 4); - codes[i] = (((x & 0x00FF) << 8) | ((x & 0xFF00) >> 8)) >> (16 - lens[i]); + // we should change constants, if lens[*] can be larger than 12. + UInt32 x = *codes; + x = ((x & (0x555 )) << 2) + (x & (0xAAA )); + x = ((x & (0x333 << 1)) << 4) | (x & (0xCCC << 1)); + x = ((x & (0xF0F << 3)) << 8) | (x & (0x0F0 << 3)); + // we can use (x) instead of (x & (0xFF << 7)), if we support garabage data after (*lens) bits. + *codes++ = (((x & (0xFF << 7)) << 16) | x) >> (*lens ^ 31); } + while (++lens != lens_lim); } NO_INLINE void CCoder::WriteBlock() @@ -703,24 +741,28 @@ NO_INLINE void CCoder::WriteBlock() Huffman_ReverseBits(mainCodes, m_NewLevels.litLenLevels, kFixedMainTableSize); Huffman_ReverseBits(distCodes, m_NewLevels.distLevels, kDistTableSize64); - for (UInt32 i = 0; i < m_ValueIndex; i++) + CCodeValue *values = m_Values; + const CCodeValue * const values_lim = values + m_ValueIndex; + + if (values != values_lim) + do { - const CCodeValue &codeValue = m_Values[i]; - if (codeValue.IsLiteral()) - WRITE_HF2(mainCodes, m_NewLevels.litLenLevels, codeValue.Pos); + const UInt32 len = values->Len; + const UInt32 dist = values->Pos; + if (len == k_CodeValue_Len_Is_Literal_Flag) + WRITE_HF2(mainCodes, m_NewLevels.litLenLevels, dist); else { - UInt32 len = codeValue.Len; - UInt32 lenSlot = g_LenSlots[len]; + const unsigned lenSlot = g_LenSlots[len]; WRITE_HF2(mainCodes, m_NewLevels.litLenLevels, kSymbolMatch + lenSlot); m_OutStream.WriteBits(len - m_LenStart[lenSlot], m_LenDirectBits[lenSlot]); - UInt32 dist = codeValue.Pos; - UInt32 posSlot = GetPosSlot(dist); + const unsigned posSlot = GetPosSlot(dist); WRITE_HF2(distCodes, m_NewLevels.distLevels, posSlot); m_OutStream.WriteBits(dist - kDistStart[posSlot], kDistDirectBits[posSlot]); } } - WRITE_HF2(mainCodes, m_NewLevels.litLenLevels, kSymbolEndOfBlock); + while (++values != values_lim); + WRITE_HF2_NO_INLINE(mainCodes, m_NewLevels.litLenLevels, kSymbolEndOfBlock); } static UInt32 GetStorePrice(UInt32 blockSize, unsigned bitPosition) @@ -769,10 +811,10 @@ NO_INLINE UInt32 CCoder::TryDynBlock(unsigned tableIndex, UInt32 numPasses) { m_Pos = posTemp; TryBlock(); - unsigned numHuffBits = - (m_ValueIndex > 18000 ? 12 : - (m_ValueIndex > 7000 ? 11 : - (m_ValueIndex > 2000 ? 10 : 9))); + const unsigned numHuffBits = + m_ValueIndex > 18000 ? MAX_HUF_LEN_12 : + m_ValueIndex > 7000 ? 11 : + m_ValueIndex > 2000 ? 10 : 9; MakeTables(numHuffBits); SetPrices(m_NewLevels); } @@ -780,11 +822,11 @@ NO_INLINE UInt32 CCoder::TryDynBlock(unsigned tableIndex, UInt32 numPasses) (CLevels &)t = m_NewLevels; m_NumLitLenLevels = kMainTableSize; - while (m_NumLitLenLevels > kNumLitLenCodesMin && m_NewLevels.litLenLevels[m_NumLitLenLevels - 1] == 0) + while (m_NumLitLenLevels > kNumLitLenCodesMin && m_NewLevels.litLenLevels[(size_t)m_NumLitLenLevels - 1] == 0) m_NumLitLenLevels--; m_NumDistLevels = kDistTableSize64; - while (m_NumDistLevels > kNumDistCodesMin && m_NewLevels.distLevels[m_NumDistLevels - 1] == 0) + while (m_NumDistLevels > kNumDistCodesMin && m_NewLevels.distLevels[(size_t)m_NumDistLevels - 1] == 0) m_NumDistLevels--; UInt32 levelFreqs[kLevelTableSize]; @@ -923,14 +965,6 @@ void CCoder::CodeBlock(unsigned tableIndex, bool finalBlock) } } -SRes Read(void *object, void *data, size_t *size) -{ - const UInt32 kStepSize = (UInt32)1 << 31; - UInt32 curSize = ((*size < kStepSize) ? (UInt32)*size : kStepSize); - HRESULT res = ((CSeqInStream *)object)->RealStream->Read(data, curSize, &curSize); - *size = curSize; - return (SRes)res; -} HRESULT CCoder::CodeReal(ISequentialInStream *inStream, ISequentialOutStream *outStream, const UInt64 * /* inSize */ , const UInt64 * /* outSize */ , ICompressProgressInfo *progress) @@ -938,16 +972,20 @@ HRESULT CCoder::CodeReal(ISequentialInStream *inStream, ISequentialOutStream *ou m_CheckStatic = (m_NumPasses != 1 || m_NumDivPasses != 1); m_IsMultiPass = (m_CheckStatic || (m_NumPasses != 1 || m_NumDivPasses != 1)); - RINOK(Create()); + /* we can set stream mode before MatchFinder_Create + if default MatchFinder mode was not STREAM_MODE) */ + // MatchFinder_SET_STREAM_MODE(&_lzInWindow); + + CSeqInStreamWrap _seqInStream; + _seqInStream.Init(inStream); + MatchFinder_SET_STREAM(&_lzInWindow, &_seqInStream.vt) + + RINOK(Create()) m_ValueBlockSize = (7 << 10) + (1 << 12) * m_NumDivPasses; UInt64 nowPos = 0; - _seqInStream.RealStream = inStream; - _seqInStream.SeqInStream.Read = Read; - _lzInWindow.stream = &_seqInStream.SeqInStream; - MatchFinder_Init(&_lzInWindow); m_OutStream.SetStream(outStream); m_OutStream.Init(); @@ -969,12 +1007,16 @@ HRESULT CCoder::CodeReal(ISequentialInStream *inStream, ISequentialOutStream *ou if (progress != NULL) { UInt64 packSize = m_OutStream.GetProcessedSize(); - RINOK(progress->SetRatioInfo(&nowPos, &packSize)); + RINOK(progress->SetRatioInfo(&nowPos, &packSize)) } } while (Inline_MatchFinder_GetNumAvailableBytes(&_lzInWindow) != 0); + + if (_seqInStream.Res != S_OK) + return _seqInStream.Res; + if (_lzInWindow.result != SZ_OK) - return _lzInWindow.result; + return SResToHRESULT(_lzInWindow.result); return m_OutStream.Flush(); } @@ -986,18 +1028,18 @@ HRESULT CCoder::BaseCode(ISequentialInStream *inStream, ISequentialOutStream *ou catch(...) { return E_FAIL; } } -STDMETHODIMP CCOMCoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress) +Z7_COM7F_IMF(CCOMCoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, + const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress)) { return BaseCode(inStream, outStream, inSize, outSize, progress); } -STDMETHODIMP CCOMCoder::SetCoderProperties(const PROPID *propIDs, const PROPVARIANT *props, UInt32 numProps) +Z7_COM7F_IMF(CCOMCoder::SetCoderProperties(const PROPID *propIDs, const PROPVARIANT *props, UInt32 numProps)) { return BaseSetEncoderProperties2(propIDs, props, numProps); } -STDMETHODIMP CCOMCoder64::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress) +Z7_COM7F_IMF(CCOMCoder64::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, + const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress)) { return BaseCode(inStream, outStream, inSize, outSize, progress); } -STDMETHODIMP CCOMCoder64::SetCoderProperties(const PROPID *propIDs, const PROPVARIANT *props, UInt32 numProps) +Z7_COM7F_IMF(CCOMCoder64::SetCoderProperties(const PROPID *propIDs, const PROPVARIANT *props, UInt32 numProps)) { return BaseSetEncoderProperties2(propIDs, props, numProps); } }}} diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/DeflateEncoder.h b/src/plugins/7zip/7za/cpp/7zip/Compress/DeflateEncoder.h index 6c4ae4fc..be181e15 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/DeflateEncoder.h +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/DeflateEncoder.h @@ -1,7 +1,7 @@ // DeflateEncoder.h -#ifndef __DEFLATE_ENCODER_H -#define __DEFLATE_ENCODER_H +#ifndef ZIP7_INC_DEFLATE_ENCODER_H +#define ZIP7_INC_DEFLATE_ENCODER_H #include "../../../C/LzFind.h" @@ -46,11 +46,6 @@ struct CTables: public CLevels void InitStructures(); }; -typedef struct _CSeqInStream -{ - ISeqInStream SeqInStream; - ISequentialInStream *RealStream; -} CSeqInStream; struct CEncProps { @@ -76,8 +71,6 @@ class CCoder CMatchFinder _lzInWindow; CBitlEncoder m_OutStream; - CSeqInStream _seqInStream; - public: CCodeValue *m_Values; @@ -183,32 +176,26 @@ class CCoder }; -class CCOMCoder : +class CCOMCoder Z7_final: public ICompressCoder, public ICompressSetCoderProperties, public CMyUnknownImp, public CCoder { + Z7_IFACES_IMP_UNK_2(ICompressCoder, ICompressSetCoderProperties) public: - MY_UNKNOWN_IMP2(ICompressCoder, ICompressSetCoderProperties) - CCOMCoder(): CCoder(false) {}; - STDMETHOD(Code)(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress); - STDMETHOD(SetCoderProperties)(const PROPID *propIDs, const PROPVARIANT *props, UInt32 numProps); + CCOMCoder(): CCoder(false) {} }; -class CCOMCoder64 : +class CCOMCoder64 Z7_final: public ICompressCoder, public ICompressSetCoderProperties, public CMyUnknownImp, public CCoder { + Z7_IFACES_IMP_UNK_2(ICompressCoder, ICompressSetCoderProperties) public: - MY_UNKNOWN_IMP2(ICompressCoder, ICompressSetCoderProperties) - CCOMCoder64(): CCoder(true) {}; - STDMETHOD(Code)(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress); - STDMETHOD(SetCoderProperties)(const PROPID *propIDs, const PROPVARIANT *props, UInt32 numProps); + CCOMCoder64(): CCoder(true) {} }; }}} diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/DeflateRegister.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/DeflateRegister.cpp index 387be6ed..eaedb84a 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/DeflateRegister.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/DeflateRegister.cpp @@ -5,7 +5,7 @@ #include "../Common/RegisterCodec.h" #include "DeflateDecoder.h" -#if !defined(EXTRACT_ONLY) && !defined(DEFLATE_EXTRACT_ONLY) +#if !defined(Z7_EXTRACT_ONLY) && !defined(Z7_DEFLATE_EXTRACT_ONLY) #include "DeflateEncoder.h" #endif @@ -14,7 +14,7 @@ namespace NDeflate { REGISTER_CODEC_CREATE(CreateDec, NDecoder::CCOMCoder) -#if !defined(EXTRACT_ONLY) && !defined(DEFLATE_EXTRACT_ONLY) +#if !defined(Z7_EXTRACT_ONLY) && !defined(Z7_DEFLATE_EXTRACT_ONLY) REGISTER_CODEC_CREATE(CreateEnc, NEncoder::CCOMCoder) #else #define CreateEnc NULL diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/DeltaFilter.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/DeltaFilter.cpp index 3986ae4f..c234b7a6 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/DeltaFilter.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/DeltaFilter.cpp @@ -23,41 +23,40 @@ struct CDelta }; -#ifndef EXTRACT_ONLY +#ifndef Z7_EXTRACT_ONLY -class CEncoder: +class CEncoder Z7_final: public ICompressFilter, public ICompressSetCoderProperties, public ICompressWriteCoderProperties, - CDelta, - public CMyUnknownImp + public CMyUnknownImp, + CDelta { -public: - MY_UNKNOWN_IMP3(ICompressFilter, ICompressSetCoderProperties, ICompressWriteCoderProperties) - INTERFACE_ICompressFilter(;) - STDMETHOD(SetCoderProperties)(const PROPID *propIDs, const PROPVARIANT *props, UInt32 numProps); - STDMETHOD(WriteCoderProperties)(ISequentialOutStream *outStream); + Z7_IFACES_IMP_UNK_3( + ICompressFilter, + ICompressSetCoderProperties, + ICompressWriteCoderProperties) }; -STDMETHODIMP CEncoder::Init() +Z7_COM7F_IMF(CEncoder::Init()) { DeltaInit(); return S_OK; } -STDMETHODIMP_(UInt32) CEncoder::Filter(Byte *data, UInt32 size) +Z7_COM7F_IMF2(UInt32, CEncoder::Filter(Byte *data, UInt32 size)) { Delta_Encode(_state, _delta, data, size); return size; } -STDMETHODIMP CEncoder::SetCoderProperties(const PROPID *propIDs, const PROPVARIANT *props, UInt32 numProps) +Z7_COM7F_IMF(CEncoder::SetCoderProperties(const PROPID *propIDs, const PROPVARIANT *props, UInt32 numProps)) { - UInt32 delta = _delta; + unsigned delta = _delta; for (UInt32 i = 0; i < numProps; i++) { const PROPVARIANT &prop = props[i]; - PROPID propID = propIDs[i]; + const PROPID propID = propIDs[i]; if (propID >= NCoderPropID::kReduceSize) continue; if (prop.vt != VT_UI4) @@ -65,9 +64,9 @@ STDMETHODIMP CEncoder::SetCoderProperties(const PROPID *propIDs, const PROPVARIA switch (propID) { case NCoderPropID::kDefaultProp: - delta = (UInt32)prop.ulVal; - if (delta < 1 || delta > 256) + if (prop.ulVal < 1 || prop.ulVal > 256) return E_INVALIDARG; + delta = prop.ulVal; break; case NCoderPropID::kNumThreads: break; case NCoderPropID::kLevel: break; @@ -78,40 +77,39 @@ STDMETHODIMP CEncoder::SetCoderProperties(const PROPID *propIDs, const PROPVARIA return S_OK; } -STDMETHODIMP CEncoder::WriteCoderProperties(ISequentialOutStream *outStream) +Z7_COM7F_IMF(CEncoder::WriteCoderProperties(ISequentialOutStream *outStream)) { - Byte prop = (Byte)(_delta - 1); + const Byte prop = (Byte)(_delta - 1); return outStream->Write(&prop, 1, NULL); } #endif -class CDecoder: +class CDecoder Z7_final: public ICompressFilter, public ICompressSetDecoderProperties2, - CDelta, - public CMyUnknownImp + public CMyUnknownImp, + CDelta { -public: - MY_UNKNOWN_IMP2(ICompressFilter, ICompressSetDecoderProperties2) - INTERFACE_ICompressFilter(;) - STDMETHOD(SetDecoderProperties2)(const Byte *data, UInt32 size); + Z7_IFACES_IMP_UNK_2( + ICompressFilter, + ICompressSetDecoderProperties2) }; -STDMETHODIMP CDecoder::Init() +Z7_COM7F_IMF(CDecoder::Init()) { DeltaInit(); return S_OK; } -STDMETHODIMP_(UInt32) CDecoder::Filter(Byte *data, UInt32 size) +Z7_COM7F_IMF2(UInt32, CDecoder::Filter(Byte *data, UInt32 size)) { Delta_Decode(_state, _delta, data, size); return size; } -STDMETHODIMP CDecoder::SetDecoderProperties2(const Byte *props, UInt32 size) +Z7_COM7F_IMF(CDecoder::SetDecoderProperties2(const Byte *props, UInt32 size)) { if (size != 1) return E_INVALIDARG; diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/DllExports2Compress.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/DllExports2Compress.cpp index a6ff6905..f3b862d1 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/DllExports2Compress.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/DllExports2Compress.cpp @@ -8,6 +8,15 @@ #include "../Common/RegisterCodec.h" +extern "C" +BOOL WINAPI DllMain( + #ifdef UNDER_CE + HANDLE + #else + HINSTANCE + #endif + /* hInstance */, DWORD /* dwReason */, LPVOID /*lpReserved*/); + extern "C" BOOL WINAPI DllMain( #ifdef UNDER_CE @@ -22,6 +31,7 @@ BOOL WINAPI DllMain( STDAPI CreateCoder(const GUID *clsid, const GUID *iid, void **outObject); +STDAPI CreateObject(const GUID *clsid, const GUID *iid, void **outObject); STDAPI CreateObject(const GUID *clsid, const GUID *iid, void **outObject) { return CreateCoder(clsid, iid, outObject); diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/DllExportsCompress.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/DllExportsCompress.cpp index c58d2d5e..24749d27 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/DllExportsCompress.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/DllExportsCompress.cpp @@ -27,6 +27,16 @@ void RegisterHasher(const CHasherInfo *hashInfo) throw() } #ifdef _WIN32 + +extern "C" +BOOL WINAPI DllMain( + #ifdef UNDER_CE + HANDLE + #else + HINSTANCE + #endif + , DWORD /* dwReason */, LPVOID /*lpReserved*/); + extern "C" BOOL WINAPI DllMain( #ifdef UNDER_CE @@ -42,6 +52,7 @@ BOOL WINAPI DllMain( STDAPI CreateCoder(const GUID *clsid, const GUID *iid, void **outObject); +STDAPI CreateObject(const GUID *clsid, const GUID *iid, void **outObject); STDAPI CreateObject(const GUID *clsid, const GUID *iid, void **outObject) { return CreateCoder(clsid, iid, outObject); diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/HuffmanDecoder.h b/src/plugins/7zip/7za/cpp/7zip/Compress/HuffmanDecoder.h index 04364c14..318190d4 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/HuffmanDecoder.h +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/HuffmanDecoder.h @@ -1,256 +1,523 @@ // Compress/HuffmanDecoder.h -#ifndef __COMPRESS_HUFFMAN_DECODER_H -#define __COMPRESS_HUFFMAN_DECODER_H +#ifndef ZIP7_INC_COMPRESS_HUFFMAN_DECODER_H +#define ZIP7_INC_COMPRESS_HUFFMAN_DECODER_H +#include "../../../C/CpuArch.h" #include "../../Common/MyTypes.h" namespace NCompress { namespace NHuffman { -template -class CDecoder +// const unsigned kNumTableBits_Default = 9; + +#if 0 || 0 && defined(MY_CPU_64BIT) +// for debug or optimization: +// 64-BIT limit array can be faster for some compilers. +// for debug or optimization: +#define Z7_HUFF_USE_64BIT_LIMIT +#else +// sizet value variable allows to eliminate some move operation in some compilers. +// for debug or optimization: +// #define Z7_HUFF_USE_SIZET_VALUE +#endif + +// v0 must normalized to (32 bits) : (v0 < ((UInt64)1 << 32)) + +#ifdef Z7_HUFF_USE_64BIT_LIMIT + typedef UInt64 CLimitInt; + typedef UInt64 CValueInt; + // all _limits[*] are normalized and limited by ((UInt64)1 << 32). + // we don't use (v1) in this branch + #define Z7_HUFF_NUM_LIMIT_BITS(kNumBitsMax) 32 + #define Z7_HUFF_TABLE_COMPARE(huf, kNumTableBits, v0, v1) \ + ((NCompress::NHuffman::CLimitInt)v0 >= (huf)->_limits[0]) + #define Z7_HUFF_GET_VAL_FOR_LIMITS(v0, v1, kNumBitsMax, kNumTableBits) (v0) + #define Z7_HUFF_GET_VAL_FOR_TABLE( v0, v1, kNumBitsMax, kNumTableBits) ((v0) >> (32 - kNumTableBits)) + #define Z7_HUFF_PRECALC_V1(kNumTableBits, v0, v1) +#else + typedef UInt32 CLimitInt; + typedef + #ifdef Z7_HUFF_USE_SIZET_VALUE + size_t + #else + UInt32 + #endif + CValueInt; + // v1 must be precalculated from v0 in this branch + // _limits[0] and (v1) are normalized and limited by (1 << kNumTableBits). + // _limits[non_0] are normalized and limited by (1 << kNumBitsMax). + #define Z7_HUFF_NUM_LIMIT_BITS(kNumBitsMax) (kNumBitsMax) + #define Z7_HUFF_TABLE_COMPARE(huf, kNumTableBits, v0, v1) \ + ((NCompress::NHuffman::CLimitInt)v1 >= (huf)->_limits[0]) + #define Z7_HUFF_GET_VAL_FOR_LIMITS(v0, v1, kNumBitsMax, kNumTableBits) ((v0) >> (32 - kNumBitsMax)) + #define Z7_HUFF_GET_VAL_FOR_TABLE( v0, v1, kNumBitsMax, kNumTableBits) (v1) + #define Z7_HUFF_PRECALC_V1(kNumTableBits, v0, v1) const UInt32 v1 = ((v0) >> (32 - kNumTableBits)); +#endif + + +enum enum_BuildMode { - UInt32 _limits[kNumBitsMax + 2]; - UInt32 _poses[kNumBitsMax + 1]; - UInt16 _lens[1 << kNumTableBits]; - UInt16 _symbols[m_NumSymbols]; -public: + k_BuildMode_Partial = 0, + k_BuildMode_Full = 1, + k_BuildMode_Full_or_Empty = 2 +}; + - bool Build(const Byte *lens) throw() +template +struct CDecoderBase +{ + CLimitInt _limits[kNumBitsMax + 2 - kNumTableBits]; + UInt32 _poses[kNumBitsMax - kNumTableBits]; // unsigned +union +{ + // if defined(MY_CPU_64BIT), we need 64-bit alignment for _symbols. + // if !defined(MY_CPU_64BIT), we need 32-bit alignment for _symbols + // but we provide alignment for _lens. + // _symbols also will be aligned, if _lens are aligned + #if defined(MY_CPU_64BIT) + UInt64 + #else + UInt32 + #endif + _pad_align[m_NumSymbols < (1u << sizeof(symType) * 8) ? 1 : -1]; + /* if symType is Byte, we use 16-bytes padding to avoid cache + bank conflict between _lens and _symbols: */ + Byte _lens[(1 << kNumTableBits) + (sizeof(symType) == 1 ? 16 : 0)]; +} _u; + symType _symbols[(1 << kNumTableBits) + m_NumSymbols - (kNumTableBits + 1)]; + + /* + Z7_FORCE_INLINE + bool IsFull() const { - UInt32 lenCounts[kNumBitsMax + 1]; - UInt32 tmpPoses[kNumBitsMax + 1]; - - unsigned i; + return _limits[kNumBitsMax - kNumTableBits] == + (CLimitInt)1u << Z7_HUFF_NUM_LIMIT_BITS(kNumBitsMax); + } + Z7_FORCE_INLINE + bool IsEmpty() const + { + return _limits[kNumBitsMax - kNumTableBits] == 0; + } + Z7_FORCE_INLINE + bool Is_Full_or_Empty() const + { + return 0 == (_limits[kNumBitsMax - kNumTableBits] & + ~((CLimitInt)1 << Z7_HUFF_NUM_LIMIT_BITS(kNumBitsMax))); + } + */ + + Z7_FORCE_INLINE + bool Build(const Byte *lens, enum_BuildMode buidMode = k_BuildMode_Partial) throw() + { + unsigned counts[kNumBitsMax + 1]; + size_t i; for (i = 0; i <= kNumBitsMax; i++) - lenCounts[i] = 0; - - UInt32 sym; + counts[i] = 0; + for (i = 0; i < m_NumSymbols; i++) + counts[lens[i]]++; - for (sym = 0; sym < m_NumSymbols; sym++) - lenCounts[lens[sym]]++; - - lenCounts[0] = 0; - _poses[0] = 0; - _limits[0] = 0; - UInt32 startPos = 0; - const UInt32 kMaxValue = (UInt32)1 << kNumBitsMax; - - for (i = 1; i <= kNumBitsMax; i++) + UInt32 sum = 0; + for (i = 1; i <= kNumTableBits; i++) { - startPos += lenCounts[i] << (kNumBitsMax - i); - if (startPos > kMaxValue) - return false; - _limits[i] = startPos; - _poses[i] = _poses[i - 1] + lenCounts[i - 1]; - tmpPoses[i] = _poses[i]; + sum <<= 1; + sum += counts[i]; } - _limits[kNumBitsMax + 1] = kMaxValue; - - for (sym = 0; sym < m_NumSymbols; sym++) + CLimitInt startPos = (CLimitInt)sum; + _limits[0] = + #ifdef Z7_HUFF_USE_64BIT_LIMIT + startPos << (Z7_HUFF_NUM_LIMIT_BITS(kNumBitsMax) - kNumTableBits); + #else + startPos; + #endif + + for (i = kNumTableBits + 1; i <= kNumBitsMax; i++) { - unsigned len = lens[sym]; - if (len == 0) - continue; + startPos <<= 1; + _poses[i - (kNumTableBits + 1)] = (UInt32)(startPos - sum); + const unsigned cnt = counts[i]; + counts[i] = sum; + sum += cnt; + startPos += cnt; + _limits[i - kNumTableBits] = startPos << (Z7_HUFF_NUM_LIMIT_BITS(kNumBitsMax) - i); + } - unsigned offset = tmpPoses[len]; - _symbols[offset] = (UInt16)sym; - tmpPoses[len] = offset + 1; + _limits[kNumBitsMax + 1 - kNumTableBits] = + (CLimitInt)1 << Z7_HUFF_NUM_LIMIT_BITS(kNumBitsMax); - if (len <= kNumTableBits) - { - offset -= _poses[len]; - UInt32 num = (UInt32)1 << (kNumTableBits - len); - UInt16 val = (UInt16)((sym << 4) | len); - UInt16 *dest = _lens + (_limits[len - 1] >> (kNumBitsMax - kNumTableBits)) + (offset << (kNumTableBits - len)); - for (UInt32 k = 0; k < num; k++) - dest[k] = val; - } + if (buidMode == k_BuildMode_Partial) + { + if (startPos > (1u << kNumBitsMax)) return false; } - - return true; - } - - bool BuildFull(const Byte *lens, UInt32 numSymbols = m_NumSymbols) throw() - { - UInt32 lenCounts[kNumBitsMax + 1]; - UInt32 tmpPoses[kNumBitsMax + 1]; - - unsigned i; - for (i = 0; i <= kNumBitsMax; i++) - lenCounts[i] = 0; - - UInt32 sym; - - for (sym = 0; sym < numSymbols; sym++) - lenCounts[lens[sym]]++; - - lenCounts[0] = 0; - _poses[0] = 0; - _limits[0] = 0; - UInt32 startPos = 0; - const UInt32 kMaxValue = (UInt32)1 << kNumBitsMax; - - for (i = 1; i <= kNumBitsMax; i++) + else { - startPos += lenCounts[i] << (kNumBitsMax - i); - if (startPos > kMaxValue) - return false; - _limits[i] = startPos; - _poses[i] = _poses[i - 1] + lenCounts[i - 1]; - tmpPoses[i] = _poses[i]; + if (buidMode != k_BuildMode_Full && startPos == 0) return true; + if (startPos != (1u << kNumBitsMax)) return false; + } + size_t sum2 = 0; + for (i = 1; i <= kNumTableBits; i++) + { + const unsigned cnt = counts[i] << (kNumTableBits - i); + counts[i] = (unsigned)sum2 >> (kNumTableBits - i); + memset(_u._lens + sum2, (int)i, cnt); + sum2 += cnt; } - - _limits[kNumBitsMax + 1] = kMaxValue; - for (sym = 0; sym < numSymbols; sym++) +#ifdef MY_CPU_64BIT + symType4 + // UInt64 // for symType = UInt16 + // UInt32 // for symType = Byte +#else + UInt32 +#endif + v = 0; + for (i = 0; i < m_NumSymbols; i++, + v += + 1 + + ( (UInt32)1 << (sizeof(symType) * 8 * 1)) + // 0x00010001 // for symType = UInt16 + // 0x00000101 // for symType = Byte +#ifdef MY_CPU_64BIT + + ((symType4)1 << (sizeof(symType) * 8 * 2)) + + ((symType4)1 << (sizeof(symType) * 8 * 3)) + // 0x0001000100010001 // for symType = UInt16 + // 0x0000000001010101 // for symType = Byte +#endif + ) { - unsigned len = lens[sym]; + const unsigned len = lens[i]; if (len == 0) continue; - - unsigned offset = tmpPoses[len]; - _symbols[offset] = (UInt16)sym; - tmpPoses[len] = offset + 1; - - if (len <= kNumTableBits) + const size_t offset = counts[len]; + counts[len] = (unsigned)offset + 1; + if (len >= kNumTableBits) + _symbols[offset] = (symType)v; + else { - offset -= _poses[len]; - UInt32 num = (UInt32)1 << (kNumTableBits - len); - UInt16 val = (UInt16)((sym << 4) | len); - UInt16 *dest = _lens + (_limits[len - 1] >> (kNumBitsMax - kNumTableBits)) + (offset << (kNumTableBits - len)); - for (UInt32 k = 0; k < num; k++) - dest[k] = val; + Byte *s2 = (Byte *)(void *)_symbols + (offset << + (kNumTableBits + sizeof(symType) / 2 - len)); + Byte *lim = s2 + ((size_t)1 << + (kNumTableBits + sizeof(symType) / 2 - len)); + if (len >= kNumTableBits - 2) + { + *(symType2 *)(void *)(s2 ) = (symType2)v; + *(symType2 *)(void *)(lim - sizeof(symType) * 2) = (symType2)v; + } + else + { +#ifdef MY_CPU_64BIT + symType4 *s = (symType4 *)(void *)s2; + do + { + s[0] = v; s[1] = v; s += 2; + } + while (s != (const symType4 *)(const void *)lim); +#else + symType2 *s = (symType2 *)(void *)s2; + do + { + s[0] = (symType2)v; s[1] = (symType2)v; s += 2; + s[0] = (symType2)v; s[1] = (symType2)v; s += 2; + } + while (s != (const symType2 *)(const void *)lim); +#endif + } } } - - return startPos == kMaxValue; + return true; } + +#define Z7_HUFF_DECODE_ERROR_SYM_CHECK_YES(_numBits_, kNumBitsMax, error_op) if (_numBits_ > kNumBitsMax) { error_op } +#define Z7_HUFF_DECODE_ERROR_SYM_CHECK_NO( _numBits_, kNumBitsMax, error_op) + + +#define Z7_HUFF_DECODE_BASE_TREE_BRANCH(sym, huf, kNumBitsMax, kNumTableBits, \ + v0, v1, \ + get_val_for_limits, \ + check_op, error_op, _numBits_) \ +{ \ + const NHuffman::CValueInt _val = get_val_for_limits(v0, v1, kNumBitsMax, kNumTableBits); \ + _numBits_ = kNumTableBits + 1; \ + if ((NCompress::NHuffman::CLimitInt)_val >= (huf)->_limits[1]) \ + do { _numBits_++; } \ + while ((NCompress::NHuffman::CLimitInt)_val >= (huf)->_limits[_numBits_ - kNumTableBits]); \ + check_op(_numBits_, kNumBitsMax, error_op) \ + sym = (huf)->_symbols[(/* (UInt32) */ (_val >> ((Z7_HUFF_NUM_LIMIT_BITS(kNumBitsMax) - (unsigned)_numBits_)))) \ + - (huf)->_poses[_numBits_ - (kNumTableBits + 1)]]; \ +} + +/* + Z7_HUFF_DECODE_BASE_TREE_BRANCH(sym, huf, kNumBitsMax, kNumTableBits, \ + v0, v1, \ + get_val_for_limits, \ + check_op, error_op, _numBits_) \ + +*/ + +#define Z7_HUFF_DECODE_BASE(sym, huf, kNumBitsMax, kNumTableBits, \ + v0, v1, \ + get_val_for_table, get_val_for_limits, \ + check_op, error_op, move_pos_op, after_op, bs) \ +{ \ + if (Z7_HUFF_TABLE_COMPARE(huf, kNumTableBits, v0, v1)) \ + { \ + const NHuffman::CValueInt _val = get_val_for_limits(v0, v1, kNumBitsMax, kNumTableBits); \ + size_t _numBits_ = kNumTableBits + 1; \ + if ((NCompress::NHuffman::CLimitInt)_val >= (huf)->_limits[1]) \ + do { _numBits_++; } \ + while ((NCompress::NHuffman::CLimitInt)_val >= (huf)->_limits[_numBits_ - kNumTableBits]); \ + check_op(_numBits_, kNumBitsMax, error_op) \ + sym = (huf)->_symbols[(/* (UInt32) */ (_val >> ((Z7_HUFF_NUM_LIMIT_BITS(kNumBitsMax) - (unsigned)_numBits_)))) \ + - (huf)->_poses[_numBits_ - (kNumTableBits + 1)]]; \ + move_pos_op(bs, _numBits_); \ + } \ + else \ + { \ + const size_t _val = get_val_for_table(v0, v1, kNumBitsMax, kNumTableBits); \ + const size_t _numBits_ = (huf)->_u._lens[_val]; \ + sym = (huf)->_symbols[_val]; \ + move_pos_op(bs, _numBits_); \ + } \ + after_op \ +} + +#define Z7_HUFF_DECODE_10(sym, huf, kNumBitsMax, kNumTableBits, \ + v0, v1, \ + check_op, error_op, move_pos_op, after_op, bs) \ + Z7_HUFF_DECODE_BASE(sym, huf, kNumBitsMax, kNumTableBits, \ + v0, v1, \ + Z7_HUFF_GET_VAL_FOR_TABLE, \ + Z7_HUFF_GET_VAL_FOR_LIMITS, \ + check_op, error_op, move_pos_op, after_op, bs) \ + + +#define Z7_HUFF_DECODE_VAL_IN_HIGH32(sym, huf, kNumBitsMax, kNumTableBits, \ + v0, \ + check_op, error_op, move_pos_op, after_op, bs) \ +{ \ + Z7_HUFF_PRECALC_V1(kNumTableBits, v0, _v1_temp) \ + Z7_HUFF_DECODE_10(sym, huf, kNumBitsMax, kNumTableBits, \ + v0, _v1_temp, \ + check_op, error_op, move_pos_op, after_op, bs) \ +} + +#if 0 || defined(Z7_HUFF_USE_64BIT_LIMIT) +// this branch uses bitStream->GetValue_InHigh32bits(). +#define Z7_HUFF_DECODE_0(sym, huf, kNumBitsMax, kNumTableBits, bitStream, \ + check_op, error_op, move_pos_op) \ +{ \ + const UInt32 v0 = (bitStream)->GetValue_InHigh32bits(); \ + Z7_HUFF_PRECALC_V1(kNumTableBits, v0, v1); \ + Z7_HUFF_DECODE_BASE(sym, huf, kNumBitsMax, kNumTableBits, \ + v0, v1, \ + Z7_HUFF_GET_VAL_FOR_TABLE, \ + Z7_HUFF_GET_VAL_FOR_LIMITS, \ + check_op, error_op, move_pos_op, {}, bitStream) \ +} +#else +/* +this branch uses bitStream->GetValue(). +So we use SIMPLE versions for v0, v1 calculation: + v0 is normalized for kNumBitsMax + v1 is normalized for kNumTableBits +*/ +#define Z7_HUFF_GET_VAL_FOR_LIMITS_SIMPLE(v0, v1, kNumBitsMax, kNumTableBits) v0 +#define Z7_HUFF_GET_VAL_FOR_TABLE_SIMPLE( v0, v1, kNumBitsMax, kNumTableBits) v1 +#define Z7_HUFF_DECODE_0(sym, huf, kNumBitsMax, kNumTableBits, bitStream, check_op, error_op, move_pos_op) \ +{ \ + const UInt32 v0 = (bitStream)->GetValue(kNumBitsMax); \ + const UInt32 v1 = v0 >> (kNumBitsMax - kNumTableBits); \ + Z7_HUFF_DECODE_BASE(sym, huf, kNumBitsMax, kNumTableBits, \ + v0, v1, \ + Z7_HUFF_GET_VAL_FOR_TABLE_SIMPLE, \ + Z7_HUFF_GET_VAL_FOR_LIMITS_SIMPLE, \ + check_op, error_op, move_pos_op, {}, bitStream) \ +} +#endif + +#define Z7_HUFF_bitStream_MovePos(bitStream, numBits) (bitStream)->MovePos((unsigned)(numBits)) + +#define Z7_HUFF_DECODE_1(sym, huf, kNumBitsMax, kNumTableBits, bitStream, check_op, error_op) \ + Z7_HUFF_DECODE_0(sym, huf, kNumBitsMax, kNumTableBits, bitStream, check_op, error_op, \ + Z7_HUFF_bitStream_MovePos) + +// MovePosCheck + +#define Z7_HUFF_DECODE_2(sym, huf, kNumBitsMax, kNumTableBits, bitStream, check_op, error_op) \ + Z7_HUFF_DECODE_0(sym, huf, kNumBitsMax, kNumTableBits, bitStream, check_op, error_op, \ + Z7_HUFF_bitStream_MovePos) + +// MovePosCheck + +#define Z7_HUFF_DECODE_CHECK(sym, huf, kNumBitsMax, kNumTableBits, bitStream, error_op) \ + Z7_HUFF_DECODE_1( sym, huf, kNumBitsMax, kNumTableBits, bitStream, \ + Z7_HUFF_DECODE_ERROR_SYM_CHECK_YES, error_op) + template - UInt32 Decode(TBitDecoder *bitStream) const throw() + Z7_FORCE_INLINE + bool Decode2(TBitDecoder *bitStream, unsigned &sym) const { - UInt32 val = bitStream->GetValue(kNumBitsMax); - - if (val < _limits[kNumTableBits]) - { - UInt32 pair = _lens[val >> (kNumBitsMax - kNumTableBits)]; - bitStream->MovePos((unsigned)(pair & 0xF)); - return pair >> 4; - } + Z7_HUFF_DECODE_CHECK(sym, this, kNumBitsMax, kNumTableBits, bitStream, + { return false; } + ) + return true; + } - unsigned numBits; - for (numBits = kNumTableBits + 1; val >= _limits[numBits]; numBits++); - - if (numBits > kNumBitsMax) - return 0xFFFFFFFF; + template + Z7_FORCE_INLINE + bool Decode_SymCheck_MovePosCheck(TBitDecoder *bitStream, unsigned &sym) const + { + Z7_HUFF_DECODE_0(sym, this, kNumBitsMax, kNumTableBits, bitStream, + Z7_HUFF_DECODE_ERROR_SYM_CHECK_YES, + { return false; }, + { return (bitStream)->MovePosCheck; } + ) + } - bitStream->MovePos(numBits); - UInt32 index = _poses[numBits] + ((val - _limits[numBits - 1]) >> (kNumBitsMax - numBits)); - return _symbols[index]; + template + Z7_FORCE_INLINE + unsigned Decode(TBitDecoder *bitStream) const + { + unsigned sym; + Z7_HUFF_DECODE_CHECK(sym, this, kNumBitsMax, kNumTableBits, bitStream, + { return (unsigned)(int)(Int32)0xffffffff; } + ) + return sym; } + template - UInt32 DecodeFull(TBitDecoder *bitStream) const throw() + Z7_FORCE_INLINE + unsigned DecodeFull(TBitDecoder *bitStream) const { - UInt32 val = bitStream->GetValue(kNumBitsMax); - + /* + const UInt32 val = bitStream->GetValue(kNumBitsMax); if (val < _limits[kNumTableBits]) { - UInt32 pair = _lens[val >> (kNumBitsMax - kNumTableBits)]; - bitStream->MovePos((unsigned)(pair & 0xF)); - return pair >> 4; + const unsigned pair = _u._lens[(size_t)(val >> (kNumBitsMax - kNumTableBits))]; + bitStream->MovePos(pair & kPairLenMask); + return pair >> kNumPairLenBits; } unsigned numBits; for (numBits = kNumTableBits + 1; val >= _limits[numBits]; numBits++); bitStream->MovePos(numBits); - UInt32 index = _poses[numBits] + ((val - _limits[numBits - 1]) >> (kNumBitsMax - numBits)); - return _symbols[index]; + return _symbols[_poses[numBits] + (unsigned) + ((val - _limits[(size_t)numBits - 1]) >> (kNumBitsMax - numBits))]; + */ + unsigned sym; + Z7_HUFF_DECODE_2(sym, this, kNumBitsMax, kNumTableBits, bitStream, + Z7_HUFF_DECODE_ERROR_SYM_CHECK_NO, {} + ) + return sym; } }; +template +struct CDecoder: public CDecoderBase + {}; + +template +struct CDecoder256: public CDecoderBase + {}; -template + +template class CDecoder7b { - Byte _lens[1 << 7]; public: + Byte _lens[1 << 7]; - bool Build(const Byte *lens) throw() + bool Build(const Byte *lens, bool full) throw() { const unsigned kNumBitsMax = 7; - UInt32 lenCounts[kNumBitsMax + 1]; - UInt32 tmpPoses[kNumBitsMax + 1]; - UInt32 _poses[kNumBitsMax + 1]; - UInt32 _limits[kNumBitsMax + 1]; - + unsigned counts[kNumBitsMax + 1]; + unsigned _poses[kNumBitsMax + 1]; + unsigned _limits[kNumBitsMax + 1]; unsigned i; for (i = 0; i <= kNumBitsMax; i++) - lenCounts[i] = 0; - - UInt32 sym; - - for (sym = 0; sym < m_NumSymbols; sym++) - lenCounts[lens[sym]]++; + counts[i] = 0; + for (i = 0; i < numSymbols; i++) + counts[lens[i]]++; - lenCounts[0] = 0; - _poses[0] = 0; _limits[0] = 0; - UInt32 startPos = 0; - const UInt32 kMaxValue = (UInt32)1 << kNumBitsMax; + const unsigned kMaxValue = 1u << kNumBitsMax; + unsigned startPos = 0; + unsigned sum = 0; for (i = 1; i <= kNumBitsMax; i++) { - startPos += lenCounts[i] << (kNumBitsMax - i); + const unsigned cnt = counts[i]; + startPos += cnt << (kNumBitsMax - i); + _limits[i] = startPos; + counts[i] = sum; + _poses[i] = sum; + sum += cnt; + } + + counts[0] = sum; + _poses[0] = sum; + + if (full) + { + if (startPos != kMaxValue) + return false; + } + else + { if (startPos > kMaxValue) return false; - _limits[i] = startPos; - _poses[i] = _poses[i - 1] + lenCounts[i - 1]; - tmpPoses[i] = _poses[i]; } - for (sym = 0; sym < m_NumSymbols; sym++) + + for (i = 0; i < numSymbols; i++) { - unsigned len = lens[sym]; + const unsigned len = lens[i]; if (len == 0) continue; - - unsigned offset = tmpPoses[len]; - tmpPoses[len] = offset + 1; - + const unsigned offset = counts[len]++; { - offset -= _poses[len]; - UInt32 num = (UInt32)1 << (kNumBitsMax - len); - Byte val = (Byte)((sym << 3) | len); - Byte *dest = _lens + (_limits[len - 1]) + (offset << (kNumBitsMax - len)); - for (UInt32 k = 0; k < num; k++) - dest[k] = val; + Byte *dest = _lens + _limits[(size_t)len - 1] + + ((offset - _poses[len]) << (kNumBitsMax - len)); + const unsigned num = (unsigned)1 << (kNumBitsMax - len); + const unsigned val = (i << 3) + len; + for (unsigned k = 0; k < num; k++) + dest[k] = (Byte)val; } } + if (!full) { - UInt32 limit = _limits[kNumBitsMax]; - UInt32 num = ((UInt32)1 << kNumBitsMax) - limit; + const unsigned limit = _limits[kNumBitsMax]; + const unsigned num = ((unsigned)1 << kNumBitsMax) - limit; Byte *dest = _lens + limit; - for (UInt32 k = 0; k < num; k++) - dest[k] = (Byte)(0x1F << 3); + for (unsigned k = 0; k < num; k++) + dest[k] = (Byte) + // (0x1f << 3); + ((0x1f << 3) + 0x7); } return true; } +#define Z7_HUFF_DECODER_7B_DECODE(dest, huf, get_val, move_pos, bs) \ + { \ + const unsigned pair = huf->_lens[(size_t)get_val(7)]; \ + const unsigned numBits = pair & 0x7; \ + move_pos(bs, numBits); \ + dest = pair >> 3; \ + } + template - UInt32 Decode(TBitDecoder *bitStream) const throw() + unsigned Decode(TBitDecoder *bitStream) const { - UInt32 val = bitStream->GetValue(7); - UInt32 pair = _lens[val]; - bitStream->MovePos((unsigned)(pair & 0x7)); + const unsigned pair = _lens[(size_t)bitStream->GetValue(7)]; + bitStream->MovePos(pair & 0x7); return pair >> 3; } }; diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/ImplodeDecoder.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/ImplodeDecoder.cpp index e1d68a43..bcf5d4f6 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/ImplodeDecoder.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/ImplodeDecoder.cpp @@ -10,214 +10,240 @@ namespace NCompress { namespace NImplode { namespace NDecoder { -class CException +bool CHuffmanDecoder::Build(const Byte *lens, unsigned numSymbols) throw() { -public: - enum ECauseType + unsigned counts[kNumHuffmanBits + 1]; + unsigned i; + for (i = 0; i <= kNumHuffmanBits; i++) + counts[i] = 0; + for (i = 0; i < numSymbols; i++) + counts[lens[i]]++; + + const UInt32 kMaxValue = (UInt32)1 << kNumHuffmanBits; + // _limits[0] = kMaxValue; + UInt32 startPos = kMaxValue; + unsigned sum = 0; + + for (i = 1; i <= kNumHuffmanBits; i++) { - kData - } m_Cause; - CException(ECauseType cause): m_Cause(cause) {} -}; - -static const int kNumDistanceLowDirectBitsForBigDict = 7; -static const int kNumDistanceLowDirectBitsForSmallDict = 6; - -static const int kNumBitsInByte = 8; - -// static const int kLevelStructuresNumberFieldSize = kNumBitsInByte; -static const int kLevelStructuresNumberAdditionalValue = 1; - -static const int kNumLevelStructureLevelBits = 4; -static const int kLevelStructureLevelAdditionalValue = 1; - -static const int kNumLevelStructureRepNumberBits = 4; -static const int kLevelStructureRepNumberAdditionalValue = 1; + const unsigned cnt = counts[i]; + const UInt32 range = (UInt32)cnt << (kNumHuffmanBits - i); + if (startPos < range) + return false; + startPos -= range; + _limits[i] = startPos; + _poses[i] = sum; + sum += cnt; + counts[i] = sum; + } + // counts[0] += sum; + if (startPos != 0) + return false; + for (i = 0; i < numSymbols; i++) + { + const unsigned len = lens[i]; + if (len != 0) + _symbols[--counts[len]] = (Byte)i; + } + return true; +} -static const int kLiteralTableSize = (1 << kNumBitsInByte); -static const int kDistanceTableSize = 64; -static const int kLengthTableSize = 64; +unsigned CHuffmanDecoder::Decode(CInBit *inStream) const throw() +{ + const UInt32 val = inStream->GetValue(kNumHuffmanBits); + size_t numBits; + for (numBits = 1; val < _limits[numBits]; numBits++); + const unsigned sym = _symbols[_poses[numBits] + + (unsigned)((val - _limits[numBits]) >> (kNumHuffmanBits - numBits))]; + inStream->MovePos(numBits); + return sym; +} -static const UInt32 kHistorySize = - (1 << MyMax(kNumDistanceLowDirectBitsForBigDict, - kNumDistanceLowDirectBitsForSmallDict)) * - kDistanceTableSize; // = 8 KB; -static const int kNumAdditionalLengthBits = 8; -static const UInt32 kMatchMinLenWhenLiteralsOn = 3; -static const UInt32 kMatchMinLenWhenLiteralsOff = 2; +static const unsigned kNumLenDirectBits = 8; -static const UInt32 kMatchMinLenMax = MyMax(kMatchMinLenWhenLiteralsOn, - kMatchMinLenWhenLiteralsOff); // 3 +static const unsigned kNumDistDirectBitsSmall = 6; +static const unsigned kNumDistDirectBitsBig = 7; -// static const UInt32 kMatchMaxLenMax = kMatchMinLenMax + (kLengthTableSize - 1) + (1 << kNumAdditionalLengthBits) - 1; // or 2 +static const unsigned kLitTableSize = (1 << 8); +static const unsigned kDistTableSize = 64; +static const unsigned kLenTableSize = 64; -enum -{ - kMatchId = 0, - kLiteralId = 1 -}; +static const UInt32 kHistorySize = (1 << kNumDistDirectBitsBig) * kDistTableSize; // 8 KB CCoder::CCoder(): - m_LiteralDecoder(kLiteralTableSize), - m_LengthDecoder(kLengthTableSize), - m_DistanceDecoder(kDistanceTableSize) -{ -} - -/* -void CCoder::ReleaseStreams() -{ - m_OutWindowStream.ReleaseStream(); - m_InBitStream.ReleaseStream(); -} -*/ - -bool CCoder::ReadLevelItems(NImplode::NHuffman::CDecoder &decoder, - Byte *levels, int numLevelItems) -{ - int numCodedStructures = m_InBitStream.ReadBits(kNumBitsInByte) + - kLevelStructuresNumberAdditionalValue; - int currentIndex = 0; - for (int i = 0; i < numCodedStructures; i++) - { - int level = m_InBitStream.ReadBits(kNumLevelStructureLevelBits) + - kLevelStructureLevelAdditionalValue; - int rep = m_InBitStream.ReadBits(kNumLevelStructureRepNumberBits) + - kLevelStructureRepNumberAdditionalValue; - if (currentIndex + rep > numLevelItems) - throw CException(CException::kData); - for (int j = 0; j < rep; j++) - levels[currentIndex++] = (Byte)level; - } - if (currentIndex != numLevelItems) - return false; - return decoder.SetCodeLengths(levels); -} + _flags(0), + _fullStreamMode(false) +{} -bool CCoder::ReadTables(void) +bool CCoder::BuildHuff(CHuffmanDecoder &decoder, unsigned numSymbols) { - if (m_LiteralsOn) + Byte levels[kMaxHuffTableSize]; + unsigned numRecords = (unsigned)_inBitStream.ReadAlignedByte() + 1; + unsigned index = 0; + do { - Byte literalLevels[kLiteralTableSize]; - if (!ReadLevelItems(m_LiteralDecoder, literalLevels, kLiteralTableSize)) + const unsigned b = (unsigned)_inBitStream.ReadAlignedByte(); + const unsigned level = (b & 0xF) + 1; + const unsigned rep = ((unsigned)b >> 4) + 1; + if (index + rep > numSymbols) return false; + for (unsigned j = 0; j < rep; j++) + levels[index++] = (Byte)level; } + while (--numRecords); - Byte lengthLevels[kLengthTableSize]; - if (!ReadLevelItems(m_LengthDecoder, lengthLevels, kLengthTableSize)) + if (index != numSymbols) return false; - - Byte distanceLevels[kDistanceTableSize]; - return ReadLevelItems(m_DistanceDecoder, distanceLevels, kDistanceTableSize); + return decoder.Build(levels, numSymbols); } -/* -class CCoderReleaser -{ - CCoder *m_Coder; -public: - CCoderReleaser(CCoder *coder): m_Coder(coder) {} - ~CCoderReleaser() { m_Coder->ReleaseStreams(); } -}; -*/ HRESULT CCoder::CodeReal(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 * /* inSize */, const UInt64 *outSize, ICompressProgressInfo *progress) + const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress) { - if (!m_InBitStream.Create(1 << 20)) + if (!_inBitStream.Create(1 << 18)) return E_OUTOFMEMORY; - if (!m_OutWindowStream.Create(kHistorySize)) + if (!_outWindowStream.Create(kHistorySize << 1)) // 16 KB return E_OUTOFMEMORY; - if (outSize == NULL) + if (!outSize) return E_INVALIDARG; - UInt64 pos = 0, unPackSize = *outSize; - m_OutWindowStream.SetStream(outStream); - m_OutWindowStream.Init(false); - m_InBitStream.SetStream(inStream); - m_InBitStream.Init(); - // CCoderReleaser coderReleaser(this); - - if (!ReadTables()) + _outWindowStream.SetStream(outStream); + _outWindowStream.Init(false); + _inBitStream.SetStream(inStream); + _inBitStream.Init(); + + const unsigned numDistDirectBits = (_flags & 2) ? + kNumDistDirectBitsBig: + kNumDistDirectBitsSmall; + const bool literalsOn = ((_flags & 4) != 0); + const UInt32 minMatchLen = (literalsOn ? 3 : 2); + + if (literalsOn) + if (!BuildHuff(_litDecoder, kLitTableSize)) + return S_FALSE; + if (!BuildHuff(_lenDecoder, kLenTableSize)) + return S_FALSE; + if (!BuildHuff(_distDecoder, kDistTableSize)) return S_FALSE; - + + UInt64 prevProgress = 0; + bool moreOut = false; + UInt64 pos = 0, unPackSize = *outSize; + while (pos < unPackSize) { - if (progress != NULL && pos % (1 << 16) == 0) + if (pos - prevProgress >= (1u << 18) && progress) { - UInt64 packSize = m_InBitStream.GetProcessedSize(); - RINOK(progress->SetRatioInfo(&packSize, &pos)); + prevProgress = pos; + const UInt64 packSize = _inBitStream.GetProcessedSize(); + RINOK(progress->SetRatioInfo(&packSize, &pos)) } - if (m_InBitStream.ReadBits(1) == kMatchId) // match + + if (_inBitStream.ReadBits(1) != 0) { - UInt32 lowDistBits = m_InBitStream.ReadBits(m_NumDistanceLowDirectBits); - UInt32 distance = m_DistanceDecoder.DecodeSymbol(&m_InBitStream); - if (distance >= kDistanceTableSize) - return S_FALSE; - distance = (distance << m_NumDistanceLowDirectBits) + lowDistBits; - UInt32 lengthSymbol = m_LengthDecoder.DecodeSymbol(&m_InBitStream); - if (lengthSymbol >= kLengthTableSize) - return S_FALSE; - UInt32 length = lengthSymbol + m_MinMatchLength; - if (lengthSymbol == kLengthTableSize - 1) // special symbol = 63 - length += m_InBitStream.ReadBits(kNumAdditionalLengthBits); - while (distance >= pos && length > 0) + Byte b; + if (literalsOn) { - m_OutWindowStream.PutByte(0); - pos++; - length--; + const unsigned sym = _litDecoder.Decode(&_inBitStream); + // if (sym >= kLitTableSize) break; + b = (Byte)sym; } - if (length > 0) - m_OutWindowStream.CopyBlock(distance, length); - pos += length; + else + b = (Byte)_inBitStream.ReadBits(8); + _outWindowStream.PutByte(b); + pos++; } else { - Byte b; - if (m_LiteralsOn) + const UInt32 lowDistBits = _inBitStream.ReadBits(numDistDirectBits); + UInt32 dist = (UInt32)_distDecoder.Decode(&_inBitStream); + // if (dist >= kDistTableSize) break; + dist = (dist << numDistDirectBits) + lowDistBits; + unsigned len = _lenDecoder.Decode(&_inBitStream); + // if (len >= kLenTableSize) break; + if (len == kLenTableSize - 1) + len += _inBitStream.ReadBits(kNumLenDirectBits); + len += minMatchLen; { - UInt32 temp = m_LiteralDecoder.DecodeSymbol(&m_InBitStream); - if (temp >= kLiteralTableSize) - return S_FALSE; - b = (Byte)temp; + const UInt64 limit = unPackSize - pos; + // limit != 0 + if (len > limit) + { + moreOut = true; + len = (UInt32)limit; + } } - else - b = (Byte)m_InBitStream.ReadBits(kNumBitsInByte); - m_OutWindowStream.PutByte(b); - pos++; + do + { + // len != 0 + if (dist < pos) + { + _outWindowStream.CopyBlock(dist, len); + pos += len; + break; + } + _outWindowStream.PutByte(0); + pos++; + } + while (--len); } } - if (pos > unPackSize) - return S_FALSE; - return m_OutWindowStream.Flush(); + + HRESULT res = _outWindowStream.Flush(); + + if (res == S_OK) + { + if (_fullStreamMode) + { + if (moreOut) + res = S_FALSE; + if (inSize && *inSize != _inBitStream.GetProcessedSize()) + res = S_FALSE; + } + if (pos != unPackSize) + res = S_FALSE; + } + + return res; } -STDMETHODIMP CCoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress) + +Z7_COM7F_IMF(CCoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, + const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress)) { try { return CodeReal(inStream, outStream, inSize, outSize, progress); } - catch(const CLzOutWindowException &e) { return e.ErrorCode; } + // catch(const CInBufferException &e) { return e.ErrorCode; } + // catch(const CLzOutWindowException &e) { return e.ErrorCode; } + catch(const CSystemException &e) { return e.ErrorCode; } catch(...) { return S_FALSE; } } -STDMETHODIMP CCoder::SetDecoderProperties2(const Byte *data, UInt32 size) + +Z7_COM7F_IMF(CCoder::SetDecoderProperties2(const Byte *data, UInt32 size)) { - if (size < 1) - return E_INVALIDARG; - Byte flag = data[0]; - m_BigDictionaryOn = ((flag & 2) != 0); - m_NumDistanceLowDirectBits = m_BigDictionaryOn ? - kNumDistanceLowDirectBitsForBigDict: - kNumDistanceLowDirectBitsForSmallDict; - m_LiteralsOn = ((flag & 4) != 0); - m_MinMatchLength = m_LiteralsOn ? - kMatchMinLenWhenLiteralsOn : - kMatchMinLenWhenLiteralsOff; + if (size == 0) + return E_NOTIMPL; + _flags = data[0]; + return S_OK; +} + + +Z7_COM7F_IMF(CCoder::SetFinishMode(UInt32 finishMode)) +{ + _fullStreamMode = (finishMode != 0); + return S_OK; +} + + +Z7_COM7F_IMF(CCoder::GetInStreamProcessedSize(UInt64 *value)) +{ + *value = _inBitStream.GetProcessedSize(); return S_OK; } diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/ImplodeDecoder.h b/src/plugins/7zip/7za/cpp/7zip/Compress/ImplodeDecoder.h index 5876ecac..60325150 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/ImplodeDecoder.h +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/ImplodeDecoder.h @@ -1,54 +1,59 @@ // ImplodeDecoder.h -#ifndef __COMPRESS_IMPLODE_DECODER_H -#define __COMPRESS_IMPLODE_DECODER_H +#ifndef ZIP7_INC_COMPRESS_IMPLODE_DECODER_H +#define ZIP7_INC_COMPRESS_IMPLODE_DECODER_H #include "../../Common/MyCom.h" #include "../ICoder.h" -#include "ImplodeHuffmanDecoder.h" +#include "../Common/InBuffer.h" + +#include "BitlDecoder.h" #include "LzOutWindow.h" namespace NCompress { namespace NImplode { namespace NDecoder { -class CCoder: - public ICompressCoder, - public ICompressSetDecoderProperties2, - public CMyUnknownImp -{ - CLzOutWindow m_OutWindowStream; - NBitl::CDecoder m_InBitStream; - - NImplode::NHuffman::CDecoder m_LiteralDecoder; - NImplode::NHuffman::CDecoder m_LengthDecoder; - NImplode::NHuffman::CDecoder m_DistanceDecoder; - - bool m_BigDictionaryOn; - bool m_LiteralsOn; +typedef NBitl::CDecoder CInBit; - int m_NumDistanceLowDirectBits; - UInt32 m_MinMatchLength; +const unsigned kNumHuffmanBits = 16; +const unsigned kMaxHuffTableSize = 1 << 8; - bool ReadLevelItems(NImplode::NHuffman::CDecoder &table, Byte *levels, int numLevelItems); - bool ReadTables(); - void DeCodeLevelTable(Byte *newLevels, int numLevels); +class CHuffmanDecoder +{ + UInt32 _limits[kNumHuffmanBits + 1]; + UInt32 _poses[kNumHuffmanBits + 1]; + Byte _symbols[kMaxHuffTableSize]; public: - CCoder(); + bool Build(const Byte *lens, unsigned numSymbols) throw(); + unsigned Decode(CInBit *inStream) const throw(); +}; - MY_UNKNOWN_IMP1(ICompressSetDecoderProperties2) - // void ReleaseStreams(); +Z7_CLASS_IMP_NOQIB_4( + CCoder + , ICompressCoder + , ICompressSetDecoderProperties2 + , ICompressSetFinishMode + , ICompressGetInStreamProcessedSize +) + Byte _flags; + bool _fullStreamMode; + + CLzOutWindow _outWindowStream; + CInBit _inBitStream; - HRESULT CodeReal(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress); + CHuffmanDecoder _litDecoder; + CHuffmanDecoder _lenDecoder; + CHuffmanDecoder _distDecoder; - STDMETHOD(Code)(ISequentialInStream *inStream, ISequentialOutStream *outStream, + bool BuildHuff(CHuffmanDecoder &table, unsigned numSymbols); + HRESULT CodeReal(ISequentialInStream *inStream, ISequentialOutStream *outStream, const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress); - - STDMETHOD(SetDecoderProperties2)(const Byte *data, UInt32 size); +public: + CCoder(); }; }}} diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/ImplodeHuffmanDecoder.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/ImplodeHuffmanDecoder.cpp index d385d7b1..7d31bb94 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/ImplodeHuffmanDecoder.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/ImplodeHuffmanDecoder.cpp @@ -1,89 +1,3 @@ // ImplodeHuffmanDecoder.cpp #include "StdAfx.h" - -#include "ImplodeHuffmanDecoder.h" - -namespace NCompress { -namespace NImplode { -namespace NHuffman { - -CDecoder::CDecoder(UInt32 numSymbols): - m_NumSymbols(numSymbols) -{ - m_Symbols = new UInt32[m_NumSymbols]; -} - -CDecoder::~CDecoder() -{ - delete []m_Symbols; -} - -bool CDecoder::SetCodeLengths(const Byte *codeLengths) -{ - // unsigned lenCounts[kNumBitsInLongestCode + 1], tmpPositions[kNumBitsInLongestCode + 1]; - unsigned lenCounts[kNumBitsInLongestCode + 2], tmpPositions[kNumBitsInLongestCode + 1]; - unsigned i; - for (i = 0; i <= kNumBitsInLongestCode; i++) - lenCounts[i] = 0; - UInt32 symbolIndex; - for (symbolIndex = 0; symbolIndex < m_NumSymbols; symbolIndex++) - lenCounts[codeLengths[symbolIndex]]++; - // lenCounts[0] = 0; - - // tmpPositions[0] = m_Positions[0] = m_Limitits[0] = 0; - m_Limitits[kNumBitsInLongestCode + 1] = 0; - m_Positions[kNumBitsInLongestCode + 1] = 0; - lenCounts[kNumBitsInLongestCode + 1] = 0; - - - UInt32 startPos = 0; - static const UInt32 kMaxValue = (1 << kNumBitsInLongestCode); - - for (i = kNumBitsInLongestCode; i > 0; i--) - { - startPos += lenCounts[i] << (kNumBitsInLongestCode - i); - if (startPos > kMaxValue) - return false; - m_Limitits[i] = startPos; - m_Positions[i] = m_Positions[i + 1] + lenCounts[i + 1]; - tmpPositions[i] = m_Positions[i] + lenCounts[i]; - - } - - // if _ZIP_MODE do not throw exception for trees containing only one node - // #ifndef _ZIP_MODE - if (startPos != kMaxValue) - return false; - // #endif - - for (symbolIndex = 0; symbolIndex < m_NumSymbols; symbolIndex++) - if (codeLengths[symbolIndex] != 0) - m_Symbols[--tmpPositions[codeLengths[symbolIndex]]] = symbolIndex; - return true; -} - -UInt32 CDecoder::DecodeSymbol(CInBit *inStream) -{ - UInt32 numBits = 0; - UInt32 value = inStream->GetValue(kNumBitsInLongestCode); - unsigned i; - for (i = kNumBitsInLongestCode; i > 0; i--) - { - if (value < m_Limitits[i]) - { - numBits = i; - break; - } - } - if (i == 0) - return 0xFFFFFFFF; - inStream->MovePos(numBits); - UInt32 index = m_Positions[numBits] + - ((value - m_Limitits[numBits + 1]) >> (kNumBitsInLongestCode - numBits)); - if (index >= m_NumSymbols) - return 0xFFFFFFFF; - return m_Symbols[index]; -} - -}}} diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/ImplodeHuffmanDecoder.h b/src/plugins/7zip/7za/cpp/7zip/Compress/ImplodeHuffmanDecoder.h index 691a8e93..dcf8ac6b 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/ImplodeHuffmanDecoder.h +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/ImplodeHuffmanDecoder.h @@ -1,34 +1,6 @@ // ImplodeHuffmanDecoder.h -#ifndef __IMPLODE_HUFFMAN_DECODER_H -#define __IMPLODE_HUFFMAN_DECODER_H - -#include "../Common/InBuffer.h" - -#include "BitlDecoder.h" - -namespace NCompress { -namespace NImplode { -namespace NHuffman { - -const unsigned kNumBitsInLongestCode = 16; - -typedef NBitl::CDecoder CInBit; - -class CDecoder -{ - UInt32 m_Limitits[kNumBitsInLongestCode + 2]; // m_Limitits[i] = value limit for symbols with length = i - UInt32 m_Positions[kNumBitsInLongestCode + 2]; // m_Positions[i] = index in m_Symbols[] of first symbol with length = i - UInt32 m_NumSymbols; // number of symbols in m_Symbols - UInt32 *m_Symbols; // symbols: at first with len=1 then 2, ... 15. -public: - CDecoder(UInt32 numSymbols); - ~CDecoder(); - - bool SetCodeLengths(const Byte *codeLengths); - UInt32 DecodeSymbol(CInBit *inStream); -}; - -}}} +#ifndef ZIP7_INC_IMPLODE_HUFFMAN_DECODER_H +#define ZIP7_INC_IMPLODE_HUFFMAN_DECODER_H #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/LzOutWindow.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/LzOutWindow.cpp index eb346407..aae02eb8 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/LzOutWindow.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/LzOutWindow.cpp @@ -8,7 +8,7 @@ void CLzOutWindow::Init(bool solid) throw() { if (!solid) COutBuffer::Init(); - #ifdef _NO_EXCEPTIONS + #ifdef Z7_NO_EXCEPTIONS ErrorCode = S_OK; #endif } diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/LzOutWindow.h b/src/plugins/7zip/7za/cpp/7zip/Compress/LzOutWindow.h index 5591744d..599b124e 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/LzOutWindow.h +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/LzOutWindow.h @@ -1,11 +1,11 @@ // LzOutWindow.h -#ifndef __LZ_OUT_WINDOW_H -#define __LZ_OUT_WINDOW_H +#ifndef ZIP7_INC_LZ_OUT_WINDOW_H +#define ZIP7_INC_LZ_OUT_WINDOW_H #include "../Common/OutBuffer.h" -#ifndef _NO_EXCEPTIONS +#ifndef Z7_NO_EXCEPTIONS typedef COutBufferException CLzOutWindowException; #endif @@ -56,6 +56,39 @@ class CLzOutWindow: public COutBuffer if (pos == _limitPos) FlushWithCheck(); } + + void PutBytes(const Byte *data, UInt32 size) + { + if (size == 0) + return; + UInt32 pos = _pos; + Byte *buf = _buf; + buf[pos++] = *data++; + size--; + for (;;) + { + UInt32 limitPos = _limitPos; + UInt32 rem = limitPos - pos; + if (rem == 0) + { + _pos = pos; + FlushWithCheck(); + pos = _pos; + continue; + } + + if (size == 0) + break; + + if (rem > size) + rem = size; + size -= rem; + do + buf[pos++] = *data++; + while (--rem); + } + _pos = pos; + } Byte GetByte(UInt32 distance) const { diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/LzfseDecoder.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/LzfseDecoder.cpp new file mode 100644 index 00000000..b224a339 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/LzfseDecoder.cpp @@ -0,0 +1,950 @@ +// LzfseDecoder.cpp + +/* +This code implements LZFSE data decompressing. +The code from "LZFSE compression library" was used. + +2018 : Igor Pavlov : BSD 3-clause License : the code in this file +2015-2017 : Apple Inc : BSD 3-clause License : original "LZFSE compression library" code + +The code in the "LZFSE compression library" is licensed under the "BSD 3-clause License": +---- +Copyright (c) 2015-2016, Apple Inc. All rights reserved. + +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 copyright holder(s) nor the names of any contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 +COPYRIGHT OWNER 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 "StdAfx.h" + +// #define SHOW_DEBUG_INFO + +#ifdef SHOW_DEBUG_INFO +#include +#endif + +#ifdef SHOW_DEBUG_INFO +#define PRF(x) x +#else +#define PRF(x) +#endif + +#include "../../../C/CpuArch.h" + +#include "LzfseDecoder.h" + +namespace NCompress { +namespace NLzfse { + +static const Byte kSignature_LZFSE_V1 = 0x31; // '1' +static const Byte kSignature_LZFSE_V2 = 0x32; // '2' + + +HRESULT CDecoder::GetUInt32(UInt32 &val) +{ + Byte b[4]; + for (unsigned i = 0; i < 4; i++) + if (!m_InStream.ReadByte(b[i])) + return S_FALSE; + val = GetUi32(b); + return S_OK; +} + + + +HRESULT CDecoder::DecodeUncompressed(UInt32 unpackSize) +{ + PRF(printf("\nUncompressed %7u\n", unpackSize)); + + const unsigned kBufSize = 1 << 8; + Byte buf[kBufSize]; + for (;;) + { + if (unpackSize == 0) + return S_OK; + UInt32 cur = unpackSize; + if (cur > kBufSize) + cur = kBufSize; + const UInt32 cur2 = (UInt32)m_InStream.ReadBytes(buf, cur); + m_OutWindowStream.PutBytes(buf, cur2); + if (cur != cur2) + return S_FALSE; + } +} + + + +HRESULT CDecoder::DecodeLzvn(UInt32 unpackSize, UInt32 packSize) +{ + PRF(printf("\nLZVN 0x%07x 0x%07x\n", unpackSize, packSize)); + + UInt32 D = 0; + + for (;;) + { + if (packSize == 0) + return S_FALSE; + Byte b; + if (!m_InStream.ReadByte(b)) + return S_FALSE; + packSize--; + + UInt32 M; + UInt32 L; + + if (b >= 0xE0) + { + /* + large L - 11100000 LLLLLLLL + small L - 1110LLLL + + large Rep - 11110000 MMMMMMMM + small Rep - 1111MMMM + */ + + M = b & 0xF; + if (M == 0) + { + if (packSize == 0) + return S_FALSE; + Byte b1; + if (!m_InStream.ReadByte(b1)) + return S_FALSE; + packSize--; + M = (UInt32)b1 + 16; + } + L = 0; + if ((b & 0x10) == 0) + { + // Literals only + L = M; + M = 0; + } + } + + // ERROR codes + else if ((b & 0xF0) == 0x70) // 0111xxxx + return S_FALSE; + else if ((b & 0xF0) == 0xD0) // 1101xxxx + return S_FALSE; + + else + { + if ((b & 0xE0) == 0xA0) + { + // medium - 101LLMMM DDDDDDMM DDDDDDDD + if (packSize < 2) + return S_FALSE; + Byte b1; + if (!m_InStream.ReadByte(b1)) + return S_FALSE; + packSize--; + + Byte b2; + if (!m_InStream.ReadByte(b2)) + return S_FALSE; + packSize--; + L = (((UInt32)b >> 3) & 3); + M = (((UInt32)b & 7) << 2) + (b1 & 3); + D = ((UInt32)b1 >> 2) + ((UInt32)b2 << 6); + } + else + { + L = (UInt32)b >> 6; + M = ((UInt32)b >> 3) & 7; + if ((b & 0x7) == 6) + { + // REP - LLMMM110 + if (L == 0) + { + // spec + if (M == 0) + break; // EOS + if (M <= 2) + continue; // NOP + return S_FALSE; // UNDEFINED + } + } + else + { + if (packSize == 0) + return S_FALSE; + Byte b1; + if (!m_InStream.ReadByte(b1)) + return S_FALSE; + packSize--; + + // large - LLMMM111 DDDDDDDD DDDDDDDD + // small - LLMMMDDD DDDDDDDD + D = ((UInt32)b & 7); + if (D == 7) + { + if (packSize == 0) + return S_FALSE; + Byte b2; + if (!m_InStream.ReadByte(b2)) + return S_FALSE; + packSize--; + D = b2; + } + D = (D << 8) + b1; + } + } + + M += 3; + } + { + for (unsigned i = 0; i < L; i++) + { + if (packSize == 0 || unpackSize == 0) + return S_FALSE; + Byte b1; + if (!m_InStream.ReadByte(b1)) + return S_FALSE; + packSize--; + m_OutWindowStream.PutByte(b1); + unpackSize--; + } + } + + if (M != 0) + { + if (unpackSize == 0 || D == 0) + return S_FALSE; + unsigned cur = M; + if (cur > unpackSize) + cur = (unsigned)unpackSize; + if (!m_OutWindowStream.CopyBlock(D - 1, cur)) + return S_FALSE; + unpackSize -= cur; + if (cur != M) + return S_FALSE; + } + } + + if (unpackSize != 0) + return S_FALSE; + + // LZVN encoder writes 7 additional zero bytes + if (packSize < 7) + return S_FALSE; + for (unsigned i = 0; i < 7; i++) + { + Byte b; + if (!m_InStream.ReadByte(b)) + return S_FALSE; + if (b != 0) + return S_FALSE; + } + packSize -= 7; + if (packSize) + { + PRF(printf("packSize after unused = %u\n", packSize)); + // if (packSize <= 0x100) { Byte buf[0x100]; m_InStream.ReadBytes(buf, packSize); } + /* Lzvn block that is used in HFS can contain junk data + (at least 256 bytes) after payload data. Why? + We ignore that junk data, if it's HFS (LzvnMode) mode. */ + if (!LzvnMode) + return S_FALSE; + } + return S_OK; +} + + + +// ---------- LZFSE ---------- + +#define MATCHES_PER_BLOCK 10000 +#define LITERALS_PER_BLOCK (4 * MATCHES_PER_BLOCK) + +#define NUM_L_SYMBOLS 20 +#define NUM_M_SYMBOLS 20 +#define NUM_D_SYMBOLS 64 +#define NUM_LIT_SYMBOLS 256 + +#define NUM_SYMBOLS ( \ + NUM_L_SYMBOLS + \ + NUM_M_SYMBOLS + \ + NUM_D_SYMBOLS + \ + NUM_LIT_SYMBOLS) + +#define NUM_L_STATES (1 << 6) +#define NUM_M_STATES (1 << 6) +#define NUM_D_STATES (1 << 8) +#define NUM_LIT_STATES (1 << 10) + + +typedef UInt32 CFseState; + + +static UInt32 SumFreqs(const UInt16 *freqs, unsigned num) +{ + UInt32 sum = 0; + for (unsigned i = 0; i < num; i++) + sum += (UInt32)freqs[i]; + return sum; +} + + +static Z7_FORCE_INLINE unsigned CountZeroBits(UInt32 val, UInt32 mask) +{ + for (unsigned i = 0;;) + { + if (val & mask) + return i; + i++; + mask >>= 1; + } +} + + +static Z7_FORCE_INLINE void InitLitTable(const UInt16 *freqs, UInt32 *table) +{ + for (unsigned i = 0; i < NUM_LIT_SYMBOLS; i++) + { + unsigned f = freqs[i]; + if (f == 0) + continue; + + // 0 < f <= numStates + // 0 <= k <= numStatesLog + // numStates <= (f<> k) - f; + + /* + CEntry + { + Byte k; + Byte symbol; + UInt16 delta; + }; + */ + + UInt32 e = ((UInt32)i << 8) + k; + k += 16; + UInt32 d = e + ((UInt32)f << k) - ((UInt32)NUM_LIT_STATES << 16); + UInt32 step = (UInt32)1 << k; + + unsigned j = 0; + do + { + *table++ = d; + d += step; + } + while (++j < j0); + + e--; + step >>= 1; + + for (j = j0; j < f; j++) + { + *table++ = e; + e += step; + } + } +} + + +typedef struct +{ + Byte totalBits; + Byte extraBits; + UInt16 delta; + UInt32 vbase; +} CExtraEntry; + + +static void InitExtraDecoderTable(unsigned numStates, + unsigned numSymbols, + const UInt16 *freqs, + const Byte *vbits, + CExtraEntry *table) +{ + UInt32 vbase = 0; + + for (unsigned i = 0; i < numSymbols; i++) + { + unsigned f = freqs[i]; + unsigned extraBits = vbits[i]; + + if (f != 0) + { + unsigned k = CountZeroBits(f, numStates); + unsigned j0 = ((2 * numStates) >> k) - f; + + unsigned j = 0; + do + { + CExtraEntry *e = table++; + e->totalBits = (Byte)(k + extraBits); + e->extraBits = (Byte)extraBits; + e->delta = (UInt16)(((f + j) << k) - numStates); + e->vbase = vbase; + } + while (++j < j0); + + f -= j0; + k--; + + for (j = 0; j < f; j++) + { + CExtraEntry *e = table++; + e->totalBits = (Byte)(k + extraBits); + e->extraBits = (Byte)extraBits; + e->delta = (UInt16)(j << k); + e->vbase = vbase; + } + } + + vbase += ((UInt32)1 << extraBits); + } +} + + +static const Byte k_L_extra[NUM_L_SYMBOLS] = +{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 5, 8 +}; + +static const Byte k_M_extra[NUM_M_SYMBOLS] = +{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 5, 8, 11 +}; + +static const Byte k_D_extra[NUM_D_SYMBOLS] = +{ + 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, + 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, + 8, 8, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 11, + 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15 +}; + + + +// ---------- CBitStream ---------- + +typedef struct +{ + UInt32 accum; + unsigned numBits; // [0, 31] - Number of valid bits in (accum), other bits are 0 +} CBitStream; + + +static Z7_FORCE_INLINE int FseInStream_Init(CBitStream *s, + int n, // [-7, 0], (-n == number_of_unused_bits) in last byte + const Byte **pbuf) +{ + *pbuf -= 4; + s->accum = GetUi32(*pbuf); + if (n) + { + s->numBits = (unsigned)(n + 32); + if ((s->accum >> s->numBits) != 0) + return -1; // ERROR, encoder should have zeroed the upper bits + } + else + { + *pbuf += 1; + s->accum >>= 8; + s->numBits = 24; + } + return 0; // OK +} + + +// 0 <= numBits < 32 +#define mask31(x, numBits) ((x) & (((UInt32)1 << (numBits)) - 1)) + +#define FseInStream_FLUSH \ + { const unsigned nbits = (31 - in.numBits) & (unsigned)-8; \ + if (nbits) { \ + buf -= (nbits >> 3); \ + if (buf < buf_check) return S_FALSE; \ + UInt32 v = GetUi32(buf); \ + in.accum = (in.accum << nbits) | mask31(v, nbits); \ + in.numBits += nbits; }} + + + +static Z7_FORCE_INLINE UInt32 BitStream_Pull(CBitStream *s, unsigned numBits) +{ + s->numBits -= numBits; + const UInt32 v = s->accum >> s->numBits; + s->accum = mask31(s->accum, s->numBits); + return v; +} + + +#define DECODE_LIT(dest, pstate) { \ + UInt32 e = lit_decoder[pstate]; \ + pstate = (CFseState)((e >> 16) + BitStream_Pull(&in, e & 0xff)); \ + dest = (Byte)(e >> 8); } + + +static Z7_FORCE_INLINE UInt32 FseDecodeExtra(CFseState *pstate, + const CExtraEntry *table, + CBitStream *s) +{ + const CExtraEntry *e = &table[*pstate]; + UInt32 v = BitStream_Pull(s, e->totalBits); + unsigned extraBits = e->extraBits; + *pstate = (CFseState)(e->delta + (v >> extraBits)); + return e->vbase + mask31(v, extraBits); +} + + +#define freqs_L (freqs) +#define freqs_M (freqs_L + NUM_L_SYMBOLS) +#define freqs_D (freqs_M + NUM_M_SYMBOLS) +#define freqs_LIT (freqs_D + NUM_D_SYMBOLS) + +#define GET_BITS_64(v, offset, num, dest) dest = (UInt32) ((v >> (offset)) & ((1 << (num)) - 1)); +#define GET_BITS_64_Int32(v, offset, num, dest) dest = (Int32)((v >> (offset)) & ((1 << (num)) - 1)); +#define GET_BITS_32(v, offset, num, dest) dest = (CFseState)((v >> (offset)) & ((1 << (num)) - 1)); + + +HRESULT CDecoder::DecodeLzfse(UInt32 unpackSize, Byte version) +{ + PRF(printf("\nLZFSE-%d %7u", version - '0', unpackSize)); + + UInt32 numLiterals; + UInt32 litPayloadSize; + Int32 literal_bits; + + UInt32 lit_state_0; + UInt32 lit_state_1; + UInt32 lit_state_2; + UInt32 lit_state_3; + + UInt32 numMatches; + UInt32 lmdPayloadSize; + Int32 lmd_bits; + + CFseState l_state; + CFseState m_state; + CFseState d_state; + + UInt16 freqs[NUM_SYMBOLS]; + + if (version == kSignature_LZFSE_V1) + { + return E_NOTIMPL; + // we need examples to test LZFSE-V1 code + /* + const unsigned k_v1_SubHeaderSize = 7 * 4 + 7 * 2; + const unsigned k_v1_HeaderSize = k_v1_SubHeaderSize + NUM_SYMBOLS * 2; + _buffer.AllocAtLeast(k_v1_HeaderSize); + if (m_InStream.ReadBytes(_buffer, k_v1_HeaderSize) != k_v1_HeaderSize) + return S_FALSE; + + const Byte *buf = _buffer; + #define GET_32(offs, dest) dest = GetUi32(buf + offs) + #define GET_16(offs, dest) dest = GetUi16(buf + offs) + + UInt32 payload_bytes; + GET_32(0, payload_bytes); + GET_32(4, numLiterals); + GET_32(8, numMatches); + GET_32(12, litPayloadSize); + GET_32(16, lmdPayloadSize); + if (litPayloadSize > (1 << 20) || lmdPayloadSize > (1 << 20)) + return S_FALSE; + GET_32(20, literal_bits); + if (literal_bits < -7 || literal_bits > 0) + return S_FALSE; + + GET_16(24, lit_state_0); + GET_16(26, lit_state_1); + GET_16(28, lit_state_2); + GET_16(30, lit_state_3); + + GET_32(32, lmd_bits); + if (lmd_bits < -7 || lmd_bits > 0) + return S_FALSE; + + GET_16(36, l_state); + GET_16(38, m_state); + GET_16(40, d_state); + + for (unsigned i = 0; i < NUM_SYMBOLS; i++) + freqs[i] = GetUi16(buf + k_v1_SubHeaderSize + i * 2); + */ + } + else + { + UInt32 headerSize; + { + const unsigned kPreHeaderSize = 4 * 2; // signature and upackSize + const unsigned kHeaderSize = 8 * 3; + Byte temp[kHeaderSize]; + if (m_InStream.ReadBytes(temp, kHeaderSize) != kHeaderSize) + return S_FALSE; + + UInt64 v; + + v = GetUi64(temp); + GET_BITS_64(v, 0, 20, numLiterals) + GET_BITS_64(v, 20, 20, litPayloadSize) + GET_BITS_64(v, 40, 20, numMatches) + GET_BITS_64_Int32(v, 60, 3 + 1, literal_bits) // (NumberOfUsedBits - 1) + literal_bits -= 7; // (-NumberOfUnusedBits) + if (literal_bits > 0) + return S_FALSE; + // GET_BITS_64(v, 63, 1, unused); + + v = GetUi64(temp + 8); + GET_BITS_64(v, 0, 10, lit_state_0) + GET_BITS_64(v, 10, 10, lit_state_1) + GET_BITS_64(v, 20, 10, lit_state_2) + GET_BITS_64(v, 30, 10, lit_state_3) + GET_BITS_64(v, 40, 20, lmdPayloadSize) + GET_BITS_64_Int32(v, 60, 3 + 1, lmd_bits) + lmd_bits -= 7; + if (lmd_bits > 0) + return S_FALSE; + // GET_BITS_64(v, 63, 1, unused) + + UInt32 v32 = GetUi32(temp + 20); + // (total header size in bytes; this does not + // correspond to a field in the uncompressed header version, + // but is required; we wouldn't know the size of the + // compresssed header otherwise. + GET_BITS_32(v32, 0, 10, l_state) + GET_BITS_32(v32, 10, 10, m_state) + GET_BITS_32(v32, 20, 10 + 2, d_state) + // GET_BITS_64(v, 62, 2, unused) + + headerSize = GetUi32(temp + 16); + if (headerSize <= kPreHeaderSize + kHeaderSize) + return S_FALSE; + headerSize -= kPreHeaderSize + kHeaderSize; + } + + // no freqs case is not allowed ? + // memset(freqs, 0, sizeof(freqs)); + // if (headerSize != 0) + { + static const Byte numBitsTable[32] = + { + 2, 3, 2, 5, 2, 3, 2, 8, 2, 3, 2, 5, 2, 3, 2, 14, + 2, 3, 2, 5, 2, 3, 2, 8, 2, 3, 2, 5, 2, 3, 2, 14 + }; + + static const Byte valueTable[32] = + { + 0, 2, 1, 4, 0, 3, 1, 8, 0, 2, 1, 5, 0, 3, 1, 24, + 0, 2, 1, 6, 0, 3, 1, 8, 0, 2, 1, 7, 0, 3, 1, 24 + }; + + UInt32 accum = 0; + unsigned numBits = 0; + + for (unsigned i = 0; i < NUM_SYMBOLS; i++) + { + while (numBits <= 14 && headerSize != 0) + { + Byte b; + if (!m_InStream.ReadByte(b)) + return S_FALSE; + accum |= (UInt32)b << numBits; + numBits += 8; + headerSize--; + } + + unsigned b = (unsigned)accum & 31; + unsigned n = numBitsTable[b]; + if (numBits < n) + return S_FALSE; + numBits -= n; + UInt32 f = valueTable[b]; + if (n >= 8) + f += ((accum >> 4) & (0x3ff >> (14 - n))); + accum >>= n; + freqs[i] = (UInt16)f; + } + + if (numBits >= 8 || headerSize != 0) + return S_FALSE; + } + } + + PRF(printf(" Literals=%6u Matches=%6u", numLiterals, numMatches)); + + if (numLiterals > LITERALS_PER_BLOCK + || (numLiterals & 3) != 0 + || numMatches > MATCHES_PER_BLOCK + || lit_state_0 >= NUM_LIT_STATES + || lit_state_1 >= NUM_LIT_STATES + || lit_state_2 >= NUM_LIT_STATES + || lit_state_3 >= NUM_LIT_STATES + || l_state >= NUM_L_STATES + || m_state >= NUM_M_STATES + || d_state >= NUM_D_STATES) + return S_FALSE; + + // only full table is allowed ? + if ( SumFreqs(freqs_L, NUM_L_SYMBOLS) != NUM_L_STATES + || SumFreqs(freqs_M, NUM_M_SYMBOLS) != NUM_M_STATES + || SumFreqs(freqs_D, NUM_D_SYMBOLS) != NUM_D_STATES + || SumFreqs(freqs_LIT, NUM_LIT_SYMBOLS) != NUM_LIT_STATES) + return S_FALSE; + + + const unsigned kPad = 16; + + // ---------- Decode literals ---------- + + { + _literals.AllocAtLeast(LITERALS_PER_BLOCK + 16); + _buffer.AllocAtLeast(kPad + litPayloadSize); + memset(_buffer, 0, kPad); + + if (m_InStream.ReadBytes(_buffer + kPad, litPayloadSize) != litPayloadSize) + return S_FALSE; + + UInt32 lit_decoder[NUM_LIT_STATES]; + InitLitTable(freqs_LIT, lit_decoder); + + const Byte *buf_start = _buffer + kPad; + const Byte *buf_check = buf_start - 4; + const Byte *buf = buf_start + litPayloadSize; + CBitStream in; + if (FseInStream_Init(&in, literal_bits, &buf) != 0) + return S_FALSE; + + Byte *lit = _literals; + const Byte *lit_limit = lit + numLiterals; + for (; lit < lit_limit; lit += 4) + { + FseInStream_FLUSH + DECODE_LIT (lit[0], lit_state_0) + DECODE_LIT (lit[1], lit_state_1) + FseInStream_FLUSH + DECODE_LIT (lit[2], lit_state_2) + DECODE_LIT (lit[3], lit_state_3) + } + + if ((buf_start - buf) * 8 != (int)in.numBits) + return S_FALSE; + } + + + // ---------- Decode LMD ---------- + + _buffer.AllocAtLeast(kPad + lmdPayloadSize); + memset(_buffer, 0, kPad); + if (m_InStream.ReadBytes(_buffer + kPad, lmdPayloadSize) != lmdPayloadSize) + return S_FALSE; + + CExtraEntry l_decoder[NUM_L_STATES]; + CExtraEntry m_decoder[NUM_M_STATES]; + CExtraEntry d_decoder[NUM_D_STATES]; + + InitExtraDecoderTable(NUM_L_STATES, NUM_L_SYMBOLS, freqs_L, k_L_extra, l_decoder); + InitExtraDecoderTable(NUM_M_STATES, NUM_M_SYMBOLS, freqs_M, k_M_extra, m_decoder); + InitExtraDecoderTable(NUM_D_STATES, NUM_D_SYMBOLS, freqs_D, k_D_extra, d_decoder); + + const Byte *buf_start = _buffer + kPad; + const Byte *buf_check = buf_start - 4; + const Byte *buf = buf_start + lmdPayloadSize; + CBitStream in; + if (FseInStream_Init(&in, lmd_bits, &buf)) + return S_FALSE; + + const Byte *lit = _literals; + const Byte *lit_limit = lit + numLiterals; + + UInt32 D = 0; + + for (;;) + { + if (numMatches == 0) + break; + numMatches--; + + FseInStream_FLUSH + + unsigned L = (unsigned)FseDecodeExtra(&l_state, l_decoder, &in); + + FseInStream_FLUSH + + unsigned M = (unsigned)FseDecodeExtra(&m_state, m_decoder, &in); + + FseInStream_FLUSH + + { + UInt32 new_D = FseDecodeExtra(&d_state, d_decoder, &in); + if (new_D) + D = new_D; + } + + if (L != 0) + { + if (L > (size_t)(lit_limit - lit)) + return S_FALSE; + unsigned cur = L; + if (cur > unpackSize) + cur = (unsigned)unpackSize; + m_OutWindowStream.PutBytes(lit, cur); + unpackSize -= cur; + lit += cur; + if (cur != L) + return S_FALSE; + } + + if (M != 0) + { + if (unpackSize == 0 || D == 0) + return S_FALSE; + unsigned cur = M; + if (cur > unpackSize) + cur = (unsigned)unpackSize; + if (!m_OutWindowStream.CopyBlock(D - 1, cur)) + return S_FALSE; + unpackSize -= cur; + if (cur != M) + return S_FALSE; + } + } + + if (unpackSize != 0) + return S_FALSE; + + // LZFSE encoder writes 8 additional zero bytes before LMD payload + // We test it: + if ((size_t)(buf - buf_start) * 8 + in.numBits != 64) + return S_FALSE; + if (GetUi64(buf_start) != 0) + return S_FALSE; + + return S_OK; +} + + +HRESULT CDecoder::CodeReal(ISequentialInStream *inStream, ISequentialOutStream *outStream, + const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress) +{ + PRF(printf("\n\nLzfseDecoder %7u %7u\n", (unsigned)*outSize, (unsigned)*inSize)); + + const UInt32 kLzfseDictSize = 1 << 18; + if (!m_OutWindowStream.Create(kLzfseDictSize)) + return E_OUTOFMEMORY; + if (!m_InStream.Create(1 << 18)) + return E_OUTOFMEMORY; + + m_OutWindowStream.SetStream(outStream); + m_OutWindowStream.Init(false); + m_InStream.SetStream(inStream); + m_InStream.Init(); + + CCoderReleaser coderReleaser(this); + + UInt64 prevOut = 0; + UInt64 prevIn = 0; + + if (LzvnMode) + { + if (!outSize || !inSize) + return E_NOTIMPL; + const UInt64 unpackSize = *outSize; + const UInt64 packSize = *inSize; + if (unpackSize > (UInt32)(Int32)-1 + || packSize > (UInt32)(Int32)-1) + return S_FALSE; + RINOK(DecodeLzvn((UInt32)unpackSize, (UInt32)packSize)) + } + else + for (;;) + { + const UInt64 pos = m_OutWindowStream.GetProcessedSize(); + const UInt64 packPos = m_InStream.GetProcessedSize(); + + if (progress && ((pos - prevOut) >= (1 << 22) || (packPos - prevIn) >= (1 << 22))) + { + RINOK(progress->SetRatioInfo(&packPos, &pos)) + prevIn = packPos; + prevOut = pos; + } + + UInt32 v; + RINOK(GetUInt32(v)) + if ((v & 0xFFFFFF) != 0x787662) // bvx + return S_FALSE; + v >>= 24; + + if (v == 0x24) // '$', end of stream + break; + + UInt32 unpackSize; + RINOK(GetUInt32(unpackSize)) + + UInt32 cur = unpackSize; + if (outSize) + { + const UInt64 rem = *outSize - pos; + if (cur > rem) + cur = (UInt32)rem; + } + unpackSize -= cur; + + HRESULT res; + if (v == kSignature_LZFSE_V1 || v == kSignature_LZFSE_V2) + res = DecodeLzfse(cur, (Byte)v); + else if (v == 0x6E) // 'n' + { + UInt32 packSize; + res = GetUInt32(packSize); + if (res == S_OK) + res = DecodeLzvn(cur, packSize); + } + else if (v == 0x2D) // '-' + res = DecodeUncompressed(cur); + else + return E_NOTIMPL; + + if (res != S_OK) + return res; + + if (unpackSize != 0) + return S_FALSE; + } + + coderReleaser.NeedFlush = false; + HRESULT res = m_OutWindowStream.Flush(); + if (res == S_OK) + if ((!LzvnMode && inSize && *inSize != m_InStream.GetProcessedSize()) + || (outSize && *outSize != m_OutWindowStream.GetProcessedSize())) + res = S_FALSE; + return res; +} + + +Z7_COM7F_IMF(CDecoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, + const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress)) +{ + try { return CodeReal(inStream, outStream, inSize, outSize, progress); } + catch(const CInBufferException &e) { return e.ErrorCode; } + catch(const CLzOutWindowException &e) { return e.ErrorCode; } + catch(...) { return E_OUTOFMEMORY; } + // catch(...) { return S_FALSE; } +} + +}} diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/LzfseDecoder.h b/src/plugins/7zip/7za/cpp/7zip/Compress/LzfseDecoder.h new file mode 100644 index 00000000..b7227dc2 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/LzfseDecoder.h @@ -0,0 +1,63 @@ +// LzfseDecoder.h + +#ifndef ZIP7_INC_LZFSE_DECODER_H +#define ZIP7_INC_LZFSE_DECODER_H + +#include "../../Common/MyBuffer.h" +#include "../../Common/MyCom.h" + +#include "../ICoder.h" + +#include "../Common/InBuffer.h" + +#include "LzOutWindow.h" + +namespace NCompress { +namespace NLzfse { + +Z7_CLASS_IMP_NOQIB_1( + CDecoder + , ICompressCoder +) + CLzOutWindow m_OutWindowStream; + CInBuffer m_InStream; + CByteBuffer _literals; + CByteBuffer _buffer; + + class CCoderReleaser + { + CDecoder *m_Coder; + public: + bool NeedFlush; + CCoderReleaser(CDecoder *coder): m_Coder(coder), NeedFlush(true) {} + ~CCoderReleaser() + { + if (NeedFlush) + m_Coder->m_OutWindowStream.Flush(); + } + }; + friend class CCoderReleaser; + + HRESULT GetUInt32(UInt32 &val); + + HRESULT DecodeUncompressed(UInt32 unpackSize); + HRESULT DecodeLzvn(UInt32 unpackSize, UInt32 packSize); + HRESULT DecodeLzfse(UInt32 unpackSize, Byte version); + + HRESULT CodeReal(ISequentialInStream *inStream, ISequentialOutStream *outStream, + const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress); +public: + bool LzvnMode; + + CDecoder(): + LzvnMode(false) + {} + + // sizes are checked in Code() + // UInt64 GetInputProcessedSize() const { return m_InStream.GetProcessedSize(); } + // UInt64 GetOutputProcessedSize() const { return m_OutWindowStream.GetProcessedSize(); } +}; + +}} + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/LzhDecoder.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/LzhDecoder.cpp index b0aa7536..fb115da6 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/LzhDecoder.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/LzhDecoder.cpp @@ -10,29 +10,17 @@ namespace NDecoder { static const UInt32 kWindowSizeMin = 1 << 16; -static bool CheckCodeLens(const Byte *lens, unsigned num) -{ - UInt32 sum = 0; - for (unsigned i = 0; i < num; i++) - { - unsigned len = lens[i]; - if (len != 0) - sum += ((UInt32)1 << (NUM_CODE_BITS - len)); - } - return sum == ((UInt32)1 << NUM_CODE_BITS); -} - bool CCoder::ReadTP(unsigned num, unsigned numBits, int spec) { _symbolT = -1; - UInt32 n = _inBitStream.ReadBits(numBits); + const unsigned n = (unsigned)_inBitStream.ReadBits(numBits); if (n == 0) { - _symbolT = _inBitStream.ReadBits(numBits); - return ((unsigned)_symbolT < num); + const unsigned s = (unsigned)_inBitStream.ReadBits(numBits); + _symbolT = (int)s; + return (s < num); } - if (n > num) return false; @@ -41,37 +29,31 @@ bool CCoder::ReadTP(unsigned num, unsigned numBits, int spec) unsigned i; for (i = 0; i < NPT; i++) lens[i] = 0; - i = 0; - do { - UInt32 val = _inBitStream.GetValue(16); + unsigned val = (unsigned)_inBitStream.GetValue(16); unsigned c = val >> 13; - + unsigned mov = 3; if (c == 7) { - UInt32 mask = 1 << 12; - while (mask & val) + while (val & (1 << 12)) { - mask >>= 1; + val += val; c++; } if (c > 16) return false; + mov = c - 3; } - - _inBitStream.MovePos(c < 7 ? 3 : c - 3); lens[i++] = (Byte)c; - - if (i == (unsigned)spec) + _inBitStream.MovePos(mov); + if ((int)i == spec) i += _inBitStream.ReadBits(2); } while (i < n); - if (!CheckCodeLens(lens, NPT)) - return false; - return _decoderT.Build(lens); + return _decoderT.Build(lens, NHuffman::k_BuildMode_Full); } } @@ -81,27 +63,24 @@ bool CCoder::ReadC() { _symbolC = -1; - unsigned n = _inBitStream.ReadBits(NUM_C_BITS); - + const unsigned n = (unsigned)_inBitStream.ReadBits(NUM_C_BITS); if (n == 0) { - _symbolC = _inBitStream.ReadBits(NUM_C_BITS); - return ((unsigned)_symbolC < NC); + const unsigned s = (unsigned)_inBitStream.ReadBits(NUM_C_BITS); + _symbolC = (int)s; + return (s < NC); } - if (n > NC) return false; { Byte lens[NC]; - unsigned i = 0; - do { - UInt32 c = (unsigned)_symbolT; + unsigned c = (unsigned)_symbolT; if (_symbolT < 0) - c = _decoderT.Decode(&_inBitStream); + c = _decoderT.DecodeFull(&_inBitStream); if (c <= 2) { @@ -124,19 +103,13 @@ bool CCoder::ReadC() } while (i < n); - while (i < NC) - lens[i++] = 0; - - if (!CheckCodeLens(lens, NC)) - return false; - return _decoderC.Build(lens); + while (i < NC) lens[i++] = 0; + return _decoderC.Build(lens, /* n, */ NHuffman::k_BuildMode_Full); } } -HRESULT CCoder::CodeReal(UInt64 rem, ICompressProgressInfo *progress) +HRESULT CCoder::CodeReal(UInt32 rem, ICompressProgressInfo *progress) { - unsigned pbit = (DictSize <= (1 << 14) ? 4 : 5); - UInt32 blockSize = 0; while (rem != 0) @@ -145,12 +118,11 @@ HRESULT CCoder::CodeReal(UInt64 rem, ICompressProgressInfo *progress) { if (_inBitStream.ExtraBitsWereRead()) return S_FALSE; - if (progress) { - UInt64 packSize = _inBitStream.GetProcessedSize(); - UInt64 pos = _outWindow.GetProcessedSize(); - RINOK(progress->SetRatioInfo(&packSize, &pos)); + const UInt64 packSize = _inBitStream.GetProcessedSize(); + const UInt64 pos = _outWindow.GetProcessedSize(); + RINOK(progress->SetRatioInfo(&packSize, &pos)) } blockSize = _inBitStream.ReadBits(16); @@ -161,15 +133,16 @@ HRESULT CCoder::CodeReal(UInt64 rem, ICompressProgressInfo *progress) return S_FALSE; if (!ReadC()) return S_FALSE; + const unsigned pbit = (DictSize <= (1 << 14) ? 4 : 5); if (!ReadTP(NP, pbit, -1)) return S_FALSE; } blockSize--; - UInt32 number = (unsigned)_symbolC; + unsigned number = (unsigned)_symbolC; if (_symbolC < 0) - number = _decoderC.Decode(&_inBitStream); + number = _decoderC.DecodeFull(&_inBitStream); if (number < 256) { @@ -178,11 +151,11 @@ HRESULT CCoder::CodeReal(UInt64 rem, ICompressProgressInfo *progress) } else { - UInt32 len = number - 256 + kMatchMinLen; + const unsigned len = number - 256 + kMatchMinLen; - UInt32 dist = (unsigned)_symbolT; + UInt32 dist = (UInt32)(unsigned)_symbolT; if (_symbolT < 0) - dist = _decoderT.Decode(&_inBitStream); + dist = (UInt32)_decoderT.DecodeFull(&_inBitStream); if (dist > 1) { @@ -194,7 +167,11 @@ HRESULT CCoder::CodeReal(UInt64 rem, ICompressProgressInfo *progress) return S_FALSE; if (len > rem) - len = (UInt32)rem; + { + // if (FinishMode) + return S_FALSE; + // len = (unsigned)rem; + } if (!_outWindow.CopyBlock(dist, len)) return S_FALSE; @@ -202,44 +179,37 @@ HRESULT CCoder::CodeReal(UInt64 rem, ICompressProgressInfo *progress) } } - if (FinishMode) + // if (FinishMode) { if (blockSize != 0) return S_FALSE; if (_inBitStream.ReadAlignBits() != 0) return S_FALSE; } - if (_inBitStream.ExtraBitsWereRead()) return S_FALSE; - return S_OK; } -STDMETHODIMP CCoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 * /* inSize */, const UInt64 *outSize, ICompressProgressInfo *progress) +HRESULT CCoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, + const UInt32 outSize, ICompressProgressInfo *progress) { try { - if (!outSize) - return E_INVALIDARG; - if (!_outWindow.Create(DictSize > kWindowSizeMin ? DictSize : kWindowSizeMin)) return E_OUTOFMEMORY; if (!_inBitStream.Create(1 << 17)) return E_OUTOFMEMORY; - _outWindow.SetStream(outStream); _outWindow.Init(false); _inBitStream.SetStream(inStream); _inBitStream.Init(); - - CCoderReleaser coderReleaser(this); - - RINOK(CodeReal(*outSize, progress)); - - coderReleaser.Disable(); + { + CCoderReleaser coderReleaser(this); + RINOK(CodeReal(outSize, progress)) + coderReleaser.Disable(); + } return _outWindow.Flush(); } catch(const CInBufferException &e) { return e.ErrorCode; } diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/LzhDecoder.h b/src/plugins/7zip/7za/cpp/7zip/Compress/LzhDecoder.h index 5e13d823..204c52c9 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/LzhDecoder.h +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/LzhDecoder.h @@ -1,7 +1,7 @@ // LzhDecoder.h -#ifndef __COMPRESS_LZH_DECODER_H -#define __COMPRESS_LZH_DECODER_H +#ifndef ZIP7_INC_COMPRESS_LZH_DECODER_H +#define ZIP7_INC_COMPRESS_LZH_DECODER_H #include "../../Common/MyCom.h" @@ -19,25 +19,25 @@ namespace NDecoder { const unsigned kMatchMinLen = 3; const unsigned kMatchMaxLen = 256; -const unsigned NC = (256 + kMatchMaxLen - kMatchMinLen + 1); +const unsigned NC = 256 + kMatchMaxLen - kMatchMinLen + 1; const unsigned NUM_CODE_BITS = 16; const unsigned NUM_DIC_BITS_MAX = 25; -const unsigned NT = (NUM_CODE_BITS + 3); -const unsigned NP = (NUM_DIC_BITS_MAX + 1); +const unsigned NT = NUM_CODE_BITS + 3; +const unsigned NP = NUM_DIC_BITS_MAX + 1; const unsigned NPT = NP; // Max(NT, NP) -class CCoder: - public ICompressCoder, - public CMyUnknownImp +class CCoder { CLzOutWindow _outWindow; NBitm::CDecoder _inBitStream; int _symbolT; int _symbolC; + UInt32 DictSize; + // bool FinishMode; - NHuffman::CDecoder _decoderT; - NHuffman::CDecoder _decoderC; + NHuffman::CDecoder256 _decoderT; + NHuffman::CDecoder _decoderC; class CCoderReleaser { @@ -52,21 +52,15 @@ class CCoder: bool ReadTP(unsigned num, unsigned numBits, int spec); bool ReadC(); - HRESULT CodeReal(UInt64 outSize, ICompressProgressInfo *progress); + HRESULT CodeReal(UInt32 outSize, ICompressProgressInfo *progress); public: - MY_UNKNOWN_IMP - - UInt32 DictSize; - bool FinishMode; - - STDMETHOD(Code)(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress); - - void SetDictSize(unsigned dictSize) { DictSize = dictSize; } - - CCoder(): DictSize(1 << 16), FinishMode(false) {} - + CCoder(): DictSize(1 << 16) + // , FinishMode(true) + {} + void SetDictSize(UInt32 dictSize) { DictSize = dictSize; } UInt64 GetInputProcessedSize() const { return _inBitStream.GetProcessedSize(); } + HRESULT Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, + UInt32 outSize, ICompressProgressInfo *progress); }; }}} diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/Lzma2Decoder.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/Lzma2Decoder.cpp index 1a378bbe..eab78000 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/Lzma2Decoder.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/Lzma2Decoder.cpp @@ -2,263 +2,264 @@ #include "StdAfx.h" +// #include + #include "../../../C/Alloc.h" +// #include "../../../C/CpuTicks.h" #include "../Common/StreamUtils.h" #include "Lzma2Decoder.h" -static HRESULT SResToHRESULT(SRes res) -{ - switch (res) - { - case SZ_OK: return S_OK; - case SZ_ERROR_MEM: return E_OUTOFMEMORY; - case SZ_ERROR_PARAM: return E_INVALIDARG; - // case SZ_ERROR_PROGRESS: return E_ABORT; - case SZ_ERROR_DATA: return S_FALSE; - } - return E_FAIL; -} - namespace NCompress { namespace NLzma2 { CDecoder::CDecoder(): - _inBuf(NULL), - _inBufSize(0), - _inBufSizeNew(1 << 20), - _outStepSize(1 << 22), - _outSizeDefined(false), - _finishMode(false) -{ - Lzma2Dec_Construct(&_state); -} - -STDMETHODIMP CDecoder::SetInBufSize(UInt32 , UInt32 size) { _inBufSizeNew = size; return S_OK; } -STDMETHODIMP CDecoder::SetOutBufSize(UInt32 , UInt32 size) { _outStepSize = size; return S_OK; } + _dec(NULL) + , _inProcessed(0) + , _prop(0xFF) + , _finishMode(false) + , _inBufSize(1 << 20) + , _outStep(1 << 20) + #ifndef Z7_ST + , _tryMt(1) + , _numThreads(1) + , _memUsage((UInt64)(sizeof(size_t)) << 28) + #endif +{} CDecoder::~CDecoder() { - Lzma2Dec_Free(&_state, &g_Alloc); - MidFree(_inBuf); + if (_dec) + Lzma2DecMt_Destroy(_dec); } -STDMETHODIMP CDecoder::SetDecoderProperties2(const Byte *prop, UInt32 size) +Z7_COM7F_IMF(CDecoder::SetInBufSize(UInt32 , UInt32 size)) { _inBufSize = size; return S_OK; } +Z7_COM7F_IMF(CDecoder::SetOutBufSize(UInt32 , UInt32 size)) { _outStep = size; return S_OK; } + +Z7_COM7F_IMF(CDecoder::SetDecoderProperties2(const Byte *prop, UInt32 size)) { if (size != 1) return E_NOTIMPL; - - RINOK(SResToHRESULT(Lzma2Dec_Allocate(&_state, prop[0], &g_Alloc))); - if (!_inBuf || _inBufSize != _inBufSizeNew) - { - MidFree(_inBuf); - _inBufSize = 0; - _inBuf = (Byte *)MidAlloc(_inBufSizeNew); - if (!_inBuf) - return E_OUTOFMEMORY; - _inBufSize = _inBufSizeNew; - } - + if (prop[0] > 40) + return E_NOTIMPL; + _prop = prop[0]; return S_OK; } -STDMETHODIMP CDecoder::GetInStreamProcessedSize(UInt64 *value) { *value = _inSizeProcessed; return S_OK; } -STDMETHODIMP CDecoder::SetInStream(ISequentialInStream *inStream) { _inStream = inStream; return S_OK; } -STDMETHODIMP CDecoder::ReleaseInStream() { _inStream.Release(); return S_OK; } -STDMETHODIMP CDecoder::SetOutStreamSize(const UInt64 *outSize) +Z7_COM7F_IMF(CDecoder::SetFinishMode(UInt32 finishMode)) { - _outSizeDefined = (outSize != NULL); - _outSize = 0; - if (_outSizeDefined) - _outSize = *outSize; - - Lzma2Dec_Init(&_state); - - _inPos = _inSize = 0; - _inSizeProcessed = _outSizeProcessed = 0; + _finishMode = (finishMode != 0); return S_OK; } -STDMETHODIMP CDecoder::SetFinishMode(UInt32 finishMode) + + +#ifndef Z7_ST + +static UInt64 Get_ExpectedBlockSize_From_Dict(UInt32 dictSize) { - _finishMode = (finishMode != 0); - return S_OK; + const UInt32 kMinSize = (UInt32)1 << 20; + const UInt32 kMaxSize = (UInt32)1 << 28; + UInt64 blockSize = (UInt64)dictSize << 2; + if (blockSize < kMinSize) blockSize = kMinSize; + if (blockSize > kMaxSize) blockSize = kMaxSize; + if (blockSize < dictSize) blockSize = dictSize; + blockSize += (kMinSize - 1); + blockSize &= ~(UInt64)(kMinSize - 1); + return blockSize; } -STDMETHODIMP CDecoder::Code(ISequentialInStream *inStream, - ISequentialOutStream *outStream, const UInt64 *inSize, - const UInt64 *outSize, ICompressProgressInfo *progress) +#define LZMA2_DIC_SIZE_FROM_PROP_FULL(p) ((p) == 40 ? 0xFFFFFFFF : (((UInt32)2 | ((p) & 1)) << ((p) / 2 + 11))) + +#endif + +#define RET_IF_WRAP_ERROR_CONFIRMED(wrapRes, sRes, sResErrorCode) \ + if (wrapRes != S_OK && sRes == sResErrorCode) return wrapRes; + +#define RET_IF_WRAP_ERROR(wrapRes, sRes, sResErrorCode) \ + if (wrapRes != S_OK /* && (sRes == SZ_OK || sRes == sResErrorCode) */) return wrapRes; + +Z7_COM7F_IMF(CDecoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, + const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress)) { - if (!_inBuf) - return S_FALSE; - SetOutStreamSize(outSize); - - UInt32 step = _outStepSize; - const UInt32 kOutStepSize_Min = 1 << 12; - if (step < kOutStepSize_Min) - step = kOutStepSize_Min; - - SizeT wrPos = _state.decoder.dicPos; + _inProcessed = 0; - SizeT next = (_state.decoder.dicBufSize - _state.decoder.dicPos < step) ? - _state.decoder.dicBufSize : - _state.decoder.dicPos + step; + if (!_dec) + { + _dec = Lzma2DecMt_Create( + // &g_AlignedAlloc, + &g_Alloc, + &g_MidAlloc); + if (!_dec) + return E_OUTOFMEMORY; + } - HRESULT hres = S_OK; + CLzma2DecMtProps props; + Lzma2DecMtProps_Init(&props); - for (;;) + props.inBufSize_ST = _inBufSize; + props.outStep_ST = _outStep; + + #ifndef Z7_ST { - if (_inPos == _inSize) - { - _inPos = _inSize = 0; - hres = inStream->Read(_inBuf, _inBufSize, &_inSize); - if (hres != S_OK) - break; - } + props.numThreads = 1; + UInt32 numThreads = _numThreads; - SizeT dicPos = _state.decoder.dicPos; - SizeT curSize = next - dicPos; - - ELzmaFinishMode finishMode = LZMA_FINISH_ANY; - if (_outSizeDefined) + if (_tryMt && numThreads >= 1) { - const UInt64 rem = _outSize - _outSizeProcessed; - if (curSize >= rem) + const UInt64 useLimit = _memUsage; + const UInt32 dictSize = LZMA2_DIC_SIZE_FROM_PROP_FULL(_prop); + const UInt64 expectedBlockSize64 = Get_ExpectedBlockSize_From_Dict(dictSize); + const size_t expectedBlockSize = (size_t)expectedBlockSize64; + const size_t inBlockMax = expectedBlockSize + expectedBlockSize / 16; + if (expectedBlockSize == expectedBlockSize64 && inBlockMax >= expectedBlockSize) { - curSize = (SizeT)rem; - if (_finishMode) - finishMode = LZMA_FINISH_END; + props.outBlockMax = expectedBlockSize; + props.inBlockMax = inBlockMax; + const size_t kOverheadSize = props.inBufSize_MT + (1 << 16); + const UInt64 okThreads = useLimit / (props.outBlockMax + props.inBlockMax + kOverheadSize); + if (numThreads > okThreads) + numThreads = (UInt32)okThreads; + if (numThreads == 0) + numThreads = 1; + props.numThreads = numThreads; } } + } + #endif - SizeT inSizeProcessed = _inSize - _inPos; - ELzmaStatus status; - SRes res = Lzma2Dec_DecodeToDic(&_state, dicPos + curSize, _inBuf + _inPos, &inSizeProcessed, finishMode, &status); + CSeqInStreamWrap inWrap; + CSeqOutStreamWrap outWrap; + CCompressProgressWrap progressWrap; - _inPos += (UInt32)inSizeProcessed; - _inSizeProcessed += inSizeProcessed; - SizeT outSizeProcessed = _state.decoder.dicPos - dicPos; - _outSizeProcessed += outSizeProcessed; + inWrap.Init(inStream); + outWrap.Init(outStream); + progressWrap.Init(progress); - bool finished = (inSizeProcessed == 0 && outSizeProcessed == 0 - || status == LZMA_STATUS_FINISHED_WITH_MARK); - bool outFinished = (_outSizeDefined && _outSizeProcessed >= _outSize); + SRes res; - if (res != 0 - || _state.decoder.dicPos >= next - || finished - || outFinished) - { - HRESULT res2 = WriteStream(outStream, _state.decoder.dic + wrPos, _state.decoder.dicPos - wrPos); + UInt64 inProcessed = 0; + int isMT = False; - if (_state.decoder.dicPos == _state.decoder.dicBufSize) - _state.decoder.dicPos = 0; - - wrPos = _state.decoder.dicPos; + #ifndef Z7_ST + isMT = _tryMt; + #endif - next = (_state.decoder.dicBufSize - _state.decoder.dicPos < step) ? - _state.decoder.dicBufSize : - _state.decoder.dicPos + step; + // UInt64 cpuTicks = GetCpuTicks(); - if (res != 0) - return S_FALSE; - RINOK(res2); + res = Lzma2DecMt_Decode(_dec, _prop, &props, + &outWrap.vt, outSize, _finishMode, + &inWrap.vt, + &inProcessed, + &isMT, + progress ? &progressWrap.vt : NULL); - if (finished) - { - if (status == LZMA_STATUS_FINISHED_WITH_MARK) - { - if (_finishMode && inSize && *inSize != _inSizeProcessed) - return S_FALSE; - if (finishMode == LZMA_FINISH_END && !outFinished) - return S_FALSE; - return S_OK; - } - return (finishMode == LZMA_FINISH_END) ? S_FALSE : S_OK; - } + /* + cpuTicks = GetCpuTicks() - cpuTicks; + printf("\n ticks = %10I64u\n", cpuTicks / 1000000); + */ - if (outFinished && finishMode == LZMA_FINISH_ANY) - return S_OK; - } - - if (progress) - { - RINOK(progress->SetRatioInfo(&_inSizeProcessed, &_outSizeProcessed)); - } + + #ifndef Z7_ST + /* we reset _tryMt, only if p->props.numThreads was changed */ + if (props.numThreads > 1) + _tryMt = isMT; + #endif + + _inProcessed = inProcessed; + + RET_IF_WRAP_ERROR(progressWrap.Res, res, SZ_ERROR_PROGRESS) + RET_IF_WRAP_ERROR(outWrap.Res, res, SZ_ERROR_WRITE) + RET_IF_WRAP_ERROR_CONFIRMED(inWrap.Res, res, SZ_ERROR_READ) + + if (res == SZ_OK && _finishMode) + { + if (inSize && *inSize != inProcessed) + res = SZ_ERROR_DATA; + if (outSize && *outSize != outWrap.Processed) + res = SZ_ERROR_DATA; } - HRESULT res2 = WriteStream(outStream, _state.decoder.dic + wrPos, _state.decoder.dicPos - wrPos); - if (hres != S_OK) - return hres; - return res2; + return SResToHRESULT(res); } -#ifndef NO_READ_FROM_CODER -STDMETHODIMP CDecoder::Read(void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CDecoder::GetInStreamProcessedSize(UInt64 *value)) { - UInt32 totalProcessed = 0; + *value = _inProcessed; + return S_OK; +} + + +#ifndef Z7_ST + +Z7_COM7F_IMF(CDecoder::SetNumberOfThreads(UInt32 numThreads)) +{ + _numThreads = numThreads; + return S_OK; +} + +Z7_COM7F_IMF(CDecoder::SetMemLimit(UInt64 memUsage)) +{ + _memUsage = memUsage; + return S_OK; +} + +#endif + + +#ifndef Z7_NO_READ_FROM_CODER + +Z7_COM7F_IMF(CDecoder::SetOutStreamSize(const UInt64 *outSize)) +{ + CLzma2DecMtProps props; + Lzma2DecMtProps_Init(&props); + props.inBufSize_ST = _inBufSize; + props.outStep_ST = _outStep; + + _inProcessed = 0; + + if (!_dec) + { + _dec = Lzma2DecMt_Create(&g_AlignedAlloc, &g_MidAlloc); + if (!_dec) + return E_OUTOFMEMORY; + } + + _inWrap.Init(_inStream); + const SRes res = Lzma2DecMt_Init(_dec, _prop, &props, outSize, _finishMode, &_inWrap.vt); + + if (res != SZ_OK) + return SResToHRESULT(res); + return S_OK; +} + + +Z7_COM7F_IMF(CDecoder::SetInStream(ISequentialInStream *inStream)) + { _inStream = inStream; return S_OK; } +Z7_COM7F_IMF(CDecoder::ReleaseInStream()) + { _inStream.Release(); return S_OK; } + + +Z7_COM7F_IMF(CDecoder::Read(void *data, UInt32 size, UInt32 *processedSize)) +{ if (processedSize) *processedSize = 0; - for (;;) - { - if (_inPos == _inSize) - { - _inPos = _inSize = 0; - RINOK(_inStream->Read(_inBuf, _inBufSize, &_inSize)); - } - { - ELzmaFinishMode finishMode = LZMA_FINISH_ANY; - if (_outSizeDefined) - { - const UInt64 rem = _outSize - _outSizeProcessed; - if (rem <= size) - { - size = (UInt32)rem; - if (_finishMode) - finishMode = LZMA_FINISH_END; - } - } + size_t size2 = size; + UInt64 inProcessed = 0; - SizeT outProcessed = size; - SizeT inProcessed = _inSize - _inPos; - - ELzmaStatus status; - SRes res = Lzma2Dec_DecodeToBuf(&_state, (Byte *)data, &outProcessed, - _inBuf + _inPos, &inProcessed, finishMode, &status); - - _inPos += (UInt32)inProcessed; - _inSizeProcessed += inProcessed; - _outSizeProcessed += outProcessed; - size -= (UInt32)outProcessed; - data = (Byte *)data + outProcessed; - - totalProcessed += (UInt32)outProcessed; - if (processedSize) - *processedSize = totalProcessed; - - if (res != SZ_OK) - { - if (totalProcessed != 0) - return S_OK; - return SResToHRESULT(res); - } - - if (inProcessed == 0 && outProcessed == 0) - return S_OK; - if (status == LZMA_STATUS_FINISHED_WITH_MARK) - return S_OK; - if (outProcessed != 0) - { - if (finishMode != LZMA_FINISH_END || _outSize != _outSizeProcessed) - return S_OK; - } - } - } + const SRes res = Lzma2DecMt_Read(_dec, (Byte *)data, &size2, &inProcessed); + + _inProcessed += inProcessed; + if (processedSize) + *processedSize = (UInt32)size2; + if (res != SZ_OK) + return SResToHRESULT(res); + return S_OK; } #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/Lzma2Decoder.h b/src/plugins/7zip/7za/cpp/7zip/Compress/Lzma2Decoder.h index a87912fb..7ca717e7 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/Lzma2Decoder.h +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/Lzma2Decoder.h @@ -1,86 +1,85 @@ // Lzma2Decoder.h -#ifndef __LZMA2_DECODER_H -#define __LZMA2_DECODER_H +#ifndef ZIP7_INC_LZMA2_DECODER_H +#define ZIP7_INC_LZMA2_DECODER_H -#include "../../../C/Lzma2Dec.h" +#include "../../../C/Lzma2DecMt.h" -#include "../../Common/MyCom.h" - -#include "../ICoder.h" +#include "../Common/CWrappers.h" namespace NCompress { namespace NLzma2 { -class CDecoder: +class CDecoder Z7_final: public ICompressCoder, public ICompressSetDecoderProperties2, public ICompressSetFinishMode, public ICompressGetInStreamProcessedSize, public ICompressSetBufSize, - #ifndef NO_READ_FROM_CODER + #ifndef Z7_NO_READ_FROM_CODER public ICompressSetInStream, public ICompressSetOutStreamSize, public ISequentialInStream, - #endif + #endif + #ifndef Z7_ST + public ICompressSetCoderMt, + public ICompressSetMemLimit, + #endif public CMyUnknownImp { - CMyComPtr _inStream; - Byte *_inBuf; - UInt32 _inPos; - UInt32 _inSize; - - bool _finishMode; - bool _outSizeDefined; - UInt64 _outSize; - - UInt64 _inSizeProcessed; - UInt64 _outSizeProcessed; - + Z7_COM_QI_BEGIN2(ICompressCoder) + Z7_COM_QI_ENTRY(ICompressSetDecoderProperties2) + Z7_COM_QI_ENTRY(ICompressSetFinishMode) + Z7_COM_QI_ENTRY(ICompressGetInStreamProcessedSize) + Z7_COM_QI_ENTRY(ICompressSetBufSize) + #ifndef Z7_NO_READ_FROM_CODER + Z7_COM_QI_ENTRY(ICompressSetInStream) + Z7_COM_QI_ENTRY(ICompressSetOutStreamSize) + Z7_COM_QI_ENTRY(ISequentialInStream) + #endif + #ifndef Z7_ST + Z7_COM_QI_ENTRY(ICompressSetCoderMt) + Z7_COM_QI_ENTRY(ICompressSetMemLimit) + #endif + Z7_COM_QI_END + Z7_COM_ADDREF_RELEASE + + Z7_IFACE_COM7_IMP(ICompressCoder) + Z7_IFACE_COM7_IMP(ICompressSetDecoderProperties2) + Z7_IFACE_COM7_IMP(ICompressSetFinishMode) + Z7_IFACE_COM7_IMP(ICompressGetInStreamProcessedSize) + Z7_IFACE_COM7_IMP(ICompressSetBufSize) + #ifndef Z7_NO_READ_FROM_CODER + Z7_IFACE_COM7_IMP(ICompressSetOutStreamSize) + Z7_IFACE_COM7_IMP(ICompressSetInStream) + Z7_IFACE_COM7_IMP(ISequentialInStream) + #endif + #ifndef Z7_ST + Z7_IFACE_COM7_IMP(ICompressSetCoderMt) + Z7_IFACE_COM7_IMP(ICompressSetMemLimit) + #endif + + CLzma2DecMtHandle _dec; + UInt64 _inProcessed; + Byte _prop; + int _finishMode; UInt32 _inBufSize; - UInt32 _inBufSizeNew; - UInt32 _outStepSize; - - CLzma2Dec _state; -public: - - MY_QUERYINTERFACE_BEGIN2(ICompressCoder) - MY_QUERYINTERFACE_ENTRY(ICompressSetDecoderProperties2) - MY_QUERYINTERFACE_ENTRY(ICompressSetFinishMode) - MY_QUERYINTERFACE_ENTRY(ICompressGetInStreamProcessedSize) - MY_QUERYINTERFACE_ENTRY(ICompressSetBufSize) - #ifndef NO_READ_FROM_CODER - MY_QUERYINTERFACE_ENTRY(ICompressSetInStream) - MY_QUERYINTERFACE_ENTRY(ICompressSetOutStreamSize) - MY_QUERYINTERFACE_ENTRY(ISequentialInStream) - #endif - MY_QUERYINTERFACE_END - MY_ADDREF_RELEASE - - STDMETHOD(Code)(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress); + UInt32 _outStep; - STDMETHOD(SetDecoderProperties2)(const Byte *data, UInt32 size); + #ifndef Z7_ST + int _tryMt; + UInt32 _numThreads; + UInt64 _memUsage; + #endif - STDMETHOD(SetFinishMode)(UInt32 finishMode); - - STDMETHOD(GetInStreamProcessedSize)(UInt64 *value); - - STDMETHOD(SetInBufSize)(UInt32 streamIndex, UInt32 size); - STDMETHOD(SetOutBufSize)(UInt32 streamIndex, UInt32 size); - - STDMETHOD(SetInStream)(ISequentialInStream *inStream); - STDMETHOD(ReleaseInStream)(); - - STDMETHOD(SetOutStreamSize)(const UInt64 *outSize); - - #ifndef NO_READ_FROM_CODER - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); - #endif + #ifndef Z7_NO_READ_FROM_CODER + CMyComPtr _inStream; + CSeqInStreamWrap _inWrap; + #endif +public: CDecoder(); - virtual ~CDecoder(); - + ~CDecoder(); }; }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/Lzma2Encoder.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/Lzma2Encoder.cpp index 06a11f10..ffe11529 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/Lzma2Encoder.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/Lzma2Encoder.cpp @@ -21,18 +21,20 @@ namespace NLzma2 { CEncoder::CEncoder() { - _encoder = 0; - _encoder = Lzma2Enc_Create(&g_Alloc, &g_BigAlloc); - if (_encoder == 0) + _encoder = NULL; + _encoder = Lzma2Enc_Create(&g_AlignedAlloc, &g_BigAlloc); + if (!_encoder) throw 1; } CEncoder::~CEncoder() { - if (_encoder != 0) + if (_encoder) Lzma2Enc_Destroy(_encoder); } + +HRESULT SetLzma2Prop(PROPID propID, const PROPVARIANT &prop, CLzma2EncProps &lzma2Props); HRESULT SetLzma2Prop(PROPID propID, const PROPVARIANT &prop, CLzma2EncProps &lzma2Props) { switch (propID) @@ -42,57 +44,90 @@ HRESULT SetLzma2Prop(PROPID propID, const PROPVARIANT &prop, CLzma2EncProps &lzm if (prop.vt == VT_UI4) lzma2Props.blockSize = prop.ulVal; else if (prop.vt == VT_UI8) - { - size_t v = (size_t)prop.uhVal.QuadPart; - if (v != prop.uhVal.QuadPart) - return E_INVALIDARG; - lzma2Props.blockSize = v; - } + lzma2Props.blockSize = prop.uhVal.QuadPart; else return E_INVALIDARG; break; } case NCoderPropID::kNumThreads: - if (prop.vt != VT_UI4) return E_INVALIDARG; lzma2Props.numTotalThreads = (int)(prop.ulVal); break; + if (prop.vt != VT_UI4) + return E_INVALIDARG; + lzma2Props.numTotalThreads = (int)prop.ulVal; + break; + case NCoderPropID::kNumThreadGroups: + if (prop.vt != VT_UI4) + return E_INVALIDARG; + // 16-bit value supported by Windows + if (prop.ulVal >= (1u << 16)) + return E_INVALIDARG; + lzma2Props.numThreadGroups = (unsigned)prop.ulVal; + break; default: - RINOK(NLzma::SetLzmaProp(propID, prop, lzma2Props.lzmaProps)); + RINOK(NLzma::SetLzmaProp(propID, prop, lzma2Props.lzmaProps)) } return S_OK; } -STDMETHODIMP CEncoder::SetCoderProperties(const PROPID *propIDs, - const PROPVARIANT *coderProps, UInt32 numProps) + +Z7_COM7F_IMF(CEncoder::SetCoderProperties(const PROPID *propIDs, + const PROPVARIANT *coderProps, UInt32 numProps)) { CLzma2EncProps lzma2Props; Lzma2EncProps_Init(&lzma2Props); for (UInt32 i = 0; i < numProps; i++) { - RINOK(SetLzma2Prop(propIDs[i], coderProps[i], lzma2Props)); + RINOK(SetLzma2Prop(propIDs[i], coderProps[i], lzma2Props)) } return SResToHRESULT(Lzma2Enc_SetProps(_encoder, &lzma2Props)); } -STDMETHODIMP CEncoder::WriteCoderProperties(ISequentialOutStream *outStream) + +Z7_COM7F_IMF(CEncoder::SetCoderPropertiesOpt(const PROPID *propIDs, + const PROPVARIANT *coderProps, UInt32 numProps)) +{ + for (UInt32 i = 0; i < numProps; i++) + { + const PROPVARIANT &prop = coderProps[i]; + const PROPID propID = propIDs[i]; + if (propID == NCoderPropID::kExpectedDataSize) + if (prop.vt == VT_UI8) + Lzma2Enc_SetDataSize(_encoder, prop.uhVal.QuadPart); + } + return S_OK; +} + + +Z7_COM7F_IMF(CEncoder::WriteCoderProperties(ISequentialOutStream *outStream)) { - Byte prop = Lzma2Enc_WriteProperties(_encoder); + const Byte prop = Lzma2Enc_WriteProperties(_encoder); return WriteStream(outStream, &prop, 1); } -STDMETHODIMP CEncoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 * /* inSize */, const UInt64 * /* outSize */, ICompressProgressInfo *progress) + +#define RET_IF_WRAP_ERROR(wrapRes, sRes, sResErrorCode) \ + if (wrapRes != S_OK /* && (sRes == SZ_OK || sRes == sResErrorCode) */) return wrapRes; + +Z7_COM7F_IMF(CEncoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, + const UInt64 * /* inSize */, const UInt64 * /* outSize */, ICompressProgressInfo *progress)) { - CSeqInStreamWrap inWrap(inStream); - CSeqOutStreamWrap outWrap(outStream); - CCompressProgressWrap progressWrap(progress); - - SRes res = Lzma2Enc_Encode(_encoder, &outWrap.p, &inWrap.p, progress ? &progressWrap.p : NULL); - if (res == SZ_ERROR_READ && inWrap.Res != S_OK) - return inWrap.Res; - if (res == SZ_ERROR_WRITE && outWrap.Res != S_OK) - return outWrap.Res; - if (res == SZ_ERROR_PROGRESS && progressWrap.Res != S_OK) - return progressWrap.Res; + CSeqInStreamWrap inWrap; + CSeqOutStreamWrap outWrap; + CCompressProgressWrap progressWrap; + + inWrap.Init(inStream); + outWrap.Init(outStream); + progressWrap.Init(progress); + + SRes res = Lzma2Enc_Encode2(_encoder, + &outWrap.vt, NULL, NULL, + &inWrap.vt, NULL, 0, + progress ? &progressWrap.vt : NULL); + + RET_IF_WRAP_ERROR(inWrap.Res, res, SZ_ERROR_READ) + RET_IF_WRAP_ERROR(outWrap.Res, res, SZ_ERROR_WRITE) + RET_IF_WRAP_ERROR(progressWrap.Res, res, SZ_ERROR_PROGRESS) + return SResToHRESULT(res); } diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/Lzma2Encoder.h b/src/plugins/7zip/7za/cpp/7zip/Compress/Lzma2Encoder.h index 8ff6e838..18c29e12 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/Lzma2Encoder.h +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/Lzma2Encoder.h @@ -1,7 +1,7 @@ // Lzma2Encoder.h -#ifndef __LZMA2_ENCODER_H -#define __LZMA2_ENCODER_H +#ifndef ZIP7_INC_LZMA2_ENCODER_H +#define ZIP7_INC_LZMA2_ENCODER_H #include "../../../C/Lzma2Enc.h" @@ -12,23 +12,17 @@ namespace NCompress { namespace NLzma2 { -class CEncoder: - public ICompressCoder, - public ICompressSetCoderProperties, - public ICompressWriteCoderProperties, - public CMyUnknownImp -{ +Z7_CLASS_IMP_COM_4( + CEncoder + , ICompressCoder + , ICompressSetCoderProperties + , ICompressWriteCoderProperties + , ICompressSetCoderPropertiesOpt +) CLzma2EncHandle _encoder; public: - MY_UNKNOWN_IMP3(ICompressCoder, ICompressSetCoderProperties, ICompressWriteCoderProperties) - - STDMETHOD(Code)(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress); - STDMETHOD(SetCoderProperties)(const PROPID *propIDs, const PROPVARIANT *props, UInt32 numProps); - STDMETHOD(WriteCoderProperties)(ISequentialOutStream *outStream); - CEncoder(); - virtual ~CEncoder(); + ~CEncoder(); }; }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/Lzma2Register.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/Lzma2Register.cpp index 8f279ebf..fe533463 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/Lzma2Register.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/Lzma2Register.cpp @@ -6,7 +6,7 @@ #include "Lzma2Decoder.h" -#ifndef EXTRACT_ONLY +#ifndef Z7_EXTRACT_ONLY #include "Lzma2Encoder.h" #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/LzmaDecoder.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/LzmaDecoder.cpp index 9002678a..4b67c8e9 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/LzmaDecoder.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/LzmaDecoder.cpp @@ -17,6 +17,7 @@ static HRESULT SResToHRESULT(SRes res) case SZ_ERROR_PARAM: return E_INVALIDARG; case SZ_ERROR_UNSUPPORTED: return E_NOTIMPL; case SZ_ERROR_DATA: return S_FALSE; + default: break; } return E_FAIL; } @@ -24,243 +25,324 @@ static HRESULT SResToHRESULT(SRes res) namespace NCompress { namespace NLzma { -CDecoder::CDecoder(): _inBuf(0), _propsWereSet(false), _outSizeDefined(false), - _inBufSize(1 << 20), - _outBufSize(1 << 22), +CDecoder::CDecoder(): FinishStream(false), - NeedMoreInput(false) + _propsWereSet(false), + _outSizeDefined(false), + _outStep(1 << 20), + _inBufSize(0), + _inBufSizeNew(1 << 20), + _lzmaStatus(LZMA_STATUS_NOT_SPECIFIED), + _inBuf(NULL) { - _inSizeProcessed = 0; - _inPos = _inSize = 0; - LzmaDec_Construct(&_state); + _inProcessed = 0; + _inPos = _inLim = 0; + + /* + AlignOffsetAlloc_CreateVTable(&_alloc); + _alloc.numAlignBits = 7; + _alloc.offset = 0; + */ + LzmaDec_CONSTRUCT(&_state) } CDecoder::~CDecoder() { - LzmaDec_Free(&_state, &g_Alloc); + LzmaDec_Free(&_state, &g_AlignedAlloc); // &_alloc.vt MyFree(_inBuf); } -STDMETHODIMP CDecoder::SetInBufSize(UInt32 , UInt32 size) { _inBufSize = size; return S_OK; } -STDMETHODIMP CDecoder::SetOutBufSize(UInt32 , UInt32 size) { _outBufSize = size; return S_OK; } +Z7_COM7F_IMF(CDecoder::SetInBufSize(UInt32 , UInt32 size)) + { _inBufSizeNew = size; return S_OK; } +Z7_COM7F_IMF(CDecoder::SetOutBufSize(UInt32 , UInt32 size)) + { _outStep = size; return S_OK; } HRESULT CDecoder::CreateInputBuffer() { - if (_inBuf == 0 || _inBufSize != _inBufSizeAllocated) + if (!_inBuf || _inBufSizeNew != _inBufSize) { MyFree(_inBuf); - _inBuf = (Byte *)MyAlloc(_inBufSize); - if (_inBuf == 0) + _inBufSize = 0; + _inBuf = (Byte *)MyAlloc(_inBufSizeNew); + if (!_inBuf) return E_OUTOFMEMORY; - _inBufSizeAllocated = _inBufSize; + _inBufSize = _inBufSizeNew; } return S_OK; } -STDMETHODIMP CDecoder::SetDecoderProperties2(const Byte *prop, UInt32 size) + +Z7_COM7F_IMF(CDecoder::SetDecoderProperties2(const Byte *prop, UInt32 size)) { - RINOK(SResToHRESULT(LzmaDec_Allocate(&_state, prop, size, &g_Alloc))); + RINOK(SResToHRESULT(LzmaDec_Allocate(&_state, prop, size, &g_AlignedAlloc))) // &_alloc.vt _propsWereSet = true; return CreateInputBuffer(); } + void CDecoder::SetOutStreamSizeResume(const UInt64 *outSize) { _outSizeDefined = (outSize != NULL); + _outSize = 0; if (_outSizeDefined) _outSize = *outSize; - _outSizeProcessed = 0; - _wrPos = 0; + _outProcessed = 0; + _lzmaStatus = LZMA_STATUS_NOT_SPECIFIED; + LzmaDec_Init(&_state); } -STDMETHODIMP CDecoder::SetOutStreamSize(const UInt64 *outSize) + +Z7_COM7F_IMF(CDecoder::SetOutStreamSize(const UInt64 *outSize)) { - _inSizeProcessed = 0; - _inPos = _inSize = 0; - NeedMoreInput = false; + _inProcessed = 0; + _inPos = _inLim = 0; SetOutStreamSizeResume(outSize); return S_OK; } -STDMETHODIMP CDecoder::SetFinishMode(UInt32 finishMode) + +Z7_COM7F_IMF(CDecoder::SetFinishMode(UInt32 finishMode)) { FinishStream = (finishMode != 0); return S_OK; } + +Z7_COM7F_IMF(CDecoder::GetInStreamProcessedSize(UInt64 *value)) +{ + *value = _inProcessed; + return S_OK; +} + + HRESULT CDecoder::CodeSpec(ISequentialInStream *inStream, ISequentialOutStream *outStream, ICompressProgressInfo *progress) { - if (_inBuf == 0 || !_propsWereSet) + if (!_inBuf || !_propsWereSet) return S_FALSE; + + const UInt64 startInProgress = _inProcessed; + SizeT wrPos = _state.dicPos; + HRESULT readRes = S_OK; - UInt64 startInProgress = _inSizeProcessed; - - SizeT next = (_state.dicBufSize - _state.dicPos < _outBufSize) ? _state.dicBufSize : (_state.dicPos + _outBufSize); for (;;) { - if (_inPos == _inSize) + if (_inPos == _inLim && readRes == S_OK) { - _inPos = _inSize = 0; - RINOK(inStream->Read(_inBuf, _inBufSizeAllocated, &_inSize)); + _inPos = _inLim = 0; + readRes = inStream->Read(_inBuf, _inBufSize, &_inLim); + } + + const SizeT dicPos = _state.dicPos; + SizeT size; + { + SizeT next = _state.dicBufSize; + if (next - wrPos > _outStep) + next = wrPos + _outStep; + size = next - dicPos; } - SizeT dicPos = _state.dicPos; - SizeT curSize = next - dicPos; - ELzmaFinishMode finishMode = LZMA_FINISH_ANY; if (_outSizeDefined) { - const UInt64 rem = _outSize - _outSizeProcessed; - if (rem <= curSize) + const UInt64 rem = _outSize - _outProcessed; + if (size >= rem) { - curSize = (SizeT)rem; + size = (SizeT)rem; if (FinishStream) finishMode = LZMA_FINISH_END; } } - SizeT inSizeProcessed = _inSize - _inPos; + SizeT inProcessed = _inLim - _inPos; ELzmaStatus status; - SRes res = LzmaDec_DecodeToDic(&_state, dicPos + curSize, _inBuf + _inPos, &inSizeProcessed, finishMode, &status); - _inPos += (UInt32)inSizeProcessed; - _inSizeProcessed += inSizeProcessed; - SizeT outSizeProcessed = _state.dicPos - dicPos; - _outSizeProcessed += outSizeProcessed; + const SRes res = LzmaDec_DecodeToDic(&_state, dicPos + size, _inBuf + _inPos, &inProcessed, finishMode, &status); + + _lzmaStatus = status; + _inPos += (UInt32)inProcessed; + _inProcessed += inProcessed; + const SizeT outProcessed = _state.dicPos - dicPos; + _outProcessed += outProcessed; - bool finished = (inSizeProcessed == 0 && outSizeProcessed == 0); - bool stopDecoding = (_outSizeDefined && _outSizeProcessed >= _outSize); + // we check for LZMA_STATUS_NEEDS_MORE_INPUT to allow RangeCoder initialization, if (_outSizeDefined && _outSize == 0) + const bool outFinished = (_outSizeDefined && _outProcessed >= _outSize); - if (res != 0 || _state.dicPos == next || finished || stopDecoding) + const bool needStop = (res != 0 + || (inProcessed == 0 && outProcessed == 0) + || status == LZMA_STATUS_FINISHED_WITH_MARK + || (outFinished && status != LZMA_STATUS_NEEDS_MORE_INPUT)); + + if (needStop || outProcessed >= size) { - HRESULT res2 = WriteStream(outStream, _state.dic + _wrPos, _state.dicPos - _wrPos); + const HRESULT res2 = WriteStream(outStream, _state.dic + wrPos, _state.dicPos - wrPos); - _wrPos = _state.dicPos; if (_state.dicPos == _state.dicBufSize) - { _state.dicPos = 0; - _wrPos = 0; - } - next = (_state.dicBufSize - _state.dicPos < _outBufSize) ? _state.dicBufSize : (_state.dicPos + _outBufSize); + wrPos = _state.dicPos; + + RINOK(res2) - if (res != 0) - return S_FALSE; - RINOK(res2); - if (stopDecoding) + if (needStop) { - if (status == LZMA_STATUS_NEEDS_MORE_INPUT) - NeedMoreInput = true; - if (FinishStream && - status != LZMA_STATUS_FINISHED_WITH_MARK && - status != LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK) + if (res != 0) + { + // return SResToHRESULT(res); return S_FALSE; - return S_OK; - } - if (finished) - { - if (status == LZMA_STATUS_NEEDS_MORE_INPUT) - NeedMoreInput = true; - return (status == LZMA_STATUS_FINISHED_WITH_MARK ? S_OK : S_FALSE); + } + + if (status == LZMA_STATUS_FINISHED_WITH_MARK) + { + if (FinishStream) + if (_outSizeDefined && _outSize != _outProcessed) + return S_FALSE; + return readRes; + } + + if (outFinished && status != LZMA_STATUS_NEEDS_MORE_INPUT) + if (!FinishStream || status == LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK) + return readRes; + + return S_FALSE; } } + if (progress) { - UInt64 inSize = _inSizeProcessed - startInProgress; - RINOK(progress->SetRatioInfo(&inSize, &_outSizeProcessed)); + const UInt64 inSize = _inProcessed - startInProgress; + RINOK(progress->SetRatioInfo(&inSize, &_outProcessed)) } } } -STDMETHODIMP CDecoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 * /* inSize */, const UInt64 *outSize, ICompressProgressInfo *progress) + +Z7_COM7F_IMF(CDecoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, + const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress)) { - if (_inBuf == 0) + if (!_inBuf) return E_INVALIDARG; SetOutStreamSize(outSize); - return CodeSpec(inStream, outStream, progress); + HRESULT res = CodeSpec(inStream, outStream, progress); + if (res == S_OK) + if (FinishStream && inSize && *inSize != _inProcessed) + res = S_FALSE; + return res; } -#ifndef NO_READ_FROM_CODER -STDMETHODIMP CDecoder::SetInStream(ISequentialInStream *inStream) { _inStream = inStream; return S_OK; } -STDMETHODIMP CDecoder::ReleaseInStream() { _inStream.Release(); return S_OK; } +#ifndef Z7_NO_READ_FROM_CODER -STDMETHODIMP CDecoder::Read(void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CDecoder::SetInStream(ISequentialInStream *inStream)) + { _inStream = inStream; return S_OK; } +Z7_COM7F_IMF(CDecoder::ReleaseInStream()) + { _inStream.Release(); return S_OK; } + +Z7_COM7F_IMF(CDecoder::Read(void *data, UInt32 size, UInt32 *processedSize)) { if (processedSize) *processedSize = 0; - do + + ELzmaFinishMode finishMode = LZMA_FINISH_ANY; + if (_outSizeDefined) { - if (_inPos == _inSize) + const UInt64 rem = _outSize - _outProcessed; + if (size >= rem) { - _inPos = _inSize = 0; - RINOK(_inStream->Read(_inBuf, _inBufSizeAllocated, &_inSize)); + size = (UInt32)rem; + if (FinishStream) + finishMode = LZMA_FINISH_END; } + } + + HRESULT readRes = S_OK; + + for (;;) + { + if (_inPos == _inLim && readRes == S_OK) { - SizeT inProcessed = _inSize - _inPos; + _inPos = _inLim = 0; + readRes = _inStream->Read(_inBuf, _inBufSize, &_inLim); + } - if (_outSizeDefined) - { - const UInt64 rem = _outSize - _outSizeProcessed; - if (rem < size) - size = (UInt32)rem; - } + SizeT inProcessed = _inLim - _inPos; + SizeT outProcessed = size; + ELzmaStatus status; + + const SRes res = LzmaDec_DecodeToBuf(&_state, (Byte *)data, &outProcessed, + _inBuf + _inPos, &inProcessed, finishMode, &status); + + _lzmaStatus = status; + _inPos += (UInt32)inProcessed; + _inProcessed += inProcessed; + _outProcessed += outProcessed; + size -= (UInt32)outProcessed; + data = (Byte *)data + outProcessed; + if (processedSize) + *processedSize += (UInt32)outProcessed; + + if (res != 0) + return S_FALSE; + + /* + if (status == LZMA_STATUS_FINISHED_WITH_MARK) + return readRes; - SizeT outProcessed = size; - ELzmaStatus status; - SRes res = LzmaDec_DecodeToBuf(&_state, (Byte *)data, &outProcessed, - _inBuf + _inPos, &inProcessed, LZMA_FINISH_ANY, &status); - _inPos += (UInt32)inProcessed; - _inSizeProcessed += inProcessed; - _outSizeProcessed += outProcessed; - size -= (UInt32)outProcessed; - data = (Byte *)data + outProcessed; - if (processedSize) - *processedSize += (UInt32)outProcessed; - RINOK(SResToHRESULT(res)); - if (inProcessed == 0 && outProcessed == 0) - return S_OK; + if (size == 0 && status != LZMA_STATUS_NEEDS_MORE_INPUT) + { + if (FinishStream + && _outSizeDefined && _outProcessed >= _outSize + && status != LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK) + return S_FALSE; + return readRes; } + */ + + if (inProcessed == 0 && outProcessed == 0) + return readRes; } - while (size != 0); - return S_OK; } + HRESULT CDecoder::CodeResume(ISequentialOutStream *outStream, const UInt64 *outSize, ICompressProgressInfo *progress) { SetOutStreamSizeResume(outSize); return CodeSpec(_inStream, outStream, progress); } + HRESULT CDecoder::ReadFromInputStream(void *data, UInt32 size, UInt32 *processedSize) { - RINOK(CreateInputBuffer()); + RINOK(CreateInputBuffer()) + if (processedSize) *processedSize = 0; - while (size > 0) + + HRESULT readRes = S_OK; + + while (size != 0) { - if (_inPos == _inSize) + if (_inPos == _inLim) { - _inPos = _inSize = 0; - RINOK(_inStream->Read(_inBuf, _inBufSizeAllocated, &_inSize)); - if (_inSize == 0) + _inPos = _inLim = 0; + if (readRes == S_OK) + readRes = _inStream->Read(_inBuf, _inBufSize, &_inLim); + if (_inLim == 0) break; } - { - UInt32 curSize = _inSize - _inPos; - if (curSize > size) - curSize = size; - memcpy(data, _inBuf + _inPos, curSize); - _inPos += curSize; - _inSizeProcessed += curSize; - size -= curSize; - data = (Byte *)data + curSize; - if (processedSize) - *processedSize += curSize; - } + + UInt32 cur = _inLim - _inPos; + if (cur > size) + cur = size; + memcpy(data, _inBuf + _inPos, cur); + _inPos += cur; + _inProcessed += cur; + size -= cur; + data = (Byte *)data + cur; + if (processedSize) + *processedSize += cur; } - return S_OK; + + return readRes; } #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/LzmaDecoder.h b/src/plugins/7zip/7za/cpp/7zip/Compress/LzmaDecoder.h index f1f839a4..095e76ff 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/LzmaDecoder.h +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/LzmaDecoder.h @@ -1,8 +1,9 @@ // LzmaDecoder.h -#ifndef __LZMA_DECODER_H -#define __LZMA_DECODER_H +#ifndef ZIP7_INC_LZMA_DECODER_H +#define ZIP7_INC_LZMA_DECODER_H +// #include "../../../C/Alloc.h" #include "../../../C/LzmaDec.h" #include "../../Common/MyCom.h" @@ -11,79 +12,100 @@ namespace NCompress { namespace NLzma { -class CDecoder: +class CDecoder Z7_final: public ICompressCoder, public ICompressSetDecoderProperties2, public ICompressSetFinishMode, + public ICompressGetInStreamProcessedSize, public ICompressSetBufSize, - #ifndef NO_READ_FROM_CODER + #ifndef Z7_NO_READ_FROM_CODER public ICompressSetInStream, public ICompressSetOutStreamSize, public ISequentialInStream, - #endif + #endif public CMyUnknownImp { - CMyComPtr _inStream; - Byte *_inBuf; - UInt32 _inPos; - UInt32 _inSize; - CLzmaDec _state; + Z7_COM_QI_BEGIN2(ICompressCoder) + Z7_COM_QI_ENTRY(ICompressSetDecoderProperties2) + Z7_COM_QI_ENTRY(ICompressSetFinishMode) + Z7_COM_QI_ENTRY(ICompressGetInStreamProcessedSize) + Z7_COM_QI_ENTRY(ICompressSetBufSize) + #ifndef Z7_NO_READ_FROM_CODER + Z7_COM_QI_ENTRY(ICompressSetInStream) + Z7_COM_QI_ENTRY(ICompressSetOutStreamSize) + Z7_COM_QI_ENTRY(ISequentialInStream) + #endif + Z7_COM_QI_END + Z7_COM_ADDREF_RELEASE + + Z7_IFACE_COM7_IMP(ICompressCoder) +public: + Z7_IFACE_COM7_IMP(ICompressSetDecoderProperties2) +private: + Z7_IFACE_COM7_IMP(ICompressSetFinishMode) + Z7_IFACE_COM7_IMP(ICompressGetInStreamProcessedSize) + // Z7_IFACE_COM7_IMP(ICompressSetOutStreamSize) + + Z7_IFACE_COM7_IMP(ICompressSetBufSize) + + #ifndef Z7_NO_READ_FROM_CODER +public: + Z7_IFACE_COM7_IMP(ICompressSetInStream) +private: + Z7_IFACE_COM7_IMP(ISequentialInStream) + Z7_IFACE_COM7_IMP(ICompressSetOutStreamSize) + #else + Z7_COM7F_IMF(SetOutStreamSize(const UInt64 *outSize)); + #endif + +public: + bool FinishStream; // set it before decoding, if you need to decode full LZMA stream +private: bool _propsWereSet; bool _outSizeDefined; - UInt64 _outSize; - UInt64 _inSizeProcessed; - UInt64 _outSizeProcessed; - UInt32 _inBufSizeAllocated; + UInt32 _outStep; UInt32 _inBufSize; - UInt32 _outBufSize; - SizeT _wrPos; + UInt32 _inBufSizeNew; + + ELzmaStatus _lzmaStatus; + UInt32 _inPos; + UInt32 _inLim; + Byte *_inBuf; + + UInt64 _outSize; + UInt64 _inProcessed; + UInt64 _outProcessed; + + // CAlignOffsetAlloc _alloc; + + CLzmaDec _state; HRESULT CreateInputBuffer(); HRESULT CodeSpec(ISequentialInStream *inStream, ISequentialOutStream *outStream, ICompressProgressInfo *progress); void SetOutStreamSizeResume(const UInt64 *outSize); + #ifndef Z7_NO_READ_FROM_CODER +private: + CMyComPtr _inStream; public: - MY_QUERYINTERFACE_BEGIN2(ICompressCoder) - MY_QUERYINTERFACE_ENTRY(ICompressSetDecoderProperties2) - MY_QUERYINTERFACE_ENTRY(ICompressSetFinishMode) - MY_QUERYINTERFACE_ENTRY(ICompressSetBufSize) - #ifndef NO_READ_FROM_CODER - MY_QUERYINTERFACE_ENTRY(ICompressSetInStream) - MY_QUERYINTERFACE_ENTRY(ICompressSetOutStreamSize) - MY_QUERYINTERFACE_ENTRY(ISequentialInStream) - #endif - MY_QUERYINTERFACE_END - MY_ADDREF_RELEASE - - STDMETHOD(Code)(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress); - STDMETHOD(SetDecoderProperties2)(const Byte *data, UInt32 size); - STDMETHOD(SetFinishMode)(UInt32 finishMode); - STDMETHOD(SetOutStreamSize)(const UInt64 *outSize); - STDMETHOD(SetInBufSize)(UInt32 streamIndex, UInt32 size); - STDMETHOD(SetOutBufSize)(UInt32 streamIndex, UInt32 size); - - #ifndef NO_READ_FROM_CODER - - STDMETHOD(SetInStream)(ISequentialInStream *inStream); - STDMETHOD(ReleaseInStream)(); - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); - HRESULT CodeResume(ISequentialOutStream *outStream, const UInt64 *outSize, ICompressProgressInfo *progress); HRESULT ReadFromInputStream(void *data, UInt32 size, UInt32 *processedSize); - UInt64 GetInputProcessedSize() const { return _inSizeProcessed; } - - #endif - - bool FinishStream; // set it before decoding, if you need to decode full LZMA stream - - bool NeedMoreInput; // it's set by decoder, if it needs more input data to decode stream + #endif +public: CDecoder(); - virtual ~CDecoder(); - - UInt64 GetOutputProcessedSize() const { return _outSizeProcessed; } + ~CDecoder(); + + UInt64 GetInputProcessedSize() const { return _inProcessed; } + UInt64 GetOutputProcessedSize() const { return _outProcessed; } + bool NeedsMoreInput() const { return _lzmaStatus == LZMA_STATUS_NEEDS_MORE_INPUT; } + bool CheckFinishStatus(bool withEndMark) const + { + return _lzmaStatus == (withEndMark ? + LZMA_STATUS_FINISHED_WITH_MARK : + LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK); + } }; }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/LzmaEncoder.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/LzmaEncoder.cpp index 0a7e294d..bca2eee5 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/LzmaEncoder.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/LzmaEncoder.cpp @@ -9,13 +9,28 @@ #include "LzmaEncoder.h" +// #define LOG_LZMA_THREADS + +#ifdef LOG_LZMA_THREADS + +#include + +#include "../../Common/IntToString.h" +#include "../../Windows/TimeUtils.h" + +EXTERN_C_BEGIN +void LzmaEnc_GetLzThreads(CLzmaEncHandle pp, HANDLE lz_threads[2]); +EXTERN_C_END + +#endif + namespace NCompress { namespace NLzma { CEncoder::CEncoder() { _encoder = NULL; - _encoder = LzmaEnc_Create(&g_Alloc); + _encoder = LzmaEnc_Create(&g_AlignedAlloc); if (!_encoder) throw 1; } @@ -23,49 +38,51 @@ CEncoder::CEncoder() CEncoder::~CEncoder() { if (_encoder) - LzmaEnc_Destroy(_encoder, &g_Alloc, &g_BigAlloc); + LzmaEnc_Destroy(_encoder, &g_AlignedAlloc, &g_BigAlloc); } -static inline wchar_t GetUpperChar(wchar_t c) +static inline wchar_t GetLowCharFast(wchar_t c) { - if (c >= 'a' && c <= 'z') - c -= 0x20; - return c; + return c |= 0x20; } static int ParseMatchFinder(const wchar_t *s, int *btMode, int *numHashBytes) { - wchar_t c = GetUpperChar(*s++); - if (c == L'H') + const wchar_t c = GetLowCharFast(*s++); + if (c == 'h') { - if (GetUpperChar(*s++) != L'C') + if (GetLowCharFast(*s++) != 'c') return 0; - int numHashBytesLoc = (int)(*s++ - L'0'); - if (numHashBytesLoc < 4 || numHashBytesLoc > 4) + const int num = (int)(*s++ - L'0'); + if (num < 4 || num > 5) return 0; if (*s != 0) return 0; *btMode = 0; - *numHashBytes = numHashBytesLoc; + *numHashBytes = num; return 1; } - if (c != L'B') + if (c != 'b') return 0; - if (GetUpperChar(*s++) != L'T') - return 0; - int numHashBytesLoc = (int)(*s++ - L'0'); - if (numHashBytesLoc < 2 || numHashBytesLoc > 4) - return 0; - if (*s != 0) - return 0; - *btMode = 1; - *numHashBytes = numHashBytesLoc; - return 1; + { + if (GetLowCharFast(*s++) != 't') + return 0; + const int num = (int)(*s++ - L'0'); + if (num < 2 || num > 5) + return 0; + if (*s != 0) + return 0; + *btMode = 1; + *numHashBytes = num; + return 1; + } } -#define SET_PROP_32(_id_, _dest_) case NCoderPropID::_id_: ep._dest_ = v; break; +#define SET_PROP_32(_id_, _dest_) case NCoderPropID::_id_: ep._dest_ = (int)v; break; +#define SET_PROP_32U(_id_, _dest_) case NCoderPropID::_id_: ep._dest_ = v; break; +HRESULT SetLzmaProp(PROPID propID, const PROPVARIANT &prop, CLzmaEncProps &ep); HRESULT SetLzmaProp(PROPID propID, const PROPVARIANT &prop, CLzmaEncProps &ep) { if (propID == NCoderPropID::kMatchFinder) @@ -74,25 +91,88 @@ HRESULT SetLzmaProp(PROPID propID, const PROPVARIANT &prop, CLzmaEncProps &ep) return E_INVALIDARG; return ParseMatchFinder(prop.bstrVal, &ep.btMode, &ep.numHashBytes) ? S_OK : E_INVALIDARG; } + + if (propID == NCoderPropID::kAffinity) + { + if (prop.vt == VT_UI8) + ep.affinity = prop.uhVal.QuadPart; + else + return E_INVALIDARG; + return S_OK; + } + + if (propID == NCoderPropID::kAffinityInGroup) + { + if (prop.vt == VT_UI8) + ep.affinityInGroup = prop.uhVal.QuadPart; + else + return E_INVALIDARG; + return S_OK; + } + + if (propID == NCoderPropID::kThreadGroup) + { + if (prop.vt == VT_UI4) + ep.affinityGroup = (Int32)(UInt32)prop.ulVal; + else + return E_INVALIDARG; + return S_OK; + } + + if (propID == NCoderPropID::kHashBits) + { + if (prop.vt == VT_UI4) + ep.numHashOutBits = prop.ulVal; + else + return E_INVALIDARG; + return S_OK; + } + if (propID > NCoderPropID::kReduceSize) return S_OK; + if (propID == NCoderPropID::kReduceSize) { if (prop.vt == VT_UI8) ep.reduceSize = prop.uhVal.QuadPart; + else + return E_INVALIDARG; return S_OK; } + + if (propID == NCoderPropID::kDictionarySize) + { + if (prop.vt == VT_UI8) + { + // 21.03 : we support 64-bit VT_UI8 for dictionary and (dict == 4 GiB) + const UInt64 v = prop.uhVal.QuadPart; + if (v > ((UInt64)1 << 32)) + return E_INVALIDARG; + UInt32 dict; + if (v == ((UInt64)1 << 32)) + dict = (UInt32)(Int32)-1; + else + dict = (UInt32)v; + ep.dictSize = dict; + return S_OK; + } + } + if (prop.vt != VT_UI4) return E_INVALIDARG; - UInt32 v = prop.ulVal; + const UInt32 v = prop.ulVal; switch (propID) { - case NCoderPropID::kDefaultProp: if (v > 31) return E_INVALIDARG; ep.dictSize = (UInt32)1 << (unsigned)v; break; + case NCoderPropID::kDefaultProp: + if (v > 32) + return E_INVALIDARG; + ep.dictSize = (v == 32) ? (UInt32)(Int32)-1 : (UInt32)1 << (unsigned)v; + break; SET_PROP_32(kLevel, level) SET_PROP_32(kNumFastBytes, fb) - SET_PROP_32(kMatchFinderCycles, mc) + SET_PROP_32U(kMatchFinderCycles, mc) SET_PROP_32(kAlgorithm, algo) - SET_PROP_32(kDictionarySize, dictSize) + SET_PROP_32U(kDictionarySize, dictSize) SET_PROP_32(kPosStateBits, pb) SET_PROP_32(kLitPosBits, lp) SET_PROP_32(kLitContextBits, lc) @@ -102,8 +182,8 @@ HRESULT SetLzmaProp(PROPID propID, const PROPVARIANT &prop, CLzmaEncProps &ep) return S_OK; } -STDMETHODIMP CEncoder::SetCoderProperties(const PROPID *propIDs, - const PROPVARIANT *coderProps, UInt32 numProps) +Z7_COM7F_IMF(CEncoder::SetCoderProperties(const PROPID *propIDs, + const PROPVARIANT *coderProps, UInt32 numProps)) { CLzmaEncProps props; LzmaEncProps_Init(&props); @@ -111,41 +191,183 @@ STDMETHODIMP CEncoder::SetCoderProperties(const PROPID *propIDs, for (UInt32 i = 0; i < numProps; i++) { const PROPVARIANT &prop = coderProps[i]; - PROPID propID = propIDs[i]; + const PROPID propID = propIDs[i]; switch (propID) { case NCoderPropID::kEndMarker: - if (prop.vt != VT_BOOL) return E_INVALIDARG; props.writeEndMark = (prop.boolVal != VARIANT_FALSE); break; + if (prop.vt != VT_BOOL) + return E_INVALIDARG; + props.writeEndMark = (prop.boolVal != VARIANT_FALSE); + break; default: - RINOK(SetLzmaProp(propID, prop, props)); + RINOK(SetLzmaProp(propID, prop, props)) } } return SResToHRESULT(LzmaEnc_SetProps(_encoder, &props)); } -STDMETHODIMP CEncoder::WriteCoderProperties(ISequentialOutStream *outStream) + +Z7_COM7F_IMF(CEncoder::SetCoderPropertiesOpt(const PROPID *propIDs, + const PROPVARIANT *coderProps, UInt32 numProps)) +{ + for (UInt32 i = 0; i < numProps; i++) + { + const PROPVARIANT &prop = coderProps[i]; + const PROPID propID = propIDs[i]; + if (propID == NCoderPropID::kExpectedDataSize) + if (prop.vt == VT_UI8) + LzmaEnc_SetDataSize(_encoder, prop.uhVal.QuadPart); + } + return S_OK; +} + + +Z7_COM7F_IMF(CEncoder::WriteCoderProperties(ISequentialOutStream *outStream)) { Byte props[LZMA_PROPS_SIZE]; - size_t size = LZMA_PROPS_SIZE; - RINOK(LzmaEnc_WriteProperties(_encoder, props, &size)); + SizeT size = LZMA_PROPS_SIZE; + RINOK(LzmaEnc_WriteProperties(_encoder, props, &size)) return WriteStream(outStream, props, size); } -STDMETHODIMP CEncoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 * /* inSize */, const UInt64 * /* outSize */, ICompressProgressInfo *progress) + +#define RET_IF_WRAP_ERROR(wrapRes, sRes, sResErrorCode) \ + if (wrapRes != S_OK /* && (sRes == SZ_OK || sRes == sResErrorCode) */) return wrapRes; + + + +#ifdef LOG_LZMA_THREADS + +static inline UInt64 GetTime64(const FILETIME &t) { return ((UInt64)t.dwHighDateTime << 32) | t.dwLowDateTime; } + +static void PrintNum(UInt64 val, unsigned numDigits, char c = ' ') +{ + char temp[64]; + char *p = temp + 32; + ConvertUInt64ToString(val, p); + unsigned len = (unsigned)strlen(p); + for (; len < numDigits; len++) + *--p = c; + printf("%s", p); +} + +static void PrintTime(const char *s, UInt64 val, UInt64 total) { - CSeqInStreamWrap inWrap(inStream); - CSeqOutStreamWrap outWrap(outStream); - CCompressProgressWrap progressWrap(progress); + printf(" %s :", s); + const UInt32 kFreq = 10000000; + UInt64 sec = val / kFreq; + PrintNum(sec, 6); + printf(" ."); + UInt32 ms = (UInt32)(val - (sec * kFreq)) / (kFreq / 1000); + PrintNum(ms, 3, '0'); + + while (val > ((UInt64)1 << 56)) + { + val >>= 1; + total >>= 1; + } + + UInt64 percent = 0; + if (total != 0) + percent = val * 100 / total; + printf(" ="); + PrintNum(percent, 4); + printf("%%"); +} + + +struct CBaseStat +{ + UInt64 kernelTime, userTime; + + BOOL Get(HANDLE thread, const CBaseStat *prevStat) + { + FILETIME creationTimeFT, exitTimeFT, kernelTimeFT, userTimeFT; + BOOL res = GetThreadTimes(thread + , &creationTimeFT, &exitTimeFT, &kernelTimeFT, &userTimeFT); + if (res) + { + kernelTime = GetTime64(kernelTimeFT); + userTime = GetTime64(userTimeFT); + if (prevStat) + { + kernelTime -= prevStat->kernelTime; + userTime -= prevStat->userTime; + } + } + return res; + } +}; + + +static void PrintStat(HANDLE thread, UInt64 totalTime, const CBaseStat *prevStat) +{ + CBaseStat newStat; + if (!newStat.Get(thread, prevStat)) + return; + + PrintTime("K", newStat.kernelTime, totalTime); + + const UInt64 processTime = newStat.kernelTime + newStat.userTime; + + PrintTime("U", newStat.userTime, totalTime); + PrintTime("S", processTime, totalTime); + printf("\n"); + // PrintTime("G ", totalTime, totalTime); +} + +#endif + + + +Z7_COM7F_IMF(CEncoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, + const UInt64 * /* inSize */, const UInt64 * /* outSize */, ICompressProgressInfo *progress)) +{ + CSeqInStreamWrap inWrap; + CSeqOutStreamWrap outWrap; + CCompressProgressWrap progressWrap; + + inWrap.Init(inStream); + outWrap.Init(outStream); + progressWrap.Init(progress); + + #ifdef LOG_LZMA_THREADS + + FILETIME startTimeFT; + NWindows::NTime::GetCurUtcFileTime(startTimeFT); + UInt64 totalTime = GetTime64(startTimeFT); + CBaseStat oldStat; + if (!oldStat.Get(GetCurrentThread(), NULL)) + return E_FAIL; + + #endif + + + SRes res = LzmaEnc_Encode(_encoder, &outWrap.vt, &inWrap.vt, + progress ? &progressWrap.vt : NULL, &g_AlignedAlloc, &g_BigAlloc); - SRes res = LzmaEnc_Encode(_encoder, &outWrap.p, &inWrap.p, progress ? &progressWrap.p : NULL, &g_Alloc, &g_BigAlloc); _inputProcessed = inWrap.Processed; - if (res == SZ_ERROR_READ && inWrap.Res != S_OK) - return inWrap.Res; - if (res == SZ_ERROR_WRITE && outWrap.Res != S_OK) - return outWrap.Res; - if (res == SZ_ERROR_PROGRESS && progressWrap.Res != S_OK) - return progressWrap.Res; + + RET_IF_WRAP_ERROR(inWrap.Res, res, SZ_ERROR_READ) + RET_IF_WRAP_ERROR(outWrap.Res, res, SZ_ERROR_WRITE) + RET_IF_WRAP_ERROR(progressWrap.Res, res, SZ_ERROR_PROGRESS) + + + #ifdef LOG_LZMA_THREADS + + NWindows::NTime::GetCurUtcFileTime(startTimeFT); + totalTime = GetTime64(startTimeFT) - totalTime; + HANDLE lz_threads[2]; + LzmaEnc_GetLzThreads(_encoder, lz_threads); + printf("\n"); + printf("Main: "); PrintStat(GetCurrentThread(), totalTime, &oldStat); + printf("Hash: "); PrintStat(lz_threads[0], totalTime, NULL); + printf("BinT: "); PrintStat(lz_threads[1], totalTime, NULL); + // PrintTime("Total: ", totalTime, totalTime); + printf("\n"); + + #endif + return SResToHRESULT(res); } diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/LzmaEncoder.h b/src/plugins/7zip/7za/cpp/7zip/Compress/LzmaEncoder.h index f919ac2c..2836d7df 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/LzmaEncoder.h +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/LzmaEncoder.h @@ -1,7 +1,7 @@ // LzmaEncoder.h -#ifndef __LZMA_ENCODER_H -#define __LZMA_ENCODER_H +#ifndef ZIP7_INC_LZMA_ENCODER_H +#define ZIP7_INC_LZMA_ENCODER_H #include "../../../C/LzmaEnc.h" @@ -12,25 +12,32 @@ namespace NCompress { namespace NLzma { -class CEncoder: +class CEncoder Z7_final: public ICompressCoder, public ICompressSetCoderProperties, public ICompressWriteCoderProperties, + public ICompressSetCoderPropertiesOpt, public CMyUnknownImp { + Z7_COM_UNKNOWN_IMP_4( + ICompressCoder, + ICompressSetCoderProperties, + ICompressWriteCoderProperties, + ICompressSetCoderPropertiesOpt) + Z7_IFACE_COM7_IMP(ICompressCoder) +public: + Z7_IFACE_COM7_IMP(ICompressSetCoderProperties) + Z7_IFACE_COM7_IMP(ICompressWriteCoderProperties) + Z7_IFACE_COM7_IMP(ICompressSetCoderPropertiesOpt) + CLzmaEncHandle _encoder; UInt64 _inputProcessed; -public: - MY_UNKNOWN_IMP3(ICompressCoder, ICompressSetCoderProperties, ICompressWriteCoderProperties) - - STDMETHOD(Code)(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress); - STDMETHOD(SetCoderProperties)(const PROPID *propIDs, const PROPVARIANT *props, UInt32 numProps); - STDMETHOD(WriteCoderProperties)(ISequentialOutStream *outStream); CEncoder(); - virtual ~CEncoder(); + ~CEncoder(); + UInt64 GetInputProcessedSize() const { return _inputProcessed; } + bool IsWriteEndMark() const { return LzmaEnc_IsWriteEndMark(_encoder) != 0; } }; }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/LzmaRegister.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/LzmaRegister.cpp index c802a99f..887f7a2d 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/LzmaRegister.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/LzmaRegister.cpp @@ -6,7 +6,7 @@ #include "LzmaDecoder.h" -#ifndef EXTRACT_ONLY +#ifndef Z7_EXTRACT_ONLY #include "LzmaEncoder.h" #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/LzmsDecoder.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/LzmsDecoder.cpp index 0e823446..353798a0 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/LzmsDecoder.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/LzmsDecoder.cpp @@ -10,6 +10,61 @@ namespace NCompress { namespace NLzms { +class CBitDecoder +{ +public: + const Byte *_buf; + unsigned _bitPos; + + void Init(const Byte *buf, size_t size) throw() + { + _buf = buf + size; + _bitPos = 0; + } + + Z7_FORCE_INLINE + UInt32 GetValue(unsigned numBits) const + { + UInt32 v = + ((UInt32)_buf[-1] << 16) | + ((UInt32)_buf[-2] << 8) | + (UInt32)_buf[-3]; + v >>= 24 - numBits - _bitPos; + return v & ((1u << numBits) - 1); + } + + Z7_FORCE_INLINE + UInt32 GetValue_InHigh32bits() + { + return GetUi32(_buf - 4) << _bitPos; + } + + void MovePos(unsigned numBits) + { + _bitPos += numBits; + _buf -= (_bitPos >> 3); + _bitPos &= 7; + } + + UInt32 ReadBits32(unsigned numBits) + { + UInt32 mask = (((UInt32)1 << numBits) - 1); + numBits += _bitPos; + const Byte *buf = _buf; + UInt32 v = GetUi32(buf - 4); + if (numBits > 32) + { + v <<= (numBits - 32); + v |= (UInt32)buf[-5] >> (40 - numBits); + } + else + v >>= (32 - numBits); + _buf = buf - (numBits >> 3); + _bitPos = numBits & 7; + return v & mask; + } +}; + static UInt32 g_PosBases[k_NumPosSyms /* + 1 */]; static Byte g_PosDirectBits[k_NumPosSyms]; @@ -77,7 +132,7 @@ static unsigned GetNumPosSlots(size_t size) unsigned right = k_NumPosSyms; for (;;) { - unsigned m = (left + right) / 2; + const unsigned m = (left + right) / 2; if (left == m) return m + 1; if (size >= g_PosBases[m]) @@ -91,7 +146,7 @@ static unsigned GetNumPosSlots(size_t size) static const Int32 k_x86_WindowSize = 65535; static const Int32 k_x86_TransOffset = 1023; -static const size_t k_x86_HistorySize = (1 << 16); +static const size_t k_x86_HistorySize = 1 << 16; static void x86_Filter(Byte *data, UInt32 size, Int32 *history) { @@ -114,8 +169,8 @@ static void x86_Filter(Byte *data, UInt32 size, Int32 *history) size -= 16; const unsigned kSave = 6; - const Byte savedByte = data[size + kSave]; - data[size + kSave] = 0xE8; + const Byte savedByte = data[(size_t)size + kSave]; + data[(size_t)size + kSave] = 0xE8; Int32 last_x86_pos = -k_x86_TransOffset - 1; // first byte is ignored @@ -123,7 +178,7 @@ static void x86_Filter(Byte *data, UInt32 size, Int32 *history) for (;;) { - const Byte *p = data + (UInt32)i; + Byte *p = data + (UInt32)i; for (;;) { @@ -139,31 +194,19 @@ static void x86_Filter(Byte *data, UInt32 size, Int32 *history) Int32 maxTransOffset = k_x86_TransOffset; - Byte b = p[0]; + const Byte b = p[0]; - if (b == 0x48) + if ((b & 0x80) == 0) // REX (0x48 or 0x4c) { - if (p[1] == 0x8B) + const unsigned b2 = p[2] - 0x5; // [RIP + disp32] + if (b2 & 0x7) + continue; + if (p[1] != 0x8d) // LEA { - if ((p[2] & 0xF7) != 0x5) + if (p[1] != 0x8b || b != 0x48 || (b2 & 0xf7)) continue; // MOV RAX / RCX, [RIP + disp32] } - else if (p[1] == 0x8D) // LEA - { - if ((p[2] & 0x7) != 0x5) - continue; - // LEA R**, [] - } - else - continue; - codeLen = 3; - } - else if (b == 0x4C) - { - if (p[1] != 0x8D || (p[2] & 0x7) != 0x5) - continue; - // LEA R*, [] codeLen = 3; } else if (b == 0xE8) @@ -198,29 +241,29 @@ static void x86_Filter(Byte *data, UInt32 size, Int32 *history) Int32 *target; { - const Byte *p2 = p + codeLen; + Byte *p2 = p + codeLen; UInt32 n = GetUi32(p2); if (i - last_x86_pos <= maxTransOffset) { - n -= i; - SetUi32(p2, n); + n = (UInt32)((Int32)n - i); + SetUi32(p2, n) } target = history + (((UInt32)i + n) & 0xFFFF); } - i += codeLen + sizeof(UInt32) - 1; + i += (Int32)(codeLen + sizeof(UInt32) - 1); if (i - *target <= k_x86_WindowSize) last_x86_pos = i; *target = i; } - data[size + kSave] = savedByte; + data[(size_t)size + kSave] = savedByte; } -static const int kLenIdNeedInit = -2; +// static const int kLenIdNeedInit = -2; CDecoder::CDecoder(): _x86_history(NULL) @@ -232,7 +275,7 @@ CDecoder::~CDecoder() ::MidFree(_x86_history); } -#define RIF(x) { if (!(x)) return false; } +// #define RIF(x) { if (!(x)) return false; } #define LIMIT_CHECK if (_bs._buf < _rc.cur) return S_FALSE; // #define LIMIT_CHECK @@ -318,8 +361,8 @@ HRESULT CDecoder::CodeReal(const Byte *in, size_t inSize, Byte *_win, size_t out { if (_rc.Decode(&mainState, k_NumMainProbs, mainProbs) == 0) { - UInt32 number; - HUFF_DEC(number, m_LitDecoder); + unsigned number; + HUFF_DEC(number, m_LitDecoder) LIMIT_CHECK _win[_pos++] = (Byte)number; prevType = 0; @@ -330,13 +373,13 @@ HRESULT CDecoder::CodeReal(const Byte *in, size_t inSize, Byte *_win, size_t out if (_rc.Decode(&lzRepStates[0], k_NumRepProbs, lzRepProbs[0]) == 0) { - UInt32 number; - HUFF_DEC(number, m_PosDecoder); + unsigned number; + HUFF_DEC(number, m_PosDecoder) LIMIT_CHECK - unsigned numDirectBits = g_PosDirectBits[number]; + const unsigned numDirectBits = g_PosDirectBits[number]; distance = g_PosBases[number]; - READ_BITS_CHECK(numDirectBits); + READ_BITS_CHECK(numDirectBits) distance += _bs.ReadBits32(numDirectBits); // LIMIT_CHECK _reps[3] = _reps[2]; @@ -393,14 +436,14 @@ HRESULT CDecoder::CodeReal(const Byte *in, size_t inSize, Byte *_win, size_t out } } - UInt32 lenSlot; - HUFF_DEC(lenSlot, m_LenDecoder); + unsigned lenSlot; + HUFF_DEC(lenSlot, m_LenDecoder) LIMIT_CHECK UInt32 len = g_LenBases[lenSlot]; { - unsigned numDirectBits = k_LenDirectBits[lenSlot]; - READ_BITS_CHECK(numDirectBits); + const unsigned numDirectBits = k_LenDirectBits[lenSlot]; + READ_BITS_CHECK(numDirectBits) len += _bs.ReadBits32(numDirectBits); } // LIMIT_CHECK @@ -424,21 +467,21 @@ HRESULT CDecoder::CodeReal(const Byte *in, size_t inSize, Byte *_win, size_t out { UInt64 distance; - UInt32 power; + unsigned power; UInt32 distance32; if (_rc.Decode(&deltaRepStates[0], k_NumRepProbs, deltaRepProbs[0]) == 0) { - HUFF_DEC(power, m_PowerDecoder); + HUFF_DEC(power, m_PowerDecoder) LIMIT_CHECK - UInt32 number; - HUFF_DEC(number, m_DeltaDecoder); + unsigned number; + HUFF_DEC(number, m_DeltaDecoder) LIMIT_CHECK - unsigned numDirectBits = g_PosDirectBits[number]; + const unsigned numDirectBits = g_PosDirectBits[number]; distance32 = g_PosBases[number]; - READ_BITS_CHECK(numDirectBits); + READ_BITS_CHECK(numDirectBits) distance32 += _bs.ReadBits32(numDirectBits); // LIMIT_CHECK @@ -500,16 +543,16 @@ HRESULT CDecoder::CodeReal(const Byte *in, size_t inSize, Byte *_win, size_t out power = (UInt32)(_deltaReps[0] >> 32); } - UInt32 dist = (distance32 << power); + const UInt32 dist = (distance32 << power); - UInt32 lenSlot; - HUFF_DEC(lenSlot, m_LenDecoder); + unsigned lenSlot; + HUFF_DEC(lenSlot, m_LenDecoder) LIMIT_CHECK UInt32 len = g_LenBases[lenSlot]; { - unsigned numDirectBits = k_LenDirectBits[lenSlot]; - READ_BITS_CHECK(numDirectBits); + const unsigned numDirectBits = k_LenDirectBits[lenSlot]; + READ_BITS_CHECK(numDirectBits) len += _bs.ReadBits32(numDirectBits); } // LIMIT_CHECK @@ -517,9 +560,9 @@ HRESULT CDecoder::CodeReal(const Byte *in, size_t inSize, Byte *_win, size_t out if (len > outSize - _pos) return S_FALSE; - if (dist > _pos) - return S_FALSE; size_t span = (size_t)1 << power; + if ((UInt64)dist + span > _pos) + return S_FALSE; Byte *dest = _win + _pos - span; const Byte *src = dest - dist; _pos += len; @@ -539,8 +582,8 @@ HRESULT CDecoder::CodeReal(const Byte *in, size_t inSize, Byte *_win, size_t out _rc.Normalize(); if (_rc.code != 0) return S_FALSE; - if (_rc.cur > _bs._buf || - _rc.cur == _bs._buf && _bs._bitPos != 0) + if (_rc.cur > _bs._buf + || (_rc.cur == _bs._buf && _bs._bitPos != 0)) return S_FALSE; /* diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/LzmsDecoder.h b/src/plugins/7zip/7za/cpp/7zip/Compress/LzmsDecoder.h index 510d3389..bb21262d 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/LzmsDecoder.h +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/LzmsDecoder.h @@ -1,77 +1,17 @@ // LzmsDecoder.h // The code is based on LZMS description from wimlib code -#ifndef __LZMS_DECODER_H -#define __LZMS_DECODER_H - -// #define SHOW_DEBUG_INFO - -#ifdef SHOW_DEBUG_INFO -#include -#define PRF(x) x -#else -// #define PRF(x) -#endif +#ifndef ZIP7_INC_LZMS_DECODER_H +#define ZIP7_INC_LZMS_DECODER_H #include "../../../C/CpuArch.h" #include "../../../C/HuffEnc.h" -#include "../../Common/MyBuffer.h" -#include "../../Common/MyCom.h" - -#include "../ICoder.h" - #include "HuffmanDecoder.h" namespace NCompress { namespace NLzms { -class CBitDecoder -{ -public: - const Byte *_buf; - unsigned _bitPos; - - void Init(const Byte *buf, size_t size) throw() - { - _buf = buf + size; - _bitPos = 0; - } - - UInt32 GetValue(unsigned numBits) const - { - UInt32 v = ((UInt32)_buf[-1] << 16) | ((UInt32)_buf[-2] << 8) | (UInt32)_buf[-3]; - v >>= (24 - numBits - _bitPos); - return v & ((1 << numBits) - 1); - } - - void MovePos(unsigned numBits) - { - _bitPos += numBits; - _buf -= (_bitPos >> 3); - _bitPos &= 7; - } - - UInt32 ReadBits32(unsigned numBits) - { - UInt32 mask = (((UInt32)1 << numBits) - 1); - numBits += _bitPos; - const Byte *buf = _buf; - UInt32 v = GetUi32(buf - 4); - if (numBits > 32) - { - v <<= (numBits - 32); - v |= (UInt32)buf[-5] >> (40 - numBits); - } - else - v >>= (32 - numBits); - _buf = buf - (numBits >> 3); - _bitPos = numBits & 7; - return v & mask; - } -}; - - const unsigned k_NumLitSyms = 256; const unsigned k_NumLenSyms = 54; const unsigned k_NumPosSyms = 799; @@ -106,18 +46,17 @@ class CHuffDecoder: public NCompress::NHuffman::CDecoderBuildFull(levels, NumSyms); + + this->Build(levels, /* NumSyms, */ NHuffman::k_BuildMode_Full); } void Rebuild() throw() { Generate(); RebuildRem = m_RebuildFreq; - UInt32 num = NumSyms; + const UInt32 num = NumSyms; for (UInt32 i = 0; i < num; i++) Freqs[i] = (Freqs[i] >> 1) + 1; } @@ -158,7 +97,7 @@ struct CProbEntry void Update(unsigned bit) throw() { - Prob += (Int32)(Hist >> (k_ProbLimit - 1)) - (Int32)bit; + Prob += (UInt32)((Int32)(Hist >> (k_ProbLimit - 1)) - (Int32)bit); Hist = (Hist << 1) | bit; } }; @@ -197,7 +136,7 @@ struct CRangeDecoder CProbEntry *entry = &probs[st]; st = (st << 1) & (numStates - 1); - UInt32 prob = entry->GetProb(); + const UInt32 prob = entry->GetProb(); if (range <= 0xFFFF) { @@ -208,7 +147,7 @@ struct CRangeDecoder cur += 2; } - UInt32 bound = (range >> k_NumProbBits) * prob; + const UInt32 bound = (range >> k_NumProbBits) * prob; if (code < bound) { @@ -232,7 +171,6 @@ struct CRangeDecoder class CDecoder { // CRangeDecoder _rc; - // CBitDecoder _bs; size_t _pos; UInt32 _reps[k_NumReps + 1]; @@ -263,7 +201,7 @@ class CDecoder ~CDecoder(); HRESULT Code(const Byte *in, size_t inSize, Byte *out, size_t outSize); - const size_t GetUnpackSize() const { return _pos; } + size_t GetUnpackSize() const { return _pos; } }; }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/Lzx.h b/src/plugins/7zip/7za/cpp/7zip/Compress/Lzx.h index 29ca4cac..39fca0c8 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/Lzx.h +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/Lzx.h @@ -1,7 +1,9 @@ // Lzx.h -#ifndef __COMPRESS_LZX_H -#define __COMPRESS_LZX_H +#ifndef ZIP7_INC_COMPRESS_LZX_H +#define ZIP7_INC_COMPRESS_LZX_H + +#include "../../Common/MyTypes.h" namespace NCompress { namespace NLzx { @@ -50,7 +52,9 @@ const unsigned kNumDictBits_Max = 21; const UInt32 kDictSize_Max = (UInt32)1 << kNumDictBits_Max; const unsigned kNumLinearPosSlotBits = 17; -const unsigned kNumPowerPosSlots = 38; +// const unsigned kNumPowerPosSlots = 38; +// const unsigned kNumPowerPosSlots = (kNumLinearPosSlotBits + 1) * 2; // non-including two first linear slots. +const unsigned kNumPowerPosSlots = (kNumLinearPosSlotBits + 2) * 2; // including two first linear slots. }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/LzxDecoder.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/LzxDecoder.cpp index 55231ce5..a7b9986c 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/LzxDecoder.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/LzxDecoder.cpp @@ -3,6 +3,7 @@ #include "StdAfx.h" #include +// #include // #define SHOW_DEBUG_INFO @@ -14,448 +15,1433 @@ #endif #include "../../../C/Alloc.h" +#include "../../../C/RotateDefs.h" +#include "../../../C/CpuArch.h" #include "LzxDecoder.h" + +#ifdef MY_CPU_X86_OR_AMD64 +#if defined(MY_CPU_AMD64) \ + || defined(__SSE2__) \ + || defined(_M_IX86_FP) && (_M_IX86_FP >= 2) \ + || 0 && defined(_MSC_VER) && (_MSC_VER >= 1400) // set (1 &&) for debug + +#if defined(__clang__) && (__clang_major__ >= 2) \ + || defined(__GNUC__) && (__GNUC__ >= 4) \ + || defined(_MSC_VER) && (_MSC_VER >= 1400) +#define Z7_LZX_X86_FILTER_USE_SSE2 +#endif +#endif +#endif + + +#ifdef Z7_LZX_X86_FILTER_USE_SSE2 +// #ifdef MY_CPU_X86_OR_AMD64 +#include // SSE2 +// #endif + #if defined(__clang__) || defined(__GNUC__) + typedef int ctz_type; + #define MY_CTZ(dest, mask) dest = __builtin_ctz((UInt32)(mask)) + #else // #if defined(_MSC_VER) + #if (_MSC_VER >= 1600) + // #include + #endif + typedef unsigned long ctz_type; + #define MY_CTZ(dest, mask) _BitScanForward(&dest, (mask)); + #endif // _MSC_VER +#endif + +// when window buffer is filled, we must wrap position to zero, +// and we want to wrap at same points where original-lzx must wrap. +// But the wrapping is possible in point where chunk is finished. +// Usually (chunk_size == 32KB), but (chunk_size != 32KB) also is allowed. +// So we don't use additional buffer space over required (winSize). +// And we can't use large overwrite after (len) in CopyLzMatch(). +// But we are allowed to write 3 bytes after (len), because +// (delta <= _winSize - 3). + +// #define k_Lz_OverwriteSize 0 // for debug : to disable overwrite +#define k_Lz_OverwriteSize 3 // = kNumReps +#if k_Lz_OverwriteSize > 0 +// (k_Lz_OutBufSize_Add >= k_Lz_OverwriteSize) is required +// we use value 4 to simplify memset() code. +#define k_Lz_OutBufSize_Add (k_Lz_OverwriteSize + 1) // == 4 +#else +#define k_Lz_OutBufSize_Add 0 +#endif + +// (len != 0) +// (0 < delta <= _winSize - 3) +Z7_FORCE_INLINE +void CopyLzMatch(Byte *dest, const Byte *src, UInt32 len, UInt32 delta) +{ + if (delta >= 4) + { +#if k_Lz_OverwriteSize >= 3 + // optimized code with overwrite to reduce the number of branches + #ifdef MY_CPU_LE_UNALIGN + *(UInt32 *)(void *)(dest) = *(const UInt32 *)(const void *)(src); + #else + dest[0] = src[0]; + dest[1] = src[1]; + dest[2] = src[2]; + dest[3] = src[3]; + #endif + len--; + src++; + dest++; + { +#else + // no overwrite in out buffer + dest[0] = src[0]; + { + const unsigned m = (unsigned)len & 1; + src += m; + dest += m; + } + if (len &= ~(unsigned)1) + { + dest[0] = src[0]; + dest[1] = src[1]; +#endif + // len == 0 is allowed here + { + const unsigned m = (unsigned)len & 3; + src += m; + dest += m; + } + if (len &= ~(unsigned)3) + { +#ifdef MY_CPU_LE_UNALIGN + #if 1 + *(UInt32 *)(void *)(dest) = *(const UInt32 *)(const void *)(src); + { + const unsigned m = (unsigned)len & 7; + dest += m; + src += m; + } + if (len &= ~(unsigned)7) + do + { + *(UInt32 *)(void *)(dest ) = *(const UInt32 *)(const void *)(src); + *(UInt32 *)(void *)(dest + 4) = *(const UInt32 *)(const void *)(src + 4); + src += 8; + dest += 8; + } + while (len -= 8); + #else + // gcc-11 -O3 for x64 generates incorrect code here + do + { + *(UInt32 *)(void *)(dest) = *(const UInt32 *)(const void *)(src); + src += 4; + dest += 4; + } + while (len -= 4); + #endif +#else + do + { + const Byte b0 = src[0]; + const Byte b1 = src[1]; + dest[0] = b0; + dest[1] = b1; + const Byte b2 = src[2]; + const Byte b3 = src[3]; + dest[2] = b2; + dest[3] = b3; + src += 4; + dest += 4; + } + while (len -= 4); +#endif + } + } + } + else // (delta < 4) + { + const unsigned b0 = *src; + *dest = (Byte)b0; + if (len >= 2) + { + if (delta < 2) + { + dest += (unsigned)len & 1; + dest[0] = (Byte)b0; + dest[1] = (Byte)b0; + dest += (unsigned)len & 2; + if (len &= ~(unsigned)3) + { +#ifdef MY_CPU_LE_UNALIGN + #ifdef MY_CPU_64BIT + const UInt64 a = (UInt64)b0 * 0x101010101010101; + *(UInt32 *)(void *)dest = (UInt32)a; + dest += (unsigned)len & 7; + if (len &= ~(unsigned)7) + { + // *(UInt64 *)(void *)dest = a; + // dest += 8; + // len -= 8; + // if (len) + { + // const ptrdiff_t delta = (ptrdiff_t)dest & 7; + // dest -= delta; + do + { + *(UInt64 *)(void *)dest = a; + dest += 8; + } + while (len -= 8); + // dest += delta - 8; + // *(UInt64 *)(void *)dest = a; + } + } + #else + const UInt32 a = (UInt32)b0 * 0x1010101; + do + { + *(UInt32 *)(void *)dest = a; + dest += 4; + } + while (len -= 4); + #endif +#else + do + { + dest[0] = (Byte)b0; + dest[1] = (Byte)b0; + dest[2] = (Byte)b0; + dest[3] = (Byte)b0; + dest += 4; + } + while (len -= 4); +#endif + } + } + else if (delta == 2) + { + const unsigned m = (unsigned)len & 1; + len &= ~(unsigned)1; + src += m; + dest += m; + { + const Byte a0 = src[0]; + const Byte a1 = src[1]; + do + { + dest[0] = a0; + dest[1] = a1; + dest += 2; + } + while (len -= 2); + } + } + else /* if (delta == 3) */ + { + const unsigned b1 = src[1]; + dest[1] = (Byte)b1; + if (len -= 2) + { + const unsigned b2 = src[2]; + dest += 2; + do + { + dest[0] = (Byte)b2; if (--len == 0) break; + dest[1] = (Byte)b0; if (--len == 0) break; + dest[2] = (Byte)b1; + dest += 3; + } + while (--len); + } + } + } + } +} + +// #define Z7_LZX_SHOW_STAT +#ifdef Z7_LZX_SHOW_STAT +#include +#endif + namespace NCompress { namespace NLzx { -static void x86_Filter(Byte *data, UInt32 size, UInt32 processedSize, UInt32 translationSize) +// #define Z7_LZX_SHOW_STAT +#ifdef Z7_LZX_SHOW_STAT +static UInt32 g_stats_Num_x86[3]; +static UInt32 g_stats_NumTables; +static UInt32 g_stats_NumLits; +static UInt32 g_stats_NumAlign; +static UInt32 g_stats_main[kMainTableSize]; +static UInt32 g_stats_len[kNumLenSymbols]; +static UInt32 g_stats_main_levels[kNumHuffmanBits + 1]; +static UInt32 g_stats_len_levels[kNumHuffmanBits + 1]; +#define UPDATE_STAT(a) a +static void PrintVal(UInt32 v) +{ + printf("\n : %9u", v); +} +static void PrintStat(const char *name, const UInt32 *a, size_t num) +{ + printf("\n\n==== %s:", name); + UInt32 sum = 0; + size_t i; + for (i = 0; i < num; i++) + sum += a[i]; + PrintVal(sum); + if (sum != 0) + { + for (i = 0; i < num; i++) + { + if (i % 8 == 0) + printf("\n"); + printf("\n%3x : %9u : %5.2f", (unsigned)i, (unsigned)a[i], (double)a[i] * 100 / sum); + } + } + printf("\n"); +} + +static struct CStat { - const UInt32 kResidue = 10; + ~CStat() + { + PrintStat("x86_filter", g_stats_Num_x86, Z7_ARRAY_SIZE(g_stats_Num_x86)); + printf("\nTables:"); PrintVal(g_stats_NumTables); + printf("\nLits:"); PrintVal(g_stats_NumLits); + printf("\nAlign:"); PrintVal(g_stats_NumAlign); + PrintStat("Main", g_stats_main, Z7_ARRAY_SIZE(g_stats_main)); + PrintStat("Len", g_stats_len, Z7_ARRAY_SIZE(g_stats_len)); + PrintStat("Main Levels", g_stats_main_levels, Z7_ARRAY_SIZE(g_stats_main_levels)); + PrintStat("Len Levels", g_stats_len_levels, Z7_ARRAY_SIZE(g_stats_len_levels)); + } +} g_stat; +#else +#define UPDATE_STAT(a) +#endif + + + +/* +3 p015 : ivb- : or r32,r32 / add r32,r32 +4 p0156 : hsw+ +5 p0156b: adl+ +2 p0_5 : ivb- : shl r32,i8 +2 p0__6 : hsw+ +1 p5 : ivb- : jb +2 p0__6 : hsw+ +2 p0_5 : wsm- : SSE2 : pcmpeqb : _mm_cmpeq_epi8 +2 p_15 : snb-bdw +2 p01 : skl+ +1 p0 : SSE2 : pmovmskb : _mm_movemask_epi8 +*/ +/* + v24.00: the code was fixed for more compatibility with original-ms-cab-decoder. + for ((Int32)translationSize >= 0) : LZX specification shows the code with signed Int32. + for ((Int32)translationSize < 0) : no specification for that case, but we support that case. + We suppose our code now is compatible with original-ms-cab-decoder. + + Starting byte of data stream (real_pos == 0) is special corner case, + where we don't need any conversion (as in original-ms-cab-decoder). + Our optimization: we use unsigned (UInt32 pos) (pos = -1 - real_pos). + So (pos) is always negative: ((Int32)pos < 0). + It allows us to use simple comparison (v > pos) instead of more complex comparisons. +*/ +// (p) will point 5 bytes after 0xe8 byte: +// pos == -1 - (p - 5 - data_start) == 4 + data_start - p +// (FILTER_PROCESSED_SIZE_DELTA == 4) is optimized value for better speed in some compilers: +#define FILTER_PROCESSED_SIZE_DELTA 4 + +#if defined(MY_CPU_X86_OR_AMD64) || defined(MY_CPU_ARM_OR_ARM64) + // optimized branch: + // size_t must be at least 32-bit for this branch. + #if 1 // use 1 for simpler code + // use integer (low 32 bits of pointer) instead of pointer + #define X86_FILTER_PREPARE processedSize4 = (UInt32)(size_t)(ptrdiff_t)data + \ + (UInt32)(4 - FILTER_PROCESSED_SIZE_DELTA) - processedSize4; + #define X86_FILTER_CALC_pos(p) const UInt32 pos = processedSize4 - (UInt32)(size_t)(ptrdiff_t)p; + #else + // note: (dataStart) pointer can point out of array ranges: + #define X86_FILTER_PREPARE const Byte *dataStart = data + \ + (4 - FILTER_PROCESSED_SIZE_DELTA) - processedSize4; + #define X86_FILTER_CALC_pos(p) const UInt32 pos = (UInt32)(size_t)(dataStart - p); + #endif +#else + // non-optimized branch for unusual platforms (16-bit size_t or unusual size_t): + #define X86_FILTER_PREPARE processedSize4 = \ + (UInt32)(4 - FILTER_PROCESSED_SIZE_DELTA) - processedSize4; + #define X86_FILTER_CALC_pos(p) const UInt32 pos = processedSize4 - (UInt32)(size_t)(p - data); +#endif + +#define X86_TRANSLATE_PRE(p) \ + UInt32 v = GetUi32((p) - 4); + +#define X86_TRANSLATE_POST(p) \ + { \ + X86_FILTER_CALC_pos(p) \ + if (v < translationSize) { \ + UPDATE_STAT(g_stats_Num_x86[0]++;) \ + v += pos + 1; \ + SetUi32((p) - 4, v) \ + } \ + else if (v > pos) { \ + UPDATE_STAT(g_stats_Num_x86[1]++;) \ + v += translationSize; \ + SetUi32((p) - 4, v) \ + } else { UPDATE_STAT(g_stats_Num_x86[2]++;) } \ + } + + +/* + if ( defined(Z7_LZX_X86_FILTER_USE_SSE2) + && defined(Z7_LZX_X86_FILTER_USE_SSE2_ALIGNED)) + the function can read up to aligned_for_32_up_from(size) bytes in (data). +*/ +// processedSize < (1 << 30) +Z7_NO_INLINE +static void x86_Filter4(Byte *data, size_t size, UInt32 processedSize4, UInt32 translationSize) +{ + const size_t kResidue = 10; if (size <= kResidue) return; - size -= kResidue; - - Byte save = data[size + 4]; - data[size + 4] = 0xE8; - - for (UInt32 i = 0;;) + Byte * const lim = data + size - kResidue + 4; + const Byte save = lim[0]; + lim[0] = 0xe8; + X86_FILTER_PREPARE + Byte *p = data; + +#define FILTER_RETURN_IF_LIM(_p_) if (_p_ > lim) { lim[0] = save; return; } + +#ifdef Z7_LZX_X86_FILTER_USE_SSE2 + +// sse2-aligned/sse2-unaligned provide same speed on real data. +// but the code is smaller for sse2-unaligned version. +// for debug : define it to get alternative version with aligned 128-bit reads: +// #define Z7_LZX_X86_FILTER_USE_SSE2_ALIGNED + +#define FILTER_MASK_INT UInt32 +#define FILTER_NUM_VECTORS_IN_CHUNK 2 +#define FILTER_CHUNK_BYTES_OFFSET (16 * FILTER_NUM_VECTORS_IN_CHUNK - 5) + +#ifdef Z7_LZX_X86_FILTER_USE_SSE2_ALIGNED + // aligned version doesn't uses additional space if buf size is aligned for 32 + #define k_Filter_OutBufSize_Add 0 + #define k_Filter_OutBufSize_AlignMask (16 * FILTER_NUM_VECTORS_IN_CHUNK - 1) + #define FILTER_LOAD_128(p) _mm_load_si128 ((const __m128i *)(const void *)(p)) +#else + #define k_Filter_OutBufSize_Add (16 * FILTER_NUM_VECTORS_IN_CHUNK) + #define k_Filter_OutBufSize_AlignMask 0 + #define FILTER_LOAD_128(p) _mm_loadu_si128((const __m128i *)(const void *)(p)) +#endif + +#define GET_E8_MASK(dest, dest1, p) \ +{ \ + __m128i v0 = FILTER_LOAD_128(p); \ + __m128i v1 = FILTER_LOAD_128(p + 16); \ + p += 16 * FILTER_NUM_VECTORS_IN_CHUNK; \ + v0 = _mm_cmpeq_epi8(v0, k_e8_Vector); \ + v1 = _mm_cmpeq_epi8(v1, k_e8_Vector); \ + dest = (unsigned)_mm_movemask_epi8(v0); \ + dest1 = (unsigned)_mm_movemask_epi8(v1); \ +} + + const __m128i k_e8_Vector = _mm_set1_epi32((Int32)(UInt32)0xe8e8e8e8); + for (;;) { - const Byte *p = data + i; + // for debug: define it for smaller code: + // #define Z7_LZX_X86_FILTER_CALC_IN_LOOP + // without Z7_LZX_X86_FILTER_CALC_IN_LOOP, we can get faster and simpler loop + FILTER_MASK_INT mask; + { + FILTER_MASK_INT mask1; + do + { + GET_E8_MASK(mask, mask1, p) + #ifndef Z7_LZX_X86_FILTER_CALC_IN_LOOP + mask += mask1; + #else + mask |= mask1 << 16; + #endif + } + while (!mask); + + #ifndef Z7_LZX_X86_FILTER_CALC_IN_LOOP + mask -= mask1; + mask |= mask1 << 16; + #endif + } + +#ifdef Z7_LZX_X86_FILTER_USE_SSE2_ALIGNED for (;;) { - if (*p++ == 0xE8) break; - if (*p++ == 0xE8) break; - if (*p++ == 0xE8) break; - if (*p++ == 0xE8) break; + ctz_type index; + typedef + #ifdef MY_CPU_64BIT + UInt64 + #else + UInt32 + #endif + SUPER_MASK_INT; + SUPER_MASK_INT superMask; + { + MY_CTZ(index, mask); + Byte *p2 = p - FILTER_CHUNK_BYTES_OFFSET + (unsigned)index; + X86_TRANSLATE_PRE(p2) + superMask = ~(SUPER_MASK_INT)0x1f << index; + FILTER_RETURN_IF_LIM(p2) + X86_TRANSLATE_POST(p2) + mask &= (UInt32)superMask; + } + if (mask) + continue; + if (index <= FILTER_CHUNK_BYTES_OFFSET) + break; + { + FILTER_MASK_INT mask1; + GET_E8_MASK(mask, mask1, p) + mask &= + #ifdef MY_CPU_64BIT + (UInt32)(superMask >> 32); + #else + ((FILTER_MASK_INT)0 - 1) << ((int)index - FILTER_CHUNK_BYTES_OFFSET); + #endif + mask |= mask1 << 16; + } + if (!mask) + break; } - - i = (UInt32)(p - data); - - if (i > size) - break; +#else // ! Z7_LZX_X86_FILTER_USE_SSE2_ALIGNED { - Int32 v = GetUi32(p); - Int32 pos = (Int32)((Int32)1 - (Int32)(processedSize + i)); - i += 4; - if (v >= pos && v < (Int32)translationSize) + // we use simplest version without loop: + // for (;;) { - v += (v >= 0 ? pos : translationSize); - SetUi32(p, v); + ctz_type index; + MY_CTZ(index, mask); + /* + printf("\np=%p, mask=%8x, index = %2d, p + index = %x\n", + (p - 16 * FILTER_NUM_VECTORS_IN_CHUNK), (unsigned)mask, + (unsigned)index, (unsigned)((unsigned)(ptrdiff_t)(p - 16 * FILTER_NUM_VECTORS_IN_CHUNK) + index)); + */ + p += (size_t)(unsigned)index - FILTER_CHUNK_BYTES_OFFSET; + FILTER_RETURN_IF_LIM(p) + // mask &= ~(FILTER_MASK_INT)0x1f << index; mask >>= index; + X86_TRANSLATE_PRE(p) + X86_TRANSLATE_POST(p) + // if (!mask) break; // p += 16 * FILTER_NUM_VECTORS_IN_CHUNK; } } +#endif // ! Z7_LZX_X86_FILTER_USE_SSE2_ALIGNED } - data[size + 4] = save; +#else // ! Z7_LZX_X86_FILTER_USE_SSE2 + +#define k_Filter_OutBufSize_Add 0 +#define k_Filter_OutBufSize_AlignMask 0 + + + for (;;) + { + for (;;) + { + if (p[0] == 0xe8) { p += 5; break; } + if (p[1] == 0xe8) { p += 6; break; } + if (p[2] == 0xe8) { p += 7; break; } + p += 4; + if (p[-1] == 0xe8) { p += 4; break; } + } + FILTER_RETURN_IF_LIM(p) + X86_TRANSLATE_PRE(p) + X86_TRANSLATE_POST(p) + } + +#endif // ! Z7_LZX_X86_FILTER_USE_SSE2 } -CDecoder::CDecoder(bool wimMode): +CDecoder::CDecoder() throw(): _win(NULL), - _keepHistory(false), + _isUncompressedBlock(false), _skipByte(false), - _wimMode(wimMode), + _keepHistory(false), + _keepHistoryForNext(true), + _needAlloc(true), + _wimMode(false), _numDictBits(15), _unpackBlockSize(0), - _x86_buf(NULL), _x86_translationSize(0), - KeepHistoryForNext(true), - NeedAlloc(true), + _x86_buf(NULL), _unpackedData(NULL) { + { + // it's better to get empty virtual entries, if mispredicted value can be used: + memset(_reps, 0, kPosSlotOffset * sizeof(_reps[0])); + memset(_extra, 0, kPosSlotOffset); +#define SET_NUM_BITS(i) i // #define NUM_BITS_DELTA 31 + _extra[kPosSlotOffset + 0] = SET_NUM_BITS(0); + _extra[kPosSlotOffset + 1] = SET_NUM_BITS(0); + // reps[0] = 0 - (kNumReps - 1); + // reps[1] = 1 - (kNumReps - 1); + UInt32 a = 2 - (kNumReps - 1); + UInt32 delta = 1; + unsigned i; + for (i = 0; i < kNumLinearPosSlotBits; i++) + { + _extra[(size_t)i * 2 + 2 + kPosSlotOffset] = (Byte)(SET_NUM_BITS(i)); + _extra[(size_t)i * 2 + 3 + kPosSlotOffset] = (Byte)(SET_NUM_BITS(i)); + _reps [(size_t)i * 2 + 2 + kPosSlotOffset] = a; a += delta; + _reps [(size_t)i * 2 + 3 + kPosSlotOffset] = a; a += delta; + delta += delta; + } + for (i = kNumLinearPosSlotBits * 2 + 2; i < kNumPosSlots; i++) + { + _extra[(size_t)i + kPosSlotOffset] = SET_NUM_BITS(kNumLinearPosSlotBits); + _reps [(size_t)i + kPosSlotOffset] = a; + a += (UInt32)1 << kNumLinearPosSlotBits; + } + } } -CDecoder::~CDecoder() +CDecoder::~CDecoder() throw() { - if (NeedAlloc) - ::MidFree(_win); - ::MidFree(_x86_buf); + if (_needAlloc) + // BigFree + z7_AlignedFree + (_win); + z7_AlignedFree(_x86_buf); } -HRESULT CDecoder::Flush() +HRESULT CDecoder::Flush() throw() { + // UInt32 t = _x86_processedSize; for (int y = 0; y < 50; y++) { _x86_processedSize = t; // benchmark: (branch predicted) if (_x86_translationSize != 0) { Byte *destData = _win + _writePos; - UInt32 curSize = _pos - _writePos; - if (KeepHistoryForNext) + const UInt32 curSize = _pos - _writePos; + if (_keepHistoryForNext) { + const size_t kChunkSize = (size_t)1 << 15; + if (curSize > kChunkSize) + return E_NOTIMPL; if (!_x86_buf) { - // we must change it to support another chunk sizes - const size_t kChunkSize = (size_t)1 << 15; - if (curSize > kChunkSize) - return E_NOTIMPL; - _x86_buf = (Byte *)::MidAlloc(kChunkSize); + // (kChunkSize % 32 == 0) is required in some cases, because + // the filter can read data by 32-bytes chunks in some cases. + // if (chunk_size > (1 << 15)) is possible, then we must the code: + const size_t kAllocSize = kChunkSize + k_Filter_OutBufSize_Add; + _x86_buf = (Byte *)z7_AlignedAlloc(kAllocSize); if (!_x86_buf) return E_OUTOFMEMORY; + #if 0 != k_Filter_OutBufSize_Add || \ + 0 != k_Filter_OutBufSize_AlignMask + // x86_Filter4() can read after curSize. + // So we set all data to zero to prevent reading of uninitialized data: + memset(_x86_buf, 0, kAllocSize); // optional + #endif } + // for (int yy = 0; yy < 1; yy++) // for debug memcpy(_x86_buf, destData, curSize); _unpackedData = _x86_buf; destData = _x86_buf; } - x86_Filter(destData, (UInt32)curSize, _x86_processedSize, _x86_translationSize); + else + { + // x86_Filter4() can overread after (curSize), + // so we can do memset() after (curSize): + // k_Filter_OutBufSize_AlignMask also can be used + // if (!_overDict) memset(destData + curSize, 0, k_Filter_OutBufSize_Add); + } + x86_Filter4(destData, curSize, _x86_processedSize - FILTER_PROCESSED_SIZE_DELTA, _x86_translationSize); _x86_processedSize += (UInt32)curSize; if (_x86_processedSize >= ((UInt32)1 << 30)) _x86_translationSize = 0; } - + // } return S_OK; } -UInt32 CDecoder::ReadBits(unsigned numBits) { return _bitStream.ReadBitsSmall(numBits); } + +// (NUM_DELTA_BYTES == 2) reduces the code in main loop. +#if 1 + #define NUM_DELTA_BYTES 2 +#else + #define NUM_DELTA_BYTES 0 +#endif + +#define NUM_DELTA_BIT_OFFSET_BITS (NUM_DELTA_BYTES * 8) + +#if NUM_DELTA_BIT_OFFSET_BITS > 0 + #define DECODE_ERROR_CODE 0 + #define IS_OVERFLOW_bitOffset(bo) ((bo) >= 0) + // ( >= 0) comparison after bitOffset change gives simpler commands than ( > 0) comparison +#else + #define DECODE_ERROR_CODE 1 + #define IS_OVERFLOW_bitOffset(bo) ((bo) > 0) +#endif + +// (numBits != 0) +#define GET_VAL_BASE(numBits) (_value >> (32 - (numBits))) + +#define Z7_LZX_HUFF_DECODE( sym, huff, kNumTableBits, move_pos_op, check_op, error_op) \ + Z7_HUFF_DECODE_VAL_IN_HIGH32(sym, huff, kNumHuffmanBits, kNumTableBits, \ + _value, check_op, error_op, move_pos_op, NORMALIZE, bs) + +#define Z7_LZX_HUFF_DECODE_CHECK_YES(sym, huff, kNumTableBits, move_pos_op) \ + Z7_LZX_HUFF_DECODE( sym, huff, kNumTableBits, move_pos_op, \ + Z7_HUFF_DECODE_ERROR_SYM_CHECK_YES, { return DECODE_ERROR_CODE; }) + +#define Z7_LZX_HUFF_DECODE_CHECK_NO( sym, huff, kNumTableBits, move_pos_op) \ + Z7_LZX_HUFF_DECODE( sym, huff, kNumTableBits, move_pos_op, \ + Z7_HUFF_DECODE_ERROR_SYM_CHECK_NO, {}) + +#define NORMALIZE \ +{ \ + const Byte *ptr = _buf + (_bitOffset >> 4) * 2; \ + /* _value = (((UInt32)GetUi16(ptr) << 16) | GetUi16(ptr + 2)) << (_bitOffset & 15); */ \ + const UInt32 v = GetUi32(ptr); \ + _value = rotlFixed (v, ((int)_bitOffset & 15) + 16); \ +} + +#define MOVE_POS(bs, numBits) \ +{ \ + _bitOffset += numBits; \ +} + +#define MOVE_POS_STAT(bs, numBits) \ +{ \ + UPDATE_STAT(g_stats_len_levels[numBits]++;) \ + MOVE_POS(bs, numBits); \ +} + +#define MOVE_POS_CHECK(bs, numBits) \ +{ \ + if (IS_OVERFLOW_bitOffset(_bitOffset += numBits)) return DECODE_ERROR_CODE; \ +} + +#define MOVE_POS_CHECK_STAT(bs, numBits) \ +{ \ + UPDATE_STAT(g_stats_main_levels[numBits]++;) \ + MOVE_POS_CHECK(bs, numBits) \ +} + + +// (numBits == 0) is supported + +#ifdef Z7_HUFF_USE_64BIT_LIMIT + +#define MACRO_ReadBitsBig_pre(numBits) \ +{ \ + _bitOffset += (numBits); \ + _value >>= 32 - (numBits); \ +} + +#else + +#define MACRO_ReadBitsBig_pre(numBits) \ +{ \ + _bitOffset += (numBits); \ + _value = (UInt32)((UInt32)_value >> 1 >> (31 ^ (numBits))); \ +} + +#endif + + +#define MACRO_ReadBitsBig_add(dest) \ + { dest += (UInt32)_value; } + +#define MACRO_ReadBitsBig_add3(dest) \ + { dest += (UInt32)(_value) << 3; } + + +// (numBits != 0) +#define MACRO_ReadBits_NonZero(val, numBits) \ +{ \ + val = (UInt32)(_value >> (32 - (numBits))); \ + MOVE_POS(bs, numBits); \ + NORMALIZE \ +} + + +struct CBitDecoder +{ + ptrdiff_t _bitOffset; + const Byte *_buf; + + Z7_FORCE_INLINE + UInt32 GetVal() const + { + const Byte *ptr = _buf + (_bitOffset >> 4) * 2; + const UInt32 v = GetUi32(ptr); + return rotlFixed (v, ((int)_bitOffset & 15) + 16); + } + + Z7_FORCE_INLINE + bool IsOverRead() const + { + return _bitOffset > (int)(0 - NUM_DELTA_BIT_OFFSET_BITS); + } + + + Z7_FORCE_INLINE + bool WasBitStreamFinishedOK() const + { + // we check that all 0-15 unused bits are zeros: + if (_bitOffset == 0 - NUM_DELTA_BIT_OFFSET_BITS) + return true; + if ((_bitOffset + NUM_DELTA_BIT_OFFSET_BITS + 15) & ~(ptrdiff_t)15) + return false; + const Byte *ptr = _buf - NUM_DELTA_BYTES - 2; + if ((UInt16)(GetUi16(ptr) << (_bitOffset & 15))) + return false; + return true; + } + + // (numBits != 0) + Z7_FORCE_INLINE + UInt32 ReadBits_NonZero(unsigned numBits) throw() + { + const UInt32 val = GetVal() >> (32 - numBits); + _bitOffset += numBits; + return val; + } +}; + + +class CBitByteDecoder: public CBitDecoder +{ + size_t _size; +public: + + Z7_FORCE_INLINE + void Init_ByteMode(const Byte *data, size_t size) + { + _buf = data; + _size = size; + } + + Z7_FORCE_INLINE + void Init_BitMode(const Byte *data, size_t size) + { + _size = size & 1; + size &= ~(size_t)1; + _buf = data + size + NUM_DELTA_BYTES; + _bitOffset = 0 - (ptrdiff_t)(size * 8) - NUM_DELTA_BIT_OFFSET_BITS; + } + + Z7_FORCE_INLINE + void Switch_To_BitMode() + { + Init_BitMode(_buf, _size); + } + + Z7_FORCE_INLINE + bool Switch_To_ByteMode() + { + /* here we check that unused bits in high 16-bits word are zeros. + If high word is full (all 16-bits are unused), + we check that all 16-bits are zeros. + So we check and skip (1-16 bits) unused bits */ + if ((GetVal() >> (16 + (_bitOffset & 15))) != 0) + return false; + _bitOffset += 16; + _bitOffset &= ~(ptrdiff_t)15; + if (_bitOffset > 0 - NUM_DELTA_BIT_OFFSET_BITS) + return false; + const ptrdiff_t delta = _bitOffset >> 3; + _size = (size_t)((ptrdiff_t)(_size) - delta - NUM_DELTA_BYTES); + _buf += delta; + // _bitOffset = 0; // optional + return true; + } + + Z7_FORCE_INLINE + size_t GetRem() const { return _size; } + + Z7_FORCE_INLINE + UInt32 ReadUInt32() + { + const Byte *ptr = _buf; + const UInt32 v = GetUi32(ptr); + _buf += 4; + _size -= 4; + return v; + } + + Z7_FORCE_INLINE + void CopyTo(Byte *dest, size_t size) + { + memcpy(dest, _buf, size); + _buf += size; + _size -= size; + } + + Z7_FORCE_INLINE + bool IsOneDirectByteLeft() const + { + return GetRem() == 1; + } + + Z7_FORCE_INLINE + Byte DirectReadByte() + { + _size--; + return *_buf++; + } +}; + + +// numBits != 0 +// Z7_FORCE_INLINE +Z7_NO_INLINE +static +UInt32 ReadBits(CBitDecoder &_bitStream, unsigned numBits) +{ + return _bitStream.ReadBits_NonZero(numBits); +} #define RIF(x) { if (!(x)) return false; } -bool CDecoder::ReadTable(Byte *levels, unsigned numSymbols) + +/* +MSVC compiler adds extra move operation, + if we access array with 32-bit index + array[calc_index_32_bit(32-bit_var)] + where calc_index_32_bit operations are: ((unsigned)a>>cnt), &, ^, | + clang is also affected for ((unsigned)a>>cnt) in byte array. +*/ + +// it can overread input buffer for 7-17 bytes. +// (levels != levelsEnd) +Z7_NO_INLINE +static ptrdiff_t ReadTable(ptrdiff_t _bitOffset, const Byte *_buf, Byte *levels, const Byte *levelsEnd) { + const unsigned kNumTableBits_Level = 7; + NHuffman::CDecoder256 _levelDecoder; + NHuffman::CValueInt _value; + // optional check to reduce size of overread zone: + if (_bitOffset > (int)0 - (int)NUM_DELTA_BIT_OFFSET_BITS - (int)(kLevelTableSize * kNumLevelBits)) + return DECODE_ERROR_CODE; + NORMALIZE { - Byte levels2[kLevelTableSize]; - for (unsigned i = 0; i < kLevelTableSize; i++) - levels2[i] = (Byte)ReadBits(kNumLevelBits); - RIF(_levelDecoder.Build(levels2)); + Byte levels2[kLevelTableSize / 4 * 4]; + for (size_t i = 0; i < kLevelTableSize / 4 * 4; i += 4) + { + UInt32 val; + MACRO_ReadBits_NonZero(val, kNumLevelBits * 4) + levels2[i + 0] = (Byte)((val >> (3 * kNumLevelBits))); + levels2[i + 1] = (Byte)((val >> (2 * kNumLevelBits)) & ((1u << kNumLevelBits) - 1)); + levels2[i + 2] = (Byte)((Byte)val >> (1 * kNumLevelBits)); + levels2[i + 3] = (Byte)((val) & ((1u << kNumLevelBits) - 1)); + } + RIF(_levelDecoder.Build(levels2, NHuffman::k_BuildMode_Full)) } - unsigned i = 0; do { - UInt32 sym = _levelDecoder.Decode(&_bitStream); + unsigned sym; + Z7_LZX_HUFF_DECODE_CHECK_NO(sym, &_levelDecoder, kNumTableBits_Level, MOVE_POS_CHECK) + // Z7_HUFF_DECODE_CHECK(sym, &_levelDecoder, kNumHuffmanBits, kNumTableBits_Level, &bitStream, return false) + // sym = _levelDecoder.Decode(&bitStream); + // if (!_levelDecoder.Decode_SymCheck_MovePosCheck(&bitStream, sym)) return false; + if (sym <= kNumHuffmanBits) { - int delta = (int)levels[i] - (int)sym; - delta += (delta < 0) ? (kNumHuffmanBits + 1) : 0; - levels[i++] = (Byte)delta; + int delta = (int)*levels - (int)sym; + delta += delta < 0 ? kNumHuffmanBits + 1 : 0; + *levels++ = (Byte)delta; continue; } unsigned num; - Byte symbol; + int symbol; if (sym < kLevelSym_Same) { - sym -= kLevelSym_Zero1; - num = kLevelSym_Zero1_Start + ((unsigned)sym << kLevelSym_Zero1_NumBits) + - (unsigned)ReadBits(kLevelSym_Zero1_NumBits + sym); + // sym -= kLevelSym_Zero1; + MACRO_ReadBits_NonZero(num, kLevelSym_Zero1_NumBits + (sym - kLevelSym_Zero1)) + num += (sym << kLevelSym_Zero1_NumBits) - (kLevelSym_Zero1 << kLevelSym_Zero1_NumBits) + kLevelSym_Zero1_Start; symbol = 0; } - else if (sym == kLevelSym_Same) + // else if (sym != kLevelSym_Same) return DECODE_ERROR_CODE; + else // (sym == kLevelSym_Same) { - num = kLevelSym_Same_Start + (unsigned)ReadBits(kLevelSym_Same_NumBits); - sym = _levelDecoder.Decode(&_bitStream); - if (sym > kNumHuffmanBits) - return false; - int delta = (int)levels[i] - (int)sym; - delta += (delta < 0) ? (kNumHuffmanBits + 1) : 0; - symbol = (Byte)delta; + MACRO_ReadBits_NonZero(num, kLevelSym_Same_NumBits) + num += kLevelSym_Same_Start; + // + (unsigned)bitStream.ReadBitsSmall(kLevelSym_Same_NumBits); + // Z7_HUFF_DECODE_CHECK(sym, &_levelDecoder, kNumHuffmanBits, kNumTableBits_Level, &bitStream, return DECODE_ERROR_CODE) + // if (!_levelDecoder.Decode2(&bitStream, sym)) return DECODE_ERROR_CODE; + // sym = _levelDecoder.Decode(&bitStream); + + Z7_LZX_HUFF_DECODE_CHECK_NO(sym, &_levelDecoder, kNumTableBits_Level, MOVE_POS) + + if (sym > kNumHuffmanBits) return DECODE_ERROR_CODE; + symbol = *levels - (int)sym; + symbol += symbol < 0 ? kNumHuffmanBits + 1 : 0; } - else - return false; - unsigned limit = i + num; - if (limit > numSymbols) + if (num > (size_t)(levelsEnd - levels)) return false; - + const Byte *limit = levels + num; do - levels[i++] = symbol; - while (i < limit); + *levels++ = (Byte)symbol; + while (levels != limit); } - while (i < numSymbols); + while (levels != levelsEnd); - return true; + return _bitOffset; } -bool CDecoder::ReadTables(void) -{ - { - if (_skipByte) - { - if (_bitStream.DirectReadByte() != 0) - return false; - } +static const unsigned kPosSlotDelta = 256 / kNumLenSlots - kPosSlotOffset; + - _bitStream.NormalizeBig(); +#define READ_TABLE(_bitStream, levels, levelsEnd) \ +{ \ + _bitStream._bitOffset = ReadTable(_bitStream._bitOffset, _bitStream._buf, levels, levelsEnd); \ + if (_bitStream.IsOverRead()) return false; \ +} - unsigned blockType = (unsigned)ReadBits(kBlockType_NumBits); - if (blockType > kBlockType_Uncompressed) +// can over-read input buffer for less than 32 bytes +bool CDecoder::ReadTables(CBitByteDecoder &_bitStream) throw() +{ + UPDATE_STAT(g_stats_NumTables++;) + { + const unsigned blockType = (unsigned)ReadBits(_bitStream, kBlockType_NumBits); + // if (blockType > kBlockType_Uncompressed || blockType == 0) + if ((unsigned)(blockType - 1) > kBlockType_Uncompressed - 1) return false; - - _unpackBlockSize = (1 << 15); - if (!_wimMode || ReadBits(1) == 0) + _unpackBlockSize = 1u << 15; + if (!_wimMode || ReadBits(_bitStream, 1) == 0) { - _unpackBlockSize = ReadBits(16); + _unpackBlockSize = ReadBits(_bitStream, 16); // wimlib supports chunks larger than 32KB (unsupported my MS wim). if (!_wimMode || _numDictBits >= 16) { _unpackBlockSize <<= 8; - _unpackBlockSize |= ReadBits(8); + _unpackBlockSize |= ReadBits(_bitStream, 8); } } PRF(printf("\nBlockSize = %6d %s ", _unpackBlockSize, (_pos & 1) ? "@@@" : " ")); _isUncompressedBlock = (blockType == kBlockType_Uncompressed); - _skipByte = false; if (_isUncompressedBlock) { _skipByte = ((_unpackBlockSize & 1) != 0); - - PRF(printf(" UncompressedBlock ")); - if (_unpackBlockSize & 1) - { - PRF(printf(" ######### ")); - } - - if (!_bitStream.PrepareUncompressed()) + // printf("\n UncompressedBlock %d", _unpackBlockSize); + PRF(printf(" UncompressedBlock ");) + // if (_unpackBlockSize & 1) { PRF(printf(" ######### ")); } + if (!_bitStream.Switch_To_ByteMode()) return false; if (_bitStream.GetRem() < kNumReps * 4) return false; - for (unsigned i = 0; i < kNumReps; i++) { - UInt32 rep = _bitStream.ReadUInt32(); - if (rep > _winSize) + const UInt32 rep = _bitStream.ReadUInt32(); + // here we allow only such values for (rep) that can be set also by LZ code: + if (rep == 0 || rep > _winSize - kNumReps) return false; - _reps[i] = rep; + _reps[(size_t)i + kPosSlotOffset] = rep; } - + // printf("\n"); return true; } - - _numAlignBits = 64; - + + // _numAlignBits = 64; + // const UInt32 k_numAlignBits_PosSlots_MAX = 64 + kPosSlotDelta; + // _numAlignBits_PosSlots = k_numAlignBits_PosSlots_MAX; + const UInt32 k_numAlignBits_Dist_MAX = (UInt32)(Int32)-1; + _numAlignBits_Dist = k_numAlignBits_Dist_MAX; if (blockType == kBlockType_Aligned) { Byte levels[kAlignTableSize]; - _numAlignBits = kNumAlignBits; + // unsigned not0 = 0; + unsigned not3 = 0; for (unsigned i = 0; i < kAlignTableSize; i++) - levels[i] = (Byte)ReadBits(kNumAlignLevelBits); - RIF(_alignDecoder.Build(levels)); + { + const unsigned val = ReadBits(_bitStream, kNumAlignLevelBits); + levels[i] = (Byte)val; + // not0 |= val; + not3 |= (val ^ 3); + } + // static unsigned number = 0, all = 0; all++; + // if (!not0) return false; // Build(true) will test this case + if (not3) + { + // _numAlignBits_PosSlots = (kNumAlignBits + 1) * 2 + kPosSlotDelta; + // _numAlignBits = kNumAlignBits; + _numAlignBits_Dist = (1u << (kNumAlignBits + 1)) - (kNumReps - 1); + RIF(_alignDecoder.Build(levels, true)) // full + } + // else { number++; if (number % 4 == 0) printf("\nnumber= %u : %u%%", number, number * 100 / all); } + } + // if (_numAlignBits_PosSlots == k_numAlignBits_PosSlots_MAX) + if (_numAlignBits_Dist == k_numAlignBits_Dist_MAX) + { + size_t i; + for (i = 3; i < kNumLinearPosSlotBits; i++) + { + _extra[i * 2 + 2 + kPosSlotOffset] = (Byte)(SET_NUM_BITS(i)); + _extra[i * 2 + 3 + kPosSlotOffset] = (Byte)(SET_NUM_BITS(i)); + } + for (i = kNumLinearPosSlotBits * 2 + 2; i < kNumPosSlots; i++) + _extra[i + kPosSlotOffset] = (Byte)SET_NUM_BITS(kNumLinearPosSlotBits); + } + else + { + size_t i; + for (i = 3; i < kNumLinearPosSlotBits; i++) + { + _extra[i * 2 + 2 + kPosSlotOffset] = (Byte)(SET_NUM_BITS(i) - 3); + _extra[i * 2 + 3 + kPosSlotOffset] = (Byte)(SET_NUM_BITS(i) - 3); + } + for (i = kNumLinearPosSlotBits * 2 + 2; i < kNumPosSlots; i++) + _extra[i + kPosSlotOffset] = (Byte)(SET_NUM_BITS(kNumLinearPosSlotBits) - 3); } } - RIF(ReadTable(_mainLevels, 256)); - RIF(ReadTable(_mainLevels + 256, _numPosLenSlots)); - unsigned end = 256 + _numPosLenSlots; + READ_TABLE(_bitStream, _mainLevels, _mainLevels + 256) + READ_TABLE(_bitStream, _mainLevels + 256, _mainLevels + 256 + _numPosLenSlots) + const unsigned end = 256 + _numPosLenSlots; memset(_mainLevels + end, 0, kMainTableSize - end); - RIF(_mainDecoder.Build(_mainLevels)); - RIF(ReadTable(_lenLevels, kNumLenSymbols)); - return _lenDecoder.Build(_lenLevels); + // #define NUM_CYC 1 + // unsigned j; for (j = 0; j < NUM_CYC; j++) + RIF(_mainDecoder.Build(_mainLevels, NHuffman::k_BuildMode_Full)) + // if (kNumLenSymols_Big_Start) + memset(_lenLevels, 0, kNumLenSymols_Big_Start); + READ_TABLE(_bitStream, + _lenLevels + kNumLenSymols_Big_Start, + _lenLevels + kNumLenSymols_Big_Start + kNumLenSymbols) + // for (j = 0; j < NUM_CYC; j++) + RIF(_lenDecoder.Build(_lenLevels, NHuffman::k_BuildMode_Full_or_Empty)) + return true; +} + + + +static ptrdiff_t CodeLz(CDecoder *dec, size_t next, ptrdiff_t _bitOffset, const Byte *_buf) throw() +{ + { + Byte *const win = dec->_win; + const UInt32 winSize = dec->_winSize; + Byte *pos = win + dec->_pos; + const Byte * const posEnd = pos + next; + NHuffman::CValueInt _value; + + NORMALIZE + +#if 1 + #define HUFF_DEC_PREFIX dec-> +#else + const NHuffman::CDecoder _mainDecoder = dec->_mainDecoder; + const NHuffman::CDecoder256 _lenDecoder = dec->_lenDecoder; + const NHuffman::CDecoder7b _alignDecoder = dec->_alignDecoder; + #define HUFF_DEC_PREFIX +#endif + + do + { + unsigned sym; + // printf("\npos = %6u", pos - win); + { + const NHuffman::CDecoder + *mainDecoder = & HUFF_DEC_PREFIX _mainDecoder; + Z7_LZX_HUFF_DECODE_CHECK_NO(sym, mainDecoder, kNumTableBits_Main, MOVE_POS_CHECK_STAT) + } + // if (!_mainDecoder.Decode_SymCheck_MovePosCheck(&bitStream, sym)) return DECODE_ERROR_CODE; + // sym = _mainDecoder.Decode(&bitStream); + // if (bitStream.WasExtraReadError_Fast()) return DECODE_ERROR_CODE; + + // printf(" sym = %3x", sym); + UPDATE_STAT(g_stats_main[sym]++;) + + if (sym < 256) + { + UPDATE_STAT(g_stats_NumLits++;) + *pos++ = (Byte)sym; + } + else + { + // sym -= 256; + // if (sym >= _numPosLenSlots) return DECODE_ERROR_CODE; + const unsigned posSlot = sym / kNumLenSlots; + unsigned len = sym % kNumLenSlots + kMatchMinLen; + if (len == kNumLenSlots - 1 + kMatchMinLen) + { + const NHuffman::CDecoder256 + *lenDecoder = & HUFF_DEC_PREFIX _lenDecoder; + Z7_LZX_HUFF_DECODE_CHECK_YES(len, lenDecoder, kNumTableBits_Len, MOVE_POS_STAT) + // if (!_lenDecoder.Decode2(&bitStream, len)) return DECODE_ERROR_CODE; + // len = _lenDecoder.Decode(&bitStream); + // if (len >= kNumLenSymbols) return DECODE_ERROR_CODE; + UPDATE_STAT(g_stats_len[len - kNumLenSymols_Big_Start]++;) + len += kNumLenSlots - 1 + kMatchMinLen - kNumLenSymols_Big_Start; + } + /* + if ((next -= len) < 0) + return DECODE_ERROR_CODE; + */ + UInt32 dist; + + dist = dec->_reps[(size_t)posSlot - kPosSlotDelta]; + if (posSlot < kNumReps + 256 / kNumLenSlots) + { + // if (posSlot != kNumReps + kPosSlotDelta) + // if (posSlot - (kNumReps + kPosSlotDelta + 1) < 2) + dec->_reps[(size_t)posSlot - kPosSlotDelta] = dec->_reps[kPosSlotOffset]; + /* + if (posSlot != kPosSlotDelta) + { + UInt32 temp = dist; + if (posSlot == kPosSlotDelta + 1) + { + dist = reps[1]; + reps[1] = temp; + } + else + { + dist = reps[2]; + reps[2] = temp; + } + // dist = reps[(size_t)(posSlot) - kPosSlotDelta]; + // reps[(size_t)(posSlot) - kPosSlotDelta] = reps[0]; + // reps[(size_t)(posSlot) - kPosSlotDelta] = temp; + } + */ + } + else // if (posSlot != kNumReps + kPosSlotDelta) + { + unsigned numDirectBits; +#if 0 + if (posSlot < kNumPowerPosSlots + kPosSlotDelta) + { + numDirectBits = (posSlot - 2 - kPosSlotDelta) >> 1; + dist = (UInt32)(2 | (posSlot & 1)) << numDirectBits; + } + else + { + numDirectBits = kNumLinearPosSlotBits; + dist = (UInt32)(posSlot - 0x22 - kPosSlotDelta) << kNumLinearPosSlotBits; + } + dist -= kNumReps - 1; +#else + numDirectBits = dec->_extra[(size_t)posSlot - kPosSlotDelta]; + // dist = reps[(size_t)(posSlot) - kPosSlotDelta]; +#endif + dec->_reps[kPosSlotOffset + 2] = + dec->_reps[kPosSlotOffset + 1]; + dec->_reps[kPosSlotOffset + 1] = + dec->_reps[kPosSlotOffset + 0]; + + // dist += val; dist += bitStream.ReadBitsBig(numDirectBits); + // if (posSlot >= _numAlignBits_PosSlots) + // if (numDirectBits >= _numAlignBits) + // if (val >= _numAlignBits_Dist) + // UInt32 val; MACRO_ReadBitsBig(val , numDirectBits) + // dist += val; + // dist += (UInt32)((UInt32)_value >> 1 >> (/* 31 ^ */ (numDirectBits))); + // MOVE_POS((numDirectBits ^ 31)) + MACRO_ReadBitsBig_pre(numDirectBits) + // dist += (UInt32)_value; + if (dist >= dec->_numAlignBits_Dist) + { + // if (numDirectBits != _numAlignBits) + { + // UInt32 val; + // dist -= (UInt32)_value; + MACRO_ReadBitsBig_add3(dist) + NORMALIZE + // dist += (val << kNumAlignBits); + // dist += bitStream.ReadBitsSmall(numDirectBits - kNumAlignBits) << kNumAlignBits; + } + { + // const unsigned alignTemp = _alignDecoder.Decode(&bitStream); + const NHuffman::CDecoder7b *alignDecoder = & HUFF_DEC_PREFIX _alignDecoder; + unsigned alignTemp; + UPDATE_STAT(g_stats_NumAlign++;) + Z7_HUFF_DECODER_7B_DECODE(alignTemp, alignDecoder, GET_VAL_BASE, MOVE_POS, bs) + // NORMALIZE + // if (alignTemp >= kAlignTableSize) return DECODE_ERROR_CODE; + dist += alignTemp; + } + } + else + { + { + MACRO_ReadBitsBig_add(dist) + // dist += bitStream.ReadBitsSmall(numDirectBits - kNumAlignBits) << kNumAlignBits; + } + } + NORMALIZE + /* + else + { + UInt32 val; + MACRO_ReadBitsBig(val, numDirectBits) + dist += val; + // dist += bitStream.ReadBitsBig(numDirectBits); + } + */ + } + dec->_reps[kPosSlotOffset + 0] = dist; + + Byte *dest = pos; + if (len > (size_t)(posEnd - pos)) + return DECODE_ERROR_CODE; + Int32 srcPos = (Int32)(pos - win); + pos += len; + srcPos -= (Int32)dist; + if (srcPos < 0) // fast version + { + if (!dec->_overDict) + return DECODE_ERROR_CODE; + srcPos &= winSize - 1; + UInt32 rem = winSize - (UInt32)srcPos; + if (len > rem) + { + len -= rem; + const Byte *src = win + (UInt32)srcPos; + do + *dest++ = *src++; + while (--rem); + srcPos = 0; + } + } + CopyLzMatch(dest, win + (UInt32)srcPos, len, dist); + } + } + while (pos != posEnd); + + return _bitOffset; + } } -HRESULT CDecoder::CodeSpec(UInt32 curSize) + + +// inSize != 0 +// outSize != 0 ??? +HRESULT CDecoder::CodeSpec(const Byte *inData, size_t inSize, UInt32 outSize) throw() { - if (!_keepHistory || !_isUncompressedBlock) - _bitStream.NormalizeBig(); + // ((inSize & 1) != 0) case is possible, if current call will be finished with Uncompressed Block. + CBitByteDecoder _bitStream; + if (_keepHistory && _isUncompressedBlock) + _bitStream.Init_ByteMode(inData, inSize); + else + _bitStream.Init_BitMode(inData, inSize); if (!_keepHistory) { + _isUncompressedBlock = false; _skipByte = false; _unpackBlockSize = 0; - - memset(_mainLevels, 0, kMainTableSize); - memset(_lenLevels, 0, kNumLenSymbols); - + memset(_mainLevels, 0, sizeof(_mainLevels)); + memset(_lenLevels, 0, sizeof(_lenLevels)); { _x86_translationSize = 12000000; if (!_wimMode) { _x86_translationSize = 0; - if (ReadBits(1) != 0) + if (ReadBits(_bitStream, 1) != 0) { - UInt32 v = ReadBits(16) << 16; - v |= ReadBits(16); + UInt32 v = ReadBits(_bitStream, 16) << 16; + v |= ReadBits(_bitStream, 16); _x86_translationSize = v; } } - _x86_processedSize = 0; } - - _reps[0] = 1; - _reps[1] = 1; - _reps[2] = 1; + _reps[0 + kPosSlotOffset] = 1; + _reps[1 + kPosSlotOffset] = 1; + _reps[2 + kPosSlotOffset] = 1; } - while (curSize > 0) + while (outSize) { + /* + // check it for bit mode only: if (_bitStream.WasExtraReadError_Fast()) return S_FALSE; - + */ if (_unpackBlockSize == 0) { - if (!ReadTables()) + if (_skipByte) + { + if (_bitStream.GetRem() < 1) + return S_FALSE; + if (_bitStream.DirectReadByte() != 0) + return S_FALSE; + } + if (_isUncompressedBlock) + _bitStream.Switch_To_BitMode(); + if (!ReadTables(_bitStream)) return S_FALSE; continue; } + // _unpackBlockSize != 0 UInt32 next = _unpackBlockSize; - if (next > curSize) - next = curSize; + if (next > outSize) + next = outSize; + // next != 0 + + // PRF(printf("\nnext = %d", (unsigned)next);) if (_isUncompressedBlock) { - size_t rem = _bitStream.GetRem(); - if (rem == 0) + if (_bitStream.GetRem() < next) return S_FALSE; - if (next > rem) - next = (UInt32)rem; _bitStream.CopyTo(_win + _pos, next); _pos += next; - curSize -= next; _unpackBlockSize -= next; - - /* we don't know where skipByte can be placed, if it's end of chunk: - 1) in current chunk - there are such cab archives, if chunk is last - 2) in next chunk - are there such archives ? */ - - if (_skipByte - && _unpackBlockSize == 0 - && curSize == 0 - && _bitStream.IsOneDirectByteLeft()) - { - _skipByte = false; - if (_bitStream.DirectReadByte() != 0) - return S_FALSE; - } - - continue; } - - curSize -= next; - _unpackBlockSize -= next; - - Byte *win = _win; - - while (next > 0) + else { - if (_bitStream.WasExtraReadError_Fast()) + _unpackBlockSize -= next; + _bitStream._bitOffset = CodeLz(this, next, _bitStream._bitOffset, _bitStream._buf); + if (_bitStream.IsOverRead()) return S_FALSE; + _pos += next; + } + outSize -= next; + } - UInt32 sym = _mainDecoder.Decode(&_bitStream); - - if (sym < 256) - { - win[_pos++] = (Byte)sym; - next--; - continue; - } - { - sym -= 256; - if (sym >= _numPosLenSlots) - return S_FALSE; - UInt32 posSlot = sym / kNumLenSlots; - UInt32 lenSlot = sym % kNumLenSlots; - UInt32 len = kMatchMinLen + lenSlot; - - if (lenSlot == kNumLenSlots - 1) - { - UInt32 lenTemp = _lenDecoder.Decode(&_bitStream); - if (lenTemp >= kNumLenSymbols) - return S_FALSE; - len = kMatchMinLen + kNumLenSlots - 1 + lenTemp; - } - - UInt32 dist; - - if (posSlot < kNumReps) - { - dist = _reps[posSlot]; - _reps[posSlot] = _reps[0]; - _reps[0] = dist; - } - else - { - unsigned numDirectBits; - - if (posSlot < kNumPowerPosSlots) - { - numDirectBits = (unsigned)(posSlot >> 1) - 1; - dist = ((2 | (posSlot & 1)) << numDirectBits); - } - else - { - numDirectBits = kNumLinearPosSlotBits; - dist = ((posSlot - 0x22) << kNumLinearPosSlotBits); - } - - if (numDirectBits >= _numAlignBits) - { - dist += (_bitStream.ReadBitsSmall(numDirectBits - kNumAlignBits) << kNumAlignBits); - UInt32 alignTemp = _alignDecoder.Decode(&_bitStream); - if (alignTemp >= kAlignTableSize) - return S_FALSE; - dist += alignTemp; - } - else - dist += _bitStream.ReadBitsBig(numDirectBits); - - dist -= kNumReps - 1; - _reps[2] = _reps[1]; - _reps[1] = _reps[0]; - _reps[0] = dist; - } - - if (len > next) - return S_FALSE; - - if (dist > _pos && !_overDict) - return S_FALSE; - - Byte *dest = win + _pos; - const UInt32 mask = (_winSize - 1); - UInt32 srcPos = (_pos - dist) & mask; + // outSize == 0 - next -= len; - - if (len > _winSize - srcPos) - { - _pos += len; - do - { - *dest++ = win[srcPos++]; - srcPos &= mask; - } - while (--len); - } - else - { - ptrdiff_t src = (ptrdiff_t)srcPos - (ptrdiff_t)_pos; - _pos += len; - const Byte *lim = dest + len; - *(dest) = *(dest + src); - dest++; - do - *(dest) = *(dest + src); - while (++dest != lim); - } - } + if (_isUncompressedBlock) + { + /* we don't know where skipByte can be placed, if it's end of chunk: + 1) in current chunk - there are such cab archives, if chunk is last + 2) in next chunk - are there such archives ? */ + if (_unpackBlockSize == 0 + && _skipByte + // && outSize == 0 + && _bitStream.IsOneDirectByteLeft()) + { + _skipByte = false; + if (_bitStream.DirectReadByte() != 0) + return S_FALSE; } } - if (!_bitStream.WasFinishedOK()) + if (_bitStream.GetRem() != 0) return S_FALSE; - + if (!_isUncompressedBlock) + if (!_bitStream.WasBitStreamFinishedOK()) + return S_FALSE; return S_OK; } -HRESULT CDecoder::Code(const Byte *inData, size_t inSize, UInt32 outSize) +#if k_Filter_OutBufSize_Add > k_Lz_OutBufSize_Add + #define k_OutBufSize_Add k_Filter_OutBufSize_Add +#else + #define k_OutBufSize_Add k_Lz_OutBufSize_Add +#endif + +HRESULT CDecoder::Code_WithExceedReadWrite(const Byte *inData, size_t inSize, UInt32 outSize) throw() { if (!_keepHistory) { @@ -466,63 +1452,65 @@ HRESULT CDecoder::Code(const Byte *inData, size_t inSize, UInt32 outSize) { _pos = 0; _overDict = true; +#if k_OutBufSize_Add > 0 + // data after (_winSize) can be used, because we can use overwrite. + // memset(_win + _winSize, 0, k_OutBufSize_Add); +#endif } - _writePos = _pos; _unpackedData = _win + _pos; - + if (outSize > _winSize - _pos) return S_FALSE; + + PRF(printf("\ninSize = %d", (unsigned)inSize);) + PRF(if ((inSize & 1) != 0) printf("---------");) - PRF(printf("\ninSize = %d", inSize)); - if ((inSize & 1) != 0) - { - PRF(printf(" ---------")); - } - - if (inSize < 1) + if (inSize == 0) return S_FALSE; - - _bitStream.Init(inData, inSize); - - HRESULT res = CodeSpec(outSize); - HRESULT res2 = Flush(); + const HRESULT res = CodeSpec(inData, inSize, outSize); + const HRESULT res2 = Flush(); return (res == S_OK ? res2 : res); } -HRESULT CDecoder::SetParams2(unsigned numDictBits) +HRESULT CDecoder::SetParams2(unsigned numDictBits) throw() { - _numDictBits = numDictBits; - if (numDictBits < kNumDictBits_Min || numDictBits > kNumDictBits_Max) + if (numDictBits < kNumDictBits_Min || + numDictBits > kNumDictBits_Max) return E_INVALIDARG; - unsigned numPosSlots = (numDictBits < 20) ? - numDictBits * 2 : - 34 + ((unsigned)1 << (numDictBits - 17)); - _numPosLenSlots = numPosSlots * kNumLenSlots; + _numDictBits = (Byte)numDictBits; + const unsigned numPosSlots2 = (numDictBits < 20) ? + numDictBits : 17 + (1u << (numDictBits - 18)); + _numPosLenSlots = numPosSlots2 * (kNumLenSlots * 2); return S_OK; } -HRESULT CDecoder::SetParams_and_Alloc(unsigned numDictBits) +HRESULT CDecoder::Set_DictBits_and_Alloc(unsigned numDictBits) throw() { - RINOK(SetParams2(numDictBits)); - - UInt32 newWinSize = (UInt32)1 << numDictBits; - - if (NeedAlloc) + RINOK(SetParams2(numDictBits)) + const UInt32 newWinSize = (UInt32)1 << numDictBits; + if (_needAlloc) { if (!_win || newWinSize != _winSize) { - ::MidFree(_win); + // BigFree + z7_AlignedFree + (_win); _winSize = 0; - _win = (Byte *)::MidAlloc(newWinSize); + const size_t alloc_size = newWinSize + k_OutBufSize_Add; + _win = (Byte *) + // BigAlloc + z7_AlignedAlloc + (alloc_size); if (!_win) return E_OUTOFMEMORY; + // optional: + memset(_win, 0, alloc_size); } } - - _winSize = (UInt32)newWinSize; + _winSize = newWinSize; return S_OK; } diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/LzxDecoder.h b/src/plugins/7zip/7za/cpp/7zip/Compress/LzxDecoder.h index a5c6f12c..b4556a6a 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/LzxDecoder.h +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/LzxDecoder.h @@ -1,11 +1,7 @@ // LzxDecoder.h -#ifndef __LZX_DECODER_H -#define __LZX_DECODER_H - -#include "../../../C/CpuArch.h" - -#include "../../Common/MyCom.h" +#ifndef ZIP7_INC_LZX_DECODER_H +#define ZIP7_INC_LZX_DECODER_H #include "HuffmanDecoder.h" #include "Lzx.h" @@ -13,232 +9,96 @@ namespace NCompress { namespace NLzx { -class CBitDecoder -{ - unsigned _bitPos; - UInt32 _value; - const Byte *_buf; - const Byte *_bufLim; - UInt32 _extraSize; -public: - - void Init(const Byte *data, size_t size) - { - _buf = data; - _bufLim = data + size - 1; - _bitPos = 0; - _extraSize = 0; - } - - size_t GetRem() const { return _bufLim + 1 - _buf; } - bool WasExtraReadError_Fast() const { return _extraSize > 4; } - - bool WasFinishedOK() const - { - if (_buf != _bufLim + 1) - return false; - if ((_bitPos >> 4) * 2 != _extraSize) - return false; - unsigned numBits = _bitPos & 15; - return (((_value >> (_bitPos - numBits)) & (((UInt32)1 << numBits) - 1)) == 0); - } - - void NormalizeSmall() - { - if (_bitPos <= 16) - { - UInt32 val; - if (_buf >= _bufLim) - { - val = 0xFFFF; - _extraSize += 2; - } - else - { - val = GetUi16(_buf); - _buf += 2; - } - _value = (_value << 16) | val; - _bitPos += 16; - } - } - - void NormalizeBig() - { - if (_bitPos <= 16) - { - { - UInt32 val; - if (_buf >= _bufLim) - { - val = 0xFFFF; - _extraSize += 2; - } - else - { - val = GetUi16(_buf); - _buf += 2; - } - _value = (_value << 16) | val; - _bitPos += 16; - } - if (_bitPos <= 16) - { - UInt32 val; - if (_buf >= _bufLim) - { - val = 0xFFFF; - _extraSize += 2; - } - else - { - val = GetUi16(_buf); - _buf += 2; - } - _value = (_value << 16) | val; - _bitPos += 16; - } - } - } - - UInt32 GetValue(unsigned numBits) const - { - return (_value >> (_bitPos - numBits)) & (((UInt32)1 << numBits) - 1); - } - - void MovePos(unsigned numBits) - { - _bitPos -= numBits; - NormalizeSmall(); - } - - UInt32 ReadBitsSmall(unsigned numBits) - { - _bitPos -= numBits; - UInt32 val = (_value >> _bitPos) & (((UInt32)1 << numBits) - 1); - NormalizeSmall(); - return val; - } +const unsigned kAdditionalOutputBufSize = 32 * 2; - UInt32 ReadBitsBig(unsigned numBits) - { - _bitPos -= numBits; - UInt32 val = (_value >> _bitPos) & (((UInt32)1 << numBits) - 1); - NormalizeBig(); - return val; - } +const unsigned kNumTableBits_Main = 11; +const unsigned kNumTableBits_Len = 8; - bool PrepareUncompressed() - { - if (_extraSize != 0) - return false; - unsigned numBits = _bitPos - 16; - if (((_value >> 16) & (((UInt32)1 << numBits) - 1)) != 0) - return false; - _buf -= 2; - _bitPos = 0; - return true; - } +// if (kNumLenSymols_Big <= 256) we can use NHuffman::CDecoder256 +// if (kNumLenSymols_Big > 256) we must use NHuffman::CDecoder +// const unsigned kNumLenSymols_Big_Start = kNumLenSlots - 1 + kMatchMinLen; // 8 - 1 + 2 +const unsigned kNumLenSymols_Big_Start = 0; +// const unsigned kNumLenSymols_Big_Start = 0; +const unsigned kNumLenSymols_Big = kNumLenSymols_Big_Start + kNumLenSymbols; - UInt32 ReadUInt32() - { - UInt32 v = GetUi32(_buf); - _buf += 4; - return v; - } - - void CopyTo(Byte *dest, size_t size) - { - memcpy(dest, _buf, size); - _buf += size; - } - - bool IsOneDirectByteLeft() const { return _buf == _bufLim && _extraSize == 0; } - - Byte DirectReadByte() - { - if (_buf > _bufLim) - { - _extraSize++; - return 0xFF; - } - return *_buf++; - } -}; +#if 1 + // for smallest structure size: + const unsigned kPosSlotOffset = 0; +#else + // use virtual entries for mispredicted branches: + const unsigned kPosSlotOffset = 256 / kNumLenSlots; +#endif +class CBitByteDecoder; -class CDecoder: - public IUnknown, - public CMyUnknownImp +class CDecoder { - CBitDecoder _bitStream; - Byte *_win; +public: UInt32 _pos; UInt32 _winSize; + Byte *_win; bool _overDict; bool _isUncompressedBlock; bool _skipByte; - unsigned _numAlignBits; + bool _keepHistory; + bool _keepHistoryForNext; + bool _needAlloc; + bool _wimMode; + Byte _numDictBits; - UInt32 _reps[kNumReps]; - UInt32 _numPosLenSlots; + // unsigned _numAlignBits_PosSlots; + // unsigned _numAlignBits; + UInt32 _numAlignBits_Dist; +private: + unsigned _numPosLenSlots; UInt32 _unpackBlockSize; -public: - bool KeepHistoryForNext; - bool NeedAlloc; -private: - bool _keepHistory; - bool _wimMode; - unsigned _numDictBits; UInt32 _writePos; - Byte *_x86_buf; UInt32 _x86_translationSize; UInt32 _x86_processedSize; + Byte *_x86_buf; Byte *_unpackedData; - - NHuffman::CDecoder _mainDecoder; - NHuffman::CDecoder _lenDecoder; - NHuffman::CDecoder7b _alignDecoder; - NHuffman::CDecoder _levelDecoder; +public: + Byte _extra[kPosSlotOffset + kNumPosSlots]; + UInt32 _reps[kPosSlotOffset + kNumPosSlots]; + NHuffman::CDecoder _mainDecoder; + NHuffman::CDecoder256 _lenDecoder; + NHuffman::CDecoder7b _alignDecoder; +private: Byte _mainLevels[kMainTableSize]; - Byte _lenLevels[kNumLenSymbols]; + Byte _lenLevels[kNumLenSymols_Big]; - HRESULT Flush(); + HRESULT Flush() throw(); + bool ReadTables(CBitByteDecoder &_bitStream) throw(); - UInt32 ReadBits(unsigned numBits); - bool ReadTable(Byte *levels, unsigned numSymbols); - bool ReadTables(); - - HRESULT CodeSpec(UInt32 size); - HRESULT SetParams2(unsigned numDictBits); + HRESULT CodeSpec(const Byte *inData, size_t inSize, UInt32 outSize) throw(); + HRESULT SetParams2(unsigned numDictBits) throw(); public: - CDecoder(bool wimMode = false); - ~CDecoder(); - - MY_UNKNOWN_IMP + CDecoder() throw(); + ~CDecoder() throw(); + + void Set_WimMode(bool wimMode) { _wimMode = wimMode; } + void Set_KeepHistory(bool keepHistory) { _keepHistory = keepHistory; } + void Set_KeepHistoryForNext(bool keepHistoryForNext) { _keepHistoryForNext = keepHistoryForNext; } - HRESULT SetExternalWindow(Byte *win, unsigned numDictBits) + HRESULT Set_ExternalWindow_DictBits(Byte *win, unsigned numDictBits) { - NeedAlloc = false; + _needAlloc = false; _win = win; _winSize = (UInt32)1 << numDictBits; return SetParams2(numDictBits); } + HRESULT Set_DictBits_and_Alloc(unsigned numDictBits) throw(); - void SetKeepHistory(bool keepHistory) { _keepHistory = keepHistory; } - - HRESULT SetParams_and_Alloc(unsigned numDictBits); - - HRESULT Code(const Byte *inData, size_t inSize, UInt32 outSize); + HRESULT Code_WithExceedReadWrite(const Byte *inData, size_t inSize, UInt32 outSize) throw(); - bool WasBlockFinished() const { return _unpackBlockSize == 0; } + bool WasBlockFinished() const { return _unpackBlockSize == 0; } const Byte *GetUnpackData() const { return _unpackedData; } - const UInt32 GetUnpackSize() const { return _pos - _writePos; } + UInt32 GetUnpackSize() const { return _pos - _writePos; } }; }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/Mtf8.h b/src/plugins/7zip/7za/cpp/7zip/Compress/Mtf8.h index 5d49fe44..5fce30e9 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/Mtf8.h +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/Mtf8.h @@ -1,7 +1,7 @@ // Mtf8.h -#ifndef __COMPRESS_MTF8_H -#define __COMPRESS_MTF8_H +#ifndef ZIP7_INC_COMPRESS_MTF8_H +#define ZIP7_INC_COMPRESS_MTF8_H #include "../../../C/CpuArch.h" @@ -11,11 +11,23 @@ struct CMtf8Encoder { Byte Buf[256]; - unsigned FindAndMove(Byte v) + unsigned FindAndMove(Byte v) throw() { - unsigned pos; +#if 1 + Byte b = Buf[0]; + if (v == b) + return 0; + Buf[0] = v; + for (unsigned pos = 0;;) + { + Byte a; + a = Buf[++pos]; Buf[pos] = b; if (v == a) return pos; + b = Buf[++pos]; Buf[pos] = a; if (v == b) return pos; + } +#else + size_t pos; for (pos = 0; Buf[pos] != v; pos++); - unsigned resPos = pos; + const unsigned resPos = (unsigned)pos; for (; pos >= 8; pos -= 8) { Buf[pos] = Buf[pos - 1]; @@ -31,6 +43,7 @@ struct CMtf8Encoder Buf[pos] = Buf[pos - 1]; Buf[0] = v; return resPos; +#endif } }; @@ -39,9 +52,10 @@ struct CMtf8Decoder { Byte Buf[256]; - void Init(int) {}; + void StartInit() { memset(Buf, 0, sizeof(Buf)); } + void Add(unsigned pos, Byte val) { Buf[pos] = val; } Byte GetHead() const { return Buf[0]; } - Byte GetAndMove(int pos) + Byte GetAndMove(unsigned pos) { Byte res = Buf[pos]; for (; pos >= 8; pos -= 8) @@ -64,35 +78,40 @@ struct CMtf8Decoder */ #ifdef MY_CPU_64BIT -typedef UInt64 CMtfVar; -#define MTF_MOVS 3 + typedef UInt64 CMtfVar; + #define Z7_MTF_MOVS 3 #else -typedef UInt32 CMtfVar; -#define MTF_MOVS 2 + typedef UInt32 CMtfVar; + #define Z7_MTF_MOVS 2 #endif -#define MTF_MASK ((1 << MTF_MOVS) - 1) +#define Z7_MTF_MASK ((1 << Z7_MTF_MOVS) - 1) struct CMtf8Decoder { - CMtfVar Buf[256 >> MTF_MOVS]; + CMtfVar Buf[256 >> Z7_MTF_MOVS]; void StartInit() { memset(Buf, 0, sizeof(Buf)); } - void Add(unsigned pos, Byte val) { Buf[pos >> MTF_MOVS] |= ((CMtfVar)val << ((pos & MTF_MASK) << 3)); } + void Add(unsigned pos, Byte val) { Buf[pos >> Z7_MTF_MOVS] |= ((CMtfVar)val << ((pos & Z7_MTF_MASK) << 3)); } Byte GetHead() const { return (Byte)Buf[0]; } - Byte GetAndMove(unsigned pos) + + Z7_FORCE_INLINE + Byte GetAndMove(unsigned pos) throw() { - UInt32 lim = ((UInt32)pos >> MTF_MOVS); - pos = (pos & MTF_MASK) << 3; + const UInt32 lim = ((UInt32)pos >> Z7_MTF_MOVS); + pos = (pos & Z7_MTF_MASK) << 3; CMtfVar prev = (Buf[lim] >> pos) & 0xFF; UInt32 i = 0; + + + /* if ((lim & 1) != 0) { CMtfVar next = Buf[0]; Buf[0] = (next << 8) | prev; - prev = (next >> (MTF_MASK << 3)); + prev = (next >> (Z7_MTF_MASK << 3)); i = 1; lim -= 1; } @@ -101,11 +120,21 @@ struct CMtf8Decoder CMtfVar n0 = Buf[i]; CMtfVar n1 = Buf[i + 1]; Buf[i ] = (n0 << 8) | prev; - Buf[i + 1] = (n1 << 8) | (n0 >> (MTF_MASK << 3)); - prev = (n1 >> (MTF_MASK << 3)); + Buf[i + 1] = (n1 << 8) | (n0 >> (Z7_MTF_MASK << 3)); + prev = (n1 >> (Z7_MTF_MASK << 3)); + } + */ + + for (; i < lim; i++) + { + const CMtfVar n0 = Buf[i]; + Buf[i ] = (n0 << 8) | prev; + prev = (n0 >> (Z7_MTF_MASK << 3)); } - CMtfVar next = Buf[i]; - CMtfVar mask = (((CMtfVar)0x100 << pos) - 1); + + + const CMtfVar next = Buf[i]; + const CMtfVar mask = (((CMtfVar)0x100 << pos) - 1); Buf[i] = (next & ~mask) | (((next << 8) | prev) & mask); return (Byte)Buf[0]; } @@ -117,7 +146,7 @@ class CMtf8Decoder { Byte SmallBuffer[kSmallSize]; int SmallSize; - Byte Counts[16]; + int Counts[16]; int Size; public: Byte Buf[256]; @@ -140,6 +169,11 @@ class CMtf8Decoder } } + void Add(unsigned pos, Byte val) + { + Buf[pos] = val; + } + Byte GetAndMove(int pos) { if (pos < SmallSize) diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/PpmdDecoder.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/PpmdDecoder.cpp index c868730e..3dbde751 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/PpmdDecoder.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/PpmdDecoder.cpp @@ -1,5 +1,4 @@ // PpmdDecoder.cpp -// 2009-03-11 : Igor Pavlov : Public domain #include "StdAfx.h" @@ -13,13 +12,13 @@ namespace NCompress { namespace NPpmd { -static const UInt32 kBufSize = (1 << 20); +static const UInt32 kBufSize = (1 << 16); enum { kStatus_NeedInit, kStatus_Normal, - kStatus_Finished, + kStatus_Finished_With_Mark, kStatus_Error }; @@ -29,12 +28,12 @@ CDecoder::~CDecoder() Ppmd7_Free(&_ppmd, &g_BigAlloc); } -STDMETHODIMP CDecoder::SetDecoderProperties2(const Byte *props, UInt32 size) +Z7_COM7F_IMF(CDecoder::SetDecoderProperties2(const Byte *props, UInt32 size)) { if (size < 5) return E_INVALIDARG; _order = props[0]; - UInt32 memSize = GetUi32(props + 1); + const UInt32 memSize = GetUi32(props + 1); if (_order < PPMD7_MIN_ORDER || _order > PPMD7_MAX_ORDER || memSize < PPMD7_MIN_MEM_SIZE || @@ -47,23 +46,37 @@ STDMETHODIMP CDecoder::SetDecoderProperties2(const Byte *props, UInt32 size) return S_OK; } +#define MY_rangeDec _ppmd.rc.dec + +#define CHECK_EXTRA_ERROR \ + if (_inStream.Extra) { \ + _status = kStatus_Error; \ + return (_res = (_inStream.Res != SZ_OK ? _inStream.Res: S_FALSE)); } + + HRESULT CDecoder::CodeSpec(Byte *memStream, UInt32 size) { + if (_res != S_OK) + return _res; + switch (_status) { - case kStatus_Finished: return S_OK; + case kStatus_Finished_With_Mark: return S_OK; case kStatus_Error: return S_FALSE; case kStatus_NeedInit: _inStream.Init(); - if (!Ppmd7z_RangeDec_Init(&_rangeDec)) + if (!Ppmd7z_RangeDec_Init(&MY_rangeDec)) { _status = kStatus_Error; - return S_FALSE; + return (_res = S_FALSE); } + CHECK_EXTRA_ERROR _status = kStatus_Normal; Ppmd7_Init(&_ppmd, _order); break; + default: break; } + if (_outSizeDefined) { const UInt64 rem = _outSize - _processedSize; @@ -71,29 +84,54 @@ HRESULT CDecoder::CodeSpec(Byte *memStream, UInt32 size) size = (UInt32)rem; } - UInt32 i; int sym = 0; - for (i = 0; i != size; i++) { - sym = Ppmd7_DecodeSymbol(&_ppmd, &_rangeDec.p); - if (_inStream.Extra || sym < 0) - break; - memStream[i] = (Byte)sym; + Byte *buf = memStream; + const Byte *lim = buf + size; + for (; buf != lim; buf++) + { + sym = Ppmd7z_DecodeSymbol(&_ppmd); + if (_inStream.Extra || sym < 0) + break; + *buf = (Byte)sym; + } + /* + buf = Ppmd7z_DecodeSymbols(&_ppmd, buf, lim); + sym = _ppmd.LastSymbol; + */ + _processedSize += (size_t)(buf - memStream); + } + + CHECK_EXTRA_ERROR + + if (sym >= 0) + { + if (!FinishStream + || !_outSizeDefined + || _outSize != _processedSize + || MY_rangeDec.Code == 0) + return S_OK; + /* + // We can decode additional End Marker here: + sym = Ppmd7z_DecodeSymbol(&_ppmd); + CHECK_EXTRA_ERROR + */ } - _processedSize += i; - if (_inStream.Extra) + if (sym != PPMD7_SYM_END || MY_rangeDec.Code != 0) { _status = kStatus_Error; - return _inStream.Res; + return (_res = S_FALSE); } - if (sym < 0) - _status = (sym < -1) ? kStatus_Error : kStatus_Finished; + + _status = kStatus_Finished_With_Mark; return S_OK; } -STDMETHODIMP CDecoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 * /* inSize */, const UInt64 *outSize, ICompressProgressInfo *progress) + + +Z7_COM7F_IMF(CDecoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, + const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress)) { if (!_outBuf) { @@ -108,51 +146,69 @@ STDMETHODIMP CDecoder::Code(ISequentialInStream *inStream, ISequentialOutStream do { const UInt64 startPos = _processedSize; - HRESULT res = CodeSpec(_outBuf, kBufSize); - size_t processed = (size_t)(_processedSize - startPos); - RINOK(WriteStream(outStream, _outBuf, processed)); - RINOK(res); - if (_status == kStatus_Finished) + const HRESULT res = CodeSpec(_outBuf, kBufSize); + const size_t processed = (size_t)(_processedSize - startPos); + RINOK(WriteStream(outStream, _outBuf, processed)) + RINOK(res) + if (_status == kStatus_Finished_With_Mark) break; if (progress) { - UInt64 inSize = _inStream.GetProcessed(); - RINOK(progress->SetRatioInfo(&inSize, &_processedSize)); + const UInt64 inProcessed = _inStream.GetProcessed(); + RINOK(progress->SetRatioInfo(&inProcessed, &_processedSize)) } } while (!_outSizeDefined || _processedSize < _outSize); + + if (FinishStream && inSize && *inSize != _inStream.GetProcessed()) + return S_FALSE; + return S_OK; } -STDMETHODIMP CDecoder::SetOutStreamSize(const UInt64 *outSize) + +Z7_COM7F_IMF(CDecoder::SetOutStreamSize(const UInt64 *outSize)) { _outSizeDefined = (outSize != NULL); if (_outSizeDefined) _outSize = *outSize; _processedSize = 0; _status = kStatus_NeedInit; + _res = SZ_OK; + return S_OK; +} + +Z7_COM7F_IMF(CDecoder::SetFinishMode(UInt32 finishMode)) +{ + FinishStream = (finishMode != 0); + return S_OK; +} + +Z7_COM7F_IMF(CDecoder::GetInStreamProcessedSize(UInt64 *value)) +{ + *value = _inStream.GetProcessed(); return S_OK; } -#ifndef NO_READ_FROM_CODER +#ifndef Z7_NO_READ_FROM_CODER -STDMETHODIMP CDecoder::SetInStream(ISequentialInStream *inStream) +Z7_COM7F_IMF(CDecoder::SetInStream(ISequentialInStream *inStream)) { InSeqStream = inStream; _inStream.Stream = inStream; return S_OK; } -STDMETHODIMP CDecoder::ReleaseInStream() +Z7_COM7F_IMF(CDecoder::ReleaseInStream()) { InSeqStream.Release(); return S_OK; } -STDMETHODIMP CDecoder::Read(void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CDecoder::Read(void *data, UInt32 size, UInt32 *processedSize)) { const UInt64 startPos = _processedSize; - HRESULT res = CodeSpec((Byte *)data, size); + const HRESULT res = CodeSpec((Byte *)data, size); if (processedSize) *processedSize = (UInt32)(_processedSize - startPos); return res; diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/PpmdDecoder.h b/src/plugins/7zip/7za/cpp/7zip/Compress/PpmdDecoder.h index 8ebcd700..22e5bd49 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/PpmdDecoder.h +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/PpmdDecoder.h @@ -1,37 +1,63 @@ // PpmdDecoder.h -// 2009-03-11 : Igor Pavlov : Public domain -#ifndef __COMPRESS_PPMD_DECODER_H -#define __COMPRESS_PPMD_DECODER_H +#ifndef ZIP7_INC_COMPRESS_PPMD_DECODER_H +#define ZIP7_INC_COMPRESS_PPMD_DECODER_H #include "../../../C/Ppmd7.h" #include "../../Common/MyCom.h" -#include "../Common/CWrappers.h" - #include "../ICoder.h" +#include "../Common/CWrappers.h" + namespace NCompress { namespace NPpmd { -class CDecoder : +class CDecoder Z7_final: public ICompressCoder, public ICompressSetDecoderProperties2, - #ifndef NO_READ_FROM_CODER + public ICompressSetFinishMode, + public ICompressGetInStreamProcessedSize, + #ifndef Z7_NO_READ_FROM_CODER public ICompressSetInStream, public ICompressSetOutStreamSize, public ISequentialInStream, - #endif + #endif public CMyUnknownImp { + Z7_COM_QI_BEGIN2(ICompressCoder) + Z7_COM_QI_ENTRY(ICompressSetDecoderProperties2) + Z7_COM_QI_ENTRY(ICompressSetFinishMode) + Z7_COM_QI_ENTRY(ICompressGetInStreamProcessedSize) + #ifndef Z7_NO_READ_FROM_CODER + Z7_COM_QI_ENTRY(ICompressSetInStream) + Z7_COM_QI_ENTRY(ICompressSetOutStreamSize) + Z7_COM_QI_ENTRY(ISequentialInStream) + #endif + Z7_COM_QI_END + Z7_COM_ADDREF_RELEASE + + Z7_IFACE_COM7_IMP(ICompressCoder) + Z7_IFACE_COM7_IMP(ICompressSetDecoderProperties2) + Z7_IFACE_COM7_IMP(ICompressSetFinishMode) + Z7_IFACE_COM7_IMP(ICompressGetInStreamProcessedSize) + #ifndef Z7_NO_READ_FROM_CODER + Z7_IFACE_COM7_IMP(ICompressSetOutStreamSize) + Z7_IFACE_COM7_IMP(ICompressSetInStream) + Z7_IFACE_COM7_IMP(ISequentialInStream) + #else + Z7_COM7F_IMF(SetOutStreamSize(const UInt64 *outSize)); + #endif + Byte *_outBuf; - CPpmd7z_RangeDec _rangeDec; CByteInBufWrap _inStream; CPpmd7 _ppmd; Byte _order; + bool FinishStream; bool _outSizeDefined; + HRESULT _res; int _status; UInt64 _outSize; UInt64 _processedSize; @@ -40,34 +66,17 @@ class CDecoder : public: - #ifndef NO_READ_FROM_CODER + #ifndef Z7_NO_READ_FROM_CODER CMyComPtr InSeqStream; - MY_UNKNOWN_IMP4( - ICompressSetDecoderProperties2, - ICompressSetInStream, - ICompressSetOutStreamSize, - ISequentialInStream) - #else - MY_UNKNOWN_IMP1( - ICompressSetDecoderProperties2) - #endif - - STDMETHOD(Code)(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress); - STDMETHOD(SetDecoderProperties2)(const Byte *data, UInt32 size); - STDMETHOD(SetOutStreamSize)(const UInt64 *outSize); - - #ifndef NO_READ_FROM_CODER - STDMETHOD(SetInStream)(ISequentialInStream *inStream); - STDMETHOD(ReleaseInStream)(); - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); - #endif - - CDecoder(): _outBuf(NULL), _outSizeDefined(false) + #endif + + CDecoder(): + _outBuf(NULL), + FinishStream(false), + _outSizeDefined(false) { - Ppmd7z_RangeDec_CreateVTable(&_rangeDec); - _rangeDec.Stream = &_inStream.p; Ppmd7_Construct(&_ppmd); + _ppmd.rc.dec.Stream = &_inStream.vt; } ~CDecoder(); diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/PpmdEncoder.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/PpmdEncoder.cpp index 00ea9668..2dfca6df 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/PpmdEncoder.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/PpmdEncoder.cpp @@ -3,7 +3,6 @@ #include "StdAfx.h" #include "../../../C/Alloc.h" -#include "../../../C/CpuArch.h" #include "../Common/StreamUtils.h" @@ -21,11 +20,11 @@ void CEncProps::Normalize(int level) if (level < 0) level = 5; if (level > 9) level = 9; if (MemSize == (UInt32)(Int32)-1) - MemSize = level >= 9 ? ((UInt32)192 << 20) : ((UInt32)1 << (level + 19)); + MemSize = (UInt32)1 << (level + 19); const unsigned kMult = 16; if (MemSize / kMult > ReduceSize) { - for (unsigned i = 16; i <= 31; i++) + for (unsigned i = 16; i < 32; i++) { UInt32 m = (UInt32)1 << i; if (ReduceSize <= m / kMult) @@ -43,8 +42,8 @@ CEncoder::CEncoder(): _inBuf(NULL) { _props.Normalize(-1); - _rangeEnc.Stream = &_outStream.p; Ppmd7_Construct(&_ppmd); + _ppmd.rc.enc.Stream = &_outStream.vt; } CEncoder::~CEncoder() @@ -53,14 +52,14 @@ CEncoder::~CEncoder() Ppmd7_Free(&_ppmd, &g_BigAlloc); } -STDMETHODIMP CEncoder::SetCoderProperties(const PROPID *propIDs, const PROPVARIANT *coderProps, UInt32 numProps) +Z7_COM7F_IMF(CEncoder::SetCoderProperties(const PROPID *propIDs, const PROPVARIANT *coderProps, UInt32 numProps)) { int level = -1; CEncProps props; for (UInt32 i = 0; i < numProps; i++) { const PROPVARIANT &prop = coderProps[i]; - PROPID propID = propIDs[i]; + const PROPID propID = propIDs[i]; if (propID > NCoderPropID::kReduceSize) continue; if (propID == NCoderPropID::kReduceSize) @@ -69,16 +68,50 @@ STDMETHODIMP CEncoder::SetCoderProperties(const PROPID *propIDs, const PROPVARIA props.ReduceSize = (UInt32)prop.uhVal.QuadPart; continue; } + + if (propID == NCoderPropID::kUsedMemorySize) + { + // here we have selected (4 GiB - 1 KiB) as replacement for (4 GiB) MEM_SIZE. + const UInt32 kPpmd_Default_4g = (UInt32)0 - ((UInt32)1 << 10); + UInt32 v; + if (prop.vt == VT_UI8) + { + // 21.03 : we support 64-bit values (for 4 GiB value) + const UInt64 v64 = prop.uhVal.QuadPart; + if (v64 > ((UInt64)1 << 32)) + return E_INVALIDARG; + if (v64 == ((UInt64)1 << 32)) + v = kPpmd_Default_4g; + else + v = (UInt32)v64; + } + else if (prop.vt == VT_UI4) + v = (UInt32)prop.ulVal; + else + return E_INVALIDARG; + if (v > PPMD7_MAX_MEM_SIZE) + v = kPpmd_Default_4g; + + /* here we restrict MEM_SIZE for Encoder. + It's for better performance of encoding and decoding. + The Decoder still supports more MEM_SIZE values. */ + if (v < ((UInt32)1 << 16) || (v & 3) != 0) + return E_INVALIDARG; + // if (v < PPMD7_MIN_MEM_SIZE) return E_INVALIDARG; // (1 << 11) + /* + Supported MEM_SIZE range : + [ (1 << 11) , 0xFFFFFFFF - 12 * 3 ] - current 7-Zip's Ppmd7 constants + [ 1824 , 0xFFFFFFFF ] - real limits of Ppmd7 code + */ + props.MemSize = v; + continue; + } + if (prop.vt != VT_UI4) return E_INVALIDARG; - UInt32 v = (UInt32)prop.ulVal; + const UInt32 v = (UInt32)prop.ulVal; switch (propID) { - case NCoderPropID::kUsedMemorySize: - if (v < (1 << 16) || v > PPMD7_MAX_MEM_SIZE || (v & 3) != 0) - return E_INVALIDARG; - props.MemSize = v; - break; case NCoderPropID::kOrder: if (v < 2 || v > 32) return E_INVALIDARG; @@ -94,17 +127,17 @@ STDMETHODIMP CEncoder::SetCoderProperties(const PROPID *propIDs, const PROPVARIA return S_OK; } -STDMETHODIMP CEncoder::WriteCoderProperties(ISequentialOutStream *outStream) +Z7_COM7F_IMF(CEncoder::WriteCoderProperties(ISequentialOutStream *outStream)) { const UInt32 kPropSize = 5; Byte props[kPropSize]; props[0] = (Byte)_props.Order; - SetUi32(props + 1, _props.MemSize); + SetUi32(props + 1, _props.MemSize) return WriteStream(outStream, props, kPropSize); } -HRESULT CEncoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 * /* inSize */, const UInt64 * /* outSize */, ICompressProgressInfo *progress) +Z7_COM7F_IMF(CEncoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, + const UInt64 * /* inSize */, const UInt64 * /* outSize */, ICompressProgressInfo *progress)) { if (!_inBuf) { @@ -120,31 +153,39 @@ HRESULT CEncoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outS _outStream.Stream = outStream; _outStream.Init(); - Ppmd7z_RangeEnc_Init(&_rangeEnc); - Ppmd7_Init(&_ppmd, _props.Order); + Ppmd7z_Init_RangeEnc(&_ppmd); + Ppmd7_Init(&_ppmd, (unsigned)_props.Order); UInt64 processed = 0; for (;;) { UInt32 size; - RINOK(inStream->Read(_inBuf, kBufSize, &size)); + RINOK(inStream->Read(_inBuf, kBufSize, &size)) if (size == 0) { // We don't write EndMark in PPMD-7z. - // Ppmd7_EncodeSymbol(&_ppmd, &_rangeEnc, -1); - Ppmd7z_RangeEnc_FlushData(&_rangeEnc); + // Ppmd7z_EncodeSymbol(&_ppmd, -1); + Ppmd7z_Flush_RangeEnc(&_ppmd); return _outStream.Flush(); } - for (UInt32 i = 0; i < size; i++) + const Byte *buf = _inBuf; + const Byte *lim = buf + size; + /* + for (; buf < lim; buf++) { - Ppmd7_EncodeSymbol(&_ppmd, &_rangeEnc, _inBuf[i]); + Ppmd7z_EncodeSymbol(&_ppmd, *buf); RINOK(_outStream.Res); } + */ + + Ppmd7z_EncodeSymbols(&_ppmd, buf, lim); + RINOK(_outStream.Res) + processed += size; if (progress) { - UInt64 outSize = _outStream.GetProcessed(); - RINOK(progress->SetRatioInfo(&processed, &outSize)); + const UInt64 outSize = _outStream.GetProcessed(); + RINOK(progress->SetRatioInfo(&processed, &outSize)) } } } diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/PpmdEncoder.h b/src/plugins/7zip/7za/cpp/7zip/Compress/PpmdEncoder.h index 671a5353..057cccbe 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/PpmdEncoder.h +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/PpmdEncoder.h @@ -1,7 +1,7 @@ // PpmdEncoder.h -#ifndef __COMPRESS_PPMD_ENCODER_H -#define __COMPRESS_PPMD_ENCODER_H +#ifndef ZIP7_INC_COMPRESS_PPMD_ENCODER_H +#define ZIP7_INC_COMPRESS_PPMD_ENCODER_H #include "../../../C/Ppmd7.h" @@ -29,26 +29,17 @@ struct CEncProps void Normalize(int level); }; -class CEncoder : - public ICompressCoder, - public ICompressSetCoderProperties, - public ICompressWriteCoderProperties, - public CMyUnknownImp -{ +Z7_CLASS_IMP_COM_3( + CEncoder + , ICompressCoder + , ICompressSetCoderProperties + , ICompressWriteCoderProperties +) Byte *_inBuf; CByteOutBufWrap _outStream; - CPpmd7z_RangeEnc _rangeEnc; CPpmd7 _ppmd; CEncProps _props; public: - MY_UNKNOWN_IMP3( - ICompressCoder, - ICompressSetCoderProperties, - ICompressWriteCoderProperties) - STDMETHOD(Code)(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress); - STDMETHOD(SetCoderProperties)(const PROPID *propIDs, const PROPVARIANT *props, UInt32 numProps); - STDMETHOD(WriteCoderProperties)(ISequentialOutStream *outStream); CEncoder(); ~CEncoder(); }; diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/PpmdRegister.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/PpmdRegister.cpp index a3ebb5f3..fb5619c9 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/PpmdRegister.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/PpmdRegister.cpp @@ -6,7 +6,7 @@ #include "PpmdDecoder.h" -#ifndef EXTRACT_ONLY +#ifndef Z7_EXTRACT_ONLY #include "PpmdEncoder.h" #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/PpmdZip.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/PpmdZip.cpp index 24dd32f2..e59e52f1 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/PpmdZip.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/PpmdZip.cpp @@ -12,11 +12,20 @@ namespace NCompress { namespace NPpmdZip { +static const UInt32 kBufSize = 1 << 20; + +bool CBuf::Alloc() +{ + if (!Buf) + Buf = (Byte *)::MidAlloc(kBufSize); + return (Buf != NULL); +} + CDecoder::CDecoder(bool fullFileMode): _fullFileMode(fullFileMode) { - _ppmd.Stream.In = &_inStream.p; Ppmd8_Construct(&_ppmd); + _ppmd.Stream.In = &_inStream.vt; } CDecoder::~CDecoder() @@ -24,9 +33,11 @@ CDecoder::~CDecoder() Ppmd8_Free(&_ppmd, &g_BigAlloc); } -STDMETHODIMP CDecoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 * /* inSize */, const UInt64 *outSize, ICompressProgressInfo *progress) +Z7_COM7F_IMF(CDecoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, + const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress)) { + // try { + if (!_outStream.Alloc()) return E_OUTOFMEMORY; if (!_inStream.Alloc(1 << 20)) @@ -42,10 +53,10 @@ STDMETHODIMP CDecoder::Code(ISequentialInStream *inStream, ISequentialOutStream if (_inStream.Extra) return S_FALSE; - UInt32 val = GetUi16(buf); - UInt32 order = (val & 0xF) + 1; - UInt32 mem = ((val >> 4) & 0xFF) + 1; - UInt32 restor = (val >> 12); + const UInt32 val = GetUi16(buf); + const unsigned order = (val & 0xF) + 1; + const UInt32 mem = ((val >> 4) & 0xFF) + 1; + const unsigned restor = (val >> 12); if (order < 2 || restor > 2) return S_FALSE; @@ -57,38 +68,47 @@ STDMETHODIMP CDecoder::Code(ISequentialInStream *inStream, ISequentialOutStream if (!Ppmd8_Alloc(&_ppmd, mem << 20, &g_BigAlloc)) return E_OUTOFMEMORY; - if (!Ppmd8_RangeDec_Init(&_ppmd)) + if (!Ppmd8_Init_RangeDec(&_ppmd)) return S_FALSE; Ppmd8_Init(&_ppmd, order, restor); } bool wasFinished = false; UInt64 processedSize = 0; - while (!outSize || processedSize < *outSize) + + for (;;) { size_t size = kBufSize; - if (outSize != NULL) + if (outSize) { const UInt64 rem = *outSize - processedSize; if (size > rem) + { size = (size_t)rem; + if (size == 0) + break; + } } - Byte *data = _outStream.Buf; - size_t i = 0; - int sym = 0; + + int sym; + Byte *buf = _outStream.Buf; + const Byte *lim = buf + size; + do { sym = Ppmd8_DecodeSymbol(&_ppmd); if (_inStream.Extra || sym < 0) break; - data[i] = (Byte)sym; + *buf++ = (Byte)sym; } - while (++i != size); - processedSize += i; + while (buf != lim); + + const size_t cur = (size_t)(buf - _outStream.Buf); + processedSize += cur; - RINOK(WriteStream(outStream, _outStream.Buf, i)); + RINOK(WriteStream(outStream, _outStream.Buf, cur)) - RINOK(_inStream.Res); + RINOK(_inStream.Res) if (_inStream.Extra) return S_FALSE; @@ -99,28 +119,51 @@ STDMETHODIMP CDecoder::Code(ISequentialInStream *inStream, ISequentialOutStream wasFinished = true; break; } + if (progress) { - UInt64 inSize = _inStream.GetProcessed(); - RINOK(progress->SetRatioInfo(&inSize, &processedSize)); + const UInt64 inProccessed = _inStream.GetProcessed(); + RINOK(progress->SetRatioInfo(&inProccessed, &processedSize)) } } - RINOK(_inStream.Res); + + RINOK(_inStream.Res) + if (_fullFileMode) { if (!wasFinished) { - int res = Ppmd8_DecodeSymbol(&_ppmd); - RINOK(_inStream.Res); + const int res = Ppmd8_DecodeSymbol(&_ppmd); + RINOK(_inStream.Res) if (_inStream.Extra || res != -1) return S_FALSE; } if (!Ppmd8_RangeDec_IsFinishedOK(&_ppmd)) return S_FALSE; + + if (inSize && *inSize != _inStream.GetProcessed()) + return S_FALSE; } + + return S_OK; + + // } catch (...) { return E_FAIL; } +} + + +Z7_COM7F_IMF(CDecoder::SetFinishMode(UInt32 finishMode)) +{ + _fullFileMode = (finishMode != 0); return S_OK; } +Z7_COM7F_IMF(CDecoder::GetInStreamProcessedSize(UInt64 *value)) +{ + *value = _inStream.GetProcessed(); + return S_OK; +} + + // ---------- Encoder ---------- @@ -130,21 +173,14 @@ void CEncProps::Normalize(int level) if (level == 0) level = 1; if (level > 9) level = 9; if (MemSizeMB == (UInt32)(Int32)-1) - MemSizeMB = (1 << ((level > 8 ? 8 : level) - 1)); + MemSizeMB = 1 << (level - 1); const unsigned kMult = 16; - if ((MemSizeMB << 20) / kMult > ReduceSize) - { - for (UInt32 m = (1 << 20); m <= (1 << 28); m <<= 1) + for (UInt32 m = 1; m < MemSizeMB; m <<= 1) + if (ReduceSize <= (m << 20) / kMult) { - if (ReduceSize <= m / kMult) - { - m >>= 20; - if (MemSizeMB > m) - MemSizeMB = m; - break; - } + MemSizeMB = m; + break; } - } if (Order == -1) Order = 3 + level; if (Restor == -1) Restor = level < 7 ? @@ -157,25 +193,26 @@ CEncoder::~CEncoder() Ppmd8_Free(&_ppmd, &g_BigAlloc); } -STDMETHODIMP CEncoder::SetCoderProperties(const PROPID *propIDs, const PROPVARIANT *coderProps, UInt32 numProps) +Z7_COM7F_IMF(CEncoder::SetCoderProperties(const PROPID *propIDs, const PROPVARIANT *coderProps, UInt32 numProps)) { int level = -1; CEncProps props; for (UInt32 i = 0; i < numProps; i++) { const PROPVARIANT &prop = coderProps[i]; - PROPID propID = propIDs[i]; + const PROPID propID = propIDs[i]; if (propID > NCoderPropID::kReduceSize) continue; if (propID == NCoderPropID::kReduceSize) { + props.ReduceSize = (UInt32)(Int32)-1; if (prop.vt == VT_UI8 && prop.uhVal.QuadPart < (UInt32)(Int32)-1) props.ReduceSize = (UInt32)prop.uhVal.QuadPart; continue; } if (prop.vt != VT_UI4) return E_INVALIDARG; - UInt32 v = (UInt32)prop.ulVal; + const UInt32 v = (UInt32)prop.ulVal; switch (propID) { case NCoderPropID::kUsedMemorySize: @@ -191,9 +228,9 @@ STDMETHODIMP CEncoder::SetCoderProperties(const PROPID *propIDs, const PROPVARIA case NCoderPropID::kNumThreads: break; case NCoderPropID::kLevel: level = (int)v; break; case NCoderPropID::kAlgorithm: - if (v > 1) + if (v >= PPMD8_RESTORE_METHOD_UNSUPPPORTED) return E_INVALIDARG; - props.Restor = v; + props.Restor = (int)v; break; default: return E_INVALIDARG; } @@ -206,12 +243,12 @@ STDMETHODIMP CEncoder::SetCoderProperties(const PROPID *propIDs, const PROPVARIA CEncoder::CEncoder() { _props.Normalize(-1); - _ppmd.Stream.Out = &_outStream.p; + _ppmd.Stream.Out = &_outStream.vt; Ppmd8_Construct(&_ppmd); } -STDMETHODIMP CEncoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 * /* inSize */, const UInt64 * /* outSize */, ICompressProgressInfo *progress) +Z7_COM7F_IMF(CEncoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, + const UInt64 * /* inSize */, const UInt64 * /* outSize */, ICompressProgressInfo *progress)) { if (!_inStream.Alloc()) return E_OUTOFMEMORY; @@ -223,35 +260,48 @@ STDMETHODIMP CEncoder::Code(ISequentialInStream *inStream, ISequentialOutStream _outStream.Stream = outStream; _outStream.Init(); - Ppmd8_RangeEnc_Init(&_ppmd); - Ppmd8_Init(&_ppmd, _props.Order, _props.Restor); + Ppmd8_Init_RangeEnc(&_ppmd) + Ppmd8_Init(&_ppmd, (unsigned)_props.Order, (unsigned)_props.Restor); - UInt32 val = (UInt32)((_props.Order - 1) + ((_props.MemSizeMB - 1) << 4) + (_props.Restor << 12)); - _outStream.WriteByte((Byte)(val & 0xFF)); - _outStream.WriteByte((Byte)(val >> 8)); - RINOK(_outStream.Res); + { + const unsigned val = + ((unsigned)_props.Order - 1) + + (((unsigned)_props.MemSizeMB - 1) << 4) + + ((unsigned)_props.Restor << 12); + _outStream.WriteByte((Byte)(val & 0xFF)); + _outStream.WriteByte((Byte)(val >> 8)); + } + RINOK(_outStream.Res) UInt64 processed = 0; for (;;) { UInt32 size; - RINOK(inStream->Read(_inStream.Buf, kBufSize, &size)); + RINOK(inStream->Read(_inStream.Buf, kBufSize, &size)) if (size == 0) { Ppmd8_EncodeSymbol(&_ppmd, -1); - Ppmd8_RangeEnc_FlushData(&_ppmd); + Ppmd8_Flush_RangeEnc(&_ppmd); return _outStream.Flush(); } - for (UInt32 i = 0; i < size; i++) + + processed += size; + const Byte *buf = _inStream.Buf; + const Byte *lim = buf + size; + do { - Ppmd8_EncodeSymbol(&_ppmd, _inStream.Buf[i]); - RINOK(_outStream.Res); + Ppmd8_EncodeSymbol(&_ppmd, *buf); + if (_outStream.Res != S_OK) + break; } - processed += size; - if (progress != NULL) + while (++buf != lim); + + RINOK(_outStream.Res) + + if (progress) { - UInt64 outSize = _outStream.GetProcessed(); - RINOK(progress->SetRatioInfo(&processed, &outSize)); + const UInt64 outProccessed = _outStream.GetProcessed(); + RINOK(progress->SetRatioInfo(&processed, &outProccessed)) } } } diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/PpmdZip.h b/src/plugins/7zip/7za/cpp/7zip/Compress/PpmdZip.h index e15a060d..6cc6af12 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/PpmdZip.h +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/PpmdZip.h @@ -1,52 +1,46 @@ // PpmdZip.h -#ifndef __COMPRESS_PPMD_ZIP_H -#define __COMPRESS_PPMD_ZIP_H +#ifndef ZIP7_INC_COMPRESS_PPMD_ZIP_H +#define ZIP7_INC_COMPRESS_PPMD_ZIP_H #include "../../../C/Alloc.h" #include "../../../C/Ppmd8.h" #include "../../Common/MyCom.h" -#include "../Common/CWrappers.h" - #include "../ICoder.h" +#include "../Common/CWrappers.h" + namespace NCompress { namespace NPpmdZip { -static const UInt32 kBufSize = (1 << 20); - struct CBuf { Byte *Buf; - CBuf(): Buf(0) {} + CBuf(): Buf(NULL) {} ~CBuf() { ::MidFree(Buf); } - bool Alloc() - { - if (!Buf) - Buf = (Byte *)::MidAlloc(kBufSize); - return (Buf != 0); - } + bool Alloc(); }; -class CDecoder : - public ICompressCoder, - public CMyUnknownImp -{ + +Z7_CLASS_IMP_NOQIB_3( + CDecoder + , ICompressCoder + , ICompressSetFinishMode + , ICompressGetInStreamProcessedSize +) + bool _fullFileMode; CByteInBufWrap _inStream; CBuf _outStream; CPpmd8 _ppmd; - bool _fullFileMode; public: - MY_UNKNOWN_IMP - STDMETHOD(Code)(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress); - CDecoder(bool fullFileMode); + CDecoder(bool fullFileMode = true); ~CDecoder(); }; + struct CEncProps { UInt32 MemSizeMB; @@ -64,20 +58,17 @@ struct CEncProps void Normalize(int level); }; -class CEncoder : - public ICompressCoder, - public ICompressSetCoderProperties, - public CMyUnknownImp -{ + +Z7_CLASS_IMP_NOQIB_2( + CEncoder + , ICompressCoder + , ICompressSetCoderProperties +) CByteOutBufWrap _outStream; CBuf _inStream; CPpmd8 _ppmd; CEncProps _props; public: - MY_UNKNOWN_IMP1(ICompressSetCoderProperties) - STDMETHOD(Code)(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress); - STDMETHOD(SetCoderProperties)(const PROPID *propIDs, const PROPVARIANT *props, UInt32 numProps); CEncoder(); ~CEncoder(); }; diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/QuantumDecoder.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/QuantumDecoder.cpp index 2adb9053..98817a40 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/QuantumDecoder.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/QuantumDecoder.cpp @@ -2,6 +2,11 @@ #include "StdAfx.h" +// #include + +#include "../../../C/Alloc.h" +#include "../../../C/CpuArch.h" + #include "../../Common/Defs.h" #include "QuantumDecoder.h" @@ -11,184 +16,339 @@ namespace NQuantum { static const unsigned kNumLenSymbols = 27; static const unsigned kMatchMinLen = 3; -static const unsigned kNumSimplePosSlots = 4; static const unsigned kNumSimpleLenSlots = 6; -static const UInt16 kUpdateStep = 8; -static const UInt16 kFreqSumMax = 3800; -static const unsigned kReorderCountStart = 4; +static const unsigned kUpdateStep = 8; +static const unsigned kFreqSumMax = 3800; +static const unsigned kReorderCount_Start = 4; static const unsigned kReorderCount = 50; -void CModelDecoder::Init(unsigned numItems) + +class CRangeDecoder { - NumItems = numItems; - ReorderCount = kReorderCountStart; - for (unsigned i = 0; i < numItems; i++) + UInt32 Low; + UInt32 Range; + UInt32 Code; + + unsigned _bitOffset; + const Byte *_buf; + const Byte *_bufLim; + +public: + + Z7_FORCE_INLINE + void Init(const Byte *inData, size_t inSize) { - Freqs[i] = (UInt16)(numItems - i); - Vals[i] = (Byte)i; + Code = ((UInt32)*inData << 8) | inData[1]; + _buf = inData + 2; + _bufLim = inData + inSize; + _bitOffset = 0; + Low = 0; + Range = 0x10000; } - Freqs[numItems] = 0; -} + Z7_FORCE_INLINE + bool WasExtraRead() const + { + return _buf > _bufLim; + } + + Z7_FORCE_INLINE + UInt32 ReadBits(unsigned numBits) // numBits > 0 + { + unsigned bitOffset = _bitOffset; + const Byte *buf = _buf; + const UInt32 res = GetBe32(buf) << bitOffset; + bitOffset += numBits; + _buf = buf + (bitOffset >> 3); + _bitOffset = bitOffset & 7; + return res >> (32 - numBits); + } + + // ---------- Range Decoder functions ---------- + + Z7_FORCE_INLINE + bool Finish() + { + const unsigned numBits = 2 + ((16 - 2 - _bitOffset) & 7); + if (ReadBits(numBits) != 0) + return false; + return _buf == _bufLim; + } + + Z7_FORCE_INLINE + UInt32 GetThreshold(UInt32 total) const + { + return ((Code + 1) * total - 1) / Range; // & 0xFFFF is not required; + } + + Z7_FORCE_INLINE + void Decode(UInt32 start, UInt32 end, UInt32 total) + { + // UInt32 hi = ~(Low + end * Range / total - 1); + UInt32 hi = 0 - (Low + end * Range / total); + const UInt32 offset = start * Range / total; + UInt32 lo = Low + offset; + Code -= offset; + UInt32 numBits = 0; + lo ^= hi; + while (lo & (1u << 15)) + { + lo <<= 1; + hi <<= 1; + numBits++; + } + lo ^= hi; + UInt32 an = lo & hi; + while (an & (1u << 14)) + { + an <<= 1; + lo <<= 1; + hi <<= 1; + numBits++; + } + Low = lo; + Range = ((~hi - lo) & 0xffff) + 1; + if (numBits) + Code = (Code << numBits) + ReadBits(numBits); + } +}; + + +// Z7_FORCE_INLINE +Z7_NO_INLINE unsigned CModelDecoder::Decode(CRangeDecoder *rc) +// Z7_NO_INLINE void CModelDecoder::Normalize() { - UInt32 threshold = rc->GetThreshold(Freqs[0]); - unsigned i; - for (i = 1; Freqs[i] > threshold; i++); - - rc->Decode(Freqs[i], Freqs[i - 1], Freqs[0]); - unsigned res = Vals[--i]; - - do - Freqs[i] += kUpdateStep; - while (i--); - if (Freqs[0] > kFreqSumMax) { if (--ReorderCount == 0) { ReorderCount = kReorderCount; - for (i = 0; i < NumItems; i++) - Freqs[i] = (UInt16)(((Freqs[i] - Freqs[i + 1]) + 1) >> 1); - for (i = 0; i < NumItems - 1; i++) - for (unsigned j = i + 1; j < NumItems; j++) - if (Freqs[i] < Freqs[j]) - { - UInt16 tmpFreq = Freqs[i]; - Byte tmpVal = Vals[i]; - Freqs[i] = Freqs[j]; - Vals[i] = Vals[j]; - Freqs[j] = tmpFreq; - Vals[j] = tmpVal; - } - + { + unsigned i = NumItems; + unsigned next = 0; + UInt16 *freqs = &Freqs[i]; + do + { + const unsigned freq = *--freqs; + *freqs = (UInt16)((freq - next + 1) >> 1); + next = freq; + } + while (--i); + } + { + for (unsigned i = 0; i < NumItems - 1; i++) + { + UInt16 freq = Freqs[i]; + for (unsigned k = i + 1; k < NumItems; k++) + if (freq < Freqs[k]) + { + const UInt16 freq2 = Freqs[k]; + Freqs[k] = freq; + Freqs[i] = freq2; + freq = freq2; + const Byte val = Vals[i]; + Vals[i] = Vals[k]; + Vals[k] = val; + } + } + } + unsigned i = NumItems; + unsigned freq = 0; + UInt16 *freqs = &Freqs[i]; do - Freqs[i] = (UInt16)(Freqs[i] + Freqs[i + 1]); - while (i--); + { + freq += *--freqs; + *freqs = (UInt16)freq; + } + while (--i); } else { - i = NumItems - 1; + unsigned i = NumItems; + unsigned next = 1; + UInt16 *freqs = &Freqs[i]; do { - Freqs[i] >>= 1; - if (Freqs[i] <= Freqs[i + 1]) - Freqs[i] = (UInt16)(Freqs[i + 1] + 1); + unsigned freq = *--freqs >> 1; + if (freq < next) + freq = next; + *freqs = (UInt16)freq; + next = freq + 1; } - while (i--); + while (--i); } } - + unsigned res; + { + const unsigned freq0 = Freqs[0]; + Freqs[0] = (UInt16)(freq0 + kUpdateStep); + const unsigned threshold = rc->GetThreshold(freq0); + UInt16 *freqs = &Freqs[1]; + unsigned freq = *freqs; + while (freq > threshold) + { + *freqs++ = (UInt16)(freq + kUpdateStep); + freq = *freqs; + } + res = Vals[freqs - Freqs - 1]; + rc->Decode(freq, freqs[-1] - kUpdateStep, freq0); + } return res; } -void CDecoder::Init() +Z7_NO_INLINE +void CModelDecoder::Init(unsigned numItems, unsigned startVal) { - m_Selector.Init(kNumSelectors); - unsigned i; - for (i = 0; i < kNumLitSelectors; i++) - m_Literals[i].Init(kNumLitSymbols); - unsigned numItems = (_numDictBits == 0 ? 1 : (_numDictBits << 1)); - const unsigned kNumPosSymbolsMax[kNumMatchSelectors] = { 24, 36, 42 }; - for (i = 0; i < kNumMatchSelectors; i++) - m_PosSlot[i].Init(MyMin(numItems, kNumPosSymbolsMax[i])); - m_LenSlot.Init(kNumLenSymbols); + NumItems = numItems; + ReorderCount = kReorderCount_Start; + UInt16 *freqs = Freqs; + freqs[numItems] = 0; + Byte *vals = Vals; + do + { + *freqs++ = (UInt16)numItems; + *vals++ = (Byte)startVal; + startVal++; + } + while (--numItems); } -HRESULT CDecoder::CodeSpec(const Byte *inData, size_t inSize, UInt32 outSize) +HRESULT CDecoder::Code(const Byte *inData, size_t inSize, UInt32 outSize, bool keepHistory) { if (inSize < 2) return S_FALSE; + if (!keepHistory) + { + _winPos = 0; + m_Selector.Init(kNumSelectors, 0); + unsigned i; + for (i = 0; i < kNumLitSelectors; i++) + m_Literals[i].Init(kNumLitSymbols, i * kNumLitSymbols); + const unsigned numItems = (_numDictBits == 0 ? 1 : (_numDictBits << 1)); + // const unsigned kNumPosSymbolsMax[kNumMatchSelectors] = { 24, 36, 42 }; + for (i = 0; i < kNumMatchSelectors; i++) + { + const unsigned num = 24 + i * 6 + ((i + 1) & 2) * 3; + m_PosSlot[i].Init(MyMin(numItems, num), 0); + } + m_LenSlot.Init(kNumLenSymbols, kMatchMinLen + kNumMatchSelectors - 1); + } CRangeDecoder rc; - rc.Stream.SetStreamAndInit(inData, inSize); - rc.Init(); + rc.Init(inData, inSize); + const UInt32 winSize = _winSize; + Byte *pos; + { + UInt32 winPos = _winPos; + if (winPos == winSize) + { + winPos = 0; + _winPos = winPos; + _overWin = true; + } + if (outSize > winSize - winPos) + return S_FALSE; + pos = _win + winPos; + } while (outSize != 0) { - if (rc.Stream.WasExtraRead()) + if (rc.WasExtraRead()) return S_FALSE; - unsigned selector = m_Selector.Decode(&rc); + const unsigned selector = m_Selector.Decode(&rc); if (selector < kNumLitSelectors) { - Byte b = (Byte)((selector << (8 - kNumLitSelectorBits)) + m_Literals[selector].Decode(&rc)); - _outWindow.PutByte(b); - outSize--; + const unsigned b = m_Literals[selector].Decode(&rc); + *pos++ = (Byte)b; + --outSize; + // if (--outSize == 0) break; } else { - selector -= kNumLitSelectors; - unsigned len = selector + kMatchMinLen; + unsigned len = selector - kNumLitSelectors + kMatchMinLen; - if (selector == 2) + if (selector == kNumLitSelectors + kNumMatchSelectors - 1) { - unsigned lenSlot = m_LenSlot.Decode(&rc); - if (lenSlot >= kNumSimpleLenSlots) + len = m_LenSlot.Decode(&rc); + if (len >= kNumSimpleLenSlots + kMatchMinLen + kNumMatchSelectors - 1) { - lenSlot -= 2; - unsigned numDirectBits = (unsigned)(lenSlot >> 2); - len += ((4 | (lenSlot & 3)) << numDirectBits) - 2; + len -= kNumSimpleLenSlots - 4 + kMatchMinLen + kNumMatchSelectors - 1; + const unsigned numDirectBits = (unsigned)(len >> 2); + len = ((4 | (len & 3)) << numDirectBits) - (4 << 1) + + kNumSimpleLenSlots + + kMatchMinLen + kNumMatchSelectors - 1; if (numDirectBits < 6) - len += rc.Stream.ReadBits(numDirectBits); + len += rc.ReadBits(numDirectBits); } - else - len += lenSlot; } - UInt32 dist = m_PosSlot[selector].Decode(&rc); + UInt32 dist = m_PosSlot[(size_t)selector - kNumLitSelectors].Decode(&rc); - if (dist >= kNumSimplePosSlots) + if (dist >= 4) { - unsigned numDirectBits = (unsigned)((dist >> 1) - 1); - dist = ((2 | (dist & 1)) << numDirectBits) + rc.Stream.ReadBits(numDirectBits); + const unsigned numDirectBits = (unsigned)((dist >> 1) - 1); + dist = ((2 | (dist & 1)) << numDirectBits) + rc.ReadBits(numDirectBits); } - unsigned locLen = len; - if (len > outSize) - locLen = (unsigned)outSize; - if (!_outWindow.CopyBlock(dist, locLen)) - return S_FALSE; - outSize -= locLen; - len -= locLen; - if (len != 0) + if ((Int32)(outSize -= len) < 0) return S_FALSE; + + ptrdiff_t srcPos = (ptrdiff_t)(Int32)((pos - _win) - (ptrdiff_t)dist - 1); + if (srcPos < 0) + { + if (!_overWin) + return S_FALSE; + UInt32 rem = (UInt32)-srcPos; + srcPos += winSize; + if (rem < len) + { + const Byte *src = _win + srcPos; + len -= rem; + do + *pos++ = *src++; + while (--rem); + srcPos = 0; + } + } + const Byte *src = _win + srcPos; + do + *pos++ = *src++; + while (--len); + // if (outSize == 0) break; } } + _winPos = (UInt32)(size_t)(pos - _win); return rc.Finish() ? S_OK : S_FALSE; } -HRESULT CDecoder::Code(const Byte *inData, size_t inSize, - ISequentialOutStream *outStream, UInt32 outSize, - bool keepHistory) -{ - try - { - _outWindow.SetStream(outStream); - _outWindow.Init(keepHistory); - if (!keepHistory) - Init(); - - HRESULT res = CodeSpec(inData, inSize, outSize); - HRESULT res2 = _outWindow.Flush(); - return res != S_OK ? res : res2; - } - catch(const CLzOutWindowException &e) { return e.ErrorCode; } - catch(...) { return S_FALSE; } -} HRESULT CDecoder::SetParams(unsigned numDictBits) { if (numDictBits > 21) return E_INVALIDARG; _numDictBits = numDictBits; - if (!_outWindow.Create((UInt32)1 << _numDictBits)) - return E_OUTOFMEMORY; + _winPos = 0; + _overWin = false; + + if (numDictBits < 15) + numDictBits = 15; + _winSize = (UInt32)1 << numDictBits; + if (!_win || _winSize > _winSize_allocated) + { + MidFree(_win); + _win = NULL; + _win = (Byte *)MidAlloc(_winSize); + if (!_win) + return E_OUTOFMEMORY; + _winSize_allocated = _winSize; + } return S_OK; } diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/QuantumDecoder.h b/src/plugins/7zip/7za/cpp/7zip/Compress/QuantumDecoder.h index afeba708..fd5baea7 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/QuantumDecoder.h +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/QuantumDecoder.h @@ -1,152 +1,43 @@ // QuantumDecoder.h -#ifndef __COMPRESS_QUANTUM_DECODER_H -#define __COMPRESS_QUANTUM_DECODER_H +#ifndef ZIP7_INC_COMPRESS_QUANTUM_DECODER_H +#define ZIP7_INC_COMPRESS_QUANTUM_DECODER_H -#include "../../Common/MyCom.h" - -#include "LzOutWindow.h" +#include "../../Common/MyTypes.h" namespace NCompress { namespace NQuantum { -class CBitDecoder -{ - UInt32 Value; - bool _extra; - const Byte *_buf; - const Byte *_bufLim; -public: - void SetStreamAndInit(const Byte *inData, size_t inSize) - { - _buf = inData; - _bufLim = inData + inSize; - Value = 0x10000; - _extra = false; - } - - bool WasExtraRead() const { return _extra; } - - bool WasFinishedOK() const - { - return !_extra && _buf == _bufLim; - } - - UInt32 ReadBit() - { - if (Value >= 0x10000) - { - Byte b; - if (_buf >= _bufLim) - { - b = 0xFF; - _extra = true; - } - else - b = *_buf++; - Value = 0x100 | b; - } - UInt32 res = (Value >> 7) & 1; - Value <<= 1; - return res; - } - - UInt32 ReadStart16Bits() - { - // we use check for extra read in another code. - UInt32 val = ((UInt32)*_buf << 8) | _buf[1]; - _buf += 2; - return val; - } - - UInt32 ReadBits(unsigned numBits) // numBits > 0 - { - UInt32 res = 0; - do - res = (res << 1) | ReadBit(); - while (--numBits); - return res; - } -}; - - -class CRangeDecoder -{ - UInt32 Low; - UInt32 Range; - UInt32 Code; -public: - CBitDecoder Stream; - - void Init() - { - Low = 0; - Range = 0x10000; - Code = Stream.ReadStart16Bits(); - } - - bool Finish() - { - // do all streams use these two bits at end? - if (Stream.ReadBit() != 0) return false; - if (Stream.ReadBit() != 0) return false; - return Stream.WasFinishedOK(); - } - - UInt32 GetThreshold(UInt32 total) const - { - return ((Code + 1) * total - 1) / Range; // & 0xFFFF is not required; - } - - void Decode(UInt32 start, UInt32 end, UInt32 total) - { - UInt32 high = Low + end * Range / total - 1; - UInt32 offset = start * Range / total; - Code -= offset; - Low += offset; - for (;;) - { - if ((Low & 0x8000) != (high & 0x8000)) - { - if ((Low & 0x4000) == 0 || (high & 0x4000) != 0) - break; - Low &= 0x3FFF; - high |= 0x4000; - } - Low = (Low << 1) & 0xFFFF; - high = ((high << 1) | 1) & 0xFFFF; - Code = ((Code << 1) | Stream.ReadBit()); - } - Range = high - Low + 1; - } -}; - - const unsigned kNumLitSelectorBits = 2; -const unsigned kNumLitSelectors = (1 << kNumLitSelectorBits); +const unsigned kNumLitSelectors = 1 << kNumLitSelectorBits; const unsigned kNumLitSymbols = 1 << (8 - kNumLitSelectorBits); const unsigned kNumMatchSelectors = 3; const unsigned kNumSelectors = kNumLitSelectors + kNumMatchSelectors; const unsigned kNumSymbolsMax = kNumLitSymbols; // 64 +class CRangeDecoder; class CModelDecoder { unsigned NumItems; unsigned ReorderCount; - UInt16 Freqs[kNumSymbolsMax + 1]; Byte Vals[kNumSymbolsMax]; + UInt16 Freqs[kNumSymbolsMax + 1]; public: - void Init(unsigned numItems); + Byte _pad[64 - 10]; // for structure size alignment + + void Init(unsigned numItems, unsigned startVal); unsigned Decode(CRangeDecoder *rc); }; -class CDecoder: - public IUnknown, - public CMyUnknownImp +class CDecoder { - CLzOutWindow _outWindow; + UInt32 _winSize; + UInt32 _winPos; + UInt32 _winSize_allocated; + bool _overWin; + Byte *_win; unsigned _numDictBits; CModelDecoder m_Selector; @@ -157,17 +48,11 @@ class CDecoder: void Init(); HRESULT CodeSpec(const Byte *inData, size_t inSize, UInt32 outSize); public: - - MY_UNKNOWN_IMP - - HRESULT Code(const Byte *inData, size_t inSize, - ISequentialOutStream *outStream, UInt32 outSize, - bool keepHistory); - + HRESULT Code(const Byte *inData, size_t inSize, UInt32 outSize, bool keepHistory); HRESULT SetParams(unsigned numDictBits); - - CDecoder(): _numDictBits(0) {} - virtual ~CDecoder() {} + + CDecoder(): _win(NULL), _numDictBits(0) {} + const Byte * GetDataPtr() const { return _win + _winPos; } }; }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/Rar1Decoder.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/Rar1Decoder.cpp index 1aaedcc1..050cde21 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/Rar1Decoder.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/Rar1Decoder.cpp @@ -9,77 +9,85 @@ namespace NCompress { namespace NRar1 { -static const UInt32 PosL1[] = {0,0,0,2,3,5,7,11,16,20,24,32,32, 256}; -static const UInt32 PosL2[] = {0,0,0,0,5,7,9,13,18,22,26,34,36, 256}; -static const UInt32 PosHf0[] = {0,0,0,0,0,8,16,24,33,33,33,33,33, 257}; -static const UInt32 PosHf1[] = {0,0,0,0,0,0,4,44,60,76,80,80,127, 257}; -static const UInt32 PosHf2[] = {0,0,0,0,0,0,2,7,53,117,233, 257,0}; -static const UInt32 PosHf3[] = {0,0,0,0,0,0,0,2,16,218,251, 257,0}; -static const UInt32 PosHf4[] = {0,0,0,0,0,0,0,0,0,255, 257,0,0}; +static const unsigned kNumBits = 12; -static const UInt32 kHistorySize = (1 << 16); +static const Byte kShortLen1[16 * 3] = +{ + 0,0xa0,0xd0,0xe0,0xf0,0xf8,0xfc,0xfe,0xff,0xc0,0x80,0x90,0x98,0x9c,0xb0,0, + 1,3,4,4,5,6,7,8,8,4,4,5,6,6,0,0, + 1,4,4,4,5,6,7,8,8,4,4,5,6,6,4,0 +}; -/* -class CCoderReleaser +static const Byte kShortLen2[16 * 3] = { - CDecoder *m_Coder; -public: - CCoderReleaser(CDecoder *coder): m_Coder(coder) {} - ~CCoderReleaser() { m_Coder->ReleaseStreams(); } + 0,0x40,0x60,0xa0,0xd0,0xe0,0xf0,0xf8,0xfc,0xc0,0x80,0x90,0x98,0x9c,0xb0,0, + 2,3,3,3,4,4,5,6,6,4,4,5,6,6,0,0, + 2,3,3,4,4,4,5,6,6,4,4,5,6,6,4,0 }; -*/ -CDecoder::CDecoder(): m_IsSolid(false) { } +static const Byte PosL1[kNumBits + 1] = { 0,0,2,1,2,2,4,5,4,4,8,0,224 }; +static const Byte PosL2[kNumBits + 1] = { 0,0,0,5,2,2,4,5,4,4,8,2,220 }; -void CDecoder::InitStructures() -{ - for (int i = 0; i < kNumRepDists; i++) - m_RepDists[i] = 0; - m_RepDistPtr = 0; - LastLength = 0; - LastDist = 0; -} +static const Byte PosHf0[kNumBits + 1] = { 0,0,0,0,8,8,8,9,0,0,0,0,224 }; +static const Byte PosHf1[kNumBits + 1] = { 0,0,0,0,0,4,40,16,16,4,0,47,130 }; +static const Byte PosHf2[kNumBits + 1] = { 0,0,0,0,0,2,5,46,64,116,24,0,0 }; +static const Byte PosHf3[kNumBits + 1] = { 0,0,0,0,0,0,2,14,202,33,6,0,0 }; +static const Byte PosHf4[kNumBits + 1] = { 0,0,0,0,0,0,0,0,255,2,0,0,0 }; + +static const UInt32 kHistorySize = (1 << 16); + +CDecoder::CDecoder(): + _isSolid(false), + _solidAllowed(false) + {} -UInt32 CDecoder::ReadBits(int numBits) { return m_InBitStream.ReadBits(numBits); } +UInt32 CDecoder::ReadBits(unsigned numBits) { return m_InBitStream.ReadBits(numBits); } HRESULT CDecoder::CopyBlock(UInt32 distance, UInt32 len) { if (len == 0) return S_FALSE; + if (m_UnpackSize < len) + return S_FALSE; m_UnpackSize -= len; return m_OutWindowStream.CopyBlock(distance, len) ? S_OK : S_FALSE; } -UInt32 CDecoder::DecodeNum(const UInt32 *posTab) +UInt32 CDecoder::DecodeNum(const Byte *numTab) { - UInt32 startPos = 2; - UInt32 num = m_InBitStream.GetValue(12); + /* + { + // we can check that tables are correct + UInt32 sum = 0; + for (unsigned i = 0; i <= kNumBits; i++) + sum += ((UInt32)numTab[i] << (kNumBits - i)); + if (sum != (1 << kNumBits)) + throw 111; + } + */ + + UInt32 val = m_InBitStream.GetValue(kNumBits); + UInt32 sum = 0; + unsigned i = 2; + for (;;) { - UInt32 cur = (posTab[startPos + 1] - posTab[startPos]) << (12 - startPos); - if (num < cur) + const UInt32 num = numTab[i]; + const UInt32 cur = num << (kNumBits - i); + if (val < cur) break; - startPos++; - num -= cur; + i++; + val -= cur; + sum += num; } - m_InBitStream.MovePos(startPos); - return((num >> (12 - startPos)) + posTab[startPos]); + m_InBitStream.MovePos(i); + return ((val >> (kNumBits - i)) + sum); } -static const Byte kShortLen1 [] = {1,3,4,4,5,6,7,8,8,4,4,5,6,6 }; -static const Byte kShortLen1a[] = {1,4,4,4,5,6,7,8,8,4,4,5,6,6,4 }; -static const Byte kShortLen2 [] = {2,3,3,3,4,4,5,6,6,4,4,5,6,6 }; -static const Byte kShortLen2a[] = {2,3,3,4,4,4,5,6,6,4,4,5,6,6,4 }; -static const UInt32 kShortXor1[] = {0,0xa0,0xd0,0xe0,0xf0,0xf8,0xfc,0xfe,0xff,0xc0,0x80,0x90,0x98,0x9c,0xb0}; -static const UInt32 kShortXor2[] = {0,0x40,0x60,0xa0,0xd0,0xe0,0xf0,0xf8,0xfc,0xc0,0x80,0x90,0x98,0x9c,0xb0}; HRESULT CDecoder::ShortLZ() { - UInt32 len, saveLen, dist; - int distancePlace; - const Byte *kShortLen; - const UInt32 *kShortXor; NumHuf = 0; if (LCount == 2) @@ -91,20 +99,14 @@ HRESULT CDecoder::ShortLZ() UInt32 bitField = m_InBitStream.GetValue(8); - if (AvrLn1 < 37) + UInt32 len, dist; { - kShortLen = Buf60 ? kShortLen1a : kShortLen1; - kShortXor = kShortXor1; + const Byte *xors = (AvrLn1 < 37) ? kShortLen1 : kShortLen2; + const Byte *lens = xors + 16 + Buf60; + for (len = 0; ((bitField ^ xors[len]) >> (8 - lens[len])) != 0; len++); + m_InBitStream.MovePos(lens[len]); } - else - { - kShortLen = Buf60 ? kShortLen2a : kShortLen2; - kShortXor = kShortXor2; - } - - for (len = 0; ((bitField ^ kShortXor[len]) & (~(0xff >> kShortLen[len]))) != 0; len++); - m_InBitStream.MovePos(kShortLen[len]); - + if (len >= 9) { if (len == 9) @@ -112,9 +114,11 @@ HRESULT CDecoder::ShortLZ() LCount++; return CopyBlock(LastDist, LastLength); } + + LCount = 0; + if (len == 14) { - LCount = 0; len = DecodeNum(PosL2) + 5; dist = 0x8000 + ReadBits(15) - 1; LastLength = len; @@ -122,19 +126,22 @@ HRESULT CDecoder::ShortLZ() return CopyBlock(dist, len); } - LCount = 0; - saveLen = len; + const UInt32 saveLen = len; dist = m_RepDists[(m_RepDistPtr - (len - 9)) & 3]; - len = DecodeNum(PosL1) + 2; - if (len == 0x101 && saveLen == 10) + + len = DecodeNum(PosL1); + + if (len == 0xff && saveLen == 10) { - Buf60 ^= 1; + Buf60 ^= 16; return S_OK; } if (dist >= 256) + { len++; - if (dist >= MaxDist3 - 1) - len++; + if (dist >= MaxDist3 - 1) + len++; + } } else { @@ -142,21 +149,23 @@ HRESULT CDecoder::ShortLZ() AvrLn1 += len; AvrLn1 -= AvrLn1 >> 4; - distancePlace = DecodeNum(PosHf2) & 0xff; - dist = ChSetA[(unsigned)distancePlace]; - if (--distancePlace != -1) + unsigned distancePlace = DecodeNum(PosHf2) & 0xff; + + dist = ChSetA[distancePlace]; + + if (distancePlace != 0) { PlaceA[dist]--; - UInt32 lastDistance = ChSetA[(unsigned)distancePlace]; + UInt32 lastDistance = ChSetA[(size_t)distancePlace - 1]; PlaceA[lastDistance]++; - ChSetA[(unsigned)distancePlace + 1] = lastDistance; - ChSetA[(unsigned)distancePlace] = dist; + ChSetA[distancePlace] = lastDistance; + ChSetA[(size_t)distancePlace - 1] = dist; } - len += 2; } m_RepDists[m_RepDistPtr++] = dist; m_RepDistPtr &= 3; + len += 2; LastLength = len; LastDist = dist; return CopyBlock(dist, len); @@ -177,12 +186,10 @@ HRESULT CDecoder::LongLZ() Nlzb = 0x90; Nhfb >>= 1; } - oldAvr2=AvrLn2; + oldAvr2 = AvrLn2; - if (AvrLn2 >= 122) - len = DecodeNum(PosL2); - else if (AvrLn2 >= 64) - len = DecodeNum(PosL1); + if (AvrLn2 >= 64) + len = DecodeNum(AvrLn2 < 122 ? PosL1 : PosL2); else { UInt32 bitField = m_InBitStream.GetValue(16); @@ -193,8 +200,8 @@ HRESULT CDecoder::LongLZ() } else { - for (len = 0; ((bitField << len) & 0x8000) == 0; len++) - ; + for (len = 0; ((bitField << len) & 0x8000) == 0; len++); + m_InBitStream.MovePos(len + 1); } } @@ -202,24 +209,26 @@ HRESULT CDecoder::LongLZ() AvrLn2 += len; AvrLn2 -= AvrLn2 >> 5; - if (AvrPlcB > 0x28ff) - distancePlace = DecodeNum(PosHf2); - else if (AvrPlcB > 0x6ff) - distancePlace = DecodeNum(PosHf1); - else - distancePlace = DecodeNum(PosHf0); + { + const Byte *tab; + if (AvrPlcB >= 0x2900) tab = PosHf2; + else if (AvrPlcB >= 0x0700) tab = PosHf1; + else tab = PosHf0; + distancePlace = DecodeNum(tab); // [0, 256] + } AvrPlcB += distancePlace; AvrPlcB -= AvrPlcB >> 8; + + distancePlace &= 0xff; for (;;) { - dist = ChSetB[distancePlace & 0xff]; + dist = ChSetB[distancePlace]; newDistancePlace = NToPlB[dist++ & 0xff]++; - if (!(dist & 0xff)) - CorrHuff(ChSetB,NToPlB); - else + if (dist & 0xff) break; + CorrHuff(ChSetB,NToPlB); } ChSetB[distancePlace] = ChSetB[newDistancePlace]; @@ -230,14 +239,15 @@ HRESULT CDecoder::LongLZ() oldAvr3 = AvrLn3; if (len != 1 && len != 4) + { if (len == 0 && dist <= MaxDist3) { AvrLn3++; AvrLn3 -= AvrLn3 >> 8; } - else - if (AvrLn3 > 0) - AvrLn3--; + else if (AvrLn3 > 0) + AvrLn3--; + } len += 3; @@ -246,7 +256,7 @@ HRESULT CDecoder::LongLZ() if (dist <= 256) len += 8; - if (oldAvr3 > 0xb0 || AvrPlc >= 0x2a00 && oldAvr2 < 0x40) + if (oldAvr3 > 0xb0 || (AvrPlc >= 0x2a00 && oldAvr2 < 0x40)) MaxDist3 = 0x7f00; else MaxDist3 = 0x2001; @@ -265,57 +275,62 @@ HRESULT CDecoder::HuffDecode() UInt32 curByte, newBytePlace; UInt32 len; UInt32 dist; - int bytePlace; - - if (AvrPlc > 0x75ff) bytePlace = DecodeNum(PosHf4); - else if (AvrPlc > 0x5dff) bytePlace = DecodeNum(PosHf3); - else if (AvrPlc > 0x35ff) bytePlace = DecodeNum(PosHf2); - else if (AvrPlc > 0x0dff) bytePlace = DecodeNum(PosHf1); - else bytePlace = DecodeNum(PosHf0); + unsigned bytePlace; + { + const Byte *tab; + + if (AvrPlc >= 0x7600) tab = PosHf4; + else if (AvrPlc >= 0x5e00) tab = PosHf3; + else if (AvrPlc >= 0x3600) tab = PosHf2; + else if (AvrPlc >= 0x0e00) tab = PosHf1; + else tab = PosHf0; + + bytePlace = DecodeNum(tab); // [0, 256] + } if (StMode) { - if (--bytePlace == -1) + if (bytePlace == 0) { if (ReadBits(1)) { - NumHuf = StMode = 0; + NumHuf = 0; + StMode = false; return S_OK; } - else - { - len = (ReadBits(1)) ? 4 : 3; - dist = DecodeNum(PosHf2); - dist = (dist << 5) | ReadBits(5); - return CopyBlock(dist - 1, len); - } + len = ReadBits(1) + 3; + dist = DecodeNum(PosHf2); + dist = (dist << 5) | ReadBits(5); + if (dist == 0) + return S_FALSE; + return CopyBlock(dist - 1, len); } + bytePlace--; // bytePlace is [0, 255] } else if (NumHuf++ >= 16 && FlagsCnt == 0) - StMode = 1; + StMode = true; bytePlace &= 0xff; AvrPlc += bytePlace; AvrPlc -= AvrPlc >> 8; - Nhfb+=16; + Nhfb += 16; if (Nhfb > 0xff) { - Nhfb=0x90; + Nhfb = 0x90; Nlzb >>= 1; } - m_UnpackSize --; + m_UnpackSize--; m_OutWindowStream.PutByte((Byte)(ChSet[bytePlace] >> 8)); for (;;) { curByte = ChSet[bytePlace]; newBytePlace = NToPl[curByte++ & 0xff]++; - if ((curByte & 0xff) > 0xa1) - CorrHuff(ChSet, NToPl); - else + if ((curByte & 0xff) <= 0xa1) break; + CorrHuff(ChSet, NToPl); } ChSet[bytePlace] = ChSet[newBytePlace]; @@ -327,7 +342,10 @@ HRESULT CDecoder::HuffDecode() void CDecoder::GetFlagsBuf() { UInt32 flags, newFlagsPlace; - UInt32 flagsPlace = DecodeNum(PosHf2); + const UInt32 flagsPlace = DecodeNum(PosHf2); // [0, 256] + + if (flagsPlace >= Z7_ARRAY_SIZE(ChSetC)) + return; for (;;) { @@ -343,138 +361,145 @@ void CDecoder::GetFlagsBuf() ChSetC[newFlagsPlace] = flags; } -void CDecoder::InitData() -{ - if (!m_IsSolid) - { - AvrPlcB = AvrLn1 = AvrLn2 = AvrLn3 = NumHuf = Buf60 = 0; - AvrPlc = 0x3500; - MaxDist3 = 0x2001; - Nhfb = Nlzb = 0x80; - } - FlagsCnt = 0; - FlagBuf = 0; - StMode = 0; - LCount = 0; -} -void CDecoder::CorrHuff(UInt32 *CharSet,UInt32 *NumToPlace) +void CDecoder::CorrHuff(UInt32 *CharSet, UInt32 *NumToPlace) { int i; for (i = 7; i >= 0; i--) - for (int j = 0; j < 32; j++, CharSet++) - *CharSet = (*CharSet & ~0xff) | i; + for (unsigned j = 0; j < 32; j++, CharSet++) + *CharSet = (*CharSet & ~(UInt32)0xff) | (unsigned)i; memset(NumToPlace, 0, sizeof(NToPl)); for (i = 6; i >= 0; i--) - NumToPlace[i] = (7 - i) * 32; + NumToPlace[i] = (7 - (unsigned)i) * 32; } -void CDecoder::InitHuff() -{ - for (UInt32 i = 0; i < 256; i++) - { - Place[i] = PlaceA[i] = PlaceB[i] = i; - PlaceC[i] = (~i + 1) & 0xff; - ChSet[i] = ChSetB[i] = i << 8; - ChSetA[i] = i; - ChSetC[i] = ((~i + 1) & 0xff) << 8; - } - memset(NToPl, 0, sizeof(NToPl)); - memset(NToPlB, 0, sizeof(NToPlB)); - memset(NToPlC, 0, sizeof(NToPlC)); - CorrHuff(ChSetB, NToPlB); -} + HRESULT CDecoder::CodeReal(ISequentialInStream *inStream, ISequentialOutStream *outStream, const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo * /* progress */) { - if (inSize == NULL || outSize == NULL) + if (!inSize || !outSize) return E_INVALIDARG; + if (_isSolid && !_solidAllowed) + return S_FALSE; + + _solidAllowed = false; + if (!m_OutWindowStream.Create(kHistorySize)) return E_OUTOFMEMORY; if (!m_InBitStream.Create(1 << 20)) return E_OUTOFMEMORY; - m_UnpackSize = (Int64)*outSize; + m_UnpackSize = *outSize; + m_OutWindowStream.SetStream(outStream); - m_OutWindowStream.Init(m_IsSolid); + m_OutWindowStream.Init(_isSolid); m_InBitStream.SetStream(inStream); m_InBitStream.Init(); - // CCoderReleaser coderReleaser(this); - InitData(); - if (!m_IsSolid) + // InitData + + FlagsCnt = 0; + FlagBuf = 0; + StMode = false; + LCount = 0; + + if (!_isSolid) { - InitStructures(); - InitHuff(); + AvrPlcB = AvrLn1 = AvrLn2 = AvrLn3 = NumHuf = Buf60 = 0; + AvrPlc = 0x3500; + MaxDist3 = 0x2001; + Nhfb = Nlzb = 0x80; + + { + // InitStructures + for (unsigned i = 0; i < kNumRepDists; i++) + m_RepDists[i] = 0; + m_RepDistPtr = 0; + LastLength = 0; + LastDist = 0; + } + + // InitHuff + + for (UInt32 i = 0; i < 256; i++) + { + Place[i] = PlaceA[i] = PlaceB[i] = i; + UInt32 c = (~i + 1) & 0xff; + PlaceC[i] = c; + ChSet[i] = ChSetB[i] = i << 8; + ChSetA[i] = i; + ChSetC[i] = c << 8; + } + memset(NToPl, 0, sizeof(NToPl)); + memset(NToPlB, 0, sizeof(NToPlB)); + memset(NToPlC, 0, sizeof(NToPlC)); + CorrHuff(ChSetB, NToPlB); } + if (m_UnpackSize > 0) { GetFlagsBuf(); FlagsCnt = 8; } - while (m_UnpackSize > 0) + while (m_UnpackSize != 0) { - if (StMode) + if (!StMode) { - RINOK(HuffDecode()); - continue; - } - - if (--FlagsCnt < 0) - { - GetFlagsBuf(); - FlagsCnt=7; - } - - if (FlagBuf & 0x80) - { - FlagBuf <<= 1; - if (Nlzb > Nhfb) - { - RINOK(LongLZ()); - } - else - { - RINOK(HuffDecode()); - } - } - else - { - FlagBuf <<= 1; if (--FlagsCnt < 0) { GetFlagsBuf(); FlagsCnt = 7; } + if (FlagBuf & 0x80) { FlagBuf <<= 1; if (Nlzb > Nhfb) { - RINOK(HuffDecode()); - } - else - { - RINOK(LongLZ()); + RINOK(LongLZ()) + continue; } } else { FlagBuf <<= 1; - RINOK(ShortLZ()); + + if (--FlagsCnt < 0) + { + GetFlagsBuf(); + FlagsCnt = 7; + } + + if ((FlagBuf & 0x80) == 0) + { + FlagBuf <<= 1; + RINOK(ShortLZ()) + continue; + } + + FlagBuf <<= 1; + + if (Nlzb <= Nhfb) + { + RINOK(LongLZ()) + continue; + } } } + + RINOK(HuffDecode()) } - if (m_UnpackSize < 0) - return S_FALSE; + + _solidAllowed = true; return m_OutWindowStream.Flush(); } -STDMETHODIMP CDecoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress) + +Z7_COM7F_IMF(CDecoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, + const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress)) { try { return CodeReal(inStream, outStream, inSize, outSize, progress); } catch(const CInBufferException &e) { return e.ErrorCode; } @@ -482,11 +507,11 @@ STDMETHODIMP CDecoder::Code(ISequentialInStream *inStream, ISequentialOutStream catch(...) { return S_FALSE; } } -STDMETHODIMP CDecoder::SetDecoderProperties2(const Byte *data, UInt32 size) +Z7_COM7F_IMF(CDecoder::SetDecoderProperties2(const Byte *data, UInt32 size)) { if (size < 1) return E_INVALIDARG; - m_IsSolid = ((data[0] & 1) != 0); + _isSolid = ((data[0] & 1) != 0); return S_OK; } diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/Rar1Decoder.h b/src/plugins/7zip/7za/cpp/7zip/Compress/Rar1Decoder.h index 630f0893..e1e22e2a 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/Rar1Decoder.h +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/Rar1Decoder.h @@ -2,8 +2,8 @@ // According to unRAR license, this code may not be used to develop // a program that creates RAR archives -#ifndef __COMPRESS_RAR1_DECODER_H -#define __COMPRESS_RAR1_DECODER_H +#ifndef ZIP7_INC_COMPRESS_RAR1_DECODER_H +#define ZIP7_INC_COMPRESS_RAR1_DECODER_H #include "../../Common/MyCom.h" @@ -12,77 +12,57 @@ #include "../Common/InBuffer.h" #include "BitmDecoder.h" -#include "HuffmanDecoder.h" #include "LzOutWindow.h" namespace NCompress { namespace NRar1 { -const UInt32 kNumRepDists = 4; +const unsigned kNumRepDists = 4; -typedef NBitm::CDecoder CBitDecoder; +Z7_CLASS_IMP_COM_2( + CDecoder + , ICompressCoder + , ICompressSetDecoderProperties2 +) + bool _isSolid; + bool _solidAllowed; + bool StMode; -class CDecoder : - public ICompressCoder, - public ICompressSetDecoderProperties2, - public CMyUnknownImp -{ -public: CLzOutWindow m_OutWindowStream; - CBitDecoder m_InBitStream; + NBitm::CDecoder m_InBitStream; - UInt32 m_RepDists[kNumRepDists]; - UInt32 m_RepDistPtr; + UInt64 m_UnpackSize; UInt32 LastDist; UInt32 LastLength; - Int64 m_UnpackSize; - bool m_IsSolid; + UInt32 m_RepDistPtr; + UInt32 m_RepDists[kNumRepDists]; + + int FlagsCnt; + UInt32 FlagBuf, AvrPlc, AvrPlcB, AvrLn1, AvrLn2, AvrLn3; + unsigned Buf60, NumHuf, LCount; + UInt32 Nhfb, Nlzb, MaxDist3; - UInt32 ReadBits(int numBits); - HRESULT CopyBlock(UInt32 distance, UInt32 len); + UInt32 ChSet[256], ChSetA[256], ChSetB[256], ChSetC[256]; + UInt32 Place[256], PlaceA[256], PlaceB[256], PlaceC[256]; + UInt32 NToPl[256], NToPlB[256], NToPlC[256]; - UInt32 DecodeNum(const UInt32 *posTab); + UInt32 ReadBits(unsigned numBits); + HRESULT CopyBlock(UInt32 distance, UInt32 len); + UInt32 DecodeNum(const Byte *numTab); HRESULT ShortLZ(); HRESULT LongLZ(); HRESULT HuffDecode(); void GetFlagsBuf(); - void InitData(); - void InitHuff(); void CorrHuff(UInt32 *CharSet, UInt32 *NumToPlace); void OldUnpWriteBuf(); - UInt32 ChSet[256],ChSetA[256],ChSetB[256],ChSetC[256]; - UInt32 Place[256],PlaceA[256],PlaceB[256],PlaceC[256]; - UInt32 NToPl[256],NToPlB[256],NToPlC[256]; - UInt32 FlagBuf,AvrPlc,AvrPlcB,AvrLn1,AvrLn2,AvrLn3; - int Buf60,NumHuf,StMode,LCount,FlagsCnt; - UInt32 Nhfb,Nlzb,MaxDist3; - - void InitStructures(); - HRESULT CodeReal(ISequentialInStream *inStream, ISequentialOutStream *outStream, const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress); public: CDecoder(); - - MY_UNKNOWN_IMP1(ICompressSetDecoderProperties2) - - /* - void ReleaseStreams() - { - m_OutWindowStream.ReleaseStream(); - m_InBitStream.ReleaseStream(); - } - */ - - STDMETHOD(Code)(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress); - - STDMETHOD(SetDecoderProperties2)(const Byte *data, UInt32 size); - }; }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/Rar2Decoder.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/Rar2Decoder.cpp index db67433a..63c04a16 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/Rar2Decoder.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/Rar2Decoder.cpp @@ -4,6 +4,8 @@ #include "StdAfx.h" +#include + #include "Rar2Decoder.h" namespace NCompress { @@ -11,30 +13,32 @@ namespace NRar2 { namespace NMultimedia { +#define my_abs(x) (unsigned)abs(x) + Byte CFilter::Decode(int &channelDelta, Byte deltaByte) { D4 = D3; D3 = D2; D2 = LastDelta - D1; D1 = LastDelta; - int predictedValue = ((8 * LastChar + K1 * D1 + K2 * D2 + K3 * D3 + K4 * D4 + K5 * channelDelta) >> 3); + const int predictedValue = ((8 * LastChar + K1 * D1 + K2 * D2 + K3 * D3 + K4 * D4 + K5 * channelDelta) >> 3); - Byte realValue = (Byte)(predictedValue - deltaByte); + const Byte realValue = (Byte)(predictedValue - deltaByte); { - int i = ((int)(signed char)deltaByte) << 3; - - Dif[0] += abs(i); - Dif[1] += abs(i - D1); - Dif[2] += abs(i + D1); - Dif[3] += abs(i - D2); - Dif[4] += abs(i + D2); - Dif[5] += abs(i - D3); - Dif[6] += abs(i + D3); - Dif[7] += abs(i - D4); - Dif[8] += abs(i + D4); - Dif[9] += abs(i - channelDelta); - Dif[10] += abs(i + channelDelta); + const int i = ((int)(signed char)deltaByte) << 3; + + Dif[0] += my_abs(i); + Dif[1] += my_abs(i - D1); + Dif[2] += my_abs(i + D1); + Dif[3] += my_abs(i - D2); + Dif[4] += my_abs(i + D2); + Dif[5] += my_abs(i - D3); + Dif[6] += my_abs(i + D3); + Dif[7] += my_abs(i - D4); + Dif[8] += my_abs(i + D4); + Dif[9] += my_abs(i - channelDelta); + Dif[10] += my_abs(i + channelDelta); } channelDelta = LastDelta = (signed char)(realValue - LastChar); @@ -46,7 +50,7 @@ Byte CFilter::Decode(int &channelDelta, Byte deltaByte) UInt32 numMinDif = 0; Dif[0] = 0; - for (unsigned i = 1; i < ARRAY_SIZE(Dif); i++) + for (unsigned i = 1; i < Z7_ARRAY_SIZE(Dif); i++) { if (Dif[i] < minDif) { @@ -77,10 +81,11 @@ Byte CFilter::Decode(int &channelDelta, Byte deltaByte) static const UInt32 kHistorySize = 1 << 20; -static const UInt32 kWindowReservSize = (1 << 22) + 256; +// static const UInt32 kWindowReservSize = (1 << 22) + 256; CDecoder::CDecoder(): - m_IsSolid(false), + _isSolid(false), + _solidAllowed(false), m_TablesOK(false) { } @@ -88,7 +93,7 @@ CDecoder::CDecoder(): void CDecoder::InitStructures() { m_MmFilter.Init(); - for (unsigned i = 0; i < kNumRepDists; i++) + for (unsigned i = 0; i < kNumReps; i++) m_RepDists[i] = 0; m_RepDistPtr = 0; m_LastLength = 0; @@ -99,12 +104,37 @@ UInt32 CDecoder::ReadBits(unsigned numBits) { return m_InBitStream.ReadBits(numB #define RIF(x) { if (!(x)) return false; } +static const unsigned kRepBothNumber = 256; +static const unsigned kRepNumber = kRepBothNumber + 1; +static const unsigned kLen2Number = kRepNumber + kNumReps; +static const unsigned kReadTableNumber = kLen2Number + kNumLen2Symbols; +static const unsigned kMatchNumber = kReadTableNumber + 1; + +// static const unsigned kDistTableStart = kMainTableSize; +// static const unsigned kLenTableStart = kDistTableStart + kDistTableSize; + +static const UInt32 kDistStart [kDistTableSize] = {0,1,2,3,4,6,8,12,16,24,32,48,64,96,128,192,256,384,512,768,1024,1536,2048,3072,4096,6144,8192,12288,16384,24576,32768U,49152U,65536,98304,131072,196608,262144,327680,393216,458752,524288,589824,655360,720896,786432,851968,917504,983040}; +static const Byte kDistDirectBits[kDistTableSize] = {0,0,0,0,1,1,2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16}; + +static const Byte kLen2DistStarts [kNumLen2Symbols]={0,4,8,16,32,64,128,192}; +static const Byte kLen2DistDirectBits[kNumLen2Symbols]={2,2,3, 4, 5, 6, 6, 6}; + +static const UInt32 kDistLimit2 = 0x101 - 1; +static const UInt32 kDistLimit3 = 0x2000 - 1; +static const UInt32 kDistLimit4 = 0x40000 - 1; + +// static const UInt32 kMatchMaxLen = 255 + 2; +// static const UInt32 kMatchMaxLenMax = 255 + 5; + + bool CDecoder::ReadTables(void) { m_TablesOK = false; + const unsigned kLevelTableSize = 19; Byte levelLevels[kLevelTableSize]; - Byte newLevels[kMaxTableSize]; + Byte lens[kMaxTableSize]; + m_AudioMode = (ReadBits(1) == 1); if (ReadBits(1) == 0) @@ -117,7 +147,7 @@ bool CDecoder::ReadTables(void) m_NumChannels = ReadBits(2) + 1; if (m_MmFilter.CurrentChannel >= m_NumChannels) m_MmFilter.CurrentChannel = 0; - numLevels = m_NumChannels * kMMTableSize; + numLevels = m_NumChannels * k_MM_TableSize; } else numLevels = kHeapTablesSizesSum; @@ -125,60 +155,74 @@ bool CDecoder::ReadTables(void) unsigned i; for (i = 0; i < kLevelTableSize; i++) levelLevels[i] = (Byte)ReadBits(4); - RIF(m_LevelDecoder.Build(levelLevels)); + NHuffman::CDecoder256 m_LevelDecoder; + RIF(m_LevelDecoder.Build(levelLevels, NHuffman::k_BuildMode_Full)) i = 0; - - while (i < numLevels) + do { - UInt32 sym = m_LevelDecoder.Decode(&m_InBitStream); - if (sym < kTableDirectLevels) + const unsigned sym = m_LevelDecoder.DecodeFull(&m_InBitStream); + if (sym < 16) { - newLevels[i] = (Byte)((sym + m_LastLevels[i]) & kLevelMask); + lens[i] = (Byte)((sym + m_LastLevels[i]) & 15); i++; } +#if 0 + else if (sym >= kLevelTableSize) + return false; +#endif else { - if (sym == kTableLevelRepNumber) + unsigned num; + Byte v; + if (sym == 16) { - unsigned t = ReadBits(2) + 3; - for (unsigned reps = t; reps > 0 && i < numLevels; reps--, i++) - newLevels[i] = newLevels[i - 1]; + if (i == 0) + return false; + num = ReadBits(2) + 3; + v = lens[(size_t)i - 1]; } else { - unsigned num; - if (sym == kTableLevel0Number) - num = ReadBits(3) + 3; - else if (sym == kTableLevel0Number2) - num = ReadBits(7) + 11; - else - return false; - for (; num > 0 && i < numLevels; num--) - newLevels[i++] = 0; + num = (sym - 17) * 4; + num += num + 3 + ReadBits(3 + num); + v = 0; + } + num += i; + if (num > numLevels) + { + // return false; + num = numLevels; // original unRAR } + do + lens[i++] = v; + while (i < num); } } + while (i < numLevels); + + if (m_InBitStream.ExtraBitsWereRead()) + return false; if (m_AudioMode) for (i = 0; i < m_NumChannels; i++) { - RIF(m_MMDecoders[i].Build(&newLevels[i * kMMTableSize])); + RIF(m_MMDecoders[i].Build(&lens[(size_t)i * k_MM_TableSize])) } else { - RIF(m_MainDecoder.Build(&newLevels[0])); - RIF(m_DistDecoder.Build(&newLevels[kMainTableSize])); - RIF(m_LenDecoder.Build(&newLevels[kMainTableSize + kDistTableSize])); + RIF(m_MainDecoder.Build(&lens[0])) + RIF(m_DistDecoder.Build(&lens[kMainTableSize])) + RIF(m_LenDecoder.Build(&lens[kMainTableSize + kDistTableSize])) } - memcpy(m_LastLevels, newLevels, kMaxTableSize); + memcpy(m_LastLevels, lens, kMaxTableSize); m_TablesOK = true; - return true; } + bool CDecoder::ReadLastTables() { // it differs a little from pure RAR sources; @@ -186,53 +230,43 @@ bool CDecoder::ReadLastTables() // + 2 works for: return 0xFF; in CInBuffer::ReadByte. if (m_InBitStream.GetProcessedSize() + 7 <= m_PackSize) // test it: probably incorrect; // if (m_InBitStream.GetProcessedSize() + 2 <= m_PackSize) // test it: probably incorrect; + { if (m_AudioMode) { - UInt32 symbol = m_MMDecoders[m_MmFilter.CurrentChannel].Decode(&m_InBitStream); + const unsigned symbol = m_MMDecoders[m_MmFilter.CurrentChannel].Decode(&m_InBitStream); if (symbol == 256) return ReadTables(); - if (symbol >= kMMTableSize) + if (symbol >= k_MM_TableSize) return false; } else { - UInt32 sym = m_MainDecoder.Decode(&m_InBitStream); + const unsigned sym = m_MainDecoder.Decode(&m_InBitStream); if (sym == kReadTableNumber) return ReadTables(); if (sym >= kMainTableSize) return false; } + } return true; } -/* -class CCoderReleaser -{ - CDecoder *m_Coder; -public: - CCoderReleaser(CDecoder *coder): m_Coder(coder) {} - ~CCoderReleaser() - { - m_Coder->ReleaseStreams(); - } -}; -*/ bool CDecoder::DecodeMm(UInt32 pos) { - while (pos-- > 0) + while (pos-- != 0) { - UInt32 symbol = m_MMDecoders[m_MmFilter.CurrentChannel].Decode(&m_InBitStream); - if (symbol == 256) - return true; - if (symbol >= kMMTableSize) + const unsigned symbol = m_MMDecoders[m_MmFilter.CurrentChannel].Decode(&m_InBitStream); + if (m_InBitStream.ExtraBitsWereRead()) return false; + if (symbol >= 256) + return symbol == 256; /* Byte byPredict = m_Predictor.Predict(); Byte byReal = (Byte)(byPredict - (Byte)symbol); m_Predictor.Update(byReal, byPredict); */ - Byte byReal = m_MmFilter.Decode((Byte)symbol); + const Byte byReal = m_MmFilter.Decode((Byte)symbol); m_OutWindowStream.PutByte(byReal); if (++m_MmFilter.CurrentChannel == m_NumChannels) m_MmFilter.CurrentChannel = 0; @@ -240,12 +274,23 @@ bool CDecoder::DecodeMm(UInt32 pos) return true; } + +typedef unsigned CLenType; + +static inline CLenType SlotToLen(CBitDecoder &_bitStream, CLenType slot) +{ + const unsigned numBits = ((unsigned)slot >> 2) - 1; + return ((4 | (slot & 3)) << numBits) + (CLenType)_bitStream.ReadBits(numBits); +} + bool CDecoder::DecodeLz(Int32 pos) { while (pos > 0) { - UInt32 sym = m_MainDecoder.Decode(&m_InBitStream); - UInt32 length, distance; + unsigned sym = m_MainDecoder.Decode(&m_InBitStream); + if (m_InBitStream.ExtraBitsWereRead()) + return false; + UInt32 len, distance; if (sym < 256) { m_OutWindowStream.PutByte(Byte(sym)); @@ -254,44 +299,51 @@ bool CDecoder::DecodeLz(Int32 pos) } else if (sym >= kMatchNumber) { - sym -= kMatchNumber; - length = kNormalMatchMinLen + UInt32(kLenStart[sym]) + - m_InBitStream.ReadBits(kLenDirectBits[sym]); + if (sym >= kMainTableSize) + return false; + len = sym - kMatchNumber; + if (len >= 8) + len = SlotToLen(m_InBitStream, len); + len += 3; + sym = m_DistDecoder.Decode(&m_InBitStream); if (sym >= kDistTableSize) return false; distance = kDistStart[sym] + m_InBitStream.ReadBits(kDistDirectBits[sym]); if (distance >= kDistLimit3) { - length += 2 - ((distance - kDistLimit4) >> 31); - // length++; + len += 2 - ((distance - kDistLimit4) >> 31); + // len++; // if (distance >= kDistLimit4) - // length++; + // len++; } } else if (sym == kRepBothNumber) { - length = m_LastLength; - if (length == 0) + len = m_LastLength; + if (len == 0) return false; distance = m_RepDists[(m_RepDistPtr + 4 - 1) & 3]; } else if (sym < kLen2Number) { distance = m_RepDists[(m_RepDistPtr - (sym - kRepNumber + 1)) & 3]; - sym = m_LenDecoder.Decode(&m_InBitStream); - if (sym >= kLenTableSize) + len = m_LenDecoder.Decode(&m_InBitStream); + if (len >= kLenTableSize) return false; - length = 2 + kLenStart[sym] + m_InBitStream.ReadBits(kLenDirectBits[sym]); + if (len >= 8) + len = SlotToLen(m_InBitStream, len); + len += 2; + if (distance >= kDistLimit2) { - length++; + len++; if (distance >= kDistLimit3) { - length += 2 - ((distance - kDistLimit4) >> 31); - // length++; + len += 2 - ((distance - kDistLimit4) >> 31); + // len++; // if (distance >= kDistLimit4) - // length++; + // len++; } } } @@ -300,17 +352,16 @@ bool CDecoder::DecodeLz(Int32 pos) sym -= kLen2Number; distance = kLen2DistStarts[sym] + m_InBitStream.ReadBits(kLen2DistDirectBits[sym]); - length = 2; + len = 2; } - else if (sym == kReadTableNumber) + else // (sym == kReadTableNumber) return true; - else - return false; + m_RepDists[m_RepDistPtr++ & 3] = distance; - m_LastLength = length; - if (!m_OutWindowStream.CopyBlock(distance, length)) + m_LastLength = len; + if (!m_OutWindowStream.CopyBlock(distance, len)) return false; - pos -= length; + pos -= len; } return true; } @@ -318,9 +369,13 @@ bool CDecoder::DecodeLz(Int32 pos) HRESULT CDecoder::CodeReal(ISequentialInStream *inStream, ISequentialOutStream *outStream, const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress) { - if (inSize == NULL || outSize == NULL) + if (!inSize || !outSize) return E_INVALIDARG; + if (_isSolid && !_solidAllowed) + return S_FALSE; + _solidAllowed = false; + if (!m_OutWindowStream.Create(kHistorySize)) return E_OUTOFMEMORY; if (!m_InBitStream.Create(1 << 20)) @@ -331,12 +386,12 @@ HRESULT CDecoder::CodeReal(ISequentialInStream *inStream, ISequentialOutStream * UInt64 pos = 0, unPackSize = *outSize; m_OutWindowStream.SetStream(outStream); - m_OutWindowStream.Init(m_IsSolid); + m_OutWindowStream.Init(_isSolid); m_InBitStream.SetStream(inStream); m_InBitStream.Init(); // CCoderReleaser coderReleaser(this); - if (!m_IsSolid) + if (!_isSolid) { InitStructures(); if (unPackSize == 0) @@ -344,6 +399,7 @@ HRESULT CDecoder::CodeReal(ISequentialInStream *inStream, ISequentialOutStream * if (m_InBitStream.GetProcessedSize() + 2 <= m_PackSize) // test it: probably incorrect; if (!ReadTables()) return S_FALSE; + _solidAllowed = true; return S_OK; } ReadTables(); @@ -352,7 +408,7 @@ HRESULT CDecoder::CodeReal(ISequentialInStream *inStream, ISequentialOutStream * if (!m_TablesOK) return S_FALSE; - UInt64 startPos = m_OutWindowStream.GetProcessedSize(); + const UInt64 startPos = m_OutWindowStream.GetProcessedSize(); while (pos < unPackSize) { UInt32 blockSize = 1 << 20; @@ -369,16 +425,20 @@ HRESULT CDecoder::CodeReal(ISequentialInStream *inStream, ISequentialOutStream * if (!DecodeLz((Int32)blockSize)) return S_FALSE; } - UInt64 globalPos = m_OutWindowStream.GetProcessedSize(); + + if (m_InBitStream.ExtraBitsWereRead()) + return S_FALSE; + + const UInt64 globalPos = m_OutWindowStream.GetProcessedSize(); pos = globalPos - blockStartPos; if (pos < blockSize) if (!ReadTables()) return S_FALSE; pos = globalPos - startPos; - if (progress != 0) + if (progress) { - UInt64 packSize = m_InBitStream.GetProcessedSize(); - RINOK(progress->SetRatioInfo(&packSize, &pos)); + const UInt64 packSize = m_InBitStream.GetProcessedSize(); + RINOK(progress->SetRatioInfo(&packSize, &pos)) } } if (pos > unPackSize) @@ -386,11 +446,14 @@ HRESULT CDecoder::CodeReal(ISequentialInStream *inStream, ISequentialOutStream * if (!ReadLastTables()) return S_FALSE; + + _solidAllowed = true; + return m_OutWindowStream.Flush(); } -STDMETHODIMP CDecoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress) +Z7_COM7F_IMF(CDecoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, + const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress)) { try { return CodeReal(inStream, outStream, inSize, outSize, progress); } catch(const CInBufferException &e) { return e.ErrorCode; } @@ -398,11 +461,11 @@ STDMETHODIMP CDecoder::Code(ISequentialInStream *inStream, ISequentialOutStream catch(...) { return S_FALSE; } } -STDMETHODIMP CDecoder::SetDecoderProperties2(const Byte *data, UInt32 size) +Z7_COM7F_IMF(CDecoder::SetDecoderProperties2(const Byte *data, UInt32 size)) { if (size < 1) return E_INVALIDARG; - m_IsSolid = ((data[0] & 1) != 0); + _isSolid = ((data[0] & 1) != 0); return S_OK; } diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/Rar2Decoder.h b/src/plugins/7zip/7za/cpp/7zip/Compress/Rar2Decoder.h index 0d8142b5..c9ddedbe 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/Rar2Decoder.h +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/Rar2Decoder.h @@ -2,8 +2,8 @@ // According to unRAR license, this code may not be used to develop // a program that creates RAR archives -#ifndef __COMPRESS_RAR2_DECODER_H -#define __COMPRESS_RAR2_DECODER_H +#ifndef ZIP7_INC_COMPRESS_RAR2_DECODER_H +#define ZIP7_INC_COMPRESS_RAR2_DECODER_H #include "../../Common/MyCom.h" @@ -18,59 +18,16 @@ namespace NCompress { namespace NRar2 { -const unsigned kNumRepDists = 4; +const unsigned kNumReps = 4; const unsigned kDistTableSize = 48; - -const unsigned kMMTableSize = 256 + 1; - -const UInt32 kMainTableSize = 298; -const UInt32 kLenTableSize = 28; - -const UInt32 kDistTableStart = kMainTableSize; -const UInt32 kLenTableStart = kDistTableStart + kDistTableSize; - -const UInt32 kHeapTablesSizesSum = kMainTableSize + kDistTableSize + kLenTableSize; - -const UInt32 kLevelTableSize = 19; - -const UInt32 kMMTablesSizesSum = kMMTableSize * 4; - -const UInt32 kMaxTableSize = kMMTablesSizesSum; - -const UInt32 kTableDirectLevels = 16; -const UInt32 kTableLevelRepNumber = kTableDirectLevels; -const UInt32 kTableLevel0Number = kTableLevelRepNumber + 1; -const UInt32 kTableLevel0Number2 = kTableLevel0Number + 1; - -const UInt32 kLevelMask = 0xF; - - -const UInt32 kRepBothNumber = 256; -const UInt32 kRepNumber = kRepBothNumber + 1; -const UInt32 kLen2Number = kRepNumber + 4; - -const UInt32 kLen2NumNumbers = 8; -const UInt32 kReadTableNumber = kLen2Number + kLen2NumNumbers; -const UInt32 kMatchNumber = kReadTableNumber + 1; - -const Byte kLenStart [kLenTableSize] = {0,1,2,3,4,5,6,7,8,10,12,14,16,20,24,28,32,40,48,56,64,80,96,112,128,160,192,224}; -const Byte kLenDirectBits[kLenTableSize] = {0,0,0,0,0,0,0,0,1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5}; - -const UInt32 kDistStart [kDistTableSize] = {0,1,2,3,4,6,8,12,16,24,32,48,64,96,128,192,256,384,512,768,1024,1536,2048,3072,4096,6144,8192,12288,16384,24576,32768U,49152U,65536,98304,131072,196608,262144,327680,393216,458752,524288,589824,655360,720896,786432,851968,917504,983040}; -const Byte kDistDirectBits[kDistTableSize] = {0,0,0,0,1,1,2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16}; - -const Byte kLevelDirectBits[kLevelTableSize] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 7}; - -const Byte kLen2DistStarts[kLen2NumNumbers]={0,4,8,16,32,64,128,192}; -const Byte kLen2DistDirectBits[kLen2NumNumbers]={2,2,3, 4, 5, 6, 6, 6}; - -const UInt32 kDistLimit2 = 0x101 - 1; -const UInt32 kDistLimit3 = 0x2000 - 1; -const UInt32 kDistLimit4 = 0x40000 - 1; - -const UInt32 kMatchMaxLen = 255 + 2; -const UInt32 kMatchMaxLenMax = 255 + 5; -const UInt32 kNormalMatchMinLen = 3; +const unsigned kNumLen2Symbols = 8; +const unsigned kLenTableSize = 28; +const unsigned kMainTableSize = 256 + 2 + kNumReps + kNumLen2Symbols + kLenTableSize; +const unsigned kHeapTablesSizesSum = kMainTableSize + kDistTableSize + kLenTableSize; +const unsigned k_MM_TableSize = 256 + 1; +const unsigned k_MM_NumChanelsMax = 4; +const unsigned k_MM_TablesSizesSum = k_MM_TableSize * k_MM_NumChanelsMax; +const unsigned kMaxTableSize = k_MM_TablesSizesSum; namespace NMultimedia { @@ -83,18 +40,13 @@ struct CFilter UInt32 ByteCount; int LastChar; - Byte Decode(int &channelDelta, Byte delta); - void Init() { memset(this, 0, sizeof(*this)); } - + Byte Decode(int &channelDelta, Byte delta); }; -const unsigned kNumChanelsMax = 4; - -class CFilter2 +struct CFilter2 { -public: - CFilter m_Filters[kNumChanelsMax]; + CFilter m_Filters[k_MM_NumChanelsMax]; int m_ChannelDelta; unsigned CurrentChannel; @@ -103,46 +55,42 @@ class CFilter2 { return m_Filters[CurrentChannel].Decode(m_ChannelDelta, delta); } - }; } typedef NBitm::CDecoder CBitDecoder; -const unsigned kNumHuffmanBits = 15; +const unsigned kNumHufBits = 15; + +Z7_CLASS_IMP_NOQIB_2( + CDecoder + , ICompressCoder + , ICompressSetDecoderProperties2 +) + bool _isSolid; + bool _solidAllowed; + bool m_TablesOK; + bool m_AudioMode; -class CDecoder : - public ICompressCoder, - public ICompressSetDecoderProperties2, - public CMyUnknownImp -{ CLzOutWindow m_OutWindowStream; CBitDecoder m_InBitStream; UInt32 m_RepDistPtr; - UInt32 m_RepDists[kNumRepDists]; - + UInt32 m_RepDists[kNumReps]; UInt32 m_LastLength; + unsigned m_NumChannels; - bool m_IsSolid; - bool m_TablesOK; - bool m_AudioMode; - - NHuffman::CDecoder m_MainDecoder; - NHuffman::CDecoder m_DistDecoder; - NHuffman::CDecoder m_LenDecoder; - NHuffman::CDecoder m_MMDecoders[NMultimedia::kNumChanelsMax]; - NHuffman::CDecoder m_LevelDecoder; + NHuffman::CDecoder m_MainDecoder; + NHuffman::CDecoder256 m_DistDecoder; + NHuffman::CDecoder256 m_LenDecoder; + NHuffman::CDecoder m_MMDecoders[k_MM_NumChanelsMax]; UInt64 m_PackSize; - - unsigned m_NumChannels; + NMultimedia::CFilter2 m_MmFilter; - Byte m_LastLevels[kMaxTableSize]; - void InitStructures(); UInt32 ReadBits(unsigned numBits); bool ReadTables(); @@ -156,22 +104,6 @@ class CDecoder : public: CDecoder(); - - MY_UNKNOWN_IMP1(ICompressSetDecoderProperties2) - - /* - void ReleaseStreams() - { - m_OutWindowStream.ReleaseStream(); - m_InBitStream.ReleaseStream(); - } - */ - - STDMETHOD(Code)(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress); - - STDMETHOD(SetDecoderProperties2)(const Byte *data, UInt32 size); - }; }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/Rar3Decoder.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/Rar3Decoder.cpp index 3bf25132..c17d46dc 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/Rar3Decoder.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/Rar3Decoder.cpp @@ -17,22 +17,16 @@ namespace NRar3 { static const UInt32 kNumAlignReps = 15; -static const UInt32 kSymbolReadTable = 256; -static const UInt32 kSymbolRep = 259; -static const UInt32 kSymbolLen2 = kSymbolRep + kNumReps; - -static const Byte kLenStart [kLenTableSize] = {0,1,2,3,4,5,6,7,8,10,12,14,16,20,24,28,32,40,48,56,64,80,96,112,128,160,192,224}; -static const Byte kLenDirectBits[kLenTableSize] = {0,0,0,0,0,0,0,0,1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5}; +static const unsigned kSymbolReadTable = 256; +static const unsigned kSymbolRep = 259; static const Byte kDistDirectBits[kDistTableSize] = {0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,14,14,15,15, 16,16,16,16,16,16,16,16,16,16,16,16,16,16, 18,18,18,18,18,18,18,18,18,18,18,18}; -static const Byte kLevelDirectBits[kLevelTableSize] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7}; - -static const Byte kLen2DistStarts[kNumLen2Symbols]={0,4,8,16,32,64,128,192}; -static const Byte kLen2DistDirectBits[kNumLen2Symbols]={2,2,3, 4, 5, 6, 6, 6}; +static const Byte kLen2DistStarts[kNumLen2Symbols] = {0,4,8,16,32,64,128,192}; +static const Byte kLen2DistDirectBits[kNumLen2Symbols] = {2,2,3, 4, 5, 6, 6, 6}; static const UInt32 kDistLimit3 = 0x2000 - 2; static const UInt32 kDistLimit4 = 0x40000 - 2; @@ -44,57 +38,40 @@ static const UInt32 kVmCodeSizeMax = 1 << 16; extern "C" { -static UInt32 Range_GetThreshold(void *pp, UInt32 total) -{ - CRangeDecoder *p = (CRangeDecoder *)pp; - return p->Code / (p->Range /= total); -} - -static void Range_Decode(void *pp, UInt32 start, UInt32 size) +static Byte Wrap_ReadByte(IByteInPtr pp) throw() { - CRangeDecoder *p = (CRangeDecoder *)pp; - start *= p->Range; - p->Low += start; - p->Code -= start; - p->Range *= size; - p->Normalize(); + CByteIn *p = Z7_CONTAINER_FROM_VTBL_CLS(pp, CByteIn, IByteIn_obj); + return p->BitDecoder.Stream.ReadByte(); } -static UInt32 Range_DecodeBit(void *pp, UInt32 size0) +static Byte Wrap_ReadBits8(IByteInPtr pp) throw() { - CRangeDecoder *p = (CRangeDecoder *)pp; - if (p->Code / (p->Range >>= 14) < size0) - { - Range_Decode(p, 0, size0); - return 0; - } - else - { - Range_Decode(p, size0, (1 << 14) - size0); - return 1; - } + CByteIn *p = Z7_CONTAINER_FROM_VTBL_CLS(pp, CByteIn, IByteIn_obj); + return (Byte)p->BitDecoder.ReadByteFromAligned(); } } -CRangeDecoder::CRangeDecoder() -{ - s.GetThreshold = Range_GetThreshold; - s.Decode = Range_Decode; - s.DecodeBit = Range_DecodeBit; -} CDecoder::CDecoder(): - _window(0), + _isSolid(false), + _solidAllowed(false), + _window(NULL), _winPos(0), _wrPtr(0), _lzSize(0), _writtenFileSize(0), - _vmData(0), - _vmCode(0), - m_IsSolid(false) + _vmData(NULL), + _vmCode(NULL) { Ppmd7_Construct(&_ppmd); + + UInt32 start = 0; + for (UInt32 i = 0; i < kDistTableSize; i++) + { + kDistStart[i] = start; + start += ((UInt32)1 << kDistDirectBits[i]); + } } CDecoder::~CDecoder() @@ -129,11 +106,11 @@ HRESULT CDecoder::WriteArea(UInt32 startPtr, UInt32 endPtr) { if (startPtr <= endPtr) return WriteData(_window + startPtr, endPtr - startPtr); - RINOK(WriteData(_window + startPtr, kWindowSize - startPtr)); + RINOK(WriteData(_window + startPtr, kWindowSize - startPtr)) return WriteData(_window, endPtr); } -void CDecoder::ExecuteFilter(int tempFilterIndex, NVm::CBlockRef &outBlockRef) +void CDecoder::ExecuteFilter(unsigned tempFilterIndex, NVm::CBlockRef &outBlockRef) { CTempFilter *tempFilter = _tempFilters[tempFilterIndex]; tempFilter->InitR[6] = (UInt32)_writtenFileSize; @@ -142,9 +119,11 @@ void CDecoder::ExecuteFilter(int tempFilterIndex, NVm::CBlockRef &outBlockRef) CFilter *filter = _filters[tempFilter->FilterIndex]; if (!filter->IsSupported) _unsupportedFilter = true; - _vm.Execute(filter, tempFilter, outBlockRef, filter->GlobalData); + if (!_vm.Execute(filter, tempFilter, outBlockRef, filter->GlobalData)) + _unsupportedFilter = true; delete tempFilter; - _tempFilters[tempFilterIndex] = 0; + _tempFilters[tempFilterIndex] = NULL; + _numEmptyTempFilters++; } HRESULT CDecoder::WriteBuf() @@ -167,7 +146,7 @@ HRESULT CDecoder::WriteBuf() { if (writtenBorder != blockStart) { - RINOK(WriteArea(writtenBorder, blockStart)); + RINOK(WriteArea(writtenBorder, blockStart)) writtenBorder = blockStart; writeSize = (_winPos - writtenBorder) & kWindowMask; } @@ -221,6 +200,7 @@ HRESULT CDecoder::WriteBuf() void CDecoder::InitFilters() { _lastFilter = 0; + _numEmptyTempFilters = 0; unsigned i; for (i = 0; i < _tempFilters.Size(); i++) delete _tempFilters[i]; @@ -270,24 +250,27 @@ bool CDecoder::AddVmCode(UInt32 firstByte, UInt32 codeSize) filter->ExecCount++; } - unsigned numEmptyItems = 0; + if (_numEmptyTempFilters != 0) { - FOR_VECTOR (i, _tempFilters) + const unsigned num = _tempFilters.Size(); + CTempFilter **tempFilters = _tempFilters.NonConstData(); + + unsigned w = 0; + for (unsigned i = 0; i < num; i++) { - _tempFilters[i - numEmptyItems] = _tempFilters[i]; - if (!_tempFilters[i]) - numEmptyItems++; - if (numEmptyItems != 0) - _tempFilters[i] = NULL; + CTempFilter *tf = tempFilters[i]; + if (tf) + tempFilters[w++] = tf; } + + _tempFilters.DeleteFrom(w); + _numEmptyTempFilters = 0; } - if (numEmptyItems == 0) - { - _tempFilters.Add(NULL); - numEmptyItems = 1; - } + + if (_tempFilters.Size() > MAX_UNPACK_FILTERS) + return false; CTempFilter *tempFilter = new CTempFilter; - _tempFilters[_tempFilters.Size() - numEmptyItems] = tempFilter; + _tempFilters.Add(tempFilter); tempFilter->FilterIndex = filterIndex; UInt32 blockStart = inp.ReadEncodedUInt32(); @@ -351,58 +334,63 @@ bool CDecoder::AddVmCode(UInt32 firstByte, UInt32 codeSize) bool CDecoder::ReadVmCodeLZ() { UInt32 firstByte = ReadBits(8); - UInt32 length = (firstByte & 7) + 1; - if (length == 7) - length = ReadBits(8) + 7; - else if (length == 8) - length = ReadBits(16); - if (length > kVmDataSizeMax) + UInt32 len = (firstByte & 7) + 1; + if (len == 7) + len = ReadBits(8) + 7; + else if (len == 8) + len = ReadBits(16); + if (len > kVmDataSizeMax) return false; - for (UInt32 i = 0; i < length; i++) + for (UInt32 i = 0; i < len; i++) _vmData[i] = (Byte)ReadBits(8); - return AddVmCode(firstByte, length); + return AddVmCode(firstByte, len); } + +// int CDecoder::DecodePpmSymbol() { return Ppmd7a_DecodeSymbol(&_ppmd); } +#define DecodePpmSymbol() Ppmd7a_DecodeSymbol(&_ppmd) + + bool CDecoder::ReadVmCodePPM() { - int firstByte = DecodePpmSymbol(); + const int firstByte = DecodePpmSymbol(); if (firstByte < 0) return false; - UInt32 length = (firstByte & 7) + 1; - if (length == 7) + UInt32 len = (firstByte & 7) + 1; + if (len == 7) { - int b1 = DecodePpmSymbol(); + const int b1 = DecodePpmSymbol(); if (b1 < 0) return false; - length = b1 + 7; + len = (unsigned)b1 + 7; } - else if (length == 8) + else if (len == 8) { - int b1 = DecodePpmSymbol(); + const int b1 = DecodePpmSymbol(); if (b1 < 0) return false; - int b2 = DecodePpmSymbol(); + const int b2 = DecodePpmSymbol(); if (b2 < 0) return false; - length = b1 * 256 + b2; + len = (unsigned)b1 * 256 + (unsigned)b2; } - if (length > kVmDataSizeMax) + if (len > kVmDataSizeMax) return false; if (InputEofError_Fast()) return false; - for (UInt32 i = 0; i < length; i++) + for (UInt32 i = 0; i < len; i++) { - int b = DecodePpmSymbol(); + const int b = DecodePpmSymbol(); if (b < 0) return false; _vmData[i] = (Byte)b; } - return AddVmCode(firstByte, length); + return AddVmCode((unsigned)firstByte, len); } #define RIF(x) { if (!(x)) return S_FALSE; } -UInt32 CDecoder::ReadBits(int numBits) { return m_InBitStream.BitDecoder.ReadBits(numBits); } +UInt32 CDecoder::ReadBits(unsigned numBits) { return m_InBitStream.BitDecoder.ReadBits(numBits); } // ---------- PPM ---------- @@ -410,22 +398,25 @@ HRESULT CDecoder::InitPPM() { unsigned maxOrder = (unsigned)ReadBits(7); - bool reset = ((maxOrder & 0x20) != 0); - int maxMB = 0; + const bool reset = ((maxOrder & 0x20) != 0); + UInt32 maxMB = 0; if (reset) - maxMB = (Byte)ReadBits(8); + maxMB = (Byte)Wrap_ReadBits8(&m_InBitStream.IByteIn_obj); else { if (PpmError || !Ppmd7_WasAllocated(&_ppmd)) return S_FALSE; } if (maxOrder & 0x40) - PpmEscChar = (Byte)ReadBits(8); - m_InBitStream.InitRangeCoder(); - /* - if (m_InBitStream.m_BitPos != 0) - return S_FALSE; - */ + PpmEscChar = (Byte)Wrap_ReadBits8(&m_InBitStream.IByteIn_obj); + + _ppmd.rc.dec.Stream = &m_InBitStream.IByteIn_obj; + m_InBitStream.IByteIn_obj.Read = Wrap_ReadBits8; + + Ppmd7a_RangeDec_Init(&_ppmd.rc.dec); + + m_InBitStream.IByteIn_obj.Read = Wrap_ReadByte; + if (reset) { PpmError = true; @@ -445,7 +436,6 @@ HRESULT CDecoder::InitPPM() return S_OK; } -int CDecoder::DecodePpmSymbol() { return Ppmd7_DecodeSymbol(&_ppmd, &m_InBitStream.s); } HRESULT CDecoder::DecodePPM(Int32 num, bool &keepDecompressing) { @@ -456,7 +446,7 @@ HRESULT CDecoder::DecodePPM(Int32 num, bool &keepDecompressing) { if (((_wrPtr - _winPos) & kWindowMask) < 260 && _wrPtr != _winPos) { - RINOK(WriteBuf()); + RINOK(WriteBuf()) if (_writtenFileSize > _unpackSize) { keepDecompressing = false; @@ -465,7 +455,7 @@ HRESULT CDecoder::DecodePPM(Int32 num, bool &keepDecompressing) } if (InputEofError_Fast()) return false; - int c = DecodePpmSymbol(); + const int c = DecodePpmSymbol(); if (c < 0) { PpmError = true; @@ -473,7 +463,7 @@ HRESULT CDecoder::DecodePPM(Int32 num, bool &keepDecompressing) } if (c == PpmEscChar) { - int nextCh = DecodePpmSymbol(); + const int nextCh = DecodePpmSymbol(); if (nextCh < 0) { PpmError = true; @@ -494,34 +484,34 @@ HRESULT CDecoder::DecodePPM(Int32 num, bool &keepDecompressing) } if (nextCh == 4 || nextCh == 5) { - UInt32 distance = 0; - UInt32 length = 4; + UInt32 dist = 0; + UInt32 len = 4; if (nextCh == 4) { for (int i = 0; i < 3; i++) { - int c2 = DecodePpmSymbol(); + const int c2 = DecodePpmSymbol(); if (c2 < 0) { PpmError = true; return S_FALSE; } - distance = (distance << 8) + (Byte)c2; + dist = (dist << 8) + (Byte)c2; } - distance++; - length += 28; + dist++; + len += 28; } - int c2 = DecodePpmSymbol(); + const int c2 = DecodePpmSymbol(); if (c2 < 0) { PpmError = true; return S_FALSE; } - length += c2; - if (distance >= _lzSize) + len += (unsigned)c2; + if (dist >= _lzSize) return S_FALSE; - CopyBlock(distance, length); - num -= (Int32)length; + CopyBlock(dist, len); + num -= (Int32)len; continue; } } @@ -545,21 +535,26 @@ HRESULT CDecoder::ReadTables(bool &keepDecompressing) return InitPPM(); } + TablesRead = false; + TablesOK = false; + _lzMode = true; PrevAlignBits = 0; PrevAlignCount = 0; + const unsigned kLevelTableSize = 20; Byte levelLevels[kLevelTableSize]; - Byte newLevels[kTablesSizesSum]; + Byte lens[kTablesSizesSum]; if (ReadBits(1) == 0) memset(m_LastLevels, 0, kTablesSizesSum); - int i; + unsigned i; + for (i = 0; i < kLevelTableSize; i++) { - UInt32 length = ReadBits(4); - if (length == 15) + const UInt32 len = ReadBits(4); + if (len == 15) { UInt32 zeroCount = ReadBits(4); if (zeroCount != 0) @@ -571,41 +566,50 @@ HRESULT CDecoder::ReadTables(bool &keepDecompressing) continue; } } - levelLevels[i] = (Byte)length; + levelLevels[i] = (Byte)len; } - RIF(m_LevelDecoder.Build(levelLevels)); + + NHuffman::CDecoder256 m_LevelDecoder; + RIF(m_LevelDecoder.Build(levelLevels, NHuffman::k_BuildMode_Full)) + i = 0; - while (i < kTablesSizesSum) + + do { - UInt32 sym = m_LevelDecoder.Decode(&m_InBitStream.BitDecoder); + const unsigned sym = m_LevelDecoder.DecodeFull(&m_InBitStream.BitDecoder); if (sym < 16) { - newLevels[i] = Byte((sym + m_LastLevels[i]) & 15); + lens[i] = Byte((sym + m_LastLevels[i]) & 15); i++; } +#if 0 else if (sym > kLevelTableSize) return S_FALSE; +#endif else { - int num; - if (((sym - 16) & 1) == 0) - num = ReadBits(3) + 3; - else - num = ReadBits(7) + 11; - if (sym < 18) + unsigned num = ((sym /* - 16 */) & 1) * 4; + num += num + 3 + (unsigned)ReadBits(num + 3); + num += i; + if (num > kTablesSizesSum) + num = kTablesSizesSum; + Byte v = 0; + if (sym < 16 + 2) { if (i == 0) return S_FALSE; - for (; num > 0 && i < kTablesSizesSum; num--, i++) - newLevels[i] = newLevels[i - 1]; - } - else - { - for (; num > 0 && i < kTablesSizesSum; num--) - newLevels[i++] = 0; + v = lens[(size_t)i - 1]; } + do + lens[i++] = v; + while (i < num); } } + while (i < kTablesSizesSum); + + if (InputEofError()) + return S_FALSE; + TablesRead = true; // original code has check here: @@ -617,12 +621,15 @@ HRESULT CDecoder::ReadTables(bool &keepDecompressing) } */ - RIF(m_MainDecoder.Build(&newLevels[0])); - RIF(m_DistDecoder.Build(&newLevels[kMainTableSize])); - RIF(m_AlignDecoder.Build(&newLevels[kMainTableSize + kDistTableSize])); - RIF(m_LenDecoder.Build(&newLevels[kMainTableSize + kDistTableSize + kAlignTableSize])); + RIF(m_MainDecoder.Build(&lens[0])) + RIF(m_DistDecoder.Build(&lens[kMainTableSize])) + RIF(m_AlignDecoder.Build(&lens[kMainTableSize + kDistTableSize])) + RIF(m_LenDecoder.Build(&lens[kMainTableSize + kDistTableSize + kAlignTableSize])) + + memcpy(m_LastLevels, lens, kTablesSizesSum); + + TablesOK = true; - memcpy(m_LastLevels, newLevels, kTablesSizesSum); return S_OK; } @@ -641,34 +648,17 @@ class CCoderReleaser HRESULT CDecoder::ReadEndOfBlock(bool &keepDecompressing) { - if (ReadBits(1) != 0) + if (ReadBits(1) == 0) { - // old file - TablesRead = false; - return ReadTables(keepDecompressing); + // new file + keepDecompressing = false; + TablesRead = (ReadBits(1) == 0); + return S_OK; } - // new file - keepDecompressing = false; - TablesRead = (ReadBits(1) == 0); - return S_OK; + TablesRead = false; + return ReadTables(keepDecompressing); } -UInt32 kDistStart[kDistTableSize]; - -class CDistInit -{ -public: - CDistInit() { Init(); } - void Init() - { - UInt32 start = 0; - for (UInt32 i = 0; i < kDistTableSize; i++) - { - kDistStart[i] = start; - start += (1 << kDistDirectBits[i]); - } - } -} g_DistInit; HRESULT CDecoder::DecodeLZ(bool &keepDecompressing) { @@ -676,12 +666,12 @@ HRESULT CDecoder::DecodeLZ(bool &keepDecompressing) UInt32 rep1 = _reps[1]; UInt32 rep2 = _reps[2]; UInt32 rep3 = _reps[3]; - UInt32 length = _lastLength; + UInt32 len = _lastLength; for (;;) { if (((_wrPtr - _winPos) & kWindowMask) < 260 && _wrPtr != _winPos) { - RINOK(WriteBuf()); + RINOK(WriteBuf()) if (_writtenFileSize > _unpackSize) { keepDecompressing = false; @@ -692,7 +682,7 @@ HRESULT CDecoder::DecodeLZ(bool &keepDecompressing) if (InputEofError_Fast()) return S_FALSE; - UInt32 sym = m_MainDecoder.Decode(&m_InBitStream.BitDecoder); + unsigned sym = m_MainDecoder.Decode(&m_InBitStream.BitDecoder); if (sym < 256) { PutByte((Byte)sym); @@ -700,7 +690,7 @@ HRESULT CDecoder::DecodeLZ(bool &keepDecompressing) } else if (sym == kSymbolReadTable) { - RINOK(ReadEndOfBlock(keepDecompressing)); + RINOK(ReadEndOfBlock(keepDecompressing)) break; } else if (sym == 257) @@ -711,35 +701,40 @@ HRESULT CDecoder::DecodeLZ(bool &keepDecompressing) } else if (sym == 258) { - if (length == 0) + if (len == 0) return S_FALSE; } else if (sym < kSymbolRep + 4) { if (sym != kSymbolRep) { - UInt32 distance; + UInt32 dist; if (sym == kSymbolRep + 1) - distance = rep1; + dist = rep1; else { if (sym == kSymbolRep + 2) - distance = rep2; + dist = rep2; else { - distance = rep3; + dist = rep3; rep3 = rep2; } rep2 = rep1; } rep1 = rep0; - rep0 = distance; + rep0 = dist; } - const UInt32 sym2 = m_LenDecoder.Decode(&m_InBitStream.BitDecoder); + const unsigned sym2 = m_LenDecoder.Decode(&m_InBitStream.BitDecoder); if (sym2 >= kLenTableSize) return S_FALSE; - length = 2 + kLenStart[sym2] + m_InBitStream.BitDecoder.ReadBits(kLenDirectBits[sym2]); + len = 2 + sym2; + if (sym2 >= 8) + { + const unsigned num = (sym2 >> 2) - 1; + len = 2 + (UInt32)((4 + (sym2 & 3)) << num) + m_InBitStream.BitDecoder.ReadBits_upto8(num); + } } else { @@ -749,18 +744,23 @@ HRESULT CDecoder::DecodeLZ(bool &keepDecompressing) if (sym < 271) { sym -= 263; - rep0 = kLen2DistStarts[sym] + m_InBitStream.BitDecoder.ReadBits(kLen2DistDirectBits[sym]); - length = 2; + rep0 = kLen2DistStarts[sym] + m_InBitStream.BitDecoder.ReadBits_upto8(kLen2DistDirectBits[sym]); + len = 2; } else if (sym < 299) { sym -= 271; - length = kNormalMatchMinLen + (UInt32)kLenStart[sym] + m_InBitStream.BitDecoder.ReadBits(kLenDirectBits[sym]); - const UInt32 sym2 = m_DistDecoder.Decode(&m_InBitStream.BitDecoder); + len = kNormalMatchMinLen + sym; + if (sym >= 8) + { + const unsigned num = (sym >> 2) - 1; + len = kNormalMatchMinLen + (UInt32)((4 + (sym & 3)) << num) + m_InBitStream.BitDecoder.ReadBits_upto8(num); + } + const unsigned sym2 = m_DistDecoder.Decode(&m_InBitStream.BitDecoder); if (sym2 >= kDistTableSize) return S_FALSE; rep0 = kDistStart[sym2]; - int numBits = kDistDirectBits[sym2]; + unsigned numBits = kDistDirectBits[sym2]; if (sym2 >= (kNumAlignBits * 2) + 2) { if (numBits > kNumAlignBits) @@ -772,7 +772,7 @@ HRESULT CDecoder::DecodeLZ(bool &keepDecompressing) } else { - const UInt32 sym3 = m_AlignDecoder.Decode(&m_InBitStream.BitDecoder); + const unsigned sym3 = m_AlignDecoder.Decode(&m_InBitStream.BitDecoder); if (sym3 < (1 << kNumAlignBits)) { rep0 += sym3; @@ -788,35 +788,37 @@ HRESULT CDecoder::DecodeLZ(bool &keepDecompressing) } } else - rep0 += m_InBitStream.BitDecoder.ReadBits(numBits); - length += ((kDistLimit4 - rep0) >> 31) + ((kDistLimit3 - rep0) >> 31); + rep0 += m_InBitStream.BitDecoder.ReadBits_upto8(numBits); + len += ((UInt32)(kDistLimit4 - rep0) >> 31) + ((UInt32)(kDistLimit3 - rep0) >> 31); } else return S_FALSE; } if (rep0 >= _lzSize) return S_FALSE; - CopyBlock(rep0, length); + CopyBlock(rep0, len); } _reps[0] = rep0; _reps[1] = rep1; _reps[2] = rep2; _reps[3] = rep3; - _lastLength = length; + _lastLength = len; return S_OK; } + HRESULT CDecoder::CodeReal(ICompressProgressInfo *progress) { _writtenFileSize = 0; _unsupportedFilter = false; - if (!m_IsSolid) + + if (!_isSolid) { _lzSize = 0; _winPos = 0; _wrPtr = 0; - for (int i = 0; i < kNumReps; i++) + for (unsigned i = 0; i < kNumReps; i++) _reps[i] = 0; _lastLength = 0; memset(m_LastLevels, 0, kTablesSizesSum); @@ -824,13 +826,23 @@ HRESULT CDecoder::CodeReal(ICompressProgressInfo *progress) PpmEscChar = 2; PpmError = true; InitFilters(); + // _errorMode = false; } - if (!m_IsSolid || !TablesRead) + + /* + if (_errorMode) + return S_FALSE; + */ + + if (!_isSolid || !TablesRead) { bool keepDecompressing; - RINOK(ReadTables(keepDecompressing)); + RINOK(ReadTables(keepDecompressing)) if (!keepDecompressing) + { + _solidAllowed = true; return S_OK; + } } for (;;) @@ -838,6 +850,8 @@ HRESULT CDecoder::CodeReal(ICompressProgressInfo *progress) bool keepDecompressing; if (_lzMode) { + if (!TablesOK) + return S_FALSE; RINOK(DecodeLZ(keepDecompressing)) } else @@ -848,14 +862,17 @@ HRESULT CDecoder::CodeReal(ICompressProgressInfo *progress) if (InputEofError()) return S_FALSE; - UInt64 packSize = m_InBitStream.BitDecoder.GetProcessedSize(); - RINOK(progress->SetRatioInfo(&packSize, &_writtenFileSize)); + const UInt64 packSize = m_InBitStream.BitDecoder.GetProcessedSize(); + RINOK(progress->SetRatioInfo(&packSize, &_writtenFileSize)) if (!keepDecompressing) break; } - RINOK(WriteBuf()); - UInt64 packSize = m_InBitStream.BitDecoder.GetProcessedSize(); - RINOK(progress->SetRatioInfo(&packSize, &_writtenFileSize)); + + _solidAllowed = true; + + RINOK(WriteBuf()) + const UInt64 packSize = m_InBitStream.BitDecoder.GetProcessedSize(); + RINOK(progress->SetRatioInfo(&packSize, &_writtenFileSize)) if (_writtenFileSize < _unpackSize) return S_FALSE; @@ -865,14 +882,18 @@ HRESULT CDecoder::CodeReal(ICompressProgressInfo *progress) return S_OK; } -STDMETHODIMP CDecoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress) +Z7_COM7F_IMF(CDecoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, + const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress)) { try { if (!inSize) return E_INVALIDARG; + if (_isSolid && !_solidAllowed) + return S_FALSE; + _solidAllowed = false; + if (!_vmData) { _vmData = (Byte *)::MidAlloc(kVmDataSizeMax + kVmCodeSizeMax); @@ -901,17 +922,17 @@ STDMETHODIMP CDecoder::Code(ISequentialInStream *inStream, ISequentialOutStream _unpackSize = outSize ? *outSize : (UInt64)(Int64)-1; return CodeReal(progress); } - catch(const CInBufferException &e) { return e.ErrorCode; } - catch(...) { return S_FALSE; } + catch(const CInBufferException &e) { /* _errorMode = true; */ return e.ErrorCode; } + catch(...) { /* _errorMode = true; */ return S_FALSE; } // CNewException is possible here. But probably CNewException is caused // by error in data stream. } -STDMETHODIMP CDecoder::SetDecoderProperties2(const Byte *data, UInt32 size) +Z7_COM7F_IMF(CDecoder::SetDecoderProperties2(const Byte *data, UInt32 size)) { if (size < 1) return E_INVALIDARG; - m_IsSolid = ((data[0] & 1) != 0); + _isSolid = ((data[0] & 1) != 0); return S_OK; } diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/Rar3Decoder.h b/src/plugins/7zip/7za/cpp/7zip/Compress/Rar3Decoder.h index c130cec6..48fc2124 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/Rar3Decoder.h +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/Rar3Decoder.h @@ -4,8 +4,8 @@ /* This code uses Carryless rangecoder (1999): Dmitry Subbotin : Public domain */ -#ifndef __COMPRESS_RAR3_DECODER_H -#define __COMPRESS_RAR3_DECODER_H +#ifndef ZIP7_INC_COMPRESS_RAR3_DECODER_H +#define ZIP7_INC_COMPRESS_RAR3_DECODER_H #include "../../../C/Ppmd7.h" @@ -22,21 +22,21 @@ namespace NCompress { namespace NRar3 { -const UInt32 kWindowSize = 1 << 22; -const UInt32 kWindowMask = (kWindowSize - 1); +const unsigned kNumHuffmanBits = 15; -const UInt32 kNumReps = 4; -const UInt32 kNumLen2Symbols = 8; -const UInt32 kLenTableSize = 28; -const UInt32 kMainTableSize = 256 + 1 + 1 + 1 + kNumReps + kNumLen2Symbols + kLenTableSize; -const UInt32 kDistTableSize = 60; +const UInt32 kWindowSize = 1 << 22; +const UInt32 kWindowMask = kWindowSize - 1; -const int kNumAlignBits = 4; -const UInt32 kAlignTableSize = (1 << kNumAlignBits) + 1; +const unsigned kNumReps = 4; +const unsigned kNumLen2Symbols = 8; +const unsigned kLenTableSize = 28; +const unsigned kMainTableSize = 256 + 3 + kNumReps + kNumLen2Symbols + kLenTableSize; +const unsigned kDistTableSize = 60; -const UInt32 kLevelTableSize = 20; +const unsigned kNumAlignBits = 4; +const unsigned kAlignTableSize = (1 << kNumAlignBits) + 1; -const UInt32 kTablesSizesSum = kMainTableSize + kDistTableSize + kAlignTableSize + kLenTableSize; +const unsigned kTablesSizesSum = kMainTableSize + kDistTableSize + kAlignTableSize + kLenTableSize; class CBitDecoder { @@ -68,6 +68,7 @@ class CBitDecoder _value = _value & ((1 << _bitPos) - 1); } + Z7_FORCE_INLINE UInt32 GetValue(unsigned numBits) { if (_bitPos < numBits) @@ -82,7 +83,15 @@ class CBitDecoder } return _value >> (_bitPos - numBits); } + + Z7_FORCE_INLINE + UInt32 GetValue_InHigh32bits() + { + return GetValue(kNumHuffmanBits) << (32 - kNumHuffmanBits); + } + + Z7_FORCE_INLINE void MovePos(unsigned numBits) { _bitPos -= numBits; @@ -91,48 +100,44 @@ class CBitDecoder UInt32 ReadBits(unsigned numBits) { - UInt32 res = GetValue(numBits); + const UInt32 res = GetValue(numBits); MovePos(numBits); return res; } -}; -const UInt32 kTopValue = (1 << 24); -const UInt32 kBot = (1 << 15); - -struct CRangeDecoder -{ - IPpmd7_RangeDec s; - UInt32 Range; - UInt32 Code; - UInt32 Low; - CBitDecoder BitDecoder; - SRes Res; - -public: - void InitRangeCoder() + UInt32 ReadBits_upto8(unsigned numBits) { - Code = 0; - Low = 0; - Range = 0xFFFFFFFF; - for (int i = 0; i < 4; i++) - Code = (Code << 8) | BitDecoder.ReadBits(8); + if (_bitPos < numBits) + { + _bitPos += 8; + _value = (_value << 8) | Stream.ReadByte(); + } + _bitPos -= numBits; + const UInt32 res = _value >> _bitPos; + _value = _value & ((1 << _bitPos) - 1); + return res; } - void Normalize() + Byte ReadByteFromAligned() { - while ((Low ^ (Low + Range)) < kTopValue || - Range < kBot && ((Range = (0 - Low) & (kBot - 1)), 1)) - { - Code = (Code << 8) | BitDecoder.Stream.ReadByte(); - Range <<= 8; - Low <<= 8; - } + if (_bitPos == 0) + return Stream.ReadByte(); + const unsigned bitsPos = _bitPos - 8; + const Byte b = (Byte)(_value >> bitsPos); + _value = _value & ((1 << bitsPos) - 1); + _bitPos = bitsPos; + return b; } +}; - CRangeDecoder(); + +struct CByteIn +{ + IByteIn IByteIn_obj; + CBitDecoder BitDecoder; }; + struct CFilter: public NVm::CProgram { CRecordVector GlobalData; @@ -158,14 +163,20 @@ struct CTempFilter: public NVm::CProgramInitState } }; -const int kNumHuffmanBits = 15; -class CDecoder: - public ICompressCoder, - public ICompressSetDecoderProperties2, - public CMyUnknownImp -{ - CRangeDecoder m_InBitStream; +Z7_CLASS_IMP_NOQIB_2( + CDecoder + , ICompressCoder + , ICompressSetDecoderProperties2 +) + bool _isSolid; + bool _solidAllowed; + // bool _errorMode; + + bool _lzMode; + bool _unsupportedFilter; + + CByteIn m_InBitStream; Byte *_window; UInt32 _winPos; UInt32 _wrPtr; @@ -173,11 +184,12 @@ class CDecoder: UInt64 _unpackSize; UInt64 _writtenFileSize; // if it's > _unpackSize, then _unpackSize only written ISequentialOutStream *_outStream; - NHuffman::CDecoder m_MainDecoder; - NHuffman::CDecoder m_DistDecoder; - NHuffman::CDecoder m_AlignDecoder; - NHuffman::CDecoder m_LenDecoder; - NHuffman::CDecoder m_LevelDecoder; + + NHuffman::CDecoder m_MainDecoder; + UInt32 kDistStart[kDistTableSize]; + NHuffman::CDecoder256 m_DistDecoder; + NHuffman::CDecoder256 m_AlignDecoder; + NHuffman::CDecoder256 m_LenDecoder; UInt32 _reps[kNumReps]; UInt32 _lastLength; @@ -189,26 +201,23 @@ class CDecoder: NVm::CVm _vm; CRecordVector _filters; CRecordVector _tempFilters; + unsigned _numEmptyTempFilters; UInt32 _lastFilter; - bool m_IsSolid; - - bool _lzMode; - bool _unsupportedFilter; - UInt32 PrevAlignBits; UInt32 PrevAlignCount; bool TablesRead; + bool TablesOK; + bool PpmError; - CPpmd7 _ppmd; int PpmEscChar; - bool PpmError; + CPpmd7 _ppmd; HRESULT WriteDataToStream(const Byte *data, UInt32 size); HRESULT WriteData(const Byte *data, UInt32 size); HRESULT WriteArea(UInt32 startPtr, UInt32 endPtr); - void ExecuteFilter(int tempFilterIndex, NVm::CBlockRef &outBlockRef); + void ExecuteFilter(unsigned tempFilterIndex, NVm::CBlockRef &outBlockRef); HRESULT WriteBuf(); void InitFilters(); @@ -216,10 +225,10 @@ class CDecoder: bool ReadVmCodeLZ(); bool ReadVmCodePPM(); - UInt32 ReadBits(int numBits); + UInt32 ReadBits(unsigned numBits); HRESULT InitPPM(); - int DecodePpmSymbol(); + // int DecodePpmSymbol(); HRESULT DecodePPM(Int32 num, bool &keepDecompressing); HRESULT ReadTables(bool &keepDecompressing); @@ -230,21 +239,10 @@ class CDecoder: bool InputEofError() const { return m_InBitStream.BitDecoder.ExtraBitsWereRead(); } bool InputEofError_Fast() const { return (m_InBitStream.BitDecoder.Stream.NumExtraBytes > 2); } -public: - CDecoder(); - ~CDecoder(); - - MY_UNKNOWN_IMP1(ICompressSetDecoderProperties2) - - STDMETHOD(Code)(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress); - - STDMETHOD(SetDecoderProperties2)(const Byte *data, UInt32 size); - - void CopyBlock(UInt32 distance, UInt32 len) + void CopyBlock(UInt32 dist, UInt32 len) { _lzSize += len; - UInt32 pos = (_winPos - distance - 1) & kWindowMask; + UInt32 pos = (_winPos - dist - 1) & kWindowMask; Byte *window = _window; UInt32 winPos = _winPos; if (kWindowSize - winPos > len && kWindowSize - pos > len) @@ -269,12 +267,15 @@ class CDecoder: void PutByte(Byte b) { - _window[_winPos] = b; - _winPos = (_winPos + 1) & kWindowMask; + const UInt32 wp = _winPos; + _window[wp] = b; + _winPos = (wp + 1) & kWindowMask; _lzSize++; } - +public: + CDecoder(); + ~CDecoder(); }; }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/Rar3Vm.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/Rar3Vm.cpp index 5dca9eab..f4ca5004 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/Rar3Vm.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/Rar3Vm.cpp @@ -29,12 +29,12 @@ UInt32 CMemBitDecoder::ReadBits(unsigned numBits) UInt32 res = 0; for (;;) { - unsigned b = _bitPos < _bitSize ? (unsigned)_data[_bitPos >> 3] : 0; - unsigned avail = (unsigned)(8 - (_bitPos & 7)); + const unsigned b = _bitPos < _bitSize ? (unsigned)_data[_bitPos >> 3] : 0; + const unsigned avail = (unsigned)(8 - (_bitPos & 7)); if (numBits <= avail) { _bitPos += numBits; - return res | (b >> (avail - numBits)) & ((1 << numBits) - 1); + return res | ((b >> (avail - numBits)) & ((1 << numBits) - 1)); } numBits -= avail; res |= (UInt32)(b & ((1 << avail) - 1)) << numBits; @@ -46,8 +46,8 @@ UInt32 CMemBitDecoder::ReadBit() { return ReadBits(1); } UInt32 CMemBitDecoder::ReadEncodedUInt32() { - unsigned v = (unsigned)ReadBits(2); - UInt32 res = ReadBits(4 << v); + const unsigned v = (unsigned)ReadBits(2); + UInt32 res = ReadBits(4u << v); if (v == 1 && res < 16) res = 0xFFFFFF00 | (res << 4) | ReadBits(4); return res; @@ -57,7 +57,13 @@ namespace NVm { static const UInt32 kStackRegIndex = kNumRegs - 1; -#ifdef RARVM_VM_ENABLE +#ifdef Z7_RARVM_VM_ENABLE + +#if defined(Z7_GCC_VERSION) && (Z7_GCC_VERSION >= 40400) \ + || defined(Z7_CLANG_VERSION) && (Z7_CLANG_VERSION >= 30000) +// enumeration values not explicitly handled in switch +#pragma GCC diagnostic ignored "-Wswitch-enum" +#endif static const UInt32 FLAG_C = 1; static const UInt32 FLAG_Z = 2; @@ -144,7 +150,7 @@ bool CVm::Execute(CProgram *prg, const CProgramInitState *initState, R[kNumRegs] = 0; Flags = 0; - UInt32 globalSize = MyMin((UInt32)initState->GlobalData.Size(), kGlobalSize); + const UInt32 globalSize = MyMin((UInt32)initState->GlobalData.Size(), kGlobalSize); if (globalSize != 0) memcpy(Mem + kGlobalOffset, &initState->GlobalData[0], globalSize); UInt32 staticSize = MyMin((UInt32)prg->StaticData.Size(), kGlobalSize - globalSize); @@ -153,13 +159,13 @@ bool CVm::Execute(CProgram *prg, const CProgramInitState *initState, bool res = true; - #ifdef RARVM_STANDARD_FILTERS + #ifdef Z7_RARVM_STANDARD_FILTERS if (prg->StandardFilterIndex >= 0) - ExecuteStandardFilter(prg->StandardFilterIndex); + res = ExecuteStandardFilter((unsigned)prg->StandardFilterIndex); else #endif { - #ifdef RARVM_VM_ENABLE + #ifdef Z7_RARVM_VM_ENABLE res = ExecuteCode(prg); if (!res) { @@ -192,7 +198,7 @@ bool CVm::Execute(CProgram *prg, const CProgramInitState *initState, return res; } -#ifdef RARVM_VM_ENABLE +#ifdef Z7_RARVM_VM_ENABLE #define SET_IP(IP) \ if ((IP) >= numCommands) return true; \ @@ -200,7 +206,7 @@ bool CVm::Execute(CProgram *prg, const CProgramInitState *initState, cmd = commands + (IP); #define GET_FLAG_S_B(res) (((res) & 0x80) ? FLAG_S : 0) -#define SET_IP_OP1 { UInt32 val = GetOperand32(&cmd->Op1); SET_IP(val); } +#define SET_IP_OP1 { const UInt32 val = GetOperand32(&cmd->Op1); SET_IP(val) } #define FLAGS_UPDATE_SZ Flags = res == 0 ? FLAG_Z : res & FLAG_S #define FLAGS_UPDATE_SZ_B Flags = (res & 0xFF) == 0 ? FLAG_Z : GET_FLAG_S_B(res) @@ -220,6 +226,7 @@ void CVm::SetOperand32(const COperand *op, UInt32 val) { case OP_TYPE_REG: R[op->Data] = val; return; case OP_TYPE_REGMEM: SetValue32(&Mem[(op->Base + R[op->Data]) & kSpaceMask], val); return; + default: break; } } @@ -228,7 +235,7 @@ Byte CVm::GetOperand8(const COperand *op) const switch (op->Type) { case OP_TYPE_REG: return (Byte)R[op->Data]; - case OP_TYPE_REGMEM: return Mem[(op->Base + R[op->Data]) & kSpaceMask];; + case OP_TYPE_REGMEM: return Mem[(op->Base + R[op->Data]) & kSpaceMask]; default: return (Byte)op->Data; } } @@ -239,6 +246,7 @@ void CVm::SetOperand8(const COperand *op, Byte val) { case OP_TYPE_REG: R[op->Data] = (R[op->Data] & 0xFFFFFF00) | val; return; case OP_TYPE_REGMEM: Mem[(op->Base + R[op->Data]) & kSpaceMask] = val; return; + default: break; } } @@ -262,7 +270,7 @@ bool CVm::ExecuteCode(const CProgram *prg) Int32 maxOpCount = 25000000; const CCommand *commands = &prg->Commands[0]; const CCommand *cmd = commands; - UInt32 numCommands = prg->Commands.Size(); + const UInt32 numCommands = prg->Commands.Size(); if (numCommands == 0) return false; @@ -278,152 +286,152 @@ bool CVm::ExecuteCode(const CProgram *prg) break; case CMD_CMP: { - UInt32 v1 = GetOperand32(&cmd->Op1); - UInt32 res = v1 - GetOperand32(&cmd->Op2); + const UInt32 v1 = GetOperand32(&cmd->Op1); + const UInt32 res = v1 - GetOperand32(&cmd->Op2); Flags = res == 0 ? FLAG_Z : (res > v1) | (res & FLAG_S); } break; case CMD_CMPB: { - Byte v1 = GetOperand8(&cmd->Op1); - Byte res = (Byte)((v1 - GetOperand8(&cmd->Op2)) & 0xFF); + const Byte v1 = GetOperand8(&cmd->Op1); + const Byte res = (Byte)((v1 - GetOperand8(&cmd->Op2)) & 0xFF); Flags = res == 0 ? FLAG_Z : (res > v1) | GET_FLAG_S_B(res); } break; case CMD_ADD: { - UInt32 v1 = GetOperand32(&cmd->Op1); - UInt32 res = v1 + GetOperand32(&cmd->Op2); + const UInt32 v1 = GetOperand32(&cmd->Op1); + const UInt32 res = v1 + GetOperand32(&cmd->Op2); SetOperand32(&cmd->Op1, res); Flags = (res < v1) | (res == 0 ? FLAG_Z : (res & FLAG_S)); } break; case CMD_ADDB: { - Byte v1 = GetOperand8(&cmd->Op1); - Byte res = (Byte)((v1 + GetOperand8(&cmd->Op2)) & 0xFF); + const Byte v1 = GetOperand8(&cmd->Op1); + const Byte res = (Byte)((v1 + GetOperand8(&cmd->Op2)) & 0xFF); SetOperand8(&cmd->Op1, (Byte)res); Flags = (res < v1) | (res == 0 ? FLAG_Z : GET_FLAG_S_B(res)); } break; case CMD_ADC: { - UInt32 v1 = GetOperand(cmd->ByteMode, &cmd->Op1); - UInt32 FC = (Flags & FLAG_C); + const UInt32 v1 = GetOperand(cmd->ByteMode, &cmd->Op1); + const UInt32 FC = (Flags & FLAG_C); UInt32 res = v1 + GetOperand(cmd->ByteMode, &cmd->Op2) + FC; if (cmd->ByteMode) res &= 0xFF; SetOperand(cmd->ByteMode, &cmd->Op1, res); - Flags = (res < v1 || res == v1 && FC) | (res == 0 ? FLAG_Z : (res & FLAG_S)); + Flags = (res < v1 || (res == v1 && FC)) | (res == 0 ? FLAG_Z : (res & FLAG_S)); } break; case CMD_SUB: { - UInt32 v1 = GetOperand32(&cmd->Op1); - UInt32 res = v1 - GetOperand32(&cmd->Op2); + const UInt32 v1 = GetOperand32(&cmd->Op1); + const UInt32 res = v1 - GetOperand32(&cmd->Op2); SetOperand32(&cmd->Op1, res); Flags = res == 0 ? FLAG_Z : (res > v1) | (res & FLAG_S); } break; case CMD_SUBB: { - UInt32 v1 = GetOperand8(&cmd->Op1); - UInt32 res = v1 - GetOperand8(&cmd->Op2); + const UInt32 v1 = GetOperand8(&cmd->Op1); + const UInt32 res = v1 - GetOperand8(&cmd->Op2); SetOperand8(&cmd->Op1, (Byte)res); Flags = res == 0 ? FLAG_Z : (res > v1) | (res & FLAG_S); } break; case CMD_SBB: { - UInt32 v1 = GetOperand(cmd->ByteMode, &cmd->Op1); - UInt32 FC = (Flags & FLAG_C); + const UInt32 v1 = GetOperand(cmd->ByteMode, &cmd->Op1); + const UInt32 FC = (Flags & FLAG_C); UInt32 res = v1 - GetOperand(cmd->ByteMode, &cmd->Op2) - FC; // Flags = res == 0 ? FLAG_Z : (res > v1 || res == v1 && FC) | (res & FLAG_S); if (cmd->ByteMode) res &= 0xFF; SetOperand(cmd->ByteMode, &cmd->Op1, res); - Flags = (res > v1 || res == v1 && FC) | (res == 0 ? FLAG_Z : (res & FLAG_S)); + Flags = (res > v1 || (res == v1 && FC)) | (res == 0 ? FLAG_Z : (res & FLAG_S)); } break; case CMD_INC: { - UInt32 res = GetOperand32(&cmd->Op1) + 1; + const UInt32 res = GetOperand32(&cmd->Op1) + 1; SetOperand32(&cmd->Op1, res); FLAGS_UPDATE_SZ; } break; case CMD_INCB: { - Byte res = (Byte)(GetOperand8(&cmd->Op1) + 1); - SetOperand8(&cmd->Op1, res);; + const Byte res = (Byte)(GetOperand8(&cmd->Op1) + 1); + SetOperand8(&cmd->Op1, res); FLAGS_UPDATE_SZ_B; } break; case CMD_DEC: { - UInt32 res = GetOperand32(&cmd->Op1) - 1; + const UInt32 res = GetOperand32(&cmd->Op1) - 1; SetOperand32(&cmd->Op1, res); FLAGS_UPDATE_SZ; } break; case CMD_DECB: { - Byte res = (Byte)(GetOperand8(&cmd->Op1) - 1); - SetOperand8(&cmd->Op1, res);; + const Byte res = (Byte)(GetOperand8(&cmd->Op1) - 1); + SetOperand8(&cmd->Op1, res); FLAGS_UPDATE_SZ_B; } break; case CMD_XOR: { - UInt32 res = GetOperand32(&cmd->Op1) ^ GetOperand32(&cmd->Op2); + const UInt32 res = GetOperand32(&cmd->Op1) ^ GetOperand32(&cmd->Op2); SetOperand32(&cmd->Op1, res); FLAGS_UPDATE_SZ; } break; case CMD_XORB: { - Byte res = (Byte)(GetOperand8(&cmd->Op1) ^ GetOperand8(&cmd->Op2)); + const Byte res = (Byte)(GetOperand8(&cmd->Op1) ^ GetOperand8(&cmd->Op2)); SetOperand8(&cmd->Op1, res); FLAGS_UPDATE_SZ_B; } break; case CMD_AND: { - UInt32 res = GetOperand32(&cmd->Op1) & GetOperand32(&cmd->Op2); + const UInt32 res = GetOperand32(&cmd->Op1) & GetOperand32(&cmd->Op2); SetOperand32(&cmd->Op1, res); FLAGS_UPDATE_SZ; } break; case CMD_ANDB: { - Byte res = (Byte)(GetOperand8(&cmd->Op1) & GetOperand8(&cmd->Op2)); + const Byte res = (Byte)(GetOperand8(&cmd->Op1) & GetOperand8(&cmd->Op2)); SetOperand8(&cmd->Op1, res); FLAGS_UPDATE_SZ_B; } break; case CMD_OR: { - UInt32 res = GetOperand32(&cmd->Op1) | GetOperand32(&cmd->Op2); + const UInt32 res = GetOperand32(&cmd->Op1) | GetOperand32(&cmd->Op2); SetOperand32(&cmd->Op1, res); FLAGS_UPDATE_SZ; } break; case CMD_ORB: { - Byte res = (Byte)(GetOperand8(&cmd->Op1) | GetOperand8(&cmd->Op2)); + const Byte res = (Byte)(GetOperand8(&cmd->Op1) | GetOperand8(&cmd->Op2)); SetOperand8(&cmd->Op1, res); FLAGS_UPDATE_SZ_B; } break; case CMD_TEST: { - UInt32 res = GetOperand32(&cmd->Op1) & GetOperand32(&cmd->Op2); + const UInt32 res = GetOperand32(&cmd->Op1) & GetOperand32(&cmd->Op2); FLAGS_UPDATE_SZ; } break; case CMD_TESTB: { - Byte res = (Byte)(GetOperand8(&cmd->Op1) & GetOperand8(&cmd->Op2)); + const Byte res = (Byte)(GetOperand8(&cmd->Op1) & GetOperand8(&cmd->Op2)); FLAGS_UPDATE_SZ_B; } break; @@ -432,14 +440,14 @@ bool CVm::ExecuteCode(const CProgram *prg) break; case CMD_NEG: { - UInt32 res = 0 - GetOperand32(&cmd->Op1); + const UInt32 res = 0 - GetOperand32(&cmd->Op1); SetOperand32(&cmd->Op1, res); Flags = res == 0 ? FLAG_Z : FLAG_C | (res & FLAG_S); } break; case CMD_NEGB: { - Byte res = (Byte)(0 - GetOperand8(&cmd->Op1)); + const Byte res = (Byte)(0 - GetOperand8(&cmd->Op1)); SetOperand8(&cmd->Op1, res); Flags = res == 0 ? FLAG_Z : FLAG_C | GET_FLAG_S_B(res); } @@ -447,115 +455,115 @@ bool CVm::ExecuteCode(const CProgram *prg) case CMD_SHL: { - UInt32 v1 = GetOperand32(&cmd->Op1); - int v2 = (int)GetOperand32(&cmd->Op2); - UInt32 res = v1 << v2; + const UInt32 v1 = GetOperand32(&cmd->Op1); + const int v2 = (int)GetOperand32(&cmd->Op2); + const UInt32 res = v1 << v2; SetOperand32(&cmd->Op1, res); Flags = (res == 0 ? FLAG_Z : (res & FLAG_S)) | ((v1 << (v2 - 1)) & 0x80000000 ? FLAG_C : 0); } break; case CMD_SHLB: { - Byte v1 = GetOperand8(&cmd->Op1); - int v2 = (int)GetOperand8(&cmd->Op2); - Byte res = (Byte)(v1 << v2); + const Byte v1 = GetOperand8(&cmd->Op1); + const int v2 = (int)GetOperand8(&cmd->Op2); + const Byte res = (Byte)(v1 << v2); SetOperand8(&cmd->Op1, res); Flags = (res == 0 ? FLAG_Z : GET_FLAG_S_B(res)) | ((v1 << (v2 - 1)) & 0x80 ? FLAG_C : 0); } break; case CMD_SHR: { - UInt32 v1 = GetOperand32(&cmd->Op1); - int v2 = (int)GetOperand32(&cmd->Op2); - UInt32 res = v1 >> v2; + const UInt32 v1 = GetOperand32(&cmd->Op1); + const int v2 = (int)GetOperand32(&cmd->Op2); + const UInt32 res = v1 >> v2; SetOperand32(&cmd->Op1, res); Flags = (res == 0 ? FLAG_Z : (res & FLAG_S)) | ((v1 >> (v2 - 1)) & FLAG_C); } break; case CMD_SHRB: { - Byte v1 = GetOperand8(&cmd->Op1); - int v2 = (int)GetOperand8(&cmd->Op2); - Byte res = (Byte)(v1 >> v2); + const Byte v1 = GetOperand8(&cmd->Op1); + const int v2 = (int)GetOperand8(&cmd->Op2); + const Byte res = (Byte)(v1 >> v2); SetOperand8(&cmd->Op1, res); Flags = (res == 0 ? FLAG_Z : GET_FLAG_S_B(res)) | ((v1 >> (v2 - 1)) & FLAG_C); } break; case CMD_SAR: { - UInt32 v1 = GetOperand32(&cmd->Op1); - int v2 = (int)GetOperand32(&cmd->Op2); - UInt32 res = UInt32(((Int32)v1) >> v2); + const UInt32 v1 = GetOperand32(&cmd->Op1); + const int v2 = (int)GetOperand32(&cmd->Op2); + const UInt32 res = UInt32(((Int32)v1) >> v2); SetOperand32(&cmd->Op1, res); Flags= (res == 0 ? FLAG_Z : (res & FLAG_S)) | ((v1 >> (v2 - 1)) & FLAG_C); } break; case CMD_SARB: { - Byte v1 = GetOperand8(&cmd->Op1); - int v2 = (int)GetOperand8(&cmd->Op2); - Byte res = (Byte)(((signed char)v1) >> v2); + const Byte v1 = GetOperand8(&cmd->Op1); + const int v2 = (int)GetOperand8(&cmd->Op2); + const Byte res = (Byte)(((signed char)v1) >> v2); SetOperand8(&cmd->Op1, res); Flags= (res == 0 ? FLAG_Z : GET_FLAG_S_B(res)) | ((v1 >> (v2 - 1)) & FLAG_C); } break; case CMD_JMP: - SET_IP_OP1; + SET_IP_OP1 continue; case CMD_JZ: if ((Flags & FLAG_Z) != 0) { - SET_IP_OP1; + SET_IP_OP1 continue; } break; case CMD_JNZ: if ((Flags & FLAG_Z) == 0) { - SET_IP_OP1; + SET_IP_OP1 continue; } break; case CMD_JS: if ((Flags & FLAG_S) != 0) { - SET_IP_OP1; + SET_IP_OP1 continue; } break; case CMD_JNS: if ((Flags & FLAG_S) == 0) { - SET_IP_OP1; + SET_IP_OP1 continue; } break; case CMD_JB: if ((Flags & FLAG_C) != 0) { - SET_IP_OP1; + SET_IP_OP1 continue; } break; case CMD_JBE: if ((Flags & (FLAG_C | FLAG_Z)) != 0) { - SET_IP_OP1; + SET_IP_OP1 continue; } break; case CMD_JA: if ((Flags & (FLAG_C | FLAG_Z)) == 0) { - SET_IP_OP1; + SET_IP_OP1 continue; } break; case CMD_JAE: if ((Flags & FLAG_C) == 0) { - SET_IP_OP1; + SET_IP_OP1 continue; } break; @@ -571,7 +579,7 @@ bool CVm::ExecuteCode(const CProgram *prg) case CMD_CALL: R[kStackRegIndex] -= 4; SetValue32(&Mem[R[kStackRegIndex] & kSpaceMask], (UInt32)(cmd - commands + 1)); - SET_IP_OP1; + SET_IP_OP1 continue; case CMD_PUSHA: @@ -604,29 +612,29 @@ bool CVm::ExecuteCode(const CProgram *prg) break; case CMD_XCHG: { - UInt32 v1 = GetOperand(cmd->ByteMode, &cmd->Op1); + const UInt32 v1 = GetOperand(cmd->ByteMode, &cmd->Op1); SetOperand(cmd->ByteMode, &cmd->Op1, GetOperand(cmd->ByteMode, &cmd->Op2)); SetOperand(cmd->ByteMode, &cmd->Op2, v1); } break; case CMD_MUL: { - UInt32 res = GetOperand32(&cmd->Op1) * GetOperand32(&cmd->Op2); + const UInt32 res = GetOperand32(&cmd->Op1) * GetOperand32(&cmd->Op2); SetOperand32(&cmd->Op1, res); } break; case CMD_MULB: { - Byte res = (Byte)(GetOperand8(&cmd->Op1) * GetOperand8(&cmd->Op2)); + const Byte res = (Byte)(GetOperand8(&cmd->Op1) * GetOperand8(&cmd->Op2)); SetOperand8(&cmd->Op1, res); } break; case CMD_DIV: { - UInt32 divider = GetOperand(cmd->ByteMode, &cmd->Op2); + const UInt32 divider = GetOperand(cmd->ByteMode, &cmd->Op2); if (divider != 0) { - UInt32 res = GetOperand(cmd->ByteMode, &cmd->Op1) / divider; + const UInt32 res = GetOperand(cmd->ByteMode, &cmd->Op1) / divider; SetOperand(cmd->ByteMode, &cmd->Op1, res); } } @@ -636,8 +644,8 @@ bool CVm::ExecuteCode(const CProgram *prg) { if (R[kStackRegIndex] >= kSpaceSize) return true; - UInt32 ip = GetValue32(&Mem[R[kStackRegIndex] & kSpaceMask]); - SET_IP(ip); + const UInt32 ip = GetValue32(&Mem[R[kStackRegIndex] & kSpaceMask]); + SET_IP(ip) R[kStackRegIndex] += 4; continue; } @@ -695,7 +703,7 @@ void CProgram::ReadProgram(const Byte *code, UInt32 codeSize) if (inp.ReadBit()) { - UInt32 dataSize = inp.ReadEncodedUInt32() + 1; + const UInt32 dataSize = inp.ReadEncodedUInt32() + 1; for (UInt32 i = 0; inp.Avail() && i < dataSize; i++) StaticData.Add((Byte)inp.ReadBits(8)); } @@ -705,28 +713,30 @@ void CProgram::ReadProgram(const Byte *code, UInt32 codeSize) Commands.Add(CCommand()); CCommand *cmd = &Commands.Back(); + unsigned opCode; if (inp.ReadBit() == 0) - cmd->OpCode = (ECommand)inp.ReadBits(3); + opCode = inp.ReadBits(3); else - cmd->OpCode = (ECommand)(8 + inp.ReadBits(5)); - - if (kCmdFlags[(unsigned)cmd->OpCode] & CF_BYTEMODE) + opCode = 8 + inp.ReadBits(5); + cmd->OpCode = (ECommand)opCode; + const unsigned cmdFlags = kCmdFlags[opCode]; + if (cmdFlags & CF_BYTEMODE) cmd->ByteMode = (inp.ReadBit()) ? true : false; else cmd->ByteMode = 0; - int opNum = (kCmdFlags[(unsigned)cmd->OpCode] & CF_OPMASK); + const unsigned opNum = (cmdFlags & CF_OPMASK); - if (opNum > 0) + if (opNum) { DecodeArg(inp, cmd->Op1, cmd->ByteMode); if (opNum == 2) DecodeArg(inp, cmd->Op2, cmd->ByteMode); else { - if (cmd->Op1.Type == OP_TYPE_INT && (kCmdFlags[(unsigned)cmd->OpCode] & (CF_JUMP | CF_PROC))) + if (cmd->Op1.Type == OP_TYPE_INT && (cmdFlags & (CF_JUMP | CF_PROC))) { - int dist = cmd->Op1.Data; + Int32 dist = (Int32)cmd->Op1.Data; if (dist >= 256) dist -= 256; else @@ -739,7 +749,7 @@ void CProgram::ReadProgram(const Byte *code, UInt32 codeSize) dist -= 16; dist += Commands.Size() - 1; } - cmd->Op1.Data = dist; + cmd->Op1.Data = (UInt32)dist; } } } @@ -763,6 +773,7 @@ void CProgram::ReadProgram(const Byte *code, UInt32 codeSize) case CMD_SHR: cmd->OpCode = CMD_SHRB; break; case CMD_SAR: cmd->OpCode = CMD_SARB; break; case CMD_MUL: cmd->OpCode = CMD_MULB; break; + default: break; } } } @@ -771,7 +782,7 @@ void CProgram::ReadProgram(const Byte *code, UInt32 codeSize) #endif -#ifdef RARVM_STANDARD_FILTERS +#ifdef Z7_RARVM_STANDARD_FILTERS enum EStandardFilter { @@ -803,12 +814,13 @@ kStdFilters[]= static int FindStandardFilter(const Byte *code, UInt32 codeSize) { - UInt32 crc = CrcCalc(code, codeSize); - for (unsigned i = 0; i < ARRAY_SIZE(kStdFilters); i++) + // return -1; // for debug VM execution + const UInt32 crc = CrcCalc(code, codeSize); + for (unsigned i = 0; i < Z7_ARRAY_SIZE(kStdFilters); i++) { const CStandardFilterSignature &sfs = kStdFilters[i]; if (sfs.CRC == crc && sfs.Length == codeSize) - return i; + return (int)i; } return -1; } @@ -820,11 +832,11 @@ bool CProgram::PrepareProgram(const Byte *code, UInt32 codeSize) { IsSupported = false; - #ifdef RARVM_VM_ENABLE + #ifdef Z7_RARVM_VM_ENABLE Commands.Clear(); #endif - #ifdef RARVM_STANDARD_FILTERS + #ifdef Z7_RARVM_STANDARD_FILTERS StandardFilterIndex = -1; #endif @@ -838,20 +850,20 @@ bool CProgram::PrepareProgram(const Byte *code, UInt32 codeSize) { IsSupported = true; isOK = true; - #ifdef RARVM_STANDARD_FILTERS + #ifdef Z7_RARVM_STANDARD_FILTERS StandardFilterIndex = FindStandardFilter(code, codeSize); if (StandardFilterIndex >= 0) return true; #endif - #ifdef RARVM_VM_ENABLE + #ifdef Z7_RARVM_VM_ENABLE ReadProgram(code + 1, codeSize - 1); #else IsSupported = false; #endif } - #ifdef RARVM_VM_ENABLE + #ifdef Z7_RARVM_VM_ENABLE Commands.Add(CCommand()); Commands.Back().OpCode = CMD_RET; #endif @@ -865,7 +877,7 @@ void CVm::SetMemory(UInt32 pos, const Byte *data, UInt32 dataSize) memmove(Mem + pos, data, MyMin(dataSize, kSpaceSize - pos)); } -#ifdef RARVM_STANDARD_FILTERS +#ifdef Z7_RARVM_STANDARD_FILTERS static void E8E9Decode(Byte *data, UInt32 dataSize, UInt32 fileOffset, bool e9) { @@ -873,17 +885,17 @@ static void E8E9Decode(Byte *data, UInt32 dataSize, UInt32 fileOffset, bool e9) return; dataSize -= 4; const UInt32 kFileSize = 0x1000000; - Byte cmpMask = (Byte)(e9 ? 0xFE : 0xFF); + const Byte cmpMask = (Byte)(e9 ? 0xFE : 0xFF); for (UInt32 curPos = 0; curPos < dataSize;) { curPos++; if (((*data++) & cmpMask) == 0xE8) { UInt32 offset = curPos + fileOffset; - UInt32 addr = (Int32)GetValue32(data); + UInt32 addr = GetValue32(data); if (addr < kFileSize) SetValue32(data, addr - offset); - else if ((Int32)addr < 0 && (Int32)(addr + offset) >= 0) + else if ((addr & 0x80000000) != 0 && ((addr + offset) & 0x80000000) == 0) SetValue32(data, addr + kFileSize); data += 4; curPos += 4; @@ -891,57 +903,51 @@ static void E8E9Decode(Byte *data, UInt32 dataSize, UInt32 fileOffset, bool e9) } } -static inline UInt32 ItaniumGetOpType(const Byte *data, unsigned bitPos) -{ - return (data[bitPos >> 3] >> (bitPos & 7)) & 0xF; -} - -static const Byte kCmdMasks[16] = {4,4,6,6,0,0,7,7,4,4,0,0,4,4,0,0}; static void ItaniumDecode(Byte *data, UInt32 dataSize, UInt32 fileOffset) { - UInt32 curPos = 0; + if (dataSize <= 21) + return; fileOffset >>= 4; - while (curPos < dataSize - 21) + dataSize -= 21; + dataSize += 15; + dataSize >>= 4; + dataSize += fileOffset; + do { - int b = (data[0] & 0x1F) - 0x10; - if (b >= 0) + unsigned m = ((UInt32)0x334B0000 >> (data[0] & 0x1E)) & 3; + if (m) { - Byte cmdMask = kCmdMasks[b]; - if (cmdMask != 0) - for (unsigned i = 0; i < 3; i++) - if (cmdMask & (1 << i)) - { - unsigned startPos = i * 41 + 18; - if (ItaniumGetOpType(data, startPos + 24) == 5) - { - const UInt32 kMask = 0xFFFFF; - Byte *p = data + (startPos >> 3); - UInt32 bitField = ((UInt32)p[0]) | ((UInt32)p[1] << 8) | ((UInt32)p[2] << 16); - unsigned inBit = (startPos & 7); - UInt32 offset = (bitField >> inBit) & kMask; - UInt32 andMask = ~(kMask << inBit); - bitField = ((offset - fileOffset) & kMask) << inBit; - for (unsigned j = 0; j < 3; j++) - { - p[j] &= andMask; - p[j] |= bitField; - andMask >>= 8; - bitField >>= 8; - } - } - } + m++; + do + { + Byte *p = data + ((size_t)m * 5 - 8); + if (((p[3] >> m) & 15) == 5) + { + const UInt32 kMask = 0xFFFFF; + // UInt32 raw = ((UInt32)p[0]) | ((UInt32)p[1] << 8) | ((UInt32)p[2] << 16); + UInt32 raw = GetUi32(p); + UInt32 v = raw >> m; + v -= fileOffset; + v &= kMask; + raw &= ~(kMask << m); + raw |= (v << m); + // p[0] = (Byte)raw; p[1] = (Byte)(raw >> 8); p[2] = (Byte)(raw >> 16); + SetUi32(p, raw) + } + } + while (++m <= 4); } data += 16; - curPos += 16; - fileOffset++; } + while (++fileOffset != dataSize); } + static void DeltaDecode(Byte *data, UInt32 dataSize, UInt32 numChannels) { UInt32 srcPos = 0; - UInt32 border = dataSize * 2; + const UInt32 border = dataSize * 2; for (UInt32 curChannel = 0; curChannel < numChannels; curChannel++) { Byte prevByte = 0; @@ -953,24 +959,25 @@ static void DeltaDecode(Byte *data, UInt32 dataSize, UInt32 numChannels) static void RgbDecode(Byte *srcData, UInt32 dataSize, UInt32 width, UInt32 posR) { Byte *destData = srcData + dataSize; - const UInt32 numChannels = 3; - for (UInt32 curChannel = 0; curChannel < numChannels; curChannel++) + const UInt32 kNumChannels = 3; + + for (UInt32 curChannel = 0; curChannel < kNumChannels; curChannel++) { Byte prevByte = 0; - for (UInt32 i = curChannel; i < dataSize; i+= numChannels) + for (UInt32 i = curChannel; i < dataSize; i += kNumChannels) { - unsigned int predicted; + unsigned predicted; if (i < width) predicted = prevByte; else { - unsigned int upperLeftByte = destData[i - width]; - unsigned int upperByte = destData[i - width + 3]; + const unsigned upperLeftByte = destData[i - width]; + const unsigned upperByte = destData[i - width + 3]; predicted = prevByte + upperByte - upperLeftByte; - int pa = abs((int)(predicted - prevByte)); - int pb = abs((int)(predicted - upperByte)); - int pc = abs((int)(predicted - upperLeftByte)); + const int pa = abs((int)(predicted - prevByte)); + const int pb = abs((int)(predicted - upperByte)); + const int pc = abs((int)(predicted - upperLeftByte)); if (pa <= pb && pa <= pc) predicted = prevByte; else @@ -984,14 +991,17 @@ static void RgbDecode(Byte *srcData, UInt32 dataSize, UInt32 width, UInt32 posR) } if (dataSize < 3) return; - for (UInt32 i = posR, border = dataSize - 2; i < border; i += 3) + const UInt32 border = dataSize - 2; + for (UInt32 i = posR; i < border; i += 3) { - Byte g = destData[i + 1]; + const Byte g = destData[i + 1]; destData[i ] = (Byte)(destData[i ] + g); destData[i + 2] = (Byte)(destData[i + 2] + g); } } +#define my_abs(x) (unsigned)abs(x) + static void AudioDecode(Byte *srcData, UInt32 dataSize, UInt32 numChannels) { Byte *destData = srcData + dataSize; @@ -1005,34 +1015,34 @@ static void AudioDecode(Byte *srcData, UInt32 dataSize, UInt32 numChannels) for (UInt32 i = curChannel, byteCount = 0; i < dataSize; i += numChannels, byteCount++) { D3 = D2; - D2 = prevDelta - D1; - D1 = prevDelta; + D2 = (Int32)prevDelta - D1; + D1 = (Int32)prevDelta; - UInt32 predicted = 8 * prevByte + K1 * D1 + K2 * D2 + K3 * D3; + UInt32 predicted = (UInt32)((Int32)(8 * prevByte) + K1 * D1 + K2 * D2 + K3 * D3); predicted = (predicted >> 3) & 0xFF; - UInt32 curByte = *(srcData++); + const UInt32 curByte = *(srcData++); predicted -= curByte; destData[i] = (Byte)predicted; prevDelta = (UInt32)(Int32)(signed char)(predicted - prevByte); prevByte = predicted; - Int32 D = ((Int32)(signed char)curByte) << 3; + const Int32 D = ((Int32)(signed char)curByte) << 3; - dif[0] += abs(D); - dif[1] += abs(D - D1); - dif[2] += abs(D + D1); - dif[3] += abs(D - D2); - dif[4] += abs(D + D2); - dif[5] += abs(D - D3); - dif[6] += abs(D + D3); + dif[0] += my_abs(D); + dif[1] += my_abs(D - D1); + dif[2] += my_abs(D + D1); + dif[3] += my_abs(D - D2); + dif[4] += my_abs(D + D2); + dif[5] += my_abs(D - D3); + dif[6] += my_abs(D + D3); if ((byteCount & 0x1F) == 0) { UInt32 minDif = dif[0], numMinDif = 0; dif[0] = 0; - for (unsigned j = 1; j < ARRAY_SIZE(dif); j++) + for (unsigned j = 1; j < Z7_ARRAY_SIZE(dif); j++) { if (dif[j] < minDif) { @@ -1070,11 +1080,11 @@ static UInt32 UpCaseDecode(Byte *data, UInt32 dataSize) } */ -void CVm::ExecuteStandardFilter(unsigned filterIndex) +bool CVm::ExecuteStandardFilter(unsigned filterIndex) { - UInt32 dataSize = R[4]; + const UInt32 dataSize = R[4]; if (dataSize >= kGlobalOffset) - return; + return false; EStandardFilter filterType = kStdFilters[filterIndex].Type; switch (filterType) @@ -1083,42 +1093,59 @@ void CVm::ExecuteStandardFilter(unsigned filterIndex) case SF_E8E9: E8E9Decode(Mem, dataSize, R[6], (filterType == SF_E8E9)); break; + case SF_ITANIUM: ItaniumDecode(Mem, dataSize, R[6]); break; + case SF_DELTA: + { if (dataSize >= kGlobalOffset / 2) - break; + return false; + const UInt32 numChannels = R[0]; + if (numChannels == 0 || numChannels > 1024) // unrar 5.5.5 + return false; SetBlockPos(dataSize); - DeltaDecode(Mem, dataSize, R[0]); + DeltaDecode(Mem, dataSize, numChannels); break; + } + case SF_RGB: - if (dataSize >= kGlobalOffset / 2) - break; - { - UInt32 width = R[0]; - if (width <= 3) - break; - SetBlockPos(dataSize); - RgbDecode(Mem, dataSize, width, R[1]); - } + { + if (dataSize >= kGlobalOffset / 2 || dataSize < 3) // unrar 5.5.5 + return false; + const UInt32 width = R[0]; + const UInt32 posR = R[1]; + if (width < 3 || width - 3 > dataSize || posR > 2) // unrar 5.5.5 + return false; + SetBlockPos(dataSize); + RgbDecode(Mem, dataSize, width, posR); break; + } + case SF_AUDIO: + { if (dataSize >= kGlobalOffset / 2) - break; + return false; + const UInt32 numChannels = R[0]; + if (numChannels == 0 || numChannels > 128) // unrar 5.5.5 + return false; SetBlockPos(dataSize); - AudioDecode(Mem, dataSize, R[0]); + AudioDecode(Mem, dataSize, numChannels); break; + } + /* case SF_UPCASE: if (dataSize >= kGlobalOffset / 2) - break; + return false; UInt32 destSize = UpCaseDecode(Mem, dataSize); SetBlockSize(destSize); SetBlockPos(dataSize); break; */ } + return true; } #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/Rar3Vm.h b/src/plugins/7zip/7za/cpp/7zip/Compress/Rar3Vm.h index 4272c93e..fde7e95e 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/Rar3Vm.h +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/Rar3Vm.h @@ -2,15 +2,15 @@ // According to unRAR license, this code may not be used to develop // a program that creates RAR archives -#ifndef __COMPRESS_RAR3_VM_H -#define __COMPRESS_RAR3_VM_H +#ifndef ZIP7_INC_COMPRESS_RAR3_VM_H +#define ZIP7_INC_COMPRESS_RAR3_VM_H #include "../../../C/CpuArch.h" #include "../../Common/MyVector.h" -#define RARVM_STANDARD_FILTERS -// #define RARVM_VM_ENABLE +#define Z7_RARVM_STANDARD_FILTERS +// #define Z7_RARVM_VM_ENABLE namespace NCompress { namespace NRar3 { @@ -37,7 +37,7 @@ class CMemBitDecoder namespace NVm { inline UInt32 GetValue32(const void *addr) { return GetUi32(addr); } -inline void SetValue32(void *addr, UInt32 value) { SetUi32(addr, value); } +inline void SetValue32(void *addr, UInt32 value) { SetUi32(addr, value) } const unsigned kNumRegBits = 3; const UInt32 kNumRegs = 1 << kNumRegBits; @@ -58,7 +58,7 @@ namespace NGlobalOffset } -#ifdef RARVM_VM_ENABLE +#ifdef Z7_RARVM_VM_ENABLE enum ECommand { @@ -104,14 +104,14 @@ struct CBlockRef class CProgram { - #ifdef RARVM_VM_ENABLE + #ifdef Z7_RARVM_VM_ENABLE void ReadProgram(const Byte *code, UInt32 codeSize); public: CRecordVector Commands; #endif public: - #ifdef RARVM_STANDARD_FILTERS + #ifdef Z7_RARVM_STANDARD_FILTERS int StandardFilterIndex; #endif @@ -150,7 +150,7 @@ class CVm if (byteMode) *(Byte *)addr = (Byte)value; else - SetUi32(addr, value); + SetUi32(addr, value) } UInt32 GetFixedGlobalValue32(UInt32 globalOffset) { return GetValue(false, &Mem[kGlobalOffset + globalOffset]); } @@ -162,7 +162,7 @@ class CVm private: - #ifdef RARVM_VM_ENABLE + #ifdef Z7_RARVM_VM_ENABLE UInt32 GetOperand32(const COperand *op) const; void SetOperand32(const COperand *op, UInt32 val); Byte GetOperand8(const COperand *op) const; @@ -172,8 +172,8 @@ class CVm bool ExecuteCode(const CProgram *prg); #endif - #ifdef RARVM_STANDARD_FILTERS - void ExecuteStandardFilter(unsigned filterIndex); + #ifdef Z7_RARVM_STANDARD_FILTERS + bool ExecuteStandardFilter(unsigned filterIndex); #endif Byte *Mem; diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/Rar5Decoder.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/Rar5Decoder.cpp index fdd65a11..7279b5ac 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/Rar5Decoder.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/Rar5Decoder.cpp @@ -4,52 +4,662 @@ #include "StdAfx.h" +#define DICT_SIZE_MAX ((UInt64)1 << DICT_SIZE_BITS_MAX) + +// #include // SSE2 +// #endif + +#include "../../../C/CpuArch.h" +#if 0 +#include "../../../C/Bra.h" +#endif + +#if defined(MY_CPU_ARM64) +#include +#endif + +// #define Z7_RAR5_SHOW_STAT // #include +#ifdef Z7_RAR5_SHOW_STAT +#include +#endif #include "../Common/StreamUtils.h" #include "Rar5Decoder.h" +/* +Note: original-unrar claims that encoder has limitation for Distance: + (Distance <= MaxWinSize - MAX_INC_LZ_MATCH) + MAX_INC_LZ_MATCH = 0x1001 + 3; +*/ + +#define LZ_ERROR_TYPE_NO 0 +#define LZ_ERROR_TYPE_HEADER 1 +// #define LZ_ERROR_TYPE_SYM 1 +#define LZ_ERROR_TYPE_DIST 2 + +static +void My_ZeroMemory(void *p, size_t size) +{ + #if defined(MY_CPU_AMD64) && !defined(_M_ARM64EC) \ + && defined(Z7_MSC_VER_ORIGINAL) && (Z7_MSC_VER_ORIGINAL <= 1400) + // __stosq((UInt64 *)(void *)win, 0, size / 8); + /* + printf("\n__stosb \n"); + #define STEP_BIG (1 << 28) + for (size_t i = 0; i < ((UInt64)1 << 50); i += STEP_BIG) + { + printf("\n__stosb end %p\n", (void *)i); + __stosb((Byte *)p + i, 0, STEP_BIG); + } + */ + // __stosb((Byte *)p, 0, 0); + __stosb((Byte *)p, 0, size); + #else + // SecureZeroMemory (win, STEP); + // ZeroMemory(win, STEP); + // memset(win, 0, STEP); + memset(p, 0, size); + #endif +} + + + +#ifdef MY_CPU_LE_UNALIGN + #define Z7_RAR5_DEC_USE_UNALIGNED_COPY +#endif + +#ifdef Z7_RAR5_DEC_USE_UNALIGNED_COPY + + #define COPY_CHUNK_SIZE 16 + + #define COPY_CHUNK_4_2(dest, src) \ + { \ + ((UInt32 *)(void *)dest)[0] = ((const UInt32 *)(const void *)src)[0]; \ + ((UInt32 *)(void *)dest)[1] = ((const UInt32 *)(const void *)src)[1]; \ + src += 4 * 2; \ + dest += 4 * 2; \ + } + + /* sse2 doesn't help here in GCC and CLANG. + so we disabled sse2 here */ +#if 0 + #if defined(MY_CPU_AMD64) + #define Z7_RAR5_DEC_USE_SSE2 + #elif defined(MY_CPU_X86) + #if defined(_MSC_VER) && _MSC_VER >= 1300 && defined(_M_IX86_FP) && (_M_IX86_FP >= 2) \ + || defined(__SSE2__) \ + // || 1 == 1 // for debug only + #define Z7_RAR5_DEC_USE_SSE2 + #endif + #endif +#endif + + #if defined(MY_CPU_ARM64) + + #define COPY_OFFSET_MIN 16 + #define COPY_CHUNK1(dest, src) \ + { \ + vst1q_u8((uint8_t *)(void *)dest, \ + vld1q_u8((const uint8_t *)(const void *)src)); \ + src += 16; \ + dest += 16; \ + } + + #define COPY_CHUNK(dest, src) \ + { \ + COPY_CHUNK1(dest, src) \ + if (dest >= lim) break; \ + COPY_CHUNK1(dest, src) \ + } + + #elif defined(Z7_RAR5_DEC_USE_SSE2) + #include // sse2 + #define COPY_OFFSET_MIN 16 + + #define COPY_CHUNK1(dest, src) \ + { \ + _mm_storeu_si128((__m128i *)(void *)dest, \ + _mm_loadu_si128((const __m128i *)(const void *)src)); \ + src += 16; \ + dest += 16; \ + } + + #define COPY_CHUNK(dest, src) \ + { \ + COPY_CHUNK1(dest, src) \ + if (dest >= lim) break; \ + COPY_CHUNK1(dest, src) \ + } + + #elif defined(MY_CPU_64BIT) + #define COPY_OFFSET_MIN 8 + + #define COPY_CHUNK(dest, src) \ + { \ + ((UInt64 *)(void *)dest)[0] = ((const UInt64 *)(const void *)src)[0]; \ + src += 8 * 1; dest += 8 * 1; \ + ((UInt64 *)(void *)dest)[0] = ((const UInt64 *)(const void *)src)[0]; \ + src += 8 * 1; dest += 8 * 1; \ + } + + #else + #define COPY_OFFSET_MIN 4 + + #define COPY_CHUNK(dest, src) \ + { \ + COPY_CHUNK_4_2(dest, src); \ + COPY_CHUNK_4_2(dest, src); \ + } + + #endif +#endif + + +#ifndef COPY_CHUNK_SIZE + #define COPY_OFFSET_MIN 4 + #define COPY_CHUNK_SIZE 8 + #define COPY_CHUNK_2(dest, src) \ + { \ + const Byte a0 = src[0]; \ + const Byte a1 = src[1]; \ + dest[0] = a0; \ + dest[1] = a1; \ + src += 2; \ + dest += 2; \ + } + #define COPY_CHUNK(dest, src) \ + { \ + COPY_CHUNK_2(dest, src) \ + COPY_CHUNK_2(dest, src) \ + COPY_CHUNK_2(dest, src) \ + COPY_CHUNK_2(dest, src) \ + } +#endif + + +#define COPY_CHUNKS \ +{ \ + Z7_PRAGMA_OPT_DISABLE_LOOP_UNROLL_VECTORIZE \ + do { COPY_CHUNK(dest, src) } \ + while (dest < lim); \ +} + namespace NCompress { namespace NRar5 { +typedef +#if 1 + unsigned +#else + size_t +#endif + CLenType; + +// (len != 0) +static +Z7_FORCE_INLINE +// Z7_ATTRIB_NO_VECTOR +void CopyMatch(size_t offset, Byte *dest, const Byte *src, const Byte *lim) +{ + { + // (COPY_OFFSET_MIN >= 4) + if (offset >= COPY_OFFSET_MIN) + { + COPY_CHUNKS + // return; + } + else + #if (COPY_OFFSET_MIN > 4) + #if COPY_CHUNK_SIZE < 8 + #error Stop_Compiling_Bad_COPY_CHUNK_SIZE + #endif + if (offset >= 4) + { + Z7_PRAGMA_OPT_DISABLE_LOOP_UNROLL_VECTORIZE + do + { + COPY_CHUNK_4_2(dest, src) + #if COPY_CHUNK_SIZE < 16 + if (dest >= lim) break; + #endif + COPY_CHUNK_4_2(dest, src) + } + while (dest < lim); + // return; + } + else + #endif + { + // (offset < 4) + const unsigned b0 = src[0]; + if (offset < 2) + { + #if defined(Z7_RAR5_DEC_USE_UNALIGNED_COPY) && (COPY_CHUNK_SIZE == 16) + #if defined(MY_CPU_64BIT) + { + const UInt64 v64 = (UInt64)b0 * 0x0101010101010101; + Z7_PRAGMA_OPT_DISABLE_LOOP_UNROLL_VECTORIZE + do + { + ((UInt64 *)(void *)dest)[0] = v64; + ((UInt64 *)(void *)dest)[1] = v64; + dest += 16; + } + while (dest < lim); + } + #else + { + UInt32 v = b0; + v |= v << 8; + v |= v << 16; + do + { + ((UInt32 *)(void *)dest)[0] = v; + ((UInt32 *)(void *)dest)[1] = v; + dest += 8; + ((UInt32 *)(void *)dest)[0] = v; + ((UInt32 *)(void *)dest)[1] = v; + dest += 8; + } + while (dest < lim); + } + #endif + #else + do + { + dest[0] = (Byte)b0; + dest[1] = (Byte)b0; + dest += 2; + dest[0] = (Byte)b0; + dest[1] = (Byte)b0; + dest += 2; + } + while (dest < lim); + #endif + } + else if (offset == 2) + { + const Byte b1 = src[1]; + { + do + { + dest[0] = (Byte)b0; + dest[1] = b1; + dest += 2; + } + while (dest < lim); + } + } + else // (offset == 3) + { + const Byte b1 = src[1]; + const Byte b2 = src[2]; + do + { + dest[0] = (Byte)b0; + dest[1] = b1; + dest[2] = b2; + dest += 3; + } + while (dest < lim); + } + } + } +} + static const size_t kInputBufSize = 1 << 20; - +static const UInt32 k_Filter_BlockSize_MAX = 1 << 22; +static const unsigned k_Filter_AfterPad_Size = 64; + +#ifdef Z7_RAR5_SHOW_STAT +static const unsigned kNumStats1 = 10; +static const unsigned kNumStats2 = (1 << 12) + 16; +static UInt32 g_stats1[kNumStats1]; +static UInt32 g_stats2[kNumStats1][kNumStats2]; +#endif + +#if 1 +MY_ALIGN(32) +// DICT_SIZE_BITS_MAX-1 are required +static const Byte k_LenPlusTable[DICT_SIZE_BITS_MAX] = + { 0,0,0,0,0,0,0,1,1,1,1,1,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3 }; +#endif + + + +class CBitDecoder +{ +public: + const Byte *_buf; + const Byte *_bufCheck_Block; // min(ptr for _blockEnd, _bufCheck) + unsigned _bitPos; // = [0 ... 7] + bool _wasFinished; + bool _minorError; + unsigned _blockEndBits7; // = [0 ... 7] : the number of additional bits in (_blockEnd) poisition. + HRESULT _hres; + const Byte *_bufCheck; // relaxed limit (16 bytes before real end of input data in buffer) + Byte *_bufLim; // end if input data + Byte *_bufBase; + ISequentialInStream *_stream; + + UInt64 _processedSize; + UInt64 _blockEnd; // absolute end of current block + // but it doesn't include additional _blockEndBits7 [0 ... 7] bits + + Z7_FORCE_INLINE + void CopyFrom(const CBitDecoder &a) + { + _buf = a._buf; + _bufCheck_Block = a._bufCheck_Block; + _bitPos = a._bitPos; + _wasFinished = a._wasFinished; + _blockEndBits7 = a._blockEndBits7; + _bufCheck = a._bufCheck; + _bufLim = a._bufLim; + _bufBase = a._bufBase; + + _processedSize = a._processedSize; + _blockEnd = a._blockEnd; + } + + Z7_FORCE_INLINE + void RestoreFrom2(const CBitDecoder &a) + { + _buf = a._buf; + _bitPos = a._bitPos; + } + + Z7_FORCE_INLINE + void SetCheck_forBlock() + { + _bufCheck_Block = _bufCheck; + if (_bufCheck > _buf) + { + const UInt64 processed = GetProcessedSize_Round(); + if (_blockEnd < processed) + _bufCheck_Block = _buf; + else + { + const UInt64 delta = _blockEnd - processed; + if ((size_t)(_bufCheck - _buf) > delta) + _bufCheck_Block = _buf + (size_t)delta; + } + } + } + + Z7_FORCE_INLINE + bool IsBlockOverRead() const + { + const UInt64 v = GetProcessedSize_Round(); + if (v < _blockEnd) return false; + if (v > _blockEnd) return true; + return _bitPos > _blockEndBits7; + } + + /* + CBitDecoder() throw(): + _buf(0), + _bufLim(0), + _bufBase(0), + _stream(0), + _processedSize(0), + _wasFinished(false) + {} + */ + + Z7_FORCE_INLINE + void Init() throw() + { + _blockEnd = 0; + _blockEndBits7 = 0; + + _bitPos = 0; + _processedSize = 0; + _buf = _bufBase; + _bufLim = _bufBase; + _bufCheck = _buf; + _bufCheck_Block = _buf; + _wasFinished = false; + _minorError = false; + } + + void Prepare2() throw(); + + Z7_FORCE_INLINE + void Prepare() throw() + { + if (_buf >= _bufCheck) + Prepare2(); + } + + Z7_FORCE_INLINE + bool ExtraBitsWereRead() const + { + return _buf >= _bufLim && (_buf > _bufLim || _bitPos != 0); + } + + Z7_FORCE_INLINE bool InputEofError() const { return ExtraBitsWereRead(); } + + Z7_FORCE_INLINE unsigned GetProcessedBits7() const { return _bitPos; } + Z7_FORCE_INLINE UInt64 GetProcessedSize_Round() const { return _processedSize + (size_t)(_buf - _bufBase); } + Z7_FORCE_INLINE UInt64 GetProcessedSize() const { return _processedSize + (size_t)(_buf - _bufBase) + ((_bitPos + 7) >> 3); } + + Z7_FORCE_INLINE + void AlignToByte() + { + if (_bitPos != 0) + { +#if 1 + // optional check of unused bits for strict checking: + // original-unrar doesn't check it: + const unsigned b = (unsigned)*_buf << _bitPos; + if (b & 0xff) + _minorError = true; +#endif + _buf++; + _bitPos = 0; + } + // _buf += (_bitPos + 7) >> 3; + // _bitPos = 0; + } + + Z7_FORCE_INLINE + Byte ReadByte_InAligned() + { + return *_buf++; + } + + Z7_FORCE_INLINE + UInt32 GetValue(unsigned numBits) const + { + // 0 < numBits <= 17 : supported values +#if defined(Z7_CPU_FAST_BSWAP_SUPPORTED) && defined(MY_CPU_LE_UNALIGN) + UInt32 v = GetBe32(_buf); +#if 1 + return (v >> (32 - numBits - _bitPos)) & ((1u << numBits) - 1); +#else + return (v << _bitPos) >> (32 - numBits); +#endif +#else + UInt32 v = ((UInt32)_buf[0] << 16) | ((UInt32)_buf[1] << 8) | (UInt32)_buf[2]; + v >>= 24 - numBits - _bitPos; + return v & ((1 << numBits) - 1); +#endif + } + + Z7_FORCE_INLINE + UInt32 GetValue_InHigh32bits() const + { + // 0 < numBits <= 17 : supported vales +#if defined(Z7_CPU_FAST_BSWAP_SUPPORTED) && defined(MY_CPU_LE_UNALIGN) + return GetBe32(_buf) << _bitPos; +#else + const UInt32 v = ((UInt32)_buf[0] << 16) | ((UInt32)_buf[1] << 8) | (UInt32)_buf[2]; + return v << (_bitPos + 8); +#endif + } + + + Z7_FORCE_INLINE + void MovePos(unsigned numBits) + { + numBits += _bitPos; + _buf += numBits >> 3; + _bitPos = numBits & 7; + } + + + Z7_FORCE_INLINE + UInt32 ReadBits9(unsigned numBits) + { + const Byte *buf = _buf; + UInt32 v = ((UInt32)buf[0] << 8) | (UInt32)buf[1]; + v &= (UInt32)0xFFFF >> _bitPos; + numBits += _bitPos; + v >>= 16 - numBits; + _buf = buf + (numBits >> 3); + _bitPos = numBits & 7; + return v; + } + + Z7_FORCE_INLINE + UInt32 ReadBits_9fix(unsigned numBits) + { + const Byte *buf = _buf; + UInt32 v = ((UInt32)buf[0] << 8) | (UInt32)buf[1]; + const UInt32 mask = (1u << numBits) - 1; + numBits += _bitPos; + v >>= 16 - numBits; + _buf = buf + (numBits >> 3); + _bitPos = numBits & 7; + return v & mask; + } + +#if 1 && defined(MY_CPU_SIZEOF_POINTER) && (MY_CPU_SIZEOF_POINTER == 8) +#define Z7_RAR5_USE_64BIT +#endif + +#ifdef Z7_RAR5_USE_64BIT +#define MAX_DICT_LOG (sizeof(size_t) / 8 * 5 + 31) +#else +#define MAX_DICT_LOG 31 +#endif + +#ifdef Z7_RAR5_USE_64BIT + + Z7_FORCE_INLINE + size_t ReadBits_Big(unsigned numBits, UInt64 v) + { + const UInt64 mask = ((UInt64)1 << numBits) - 1; + numBits += _bitPos; + const Byte *buf = _buf; + // UInt64 v = GetBe64(buf); + v >>= 64 - numBits; + _buf = buf + (numBits >> 3); + _bitPos = numBits & 7; + return (size_t)(v & mask); + } + #define ReadBits_Big25 ReadBits_Big + +#else + + // (numBits <= 25) for 32-bit mode + Z7_FORCE_INLINE + size_t ReadBits_Big25(unsigned numBits, UInt32 v) + { + const UInt32 mask = ((UInt32)1 << numBits) - 1; + numBits += _bitPos; + v >>= 32 - numBits; + _buf += numBits >> 3; + _bitPos = numBits & 7; + return v & mask; + } + + // numBits != 0 + Z7_FORCE_INLINE + size_t ReadBits_Big(unsigned numBits, UInt32 v) + { + const Byte *buf = _buf; + // UInt32 v = GetBe32(buf); +#if 0 + const UInt32 mask = ((UInt32)1 << numBits) - 1; + numBits += _bitPos; + if (numBits > 32) + { + v <<= numBits - 32; + v |= (UInt32)buf[4] >> (40 - numBits); + } + else + v >>= 32 - numBits; + _buf = buf + (numBits >> 3); + _bitPos = numBits & 7; + return v & mask; +#else + v <<= _bitPos; + v |= (UInt32)buf[4] >> (8 - _bitPos); + v >>= 32 - numBits; + numBits += _bitPos; + _buf = buf + (numBits >> 3); + _bitPos = numBits & 7; + return v; +#endif + } +#endif +}; + + +static const unsigned kLookaheadSize = 16; +static const unsigned kInputBufferPadZone = kLookaheadSize; + +Z7_NO_INLINE void CBitDecoder::Prepare2() throw() { - const unsigned kSize = 16; if (_buf > _bufLim) return; - size_t rem = _bufLim - _buf; + size_t rem = (size_t)(_bufLim - _buf); if (rem != 0) memmove(_bufBase, _buf, rem); _bufLim = _bufBase + rem; - _processedSize += (_buf - _bufBase); + _processedSize += (size_t)(_buf - _bufBase); _buf = _bufBase; + // we do not look ahead more than 16 bytes before limit checks. + if (!_wasFinished) { - UInt32 processed = (UInt32)(kInputBufSize - rem); - _hres = _stream->Read(_bufLim, (UInt32)processed, &processed); - _bufLim += processed; - _wasFinished = (processed == 0); - if (_hres != S_OK) + while (rem <= kLookaheadSize) { - _wasFinished = true; - // throw CInBufferException(result); + UInt32 processed = (UInt32)(kInputBufSize - rem); + // processed = 33; // for debug + _hres = _stream->Read(_bufLim, processed, &processed); + _bufLim += processed; + rem += processed; + if (processed == 0 || _hres != S_OK) + { + _wasFinished = true; + // if (_hres != S_OK) throw CInBufferException(result); + break; + } } } - rem = _bufLim - _buf; - _bufCheck = _buf; - if (rem < kSize) - memset(_bufLim, 0xFF, kSize - rem); + // we always fill pad zone here. + // so we don't need to call Prepare2() if (_wasFinished == true) + memset(_bufLim, 0xFF, kLookaheadSize); + + if (rem < kLookaheadSize) + { + _bufCheck = _buf; + // memset(_bufLim, 0xFF, kLookaheadSize - rem); + } else - _bufCheck = _bufLim - kSize; + _bufCheck = _bufLim - kLookaheadSize; - SetCheck2(); + SetCheck_forBlock(); } @@ -61,28 +671,79 @@ enum FilterType FILTER_ARM }; -static const size_t kWriteStep = (size_t)1 << 22; +static const size_t kWriteStep = (size_t)1 << 18; + // (size_t)1 << 22; // original-unrar + +// Original unRAR claims that maximum possible filter block size is (1 << 16) now, +// and (1 << 17) is minimum win size required to support filter. +// Original unRAR uses (1u << 18) for "extra safety and possible filter area size expansion" +// We can use any win size, but we use same (1u << 18) for compatibility +// with WinRar + +// static const unsigned kWinSize_Log_Min = 17; +static const size_t kWinSize_Min = 1u << 18; CDecoder::CDecoder(): + _isSolid(false), + _is_v7(false), + _wasInit(false), + // _dictSizeLog(0), + _dictSize(kWinSize_Min), _window(NULL), _winPos(0), - _winSizeAllocated(0), + _winSize(0), + _dictSize_forCheck(0), _lzSize(0), _lzEnd(0), _writtenFileSize(0), - _dictSizeLog(0), - _isSolid(false), - _wasInit(false), + _filters(NULL), + _winSize_Allocated(0), _inputBuf(NULL) { +#if 1 + memcpy(m_LenPlusTable, k_LenPlusTable, sizeof(k_LenPlusTable)); +#endif + // printf("\nsizeof(CDecoder) == %d\n", sizeof(CDecoder)); } CDecoder::~CDecoder() { - ::MidFree(_window); - ::MidFree(_inputBuf); +#ifdef Z7_RAR5_SHOW_STAT + printf("\n%4d :", 0); + for (unsigned k = 0; k < kNumStats1; k++) + printf(" %8u", (unsigned)g_stats1[k]); + printf("\n"); + for (unsigned i = 0; i < kNumStats2; i++) + { + printf("\n%4d :", i); + for (unsigned k = 0; k < kNumStats1; k++) + printf(" %8u", (unsigned)g_stats2[k][i]); + } + printf("\n"); +#endif + +#define Z7_RAR_FREE_WINDOW ::BigFree(_window); + + Z7_RAR_FREE_WINDOW + z7_AlignedFree(_inputBuf); + z7_AlignedFree(_filters); +} + +Z7_NO_INLINE +void CDecoder::DeleteUnusedFilters() +{ + if (_numUnusedFilters != 0) + { + // printf("\nDeleteUnusedFilters _numFilters = %6u\n", _numFilters); + const unsigned n = _numFilters - _numUnusedFilters; + _numFilters = n; + memmove(_filters, _filters + _numUnusedFilters, n * sizeof(CFilter)); + _numUnusedFilters = 0; + } } + +Z7_NO_INLINE HRESULT CDecoder::WriteData(const Byte *data, size_t size) { HRESULT res = S_OK; @@ -91,7 +752,7 @@ HRESULT CDecoder::WriteData(const Byte *data, size_t size) size_t cur = size; if (_unpackSize_Defined) { - UInt64 rem = _unpackSize - _writtenFileSize; + const UInt64 rem = _unpackSize - _writtenFileSize; if (cur > rem) cur = (size_t)rem; } @@ -103,149 +764,217 @@ HRESULT CDecoder::WriteData(const Byte *data, size_t size) return res; } + +#if defined(MY_CPU_SIZEOF_POINTER) \ + && ( MY_CPU_SIZEOF_POINTER == 4 \ + || MY_CPU_SIZEOF_POINTER == 8) + #define BR_CONV_USE_OPT_PC_PTR +#endif + +#ifdef BR_CONV_USE_OPT_PC_PTR +#define BR_PC_INIT(lim_back) pc -= (UInt32)(SizeT)data; +#define BR_PC_GET (pc + (UInt32)(SizeT)data) +#else +#define BR_PC_INIT(lim_back) pc += (UInt32)dataSize - (lim_back); +#define BR_PC_GET (pc - (UInt32)(SizeT)(data_lim - data)) +#endif + +#ifdef MY_CPU_LE_UNALIGN +#define Z7_RAR5_FILTER_USE_LE_UNALIGN +#endif + +#ifdef Z7_RAR5_FILTER_USE_LE_UNALIGN +#define RAR_E8_FILT(mask) \ +{ \ + for (;;) \ + { UInt32 v; \ + do { \ + v = GetUi32(data) ^ (UInt32)0xe8e8e8e8; \ + data += 4; \ + if ((v & ((UInt32)(mask) << (8 * 0))) == 0) { data -= 3; break; } \ + if ((v & ((UInt32)(mask) << (8 * 1))) == 0) { data -= 2; break; } \ + if ((v & ((UInt32)(mask) << (8 * 2))) == 0) { data -= 1; break; } } \ + while((v & ((UInt32)(mask) << (8 * 3)))); \ + if (data > data_lim) break; \ + const UInt32 offset = BR_PC_GET & (kFileSize - 1); \ + const UInt32 addr = GetUi32(data); \ + data += 4; \ + if (addr < kFileSize) \ + SetUi32(data - 4, addr - offset) \ + else if (addr > ~offset) /* if (addr > ((UInt32)0xFFFFFFFF - offset)) */ \ + SetUi32(data - 4, addr + kFileSize) \ + } \ +} +#else +#define RAR_E8_FILT(get_byte) \ +{ \ + for (;;) \ + { \ + if ((get_byte) != 0xe8) \ + if ((get_byte) != 0xe8) \ + if ((get_byte) != 0xe8) \ + if ((get_byte) != 0xe8) \ + continue; \ + { if (data > data_lim) break; \ + const UInt32 offset = BR_PC_GET & (kFileSize - 1); \ + const UInt32 addr = GetUi32(data); \ + data += 4; \ + if (addr < kFileSize) \ + SetUi32(data - 4, addr - offset) \ + else if (addr > ~offset) /* if (addr > ((UInt32)0xFFFFFFFF - offset)) */ \ + SetUi32(data - 4, addr + kFileSize) \ + } \ + } \ +} +#endif + HRESULT CDecoder::ExecuteFilter(const CFilter &f) { - bool useDest = false; - Byte *data = _filterSrc; UInt32 dataSize = f.Size; - // printf("\nType = %d offset = %9d size = %5d", f.Type, (unsigned)(f.Start - _lzFileStart), dataSize); - - switch (f.Type) + + if (f.Type == FILTER_DELTA) { - case FILTER_E8: - case FILTER_E8E9: + // static unsigned g1 = 0, g2 = 0; g1 += dataSize; + // if (g2++ % 100 == 0) printf("DELTA num %8u, size %8u MiB, channels = %2u curSize=%8u\n", g2, (g1 >> 20), f.Channels, dataSize); + _filterDst.AllocAtLeast_max((size_t)dataSize, k_Filter_BlockSize_MAX); + if (!_filterDst.IsAllocated()) + return E_OUTOFMEMORY; + + Byte *dest = _filterDst; + const unsigned numChannels = f.Channels; + unsigned curChannel = 0; + do { - // printf(" FILTER_E8"); - if (dataSize > 4) - { - dataSize -= 4; - UInt32 fileOffset = (UInt32)(f.Start - _lzFileStart); - - const UInt32 kFileSize = (UInt32)1 << 24; - Byte cmpMask = (Byte)(f.Type == FILTER_E8 ? 0xFF : 0xFE); - - for (UInt32 curPos = 0; curPos < dataSize;) - { - curPos++; - if (((*data++) & cmpMask) == 0xE8) - { - UInt32 offset = (curPos + fileOffset) & (kFileSize - 1); - UInt32 addr = GetUi32(data); - - if (addr < kFileSize) - { - SetUi32(data, addr - offset); - } - else if (addr > ((UInt32)0xFFFFFFFF - offset)) // (addr > ~(offset)) - { - SetUi32(data, addr + kFileSize); - } - - data += 4; - curPos += 4; - } - } - } - break; + Byte prevByte = 0; + Byte *dest2 = dest + curChannel; + const Byte *dest_lim = dest + dataSize; + for (; dest2 < dest_lim; dest2 += numChannels) + *dest2 = (prevByte = (Byte)(prevByte - *data++)); } - - case FILTER_ARM: + while (++curChannel != numChannels); + // return WriteData(dest, dataSize); + data = dest; + } + else if (f.Type < FILTER_ARM) + { + // FILTER_E8 or FILTER_E8E9 + if (dataSize > 4) { - if (dataSize >= 4) + UInt32 pc = (UInt32)(f.Start - _lzFileStart); + const UInt32 kFileSize = (UInt32)1 << 24; + const Byte *data_lim = data + dataSize - 4; + BR_PC_INIT(4) // because (data_lim) was moved back for 4 bytes + data[dataSize] = 0xe8; + if (f.Type == FILTER_E8) { - dataSize -= 4; - UInt32 fileOffset = (UInt32)(f.Start - _lzFileStart); - - for (UInt32 curPos = 0; curPos <= dataSize; curPos += 4) - { - Byte *d = data + curPos; - if (d[3] == 0xEB) - { - UInt32 offset = d[0] | ((UInt32)d[1] << 8) | ((UInt32)d[2] << 16); - offset -= (fileOffset + curPos) >> 2; - d[0] = (Byte)offset; - d[1] = (Byte)(offset >> 8); - d[2] = (Byte)(offset >> 16); - } - } + // static unsigned g1 = 0; g1 += dataSize; printf("\n FILTER_E8 %u", (g1 >> 20)); +#ifdef Z7_RAR5_FILTER_USE_LE_UNALIGN + RAR_E8_FILT (0xff) +#else + RAR_E8_FILT (*data++) +#endif + } + else + { + // static unsigned g1 = 0; g1 += dataSize; printf("\n FILTER_E8_E9 %u", (g1 >> 20)); +#ifdef Z7_RAR5_FILTER_USE_LE_UNALIGN + RAR_E8_FILT (0xfe) +#else + RAR_E8_FILT (*data++ & 0xfe) +#endif } - break; } - - case FILTER_DELTA: + data = _filterSrc; + } + else if (f.Type == FILTER_ARM) + { + UInt32 pc = (UInt32)(f.Start - _lzFileStart); +#if 0 + // z7_BranchConv_ARM_Dec expects that (fileOffset & 3) == 0; + // but even if (fileOffset & 3) then current code + // in z7_BranchConv_ARM_Dec works same way as unrar's code still. + z7_BranchConv_ARM_Dec(data, dataSize, pc - 8); +#else + dataSize &= ~(UInt32)3; + if (dataSize) { - // printf(" channels = %d", f.Channels); - _filterDst.AllocAtLeast(dataSize); - if (!_filterDst.IsAllocated()) - return E_OUTOFMEMORY; - - Byte *dest = _filterDst; - UInt32 numChannels = f.Channels; - - for (UInt32 curChannel = 0; curChannel < numChannels; curChannel++) + Byte *data_lim = data + dataSize; + data_lim[3] = 0xeb; + BR_PC_INIT(0) + pc -= 4; // because (data) will point to next instruction + for (;;) // do { - Byte prevByte = 0; - for (UInt32 destPos = curChannel; destPos < dataSize; destPos += numChannels) - dest[destPos] = (prevByte = (Byte)(prevByte - *data++)); + data += 4; + if (data[-1] != 0xeb) + continue; + if (data > data_lim) + break; + { + UInt32 v = GetUi32a(data - 4) - (BR_PC_GET >> 2); + v &= 0x00ffffff; + v |= 0xeb000000; + SetUi32a(data - 4, v) + } } - useDest = true; - break; } - - default: - _unsupportedFilter = true; +#endif + data = _filterSrc; } - - return WriteData(useDest ? - (const Byte *)_filterDst : - (const Byte *)_filterSrc, - f.Size); + else + { + _unsupportedFilter = true; + My_ZeroMemory(data, dataSize); + // return S_OK; // unrar + } + // return WriteData(_filterSrc, (size_t)f.Size); + return WriteData(data, (size_t)f.Size); } HRESULT CDecoder::WriteBuf() { DeleteUnusedFilters(); + const UInt64 lzSize = _lzSize + _winPos; - for (unsigned i = 0; i < _filters.Size();) + for (unsigned i = 0; i < _numFilters;) { - const CFilter &f = _filters[i]; - - UInt64 blockStart = f.Start; - - size_t lzAvail = (size_t)(_lzSize - _lzWritten); + const size_t lzAvail = (size_t)(lzSize - _lzWritten); if (lzAvail == 0) break; - + // (lzAvail != 0) + const CFilter &f = _filters[i]; + const UInt64 blockStart = f.Start; if (blockStart > _lzWritten) { - UInt64 rem = blockStart - _lzWritten; + const UInt64 rem = blockStart - _lzWritten; + // (rem != 0) size_t size = lzAvail; if (size > rem) size = (size_t)rem; - if (size != 0) - { - RINOK(WriteData(_window + _winPos - lzAvail, size)); - _lzWritten += size; - } + // (size != 0) + RINOK(WriteData(_window + _winPos - lzAvail, size)) + _lzWritten += size; continue; } - - UInt32 blockSize = f.Size; + + // (blockStart <= _lzWritten) + const UInt32 blockSize = f.Size; size_t offset = (size_t)(_lzWritten - blockStart); if (offset == 0) { - _filterSrc.AllocAtLeast(blockSize); + _filterSrc.AllocAtLeast_max( + (size_t)blockSize + k_Filter_AfterPad_Size, + k_Filter_BlockSize_MAX + k_Filter_AfterPad_Size); if (!_filterSrc.IsAllocated()) return E_OUTOFMEMORY; } - size_t blockRem = (size_t)blockSize - offset; + const size_t blockRem = (size_t)blockSize - offset; size_t size = lzAvail; if (size > blockRem) - size = blockRem; + size = blockRem; memcpy(_filterSrc + offset, _window + _winPos - lzAvail, size); _lzWritten += size; offset += size; @@ -253,27 +982,31 @@ HRESULT CDecoder::WriteBuf() return S_OK; _numUnusedFilters = ++i; - RINOK(ExecuteFilter(f)); + RINOK(ExecuteFilter(f)) } DeleteUnusedFilters(); - - if (!_filters.IsEmpty()) + if (_numFilters) return S_OK; - - size_t lzAvail = (size_t)(_lzSize - _lzWritten); - RINOK(WriteData(_window + _winPos - lzAvail, lzAvail)); + const size_t lzAvail = (size_t)(lzSize - _lzWritten); + RINOK(WriteData(_window + _winPos - lzAvail, lzAvail)) _lzWritten += lzAvail; return S_OK; } +Z7_NO_INLINE static UInt32 ReadUInt32(CBitDecoder &bi) { - unsigned numBytes = bi.ReadBits9fix(2) + 1; + const unsigned numBits = (unsigned)bi.ReadBits_9fix(2) * 8 + 8; UInt32 v = 0; - for (unsigned i = 0; i < numBytes; i++) - v += ((UInt32)bi.ReadBits9fix(8) << (i * 8)); + unsigned i = 0; + do + { + v += (UInt32)bi.ReadBits_9fix(8) << i; + i += 8; + } + while (i != numBits); return v; } @@ -284,11 +1017,11 @@ HRESULT CDecoder::AddFilter(CBitDecoder &_bitStream) { DeleteUnusedFilters(); - if (_filters.Size() >= MAX_UNPACK_FILTERS) + if (_numFilters >= MAX_UNPACK_FILTERS) { - RINOK(WriteBuf()); + RINOK(WriteBuf()) DeleteUnusedFilters(); - if (_filters.Size() >= MAX_UNPACK_FILTERS) + if (_numFilters >= MAX_UNPACK_FILTERS) { _unsupportedFilter = true; InitFilters(); @@ -298,16 +1031,27 @@ HRESULT CDecoder::AddFilter(CBitDecoder &_bitStream) _bitStream.Prepare(); CFilter f; - UInt32 blockStart = ReadUInt32(_bitStream); + const UInt32 blockStart = ReadUInt32(_bitStream); f.Size = ReadUInt32(_bitStream); - // if (f.Size > ((UInt32)1 << 16)) _unsupportedFilter = true; + if (f.Size > k_Filter_BlockSize_MAX) + { + _unsupportedFilter = true; + f.Size = 0; // unrar 5.5.5 + } - f.Type = (Byte)_bitStream.ReadBits9fix(3); + f.Type = (Byte)_bitStream.ReadBits_9fix(3); f.Channels = 0; if (f.Type == FILTER_DELTA) - f.Channels = (Byte)(_bitStream.ReadBits9fix(5) + 1); - f.Start = _lzSize + blockStart; + f.Channels = (Byte)(_bitStream.ReadBits_9fix(5) + 1); + f.Start = _lzSize + _winPos + blockStart; + +#if 0 + static unsigned z_cnt = 0; if (z_cnt++ % 100 == 0) + printf ("\nFilter %7u : %4u : %8p, st=%8x, size=%8x, type=%u ch=%2u", + z_cnt, (unsigned)_filters.Size(), (void *)(size_t)(_lzSize + _winPos), + (unsigned)blockStart, (unsigned)f.Size, (unsigned)f.Type, (unsigned)f.Channels); +#endif if (f.Start < _filterEnd) _unsupportedFilter = true; @@ -315,8 +1059,18 @@ HRESULT CDecoder::AddFilter(CBitDecoder &_bitStream) { _filterEnd = f.Start + f.Size; if (f.Size != 0) - _filters.Add(f); - } + { + if (!_filters) + { + _filters = (CFilter *)z7_AlignedAlloc(MAX_UNPACK_FILTERS * sizeof(CFilter)); + if (!_filters) + return E_OUTOFMEMORY; + } + // printf("\n_numFilters = %6u\n", _numFilters); + const unsigned i = _numFilters++; + _filters[i] = f; + } + } return S_OK; } @@ -324,159 +1078,248 @@ HRESULT CDecoder::AddFilter(CBitDecoder &_bitStream) #define RIF(x) { if (!(x)) return S_FALSE; } +#if 1 +#define PRINT_CNT(name, skip) +#else +#define PRINT_CNT(name, skip) \ + { static unsigned g_cnt = 0; if (g_cnt++ % skip == 0) printf("\n%16s: %8u", name, g_cnt); } +#endif + HRESULT CDecoder::ReadTables(CBitDecoder &_bitStream) { if (_progress) { - UInt64 packSize = _bitStream.GetProcessedSize(); - RINOK(_progress->SetRatioInfo(&packSize, &_writtenFileSize)); + const UInt64 packSize = _bitStream.GetProcessedSize(); + if (packSize - _progress_Pack >= (1u << 24) + || _writtenFileSize - _progress_Unpack >= (1u << 26)) + { + _progress_Pack = packSize; + _progress_Unpack = _writtenFileSize; + RINOK(_progress->SetRatioInfo(&_progress_Pack, &_writtenFileSize)) + } + // printf("\ntable read pos=%p packSize=%p _writtenFileSize = %p\n", (size_t)_winPos, (size_t)packSize, (size_t)_writtenFileSize); } - _bitStream.AlignToByte(); + // _bitStream is aligned already _bitStream.Prepare(); - - unsigned flags = _bitStream.ReadByteInAligned(); - unsigned checkSum = _bitStream.ReadByteInAligned(); - checkSum ^= flags; - - UInt32 blockSize; { - unsigned num = (flags >> 3) & 3; - if (num == 3) + const unsigned flags = _bitStream.ReadByte_InAligned(); + /* ((flags & 20) == 0) in all rar archives now, + but (flags & 20) flag can be used as some decoding hint in future versions of original rar. + So we ignore that bit here. */ + unsigned checkSum = _bitStream.ReadByte_InAligned(); + checkSum ^= flags; + const unsigned num = (flags >> 3) & 3; + if (num >= 3) return S_FALSE; - blockSize = _bitStream.ReadByteInAligned(); - if (num > 0) + UInt32 blockSize = _bitStream.ReadByte_InAligned(); + checkSum ^= blockSize; + if (num != 0) { - blockSize += (UInt32)_bitStream.ReadByteInAligned() << 8; + { + const unsigned b = _bitStream.ReadByte_InAligned(); + checkSum ^= b; + blockSize += (UInt32)b << 8; + } if (num > 1) - blockSize += (UInt32)_bitStream.ReadByteInAligned() << 16; + { + const unsigned b = _bitStream.ReadByte_InAligned(); + checkSum ^= b; + blockSize += (UInt32)b << 16; + } } - } - - checkSum ^= blockSize ^ (blockSize >> 8) ^ (blockSize >> 16); - if ((Byte)checkSum != 0x5A) - return S_FALSE; - - unsigned blockSizeBits7 = (flags & 7) + 1; - - if (blockSize == 0 && blockSizeBits7 != 8) - return S_FALSE; - - blockSize += (blockSizeBits7 >> 3); - blockSize--; - - _bitStream._blockEndBits7 = (Byte)(blockSizeBits7 & 7); - _bitStream._blockEnd = _bitStream.GetProcessedSize_Round() + blockSize; - - _bitStream.SetCheck2(); - - _isLastBlock = ((flags & 0x40) != 0); - - if ((flags & 0x80) == 0) - { - if (!_tableWasFilled && blockSize != 0) + if (checkSum != 0x5A) return S_FALSE; - return S_OK; + unsigned blockSizeBits7 = (flags & 7) + 1; + blockSize += (UInt32)(blockSizeBits7 >> 3); + if (blockSize == 0) + { + // it's error in data stream + // but original-unrar ignores that error + _bitStream._minorError = true; +#if 1 + // we ignore that error as original-unrar: + blockSizeBits7 = 0; + blockSize = 1; +#else + // we can stop decoding: + return S_FALSE; +#endif + } + blockSize--; + blockSizeBits7 &= 7; + PRINT_CNT("Blocks", 100) + /* + { + static unsigned g_prev = 0; + static unsigned g_cnt = 0; + unsigned proc = unsigned(_winPos); + if (g_cnt++ % 100 == 0) printf(" c_size = %8u ", blockSize); + if (g_cnt++ % 100 == 1) printf(" unp_size = %8u", proc - g_prev); + g_prev = proc; + } + */ + _bitStream._blockEndBits7 = blockSizeBits7; + _bitStream._blockEnd = _bitStream.GetProcessedSize_Round() + blockSize; + _bitStream.SetCheck_forBlock(); + _isLastBlock = ((flags & 0x40) != 0); + if ((flags & 0x80) == 0) + { + if (!_tableWasFilled) + // if (blockSize != 0 || blockSizeBits7 != 0) + if (blockSize + blockSizeBits7 != 0) + return S_FALSE; + return S_OK; + } + _tableWasFilled = false; } - _tableWasFilled = false; + PRINT_CNT("Tables", 100); + const unsigned kLevelTableSize = 20; + const unsigned k_NumHufTableBits_Level = 6; + NHuffman::CDecoder256 m_LevelDecoder; + const unsigned kTablesSizesSum_MAX = kMainTableSize + kDistTableSize_MAX + kAlignTableSize + kLenTableSize; + Byte lens[kTablesSizesSum_MAX]; { - Byte lens2[kLevelTableSize]; - - for (unsigned i = 0; i < kLevelTableSize;) + // (kLevelTableSize + 16 < kTablesSizesSum). So we use lens[] array for (Level) table + // Byte lens2[kLevelTableSize + 16]; + unsigned i = 0; + do { - _bitStream.Prepare(); - unsigned len = (unsigned)_bitStream.ReadBits9fix(4); + if (_bitStream._buf >= _bitStream._bufCheck_Block) + { + _bitStream.Prepare(); + if (_bitStream.IsBlockOverRead()) + return S_FALSE; + } + const unsigned len = (unsigned)_bitStream.ReadBits_9fix(4); if (len == 15) { - unsigned num = (unsigned)_bitStream.ReadBits9fix(4); + unsigned num = (unsigned)_bitStream.ReadBits_9fix(4); if (num != 0) { num += 2; num += i; + // we are allowed to overwrite to lens[] for extra 16 bytes after kLevelTableSize +#if 0 if (num > kLevelTableSize) + { + // we ignore this error as original-unrar num = kLevelTableSize; + // return S_FALSE; + } +#endif do - lens2[i++] = 0; + lens[i++] = 0; while (i < num); continue; } } - lens2[i++] = (Byte)len; + lens[i++] = (Byte)len; } - + while (i < kLevelTableSize); if (_bitStream.IsBlockOverRead()) return S_FALSE; - - RIF(m_LevelDecoder.Build(lens2)); + RIF(m_LevelDecoder.Build(lens, NHuffman::k_BuildMode_Full)) } - - Byte lens[kTablesSizesSum]; + unsigned i = 0; - - while (i < kTablesSizesSum) + const unsigned tableSize = _is_v7 ? + kTablesSizesSum_MAX : + kTablesSizesSum_MAX - kExtraDistSymbols_v7; + do { - if (_bitStream._buf >= _bitStream._bufCheck2) + if (_bitStream._buf >= _bitStream._bufCheck_Block) { - if (_bitStream._buf >= _bitStream._bufCheck) - _bitStream.Prepare(); + // if (_bitStream._buf >= _bitStream._bufCheck) + _bitStream.Prepare(); if (_bitStream.IsBlockOverRead()) return S_FALSE; } - - UInt32 sym = m_LevelDecoder.Decode(&_bitStream); - + const unsigned sym = m_LevelDecoder.DecodeFull(&_bitStream); if (sym < 16) lens[i++] = (Byte)sym; +#if 0 else if (sym > kLevelTableSize) return S_FALSE; +#endif else { - sym -= 16; - unsigned sh = ((sym & 1) << 2); - unsigned num = (unsigned)_bitStream.ReadBits9(3 + sh) + 3 + (sh << 1); - + unsigned num = ((sym /* - 16 */) & 1) * 4; + num += num + 3 + (unsigned)_bitStream.ReadBits9(num + 3); num += i; - if (num > kTablesSizesSum) - num = kTablesSizesSum; - - if (sym < 2) + if (num > tableSize) { - if (i == 0) - { - // return S_FALSE; - continue; // original unRAR - } - Byte v = lens[i - 1]; - do - lens[i++] = v; - while (i < num); + // we ignore this error as original-unrar + num = tableSize; + // return S_FALSE; } - else + unsigned v = 0; + if (sym < 16 + 2) { - do - lens[i++] = 0; - while (i < num); + if (i == 0) + return S_FALSE; + v = lens[(size_t)i - 1]; } + do + lens[i++] = (Byte)v; + while (i < num); } } + while (i < tableSize); if (_bitStream.IsBlockOverRead()) return S_FALSE; if (_bitStream.InputEofError()) return S_FALSE; - RIF(m_MainDecoder.Build(&lens[0])); - RIF(m_DistDecoder.Build(&lens[kMainTableSize])); - RIF(m_AlignDecoder.Build(&lens[kMainTableSize + kDistTableSize])); - RIF(m_LenDecoder.Build(&lens[kMainTableSize + kDistTableSize + kAlignTableSize])); + /* We suppose that original-rar encoder can create only two cases for Huffman: + 1) Empty Huffman tree (if num_used_symbols == 0) + 2) Full Huffman tree (if num_used_symbols != 0) + Usually the block contains at least one symbol for m_MainDecoder. + So original-rar-encoder creates full Huffman tree for m_MainDecoder. + But we suppose that (num_used_symbols == 0) is possible for m_MainDecoder, + because file must be finished with (_isLastBlock) flag, + even if there are no symbols in m_MainDecoder. + So we use k_BuildMode_Full_or_Empty for m_MainDecoder. + */ + const NHuffman::enum_BuildMode buildMode = NHuffman:: + k_BuildMode_Full_or_Empty; // strict check + // k_BuildMode_Partial; // non-strict check (ignore errors) + + RIF(m_MainDecoder.Build(&lens[0], buildMode)) + if (!_is_v7) + { +#if 1 + /* we use this manual loop to avoid compiler BUG. + GCC 4.9.2 compiler has BUG with overlapping memmove() to right in local array. */ + Byte *dest = lens + kMainTableSize + kDistTableSize_v6 + + kAlignTableSize + kLenTableSize - 1; + unsigned num = kAlignTableSize + kLenTableSize; + do + { + dest[kExtraDistSymbols_v7] = dest[0]; + dest--; + } + while (--num); +#else + memmove(lens + kMainTableSize + kDistTableSize_v6 + kExtraDistSymbols_v7, + lens + kMainTableSize + kDistTableSize_v6, + kAlignTableSize + kLenTableSize); +#endif + memset(lens + kMainTableSize + kDistTableSize_v6, 0, kExtraDistSymbols_v7); + } + + RIF(m_DistDecoder.Build(&lens[kMainTableSize], buildMode)) + RIF( m_LenDecoder.Build(&lens[kMainTableSize + + kDistTableSize_MAX + kAlignTableSize], buildMode)) _useAlignBits = false; - // _useAlignBits = true; for (i = 0; i < kAlignTableSize; i++) - if (lens[kMainTableSize + kDistTableSize + i] != kNumAlignBits) + if (lens[kMainTableSize + kDistTableSize_MAX + (size_t)i] != kNumAlignBits) { + RIF(m_AlignDecoder.Build(&lens[kMainTableSize + kDistTableSize_MAX], buildMode)) _useAlignBits = true; break; } @@ -485,135 +1328,114 @@ HRESULT CDecoder::ReadTables(CBitDecoder &_bitStream) return S_OK; } +static inline CLenType SlotToLen(CBitDecoder &_bitStream, CLenType slot) +{ + const unsigned numBits = ((unsigned)slot >> 2) - 1; + return ((4 | (slot & 3)) << numBits) + (CLenType)_bitStream.ReadBits9(numBits); +} + + +static const unsigned kSymbolRep = 258; +static const unsigned kMaxMatchLen = 0x1001 + 3; -static inline unsigned SlotToLen(CBitDecoder &_bitStream, unsigned slot) +enum enum_exit_type { - if (slot < 8) - return slot + 2; - unsigned numBits = (slot >> 2) - 1; - return 2 + ((4 | (slot & 3)) << numBits) + _bitStream.ReadBits9(numBits); + Z7_RAR_EXIT_TYPE_NONE, + Z7_RAR_EXIT_TYPE_ADD_FILTER +}; + + +#define LZ_RESTORE \ +{ \ + _reps[0] = rep0; \ + _winPos = (size_t)(winPos - _window); \ + _buf_Res = _bitStream._buf; \ + _bitPos_Res = _bitStream._bitPos; \ } +#define LZ_LOOP_BREAK_OK { break; } +// #define LZ_LOOP_BREAK_ERROR { _lzError = LZ_ERROR_TYPE_SYM; break; } +// #define LZ_LOOP_BREAK_ERROR { LZ_RESTORE; return S_FALSE; } +#define LZ_LOOP_BREAK_ERROR { goto decode_error; } +// goto decode_error; } +// #define LZ_LOOP_BREAK_ERROR { break; } -static const UInt32 kSymbolRep = 258; -// static const unsigned kMaxMatchLen = 0x1001 + 3; +#define Z7_RAR_HUFF_DECODE_CHECK_break(sym, huf, kNumTableBits, bitStream) \ + Z7_HUFF_DECODE_CHECK(sym, huf, kNumHufBits, kNumTableBits, bitStream, { LZ_LOOP_BREAK_ERROR }) -HRESULT CDecoder::DecodeLZ() + +/* + DecodeLZ2() will stop decoding if it reaches limit when (_winPos >= _limit) + at return: + (_winPos < _limit + kMaxMatchLen) + also it can write up to (COPY_CHUNK_SIZE - 1) additional junk bytes after (_winPos). +*/ +HRESULT CDecoder::DecodeLZ2(const CBitDecoder &bitStream) throw() { - CBitDecoder _bitStream; - _bitStream._stream = _inStream; - _bitStream._bufBase = _inputBuf; - _bitStream.Init(); +#if 0 + Byte k_LenPlusTable_LOC[DICT_SIZE_BITS_MAX]; + memcpy(k_LenPlusTable_LOC, k_LenPlusTable, sizeof(k_LenPlusTable)); +#endif - UInt32 rep0 = _reps[0]; + PRINT_CNT("DecodeLZ2", 2000); - UInt32 remLen = 0; + CBitDecoder _bitStream; + _bitStream.CopyFrom(bitStream); + // _bitStream._stream = _inStream; + // _bitStream._bufBase = _inputBuf; + // _bitStream.Init(); + + // _reps[*] can be larger than _winSize, if _winSize was reduced in solid stream. + size_t rep0 = _reps[0]; + // size_t rep1 = _reps[1]; + // Byte *win = _window; + Byte *winPos = _window + _winPos; + const Byte *limit = _window + _limit; + _exitType = Z7_RAR_EXIT_TYPE_NONE; - size_t limit; - { - size_t rem = _winSize - _winPos; - if (rem > kWriteStep) - rem = kWriteStep; - limit = _winPos + rem; - } - for (;;) { - if (_winPos >= limit) - { - RINOK(WriteBuf()); - if (_unpackSize_Defined && _writtenFileSize > _unpackSize) - break; // return S_FALSE; - - { - size_t rem = _winSize - _winPos; - - if (rem == 0) - { - _winPos = 0; - rem = _winSize; - } - if (rem > kWriteStep) - rem = kWriteStep; - limit = _winPos + rem; - } - - if (remLen != 0) - { - size_t winPos = _winPos; - size_t winMask = _winMask; - size_t pos = (winPos - (size_t)rep0 - 1) & winMask; - - Byte *win = _window; - do - { - if (winPos >= limit) - break; - win[winPos] = win[pos]; - winPos++; - pos = (pos + 1) & winMask; - } - while (--remLen != 0); - - _lzSize += winPos - _winPos; - _winPos = winPos; - continue; - } - } - - if (_bitStream._buf >= _bitStream._bufCheck2) + if (winPos >= limit) + LZ_LOOP_BREAK_OK + // (winPos < limit) + if (_bitStream._buf >= _bitStream._bufCheck_Block) { if (_bitStream.InputEofError()) - break; // return S_FALSE; + LZ_LOOP_BREAK_OK if (_bitStream._buf >= _bitStream._bufCheck) - _bitStream.Prepare2(); - - UInt64 processed = _bitStream.GetProcessedSize_Round(); - if (processed >= _bitStream._blockEnd) { - if (processed > _bitStream._blockEnd) - break; // return S_FALSE; - { - unsigned bits7 = _bitStream.GetProcessedBits7(); - if (bits7 > _bitStream._blockEndBits7) - break; // return S_FALSE; - if (bits7 == _bitStream._blockEndBits7) - { - if (_isLastBlock) - { - _reps[0] = rep0; - - if (_bitStream.InputEofError()) - break; - - /* - // packSize can be 15 bytes larger for encrypted archive - if (_packSize_Defined && _packSize < _bitStream.GetProcessedSize()) - break; - */ - - return _bitStream._hres; - // break; - } - RINOK(ReadTables(_bitStream)); - continue; - } - } + if (!_bitStream._wasFinished) + LZ_LOOP_BREAK_OK + // _bitStream._wasFinished == true + // we don't need Prepare() here, because all data was read + // and PadZone (16 bytes) after data was filled. } + const UInt64 processed = _bitStream.GetProcessedSize_Round(); + // some cases are error, but the caller will process such error cases. + if (processed >= _bitStream._blockEnd && + (processed > _bitStream._blockEnd + || _bitStream.GetProcessedBits7() >= _bitStream._blockEndBits7)) + LZ_LOOP_BREAK_OK + // that check is not required, but it can help, if there is BUG in another code + if (!_tableWasFilled) + LZ_LOOP_BREAK_ERROR } - - UInt32 sym = m_MainDecoder.Decode(&_bitStream); + +#if 0 + const unsigned sym = m_MainDecoder.Decode(&_bitStream); +#else + unsigned sym; + Z7_RAR_HUFF_DECODE_CHECK_break(sym, &m_MainDecoder, k_NumHufTableBits_Main, &_bitStream) +#endif if (sym < 256) { - size_t winPos = _winPos; - _window[winPos] = (Byte)sym; - _winPos = winPos + 1; - _lzSize++; + *winPos++ = (Byte)sym; + // _lzSize++; continue; } - UInt32 len; + CLenType len; if (sym < kSymbolRep + kNumReps) { @@ -621,130 +1443,352 @@ HRESULT CDecoder::DecodeLZ() { if (sym != kSymbolRep) { - UInt32 dist; - if (sym == kSymbolRep + 1) - dist = _reps[1]; - else - { - if (sym == kSymbolRep + 2) - dist = _reps[2]; - else - { - dist = _reps[3]; - _reps[3] = _reps[2]; - } - _reps[2] = _reps[1]; - } + size_t dist = _reps[1]; _reps[1] = rep0; rep0 = dist; + if (sym >= kSymbolRep + 2) + { + #if 1 + rep0 = _reps[(size_t)sym - kSymbolRep]; + _reps[(size_t)sym - kSymbolRep] = _reps[2]; + _reps[2] = dist; + #else + if (sym != kSymbolRep + 2) + { + rep0 = _reps[3]; + _reps[3] = _reps[2]; + _reps[2] = dist; + } + else + { + rep0 = _reps[2]; + _reps[2] = dist; + } + #endif + } } - - const UInt32 sym2 = m_LenDecoder.Decode(&_bitStream); - if (sym2 >= kLenTableSize) - break; // return S_FALSE; - len = SlotToLen(_bitStream, sym2); +#if 0 + len = m_LenDecoder.Decode(&_bitStream); + if (len >= kLenTableSize) + LZ_LOOP_BREAK_ERROR +#else + Z7_RAR_HUFF_DECODE_CHECK_break(len, &m_LenDecoder, k_NumHufTableBits_Len, &_bitStream) +#endif + if (len >= 8) + len = SlotToLen(_bitStream, len); + len += 2; + // _lastLen = (UInt32)len; } - else + else if (sym != 256) { - if (sym == 256) + len = (CLenType)_lastLen; + if (len == 0) { - RINOK(AddFilter(_bitStream)); + // we ignore (_lastLen == 0) case, like original-unrar. + // that case can mean error in stream. + // lzError = true; + // return S_FALSE; continue; } - else // if (sym == 257) - { - len = _lastLen; - // if (len = 0), we ignore that symbol, like original unRAR code, but it can mean error in stream. - // if (len == 0) return S_FALSE; - if (len == 0) - continue; - } + } + else + { + _exitType = Z7_RAR_EXIT_TYPE_ADD_FILTER; + LZ_LOOP_BREAK_OK } } +#if 0 else if (sym >= kMainTableSize) - break; // return S_FALSE; + LZ_LOOP_BREAK_ERROR +#endif else { _reps[3] = _reps[2]; _reps[2] = _reps[1]; _reps[1] = rep0; - len = SlotToLen(_bitStream, sym - (kSymbolRep + kNumReps)); - - rep0 = m_DistDecoder.Decode(&_bitStream); + len = sym - (kSymbolRep + kNumReps); + if (len >= 8) + len = SlotToLen(_bitStream, len); + len += 2; + // _lastLen = (UInt32)len; +#if 0 + rep0 = (UInt32)m_DistDecoder.Decode(&_bitStream); +#else + Z7_RAR_HUFF_DECODE_CHECK_break(rep0, &m_DistDecoder, k_NumHufTableBits_Dist, &_bitStream) +#endif + if (rep0 >= 4) { - if (rep0 >= _numCorrectDistSymbols) - break; // return S_FALSE; - unsigned numBits = (rep0 >> 1) - 1; +#if 0 + if (rep0 >= kDistTableSize_MAX) + LZ_LOOP_BREAK_ERROR +#endif + const unsigned numBits = ((unsigned)rep0 - 2) >> 1; rep0 = (2 | (rep0 & 1)) << numBits; - + + const Byte *buf = _bitStream._buf; +#ifdef Z7_RAR5_USE_64BIT + const UInt64 v = GetBe64(buf); +#else + const UInt32 v = GetBe32(buf); +#endif + + // _lastLen = (UInt32)len; if (numBits < kNumAlignBits) - rep0 += _bitStream.ReadBits9(numBits); + { + rep0 += // _bitStream.ReadBits9(numBits); + _bitStream.ReadBits_Big25(numBits, v); + } else { - len += (numBits >= 7); - len += (numBits >= 12); - len += (numBits >= 17); - + #if !defined(MY_CPU_AMD64) + len += k_LenPlusTable[numBits]; + #elif 0 + len += k_LenPlusTable_LOC[numBits]; + #elif 1 + len += m_LenPlusTable[numBits]; + #elif 1 && defined(MY_CPU_64BIT) && defined(MY_CPU_AMD64) + // len += (unsigned)((UInt64)0xfffffffeaa554000 >> (numBits * 2)) & 3; + len += (unsigned)((UInt64)0xfffffffffeaa5540 >> (numBits * 2 - 8)) & 3; + #elif 1 + len += 3; + len -= (unsigned)(numBits - 7) >> (sizeof(unsigned) * 8 - 1); + len -= (unsigned)(numBits - 12) >> (sizeof(unsigned) * 8 - 1); + len -= (unsigned)(numBits - 17) >> (sizeof(unsigned) * 8 - 1); + #elif 1 + len += 3; + len -= (0x155aabf >> (numBits - 4) >> (numBits - 4)) & 3; + #elif 1 + len += (numBits >= 7); + len += (numBits >= 12); + len += (numBits >= 17); + #endif + // _lastLen = (UInt32)len; if (_useAlignBits) { // if (numBits > kNumAlignBits) - rep0 += (_bitStream.ReadBits32(numBits - kNumAlignBits) << kNumAlignBits); - UInt32 a = m_AlignDecoder.Decode(&_bitStream); + rep0 += (_bitStream.ReadBits_Big25(numBits - kNumAlignBits, v) << kNumAlignBits); +#if 0 + const unsigned a = m_AlignDecoder.Decode(&_bitStream); if (a >= kAlignTableSize) - break; // return S_FALSE; + LZ_LOOP_BREAK_ERROR +#else + unsigned a; + Z7_RAR_HUFF_DECODE_CHECK_break(a, &m_AlignDecoder, k_NumHufTableBits_Align, &_bitStream) +#endif rep0 += a; } else - rep0 += _bitStream.ReadBits32(numBits); + rep0 += _bitStream.ReadBits_Big(numBits, v); +#ifndef Z7_RAR5_USE_64BIT + if (numBits >= 30) // we don't want 32-bit overflow case + rep0 = (size_t)0 - 1 - 1; +#endif } } + rep0++; } - _lastLen = len; - - if (rep0 >= _lzSize) - _lzError = true; - { - UInt32 lenCur = len; - size_t winPos = _winPos; - size_t pos = (winPos - (size_t)rep0 - 1) & _winMask; + _lastLen = (UInt32)len; + // len != 0 + +#ifdef Z7_RAR5_SHOW_STAT { - size_t rem = limit - winPos; - // size_t rem = _winSize - winPos; + size_t index = rep0; + if (index >= kNumStats1) + index = kNumStats1 - 1; + g_stats1[index]++; + g_stats2[index][len]++; + } +#endif - if (lenCur > rem) + Byte *dest = winPos; + winPos += len; + if (rep0 <= _dictSize_forCheck) + { + const Byte *src; + const size_t winPos_temp = (size_t)(dest - _window); + if (rep0 > winPos_temp) { - lenCur = (UInt32)rem; - remLen = len - lenCur; + if (_lzSize == 0) + goto error_dist; + size_t back = rep0 - winPos_temp; + // STAT_INC(g_NumOver) + src = dest + (_winSize - rep0); + if (back < len) + { + // len -= (CLenType)back; + Z7_PRAGMA_OPT_DISABLE_LOOP_UNROLL_VECTORIZE + do + *dest++ = *src++; + while (--back); + src = dest - rep0; + } } + else + src = dest - rep0; + CopyMatch(rep0, dest, src, winPos); + continue; } - - Byte *win = _window; - _lzSize += lenCur; - _winPos = winPos + lenCur; - if (_winSize - pos >= lenCur) + +error_dist: + // LZ_LOOP_BREAK_ERROR; + _lzError = LZ_ERROR_TYPE_DIST; + do + *dest++ = 0; + while (dest < winPos); + continue; + } + } + + LZ_RESTORE + return S_OK; + +#if 1 +decode_error: + /* + if (_bitStream._hres != S_OK) + return _bitStream._hres; + */ + LZ_RESTORE + return S_FALSE; +#endif +} + + + +/* +input conditions: + _winPos < _winSize +return: + _winPos < _winSize is expected, if (return_res == S_OK) + _winPos >= _winSize is possible in (return_res != S_OK) +*/ +HRESULT CDecoder::DecodeLZ() +{ + CBitDecoder _bitStream; + _bitStream._stream = _inStream; + _bitStream._bufBase = _inputBuf; + _bitStream.Init(); + + // _reps[*] can be larger than _winSize, if _winSize was reduced in solid stream. + size_t winPos = _winPos; + Byte *win = _window; + size_t limit; + { + size_t rem = _winSize - winPos; + if (rem > kWriteStep) + rem = kWriteStep; + limit = winPos + rem; + } + + for (;;) + { + if (winPos >= limit) + { + _winPos = winPos < _winSize ? winPos : _winSize; + // _winPos == min(winPos, _winSize) + // we will not write data after _winSize + RINOK(WriteBuf()) + if (_unpackSize_Defined && _writtenFileSize > _unpackSize) + break; // return S_FALSE; + const size_t wp = _winPos; + size_t rem = _winSize - wp; + if (rem == 0) { - const Byte *src = win + pos; - Byte *dest = win + winPos; - do - *dest++ = *src++; - while (--lenCur != 0); + _lzSize += wp; + winPos -= wp; + // (winPos < kMaxMatchLen < _winSize) + // so memmove is not required here + if (winPos) + memcpy(win, win + _winSize, winPos); + limit = _winSize; + if (limit >= kWriteStep) + { + limit = kWriteStep; + continue; + } + rem = _winSize - winPos; } - else + if (rem > kWriteStep) + rem = kWriteStep; + limit = winPos + rem; + continue; + } + + // (winPos < limit) + + if (_bitStream._buf >= _bitStream._bufCheck_Block) + { + _winPos = winPos; + if (_bitStream.InputEofError()) + break; // return S_FALSE; + _bitStream.Prepare(); + + const UInt64 processed = _bitStream.GetProcessedSize_Round(); + if (processed >= _bitStream._blockEnd) { - do + if (processed > _bitStream._blockEnd) + break; // return S_FALSE; { - win[winPos] = win[pos]; - winPos++; - pos = (pos + 1) & _winMask; + const unsigned bits7 = _bitStream.GetProcessedBits7(); + if (bits7 >= _bitStream._blockEndBits7) + { + if (bits7 > _bitStream._blockEndBits7) + { +#if 1 + // we ignore thar error as original unrar + _bitStream._minorError = true; +#else + break; // return S_FALSE; +#endif + } + _bitStream.AlignToByte(); + // if (!_bitStream.AlignToByte()) break; + if (_isLastBlock) + { + if (_bitStream.InputEofError()) + break; + /* + // packSize can be 15 bytes larger for encrypted archive + if (_packSize_Defined && _packSize < _bitStream.GetProcessedSize()) + break; + */ + if (_bitStream._minorError) + return S_FALSE; + return _bitStream._hres; + // break; + } + RINOK(ReadTables(_bitStream)) + continue; + } } - while (--lenCur != 0); } + + // end of block was not reached. + // so we must decode more symbols + // that check is not required, but it can help, if there is BUG in another code + if (!_tableWasFilled) + break; // return S_FALSE; + } + + _limit = limit; + _winPos = winPos; + RINOK(DecodeLZ2(_bitStream)) + _bitStream._buf = _buf_Res; + _bitStream._bitPos = _bitPos_Res; + + winPos = _winPos; + if (_exitType == Z7_RAR_EXIT_TYPE_ADD_FILTER) + { + RINOK(AddFilter(_bitStream)) + continue; } } + + _winPos = winPos; if (_bitStream._hres != S_OK) return _bitStream._hres; @@ -753,214 +1797,263 @@ HRESULT CDecoder::DecodeLZ() } + HRESULT CDecoder::CodeReal() { _unsupportedFilter = false; - _lzError = false; _writeError = false; - + /* if (!_isSolid || !_wasInit) { - size_t clearSize = _winSize; - if (_lzSize < _winSize) - clearSize = (size_t)_lzSize; - memset(_window, 0, clearSize); - _wasInit = true; - _lzSize = 0; + // _lzSize = 0; _lzWritten = 0; _winPos = 0; - for (unsigned i = 0; i < kNumReps; i++) - _reps[i] = (UInt32)0 - 1; - + _reps[i] = (size_t)0 - 1; _lastLen = 0; _tableWasFilled = false; } - + */ _isLastBlock = false; InitFilters(); _filterEnd = 0; _writtenFileSize = 0; - - _lzFileStart = _lzSize; - _lzWritten = _lzSize; + const UInt64 lzSize = _lzSize + _winPos; + _lzFileStart = lzSize; + _lzWritten = lzSize; HRESULT res = DecodeLZ(); HRESULT res2 = S_OK; if (!_writeError && res != E_OUTOFMEMORY) res2 = WriteBuf(); - /* if (res == S_OK) if (InputEofError()) res = S_FALSE; */ - if (res == S_OK) + { + // _solidAllowed = true; res = res2; - + } if (res == S_OK && _unpackSize_Defined && _writtenFileSize != _unpackSize) return S_FALSE; return res; } -// Original unRAR claims that maximum possible filter block size is (1 << 16) now, -// and (1 << 17) is minimum win size required to support filter. -// Original unRAR uses (1 << 18) for "extra safety and possible filter area size expansion" -// We can use any win size. - -static const unsigned kWinSize_Log_Min = 17; -STDMETHODIMP CDecoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 * /* inSize */, const UInt64 *outSize, ICompressProgressInfo *progress) +Z7_COM7F_IMF(CDecoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, + const UInt64 * /* inSize */, const UInt64 *outSize, ICompressProgressInfo *progress)) { - try + _lzError = LZ_ERROR_TYPE_NO; +/* + if file is soild, but decoding of previous file was not finished, + we still try to decode new file. + We need correct huffman table at starting block. + And rar encoder probably writes huffman table at start block, if file is big. + So we have good chance to get correct huffman table in some file after corruption. + Also we try to recover window by filling zeros, if previous file + was decoded to smaller size than required. + But if filling size is big, we do full reset of window instead. +*/ + #define Z7_RAR_RECOVER_SOLID_LIMIT (1 << 20) + // #define Z7_RAR_RECOVER_SOLID_LIMIT 0 // do not fill zeros { - if (_dictSizeLog >= sizeof(size_t) * 8) - return E_NOTIMPL; - - if (!_isSolid) - _lzEnd = 0; + // if (_winPos > 100) _winPos -= 100; // for debug: corruption + const UInt64 lzSize = _lzSize + _winPos; +/* + if previous file was decoded with error or for some another cases, then + (lzSize > _lzEnd) is possible + (_winPos > _winSize) is possible + (_winPos < _winSize + kMaxMatchLen) +*/ + if (!_window + || !_isSolid + || !_wasInit + || (lzSize < _lzEnd +#if Z7_RAR_RECOVER_SOLID_LIMIT != 0 + && lzSize + Z7_RAR_RECOVER_SOLID_LIMIT < _lzEnd +#endif + )) + { + if (_isSolid) + _lzError = LZ_ERROR_TYPE_HEADER; + _lzSize = 0; + // _lzEnd = 0; // it will be set later + // _lzWritten = 0; // it will be set later + _winPos = 0; + for (unsigned i = 0; i < kNumReps; i++) + _reps[i] = (size_t)0 - 1; + _lastLen = 0; + _tableWasFilled = false; + _wasInit = true; + } else { - if (_lzSize < _lzEnd) + const size_t ws = _winSize; + if (_winPos >= ws) { - if (_window) + // we must normalize (_winPos) and data in _window, + _winPos -= ws; + _lzSize += ws; + // (_winPos < kMaxMatchLen < _winSize) + // if (_window) + memcpy(_window, _window + ws, _winPos); // memmove is not required here + } + +#if Z7_RAR_RECOVER_SOLID_LIMIT != 0 + if (lzSize < _lzEnd) + { +#if 0 + return S_FALSE; +#else + // we can report that recovering was made: + // _lzError = LZ_ERROR_TYPE_HEADER; + // We write zeros to area after corruption: + // if (_window) { - UInt64 rem = _lzEnd - _lzSize; - if (rem >= _winSize) - memset(_window, 0, _winSize); + UInt64 rem = _lzEnd - lzSize; + if (rem >= ws) + { + My_ZeroMemory(_window, ws); + _lzSize = ws; + _winPos = 0; + } else { - size_t pos = (size_t)_lzSize & _winSize; - size_t rem2 = _winSize - pos; - if (rem2 > rem) - rem2 = (size_t)rem; - memset(_window + pos, 0, rem2); - rem -= rem2; - memset(_window, 0, (size_t)rem); + // rem < _winSize + // _winPos <= ws + const size_t cur = ws - _winPos; + if (cur <= rem) + { + rem -= cur; + My_ZeroMemory(_window + _winPos, cur); + _lzSize = ws; + _winPos = 0; + } + My_ZeroMemory(_window + _winPos, (size_t)rem); + _winPos += (size_t)rem; } } - _lzEnd &= ((((UInt64)1) << 33) - 1); - _lzSize = _lzEnd; - _winPos = (size_t)(_lzSize & _winSize); + // else return S_FALSE; +#endif } - _lzEnd = _lzSize; } +#endif + } - size_t newSize; + // _winPos < _winSize + // we don't want _lzSize overflow + if (_lzSize >= DICT_SIZE_MAX) + _lzSize = DICT_SIZE_MAX; + _lzEnd = _lzSize + _winPos; + // _lzSize <= DICT_SIZE_MAX + // _lzEnd < DICT_SIZE_MAX + _winSize + + size_t newSize = _dictSize; + if (newSize < kWinSize_Min) + newSize = kWinSize_Min; + + _unpackSize = 0; + _unpackSize_Defined = (outSize != NULL); + if (_unpackSize_Defined) + _unpackSize = *outSize; + + if ((Int64)_unpackSize >= 0) + _lzEnd += _unpackSize; // known end after current file + else + _lzEnd = 0; // unknown end + + if (_isSolid && _window) + { + // If dictionary was decreased in solid, we use old dictionary. + if (newSize > _dictSize_forCheck) { - unsigned newSizeLog = _dictSizeLog; - if (newSizeLog < kWinSize_Log_Min) - newSizeLog = kWinSize_Log_Min; - newSize = (size_t)1 << newSizeLog; - _numCorrectDistSymbols = newSizeLog * 2; + // If dictionary was increased in solid, we don't want grow. + return S_FALSE; // E_OUTOFMEMORY } - - // If dictionary was reduced, we use allocated dictionary block - // for compatibility with original unRAR decoder. - - if (_window && newSize < _winSizeAllocated) - _winSize = _winSizeAllocated; - else if (!_window || _winSize != newSize) + // (newSize <= _dictSize_forCheck) + } + else + { + // !_isSolid || !_window + _dictSize_forCheck = newSize; { - if (!_isSolid) - { - ::MidFree(_window); - _window = NULL; - _winSizeAllocated = 0; - } - - Byte *win; - - { - win = (Byte *)::MidAlloc(newSize); - if (!win) - return E_OUTOFMEMORY; - memset(win, 0, newSize); - } - - if (_isSolid && _window) - { - // original unRAR claims: - // "Archiving code guarantees that win size does not grow in the same solid stream", - // but the original unRAR decoder still supports such grow case. - - Byte *winOld = _window; - size_t oldSize = _winSize; - size_t newMask = newSize - 1; - size_t oldMask = _winSize - 1; - size_t winPos = _winPos; - for (size_t i = 1; i <= oldSize; i++) - win[(winPos - i) & newMask] = winOld[(winPos - i) & oldMask]; - ::MidFree(_window); - } - - _window = win; - _winSizeAllocated = newSize; - _winSize = newSize; + size_t newSize_small = newSize; + const size_t k_Win_AlignSize = 1u << 18; + /* here we add (1 << 7) instead of (COPY_CHUNK_SIZE - 1), because + we want to get same (_winSize) for different COPY_CHUNK_SIZE values. */ + // newSize += (COPY_CHUNK_SIZE - 1) + (k_Win_AlignSize - 1); // for debug : we can get smallest (_winSize) + newSize += (1 << 7) + k_Win_AlignSize; + newSize &= ~(size_t)(k_Win_AlignSize - 1); + if (newSize < newSize_small) + return E_OUTOFMEMORY; } - - _winMask = _winSize - 1; - _winPos &= _winMask; - - if (!_inputBuf) + // (!_isSolid || !_window) + const size_t allocSize = newSize + kMaxMatchLen + 64; + if (allocSize < newSize) + return E_OUTOFMEMORY; + if (!_window || allocSize > _winSize_Allocated) { - _inputBuf = (Byte *)::MidAlloc(kInputBufSize); - if (!_inputBuf) + Z7_RAR_FREE_WINDOW + _window = NULL; + _winSize_Allocated = 0; + Byte *win = (Byte *)::BigAlloc(allocSize); + if (!win) return E_OUTOFMEMORY; + _window = win; + _winSize_Allocated = allocSize; } - - _inStream = inStream; - _outStream = outStream; - - /* - _packSize = 0; - _packSize_Defined = (inSize != NULL); - if (_packSize_Defined) - _packSize = *inSize; - */ - - _unpackSize = 0; - _unpackSize_Defined = (outSize != NULL); - if (_unpackSize_Defined) - _unpackSize = *outSize; - - if ((Int64)_unpackSize >= 0) - _lzEnd += _unpackSize; - else - _lzEnd = 0; - - _progress = progress; - - HRESULT res = CodeReal(); - - if (res != S_OK) - return res; - if (_lzError) - return S_FALSE; - if (_unsupportedFilter) - return E_NOTIMPL; - return S_OK; + _winSize = newSize; + } + + if (!_inputBuf) + { + _inputBuf = (Byte *)z7_AlignedAlloc(kInputBufSize + kInputBufferPadZone); + if (!_inputBuf) + return E_OUTOFMEMORY; } - // catch(const CInBufferException &e) { return e.ErrorCode; } - // catch(...) { return S_FALSE; } - catch(...) { return E_OUTOFMEMORY; } - // CNewException is possible here. But probably CNewException is caused - // by error in data stream. + + _inStream = inStream; + _outStream = outStream; + _progress = progress; + _progress_Pack = 0; + _progress_Unpack = 0; + + const HRESULT res = CodeReal(); + + if (res != S_OK) + return res; + // _lzError = LZ_ERROR_TYPE_HEADER; // for debug + if (_lzError) + return S_FALSE; + if (_unsupportedFilter) + return E_NOTIMPL; + return S_OK; } -STDMETHODIMP CDecoder::SetDecoderProperties2(const Byte *data, UInt32 size) + +Z7_COM7F_IMF(CDecoder::SetDecoderProperties2(const Byte *data, UInt32 size)) { if (size != 2) + return E_INVALIDARG; + const unsigned pow = data[0]; + const unsigned b1 = data[1]; + const unsigned frac = b1 >> 3; + // unsigned pow = 15 + 8; + // unsigned frac = 1; + if (pow + ((frac + 31) >> 5) > MAX_DICT_LOG - 17) + // if (frac + (pow << 8) >= ((8 * 2 + 7) << 5) + 8 / 8) return E_NOTIMPL; - _dictSizeLog = (Byte)((data[0] & 0xF) + 17); - _isSolid = ((data[1] & 1) != 0); + _dictSize = (size_t)(frac + 32) << (pow + 12); + _isSolid = (b1 & 1) != 0; + _is_v7 = (b1 & 2) != 0; + // printf("\ndict size = %p\n", (void *)(size_t)_dictSize); return S_OK; } diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/Rar5Decoder.h b/src/plugins/7zip/7za/cpp/7zip/Compress/Rar5Decoder.h index b0a4dd12..66c1c3c2 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/Rar5Decoder.h +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/Rar5Decoder.h @@ -2,16 +2,11 @@ // According to unRAR license, this code may not be used to develop // a program that creates RAR archives -#ifndef __COMPRESS_RAR5_DECODER_H -#define __COMPRESS_RAR5_DECODER_H +#ifndef ZIP7_INC_COMPRESS_RAR5_DECODER_H +#define ZIP7_INC_COMPRESS_RAR5_DECODER_H -#include "../../../C/Alloc.h" -#include "../../../C/CpuArch.h" - -#include "../../Common/MyBuffer.h" +#include "../../Common/MyBuffer2.h" #include "../../Common/MyCom.h" -#include "../../Common/MyException.h" -#include "../../Common/MyVector.h" #include "../ICoder.h" @@ -20,200 +15,7 @@ namespace NCompress { namespace NRar5 { -class CMidBuffer -{ - Byte *_data; - size_t _size; - - CLASS_NO_COPY(CMidBuffer) - -public: - CMidBuffer(): _data(NULL), _size(0) {}; - ~CMidBuffer() { ::MidFree(_data); } - - bool IsAllocated() const { return _data != NULL; } - operator Byte *() { return _data; } - operator const Byte *() const { return _data; } - size_t Size() const { return _size; } - - void AllocAtLeast(size_t size) - { - if (size > _size) - { - const size_t kMinSize = (1 << 16); - if (size < kMinSize) - size = kMinSize; - ::MidFree(_data); - _data = (Byte *)::MidAlloc(size); - _size = size; - } - } -}; - -/* -struct CInBufferException: public CSystemException -{ - CInBufferException(HRESULT errorCode): CSystemException(errorCode) {} -}; -*/ - -class CBitDecoder -{ -public: - const Byte *_buf; - unsigned _bitPos; - bool _wasFinished; - Byte _blockEndBits7; - const Byte *_bufCheck2; - const Byte *_bufCheck; - Byte *_bufLim; - Byte *_bufBase; - - UInt64 _processedSize; - UInt64 _blockEnd; - - ISequentialInStream *_stream; - HRESULT _hres; - - void SetCheck2() - { - _bufCheck2 = _bufCheck; - if (_bufCheck > _buf) - { - UInt64 processed = GetProcessedSize_Round(); - if (_blockEnd < processed) - _bufCheck2 = _buf; - else - { - UInt64 delta = _blockEnd - processed; - if ((size_t)(_bufCheck - _buf) > delta) - _bufCheck2 = _buf + (size_t)delta; - } - } - } - - bool IsBlockOverRead() const - { - UInt64 v = GetProcessedSize_Round(); - if (v < _blockEnd) - return false; - if (v > _blockEnd) - return true; - return _bitPos > _blockEndBits7; - } - - /* - CBitDecoder() throw(): - _buf(0), - _bufLim(0), - _bufBase(0), - _stream(0), - _processedSize(0), - _wasFinished(false) - {} - */ - - void Init() throw() - { - _blockEnd = 0; - _blockEndBits7 = 0; - - _bitPos = 0; - _processedSize = 0; - _buf = _bufBase; - _bufLim = _bufBase; - _bufCheck = _buf; - _bufCheck2 = _buf; - _wasFinished = false; - } - - void Prepare2() throw(); - - void Prepare() throw() - { - if (_buf >= _bufCheck) - Prepare2(); - } - - bool ExtraBitsWereRead() const - { - return _buf >= _bufLim && (_buf > _bufLim || _bitPos != 0); - } - - bool InputEofError() const { return ExtraBitsWereRead(); } - - unsigned GetProcessedBits7() const { return _bitPos; } - UInt64 GetProcessedSize_Round() const { return _processedSize + (_buf - _bufBase); } - UInt64 GetProcessedSize() const { return _processedSize + (_buf - _bufBase) + ((_bitPos + 7) >> 3); } - - void AlignToByte() - { - _buf += (_bitPos + 7) >> 3; - _bitPos = 0; - } - - Byte ReadByteInAligned() - { - return *_buf++; - } - - UInt32 GetValue(unsigned numBits) - { - UInt32 v = ((UInt32)_buf[0] << 16) | ((UInt32)_buf[1] << 8) | (UInt32)_buf[2]; - v >>= (24 - numBits - _bitPos); - return v & ((1 << numBits) - 1); - } - - void MovePos(unsigned numBits) - { - _bitPos += numBits; - _buf += (_bitPos >> 3); - _bitPos &= 7; - } - - UInt32 ReadBits9(unsigned numBits) - { - const Byte *buf = _buf; - UInt32 v = ((UInt32)buf[0] << 8) | (UInt32)buf[1]; - v &= ((UInt32)0xFFFF >> _bitPos); - numBits += _bitPos; - v >>= (16 - numBits); - _buf = buf + (numBits >> 3); - _bitPos = numBits & 7; - return v; - } - - UInt32 ReadBits9fix(unsigned numBits) - { - const Byte *buf = _buf; - UInt32 v = ((UInt32)buf[0] << 8) | (UInt32)buf[1]; - UInt32 mask = ((1 << numBits) - 1); - numBits += _bitPos; - v >>= (16 - numBits); - _buf = buf + (numBits >> 3); - _bitPos = numBits & 7; - return v & mask; - } - - UInt32 ReadBits32(unsigned numBits) - { - UInt32 mask = ((1 << numBits) - 1); - numBits += _bitPos; - const Byte *buf = _buf; - UInt32 v = GetBe32(buf); - if (numBits > 32) - { - v <<= (numBits - 32); - v |= (UInt32)buf[4] >> (40 - numBits); - } - else - v >>= (32 - numBits); - _buf = buf + (numBits >> 3); - _bitPos = numBits & 7; - return v & mask; - } -}; - +class CBitDecoder; struct CFilter { @@ -227,38 +29,59 @@ struct CFilter const unsigned kNumReps = 4; const unsigned kLenTableSize = 11 * 4; const unsigned kMainTableSize = 256 + 1 + 1 + kNumReps + kLenTableSize; -const unsigned kDistTableSize = 64; +const unsigned kExtraDistSymbols_v7 = 16; +const unsigned kDistTableSize_v6 = 64; +const unsigned kDistTableSize_MAX = 64 + kExtraDistSymbols_v7; const unsigned kNumAlignBits = 4; -const unsigned kAlignTableSize = (1 << kNumAlignBits); -const unsigned kLevelTableSize = 20; -const unsigned kTablesSizesSum = kMainTableSize + kDistTableSize + kAlignTableSize + kLenTableSize; +const unsigned kAlignTableSize = 1 << kNumAlignBits; -const unsigned kNumHuffmanBits = 15; +const unsigned kNumHufBits = 15; -class CDecoder: - public ICompressCoder, - public ICompressSetDecoderProperties2, - public CMyUnknownImp -{ +const unsigned k_NumHufTableBits_Main = 10; +const unsigned k_NumHufTableBits_Dist = 7; +const unsigned k_NumHufTableBits_Len = 7; +const unsigned k_NumHufTableBits_Align = 6; + +const unsigned DICT_SIZE_BITS_MAX = 40; + +Z7_CLASS_IMP_NOQIB_2( + CDecoder + , ICompressCoder + , ICompressSetDecoderProperties2 +) bool _useAlignBits; bool _isLastBlock; bool _unpackSize_Defined; // bool _packSize_Defined; bool _unsupportedFilter; - bool _lzError; + Byte _lzError; bool _writeError; - - // CBitDecoder _bitStream; + + bool _isSolid; + // bool _solidAllowed; + bool _is_v7; + bool _tableWasFilled; + bool _wasInit; + + Byte _exitType; + + // Byte _dictSizeLog; + size_t _dictSize; Byte *_window; size_t _winPos; size_t _winSize; - size_t _winMask; - + size_t _dictSize_forCheck; + size_t _limit; + const Byte *_buf_Res; UInt64 _lzSize; + size_t _reps[kNumReps]; + unsigned _bitPos_Res; + UInt32 _lastLen; - unsigned _numCorrectDistSymbols; + // unsigned _numCorrectDistSymbols; unsigned _numUnusedFilters; + unsigned _numFilters; UInt64 _lzWritten; UInt64 _lzFileStart; @@ -266,68 +89,42 @@ class CDecoder: // UInt64 _packSize; UInt64 _lzEnd; UInt64 _writtenFileSize; - size_t _winSizeAllocated; - - Byte _dictSizeLog; - bool _tableWasFilled; - bool _isSolid; - bool _wasInit; - - UInt32 _reps[kNumReps]; - UInt32 _lastLen; - UInt64 _filterEnd; - CMidBuffer _filterSrc; - CMidBuffer _filterDst; - - CRecordVector _filters; + UInt64 _progress_Pack; + UInt64 _progress_Unpack; + CAlignedBuffer _filterSrc; + CAlignedBuffer _filterDst; + CFilter *_filters; + size_t _winSize_Allocated; ISequentialInStream *_inStream; ISequentialOutStream *_outStream; ICompressProgressInfo *_progress; Byte *_inputBuf; - NHuffman::CDecoder m_MainDecoder; - NHuffman::CDecoder m_DistDecoder; - NHuffman::CDecoder m_AlignDecoder; - NHuffman::CDecoder m_LenDecoder; - NHuffman::CDecoder m_LevelDecoder; - + NHuffman::CDecoder m_MainDecoder; + NHuffman::CDecoder256 m_DistDecoder; + NHuffman::CDecoder256 m_AlignDecoder; + NHuffman::CDecoder256 m_LenDecoder; + Byte m_LenPlusTable[DICT_SIZE_BITS_MAX]; void InitFilters() { _numUnusedFilters = 0; - _filters.Clear(); - } - - void DeleteUnusedFilters() - { - if (_numUnusedFilters != 0) - { - _filters.DeleteFrontal(_numUnusedFilters); - _numUnusedFilters = 0; - } + _numFilters = 0; } - + void DeleteUnusedFilters(); HRESULT WriteData(const Byte *data, size_t size); HRESULT ExecuteFilter(const CFilter &f); HRESULT WriteBuf(); HRESULT AddFilter(CBitDecoder &_bitStream); - HRESULT ReadTables(CBitDecoder &_bitStream); + HRESULT DecodeLZ2(const CBitDecoder &_bitStream) throw(); HRESULT DecodeLZ(); HRESULT CodeReal(); - public: CDecoder(); ~CDecoder(); - - MY_UNKNOWN_IMP1(ICompressSetDecoderProperties2) - - STDMETHOD(Code)(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress); - - STDMETHOD(SetDecoderProperties2)(const Byte *data, UInt32 size); }; }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/ShrinkDecoder.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/ShrinkDecoder.cpp index 80b7e67c..c8e2083c 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/ShrinkDecoder.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/ShrinkDecoder.cpp @@ -1,10 +1,7 @@ // ShrinkDecoder.cpp - #include "StdAfx.h" -#include - #include "../../../C/Alloc.h" #include "../Common/InBuffer.h" @@ -16,104 +13,154 @@ namespace NCompress { namespace NShrink { +static const UInt32 kEmpty = 256; // kNumItems; static const UInt32 kBufferSize = (1 << 18); static const unsigned kNumMinBits = 9; HRESULT CDecoder::CodeReal(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 * /* inSize */, const UInt64 * /* outSize */, ICompressProgressInfo *progress) + const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress) { NBitl::CBaseDecoder inBuffer; COutBuffer outBuffer; if (!inBuffer.Create(kBufferSize)) return E_OUTOFMEMORY; + if (!outBuffer.Create(kBufferSize)) + return E_OUTOFMEMORY; + inBuffer.SetStream(inStream); inBuffer.Init(); - if (!outBuffer.Create(kBufferSize)) - return E_OUTOFMEMORY; outBuffer.SetStream(outStream); outBuffer.Init(); { - unsigned i; - for (i = 0; i < 257; i++) - _parents[i] = (UInt16)i; - for (; i < kNumItems; i++) - _parents[i] = kNumItems; - for (i = 0; i < kNumItems; i++) - _suffixes[i] = 0; + for (unsigned i = 0; i < kNumItems; i++) + _parents[i] = kEmpty; } - UInt64 prevPos = 0; + UInt64 outPrev = 0, inPrev = 0; unsigned numBits = kNumMinBits; unsigned head = 257; int lastSym = -1; - Byte lastChar2 = 0; + Byte lastChar = 0; + bool moreOut = false; + + HRESULT res = S_FALSE; for (;;) { + _inProcessed = inBuffer.GetProcessedSize(); + const UInt64 nowPos = outBuffer.GetProcessedSize(); + + bool eofCheck = false; + + if (outSize && nowPos >= *outSize) + { + if (!_fullStreamMode || moreOut) + { + res = S_OK; + break; + } + eofCheck = true; + // Is specSym(=256) allowed after end of stream ? + // Do we need to read it here ? + } + + if (progress) + { + if (nowPos - outPrev >= (1 << 20) || _inProcessed - inPrev >= (1 << 20)) + { + outPrev = nowPos; + inPrev = _inProcessed; + res = progress->SetRatioInfo(&_inProcessed, &nowPos); + if (res != SZ_OK) + { + // break; + return res; + } + } + } + UInt32 sym = inBuffer.ReadBits(numBits); - + if (inBuffer.ExtraBitsWereRead()) + { + res = S_OK; break; + } if (sym == 256) { sym = inBuffer.ReadBits(numBits); + + if (inBuffer.ExtraBitsWereRead()) + break; + if (sym == 1) { if (numBits >= kNumMaxBits) - return S_FALSE; + break; numBits++; continue; } if (sym != 2) - return S_FALSE; { + break; + // continue; // info-zip just ignores such code + } + { + /* + ---------- Free leaf nodes ---------- + Note : that code can mark _parents[lastSym] as free, and next + inserted node will be Orphan in that case. + */ + unsigned i; - for (i = 257; i < kNumItems; i++) + for (i = 256; i < kNumItems; i++) _stack[i] = 0; for (i = 257; i < kNumItems; i++) { unsigned par = _parents[i]; - if (par != kNumItems) + if (par != kEmpty) _stack[par] = 1; } for (i = 257; i < kNumItems; i++) if (_stack[i] == 0) - _parents[i] = kNumItems; - + _parents[i] = kEmpty; head = 257; - continue; } } + if (eofCheck) + { + // It's can be error case. + // That error can be detected later in (*inSize != _inProcessed) check. + res = S_OK; + break; + } + bool needPrev = false; if (head < kNumItems && lastSym >= 0) { - while (head < kNumItems && _parents[head] != kNumItems) + while (head < kNumItems && _parents[head] != kEmpty) head++; if (head < kNumItems) { - if (head == (unsigned)lastSym) - { - // we need to fix the code for that case - // _parents[head] is not allowed to link to itself - return E_NOTIMPL; - } + /* + if (head == lastSym), it updates Orphan to self-linked Orphan and creates two problems: + 1) we must check _stack[i++] overflow in code that walks tree nodes. + 2) self-linked node can not be removed. So such self-linked nodes can occupy all _parents items. + */ needPrev = true; _parents[head] = (UInt16)lastSym; - _suffixes[head] = (Byte)lastChar2; + _suffixes[head] = (Byte)lastChar; head++; } } - if (_parents[sym] == kNumItems) - return S_FALSE; - - lastSym = sym; + lastSym = (int)sym; unsigned cur = sym; unsigned i = 0; @@ -121,40 +168,77 @@ HRESULT CDecoder::CodeReal(ISequentialInStream *inStream, ISequentialOutStream * { _stack[i++] = _suffixes[cur]; cur = _parents[cur]; + // don't change that code: + // Orphan Check and self-linked Orphan check (_stack overflow check); + if (cur == kEmpty || i >= kNumItems) + break; } + if (cur == kEmpty || i >= kNumItems) + break; + _stack[i++] = (Byte)cur; - lastChar2 = (Byte)cur; + lastChar = (Byte)cur; if (needPrev) - _suffixes[head - 1] = (Byte)cur; + _suffixes[(size_t)head - 1] = (Byte)cur; + + if (outSize) + { + const UInt64 limit = *outSize - nowPos; + if (i > limit) + { + moreOut = true; + i = (unsigned)limit; + } + } do outBuffer.WriteByte(_stack[--i]); while (i); - - if (progress) + } + + RINOK(outBuffer.Flush()) + + if (res == S_OK) + if (_fullStreamMode) { + if (moreOut) + res = S_FALSE; const UInt64 nowPos = outBuffer.GetProcessedSize(); - if (nowPos - prevPos >= (1 << 18)) - { - prevPos = nowPos; - const UInt64 packSize = inBuffer.GetProcessedSize(); - RINOK(progress->SetRatioInfo(&packSize, &nowPos)); - } + if (outSize && *outSize != nowPos) + res = S_FALSE; + if (inSize && *inSize != _inProcessed) + res = S_FALSE; } - } - return outBuffer.Flush(); + return res; } -STDMETHODIMP CDecoder ::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress) + +Z7_COM7F_IMF(CDecoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, + const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress)) { try { return CodeReal(inStream, outStream, inSize, outSize, progress); } - catch(const CInBufferException &e) { return e.ErrorCode; } - catch(const COutBufferException &e) { return e.ErrorCode; } + // catch(const CInBufferException &e) { return e.ErrorCode; } + // catch(const COutBufferException &e) { return e.ErrorCode; } + catch(const CSystemException &e) { return e.ErrorCode; } catch(...) { return S_FALSE; } } + +Z7_COM7F_IMF(CDecoder::SetFinishMode(UInt32 finishMode)) +{ + _fullStreamMode = (finishMode != 0); + return S_OK; +} + + +Z7_COM7F_IMF(CDecoder::GetInStreamProcessedSize(UInt64 *value)) +{ + *value = _inProcessed; + return S_OK; +} + + }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/ShrinkDecoder.h b/src/plugins/7zip/7za/cpp/7zip/Compress/ShrinkDecoder.h index 42e5eee7..5e7f78f3 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/ShrinkDecoder.h +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/ShrinkDecoder.h @@ -1,7 +1,7 @@ // ShrinkDecoder.h -#ifndef __COMPRESS_SHRINK_DECODER_H -#define __COMPRESS_SHRINK_DECODER_H +#ifndef ZIP7_INC_COMPRESS_SHRINK_DECODER_H +#define ZIP7_INC_COMPRESS_SHRINK_DECODER_H #include "../../Common/MyCom.h" @@ -13,22 +13,21 @@ namespace NShrink { const unsigned kNumMaxBits = 13; const unsigned kNumItems = 1 << kNumMaxBits; -class CDecoder : - public ICompressCoder, - public CMyUnknownImp -{ +Z7_CLASS_IMP_NOQIB_3( + CDecoder + , ICompressCoder + , ICompressSetFinishMode + , ICompressGetInStreamProcessedSize +) + bool _fullStreamMode; + UInt64 _inProcessed; + UInt16 _parents[kNumItems]; Byte _suffixes[kNumItems]; Byte _stack[kNumItems]; -public: - MY_UNKNOWN_IMP - HRESULT CodeReal(ISequentialInStream *inStream, ISequentialOutStream *outStream, const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress); - - STDMETHOD(Code)(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress); }; }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/StdAfx.h b/src/plugins/7zip/7za/cpp/7zip/Compress/StdAfx.h index 1cbd7fea..80866550 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/StdAfx.h +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/StdAfx.h @@ -1,8 +1,11 @@ // StdAfx.h -#ifndef __STDAFX_H -#define __STDAFX_H +#ifndef ZIP7_INC_STDAFX_H +#define ZIP7_INC_STDAFX_H +#if defined(_MSC_VER) && _MSC_VER >= 1800 +#pragma warning(disable : 4464) // relative include path contains '..' +#endif #include "../../Common/Common.h" #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/XpressDecoder.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/XpressDecoder.cpp index a4d45335..47f60613 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/XpressDecoder.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/XpressDecoder.cpp @@ -2,45 +2,301 @@ #include "StdAfx.h" -// #include - #include "../../../C/CpuArch.h" +#include "../../../C/RotateDefs.h" #include "HuffmanDecoder.h" +#include "XpressDecoder.h" -namespace NCompress { -namespace NXpress { +#ifdef MY_CPU_LE_UNALIGN + #define Z7_XPRESS_DEC_USE_UNALIGNED_COPY +#endif + +#ifdef Z7_XPRESS_DEC_USE_UNALIGNED_COPY + + #define COPY_CHUNK_SIZE 16 + + #define COPY_CHUNK_4_2(dest, src) \ + { \ + ((UInt32 *)(void *)dest)[0] = ((const UInt32 *)(const void *)src)[0]; \ + ((UInt32 *)(void *)dest)[1] = ((const UInt32 *)(const void *)src)[1]; \ + src += 4 * 2; \ + dest += 4 * 2; \ + } + + /* sse2 doesn't help here in GCC and CLANG. + so we disabled sse2 here */ +#if 0 + #if defined(MY_CPU_AMD64) + #define Z7_XPRESS_DEC_USE_SSE2 + #elif defined(MY_CPU_X86) + #if defined(_MSC_VER) && _MSC_VER >= 1300 && defined(_M_IX86_FP) && (_M_IX86_FP >= 2) \ + || defined(__SSE2__) \ + // || 1 == 1 // for debug only + #define Z7_XPRESS_DEC_USE_SSE2 + #endif + #endif +#endif + + #if defined(MY_CPU_ARM64) + #include + #define COPY_OFFSET_MIN 16 + #define COPY_CHUNK1(dest, src) \ + { \ + vst1q_u8((uint8_t *)(void *)dest, \ + vld1q_u8((const uint8_t *)(const void *)src)); \ + src += 16; \ + dest += 16; \ + } + + #define COPY_CHUNK(dest, src) \ + { \ + COPY_CHUNK1(dest, src) \ + if (dest >= dest_lim) break; \ + COPY_CHUNK1(dest, src) \ + } + + #elif defined(Z7_XPRESS_DEC_USE_SSE2) + #include // sse2 + #define COPY_OFFSET_MIN 16 + + #define COPY_CHUNK1(dest, src) \ + { \ + _mm_storeu_si128((__m128i *)(void *)dest, \ + _mm_loadu_si128((const __m128i *)(const void *)src)); \ + src += 16; \ + dest += 16; \ + } + + #define COPY_CHUNK(dest, src) \ + { \ + COPY_CHUNK1(dest, src) \ + if (dest >= dest_lim) break; \ + COPY_CHUNK1(dest, src) \ + } + + #elif defined(MY_CPU_64BIT) + #define COPY_OFFSET_MIN 8 + + #define COPY_CHUNK(dest, src) \ + { \ + ((UInt64 *)(void *)dest)[0] = ((const UInt64 *)(const void *)src)[0]; \ + ((UInt64 *)(void *)dest)[1] = ((const UInt64 *)(const void *)src)[1]; \ + src += 8 * 2; \ + dest += 8 * 2; \ + } + + #else + #define COPY_OFFSET_MIN 4 + + #define COPY_CHUNK(dest, src) \ + { \ + COPY_CHUNK_4_2(dest, src); \ + COPY_CHUNK_4_2(dest, src); \ + } + + #endif +#endif -struct CBitStream + +#ifndef COPY_CHUNK_SIZE + #define COPY_OFFSET_MIN 4 + #define COPY_CHUNK_SIZE 8 + #define COPY_CHUNK_2(dest, src) \ + { \ + const Byte a0 = src[0]; \ + const Byte a1 = src[1]; \ + dest[0] = a0; \ + dest[1] = a1; \ + src += 2; \ + dest += 2; \ + } + #define COPY_CHUNK(dest, src) \ + { \ + COPY_CHUNK_2(dest, src) \ + COPY_CHUNK_2(dest, src) \ + COPY_CHUNK_2(dest, src) \ + COPY_CHUNK_2(dest, src) \ + } +#endif + + +#define COPY_CHUNKS \ +{ \ + Z7_PRAGMA_OPT_DISABLE_LOOP_UNROLL_VECTORIZE \ + do { COPY_CHUNK(dest, src) } \ + while (dest < dest_lim); \ +} + + +static +Z7_FORCE_INLINE +// Z7_ATTRIB_NO_VECTOR +void CopyMatch_1(Byte *dest, const Byte *dest_lim) { - UInt32 Value; - unsigned BitPos; + const unsigned b0 = dest[-1]; + { +#if defined(Z7_XPRESS_DEC_USE_UNALIGNED_COPY) && (COPY_CHUNK_SIZE == 16) + #if defined(MY_CPU_64BIT) + { + const UInt64 v64 = (UInt64)b0 * 0x0101010101010101; + Z7_PRAGMA_OPT_DISABLE_LOOP_UNROLL_VECTORIZE + do + { + ((UInt64 *)(void *)dest)[0] = v64; + ((UInt64 *)(void *)dest)[1] = v64; + dest += 16; + } + while (dest < dest_lim); + } + #else + { + UInt32 v = b0; + v |= v << 8; + v |= v << 16; + do + { + ((UInt32 *)(void *)dest)[0] = v; + ((UInt32 *)(void *)dest)[1] = v; + dest += 8; + ((UInt32 *)(void *)dest)[0] = v; + ((UInt32 *)(void *)dest)[1] = v; + dest += 8; + } + while (dest < dest_lim); + } + #endif +#else + do + { + dest[0] = (Byte)b0; + dest[1] = (Byte)b0; + dest += 2; + dest[0] = (Byte)b0; + dest[1] = (Byte)b0; + dest += 2; + } + while (dest < dest_lim); +#endif + } +} - UInt32 GetValue(unsigned numBits) const - { - return (Value >> (BitPos - numBits)) & ((1 << numBits) - 1); - } - - void MovePos(unsigned numBits) + +// (offset != 1) +static +Z7_FORCE_INLINE +// Z7_ATTRIB_NO_VECTOR +void CopyMatch_Non1(Byte *dest, size_t offset, const Byte *dest_lim) +{ + const Byte *src = dest - offset; { - BitPos -= numBits; + // (COPY_OFFSET_MIN >= 4) + if (offset >= COPY_OFFSET_MIN) + { + COPY_CHUNKS + // return; + } + else +#if (COPY_OFFSET_MIN > 4) + #if COPY_CHUNK_SIZE < 8 + #error Stop_Compiling_Bad_COPY_CHUNK_SIZE + #endif + if (offset >= 4) + { + Z7_PRAGMA_OPT_DISABLE_LOOP_UNROLL_VECTORIZE + do + { + COPY_CHUNK_4_2(dest, src) + #if COPY_CHUNK_SIZE != 16 + if (dest >= dest_lim) break; + #endif + COPY_CHUNK_4_2(dest, src) + } + while (dest < dest_lim); + // return; + } + else +#endif + { + // (offset < 4) + if (offset == 2) + { +#if defined(Z7_XPRESS_DEC_USE_UNALIGNED_COPY) + UInt32 w0 = GetUi16(src); + w0 += w0 << 16; + do + { + SetUi32(dest, w0) + dest += 4; + } + while (dest < dest_lim); +#else + const unsigned b0 = src[0]; + const Byte b1 = src[1]; + do + { + dest[0] = (Byte)b0; + dest[1] = b1; + dest += 2; + } + while (dest < dest_lim); +#endif + } + else // (offset == 3) + { + const unsigned b0 = src[0]; +#if defined(Z7_XPRESS_DEC_USE_UNALIGNED_COPY) + const unsigned w1 = GetUi16(src + 1); + do + { + dest[0] = (Byte)b0; + SetUi16(dest + 1, (UInt16)w1) + dest += 3; + } + while (dest < dest_lim); +#else + const Byte b1 = src[1]; + const Byte b2 = src[2]; + do + { + dest[0] = (Byte)b0; + dest[1] = b1; + dest[2] = b2; + dest += 3; + } + while (dest < dest_lim); +#endif + } + } } -}; +} + + +namespace NCompress { +namespace NXpress { #define BIT_STREAM_NORMALIZE \ - if (bs.BitPos < 16) { \ + if (BitPos > 16) { \ if (in >= lim) return S_FALSE; \ - bs.Value = (bs.Value << 16) | GetUi16(in); \ - in += 2; bs.BitPos += 16; } - -const unsigned kNumHuffBits = 15; -const unsigned kNumLenSlots = 16; -const unsigned kNumPosSlots = 16; -const unsigned kNumSyms = 256 + kNumPosSlots * kNumLenSlots; - -HRESULT Decode(const Byte *in, size_t inSize, Byte *out, size_t outSize) + BitPos -= 16; \ + Value |= (UInt32)GetUi16(in) << BitPos; \ + in += 2; } + +#define MOVE_POS(bs, numBits) \ + BitPos += (unsigned)numBits; \ + Value <<= numBits; \ + + +static const unsigned kNumHuffBits = 15; +static const unsigned kNumTableBits = 10; +static const unsigned kNumLenBits = 4; +static const unsigned kLenMask = (1 << kNumLenBits) - 1; +static const unsigned kNumPosSlots = 16; +static const unsigned kNumSyms = 256 + (kNumPosSlots << kNumLenBits); + +HRESULT Decode_WithExceedWrite(const Byte *in, size_t inSize, Byte *out, size_t outSize) { - NCompress::NHuffman::CDecoder huff; + NCompress::NHuffman::CDecoder huff; if (inSize < kNumSyms / 2 + 4) return S_FALSE; @@ -48,50 +304,57 @@ HRESULT Decode(const Byte *in, size_t inSize, Byte *out, size_t outSize) Byte levels[kNumSyms]; for (unsigned i = 0; i < kNumSyms / 2; i++) { - Byte b = in[i]; - levels[i * 2] = (Byte)(b & 0xF); - levels[i * 2 + 1] = (Byte)(b >> 4); + const unsigned b = in[i]; + levels[(size_t)i * 2 ] = (Byte)(b & 0xf); + levels[(size_t)i * 2 + 1] = (Byte)(b >> 4); } - if (!huff.BuildFull(levels)) + if (!huff.Build(levels, NHuffman::k_BuildMode_Full)) return S_FALSE; } + UInt32 Value; + unsigned BitPos; // how many bits in (Value) were processed - CBitStream bs; - - const Byte *lim = in + inSize - 1; - + const Byte *lim = in + inSize - 1; // points to last byte in += kNumSyms / 2; - bs.Value = (GetUi16(in) << 16) | GetUi16(in + 2); +#ifdef MY_CPU_LE_UNALIGN + Value = GetUi32(in); + Value = rotlFixed(Value, 16); +#else + Value = ((UInt32)GetUi16(in) << 16) | GetUi16(in + 2); +#endif + in += 4; - bs.BitPos = 32; - - size_t pos = 0; + BitPos = 0; + Byte *dest = out; + const Byte *outLim = out + outSize; for (;;) { - // printf("\n%d", pos); - UInt32 sym = huff.DecodeFull(&bs); - // printf(" sym = %d", sym); + unsigned sym; + Z7_HUFF_DECODE_VAL_IN_HIGH32(sym, &huff, kNumHuffBits, kNumTableBits, + Value, Z7_HUFF_DECODE_ERROR_SYM_CHECK_NO, {}, MOVE_POS, {}, bs) + // 0 < BitPos <= 31 BIT_STREAM_NORMALIZE + // 0 < BitPos <= 16 - if (pos >= outSize) - return (sym == 256 && in == lim + 1) ? S_OK : S_FALSE; + if (dest >= outLim) + return (sym == 256 && Value == 0 && in == lim + 1) ? S_OK : S_FALSE; if (sym < 256) - out[pos++] = (Byte)sym; + *dest++ = (Byte)sym; else { - sym -= 256; - UInt32 dist = sym / kNumLenSlots; - UInt32 len = sym & (kNumLenSlots - 1); + const unsigned distBits = (unsigned)(Byte)sym >> kNumLenBits; // (sym - 256) >> kNumLenBits; + UInt32 len = (UInt32)(sym & kLenMask); - if (len == kNumLenSlots - 1) + if (len == kLenMask) { if (in > lim) return S_FALSE; + // here we read input bytes in out-of-order related to main input stream (bits in Value): len = *in++; - if (len == 0xFF) + if (len == 0xff) { if (in >= lim) return S_FALSE; @@ -99,29 +362,36 @@ HRESULT Decode(const Byte *in, size_t inSize, Byte *out, size_t outSize) in += 2; } else - len += kNumLenSlots - 1; + len += kLenMask; } - bs.BitPos -= dist; - dist = (UInt32)1 << dist; - dist += ((bs.Value >> bs.BitPos) & (dist - 1)); - - BIT_STREAM_NORMALIZE - - if (len > outSize - pos) - return S_FALSE; - if (dist > pos) + len += 3; + if (len > (size_t)(outLim - dest)) return S_FALSE; - Byte *dest = out + pos; - const Byte *src = dest - dist; - pos += len + 3; - len += 1; - *dest++ = *src++; - *dest++ = *src++; - do - *dest++ = *src++; - while (--len); + if (distBits == 0) + { + // d == 1 + if (dest == out) + return S_FALSE; + Byte *destTemp = dest; + dest += len; + CopyMatch_1(destTemp, dest); + } + else + { + unsigned d = (unsigned)(Value >> (32 - distBits)); + MOVE_POS(bs, distBits) + d += 1u << distBits; + // 0 < BitPos <= 31 + BIT_STREAM_NORMALIZE + // 0 < BitPos <= 16 + if (d > (size_t)(dest - out)) + return S_FALSE; + Byte *destTemp = dest; + dest += len; + CopyMatch_Non1(destTemp, d, dest); + } } } } diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/XpressDecoder.h b/src/plugins/7zip/7za/cpp/7zip/Compress/XpressDecoder.h index cada85bf..7d176029 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/XpressDecoder.h +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/XpressDecoder.h @@ -1,12 +1,17 @@ // XpressDecoder.h -#ifndef __XPRESS_DECODER_H -#define __XPRESS_DECODER_H +#ifndef ZIP7_INC_XPRESS_DECODER_H +#define ZIP7_INC_XPRESS_DECODER_H + +#include "../../Common/MyTypes.h" namespace NCompress { namespace NXpress { -HRESULT Decode(const Byte *in, size_t inSize, Byte *out, size_t outSize); +// (out) buffer size must be larger than (outSize) for the following value: +const unsigned kAdditionalOutputBufSize = 32; + +HRESULT Decode_WithExceedWrite(const Byte *in, size_t inSize, Byte *out, size_t outSize); }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/XzDecoder.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/XzDecoder.cpp new file mode 100644 index 00000000..62a78bfe --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/XzDecoder.cpp @@ -0,0 +1,151 @@ +// XzDecoder.cpp + +#include "StdAfx.h" + +#include "../../../C/Alloc.h" + +#include "../Common/CWrappers.h" + +#include "XzDecoder.h" + +namespace NCompress { +namespace NXz { + +#define RET_IF_WRAP_ERROR_CONFIRMED(wrapRes, sRes, sResErrorCode) \ + if (wrapRes != S_OK && sRes == sResErrorCode) return wrapRes; + +#define RET_IF_WRAP_ERROR(wrapRes, sRes, sResErrorCode) \ + if (wrapRes != S_OK /* && (sRes == SZ_OK || sRes == sResErrorCode) */) return wrapRes; + +static HRESULT SResToHRESULT_Code(SRes res) throw() +{ + if (res < 0) + return res; + switch (res) + { + case SZ_OK: return S_OK; + case SZ_ERROR_MEM: return E_OUTOFMEMORY; + case SZ_ERROR_UNSUPPORTED: return E_NOTIMPL; + default: break; + } + return S_FALSE; +} + + +HRESULT CDecoder::Decode(ISequentialInStream *seqInStream, ISequentialOutStream *outStream, + const UInt64 *outSizeLimit, bool finishStream, ICompressProgressInfo *progress) +{ + MainDecodeSRes = SZ_OK; + MainDecodeSRes_wasUsed = false; + XzStatInfo_Clear(&Stat); + + if (!xz) + { + xz = XzDecMt_Create(&g_Alloc, &g_MidAlloc); + if (!xz) + return E_OUTOFMEMORY; + } + + CXzDecMtProps props; + XzDecMtProps_Init(&props); + + int isMT = False; + + #ifndef Z7_ST + { + props.numThreads = 1; + const UInt32 numThreads = _numThreads; + + if (_tryMt && numThreads > 1) + { + size_t memUsage = (size_t)_memUsage; + if (memUsage != _memUsage) + memUsage = (size_t)0 - 1; + props.memUseMax = memUsage; + isMT = (numThreads > 1); + } + + props.numThreads = numThreads; + } + #endif + + CSeqInStreamWrap inWrap; + CSeqOutStreamWrap outWrap; + CCompressProgressWrap progressWrap; + + inWrap.Init(seqInStream); + outWrap.Init(outStream); + progressWrap.Init(progress); + + SRes res = XzDecMt_Decode(xz, + &props, + outSizeLimit, finishStream, + &outWrap.vt, + &inWrap.vt, + &Stat, + &isMT, + progress ? &progressWrap.vt : NULL); + + MainDecodeSRes = res; + + #ifndef Z7_ST + // _tryMt = isMT; + #endif + + RET_IF_WRAP_ERROR(outWrap.Res, res, SZ_ERROR_WRITE) + RET_IF_WRAP_ERROR(progressWrap.Res, res, SZ_ERROR_PROGRESS) + RET_IF_WRAP_ERROR_CONFIRMED(inWrap.Res, res, SZ_ERROR_READ) + + // return E_OUTOFMEMORY; // for debug check + + MainDecodeSRes_wasUsed = true; + + if (res == SZ_OK && finishStream) + { + /* + if (inSize && *inSize != Stat.PhySize) + res = SZ_ERROR_DATA; + */ + if (outSizeLimit && *outSizeLimit != outWrap.Processed) + res = SZ_ERROR_DATA; + } + + return SResToHRESULT_Code(res); +} + + +Z7_COM7F_IMF(CComDecoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, + const UInt64 * /* inSize */, const UInt64 *outSize, ICompressProgressInfo *progress)) +{ + return Decode(inStream, outStream, outSize, _finishStream, progress); +} + +Z7_COM7F_IMF(CComDecoder::SetFinishMode(UInt32 finishMode)) +{ + _finishStream = (finishMode != 0); + return S_OK; +} + +Z7_COM7F_IMF(CComDecoder::GetInStreamProcessedSize(UInt64 *value)) +{ + *value = Stat.InSize; + return S_OK; +} + +#ifndef Z7_ST + +Z7_COM7F_IMF(CComDecoder::SetNumberOfThreads(UInt32 numThreads)) +{ + _numThreads = numThreads; + return S_OK; +} + +Z7_COM7F_IMF(CComDecoder::SetMemLimit(UInt64 memUsage)) +{ + _memUsage = memUsage; + return S_OK; +} + +#endif + +}} diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/XzDecoder.h b/src/plugins/7zip/7za/cpp/7zip/Compress/XzDecoder.h new file mode 100644 index 00000000..40ed4f5c --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/XzDecoder.h @@ -0,0 +1,86 @@ +// XzDecoder.h + +#ifndef ZIP7_INC_XZ_DECODER_H +#define ZIP7_INC_XZ_DECODER_H + +#include "../../../C/Xz.h" + +#include "../../Common/MyCom.h" + +#include "../ICoder.h" + +namespace NCompress { +namespace NXz { + +struct CDecoder +{ + CXzDecMtHandle xz; + int _tryMt; + UInt32 _numThreads; + UInt64 _memUsage; + + SRes MainDecodeSRes; // it's not HRESULT + bool MainDecodeSRes_wasUsed; + CXzStatInfo Stat; + + CDecoder(): + xz(NULL), + _tryMt(True), + _numThreads(1), + _memUsage((UInt64)(sizeof(size_t)) << 28), + MainDecodeSRes(SZ_OK), + MainDecodeSRes_wasUsed(false) + {} + + ~CDecoder() + { + if (xz) + XzDecMt_Destroy(xz); + } + + /* Decode() can return S_OK, if there is data after good xz streams, and that data is not new xz stream. + check also (Stat.DataAfterEnd) flag */ + + HRESULT Decode(ISequentialInStream *seqInStream, ISequentialOutStream *outStream, + const UInt64 *outSizeLimit, bool finishStream, ICompressProgressInfo *compressProgress); +}; + + +class CComDecoder Z7_final: + public ICompressCoder, + public ICompressSetFinishMode, + public ICompressGetInStreamProcessedSize, + #ifndef Z7_ST + public ICompressSetCoderMt, + public ICompressSetMemLimit, + #endif + public CMyUnknownImp, + public CDecoder +{ + Z7_COM_QI_BEGIN2(ICompressCoder) + Z7_COM_QI_ENTRY(ICompressSetFinishMode) + Z7_COM_QI_ENTRY(ICompressGetInStreamProcessedSize) + #ifndef Z7_ST + Z7_COM_QI_ENTRY(ICompressSetCoderMt) + Z7_COM_QI_ENTRY(ICompressSetMemLimit) + #endif + Z7_COM_QI_END + Z7_COM_ADDREF_RELEASE + + Z7_IFACE_COM7_IMP(ICompressCoder) + Z7_IFACE_COM7_IMP(ICompressSetFinishMode) + Z7_IFACE_COM7_IMP(ICompressGetInStreamProcessedSize) + #ifndef Z7_ST + Z7_IFACE_COM7_IMP(ICompressSetCoderMt) + Z7_IFACE_COM7_IMP(ICompressSetMemLimit) + #endif + + bool _finishStream; + +public: + CComDecoder(): _finishStream(false) {} +}; + +}} + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/XzEncoder.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/XzEncoder.cpp new file mode 100644 index 00000000..1bca3228 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/XzEncoder.cpp @@ -0,0 +1,243 @@ +// XzEncoder.cpp + +#include "StdAfx.h" + +#include "../../../C/Alloc.h" + +#include "../../Common/MyString.h" +#include "../../Common/StringToInt.h" + +#include "../Common/CWrappers.h" +#include "../Common/StreamUtils.h" + +#include "XzEncoder.h" + +namespace NCompress { + +namespace NLzma2 { +HRESULT SetLzma2Prop(PROPID propID, const PROPVARIANT &prop, CLzma2EncProps &lzma2Props); +} + +namespace NXz { + +void CEncoder::InitCoderProps() +{ + XzProps_Init(&xzProps); +} + +CEncoder::CEncoder() +{ + XzProps_Init(&xzProps); + _encoder = NULL; + _encoder = XzEnc_Create(&g_Alloc, &g_BigAlloc); + if (!_encoder) + throw 1; +} + +CEncoder::~CEncoder() +{ + if (_encoder) + XzEnc_Destroy(_encoder); +} + + +struct CMethodNamePair +{ + UInt32 Id; + const char *Name; +}; + +static const CMethodNamePair g_NamePairs[] = +{ + { XZ_ID_Delta, "Delta" }, + { XZ_ID_X86, "BCJ" }, + { XZ_ID_PPC, "PPC" }, + { XZ_ID_IA64, "IA64" }, + { XZ_ID_ARM, "ARM" }, + { XZ_ID_ARMT, "ARMT" }, + { XZ_ID_SPARC, "SPARC" } + // { XZ_ID_LZMA2, "LZMA2" } +}; + +static int FilterIdFromName(const wchar_t *name) +{ + for (unsigned i = 0; i < Z7_ARRAY_SIZE(g_NamePairs); i++) + { + const CMethodNamePair &pair = g_NamePairs[i]; + if (StringsAreEqualNoCase_Ascii(name, pair.Name)) + return (int)pair.Id; + } + return -1; +} + + +HRESULT CEncoder::SetCheckSize(UInt32 checkSizeInBytes) +{ + unsigned id; + switch (checkSizeInBytes) + { + case 0: id = XZ_CHECK_NO; break; + case 4: id = XZ_CHECK_CRC32; break; + case 8: id = XZ_CHECK_CRC64; break; + case 32: id = XZ_CHECK_SHA256; break; + default: return E_INVALIDARG; + } + xzProps.checkId = id; + return S_OK; +} + + +HRESULT CEncoder::SetCoderProp(PROPID propID, const PROPVARIANT &prop) +{ + if (propID == NCoderPropID::kNumThreads) + { + if (prop.vt != VT_UI4) + return E_INVALIDARG; + xzProps.numTotalThreads = (int)(prop.ulVal); + return S_OK; + } + + if (propID == NCoderPropID::kCheckSize) + { + if (prop.vt != VT_UI4) + return E_INVALIDARG; + return SetCheckSize(prop.ulVal); + } + + if (propID == NCoderPropID::kBlockSize2) + { + if (prop.vt == VT_UI4) + xzProps.blockSize = prop.ulVal; + else if (prop.vt == VT_UI8) + xzProps.blockSize = prop.uhVal.QuadPart; + else + return E_INVALIDARG; + return S_OK; + } + + if (propID == NCoderPropID::kReduceSize) + { + if (prop.vt == VT_UI8) + xzProps.reduceSize = prop.uhVal.QuadPart; + else + return E_INVALIDARG; + return S_OK; + } + + if (propID == NCoderPropID::kFilter) + { + if (prop.vt == VT_UI4) + { + const UInt32 id32 = prop.ulVal; + if (id32 == XZ_ID_Delta) + return E_INVALIDARG; + xzProps.filterProps.id = prop.ulVal; + } + else + { + if (prop.vt != VT_BSTR) + return E_INVALIDARG; + + const wchar_t *name = prop.bstrVal; + const wchar_t *end; + + UInt32 id32 = ConvertStringToUInt32(name, &end); + + if (end != name) + name = end; + else + { + if (IsString1PrefixedByString2_NoCase_Ascii(name, "Delta")) + { + name += 5; // strlen("Delta"); + id32 = XZ_ID_Delta; + } + else + { + const int filterId = FilterIdFromName(prop.bstrVal); + if (filterId < 0 /* || filterId == XZ_ID_LZMA2 */) + return E_INVALIDARG; + id32 = (UInt32)(unsigned)filterId; + } + } + + if (id32 == XZ_ID_Delta) + { + const wchar_t c = *name; + if (c != '-' && c != ':') + return E_INVALIDARG; + name++; + const UInt32 delta = ConvertStringToUInt32(name, &end); + if (end == name || *end != 0 || delta == 0 || delta > 256) + return E_INVALIDARG; + xzProps.filterProps.delta = delta; + } + + xzProps.filterProps.id = id32; + } + + return S_OK; + } + + return NLzma2::SetLzma2Prop(propID, prop, xzProps.lzma2Props); +} + + +Z7_COM7F_IMF(CEncoder::SetCoderProperties(const PROPID *propIDs, + const PROPVARIANT *coderProps, UInt32 numProps)) +{ + XzProps_Init(&xzProps); + + for (UInt32 i = 0; i < numProps; i++) + { + RINOK(SetCoderProp(propIDs[i], coderProps[i])) + } + + return S_OK; + // return SResToHRESULT(XzEnc_SetProps(_encoder, &xzProps)); +} + + +Z7_COM7F_IMF(CEncoder::SetCoderPropertiesOpt(const PROPID *propIDs, + const PROPVARIANT *coderProps, UInt32 numProps)) +{ + for (UInt32 i = 0; i < numProps; i++) + { + const PROPVARIANT &prop = coderProps[i]; + const PROPID propID = propIDs[i]; + if (propID == NCoderPropID::kExpectedDataSize) + if (prop.vt == VT_UI8) + XzEnc_SetDataSize(_encoder, prop.uhVal.QuadPart); + } + return S_OK; +} + + +#define RET_IF_WRAP_ERROR(wrapRes, sRes, sResErrorCode) \ + if (wrapRes != S_OK /* && (sRes == SZ_OK || sRes == sResErrorCode) */) return wrapRes; + +Z7_COM7F_IMF(CEncoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, + const UInt64 * /* inSize */, const UInt64 * /* outSize */, ICompressProgressInfo *progress)) +{ + CSeqInStreamWrap inWrap; + CSeqOutStreamWrap outWrap; + CCompressProgressWrap progressWrap; + + inWrap.Init(inStream); + outWrap.Init(outStream); + progressWrap.Init(progress); + + SRes res = XzEnc_SetProps(_encoder, &xzProps); + if (res == SZ_OK) + res = XzEnc_Encode(_encoder, &outWrap.vt, &inWrap.vt, progress ? &progressWrap.vt : NULL); + + // SRes res = Xz_Encode(&outWrap.vt, &inWrap.vt, &xzProps, progress ? &progressWrap.vt : NULL); + + RET_IF_WRAP_ERROR(inWrap.Res, res, SZ_ERROR_READ) + RET_IF_WRAP_ERROR(outWrap.Res, res, SZ_ERROR_WRITE) + RET_IF_WRAP_ERROR(progressWrap.Res, res, SZ_ERROR_PROGRESS) + + return SResToHRESULT(res); +} + +}} diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/XzEncoder.h b/src/plugins/7zip/7za/cpp/7zip/Compress/XzEncoder.h new file mode 100644 index 00000000..434f5820 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/XzEncoder.h @@ -0,0 +1,35 @@ +// XzEncoder.h + +#ifndef ZIP7_INC_XZ_ENCODER_H +#define ZIP7_INC_XZ_ENCODER_H + +#include "../../../C/XzEnc.h" + +#include "../../Common/MyCom.h" + +#include "../ICoder.h" + +namespace NCompress { +namespace NXz { + +Z7_CLASS_IMP_COM_3( + CEncoder + , ICompressCoder + , ICompressSetCoderProperties + , ICompressSetCoderPropertiesOpt +) + CXzEncHandle _encoder; +public: + CXzProps xzProps; + + void InitCoderProps(); + HRESULT SetCheckSize(UInt32 checkSizeInBytes); + HRESULT SetCoderProp(PROPID propID, const PROPVARIANT &prop); + + CEncoder(); + ~CEncoder(); +}; + +}} + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/ZDecoder.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/ZDecoder.cpp index 7308bc0b..25eb80ee 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/ZDecoder.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/ZDecoder.cpp @@ -14,7 +14,7 @@ namespace NCompress { namespace NZ { -static const UInt32 kBufferSize = (1 << 20); +static const size_t kBufferSize = 1 << 20; static const Byte kNumBitsMask = 0x1F; static const Byte kBlockModeMask = 0x80; static const unsigned kNumMinBits = 9; @@ -22,21 +22,22 @@ static const unsigned kNumMaxBits = 16; void CDecoder::Free() { - MyFree(_parents); _parents = 0; - MyFree(_suffixes); _suffixes = 0; - MyFree(_stack); _stack = 0; + MyFree(_parents); _parents = NULL; + MyFree(_suffixes); _suffixes = NULL; + MyFree(_stack); _stack = NULL; } CDecoder::~CDecoder() { Free(); } -HRESULT CDecoder::CodeReal(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 * /* inSize */, const UInt64 * /* outSize */, ICompressProgressInfo *progress) +HRESULT CDecoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, + ICompressProgressInfo *progress) { + try { + // PackSize = 0; + CInBuffer inBuffer; COutBuffer outBuffer; - PackSize = 0; - if (!inBuffer.Create(kBufferSize)) return E_OUTOFMEMORY; inBuffer.SetStream(inStream); @@ -52,29 +53,29 @@ HRESULT CDecoder::CodeReal(ISequentialInStream *inStream, ISequentialOutStream * if (inBuffer.ReadBytes(buf, 3) < 3) return S_FALSE; if (buf[0] != 0x1F || buf[1] != 0x9D) - return S_FALSE;; + return S_FALSE; } - Byte prop = buf[2]; + const Byte prop = buf[2]; if ((prop & 0x60) != 0) return S_FALSE; - unsigned maxbits = prop & kNumBitsMask; + const unsigned maxbits = prop & kNumBitsMask; if (maxbits < kNumMinBits || maxbits > kNumMaxBits) return S_FALSE; - UInt32 numItems = 1 << maxbits; + const UInt32 numItems = (UInt32)1 << maxbits; // Speed optimization: blockSymbol can contain unused velue. - if (maxbits != _numMaxBits || _parents == 0 || _suffixes == 0 || _stack == 0) + if (maxbits != _numMaxBits || !_parents || !_suffixes || !_stack) { Free(); - _parents = (UInt16 *)MyAlloc(numItems * sizeof(UInt16)); if (_parents == 0) return E_OUTOFMEMORY; - _suffixes = (Byte *)MyAlloc(numItems * sizeof(Byte)); if (_suffixes == 0) return E_OUTOFMEMORY; - _stack = (Byte *)MyAlloc(numItems * sizeof(Byte)); if (_stack == 0) return E_OUTOFMEMORY; + _parents = (UInt16 *)MyAlloc(numItems * sizeof(UInt16)); if (!_parents) return E_OUTOFMEMORY; + _suffixes = (Byte *)MyAlloc(numItems * sizeof(Byte)); if (!_suffixes) return E_OUTOFMEMORY; + _stack = (Byte *)MyAlloc(numItems * sizeof(Byte)); if (!_stack) return E_OUTOFMEMORY; _numMaxBits = maxbits; } UInt64 prevPos = 0; - UInt32 blockSymbol = ((prop & kBlockModeMask) != 0) ? 256 : ((UInt32)1 << kNumMaxBits); + const UInt32 blockSymbol = ((prop & kBlockModeMask) != 0) ? 256 : ((UInt32)1 << kNumMaxBits); unsigned numBits = kNumMinBits; UInt32 head = (blockSymbol == 256) ? 257 : 256; bool needPrev = false; @@ -91,18 +92,18 @@ HRESULT CDecoder::CodeReal(ISequentialInStream *inStream, ISequentialOutStream * { numBufBits = (unsigned)inBuffer.ReadBytes(buf, numBits) * 8; bitPos = 0; - UInt64 nowPos = outBuffer.GetProcessedSize(); + const UInt64 nowPos = outBuffer.GetProcessedSize(); if (progress && nowPos - prevPos >= (1 << 13)) { prevPos = nowPos; - UInt64 packSize = inBuffer.GetProcessedSize(); - RINOK(progress->SetRatioInfo(&packSize, &nowPos)); + const UInt64 packSize = inBuffer.GetProcessedSize(); + RINOK(progress->SetRatioInfo(&packSize, &nowPos)) } } - unsigned bytePos = bitPos >> 3; - UInt32 symbol = buf[bytePos] | ((UInt32)buf[bytePos + 1] << 8) | ((UInt32)buf[bytePos + 2] << 16); + const unsigned bytePos = bitPos >> 3; + UInt32 symbol = buf[bytePos] | ((UInt32)buf[(size_t)bytePos + 1] << 8) | ((UInt32)buf[(size_t)bytePos + 2] << 16); symbol >>= (bitPos & 7); - symbol &= (1 << numBits) - 1; + symbol &= ((UInt32)1 << numBits) - 1; bitPos += numBits; if (bitPos > numBufBits) break; @@ -129,7 +130,7 @@ HRESULT CDecoder::CodeReal(ISequentialInStream *inStream, ISequentialOutStream * _stack[i++] = (Byte)cur; if (needPrev) { - _suffixes[head - 1] = (Byte)cur; + _suffixes[(size_t)head - 1] = (Byte)cur; if (symbol == head - 1) _stack[0] = (Byte)cur; } @@ -152,34 +153,31 @@ HRESULT CDecoder::CodeReal(ISequentialInStream *inStream, ISequentialOutStream * else needPrev = false; } - PackSize = inBuffer.GetProcessedSize(); - HRESULT res2 = outBuffer.Flush(); + // PackSize = inBuffer.GetProcessedSize(); + const HRESULT res2 = outBuffer.Flush(); return (res == S_OK) ? res2 : res; -} - -STDMETHODIMP CDecoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress) -{ - try { return CodeReal(inStream, outStream, inSize, outSize, progress); } + + } catch(const CInBufferException &e) { return e.ErrorCode; } catch(const COutBufferException &e) { return e.ErrorCode; } catch(...) { return S_FALSE; } } + bool CheckStream(const Byte *data, size_t size) { if (size < 3) return false; if (data[0] != 0x1F || data[1] != 0x9D) return false; - Byte prop = data[2]; + const Byte prop = data[2]; if ((prop & 0x60) != 0) return false; - unsigned maxbits = prop & kNumBitsMask; + const unsigned maxbits = prop & kNumBitsMask; if (maxbits < kNumMinBits || maxbits > kNumMaxBits) return false; - UInt32 numItems = 1 << maxbits; - UInt32 blockSymbol = ((prop & kBlockModeMask) != 0) ? 256 : ((UInt32)1 << kNumMaxBits); + const UInt32 numItems = (UInt32)1 << maxbits; + const UInt32 blockSymbol = ((prop & kBlockModeMask) != 0) ? 256 : ((UInt32)1 << kNumMaxBits); unsigned numBits = kNumMinBits; UInt32 head = (blockSymbol == 256) ? 257 : 256; unsigned bitPos = 0; @@ -192,17 +190,17 @@ bool CheckStream(const Byte *data, size_t size) { if (numBufBits == bitPos) { - unsigned num = (numBits < size) ? numBits : (unsigned)size; + const unsigned num = (numBits < size) ? numBits : (unsigned)size; memcpy(buf, data, num); data += num; size -= num; numBufBits = num * 8; bitPos = 0; } - unsigned bytePos = bitPos >> 3; + const unsigned bytePos = bitPos >> 3; UInt32 symbol = buf[bytePos] | ((UInt32)buf[bytePos + 1] << 8) | ((UInt32)buf[bytePos + 2] << 16); symbol >>= (bitPos & 7); - symbol &= (1 << numBits) - 1; + symbol &= ((UInt32)1 << numBits) - 1; bitPos += numBits; if (bitPos > numBufBits) { diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/ZDecoder.h b/src/plugins/7zip/7za/cpp/7zip/Compress/ZDecoder.h index 19acd498..fd67f8f6 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/ZDecoder.h +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/ZDecoder.h @@ -1,7 +1,7 @@ // ZDecoder.h -#ifndef __COMPRESS_Z_DECODER_H -#define __COMPRESS_Z_DECODER_H +#ifndef ZIP7_INC_COMPRESS_Z_DECODER_H +#define ZIP7_INC_COMPRESS_Z_DECODER_H #include "../../Common/MyCom.h" @@ -12,9 +12,7 @@ namespace NZ { // Z decoder decodes Z data stream, including 3 bytes of header. -class CDecoder: - public ICompressCoder, - public CMyUnknownImp +class CDecoder { UInt16 *_parents; Byte *_suffixes; @@ -22,18 +20,13 @@ class CDecoder: unsigned _numMaxBits; public: - CDecoder(): _parents(0), _suffixes(0), _stack(0), /* _prop(0), */ _numMaxBits(0) {}; + CDecoder(): _parents(NULL), _suffixes(NULL), _stack(NULL), /* _prop(0), */ _numMaxBits(0) {} ~CDecoder(); void Free(); - UInt64 PackSize; + // UInt64 PackSize; - MY_UNKNOWN_IMP1(ICompressCoder) - - HRESULT CodeReal(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress); - - STDMETHOD(Code)(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress); + HRESULT Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, + ICompressProgressInfo *progress); }; /* diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/ZlibDecoder.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/ZlibDecoder.cpp index 8f3a63cb..dc018942 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/ZlibDecoder.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/ZlibDecoder.cpp @@ -2,6 +2,8 @@ #include "StdAfx.h" +#include "../../../C/CpuArch.h" + #include "../Common/StreamUtils.h" #include "ZlibDecoder.h" @@ -15,28 +17,45 @@ namespace NZlib { #define ADLER_MOD 65521 #define ADLER_LOOP_MAX 5550 -UInt32 Adler32_Update(UInt32 adler, const Byte *buf, size_t size) +UInt32 Adler32_Update(UInt32 adler, const Byte *data, size_t size); +UInt32 Adler32_Update(UInt32 adler, const Byte *data, size_t size) { - UInt32 a = adler & 0xFFFF; - UInt32 b = (adler >> 16) & 0xFFFF; - while (size > 0) + if (size == 0) + return adler; + UInt32 a = adler & 0xffff; + UInt32 b = adler >> 16; + do { - unsigned curSize = (size > ADLER_LOOP_MAX) ? ADLER_LOOP_MAX : (unsigned )size; - unsigned i; - for (i = 0; i < curSize; i++) + size_t cur = size; + if (cur > ADLER_LOOP_MAX) + cur = ADLER_LOOP_MAX; + size -= cur; + const Byte *lim = data + cur; + if (cur >= 4) { - a += buf[i]; - b += a; + lim -= 4 - 1; + do + { + a += data[0]; b += a; + a += data[1]; b += a; + a += data[2]; b += a; + a += data[3]; b += a; + data += 4; + } + while (data < lim); + lim += 4 - 1; } - buf += curSize; - size -= curSize; + if (data != lim) { a += *data++; b += a; + if (data != lim) { a += *data++; b += a; + if (data != lim) { a += *data++; b += a; }}} a %= ADLER_MOD; b %= ADLER_MOD; } + while (size); return (b << 16) + a; } -STDMETHODIMP COutStreamWithAdler::Write(const void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(COutStreamWithAdler::Write(const void *data, UInt32 size, UInt32 *processedSize)) { HRESULT result = S_OK; if (_stream) @@ -48,42 +67,67 @@ STDMETHODIMP COutStreamWithAdler::Write(const void *data, UInt32 size, UInt32 *p return result; } -STDMETHODIMP CDecoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress) +Z7_COM7F_IMF(CDecoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, + const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress)) { DEFLATE_TRY_BEGIN - if (!AdlerStream) - AdlerStream = AdlerSpec = new COutStreamWithAdler; - if (!DeflateDecoder) - { - DeflateDecoderSpec = new NDeflate::NDecoder::CCOMCoder; - DeflateDecoderSpec->ZlibMode = true; - DeflateDecoder = DeflateDecoderSpec; - } + _inputProcessedSize_Additional = 0; + AdlerStream.Create_if_Empty(); + DeflateDecoder.Create_if_Empty(); + DeflateDecoder->Set_NeedFinishInput(true); if (inSize && *inSize < 2) return S_FALSE; - Byte buf[2]; - RINOK(ReadStream_FALSE(inStream, buf, 2)); - if (!IsZlib(buf)) - return S_FALSE; - - AdlerSpec->SetStream(outStream); - AdlerSpec->Init(); - + { + Byte buf[2]; + RINOK(ReadStream_FALSE(inStream, buf, 2)) + if (!IsZlib(buf)) + return S_FALSE; + } + _inputProcessedSize_Additional = 2; + AdlerStream->SetStream(outStream); + AdlerStream->Init(); + // NDeflate::NDecoder::Code() ignores inSize + /* UInt64 inSize2 = 0; if (inSize) inSize2 = *inSize - 2; - - HRESULT res = DeflateDecoder->Code(inStream, AdlerStream, inSize ? &inSize2 : NULL, outSize, progress); - AdlerSpec->ReleaseStream(); + */ + const HRESULT res = DeflateDecoder.Interface()->Code(inStream, AdlerStream, + /* inSize ? &inSize2 : */ NULL, outSize, progress); + AdlerStream->ReleaseStream(); if (res == S_OK) { - const Byte *p = DeflateDecoderSpec->ZlibFooter; - UInt32 adler = ((UInt32)p[0] << 24) | ((UInt32)p[1] << 16) | ((UInt32)p[2] << 8) | p[3]; - if (adler != AdlerSpec->GetAdler()) - return S_FALSE; + UInt32 footer32[1]; + UInt32 processedSize; + RINOK(DeflateDecoder->ReadUnusedFromInBuf(footer32, 4, &processedSize)) + if (processedSize != 4) + { + size_t processedSize2 = 4 - processedSize; + RINOK(ReadStream(inStream, (Byte *)(void *)footer32 + processedSize, &processedSize2)) + _inputProcessedSize_Additional += (Int32)processedSize2; + processedSize += (UInt32)processedSize2; + } + + if (processedSize == 4) + { + const UInt32 adler = GetBe32a(footer32); + if (adler != AdlerStream->GetAdler()) + return S_FALSE; // adler error + } + else if (!IsAdlerOptional) + return S_FALSE; // unexpeced end of stream (can't read adler) + else + { + // IsAdlerOptional == true + if (processedSize != 0) + { + // we exclude adler bytes from processed size: + _inputProcessedSize_Additional -= (Int32)processedSize; + return S_FALSE; + } + } } return res; DEFLATE_TRY_END diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/ZlibDecoder.h b/src/plugins/7zip/7za/cpp/7zip/Compress/ZlibDecoder.h index 8c5e73b0..54886530 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/ZlibDecoder.h +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/ZlibDecoder.h @@ -1,7 +1,7 @@ // ZlibDecoder.h -#ifndef __ZLIB_DECODER_H -#define __ZLIB_DECODER_H +#ifndef ZIP7_INC_ZLIB_DECODER_H +#define ZIP7_INC_ZLIB_DECODER_H #include "DeflateDecoder.h" @@ -10,16 +10,14 @@ namespace NZlib { const UInt32 ADLER_INIT_VAL = 1; -class COutStreamWithAdler: - public ISequentialOutStream, - public CMyUnknownImp -{ - CMyComPtr _stream; +Z7_CLASS_IMP_NOQIB_1( + COutStreamWithAdler + , ISequentialOutStream +) UInt32 _adler; + CMyComPtr _stream; UInt64 _size; public: - MY_UNKNOWN_IMP - STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize); void SetStream(ISequentialOutStream *stream) { _stream = stream; } void ReleaseStream() { _stream.Release(); } void Init() { _adler = ADLER_INIT_VAL; _size = 0; } @@ -27,23 +25,24 @@ class COutStreamWithAdler: UInt64 GetSize() const { return _size; } }; -class CDecoder: - public ICompressCoder, - public CMyUnknownImp -{ - COutStreamWithAdler *AdlerSpec; - CMyComPtr AdlerStream; - - NCompress::NDeflate::NDecoder::CCOMCoder *DeflateDecoderSpec; - CMyComPtr DeflateDecoder; +Z7_CLASS_IMP_NOQIB_1( + CDecoder + , ICompressCoder +) + CMyComPtr2 AdlerStream; + CMyComPtr2 DeflateDecoder; + Int32 _inputProcessedSize_Additional; public: - STDMETHOD(Code)(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress); - - UInt64 GetInputProcessedSize() const { return DeflateDecoderSpec->GetInputProcessedSize() + 2; } - UInt64 GetOutputProcessedSize() const { return AdlerSpec->GetSize(); } - - MY_UNKNOWN_IMP + bool IsAdlerOptional; + + CDecoder(): IsAdlerOptional(false) {} + UInt64 GetInputProcessedSize() const + { + return (UInt64)( + (Int64)DeflateDecoder->GetInputProcessedSize() + + (Int64)_inputProcessedSize_Additional); + } + UInt64 GetOutputProcessedSize() const { return AdlerStream->GetSize(); } }; static bool inline IsZlib(const Byte *p) @@ -65,8 +64,8 @@ static bool inline IsZlib_3bytes(const Byte *p) { if (!IsZlib(p)) return false; - unsigned val = p[2]; - unsigned blockType = (val >> 1) & 0x3; + const unsigned val = p[2]; + const unsigned blockType = (val >> 1) & 0x3; if (blockType == 3) // unsupported block type for deflate return false; if (blockType == NCompress::NDeflate::NBlockType::kStored && (val >> 3) != 0) diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/ZlibEncoder.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/ZlibEncoder.cpp index 09235c33..3670d303 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/ZlibEncoder.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/ZlibEncoder.cpp @@ -14,12 +14,12 @@ namespace NZlib { UInt32 Adler32_Update(UInt32 adler, const Byte *buf, size_t size); -STDMETHODIMP CInStreamWithAdler::Read(void *data, UInt32 size, UInt32 *processedSize) +Z7_COM7F_IMF(CInStreamWithAdler::Read(void *data, UInt32 size, UInt32 *processedSize)) { - HRESULT result = _stream->Read(data, size, &size); + const HRESULT result = _stream->Read(data, size, &size); _adler = Adler32_Update(_adler, (const Byte *)data, size); _size += size; - if (processedSize != NULL) + if (processedSize) *processedSize = size; return result; } @@ -30,8 +30,8 @@ void CEncoder::Create() DeflateEncoder = DeflateEncoderSpec = new NDeflate::NEncoder::CCOMCoder; } -STDMETHODIMP CEncoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 *inSize, const UInt64 * /* outSize */, ICompressProgressInfo *progress) +Z7_COM7F_IMF(CEncoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, + const UInt64 *inSize, const UInt64 * /* outSize */, ICompressProgressInfo *progress)) { DEFLATE_TRY_BEGIN if (!AdlerStream) @@ -40,19 +40,19 @@ STDMETHODIMP CEncoder::Code(ISequentialInStream *inStream, ISequentialOutStream { Byte buf[2] = { 0x78, 0xDA }; - RINOK(WriteStream(outStream, buf, 2)); + RINOK(WriteStream(outStream, buf, 2)) } AdlerSpec->SetStream(inStream); AdlerSpec->Init(); - HRESULT res = DeflateEncoder->Code(AdlerStream, outStream, inSize, NULL, progress); + const HRESULT res = DeflateEncoder->Code(AdlerStream, outStream, inSize, NULL, progress); AdlerSpec->ReleaseStream(); - RINOK(res); + RINOK(res) { - UInt32 a = AdlerSpec->GetAdler(); - Byte buf[4] = { (Byte)(a >> 24), (Byte)(a >> 16), (Byte)(a >> 8), (Byte)(a) }; + const UInt32 a = AdlerSpec->GetAdler(); + const Byte buf[4] = { (Byte)(a >> 24), (Byte)(a >> 16), (Byte)(a >> 8), (Byte)(a) }; return WriteStream(outStream, buf, 4); } DEFLATE_TRY_END diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/ZlibEncoder.h b/src/plugins/7zip/7za/cpp/7zip/Compress/ZlibEncoder.h index 621cc1d0..484a307c 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Compress/ZlibEncoder.h +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/ZlibEncoder.h @@ -1,23 +1,21 @@ // ZlibEncoder.h -#ifndef __ZLIB_ENCODER_H -#define __ZLIB_ENCODER_H +#ifndef ZIP7_INC_ZLIB_ENCODER_H +#define ZIP7_INC_ZLIB_ENCODER_H #include "DeflateEncoder.h" namespace NCompress { namespace NZlib { -class CInStreamWithAdler: - public ISequentialInStream, - public CMyUnknownImp -{ +Z7_CLASS_IMP_NOQIB_1( + CInStreamWithAdler + , ISequentialInStream +) CMyComPtr _stream; UInt32 _adler; UInt64 _size; public: - MY_UNKNOWN_IMP - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize); void SetStream(ISequentialInStream *stream) { _stream = stream; } void ReleaseStream() { _stream.Release(); } void Init() { _adler = 1; _size = 0; } // ADLER_INIT_VAL @@ -25,10 +23,10 @@ class CInStreamWithAdler: UInt64 GetSize() const { return _size; } }; -class CEncoder: - public ICompressCoder, - public CMyUnknownImp -{ +Z7_CLASS_IMP_NOQIB_1( + CEncoder + , ICompressCoder +) CInStreamWithAdler *AdlerSpec; CMyComPtr AdlerStream; CMyComPtr DeflateEncoder; @@ -36,11 +34,7 @@ class CEncoder: NCompress::NDeflate::NEncoder::CCOMCoder *DeflateEncoderSpec; void Create(); - STDMETHOD(Code)(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress); UInt64 GetInputProcessedSize() const { return AdlerSpec->GetSize(); } - - MY_UNKNOWN_IMP }; }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/ZstdDecoder.cpp b/src/plugins/7zip/7za/cpp/7zip/Compress/ZstdDecoder.cpp new file mode 100644 index 00000000..c9584756 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/ZstdDecoder.cpp @@ -0,0 +1,437 @@ +// ZstdDecoder.cpp + +#include "StdAfx.h" + +// #include + +#include "../../../C/Alloc.h" + +#include "../Common/CWrappers.h" +#include "../Common/StreamUtils.h" + +#include "ZstdDecoder.h" + +namespace NCompress { +namespace NZstd { + +static const size_t k_Zstd_BlockSizeMax = 1 << 17; +/* + we set _outStepMask as (k_Zstd_BlockSizeMax - 1), because: + - cycSize in zstd decoder for isCyclicMode is aligned for (1 << 17) only. + So some write sizes will be multiple of ((1 << 17) * n). + - Also it can be optimal to flush data after each block decoding. +*/ + +CDecoder::CDecoder(): + _outStepMask(k_Zstd_BlockSizeMax - 1) // must be = (1 << x) - 1 + , _dec(NULL) + , _inProcessed(0) + , _inBufSize(1u << 19) // larger value will reduce the number of memcpy() calls in CZstdDec code + , _inBuf(NULL) + , FinishMode(false) + , DisableHash(False) + // , DisableHash(True) // for debug : fast decoding without hash calculation +{ + // ZstdDecInfo_Clear(&ResInfo); +} + +CDecoder::~CDecoder() +{ + if (_dec) + ZstdDec_Destroy(_dec); + MidFree(_inBuf); +} + + +Z7_COM7F_IMF(CDecoder::SetInBufSize(UInt32 , UInt32 size)) + { _inBufSize = size; return S_OK; } +Z7_COM7F_IMF(CDecoder::SetOutBufSize(UInt32 , UInt32 size)) +{ + // we round it down: + size >>= 1; + size |= size >> (1 << 0); + size |= size >> (1 << 1); + size |= size >> (1 << 2); + size |= size >> (1 << 3); + size |= size >> (1 << 4); + _outStepMask = size; // it's (1 << x) - 1 now + return S_OK; +} + +Z7_COM7F_IMF(CDecoder::SetDecoderProperties2(const Byte * /* prop */, UInt32 /* size */)) +{ + // if (size != 3 && size != 5) return E_NOTIMPL; + return S_OK; +} + + +Z7_COM7F_IMF(CDecoder::SetFinishMode(UInt32 finishMode)) +{ + FinishMode = (finishMode != 0); + // FinishMode = false; // for debug + return S_OK; +} + + +Z7_COM7F_IMF(CDecoder::ReadUnusedFromInBuf(void *data, UInt32 size, UInt32 *processedSize)) +{ + size_t cur = ZstdDec_ReadUnusedFromInBuf(_dec, _afterDecoding_tempPos, data, size); + _afterDecoding_tempPos += cur; + size -= (UInt32)cur; + if (size) + { + const size_t rem = _state.inLim - _state.inPos; + if (size > rem) + size = (UInt32)rem; + if (size) + { + memcpy((Byte *)data + cur, _state.inBuf + _state.inPos, size); + _state.inPos += size; + cur += size; + } + } + *processedSize = (UInt32)cur; + return S_OK; +} + + + +HRESULT CDecoder::Prepare(const UInt64 *outSize) +{ + _inProcessed = 0; + _afterDecoding_tempPos = 0; + ZstdDecState_Clear(&_state); + ZstdDecInfo_CLEAR(&ResInfo) + // _state.outStep = _outStepMask + 1; // must be = (1 << x) + _state.disableHash = DisableHash; + if (outSize) + { + _state.outSize_Defined = True; + _state.outSize = *outSize; + // _state.outSize = 0; // for debug + } + if (!_dec) + { + _dec = ZstdDec_Create(&g_AlignedAlloc, &g_BigAlloc); + if (!_dec) + return E_OUTOFMEMORY; + } + if (!_inBuf || _inBufSize != _inBufSize_Allocated) + { + MidFree(_inBuf); + _inBuf = NULL; + _inBufSize_Allocated = 0; + _inBuf = (Byte *)MidAlloc(_inBufSize); + if (!_inBuf) + return E_OUTOFMEMORY; + _inBufSize_Allocated = _inBufSize; + } + _state.inBuf = _inBuf; + ZstdDec_Init(_dec); + return S_OK; +} + + +Z7_COM7F_IMF(CDecoder::Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, + const UInt64 *inSize, const UInt64 *outSize, ICompressProgressInfo *progress)) +{ + RINOK(Prepare(outSize)) + + UInt64 inPrev = 0; + UInt64 outPrev = 0; + UInt64 writtenSize = 0; + bool readWasFinished = false; + SRes sres = SZ_OK; + HRESULT hres = S_OK; + HRESULT hres_Read = S_OK; + + for (;;) + { + if (_state.inPos == _state.inLim && !readWasFinished) + { + _state.inPos = 0; + _state.inLim = _inBufSize; + hres_Read = ReadStream(inStream, _inBuf, &_state.inLim); + // _state.inLim -= 5; readWasFinished = True; // for debug + if (_state.inLim != _inBufSize || hres_Read != S_OK) + { + // hres_Read = 99; // for debug + readWasFinished = True; + } + } + { + const size_t inPos_Start = _state.inPos; + sres = ZstdDec_Decode(_dec, &_state); + _inProcessed += _state.inPos - inPos_Start; + } + /* + if (_state.status == ZSTD_STATUS_FINISHED_FRAME) + printf("\nfinished frame pos=%8x, checksum=%08x\n", (unsigned)_state.outProcessed, (unsigned)_state.info.checksum); + */ + const bool needStop = (sres != SZ_OK) + || _state.status == ZSTD_STATUS_OUT_REACHED + || (outSize && *outSize < _state.outProcessed) + || (readWasFinished && _state.inPos == _state.inLim + && ZstdDecState_DOES_NEED_MORE_INPUT_OR_FINISHED_FRAME(&_state)); + + size_t size = _state.winPos - _state.wrPos; // full write size + if (size) + { + if (!needStop) + { + // we try to flush on aligned positions, if possible + size = _state.needWrite_Size; // minimal required write size + const size_t alignedPos = _state.winPos & ~(size_t)_outStepMask; + if (alignedPos > _state.wrPos) + { + const size_t size2 = alignedPos - _state.wrPos; // optimized aligned size + if (size < size2) + size = size2; + } + } + if (size) + { + { + size_t curSize = size; + if (outSize) + { + const UInt64 rem = *outSize - writtenSize; + if (curSize > rem) + curSize = (size_t)rem; + } + if (curSize) + { + // printf("Write wrPos=%8x, size=%8x\n", (unsigned)_state.wrPos, (unsigned)size); + hres = WriteStream(outStream, _state.win + _state.wrPos, curSize); + if (hres != S_OK) + break; + writtenSize += curSize; // it's real size of data that was written to stream + } + } + _state.wrPos += size; // virtual written size, that will be reported to CZstdDec + // _state.needWrite_Size = 0; // optional + } + } + + if (needStop) + break; + if (progress) + if (_inProcessed - inPrev >= (1 << 27) + || _state.outProcessed - outPrev >= (1 << 28)) + { + inPrev = _inProcessed; + outPrev = _state.outProcessed; + RINOK(progress->SetRatioInfo(&inPrev, &outPrev)) + } + } + + if (hres == S_OK) + { + ZstdDec_GetResInfo(_dec, &_state, sres, &ResInfo); + sres = ResInfo.decode_SRes; + /* now (ResInfo.decode_SRes) can contain 2 extra error codes: + - SZ_ERROR_NO_ARCHIVE : if no frames + - SZ_ERROR_INPUT_EOF : if ZSTD_STATUS_NEEDS_MORE_INPUT + */ + _inProcessed -= ResInfo.extraSize; + if (hres_Read != S_OK && _state.inLim == _state.inPos && readWasFinished) + { + /* if (there is stream reading error, + and decoding was stopped because of end of input stream), + then we use reading error as main error code */ + if (sres == SZ_OK || + sres == SZ_ERROR_INPUT_EOF || + sres == SZ_ERROR_NO_ARCHIVE) + hres = hres_Read; + } + if (sres == SZ_ERROR_INPUT_EOF && !FinishMode) + { + /* SZ_ERROR_INPUT_EOF case is allowed case for (!FinishMode) mode. + So we restore SZ_OK result for that case: */ + ResInfo.decode_SRes = sres = SZ_OK; + } + if (hres == S_OK) + { + hres = SResToHRESULT(sres); + if (hres == S_OK && FinishMode) + { + if ((inSize && *inSize != _inProcessed) + || ResInfo.is_NonFinishedFrame + || (outSize && (*outSize != writtenSize || writtenSize != _state.outProcessed))) + hres = S_FALSE; + } + } + } + return hres; +} + + +Z7_COM7F_IMF(CDecoder::GetInStreamProcessedSize(UInt64 *value)) +{ + *value = _inProcessed; + return S_OK; +} + + +#ifndef Z7_NO_READ_FROM_CODER_ZSTD + +Z7_COM7F_IMF(CDecoder::SetOutStreamSize(const UInt64 *outSize)) +{ + _inProcessed = 0; + _hres_Read = S_OK; + _hres_Decode = S_OK; + _writtenSize = 0; + _readWasFinished = false; + _wasFinished = false; + return Prepare(outSize); +} + + +Z7_COM7F_IMF(CDecoder::SetInStream(ISequentialInStream *inStream)) + { _inStream = inStream; return S_OK; } +Z7_COM7F_IMF(CDecoder::ReleaseInStream()) + { _inStream.Release(); return S_OK; } + + +// if SetInStream() mode: the caller must call GetFinishResult() after full decoding +// to check that there decoding was finished correctly + +HRESULT CDecoder::GetFinishResult() +{ + if (_state.winPos != _state.wrPos || !_wasFinished) + return FinishMode ? S_FALSE : S_OK; + // _state.winPos == _state.wrPos + // _wasFinished == true + if (FinishMode && _hres_Decode == S_OK && _state.outSize_Defined && _state.outSize != _writtenSize) + _hres_Decode = S_FALSE; + return _hres_Decode; +} + + +Z7_COM7F_IMF(CDecoder::Read(void *data, UInt32 size, UInt32 *processedSize)) +{ + if (processedSize) + *processedSize = 0; + + for (;;) + { + if (_state.outSize_Defined) + { + // _writtenSize <= _state.outSize + const UInt64 rem = _state.outSize - _writtenSize; + if (size > rem) + size = (UInt32)rem; + } + { + size_t cur = _state.winPos - _state.wrPos; + if (cur) + { + // _state.winPos != _state.wrPos; + // so there is some decoded data that was not written still + if (size == 0) + { + // if (FinishMode) and we are not allowed to write more, then it's data error + if (FinishMode && _state.outSize_Defined && _state.outSize == _writtenSize) + return S_FALSE; + return S_OK; + } + if (cur > size) + cur = (size_t)size; + // cur != 0 + memcpy(data, _state.win + _state.wrPos, cur); + _state.wrPos += cur; + _writtenSize += cur; + data = (void *)((Byte *)data + cur); + if (processedSize) + *processedSize += (UInt32)cur; + size -= (UInt32)cur; + continue; + } + } + + // _state.winPos == _state.wrPos + if (_wasFinished) + { + if (_hres_Decode == S_OK && FinishMode + && _state.outSize_Defined && _state.outSize != _writtenSize) + _hres_Decode = S_FALSE; + return _hres_Decode; + } + + // _wasFinished == false + if (size == 0 && _state.outSize_Defined && _state.outSize != _state.outProcessed) + { + /* size == 0 : so the caller don't need more data now. + _state.outSize > _state.outProcessed : so more data will be requested + later by caller for full processing. + So we exit without ZstdDec_Decode() call, because we don't want + ZstdDec_Decode() to start new block decoding + */ + return S_OK; + } + // size != 0 || !_state.outSize_Defined || _state.outSize == _state.outProcessed) + + if (_state.inPos == _state.inLim && !_readWasFinished) + { + _state.inPos = 0; + _state.inLim = _inBufSize; + _hres_Read = ReadStream(_inStream, _inBuf, &_state.inLim); + if (_state.inLim != _inBufSize || _hres_Read != S_OK) + { + // _hres_Read = 99; // for debug + _readWasFinished = True; + } + } + + SRes sres; + { + const SizeT inPos_Start = _state.inPos; + sres = ZstdDec_Decode(_dec, &_state); + _inProcessed += _state.inPos - inPos_Start; + } + + const bool inFinished = (_state.inPos == _state.inLim) && _readWasFinished; + + _wasFinished = (sres != SZ_OK) + || _state.status == ZSTD_STATUS_OUT_REACHED + || (_state.outSize_Defined && _state.outSize < _state.outProcessed) + || (inFinished + && ZstdDecState_DOES_NEED_MORE_INPUT_OR_FINISHED_FRAME(&_state)); + + if (!_wasFinished) + continue; + + // _wasFinished == true + /* (_state.winPos != _state.wrPos) is possible here. + So we still can have some data to flush, + but we must all result variables . + */ + HRESULT hres = S_OK; + ZstdDec_GetResInfo(_dec, &_state, sres, &ResInfo); + sres = ResInfo.decode_SRes; + _inProcessed -= ResInfo.extraSize; + if (_hres_Read != S_OK && inFinished) + { + if (sres == SZ_OK || + sres == SZ_ERROR_INPUT_EOF || + sres == SZ_ERROR_NO_ARCHIVE) + hres = _hres_Read; + } + if (sres == SZ_ERROR_INPUT_EOF && !FinishMode) + ResInfo.decode_SRes = sres = SZ_OK; + if (hres == S_OK) + { + hres = SResToHRESULT(sres); + if (hres == S_OK && FinishMode) + if (!inFinished + || ResInfo.is_NonFinishedFrame + || (_state.outSize_Defined && _state.outSize != _state.outProcessed)) + hres = S_FALSE; + } + _hres_Decode = hres; + } +} + +#endif + +}} diff --git a/src/plugins/7zip/7za/cpp/7zip/Compress/ZstdDecoder.h b/src/plugins/7zip/7za/cpp/7zip/Compress/ZstdDecoder.h new file mode 100644 index 00000000..6e15a964 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Compress/ZstdDecoder.h @@ -0,0 +1,98 @@ +// ZstdDecoder.h + +#ifndef ZIP7_INC_ZSTD_DECODER_H +#define ZIP7_INC_ZSTD_DECODER_H + +#include "../../../C/ZstdDec.h" + +#include "../../Common/MyCom.h" +#include "../ICoder.h" + +namespace NCompress { +namespace NZstd { + +#ifdef Z7_NO_READ_FROM_CODER +#define Z7_NO_READ_FROM_CODER_ZSTD +#endif + +#ifndef Z7_NO_READ_FROM_CODER_ZSTD +// #define Z7_NO_READ_FROM_CODER_ZSTD +#endif + +class CDecoder Z7_final: + public ICompressCoder, + public ICompressSetDecoderProperties2, + public ICompressSetFinishMode, + public ICompressGetInStreamProcessedSize, + public ICompressReadUnusedFromInBuf, + public ICompressSetBufSize, + #ifndef Z7_NO_READ_FROM_CODER_ZSTD + public ICompressSetInStream, + public ICompressSetOutStreamSize, + public ISequentialInStream, + #endif + public CMyUnknownImp +{ + Z7_COM_QI_BEGIN2(ICompressCoder) + Z7_COM_QI_ENTRY(ICompressSetDecoderProperties2) + Z7_COM_QI_ENTRY(ICompressSetFinishMode) + Z7_COM_QI_ENTRY(ICompressGetInStreamProcessedSize) + Z7_COM_QI_ENTRY(ICompressReadUnusedFromInBuf) + Z7_COM_QI_ENTRY(ICompressSetBufSize) + #ifndef Z7_NO_READ_FROM_CODER_ZSTD + Z7_COM_QI_ENTRY(ICompressSetInStream) + Z7_COM_QI_ENTRY(ICompressSetOutStreamSize) + Z7_COM_QI_ENTRY(ISequentialInStream) + #endif + Z7_COM_QI_END + Z7_COM_ADDREF_RELEASE + + Z7_IFACE_COM7_IMP(ICompressCoder) + Z7_IFACE_COM7_IMP(ICompressSetDecoderProperties2) + Z7_IFACE_COM7_IMP(ICompressSetFinishMode) + Z7_IFACE_COM7_IMP(ICompressGetInStreamProcessedSize) + Z7_IFACE_COM7_IMP(ICompressReadUnusedFromInBuf) + Z7_IFACE_COM7_IMP(ICompressSetBufSize) + #ifndef Z7_NO_READ_FROM_CODER_ZSTD + Z7_IFACE_COM7_IMP(ICompressSetOutStreamSize) + Z7_IFACE_COM7_IMP(ICompressSetInStream) + Z7_IFACE_COM7_IMP(ISequentialInStream) + #endif + + HRESULT Prepare(const UInt64 *outSize); + + UInt32 _outStepMask; + CZstdDecHandle _dec; +public: + UInt64 _inProcessed; + CZstdDecState _state; + +private: + UInt32 _inBufSize; + UInt32 _inBufSize_Allocated; + Byte *_inBuf; + size_t _afterDecoding_tempPos; + + #ifndef Z7_NO_READ_FROM_CODER_ZSTD + CMyComPtr _inStream; + HRESULT _hres_Read; + HRESULT _hres_Decode; + UInt64 _writtenSize; + bool _readWasFinished; + bool _wasFinished; + #endif + +public: + bool FinishMode; + Byte DisableHash; + CZstdDecResInfo ResInfo; + + HRESULT GetFinishResult(); + + CDecoder(); + ~CDecoder(); +}; + +}} + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Crc.mak b/src/plugins/7zip/7za/cpp/7zip/Crc.mak new file mode 100644 index 00000000..938fc7e9 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Crc.mak @@ -0,0 +1,8 @@ +C_OBJS = $(C_OBJS) \ + $O\7zCrc.obj +!IF defined(USE_NO_ASM) || defined(USE_C_CRC) || "$(PLATFORM)" == "ia64" || "$(PLATFORM)" == "mips" || "$(PLATFORM)" == "arm" || "$(PLATFORM)" == "arm64" +C_OBJS = $(C_OBJS) \ +!ELSE +ASM_OBJS = $(ASM_OBJS) \ +!ENDIF + $O\7zCrcOpt.obj diff --git a/src/plugins/7zip/7za/cpp/7zip/Crc64.mak b/src/plugins/7zip/7za/cpp/7zip/Crc64.mak new file mode 100644 index 00000000..27a50d7b --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Crc64.mak @@ -0,0 +1,8 @@ +C_OBJS = $(C_OBJS) \ + $O\XzCrc64.obj +!IF defined(USE_NO_ASM) || defined(USE_C_CRC64) || "$(PLATFORM)" == "ia64" || "$(PLATFORM)" == "mips" || "$(PLATFORM)" == "arm" || "$(PLATFORM)" == "arm64" +C_OBJS = $(C_OBJS) \ +!ELSE +ASM_OBJS = $(ASM_OBJS) \ +!ENDIF + $O\XzCrc64Opt.obj diff --git a/src/plugins/7zip/7za/cpp/7zip/Crypto/7zAES.cpp b/src/plugins/7zip/7za/cpp/7zip/Crypto/7zAES.cpp index d33b562a..b2031050 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Crypto/7zAES.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Crypto/7zAES.cpp @@ -2,11 +2,13 @@ #include "StdAfx.h" +#include "../../../C/CpuArch.h" #include "../../../C/Sha256.h" #include "../../Common/ComTry.h" +#include "../../Common/MyBuffer2.h" -#ifndef _7ZIP_ST +#ifndef Z7_ST #include "../../Windows/Synchronization.h" #endif @@ -15,7 +17,7 @@ #include "7zAes.h" #include "MyAes.h" -#ifndef EXTRACT_ONLY +#ifndef Z7_EXTRACT_ONLY #include "RandGen.h" #endif @@ -48,31 +50,66 @@ void CKeyInfo::CalcKey() } else { - size_t bufSize = 8 + SaltSize + Password.Size(); - CObjArray buf(bufSize); + const unsigned kUnrPow = 6; + const UInt32 numUnroll = (UInt32)1 << (NumCyclesPower <= kUnrPow ? (unsigned)NumCyclesPower : kUnrPow); + + const size_t bufSize = 8 + SaltSize + Password.Size(); + const size_t unrollSize = bufSize * numUnroll; + + // MY_ALIGN (16) + // CSha256 sha; + const size_t shaAllocSize = sizeof(CSha256) + unrollSize + bufSize * 2; + CAlignedBuffer1 sha(shaAllocSize); + Byte *buf = sha + sizeof(CSha256); + memcpy(buf, Salt, SaltSize); memcpy(buf + SaltSize, Password, Password.Size()); + memset(buf + bufSize - 8, 0, 8); - CSha256 sha; - Sha256_Init(&sha); - - Byte *ctr = buf + SaltSize + Password.Size(); - - for (unsigned i = 0; i < 8; i++) - ctr[i] = 0; + Sha256_Init((CSha256 *)(void *)(Byte *)sha); + { + { + Byte *dest = buf; + for (UInt32 i = 1; i < numUnroll; i++) + { + dest += bufSize; + memcpy(dest, buf, bufSize); + } + } + + const UInt32 numRounds = (UInt32)1 << NumCyclesPower; + UInt32 r = 0; + do + { + Byte *dest = buf + bufSize - 8; + UInt32 i = r; + r += numUnroll; + do + { + SetUi32(dest, i) i++; dest += bufSize; + // SetUi32(dest, i) i++; dest += bufSize; + } + while (i < r); + Sha256_Update((CSha256 *)(void *)(Byte *)sha, buf, unrollSize); + } + while (r < numRounds); + } + /* UInt64 numRounds = (UInt64)1 << NumCyclesPower; do { - Sha256_Update(&sha, buf, bufSize); + Sha256_Update((CSha256 *)(Byte *)sha, buf, bufSize); for (unsigned i = 0; i < 8; i++) if (++(ctr[i]) != 0) break; } while (--numRounds != 0); + */ - Sha256_Final(&sha, Key); + Sha256_Final((CSha256 *)(void *)(Byte *)sha, Key); + memset(sha, 0, shaAllocSize); } } @@ -117,7 +154,7 @@ void CKeyInfoCache::Add(const CKeyInfo &key) static CKeyInfoCache g_GlobalKeyCache(32); -#ifndef _7ZIP_ST +#ifndef Z7_ST static NWindows::NSynchronization::CCriticalSection g_GlobalKeyCacheCriticalSection; #define MT_LOCK NWindows::NSynchronization::CCriticalSectionLock lock(g_GlobalKeyCacheCriticalSection); #else @@ -149,10 +186,10 @@ void CBase::PrepareKey() g_GlobalKeyCache.FindAndAdd(_key); } -#ifndef EXTRACT_ONLY +#ifndef Z7_EXTRACT_ONLY /* -STDMETHODIMP CEncoder::ResetSalt() +Z7_COM7F_IMF(CEncoder::ResetSalt()) { _key.SaltSize = 4; g_RandomGenerator.Generate(_key.Salt, _key.SaltSize); @@ -160,16 +197,16 @@ STDMETHODIMP CEncoder::ResetSalt() } */ -STDMETHODIMP CEncoder::ResetInitVector() +Z7_COM7F_IMF(CEncoder::ResetInitVector()) { for (unsigned i = 0; i < sizeof(_iv); i++) _iv[i] = 0; - _ivSize = 8; - g_RandomGenerator.Generate(_iv, _ivSize); + _ivSize = 16; + MY_RAND_GEN(_iv, _ivSize); return S_OK; } -STDMETHODIMP CEncoder::WriteCoderProperties(ISequentialOutStream *outStream) +Z7_COM7F_IMF(CEncoder::WriteCoderProperties(ISequentialOutStream *outStream)) { Byte props[2 + sizeof(_key.Salt) + sizeof(_iv)]; unsigned propsSize = 1; @@ -207,7 +244,7 @@ CDecoder::CDecoder() _aesFilter = new CAesCbcDecoder(kKeySize); } -STDMETHODIMP CDecoder::SetDecoderProperties2(const Byte *data, UInt32 size) +Z7_COM7F_IMF(CDecoder::SetDecoderProperties2(const Byte *data, UInt32 size)) { _key.ClearProps(); @@ -219,19 +256,16 @@ STDMETHODIMP CDecoder::SetDecoderProperties2(const Byte *data, UInt32 size) if (size == 0) return S_OK; - Byte b0 = data[0]; - + const unsigned b0 = data[0]; _key.NumCyclesPower = b0 & 0x3F; if ((b0 & 0xC0) == 0) return size == 1 ? S_OK : E_INVALIDARG; - if (size <= 1) return E_INVALIDARG; - Byte b1 = data[1]; - - unsigned saltSize = ((b0 >> 7) & 1) + (b1 >> 4); - unsigned ivSize = ((b0 >> 6) & 1) + (b1 & 0x0F); + const unsigned b1 = data[1]; + const unsigned saltSize = ((b0 >> 7) & 1) + (b1 >> 4); + const unsigned ivSize = ((b0 >> 6) & 1) + (b1 & 0x0F); if (size != 2 + saltSize + ivSize) return E_INVALIDARG; @@ -246,33 +280,34 @@ STDMETHODIMP CDecoder::SetDecoderProperties2(const Byte *data, UInt32 size) } -STDMETHODIMP CBaseCoder::CryptoSetPassword(const Byte *data, UInt32 size) +Z7_COM7F_IMF(CBaseCoder::CryptoSetPassword(const Byte *data, UInt32 size)) { COM_TRY_BEGIN + _key.Password.Wipe(); _key.Password.CopyFrom(data, (size_t)size); return S_OK; COM_TRY_END } -STDMETHODIMP CBaseCoder::Init() +Z7_COM7F_IMF(CBaseCoder::Init()) { COM_TRY_BEGIN PrepareKey(); CMyComPtr cp; - RINOK(_aesFilter.QueryInterface(IID_ICryptoProperties, &cp)); + RINOK(_aesFilter.QueryInterface(IID_ICryptoProperties, &cp)) if (!cp) return E_FAIL; - RINOK(cp->SetKey(_key.Key, kKeySize)); - RINOK(cp->SetInitVector(_iv, sizeof(_iv))); + RINOK(cp->SetKey(_key.Key, kKeySize)) + RINOK(cp->SetInitVector(_iv, sizeof(_iv))) return _aesFilter->Init(); COM_TRY_END } -STDMETHODIMP_(UInt32) CBaseCoder::Filter(Byte *data, UInt32 size) +Z7_COM7F_IMF2(UInt32, CBaseCoder::Filter(Byte *data, UInt32 size)) { return _aesFilter->Filter(data, size); } diff --git a/src/plugins/7zip/7za/cpp/7zip/Crypto/7zAES.h b/src/plugins/7zip/7za/cpp/7zip/Crypto/7zAES.h index 84e07ac9..8f7bf03e 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Crypto/7zAES.h +++ b/src/plugins/7zip/7za/cpp/7zip/Crypto/7zAES.h @@ -1,7 +1,7 @@ // 7zAes.h -#ifndef __CRYPTO_7Z_AES_H -#define __CRYPTO_7Z_AES_H +#ifndef ZIP7_INC_CRYPTO_7Z_AES_H +#define ZIP7_INC_CRYPTO_7Z_AES_H #include "../../Common/MyBuffer.h" #include "../../Common/MyCom.h" @@ -37,6 +37,20 @@ class CKeyInfo for (unsigned i = 0; i < sizeof(Salt); i++) Salt[i] = 0; } + + void Wipe() + { + Password.Wipe(); + NumCyclesPower = 0; + SaltSize = 0; + Z7_memset_0_ARRAY(Salt); + Z7_memset_0_ARRAY(Key); + } + +#ifdef Z7_CPP_IS_SUPPORTED_default + CKeyInfo(const CKeyInfo &) = default; +#endif + ~CKeyInfo() { Wipe(); } }; class CKeyInfoCache @@ -68,48 +82,46 @@ class CBaseCoder: public CMyUnknownImp, public CBase { + Z7_IFACE_COM7_IMP(ICompressFilter) + Z7_IFACE_COM7_IMP(ICryptoSetPassword) protected: + virtual ~CBaseCoder() {} CMyComPtr _aesFilter; - -public: - INTERFACE_ICompressFilter(;) - - STDMETHOD(CryptoSetPassword)(const Byte *data, UInt32 size); }; -#ifndef EXTRACT_ONLY +#ifndef Z7_EXTRACT_ONLY -class CEncoder: +class CEncoder Z7_final: public CBaseCoder, public ICompressWriteCoderProperties, // public ICryptoResetSalt, public ICryptoResetInitVector { -public: - MY_UNKNOWN_IMP4( + Z7_COM_UNKNOWN_IMP_4( ICompressFilter, ICryptoSetPassword, ICompressWriteCoderProperties, // ICryptoResetSalt, ICryptoResetInitVector) - STDMETHOD(WriteCoderProperties)(ISequentialOutStream *outStream); - // STDMETHOD(ResetSalt)(); - STDMETHOD(ResetInitVector)(); + Z7_IFACE_COM7_IMP(ICompressWriteCoderProperties) + // Z7_IFACE_COM7_IMP(ICryptoResetSalt) + Z7_IFACE_COM7_IMP(ICryptoResetInitVector) +public: CEncoder(); }; #endif -class CDecoder: +class CDecoder Z7_final: public CBaseCoder, public ICompressSetDecoderProperties2 { -public: - MY_UNKNOWN_IMP3( + Z7_COM_UNKNOWN_IMP_3( ICompressFilter, ICryptoSetPassword, ICompressSetDecoderProperties2) - STDMETHOD(SetDecoderProperties2)(const Byte *data, UInt32 size); + Z7_IFACE_COM7_IMP(ICompressSetDecoderProperties2) +public: CDecoder(); }; diff --git a/src/plugins/7zip/7za/cpp/7zip/Crypto/7zAESRegister.cpp b/src/plugins/7zip/7za/cpp/7zip/Crypto/7zAESRegister.cpp index f9d59699..69d0890a 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Crypto/7zAESRegister.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Crypto/7zAESRegister.cpp @@ -9,9 +9,9 @@ namespace NCrypto { namespace N7z { -REGISTER_FILTER_E(7zAES, - CDecoder(), - CEncoder(), +REGISTER_FILTER_E(SzAES, + CDecoder, + CEncoder, 0x6F10701, "7zAES") }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Crypto/HmacSha1.cpp b/src/plugins/7zip/7za/cpp/7zip/Crypto/HmacSha1.cpp index 359b65bc..30b1a8f6 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Crypto/HmacSha1.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Crypto/HmacSha1.cpp @@ -2,6 +2,8 @@ #include "StdAfx.h" +#include + #include "../../../C/CpuArch.h" #include "HmacSha1.h" @@ -11,110 +13,82 @@ namespace NSha1 { void CHmac::SetKey(const Byte *key, size_t keySize) { - Byte keyTemp[kBlockSize]; + MY_ALIGN (16) + UInt32 temp[SHA1_NUM_BLOCK_WORDS]; size_t i; - for (i = 0; i < kBlockSize; i++) - keyTemp[i] = 0; + for (i = 0; i < SHA1_NUM_BLOCK_WORDS; i++) + temp[i] = 0; if (keySize > kBlockSize) { _sha.Init(); _sha.Update(key, keySize); - _sha.Final(keyTemp); + _sha.Final((Byte *)temp); } else - for (i = 0; i < keySize; i++) - keyTemp[i] = key[i]; + memcpy(temp, key, keySize); - for (i = 0; i < kBlockSize; i++) - keyTemp[i] ^= 0x36; + for (i = 0; i < SHA1_NUM_BLOCK_WORDS; i++) + temp[i] ^= 0x36363636; _sha.Init(); - _sha.Update(keyTemp, kBlockSize); + _sha.Update((const Byte *)temp, kBlockSize); - for (i = 0; i < kBlockSize; i++) - keyTemp[i] ^= 0x36 ^ 0x5C; + for (i = 0; i < SHA1_NUM_BLOCK_WORDS; i++) + temp[i] ^= 0x36363636 ^ 0x5C5C5C5C; _sha2.Init(); - _sha2.Update(keyTemp, kBlockSize); + _sha2.Update((const Byte *)temp, kBlockSize); } -void CHmac::Final(Byte *mac, size_t macSize) + +void CHmac::Final(Byte *mac) { - Byte digest[kDigestSize]; - _sha.Final(digest); - _sha2.Update(digest, kDigestSize); - _sha2.Final(digest); - for (size_t i = 0; i < macSize; i++) - mac[i] = digest[i]; + _sha.Final(mac); + _sha2.Update(mac, kDigestSize); + _sha2.Final(mac); } -void CHmac32::SetKey(const Byte *key, size_t keySize) +void CHmac::GetLoopXorDigest1(void *mac, UInt32 numIteration) { - UInt32 keyTemp[kNumBlockWords]; - size_t i; - - for (i = 0; i < kNumBlockWords; i++) - keyTemp[i] = 0; - - if (keySize > kBlockSize) - { - CContext sha; - sha.Init(); - sha.Update(key, keySize); - Byte digest[kDigestSize]; - sha.Final(digest); - - for (i = 0 ; i < kNumDigestWords; i++) - keyTemp[i] = GetBe32(digest + i * 4 + 0); - } - else - for (i = 0; i < keySize; i++) - keyTemp[i / 4] |= (key[i] << (24 - 8 * (i & 3))); - - for (i = 0; i < kNumBlockWords; i++) - keyTemp[i] ^= 0x36363636; - - _sha.Init(); - _sha.Update(keyTemp, kNumBlockWords); - - for (i = 0; i < kNumBlockWords; i++) - keyTemp[i] ^= 0x36363636 ^ 0x5C5C5C5C; + MY_ALIGN (16) UInt32 block [SHA1_NUM_BLOCK_WORDS]; + MY_ALIGN (16) UInt32 block2[SHA1_NUM_BLOCK_WORDS]; + MY_ALIGN (16) UInt32 mac2 [SHA1_NUM_BLOCK_WORDS]; - _sha2.Init(); - _sha2.Update(keyTemp, kNumBlockWords); -} + _sha. PrepareBlock((Byte *)block, SHA1_DIGEST_SIZE); + _sha2.PrepareBlock((Byte *)block2, SHA1_DIGEST_SIZE); -void CHmac32::Final(UInt32 *mac, size_t macSize) -{ - UInt32 digest[kNumDigestWords]; - _sha.Final(digest); - _sha2.Update(digest, kNumDigestWords); - _sha2.Final(digest); - for (size_t i = 0; i < macSize; i++) - mac[i] = digest[i]; -} + block[0] = ((const UInt32 *)mac)[0]; + block[1] = ((const UInt32 *)mac)[1]; + block[2] = ((const UInt32 *)mac)[2]; + block[3] = ((const UInt32 *)mac)[3]; + block[4] = ((const UInt32 *)mac)[4]; -void CHmac32::GetLoopXorDigest(UInt32 *mac, UInt32 numIteration) -{ - UInt32 block[kNumBlockWords]; - UInt32 block2[kNumBlockWords]; - - _sha.PrepareBlock(block, kNumDigestWords); - _sha2.PrepareBlock(block2, kNumDigestWords); + mac2[0] = block[0]; + mac2[1] = block[1]; + mac2[2] = block[2]; + mac2[3] = block[3]; + mac2[4] = block[4]; - for (unsigned s = 0; s < kNumDigestWords; s++) - block[s] = mac[s]; - for (UInt32 i = 0; i < numIteration; i++) { - _sha.GetBlockDigest(block, block2); - _sha2.GetBlockDigest(block2, block); - for (unsigned s = 0; s < kNumDigestWords; s++) - mac[s] ^= block[s]; + _sha. GetBlockDigest((const Byte *)block, (Byte *)block2); + _sha2.GetBlockDigest((const Byte *)block2, (Byte *)block); + + mac2[0] ^= block[0]; + mac2[1] ^= block[1]; + mac2[2] ^= block[2]; + mac2[3] ^= block[3]; + mac2[4] ^= block[4]; } + + ((UInt32 *)mac)[0] = mac2[0]; + ((UInt32 *)mac)[1] = mac2[1]; + ((UInt32 *)mac)[2] = mac2[2]; + ((UInt32 *)mac)[3] = mac2[3]; + ((UInt32 *)mac)[4] = mac2[4]; } }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Crypto/HmacSha1.h b/src/plugins/7zip/7za/cpp/7zip/Crypto/HmacSha1.h index 6ba015e8..a36c78e1 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Crypto/HmacSha1.h +++ b/src/plugins/7zip/7za/cpp/7zip/Crypto/HmacSha1.h @@ -1,15 +1,15 @@ // HmacSha1.h // Implements HMAC-SHA-1 (RFC2104, FIPS-198) -#ifndef __CRYPTO_HMAC_SHA1_H -#define __CRYPTO_HMAC_SHA1_H +#ifndef ZIP7_INC_CRYPTO_HMAC_SHA1_H +#define ZIP7_INC_CRYPTO_HMAC_SHA1_H #include "Sha1Cls.h" namespace NCrypto { namespace NSha1 { -// Use: SetKey(key, keySize); for () Update(data, size); Final(mac, macSize); +// Use: SetKey(key, keySize); for () Update(data, size); FinalFull(mac); class CHmac { @@ -18,20 +18,12 @@ class CHmac public: void SetKey(const Byte *key, size_t keySize); void Update(const Byte *data, size_t dataSize) { _sha.Update(data, dataSize); } - void Final(Byte *mac, size_t macSize = kDigestSize); -}; - -class CHmac32 -{ - CContext32 _sha; - CContext32 _sha2; -public: - void SetKey(const Byte *key, size_t keySize); - void Update(const UInt32 *data, size_t dataSize) { _sha.Update(data, dataSize); } - void Final(UInt32 *mac, size_t macSize = kNumDigestWords); - // It'sa for hmac function. in,out: mac[kNumDigestWords]. - void GetLoopXorDigest(UInt32 *mac, UInt32 numIteration); + // Final() : mac is recommended to be aligned for 4 bytes + // GetLoopXorDigest1() : mac is required to be aligned for 4 bytes + // The caller can use: UInt32 mac[NSha1::kNumDigestWords] and typecast to (Byte *) and (void *); + void Final(Byte *mac); + void GetLoopXorDigest1(void *mac, UInt32 numIteration); }; }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Crypto/HmacSha256.cpp b/src/plugins/7zip/7za/cpp/7zip/Crypto/HmacSha256.cpp index 2e1efb48..56f8ede1 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Crypto/HmacSha256.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Crypto/HmacSha256.cpp @@ -2,6 +2,8 @@ #include "StdAfx.h" +#include + #include "../../../C/CpuArch.h" #include "HmacSha256.h" @@ -9,39 +11,38 @@ namespace NCrypto { namespace NSha256 { -static const unsigned kBlockSize = 64; - void CHmac::SetKey(const Byte *key, size_t keySize) { - Byte temp[kBlockSize]; + MY_ALIGN (16) + UInt32 temp[SHA256_NUM_BLOCK_WORDS]; size_t i; - for (i = 0; i < kBlockSize; i++) + for (i = 0; i < SHA256_NUM_BLOCK_WORDS; i++) temp[i] = 0; if (keySize > kBlockSize) { Sha256_Init(&_sha); Sha256_Update(&_sha, key, keySize); - Sha256_Final(&_sha, temp); + Sha256_Final(&_sha, (Byte *)temp); } else - for (i = 0; i < keySize; i++) - temp[i] = key[i]; + memcpy(temp, key, keySize); - for (i = 0; i < kBlockSize; i++) - temp[i] ^= 0x36; + for (i = 0; i < SHA256_NUM_BLOCK_WORDS; i++) + temp[i] ^= 0x36363636; Sha256_Init(&_sha); - Sha256_Update(&_sha, temp, kBlockSize); + Sha256_Update(&_sha, (const Byte *)temp, kBlockSize); - for (i = 0; i < kBlockSize; i++) - temp[i] ^= 0x36 ^ 0x5C; + for (i = 0; i < SHA256_NUM_BLOCK_WORDS; i++) + temp[i] ^= 0x36363636 ^ 0x5C5C5C5C; Sha256_Init(&_sha2); - Sha256_Update(&_sha2, temp, kBlockSize); + Sha256_Update(&_sha2, (const Byte *)temp, kBlockSize); } + void CHmac::Final(Byte *mac) { Sha256_Final(&_sha, mac); @@ -49,14 +50,4 @@ void CHmac::Final(Byte *mac) Sha256_Final(&_sha2, mac); } -/* -void CHmac::Final(Byte *mac, size_t macSize) -{ - Byte digest[SHA256_DIGEST_SIZE]; - Final(digest); - for (size_t i = 0; i < macSize; i++) - mac[i] = digest[i]; -} -*/ - }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Crypto/HmacSha256.h b/src/plugins/7zip/7za/cpp/7zip/Crypto/HmacSha256.h index 233424a0..4039c0ce 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Crypto/HmacSha256.h +++ b/src/plugins/7zip/7za/cpp/7zip/Crypto/HmacSha256.h @@ -1,14 +1,15 @@ // HmacSha256.h // Implements HMAC-SHA-256 (RFC2104, FIPS-198) -#ifndef __CRYPTO_HMAC_SHA256_H -#define __CRYPTO_HMAC_SHA256_H +#ifndef ZIP7_INC_CRYPTO_HMAC_SHA256_H +#define ZIP7_INC_CRYPTO_HMAC_SHA256_H #include "../../../C/Sha256.h" namespace NCrypto { namespace NSha256 { +const unsigned kBlockSize = SHA256_BLOCK_SIZE; const unsigned kDigestSize = SHA256_DIGEST_SIZE; class CHmac @@ -19,7 +20,6 @@ class CHmac void SetKey(const Byte *key, size_t keySize); void Update(const Byte *data, size_t dataSize) { Sha256_Update(&_sha, data, dataSize); } void Final(Byte *mac); - // void Final(Byte *mac, size_t macSize); }; }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Crypto/MyAES.cpp b/src/plugins/7zip/7za/cpp/7zip/Crypto/MyAES.cpp index 52eaab7a..0ae0e162 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Crypto/MyAES.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Crypto/MyAES.cpp @@ -10,103 +10,268 @@ namespace NCrypto { static struct CAesTabInit { CAesTabInit() { AesGenTables();} } g_AesTabInit; -CAesCbcCoder::CAesCbcCoder(bool encodeMode, unsigned keySize): - _keySize(keySize), +CAesCoder::CAesCoder( + // bool encodeMode, + unsigned keySize + // , bool ctrMode + ): _keyIsSet(false), - _encodeMode(encodeMode) + // _encodeMode(encodeMode), + // _ctrMode(ctrMode), + _keySize(keySize), + // _ctrPos(0), // _ctrPos =0 will be set in Init() + _aes(AES_NUM_IVMRK_WORDS * 4 + AES_BLOCK_SIZE * 2) { - _offset = ((0 - (unsigned)(ptrdiff_t)_aes) & 0xF) / sizeof(UInt32); + // _offset = ((0 - (unsigned)(ptrdiff_t)_aes) & 0xF) / sizeof(UInt32); memset(_iv, 0, AES_BLOCK_SIZE); - SetFunctions(0); + /* + // we can use the following code to test 32-bit overflow case for AES-CTR + for (unsigned i = 0; i < 16; i++) _iv[i] = (Byte)(i + 1); + _iv[0] = 0xFE; _iv[1] = _iv[2] = _iv[3] = 0xFF; + */ } -STDMETHODIMP CAesCbcCoder::Init() +Z7_COM7F_IMF(CAesCoder::Init()) { - AesCbc_Init(_aes + _offset, _iv); - return _keyIsSet ? S_OK : E_FAIL; + _ctrPos = 0; + AesCbc_Init(Aes(), _iv); + return _keyIsSet ? S_OK : E_NOTIMPL; // E_FAIL } -STDMETHODIMP_(UInt32) CAesCbcCoder::Filter(Byte *data, UInt32 size) +Z7_COM7F_IMF2(UInt32, CAesCoder::Filter(Byte *data, UInt32 size)) { if (!_keyIsSet) return 0; - if (size == 0) - return 0; if (size < AES_BLOCK_SIZE) + { + if (size == 0) + return 0; return AES_BLOCK_SIZE; + } size >>= 4; - _codeFunc(_aes + _offset, data, size); + // (data) must be aligned for 16-bytes here + _codeFunc(Aes(), data, size); return size << 4; } -STDMETHODIMP CAesCbcCoder::SetKey(const Byte *data, UInt32 size) + +Z7_COM7F_IMF(CAesCoder::SetKey(const Byte *data, UInt32 size)) { if ((size & 0x7) != 0 || size < 16 || size > 32) return E_INVALIDARG; if (_keySize != 0 && size != _keySize) return E_INVALIDARG; - AES_SET_KEY_FUNC setKeyFunc = _encodeMode ? Aes_SetKey_Enc : Aes_SetKey_Dec; - setKeyFunc(_aes + _offset + 4, data, size); + _setKeyFunc(Aes() + 4, data, size); _keyIsSet = true; return S_OK; } -STDMETHODIMP CAesCbcCoder::SetInitVector(const Byte *data, UInt32 size) +Z7_COM7F_IMF(CAesCoder::SetInitVector(const Byte *data, UInt32 size)) { if (size != AES_BLOCK_SIZE) return E_INVALIDARG; memcpy(_iv, data, size); - CAesCbcCoder::Init(); // don't call virtual function here !!! + /* we allow SetInitVector() call before SetKey() call. + so we ignore possible error in Init() here */ + CAesCoder::Init(); // don't call virtual function here !!! return S_OK; } -EXTERN_C_BEGIN - -void MY_FAST_CALL AesCbc_Encode(UInt32 *ivAes, Byte *data, size_t numBlocks); -void MY_FAST_CALL AesCbc_Decode(UInt32 *ivAes, Byte *data, size_t numBlocks); -void MY_FAST_CALL AesCtr_Code(UInt32 *ivAes, Byte *data, size_t numBlocks); -void MY_FAST_CALL AesCbc_Encode_Intel(UInt32 *ivAes, Byte *data, size_t numBlocks); -void MY_FAST_CALL AesCbc_Decode_Intel(UInt32 *ivAes, Byte *data, size_t numBlocks); -void MY_FAST_CALL AesCtr_Code_Intel(UInt32 *ivAes, Byte *data, size_t numBlocks); +#ifndef Z7_SFX -EXTERN_C_END +/* +Z7_COM7F_IMF(CAesCtrCoder::Init()) +{ + _ctrPos = 0; + return CAesCoder::Init(); +} +*/ -bool CAesCbcCoder::SetFunctions(UInt32 algo) +Z7_COM7F_IMF2(UInt32, CAesCtrCoder::Filter(Byte *data, UInt32 size)) { - _codeFunc = _encodeMode ? - g_AesCbc_Encode : - g_AesCbc_Decode; - if (algo == 1) + if (!_keyIsSet) + return 0; + if (size == 0) + return 0; + + if (_ctrPos != 0) { - _codeFunc = _encodeMode ? - AesCbc_Encode: - AesCbc_Decode; + /* Optimized caller will not call here */ + const Byte *ctr = (Byte *)(Aes() + AES_NUM_IVMRK_WORDS); + unsigned num = 0; + for (unsigned i = _ctrPos; i != AES_BLOCK_SIZE; i++) + { + if (num == size) + { + _ctrPos = i; + return num; + } + data[num++] ^= ctr[i]; + } + _ctrPos = 0; + /* if (num < size) { + we can filter more data with _codeFunc(). + But it's supposed that the caller can work correctly, + even if we do only partial filtering here. + So we filter data only for current 16-byte block. } + */ + /* + size -= num; + size >>= 4; + // (data) must be aligned for 16-bytes here + _codeFunc(Aes(), data + num, size); + return num + (size << 4); + */ + return num; } - if (algo == 2) + + if (size < AES_BLOCK_SIZE) { - #ifdef MY_CPU_X86_OR_AMD64 - if (g_AesCbc_Encode != AesCbc_Encode_Intel) - #endif - return false; + /* The good optimized caller can call here only in last Filter() call. + But we support also non-optimized callers, + where another Filter() calls are allowed after this call. + */ + Byte *ctr = (Byte *)(Aes() + AES_NUM_IVMRK_WORDS); + memset(ctr, 0, AES_BLOCK_SIZE); + memcpy(ctr, data, size); + _codeFunc(Aes(), ctr, 1); + memcpy(data, ctr, size); + _ctrPos = size; + return size; } - return true; + + size >>= 4; + // (data) must be aligned for 16-bytes here + _codeFunc(Aes(), data, size); + return size << 4; } -STDMETHODIMP CAesCbcCoder::SetCoderProperties(const PROPID *propIDs, const PROPVARIANT *coderProps, UInt32 numProps) +#endif // Z7_SFX + + +#ifndef Z7_EXTRACT_ONLY + +#ifdef MY_CPU_X86_OR_AMD64 + + #if defined(__INTEL_COMPILER) + #if (__INTEL_COMPILER >= 1110) + #define USE_HW_AES + #if (__INTEL_COMPILER >= 1900) + #define USE_HW_VAES + #endif + #endif + #elif defined(Z7_CLANG_VERSION) && (Z7_CLANG_VERSION >= 30800) \ + || defined(Z7_GCC_VERSION) && (Z7_GCC_VERSION >= 40400) + #define USE_HW_AES + #if defined(__clang__) && (__clang_major__ >= 8) \ + || defined(__GNUC__) && (__GNUC__ >= 8) + #define USE_HW_VAES + #endif + #elif defined(_MSC_VER) + #define USE_HW_AES + #define USE_HW_VAES + #endif + +#elif defined(MY_CPU_ARM_OR_ARM64) && defined(MY_CPU_LE) + + #if defined(__ARM_FEATURE_AES) \ + || defined(__ARM_FEATURE_CRYPTO) + #define USE_HW_AES + #else + #if defined(MY_CPU_ARM64) \ + || defined(__ARM_ARCH) && (__ARM_ARCH >= 4) \ + || defined(Z7_MSC_VER_ORIGINAL) + #if defined(__ARM_FP) && \ + ( defined(Z7_CLANG_VERSION) && (Z7_CLANG_VERSION >= 30800) \ + || defined(__GNUC__) && (__GNUC__ >= 6) \ + ) \ + || defined(Z7_MSC_VER_ORIGINAL) && (_MSC_VER >= 1910) + #if defined(MY_CPU_ARM64) \ + || !defined(Z7_CLANG_VERSION) \ + || defined(__ARM_NEON) && \ + (Z7_CLANG_VERSION < 170000 || \ + Z7_CLANG_VERSION > 170001) + #define USE_HW_AES + #endif + #endif + #endif + #endif +#endif + +#ifdef USE_HW_AES +// #pragma message("=== MyAES.c USE_HW_AES === ") + + #define SET_AES_FUNC_2(f2) \ + if (algo == 2) if (g_Aes_SupportedFunctions_Flags & k_Aes_SupportedFunctions_HW) \ + { f = f2; } + #ifdef USE_HW_VAES + #define SET_AES_FUNC_23(f2, f3) \ + SET_AES_FUNC_2(f2) \ + if (algo == 3) if (g_Aes_SupportedFunctions_Flags & k_Aes_SupportedFunctions_HW_256) \ + { f = f3; } + #else // USE_HW_VAES + #define SET_AES_FUNC_23(f2, f3) \ + SET_AES_FUNC_2(f2) + #endif // USE_HW_VAES +#else // USE_HW_AES + #define SET_AES_FUNC_23(f2, f3) +#endif // USE_HW_AES + +#define SET_AES_FUNCS(c, f0, f1, f2, f3) \ + bool c::SetFunctions(UInt32 algo) { \ + _codeFunc = f0; if (algo < 1) return true; \ + AES_CODE_FUNC f = NULL; \ + if (algo == 1) { f = f1; } \ + SET_AES_FUNC_23(f2, f3) \ + if (f) { _codeFunc = f; return true; } \ + return false; } + + + +#ifndef Z7_SFX +SET_AES_FUNCS( + CAesCtrCoder, + g_AesCtr_Code, + AesCtr_Code, + AesCtr_Code_HW, + AesCtr_Code_HW_256) +#endif + +SET_AES_FUNCS( + CAesCbcEncoder, + g_AesCbc_Encode, + AesCbc_Encode, + AesCbc_Encode_HW, + AesCbc_Encode_HW) + +SET_AES_FUNCS( + CAesCbcDecoder, + g_AesCbc_Decode, + AesCbc_Decode, + AesCbc_Decode_HW, + AesCbc_Decode_HW_256) + +Z7_COM7F_IMF(CAesCoder::SetCoderProperties(const PROPID *propIDs, const PROPVARIANT *coderProps, UInt32 numProps)) { + UInt32 algo = 0; for (UInt32 i = 0; i < numProps; i++) { - const PROPVARIANT &prop = coderProps[i]; if (propIDs[i] == NCoderPropID::kDefaultProp) { + const PROPVARIANT &prop = coderProps[i]; if (prop.vt != VT_UI4) return E_INVALIDARG; - if (!SetFunctions(prop.ulVal)) + if (prop.ulVal > 3) return E_NOTIMPL; + algo = prop.ulVal; } } + if (!SetFunctions(algo)) + return E_NOTIMPL; return S_OK; } +#endif // Z7_EXTRACT_ONLY + } diff --git a/src/plugins/7zip/7za/cpp/7zip/Crypto/MyAES.h b/src/plugins/7zip/7za/cpp/7zip/Crypto/MyAES.h index 84c21de3..a3c1c27f 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Crypto/MyAES.h +++ b/src/plugins/7zip/7za/cpp/7zip/Crypto/MyAES.h @@ -1,55 +1,122 @@ // Crypto/MyAes.h -#ifndef __CRYPTO_MY_AES_H -#define __CRYPTO_MY_AES_H +#ifndef ZIP7_INC_CRYPTO_MY_AES_H +#define ZIP7_INC_CRYPTO_MY_AES_H #include "../../../C/Aes.h" +#include "../../Common/MyBuffer2.h" #include "../../Common/MyCom.h" #include "../ICoder.h" namespace NCrypto { -class CAesCbcCoder: +#ifdef Z7_EXTRACT_ONLY +#define Z7_IFACEN_IAesCoderSetFunctions(x) +#else +#define Z7_IFACEN_IAesCoderSetFunctions(x) \ + virtual bool SetFunctions(UInt32 algo) x +#endif + + +class CAesCoder: public ICompressFilter, public ICryptoProperties, + #ifndef Z7_EXTRACT_ONLY public ICompressSetCoderProperties, + #endif public CMyUnknownImp { - AES_CODE_FUNC _codeFunc; - unsigned _offset; - unsigned _keySize; + Z7_COM_QI_BEGIN2(ICompressFilter) + Z7_COM_QI_ENTRY(ICryptoProperties) + #ifndef Z7_EXTRACT_ONLY + Z7_COM_QI_ENTRY(ICompressSetCoderProperties) + #endif + Z7_COM_QI_END + Z7_COM_ADDREF_RELEASE + +public: + Z7_IFACE_COM7_IMP_NONFINAL(ICompressFilter) + Z7_IFACE_COM7_IMP(ICryptoProperties) +private: + #ifndef Z7_EXTRACT_ONLY + Z7_IFACE_COM7_IMP(ICompressSetCoderProperties) + #endif + +protected: bool _keyIsSet; - bool _encodeMode; - UInt32 _aes[AES_NUM_IVMRK_WORDS + 3]; + // bool _encodeMode; + // bool _ctrMode; + // unsigned _offset; + unsigned _keySize; + unsigned _ctrPos; // we need _ctrPos here for Init() / SetInitVector() + AES_CODE_FUNC _codeFunc; + AES_SET_KEY_FUNC _setKeyFunc; +private: + // UInt32 _aes[AES_NUM_IVMRK_WORDS + 3]; + CAlignedBuffer1 _aes; + Byte _iv[AES_BLOCK_SIZE]; - bool SetFunctions(UInt32 algo); + // UInt32 *Aes() { return _aes + _offset; } +protected: + UInt32 *Aes() { return (UInt32 *)(void *)(Byte *)_aes; } + + Z7_IFACE_PURE(IAesCoderSetFunctions) public: - CAesCbcCoder(bool encodeMode, unsigned keySize); - - MY_UNKNOWN_IMP3(ICompressFilter, ICryptoProperties, ICompressSetCoderProperties) - - INTERFACE_ICompressFilter(;) - - STDMETHOD(SetKey)(const Byte *data, UInt32 size); - STDMETHOD(SetInitVector)(const Byte *data, UInt32 size); - - STDMETHOD(SetCoderProperties)(const PROPID *propIDs, const PROPVARIANT *props, UInt32 numProps); + CAesCoder( + // bool encodeMode, + unsigned keySize + // , bool ctrMode + ); + virtual ~CAesCoder() {} // we need virtual destructor for derived classes + void SetKeySize(unsigned size) { _keySize = size; } }; -struct CAesCbcEncoder: public CAesCbcCoder + +#ifndef Z7_EXTRACT_ONLY +struct CAesCbcEncoder: public CAesCoder { - CAesCbcEncoder(unsigned keySize = 0): CAesCbcCoder(true, keySize) {} + CAesCbcEncoder(unsigned keySize = 0): CAesCoder(keySize) + { + _setKeyFunc = Aes_SetKey_Enc; + _codeFunc = g_AesCbc_Encode; + } + Z7_IFACE_IMP(IAesCoderSetFunctions) }; +#endif -struct CAesCbcDecoder: public CAesCbcCoder +struct CAesCbcDecoder: public CAesCoder { - CAesCbcDecoder(unsigned keySize = 0): CAesCbcCoder(false, keySize) {} + CAesCbcDecoder(unsigned keySize = 0): CAesCoder(keySize) + { + _setKeyFunc = Aes_SetKey_Dec; + _codeFunc = g_AesCbc_Decode; + } + Z7_IFACE_IMP(IAesCoderSetFunctions) }; +#ifndef Z7_SFX +struct CAesCtrCoder: public CAesCoder +{ +private: + // unsigned _ctrPos; + // Z7_IFACE_COM7_IMP(ICompressFilter) + // Z7_COM7F_IMP(Init()) + Z7_COM7F_IMP2(UInt32, Filter(Byte *data, UInt32 size)) +public: + CAesCtrCoder(unsigned keySize = 0): CAesCoder(keySize) + { + _ctrPos = 0; + _setKeyFunc = Aes_SetKey_Enc; + _codeFunc = g_AesCtr_Code; + } + Z7_IFACE_IMP(IAesCoderSetFunctions) +}; +#endif + } #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Crypto/MyAesReg.cpp b/src/plugins/7zip/7za/cpp/7zip/Crypto/MyAesReg.cpp index 28006835..e2b4f72d 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Crypto/MyAesReg.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Crypto/MyAesReg.cpp @@ -8,9 +8,22 @@ namespace NCrypto { -REGISTER_FILTER_E(AES256CBC, - CAesCbcDecoder(32), - CAesCbcEncoder(32), - 0x6F00181, "AES256CBC") +#ifndef Z7_SFX + +#define REGISTER_AES_2(name, nameString, keySize) \ + REGISTER_FILTER_E(name, \ + CAesCbcDecoder(keySize), \ + CAesCbcEncoder(keySize), \ + 0x6F00100 | ((keySize - 16) * 8) | (/* isCtr */ 0 ? 4 : 1), \ + nameString) \ + +#define REGISTER_AES(name, nameString) \ + /* REGISTER_AES_2(AES128 ## name, "AES128" nameString, 16) */ \ + /* REGISTER_AES_2(AES192 ## name, "AES192" nameString, 24) */ \ + REGISTER_AES_2(AES256 ## name, "AES256" nameString, 32) \ + +REGISTER_AES(CBC, "CBC") + +#endif } diff --git a/src/plugins/7zip/7za/cpp/7zip/Crypto/Pbkdf2HmacSha1.cpp b/src/plugins/7zip/7za/cpp/7zip/Crypto/Pbkdf2HmacSha1.cpp index a7fcb728..3d9e4c1a 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Crypto/Pbkdf2HmacSha1.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Crypto/Pbkdf2HmacSha1.cpp @@ -2,9 +2,12 @@ #include "StdAfx.h" +#include + #include "../../../C/CpuArch.h" #include "HmacSha1.h" +#include "Pbkdf2HmacSha1.h" namespace NCrypto { namespace NSha1 { @@ -14,81 +17,29 @@ void Pbkdf2Hmac(const Byte *pwd, size_t pwdSize, UInt32 numIterations, Byte *key, size_t keySize) { + MY_ALIGN (16) CHmac baseCtx; baseCtx.SetKey(pwd, pwdSize); for (UInt32 i = 1; keySize != 0; i++) { - CHmac ctx = baseCtx; + MY_ALIGN (16) + CHmac ctx; + ctx = baseCtx; ctx.Update(salt, saltSize); - Byte u[kDigestSize]; - SetBe32(u, i); - - ctx.Update(u, 4); - ctx.Final(u, kDigestSize); - - const unsigned curSize = (keySize < kDigestSize) ? (unsigned)keySize : kDigestSize; - unsigned s; - for (s = 0; s < curSize; s++) - key[s] = u[s]; - - for (UInt32 j = numIterations; j > 1; j--) - { - ctx = baseCtx; - ctx.Update(u, kDigestSize); - ctx.Final(u, kDigestSize); - for (s = 0; s < curSize; s++) - key[s] ^= u[s]; - } - - key += curSize; - keySize -= curSize; - } -} - -void Pbkdf2Hmac32(const Byte *pwd, size_t pwdSize, - const UInt32 *salt, size_t saltSize, - UInt32 numIterations, - UInt32 *key, size_t keySize) -{ - CHmac32 baseCtx; - baseCtx.SetKey(pwd, pwdSize); - - for (UInt32 i = 1; keySize != 0; i++) - { - CHmac32 ctx = baseCtx; - ctx.Update(salt, saltSize); - + MY_ALIGN (16) UInt32 u[kNumDigestWords]; - u[0] = i; + SetBe32(u, i) - ctx.Update(u, 1); - ctx.Final(u, kNumDigestWords); + ctx.Update((const Byte *)u, 4); + ctx.Final((Byte *)u); - // Speed-optimized code start ctx = baseCtx; - ctx.GetLoopXorDigest(u, numIterations - 1); - // Speed-optimized code end - - const unsigned curSize = (keySize < kNumDigestWords) ? (unsigned)keySize : kNumDigestWords; - unsigned s; - for (s = 0; s < curSize; s++) - key[s] = u[s]; - - /* - // Default code start - for (UInt32 j = numIterations; j > 1; j--) - { - ctx = baseCtx; - ctx.Update(u, kNumDigestWords); - ctx.Final(u, kNumDigestWords); - for (s = 0; s < curSize; s++) - key[s] ^= u[s]; - } - // Default code end - */ + ctx.GetLoopXorDigest1((void *)u, numIterations - 1); + const unsigned curSize = (keySize < kDigestSize) ? (unsigned)keySize : kDigestSize; + memcpy(key, (const Byte *)u, curSize); key += curSize; keySize -= curSize; } diff --git a/src/plugins/7zip/7za/cpp/7zip/Crypto/Pbkdf2HmacSha1.h b/src/plugins/7zip/7za/cpp/7zip/Crypto/Pbkdf2HmacSha1.h index 6560b8d1..89cbe414 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Crypto/Pbkdf2HmacSha1.h +++ b/src/plugins/7zip/7za/cpp/7zip/Crypto/Pbkdf2HmacSha1.h @@ -1,8 +1,8 @@ // Pbkdf2HmacSha1.h // Password-Based Key Derivation Function (RFC 2898, PKCS #5) based on HMAC-SHA-1 -#ifndef __CRYPTO_PBKDF2_HMAC_SHA1_H -#define __CRYPTO_PBKDF2_HMAC_SHA1_H +#ifndef ZIP7_INC_CRYPTO_PBKDF2_HMAC_SHA1_H +#define ZIP7_INC_CRYPTO_PBKDF2_HMAC_SHA1_H #include @@ -14,9 +14,6 @@ namespace NSha1 { void Pbkdf2Hmac(const Byte *pwd, size_t pwdSize, const Byte *salt, size_t saltSize, UInt32 numIterations, Byte *key, size_t keySize); -void Pbkdf2Hmac32(const Byte *pwd, size_t pwdSize, const UInt32 *salt, size_t saltSize, - UInt32 numIterations, UInt32 *key, size_t keySize); - }} #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Crypto/RandGen.cpp b/src/plugins/7zip/7za/cpp/7zip/Crypto/RandGen.cpp index 408f73f1..05a6c06d 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Crypto/RandGen.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Crypto/RandGen.cpp @@ -2,14 +2,44 @@ #include "StdAfx.h" -#ifndef _7ZIP_ST +#include "RandGen.h" + +#ifndef USE_STATIC_SYSTEM_RAND + +#ifndef Z7_ST #include "../../Windows/Synchronization.h" #endif -#include "RandGen.h" -#ifndef _WIN32 +#ifdef _WIN32 + +#ifdef _WIN64 +#define USE_STATIC_RtlGenRandom +#endif + +#ifdef USE_STATIC_RtlGenRandom + +// #include + +EXTERN_C_BEGIN +#ifndef RtlGenRandom + #define RtlGenRandom SystemFunction036 + BOOLEAN WINAPI RtlGenRandom(PVOID RandomBuffer, ULONG RandomBufferLength); +#endif +EXTERN_C_END + +#else +EXTERN_C_BEGIN +typedef BOOLEAN (WINAPI * Func_RtlGenRandom)(PVOID RandomBuffer, ULONG RandomBufferLength); +EXTERN_C_END +#endif + + +#else #include +#include +#include +#include #define USE_POSIX_TIME #define USE_POSIX_TIME2 #endif @@ -21,61 +51,141 @@ #endif #endif -// This is not very good random number generator. -// Please use it only for salt. -// First generated data block depends from timer and processID. +// The seed and first generated data block depend from processID, +// theadID, timer and system random generator, if available. // Other generated data blocks depend from previous state -// Maybe it's possible to restore original timer value from generated value. #define HASH_UPD(x) Sha256_Update(&hash, (const Byte *)&x, sizeof(x)); void CRandomGenerator::Init() { + MY_ALIGN (16) CSha256 hash; Sha256_Init(&hash); + unsigned numIterations = 1000; + + { + #ifndef UNDER_CE + const unsigned kNumIterations_Small = 100; + const unsigned kBufSize = 32; + MY_ALIGN (16) + Byte buf[kBufSize]; + #endif + #ifdef _WIN32 + DWORD w = ::GetCurrentProcessId(); - HASH_UPD(w); + HASH_UPD(w) w = ::GetCurrentThreadId(); - HASH_UPD(w); + HASH_UPD(w) + + #ifdef UNDER_CE + /* + if (CeGenRandom(kBufSize, buf)) + { + numIterations = kNumIterations_Small; + Sha256_Update(&hash, buf, kBufSize); + } + */ + #elif defined(USE_STATIC_RtlGenRandom) + if (RtlGenRandom(buf, kBufSize)) + { + numIterations = kNumIterations_Small; + Sha256_Update(&hash, buf, kBufSize); + } #else + { + const HMODULE hModule = ::LoadLibrary(TEXT("advapi32.dll")); + if (hModule) + { + // SystemFunction036() is real name of RtlGenRandom() function + const + Func_RtlGenRandom + my_RtlGenRandom = Z7_GET_PROC_ADDRESS( + Func_RtlGenRandom, hModule, "SystemFunction036"); + if (my_RtlGenRandom) + { + if (my_RtlGenRandom(buf, kBufSize)) + { + numIterations = kNumIterations_Small; + Sha256_Update(&hash, buf, kBufSize); + } + } + ::FreeLibrary(hModule); + } + } + #endif + + #else + pid_t pid = getpid(); - HASH_UPD(pid); + HASH_UPD(pid) pid = getppid(); - HASH_UPD(pid); + HASH_UPD(pid) + + { + int f = open("/dev/urandom", O_RDONLY); + unsigned numBytes = kBufSize; + if (f >= 0) + { + do + { + ssize_t n = read(f, buf, numBytes); + if (n <= 0) + break; + Sha256_Update(&hash, buf, (size_t)n); + numBytes -= (unsigned)n; + } + while (numBytes); + close(f); + if (numBytes == 0) + numIterations = kNumIterations_Small; + } + } + /* + { + int n = getrandom(buf, kBufSize, 0); + if (n > 0) + { + Sha256_Update(&hash, buf, n); + if (n == kBufSize) + numIterations = kNumIterations_Small; + } + } + */ + + #endif + } + + #ifdef _DEBUG + numIterations = 2; #endif - for (unsigned i = 0; i < - #ifdef _DEBUG - 2; - #else - 1000; - #endif - i++) + do { #ifdef _WIN32 LARGE_INTEGER v; if (::QueryPerformanceCounter(&v)) - HASH_UPD(v.QuadPart); + HASH_UPD(v.QuadPart) #endif #ifdef USE_POSIX_TIME #ifdef USE_POSIX_TIME2 timeval v; - if (gettimeofday(&v, 0) == 0) + if (gettimeofday(&v, NULL) == 0) { - HASH_UPD(v.tv_sec); - HASH_UPD(v.tv_usec); + HASH_UPD(v.tv_sec) + HASH_UPD(v.tv_usec) } #endif - time_t v2 = time(NULL); - HASH_UPD(v2); + const time_t v2 = time(NULL); + HASH_UPD(v2) #endif #ifdef _WIN32 - DWORD tickCount = ::GetTickCount(); - HASH_UPD(tickCount); + const DWORD tickCount = ::GetTickCount(); + HASH_UPD(tickCount) #endif for (unsigned j = 0; j < 100; j++) @@ -85,11 +195,13 @@ void CRandomGenerator::Init() Sha256_Update(&hash, _buff, SHA256_DIGEST_SIZE); } } + while (--numIterations); + Sha256_Final(&hash, _buff); _needInit = false; } -#ifndef _7ZIP_ST +#ifndef Z7_ST static NWindows::NSynchronization::CCriticalSection g_CriticalSection; #define MT_LOCK NWindows::NSynchronization::CCriticalSectionLock lock(g_CriticalSection); #else @@ -104,6 +216,7 @@ void CRandomGenerator::Generate(Byte *data, unsigned size) Init(); while (size != 0) { + MY_ALIGN (16) CSha256 hash; Sha256_Init(&hash); @@ -112,8 +225,9 @@ void CRandomGenerator::Generate(Byte *data, unsigned size) Sha256_Init(&hash); UInt32 salt = 0xF672ABD1; - HASH_UPD(salt); + HASH_UPD(salt) Sha256_Update(&hash, _buff, SHA256_DIGEST_SIZE); + MY_ALIGN (16) Byte buff[SHA256_DIGEST_SIZE]; Sha256_Final(&hash, buff); for (unsigned i = 0; i < SHA256_DIGEST_SIZE && size != 0; i++, size--) @@ -121,4 +235,7 @@ void CRandomGenerator::Generate(Byte *data, unsigned size) } } +MY_ALIGN (16) CRandomGenerator g_RandomGenerator; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Crypto/RandGen.h b/src/plugins/7zip/7za/cpp/7zip/Crypto/RandGen.h index cfdcd60d..3bd69ec2 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Crypto/RandGen.h +++ b/src/plugins/7zip/7za/cpp/7zip/Crypto/RandGen.h @@ -1,10 +1,25 @@ // RandGen.h -#ifndef __CRYPTO_RAND_GEN_H -#define __CRYPTO_RAND_GEN_H +#ifndef ZIP7_INC_CRYPTO_RAND_GEN_H +#define ZIP7_INC_CRYPTO_RAND_GEN_H #include "../../../C/Sha256.h" +#ifdef _WIN64 +// #define USE_STATIC_SYSTEM_RAND +#endif + +#ifdef USE_STATIC_SYSTEM_RAND + +#ifdef _WIN32 +#include +#define MY_RAND_GEN(data, size) RtlGenRandom(data, size) +#else +#define MY_RAND_GEN(data, size) getrandom(data, size, 0) +#endif + +#else + class CRandomGenerator { Byte _buff[SHA256_DIGEST_SIZE]; @@ -12,10 +27,15 @@ class CRandomGenerator void Init(); public: - CRandomGenerator(): _needInit(true) {}; + CRandomGenerator(): _needInit(true) {} void Generate(Byte *data, unsigned size); }; +MY_ALIGN (16) extern CRandomGenerator g_RandomGenerator; +#define MY_RAND_GEN(data, size) g_RandomGenerator.Generate(data, size) + +#endif + #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Crypto/Rar20Crypto.cpp b/src/plugins/7zip/7za/cpp/7zip/Crypto/Rar20Crypto.cpp index 346d969e..8e55763c 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Crypto/Rar20Crypto.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Crypto/Rar20Crypto.cpp @@ -54,7 +54,7 @@ void CData::SetPassword(const Byte *data, unsigned size) Keys[3] = 0xA4E7F123L; Byte psw[128]; - memset(psw, 0, sizeof(psw)); + Z7_memset_0_ARRAY(psw); if (size != 0) { if (size >= sizeof(psw)) @@ -68,7 +68,7 @@ void CData::SetPassword(const Byte *data, unsigned size) for (unsigned i = 0; i < size; i += 2) { unsigned n1 = (Byte)g_CrcTable[(psw[i] - j) & 0xFF]; - unsigned n2 = (Byte)g_CrcTable[(psw[i + 1] + j) & 0xFF]; + unsigned n2 = (Byte)g_CrcTable[(psw[(size_t)i + 1] + j) & 0xFF]; for (unsigned k = 1; (n1 & 0xFF) != n2; n1++, k++) Swap(SubstTable[n1 & 0xFF], SubstTable[(n1 + i + k) & 0xFF]); } @@ -99,22 +99,22 @@ void CData::CryptBlock(Byte *buf, bool encrypt) B = D; D = TB; } - SetUi32(buf + 0, C ^ Keys[0]); - SetUi32(buf + 4, D ^ Keys[1]); - SetUi32(buf + 8, A ^ Keys[2]); - SetUi32(buf + 12, B ^ Keys[3]); + SetUi32(buf + 0, C ^ Keys[0]) + SetUi32(buf + 4, D ^ Keys[1]) + SetUi32(buf + 8, A ^ Keys[2]) + SetUi32(buf + 12, B ^ Keys[3]) UpdateKeys(encrypt ? buf : inBuf); } -STDMETHODIMP CDecoder::Init() +Z7_COM7F_IMF(CDecoder::Init()) { return S_OK; } static const UInt32 kBlockSize = 16; -STDMETHODIMP_(UInt32) CDecoder::Filter(Byte *data, UInt32 size) +Z7_COM7F_IMF2(UInt32, CDecoder::Filter(Byte *data, UInt32 size)) { if (size == 0) return 0; diff --git a/src/plugins/7zip/7za/cpp/7zip/Crypto/Rar20Crypto.h b/src/plugins/7zip/7za/cpp/7zip/Crypto/Rar20Crypto.h index b3033700..85507182 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Crypto/Rar20Crypto.h +++ b/src/plugins/7zip/7za/cpp/7zip/Crypto/Rar20Crypto.h @@ -1,7 +1,7 @@ // Crypto/Rar20Crypto.h -#ifndef __CRYPTO_RAR20_CRYPTO_H -#define __CRYPTO_RAR20_CRYPTO_H +#ifndef ZIP7_INC_CRYPTO_RAR20_CRYPTO_H +#define ZIP7_INC_CRYPTO_RAR20_CRYPTO_H #include "../../Common/MyCom.h" @@ -20,27 +20,33 @@ class CData UInt32 SubstLong(UInt32 t) const { - return (UInt32)SubstTable[(unsigned)t & 255] - | ((UInt32)SubstTable[(unsigned)(t >> 8) & 255] << 8) + return (UInt32)SubstTable[(unsigned)t & 255] + | ((UInt32)SubstTable[(unsigned)(t >> 8) & 255] << 8) | ((UInt32)SubstTable[(unsigned)(t >> 16) & 255] << 16) - | ((UInt32)SubstTable[(unsigned)(t >> 24) & 255] << 24); + | ((UInt32)SubstTable[(unsigned)(t >> 24) ] << 24); } void UpdateKeys(const Byte *data); void CryptBlock(Byte *buf, bool encrypt); public: + ~CData() { Wipe(); } + void Wipe() + { + Z7_memset_0_ARRAY(SubstTable); + Z7_memset_0_ARRAY(Keys); + } + void EncryptBlock(Byte *buf) { CryptBlock(buf, true); } void DecryptBlock(Byte *buf) { CryptBlock(buf, false); } void SetPassword(const Byte *password, unsigned passwordLen); }; -class CDecoder: +class CDecoder Z7_final: public ICompressFilter, public CMyUnknownImp, public CData { -public: - MY_UNKNOWN_IMP - INTERFACE_ICompressFilter(;) + Z7_COM_UNKNOWN_IMP_0 + Z7_IFACE_COM7_IMP(ICompressFilter) }; }} diff --git a/src/plugins/7zip/7za/cpp/7zip/Crypto/Rar5Aes.cpp b/src/plugins/7zip/7za/cpp/7zip/Crypto/Rar5Aes.cpp index 648a3d4d..34ea4ff4 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Crypto/Rar5Aes.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Crypto/Rar5Aes.cpp @@ -4,19 +4,21 @@ #include "../../../C/CpuArch.h" -#ifndef _7ZIP_ST +#ifndef Z7_ST #include "../../Windows/Synchronization.h" #endif +#include "HmacSha256.h" #include "Rar5Aes.h" +#define MY_ALIGN_FOR_SHA256 MY_ALIGN(16) + namespace NCrypto { namespace NRar5 { static const unsigned kNumIterationsLog_Max = 24; - -static const unsigned kPswCheckCsumSize = 4; -static const unsigned kCheckSize = kPswCheckSize + kPswCheckCsumSize; +static const unsigned kPswCheckCsumSize32 = 1; +static const unsigned kCheckSize32 = kPswCheckSize32 + kPswCheckCsumSize32; CKey::CKey(): _needCalc(true), @@ -26,18 +28,31 @@ CKey::CKey(): _salt[i] = 0; } +CKey::~CKey() +{ + Wipe(); +} + +void CKey::Wipe() +{ + _password.Wipe(); + Z7_memset_0_ARRAY(_salt); + // Z7_memset_0_ARRAY(_key32); + // Z7_memset_0_ARRAY(_check_Calced32); + // Z7_memset_0_ARRAY(_hashKey32); + CKeyBase::Wipe(); +} + CDecoder::CDecoder(): CAesCbcDecoder(kAesKeySize) {} static unsigned ReadVarInt(const Byte *p, unsigned maxSize, UInt64 *val) { - unsigned i; *val = 0; - - for (i = 0; i < maxSize;) + for (unsigned i = 0; i < maxSize && i < 10;) { - Byte b = p[i]; - if (i < 10) - *val |= (UInt64)(b & 0x7F) << (7 * i++); + const Byte b = p[i]; + *val |= (UInt64)(b & 0x7F) << (7 * i); + i++; if ((b & 0x80) == 0) return i; } @@ -64,7 +79,7 @@ HRESULT CDecoder::SetDecoderProps(const Byte *p, unsigned size, bool includeIV, size -= num; bool isCheck = IsThereCheck(); - if (size != 1 + kSaltSize + (includeIV ? AES_BLOCK_SIZE : 0) + (unsigned)(isCheck ? kCheckSize : 0)) + if (size != 1 + kSaltSize + (includeIV ? AES_BLOCK_SIZE : 0) + (unsigned)(isCheck ? kCheckSize32 * 4 : 0)) return E_NOTIMPL; if (_numIterationsLog != p[0]) @@ -93,19 +108,21 @@ HRESULT CDecoder::SetDecoderProps(const Byte *p, unsigned size, bool includeIV, if (isCheck) { - memcpy(_check, p, kPswCheckSize); + memcpy(_check32, p, sizeof(_check32)); + MY_ALIGN_FOR_SHA256 CSha256 sha; + MY_ALIGN_FOR_SHA256 Byte digest[SHA256_DIGEST_SIZE]; Sha256_Init(&sha); - Sha256_Update(&sha, _check, kPswCheckSize); + Sha256_Update(&sha, (const Byte *)_check32, sizeof(_check32)); Sha256_Final(&sha, digest); - _canCheck = (memcmp(digest, p + kPswCheckSize, kPswCheckCsumSize) == 0); + _canCheck = (memcmp(digest, p + sizeof(_check32), kPswCheckCsumSize32 * 4) == 0); if (_canCheck && isService) { // There was bug in RAR 5.21- : PswCheck field in service records ("QO") contained zeros. // so we disable password checking for such bad records. _canCheck = false; - for (unsigned i = 0; i < kPswCheckSize; i++) + for (unsigned i = 0; i < kPswCheckSize32 * 4; i++) if (p[i] != 0) { _canCheck = true; @@ -123,47 +140,52 @@ void CDecoder::SetPassword(const Byte *data, size_t size) if (size != _password.Size() || memcmp(data, _password, size) != 0) { _needCalc = true; + _password.Wipe(); _password.CopyFrom(data, size); } } -STDMETHODIMP CDecoder::Init() +Z7_COM7F_IMF(CDecoder::Init()) { CalcKey_and_CheckPassword(); - RINOK(SetKey(_key, kAesKeySize)); - RINOK(SetInitVector(_iv, AES_BLOCK_SIZE)); - return CAesCbcCoder::Init(); + RINOK(SetKey((const Byte *)_key32, kAesKeySize)) + RINOK(SetInitVector(_iv, AES_BLOCK_SIZE)) + return CAesCoder::Init(); } UInt32 CDecoder::Hmac_Convert_Crc32(UInt32 crc) const { + MY_ALIGN_FOR_SHA256 NSha256::CHmac ctx; - ctx.SetKey(_hashKey, NSha256::kDigestSize); - Byte v[4]; - SetUi32(v, crc); - ctx.Update(v, 4); - Byte h[NSha256::kDigestSize]; - ctx.Final(h); + ctx.SetKey((const Byte *)_hashKey32, NSha256::kDigestSize); + UInt32 v; + SetUi32a(&v, crc) + ctx.Update((const Byte *)&v, 4); + MY_ALIGN_FOR_SHA256 + UInt32 h[SHA256_NUM_DIGEST_WORDS]; + ctx.Final((Byte *)h); crc = 0; - for (unsigned i = 0; i < NSha256::kDigestSize; i++) - crc ^= (UInt32)h[i] << ((i & 3) * 8); + for (unsigned i = 0; i < SHA256_NUM_DIGEST_WORDS; i++) + crc ^= (UInt32)GetUi32a(h + i); return crc; -}; +} void CDecoder::Hmac_Convert_32Bytes(Byte *data) const { + MY_ALIGN_FOR_SHA256 NSha256::CHmac ctx; - ctx.SetKey(_hashKey, NSha256::kDigestSize); + ctx.SetKey((const Byte *)_hashKey32, NSha256::kDigestSize); ctx.Update(data, NSha256::kDigestSize); ctx.Final(data); -}; +} + +static CKey g_Key; -#ifndef _7ZIP_ST - static CKey g_Key; +#ifndef Z7_ST static NWindows::NSynchronization::CCriticalSection g_GlobalKeyCacheCriticalSection; #define MT_LOCK NWindows::NSynchronization::CCriticalSectionLock lock(g_GlobalKeyCacheCriticalSection); #else @@ -185,27 +207,31 @@ bool CDecoder::CalcKey_and_CheckPassword() if (_needCalc) { - Byte pswCheck[SHA256_DIGEST_SIZE]; - + MY_ALIGN_FOR_SHA256 + UInt32 pswCheck[SHA256_NUM_DIGEST_WORDS]; { // Pbkdf HMAC-SHA-256 - + MY_ALIGN_FOR_SHA256 NSha256::CHmac baseCtx; baseCtx.SetKey(_password, _password.Size()); - - NSha256::CHmac ctx = baseCtx; + MY_ALIGN_FOR_SHA256 + NSha256::CHmac ctx; + ctx = baseCtx; ctx.Update(_salt, sizeof(_salt)); - Byte u[NSha256::kDigestSize]; - Byte key[NSha256::kDigestSize]; + MY_ALIGN_FOR_SHA256 + UInt32 u[SHA256_NUM_DIGEST_WORDS]; + MY_ALIGN_FOR_SHA256 + UInt32 key[SHA256_NUM_DIGEST_WORDS]; - u[0] = 0; - u[1] = 0; - u[2] = 0; - u[3] = 1; + // u[0] = 0; + // u[1] = 0; + // u[2] = 0; + // u[3] = 1; + SetUi32a(u, 0x1000000) - ctx.Update(u, 4); - ctx.Final(u); + ctx.Update((const Byte *)(const void *)u, 4); + ctx.Final((Byte *)(void *)u); memcpy(key, u, NSha256::kDigestSize); @@ -213,35 +239,24 @@ bool CDecoder::CalcKey_and_CheckPassword() for (unsigned i = 0; i < 3; i++) { - UInt32 j = numIterations; - - for (; j != 0; j--) + for (; numIterations != 0; numIterations--) { ctx = baseCtx; - ctx.Update(u, NSha256::kDigestSize); - ctx.Final(u); - for (unsigned s = 0; s < NSha256::kDigestSize; s++) + ctx.Update((const Byte *)(const void *)u, NSha256::kDigestSize); + ctx.Final((Byte *)(void *)u); + for (unsigned s = 0; s < Z7_ARRAY_SIZE(u); s++) key[s] ^= u[s]; } // RAR uses additional iterations for additional keys - memcpy((i == 0 ? _key : (i == 1 ? _hashKey : pswCheck)), key, NSha256::kDigestSize); + memcpy(i == 0 ? _key32 : i == 1 ? _hashKey32 : pswCheck, + key, NSha256::kDigestSize); numIterations = 16; } } - - { - unsigned i; - - for (i = 0; i < kPswCheckSize; i++) - _check_Calced[i] = pswCheck[i]; - - for (i = kPswCheckSize; i < SHA256_DIGEST_SIZE; i++) - _check_Calced[i & (kPswCheckSize - 1)] ^= pswCheck[i]; - } - + _check_Calced32[0] = pswCheck[0] ^ pswCheck[2] ^ pswCheck[4] ^ pswCheck[6]; + _check_Calced32[1] = pswCheck[1] ^ pswCheck[3] ^ pswCheck[5] ^ pswCheck[7]; _needCalc = false; - { MT_LOCK g_Key = *this; @@ -250,7 +265,7 @@ bool CDecoder::CalcKey_and_CheckPassword() } if (IsThereCheck() && _canCheck) - return (memcmp(_check_Calced, _check, kPswCheckSize) == 0); + return memcmp(_check_Calced32, _check32, sizeof(_check32)) == 0; return true; } diff --git a/src/plugins/7zip/7za/cpp/7zip/Crypto/Rar5Aes.h b/src/plugins/7zip/7za/cpp/7zip/Crypto/Rar5Aes.h index c69b24e4..c6059aa2 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Crypto/Rar5Aes.h +++ b/src/plugins/7zip/7za/cpp/7zip/Crypto/Rar5Aes.h @@ -1,20 +1,19 @@ // Crypto/Rar5Aes.h -#ifndef __CRYPTO_RAR5_AES_H -#define __CRYPTO_RAR5_AES_H +#ifndef ZIP7_INC_CRYPTO_RAR5_AES_H +#define ZIP7_INC_CRYPTO_RAR5_AES_H -#include "../../../C/Aes.h" +#include "../../../C/Sha256.h" #include "../../Common/MyBuffer.h" -#include "HmacSha256.h" #include "MyAes.h" namespace NCrypto { namespace NRar5 { const unsigned kSaltSize = 16; -const unsigned kPswCheckSize = 8; +const unsigned kPswCheckSize32 = 2; const unsigned kAesKeySize = 32; namespace NCryptoFlags @@ -23,51 +22,65 @@ namespace NCryptoFlags const unsigned kUseMAC = 1 << 1; } -struct CKey +struct CKeyBase { - bool _needCalc; +protected: + UInt32 _key32[kAesKeySize / 4]; + UInt32 _hashKey32[SHA256_NUM_DIGEST_WORDS]; + UInt32 _check_Calced32[kPswCheckSize32]; - unsigned _numIterationsLog; - Byte _salt[kSaltSize]; - CByteBuffer _password; + void Wipe() + { + memset(this, 0, sizeof(*this)); + } - Byte _key[kAesKeySize]; - Byte _check_Calced[kPswCheckSize]; - Byte _hashKey[SHA256_DIGEST_SIZE]; - - void CopyCalcedKeysFrom(const CKey &k) + void CopyCalcedKeysFrom(const CKeyBase &k) { - memcpy(_key, k._key, sizeof(_key)); - memcpy(_check_Calced, k._check_Calced, sizeof(_check_Calced)); - memcpy(_hashKey, k._hashKey, sizeof(_hashKey)); + *this = k; } +}; +struct CKey: public CKeyBase +{ + CByteBuffer _password; + bool _needCalc; + unsigned _numIterationsLog; + Byte _salt[kSaltSize]; + bool IsKeyEqualTo(const CKey &key) { - return (_numIterationsLog == key._numIterationsLog + return _numIterationsLog == key._numIterationsLog && memcmp(_salt, key._salt, sizeof(_salt)) == 0 - && _password == key._password); + && _password == key._password; } - + CKey(); + ~CKey(); + + void Wipe(); + +#ifdef Z7_CPP_IS_SUPPORTED_default + // CKey(const CKey &) = default; + CKey& operator =(const CKey &) = default; +#endif }; -class CDecoder: +class CDecoder Z7_final: public CAesCbcDecoder, public CKey { - Byte _check[kPswCheckSize]; + UInt32 _check32[kPswCheckSize32]; bool _canCheck; UInt64 Flags; - bool IsThereCheck() const { return ((Flags & NCryptoFlags::kPswCheck) != 0); } + bool IsThereCheck() const { return (Flags & NCryptoFlags::kPswCheck) != 0; } public: Byte _iv[AES_BLOCK_SIZE]; CDecoder(); - STDMETHOD(Init)(); + Z7_COM7F_IMP(Init()) void SetPassword(const Byte *data, size_t size); HRESULT SetDecoderProps(const Byte *data, unsigned size, bool includeIV, bool isService); diff --git a/src/plugins/7zip/7za/cpp/7zip/Crypto/RarAes.cpp b/src/plugins/7zip/7za/cpp/7zip/Crypto/RarAes.cpp index 98e9f62d..e63f82c6 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Crypto/RarAes.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Crypto/RarAes.cpp @@ -2,6 +2,9 @@ #include "StdAfx.h" +#include "../../../C/CpuArch.h" +#include "../../../C/RotateDefs.h" + #include "RarAes.h" #include "Sha1Cls.h" @@ -71,17 +74,54 @@ void CDecoder::SetPassword(const Byte *data, unsigned size) } if (!_needCalc && !same) _needCalc = true; + _password.Wipe(); _password.CopyFrom(data, (size_t)size); } -STDMETHODIMP CDecoder::Init() +Z7_COM7F_IMF(CDecoder::Init()) { CalcKey(); - RINOK(SetKey(_key, kAesKeySize)); - RINOK(SetInitVector(_iv, AES_BLOCK_SIZE)); - return CAesCbcCoder::Init(); + RINOK(SetKey(_key, kAesKeySize)) + RINOK(SetInitVector(_iv, AES_BLOCK_SIZE)) + return CAesCoder::Init(); } + +// if (password_size_in_bytes + SaltSize > 64), +// the original rar code updates password_with_salt buffer +// with some generated data from SHA1 code. + +// #define RAR_SHA1_REDUCE + +#ifdef RAR_SHA1_REDUCE + #define kNumW 16 + #define WW(i) W[(i)&15] +#else + #define kNumW 80 + #define WW(i) W[i] +#endif + +static void UpdatePswDataSha1(Byte *data) +{ + UInt32 W[kNumW]; + size_t i; + + for (i = 0; i < SHA1_NUM_BLOCK_WORDS; i++) + W[i] = GetBe32(data + i * 4); + + for (i = 16; i < 80; i++) + { + const UInt32 t = WW((i)-3) ^ WW((i)-8) ^ WW((i)-14) ^ WW((i)-16); + WW(i) = rotlFixed(t, 1); + } + + for (i = 0; i < SHA1_NUM_BLOCK_WORDS; i++) + { + SetUi32(data + i * 4, W[kNumW - SHA1_NUM_BLOCK_WORDS + i]) + } +} + + void CDecoder::CalcKey() { if (!_needCalc) @@ -89,6 +129,7 @@ void CDecoder::CalcKey() const unsigned kSaltSize = 8; + MY_ALIGN (16) Byte buf[kPasswordLen_Bytes_MAX + kSaltSize]; if (_password.Size() != 0) @@ -102,20 +143,48 @@ void CDecoder::CalcKey() rawSize += kSaltSize; } + MY_ALIGN (16) NSha1::CContext sha; sha.Init(); + MY_ALIGN (16) Byte digest[NSha1::kDigestSize]; // rar reverts hash for sha. - const UInt32 kNumRounds = ((UInt32)1 << 18); + const UInt32 kNumRounds = (UInt32)1 << 18; + UInt32 pos = 0; UInt32 i; for (i = 0; i < kNumRounds; i++) { - sha.UpdateRar(buf, rawSize /* , _rar350Mode */); + sha.Update(buf, rawSize); + // if (_rar350Mode) + { + const UInt32 kBlockSize = 64; + const UInt32 endPos = (pos + (UInt32)rawSize) & ~(kBlockSize - 1); + if (endPos > pos + kBlockSize) + { + UInt32 curPos = pos & ~(kBlockSize - 1); + curPos += kBlockSize; + do + { + UpdatePswDataSha1(buf + (curPos - pos)); + curPos += kBlockSize; + } + while (curPos != endPos); + } + } + pos += (UInt32)rawSize; +#if 1 + UInt32 pswNum; + SetUi32a(&pswNum, i) + sha.Update((const Byte *)&pswNum, 3); +#else Byte pswNum[3] = { (Byte)i, (Byte)(i >> 8), (Byte)(i >> 16) }; - sha.UpdateRar(pswNum, 3 /* , _rar350Mode */); + sha.Update(pswNum, 3); +#endif + pos += 3; if (i % (kNumRounds / 16) == 0) { + MY_ALIGN (16) NSha1::CContext shaTemp = sha; shaTemp.Final(digest); _iv[i / (kNumRounds / 16)] = (Byte)digest[4 * 4 + 3]; diff --git a/src/plugins/7zip/7za/cpp/7zip/Crypto/RarAes.h b/src/plugins/7zip/7za/cpp/7zip/Crypto/RarAes.h index 645da1af..d3c104c8 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Crypto/RarAes.h +++ b/src/plugins/7zip/7za/cpp/7zip/Crypto/RarAes.h @@ -1,7 +1,7 @@ // Crypto/RarAes.h -#ifndef __CRYPTO_RAR_AES_H -#define __CRYPTO_RAR_AES_H +#ifndef ZIP7_INC_CRYPTO_RAR_AES_H +#define ZIP7_INC_CRYPTO_RAR_AES_H #include "../../../C/Aes.h" @@ -16,10 +16,8 @@ namespace NRar3 { const unsigned kAesKeySize = 16; -class CDecoder: +class CDecoder Z7_final: public CAesCbcDecoder - // public ICompressSetDecoderProperties2, - // public ICryptoSetPassword { Byte _salt[8]; bool _thereIsSalt; @@ -33,17 +31,21 @@ class CDecoder: void CalcKey(); public: - /* - MY_UNKNOWN_IMP1( - ICryptoSetPassword - // ICompressSetDecoderProperties2 - */ - STDMETHOD(Init)(); + Z7_COM7F_IMP(Init()) void SetPassword(const Byte *data, unsigned size); HRESULT SetDecoderProperties2(const Byte *data, UInt32 size); CDecoder(); + + ~CDecoder() Z7_DESTRUCTOR_override { Wipe(); } + void Wipe() + { + _password.Wipe(); + Z7_memset_0_ARRAY(_salt); + Z7_memset_0_ARRAY(_key); + Z7_memset_0_ARRAY(_iv); + } // void SetRar350Mode(bool rar350Mode) { _rar350Mode = rar350Mode; } }; diff --git a/src/plugins/7zip/7za/cpp/7zip/Crypto/Sha1Cls.h b/src/plugins/7zip/7za/cpp/7zip/Crypto/Sha1Cls.h index 71acbdec..56ae040d 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Crypto/Sha1Cls.h +++ b/src/plugins/7zip/7za/cpp/7zip/Crypto/Sha1Cls.h @@ -1,7 +1,7 @@ -// Crypto/Sha1.h +// Crypto/Sha1Cls.h -#ifndef __CRYPTO_SHA1_H -#define __CRYPTO_SHA1_H +#ifndef ZIP7_INC_CRYPTO_SHA1_CLS_H +#define ZIP7_INC_CRYPTO_SHA1_CLS_H #include "../../../C/Sha1.h" @@ -14,35 +14,21 @@ const unsigned kNumDigestWords = SHA1_NUM_DIGEST_WORDS; const unsigned kBlockSize = SHA1_BLOCK_SIZE; const unsigned kDigestSize = SHA1_DIGEST_SIZE; -class CContextBase +class CContext { -protected: CSha1 _s; public: void Init() throw() { Sha1_Init(&_s); } - void GetBlockDigest(const UInt32 *blockData, UInt32 *destDigest) throw() { Sha1_GetBlockDigest(&_s, blockData, destDigest); } -}; - -class CContext: public CContextBase -{ -public: void Update(const Byte *data, size_t size) throw() { Sha1_Update(&_s, data, size); } - void UpdateRar(Byte *data, size_t size /* , bool rar350Mode */) throw() { Sha1_Update_Rar(&_s, data, size /* , rar350Mode ? 1 : 0 */); } void Final(Byte *digest) throw() { Sha1_Final(&_s, digest); } -}; - -class CContext32: public CContextBase -{ -public: - void Update(const UInt32 *data, size_t size) throw() { Sha1_32_Update(&_s, data, size); } - void Final(UInt32 *digest) throw() { Sha1_32_Final(&_s, digest); } - - /* PrepareBlock can be used only when size <= 13. size in Words - _buffer must be empty (_count & 0xF) == 0) */ - void PrepareBlock(UInt32 *block, unsigned size) const throw() + void PrepareBlock(Byte *block, unsigned size) const throw() + { + Sha1_PrepareBlock(&_s, block, size); + } + void GetBlockDigest(const Byte *blockData, Byte *destDigest) const throw() { - Sha1_32_PrepareBlock(&_s, block, size); + Sha1_GetBlockDigest(&_s, blockData, destDigest); } }; diff --git a/src/plugins/7zip/7za/cpp/7zip/Crypto/StdAfx.h b/src/plugins/7zip/7za/cpp/7zip/Crypto/StdAfx.h index 1cbd7fea..80866550 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Crypto/StdAfx.h +++ b/src/plugins/7zip/7za/cpp/7zip/Crypto/StdAfx.h @@ -1,8 +1,11 @@ // StdAfx.h -#ifndef __STDAFX_H -#define __STDAFX_H +#ifndef ZIP7_INC_STDAFX_H +#define ZIP7_INC_STDAFX_H +#if defined(_MSC_VER) && _MSC_VER >= 1800 +#pragma warning(disable : 4464) // relative include path contains '..' +#endif #include "../../Common/Common.h" #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Crypto/WzAes.cpp b/src/plugins/7zip/7za/cpp/7zip/Crypto/WzAes.cpp index 4572f06e..3f2adab0 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Crypto/WzAes.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Crypto/WzAes.cpp @@ -16,9 +16,6 @@ Note: you must include MyAes.cpp to project to initialize AES tables #include "RandGen.h" #include "WzAes.h" -// define it if you don't want to use speed-optimized version of NSha1::Pbkdf2Hmac -// #define _NO_WZAES_OPTIMIZATIONS - namespace NCrypto { namespace NWzAes { @@ -26,69 +23,44 @@ const unsigned kAesKeySizeMax = 32; static const UInt32 kNumKeyGenIterations = 1000; -STDMETHODIMP CBaseCoder::CryptoSetPassword(const Byte *data, UInt32 size) +Z7_COM7F_IMF(CBaseCoder::CryptoSetPassword(const Byte *data, UInt32 size)) { if (size > kPasswordSizeMax) return E_INVALIDARG; + _key.Password.Wipe(); _key.Password.CopyFrom(data, (size_t)size); return S_OK; } void CBaseCoder::Init2() { + _hmacOverCalc = 0; const unsigned dkSizeMax32 = (2 * kAesKeySizeMax + kPwdVerifSize + 3) / 4; Byte dk[dkSizeMax32 * 4]; const unsigned keySize = _key.GetKeySize(); - const unsigned dkSize = 2 * keySize + kPwdVerifSize; + const unsigned dkSize = 2 * keySize + ((kPwdVerifSize + 3) & ~(unsigned)3); // for (unsigned ii = 0; ii < 1000; ii++) { - #ifdef _NO_WZAES_OPTIMIZATIONS - NSha1::Pbkdf2Hmac( _key.Password, _key.Password.Size(), _key.Salt, _key.GetSaltSize(), kNumKeyGenIterations, dk, dkSize); - - #else - - UInt32 dk32[dkSizeMax32]; - const unsigned dkSize32 = (dkSize + 3) / 4; - UInt32 salt[kSaltSizeMax / 4]; - unsigned numSaltWords = _key.GetNumSaltWords(); - - for (unsigned i = 0; i < numSaltWords; i++) - { - const Byte *src = _key.Salt + i * 4; - salt[i] = GetBe32(src); - } - - NSha1::Pbkdf2Hmac32( - _key.Password, _key.Password.Size(), - salt, numSaltWords, - kNumKeyGenIterations, - dk32, dkSize32); - - /* - for (unsigned j = 0; j < dkSize; j++) - dk[j] = (Byte)(dk32[j / 4] >> (24 - 8 * (j & 3))); - */ - for (unsigned j = 0; j < dkSize32; j++) - SetBe32(dk + j * 4, dk32[j]); - - #endif } - _hmac.SetKey(dk + keySize, keySize); + Hmac()->SetKey(dk + keySize, keySize); memcpy(_key.PwdVerifComputed, dk + 2 * keySize, kPwdVerifSize); - Aes_SetKey_Enc(_aes.aes + _aes.offset + 8, dk, keySize); - AesCtr2_Init(&_aes); + // Aes_SetKey_Enc(_aes.Aes() + 8, dk, keySize); + // AesCtr2_Init(&_aes); + _aesCoderSpec->SetKeySize(keySize); + if (_aesCoderSpec->SetKey(dk, keySize) != S_OK) throw 2; + if (_aesCoderSpec->Init() != S_OK) throw 3; } -STDMETHODIMP CBaseCoder::Init() +Z7_COM7F_IMF(CBaseCoder::Init()) { return S_OK; } @@ -96,21 +68,22 @@ STDMETHODIMP CBaseCoder::Init() HRESULT CEncoder::WriteHeader(ISequentialOutStream *outStream) { unsigned saltSize = _key.GetSaltSize(); - g_RandomGenerator.Generate(_key.Salt, saltSize); + MY_RAND_GEN(_key.Salt, saltSize); Init2(); - RINOK(WriteStream(outStream, _key.Salt, saltSize)); + RINOK(WriteStream(outStream, _key.Salt, saltSize)) return WriteStream(outStream, _key.PwdVerifComputed, kPwdVerifSize); } HRESULT CEncoder::WriteFooter(ISequentialOutStream *outStream) { - Byte mac[kMacSize]; - _hmac.Final(mac, kMacSize); + MY_ALIGN (16) + UInt32 mac[NSha1::kNumDigestWords]; + Hmac()->Final((Byte *)mac); return WriteStream(outStream, mac, kMacSize); } /* -STDMETHODIMP CDecoder::SetDecoderProperties2(const Byte *data, UInt32 size) +Z7_COM7F_IMF(CDecoder::SetDecoderProperties2(const Byte *data, UInt32 size)) { if (size != 1) return E_INVALIDARG; @@ -121,10 +94,10 @@ STDMETHODIMP CDecoder::SetDecoderProperties2(const Byte *data, UInt32 size) HRESULT CDecoder::ReadHeader(ISequentialInStream *inStream) { - unsigned saltSize = _key.GetSaltSize(); - unsigned extraSize = saltSize + kPwdVerifSize; + const unsigned saltSize = _key.GetSaltSize(); + const unsigned extraSize = saltSize + kPwdVerifSize; Byte temp[kSaltSizeMax + kPwdVerifSize]; - RINOK(ReadStream_FAIL(inStream, temp, extraSize)); + RINOK(ReadStream_FAIL(inStream, temp, extraSize)) unsigned i; for (i = 0; i < saltSize; i++) _key.Salt[i] = temp[i]; @@ -150,34 +123,43 @@ bool CDecoder::Init_and_CheckPassword() HRESULT CDecoder::CheckMac(ISequentialInStream *inStream, bool &isOK) { isOK = false; + MY_ALIGN (16) Byte mac1[kMacSize]; - RINOK(ReadStream_FAIL(inStream, mac1, kMacSize)); - Byte mac2[kMacSize]; - _hmac.Final(mac2, kMacSize); - isOK = CompareArrays(mac1, mac2, kMacSize); + RINOK(ReadStream_FAIL(inStream, mac1, kMacSize)) + MY_ALIGN (16) + UInt32 mac2[NSha1::kNumDigestWords]; + Hmac()->Final((Byte *)mac2); + isOK = CompareArrays(mac1, (const Byte *)mac2, kMacSize); + if (_hmacOverCalc) + isOK = false; return S_OK; } -CAesCtr2::CAesCtr2() +/* + +CAesCtr2::CAesCtr2(): + aes((4 + AES_NUM_IVMRK_WORDS) * 4) { - offset = ((0 - (unsigned)(ptrdiff_t)aes) & 0xF) / sizeof(UInt32); + // offset = ((0 - (unsigned)(ptrdiff_t)aes) & 0xF) / sizeof(UInt32); + // first 16 bytes are buffer for last block data. + // so the ivAES is aligned for (Align + 16). } void AesCtr2_Init(CAesCtr2 *p) { - UInt32 *ctr = p->aes + p->offset + 4; + UInt32 *ctr = p->Aes() + 4; unsigned i; for (i = 0; i < 4; i++) ctr[i] = 0; p->pos = AES_BLOCK_SIZE; } -/* (size != 16 * N) is allowed only for last call */ +// (size != 16 * N) is allowed only for last call void AesCtr2_Code(CAesCtr2 *p, Byte *data, SizeT size) { unsigned pos = p->pos; - UInt32 *buf32 = p->aes + p->offset; + UInt32 *buf32 = p->Aes(); if (size == 0) return; @@ -188,6 +170,8 @@ void AesCtr2_Code(CAesCtr2 *p, Byte *data, SizeT size) *data++ ^= buf[pos++]; while (--size != 0 && pos != AES_BLOCK_SIZE); } + + // (size == 0 || pos == AES_BLOCK_SIZE) if (size >= 16) { @@ -196,8 +180,10 @@ void AesCtr2_Code(CAesCtr2 *p, Byte *data, SizeT size) size2 <<= 4; data += size2; size -= size2; - pos = AES_BLOCK_SIZE; + // (pos == AES_BLOCK_SIZE) } + + // (size < 16) if (size != 0) { @@ -215,20 +201,30 @@ void AesCtr2_Code(CAesCtr2 *p, Byte *data, SizeT size) p->pos = pos; } +*/ /* (size != 16 * N) is allowed only for last Filter() call */ -STDMETHODIMP_(UInt32) CEncoder::Filter(Byte *data, UInt32 size) +Z7_COM7F_IMF2(UInt32, CEncoder::Filter(Byte *data, UInt32 size)) { - AesCtr2_Code(&_aes, data, size); - _hmac.Update(data, size); + // AesCtr2_Code(&_aes, data, size); + size = _aesCoder->Filter(data, size); + Hmac()->Update(data, size); return size; } -STDMETHODIMP_(UInt32) CDecoder::Filter(Byte *data, UInt32 size) +Z7_COM7F_IMF2(UInt32, CDecoder::Filter(Byte *data, UInt32 size)) { - _hmac.Update(data, size); - AesCtr2_Code(&_aes, data, size); + if (size >= 16) + size &= ~(UInt32)15; + if (_hmacOverCalc < size) + { + Hmac()->Update(data + _hmacOverCalc, size - _hmacOverCalc); + _hmacOverCalc = size; + } + // AesCtr2_Code(&_aes, data, size); + size = _aesCoder->Filter(data, size); + _hmacOverCalc -= size; return size; } diff --git a/src/plugins/7zip/7za/cpp/7zip/Crypto/WzAes.h b/src/plugins/7zip/7za/cpp/7zip/Crypto/WzAes.h index 41f9949e..113ec81a 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Crypto/WzAes.h +++ b/src/plugins/7zip/7za/cpp/7zip/Crypto/WzAes.h @@ -9,18 +9,15 @@ specified in "A Password Based File Encryption Utility": - 2 bytes contain Password Verifier's Code */ -#ifndef __CRYPTO_WZ_AES_H -#define __CRYPTO_WZ_AES_H - -#include "../../../C/Aes.h" +#ifndef ZIP7_INC_CRYPTO_WZ_AES_H +#define ZIP7_INC_CRYPTO_WZ_AES_H #include "../../Common/MyBuffer.h" -#include "../../Common/MyCom.h" -#include "../ICoder.h" #include "../IPassword.h" #include "HmacSha1.h" +#include "MyAes.h" namespace NCrypto { namespace NWzAes { @@ -64,37 +61,65 @@ struct CKeyInfo unsigned GetNumSaltWords() const { return (KeySizeMode + 1); } CKeyInfo(): KeySizeMode(kKeySizeMode_AES256) {} + + void Wipe() + { + Password.Wipe(); + Z7_memset_0_ARRAY(Salt); + Z7_memset_0_ARRAY(PwdVerifComputed); + } + + ~CKeyInfo() { Wipe(); } }; +/* struct CAesCtr2 { unsigned pos; - unsigned offset; - UInt32 aes[4 + AES_NUM_IVMRK_WORDS + 3]; + CAlignedBuffer aes; + UInt32 *Aes() { return (UInt32 *)(Byte *)aes; } + + // unsigned offset; + // UInt32 aes[4 + AES_NUM_IVMRK_WORDS + 3]; + // UInt32 *Aes() { return aes + offset; } CAesCtr2(); }; void AesCtr2_Init(CAesCtr2 *p); void AesCtr2_Code(CAesCtr2 *p, Byte *data, SizeT size); +*/ class CBaseCoder: public ICompressFilter, public ICryptoSetPassword, public CMyUnknownImp { + Z7_COM_UNKNOWN_IMP_1(ICryptoSetPassword) + Z7_COM7F_IMP(Init()) +public: + Z7_IFACE_COM7_IMP(ICryptoSetPassword) protected: CKeyInfo _key; - NSha1::CHmac _hmac; - CAesCtr2 _aes; - void Init2(); -public: - MY_UNKNOWN_IMP1(ICryptoSetPassword) + // NSha1::CHmac _hmac; + // NSha1::CHmac *Hmac() { return &_hmac; } + CAlignedBuffer1 _hmacBuf; + UInt32 _hmacOverCalc; - STDMETHOD(CryptoSetPassword)(const Byte *data, UInt32 size); + NSha1::CHmac *Hmac() { return (NSha1::CHmac *)(void *)(Byte *)_hmacBuf; } + + // CAesCtr2 _aes; + CAesCoder *_aesCoderSpec; + CMyComPtr _aesCoder; + CBaseCoder(): + _hmacBuf(sizeof(NSha1::CHmac)) + { + _aesCoderSpec = new CAesCtrCoder(32); + _aesCoder = _aesCoderSpec; + } - STDMETHOD(Init)(); - + void Init2(); +public: unsigned GetHeaderSize() const { return _key.GetSaltSize() + kPwdVerifSize; } unsigned GetAddPackSize() const { return GetHeaderSize() + kMacSize; } @@ -105,26 +130,27 @@ class CBaseCoder: _key.KeySizeMode = (EKeySizeMode)mode; return true; } + + virtual ~CBaseCoder() {} }; -class CEncoder: +class CEncoder Z7_final: public CBaseCoder { + Z7_COM7F_IMP2(UInt32, Filter(Byte *data, UInt32 size)) public: - STDMETHOD_(UInt32, Filter)(Byte *data, UInt32 size); HRESULT WriteHeader(ISequentialOutStream *outStream); HRESULT WriteFooter(ISequentialOutStream *outStream); }; -class CDecoder: +class CDecoder Z7_final: public CBaseCoder // public ICompressSetDecoderProperties2 { Byte _pwdVerifFromArchive[kPwdVerifSize]; + Z7_COM7F_IMP2(UInt32, Filter(Byte *data, UInt32 size)) public: - // ICompressSetDecoderProperties2 - // STDMETHOD(SetDecoderProperties2)(const Byte *data, UInt32 size); - STDMETHOD_(UInt32, Filter)(Byte *data, UInt32 size); + // Z7_IFACE_COM7_IMP(ICompressSetDecoderProperties2) HRESULT ReadHeader(ISequentialInStream *inStream); bool Init_and_CheckPassword(); HRESULT CheckMac(ISequentialInStream *inStream, bool &isOK); diff --git a/src/plugins/7zip/7za/cpp/7zip/Crypto/ZipCrypto.cpp b/src/plugins/7zip/7za/cpp/7zip/Crypto/ZipCrypto.cpp index ae715063..047e2bd6 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Crypto/ZipCrypto.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Crypto/ZipCrypto.cpp @@ -20,14 +20,14 @@ namespace NZip { #define DECRYPT_BYTE_1 UInt32 temp = key2 | 2; #define DECRYPT_BYTE_2 ((Byte)((temp * (temp ^ 1)) >> 8)) -STDMETHODIMP CCipher::CryptoSetPassword(const Byte *data, UInt32 size) +Z7_COM7F_IMF(CCipher::CryptoSetPassword(const Byte *data, UInt32 size)) { UInt32 key0 = 0x12345678; UInt32 key1 = 0x23456789; UInt32 key2 = 0x34567890; for (UInt32 i = 0; i < size; i++) - UPDATE_KEYS(data[i]); + UPDATE_KEYS(data[i]) KeyMem0 = key0; KeyMem1 = key1; @@ -36,7 +36,7 @@ STDMETHODIMP CCipher::CryptoSetPassword(const Byte *data, UInt32 size) return S_OK; } -STDMETHODIMP CCipher::Init() +Z7_COM7F_IMF(CCipher::Init()) { return S_OK; } @@ -49,7 +49,7 @@ HRESULT CEncoder::WriteHeader_Check16(ISequentialOutStream *outStream, UInt16 cr PKZIP 2.0+ used 1 byte CRC check. It's more secure. We also use 1 byte CRC. */ - g_RandomGenerator.Generate(h, kHeaderSize - 1); + MY_RAND_GEN(h, kHeaderSize - 1); // h[kHeaderSize - 2] = (Byte)(crc); h[kHeaderSize - 1] = (Byte)(crc >> 8); @@ -58,7 +58,7 @@ HRESULT CEncoder::WriteHeader_Check16(ISequentialOutStream *outStream, UInt16 cr return WriteStream(outStream, h, kHeaderSize); } -STDMETHODIMP_(UInt32) CEncoder::Filter(Byte *data, UInt32 size) +Z7_COM7F_IMF2(UInt32, CEncoder::Filter(Byte *data, UInt32 size)) { UInt32 key0 = this->Key0; UInt32 key1 = this->Key1; @@ -69,7 +69,7 @@ STDMETHODIMP_(UInt32) CEncoder::Filter(Byte *data, UInt32 size) Byte b = data[i]; DECRYPT_BYTE_1 data[i] = (Byte)(b ^ DECRYPT_BYTE_2); - UPDATE_KEYS(b); + UPDATE_KEYS(b) } this->Key0 = key0; @@ -90,7 +90,7 @@ void CDecoder::Init_BeforeDecode() Filter(_header, kHeaderSize); } -STDMETHODIMP_(UInt32) CDecoder::Filter(Byte *data, UInt32 size) +Z7_COM7F_IMF2(UInt32, CDecoder::Filter(Byte *data, UInt32 size)) { UInt32 key0 = this->Key0; UInt32 key1 = this->Key1; @@ -100,7 +100,7 @@ STDMETHODIMP_(UInt32) CDecoder::Filter(Byte *data, UInt32 size) { DECRYPT_BYTE_1 Byte b = (Byte)(data[i] ^ DECRYPT_BYTE_2); - UPDATE_KEYS(b); + UPDATE_KEYS(b) data[i] = b; } diff --git a/src/plugins/7zip/7za/cpp/7zip/Crypto/ZipCrypto.h b/src/plugins/7zip/7za/cpp/7zip/Crypto/ZipCrypto.h index b23d4216..485470ed 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Crypto/ZipCrypto.h +++ b/src/plugins/7zip/7za/cpp/7zip/Crypto/ZipCrypto.h @@ -1,7 +1,7 @@ // Crypto/ZipCrypto.h -#ifndef __CRYPTO_ZIP_CRYPTO_H -#define __CRYPTO_ZIP_CRYPTO_H +#ifndef ZIP7_INC_CRYPTO_ZIP_CRYPTO_H +#define ZIP7_INC_CRYPTO_ZIP_CRYPTO_H #include "../../Common/MyCom.h" @@ -30,6 +30,10 @@ class CCipher: public ICryptoSetPassword, public CMyUnknownImp { + Z7_COM_UNKNOWN_IMP_1(ICryptoSetPassword) + Z7_COM7F_IMP(Init()) +public: + Z7_IFACE_COM7_IMP(ICryptoSetPassword) protected: UInt32 Key0; UInt32 Key1; @@ -47,23 +51,26 @@ class CCipher: } public: - MY_UNKNOWN_IMP1(ICryptoSetPassword) - STDMETHOD(Init)(); - STDMETHOD(CryptoSetPassword)(const Byte *data, UInt32 size); + virtual ~CCipher() + { + Key0 = KeyMem0 = + Key1 = KeyMem1 = + Key2 = KeyMem2 = 0; + } }; -class CEncoder: public CCipher +class CEncoder Z7_final: public CCipher { + Z7_COM7F_IMP2(UInt32, Filter(Byte *data, UInt32 size)) public: - STDMETHOD_(UInt32, Filter)(Byte *data, UInt32 size); HRESULT WriteHeader_Check16(ISequentialOutStream *outStream, UInt16 crc); }; -class CDecoder: public CCipher +class CDecoder Z7_final: public CCipher { + Z7_COM7F_IMP2(UInt32, Filter(Byte *data, UInt32 size)) public: Byte _header[kHeaderSize]; - STDMETHOD_(UInt32, Filter)(Byte *data, UInt32 size); HRESULT ReadHeader(ISequentialInStream *inStream); void Init_BeforeDecode(); }; diff --git a/src/plugins/7zip/7za/cpp/7zip/Crypto/ZipStrong.cpp b/src/plugins/7zip/7za/cpp/7zip/Crypto/ZipStrong.cpp index 6d73a34b..c4e83118 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Crypto/ZipStrong.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/Crypto/ZipStrong.cpp @@ -15,79 +15,105 @@ namespace NZipStrong { static const UInt16 kAES128 = 0x660E; -// DeriveKey* function is similar to CryptDeriveKey() from Windows. -// But MSDN tells that we need such scheme only if -// "the required key length is longer than the hash value" -// but ZipStrong uses it always. +/* + DeriveKey() function is similar to CryptDeriveKey() from Windows. + New version of MSDN contains the following condition in CryptDeriveKey() description: + "If the hash is not a member of the SHA-2 family and the required key is for either 3DES or AES". + Now we support ZipStrong for AES only. And it uses SHA1. + Our DeriveKey() code is equal to CryptDeriveKey() in Windows for such conditions: (SHA1 + AES). + if (method != AES && method != 3DES), probably we need another code. +*/ -static void DeriveKey2(const Byte *digest, Byte c, Byte *dest) +static void DeriveKey2(const UInt32 *digest32, Byte c, UInt32 *dest32) { - Byte buf[64]; - memset(buf, c, 64); - for (unsigned i = 0; i < NSha1::kDigestSize; i++) - buf[i] ^= digest[i]; + const unsigned kBufSize = 64; + MY_ALIGN (16) + UInt32 buf32[kBufSize / 4]; + memset(buf32, c, kBufSize); + for (unsigned i = 0; i < NSha1::kNumDigestWords; i++) + buf32[i] ^= digest32[i]; + MY_ALIGN (16) NSha1::CContext sha; sha.Init(); - sha.Update(buf, 64); - sha.Final(dest); + sha.Update((const Byte *)buf32, kBufSize); + sha.Final((Byte *)dest32); } static void DeriveKey(NSha1::CContext &sha, Byte *key) { - Byte digest[NSha1::kDigestSize]; - sha.Final(digest); - Byte temp[NSha1::kDigestSize * 2]; - DeriveKey2(digest, 0x36, temp); - DeriveKey2(digest, 0x5C, temp + NSha1::kDigestSize); - memcpy(key, temp, 32); + MY_ALIGN (16) + UInt32 digest32[NSha1::kNumDigestWords]; + sha.Final((Byte *)digest32); + MY_ALIGN (16) + UInt32 temp32[NSha1::kNumDigestWords * 2]; + DeriveKey2(digest32, 0x36, temp32); + DeriveKey2(digest32, 0x5C, temp32 + NSha1::kNumDigestWords); + memcpy(key, temp32, 32); } void CKeyInfo::SetPassword(const Byte *data, UInt32 size) { + MY_ALIGN (16) NSha1::CContext sha; sha.Init(); sha.Update(data, size); DeriveKey(sha, MasterKey); } -STDMETHODIMP CBaseCoder::CryptoSetPassword(const Byte *data, UInt32 size) + + +CDecoder::CDecoder() +{ + CAesCbcDecoder *d = new CAesCbcDecoder(); + _cbcDecoder = d; + _aesFilter = d; +} + +Z7_COM7F_IMF(CDecoder::CryptoSetPassword(const Byte *data, UInt32 size)) { _key.SetPassword(data, size); return S_OK; } -STDMETHODIMP CBaseCoder::Init() +Z7_COM7F_IMF(CDecoder::Init()) { return S_OK; } +Z7_COM7F_IMF2(UInt32, CDecoder::Filter(Byte *data, UInt32 size)) +{ + return _aesFilter->Filter(data, size); +} + + HRESULT CDecoder::ReadHeader(ISequentialInStream *inStream, UInt32 crc, UInt64 unpackSize) { Byte temp[4]; - RINOK(ReadStream_FALSE(inStream, temp, 2)); + RINOK(ReadStream_FALSE(inStream, temp, 2)) _ivSize = GetUi16(temp); if (_ivSize == 0) { memset(_iv, 0, 16); - SetUi32(_iv + 0, crc); - SetUi64(_iv + 4, unpackSize); + SetUi32(_iv + 0, crc) + SetUi64(_iv + 4, unpackSize) _ivSize = 12; } else if (_ivSize == 16) { - RINOK(ReadStream_FALSE(inStream, _iv, _ivSize)); + RINOK(ReadStream_FALSE(inStream, _iv, _ivSize)) } else return E_NOTIMPL; - RINOK(ReadStream_FALSE(inStream, temp, 4)); + RINOK(ReadStream_FALSE(inStream, temp, 4)) _remSize = GetUi32(temp); - const UInt32 kAlign = 16; + // const UInt32 kAlign = 16; if (_remSize < 16 || _remSize > (1 << 18)) return E_NOTIMPL; - if (_remSize + kAlign > _buf.Size()) + if (_remSize > _bufAligned.Size()) { - _buf.Alloc(_remSize + kAlign); - _bufAligned = (Byte *)((ptrdiff_t)((Byte *)_buf + kAlign - 1) & ~(ptrdiff_t)(kAlign - 1)); + _bufAligned.AllocAtLeast(_remSize); + if (!(Byte *)_bufAligned) + return E_OUTOFMEMORY; } return ReadStream_FALSE(inStream, _bufAligned, _remSize); } @@ -97,26 +123,26 @@ HRESULT CDecoder::Init_and_CheckPassword(bool &passwOK) passwOK = false; if (_remSize < 16) return E_NOTIMPL; - Byte *p = _bufAligned; - UInt16 format = GetUi16(p); + Byte * const p = _bufAligned; + const unsigned format = GetUi16a(p); if (format != 3) return E_NOTIMPL; - UInt16 algId = GetUi16(p + 2); + unsigned algId = GetUi16a(p + 2); if (algId < kAES128) return E_NOTIMPL; algId -= kAES128; if (algId > 2) return E_NOTIMPL; - UInt16 bitLen = GetUi16(p + 4); - UInt16 flags = GetUi16(p + 6); + const unsigned bitLen = GetUi16a(p + 4); + const unsigned flags = GetUi16a(p + 6); if (algId * 64 + 128 != bitLen) return E_NOTIMPL; _key.KeySize = 16 + algId * 8; - bool cert = ((flags & 2) != 0); + const bool cert = ((flags & 2) != 0); - if ((flags & 0x4000) != 0) + if (flags & 0x4000) { - // Use 3DES + // Use 3DES for rd data return E_NOTIMPL; } @@ -130,22 +156,26 @@ HRESULT CDecoder::Init_and_CheckPassword(bool &passwOK) return E_NOTIMPL; } - UInt32 rdSize = GetUi16(p + 8); + UInt32 rdSize = GetUi16a(p + 8); if (rdSize + 16 > _remSize) return E_NOTIMPL; + const unsigned kPadSize = kAesPadAllign; // is equal to blockSize of cipher for rd + /* if (cert) { - // how to filter rd, if ((rdSize & 0xF) != 0) ? if ((rdSize & 0x7) != 0) return E_NOTIMPL; } else */ { - if ((rdSize & 0xF) != 0) + // PKCS7 padding + if (rdSize < kPadSize) + return E_NOTIMPL; + if (rdSize & (kPadSize - 1)) return E_NOTIMPL; } @@ -189,27 +219,36 @@ HRESULT CDecoder::Init_and_CheckPassword(bool &passwOK) UInt32 validSize = GetUi16(p2); p2 += 2; - const size_t validOffset = p2 - p; + const size_t validOffset = (size_t)(p2 - p); if ((validSize & 0xF) != 0 || validOffset + validSize != _remSize) return E_NOTIMPL; { - RINOK(SetKey(_key.MasterKey, _key.KeySize)); - RINOK(SetInitVector(_iv, 16)); - RINOK(Init()); + RINOK(_cbcDecoder->SetKey(_key.MasterKey, _key.KeySize)) + RINOK(_cbcDecoder->SetInitVector(_iv, 16)) + // SetInitVector() calls also Init() + RINOK(_cbcDecoder->Init()) // it's optional Filter(p, rdSize); + + rdSize -= kPadSize; + for (unsigned i = 0; i < kPadSize; i++) + if (p[(size_t)rdSize + i] != kPadSize) + return S_OK; // passwOK = false; } + MY_ALIGN (16) Byte fileKey[32]; + MY_ALIGN (16) NSha1::CContext sha; sha.Init(); sha.Update(_iv, _ivSize); - sha.Update(p, rdSize - 16); // we don't use last 16 bytes (PAD bytes) + sha.Update(p, rdSize); DeriveKey(sha, fileKey); - RINOK(SetKey(fileKey, _key.KeySize)); - RINOK(SetInitVector(_iv, 16)); - Init(); + RINOK(_cbcDecoder->SetKey(fileKey, _key.KeySize)) + RINOK(_cbcDecoder->SetInitVector(_iv, 16)) + // SetInitVector() calls also Init() + RINOK(_cbcDecoder->Init()) // it's optional memmove(p, p + validOffset, validSize); Filter(p, validSize); diff --git a/src/plugins/7zip/7za/cpp/7zip/Crypto/ZipStrong.h b/src/plugins/7zip/7za/cpp/7zip/Crypto/ZipStrong.h index c5809e7b..a2b5cddf 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Crypto/ZipStrong.h +++ b/src/plugins/7zip/7za/cpp/7zip/Crypto/ZipStrong.h @@ -1,9 +1,9 @@ // Crypto/ZipStrong.h -#ifndef __CRYPTO_ZIP_STRONG_H -#define __CRYPTO_ZIP_STRONG_H +#ifndef ZIP7_INC_CRYPTO_ZIP_STRONG_H +#define ZIP7_INC_CRYPTO_ZIP_STRONG_H -#include "../../Common/MyBuffer.h" +#include "../../Common/MyBuffer2.h" #include "../IPassword.h" @@ -27,30 +27,45 @@ struct CKeyInfo UInt32 KeySize; void SetPassword(const Byte *data, UInt32 size); + + void Wipe() + { + Z7_memset_0_ARRAY(MasterKey); + } }; -class CBaseCoder: - public CAesCbcDecoder, - public ICryptoSetPassword -{ -protected: + +const unsigned kAesPadAllign = AES_BLOCK_SIZE; + +Z7_CLASS_IMP_COM_2( + CDecoder + , ICompressFilter + , ICryptoSetPassword +) + CAesCbcDecoder *_cbcDecoder; + CMyComPtr _aesFilter; CKeyInfo _key; - CByteBuffer _buf; - Byte *_bufAligned; -public: - STDMETHOD(Init)(); - STDMETHOD(CryptoSetPassword)(const Byte *data, UInt32 size); -}; + CAlignedBuffer _bufAligned; -class CDecoder: public CBaseCoder -{ UInt32 _ivSize; Byte _iv[16]; UInt32 _remSize; public: - MY_UNKNOWN_IMP1(ICryptoSetPassword) HRESULT ReadHeader(ISequentialInStream *inStream, UInt32 crc, UInt64 unpackSize); HRESULT Init_and_CheckPassword(bool &passwOK); + UInt32 GetPadSize(UInt32 packSize32) const + { + // Padding is to align to blockSize of cipher. + // Change it, if is not AES + return kAesPadAllign - (packSize32 & (kAesPadAllign - 1)); + } + CDecoder(); + ~CDecoder() { Wipe(); } + void Wipe() + { + Z7_memset_0_ARRAY(_iv); + _key.Wipe(); + } }; }} diff --git a/src/plugins/7zip/7za/cpp/7zip/GuiCommon.rc b/src/plugins/7zip/7za/cpp/7zip/GuiCommon.rc index 6b26ddd8..bf8ad8bc 100644 --- a/src/plugins/7zip/7za/cpp/7zip/GuiCommon.rc +++ b/src/plugins/7zip/7za/cpp/7zip/GuiCommon.rc @@ -54,28 +54,66 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #define MY_MODAL_DIALOG_STYLE STYLE DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU - #define MY_MODAL_RESIZE_DIALOG_STYLE MY_MODAL_DIALOG_STYLE | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SIZEBOX | WS_THICKFRAME #define MY_PAGE_STYLE STYLE WS_CHILD | WS_DISABLED | WS_CAPTION #define MY_FONT FONT 8, "MS Shell Dlg" +#define MY_MODAL_DIALOG_POSTFIX 0, 0, xs, ys MY_MODAL_DIALOG_STYLE MY_FONT + #define SMALL_PAGE_SIZE_X 120 // #define MY_DIALOG DIALOG 0, 0, xs, ys MY_MODAL_DIALOG_STYLE MY_FONT // #define MY_RESIZE_DIALOG DIALOG 0, 0, xs, ys MY_MODAL_RESIZE_DIALOG_STYLE MY_FONT -#define MY_PAGE DIALOG 0, 0, xs, ys MY_PAGE_STYLE MY_FONT +#define MY_PAGE_POSTFIX 0, 0, xs, ys MY_PAGE_STYLE MY_FONT +#define MY_PAGE DIALOG MY_PAGE_POSTFIX #define OK_CANCEL \ DEFPUSHBUTTON "OK", IDOK, bx2, by, bxs, bys \ PUSHBUTTON "Cancel", IDCANCEL, bx1, by, bxs, bys +#define CONTINUE_CANCEL \ + DEFPUSHBUTTON "Continue",IDCONTINUE, bx2, by, bxs, bys \ + PUSHBUTTON "Cancel", IDCANCEL, bx1, by, bxs, bys + +#define MY_BUTTON__CLOSE \ + DEFPUSHBUTTON "&Close", IDCLOSE, bx1, by, bxs, bys + #define MY_COMBO CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP #define MY_COMBO_SORTED MY_COMBO | CBS_SORT -#define MY_COMBO_WITH_EDIT CBS_DROPDOWN | CBS_AUTOHSCROLL | WS_VSCROLL | WS_TABSTOP +#define MY_COMBO_WITH_EDIT CBS_DROPDOWN | CBS_AUTOHSCROLL | WS_VSCROLL | WS_TABSTOP #define MY_CHECKBOX "Button", BS_AUTOCHECKBOX | WS_TABSTOP +#define cboxColonSize 18 +#define colonString ":" + +#define MY_CONTROL_CHECKBOX(_text,_id,_x,_y,_xsize) CONTROL _text, _id, MY_CHECKBOX, _x,_y,_xsize,10 +#define MY_CONTROL_CHECKBOX_2LINES(_text,_id,_x,_y,_xsize) CONTROL _text, _id, MY_CHECKBOX | BS_MULTILINE, _x,_y,_xsize,16 +#define MY_CONTROL_CHECKBOX_COLON(_id,_x,_y) MY_CONTROL_CHECKBOX( colonString, _id, _x,_y,cboxColonSize) + +#define MY_AUTORADIOBUTTON "Button", BS_AUTORADIOBUTTON +#define MY_AUTORADIOBUTTON_GROUP MY_AUTORADIOBUTTON | WS_GROUP + +#define MY_CONTROL_AUTORADIOBUTTON(_text,_id,_x,_y,_xsize) CONTROL _text, _id, MY_AUTORADIOBUTTON, _x,_y,_xsize,10 +#define MY_CONTROL_AUTORADIOBUTTON_GROUP(_text,_id,_x,_y,_xsize) CONTROL _text, _id, MY_AUTORADIOBUTTON_GROUP, _x,_y,_xsize,10 + #define MY_TEXT_NOPREFIX 8, SS_NOPREFIX + + +// WS_EX_CLIENTEDGE +#define MY_CONTROL_EDIT_WITH_SPIN(_id_edit, _id_spin, _text, _x, _y, _xSize) \ + EDITTEXT _id_edit, _x, _y, _xSize, 12, ES_CENTER | ES_NUMBER | ES_AUTOHSCROLL \ + CONTROL _text, _id_spin, L"msctls_updown32", \ + UDS_SETBUDDYINT \ + | UDS_ALIGNRIGHT \ + | UDS_AUTOBUDDY \ + | UDS_ARROWKEYS \ + | UDS_NOTHOUSANDS, \ + _x + _xSize, _y, 8, 12 // these values are unused + + +#define OPTIONS_PAGE_XC_SIZE 300 +#define OPTIONS_PAGE_YC_SIZE 280 diff --git a/src/plugins/7zip/7za/cpp/7zip/Guid.txt b/src/plugins/7zip/7za/cpp/7zip/Guid.txt index 7edab6ea..fbae89c5 100644 --- a/src/plugins/7zip/7za/cpp/7zip/Guid.txt +++ b/src/plugins/7zip/7za/cpp/7zip/Guid.txt @@ -19,9 +19,13 @@ 0F IOutFolderArchive 10 IFolderArchiveUpdateCallback2 11 IFolderScanProgress + 12 IFolderSetZoneIdMode + 13 IFolderSetZoneIdFile + 14 IFolderArchiveUpdateCallback_MoveArc 20 IFileExtractCallback.h::IGetProp - 30 IFileExtractCallback.h::IFolderExtractToStreamCallback + 30 IFileExtractCallback.h::IFolderExtractToStreamCallback (old) + 31 IFileExtractCallback.h::IFolderExtractToStreamCallback (new 21.04) 03 IStream.h @@ -29,10 +33,14 @@ 02 ISequentialOutStream 03 IInStream 04 IOutStream + 06 IStreamGetSize 07 IOutStreamFinish 08 IStreamGetProps 09 IStreamGetProps2 + 0A IStreamGetProp + + 10 IStreamSetRestriction 04 ICoder.h @@ -40,6 +48,7 @@ 04 ICompressProgressInfo 05 ICompressCoder 18 ICompressCoder2 + 1F ICompressSetCoderPropertiesOpt 20 ICompressSetCoderProperties 21 ICompressSetDecoderProperties // 22 ICompressSetDecoderProperties2 @@ -47,6 +56,9 @@ 24 ICompressGetInStreamProcessedSize 25 ICompressSetCoderMt 26 ICompressSetFinishMode + 27 ICompressGetInStreamProcessedSize2 + 28 ICompressSetMemLimit + 29 ICompressReadUnusedFromInBuf 30 ICompressGetSubStreamSize 31 ICompressSetInStream @@ -84,10 +96,13 @@ 04 IArchiveKeepModeForNextOpen 05 IArchiveAllowTail + 09 IArchiveRequestMemoryUseCallback + 10 IArchiveOpenCallback 20 IArchiveExtractCallback - 21 IArchiveExtractCallbackMessage + 21 IArchiveExtractCallbackMessage (deprecated in v23) + 22 IArchiveExtractCallbackMessage2 (new in v23) 30 IArchiveOpenVolumeCallback 40 IInArchiveGetStream @@ -100,6 +115,9 @@ 80 IArchiveUpdateCallback 82 IArchiveUpdateCallback2 83 IArchiveUpdateCallbackFile + 84 IArchiveGetDiskProperty + 85 IArchiveUpdateCallbackArcProp (Reserved) + A0 IOutArchive @@ -162,7 +180,16 @@ Handler GUIDs: 0B lzma86 0C xz 0D ppmd - + 0E zstd + + BF LVM + C0 AVB + C1 LP + C2 Sparse + C3 APFS + C4 Vhdx + C5 Base64 + C6 COFF C7 Ext C8 VMDK C9 VDI diff --git a/src/plugins/7zip/7za/cpp/7zip/ICoder.h b/src/plugins/7zip/7za/cpp/7zip/ICoder.h index 75db9399..20d0ff72 100644 --- a/src/plugins/7zip/7za/cpp/7zip/ICoder.h +++ b/src/plugins/7zip/7za/cpp/7zip/ICoder.h @@ -1,39 +1,40 @@ // ICoder.h -#ifndef __ICODER_H -#define __ICODER_H +#ifndef ZIP7_INC_ICODER_H +#define ZIP7_INC_ICODER_H #include "IStream.h" -#define CODER_INTERFACE(i, x) DECL_INTERFACE(i, 4, x) +Z7_PURE_INTERFACES_BEGIN -CODER_INTERFACE(ICompressProgressInfo, 0x04) -{ - STDMETHOD(SetRatioInfo)(const UInt64 *inSize, const UInt64 *outSize) PURE; - - /* (inSize) can be NULL, if unknown - (outSize) can be NULL, if unknown +#define Z7_IFACE_CONSTR_CODER(i, n) \ + Z7_DECL_IFACE_7ZIP(i, 4, n) \ + { Z7_IFACE_COM7_PURE(i) }; +#define Z7_IFACEM_ICompressProgressInfo(x) \ + x(SetRatioInfo(const UInt64 *inSize, const UInt64 *outSize)) +Z7_IFACE_CONSTR_CODER(ICompressProgressInfo, 0x04) + /* + SetRatioInfo() + (inSize) can be NULL, if unknown + (outSize) can be NULL, if unknown returns: S_OK E_ABORT : Break by user another error codes */ -}; -CODER_INTERFACE(ICompressCoder, 0x05) -{ - STDMETHOD(Code)(ISequentialInStream *inStream, ISequentialOutStream *outStream, - const UInt64 *inSize, const UInt64 *outSize, - ICompressProgressInfo *progress) PURE; -}; +#define Z7_IFACEM_ICompressCoder(x) \ + x(Code(ISequentialInStream *inStream, ISequentialOutStream *outStream, \ + const UInt64 *inSize, const UInt64 *outSize, \ + ICompressProgressInfo *progress)) +Z7_IFACE_CONSTR_CODER(ICompressCoder, 0x05) -CODER_INTERFACE(ICompressCoder2, 0x18) -{ - STDMETHOD(Code)(ISequentialInStream * const *inStreams, const UInt64 * const *inSizes, UInt32 numInStreams, - ISequentialOutStream * const *outStreams, const UInt64 * const *outSizes, UInt32 numOutStreams, - ICompressProgressInfo *progress) PURE; -}; +#define Z7_IFACEM_ICompressCoder2(x) \ + x(Code(ISequentialInStream * const *inStreams, const UInt64 *const *inSizes, UInt32 numInStreams, \ + ISequentialOutStream *const *outStreams, const UInt64 *const *outSizes, UInt32 numOutStreams, \ + ICompressProgressInfo *progress)) +Z7_IFACE_CONSTR_CODER(ICompressCoder2, 0x18) /* ICompressCoder::Code @@ -43,6 +44,7 @@ CODER_INTERFACE(ICompressCoder2, 0x18) S_OK : OK S_FALSE : data error (for decoders) E_OUTOFMEMORY : memory allocation error + E_NOTIMPL : unsupported encoding method (for decoders) another error code : some error. For example, it can be error code received from inStream or outStream function. Parameters: @@ -53,7 +55,7 @@ CODER_INTERFACE(ICompressCoder2, 0x18) { Encoders in 7-Zip ignore (inSize). Decoder can use (*inSize) to check that stream was decoded correctly. - Some decoder in 7-Zip check it, if (full_decoding mode was set via ICompressSetFinishMode) + Some decoders in 7-Zip check it, if (full_decoding mode was set via ICompressSetFinishMode) } If it's required to limit the reading from input stream (inStream), it can @@ -104,82 +106,144 @@ namespace NCoderPropID enum EEnum { kDefaultProp = 0, - kDictionarySize, - kUsedMemorySize, - kOrder, - kBlockSize, - kPosStateBits, - kLitContextBits, - kLitPosBits, - kNumFastBytes, - kMatchFinder, - kMatchFinderCycles, - kNumPasses, - kAlgorithm, - kNumThreads, - kEndMarker, - kLevel, - kReduceSize // estimated size of data that will be compressed. Encoder can use this value to reduce dictionary size. + kDictionarySize, // VT_UI4 + kUsedMemorySize, // VT_UI4 + kOrder, // VT_UI4 + kBlockSize, // VT_UI4 or VT_UI8 + kPosStateBits, // VT_UI4 + kLitContextBits, // VT_UI4 + kLitPosBits, // VT_UI4 + kNumFastBytes, // VT_UI4 + kMatchFinder, // VT_BSTR + kMatchFinderCycles, // VT_UI4 + kNumPasses, // VT_UI4 + kAlgorithm, // VT_UI4 + kNumThreads, // VT_UI4 + kEndMarker, // VT_BOOL + kLevel, // VT_UI4 + kReduceSize, // VT_UI8 : it's estimated size of largest data stream that will be compressed + // encoder can use this value to reduce dictionary size and allocate data buffers + + kExpectedDataSize, // VT_UI8 : for ICompressSetCoderPropertiesOpt : + // it's estimated size of current data stream + // real data size can differ from that size + // encoder can use this value to optimize encoder initialization + + kBlockSize2, // VT_UI4 or VT_UI8 + kCheckSize, // VT_UI4 : size of digest in bytes + kFilter, // VT_BSTR + kMemUse, // VT_UI8 + kAffinity, // VT_UI8 + kBranchOffset, // VT_UI4 + kHashBits, // VT_UI4 + kNumThreadGroups, // VT_UI4 + kThreadGroup, // VT_UI4 + kAffinityInGroup, // VT_UI8 + /* + // kHash3Bits, // VT_UI4 + // kHash2Bits, // VT_UI4 + // kChainBits, // VT_UI4 + kChainSize, // VT_UI4 + kNativeLevel, // VT_UI4 + kFast, // VT_UI4 + kMinMatch, // VT_UI4 The minimum slen is 3 and the maximum is 7. + kOverlapLog, // VT_UI4 The minimum ovlog is 0 and the maximum is 9. (default: 6) + kRowMatchFinder, // VT_BOOL + kLdmEnable, // VT_BOOL + // kLdmWindowSizeLog, // VT_UI4 + kLdmWindowSize, // VT_UI4 + kLdmHashLog, // VT_UI4 The minimum ldmhlog is 6 and the maximum is 26 (default: 20). + kLdmMinMatchLength, // VT_UI4 The minimum ldmslen is 4 and the maximum is 4096 (default: 64). + kLdmBucketSizeLog, // VT_UI4 The minimum ldmblog is 0 and the maximum is 8 (default: 3). + kLdmHashRateLog, // VT_UI4 The default value is wlog - ldmhlog. + kWriteUnpackSizeFlag, // VT_BOOL + kUsePledged, // VT_BOOL + kUseSizeHintPledgedForSmall, // VT_BOOL + kUseSizeHintForEach, // VT_BOOL + kUseSizeHintGlobal, // VT_BOOL + kParamSelectMode, // VT_UI4 + // kSearchLog, // VT_UI4 The minimum slog is 1 and the maximum is 26 + // kTargetLen, // VT_UI4 The minimum tlen is 0 and the maximum is 999. + */ + k_NUM_DEFINED }; } -CODER_INTERFACE(ICompressSetCoderProperties, 0x20) -{ - STDMETHOD(SetCoderProperties)(const PROPID *propIDs, const PROPVARIANT *props, UInt32 numProps) PURE; -}; +#define Z7_IFACEM_ICompressSetCoderPropertiesOpt(x) \ + x(SetCoderPropertiesOpt(const PROPID *propIDs, const PROPVARIANT *props, UInt32 numProps)) +Z7_IFACE_CONSTR_CODER(ICompressSetCoderPropertiesOpt, 0x1F) + + +#define Z7_IFACEM_ICompressSetCoderProperties(x) \ + x(SetCoderProperties(const PROPID *propIDs, const PROPVARIANT *props, UInt32 numProps)) +Z7_IFACE_CONSTR_CODER(ICompressSetCoderProperties, 0x20) /* -CODER_INTERFACE(ICompressSetCoderProperties, 0x21) -{ - STDMETHOD(SetDecoderProperties)(ISequentialInStream *inStream) PURE; -}; +#define Z7_IFACEM_ICompressSetDecoderProperties(x) \ + x(SetDecoderProperties(ISequentialInStream *inStream)) +Z7_IFACE_CONSTR_CODER(ICompressSetDecoderProperties, 0x21) */ -CODER_INTERFACE(ICompressSetDecoderProperties2, 0x22) -{ +#define Z7_IFACEM_ICompressSetDecoderProperties2(x) \ + x(SetDecoderProperties2(const Byte *data, UInt32 size)) +Z7_IFACE_CONSTR_CODER(ICompressSetDecoderProperties2, 0x22) /* returns: S_OK E_NOTIMP : unsupported properties E_INVALIDARG : incorrect (or unsupported) properties E_OUTOFMEMORY : memory allocation error */ - STDMETHOD(SetDecoderProperties2)(const Byte *data, UInt32 size) PURE; -}; -CODER_INTERFACE(ICompressWriteCoderProperties, 0x23) -{ - STDMETHOD(WriteCoderProperties)(ISequentialOutStream *outStream) PURE; -}; -CODER_INTERFACE(ICompressGetInStreamProcessedSize, 0x24) -{ - STDMETHOD(GetInStreamProcessedSize)(UInt64 *value) PURE; -}; +#define Z7_IFACEM_ICompressWriteCoderProperties(x) \ + x(WriteCoderProperties(ISequentialOutStream *outStream)) +Z7_IFACE_CONSTR_CODER(ICompressWriteCoderProperties, 0x23) -CODER_INTERFACE(ICompressSetCoderMt, 0x25) -{ - STDMETHOD(SetNumberOfThreads)(UInt32 numThreads) PURE; -}; +#define Z7_IFACEM_ICompressGetInStreamProcessedSize(x) \ + x(GetInStreamProcessedSize(UInt64 *value)) +Z7_IFACE_CONSTR_CODER(ICompressGetInStreamProcessedSize, 0x24) -CODER_INTERFACE(ICompressSetFinishMode, 0x26) -{ - STDMETHOD(SetFinishMode)(UInt32 finishMode) PURE; +#define Z7_IFACEM_ICompressSetCoderMt(x) \ + x(SetNumberOfThreads(UInt32 numThreads)) +Z7_IFACE_CONSTR_CODER(ICompressSetCoderMt, 0x25) +#define Z7_IFACEM_ICompressSetFinishMode(x) \ + x(SetFinishMode(UInt32 finishMode)) +Z7_IFACE_CONSTR_CODER(ICompressSetFinishMode, 0x26) /* finishMode: 0 : partial decoding is allowed. It's default mode for ICompressCoder::Code(), if (outSize) is defined. 1 : full decoding. The stream must be finished at the end of decoding. */ -}; +#define Z7_IFACEM_ICompressGetInStreamProcessedSize2(x) \ + x(GetInStreamProcessedSize2(UInt32 streamIndex, UInt64 *value)) +Z7_IFACE_CONSTR_CODER(ICompressGetInStreamProcessedSize2, 0x27) + +#define Z7_IFACEM_ICompressSetMemLimit(x) \ + x(SetMemLimit(UInt64 memUsage)) +Z7_IFACE_CONSTR_CODER(ICompressSetMemLimit, 0x28) -CODER_INTERFACE(ICompressGetSubStreamSize, 0x30) -{ - STDMETHOD(GetSubStreamSize)(UInt64 subStream, UInt64 *value) PURE; +/* + ICompressReadUnusedFromInBuf is supported by ICoder object + call ReadUnusedFromInBuf() after ICoder::Code(inStream, ...). + ICoder::Code(inStream, ...) decodes data, and the ICoder object is allowed + to read from inStream to internal buffers more data than minimal data required for decoding. + So we can call ReadUnusedFromInBuf() from same ICoder object to read unused input + data from the internal buffer. + in ReadUnusedFromInBuf(): the Coder is not allowed to use (ISequentialInStream *inStream) object, that was sent to ICoder::Code(). +*/ +#define Z7_IFACEM_ICompressReadUnusedFromInBuf(x) \ + x(ReadUnusedFromInBuf(void *data, UInt32 size, UInt32 *processedSize)) +Z7_IFACE_CONSTR_CODER(ICompressReadUnusedFromInBuf, 0x29) + + +#define Z7_IFACEM_ICompressGetSubStreamSize(x) \ + x(GetSubStreamSize(UInt64 subStream, UInt64 *value)) +Z7_IFACE_CONSTR_CODER(ICompressGetSubStreamSize, 0x30) /* returns: S_OK : (*value) contains the size or estimated size (can be incorrect size) S_FALSE : size is undefined E_NOTIMP : the feature is not implemented - Let's (read_size) is size of data that was already read by ISequentialInStream::Read(). The caller should call GetSubStreamSize() after each Read() and check sizes: if (start_of_subStream + *value < read_size) @@ -189,135 +253,153 @@ CODER_INTERFACE(ICompressGetSubStreamSize, 0x30) subStream++; } */ -}; -CODER_INTERFACE(ICompressSetInStream, 0x31) -{ - STDMETHOD(SetInStream)(ISequentialInStream *inStream) PURE; - STDMETHOD(ReleaseInStream)() PURE; -}; +#define Z7_IFACEM_ICompressSetInStream(x) \ + x(SetInStream(ISequentialInStream *inStream)) \ + x(ReleaseInStream()) +Z7_IFACE_CONSTR_CODER(ICompressSetInStream, 0x31) -CODER_INTERFACE(ICompressSetOutStream, 0x32) -{ - STDMETHOD(SetOutStream)(ISequentialOutStream *outStream) PURE; - STDMETHOD(ReleaseOutStream)() PURE; -}; +#define Z7_IFACEM_ICompressSetOutStream(x) \ + x(SetOutStream(ISequentialOutStream *outStream)) \ + x(ReleaseOutStream()) +Z7_IFACE_CONSTR_CODER(ICompressSetOutStream, 0x32) /* -CODER_INTERFACE(ICompressSetInStreamSize, 0x33) -{ - STDMETHOD(SetInStreamSize)(const UInt64 *inSize) PURE; -}; +#define Z7_IFACEM_ICompressSetInStreamSize(x) \ + x(SetInStreamSize(const UInt64 *inSize)) \ +Z7_IFACE_CONSTR_CODER(ICompressSetInStreamSize, 0x33) */ -CODER_INTERFACE(ICompressSetOutStreamSize, 0x34) -{ - STDMETHOD(SetOutStreamSize)(const UInt64 *outSize) PURE; - +#define Z7_IFACEM_ICompressSetOutStreamSize(x) \ + x(SetOutStreamSize(const UInt64 *outSize)) +Z7_IFACE_CONSTR_CODER(ICompressSetOutStreamSize, 0x34) /* That function initializes decoder structures. Call this function only for stream version of decoder. if (outSize == NULL), then output size is unknown if (outSize != NULL), then the decoder must stop decoding after (*outSize) bytes. */ -}; - -CODER_INTERFACE(ICompressSetBufSize, 0x35) -{ - STDMETHOD(SetInBufSize)(UInt32 streamIndex, UInt32 size) PURE; - STDMETHOD(SetOutBufSize)(UInt32 streamIndex, UInt32 size) PURE; -}; -CODER_INTERFACE(ICompressInitEncoder, 0x36) -{ - STDMETHOD(InitEncoder)() PURE; +#define Z7_IFACEM_ICompressSetBufSize(x) \ + x(SetInBufSize(UInt32 streamIndex, UInt32 size)) \ + x(SetOutBufSize(UInt32 streamIndex, UInt32 size)) + +Z7_IFACE_CONSTR_CODER(ICompressSetBufSize, 0x35) +#define Z7_IFACEM_ICompressInitEncoder(x) \ + x(InitEncoder()) +Z7_IFACE_CONSTR_CODER(ICompressInitEncoder, 0x36) /* That function initializes encoder structures. Call this function only for stream version of encoder. */ -}; -CODER_INTERFACE(ICompressSetInStream2, 0x37) -{ - STDMETHOD(SetInStream2)(UInt32 streamIndex, ISequentialInStream *inStream) PURE; - STDMETHOD(ReleaseInStream2)(UInt32 streamIndex) PURE; -}; +#define Z7_IFACEM_ICompressSetInStream2(x) \ + x(SetInStream2(UInt32 streamIndex, ISequentialInStream *inStream)) \ + x(ReleaseInStream2(UInt32 streamIndex)) +Z7_IFACE_CONSTR_CODER(ICompressSetInStream2, 0x37) /* -CODER_INTERFACE(ICompressSetOutStream2, 0x38) -{ - STDMETHOD(SetOutStream2)(UInt32 streamIndex, ISequentialOutStream *outStream) PURE; - STDMETHOD(ReleaseOutStream2)(UInt32 streamIndex) PURE; -}; - -CODER_INTERFACE(ICompressSetInStreamSize2, 0x39) -{ - STDMETHOD(SetInStreamSize2)(UInt32 streamIndex, const UInt64 *inSize) PURE; -}; +#define Z7_IFACEM_ICompressSetOutStream2(x) \ + x(SetOutStream2(UInt32 streamIndex, ISequentialOutStream *outStream)) + x(ReleaseOutStream2(UInt32 streamIndex)) +Z7_IFACE_CONSTR_CODER(ICompressSetOutStream2, 0x38) + +#define Z7_IFACEM_ICompressSetInStreamSize2(x) \ + x(SetInStreamSize2(UInt32 streamIndex, const UInt64 *inSize)) +Z7_IFACE_CONSTR_CODER(ICompressSetInStreamSize2, 0x39) */ +/* +#define Z7_IFACEM_ICompressInSubStreams(x) \ + x(GetNextInSubStream(UInt64 *streamIndexRes, ISequentialInStream **stream)) +Z7_IFACE_CONSTR_CODER(ICompressInSubStreams, 0x3A) + +#define Z7_IFACEM_ICompressOutSubStreams(x) \ + x(GetNextOutSubStream(UInt64 *streamIndexRes, ISequentialOutStream **stream)) +Z7_IFACE_CONSTR_CODER(ICompressOutSubStreams, 0x3B) +*/ /* ICompressFilter - Filter() converts as most as possible bytes - returns: (outSize): + Filter(Byte *data, UInt32 size) + (size) + converts as most as possible bytes required for fast processing. + Some filters have (smallest_fast_block). + For example, (smallest_fast_block == 16) for AES CBC/CTR filters. + If data stream is not finished, caller must call Filter() for larger block: + where (size >= smallest_fast_block). + if (size >= smallest_fast_block) + { + The filter can leave some bytes at the end of data without conversion: + if there are data alignment reasons or speed reasons. + The caller can read additional data from stream and call Filter() again. + } + If data stream was finished, caller can call Filter() for (size < smallest_fast_block) + + (data) parameter: + Some filters require alignment for any Filter() call: + 1) (stream_offset % alignment_size) == (data % alignment_size) + 2) (alignment_size == 2^N) + where (stream_offset) - is the number of bytes that were already filtered before. + The callers of Filter() are required to meet these requirements. + (alignment_size) can be different: + 16 : for AES filters + 4 or 2 : for some branch convert filters + 1 : for another filters + (alignment_size >= 16) is enough for all current filters of 7-Zip. + But the caller can use larger (alignment_size). + Recommended alignment for (data) of Filter() call is (alignment_size == 64). + Also it's recommended to use aligned value for (size): + (size % alignment_size == 0), + if it's not last call of Filter() for current stream. + + returns: (outSize): + if (outSize == 0) : Filter have not converted anything. + So the caller can stop processing, if data stream was finished. if (outSize <= size) : Filter have converted outSize bytes if (outSize > size) : Filter have not converted anything. and it needs at least outSize bytes to convert one block (it's for crypto block algorithms). */ -#define INTERFACE_ICompressFilter(x) \ - STDMETHOD(Init)() x; \ - STDMETHOD_(UInt32, Filter)(Byte *data, UInt32 size) x; \ +#define Z7_IFACEM_ICompressFilter(x) \ + x(Init()) \ + x##2(UInt32, Filter(Byte *data, UInt32 size)) +Z7_IFACE_CONSTR_CODER(ICompressFilter, 0x40) -CODER_INTERFACE(ICompressFilter, 0x40) -{ - INTERFACE_ICompressFilter(PURE); -}; +#define Z7_IFACEM_ICompressCodecsInfo(x) \ + x(GetNumMethods(UInt32 *numMethods)) \ + x(GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) \ + x(CreateDecoder(UInt32 index, const GUID *iid, void* *coder)) \ + x(CreateEncoder(UInt32 index, const GUID *iid, void* *coder)) +Z7_IFACE_CONSTR_CODER(ICompressCodecsInfo, 0x60) -CODER_INTERFACE(ICompressCodecsInfo, 0x60) -{ - STDMETHOD(GetNumMethods)(UInt32 *numMethods) PURE; - STDMETHOD(GetProperty)(UInt32 index, PROPID propID, PROPVARIANT *value) PURE; - STDMETHOD(CreateDecoder)(UInt32 index, const GUID *iid, void **coder) PURE; - STDMETHOD(CreateEncoder)(UInt32 index, const GUID *iid, void **coder) PURE; -}; +#define Z7_IFACEM_ISetCompressCodecsInfo(x) \ + x(SetCompressCodecsInfo(ICompressCodecsInfo *compressCodecsInfo)) +Z7_IFACE_CONSTR_CODER(ISetCompressCodecsInfo, 0x61) -CODER_INTERFACE(ISetCompressCodecsInfo, 0x61) -{ - STDMETHOD(SetCompressCodecsInfo)(ICompressCodecsInfo *compressCodecsInfo) PURE; -}; - -CODER_INTERFACE(ICryptoProperties, 0x80) -{ - STDMETHOD(SetKey)(const Byte *data, UInt32 size) PURE; - STDMETHOD(SetInitVector)(const Byte *data, UInt32 size) PURE; -}; +#define Z7_IFACEM_ICryptoProperties(x) \ + x(SetKey(const Byte *data, UInt32 size)) \ + x(SetInitVector(const Byte *data, UInt32 size)) +Z7_IFACE_CONSTR_CODER(ICryptoProperties, 0x80) /* -CODER_INTERFACE(ICryptoResetSalt, 0x88) -{ - STDMETHOD(ResetSalt)() PURE; -}; + x(ResetSalt()) +Z7_IFACE_CONSTR_CODER(ICryptoResetSalt, 0x88) */ -CODER_INTERFACE(ICryptoResetInitVector, 0x8C) -{ - STDMETHOD(ResetInitVector)() PURE; - +#define Z7_IFACEM_ICryptoResetInitVector(x) \ + x(ResetInitVector()) +Z7_IFACE_CONSTR_CODER(ICryptoResetInitVector, 0x8C) /* Call ResetInitVector() only for encoding. Call ResetInitVector() before encoding and before WriteCoderProperties(). Crypto encoder can create random IV in that function. */ -}; -CODER_INTERFACE(ICryptoSetPassword, 0x90) -{ - STDMETHOD(CryptoSetPassword)(const Byte *data, UInt32 size) PURE; -}; +#define Z7_IFACEM_ICryptoSetPassword(x) \ + x(CryptoSetPassword(const Byte *data, UInt32 size)) +Z7_IFACE_CONSTR_CODER(ICryptoSetPassword, 0x90) -CODER_INTERFACE(ICryptoSetCRC, 0xA0) -{ - STDMETHOD(CryptoSetCRC)(UInt32 crc) PURE; -}; +#define Z7_IFACEM_ICryptoSetCRC(x) \ + x(CryptoSetCRC(UInt32 crc)) +Z7_IFACE_CONSTR_CODER(ICryptoSetCRC, 0xA0) namespace NMethodPropID @@ -333,28 +415,53 @@ namespace NMethodPropID kDescription, kDecoderIsAssigned, kEncoderIsAssigned, - kDigestSize + kDigestSize, + kIsFilter }; } - -#define INTERFACE_IHasher(x) \ - STDMETHOD_(void, Init)() throw() x; \ - STDMETHOD_(void, Update)(const void *data, UInt32 size) throw() x; \ - STDMETHOD_(void, Final)(Byte *digest) throw() x; \ - STDMETHOD_(UInt32, GetDigestSize)() throw() x; \ - -CODER_INTERFACE(IHasher, 0xC0) +namespace NModuleInterfaceType { - INTERFACE_IHasher(PURE) -}; + /* + virtual destructor in IUnknown: + - no : 7-Zip (Windows) + - no : 7-Zip (Linux) (v23) in default mode + - yes : p7zip + - yes : 7-Zip (Linux) before v23 + - yes : 7-Zip (Linux) (v23), if Z7_USE_VIRTUAL_DESTRUCTOR_IN_IUNKNOWN is defined + */ + const UInt32 k_IUnknown_VirtDestructor_No = 0; + const UInt32 k_IUnknown_VirtDestructor_Yes = 1; + const UInt32 k_IUnknown_VirtDestructor_ThisModule = + #if !defined(_WIN32) && defined(Z7_USE_VIRTUAL_DESTRUCTOR_IN_IUNKNOWN) + k_IUnknown_VirtDestructor_Yes; + #else + k_IUnknown_VirtDestructor_No; + #endif +} -CODER_INTERFACE(IHashers, 0xC1) +namespace NModulePropID { - STDMETHOD_(UInt32, GetNumHashers)() PURE; - STDMETHOD(GetHasherProp)(UInt32 index, PROPID propID, PROPVARIANT *value) PURE; - STDMETHOD(CreateHasher)(UInt32 index, IHasher **hasher) PURE; -}; + enum EEnum + { + kInterfaceType, // VT_UI4 + kVersion // VT_UI4 + }; +} + + +#define Z7_IFACEM_IHasher(x) \ + x##2(void, Init()) \ + x##2(void, Update(const void *data, UInt32 size)) \ + x##2(void, Final(Byte *digest)) \ + x##2(UInt32, GetDigestSize()) +Z7_IFACE_CONSTR_CODER(IHasher, 0xC0) + +#define Z7_IFACEM_IHashers(x) \ + x##2(UInt32, GetNumHashers()) \ + x(GetHasherProp(UInt32 index, PROPID propID, PROPVARIANT *value)) \ + x(CreateHasher(UInt32 index, IHasher **hasher)) +Z7_IFACE_CONSTR_CODER(IHashers, 0xC1) extern "C" { @@ -366,6 +473,8 @@ extern "C" typedef HRESULT (WINAPI *Func_GetHashers)(IHashers **hashers); typedef HRESULT (WINAPI *Func_SetCodecs)(ICompressCodecsInfo *compressCodecsInfo); + typedef HRESULT (WINAPI *Func_GetModuleProp)(PROPID propID, PROPVARIANT *value); } +Z7_PURE_INTERFACES_END #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/IDecl.h b/src/plugins/7zip/7za/cpp/7zip/IDecl.h index 077ef0ee..4dbf1eba 100644 --- a/src/plugins/7zip/7za/cpp/7zip/IDecl.h +++ b/src/plugins/7zip/7za/cpp/7zip/IDecl.h @@ -1,8 +1,9 @@ // IDecl.h -#ifndef __IDECL_H -#define __IDECL_H +#ifndef ZIP7_INC_IDECL_H +#define ZIP7_INC_IDECL_H +#include "../Common/Common0.h" #include "../Common/MyUnknown.h" #define k_7zip_GUID_Data1 0x23170F69 @@ -14,15 +15,62 @@ #define k_7zip_GUID_Data3_Encoder 0x2791 #define k_7zip_GUID_Data3_Hasher 0x2792 - -#define DECL_INTERFACE_SUB(i, base, groupId, subId) \ - DEFINE_GUID(IID_ ## i, \ +#define Z7_DECL_IFACE_7ZIP_SUB(i, _base, groupId, subId) \ + Z7_DEFINE_GUID(IID_ ## i, \ k_7zip_GUID_Data1, \ k_7zip_GUID_Data2, \ k_7zip_GUID_Data3_Common, \ 0, 0, 0, (groupId), 0, (subId), 0, 0); \ - struct i: public base + struct Z7_DECLSPEC_NOVTABLE i: public _base + +#define Z7_DECL_IFACE_7ZIP(i, groupId, subId) \ + Z7_DECL_IFACE_7ZIP_SUB(i, IUnknown, groupId, subId) + + +#ifdef COM_DECLSPEC_NOTHROW +#define Z7_COMWF_B COM_DECLSPEC_NOTHROW STDMETHODIMP +#define Z7_COMWF_B_(t) COM_DECLSPEC_NOTHROW STDMETHODIMP_(t) +#else +#define Z7_COMWF_B STDMETHODIMP +#define Z7_COMWF_B_(t) STDMETHODIMP_(t) +#endif + +#if defined(_MSC_VER) && !defined(COM_DECLSPEC_NOTHROW) +#define Z7_COM7F_B __declspec(nothrow) STDMETHODIMP +#define Z7_COM7F_B_(t) __declspec(nothrow) STDMETHODIMP_(t) +#else +#define Z7_COM7F_B Z7_COMWF_B +#define Z7_COM7F_B_(t) Z7_COMWF_B_(t) +#endif + +// #define Z7_COM7F_E Z7_noexcept +#define Z7_COM7F_E throw() +#define Z7_COM7F_EO Z7_COM7F_E Z7_override +#define Z7_COM7F_EOF Z7_COM7F_EO Z7_final +#define Z7_COM7F_IMF(f) Z7_COM7F_B f Z7_COM7F_E +#define Z7_COM7F_IMF2(t, f) Z7_COM7F_B_(t) f Z7_COM7F_E + +#define Z7_COM7F_PURE(f) virtual Z7_COM7F_IMF(f) =0; +#define Z7_COM7F_PURE2(t, f) virtual Z7_COM7F_IMF2(t, f) =0; +#define Z7_COM7F_IMP(f) Z7_COM7F_IMF(f) Z7_override Z7_final; +#define Z7_COM7F_IMP2(t, f) Z7_COM7F_IMF2(t, f) Z7_override Z7_final; +#define Z7_COM7F_IMP_NONFINAL(f) Z7_COM7F_IMF(f) Z7_override; +#define Z7_COM7F_IMP_NONFINAL2(t, f) Z7_COM7F_IMF2(t, f) Z7_override; + +#define Z7_IFACE_PURE(name) Z7_IFACEN_ ## name(=0;) +#define Z7_IFACE_IMP(name) Z7_IFACEN_ ## name(Z7_override Z7_final;) + +#define Z7_IFACE_COM7_PURE(name) Z7_IFACEM_ ## name(Z7_COM7F_PURE) +#define Z7_IFACE_COM7_IMP(name) Z7_IFACEM_ ## name(Z7_COM7F_IMP) +#define Z7_IFACE_COM7_IMP_NONFINAL(name) Z7_IFACEM_ ## name(Z7_COM7F_IMP_NONFINAL) + + +#define Z7_IFACE_DECL_PURE(name) \ + DECLARE_INTERFACE(name) \ + { Z7_IFACE_PURE(name) }; -#define DECL_INTERFACE(i, groupId, subId) DECL_INTERFACE_SUB(i, IUnknown, groupId, subId) +#define Z7_IFACE_DECL_PURE_(name, baseiface) \ + DECLARE_INTERFACE_(name, baseiface) \ + { Z7_IFACE_PURE(name) }; #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/IPassword.h b/src/plugins/7zip/7za/cpp/7zip/IPassword.h index 7ea45537..689f08cb 100644 --- a/src/plugins/7zip/7za/cpp/7zip/IPassword.h +++ b/src/plugins/7zip/7za/cpp/7zip/IPassword.h @@ -1,23 +1,54 @@ // IPassword.h -#ifndef __IPASSWORD_H -#define __IPASSWORD_H +#ifndef ZIP7_INC_IPASSWORD_H +#define ZIP7_INC_IPASSWORD_H #include "../Common/MyTypes.h" -#include "../Common/MyUnknown.h" #include "IDecl.h" -#define PASSWORD_INTERFACE(i, x) DECL_INTERFACE(i, 5, x) +Z7_PURE_INTERFACES_BEGIN -PASSWORD_INTERFACE(ICryptoGetTextPassword, 0x10) -{ - STDMETHOD(CryptoGetTextPassword)(BSTR *password) PURE; -}; +#define Z7_IFACE_CONSTR_PASSWORD(i, n) \ + Z7_DECL_IFACE_7ZIP(i, 5, n) \ + { Z7_IFACE_COM7_PURE(i) }; -PASSWORD_INTERFACE(ICryptoGetTextPassword2, 0x11) -{ - STDMETHOD(CryptoGetTextPassword2)(Int32 *passwordIsDefined, BSTR *password) PURE; -}; +/* +How to use output parameter (BSTR *password): +in: The caller is required to set BSTR value as NULL (no string). + The callee (in 7-Zip code) ignores the input value stored in BSTR variable, + +out: The callee rewrites BSTR variable (*password) with new allocated string pointer. + The caller must free BSTR string with function SysFreeString(); +*/ + +#define Z7_IFACEM_ICryptoGetTextPassword(x) \ + x(CryptoGetTextPassword(BSTR *password)) +Z7_IFACE_CONSTR_PASSWORD(ICryptoGetTextPassword, 0x10) + + +/* +CryptoGetTextPassword2() +in: + The caller is required to set BSTR value as NULL (no string). + The caller is not required to set (*passwordIsDefined) value. + +out: + Return code: != S_OK : error code + Return code: S_OK : success + + if (*passwordIsDefined == 1), the variable (*password) contains password string + + if (*passwordIsDefined == 0), the password is not defined, + but the callee still could set (*password) to some allocated string, for example, as empty string. + + The caller must free BSTR string with function SysFreeString() +*/ + +#define Z7_IFACEM_ICryptoGetTextPassword2(x) \ + x(CryptoGetTextPassword2(Int32 *passwordIsDefined, BSTR *password)) +Z7_IFACE_CONSTR_PASSWORD(ICryptoGetTextPassword2, 0x11) + +Z7_PURE_INTERFACES_END #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/IProgress.h b/src/plugins/7zip/7za/cpp/7zip/IProgress.h index fac951ec..67149830 100644 --- a/src/plugins/7zip/7za/cpp/7zip/IProgress.h +++ b/src/plugins/7zip/7za/cpp/7zip/IProgress.h @@ -1,19 +1,20 @@ // IProgress.h -#ifndef __IPROGRESS_H -#define __IPROGRESS_H +#ifndef ZIP7_INC_IPROGRESS_H +#define ZIP7_INC_IPROGRESS_H #include "../Common/MyTypes.h" #include "IDecl.h" -#define INTERFACE_IProgress(x) \ - STDMETHOD(SetTotal)(UInt64 total) x; \ - STDMETHOD(SetCompleted)(const UInt64 *completeValue) x; \ +Z7_PURE_INTERFACES_BEGIN -DECL_INTERFACE(IProgress, 0, 5) -{ - INTERFACE_IProgress(PURE) -}; +#define Z7_IFACEM_IProgress(x) \ + x(SetTotal(UInt64 total)) \ + x(SetCompleted(const UInt64 *completeValue)) \ +Z7_DECL_IFACE_7ZIP(IProgress, 0, 5) + { Z7_IFACE_COM7_PURE(IProgress) }; + +Z7_PURE_INTERFACES_END #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/IStream.h b/src/plugins/7zip/7za/cpp/7zip/IStream.h index 9a0bcbf3..0c44a917 100644 --- a/src/plugins/7zip/7za/cpp/7zip/IStream.h +++ b/src/plugins/7zip/7za/cpp/7zip/IStream.h @@ -1,24 +1,29 @@ // IStream.h -#ifndef __ISTREAM_H -#define __ISTREAM_H +#ifndef ZIP7_INC_ISTREAM_H +#define ZIP7_INC_ISTREAM_H +#include "../Common/Common0.h" #include "../Common/MyTypes.h" #include "../Common/MyWindows.h" #include "IDecl.h" -#define STREAM_INTERFACE_SUB(i, base, x) DECL_INTERFACE_SUB(i, base, 3, x) -#define STREAM_INTERFACE(i, x) STREAM_INTERFACE_SUB(i, IUnknown, x) +Z7_PURE_INTERFACES_BEGIN -STREAM_INTERFACE(ISequentialInStream, 0x01) -{ - STDMETHOD(Read)(void *data, UInt32 size, UInt32 *processedSize) PURE; - - /* +#define Z7_IFACE_CONSTR_STREAM_SUB(i, base, n) \ + Z7_DECL_IFACE_7ZIP_SUB(i, base, 3, n) \ + { Z7_IFACE_COM7_PURE(i) }; + +#define Z7_IFACE_CONSTR_STREAM(i, n) \ + Z7_IFACE_CONSTR_STREAM_SUB(i, IUnknown, n) + + +/* +ISequentialInStream::Read() The requirement for caller: (processedSize != NULL). The callee can allow (processedSize == NULL) for compatibility reasons. - + if (size == 0), this function returns S_OK and (*processedSize) is set to 0. if (size != 0) @@ -31,21 +36,21 @@ STREAM_INTERFACE(ISequentialInStream, 0x01) If seek pointer before Read() call was changed to position past the end of stream: if (seek_pointer >= stream_size), this function returns S_OK and (*processedSize) is set to 0. - + ERROR CASES: If the function returns error code, then (*processedSize) is size of data written to (data) buffer (it can be data before error or data with errors). The recommended way for callee to work with reading errors: 1) write part of data before error to (data) buffer and return S_OK. 2) return error code for further calls of Read(). - */ -}; +*/ +#define Z7_IFACEM_ISequentialInStream(x) \ + x(Read(void *data, UInt32 size, UInt32 *processedSize)) +Z7_IFACE_CONSTR_STREAM(ISequentialInStream, 0x01) -STREAM_INTERFACE(ISequentialOutStream, 0x02) -{ - STDMETHOD(Write)(const void *data, UInt32 size, UInt32 *processedSize) PURE; - - /* + +/* +ISequentialOutStream::Write() The requirement for caller: (processedSize != NULL). The callee can allow (processedSize == NULL) for compatibility reasons. @@ -59,8 +64,13 @@ STREAM_INTERFACE(ISequentialOutStream, 0x02) ERROR CASES: If the function returns error code, then (*processedSize) is size of data written from (data) buffer. - */ -}; +*/ +#define Z7_IFACEM_ISequentialOutStream(x) \ + x(Write(const void *data, UInt32 size, UInt32 *processedSize)) +Z7_IFACE_CONSTR_STREAM(ISequentialOutStream, 0x02) + + +#ifdef _WIN32 #ifdef __HRESULT_FROM_WIN32 #define HRESULT_WIN32_ERROR_NEGATIVE_SEEK __HRESULT_FROM_WIN32(ERROR_NEGATIVE_SEEK) @@ -68,43 +78,44 @@ STREAM_INTERFACE(ISequentialOutStream, 0x02) #define HRESULT_WIN32_ERROR_NEGATIVE_SEEK HRESULT_FROM_WIN32(ERROR_NEGATIVE_SEEK) #endif -/* Seek() Function - If you seek before the beginning of the stream, Seek() function returns error code: +#else + +#define HRESULT_WIN32_ERROR_NEGATIVE_SEEK MY_E_ERROR_NEGATIVE_SEEK + +#endif + + +/* +IInStream::Seek() / IOutStream::Seek() + If you seek to position before the beginning of the stream, + Seek() function returns error code: Recommended error code is __HRESULT_FROM_WIN32(ERROR_NEGATIVE_SEEK). or STG_E_INVALIDFUNCTION - It is allowed to seek past the end of the stream. - - if Seek() returns error, then the value of *newPosition is undefined. */ -STREAM_INTERFACE_SUB(IInStream, ISequentialInStream, 0x03) -{ - STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition) PURE; -}; +#define Z7_IFACEM_IInStream(x) \ + x(Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition)) +Z7_IFACE_CONSTR_STREAM_SUB(IInStream, ISequentialInStream, 0x03) -STREAM_INTERFACE_SUB(IOutStream, ISequentialOutStream, 0x04) -{ - STDMETHOD(Seek)(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition) PURE; - STDMETHOD(SetSize)(UInt64 newSize) PURE; -}; +#define Z7_IFACEM_IOutStream(x) \ + x(Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition)) \ + x(SetSize(UInt64 newSize)) +Z7_IFACE_CONSTR_STREAM_SUB(IOutStream, ISequentialOutStream, 0x04) -STREAM_INTERFACE(IStreamGetSize, 0x06) -{ - STDMETHOD(GetSize)(UInt64 *size) PURE; -}; +#define Z7_IFACEM_IStreamGetSize(x) \ + x(GetSize(UInt64 *size)) +Z7_IFACE_CONSTR_STREAM(IStreamGetSize, 0x06) -STREAM_INTERFACE(IOutStreamFinish, 0x07) -{ - STDMETHOD(OutStreamFinish)() PURE; -}; +#define Z7_IFACEM_IOutStreamFinish(x) \ + x(OutStreamFinish()) +Z7_IFACE_CONSTR_STREAM(IOutStreamFinish, 0x07) +#define Z7_IFACEM_IStreamGetProps(x) \ + x(GetProps(UInt64 *size, FILETIME *cTime, FILETIME *aTime, FILETIME *mTime, UInt32 *attrib)) +Z7_IFACE_CONSTR_STREAM(IStreamGetProps, 0x08) -STREAM_INTERFACE(IStreamGetProps, 0x08) -{ - STDMETHOD(GetProps)(UInt64 *size, FILETIME *cTime, FILETIME *aTime, FILETIME *mTime, UInt32 *attrib) PURE; -}; struct CStreamFileProps { @@ -119,9 +130,81 @@ struct CStreamFileProps FILETIME MTime; }; -STREAM_INTERFACE(IStreamGetProps2, 0x09) -{ - STDMETHOD(GetProps2)(CStreamFileProps *props) PURE; -}; +#define Z7_IFACEM_IStreamGetProps2(x) \ + x(GetProps2(CStreamFileProps *props)) +Z7_IFACE_CONSTR_STREAM(IStreamGetProps2, 0x09) + +#define Z7_IFACEM_IStreamGetProp(x) \ + x(GetProperty(PROPID propID, PROPVARIANT *value)) \ + x(ReloadProps()) +Z7_IFACE_CONSTR_STREAM(IStreamGetProp, 0x0a) + + +/* +IStreamSetRestriction::SetRestriction(UInt64 begin, UInt64 end) + + It sets region of data in output stream that is restricted. + For restricted region it's expected (or allowed) + that the caller can write to same region with different calls of Write()/SetSize(). + Another regions of output stream will be supposed as non-restricted: + - The callee usually doesn't flush the data in restricted region. + - The callee usually can flush data from non-restricted region after writing. + +Actual restiction rules depend also from current stream position. +It's recommended to call SetRestriction() just before the Write() call. +So the callee can optimize writing and flushing, if that Write() +operation is not restricted. + +Note: Each new call of SetRestriction() sets new restictions, +so previous restrction calls has no effect anymore. + +inputs: + + (begin > end) is not allowed, and returns E_FAIL; + + if (begin == end) + { + No restriction. + The caller will call Write() in sequential order. + After SetRestriction(begin, begin), but before next call of SetRestriction() + { + Additional condition: + it's expected that current stream seek position is equal to stream size. + The callee can make final flushing for any data before current stream seek position. + For each Write(size) call: + The callee can make final flushing for that new written data. + } + The pair of values (begin == 0 && end == 0) is recommended to remove write restriction. + } + + if (begin < end) + { + it means that callee must NOT flush any data in region [begin, end). + The caller is allowed to Seek() to that region and rewrite the + data in that restriction region. + if (end == (UInt64)(Int64)-1) + { + there is no upper bound for restricted region. + So non-restricted region will be [0, begin) in that case + } + } + + returns: + - if (begin > end) it return ERROR code (E_FAIL) + - S_OK : if no errors. + - Also the call of SetRestriction() can initiate the flushing of already written data. + So it can return the result of that flushing. + + Note: IOutStream::SetSize() also can change the data. + So it's not expected the call + IOutStream::SetSize() to region that was written before as unrestricted. +*/ + +#define Z7_IFACEM_IStreamSetRestriction(x) \ + x(SetRestriction(UInt64 begin, UInt64 end)) \ + +Z7_IFACE_CONSTR_STREAM(IStreamSetRestriction, 0x10) + +Z7_PURE_INTERFACES_END #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/LzFindOpt.mak b/src/plugins/7zip/7za/cpp/7zip/LzFindOpt.mak new file mode 100644 index 00000000..169e10f0 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/LzFindOpt.mak @@ -0,0 +1,7 @@ +!IF defined(USE_C_LZFINDOPT) || "$(PLATFORM)" != "x64" +C_OBJS = $(C_OBJS) \ + $O\LzFindOpt.obj +!ELSE +ASM_OBJS = $(ASM_OBJS) \ + $O\LzFindOpt.obj +!ENDIF diff --git a/src/plugins/7zip/7za/cpp/7zip/LzmaDec.mak b/src/plugins/7zip/7za/cpp/7zip/LzmaDec.mak new file mode 100644 index 00000000..624ea9e6 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/LzmaDec.mak @@ -0,0 +1,7 @@ +!IF "$(PLATFORM)" == "x64" || ("$(PLATFORM)" == "arm64" && !defined(NO_ASM_GNU)) +!IFNDEF NO_ASM +CFLAGS_C_SPEC = -DZ7_LZMA_DEC_OPT +ASM_OBJS = $(ASM_OBJS) \ + $O\LzmaDecOpt.obj +!ENDIF +!ENDIF diff --git a/src/plugins/7zip/7za/cpp/7zip/LzmaDec_gcc.mak b/src/plugins/7zip/7za/cpp/7zip/LzmaDec_gcc.mak new file mode 100644 index 00000000..51924f50 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/LzmaDec_gcc.mak @@ -0,0 +1,14 @@ +ifdef USE_ASM +ifdef IS_X64 +USE_LZMA_DEC_ASM=1 +endif +ifdef IS_ARM64 +USE_LZMA_DEC_ASM=1 +endif +endif + +ifdef USE_LZMA_DEC_ASM + +LZMA_DEC_OPT_OBJS= $O/LzmaDecOpt.o + +endif diff --git a/src/plugins/7zip/7za/cpp/7zip/MyVersionInfo.rc b/src/plugins/7zip/7za/cpp/7zip/MyVersionInfo.rc index fab66860..fc63d798 100644 --- a/src/plugins/7zip/7za/cpp/7zip/MyVersionInfo.rc +++ b/src/plugins/7zip/7za/cpp/7zip/MyVersionInfo.rc @@ -1,2 +1,2 @@ #include "MyVersion.h" -#include "..\..\C\7zVersion.rc" +#include "../../C/7zVersion.rc" diff --git a/src/plugins/7zip/7za/cpp/7zip/PropID.h b/src/plugins/7zip/7za/cpp/7zip/PropID.h index 1822f402..e0747942 100644 --- a/src/plugins/7zip/7za/cpp/7zip/PropID.h +++ b/src/plugins/7zip/7za/cpp/7zip/PropID.h @@ -1,7 +1,7 @@ // PropID.h -#ifndef __7ZIP_PROP_ID_H -#define __7ZIP_PROP_ID_H +#ifndef ZIP7_INC_7ZIP_PROP_ID_H +#define ZIP7_INC_7ZIP_PROP_ID_H #include "../Common/MyTypes.h" @@ -103,6 +103,15 @@ enum kpidReadOnly, kpidOutName, kpidCopyLink, + kpidArcFileName, + kpidIsHash, + kpidChangeTime, + kpidUserId, + kpidGroupId, + kpidDeviceMajor, + kpidDeviceMinor, + kpidDevMajor, + kpidDevMinor, kpid_NUM_DEFINED, @@ -124,4 +133,46 @@ const UInt32 kpv_ErrorFlags_DataError = 1 << 9; const UInt32 kpv_ErrorFlags_CrcError = 1 << 10; // const UInt32 kpv_ErrorFlags_Unsupported = 1 << 11; +/* +linux ctime : + file metadata was last changed. + changing the file modification time + counts as a metadata change, so will also have the side effect of updating the ctime. + +PROPVARIANT for timestamps in 7-Zip: +{ + vt = VT_FILETIME + wReserved1: set precision level + 0 : base value (backward compatibility value) + only filetime is used (7 digits precision). + wReserved2 and wReserved3 can contain random data + 1 : Unix (1 sec) + 2 : DOS (2 sec) + 3 : High Precision (1 ns) + 16 - 3 : (reserved) = 1 day + 16 - 2 : (reserved) = 1 hour + 16 - 1 : (reserved) = 1 minute + 16 + 0 : 1 sec (0 digits after point) + 16 + (1,2,3,4,5,6,7,8,9) : set subsecond precision level : + (number of decimal digits after point) + 16 + 9 : 1 ns (9 digits after point) + wReserved2 = ns % 100 : if (8 or 9 digits pecision) + = 0 : if not (8 or 9 digits pecision) + wReserved3 = 0; + filetime +} + +NOTE: TAR-PAX archives created by GNU TAR don't keep + whole information about original level of precision, + and timestamp are stored in reduced form, where tail zero + digits after point are removed. + So 7-Zip can return different precision levels for different items for such TAR archives. +*/ + +/* +TimePrec returned by IOutArchive::GetFileTimeType() +is used only for updating, when we compare MTime timestamp +from archive with timestamp from directory. +*/ + #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/Sha1.mak b/src/plugins/7zip/7za/cpp/7zip/Sha1.mak new file mode 100644 index 00000000..1b5f605f --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Sha1.mak @@ -0,0 +1,13 @@ +COMMON_OBJS = $(COMMON_OBJS) \ + $O\Sha1Prepare.obj + +C_OBJS = $(C_OBJS) \ + $O\Sha1.obj + +!IF defined(USE_C_SHA) || "$(PLATFORM)" == "arm" || "$(PLATFORM)" == "arm64" +C_OBJS = $(C_OBJS) \ + $O\Sha1Opt.obj +!ELSEIF "$(PLATFORM)" != "ia64" && "$(PLATFORM)" != "mips" && "$(PLATFORM)" != "arm" && "$(PLATFORM)" != "arm64" +ASM_OBJS = $(ASM_OBJS) \ + $O\Sha1Opt.obj +!ENDIF diff --git a/src/plugins/7zip/7za/cpp/7zip/Sha256.mak b/src/plugins/7zip/7za/cpp/7zip/Sha256.mak new file mode 100644 index 00000000..0bdbcb60 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Sha256.mak @@ -0,0 +1,13 @@ +COMMON_OBJS = $(COMMON_OBJS) \ + $O\Sha256Prepare.obj + +C_OBJS = $(C_OBJS) \ + $O\Sha256.obj + +!IF defined(USE_C_SHA) || "$(PLATFORM)" == "arm" || "$(PLATFORM)" == "arm64" +C_OBJS = $(C_OBJS) \ + $O\Sha256Opt.obj +!ELSEIF "$(PLATFORM)" != "ia64" && "$(PLATFORM)" != "mips" && "$(PLATFORM)" != "arm" && "$(PLATFORM)" != "arm64" +ASM_OBJS = $(ASM_OBJS) \ + $O\Sha256Opt.obj +!ENDIF diff --git a/src/plugins/7zip/7za/cpp/7zip/Sort.mak b/src/plugins/7zip/7za/cpp/7zip/Sort.mak new file mode 100644 index 00000000..ca0ff598 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/Sort.mak @@ -0,0 +1,6 @@ +!IF defined(USE_NO_ASM) || defined(USE_C_SORT) || "$(PLATFORM)" == "ia64" || "$(PLATFORM)" == "mips" || "$(PLATFORM)" == "arm" || "$(PLATFORM)" == "arm64" +C_OBJS = $(C_OBJS) \ +!ELSE +ASM_OBJS = $(ASM_OBJS) \ +!ENDIF + $O\Sort.obj diff --git a/src/plugins/7zip/7za/cpp/7zip/SubBuild.mak b/src/plugins/7zip/7za/cpp/7zip/SubBuild.mak new file mode 100644 index 00000000..f86ce436 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/SubBuild.mak @@ -0,0 +1,3 @@ + cd $(@D) + $(MAKE) -nologo $(TARGETS) + cd .. diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/7zwrapper/7zwrapper.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/7zwrapper/7zwrapper.cpp index fa19c558..4c4d3c1e 100644 --- a/src/plugins/7zip/7za/cpp/7zip/UI/7zwrapper/7zwrapper.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/UI/7zwrapper/7zwrapper.cpp @@ -63,6 +63,12 @@ static void PrintString(const AString &s) _snprintf_s(OutputBuffer + strlen(OutputBuffer), OutputBufferSize - strlen(OutputBuffer), _TRUNCATE, "%s", (LPCSTR)s); } +// Keep the crash-report wrapper's byte-oriented output ABI stable across 7-Zip string changes. +static void PrintString(const char *s) +{ + PrintString(AString(s)); +} + static void PrintNewLine() { _snprintf_s(OutputBuffer + strlen(OutputBuffer), OutputBufferSize - strlen(OutputBuffer), _TRUNCATE, "\n"); @@ -89,6 +95,11 @@ static void PrintError(const AString &s) PrintNewLine(); } +static void PrintError(const char *s) +{ + PrintError(AString(s)); +} + static HRESULT IsArchiveItemProp(IInArchive *archive, UInt32 index, PROPID propID, bool &result) { NCOM::CPropVariant prop; @@ -121,12 +132,7 @@ class CArchiveOpenCallback: public CMyUnknownImp { public: - MY_UNKNOWN_IMP1(ICryptoGetTextPassword) - - STDMETHOD(SetTotal)(const UInt64 *files, const UInt64 *bytes); - STDMETHOD(SetCompleted)(const UInt64 *files, const UInt64 *bytes); - - STDMETHOD(CryptoGetTextPassword)(BSTR *password); + Z7_IFACES_IMP_UNK_2(IArchiveOpenCallback, ICryptoGetTextPassword) bool PasswordIsDefined; UString Password; @@ -176,19 +182,8 @@ class CArchiveExtractCallback: public CMyUnknownImp { public: - MY_UNKNOWN_IMP1(ICryptoGetTextPassword) - - // IProgress - STDMETHOD(SetTotal)(UInt64 size); - STDMETHOD(SetCompleted)(const UInt64 *completeValue); - - // IArchiveExtractCallback - STDMETHOD(GetStream)(UInt32 index, ISequentialOutStream **outStream, Int32 askExtractMode); - STDMETHOD(PrepareOperation)(Int32 askExtractMode); - STDMETHOD(SetOperationResult)(Int32 resultEOperationResult); - - // ICryptoGetTextPassword - STDMETHOD(CryptoGetTextPassword)(BSTR *aPassword); + Z7_IFACES_IMP_UNK_2(IArchiveExtractCallback, ICryptoGetTextPassword) + Z7_IFACE_COM7_IMP(IProgress) private: CMyComPtr _archiveHandler; @@ -340,7 +335,7 @@ STDMETHODIMP CArchiveExtractCallback::GetStream(UInt32 index, _outFileStreamSpec = new COutFileStream; CMyComPtr outStreamLoc(_outFileStreamSpec); - if (!_outFileStreamSpec->Open(fullProcessedPath, CREATE_ALWAYS)) + if (!_outFileStreamSpec->Create_ALWAYS(fullProcessedPath)) { PrintError("Can not open output file", fullProcessedPath); return E_ABORT; @@ -446,23 +441,9 @@ class CArchiveUpdateCallback: public CMyUnknownImp { public: - MY_UNKNOWN_IMP2(IArchiveUpdateCallback2, ICryptoGetTextPassword2) - - // IProgress - STDMETHOD(SetTotal)(UInt64 size); - STDMETHOD(SetCompleted)(const UInt64 *completeValue); - - // IUpdateCallback2 - STDMETHOD(EnumProperties)(IEnumSTATPROPSTG **enumerator); - STDMETHOD(GetUpdateItemInfo)(UInt32 index, - Int32 *newData, Int32 *newProperties, UInt32 *indexInArchive); - STDMETHOD(GetProperty)(UInt32 index, PROPID propID, PROPVARIANT *value); - STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **inStream); - STDMETHOD(SetOperationResult)(Int32 operationResult); - STDMETHOD(GetVolumeSize)(UInt32 index, UInt64 *size); - STDMETHOD(GetVolumeStream)(UInt32 index, ISequentialOutStream **volumeStream); - - STDMETHOD(CryptoGetTextPassword2)(Int32 *passwordIsDefined, BSTR *password); + Z7_IFACES_IMP_UNK_2(IArchiveUpdateCallback2, ICryptoGetTextPassword2) + Z7_IFACE_COM7_IMP(IProgress) + Z7_IFACE_COM7_IMP(IArchiveUpdateCallback) public: CRecordVector VolumesSizes; @@ -506,11 +487,6 @@ STDMETHODIMP CArchiveUpdateCallback::SetCompleted(const UInt64 * /* completeValu } -STDMETHODIMP CArchiveUpdateCallback::EnumProperties(IEnumSTATPROPSTG ** /* enumerator */) -{ - return E_NOTIMPL; -} - STDMETHODIMP CArchiveUpdateCallback::GetUpdateItemInfo(UInt32 /* index */, Int32 *newData, Int32 *newProperties, UInt32 *indexInArchive) { @@ -631,7 +607,7 @@ STDMETHODIMP CArchiveUpdateCallback::GetVolumeStream(UInt32 index, ISequentialOu fileName += VolExt; COutFileStream *streamSpec = new COutFileStream; CMyComPtr streamLoc(streamSpec); - if (!streamSpec->Create(us2fs(fileName), false)) + if (!streamSpec->Create_ALWAYS(us2fs(fileName))) return ::GetLastError(); *volumeStream = streamLoc.Detach(); return S_OK; @@ -699,7 +675,7 @@ BOOL WINAPI CompressFiles(const char* archiveName7z, const char* sourceDir, cons PrintError("Can not load 7-zip library"); return FALSE; } - CreateObjectFunc createObjectFunc = (CreateObjectFunc)lib.GetProc("CreateObject"); + CreateObjectFunc createObjectFunc = (CreateObjectFunc)::GetProcAddress(lib.Get_HMODULE(), "CreateObject"); if (createObjectFunc == 0) { PrintError("Can not get CreateObject"); @@ -743,7 +719,7 @@ BOOL WINAPI CompressFiles(const char* archiveName7z, const char* sourceDir, cons COutFileStream *outFileStreamSpec = new COutFileStream; CMyComPtr outFileStream = outFileStreamSpec; - if (!outFileStreamSpec->Create(archiveName, false)) + if (!outFileStreamSpec->Create_ALWAYS(archiveName)) { PrintError("Can't create archive file"); return FALSE; @@ -855,4 +831,4 @@ BOOL APIENTRY DllMain( HMODULE hModule, break; } return TRUE; -} \ No newline at end of file +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Agent/Agent.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Agent/Agent.cpp new file mode 100644 index 00000000..46b740ad --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Agent/Agent.cpp @@ -0,0 +1,1968 @@ +// Agent.cpp + +#include "StdAfx.h" + +#include + +#include "../../../../C/Sort.h" + +#include "../../../Common/ComTry.h" + +#include "../../../Windows/FileDir.h" +#include "../../../Windows/FileName.h" +#include "../../../Windows/PropVariantConv.h" + +#ifndef Z7_ST +#include "../../../Windows/Synchronization.h" +#endif + +#include "../Common/ArchiveExtractCallback.h" +#include "../FileManager/RegistryUtils.h" + +#include "Agent.h" + +using namespace NWindows; + +CCodecs *g_CodecsObj; + +static const bool k_keepEmptyDirPrefixes = + false; // 22.00 + // true; // 21.07 + +#ifdef Z7_EXTERNAL_CODECS + extern + CExternalCodecs g_ExternalCodecs; + CExternalCodecs g_ExternalCodecs; + extern + const CExternalCodecs *g_ExternalCodecs_Ptr; + const CExternalCodecs *g_ExternalCodecs_Ptr; + static CCodecs::CReleaser g_CodecsReleaser; +#else + extern + CMyComPtr g_CodecsRef; + CMyComPtr g_CodecsRef; +#endif + +#ifndef Z7_ST +static NSynchronization::CCriticalSection g_CriticalSection; +#define MT_LOCK NSynchronization::CCriticalSectionLock lock(g_CriticalSection); +#else +#define MT_LOCK +#endif + +void FreeGlobalCodecs() +{ + MT_LOCK + + #ifdef Z7_EXTERNAL_CODECS + if (g_CodecsObj) + { + g_CodecsObj->CloseLibs(); + } + g_CodecsReleaser.Set(NULL); + g_CodecsObj = NULL; + g_ExternalCodecs.ClearAndRelease(); + g_ExternalCodecs_Ptr = NULL; + #else + g_CodecsRef.Release(); + #endif +} + +HRESULT LoadGlobalCodecs() +{ + MT_LOCK + + if (g_CodecsObj) + return S_OK; + + g_CodecsObj = new CCodecs; + + #ifdef Z7_EXTERNAL_CODECS + g_ExternalCodecs.GetCodecs = g_CodecsObj; + g_ExternalCodecs.GetHashers = g_CodecsObj; + g_CodecsReleaser.Set(g_CodecsObj); + #else + g_CodecsRef.Release(); + g_CodecsRef = g_CodecsObj; + #endif + + RINOK(g_CodecsObj->Load()) + if (g_CodecsObj->Formats.IsEmpty()) + { + FreeGlobalCodecs(); + return E_NOTIMPL; + } + + Codecs_AddHashArcHandler(g_CodecsObj); + + #ifdef Z7_EXTERNAL_CODECS + RINOK(g_ExternalCodecs.Load()) + g_ExternalCodecs_Ptr = &g_ExternalCodecs; + #endif + + return S_OK; +} + +Z7_COM7F_IMF(CAgentFolder::GetAgentFolder(CAgentFolder **agentFolder)) +{ + *agentFolder = this; + return S_OK; +} + +void CAgentFolder::LoadFolder(unsigned proxyDirIndex) +{ + CProxyItem item; + item.DirIndex = proxyDirIndex; + + if (_proxy2) + { + const CProxyDir2 &dir = _proxy2->Dirs[proxyDirIndex]; + FOR_VECTOR (i, dir.Items) + { + item.Index = i; + _items.Add(item); + const CProxyFile2 &file = _proxy2->Files[dir.Items[i]]; + if (file.DirIndex != -1) + LoadFolder((unsigned)file.DirIndex); + if (_loadAltStreams && file.AltDirIndex != -1) + LoadFolder((unsigned)file.AltDirIndex); + } + return; + } + + const CProxyDir &dir = _proxy->Dirs[proxyDirIndex]; + unsigned i; + for (i = 0; i < dir.SubDirs.Size(); i++) + { + item.Index = i; + _items.Add(item); + LoadFolder(dir.SubDirs[i]); + } + + unsigned start = dir.SubDirs.Size(); + for (i = 0; i < dir.SubFiles.Size(); i++) + { + item.Index = start + i; + _items.Add(item); + } +} + +Z7_COM7F_IMF(CAgentFolder::LoadItems()) +{ + if (!_agentSpec->_archiveLink.IsOpen) + return E_FAIL; + _items.Clear(); + if (_flatMode) + { + LoadFolder(_proxyDirIndex); + if (_proxy2 && _loadAltStreams) + { + if (_proxyDirIndex == k_Proxy2_RootDirIndex) + LoadFolder(k_Proxy2_AltRootDirIndex); + } + } + return S_OK; +} + +Z7_COM7F_IMF(CAgentFolder::GetNumberOfItems(UInt32 *numItems)) +{ + if (_flatMode) + *numItems = _items.Size(); + else if (_proxy2) + *numItems = _proxy2->Dirs[_proxyDirIndex].Items.Size(); + else + { + const CProxyDir *dir = &_proxy->Dirs[_proxyDirIndex]; + *numItems = dir->SubDirs.Size() + dir->SubFiles.Size(); + } + return S_OK; +} + +#define SET_realIndex_AND_dir \ + unsigned realIndex; const CProxyDir *dir; \ + if (_flatMode) { const CProxyItem &item = _items[index]; dir = &_proxy->Dirs[item.DirIndex]; realIndex = item.Index; } \ + else { dir = &_proxy->Dirs[_proxyDirIndex]; realIndex = index; } + +#define SET_realIndex_AND_dir_2 \ + unsigned realIndex; const CProxyDir2 *dir; \ + if (_flatMode) { const CProxyItem &item = _items[index]; dir = &_proxy2->Dirs[item.DirIndex]; realIndex = item.Index; } \ + else { dir = &_proxy2->Dirs[_proxyDirIndex]; realIndex = index; } + +UString CAgentFolder::GetName(UInt32 index) const +{ + if (_proxy2) + { + SET_realIndex_AND_dir_2 + return _proxy2->Files[dir->Items[realIndex]].Name; + } + SET_realIndex_AND_dir + if (realIndex < dir->SubDirs.Size()) + return _proxy->Dirs[dir->SubDirs[realIndex]].Name; + return _proxy->Files[dir->SubFiles[realIndex - dir->SubDirs.Size()]].Name; +} + +void CAgentFolder::GetPrefix(UInt32 index, UString &prefix) const +{ + if (!_flatMode) + { + prefix.Empty(); + return; + } + + const CProxyItem &item = _items[index]; + unsigned proxyIndex = item.DirIndex; + + if (_proxy2) + { + // that code is unused. 7-Zip gets prefix via GetItemPrefix() . + + unsigned len = 0; + while (proxyIndex != _proxyDirIndex && proxyIndex >= k_Proxy2_NumRootDirs) + { + const CProxyFile2 &file = _proxy2->Files[(unsigned)_proxy2->Dirs[proxyIndex].ArcIndex]; + len += file.NameLen + 1; + proxyIndex = (file.Parent == -1) ? 0 : (unsigned)_proxy2->Files[(unsigned)file.Parent].GetDirIndex(file.IsAltStream); + } + + wchar_t *p = prefix.GetBuf_SetEnd(len) + len; + proxyIndex = item.DirIndex; + while (proxyIndex != _proxyDirIndex && proxyIndex >= k_Proxy2_NumRootDirs) + { + const CProxyFile2 &file = _proxy2->Files[(unsigned)_proxy2->Dirs[proxyIndex].ArcIndex]; + p--; + *p = WCHAR_PATH_SEPARATOR; + p -= file.NameLen; + wmemcpy(p, file.Name, file.NameLen); + proxyIndex = (file.Parent == -1) ? 0 : (unsigned)_proxy2->Files[(unsigned)file.Parent].GetDirIndex(file.IsAltStream); + } + } + else + { + unsigned len = 0; + while (proxyIndex != _proxyDirIndex) + { + const CProxyDir *dir = &_proxy->Dirs[proxyIndex]; + len += dir->NameLen + 1; + proxyIndex = (unsigned)dir->ParentDir; + } + + wchar_t *p = prefix.GetBuf_SetEnd(len) + len; + proxyIndex = item.DirIndex; + while (proxyIndex != _proxyDirIndex) + { + const CProxyDir *dir = &_proxy->Dirs[proxyIndex]; + p--; + *p = WCHAR_PATH_SEPARATOR; + p -= dir->NameLen; + wmemcpy(p, dir->Name, dir->NameLen); + proxyIndex = (unsigned)dir->ParentDir; + } + } +} + +UString CAgentFolder::GetFullPrefix(UInt32 index) const +{ + unsigned foldIndex = _proxyDirIndex; + + if (_flatMode) + foldIndex = _items[index].DirIndex; + + if (_proxy2) + return _proxy2->Dirs[foldIndex].PathPrefix; + else + return _proxy->GetDirPath_as_Prefix(foldIndex); +} + +Z7_COM7F_IMF2(UInt64, CAgentFolder::GetItemSize(UInt32 index)) +{ + unsigned arcIndex; + if (_proxy2) + { + SET_realIndex_AND_dir_2 + arcIndex = dir->Items[realIndex]; + const CProxyFile2 &item = _proxy2->Files[arcIndex]; + if (item.IsDir()) + { + const CProxyDir2 &itemFolder = _proxy2->Dirs[item.DirIndex]; + if (!_flatMode) + return itemFolder.Size; + } + } + else + { + SET_realIndex_AND_dir + if (realIndex < dir->SubDirs.Size()) + { + const CProxyDir &item = _proxy->Dirs[dir->SubDirs[realIndex]]; + if (!_flatMode) + return item.Size; + if (!item.IsLeaf()) + return 0; + arcIndex = (unsigned)item.ArcIndex; + } + else + { + arcIndex = dir->SubFiles[realIndex - dir->SubDirs.Size()]; + } + } + NCOM::CPropVariant prop; + _agentSpec->GetArchive()->GetProperty(arcIndex, kpidSize, &prop); + if (prop.vt == VT_UI8) + return prop.uhVal.QuadPart; + else + return 0; +} + +Z7_COM7F_IMF(CAgentFolder::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) +{ + COM_TRY_BEGIN + NCOM::CPropVariant prop; + + if (propID == kpidPrefix) + { + if (_flatMode) + { + UString prefix; + GetPrefix(index, prefix); + prop = prefix; + } + } + else if (_proxy2) + { + SET_realIndex_AND_dir_2 + unsigned arcIndex = dir->Items[realIndex]; + const CProxyFile2 &item = _proxy2->Files[arcIndex]; + /* + if (propID == kpidNumAltStreams) + { + if (item.AltDirIndex != -1) + prop = _proxy2->Dirs[item.AltDirIndex].Items.Size(); + } + else + */ + if (!item.IsDir()) + { + switch (propID) + { + case kpidIsDir: prop = false; break; + case kpidName: prop = item.Name; break; + default: return _agentSpec->GetArchive()->GetProperty(arcIndex, propID, value); + } + } + else + { + const CProxyDir2 &itemFolder = _proxy2->Dirs[item.DirIndex]; + if (!_flatMode && propID == kpidSize) + prop = itemFolder.Size; + else if (!_flatMode && propID == kpidPackSize) + prop = itemFolder.PackSize; + else switch (propID) + { + case kpidIsDir: prop = true; break; + case kpidNumSubDirs: prop = itemFolder.NumSubDirs; break; + case kpidNumSubFiles: prop = itemFolder.NumSubFiles; break; + case kpidName: prop = item.Name; break; + case kpidCRC: + { + // if (itemFolder.IsLeaf) + if (!item.Ignore) + { + RINOK(_agentSpec->GetArchive()->GetProperty(arcIndex, propID, value)) + } + if (itemFolder.CrcIsDefined && value->vt == VT_EMPTY) + prop = itemFolder.Crc; + break; + } + default: + // if (itemFolder.IsLeaf) + if (!item.Ignore) + return _agentSpec->GetArchive()->GetProperty(arcIndex, propID, value); + } + } + } + else + { + SET_realIndex_AND_dir + if (realIndex < dir->SubDirs.Size()) + { + const CProxyDir &item = _proxy->Dirs[dir->SubDirs[realIndex]]; + if (!_flatMode && propID == kpidSize) + prop = item.Size; + else if (!_flatMode && propID == kpidPackSize) + prop = item.PackSize; + else + switch (propID) + { + case kpidIsDir: prop = true; break; + case kpidNumSubDirs: prop = item.NumSubDirs; break; + case kpidNumSubFiles: prop = item.NumSubFiles; break; + case kpidName: prop = item.Name; break; + case kpidCRC: + { + if (item.IsLeaf()) + { + RINOK(_agentSpec->GetArchive()->GetProperty((unsigned)item.ArcIndex, propID, value)) + } + if (item.CrcIsDefined && value->vt == VT_EMPTY) + prop = item.Crc; + break; + } + default: + if (item.IsLeaf()) + return _agentSpec->GetArchive()->GetProperty((unsigned)item.ArcIndex, propID, value); + } + } + else + { + unsigned arcIndex = dir->SubFiles[realIndex - dir->SubDirs.Size()]; + switch (propID) + { + case kpidIsDir: prop = false; break; + case kpidName: prop = _proxy->Files[arcIndex].Name; break; + default: + return _agentSpec->GetArchive()->GetProperty(arcIndex, propID, value); + } + } + } + prop.Detach(value); + return S_OK; + COM_TRY_END +} + +static UInt64 GetUInt64Prop(IInArchive *archive, UInt32 index, PROPID propID) +{ + NCOM::CPropVariant prop; + if (archive->GetProperty(index, propID, &prop) != S_OK) + throw 111233443; + UInt64 v = 0; + if (ConvertPropVariantToUInt64(prop, v)) + return v; + return 0; +} + +Z7_COM7F_IMF(CAgentFolder::GetItemName(UInt32 index, const wchar_t **name, unsigned *len)) +{ + if (_proxy2) + { + SET_realIndex_AND_dir_2 + unsigned arcIndex = dir->Items[realIndex]; + const CProxyFile2 &item = _proxy2->Files[arcIndex]; + *name = item.Name; + *len = item.NameLen; + return S_OK; + } + else + { + SET_realIndex_AND_dir + if (realIndex < dir->SubDirs.Size()) + { + const CProxyDir &item = _proxy->Dirs[dir->SubDirs[realIndex]]; + *name = item.Name; + *len = item.NameLen; + return S_OK; + } + else + { + const CProxyFile &item = _proxy->Files[dir->SubFiles[realIndex - dir->SubDirs.Size()]]; + *name = item.Name; + *len = item.NameLen; + return S_OK; + } + } +} + +Z7_COM7F_IMF(CAgentFolder::GetItemPrefix(UInt32 index, const wchar_t **name, unsigned *len)) +{ + *name = NULL; + *len = 0; + if (!_flatMode) + return S_OK; + + if (_proxy2) + { + const CProxyItem &item = _items[index]; + const UString &s = _proxy2->Dirs[item.DirIndex].PathPrefix; + unsigned baseLen = _proxy2->Dirs[_proxyDirIndex].PathPrefix.Len(); + if (baseLen <= s.Len()) + { + *name = (const wchar_t *)s + baseLen; + *len = s.Len() - baseLen; + } + else + { + return E_FAIL; + // throw 111l; + } + } + return S_OK; +} + +static int CompareRawProps(IArchiveGetRawProps *rawProps, unsigned arcIndex1, unsigned arcIndex2, PROPID propID) +{ + // if (propID == kpidSha1) + if (rawProps) + { + const void *p1, *p2; + UInt32 size1, size2; + UInt32 propType1, propType2; + const HRESULT res1 = rawProps->GetRawProp(arcIndex1, propID, &p1, &size1, &propType1); + const HRESULT res2 = rawProps->GetRawProp(arcIndex2, propID, &p2, &size2, &propType2); + if (res1 == S_OK && res2 == S_OK) + { + for (UInt32 i = 0; i < size1 && i < size2; i++) + { + const Byte b1 = ((const Byte *)p1)[i]; + const Byte b2 = ((const Byte *)p2)[i]; + if (b1 < b2) return -1; + if (b1 > b2) return 1; + } + if (size1 < size2) return -1; + if (size1 > size2) return 1; + return 0; + } + } + return 0; +} + +// returns pointer to extension including '.' + +static const wchar_t *GetExtension(const wchar_t *name) +{ + for (const wchar_t *dotPtr = NULL;; name++) + { + wchar_t c = *name; + if (c == 0) + return dotPtr ? dotPtr : name; + if (c == '.') + dotPtr = name; + } +} + + +int CAgentFolder::CompareItems3(UInt32 index1, UInt32 index2, PROPID propID) +{ + NCOM::CPropVariant prop1, prop2; + // Name must be first property + GetProperty(index1, propID, &prop1); + GetProperty(index2, propID, &prop2); + if (prop1.vt != prop2.vt) + return MyCompare(prop1.vt, prop2.vt); + if (prop1.vt == VT_BSTR) + return MyStringCompareNoCase(prop1.bstrVal, prop2.bstrVal); + return prop1.Compare(prop2); +} + + +int CAgentFolder::CompareItems2(UInt32 index1, UInt32 index2, PROPID propID, Int32 propIsRaw) +{ + unsigned realIndex1, realIndex2; + const CProxyDir2 *dir1, *dir2; + + if (_flatMode) + { + const CProxyItem &item1 = _items[index1]; + const CProxyItem &item2 = _items[index2]; + dir1 = &_proxy2->Dirs[item1.DirIndex]; + dir2 = &_proxy2->Dirs[item2.DirIndex]; + realIndex1 = item1.Index; + realIndex2 = item2.Index; + } + else + { + dir2 = dir1 = &_proxy2->Dirs[_proxyDirIndex]; + realIndex1 = index1; + realIndex2 = index2; + } + + UInt32 arcIndex1; + UInt32 arcIndex2; + bool isDir1, isDir2; + arcIndex1 = dir1->Items[realIndex1]; + arcIndex2 = dir2->Items[realIndex2]; + const CProxyFile2 &prox1 = _proxy2->Files[arcIndex1]; + const CProxyFile2 &prox2 = _proxy2->Files[arcIndex2]; + + if (propID == kpidName) + { + return CompareFileNames_ForFolderList(prox1.Name, prox2.Name); + } + + if (propID == kpidPrefix) + { + if (!_flatMode) + return 0; + return CompareFileNames_ForFolderList( + _proxy2->Dirs[_items[index1].DirIndex].PathPrefix, + _proxy2->Dirs[_items[index2].DirIndex].PathPrefix); + } + + if (propID == kpidExtension) + { + return CompareFileNames_ForFolderList( + GetExtension(prox1.Name), + GetExtension(prox2.Name)); + } + + isDir1 = prox1.IsDir(); + isDir2 = prox2.IsDir(); + + if (propID == kpidIsDir) + { + if (isDir1 == isDir2) + return 0; + return isDir1 ? -1 : 1; + } + + const CProxyDir2 *proxFolder1 = NULL; + const CProxyDir2 *proxFolder2 = NULL; + if (isDir1) proxFolder1 = &_proxy2->Dirs[prox1.DirIndex]; + if (isDir2) proxFolder2 = &_proxy2->Dirs[prox2.DirIndex]; + + if (propID == kpidNumSubDirs) + { + UInt32 n1 = 0; + UInt32 n2 = 0; + if (isDir1) n1 = proxFolder1->NumSubDirs; + if (isDir2) n2 = proxFolder2->NumSubDirs; + return MyCompare(n1, n2); + } + + if (propID == kpidNumSubFiles) + { + UInt32 n1 = 0; + UInt32 n2 = 0; + if (isDir1) n1 = proxFolder1->NumSubFiles; + if (isDir2) n2 = proxFolder2->NumSubFiles; + return MyCompare(n1, n2); + } + + if (propID == kpidSize) + { + UInt64 n1, n2; + if (isDir1) + n1 = _flatMode ? 0 : proxFolder1->Size; + else + n1 = GetUInt64Prop(_agentSpec->GetArchive(), arcIndex1, kpidSize); + if (isDir2) + n2 = _flatMode ? 0 : proxFolder2->Size; + else + n2 = GetUInt64Prop(_agentSpec->GetArchive(), arcIndex2, kpidSize); + return MyCompare(n1, n2); + } + + if (propID == kpidPackSize) + { + UInt64 n1, n2; + if (isDir1) + n1 = _flatMode ? 0 : proxFolder1->PackSize; + else + n1 = GetUInt64Prop(_agentSpec->GetArchive(), arcIndex1, kpidPackSize); + if (isDir2) + n2 = _flatMode ? 0 : proxFolder2->PackSize; + else + n2 = GetUInt64Prop(_agentSpec->GetArchive(), arcIndex2, kpidPackSize); + return MyCompare(n1, n2); + } + + if (propID == kpidCRC) + { + UInt64 n1, n2; + if (!isDir1 || !prox1.Ignore) + n1 = GetUInt64Prop(_agentSpec->GetArchive(), arcIndex1, kpidCRC); + else + n1 = proxFolder1->Crc; + if (!isDir2 || !prox2.Ignore) + n2 = GetUInt64Prop(_agentSpec->GetArchive(), arcIndex2, kpidCRC); + else + n2 = proxFolder2->Crc; + return MyCompare(n1, n2); + } + + if (propIsRaw) + return CompareRawProps(_agentSpec->_archiveLink.GetArchiveGetRawProps(), arcIndex1, arcIndex2, propID); + + return CompareItems3(index1, index2, propID); +} + + +Z7_COM7F_IMF2(Int32, CAgentFolder::CompareItems(UInt32 index1, UInt32 index2, PROPID propID, Int32 propIsRaw)) +{ + try { + if (_proxy2) + return CompareItems2(index1, index2, propID, propIsRaw); + + unsigned realIndex1, realIndex2; + const CProxyDir *dir1, *dir2; + + if (_flatMode) + { + const CProxyItem &item1 = _items[index1]; + const CProxyItem &item2 = _items[index2]; + dir1 = &_proxy->Dirs[item1.DirIndex]; + dir2 = &_proxy->Dirs[item2.DirIndex]; + realIndex1 = item1.Index; + realIndex2 = item2.Index; + } + else + { + dir2 = dir1 = &_proxy->Dirs[_proxyDirIndex]; + realIndex1 = index1; + realIndex2 = index2; + } + + if (propID == kpidPrefix) + { + if (!_flatMode) + return 0; + UString prefix1, prefix2; + GetPrefix(index1, prefix1); + GetPrefix(index2, prefix2); + return CompareFileNames_ForFolderList(prefix1, prefix2); + } + + UInt32 arcIndex1; + UInt32 arcIndex2; + + const CProxyDir *proxFolder1 = NULL; + const CProxyDir *proxFolder2 = NULL; + + if (realIndex1 < dir1->SubDirs.Size()) + { + proxFolder1 = &_proxy->Dirs[dir1->SubDirs[realIndex1]]; + arcIndex1 = (unsigned)proxFolder1->ArcIndex; + } + else + arcIndex1 = dir1->SubFiles[realIndex1 - dir1->SubDirs.Size()]; + + if (realIndex2 < dir2->SubDirs.Size()) + { + proxFolder2 = &_proxy->Dirs[dir2->SubDirs[realIndex2]]; + arcIndex2 = (unsigned)proxFolder2->ArcIndex; + } + else + arcIndex2 = dir2->SubFiles[realIndex2 - dir2->SubDirs.Size()]; + + if (propID == kpidName) + return CompareFileNames_ForFolderList( + proxFolder1 ? proxFolder1->Name : _proxy->Files[arcIndex1].Name, + proxFolder2 ? proxFolder2->Name : _proxy->Files[arcIndex2].Name); + + if (propID == kpidExtension) + return CompareFileNames_ForFolderList( + GetExtension(proxFolder1 ? proxFolder1->Name : _proxy->Files[arcIndex1].Name), + GetExtension(proxFolder2 ? proxFolder2->Name : _proxy->Files[arcIndex2].Name)); + + if (propID == kpidIsDir) + { + if (proxFolder1) + return proxFolder2 ? 0 : -1; + return proxFolder2 ? 1 : 0; + } + + if (propID == kpidNumSubDirs) + { + UInt32 n1 = 0; + UInt32 n2 = 0; + if (proxFolder1) n1 = proxFolder1->NumSubDirs; + if (proxFolder2) n2 = proxFolder2->NumSubDirs; + return MyCompare(n1, n2); + } + + if (propID == kpidNumSubFiles) + { + UInt32 n1 = 0; + UInt32 n2 = 0; + if (proxFolder1) n1 = proxFolder1->NumSubFiles; + if (proxFolder2) n2 = proxFolder2->NumSubFiles; + return MyCompare(n1, n2); + } + + if (propID == kpidSize) + { + UInt64 n1, n2; + if (proxFolder1) + n1 = _flatMode ? 0 : proxFolder1->Size; + else + n1 = GetUInt64Prop(_agentSpec->GetArchive(), arcIndex1, kpidSize); + if (proxFolder2) + n2 = _flatMode ? 0 : proxFolder2->Size; + else + n2 = GetUInt64Prop(_agentSpec->GetArchive(), arcIndex2, kpidSize); + return MyCompare(n1, n2); + } + + if (propID == kpidPackSize) + { + UInt64 n1, n2; + if (proxFolder1) + n1 = _flatMode ? 0 : proxFolder1->PackSize; + else + n1 = GetUInt64Prop(_agentSpec->GetArchive(), arcIndex1, kpidPackSize); + if (proxFolder2) + n2 = _flatMode ? 0 : proxFolder2->PackSize; + else + n2 = GetUInt64Prop(_agentSpec->GetArchive(), arcIndex2, kpidPackSize); + return MyCompare(n1, n2); + } + + if (propID == kpidCRC) + { + UInt64 n1, n2; + if (proxFolder1 && !proxFolder1->IsLeaf()) + n1 = proxFolder1->Crc; + else + n1 = GetUInt64Prop(_agentSpec->GetArchive(), arcIndex1, kpidCRC); + if (proxFolder2 && !proxFolder2->IsLeaf()) + n2 = proxFolder2->Crc; + else + n2 = GetUInt64Prop(_agentSpec->GetArchive(), arcIndex2, kpidCRC); + return MyCompare(n1, n2); + } + + if (propIsRaw) + { + bool isVirt1 = (proxFolder1 && !proxFolder1->IsLeaf()); + bool isVirt2 = (proxFolder2 && !proxFolder2->IsLeaf()); + if (isVirt1) + return isVirt2 ? 0 : -1; + if (isVirt2) + return 1; + return CompareRawProps(_agentSpec->_archiveLink.GetArchiveGetRawProps(), arcIndex1, arcIndex2, propID); + } + + return CompareItems3(index1, index2, propID); + + } catch(...) { return 0; } +} + + +HRESULT CAgentFolder::BindToFolder_Internal(unsigned proxyDirIndex, IFolderFolder **resultFolder) +{ + /* + CMyComPtr parentFolder; + + if (_proxy2) + { + const CProxyDir2 &dir = _proxy2->Dirs[proxyDirIndex]; + int par = _proxy2->GetParentFolderOfFile(dir.ArcIndex); + if (par != (int)_proxyDirIndex) + { + RINOK(BindToFolder_Internal(par, &parentFolder)); + } + else + parentFolder = this; + } + else + { + const CProxyDir &dir = _proxy->Dirs[proxyDirIndex]; + if (dir.Parent != (int)_proxyDirIndex) + { + RINOK(BindToFolder_Internal(dir.Parent, &parentFolder)); + } + else + parentFolder = this; + } + */ + CAgentFolder *folderSpec = new CAgentFolder; + CMyComPtr agentFolder = folderSpec; + folderSpec->Init(_proxy, _proxy2, proxyDirIndex, /* parentFolder, */ _agentSpec); + *resultFolder = agentFolder.Detach(); + return S_OK; +} + +Z7_COM7F_IMF(CAgentFolder::BindToFolder(UInt32 index, IFolderFolder **resultFolder)) +{ + COM_TRY_BEGIN + if (_proxy2) + { + SET_realIndex_AND_dir_2 + const unsigned arcIndex = dir->Items[realIndex]; + const CProxyFile2 &item = _proxy2->Files[arcIndex]; + if (!item.IsDir()) + return E_INVALIDARG; + return BindToFolder_Internal((unsigned)item.DirIndex, resultFolder); + } + SET_realIndex_AND_dir + if (realIndex >= (UInt32)dir->SubDirs.Size()) + return E_INVALIDARG; + return BindToFolder_Internal(dir->SubDirs[realIndex], resultFolder); + COM_TRY_END +} + +Z7_COM7F_IMF(CAgentFolder::BindToFolder(const wchar_t *name, IFolderFolder **resultFolder)) +{ + COM_TRY_BEGIN + if (_proxy2) + { + const int index = _proxy2->FindItem(_proxyDirIndex, name, true); + if (index == -1) + return E_INVALIDARG; + return BindToFolder_Internal((unsigned)_proxy2->Files[_proxy2->Dirs[_proxyDirIndex].Items[index]].DirIndex, resultFolder); + } + const int index = _proxy->FindSubDir(_proxyDirIndex, name); + if (index == -1) + return E_INVALIDARG; + return BindToFolder_Internal((unsigned)index, resultFolder); + COM_TRY_END +} + + + +// ---------- IFolderAltStreams ---------- + +HRESULT CAgentFolder::BindToAltStreams_Internal(unsigned proxyDirIndex, IFolderFolder **resultFolder) +{ + *resultFolder = NULL; + if (!_proxy2) + return S_OK; + + /* + CMyComPtr parentFolder; + + int par = _proxy2->GetParentFolderOfFile(_proxy2->Dirs[proxyDirIndex].ArcIndex); + if (par != (int)_proxyDirIndex) + { + RINOK(BindToFolder_Internal(par, &parentFolder)); + if (!parentFolder) + return S_OK; + } + else + parentFolder = this; + */ + + CAgentFolder *folderSpec = new CAgentFolder; + CMyComPtr agentFolder = folderSpec; + folderSpec->Init(_proxy, _proxy2, proxyDirIndex, /* parentFolder, */ _agentSpec); + *resultFolder = agentFolder.Detach(); + return S_OK; +} + +Z7_COM7F_IMF(CAgentFolder::BindToAltStreams(UInt32 index, IFolderFolder **resultFolder)) +{ + COM_TRY_BEGIN + + *resultFolder = NULL; + + if (!_proxy2) + return S_OK; + + if (_proxy2->IsAltDir(_proxyDirIndex)) + return S_OK; + + { + if (index == (UInt32)(Int32)-1) + { + unsigned altDirIndex; + // IFolderFolder *parentFolder; + + if (_proxyDirIndex == k_Proxy2_RootDirIndex) + { + altDirIndex = k_Proxy2_AltRootDirIndex; + // parentFolder = this; // we want to use Root dir as parent for alt root + } + else + { + const unsigned arcIndex = (unsigned)_proxy2->Dirs[_proxyDirIndex].ArcIndex; + const CProxyFile2 &item = _proxy2->Files[arcIndex]; + if (item.AltDirIndex == -1) + return S_OK; + altDirIndex = (unsigned)item.AltDirIndex; + // parentFolder = _parentFolder; + } + + CAgentFolder *folderSpec = new CAgentFolder; + CMyComPtr agentFolder = folderSpec; + folderSpec->Init(_proxy, _proxy2, altDirIndex, /* parentFolder, */ _agentSpec); + *resultFolder = agentFolder.Detach(); + return S_OK; + } + + SET_realIndex_AND_dir_2 + const unsigned arcIndex = dir->Items[realIndex]; + const CProxyFile2 &item = _proxy2->Files[arcIndex]; + if (item.AltDirIndex == -1) + return S_OK; + return BindToAltStreams_Internal((unsigned)item.AltDirIndex, resultFolder); + } + + COM_TRY_END +} + +Z7_COM7F_IMF(CAgentFolder::BindToAltStreams(const wchar_t *name, IFolderFolder **resultFolder)) +{ + COM_TRY_BEGIN + + *resultFolder = NULL; + + if (!_proxy2) + return S_OK; + + if (_proxy2->IsAltDir(_proxyDirIndex)) + return S_OK; + + if (name[0] == 0) + return BindToAltStreams((UInt32)(Int32)-1, resultFolder); + + { + const CProxyDir2 &dir = _proxy2->Dirs[_proxyDirIndex]; + FOR_VECTOR (i, dir.Items) + { + const CProxyFile2 &file = _proxy2->Files[dir.Items[i]]; + if (file.AltDirIndex != -1) + if (CompareFileNames(file.Name, name) == 0) + return BindToAltStreams_Internal((unsigned)file.AltDirIndex, resultFolder); + } + return E_INVALIDARG; + } + COM_TRY_END +} + +Z7_COM7F_IMF(CAgentFolder::AreAltStreamsSupported(UInt32 index, Int32 *isSupported)) +{ + *isSupported = BoolToInt(false); + + if (!_proxy2) + return S_OK; + + if (_proxy2->IsAltDir(_proxyDirIndex)) + return S_OK; + + unsigned arcIndex; + + if (index == (UInt32)(Int32)-1) + { + if (_proxyDirIndex == k_Proxy2_RootDirIndex) + { + *isSupported = BoolToInt(true); + return S_OK; + } + arcIndex = (unsigned)_proxy2->Dirs[_proxyDirIndex].ArcIndex; + } + else + { + SET_realIndex_AND_dir_2 + arcIndex = dir->Items[realIndex]; + } + + if (_proxy2->Files[arcIndex].AltDirIndex != -1) + *isSupported = BoolToInt(true); + return S_OK; +} + + +Z7_COM7F_IMF(CAgentFolder::BindToParentFolder(IFolderFolder **resultFolder)) +{ + COM_TRY_BEGIN + /* + CMyComPtr parentFolder = _parentFolder; + *resultFolder = parentFolder.Detach(); + */ + *resultFolder = NULL; + + unsigned proxyDirIndex; + + if (_proxy2) + { + if (_proxyDirIndex == k_Proxy2_RootDirIndex) + return S_OK; + if (_proxyDirIndex == k_Proxy2_AltRootDirIndex) + proxyDirIndex = k_Proxy2_RootDirIndex; + else + { + const CProxyDir2 &fold = _proxy2->Dirs[_proxyDirIndex]; + const CProxyFile2 &file = _proxy2->Files[(unsigned)fold.ArcIndex]; + const int parentIndex = file.Parent; + if (parentIndex == -1) + proxyDirIndex = k_Proxy2_RootDirIndex; + else + proxyDirIndex = (unsigned)_proxy2->Files[(unsigned)parentIndex].DirIndex; + } + } + else + { + const int parent = _proxy->Dirs[_proxyDirIndex].ParentDir; + if (parent == -1) + return S_OK; + proxyDirIndex = (unsigned)parent; + } + + CAgentFolder *folderSpec = new CAgentFolder; + CMyComPtr agentFolder = folderSpec; + folderSpec->Init(_proxy, _proxy2, proxyDirIndex, /* parentFolder, */ _agentSpec); + *resultFolder = agentFolder.Detach(); + + return S_OK; + COM_TRY_END +} + +Z7_COM7F_IMF(CAgentFolder::GetStream(UInt32 index, ISequentialInStream **stream)) +{ + Z7_DECL_CMyComPtr_QI_FROM( + IInArchiveGetStream, + getStream, _agentSpec->GetArchive()) + if (!getStream) + return S_OK; + + UInt32 arcIndex; + if (_proxy2) + { + SET_realIndex_AND_dir_2 + arcIndex = dir->Items[realIndex]; + } + else + { + SET_realIndex_AND_dir + + if (realIndex < dir->SubDirs.Size()) + { + const CProxyDir &item = _proxy->Dirs[dir->SubDirs[realIndex]]; + if (!item.IsLeaf()) + return S_OK; + arcIndex = (unsigned)item.ArcIndex; + } + else + arcIndex = dir->SubFiles[realIndex - dir->SubDirs.Size()]; + } + return getStream->GetStream(arcIndex, stream); +} + +// static const unsigned k_FirstOptionalProp = 2; + +static const PROPID kProps[] = +{ + kpidNumSubDirs, + kpidNumSubFiles, + + // kpidNumAltStreams, + kpidPrefix +}; + +struct CArchiveItemPropertyTemp +{ + UString Name; + PROPID ID; + VARTYPE Type; +}; + +Z7_COM7F_IMF(CAgentFolder::GetNumberOfProperties(UInt32 *numProps)) +{ + COM_TRY_BEGIN + RINOK(_agentSpec->GetArchive()->GetNumberOfProperties(numProps)) + *numProps += Z7_ARRAY_SIZE(kProps); + if (!_flatMode) + (*numProps)--; + /* + if (!_agentSpec->ThereIsAltStreamProp) + (*numProps)--; + */ + /* + bool thereIsPathProp = _proxy2 ? + _agentSpec->_proxy2->ThereIsPathProp : + _agentSpec->_proxy->ThereIsPathProp; + */ + + // if there is kpidPath, we change kpidPath to kpidName + // if there is no kpidPath, we add kpidName. + if (!_agentSpec->ThereIsPathProp) + (*numProps)++; + return S_OK; + COM_TRY_END +} + +Z7_COM7F_IMF(CAgentFolder::GetPropertyInfo(UInt32 index, BSTR *name, PROPID *propID, VARTYPE *varType)) +{ + COM_TRY_BEGIN + UInt32 numProps; + _agentSpec->GetArchive()->GetNumberOfProperties(&numProps); + + /* + bool thereIsPathProp = _proxy2 ? + _agentSpec->_proxy2->ThereIsPathProp : + _agentSpec->_proxy->ThereIsPathProp; + */ + + if (!_agentSpec->ThereIsPathProp) + { + if (index == 0) + { + *propID = kpidName; + *varType = VT_BSTR; + *name = NULL; + return S_OK; + } + index--; + } + + if (index < numProps) + { + RINOK(_agentSpec->GetArchive()->GetPropertyInfo(index, name, propID, varType)) + if (*propID == kpidPath) + *propID = kpidName; + } + else + { + index -= numProps; + /* + if (index >= k_FirstOptionalProp) + { + if (!_agentSpec->ThereIsAltStreamProp) + index++; + } + */ + *propID = kProps[index]; + *varType = k7z_PROPID_To_VARTYPE[(unsigned)*propID]; + *name = NULL; + } + return S_OK; + COM_TRY_END +} + +static const PROPID kFolderProps[] = +{ + kpidSize, + kpidPackSize, + kpidNumSubDirs, + kpidNumSubFiles, + kpidCRC +}; + +Z7_COM7F_IMF(CAgentFolder::GetFolderProperty(PROPID propID, PROPVARIANT *value)) +{ + COM_TRY_BEGIN + + NWindows::NCOM::CPropVariant prop; + + if (propID == kpidReadOnly) + { + if (_agentSpec->Is_Attrib_ReadOnly()) + prop = true; + else + prop = _agentSpec->IsThere_ReadOnlyArc(); + } + else if (propID == kpidIsHash) + { + prop = _agentSpec->_isHashHandler; + } + else if (_proxy2) + { + const CProxyDir2 &dir = _proxy2->Dirs[_proxyDirIndex]; + if (propID == kpidName) + { + if (dir.ArcIndex != -1) + prop = _proxy2->Files[(unsigned)dir.ArcIndex].Name; + } + else if (propID == kpidPath) + { + bool isAltStreamFolder = false; + prop = _proxy2->GetDirPath_as_Prefix(_proxyDirIndex, isAltStreamFolder); + } + else switch (propID) + { + case kpidSize: prop = dir.Size; break; + case kpidPackSize: prop = dir.PackSize; break; + case kpidNumSubDirs: prop = dir.NumSubDirs; break; + case kpidNumSubFiles: prop = dir.NumSubFiles; break; + // case kpidName: prop = dir.Name; break; + // case kpidPath: prop = _proxy2->GetFullPathPrefix(_proxyDirIndex); break; + case kpidType: prop = UString("7-Zip.") + _agentSpec->ArchiveType; break; + case kpidCRC: if (dir.CrcIsDefined) { prop = dir.Crc; } break; + } + + } + else + { + const CProxyDir &dir = _proxy->Dirs[_proxyDirIndex]; + switch (propID) + { + case kpidSize: prop = dir.Size; break; + case kpidPackSize: prop = dir.PackSize; break; + case kpidNumSubDirs: prop = dir.NumSubDirs; break; + case kpidNumSubFiles: prop = dir.NumSubFiles; break; + case kpidName: prop = dir.Name; break; + case kpidPath: prop = _proxy->GetDirPath_as_Prefix(_proxyDirIndex); break; + case kpidType: prop = UString("7-Zip.") + _agentSpec->ArchiveType; break; + case kpidCRC: if (dir.CrcIsDefined) prop = dir.Crc; break; + } + } + prop.Detach(value); + return S_OK; + COM_TRY_END +} + +Z7_COM7F_IMF(CAgentFolder::GetNumberOfFolderProperties(UInt32 *numProps)) +{ + *numProps = Z7_ARRAY_SIZE(kFolderProps); + return S_OK; +} + +IMP_IFolderFolder_GetProp( + CAgentFolder::GetFolderPropertyInfo, + kFolderProps) + +Z7_COM7F_IMF(CAgentFolder::GetParent(UInt32 /* index */, UInt32 * /* parent */, UInt32 * /* parentType */)) +{ + return E_FAIL; +} + + +Z7_COM7F_IMF(CAgentFolder::GetNumRawProps(UInt32 *numProps)) +{ + IArchiveGetRawProps *rawProps = _agentSpec->_archiveLink.GetArchiveGetRawProps(); + if (rawProps) + return rawProps->GetNumRawProps(numProps); + *numProps = 0; + return S_OK; +} + +Z7_COM7F_IMF(CAgentFolder::GetRawPropInfo(UInt32 index, BSTR *name, PROPID *propID)) +{ + IArchiveGetRawProps *rawProps = _agentSpec->_archiveLink.GetArchiveGetRawProps(); + if (rawProps) + return rawProps->GetRawPropInfo(index, name, propID); + return E_FAIL; +} + +Z7_COM7F_IMF(CAgentFolder::GetRawProp(UInt32 index, PROPID propID, const void **data, UInt32 *dataSize, UInt32 *propType)) +{ + IArchiveGetRawProps *rawProps = _agentSpec->_archiveLink.GetArchiveGetRawProps(); + if (rawProps) + { + unsigned arcIndex; + if (_proxy2) + { + SET_realIndex_AND_dir_2 + arcIndex = dir->Items[realIndex]; + } + else + { + SET_realIndex_AND_dir + if (realIndex < dir->SubDirs.Size()) + { + const CProxyDir &item = _proxy->Dirs[dir->SubDirs[realIndex]]; + if (!item.IsLeaf()) + { + *data = NULL; + *dataSize = 0; + *propType = 0; + return S_OK; + } + arcIndex = (unsigned)item.ArcIndex; + } + else + arcIndex = dir->SubFiles[realIndex - dir->SubDirs.Size()]; + } + return rawProps->GetRawProp(arcIndex, propID, data, dataSize, propType); + } + *data = NULL; + *dataSize = 0; + *propType = 0; + return S_OK; +} + +Z7_COM7F_IMF(CAgentFolder::GetFolderArcProps(IFolderArcProps **object)) +{ + CMyComPtr temp = _agentSpec; + *object = temp.Detach(); + return S_OK; +} + + +Z7_COM7F_IMF(CAgentFolder::SetFlatMode(Int32 flatMode)) +{ + _flatMode = IntToBool(flatMode); + return S_OK; +} + + +int CAgentFolder::GetRealIndex(unsigned index) const +{ + if (!_flatMode) + { + if (_proxy2) + return (int)_proxy2->GetRealIndex(_proxyDirIndex, index); + else + return _proxy->GetRealIndex(_proxyDirIndex, index); + } + { + const CProxyItem &item = _items[index]; + if (_proxy2) + { + const CProxyDir2 *dir = &_proxy2->Dirs[item.DirIndex]; + return (int)dir->Items[item.Index]; + } + else + { + const CProxyDir *dir = &_proxy->Dirs[item.DirIndex]; + const unsigned realIndex = item.Index; + if (realIndex < dir->SubDirs.Size()) + { + const CProxyDir &f = _proxy->Dirs[dir->SubDirs[realIndex]]; + if (!f.IsLeaf()) + return -1; + return f.ArcIndex; + } + return (int)dir->SubFiles[realIndex - dir->SubDirs.Size()]; + } + } +} + +void CAgentFolder::GetRealIndices(const UInt32 *indices, UInt32 numItems, bool includeAltStreams, bool includeFolderSubItemsInFlatMode, CUIntVector &realIndices) const +{ + if (!_flatMode) + { + if (_proxy2) + _proxy2->GetRealIndices(_proxyDirIndex, indices, numItems, includeAltStreams, realIndices); + else + _proxy->GetRealIndices(_proxyDirIndex, indices, numItems, realIndices); + return; + } + + realIndices.Clear(); + + for (UInt32 i = 0; i < numItems; i++) + { + const CProxyItem &item = _items[indices[i]]; + if (_proxy2) + { + const CProxyDir2 *dir = &_proxy2->Dirs[item.DirIndex]; + _proxy2->AddRealIndices_of_ArcItem(dir->Items[item.Index], includeAltStreams, realIndices); + continue; + } + UInt32 arcIndex; + { + const CProxyDir *dir = &_proxy->Dirs[item.DirIndex]; + unsigned realIndex = item.Index; + if (realIndex < dir->SubDirs.Size()) + { + if (includeFolderSubItemsInFlatMode) + { + _proxy->AddRealIndices(dir->SubDirs[realIndex], realIndices); + continue; + } + const CProxyDir &f = _proxy->Dirs[dir->SubDirs[realIndex]]; + if (!f.IsLeaf()) + continue; + arcIndex = (unsigned)f.ArcIndex; + } + else + arcIndex = dir->SubFiles[realIndex - dir->SubDirs.Size()]; + } + realIndices.Add(arcIndex); + } + + HeapSort(realIndices.NonConstData(), realIndices.Size()); +} + +Z7_COM7F_IMF(CAgentFolder::Extract(const UInt32 *indices, + UInt32 numItems, + Int32 includeAltStreams, + Int32 replaceAltStreamColon, + NExtract::NPathMode::EEnum pathMode, + NExtract::NOverwriteMode::EEnum overwriteMode, + const wchar_t *path, + Int32 testMode, + IFolderArchiveExtractCallback *extractCallback2)) +{ + COM_TRY_BEGIN + + if (!testMode && _agentSpec->_isHashHandler) + return E_NOTIMPL; + + CArchiveExtractCallback *extractCallbackSpec = new CArchiveExtractCallback; + CMyComPtr extractCallback = extractCallbackSpec; + UStringVector pathParts; + bool isAltStreamFolder = false; + if (_proxy2) + _proxy2->GetDirPathParts(_proxyDirIndex, pathParts, isAltStreamFolder); + else + _proxy->GetDirPathParts(_proxyDirIndex, pathParts); + + /* + if (_flatMode) + pathMode = NExtract::NPathMode::kNoPathnames; + */ + + extractCallbackSpec->InitForMulti( + false, // multiArchives + pathMode, + overwriteMode, + _zoneMode, + k_keepEmptyDirPrefixes); + + if (extractCallback2) + extractCallback2->SetTotal(_agentSpec->GetArc().GetEstmatedPhySize()); + + FString pathU; + if (path) + { + pathU = us2fs(path); + if (!pathU.IsEmpty() + && !NFile::NName::IsAltStreamPrefixWithColon(path)) + { + NFile::NName::NormalizeDirPathPrefix(pathU); + NFile::NDir::CreateComplexDir(pathU); + } + } + + CExtractNtOptions extractNtOptions; + extractNtOptions.AltStreams.Val = IntToBool(includeAltStreams); // change it!!! + extractNtOptions.AltStreams.Def = true; + + extractNtOptions.ReplaceColonForAltStream = IntToBool(replaceAltStreamColon); + + extractCallbackSpec->InitBeforeNewArchive(); + + #if defined(_WIN32) && !defined(UNDER_CE) + if (_zoneMode != NExtract::NZoneIdMode::kNone) + { + ReadZoneFile_Of_BaseFile(us2fs(_agentSpec->_archiveFilePath), extractCallbackSpec->ZoneBuf); + if (_zoneBuf.Size() != 0) + extractCallbackSpec->ZoneBuf = _zoneBuf; + } + #endif + + extractCallbackSpec->Init( + extractNtOptions, + NULL, &_agentSpec->GetArc(), + extractCallback2, + false, // stdOutMode + IntToBool(testMode), + pathU, + pathParts, isAltStreamFolder, + (UInt64)(Int64)-1); + + if (_proxy2) + extractCallbackSpec->SetBaseParentFolderIndex((unsigned)_proxy2->Dirs[_proxyDirIndex].ArcIndex); + + // do we need another base folder for subfolders ? + extractCallbackSpec->DirPathPrefix_for_HashFiles = _agentSpec->_hashBaseFolderPrefix; + + CUIntVector realIndices; + GetRealIndices(indices, numItems, IntToBool(includeAltStreams), + false, // includeFolderSubItemsInFlatMode + realIndices); // + + #ifdef SUPPORT_LINKS + + if (!testMode) + { + RINOK(extractCallbackSpec->PrepareHardLinks(&realIndices)) + } + + #endif + + { + CArchiveExtractCallback_Closer ecsCloser(extractCallbackSpec); + + HRESULT res = _agentSpec->GetArchive()->Extract(realIndices.ConstData(), + realIndices.Size(), testMode, extractCallback); + + const HRESULT res2 = ecsCloser.Close(); + if (res == S_OK) + res = res2; + return res; + } + + COM_TRY_END +} + +///////////////////////////////////////// +// CAgent + +CAgent::CAgent(): + _proxy(NULL), + _proxy2(NULL), + _updatePathPrefix_is_AltFolder(false), + _isDeviceFile(false), + _isHashHandler(false) +{ +} + +CAgent::~CAgent() +{ + if (_proxy) + delete _proxy; + if (_proxy2) + delete _proxy2; +} + +bool CAgent::CanUpdate() const +{ + // FAR plugin uses empty agent to create new archive !!! + if (_archiveLink.Arcs.Size() == 0) + return true; + if (_isDeviceFile) + return false; + if (_archiveLink.Arcs.Size() != 1) + return false; + if (_archiveLink.Arcs[0].ErrorInfo.ThereIsTail) + return false; + return true; +} + +Z7_COM7F_IMF(CAgent::Open( + IInStream *inStream, + const wchar_t *filePath, + const wchar_t *arcFormat, + BSTR *archiveType, + IArchiveOpenCallback *openArchiveCallback)) +{ + COM_TRY_BEGIN + _archiveFilePath = filePath; + _hashBaseFolderPrefix.Empty(); + _attrib = 0; + _isDeviceFile = false; + _isHashHandler = false; + NFile::NFind::CFileInfo fi; + if (!inStream) + { + if (!fi.Find(us2fs(_archiveFilePath))) + return GetLastError_noZero_HRESULT(); + if (fi.IsDir()) + return E_FAIL; + _attrib = fi.Attrib; + _isDeviceFile = fi.IsDevice; + FString dirPrefix, fileName; + if (NFile::NDir::GetFullPathAndSplit(us2fs(_archiveFilePath), dirPrefix, fileName)) + { + NFile::NName::NormalizeDirPathPrefix(dirPrefix); + _hashBaseFolderPrefix = dirPrefix; + } + } + CArcInfoEx archiverInfo0, archiverInfo1; + + RINOK(LoadGlobalCodecs()) + + CObjectVector types; + if (!ParseOpenTypes(*g_CodecsObj, arcFormat, types)) + return S_FALSE; + + /* + CObjectVector optProps; + if (Read_ShowDeleted()) + { + COptionalOpenProperties &optPair = optProps.AddNew(); + optPair.FormatName = "ntfs"; + // optPair.Props.AddNew().Name = "LS"; + optPair.Props.AddNew().Name = "LD"; + } + */ + + COpenOptions options; + options.props = NULL; + options.codecs = g_CodecsObj; + options.types = &types; + CIntVector exl; + options.excludedFormats = &exl; + options.stdInMode = false; + options.stream = inStream; + options.filePath = _archiveFilePath; + options.callback = openArchiveCallback; + + HRESULT res = _archiveLink.Open(options); + + if (!_archiveLink.Arcs.IsEmpty()) + { + CArc &arc = _archiveLink.Arcs.Back(); + if (!inStream) + { + arc.MTime.Set_From_FiTime(fi.MTime); + arc.MTime.Def = !fi.IsDevice; + } + + ArchiveType = GetTypeOfArc(arc); + if (archiveType) + { + RINOK(StringToBstr(ArchiveType, archiveType)) + } + + if (arc.IsHashHandler(options)) + _isHashHandler = true; + } + + return res; + + COM_TRY_END +} + + +Z7_COM7F_IMF(CAgent::ReOpen(IArchiveOpenCallback *openArchiveCallback)) +{ + COM_TRY_BEGIN + if (_proxy2) + { + delete _proxy2; + _proxy2 = NULL; + } + if (_proxy) + { + delete _proxy; + _proxy = NULL; + } + + CObjectVector incl; + CIntVector exl; + + COpenOptions options; + options.props = NULL; + options.codecs = g_CodecsObj; + options.types = &incl; + options.excludedFormats = &exl; + options.stdInMode = false; + options.filePath = _archiveFilePath; + options.callback = openArchiveCallback; + + RINOK(_archiveLink.ReOpen(options)) + return ReadItems(); + COM_TRY_END +} + +Z7_COM7F_IMF(CAgent::Close()) +{ + COM_TRY_BEGIN + return _archiveLink.Close(); + COM_TRY_END +} + +/* +Z7_COM7F_IMF(CAgent::EnumProperties(IEnumSTATPROPSTG **EnumProperties) +{ + return _archive->EnumProperties(EnumProperties); +} +*/ + +HRESULT CAgent::ReadItems() +{ + if (_proxy || _proxy2) + return S_OK; + + const CArc &arc = GetArc(); + bool useProxy2 = (arc.GetRawProps && arc.IsTree); + + // useProxy2 = false; + + if (useProxy2) + _proxy2 = new CProxyArc2(); + else + _proxy = new CProxyArc(); + + { + ThereIsPathProp = false; + // ThereIsAltStreamProp = false; + UInt32 numProps; + arc.Archive->GetNumberOfProperties(&numProps); + for (UInt32 i = 0; i < numProps; i++) + { + CMyComBSTR name; + PROPID propID; + VARTYPE varType; + RINOK(arc.Archive->GetPropertyInfo(i, &name, &propID, &varType)) + if (propID == kpidPath) + ThereIsPathProp = true; + /* + if (propID == kpidIsAltStream) + ThereIsAltStreamProp = true; + */ + } + } + + if (_proxy2) + return _proxy2->Load(GetArc(), NULL); + return _proxy->Load(GetArc(), NULL); +} + +Z7_COM7F_IMF(CAgent::BindToRootFolder(IFolderFolder **resultFolder)) +{ + COM_TRY_BEGIN + if (!_archiveLink.Arcs.IsEmpty()) + { + RINOK(ReadItems()) + } + CAgentFolder *folderSpec = new CAgentFolder; + CMyComPtr rootFolder = folderSpec; + folderSpec->Init(_proxy, _proxy2, k_Proxy_RootDirIndex, /* NULL, */ this); + *resultFolder = rootFolder.Detach(); + return S_OK; + COM_TRY_END +} + +Z7_COM7F_IMF(CAgent::Extract( + NExtract::NPathMode::EEnum pathMode, + NExtract::NOverwriteMode::EEnum overwriteMode, + const wchar_t *path, + Int32 testMode, + IFolderArchiveExtractCallback *extractCallback2)) +{ + COM_TRY_BEGIN + + if (!testMode && _isHashHandler) + return E_NOTIMPL; + + CArchiveExtractCallback *extractCallbackSpec = new CArchiveExtractCallback; + CMyComPtr extractCallback = extractCallbackSpec; + extractCallbackSpec->InitForMulti( + false, // multiArchives + pathMode, + overwriteMode, + NExtract::NZoneIdMode::kNone, + k_keepEmptyDirPrefixes); + + CExtractNtOptions extractNtOptions; + extractNtOptions.AltStreams.Val = true; // change it!!! + extractNtOptions.AltStreams.Def = true; // change it!!! + extractNtOptions.ReplaceColonForAltStream = false; // change it!!! + + extractCallbackSpec->Init( + extractNtOptions, + NULL, &GetArc(), + extractCallback2, + false, // stdOutMode + IntToBool(testMode), + us2fs(path), + UStringVector(), false, + (UInt64)(Int64)-1); + + extractCallbackSpec->DirPathPrefix_for_HashFiles = _hashBaseFolderPrefix; + + #ifdef SUPPORT_LINKS + + if (!testMode) + { + RINOK(extractCallbackSpec->PrepareHardLinks(NULL)) // NULL means all items + } + + #endif + + return GetArchive()->Extract(NULL, (UInt32)(Int32)-1, testMode, extractCallback); + COM_TRY_END +} + +Z7_COM7F_IMF(CAgent::GetNumberOfProperties(UInt32 *numProps)) +{ + COM_TRY_BEGIN + return GetArchive()->GetNumberOfProperties(numProps); + COM_TRY_END +} + +Z7_COM7F_IMF(CAgent::GetPropertyInfo(UInt32 index, + BSTR *name, PROPID *propID, VARTYPE *varType)) +{ + COM_TRY_BEGIN + RINOK(GetArchive()->GetPropertyInfo(index, name, propID, varType)) + if (*propID == kpidPath) + *propID = kpidName; + return S_OK; + COM_TRY_END +} + +Z7_COM7F_IMF(CAgent::GetArcNumLevels(UInt32 *numLevels)) +{ + *numLevels = _archiveLink.Arcs.Size(); + return S_OK; +} + +Z7_COM7F_IMF(CAgent::GetArcProp(UInt32 level, PROPID propID, PROPVARIANT *value)) +{ + COM_TRY_BEGIN + NWindows::NCOM::CPropVariant prop; + if (level > (UInt32)_archiveLink.Arcs.Size()) + return E_INVALIDARG; + if (level == (UInt32)_archiveLink.Arcs.Size()) + { + switch (propID) + { + case kpidPath: + if (!_archiveLink.NonOpen_ArcPath.IsEmpty()) + prop = _archiveLink.NonOpen_ArcPath; + break; + case kpidErrorType: + if (_archiveLink.NonOpen_ErrorInfo.ErrorFormatIndex >= 0) + prop = g_CodecsObj->Formats[_archiveLink.NonOpen_ErrorInfo.ErrorFormatIndex].Name; + break; + case kpidErrorFlags: + { + UInt32 flags = _archiveLink.NonOpen_ErrorInfo.GetErrorFlags(); + if (flags != 0) + prop = flags; + break; + } + case kpidWarningFlags: + { + UInt32 flags = _archiveLink.NonOpen_ErrorInfo.GetWarningFlags(); + if (flags != 0) + prop = flags; + break; + } + } + } + else + { + const CArc &arc = _archiveLink.Arcs[level]; + switch (propID) + { + case kpidType: prop = GetTypeOfArc(arc); break; + case kpidPath: prop = arc.Path; break; + case kpidErrorType: + if (arc.ErrorInfo.ErrorFormatIndex >= 0) + prop = g_CodecsObj->Formats[arc.ErrorInfo.ErrorFormatIndex].Name; + break; + case kpidErrorFlags: + { + const UInt32 flags = arc.ErrorInfo.GetErrorFlags(); + if (flags != 0) + prop = flags; + break; + } + case kpidWarningFlags: + { + const UInt32 flags = arc.ErrorInfo.GetWarningFlags(); + if (flags != 0) + prop = flags; + break; + } + case kpidOffset: + { + const Int64 v = arc.GetGlobalOffset(); + if (v != 0) + prop.Set_Int64(v); + break; + } + case kpidTailSize: + { + if (arc.ErrorInfo.TailSize != 0) + prop = arc.ErrorInfo.TailSize; + break; + } + default: return arc.Archive->GetArchiveProperty(propID, value); + } + } + prop.Detach(value); + return S_OK; + COM_TRY_END +} + +Z7_COM7F_IMF(CAgent::GetArcNumProps(UInt32 level, UInt32 *numProps)) +{ + return _archiveLink.Arcs[level].Archive->GetNumberOfArchiveProperties(numProps); +} + +Z7_COM7F_IMF(CAgent::GetArcPropInfo(UInt32 level, UInt32 index, BSTR *name, PROPID *propID, VARTYPE *varType)) +{ + return _archiveLink.Arcs[level].Archive->GetArchivePropertyInfo(index, name, propID, varType); +} + +// MainItemProperty +Z7_COM7F_IMF(CAgent::GetArcProp2(UInt32 level, PROPID propID, PROPVARIANT *value)) +{ + return _archiveLink.Arcs[level - 1].Archive->GetProperty(_archiveLink.Arcs[level].SubfileIndex, propID, value); +} + +Z7_COM7F_IMF(CAgent::GetArcNumProps2(UInt32 level, UInt32 *numProps)) +{ + return _archiveLink.Arcs[level - 1].Archive->GetNumberOfProperties(numProps); +} + +Z7_COM7F_IMF(CAgent::GetArcPropInfo2(UInt32 level, UInt32 index, BSTR *name, PROPID *propID, VARTYPE *varType)) +{ + return _archiveLink.Arcs[level - 1].Archive->GetPropertyInfo(index, name, propID, varType); +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Agent/Agent.h b/src/plugins/7zip/7za/cpp/7zip/UI/Agent/Agent.h new file mode 100644 index 00000000..a63e4598 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Agent/Agent.h @@ -0,0 +1,358 @@ +// Agent/Agent.h + +#ifndef ZIP7_INC_AGENT_AGENT_H +#define ZIP7_INC_AGENT_AGENT_H + +#include "../../../Common/MyCom.h" + +#include "../../../Windows/PropVariant.h" + +#include "../Common/LoadCodecs.h" +#include "../Common/OpenArchive.h" +#include "../Common/UpdateAction.h" + +#include "../FileManager/IFolder.h" + +#include "AgentProxy.h" +#include "IFolderArchive.h" + +extern CCodecs *g_CodecsObj; +HRESULT LoadGlobalCodecs(); +void FreeGlobalCodecs(); + +class CAgentFolder; + +Z7_PURE_INTERFACES_BEGIN + +#define Z7_IFACEM_IArchiveFolderInternal(x) \ + x(GetAgentFolder(CAgentFolder **agentFolder)) +Z7_IFACE_CONSTR_FOLDERARC(IArchiveFolderInternal, 0xC) + +Z7_PURE_INTERFACES_END + +struct CProxyItem +{ + unsigned DirIndex; + unsigned Index; +}; + +class CAgent; + +enum AGENT_OP +{ + AGENT_OP_Uni, + AGENT_OP_Delete, + AGENT_OP_CreateFolder, + AGENT_OP_Rename, + AGENT_OP_CopyFromFile, + AGENT_OP_Comment +}; + +class CAgentFolder Z7_final: + public IFolderFolder, + public IFolderAltStreams, + public IFolderProperties, + public IArchiveGetRawProps, + public IGetFolderArcProps, + public IFolderCompare, + public IFolderGetItemName, + public IArchiveFolder, + public IArchiveFolderInternal, + public IInArchiveGetStream, + public IFolderSetZoneIdMode, + public IFolderSetZoneIdFile, + public IFolderOperations, + public IFolderSetFlatMode, + public CMyUnknownImp +{ + Z7_COM_QI_BEGIN2(IFolderFolder) + Z7_COM_QI_ENTRY(IFolderAltStreams) + Z7_COM_QI_ENTRY(IFolderProperties) + Z7_COM_QI_ENTRY(IArchiveGetRawProps) + Z7_COM_QI_ENTRY(IGetFolderArcProps) + Z7_COM_QI_ENTRY(IFolderCompare) + Z7_COM_QI_ENTRY(IFolderGetItemName) + Z7_COM_QI_ENTRY(IArchiveFolder) + Z7_COM_QI_ENTRY(IArchiveFolderInternal) + Z7_COM_QI_ENTRY(IInArchiveGetStream) + Z7_COM_QI_ENTRY(IFolderSetZoneIdMode) + Z7_COM_QI_ENTRY(IFolderSetZoneIdFile) + Z7_COM_QI_ENTRY(IFolderOperations) + Z7_COM_QI_ENTRY(IFolderSetFlatMode) + Z7_COM_QI_END + Z7_COM_ADDREF_RELEASE + + Z7_IFACE_COM7_IMP(IFolderFolder) + Z7_IFACE_COM7_IMP(IFolderAltStreams) + Z7_IFACE_COM7_IMP(IFolderProperties) + Z7_IFACE_COM7_IMP(IArchiveGetRawProps) + Z7_IFACE_COM7_IMP(IGetFolderArcProps) + Z7_IFACE_COM7_IMP(IFolderCompare) + Z7_IFACE_COM7_IMP(IFolderGetItemName) + Z7_IFACE_COM7_IMP(IArchiveFolder) + Z7_IFACE_COM7_IMP(IArchiveFolderInternal) + Z7_IFACE_COM7_IMP(IInArchiveGetStream) + Z7_IFACE_COM7_IMP(IFolderSetZoneIdMode) + Z7_IFACE_COM7_IMP(IFolderSetZoneIdFile) + Z7_IFACE_COM7_IMP(IFolderOperations) + Z7_IFACE_COM7_IMP(IFolderSetFlatMode) + + void LoadFolder(unsigned proxyDirIndex); +public: + HRESULT BindToFolder_Internal(unsigned proxyDirIndex, IFolderFolder **resultFolder); + HRESULT BindToAltStreams_Internal(unsigned proxyDirIndex, IFolderFolder **resultFolder); + int GetRealIndex(unsigned index) const; + void GetRealIndices(const UInt32 *indices, UInt32 numItems, + bool includeAltStreams, bool includeFolderSubItemsInFlatMode, CUIntVector &realIndices) const; + + int CompareItems3(UInt32 index1, UInt32 index2, PROPID propID); + int CompareItems2(UInt32 index1, UInt32 index2, PROPID propID, Int32 propIsRaw); + + CAgentFolder(): + _isAltStreamFolder(false), + _flatMode(false), + _loadAltStreams(false), // _loadAltStreams alt streams works in flat mode, but we don't use it now + _proxyDirIndex(0), + _zoneMode(NExtract::NZoneIdMode::kNone) + /* , _replaceAltStreamCharsMode(0) */ + {} + + void Init(const CProxyArc *proxy, const CProxyArc2 *proxy2, + unsigned proxyDirIndex, + /* IFolderFolder *parentFolder, */ + CAgent *agent) + { + _proxy = proxy; + _proxy2 = proxy2; + _proxyDirIndex = proxyDirIndex; + _isAltStreamFolder = false; + if (_proxy2) + _isAltStreamFolder = _proxy2->IsAltDir(proxyDirIndex); + // _parentFolder = parentFolder; + _agent = (IInFolderArchive *)agent; + _agentSpec = agent; + } + + void GetPathParts(UStringVector &pathParts, bool &isAltStreamFolder); + HRESULT CommonUpdateOperation( + AGENT_OP operation, + bool moveMode, + const wchar_t *newItemName, + const NUpdateArchive::CActionSet *actionSet, + const UInt32 *indices, UInt32 numItems, + IProgress *progress); + + + void GetPrefix(UInt32 index, UString &prefix) const; + UString GetName(UInt32 index) const; + UString GetFullPrefix(UInt32 index) const; // relative too root folder of archive + +public: + bool _isAltStreamFolder; + bool _flatMode; + bool _loadAltStreams; // in Flat mode + const CProxyArc *_proxy; + const CProxyArc2 *_proxy2; + unsigned _proxyDirIndex; + NExtract::NZoneIdMode::EEnum _zoneMode; + CByteBuffer _zoneBuf; + // Int32 _replaceAltStreamCharsMode; + // CMyComPtr _parentFolder; + CMyComPtr _agent; + CAgent *_agentSpec; + CRecordVector _items; +}; + + + +class CAgent Z7_final: + public IInFolderArchive, + public IFolderArcProps, + #ifndef Z7_EXTRACT_ONLY + public IOutFolderArchive, + public ISetProperties, + #endif + public CMyUnknownImp +{ + Z7_COM_QI_BEGIN2(IInFolderArchive) + Z7_COM_QI_ENTRY(IFolderArcProps) + #ifndef Z7_EXTRACT_ONLY + Z7_COM_QI_ENTRY(IOutFolderArchive) + Z7_COM_QI_ENTRY(ISetProperties) + #endif + Z7_COM_QI_END + Z7_COM_ADDREF_RELEASE + + Z7_IFACE_COM7_IMP(IInFolderArchive) + Z7_IFACE_COM7_IMP(IFolderArcProps) + + #ifndef Z7_EXTRACT_ONLY + Z7_IFACE_COM7_IMP(ISetProperties) + +public: + Z7_IFACE_COM7_IMP(IOutFolderArchive) + HRESULT CommonUpdate(ISequentialOutStream *outArchiveStream, + unsigned numUpdateItems, IArchiveUpdateCallback *updateCallback); + + HRESULT CreateFolder(ISequentialOutStream *outArchiveStream, + const wchar_t *folderName, IFolderArchiveUpdateCallback *updateCallback100); + + HRESULT RenameItem(ISequentialOutStream *outArchiveStream, + const UInt32 *indices, UInt32 numItems, const wchar_t *newItemName, + IFolderArchiveUpdateCallback *updateCallback100); + + HRESULT CommentItem(ISequentialOutStream *outArchiveStream, + const UInt32 *indices, UInt32 numItems, const wchar_t *newItemName, + IFolderArchiveUpdateCallback *updateCallback100); + + HRESULT UpdateOneFile(ISequentialOutStream *outArchiveStream, + const UInt32 *indices, UInt32 numItems, const wchar_t *diskFilePath, + IFolderArchiveUpdateCallback *updateCallback100); + #endif + +private: + HRESULT ReadItems(); + +public: + CProxyArc *_proxy; + CProxyArc2 *_proxy2; + CArchiveLink _archiveLink; + + UString ArchiveType; + + FStringVector _names; + FString _folderPrefix; // for new files from disk + + UString _updatePathPrefix; + CAgentFolder *_agentFolder; + + UString _archiveFilePath; // it can be path of non-existing file if file is virtual + + DWORD _attrib; + bool _updatePathPrefix_is_AltFolder; + bool ThereIsPathProp; + bool _isDeviceFile; + bool _isHashHandler; + + FString _hashBaseFolderPrefix; + + #ifndef Z7_EXTRACT_ONLY + CObjectVector m_PropNames; + CObjectVector m_PropValues; + #endif + + CAgent(); + ~CAgent(); + + const CArc &GetArc() const { return _archiveLink.Arcs.Back(); } + IInArchive *GetArchive() const { if ( _archiveLink.Arcs.IsEmpty()) return NULL; return GetArc().Archive; } + bool CanUpdate() const; + + bool Is_Attrib_ReadOnly() const + { + return _attrib != INVALID_FILE_ATTRIBUTES && (_attrib & FILE_ATTRIBUTE_READONLY); + } + + bool IsThere_ReadOnlyArc() const + { + FOR_VECTOR (i, _archiveLink.Arcs) + { + const CArc &arc = _archiveLink.Arcs[i]; + if (arc.FormatIndex < 0 + || arc.IsReadOnly + || !g_CodecsObj->Formats[arc.FormatIndex].UpdateEnabled) + return true; + } + return false; + } + + UString GetTypeOfArc(const CArc &arc) const + { + if (arc.FormatIndex < 0) + return UString("Parser"); + return g_CodecsObj->GetFormatNamePtr(arc.FormatIndex); + } + + UString GetErrorMessage() const + { + UString s; + for (int i = (int)_archiveLink.Arcs.Size() - 1; i >= 0; i--) + { + const CArc &arc = _archiveLink.Arcs[i]; + + UString s2; + if (arc.ErrorInfo.ErrorFormatIndex >= 0) + { + if (arc.ErrorInfo.ErrorFormatIndex == arc.FormatIndex) + s2 += "Warning: The archive is open with offset"; + else + { + s2 += "Cannot open the file as ["; + s2 += g_CodecsObj->GetFormatNamePtr(arc.ErrorInfo.ErrorFormatIndex); + s2 += "] archive"; + } + } + + if (!arc.ErrorInfo.ErrorMessage.IsEmpty()) + { + if (!s2.IsEmpty()) + s2.Add_LF(); + s2 += "\n["; + s2 += GetTypeOfArc(arc); + s2 += "]: "; + s2 += arc.ErrorInfo.ErrorMessage; + } + + if (!s2.IsEmpty()) + { + if (!s.IsEmpty()) + s += "--------------------\n"; + s += arc.Path; + s.Add_LF(); + s += s2; + s.Add_LF(); + } + } + return s; + } + + void KeepModeForNextOpen() { _archiveLink.KeepModeForNextOpen(); } +}; + + +// #ifdef NEW_FOLDER_INTERFACE + +struct CCodecIcons +{ + struct CIconPair + { + UString Ext; + int IconIndex; + }; + CObjectVector IconPairs; + + // void Clear() { IconPairs.Clear(); } + void LoadIcons(HMODULE m); + bool FindIconIndex(const UString &ext, int &iconIndex) const; +}; + + +Z7_CLASS_IMP_COM_1( + CArchiveFolderManager + , IFolderManager +) + CObjectVector CodecIconsVector; + CCodecIcons InternalIcons; + bool WasLoaded; + + void LoadFormats(); + // int FindFormat(const UString &type); +public: + CArchiveFolderManager(): + WasLoaded(false) + {} +}; + +// #endif + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Agent/AgentOut.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Agent/AgentOut.cpp new file mode 100644 index 00000000..265b9430 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Agent/AgentOut.cpp @@ -0,0 +1,719 @@ +// AgentOut.cpp + +#include "StdAfx.h" + +#include "../../../Common/Wildcard.h" + +#include "../../../Windows/FileDir.h" +#include "../../../Windows/FileName.h" +#include "../../../Windows/TimeUtils.h" + +#include "../../Compress/CopyCoder.h" + +#include "../../Common/FileStreams.h" + +#include "../../Archive/Common/ItemNameUtils.h" + +#include "Agent.h" +#include "UpdateCallbackAgent.h" + +using namespace NWindows; +using namespace NCOM; + +Z7_COM7F_IMF(CAgent::SetFolder(IFolderFolder *folder)) +{ + _updatePathPrefix.Empty(); + _updatePathPrefix_is_AltFolder = false; + _agentFolder = NULL; + + if (!folder) + return S_OK; + + { + Z7_DECL_CMyComPtr_QI_FROM( + IArchiveFolderInternal, + afi, folder) + if (afi) + { + RINOK(afi->GetAgentFolder(&_agentFolder)) + } + if (!_agentFolder) + return E_FAIL; + } + + if (_proxy2) + _updatePathPrefix = _proxy2->GetDirPath_as_Prefix(_agentFolder->_proxyDirIndex, _updatePathPrefix_is_AltFolder); + else + _updatePathPrefix = _proxy->GetDirPath_as_Prefix(_agentFolder->_proxyDirIndex); + return S_OK; +} + +Z7_COM7F_IMF(CAgent::SetFiles(const wchar_t *folderPrefix, + const wchar_t * const *names, UInt32 numNames)) +{ + _folderPrefix = us2fs(folderPrefix); + _names.ClearAndReserve(numNames); + for (UInt32 i = 0; i < numNames; i++) + _names.AddInReserved(us2fs(names[i])); + return S_OK; +} + +static HRESULT EnumerateArchiveItems(CAgent *agent, + const CProxyDir &item, + const UString &prefix, + CObjectVector &arcItems) +{ + unsigned i; + + for (i = 0; i < item.SubFiles.Size(); i++) + { + unsigned arcIndex = item.SubFiles[i]; + const CProxyFile &fileItem = agent->_proxy->Files[arcIndex]; + CArcItem ai; + RINOK(agent->GetArc().GetItem_MTime(arcIndex, ai.MTime)) + RINOK(agent->GetArc().GetItem_Size(arcIndex, ai.Size, ai.Size_Defined)) + ai.IsDir = false; + ai.Name = prefix + fileItem.Name; + ai.Censored = true; // test it + ai.IndexInServer = arcIndex; + arcItems.Add(ai); + } + + for (i = 0; i < item.SubDirs.Size(); i++) + { + const CProxyDir &dirItem = agent->_proxy->Dirs[item.SubDirs[i]]; + UString fullName = prefix + dirItem.Name; + if (dirItem.IsLeaf()) + { + CArcItem ai; + RINOK(agent->GetArc().GetItem_MTime((unsigned)dirItem.ArcIndex, ai.MTime)) + ai.IsDir = true; + ai.Size_Defined = false; + ai.Name = fullName; + ai.Censored = true; // test it + ai.IndexInServer = (unsigned)dirItem.ArcIndex; + arcItems.Add(ai); + } + RINOK(EnumerateArchiveItems(agent, dirItem, fullName + WCHAR_PATH_SEPARATOR, arcItems)) + } + + return S_OK; +} + +static HRESULT EnumerateArchiveItems2(const CAgent *agent, + unsigned dirIndex, + const UString &prefix, + CObjectVector &arcItems) +{ + const CProxyDir2 &dir = agent->_proxy2->Dirs[dirIndex]; + FOR_VECTOR (i, dir.Items) + { + unsigned arcIndex = dir.Items[i]; + const CProxyFile2 &file = agent->_proxy2->Files[arcIndex]; + CArcItem ai; + ai.IndexInServer = arcIndex; + ai.Name = prefix + file.Name; + ai.Censored = true; // test it + RINOK(agent->GetArc().GetItem_MTime(arcIndex, ai.MTime)) + ai.IsDir = file.IsDir(); + ai.Size_Defined = false; + ai.IsAltStream = file.IsAltStream; + if (!ai.IsDir) + { + RINOK(agent->GetArc().GetItem_Size(arcIndex, ai.Size, ai.Size_Defined)) + ai.IsDir = false; + } + arcItems.Add(ai); + + if (file.AltDirIndex != -1) + { + RINOK(EnumerateArchiveItems2(agent, (unsigned)file.AltDirIndex, ai.Name + L':', arcItems)) + } + + if (ai.IsDir) + { + RINOK(EnumerateArchiveItems2(agent, (unsigned)file.DirIndex, ai.Name + WCHAR_PATH_SEPARATOR, arcItems)) + } + } + return S_OK; +} + +struct CAgUpCallbackImp Z7_final: public IUpdateProduceCallback +{ + const CObjectVector *_arcItems; + IFolderArchiveUpdateCallback *_callback; + + CAgUpCallbackImp(const CObjectVector *a, + IFolderArchiveUpdateCallback *callback): _arcItems(a), _callback(callback) {} + HRESULT ShowDeleteFile(unsigned arcIndex) Z7_override; +}; + +HRESULT CAgUpCallbackImp::ShowDeleteFile(unsigned arcIndex) +{ + return _callback->DeleteOperation((*_arcItems)[arcIndex].Name); +} + + +static void SetInArchiveInterfaces(CAgent *agent, CArchiveUpdateCallback *upd) +{ + if (agent->_archiveLink.Arcs.IsEmpty()) + return; + const CArc &arc = agent->GetArc(); + upd->Arc = &arc; + upd->Archive = arc.Archive; + + upd->ArcFileName = ExtractFileNameFromPath(arc.Path); +} + +struct CDirItemsCallback_AgentOut Z7_final: public IDirItemsCallback +{ + CMyComPtr FolderScanProgress; + IFolderArchiveUpdateCallback *FolderArchiveUpdateCallback; + HRESULT ErrorCode; + + CDirItemsCallback_AgentOut(): FolderArchiveUpdateCallback(NULL), ErrorCode(S_OK) {} + + HRESULT ScanError(const FString &name, DWORD systemError) Z7_override + { + const HRESULT hres = HRESULT_FROM_WIN32(systemError); + if (FolderArchiveUpdateCallback) + return FolderScanProgress->ScanError(fs2us(name), hres); + ErrorCode = hres; + return ErrorCode; + } + + HRESULT ScanProgress(const CDirItemsStat &st, const FString &path, bool isDir) Z7_override + { + if (FolderScanProgress) + return FolderScanProgress->ScanProgress(st.NumDirs, st.NumFiles + st.NumAltStreams, + st.GetTotalBytes(), fs2us(path), BoolToInt(isDir)); + if (FolderArchiveUpdateCallback) + return FolderArchiveUpdateCallback->SetNumFiles(st.NumFiles); + return S_OK; + } +}; + + +Z7_COM7F_IMF(CAgent::DoOperation( + FStringVector *requestedPaths, + FStringVector *processedPaths, + CCodecs *codecs, + int formatIndex, + ISequentialOutStream *outArchiveStream, + const Byte *stateActions, + const wchar_t *sfxModule, + IFolderArchiveUpdateCallback *updateCallback100)) +{ + if (!CanUpdate()) + return E_NOTIMPL; + + NUpdateArchive::CActionSet actionSet; + { + for (unsigned i = 0; i < NUpdateArchive::NPairState::kNumValues; i++) + actionSet.StateActions[i] = (NUpdateArchive::NPairAction::EEnum)stateActions[i]; + } + + CDirItemsCallback_AgentOut enumCallback; + if (updateCallback100) + { + enumCallback.FolderArchiveUpdateCallback = updateCallback100; + updateCallback100->QueryInterface(IID_IFolderScanProgress, (void **)&enumCallback.FolderScanProgress); + } + + CDirItems dirItems; + dirItems.Callback = &enumCallback; + + { + FString folderPrefix = _folderPrefix; + if (!NFile::NName::IsAltStreamPrefixWithColon(fs2us(folderPrefix))) + NFile::NName::NormalizeDirPathPrefix(folderPrefix); + + RINOK(dirItems.EnumerateItems2(folderPrefix, _updatePathPrefix, _names, requestedPaths)) + + if (_updatePathPrefix_is_AltFolder) + { + FOR_VECTOR(i, dirItems.Items) + { + CDirItem &item = dirItems.Items[i]; + if (item.IsDir()) + return E_NOTIMPL; + item.IsAltStream = true; + } + } + } + + CMyComPtr outArchive; + + if (GetArchive()) + { + RINOK(GetArchive()->QueryInterface(IID_IOutArchive, (void **)&outArchive)) + } + else + { + if (formatIndex < 0) + return E_FAIL; + RINOK(codecs->CreateOutArchive((unsigned)formatIndex, outArchive)) + + #ifdef Z7_EXTERNAL_CODECS + { + CMyComPtr setCompressCodecsInfo; + outArchive.QueryInterface(IID_ISetCompressCodecsInfo, (void **)&setCompressCodecsInfo); + if (setCompressCodecsInfo) + { + RINOK(setCompressCodecsInfo->SetCompressCodecsInfo(codecs)) + } + } + #endif + } + + NFileTimeType::EEnum fileTimeType = NFileTimeType::kNotDefined; + UInt32 value; + RINOK(outArchive->GetFileTimeType(&value)) + // we support any future fileType here. + // 22.00: + fileTimeType = (NFileTimeType::EEnum)value; + /* + switch (value) + { + case NFileTimeType::kWindows: + case NFileTimeType::kDOS: + case NFileTimeType::kUnix: + fileTimeType = NFileTimeType::EEnum(value); + break; + default: + { + return E_FAIL; + } + } + */ + + + CObjectVector arcItems; + if (GetArchive()) + { + RINOK(ReadItems()) + if (_proxy2) + { + RINOK(EnumerateArchiveItems2(this, k_Proxy2_RootDirIndex, L"", arcItems)) + RINOK(EnumerateArchiveItems2(this, k_Proxy2_AltRootDirIndex, L":", arcItems)) + } + else + { + RINOK(EnumerateArchiveItems(this, _proxy->Dirs[0], L"", arcItems)) + } + } + + CRecordVector updatePairs2; + + { + CRecordVector updatePairs; + GetUpdatePairInfoList(dirItems, arcItems, fileTimeType, updatePairs); + CAgUpCallbackImp upCallback(&arcItems, updateCallback100); + UpdateProduce(updatePairs, actionSet, updatePairs2, &upCallback); + } + + UInt32 numFiles = 0; + { + FOR_VECTOR (i, updatePairs2) + if (updatePairs2[i].NewData) + numFiles++; + } + + if (updateCallback100) + { + RINOK(updateCallback100->SetNumFiles(numFiles)) + } + + CUpdateCallbackAgent updateCallbackAgent; + updateCallbackAgent.SetCallback(updateCallback100); + CMyComPtr2_Create updateCallback; + + updateCallback->DirItems = &dirItems; + updateCallback->ArcItems = &arcItems; + updateCallback->UpdatePairs = &updatePairs2; + + SetInArchiveInterfaces(this, updateCallback.ClsPtr()); + + updateCallback->Callback = &updateCallbackAgent; + + CByteBuffer processedItems; + if (processedPaths) + { + unsigned num = dirItems.Items.Size(); + processedItems.Alloc(num); + for (unsigned i = 0; i < num; i++) + processedItems[i] = 0; + updateCallback->ProcessedItemsStatuses = processedItems; + } + + Z7_DECL_CMyComPtr_QI_FROM( + ISetProperties, + setProperties, outArchive) + if (setProperties) + { + if (m_PropNames.Size() == 0) + { + RINOK(setProperties->SetProperties(NULL, NULL, 0)) + } + else + { + CRecordVector names; + FOR_VECTOR (i, m_PropNames) + names.Add((const wchar_t *)m_PropNames[i]); + + CPropVariant *propValues = new CPropVariant[m_PropValues.Size()]; + try + { + FOR_VECTOR (i, m_PropValues) + propValues[i] = m_PropValues[i]; + RINOK(setProperties->SetProperties(names.ConstData(), propValues, names.Size())) + } + catch(...) + { + delete []propValues; + return E_FAIL; + } + delete []propValues; + } + } + m_PropNames.Clear(); + m_PropValues.Clear(); + + if (sfxModule != NULL) + { + CMyComPtr2_Create sfxStream; + if (!sfxStream->Open(us2fs(sfxModule))) + return E_FAIL; + // throw "Can't open sfx module"; + RINOK(NCompress::CopyStream(sfxStream, outArchiveStream, NULL)) + } + + const HRESULT res = outArchive->UpdateItems(outArchiveStream, updatePairs2.Size(), updateCallback); + if (res == S_OK && processedPaths) + { + { + /* OutHandler for 7z archives doesn't report compression operation for empty files. + So we must include these files manually */ + FOR_VECTOR(i, updatePairs2) + { + const CUpdatePair2 &up = updatePairs2[i]; + if (up.DirIndex != -1 && up.NewData) + { + const CDirItem &di = dirItems.Items[(unsigned)up.DirIndex]; + if (!di.IsDir() && di.Size == 0) + processedItems[(unsigned)up.DirIndex] = 1; + } + } + } + + FOR_VECTOR (i, dirItems.Items) + if (processedItems[i] != 0) + processedPaths->Add(dirItems.GetPhyPath(i)); + } + return res; +} + +Z7_COM7F_IMF(CAgent::DoOperation2( + FStringVector *requestedPaths, + FStringVector *processedPaths, + ISequentialOutStream *outArchiveStream, + const Byte *stateActions, const wchar_t *sfxModule, IFolderArchiveUpdateCallback *updateCallback100)) +{ + return DoOperation(requestedPaths, processedPaths, g_CodecsObj, -1, outArchiveStream, stateActions, sfxModule, updateCallback100); +} + +HRESULT CAgent::CommonUpdate(ISequentialOutStream *outArchiveStream, + unsigned numUpdateItems, IArchiveUpdateCallback *updateCallback) +{ + if (!CanUpdate()) + return E_NOTIMPL; + CMyComPtr outArchive; + RINOK(GetArchive()->QueryInterface(IID_IOutArchive, (void **)&outArchive)) + return outArchive->UpdateItems(outArchiveStream, numUpdateItems, updateCallback); +} + +Z7_COM7F_IMF(CAgent::DeleteItems(ISequentialOutStream *outArchiveStream, + const UInt32 *indices, UInt32 numItems, + IFolderArchiveUpdateCallback *updateCallback100)) +{ + if (!CanUpdate()) + return E_NOTIMPL; + CRecordVector updatePairs; + CUpdateCallbackAgent updateCallbackAgent; + updateCallbackAgent.SetCallback(updateCallback100); + CMyComPtr2_Create updateCallback; + + CUIntVector realIndices; + _agentFolder->GetRealIndices(indices, numItems, + true, // includeAltStreams + false, // includeFolderSubItemsInFlatMode, we don't want to delete subItems in Flat Mode + realIndices); + unsigned curIndex = 0; + UInt32 numItemsInArchive; + RINOK(GetArchive()->GetNumberOfItems(&numItemsInArchive)) + + UString deletePath; + + for (UInt32 i = 0; i < numItemsInArchive; i++) + { + if (curIndex < realIndices.Size()) + if (realIndices[curIndex] == i) + { + RINOK(GetArc().GetItem_Path2(i, deletePath)) + RINOK(updateCallback100->DeleteOperation(deletePath)) + + curIndex++; + continue; + } + CUpdatePair2 up2; + up2.SetAs_NoChangeArcItem(i); + updatePairs.Add(up2); + } + updateCallback->UpdatePairs = &updatePairs; + + SetInArchiveInterfaces(this, updateCallback.ClsPtr()); + + updateCallback->Callback = &updateCallbackAgent; + return CommonUpdate(outArchiveStream, updatePairs.Size(), updateCallback); +} + +HRESULT CAgent::CreateFolder(ISequentialOutStream *outArchiveStream, + const wchar_t *folderName, IFolderArchiveUpdateCallback *updateCallback100) +{ + if (!CanUpdate()) + return E_NOTIMPL; + CRecordVector updatePairs; + CDirItems dirItems; + CUpdateCallbackAgent updateCallbackAgent; + updateCallbackAgent.SetCallback(updateCallback100); + CMyComPtr2_Create updateCallback; + + UInt32 numItemsInArchive; + RINOK(GetArchive()->GetNumberOfItems(&numItemsInArchive)) + for (UInt32 i = 0; i < numItemsInArchive; i++) + { + CUpdatePair2 up2; + up2.SetAs_NoChangeArcItem(i); + updatePairs.Add(up2); + } + CUpdatePair2 up2; + up2.Construct(); + up2.NewData = up2.NewProps = true; + up2.UseArcProps = false; + up2.DirIndex = 0; + + updatePairs.Add(up2); + + updatePairs.ReserveDown(); + + CDirItem di; + + di.Attrib = FILE_ATTRIBUTE_DIRECTORY; + di.Size = 0; + bool isAltStreamFolder = false; + if (_proxy2) + di.Name = _proxy2->GetDirPath_as_Prefix(_agentFolder->_proxyDirIndex, isAltStreamFolder); + else + di.Name = _proxy->GetDirPath_as_Prefix(_agentFolder->_proxyDirIndex); + di.Name += folderName; + + FILETIME ft; + NTime::GetCurUtcFileTime(ft); + di.CTime = di.ATime = di.MTime = ft; + + dirItems.Items.Add(di); + + updateCallback->Callback = &updateCallbackAgent; + updateCallback->DirItems = &dirItems; + updateCallback->UpdatePairs = &updatePairs; + + SetInArchiveInterfaces(this, updateCallback.ClsPtr()); + + return CommonUpdate(outArchiveStream, updatePairs.Size(), updateCallback); +} + + +HRESULT CAgent::RenameItem(ISequentialOutStream *outArchiveStream, + const UInt32 *indices, UInt32 numItems, const wchar_t *newItemName, + IFolderArchiveUpdateCallback *updateCallback100) +{ + if (!CanUpdate()) + return E_NOTIMPL; + if (numItems != 1) + return E_INVALIDARG; + if (!_archiveLink.IsOpen) + return E_FAIL; + CRecordVector updatePairs; + CUpdateCallbackAgent updateCallbackAgent; + updateCallbackAgent.SetCallback(updateCallback100); + CMyComPtr2_Create updateCallback; + + CUIntVector realIndices; + _agentFolder->GetRealIndices(indices, numItems, + true, // includeAltStreams + true, // includeFolderSubItemsInFlatMode + realIndices); + + const UInt32 ind0 = indices[0]; + const int mainRealIndex = _agentFolder->GetRealIndex(ind0); + const UString fullPrefix = _agentFolder->GetFullPrefix(ind0); + UString name = _agentFolder->GetName(ind0); + // 22.00 : we normalize name + NArchive::NItemName::NormalizeSlashes_in_FileName_for_OsPath(name); + const UString oldItemPath = fullPrefix + name; + const UString newItemPath = fullPrefix + newItemName; + + UStringVector newNames; + + unsigned curIndex = 0; + UInt32 numItemsInArchive; + RINOK(GetArchive()->GetNumberOfItems(&numItemsInArchive)) + + for (UInt32 i = 0; i < numItemsInArchive; i++) + { + CUpdatePair2 up2; + up2.SetAs_NoChangeArcItem(i); + if (curIndex < realIndices.Size()) + if (realIndices[curIndex] == i) + { + up2.NewProps = true; + RINOK(GetArc().IsItem_Anti(i, up2.IsAnti)) // it must work without that line too. + + UString oldFullPath; + RINOK(GetArc().GetItem_Path2(i, oldFullPath)) + + if (!IsPath1PrefixedByPath2(oldFullPath, oldItemPath)) + return E_INVALIDARG; + + up2.NewNameIndex = (int)newNames.Add(newItemPath + oldFullPath.Ptr(oldItemPath.Len())); + up2.IsMainRenameItem = (mainRealIndex == (int)i); + curIndex++; + } + updatePairs.Add(up2); + } + + updateCallback->Callback = &updateCallbackAgent; + updateCallback->UpdatePairs = &updatePairs; + updateCallback->NewNames = &newNames; + + SetInArchiveInterfaces(this, updateCallback.ClsPtr()); + + return CommonUpdate(outArchiveStream, updatePairs.Size(), updateCallback); +} + + +HRESULT CAgent::CommentItem(ISequentialOutStream *outArchiveStream, + const UInt32 *indices, UInt32 numItems, const wchar_t *newItemName, + IFolderArchiveUpdateCallback *updateCallback100) +{ + if (!CanUpdate()) + return E_NOTIMPL; + if (numItems != 1) + return E_INVALIDARG; + if (!_archiveLink.IsOpen) + return E_FAIL; + + CRecordVector updatePairs; + CUpdateCallbackAgent updateCallbackAgent; + updateCallbackAgent.SetCallback(updateCallback100); + CMyComPtr2_Create updateCallback; + + const int mainRealIndex = _agentFolder->GetRealIndex(indices[0]); + + if (mainRealIndex < 0) + return E_NOTIMPL; + + UInt32 numItemsInArchive; + RINOK(GetArchive()->GetNumberOfItems(&numItemsInArchive)) + + UString newName = newItemName; + + for (UInt32 i = 0; i < numItemsInArchive; i++) + { + CUpdatePair2 up2; + up2.SetAs_NoChangeArcItem(i); + if ((int)i == mainRealIndex) + up2.NewProps = true; + updatePairs.Add(up2); + } + + updateCallback->Callback = &updateCallbackAgent; + updateCallback->UpdatePairs = &updatePairs; + updateCallback->CommentIndex = mainRealIndex; + updateCallback->Comment = &newName; + + SetInArchiveInterfaces(this, updateCallback.ClsPtr()); + + return CommonUpdate(outArchiveStream, updatePairs.Size(), updateCallback); +} + + + +HRESULT CAgent::UpdateOneFile(ISequentialOutStream *outArchiveStream, + const UInt32 *indices, UInt32 numItems, const wchar_t *diskFilePath, + IFolderArchiveUpdateCallback *updateCallback100) +{ + if (!CanUpdate()) + return E_NOTIMPL; + CRecordVector updatePairs; + CDirItems dirItems; + CUpdateCallbackAgent updateCallbackAgent; + updateCallbackAgent.SetCallback(updateCallback100); + CMyComPtr2_Create updateCallback; + + UInt32 realIndex; + { + CUIntVector realIndices; + _agentFolder->GetRealIndices(indices, numItems, + false, // includeAltStreams // we update only main stream of file + false, // includeFolderSubItemsInFlatMode + realIndices); + if (realIndices.Size() != 1) + return E_FAIL; + realIndex = realIndices[0]; + } + + { + FStringVector filePaths; + filePaths.Add(us2fs(diskFilePath)); + dirItems.EnumerateItems2(FString(), UString(), filePaths, NULL); + if (dirItems.Items.Size() != 1) + return E_FAIL; + } + + UInt32 numItemsInArchive; + RINOK(GetArchive()->GetNumberOfItems(&numItemsInArchive)) + for (UInt32 i = 0; i < numItemsInArchive; i++) + { + CUpdatePair2 up2; + up2.SetAs_NoChangeArcItem(i); + if (realIndex == i) + { + up2.DirIndex = 0; + up2.NewData = true; + up2.NewProps = true; + up2.UseArcProps = false; + } + updatePairs.Add(up2); + } + updateCallback->DirItems = &dirItems; + updateCallback->Callback = &updateCallbackAgent; + updateCallback->UpdatePairs = &updatePairs; + + SetInArchiveInterfaces(this, updateCallback.ClsPtr()); + + updateCallback->KeepOriginalItemNames = true; + return CommonUpdate(outArchiveStream, updatePairs.Size(), updateCallback); +} + +Z7_COM7F_IMF(CAgent::SetProperties(const wchar_t * const *names, const PROPVARIANT *values, UInt32 numProps)) +{ + m_PropNames.Clear(); + m_PropValues.Clear(); + for (UInt32 i = 0; i < numProps; i++) + { + m_PropNames.Add(names[i]); + m_PropValues.Add(values[i]); + } + return S_OK; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Agent/AgentProxy.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Agent/AgentProxy.cpp new file mode 100644 index 00000000..c8edd193 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Agent/AgentProxy.cpp @@ -0,0 +1,790 @@ +// AgentProxy.cpp + +#include "StdAfx.h" + +// #include +#ifdef _WIN32 +#include +#else +#include +#endif + +#include "../../../../C/Sort.h" +#include "../../../../C/CpuArch.h" + +#include "../../../Common/UTFConvert.h" +#include "../../../Common/Wildcard.h" + +#include "../../../Windows/PropVariant.h" +#include "../../../Windows/PropVariantConv.h" + +#include "../../Archive/Common/ItemNameUtils.h" + +#include "AgentProxy.h" + +using namespace NWindows; + +int CProxyArc::FindSubDir(unsigned dirIndex, const wchar_t *name, unsigned &insertPos) const +{ + const CRecordVector &subDirs = Dirs[dirIndex].SubDirs; + unsigned left = 0, right = subDirs.Size(); + for (;;) + { + if (left == right) + { + insertPos = left; + return -1; + } + const unsigned mid = (unsigned)(((size_t)left + (size_t)right) / 2); + const unsigned dirIndex2 = subDirs[mid]; + const int comp = CompareFileNames(name, Dirs[dirIndex2].Name); + if (comp == 0) + return (int)dirIndex2; + if (comp < 0) + right = mid; + else + left = mid + 1; + } +} + +int CProxyArc::FindSubDir(unsigned dirIndex, const wchar_t *name) const +{ + unsigned insertPos; + return FindSubDir(dirIndex, name, insertPos); +} + +static const wchar_t *AllocStringAndCopy(const wchar_t *s, size_t len) +{ + wchar_t *p = new wchar_t[len + 1]; + MyStringCopy(p, s); + return p; +} + +static const wchar_t *AllocStringAndCopy(const UString &s) +{ + return AllocStringAndCopy(s, s.Len()); +} + +unsigned CProxyArc::AddDir(unsigned dirIndex, int arcIndex, const UString &name) +{ + unsigned insertPos; + int subDirIndex = FindSubDir(dirIndex, name, insertPos); + if (subDirIndex != -1) + { + if (arcIndex != -1) + { + CProxyDir &item = Dirs[(unsigned)subDirIndex]; + if (item.ArcIndex == -1) + item.ArcIndex = arcIndex; + } + return (unsigned)subDirIndex; + } + subDirIndex = (int)Dirs.Size(); + Dirs[dirIndex].SubDirs.Insert(insertPos, (unsigned)subDirIndex); + CProxyDir &item = Dirs.AddNew(); + + item.NameLen = name.Len(); + item.Name = AllocStringAndCopy(name); + + item.ArcIndex = arcIndex; + item.ParentDir = (int)dirIndex; + return (unsigned)subDirIndex; +} + +void CProxyDir::Clear() +{ + SubDirs.Clear(); + SubFiles.Clear(); +} + +void CProxyArc::GetDirPathParts(unsigned dirIndex, UStringVector &pathParts) const +{ + pathParts.Clear(); + // while (dirIndex != -1) + for (;;) + { + const CProxyDir &dir = Dirs[dirIndex]; + dirIndex = (unsigned)dir.ParentDir; + if (dir.ParentDir == -1) + break; + pathParts.Insert(0, dir.Name); + // 22.00: we normalize name + NArchive::NItemName::NormalizeSlashes_in_FileName_for_OsPath(pathParts[0]); + } +} + +UString CProxyArc::GetDirPath_as_Prefix(unsigned dirIndex) const +{ + UString s; + // while (dirIndex != -1) + for (;;) + { + const CProxyDir &dir = Dirs[dirIndex]; + dirIndex = (unsigned)dir.ParentDir; + if (dir.ParentDir == -1) + break; + s.InsertAtFront(WCHAR_PATH_SEPARATOR); + s.Insert(0, dir.Name); + // 22.00: we normalize name + NArchive::NItemName::NormalizeSlashes_in_FileName_for_OsPath(s.GetBuf(), MyStringLen(dir.Name)); + } + return s; +} + +void CProxyArc::AddRealIndices(unsigned dirIndex, CUIntVector &realIndices) const +{ + const CProxyDir &dir = Dirs[dirIndex]; + if (dir.IsLeaf()) + realIndices.Add((unsigned)dir.ArcIndex); + unsigned i; + for (i = 0; i < dir.SubDirs.Size(); i++) + AddRealIndices(dir.SubDirs[i], realIndices); + for (i = 0; i < dir.SubFiles.Size(); i++) + realIndices.Add(dir.SubFiles[i]); +} + +int CProxyArc::GetRealIndex(unsigned dirIndex, unsigned index) const +{ + const CProxyDir &dir = Dirs[dirIndex]; + const unsigned numDirItems = dir.SubDirs.Size(); + if (index < numDirItems) + { + const CProxyDir &f = Dirs[dir.SubDirs[index]]; + if (f.IsLeaf()) + return f.ArcIndex; + return -1; + } + return (int)dir.SubFiles[index - numDirItems]; +} + +void CProxyArc::GetRealIndices(unsigned dirIndex, const UInt32 *indices, UInt32 numItems, CUIntVector &realIndices) const +{ + const CProxyDir &dir = Dirs[dirIndex]; + realIndices.Clear(); + for (UInt32 i = 0; i < numItems; i++) + { + const UInt32 index = indices[i]; + const unsigned numDirItems = dir.SubDirs.Size(); + if (index < numDirItems) + AddRealIndices(dir.SubDirs[index], realIndices); + else + realIndices.Add(dir.SubFiles[index - numDirItems]); + } + HeapSort(realIndices.NonConstData(), realIndices.Size()); +} + +/////////////////////////////////////////////// +// CProxyArc + +static bool GetSize(IInArchive *archive, UInt32 index, PROPID propID, UInt64 &size) +{ + size = 0; + NCOM::CPropVariant prop; + if (archive->GetProperty(index, propID, &prop) != S_OK) + throw 20120228; + return ConvertPropVariantToUInt64(prop, size); +} + +void CProxyArc::CalculateSizes(unsigned dirIndex, IInArchive *archive) +{ + CProxyDir &dir = Dirs[dirIndex]; + dir.Size = dir.PackSize = 0; + dir.NumSubDirs = dir.SubDirs.Size(); + dir.NumSubFiles = dir.SubFiles.Size(); + dir.CrcIsDefined = true; + dir.Crc = 0; + + unsigned i; + + for (i = 0; i < dir.SubFiles.Size(); i++) + { + const UInt32 index = (UInt32)dir.SubFiles[i]; + UInt64 size, packSize; + const bool sizeDefined = GetSize(archive, index, kpidSize, size); + dir.Size += size; + GetSize(archive, index, kpidPackSize, packSize); + dir.PackSize += packSize; + { + NCOM::CPropVariant prop; + if (archive->GetProperty(index, kpidCRC, &prop) == S_OK) + { + if (prop.vt == VT_UI4) + dir.Crc += prop.ulVal; + else if (prop.vt != VT_EMPTY || size != 0 || !sizeDefined) + dir.CrcIsDefined = false; + } + else + dir.CrcIsDefined = false; + } + } + + for (i = 0; i < dir.SubDirs.Size(); i++) + { + unsigned subDirIndex = dir.SubDirs[i]; + CalculateSizes(subDirIndex, archive); + CProxyDir &f = Dirs[subDirIndex]; + dir.Size += f.Size; + dir.PackSize += f.PackSize; + dir.NumSubFiles += f.NumSubFiles; + dir.NumSubDirs += f.NumSubDirs; + dir.Crc += f.Crc; + if (!f.CrcIsDefined) + dir.CrcIsDefined = false; + } +} + + +void CProxyArc::FreeFiles() +{ + const unsigned numFiles = NumFiles; + NumFiles = 0; + CProxyFile *f = Files; + for (unsigned i = 0; i < numFiles; i++, f++) + if (f->NeedDeleteName) + delete [](wchar_t *)(void *)f->Name; + delete []Files; + Files = NULL; +} + +static const UInt32 k_NumFiles_Max = 0x7fffffff - 15; + +HRESULT CProxyArc::Load(const CArc &arc, IProgress *progress) +{ + // DWORD tickCount = GetTickCount(); for (int ttt = 0; ttt < 1; ttt++) { + + FreeFiles(); + Dirs.Clear(); + + Dirs.AddNew(); + IInArchive *archive = arc.Archive; + + UInt32 numItems; + RINOK(archive->GetNumberOfItems(&numItems)) + if (numItems > k_NumFiles_Max) + return E_OUTOFMEMORY; + + if (progress) + RINOK(progress->SetTotal(numItems)) + + Z7_ARRAY_NEW(Files, CProxyFile, numItems) + memset(Files, 0, (size_t)numItems * sizeof(*Files)); + NumFiles = numItems; + + UString path; + UString name; + NCOM::CPropVariant prop; + + for (UInt32 i = 0; i < numItems; i++) + { + if (progress && (i & 0xFFFF) == 0) + { + const UInt64 currentItemIndex = i; + RINOK(progress->SetCompleted(¤tItemIndex)) + } + + const wchar_t *s = NULL; + unsigned len = 0; + bool isPtrName = false; + + #if WCHAR_PATH_SEPARATOR != L'/' + wchar_t separatorChar = WCHAR_PATH_SEPARATOR; + #endif + + #if defined(MY_CPU_LE) && defined(_WIN32) + // it works only if (sizeof(wchar_t) == 2) + if (arc.GetRawProps) + { + const void *p; + UInt32 size; + UInt32 propType; + if (arc.GetRawProps->GetRawProp(i, kpidPath, &p, &size, &propType) == S_OK + && propType == NPropDataType::kUtf16z + && size > 2) + { + // is (size <= 2), it's empty name, and we call default arc.GetItemPath(); + len = size / 2 - 1; + s = (const wchar_t *)p; + isPtrName = true; + #if WCHAR_PATH_SEPARATOR != L'/' + separatorChar = L'/'; // 0 + #endif + } + } + if (!s) + #endif + { + prop.Clear(); + RINOK(arc.Archive->GetProperty(i, kpidPath, &prop)) + if (prop.vt == VT_BSTR) + { + s = prop.bstrVal; + len = ::SysStringLen(prop.bstrVal); + } + else if (prop.vt != VT_EMPTY) + return E_FAIL; + if (len == 0) + { + RINOK(arc.GetItem_DefaultPath(i, path)) + len = path.Len(); + s = path; + } + + /* + RINOK(arc.GetItemPath(i, path)); + len = path.Len(); + s = path; + */ + } + + unsigned curItem = 0; + + /* + if (arc.Ask_Deleted) + { + bool isDeleted = false; + RINOK(Archive_IsItem_Deleted(archive, i, isDeleted)); + if (isDeleted) + curItem = AddDirSubItem(curItem, (UInt32)(Int32)-1, false, L"[DELETED]"); + } + */ + + unsigned namePos = 0; + + unsigned numLevels = 0; + + for (unsigned j = 0; j < len; j++) + { + const wchar_t c = s[j]; + if (c == L'/' + #if WCHAR_PATH_SEPARATOR != L'/' + || (c == separatorChar) + #endif + ) + { + const unsigned kLevelLimit = 1 << 10; + if (numLevels <= kLevelLimit) + { + if (numLevels == kLevelLimit) + name = "[LONG_PATH]"; + else + name.SetFrom(s + namePos, j - namePos); + // 22.00: we can normalize dir here + // NArchive::NItemName::NormalizeSlashes_in_FileName_for_OsPath(name); + curItem = AddDir(curItem, -1, name); + } + namePos = j + 1; + numLevels++; + } + } + + /* + that code must be implemeted to hide alt streams in list. + if (arc.Ask_AltStreams) + { + bool isAltStream; + RINOK(Archive_IsItem_AltStream(archive, i, isAltStream)); + if (isAltStream) + { + + } + } + */ + + bool isDir; + RINOK(Archive_IsItem_Dir(archive, i, isDir)) + + CProxyFile &f = Files[i]; + f.Construct(); // optional because memset() in code above + + f.NameLen = len - namePos; + s += namePos; + + if (isPtrName) + f.Name = s; + else + { + f.Name = AllocStringAndCopy(s, f.NameLen); + f.NeedDeleteName = true; + } + + if (isDir) + { + name = s; + // 22.00: we can normalize dir here + // NArchive::NItemName::NormalizeSlashes_in_FileName_for_OsPath(name); + AddDir(curItem, (int)i, name); + } + else + Dirs[curItem].SubFiles.Add(i); + } + + CalculateSizes(0, archive); + + // } char s[128]; sprintf(s, "Load archive: %7d ms", GetTickCount() - tickCount); OutputDebugStringA(s); + + return S_OK; +} + + + +// ---------- for Tree-mode archive ---------- + +void CProxyArc2::GetDirPathParts(unsigned dirIndex, UStringVector &pathParts, bool &isAltStreamDir) const +{ + pathParts.Clear(); + + isAltStreamDir = false; + + if (dirIndex == k_Proxy2_RootDirIndex) + return; + if (dirIndex == k_Proxy2_AltRootDirIndex) + { + isAltStreamDir = true; + return; + } + + while (dirIndex >= k_Proxy2_NumRootDirs) + { + const CProxyDir2 &dir = Dirs[dirIndex]; + const CProxyFile2 &file = Files[(unsigned)dir.ArcIndex]; + if (pathParts.IsEmpty() && (int)dirIndex == file.AltDirIndex) + isAltStreamDir = true; + pathParts.Insert(0, file.Name); + const int par = file.Parent; + if (par == -1) + break; + dirIndex = (unsigned)Files[(unsigned)par].DirIndex; + // if ((int)dirIndex == -1) break; + } +} + +bool CProxyArc2::IsAltDir(unsigned dirIndex) const +{ + if (dirIndex == k_Proxy2_RootDirIndex) + return false; + if (dirIndex == k_Proxy2_AltRootDirIndex) + return true; + const CProxyDir2 &dir = Dirs[dirIndex]; + const CProxyFile2 &file = Files[(unsigned)dir.ArcIndex]; + return ((int)dirIndex == file.AltDirIndex); +} + +UString CProxyArc2::GetDirPath_as_Prefix(unsigned dirIndex, bool &isAltStreamDir) const +{ + isAltStreamDir = false; + const CProxyDir2 &dir = Dirs[dirIndex]; + if (dirIndex == k_Proxy2_AltRootDirIndex) + isAltStreamDir = true; + else if (dirIndex >= k_Proxy2_NumRootDirs) + { + const CProxyFile2 &file = Files[(unsigned)dir.ArcIndex]; + isAltStreamDir = ((int)dirIndex == file.AltDirIndex); + } + return dir.PathPrefix; +} + +void CProxyArc2::AddRealIndices_of_ArcItem(unsigned arcIndex, bool includeAltStreams, CUIntVector &realIndices) const +{ + realIndices.Add(arcIndex); + const CProxyFile2 &file = Files[arcIndex]; + if (file.DirIndex != -1) + AddRealIndices_of_Dir((unsigned)file.DirIndex, includeAltStreams, realIndices); + if (includeAltStreams && file.AltDirIndex != -1) + AddRealIndices_of_Dir((unsigned)file.AltDirIndex, includeAltStreams, realIndices); +} + +void CProxyArc2::AddRealIndices_of_Dir(unsigned dirIndex, bool includeAltStreams, CUIntVector &realIndices) const +{ + const CRecordVector &subFiles = Dirs[dirIndex].Items; + FOR_VECTOR (i, subFiles) + { + AddRealIndices_of_ArcItem(subFiles[i], includeAltStreams, realIndices); + } +} + +unsigned CProxyArc2::GetRealIndex(unsigned dirIndex, unsigned index) const +{ + return Dirs[dirIndex].Items[index]; +} + +void CProxyArc2::GetRealIndices(unsigned dirIndex, const UInt32 *indices, UInt32 numItems, bool includeAltStreams, CUIntVector &realIndices) const +{ + const CProxyDir2 &dir = Dirs[dirIndex]; + realIndices.Clear(); + for (UInt32 i = 0; i < numItems; i++) + { + AddRealIndices_of_ArcItem(dir.Items[indices[i]], includeAltStreams, realIndices); + } + HeapSort(realIndices.NonConstData(), realIndices.Size()); +} + +void CProxyArc2::CalculateSizes(unsigned dirIndex, IInArchive *archive) +{ + CProxyDir2 &dir = Dirs[dirIndex]; + dir.Size = dir.PackSize = 0; + dir.NumSubDirs = 0; // dir.SubDirs.Size(); + dir.NumSubFiles = 0; // dir.Files.Size(); + dir.CrcIsDefined = true; + dir.Crc = 0; + + FOR_VECTOR (i, dir.Items) + { + UInt32 index = dir.Items[i]; + UInt64 size, packSize; + const bool sizeDefined = GetSize(archive, index, kpidSize, size); + dir.Size += size; + GetSize(archive, index, kpidPackSize, packSize); + dir.PackSize += packSize; + { + NCOM::CPropVariant prop; + if (archive->GetProperty(index, kpidCRC, &prop) == S_OK) + { + if (prop.vt == VT_UI4) + dir.Crc += prop.ulVal; + else if (prop.vt != VT_EMPTY || size != 0 || !sizeDefined) + dir.CrcIsDefined = false; + } + else + dir.CrcIsDefined = false; + } + + const CProxyFile2 &subFile = Files[index]; + if (subFile.DirIndex == -1) + { + dir.NumSubFiles++; + } + else + { + // 22.00: we normalize name + UString s = subFile.Name; + NArchive::NItemName::NormalizeSlashes_in_FileName_for_OsPath(s); + dir.NumSubDirs++; + CProxyDir2 &f = Dirs[subFile.DirIndex]; + f.PathPrefix = dir.PathPrefix + s + WCHAR_PATH_SEPARATOR; + CalculateSizes((unsigned)subFile.DirIndex, archive); + dir.Size += f.Size; + dir.PackSize += f.PackSize; + dir.NumSubFiles += f.NumSubFiles; + dir.NumSubDirs += f.NumSubDirs; + dir.Crc += f.Crc; + if (!f.CrcIsDefined) + dir.CrcIsDefined = false; + } + + if (subFile.AltDirIndex == -1) + { + // dir.NumSubFiles++; + } + else + { + // dir.NumSubDirs++; + CProxyDir2 &f = Dirs[subFile.AltDirIndex]; + f.PathPrefix = dir.PathPrefix + subFile.Name + L':'; + CalculateSizes((unsigned)subFile.AltDirIndex, archive); + } + } +} + + +bool CProxyArc2::IsThere_SubDir(unsigned dirIndex, const UString &name) const +{ + const CRecordVector &subFiles = Dirs[dirIndex].Items; + FOR_VECTOR (i, subFiles) + { + const CProxyFile2 &file = Files[subFiles[i]]; + if (file.IsDir()) + if (CompareFileNames(name, file.Name) == 0) + return true; + } + return false; +} + + +void CProxyArc2::FreeFiles() +{ + const unsigned numFiles = NumFiles; + NumFiles = 0; + CProxyFile2 *f = Files; + for (unsigned i = 0; i < numFiles; i++, f++) + if (f->NeedDeleteName) + delete [](wchar_t *)(void *)f->Name; + delete []Files; + Files = NULL; +} + +HRESULT CProxyArc2::Load(const CArc &arc, IProgress *progress) +{ + if (!arc.GetRawProps) + return E_FAIL; + + // DWORD tickCount = GetTickCount(); for (int ttt = 0; ttt < 1; ttt++) { + + Dirs.Clear(); + FreeFiles(); + + IInArchive *archive = arc.Archive; + + UInt32 numItems; + RINOK(archive->GetNumberOfItems(&numItems)) + if (numItems > k_NumFiles_Max) + return E_OUTOFMEMORY; + if (progress) + RINOK(progress->SetTotal(numItems)) + UString fileName; + + + { + // Dirs[0] - root dir + /* CProxyDir2 &dir = */ Dirs.AddNew(); + } + + { + // Dirs[1] - for alt streams of root dir + CProxyDir2 &dir = Dirs.AddNew(); + dir.PathPrefix = ':'; + } + + Z7_ARRAY_NEW(Files, CProxyFile2, numItems) + memset(Files, 0, (size_t)numItems * sizeof(*Files)); + NumFiles = numItems; + + UString tempUString; + AString tempAString; + + UInt32 i; + for (i = 0; i < numItems; i++) + { + if (progress && (i & 0xFFFFF) == 0) + { + const UInt64 currentItemIndex = i; + RINOK(progress->SetCompleted(¤tItemIndex)) + } + + CProxyFile2 &file = Files[i]; + file.Construct(); + + const void *p; + UInt32 size; + UInt32 propType; + RINOK(arc.GetRawProps->GetRawProp(i, kpidName, &p, &size, &propType)) + + #ifdef MY_CPU_LE + if (p && propType == PROP_DATA_TYPE_wchar_t_PTR_Z_LE) + { + file.Name = (const wchar_t *)p; + file.NameLen = 0; + if (size >= sizeof(wchar_t)) + file.NameLen = size / (unsigned)sizeof(wchar_t) - 1; + } + else + #endif + if (p && propType == NPropDataType::kUtf8z) + { + tempAString = (const char *)p; + ConvertUTF8ToUnicode(tempAString, tempUString); + file.NameLen = tempUString.Len(); + file.Name = AllocStringAndCopy(tempUString); + file.NeedDeleteName = true; + } + else + { + NCOM::CPropVariant prop; + RINOK(arc.Archive->GetProperty(i, kpidName, &prop)) + const wchar_t *s; + if (prop.vt == VT_BSTR) + s = prop.bstrVal; + else if (prop.vt == VT_EMPTY) + s = L"[Content]"; + else + return E_FAIL; + file.NameLen = MyStringLen(s); + file.Name = AllocStringAndCopy(s, file.NameLen); + file.NeedDeleteName = true; + } + + UInt32 parent = (UInt32)(Int32)-1; + UInt32 parentType = 0; + RINOK(arc.GetRawProps->GetParent(i, &parent, &parentType)) + file.Parent = (Int32)parent; + + if (arc.Ask_Deleted) + { + bool isDeleted = false; + RINOK(Archive_IsItem_Deleted(archive, i, isDeleted)) + if (isDeleted) + { + // continue; + // curItem = AddDirSubItem(curItem, (UInt32)(Int32)-1, false, L"[DELETED]"); + } + } + + bool isDir; + RINOK(Archive_IsItem_Dir(archive, i, isDir)) + + if (isDir) + { + file.DirIndex = (int)Dirs.Size(); + CProxyDir2 &dir = Dirs.AddNew(); + dir.ArcIndex = (int)i; + } + if (arc.Ask_AltStream) + RINOK(Archive_IsItem_AltStream(archive, i, file.IsAltStream)) + } + + for (i = 0; i < numItems; i++) + { + CProxyFile2 &file = Files[i]; + int dirIndex; + + if (file.IsAltStream) + { + if (file.Parent == -1) + dirIndex = k_Proxy2_AltRootDirIndex; + else + { + int &folderIndex2 = Files[(unsigned)file.Parent].AltDirIndex; + if (folderIndex2 == -1) + { + folderIndex2 = (int)Dirs.Size(); + CProxyDir2 &dir = Dirs.AddNew(); + dir.ArcIndex = file.Parent; + } + dirIndex = folderIndex2; + } + } + else + { + if (file.Parent == -1) + dirIndex = k_Proxy2_RootDirIndex; + else + { + dirIndex = Files[(unsigned)file.Parent].DirIndex; + if (dirIndex == -1) + return E_FAIL; + } + } + + Dirs[dirIndex].Items.Add(i); + } + + for (i = 0; i < k_Proxy2_NumRootDirs; i++) + CalculateSizes(i, archive); + + // } char s[128]; sprintf(s, "Load archive: %7d ms", GetTickCount() - tickCount); OutputDebugStringA(s); + + return S_OK; +} + +int CProxyArc2::FindItem(unsigned dirIndex, const wchar_t *name, bool foldersOnly) const +{ + const CProxyDir2 &dir = Dirs[dirIndex]; + FOR_VECTOR (i, dir.Items) + { + const CProxyFile2 &file = Files[dir.Items[i]]; + if (foldersOnly && file.DirIndex == -1) + continue; + if (CompareFileNames(file.Name, name) == 0) + return (int)i; + } + return -1; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Agent/AgentProxy.h b/src/plugins/7zip/7za/cpp/7zip/UI/Agent/AgentProxy.h new file mode 100644 index 00000000..c6736a81 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Agent/AgentProxy.h @@ -0,0 +1,174 @@ +// AgentProxy.h + +#ifndef ZIP7_INC_AGENT_PROXY_H +#define ZIP7_INC_AGENT_PROXY_H + +#include "../Common/OpenArchive.h" + +struct CProxyFile +{ + const wchar_t *Name; + unsigned NameLen; + bool NeedDeleteName; + + void Construct() + { + Name = NULL; + NameLen = 0; + NeedDeleteName = false; + } +}; + +const unsigned k_Proxy_RootDirIndex = 0; + +struct CProxyDir +{ + const wchar_t *Name; + unsigned NameLen; + + int ArcIndex; // index in proxy->Files[] ; -1 if there is no item for that folder + int ParentDir; // index in proxy->Dirs[] ; -1 for root folder; ; + CRecordVector SubDirs; + CRecordVector SubFiles; + + UInt64 Size; + UInt64 PackSize; + UInt32 Crc; + UInt32 NumSubDirs; + UInt32 NumSubFiles; + bool CrcIsDefined; + + CProxyDir(): Name(NULL), NameLen(0), ParentDir(-1) {} + ~CProxyDir() { delete [](wchar_t *)(void *)Name; } + + void Clear(); + bool IsLeaf() const { return ArcIndex != -1; } +}; + +class CProxyArc +{ + int FindSubDir(unsigned dirIndex, const wchar_t *name, unsigned &insertPos) const; + + void CalculateSizes(unsigned dirIndex, IInArchive *archive); + unsigned AddDir(unsigned dirIndex, int arcIndex, const UString &name); + void FreeFiles(); +public: + CObjectVector Dirs; // Dirs[0] - root + CProxyFile *Files; // all items from archive in same order + unsigned NumFiles; + + CProxyArc(): Files(NULL), NumFiles(0) {} + ~CProxyArc() { FreeFiles(); } + + // returns index in Dirs[], or -1, + int FindSubDir(unsigned dirIndex, const wchar_t *name) const; + + void GetDirPathParts(unsigned dirIndex, UStringVector &pathParts) const; + // returns full path of Dirs[dirIndex], including back slash + UString GetDirPath_as_Prefix(unsigned dirIndex) const; + + // AddRealIndices DOES ADD also item represented by dirIndex (if it's Leaf) + void AddRealIndices(unsigned dirIndex, CUIntVector &realIndices) const; + int GetRealIndex(unsigned dirIndex, unsigned index) const; + void GetRealIndices(unsigned dirIndex, const UInt32 *indices, UInt32 numItems, CUIntVector &realIndices) const; + + HRESULT Load(const CArc &arc, IProgress *progress); +}; + + +// ---------- for Tree-mode archive ---------- + +struct CProxyFile2 +{ + int DirIndex; // >= 0 for dir. (index in ProxyArchive2->Dirs) + int AltDirIndex; // >= 0 if there are alt streams. (index in ProxyArchive2->Dirs) + int Parent; // >= 0 if there is parent. (index in archive and in ProxyArchive2->Files) + const wchar_t *Name; + unsigned NameLen; + bool NeedDeleteName; + bool Ignore; + bool IsAltStream; + + int GetDirIndex(bool forAltStreams) const { return forAltStreams ? AltDirIndex : DirIndex; } + + bool IsDir() const { return DirIndex != -1; } + + void Construct() + { + DirIndex = -1; + AltDirIndex = -1; + Parent = -1; + Name = NULL; + NameLen = 0; + NeedDeleteName = false; + Ignore = false; + IsAltStream = false; + } +}; + +struct CProxyDir2 +{ + int ArcIndex; // = -1 for root folders, index in proxy->Files[] + CRecordVector Items; + UString PathPrefix; + UInt64 Size; + UInt64 PackSize; + bool CrcIsDefined; + UInt32 Crc; + UInt32 NumSubDirs; + UInt32 NumSubFiles; + + CProxyDir2(): ArcIndex(-1) {} +}; + +const unsigned k_Proxy2_RootDirIndex = k_Proxy_RootDirIndex; +const unsigned k_Proxy2_AltRootDirIndex = 1; +const unsigned k_Proxy2_NumRootDirs = 2; + +class CProxyArc2 +{ + void CalculateSizes(unsigned dirIndex, IInArchive *archive); + // AddRealIndices_of_Dir DOES NOT ADD item itself represented by dirIndex + void AddRealIndices_of_Dir(unsigned dirIndex, bool includeAltStreams, CUIntVector &realIndices) const; + void FreeFiles(); +public: + CObjectVector Dirs; // Dirs[0] - root folder + // Dirs[1] - for alt streams of root dir + CProxyFile2 *Files; // all items from archive in same order + unsigned NumFiles; + + CProxyArc2(): Files(NULL), NumFiles(0) {} + ~CProxyArc2() { FreeFiles(); } + + bool IsThere_SubDir(unsigned dirIndex, const UString &name) const; + + void GetDirPathParts(unsigned dirIndex, UStringVector &pathParts, bool &isAltStreamDir) const; + UString GetDirPath_as_Prefix(unsigned dirIndex, bool &isAltStreamDir) const; + bool IsAltDir(unsigned dirIndex) const; + + // AddRealIndices_of_ArcItem DOES ADD item and subItems + void AddRealIndices_of_ArcItem(unsigned arcIndex, bool includeAltStreams, CUIntVector &realIndices) const; + unsigned GetRealIndex(unsigned dirIndex, unsigned index) const; + void GetRealIndices(unsigned dirIndex, const UInt32 *indices, UInt32 numItems, bool includeAltStreams, CUIntVector &realIndices) const; + + HRESULT Load(const CArc &arc, IProgress *progress); + + int GetParentDirOfFile(UInt32 arcIndex) const + { + const CProxyFile2 &file = Files[arcIndex]; + + if (file.Parent == -1) + return file.IsAltStream ? + k_Proxy2_AltRootDirIndex : + k_Proxy2_RootDirIndex; + + const CProxyFile2 &parentFile = Files[file.Parent]; + return file.IsAltStream ? + parentFile.AltDirIndex : + parentFile.DirIndex; + } + + int FindItem(unsigned dirIndex, const wchar_t *name, bool foldersOnly) const; +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Agent/ArchiveFolder.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Agent/ArchiveFolder.cpp new file mode 100644 index 00000000..eea681cf --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Agent/ArchiveFolder.cpp @@ -0,0 +1,57 @@ +// Agent/ArchiveFolder.cpp + +#include "StdAfx.h" + +#include "../../../Common/ComTry.h" + +#include "../Common/ArchiveExtractCallback.h" + +#include "Agent.h" + +/* +Z7_COM7F_IMF(CAgentFolder::SetReplaceAltStreamCharsMode(Int32 replaceAltStreamCharsMode)) +{ + _replaceAltStreamCharsMode = replaceAltStreamCharsMode; + return S_OK; +} +*/ + +Z7_COM7F_IMF(CAgentFolder::SetZoneIdMode(NExtract::NZoneIdMode::EEnum zoneMode)) +{ + _zoneMode = zoneMode; + return S_OK; +} + +Z7_COM7F_IMF(CAgentFolder::SetZoneIdFile(const Byte *data, UInt32 size)) +{ + _zoneBuf.CopyFrom(data, size); + return S_OK; +} + + +Z7_COM7F_IMF(CAgentFolder::CopyTo(Int32 moveMode, const UInt32 *indices, UInt32 numItems, + Int32 includeAltStreams, Int32 replaceAltStreamCharsMode, + const wchar_t *path, IFolderOperationsExtractCallback *callback)) +{ + if (moveMode) + return E_NOTIMPL; + COM_TRY_BEGIN + CMyComPtr extractCallback2; + { + CMyComPtr callbackWrap = callback; + RINOK(callbackWrap.QueryInterface(IID_IFolderArchiveExtractCallback, &extractCallback2)) + } + NExtract::NPathMode::EEnum pathMode; + if (!_flatMode) + pathMode = NExtract::NPathMode::kCurPaths; + else + pathMode = (_proxy2 && _loadAltStreams) ? + NExtract::NPathMode::kNoPathsAlt : + NExtract::NPathMode::kNoPaths; + + return Extract(indices, numItems, + includeAltStreams, replaceAltStreamCharsMode, + pathMode, NExtract::NOverwriteMode::kAsk, + path, BoolToInt(false), extractCallback2); + COM_TRY_END +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Agent/ArchiveFolderOpen.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Agent/ArchiveFolderOpen.cpp new file mode 100644 index 00000000..3fa027dd --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Agent/ArchiveFolderOpen.cpp @@ -0,0 +1,231 @@ +// Agent/ArchiveFolderOpen.cpp + +#include "StdAfx.h" + +// #ifdef NEW_FOLDER_INTERFACE + +#include "../../../Common/StringToInt.h" +#include "../../../Windows/DLL.h" +#include "../../../Windows/ResourceString.h" + +#include "Agent.h" + +extern HINSTANCE g_hInstance; +static const UINT kIconTypesResId = 100; + +void CCodecIcons::LoadIcons(HMODULE m) +{ + IconPairs.Clear(); + UString iconTypes; + NWindows::MyLoadString(m, kIconTypesResId, iconTypes); + UStringVector pairs; + SplitString(iconTypes, pairs); + FOR_VECTOR (i, pairs) + { + const UString &s = pairs[i]; + int pos = s.Find(L':'); + CIconPair iconPair; + iconPair.IconIndex = -1; + if (pos < 0) + pos = (int)s.Len(); + else + { + const UString num = s.Ptr((unsigned)pos + 1); + if (!num.IsEmpty()) + { + const wchar_t *end; + iconPair.IconIndex = (int)ConvertStringToUInt32(num, &end); + if (*end != 0) + continue; + } + } + iconPair.Ext = s.Left((unsigned)pos); + IconPairs.Add(iconPair); + } +} + +bool CCodecIcons::FindIconIndex(const UString &ext, int &iconIndex) const +{ + iconIndex = -1; + FOR_VECTOR (i, IconPairs) + { + const CIconPair &pair = IconPairs[i]; + if (ext.IsEqualTo_NoCase(pair.Ext)) + { + iconIndex = pair.IconIndex; + return true; + } + } + return false; +} + + +void CArchiveFolderManager::LoadFormats() +{ + if (WasLoaded) + return; + + LoadGlobalCodecs(); + + #ifdef Z7_EXTERNAL_CODECS + CodecIconsVector.Clear(); + FOR_VECTOR (i, g_CodecsObj->Libs) + { + CCodecIcons &ci = CodecIconsVector.AddNew(); + ci.LoadIcons(g_CodecsObj->Libs[i].Lib.Get_HMODULE()); + } + #endif + InternalIcons.LoadIcons(g_hInstance); + WasLoaded = true; +} + +/* +int CArchiveFolderManager::FindFormat(const UString &type) +{ + FOR_VECTOR (i, g_CodecsObj->Formats) + if (type.IsEqualTo_NoCase(g_CodecsObj->Formats[i].Name)) + return (int)i; + return -1; +} +*/ + +Z7_COM7F_IMF(CArchiveFolderManager::OpenFolderFile(IInStream *inStream, + const wchar_t *filePath, const wchar_t *arcFormat, + IFolderFolder **resultFolder, IProgress *progress)) +{ + CMyComPtr openArchiveCallback; + if (progress) + { + CMyComPtr progressWrapper = progress; + progressWrapper.QueryInterface(IID_IArchiveOpenCallback, &openArchiveCallback); + } + CAgent *agent = new CAgent(); + CMyComPtr archive = agent; + + const HRESULT res = archive->Open(inStream, filePath, arcFormat, NULL, openArchiveCallback); + + if (res != S_OK) + { + if (res != S_FALSE) + return res; + /* 20.01: we create folder even for Non-Open cases, if there is NonOpen_ErrorInfo information. + So we can get error information from that IFolderFolder later. */ + if (!agent->_archiveLink.NonOpen_ErrorInfo.IsThereErrorOrWarning()) + return res; + } + + RINOK(archive->BindToRootFolder(resultFolder)) + return res; +} + +/* +HRESULT CAgent::FolderReOpen( + IArchiveOpenCallback *openArchiveCallback) +{ + return ReOpenArchive(_archive, _archiveFilePath); +} +*/ + + +/* +Z7_COM7F_IMF(CArchiveFolderManager::GetExtensions(const wchar_t *type, BSTR *extensions)) +{ + *extensions = 0; + int formatIndex = FindFormat(type); + if (formatIndex < 0) + return E_INVALIDARG; + // Exts[0].Ext; + return StringToBstr(g_CodecsObj.Formats[formatIndex].GetAllExtensions(), extensions); +} +*/ + +static void AddIconExt(const CCodecIcons &lib, UString &dest) +{ + FOR_VECTOR (i, lib.IconPairs) + { + dest.Add_Space_if_NotEmpty(); + dest += lib.IconPairs[i].Ext; + } +} + + +Z7_COM7F_IMF(CArchiveFolderManager::GetExtensions(BSTR *extensions)) +{ + *extensions = NULL; + LoadFormats(); + UString res; + + #ifdef Z7_EXTERNAL_CODECS + /* + FOR_VECTOR (i, g_CodecsObj->Libs) + AddIconExt(g_CodecsObj->Libs[i].CodecIcons, res); + */ + FOR_VECTOR (i, CodecIconsVector) + AddIconExt(CodecIconsVector[i], res); + #endif + + AddIconExt( + // g_CodecsObj-> + InternalIcons, res); + + return StringToBstr(res, extensions); +} + + +Z7_COM7F_IMF(CArchiveFolderManager::GetIconPath(const wchar_t *ext, BSTR *iconPath, Int32 *iconIndex)) +{ + *iconPath = NULL; + *iconIndex = 0; + LoadFormats(); + + #ifdef Z7_EXTERNAL_CODECS + // FOR_VECTOR (i, g_CodecsObj->Libs) + FOR_VECTOR (i, CodecIconsVector) + { + int ii; + if (CodecIconsVector[i].FindIconIndex(ext, ii)) + { + const CCodecLib &lib = g_CodecsObj->Libs[i]; + *iconIndex = ii; + return StringToBstr(fs2us(lib.Path), iconPath); + } + } + #endif + + int ii; + if (InternalIcons.FindIconIndex(ext, ii)) + { + FString path; + if (NWindows::NDLL::MyGetModuleFileName(path)) + { + *iconIndex = ii; + return StringToBstr(fs2us(path), iconPath); + } + } + return S_OK; +} + +/* +Z7_COM7F_IMF(CArchiveFolderManager::GetTypes(BSTR *types)) +{ + LoadFormats(); + UString typesStrings; + FOR_VECTOR(i, g_CodecsObj.Formats) + { + const CArcInfoEx &ai = g_CodecsObj.Formats[i]; + if (ai.AssociateExts.Size() == 0) + continue; + if (i != 0) + typesStrings.Add_Space(); + typesStrings += ai.Name; + } + return StringToBstr(typesStrings, types); +} +Z7_COM7F_IMF(CArchiveFolderManager::CreateFolderFile(const wchar_t * type, + const wchar_t * filePath, IProgress progress)) +{ + return E_NOTIMPL; +} +*/ + +// #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Agent/ArchiveFolderOut.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Agent/ArchiveFolderOut.cpp new file mode 100644 index 00000000..1da66015 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Agent/ArchiveFolderOut.cpp @@ -0,0 +1,456 @@ +// ArchiveFolderOut.cpp + +#include "StdAfx.h" + +#include "../../../Common/ComTry.h" + +#include "../../../Windows/FileDir.h" + +#include "../../Common/FileStreams.h" +#include "../../Common/LimitedStreams.h" + +#include "../../Compress/CopyCoder.h" + +#include "../Common/WorkDir.h" + +#include "Agent.h" + +using namespace NWindows; +using namespace NFile; +using namespace NDir; + +void CAgentFolder::GetPathParts(UStringVector &pathParts, bool &isAltStreamFolder) +{ + if (_proxy2) + _proxy2->GetDirPathParts(_proxyDirIndex, pathParts, isAltStreamFolder); + else + _proxy->GetDirPathParts(_proxyDirIndex, pathParts); +} + +static bool Delete_EmptyFolder_And_EmptySubFolders(const FString &path) +{ + { + const FString pathPrefix = path + FCHAR_PATH_SEPARATOR; + CObjectVector names; + { + NFind::CDirEntry fileInfo; + NFind::CEnumerator enumerator; + enumerator.SetDirPrefix(pathPrefix); + for (;;) + { + bool found; + if (!enumerator.Next(fileInfo, found)) + return false; + if (!found) + break; + if (fileInfo.IsDir()) + names.Add(fileInfo.Name); + } + } + bool res = true; + FOR_VECTOR (i, names) + { + if (!Delete_EmptyFolder_And_EmptySubFolders(pathPrefix + names[i])) + res = false; + } + if (!res) + return false; + } + // we clear read-only attrib to remove read-only dir + if (!SetFileAttrib(path, 0)) + return false; + return RemoveDir(path); +} + + + +struct C_CopyFileProgress_to_FolderCallback_MoveArc Z7_final: + public ICopyFileProgress +{ + IFolderArchiveUpdateCallback_MoveArc *Callback; + HRESULT CallbackResult; + + virtual DWORD CopyFileProgress(UInt64 total, UInt64 current) Z7_override + { + HRESULT res = Callback->MoveArc_Progress(total, current); + CallbackResult = res; + // we can ignore E_ABORT here, because we update archive, + // and we want to get correct archive after updating + if (res == E_ABORT) + res = S_OK; + return res == S_OK ? PROGRESS_CONTINUE : PROGRESS_CANCEL; + } + + C_CopyFileProgress_to_FolderCallback_MoveArc( + IFolderArchiveUpdateCallback_MoveArc *callback) : + Callback(callback), + CallbackResult(S_OK) + {} +}; + + +HRESULT CAgentFolder::CommonUpdateOperation( + AGENT_OP operation, + bool moveMode, + const wchar_t *newItemName, + const NUpdateArchive::CActionSet *actionSet, + const UInt32 *indices, UInt32 numItems, + IProgress *progress) +{ + if (moveMode && _agentSpec->_isHashHandler) + return E_NOTIMPL; + + if (!_agentSpec->CanUpdate()) + return E_NOTIMPL; + + CMyComPtr updateCallback100; + if (progress) + progress->QueryInterface(IID_IFolderArchiveUpdateCallback, (void **)&updateCallback100); + + try + { + + RINOK(_agentSpec->SetFolder(this)) + + // ---------- Save FolderItem ---------- + + UStringVector pathParts; + bool isAltStreamFolder = false; + GetPathParts(pathParts, isAltStreamFolder); + + FStringVector requestedPaths; + FStringVector processedPaths; + + CWorkDirTempFile tempFile; + RINOK(tempFile.CreateTempFile(us2fs(_agentSpec->_archiveFilePath))) + { + CMyComPtr tailStream; + const CArc &arc = *_agentSpec->_archiveLink.GetArc(); + + if (arc.ArcStreamOffset == 0) + tailStream = tempFile.OutStream; + else + { + if (arc.Offset < 0) + return E_NOTIMPL; + RINOK(arc.InStream->Seek(0, STREAM_SEEK_SET, NULL)) + RINOK(NCompress::CopyStream_ExactSize(arc.InStream, tempFile.OutStream, arc.ArcStreamOffset, NULL)) + CTailOutStream *tailStreamSpec = new CTailOutStream; + tailStream = tailStreamSpec; + tailStreamSpec->Stream = tempFile.OutStream; + tailStreamSpec->Offset = arc.ArcStreamOffset; + tailStreamSpec->Init(); + } + + HRESULT result; + + switch ((int)operation) + { + case AGENT_OP_Delete: + result = _agentSpec->DeleteItems(tailStream, indices, numItems, updateCallback100); + break; + case AGENT_OP_CreateFolder: + result = _agentSpec->CreateFolder(tailStream, newItemName, updateCallback100); + break; + case AGENT_OP_Rename: + result = _agentSpec->RenameItem(tailStream, indices, numItems, newItemName, updateCallback100); + break; + case AGENT_OP_Comment: + result = _agentSpec->CommentItem(tailStream, indices, numItems, newItemName, updateCallback100); + break; + case AGENT_OP_CopyFromFile: + result = _agentSpec->UpdateOneFile(tailStream, indices, numItems, newItemName, updateCallback100); + break; + case AGENT_OP_Uni: + { + Byte actionSetByte[NUpdateArchive::NPairState::kNumValues]; + for (unsigned i = 0; i < NUpdateArchive::NPairState::kNumValues; i++) + actionSetByte[i] = (Byte)actionSet->StateActions[i]; + result = _agentSpec->DoOperation2( + moveMode ? &requestedPaths : NULL, + moveMode ? &processedPaths : NULL, + tailStream, actionSetByte, NULL, updateCallback100); + break; + } + default: + return E_FAIL; + } + + RINOK(result) + } + + _agentSpec->KeepModeForNextOpen(); + _agent->Close(); + + // before 9.26: if there was error for MoveToOriginal archive was closed. + // now: we reopen archive after close + + // m_FolderItem = NULL; + _items.Clear(); + _proxyDirIndex = k_Proxy_RootDirIndex; + + CMyComPtr updateCallback_MoveArc; + if (progress) + progress->QueryInterface(IID_IFolderArchiveUpdateCallback_MoveArc, (void **)&updateCallback_MoveArc); + + HRESULT res; + if (updateCallback_MoveArc) + { + const FString &tempFilePath = tempFile.Get_TempFilePath(); + UInt64 totalSize = 0; + { + NFind::CFileInfo fi; + if (fi.Find(tempFilePath)) + totalSize = fi.Size; + } + RINOK(updateCallback_MoveArc->MoveArc_Start( + fs2us(tempFilePath), + fs2us(tempFile.Get_OriginalFilePath()), + totalSize, + 1)) // updateMode + + C_CopyFileProgress_to_FolderCallback_MoveArc prox(updateCallback_MoveArc); + res = tempFile.MoveToOriginal( + true, // deleteOriginal + &prox); + if (res == S_OK) + { + res = updateCallback_MoveArc->MoveArc_Finish(); + // we don't return after E_ABORT here, because + // we want to reopen new archive still. + } + else if (prox.CallbackResult != S_OK) + res = prox.CallbackResult; + + // if updating callback returned E_ABORT, + // then openCallback still can return E_ABORT also. + // So ReOpen() will return with E_ABORT. + // But we want to open archive still. + // And Before_ArcReopen() call will clear user break status in that case. + RINOK(updateCallback_MoveArc->Before_ArcReopen()) + } + else + res = tempFile.MoveToOriginal(true); // deleteOriginal + + // RINOK(res); + if (res == S_OK) + { + if (moveMode) + { + unsigned i; + for (i = 0; i < processedPaths.Size(); i++) + { + DeleteFileAlways(processedPaths[i]); + } + for (i = 0; i < requestedPaths.Size(); i++) + { + const FString &fs = requestedPaths[i]; + if (NFind::DoesDirExist(fs)) + Delete_EmptyFolder_And_EmptySubFolders(fs); + } + } + } + + { + CMyComPtr openCallback; + if (updateCallback100) + updateCallback100->QueryInterface(IID_IArchiveOpenCallback, (void **)&openCallback); + RINOK(_agent->ReOpen(openCallback)) + } + + // CAgent::ReOpen() deletes _proxy and _proxy2 + // _items.Clear(); + _proxy = NULL; + _proxy2 = NULL; + // _proxyDirIndex = k_Proxy_RootDirIndex; + _isAltStreamFolder = false; + + + // ---------- Restore FolderItem ---------- + + CMyComPtr archiveFolder; + RINOK(_agent->BindToRootFolder(&archiveFolder)) + + // CAgent::BindToRootFolder() changes _proxy and _proxy2 + _proxy = _agentSpec->_proxy; + _proxy2 = _agentSpec->_proxy2; + + if (_proxy) + { + FOR_VECTOR (i, pathParts) + { + const int next = _proxy->FindSubDir(_proxyDirIndex, pathParts[i]); + if (next == -1) + break; + _proxyDirIndex = (unsigned)next; + } + } + + if (_proxy2) + { + if (pathParts.IsEmpty() && isAltStreamFolder) + { + _proxyDirIndex = k_Proxy2_AltRootDirIndex; + } + else FOR_VECTOR (i, pathParts) + { + const bool dirOnly = (i + 1 < pathParts.Size() || !isAltStreamFolder); + const int index = _proxy2->FindItem(_proxyDirIndex, pathParts[i], dirOnly); + if (index == -1) + break; + + const CProxyFile2 &file = _proxy2->Files[_proxy2->Dirs[_proxyDirIndex].Items[index]]; + + if (dirOnly) + _proxyDirIndex = (unsigned)file.DirIndex; + else + { + if (file.AltDirIndex != -1) + _proxyDirIndex = (unsigned)file.AltDirIndex; + break; + } + } + } + + /* + if (pathParts.IsEmpty() && isAltStreamFolder) + { + CMyComPtr folderAltStreams; + archiveFolder.QueryInterface(IID_IFolderAltStreams, &folderAltStreams); + if (folderAltStreams) + { + CMyComPtr newFolder; + folderAltStreams->BindToAltStreams((UInt32)(Int32)-1, &newFolder); + if (newFolder) + archiveFolder = newFolder; + } + } + + FOR_VECTOR (i, pathParts) + { + CMyComPtr newFolder; + + if (isAltStreamFolder && i == pathParts.Size() - 1) + { + CMyComPtr folderAltStreams; + archiveFolder.QueryInterface(IID_IFolderAltStreams, &folderAltStreams); + if (folderAltStreams) + folderAltStreams->BindToAltStreams(pathParts[i], &newFolder); + } + else + archiveFolder->BindToFolder(pathParts[i], &newFolder); + + if (!newFolder) + break; + archiveFolder = newFolder; + } + + CMyComPtr archiveFolderInternal; + RINOK(archiveFolder.QueryInterface(IID_IArchiveFolderInternal, &archiveFolderInternal)); + CAgentFolder *agentFolder; + RINOK(archiveFolderInternal->GetAgentFolder(&agentFolder)); + _proxyDirIndex = agentFolder->_proxyDirIndex; + // _parentFolder = agentFolder->_parentFolder; + */ + + if (_proxy2) + _isAltStreamFolder = _proxy2->IsAltDir(_proxyDirIndex); + + return res; + + } + catch(const UString &s) + { + if (updateCallback100) + { + UString s2 ("Error: "); + s2 += s; + RINOK(updateCallback100->UpdateErrorMessage(s2)) + return E_FAIL; + } + throw; + } +} + + +Z7_COM7F_IMF(CAgentFolder::CopyFrom(Int32 moveMode, + const wchar_t *fromFolderPath, /* test it */ + const wchar_t * const *itemsPaths, + UInt32 numItems, + IProgress *progress)) +{ + COM_TRY_BEGIN + { + RINOK(_agentSpec->SetFiles(fromFolderPath, itemsPaths, numItems)) + return CommonUpdateOperation(AGENT_OP_Uni, (moveMode != 0), NULL, + &NUpdateArchive::k_ActionSet_Add, + NULL, 0, progress); + } + COM_TRY_END +} + +Z7_COM7F_IMF(CAgentFolder::CopyFromFile(UInt32 destIndex, const wchar_t *itemPath, IProgress *progress)) +{ + COM_TRY_BEGIN + return CommonUpdateOperation(AGENT_OP_CopyFromFile, false, itemPath, + &NUpdateArchive::k_ActionSet_Add, + &destIndex, 1, progress); + COM_TRY_END +} + +Z7_COM7F_IMF(CAgentFolder::Delete(const UInt32 *indices, UInt32 numItems, IProgress *progress)) +{ + COM_TRY_BEGIN + return CommonUpdateOperation(AGENT_OP_Delete, false, NULL, + &NUpdateArchive::k_ActionSet_Delete, indices, numItems, progress); + COM_TRY_END +} + +Z7_COM7F_IMF(CAgentFolder::CreateFolder(const wchar_t *name, IProgress *progress)) +{ + COM_TRY_BEGIN + + if (_isAltStreamFolder) + return E_NOTIMPL; + + if (_proxy2) + { + if (_proxy2->IsThere_SubDir(_proxyDirIndex, name)) + return ERROR_ALREADY_EXISTS; + } + else + { + if (_proxy->FindSubDir(_proxyDirIndex, name) != -1) + return ERROR_ALREADY_EXISTS; + } + + return CommonUpdateOperation(AGENT_OP_CreateFolder, false, name, NULL, NULL, 0, progress); + COM_TRY_END +} + +Z7_COM7F_IMF(CAgentFolder::Rename(UInt32 index, const wchar_t *newName, IProgress *progress)) +{ + COM_TRY_BEGIN + return CommonUpdateOperation(AGENT_OP_Rename, false, newName, NULL, + &index, 1, progress); + COM_TRY_END +} + +Z7_COM7F_IMF(CAgentFolder::CreateFile(const wchar_t * /* name */, IProgress * /* progress */)) +{ + return E_NOTIMPL; +} + +Z7_COM7F_IMF(CAgentFolder::SetProperty(UInt32 index, PROPID propID, + const PROPVARIANT *value, IProgress *progress)) +{ + COM_TRY_BEGIN + if (propID != kpidComment || value->vt != VT_BSTR) + return E_NOTIMPL; + if (!_agentSpec || !_agentSpec->GetTypeOfArc(_agentSpec->GetArc()).IsEqualTo_Ascii_NoCase("zip")) + return E_NOTIMPL; + + return CommonUpdateOperation(AGENT_OP_Comment, false, value->bstrVal, NULL, + &index, 1, progress); + COM_TRY_END +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Agent/IFolderArchive.h b/src/plugins/7zip/7za/cpp/7zip/UI/Agent/IFolderArchive.h new file mode 100644 index 00000000..12b900fc --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Agent/IFolderArchive.h @@ -0,0 +1,123 @@ +// IFolderArchive.h + +#ifndef ZIP7_INC_IFOLDER_ARCHIVE_H +#define ZIP7_INC_IFOLDER_ARCHIVE_H + +#include "../../../Common/MyString.h" + +#include "../../Archive/IArchive.h" +#include "../../UI/Common/LoadCodecs.h" +#include "../../UI/FileManager/IFolder.h" + +#include "../Common/ExtractMode.h" +#include "../Common/IFileExtractCallback.h" + +Z7_PURE_INTERFACES_BEGIN + +/* ---------- IArchiveFolder ---------- +IArchiveFolder is implemented by CAgentFolder (Agent/Agent.h) +IArchiveFolder is used by: + - FileManager/PanelCopy.cpp + CPanel::CopyTo(), if (options->testMode) + - FAR/PluginRead.cpp + CPlugin::ExtractFiles +*/ + +#define Z7_IFACEM_IArchiveFolder(x) \ + x(Extract(const UInt32 *indices, UInt32 numItems, \ + Int32 includeAltStreams, \ + Int32 replaceAltStreamCharsMode, \ + NExtract::NPathMode::EEnum pathMode, \ + NExtract::NOverwriteMode::EEnum overwriteMode, \ + const wchar_t *path, Int32 testMode, \ + IFolderArchiveExtractCallback *extractCallback2)) \ + +Z7_IFACE_CONSTR_FOLDERARC(IArchiveFolder, 0x0D) + + +/* ---------- IInFolderArchive ---------- +IInFolderArchive is implemented by CAgent (Agent/Agent.h) +IInFolderArchive Is used by FAR/Plugin +*/ + +#define Z7_IFACEM_IInFolderArchive(x) \ + x(Open(IInStream *inStream, const wchar_t *filePath, const wchar_t *arcFormat, BSTR *archiveTypeRes, IArchiveOpenCallback *openArchiveCallback)) \ + x(ReOpen(IArchiveOpenCallback *openArchiveCallback)) \ + x(Close()) \ + x(GetNumberOfProperties(UInt32 *numProperties)) \ + x(GetPropertyInfo(UInt32 index, BSTR *name, PROPID *propID, VARTYPE *varType)) \ + x(BindToRootFolder(IFolderFolder **resultFolder)) \ + x(Extract(NExtract::NPathMode::EEnum pathMode, \ + NExtract::NOverwriteMode::EEnum overwriteMode, const wchar_t *path, \ + Int32 testMode, IFolderArchiveExtractCallback *extractCallback2)) \ + +Z7_IFACE_CONSTR_FOLDERARC(IInFolderArchive, 0x0E) + +#define Z7_IFACEM_IFolderArchiveUpdateCallback(x) \ + x(CompressOperation(const wchar_t *name)) \ + x(DeleteOperation(const wchar_t *name)) \ + x(OperationResult(Int32 opRes)) \ + x(UpdateErrorMessage(const wchar_t *message)) \ + x(SetNumFiles(UInt64 numFiles)) \ + +Z7_IFACE_CONSTR_FOLDERARC_SUB(IFolderArchiveUpdateCallback, IProgress, 0x0B) + +#define Z7_IFACEM_IOutFolderArchive(x) \ + x(SetFolder(IFolderFolder *folder)) \ + x(SetFiles(const wchar_t *folderPrefix, const wchar_t * const *names, UInt32 numNames)) \ + x(DeleteItems(ISequentialOutStream *outArchiveStream, \ + const UInt32 *indices, UInt32 numItems, IFolderArchiveUpdateCallback *updateCallback)) \ + x(DoOperation( \ + FStringVector *requestedPaths, \ + FStringVector *processedPaths, \ + CCodecs *codecs, int index, \ + ISequentialOutStream *outArchiveStream, const Byte *stateActions, const wchar_t *sfxModule, \ + IFolderArchiveUpdateCallback *updateCallback)) \ + x(DoOperation2( \ + FStringVector *requestedPaths, \ + FStringVector *processedPaths, \ + ISequentialOutStream *outArchiveStream, const Byte *stateActions, const wchar_t *sfxModule, \ + IFolderArchiveUpdateCallback *updateCallback)) \ + +Z7_IFACE_CONSTR_FOLDERARC(IOutFolderArchive, 0x0F) + + +#define Z7_IFACEM_IFolderArchiveUpdateCallback2(x) \ + x(OpenFileError(const wchar_t *path, HRESULT errorCode)) \ + x(ReadingFileError(const wchar_t *path, HRESULT errorCode)) \ + x(ReportExtractResult(Int32 opRes, Int32 isEncrypted, const wchar_t *path)) \ + x(ReportUpdateOperation(UInt32 notifyOp, const wchar_t *path, Int32 isDir)) \ + +Z7_IFACE_CONSTR_FOLDERARC(IFolderArchiveUpdateCallback2, 0x10) + + +#define Z7_IFACEM_IFolderScanProgress(x) \ + x(ScanError(const wchar_t *path, HRESULT errorCode)) \ + x(ScanProgress(UInt64 numFolders, UInt64 numFiles, UInt64 totalSize, const wchar_t *path, Int32 isDir)) \ + +Z7_IFACE_CONSTR_FOLDERARC(IFolderScanProgress, 0x11) + + +#define Z7_IFACEM_IFolderSetZoneIdMode(x) \ + x(SetZoneIdMode(NExtract::NZoneIdMode::EEnum zoneMode)) \ + +Z7_IFACE_CONSTR_FOLDERARC(IFolderSetZoneIdMode, 0x12) + +#define Z7_IFACEM_IFolderSetZoneIdFile(x) \ + x(SetZoneIdFile(const Byte *data, UInt32 size)) \ + +Z7_IFACE_CONSTR_FOLDERARC(IFolderSetZoneIdFile, 0x13) + + +// if the caller calls Before_ArcReopen(), the callee must +// clear user break status, because the caller want to open archive still. +#define Z7_IFACEM_IFolderArchiveUpdateCallback_MoveArc(x) \ + x(MoveArc_Start(const wchar_t *srcTempPath, const wchar_t *destFinalPath, UInt64 size, Int32 updateMode)) \ + x(MoveArc_Progress(UInt64 totalSize, UInt64 currentSize)) \ + x(MoveArc_Finish()) \ + x(Before_ArcReopen()) \ + +Z7_IFACE_CONSTR_FOLDERARC(IFolderArchiveUpdateCallback_MoveArc, 0x14) + +Z7_PURE_INTERFACES_END +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Agent/StdAfx.h b/src/plugins/7zip/7za/cpp/7zip/UI/Agent/StdAfx.h new file mode 100644 index 00000000..035267cc --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Agent/StdAfx.h @@ -0,0 +1,11 @@ +// StdAfx.h + +#ifndef ZIP7_INC_STDAFX_H +#define ZIP7_INC_STDAFX_H + +#if defined(_MSC_VER) && _MSC_VER >= 1800 +#pragma warning(disable : 4464) // relative include path contains '..' +#endif +#include "../../../Common/Common.h" + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Agent/UpdateCallbackAgent.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Agent/UpdateCallbackAgent.cpp new file mode 100644 index 00000000..8299902d --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Agent/UpdateCallbackAgent.cpp @@ -0,0 +1,208 @@ +// UpdateCallbackAgent.h + +#include "StdAfx.h" + +#include "../../../Common/IntToString.h" + +#include "../../../Windows/ErrorMsg.h" + +#include "UpdateCallbackAgent.h" + +using namespace NWindows; + +void CUpdateCallbackAgent::SetCallback(IFolderArchiveUpdateCallback *callback) +{ + Callback = callback; + _compressProgress.Release(); + Callback2.Release(); + if (Callback) + { + Callback.QueryInterface(IID_ICompressProgressInfo, &_compressProgress); + Callback.QueryInterface(IID_IFolderArchiveUpdateCallback2, &Callback2); + } +} + +HRESULT CUpdateCallbackAgent::SetNumItems(const CArcToDoStat &stat) +{ + if (Callback) + return Callback->SetNumFiles(stat.Get_NumDataItems_Total()); + return S_OK; +} + + +HRESULT CUpdateCallbackAgent::WriteSfx(const wchar_t * /* name */, UInt64 /* size */) +{ + return S_OK; +} + + +HRESULT CUpdateCallbackAgent::SetTotal(UINT64 size) +{ + if (Callback) + return Callback->SetTotal(size); + return S_OK; +} + +HRESULT CUpdateCallbackAgent::SetCompleted(const UINT64 *completeValue) +{ + if (Callback) + return Callback->SetCompleted(completeValue); + return S_OK; +} + +HRESULT CUpdateCallbackAgent::SetRatioInfo(const UInt64 *inSize, const UInt64 *outSize) +{ + if (_compressProgress) + return _compressProgress->SetRatioInfo(inSize, outSize); + return S_OK; +} + +HRESULT CUpdateCallbackAgent::CheckBreak() +{ + return S_OK; +} + +/* +HRESULT CUpdateCallbackAgent::Finalize() +{ + return S_OK; +} +*/ + +HRESULT CUpdateCallbackAgent::OpenFileError(const FString &path, DWORD systemError) +{ + const HRESULT hres = HRESULT_FROM_WIN32(systemError); + // if (systemError == ERROR_SHARING_VIOLATION) + { + if (Callback2) + { + RINOK(Callback2->OpenFileError(fs2us(path), hres)) + return S_FALSE; + } + + if (Callback) + { + UString s ("WARNING: "); + s += NError::MyFormatMessage(systemError); + s += ": "; + s += fs2us(path); + RINOK(Callback->UpdateErrorMessage(s)) + return S_FALSE; + } + } + // FailedFiles.Add(name); + return hres; +} + +HRESULT CUpdateCallbackAgent::ReadingFileError(const FString &path, DWORD systemError) +{ + const HRESULT hres = HRESULT_FROM_WIN32(systemError); + + // if (systemError == ERROR_SHARING_VIOLATION) + { + if (Callback2) + { + RINOK(Callback2->ReadingFileError(fs2us(path), hres)) + } + else if (Callback) + { + UString s ("ERROR: "); + s += NError::MyFormatMessage(systemError); + s += ": "; + s += fs2us(path); + RINOK(Callback->UpdateErrorMessage(s)) + } + } + // FailedFiles.Add(name); + return hres; +} + +HRESULT CUpdateCallbackAgent::GetStream(const wchar_t *name, bool isDir, bool /* isAnti */, UInt32 mode) +{ + if (Callback2) + return Callback2->ReportUpdateOperation(mode, name, BoolToInt(isDir)); + if (Callback) + return Callback->CompressOperation(name); + return S_OK; +} + +HRESULT CUpdateCallbackAgent::SetOperationResult(Int32 operationResult) +{ + if (Callback) + return Callback->OperationResult(operationResult); + return S_OK; +} + +void SetExtractErrorMessage(Int32 opRes, Int32 encrypted, const wchar_t *fileName, UString &s); + +HRESULT CUpdateCallbackAgent::ReportExtractResult(Int32 opRes, Int32 isEncrypted, const wchar_t *name) +{ + if (Callback2) + { + return Callback2->ReportExtractResult(opRes, isEncrypted, name); + } + /* + if (mode != NArchive::NExtract::NOperationResult::kOK) + { + Int32 encrypted = 0; + UString s; + SetExtractErrorMessage(mode, encrypted, name, s); + // ProgressDialog->Sync.AddError_Message(s); + } + */ + return S_OK; +} + +HRESULT CUpdateCallbackAgent::ReportUpdateOperation(UInt32 op, const wchar_t *name, bool isDir) +{ + if (Callback2) + { + return Callback2->ReportUpdateOperation(op, name, BoolToInt(isDir)); + } + return S_OK; +} + +/* +HRESULT CUpdateCallbackAgent::SetPassword(const UString & + #ifndef Z7_NO_CRYPTO + password + #endif + ) +{ + #ifndef Z7_NO_CRYPTO + PasswordIsDefined = true; + Password = password; + #endif + return S_OK; +} +*/ + +HRESULT CUpdateCallbackAgent::CryptoGetTextPassword2(Int32 *passwordIsDefined, BSTR *password) +{ + *password = NULL; + *passwordIsDefined = BoolToInt(false); + if (!_cryptoGetTextPassword) + { + if (!Callback) + return S_OK; + Callback.QueryInterface(IID_ICryptoGetTextPassword2, &_cryptoGetTextPassword); + if (!_cryptoGetTextPassword) + return S_OK; + } + return _cryptoGetTextPassword->CryptoGetTextPassword2(passwordIsDefined, password); +} + +HRESULT CUpdateCallbackAgent::CryptoGetTextPassword(BSTR *password) +{ + *password = NULL; + CMyComPtr getTextPassword; + Callback.QueryInterface(IID_ICryptoGetTextPassword, &getTextPassword); + if (!getTextPassword) + return E_NOTIMPL; + return getTextPassword->CryptoGetTextPassword(password); +} + +HRESULT CUpdateCallbackAgent::ShowDeleteFile(const wchar_t *name, bool /* isDir */) +{ + return Callback->DeleteOperation(name); +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Agent/UpdateCallbackAgent.h b/src/plugins/7zip/7za/cpp/7zip/UI/Agent/UpdateCallbackAgent.h new file mode 100644 index 00000000..179ce988 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Agent/UpdateCallbackAgent.h @@ -0,0 +1,22 @@ +// UpdateCallbackAgent.h + +#ifndef ZIP7_INC_UPDATE_CALLBACK_AGENT_H +#define ZIP7_INC_UPDATE_CALLBACK_AGENT_H + +#include "../Common/UpdateCallback.h" + +#include "IFolderArchive.h" + +class CUpdateCallbackAgent Z7_final: public IUpdateCallbackUI +{ + Z7_IFACE_IMP(IUpdateCallbackUI) + + CMyComPtr _cryptoGetTextPassword; + CMyComPtr Callback; + CMyComPtr Callback2; + CMyComPtr _compressProgress; +public: + void SetCallback(IFolderArchiveUpdateCallback *callback); +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Client7z/Client7z.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Client7z/Client7z.cpp index 55650cfa..b1e43dad 100644 --- a/src/plugins/7zip/7za/cpp/7zip/UI/Client7z/Client7z.cpp +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Client7z/Client7z.cpp @@ -5,10 +5,9 @@ #include #include "../../../Common/MyWindows.h" - -#include "../../../Common/Defs.h" #include "../../../Common/MyInitGuid.h" +#include "../../../Common/Defs.h" #include "../../../Common/IntToString.h" #include "../../../Common/StringConvert.h" @@ -24,46 +23,85 @@ #include "../../Archive/IArchive.h" +#if 0 +// for password request functions: +#include "../../UI/Console/UserInputUtils.h" +#endif + #include "../../IPassword.h" #include "../../../../C/7zVersion.h" #ifdef _WIN32 -HINSTANCE g_hInstance = 0; +extern +HINSTANCE g_hInstance; +HINSTANCE g_hInstance = NULL; #endif -// Tou can find the list of all GUIDs in Guid.txt file. -// use another CLSIDs, if you want to support other formats (zip, rar, ...). -// {23170F69-40C1-278A-1000-000110070000} +Z7_DIAGNOSTIC_IGNORE_CAST_FUNCTION + +// You can find full list of all GUIDs supported by 7-Zip in Guid.txt file. +// 7z format GUID: {23170F69-40C1-278A-1000-000110070000} -DEFINE_GUID(CLSID_CFormat7z, - 0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0x07, 0x00, 0x00); -DEFINE_GUID(CLSID_CFormatXz, - 0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, 0x0C, 0x00, 0x00); +#define DEFINE_GUID_ARC(name, id) Z7_DEFINE_GUID(name, \ + 0x23170F69, 0x40C1, 0x278A, 0x10, 0x00, 0x00, 0x01, 0x10, id, 0x00, 0x00); -#define CLSID_Format CLSID_CFormat7z -// #define CLSID_Format CLSID_CFormatXz +enum +{ + kId_Zip = 1, + kId_BZip2 = 2, + kId_7z = 7, + kId_Xz = 0xC, + kId_Tar = 0xEE, + kId_GZip = 0xEF +}; + +// use another id, if you want to support other formats (zip, Xz, ...). +// DEFINE_GUID_ARC (CLSID_Format, kId_Zip) +// DEFINE_GUID_ARC (CLSID_Format, kId_BZip2) +// DEFINE_GUID_ARC (CLSID_Format, kId_Xz) +// DEFINE_GUID_ARC (CLSID_Format, kId_Tar) +// DEFINE_GUID_ARC (CLSID_Format, kId_GZip) +DEFINE_GUID_ARC (CLSID_Format, kId_7z) using namespace NWindows; using namespace NFile; using namespace NDir; +#ifdef _WIN32 #define kDllName "7z.dll" +#else +#define kDllName "7z.so" +#endif -static const char *kCopyrightString = "\n7-Zip " MY_VERSION -" (" kDllName " client) " -MY_COPYRIGHT " " MY_DATE "\n"; +static const char * const kCopyrightString = + "\n" + "7-Zip" + " (" kDllName " client)" + " " MY_VERSION + " : " MY_COPYRIGHT_DATE + "\n"; -static const char *kHelpString = -"Usage: Client7z.exe [a | l | x ] archive.7z [fileName ...]\n" +static const char * const kHelpString = +"Usage: 7zcl.exe [a | l | x] archive.7z [fileName ...]\n" "Examples:\n" -" Client7z.exe a archive.7z f1.txt f2.txt : compress two files to archive.7z\n" -" Client7z.exe l archive.7z : List contents of archive.7z\n" -" Client7z.exe x archive.7z : eXtract files from archive.7z\n"; +" 7zcl.exe a archive.7z f1.txt f2.txt : compress two files to archive.7z\n" +" 7zcl.exe l archive.7z : List contents of archive.7z\n" +" 7zcl.exe x archive.7z : eXtract files from archive.7z\n"; -static AString FStringToConsoleString(const FString &s) +static void Convert_UString_to_AString(const UString &s, AString &temp) { - return GetOemString(fs2us(s)); + int codePage = CP_OEMCP; + /* + int g_CodePage = -1; + int codePage = g_CodePage; + if (codePage == -1) + codePage = CP_OEMCP; + if (codePage == CP_UTF8) + ConvertUnicodeToUTF8(s, temp); + else + */ + UnicodeStringToMultiByte2(temp, s, (UINT)codePage); } static FString CmdStringToFString(const char *s) @@ -71,46 +109,58 @@ static FString CmdStringToFString(const char *s) return us2fs(GetUnicodeString(s)); } -static void PrintString(const UString &s) +static void Print(const char *s) +{ + fputs(s, stdout); +} + +static void Print(const AString &s) { - printf("%s", (LPCSTR)GetOemString(s)); + Print(s.Ptr()); } -static void PrintString(const AString &s) +static void Print(const UString &s) { - printf("%s", (LPCSTR)s); + AString as; + Convert_UString_to_AString(s, as); + Print(as); +} + +static void Print(const wchar_t *s) +{ + Print(UString(s)); } static void PrintNewLine() { - PrintString("\n"); + Print("\n"); } -static void PrintStringLn(const AString &s) +static void PrintStringLn(const char *s) { - PrintString(s); + Print(s); PrintNewLine(); } -static void PrintError(const char *message, const FString &name) +static void PrintError(const char *message) { - printf("Error: %s", (LPCSTR)message); + Print("Error: "); PrintNewLine(); - PrintString(FStringToConsoleString(name)); + Print(message); PrintNewLine(); } -static void PrintError(const AString &s) +static void PrintError(const char *message, const FString &name) { - PrintNewLine(); - PrintString(s); - PrintNewLine(); + PrintError(message); + Print(name); } + static HRESULT IsArchiveItemProp(IInArchive *archive, UInt32 index, PROPID propID, bool &result) { NCOM::CPropVariant prop; - RINOK(archive->GetProperty(index, propID, &prop)); + RINOK(archive->GetProperty(index, propID, &prop)) if (prop.vt == VT_BOOL) result = VARIANT_BOOLToBool(prop.boolVal); else if (prop.vt == VT_EMPTY) @@ -126,25 +176,20 @@ static HRESULT IsArchiveItemFolder(IInArchive *archive, UInt32 index, bool &resu } -static const wchar_t *kEmptyFileAlias = L"[Content]"; +static const wchar_t * const kEmptyFileAlias = L"[Content]"; ////////////////////////////////////////////////////////////// // Archive Open callback class -class CArchiveOpenCallback: +class CArchiveOpenCallback Z7_final: public IArchiveOpenCallback, public ICryptoGetTextPassword, public CMyUnknownImp { + Z7_IFACES_IMP_UNK_2(IArchiveOpenCallback, ICryptoGetTextPassword) public: - MY_UNKNOWN_IMP1(ICryptoGetTextPassword) - - STDMETHOD(SetTotal)(const UInt64 *files, const UInt64 *bytes); - STDMETHOD(SetCompleted)(const UInt64 *files, const UInt64 *bytes); - - STDMETHOD(CryptoGetTextPassword)(BSTR *password); bool PasswordIsDefined; UString Password; @@ -152,67 +197,142 @@ class CArchiveOpenCallback: CArchiveOpenCallback() : PasswordIsDefined(false) {} }; -STDMETHODIMP CArchiveOpenCallback::SetTotal(const UInt64 * /* files */, const UInt64 * /* bytes */) +Z7_COM7F_IMF(CArchiveOpenCallback::SetTotal(const UInt64 * /* files */, const UInt64 * /* bytes */)) { return S_OK; } -STDMETHODIMP CArchiveOpenCallback::SetCompleted(const UInt64 * /* files */, const UInt64 * /* bytes */) +Z7_COM7F_IMF(CArchiveOpenCallback::SetCompleted(const UInt64 * /* files */, const UInt64 * /* bytes */)) { return S_OK; } -STDMETHODIMP CArchiveOpenCallback::CryptoGetTextPassword(BSTR *password) +Z7_COM7F_IMF(CArchiveOpenCallback::CryptoGetTextPassword(BSTR *password)) { if (!PasswordIsDefined) { // You can ask real password here from user - // Password = GetPassword(OutStream); - // PasswordIsDefined = true; +#if 0 + RINOK(GetPassword_HRESULT(&g_StdOut, Password)) + PasswordIsDefined = true; +#else PrintError("Password is not defined"); return E_ABORT; +#endif } return StringToBstr(Password, password); } + +static const char * const kIncorrectCommand = "incorrect command"; + ////////////////////////////////////////////////////////////// // Archive Extracting callback class -static const char *kTestingString = "Testing "; -static const char *kExtractingString = "Extracting "; -static const char *kSkippingString = "Skipping "; +static const char * const kTestingString = "Testing "; +static const char * const kExtractingString = "Extracting "; +static const char * const kSkippingString = "Skipping "; +static const char * const kReadingString = "Reading "; -static const char *kUnsupportedMethod = "Unsupported Method"; -static const char *kCRCFailed = "CRC Failed"; -static const char *kDataError = "Data Error"; -static const char *kUnavailableData = "Unavailable data"; -static const char *kUnexpectedEnd = "Unexpected end of data"; -static const char *kDataAfterEnd = "There are some data after the end of the payload data"; -static const char *kIsNotArc = "Is not archive"; -static const char *kHeadersError = "Headers Error"; +static const char * const kUnsupportedMethod = "Unsupported Method"; +static const char * const kCRCFailed = "CRC Failed"; +static const char * const kDataError = "Data Error"; +static const char * const kUnavailableData = "Unavailable data"; +static const char * const kUnexpectedEnd = "Unexpected end of data"; +static const char * const kDataAfterEnd = "There are some data after the end of the payload data"; +static const char * const kIsNotArc = "Is not archive"; +static const char * const kHeadersError = "Headers Error"; -class CArchiveExtractCallback: - public IArchiveExtractCallback, - public ICryptoGetTextPassword, - public CMyUnknownImp + +struct CArcTime { -public: - MY_UNKNOWN_IMP1(ICryptoGetTextPassword) + FILETIME FT; + UInt16 Prec; + Byte Ns100; + bool Def; - // IProgress - STDMETHOD(SetTotal)(UInt64 size); - STDMETHOD(SetCompleted)(const UInt64 *completeValue); + CArcTime() + { + Clear(); + } - // IArchiveExtractCallback - STDMETHOD(GetStream)(UInt32 index, ISequentialOutStream **outStream, Int32 askExtractMode); - STDMETHOD(PrepareOperation)(Int32 askExtractMode); - STDMETHOD(SetOperationResult)(Int32 resultEOperationResult); + void Clear() + { + FT.dwHighDateTime = FT.dwLowDateTime = 0; + Prec = 0; + Ns100 = 0; + Def = false; + } - // ICryptoGetTextPassword - STDMETHOD(CryptoGetTextPassword)(BSTR *aPassword); + bool IsZero() const + { + return FT.dwLowDateTime == 0 && FT.dwHighDateTime == 0 && Ns100 == 0; + } + + int GetNumDigits() const + { + if (Prec == k_PropVar_TimePrec_Unix || + Prec == k_PropVar_TimePrec_DOS) + return 0; + if (Prec == k_PropVar_TimePrec_HighPrec) + return 9; + if (Prec == k_PropVar_TimePrec_0) + return 7; + int digits = (int)Prec - (int)k_PropVar_TimePrec_Base; + if (digits < 0) + digits = 0; + return digits; + } + + void Write_To_FiTime(CFiTime &dest) const + { + #ifdef _WIN32 + dest = FT; + #else + if (FILETIME_To_timespec(FT, dest)) + if ((Prec == k_PropVar_TimePrec_Base + 8 || + Prec == k_PropVar_TimePrec_Base + 9) + && Ns100 != 0) + { + dest.tv_nsec += Ns100; + } + #endif + } + + void Set_From_Prop(const PROPVARIANT &prop) + { + FT = prop.filetime; + unsigned prec = 0; + unsigned ns100 = 0; + const unsigned prec_Temp = prop.wReserved1; + if (prec_Temp != 0 + && prec_Temp <= k_PropVar_TimePrec_1ns + && prop.wReserved3 == 0) + { + const unsigned ns100_Temp = prop.wReserved2; + if (ns100_Temp < 100) + { + ns100 = ns100_Temp; + prec = prec_Temp; + } + } + Prec = (UInt16)prec; + Ns100 = (Byte)ns100; + Def = true; + } +}; + + + +class CArchiveExtractCallback Z7_final: + public IArchiveExtractCallback, + public ICryptoGetTextPassword, + public CMyUnknownImp +{ + Z7_IFACES_IMP_UNK_2(IArchiveExtractCallback, ICryptoGetTextPassword) + Z7_IFACE_COM7_IMP(IProgress) -private: CMyComPtr _archiveHandler; FString _directoryPath; // Output directory UString _filePath; // name inside arcvhive @@ -220,11 +340,10 @@ class CArchiveExtractCallback: bool _extractMode; struct CProcessedFileInfo { - FILETIME MTime; + CArcTime MTime; UInt32 Attrib; bool isDir; - bool AttribDefined; - bool MTimeDefined; + bool Attrib_Defined; } _processedFileInfo; COutFileStream *_outFileStreamSpec; @@ -248,26 +367,26 @@ void CArchiveExtractCallback::Init(IInArchive *archiveHandler, const FString &di NName::NormalizeDirPathPrefix(_directoryPath); } -STDMETHODIMP CArchiveExtractCallback::SetTotal(UInt64 /* size */) +Z7_COM7F_IMF(CArchiveExtractCallback::SetTotal(UInt64 /* size */)) { return S_OK; } -STDMETHODIMP CArchiveExtractCallback::SetCompleted(const UInt64 * /* completeValue */) +Z7_COM7F_IMF(CArchiveExtractCallback::SetCompleted(const UInt64 * /* completeValue */)) { return S_OK; } -STDMETHODIMP CArchiveExtractCallback::GetStream(UInt32 index, - ISequentialOutStream **outStream, Int32 askExtractMode) +Z7_COM7F_IMF(CArchiveExtractCallback::GetStream(UInt32 index, + ISequentialOutStream **outStream, Int32 askExtractMode)) { - *outStream = 0; + *outStream = NULL; _outFileStream.Release(); { // Get Name NCOM::CPropVariant prop; - RINOK(_archiveHandler->GetProperty(index, kpidPath, &prop)); + RINOK(_archiveHandler->GetProperty(index, kpidPath, &prop)) UString fullPath; if (prop.vt == VT_EMPTY) @@ -287,36 +406,35 @@ STDMETHODIMP CArchiveExtractCallback::GetStream(UInt32 index, { // Get Attrib NCOM::CPropVariant prop; - RINOK(_archiveHandler->GetProperty(index, kpidAttrib, &prop)); + RINOK(_archiveHandler->GetProperty(index, kpidAttrib, &prop)) if (prop.vt == VT_EMPTY) { _processedFileInfo.Attrib = 0; - _processedFileInfo.AttribDefined = false; + _processedFileInfo.Attrib_Defined = false; } else { if (prop.vt != VT_UI4) return E_FAIL; _processedFileInfo.Attrib = prop.ulVal; - _processedFileInfo.AttribDefined = true; + _processedFileInfo.Attrib_Defined = true; } } - RINOK(IsArchiveItemFolder(_archiveHandler, index, _processedFileInfo.isDir)); + RINOK(IsArchiveItemFolder(_archiveHandler, index, _processedFileInfo.isDir)) { + _processedFileInfo.MTime.Clear(); // Get Modified Time NCOM::CPropVariant prop; - RINOK(_archiveHandler->GetProperty(index, kpidMTime, &prop)); - _processedFileInfo.MTimeDefined = false; + RINOK(_archiveHandler->GetProperty(index, kpidMTime, &prop)) switch (prop.vt) { case VT_EMPTY: // _processedFileInfo.MTime = _utcMTimeDefault; break; case VT_FILETIME: - _processedFileInfo.MTime = prop.filetime; - _processedFileInfo.MTimeDefined = true; + _processedFileInfo.MTime.Set_From_Prop(prop); break; default: return E_FAIL; @@ -326,7 +444,7 @@ STDMETHODIMP CArchiveExtractCallback::GetStream(UInt32 index, { // Get Size NCOM::CPropVariant prop; - RINOK(_archiveHandler->GetProperty(index, kpidSize, &prop)); + RINOK(_archiveHandler->GetProperty(index, kpidSize, &prop)) UInt64 newFileSize; /* bool newFileSizeDefined = */ ConvertPropVariantToUInt64(prop, newFileSize); } @@ -353,16 +471,16 @@ STDMETHODIMP CArchiveExtractCallback::GetStream(UInt32 index, { if (!DeleteFileAlways(fullProcessedPath)) { - PrintError("Can not delete output file", fullProcessedPath); + PrintError("Cannot delete output file", fullProcessedPath); return E_ABORT; } } _outFileStreamSpec = new COutFileStream; CMyComPtr outStreamLoc(_outFileStreamSpec); - if (!_outFileStreamSpec->Open(fullProcessedPath, CREATE_ALWAYS)) + if (!_outFileStreamSpec->Create_ALWAYS(fullProcessedPath)) { - PrintError("Can not open output file", fullProcessedPath); + PrintError("Cannot open output file", fullProcessedPath); return E_ABORT; } _outFileStream = outStreamLoc; @@ -371,24 +489,27 @@ STDMETHODIMP CArchiveExtractCallback::GetStream(UInt32 index, return S_OK; } -STDMETHODIMP CArchiveExtractCallback::PrepareOperation(Int32 askExtractMode) +Z7_COM7F_IMF(CArchiveExtractCallback::PrepareOperation(Int32 askExtractMode)) { _extractMode = false; switch (askExtractMode) { case NArchive::NExtract::NAskMode::kExtract: _extractMode = true; break; - }; + } switch (askExtractMode) { - case NArchive::NExtract::NAskMode::kExtract: PrintString(kExtractingString); break; - case NArchive::NExtract::NAskMode::kTest: PrintString(kTestingString); break; - case NArchive::NExtract::NAskMode::kSkip: PrintString(kSkippingString); break; - }; - PrintString(_filePath); + case NArchive::NExtract::NAskMode::kExtract: Print(kExtractingString); break; + case NArchive::NExtract::NAskMode::kTest: Print(kTestingString); break; + case NArchive::NExtract::NAskMode::kSkip: Print(kSkippingString); break; + case NArchive::NExtract::NAskMode::kReadExternal: Print(kReadingString); break; + default: + Print("??? "); break; + } + Print(_filePath); return S_OK; } -STDMETHODIMP CArchiveExtractCallback::SetOperationResult(Int32 operationResult) +Z7_COM7F_IMF(CArchiveExtractCallback::SetOperationResult(Int32 operationResult)) { switch (operationResult) { @@ -397,7 +518,7 @@ STDMETHODIMP CArchiveExtractCallback::SetOperationResult(Int32 operationResult) default: { NumErrors++; - PrintString(" : "); + Print(" : "); const char *s = NULL; switch (operationResult) { @@ -428,42 +549,49 @@ STDMETHODIMP CArchiveExtractCallback::SetOperationResult(Int32 operationResult) } if (s) { - PrintString("Error : "); - PrintString(s); + Print("Error : "); + Print(s); } else { char temp[16]; - ConvertUInt32ToString(operationResult, temp); - PrintString("Error #"); - PrintString(temp); + ConvertUInt32ToString((UInt32)operationResult, temp); + Print("Error #"); + Print(temp); } } } if (_outFileStream) { - if (_processedFileInfo.MTimeDefined) - _outFileStreamSpec->SetMTime(&_processedFileInfo.MTime); - RINOK(_outFileStreamSpec->Close()); + if (_processedFileInfo.MTime.Def) + { + CFiTime ft; + _processedFileInfo.MTime.Write_To_FiTime(ft); + _outFileStreamSpec->SetMTime(&ft); + } + RINOK(_outFileStreamSpec->Close()) } _outFileStream.Release(); - if (_extractMode && _processedFileInfo.AttribDefined) - SetFileAttrib(_diskFilePath, _processedFileInfo.Attrib); + if (_extractMode && _processedFileInfo.Attrib_Defined) + SetFileAttrib_PosixHighDetect(_diskFilePath, _processedFileInfo.Attrib); PrintNewLine(); return S_OK; } -STDMETHODIMP CArchiveExtractCallback::CryptoGetTextPassword(BSTR *password) +Z7_COM7F_IMF(CArchiveExtractCallback::CryptoGetTextPassword(BSTR *password)) { if (!PasswordIsDefined) { +#if 0 // You can ask real password here from user - // Password = GetPassword(OutStream); - // PasswordIsDefined = true; + RINOK(GetPassword_HRESULT(&g_StdOut, Password)) + PasswordIsDefined = true; +#else PrintError("Password is not defined"); return E_ABORT; +#endif } return StringToBstr(Password, password); } @@ -473,41 +601,24 @@ STDMETHODIMP CArchiveExtractCallback::CryptoGetTextPassword(BSTR *password) ////////////////////////////////////////////////////////////// // Archive Creating callback class -struct CDirItem +struct CDirItem: public NWindows::NFile::NFind::CFileInfoBase { - UInt64 Size; - FILETIME CTime; - FILETIME ATime; - FILETIME MTime; - UString Name; - FString FullPath; - UInt32 Attrib; + UString Path_For_Handler; + FString FullPath; // for filesystem - bool isDir() const { return (Attrib & FILE_ATTRIBUTE_DIRECTORY) != 0 ; } + CDirItem(const NWindows::NFile::NFind::CFileInfo &fi): + CFileInfoBase(fi) + {} }; -class CArchiveUpdateCallback: +class CArchiveUpdateCallback Z7_final: public IArchiveUpdateCallback2, public ICryptoGetTextPassword2, public CMyUnknownImp { -public: - MY_UNKNOWN_IMP2(IArchiveUpdateCallback2, ICryptoGetTextPassword2) - - // IProgress - STDMETHOD(SetTotal)(UInt64 size); - STDMETHOD(SetCompleted)(const UInt64 *completeValue); - - // IUpdateCallback2 - STDMETHOD(GetUpdateItemInfo)(UInt32 index, - Int32 *newData, Int32 *newProperties, UInt32 *indexInArchive); - STDMETHOD(GetProperty)(UInt32 index, PROPID propID, PROPVARIANT *value); - STDMETHOD(GetStream)(UInt32 index, ISequentialInStream **inStream); - STDMETHOD(SetOperationResult)(Int32 operationResult); - STDMETHOD(GetVolumeSize)(UInt32 index, UInt64 *size); - STDMETHOD(GetVolumeStream)(UInt32 index, ISequentialOutStream **volumeStream); - - STDMETHOD(CryptoGetTextPassword2)(Int32 *passwordIsDefined, BSTR *password); + Z7_IFACES_IMP_UNK_2(IArchiveUpdateCallback2, ICryptoGetTextPassword2) + Z7_IFACE_COM7_IMP(IProgress) + Z7_IFACE_COM7_IMP(IArchiveUpdateCallback) public: CRecordVector VolumesSizes; @@ -526,7 +637,11 @@ class CArchiveUpdateCallback: FStringVector FailedFiles; CRecordVector FailedCodes; - CArchiveUpdateCallback(): PasswordIsDefined(false), AskPassword(false), DirItems(0) {}; + CArchiveUpdateCallback(): + DirItems(NULL), + PasswordIsDefined(false), + AskPassword(false) + {} ~CArchiveUpdateCallback() { Finilize(); } HRESULT Finilize(); @@ -540,18 +655,18 @@ class CArchiveUpdateCallback: } }; -STDMETHODIMP CArchiveUpdateCallback::SetTotal(UInt64 /* size */) +Z7_COM7F_IMF(CArchiveUpdateCallback::SetTotal(UInt64 /* size */)) { return S_OK; } -STDMETHODIMP CArchiveUpdateCallback::SetCompleted(const UInt64 * /* completeValue */) +Z7_COM7F_IMF(CArchiveUpdateCallback::SetCompleted(const UInt64 * /* completeValue */)) { return S_OK; } -STDMETHODIMP CArchiveUpdateCallback::GetUpdateItemInfo(UInt32 /* index */, - Int32 *newData, Int32 *newProperties, UInt32 *indexInArchive) +Z7_COM7F_IMF(CArchiveUpdateCallback::GetUpdateItemInfo(UInt32 /* index */, + Int32 *newData, Int32 *newProperties, UInt32 *indexInArchive)) { if (newData) *newData = BoolToInt(true); @@ -562,7 +677,7 @@ STDMETHODIMP CArchiveUpdateCallback::GetUpdateItemInfo(UInt32 /* index */, return S_OK; } -STDMETHODIMP CArchiveUpdateCallback::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value) +Z7_COM7F_IMF(CArchiveUpdateCallback::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) { NCOM::CPropVariant prop; @@ -574,16 +689,17 @@ STDMETHODIMP CArchiveUpdateCallback::GetProperty(UInt32 index, PROPID propID, PR } { - const CDirItem &dirItem = (*DirItems)[index]; + const CDirItem &di = (*DirItems)[index]; switch (propID) { - case kpidPath: prop = dirItem.Name; break; - case kpidIsDir: prop = dirItem.isDir(); break; - case kpidSize: prop = dirItem.Size; break; - case kpidAttrib: prop = dirItem.Attrib; break; - case kpidCTime: prop = dirItem.CTime; break; - case kpidATime: prop = dirItem.ATime; break; - case kpidMTime: prop = dirItem.MTime; break; + case kpidPath: prop = di.Path_For_Handler; break; + case kpidIsDir: prop = di.IsDir(); break; + case kpidSize: prop = di.Size; break; + case kpidCTime: PropVariant_SetFrom_FiTime(prop, di.CTime); break; + case kpidATime: PropVariant_SetFrom_FiTime(prop, di.ATime); break; + case kpidMTime: PropVariant_SetFrom_FiTime(prop, di.MTime); break; + case kpidAttrib: prop = (UInt32)di.GetWinAttrib(); break; + case kpidPosixAttrib: prop = (UInt32)di.GetPosixAttrib(); break; } } prop.Detach(value); @@ -602,20 +718,20 @@ HRESULT CArchiveUpdateCallback::Finilize() static void GetStream2(const wchar_t *name) { - PrintString("Compressing "); + Print("Compressing "); if (name[0] == 0) name = kEmptyFileAlias; - PrintString(name); + Print(name); } -STDMETHODIMP CArchiveUpdateCallback::GetStream(UInt32 index, ISequentialInStream **inStream) +Z7_COM7F_IMF(CArchiveUpdateCallback::GetStream(UInt32 index, ISequentialInStream **inStream)) { - RINOK(Finilize()); + RINOK(Finilize()) const CDirItem &dirItem = (*DirItems)[index]; - GetStream2(dirItem.Name); + GetStream2(dirItem.Path_For_Handler); - if (dirItem.isDir()) + if (dirItem.IsDir()) return S_OK; { @@ -624,14 +740,14 @@ STDMETHODIMP CArchiveUpdateCallback::GetStream(UInt32 index, ISequentialInStream FString path = DirPrefix + dirItem.FullPath; if (!inStreamSpec->Open(path)) { - DWORD sysError = ::GetLastError(); - FailedCodes.Add(sysError); + const DWORD sysError = ::GetLastError(); + FailedCodes.Add(HRESULT_FROM_WIN32(sysError)); FailedFiles.Add(path); // if (systemError == ERROR_SHARING_VIOLATION) { PrintNewLine(); PrintError("WARNING: can't open file"); - // PrintString(NError::MyFormatMessageW(systemError)); + // Print(NError::MyFormatMessageW(systemError)); return S_FALSE; } // return sysError; @@ -641,13 +757,13 @@ STDMETHODIMP CArchiveUpdateCallback::GetStream(UInt32 index, ISequentialInStream return S_OK; } -STDMETHODIMP CArchiveUpdateCallback::SetOperationResult(Int32 /* operationResult */) +Z7_COM7F_IMF(CArchiveUpdateCallback::SetOperationResult(Int32 /* operationResult */)) { m_NeedBeClosed = true; return S_OK; } -STDMETHODIMP CArchiveUpdateCallback::GetVolumeSize(UInt32 index, UInt64 *size) +Z7_COM7F_IMF(CArchiveUpdateCallback::GetVolumeSize(UInt32 index, UInt64 *size)) { if (VolumesSizes.Size() == 0) return S_FALSE; @@ -657,7 +773,7 @@ STDMETHODIMP CArchiveUpdateCallback::GetVolumeSize(UInt32 index, UInt64 *size) return S_OK; } -STDMETHODIMP CArchiveUpdateCallback::GetVolumeStream(UInt32 index, ISequentialOutStream **volumeStream) +Z7_COM7F_IMF(CArchiveUpdateCallback::GetVolumeStream(UInt32 index, ISequentialOutStream **volumeStream)) { wchar_t temp[16]; ConvertUInt32ToString(index + 1, temp); @@ -665,28 +781,30 @@ STDMETHODIMP CArchiveUpdateCallback::GetVolumeStream(UInt32 index, ISequentialOu while (res.Len() < 2) res.InsertAtFront(L'0'); UString fileName = VolName; - fileName += L'.'; + fileName.Add_Dot(); fileName += res; fileName += VolExt; COutFileStream *streamSpec = new COutFileStream; CMyComPtr streamLoc(streamSpec); - if (!streamSpec->Create(us2fs(fileName), false)) - return ::GetLastError(); + if (!streamSpec->Create_NEW(us2fs(fileName))) + return GetLastError_noZero_HRESULT(); *volumeStream = streamLoc.Detach(); return S_OK; } -STDMETHODIMP CArchiveUpdateCallback::CryptoGetTextPassword2(Int32 *passwordIsDefined, BSTR *password) +Z7_COM7F_IMF(CArchiveUpdateCallback::CryptoGetTextPassword2(Int32 *passwordIsDefined, BSTR *password)) { if (!PasswordIsDefined) { if (AskPassword) { - // You can ask real password here from user - // Password = GetPassword(OutStream); - // PasswordIsDefined = true; +#if 0 + RINOK(GetPassword_HRESULT(&g_StdOut, Password)) + PasswordIsDefined = true; +#else PrintError("Password is not defined"); return E_ABORT; +#endif } } *passwordIsDefined = BoolToInt(PasswordIsDefined); @@ -696,62 +814,126 @@ STDMETHODIMP CArchiveUpdateCallback::CryptoGetTextPassword2(Int32 *passwordIsDef // Main function +#if defined(_UNICODE) && !defined(_WIN64) && !defined(UNDER_CE) #define NT_CHECK_FAIL_ACTION PrintError("Unsupported Windows version"); return 1; +#endif -int MY_CDECL main(int numArgs, const char *args[]) +int Z7_CDECL main(int numArgs, const char *args[]) { NT_CHECK + #ifdef ENV_HAVE_LOCALE + MY_SetLocale(); + #endif + PrintStringLn(kCopyrightString); - if (numArgs < 3) + if (numArgs < 2) { PrintStringLn(kHelpString); - return 1; + return 0; } - + + FString dllPrefix; + + #ifdef _WIN32 + dllPrefix = NDLL::GetModuleDirPrefix(); + #else + { + AString s (args[0]); + int sep = s.ReverseFind_PathSepar(); + s.DeleteFrom(sep + 1); + dllPrefix = s; + } + #endif + NDLL::CLibrary lib; - if (!lib.Load(NDLL::GetModuleDirPrefix() + FTEXT(kDllName))) + if (!lib.Load(dllPrefix + FTEXT(kDllName))) { - PrintError("Can not load 7-zip library"); + PrintError("Cannot load 7-zip library"); return 1; } - Func_CreateObject createObjectFunc = (Func_CreateObject)lib.GetProc("CreateObject"); - if (!createObjectFunc) +#if defined(__clang__) +#pragma GCC diagnostic ignored "-Wc++98-compat-pedantic" +#endif + +#ifdef _WIN32 +Z7_DIAGNOSTIC_IGNORE_CAST_FUNCTION +#endif + + Func_CreateObject + f_CreateObject = Z7_GET_PROC_ADDRESS( + Func_CreateObject, lib.Get_HMODULE(), + "CreateObject"); + if (!f_CreateObject) { - PrintError("Can not get CreateObject"); + PrintError("Cannot get CreateObject"); return 1; } - char c; + char c = 0; + UString password; + bool passwordIsDefined = false; + CObjectVector params; + + for (int curCmd = 1; curCmd < numArgs; curCmd++) { - AString command = args[1]; - if (command.Len() != 1) + AString a(args[curCmd]); + + if (!a.IsEmpty()) + { + if (a[0] == '-') + { + if (!passwordIsDefined && a[1] == 'p') + { + password = GetUnicodeString(a.Ptr(2)); + passwordIsDefined = true; + continue; + } + } + else + { + if (c) + { + params.Add(CmdStringToFString(a)); + continue; + } + if (a.Len() == 1) + { + c = (char)MyCharLower_Ascii(a[0]); + continue; + } + } + } { - PrintError("incorrect command"); + PrintError(kIncorrectCommand); return 1; } - c = (char)MyCharLower_Ascii(command[0]); } - FString archiveName = CmdStringToFString(args[2]); + if (!c || params.Size() < 1) + { + PrintError(kIncorrectCommand); + return 1; + } + + const FString &archiveName = params[0]; if (c == 'a') { // create archive command - if (numArgs < 4) + if (params.Size() < 2) { - PrintStringLn(kHelpString); + PrintError(kIncorrectCommand); return 1; } CObjectVector dirItems; { - int i; - for (i = 3; i < numArgs; i++) + unsigned i; + for (i = 1; i < params.Size(); i++) { - CDirItem di; - FString name = CmdStringToFString(args[i]); + const FString &name = params[i]; NFind::CFileInfo fi; if (!fi.Find(name)) @@ -759,13 +941,10 @@ int MY_CDECL main(int numArgs, const char *args[]) PrintError("Can't find file", name); return 1; } + + CDirItem di(fi); - di.Attrib = fi.Attrib; - di.Size = fi.Size; - di.CTime = fi.CTime; - di.ATime = fi.ATime; - di.MTime = fi.MTime; - di.Name = fs2us(name); + di.Path_For_Handler = fs2us(name); di.FullPath = name; dirItems.Add(di); } @@ -773,35 +952,37 @@ int MY_CDECL main(int numArgs, const char *args[]) COutFileStream *outFileStreamSpec = new COutFileStream; CMyComPtr outFileStream = outFileStreamSpec; - if (!outFileStreamSpec->Create(archiveName, false)) + if (!outFileStreamSpec->Create_NEW(archiveName)) { PrintError("can't create archive file"); return 1; } CMyComPtr outArchive; - if (createObjectFunc(&CLSID_Format, &IID_IOutArchive, (void **)&outArchive) != S_OK) + if (f_CreateObject(&CLSID_Format, &IID_IOutArchive, (void **)&outArchive) != S_OK) { - PrintError("Can not get class object"); + PrintError("Cannot get class object"); return 1; } CArchiveUpdateCallback *updateCallbackSpec = new CArchiveUpdateCallback; CMyComPtr updateCallback(updateCallbackSpec); updateCallbackSpec->Init(&dirItems); - // updateCallbackSpec->PasswordIsDefined = true; - // updateCallbackSpec->Password = L"1"; + updateCallbackSpec->PasswordIsDefined = passwordIsDefined; + updateCallbackSpec->Password = password; /* { const wchar_t *names[] = { + L"m", L"s", L"x" }; - const unsigned kNumProps = ARRAY_SIZE(names); + const unsigned kNumProps = Z7_ARRAY_SIZE(names); NCOM::CPropVariant values[kNumProps] = { + L"lzma", false, // solid mode OFF (UInt32)9 // compression level = 9 - ultra }; @@ -812,7 +993,11 @@ int MY_CDECL main(int numArgs, const char *args[]) PrintError("ISetProperties unsupported"); return 1; } - RINOK(setProperties->SetProperties(names, values, kNumProps)); + if (setProperties->SetProperties(names, values, kNumProps) != S_OK) + { + PrintError("SetProperties() error"); + return 1; + } } */ @@ -837,9 +1022,9 @@ int MY_CDECL main(int numArgs, const char *args[]) } else { - if (numArgs != 3) + if (params.Size() != 1) { - PrintStringLn(kHelpString); + PrintError(kIncorrectCommand); return 1; } @@ -851,14 +1036,14 @@ int MY_CDECL main(int numArgs, const char *args[]) listCommand = false; else { - PrintError("incorrect command"); + PrintError(kIncorrectCommand); return 1; } CMyComPtr archive; - if (createObjectFunc(&CLSID_Format, &IID_IInArchive, (void **)&archive) != S_OK) + if (f_CreateObject(&CLSID_Format, &IID_IInArchive, (void **)&archive) != S_OK) { - PrintError("Can not get class object"); + PrintError("Cannot get class object"); return 1; } @@ -867,21 +1052,20 @@ int MY_CDECL main(int numArgs, const char *args[]) if (!fileSpec->Open(archiveName)) { - PrintError("Can not open archive file", archiveName); + PrintError("Cannot open archive file", archiveName); return 1; } { CArchiveOpenCallback *openCallbackSpec = new CArchiveOpenCallback; CMyComPtr openCallback(openCallbackSpec); - openCallbackSpec->PasswordIsDefined = false; - // openCallbackSpec->PasswordIsDefined = true; - // openCallbackSpec->Password = L"1"; + openCallbackSpec->PasswordIsDefined = passwordIsDefined; + openCallbackSpec->Password = password; const UInt64 scanSize = 1 << 23; if (archive->Open(file, &scanSize, openCallback) != S_OK) { - PrintError("Can not open file as archive", archiveName); + PrintError("Cannot open file as archive", archiveName); return 1; } } @@ -897,19 +1081,19 @@ int MY_CDECL main(int numArgs, const char *args[]) // Get uncompressed size of file NCOM::CPropVariant prop; archive->GetProperty(i, kpidSize, &prop); - char s[32]; + char s[64]; ConvertPropVariantToShortString(prop, s); - PrintString(s); - PrintString(" "); + Print(s); + Print(" "); } { // Get name of file NCOM::CPropVariant prop; archive->GetProperty(i, kpidPath, &prop); if (prop.vt == VT_BSTR) - PrintString(prop.bstrVal); + Print(prop.bstrVal); else if (prop.vt != VT_EMPTY) - PrintString("ERROR!"); + Print("ERROR!"); } PrintNewLine(); } @@ -919,10 +1103,9 @@ int MY_CDECL main(int numArgs, const char *args[]) // Extract command CArchiveExtractCallback *extractCallbackSpec = new CArchiveExtractCallback; CMyComPtr extractCallback(extractCallbackSpec); - extractCallbackSpec->Init(archive, FTEXT("")); // second parameter is output folder path - extractCallbackSpec->PasswordIsDefined = false; - // extractCallbackSpec->PasswordIsDefined = true; - // extractCallbackSpec->Password = L"1"; + extractCallbackSpec->Init(archive, FString()); // second parameter is output folder path + extractCallbackSpec->PasswordIsDefined = passwordIsDefined; + extractCallbackSpec->Password = password; /* const wchar_t *names[] = @@ -939,7 +1122,13 @@ int MY_CDECL main(int numArgs, const char *args[]) CMyComPtr setProperties; archive->QueryInterface(IID_ISetProperties, (void **)&setProperties); if (setProperties) - setProperties->SetProperties(names, values, kNumProps); + { + if (setProperties->SetProperties(names, values, kNumProps) != S_OK) + { + PrintError("SetProperties() error"); + return 1; + } + } */ HRESULT result = archive->Extract(NULL, (UInt32)(Int32)(-1), false, extractCallback); diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Client7z/Client7z.dsp b/src/plugins/7zip/7za/cpp/7zip/UI/Client7z/Client7z.dsp index 8ad7fb13..1b5826f4 100644 --- a/src/plugins/7zip/7za/cpp/7zip/UI/Client7z/Client7z.dsp +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Client7z/Client7z.dsp @@ -66,7 +66,7 @@ LINK32=link.exe # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" # ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /Yu"stdafx.h" /FD /GZ /c -# ADD CPP /nologo /W4 /WX /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /Yu"stdafx.h" /FD /GZ /c +# ADD CPP /nologo /MDd /W4 /WX /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /Yu"stdafx.h" /FD /GZ /c # ADD BASE RSC /l 0x419 /d "_DEBUG" # ADD RSC /l 0x419 /d "_DEBUG" BSC32=bscmake.exe @@ -104,6 +104,10 @@ SOURCE=.\StdAfx.h # PROP Default_Filter "" # Begin Source File +SOURCE=..\..\..\Windows\Defs.h +# End Source File +# Begin Source File + SOURCE=..\..\..\Windows\DLL.cpp # End Source File # Begin Source File @@ -144,6 +148,10 @@ SOURCE=..\..\..\Windows\FileName.h # End Source File # Begin Source File +SOURCE=..\..\..\Windows\NtCheck.h +# End Source File +# Begin Source File + SOURCE=..\..\..\Windows\PropVariant.cpp # End Source File # Begin Source File @@ -158,12 +166,28 @@ SOURCE=..\..\..\Windows\PropVariantConv.cpp SOURCE=..\..\..\Windows\PropVariantConv.h # End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\TimeUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\TimeUtils.h +# End Source File # End Group # Begin Group "Common" # PROP Default_Filter "" # Begin Source File +SOURCE=..\..\..\Common\Common.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Defs.h +# End Source File +# Begin Source File + SOURCE=..\..\..\Common\IntToString.cpp # End Source File # Begin Source File @@ -172,6 +196,22 @@ SOURCE=..\..\..\Common\IntToString.h # End Source File # Begin Source File +SOURCE=..\..\..\Common\MyBuffer.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyCom.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyInitGuid.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyLinux.h +# End Source File +# Begin Source File + SOURCE=..\..\..\Common\MyString.cpp # End Source File # Begin Source File @@ -180,6 +220,10 @@ SOURCE=..\..\..\Common\MyString.h # End Source File # Begin Source File +SOURCE=..\..\..\Common\MyTypes.h +# End Source File +# Begin Source File + SOURCE=..\..\..\Common\MyVector.cpp # End Source File # Begin Source File @@ -188,6 +232,10 @@ SOURCE=..\..\..\Common\MyVector.h # End Source File # Begin Source File +SOURCE=..\..\..\Common\MyWindows.h +# End Source File +# Begin Source File + SOURCE=..\..\..\Common\NewHandler.cpp # End Source File # Begin Source File @@ -222,6 +270,54 @@ SOURCE=..\..\Common\FileStreams.cpp SOURCE=..\..\Common\FileStreams.h # End Source File +# Begin Source File + +SOURCE=..\..\Common\UniqBlocks.h +# End Source File +# End Group +# Begin Group "C" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\..\C\7zTypes.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zVersion.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zWindows.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Compiler.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\CpuArch.h +# End Source File +# End Group +# Begin Group "7zip" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\IDecl.h +# End Source File +# Begin Source File + +SOURCE=..\..\IPassword.h +# End Source File +# Begin Source File + +SOURCE=..\..\IProgress.h +# End Source File +# Begin Source File + +SOURCE=..\..\PropID.h +# End Source File # End Group # Begin Source File @@ -229,7 +325,7 @@ SOURCE=.\Client7z.cpp # End Source File # Begin Source File -SOURCE=..\..\..\..\C\Sort.h +SOURCE=..\..\Archive\IArchive.h # End Source File # End Target # End Project diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Client7z/StdAfx.h b/src/plugins/7zip/7za/cpp/7zip/UI/Client7z/StdAfx.h index 2854ff3e..035267cc 100644 --- a/src/plugins/7zip/7za/cpp/7zip/UI/Client7z/StdAfx.h +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Client7z/StdAfx.h @@ -1,8 +1,11 @@ // StdAfx.h -#ifndef __STDAFX_H -#define __STDAFX_H +#ifndef ZIP7_INC_STDAFX_H +#define ZIP7_INC_STDAFX_H +#if defined(_MSC_VER) && _MSC_VER >= 1800 +#pragma warning(disable : 4464) // relative include path contains '..' +#endif #include "../../../Common/Common.h" #endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Client7z/makefile b/src/plugins/7zip/7za/cpp/7zip/UI/Client7z/makefile index 99a6d494..9eff2629 100644 --- a/src/plugins/7zip/7za/cpp/7zip/UI/Client7z/makefile +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Client7z/makefile @@ -8,9 +8,9 @@ COMMON_OBJS = \ $O\IntToString.obj \ $O\NewHandler.obj \ $O\MyString.obj \ + $O\MyVector.obj \ $O\StringConvert.obj \ $O\StringToInt.obj \ - $O\MyVector.obj \ $O\Wildcard.obj \ WIN_OBJS = \ @@ -21,6 +21,7 @@ WIN_OBJS = \ $O\FileName.obj \ $O\PropVariant.obj \ $O\PropVariantConv.obj \ + $O\TimeUtils.obj \ 7ZIP_COMMON_OBJS = \ $O\FileStreams.obj \ diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Client7z/makefile.gcc b/src/plugins/7zip/7za/cpp/7zip/UI/Client7z/makefile.gcc new file mode 100644 index 00000000..0f89cb0f --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Client7z/makefile.gcc @@ -0,0 +1,72 @@ +PROG = 7zcl +IS_NOT_STANDALONE = 1 + +# IS_X64 = 1 + + + +ifdef SystemDrive +IS_MINGW = 1 +else +ifdef SYSTEMDRIVE +# ifdef OS +IS_MINGW = 1 +endif +endif + + +ifdef IS_MINGW + +SYS_OBJS = \ + $O/resource.o \ + +else + +SYS_OBJS = \ + $O/MyWindows.o \ + +endif + + +LOCAL_FLAGS = \ + + +CURRENT_OBJS = \ + $O/Client7z.o \ + +COMMON_OBJS = \ + $O/IntToString.o \ + $O/MyString.o \ + $O/MyVector.o \ + $O/NewHandler.o \ + $O/StringConvert.o \ + $O/StringToInt.o \ + $O/UTFConvert.o \ + $O/Wildcard.o \ + +WIN_OBJS = \ + $O/DLL.o \ + $O/FileDir.o \ + $O/FileFind.o \ + $O/FileIO.o \ + $O/FileName.o \ + $O/PropVariant.o \ + $O/PropVariantConv.o \ + $O/TimeUtils.o \ + +7ZIP_COMMON_OBJS = \ + $O/FileStreams.o \ + +C_OBJS = \ + $O/Alloc.o \ + +OBJS = \ + $(C_OBJS) \ + $(COMMON_OBJS) \ + $(WIN_OBJS) \ + $(SYS_OBJS) \ + $(7ZIP_COMMON_OBJS) \ + $(CURRENT_OBJS) \ + + +include ../../7zip_gcc.mak diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Client7z/resource.rc b/src/plugins/7zip/7za/cpp/7zip/UI/Client7z/resource.rc index a09bb044..462df6fa 100644 --- a/src/plugins/7zip/7za/cpp/7zip/UI/Client7z/resource.rc +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Client7z/resource.rc @@ -1,3 +1,3 @@ #include "../../MyVersionInfo.rc" -MY_VERSION_INFO_APP("7-Zip client", "7zcl") +MY_VERSION_INFO_APP("7-Zip client" , "7zcl") diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/ArchiveCommandLine.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Common/ArchiveCommandLine.cpp new file mode 100644 index 00000000..f2738706 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/ArchiveCommandLine.cpp @@ -0,0 +1,1913 @@ +// ArchiveCommandLine.cpp + +#include "StdAfx.h" +#undef printf +#undef sprintf + +#ifdef _WIN32 +#ifndef UNDER_CE +#include +#endif +#else +// for isatty() +#include +#endif + +#include + +#ifdef Z7_LARGE_PAGES +#include "../../../../C/Alloc.h" +#endif + +#include "../../../Common/IntToString.h" +#include "../../../Common/ListFileUtils.h" +#include "../../../Common/MyException.h" +#include "../../../Common/StringConvert.h" +#include "../../../Common/StringToInt.h" + +#include "../../../Windows/ErrorMsg.h" +#include "../../../Windows/FileDir.h" +#include "../../../Windows/FileName.h" +#include "../../../Windows/PropVariantConv.h" +#include "../../../Windows/System.h" +#ifdef _WIN32 +#include "../../../Windows/FileMapping.h" +#include "../../../Windows/MemoryLock.h" +#include "../../../Windows/Synchronization.h" +#endif + +#include "ArchiveCommandLine.h" +#include "EnumDirItems.h" +#include "Update.h" +#include "UpdateAction.h" + +extern bool g_CaseSensitive; +extern bool g_PathTrailReplaceMode; + +#ifdef Z7_LARGE_PAGES +extern +bool g_LargePagesMode; +bool g_LargePagesMode = false; +#endif + +/* +#ifdef ENV_HAVE_LSTAT +EXTERN_C_BEGIN +extern int global_use_lstat; +EXTERN_C_END +#endif +*/ + +#ifdef UNDER_CE + +#define MY_IS_TERMINAL(x) false; + +#else + +static bool MY_IS_TERMINAL(FILE *x) +{ +#ifdef _WIN32 + /* +crt/stdio.h: +typedef struct _iobuf FILE; +#define stdin (&_iob[0]) +#define stdout (&_iob[1]) +#define stderr (&_iob[2]) +*/ + // fprintf(stderr, "\nMY_IS_TERMINAL = %p", x); + const int fd = _fileno(x); + /* (fd) is 0, 1 or 2 in console program. + docs: If stdout or stderr is not associated with + an output stream (for example, in a Windows application + without a console window), the file descriptor returned is -2. + In previous versions, the file descriptor returned was -1. + */ + if (fd < 0) // is not associated with an output stream application (without a console window) + return false; + // fprintf(stderr, "\n\nstderr _fileno(%p) = %d", x, fd); + if (!_isatty(fd)) + return false; + // fprintf(stderr, "\nisatty_val = true"); + const HANDLE h = (HANDLE)_get_osfhandle(fd); + /* _get_osfhandle() returns intptr_t in new SDK, or long in MSVC6. + Also it can return (INVALID_HANDLE_VALUE). + docs: _get_osfhandle also returns the special value -2 when + the file descriptor is not associated with a stream + in old msvcrt.dll: it returns (-1) for incorrect value + */ + // fprintf(stderr, "\n_get_osfhandle() = %p", (void *)h); + if (h == NULL || h == INVALID_HANDLE_VALUE) + return false; + DWORD st; + // fprintf(stderr, "\nGetConsoleMode() = %u", (unsigned)GetConsoleMode(h, &st)); + return GetConsoleMode(h, &st) != 0; +#else + return isatty(fileno(x)) != 0; +#endif +} + +#endif + +using namespace NCommandLineParser; +using namespace NWindows; +using namespace NFile; + +static bool StringToUInt32(const wchar_t *s, UInt32 &v) +{ + if (*s == 0) + return false; + const wchar_t *end; + v = ConvertStringToUInt32(s, &end); + return *end == 0; +} + + +namespace NKey { +enum Enum +{ + kHelp1 = 0, + kHelp2, + kHelp3, + + kDisableHeaders, + kDisablePercents, + kShowTime, + kLogLevel, + + kOutStream, + kErrStream, + kPercentStream, + + kYes, + + kShowDialog, + kOverwrite, + + kArchiveType, + kExcludedArcType, + + kProperty, + kOutputDir, + kWorkingDir, + + kInclude, + kExclude, + kArInclude, + kArExclude, + kNoArName, + + kUpdate, + kVolume, + kRecursed, + + kAffinity, + kSfx, + kEmail, + kHash, + // kHashGenFile, + kHashDir, + kExtractMemLimit, + + kStdIn, + kStdOut, + + kLargePages, + kListfileCharSet, + kConsoleCharSet, + kTechMode, + kListFields, + kListPathSlash, + kListTimestampUTC, + + kPreserveATime, + kShareForWrite, + kStopAfterOpenError, + kCaseSensitive, + kArcNameMode, + + kUseSlashMark, + kDisableWildcardParsing, + kElimDup, + kFullPathMode, + kOutDirMode, + + kHardLinks, + kSymLinks_AllowDangerous, + kSymLinks, + kNtSecurity, + + kStoreOwnerId, + kStoreOwnerName, + + kZoneFile, + kAltStreams, + kReplaceColonForAltStream, + kWriteToAltStreamIfColon, + + kNameTrailReplace, + + kDeleteAfterCompressing, + kSetArcMTime + + #ifndef Z7_NO_CRYPTO + , kPassword + #endif +}; + +} + + +static const wchar_t kRecursedIDChar = 'r'; +static const char * const kRecursedPostCharSet = "0-"; + +static const char * const k_ArcNameMode_PostCharSet = "sea"; + +static const char * const k_Stream_PostCharSet = "012"; + +static inline EArcNameMode ParseArcNameMode(int postCharIndex) +{ + switch (postCharIndex) + { + case 1: return k_ArcNameMode_Exact; + case 2: return k_ArcNameMode_Add; + default: return k_ArcNameMode_Smart; + } +} + +namespace NRecursedPostCharIndex { + enum EEnum + { + kWildcardRecursionOnly = 0, + kNoRecursion = 1 + }; +} + +// static const char +#define kImmediateNameID '!' +#ifdef _WIN32 +#define kMapNameID '#' +#endif +#define kFileListID '@' + +static const Byte kSomeCludePostStringMinSize = 2; // at least <@|!>ame must be +static const Byte kSomeCludeAfterRecursedPostStringMinSize = 2; // at least <@|!>ame must be + +static const char * const kOverwritePostCharSet = "asut"; + +static const NExtract::NOverwriteMode::EEnum k_OverwriteModes[] = +{ + NExtract::NOverwriteMode::kOverwrite, + NExtract::NOverwriteMode::kSkip, + NExtract::NOverwriteMode::kRename, + NExtract::NOverwriteMode::kRenameExisting +}; + + + +#define SWFRM_3(t, mu, mi) t, mu, mi, NULL + +#define SWFRM_1(t) SWFRM_3(t, false, 0) +#define SWFRM_SIMPLE SWFRM_1(NSwitchType::kSimple) +#define SWFRM_MINUS SWFRM_1(NSwitchType::kMinus) +#define SWFRM_STRING SWFRM_1(NSwitchType::kString) + +#define SWFRM_STRING_SINGL(mi) SWFRM_3(NSwitchType::kString, false, mi) +#define SWFRM_STRING_MULT(mi) SWFRM_3(NSwitchType::kString, true, mi) + + +static const CSwitchForm kSwitchForms[] = +{ + { "?", SWFRM_SIMPLE }, + { "h", SWFRM_SIMPLE }, + { "-help", SWFRM_SIMPLE }, + + { "ba", SWFRM_SIMPLE }, + { "bd", SWFRM_SIMPLE }, + { "bt", SWFRM_SIMPLE }, + { "bb", SWFRM_STRING_SINGL(0) }, + + { "bso", NSwitchType::kChar, false, 1, k_Stream_PostCharSet }, + { "bse", NSwitchType::kChar, false, 1, k_Stream_PostCharSet }, + { "bsp", NSwitchType::kChar, false, 1, k_Stream_PostCharSet }, + + { "y", SWFRM_SIMPLE }, + + { "ad", SWFRM_SIMPLE }, + { "ao", NSwitchType::kChar, false, 1, kOverwritePostCharSet}, + + { "t", SWFRM_STRING_SINGL(1) }, + { "stx", SWFRM_STRING_MULT(1) }, + + { "m", SWFRM_STRING_MULT(1) }, + { "o", SWFRM_STRING_SINGL(1) }, + { "w", SWFRM_STRING }, + + { "i", SWFRM_STRING_MULT(kSomeCludePostStringMinSize) }, + { "x", SWFRM_STRING_MULT(kSomeCludePostStringMinSize) }, + { "ai", SWFRM_STRING_MULT(kSomeCludePostStringMinSize) }, + { "ax", SWFRM_STRING_MULT(kSomeCludePostStringMinSize) }, + { "an", SWFRM_SIMPLE }, + + { "u", SWFRM_STRING_MULT(1) }, + { "v", SWFRM_STRING_MULT(1) }, + { "r", NSwitchType::kChar, false, 0, kRecursedPostCharSet }, + + { "stm", SWFRM_STRING }, + { "sfx", SWFRM_STRING }, + { "seml", SWFRM_STRING_SINGL(0) }, + { "scrc", SWFRM_STRING_MULT(0) }, + // { "scrf", SWFRM_STRING_SINGL(1) }, + { "shd", SWFRM_STRING_SINGL(1) }, + { "smemx", SWFRM_STRING }, + + { "si", SWFRM_STRING }, + { "so", SWFRM_SIMPLE }, + + { "slp", SWFRM_STRING }, + { "scs", SWFRM_STRING }, + { "scc", SWFRM_STRING }, + { "slt", SWFRM_SIMPLE }, + { "slf", SWFRM_STRING_SINGL(1) }, + { "slsl", SWFRM_MINUS }, + { "slmu", SWFRM_MINUS }, + + { "ssp", SWFRM_SIMPLE }, + { "ssw", SWFRM_SIMPLE }, + { "sse", SWFRM_SIMPLE }, + { "ssc", SWFRM_MINUS }, + { "sa", NSwitchType::kChar, false, 1, k_ArcNameMode_PostCharSet }, + + { "spm", SWFRM_STRING_SINGL(0) }, + { "spd", SWFRM_SIMPLE }, + { "spe", SWFRM_MINUS }, + { "spf", SWFRM_STRING_SINGL(0) }, + { "spo", NSwitchType::kChar, false, 1, "dcr" }, // kOutDirMode + + { "snh", SWFRM_MINUS }, + { "snld", SWFRM_STRING }, + { "snl", SWFRM_MINUS }, + { "sni", SWFRM_SIMPLE }, + + { "snoi", SWFRM_MINUS }, + { "snon", SWFRM_MINUS }, + + { "snz", SWFRM_STRING_SINGL(0) }, + { "sns", SWFRM_MINUS }, + { "snr", SWFRM_SIMPLE }, + { "snc", SWFRM_SIMPLE }, + + { "snt", SWFRM_MINUS }, + + { "sdel", SWFRM_SIMPLE }, + { "stl", SWFRM_SIMPLE } + + #ifndef Z7_NO_CRYPTO + , { "p", SWFRM_STRING } + #endif +}; + +static const char * const kUniversalWildcard = "*"; +static const unsigned kMinNonSwitchWords = 1; +static const unsigned kCommandIndex = 0; + +// static const char * const kUserErrorMessage = "Incorrect command line"; +// static const char * const kCannotFindListFile = "Cannot find listfile"; +static const char * const kIncorrectListFile = "Incorrect item in listfile.\nCheck charset encoding and -scs switch."; +static const char * const kTerminalOutError = "I won't write compressed data to a terminal"; +static const char * const kSameTerminalError = "I won't write data and program's messages to same stream"; +static const char * const kEmptyFilePath = "Empty file path"; + +bool CArcCommand::IsFromExtractGroup() const +{ + switch ((int)CommandType) + { + case NCommandType::kTest: + case NCommandType::kExtract: + case NCommandType::kExtractFull: + return true; + default: + return false; + } +} + +NExtract::NPathMode::EEnum CArcCommand::GetPathMode() const +{ + switch ((int)CommandType) + { + case NCommandType::kTest: + case NCommandType::kExtractFull: + return NExtract::NPathMode::kFullPaths; + default: + return NExtract::NPathMode::kNoPaths; + } +} + +bool CArcCommand::IsFromUpdateGroup() const +{ + switch ((int)CommandType) + { + case NCommandType::kAdd: + case NCommandType::kUpdate: + case NCommandType::kDelete: + case NCommandType::kRename: + return true; + default: + return false; + } +} + +static NRecursedType::EEnum GetRecursedTypeFromIndex(int index) +{ + switch (index) + { + case NRecursedPostCharIndex::kWildcardRecursionOnly: + return NRecursedType::kWildcardOnlyRecursed; + case NRecursedPostCharIndex::kNoRecursion: + return NRecursedType::kNonRecursed; + default: + return NRecursedType::kRecursed; + } +} + +static const char * const g_Commands = "audtexlbih"; + +static bool ParseArchiveCommand(const UString &commandString, CArcCommand &command) +{ + UString s (commandString); + s.MakeLower_Ascii(); + if (s.Len() == 1) + { + if (s[0] > 0x7F) + return false; + int index = FindCharPosInString(g_Commands, (char)s[0]); + if (index < 0) + return false; + command.CommandType = (NCommandType::EEnum)index; + return true; + } + if (s.Len() == 2 && s[0] == 'r' && s[1] == 'n') + { + command.CommandType = (NCommandType::kRename); + return true; + } + return false; +} + +// ------------------------------------------------------------------ +// filenames functions + +struct CNameOption +{ + bool Include; + bool WildcardMatching; + Byte MarkMode; + NRecursedType::EEnum RecursedType; + + CNameOption(): + Include(true), + WildcardMatching(true), + MarkMode(NWildcard::kMark_FileOrDir), + RecursedType(NRecursedType::kNonRecursed) + {} +}; + + +static void AddNameToCensor(NWildcard::CCensor &censor, + const CNameOption &nop, const UString &name) +{ + bool recursed = false; + + switch ((int)nop.RecursedType) + { + case NRecursedType::kWildcardOnlyRecursed: + recursed = DoesNameContainWildcard(name); + break; + case NRecursedType::kRecursed: + recursed = true; + break; + default: + break; + } + + NWildcard::CCensorPathProps props; + props.Recursive = recursed; + props.WildcardMatching = nop.WildcardMatching; + props.MarkMode = nop.MarkMode; + censor.AddPreItem(nop.Include, name, props); +} + +#ifndef Z7_EXTRACT_ONLY +static void AddRenamePair(CObjectVector *renamePairs, + const UString &oldName, const UString &newName, NRecursedType::EEnum type, + bool wildcardMatching) +{ + CRenamePair &pair = renamePairs->AddNew(); + pair.OldName = oldName; + pair.NewName = newName; + pair.RecursedType = type; + pair.WildcardParsing = wildcardMatching; + + if (!pair.Prepare()) + { + UString val; + val += pair.OldName; + val.Add_LF(); + val += pair.NewName; + val.Add_LF(); + if (type == NRecursedType::kRecursed) + val += "-r"; + else if (type == NRecursedType::kWildcardOnlyRecursed) + val += "-r0"; + throw CArcCmdLineException("Unsupported rename command:", val); + } +} +#endif + +static void AddToCensorFromListFile( + CObjectVector *renamePairs, + NWildcard::CCensor &censor, + const CNameOption &nop, LPCWSTR fileName, UInt32 codePage) +{ + UStringVector names; + /* + if (!NFind::DoesFileExist_FollowLink(us2fs(fileName))) + throw CArcCmdLineException(kCannotFindListFile, fileName); + */ + DWORD lastError = 0; + if (!ReadNamesFromListFile2(us2fs(fileName), names, codePage, lastError)) + { + if (lastError != 0) + { + UString m; + m = "The file operation error for listfile"; + m.Add_LF(); + m += NError::MyFormatMessage(lastError); + throw CArcCmdLineException(m, fileName); + } + throw CArcCmdLineException(kIncorrectListFile, fileName); + } + if (renamePairs) + { + #ifndef Z7_EXTRACT_ONLY + if ((names.Size() & 1) != 0) + throw CArcCmdLineException(kIncorrectListFile, fileName); + for (unsigned i = 0; i < names.Size(); i += 2) + { + // change type !!!! + AddRenamePair(renamePairs, names[i], names[i + 1], nop.RecursedType, nop.WildcardMatching); + } + #else + throw "not implemented"; + #endif + } + else + FOR_VECTOR (i, names) + AddNameToCensor(censor, nop, names[i]); +} + +static void AddToCensorFromNonSwitchesStrings( + CObjectVector *renamePairs, + unsigned startIndex, + NWildcard::CCensor &censor, + const UStringVector &nonSwitchStrings, + int stopSwitchIndex, + const CNameOption &nop, + bool thereAreSwitchIncludes, UInt32 codePage) +{ + // another default + if ((renamePairs || nonSwitchStrings.Size() == startIndex) && !thereAreSwitchIncludes) + { + /* for rename command: -i switch sets the mask for archive item reading. + if (thereAreSwitchIncludes), { we don't use UniversalWildcard. } + also for non-rename command: we set UniversalWildcard, only if there are no nonSwitches. */ + // we use default fileds in (CNameOption) for UniversalWildcard. + CNameOption nop2; + // recursive mode is not important for UniversalWildcard (*) + // nop2.RecursedType = nop.RecursedType; // we don't need it + /* + nop2.RecursedType = NRecursedType::kNonRecursed; + nop2.Include = true; + nop2.WildcardMatching = true; + nop2.MarkMode = NWildcard::kMark_FileOrDir; + */ + AddNameToCensor(censor, nop2, UString(kUniversalWildcard)); + } + + int oldIndex = -1; + + if (stopSwitchIndex < 0) + stopSwitchIndex = (int)nonSwitchStrings.Size(); + + for (unsigned i = startIndex; i < nonSwitchStrings.Size(); i++) + { + const UString &s = nonSwitchStrings[i]; + if (s.IsEmpty()) + throw CArcCmdLineException(kEmptyFilePath); + if (i < (unsigned)stopSwitchIndex && s[0] == kFileListID) + AddToCensorFromListFile(renamePairs, censor, nop, s.Ptr(1), codePage); + else if (renamePairs) + { + #ifndef Z7_EXTRACT_ONLY + if (oldIndex == -1) + oldIndex = (int)i; + else + { + // NRecursedType::EEnum type is used for global wildcard (-i! switches) + AddRenamePair(renamePairs, nonSwitchStrings[(unsigned)oldIndex], s, NRecursedType::kNonRecursed, nop.WildcardMatching); + // AddRenamePair(renamePairs, nonSwitchStrings[oldIndex], s, type); + oldIndex = -1; + } + #else + throw "not implemented"; + #endif + } + else + AddNameToCensor(censor, nop, s); + } + + if (oldIndex != -1) + { + throw CArcCmdLineException("There is no second file name for rename pair:", nonSwitchStrings[(unsigned)oldIndex]); + } +} + +#ifdef _WIN32 + +struct CEventSetEnd +{ + UString Name; + + CEventSetEnd(const wchar_t *name): Name(name) {} + ~CEventSetEnd() + { + NSynchronization::CManualResetEvent event; + if (event.Open(EVENT_MODIFY_STATE, false, GetSystemString(Name)) == 0) + event.Set(); + } +}; + +static const char * const k_IncorrectMapCommand = "Incorrect Map command"; + +static const char *ParseMapWithPaths( + NWildcard::CCensor &censor, + const UString &s2, + const CNameOption &nop) +{ + UString s (s2); + const int pos = s.Find(L':'); + if (pos < 0) + return k_IncorrectMapCommand; + const int pos2 = s.Find(L':', (unsigned)(pos + 1)); + if (pos2 < 0) + return k_IncorrectMapCommand; + + CEventSetEnd eventSetEnd((const wchar_t *)s + (unsigned)(pos2 + 1)); + s.DeleteFrom((unsigned)pos2); + UInt32 size; + if (!StringToUInt32(s.Ptr((unsigned)(pos + 1)), size) + || size < sizeof(wchar_t) + || size > ((UInt32)1 << 31) + || size % sizeof(wchar_t) != 0) + return "Unsupported Map data size"; + + s.DeleteFrom((unsigned)pos); + CFileMapping map; + if (map.Open(FILE_MAP_READ, GetSystemString(s)) != 0) + return "Cannot open mapping"; + const LPVOID data = map.Map(FILE_MAP_READ, 0, size); + if (!data) + return "MapViewOfFile error"; + CFileUnmapper unmapper(data); + + UString name; + const wchar_t *p = (const wchar_t *)data; + if (*p != 0) // data format marker + return "Unsupported Map data"; + const UInt32 numChars = size / sizeof(wchar_t); + for (UInt32 i = 1; i < numChars; i++) + { + const wchar_t c = p[i]; + if (c == 0) + { + // MessageBoxW(0, name, L"7-Zip", 0); + AddNameToCensor(censor, nop, name); + name.Empty(); + } + else + name += c; + } + if (!name.IsEmpty()) + return "Map data error"; + + return NULL; +} + +#endif + +static void AddSwitchWildcardsToCensor( + NWildcard::CCensor &censor, + const UStringVector &strings, + const CNameOption &nop, + UInt32 codePage) +{ + const char *errorMessage = NULL; + unsigned i; + for (i = 0; i < strings.Size(); i++) + { + const UString &name = strings[i]; + unsigned pos = 0; + + if (name.Len() < kSomeCludePostStringMinSize) + { + errorMessage = "Too short switch"; + break; + } + + if (!nop.Include) + { + if (name.IsEqualTo_Ascii_NoCase("td")) + { + censor.ExcludeDirItems = true; + continue; + } + if (name.IsEqualTo_Ascii_NoCase("tf")) + { + censor.ExcludeFileItems = true; + continue; + } + } + + CNameOption nop2 = nop; + + bool type_WasUsed = false; + bool recursed_WasUsed = false; + bool matching_WasUsed = false; + bool error = false; + + for (;;) + { + wchar_t c = ::MyCharLower_Ascii(name[pos]); + if (c == kRecursedIDChar) + { + if (recursed_WasUsed) + { + error = true; + break; + } + recursed_WasUsed = true; + pos++; + c = name[pos]; + int index = -1; + if (c <= 0x7F) + index = FindCharPosInString(kRecursedPostCharSet, (char)c); + nop2.RecursedType = GetRecursedTypeFromIndex(index); + if (index >= 0) + { + pos++; + continue; + } + } + + if (c == 'w') + { + if (matching_WasUsed) + { + error = true; + break; + } + matching_WasUsed = true; + nop2.WildcardMatching = true; + pos++; + if (name[pos] == '-') + { + nop2.WildcardMatching = false; + pos++; + } + } + else if (c == 'm') + { + if (type_WasUsed) + { + error = true; + break; + } + type_WasUsed = true; + pos++; + nop2.MarkMode = NWildcard::kMark_StrictFile; + c = name[pos]; + if (c == '-') + { + nop2.MarkMode = NWildcard::kMark_FileOrDir; + pos++; + } + else if (c == '2') + { + nop2.MarkMode = NWildcard::kMark_StrictFile_IfWildcard; + pos++; + } + } + else + break; + } + + if (error) + { + errorMessage = "inorrect switch"; + break; + } + + if (name.Len() < pos + kSomeCludeAfterRecursedPostStringMinSize) + { + errorMessage = "Too short switch"; + break; + } + + const UString tail = name.Ptr(pos + 1); + + const wchar_t c = name[pos]; + + if (c == kImmediateNameID) + AddNameToCensor(censor, nop2, tail); + else if (c == kFileListID) + AddToCensorFromListFile(NULL, censor, nop2, tail, codePage); + #ifdef _WIN32 + else if (c == kMapNameID) + { + errorMessage = ParseMapWithPaths(censor, tail, nop2); + if (errorMessage) + break; + } + #endif + else + { + errorMessage = "Incorrect wildcard type marker"; + break; + } + } + + if (i != strings.Size()) + throw CArcCmdLineException(errorMessage, strings[i]); +} + +/* +static NUpdateArchive::NPairAction::EEnum GetUpdatePairActionType(int i) +{ + switch (i) + { + case NUpdateArchive::NPairAction::kIgnore: return NUpdateArchive::NPairAction::kIgnore; + case NUpdateArchive::NPairAction::kCopy: return NUpdateArchive::NPairAction::kCopy; + case NUpdateArchive::NPairAction::kCompress: return NUpdateArchive::NPairAction::kCompress; + case NUpdateArchive::NPairAction::kCompressAsAnti: return NUpdateArchive::NPairAction::kCompressAsAnti; + } + throw 98111603; +} +*/ + +static const char * const kUpdatePairStateIDSet = "pqrxyzw"; +static const int kUpdatePairStateNotSupportedActions[] = {2, 2, 1, -1, -1, -1, -1}; + +static const unsigned kNumUpdatePairActions = 4; +static const char * const kUpdateIgnoreItselfPostStringID = "-"; +static const wchar_t kUpdateNewArchivePostCharID = '!'; + + +static bool ParseUpdateCommandString2(const UString &command, + NUpdateArchive::CActionSet &actionSet, UString &postString) +{ + for (unsigned i = 0; i < command.Len();) + { + wchar_t c = MyCharLower_Ascii(command[i]); + int statePos = FindCharPosInString(kUpdatePairStateIDSet, (char)c); + if (c > 0x7F || statePos < 0) + { + postString = command.Ptr(i); + return true; + } + i++; + if (i >= command.Len()) + return false; + c = command[i]; + if (c < '0' || c >= (wchar_t)('0' + kNumUpdatePairActions)) + return false; + unsigned actionPos = (unsigned)(c - '0'); + actionSet.StateActions[(unsigned)statePos] = (NUpdateArchive::NPairAction::EEnum)(actionPos); + if (kUpdatePairStateNotSupportedActions[(unsigned)statePos] == (int)actionPos) + return false; + i++; + } + postString.Empty(); + return true; +} + +static void ParseUpdateCommandString(CUpdateOptions &options, + const UStringVector &updatePostStrings, + const NUpdateArchive::CActionSet &defaultActionSet) +{ + const char *errorMessage = "incorrect update switch command"; + unsigned i; + for (i = 0; i < updatePostStrings.Size(); i++) + { + const UString &updateString = updatePostStrings[i]; + if (updateString.IsEqualTo(kUpdateIgnoreItselfPostStringID)) + { + if (options.UpdateArchiveItself) + { + options.UpdateArchiveItself = false; + options.Commands.Delete(0); + } + } + else + { + NUpdateArchive::CActionSet actionSet = defaultActionSet; + + UString postString; + if (!ParseUpdateCommandString2(updateString, actionSet, postString)) + break; + if (postString.IsEmpty()) + { + if (options.UpdateArchiveItself) + options.Commands[0].ActionSet = actionSet; + } + else + { + if (postString[0] != kUpdateNewArchivePostCharID) + break; + CUpdateArchiveCommand uc; + UString archivePath = postString.Ptr(1); + if (archivePath.IsEmpty()) + break; + uc.UserArchivePath = archivePath; + uc.ActionSet = actionSet; + options.Commands.Add(uc); + } + } + } + if (i != updatePostStrings.Size()) + throw CArcCmdLineException(errorMessage, updatePostStrings[i]); +} + +bool ParseComplexSize(const wchar_t *s, UInt64 &result); + +static void SetAddCommandOptions( + NCommandType::EEnum commandType, + const CParser &parser, + CUpdateOptions &options) +{ + NUpdateArchive::CActionSet defaultActionSet; + switch ((int)commandType) + { + case NCommandType::kAdd: + defaultActionSet = NUpdateArchive::k_ActionSet_Add; + break; + case NCommandType::kDelete: + defaultActionSet = NUpdateArchive::k_ActionSet_Delete; + break; + default: + defaultActionSet = NUpdateArchive::k_ActionSet_Update; + } + + options.UpdateArchiveItself = true; + + options.Commands.Clear(); + CUpdateArchiveCommand updateMainCommand; + updateMainCommand.ActionSet = defaultActionSet; + options.Commands.Add(updateMainCommand); + if (parser[NKey::kUpdate].ThereIs) + ParseUpdateCommandString(options, parser[NKey::kUpdate].PostStrings, + defaultActionSet); + if (parser[NKey::kWorkingDir].ThereIs) + { + const UString &postString = parser[NKey::kWorkingDir].PostStrings[0]; + if (postString.IsEmpty()) + NDir::MyGetTempPath(options.WorkingDir); + else + options.WorkingDir = us2fs(postString); + } + options.SfxMode = parser[NKey::kSfx].ThereIs; + if (options.SfxMode) + options.SfxModule = us2fs(parser[NKey::kSfx].PostStrings[0]); + + if (parser[NKey::kVolume].ThereIs) + { + const UStringVector &sv = parser[NKey::kVolume].PostStrings; + FOR_VECTOR (i, sv) + { + UInt64 size; + if (!ParseComplexSize(sv[i], size)) + throw CArcCmdLineException("Incorrect volume size:", sv[i]); + if (i == sv.Size() - 1 && size == 0) + throw CArcCmdLineException("zero size last volume is not allowed"); + options.VolumesSizes.Add(size); + } + } +} + +static void SetMethodOptions(const CParser &parser, CObjectVector &properties) +{ + if (parser[NKey::kProperty].ThereIs) + { + FOR_VECTOR (i, parser[NKey::kProperty].PostStrings) + { + CProperty prop; + prop.Name = parser[NKey::kProperty].PostStrings[i]; + int index = prop.Name.Find(L'='); + if (index >= 0) + { + prop.Value = prop.Name.Ptr((unsigned)(index + 1)); + prop.Name.DeleteFrom((unsigned)index); + } + properties.Add(prop); + } + } +} + + +static inline void SetStreamMode(const CSwitchResult &sw, unsigned &res) +{ + if (sw.ThereIs) + res = (unsigned)sw.PostCharIndex; +} + + +#if defined(_WIN32) && !defined(UNDER_CE) +static void PrintHex(UString &s, UInt64 v) +{ + char temp[32]; + ConvertUInt64ToHex(v, temp); + s += temp; +} +#endif + + +#if 0 && defined(Z7_LARGE_PAGES) && defined(__linux__) +bool Get_HugePageSize(UInt64 &pageSize); +extern "C" { extern size_t g_LargePageSize; } +#endif + + +void CArcCmdLineParser::Parse1(const UStringVector &commandStrings, + CArcCmdLineOptions &options) +{ + Parse1Log.Empty(); + if (!parser.ParseStrings(kSwitchForms, Z7_ARRAY_SIZE(kSwitchForms), commandStrings)) + throw CArcCmdLineException(parser.ErrorMessage, parser.ErrorLine); + + options.IsInTerminal = MY_IS_TERMINAL(stdin); + options.IsStdOutTerminal = MY_IS_TERMINAL(stdout); + options.IsStdErrTerminal = MY_IS_TERMINAL(stderr); + + options.HelpMode = parser[NKey::kHelp1].ThereIs || parser[NKey::kHelp2].ThereIs || parser[NKey::kHelp3].ThereIs; + options.YesToAll = parser[NKey::kYes].ThereIs; + + options.StdInMode = parser[NKey::kStdIn].ThereIs; + options.StdOutMode = parser[NKey::kStdOut].ThereIs; + options.EnableHeaders = !parser[NKey::kDisableHeaders].ThereIs; + if (parser[NKey::kListFields].ThereIs) + { + const UString &s = parser[NKey::kListFields].PostStrings[0]; + options.ListFields = GetAnsiString(s); + } + if (parser[NKey::kListPathSlash].ThereIs) + { + options.ListPathSeparatorSlash.Val = !parser[NKey::kListPathSlash].WithMinus; + options.ListPathSeparatorSlash.Def = true; + } + if (parser[NKey::kListTimestampUTC].ThereIs) + g_Timestamp_Show_UTC = !parser[NKey::kListTimestampUTC].WithMinus; + options.TechMode = parser[NKey::kTechMode].ThereIs; + options.ShowTime = parser[NKey::kShowTime].ThereIs; + + if (parser[NKey::kDisablePercents].ThereIs) + options.DisablePercents = true; + + if (parser[NKey::kDisablePercents].ThereIs + || options.StdOutMode + || !options.IsStdOutTerminal) + options.Number_for_Percents = k_OutStream_disabled; + + if (options.StdOutMode) + options.Number_for_Out = k_OutStream_disabled; + + SetStreamMode(parser[NKey::kOutStream], options.Number_for_Out); + SetStreamMode(parser[NKey::kErrStream], options.Number_for_Errors); + SetStreamMode(parser[NKey::kPercentStream], options.Number_for_Percents); + + if (parser[NKey::kLogLevel].ThereIs) + { + const UString &s = parser[NKey::kLogLevel].PostStrings[0]; + if (s.IsEmpty()) + options.LogLevel = 1; + else + { + UInt32 v; + if (!StringToUInt32(s, v)) + throw CArcCmdLineException("Unsupported switch postfix -bb", s); + options.LogLevel = (unsigned)v; + } + } + + if (parser[NKey::kCaseSensitive].ThereIs) + { + options.CaseSensitive = + g_CaseSensitive = !parser[NKey::kCaseSensitive].WithMinus; + options.CaseSensitive_Change = true; + } + + + #if defined(_WIN32) && !defined(UNDER_CE) + NSecurity::EnablePrivilege_SymLink(); + #endif + + // options.LargePages = false; + +#if defined(Z7_LARGE_PAGES) + if (parser[NKey::kLargePages].ThereIs) + { + const UString &s = parser[NKey::kLargePages].PostStrings[0]; + UInt32 slp_Risk = 1; + unsigned flags = 0; + unsigned cmd = 0; + size_t pageSize = 0; + size_t threshold = 0; + + if (s.IsEqualTo("-")) + slp_Risk = 0; + else if (!s.IsEmpty()) + { + unsigned index = 0; + while (index < s.Len()) + { + const bool isStart = (index == 0); + UString s2; + { + const int pos = s.Find(L':', index); + if (pos < 0) + { + s2 = s.Ptr(index); + index = s.Len(); + } + else + { + s2 = s.Mid(index, (unsigned)pos - index); + index = (unsigned)pos + 1; + } + } + if (s2.IsEmpty()) + continue; + + if (isStart) + { + if (StringToUInt32(s2, slp_Risk)) + continue; + } + else if (s2.IsPrefixedBy_Ascii_NoCase("ps")) + { + UInt32 ps = 0; + if (StringToUInt32(s2.Ptr(2), ps)) + { + if (ps < sizeof(size_t) * 8) + { + pageSize = (size_t)1 << ps; + flags |= Z7_LARGE_PAGES_FLAG_DIRECT_PAGE_SIZE; + continue; + } + } + } + else if (s2.IsPrefixedBy_Ascii_NoCase("min")) + { + UInt32 ps = 0; + if (StringToUInt32(s2.Ptr(3), ps)) + { + if (ps < sizeof(size_t) * 8) + { + threshold = (size_t)1 << ps; + flags |= Z7_LARGE_PAGES_FLAG_DIRECT_THRESHOLD; + continue; + } + } + } + else if (s2.IsEqualTo_Ascii_NoCase("failstop")) + { + flags |= Z7_LARGE_PAGES_FLAG_FAIL_STOP; + continue; + } + else if (s2.IsEqualTo_Ascii_NoCase("nomadvise")) + { + cmd = Z7_LARGE_PAGES_FLAG_NO_MADVISE; + continue; + } + else if (s2.IsEqualTo_Ascii_NoCase("nohuge")) + { + cmd = Z7_LARGE_PAGES_FLAG_NO_HUGEPAGE; + continue; + } + throw CArcCmdLineException("Unsupported switch postfix for -slp", s); + } + } + + if (slp_Risk <= + #if defined(_WIN32) && !defined(UNDER_CE) + (unsigned)NSecurity::Get_LargePages_RiskLevel() + #else + 0 + #endif + ) + cmd = Z7_LARGE_PAGES_FLAG_NO_PAGECODE; + if (cmd == 0) + cmd = Z7_LARGE_PAGES_FLAG_USE_HUGEPAGE; + flags |= cmd; + +#if 0 && defined(Z7_LARGE_PAGES) && defined(__linux__) + if ((flags & Z7_LARGE_PAGES_FLAG_DIRECT_PAGE_SIZE) == 0) + { + UInt64 pageSize64; + if (g_LargePageSize + && Get_HugePageSize(pageSize64) + && (pageSize64 & (pageSize64 - 1)) == 0 + && pageSize64 <= (1u << 25)) + { + pageSize = (size_t)pageSize64; + flags |= Z7_LARGE_PAGES_FLAG_DIRECT_PAGE_SIZE; + printf("\npageSize=0x%x\n", (unsigned)pageSize); + } + } +#endif + +#if defined(Z7_LARGE_PAGES) + z7_LargePage_Set(flags, pageSize, threshold); + if (flags & Z7_LARGE_PAGES_FLAG_USE_HUGEPAGE) + { + // note: this process also can inherit that Privilege from parent process + g_LargePagesMode = true; + #if defined(_WIN32) && !defined(UNDER_CE) + if (!NSecurity::EnablePrivilege_LockMemory()) + { + g_LargePagesMode = false; + if (flags & Z7_LARGE_PAGES_FLAG_FAIL_STOP) + throw CSystemException(GetLastError_noZero_HRESULT()); + } + #endif + } +#endif + } +#endif // Z7_LARGE_PAGES + + +#ifndef UNDER_CE + + if (parser[NKey::kAffinity].ThereIs) + { + const UString &s = parser[NKey::kAffinity].PostStrings[0]; + if (!s.IsEmpty()) + { + AString a; + a.SetFromWStr_if_Ascii(s); + Parse1Log += "Set process affinity mask: "; + + bool isError = false; + +#ifdef _WIN32 + + UInt64 v = 0; + { + const char *end; + v = ConvertHexStringToUInt64(a, &end); + if (*end != 0) + a.Empty(); + } + if (a.IsEmpty()) + isError = true; + else + { +#ifndef _WIN64 + if (v >= ((UInt64)1 << 32)) + throw CArcCmdLineException("unsupported value -stm", s); + else +#endif + { + PrintHex(Parse1Log, v); + if (!SetProcessAffinityMask(GetCurrentProcess(), (DWORD_PTR)v)) + { + const DWORD lastError = GetLastError(); + Parse1Log += " : ERROR : "; + Parse1Log += NError::MyFormatMessage(lastError); + } + } + } + +#else // _WIN32 + + if (a.Len() != s.Len()) + isError = true; + else + { + Parse1Log += a; + NSystem::CProcessAffinity aff; + aff.CpuZero(); + unsigned cpu = 0; + unsigned i = a.Len(); + while (i) + { + unsigned v = (Byte)a[--i]; + Z7_PARSE_HEX_DIGIT(v, { isError = true; break; }) + for (unsigned mask = 1; mask != 1u << 4; mask <<= 1, cpu++) + if (v & mask) + aff.CpuSet(cpu); + } + if (!isError) + if (!aff.SetProcAffinity()) + { + const DWORD lastError = GetLastError(); + Parse1Log += " : ERROR : "; + Parse1Log += NError::MyFormatMessage(lastError); + } + } +#endif // _WIN32 + + if (isError) + throw CArcCmdLineException("Unsupported switch postfix -stm", s); + + Parse1Log.Add_LF(); + } + } + +#endif +} + + + +struct CCodePagePair +{ + const char *Name; + UInt32 CodePage; +}; + +static const unsigned kNumByteOnlyCodePages = 3; + +static const CCodePagePair g_CodePagePairs[] = +{ + { "utf-8", CP_UTF8 }, + { "win", CP_ACP }, + { "dos", CP_OEMCP }, + { "utf-16le", Z7_WIN_CP_UTF16 }, + { "utf-16be", Z7_WIN_CP_UTF16BE } +}; + +static Int32 FindCharset(const NCommandLineParser::CParser &parser, unsigned keyIndex, + bool byteOnlyCodePages, Int32 defaultVal) +{ + if (!parser[keyIndex].ThereIs) + return defaultVal; + + UString name (parser[keyIndex].PostStrings.Back()); + UInt32 v; + if (StringToUInt32(name, v)) + if (v < ((UInt32)1 << 16)) + return (Int32)v; + name.MakeLower_Ascii(); + const unsigned num = byteOnlyCodePages ? kNumByteOnlyCodePages : Z7_ARRAY_SIZE(g_CodePagePairs); + for (unsigned i = 0;; i++) + { + if (i == num) // to disable warnings from different compilers + throw CArcCmdLineException("Unsupported charset:", name); + const CCodePagePair &pair = g_CodePagePairs[i]; + if (name.IsEqualTo(pair.Name)) + return (Int32)pair.CodePage; + } +} + + +static void SetBoolPair(NCommandLineParser::CParser &parser, unsigned switchID, CBoolPair &bp) +{ + bp.Def = parser[switchID].ThereIs; + if (bp.Def) + bp.Val = !parser[switchID].WithMinus; +} + + +static bool ParseSizeString(const wchar_t *s, UInt64 &res) +{ + const wchar_t *end; + const UInt64 v = ConvertStringToUInt64(s, &end); + if (s == end) + return false; + const wchar_t c = *end; + + if (c == 0) + { + res = v; + return true; + } + if (end[1] != 0) + return false; + + unsigned numBits; + switch (MyCharLower_Ascii(c)) + { + case 'b': numBits = 0; break; + case 'k': numBits = 10; break; + case 'm': numBits = 20; break; + case 'g': numBits = 30; break; + case 't': numBits = 40; break; + default: return false; + } + const UInt64 val2 = v << numBits; + if ((val2 >> numBits) != v) + return false; + res = val2; + return true; +} + +void CArcCmdLineParser::Parse2(CArcCmdLineOptions &options) +{ + const UStringVector &nonSwitchStrings = parser.NonSwitchStrings; + const unsigned numNonSwitchStrings = nonSwitchStrings.Size(); + if (numNonSwitchStrings < kMinNonSwitchWords) + throw CArcCmdLineException("The command must be specified"); + + if (!ParseArchiveCommand(nonSwitchStrings[kCommandIndex], options.Command)) + throw CArcCmdLineException("Unsupported command:", nonSwitchStrings[kCommandIndex]); + + if (parser[NKey::kHash].ThereIs) + options.HashMethods = parser[NKey::kHash].PostStrings; + + /* + if (parser[NKey::kHashGenFile].ThereIs) + { + const UString &s = parser[NKey::kHashGenFile].PostStrings[0]; + for (unsigned i = 0 ; i < s.Len();) + { + const wchar_t c = s[i++]; + if (!options.HashOptions.ParseFlagCharOption(c, true)) + { + if (c != '=') + throw CArcCmdLineException("Unsupported hash mode switch:", s); + options.HashOptions.HashFilePath = s.Ptr(i); + break; + } + } + } + */ + + if (parser[NKey::kHashDir].ThereIs) + options.ExtractOptions.HashDir = parser[NKey::kHashDir].PostStrings[0]; + + if (parser[NKey::kExtractMemLimit].ThereIs) + { + const UString &s = parser[NKey::kExtractMemLimit].PostStrings[0]; + if (!ParseSizeString(s, options.ExtractOptions.NtOptions.MemLimit)) + throw CArcCmdLineException("Unsupported -smemx:", s); + } + + if (parser[NKey::kElimDup].ThereIs) + { + options.ExtractOptions.ElimDup.Def = true; + options.ExtractOptions.ElimDup.Val = !parser[NKey::kElimDup].WithMinus; + } + + NWildcard::ECensorPathMode censorPathMode = NWildcard::k_RelatPath; + bool fullPathMode = parser[NKey::kFullPathMode].ThereIs; + if (fullPathMode) + { + censorPathMode = NWildcard::k_AbsPath; + const UString &s = parser[NKey::kFullPathMode].PostStrings[0]; + if (!s.IsEmpty()) + { + if (s.IsEqualTo("2")) + censorPathMode = NWildcard::k_FullPath; + else + throw CArcCmdLineException("Unsupported -spf:", s); + } + } + + if (parser[NKey::kNameTrailReplace].ThereIs) + g_PathTrailReplaceMode = !parser[NKey::kNameTrailReplace].WithMinus; + + CNameOption nop; + + if (parser[NKey::kRecursed].ThereIs) + nop.RecursedType = GetRecursedTypeFromIndex(parser[NKey::kRecursed].PostCharIndex); + + if (parser[NKey::kDisableWildcardParsing].ThereIs) + nop.WildcardMatching = false; + + if (parser[NKey::kUseSlashMark].ThereIs) + { + const UString &s = parser[NKey::kUseSlashMark].PostStrings[0]; + if (s.IsEmpty()) + nop.MarkMode = NWildcard::kMark_StrictFile; + else if (s.IsEqualTo_Ascii_NoCase("-")) + nop.MarkMode = NWildcard::kMark_FileOrDir; + else if (s.IsEqualTo_Ascii_NoCase("2")) + nop.MarkMode = NWildcard::kMark_StrictFile_IfWildcard; + else + throw CArcCmdLineException("Unsupported -spm:", s); + } + + + options.ConsoleCodePage = FindCharset(parser, NKey::kConsoleCharSet, true, -1); + + const UInt32 codePage = (UInt32)FindCharset(parser, NKey::kListfileCharSet, false, CP_UTF8); + + bool thereAreSwitchIncludes = false; + + if (parser[NKey::kInclude].ThereIs) + { + thereAreSwitchIncludes = true; + nop.Include = true; + AddSwitchWildcardsToCensor(options.Censor, + parser[NKey::kInclude].PostStrings, nop, codePage); + } + + if (parser[NKey::kExclude].ThereIs) + { + nop.Include = false; + AddSwitchWildcardsToCensor(options.Censor, + parser[NKey::kExclude].PostStrings, nop, codePage); + } + + unsigned curCommandIndex = kCommandIndex + 1; + bool thereIsArchiveName = !parser[NKey::kNoArName].ThereIs && + options.Command.CommandType != NCommandType::kBenchmark && + options.Command.CommandType != NCommandType::kInfo && + options.Command.CommandType != NCommandType::kHash; + + const bool isExtractGroupCommand = options.Command.IsFromExtractGroup(); + const bool isExtractOrList = isExtractGroupCommand || options.Command.CommandType == NCommandType::kList; + const bool isRename = options.Command.CommandType == NCommandType::kRename; + options.UpdateOptions.RenameMode = isRename; + + if ((isExtractOrList || isRename) && options.StdInMode) + thereIsArchiveName = false; + + if (parser[NKey::kArcNameMode].ThereIs) + options.UpdateOptions.ArcNameMode = ParseArcNameMode(parser[NKey::kArcNameMode].PostCharIndex); + + if (thereIsArchiveName) + { + if (curCommandIndex >= numNonSwitchStrings) + throw CArcCmdLineException("Cannot find archive name"); + options.ArchiveName = nonSwitchStrings[curCommandIndex++]; + if (options.ArchiveName.IsEmpty()) + throw CArcCmdLineException("Archive name cannot by empty"); + #ifdef _WIN32 + // options.ArchiveName.Replace(L'/', WCHAR_PATH_SEPARATOR); + #endif + } + + nop.Include = true; + AddToCensorFromNonSwitchesStrings(isRename ? &options.UpdateOptions.RenamePairs : NULL, + curCommandIndex, options.Censor, + nonSwitchStrings, parser.StopSwitchIndex, + nop, + thereAreSwitchIncludes, codePage); + + #ifndef Z7_NO_CRYPTO + options.PasswordEnabled = parser[NKey::kPassword].ThereIs; + if (options.PasswordEnabled) + options.Password = parser[NKey::kPassword].PostStrings[0]; + #endif + + options.ShowDialog = parser[NKey::kShowDialog].ThereIs; + + if (parser[NKey::kArchiveType].ThereIs) + options.ArcType = parser[NKey::kArchiveType].PostStrings[0]; + + options.ExcludedArcTypes = parser[NKey::kExcludedArcType].PostStrings; + + SetMethodOptions(parser, options.Properties); + + if (parser[NKey::kNtSecurity].ThereIs) options.NtSecurity.SetTrueTrue(); + + SetBoolPair(parser, NKey::kAltStreams, options.AltStreams); + SetBoolPair(parser, NKey::kHardLinks, options.HardLinks); + SetBoolPair(parser, NKey::kSymLinks, options.SymLinks); + + SetBoolPair(parser, NKey::kStoreOwnerId, options.StoreOwnerId); + SetBoolPair(parser, NKey::kStoreOwnerName, options.StoreOwnerName); + /* + bool supportSymLink = options.SymLinks.Val; + if (!options.SymLinks.Def) + { + if (isExtractOrList) + supportSymLink = true; + else + supportSymLink = false; + } + #ifdef ENV_HAVE_LSTAT + if (supportSymLink) + global_use_lstat = 1; + else + global_use_lstat = 0; + #endif + */ + + if (isExtractOrList) + { + CExtractOptionsBase &eo = options.ExtractOptions; + + eo.ExcludeDirItems = options.Censor.ExcludeDirItems; + eo.ExcludeFileItems = options.Censor.ExcludeFileItems; + + { + CExtractNtOptions &nt = eo.NtOptions; + nt.NtSecurity = options.NtSecurity; + + nt.AltStreams = options.AltStreams; + if (!options.AltStreams.Def) + nt.AltStreams.Val = true; + + nt.HardLinks = options.HardLinks; + if (!options.HardLinks.Def) + nt.HardLinks.Val = true; + + nt.SymLinks = options.SymLinks; + if (!options.SymLinks.Def) + nt.SymLinks.Val = true; + + if (parser[NKey::kSymLinks_AllowDangerous].ThereIs) + { + const UString &s = parser[NKey::kSymLinks_AllowDangerous].PostStrings[0]; + UInt32 v = 9; // default value for "-snld" instead of default = 5 without "-snld". + if (!s.IsEmpty()) + if (!StringToUInt32(s, v)) + throw CArcCmdLineException("Unsupported switch postfix -snld", s); + nt.SymLinks_DangerousLevel = (unsigned)v; + } + + nt.ReplaceColonForAltStream = parser[NKey::kReplaceColonForAltStream].ThereIs; + nt.WriteToAltStreamIfColon = parser[NKey::kWriteToAltStreamIfColon].ThereIs; + + nt.ExtractOwner = options.StoreOwnerId.Val; // StoreOwnerName + + if (parser[NKey::kPreserveATime].ThereIs) + nt.PreserveATime = true; + if (parser[NKey::kShareForWrite].ThereIs) + nt.OpenShareForWrite = true; + } + + if (parser[NKey::kZoneFile].ThereIs) + { + eo.ZoneMode = NExtract::NZoneIdMode::kAll; + const UString &s = parser[NKey::kZoneFile].PostStrings[0]; + if (!s.IsEmpty()) + { + if (s.IsEqualTo("0")) eo.ZoneMode = NExtract::NZoneIdMode::kNone; + else if (s.IsEqualTo("1")) eo.ZoneMode = NExtract::NZoneIdMode::kAll; + else if (s.IsEqualTo("2")) eo.ZoneMode = NExtract::NZoneIdMode::kOffice; + else + throw CArcCmdLineException("Unsupported -snz:", s); + } + } + + options.Censor.AddPathsToCensor(NWildcard::k_AbsPath); + options.Censor.ExtendExclude(); + + // are there paths that look as non-relative (!Prefix.IsEmpty()) + if (!options.Censor.AllAreRelative()) + throw CArcCmdLineException("Cannot use absolute pathnames for this command"); + + NWildcard::CCensor &arcCensor = options.arcCensor; + + CNameOption nopArc; + // nopArc.RecursedType = NRecursedType::kNonRecursed; // default: we don't want recursing for archives, if -r specified + // is it OK, external switches can disable WildcardMatching and MarcMode for arc. + nopArc.WildcardMatching = nop.WildcardMatching; + nopArc.MarkMode = nop.MarkMode; + + if (parser[NKey::kArInclude].ThereIs) + { + nopArc.Include = true; + AddSwitchWildcardsToCensor(arcCensor, parser[NKey::kArInclude].PostStrings, nopArc, codePage); + } + if (parser[NKey::kArExclude].ThereIs) + { + nopArc.Include = false; + AddSwitchWildcardsToCensor(arcCensor, parser[NKey::kArExclude].PostStrings, nopArc, codePage); + } + + if (thereIsArchiveName) + { + nopArc.Include = true; + AddNameToCensor(arcCensor, nopArc, options.ArchiveName); + } + + arcCensor.AddPathsToCensor(NWildcard::k_RelatPath); + + #ifdef _WIN32 + ConvertToLongNames(arcCensor); + #endif + + arcCensor.ExtendExclude(); + + if (options.StdInMode) + options.ArcName_for_StdInMode = parser[NKey::kStdIn].PostStrings.Front(); + + if (isExtractGroupCommand) + { + if (options.StdOutMode) + { + if ( + options.Number_for_Percents == k_OutStream_stdout + // || options.Number_for_Out == k_OutStream_stdout + // || options.Number_for_Errors == k_OutStream_stdout + || + ( + (options.IsStdOutTerminal && options.IsStdErrTerminal) + && + ( + options.Number_for_Percents != k_OutStream_disabled + // || options.Number_for_Out != k_OutStream_disabled + // || options.Number_for_Errors != k_OutStream_disabled + ) + ) + ) + throw CArcCmdLineException(kSameTerminalError); + } + + if (parser[NKey::kOutputDir].ThereIs) + { + eo.OutputDir = us2fs(parser[NKey::kOutputDir].PostStrings[0]); + #ifdef _WIN32 + NFile::NName::NormalizeDirSeparators(eo.OutputDir); + #endif + NFile::NName::NormalizeDirPathPrefix(eo.OutputDir); + } + if (parser[NKey::kOutDirMode].ThereIs) + { + const int index = parser[NKey::kOutDirMode].PostCharIndex; + eo.OutDirMode = + (index == 0) ? NExtractOutDirMode::k_Direct : + (index == 1) ? NExtractOutDirMode::k_AddArcName : + NExtractOutDirMode::k_ReplaceAsterisk; + } + + eo.OverwriteMode = NExtract::NOverwriteMode::kAsk; + if (parser[NKey::kOverwrite].ThereIs) + { + eo.OverwriteMode = k_OverwriteModes[(unsigned)parser[NKey::kOverwrite].PostCharIndex]; + eo.OverwriteMode_Force = true; + } + else if (options.YesToAll) + { + eo.OverwriteMode = NExtract::NOverwriteMode::kOverwrite; + eo.OverwriteMode_Force = true; + } + } + + eo.PathMode = options.Command.GetPathMode(); + if (censorPathMode == NWildcard::k_AbsPath) + { + eo.PathMode = NExtract::NPathMode::kAbsPaths; + eo.PathMode_Force = true; + } + else if (censorPathMode == NWildcard::k_FullPath) + { + eo.PathMode = NExtract::NPathMode::kFullPaths; + eo.PathMode_Force = true; + } + } + else if (options.Command.IsFromUpdateGroup()) + { + if (parser[NKey::kArInclude].ThereIs) + throw CArcCmdLineException("-ai switch is not supported for this command"); + + CUpdateOptions &updateOptions = options.UpdateOptions; + + SetAddCommandOptions(options.Command.CommandType, parser, updateOptions); + + updateOptions.MethodMode.Properties = options.Properties; + + if (parser[NKey::kPreserveATime].ThereIs) + updateOptions.PreserveATime = true; + if (parser[NKey::kShareForWrite].ThereIs) + updateOptions.OpenShareForWrite = true; + if (parser[NKey::kStopAfterOpenError].ThereIs) + updateOptions.StopAfterOpenError = true; + + updateOptions.PathMode = censorPathMode; + + updateOptions.AltStreams = options.AltStreams; + updateOptions.NtSecurity = options.NtSecurity; + updateOptions.HardLinks = options.HardLinks; + updateOptions.SymLinks = options.SymLinks; + + updateOptions.StoreOwnerId = options.StoreOwnerId; + updateOptions.StoreOwnerName = options.StoreOwnerName; + + updateOptions.EMailMode = parser[NKey::kEmail].ThereIs; + if (updateOptions.EMailMode) + { + updateOptions.EMailAddress = parser[NKey::kEmail].PostStrings.Front(); + if (updateOptions.EMailAddress.Len() > 0) + if (updateOptions.EMailAddress[0] == L'.') + { + updateOptions.EMailRemoveAfter = true; + updateOptions.EMailAddress.Delete(0); + } + } + + updateOptions.StdOutMode = options.StdOutMode; + updateOptions.StdInMode = options.StdInMode; + + updateOptions.DeleteAfterCompressing = parser[NKey::kDeleteAfterCompressing].ThereIs; + updateOptions.SetArcMTime = parser[NKey::kSetArcMTime].ThereIs; + + if (updateOptions.StdOutMode && updateOptions.EMailMode) + throw CArcCmdLineException("stdout mode and email mode cannot be combined"); + + if (updateOptions.StdOutMode) + { + if (options.IsStdOutTerminal) + throw CArcCmdLineException(kTerminalOutError); + + if (options.Number_for_Percents == k_OutStream_stdout + || options.Number_for_Out == k_OutStream_stdout + || options.Number_for_Errors == k_OutStream_stdout) + throw CArcCmdLineException(kSameTerminalError); + } + + if (updateOptions.StdInMode) + updateOptions.StdInFileName = parser[NKey::kStdIn].PostStrings.Front(); + + if (options.Command.CommandType == NCommandType::kRename) + if (updateOptions.Commands.Size() != 1) + throw CArcCmdLineException("Only one archive can be created with rename command"); + } + else if (options.Command.CommandType == NCommandType::kBenchmark) + { + options.NumIterations = 1; + options.NumIterations_Defined = false; + if (curCommandIndex < numNonSwitchStrings) + { + if (!StringToUInt32(nonSwitchStrings[curCommandIndex], options.NumIterations)) + throw CArcCmdLineException("Incorrect number of benchmark iterations", nonSwitchStrings[curCommandIndex]); + curCommandIndex++; + options.NumIterations_Defined = true; + } + } + else if (options.Command.CommandType == NCommandType::kHash) + { + options.Censor.AddPathsToCensor(censorPathMode); + options.Censor.ExtendExclude(); + + CHashOptions &hashOptions = options.HashOptions; + hashOptions.PathMode = censorPathMode; + hashOptions.Methods = options.HashMethods; + // hashOptions.HashFilePath = options.HashFilePath; + if (parser[NKey::kPreserveATime].ThereIs) + hashOptions.PreserveATime = true; + if (parser[NKey::kShareForWrite].ThereIs) + hashOptions.OpenShareForWrite = true; + hashOptions.StdInMode = options.StdInMode; + hashOptions.AltStreamsMode = options.AltStreams.Val; + hashOptions.SymLinks = options.SymLinks; + } + else if (options.Command.CommandType == NCommandType::kInfo) + { + } + else + throw 20150919; +} + + + +#ifndef _WIN32 + +static AString g_ModuleDirPrefix; + +void Set_ModuleDirPrefix_From_ProgArg0(const char *s); +void Set_ModuleDirPrefix_From_ProgArg0(const char *s) +{ + AString a (s); + int sep = a.ReverseFind_PathSepar(); + a.DeleteFrom((unsigned)(sep + 1)); + g_ModuleDirPrefix = a; +} + +namespace NWindows { +namespace NDLL { + +FString GetModuleDirPrefix(); +FString GetModuleDirPrefix() +{ + FString s; + + s = fas2fs(g_ModuleDirPrefix); + if (s.IsEmpty()) + s = FTEXT(".") FSTRING_PATH_SEPARATOR; + return s; + /* + setenv("_7ZIP_HOME_DIR", "/test/", 0); + const char *home = getenv("_7ZIP_HOME_DIR"); + if (home) + s = home; + else + s = FTEXT(".") FSTRING_PATH_SEPARATOR; + return s; + */ +} + +}} + +#endif // ! _WIN32 diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/ArchiveCommandLine.h b/src/plugins/7zip/7za/cpp/7zip/UI/Common/ArchiveCommandLine.h new file mode 100644 index 00000000..d17ec5a3 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/ArchiveCommandLine.h @@ -0,0 +1,170 @@ +// ArchiveCommandLine.h + +#ifndef ZIP7_INC_ARCHIVE_COMMAND_LINE_H +#define ZIP7_INC_ARCHIVE_COMMAND_LINE_H + +#include "../../../Common/CommandLineParser.h" +#include "../../../Common/Wildcard.h" + +#include "EnumDirItems.h" + +#include "Extract.h" +#include "HashCalc.h" +#include "Update.h" + +typedef CMessagePathException CArcCmdLineException; + +namespace NCommandType { enum EEnum +{ + kAdd = 0, + kUpdate, + kDelete, + kTest, + kExtract, + kExtractFull, + kList, + kBenchmark, + kInfo, + kHash, + kRename +};} + +struct CArcCommand +{ + NCommandType::EEnum CommandType; + + bool IsFromExtractGroup() const; + bool IsFromUpdateGroup() const; + bool IsTestCommand() const { return CommandType == NCommandType::kTest; } + NExtract::NPathMode::EEnum GetPathMode() const; +}; + +enum +{ + k_OutStream_disabled = 0, + k_OutStream_stdout = 1, + k_OutStream_stderr = 2 +}; + +struct CArcCmdLineOptions +{ + bool HelpMode; + + // bool LargePages; + bool CaseSensitive_Change; + bool CaseSensitive; + + bool IsInTerminal; + bool IsStdOutTerminal; + bool IsStdErrTerminal; + bool StdInMode; + bool StdOutMode; + bool EnableHeaders; + bool DisablePercents; + + + bool YesToAll; + bool ShowDialog; + bool TechMode; + bool ShowTime; + CBoolPair ListPathSeparatorSlash; + + CBoolPair NtSecurity; + CBoolPair AltStreams; + CBoolPair HardLinks; + CBoolPair SymLinks; + + CBoolPair StoreOwnerId; + CBoolPair StoreOwnerName; + + AString ListFields; + + int ConsoleCodePage; + + NWildcard::CCensor Censor; + + CArcCommand Command; + UString ArchiveName; + + #ifndef Z7_NO_CRYPTO + bool PasswordEnabled; + UString Password; + #endif + + UStringVector HashMethods; + // UString HashFilePath; + + // bool AppendName; + // UStringVector ArchivePathsSorted; + // UStringVector ArchivePathsFullSorted; + NWildcard::CCensor arcCensor; + UString ArcName_for_StdInMode; + + CObjectVector Properties; + + CExtractOptionsBase ExtractOptions; + + CUpdateOptions UpdateOptions; + CHashOptions HashOptions; + UString ArcType; + UStringVector ExcludedArcTypes; + + unsigned Number_for_Out; + unsigned Number_for_Errors; + unsigned Number_for_Percents; + unsigned LogLevel; + + // bool IsOutAllowed() const { return Number_for_Out != k_OutStream_disabled; } + + // Benchmark + UInt32 NumIterations; + bool NumIterations_Defined; + + CArcCmdLineOptions(): + HelpMode(false), + // LargePages(false), + CaseSensitive_Change(false), + CaseSensitive(false), + + IsInTerminal(false), + IsStdOutTerminal(false), + IsStdErrTerminal(false), + + StdInMode(false), + StdOutMode(false), + + EnableHeaders(false), + DisablePercents(false), + + YesToAll(false), + ShowDialog(false), + TechMode(false), + ShowTime(false), + + ConsoleCodePage(-1), + + Number_for_Out(k_OutStream_stdout), + Number_for_Errors(k_OutStream_stderr), + Number_for_Percents(k_OutStream_stdout), + + LogLevel(0) + { + ListPathSeparatorSlash.Val = +#ifdef _WIN32 + false; +#else + true; +#endif + } +}; + +class CArcCmdLineParser +{ + NCommandLineParser::CParser parser; +public: + UString Parse1Log; + void Parse1(const UStringVector &commandStrings, CArcCmdLineOptions &options); + void Parse2(CArcCmdLineOptions &options); +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/ArchiveExtractCallback.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Common/ArchiveExtractCallback.cpp new file mode 100644 index 00000000..7f6da0ce --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/ArchiveExtractCallback.cpp @@ -0,0 +1,3083 @@ +// ArchiveExtractCallback.cpp + +#include "StdAfx.h" + +#undef sprintf +#undef printf + +// #include + +#include "../../../../C/Alloc.h" +#include "../../../../C/CpuArch.h" + +#include "../../../Common/ComTry.h" +#include "../../../Common/IntToString.h" +#include "../../../Common/StringConvert.h" +#include "../../../Common/UTFConvert.h" +#include "../../../Common/Wildcard.h" + +#include "../../../Windows/ErrorMsg.h" +#include "../../../Windows/FileDir.h" +#include "../../../Windows/FileFind.h" +#include "../../../Windows/FileName.h" +#include "../../../Windows/PropVariant.h" +#include "../../../Windows/PropVariantConv.h" + +#if defined(_WIN32) && !defined(UNDER_CE) && !defined(Z7_SFX) +#define Z7_USE_SECURITY_CODE +#include "../../../Windows/SecurityUtils.h" +#endif + +#include "../../Common/FilePathAutoRename.h" +#include "../../Common/StreamUtils.h" + +#include "../../Archive/Common/ItemNameUtils.h" + +#include "../Common/ExtractingFilePath.h" +#include "../Common/PropIDUtils.h" + +#include "ArchiveExtractCallback.h" + +using namespace NWindows; +using namespace NFile; +using namespace NDir; + +static const char * const kCantAutoRename = "Cannot create file with auto name"; +static const char * const kCantRenameFile = "Cannot rename existing file"; +static const char * const kCantDeleteOutputFile = "Cannot delete output file"; +static const char * const kCantDeleteOutputDir = "Cannot delete output folder"; +static const char * const kCantOpenOutFile = "Cannot open output file"; +#ifndef Z7_SFX +static const char * const kCantOpenInFile = "Cannot open input file"; +#endif +static const char * const kCantSetFileLen = "Cannot set length for output file"; +#ifdef SUPPORT_LINKS +static const char * const kCantCreateHardLink = "Cannot create hard link"; +static const char * const kCantCreateSymLink = "Cannot create symbolic link"; +static const char * const k_HardLink_to_SymLink_Ignored = "Hard link to symbolic link was ignored"; +static const char * const k_CantDelete_File_for_SymLink = "Cannot delete file for symbolic link creation"; +static const char * const k_CantDelete_Dir_for_SymLink = "Cannot delete directory for symbolic link creation"; +#endif + +static const unsigned k_LinkDataSize_LIMIT = 1 << 12; + +#ifdef SUPPORT_LINKS +#if WCHAR_PATH_SEPARATOR != L'/' + // we convert linux slashes to windows slashes for further processing. + // also we convert linux backslashes to BackslashReplacement character. + #define REPLACE_SLASHES_from_Linux_to_Sys(s) \ + { NArchive::NItemName::ReplaceToWinSlashes(s, true); } // useBackslashReplacement + // { s.Replace(L'/', WCHAR_PATH_SEPARATOR); } +#else + #define REPLACE_SLASHES_from_Linux_to_Sys(s) +#endif +#endif + +#ifndef Z7_SFX + +Z7_COM7F_IMF(COutStreamWithHash::Write(const void *data, UInt32 size, UInt32 *processedSize)) +{ + HRESULT result = S_OK; + if (_stream) + result = _stream->Write(data, size, &size); + if (_calculate) + _hash->Update(data, size); + _size += size; + if (processedSize) + *processedSize = size; + return result; +} + +#endif // Z7_SFX + + +#ifdef Z7_USE_SECURITY_CODE +bool InitLocalPrivileges(); +bool InitLocalPrivileges() +{ + NSecurity::CAccessToken token; + if (!token.OpenProcessToken(GetCurrentProcess(), + TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES)) + return false; + + TOKEN_PRIVILEGES tp; + + tp.PrivilegeCount = 1; + tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + + if (!::LookupPrivilegeValue(NULL, SE_SECURITY_NAME, &tp.Privileges[0].Luid)) + return false; + if (!token.AdjustPrivileges(&tp)) + return false; + return (GetLastError() == ERROR_SUCCESS); +} +#endif // Z7_USE_SECURITY_CODE + + + +#if defined(_WIN32) && !defined(UNDER_CE) && !defined(Z7_SFX) + +static const char * const kOfficeExtensions = + " doc dot wbk" + " docx docm dotx dotm docb wll wwl" + " xls xlt xlm" + " xlsx xlsm xltx xltm xlsb xla xlam" + " ppt pot pps ppa ppam" + " pptx pptm potx potm ppam ppsx ppsm sldx sldm" + " "; + +static bool FindExt2(const char *p, const UString &name) +{ + const int pathPos = name.ReverseFind_PathSepar(); + const int dotPos = name.ReverseFind_Dot(); + if (dotPos < 0 + || dotPos < pathPos + || dotPos == (int)name.Len() - 1) + return false; + + AString s; + for (unsigned pos = (unsigned)(dotPos + 1);; pos++) + { + const wchar_t c = name[pos]; + if (c <= 0) + break; + if (c >= 0x80) + return false; + s.Add_Char((char)MyCharLower_Ascii((char)c)); + } + for (unsigned i = 0; p[i] != 0;) + { + unsigned j; + for (j = i; p[j] != ' '; j++); + if (s.Len() == j - i && memcmp(p + i, (const char *)s, s.Len()) == 0) + return true; + i = j + 1; + } + return false; +} + + +static const char * const k_ZoneId_StreamName_With_Colon_Prefix = ":Zone.Identifier"; + +bool Is_ZoneId_StreamName(const wchar_t *s) +{ + return StringsAreEqualNoCase_Ascii(s, k_ZoneId_StreamName_With_Colon_Prefix + 1); +} + +void ReadZoneFile_Of_BaseFile(CFSTR fileName, CByteBuffer &buf) +{ + buf.Free(); + FString path (fileName); + path += k_ZoneId_StreamName_With_Colon_Prefix; + NIO::CInFile file; + if (!file.Open(path)) + return; + UInt64 fileSize; + if (!file.GetLength(fileSize)) + return; + if (fileSize == 0 || fileSize >= (1u << 15)) + return; + buf.Alloc((size_t)fileSize); + size_t processed; + if (file.ReadFull(buf, (size_t)fileSize, processed) && processed == fileSize) + return; + buf.Free(); +} + +bool WriteZoneFile_To_BaseFile(CFSTR fileName, const CByteBuffer &buf) +{ + FString path (fileName); + path += k_ZoneId_StreamName_With_Colon_Prefix; + NIO::COutFile file; + if (!file.Create_ALWAYS(path)) + return false; + return file.WriteFull(buf, buf.Size()); +} + +#endif + + +#ifdef SUPPORT_LINKS + +int CHardLinkNode::Compare(const CHardLinkNode &a) const +{ + if (StreamId < a.StreamId) return -1; + if (StreamId > a.StreamId) return 1; + return MyCompare(INode, a.INode); +} + +static HRESULT Archive_Get_HardLinkNode(IInArchive *archive, UInt32 index, CHardLinkNode &h, bool &defined) +{ + h.INode = 0; + h.StreamId = (UInt64)(Int64)-1; + defined = false; + { + NCOM::CPropVariant prop; + RINOK(archive->GetProperty(index, kpidINode, &prop)) + if (!ConvertPropVariantToUInt64(prop, h.INode)) + return S_OK; + } + { + NCOM::CPropVariant prop; + RINOK(archive->GetProperty(index, kpidStreamId, &prop)) + ConvertPropVariantToUInt64(prop, h.StreamId); + } + defined = true; + return S_OK; +} + + +HRESULT CArchiveExtractCallback::PrepareHardLinks(const CRecordVector *realIndices) +{ + _hardLinks.Clear(); + + if (!_arc->Ask_INode) + return S_OK; + + IInArchive * const archive = _arc->Archive; + CRecordVector &hardIDs = _hardLinks.IDs; + + { + UInt32 numItems; + if (realIndices) + numItems = realIndices->Size(); + else + { + RINOK(archive->GetNumberOfItems(&numItems)) + } + + for (UInt32 i = 0; i < numItems; i++) + { + CHardLinkNode h; + bool defined; + const UInt32 realIndex = realIndices ? (*realIndices)[i] : i; + + RINOK(Archive_Get_HardLinkNode(archive, realIndex, h, defined)) + if (defined) + { + bool isAltStream = false; + RINOK(Archive_IsItem_AltStream(archive, realIndex, isAltStream)) + if (!isAltStream) + { + bool isDir = false; + RINOK(Archive_IsItem_Dir(archive, realIndex, isDir)) + if (!isDir) + hardIDs.Add(h); + } + } + } + } + + hardIDs.Sort2(); + + { + // we keep only items that have 2 or more items + unsigned k = 0; + unsigned numSame = 1; + for (unsigned i = 1; i < hardIDs.Size(); i++) + { + if (hardIDs[i].Compare(hardIDs[i - 1]) != 0) + numSame = 1; + else if (++numSame == 2) + { + if (i - 1 != k) + hardIDs[k] = hardIDs[i - 1]; + k++; + } + } + hardIDs.DeleteFrom(k); + } + + _hardLinks.PrepareLinks(); + return S_OK; +} + +#endif // SUPPORT_LINKS + + +CArchiveExtractCallback::CArchiveExtractCallback(): + // Write_CTime(true), + // Write_ATime(true), + // Write_MTime(true), + Is_elimPrefix_Mode(false), + _arc(NULL), + _multiArchives(false) +{ + #ifdef Z7_USE_SECURITY_CODE + _saclEnabled = InitLocalPrivileges(); + #endif +} + + +void CArchiveExtractCallback::InitBeforeNewArchive() +{ +#if defined(_WIN32) && !defined(UNDER_CE) && !defined(Z7_SFX) + ZoneBuf.Free(); +#endif +} + +void CArchiveExtractCallback::Init( + const CExtractNtOptions &ntOptions, + const NWildcard::CCensorNode *wildcardCensor, + const CArc *arc, + IFolderArchiveExtractCallback *extractCallback2, + bool stdOutMode, bool testMode, + const FString &directoryPath, + const UStringVector &removePathParts, bool removePartsForAltStreams, + UInt64 packSize) +{ + ClearExtractedDirsInfo(); + _outFileStream.Release(); + _bufPtrSeqOutStream.Release(); + +#ifdef SUPPORT_LINKS + _hardLinks.Clear(); + _postLinks.Clear(); +#endif + +#ifdef SUPPORT_ALT_STREAMS + _renamedFiles.Clear(); +#endif + + _ntOptions = ntOptions; + _wildcardCensor = wildcardCensor; + _stdOutMode = stdOutMode; + _testMode = testMode; + _packTotal = packSize; + _progressTotal = packSize; + // _progressTotal = 0; + // _progressTotal_Defined = false; + // _progressTotal_Defined = true; + _extractCallback2 = extractCallback2; + /* + _compressProgress.Release(); + _extractCallback2.QueryInterface(IID_ICompressProgressInfo, &_compressProgress); + _callbackMessage.Release(); + _extractCallback2.QueryInterface(IID_IArchiveExtractCallbackMessage2, &_callbackMessage); + */ + _folderArchiveExtractCallback2.Release(); + _extractCallback2.QueryInterface(IID_IFolderArchiveExtractCallback2, &_folderArchiveExtractCallback2); + + #ifndef Z7_SFX + + ExtractToStreamCallback.Release(); + _extractCallback2.QueryInterface(IID_IFolderExtractToStreamCallback, &ExtractToStreamCallback); + if (ExtractToStreamCallback) + { + Int32 useStreams = 0; + if (ExtractToStreamCallback->UseExtractToStream(&useStreams) != S_OK) + useStreams = 0; + if (useStreams == 0) + ExtractToStreamCallback.Release(); + } + + #endif + + LocalProgressSpec->Init(extractCallback2, true); + LocalProgressSpec->SendProgress = false; + + _removePathParts = removePathParts; + _removePartsForAltStreams = removePartsForAltStreams; + + #ifndef Z7_SFX + _baseParentFolder = (UInt32)(Int32)-1; + _use_baseParentFolder_mode = false; + #endif + + _arc = arc; + _dirPathPrefix = directoryPath; + _dirPathPrefix_Full = directoryPath; + #if defined(_WIN32) && !defined(UNDER_CE) + if (!NName::IsAltPathPrefix(_dirPathPrefix)) + #endif + { + NName::NormalizeDirPathPrefix(_dirPathPrefix); + NDir::MyGetFullPathName(directoryPath, _dirPathPrefix_Full); + NName::NormalizeDirPathPrefix(_dirPathPrefix_Full); + } +} + + +Z7_COM7F_IMF(CArchiveExtractCallback::SetTotal(UInt64 size)) +{ + COM_TRY_BEGIN + _progressTotal = size; + // _progressTotal_Defined = true; + if (!_multiArchives && _extractCallback2) + return _extractCallback2->SetTotal(size); + return S_OK; + COM_TRY_END +} + + +static void NormalizeVals(UInt64 &v1, UInt64 &v2) +{ + const UInt64 kMax = (UInt64)1 << 31; + while (v1 > kMax) + { + v1 >>= 1; + v2 >>= 1; + } +} + + +static UInt64 MyMultDiv64(UInt64 unpCur, UInt64 unpTotal, UInt64 packTotal) +{ + NormalizeVals(packTotal, unpTotal); + NormalizeVals(unpCur, unpTotal); + if (unpTotal == 0) + unpTotal = 1; + return unpCur * packTotal / unpTotal; +} + + +Z7_COM7F_IMF(CArchiveExtractCallback::SetCompleted(const UInt64 *completeValue)) +{ + COM_TRY_BEGIN + + if (!_extractCallback2) + return S_OK; + + UInt64 packCur; + if (_multiArchives) + { + packCur = LocalProgressSpec->InSize; + if (completeValue /* && _progressTotal_Defined */) + packCur += MyMultDiv64(*completeValue, _progressTotal, _packTotal); + completeValue = &packCur; + } + return _extractCallback2->SetCompleted(completeValue); + + COM_TRY_END +} + + +Z7_COM7F_IMF(CArchiveExtractCallback::SetRatioInfo(const UInt64 *inSize, const UInt64 *outSize)) +{ + COM_TRY_BEGIN + return LocalProgressSpec.Interface()->SetRatioInfo(inSize, outSize); + COM_TRY_END +} + + +void CArchiveExtractCallback::CreateComplexDirectory( + const UStringVector &dirPathParts, bool isFinal, FString &fullPath) +{ + // we use (_item.IsDir) in this function + + bool isAbsPath = false; + + if (!dirPathParts.IsEmpty()) + { + const UString &s = dirPathParts[0]; + if (s.IsEmpty()) + isAbsPath = true; + #if defined(_WIN32) && !defined(UNDER_CE) + else + { + if (NName::IsDrivePath2(s)) + isAbsPath = true; + } + #endif + } + + if (_pathMode == NExtract::NPathMode::kAbsPaths && isAbsPath) + fullPath.Empty(); + else + fullPath = _dirPathPrefix; + + FOR_VECTOR (i, dirPathParts) + { + if (i != 0) + fullPath.Add_PathSepar(); + const UString &s = dirPathParts[i]; + fullPath += us2fs(s); + + const bool isFinalDir = (i == dirPathParts.Size() - 1 && isFinal && _item.IsDir); + + if (fullPath.IsEmpty()) + { + if (isFinalDir) + _itemFailure = true; + continue; + } + + #if defined(_WIN32) && !defined(UNDER_CE) + if (_pathMode == NExtract::NPathMode::kAbsPaths) + if (i == 0 && s.Len() == 2 && NName::IsDrivePath2(s)) + { + if (isFinalDir) + { + // we don't want to call SetAttrib() for root drive path + _itemFailure = true; + } + continue; + } + #endif + + HRESULT hres = S_OK; + if (!CreateDir(fullPath)) + hres = GetLastError_noZero_HRESULT(); + if (isFinalDir) + { + if (!NFile::NFind::DoesDirExist(fullPath)) + { + _itemFailure = true; + SendMessageError_with_Error(hres, "Cannot create folder", fullPath); + } + } + } +} + + +HRESULT CArchiveExtractCallback::GetTime(UInt32 index, PROPID propID, CArcTime &ft) +{ + ft.Clear(); + NCOM::CPropVariant prop; + RINOK(_arc->Archive->GetProperty(index, propID, &prop)) + if (prop.vt == VT_FILETIME) + ft.Set_From_Prop(prop); + else if (prop.vt != VT_EMPTY) + return E_FAIL; + return S_OK; +} + + +HRESULT CArchiveExtractCallback::GetUnpackSize() +{ + return _arc->GetItem_Size(_index, _curSize, _curSize_Defined); +} + +static void AddPathToMessage(UString &s, const FString &path) +{ + s += " : "; + s += fs2us(path); +} + +HRESULT CArchiveExtractCallback::SendMessageError(const char *message, const FString &path) const +{ + UString s (message); + AddPathToMessage(s, path); + return _extractCallback2->MessageError(s); +} + + +HRESULT CArchiveExtractCallback::SendMessageError_with_Error(HRESULT errorCode, const char *message, const FString &path) const +{ + UString s (message); + if (errorCode != S_OK) + { + s += " : "; + s += NError::MyFormatMessage(errorCode); + } + AddPathToMessage(s, path); + return _extractCallback2->MessageError(s); +} + +HRESULT CArchiveExtractCallback::SendMessageError_with_LastError(const char *message, const FString &path) const +{ + const HRESULT errorCode = GetLastError_noZero_HRESULT(); + return SendMessageError_with_Error(errorCode, message, path); +} + +HRESULT CArchiveExtractCallback::SendMessageError2(HRESULT errorCode, const char *message, const FString &path1, const FString &path2) const +{ + UString s (message); + if (errorCode != 0) + { + s += " : "; + s += NError::MyFormatMessage(errorCode); + } + AddPathToMessage(s, path1); + AddPathToMessage(s, path2); + return _extractCallback2->MessageError(s); +} + +HRESULT CArchiveExtractCallback::SendMessageError2_with_LastError( + const char *message, const FString &path1, const FString &path2) const +{ + const HRESULT errorCode = GetLastError_noZero_HRESULT(); + return SendMessageError2(errorCode, message, path1, path2); +} + +#ifndef Z7_SFX + +Z7_CLASS_IMP_COM_1( + CGetProp + , IGetProp +) +public: + UInt32 IndexInArc; + const CArc *Arc; + // UString BaseName; // relative path +}; + +Z7_COM7F_IMF(CGetProp::GetProp(PROPID propID, PROPVARIANT *value)) +{ + /* + if (propID == kpidBaseName) + { + COM_TRY_BEGIN + NCOM::CPropVariant prop = BaseName; + prop.Detach(value); + return S_OK; + COM_TRY_END + } + */ + return Arc->Archive->GetProperty(IndexInArc, propID, value); +} + +#endif // Z7_SFX + + +struct CLinkLevelsInfo +{ + bool IsAbsolute; + bool ParentDirDots_after_NonParent; + int LowLevel; + int FinalLevel; + + void Parse(const UString &path, bool isWSL); +}; + +void CLinkLevelsInfo::Parse(const UString &path, bool isWSL) +{ + IsAbsolute = isWSL ? + IS_PATH_SEPAR(path[0]) : + NName::IsAbsolutePath(path); + LowLevel = 0; + FinalLevel = 0; + ParentDirDots_after_NonParent = false; + bool nonParentDir = false; + + UStringVector parts; + SplitPathToParts(path, parts); + int level = 0; + + FOR_VECTOR (i, parts) + { + const UString &s = parts[i]; + if (s.IsEmpty()) + { + if (i == 0) + IsAbsolute = true; + continue; + } + if (s.IsEqualTo(".")) + continue; + if (s.IsEqualTo("..")) + { + if (IsAbsolute || nonParentDir) + ParentDirDots_after_NonParent = true; + level--; + if (LowLevel > level) + LowLevel = level; + } + else + { + nonParentDir = true; + level++; + } + } + + FinalLevel = level; +} + + +static bool IsSafePath(const UString &path, bool isWSL) +{ + CLinkLevelsInfo levelsInfo; + levelsInfo.Parse(path, isWSL); + return !levelsInfo.IsAbsolute + && levelsInfo.LowLevel >= 0 + && levelsInfo.FinalLevel > 0; +} + +bool IsSafePath(const UString &path); +bool IsSafePath(const UString &path) +{ + return IsSafePath(path, false); // isWSL +} + +bool CensorNode_CheckPath2(const NWildcard::CCensorNode &node, const CReadArcItem &item, bool &include); +bool CensorNode_CheckPath2(const NWildcard::CCensorNode &node, const CReadArcItem &item, bool &include) +{ + bool found = false; + + // CheckPathVect() doesn't check path to Parent nodes + if (node.CheckPathVect(item.PathParts, !item.MainIsDir, include)) + { + if (!include) + return true; + + #ifdef SUPPORT_ALT_STREAMS + if (!item.IsAltStream) + return true; + #endif + + found = true; + } + + #ifdef SUPPORT_ALT_STREAMS + + if (!item.IsAltStream) + return false; + + UStringVector pathParts2 = item.PathParts; + if (pathParts2.IsEmpty()) + pathParts2.AddNew(); + UString &back = pathParts2.Back(); + back.Add_Colon(); + back += item.AltStreamName; + bool include2; + + if (node.CheckPathVect(pathParts2, + true, // isFile, + include2)) + { + include = include2; + return true; + } + + #endif // SUPPORT_ALT_STREAMS + + return found; +} + + +bool CensorNode_CheckPath(const NWildcard::CCensorNode &node, const CReadArcItem &item) +{ + bool include; + if (CensorNode_CheckPath2(node, item, include)) + return include; + return false; +} + + +static FString MakePath_from_2_Parts(const FString &prefix, const FString &path) +{ + FString s (prefix); + #if defined(_WIN32) && !defined(UNDER_CE) + if (!path.IsEmpty() && path[0] == ':' && !prefix.IsEmpty() && IsPathSepar(prefix.Back())) + { + if (!NName::IsDriveRootPath_SuperAllowed(prefix)) + s.DeleteBack(); + } + #endif + s += path; + return s; +} + + + +#ifdef SUPPORT_LINKS + +/* +struct CTempMidBuffer +{ + void *Buf; + + CTempMidBuffer(size_t size): Buf(NULL) { Buf = ::MidAlloc(size); } + ~CTempMidBuffer() { ::MidFree(Buf); } +}; + +HRESULT CArchiveExtractCallback::MyCopyFile(ISequentialOutStream *outStream) +{ + const size_t kBufSize = 1 << 16; + CTempMidBuffer buf(kBufSize); + if (!buf.Buf) + return E_OUTOFMEMORY; + + NIO::CInFile inFile; + NIO::COutFile outFile; + + if (!inFile.Open(_copyFile_Path)) + return SendMessageError_with_LastError("Open error", _copyFile_Path); + + for (;;) + { + UInt32 num; + + if (!inFile.Read(buf.Buf, kBufSize, num)) + return SendMessageError_with_LastError("Read error", _copyFile_Path); + + if (num == 0) + return S_OK; + + + RINOK(WriteStream(outStream, buf.Buf, num)); + } +} +*/ + + +HRESULT CArchiveExtractCallback::ReadLink() +{ + IInArchive * const archive = _arc->Archive; + const UInt32 index = _index; + // _link.Clear(); // _link.Clear() was called already. + { + NCOM::CPropVariant prop; + RINOK(archive->GetProperty(index, kpidHardLink, &prop)) + if (prop.vt == VT_BSTR) + { + _link.LinkType = k_LinkType_HardLink; + _link.isRelative = false; // RAR5, TAR: hard links are from root folder of archive + _link.LinkPath.SetFromBstr(prop.bstrVal); + // 7-Zip 24-: tar handler returned original path (with linux slash in most case) + // 7-Zip 24-: rar5 handler returned path with system slash. + // 7-Zip 25+: tar/rar5 handlers return linux path in most cases. + } + else if (prop.vt != VT_EMPTY) + return E_FAIL; + } + /* + { + NCOM::CPropVariant prop; + RINOK(archive->GetProperty(index, kpidCopyLink, &prop)); + if (prop.vt == VT_BSTR) + { + _link.LinkType = k_LinkType_CopyLink; + _link.isRelative = false; // RAR5: copy links are from root folder of archive + _link.LinkPath.SetFromBstr(prop.bstrVal); + } + else if (prop.vt != VT_EMPTY) + return E_FAIL; + } + */ + { + NCOM::CPropVariant prop; + RINOK(archive->GetProperty(index, kpidSymLink, &prop)) + if (prop.vt == VT_BSTR) + { + _link.LinkType = k_LinkType_PureSymLink; + _link.isRelative = true; // RAR5, TAR: symbolic links are relative by default + _link.LinkPath.SetFromBstr(prop.bstrVal); + // 7-Zip 24-: (tar, cpio, xar, ext, iso) handlers returned returned original path (with linux slash in most case) + // 7-Zip 24-: rar5 handler returned path with system slash. + // 7-Zip 25+: all handlers return linux path in most cases. + } + else if (prop.vt != VT_EMPTY) + return E_FAIL; + } + + // linux path separator in (_link.LinkPath) is expected for most cases, + // if new handler code is used, and if data in archive is correct. + // NtReparse_Data = NULL; + // NtReparse_Size = 0; + if (!_link.LinkPath.IsEmpty()) + { + REPLACE_SLASHES_from_Linux_to_Sys(_link.LinkPath) + } + else if (_arc->GetRawProps) + { + const void *data; + UInt32 dataSize, propType; + if (_arc->GetRawProps->GetRawProp(_index, kpidNtReparse, &data, &dataSize, &propType) == S_OK + // && dataSize == 1234567 // for debug: unpacking without reparse + && dataSize) + { + if (propType != NPropDataType::kRaw) + return E_FAIL; + // 21.06: we need kpidNtReparse in linux for wim archives created in Windows + // NtReparse_Data = data; + // NtReparse_Size = dataSize; + // we ignore error code here, if there is failure of parsing: + _link.Parse_from_WindowsReparseData((const Byte *)data, dataSize); + } + } + + if (_link.LinkPath.IsEmpty()) + return S_OK; + // (_link.LinkPath) uses system path separator. + // windows: (_link.LinkPath) doesn't contain linux separator (slash). + { + // _link.LinkPath = "\\??\\r:\\1\\2"; // for debug + // rar5+ returns kpidSymLink absolute link path with "\??\" prefix. + // we normalize such prefix: + if (_link.LinkPath.IsPrefixedBy(STRING_PATH_SEPARATOR "??" STRING_PATH_SEPARATOR)) + { + _link.isRelative = false; + // we normalize prefix from "\??\" to "\\?\": + _link.LinkPath.ReplaceOneCharAtPos(1, WCHAR_PATH_SEPARATOR); + _link.isWindowsPath = true; + if (_link.LinkPath.IsPrefixedBy_Ascii_NoCase( + STRING_PATH_SEPARATOR + STRING_PATH_SEPARATOR "?" + STRING_PATH_SEPARATOR "UNC" + STRING_PATH_SEPARATOR)) + { + // we normalize prefix from "\\?\UNC\path" to "\\path": + _link.LinkPath.DeleteFrontal(6); + _link.LinkPath.ReplaceOneCharAtPos(0, WCHAR_PATH_SEPARATOR); + } + else + { + const unsigned k_prefix_Size = 4; + if (NName::IsDrivePath(_link.LinkPath.Ptr(k_prefix_Size))) + _link.LinkPath.DeleteFrontal(k_prefix_Size); + } + } + } + _link.Normalize_to_RelativeSafe(_removePathParts); + return S_OK; +} + +#endif // SUPPORT_LINKS + + +#ifndef _WIN32 + +static HRESULT GetOwner(IInArchive *archive, + UInt32 index, UInt32 pidName, UInt32 pidId, CProcessedFileInfo::COwnerInfo &res) +{ + { + NWindows::NCOM::CPropVariant prop; + RINOK(archive->GetProperty(index, pidId, &prop)) + if (prop.vt == VT_UI4) + { + res.Id_Defined = true; + res.Id = prop.ulVal; + // res.Id++; // for debug + // if (pidId == kpidGroupId) res.Id += 7; // for debug + // res.Id = 0; // for debug + } + else if (prop.vt != VT_EMPTY) + return E_INVALIDARG; + } + { + NWindows::NCOM::CPropVariant prop; + RINOK(archive->GetProperty(index, pidName, &prop)) + if (prop.vt == VT_BSTR) + { + const UString s = prop.bstrVal; + ConvertUnicodeToUTF8(s, res.Name); + } + else if (prop.vt == VT_UI4) + { + res.Id_Defined = true; + res.Id = prop.ulVal; + } + else if (prop.vt != VT_EMPTY) + return E_INVALIDARG; + } + return S_OK; +} + +#endif + + +HRESULT CArchiveExtractCallback::Read_fi_Props() +{ + IInArchive * const archive = _arc->Archive; + const UInt32 index = _index; + + _fi.Attrib_Defined = false; + + #ifndef _WIN32 + _fi.Owner.Clear(); + _fi.Group.Clear(); + #endif + + { + NCOM::CPropVariant prop; + RINOK(archive->GetProperty(index, kpidPosixAttrib, &prop)) + if (prop.vt == VT_UI4) + { + _fi.SetFromPosixAttrib(prop.ulVal); + } + else if (prop.vt != VT_EMPTY) + return E_FAIL; + } + + { + NCOM::CPropVariant prop; + RINOK(archive->GetProperty(index, kpidAttrib, &prop)) + if (prop.vt == VT_UI4) + { + _fi.Attrib = prop.ulVal; + _fi.Attrib_Defined = true; + } + else if (prop.vt != VT_EMPTY) + return E_FAIL; + } + + RINOK(GetTime(index, kpidCTime, _fi.CTime)) + RINOK(GetTime(index, kpidATime, _fi.ATime)) + RINOK(GetTime(index, kpidMTime, _fi.MTime)) + + #ifndef _WIN32 + if (_ntOptions.ExtractOwner) + { + // SendMessageError_with_LastError("_ntOptions.ExtractOwner", _diskFilePath); + GetOwner(archive, index, kpidUser, kpidUserId, _fi.Owner); + GetOwner(archive, index, kpidGroup, kpidGroupId, _fi.Group); + } + #endif + + return S_OK; +} + + + +void CArchiveExtractCallback::CorrectPathParts() +{ + UStringVector &pathParts = _item.PathParts; + + #ifdef SUPPORT_ALT_STREAMS + if (!_item.IsAltStream + || !pathParts.IsEmpty() + || !(_removePartsForAltStreams || _pathMode == NExtract::NPathMode::kNoPathsAlt)) + #endif + Correct_FsPath(_pathMode == NExtract::NPathMode::kAbsPaths, _keepAndReplaceEmptyDirPrefixes, pathParts, _item.MainIsDir); + + #ifdef SUPPORT_ALT_STREAMS + + if (_item.IsAltStream) + { + UString s (_item.AltStreamName); + Correct_AltStream_Name(s); + bool needColon = true; + + if (pathParts.IsEmpty()) + { + pathParts.AddNew(); + if (_removePartsForAltStreams || _pathMode == NExtract::NPathMode::kNoPathsAlt) + needColon = false; + } + #ifdef _WIN32 + else if (_pathMode == NExtract::NPathMode::kAbsPaths && + NWildcard::GetNumPrefixParts_if_DrivePath(pathParts) == pathParts.Size()) + pathParts.AddNew(); + #endif + + UString &name = pathParts.Back(); + if (needColon) + name.Add_Char((char)(_ntOptions.ReplaceColonForAltStream ? '_' : ':')); + name += s; + } + + #endif // SUPPORT_ALT_STREAMS +} + + +static void GetFiTimesCAM(const CProcessedFileInfo &fi, CFiTimesCAM &pt, const CArc &arc) +{ + pt.CTime_Defined = false; + pt.ATime_Defined = false; + pt.MTime_Defined = false; + + // if (Write_MTime) + { + if (fi.MTime.Def) + { + fi.MTime.Write_To_FiTime(pt.MTime); + pt.MTime_Defined = true; + } + else if (arc.MTime.Def) + { + arc.MTime.Write_To_FiTime(pt.MTime); + pt.MTime_Defined = true; + } + } + + if (/* Write_CTime && */ fi.CTime.Def) + { + fi.CTime.Write_To_FiTime(pt.CTime); + pt.CTime_Defined = true; + } + + if (/* Write_ATime && */ fi.ATime.Def) + { + fi.ATime.Write_To_FiTime(pt.ATime); + pt.ATime_Defined = true; + } +} + + +void CArchiveExtractCallback::CreateFolders() +{ + // 21.04 : we don't change original (_item.PathParts) here + UStringVector pathParts = _item.PathParts; + + bool isFinal = true; + // bool is_DirOp = false; + if (!pathParts.IsEmpty()) + { + /* v23: if we extract symlink, and we know that it links to dir: + Linux: we don't create dir item (symlink_from_path) here. + Windows: SetReparseData() will create dir item, if it doesn't exist, + but if we create dir item here, it's not problem. */ + if (!_item.IsDir + #ifdef SUPPORT_LINKS + // #ifndef WIN32 + || !_link.LinkPath.IsEmpty() + // #endif + #endif + ) + { + pathParts.DeleteBack(); + isFinal = false; // last path part was excluded + } + // else is_DirOp = true; + } + + if (pathParts.IsEmpty()) + { + /* if (_some_pathParts_wereRemoved && Is_elimPrefix_Mode), + then we can have empty pathParts() here for root folder. + v24.00: fixed: we set timestamps for such folder still. + */ + if (!_some_pathParts_wereRemoved || + !Is_elimPrefix_Mode) + return; + // return; // ignore empty paths case + } + /* + if (is_DirOp) + { + RINOK(PrepareOperation(NArchive::NExtract::NAskMode::kExtract)) + _op_WasReported = true; + } + */ + + FString fullPathNew; + CreateComplexDirectory(pathParts, isFinal, fullPathNew); + + /* + if (is_DirOp) + { + RINOK(SetOperationResult( + // _itemFailure ? NArchive::NExtract::NOperationResult::kDataError : + NArchive::NExtract::NOperationResult::kOK + )) + } + */ + + if (!_item.IsDir) + return; + if (fullPathNew.IsEmpty()) + return; + + if (_itemFailure) + return; + + CDirPathTime pt; + GetFiTimesCAM(_fi, pt, *_arc); + + if (pt.IsSomeTimeDefined()) + { + pt.Path = fullPathNew; + pt.SetDirTime_to_FS_2(); + _extractedFolders.Add(pt); + } +} + + + +/* + CheckExistFile(fullProcessedPath) + it can change: fullProcessedPath, _isRenamed, _overwriteMode + (needExit = true) means that we must exit GetStream() even for S_OK result. +*/ + +HRESULT CArchiveExtractCallback::CheckExistFile(FString &fullProcessedPath, bool &needExit) +{ + needExit = true; // it was set already before + + NFind::CFileInfo fileInfo; + + if (fileInfo.Find(fullProcessedPath)) + { + if (_overwriteMode == NExtract::NOverwriteMode::kSkip) + return S_OK; + + if (_overwriteMode == NExtract::NOverwriteMode::kAsk) + { + const int slashPos = fullProcessedPath.ReverseFind_PathSepar(); + const FString realFullProcessedPath = fullProcessedPath.Left((unsigned)(slashPos + 1)) + fileInfo.Name; + + /* (fileInfo) can be symbolic link. + we can show final file properties here. */ + + FILETIME ft1; + FiTime_To_FILETIME(fileInfo.MTime, ft1); + + Int32 overwriteResult; + RINOK(_extractCallback2->AskOverwrite( + fs2us(realFullProcessedPath), &ft1, &fileInfo.Size, _item.Path, + _fi.MTime.Def ? &_fi.MTime.FT : NULL, + _curSize_Defined ? &_curSize : NULL, + &overwriteResult)) + + switch (overwriteResult) + { + case NOverwriteAnswer::kCancel: + return E_ABORT; + case NOverwriteAnswer::kNo: + return S_OK; + case NOverwriteAnswer::kNoToAll: + _overwriteMode = NExtract::NOverwriteMode::kSkip; + return S_OK; + + case NOverwriteAnswer::kYes: + break; + case NOverwriteAnswer::kYesToAll: + _overwriteMode = NExtract::NOverwriteMode::kOverwrite; + break; + case NOverwriteAnswer::kAutoRename: + _overwriteMode = NExtract::NOverwriteMode::kRename; + break; + default: + return E_FAIL; + } + } // NExtract::NOverwriteMode::kAsk + + if (_overwriteMode == NExtract::NOverwriteMode::kRename) + { + if (!AutoRenamePath(fullProcessedPath)) + { + RINOK(SendMessageError(kCantAutoRename, fullProcessedPath)) + return E_FAIL; + } + _isRenamed = true; + } + else if (_overwriteMode == NExtract::NOverwriteMode::kRenameExisting) + { + FString existPath (fullProcessedPath); + if (!AutoRenamePath(existPath)) + { + RINOK(SendMessageError(kCantAutoRename, fullProcessedPath)) + return E_FAIL; + } + // MyMoveFile can rename folders. So it's OK to use it for folders too + if (!MyMoveFile(fullProcessedPath, existPath)) + { + RINOK(SendMessageError2_with_LastError(kCantRenameFile, existPath, fullProcessedPath)) + return E_FAIL; + } + } + else // not Rename* + { + if (fileInfo.IsDir()) + { + // do we need to delete all files in folder? + if (!RemoveDir(fullProcessedPath)) + { + RINOK(SendMessageError_with_LastError(kCantDeleteOutputDir, fullProcessedPath)) + return S_OK; + } + } + else // fileInfo is not Dir + { + if (NFind::DoesFileExist_Raw(fullProcessedPath)) + if (!DeleteFileAlways(fullProcessedPath)) + if (GetLastError() != ERROR_FILE_NOT_FOUND) // check it in linux + { + RINOK(SendMessageError_with_LastError(kCantDeleteOutputFile, fullProcessedPath)) + return S_OK; + // return E_FAIL; + } + } // fileInfo is not Dir + } // not Rename* + } + else // not Find(fullProcessedPath) + { + #if defined(_WIN32) && !defined(UNDER_CE) + // we need to clear READ-ONLY of parent before creating alt stream + const int colonPos = NName::FindAltStreamColon(fullProcessedPath); + if (colonPos >= 0 && fullProcessedPath[(unsigned)colonPos + 1] != 0) + { + FString parentFsPath (fullProcessedPath); + parentFsPath.DeleteFrom((unsigned)colonPos); + NFind::CFileInfo parentFi; + if (parentFi.Find(parentFsPath)) + { + if (parentFi.IsReadOnly()) + { + _altStream_NeedRestore_Attrib_for_parentFsPath = parentFsPath; + _altStream_NeedRestore_AttribVal = parentFi.Attrib; + SetFileAttrib(parentFsPath, parentFi.Attrib & ~(DWORD)FILE_ATTRIBUTE_READONLY); + } + } + } + #endif // defined(_WIN32) && !defined(UNDER_CE) + } + + needExit = false; + return S_OK; +} + + + +/* +return: + needExit = false: caller will use (outStreamLoc) and _hashStreamSpec + needExit = true : caller will not use (outStreamLoc) and _hashStreamSpec. +*/ +HRESULT CArchiveExtractCallback::GetExtractStream(CMyComPtr &outStreamLoc, bool &needExit) +{ + needExit = true; + + RINOK(Read_fi_Props()) + + #ifdef SUPPORT_LINKS + IInArchive * const archive = _arc->Archive; + #endif + + const UInt32 index = _index; + + bool isAnti = false; + RINOK(_arc->IsItem_Anti(index, isAnti)) + + CorrectPathParts(); + UString processedPath (MakePathFromParts(_item.PathParts)); + + if (!isAnti) + { + // 21.04: CreateFolders doesn't change (_item.PathParts) + CreateFolders(); + } + + FString fullProcessedPath (us2fs(processedPath)); + if (_pathMode != NExtract::NPathMode::kAbsPaths + || !NName::IsAbsolutePath(processedPath)) + { + fullProcessedPath = MakePath_from_2_Parts(_dirPathPrefix, fullProcessedPath); + } + + #ifdef SUPPORT_ALT_STREAMS + if (_item.IsAltStream && _item.ParentIndex != (UInt32)(Int32)-1) + { + const int renIndex = _renamedFiles.FindInSorted(CIndexToPathPair(_item.ParentIndex)); + if (renIndex != -1) + { + const CIndexToPathPair &pair = _renamedFiles[(unsigned)renIndex]; + fullProcessedPath = pair.Path; + fullProcessedPath.Add_Colon(); + UString s (_item.AltStreamName); + Correct_AltStream_Name(s); + fullProcessedPath += us2fs(s); + } + } + #endif // SUPPORT_ALT_STREAMS + + if (_item.IsDir) + { + _diskFilePath = fullProcessedPath; + if (isAnti) + RemoveDir(_diskFilePath); + #ifdef SUPPORT_LINKS + if (_link.LinkPath.IsEmpty()) + #endif + { + if (!isAnti) + SetAttrib(); + return S_OK; + } + } + else if (!_isSplit) + { + RINOK(CheckExistFile(fullProcessedPath, needExit)) + if (needExit) + return S_OK; + needExit = true; + } + + _diskFilePath = fullProcessedPath; + + + if (isAnti) + { + needExit = false; + return S_OK; + } + + // not anti + + #ifdef SUPPORT_LINKS + + if (!_link.LinkPath.IsEmpty()) + { + #ifndef UNDER_CE + { + bool linkWasSet = false; + RINOK(SetLink(fullProcessedPath, _link, linkWasSet)) +/* + // we don't set attributes for placeholder. + if (linkWasSet) + { + _isSymLinkCreated = _link.Is_AnySymLink(); + SetAttrib(); + // printf("\nlinkWasSet %s\n", GetAnsiString(_diskFilePath)); + } +*/ + } + #endif // UNDER_CE + + // if (_copyFile_Path.IsEmpty()) + { + needExit = false; + return S_OK; + } + } + + if (!_hardLinks.IDs.IsEmpty() && !_item.IsAltStream && !_item.IsDir) + { + CHardLinkNode h; + bool defined; + RINOK(Archive_Get_HardLinkNode(archive, index, h, defined)) + if (defined) + { + const int linkIndex = _hardLinks.IDs.FindInSorted2(h); + if (linkIndex != -1) + { + FString &hl = _hardLinks.Links[(unsigned)linkIndex]; + if (hl.IsEmpty()) + hl = fullProcessedPath; + else + { + bool link_was_Created = false; + RINOK(CreateHardLink2(fullProcessedPath, hl, link_was_Created)) + if (!link_was_Created) + return S_OK; + // printf("\nHard linkWasSet Archive_Get_HardLinkNode %s\n", GetAnsiString(_diskFilePath)); + // _needSetAttrib = true; // do we need to set attribute ? + SetAttrib(); + /* if we set (needExit = false) here, _hashStreamSpec will be used, + and hash will be calulated for all hard links files (it's slower). + But "Test" operation also calculates hashes. + */ + needExit = false; + return S_OK; + } + } + } + } + + #endif // SUPPORT_LINKS + + + // ---------- CREATE WRITE FILE ----- + + _outFileStreamSpec = new COutFileStream; + CMyComPtr outFileStream_Loc(_outFileStreamSpec); + + if (!_outFileStreamSpec->Create_ALWAYS_or_Open_ALWAYS(fullProcessedPath, !_isSplit)) + { + // if (::GetLastError() != ERROR_FILE_EXISTS || !isSplit) + { + RINOK(SendMessageError_with_LastError(kCantOpenOutFile, fullProcessedPath)) + return S_OK; + } + } + + _needSetAttrib = true; + + bool is_SymLink_in_Data = false; + + if (_curSize_Defined && _curSize && _curSize < k_LinkDataSize_LIMIT) + { + if (_fi.IsLinuxSymLink()) + { + is_SymLink_in_Data = true; + _is_SymLink_in_Data_Linux = true; + } + else if (_fi.IsReparse()) + { + is_SymLink_in_Data = true; + _is_SymLink_in_Data_Linux = false; + } + } + + if (is_SymLink_in_Data) + { + _outMemBuf.Alloc((size_t)_curSize); + _bufPtrSeqOutStream_Spec = new CBufPtrSeqOutStream; + _bufPtrSeqOutStream = _bufPtrSeqOutStream_Spec; + _bufPtrSeqOutStream_Spec->Init(_outMemBuf, _outMemBuf.Size()); + outStreamLoc = _bufPtrSeqOutStream; + } + else // not reparse + { + if (_ntOptions.PreAllocateOutFile && !_isSplit && _curSize_Defined && _curSize > (1 << 12)) + { + // UInt64 ticks = GetCpuTicks(); + _fileLength_that_WasSet = _curSize; + bool res = _outFileStreamSpec->File.SetLength(_curSize); + _fileLength_WasSet = res; + + // ticks = GetCpuTicks() - ticks; + // printf("\nticks = %10d\n", (unsigned)ticks); + if (!res) + { + RINOK(SendMessageError_with_LastError(kCantSetFileLen, fullProcessedPath)) + } + + /* + _outFileStreamSpec->File.Close(); + ticks = GetCpuTicks() - ticks; + printf("\nticks = %10d\n", (unsigned)ticks); + return S_FALSE; + */ + + /* + File.SetLength() on FAT (xp64): is fast, but then File.Close() can be slow, + if we don't write any data. + File.SetLength() for remote share file (exFAT) can be slow in some cases, + and the Windows can return "network error" after 1 minute, + while remote file still can grow. + We need some way to detect such bad cases and disable PreAllocateOutFile mode. + */ + + res = _outFileStreamSpec->SeekToBegin_bool(); + if (!res) + { + RINOK(SendMessageError_with_LastError("Cannot seek to begin of file", fullProcessedPath)) + } + } // PreAllocateOutFile + + #ifdef SUPPORT_ALT_STREAMS + if (_isRenamed && !_item.IsAltStream) + { + CIndexToPathPair pair(index, fullProcessedPath); + unsigned oldSize = _renamedFiles.Size(); + unsigned insertIndex = _renamedFiles.AddToUniqueSorted(pair); + if (oldSize == _renamedFiles.Size()) + _renamedFiles[insertIndex].Path = fullProcessedPath; + } + #endif // SUPPORT_ALT_STREAMS + + if (_isSplit) + { + RINOK(outFileStream_Loc->Seek((Int64)_position, STREAM_SEEK_SET, NULL)) + } + outStreamLoc = outFileStream_Loc; + } // if not reparse + + _outFileStream = outFileStream_Loc; + + needExit = false; + return S_OK; +} + + + +HRESULT CArchiveExtractCallback::GetItem(UInt32 index) +{ + #ifndef Z7_SFX + _item._use_baseParentFolder_mode = _use_baseParentFolder_mode; + if (_use_baseParentFolder_mode) + { + _item._baseParentFolder = (int)_baseParentFolder; + if (_pathMode == NExtract::NPathMode::kFullPaths || + _pathMode == NExtract::NPathMode::kAbsPaths) + _item._baseParentFolder = -1; + } + #endif // Z7_SFX + + #ifdef SUPPORT_ALT_STREAMS + _item.WriteToAltStreamIfColon = _ntOptions.WriteToAltStreamIfColon; + #endif + + return _arc->GetItem(index, _item); +} + + +Z7_COM7F_IMF(CArchiveExtractCallback::GetStream(UInt32 index, ISequentialOutStream **outStream, Int32 askExtractMode)) +{ + COM_TRY_BEGIN + + *outStream = NULL; + + #ifndef Z7_SFX + if (_hashStream) + _hashStreamSpec->ReleaseStream(); + _hashStreamWasUsed = false; + #endif + + _outFileStream.Release(); + _bufPtrSeqOutStream.Release(); + + _encrypted = false; + _isSplit = false; + _curSize_Defined = false; + _fileLength_WasSet = false; + _isRenamed = false; + // _fi.Clear(); + _extractMode = false; + _is_SymLink_in_Data_Linux = false; + _needSetAttrib = false; + _isSymLinkCreated = false; + _itemFailure = false; + _some_pathParts_wereRemoved = false; + // _op_WasReported = false; + + _position = 0; + _curSize = 0; + _fileLength_that_WasSet = 0; + _index = index; + +#if defined(_WIN32) && !defined(UNDER_CE) + _altStream_NeedRestore_AttribVal = 0; + _altStream_NeedRestore_Attrib_for_parentFsPath.Empty(); +#endif + + _diskFilePath.Empty(); + + #ifdef SUPPORT_LINKS + // _copyFile_Path.Empty(); + _link.Clear(); + #endif + + + switch (askExtractMode) + { + case NArchive::NExtract::NAskMode::kExtract: + if (_testMode) + { + // askExtractMode = NArchive::NExtract::NAskMode::kTest; + } + else + _extractMode = true; + break; + default: break; + } + + + IInArchive * const archive = _arc->Archive; + + RINOK(GetItem(index)) + + { + NCOM::CPropVariant prop; + RINOK(archive->GetProperty(index, kpidPosition, &prop)) + if (prop.vt != VT_EMPTY) + { + if (prop.vt != VT_UI8) + return E_FAIL; + _position = prop.uhVal.QuadPart; + _isSplit = true; + } + } + +#ifdef SUPPORT_LINKS + RINOK(ReadLink()) +#endif + + RINOK(Archive_GetItemBoolProp(archive, index, kpidEncrypted, _encrypted)) + + RINOK(GetUnpackSize()) + + #ifdef SUPPORT_ALT_STREAMS + if (!_ntOptions.AltStreams.Val && _item.IsAltStream) + return S_OK; + #endif // SUPPORT_ALT_STREAMS + + // we can change (_item.PathParts) in this function + UStringVector &pathParts = _item.PathParts; + + if (_wildcardCensor) + { + if (!CensorNode_CheckPath(*_wildcardCensor, _item)) + return S_OK; + } + +#if defined(_WIN32) && !defined(UNDER_CE) && !defined(Z7_SFX) + if (askExtractMode == NArchive::NExtract::NAskMode::kExtract + && !_testMode + && _item.IsAltStream + && ZoneBuf.Size() != 0 + && Is_ZoneId_StreamName(_item.AltStreamName)) + if (ZoneMode != NExtract::NZoneIdMode::kOffice + || _item.PathParts.IsEmpty() + || FindExt2(kOfficeExtensions, _item.PathParts.Back())) + return S_OK; +#endif + + + #ifndef Z7_SFX + if (_use_baseParentFolder_mode) + { + if (!pathParts.IsEmpty()) + { + unsigned numRemovePathParts = 0; + + #ifdef SUPPORT_ALT_STREAMS + if (_pathMode == NExtract::NPathMode::kNoPathsAlt && _item.IsAltStream) + numRemovePathParts = pathParts.Size(); + else + #endif + if (_pathMode == NExtract::NPathMode::kNoPaths || + _pathMode == NExtract::NPathMode::kNoPathsAlt) + numRemovePathParts = pathParts.Size() - 1; + pathParts.DeleteFrontal(numRemovePathParts); + } + } + else + #endif // Z7_SFX + { + if (pathParts.IsEmpty()) + { + if (_item.IsDir) + return S_OK; + /* + #ifdef SUPPORT_ALT_STREAMS + if (!_item.IsAltStream) + #endif + return E_FAIL; + */ + } + + unsigned numRemovePathParts = 0; + + switch ((int)_pathMode) + { + case NExtract::NPathMode::kFullPaths: + case NExtract::NPathMode::kCurPaths: + { + if (_removePathParts.IsEmpty()) + break; + bool badPrefix = false; + + if (pathParts.Size() < _removePathParts.Size()) + badPrefix = true; + else + { + if (pathParts.Size() == _removePathParts.Size()) + { + if (_removePartsForAltStreams) + { + #ifdef SUPPORT_ALT_STREAMS + if (!_item.IsAltStream) + #endif + badPrefix = true; + } + else + { + if (!_item.MainIsDir) + badPrefix = true; + } + } + + if (!badPrefix) + FOR_VECTOR (i, _removePathParts) + { + if (CompareFileNames(_removePathParts[i], pathParts[i]) != 0) + { + badPrefix = true; + break; + } + } + } + + if (badPrefix) + { + if (askExtractMode == NArchive::NExtract::NAskMode::kExtract && !_testMode) + return E_FAIL; + } + else + { + numRemovePathParts = _removePathParts.Size(); + _some_pathParts_wereRemoved = true; + } + break; + } + + case NExtract::NPathMode::kNoPaths: + { + if (!pathParts.IsEmpty()) + numRemovePathParts = pathParts.Size() - 1; + break; + } + case NExtract::NPathMode::kNoPathsAlt: + { + #ifdef SUPPORT_ALT_STREAMS + if (_item.IsAltStream) + numRemovePathParts = pathParts.Size(); + else + #endif + if (!pathParts.IsEmpty()) + numRemovePathParts = pathParts.Size() - 1; + break; + } + case NExtract::NPathMode::kAbsPaths: + default: + break; + } + + pathParts.DeleteFrontal(numRemovePathParts); + } + + + #ifndef Z7_SFX + + if (ExtractToStreamCallback) + { + CMyComPtr2_Create GetProp; + GetProp->Arc = _arc; + GetProp->IndexInArc = index; + UString name (MakePathFromParts(pathParts)); + // GetProp->BaseName = name; + #ifdef SUPPORT_ALT_STREAMS + if (_item.IsAltStream) + { + if (!pathParts.IsEmpty() || (!_removePartsForAltStreams && _pathMode != NExtract::NPathMode::kNoPathsAlt)) + name.Add_Colon(); + name += _item.AltStreamName; + } + #endif + + return ExtractToStreamCallback->GetStream7(name, BoolToInt(_item.IsDir), outStream, askExtractMode, GetProp); + } + + #endif // Z7_SFX + + + CMyComPtr outStreamLoc; + + if (askExtractMode == NArchive::NExtract::NAskMode::kExtract && !_testMode) + { + if (_stdOutMode) + outStreamLoc = new CStdOutFileStream; + else + { + if (_isSplit) + { + RINOK(PrepareOperation(askExtractMode)) + return SetOperationResult(NArchive::NExtract::NOperationResult::kUnsupportedMethod); + } + bool needExit = true; + RINOK(GetExtractStream(outStreamLoc, needExit)) + if (needExit) + return S_OK; + } + } + + #ifndef Z7_SFX + if (_hashStream) + { + if (askExtractMode == NArchive::NExtract::NAskMode::kExtract || + askExtractMode == NArchive::NExtract::NAskMode::kTest) + { + _hashStreamSpec->SetStream(outStreamLoc); + outStreamLoc = _hashStream; + _hashStreamSpec->Init(true); + _hashStreamWasUsed = true; + } + } + #endif // Z7_SFX + + if (outStreamLoc) + { + /* + #ifdef SUPPORT_LINKS + if (!_copyFile_Path.IsEmpty()) + { + RINOK(PrepareOperation(askExtractMode)); + RINOK(MyCopyFile(outStreamLoc)); + return SetOperationResult(NArchive::NExtract::NOperationResult::kOK); + } + if (_link.isCopyLink && _testMode) + return S_OK; + #endif + */ + *outStream = outStreamLoc.Detach(); + } + + return S_OK; + + COM_TRY_END +} + + + + + + + + + + + +Z7_COM7F_IMF(CArchiveExtractCallback::PrepareOperation(Int32 askExtractMode)) +{ + COM_TRY_BEGIN + + #ifndef Z7_SFX + // if (!_op_WasReported) + if (ExtractToStreamCallback) + return ExtractToStreamCallback->PrepareOperation7(askExtractMode); + #endif + + _extractMode = false; + + switch (askExtractMode) + { + case NArchive::NExtract::NAskMode::kExtract: + if (_testMode) + askExtractMode = NArchive::NExtract::NAskMode::kTest; + else + _extractMode = true; + break; + default: break; + } + + // if (_op_WasReported) return S_OK; + + return _extractCallback2->PrepareOperation(_item.Path, BoolToInt(_item.IsDir), + askExtractMode, _isSplit ? &_position: NULL); + + COM_TRY_END +} + + + + + +HRESULT CArchiveExtractCallback::CloseFile() +{ + if (!_outFileStream) + return S_OK; + + HRESULT hres = S_OK; + + const UInt64 processedSize = _outFileStreamSpec->ProcessedSize; + if (_fileLength_WasSet && _fileLength_that_WasSet > processedSize) + { + const bool res = _outFileStreamSpec->File.SetLength(processedSize); + _fileLength_WasSet = res; + if (!res) + { + const HRESULT hres2 = SendMessageError_with_LastError(kCantSetFileLen, us2fs(_item.Path)); + if (hres == S_OK) + hres = hres2; + } + } + + _curSize = processedSize; + _curSize_Defined = true; + + #if defined(_WIN32) && !defined(UNDER_CE) && !defined(Z7_SFX) + if (ZoneBuf.Size() != 0 + && !_item.IsAltStream) + { + // if (NFind::DoesFileExist_Raw(tempFilePath)) + if (ZoneMode != NExtract::NZoneIdMode::kOffice || + FindExt2(kOfficeExtensions, fs2us(_diskFilePath))) + { + // we must write zone file before setting of timestamps + if (!WriteZoneFile_To_BaseFile(_diskFilePath, ZoneBuf)) + { + // we can't write it in FAT + // SendMessageError_with_LastError("Can't write Zone.Identifier stream", path); + } + } + } + #endif + + CFiTimesCAM t; + GetFiTimesCAM(_fi, t, *_arc); + + // #ifdef _WIN32 + if (t.IsSomeTimeDefined()) + _outFileStreamSpec->SetTime( + t.CTime_Defined ? &t.CTime : NULL, + t.ATime_Defined ? &t.ATime : NULL, + t.MTime_Defined ? &t.MTime : NULL); + // #endif + + RINOK(_outFileStreamSpec->Close()) + _outFileStream.Release(); + +#if defined(_WIN32) && !defined(UNDER_CE) + if (!_altStream_NeedRestore_Attrib_for_parentFsPath.IsEmpty()) + { + SetFileAttrib(_altStream_NeedRestore_Attrib_for_parentFsPath, _altStream_NeedRestore_AttribVal); + _altStream_NeedRestore_Attrib_for_parentFsPath.Empty(); + } +#endif + + return hres; +} + + +#ifdef SUPPORT_LINKS + +static bool CheckLinkPath_in_FS_for_pathParts(const FString &path, const UStringVector &v) +{ + FString path2 = path; + FOR_VECTOR (i, v) + { + // if (i == v.Size() - 1) path = path2; // we don't need last part in returned path + path2 += us2fs(v[i]); + NFind::CFileInfo fi; + // printf("\nCheckLinkPath_in_FS_for_pathParts(): %s\n", GetOemString(path2).Ptr()); + if (fi.Find(path2) && fi.IsOsSymLink()) + return false; + path2.Add_PathSepar(); + } + return true; +} + +/* +link.isRelative / relative_item_PathPrefix + false / empty + true / item path without last part +*/ +static bool CheckLinkPath_in_FS( + const FString &pathPrefix_in_FS, + const CPostLink &postLink, + const UString &relative_item_PathPrefix) +{ + const CLinkInfo &link = postLink.LinkInfo; + if (postLink.item_PathParts.IsEmpty() || link.LinkPath.IsEmpty()) + return false; + FString path; + { + const UString &s = postLink.item_PathParts[0]; + if (!s.IsEmpty() && !NName::IsAbsolutePath(s)) + path = pathPrefix_in_FS; // item_PathParts is relative. So we use absolutre prefix + } + if (!CheckLinkPath_in_FS_for_pathParts(path, postLink.item_PathParts)) + return false; + path += us2fs(relative_item_PathPrefix); + UStringVector v; + SplitPathToParts(link.LinkPath, v); + // we check target paths: + return CheckLinkPath_in_FS_for_pathParts(path, v); +} + +static const unsigned k_DangLevel_MAX_for_Link_over_Link = 9; + +HRESULT CArchiveExtractCallback::CreateHardLink2( + const FString &newFilePath, const FString &existFilePath, bool &link_was_Created) const +{ + link_was_Created = false; + if (_ntOptions.SymLinks_DangerousLevel <= k_DangLevel_MAX_for_Link_over_Link) + { + NFind::CFileInfo fi; + if (fi.Find(existFilePath) && fi.IsOsSymLink()) + return SendMessageError2(0, k_HardLink_to_SymLink_Ignored, newFilePath, existFilePath); + } + if (!MyCreateHardLink(newFilePath, existFilePath)) + return SendMessageError2_with_LastError(kCantCreateHardLink, newFilePath, existFilePath); + link_was_Created = true; + return S_OK; +} + + + +HRESULT CArchiveExtractCallback::SetLink( + const FString &fullProcessedPath_from, + const CLinkInfo &link, + bool &linkWasSet) // placeholder was created +{ + linkWasSet = false; + if (link.LinkPath.IsEmpty()) + return S_OK; + if (!_ntOptions.SymLinks.Val && link.Is_AnySymLink()) + return S_OK; + CPostLink postLink; + postLink.Index_in_Arc = _index; + postLink.item_IsDir = _item.IsDir; + postLink.item_Path = _item.Path; + postLink.item_PathParts = _item.PathParts; + postLink.item_FileInfo = _fi; + postLink.fullProcessedPath_from = fullProcessedPath_from; + postLink.LinkInfo = link; + _postLinks.Add(postLink); + + // file doesn't exist in most cases. So we don't check for error. + DeleteLinkFileAlways_or_RemoveEmptyDir(fullProcessedPath_from, false); // checkThatFileIsEmpty = false + + NIO::COutFile outFile; + if (!outFile.Create_NEW(fullProcessedPath_from)) + return SendMessageError("Cannot create temporary link file", fullProcessedPath_from); +#if 0 // 1 for debug + // here we can write link path to temporary link file placeholder, + // but empty placeholder is better, because we don't want to get any non-eampty data instead of link file. + AString s; + ConvertUnicodeToUTF8(link.LinkPath, s); + outFile.WriteFull(s, s.Len()); +#endif + linkWasSet = true; + return S_OK; +} + + +// if file/dir is symbolic link it will remove only link itself +HRESULT CArchiveExtractCallback::DeleteLinkFileAlways_or_RemoveEmptyDir( + const FString &path, bool checkThatFileIsEmpty) const +{ + NFile::NFind::CFileInfo fi; + if (fi.Find(path)) // followLink = false + { + if (fi.IsDir()) + { + if (RemoveDirAlways_if_Empty(path)) + return S_OK; + } + else + { + // link file placeholder must be empty + if (checkThatFileIsEmpty && !fi.IsOsSymLink() && fi.Size != 0) + return SendMessageError("Temporary link file is not empty", path); + if (DeleteFileAlways(path)) + return S_OK; + } + if (GetLastError() != ERROR_FILE_NOT_FOUND) + return SendMessageError_with_LastError( + fi.IsDir() ? + k_CantDelete_Dir_for_SymLink: + k_CantDelete_File_for_SymLink, + path); + } + return S_OK; +} + + +/* +in: + link.LinkPath : must be relative (non-absolute) path in any case !!! + link.isRelative / target path that must stored as created link: + == false / _dirPathPrefix_Full + link.LinkPath + == true / link.LinkPath +*/ +static HRESULT SetLink2(const CArchiveExtractCallback &callback, + const CPostLink &postLink, bool &linkWasSet) +{ + const CLinkInfo &link = postLink.LinkInfo; + const FString &fullProcessedPath_from = postLink.fullProcessedPath_from; // full file path in FS (fullProcessedPath_from) + + const unsigned level = callback._ntOptions.SymLinks_DangerousLevel; + if (level < 20) + { + /* + We want to use additional check for links that can link to directory. + - linux: all symbolic links are files. + - windows: we can have file/directory symbolic link, + but file symbolic link works like directory link in windows. + So we use additional check for all relative links. + + We don't allow decreasing of final level of link. + So if some another extracted file will use this link, + then number of real path parts (after link redirection) cannot be + smaller than number of requested path parts from archive records. + + here we check only (link.LinkPath) without (_item.PathParts). + */ + CLinkLevelsInfo li; + li.Parse(link.LinkPath, link.Is_WSL()); + bool isDang; + UString relativePathPrefix; + if (li.IsAbsolute // unexpected + || li.ParentDirDots_after_NonParent + || (level <= 5 && link.isRelative && li.FinalLevel < 1) // final level lower + || (level <= 5 && link.isRelative && li.LowLevel < 0) // negative temporary levels + ) + isDang = true; + else // if (!isDang) + { + UString path; + if (link.isRelative) + { + // item_PathParts : parts that will be created in output folder. + // we want to get directory prefix of link item. + // so we remove file name (last non-empty part) from PathParts: + UStringVector v = postLink.item_PathParts; + while (!v.IsEmpty()) + { + const unsigned len = v.Back().Len(); + v.DeleteBack(); + if (len) + break; + } + path = MakePathFromParts(v); + NName::NormalizeDirPathPrefix(path); + relativePathPrefix = path; + } + path += link.LinkPath; + /* + path is calculated virtual target path of link + path is relative to root folder of extracted items + if (!link.isRelative), then (path == link.LinkPath) + */ + isDang = false; + if (!IsSafePath(path, link.Is_WSL())) + isDang = true; + } + const char *message = NULL; + if (isDang) + message = "Dangerous link path was ignored"; + else if (level <= k_DangLevel_MAX_for_Link_over_Link + && !CheckLinkPath_in_FS(callback._dirPathPrefix_Full, + postLink, relativePathPrefix)) + message = "Dangerous link via another link was ignored"; + if (message) + return callback.SendMessageError2(0, // errorCode + message, us2fs(postLink.item_Path), us2fs(link.LinkPath)); + } + + FString target; // target path that will be stored to link field + if (link.Is_HardLink() /* || link.IsCopyLink */ || !link.isRelative) + { + // isRelative == false + // all hard links and absolute symbolic links + // relatPath == link.LinkPath + // we get absolute link path for target: + if (!NName::GetFullPath(callback._dirPathPrefix_Full, us2fs(link.LinkPath), target)) + return callback.SendMessageError("Incorrect link path", us2fs(link.LinkPath)); + // (target) is (_dirPathPrefix_Full + relatPath) + } + else + { + // link.isRelative == true + // relative symbolic links only + target = us2fs(link.LinkPath); + } + if (target.IsEmpty()) + return callback.SendMessageError("Empty link", fullProcessedPath_from); + + if (link.Is_HardLink() /* || link.IsCopyLink */) + { + // if (link.isHardLink) + { + RINOK(callback.DeleteLinkFileAlways_or_RemoveEmptyDir(fullProcessedPath_from, true)) // checkThatFileIsEmpty + { + // RINOK(SendMessageError_with_LastError(k_Cant_DeleteTempLinkFile, fullProcessedPath_from)) + } + return callback.CreateHardLink2(fullProcessedPath_from, target, linkWasSet); + /* + RINOK(PrepareOperation(NArchive::NExtract::NAskMode::kExtract)) + _op_WasReported = true; + RINOK(SetOperationResult(NArchive::NExtract::NOperationResult::kOK)) + linkWasSet = true; + return S_OK; + */ + } + /* + // IsCopyLink + { + NFind::CFileInfo fi; + if (!fi.Find(target)) + { + RINOK(SendMessageError2("Cannot find the file for copying", target, fullProcessedPath)); + } + else + { + if (_curSize_Defined && _curSize == fi.Size) + _copyFile_Path = target; + else + { + RINOK(SendMessageError2("File size collision for file copying", target, fullProcessedPath)); + } + // RINOK(MyCopyFile(target, fullProcessedPath)); + } + } + */ + } + + // is Symbolic link + + /* + if (_item.IsDir && !isRelative) + { + // Windows before Vista doesn't support symbolic links. + // we could convert such symbolic links to Junction Points + // isJunction = true; + } + */ + +#ifdef _WIN32 + const bool isDir = (postLink.item_IsDir || link.LinkType == k_LinkType_Junction); +#endif + + +#ifdef _WIN32 + CByteBuffer data; + // printf("\nFillLinkData(): %s\n", GetOemString(target).Ptr()); + if (link.Is_WSL()) + { + Convert_WinPath_to_WslLinuxPath(target, !link.isRelative); + FillLinkData_WslLink(data, fs2us(target)); + } + else + FillLinkData_WinLink(data, fs2us(target), link.LinkType != k_LinkType_Junction); + if (data.Size() == 0) + return callback.SendMessageError("Cannot fill link data", us2fs(postLink.item_Path)); + /* + if (NtReparse_Size != data.Size() || memcmp(NtReparse_Data, data, data.Size()) != 0) + SendMessageError("reconstructed Reparse is different", fs2us(target)); + */ + { + // we check that reparse data is correct, but we ignore attr.MinorError. + CReparseAttr attr; + if (!attr.Parse(data, data.Size())) + return callback.SendMessageError("Internal error for symbolic link file", us2fs(postLink.item_Path)); + } +#endif + + RINOK(callback.DeleteLinkFileAlways_or_RemoveEmptyDir(fullProcessedPath_from, true)) // checkThatFileIsEmpty +#ifdef _WIN32 + if (!NFile::NIO::SetReparseData(fullProcessedPath_from, isDir, data, (DWORD)data.Size())) +#else // ! _WIN32 + if (!NFile::NIO::SetSymLink(fullProcessedPath_from, target)) +#endif // ! _WIN32 + { + return callback.SendMessageError_with_LastError(kCantCreateSymLink, fullProcessedPath_from); + } + linkWasSet = true; + return S_OK; +} + + + +bool CLinkInfo::Parse_from_WindowsReparseData(const Byte *data, size_t dataSize) +{ + CReparseAttr reparse; + if (!reparse.Parse(data, dataSize)) + return false; + // const AString s = GetAnsiString(LinkPath); + // printf("\nlinkPath: %s\n", s.Ptr()); + LinkPath = reparse.GetPath(); + if (reparse.IsSymLink_WSL()) + { + LinkType = k_LinkType_WSL; + isRelative = reparse.IsRelative_WSL(); // detected from LinkPath[0] + // LinkPath is original raw name converted to UString from AString + // Linux separator '/' is expected here. + REPLACE_SLASHES_from_Linux_to_Sys(LinkPath) + } + else + { + LinkType = reparse.IsMountPoint() ? k_LinkType_Junction : k_LinkType_PureSymLink; + isRelative = reparse.IsRelative_Win(); // detected by (Flags == Z7_WIN_SYMLINK_FLAG_RELATIVE) + isWindowsPath = true; + // LinkPath is original windows link path from raparse data with \??\ prefix removed. + // windows '\\' separator is expected here. + // linux '/' separator is not expected here. + // we translate both types of separators to system separator. + LinkPath.Replace( +#if WCHAR_PATH_SEPARATOR == L'\\' + L'/' +#else + L'\\' +#endif + , WCHAR_PATH_SEPARATOR); + } + // (LinkPath) uses system path separator. + // windows: (LinkPath) doesn't contain linux separator (slash). + return true; +} + + +bool CLinkInfo::Parse_from_LinuxData(const Byte *data, size_t dataSize) +{ + // Clear(); // *this object was cleared by constructor already. + LinkType = k_LinkType_PureSymLink; + AString utf; + if (dataSize >= k_LinkDataSize_LIMIT) + return false; + utf.SetFrom_CalcLen((const char *)data, (unsigned)dataSize); + UString u; + if (!ConvertUTF8ToUnicode(utf, u)) + return false; + if (u.IsEmpty()) + return false; + const wchar_t c = u[0]; + isRelative = (c != L'/'); + // linux path separator is expected + REPLACE_SLASHES_from_Linux_to_Sys(u) + LinkPath = u; + // (LinkPath) uses system path separator. + // windows: (LinkPath) doesn't contain linux separator (slash). + return true; +} + + +// in/out: (LinkPath) uses system path separator +// in/out: windows: (LinkPath) doesn't contain linux separator (slash). +// out: (LinkPath) is relative path, and LinkPath[0] is not path separator +// out: isRelative changed to false, if any prefix was removed. +// note: absolute windows links "c:\" to root will be reduced to empty string: +void CLinkInfo::Remove_AbsPathPrefixes() +{ + while (!LinkPath.IsEmpty()) + { + unsigned n = 0; + if (!Is_WSL()) + { + n = +#ifndef _WIN32 + isWindowsPath ? + NName::GetRootPrefixSize_WINDOWS(LinkPath) : +#endif + NName::GetRootPrefixSize(LinkPath); +/* + // "c:path" will be ignored later as "Dangerous absolute path" + // so check is not required + if (n == 0 +#ifndef _WIN32 + && isWindowsPath +#endif + && NName::IsDrivePath2(LinkPath)) + n = 2; +*/ + } + if (n == 0) + { + if (!IS_PATH_SEPAR(LinkPath[0])) + break; + n = 1; + } + isRelative = false; // (LinkPath) will be treated as relative to root folder of archive + LinkPath.DeleteFrontal(n); + } +} + + +/* + it removes redundant separators, if there are double separators, + but it keeps double separators at start of string //name/. + in/out: system path separator is used + windows: slash character (linux separator) is not treated as separator + windows: (path) doesn't contain linux separator (slash). +*/ +static void RemoveRedundantPathSeparators(UString &path) +{ + wchar_t *dest = path.GetBuf(); + const wchar_t * const start = dest; + const wchar_t *src = dest; + for (;;) + { + wchar_t c = *src++; + if (c == 0) + break; + // if (IS_PATH_SEPAR(c)) // for Windows: we can change (/) to (\). + if (c == WCHAR_PATH_SEPARATOR) + { + if (dest - start >= 2 && dest[-1] == WCHAR_PATH_SEPARATOR) + continue; + // c = WCHAR_PATH_SEPARATOR; // for Windows: we can change (/) to (\). + } + *dest++ = c; + } + *dest = 0; + path.ReleaseBuf_SetLen((unsigned)(dest - path.Ptr())); +} + + +// in/out: (LinkPath) uses system path separator +// in/out: windows: (LinkPath) doesn't contain linux separator (slash). +// out: (LinkPath) is relative path, and LinkPath[0] is not path separator +void CLinkInfo::Normalize_to_RelativeSafe(UStringVector &removePathParts) +{ + // We WILL NOT WRITE original absolute link path from archive to filesystem. + // So here we remove all root prefixes from (LinkPath). + // If we see any absolute root prefix, then we suppose that this prefix is virtual prefix + // that shows that link is relative to root folder of archive + RemoveRedundantPathSeparators(LinkPath); + // LinkPath = "\\\\?\\r:test\\test2"; // for debug + Remove_AbsPathPrefixes(); + // (LinkPath) now is relative: + // if (isRelative == false), then (LinkPath) is relative to root folder of archive + // if (isRelative == true ), then (LinkPath) is relative to current item + if (LinkPath.IsEmpty() || isRelative || removePathParts.Size() == 0) + return; + + // if LinkPath is prefixed by _removePathParts, we remove these paths + UStringVector pathParts; + SplitPathToParts(LinkPath, pathParts); + bool badPrefix = false; + { + FOR_VECTOR (i, removePathParts) + { + if (i >= pathParts.Size() + || CompareFileNames(removePathParts[i], pathParts[i]) != 0) + { + badPrefix = true; + break; + } + } + } + if (!badPrefix) + pathParts.DeleteFrontal(removePathParts.Size()); + LinkPath = MakePathFromParts(pathParts); + Remove_AbsPathPrefixes(); +} + +#endif // SUPPORT_LINKS + + +HRESULT CArchiveExtractCallback::CloseReparseAndFile() +{ + HRESULT res = S_OK; + +#ifdef SUPPORT_LINKS + + size_t reparseSize = 0; + bool repraseMode = false; + bool needSetReparse = false; + CLinkInfo link; + + if (_bufPtrSeqOutStream) + { + repraseMode = true; + reparseSize = _bufPtrSeqOutStream_Spec->GetPos(); + if (_curSize_Defined && reparseSize == _outMemBuf.Size()) + { + /* + CReparseAttr reparse; + DWORD errorCode = 0; + needSetReparse = reparse.Parse(_outMemBuf, reparseSize, errorCode); + if (needSetReparse) + { + UString LinkPath = reparse.GetPath(); + #ifndef _WIN32 + LinkPath.Replace(L'\\', WCHAR_PATH_SEPARATOR); + #endif + } + */ + needSetReparse = _is_SymLink_in_Data_Linux ? + link.Parse_from_LinuxData(_outMemBuf, reparseSize) : + link.Parse_from_WindowsReparseData(_outMemBuf, reparseSize); + if (!needSetReparse) + res = SendMessageError_with_LastError("Incorrect reparse stream", us2fs(_item.Path)); + // (link.LinkPath) uses system path separator. + // windows: (link.LinkPath) doesn't contain linux separator (slash). + } + else + { + res = SendMessageError_with_LastError("Unknown reparse stream", us2fs(_item.Path)); + } + if (!needSetReparse && _outFileStream) + { + const HRESULT res2 = WriteStream(_outFileStream, _outMemBuf, reparseSize); + if (res == S_OK) + res = res2; + } + _bufPtrSeqOutStream.Release(); + } + +#endif // SUPPORT_LINKS + + const HRESULT res2 = CloseFile(); + if (res == S_OK) + res = res2; + RINOK(res) + +#ifdef SUPPORT_LINKS + if (repraseMode) + { + _curSize = reparseSize; + _curSize_Defined = true; + if (needSetReparse) + { + // empty file was created so we must delete it. + // in Linux : we must delete empty file before symbolic link creation + // in Windows : we can create symbolic link even without file deleting + if (!DeleteFileAlways(_diskFilePath)) + { + RINOK(SendMessageError_with_LastError("can't delete file", _diskFilePath)) + } + { + bool linkWasSet = false; + // link.LinkPath = "r:\\1\\2"; // for debug + // link.isJunction = true; // for debug + link.Normalize_to_RelativeSafe(_removePathParts); + RINOK(SetLink(_diskFilePath, link, linkWasSet)) +/* + // we don't set attributes for placeholder. + if (linkWasSet) + _isSymLinkCreated = true; // link.IsSymLink(); + else +*/ + _needSetAttrib = false; + } + } + } +#endif // SUPPORT_LINKS + return res; +} + + +static void SetAttrib_Base(const FString &path, const CProcessedFileInfo &fi, + const CArchiveExtractCallback &callback) +{ +#ifndef _WIN32 + if (fi.Owner.Id_Defined && + fi.Group.Id_Defined) + { + if (my_chown(path, fi.Owner.Id, fi.Group.Id) != 0) + callback.SendMessageError_with_LastError("Cannot set owner", path); + } +#endif + + if (fi.Attrib_Defined) + { + // const AString s = GetAnsiString(_diskFilePath); + // printf("\nSetFileAttrib_PosixHighDetect: %s: hex:%x\n", s.Ptr(), _fi.Attrib); + if (!SetFileAttrib_PosixHighDetect(path, fi.Attrib)) + { + // do we need error message here in Windows and in posix? + callback.SendMessageError_with_LastError("Cannot set file attribute", path); + } + } +} + +void CArchiveExtractCallback::SetAttrib() const +{ +#ifndef _WIN32 + // Linux now doesn't support permissions for symlinks + if (_isSymLinkCreated) + return; +#endif + + if (_itemFailure + || _diskFilePath.IsEmpty() + || _stdOutMode + || !_extractMode) + return; + + SetAttrib_Base(_diskFilePath, _fi, *this); +} + + +#ifdef Z7_USE_SECURITY_CODE +HRESULT CArchiveExtractCallback::SetSecurityInfo(UInt32 indexInArc, const FString &path) const +{ + if (!_stdOutMode && _extractMode && _ntOptions.NtSecurity.Val && _arc->GetRawProps) + { + const void *data; + UInt32 dataSize; + UInt32 propType; + _arc->GetRawProps->GetRawProp(indexInArc, kpidNtSecure, &data, &dataSize, &propType); + if (dataSize != 0) + { + if (propType != NPropDataType::kRaw) + return E_FAIL; + if (CheckNtSecure((const Byte *)data, dataSize)) + { + SECURITY_INFORMATION securInfo = DACL_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION; + if (_saclEnabled) + securInfo |= SACL_SECURITY_INFORMATION; + // if (! + ::SetFileSecurityW(fs2us(path), securInfo, (PSECURITY_DESCRIPTOR)(void *)(const Byte *)(data)); + { + // RINOK(SendMessageError_with_LastError("SetFileSecurity FAILS", path)) + } + } + } + } + return S_OK; +} +#endif // Z7_USE_SECURITY_CODE + + +Z7_COM7F_IMF(CArchiveExtractCallback::SetOperationResult(Int32 opRes)) +{ + COM_TRY_BEGIN + + // if (_isSplit) opRes = NArchive::NExtract::NOperationResult::kUnsupportedMethod; + // printf("\nCArchiveExtractCallback::SetOperationResult: %d %s\n", opRes, GetAnsiString(_diskFilePath)); + + #ifndef Z7_SFX + if (ExtractToStreamCallback) + { + GetUnpackSize(); + return ExtractToStreamCallback->SetOperationResult8(opRes, BoolToInt(_encrypted), _curSize); + } + #endif + + #ifndef Z7_SFX + + if (_hashStreamWasUsed) + { + _hashStreamSpec->_hash->Final(_item.IsDir, + #ifdef SUPPORT_ALT_STREAMS + _item.IsAltStream + #else + false + #endif + , _item.Path); + _curSize = _hashStreamSpec->GetSize(); + _curSize_Defined = true; + _hashStreamSpec->ReleaseStream(); + _hashStreamWasUsed = false; + } + + #endif // Z7_SFX + + RINOK(CloseReparseAndFile()) + +#ifdef Z7_USE_SECURITY_CODE + RINOK(SetSecurityInfo(_index, _diskFilePath)) +#endif + + if (!_curSize_Defined) + GetUnpackSize(); + + if (_curSize_Defined) + { + #ifdef SUPPORT_ALT_STREAMS + if (_item.IsAltStream) + AltStreams_UnpackSize += _curSize; + else + #endif + UnpackSize += _curSize; + } + + if (_item.IsDir) + NumFolders++; + #ifdef SUPPORT_ALT_STREAMS + else if (_item.IsAltStream) + NumAltStreams++; + #endif + else + NumFiles++; + + if (_needSetAttrib) + SetAttrib(); + + RINOK(_extractCallback2->SetOperationResult(opRes, BoolToInt(_encrypted))) + + return S_OK; + + COM_TRY_END +} + + + +Z7_COM7F_IMF(CArchiveExtractCallback::ReportExtractResult(UInt32 indexType, UInt32 index, Int32 opRes)) +{ + if (_folderArchiveExtractCallback2) + { + bool isEncrypted = false; + UString s; + + if (indexType == NArchive::NEventIndexType::kInArcIndex && index != (UInt32)(Int32)-1) + { + CReadArcItem item; + RINOK(_arc->GetItem(index, item)) + s = item.Path; + RINOK(Archive_GetItemBoolProp(_arc->Archive, index, kpidEncrypted, isEncrypted)) + } + else + { + s = '#'; + s.Add_UInt32(index); + // if (indexType == NArchive::NEventIndexType::kBlockIndex) {} + } + + return _folderArchiveExtractCallback2->ReportExtractResult(opRes, isEncrypted, s); + } + + return S_OK; +} + + +Z7_COM7F_IMF(CArchiveExtractCallback::CryptoGetTextPassword(BSTR *password)) +{ + COM_TRY_BEGIN + if (!_cryptoGetTextPassword) + { + RINOK(_extractCallback2.QueryInterface(IID_ICryptoGetTextPassword, + &_cryptoGetTextPassword)) + } + return _cryptoGetTextPassword->CryptoGetTextPassword(password); + COM_TRY_END +} + + +#ifndef Z7_SFX + +// ---------- HASH functions ---------- + +FString CArchiveExtractCallback::Hash_GetFullFilePath() +{ + // this function changes _item.PathParts. + CorrectPathParts(); + const UStringVector &pathParts = _item.PathParts; + const UString processedPath (MakePathFromParts(pathParts)); + FString fullProcessedPath (us2fs(processedPath)); + if (_pathMode != NExtract::NPathMode::kAbsPaths + || !NName::IsAbsolutePath(processedPath)) + { + fullProcessedPath = MakePath_from_2_Parts( + DirPathPrefix_for_HashFiles, + // _dirPathPrefix, + fullProcessedPath); + } + return fullProcessedPath; +} + + +Z7_COM7F_IMF(CArchiveExtractCallback::GetDiskProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) +{ + COM_TRY_BEGIN + NCOM::CPropVariant prop; + if (propID == kpidSize) + { + RINOK(GetItem(index)) + const FString fullProcessedPath = Hash_GetFullFilePath(); + NFile::NFind::CFileInfo fi; + if (fi.Find_FollowLink(fullProcessedPath)) + if (!fi.IsDir()) + prop = (UInt64)fi.Size; + } + prop.Detach(value); + return S_OK; + COM_TRY_END +} + + +Z7_COM7F_IMF(CArchiveExtractCallback::GetStream2(UInt32 index, ISequentialInStream **inStream, UInt32 mode)) +{ + COM_TRY_BEGIN + *inStream = NULL; + // if (index != _index) return E_FAIL; + if (mode != NUpdateNotifyOp::kHashRead) + return E_FAIL; + + RINOK(GetItem(index)) + const FString fullProcessedPath = Hash_GetFullFilePath(); + + CInFileStream *inStreamSpec = new CInFileStream; + CMyComPtr inStreamRef = inStreamSpec; + inStreamSpec->Set_PreserveATime(_ntOptions.PreserveATime); + if (!inStreamSpec->OpenShared(fullProcessedPath, _ntOptions.OpenShareForWrite)) + { + RINOK(SendMessageError_with_LastError(kCantOpenInFile, fullProcessedPath)) + return S_OK; + } + *inStream = inStreamRef.Detach(); + return S_OK; + COM_TRY_END +} + + +Z7_COM7F_IMF(CArchiveExtractCallback::ReportOperation( + UInt32 /* indexType */, UInt32 /* index */, UInt32 /* op */)) +{ + // COM_TRY_BEGIN + return S_OK; + // COM_TRY_END +} + + +Z7_COM7F_IMF(CArchiveExtractCallback::RequestMemoryUse( + UInt32 flags, UInt32 indexType, UInt32 index, const wchar_t *path, + UInt64 requiredSize, UInt64 *allowedSize, UInt32 *answerFlags)) +{ + if ((flags & NRequestMemoryUseFlags::k_IsReport) == 0) + { + const UInt64 memLimit = _ntOptions.MemLimit; + if (memLimit != (UInt64)(Int64)-1) + { + // we overwrite allowedSize + *allowedSize = memLimit; + if (requiredSize <= memLimit) + { + *answerFlags = NRequestMemoryAnswerFlags::k_Allow; + return S_OK; + } + *answerFlags = NRequestMemoryAnswerFlags::k_Limit_Exceeded; + if (flags & NRequestMemoryUseFlags::k_SkipArc_IsExpected) + *answerFlags |= NRequestMemoryAnswerFlags::k_SkipArc; + flags |= NRequestMemoryUseFlags::k_SLimit_Exceeded + | NRequestMemoryUseFlags::k_AllowedSize_WasForced; + } + } + + if (!_requestMemoryUseCallback) + { + _extractCallback2.QueryInterface(IID_IArchiveRequestMemoryUseCallback, + &_requestMemoryUseCallback); + if (!_requestMemoryUseCallback) + { + // keep default (answerFlags) from caller or (answerFlags) that was set in this function + return S_OK; + } + } + +#if 0 + if ((flags & NRequestMemoryUseFlags::k_IsReport) == 0) + if (requiredSize <= *allowedSize) + { + // it's expected, that *answerFlags was set to NRequestMemoryAnswerFlags::k_Allow already, + // because it's default answer for (requiredSize <= *allowedSize) case. + *answerFlags = NRequestMemoryAnswerFlags::k_Allow; // optional code + } + else + { + // we clear *answerFlags, because we want to disable dafault "Allow", if it's set. + // *answerFlags = 0; + /* + NRequestMemoryAnswerFlags::k_SkipArc | + NRequestMemoryAnswerFlags::k_Limit_Exceeded; + */ + } +#endif + + UString s; + if (!path + && indexType == NArchive::NEventIndexType::kInArcIndex + && index != (UInt32)(Int32)-1 + && _arc) + { + RINOK(_arc->GetItem_Path(index, s)) + path = s.Ptr(); + } + + return _requestMemoryUseCallback->RequestMemoryUse( + flags, indexType, index, path, + requiredSize, allowedSize, answerFlags); +} + +#endif // Z7_SFX + + + +// ------------ After Extracting functions ------------ + +void CDirPathSortPair::SetNumSlashes(const FChar *s) +{ + for (unsigned numSlashes = 0;;) + { + FChar c = *s++; + if (c == 0) + { + Len = numSlashes; + return; + } + if (IS_PATH_SEPAR(c)) + numSlashes++; + } +} + + +bool CFiTimesCAM::SetDirTime_to_FS(CFSTR path) const +{ + // it's same function for dir and for file + return NDir::SetDirTime(path, + CTime_Defined ? &CTime : NULL, + ATime_Defined ? &ATime : NULL, + MTime_Defined ? &MTime : NULL); +} + + +#ifdef SUPPORT_LINKS + +bool CFiTimesCAM::SetLinkFileTime_to_FS(CFSTR path) const +{ + // it's same function for dir and for file + return NDir::SetLinkFileTime(path, + CTime_Defined ? &CTime : NULL, + ATime_Defined ? &ATime : NULL, + MTime_Defined ? &MTime : NULL); +} + +HRESULT CArchiveExtractCallback::SetPostLinks() const +{ + FOR_VECTOR (i, _postLinks) + { + const CPostLink &link = _postLinks[i]; + bool linkWasSet = false; + RINOK(SetLink2(*this, link, linkWasSet)) + if (linkWasSet) + { +#ifdef _WIN32 + // Linux now doesn't support permissions for symlinks + SetAttrib_Base(link.fullProcessedPath_from, link.item_FileInfo, *this); +#endif + + CFiTimesCAM pt; + GetFiTimesCAM(link.item_FileInfo, pt, *_arc); + if (pt.IsSomeTimeDefined()) + pt.SetLinkFileTime_to_FS(link.fullProcessedPath_from); + +#ifdef Z7_USE_SECURITY_CODE + // we set security information after timestamps setting + RINOK(SetSecurityInfo(link.Index_in_Arc, link.fullProcessedPath_from)) +#endif + } + } + return S_OK; +} + +#endif + + +HRESULT CArchiveExtractCallback::SetDirsTimes() +{ + if (!_arc) + return S_OK; + + CRecordVector pairs; + pairs.ClearAndSetSize(_extractedFolders.Size()); + unsigned i; + + for (i = 0; i < _extractedFolders.Size(); i++) + { + CDirPathSortPair &pair = pairs[i]; + pair.Index = i; + pair.SetNumSlashes(_extractedFolders[i].Path); + } + + pairs.Sort2(); + + HRESULT res = S_OK; + + for (i = 0; i < pairs.Size(); i++) + { + const CDirPathTime &dpt = _extractedFolders[pairs[i].Index]; + if (!dpt.SetDirTime_to_FS_2()) + { + // result = E_FAIL; + // do we need error message here in Windows and in posix? + // SendMessageError_with_LastError("Cannot set directory time", dpt.Path); + } + } + + /* + #ifndef _WIN32 + for (i = 0; i < _delayedSymLinks.Size(); i++) + { + const CDelayedSymLink &link = _delayedSymLinks[i]; + if (!link.Create()) + { + if (res == S_OK) + res = GetLastError_noZero_HRESULT(); + // res = E_FAIL; + // do we need error message here in Windows and in posix? + SendMessageError_with_LastError("Cannot create Symbolic Link", link._source); + } + } + #endif // _WIN32 + */ + + ClearExtractedDirsInfo(); + return res; +} + + +HRESULT CArchiveExtractCallback::CloseArc() +{ + // we call CloseReparseAndFile() here because we can have non-closed file in some cases? + HRESULT res = CloseReparseAndFile(); +#ifdef SUPPORT_LINKS + { + const HRESULT res2 = SetPostLinks(); + if (res == S_OK) + res = res2; + } +#endif + { + const HRESULT res2 = SetDirsTimes(); + if (res == S_OK) + res = res2; + } + _arc = NULL; + return res; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/ArchiveExtractCallback.h b/src/plugins/7zip/7za/cpp/7zip/UI/Common/ArchiveExtractCallback.h new file mode 100644 index 00000000..3c627634 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/ArchiveExtractCallback.h @@ -0,0 +1,641 @@ +// ArchiveExtractCallback.h + +#ifndef ZIP7_INC_ARCHIVE_EXTRACT_CALLBACK_H +#define ZIP7_INC_ARCHIVE_EXTRACT_CALLBACK_H + +#include "../../../Common/MyCom.h" +#include "../../../Common/MyLinux.h" +#include "../../../Common/Wildcard.h" + +#include "../../IPassword.h" + +#include "../../Common/FileStreams.h" +#include "../../Common/ProgressUtils.h" +#include "../../Common/StreamObjects.h" + +#include "../../Archive/IArchive.h" + +#include "ExtractMode.h" +#include "IFileExtractCallback.h" +#include "OpenArchive.h" + +#include "HashCalc.h" + +#ifndef Z7_SFX + +Z7_CLASS_IMP_NOQIB_1( + COutStreamWithHash + , ISequentialOutStream +) + bool _calculate; + CMyComPtr _stream; + UInt64 _size; +public: + IHashCalc *_hash; + + void SetStream(ISequentialOutStream *stream) { _stream = stream; } + void ReleaseStream() { _stream.Release(); } + void Init(bool calculate = true) + { + InitCRC(); + _size = 0; + _calculate = calculate; + } + void EnableCalc(bool calculate) { _calculate = calculate; } + void InitCRC() { _hash->InitForNewFile(); } + UInt64 GetSize() const { return _size; } +}; + +#endif + +struct CExtractNtOptions +{ + CBoolPair NtSecurity; + CBoolPair SymLinks; + CBoolPair HardLinks; + CBoolPair AltStreams; + bool ReplaceColonForAltStream; + bool WriteToAltStreamIfColon; + + bool ExtractOwner; + + bool PreAllocateOutFile; + + // used for hash arcs only, when we open external files + bool PreserveATime; + bool OpenShareForWrite; + + unsigned SymLinks_DangerousLevel; + + UInt64 MemLimit; + + CExtractNtOptions(): + ReplaceColonForAltStream(false), + WriteToAltStreamIfColon(false), + ExtractOwner(false), + PreserveATime(false), + OpenShareForWrite(false), + SymLinks_DangerousLevel(5), + MemLimit((UInt64)(Int64)-1) + { + SymLinks.Val = true; + HardLinks.Val = true; + AltStreams.Val = true; + + PreAllocateOutFile = + #ifdef _WIN32 + true; + #else + false; + #endif + } +}; + + +#ifndef Z7_SFX +#ifndef UNDER_CE +#define SUPPORT_LINKS +#endif +#endif + + +#ifdef SUPPORT_LINKS + +struct CHardLinkNode +{ + UInt64 StreamId; + UInt64 INode; + + int Compare(const CHardLinkNode &a) const; +}; + +class CHardLinks +{ +public: + CRecordVector IDs; + CObjectVector Links; + + void Clear() + { + IDs.Clear(); + Links.Clear(); + } + + void PrepareLinks() + { + while (Links.Size() < IDs.Size()) + Links.AddNew(); + } +}; + +#endif + +#ifdef SUPPORT_ALT_STREAMS + +struct CIndexToPathPair +{ + UInt32 Index; + FString Path; + + CIndexToPathPair(UInt32 index): Index(index) {} + CIndexToPathPair(UInt32 index, const FString &path): Index(index), Path(path) {} + + int Compare(const CIndexToPathPair &pair) const + { + return MyCompare(Index, pair.Index); + } +}; + +#endif + + + +struct CFiTimesCAM +{ + CFiTime CTime; + CFiTime ATime; + CFiTime MTime; + + bool CTime_Defined; + bool ATime_Defined; + bool MTime_Defined; + + bool IsSomeTimeDefined() const + { + return + CTime_Defined | + ATime_Defined | + MTime_Defined; + } + bool SetDirTime_to_FS(CFSTR path) const; +#ifdef SUPPORT_LINKS + bool SetLinkFileTime_to_FS(CFSTR path) const; +#endif +}; + +struct CDirPathTime: public CFiTimesCAM +{ + FString Path; + + bool SetDirTime_to_FS_2() const { return SetDirTime_to_FS(Path); } +}; + + +#ifdef SUPPORT_LINKS + +enum ELinkType +{ + k_LinkType_HardLink, + k_LinkType_PureSymLink, + k_LinkType_Junction, + k_LinkType_WSL + // , k_LinkType_CopyLink; +}; + + +struct CLinkInfo +{ + ELinkType LinkType; + bool isRelative; + // if (isRelative == false), then (LinkPath) is relative to root folder of archive + // if (isRelative == true ), then (LinkPath) is relative to current item + bool isWindowsPath; + UString LinkPath; + + bool Is_HardLink() const { return LinkType == k_LinkType_HardLink; } + bool Is_AnySymLink() const { return LinkType != k_LinkType_HardLink; } + + bool Is_WSL() const { return LinkType == k_LinkType_WSL; } + + CLinkInfo(): + LinkType(k_LinkType_PureSymLink), + isRelative(false), + isWindowsPath(false) + {} + + void Clear() + { + LinkType = k_LinkType_PureSymLink; + isRelative = false; + isWindowsPath = false; + LinkPath.Empty(); + } + + bool Parse_from_WindowsReparseData(const Byte *data, size_t dataSize); + bool Parse_from_LinuxData(const Byte *data, size_t dataSize); + void Normalize_to_RelativeSafe(UStringVector &removePathParts); +private: + void Remove_AbsPathPrefixes(); +}; + +#endif // SUPPORT_LINKS + + + +struct CProcessedFileInfo +{ + CArcTime CTime; + CArcTime ATime; + CArcTime MTime; + UInt32 Attrib; + bool Attrib_Defined; + +#ifndef _WIN32 + +struct COwnerInfo +{ + bool Id_Defined; + UInt32 Id; + AString Name; + + void Clear() + { + Id_Defined = false; + Id = 0; + Name.Empty(); + } +}; + + COwnerInfo Owner; + COwnerInfo Group; +#endif + + void Clear() + { +#ifndef _WIN32 + Attrib_Defined = false; + Owner.Clear(); +#endif + } + + bool IsReparse() const + { + return (Attrib_Defined && (Attrib & FILE_ATTRIBUTE_REPARSE_POINT) != 0); + } + + bool IsLinuxSymLink() const + { + return (Attrib_Defined && MY_LIN_S_ISLNK(Attrib >> 16)); + } + + void SetFromPosixAttrib(UInt32 a) + { + // here we set only part of combined attribute required by SetFileAttrib() call + #ifdef _WIN32 + // Windows sets FILE_ATTRIBUTE_NORMAL, if we try to set 0 as attribute. + Attrib = MY_LIN_S_ISDIR(a) ? + FILE_ATTRIBUTE_DIRECTORY : + FILE_ATTRIBUTE_ARCHIVE; + if ((a & 0222) == 0) // (& S_IWUSR) in p7zip + Attrib |= FILE_ATTRIBUTE_READONLY; + // 22.00 : we need type bits for (MY_LIN_S_IFLNK) for IsLinuxSymLink() + a &= MY_LIN_S_IFMT; + if (a == MY_LIN_S_IFLNK) + Attrib |= (a << 16); + #else + Attrib = (a << 16) | FILE_ATTRIBUTE_UNIX_EXTENSION; + #endif + Attrib_Defined = true; + } +}; + + +#ifdef SUPPORT_LINKS + +struct CPostLink +{ + UInt32 Index_in_Arc; + bool item_IsDir; // _item.IsDir + UString item_Path; // _item.Path; + UStringVector item_PathParts; // _item.PathParts; + CProcessedFileInfo item_FileInfo; // _fi + FString fullProcessedPath_from; // full file path in FS + CLinkInfo LinkInfo; +}; + +/* +struct CPostLinks +{ + void Clear() + { + Links.Clear(); + } +}; +*/ + +#endif // SUPPORT_LINKS + + + +class CArchiveExtractCallback Z7_final: + public IArchiveExtractCallback, + public IArchiveExtractCallbackMessage2, + public ICryptoGetTextPassword, + public ICompressProgressInfo, +#ifndef Z7_SFX + public IArchiveUpdateCallbackFile, + public IArchiveGetDiskProperty, + public IArchiveRequestMemoryUseCallback, +#endif + public CMyUnknownImp +{ + /* IArchiveExtractCallback, */ + Z7_COM_QI_BEGIN2(IArchiveExtractCallbackMessage2) + Z7_COM_QI_ENTRY(ICryptoGetTextPassword) + Z7_COM_QI_ENTRY(ICompressProgressInfo) +#ifndef Z7_SFX + Z7_COM_QI_ENTRY(IArchiveUpdateCallbackFile) + Z7_COM_QI_ENTRY(IArchiveGetDiskProperty) + Z7_COM_QI_ENTRY(IArchiveRequestMemoryUseCallback) +#endif + Z7_COM_QI_END + Z7_COM_ADDREF_RELEASE + + Z7_IFACE_COM7_IMP(IProgress) + Z7_IFACE_COM7_IMP(IArchiveExtractCallback) + Z7_IFACE_COM7_IMP(IArchiveExtractCallbackMessage2) + Z7_IFACE_COM7_IMP(ICryptoGetTextPassword) + Z7_IFACE_COM7_IMP(ICompressProgressInfo) +#ifndef Z7_SFX + Z7_IFACE_COM7_IMP(IArchiveUpdateCallbackFile) + Z7_IFACE_COM7_IMP(IArchiveGetDiskProperty) + Z7_IFACE_COM7_IMP(IArchiveRequestMemoryUseCallback) +#endif + + // bool Write_CTime; + // bool Write_ATime; + // bool Write_MTime; + bool _stdOutMode; + bool _testMode; + bool _removePartsForAltStreams; +public: + bool Is_elimPrefix_Mode; +private: + + const CArc *_arc; +public: + CExtractNtOptions _ntOptions; +private: + bool _encrypted; + bool _isSplit; + bool _curSize_Defined; + bool _fileLength_WasSet; + + bool _isRenamed; + bool _extractMode; + bool _is_SymLink_in_Data_Linux; // false = WIN32, true = LINUX. + // _is_SymLink_in_Data_Linux is detected from Windows/Linux part of attributes of file. + bool _needSetAttrib; + bool _isSymLinkCreated; + bool _itemFailure; + bool _some_pathParts_wereRemoved; + + bool _multiArchives; + bool _keepAndReplaceEmptyDirPrefixes; // replace them to "_"; +#if defined(_WIN32) && !defined(UNDER_CE) && !defined(Z7_SFX) + bool _saclEnabled; +#endif + + NExtract::NPathMode::EEnum _pathMode; + NExtract::NOverwriteMode::EEnum _overwriteMode; + + CMyComPtr _extractCallback2; + const NWildcard::CCensorNode *_wildcardCensor; // we need wildcard for single pass mode (stdin) + // CMyComPtr _compressProgress; + // CMyComPtr _callbackMessage; + CMyComPtr _folderArchiveExtractCallback2; + CMyComPtr _cryptoGetTextPassword; + + FString _dirPathPrefix; +public: + FString _dirPathPrefix_Full; +private: + + #ifndef Z7_SFX + + CMyComPtr ExtractToStreamCallback; + CMyComPtr _requestMemoryUseCallback; + + #endif + + CReadArcItem _item; + FString _diskFilePath; + + CProcessedFileInfo _fi; + + UInt64 _position; + UInt64 _curSize; + UInt64 _fileLength_that_WasSet; + UInt32 _index; + +// #ifdef SUPPORT_ALT_STREAMS +#if defined(_WIN32) && !defined(UNDER_CE) + DWORD _altStream_NeedRestore_AttribVal; + FString _altStream_NeedRestore_Attrib_for_parentFsPath; +#endif +// #endif + + COutFileStream *_outFileStreamSpec; + CMyComPtr _outFileStream; + + CByteBuffer _outMemBuf; + CBufPtrSeqOutStream *_bufPtrSeqOutStream_Spec; + CMyComPtr _bufPtrSeqOutStream; + + #ifndef Z7_SFX + COutStreamWithHash *_hashStreamSpec; + CMyComPtr _hashStream; + bool _hashStreamWasUsed; + + bool _use_baseParentFolder_mode; + UInt32 _baseParentFolder; + #endif + + UStringVector _removePathParts; + + UInt64 _packTotal; + UInt64 _progressTotal; + // bool _progressTotal_Defined; + + CObjectVector _extractedFolders; + + #ifndef _WIN32 + // CObjectVector _delayedSymLinks; + #endif + + void CreateComplexDirectory( + const UStringVector &dirPathParts, bool isFinal, FString &fullPath); + HRESULT GetTime(UInt32 index, PROPID propID, CArcTime &ft); + HRESULT GetUnpackSize(); + + FString Hash_GetFullFilePath(); + + void SetAttrib() const; + +public: + HRESULT SendMessageError(const char *message, const FString &path) const; + HRESULT SendMessageError_with_Error(HRESULT errorCode, const char *message, const FString &path) const; + HRESULT SendMessageError_with_LastError(const char *message, const FString &path) const; + HRESULT SendMessageError2(HRESULT errorCode, const char *message, const FString &path1, const FString &path2) const; + HRESULT SendMessageError2_with_LastError(const char *message, const FString &path1, const FString &path2) const; + +#if defined(_WIN32) && !defined(UNDER_CE) && !defined(Z7_SFX) + NExtract::NZoneIdMode::EEnum ZoneMode; + CByteBuffer ZoneBuf; +#endif + + CMyComPtr2_Create LocalProgressSpec; + + UInt64 NumFolders; + UInt64 NumFiles; + UInt64 NumAltStreams; + UInt64 UnpackSize; + UInt64 AltStreams_UnpackSize; + + FString DirPathPrefix_for_HashFiles; + + CArchiveExtractCallback(); + + void InitForMulti(bool multiArchives, + NExtract::NPathMode::EEnum pathMode, + NExtract::NOverwriteMode::EEnum overwriteMode, + NExtract::NZoneIdMode::EEnum zoneMode, + bool keepAndReplaceEmptyDirPrefixes) + { + _multiArchives = multiArchives; + _pathMode = pathMode; + _overwriteMode = overwriteMode; +#if defined(_WIN32) && !defined(UNDER_CE) && !defined(Z7_SFX) + ZoneMode = zoneMode; +#else + UNUSED_VAR(zoneMode) +#endif + _keepAndReplaceEmptyDirPrefixes = keepAndReplaceEmptyDirPrefixes; + NumFolders = NumFiles = NumAltStreams = UnpackSize = AltStreams_UnpackSize = 0; + } + + #ifndef Z7_SFX + + void SetHashMethods(IHashCalc *hash) + { + if (!hash) + return; + _hashStreamSpec = new COutStreamWithHash; + _hashStream = _hashStreamSpec; + _hashStreamSpec->_hash = hash; + } + + #endif + + void InitBeforeNewArchive(); + + void Init( + const CExtractNtOptions &ntOptions, + const NWildcard::CCensorNode *wildcardCensor, + const CArc *arc, + IFolderArchiveExtractCallback *extractCallback2, + bool stdOutMode, bool testMode, + const FString &directoryPath, + const UStringVector &removePathParts, bool removePartsForAltStreams, + UInt64 packSize); + + +#ifdef SUPPORT_LINKS + +private: + CHardLinks _hardLinks; + CObjectVector _postLinks; + CLinkInfo _link; + // const void *NtReparse_Data; + // UInt32 NtReparse_Size; + + // FString _copyFile_Path; + // HRESULT MyCopyFile(ISequentialOutStream *outStream); + HRESULT ReadLink(); + HRESULT SetLink( + const FString &fullProcessedPath_from, + const CLinkInfo &linkInfo, + bool &linkWasSet); + HRESULT SetPostLinks() const; + +public: + HRESULT CreateHardLink2(const FString &newFilePath, + const FString &existFilePath, bool &link_was_Created) const; + HRESULT DeleteLinkFileAlways_or_RemoveEmptyDir(const FString &path, bool checkThatFileIsEmpty) const; + HRESULT PrepareHardLinks(const CRecordVector *realIndices); // NULL means all items +#endif + +private: + + #ifdef SUPPORT_ALT_STREAMS + CObjectVector _renamedFiles; + #endif + + // call it after Init() + +public: + #ifndef Z7_SFX + void SetBaseParentFolderIndex(UInt32 indexInArc) + { + _baseParentFolder = indexInArc; + _use_baseParentFolder_mode = true; + } + #endif + + HRESULT CloseArc(); + +private: + void ClearExtractedDirsInfo() + { + _extractedFolders.Clear(); + #ifndef _WIN32 + // _delayedSymLinks.Clear(); + #endif + } + + HRESULT Read_fi_Props(); + void CorrectPathParts(); + void CreateFolders(); + + HRESULT CheckExistFile(FString &fullProcessedPath, bool &needExit); + HRESULT GetExtractStream(CMyComPtr &outStreamLoc, bool &needExit); + HRESULT GetItem(UInt32 index); + + HRESULT CloseFile(); + HRESULT CloseReparseAndFile(); + HRESULT SetDirsTimes(); + HRESULT SetSecurityInfo(UInt32 indexInArc, const FString &path) const; +}; + + +struct CArchiveExtractCallback_Closer +{ + CArchiveExtractCallback *_ref; + + CArchiveExtractCallback_Closer(CArchiveExtractCallback *ref): _ref(ref) {} + + HRESULT Close() + { + HRESULT res = S_OK; + if (_ref) + { + res = _ref->CloseArc(); + _ref = NULL; + } + return res; + } + + ~CArchiveExtractCallback_Closer() + { + Close(); + } +}; + + +bool CensorNode_CheckPath(const NWildcard::CCensorNode &node, const CReadArcItem &item); + +bool Is_ZoneId_StreamName(const wchar_t *s); +void ReadZoneFile_Of_BaseFile(CFSTR fileName, CByteBuffer &buf); +bool WriteZoneFile_To_BaseFile(CFSTR fileName, const CByteBuffer &buf); + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/ArchiveName.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Common/ArchiveName.cpp new file mode 100644 index 00000000..f859d94f --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/ArchiveName.cpp @@ -0,0 +1,176 @@ +// ArchiveName.cpp + +#include "StdAfx.h" + +#include "../../../../C/Sort.h" + +#include "../../../Common/Wildcard.h" +#include "../../../Common/StringToInt.h" + +#include "../../../Windows/FileDir.h" +#include "../../../Windows/FileName.h" + +#include "ArchiveName.h" +#include "ExtractingFilePath.h" + +using namespace NWindows; +using namespace NFile; + + +static const char * const g_ArcExts = + "7z" + "\0" "zip" + "\0" "tar" + "\0" "wim" + "\0"; + +static const char * const g_HashExts = + "sha256" + "\0"; + + +UString CreateArchiveName( + const UStringVector &paths, + bool isHash, + const NFind::CFileInfo *fi, + UString &baseName) +{ + bool keepName = isHash; + /* + if (paths.Size() == 1) + { + const UString &name = paths[0]; + if (name.Len() > 4) + if (CompareFileNames(name.RightPtr(4), L".tar") == 0) + keepName = true; + } + */ + + UString name ("Archive"); + NFind::CFileInfo fi3; + if (paths.Size() > 1) + fi = NULL; + if (!fi && paths.Size() != 0) + { + const UString &path = paths.Front(); + if (paths.Size() == 1) + { + if (fi3.Find(us2fs(path))) + fi = &fi3; + } + else + { + // we try to use name of parent folder + FString dirPrefix; + if (NDir::GetOnlyDirPrefix(us2fs(path), dirPrefix)) + { + if (!dirPrefix.IsEmpty() && IsPathSepar(dirPrefix.Back())) + { + #if defined(_WIN32) && !defined(UNDER_CE) + if (NName::IsDriveRootPath_SuperAllowed(dirPrefix)) + { + if (path != fs2us(dirPrefix)) + name = dirPrefix[dirPrefix.Len() - 3]; // only letter + } + else + #endif + { + dirPrefix.DeleteBack(); + if (!dirPrefix.IsEmpty()) + { + const int slash = dirPrefix.ReverseFind_PathSepar(); + if (slash >= 0 && slash != (int)dirPrefix.Len() - 1) + name = dirPrefix.Ptr(slash + 1); + else if (fi3.Find(dirPrefix)) + name = fs2us(fi3.Name); + } + } + } + } + } + } + + if (fi) + { + name = fs2us(fi->Name); + if (!fi->IsDir() && !keepName) + { + const int dotPos = name.Find(L'.'); + if (dotPos > 0 && name.Find(L'.', (unsigned)dotPos + 1) < 0) + name.DeleteFrom((unsigned)dotPos); + } + } + name = Get_Correct_FsFile_Name(name); + + CRecordVector ids; + bool simple_IsAllowed = true; + // for (int y = 0; y < 10000; y++) // for debug + { + // ids.Clear(); + UString n; + + FOR_VECTOR (i, paths) + { + const UString &a = paths[i]; + const int slash = a.ReverseFind_PathSepar(); + // if (name.Len() >= a.Len() - slash + 1) continue; + const wchar_t *s = a.Ptr(slash + 1); + if (!IsPath1PrefixedByPath2(s, name)) + continue; + s += name.Len(); + const char *exts = isHash ? g_HashExts : g_ArcExts; + + for (;;) + { + const char *ext = exts; + const unsigned len = MyStringLen(ext); + if (len == 0) + break; + exts += len + 1; + n = s; + if (n.Len() <= len) + continue; + if (!StringsAreEqualNoCase_Ascii(n.RightPtr(len), ext)) + continue; + n.DeleteFrom(n.Len() - len); + if (n.Back() != '.') + continue; + n.DeleteBack(); + if (n.IsEmpty()) + { + simple_IsAllowed = false; + break; + } + if (n.Len() < 2) + continue; + if (n[0] != '_') + continue; + const wchar_t *end; + const UInt32 v = ConvertStringToUInt32(n.Ptr(1), &end); + if (*end != 0) + continue; + ids.Add(v); + break; + } + } + } + + baseName = name; + if (!simple_IsAllowed) + { + HeapSort(ids.NonConstData(), ids.Size()); + UInt32 v = 2; + const unsigned num = ids.Size(); + for (unsigned i = 0; i < num; i++) + { + const UInt32 id = ids[i]; + if (id > v) + break; + if (id == v) + v = id + 1; + } + name.Add_Char('_'); + name.Add_UInt32(v); + } + return name; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/ArchiveName.h b/src/plugins/7zip/7za/cpp/7zip/UI/Common/ArchiveName.h new file mode 100644 index 00000000..9b6b7fe9 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/ArchiveName.h @@ -0,0 +1,16 @@ +// ArchiveName.h + +#ifndef ZIP7_INC_ARCHIVE_NAME_H +#define ZIP7_INC_ARCHIVE_NAME_H + +#include "../../../Windows/FileFind.h" + +/* (fi != NULL) only if (paths.Size() == 1) */ + +UString CreateArchiveName( + const UStringVector &paths, + bool isHash, + const NWindows::NFile::NFind::CFileInfo *fi, + UString &baseName); + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/ArchiveOpenCallback.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Common/ArchiveOpenCallback.cpp new file mode 100644 index 00000000..8a729369 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/ArchiveOpenCallback.cpp @@ -0,0 +1,398 @@ +// ArchiveOpenCallback.cpp + +#include "StdAfx.h" + +#include "../../../Common/ComTry.h" + +#include "../../../Windows/FileName.h" +#include "../../../Windows/PropVariant.h" +#include "../../../Windows/System.h" + +#include "../../Common/StreamUtils.h" + +#include "ArchiveOpenCallback.h" + +// #define DEBUG_VOLUMES + +#ifdef DEBUG_VOLUMES +#include +#endif + + +#ifdef DEBUG_VOLUMES + #define PRF(x) x +#else + #define PRF(x) +#endif + +using namespace NWindows; + +HRESULT COpenCallbackImp::Init2(const FString &folderPrefix, const FString &fileName) +{ + Volumes.Init(); + FileNames.Clear(); + FileNames_WasUsed.Clear(); + FileSizes.Clear(); + _subArchiveMode = false; + // TotalSize = 0; + PasswordWasAsked = false; + _folderPrefix = folderPrefix; + if (!_fileInfo.Find_FollowLink(_folderPrefix + fileName)) + { + // throw 20121118; + return GetLastError_noZero_HRESULT(); + } + return S_OK; +} + +Z7_COM7F_IMF(COpenCallbackImp::SetSubArchiveName(const wchar_t *name)) +{ + _subArchiveMode = true; + _subArchiveName = name; + // TotalSize = 0; + return S_OK; +} + +Z7_COM7F_IMF(COpenCallbackImp::SetTotal(const UInt64 *files, const UInt64 *bytes)) +{ + COM_TRY_BEGIN + if (ReOpenCallback) + return ReOpenCallback->SetTotal(files, bytes); + if (!Callback) + return S_OK; + return Callback->Open_SetTotal(files, bytes); + COM_TRY_END +} + +Z7_COM7F_IMF(COpenCallbackImp::SetCompleted(const UInt64 *files, const UInt64 *bytes)) +{ + COM_TRY_BEGIN + if (ReOpenCallback) + return ReOpenCallback->SetCompleted(files, bytes); + if (!Callback) + return S_OK; + return Callback->Open_SetCompleted(files, bytes); + COM_TRY_END +} + + +Z7_COM7F_IMF(COpenCallbackImp::GetProperty(PROPID propID, PROPVARIANT *value)) +{ + COM_TRY_BEGIN + NCOM::CPropVariant prop; + if (_subArchiveMode) + switch (propID) + { + case kpidName: prop = _subArchiveName; break; + // case kpidSize: prop = _subArchiveSize; break; // we don't use it now + default: break; + } + else + switch (propID) + { + case kpidName: prop = fs2us(_fileInfo.Name); break; + case kpidIsDir: prop = _fileInfo.IsDir(); break; + case kpidSize: prop = _fileInfo.Size; break; + case kpidAttrib: prop = (UInt32)_fileInfo.GetWinAttrib(); break; + case kpidPosixAttrib: prop = (UInt32)_fileInfo.GetPosixAttrib(); break; + case kpidCTime: PropVariant_SetFrom_FiTime(prop, _fileInfo.CTime); break; + case kpidATime: PropVariant_SetFrom_FiTime(prop, _fileInfo.ATime); break; + case kpidMTime: PropVariant_SetFrom_FiTime(prop, _fileInfo.MTime); break; + default: break; + } + prop.Detach(value); + return S_OK; + COM_TRY_END +} + + +// ---------- CInFileStreamVol ---------- + +Z7_class_final(CInFileStreamVol): + public IInStream + , public IStreamGetSize + , public CMyUnknownImp +{ + Z7_IFACES_IMP_UNK_3( + IInStream, + ISequentialInStream, + IStreamGetSize) +public: + unsigned FileIndex; + COpenCallbackImp *OpenCallbackImp; + CMyComPtr OpenCallbackRef; + + HRESULT EnsureOpen() + { + return OpenCallbackImp->Volumes.EnsureOpen(FileIndex); + } + + ~CInFileStreamVol() + { + if (OpenCallbackRef) + OpenCallbackImp->AtCloseFile(FileIndex); + } +}; + + +void CMultiStreams::InsertToList(unsigned index) +{ + { + CSubStream &s = Streams[index]; + s.Next = Head; + s.Prev = -1; + } + if (Head != -1) + Streams[(unsigned)Head].Prev = (int)index; + else + { + // if (Tail != -1) throw 1; + Tail = (int)index; + } + Head = (int)index; + NumListItems++; +} + +// s must bee in List +void CMultiStreams::RemoveFromList(CSubStream &s) +{ + if (s.Next != -1) Streams[(unsigned)s.Next].Prev = s.Prev; else Tail = s.Prev; + if (s.Prev != -1) Streams[(unsigned)s.Prev].Next = s.Next; else Head = s.Next; + s.Next = -1; // optional + s.Prev = -1; // optional + NumListItems--; +} + +void CMultiStreams::CloseFile(unsigned index) +{ + CSubStream &s = Streams[index]; + if (s.Stream) + { + s.Stream.Release(); + RemoveFromList(s); + // s.InFile->Close(); + // s.IsOpen = false; + #ifdef DEBUG_VOLUMES + static int numClosing = 0; + numClosing++; + printf("\nCloseFile %u, total_closes = %u, num_open_files = %u\n", index, numClosing, NumListItems); + #endif + } +} + +void CMultiStreams::Init() +{ + Head = -1; + Tail = -1; + NumListItems = 0; + Streams.Clear(); +} + +CMultiStreams::CMultiStreams(): + Head(-1), + Tail(-1), + NumListItems(0) +{ + NumOpenFiles_AllowedMax = NSystem::Get_File_OPEN_MAX_Reduced_for_3_tasks(); + PRF(printf("\nNumOpenFiles_Limit = %u\n", NumOpenFiles_AllowedMax)); +} + + +HRESULT CMultiStreams::PrepareToOpenNew() +{ + if (NumListItems < NumOpenFiles_AllowedMax) + return S_OK; + if (Tail == -1) + return E_FAIL; + CMultiStreams::CSubStream &tailStream = Streams[(unsigned)Tail]; + RINOK(InStream_GetPos(tailStream.Stream, tailStream.LocalPos)) + CloseFile((unsigned)Tail); + return S_OK; +} + + +HRESULT CMultiStreams::EnsureOpen(unsigned index) +{ + CMultiStreams::CSubStream &s = Streams[index]; + if (s.Stream) + { + if ((int)index != Head) + { + RemoveFromList(s); + InsertToList(index); + } + } + else + { + RINOK(PrepareToOpenNew()) + { + CInFileStream *inFile = new CInFileStream; + CMyComPtr inStreamTemp = inFile; + if (!inFile->Open(s.Path)) + return GetLastError_noZero_HRESULT(); + s.FileSpec = inFile; + s.Stream = s.FileSpec; + InsertToList(index); + } + // s.IsOpen = true; + if (s.LocalPos != 0) + { + RINOK(s.Stream->Seek((Int64)s.LocalPos, STREAM_SEEK_SET, &s.LocalPos)) + } + #ifdef DEBUG_VOLUMES + static int numOpens = 0; + numOpens++; + printf("\n-- %u, ReOpen, total_reopens = %u, num_open_files = %u\n", index, numOpens, NumListItems); + #endif + } + return S_OK; +} + + +Z7_COM7F_IMF(CInFileStreamVol::Read(void *data, UInt32 size, UInt32 *processedSize)) +{ + if (processedSize) + *processedSize = 0; + if (size == 0) + return S_OK; + RINOK(EnsureOpen()) + CMultiStreams::CSubStream &s = OpenCallbackImp->Volumes.Streams[FileIndex]; + PRF(printf("\n== %u, Read =%u \n", FileIndex, size)); + return s.Stream->Read(data, size, processedSize); +} + +Z7_COM7F_IMF(CInFileStreamVol::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition)) +{ + // if (seekOrigin >= 3) return STG_E_INVALIDFUNCTION; + RINOK(EnsureOpen()) + CMultiStreams::CSubStream &s = OpenCallbackImp->Volumes.Streams[FileIndex]; + PRF(printf("\n-- %u, Seek seekOrigin=%u Seek =%u\n", FileIndex, seekOrigin, (unsigned)offset)); + return s.Stream->Seek(offset, seekOrigin, newPosition); +} + +Z7_COM7F_IMF(CInFileStreamVol::GetSize(UInt64 *size)) +{ + RINOK(EnsureOpen()) + CMultiStreams::CSubStream &s = OpenCallbackImp->Volumes.Streams[FileIndex]; + return s.FileSpec->GetSize(size); +} + + +// from ArchiveExtractCallback.cpp +bool IsSafePath(const UString &path); + +Z7_COM7F_IMF(COpenCallbackImp::GetStream(const wchar_t *name, IInStream **inStream)) +{ + COM_TRY_BEGIN + *inStream = NULL; + + if (_subArchiveMode) + return S_FALSE; + if (Callback) + { + RINOK(Callback->Open_CheckBreak()) + } + + UString name2 = name; + + + #ifndef Z7_SFX + + #ifdef _WIN32 + name2.Replace(L'/', WCHAR_PATH_SEPARATOR); + #endif + + // if (!allowAbsVolPaths) + if (!IsSafePath(name2)) + return S_FALSE; + + #ifdef _WIN32 + /* WIN32 allows wildcards in Find() function + and doesn't allow wildcard in File.Open() + so we can work without the following wildcard check here */ + if (name2.Find(L'*') >= 0) + return S_FALSE; + { + unsigned startPos = 0; + if (name2.IsPrefixedBy_Ascii_NoCase("\\\\?\\")) + startPos = 3; + if (name2.Find(L'?', startPos) >= 0) + return S_FALSE; + } + #endif + + #endif + + + FString fullPath; + if (!NFile::NName::GetFullPath(_folderPrefix, us2fs(name2), fullPath)) + return S_FALSE; + if (!_fileInfo.Find_FollowLink(fullPath)) + return S_FALSE; + if (_fileInfo.IsDir()) + return S_FALSE; + + CMultiStreams::CSubStream s; + + { + CInFileStream *inFile = new CInFileStream; + CMyComPtr inStreamTemp = inFile; + if (!inFile->Open(fullPath)) + return GetLastError_noZero_HRESULT(); + RINOK(Volumes.PrepareToOpenNew()) + s.FileSpec = inFile; + s.Stream = s.FileSpec; + s.Path = fullPath; + // s.Size = _fileInfo.Size; + // s.IsOpen = true; + } + + const unsigned fileIndex = Volumes.Streams.Add(s); + Volumes.InsertToList(fileIndex); + + FileSizes.Add(_fileInfo.Size); + FileNames.Add(name2); + FileNames_WasUsed.Add(true); + + CInFileStreamVol *inFile = new CInFileStreamVol; + CMyComPtr inStreamTemp = inFile; + inFile->FileIndex = fileIndex; + inFile->OpenCallbackImp = this; + inFile->OpenCallbackRef = this; + // TotalSize += _fileInfo.Size; + *inStream = inStreamTemp.Detach(); + return S_OK; + COM_TRY_END +} + + +#ifndef Z7_NO_CRYPTO +Z7_COM7F_IMF(COpenCallbackImp::CryptoGetTextPassword(BSTR *password)) +{ + COM_TRY_BEGIN + if (ReOpenCallback) + { + Z7_DECL_CMyComPtr_QI_FROM( + ICryptoGetTextPassword, + getTextPassword, ReOpenCallback) + if (getTextPassword) + return getTextPassword->CryptoGetTextPassword(password); + } + if (!Callback) + return E_NOTIMPL; + PasswordWasAsked = true; + return Callback->Open_CryptoGetTextPassword(password); + COM_TRY_END +} +#endif + +// IProgress +Z7_COM7F_IMF(COpenCallbackImp::SetTotal(const UInt64 /* total */)) +{ + return S_OK; +} + +Z7_COM7F_IMF(COpenCallbackImp::SetCompleted(const UInt64 * /* completed */)) +{ + return S_OK; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/ArchiveOpenCallback.h b/src/plugins/7zip/7za/cpp/7zip/UI/Common/ArchiveOpenCallback.h new file mode 100644 index 00000000..a7b8b761 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/ArchiveOpenCallback.h @@ -0,0 +1,182 @@ +// ArchiveOpenCallback.h + +#ifndef ZIP7_INC_ARCHIVE_OPEN_CALLBACK_H +#define ZIP7_INC_ARCHIVE_OPEN_CALLBACK_H + +#include "../../../Common/MyCom.h" + +#include "../../../Windows/FileFind.h" + +#include "../../Common/FileStreams.h" + +#ifndef Z7_NO_CRYPTO +#include "../../IPassword.h" +#endif +#include "../../Archive/IArchive.h" + +Z7_PURE_INTERFACES_BEGIN + +#ifdef Z7_NO_CRYPTO + +#define Z7_IFACEM_IOpenCallbackUI_Crypto(x) + +#else + +#define Z7_IFACEM_IOpenCallbackUI_Crypto(x) \ + virtual HRESULT Open_CryptoGetTextPassword(BSTR *password) x \ + /* virtual HRESULT Open_GetPasswordIfAny(bool &passwordIsDefined, UString &password) x */ \ + /* virtual bool Open_WasPasswordAsked() x */ \ + /* virtual void Open_Clear_PasswordWasAsked_Flag() x */ \ + +#endif + +#define Z7_IFACEN_IOpenCallbackUI(x) \ + virtual HRESULT Open_CheckBreak() x \ + virtual HRESULT Open_SetTotal(const UInt64 *files, const UInt64 *bytes) x \ + virtual HRESULT Open_SetCompleted(const UInt64 *files, const UInt64 *bytes) x \ + virtual HRESULT Open_Finished() x \ + Z7_IFACEM_IOpenCallbackUI_Crypto(x) + +Z7_IFACE_DECL_PURE(IOpenCallbackUI) + +Z7_PURE_INTERFACES_END + + +class CMultiStreams Z7_final +{ +public: + struct CSubStream + { + CMyComPtr Stream; + CInFileStream *FileSpec; + FString Path; + // UInt64 Size; + UInt64 LocalPos; + int Next; // next older + int Prev; // prev newer + // bool IsOpen; + + CSubStream(): + FileSpec(NULL), + // Size(0), + LocalPos(0), + Next(-1), + Prev(-1) + // IsOpen(false) + {} + }; + + CObjectVector Streams; +private: + // we must use critical section here, if we want to access from different volumnes simultaneously + int Head; // newest + int Tail; // oldest + unsigned NumListItems; + unsigned NumOpenFiles_AllowedMax; +public: + + CMultiStreams(); + void Init(); + HRESULT PrepareToOpenNew(); + void InsertToList(unsigned index); + void RemoveFromList(CSubStream &s); + void CloseFile(unsigned index); + HRESULT EnsureOpen(unsigned index); +}; + + +/* + We need COpenCallbackImp class for multivolume processing. + Also we use it as proxy from COM interfaces (IArchiveOpenCallback) to internal (IOpenCallbackUI) interfaces. + If archive is multivolume: + COpenCallbackImp object will exist after Open stage. + COpenCallbackImp object will be deleted when last reference + from each volume object (CInFileStreamVol) will be closed (when archive will be closed). +*/ + +class COpenCallbackImp Z7_final: + public IArchiveOpenCallback, + public IArchiveOpenVolumeCallback, + public IArchiveOpenSetSubArchiveName, + #ifndef Z7_NO_CRYPTO + public ICryptoGetTextPassword, + #endif + public IProgress, // IProgress is used for 7zFM + public CMyUnknownImp +{ + Z7_COM_QI_BEGIN2(IArchiveOpenCallback) + Z7_COM_QI_ENTRY(IArchiveOpenVolumeCallback) + Z7_COM_QI_ENTRY(IArchiveOpenSetSubArchiveName) + #ifndef Z7_NO_CRYPTO + Z7_COM_QI_ENTRY(ICryptoGetTextPassword) + #endif + // Z7_COM_QI_ENTRY(IProgress) // the code doesn't require it + Z7_COM_QI_END + Z7_COM_ADDREF_RELEASE + + Z7_IFACE_COM7_IMP(IArchiveOpenCallback) + Z7_IFACE_COM7_IMP(IArchiveOpenVolumeCallback) + Z7_IFACE_COM7_IMP(IProgress) +public: + Z7_IFACE_COM7_IMP(IArchiveOpenSetSubArchiveName) +private: + #ifndef Z7_NO_CRYPTO + Z7_IFACE_COM7_IMP(ICryptoGetTextPassword) + #endif + + bool _subArchiveMode; + +public: + bool PasswordWasAsked; + UStringVector FileNames; + CBoolVector FileNames_WasUsed; + CRecordVector FileSizes; + + void AtCloseFile(unsigned fileIndex) + { + FileNames_WasUsed[fileIndex] = false; + Volumes.CloseFile(fileIndex); + } + + /* we have two ways to Callback from this object + 1) IArchiveOpenCallback * ReOpenCallback - for ReOpen function, when IOpenCallbackUI is not available + 2) IOpenCallbackUI *Callback - for usual callback + we can't transfer IOpenCallbackUI pointer via internal interface, + so we use ReOpenCallback to callback without IOpenCallbackUI. + */ + + /* we use Callback/ReOpenCallback only at Open stage. + So the CMyComPtr reference counter is not required, + and we don't want additional reference to unused object, + if COpenCallbackImp is not closed + */ + IArchiveOpenCallback *ReOpenCallback; + // CMyComPtr ReOpenCallback; + IOpenCallbackUI *Callback; + // CMyComPtr Callback_Ref; + +private: + FString _folderPrefix; + UString _subArchiveName; + NWindows::NFile::NFind::CFileInfo _fileInfo; + +public: + CMultiStreams Volumes; + + // UInt64 TotalSize; + + COpenCallbackImp(): + _subArchiveMode(false), + PasswordWasAsked(false), + ReOpenCallback(NULL), + Callback(NULL) {} + + HRESULT Init2(const FString &folderPrefix, const FString &fileName); + + bool SetSecondFileInfo(CFSTR newName) + { + return _fileInfo.Find_FollowLink(newName) && !_fileInfo.IsDir(); + } +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/Bench.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Common/Bench.cpp new file mode 100644 index 00000000..30fc1e6f --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/Bench.cpp @@ -0,0 +1,5031 @@ +// Bench.cpp + +#include "StdAfx.h" + +// #include + +#ifndef _WIN32 +#define USE_POSIX_TIME +#define USE_POSIX_TIME2 +#endif // _WIN32 + +#ifdef USE_POSIX_TIME +#include +#include +#ifdef USE_POSIX_TIME2 +#include +#include +#endif +#endif // USE_POSIX_TIME + +#ifdef _WIN32 +#define USE_ALLOCA +#endif + +#ifdef USE_ALLOCA +#ifdef _WIN32 +#include +#else +#include +#endif +#define BENCH_ALLOCA_VALUE(index) (((index) * 64 * 21) & 0x7FF) +#endif + +#include "../../../../C/7zCrc.h" +#include "../../../../C/RotateDefs.h" +#include "../../../../C/CpuArch.h" + +#ifndef Z7_ST +#include "../../../Windows/Synchronization.h" +#include "../../../Windows/Thread.h" +#endif + +#include "../../../Windows/FileFind.h" +#include "../../../Windows/FileIO.h" +#include "../../../Windows/SystemInfo.h" + +#include "../../../Common/MyBuffer2.h" +#include "../../../Common/IntToString.h" +#include "../../../Common/StringConvert.h" +#include "../../../Common/StringToInt.h" +#include "../../../Common/Wildcard.h" + +#include "../../Common/MethodProps.h" +#include "../../Common/StreamObjects.h" +#include "../../Common/StreamUtils.h" + +#include "Bench.h" + +using namespace NWindows; + +#ifndef Z7_ST +static const UInt32 k_LZMA = 0x030101; +#endif + +static const UInt64 kComplexInCommands = (UInt64)1 << + #ifdef UNDER_CE + 31; + #else + 34; + #endif + +static const UInt32 kComplexInMs = 4000; + +static void SetComplexCommandsMs(UInt32 complexInMs, + bool isSpecifiedFreq, UInt64 cpuFreq, UInt64 &complexInCommands) +{ + complexInCommands = kComplexInCommands; + const UInt64 kMinFreq = (UInt64)1000000 * 4; + const UInt64 kMaxFreq = (UInt64)1000000 * 20000; + if (cpuFreq < kMinFreq && !isSpecifiedFreq) + cpuFreq = kMinFreq; + if (cpuFreq < kMaxFreq || isSpecifiedFreq) + { + if (complexInMs != 0) + complexInCommands = complexInMs * cpuFreq / 1000; + else + complexInCommands = cpuFreq >> 2; + } +} + +// const UInt64 kBenchmarkUsageMult = 1000000; // for debug +static const unsigned kBenchmarkUsageMultBits = 16; +static const UInt64 kBenchmarkUsageMult = 1 << kBenchmarkUsageMultBits; + +UInt64 Benchmark_GetUsage_Percents(UInt64 usage) +{ + return (100 * usage + kBenchmarkUsageMult / 2) / kBenchmarkUsageMult; +} + +static const unsigned kNumHashDictBits = 17; +static const UInt32 kFilterUnpackSize = (47 << 10); // + 5; // for test + +static const unsigned kOldLzmaDictBits = 32; + +// static const size_t kAdditionalSize = (size_t)1 << 32; // for debug +static const size_t kAdditionalSize = (size_t)1 << 16; +static const size_t kCompressedAdditionalSize = 1 << 10; + +static const UInt32 kMaxMethodPropSize = 1 << 6; + + +#define ALLOC_WITH_HRESULT(_buffer_, _size_) \ + { (_buffer_)->Alloc(_size_); \ + if (_size_ && !(_buffer_)->IsAllocated()) return E_OUTOFMEMORY; } + + +class CBaseRandomGenerator +{ + UInt32 A1; + UInt32 A2; + UInt32 Salt; +public: + CBaseRandomGenerator(UInt32 salt = 0): Salt(salt) { Init(); } + void Init() { A1 = 362436069; A2 = 521288629;} + Z7_FORCE_INLINE + UInt32 GetRnd() + { +#if 0 + // for debug: + return 0x0c080400; + // return 0; +#else + return Salt ^ + ( + ((A1 = 36969 * (A1 & 0xffff) + (A1 >> 16)) << 16) + + ((A2 = 18000 * (A2 & 0xffff) + (A2 >> 16)) ) + ); +#endif + } +}; + + +static const size_t k_RandBuf_AlignMask = 4 - 1; + +Z7_NO_INLINE +static void RandGen_BufAfterPad(Byte *buf, size_t size) +{ + CBaseRandomGenerator RG; + for (size_t i = 0; i < size; i += 4) + { + const UInt32 v = RG.GetRnd(); + SetUi32a(buf + i, v) + } + /* + UInt32 v = RG.GetRnd(); + for (; i < size; i++) + { + buf[i] = (Byte)v; + v >>= 8; + } + */ +} + + +class CBenchRandomGenerator: public CMidAlignedBuffer +{ + static UInt32 GetVal(UInt32 &res, unsigned numBits) + { + const UInt32 val = res & (((UInt32)1 << numBits) - 1); + res >>= numBits; + return val; + } + + static UInt32 GetLen(UInt32 &r) + { + const unsigned len = (unsigned)GetVal(r, 2); + return GetVal(r, 1 + len); + } + +public: + + void GenerateSimpleRandom(UInt32 salt) + { + CBaseRandomGenerator rg(salt); + const size_t bufSize = Size(); + Byte *buf = (Byte *)*this; + for (size_t i = 0; i < bufSize; i++) + buf[i] = (Byte)rg.GetRnd(); + } + + void GenerateLz(unsigned dictBits, UInt32 salt) + { + CBaseRandomGenerator rg(salt); + size_t pos = 0; + size_t rep0 = 1; + const size_t bufSize = Size(); + Byte *buf = (Byte *)*this; + unsigned posBits = 1; + + // printf("\n dictBits = %d\n", (UInt32)dictBits); + // printf("\n bufSize = 0x%p\n", (const void *)bufSize); + + while (pos < bufSize) + { + /* + if (pos >= ((UInt32)1 << 31)) + printf(" %x\n", pos); + */ + UInt32 r = rg.GetRnd(); + if (GetVal(r, 1) == 0 || pos < 1024) + buf[pos++] = (Byte)(r & 0xFF); + else + { + UInt32 len; + len = 1 + GetLen(r); + + if (GetVal(r, 3) != 0) + { + len += GetLen(r); + + while (((size_t)1 << posBits) < pos) + posBits++; + + unsigned numBitsMax = dictBits; + if (numBitsMax > posBits) + numBitsMax = posBits; + + const unsigned kAddBits = 6; + unsigned numLogBits = 5; + if (numBitsMax <= (1 << 4) - 1 + kAddBits) + numLogBits = 4; + + for (;;) + { + const UInt32 ppp = GetVal(r, numLogBits) + kAddBits; + r = rg.GetRnd(); + if (ppp > numBitsMax) + continue; + // rep0 = GetVal(r, ppp); + rep0 = r & (((size_t)1 << ppp) - 1); + if (rep0 < pos) + break; + r = rg.GetRnd(); + } + rep0++; + } + + // len *= 300; // for debug + { + const size_t rem = bufSize - pos; + if (len > rem) + len = (UInt32)rem; + } + Byte *dest = buf + pos; + const Byte *src = dest - rep0; + pos += len; + for (UInt32 i = 0; i < len; i++) + *dest++ = *src++; + } + } + // printf("\n CRC = %x\n", CrcCalc(buf, bufSize)); + } +}; + + +Z7_CLASS_IMP_NOQIB_1( + CBenchmarkInStream + , ISequentialInStream +) + const Byte *Data; + size_t Pos; + size_t Size; +public: + void Init(const Byte *data, size_t size) + { + Data = data; + Size = size; + Pos = 0; + } + bool WasFinished() const { return Pos == Size; } +}; + +Z7_COM7F_IMF(CBenchmarkInStream::Read(void *data, UInt32 size, UInt32 *processedSize)) +{ + const UInt32 kMaxBlockSize = (1 << 20); + if (size > kMaxBlockSize) + size = kMaxBlockSize; + const size_t remain = Size - Pos; + if (size > remain) + size = (UInt32)remain; + + if (size) + memcpy(data, Data + Pos, size); + + Pos += size; + if (processedSize) + *processedSize = size; + return S_OK; +} + + +class CBenchmarkOutStream Z7_final: + public ISequentialOutStream, + public CMyUnknownImp, + public CMidAlignedBuffer +{ + Z7_COM_UNKNOWN_IMP_0 + Z7_IFACE_COM7_IMP(ISequentialOutStream) + // bool _overflow; +public: + size_t Pos; + bool RealCopy; + bool CalcCrc; + UInt32 Crc; + + // CBenchmarkOutStream(): _overflow(false) {} + void Init(bool realCopy, bool calcCrc) + { + Crc = CRC_INIT_VAL; + RealCopy = realCopy; + CalcCrc = calcCrc; + // _overflow = false; + Pos = 0; + } + + void InitCrc() + { + Crc = CRC_INIT_VAL; + } + + void Calc(const void *data, size_t size) + { + Crc = CrcUpdate(Crc, data, size); + } + + size_t GetPos() const { return Pos; } + + // void Print() { printf("\n%8d %8d\n", (unsigned)BufferSize, (unsigned)Pos); } +}; + +Z7_COM7F_IMF(CBenchmarkOutStream::Write(const void *data, UInt32 size, UInt32 *processedSize)) +{ + size_t curSize = Size() - Pos; + if (curSize > size) + curSize = size; + if (curSize != 0) + { + if (RealCopy) + memcpy(((Byte *)*this) + Pos, data, curSize); + if (CalcCrc) + Calc(data, curSize); + Pos += curSize; + } + if (processedSize) + *processedSize = (UInt32)curSize; + if (curSize != size) + { + // _overflow = true; + return E_FAIL; + } + return S_OK; +} + + +Z7_CLASS_IMP_NOQIB_1( + CCrcOutStream + , ISequentialOutStream +) +public: + bool CalcCrc; + UInt32 Crc; + UInt64 Pos; + + CCrcOutStream(): CalcCrc(true) {} + void Init() { Crc = CRC_INIT_VAL; Pos = 0; } + void Calc(const void *data, size_t size) + { + Crc = CrcUpdate(Crc, data, size); + } +}; + +Z7_COM7F_IMF(CCrcOutStream::Write(const void *data, UInt32 size, UInt32 *processedSize)) +{ + if (CalcCrc) + Calc(data, size); + Pos += size; + if (processedSize) + *processedSize = size; + return S_OK; +} + +// #include "../../../../C/My_sys_time.h" + +static UInt64 GetTimeCount() +{ + #ifdef USE_POSIX_TIME + #ifdef USE_POSIX_TIME2 + timeval v; + if (gettimeofday(&v, NULL) == 0) + return (UInt64)(v.tv_sec) * 1000000 + (UInt64)v.tv_usec; + return (UInt64)time(NULL) * 1000000; + #else + return time(NULL); + #endif + #else + LARGE_INTEGER value; + if (::QueryPerformanceCounter(&value)) + return (UInt64)value.QuadPart; + return GetTickCount(); + #endif +} + +static UInt64 GetFreq() +{ + #ifdef USE_POSIX_TIME + #ifdef USE_POSIX_TIME2 + return 1000000; + #else + return 1; + #endif + #else + LARGE_INTEGER value; + if (::QueryPerformanceFrequency(&value)) + return (UInt64)value.QuadPart; + return 1000; + #endif +} + + +#ifdef USE_POSIX_TIME + +struct CUserTime +{ + UInt64 Sum; + clock_t Prev; + + void Init() + { + // Prev = clock(); + Sum = 0; + Prev = 0; + Update(); + Sum = 0; + } + + void Update() + { + tms t; + /* clock_t res = */ times(&t); + clock_t newVal = t.tms_utime + t.tms_stime; + Sum += (UInt64)(newVal - Prev); + Prev = newVal; + + /* + clock_t v = clock(); + if (v != -1) + { + Sum += v - Prev; + Prev = v; + } + */ + } + UInt64 GetUserTime() + { + Update(); + return Sum; + } +}; + +#else + + +struct CUserTime +{ + bool UseTick; + DWORD Prev_Tick; + UInt64 Prev; + UInt64 Sum; + + void Init() + { + UseTick = false; + Prev_Tick = 0; + Prev = 0; + Sum = 0; + Update(); + Sum = 0; + } + UInt64 GetUserTime() + { + Update(); + return Sum; + } + void Update(); +}; + +static inline UInt64 GetTime64(const FILETIME &t) { return ((UInt64)t.dwHighDateTime << 32) | t.dwLowDateTime; } + +void CUserTime::Update() +{ + DWORD new_Tick = GetTickCount(); + FILETIME creationTime, exitTime, kernelTime, userTime; + if (!UseTick && + #ifdef UNDER_CE + ::GetThreadTimes(::GetCurrentThread() + #else + ::GetProcessTimes(::GetCurrentProcess() + #endif + , &creationTime, &exitTime, &kernelTime, &userTime)) + { + UInt64 newVal = GetTime64(userTime) + GetTime64(kernelTime); + Sum += newVal - Prev; + Prev = newVal; + } + else + { + UseTick = true; + Sum += (UInt64)(new_Tick - (DWORD)Prev_Tick) * 10000; + } + Prev_Tick = new_Tick; +} + + +#endif + +static UInt64 GetUserFreq() +{ + #ifdef USE_POSIX_TIME + // return CLOCKS_PER_SEC; + return (UInt64)sysconf(_SC_CLK_TCK); + #else + return 10000000; + #endif +} + +class CBenchProgressStatus Z7_final +{ + #ifndef Z7_ST + NSynchronization::CCriticalSection CS; + #endif +public: + HRESULT Res; + bool EncodeMode; + void SetResult(HRESULT res) + { + #ifndef Z7_ST + NSynchronization::CCriticalSectionLock lock(CS); + #endif + Res = res; + } + HRESULT GetResult() + { + #ifndef Z7_ST + NSynchronization::CCriticalSectionLock lock(CS); + #endif + return Res; + } +}; + +struct CBenchInfoCalc +{ + CBenchInfo BenchInfo; + CUserTime UserTime; + + void SetStartTime(); + void SetFinishTime(CBenchInfo &dest); +}; + +void CBenchInfoCalc::SetStartTime() +{ + BenchInfo.GlobalFreq = GetFreq(); + BenchInfo.UserFreq = GetUserFreq(); + BenchInfo.GlobalTime = ::GetTimeCount(); + BenchInfo.UserTime = 0; + UserTime.Init(); +} + +void CBenchInfoCalc::SetFinishTime(CBenchInfo &dest) +{ + dest = BenchInfo; + dest.GlobalTime = ::GetTimeCount() - BenchInfo.GlobalTime; + dest.UserTime = UserTime.GetUserTime(); +} + +class CBenchProgressInfo Z7_final: + public ICompressProgressInfo, + public CMyUnknownImp, + public CBenchInfoCalc +{ + Z7_COM_UNKNOWN_IMP_0 + Z7_IFACE_COM7_IMP(ICompressProgressInfo) +public: + CBenchProgressStatus *Status; + IBenchCallback *Callback; + + CBenchProgressInfo(): Callback(NULL) {} +}; + + +Z7_COM7F_IMF(CBenchProgressInfo::SetRatioInfo(const UInt64 *inSize, const UInt64 *outSize)) +{ + HRESULT res = Status->GetResult(); + if (res != S_OK) + return res; + if (!Callback) + return res; + + /* + static UInt64 inSizePrev = 0; + static UInt64 outSizePrev = 0; + UInt64 delta1 = 0, delta2 = 0, val1 = 0, val2 = 0; + if (inSize) { val1 = *inSize; delta1 = val1 - inSizePrev; inSizePrev = val1; } + if (outSize) { val2 = *outSize; delta2 = val2 - outSizePrev; outSizePrev = val2; } + UInt64 percents = delta2 * 1000; + if (delta1 != 0) + percents /= delta1; + printf("=== %7d %7d %7d %7d ratio = %4d\n", + (unsigned)(val1 >> 10), (unsigned)(delta1 >> 10), + (unsigned)(val2 >> 10), (unsigned)(delta2 >> 10), + (unsigned)percents); + */ + + CBenchInfo info; + SetFinishTime(info); + if (Status->EncodeMode) + { + info.UnpackSize = BenchInfo.UnpackSize + *inSize; + info.PackSize = BenchInfo.PackSize + *outSize; + res = Callback->SetEncodeResult(info, false); + } + else + { + info.PackSize = BenchInfo.PackSize + *inSize; + info.UnpackSize = BenchInfo.UnpackSize + *outSize; + res = Callback->SetDecodeResult(info, false); + } + if (res != S_OK) + Status->SetResult(res); + return res; +} + +static const unsigned kSubBits = 8; + +static unsigned GetLogSize(UInt64 size) +{ + unsigned i = 0; + for (;;) + { + i++; size >>= 1; if (size == 0) break; + } + return i; +} + + +static UInt32 GetLogSize_Sub(UInt64 size) +{ + if (size <= 1) + return 0; + const unsigned i = GetLogSize(size) - 1; + UInt32 v; + if (i <= kSubBits) + v = (UInt32)(size) << (kSubBits - i); + else + v = (UInt32)(size >> (i - kSubBits)); + return ((UInt32)i << kSubBits) + (v & (((UInt32)1 << kSubBits) - 1)); +} + + +static UInt64 Get_UInt64_from_double(double v) +{ + const UInt64 kMaxVal = (UInt64)1 << 62; + if (v > (double)(Int64)kMaxVal) + return kMaxVal; + return (UInt64)v; +} + +static UInt64 MyMultDiv64(UInt64 m1, UInt64 m2, UInt64 d) +{ + if (d == 0) + d = 1; + const double v = + (double)(Int64)m1 * + (double)(Int64)m2 / + (double)(Int64)d; + return Get_UInt64_from_double(v); + /* + unsigned n1 = GetLogSize(m1); + unsigned n2 = GetLogSize(m2); + while (n1 + n2 > 64) + { + if (n1 >= n2) + { + m1 >>= 1; + n1--; + } + else + { + m2 >>= 1; + n2--; + } + d >>= 1; + } + + if (d == 0) + d = 1; + return m1 * m2 / d; + */ +} + + +UInt64 CBenchInfo::GetUsage() const +{ + UInt64 userTime = UserTime; + UInt64 userFreq = UserFreq; + UInt64 globalTime = GlobalTime; + UInt64 globalFreq = GlobalFreq; + + if (userFreq == 0) + userFreq = 1; + if (globalTime == 0) + globalTime = 1; + + const double v = + ((double)(Int64)userTime / (double)(Int64)userFreq) + * ((double)(Int64)globalFreq / (double)(Int64)globalTime) + * (double)(Int64)kBenchmarkUsageMult; + return Get_UInt64_from_double(v); + /* + return MyMultDiv64( + MyMultDiv64(kBenchmarkUsageMult, userTime, userFreq), + globalFreq, globalTime); + */ +} + + +UInt64 CBenchInfo::GetRatingPerUsage(UInt64 rating) const +{ + if (UserTime == 0) + { + return 0; + // userTime = 1; + } + UInt64 globalFreq = GlobalFreq; + if (globalFreq == 0) + globalFreq = 1; + + const double v = + ((double)(Int64)GlobalTime / (double)(Int64)globalFreq) + * ((double)(Int64)UserFreq / (double)(Int64)UserTime) + * (double)(Int64)rating; + return Get_UInt64_from_double(v); + /* + return MyMultDiv64( + MyMultDiv64(rating, UserFreq, UserTime), + GlobalTime, globalFreq); + */ +} + + +UInt64 CBenchInfo::GetSpeed(UInt64 numUnits) const +{ + return MyMultDiv64(numUnits, GlobalFreq, GlobalTime); +} + +static UInt64 GetNumCommands_from_Size_and_Complexity(UInt64 size, Int32 complexity) +{ + return complexity >= 0 ? + size * (UInt32)complexity : + size / (UInt32)(-complexity); +} + +struct CBenchProps +{ + bool LzmaRatingMode; + + Int32 EncComplex; + Int32 DecComplexCompr; + Int32 DecComplexUnc; + + unsigned KeySize; + + CBenchProps(): + LzmaRatingMode(false), + KeySize(0) + {} + + void SetLzmaCompexity(); + + UInt64 GetNumCommands_Enc(UInt64 unpackSize) const + { + const UInt32 kMinSize = 100; + if (unpackSize < kMinSize) + unpackSize = kMinSize; + return GetNumCommands_from_Size_and_Complexity(unpackSize, EncComplex); + } + + UInt64 GetNumCommands_Dec(UInt64 packSize, UInt64 unpackSize) const + { + return + GetNumCommands_from_Size_and_Complexity(packSize, DecComplexCompr) + + GetNumCommands_from_Size_and_Complexity(unpackSize, DecComplexUnc); + } + + UInt64 GetRating_Enc(UInt64 dictSize, UInt64 elapsedTime, UInt64 freq, UInt64 size) const; + UInt64 GetRating_Dec(UInt64 elapsedTime, UInt64 freq, UInt64 outSize, UInt64 inSize, UInt64 numIterations) const; +}; + +void CBenchProps::SetLzmaCompexity() +{ + EncComplex = 1200; + DecComplexUnc = 4; + DecComplexCompr = 190; + LzmaRatingMode = true; +} + +UInt64 CBenchProps::GetRating_Enc(UInt64 dictSize, UInt64 elapsedTime, UInt64 freq, UInt64 size) const +{ + if (dictSize < (1 << kBenchMinDicLogSize)) + dictSize = (1 << kBenchMinDicLogSize); + Int32 encComplex = EncComplex; + if (LzmaRatingMode) + { + /* + for (UInt64 uu = 0; uu < (UInt64)0xf << 60;) + { + unsigned rr = GetLogSize_Sub(uu); + printf("\n%16I64x , log = %4x", uu, rr); + uu += 1; + uu += uu / 50; + } + */ + // throw 1; + const UInt32 t = GetLogSize_Sub(dictSize) - (kBenchMinDicLogSize << kSubBits); + encComplex = 870 + ((t * t * 5) >> (2 * kSubBits)); + } + const UInt64 numCommands = GetNumCommands_from_Size_and_Complexity(size, encComplex); + return MyMultDiv64(numCommands, freq, elapsedTime); +} + +UInt64 CBenchProps::GetRating_Dec(UInt64 elapsedTime, UInt64 freq, UInt64 outSize, UInt64 inSize, UInt64 numIterations) const +{ + const UInt64 numCommands = GetNumCommands_Dec(inSize, outSize) * numIterations; + return MyMultDiv64(numCommands, freq, elapsedTime); +} + + + +UInt64 CBenchInfo::GetRating_LzmaEnc(UInt64 dictSize) const +{ + CBenchProps props; + props.SetLzmaCompexity(); + return props.GetRating_Enc(dictSize, GlobalTime, GlobalFreq, UnpackSize * NumIterations); +} + +UInt64 CBenchInfo::GetRating_LzmaDec() const +{ + CBenchProps props; + props.SetLzmaCompexity(); + return props.GetRating_Dec(GlobalTime, GlobalFreq, UnpackSize, PackSize, NumIterations); +} + + +#ifndef Z7_ST + +#define NUM_CPU_LEVELS_MAX 3 + +struct CAffinityMode +{ + unsigned NumBundleThreads; + unsigned NumLevels; + unsigned NumCoreThreads; + unsigned NumCores; + // unsigned DivideNum; + +#ifdef _WIN32 + unsigned NumGroups; +#endif + + UInt32 Sizes[NUM_CPU_LEVELS_MAX]; + + void SetLevels(unsigned numCores, unsigned numCoreThreads); + DWORD_PTR GetAffinityMask(UInt32 bundleIndex, CCpuSet *cpuSet) const; + bool NeedAffinity() const { return NumBundleThreads != 0; } + +#ifdef _WIN32 + bool NeedGroupsMode() const { return NumGroups > 1; } +#endif + + WRes CreateThread_WithAffinity(NWindows::CThread &thread, THREAD_FUNC_TYPE startAddress, LPVOID parameter, UInt32 bundleIndex) const + { +#ifdef _WIN32 + if (NeedGroupsMode()) // we need fix for bundleIndex usage + return thread.Create_With_Group(startAddress, parameter, bundleIndex % NumGroups); +#endif + if (NeedAffinity()) + { + CCpuSet cpuSet; + GetAffinityMask(bundleIndex, &cpuSet); + return thread.Create_With_CpuSet(startAddress, parameter, &cpuSet); + } + return thread.Create(startAddress, parameter); + } + + CAffinityMode(): + NumBundleThreads(0), + NumLevels(0), + NumCoreThreads(1) +#ifdef _WIN32 + , NumGroups(0) +#endif + // DivideNum(1) + {} +}; + +void CAffinityMode::SetLevels(unsigned numCores, unsigned numCoreThreads) +{ + NumCores = numCores; + NumCoreThreads = numCoreThreads; + NumLevels = 0; + if (numCoreThreads == 0 || numCores == 0 || numCores % numCoreThreads != 0) + return; + UInt32 c = numCores / numCoreThreads; + UInt32 c2 = 1; + while ((c & 1) == 0) + { + c >>= 1; + c2 <<= 1; + } + if (c2 != 1) + Sizes[NumLevels++] = c2; + if (c != 1) + Sizes[NumLevels++] = c; + if (numCoreThreads != 1) + Sizes[NumLevels++] = numCoreThreads; + if (NumLevels == 0) + Sizes[NumLevels++] = 1; + + /* + printf("\n Cores:"); + for (unsigned i = 0; i < NumLevels; i++) + { + printf(" %d", Sizes[i]); + } + printf("\n"); + */ +} + + +DWORD_PTR CAffinityMode::GetAffinityMask(UInt32 bundleIndex, CCpuSet *cpuSet) const +{ + CpuSet_Zero(cpuSet); + + if (NumLevels == 0) + return 0; + + // printf("\n%2d", bundleIndex); + + /* + UInt32 low = 0; + if (DivideNum != 1) + { + low = bundleIndex % DivideNum; + bundleIndex /= DivideNum; + } + */ + + UInt32 numGroups = NumCores / NumBundleThreads; + UInt32 m = bundleIndex % numGroups; + UInt32 v = 0; + for (unsigned i = 0; i < NumLevels; i++) + { + UInt32 size = Sizes[i]; + while ((size & 1) == 0) + { + v *= 2; + v |= (m & 1); + m >>= 1; + size >>= 1; + } + v *= size; + v += m % size; + m /= size; + } + + // UInt32 nb = NumBundleThreads / DivideNum; + UInt32 nb = NumBundleThreads; + + DWORD_PTR mask = ((DWORD_PTR)1 << nb) - 1; + // v += low; + mask <<= v; + + // printf(" %2d %8x \n ", v, (unsigned)mask); + #ifdef _WIN32 + *cpuSet = mask; + #else + { + for (unsigned k = 0; k < nb; k++) + CpuSet_Set(cpuSet, v + k); + } + #endif + + return mask; +} + + +struct CBenchSyncCommon +{ + bool ExitMode; + NSynchronization::CManualResetEvent StartEvent; + + CBenchSyncCommon(): ExitMode(false) {} +}; + +#endif + + + +enum E_CheckCrcMode +{ + k_CheckCrcMode_Never = 0, + k_CheckCrcMode_Always = 1, + k_CheckCrcMode_FirstPass = 2 +}; + +class CEncoderInfo; + +class CEncoderInfo Z7_final +{ + Z7_CLASS_NO_COPY(CEncoderInfo) + +public: + + #ifndef Z7_ST + NWindows::CThread thread[2]; + NSynchronization::CManualResetEvent ReadyEvent; + UInt32 NumDecoderSubThreads; + CBenchSyncCommon *Common; + UInt32 EncoderIndex; + UInt32 NumEncoderInternalThreads; + CAffinityMode AffinityMode; + bool IsGlobalMtMode; // if more than one benchmark encoder threads + #endif + + CMyComPtr _encoder; + CMyComPtr _encoderFilter; + CBenchProgressInfo *progressInfoSpec[2]; + CMyComPtr progressInfo[2]; + UInt64 NumIterations; + + UInt32 Salt; + + #ifdef USE_ALLOCA + size_t AllocaSize; + #endif + + unsigned KeySize; + Byte _key[32]; + Byte _iv[16]; + + HRESULT Set_Key_and_IV(ICryptoProperties *cp) + { + RINOK(cp->SetKey(_key, KeySize)) + return cp->SetInitVector(_iv, sizeof(_iv)); + } + + Byte _psw[16]; + + bool CheckCrc_Enc; /* = 1, if we want to check packed data crcs after each pass + used for filter and usual coders */ + bool UseRealData_Enc; /* = 1, if we want to use only original data for each pass + used only for filter */ + E_CheckCrcMode CheckCrcMode_Dec; + + struct CDecoderInfo + { + CEncoderInfo *Encoder; + UInt32 DecoderIndex; + bool CallbackMode; + + #ifdef USE_ALLOCA + size_t AllocaSize; + #endif + }; + CDecoderInfo decodersInfo[2]; + + CMyComPtr _decoders[2]; + CMyComPtr _decoderFilter; + + HRESULT Results[2]; + CBenchmarkOutStream *outStreamSpec; + CMyComPtr outStream; + IBenchCallback *callback; + IBenchPrintCallback *printCallback; + UInt32 crc; + size_t kBufferSize; + size_t compressedSize; + const Byte *uncompressedDataPtr; + + const Byte *fileData; + CBenchRandomGenerator rg; + + CMidAlignedBuffer rgCopy; // it must be 16-byte aligned !!! + + // CBenchmarkOutStream *propStreamSpec; + Byte propsData[kMaxMethodPropSize]; + CBufPtrSeqOutStream *propStreamSpec; + CMyComPtr propStream; + + unsigned generateDictBits; + COneMethodInfo _method; + + // for decode + size_t _uncompressedDataSize; + + HRESULT Generate(); + HRESULT Encode(); + HRESULT Decode(UInt32 decoderIndex); + + CEncoderInfo(): + #ifndef Z7_ST + Common(NULL), + IsGlobalMtMode(true), + #endif + Salt(0), + KeySize(0), + CheckCrc_Enc(true), + UseRealData_Enc(true), + CheckCrcMode_Dec(k_CheckCrcMode_Always), + outStreamSpec(NULL), + callback(NULL), + printCallback(NULL), + fileData(NULL), + propStreamSpec(NULL) + {} + + #ifndef Z7_ST + + static THREAD_FUNC_DECL EncodeThreadFunction(void *param) + { + HRESULT res; + CEncoderInfo *encoder = (CEncoderInfo *)param; + try + { + #ifdef USE_ALLOCA + alloca(encoder->AllocaSize); + #endif + + res = encoder->Encode(); + } + catch(...) + { + res = E_FAIL; + } + encoder->Results[0] = res; + if (res != S_OK) + encoder->progressInfoSpec[0]->Status->SetResult(res); + encoder->ReadyEvent.Set(); + return THREAD_FUNC_RET_ZERO; + } + + static THREAD_FUNC_DECL DecodeThreadFunction(void *param) + { + CDecoderInfo *decoder = (CDecoderInfo *)param; + + #ifdef USE_ALLOCA + alloca(decoder->AllocaSize); + // printf("\nalloca=%d\n", (unsigned)decoder->AllocaSize); + #endif + + CEncoderInfo *encoder = decoder->Encoder; + encoder->Results[decoder->DecoderIndex] = encoder->Decode(decoder->DecoderIndex); + return THREAD_FUNC_RET_ZERO; + } + + HRESULT CreateEncoderThread() + { + WRes res = 0; + if (!ReadyEvent.IsCreated()) + res = ReadyEvent.Create(); + if (res == 0) + res = AffinityMode.CreateThread_WithAffinity(thread[0], EncodeThreadFunction, this, + EncoderIndex); + return HRESULT_FROM_WIN32(res); + } + + HRESULT CreateDecoderThread(unsigned index, bool callbackMode + #ifdef USE_ALLOCA + , size_t allocaSize + #endif + ) + { + CDecoderInfo &decoder = decodersInfo[index]; + decoder.DecoderIndex = index; + decoder.Encoder = this; + + #ifdef USE_ALLOCA + decoder.AllocaSize = allocaSize; + #endif + + decoder.CallbackMode = callbackMode; + + WRes res = AffinityMode.CreateThread_WithAffinity(thread[index], DecodeThreadFunction, &decoder, + // EncoderIndex * NumEncoderInternalThreads + index + EncoderIndex + ); + + return HRESULT_FROM_WIN32(res); + } + + #endif +}; + + + + +static size_t GetBenchCompressedSize(size_t bufferSize) +{ + return kCompressedAdditionalSize + bufferSize + bufferSize / 16; + // kBufferSize / 2; +} + + +HRESULT CEncoderInfo::Generate() +{ + const COneMethodInfo &method = _method; + + // we need extra space, if input data is already compressed + const size_t kCompressedBufferSize = _encoderFilter ? + kBufferSize : + GetBenchCompressedSize(kBufferSize); + + if (kCompressedBufferSize < kBufferSize) + return E_FAIL; + + uncompressedDataPtr = fileData; + if (fileData) + { + #if !defined(Z7_ST) + if (IsGlobalMtMode) + { + /* we copy the data to local buffer of thread to eliminate + using of shared buffer by different threads */ + ALLOC_WITH_HRESULT(&rg, kBufferSize) + memcpy((Byte *)rg, fileData, kBufferSize); + uncompressedDataPtr = (const Byte *)rg; + } + #endif + } + else + { + ALLOC_WITH_HRESULT(&rg, kBufferSize) + // DWORD ttt = GetTickCount(); + if (generateDictBits == 0) + rg.GenerateSimpleRandom(Salt); + else + { + if (generateDictBits >= sizeof(size_t) * 8 + && kBufferSize > ((size_t)1 << (sizeof(size_t) * 8 - 1))) + return E_INVALIDARG; + rg.GenerateLz(generateDictBits, Salt); + // return E_ABORT; // for debug + } + // printf("\n%d\n ", GetTickCount() - ttt); + + crc = CrcCalc((const Byte *)rg, rg.Size()); + uncompressedDataPtr = (const Byte *)rg; + } + + if (!outStream) + { + outStreamSpec = new CBenchmarkOutStream; + outStream = outStreamSpec; + } + + ALLOC_WITH_HRESULT(outStreamSpec, kCompressedBufferSize) + + if (_encoderFilter) + { + /* we try to reduce the number of memcpy() in main encoding loop. + so we copy data to temp buffers here */ + ALLOC_WITH_HRESULT(&rgCopy, kBufferSize) + memcpy((Byte *)*outStreamSpec, uncompressedDataPtr, kBufferSize); + memcpy((Byte *)rgCopy, uncompressedDataPtr, kBufferSize); + } + + if (!propStream) + { + propStreamSpec = new CBufPtrSeqOutStream; // CBenchmarkOutStream; + propStream = propStreamSpec; + } + // ALLOC_WITH_HRESULT_2(propStreamSpec, kMaxMethodPropSize); + // propStreamSpec->Init(true, false); + propStreamSpec->Init(propsData, sizeof(propsData)); + + + CMyComPtr coder; + if (_encoderFilter) + coder = _encoderFilter; + else + coder = _encoder; + { + CMyComPtr scp; + coder.QueryInterface(IID_ICompressSetCoderProperties, &scp); + if (scp) + { + const UInt64 reduceSize = kBufferSize; + /* in posix : new thread uses same affinity as parent thread, + so we don't need to send affinity to coder in posix */ + UInt64 affMask = 0; + UInt32 affinityGroup = (UInt32)(Int32)-1; + // UInt64 affinityInGroup = 0; +#if !defined(Z7_ST) && defined(_WIN32) + { + CCpuSet cpuSet; + if (AffinityMode.NeedGroupsMode()) // we need fix for affinityInGroup also + affinityGroup = EncoderIndex % AffinityMode.NumGroups; + else + affMask = AffinityMode.GetAffinityMask(EncoderIndex, &cpuSet); + } +#endif + // affMask <<= 3; // debug line: to test no affinity in coder + // affMask = 0; // for debug + // affinityGroup = 0; // for debug + // affinityInGroup = 1; // for debug + RINOK(method.SetCoderProps_DSReduce_Aff(scp, &reduceSize, + affMask != 0 ? &affMask : NULL, + affinityGroup != (UInt32)(Int32)-1 ? &affinityGroup : NULL, + /* affinityInGroup != 0 ? &affinityInGroup : */ NULL)) + } + else + { + if (method.AreThereNonOptionalProps()) + return E_INVALIDARG; + } + + CMyComPtr writeCoderProps; + coder.QueryInterface(IID_ICompressWriteCoderProperties, &writeCoderProps); + if (writeCoderProps) + { + RINOK(writeCoderProps->WriteCoderProperties(propStream)) + } + + { + CMyComPtr sp; + coder.QueryInterface(IID_ICryptoSetPassword, &sp); + if (sp) + { + RINOK(sp->CryptoSetPassword(_psw, sizeof(_psw))) + + // we must call encoding one time to calculate password key for key cache. + // it must be after WriteCoderProperties! + Byte temp[16]; + memset(temp, 0, sizeof(temp)); + + if (_encoderFilter) + { + _encoderFilter->Init(); + _encoderFilter->Filter(temp, sizeof(temp)); + } + else + { + CBenchmarkInStream *inStreamSpec = new CBenchmarkInStream; + CMyComPtr inStream = inStreamSpec; + inStreamSpec->Init(temp, sizeof(temp)); + + CCrcOutStream *crcStreamSpec = new CCrcOutStream; + CMyComPtr crcStream = crcStreamSpec; + crcStreamSpec->Init(); + + RINOK(_encoder->Code(inStream, crcStream, NULL, NULL, NULL)) + } + } + } + } + + return S_OK; +} + + +static void My_FilterBench(ICompressFilter *filter, Byte *data, size_t size, UInt32 *crc) +{ + while (size != 0) + { + UInt32 cur = crc ? 1 << 17 : 1 << 24; + if (cur > size) + cur = (UInt32)size; + UInt32 processed = filter->Filter(data, cur); + /* if (processed > size) (in AES filter), we must fill last block with zeros. + but it is not important for benchmark. So we just copy that data without filtering. + if (processed == 0) then filter can't process more */ + if (processed > size || processed == 0) + processed = (UInt32)size; + if (crc) + *crc = CrcUpdate(*crc, data, processed); + data += processed; + size -= processed; + } +} + + +HRESULT CEncoderInfo::Encode() +{ + // printf("\nCEncoderInfo::Generate\n"); + + RINOK(Generate()) + + // printf("\n2222\n"); + + #ifndef Z7_ST + if (Common) + { + Results[0] = S_OK; + WRes wres = ReadyEvent.Set(); + if (wres == 0) + wres = Common->StartEvent.Lock(); + if (wres != 0) + return HRESULT_FROM_WIN32(wres); + if (Common->ExitMode) + return S_OK; + } + else + #endif + { + CBenchProgressInfo *bpi = progressInfoSpec[0]; + bpi->SetStartTime(); + } + + + CBenchInfo &bi = progressInfoSpec[0]->BenchInfo; + bi.UnpackSize = 0; + bi.PackSize = 0; + CMyComPtr cp; + CMyComPtr coder; + if (_encoderFilter) + coder = _encoderFilter; + else + coder = _encoder; + coder.QueryInterface(IID_ICryptoProperties, &cp); + CBenchmarkInStream *inStreamSpec = new CBenchmarkInStream; + CMyComPtr inStream = inStreamSpec; + + if (cp) + { + RINOK(Set_Key_and_IV(cp)) + } + + compressedSize = 0; + if (_encoderFilter) + compressedSize = kBufferSize; + + // CBenchmarkOutStream *outStreamSpec = this->outStreamSpec; + UInt64 prev = 0; + + const UInt32 mask = (CheckCrc_Enc ? 0 : 0xFFFF); + const bool useCrc = (mask < NumIterations); + bool crcPrev_defined = false; + UInt32 crcPrev = 0; + + bool useRealData_Enc = UseRealData_Enc; + bool data_Was_Changed = false; + if (useRealData_Enc) + { + /* we want memcpy() for each iteration including first iteration. + So results will be equal for different number of iterations */ + data_Was_Changed = true; + } + + const UInt64 numIterations = NumIterations; + UInt64 i = numIterations; + // printCallback->NewLine(); + + while (i != 0) + { + i--; + if (printCallback && bi.UnpackSize - prev >= (1 << 26)) + { + prev = bi.UnpackSize; + RINOK(printCallback->CheckBreak()) + } + + /* + CBenchInfo info; + progressInfoSpec[0]->SetStartTime(); + */ + + bool calcCrc = false; + if (useCrc) + calcCrc = (((UInt32)i & mask) == 0); + + if (_encoderFilter) + { + Byte *filterData = rgCopy; + if (i == numIterations - 1 || calcCrc || useRealData_Enc) + { + // printf("\nfilterData = (Byte *)*outStreamSpec;\n"); + filterData = (Byte *)*outStreamSpec; + if (data_Was_Changed) + { + // printf("\nmemcpy(filterData, uncompressedDataPtr\n"); + memcpy(filterData, uncompressedDataPtr, kBufferSize); + } + data_Was_Changed = true; + } + _encoderFilter->Init(); + if (calcCrc) + { + // printf("\nInitCrc\n"); + outStreamSpec->InitCrc(); + } + // printf("\nMy_FilterBench\n"); + My_FilterBench(_encoderFilter, filterData, kBufferSize, + calcCrc ? &outStreamSpec->Crc : NULL); + } + else + { + outStreamSpec->Init(true, calcCrc); // write real data for speed consistency at any number of iterations + inStreamSpec->Init(uncompressedDataPtr, kBufferSize); + RINOK(_encoder->Code(inStream, outStream, NULL, NULL, progressInfo[0])) + if (!inStreamSpec->WasFinished()) + return E_FAIL; + if (compressedSize != outStreamSpec->Pos) + { + if (compressedSize != 0) + return E_FAIL; + compressedSize = outStreamSpec->Pos; + } + } + + // outStreamSpec->Print(); + + if (calcCrc) + { + const UInt32 crc2 = CRC_GET_DIGEST(outStreamSpec->Crc); + if (crcPrev_defined && crcPrev != crc2) + return E_FAIL; + crcPrev = crc2; + crcPrev_defined = true; + } + + bi.UnpackSize += kBufferSize; + bi.PackSize += compressedSize; + + /* + { + progressInfoSpec[0]->SetFinishTime(info); + info.UnpackSize = 0; + info.PackSize = 0; + info.NumIterations = 1; + + info.UnpackSize = kBufferSize; + info.PackSize = compressedSize; + // printf("\n%7d\n", encoder.compressedSize); + + RINOK(callback->SetEncodeResult(info, true)) + printCallback->NewLine(); + } + */ + + } + + _encoder.Release(); + _encoderFilter.Release(); + return S_OK; +} + + +HRESULT CEncoderInfo::Decode(UInt32 decoderIndex) +{ + CBenchmarkInStream *inStreamSpec = new CBenchmarkInStream; + CMyComPtr inStream = inStreamSpec; + CMyComPtr &decoder = _decoders[decoderIndex]; + CMyComPtr coder; + if (_decoderFilter) + { + if (decoderIndex != 0) + return E_FAIL; + coder = _decoderFilter; + } + else + coder = decoder; + + // printf("\ndecoderIndex = %d, stack = %p", decoderIndex, &coder); + + CMyComPtr setDecProps; + coder.QueryInterface(IID_ICompressSetDecoderProperties2, &setDecProps); + if (!setDecProps && propStreamSpec->GetPos() != 0) + return E_FAIL; + + CCrcOutStream *crcOutStreamSpec = new CCrcOutStream; + CMyComPtr crcOutStream = crcOutStreamSpec; + + CBenchProgressInfo *pi = progressInfoSpec[decoderIndex]; + pi->BenchInfo.UnpackSize = 0; + pi->BenchInfo.PackSize = 0; + + #ifndef Z7_ST + { + CMyComPtr setCoderMt; + coder.QueryInterface(IID_ICompressSetCoderMt, &setCoderMt); + if (setCoderMt) + { + RINOK(setCoderMt->SetNumberOfThreads(NumDecoderSubThreads)) + } + } + #endif + + CMyComPtr scp; + coder.QueryInterface(IID_ICompressSetCoderProperties, &scp); + if (scp) + { + const UInt64 reduceSize = _uncompressedDataSize; + RINOK(_method.SetCoderProps(scp, &reduceSize)) + } + + CMyComPtr cp; + coder.QueryInterface(IID_ICryptoProperties, &cp); + + if (setDecProps) + { + RINOK(setDecProps->SetDecoderProperties2( + /* (const Byte *)*propStreamSpec, */ + propsData, + (UInt32)propStreamSpec->GetPos())) + } + + { + CMyComPtr sp; + coder.QueryInterface(IID_ICryptoSetPassword, &sp); + if (sp) + { + RINOK(sp->CryptoSetPassword(_psw, sizeof(_psw))) + } + } + + UInt64 prev = 0; + + if (cp) + { + RINOK(Set_Key_and_IV(cp)) + } + + CMyComPtr setFinishMode; + + if (_decoderFilter) + { + if (compressedSize > rgCopy.Size()) + return E_FAIL; + } + else + { + decoder->QueryInterface(IID_ICompressSetFinishMode, (void **)&setFinishMode); + } + + const UInt64 numIterations = NumIterations; + const E_CheckCrcMode checkCrcMode = CheckCrcMode_Dec; + + for (UInt64 i = 0; i < numIterations; i++) + { + if (printCallback && pi->BenchInfo.UnpackSize - prev >= (1 << 26)) + { + RINOK(printCallback->CheckBreak()) + prev = pi->BenchInfo.UnpackSize; + } + + const UInt64 outSize = kBufferSize; + bool calcCrc = (checkCrcMode != k_CheckCrcMode_Never); + + crcOutStreamSpec->Init(); + + if (_decoderFilter) + { + Byte *filterData = (Byte *)*outStreamSpec; + if (calcCrc) + { + calcCrc = (i == 0); + if (checkCrcMode == k_CheckCrcMode_Always) + { + calcCrc = true; + memcpy((Byte *)rgCopy, (const Byte *)*outStreamSpec, compressedSize); + filterData = rgCopy; + } + } + _decoderFilter->Init(); + My_FilterBench(_decoderFilter, filterData, compressedSize, + calcCrc ? &crcOutStreamSpec->Crc : NULL); + } + else + { + crcOutStreamSpec->CalcCrc = calcCrc; + inStreamSpec->Init((const Byte *)*outStreamSpec, compressedSize); + + if (setFinishMode) + { + RINOK(setFinishMode->SetFinishMode(BoolToUInt(true))) + } + + RINOK(decoder->Code(inStream, crcOutStream, NULL, &outSize, progressInfo[decoderIndex])) + + if (setFinishMode) + { + if (!inStreamSpec->WasFinished()) + return S_FALSE; + + CMyComPtr getInStreamProcessedSize; + decoder.QueryInterface(IID_ICompressGetInStreamProcessedSize, (void **)&getInStreamProcessedSize); + + if (getInStreamProcessedSize) + { + UInt64 processed; + RINOK(getInStreamProcessedSize->GetInStreamProcessedSize(&processed)) + if (processed != compressedSize) + return S_FALSE; + } + } + + if (crcOutStreamSpec->Pos != outSize) + return S_FALSE; + } + + if (calcCrc && CRC_GET_DIGEST(crcOutStreamSpec->Crc) != crc) + return S_FALSE; + + pi->BenchInfo.UnpackSize += kBufferSize; + pi->BenchInfo.PackSize += compressedSize; + } + + decoder.Release(); + _decoderFilter.Release(); + return S_OK; +} + + +static const UInt32 kNumThreadsMax = (1 << 12); + +struct CBenchEncoders +{ + CEncoderInfo *encoders; + CBenchEncoders(UInt32 num): encoders(NULL) { encoders = new CEncoderInfo[num]; } + ~CBenchEncoders() { delete []encoders; } +}; + + +static UInt64 GetNumIterations(UInt64 numCommands, UInt64 complexInCommands) +{ + if (numCommands < (1 << 4)) + numCommands = (1 << 4); + UInt64 res = complexInCommands / numCommands; + return (res == 0 ? 1 : res); +} + + + +#ifndef Z7_ST + +// ---------- CBenchThreadsFlusher ---------- + +struct CBenchThreadsFlusher +{ + CBenchEncoders *EncodersSpec; + CBenchSyncCommon Common; + unsigned NumThreads; + bool NeedClose; + + CBenchThreadsFlusher(): NumThreads(0), NeedClose(false) {} + + ~CBenchThreadsFlusher() + { + StartAndWait(true); + } + + WRes StartAndWait(bool exitMode = false); +}; + + +WRes CBenchThreadsFlusher::StartAndWait(bool exitMode) +{ + if (!NeedClose) + return 0; + + Common.ExitMode = exitMode; + WRes res = Common.StartEvent.Set(); + + for (unsigned i = 0; i < NumThreads; i++) + { + NWindows::CThread &t = EncodersSpec->encoders[i].thread[0]; + if (t.IsCreated()) + { + WRes res2 = t.Wait_Close(); + if (res == 0) + res = res2; + } + } + NeedClose = false; + return res; +} + +#endif // Z7_ST + + + +static void SetPseudoRand(Byte *data, size_t size, UInt32 startValue) +{ + for (size_t i = 0; i < size; i++) + { + data[i] = (Byte)startValue; + startValue++; + } +} + + + +static HRESULT MethodBench( + DECL_EXTERNAL_CODECS_LOC_VARS + UInt64 complexInCommands, + #ifndef Z7_ST + bool oldLzmaBenchMode, + UInt32 numThreads, + const CAffinityMode *affinityMode, + #endif + const COneMethodInfo &method2, + size_t uncompressedDataSize, + const Byte *fileData, + unsigned generateDictBits, + + IBenchPrintCallback *printCallback, + IBenchCallback *callback, + CBenchProps *benchProps) +{ + COneMethodInfo method = method2; + UInt64 methodId; + UInt32 numStreams; + bool isFilter; + const int codecIndex = FindMethod_Index( + EXTERNAL_CODECS_LOC_VARS + method.MethodName, true, + methodId, numStreams, isFilter); + if (codecIndex < 0) + return E_NOTIMPL; + if (numStreams != 1) + return E_INVALIDARG; + + UInt32 numEncoderThreads = 1; + UInt32 numSubDecoderThreads = 1; + + #ifndef Z7_ST + numEncoderThreads = numThreads; + + if (oldLzmaBenchMode) + if (methodId == k_LZMA) + { + if (numThreads == 1 && method.Get_NumThreads() < 0) + method.AddProp_NumThreads(1); + const UInt32 numLzmaThreads = method.Get_Lzma_NumThreads(); + if (numThreads > 1 && numLzmaThreads > 1) + { + numEncoderThreads = (numThreads + 1) / 2; // 20.03 + numSubDecoderThreads = 2; + } + } + + const bool mtEncMode = (numEncoderThreads > 1) || affinityMode->NeedAffinity(); + + #endif + + CBenchEncoders encodersSpec(numEncoderThreads); + CEncoderInfo *encoders = encodersSpec.encoders; + + UInt32 i; + + for (i = 0; i < numEncoderThreads; i++) + { + CEncoderInfo &encoder = encoders[i]; + encoder.callback = (i == 0) ? callback : NULL; + encoder.printCallback = printCallback; + + #ifndef Z7_ST + encoder.EncoderIndex = i; + encoder.NumEncoderInternalThreads = numSubDecoderThreads; + encoder.AffinityMode = *affinityMode; + + /* + if (numSubDecoderThreads > 1) + if (encoder.AffinityMode.NeedAffinity() + && encoder.AffinityMode.NumBundleThreads == 1) + { + // if old LZMA benchmark uses two threads in coder, we increase (NumBundleThreads) for old LZMA benchmark uses two threads instead of one + if (encoder.AffinityMode.NumBundleThreads * 2 <= encoder.AffinityMode.NumCores) + encoder.AffinityMode.NumBundleThreads *= 2; + } + */ + + #endif + + { + CCreatedCoder cod; + RINOK(CreateCoder_Index(EXTERNAL_CODECS_LOC_VARS (unsigned)codecIndex, true, encoder._encoderFilter, cod)) + encoder._encoder = cod.Coder; + if (!encoder._encoder && !encoder._encoderFilter) + return E_NOTIMPL; + } + + SetPseudoRand(encoder._iv, sizeof(encoder._iv), 17); + SetPseudoRand(encoder._key, sizeof(encoder._key), 51); + SetPseudoRand(encoder._psw, sizeof(encoder._psw), 123); + + for (UInt32 j = 0; j < numSubDecoderThreads; j++) + { + CCreatedCoder cod; + CMyComPtr &decoder = encoder._decoders[j]; + RINOK(CreateCoder_Id(EXTERNAL_CODECS_LOC_VARS methodId, false, encoder._decoderFilter, cod)) + decoder = cod.Coder; + if (!encoder._decoderFilter && !decoder) + return E_NOTIMPL; + } + + encoder.UseRealData_Enc = + encoder.CheckCrc_Enc = (benchProps->EncComplex) > 30; + + encoder.CheckCrcMode_Dec = k_CheckCrcMode_Always; + if (benchProps->DecComplexCompr + + benchProps->DecComplexUnc <= 30) + encoder.CheckCrcMode_Dec = + k_CheckCrcMode_FirstPass; // for filters + // k_CheckCrcMode_Never; // for debug + // k_CheckCrcMode_Always; // for debug + if (fileData) + { + encoder.UseRealData_Enc = true; + encoder.CheckCrcMode_Dec = k_CheckCrcMode_Always; + } + } + + UInt32 crc = 0; + if (fileData) + crc = CrcCalc(fileData, uncompressedDataSize); + + for (i = 0; i < numEncoderThreads; i++) + { + CEncoderInfo &encoder = encoders[i]; + encoder._method = method; + encoder.generateDictBits = generateDictBits; + encoder._uncompressedDataSize = uncompressedDataSize; + encoder.kBufferSize = uncompressedDataSize; + encoder.fileData = fileData; + encoder.crc = crc; + } + + CBenchProgressStatus status; + status.Res = S_OK; + status.EncodeMode = true; + + #ifndef Z7_ST + CBenchThreadsFlusher encoderFlusher; + if (mtEncMode) + { + WRes wres = encoderFlusher.Common.StartEvent.Create(); + if (wres != 0) + return HRESULT_FROM_WIN32(wres); + encoderFlusher.NumThreads = numEncoderThreads; + encoderFlusher.EncodersSpec = &encodersSpec; + encoderFlusher.NeedClose = true; + } + #endif + + for (i = 0; i < numEncoderThreads; i++) + { + CEncoderInfo &encoder = encoders[i]; + encoder.NumIterations = GetNumIterations(benchProps->GetNumCommands_Enc(uncompressedDataSize), complexInCommands); + // encoder.NumIterations = 3; + { +#if 0 + #define kCrcPoly 0xEDB88320 + UInt32 r = i; + unsigned num = numEncoderThreads < 256 ? 8 : 16; + do + r = (r >> 1) ^ (kCrcPoly & ((UInt32)0 - (r & 1))); + while (--num); + encoder.Salt = r; +#else + UInt32 salt0 = g_CrcTable[(Byte)i]; + UInt32 salt1 = g_CrcTable[(Byte)(i >> 8)]; + encoder.Salt = salt0 ^ (salt1 << 3); +#endif + } + + // (g_CrcTable[0] == 0), and (encoder.Salt == 0) for first thread + // printf("\n encoder index = %d, Salt = %8x\n", i, encoder.Salt); + + encoder.KeySize = benchProps->KeySize; + + for (int j = 0; j < 2; j++) + { + CBenchProgressInfo *spec = new CBenchProgressInfo; + encoder.progressInfoSpec[j] = spec; + encoder.progressInfo[j] = spec; + spec->Status = &status; + } + + if (i == 0) + { + CBenchProgressInfo *bpi = encoder.progressInfoSpec[0]; + bpi->Callback = callback; + bpi->BenchInfo.NumIterations = numEncoderThreads; + } + + #ifndef Z7_ST + if (mtEncMode) + { + #ifdef USE_ALLOCA + encoder.AllocaSize = BENCH_ALLOCA_VALUE(i); + #endif + + encoder.Common = &encoderFlusher.Common; + encoder.IsGlobalMtMode = numEncoderThreads > 1; + RINOK(encoder.CreateEncoderThread()) + } + #endif + } + + if (printCallback) + { + RINOK(printCallback->CheckBreak()) + } + + #ifndef Z7_ST + if (mtEncMode) + { + for (i = 0; i < numEncoderThreads; i++) + { + CEncoderInfo &encoder = encoders[i]; + const WRes wres = encoder.ReadyEvent.Lock(); + if (wres != 0) + return HRESULT_FROM_WIN32(wres); + RINOK(encoder.Results[0]) + } + + CBenchProgressInfo *bpi = encoders[0].progressInfoSpec[0]; + bpi->SetStartTime(); + + const WRes wres = encoderFlusher.StartAndWait(); + if (status.Res == 0 && wres != 0) + return HRESULT_FROM_WIN32(wres); + } + else + #endif + { + RINOK(encoders[0].Encode()) + } + + RINOK(status.Res) + + CBenchInfo info; + + encoders[0].progressInfoSpec[0]->SetFinishTime(info); + info.UnpackSize = 0; + info.PackSize = 0; + info.NumIterations = encoders[0].NumIterations; + + for (i = 0; i < numEncoderThreads; i++) + { + const CEncoderInfo &encoder = encoders[i]; + info.UnpackSize += encoder.kBufferSize; + info.PackSize += encoder.compressedSize; + // printf("\n%7d\n", encoder.compressedSize); + } + + RINOK(callback->SetEncodeResult(info, true)) + + + + + // ---------- Decode ---------- + + status.Res = S_OK; + status.EncodeMode = false; + + const UInt32 numDecoderThreads = numEncoderThreads * numSubDecoderThreads; + #ifndef Z7_ST + const bool mtDecoderMode = (numDecoderThreads > 1) || affinityMode->NeedAffinity(); + #endif + + for (i = 0; i < numEncoderThreads; i++) + { + CEncoderInfo &encoder = encoders[i]; + + /* + #ifndef Z7_ST + // encoder.affinityMode = *affinityMode; + if (encoder.NumEncoderInternalThreads != 1) + encoder.AffinityMode.DivideNum = encoder.NumEncoderInternalThreads; + #endif + */ + + + if (i == 0) + { + encoder.NumIterations = GetNumIterations( + benchProps->GetNumCommands_Dec( + encoder.compressedSize, + encoder.kBufferSize), + complexInCommands); + CBenchProgressInfo *bpi = encoder.progressInfoSpec[0]; + bpi->Callback = callback; + bpi->BenchInfo.NumIterations = numDecoderThreads; + bpi->SetStartTime(); + } + else + encoder.NumIterations = encoders[0].NumIterations; + + #ifndef Z7_ST + { + const int numSubThreads = method.Get_NumThreads(); + encoder.NumDecoderSubThreads = (numSubThreads <= 0) ? 1 : (unsigned)numSubThreads; + } + if (mtDecoderMode) + { + for (UInt32 j = 0; j < numSubDecoderThreads; j++) + { + const HRESULT res = encoder.CreateDecoderThread(j, (i == 0 && j == 0) + #ifdef USE_ALLOCA + , BENCH_ALLOCA_VALUE(i * numSubDecoderThreads + j) + #endif + ); + RINOK(res) + } + } + else + #endif + { + RINOK(encoder.Decode(0)) + } + } + + #ifndef Z7_ST + if (mtDecoderMode) + { + WRes wres = 0; + HRESULT res = S_OK; + for (i = 0; i < numEncoderThreads; i++) + for (UInt32 j = 0; j < numSubDecoderThreads; j++) + { + CEncoderInfo &encoder = encoders[i]; + const WRes wres2 = encoder.thread[j]. + // Wait(); // later we can get thread times from thread in UNDER_CE + Wait_Close(); + if (wres == 0 && wres2 != 0) + wres = wres2; + const HRESULT res2 = encoder.Results[j]; + if (res == 0 && res2 != 0) + res = res2; + } + if (wres != 0) + return HRESULT_FROM_WIN32(wres); + RINOK(res) + } + #endif // Z7_ST + + RINOK(status.Res) + encoders[0].progressInfoSpec[0]->SetFinishTime(info); + + /* + #ifndef Z7_ST + #ifdef UNDER_CE + if (mtDecoderMode) + for (i = 0; i < numEncoderThreads; i++) + for (UInt32 j = 0; j < numSubDecoderThreads; j++) + { + FILETIME creationTime, exitTime, kernelTime, userTime; + if (::GetThreadTimes(encoders[i].thread[j], &creationTime, &exitTime, &kernelTime, &userTime) != 0) + info.UserTime += GetTime64(userTime) + GetTime64(kernelTime); + } + #endif + #endif + */ + + info.UnpackSize = 0; + info.PackSize = 0; + info.NumIterations = numSubDecoderThreads * encoders[0].NumIterations; + + for (i = 0; i < numEncoderThreads; i++) + { + const CEncoderInfo &encoder = encoders[i]; + info.UnpackSize += encoder.kBufferSize; + info.PackSize += encoder.compressedSize; + } + + // RINOK(callback->SetDecodeResult(info, false)) // why we called before 21.03 ?? + RINOK(callback->SetDecodeResult(info, true)) + + return S_OK; +} + + + +static inline UInt64 GetDictSizeFromLog(unsigned dictSizeLog) +{ + /* + if (dictSizeLog < 32) + return (UInt32)1 << dictSizeLog; + else + return (UInt32)(Int32)-1; + */ + return (UInt64)1 << dictSizeLog; +} + + +// it's limit of current LZMA implementation that can be changed later +#define kLzmaMaxDictSize ((UInt32)15 << 28) + +static inline UInt64 GetLZMAUsage(bool multiThread, int btMode, UInt64 dict) +{ + if (dict == 0) + dict = 1; + if (dict > kLzmaMaxDictSize) + dict = kLzmaMaxDictSize; + UInt32 hs = (UInt32)dict - 1; + hs |= (hs >> 1); + hs |= (hs >> 2); + hs |= (hs >> 4); + hs |= (hs >> 8); + hs >>= 1; + hs |= 0xFFFF; + if (hs > (1 << 24)) + hs >>= 1; + hs++; + hs += (1 << 16); + + const UInt32 kBlockSizeMax = (UInt32)0 - (UInt32)(1 << 16); + UInt64 blockSize = (UInt64)dict + (1 << 16) + + (multiThread ? (1 << 20) : 0); + blockSize += (blockSize >> (blockSize < ((UInt32)1 << 30) ? 1 : 2)); + if (blockSize >= kBlockSizeMax) + blockSize = kBlockSizeMax; + + UInt64 son = (UInt64)dict; + if (btMode) + son *= 2; + const UInt64 v = (hs + son) * 4 + blockSize + + (1 << 20) + (multiThread ? (6 << 20) : 0); + + // printf("\nGetLZMAUsage = %d\n", (UInt32)(v >> 20)); + // printf("\nblockSize = %d\n", (UInt32)(blockSize >> 20)); + return v; +} + + +UInt64 GetBenchMemoryUsage(UInt32 numThreads, int level, UInt64 dictionary, bool totalBench) +{ + const size_t kBufferSize = (size_t)dictionary + kAdditionalSize; + const UInt64 kCompressedBufferSize = GetBenchCompressedSize(kBufferSize); // / 2; + if (level < 0) + level = 5; + const int algo = (level < 5 ? 0 : 1); + const int btMode = (algo == 0 ? 0 : 1); + + UInt32 numBigThreads = numThreads; + const bool lzmaMt = (totalBench || (numThreads > 1 && btMode)); + if (btMode) + { + if (!totalBench && lzmaMt) + numBigThreads /= 2; + } + return ((UInt64)kBufferSize + kCompressedBufferSize + + GetLZMAUsage(lzmaMt, btMode, dictionary) + (2 << 20)) * numBigThreads; +} + +static UInt64 GetBenchMemoryUsage_Hash(UInt32 numThreads, UInt64 dictionary) +{ + // dictionary += (dictionary >> 9); // for page tables (virtual memory) + return (UInt64)(dictionary + (1 << 15)) * numThreads + (2 << 20); +} + + +// ---------- CRC and HASH ---------- + +struct CCrcInfo_Base +{ + CMidAlignedBuffer Buffer; + const Byte *Data; + size_t Size; + bool CreateLocalBuf; + UInt32 CheckSum_Res; + + CCrcInfo_Base(): CreateLocalBuf(true), CheckSum_Res(0) {} + + HRESULT Generate(const Byte *data, size_t size); + HRESULT CrcProcess(UInt64 numIterations, + const UInt32 *checkSum, IHasher *hf, + IBenchPrintCallback *callback); +}; + + +// for debug: define it to test hash calling with unaligned data +// #define Z7_BENCH_HASH_ALIGN_BUF_OFFSET 3 + +HRESULT CCrcInfo_Base::Generate(const Byte *data, size_t size) +{ + Size = size; + Data = data; + if (!data || CreateLocalBuf) + { + Byte *buf; + const size_t size2 = (size + k_RandBuf_AlignMask) & ~(size_t)k_RandBuf_AlignMask; + if (size2 < size) + return E_OUTOFMEMORY; +#ifdef Z7_BENCH_HASH_ALIGN_BUF_OFFSET + ALLOC_WITH_HRESULT(&Buffer, size2 + Z7_BENCH_HASH_ALIGN_BUF_OFFSET) + buf = Buffer + Z7_BENCH_HASH_ALIGN_BUF_OFFSET; +#else + ALLOC_WITH_HRESULT(&Buffer, size2) + buf = Buffer; +#endif + Data = buf; + if (!data) + RandGen_BufAfterPad(buf, size); + else if (size != 0) // (CreateLocalBuf == true) + memcpy(buf, data, size); + } + return S_OK; +} + + +#if 1 +#define HashUpdate(hf, data, size) hf->Update(data, size) +#else +// for debug: +static void HashUpdate(IHasher *hf, const void *data, UInt32 size) +{ + for (;;) + { + if (size == 0) + return; + UInt32 size2 = (size * 0x85EBCA87) % size / 8; + // UInt32 size2 = size / 2; + if (size2 == 0) + size2 = 1; + hf->Update(data, size2); + data = (const void *)((const Byte *)data + size2); + size -= size2; + } +} +#endif + + +HRESULT CCrcInfo_Base::CrcProcess(UInt64 numIterations, + const UInt32 *checkSum, IHasher *hf, + IBenchPrintCallback *callback) +{ + MY_ALIGN(16) + UInt32 hash32[64 / 4]; + memset(hash32, 0, sizeof(hash32)); + + CheckSum_Res = 0; + + const UInt32 hashSize = hf->GetDigestSize(); + if (hashSize > sizeof(hash32)) + return S_FALSE; + + const Byte *buf = Data; + const size_t size = Size; + UInt32 checkSum_Prev = 0; + + UInt64 prev = 0; + UInt64 cur = 0; + + do + { + hf->Init(); + size_t pos = 0; + do + { + const size_t rem = size - pos; + const UInt32 kStep = ((UInt32)1 << 31); + const UInt32 curSize = (rem < kStep) ? (UInt32)rem : kStep; + HashUpdate(hf, buf + pos, curSize); + pos += curSize; + } + while (pos != size); + + hf->Final((Byte *)(void *)hash32); + UInt32 sum = 0; + for (UInt32 j = 0; j < hashSize; j += 4) + { + sum = rotlFixed(sum, 11); + sum += GetUi32((const Byte *)(const void *)hash32 + j); + } + if (checkSum) + { + if (sum != *checkSum) + return S_FALSE; + } + else + { + checkSum_Prev = sum; + checkSum = &checkSum_Prev; + } + if (callback) + { + cur += size; + if (cur - prev >= ((UInt32)1 << 30)) + { + prev = cur; + RINOK(callback->CheckBreak()) + } + } + } + while (--numIterations); + + CheckSum_Res = checkSum_Prev; + return S_OK; +} + +extern +UInt32 g_BenchCpuFreqTemp; // we need non-static variavble to disable compiler optimization +UInt32 g_BenchCpuFreqTemp = 1; + +#define YY1 sum += val; sum ^= val; +#define YY3 YY1 YY1 YY1 YY1 +#define YY5 YY3 YY3 YY3 YY3 +#define YY7 YY5 YY5 YY5 YY5 +static const UInt32 kNumFreqCommands = 128; + +EXTERN_C_BEGIN + +static UInt32 CountCpuFreq(UInt32 sum, UInt32 num, UInt32 val) +{ + for (UInt32 i = 0; i < num; i++) + { + YY7 + } + return sum; +} + +EXTERN_C_END + + +#ifndef Z7_ST + +struct CBaseThreadInfo +{ + NWindows::CThread Thread; + IBenchPrintCallback *Callback; + HRESULT CallbackRes; + + WRes Wait_If_Created() + { + if (!Thread.IsCreated()) + return 0; + return Thread.Wait_Close(); + } +}; + +struct CFreqInfo: public CBaseThreadInfo +{ + UInt32 ValRes; + UInt32 Size; + UInt64 NumIterations; +}; + +static THREAD_FUNC_DECL FreqThreadFunction(void *param) +{ + CFreqInfo *p = (CFreqInfo *)param; + + UInt32 sum = g_BenchCpuFreqTemp; + for (UInt64 k = p->NumIterations; k > 0; k--) + { + if (p->Callback) + { + p->CallbackRes = p->Callback->CheckBreak(); + if (p->CallbackRes != S_OK) + break; + } + sum = CountCpuFreq(sum, p->Size, g_BenchCpuFreqTemp); + } + p->ValRes = sum; + return THREAD_FUNC_RET_ZERO; +} + +struct CFreqThreads +{ + CFreqInfo *Items; + UInt32 NumThreads; + + CFreqThreads(): Items(NULL), NumThreads(0) {} + + WRes WaitAll() + { + WRes wres = 0; + for (UInt32 i = 0; i < NumThreads; i++) + { + WRes wres2 = Items[i].Wait_If_Created(); + if (wres == 0 && wres2 != 0) + wres = wres2; + } + NumThreads = 0; + return wres; + } + + ~CFreqThreads() + { + WaitAll(); + delete []Items; + } +}; + + +static THREAD_FUNC_DECL CrcThreadFunction(void *param); + +struct CCrcInfo: public CBaseThreadInfo +{ + const Byte *Data; + size_t Size; + UInt64 NumIterations; + bool CheckSumDefined; + UInt32 CheckSum; + CMyComPtr Hasher; + HRESULT Res; + UInt32 CheckSum_Res; + + #ifndef Z7_ST + NSynchronization::CManualResetEvent ReadyEvent; + UInt32 ThreadIndex; + CBenchSyncCommon *Common; + CAffinityMode AffinityMode; + #endif + + // we want to call CCrcInfo_Base::Buffer.Free() in main thread. + // so we uses non-local CCrcInfo_Base. + CCrcInfo_Base crcib; + + HRESULT CreateThread() + { + WRes res = 0; + if (!ReadyEvent.IsCreated()) + res = ReadyEvent.Create(); + if (res == 0) + res = AffinityMode.CreateThread_WithAffinity(Thread, CrcThreadFunction, this, + ThreadIndex); + return HRESULT_FROM_WIN32(res); + } + + #ifdef USE_ALLOCA + size_t AllocaSize; + #endif + + void Process(); + + CCrcInfo(): Res(E_FAIL) {} +}; + +static const bool k_Crc_CreateLocalBuf_For_File = true; // for total BW test +// static const bool k_Crc_CreateLocalBuf_For_File = false; // for shared memory read test + +void CCrcInfo::Process() +{ + crcib.CreateLocalBuf = k_Crc_CreateLocalBuf_For_File; + // we can use additional Generate() passes to reduce some time effects for new page allocation + // for (unsigned y = 0; y < 10; y++) + Res = crcib.Generate(Data, Size); + + // if (Common) + { + WRes wres = ReadyEvent.Set(); + if (wres != 0) + { + if (Res == 0) + Res = HRESULT_FROM_WIN32(wres); + return; + } + if (Res != 0) + return; + + wres = Common->StartEvent.Lock(); + + if (wres != 0) + { + Res = HRESULT_FROM_WIN32(wres); + return; + } + if (Common->ExitMode) + return; + } + + Res = crcib.CrcProcess(NumIterations, + CheckSumDefined ? &CheckSum : NULL, Hasher, + Callback); + CheckSum_Res = crcib.CheckSum_Res; + /* + We don't want to include the time of slow CCrcInfo_Base::Buffer.Free() + to time of benchmark. So we don't free Buffer here + */ + // crcib.Buffer.Free(); +} + + +static THREAD_FUNC_DECL CrcThreadFunction(void *param) +{ + CCrcInfo *p = (CCrcInfo *)param; + + #ifdef USE_ALLOCA + alloca(p->AllocaSize); + #endif + p->Process(); + return THREAD_FUNC_RET_ZERO; +} + + +struct CCrcThreads +{ + CCrcInfo *Items; + unsigned NumThreads; + CBenchSyncCommon Common; + bool NeedClose; + + CCrcThreads(): Items(NULL), NumThreads(0), NeedClose(false) {} + + WRes StartAndWait(bool exitMode = false); + + ~CCrcThreads() + { + StartAndWait(true); + delete []Items; + } +}; + + +WRes CCrcThreads::StartAndWait(bool exitMode) +{ + if (!NeedClose) + return 0; + + Common.ExitMode = exitMode; + WRes wres = Common.StartEvent.Set(); + + for (unsigned i = 0; i < NumThreads; i++) + { + WRes wres2 = Items[i].Wait_If_Created(); + if (wres == 0 && wres2 != 0) + wres = wres2; + } + NumThreads = 0; + NeedClose = false; + return wres; +} + +#endif + + +/* +static UInt32 CrcCalc1(const Byte *buf, size_t size) +{ + UInt32 crc = CRC_INIT_VAL; + for (size_t i = 0; i < size; i++) + crc = CRC_UPDATE_BYTE(crc, buf[i]); + return CRC_GET_DIGEST(crc); +} +*/ + +/* +static UInt32 RandGenCrc(Byte *buf, size_t size, CBaseRandomGenerator &RG) +{ + RandGen(buf, size, RG); + return CrcCalc1(buf, size); +} +*/ + +static bool CrcInternalTest() +{ + CAlignedBuffer buffer; + const size_t kBufSize = 1 << 11; + const size_t kCheckSize = 1 << 6; + buffer.Alloc(kBufSize); + if (!buffer.IsAllocated()) + return false; + Byte *buf = (Byte *)buffer; + RandGen_BufAfterPad(buf, kBufSize); + UInt32 sum = 0; + for (size_t i = 0; i < kBufSize - kCheckSize * 2; i += kCheckSize - 1) + for (size_t j = 0; j < kCheckSize; j++) + { + sum = rotlFixed(sum, 11); + sum += CrcCalc(buf + i + j, j); + } + return sum == 0x28462c7c; +} + +struct CBenchMethod +{ + unsigned Weight; + unsigned DictBits; + Int32 EncComplex; + Int32 DecComplexCompr; + Int32 DecComplexUnc; + const char *Name; + // unsigned KeySize; +}; + +// #define USE_SW_CMPLX + +#ifdef USE_SW_CMPLX +#define CMPLX(x) ((x) * 1000) +#else +#define CMPLX(x) (x) +#endif + +static const CBenchMethod g_Bench[] = +{ + // { 40, 17, 357, 145, 20, "LZMA:x1" }, + // { 20, 18, 360, 145, 20, "LZMA2:x1:mt2" }, + + { 20, 18, 360, 145, 20, "LZMA:x1" }, + { 20, 22, 600, 145, 20, "LZMA:x3" }, + + { 80, 24, 1220, 145, 20, "LZMA:x5:mt1" }, + { 80, 24, 1220, 145, 20, "LZMA:x5:mt2" }, + + { 10, 16, 124, 40, 14, "Deflate:x1" }, + { 20, 16, 376, 40, 14, "Deflate:x5" }, + { 10, 16, 1082, 40, 14, "Deflate:x7" }, + { 10, 17, 422, 40, 14, "Deflate64:x5" }, + + { 10, 15, 590, 69, 69, "BZip2:x1" }, + { 20, 19, 815, 122, 122, "BZip2:x5" }, + { 10, 19, 815, 122, 122, "BZip2:x5:mt2" }, + { 10, 19, 2530, 122, 122, "BZip2:x7" }, + + // { 10, 18, 1010, 0, 1150, "PPMDZip:x1" }, + { 10, 18, 1010, 0, 1150, "PPMD:x1" }, + // { 10, 22, 1655, 0, 1830, "PPMDZip:x5" }, + { 10, 22, 1655, 0, 1830, "PPMD:x5" }, + + // { 2, 0, -16, 0, -16, "Swap2" }, + { 2, 0, -16, 0, -16, "Swap4" }, + + // { 2, 0, 3, 0, 4, "Delta:1" }, + // { 2, 0, 3, 0, 4, "Delta:2" }, + // { 2, 0, 3, 0, 4, "Delta:3" }, + { 2, 0, 3, 0, 4, "Delta:4" }, + // { 2, 0, 3, 0, 4, "Delta:8" }, + // { 2, 0, 3, 0, 4, "Delta:32" }, + + { 2, 0, 2, 0, 2, "BCJ" }, + { 2, 0, 1, 0, 1, "ARM64" }, + { 2, 0, 1, 0, 1, "RISCV" }, + + // { 10, 0, 18, 0, 18, "AES128CBC:1" }, + // { 10, 0, 21, 0, 21, "AES192CBC:1" }, + { 10, 0, 24, 0, 24, "AES256CBC:1" }, + + // { 10, 0, 18, 0, 18, "AES128CTR:1" }, + // { 10, 0, 21, 0, 21, "AES192CTR:1" }, + // { 10, 0, 24, 0, 24, "AES256CTR:1" }, + // { 2, 0, CMPLX(6), 0, CMPLX(1), "AES128CBC:2" }, + // { 2, 0, CMPLX(7), 0, CMPLX(1), "AES192CBC:2" }, + { 2, 0, CMPLX(8), 0, CMPLX(1), "AES256CBC:2" }, + + // { 2, 0, CMPLX(1), 0, CMPLX(1), "AES128CTR:2" }, + // { 2, 0, CMPLX(1), 0, CMPLX(1), "AES192CTR:2" }, + // { 2, 0, CMPLX(1), 0, CMPLX(1), "AES256CTR:2" }, + + // { 1, 0, CMPLX(6), 0, -2, "AES128CBC:3" }, + // { 1, 0, CMPLX(7), 0, -2, "AES192CBC:3" }, + { 1, 0, CMPLX(8), 0, -2, "AES256CBC:3" } + + // { 1, 0, CMPLX(1), 0, -2, "AES128CTR:3" }, + // { 1, 0, CMPLX(1), 0, -2, "AES192CTR:3" }, + // { 1, 0, CMPLX(1), 0, -2, "AES256CTR:3" }, +}; + +struct CBenchHash +{ + unsigned Weight; + UInt32 Complex; + UInt32 CheckSum; + const char *Name; +}; + +// #define ARM_CRC_MUL 100 +#define ARM_CRC_MUL 1 + +#define k_Hash_Complex_Mult 256 + +static const CBenchHash g_Hash[] = +{ + { 20, 256, 0x21e207bb, "CRC32:12" } , + { 2, 128 *ARM_CRC_MUL, 0x21e207bb, "CRC32:32" }, + { 2, 64 *ARM_CRC_MUL, 0x21e207bb, "CRC32:64" }, + { 10, 256, 0x41b901d1, "CRC64" }, + { 5, 64, 0x43eac94f, "XXH64" }, + { 2, 2340, 0x3398a904, "MD5" }, + { 10, 2340, 0xff769021, "SHA1:1" }, + { 2, CMPLX((20 * 6 + 1) * 4 + 4), 0xff769021, "SHA1:2" }, + { 10, 5100, 0x7913ba03, "SHA256:1" }, + { 2, CMPLX((32 * 4 + 1) * 4 + 4), 0x7913ba03, "SHA256:2" }, + { 5, 3200, 0xe7aeb394, "SHA512:1" }, + { 2, CMPLX((40 * 4 + 1) * 4 + 4), 0xe7aeb394, "SHA512:2" }, + // { 10, 3428, 0x1cc99b18, "SHAKE128" }, + // { 10, 4235, 0x74eaddc3, "SHAKE256" }, + // { 10, 4000, 0xdf3e6863, "SHA3-224" }, + { 5, 4200, 0xcecac10d, "SHA3-256" }, + // { 10, 5538, 0x4e5d9163, "SHA3-384" }, + // { 10, 8000, 0x96a58289, "SHA3-512" }, + { 2, 4096, 0x85189d02, "BLAKE2sp:1" }, + { 2, 1024, 0x85189d02, "BLAKE2sp:2" }, // sse2-way4-fast + { 2, 512, 0x85189d02, "BLAKE2sp:3" } // avx2-way8-fast +#if 0 + , { 2, 2048, 0x85189d02, "BLAKE2sp:4" } // sse2-way1 + , { 2, 1024, 0x85189d02, "BLAKE2sp:5" } // sse2-way2 + , { 2, 1024, 0x85189d02, "BLAKE2sp:6" } // avx2-way2 + , { 2, 1024, 0x85189d02, "BLAKE2sp:7" } // avx2-way4 +#endif +}; + +static void PrintNumber(IBenchPrintCallback &f, UInt64 value, unsigned size) +{ + char s[128]; + unsigned startPos = (unsigned)sizeof(s) - 32; + memset(s, ' ', startPos); + ConvertUInt64ToString(value, s + startPos); + // if (withSpace) + { + startPos--; + size++; + } + unsigned len = (unsigned)strlen(s + startPos); + if (size > len) + { + size -= len; + if (startPos < size) + startPos = 0; + else + startPos -= size; + } + f.Print(s + startPos); +} + +static const unsigned kFieldSize_Name = 12; +static const unsigned kFieldSize_SmallName = 4; +static const unsigned kFieldSize_Speed = 9; +static const unsigned kFieldSize_Usage = 5; +static const unsigned kFieldSize_RU = 6; +static const unsigned kFieldSize_Rating = 6; +static const unsigned kFieldSize_EU = 5; +static const unsigned kFieldSize_Effec = 5; +static const unsigned kFieldSize_CrcSpeed = 8; + + +static const unsigned kFieldSize_TotalSize = 4 + kFieldSize_Speed + kFieldSize_Usage + kFieldSize_RU + kFieldSize_Rating; +static const unsigned kFieldSize_EUAndEffec = 2 + kFieldSize_EU + kFieldSize_Effec; + + +static void PrintRating(IBenchPrintCallback &f, UInt64 rating, unsigned size) +{ + PrintNumber(f, (rating + 500000) / 1000000, size); +} + + +static void PrintPercents(IBenchPrintCallback &f, UInt64 val, UInt64 divider, unsigned size) +{ + UInt64 v = 0; + if (divider != 0) + v = (val * 100 + divider / 2) / divider; + PrintNumber(f, v, size); +} + +static void PrintChars(IBenchPrintCallback &f, char c, unsigned size) +{ + char s[256]; + memset(s, (Byte)c, size); + s[size] = 0; + f.Print(s); +} + +static void PrintSpaces(IBenchPrintCallback &f, unsigned size) +{ + PrintChars(f, ' ', size); +} + +static void PrintUsage(IBenchPrintCallback &f, UInt64 usage, unsigned size) +{ + PrintNumber(f, Benchmark_GetUsage_Percents(usage), size); +} + +static void PrintResults(IBenchPrintCallback &f, UInt64 usage, UInt64 rpu, UInt64 rating, bool showFreq, UInt64 cpuFreq) +{ + PrintUsage(f, usage, kFieldSize_Usage); + PrintRating(f, rpu, kFieldSize_RU); + PrintRating(f, rating, kFieldSize_Rating); + if (showFreq) + { + if (cpuFreq == 0) + PrintSpaces(f, kFieldSize_EUAndEffec); + else + { + PrintPercents(f, rating, cpuFreq * usage / kBenchmarkUsageMult, kFieldSize_EU); + PrintPercents(f, rating, cpuFreq, kFieldSize_Effec); + } + } +} + + +void CTotalBenchRes::Generate_From_BenchInfo(const CBenchInfo &info) +{ + Speed = info.GetUnpackSizeSpeed(); + Usage = info.GetUsage(); + RPU = info.GetRatingPerUsage(Rating); +} + +void CTotalBenchRes::Mult_For_Weight(unsigned weight) +{ + NumIterations2 *= weight; + RPU *= weight; + Rating *= weight; + Usage *= weight; + Speed *= weight; +} + +void CTotalBenchRes::Update_With_Res(const CTotalBenchRes &r) +{ + Rating += r.Rating; + Usage += r.Usage; + RPU += r.RPU; + Speed += r.Speed; + // NumIterations1 = (r1.NumIterations1 + r2.NumIterations1); + NumIterations2 += r.NumIterations2; +} + +static void PrintResults(IBenchPrintCallback *f, + const CBenchInfo &info, + unsigned weight, + UInt64 rating, + bool showFreq, UInt64 cpuFreq, + CTotalBenchRes *res) +{ + CTotalBenchRes t; + t.Rating = rating; + t.NumIterations2 = 1; + t.Generate_From_BenchInfo(info); + + if (f) + { + if (t.Speed != 0) + PrintNumber(*f, t.Speed / 1024, kFieldSize_Speed); + else + PrintSpaces(*f, 1 + kFieldSize_Speed); + } + if (f) + { + PrintResults(*f, t.Usage, t.RPU, rating, showFreq, cpuFreq); + } + + if (res) + { + // res->NumIterations1++; + t.Mult_For_Weight(weight); + res->Update_With_Res(t); + } +} + +static void PrintTotals(IBenchPrintCallback &f, + bool showFreq, UInt64 cpuFreq, bool showSpeed, const CTotalBenchRes &res) +{ + const UInt64 numIterations2 = res.NumIterations2 ? res.NumIterations2 : 1; + const UInt64 speed = res.Speed / numIterations2; + if (showSpeed && speed != 0) + PrintNumber(f, speed / 1024, kFieldSize_Speed); + else + PrintSpaces(f, 1 + kFieldSize_Speed); + + // PrintSpaces(f, 1 + kFieldSize_Speed); + // UInt64 numIterations1 = res.NumIterations1; if (numIterations1 == 0) numIterations1 = 1; + PrintResults(f, res.Usage / numIterations2, res.RPU / numIterations2, res.Rating / numIterations2, showFreq, cpuFreq); +} + + +static void PrintHex(AString &s, UInt64 v) +{ + char temp[32]; + ConvertUInt64ToHex(v, temp); + s += temp; +} + +AString GetProcessThreadsInfo(const NSystem::CProcessAffinity &ti) +{ + AString s; + // s.Add_UInt32(ti.numProcessThreads); + const unsigned numSysThreads = ti.GetNumSystemThreads(); + if (ti.GetNumProcessThreads() != numSysThreads) + { + // if (ti.numProcessThreads != ti.numSysThreads) + { + s += " / "; + s.Add_UInt32(numSysThreads); + } + s += " : "; + #ifdef _WIN32 + PrintHex(s, ti.processAffinityMask); + s += " / "; + PrintHex(s, ti.systemAffinityMask); + #else + unsigned i = (numSysThreads + 3) & ~(unsigned)3; + if (i == 0) + i = 4; + for (; i >= 4; ) + { + i -= 4; + unsigned val = 0; + for (unsigned k = 0; k < 4; k++) + { + const unsigned bit = (ti.IsCpuSet(i + k) ? 1 : 0); + val += (bit << k); + } + PrintHex(s, val); + } + #endif + } +#ifdef _WIN32 + if (ti.Groups.GroupSizes.Size() > 1 || + (ti.Groups.GroupSizes.Size() == 1 + && ti.Groups.NumThreadsTotal != numSysThreads)) + { + s += " : "; + s.Add_UInt32(ti.Groups.GroupSizes.Size()); + s += " groups : "; + if (ti.Groups.NumThreadsTotal == numSysThreads) + { + s.Add_UInt32(ti.Groups.NumThreadsTotal); + s += " c : "; + } + UInt32 minSize, maxSize; + ti.Groups.Get_GroupSize_Min_Max(minSize, maxSize); + if (minSize == maxSize) + { + s.Add_UInt32(ti.Groups.GroupSizes[0]); + s += " c/g"; + } + else + FOR_VECTOR (i, ti.Groups.GroupSizes) + { + if (i != 0) + s.Add_Space(); + s.Add_UInt32(ti.Groups.GroupSizes[i]); + } + } +#endif + return s; +} + + +#ifdef Z7_LARGE_PAGES + +#ifdef _WIN32 +extern bool g_LargePagesMode; +#endif +extern "C" +{ + extern size_t g_LargePageSize; +} + +void Add_LargePages_String(AString &s) +{ + #ifdef _WIN32 + if (g_LargePagesMode || g_LargePageSize != 0) + { + s.Add_OptSpaced("(LP-"); + PrintSize_KMGT_Or_Hex(s, g_LargePageSize); + #ifdef MY_CPU_X86_OR_AMD64 + if (CPU_IsSupported_PageGB()) + s += "-1G"; + #endif + if (!g_LargePagesMode) + s += "-NA"; + s.Add_Char(')'); + } + #else + if (g_LargePageSize != 0) + { + s.Add_OptSpaced(" (LP)"); + // PrintSize_KMGT_Or_Hex(s, g_LargePageSize); + } + #endif +} + +#endif + + + +static void PrintRequirements(IBenchPrintCallback &f, const char *sizeString, + bool size_Defined, UInt64 size, const char *threadsString, UInt32 numThreads) +{ + f.Print("RAM "); + f.Print(sizeString); + if (size_Defined) + PrintNumber(f, (size >> 20), 6); + else + f.Print(" ?"); + f.Print(" MB"); + + #ifdef Z7_LARGE_PAGES + { + AString s; + Add_LargePages_String(s); + f.Print(s); + } + #endif + + f.Print(", # "); + f.Print(threadsString); + PrintNumber(f, numThreads, 3); +} + + + +struct CBenchCallbackToPrint Z7_final: public IBenchCallback +{ + bool NeedPrint; + bool Use2Columns; + bool ShowFreq; + unsigned NameFieldSize; + + unsigned EncodeWeight; + unsigned DecodeWeight; + + UInt64 CpuFreq; + UInt64 DictSize; + + IBenchPrintCallback *_file; + CBenchProps BenchProps; + CTotalBenchRes EncodeRes; + CTotalBenchRes DecodeRes; + + CBenchInfo BenchInfo_Results[2]; + + CBenchCallbackToPrint(): + NeedPrint(true), + Use2Columns(false), + ShowFreq(false), + NameFieldSize(0), + EncodeWeight(1), + DecodeWeight(1), + CpuFreq(0) + {} + + void Init() { EncodeRes.Init(); DecodeRes.Init(); } + void Print(const char *s); + void NewLine(); + + HRESULT SetFreq(bool showFreq, UInt64 cpuFreq); + HRESULT SetEncodeResult(const CBenchInfo &info, bool final) Z7_override; + HRESULT SetDecodeResult(const CBenchInfo &info, bool final) Z7_override; +}; + +HRESULT CBenchCallbackToPrint::SetFreq(bool showFreq, UInt64 cpuFreq) +{ + ShowFreq = showFreq; + CpuFreq = cpuFreq; + return S_OK; +} + +HRESULT CBenchCallbackToPrint::SetEncodeResult(const CBenchInfo &info, bool final) +{ + RINOK(_file->CheckBreak()) + if (final) + BenchInfo_Results[0] = info; + if (final) + if (NeedPrint) + { + const UInt64 rating = BenchProps.GetRating_Enc(DictSize, info.GlobalTime, info.GlobalFreq, info.UnpackSize * info.NumIterations); + PrintResults(_file, info, + EncodeWeight, rating, + ShowFreq, CpuFreq, &EncodeRes); + if (!Use2Columns) + _file->NewLine(); + } + return S_OK; +} + +static const char * const kSep = " | "; + +HRESULT CBenchCallbackToPrint::SetDecodeResult(const CBenchInfo &info, bool final) +{ + RINOK(_file->CheckBreak()) + if (final) + BenchInfo_Results[1] = info; + if (final) + if (NeedPrint) + { + const UInt64 rating = BenchProps.GetRating_Dec(info.GlobalTime, info.GlobalFreq, info.UnpackSize, info.PackSize, info.NumIterations); + if (Use2Columns) + _file->Print(kSep); + else + PrintSpaces(*_file, NameFieldSize); + CBenchInfo info2 = info; + info2.UnpackSize *= info2.NumIterations; + info2.PackSize *= info2.NumIterations; + info2.NumIterations = 1; + PrintResults(_file, info2, + DecodeWeight, rating, + ShowFreq, CpuFreq, &DecodeRes); + } + return S_OK; +} + +void CBenchCallbackToPrint::Print(const char *s) +{ + _file->Print(s); +} + +void CBenchCallbackToPrint::NewLine() +{ + _file->NewLine(); +} + +static void PrintLeft(IBenchPrintCallback &f, const char *s, unsigned size) +{ + f.Print(s); + int numSpaces = (int)size - (int)MyStringLen(s); + if (numSpaces > 0) + PrintSpaces(f, (unsigned)numSpaces); +} + +static void PrintRight(IBenchPrintCallback &f, const char *s, unsigned size) +{ + int numSpaces = (int)size - (int)MyStringLen(s); + if (numSpaces > 0) + PrintSpaces(f, (unsigned)numSpaces); + f.Print(s); +} + + +static bool DoesWildcardMatchName_NoCase(const AString &mask, const char *name) +{ + UString wildc = GetUnicodeString(mask); + UString bname = GetUnicodeString(name); + wildc.MakeLower_Ascii(); + bname.MakeLower_Ascii(); + return DoesWildcardMatchName(wildc, bname); +} + + +static HRESULT TotalBench( + DECL_EXTERNAL_CODECS_LOC_VARS + const COneMethodInfo &methodMask, + UInt64 complexInCommands, + #ifndef Z7_ST + UInt32 numThreads, + const CAffinityMode *affinityMode, + #endif + bool forceUnpackSize, + size_t unpackSize, + const Byte *fileData, + IBenchPrintCallback *printCallback, CBenchCallbackToPrint *callback) +{ + for (unsigned i = 0; i < Z7_ARRAY_SIZE(g_Bench); i++) + { + const CBenchMethod &bench = g_Bench[i]; + if (!DoesWildcardMatchName_NoCase(methodMask.MethodName, bench.Name)) + continue; + PrintLeft(*callback->_file, bench.Name, kFieldSize_Name); + { + unsigned keySize = 32; + if (IsString1PrefixedByString2(bench.Name, "AES128")) keySize = 16; + else if (IsString1PrefixedByString2(bench.Name, "AES192")) keySize = 24; + callback->BenchProps.KeySize = keySize; + } + callback->BenchProps.DecComplexUnc = bench.DecComplexUnc; + callback->BenchProps.DecComplexCompr = bench.DecComplexCompr; + callback->BenchProps.EncComplex = bench.EncComplex; + + COneMethodInfo method; + NCOM::CPropVariant propVariant; + propVariant = bench.Name; + RINOK(method.ParseMethodFromPROPVARIANT(UString(), propVariant)) + + size_t unpackSize2 = unpackSize; + if (!forceUnpackSize && bench.DictBits == 0) + unpackSize2 = kFilterUnpackSize; + + callback->EncodeWeight = bench.Weight; + callback->DecodeWeight = bench.Weight; + + const HRESULT res = MethodBench( + EXTERNAL_CODECS_LOC_VARS + complexInCommands, + #ifndef Z7_ST + false, numThreads, affinityMode, + #endif + method, + unpackSize2, fileData, + bench.DictBits, + printCallback, callback, &callback->BenchProps); + + if (res == E_NOTIMPL) + { + // callback->Print(" ---"); + // we need additional empty line as line for decompression results + if (!callback->Use2Columns) + callback->NewLine(); + } + else + { + RINOK(res) + } + + callback->NewLine(); + } + return S_OK; +} + + +struct CFreqBench +{ + // in: + UInt64 complexInCommands; + UInt32 numThreads; + bool showFreq; + UInt64 specifiedFreq; + + // out: + UInt64 CpuFreqRes; + UInt64 UsageRes; + UInt32 res; + + CFreqBench() + {} + + HRESULT FreqBench(IBenchPrintCallback *_file + #ifndef Z7_ST + , const CAffinityMode *affinityMode + #endif + ); +}; + + +HRESULT CFreqBench::FreqBench(IBenchPrintCallback *_file + #ifndef Z7_ST + , const CAffinityMode *affinityMode + #endif + ) +{ + res = 0; + CpuFreqRes = 0; + UsageRes = 0; + + if (numThreads == 0) + numThreads = 1; + + #ifdef Z7_ST + numThreads = 1; + #endif + + const UInt32 complexity = kNumFreqCommands; + UInt64 numIterations = complexInCommands / complexity; + UInt32 numIterations2 = 1 << 30; + if (numIterations > numIterations2) + numIterations /= numIterations2; + else + { + numIterations2 = (UInt32)numIterations; + numIterations = 1; + } + + CBenchInfoCalc progressInfoSpec; + + #ifndef Z7_ST + + bool mtMode = (numThreads > 1) || affinityMode->NeedAffinity(); + + if (mtMode) + { + CFreqThreads threads; + threads.Items = new CFreqInfo[numThreads]; + UInt32 i; + for (i = 0; i < numThreads; i++) + { + CFreqInfo &info = threads.Items[i]; + info.Callback = _file; + info.CallbackRes = S_OK; + info.NumIterations = numIterations; + info.Size = numIterations2; + } + progressInfoSpec.SetStartTime(); + for (i = 0; i < numThreads; i++) + { + // Sleep(10); + CFreqInfo &info = threads.Items[i]; + WRes wres = affinityMode->CreateThread_WithAffinity(info.Thread, FreqThreadFunction, &info, i); + if (info.Thread.IsCreated()) + threads.NumThreads++; + if (wres != 0) + return HRESULT_FROM_WIN32(wres); + } + WRes wres = threads.WaitAll(); + if (wres != 0) + return HRESULT_FROM_WIN32(wres); + for (i = 0; i < numThreads; i++) + { + RINOK(threads.Items[i].CallbackRes) + } + } + else + #endif + { + progressInfoSpec.SetStartTime(); + UInt32 sum = g_BenchCpuFreqTemp; + UInt64 k = numIterations; + do + { + sum = CountCpuFreq(sum, numIterations2, g_BenchCpuFreqTemp); + if (_file) + { + RINOK(_file->CheckBreak()) + } + } + while (--k); + res += sum; + } + + if (res == 0x12345678) + if (_file) + { + RINOK(_file->CheckBreak()) + } + + CBenchInfo info; + progressInfoSpec.SetFinishTime(info); + + info.UnpackSize = 0; + info.PackSize = 0; + info.NumIterations = 1; + + const UInt64 numCommands = (UInt64)numIterations * numIterations2 * numThreads * complexity; + const UInt64 rating = info.GetSpeed(numCommands); + CpuFreqRes = rating / numThreads; + UsageRes = info.GetUsage(); + + if (_file) + { + PrintResults(_file, info, + 0, // weight + rating, + showFreq, showFreq ? (specifiedFreq != 0 ? specifiedFreq : CpuFreqRes) : 0, NULL); + RINOK(_file->CheckBreak()) + } + + return S_OK; +} + + + +static HRESULT CrcBench( + DECL_EXTERNAL_CODECS_LOC_VARS + UInt64 complexInCommands, + UInt32 numThreads, + const size_t bufferSize, + const Byte *fileData, + + UInt64 &speed, + UInt64 &usage, + + UInt32 complexity, unsigned benchWeight, + const UInt32 *checkSum, + const COneMethodInfo &method, + IBenchPrintCallback *_file, + #ifndef Z7_ST + const CAffinityMode *affinityMode, + #endif + bool showRating, + CTotalBenchRes *encodeRes, + bool showFreq, UInt64 cpuFreq) +{ + if (numThreads == 0) + numThreads = 1; + + #ifdef Z7_ST + numThreads = 1; + #endif + + const AString &methodName = method.MethodName; + // methodName.RemoveChar(L'-'); + CMethodId hashID; + if (!FindHashMethod( + EXTERNAL_CODECS_LOC_VARS + methodName, hashID)) + return E_NOTIMPL; + + /* + // if will generate random data in each thread, instead of global data + CMidAlignedBuffer buffer; + if (!fileData) + { + ALLOC_WITH_HRESULT(&buffer, bufferSize) + RandGen(buffer, bufferSize); + fileData = buffer; + } + */ + + const size_t bsize = (bufferSize == 0 ? 1 : bufferSize); + UInt64 numIterations = complexInCommands * k_Hash_Complex_Mult / complexity / bsize; + if (numIterations == 0) + numIterations = 1; + + CBenchInfoCalc progressInfoSpec; + CBenchInfo info; + + #ifndef Z7_ST + bool mtEncMode = (numThreads > 1) || affinityMode->NeedAffinity(); + + if (mtEncMode) + { + CCrcThreads threads; + threads.Items = new CCrcInfo[numThreads]; + { + WRes wres = threads.Common.StartEvent.Create(); + if (wres != 0) + return HRESULT_FROM_WIN32(wres); + threads.NeedClose = true; + } + + UInt32 i; + for (i = 0; i < numThreads; i++) + { + CCrcInfo &ci = threads.Items[i]; + AString name; + RINOK(CreateHasher(EXTERNAL_CODECS_LOC_VARS hashID, name, ci.Hasher)) + if (!ci.Hasher) + return E_NOTIMPL; + CMyComPtr scp; + ci.Hasher.QueryInterface(IID_ICompressSetCoderProperties, &scp); + if (scp) + { + RINOK(method.SetCoderProps(scp)) + } + + ci.Callback = _file; + ci.Data = fileData; + ci.NumIterations = numIterations; + ci.Size = bufferSize; + ci.CheckSumDefined = false; + if (checkSum) + { + ci.CheckSum = *checkSum; + ci.CheckSumDefined = true; + } + + #ifdef USE_ALLOCA + ci.AllocaSize = BENCH_ALLOCA_VALUE(i); + #endif + } + + for (i = 0; i < numThreads; i++) + { + CCrcInfo &ci = threads.Items[i]; + ci.ThreadIndex = i; + ci.Common = &threads.Common; + ci.AffinityMode = *affinityMode; + HRESULT hres = ci.CreateThread(); + if (ci.Thread.IsCreated()) + threads.NumThreads++; + if (hres != 0) + return hres; + } + + for (i = 0; i < numThreads; i++) + { + CCrcInfo &ci = threads.Items[i]; + WRes wres = ci.ReadyEvent.Lock(); + if (wres != 0) + return HRESULT_FROM_WIN32(wres); + RINOK(ci.Res) + } + + progressInfoSpec.SetStartTime(); + + WRes wres = threads.StartAndWait(); + if (wres != 0) + return HRESULT_FROM_WIN32(wres); + + progressInfoSpec.SetFinishTime(info); + + for (i = 0; i < numThreads; i++) + { + RINOK(threads.Items[i].Res) + if (i != 0) + if (threads.Items[i].CheckSum_Res != + threads.Items[i - 1].CheckSum_Res) + return S_FALSE; + } + } + else + #endif + { + CMyComPtr hasher; + AString name; + RINOK(CreateHasher(EXTERNAL_CODECS_LOC_VARS hashID, name, hasher)) + if (!hasher) + return E_NOTIMPL; + CMyComPtr scp; + hasher.QueryInterface(IID_ICompressSetCoderProperties, &scp); + if (scp) + { + RINOK(method.SetCoderProps(scp)) + } + CCrcInfo_Base crcib; + crcib.CreateLocalBuf = false; + RINOK(crcib.Generate(fileData, bufferSize)) + progressInfoSpec.SetStartTime(); + RINOK(crcib.CrcProcess(numIterations, checkSum, hasher, _file)) + progressInfoSpec.SetFinishTime(info); + } + + + UInt64 unpSize = numIterations * bufferSize; + UInt64 unpSizeThreads = unpSize * numThreads; + info.UnpackSize = unpSizeThreads; + info.PackSize = unpSizeThreads; + info.NumIterations = 1; + + if (_file) + { + if (showRating) + { + UInt64 unpSizeThreads2 = unpSizeThreads; + if (unpSizeThreads2 == 0) + unpSizeThreads2 = numIterations * 1 * numThreads; + const UInt64 numCommands = unpSizeThreads2 * complexity / 256; + const UInt64 rating = info.GetSpeed(numCommands); + PrintResults(_file, info, + benchWeight, rating, + showFreq, cpuFreq, encodeRes); + } + RINOK(_file->CheckBreak()) + } + + speed = info.GetSpeed(unpSizeThreads); + usage = info.GetUsage(); + + return S_OK; +} + + + +static HRESULT TotalBench_Hash( + DECL_EXTERNAL_CODECS_LOC_VARS + const COneMethodInfo &methodMask, + UInt64 complexInCommands, + UInt32 numThreads, + size_t bufSize, + const Byte *fileData, + IBenchPrintCallback *printCallback, CBenchCallbackToPrint *callback, + #ifndef Z7_ST + const CAffinityMode *affinityMode, + #endif + CTotalBenchRes *encodeRes, + bool showFreq, UInt64 cpuFreq) +{ + for (unsigned i = 0; i < Z7_ARRAY_SIZE(g_Hash); i++) + { + const CBenchHash &bench = g_Hash[i]; + if (!DoesWildcardMatchName_NoCase(methodMask.MethodName, bench.Name)) + continue; + PrintLeft(*callback->_file, bench.Name, kFieldSize_Name); + // callback->BenchProps.DecComplexUnc = bench.DecComplexUnc; + // callback->BenchProps.DecComplexCompr = bench.DecComplexCompr; + // callback->BenchProps.EncComplex = bench.EncComplex; + + COneMethodInfo method; + NCOM::CPropVariant propVariant; + propVariant = bench.Name; + RINOK(method.ParseMethodFromPROPVARIANT(UString(), propVariant)) + + UInt64 speed, usage; + + const HRESULT res = CrcBench( + EXTERNAL_CODECS_LOC_VARS + complexInCommands, + numThreads, bufSize, fileData, + speed, usage, + bench.Complex, bench.Weight, + (!fileData && bufSize == (1 << kNumHashDictBits)) ? &bench.CheckSum : NULL, + method, + printCallback, + #ifndef Z7_ST + affinityMode, + #endif + true, // showRating + encodeRes, showFreq, cpuFreq); + if (res == E_NOTIMPL) + { + // callback->Print(" ---"); + } + else + { + RINOK(res) + } + callback->NewLine(); + } + return S_OK; +} + +struct CTempValues +{ + UInt64 *Values; + CTempValues(): Values(NULL) {} + void Alloc(UInt32 num) { Values = new UInt64[num]; } + ~CTempValues() { delete []Values; } +}; + +static void ParseNumberString(const UString &s, NCOM::CPropVariant &prop) +{ + const wchar_t *end; + UInt64 result = ConvertStringToUInt64(s, &end); + if (*end != 0 || s.IsEmpty()) + prop = s; + else if (result <= (UInt32)0xFFFFFFFF) + prop = (UInt32)result; + else + prop = result; +} + + +static bool AreSameMethodNames(const char *fullName, const char *shortName) +{ + return StringsAreEqualNoCase_Ascii(fullName, shortName); +} + + + + +static void Print_Usage_and_Threads(IBenchPrintCallback &f, UInt64 usage, UInt32 threads) +{ + PrintRequirements(f, "usage:", true, usage, "Benchmark threads: ", threads); +} + + +static void Print_Delimiter(IBenchPrintCallback &f) +{ + f.Print(" |"); +} + +static void Print_Pow(IBenchPrintCallback &f, unsigned pow) +{ + char s[16]; + ConvertUInt32ToString(pow, s); + unsigned pos = MyStringLen(s); + s[pos++] = ':'; + s[pos] = 0; + PrintLeft(f, s, kFieldSize_SmallName); // 4 +} + +static void Bench_BW_Print_Usage_Speed(IBenchPrintCallback &f, + UInt64 usage, UInt64 speed) +{ + PrintUsage(f, usage, kFieldSize_Usage); + PrintNumber(f, speed / 1000000, kFieldSize_CrcSpeed); +} + + +HRESULT Bench( + DECL_EXTERNAL_CODECS_LOC_VARS + IBenchPrintCallback *printCallback, + IBenchCallback *benchCallback, + const CObjectVector &props, + UInt32 numIterations, + bool multiDict, + IBenchFreqCallback *freqCallback) +{ + // for (int y = 0; y < 10000; y++) + if (!CrcInternalTest()) + return E_FAIL; + + UInt32 numCPUs = 1; + size_t ramSize = (size_t)sizeof(size_t) << 29; + + NSystem::CProcessAffinity threadsInfo; + threadsInfo.InitST(); + + #ifndef Z7_ST + + if (!threadsInfo.Get() + || (numCPUs = threadsInfo.GetNumProcessThreads()) == 0) + numCPUs = NSystem::GetNumberOfProcessors(); + // numCPUs : is number of threads assigned to process with affinity, + // or it's total number of threads in all groups, if IsGroupMode == true, and there is default affinity. + + #endif + + // numCPUs = 24; + /* + { + DWORD_PTR mask = (1 << 0); + DWORD_PTR old = SetThreadAffinityMask(GetCurrentThread(), mask); + old = old; + DWORD_PTR old2 = SetThreadAffinityMask(GetCurrentThread(), mask); + old2 = old2; + return 0; + } + */ + + const bool ramSize_Defined = NSystem::GetRamSize(ramSize); + + UInt32 numThreadsSpecified = numCPUs; + bool needSetComplexity = false; + UInt32 testTimeMs = kComplexInMs; + UInt32 startDicLog = 22; + bool startDicLog_Defined = false; + UInt64 specifiedFreq = 0; + bool multiThreadTests = false; + UInt64 complexInCommands = kComplexInCommands; + UInt32 numThreads_Start = 1; + +#ifndef Z7_ST + CAffinityMode affinityMode; +#ifdef _WIN32 + if (threadsInfo.IsGroupMode && threadsInfo.Groups.GroupSizes.Size() > 1) + affinityMode.NumGroups = threadsInfo.Groups.GroupSizes.Size(); +#endif +#endif + + + COneMethodInfo method; + + CMidAlignedBuffer fileDataBuffer; + bool use_fileData = false; + bool isFixedDict = false; + + { + unsigned i; + + if (printCallback) + { + for (i = 0; i < props.Size(); i++) + { + const CProperty &property = props[i]; + printCallback->Print(" "); + printCallback->Print(GetAnsiString(property.Name)); + if (!property.Value.IsEmpty()) + { + printCallback->Print("="); + printCallback->Print(GetAnsiString(property.Value)); + } + } + if (!props.IsEmpty()) + printCallback->NewLine(); + } + + + for (i = 0; i < props.Size(); i++) + { + const CProperty &property = props[i]; + UString name (property.Name); + name.MakeLower_Ascii(); + + if (name.IsEqualTo("file")) + { + if (property.Value.IsEmpty()) + return E_INVALIDARG; + + NFile::NIO::CInFile file; + if (!file.Open(us2fs(property.Value))) + return GetLastError_noZero_HRESULT(); + size_t len; + { + UInt64 len64; + if (!file.GetLength(len64)) + return GetLastError_noZero_HRESULT(); + if (printCallback) + { + printCallback->Print("file size ="); + PrintNumber(*printCallback, len64, 0); + printCallback->NewLine(); + } + len = (size_t)len64; + if (len != len64) + return E_INVALIDARG; + } + + // (len == 0) is allowed. Also it's allowed if Alloc(0) returns NULL here + + ALLOC_WITH_HRESULT(&fileDataBuffer, len) + use_fileData = true; + + { + size_t processed; + if (!file.ReadFull((Byte *)fileDataBuffer, len, processed)) + return GetLastError_noZero_HRESULT(); + if (processed != len) + return E_FAIL; + } + continue; + } + + NCOM::CPropVariant propVariant; + if (!property.Value.IsEmpty()) + ParseNumberString(property.Value, propVariant); + + if (name.IsEqualTo("time")) + { + RINOK(ParsePropToUInt32(UString(), propVariant, testTimeMs)) + needSetComplexity = true; + testTimeMs *= 1000; + continue; + } + + if (name.IsEqualTo("timems")) + { + RINOK(ParsePropToUInt32(UString(), propVariant, testTimeMs)) + needSetComplexity = true; + continue; + } + + if (name.IsEqualTo("tic")) + { + UInt32 v; + RINOK(ParsePropToUInt32(UString(), propVariant, v)) + if (v >= 64) + return E_INVALIDARG; + complexInCommands = (UInt64)1 << v; + continue; + } + + const bool isCurrent_fixedDict = name.IsEqualTo("df"); + if (isCurrent_fixedDict) + isFixedDict = true; + if (isCurrent_fixedDict || name.IsEqualTo("ds")) + { + RINOK(ParsePropToUInt32(UString(), propVariant, startDicLog)) + if (startDicLog > 32) + return E_INVALIDARG; + startDicLog_Defined = true; + continue; + } + + if (name.IsEqualTo("mts")) + { + RINOK(ParsePropToUInt32(UString(), propVariant, numThreads_Start)) + continue; + } + + if (name.IsEqualTo("af")) + { + UInt32 bundle; + RINOK(ParsePropToUInt32(UString(), propVariant, bundle)) + if (bundle > 0 && bundle < numCPUs) + { + #ifndef Z7_ST + affinityMode.SetLevels(numCPUs, 2); + affinityMode.NumBundleThreads = bundle; + #endif + } + continue; + } + + if (name.IsEqualTo("freq")) + { + UInt32 freq32 = 0; + RINOK(ParsePropToUInt32(UString(), propVariant, freq32)) + if (freq32 == 0) + return E_INVALIDARG; + specifiedFreq = (UInt64)freq32 * 1000000; + + if (printCallback) + { + printCallback->Print("freq="); + PrintNumber(*printCallback, freq32, 0); + printCallback->NewLine(); + } + + continue; + } + + if (name.IsPrefixedBy_Ascii_NoCase("mt")) + { + const UString s = name.Ptr(2); + if (s.IsEqualTo("*") + || (s.IsEmpty() + && propVariant.vt == VT_BSTR + && StringsAreEqual_Ascii(propVariant.bstrVal, "*"))) + { + multiThreadTests = true; + continue; + } + #ifndef Z7_ST + RINOK(ParseMtProp(s, propVariant, numCPUs, numThreadsSpecified)) + #endif + continue; + } + + RINOK(method.ParseMethodFromPROPVARIANT(name, propVariant)) + } + } + + if (printCallback) + { + AString s; + +#if 1 || !defined(Z7_MSC_VER_ORIGINAL) || (Z7_MSC_VER_ORIGINAL >= 1900) + s += "Compiler: "; + GetCompiler(s); + printCallback->Print(s); + printCallback->NewLine(); + s.Empty(); +#endif + + GetSystemInfoText(s); + printCallback->Print(s); + printCallback->NewLine(); + } + + if (printCallback) + { + printCallback->Print("1T CPU Freq (MHz):"); + } + + if (printCallback || freqCallback) + { + UInt64 numMilCommands = 1 << 6; + if (specifiedFreq != 0) + { + while (numMilCommands > 1 && specifiedFreq < (numMilCommands * 1000000)) + numMilCommands >>= 1; + } + + for (int jj = 0;; jj++) + { + if (printCallback) + RINOK(printCallback->CheckBreak()) + + UInt64 start = ::GetTimeCount(); + UInt32 sum = (UInt32)start; + sum = CountCpuFreq(sum, (UInt32)(numMilCommands * 1000000 / kNumFreqCommands), g_BenchCpuFreqTemp); + if (sum == 0xF1541213) + if (printCallback) + printCallback->Print(""); + const UInt64 realDelta = ::GetTimeCount() - start; + start = realDelta; + if (start == 0) + start = 1; + if (start > (UInt64)1 << 61) + start = 1; + const UInt64 freq = GetFreq(); + // mips is constant in some compilers + const UInt64 hzVal = MyMultDiv64(numMilCommands * 1000000, freq, start); + const UInt64 mipsVal = numMilCommands * freq / start; + if (printCallback) + { + if (realDelta == 0) + { + printCallback->Print(" -"); + } + else + { + // PrintNumber(*printCallback, start, 0); + PrintNumber(*printCallback, mipsVal, 5); + } + } + if (freqCallback) + { + RINOK(freqCallback->AddCpuFreq(1, hzVal, kBenchmarkUsageMult)) + } + + if (jj >= 1) + { + bool needStop = (numMilCommands >= (1 << + #ifdef _DEBUG + 7 + #else + 11 + #endif + )); + if (start >= freq * 16) + { + printCallback->Print(" (Cmplx)"); + if (!freqCallback) // we don't want complexity change for old gui lzma benchmark + { + needSetComplexity = true; + } + needStop = true; + } + if (needSetComplexity) + SetComplexCommandsMs(testTimeMs, false, mipsVal * 1000000, complexInCommands); + if (needStop) + break; + numMilCommands <<= 1; + } + } + if (freqCallback) + { + RINOK(freqCallback->FreqsFinished(1)) + } + } + + if (printCallback || freqCallback) + for (unsigned test = 0; test < 3; test++) + { + if (numThreadsSpecified < 2) + { + // if (test == 1) + break; + } + if (test == 2 && numThreadsSpecified <= numCPUs) + break; + if (printCallback) + printCallback->NewLine(); + + /* it can show incorrect frequency for HT threads. */ + + UInt32 numThreads = numThreadsSpecified; + if (test < 2) + { + if (numThreads >= numCPUs) + numThreads = numCPUs; + if (test == 0) + numThreads /= 2; + } + if (numThreads < 1) + numThreads = 1; + + if (printCallback) + { + char s[128]; + ConvertUInt64ToString(numThreads, s); + printCallback->Print(s); + printCallback->Print("T CPU Freq (MHz):"); + } + UInt64 numMilCommands = 1 << + #ifdef _DEBUG + 7; + #else + 10; + #endif + + if (specifiedFreq != 0) + { + while (numMilCommands > 1 && specifiedFreq < (numMilCommands * 1000000)) + numMilCommands >>= 1; + } + + // for (int jj = 0;; jj++) + for (;;) + { + if (printCallback) + RINOK(printCallback->CheckBreak()) + + { + // PrintLeft(f, "CPU", kFieldSize_Name); + + // UInt32 resVal; + + CFreqBench fb; + fb.complexInCommands = numMilCommands * 1000000; + fb.numThreads = numThreads; + // showFreq; + // fb.showFreq = (freqTest == kNumCpuTests - 1 || specifiedFreq != 0); + fb.showFreq = true; + fb.specifiedFreq = 1; + + const HRESULT res = fb.FreqBench(NULL /* printCallback */ + #ifndef Z7_ST + , &affinityMode + #endif + ); + RINOK(res) + + if (freqCallback) + { + RINOK(freqCallback->AddCpuFreq(numThreads, fb.CpuFreqRes, fb.UsageRes)) + } + + if (printCallback) + { + /* + if (realDelta == 0) + { + printCallback->Print(" -"); + } + else + */ + { + // PrintNumber(*printCallback, start, 0); + PrintUsage(*printCallback, fb.UsageRes, 3); + printCallback->Print("%"); + PrintNumber(*printCallback, fb.CpuFreqRes / 1000000, 0); + printCallback->Print(" "); + + // PrintNumber(*printCallback, fb.UsageRes, 5); + } + } + } + // if (jj >= 1) + { + const bool needStop = (numMilCommands >= (1 << + #ifdef _DEBUG + 7 + #else + 11 + #endif + )); + if (needStop) + break; + numMilCommands <<= 1; + } + } + if (freqCallback) + { + RINOK(freqCallback->FreqsFinished(numThreads)) + } + } + + + if (printCallback) + { + printCallback->NewLine(); + printCallback->NewLine(); + PrintRequirements(*printCallback, "size: ", ramSize_Defined, ramSize, "CPU hardware threads:", numCPUs); + printCallback->Print(GetProcessThreadsInfo(threadsInfo)); + printCallback->NewLine(); + } + + if (numThreadsSpecified < 1 || numThreadsSpecified > kNumThreadsMax) + return E_INVALIDARG; + + UInt64 dict = (UInt64)1 << startDicLog; + const bool dictIsDefined = (isFixedDict || method.Get_DicSize(dict)); + + const unsigned level = method.GetLevel(); + + AString &methodName = method.MethodName; + const AString original_MethodName = methodName; + if (methodName.IsEmpty()) + methodName = "LZMA"; + + if (benchCallback) + { + CBenchProps benchProps; + benchProps.SetLzmaCompexity(); + const UInt64 dictSize = method.Get_Lzma_DicSize(); + + size_t uncompressedDataSize; + if (use_fileData) + { + uncompressedDataSize = fileDataBuffer.Size(); + } + else + { + uncompressedDataSize = kAdditionalSize + (size_t)dictSize; + if (uncompressedDataSize < dictSize) + return E_INVALIDARG; + } + + return MethodBench( + EXTERNAL_CODECS_LOC_VARS + complexInCommands, + #ifndef Z7_ST + true, numThreadsSpecified, + &affinityMode, + #endif + method, + uncompressedDataSize, (const Byte *)fileDataBuffer, + kOldLzmaDictBits, printCallback, benchCallback, &benchProps); + } + + if (methodName.IsEqualTo_Ascii_NoCase("CRC")) + methodName = "crc32"; + + CMethodId hashID; + const bool isHashMethod = FindHashMethod(EXTERNAL_CODECS_LOC_VARS methodName, hashID); + int codecIndex = -1; + bool isFilter = false; + if (!isHashMethod) + { + UInt32 numStreams; + codecIndex = FindMethod_Index(EXTERNAL_CODECS_LOC_VARS original_MethodName, + true, // encode + hashID, numStreams, isFilter); + // we can allow non filter for BW tests + if (!isFilter) codecIndex = -1; + } + + CBenchCallbackToPrint callback; + callback.Init(); + callback._file = printCallback; + + if (isHashMethod || codecIndex != -1) + { + if (!printCallback) + return S_FALSE; + IBenchPrintCallback &f = *printCallback; + + UInt64 dict64 = dict; + if (!dictIsDefined) + dict64 = (1 << 27); + if (use_fileData) + { + if (!dictIsDefined) + dict64 = fileDataBuffer.Size(); + else if (dict64 > fileDataBuffer.Size()) + dict64 = fileDataBuffer.Size(); + } + + for (;;) + { + const int index = method.FindProp(NCoderPropID::kDictionarySize); + if (index < 0) + break; + method.Props.Delete((unsigned)index); + } + + // methodName.RemoveChar(L'-'); + Int32 complexity = 16 * k_Hash_Complex_Mult; // for unknown hash method + const UInt32 *checkSum = NULL; + int benchIndex = -1; + + if (isHashMethod) + { + for (unsigned i = 0; i < Z7_ARRAY_SIZE(g_Hash); i++) + { + const CBenchHash &h = g_Hash[i]; + AString benchMethod (h.Name); + AString benchProps; + const int propPos = benchMethod.Find(':'); + if (propPos >= 0) + { + benchProps = benchMethod.Ptr((unsigned)(propPos + 1)); + benchMethod.DeleteFrom((unsigned)propPos); + } + + if (AreSameMethodNames(benchMethod, methodName)) + { + const bool sameProps = method.PropsString.IsEqualTo_Ascii_NoCase(benchProps); + /* + bool isMainMethod = method.PropsString.IsEmpty(); + if (isMainMethod) + isMainMethod = !checkSum + || (benchMethod.IsEqualTo_Ascii_NoCase("crc32") && benchProps.IsEqualTo_Ascii_NoCase("8")); + if (sameProps || isMainMethod) + */ + { + complexity = (Int32)h.Complex; + checkSum = &h.CheckSum; + if (sameProps) + break; + /* + if property. is not specified, we use the complexity + for latest fastest method (crc32:64) + */ + } + } + } + // if (!checkSum) return E_NOTIMPL; + } + else + { + for (unsigned i = 0; i < Z7_ARRAY_SIZE(g_Bench); i++) + { + const CBenchMethod &bench = g_Bench[i]; + AString benchMethod (bench.Name); + AString benchProps; + const int propPos = benchMethod.Find(':'); + if (propPos >= 0) + { + benchProps = benchMethod.Ptr((unsigned)(propPos + 1)); + benchMethod.DeleteFrom((unsigned)propPos); + } + + if (AreSameMethodNames(benchMethod, methodName)) + { + const bool sameProps = method.PropsString.IsEqualTo_Ascii_NoCase(benchProps); + // bool isMainMethod = method.PropsString.IsEmpty(); + // if (sameProps || isMainMethod) + { + benchIndex = (int)i; + if (sameProps) + break; + } + } + } + // if (benchIndex < 0) return E_NOTIMPL; + } + + { + /* we count usage only for crc and filter. non-filters are not supported */ + UInt64 usage = (1 << 20); + UInt64 bufSize = dict64; + UInt32 numBlocks = isHashMethod ? 1 : 3; + if (use_fileData) + { + usage += fileDataBuffer.Size(); + if (bufSize > fileDataBuffer.Size()) + bufSize = fileDataBuffer.Size(); + if (isHashMethod) + { + numBlocks = 0; + #ifndef Z7_ST + if (numThreadsSpecified != 1) + numBlocks = (k_Crc_CreateLocalBuf_For_File ? 1 : 0); + #endif + } + } + usage += numThreadsSpecified * bufSize * numBlocks; + Print_Usage_and_Threads(f, usage, numThreadsSpecified); + } + + CUIntVector numThreadsVector; + { + unsigned nt = numThreads_Start; + for (;;) + { + if (nt > numThreadsSpecified) + break; + numThreadsVector.Add(nt); + const unsigned next = nt * 2; + const UInt32 ntHalf= numThreadsSpecified / 2; + if (ntHalf > nt && ntHalf < next) + numThreadsVector.Add(ntHalf); + if (numThreadsSpecified > nt && numThreadsSpecified < next) + numThreadsVector.Add(numThreadsSpecified); + nt = next; + } + } + + unsigned numColumns = isHashMethod ? 1 : 2; + CTempValues speedTotals; + CTempValues usageTotals; + { + const unsigned numItems = numThreadsVector.Size() * numColumns; + speedTotals.Alloc(numItems); + usageTotals.Alloc(numItems); + for (unsigned i = 0; i < numItems; i++) + { + speedTotals.Values[i] = 0; + usageTotals.Values[i] = 0; + } + } + + f.NewLine(); + for (unsigned line = 0; line < 3; line++) + { + f.NewLine(); + f.Print(line == 0 ? "THRD" : line == 1 ? " " : "Size"); + FOR_VECTOR (ti, numThreadsVector) + { + if (ti != 0) + Print_Delimiter(f); + if (line == 0) + { + PrintSpaces(f, (kFieldSize_CrcSpeed + kFieldSize_Usage + 2) * (numColumns - 1)); + PrintNumber(f, numThreadsVector[ti], 1 + kFieldSize_Usage + kFieldSize_CrcSpeed); + } + else + { + for (unsigned c = 0; c < numColumns; c++) + { + PrintRight(f, line == 1 ? "Usage" : "%", kFieldSize_Usage + 1); + PrintRight(f, line == 1 ? "BW" : "MB/s", kFieldSize_CrcSpeed + 1); + } + } + } + } + f.NewLine(); + + UInt64 numSteps = 0; + + // for (UInt32 iter = 0; iter < numIterations; iter++) + // { + unsigned pow = 10; // kNumHashDictBits + if (startDicLog_Defined) + pow = startDicLog; + + // #define NUM_SUB_BITS 2 + // pow <<= NUM_SUB_BITS; + for (;; pow++) + { + const UInt64 bufSize = (UInt64)1 << pow; + // UInt64 bufSize = (UInt64)1 << (pow >> NUM_SUB_BITS); + // bufSize += ((UInt64)pow & ((1 << NUM_SUB_BITS) - 1)) << ((pow >> NUM_SUB_BITS) - NUM_SUB_BITS); + + size_t dataSize = fileDataBuffer.Size(); + if (dataSize > bufSize || !use_fileData) + dataSize = (size_t)bufSize; + + for (UInt32 iter = 0; iter < numIterations; iter++) + { + Print_Pow(f, pow); + // PrintNumber(f, bufSize >> 10, 4); + + FOR_VECTOR (ti, numThreadsVector) + { + RINOK(f.CheckBreak()) + const UInt32 numThreads = numThreadsVector[ti]; + if (isHashMethod) + { + UInt64 speed = 0; + UInt64 usage = 0; + const HRESULT res = CrcBench(EXTERNAL_CODECS_LOC_VARS complexInCommands, + numThreads, + dataSize, (const Byte *)fileDataBuffer, + speed, usage, + (UInt32)complexity, + 1, // benchWeight, + (pow == kNumHashDictBits && !use_fileData) ? checkSum : NULL, + method, + &f, + #ifndef Z7_ST + &affinityMode, + #endif + false, // showRating + NULL, false, 0); + RINOK(res) + + if (ti != 0) + Print_Delimiter(f); + + Bench_BW_Print_Usage_Speed(f, usage, speed); + speedTotals.Values[ti] += speed; + usageTotals.Values[ti] += usage; + } + else + { + { + unsigned keySize = 32; + if (IsString1PrefixedByString2(methodName, "AES128")) keySize = 16; + else if (IsString1PrefixedByString2(methodName, "AES192")) keySize = 24; + callback.BenchProps.KeySize = keySize; + } + + COneMethodInfo method2 = method; + unsigned bench_DictBits; + + if (benchIndex >= 0) + { + const CBenchMethod &bench = g_Bench[benchIndex]; + callback.BenchProps.EncComplex = bench.EncComplex; + callback.BenchProps.DecComplexUnc = bench.DecComplexUnc; + callback.BenchProps.DecComplexCompr = bench.DecComplexCompr; + bench_DictBits = bench.DictBits; + // bench_DictBits = kOldLzmaDictBits; = 32 default : for debug + } + else + { + bench_DictBits = kOldLzmaDictBits; // = 32 default + if (isFilter) + { + const unsigned k_UnknownCoderComplexity = 4; + callback.BenchProps.EncComplex = k_UnknownCoderComplexity; + callback.BenchProps.DecComplexUnc = k_UnknownCoderComplexity; + } + else + { + callback.BenchProps.EncComplex = 1 << 10; + callback.BenchProps.DecComplexUnc = 1 << 6; + } + callback.BenchProps.DecComplexCompr = 0; + } + callback.NeedPrint = false; + + if (StringsAreEqualNoCase_Ascii(method2.MethodName, "LZMA")) + { + const NCOM::CPropVariant propVariant = (UInt32)pow; + RINOK(method2.ParseMethodFromPROPVARIANT((UString)"d", propVariant)) + } + + const HRESULT res = MethodBench( + EXTERNAL_CODECS_LOC_VARS + complexInCommands, + #ifndef Z7_ST + false, // oldLzmaBenchMode + numThreadsVector[ti], + &affinityMode, + #endif + method2, + dataSize, (const Byte *)fileDataBuffer, + bench_DictBits, + printCallback, + &callback, + &callback.BenchProps); + RINOK(res) + + if (ti != 0) + Print_Delimiter(f); + + for (unsigned i = 0; i < 2; i++) + { + const CBenchInfo &bi = callback.BenchInfo_Results[i]; + const UInt64 usage = bi.GetUsage(); + const UInt64 speed = bi.GetUnpackSizeSpeed(); + usageTotals.Values[ti * 2 + i] += usage; + speedTotals.Values[ti * 2 + i] += speed; + Bench_BW_Print_Usage_Speed(f, usage, speed); + } + } + } + + f.NewLine(); + numSteps++; + } + if (dataSize >= dict64) + break; + } + + if (numSteps != 0) + { + f.Print("Avg:"); + for (unsigned ti = 0; ti < numThreadsVector.Size(); ti++) + { + if (ti != 0) + Print_Delimiter(f); + for (unsigned i = 0; i < numColumns; i++) + Bench_BW_Print_Usage_Speed(f, + usageTotals.Values[ti * numColumns + i] / numSteps, + speedTotals.Values[ti * numColumns + i] / numSteps); + } + f.NewLine(); + } + + return S_OK; + } + + bool use2Columns = false; + + bool totalBenchMode = false; + bool onlyHashBench = false; + if (methodName.IsEqualTo_Ascii_NoCase("hash")) + { + onlyHashBench = true; + methodName = "*"; + totalBenchMode = true; + } + else if (methodName.Find('*') >= 0) + totalBenchMode = true; + + // ---------- Threads loop ---------- + for (unsigned threadsPassIndex = 0; threadsPassIndex < 3; threadsPassIndex++) + { + + UInt32 numThreads = numThreadsSpecified; + + if (!multiThreadTests) + { + if (threadsPassIndex != 0) + break; + } + else + { + numThreads = 1; + if (threadsPassIndex != 0) + { + if (numCPUs < 2) + break; + numThreads = numCPUs; + if (threadsPassIndex == 1) + { + if (numCPUs >= 4) + numThreads = numCPUs / 2; + } + else if (numCPUs < 4) + break; + } + } + + IBenchPrintCallback &f = *printCallback; + + if (threadsPassIndex > 0) + { + f.NewLine(); + f.NewLine(); + } + + if (!dictIsDefined && !onlyHashBench) + { + // we use dicSizeLog and dicSizeLog_Main for data size. + // also we use it to reduce dictionary size of LZMA encoder via NCoderPropID::kReduceSize. + const unsigned dicSizeLog_Main = (totalBenchMode ? 24 : 25); + unsigned dicSizeLog = dicSizeLog_Main; + + #ifdef UNDER_CE + dicSizeLog = (UInt64)1 << 20; + #endif + + if (ramSize_Defined) + for (; dicSizeLog > kBenchMinDicLogSize; dicSizeLog--) + if (GetBenchMemoryUsage(numThreads, (int)level, ((UInt64)1 << dicSizeLog), totalBenchMode) + (8 << 20) <= ramSize) + break; + + dict = (UInt64)1 << dicSizeLog; + + if (totalBenchMode && dicSizeLog != dicSizeLog_Main) + { + f.Print("Dictionary reduced to: "); + PrintNumber(f, dicSizeLog, 1); + f.NewLine(); + } + } + + Print_Usage_and_Threads(f, + onlyHashBench ? + GetBenchMemoryUsage_Hash(numThreads, dict) : + GetBenchMemoryUsage(numThreads, (int)level, dict, totalBenchMode), + numThreads); + + f.NewLine(); + + f.NewLine(); + + if (totalBenchMode) + { + callback.NameFieldSize = kFieldSize_Name; + use2Columns = false; + } + else + { + callback.NameFieldSize = kFieldSize_SmallName; + use2Columns = true; + } + callback.Use2Columns = use2Columns; + + bool showFreq = false; + UInt64 cpuFreq = 0; + + if (totalBenchMode) + { + showFreq = true; + } + + unsigned fileldSize = kFieldSize_TotalSize; + if (showFreq) + fileldSize += kFieldSize_EUAndEffec; + + if (use2Columns) + { + PrintSpaces(f, callback.NameFieldSize); + PrintRight(f, "Compressing", fileldSize); + f.Print(kSep); + PrintRight(f, "Decompressing", fileldSize); + } + f.NewLine(); + PrintLeft(f, totalBenchMode ? "Method" : "Dict", callback.NameFieldSize); + + int j; + + for (j = 0; j < 2; j++) + { + PrintRight(f, "Speed", kFieldSize_Speed + 1); + PrintRight(f, "Usage", kFieldSize_Usage + 1); + PrintRight(f, "R/U", kFieldSize_RU + 1); + PrintRight(f, "Rating", kFieldSize_Rating + 1); + if (showFreq) + { + PrintRight(f, "E/U", kFieldSize_EU + 1); + PrintRight(f, "Effec", kFieldSize_Effec + 1); + } + if (!use2Columns) + break; + if (j == 0) + f.Print(kSep); + } + + f.NewLine(); + PrintSpaces(f, callback.NameFieldSize); + + for (j = 0; j < 2; j++) + { + PrintRight(f, "KiB/s", kFieldSize_Speed + 1); + PrintRight(f, "%", kFieldSize_Usage + 1); + PrintRight(f, "MIPS", kFieldSize_RU + 1); + PrintRight(f, "MIPS", kFieldSize_Rating + 1); + if (showFreq) + { + PrintRight(f, "%", kFieldSize_EU + 1); + PrintRight(f, "%", kFieldSize_Effec + 1); + } + if (!use2Columns) + break; + if (j == 0) + f.Print(kSep); + } + + f.NewLine(); + f.NewLine(); + + if (specifiedFreq != 0) + cpuFreq = specifiedFreq; + + // bool showTotalSpeed = false; + + if (totalBenchMode) + { + for (UInt32 i = 0; i < numIterations; i++) + { + if (i != 0) + printCallback->NewLine(); + + const unsigned kNumCpuTests = 3; + for (unsigned freqTest = 0; freqTest < kNumCpuTests; freqTest++) + { + PrintLeft(f, "CPU", kFieldSize_Name); + + // UInt32 resVal; + + CFreqBench fb; + fb.complexInCommands = complexInCommands; + fb.numThreads = numThreads; + // showFreq; + fb.showFreq = (freqTest == kNumCpuTests - 1 || specifiedFreq != 0); + fb.specifiedFreq = specifiedFreq; + + const HRESULT res = fb.FreqBench(printCallback + #ifndef Z7_ST + , &affinityMode + #endif + ); + RINOK(res) + + cpuFreq = fb.CpuFreqRes; + callback.NewLine(); + + if (specifiedFreq != 0) + cpuFreq = specifiedFreq; + + if (testTimeMs >= 1000) + if (freqTest == kNumCpuTests - 1) + { + // SetComplexCommandsMs(testTimeMs, specifiedFreq != 0, cpuFreq, complexInCommands); + } + } + callback.NewLine(); + + // return S_OK; // change it + + callback.SetFreq(true, cpuFreq); + + if (!onlyHashBench) + { + size_t dataSize = (size_t)dict; + if (use_fileData) + { + dataSize = fileDataBuffer.Size(); + if (dictIsDefined && dataSize > dict) + dataSize = (size_t)dict; + } + + const HRESULT res = TotalBench(EXTERNAL_CODECS_LOC_VARS + method, complexInCommands, + #ifndef Z7_ST + numThreads, + &affinityMode, + #endif + dictIsDefined || use_fileData, // forceUnpackSize + dataSize, + (const Byte *)fileDataBuffer, + printCallback, &callback); + RINOK(res) + } + + { + size_t dataSize = (size_t)1 << kNumHashDictBits; + if (dictIsDefined) + { + dataSize = (size_t)dict; + if (dataSize != dict) + return E_OUTOFMEMORY; + } + if (use_fileData) + { + dataSize = fileDataBuffer.Size(); + if (dictIsDefined && dataSize > dict) + dataSize = (size_t)dict; + } + + const HRESULT res = TotalBench_Hash(EXTERNAL_CODECS_LOC_VARS + method, complexInCommands, + numThreads, + dataSize, (const Byte *)fileDataBuffer, + printCallback, &callback, + #ifndef Z7_ST + &affinityMode, + #endif + &callback.EncodeRes, true, cpuFreq); + RINOK(res) + } + + callback.NewLine(); + { + PrintLeft(f, "CPU", kFieldSize_Name); + + CFreqBench fb; + fb.complexInCommands = complexInCommands; + fb.numThreads = numThreads; + // showFreq; + fb.showFreq = (specifiedFreq != 0); + fb.specifiedFreq = specifiedFreq; + + const HRESULT res = fb.FreqBench(printCallback + #ifndef Z7_ST + , &affinityMode + #endif + ); + RINOK(res) + callback.NewLine(); + } + } + } + else + { + needSetComplexity = true; + if (!methodName.IsEqualTo_Ascii_NoCase("LZMA")) + { + unsigned i; + for (i = 0; i < Z7_ARRAY_SIZE(g_Bench); i++) + { + const CBenchMethod &h = g_Bench[i]; + AString benchMethod (h.Name); + AString benchProps; + const int propPos = benchMethod.Find(':'); + if (propPos >= 0) + { + benchProps = benchMethod.Ptr((unsigned)(propPos + 1)); + benchMethod.DeleteFrom((unsigned)propPos); + } + + if (AreSameMethodNames(benchMethod, methodName)) + { + if (benchProps.IsEmpty() + || (benchProps.IsEqualTo("x5") && method.PropsString.IsEmpty()) + || method.PropsString.IsPrefixedBy_Ascii_NoCase(benchProps)) + { + callback.BenchProps.EncComplex = h.EncComplex; + callback.BenchProps.DecComplexCompr = h.DecComplexCompr; + callback.BenchProps.DecComplexUnc = h.DecComplexUnc; + needSetComplexity = false; + break; + } + } + } + /* + if (i == Z7_ARRAY_SIZE(g_Bench)) + return E_NOTIMPL; + */ + } + if (needSetComplexity) + callback.BenchProps.SetLzmaCompexity(); + + if (startDicLog < kBenchMinDicLogSize) + startDicLog = kBenchMinDicLogSize; + + for (unsigned i = 0; i < numIterations; i++) + { + unsigned pow = (dict < GetDictSizeFromLog(startDicLog)) ? kBenchMinDicLogSize : (unsigned)startDicLog; + if (!multiDict) + pow = 32; + while (GetDictSizeFromLog(pow) > dict && pow > 0) + pow--; + for (; GetDictSizeFromLog(pow) <= dict; pow++) + { + Print_Pow(f, pow); + callback.DictSize = (UInt64)1 << pow; + + COneMethodInfo method2 = method; + + if (StringsAreEqualNoCase_Ascii(method2.MethodName, "LZMA")) + { + // We add dictionary size property. + // method2 can have two different dictionary size properties. + // And last property is main. + NCOM::CPropVariant propVariant = (UInt32)pow; + RINOK(method2.ParseMethodFromPROPVARIANT((UString)"d", propVariant)) + } + + size_t uncompressedDataSize; + if (use_fileData) + { + uncompressedDataSize = fileDataBuffer.Size(); + } + else + { + uncompressedDataSize = (size_t)callback.DictSize; + if (uncompressedDataSize != callback.DictSize) + return E_OUTOFMEMORY; + if (uncompressedDataSize >= (1 << 18)) + uncompressedDataSize += kAdditionalSize; + } + + const HRESULT res = MethodBench( + EXTERNAL_CODECS_LOC_VARS + complexInCommands, + #ifndef Z7_ST + true, numThreads, + &affinityMode, + #endif + method2, + uncompressedDataSize, (const Byte *)fileDataBuffer, + kOldLzmaDictBits, printCallback, &callback, &callback.BenchProps); + f.NewLine(); + RINOK(res) + if (!multiDict) + break; + } + } + } + + PrintChars(f, '-', callback.NameFieldSize + fileldSize); + + if (use2Columns) + { + f.Print(kSep); + PrintChars(f, '-', fileldSize); + } + + f.NewLine(); + + if (use2Columns) + { + PrintLeft(f, "Avr:", callback.NameFieldSize); + PrintTotals(f, showFreq, cpuFreq, !totalBenchMode, callback.EncodeRes); + f.Print(kSep); + PrintTotals(f, showFreq, cpuFreq, !totalBenchMode, callback.DecodeRes); + f.NewLine(); + } + + PrintLeft(f, "Tot:", callback.NameFieldSize); + CTotalBenchRes midRes; + midRes = callback.EncodeRes; + midRes.Update_With_Res(callback.DecodeRes); + + // midRes.SetSum(callback.EncodeRes, callback.DecodeRes); + PrintTotals(f, showFreq, cpuFreq, false, midRes); + f.NewLine(); + + } + return S_OK; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/Bench.h b/src/plugins/7zip/7za/cpp/7zip/UI/Common/Bench.h new file mode 100644 index 00000000..313676dd --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/Bench.h @@ -0,0 +1,121 @@ +// Bench.h + +#ifndef ZIP7_INC_7ZIP_BENCH_H +#define ZIP7_INC_7ZIP_BENCH_H + +#include "../../../Windows/System.h" + +#include "../../Common/CreateCoder.h" +#include "../../UI/Common/Property.h" + +UInt64 Benchmark_GetUsage_Percents(UInt64 usage); + +struct CBenchInfo +{ + UInt64 GlobalTime; + UInt64 GlobalFreq; + UInt64 UserTime; + UInt64 UserFreq; + UInt64 UnpackSize; + UInt64 PackSize; + UInt64 NumIterations; + + /* + during Code(): we track benchInfo only from one thread (theads with index[0]) + NumIterations means number of threads + UnpackSize and PackSize are total sizes of all iterations of current thread + after Code(): + NumIterations means the number of Iterations + UnpackSize and PackSize are total sizes of all threads + */ + + CBenchInfo(): NumIterations(0) {} + + UInt64 GetUsage() const; + UInt64 GetRatingPerUsage(UInt64 rating) const; + UInt64 GetSpeed(UInt64 numUnits) const; + UInt64 GetUnpackSizeSpeed() const { return GetSpeed(UnpackSize * NumIterations); } + + UInt64 Get_UnpackSize_Full() const { return UnpackSize * NumIterations; } + + UInt64 GetRating_LzmaEnc(UInt64 dictSize) const; + UInt64 GetRating_LzmaDec() const; +}; + + +struct CTotalBenchRes +{ + // UInt64 NumIterations1; // for Usage + UInt64 NumIterations2; // for Rating / RPU + + UInt64 Rating; + UInt64 Usage; + UInt64 RPU; + UInt64 Speed; + + void Init() { /* NumIterations1 = 0; */ NumIterations2 = 0; Rating = 0; Usage = 0; RPU = 0; Speed = 0; } + + void SetSum(const CTotalBenchRes &r1, const CTotalBenchRes &r2) + { + Rating = (r1.Rating + r2.Rating); + Usage = (r1.Usage + r2.Usage); + RPU = (r1.RPU + r2.RPU); + Speed = (r1.Speed + r2.Speed); + // NumIterations1 = (r1.NumIterations1 + r2.NumIterations1); + NumIterations2 = (r1.NumIterations2 + r2.NumIterations2); + } + + void Generate_From_BenchInfo(const CBenchInfo &info); + void Mult_For_Weight(unsigned weight); + void Update_With_Res(const CTotalBenchRes &r); +}; + + +const unsigned kBenchMinDicLogSize = 18; + +UInt64 GetBenchMemoryUsage(UInt32 numThreads, int level, UInt64 dictionary, bool totalBench); + +Z7_PURE_INTERFACES_BEGIN +DECLARE_INTERFACE(IBenchCallback) +{ + // virtual HRESULT SetFreq(bool showFreq, UInt64 cpuFreq) = 0; + virtual HRESULT SetEncodeResult(const CBenchInfo &info, bool final) = 0; + virtual HRESULT SetDecodeResult(const CBenchInfo &info, bool final) = 0; +}; + +DECLARE_INTERFACE(IBenchPrintCallback) +{ + virtual void Print(const char *s) = 0; + virtual void NewLine() = 0; + virtual HRESULT CheckBreak() = 0; +}; + +DECLARE_INTERFACE(IBenchFreqCallback) +{ + virtual HRESULT AddCpuFreq(unsigned numThreads, UInt64 freq, UInt64 usage) = 0; + virtual HRESULT FreqsFinished(unsigned numThreads) = 0; +}; +Z7_PURE_INTERFACES_END + +HRESULT Bench( + DECL_EXTERNAL_CODECS_LOC_VARS + IBenchPrintCallback *printCallback, + IBenchCallback *benchCallback, + const CObjectVector &props, + UInt32 numIterations, + bool multiDict, + IBenchFreqCallback *freqCallback = NULL); + +AString GetProcessThreadsInfo(const NWindows::NSystem::CProcessAffinity &ti); + +void GetSysInfo(AString &s1, AString &s2); +void GetCpuName(AString &s); +void AddCpuFeatures(AString &s); + +#ifdef Z7_LARGE_PAGES +void Add_LargePages_String(AString &s); +#else +// #define Add_LargePages_String +#endif + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/CompressCall.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Common/CompressCall.cpp new file mode 100644 index 00000000..1ded1b39 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/CompressCall.cpp @@ -0,0 +1,343 @@ +// CompressCall.cpp + +#include "StdAfx.h" + +#include + +#include "../../../Common/IntToString.h" +#include "../../../Common/MyCom.h" +#include "../../../Common/Random.h" +#include "../../../Common/StringConvert.h" + +#include "../../../Windows/DLL.h" +#include "../../../Windows/ErrorMsg.h" +#include "../../../Windows/FileDir.h" +#include "../../../Windows/FileMapping.h" +#include "../../../Windows/MemoryLock.h" +#include "../../../Windows/ProcessUtils.h" +#include "../../../Windows/Synchronization.h" + +#include "../FileManager/RegistryUtils.h" + +#include "CompressCall.h" + +using namespace NWindows; + +#define MY_TRY_BEGIN try { + +#define MY_TRY_FINISH } \ + catch(...) { ErrorMessageHRESULT(E_FAIL); return E_FAIL; } + +#define MY_TRY_FINISH_VOID } \ + catch(...) { ErrorMessageHRESULT(E_FAIL); } + +#define k7zGui "7zG.exe" + +// 21.07 : we can disable wildcard +// #define ISWITCH_NO_WILDCARD_POSTFIX "w-" +#define ISWITCH_NO_WILDCARD_POSTFIX + +#define kShowDialogSwitch " -ad" +#define kEmailSwitch " -seml." +#define kArchiveTypeSwitch " -t" +#define kIncludeSwitch " -i" ISWITCH_NO_WILDCARD_POSTFIX +#define kArcIncludeSwitches " -an -ai" ISWITCH_NO_WILDCARD_POSTFIX +#define kHashIncludeSwitches kIncludeSwitch +#define kStopSwitchParsing " --" + +extern HWND g_HWND; + +UString GetQuotedString(const UString &s) +{ + UString s2 ('\"'); + s2 += s; + s2.Add_Char('\"'); + return s2; +} + +static void ErrorMessage(LPCWSTR message) +{ + MessageBoxW(g_HWND, message, L"7-Zip", MB_ICONERROR | MB_OK); +} + +static void ErrorMessageHRESULT(HRESULT res, LPCWSTR s = NULL) +{ + UString s2 = NError::MyFormatMessage(res); + if (s) + { + s2.Add_LF(); + s2 += s; + } + ErrorMessage(s2); +} + +static HRESULT Call7zGui(const UString ¶ms, + // LPCWSTR curDir, + bool waitFinish, + NSynchronization::CBaseEvent *event) +{ + UString imageName = fs2us(NWindows::NDLL::GetModuleDirPrefix()); + imageName += k7zGui; + + CProcess process; + const WRes wres = process.Create(imageName, params, NULL); // curDir); + if (wres != 0) + { + const HRESULT hres = HRESULT_FROM_WIN32(wres); + ErrorMessageHRESULT(hres, imageName); + return hres; + } + if (waitFinish) + process.Wait(); + else if (event != NULL) + { + HANDLE handles[] = { process, *event }; + ::WaitForMultipleObjects(Z7_ARRAY_SIZE(handles), handles, FALSE, INFINITE); + } + return S_OK; +} + +static void AddLagePagesSwitch(UString ¶ms) +{ + if (ReadLockMemoryEnable()) + #ifndef UNDER_CE + if (NSecurity::Get_LargePages_RiskLevel() == 0) + #endif + params += " -slp"; +} + +class CRandNameGenerator +{ + CRandom _random; +public: + CRandNameGenerator() { _random.Init(); } + void GenerateName(UString &s, const char *prefix) + { + s += prefix; + s.Add_UInt32((UInt32)(unsigned)_random.Generate()); + } +}; + +static HRESULT CreateMap(const UStringVector &names, + CFileMapping &fileMapping, NSynchronization::CManualResetEvent &event, + UString ¶ms) +{ + size_t totalSize = 1; + { + FOR_VECTOR (i, names) + totalSize += (names[i].Len() + 1); + } + totalSize *= sizeof(wchar_t); + + CRandNameGenerator random; + + UString mappingName; + for (;;) + { + random.GenerateName(mappingName, "7zMap"); + const WRes wres = fileMapping.Create(PAGE_READWRITE, totalSize, GetSystemString(mappingName)); + if (fileMapping.IsCreated() && wres == 0) + break; + if (wres != ERROR_ALREADY_EXISTS) + return HRESULT_FROM_WIN32(wres); + fileMapping.Close(); + } + + UString eventName; + for (;;) + { + random.GenerateName(eventName, "7zEvent"); + const WRes wres = event.CreateWithName(false, GetSystemString(eventName)); + if (event.IsCreated() && wres == 0) + break; + if (wres != ERROR_ALREADY_EXISTS) + return HRESULT_FROM_WIN32(wres); + event.Close(); + } + + params.Add_Char('#'); + params += mappingName; + params.Add_Colon(); + char temp[32]; + ConvertUInt64ToString(totalSize, temp); + params += temp; + + params.Add_Colon(); + params += eventName; + + LPVOID data = fileMapping.Map(FILE_MAP_WRITE, 0, totalSize); + if (!data) + return E_FAIL; + CFileUnmapper unmapper(data); + { + wchar_t *cur = (wchar_t *)data; + *cur++ = 0; // it means wchar_t strings (UTF-16 in WIN32) + FOR_VECTOR (i, names) + { + const UString &s = names[i]; + const unsigned len = s.Len() + 1; + wmemcpy(cur, (const wchar_t *)s, len); + cur += len; + } + } + return S_OK; +} + +HRESULT CompressFiles( + const UString &arcPathPrefix, + const UString &arcName, + const UString &arcType, + bool addExtension, + const UStringVector &names, + bool email, bool showDialog, bool waitFinish) +{ + MY_TRY_BEGIN + UString params ('a'); + + CFileMapping fileMapping; + NSynchronization::CManualResetEvent event; + params += kIncludeSwitch; + RINOK(CreateMap(names, fileMapping, event, params)) + + if (!arcType.IsEmpty()) + { + params += kArchiveTypeSwitch; + params += arcType; + } + + if (email) + params += kEmailSwitch; + + if (showDialog) + params += kShowDialogSwitch; + + AddLagePagesSwitch(params); + + if (arcName.IsEmpty()) + params += " -an"; + + if (addExtension) + params += " -saa"; + else + params += " -sae"; + + params += kStopSwitchParsing; + params.Add_Space(); + + if (!arcName.IsEmpty()) + { + params += GetQuotedString( + // #ifdef UNDER_CE + arcPathPrefix + + // #endif + arcName); + } + + return Call7zGui(params, + // (arcPathPrefix.IsEmpty()? 0: (LPCWSTR)arcPathPrefix), + waitFinish, &event); + MY_TRY_FINISH +} + +static void ExtractGroupCommand(const UStringVector &arcPaths, UString ¶ms, bool isHash) +{ + AddLagePagesSwitch(params); + params += (isHash ? kHashIncludeSwitches : kArcIncludeSwitches); + CFileMapping fileMapping; + NSynchronization::CManualResetEvent event; + HRESULT result = CreateMap(arcPaths, fileMapping, event, params); + if (result == S_OK) + result = Call7zGui(params, false, &event); + if (result != S_OK) + ErrorMessageHRESULT(result); +} + +void ExtractArchives(const UStringVector &arcPaths, const UString &outFolder, bool showDialog, bool elimDup, UInt32 writeZone) +{ + MY_TRY_BEGIN + UString params ('x'); + if (!outFolder.IsEmpty()) + { + params += " -o"; + params += GetQuotedString(outFolder); + } + if (elimDup) + params += " -spe"; + if (writeZone != (UInt32)(Int32)-1) + { + params += " -snz"; + params.Add_UInt32(writeZone); + } + if (showDialog) + params += kShowDialogSwitch; + ExtractGroupCommand(arcPaths, params, false); + MY_TRY_FINISH_VOID +} + + +void TestArchives(const UStringVector &arcPaths, bool hashMode) +{ + MY_TRY_BEGIN + UString params ('t'); + if (hashMode) + { + params += kArchiveTypeSwitch; + params += "hash"; + } + ExtractGroupCommand(arcPaths, params, false); + MY_TRY_FINISH_VOID +} + + +void CalcChecksum(const UStringVector &paths, + const UString &methodName, + const UString &arcPathPrefix, + const UString &arcFileName) +{ + MY_TRY_BEGIN + + if (!arcFileName.IsEmpty()) + { + CompressFiles( + arcPathPrefix, + arcFileName, + UString("hash"), + false, // addExtension, + paths, + false, // email, + false, // showDialog, + false // waitFinish + ); + return; + } + + UString params ('h'); + if (!methodName.IsEmpty()) + { + params += " -scrc"; + params += methodName; + /* + if (!arcFileName.IsEmpty()) + { + // not used alternate method of generating file + params += " -scrf="; + params += GetQuotedString(arcPathPrefix + arcFileName); + } + */ + } + ExtractGroupCommand(paths, params, true); + MY_TRY_FINISH_VOID +} + +void Benchmark(bool totalMode) +{ + MY_TRY_BEGIN + UString params ('b'); + if (totalMode) + params += " -mm=*"; + AddLagePagesSwitch(params); + const HRESULT result = Call7zGui(params, false, NULL); + if (result != S_OK) + ErrorMessageHRESULT(result); + MY_TRY_FINISH_VOID +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/CompressCall.h b/src/plugins/7zip/7za/cpp/7zip/UI/Common/CompressCall.h new file mode 100644 index 00000000..f2da1631 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/CompressCall.h @@ -0,0 +1,28 @@ +// CompressCall.h + +#ifndef ZIP7_INC_COMPRESS_CALL_H +#define ZIP7_INC_COMPRESS_CALL_H + +#include "../../../Common/MyString.h" + +UString GetQuotedString(const UString &s); + +HRESULT CompressFiles( + const UString &arcPathPrefix, + const UString &arcName, + const UString &arcType, + bool addExtension, + const UStringVector &names, + bool email, bool showDialog, bool waitFinish); + +void ExtractArchives(const UStringVector &arcPaths, const UString &outFolder, bool showDialog, bool elimDup, UInt32 writeZone); +void TestArchives(const UStringVector &arcPaths, bool hashMode = false); + +void CalcChecksum(const UStringVector &paths, + const UString &methodName, + const UString &arcPathPrefix, + const UString &arcFileName); + +void Benchmark(bool totalMode); + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/CompressCall2.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Common/CompressCall2.cpp new file mode 100644 index 00000000..fef0877a --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/CompressCall2.cpp @@ -0,0 +1,327 @@ +// CompressCall2.cpp + +#include "StdAfx.h" + +#ifndef Z7_EXTERNAL_CODECS + +#include "../../../Common/MyException.h" + +#include "../../UI/Common/EnumDirItems.h" + +#include "../../UI/FileManager/LangUtils.h" + +#include "../../UI/GUI/BenchmarkDialog.h" +#include "../../UI/GUI/ExtractGUI.h" +#include "../../UI/GUI/UpdateGUI.h" +#include "../../UI/GUI/HashGUI.h" + +#include "../../UI/GUI/ExtractRes.h" + +#include "CompressCall.h" + +extern HWND g_HWND; + +#define MY_TRY_BEGIN HRESULT result; try { +#define MY_TRY_FINISH } \ + catch(CSystemException &e) { result = e.ErrorCode; } \ + catch(UString &s) { ErrorMessage(s); result = E_FAIL; } \ + catch(...) { result = E_FAIL; } \ + if (result != S_OK && result != E_ABORT) \ + ErrorMessageHRESULT(result); + +static void ThrowException_if_Error(HRESULT res) +{ + if (res != S_OK) + throw CSystemException(res); +} + +#ifdef Z7_EXTERNAL_CODECS + +#define CREATE_CODECS \ + CCodecs *codecs = new CCodecs; \ + CMyComPtr compressCodecsInfo = codecs; \ + ThrowException_if_Error(codecs->Load()); \ + Codecs_AddHashArcHandler(codecs); + +#define LOAD_EXTERNAL_CODECS \ + CExternalCodecs _externalCodecs; \ + _externalCodecs.GetCodecs = codecs; \ + _externalCodecs.GetHashers = codecs; \ + ThrowException_if_Error(_externalCodecs.Load()); + +#else + +#define CREATE_CODECS \ + CCodecs *codecs = new CCodecs; \ + CMyComPtr compressCodecsInfo = codecs; \ + ThrowException_if_Error(codecs->Load()); \ + Codecs_AddHashArcHandler(codecs); + +#define LOAD_EXTERNAL_CODECS + +#endif + + + + +UString GetQuotedString(const UString &s) +{ + UString s2 ('\"'); + s2 += s; + s2.Add_Char('\"'); + return s2; +} + +static void ErrorMessage(LPCWSTR message) +{ + MessageBoxW(g_HWND, message, L"7-Zip", MB_ICONERROR); +} + +static void ErrorMessageHRESULT(HRESULT res) +{ + ErrorMessage(HResultToMessage(res)); +} + +static void ErrorLangMessage(UINT resourceID) +{ + ErrorMessage(LangString(resourceID)); +} + +HRESULT CompressFiles( + const UString &arcPathPrefix, + const UString &arcName, + const UString &arcType, + bool addExtension, + const UStringVector &names, + bool email, bool showDialog, bool /* waitFinish */) +{ + MY_TRY_BEGIN + + CREATE_CODECS + + CUpdateCallbackGUI callback; + + callback.Init(); + + CUpdateOptions uo; + uo.EMailMode = email; + uo.SetActionCommand_Add(); + + uo.ArcNameMode = (addExtension ? k_ArcNameMode_Add : k_ArcNameMode_Exact); + + CObjectVector formatIndices; + if (!ParseOpenTypes(*codecs, arcType, formatIndices)) + { + ErrorLangMessage(IDS_UNSUPPORTED_ARCHIVE_TYPE); + return E_FAIL; + } + const UString arcPath = arcPathPrefix + arcName; + if (!uo.InitFormatIndex(codecs, formatIndices, arcPath) || + !uo.SetArcPath(codecs, arcPath)) + { + ErrorLangMessage(IDS_UPDATE_NOT_SUPPORTED); + return E_FAIL; + } + + NWildcard::CCensor censor; + FOR_VECTOR (i, names) + { + censor.AddPreItem_NoWildcard(names[i]); + } + + bool messageWasDisplayed = false; + + result = UpdateGUI(codecs, + formatIndices, arcPath, + censor, uo, showDialog, messageWasDisplayed, &callback, g_HWND); + + if (result != S_OK) + { + if (result != E_ABORT && messageWasDisplayed) + return E_FAIL; + throw CSystemException(result); + } + if (callback.FailedFiles.Size() > 0) + { + if (!messageWasDisplayed) + throw CSystemException(E_FAIL); + return E_FAIL; + } + + MY_TRY_FINISH + return S_OK; +} + + +static HRESULT ExtractGroupCommand(const UStringVector &arcPaths, + bool showDialog, CExtractOptions &eo, const char *kType = NULL) +{ + MY_TRY_BEGIN + + CREATE_CODECS + + CExtractCallbackImp *ecs = new CExtractCallbackImp; + CMyComPtr extractCallback = ecs; + + ecs->Init(); + + // eo.CalcCrc = options.CalcCrc; + + UStringVector arcPathsSorted; + UStringVector arcFullPathsSorted; + { + NWildcard::CCensor arcCensor; + FOR_VECTOR (i, arcPaths) + { + arcCensor.AddPreItem_NoWildcard(arcPaths[i]); + } + arcCensor.AddPathsToCensor(NWildcard::k_RelatPath); + CDirItemsStat st; + EnumerateDirItemsAndSort(arcCensor, NWildcard::k_RelatPath, UString(), + arcPathsSorted, arcFullPathsSorted, + st, + NULL // &scan: change it!!!! + ); + } + + CObjectVector formatIndices; + if (kType) + { + if (!ParseOpenTypes(*codecs, UString(kType), formatIndices)) + { + throw CSystemException(E_INVALIDARG); + // ErrorLangMessage(IDS_UNSUPPORTED_ARCHIVE_TYPE); + // return E_INVALIDARG; + } + } + + NWildcard::CCensor censor; + { + censor.AddPreItem_Wildcard(); + } + + censor.AddPathsToCensor(NWildcard::k_RelatPath); + + bool messageWasDisplayed = false; + + ecs->MultiArcMode = (arcPathsSorted.Size() > 1); + + result = ExtractGUI(codecs, + formatIndices, CIntVector(), + arcPathsSorted, arcFullPathsSorted, + censor.Pairs.Front().Head, eo, NULL, showDialog, messageWasDisplayed, ecs, g_HWND); + + if (result != S_OK) + { + if (result != E_ABORT && messageWasDisplayed) + return E_FAIL; + throw CSystemException(result); + } + return ecs->IsOK() ? S_OK : E_FAIL; + + MY_TRY_FINISH + return result; +} + +void ExtractArchives(const UStringVector &arcPaths, const UString &outFolder, + bool showDialog, bool elimDup, UInt32 writeZone) +{ + CExtractOptions eo; + eo.OutputDir = us2fs(outFolder); + eo.TestMode = false; + eo.ElimDup.Val = elimDup; + eo.ElimDup.Def = elimDup; + if (writeZone != (UInt32)(Int32)-1) + eo.ZoneMode = (NExtract::NZoneIdMode::EEnum)writeZone; + ExtractGroupCommand(arcPaths, showDialog, eo); +} + +void TestArchives(const UStringVector &arcPaths, bool hashMode) +{ + CExtractOptions eo; + eo.TestMode = true; + ExtractGroupCommand(arcPaths, + true, // showDialog + eo, + hashMode ? "hash" : NULL); +} + +void CalcChecksum(const UStringVector &paths, + const UString &methodName, + const UString &arcPathPrefix, + const UString &arcFileName) +{ + MY_TRY_BEGIN + + if (!arcFileName.IsEmpty()) + { + CompressFiles( + arcPathPrefix, + arcFileName, + UString("hash"), + false, // addExtension, + paths, + false, // email, + false, // showDialog, + false // waitFinish + ); + return; + } + + CREATE_CODECS + LOAD_EXTERNAL_CODECS + + NWildcard::CCensor censor; + FOR_VECTOR (i, paths) + { + censor.AddPreItem_NoWildcard(paths[i]); + } + + censor.AddPathsToCensor(NWildcard::k_RelatPath); + bool messageWasDisplayed = false; + + CHashOptions options; + options.Methods.Add(methodName); + + /* + if (!arcFileName.IsEmpty()) + options.HashFilePath = arcPathPrefix + arcFileName; + */ + + result = HashCalcGUI(EXTERNAL_CODECS_VARS_L censor, options, messageWasDisplayed); + if (result != S_OK) + { + if (result != E_ABORT && messageWasDisplayed) + return; // E_FAIL; + throw CSystemException(result); + } + + MY_TRY_FINISH + return; // result; +} + +void Benchmark(bool totalMode) +{ + MY_TRY_BEGIN + + CREATE_CODECS + LOAD_EXTERNAL_CODECS + + CObjectVector props; + if (totalMode) + { + CProperty prop; + prop.Name = "m"; + prop.Value = "*"; + props.Add(prop); + } + result = Benchmark( + EXTERNAL_CODECS_VARS_L + props, + k_NumBenchIterations_Default, + g_HWND); + + MY_TRY_FINISH +} + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/DefaultName.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Common/DefaultName.cpp new file mode 100644 index 00000000..8c34ffc7 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/DefaultName.cpp @@ -0,0 +1,37 @@ +// DefaultName.cpp + +#include "StdAfx.h" + +#include "DefaultName.h" + +static UString GetDefaultName3(const UString &fileName, + const UString &extension, const UString &addSubExtension) +{ + const unsigned extLen = extension.Len(); + const unsigned fileNameLen = fileName.Len(); + + if (fileNameLen > extLen + 1) + { + const unsigned dotPos = fileNameLen - (extLen + 1); + if (fileName[dotPos] == '.') + if (extension.IsEqualTo_NoCase(fileName.Ptr(dotPos + 1))) + return fileName.Left(dotPos) + addSubExtension; + } + + int dotPos = fileName.ReverseFind_Dot(); + if (dotPos > 0) + return fileName.Left((unsigned)dotPos) + addSubExtension; + + if (addSubExtension.IsEmpty()) + return fileName + L'~'; + else + return fileName + addSubExtension; +} + +UString GetDefaultName2(const UString &fileName, + const UString &extension, const UString &addSubExtension) +{ + UString name = GetDefaultName3(fileName, extension, addSubExtension); + name.TrimRight(); + return name; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/DefaultName.h b/src/plugins/7zip/7za/cpp/7zip/UI/Common/DefaultName.h new file mode 100644 index 00000000..46f6e9e6 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/DefaultName.h @@ -0,0 +1,11 @@ +// DefaultName.h + +#ifndef ZIP7_INC_DEFAULT_NAME_H +#define ZIP7_INC_DEFAULT_NAME_H + +#include "../../../Common/MyString.h" + +UString GetDefaultName2(const UString &fileName, + const UString &extension, const UString &addSubExtension); + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/DirItem.h b/src/plugins/7zip/7za/cpp/7zip/UI/Common/DirItem.h new file mode 100644 index 00000000..3c192495 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/DirItem.h @@ -0,0 +1,407 @@ +// DirItem.h + +#ifndef ZIP7_INC_DIR_ITEM_H +#define ZIP7_INC_DIR_ITEM_H + +#ifdef _WIN32 +#include "../../../Common/MyLinux.h" +#endif + +#include "../../../Common/MyString.h" + +#include "../../../Windows/FileFind.h" +#include "../../../Windows/PropVariant.h" +#include "../../../Windows/TimeUtils.h" + +#include "../../Common/UniqBlocks.h" + +#include "../../Archive/IArchive.h" + +struct CDirItemsStat +{ + UInt64 NumDirs; + UInt64 NumFiles; + UInt64 NumAltStreams; + UInt64 FilesSize; + UInt64 AltStreamsSize; + + UInt64 NumErrors; + + // UInt64 Get_NumItems() const { return NumDirs + NumFiles + NumAltStreams; } + UInt64 Get_NumDataItems() const { return NumFiles + NumAltStreams; } + UInt64 GetTotalBytes() const { return FilesSize + AltStreamsSize; } + + bool IsEmpty() const { return + 0 == NumDirs + && 0 == NumFiles + && 0 == NumAltStreams + && 0 == FilesSize + && 0 == AltStreamsSize + && 0 == NumErrors; } + + CDirItemsStat(): + NumDirs(0), + NumFiles(0), + NumAltStreams(0), + FilesSize(0), + AltStreamsSize(0), + NumErrors(0) + {} +}; + + +struct CDirItemsStat2: public CDirItemsStat +{ + UInt64 Anti_NumDirs; + UInt64 Anti_NumFiles; + UInt64 Anti_NumAltStreams; + + // UInt64 Get_NumItems() const { return Anti_NumDirs + Anti_NumFiles + Anti_NumAltStreams + CDirItemsStat::Get_NumItems(); } + UInt64 Get_NumDataItems2() const { return Anti_NumFiles + Anti_NumAltStreams + CDirItemsStat::Get_NumDataItems(); } + + bool IsEmpty() const { return CDirItemsStat::IsEmpty() + && 0 == Anti_NumDirs + && 0 == Anti_NumFiles + && 0 == Anti_NumAltStreams; } + + CDirItemsStat2(): + Anti_NumDirs(0), + Anti_NumFiles(0), + Anti_NumAltStreams(0) + {} +}; + + +Z7_PURE_INTERFACES_BEGIN + +#define Z7_IFACEN_IDirItemsCallback(x) \ + virtual HRESULT ScanError(const FString &path, DWORD systemError) x \ + virtual HRESULT ScanProgress(const CDirItemsStat &st, const FString &path, bool isDir) x \ + +Z7_IFACE_DECL_PURE(IDirItemsCallback) + +Z7_PURE_INTERFACES_END + + +struct CArcTime +{ + FILETIME FT; + UInt16 Prec; + Byte Ns100; + bool Def; + + CArcTime() + { + Clear(); + } + + void Clear() + { + FT.dwHighDateTime = FT.dwLowDateTime = 0; + Prec = 0; + Ns100 = 0; + Def = false; + } + + bool IsZero() const + { + return FT.dwLowDateTime == 0 && FT.dwHighDateTime == 0 && Ns100 == 0; + } + + int CompareWith(const CArcTime &a) const + { + const int res = CompareFileTime(&FT, &a.FT); + if (res != 0) + return res; + if (Ns100 < a.Ns100) return -1; + if (Ns100 > a.Ns100) return 1; + return 0; + } + + UInt64 Get_FILETIME_as_UInt64() const + { + return (((UInt64)FT.dwHighDateTime) << 32) + FT.dwLowDateTime; + } + + UInt32 Get_DosTime() const + { + FILETIME ft2 = FT; + if ((Prec == k_PropVar_TimePrec_Base + 8 || + Prec == k_PropVar_TimePrec_Base + 9) + && Ns100 != 0) + { + UInt64 u64 = Get_FILETIME_as_UInt64(); + // we round up even small (ns < 100ns) as FileTimeToDosTime() + if (u64 % 20000000 == 0) + { + u64++; + ft2.dwHighDateTime = (DWORD)(u64 >> 32); + ft2.dwHighDateTime = (DWORD)u64; + } + } + // FileTimeToDosTime() is expected to round up in Windows + UInt32 dosTime; + // we use simplified code with utctime->dos. + // do we need local time instead here? + NWindows::NTime::FileTime_To_DosTime(ft2, dosTime); + return dosTime; + } + + int GetNumDigits() const + { + if (Prec == k_PropVar_TimePrec_Unix || + Prec == k_PropVar_TimePrec_DOS) + return 0; + if (Prec == k_PropVar_TimePrec_HighPrec) + return 9; + if (Prec == k_PropVar_TimePrec_0) + return 7; + int digits = (int)Prec - (int)k_PropVar_TimePrec_Base; + if (digits < 0) + digits = 0; + return digits; + } + + void Write_To_FiTime(CFiTime &dest) const + { + #ifdef _WIN32 + dest = FT; + #else + if (FILETIME_To_timespec(FT, dest)) + if ((Prec == k_PropVar_TimePrec_Base + 8 || + Prec == k_PropVar_TimePrec_Base + 9) + && Ns100 != 0) + { + dest.tv_nsec += Ns100; + } + #endif + } + + // (Def) is not set + void Set_From_FILETIME(const FILETIME &ft) + { + FT = ft; + // Prec = k_PropVar_TimePrec_CompatNTFS; + Prec = k_PropVar_TimePrec_Base + 7; + Ns100 = 0; + } + + // (Def) is not set + // it set full form precision: k_PropVar_TimePrec_Base + numDigits + void Set_From_FiTime(const CFiTime &ts) + { + #ifdef _WIN32 + FT = ts; + Prec = k_PropVar_TimePrec_Base + 7; + // Prec = k_PropVar_TimePrec_Base; // for debug + // Prec = 0; // for debug + Ns100 = 0; + #else + unsigned ns100; + FiTime_To_FILETIME_ns100(ts, FT, ns100); + Ns100 = (Byte)ns100; + Prec = k_PropVar_TimePrec_Base + 9; + #endif + } + + void Set_From_Prop(const PROPVARIANT &prop) + { + FT = prop.filetime; + unsigned prec = 0; + unsigned ns100 = 0; + const unsigned prec_Temp = prop.wReserved1; + if (prec_Temp != 0 + && prec_Temp <= k_PropVar_TimePrec_1ns + && prop.wReserved3 == 0) + { + const unsigned ns100_Temp = prop.wReserved2; + if (ns100_Temp < 100) + { + ns100 = ns100_Temp; + prec = prec_Temp; + } + } + Prec = (UInt16)prec; + Ns100 = (Byte)ns100; + Def = true; + } +}; + + +struct CDirItem: public NWindows::NFile::NFind::CFileInfoBase +{ + UString Name; + + #ifndef UNDER_CE + CByteBuffer ReparseData; + + #ifdef _WIN32 + // UString ShortName; + CByteBuffer ReparseData2; // fixed (reduced) absolute links for WIM format + bool AreReparseData() const { return ReparseData.Size() != 0 || ReparseData2.Size() != 0; } + #else + bool AreReparseData() const { return ReparseData.Size() != 0; } + #endif // _WIN32 + + #endif // !UNDER_CE + + void Copy_From_FileInfoBase(const NWindows::NFile::NFind::CFileInfoBase &fi) + { + (NWindows::NFile::NFind::CFileInfoBase &)*this = fi; + } + + int PhyParent; + int LogParent; + int SecureIndex; + + #ifdef _WIN32 + #else + int OwnerNameIndex; + int OwnerGroupIndex; + #endif + + // bool Attrib_IsDefined; + + CDirItem(): + PhyParent(-1) + , LogParent(-1) + , SecureIndex(-1) + #ifdef _WIN32 + #else + , OwnerNameIndex(-1) + , OwnerGroupIndex(-1) + #endif + // , Attrib_IsDefined(true) + { + } + + + CDirItem(const NWindows::NFile::NFind::CFileInfo &fi, + int phyParent, int logParent, int secureIndex): + CFileInfoBase(fi) + , Name(fs2us(fi.Name)) + #if defined(_WIN32) && !defined(UNDER_CE) + // , ShortName(fs2us(fi.ShortName)) + #endif + , PhyParent(phyParent) + , LogParent(logParent) + , SecureIndex(secureIndex) + #ifdef _WIN32 + #else + , OwnerNameIndex(-1) + , OwnerGroupIndex(-1) + #endif + {} +}; + + + +class CDirItems +{ + UStringVector Prefixes; + CIntVector PhyParents; + CIntVector LogParents; + + UString GetPrefixesPath(const CIntVector &parents, int index, const UString &name) const; + + HRESULT EnumerateDir(int phyParent, int logParent, const FString &phyPrefix); + +public: + CObjectVector Items; + + bool SymLinks; + bool ScanAltStreams; + bool ExcludeDirItems; + bool ExcludeFileItems; + bool ShareForWrite; + + /* it must be called after anotrher checks */ + bool CanIncludeItem(bool isDir) const + { + return isDir ? !ExcludeDirItems : !ExcludeFileItems; + } + + + CDirItemsStat Stat; + + #if !defined(UNDER_CE) + HRESULT SetLinkInfo(CDirItem &dirItem, const NWindows::NFile::NFind::CFileInfo &fi, + const FString &phyPrefix); + #endif + + #if defined(_WIN32) && !defined(UNDER_CE) + + CUniqBlocks SecureBlocks; + CByteBuffer TempSecureBuf; + bool _saclEnabled; + bool ReadSecure; + + HRESULT AddSecurityItem(const FString &path, int &secureIndex); + HRESULT FillFixedReparse(); + + #endif + + #ifndef _WIN32 + + C_UInt32_UString_Map OwnerNameMap; + C_UInt32_UString_Map OwnerGroupMap; + bool StoreOwnerName; + + HRESULT FillDeviceSizes(); + + #endif + + IDirItemsCallback *Callback; + + CDirItems(); + + void AddDirFileInfo(int phyParent, int logParent, int secureIndex, + const NWindows::NFile::NFind::CFileInfo &fi); + + HRESULT AddError(const FString &path, DWORD errorCode); + HRESULT AddError(const FString &path); + + HRESULT ScanProgress(const FString &path); + + // unsigned GetNumFolders() const { return Prefixes.Size(); } + FString GetPhyPath(unsigned index) const; + UString GetLogPath(unsigned index) const; + + unsigned AddPrefix(int phyParent, int logParent, const UString &prefix); + void DeleteLastPrefix(); + + // HRESULT EnumerateOneDir(const FString &phyPrefix, CObjectVector &files); + HRESULT EnumerateOneDir(const FString &phyPrefix, CObjectVector &files); + + HRESULT EnumerateItems2( + const FString &phyPrefix, + const UString &logPrefix, + const FStringVector &filePaths, + FStringVector *requestedPaths); + + void ReserveDown(); +}; + + + + +struct CArcItem +{ + UInt64 Size; + UString Name; + CArcTime MTime; // it can be mtime of archive file, if MTime is not defined for item in archive + bool IsDir; + bool IsAltStream; + bool Size_Defined; + bool Censored; + UInt32 IndexInServer; + + CArcItem(): + IsDir(false), + IsAltStream(false), + Size_Defined(false), + Censored(false) + {} +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/EnumDirItems.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Common/EnumDirItems.cpp new file mode 100644 index 00000000..cada2e62 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/EnumDirItems.cpp @@ -0,0 +1,1637 @@ +// EnumDirItems.cpp + +#include "StdAfx.h" + +#include +// #include + +#ifndef _WIN32 +#include +#include +#include "../../../Common/UTFConvert.h" +#endif + +#include "../../../Common/Wildcard.h" + +#include "../../../Windows/FileDir.h" +#include "../../../Windows/FileIO.h" +#include "../../../Windows/FileName.h" + +#if defined(_WIN32) && !defined(UNDER_CE) +#define Z7_USE_SECURITY_CODE +#include "../../../Windows/SecurityUtils.h" +#endif + +#include "EnumDirItems.h" +#include "SortUtils.h" + +using namespace NWindows; +using namespace NFile; +using namespace NName; + + +static bool FindFile_KeepDots(NFile::NFind::CFileInfo &fi, const FString &path, bool followLink) +{ + const bool res = fi.Find(path, followLink); + if (!res) + return res; + if (path.IsEmpty()) + return res; + // we keep name "." and "..", if it's without tail slash + const FChar *p = path.RightPtr(1); + if (*p != '.') + return res; + if (p != path.Ptr()) + { + FChar c = p[-1]; + if (!IS_PATH_SEPAR(c)) + { + if (c != '.') + return res; + p--; + if (p != path.Ptr()) + { + c = p[-1]; + if (!IS_PATH_SEPAR(c)) + return res; + } + } + } + fi.Name = p; + return res; +} + + +void CDirItems::AddDirFileInfo(int phyParent, int logParent, int secureIndex, + const NFind::CFileInfo &fi) +{ + /* + CDirItem di(fi); + di.PhyParent = phyParent; + di.LogParent = logParent; + di.SecureIndex = secureIndex; + Items.Add(di); + */ + VECTOR_ADD_NEW_OBJECT (Items, CDirItem(fi, phyParent, logParent, secureIndex)) + + if (fi.IsDir()) + Stat.NumDirs++; + #ifdef _WIN32 + else if (fi.IsAltStream) + { + Stat.NumAltStreams++; + Stat.AltStreamsSize += fi.Size; + } + #endif + else + { + Stat.NumFiles++; + Stat.FilesSize += fi.Size; + } +} + +// (DWORD)E_FAIL +#define DI_DEFAULT_ERROR ERROR_INVALID_FUNCTION + +HRESULT CDirItems::AddError(const FString &path, DWORD errorCode) +{ + if (errorCode == 0) + errorCode = DI_DEFAULT_ERROR; + Stat.NumErrors++; + if (Callback) + return Callback->ScanError(path, errorCode); + return S_OK; +} + +HRESULT CDirItems::AddError(const FString &path) +{ + return AddError(path, ::GetLastError()); +} + +static const unsigned kScanProgressStepMask = (1 << 12) - 1; + +HRESULT CDirItems::ScanProgress(const FString &dirPath) +{ + if (Callback) + return Callback->ScanProgress(Stat, dirPath, true); + return S_OK; +} + +UString CDirItems::GetPrefixesPath(const CIntVector &parents, int index, const UString &name) const +{ + UString path; + unsigned len = name.Len(); + + int i; + for (i = index; i >= 0; i = parents[(unsigned)i]) + len += Prefixes[(unsigned)i].Len(); + + wchar_t *p = path.GetBuf_SetEnd(len) + len; + + p -= name.Len(); + wmemcpy(p, (const wchar_t *)name, name.Len()); + + for (i = index; i >= 0; i = parents[(unsigned)i]) + { + const UString &s = Prefixes[(unsigned)i]; + p -= s.Len(); + wmemcpy(p, (const wchar_t *)s, s.Len()); + } + + return path; +} + +FString CDirItems::GetPhyPath(unsigned index) const +{ + const CDirItem &di = Items[index]; + return us2fs(GetPrefixesPath(PhyParents, di.PhyParent, di.Name)); +} + +UString CDirItems::GetLogPath(unsigned index) const +{ + const CDirItem &di = Items[index]; + return GetPrefixesPath(LogParents, di.LogParent, di.Name); +} + +void CDirItems::ReserveDown() +{ + Prefixes.ReserveDown(); + PhyParents.ReserveDown(); + LogParents.ReserveDown(); + Items.ReserveDown(); +} + +unsigned CDirItems::AddPrefix(int phyParent, int logParent, const UString &prefix) +{ + PhyParents.Add(phyParent); + LogParents.Add(logParent); + return Prefixes.Add(prefix); +} + +void CDirItems::DeleteLastPrefix() +{ + PhyParents.DeleteBack(); + LogParents.DeleteBack(); + Prefixes.DeleteBack(); +} + +bool InitLocalPrivileges(); + +CDirItems::CDirItems(): + SymLinks(false), + ScanAltStreams(false) + , ExcludeDirItems(false) + , ExcludeFileItems(false) + , ShareForWrite(false) + #ifdef Z7_USE_SECURITY_CODE + , ReadSecure(false) + #endif + #ifndef _WIN32 + , StoreOwnerName(false) + #endif + , Callback(NULL) +{ + #ifdef Z7_USE_SECURITY_CODE + _saclEnabled = InitLocalPrivileges(); + #endif +} + + +#ifdef Z7_USE_SECURITY_CODE + +HRESULT CDirItems::AddSecurityItem(const FString &path, int &secureIndex) +{ + secureIndex = -1; + + SECURITY_INFORMATION securInfo = + DACL_SECURITY_INFORMATION | + GROUP_SECURITY_INFORMATION | + OWNER_SECURITY_INFORMATION; + if (_saclEnabled) + securInfo |= SACL_SECURITY_INFORMATION; + + DWORD errorCode = 0; + DWORD secureSize; + + BOOL res = ::GetFileSecurityW(fs2us(path), securInfo, (PSECURITY_DESCRIPTOR)(void *)(Byte *)TempSecureBuf, (DWORD)TempSecureBuf.Size(), &secureSize); + + if (res) + { + if (secureSize == 0) + return S_OK; + if (secureSize > TempSecureBuf.Size()) + errorCode = ERROR_INVALID_FUNCTION; + } + else + { + errorCode = GetLastError(); + if (errorCode == ERROR_INSUFFICIENT_BUFFER) + { + if (secureSize <= TempSecureBuf.Size()) + errorCode = ERROR_INVALID_FUNCTION; + else + { + TempSecureBuf.Alloc(secureSize); + res = ::GetFileSecurityW(fs2us(path), securInfo, (PSECURITY_DESCRIPTOR)(void *)(Byte *)TempSecureBuf, (DWORD)TempSecureBuf.Size(), &secureSize); + if (res) + { + if (secureSize != TempSecureBuf.Size()) + errorCode = ERROR_INVALID_FUNCTION; + } + else + errorCode = GetLastError(); + } + } + } + + if (res) + { + secureIndex = (int)SecureBlocks.AddUniq(TempSecureBuf, secureSize); + return S_OK; + } + + return AddError(path, errorCode); +} + +#endif // Z7_USE_SECURITY_CODE + + +HRESULT CDirItems::EnumerateOneDir(const FString &phyPrefix, CObjectVector &files) +{ + NFind::CEnumerator enumerator; + // printf("\n enumerator.SetDirPrefix(phyPrefix) \n"); + + enumerator.SetDirPrefix(phyPrefix); + + #ifdef _WIN32 + + NFind::CFileInfo fi; + + for (unsigned ttt = 0; ; ttt++) + { + bool found; + if (!enumerator.Next(fi, found)) + return AddError(phyPrefix); + if (!found) + return S_OK; + files.Add(fi); + if (Callback && (ttt & kScanProgressStepMask) == kScanProgressStepMask) + { + RINOK(ScanProgress(phyPrefix)) + } + } + + #else // _WIN32 + + // enumerator.SolveLinks = !SymLinks; + + CObjectVector entries; + + for (;;) + { + bool found; + NFind::CDirEntry de; + if (!enumerator.Next(de, found)) + return AddError(phyPrefix); + if (!found) + break; + entries.Add(de); + } + + FOR_VECTOR(i, entries) + { + const NFind::CDirEntry &de = entries[i]; + NFind::CFileInfo fi; + if (!enumerator.Fill_FileInfo(de, fi, !SymLinks)) + // if (!fi.Find_AfterEnumerator(path)) + { + const FString path = phyPrefix + de.Name; + { + RINOK(AddError(path)) + continue; + } + } + + files.Add(fi); + + if (Callback && (i & kScanProgressStepMask) == kScanProgressStepMask) + { + RINOK(ScanProgress(phyPrefix)) + } + } + + return S_OK; + + #endif // _WIN32 +} + + + + +HRESULT CDirItems::EnumerateDir(int phyParent, int logParent, const FString &phyPrefix) +{ + RINOK(ScanProgress(phyPrefix)) + + CObjectVector files; + RINOK(EnumerateOneDir(phyPrefix, files)) + + FOR_VECTOR (i, files) + { + #ifdef _WIN32 + const NFind::CFileInfo &fi = files[i]; + #else + const NFind::CFileInfo &fi = files[i]; + /* + NFind::CFileInfo fi; + { + const NFind::CDirEntry &di = files[i]; + const FString path = phyPrefix + di.Name; + if (!fi.Find_AfterEnumerator(path)) + { + RINOK(AddError(path)); + continue; + } + fi.Name = di.Name; + } + */ + #endif + + if (CanIncludeItem(fi.IsDir())) + { + int secureIndex = -1; + #ifdef Z7_USE_SECURITY_CODE + if (ReadSecure) + { + RINOK(AddSecurityItem(phyPrefix + fi.Name, secureIndex)) + } + #endif + AddDirFileInfo(phyParent, logParent, secureIndex, fi); + } + + if (Callback && (i & kScanProgressStepMask) == kScanProgressStepMask) + { + RINOK(ScanProgress(phyPrefix)) + } + + if (fi.IsDir()) + { + const FString name2 = fi.Name + FCHAR_PATH_SEPARATOR; + unsigned parent = AddPrefix(phyParent, logParent, fs2us(name2)); + RINOK(EnumerateDir((int)parent, (int)parent, phyPrefix + name2)) + } + } + return S_OK; +} + + +/* +EnumerateItems2() + const FStringVector &filePaths - are path without tail slashes. + All dir prefixes of filePaths will be not stores in logical paths +fix it: we can scan AltStream also. +*/ + +#ifdef _WIN32 +// #define FOLLOW_LINK_PARAM +// #define FOLLOW_LINK_PARAM2 +#define FOLLOW_LINK_PARAM , (!SymLinks) +#define FOLLOW_LINK_PARAM2 , (!dirItems.SymLinks) +#else +#define FOLLOW_LINK_PARAM , (!SymLinks) +#define FOLLOW_LINK_PARAM2 , (!dirItems.SymLinks) +#endif + +HRESULT CDirItems::EnumerateItems2( + const FString &phyPrefix, + const UString &logPrefix, + const FStringVector &filePaths, + FStringVector *requestedPaths) +{ + const int phyParent = phyPrefix.IsEmpty() ? -1 : (int)AddPrefix(-1, -1, fs2us(phyPrefix)); + const int logParent = logPrefix.IsEmpty() ? -1 : (int)AddPrefix(-1, -1, logPrefix); + + #ifdef _WIN32 + const bool phyPrefix_isAltStreamPrefix = + NFile::NName::IsAltStreamPrefixWithColon(fs2us(phyPrefix)); + #endif + + FOR_VECTOR (i, filePaths) + { + const FString &filePath = filePaths[i]; + NFind::CFileInfo fi; + const FString phyPath = phyPrefix + filePath; + if (!FindFile_KeepDots(fi, phyPath FOLLOW_LINK_PARAM)) + { + RINOK(AddError(phyPath)) + continue; + } + if (requestedPaths) + requestedPaths->Add(phyPath); + + const int delimiter = filePath.ReverseFind_PathSepar(); + FString phyPrefixCur; + int phyParentCur = phyParent; + if (delimiter >= 0) + { + phyPrefixCur.SetFrom(filePath, (unsigned)(delimiter + 1)); + phyParentCur = (int)AddPrefix(phyParent, logParent, fs2us(phyPrefixCur)); + } + + if (CanIncludeItem(fi.IsDir())) + { + int secureIndex = -1; + #ifdef Z7_USE_SECURITY_CODE + if (ReadSecure) + { + RINOK(AddSecurityItem(phyPath, secureIndex)) + } + #endif + #ifdef _WIN32 + if (phyPrefix_isAltStreamPrefix && fi.IsAltStream) + { + const int pos = fi.Name.Find(FChar(':')); + if (pos >= 0) + fi.Name.DeleteFrontal((unsigned)pos + 1); + } + #endif + AddDirFileInfo(phyParentCur, logParent, secureIndex, fi); + } + + if (fi.IsDir()) + { + const FString name2 = fi.Name + FCHAR_PATH_SEPARATOR; + const unsigned parent = AddPrefix(phyParentCur, logParent, fs2us(name2)); + RINOK(EnumerateDir((int)parent, (int)parent, phyPrefix + phyPrefixCur + name2)) + } + } + + ReserveDown(); + return S_OK; +} + + + + +static HRESULT EnumerateDirItems( + const NWildcard::CCensorNode &curNode, + const int phyParent, const int logParent, + const FString &phyPrefix, + const UStringVector &addParts, // additional parts from curNode + CDirItems &dirItems, + bool enterToSubFolders); + + +/* EnumerateDirItems_Spec() + adds new Dir item prefix, and enumerates dir items, + then it can remove that Dir item prefix, if there are no items in that dir. +*/ + + +/* + EnumerateDirItems_Spec() + it's similar to EnumerateDirItems, but phyPrefix doesn't include (curFolderName) +*/ + +static HRESULT EnumerateDirItems_Spec( + const NWildcard::CCensorNode &curNode, + const int phyParent, const int logParent, const FString &curFolderName, + const FString &phyPrefix, // without (curFolderName) + const UStringVector &addParts, // (curNode + addParts) includes (curFolderName) + CDirItems &dirItems, + bool enterToSubFolders) +{ + const FString name2 = curFolderName + FCHAR_PATH_SEPARATOR; + const unsigned parent = dirItems.AddPrefix(phyParent, logParent, fs2us(name2)); + const unsigned numItems = dirItems.Items.Size(); + HRESULT res = EnumerateDirItems( + curNode, (int)parent, (int)parent, phyPrefix + name2, + addParts, dirItems, enterToSubFolders); + if (numItems == dirItems.Items.Size()) + dirItems.DeleteLastPrefix(); + return res; +} + + +#ifndef UNDER_CE + +#ifdef _WIN32 + +static HRESULT EnumerateAltStreams( + const NFind::CFileInfo &fi, + const NWildcard::CCensorNode &curNode, + const int phyParent, const int logParent, + const FString &phyPath, // with (fi.Name), without tail slash for folders + const UStringVector &addParts, // with (fi.Name), prefix parts from curNode + bool addAllSubStreams, + CDirItems &dirItems) +{ + // we don't use (ExcludeFileItems) rules for AltStreams + // if (dirItems.ExcludeFileItems) return S_OK; + + NFind::CStreamEnumerator enumerator(phyPath); + for (;;) + { + NFind::CStreamInfo si; + bool found; + if (!enumerator.Next(si, found)) + { + return dirItems.AddError(phyPath + FTEXT(":*")); // , (DWORD)E_FAIL + } + if (!found) + return S_OK; + if (si.IsMainStream()) + continue; + UStringVector parts = addParts; + const UString reducedName = si.GetReducedName(); + parts.Back() += reducedName; + if (curNode.CheckPathToRoot(false, parts, true)) + continue; + if (!addAllSubStreams) + if (!curNode.CheckPathToRoot(true, parts, true)) + continue; + + NFind::CFileInfo fi2 = fi; + fi2.Name += us2fs(reducedName); + fi2.Size = si.Size; + fi2.Attrib &= ~(DWORD)(FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT); + fi2.IsAltStream = true; + dirItems.AddDirFileInfo(phyParent, logParent, -1, fi2); + } +} + +#endif // _WIN32 + + +/* We get Reparse data and parse it. + If there is Reparse error, we free dirItem.Reparse data. + Do we need to work with empty reparse data? +*/ + +HRESULT CDirItems::SetLinkInfo(CDirItem &dirItem, const NFind::CFileInfo &fi, + const FString &phyPrefix) +{ + if (!SymLinks) + return S_OK; + + #ifdef _WIN32 + if (!fi.HasReparsePoint() || fi.IsAltStream) + #else // _WIN32 + if (!fi.IsPosixLink()) + #endif // _WIN32 + return S_OK; + + const FString path = phyPrefix + fi.Name; + CByteBuffer &buf = dirItem.ReparseData; + if (NIO::GetReparseData(path, buf)) + { + // if (dirItem.ReparseData.Size() != 0) + Stat.FilesSize -= fi.Size; + return S_OK; + } + + DWORD res = ::GetLastError(); + buf.Free(); + return AddError(path, res); +} + +#endif // UNDER_CE + + + +static HRESULT EnumerateForItem( + const NFind::CFileInfo &fi, + const NWildcard::CCensorNode &curNode, + const int phyParent, const int logParent, const FString &phyPrefix, + const UStringVector &addParts, // additional parts from curNode, without (fi.Name) + CDirItems &dirItems, + bool enterToSubFolders) +{ + const UString name = fs2us(fi.Name); + UStringVector newParts = addParts; + newParts.Add(name); + + // check the path in exclude rules + if (curNode.CheckPathToRoot(false, newParts, !fi.IsDir())) + return S_OK; + + #if !defined(UNDER_CE) + int dirItemIndex = -1; + #if defined(_WIN32) + bool addAllSubStreams = false; + bool needAltStreams = true; + #endif // _WIN32 + #endif // !defined(UNDER_CE) + + // check the path in inlcude rules + if (curNode.CheckPathToRoot(true, newParts, !fi.IsDir())) + { + #if !defined(UNDER_CE) + // dirItemIndex = (int)dirItems.Items.Size(); + #if defined(_WIN32) + // we will not check include rules for substreams. + addAllSubStreams = true; + #endif // _WIN32 + #endif // !defined(UNDER_CE) + + if (dirItems.CanIncludeItem(fi.IsDir())) + { + int secureIndex = -1; + #ifdef Z7_USE_SECURITY_CODE + if (dirItems.ReadSecure) + { + RINOK(dirItems.AddSecurityItem(phyPrefix + fi.Name, secureIndex)) + } + #endif + #if !defined(UNDER_CE) + dirItemIndex = (int)dirItems.Items.Size(); + #endif // !defined(UNDER_CE) + dirItems.AddDirFileInfo(phyParent, logParent, secureIndex, fi); + } + else + { + #if defined(_WIN32) && !defined(UNDER_CE) + needAltStreams = false; + #endif + } + + if (fi.IsDir()) + enterToSubFolders = true; + } + + #if !defined(UNDER_CE) + + // we don't scan AltStreams for link files + + if (dirItemIndex >= 0) + { + CDirItem &dirItem = dirItems.Items[(unsigned)dirItemIndex]; + RINOK(dirItems.SetLinkInfo(dirItem, fi, phyPrefix)) + if (dirItem.ReparseData.Size() != 0) + return S_OK; + } + + #if defined(_WIN32) + if (needAltStreams && dirItems.ScanAltStreams && !fi.IsAltStream) + { + RINOK(EnumerateAltStreams(fi, curNode, phyParent, logParent, + phyPrefix + fi.Name, // with (fi.Name) + newParts, // with (fi.Name) + addAllSubStreams, + dirItems)) + } + #endif + + #endif // !defined(UNDER_CE) + + + #ifndef _WIN32 + if (!fi.IsPosixLink()) // posix link can follow to dir + #endif + if (!fi.IsDir()) + return S_OK; + + const NWildcard::CCensorNode *nextNode = NULL; + + if (addParts.IsEmpty()) + { + int index = curNode.FindSubNode(name); + if (index >= 0) + { + nextNode = &curNode.SubNodes[(unsigned)index]; + newParts.Clear(); + } + } + + if (!nextNode) + { + if (!enterToSubFolders) + return S_OK; + + #ifndef _WIN32 + if (fi.IsPosixLink()) + { + // here we can try to resolve posix link + // if the link to dir, then can we follow it + return S_OK; // we don't follow posix link + } + #else + if (dirItems.SymLinks && fi.HasReparsePoint()) + { + /* 20.03: in SymLinks mode: we don't enter to directory that + has reparse point and has no CCensorNode + NOTE: (curNode and parent nodes) still can have wildcard rules + to include some items of target directory (of reparse point), + but we ignore these rules here. + */ + return S_OK; + } + #endif + nextNode = &curNode; + } + + return EnumerateDirItems_Spec( + *nextNode, phyParent, logParent, fi.Name, + phyPrefix, // without (fi.Name) + newParts, // relative to (*nextNode). (*nextNode + newParts) includes (fi.Name) + dirItems, + enterToSubFolders); +} + + +static bool CanUseFsDirect(const NWildcard::CCensorNode &curNode) +{ + FOR_VECTOR (i, curNode.IncludeItems) + { + const NWildcard::CItem &item = curNode.IncludeItems[i]; + if (item.Recursive || item.PathParts.Size() != 1) + return false; + const UString &name = item.PathParts.Front(); + /* + if (name.IsEmpty()) + return false; + */ + + /* Windows doesn't support file name with wildcard + But if another system supports file name with wildcard, + and wildcard mode is disabled, we can ignore wildcard in name + */ + /* + #ifndef _WIN32 + if (!item.WildcardParsing) + continue; + #endif + */ + if (DoesNameContainWildcard(name)) + return false; + } + return true; +} + + +#if defined(_WIN32) && !defined(UNDER_CE) + +static bool IsVirtualFsFolder(const FString &prefix, const UString &name) +{ + UString s = fs2us(prefix); + s += name; + s.Add_PathSepar(); + // it returns (true) for non real FS folder path like - "\\SERVER\" + return IsPathSepar(s[0]) && GetRootPrefixSize(s) == 0; +} + +#endif + + + +static HRESULT EnumerateDirItems( + const NWildcard::CCensorNode &curNode, + const int phyParent, const int logParent, const FString &phyPrefix, + const UStringVector &addParts, // prefix from curNode including + CDirItems &dirItems, + bool enterToSubFolders) +{ + if (!enterToSubFolders) + { + /* if there are IncludeItems censor rules that affect items in subdirs, + then we will enter to all subfolders */ + if (curNode.NeedCheckSubDirs()) + enterToSubFolders = true; + } + + RINOK(dirItems.ScanProgress(phyPrefix)) + + // try direct_names case at first + if (addParts.IsEmpty() && !enterToSubFolders) + { + if (CanUseFsDirect(curNode)) + { + // all names are direct (no wildcards) + // so we don't need file_system's dir enumerator + CRecordVector needEnterVector; + unsigned i; + + for (i = 0; i < curNode.IncludeItems.Size(); i++) + { + const NWildcard::CItem &item = curNode.IncludeItems[i]; + const UString &name = item.PathParts.Front(); + FString fullPath = phyPrefix + us2fs(name); + + /* + // not possible now + if (!item.ForDir && !item.ForFile) + { + RINOK(dirItems.AddError(fullPath, ERROR_INVALID_PARAMETER)); + continue; + } + */ + + #if defined(_WIN32) && !defined(UNDER_CE) + bool needAltStreams = true; + #endif + + #ifdef Z7_USE_SECURITY_CODE + bool needSecurity = true; + #endif + + if (phyPrefix.IsEmpty()) + { + if (!item.ForFile) + { + /* we don't like some names for alt streams inside archive: + ":sname" for "\" + "c:::sname" for "C:\" + So we ignore alt streams for these cases */ + if (name.IsEmpty()) + { + #if defined(_WIN32) && !defined(UNDER_CE) + needAltStreams = false; + #endif + + /* + // do we need to ignore security info for "\\" folder ? + #ifdef Z7_USE_SECURITY_CODE + needSecurity = false; + #endif + */ + + fullPath = CHAR_PATH_SEPARATOR; + } + #if defined(_WIN32) && !defined(UNDER_CE) + else if (item.IsDriveItem()) + { + needAltStreams = false; + fullPath.Add_PathSepar(); + } + #endif + } + } + + NFind::CFileInfo fi; + #if defined(_WIN32) && !defined(UNDER_CE) + if (IsVirtualFsFolder(phyPrefix, name)) + { + fi.SetAsDir(); + fi.Name = us2fs(name); + } + else + #endif + if (!FindFile_KeepDots(fi, fullPath FOLLOW_LINK_PARAM2)) + { + RINOK(dirItems.AddError(fullPath)) + continue; + } + + /* + #ifdef _WIN32 + #define MY_ERROR_IS_DIR ERROR_FILE_NOT_FOUND + #define MY_ERROR_NOT_DIR DI_DEFAULT_ERROR + #else + #define MY_ERROR_IS_DIR EISDIR + #define MY_ERROR_NOT_DIR ENOTDIR + #endif + */ + + const bool isDir = fi.IsDir(); + if (isDir ? !item.ForDir : !item.ForFile) + { + // RINOK(dirItems.AddError(fullPath, isDir ? MY_ERROR_IS_DIR: MY_ERROR_NOT_DIR)); + RINOK(dirItems.AddError(fullPath, DI_DEFAULT_ERROR)) + continue; + } + { + UStringVector pathParts; + pathParts.Add(fs2us(fi.Name)); + if (curNode.CheckPathToRoot(false, pathParts, !isDir)) + continue; + } + + + if (dirItems.CanIncludeItem(fi.IsDir())) + { + int secureIndex = -1; + #ifdef Z7_USE_SECURITY_CODE + if (needSecurity && dirItems.ReadSecure) + { + RINOK(dirItems.AddSecurityItem(fullPath, secureIndex)) + } + #endif + + dirItems.AddDirFileInfo(phyParent, logParent, secureIndex, fi); + + // we don't scan AltStreams for link files + + #if !defined(UNDER_CE) + { + CDirItem &dirItem = dirItems.Items.Back(); + RINOK(dirItems.SetLinkInfo(dirItem, fi, phyPrefix)) + if (dirItem.ReparseData.Size() != 0) + continue; + } + + #if defined(_WIN32) + if (needAltStreams && dirItems.ScanAltStreams && !fi.IsAltStream) + { + UStringVector pathParts; + pathParts.Add(fs2us(fi.Name)); + RINOK(EnumerateAltStreams(fi, curNode, phyParent, logParent, + fullPath, // including (name) + pathParts, // including (fi.Name) + true, /* addAllSubStreams */ + dirItems)) + } + #endif // defined(_WIN32) + + #endif // !defined(UNDER_CE) + } + + + #ifndef _WIN32 + if (!fi.IsPosixLink()) // posix link can follow to dir + #endif + if (!isDir) + continue; + + UStringVector newParts; + const NWildcard::CCensorNode *nextNode = NULL; + int index = curNode.FindSubNode(name); + if (index >= 0) + { + for (int t = (int)needEnterVector.Size(); t <= index; t++) + needEnterVector.Add(true); + needEnterVector[(unsigned)index] = false; + nextNode = &curNode.SubNodes[(unsigned)index]; + } + else + { + #ifndef _WIN32 + if (fi.IsPosixLink()) + { + // here we can try to resolve posix link + // if the link to dir, then can we follow it + continue; // we don't follow posix link + } + #else + if (dirItems.SymLinks) + { + if (fi.HasReparsePoint()) + { + /* 20.03: in SymLinks mode: we don't enter to directory that + has reparse point and has no CCensorNode */ + continue; + } + } + #endif + nextNode = &curNode; + newParts.Add(name); // don't change it to fi.Name. It's for shortnames support + } + + RINOK(EnumerateDirItems_Spec(*nextNode, phyParent, logParent, fi.Name, phyPrefix, + newParts, dirItems, true)) + } + + for (i = 0; i < curNode.SubNodes.Size(); i++) + { + if (i < needEnterVector.Size()) + if (!needEnterVector[i]) + continue; + const NWildcard::CCensorNode &nextNode = curNode.SubNodes[i]; + FString fullPath = phyPrefix + us2fs(nextNode.Name); + NFind::CFileInfo fi; + + if (nextNode.Name.IsEmpty()) + { + if (phyPrefix.IsEmpty()) + fullPath = CHAR_PATH_SEPARATOR; + } + #ifdef _WIN32 + else if(phyPrefix.IsEmpty() + || (phyPrefix.Len() == NName::kSuperPathPrefixSize + && IsSuperPath(phyPrefix))) + { + if (NWildcard::IsDriveColonName(nextNode.Name)) + fullPath.Add_PathSepar(); + } + #endif + + // we don't want to call fi.Find() for root folder or virtual folder + if ((phyPrefix.IsEmpty() && nextNode.Name.IsEmpty()) + #if defined(_WIN32) && !defined(UNDER_CE) + || IsVirtualFsFolder(phyPrefix, nextNode.Name) + #endif + ) + { + fi.SetAsDir(); + fi.Name = us2fs(nextNode.Name); + } + else + { + if (!FindFile_KeepDots(fi, fullPath FOLLOW_LINK_PARAM2)) + { + if (!nextNode.AreThereIncludeItems()) + continue; + RINOK(dirItems.AddError(fullPath)) + continue; + } + + if (!fi.IsDir()) + { + RINOK(dirItems.AddError(fullPath, DI_DEFAULT_ERROR)) + continue; + } + } + + RINOK(EnumerateDirItems_Spec(nextNode, phyParent, logParent, fi.Name, phyPrefix, + UStringVector(), dirItems, false)) + } + + return S_OK; + } + } + + #ifdef _WIN32 + #ifndef UNDER_CE + + // scan drives, if wildcard is "*:\" + + if (phyPrefix.IsEmpty() && curNode.IncludeItems.Size() > 0) + { + unsigned i; + for (i = 0; i < curNode.IncludeItems.Size(); i++) + { + const NWildcard::CItem &item = curNode.IncludeItems[i]; + if (item.PathParts.Size() < 1) + break; + const UString &name = item.PathParts.Front(); + if (name.Len() != 2 || name[1] != ':') + break; + if (item.PathParts.Size() == 1) + if (item.ForFile || !item.ForDir) + break; + if (NWildcard::IsDriveColonName(name)) + continue; + if (name[0] != '*' && name[0] != '?') + break; + } + if (i == curNode.IncludeItems.Size()) + { + FStringVector driveStrings; + NFind::MyGetLogicalDriveStrings(driveStrings); + for (i = 0; i < driveStrings.Size(); i++) + { + FString driveName = driveStrings[i]; + if (driveName.Len() < 3 || driveName.Back() != '\\') + return E_FAIL; + driveName.DeleteBack(); + NFind::CFileInfo fi; + fi.SetAsDir(); + fi.Name = driveName; + + RINOK(EnumerateForItem(fi, curNode, phyParent, logParent, phyPrefix, + addParts, dirItems, enterToSubFolders)) + } + return S_OK; + } + } + + #endif + #endif + + + CObjectVector files; + + // for (int y = 0; y < 1; y++) + { + // files.Clear(); + RINOK(dirItems.EnumerateOneDir(phyPrefix, files)) + /* + FOR_VECTOR (i, files) + { + #ifdef _WIN32 + // const NFind::CFileInfo &fi = files[i]; + #else + NFind::CFileInfo &fi = files[i]; + { + const NFind::CFileInfo &di = files[i]; + const FString path = phyPrefix + di.Name; + if (!fi.Find_AfterEnumerator(path)) + { + RINOK(dirItems.AddError(path)); + continue; + } + fi.Name = di.Name; + } + #endif + + } + */ + } + + FOR_VECTOR (i, files) + { + #ifdef _WIN32 + const NFind::CFileInfo &fi = files[i]; + #else + const NFind::CFileInfo &fi = files[i]; + /* + NFind::CFileInfo fi; + { + const NFind::CDirEntry &di = files[i]; + const FString path = phyPrefix + di.Name; + if (!fi.Find_AfterEnumerator(path)) + { + RINOK(dirItems.AddError(path)); + continue; + } + fi.Name = di.Name; + } + */ + #endif + + RINOK(EnumerateForItem(fi, curNode, phyParent, logParent, phyPrefix, + addParts, dirItems, enterToSubFolders)) + if (dirItems.Callback && (i & kScanProgressStepMask) == kScanProgressStepMask) + { + RINOK(dirItems.ScanProgress(phyPrefix)) + } + } + + return S_OK; +} + + + + +HRESULT EnumerateItems( + const NWildcard::CCensor &censor, + const NWildcard::ECensorPathMode pathMode, + const UString &addPathPrefix, // prefix that will be added to Logical Path + CDirItems &dirItems) +{ + FOR_VECTOR (i, censor.Pairs) + { + const NWildcard::CPair &pair = censor.Pairs[i]; + const int phyParent = pair.Prefix.IsEmpty() ? -1 : (int)dirItems.AddPrefix(-1, -1, pair.Prefix); + int logParent = -1; + + if (pathMode == NWildcard::k_AbsPath) + logParent = phyParent; + else + { + if (!addPathPrefix.IsEmpty()) + logParent = (int)dirItems.AddPrefix(-1, -1, addPathPrefix); + } + + RINOK(EnumerateDirItems(pair.Head, phyParent, logParent, us2fs(pair.Prefix), UStringVector(), + dirItems, + false // enterToSubFolders + )) + } + dirItems.ReserveDown(); + + #if defined(_WIN32) && !defined(UNDER_CE) + RINOK(dirItems.FillFixedReparse()) + #endif + + #ifndef _WIN32 + RINOK(dirItems.FillDeviceSizes()) + #endif + + return S_OK; +} + + +#if defined(_WIN32) && !defined(UNDER_CE) + +HRESULT CDirItems::FillFixedReparse() +{ + FOR_VECTOR(i, Items) + { + CDirItem &item = Items[i]; + + if (!SymLinks) + { + // continue; // for debug + if (!item.Has_Attrib_ReparsePoint()) + continue; + /* + We want to get properties of target file instead of properies of symbolic link. + Probably this code is unused, because + CFileInfo::Find(with followLink = true) called Fill_From_ByHandleFileInfo() already. + */ + // if (item.IsDir()) continue; + const FString phyPath = GetPhyPath(i); + NFind::CFileInfo fi; + if (fi.Fill_From_ByHandleFileInfo(phyPath)) // item.IsDir() + { + item.Size = fi.Size; + item.CTime = fi.CTime; + item.ATime = fi.ATime; + item.MTime = fi.MTime; + item.Attrib = fi.Attrib; + continue; + } + RINOK(AddError(phyPath)) + continue; + } + + // (SymLinks == true) + if (item.ReparseData.Size() == 0) + continue; + // if (item.Size == 0) + { + // 20.03: we use Reparse Data instead of real data + item.Size = item.ReparseData.Size(); + } + + CReparseAttr attr; + if (!attr.Parse(item.ReparseData, item.ReparseData.Size())) + { + const FString phyPath = GetPhyPath(i); + AddError(phyPath, attr.ErrorCode); + continue; + } + + /* imagex/WIM reduces absolute paths in links (raparse data), + if we archive non root folder. We do same thing here */ + + // bool isWSL = false; + if (attr.IsSymLink_WSL()) + { + // isWSL = true; + // we don't change WSL symlinks + continue; + } + else + { + if (attr.IsRelative_Win()) + continue; + } + + const UString &link = attr.GetPath(); + if (!IsDrivePath(link)) + continue; + // maybe we need to support networks paths also ? + + FString fullPathF; + if (!NDir::MyGetFullPathName(GetPhyPath(i), fullPathF)) + continue; + const UString fullPath = fs2us(fullPathF); + const UString logPath = GetLogPath(i); + if (logPath.Len() >= fullPath.Len()) + continue; + if (CompareFileNames(logPath, fullPath.RightPtr(logPath.Len())) != 0) + continue; + + const UString prefix = fullPath.Left(fullPath.Len() - logPath.Len()); + if (!IsPathSepar(prefix.Back())) + continue; + + const unsigned rootPrefixSize = GetRootPrefixSize(prefix); + if (rootPrefixSize == 0) + continue; + if (rootPrefixSize == prefix.Len()) + continue; // simple case: paths are from root + if (link.Len() <= prefix.Len()) + continue; + if (CompareFileNames(link.Left(prefix.Len()), prefix) != 0) + continue; + + UString newLink = prefix.Left(rootPrefixSize); + newLink += link.Ptr(prefix.Len()); + + CByteBuffer &data = item.ReparseData2; +/* + if (isWSL) + { + Convert_WinPath_to_WslLinuxPath(newLink, true); // is absolute : change it + FillLinkData_WslLink(data, newLink); + } + else +*/ + FillLinkData_WinLink(data, newLink, !attr.IsMountPoint()); + if (data.Size() == 0) + continue; + // item.ReparseData2 = data; + } + return S_OK; +} + +#endif + + +#ifndef _WIN32 + +HRESULT CDirItems::FillDeviceSizes() +{ + { + FOR_VECTOR (i, Items) + { + CDirItem &item = Items[i]; + + if (S_ISBLK(item.mode) && item.Size == 0) + { + const FString phyPath = GetPhyPath(i); + NIO::CInFile inFile; + inFile.PreserveATime = true; + if (inFile.OpenShared(phyPath, ShareForWrite)) // fixme: OpenShared ?? + { + UInt64 size = 0; + if (inFile.GetLength(size)) + item.Size = size; + } + } + if (StoreOwnerName) + { + OwnerNameMap.Add_UInt32(item.uid); + OwnerGroupMap.Add_UInt32(item.gid); + } + } + } + + if (StoreOwnerName) + { + UString u; + AString a; + { + FOR_VECTOR (i, OwnerNameMap.Numbers) + { + // 200K/sec speed + u.Empty(); + const passwd *pw = getpwuid(OwnerNameMap.Numbers[i]); + // printf("\ngetpwuid=%s\n", pw->pw_name); + if (pw) + { + a = pw->pw_name; + ConvertUTF8ToUnicode(a, u); + } + OwnerNameMap.Strings.Add(u); + } + } + { + FOR_VECTOR (i, OwnerGroupMap.Numbers) + { + u.Empty(); + const group *gr = getgrgid(OwnerGroupMap.Numbers[i]); + if (gr) + { + // printf("\ngetgrgid %d %s\n", OwnerGroupMap.Numbers[i], gr->gr_name); + a = gr->gr_name; + ConvertUTF8ToUnicode(a, u); + } + OwnerGroupMap.Strings.Add(u); + } + } + + FOR_VECTOR (i, Items) + { + CDirItem &item = Items[i]; + { + const int index = OwnerNameMap.Find(item.uid); + if (index < 0) throw 1; + item.OwnerNameIndex = index; + } + { + const int index = OwnerGroupMap.Find(item.gid); + if (index < 0) throw 1; + item.OwnerGroupIndex = index; + } + } + } + + + // if (NeedOwnerNames) + { + /* + { + for (unsigned i = 0 ; i < 10000; i++) + { + const passwd *pw = getpwuid(i); + if (pw) + { + UString u; + ConvertUTF8ToUnicode(AString(pw->pw_name), u); + OwnerNameMap.Add(i, u); + OwnerNameMap.Add(i, u); + OwnerNameMap.Add(i, u); + } + const group *gr = getgrgid(i); + if (gr) + { + // we can use utf-8 here. + UString u; + ConvertUTF8ToUnicode(AString(gr->gr_name), u); + OwnerGroupMap.Add(i, u); + } + } + } + */ + /* + { + FOR_VECTOR (i, OwnerNameMap.Strings) + { + AString s; + ConvertUnicodeToUTF8(OwnerNameMap.Strings[i], s); + printf("\n%5d %s", (unsigned)OwnerNameMap.Numbers[i], s.Ptr()); + } + } + { + printf("\n\n=========Groups\n"); + FOR_VECTOR (i, OwnerGroupMap.Strings) + { + AString s; + ConvertUnicodeToUTF8(OwnerGroupMap.Strings[i], s); + printf("\n%5d %s", (unsigned)OwnerGroupMap.Numbers[i], s.Ptr()); + } + } + */ + } + /* + for (unsigned i = 0 ; i < 100000000; i++) + { + // const passwd *pw = getpwuid(1000); + // pw = pw; + int pos = OwnerNameMap.Find(1000); + if (pos < 0 - (int)i) + throw 1; + } + */ + + return S_OK; +} + +#endif + + + +static const char * const kCannotFindArchive = "Cannot find archive"; + +HRESULT EnumerateDirItemsAndSort( + NWildcard::CCensor &censor, + NWildcard::ECensorPathMode censorPathMode, + const UString &addPathPrefix, + UStringVector &sortedPaths, + UStringVector &sortedFullPaths, + CDirItemsStat &st, + IDirItemsCallback *callback) +{ + FStringVector paths; + + { + CDirItems dirItems; + dirItems.Callback = callback; + { + HRESULT res = EnumerateItems(censor, censorPathMode, addPathPrefix, dirItems); + st = dirItems.Stat; + RINOK(res) + } + + FOR_VECTOR (i, dirItems.Items) + { + const CDirItem &dirItem = dirItems.Items[i]; + if (!dirItem.IsDir()) + paths.Add(dirItems.GetPhyPath(i)); + } + } + + if (paths.Size() == 0) + { + // return S_OK; + throw CMessagePathException(kCannotFindArchive); + } + + UStringVector fullPaths; + + unsigned i; + + for (i = 0; i < paths.Size(); i++) + { + FString fullPath; + NFile::NDir::MyGetFullPathName(paths[i], fullPath); + fullPaths.Add(fs2us(fullPath)); + } + + CUIntVector indices; + SortFileNames(fullPaths, indices); + sortedPaths.ClearAndReserve(indices.Size()); + sortedFullPaths.ClearAndReserve(indices.Size()); + + for (i = 0; i < indices.Size(); i++) + { + unsigned index = indices[i]; + sortedPaths.AddInReserved(fs2us(paths[index])); + sortedFullPaths.AddInReserved(fullPaths[index]); + if (i > 0 && CompareFileNames(sortedFullPaths[i], sortedFullPaths[i - 1]) == 0) + throw CMessagePathException("Duplicate archive path:", sortedFullPaths[i]); + } + + return S_OK; +} + + + + +#ifdef _WIN32 + +static bool IsDotsName(const wchar_t *s) +{ + return s[0] == '.' && (s[1] == 0 || (s[1] == '.' && s[2] == 0)); +} + +// This code converts all short file names to long file names. + +static void ConvertToLongName(const UString &prefix, UString &name) +{ + if (name.IsEmpty() + || DoesNameContainWildcard(name) + || IsDotsName(name)) + return; + NFind::CFileInfo fi; + const FString path (us2fs(prefix + name)); + #ifndef UNDER_CE + if (NFile::NName::IsDevicePath(path)) + return; + #endif + if (fi.Find(path)) + name = fs2us(fi.Name); +} + +static void ConvertToLongNames(const UString &prefix, CObjectVector &items) +{ + FOR_VECTOR (i, items) + { + NWildcard::CItem &item = items[i]; + if (item.Recursive || item.PathParts.Size() != 1) + continue; + if (prefix.IsEmpty() && item.IsDriveItem()) + continue; + ConvertToLongName(prefix, item.PathParts.Front()); + } +} + +static void ConvertToLongNames(const UString &prefix, NWildcard::CCensorNode &node) +{ + ConvertToLongNames(prefix, node.IncludeItems); + ConvertToLongNames(prefix, node.ExcludeItems); + unsigned i; + for (i = 0; i < node.SubNodes.Size(); i++) + { + UString &name = node.SubNodes[i].Name; + if (prefix.IsEmpty() && NWildcard::IsDriveColonName(name)) + continue; + ConvertToLongName(prefix, name); + } + // mix folders with same name + for (i = 0; i < node.SubNodes.Size(); i++) + { + NWildcard::CCensorNode &nextNode1 = node.SubNodes[i]; + for (unsigned j = i + 1; j < node.SubNodes.Size();) + { + const NWildcard::CCensorNode &nextNode2 = node.SubNodes[j]; + if (nextNode1.Name.IsEqualTo_NoCase(nextNode2.Name)) + { + nextNode1.IncludeItems += nextNode2.IncludeItems; + nextNode1.ExcludeItems += nextNode2.ExcludeItems; + node.SubNodes.Delete(j); + } + else + j++; + } + } + for (i = 0; i < node.SubNodes.Size(); i++) + { + NWildcard::CCensorNode &nextNode = node.SubNodes[i]; + ConvertToLongNames(prefix + nextNode.Name + WCHAR_PATH_SEPARATOR, nextNode); + } +} + +void ConvertToLongNames(NWildcard::CCensor &censor) +{ + FOR_VECTOR (i, censor.Pairs) + { + NWildcard::CPair &pair = censor.Pairs[i]; + ConvertToLongNames(pair.Prefix, pair.Head); + } +} + +#endif + + +CMessagePathException::CMessagePathException(const char *a, const wchar_t *u) +{ + (*this) += a; + if (u) + { + Add_LF(); + (*this) += u; + } +} + +CMessagePathException::CMessagePathException(const wchar_t *a, const wchar_t *u) +{ + (*this) += a; + if (u) + { + Add_LF(); + (*this) += u; + } +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/EnumDirItems.h b/src/plugins/7zip/7za/cpp/7zip/UI/Common/EnumDirItems.h new file mode 100644 index 00000000..24f1c8bd --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/EnumDirItems.h @@ -0,0 +1,38 @@ +// EnumDirItems.h + +#ifndef ZIP7_INC_ENUM_DIR_ITEMS_H +#define ZIP7_INC_ENUM_DIR_ITEMS_H + +#include "../../../Common/Wildcard.h" + +#include "DirItem.h" + + +HRESULT EnumerateItems( + const NWildcard::CCensor &censor, + NWildcard::ECensorPathMode pathMode, + const UString &addPathPrefix, + CDirItems &dirItems); + + +struct CMessagePathException: public UString +{ + CMessagePathException(const char *a, const wchar_t *u = NULL); + CMessagePathException(const wchar_t *a, const wchar_t *u = NULL); +}; + + +HRESULT EnumerateDirItemsAndSort( + NWildcard::CCensor &censor, + NWildcard::ECensorPathMode pathMode, + const UString &addPathPrefix, + UStringVector &sortedPaths, + UStringVector &sortedFullPaths, + CDirItemsStat &st, + IDirItemsCallback *callback); + +#ifdef _WIN32 +void ConvertToLongNames(NWildcard::CCensor &censor); +#endif + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/ExitCode.h b/src/plugins/7zip/7za/cpp/7zip/UI/Common/ExitCode.h new file mode 100644 index 00000000..2d7b0293 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/ExitCode.h @@ -0,0 +1,27 @@ +// ExitCode.h + +#ifndef ZIP7_INC_EXIT_CODE_H +#define ZIP7_INC_EXIT_CODE_H + +namespace NExitCode { + +enum EEnum { + + kSuccess = 0, // Successful operation + kWarning = 1, // Non fatal error(s) occurred + kFatalError = 2, // A fatal error occurred + // kCRCError = 3, // A CRC error occurred when unpacking + // kLockedArchive = 4, // Attempt to modify an archive previously locked + // kWriteError = 5, // Write to disk error + // kOpenError = 6, // Open file error + kUserError = 7, // Command line option error + kMemoryError = 8, // Not enough memory for operation + // kCreateFileError = 9, // Create file error + + kUserBreak = 255 // User stopped the process + +}; + +} + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/Extract.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Common/Extract.cpp new file mode 100644 index 00000000..c61c79cc --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/Extract.cpp @@ -0,0 +1,583 @@ +// Extract.cpp + +#include "StdAfx.h" + +#include "../../../Common/StringConvert.h" + +#include "../../../Windows/FileDir.h" +#include "../../../Windows/FileName.h" +#include "../../../Windows/ErrorMsg.h" +#include "../../../Windows/PropVariant.h" +#include "../../../Windows/PropVariantConv.h" + +#include "../Common/ExtractingFilePath.h" +#include "../Common/HashCalc.h" + +#include "Extract.h" +#include "SetProperties.h" + +using namespace NWindows; +using namespace NFile; +using namespace NDir; + + +static void SetErrorMessage(const char *message, + const FString &path, HRESULT errorCode, + UString &s) +{ + s = message; + s += " : "; + s += NError::MyFormatMessage(errorCode); + s += " : "; + s += fs2us(path); +} + + +static HRESULT DecompressArchive( + CCodecs *codecs, + const CArchiveLink &arcLink, + UInt64 packSize, + const NWildcard::CCensorNode &wildcardCensor, + const CExtractOptions &options, + bool calcCrc, + IExtractCallbackUI *callback, + IFolderArchiveExtractCallback *callbackFAE, + CArchiveExtractCallback *ecs, + UString &errorMessage, + UInt64 &stdInProcessed) +{ + const CArc &arc = arcLink.Arcs.Back(); + stdInProcessed = 0; + IInArchive *archive = arc.Archive; + CRecordVector realIndices; + + UStringVector removePathParts; + + FString outDir = options.OutputDir; + if (options.OutDirMode != NExtractOutDirMode::k_Direct) + { + UString replaceName = arc.DefaultName; + if (arcLink.Arcs.Size() > 1) + { + // Most "pe" archives have same name of archive subfile "[0]" or ".rsrc_1". + // So it extracts different archives to one folder. + // We will use top level archive name + const CArc &arc0 = arcLink.Arcs[0]; + if (arc0.FormatIndex >= 0 && StringsAreEqualNoCase_Ascii(codecs->Formats[(unsigned)arc0.FormatIndex].Name, "pe")) + replaceName = arc0.DefaultName; + } + const FString correctedName = us2fs(Get_Correct_FsFile_Name(replaceName)); + if (options.OutDirMode == NExtractOutDirMode::k_AddArcName) + { + outDir += correctedName; + NFile::NName::NormalizeDirPathPrefix(outDir); + } + else // eo.OutDirMode == NExtractOutDirMode::k_ReplaceAsterisk; + outDir.Replace(FString("*"), correctedName); + } + + bool elimIsPossible = false; + UString elimPrefix; // only pure name without dir delimiter + FString outDirReduced = outDir; + + if (options.ElimDup.Val && options.PathMode != NExtract::NPathMode::kAbsPaths) + { + UString dirPrefix; + SplitPathToParts_Smart(fs2us(outDir), dirPrefix, elimPrefix); + if (!elimPrefix.IsEmpty()) + { + if (IsPathSepar(elimPrefix.Back())) + elimPrefix.DeleteBack(); + if (!elimPrefix.IsEmpty()) + { + outDirReduced = us2fs(dirPrefix); + elimIsPossible = true; + } + } + } + + const bool allFilesAreAllowed = wildcardCensor.AreAllAllowed(); + + if (!options.StdInMode) + { + UInt32 numItems; + RINOK(archive->GetNumberOfItems(&numItems)) + + CReadArcItem item; + + for (UInt32 i = 0; i < numItems; i++) + { + if (elimIsPossible + || !allFilesAreAllowed + || options.ExcludeDirItems + || options.ExcludeFileItems) + { + RINOK(arc.GetItem(i, item)) + if (item.IsDir ? options.ExcludeDirItems : options.ExcludeFileItems) + continue; + } + else + { + #ifdef SUPPORT_ALT_STREAMS + item.IsAltStream = false; + if (!options.NtOptions.AltStreams.Val && arc.Ask_AltStream) + { + RINOK(Archive_IsItem_AltStream(arc.Archive, i, item.IsAltStream)) + } + #endif + } + + #ifdef SUPPORT_ALT_STREAMS + if (!options.NtOptions.AltStreams.Val && item.IsAltStream) + continue; + #endif + + if (elimIsPossible) + { + const UString &s = + #ifdef SUPPORT_ALT_STREAMS + item.MainPath; + #else + item.Path; + #endif + if (!IsPath1PrefixedByPath2(s, elimPrefix)) + elimIsPossible = false; + else + { + wchar_t c = s[elimPrefix.Len()]; + if (c == 0) + { + if (!item.MainIsDir) + elimIsPossible = false; + } + else if (!IsPathSepar(c)) + elimIsPossible = false; + } + } + + if (!allFilesAreAllowed) + { + if (!CensorNode_CheckPath(wildcardCensor, item)) + continue; + } + + realIndices.Add(i); + } + + if (realIndices.Size() == 0) + { + callback->ThereAreNoFiles(); + return callback->ExtractResult(S_OK); + } + } + + if (elimIsPossible) + { + removePathParts.Add(elimPrefix); + // outDir = outDirReduced; + } + + #ifdef _WIN32 + // GetCorrectFullFsPath doesn't like "..". + // outDir.TrimRight(); + // outDir = GetCorrectFullFsPath(outDir); + #endif + + if (outDir.IsEmpty()) + outDir = "." STRING_PATH_SEPARATOR; + /* + #ifdef _WIN32 + else if (NName::IsAltPathPrefix(outDir)) {} + #endif + */ + else if (!CreateComplexDir(outDir)) + { + const HRESULT res = GetLastError_noZero_HRESULT(); + SetErrorMessage("Cannot create output directory", outDir, res, errorMessage); + return res; + } + + ecs->Init( + options.NtOptions, + options.StdInMode ? &wildcardCensor : NULL, + &arc, + callbackFAE, + options.StdOutMode, options.TestMode, + outDir, + removePathParts, false, + packSize); + + ecs->Is_elimPrefix_Mode = elimIsPossible; + + + #ifdef SUPPORT_LINKS + + if (!options.StdInMode && + !options.TestMode && + options.NtOptions.HardLinks.Val) + { + RINOK(ecs->PrepareHardLinks(&realIndices)) + } + + #endif + + + HRESULT result; + const Int32 testMode = (options.TestMode && !calcCrc) ? 1: 0; + + CArchiveExtractCallback_Closer ecsCloser(ecs); + + if (options.StdInMode) + { + result = archive->Extract(NULL, (UInt32)(Int32)-1, testMode, ecs); + NCOM::CPropVariant prop; + if (archive->GetArchiveProperty(kpidPhySize, &prop) == S_OK) + ConvertPropVariantToUInt64(prop, stdInProcessed); + } + else + { + // v23.02: we reset completed value that could be set by Open() operation + IArchiveExtractCallback *aec = ecs; + const UInt64 val = 0; + RINOK(aec->SetCompleted(&val)) + result = archive->Extract(realIndices.ConstData(), realIndices.Size(), testMode, aec); + } + + const HRESULT res2 = ecsCloser.Close(); + if (result == S_OK) + result = res2; + + return callback->ExtractResult(result); +} + +/* v9.31: BUG was fixed: + Sorted list for file paths was sorted with case insensitive compare function. + But FindInSorted function did binary search via case sensitive compare function */ + +int Find_FileName_InSortedVector(const UStringVector &fileNames, const UString &name); +int Find_FileName_InSortedVector(const UStringVector &fileNames, const UString &name) +{ + unsigned left = 0, right = fileNames.Size(); + while (left != right) + { + const unsigned mid = (unsigned)(((size_t)left + (size_t)right) / 2); + const UString &midVal = fileNames[mid]; + const int comp = CompareFileNames(name, midVal); + if (comp == 0) + return (int)mid; + if (comp < 0) + right = mid; + else + left = mid + 1; + } + return -1; +} + + + +HRESULT Extract( + // DECL_EXTERNAL_CODECS_LOC_VARS + CCodecs *codecs, + const CObjectVector &types, + const CIntVector &excludedFormats, + UStringVector &arcPaths, UStringVector &arcPathsFull, + const NWildcard::CCensorNode &wildcardCensor, + const CExtractOptions &options, + IOpenCallbackUI *openCallback, + IExtractCallbackUI *extractCallback, + IFolderArchiveExtractCallback *faeCallback, + #ifndef Z7_SFX + IHashCalc *hash, + #endif + UString &errorMessage, + CDecompressStat &st) +{ + st.Clear(); + UInt64 totalPackSize = 0; + CRecordVector arcSizes; + + unsigned numArcs = options.StdInMode ? 1 : arcPaths.Size(); + + unsigned i; + + for (i = 0; i < numArcs; i++) + { + NFind::CFileInfo fi; + fi.Size = 0; + if (!options.StdInMode) + { + const FString arcPath = us2fs(arcPaths[i]); + if (!fi.Find_FollowLink(arcPath)) + { + const HRESULT errorCode = GetLastError_noZero_HRESULT(); + SetErrorMessage("Cannot find archive file", arcPath, errorCode, errorMessage); + return errorCode; + } + if (fi.IsDir()) + { + HRESULT errorCode = E_FAIL; + SetErrorMessage("The item is a directory", arcPath, errorCode, errorMessage); + return errorCode; + } + } + arcSizes.Add(fi.Size); + totalPackSize += fi.Size; + } + + CBoolArr skipArcs(numArcs); + for (i = 0; i < numArcs; i++) + skipArcs[i] = false; + + CArchiveExtractCallback *ecs = new CArchiveExtractCallback; + CMyComPtr ec(ecs); + + const bool multi = (numArcs > 1); + + ecs->InitForMulti(multi, + options.PathMode, + options.OverwriteMode, + options.ZoneMode, + false // keepEmptyDirParts + ); + #ifndef Z7_SFX + ecs->SetHashMethods(hash); + #endif + + if (multi) + { + RINOK(faeCallback->SetTotal(totalPackSize)) + } + + UInt64 totalPackProcessed = 0; + bool thereAreNotOpenArcs = false; + + for (i = 0; i < numArcs; i++) + { + if (skipArcs[i]) + continue; + + ecs->InitBeforeNewArchive(); + + const UString &arcPath = arcPaths[i]; + NFind::CFileInfo fi; + if (options.StdInMode) + { + // do we need ctime and mtime? + // fi.ClearBase(); + // fi.Size = 0; // (UInt64)(Int64)-1; + if (!fi.SetAs_StdInFile()) + return GetLastError_noZero_HRESULT(); + } + else + { + if (!fi.Find_FollowLink(us2fs(arcPath)) || fi.IsDir()) + { + const HRESULT errorCode = GetLastError_noZero_HRESULT(); + SetErrorMessage("Cannot find archive file", us2fs(arcPath), errorCode, errorMessage); + return errorCode; + } + } + + /* + #ifndef Z7_NO_CRYPTO + openCallback->Open_Clear_PasswordWasAsked_Flag(); + #endif + */ + + RINOK(extractCallback->BeforeOpen(arcPath, options.TestMode)) + CArchiveLink arcLink; + + CObjectVector types2 = types; + /* + #ifndef Z7_SFX + if (types.IsEmpty()) + { + int pos = arcPath.ReverseFind(L'.'); + if (pos >= 0) + { + UString s = arcPath.Ptr(pos + 1); + int index = codecs->FindFormatForExtension(s); + if (index >= 0 && s.IsEqualTo("001")) + { + s = arcPath.Left(pos); + pos = s.ReverseFind(L'.'); + if (pos >= 0) + { + int index2 = codecs->FindFormatForExtension(s.Ptr(pos + 1)); + if (index2 >= 0) // && s.CompareNoCase(L"rar") != 0 + { + types2.Add(index2); + types2.Add(index); + } + } + } + } + } + #endif + */ + + COpenOptions op; + #ifndef Z7_SFX + op.props = &options.Properties; + #endif + op.codecs = codecs; + op.types = &types2; + op.excludedFormats = &excludedFormats; + op.stdInMode = options.StdInMode; + op.stream = NULL; + op.filePath = arcPath; + + HRESULT result = arcLink.Open_Strict(op, openCallback); + + if (result == E_ABORT) + return result; + + // arcLink.Set_ErrorsText(); + RINOK(extractCallback->OpenResult(codecs, arcLink, arcPath, result)) + + if (result != S_OK) + { + thereAreNotOpenArcs = true; + if (!options.StdInMode) + totalPackProcessed += fi.Size; + continue; + } + + #if defined(_WIN32) && !defined(UNDER_CE) && !defined(Z7_SFX) + if (options.ZoneMode != NExtract::NZoneIdMode::kNone + && !options.StdInMode) + { + ReadZoneFile_Of_BaseFile(us2fs(arcPath), ecs->ZoneBuf); + } + #endif + + + if (arcLink.Arcs.Size() != 0) + { + if (arcLink.GetArc()->IsHashHandler(op)) + { + if (!options.TestMode) + { + /* real Extracting to files is possible. + But user can think that hash archive contains real files. + So we block extracting here. */ + // v23.00 : we don't break process. + RINOK(extractCallback->OpenResult(codecs, arcLink, arcPath, E_NOTIMPL)) + thereAreNotOpenArcs = true; + if (!options.StdInMode) + totalPackProcessed += fi.Size; + continue; + // return E_NOTIMPL; // before v23 + } + FString dirPrefix = us2fs(options.HashDir); + if (dirPrefix.IsEmpty()) + { + if (!NFile::NDir::GetOnlyDirPrefix(us2fs(arcPath), dirPrefix)) + { + // return GetLastError_noZero_HRESULT(); + } + } + if (!dirPrefix.IsEmpty()) + NName::NormalizeDirPathPrefix(dirPrefix); + ecs->DirPathPrefix_for_HashFiles = dirPrefix; + } + } + + if (!options.StdInMode) + { + // numVolumes += arcLink.VolumePaths.Size(); + // arcLink.VolumesSize; + + // totalPackSize -= DeleteUsedFileNamesFromList(arcLink, i + 1, arcPaths, arcPathsFull, &arcSizes); + // numArcs = arcPaths.Size(); + if (arcLink.VolumePaths.Size() != 0) + { + Int64 correctionSize = (Int64)arcLink.VolumesSize; + FOR_VECTOR (v, arcLink.VolumePaths) + { + int index = Find_FileName_InSortedVector(arcPathsFull, arcLink.VolumePaths[v]); + if (index >= 0) + { + if ((unsigned)index > i) + { + skipArcs[(unsigned)index] = true; + correctionSize -= arcSizes[(unsigned)index]; + } + } + } + if (correctionSize != 0) + { + Int64 newPackSize = (Int64)totalPackSize + correctionSize; + if (newPackSize < 0) + newPackSize = 0; + totalPackSize = (UInt64)newPackSize; + RINOK(faeCallback->SetTotal(totalPackSize)) + } + } + } + + /* + // Now openCallback and extractCallback use same object. So we don't need to send password. + + #ifndef Z7_NO_CRYPTO + bool passwordIsDefined; + UString password; + RINOK(openCallback->Open_GetPasswordIfAny(passwordIsDefined, password)) + if (passwordIsDefined) + { + RINOK(extractCallback->SetPassword(password)) + } + #endif + */ + + CArc &arc = arcLink.Arcs.Back(); + arc.MTime.Def = !options.StdInMode + #ifdef _WIN32 + && !fi.IsDevice + #endif + ; + if (arc.MTime.Def) + arc.MTime.Set_From_FiTime(fi.MTime); + + UInt64 packProcessed; + const bool calcCrc = + #ifndef Z7_SFX + (hash != NULL); + #else + false; + #endif + + RINOK(DecompressArchive( + codecs, + arcLink, + fi.Size + arcLink.VolumesSize, + wildcardCensor, + options, + calcCrc, + extractCallback, faeCallback, ecs, + errorMessage, packProcessed)) + + if (!options.StdInMode) + packProcessed = fi.Size + arcLink.VolumesSize; + totalPackProcessed += packProcessed; + ecs->LocalProgressSpec->InSize += packProcessed; + ecs->LocalProgressSpec->OutSize = ecs->UnpackSize; + if (!errorMessage.IsEmpty()) + return E_FAIL; + } + + if (multi || thereAreNotOpenArcs) + { + RINOK(faeCallback->SetTotal(totalPackSize)) + RINOK(faeCallback->SetCompleted(&totalPackProcessed)) + } + + st.NumFolders = ecs->NumFolders; + st.NumFiles = ecs->NumFiles; + st.NumAltStreams = ecs->NumAltStreams; + st.UnpackSize = ecs->UnpackSize; + st.AltStreams_UnpackSize = ecs->AltStreams_UnpackSize; + st.NumArchives = arcPaths.Size(); + st.PackSize = ecs->LocalProgressSpec->InSize; + return S_OK; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/Extract.h b/src/plugins/7zip/7za/cpp/7zip/UI/Common/Extract.h new file mode 100644 index 00000000..130f55a9 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/Extract.h @@ -0,0 +1,118 @@ +// Extract.h + +#ifndef ZIP7_INC_EXTRACT_H +#define ZIP7_INC_EXTRACT_H + +#include "../../../Windows/FileFind.h" + +#include "../../Archive/IArchive.h" + +#include "ArchiveExtractCallback.h" +#include "ArchiveOpenCallback.h" +#include "ExtractMode.h" +#include "Property.h" + +#include "../Common/LoadCodecs.h" + +namespace NExtractOutDirMode { +enum EEnum +{ + k_Direct = 0, + k_AddArcName, + k_ReplaceAsterisk +}; +} + +struct CExtractOptionsBase +{ + CBoolPair ElimDup; + + bool ExcludeDirItems; + bool ExcludeFileItems; + + bool PathMode_Force; + bool OverwriteMode_Force; + NExtract::NPathMode::EEnum PathMode; + NExtract::NOverwriteMode::EEnum OverwriteMode; + NExtract::NZoneIdMode::EEnum ZoneMode; + NExtractOutDirMode::EEnum OutDirMode; + + CExtractNtOptions NtOptions; + + FString OutputDir; // normalized : with path separator at the end + UString HashDir; + + CExtractOptionsBase(): + ExcludeDirItems(false), + ExcludeFileItems(false), + PathMode_Force(false), + OverwriteMode_Force(false), + PathMode(NExtract::NPathMode::kFullPaths), + OverwriteMode(NExtract::NOverwriteMode::kAsk), + ZoneMode(NExtract::NZoneIdMode::kNone), + OutDirMode(NExtractOutDirMode::k_ReplaceAsterisk) + {} +}; + +struct CExtractOptions: public CExtractOptionsBase +{ + bool StdInMode; + bool StdOutMode; + bool YesToAll; + bool TestMode; + + // bool ShowDialog; + // bool PasswordEnabled; + // UString Password; + #ifndef Z7_SFX + CObjectVector Properties; + #endif + + /* + #ifdef Z7_EXTERNAL_CODECS + CCodecs *Codecs; + #endif + */ + + CExtractOptions(): + StdInMode(false), + StdOutMode(false), + YesToAll(false), + TestMode(false) + {} +}; + +struct CDecompressStat +{ + UInt64 NumArchives; + UInt64 UnpackSize; + UInt64 AltStreams_UnpackSize; + UInt64 PackSize; + UInt64 NumFolders; + UInt64 NumFiles; + UInt64 NumAltStreams; + + void Clear() + { + NumArchives = UnpackSize = AltStreams_UnpackSize = PackSize = NumFolders = NumFiles = NumAltStreams = 0; + } +}; + +HRESULT Extract( + // DECL_EXTERNAL_CODECS_LOC_VARS + CCodecs *codecs, + const CObjectVector &types, + const CIntVector &excludedFormats, + UStringVector &archivePaths, UStringVector &archivePathsFull, + const NWildcard::CCensorNode &wildcardCensor, + const CExtractOptions &options, + IOpenCallbackUI *openCallback, + IExtractCallbackUI *extractCallback, + IFolderArchiveExtractCallback *faeCallback, + #ifndef Z7_SFX + IHashCalc *hash, + #endif + UString &errorMessage, + CDecompressStat &st); + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/ExtractMode.h b/src/plugins/7zip/7za/cpp/7zip/UI/Common/ExtractMode.h new file mode 100644 index 00000000..6e38f260 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/ExtractMode.h @@ -0,0 +1,44 @@ +// ExtractMode.h + +#ifndef ZIP7_INC_EXTRACT_MODE_H +#define ZIP7_INC_EXTRACT_MODE_H + +namespace NExtract { + +namespace NPathMode +{ + enum EEnum + { + kFullPaths, + kCurPaths, + kNoPaths, + kAbsPaths, + kNoPathsAlt // alt streams must be extracted without name of base file + }; +} + +namespace NOverwriteMode +{ + enum EEnum + { + kAsk, + kOverwrite, + kSkip, + kRename, + kRenameExisting + }; +} + +namespace NZoneIdMode +{ + enum EEnum + { + kNone, + kAll, + kOffice + }; +} + +} + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/ExtractingFilePath.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Common/ExtractingFilePath.cpp new file mode 100644 index 00000000..e2346703 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/ExtractingFilePath.cpp @@ -0,0 +1,296 @@ +// ExtractingFilePath.cpp + +#include "StdAfx.h" + +#include "../../../Common/Wildcard.h" + +#include "../../../Windows/FileName.h" + +#include "ExtractingFilePath.h" + +extern +bool g_PathTrailReplaceMode; +bool g_PathTrailReplaceMode = + #ifdef _WIN32 + true + #else + false + #endif + ; + + +// #ifdef _WIN32 +static void ReplaceIncorrectChars(UString &s) +{ + { + for (unsigned i = 0; i < s.Len(); i++) + { + wchar_t c = s[i]; + if ( + #ifdef _WIN32 + c == ':' || c == '*' || c == '?' || c < 0x20 || c == '<' || c == '>' || c == '|' || c == '"' + || c == '/' + // || c == 0x202E // RLO + || + #endif + c == WCHAR_PATH_SEPARATOR) + { + #if WCHAR_PATH_SEPARATOR != L'/' + // 22.00 : WSL replacement for backslash + if (c == WCHAR_PATH_SEPARATOR) + c = WCHAR_IN_FILE_NAME_BACKSLASH_REPLACEMENT; + else + #endif + c = '_'; + s.ReplaceOneCharAtPos(i, + c + // (wchar_t)(0xf000 + c) // 21.02 debug: WSL encoding for unsupported characters + ); + } + } + } + + if (g_PathTrailReplaceMode) + { + /* + // if (g_PathTrailReplaceMode == 1) + { + if (!s.IsEmpty()) + { + wchar_t c = s.Back(); + if (c == '.' || c == ' ') + { + // s += (wchar_t)(0x9c); // STRING TERMINATOR + s += (wchar_t)'_'; + } + } + } + else + */ + { + unsigned i; + for (i = s.Len(); i != 0;) + { + wchar_t c = s[i - 1]; + if (c != '.' && c != ' ') + break; + i--; + s.ReplaceOneCharAtPos(i, '_'); + // s.ReplaceOneCharAtPos(i, (c == ' ' ? (wchar_t)(0x2423) : (wchar_t)0x00B7)); + } + /* + if (g_PathTrailReplaceMode > 1 && i != s.Len()) + { + s.DeleteFrom(i); + } + */ + } + } +} +// #endif + +/* WinXP-64 doesn't support ':', '\\' and '/' symbols in name of alt stream. + But colon in postfix ":$DATA" is allowed. + WIN32 functions don't allow empty alt stream name "name:" */ + +void Correct_AltStream_Name(UString &s) +{ + unsigned len = s.Len(); + const unsigned kPostfixSize = 6; + if (s.Len() >= kPostfixSize + && StringsAreEqualNoCase_Ascii(s.RightPtr(kPostfixSize), ":$DATA")) + len -= kPostfixSize; + for (unsigned i = 0; i < len; i++) + { + wchar_t c = s[i]; + if (c == ':' || c == '\\' || c == '/' + || c == 0x202E // RLO + ) + s.ReplaceOneCharAtPos(i, '_'); + } + if (s.IsEmpty()) + s = '_'; +} + +#ifdef _WIN32 + +static const unsigned g_ReservedWithNum_Index = 4; + +static const char * const g_ReservedNames[] = +{ + "CON", "PRN", "AUX", "NUL", + "COM", "LPT" +}; + +static bool IsSupportedName(const UString &name) +{ + for (unsigned i = 0; i < Z7_ARRAY_SIZE(g_ReservedNames); i++) + { + const char *reservedName = g_ReservedNames[i]; + unsigned len = MyStringLen(reservedName); + if (name.Len() < len) + continue; + if (!name.IsPrefixedBy_Ascii_NoCase(reservedName)) + continue; + if (i >= g_ReservedWithNum_Index) + { + wchar_t c = name[len]; + if (c < L'0' || c > L'9') + continue; + len++; + } + for (;;) + { + wchar_t c = name[len++]; + if (c == 0 || c == '.') + return false; + if (c != ' ') + break; + } + } + return true; +} + +static void CorrectUnsupportedName(UString &name) +{ + if (!IsSupportedName(name)) + name.InsertAtFront(L'_'); +} + +#endif + +static void Correct_PathPart(UString &s) +{ + // "." and ".." + if (s.IsEmpty()) + return; + + if (s[0] == '.' && (s[1] == 0 || (s[1] == '.' && s[2] == 0))) + s.Empty(); + // #ifdef _WIN32 + else + ReplaceIncorrectChars(s); + // #endif +} + +// static const char * const k_EmptyReplaceName = "[]"; +static const char k_EmptyReplaceName = '_'; + +UString Get_Correct_FsFile_Name(const UString &name) +{ + UString res = name; + Correct_PathPart(res); + + #ifdef _WIN32 + CorrectUnsupportedName(res); + #endif + + if (res.IsEmpty()) + res = k_EmptyReplaceName; + return res; +} + + +void Correct_FsPath(bool absIsAllowed, bool keepAndReplaceEmptyPrefixes, UStringVector &parts, bool isDir) +{ + unsigned i = 0; + + if (absIsAllowed) + { + #if defined(_WIN32) && !defined(UNDER_CE) + bool isDrive = false; + #endif + + if (parts[0].IsEmpty()) + { + i = 1; + #if defined(_WIN32) && !defined(UNDER_CE) + if (parts.Size() > 1 && parts[1].IsEmpty()) + { + i = 2; + if (parts.Size() > 2 && parts[2].IsEqualTo("?")) + { + i = 3; + if (parts.Size() > 3 && NWindows::NFile::NName::IsDrivePath2(parts[3])) + { + isDrive = true; + i = 4; + } + } + } + #endif + } + #if defined(_WIN32) && !defined(UNDER_CE) + else if (NWindows::NFile::NName::IsDrivePath2(parts[0])) + { + isDrive = true; + i = 1; + } + + if (isDrive) + { + // we convert "c:name" to "c:\name", if absIsAllowed path. + UString &ds = parts[i - 1]; + if (ds.Len() > 2) + { + parts.Insert(i, ds.Ptr(2)); + ds.DeleteFrom(2); + } + } + #endif + } + + if (i != 0) + keepAndReplaceEmptyPrefixes = false; + + for (; i < parts.Size();) + { + UString &s = parts[i]; + + Correct_PathPart(s); + + if (s.IsEmpty()) + { + if (!keepAndReplaceEmptyPrefixes) + if (isDir || i != parts.Size() - 1) + { + parts.Delete(i); + continue; + } + s = k_EmptyReplaceName; + } + else + { + keepAndReplaceEmptyPrefixes = false; + #ifdef _WIN32 + CorrectUnsupportedName(s); + #endif + } + + i++; + } + + if (!isDir) + { + if (parts.IsEmpty()) + parts.Add((UString)k_EmptyReplaceName); + else + { + UString &s = parts.Back(); + if (s.IsEmpty()) + s = k_EmptyReplaceName; + } + } +} + +UString MakePathFromParts(const UStringVector &parts) +{ + UString s; + FOR_VECTOR (i, parts) + { + if (i != 0) + s.Add_PathSepar(); + s += parts[i]; + } + return s; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/ExtractingFilePath.h b/src/plugins/7zip/7za/cpp/7zip/UI/Common/ExtractingFilePath.h new file mode 100644 index 00000000..bb1732fc --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/ExtractingFilePath.h @@ -0,0 +1,31 @@ +// ExtractingFilePath.h + +#ifndef ZIP7_INC_EXTRACTING_FILE_PATH_H +#define ZIP7_INC_EXTRACTING_FILE_PATH_H + +#include "../../../Common/MyString.h" + +// #ifdef _WIN32 +void Correct_AltStream_Name(UString &s); +// #endif + +// replaces unsuported characters, and replaces "." , ".." and "" to "[]" +UString Get_Correct_FsFile_Name(const UString &name); + +/* + Correct_FsPath() corrects path parts to prepare it for File System operations. + It also corrects empty path parts like "\\\\": + - frontal empty path parts : it removes them or changes them to "_" + - another empty path parts : it removes them + if (absIsAllowed && path is absolute) : it removes empty path parts after start absolute path prefix marker + else + { + if (!keepAndReplaceEmptyPrefixes) : it removes empty path parts + if ( keepAndReplaceEmptyPrefixes) : it changes each empty frontal path part to "_" + } +*/ +void Correct_FsPath(bool absIsAllowed, bool keepAndReplaceEmptyPrefixes, UStringVector &parts, bool isDir); + +UString MakePathFromParts(const UStringVector &parts); + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/HashCalc.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Common/HashCalc.cpp new file mode 100644 index 00000000..f026f809 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/HashCalc.cpp @@ -0,0 +1,2273 @@ +// HashCalc.cpp + +#include "StdAfx.h" + +#include "../../../../C/Alloc.h" +#include "../../../../C/CpuArch.h" + +#include "../../../Common/DynLimBuf.h" +#include "../../../Common/IntToString.h" +#include "../../../Common/StringToInt.h" + +#include "../../Common/FileStreams.h" +#include "../../Common/ProgressUtils.h" +#include "../../Common/StreamObjects.h" +#include "../../Common/StreamUtils.h" + +#include "../../Archive/Common/ItemNameUtils.h" +#include "../../Archive/IArchive.h" + +#include "EnumDirItems.h" +#include "HashCalc.h" + +using namespace NWindows; + +#ifdef Z7_EXTERNAL_CODECS +extern const CExternalCodecs *g_ExternalCodecs_Ptr; +#endif + +class CHashMidBuf +{ + void *_data; +public: + CHashMidBuf(): _data(NULL) {} + operator void *() { return _data; } + bool Alloc(size_t size) + { + if (_data) + return false; + _data = ::MidAlloc(size); + return _data != NULL; + } + ~CHashMidBuf() { ::MidFree(_data); } +}; + +static const char * const k_DefaultHashMethod = "CRC32"; + +HRESULT CHashBundle::SetMethods(DECL_EXTERNAL_CODECS_LOC_VARS const UStringVector &hashMethods) +{ + UStringVector names = hashMethods; + if (names.IsEmpty()) + names.Add(UString(k_DefaultHashMethod)); + + CRecordVector ids; + CObjectVector methods; + + unsigned i; + for (i = 0; i < names.Size(); i++) + { + COneMethodInfo m; + RINOK(m.ParseMethodFromString(names[i])) + + if (m.MethodName.IsEmpty()) + m.MethodName = k_DefaultHashMethod; + + if (m.MethodName.IsEqualTo("*")) + { + CRecordVector tempMethods; + GetHashMethods(EXTERNAL_CODECS_LOC_VARS tempMethods); + methods.Clear(); + ids.Clear(); + FOR_VECTOR (t, tempMethods) + { + unsigned index = ids.AddToUniqueSorted(tempMethods[t]); + if (ids.Size() != methods.Size()) + methods.Insert(index, m); + } + break; + } + else + { + // m.MethodName.RemoveChar(L'-'); + CMethodId id; + if (!FindHashMethod(EXTERNAL_CODECS_LOC_VARS m.MethodName, id)) + return E_NOTIMPL; + unsigned index = ids.AddToUniqueSorted(id); + if (ids.Size() != methods.Size()) + methods.Insert(index, m); + } + } + + for (i = 0; i < ids.Size(); i++) + { + CMyComPtr hasher; + AString name; + RINOK(CreateHasher(EXTERNAL_CODECS_LOC_VARS ids[i], name, hasher)) + if (!hasher) + throw "Can't create hasher"; + const COneMethodInfo &m = methods[i]; + { + CMyComPtr scp; + hasher.QueryInterface(IID_ICompressSetCoderProperties, &scp); + if (scp) + RINOK(m.SetCoderProps(scp, NULL)) + } + const UInt32 digestSize = hasher->GetDigestSize(); + if (digestSize > k_HashCalc_DigestSize_Max) + return E_NOTIMPL; + CHasherState &h = Hashers.AddNew(); + h.DigestSize = digestSize; + h.Hasher = hasher; + h.Name = name; + for (unsigned k = 0; k < k_HashCalc_NumGroups; k++) + h.InitDigestGroup(k); + } + + return S_OK; +} + +void CHashBundle::InitForNewFile() +{ + CurSize = 0; + FOR_VECTOR (i, Hashers) + { + CHasherState &h = Hashers[i]; + h.Hasher->Init(); + h.InitDigestGroup(k_HashCalc_Index_Current); + } +} + +void CHashBundle::Update(const void *data, UInt32 size) +{ + CurSize += size; + FOR_VECTOR (i, Hashers) + Hashers[i].Hasher->Update(data, size); +} + +void CHashBundle::SetSize(UInt64 size) +{ + CurSize = size; +} + +static void AddDigests(Byte *dest, const Byte *src, UInt32 size) +{ + unsigned next = 0; + /* + // we could use big-endian addition for sha-1 and sha-256 + // but another hashers are little-endian + if (size > 8) + { + for (unsigned i = size; i != 0;) + { + i--; + next += (unsigned)dest[i] + (unsigned)src[i]; + dest[i] = (Byte)next; + next >>= 8; + } + } + else + */ + { + for (unsigned i = 0; i < size; i++) + { + next += (unsigned)dest[i] + (unsigned)src[i]; + dest[i] = (Byte)next; + next >>= 8; + } + } + + // we use little-endian to store extra bytes + dest += k_HashCalc_DigestSize_Max; + for (unsigned i = 0; i < k_HashCalc_ExtraSize; i++) + { + next += (unsigned)dest[i]; + dest[i] = (Byte)next; + next >>= 8; + } +} + +void CHasherState::AddDigest(unsigned groupIndex, const Byte *data) +{ + NumSums[groupIndex]++; + AddDigests(Digests[groupIndex], data, DigestSize); +} + +void CHashBundle::Final(bool isDir, bool isAltStream, const UString &path) +{ + if (isDir) + NumDirs++; + else if (isAltStream) + { + NumAltStreams++; + AltStreamsSize += CurSize; + } + else + { + NumFiles++; + FilesSize += CurSize; + } + + Byte pre[16]; + memset(pre, 0, sizeof(pre)); + if (isDir) + pre[0] = 1; + + FOR_VECTOR (i, Hashers) + { + CHasherState &h = Hashers[i]; + if (!isDir) + { + h.Hasher->Final(h.Digests[0]); // k_HashCalc_Index_Current + if (!isAltStream) + h.AddDigest(k_HashCalc_Index_DataSum, h.Digests[0]); + } + + h.Hasher->Init(); + h.Hasher->Update(pre, sizeof(pre)); + h.Hasher->Update(h.Digests[0], h.DigestSize); + + for (unsigned k = 0; k < path.Len(); k++) + { + wchar_t c = path[k]; + + // 21.04: we want same hash for linux and windows paths + #if CHAR_PATH_SEPARATOR != '/' + if (c == CHAR_PATH_SEPARATOR) + c = '/'; + // if (c == (wchar_t)('\\' + 0xf000)) c = '\\'; // to debug WSL + // if (c > 0xf000 && c < 0xf080) c -= 0xf000; // to debug WSL + #endif + + Byte temp[2] = { (Byte)(c & 0xFF), (Byte)((c >> 8) & 0xFF) }; + h.Hasher->Update(temp, 2); + } + + Byte tempDigest[k_HashCalc_DigestSize_Max]; + h.Hasher->Final(tempDigest); + if (!isAltStream) + h.AddDigest(k_HashCalc_Index_NamesSum, tempDigest); + h.AddDigest(k_HashCalc_Index_StreamsSum, tempDigest); + } +} + + +static void CSum_Name_OriginalToEscape(const AString &src, AString &dest) +{ + dest.Empty(); + for (unsigned i = 0; i < src.Len();) + { + char c = src[i++]; + if (c == '\n') + { + dest.Add_Char('\\'); + c = 'n'; + } + else if (c == '\\') + dest.Add_Char('\\'); + dest.Add_Char(c); + } +} + + +static bool CSum_Name_EscapeToOriginal(const char *s, AString &dest) +{ + bool isOK = true; + dest.Empty(); + for (;;) + { + char c = *s++; + if (c == 0) + break; + if (c == '\\') + { + const char c1 = *s; + if (c1 == 'n') + { + c = '\n'; + s++; + } + else if (c1 == '\\') + { + c = c1; + s++; + } + else + { + // original md5sum returns NULL for such bad strings + isOK = false; + } + } + dest.Add_Char(c); + } + return isOK; +} + + + +static void SetSpacesAndNul(char *s, unsigned num) +{ + for (unsigned i = 0; i < num; i++) + s[i] = ' '; + s[num] = 0; +} + +static const unsigned kHashColumnWidth_Min = 4 * 2; + +static unsigned GetColumnWidth(unsigned digestSize) +{ + const unsigned width = digestSize * 2; + return width < kHashColumnWidth_Min ? kHashColumnWidth_Min: width; +} + + +static void AddHashResultLine( + AString &_s, + // bool showHash, + // UInt64 fileSize, bool showSize, + const CObjectVector &hashers + // unsigned digestIndex, = k_HashCalc_Index_Current + ) +{ + FOR_VECTOR (i, hashers) + { + const CHasherState &h = hashers[i]; + char s[k_HashCalc_DigestSize_Max * 2 + 64]; + s[0] = 0; + // if (showHash) + HashHexToString(s, h.Digests[k_HashCalc_Index_Current], h.DigestSize); + const unsigned pos = (unsigned)strlen(s); + const int numSpaces = (int)GetColumnWidth(h.DigestSize) - (int)pos; + if (numSpaces > 0) + SetSpacesAndNul(s + pos, (unsigned)numSpaces); + if (i != 0) + _s.Add_Space(); + _s += s; + } + + /* + if (showSize) + { + _s.Add_Space(); + static const unsigned kSizeField_Len = 13; // same as in HashCon.cpp + char s[kSizeField_Len + 32]; + char *p = s; + SetSpacesAndNul(s, kSizeField_Len); + p = s + kSizeField_Len; + ConvertUInt64ToString(fileSize, p); + int numSpaces = (int)kSizeField_Len - (int)strlen(p); + if (numSpaces > 0) + p -= (unsigned)numSpaces; + _s += p; + } + */ +} + + +static void Add_LF(CDynLimBuf &hashFileString, const CHashOptionsLocal &options) +{ + hashFileString += (char)(options.HashMode_Zero.Val ? 0 : '\n'); +} + + + + +static void WriteLine(CDynLimBuf &hashFileString, + const CHashOptionsLocal &options, + const UString &path2, + bool isDir, + const AString &methodName, + const AString &hashesString) +{ + if (options.HashMode_OnlyHash.Val) + { + hashFileString += hashesString; + Add_LF(hashFileString, options); + return; + } + + UString path = path2; + + bool isBin = false; + const bool zeroMode = options.HashMode_Zero.Val; + const bool tagMode = options.HashMode_Tag.Val; + +#if CHAR_PATH_SEPARATOR != '/' + path.Replace(WCHAR_PATH_SEPARATOR, L'/'); + // path.Replace((wchar_t)('\\' + 0xf000), L'\\'); // to debug WSL +#endif + + AString utf8; + ConvertUnicodeToUTF8(path, utf8); + + AString esc; + CSum_Name_OriginalToEscape(utf8, esc); + + if (!zeroMode) + { + if (esc != utf8) + { + /* Original md5sum writes escape in that case. + We do same for compatibility with original md5sum. */ + hashFileString += '\\'; + } + } + + if (isDir && !esc.IsEmpty() && esc.Back() != '/') + esc.Add_Slash(); + + if (tagMode) + { + if (!methodName.IsEmpty()) + { + hashFileString += methodName; + hashFileString += ' '; + } + hashFileString += '('; + hashFileString += esc; + hashFileString += ')'; + hashFileString += " = "; + } + + hashFileString += hashesString; + + if (!tagMode) + { + hashFileString += ' '; + hashFileString += (char)(isBin ? '*' : ' '); + hashFileString += esc; + } + + Add_LF(hashFileString, options); +} + + +static void Convert_TagName_to_MethodName(AString &method) +{ + // we need to convert at least SHA512/256 to SHA512-256, and SHA512/224 to SHA512-224 + // but we convert any '/' to '-'. + method.Replace('/', '-'); +} + +static void Convert_MethodName_to_TagName(AString &method) +{ + if (method.IsPrefixedBy_Ascii_NoCase("SHA512-2")) + method.ReplaceOneCharAtPos(6, '/'); +} + + +static void WriteLine(CDynLimBuf &hashFileString, + const CHashOptionsLocal &options, + const UString &path, + bool isDir, + const CHashBundle &hb) +{ + AString methodName; + if (!hb.Hashers.IsEmpty()) + { + methodName = hb.Hashers[0].Name; + Convert_MethodName_to_TagName(methodName); + } + AString hashesString; + AddHashResultLine(hashesString, hb.Hashers); + WriteLine(hashFileString, options, path, isDir, methodName, hashesString); +} + + +HRESULT HashCalc( + DECL_EXTERNAL_CODECS_LOC_VARS + const NWildcard::CCensor &censor, + const CHashOptions &options, + AString &errorInfo, + IHashCallbackUI *callback) +{ + CDirItems dirItems; + dirItems.Callback = callback; + + if (options.StdInMode) + { + CDirItem di; + if (!di.SetAs_StdInFile()) + return GetLastError_noZero_HRESULT(); + dirItems.Items.Add(di); + } + else + { + RINOK(callback->StartScanning()) + + dirItems.SymLinks = options.SymLinks.Val; + dirItems.ScanAltStreams = options.AltStreamsMode; + dirItems.ExcludeDirItems = censor.ExcludeDirItems; + dirItems.ExcludeFileItems = censor.ExcludeFileItems; + + dirItems.ShareForWrite = options.OpenShareForWrite; + + HRESULT res = EnumerateItems(censor, + options.PathMode, + UString(), + dirItems); + + if (res != S_OK) + { + if (res != E_ABORT) + errorInfo = "Scanning error"; + return res; + } + RINOK(callback->FinishScanning(dirItems.Stat)) + } + + unsigned i; + CHashBundle hb; + RINOK(hb.SetMethods(EXTERNAL_CODECS_LOC_VARS options.Methods)) + // hb.Init(); + + hb.NumErrors = dirItems.Stat.NumErrors; + + UInt64 totalSize = 0; + if (options.StdInMode) + { + RINOK(callback->SetNumFiles(1)) + } + else + { + totalSize = dirItems.Stat.GetTotalBytes(); + RINOK(callback->SetTotal(totalSize)) + } + + const UInt32 kBufSize = 1 << 15; + CHashMidBuf buf; + if (!buf.Alloc(kBufSize)) + return E_OUTOFMEMORY; + + UInt64 completeValue = 0; + + RINOK(callback->BeforeFirstFile(hb)) + + /* + CDynLimBuf hashFileString((size_t)1 << 31); + const bool needGenerate = !options.HashFilePath.IsEmpty(); + */ + + for (i = 0; i < dirItems.Items.Size(); i++) + { + CMyComPtr inStream; + UString path; + bool isDir = false; + bool isAltStream = false; + + if (options.StdInMode) + { +#if 1 + inStream = new CStdInFileStream; +#else + if (!CreateStdInStream(inStream)) + { + const DWORD lastError = ::GetLastError(); + const HRESULT res = callback->OpenFileError(FString("stdin"), lastError); + hb.NumErrors++; + if (res != S_FALSE && res != S_OK) + return res; + continue; + } +#endif + } + else + { + path = dirItems.GetLogPath(i); + const CDirItem &di = dirItems.Items[i]; + #ifdef _WIN32 + isAltStream = di.IsAltStream; + #endif + + #ifndef UNDER_CE + // if (di.AreReparseData()) + if (di.ReparseData.Size() != 0) + { + CBufInStream *inStreamSpec = new CBufInStream(); + inStream = inStreamSpec; + inStreamSpec->Init(di.ReparseData, di.ReparseData.Size()); + } + else + #endif + { + CInFileStream *inStreamSpec = new CInFileStream; + inStreamSpec->Set_PreserveATime(options.PreserveATime); + inStream = inStreamSpec; + isDir = di.IsDir(); + if (!isDir) + { + const FString phyPath = dirItems.GetPhyPath(i); + if (!inStreamSpec->OpenShared(phyPath, options.OpenShareForWrite)) + { + const HRESULT res = callback->OpenFileError(phyPath, ::GetLastError()); + hb.NumErrors++; + if (res != S_FALSE) + return res; + continue; + } + if (!options.StdInMode) + { + UInt64 curSize = 0; + if (inStreamSpec->GetSize(&curSize) == S_OK) + { + if (curSize > di.Size) + { + totalSize += curSize - di.Size; + RINOK(callback->SetTotal(totalSize)) + // printf("\ntotal = %d MiB\n", (unsigned)(totalSize >> 20)); + } + } + } + // inStreamSpec->ReloadProps(); + } + } + } + + RINOK(callback->GetStream(path, isDir)) + UInt64 fileSize = 0; + + hb.InitForNewFile(); + + if (!isDir) + { + for (UInt32 step = 0;; step++) + { + if ((step & 0xFF) == 0) + { + // printf("\ncompl = %d\n", (unsigned)(completeValue >> 20)); + RINOK(callback->SetCompleted(&completeValue)) + } + UInt32 size; + RINOK(inStream->Read(buf, kBufSize, &size)) + if (size == 0) + break; + hb.Update(buf, size); + fileSize += size; + completeValue += size; + } + } + + hb.Final(isDir, isAltStream, path); + + /* + if (needGenerate + && (options.HashMode_Dirs.Val || !isDir)) + { + WriteLine(hashFileString, + options, + path, // change it + isDir, + hb); + + if (hashFileString.IsError()) + return E_OUTOFMEMORY; + } + */ + + RINOK(callback->SetOperationResult(fileSize, hb, !isDir)) + RINOK(callback->SetCompleted(&completeValue)) + } + + /* + if (needGenerate) + { + NFile::NIO::COutFile file; + if (!file.Create(us2fs(options.HashFilePath), true)) // createAlways + return GetLastError_noZero_HRESULT(); + if (!file.WriteFull(hashFileString, hashFileString.Len())) + return GetLastError_noZero_HRESULT(); + } + */ + + return callback->AfterLastFile(hb); +} + + +void HashHexToString(char *dest, const Byte *data, size_t size) +{ + if (!data) + { + for (size_t i = 0; i < size; i++) + { + dest[0] = ' '; + dest[1] = ' '; + dest += 2; + } + *dest = 0; + return; + } + + if (size > 8) + ConvertDataToHex_Lower(dest, data, size); + else if (size == 0) + { + *dest = 0; + return; + } + else + { + const char *dest_start = dest; + dest += size * 2; + *dest = 0; + do + { + const size_t b = *data++; + dest -= 2; + dest[0] = GET_HEX_CHAR_UPPER(b >> 4); + dest[1] = GET_HEX_CHAR_UPPER(b & 15); + } + while (dest != dest_start); + } +} + +void CHasherState::WriteToString(unsigned digestIndex, char *s) const +{ + HashHexToString(s, Digests[digestIndex], DigestSize); + + if (digestIndex != 0 && NumSums[digestIndex] != 1) + { + unsigned numExtraBytes = GetNumExtraBytes_for_Group(digestIndex); + if (numExtraBytes > 4) + numExtraBytes = 8; + else // if (numExtraBytes >= 0) + numExtraBytes = 4; + // if (numExtraBytes != 0) + { + s += strlen(s); + *s++ = '-'; + // *s = 0; + HashHexToString(s, GetExtraData_for_Group(digestIndex), numExtraBytes); + } + } +} + + + +// ---------- Hash Handler ---------- + +namespace NHash { + +#define IsWhite(c) ((c) == ' ' || (c) == '\t') + +bool CHashPair::IsDir() const +{ + if (Name.IsEmpty() || Name.Back() != '/') + return false; + // here we expect that Dir items contain only zeros or no Hash + for (size_t i = 0; i < Hash.Size(); i++) + if (Hash.ConstData()[i] != 0) + return false; + return true; +} + + +bool CHashPair::ParseCksum(const char *s) +{ + const char *end; + + const UInt32 crc = ConvertStringToUInt32(s, &end); + if (*end != ' ') + return false; + end++; + + const UInt64 size = ConvertStringToUInt64(end, &end); + if (*end != ' ') + return false; + end++; + + Name = end; + + Hash.Alloc(4); + SetBe32a(Hash, crc) + + Size_from_Arc = size; + Size_from_Arc_Defined = true; + + return true; +} + + + +static const char *SkipWhite(const char *s) +{ + while (IsWhite(*s)) + s++; + return s; +} + +static const char * const k_CsumMethodNames[] = +{ + "sha256" + , "sha224" + , "sha512-224" + , "sha512-256" + , "sha384" + , "sha512" + , "sha3-224" + , "sha3-256" + , "sha3-384" + , "sha3-512" +// , "shake128" +// , "shake256" + , "sha1" + , "sha2" + , "sha3" + , "sha" + , "md5" + , "blake2s" + , "blake2b" + , "blake2sp" + , "xxh64" + , "crc32" + , "crc64" + , "cksum" +}; + + +// returns true, if (method) is known hash method or hash method group name. +static bool GetMethod_from_FileName(const UString &name, AString &method) +{ + method.Empty(); + AString s; + ConvertUnicodeToUTF8(name, s); + const int dotPos = s.ReverseFind_Dot(); + if (dotPos >= 0) + { + method = s.Ptr(dotPos + 1); + if (method.IsEqualTo_Ascii_NoCase("txt") || + method.IsEqualTo_Ascii_NoCase("asc")) + { + method.Empty(); + const int dotPos2 = s.Find('.'); + if (dotPos2 >= 0) + s.DeleteFrom(dotPos2); + } + } + if (method.IsEmpty()) + { + // we support file names with "sum" and "sums" postfixes: "sha256sum", "sha256sums" + unsigned size; + if (s.Len() > 4 && StringsAreEqualNoCase_Ascii(s.RightPtr(4), "sums")) + size = 4; + else if (s.Len() > 3 && StringsAreEqualNoCase_Ascii(s.RightPtr(3), "sum")) + size = 3; + else + return false; + method = s; + method.DeleteFrom(s.Len() - size); + } + + unsigned i; + for (i = 0; i < Z7_ARRAY_SIZE(k_CsumMethodNames); i++) + { + const char *m = k_CsumMethodNames[i]; + if (method.IsEqualTo_Ascii_NoCase(m)) + { + // method = m; // we can get lowcase + return true; + } + } + +/* + for (i = 0; i < Z7_ARRAY_SIZE(k_CsumMethodNames); i++) + { + const char *m = k_CsumMethodNames[i]; + if (method.IsPrefixedBy_Ascii_NoCase(m)) + { + method = m; // we get lowcase + return true; + } + } +*/ + return false; +} + + +bool CHashPair::Parse(const char *s) +{ + // here we keep compatibility with original md5sum / shasum + bool escape = false; + + s = SkipWhite(s); + + if (*s == '\\') + { + s++; + escape = true; + } + Escape = escape; + + // const char *kMethod = GetMethod_from_FileName(s); + // if (kMethod) + if ((size_t)(FindNonHexChar(s) - s) < 4) + { + // BSD-style checksum line + { + const char *s2 = s; + for (; *s2 != 0; s2++) + { + const char c = *s2; + if (c == 0) + return false; + if (c == ' ' || c == '(') + break; + } + Method.SetFrom(s, (unsigned)(s2 - s)); + s = s2; + } + IsBSD = true; + if (*s == ' ') + s++; + if (*s != '(') + return false; + s++; + { + const char *s2 = s; + for (; *s2 != 0; s2++) + {} + for (;;) + { + s2--; + if (s2 < s) + return false; + if (*s2 == ')') + break; + } + Name.SetFrom(s, (unsigned)(s2 - s)); + s = s2 + 1; + } + + s = SkipWhite(s); + if (*s != '=') + return false; + s++; + s = SkipWhite(s); + } + + { + const size_t numChars = (size_t)(FindNonHexChar(s) - s) & ~(size_t)1; + Hash.Alloc(numChars / 2); + if ((size_t)(ParseHexString(s, Hash) - Hash) != numChars / 2) + throw 101; + HashString.SetFrom(s, (unsigned)numChars); + s += numChars; + } + + if (IsBSD) + { + if (*s != 0) + return false; + if (escape) + { + const AString temp (Name); + return CSum_Name_EscapeToOriginal(temp, Name); + } + return true; + } + + if (*s == 0) + return true; + + if (*s != ' ') + return false; + s++; + const char c = *s; + if (c != ' ' + && c != '*' + && c != 'U' // shasum Universal + && c != '^' // shasum 0/1 + ) + return false; + Mode = c; + s++; + if (escape) + return CSum_Name_EscapeToOriginal(s, Name); + Name = s; + return true; +} + + +static bool GetLine(CByteBuffer &buf, bool zeroMode, bool cr_lf_Mode, size_t &posCur, AString &s) +{ + s.Empty(); + size_t pos = posCur; + const Byte *p = buf; + unsigned numDigits = 0; + for (; pos < buf.Size(); pos++) + { + const Byte b = p[pos]; + if (b == 0) + { + numDigits = 1; + break; + } + if (zeroMode) + continue; + if (b == 0x0a) + { + numDigits = 1; + break; + } + if (!cr_lf_Mode) + continue; + if (b == 0x0d) + { + if (pos + 1 >= buf.Size()) + { + numDigits = 1; + break; + // return false; + } + if (p[pos + 1] == 0x0a) + { + numDigits = 2; + break; + } + } + } + s.SetFrom((const char *)(p + posCur), (unsigned)(pos - posCur)); + posCur = pos + numDigits; + return true; +} + + +static bool Is_CR_LF_Data(const Byte *buf, size_t size) +{ + bool isCrLf = false; + for (size_t i = 0; i < size;) + { + const Byte b = buf[i]; + if (b == 0x0a) + return false; + if (b == 0x0d) + { + if (i == size - 1) + return false; + if (buf[i + 1] != 0x0a) + return false; + isCrLf = true; + i += 2; + } + else + i++; + } + return isCrLf; +} + + +static const Byte kArcProps[] = +{ + // kpidComment, + kpidCharacts +}; + +static const Byte kProps[] = +{ + kpidPath, + kpidSize, + kpidPackSize, + kpidMethod +}; + +static const Byte kRawProps[] = +{ + kpidChecksum +}; + + +Z7_COM7F_IMF(CHandler::GetParent(UInt32 /* index */ , UInt32 *parent, UInt32 *parentType)) +{ + *parentType = NParentType::kDir; + *parent = (UInt32)(Int32)-1; + return S_OK; +} + +Z7_COM7F_IMF(CHandler::GetNumRawProps(UInt32 *numProps)) +{ + *numProps = Z7_ARRAY_SIZE(kRawProps); + return S_OK; +} + +Z7_COM7F_IMF(CHandler::GetRawPropInfo(UInt32 index, BSTR *name, PROPID *propID)) +{ + *propID = kRawProps[index]; + *name = NULL; + return S_OK; +} + +Z7_COM7F_IMF(CHandler::GetRawProp(UInt32 index, PROPID propID, const void **data, UInt32 *dataSize, UInt32 *propType)) +{ + *data = NULL; + *dataSize = 0; + *propType = 0; + + if (propID == kpidChecksum) + { + const CHashPair &hp = HashPairs[index]; + if (hp.Hash.Size() != 0) + { + *data = hp.Hash; + *dataSize = (UInt32)hp.Hash.Size(); + *propType = NPropDataType::kRaw; + } + return S_OK; + } + + return S_OK; +} + +IMP_IInArchive_Props +IMP_IInArchive_ArcProps + +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) +{ + *numItems = HashPairs.Size(); + return S_OK; +} + +static void Add_OptSpace_String(UString &dest, const char *src) +{ + dest.Add_Space_if_NotEmpty(); + dest += src; +} + +Z7_COM7F_IMF(CHandler::GetArchiveProperty(PROPID propID, PROPVARIANT *value)) +{ + NCOM::CPropVariant prop; + switch (propID) + { + case kpidPhySize: if (_phySize != 0) prop = _phySize; break; + /* + case kpidErrorFlags: + { + UInt32 v = 0; + if (!_isArc) v |= kpv_ErrorFlags_IsNotArc; + // if (_sres == k_Base64_RES_NeedMoreInput) v |= kpv_ErrorFlags_UnexpectedEnd; + if (v != 0) + prop = v; + break; + } + */ + case kpidCharacts: + { + UString s; + if (_hashSize_Defined) + { + s.Add_Space_if_NotEmpty(); + s.Add_UInt32(_hashSize * 8); + s += "-bit"; + } + if (_is_PgpMethod) + { + Add_OptSpace_String(s, "PGP"); + if (!_pgpMethod.IsEmpty()) + { + s.Add_Colon(); + s += _pgpMethod; + } + } + if (_is_ZeroMode) + Add_OptSpace_String(s, "ZERO"); + if (_are_there_Tags) + Add_OptSpace_String(s, "TAG"); + if (_are_there_Dirs) + Add_OptSpace_String(s, "DIRS"); + if (!_method_from_FileName.IsEmpty()) + { + Add_OptSpace_String(s, "filename_method:"); + s += _method_from_FileName; + if (!_is_KnownMethod_in_FileName) + s += ":UNKNOWN"; + } + if (!_methods.IsEmpty()) + { + Add_OptSpace_String(s, "cmd_method:"); + s += _methods[0]; + } + prop = s; + break; + } + + case kpidReadOnly: + { + if (_isArc) + if (!CanUpdate()) + prop = true; + break; + } + default: break; + } + prop.Detach(value); + return S_OK; +} + + +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) +{ + // COM_TRY_BEGIN + NCOM::CPropVariant prop; + const CHashPair &hp = HashPairs[index]; + switch (propID) + { + case kpidIsDir: + { + prop = hp.IsDir(); + break; + } + case kpidPath: + { + UString path; + hp.Get_UString_Path(path); + + bool useBackslashReplacement = true; + if (_supportWindowsBackslash && !hp.Escape && path.Find(L"\\\\") < 0) + { +#if WCHAR_PATH_SEPARATOR == L'/' + path.Replace(L'\\', L'/'); +#else + useBackslashReplacement = false; +#endif + } + NArchive::NItemName::ReplaceToOsSlashes_Remove_TailSlash( + path, useBackslashReplacement); + prop = path; + break; + } + case kpidSize: + { + // client needs processed size of last file + if (hp.Size_from_Disk_Defined) + prop = (UInt64)hp.Size_from_Disk; + else if (hp.Size_from_Arc_Defined) + prop = (UInt64)hp.Size_from_Arc; + break; + } + case kpidPackSize: + { + prop = (UInt64)hp.Hash.Size(); + break; + } + case kpidMethod: + { + if (!hp.Method.IsEmpty()) + prop = hp.Method; + break; + } + default: break; + } + prop.Detach(value); + return S_OK; + // COM_TRY_END +} + + +static HRESULT ReadStream_to_Buf(IInStream *stream, CByteBuffer &buf, IArchiveOpenCallback *openCallback) +{ + buf.Free(); + UInt64 len; + RINOK(InStream_AtBegin_GetSize(stream, len)) + if (len == 0 || len >= ((UInt64)1 << 31)) + return S_FALSE; + buf.Alloc((size_t)len); + UInt64 pos = 0; + // return ReadStream_FALSE(stream, buf, (size_t)len); + for (;;) + { + const UInt32 kBlockSize = ((UInt32)1 << 24); + const UInt32 curSize = (len < kBlockSize) ? (UInt32)len : kBlockSize; + UInt32 processedSizeLoc; + RINOK(stream->Read((Byte *)buf + pos, curSize, &processedSizeLoc)) + if (processedSizeLoc == 0) + return E_FAIL; + len -= processedSizeLoc; + pos += processedSizeLoc; + if (len == 0) + return S_OK; + if (openCallback) + { + const UInt64 files = 0; + RINOK(openCallback->SetCompleted(&files, &pos)) + } + } +} + + +static bool isThere_Zero_Byte(const Byte *data, size_t size) +{ + for (size_t i = 0; i < size; i++) + if (data[i] == 0) + return true; + return false; +} + + +Z7_COM7F_IMF(CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback *openCallback)) +{ + COM_TRY_BEGIN + { + Close(); + + CByteBuffer buf; + RINOK(ReadStream_to_Buf(stream, buf, openCallback)) + + CObjectVector &pairs = HashPairs; + + const bool zeroMode = isThere_Zero_Byte(buf, buf.Size()); + _is_ZeroMode = zeroMode; + bool cr_lf_Mode = false; + if (!zeroMode) + cr_lf_Mode = Is_CR_LF_Data(buf, buf.Size()); + + if (openCallback) + { + Z7_DECL_CMyComPtr_QI_FROM( + IArchiveOpenVolumeCallback, + openVolumeCallback, openCallback) + if (openVolumeCallback) + { + NCOM::CPropVariant prop; + RINOK(openVolumeCallback->GetProperty(kpidName, &prop)) + if (prop.vt == VT_BSTR) + _is_KnownMethod_in_FileName = GetMethod_from_FileName(prop.bstrVal, _method_from_FileName); + } + } + + if (!_methods.IsEmpty()) + { + ConvertUnicodeToUTF8(_methods[0], _method_for_Extraction); + } + if (_method_for_Extraction.IsEmpty()) + { + // if (_is_KnownMethod_in_FileName) + _method_for_Extraction = _method_from_FileName; + } + + const bool cksumMode = _method_for_Extraction.IsEqualTo_Ascii_NoCase("cksum"); + _is_CksumMode = cksumMode; + + size_t pos = 0; + AString s; + bool minusMode = false; + unsigned numLines = 0; + + while (pos < buf.Size()) + { + if (!GetLine(buf, zeroMode, cr_lf_Mode, pos, s)) + return S_FALSE; + numLines++; + if (s.IsEmpty()) + continue; + + if (s.IsPrefixedBy_Ascii_NoCase("; ")) + { + if (numLines != 1) + return S_FALSE; + // comment line of FileVerifier++ + continue; + } + + if (s.IsPrefixedBy_Ascii_NoCase("-----")) + { + if (minusMode) + break; // end of pgp mode + minusMode = true; + if (s.IsPrefixedBy_Ascii_NoCase("-----BEGIN PGP SIGNED MESSAGE")) + { + if (_is_PgpMethod) + return S_FALSE; + if (!GetLine(buf, zeroMode, cr_lf_Mode, pos, s)) + return S_FALSE; + const char *kStart = "Hash: "; + if (!s.IsPrefixedBy_Ascii_NoCase(kStart)) + return S_FALSE; + _pgpMethod = s.Ptr((unsigned)strlen(kStart)); + _is_PgpMethod = true; + } + continue; + } + + CHashPair pair; + pair.FullLine = s; + if (cksumMode) + { + if (!pair.ParseCksum(s)) + return S_FALSE; + } + else if (!pair.Parse(s)) + return S_FALSE; + pairs.Add(pair); + } + + { + unsigned hashSize = 0; + bool hashSize_Dismatch = false; + for (unsigned i = 0; i < HashPairs.Size(); i++) + { + const CHashPair &hp = HashPairs[i]; + if (i == 0) + hashSize = (unsigned)hp.Hash.Size(); + else + if (hashSize != hp.Hash.Size()) + hashSize_Dismatch = true; + + if (hp.IsBSD) + _are_there_Tags = true; + if (!_are_there_Dirs && hp.IsDir()) + _are_there_Dirs = true; + } + if (!hashSize_Dismatch && hashSize != 0) + { + _hashSize = hashSize; + _hashSize_Defined = true; + } + } + + _phySize = buf.Size(); + _isArc = true; + return S_OK; + } + COM_TRY_END +} + + +void CHandler::ClearVars() +{ + _phySize = 0; + _isArc = false; + _is_CksumMode = false; + _is_PgpMethod = false; + _is_ZeroMode = false; + _are_there_Tags = false; + _are_there_Dirs = false; + _is_KnownMethod_in_FileName = false; + _hashSize_Defined = false; + _hashSize = 0; +} + + +Z7_COM7F_IMF(CHandler::Close()) +{ + ClearVars(); + _method_from_FileName.Empty(); + _method_for_Extraction.Empty(); + _pgpMethod.Empty(); + HashPairs.Clear(); + return S_OK; +} + + +static bool CheckDigests(const Byte *a, const Byte *b, size_t size) +{ + if (size <= 8) + { + /* we use reversed order for one digest, when text representation + uses big-order for crc-32 and crc-64 */ + for (size_t i = 0; i < size; i++) + if (a[i] != b[size - 1 - i]) + return false; + return true; + } + { + for (size_t i = 0; i < size; i++) + if (a[i] != b[i]) + return false; + return true; + } +} + + +static void AddDefaultMethod(UStringVector &methods, + const char *name, unsigned size) +{ + int shaVersion = -1; + if (name) + { + if (StringsAreEqualNoCase_Ascii(name, "sha")) + { + shaVersion = 0; + if (size == 0) + size = 32; + } + else if (StringsAreEqualNoCase_Ascii(name, "sha1")) + { + shaVersion = 1; + if (size == 0) + size = 20; + } + else if (StringsAreEqualNoCase_Ascii(name, "sha2")) + { + shaVersion = 2; + if (size == 0) + size = 32; + } + else if (StringsAreEqualNoCase_Ascii(name, "sha3")) + { + if (size == 0 || + size == 32) name = "sha3-256"; + else if (size == 28) name = "sha3-224"; + else if (size == 48) name = "sha3-384"; + else if (size == 64) name = "sha3-512"; + } + else if (StringsAreEqualNoCase_Ascii(name, "sha512")) + { + // we allow any sha512 derived hash inside .sha512 file: + if (size == 48) name = "sha384"; + else if (size == 32) name = "sha512-256"; + else if (size == 28) name = "sha512-224"; + } + if (shaVersion >= 0) + name = NULL; + } + + const char *m = NULL; + if (name) + m = name; + else + { + if (size == 64) m = "sha512"; + else if (size == 48) m = "sha384"; + else if (size == 32) m = "sha256"; + else if (size == 28) m = "sha224"; + else if (size == 20) m = "sha1"; + else if (shaVersion < 0) + { + if (size == 16) m = "md5"; + else if (size == 8) m = "crc64"; + else if (size == 4) m = "crc32"; + } + } + + if (!m) + return; + +#ifdef Z7_EXTERNAL_CODECS + const CExternalCodecs *_externalCodecs = g_ExternalCodecs_Ptr; +#endif + CMethodId id; + if (FindHashMethod(EXTERNAL_CODECS_LOC_VARS + AString(m), id)) + methods.Add(UString(m)); +} + + +Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testMode, IArchiveExtractCallback *extractCallback)) +{ + COM_TRY_BEGIN + + /* + if (testMode == 0) + return E_NOTIMPL; + */ + + const bool allFilesMode = (numItems == (UInt32)(Int32)-1); + if (allFilesMode) + numItems = HashPairs.Size(); + if (numItems == 0) + return S_OK; + + #ifdef Z7_EXTERNAL_CODECS + const CExternalCodecs *_externalCodecs = g_ExternalCodecs_Ptr; + #endif + + CHashBundle hb_Glob; + // UStringVector methods = options.Methods; + UStringVector methods; + +/* + if (methods.IsEmpty() && !utf_nameExtenstion.IsEmpty() && !_hashSize_Defined) + { + CMethodId id; + if (FindHashMethod(EXTERNAL_CODECS_LOC_VARS utf_nameExtenstion, id)) + methods.Add(_nameExtenstion); + } +*/ + + if (methods.IsEmpty() && !_pgpMethod.IsEmpty()) + { + CMethodId id; + if (FindHashMethod(EXTERNAL_CODECS_LOC_VARS _pgpMethod, id)) + methods.Add(UString(_pgpMethod)); + } + +/* + if (methods.IsEmpty() && _pgpMethod.IsEmpty() && _hashSize_Defined) + { + AddDefaultMethod(methods, + utf_nameExtenstion.IsEmpty() ? NULL : utf_nameExtenstion.Ptr(), + _hashSize); + } +*/ + + if (!methods.IsEmpty()) + { + RINOK(hb_Glob.SetMethods( + EXTERNAL_CODECS_LOC_VARS + methods)) + } + + Z7_DECL_CMyComPtr_QI_FROM( + IArchiveUpdateCallbackFile, + updateCallbackFile, extractCallback) + if (!updateCallbackFile) + return E_NOTIMPL; + { + Z7_DECL_CMyComPtr_QI_FROM( + IArchiveGetDiskProperty, + GetDiskProperty, extractCallback) + if (GetDiskProperty) + { + UInt64 totalSize = 0; + UInt32 i; + for (i = 0; i < numItems; i++) + { + const UInt32 index = allFilesMode ? i : indices[i]; + const CHashPair &hp = HashPairs[index]; + if (hp.IsDir()) + continue; + { + NCOM::CPropVariant prop; + RINOK(GetDiskProperty->GetDiskProperty(index, kpidSize, &prop)) + if (prop.vt != VT_UI8) + continue; + totalSize += prop.uhVal.QuadPart; + } + } + RINOK(extractCallback->SetTotal(totalSize)) + // RINOK(Hash_SetTotalUnpacked->Hash_SetTotalUnpacked(indices, numItems)); + } + } + + const UInt32 kBufSize = 1 << 15; + CHashMidBuf buf; + if (!buf.Alloc(kBufSize)) + return E_OUTOFMEMORY; + + CMyComPtr2_Create lps; + lps->Init(extractCallback, false); + + for (UInt32 i = 0;; i++) + { + RINOK(lps->SetCur()) + if (i >= numItems) + break; + const UInt32 index = allFilesMode ? i : indices[i]; + + CHashPair &hp = HashPairs[index]; + + UString path; + hp.Get_UString_Path(path); + + CMyComPtr inStream; + const bool isDir = hp.IsDir(); + if (!isDir) + { + RINOK(updateCallbackFile->GetStream2(index, &inStream, NUpdateNotifyOp::kHashRead)) + if (!inStream) + { + continue; // we have shown error in GetStream2() + } + // askMode = NArchive::NExtract::NAskMode::kSkip; + } + + Int32 askMode = testMode ? + NArchive::NExtract::NAskMode::kTest : + NArchive::NExtract::NAskMode::kExtract; + + CMyComPtr realOutStream; + RINOK(extractCallback->GetStream(index, &realOutStream, askMode)) + + /* PrepareOperation() can expect kExtract to set + Attrib and security of output file */ + askMode = NArchive::NExtract::NAskMode::kReadExternal; + + RINOK(extractCallback->PrepareOperation(askMode)) + + const bool isAltStream = false; + + UInt64 fileSize = 0; + + CHashBundle hb_Loc; + + CHashBundle *hb_Use = &hb_Glob; + + HRESULT res_SetMethods = S_OK; + + UStringVector methods_loc; + + if (!hp.Method.IsEmpty()) + { + hb_Use = &hb_Loc; + CMethodId id; + AString methodName = hp.Method; + Convert_TagName_to_MethodName(methodName); + if (FindHashMethod(EXTERNAL_CODECS_LOC_VARS methodName, id)) + { + methods_loc.Add(UString(methodName)); + RINOK(hb_Loc.SetMethods( + EXTERNAL_CODECS_LOC_VARS + methods_loc)) + } + else + res_SetMethods = E_NOTIMPL; + } + else if (methods.IsEmpty()) + { + AddDefaultMethod(methods_loc, + _method_for_Extraction.IsEmpty() ? NULL : + _method_for_Extraction.Ptr(), + (unsigned)hp.Hash.Size()); + if (!methods_loc.IsEmpty()) + { + hb_Use = &hb_Loc; + RINOK(hb_Loc.SetMethods( + EXTERNAL_CODECS_LOC_VARS + methods_loc)) + } + } + + const bool isSupportedMode = hp.IsSupportedMode(); + hb_Use->InitForNewFile(); + + if (inStream) + { + for (UInt32 step = 0;; step++) + { + if ((step & 0xFF) == 0) + { + RINOK(lps.Interface()->SetRatioInfo(NULL, &fileSize)) + } + UInt32 size; + RINOK(inStream->Read(buf, kBufSize, &size)) + if (size == 0) + break; + hb_Use->Update(buf, size); + if (realOutStream) + { + RINOK(WriteStream(realOutStream, buf, size)) + } + fileSize += size; + } + + hp.Size_from_Disk = fileSize; + hp.Size_from_Disk_Defined = true; + } + + realOutStream.Release(); + inStream.Release(); + + lps->InSize += hp.Hash.Size(); + lps->OutSize += fileSize; + + hb_Use->Final(isDir, isAltStream, path); + + Int32 opRes = NArchive::NExtract::NOperationResult::kUnsupportedMethod; + if (isSupportedMode + && res_SetMethods != E_NOTIMPL + && !hb_Use->Hashers.IsEmpty() + ) + { + const CHasherState &hs = hb_Use->Hashers[0]; + if (hs.DigestSize == hp.Hash.Size()) + { + opRes = NArchive::NExtract::NOperationResult::kCRCError; + if (CheckDigests(hp.Hash, hs.Digests[0], hs.DigestSize)) + if (!hp.Size_from_Arc_Defined || hp.Size_from_Arc == fileSize) + opRes = NArchive::NExtract::NOperationResult::kOK; + } + } + + RINOK(extractCallback->SetOperationResult(opRes)) + } + + return S_OK; + COM_TRY_END +} + + +// ---------- UPDATE ---------- + +struct CUpdateItem +{ + int IndexInArc; + unsigned IndexInClient; + UInt64 Size; + bool NewData; + bool NewProps; + bool IsDir; + UString Path; + + CUpdateItem(): Size(0), IsDir(false) {} +}; + + +static HRESULT GetPropString(IArchiveUpdateCallback *callback, UInt32 index, PROPID propId, + UString &res, + bool convertSlash) +{ + NCOM::CPropVariant prop; + RINOK(callback->GetProperty(index, propId, &prop)) + if (prop.vt == VT_BSTR) + { + res = prop.bstrVal; + if (convertSlash) + NArchive::NItemName::ReplaceSlashes_OsToUnix(res); + } + else if (prop.vt != VT_EMPTY) + return E_INVALIDARG; + return S_OK; +} + + +Z7_COM7F_IMF(CHandler::GetFileTimeType(UInt32 *type)) +{ + *type = NFileTimeType::kUnix; + return S_OK; +} + + +Z7_COM7F_IMF(CHandler::UpdateItems(ISequentialOutStream *outStream, UInt32 numItems, + IArchiveUpdateCallback *callback)) +{ + COM_TRY_BEGIN + + if (_isArc && !CanUpdate()) + return E_NOTIMPL; + + /* + Z7_DECL_CMyComPtr_QI_FROM(IArchiveUpdateCallbackArcProp, + reportArcProp, callback) + */ + + CObjectVector updateItems; + + UInt64 complexity = 0; + + UInt32 i; + for (i = 0; i < numItems; i++) + { + CUpdateItem ui; + Int32 newData; + Int32 newProps; + UInt32 indexInArc; + + if (!callback) + return E_FAIL; + + RINOK(callback->GetUpdateItemInfo(i, &newData, &newProps, &indexInArc)) + + ui.NewProps = IntToBool(newProps); + ui.NewData = IntToBool(newData); + ui.IndexInArc = (int)indexInArc; + ui.IndexInClient = i; + if (IntToBool(newProps)) + { + { + NCOM::CPropVariant prop; + RINOK(callback->GetProperty(i, kpidIsDir, &prop)) + if (prop.vt == VT_EMPTY) + ui.IsDir = false; + else if (prop.vt != VT_BOOL) + return E_INVALIDARG; + else + ui.IsDir = (prop.boolVal != VARIANT_FALSE); + } + + RINOK(GetPropString(callback, i, kpidPath, ui.Path, + true)) // convertSlash + /* + if (ui.IsDir && !ui.Name.IsEmpty() && ui.Name.Back() != '/') + ui.Name += '/'; + */ + } + + if (IntToBool(newData)) + { + NCOM::CPropVariant prop; + RINOK(callback->GetProperty(i, kpidSize, &prop)) + if (prop.vt == VT_UI8) + { + ui.Size = prop.uhVal.QuadPart; + complexity += ui.Size; + } + else if (prop.vt == VT_EMPTY) + ui.Size = (UInt64)(Int64)-1; + else + return E_INVALIDARG; + } + + updateItems.Add(ui); + } + + if (complexity != 0) + { + RINOK(callback->SetTotal(complexity)) + } + + #ifdef Z7_EXTERNAL_CODECS + const CExternalCodecs *_externalCodecs = g_ExternalCodecs_Ptr; + #endif + + CHashBundle hb; + UStringVector methods; + if (!_methods.IsEmpty()) + { + FOR_VECTOR(k, _methods) + { + methods.Add(_methods[k]); + } + } + else + { + Z7_DECL_CMyComPtr_QI_FROM( + IArchiveGetRootProps, + getRootProps, callback) + if (getRootProps) + { + NCOM::CPropVariant prop; + RINOK(getRootProps->GetRootProp(kpidArcFileName, &prop)) + if (prop.vt == VT_BSTR) + { + AString method; + /* const bool isKnownMethod = */ GetMethod_from_FileName(prop.bstrVal, method); + if (!method.IsEmpty()) + { + AddDefaultMethod(methods, method, _crcSize_WasSet ? _crcSize : 0); + if (methods.IsEmpty()) + return E_NOTIMPL; + } + } + } + } + if (methods.IsEmpty() && _crcSize_WasSet) + { + AddDefaultMethod(methods, + NULL, // name + _crcSize); + } + + RINOK(hb.SetMethods(EXTERNAL_CODECS_LOC_VARS methods)) + + CMyComPtr2_Create lps; + lps->Init(callback, true); + + const UInt32 kBufSize = 1 << 15; + CHashMidBuf buf; + if (!buf.Alloc(kBufSize)) + return E_OUTOFMEMORY; + + CDynLimBuf hashFileString((size_t)1 << 31); + + CHashOptionsLocal options = _options; + + if (_isArc) + { + if (!options.HashMode_Zero.Def && _is_ZeroMode) + options.HashMode_Zero.Val = true; + if (!options.HashMode_Tag.Def && _are_there_Tags) + options.HashMode_Tag.Val = true; + if (!options.HashMode_Dirs.Def && _are_there_Dirs) + options.HashMode_Dirs.Val = true; + } + if (options.HashMode_OnlyHash.Val && updateItems.Size() != 1) + options.HashMode_OnlyHash.Val = false; + + complexity = 0; + + for (i = 0; i < updateItems.Size(); i++) + { + lps->InSize = complexity; + RINOK(lps->SetCur()) + + const CUpdateItem &ui = updateItems[i]; + + /* + CHashPair item; + if (!ui.NewProps) + item = HashPairs[(unsigned)ui.IndexInArc]; + */ + + if (ui.NewData) + { + UInt64 currentComplexity = ui.Size; + UInt64 fileSize = 0; + + CMyComPtr fileInStream; + bool needWrite = true; + { + HRESULT res = callback->GetStream(ui.IndexInClient, &fileInStream); + + if (res == S_FALSE) + needWrite = false; + else + { + RINOK(res) + + if (fileInStream) + { + Z7_DECL_CMyComPtr_QI_FROM( + IStreamGetSize, + streamGetSize, fileInStream) + if (streamGetSize) + { + UInt64 size; + if (streamGetSize->GetSize(&size) == S_OK) + currentComplexity = size; + } + /* + Z7_DECL_CMyComPtr_QI_FROM( + IStreamGetProps, + getProps, fileInStream) + if (getProps) + { + FILETIME mTime; + UInt64 size2; + if (getProps->GetProps(&size2, NULL, NULL, &mTime, NULL) == S_OK) + { + currentComplexity = size2; + // item.MTime = NTime::FileTimeToUnixTime64(mTime);; + } + } + */ + } + else + { + currentComplexity = 0; + } + } + } + + hb.InitForNewFile(); + const bool isDir = ui.IsDir; + + if (needWrite && fileInStream && !isDir) + { + for (UInt32 step = 0;; step++) + { + if ((step & 0xFF) == 0) + { + RINOK(lps.Interface()->SetRatioInfo(&fileSize, NULL)) + // RINOK(callback->SetCompleted(&completeValue)); + } + UInt32 size; + RINOK(fileInStream->Read(buf, kBufSize, &size)) + if (size == 0) + break; + hb.Update(buf, size); + fileSize += size; + } + currentComplexity = fileSize; + } + + fileInStream.Release(); + const bool isAltStream = false; + hb.Final(isDir, isAltStream, ui.Path); + + if (options.HashMode_Dirs.Val || !isDir) + { + if (!hb.Hashers.IsEmpty()) + lps->OutSize += hb.Hashers[0].DigestSize; + WriteLine(hashFileString, + options, + ui.Path, + isDir, + hb); + if (hashFileString.IsError()) + return E_OUTOFMEMORY; + } + + complexity += currentComplexity; + + /* + if (reportArcProp) + { + PROPVARIANT prop; + prop.vt = VT_EMPTY; + prop.wReserved1 = 0; + + NCOM::PropVarEm_Set_UInt64(&prop, fileSize); + RINOK(reportArcProp->ReportProp(NArchive::NEventIndexType::kOutArcIndex, ui.IndexInClient, kpidSize, &prop)); + + for (unsigned k = 0; k < hb.Hashers.Size(); k++) + { + const CHasherState &hs = hb.Hashers[k]; + + if (hs.DigestSize == 4 && hs.Name.IsEqualTo_Ascii_NoCase("crc32")) + { + NCOM::PropVarEm_Set_UInt32(&prop, GetUi32(hs.Digests[k_HashCalc_Index_Current])); + RINOK(reportArcProp->ReportProp(NArchive::NEventIndexType::kOutArcIndex, ui.IndexInClient, kpidCRC, &prop)); + } + else + { + RINOK(reportArcProp->ReportRawProp(NArchive::NEventIndexType::kOutArcIndex, ui.IndexInClient, + kpidChecksum, hs.Digests[k_HashCalc_Index_Current], + hs.DigestSize, NPropDataType::kRaw)); + } + RINOK(reportArcProp->ReportFinished(NArchive::NEventIndexType::kOutArcIndex, ui.IndexInClient, NArchive::NUpdate::NOperationResult::kOK)); + } + } + */ + RINOK(callback->SetOperationResult(NArchive::NUpdate::NOperationResult::kOK)) + } + else + { + // old data + const CHashPair &existItem = HashPairs[(unsigned)ui.IndexInArc]; + if (ui.NewProps) + { + WriteLine(hashFileString, + options, + ui.Path, + ui.IsDir, + existItem.Method, existItem.HashString + ); + } + else + { + hashFileString += existItem.FullLine; + Add_LF(hashFileString, options); + } + } + if (hashFileString.IsError()) + return E_OUTOFMEMORY; + } + + RINOK(WriteStream(outStream, hashFileString, hashFileString.Len())) + + return S_OK; + COM_TRY_END +} + + + +HRESULT CHandler::SetProperty(const wchar_t *nameSpec, const PROPVARIANT &value) +{ + UString name = nameSpec; + name.MakeLower_Ascii(); + if (name.IsEmpty()) + return E_INVALIDARG; + + if (name.IsEqualTo("m")) // "hm" hash method + { + // COneMethodInfo omi; + // RINOK(omi.ParseMethodFromPROPVARIANT(L"", value)); + // _methods.Add(omi.MethodName); // change it. use omi.PropsString + if (value.vt != VT_BSTR) + return E_INVALIDARG; + UString s (value.bstrVal); + _methods.Add(s); + return S_OK; + } + + if (name.IsEqualTo("flags")) + { + if (value.vt != VT_BSTR) + return E_INVALIDARG; + if (!_options.ParseString(value.bstrVal)) + return E_INVALIDARG; + return S_OK; + } + + if (name.IsEqualTo("backslash")) + return PROPVARIANT_to_bool(value, _supportWindowsBackslash); + + if (name.IsPrefixedBy_Ascii_NoCase("crc")) + { + name.Delete(0, 3); + _crcSize = 4; + _crcSize_WasSet = true; + return ParsePropToUInt32(name, value, _crcSize); + } + + // common properties + if (name.IsPrefixedBy_Ascii_NoCase("mt") + || name.IsPrefixedBy_Ascii_NoCase("memuse")) + return S_OK; + + return E_INVALIDARG; +} + + +void CHandler::InitProps() +{ + _supportWindowsBackslash = true; + _crcSize_WasSet = false; + _crcSize = 4; + _methods.Clear(); + _options.Init_HashOptionsLocal(); +} + +Z7_COM7F_IMF(CHandler::SetProperties(const wchar_t * const *names, const PROPVARIANT *values, UInt32 numProps)) +{ + COM_TRY_BEGIN + + InitProps(); + + for (UInt32 i = 0; i < numProps; i++) + { + RINOK(SetProperty(names[i], values[i])) + } + return S_OK; + COM_TRY_END +} + +CHandler::CHandler() +{ + ClearVars(); + InitProps(); +} + +} + + + +static IInArchive *CreateHashHandler_In() { return new NHash::CHandler; } +static IOutArchive *CreateHashHandler_Out() { return new NHash::CHandler; } + +void Codecs_AddHashArcHandler(CCodecs *codecs) +{ + { + CArcInfoEx item; + + item.Name = "Hash"; + item.CreateInArchive = CreateHashHandler_In; + item.CreateOutArchive = CreateHashHandler_Out; + item.IsArcFunc = NULL; + item.Flags = + NArcInfoFlags::kKeepName + | NArcInfoFlags::kStartOpen + | NArcInfoFlags::kByExtOnlyOpen + // | NArcInfoFlags::kPureStartOpen + | NArcInfoFlags::kHashHandler + ; + + // ubuntu uses "SHA256SUMS" file + item.AddExts(UString ( + "sha256" + " sha512" + " sha384" + " sha224" + " sha512-224" + " sha512-256" + " sha3-224" + " sha3-256" + " sha3-384" + " sha3-512" + // " shake128" + // " shake256" + " sha1" + " sha2" + " sha3" + " sha" + " md5" + " blake2s" + " blake2b" + " blake2sp" + " xxh64" + " crc32" + " crc64" + " cksum" + " asc" + // " b2sum" + ), + UString()); + + item.UpdateEnabled = (item.CreateOutArchive != NULL); + item.SignatureOffset = 0; + // item.Version = MY_VER_MIX; + item.NewInterface = true; + + item.Signatures.AddNew().CopyFrom(NULL, 0); + + codecs->Formats.Add(item); + } +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/HashCalc.h b/src/plugins/7zip/7za/cpp/7zip/UI/Common/HashCalc.h new file mode 100644 index 00000000..b8f867fd --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/HashCalc.h @@ -0,0 +1,322 @@ +// HashCalc.h + +#ifndef ZIP7_INC_HASH_CALC_H +#define ZIP7_INC_HASH_CALC_H + +#include "../../../Common/UTFConvert.h" +#include "../../../Common/Wildcard.h" + +#include "../../Common/CreateCoder.h" +#include "../../Common/MethodProps.h" + +#include "DirItem.h" +#include "IFileExtractCallback.h" + +const unsigned k_HashCalc_DigestSize_Max = 64; +const unsigned k_HashCalc_ExtraSize = 8; +const unsigned k_HashCalc_NumGroups = 4; + +/* + if (size <= 8) : upper case : reversed byte order : it shows 32-bit/64-bit number, if data contains little-endian number + if (size > 8) : lower case : original byte order (as big-endian byte sequence) +*/ +void HashHexToString(char *dest, const Byte *data, size_t size); + +enum +{ + k_HashCalc_Index_Current, + k_HashCalc_Index_DataSum, + k_HashCalc_Index_NamesSum, + k_HashCalc_Index_StreamsSum +}; + +struct CHasherState +{ + CMyComPtr Hasher; + AString Name; + UInt32 DigestSize; + UInt64 NumSums[k_HashCalc_NumGroups]; + Byte Digests[k_HashCalc_NumGroups][k_HashCalc_DigestSize_Max + k_HashCalc_ExtraSize]; + + void InitDigestGroup(unsigned groupIndex) + { + NumSums[groupIndex] = 0; + memset(Digests[groupIndex], 0, sizeof(Digests[groupIndex])); + } + + const Byte *GetExtraData_for_Group(unsigned groupIndex) const + { + return Digests[groupIndex] + k_HashCalc_DigestSize_Max; + } + + unsigned GetNumExtraBytes_for_Group(unsigned groupIndex) const + { + const Byte *p = GetExtraData_for_Group(groupIndex); + // we use little-endian to read extra bytes + for (unsigned i = k_HashCalc_ExtraSize; i != 0; i--) + if (p[i - 1] != 0) + return i; + return 0; + } + + void AddDigest(unsigned groupIndex, const Byte *data); + + void WriteToString(unsigned digestIndex, char *s) const; +}; + + +Z7_PURE_INTERFACES_BEGIN + + +DECLARE_INTERFACE(IHashCalc) +{ + virtual void InitForNewFile() = 0; + virtual void Update(const void *data, UInt32 size) = 0; + virtual void SetSize(UInt64 size) = 0; + virtual void Final(bool isDir, bool isAltStream, const UString &path) = 0; +}; + +Z7_PURE_INTERFACES_END + +struct CHashBundle Z7_final: public IHashCalc +{ + CObjectVector Hashers; + + UInt64 NumDirs; + UInt64 NumFiles; + UInt64 NumAltStreams; + UInt64 FilesSize; + UInt64 AltStreamsSize; + UInt64 NumErrors; + + UInt64 CurSize; + + UString MainName; + UString FirstFileName; + + HRESULT SetMethods(DECL_EXTERNAL_CODECS_LOC_VARS const UStringVector &methods); + + // void Init() {} + CHashBundle() + { + NumDirs = NumFiles = NumAltStreams = FilesSize = AltStreamsSize = NumErrors = 0; + } + + void InitForNewFile() Z7_override; + void Update(const void *data, UInt32 size) Z7_override; + void SetSize(UInt64 size) Z7_override; + void Final(bool isDir, bool isAltStream, const UString &path) Z7_override; +}; + +Z7_PURE_INTERFACES_BEGIN + +// INTERFACE_IDirItemsCallback(x) + +#define Z7_IFACEN_IHashCallbackUI(x) \ + virtual HRESULT StartScanning() x \ + virtual HRESULT FinishScanning(const CDirItemsStat &st) x \ + virtual HRESULT SetNumFiles(UInt64 numFiles) x \ + virtual HRESULT SetTotal(UInt64 size) x \ + virtual HRESULT SetCompleted(const UInt64 *completeValue) x \ + virtual HRESULT CheckBreak() x \ + virtual HRESULT BeforeFirstFile(const CHashBundle &hb) x \ + virtual HRESULT GetStream(const wchar_t *name, bool isFolder) x \ + virtual HRESULT OpenFileError(const FString &path, DWORD systemError) x \ + virtual HRESULT SetOperationResult(UInt64 fileSize, const CHashBundle &hb, bool showHash) x \ + virtual HRESULT AfterLastFile(CHashBundle &hb) x \ + +Z7_IFACE_DECL_PURE_(IHashCallbackUI, IDirItemsCallback) + +Z7_PURE_INTERFACES_END + + +struct CHashOptionsLocal +{ + CBoolPair HashMode_Zero; + CBoolPair HashMode_Tag; + CBoolPair HashMode_Dirs; + CBoolPair HashMode_OnlyHash; + + void Init_HashOptionsLocal() + { + HashMode_Zero.Init(); + HashMode_Tag.Init(); + HashMode_Dirs.Init(); + HashMode_OnlyHash.Init(); + // HashMode_Dirs = true; // for debug + } + + CHashOptionsLocal() + { + Init_HashOptionsLocal(); + } + + bool ParseFlagCharOption(wchar_t c, bool val) + { + c = MyCharLower_Ascii(c); + if (c == 'z') HashMode_Zero.SetVal_as_Defined(val); + else if (c == 't') HashMode_Tag.SetVal_as_Defined(val); + else if (c == 'd') HashMode_Dirs.SetVal_as_Defined(val); + else if (c == 'h') HashMode_OnlyHash.SetVal_as_Defined(val); + else return false; + return true; + } + + bool ParseString(const UString &s) + { + for (unsigned i = 0; i < s.Len();) + { + const wchar_t c = s[i++]; + bool val = true; + if (i < s.Len()) + { + const wchar_t next = s[i]; + if (next == '-') + { + val = false; + i++; + } + } + if (!ParseFlagCharOption(c, val)) + return false; + } + return true; + } +}; + + +struct CHashOptions + // : public CHashOptionsLocal +{ + UStringVector Methods; + // UString HashFilePath; + + bool PreserveATime; + bool OpenShareForWrite; + bool StdInMode; + bool AltStreamsMode; + CBoolPair SymLinks; + + NWildcard::ECensorPathMode PathMode; + + CHashOptions(): + PreserveATime(false), + OpenShareForWrite(false), + StdInMode(false), + AltStreamsMode(false), + PathMode(NWildcard::k_RelatPath) {} +}; + + +HRESULT HashCalc( + DECL_EXTERNAL_CODECS_LOC_VARS + const NWildcard::CCensor &censor, + const CHashOptions &options, + AString &errorInfo, + IHashCallbackUI *callback); + + + +#ifndef Z7_SFX + +namespace NHash { + +struct CHashPair +{ + CByteBuffer Hash; + char Mode; + bool IsBSD; + bool Escape; + bool Size_from_Arc_Defined; + bool Size_from_Disk_Defined; + AString Method; + AString Name; + + AString FullLine; + AString HashString; + // unsigned HashLengthInBits; + + // AString MethodName; + UInt64 Size_from_Arc; + UInt64 Size_from_Disk; + + bool IsDir() const; + + void Get_UString_Path(UString &path) const + { + path.Empty(); + if (!ConvertUTF8ToUnicode(Name, path)) + return; + } + + bool ParseCksum(const char *s); + bool Parse(const char *s); + + bool IsSupportedMode() const + { + return Mode != 'U' && Mode != '^'; + } + + CHashPair(): + Mode(0) + , IsBSD(false) + , Escape(false) + , Size_from_Arc_Defined(false) + , Size_from_Disk_Defined(false) + // , HashLengthInBits(0) + , Size_from_Arc(0) + , Size_from_Disk(0) + {} +}; + + +Z7_CLASS_IMP_CHandler_IInArchive_3( + IArchiveGetRawProps, + /* public IGetArchiveHashHandler, */ + IOutArchive, + ISetProperties +) + bool _isArc; + bool _supportWindowsBackslash; + bool _crcSize_WasSet; + bool _is_CksumMode; + bool _is_PgpMethod; + bool _is_ZeroMode; + bool _are_there_Tags; + bool _are_there_Dirs; + bool _is_KnownMethod_in_FileName; + bool _hashSize_Defined; + unsigned _hashSize; + UInt32 _crcSize; + UInt64 _phySize; + CObjectVector HashPairs; + UStringVector _methods; + AString _method_from_FileName; + AString _pgpMethod; + AString _method_for_Extraction; + CHashOptionsLocal _options; + + void ClearVars(); + void InitProps(); + + bool CanUpdate() const + { + if (!_isArc || _is_PgpMethod || _is_CksumMode) + return false; + return true; + } + + HRESULT SetProperty(const wchar_t *nameSpec, const PROPVARIANT &value); + +public: + CHandler(); +}; + +} + +void Codecs_AddHashArcHandler(CCodecs *codecs); + +#endif + + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/IFileExtractCallback.h b/src/plugins/7zip/7za/cpp/7zip/UI/Common/IFileExtractCallback.h new file mode 100644 index 00000000..dd5c0d72 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/IFileExtractCallback.h @@ -0,0 +1,112 @@ +// IFileExtractCallback.h + +#ifndef ZIP7_INC_I_FILE_EXTRACT_CALLBACK_H +#define ZIP7_INC_I_FILE_EXTRACT_CALLBACK_H + +#include "../../../Common/MyString.h" + +#include "../../IDecl.h" + +#include "LoadCodecs.h" +#include "OpenArchive.h" + +Z7_PURE_INTERFACES_BEGIN + +#define Z7_IFACE_CONSTR_FOLDERARC_SUB(i, base, n) \ + Z7_DECL_IFACE_7ZIP_SUB(i, base, 1, n) \ + { Z7_IFACE_COM7_PURE(i) }; + +#define Z7_IFACE_CONSTR_FOLDERARC(i, n) \ + Z7_IFACE_CONSTR_FOLDERARC_SUB(i, IUnknown, n) + +namespace NOverwriteAnswer +{ + enum EEnum + { + kYes, + kYesToAll, + kNo, + kNoToAll, + kAutoRename, + kCancel + }; +} + + +/* ---------- IFolderArchiveExtractCallback ---------- +is implemented by + Console/ExtractCallbackConsole.h CExtractCallbackConsole + FileManager/ExtractCallback.h CExtractCallbackImp + FAR/ExtractEngine.cpp CExtractCallBackImp: (QueryInterface is not supported) + +IID_IFolderArchiveExtractCallback is requested by: + - Agent/ArchiveFolder.cpp + CAgentFolder::CopyTo(..., IFolderOperationsExtractCallback *callback) + is sent to IArchiveFolder::Extract() + + - FileManager/PanelCopy.cpp + CPanel::CopyTo(), if (options->testMode) + is sent to IArchiveFolder::Extract() + + IFolderArchiveExtractCallback is used by Common/ArchiveExtractCallback.cpp +*/ + +#define Z7_IFACEM_IFolderArchiveExtractCallback(x) \ + x(AskOverwrite( \ + const wchar_t *existName, const FILETIME *existTime, const UInt64 *existSize, \ + const wchar_t *newName, const FILETIME *newTime, const UInt64 *newSize, \ + Int32 *answer)) \ + x(PrepareOperation(const wchar_t *name, Int32 isFolder, Int32 askExtractMode, const UInt64 *position)) \ + x(MessageError(const wchar_t *message)) \ + x(SetOperationResult(Int32 opRes, Int32 encrypted)) \ + +Z7_IFACE_CONSTR_FOLDERARC_SUB(IFolderArchiveExtractCallback, IProgress, 0x07) + +#define Z7_IFACEM_IFolderArchiveExtractCallback2(x) \ + x(ReportExtractResult(Int32 opRes, Int32 encrypted, const wchar_t *name)) \ + +Z7_IFACE_CONSTR_FOLDERARC(IFolderArchiveExtractCallback2, 0x08) + +/* ---------- IExtractCallbackUI ---------- +is implemented by + Console/ExtractCallbackConsole.h CExtractCallbackConsole + FileManager/ExtractCallback.h CExtractCallbackImp +*/ + +#ifdef Z7_NO_CRYPTO + #define Z7_IFACEM_IExtractCallbackUI_Crypto(px) +#else + #define Z7_IFACEM_IExtractCallbackUI_Crypto(px) \ + virtual HRESULT SetPassword(const UString &password) px +#endif + +#define Z7_IFACEN_IExtractCallbackUI(px) \ + virtual HRESULT BeforeOpen(const wchar_t *name, bool testMode) px \ + virtual HRESULT OpenResult(const CCodecs *codecs, const CArchiveLink &arcLink, const wchar_t *name, HRESULT result) px \ + virtual HRESULT ThereAreNoFiles() px \ + virtual HRESULT ExtractResult(HRESULT result) px \ + Z7_IFACEM_IExtractCallbackUI_Crypto(px) + +// IExtractCallbackUI - is non-COM interface +// IFolderArchiveExtractCallback - is COM interface +// Z7_IFACE_DECL_PURE_(IExtractCallbackUI, IFolderArchiveExtractCallback) +Z7_IFACE_DECL_PURE(IExtractCallbackUI) + + + +#define Z7_IFACEM_IGetProp(x) \ + x(GetProp(PROPID propID, PROPVARIANT *value)) \ + +Z7_IFACE_CONSTR_FOLDERARC(IGetProp, 0x20) + +#define Z7_IFACEM_IFolderExtractToStreamCallback(x) \ + x(UseExtractToStream(Int32 *res)) \ + x(GetStream7(const wchar_t *name, Int32 isDir, ISequentialOutStream **outStream, Int32 askExtractMode, IGetProp *getProp)) \ + x(PrepareOperation7(Int32 askExtractMode)) \ + x(SetOperationResult8(Int32 resultEOperationResult, Int32 encrypted, UInt64 size)) \ + +Z7_IFACE_CONSTR_FOLDERARC(IFolderExtractToStreamCallback, 0x31) + +Z7_PURE_INTERFACES_END + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/LoadCodecs.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Common/LoadCodecs.cpp new file mode 100644 index 00000000..c944ee93 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/LoadCodecs.cpp @@ -0,0 +1,1354 @@ +// LoadCodecs.cpp + +/* +Z7_EXTERNAL_CODECS +--------------- + CCodecs::Load() tries to detect the directory with plugins. + It stops the checking, if it can find any of the following items: + - 7z.dll file + - "Formats" subdir + - "Codecs" subdir + The order of check: + 1) directory of client executable + 2) WIN32: directory for REGISTRY item [HKEY_*\Software\7-Zip\Path**] + The order for HKEY_* : Path** : + - HKEY_CURRENT_USER : PathXX + - HKEY_LOCAL_MACHINE : PathXX + - HKEY_CURRENT_USER : Path + - HKEY_LOCAL_MACHINE : Path + PathXX is Path32 in 32-bit code + PathXX is Path64 in 64-bit code + + +EXPORT_CODECS +------------- + if (Z7_EXTERNAL_CODECS) is defined, then the code exports internal + codecs of client from CCodecs object to external plugins. + 7-Zip doesn't use that feature. 7-Zip uses the scheme: + - client application without internal plugins. + - 7z.dll module contains all (or almost all) plugins. + 7z.dll can use codecs from another plugins, if required. +*/ + + +#include "StdAfx.h" + +#include "../../../Common/MyCom.h" +#include "../../../Common/StringToInt.h" +#include "../../../Common/StringConvert.h" + +#include "../../../Windows/ErrorMsg.h" +#include "../../../Windows/FileIO.h" +#include "../../../Windows/PropVariant.h" + +#include "LoadCodecs.h" + +#include "../../ICoder.h" +#include "../../Common/RegisterArc.h" +#include "../../Common/RegisterCodec.h" + +#ifdef Z7_EXTERNAL_CODECS +// #define EXPORT_CODECS +#endif + +#ifdef Z7_EXTERNAL_CODECS + +#include "../../../Windows/FileFind.h" +#include "../../../Windows/DLL.h" + +#ifdef _WIN32 +#include "../../../Windows/FileName.h" +#include "../../../Windows/Registry.h" +#endif + +using namespace NWindows; +using namespace NFile; + + +#define kCodecsFolderName FTEXT("Codecs") +#define kFormatsFolderName FTEXT("Formats") + + +static CFSTR const kMainDll = + #ifdef _WIN32 + FTEXT("7z.dll"); + #else + FTEXT("7z.so"); + #endif + + +#ifdef _WIN32 + +static LPCTSTR const kRegistryPath = TEXT("Software") TEXT(STRING_PATH_SEPARATOR) TEXT("7-zip"); +static LPCWSTR const kProgramPathValue = L"Path"; +static LPCWSTR const kProgramPath2Value = L"Path" + #ifdef _WIN64 + L"64"; + #else + L"32"; + #endif + +static bool ReadPathFromRegistry(HKEY baseKey, LPCWSTR value, FString &path) +{ + NRegistry::CKey key; + if (key.Open(baseKey, kRegistryPath, KEY_READ) == ERROR_SUCCESS) + { + UString pathU; + if (key.QueryValue(value, pathU) == ERROR_SUCCESS) + { + path = us2fs(pathU); + NName::NormalizeDirPathPrefix(path); + return NFind::DoesFileExist_Raw(path + kMainDll); + } + } + return false; +} + +#endif // _WIN32 + +#endif // Z7_EXTERNAL_CODECS + + +static const unsigned kNumArcsMax = 72; +static unsigned g_NumArcs = 0; +static const CArcInfo *g_Arcs[kNumArcsMax]; + +void RegisterArc(const CArcInfo *arcInfo) throw() +{ + if (g_NumArcs < kNumArcsMax) + { + g_Arcs[g_NumArcs] = arcInfo; + g_NumArcs++; + } + // else throw 1; +} + +/* +static void SplitString(const UString &srcString, UStringVector &destStrings) +{ + destStrings.Clear(); + UString s; + unsigned len = srcString.Len(); + if (len == 0) + return; + for (unsigned i = 0; i < len; i++) + { + wchar_t c = srcString[i]; + if (c == L' ') + { + if (!s.IsEmpty()) + { + destStrings.Add(s); + s.Empty(); + } + } + else + s += c; + } + if (!s.IsEmpty()) + destStrings.Add(s); +} +*/ + +int CArcInfoEx::FindExtension(const UString &ext) const +{ + FOR_VECTOR (i, Exts) + if (ext.IsEqualTo_NoCase(Exts[i].Ext)) + return (int)i; + return -1; +} + +void CArcInfoEx::AddExts(const UString &ext, const UString &addExt) +{ + UStringVector exts, addExts; + SplitString(ext, exts); + SplitString(addExt, addExts); + FOR_VECTOR (i, exts) + { + CArcExtInfo extInfo; + extInfo.Ext = exts[i]; + if (i < addExts.Size()) + { + extInfo.AddExt = addExts[i]; + if (extInfo.AddExt.IsEqualTo("*")) + extInfo.AddExt.Empty(); + } + Exts.Add(extInfo); + } +} + +#ifndef Z7_SFX + +static bool ParseSignatures(const Byte *data, unsigned size, CObjectVector &signatures) +{ + signatures.Clear(); + while (size != 0) + { + const unsigned len = *data++; + size--; + if (len > size) + return false; + signatures.AddNew().CopyFrom(data, len); + data += len; + size -= len; + } + return true; +} + +#endif // Z7_SFX + +// #include + +#ifdef Z7_EXTERNAL_CODECS + +static FString GetBaseFolderPrefixFromRegistry() +{ + FString moduleFolderPrefix = NDLL::GetModuleDirPrefix(); + + #ifdef _WIN32 + if ( !NFind::DoesFileOrDirExist(moduleFolderPrefix + kMainDll) + && !NFind::DoesFileOrDirExist(moduleFolderPrefix + kCodecsFolderName) + && !NFind::DoesFileOrDirExist(moduleFolderPrefix + kFormatsFolderName)) + { + FString path; + if ( ReadPathFromRegistry(HKEY_CURRENT_USER, kProgramPath2Value, path) + || ReadPathFromRegistry(HKEY_LOCAL_MACHINE, kProgramPath2Value, path) + || ReadPathFromRegistry(HKEY_CURRENT_USER, kProgramPathValue, path) + || ReadPathFromRegistry(HKEY_LOCAL_MACHINE, kProgramPathValue, path)) + moduleFolderPrefix = path; + } + #endif + + // printf("\nmoduleFolderPrefix = %s\n", (const char *)GetAnsiString(moduleFolderPrefix)); + return moduleFolderPrefix; +} + + +static HRESULT GetCoderClass(Func_GetMethodProperty getMethodProperty, UInt32 index, + PROPID propId, CLSID &clsId, bool &isAssigned) +{ + NCOM::CPropVariant prop; + isAssigned = false; + RINOK(getMethodProperty(index, propId, &prop)) + if (prop.vt == VT_BSTR) + { + if (::SysStringByteLen(prop.bstrVal) != sizeof(GUID)) + return E_FAIL; + isAssigned = true; + clsId = *(const GUID *)(const void *)prop.bstrVal; + } + else if (prop.vt != VT_EMPTY) + return E_FAIL; + return S_OK; +} + + +static HRESULT GetMethodBoolProp(Func_GetMethodProperty getMethodProperty, UInt32 index, + PROPID propId, bool &resVal, bool &isAssigned) +{ + NCOM::CPropVariant prop; + resVal = false; + isAssigned = false; + RINOK(getMethodProperty(index, propId, &prop)) + if (prop.vt == VT_BOOL) + { + isAssigned = true; + resVal = VARIANT_BOOLToBool(prop.boolVal); + } + else if (prop.vt != VT_EMPTY) + return E_FAIL; + return S_OK; +} + +#if defined(__clang__) +#pragma GCC diagnostic ignored "-Wc++98-compat-pedantic" +#endif + +Z7_DIAGNOSTIC_IGNORE_CAST_FUNCTION + +#define MY_GET_FUNC(dest, type, lib, func) \ + dest = Z7_GET_PROC_ADDRESS(type, lib.Get_HMODULE(), func); +// #define MY_GET_FUNC(dest, type, func) dest = (type)(func); + +#define MY_GET_FUNC_LOC(dest, type, lib, func) \ + type dest; MY_GET_FUNC(dest, type, lib, func) + +HRESULT CCodecs::LoadCodecs() +{ + CCodecLib &lib = Libs.Back(); + + MY_GET_FUNC (lib.CreateDecoder, Func_CreateDecoder, lib.Lib, "CreateDecoder") + MY_GET_FUNC (lib.CreateEncoder, Func_CreateEncoder, lib.Lib, "CreateEncoder") + MY_GET_FUNC (lib.GetMethodProperty, Func_GetMethodProperty, lib.Lib, "GetMethodProperty") + + if (lib.GetMethodProperty) + { + UInt32 numMethods = 1; + MY_GET_FUNC_LOC (getNumberOfMethods, Func_GetNumberOfMethods, lib.Lib, "GetNumberOfMethods") + if (getNumberOfMethods) + { + RINOK(getNumberOfMethods(&numMethods)) + } + for (UInt32 i = 0; i < numMethods; i++) + { + CDllCodecInfo info; + info.LibIndex = Libs.Size() - 1; + info.CodecIndex = i; + RINOK(GetCoderClass(lib.GetMethodProperty, i, NMethodPropID::kEncoder, info.Encoder, info.EncoderIsAssigned)) + RINOK(GetCoderClass(lib.GetMethodProperty, i, NMethodPropID::kDecoder, info.Decoder, info.DecoderIsAssigned)) + RINOK(GetMethodBoolProp(lib.GetMethodProperty, i, NMethodPropID::kIsFilter, info.IsFilter, info.IsFilter_Assigned)) + Codecs.Add(info); + } + } + + MY_GET_FUNC_LOC (getHashers, Func_GetHashers, lib.Lib, "GetHashers") + if (getHashers) + { + RINOK(getHashers(&lib.ComHashers)) + if (lib.ComHashers) + { + UInt32 numMethods = lib.ComHashers->GetNumHashers(); + for (UInt32 i = 0; i < numMethods; i++) + { + CDllHasherInfo info; + info.LibIndex = Libs.Size() - 1; + info.HasherIndex = i; + Hashers.Add(info); + } + } + } + + return S_OK; +} + +static HRESULT GetProp( + Func_GetHandlerProperty getProp, + Func_GetHandlerProperty2 getProp2, + UInt32 index, PROPID propID, NCOM::CPropVariant &prop) +{ + if (getProp2) + return getProp2(index, propID, &prop); + return getProp(propID, &prop); +} + +static HRESULT GetProp_Bool( + Func_GetHandlerProperty getProp, + Func_GetHandlerProperty2 getProp2, + UInt32 index, PROPID propID, bool &res) +{ + res = false; + NCOM::CPropVariant prop; + RINOK(GetProp(getProp, getProp2, index, propID, prop)) + if (prop.vt == VT_BOOL) + res = VARIANT_BOOLToBool(prop.boolVal); + else if (prop.vt != VT_EMPTY) + return E_FAIL; + return S_OK; +} + +static HRESULT GetProp_UInt32( + Func_GetHandlerProperty getProp, + Func_GetHandlerProperty2 getProp2, + UInt32 index, PROPID propID, UInt32 &res, bool &defined) +{ + res = 0; + defined = false; + NCOM::CPropVariant prop; + RINOK(GetProp(getProp, getProp2, index, propID, prop)) + if (prop.vt == VT_UI4) + { + res = prop.ulVal; + defined = true; + } + else if (prop.vt != VT_EMPTY) + return E_FAIL; + return S_OK; +} + +static HRESULT GetProp_String( + Func_GetHandlerProperty getProp, + Func_GetHandlerProperty2 getProp2, + UInt32 index, PROPID propID, UString &res) +{ + res.Empty(); + NCOM::CPropVariant prop; + RINOK(GetProp(getProp, getProp2, index, propID, prop)) + if (prop.vt == VT_BSTR) + res.SetFromBstr(prop.bstrVal); + else if (prop.vt != VT_EMPTY) + return E_FAIL; + return S_OK; +} + +static HRESULT GetProp_RawData( + Func_GetHandlerProperty getProp, + Func_GetHandlerProperty2 getProp2, + UInt32 index, PROPID propID, CByteBuffer &bb) +{ + bb.Free(); + NCOM::CPropVariant prop; + RINOK(GetProp(getProp, getProp2, index, propID, prop)) + if (prop.vt == VT_BSTR) + { + UINT len = ::SysStringByteLen(prop.bstrVal); + bb.CopyFrom((const Byte *)prop.bstrVal, len); + } + else if (prop.vt != VT_EMPTY) + return E_FAIL; + return S_OK; +} + +static const UInt32 kArcFlagsPars[] = +{ + NArchive::NHandlerPropID::kKeepName, NArcInfoFlags::kKeepName, + NArchive::NHandlerPropID::kAltStreams, NArcInfoFlags::kAltStreams, + NArchive::NHandlerPropID::kNtSecure, NArcInfoFlags::kNtSecure +}; + +HRESULT CCodecs::LoadFormats() +{ + const NDLL::CLibrary &lib = Libs.Back().Lib; + + Func_GetHandlerProperty getProp = NULL; + MY_GET_FUNC_LOC (getProp2, Func_GetHandlerProperty2, lib, "GetHandlerProperty2") + MY_GET_FUNC_LOC (getIsArc, Func_GetIsArc, lib, "GetIsArc") + + UInt32 numFormats = 1; + + if (getProp2) + { + MY_GET_FUNC_LOC (getNumberOfFormats, Func_GetNumberOfFormats, lib, "GetNumberOfFormats") + if (getNumberOfFormats) + { + RINOK(getNumberOfFormats(&numFormats)) + } + } + else + { + MY_GET_FUNC (getProp, Func_GetHandlerProperty, lib, "GetHandlerProperty") + if (!getProp) + return S_OK; + } + + for (UInt32 i = 0; i < numFormats; i++) + { + CArcInfoEx item; + item.LibIndex = (int)(Libs.Size() - 1); + item.FormatIndex = i; + + RINOK(GetProp_String(getProp, getProp2, i, NArchive::NHandlerPropID::kName, item.Name)) + + { + NCOM::CPropVariant prop; + if (GetProp(getProp, getProp2, i, NArchive::NHandlerPropID::kClassID, prop) != S_OK) + continue; + if (prop.vt != VT_BSTR) + continue; + if (::SysStringByteLen(prop.bstrVal) != sizeof(GUID)) + return E_FAIL; + item.ClassID = *(const GUID *)(const void *)prop.bstrVal; + prop.Clear(); + } + + UString ext, addExt; + RINOK(GetProp_String(getProp, getProp2, i, NArchive::NHandlerPropID::kExtension, ext)) + RINOK(GetProp_String(getProp, getProp2, i, NArchive::NHandlerPropID::kAddExtension, addExt)) + item.AddExts(ext, addExt); + + GetProp_Bool(getProp, getProp2, i, NArchive::NHandlerPropID::kUpdate, item.UpdateEnabled); + bool flags_Defined = false; + RINOK(GetProp_UInt32(getProp, getProp2, i, NArchive::NHandlerPropID::kFlags, item.Flags, flags_Defined)) + item.NewInterface = flags_Defined; + if (!flags_Defined) // && item.UpdateEnabled + { + // support for DLL version before 9.31: + for (unsigned j = 0; j < Z7_ARRAY_SIZE(kArcFlagsPars); j += 2) + { + bool val = false; + GetProp_Bool(getProp, getProp2, i, kArcFlagsPars[j], val); + if (val) + item.Flags |= kArcFlagsPars[j + 1]; + } + } + + { + bool defined = false; + RINOK(GetProp_UInt32(getProp, getProp2, i, NArchive::NHandlerPropID::kTimeFlags, item.TimeFlags, defined)) + } + + CByteBuffer sig; + RINOK(GetProp_RawData(getProp, getProp2, i, NArchive::NHandlerPropID::kSignature, sig)) + if (sig.Size() != 0) + item.Signatures.Add(sig); + else + { + RINOK(GetProp_RawData(getProp, getProp2, i, NArchive::NHandlerPropID::kMultiSignature, sig)) + ParseSignatures(sig, (unsigned)sig.Size(), item.Signatures); + } + + bool signatureOffset_Defined; + RINOK(GetProp_UInt32(getProp, getProp2, i, NArchive::NHandlerPropID::kSignatureOffset, item.SignatureOffset, signatureOffset_Defined)) + + // bool version_Defined; + // RINOK(GetProp_UInt32(getProp, getProp2, i, NArchive::NHandlerPropID::kVersion, item.Version, version_Defined)); + + if (getIsArc) + getIsArc(i, &item.IsArcFunc); + + Formats.Add(item); + } + return S_OK; +} + +#ifdef Z7_LARGE_PAGES +extern "C" +{ + extern size_t g_LargePageSize; + extern size_t g_LargePageThresholdMin; + extern UInt32 g_LargePageFlags; +} +#endif + + +void CCodecs::AddLastError(const FString &path) +{ + const HRESULT res = GetLastError_noZero_HRESULT(); + CCodecError &error = Errors.AddNew(); + error.Path = path; + error.ErrorCode = res; +} + + +static bool IsSupportedDll(CCodecLib &lib) +{ + MY_GET_FUNC_LOC ( + f_GetModuleProp, + Func_GetModuleProp, lib.Lib, + "GetModuleProp") + /* p7zip and 7-Zip before v23 used virtual destructor in IUnknown, + if _WIN32 is not defined */ + UInt32 flags = + #ifdef _WIN32 + NModuleInterfaceType::k_IUnknown_VirtDestructor_No; + #else + NModuleInterfaceType::k_IUnknown_VirtDestructor_Yes; + #endif + if (f_GetModuleProp) + { + { + NCOM::CPropVariant prop; + if (f_GetModuleProp(NModulePropID::kInterfaceType, &prop) == S_OK) + { + if (prop.vt == VT_UI4) + flags = prop.ulVal; + else if (prop.vt != VT_EMPTY) + return false; + } + } + { + NCOM::CPropVariant prop; + if (f_GetModuleProp(NModulePropID::kVersion, &prop) == S_OK) + { + if (prop.vt == VT_UI4) + lib.Version = prop.ulVal; + } + } + } + if ( + flags + // (flags & NModuleFlags::kMask) + != NModuleInterfaceType::k_IUnknown_VirtDestructor_ThisModule) + return false; + return true; +} + + +HRESULT CCodecs::LoadDll(const FString &dllPath, bool needCheckDll, bool *loadedOK) +{ + if (loadedOK) + *loadedOK = false; + + // needCheckDll = 1; + + #ifdef _WIN32 + if (needCheckDll) + { + NDLL::CLibrary lib; + if (!lib.LoadEx(dllPath, LOAD_LIBRARY_AS_DATAFILE)) + { + /* if is not win32 + // %1 is not a valid Win32 application. + // #define ERROR_BAD_EXE_FORMAT 193L + */ + // return GetLastError_noZero_HRESULT(); + const DWORD lastError = GetLastError(); + if (lastError != ERROR_BAD_EXE_FORMAT) + { + CCodecError &error = Errors.AddNew(); + error.Path = dllPath; + error.Message = "cannot load file as datafile library"; + error.ErrorCode = HRESULT_FROM_WIN32(lastError); + } + return S_OK; + } + } + #else + UNUSED_VAR(needCheckDll) + #endif + + Libs.AddNew(); + CCodecLib &lib = Libs.Back(); + lib.Path = dllPath; + bool used = false; + // HRESULT res = S_OK; + + if (lib.Lib.Load(dllPath)) + { + if (!IsSupportedDll(lib)) + { + CCodecError &error = Errors.AddNew(); + error.Path = dllPath; + error.Message = "the module is not compatible with program"; + } + else + { + if (loadedOK) + *loadedOK = true; + /* + #ifdef NEW_FOLDER_INTERFACE + lib.LoadIcons(); + #endif + */ + + /* + { + MY_GET_FUNC_LOC (_libStartup, Func_libStartup, lib.Lib, "LibStartup") + if (_libStartup) + { + HRESULT res = _libStartup(); + if (res != 0) + { + CCodecError &error = Errors.AddNew(); + error.Path = dllPath; + error.ErrorCode = res; + } + } + } + */ + + #ifdef Z7_LARGE_PAGES + { + MY_GET_FUNC_LOC (setLargePageMode2, Func_SetLargePageMode2, lib.Lib, "SetLargePageMode2") + if (setLargePageMode2) + { + /* const HRESULT hres = */ setLargePageMode2(g_LargePageFlags, g_LargePageSize, g_LargePageThresholdMin); + /* + if (hres != S_OK) + { + CCodecError &error = Errors.AddNew(); + error.Path = dllPath; + error.Message = "SetLargePageMode2 Error"; + error.ErrorCode = hres; + } + */ + } + else if (g_LargePageSize != 0) + { + MY_GET_FUNC_LOC (setLargePageMode, Func_SetLargePageMode, lib.Lib, "SetLargePageMode") + if (setLargePageMode) + setLargePageMode(); + } + } + #endif + + if (CaseSensitive_Change) + { + MY_GET_FUNC_LOC (setCaseSensitive, Func_SetCaseSensitive, lib.Lib, "SetCaseSensitive") + if (setCaseSensitive) + setCaseSensitive(CaseSensitive ? 1 : 0); + } + + /* + { + MY_GET_FUNC_LOC (setClientVersion, Func_SetClientVersion, lib.Lib, "SetClientVersion") + if (setClientVersion) + { + // const UInt32 kVersion = (MY_VER_MAJOR << 16) | MY_VER_MINOR; + setClientVersion(g_ClientVersion); + } + } + */ + + + MY_GET_FUNC (lib.CreateObject, Func_CreateObject, lib.Lib, "CreateObject") + { + unsigned startSize = Codecs.Size() + Hashers.Size(); + HRESULT res = LoadCodecs(); + if (startSize != Codecs.Size() + Hashers.Size()) + used = true; + if (res == S_OK && lib.CreateObject) + { + startSize = Formats.Size(); + res = LoadFormats(); + if (startSize != Formats.Size()) + used = true; + } + if (res != S_OK) + { + CCodecError &error = Errors.AddNew(); + error.Path = dllPath; + error.ErrorCode = res; + } + } + // plugins can use non-7-zip dlls, so we silently ignore non7zip DLLs + /* + if (!used) + { + CCodecError &error = Errors.AddNew(); + error.Path = dllPath; + error.Message = "no 7-Zip code"; + } + */ + } + } + else + { + AddLastError(dllPath); + } + + if (!used) + Libs.DeleteBack(); + + return S_OK; +} + +HRESULT CCodecs::LoadDllsFromFolder(const FString &folderPath) +{ + if (!NFile::NFind::DoesDirExist_FollowLink(folderPath)) + // if (!NFile::NFind::DoesDirExist(folderPath)) + { + // AddLastError(folderPath); + return S_OK; + } + + FString folderPrefix = folderPath; + folderPrefix.Add_PathSepar(); + + NFile::NFind::CEnumerator enumerator; + enumerator.SetDirPrefix(folderPrefix); + NFile::NFind::CDirEntry fi; + for (;;) + { + bool found; + if (!enumerator.Next(fi, found)) + { + // it can be wrong Symbolic link to folder here + AddLastError(folderPath); + break; + // return GetLastError_noZero_HRESULT(); + } + if (!found) + break; + #ifdef _WIN32 + if (fi.IsDir()) + continue; + #else + if (enumerator.DirEntry_IsDir(fi, true)) // followLink + continue; + #endif + + RINOK(LoadDll(folderPrefix + fi.Name, true)) + } + return S_OK; +} + +void CCodecs::CloseLibs() +{ + // OutputDebugStringA("~CloseLibs start"); + /* + WIN32: FreeLibrary() (CLibrary::Free()) function doesn't work as expected, + if it's called from another FreeLibrary() call. + So we need to call FreeLibrary() before global destructors. + + Also we free global links from DLLs to object of this module before CLibrary::Free() call. + */ + + FOR_VECTOR(i, Libs) + { + const CCodecLib &lib = Libs[i]; + if (lib.SetCodecs) + lib.SetCodecs(NULL); + } + + // OutputDebugStringA("~CloseLibs after SetCodecs"); + Libs.Clear(); + // OutputDebugStringA("~CloseLibs end"); +} + +#endif // Z7_EXTERNAL_CODECS + + +HRESULT CCodecs::Load() +{ + /* + #ifdef NEW_FOLDER_INTERFACE + InternalIcons.LoadIcons(g_hInstance); + #endif + */ + + Formats.Clear(); + + #ifdef Z7_EXTERNAL_CODECS + Errors.Clear(); + MainDll_ErrorPath.Empty(); + Codecs.Clear(); + Hashers.Clear(); + #endif + + for (UInt32 i = 0; i < g_NumArcs; i++) + { + const CArcInfo &arc = *g_Arcs[i]; + CArcInfoEx item; + + item.Name = arc.Name; + item.CreateInArchive = arc.CreateInArchive; + item.IsArcFunc = arc.IsArc; + item.Flags = arc.Flags; + + { + UString e, ae; + if (arc.Ext) + e = arc.Ext; + if (arc.AddExt) + ae = arc.AddExt; + item.AddExts(e, ae); + } + + #ifndef Z7_SFX + + item.CreateOutArchive = arc.CreateOutArchive; + item.UpdateEnabled = (arc.CreateOutArchive != NULL); + item.SignatureOffset = arc.SignatureOffset; + // item.Version = MY_VER_MIX; + item.NewInterface = true; + + if (arc.IsMultiSignature()) + ParseSignatures(arc.Signature, arc.SignatureSize, item.Signatures); + else + { + if (arc.SignatureSize != 0) // 21.04 + item.Signatures.AddNew().CopyFrom(arc.Signature, arc.SignatureSize); + } + + #endif + + Formats.Add(item); + } + + // printf("\nLoad codecs \n"); + + #ifdef Z7_EXTERNAL_CODECS + const FString baseFolder = GetBaseFolderPrefixFromRegistry(); + { + bool loadedOK; + RINOK(LoadDll(baseFolder + kMainDll, false, &loadedOK)) + if (!loadedOK) + MainDll_ErrorPath = kMainDll; + } + RINOK(LoadDllsFromFolder(baseFolder + kCodecsFolderName)) + RINOK(LoadDllsFromFolder(baseFolder + kFormatsFolderName)) + + NeedSetLibCodecs = true; + + if (Libs.Size() == 0) + NeedSetLibCodecs = false; + else if (Libs.Size() == 1) + { + // we don't need to set ISetCompressCodecsInfo, if all arcs and codecs are in one external module. + #ifndef EXPORT_CODECS + if (g_NumArcs == 0) + NeedSetLibCodecs = false; + #endif + } + + if (NeedSetLibCodecs) + { + /* 15.00: now we call global function in DLL: SetCompressCodecsInfo(c) + old versions called only ISetCompressCodecsInfo::SetCompressCodecsInfo(c) for each archive handler */ + + FOR_VECTOR(i, Libs) + { + CCodecLib &lib = Libs[i]; + MY_GET_FUNC (lib.SetCodecs, Func_SetCodecs, lib.Lib, "SetCodecs") + if (lib.SetCodecs) + { + RINOK(lib.SetCodecs(this)) + } + } + } + + #endif + + // we sort Formats to get fixed order of Formats after compilation. + Formats.Sort(); + return S_OK; +} + +#ifndef Z7_SFX + +int CCodecs::FindFormatForArchiveName(const UString &arcPath) const +{ + int dotPos = arcPath.ReverseFind_Dot(); + if (dotPos <= arcPath.ReverseFind_PathSepar()) + return -1; + const UString ext = arcPath.Ptr((unsigned)(dotPos + 1)); + if (ext.IsEmpty()) + return -1; + if (ext.IsEqualTo_Ascii_NoCase("exe")) + return -1; + FOR_VECTOR (i, Formats) + { + const CArcInfoEx &arc = Formats[i]; + /* + if (!arc.UpdateEnabled) + continue; + */ + if (arc.FindExtension(ext) >= 0) + return (int)i; + } + return -1; +} + +int CCodecs::FindFormatForExtension(const UString &ext) const +{ + if (ext.IsEmpty()) + return -1; + FOR_VECTOR (i, Formats) + if (Formats[i].FindExtension(ext) >= 0) + return (int)i; + return -1; +} + +int CCodecs::FindFormatForArchiveType(const UString &arcType) const +{ + FOR_VECTOR (i, Formats) + if (Formats[i].Name.IsEqualTo_NoCase(arcType)) + return (int)i; + return -1; +} + +bool CCodecs::FindFormatForArchiveType(const UString &arcType, CIntVector &formatIndices) const +{ + formatIndices.Clear(); + for (unsigned pos = 0; pos < arcType.Len();) + { + int pos2 = arcType.Find(L'.', pos); + if (pos2 < 0) + pos2 = (int)arcType.Len(); + const UString name = arcType.Mid(pos, (unsigned)pos2 - pos); + if (name.IsEmpty()) + return false; + const int index = FindFormatForArchiveType(name); + if (index < 0 && !name.IsEqualTo("*")) + { + formatIndices.Clear(); + return false; + } + formatIndices.Add(index); + pos = (unsigned)pos2 + 1; + } + return true; +} + +#endif // Z7_SFX + + +#ifdef Z7_EXTERNAL_CODECS + +// #define EXPORT_CODECS + +#ifdef EXPORT_CODECS + +extern unsigned g_NumCodecs; +STDAPI CreateDecoder(UInt32 index, const GUID *iid, void **outObject); +STDAPI CreateEncoder(UInt32 index, const GUID *iid, void **outObject); +STDAPI GetMethodProperty(UInt32 codecIndex, PROPID propID, PROPVARIANT *value); +#define NUM_EXPORT_CODECS g_NumCodecs + +extern unsigned g_NumHashers; +STDAPI CreateHasher(UInt32 index, IHasher **hasher); +STDAPI GetHasherProp(UInt32 codecIndex, PROPID propID, PROPVARIANT *value); +#define NUM_EXPORT_HASHERS g_NumHashers + +#else // EXPORT_CODECS + +#define NUM_EXPORT_CODECS 0 +#define NUM_EXPORT_HASHERS 0 + +#endif // EXPORT_CODECS + +Z7_COM7F_IMF(CCodecs::GetNumMethods(UInt32 *numMethods)) +{ + *numMethods = NUM_EXPORT_CODECS + #ifdef Z7_EXTERNAL_CODECS + + Codecs.Size() + #endif + ; + return S_OK; +} + +Z7_COM7F_IMF(CCodecs::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) +{ + #ifdef EXPORT_CODECS + if (index < g_NumCodecs) + return GetMethodProperty(index, propID, value); + #endif + + #ifdef Z7_EXTERNAL_CODECS + const CDllCodecInfo &ci = Codecs[index - NUM_EXPORT_CODECS]; + + if (propID == NMethodPropID::kDecoderIsAssigned || + propID == NMethodPropID::kEncoderIsAssigned) + { + NCOM::CPropVariant prop; + prop = (bool)((propID == NMethodPropID::kDecoderIsAssigned) ? + ci.DecoderIsAssigned : + ci.EncoderIsAssigned); + prop.Detach(value); + return S_OK; + } + + if (propID == NMethodPropID::kIsFilter && ci.IsFilter_Assigned) + { + NCOM::CPropVariant prop; + prop = (bool)ci.IsFilter; + prop.Detach(value); + return S_OK; + } + + const CCodecLib &lib = Libs[ci.LibIndex]; + return lib.GetMethodProperty(ci.CodecIndex, propID, value); + #else + return E_FAIL; + #endif +} + +Z7_COM7F_IMF(CCodecs::CreateDecoder(UInt32 index, const GUID *iid, void **coder)) +{ + #ifdef EXPORT_CODECS + if (index < g_NumCodecs) + return CreateDecoder(index, iid, coder); + #endif + + #ifdef Z7_EXTERNAL_CODECS + const CDllCodecInfo &ci = Codecs[index - NUM_EXPORT_CODECS]; + if (ci.DecoderIsAssigned) + { + const CCodecLib &lib = Libs[ci.LibIndex]; + if (lib.CreateDecoder) + return lib.CreateDecoder(ci.CodecIndex, iid, (void **)coder); + if (lib.CreateObject) + return lib.CreateObject(&ci.Decoder, iid, (void **)coder); + } + return S_OK; + #else + return E_FAIL; + #endif +} + +Z7_COM7F_IMF(CCodecs::CreateEncoder(UInt32 index, const GUID *iid, void **coder)) +{ + #ifdef EXPORT_CODECS + if (index < g_NumCodecs) + return CreateEncoder(index, iid, coder); + #endif + + #ifdef Z7_EXTERNAL_CODECS + const CDllCodecInfo &ci = Codecs[index - NUM_EXPORT_CODECS]; + if (ci.EncoderIsAssigned) + { + const CCodecLib &lib = Libs[ci.LibIndex]; + if (lib.CreateEncoder) + return lib.CreateEncoder(ci.CodecIndex, iid, (void **)coder); + if (lib.CreateObject) + return lib.CreateObject(&ci.Encoder, iid, (void **)coder); + } + return S_OK; + #else + return E_FAIL; + #endif +} + + +Z7_COM7F_IMF2(UInt32, CCodecs::GetNumHashers()) +{ + return NUM_EXPORT_HASHERS + #ifdef Z7_EXTERNAL_CODECS + + Hashers.Size() + #endif + ; +} + +Z7_COM7F_IMF(CCodecs::GetHasherProp(UInt32 index, PROPID propID, PROPVARIANT *value)) +{ + #ifdef EXPORT_CODECS + if (index < g_NumHashers) + return ::GetHasherProp(index, propID, value); + #endif + + #ifdef Z7_EXTERNAL_CODECS + const CDllHasherInfo &ci = Hashers[index - NUM_EXPORT_HASHERS]; + return Libs[ci.LibIndex].ComHashers->GetHasherProp(ci.HasherIndex, propID, value); + #else + return E_FAIL; + #endif +} + +Z7_COM7F_IMF(CCodecs::CreateHasher(UInt32 index, IHasher **hasher)) +{ + #ifdef EXPORT_CODECS + if (index < g_NumHashers) + return CreateHasher(index, hasher); + #endif + #ifdef Z7_EXTERNAL_CODECS + const CDllHasherInfo &ci = Hashers[index - NUM_EXPORT_HASHERS]; + return Libs[ci.LibIndex].ComHashers->CreateHasher(ci.HasherIndex, hasher); + #else + return E_FAIL; + #endif +} + +int CCodecs::GetCodec_LibIndex(UInt32 index) const +{ + #ifdef EXPORT_CODECS + if (index < g_NumCodecs) + return -1; + #endif + + #ifdef Z7_EXTERNAL_CODECS + const CDllCodecInfo &ci = Codecs[index - NUM_EXPORT_CODECS]; + return (int)ci.LibIndex; + #else + return -1; + #endif +} + +int CCodecs::GetHasherLibIndex(UInt32 index) +{ + #ifdef EXPORT_CODECS + if (index < g_NumHashers) + return -1; + #endif + + #ifdef Z7_EXTERNAL_CODECS + const CDllHasherInfo &ci = Hashers[index - NUM_EXPORT_HASHERS]; + return (int)ci.LibIndex; + #else + return -1; + #endif +} + +bool CCodecs::GetCodec_DecoderIsAssigned(UInt32 index) const +{ + #ifdef EXPORT_CODECS + if (index < g_NumCodecs) + { + NCOM::CPropVariant prop; + if (GetProperty(index, NMethodPropID::kDecoderIsAssigned, &prop) == S_OK) + { + if (prop.vt == VT_BOOL) + return VARIANT_BOOLToBool(prop.boolVal); + } + return false; + } + #endif + + #ifdef Z7_EXTERNAL_CODECS + return Codecs[index - NUM_EXPORT_CODECS].DecoderIsAssigned; + #else + return false; + #endif +} + + +bool CCodecs::GetCodec_EncoderIsAssigned(UInt32 index) const +{ + #ifdef EXPORT_CODECS + if (index < g_NumCodecs) + { + NCOM::CPropVariant prop; + if (GetProperty(index, NMethodPropID::kEncoderIsAssigned, &prop) == S_OK) + { + if (prop.vt == VT_BOOL) + return VARIANT_BOOLToBool(prop.boolVal); + } + return false; + } + #endif + + #ifdef Z7_EXTERNAL_CODECS + return Codecs[index - NUM_EXPORT_CODECS].EncoderIsAssigned; + #else + return false; + #endif +} + + +bool CCodecs::GetCodec_IsFilter(UInt32 index, bool &isAssigned) const +{ + isAssigned = false; + #ifdef EXPORT_CODECS + if (index < g_NumCodecs) + { + NCOM::CPropVariant prop; + if (GetProperty(index, NMethodPropID::kIsFilter, &prop) == S_OK) + { + if (prop.vt == VT_BOOL) + { + isAssigned = true; + return VARIANT_BOOLToBool(prop.boolVal); + } + } + return false; + } + #endif + + #ifdef Z7_EXTERNAL_CODECS + { + const CDllCodecInfo &c = Codecs[index - NUM_EXPORT_CODECS]; + isAssigned = c.IsFilter_Assigned; + return c.IsFilter; + } + #else + return false; + #endif +} + + +UInt32 CCodecs::GetCodec_NumStreams(UInt32 index) +{ + NCOM::CPropVariant prop; + if (GetProperty(index, NMethodPropID::kPackStreams, &prop) != S_OK) + return 0; + if (prop.vt == VT_UI4) + return (UInt32)prop.ulVal; + if (prop.vt == VT_EMPTY) + return 1; + return 0; +} + +HRESULT CCodecs::GetCodec_Id(UInt32 index, UInt64 &id) +{ + NCOM::CPropVariant prop; + RINOK(GetProperty(index, NMethodPropID::kID, &prop)) + if (prop.vt != VT_UI8) + return E_INVALIDARG; + id = prop.uhVal.QuadPart; + return S_OK; +} + +AString CCodecs::GetCodec_Name(UInt32 index) +{ + AString s; + NCOM::CPropVariant prop; + if (GetProperty(index, NMethodPropID::kName, &prop) == S_OK) + if (prop.vt == VT_BSTR) + s.SetFromWStr_if_Ascii(prop.bstrVal); + return s; +} + +UInt64 CCodecs::GetHasherId(UInt32 index) +{ + NCOM::CPropVariant prop; + if (GetHasherProp(index, NMethodPropID::kID, &prop) != S_OK) + return 0; + if (prop.vt != VT_UI8) + return 0; + return prop.uhVal.QuadPart; +} + +AString CCodecs::GetHasherName(UInt32 index) +{ + AString s; + NCOM::CPropVariant prop; + if (GetHasherProp(index, NMethodPropID::kName, &prop) == S_OK) + if (prop.vt == VT_BSTR) + s.SetFromWStr_if_Ascii(prop.bstrVal); + return s; +} + +UInt32 CCodecs::GetHasherDigestSize(UInt32 index) +{ + NCOM::CPropVariant prop; + if (GetHasherProp(index, NMethodPropID::kDigestSize, &prop) != S_OK) + return 0; + if (prop.vt != VT_UI4) + return 0; + return prop.ulVal; +} + +void CCodecs::GetCodecsErrorMessage(UString &s) +{ + s.Empty(); + FOR_VECTOR (i, Errors) + { + const CCodecError &ce = Errors[i]; + s += "Codec Load Error: "; + s += fs2us(ce.Path); + if (ce.ErrorCode != 0) + { + s += " : "; + s += NWindows::NError::MyFormatMessage(ce.ErrorCode); + } + if (!ce.Message.IsEmpty()) + { + s += " : "; + s += ce.Message; + } + s.Add_LF(); + } +} + +#endif // Z7_EXTERNAL_CODECS + +#ifndef Z7_SFX + +extern unsigned g_NumCodecs; +extern const CCodecInfo *g_Codecs[]; + +void CCodecs::Get_CodecsInfoUser_Vector(CObjectVector &v) +{ + v.Clear(); + { + for (unsigned i = 0; i < g_NumCodecs; i++) + { + const CCodecInfo &cod = *g_Codecs[i]; + CCodecInfoUser &u = v.AddNew(); + u.EncoderIsAssigned = (cod.CreateEncoder != NULL); + u.DecoderIsAssigned = (cod.CreateDecoder != NULL); + u.IsFilter_Assigned = true; + u.IsFilter = cod.IsFilter; + u.NumStreams = cod.NumStreams; + u.Name = cod.Name; + } + } + + + #ifdef Z7_EXTERNAL_CODECS + { + UInt32 numMethods; + if (GetNumMethods(&numMethods) == S_OK) + for (UInt32 j = 0; j < numMethods; j++) + { + CCodecInfoUser &u = v.AddNew(); + u.EncoderIsAssigned = GetCodec_EncoderIsAssigned(j); + u.DecoderIsAssigned = GetCodec_DecoderIsAssigned(j); + u.IsFilter = GetCodec_IsFilter(j, u.IsFilter_Assigned); + u.NumStreams = GetCodec_NumStreams(j); + u.Name = GetCodec_Name(j); + } + } + #endif +} + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/LoadCodecs.h b/src/plugins/7zip/7za/cpp/7zip/UI/Common/LoadCodecs.h new file mode 100644 index 00000000..a81bb5c7 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/LoadCodecs.h @@ -0,0 +1,482 @@ +// LoadCodecs.h + +#ifndef ZIP7_INC_LOAD_CODECS_H +#define ZIP7_INC_LOAD_CODECS_H + +/* +Client application uses LoadCodecs.* to load plugins to +CCodecs object, that contains 3 lists of plugins: + 1) Formats - internal and external archive handlers + 2) Codecs - external codecs + 3) Hashers - external hashers + +Z7_EXTERNAL_CODECS +--------------- + + if Z7_EXTERNAL_CODECS is defined, then the code tries to load external + plugins from DLL files (shared libraries). + + There are two types of executables in 7-Zip: + + 1) Executable that uses external plugins must be compiled + with Z7_EXTERNAL_CODECS defined: + - 7z.exe, 7zG.exe, 7zFM.exe + + Note: Z7_EXTERNAL_CODECS is used also in CPP/7zip/Common/CreateCoder.h + that code is used in plugin module (7z.dll). + + 2) Standalone modules are compiled without Z7_EXTERNAL_CODECS: + - SFX modules: 7z.sfx, 7zCon.sfx + - standalone versions of console 7-Zip: 7za.exe, 7zr.exe + + if Z7_EXTERNAL_CODECS is defined, CCodecs class implements interfaces: + - ICompressCodecsInfo : for Codecs + - IHashers : for Hashers + + The client application can send CCodecs object to each plugin module. + And plugin module can use ICompressCodecsInfo or IHashers interface to access + another plugins. + + There are 2 ways to send (ICompressCodecsInfo * compressCodecsInfo) to plugin + 1) for old versions: + a) request ISetCompressCodecsInfo from created archive handler. + b) call ISetCompressCodecsInfo::SetCompressCodecsInfo(compressCodecsInfo) + 2) for new versions: + a) request "SetCodecs" function from DLL file + b) call SetCodecs(compressCodecsInfo) function from DLL file +*/ + +#include "../../../Common/MyBuffer.h" +#include "../../../Common/MyCom.h" +#include "../../../Common/MyString.h" +#include "../../../Common/ComTry.h" + +#ifdef Z7_EXTERNAL_CODECS +#include "../../../Windows/DLL.h" +#endif + +#include "../../ICoder.h" + +#include "../../Archive/IArchive.h" + + +#ifdef Z7_EXTERNAL_CODECS + +struct CDllCodecInfo +{ + unsigned LibIndex; + UInt32 CodecIndex; + bool EncoderIsAssigned; + bool DecoderIsAssigned; + bool IsFilter; + bool IsFilter_Assigned; + CLSID Encoder; + CLSID Decoder; +}; + +struct CDllHasherInfo +{ + unsigned LibIndex; + UInt32 HasherIndex; +}; + +#endif + +struct CArcExtInfo +{ + UString Ext; + UString AddExt; + + CArcExtInfo() {} + CArcExtInfo(const UString &ext): Ext(ext) {} + CArcExtInfo(const UString &ext, const UString &addExt): Ext(ext), AddExt(addExt) {} +}; + + +struct CArcInfoEx +{ + UInt32 Flags; + UInt32 TimeFlags; + + Func_CreateInArchive CreateInArchive; + Func_IsArc IsArcFunc; + + UString Name; + CObjectVector Exts; + + #ifndef Z7_SFX + Func_CreateOutArchive CreateOutArchive; + bool UpdateEnabled; + bool NewInterface; + // UInt32 Version; + UInt32 SignatureOffset; + CObjectVector Signatures; + /* + #ifdef NEW_FOLDER_INTERFACE + UStringVector AssociateExts; + #endif + */ + #endif + + #ifdef Z7_EXTERNAL_CODECS + int LibIndex; + UInt32 FormatIndex; + CLSID ClassID; + #endif + + int Compare(const CArcInfoEx &a) const + { + const int res = Name.Compare(a.Name); + if (res != 0) + return res; + #ifdef Z7_EXTERNAL_CODECS + return MyCompare(LibIndex, a.LibIndex); + #else + return 0; + #endif + /* + if (LibIndex < a.LibIndex) return -1; + if (LibIndex > a.LibIndex) return 1; + return 0; + */ + } + + bool Flags_KeepName() const { return (Flags & NArcInfoFlags::kKeepName) != 0; } + bool Flags_FindSignature() const { return (Flags & NArcInfoFlags::kFindSignature) != 0; } + + bool Flags_AltStreams() const { return (Flags & NArcInfoFlags::kAltStreams) != 0; } + bool Flags_NtSecurity() const { return (Flags & NArcInfoFlags::kNtSecure) != 0; } + bool Flags_SymLinks() const { return (Flags & NArcInfoFlags::kSymLinks) != 0; } + bool Flags_HardLinks() const { return (Flags & NArcInfoFlags::kHardLinks) != 0; } + + bool Flags_UseGlobalOffset() const { return (Flags & NArcInfoFlags::kUseGlobalOffset) != 0; } + bool Flags_StartOpen() const { return (Flags & NArcInfoFlags::kStartOpen) != 0; } + bool Flags_BackwardOpen() const { return (Flags & NArcInfoFlags::kBackwardOpen) != 0; } + bool Flags_PreArc() const { return (Flags & NArcInfoFlags::kPreArc) != 0; } + bool Flags_PureStartOpen() const { return (Flags & NArcInfoFlags::kPureStartOpen) != 0; } + bool Flags_ByExtOnlyOpen() const { return (Flags & NArcInfoFlags::kByExtOnlyOpen) != 0; } + bool Flags_HashHandler() const { return (Flags & NArcInfoFlags::kHashHandler) != 0; } + + bool Flags_CTime() const { return (Flags & NArcInfoFlags::kCTime) != 0; } + bool Flags_ATime() const { return (Flags & NArcInfoFlags::kATime) != 0; } + bool Flags_MTime() const { return (Flags & NArcInfoFlags::kMTime) != 0; } + + bool Flags_CTime_Default() const { return (Flags & NArcInfoFlags::kCTime_Default) != 0; } + bool Flags_ATime_Default() const { return (Flags & NArcInfoFlags::kATime_Default) != 0; } + bool Flags_MTime_Default() const { return (Flags & NArcInfoFlags::kMTime_Default) != 0; } + + UInt32 Get_TimePrecFlags() const + { + return (TimeFlags >> NArcInfoTimeFlags::kTime_Prec_Mask_bit_index) & + (((UInt32)1 << NArcInfoTimeFlags::kTime_Prec_Mask_num_bits) - 1); + } + + UInt32 Get_DefaultTimePrec() const + { + return (TimeFlags >> NArcInfoTimeFlags::kTime_Prec_Default_bit_index) & + (((UInt32)1 << NArcInfoTimeFlags::kTime_Prec_Default_num_bits) - 1); + } + + + UString GetMainExt() const + { + if (Exts.IsEmpty()) + return UString(); + return Exts[0].Ext; + } + int FindExtension(const UString &ext) const; + + bool Is_7z() const { return Name.IsEqualTo_Ascii_NoCase("7z"); } + bool Is_Split() const { return Name.IsEqualTo_Ascii_NoCase("Split"); } + bool Is_Xz() const { return Name.IsEqualTo_Ascii_NoCase("xz"); } + bool Is_BZip2() const { return Name.IsEqualTo_Ascii_NoCase("bzip2"); } + bool Is_GZip() const { return Name.IsEqualTo_Ascii_NoCase("gzip"); } + bool Is_Tar() const { return Name.IsEqualTo_Ascii_NoCase("tar"); } + bool Is_Zip() const { return Name.IsEqualTo_Ascii_NoCase("zip"); } + bool Is_Rar() const { return Name.IsEqualTo_Ascii_NoCase("rar"); } + bool Is_Zstd() const { return Name.IsEqualTo_Ascii_NoCase("zstd"); } + + /* + UString GetAllExtensions() const + { + UString s; + for (int i = 0; i < Exts.Size(); i++) + { + if (i > 0) + s.Add_Space(); + s += Exts[i].Ext; + } + return s; + } + */ + + void AddExts(const UString &ext, const UString &addExt); + + + CArcInfoEx(): + Flags(0), + TimeFlags(0), + CreateInArchive(NULL), + IsArcFunc(NULL) + #ifndef Z7_SFX + , CreateOutArchive(NULL) + , UpdateEnabled(false) + , NewInterface(false) + // , Version(0) + , SignatureOffset(0) + #endif + #ifdef Z7_EXTERNAL_CODECS + , LibIndex(-1) + #endif + {} +}; + + +#ifdef Z7_EXTERNAL_CODECS + +struct CCodecLib +{ + NWindows::NDLL::CLibrary Lib; + FString Path; + + Func_CreateObject CreateObject; + Func_GetMethodProperty GetMethodProperty; + Func_CreateDecoder CreateDecoder; + Func_CreateEncoder CreateEncoder; + Func_SetCodecs SetCodecs; + + CMyComPtr ComHashers; + + UInt32 Version; + + /* + #ifdef NEW_FOLDER_INTERFACE + CCodecIcons CodecIcons; + void LoadIcons() { CodecIcons.LoadIcons((HMODULE)Lib); } + #endif + */ + + CCodecLib(): + CreateObject(NULL), + GetMethodProperty(NULL), + CreateDecoder(NULL), + CreateEncoder(NULL), + SetCodecs(NULL), + Version(0) + {} +}; + +#endif + +struct CCodecError +{ + FString Path; + HRESULT ErrorCode; + AString Message; + CCodecError(): ErrorCode(0) {} +}; + + +struct CCodecInfoUser +{ + // unsigned LibIndex; + // UInt32 CodecIndex; + // UInt64 id; + bool EncoderIsAssigned; + bool DecoderIsAssigned; + bool IsFilter; + bool IsFilter_Assigned; + UInt32 NumStreams; + AString Name; +}; + + +class CCodecs Z7_final: + #ifdef Z7_EXTERNAL_CODECS + public ICompressCodecsInfo, + public IHashers, + #else + public IUnknown, + #endif + public CMyUnknownImp +{ +#ifdef Z7_EXTERNAL_CODECS + Z7_IFACES_IMP_UNK_2(ICompressCodecsInfo, IHashers) +#else + Z7_COM_UNKNOWN_IMP_0 +#endif // Z7_EXTERNAL_CODECS + + Z7_CLASS_NO_COPY(CCodecs) +public: + #ifdef Z7_EXTERNAL_CODECS + + CObjectVector Libs; + FString MainDll_ErrorPath; + CObjectVector Errors; + + void AddLastError(const FString &path); + void CloseLibs(); + + class CReleaser + { + Z7_CLASS_NO_COPY(CReleaser) + + /* CCodecsReleaser object releases CCodecs links. + 1) CCodecs is COM object that is deleted when all links to that object will be released/ + 2) CCodecs::Libs[i] can hold (ICompressCodecsInfo *) link to CCodecs object itself. + To break that reference loop, we must close all CCodecs::Libs in CCodecsReleaser desttructor. */ + + CCodecs *_codecs; + + public: + CReleaser(): _codecs(NULL) {} + void Set(CCodecs *codecs) { _codecs = codecs; } + ~CReleaser() { if (_codecs) _codecs->CloseLibs(); } + }; + + bool NeedSetLibCodecs; // = false, if we don't need to set codecs for archive handler via ISetCompressCodecsInfo + + HRESULT LoadCodecs(); + HRESULT LoadFormats(); + HRESULT LoadDll(const FString &path, bool needCheckDll, bool *loadedOK = NULL); + HRESULT LoadDllsFromFolder(const FString &folderPrefix); + + HRESULT CreateArchiveHandler(const CArcInfoEx &ai, bool outHandler, void **archive) const + { + return Libs[(unsigned)ai.LibIndex].CreateObject(&ai.ClassID, outHandler ? &IID_IOutArchive : &IID_IInArchive, (void **)archive); + } + + #endif + + /* + #ifdef NEW_FOLDER_INTERFACE + CCodecIcons InternalIcons; + #endif + */ + + CObjectVector Formats; + + #ifdef Z7_EXTERNAL_CODECS + CRecordVector Codecs; + CRecordVector Hashers; + #endif + + bool CaseSensitive_Change; + bool CaseSensitive; + + CCodecs(): + #ifdef Z7_EXTERNAL_CODECS + NeedSetLibCodecs(true), + #endif + CaseSensitive_Change(false), + CaseSensitive(false) + {} + + ~CCodecs() + { + // OutputDebugStringA("~CCodecs"); + } + + const wchar_t *GetFormatNamePtr(int formatIndex) const + { + return formatIndex < 0 ? L"#" : (const wchar_t *)Formats[(unsigned)formatIndex].Name; + } + + HRESULT Load(); + + #ifndef Z7_SFX + int FindFormatForArchiveName(const UString &arcPath) const; + int FindFormatForExtension(const UString &ext) const; + int FindFormatForArchiveType(const UString &arcType) const; + bool FindFormatForArchiveType(const UString &arcType, CIntVector &formatIndices) const; + #endif + + #ifdef Z7_EXTERNAL_CODECS + + int GetCodec_LibIndex(UInt32 index) const; + bool GetCodec_DecoderIsAssigned(UInt32 index) const; + bool GetCodec_EncoderIsAssigned(UInt32 index) const; + bool GetCodec_IsFilter(UInt32 index, bool &isAssigned) const; + UInt32 GetCodec_NumStreams(UInt32 index); + HRESULT GetCodec_Id(UInt32 index, UInt64 &id); + AString GetCodec_Name(UInt32 index); + + int GetHasherLibIndex(UInt32 index); + UInt64 GetHasherId(UInt32 index); + AString GetHasherName(UInt32 index); + UInt32 GetHasherDigestSize(UInt32 index); + + void GetCodecsErrorMessage(UString &s); + + #endif + + HRESULT CreateInArchive(unsigned formatIndex, CMyComPtr &archive) const + { + const CArcInfoEx &ai = Formats[formatIndex]; + #ifdef Z7_EXTERNAL_CODECS + if (ai.LibIndex < 0) + #endif + { + COM_TRY_BEGIN + archive = ai.CreateInArchive(); + return S_OK; + COM_TRY_END + } + #ifdef Z7_EXTERNAL_CODECS + return CreateArchiveHandler(ai, false, (void **)&archive); + #endif + } + + #ifndef Z7_SFX + + HRESULT CreateOutArchive(unsigned formatIndex, CMyComPtr &archive) const + { + const CArcInfoEx &ai = Formats[formatIndex]; + #ifdef Z7_EXTERNAL_CODECS + if (ai.LibIndex < 0) + #endif + { + COM_TRY_BEGIN + archive = ai.CreateOutArchive(); + return S_OK; + COM_TRY_END + } + + #ifdef Z7_EXTERNAL_CODECS + return CreateArchiveHandler(ai, true, (void **)&archive); + #endif + } + + int FindOutFormatFromName(const UString &name) const + { + FOR_VECTOR (i, Formats) + { + const CArcInfoEx &arc = Formats[i]; + if (!arc.UpdateEnabled) + continue; + if (arc.Name.IsEqualTo_NoCase(name)) + return (int)i; + } + return -1; + } + + void Get_CodecsInfoUser_Vector(CObjectVector &v); + + #endif // Z7_SFX +}; + +#ifdef Z7_EXTERNAL_CODECS + #define CREATE_CODECS_OBJECT \ + CCodecs *codecs = new CCodecs; \ + CExternalCodecs _externalCodecs; \ + _externalCodecs.GetCodecs = codecs; \ + _externalCodecs.GetHashers = codecs; \ + CCodecs::CReleaser codecsReleaser; \ + codecsReleaser.Set(codecs); +#else + #define CREATE_CODECS_OBJECT \ + CCodecs *codecs = new CCodecs; \ + CMyComPtr _codecsRef = codecs; +#endif + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/OpenArchive.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Common/OpenArchive.cpp new file mode 100644 index 00000000..c26d4c05 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/OpenArchive.cpp @@ -0,0 +1,3702 @@ +// OpenArchive.cpp + +#include "StdAfx.h" + +// #define SHOW_DEBUG_INFO + +#ifdef SHOW_DEBUG_INFO +#include +#endif + +#include "../../../../C/CpuArch.h" + +#include "../../../Common/ComTry.h" +#include "../../../Common/IntToString.h" +#include "../../../Common/StringConvert.h" +#include "../../../Common/StringToInt.h" +#include "../../../Common/UTFConvert.h" +#include "../../../Common/Wildcard.h" + +#include "../../../Windows/FileDir.h" + +#include "../../Common/FileStreams.h" +#include "../../Common/LimitedStreams.h" +#include "../../Common/ProgressUtils.h" +#include "../../Common/StreamUtils.h" + +#include "../../Compress/CopyCoder.h" + +#include "DefaultName.h" +#include "OpenArchive.h" + +#ifndef Z7_SFX +#include "SetProperties.h" +#endif + +#ifndef Z7_SFX +#ifdef SHOW_DEBUG_INFO +#define PRF(x) x +#else +#define PRF(x) +#endif +#endif + +// increase it, if you need to support larger SFX stubs +static const UInt64 kMaxCheckStartPosition = 1 << 23; + +/* +Open: + - formatIndex >= 0 (exact Format) + 1) Open with main type. Archive handler is allowed to use archive start finder. + Warning, if there is tail. + + - formatIndex = -1 (Parser:0) (default) + - same as #1 but doesn't return Parser + + - formatIndex = -2 (#1) + - file has supported extension (like a.7z) + Open with that main type (only starting from start of file). + - open OK: + - if there is no tail - return OK + - if there is tail: + - archive is not "Self Exe" - return OK with Warning, that there is tail + - archive is "Self Exe" + ignore "Self Exe" stub, and tries to open tail + - tail can be open as archive - shows that archive and stub size property. + - tail can't be open as archive - shows Parser ??? + - open FAIL: + Try to open with all other types from offset 0 only. + If some open type is OK and physical archive size is uequal or larger + than file size, then return that archive with warning that cannot be open as [extension type]. + If extension was EXE, it will try to open as unknown_extension case + - file has unknown extension (like a.hhh) + It tries to open via parser code. + - if there is full archive or tail archive and unknown block or "Self Exe" + at front, it shows tail archive and stub size property. + - in another cases, if there is some archive inside file, it returns parser/ + - in another cases, it retuens S_FALSE + + + - formatIndex = -3 (#2) + - same as #1, but + - stub (EXE) + archive is open in Parser + + - formatIndex = -4 (#3) + - returns only Parser. skip full file archive. And show other sub-archives + + - formatIndex = -5 (#4) + - returns only Parser. skip full file archive. And show other sub-archives for each byte pos + +*/ + + + + +using namespace NWindows; + +/* +#ifdef Z7_SFX +#define OPEN_PROPS_PARAM +#else +#define OPEN_PROPS_PARAM , props +#endif +*/ + +/* +CArc::~CArc() +{ + GetRawProps.Release(); + Archive.Release(); + printf("\nCArc::~CArc()\n"); +} +*/ + +#ifndef Z7_SFX + +namespace NArchive { +namespace NParser { + +struct CParseItem +{ + UInt64 Offset; + UInt64 Size; + // UInt64 OkSize; + UString Name; + UString Extension; + FILETIME FileTime; + UString Comment; + UString ArcType; + + bool FileTime_Defined; + bool UnpackSize_Defined; + bool NumSubDirs_Defined; + bool NumSubFiles_Defined; + + bool IsSelfExe; + bool IsNotArcType; + + UInt64 UnpackSize; + UInt64 NumSubDirs; + UInt64 NumSubFiles; + + int FormatIndex; + + bool LenIsUnknown; + + CParseItem(): + // OkSize(0), + FileTime_Defined(false), + UnpackSize_Defined(false), + NumSubDirs_Defined(false), + NumSubFiles_Defined(false), + IsSelfExe(false), + IsNotArcType(false), + LenIsUnknown(false) + {} + + /* + bool IsEqualTo(const CParseItem &item) const + { + return Offset == item.Offset && Size == item.Size; + } + */ + + void NormalizeOffset() + { + if ((Int64)Offset < 0) + { + Size += Offset; + // OkSize += Offset; + Offset = 0; + } + } +}; + +Z7_CLASS_IMP_CHandler_IInArchive_1( + IInArchiveGetStream +) +public: + CObjectVector _items; + UInt64 _maxEndOffset; + CMyComPtr _stream; + + UInt64 GetLastEnd() const + { + if (_items.IsEmpty()) + return 0; + const CParseItem &back = _items.Back(); + return back.Offset + back.Size; + } + + void AddUnknownItem(UInt64 next); + int FindInsertPos(const CParseItem &item) const; + void AddItem(const CParseItem &item); + + CHandler(): _maxEndOffset(0) {} +}; + +int CHandler::FindInsertPos(const CParseItem &item) const +{ + unsigned left = 0, right = _items.Size(); + while (left != right) + { + const unsigned mid = (unsigned)(((size_t)left + (size_t)right) / 2); + const CParseItem &midItem = _items[mid]; + if (item.Offset < midItem.Offset) + right = mid; + else if (item.Offset > midItem.Offset) + left = mid + 1; + else if (item.Size < midItem.Size) + right = mid; + /* + else if (item.Size > midItem.Size) + left = mid + 1; + */ + else + { + left = mid + 1; + // return -1; + } + } + return (int)left; +} + +void CHandler::AddUnknownItem(UInt64 next) +{ + /* + UInt64 prevEnd = 0; + if (!_items.IsEmpty()) + { + const CParseItem &back = _items.Back(); + prevEnd = back.Offset + back.Size; + } + */ + if (_maxEndOffset < next) + { + CParseItem item2; + item2.Offset = _maxEndOffset; + item2.Size = next - _maxEndOffset; + _maxEndOffset = next; + _items.Add(item2); + } + else if (_maxEndOffset > next && !_items.IsEmpty()) + { + CParseItem &back = _items.Back(); + if (back.LenIsUnknown) + { + back.Size = next - back.Offset; + _maxEndOffset = next; + } + } +} + +void CHandler::AddItem(const CParseItem &item) +{ + AddUnknownItem(item.Offset); + const int pos = FindInsertPos(item); + if (pos != -1) + { + _items.Insert((unsigned)pos, item); + UInt64 next = item.Offset + item.Size; + if (_maxEndOffset < next) + _maxEndOffset = next; + } +} + +/* +static const CStatProp kProps[] = +{ + { NULL, kpidPath, VT_BSTR}, + { NULL, kpidSize, VT_UI8}, + { NULL, kpidMTime, VT_FILETIME}, + { NULL, kpidType, VT_BSTR}, + { NULL, kpidComment, VT_BSTR}, + { NULL, kpidOffset, VT_UI8}, + { NULL, kpidUnpackSize, VT_UI8}, +// { NULL, kpidNumSubDirs, VT_UI8}, +}; +*/ + +static const Byte kProps[] = +{ + kpidPath, + kpidSize, + kpidMTime, + kpidType, + kpidComment, + kpidOffset, + kpidUnpackSize +}; + +IMP_IInArchive_Props +IMP_IInArchive_ArcProps_NO + +Z7_COM7F_IMF(CHandler::Open(IInStream *stream, const UInt64 *, IArchiveOpenCallback * /* openArchiveCallback */)) +{ + COM_TRY_BEGIN + { + Close(); + _stream = stream; + } + return S_OK; + COM_TRY_END +} + +Z7_COM7F_IMF(CHandler::Close()) +{ + _items.Clear(); + _stream.Release(); + return S_OK; +} + +Z7_COM7F_IMF(CHandler::GetNumberOfItems(UInt32 *numItems)) +{ + *numItems = _items.Size(); + return S_OK; +} + +Z7_COM7F_IMF(CHandler::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) +{ + COM_TRY_BEGIN + NCOM::CPropVariant prop; + + const CParseItem &item = _items[index]; + + switch (propID) + { + case kpidPath: + { + char sz[32]; + ConvertUInt32ToString(index + 1, sz); + UString s(sz); + if (!item.Name.IsEmpty()) + { + s.Add_Dot(); + s += item.Name; + } + if (!item.Extension.IsEmpty()) + { + s.Add_Dot(); + s += item.Extension; + } + prop = s; break; + } + case kpidSize: + case kpidPackSize: prop = item.Size; break; + case kpidOffset: prop = item.Offset; break; + case kpidUnpackSize: if (item.UnpackSize_Defined) prop = item.UnpackSize; break; + case kpidNumSubFiles: if (item.NumSubFiles_Defined) prop = item.NumSubFiles; break; + case kpidNumSubDirs: if (item.NumSubDirs_Defined) prop = item.NumSubDirs; break; + case kpidMTime: if (item.FileTime_Defined) prop = item.FileTime; break; + case kpidComment: if (!item.Comment.IsEmpty()) prop = item.Comment; break; + case kpidType: if (!item.ArcType.IsEmpty()) prop = item.ArcType; break; + default: break; + } + prop.Detach(value); + return S_OK; + COM_TRY_END +} + +Z7_COM7F_IMF(CHandler::Extract(const UInt32 *indices, UInt32 numItems, + Int32 testMode, IArchiveExtractCallback *extractCallback)) +{ + COM_TRY_BEGIN + + const bool allFilesMode = (numItems == (UInt32)(Int32)-1); + if (allFilesMode) + numItems = _items.Size(); + if (_stream && numItems == 0) + return S_OK; + UInt64 totalSize = 0; + UInt32 i; + for (i = 0; i < numItems; i++) + totalSize += _items[allFilesMode ? i : indices[i]].Size; + extractCallback->SetTotal(totalSize); + + totalSize = 0; + + CLocalProgress *lps = new CLocalProgress; + CMyComPtr progress = lps; + lps->Init(extractCallback, false); + + CLimitedSequentialInStream *streamSpec = new CLimitedSequentialInStream; + CMyComPtr inStream(streamSpec); + streamSpec->SetStream(_stream); + + CLimitedSequentialOutStream *outStreamSpec = new CLimitedSequentialOutStream; + CMyComPtr outStream(outStreamSpec); + + NCompress::CCopyCoder *copyCoderSpec = new NCompress::CCopyCoder(); + CMyComPtr copyCoder = copyCoderSpec; + + for (i = 0; i < numItems; i++) + { + lps->InSize = totalSize; + lps->OutSize = totalSize; + RINOK(lps->SetCur()) + CMyComPtr realOutStream; + const Int32 askMode = testMode ? + NExtract::NAskMode::kTest : + NExtract::NAskMode::kExtract; + const UInt32 index = allFilesMode ? i : indices[i]; + const CParseItem &item = _items[index]; + + RINOK(extractCallback->GetStream(index, &realOutStream, askMode)) + UInt64 unpackSize = item.Size; + totalSize += unpackSize; + bool skipMode = false; + if (!testMode && !realOutStream) + continue; + RINOK(extractCallback->PrepareOperation(askMode)) + + outStreamSpec->SetStream(realOutStream); + realOutStream.Release(); + outStreamSpec->Init(skipMode ? 0 : unpackSize, true); + + Int32 opRes = NExtract::NOperationResult::kOK; + RINOK(InStream_SeekSet(_stream, item.Offset)) + streamSpec->Init(unpackSize); + RINOK(copyCoder->Code(inStream, outStream, NULL, NULL, progress)) + + if (outStreamSpec->GetRem() != 0) + opRes = NExtract::NOperationResult::kDataError; + outStreamSpec->ReleaseStream(); + RINOK(extractCallback->SetOperationResult(opRes)) + } + + return S_OK; + + COM_TRY_END +} + + +Z7_COM7F_IMF(CHandler::GetStream(UInt32 index, ISequentialInStream **stream)) +{ + COM_TRY_BEGIN + const CParseItem &item = _items[index]; + return CreateLimitedInStream(_stream, item.Offset, item.Size, stream); + COM_TRY_END +} + +}} + +#endif + +HRESULT Archive_GetItemBoolProp(IInArchive *arc, UInt32 index, PROPID propID, bool &result) throw() +{ + NCOM::CPropVariant prop; + result = false; + RINOK(arc->GetProperty(index, propID, &prop)) + if (prop.vt == VT_BOOL) + result = VARIANT_BOOLToBool(prop.boolVal); + else if (prop.vt != VT_EMPTY) + return E_FAIL; + return S_OK; +} + +HRESULT Archive_IsItem_Dir(IInArchive *arc, UInt32 index, bool &result) throw() +{ + return Archive_GetItemBoolProp(arc, index, kpidIsDir, result); +} + +HRESULT Archive_IsItem_Aux(IInArchive *arc, UInt32 index, bool &result) throw() +{ + return Archive_GetItemBoolProp(arc, index, kpidIsAux, result); +} + +HRESULT Archive_IsItem_AltStream(IInArchive *arc, UInt32 index, bool &result) throw() +{ + return Archive_GetItemBoolProp(arc, index, kpidIsAltStream, result); +} + +HRESULT Archive_IsItem_Deleted(IInArchive *arc, UInt32 index, bool &result) throw() +{ + return Archive_GetItemBoolProp(arc, index, kpidIsDeleted, result); +} + +static HRESULT Archive_GetArcProp_Bool(IInArchive *arc, PROPID propid, bool &result) throw() +{ + NCOM::CPropVariant prop; + result = false; + RINOK(arc->GetArchiveProperty(propid, &prop)) + if (prop.vt == VT_BOOL) + result = VARIANT_BOOLToBool(prop.boolVal); + else if (prop.vt != VT_EMPTY) + return E_FAIL; + return S_OK; +} + +static HRESULT Archive_GetArcProp_UInt(IInArchive *arc, PROPID propid, UInt64 &result, bool &defined) +{ + defined = false; + NCOM::CPropVariant prop; + RINOK(arc->GetArchiveProperty(propid, &prop)) + switch (prop.vt) + { + case VT_UI4: result = prop.ulVal; break; + case VT_I4: result = (UInt64)(Int64)prop.lVal; break; + case VT_UI8: result = (UInt64)prop.uhVal.QuadPart; break; + case VT_I8: result = (UInt64)prop.hVal.QuadPart; break; + case VT_EMPTY: return S_OK; + default: return E_FAIL; + } + defined = true; + return S_OK; +} + +static HRESULT Archive_GetArcProp_Int(IInArchive *arc, PROPID propid, Int64 &result, bool &defined) +{ + defined = false; + NCOM::CPropVariant prop; + RINOK(arc->GetArchiveProperty(propid, &prop)) + switch (prop.vt) + { + case VT_UI4: result = prop.ulVal; break; + case VT_I4: result = prop.lVal; break; + case VT_UI8: result = (Int64)prop.uhVal.QuadPart; break; + case VT_I8: result = (Int64)prop.hVal.QuadPart; break; + case VT_EMPTY: return S_OK; + default: return E_FAIL; + } + defined = true; + return S_OK; +} + +#ifndef Z7_SFX + +HRESULT CArc::GetItem_PathToParent(UInt32 index, UInt32 parent, UStringVector &parts) const +{ + if (!GetRawProps) + return E_FAIL; + if (index == parent) + return S_OK; + UInt32 curIndex = index; + + UString s; + + bool prevWasAltStream = false; + + for (;;) + { + #ifdef MY_CPU_LE + const void *p; + UInt32 size; + UInt32 propType; + RINOK(GetRawProps->GetRawProp(curIndex, kpidName, &p, &size, &propType)) + if (p && propType == PROP_DATA_TYPE_wchar_t_PTR_Z_LE) + s = (const wchar_t *)p; + else + #endif + { + NCOM::CPropVariant prop; + RINOK(Archive->GetProperty(curIndex, kpidName, &prop)) + if (prop.vt == VT_BSTR && prop.bstrVal) + s.SetFromBstr(prop.bstrVal); + else if (prop.vt == VT_EMPTY) + s.Empty(); + else + return E_FAIL; + } + + UInt32 curParent = (UInt32)(Int32)-1; + UInt32 parentType = 0; + RINOK(GetRawProps->GetParent(curIndex, &curParent, &parentType)) + + // 18.06: fixed : we don't want to split name to parts + /* + if (parentType != NParentType::kAltStream) + { + for (;;) + { + int pos = s.ReverseFind_PathSepar(); + if (pos < 0) + { + break; + } + parts.Insert(0, s.Ptr(pos + 1)); + s.DeleteFrom(pos); + } + } + */ + + parts.Insert(0, s); + + if (prevWasAltStream) + { + { + UString &s2 = parts[parts.Size() - 2]; + s2.Add_Colon(); + s2 += parts.Back(); + } + parts.DeleteBack(); + } + + if (parent == curParent) + return S_OK; + + prevWasAltStream = false; + if (parentType == NParentType::kAltStream) + prevWasAltStream = true; + + if (curParent == (UInt32)(Int32)-1) + return E_FAIL; + curIndex = curParent; + } +} + +#endif + + + +HRESULT CArc::GetItem_Path(UInt32 index, UString &result) const +{ + #ifdef MY_CPU_LE + if (GetRawProps) + { + const void *p; + UInt32 size; + UInt32 propType; + if (!IsTree) + { + if (GetRawProps->GetRawProp(index, kpidPath, &p, &size, &propType) == S_OK && + propType == NPropDataType::kUtf16z) + { + unsigned len = size / 2 - 1; + // (len) doesn't include null terminator + + /* + #if WCHAR_MAX > 0xffff + len = (unsigned)Utf16LE__Get_Num_WCHARs(p, len); + + wchar_t *s = result.GetBuf(len); + wchar_t *sEnd = Utf16LE__To_WCHARs_Sep(p, len, s); + if (s + len != sEnd) return E_FAIL; + *sEnd = 0; + + #else + */ + + wchar_t *s = result.GetBuf(len); + for (unsigned i = 0; i < len; i++) + { + wchar_t c = GetUi16(p); + p = (const void *)((const Byte *)p + 2); + + #if WCHAR_PATH_SEPARATOR != L'/' + if (c == L'/') + c = WCHAR_PATH_SEPARATOR; + else if (c == L'\\') + c = WCHAR_IN_FILE_NAME_BACKSLASH_REPLACEMENT; // WSL scheme + #endif + + *s++ = c; + } + *s = 0; + + // #endif + + result.ReleaseBuf_SetLen(len); + + Convert_UnicodeEsc16_To_UnicodeEscHigh(result); + if (len != 0) + return S_OK; + } + } + /* + else if (GetRawProps->GetRawProp(index, kpidName, &p, &size, &propType) == S_OK && + p && propType == NPropDataType::kUtf16z) + { + size -= 2; + UInt32 totalSize = size; + bool isOK = false; + + { + UInt32 index2 = index; + for (;;) + { + UInt32 parent = (UInt32)(Int32)-1; + UInt32 parentType = 0; + if (GetRawProps->GetParent(index2, &parent, &parentType) != S_OK) + break; + if (parent == (UInt32)(Int32)-1) + { + if (parentType != 0) + totalSize += 2; + isOK = true; + break; + } + index2 = parent; + UInt32 size2; + const void *p2; + if (GetRawProps->GetRawProp(index2, kpidName, &p2, &size2, &propType) != S_OK && + p2 && propType == NPropDataType::kUtf16z) + break; + totalSize += size2; + } + } + + if (isOK) + { + wchar_t *sz = result.GetBuf_SetEnd(totalSize / 2); + UInt32 pos = totalSize - size; + memcpy((Byte *)sz + pos, p, size); + UInt32 index2 = index; + for (;;) + { + UInt32 parent = (UInt32)(Int32)-1; + UInt32 parentType = 0; + if (GetRawProps->GetParent(index2, &parent, &parentType) != S_OK) + break; + if (parent == (UInt32)(Int32)-1) + { + if (parentType != 0) + sz[pos / 2 - 1] = L':'; + break; + } + index2 = parent; + UInt32 size2; + const void *p2; + if (GetRawProps->GetRawProp(index2, kpidName, &p2, &size2, &propType) != S_OK) + break; + pos -= size2; + memcpy((Byte *)sz + pos, p2, size2); + sz[(pos + size2 - 2) / 2] = (parentType == 0) ? WCHAR_PATH_SEPARATOR : L':'; + } + #ifdef _WIN32 + // result.Replace(L'/', WCHAR_PATH_SEPARATOR); + #endif + return S_OK; + } + } + */ + } + #endif + + { + NCOM::CPropVariant prop; + RINOK(Archive->GetProperty(index, kpidPath, &prop)) + if (prop.vt == VT_BSTR && prop.bstrVal) + result.SetFromBstr(prop.bstrVal); + else if (prop.vt == VT_EMPTY) + result.Empty(); + else + return E_FAIL; + } + + if (result.IsEmpty()) + return GetItem_DefaultPath(index, result); + + Convert_UnicodeEsc16_To_UnicodeEscHigh(result); + return S_OK; +} + +HRESULT CArc::GetItem_DefaultPath(UInt32 index, UString &result) const +{ + result.Empty(); + bool isDir; + RINOK(Archive_IsItem_Dir(Archive, index, isDir)) + if (!isDir) + { + result = DefaultName; + NCOM::CPropVariant prop; + RINOK(Archive->GetProperty(index, kpidExtension, &prop)) + if (prop.vt == VT_BSTR) + { + result.Add_Dot(); + result += prop.bstrVal; + } + else if (prop.vt != VT_EMPTY) + return E_FAIL; + } + return S_OK; +} + +HRESULT CArc::GetItem_Path2(UInt32 index, UString &result) const +{ + RINOK(GetItem_Path(index, result)) + if (Ask_Deleted) + { + bool isDeleted = false; + RINOK(Archive_IsItem_Deleted(Archive, index, isDeleted)) + if (isDeleted) + result.Insert(0, L"[DELETED]" WSTRING_PATH_SEPARATOR); + } + return S_OK; +} + +#ifdef SUPPORT_ALT_STREAMS + +int FindAltStreamColon_in_Path(const wchar_t *path) +{ + unsigned i = 0; + int colonPos = -1; + for (;; i++) + { + wchar_t c = path[i]; + if (c == 0) + return colonPos; + if (c == ':') + { + if (colonPos < 0) + colonPos = (int)i; + continue; + } + if (c == WCHAR_PATH_SEPARATOR) + colonPos = -1; + } +} + +#endif + +HRESULT CArc::GetItem(UInt32 index, CReadArcItem &item) const +{ + #ifdef SUPPORT_ALT_STREAMS + item.IsAltStream = false; + item.AltStreamName.Empty(); + item.MainPath.Empty(); + #endif + + item.IsDir = false; + item.Path.Empty(); + item.ParentIndex = (UInt32)(Int32)-1; + + item.PathParts.Clear(); + + RINOK(Archive_IsItem_Dir(Archive, index, item.IsDir)) + item.MainIsDir = item.IsDir; + + RINOK(GetItem_Path2(index, item.Path)) + + #ifndef Z7_SFX + UInt32 mainIndex = index; + #endif + + #ifdef SUPPORT_ALT_STREAMS + + item.MainPath = item.Path; + if (Ask_AltStream) + { + RINOK(Archive_IsItem_AltStream(Archive, index, item.IsAltStream)) + } + + bool needFindAltStream = false; + + if (item.IsAltStream) + { + needFindAltStream = true; + if (GetRawProps) + { + UInt32 parentType = 0; + UInt32 parentIndex; + RINOK(GetRawProps->GetParent(index, &parentIndex, &parentType)) + if (parentType == NParentType::kAltStream) + { + NCOM::CPropVariant prop; + RINOK(Archive->GetProperty(index, kpidName, &prop)) + if (prop.vt == VT_BSTR && prop.bstrVal) + item.AltStreamName.SetFromBstr(prop.bstrVal); + else if (prop.vt != VT_EMPTY) + return E_FAIL; + else + { + // item.IsAltStream = false; + } + /* + if (item.AltStreamName.IsEmpty()) + item.IsAltStream = false; + */ + + needFindAltStream = false; + item.ParentIndex = parentIndex; + mainIndex = parentIndex; + + if (parentIndex == (UInt32)(Int32)-1) + { + item.MainPath.Empty(); + item.MainIsDir = true; + } + else + { + RINOK(GetItem_Path2(parentIndex, item.MainPath)) + RINOK(Archive_IsItem_Dir(Archive, parentIndex, item.MainIsDir)) + } + } + } + } + + if (item.WriteToAltStreamIfColon || needFindAltStream) + { + /* Good handler must support GetRawProps::GetParent for alt streams. + So the following code currently is not used */ + int colon = FindAltStreamColon_in_Path(item.Path); + if (colon >= 0) + { + item.MainPath.DeleteFrom((unsigned)colon); + item.AltStreamName = item.Path.Ptr((unsigned)(colon + 1)); + item.MainIsDir = (colon == 0 || IsPathSepar(item.Path[(unsigned)colon - 1])); + item.IsAltStream = true; + } + } + + #endif + + #ifndef Z7_SFX + if (item._use_baseParentFolder_mode) + { + RINOK(GetItem_PathToParent(mainIndex, (unsigned)item._baseParentFolder, item.PathParts)) + + #ifdef SUPPORT_ALT_STREAMS + if ((item.WriteToAltStreamIfColon || needFindAltStream) && !item.PathParts.IsEmpty()) + { + int colon; + { + UString &s = item.PathParts.Back(); + colon = FindAltStreamColon_in_Path(s); + if (colon >= 0) + { + item.AltStreamName = s.Ptr((unsigned)(colon + 1)); + item.MainIsDir = (colon == 0 || IsPathSepar(s[(unsigned)colon - 1])); + item.IsAltStream = true; + s.DeleteFrom((unsigned)colon); + } + } + if (colon == 0) + item.PathParts.DeleteBack(); + } + #endif + + } + else + #endif + SplitPathToParts( + #ifdef SUPPORT_ALT_STREAMS + item.MainPath + #else + item.Path + #endif + , item.PathParts); + + return S_OK; +} + +#ifndef Z7_SFX + +static HRESULT Archive_GetItem_Size(IInArchive *archive, UInt32 index, UInt64 &size, bool &defined) +{ + NCOM::CPropVariant prop; + defined = false; + size = 0; + RINOK(archive->GetProperty(index, kpidSize, &prop)) + switch (prop.vt) + { + case VT_UI1: size = prop.bVal; break; + case VT_UI2: size = prop.uiVal; break; + case VT_UI4: size = prop.ulVal; break; + case VT_UI8: size = (UInt64)prop.uhVal.QuadPart; break; + case VT_EMPTY: return S_OK; + default: return E_FAIL; + } + defined = true; + return S_OK; +} + +#endif + +HRESULT CArc::GetItem_Size(UInt32 index, UInt64 &size, bool &defined) const +{ + NCOM::CPropVariant prop; + defined = false; + size = 0; + RINOK(Archive->GetProperty(index, kpidSize, &prop)) + switch (prop.vt) + { + case VT_UI1: size = prop.bVal; break; + case VT_UI2: size = prop.uiVal; break; + case VT_UI4: size = prop.ulVal; break; + case VT_UI8: size = (UInt64)prop.uhVal.QuadPart; break; + case VT_EMPTY: return S_OK; + default: return E_FAIL; + } + defined = true; + return S_OK; +} + +HRESULT CArc::GetItem_MTime(UInt32 index, CArcTime &at) const +{ + at.Clear(); + NCOM::CPropVariant prop; + RINOK(Archive->GetProperty(index, kpidMTime, &prop)) + + if (prop.vt == VT_FILETIME) + { + /* + // for debug + if (FILETIME_IsZero(prop.at) && MTime.Def) + { + at = MTime; + return S_OK; + } + */ + at.Set_From_Prop(prop); + if (at.Prec == 0) + { + // (at.Prec == 0) before version 22. + // so kpidTimeType is required for that code + prop.Clear(); + RINOK(Archive->GetProperty(index, kpidTimeType, &prop)) + if (prop.vt == VT_UI4) + { + UInt32 val = prop.ulVal; + if (val == NFileTimeType::kWindows) + val = k_PropVar_TimePrec_100ns; + /* + else if (val > k_PropVar_TimePrec_1ns) + { + val = k_PropVar_TimePrec_100ns; + // val = k_PropVar_TimePrec_1ns; + // return E_FAIL; // for debug + } + */ + at.Prec = (UInt16)val; + } + } + return S_OK; + } + + if (prop.vt != VT_EMPTY) + return E_FAIL; + if (MTime.Def) + at = MTime; + return S_OK; +} + +#ifndef Z7_SFX + +static inline bool TestSignature(const Byte *p1, const Byte *p2, size_t size) +{ + for (size_t i = 0; i < size; i++) + if (p1[i] != p2[i]) + return false; + return true; +} + + +static void MakeCheckOrder(CCodecs *codecs, + CIntVector &orderIndices, unsigned numTypes, CIntVector &orderIndices2, + const Byte *data, size_t dataSize) +{ + for (unsigned i = 0; i < numTypes; i++) + { + const int index = orderIndices[i]; + if (index < 0) + continue; + const CArcInfoEx &ai = codecs->Formats[(unsigned)index]; + if (ai.SignatureOffset == 0) + { + if (ai.Signatures.IsEmpty()) + { + if (dataSize != 0) // 21.04: no Signature means Empty Signature + continue; + } + else + { + unsigned k; + const CObjectVector &sigs = ai.Signatures; + for (k = 0; k < sigs.Size(); k++) + { + const CByteBuffer &sig = sigs[k]; + if (sig.Size() <= dataSize && TestSignature(data, sig, sig.Size())) + break; + } + if (k == sigs.Size()) + continue; + } + } + orderIndices2.Add(index); + orderIndices[i] = -1; + } +} + +#ifdef UNDER_CE + static const unsigned kNumHashBytes = 1; + #define HASH_VAL(buf) ((buf)[0]) +#else + static const unsigned kNumHashBytes = 2; + // #define HASH_VAL(buf) ((buf)[0] | ((UInt32)(buf)[1] << 8)) + #define HASH_VAL(buf) GetUi16(buf) +#endif + +static bool IsExeExt(const UString &ext) +{ + return ext.IsEqualTo_Ascii_NoCase("exe"); +} + +static const char * const k_PreArcFormats[] = +{ + "pe" + , "elf" + , "macho" + , "mub" + , "te" +}; + +static bool IsNameFromList(const UString &s, const char * const names[], size_t num) +{ + for (unsigned i = 0; i < num; i++) + if (StringsAreEqualNoCase_Ascii(s, names[i])) + return true; + return false; +} + + +static bool IsPreArcFormat(const CArcInfoEx &ai) +{ + if (ai.Flags_PreArc()) + return true; + return IsNameFromList(ai.Name, k_PreArcFormats, Z7_ARRAY_SIZE(k_PreArcFormats)); +} + +static const char * const k_Formats_with_simple_signuature[] = +{ + "7z" + , "xz" + , "rar" + , "bzip2" + , "gzip" + , "cab" + , "wim" + , "rpm" + , "vhd" + , "xar" +}; + +static bool IsNewStyleSignature(const CArcInfoEx &ai) +{ + // if (ai.Version >= 0x91F) + if (ai.NewInterface) + return true; + return IsNameFromList(ai.Name, k_Formats_with_simple_signuature, Z7_ARRAY_SIZE(k_Formats_with_simple_signuature)); +} + + + +class CArchiveOpenCallback_Offset Z7_final: + public IArchiveOpenCallback, + public IArchiveOpenVolumeCallback, + #ifndef Z7_NO_CRYPTO + public ICryptoGetTextPassword, + #endif + public CMyUnknownImp +{ + Z7_COM_QI_BEGIN2(IArchiveOpenCallback) + Z7_COM_QI_ENTRY(IArchiveOpenVolumeCallback) + #ifndef Z7_NO_CRYPTO + Z7_COM_QI_ENTRY(ICryptoGetTextPassword) + #endif + Z7_COM_QI_END + Z7_COM_ADDREF_RELEASE + + Z7_IFACE_COM7_IMP(IArchiveOpenCallback) + Z7_IFACE_COM7_IMP(IArchiveOpenVolumeCallback) + #ifndef Z7_NO_CRYPTO + Z7_IFACE_COM7_IMP(ICryptoGetTextPassword) + #endif + +public: + CMyComPtr Callback; + CMyComPtr OpenVolumeCallback; + UInt64 Files; + UInt64 Offset; + + #ifndef Z7_NO_CRYPTO + CMyComPtr GetTextPassword; + #endif +}; + +#ifndef Z7_NO_CRYPTO +Z7_COM7F_IMF(CArchiveOpenCallback_Offset::CryptoGetTextPassword(BSTR *password)) +{ + COM_TRY_BEGIN + if (GetTextPassword) + return GetTextPassword->CryptoGetTextPassword(password); + return E_NOTIMPL; + COM_TRY_END +} +#endif + +Z7_COM7F_IMF(CArchiveOpenCallback_Offset::SetTotal(const UInt64 *, const UInt64 *)) +{ + return S_OK; +} + +Z7_COM7F_IMF(CArchiveOpenCallback_Offset::SetCompleted(const UInt64 *, const UInt64 *bytes)) +{ + if (!Callback) + return S_OK; + UInt64 value = Offset; + if (bytes) + value += *bytes; + return Callback->SetCompleted(&Files, &value); +} + +Z7_COM7F_IMF(CArchiveOpenCallback_Offset::GetProperty(PROPID propID, PROPVARIANT *value)) +{ + if (OpenVolumeCallback) + return OpenVolumeCallback->GetProperty(propID, value); + NCOM::PropVariant_Clear(value); + return S_OK; + // return E_NOTIMPL; +} + +Z7_COM7F_IMF(CArchiveOpenCallback_Offset::GetStream(const wchar_t *name, IInStream **inStream)) +{ + if (OpenVolumeCallback) + return OpenVolumeCallback->GetStream(name, inStream); + return S_FALSE; +} + +#endif + + +UInt32 GetOpenArcErrorFlags(const NCOM::CPropVariant &prop, bool *isDefinedProp) +{ + if (isDefinedProp != NULL) + *isDefinedProp = false; + + switch (prop.vt) + { + case VT_UI8: if (isDefinedProp) *isDefinedProp = true; return (UInt32)prop.uhVal.QuadPart; + case VT_UI4: if (isDefinedProp) *isDefinedProp = true; return prop.ulVal; + case VT_EMPTY: return 0; + default: throw 151199; + } +} + +void CArcErrorInfo::ClearErrors() +{ + // ErrorFormatIndex = -1; // we don't need to clear ErrorFormatIndex here !!! + + ThereIsTail = false; + UnexpecedEnd = false; + IgnoreTail = false; + // NonZerosTail = false; + ErrorFlags_Defined = false; + ErrorFlags = 0; + WarningFlags = 0; + TailSize = 0; + + ErrorMessage.Empty(); + WarningMessage.Empty(); +} + +HRESULT CArc::ReadBasicProps(IInArchive *archive, UInt64 startPos, HRESULT openRes) +{ + // OkPhySize_Defined = false; + PhySize_Defined = false; + PhySize = 0; + Offset = 0; + AvailPhySize = FileSize - startPos; + + ErrorInfo.ClearErrors(); + { + NCOM::CPropVariant prop; + RINOK(archive->GetArchiveProperty(kpidErrorFlags, &prop)) + ErrorInfo.ErrorFlags = GetOpenArcErrorFlags(prop, &ErrorInfo.ErrorFlags_Defined); + } + { + NCOM::CPropVariant prop; + RINOK(archive->GetArchiveProperty(kpidWarningFlags, &prop)) + ErrorInfo.WarningFlags = GetOpenArcErrorFlags(prop); + } + + { + NCOM::CPropVariant prop; + RINOK(archive->GetArchiveProperty(kpidError, &prop)) + if (prop.vt != VT_EMPTY) + ErrorInfo.ErrorMessage = (prop.vt == VT_BSTR ? prop.bstrVal : L"Unknown error"); + } + + { + NCOM::CPropVariant prop; + RINOK(archive->GetArchiveProperty(kpidWarning, &prop)) + if (prop.vt != VT_EMPTY) + ErrorInfo.WarningMessage = (prop.vt == VT_BSTR ? prop.bstrVal : L"Unknown warning"); + } + + if (openRes == S_OK || ErrorInfo.IsArc_After_NonOpen()) + { + RINOK(Archive_GetArcProp_UInt(archive, kpidPhySize, PhySize, PhySize_Defined)) + /* + RINOK(Archive_GetArcProp_UInt(archive, kpidOkPhySize, OkPhySize, OkPhySize_Defined)); + if (!OkPhySize_Defined) + { + OkPhySize_Defined = PhySize_Defined; + OkPhySize = PhySize; + } + */ + + bool offsetDefined; + RINOK(Archive_GetArcProp_Int(archive, kpidOffset, Offset, offsetDefined)) + + Int64 globalOffset = (Int64)startPos + Offset; + AvailPhySize = (UInt64)((Int64)FileSize - globalOffset); + if (PhySize_Defined) + { + UInt64 endPos = (UInt64)(globalOffset + (Int64)PhySize); + if (endPos < FileSize) + { + AvailPhySize = PhySize; + ErrorInfo.ThereIsTail = true; + ErrorInfo.TailSize = FileSize - endPos; + } + else if (endPos > FileSize) + ErrorInfo.UnexpecedEnd = true; + } + } + + return S_OK; +} + +/* +static void PrintNumber(const char *s, int n) +{ + char temp[100]; + sprintf(temp, "%s %d", s, n); + // OutputDebugStringA(temp); + printf(temp); +} +*/ + +HRESULT CArc::PrepareToOpen(const COpenOptions &op, unsigned formatIndex, CMyComPtr &archive) +{ + // OutputDebugStringA("a1"); + // PrintNumber("formatIndex", formatIndex); + + RINOK(op.codecs->CreateInArchive(formatIndex, archive)) + // OutputDebugStringA("a2"); + if (!archive) + return S_OK; + + #ifdef Z7_EXTERNAL_CODECS + if (op.codecs->NeedSetLibCodecs) + { + const CArcInfoEx &ai = op.codecs->Formats[formatIndex]; + if (ai.LibIndex >= 0 ? + !op.codecs->Libs[(unsigned)ai.LibIndex].SetCodecs : + !op.codecs->Libs.IsEmpty()) + { + CMyComPtr setCompressCodecsInfo; + archive.QueryInterface(IID_ISetCompressCodecsInfo, (void **)&setCompressCodecsInfo); + if (setCompressCodecsInfo) + { + RINOK(setCompressCodecsInfo->SetCompressCodecsInfo(op.codecs)) + } + } + } + #endif + + + #ifndef Z7_SFX + + const CArcInfoEx &ai = op.codecs->Formats[formatIndex]; + + // OutputDebugStringW(ai.Name); + // OutputDebugStringA("a3"); + + if (ai.Flags_PreArc()) + { + /* we notify parsers that extract executables, that they don't need + to open archive, if there is tail after executable (for SFX cases) */ + CMyComPtr allowTail; + archive.QueryInterface(IID_IArchiveAllowTail, (void **)&allowTail); + if (allowTail) + allowTail->AllowTail(BoolToInt(true)); + } + + if (op.props) + { + /* + FOR_VECTOR (y, op.props) + { + const COptionalOpenProperties &optProps = (*op.props)[y]; + if (optProps.FormatName.IsEmpty() || optProps.FormatName.CompareNoCase(ai.Name) == 0) + { + RINOK(SetProperties(archive, optProps.Props)); + break; + } + } + */ + RINOK(SetProperties(archive, *op.props)) + } + + #endif + return S_OK; +} + +#ifndef Z7_SFX + +static HRESULT ReadParseItemProps(IInArchive *archive, const CArcInfoEx &ai, NArchive::NParser::CParseItem &pi) +{ + pi.Extension = ai.GetMainExt(); + pi.FileTime_Defined = false; + pi.ArcType = ai.Name; + + RINOK(Archive_GetArcProp_Bool(archive, kpidIsNotArcType, pi.IsNotArcType)) + + // RINOK(Archive_GetArcProp_Bool(archive, kpidIsSelfExe, pi.IsSelfExe)); + pi.IsSelfExe = ai.Flags_PreArc(); + + { + NCOM::CPropVariant prop; + RINOK(archive->GetArchiveProperty(kpidMTime, &prop)) + if (prop.vt == VT_FILETIME) + { + pi.FileTime_Defined = true; + pi.FileTime = prop.filetime; + } + } + + if (!pi.FileTime_Defined) + { + NCOM::CPropVariant prop; + RINOK(archive->GetArchiveProperty(kpidCTime, &prop)) + if (prop.vt == VT_FILETIME) + { + pi.FileTime_Defined = true; + pi.FileTime = prop.filetime; + } + } + + { + NCOM::CPropVariant prop; + RINOK(archive->GetArchiveProperty(kpidName, &prop)) + if (prop.vt == VT_BSTR) + { + pi.Name.SetFromBstr(prop.bstrVal); + pi.Extension.Empty(); + } + else + { + RINOK(archive->GetArchiveProperty(kpidExtension, &prop)) + if (prop.vt == VT_BSTR) + pi.Extension.SetFromBstr(prop.bstrVal); + } + } + + { + NCOM::CPropVariant prop; + RINOK(archive->GetArchiveProperty(kpidShortComment, &prop)) + if (prop.vt == VT_BSTR) + pi.Comment.SetFromBstr(prop.bstrVal); + } + + + UInt32 numItems; + RINOK(archive->GetNumberOfItems(&numItems)) + + // pi.NumSubFiles = numItems; + // RINOK(Archive_GetArcProp_UInt(archive, kpidUnpackSize, pi.UnpackSize, pi.UnpackSize_Defined)); + // if (!pi.UnpackSize_Defined) + { + pi.NumSubFiles = 0; + pi.NumSubDirs = 0; + pi.UnpackSize = 0; + for (UInt32 i = 0; i < numItems; i++) + { + UInt64 size = 0; + bool defined = false; + Archive_GetItem_Size(archive, i, size, defined); + if (defined) + { + pi.UnpackSize_Defined = true; + pi.UnpackSize += size; + } + + bool isDir = false; + Archive_IsItem_Dir(archive, i, isDir); + if (isDir) + pi.NumSubDirs++; + else + pi.NumSubFiles++; + } + if (pi.NumSubDirs != 0) + pi.NumSubDirs_Defined = true; + pi.NumSubFiles_Defined = true; + } + + return S_OK; +} + +#endif + +HRESULT CArc::CheckZerosTail(const COpenOptions &op, UInt64 offset) +{ + if (!op.stream) + return S_OK; + RINOK(InStream_SeekSet(op.stream, offset)) + const UInt32 kBufSize = 1 << 11; + Byte buf[kBufSize]; + + for (;;) + { + UInt32 processed = 0; + RINOK(op.stream->Read(buf, kBufSize, &processed)) + if (processed == 0) + { + // ErrorInfo.NonZerosTail = false; + ErrorInfo.IgnoreTail = true; + return S_OK; + } + for (size_t i = 0; i < processed; i++) + { + if (buf[i] != 0) + { + // ErrorInfo.IgnoreTail = false; + // ErrorInfo.NonZerosTail = true; + return S_OK; + } + } + } +} + + + +#ifndef Z7_SFX + +Z7_CLASS_IMP_COM_2( + CExtractCallback_To_OpenCallback + , IArchiveExtractCallback + , ICompressProgressInfo +) + Z7_IFACE_COM7_IMP(IProgress) +public: + CMyComPtr Callback; + UInt64 Files; + UInt64 Offset; + + void Init(IArchiveOpenCallback *callback) + { + Callback = callback; + Files = 0; + Offset = 0; + } +}; + +Z7_COM7F_IMF(CExtractCallback_To_OpenCallback::SetTotal(UInt64 /* size */)) +{ + return S_OK; +} + +Z7_COM7F_IMF(CExtractCallback_To_OpenCallback::SetCompleted(const UInt64 * /* completeValue */)) +{ + return S_OK; +} + +Z7_COM7F_IMF(CExtractCallback_To_OpenCallback::SetRatioInfo(const UInt64 *inSize, const UInt64 * /* outSize */)) +{ + if (Callback) + { + UInt64 value = Offset; + if (inSize) + value += *inSize; + return Callback->SetCompleted(&Files, &value); + } + return S_OK; +} + +Z7_COM7F_IMF(CExtractCallback_To_OpenCallback::GetStream(UInt32 /* index */, ISequentialOutStream **outStream, Int32 /* askExtractMode */)) +{ + *outStream = NULL; + return S_OK; +} + +Z7_COM7F_IMF(CExtractCallback_To_OpenCallback::PrepareOperation(Int32 /* askExtractMode */)) +{ + return S_OK; +} + +Z7_COM7F_IMF(CExtractCallback_To_OpenCallback::SetOperationResult(Int32 /* operationResult */)) +{ + return S_OK; +} + + +static HRESULT OpenArchiveSpec(IInArchive *archive, bool needPhySize, + IInStream *stream, const UInt64 *maxCheckStartPosition, + IArchiveOpenCallback *openCallback, + IArchiveExtractCallback *extractCallback) +{ + /* + if (needPhySize) + { + Z7_DECL_CMyComPtr_QI_FROM( + IArchiveOpen2, + open2, archive) + if (open2) + return open2->ArcOpen2(stream, kOpenFlags_RealPhySize, openCallback); + } + */ + RINOK(archive->Open(stream, maxCheckStartPosition, openCallback)) + if (needPhySize) + { + bool phySize_Defined = false; + UInt64 phySize = 0; + RINOK(Archive_GetArcProp_UInt(archive, kpidPhySize, phySize, phySize_Defined)) + if (phySize_Defined) + return S_OK; + + bool phySizeCantBeDetected = false; + RINOK(Archive_GetArcProp_Bool(archive, kpidPhySizeCantBeDetected, phySizeCantBeDetected)) + + if (!phySizeCantBeDetected) + { + PRF(printf("\n-- !phySize_Defined after Open, call archive->Extract()")); + // It's for bzip2/gz and some xz archives, where Open operation doesn't know phySize. + // But the Handler will know phySize after full archive testing. + RINOK(archive->Extract(NULL, (UInt32)(Int32)-1, BoolToInt(true), extractCallback)) + PRF(printf("\n-- OK")); + } + } + return S_OK; +} + + + +static int FindFormatForArchiveType(CCodecs *codecs, CIntVector orderIndices, const char *name) +{ + FOR_VECTOR (i, orderIndices) + { + int oi = orderIndices[i]; + if (oi >= 0) + if (StringsAreEqualNoCase_Ascii(codecs->Formats[(unsigned)oi].Name, name)) + return (int)i; + } + return -1; +} + +#endif + +HRESULT CArc::OpenStream2(const COpenOptions &op) +{ + // fprintf(stdout, "\nOpen: %S", Path); fflush(stdout); + + Archive.Release(); + GetRawProps.Release(); + GetRootProps.Release(); + + ErrorInfo.ClearErrors(); + ErrorInfo.ErrorFormatIndex = -1; + + IsParseArc = false; + ArcStreamOffset = 0; + + // OutputDebugStringA("1"); + // OutputDebugStringW(Path); + + const UString fileName = ExtractFileNameFromPath(Path); + UString extension; + { + const int dotPos = fileName.ReverseFind_Dot(); + if (dotPos >= 0) + extension = fileName.Ptr((unsigned)(dotPos + 1)); + } + + CIntVector orderIndices; + + bool searchMarkerInHandler = false; + #ifdef Z7_SFX + searchMarkerInHandler = true; + #endif + + CBoolArr isMainFormatArr(op.codecs->Formats.Size()); + { + FOR_VECTOR(i, op.codecs->Formats) + isMainFormatArr[i] = false; + } + + const UInt64 maxStartOffset = + op.openType.MaxStartOffset_Defined ? + op.openType.MaxStartOffset : + kMaxCheckStartPosition; + + #ifndef Z7_SFX + bool isUnknownExt = false; + #endif + + #ifndef Z7_SFX + bool isForced = false; + #endif + + unsigned numMainTypes = 0; + const int formatIndex = op.openType.FormatIndex; + + if (formatIndex >= 0) + { + #ifndef Z7_SFX + isForced = true; + #endif + orderIndices.Add(formatIndex); + numMainTypes = 1; + isMainFormatArr[(unsigned)formatIndex] = true; + + searchMarkerInHandler = true; + } + else + { + unsigned numFinded = 0; + #ifndef Z7_SFX + bool isPrearcExt = false; + #endif + + { + #ifndef Z7_SFX + + bool isZip = false; + bool isRar = false; + + const wchar_t c = extension[0]; + if (c == 'z' || c == 'Z' || c == 'r' || c == 'R') + { + bool isNumber = false; + for (unsigned k = 1;; k++) + { + const wchar_t d = extension[k]; + if (d == 0) + break; + if (d < '0' || d > '9') + { + isNumber = false; + break; + } + isNumber = true; + } + if (isNumber) + { + if (c == 'z' || c == 'Z') + isZip = true; + else + isRar = true; + } + } + + #endif + + FOR_VECTOR (i, op.codecs->Formats) + { + const CArcInfoEx &ai = op.codecs->Formats[i]; + + if (IgnoreSplit || !op.openType.CanReturnArc) + if (ai.Is_Split()) + continue; + if (op.excludedFormats->FindInSorted((int)i) >= 0) + continue; + + #ifndef Z7_SFX + if (IsPreArcFormat(ai)) + isPrearcExt = true; + #endif + + if (ai.FindExtension(extension) >= 0 + #ifndef Z7_SFX + || (isZip && ai.Is_Zip()) + || (isRar && ai.Is_Rar()) + #endif + ) + { + // PrintNumber("orderIndices.Insert", i); + orderIndices.Insert(numFinded++, (int)i); + isMainFormatArr[i] = true; + } + else + orderIndices.Add((int)i); + } + } + + if (!op.stream) + { + if (numFinded != 1) + return E_NOTIMPL; + orderIndices.DeleteFrom(1); + } + // PrintNumber("numFinded", numFinded ); + + /* + if (op.openOnlySpecifiedByExtension) + { + if (numFinded != 0 && !IsExeExt(extension)) + orderIndices.DeleteFrom(numFinded); + } + */ + + #ifndef Z7_SFX + + if (op.stream && orderIndices.Size() >= 2) + { + RINOK(InStream_SeekToBegin(op.stream)) + CByteBuffer byteBuffer; + CIntVector orderIndices2; + if (numFinded == 0 || IsExeExt(extension)) + { + // signature search was here + } + else if (extension.IsEqualTo("000") || extension.IsEqualTo("001")) + { + const int i = FindFormatForArchiveType(op.codecs, orderIndices, "rar"); + if (i >= 0) + { + const size_t kBufSize = (1 << 10); + byteBuffer.Alloc(kBufSize); + size_t processedSize = kBufSize; + RINOK(ReadStream(op.stream, byteBuffer, &processedSize)) + if (processedSize >= 16) + { + const Byte *buf = byteBuffer; + const Byte kRarHeader[] = { 0x52 , 0x61, 0x72, 0x21, 0x1a, 0x07, 0x00 }; + if (TestSignature(buf, kRarHeader, 7) && buf[9] == 0x73 && (buf[10] & 1) != 0) + { + orderIndices2.Add(orderIndices[(unsigned)i]); + orderIndices[(unsigned)i] = -1; + if (i >= (int)numFinded) + numFinded++; + } + } + } + } + else + { + const size_t kBufSize = (1 << 10); + byteBuffer.Alloc(kBufSize); + size_t processedSize = kBufSize; + RINOK(ReadStream(op.stream, byteBuffer, &processedSize)) + if (processedSize == 0) + return S_FALSE; + + /* + check type order: + 0) matched_extension && Backward + 1) matched_extension && (no_signuature || SignatureOffset != 0) + 2) matched_extension && (matched_signature) + // 3) no signuature + // 4) matched signuature + */ + // we move index from orderIndices to orderIndices2 for priority handlers. + + for (unsigned i = 0; i < numFinded; i++) + { + const int index = orderIndices[i]; + if (index < 0) + continue; + const CArcInfoEx &ai = op.codecs->Formats[(unsigned)index]; + if (ai.Flags_BackwardOpen()) + { + // backward doesn't need start signatures + orderIndices2.Add(index); + orderIndices[i] = -1; + } + } + + MakeCheckOrder(op.codecs, orderIndices, numFinded, orderIndices2, NULL, 0); + MakeCheckOrder(op.codecs, orderIndices, numFinded, orderIndices2, byteBuffer, processedSize); + // MakeCheckOrder(op.codecs, orderIndices, orderIndices.Size(), orderIndices2, NULL, 0); + // MakeCheckOrder(op.codecs, orderIndices, orderIndices.Size(), orderIndices2, byteBuffer, processedSize); + } + + FOR_VECTOR (i, orderIndices) + { + const int val = orderIndices[i]; + if (val != -1) + orderIndices2.Add(val); + } + orderIndices = orderIndices2; + } + + if (orderIndices.Size() >= 2) + { + const int iIso = FindFormatForArchiveType(op.codecs, orderIndices, "iso"); + const int iUdf = FindFormatForArchiveType(op.codecs, orderIndices, "udf"); + if (iUdf > iIso && iIso >= 0) + { + const int isoIndex = orderIndices[(unsigned)iIso]; + const int udfIndex = orderIndices[(unsigned)iUdf]; + orderIndices[(unsigned)iUdf] = isoIndex; + orderIndices[(unsigned)iIso] = udfIndex; + } + } + + numMainTypes = numFinded; + isUnknownExt = (numMainTypes == 0) || isPrearcExt; + + #else // Z7_SFX + + numMainTypes = orderIndices.Size(); + + // we need correct numMainTypes for mutlivolume SFX (if some volume is missing) + if (numFinded != 0) + numMainTypes = numFinded; + + #endif + } + + UInt64 fileSize = 0; + if (op.stream) + { + RINOK(InStream_GetSize_SeekToBegin(op.stream, fileSize)) + } + FileSize = fileSize; + + + #ifndef Z7_SFX + + CBoolArr skipFrontalFormat(op.codecs->Formats.Size()); + { + FOR_VECTOR(i, op.codecs->Formats) + skipFrontalFormat[i] = false; + } + + #endif + + const COpenType &mode = op.openType; + + + + + + if (mode.CanReturnArc) + { + // ---------- OPEN main type by extenssion ---------- + + unsigned numCheckTypes = orderIndices.Size(); + if (formatIndex >= 0) + numCheckTypes = numMainTypes; + + for (unsigned i = 0; i < numCheckTypes; i++) + { + FormatIndex = orderIndices[i]; + + // orderIndices[] item cannot be negative here + + bool exactOnly = false; + + #ifndef Z7_SFX + + const CArcInfoEx &ai = op.codecs->Formats[(unsigned)FormatIndex]; + // OutputDebugStringW(ai.Name); + if (i >= numMainTypes) + { + // here we allow mismatched extension only for backward handlers + if (!ai.Flags_BackwardOpen() + // && !ai.Flags_PureStartOpen() + ) + continue; + exactOnly = true; + } + + #endif + + // Some handlers do not set total bytes. So we set it here + if (op.callback) + RINOK(op.callback->SetTotal(NULL, &fileSize)) + + if (op.stream) + { + RINOK(InStream_SeekToBegin(op.stream)) + } + + CMyComPtr archive; + + RINOK(PrepareToOpen(op, (unsigned)FormatIndex, archive)) + if (!archive) + continue; + + HRESULT result; + if (op.stream) + { + UInt64 searchLimit = (!exactOnly && searchMarkerInHandler) ? maxStartOffset: 0; + result = archive->Open(op.stream, &searchLimit, op.callback); + } + else + { + CMyComPtr openSeq; + archive.QueryInterface(IID_IArchiveOpenSeq, (void **)&openSeq); + if (!openSeq) + return E_NOTIMPL; + result = openSeq->OpenSeq(op.seqStream); + } + + RINOK(ReadBasicProps(archive, 0, result)) + + if (result == S_FALSE) + { + bool isArc = ErrorInfo.IsArc_After_NonOpen(); + + #ifndef Z7_SFX + // if it's archive, we allow another open attempt for parser + if (!mode.CanReturnParser || !isArc) + skipFrontalFormat[(unsigned)FormatIndex] = true; + #endif + + if (exactOnly) + continue; + + if (i == 0 && numMainTypes == 1) + { + // we set NonOpenErrorInfo, only if there is only one main format (defined by extension). + ErrorInfo.ErrorFormatIndex = FormatIndex; + NonOpen_ErrorInfo = ErrorInfo; + + if (!mode.CanReturnParser && isArc) + { + // if (formatIndex < 0 && !searchMarkerInHandler) + { + // if bad archive was detected, we don't need additional open attempts + #ifndef Z7_SFX + if (!IsPreArcFormat(ai) /* || !mode.SkipSfxStub */) + #endif + return S_FALSE; + } + } + } + + /* + #ifndef Z7_SFX + if (IsExeExt(extension) || ai.Flags_PreArc()) + { + // openOnlyFullArc = false; + // canReturnTailArc = true; + // limitSignatureSearch = true; + } + #endif + */ + + continue; + } + + RINOK(result) + + #ifndef Z7_SFX + + bool isMainFormat = isMainFormatArr[(unsigned)FormatIndex]; + const COpenSpecFlags &specFlags = mode.GetSpec(isForced, isMainFormat, isUnknownExt); + + bool thereIsTail = ErrorInfo.ThereIsTail; + if (thereIsTail && mode.ZerosTailIsAllowed) + { + RINOK(CheckZerosTail(op, (UInt64)(Offset + (Int64)PhySize))) + if (ErrorInfo.IgnoreTail) + thereIsTail = false; + } + + if (Offset > 0) + { + if (exactOnly + || !searchMarkerInHandler + || !specFlags.CanReturn_NonStart() + || (mode.MaxStartOffset_Defined && (UInt64)Offset > mode.MaxStartOffset)) + continue; + } + if (thereIsTail) + { + if (Offset > 0) + { + if (!specFlags.CanReturnMid) + continue; + } + else if (!specFlags.CanReturnFrontal) + continue; + } + + if (Offset > 0 || thereIsTail) + { + if (formatIndex < 0) + { + if (IsPreArcFormat(ai)) + { + // openOnlyFullArc = false; + // canReturnTailArc = true; + /* + if (mode.SkipSfxStub) + limitSignatureSearch = true; + */ + // if (mode.SkipSfxStub) + { + // skipFrontalFormat[FormatIndex] = true; + continue; + } + } + } + } + + #endif + + Archive = archive; + return S_OK; + } + } + + + + #ifndef Z7_SFX + + if (!op.stream) + return S_FALSE; + + if (formatIndex >= 0 && !mode.CanReturnParser) + { + if (mode.MaxStartOffset_Defined) + { + if (mode.MaxStartOffset == 0) + return S_FALSE; + } + else + { + const CArcInfoEx &ai = op.codecs->Formats[(unsigned)formatIndex]; + if (ai.FindExtension(extension) >= 0) + { + if (ai.Flags_FindSignature() && searchMarkerInHandler) + return S_FALSE; + } + } + } + + NArchive::NParser::CHandler *handlerSpec = new NArchive::NParser::CHandler; + CMyComPtr handler = handlerSpec; + + CExtractCallback_To_OpenCallback *extractCallback_To_OpenCallback_Spec = new CExtractCallback_To_OpenCallback; + CMyComPtr extractCallback_To_OpenCallback = extractCallback_To_OpenCallback_Spec; + extractCallback_To_OpenCallback_Spec->Init(op.callback); + + { + // ---------- Check all possible START archives ---------- + // this code is better for full file archives than Parser's code. + + CByteBuffer byteBuffer; + bool endOfFile = false; + size_t processedSize; + { + size_t bufSize = 1 << 20; // it must be larger than max signature offset or IsArcFunc offset ((1 << 19) + x for UDF) + if (bufSize > fileSize) + { + bufSize = (size_t)fileSize; + endOfFile = true; + } + byteBuffer.Alloc(bufSize); + RINOK(InStream_SeekToBegin(op.stream)) + processedSize = bufSize; + RINOK(ReadStream(op.stream, byteBuffer, &processedSize)) + if (processedSize == 0) + return S_FALSE; + if (processedSize < bufSize) + endOfFile = true; + } + CUIntVector sortedFormats; + + unsigned i; + + int splitIndex = -1; + + for (i = 0; i < orderIndices.Size(); i++) + { + // orderIndices[] item cannot be negative here + unsigned form = (unsigned)orderIndices[i]; + if (skipFrontalFormat[form]) + continue; + + const CArcInfoEx &ai = op.codecs->Formats[form]; + + if (ai.Is_Split()) + { + splitIndex = (int)form; + continue; + } + + if (ai.Flags_ByExtOnlyOpen()) + continue; + + if (ai.IsArcFunc) + { + UInt32 isArcRes = ai.IsArcFunc(byteBuffer, processedSize); + if (isArcRes == k_IsArc_Res_NO) + continue; + if (isArcRes == k_IsArc_Res_NEED_MORE && endOfFile) + continue; + // if (isArcRes == k_IsArc_Res_YES_LOW_PROB) continue; + sortedFormats.Insert(0, form); + continue; + } + + const bool isNewStyleSignature = IsNewStyleSignature(ai); + bool needCheck = !isNewStyleSignature + || ai.Signatures.IsEmpty() + || ai.Flags_PureStartOpen() + || ai.Flags_StartOpen() + || ai.Flags_BackwardOpen(); + + if (isNewStyleSignature && !ai.Signatures.IsEmpty()) + { + unsigned k; + for (k = 0; k < ai.Signatures.Size(); k++) + { + const CByteBuffer &sig = ai.Signatures[k]; + if (processedSize < ai.SignatureOffset + sig.Size()) + { + if (!endOfFile) + needCheck = true; + } + else if (TestSignature(sig, byteBuffer + ai.SignatureOffset, sig.Size())) + break; + } + if (k != ai.Signatures.Size()) + { + sortedFormats.Insert(0, form); + continue; + } + } + if (needCheck) + sortedFormats.Add(form); + } + + if (splitIndex >= 0) + sortedFormats.Insert(0, (unsigned)splitIndex); + + for (i = 0; i < sortedFormats.Size(); i++) + { + FormatIndex = (int)sortedFormats[i]; + const CArcInfoEx &ai = op.codecs->Formats[(unsigned)FormatIndex]; + + if (op.callback) + RINOK(op.callback->SetTotal(NULL, &fileSize)) + + RINOK(InStream_SeekToBegin(op.stream)) + + CMyComPtr archive; + RINOK(PrepareToOpen(op, (unsigned)FormatIndex, archive)) + if (!archive) + continue; + + PRF(printf("\nSorted Open %S", (const wchar_t *)ai.Name)); + HRESULT result; + { + UInt64 searchLimit = 0; + /* + if (mode.CanReturnArc) + result = archive->Open(op.stream, &searchLimit, op.callback); + else + */ + // if (!CanReturnArc), it's ParserMode, and we need phy size + result = OpenArchiveSpec(archive, + !mode.CanReturnArc, // needPhySize + op.stream, &searchLimit, op.callback, extractCallback_To_OpenCallback); + } + + if (result == S_FALSE) + { + skipFrontalFormat[(unsigned)FormatIndex] = true; + // FIXME: maybe we must use LenIsUnknown. + // printf(" OpenForSize Error"); + continue; + } + RINOK(result) + + RINOK(ReadBasicProps(archive, 0, result)) + + if (Offset > 0) + { + continue; // good handler doesn't return such Offset > 0 + // but there are some cases like false prefixed PK00 archive, when + // we can support it? + } + + NArchive::NParser::CParseItem pi; + pi.Offset = (UInt64)Offset; + pi.Size = AvailPhySize; + + // bool needScan = false; + + if (!PhySize_Defined) + { + // it's for Z format + pi.LenIsUnknown = true; + // needScan = true; + // phySize = arcRem; + // nextNeedCheckStartOpen = false; + } + + /* + if (OkPhySize_Defined) + pi.OkSize = pi.OkPhySize; + else + pi.OkSize = pi.Size; + */ + + pi.NormalizeOffset(); + // printf(" phySize = %8d", (unsigned)phySize); + + + if (mode.CanReturnArc) + { + const bool isMainFormat = isMainFormatArr[(unsigned)FormatIndex]; + const COpenSpecFlags &specFlags = mode.GetSpec(isForced, isMainFormat, isUnknownExt); + bool openCur = false; + + if (!ErrorInfo.ThereIsTail) + openCur = true; + else + { + if (mode.ZerosTailIsAllowed) + { + RINOK(CheckZerosTail(op, (UInt64)(Offset + (Int64)PhySize))) + if (ErrorInfo.IgnoreTail) + openCur = true; + } + if (!openCur) + { + openCur = specFlags.CanReturnFrontal; + if (formatIndex < 0) // format is not forced + { + if (IsPreArcFormat(ai)) + { + // if (mode.SkipSfxStub) + { + openCur = false; + } + } + } + } + } + + if (openCur) + { + InStream = op.stream; + Archive = archive; + return S_OK; + } + } + + skipFrontalFormat[(unsigned)FormatIndex] = true; + + + // if (!mode.CanReturnArc) + /* + if (!ErrorInfo.ThereIsTail) + continue; + */ + if (pi.Offset == 0 && !pi.LenIsUnknown && pi.Size >= FileSize) + continue; + + // printf("\nAdd offset = %d", (int)pi.Offset); + RINOK(ReadParseItemProps(archive, ai, pi)) + handlerSpec->AddItem(pi); + } + } + + + + + + // ---------- PARSER ---------- + + CUIntVector arc2sig; // formatIndex to signatureIndex + CUIntVector sig2arc; // signatureIndex to formatIndex; + { + unsigned sum = 0; + FOR_VECTOR (i, op.codecs->Formats) + { + arc2sig.Add(sum); + const CObjectVector &sigs = op.codecs->Formats[i].Signatures; + sum += sigs.Size(); + FOR_VECTOR (k, sigs) + sig2arc.Add(i); + } + } + + { + const size_t kBeforeSize = 1 << 16; + const size_t kAfterSize = 1 << 20; + const size_t kBufSize = 1 << 22; // it must be more than kBeforeSize + kAfterSize + + const UInt32 kNumVals = (UInt32)1 << (kNumHashBytes * 8); + CByteArr hashBuffer(kNumVals); + Byte *hash = hashBuffer; + memset(hash, 0xFF, kNumVals); + Byte prevs[256]; + memset(prevs, 0xFF, sizeof(prevs)); + if (sig2arc.Size() >= 0xFF) + return S_FALSE; + + CUIntVector difficultFormats; + CBoolArr difficultBools(256); + { + for (unsigned i = 0; i < 256; i++) + difficultBools[i] = false; + } + + bool thereAreHandlersForSearch = false; + + // UInt32 maxSignatureEnd = 0; + + FOR_VECTOR (i, orderIndices) + { + int index = orderIndices[i]; + if (index < 0) + continue; + const CArcInfoEx &ai = op.codecs->Formats[(unsigned)index]; + if (ai.Flags_ByExtOnlyOpen()) + continue; + bool isDifficult = false; + // if (ai.Version < 0x91F) // we don't use parser with old DLL (before 9.31) + if (!ai.NewInterface) + isDifficult = true; + else + { + if (ai.Flags_StartOpen()) + isDifficult = true; + FOR_VECTOR (k, ai.Signatures) + { + const CByteBuffer &sig = ai.Signatures[k]; + /* + UInt32 signatureEnd = ai.SignatureOffset + (UInt32)sig.Size(); + if (maxSignatureEnd < signatureEnd) + maxSignatureEnd = signatureEnd; + */ + if (sig.Size() < kNumHashBytes) + { + isDifficult = true; + continue; + } + thereAreHandlersForSearch = true; + UInt32 v = HASH_VAL(sig); + unsigned sigIndex = arc2sig[(unsigned)index] + k; + prevs[sigIndex] = hash[v]; + hash[v] = (Byte)sigIndex; + } + } + if (isDifficult) + { + difficultFormats.Add((unsigned)index); + difficultBools[(unsigned)index] = true; + } + } + + if (!thereAreHandlersForSearch) + { + // openOnlyFullArc = true; + // canReturnTailArc = true; + } + + RINOK(InStream_SeekToBegin(op.stream)) + + CLimitedCachedInStream *limitedStreamSpec = new CLimitedCachedInStream; + CMyComPtr limitedStream = limitedStreamSpec; + limitedStreamSpec->SetStream(op.stream); + + CArchiveOpenCallback_Offset *openCallback_Offset_Spec = NULL; + CMyComPtr openCallback_Offset; + if (op.callback) + { + openCallback_Offset_Spec = new CArchiveOpenCallback_Offset; + openCallback_Offset = openCallback_Offset_Spec; + openCallback_Offset_Spec->Callback = op.callback; + openCallback_Offset_Spec->Callback.QueryInterface(IID_IArchiveOpenVolumeCallback, &openCallback_Offset_Spec->OpenVolumeCallback); + #ifndef Z7_NO_CRYPTO + openCallback_Offset_Spec->Callback.QueryInterface(IID_ICryptoGetTextPassword, &openCallback_Offset_Spec->GetTextPassword); + #endif + } + + if (op.callback) + RINOK(op.callback->SetTotal(NULL, &fileSize)) + + CByteBuffer &byteBuffer = limitedStreamSpec->Buffer; + byteBuffer.Alloc(kBufSize); + + UInt64 callbackPrev = 0; + bool needCheckStartOpen = true; // = true, if we need to test all archives types for current pos. + + bool endOfFile = false; + UInt64 bufPhyPos = 0; + size_t bytesInBuf = 0; + // UInt64 prevPos = 0; + + // ---------- Main Scan Loop ---------- + + UInt64 pos = 0; + + if (!mode.EachPos && handlerSpec->_items.Size() == 1) + { + NArchive::NParser::CParseItem &pi = handlerSpec->_items[0]; + if (!pi.LenIsUnknown && pi.Offset == 0) + pos = pi.Size; + } + + for (;;) + { + // printf("\nPos = %d", (int)pos); + UInt64 posInBuf = pos - bufPhyPos; + + // if (pos > ((UInt64)1 << 35)) break; + + if (!endOfFile) + { + if (bytesInBuf < kBufSize) + { + size_t processedSize = kBufSize - bytesInBuf; + // printf("\nRead ask = %d", (unsigned)processedSize); + UInt64 seekPos = bufPhyPos + bytesInBuf; + RINOK(InStream_SeekSet(op.stream, bufPhyPos + bytesInBuf)) + RINOK(ReadStream(op.stream, byteBuffer.NonConstData() + bytesInBuf, &processedSize)) + // printf(" processed = %d", (unsigned)processedSize); + if (processedSize == 0) + { + fileSize = seekPos; + endOfFile = true; + } + else + { + bytesInBuf += processedSize; + limitedStreamSpec->SetCache(processedSize, (size_t)bufPhyPos); + } + continue; + } + + if (bytesInBuf < posInBuf) + { + UInt64 skipSize = posInBuf - bytesInBuf; + if (skipSize <= kBeforeSize) + { + size_t keepSize = (size_t)(kBeforeSize - skipSize); + // printf("\nmemmove skip = %d", (int)keepSize); + memmove(byteBuffer, byteBuffer.ConstData() + bytesInBuf - keepSize, keepSize); + bytesInBuf = keepSize; + bufPhyPos = pos - keepSize; + continue; + } + // printf("\nSkip %d", (int)(skipSize - kBeforeSize)); + // RINOK(op.stream->Seek(skipSize - kBeforeSize, STREAM_SEEK_CUR, NULL)); + bytesInBuf = 0; + bufPhyPos = pos - kBeforeSize; + continue; + } + + if (bytesInBuf - posInBuf < kAfterSize) + { + size_t beg = (size_t)posInBuf - kBeforeSize; + // printf("\nmemmove for after beg = %d", (int)beg); + memmove(byteBuffer, byteBuffer.ConstData() + beg, bytesInBuf - beg); + bufPhyPos += beg; + bytesInBuf -= beg; + continue; + } + } + + if (bytesInBuf <= (size_t)posInBuf) + break; + + bool useOffsetCallback = false; + if (openCallback_Offset) + { + openCallback_Offset_Spec->Files = handlerSpec->_items.Size(); + openCallback_Offset_Spec->Offset = pos; + + useOffsetCallback = (!op.openType.CanReturnArc || handlerSpec->_items.Size() > 1); + + if (pos >= callbackPrev + (1 << 23)) + { + RINOK(openCallback_Offset->SetCompleted(NULL, NULL)) + callbackPrev = pos; + } + } + + { + UInt64 endPos = bufPhyPos + bytesInBuf; + if (fileSize < endPos) + { + FileSize = fileSize; // why ???? + fileSize = endPos; + } + } + + const size_t availSize = bytesInBuf - (size_t)posInBuf; + if (availSize < kNumHashBytes) + break; + size_t scanSize = availSize - + ((availSize >= kAfterSize) ? kAfterSize : kNumHashBytes); + + { + /* + UInt64 scanLimit = openOnlyFullArc ? + maxSignatureEnd : + op.openType.ScanSize + maxSignatureEnd; + */ + if (!mode.CanReturnParser) + { + if (pos > maxStartOffset) + break; + UInt64 remScan = maxStartOffset - pos; + if (scanSize > remScan) + scanSize = (size_t)remScan; + } + } + + scanSize++; + + const Byte *buf = byteBuffer.ConstData() + (size_t)posInBuf; + const Byte *bufLimit = buf + scanSize; + size_t ppp = 0; + + if (!needCheckStartOpen) + { + for (; buf < bufLimit && hash[HASH_VAL(buf)] == 0xFF; buf++); + ppp = (size_t)(buf - (byteBuffer.ConstData() + (size_t)posInBuf)); + pos += ppp; + if (buf == bufLimit) + continue; + } + + UInt32 v = HASH_VAL(buf); + bool nextNeedCheckStartOpen = true; + unsigned i = hash[v]; + unsigned indexOfDifficult = 0; + + // ---------- Open Loop for Current Pos ---------- + bool wasOpen = false; + + for (;;) + { + unsigned index; + bool isDifficult; + if (needCheckStartOpen && indexOfDifficult < difficultFormats.Size()) + { + index = difficultFormats[indexOfDifficult++]; + isDifficult = true; + } + else + { + if (i == 0xFF) + break; + index = sig2arc[i]; + unsigned sigIndex = i - arc2sig[index]; + i = prevs[i]; + if (needCheckStartOpen && difficultBools[index]) + continue; + const CArcInfoEx &ai = op.codecs->Formats[index]; + + if (pos < ai.SignatureOffset) + continue; + + /* + if (openOnlyFullArc) + if (pos != ai.SignatureOffset) + continue; + */ + + const CByteBuffer &sig = ai.Signatures[sigIndex]; + + if (ppp + sig.Size() > availSize + || !TestSignature(buf, sig, sig.Size())) + continue; + // printf("\nSignature OK: %10S %8x %5d", (const wchar_t *)ai.Name, (int)pos, (int)(pos - prevPos)); + // prevPos = pos; + isDifficult = false; + } + + const CArcInfoEx &ai = op.codecs->Formats[index]; + + + if ((isDifficult && pos == 0) || ai.SignatureOffset == pos) + { + // we don't check same archive second time */ + if (skipFrontalFormat[index]) + continue; + } + + UInt64 startArcPos = pos; + if (!isDifficult) + { + if (pos < ai.SignatureOffset) + continue; + startArcPos = pos - ai.SignatureOffset; + /* + // we don't need the check for Z files + if (startArcPos < handlerSpec->GetLastEnd()) + continue; + */ + } + + if (ai.IsArcFunc && startArcPos >= bufPhyPos) + { + const size_t offsetInBuf = (size_t)(startArcPos - bufPhyPos); + if (offsetInBuf < bytesInBuf) + { + const UInt32 isArcRes = ai.IsArcFunc(byteBuffer.ConstData() + offsetInBuf, bytesInBuf - offsetInBuf); + if (isArcRes == k_IsArc_Res_NO) + continue; + if (isArcRes == k_IsArc_Res_NEED_MORE && endOfFile) + continue; + /* + if (isArcRes == k_IsArc_Res_YES_LOW_PROB) + { + // if (pos != ai.SignatureOffset) + continue; + } + */ + } + // printf("\nIsArc OK: %S", (const wchar_t *)ai.Name); + } + + PRF(printf("\npos = %9I64d : %S", pos, (const wchar_t *)ai.Name)); + + const bool isMainFormat = isMainFormatArr[index]; + const COpenSpecFlags &specFlags = mode.GetSpec(isForced, isMainFormat, isUnknownExt); + + CMyComPtr archive; + RINOK(PrepareToOpen(op, index, archive)) + if (!archive) + return E_FAIL; + + // OutputDebugStringW(ai.Name); + + const UInt64 rem = fileSize - startArcPos; + + UInt64 arcStreamOffset = 0; + + if (ai.Flags_UseGlobalOffset()) + { + RINOK(limitedStreamSpec->InitAndSeek(0, fileSize)) + RINOK(InStream_SeekSet(limitedStream, startArcPos)) + } + else + { + RINOK(limitedStreamSpec->InitAndSeek(startArcPos, rem)) + arcStreamOffset = startArcPos; + } + + UInt64 maxCheckStartPosition = 0; + + if (openCallback_Offset) + { + openCallback_Offset_Spec->Files = handlerSpec->_items.Size(); + openCallback_Offset_Spec->Offset = startArcPos; + } + + // HRESULT result = archive->Open(limitedStream, &maxCheckStartPosition, openCallback_Offset); + extractCallback_To_OpenCallback_Spec->Files = 0; + extractCallback_To_OpenCallback_Spec->Offset = startArcPos; + + HRESULT result = OpenArchiveSpec(archive, + true, // needPhySize + limitedStream, &maxCheckStartPosition, + useOffsetCallback ? (IArchiveOpenCallback *)openCallback_Offset : (IArchiveOpenCallback *)op.callback, + extractCallback_To_OpenCallback); + + RINOK(ReadBasicProps(archive, ai.Flags_UseGlobalOffset() ? 0 : startArcPos, result)) + + bool isOpen = false; + + if (result == S_FALSE) + { + if (!mode.CanReturnParser) + { + if (formatIndex < 0 && ErrorInfo.IsArc_After_NonOpen()) + { + ErrorInfo.ErrorFormatIndex = (int)index; + NonOpen_ErrorInfo = ErrorInfo; + // if archive was detected, we don't need additional open attempts + return S_FALSE; + } + continue; + } + if (!ErrorInfo.IsArc_After_NonOpen() || !PhySize_Defined || PhySize == 0) + continue; + } + else + { + if (PhySize_Defined && PhySize == 0) + { + PRF(printf(" phySize_Defined && PhySize == 0 ")); + // we skip that epmty archive case with unusual unexpected (PhySize == 0) from Code function. + continue; + } + isOpen = true; + RINOK(result) + PRF(printf(" OK ")); + } + + // fprintf(stderr, "\n %8X %S", startArcPos, Path); + // printf("\nOpen OK: %S", ai.Name); + + + NArchive::NParser::CParseItem pi; + pi.Offset = startArcPos; + + if (ai.Flags_UseGlobalOffset()) + pi.Offset = (UInt64)Offset; + else if (Offset != 0) + return E_FAIL; + + const UInt64 arcRem = FileSize - pi.Offset; + UInt64 phySize = arcRem; + const bool phySize_Defined = PhySize_Defined; + if (phySize_Defined) + { + if (pi.Offset + PhySize > FileSize) + { + // ErrorInfo.ThereIsTail = true; + PhySize = FileSize - pi.Offset; + } + phySize = PhySize; + } + if (phySize == 0 || (UInt64)phySize > ((UInt64)1 << 63)) + return E_FAIL; + + /* + if (!ai.UseGlobalOffset) + { + if (phySize > arcRem) + { + ThereIsTail = true; + phySize = arcRem; + } + } + */ + + bool needScan = false; + + + if (isOpen && !phySize_Defined) + { + // it's for Z format, or bzip2,gz,xz with phySize that was not detected + pi.LenIsUnknown = true; + needScan = true; + phySize = arcRem; + nextNeedCheckStartOpen = false; + } + + pi.Size = phySize; + /* + if (OkPhySize_Defined) + pi.OkSize = OkPhySize; + */ + pi.NormalizeOffset(); + // printf(" phySize = %8d", (unsigned)phySize); + + /* + if (needSkipFullArc) + if (pi.Offset == 0 && phySize_Defined && pi.Size >= fileSize) + continue; + */ + if (pi.Offset == 0 && !pi.LenIsUnknown && pi.Size >= FileSize) + { + // it's possible for dmg archives + if (!mode.CanReturnArc) + continue; + } + + if (mode.EachPos) + pos++; + else if (needScan) + { + pos++; + /* + if (!OkPhySize_Defined) + pos++; + else + pos = pi.Offset + pi.OkSize; + */ + } + else + pos = pi.Offset + pi.Size; + + + RINOK(ReadParseItemProps(archive, ai, pi)) + + if (pi.Offset < startArcPos && !mode.EachPos /* && phySize_Defined */) + { + /* It's for DMG format. + This code deletes all previous items that are included to current item */ + + while (!handlerSpec->_items.IsEmpty()) + { + { + const NArchive::NParser::CParseItem &back = handlerSpec->_items.Back(); + if (back.Offset < pi.Offset) + break; + if (back.Offset + back.Size > pi.Offset + pi.Size) + break; + } + handlerSpec->_items.DeleteBack(); + } + } + + + if (isOpen && mode.CanReturnArc && phySize_Defined) + { + // if (pi.Offset + pi.Size >= fileSize) + bool openCur = false; + + bool thereIsTail = ErrorInfo.ThereIsTail; + if (thereIsTail && mode.ZerosTailIsAllowed) + { + RINOK(CheckZerosTail(op, (UInt64)((Int64)arcStreamOffset + Offset + (Int64)PhySize))) + if (ErrorInfo.IgnoreTail) + thereIsTail = false; + } + + if (pi.Offset != 0) + { + if (!pi.IsNotArcType) + { + if (thereIsTail) + openCur = specFlags.CanReturnMid; + else + openCur = specFlags.CanReturnTail; + } + } + else + { + if (!thereIsTail) + openCur = true; + else + openCur = specFlags.CanReturnFrontal; + + if (formatIndex >= -2) + openCur = true; + } + + if (formatIndex < 0 && pi.IsSelfExe /* && mode.SkipSfxStub */) + openCur = false; + + // We open file as SFX, if there is front archive or first archive is "Self Executable" + if (!openCur && !pi.IsSelfExe && !thereIsTail && + (!pi.IsNotArcType || pi.Offset == 0)) + { + if (handlerSpec->_items.IsEmpty()) + { + if (specFlags.CanReturnTail) + openCur = true; + } + else if (handlerSpec->_items.Size() == 1) + { + if (handlerSpec->_items[0].IsSelfExe) + { + if (mode.SpecUnknownExt.CanReturnTail) + openCur = true; + } + } + } + + if (openCur) + { + InStream = op.stream; + Archive = archive; + FormatIndex = (int)index; + ArcStreamOffset = arcStreamOffset; + return S_OK; + } + } + + /* + if (openOnlyFullArc) + { + ErrorInfo.ClearErrors(); + return S_FALSE; + } + */ + + pi.FormatIndex = (int)index; + + // printf("\nAdd offset = %d", (int)pi.Offset); + handlerSpec->AddItem(pi); + wasOpen = true; + break; + } + // ---------- End of Open Loop for Current Pos ---------- + + if (!wasOpen) + pos++; + needCheckStartOpen = (nextNeedCheckStartOpen && wasOpen); + } + // ---------- End of Main Scan Loop ---------- + + /* + if (handlerSpec->_items.Size() == 1) + { + const NArchive::NParser::CParseItem &pi = handlerSpec->_items[0]; + if (pi.Size == fileSize && pi.Offset == 0) + { + Archive = archive; + FormatIndex2 = pi.FormatIndex; + return S_OK; + } + } + */ + + if (mode.CanReturnParser) + { + bool returnParser = (handlerSpec->_items.Size() == 1); // it's possible if fileSize was not correct at start of parsing + handlerSpec->AddUnknownItem(fileSize); + if (handlerSpec->_items.Size() == 0) + return S_FALSE; + if (returnParser || handlerSpec->_items.Size() != 1) + { + // return S_FALSE; + handlerSpec->_stream = op.stream; + Archive = handler; + ErrorInfo.ClearErrors(); + IsParseArc = true; + FormatIndex = -1; // It's parser + Offset = 0; + return S_OK; + } + } + } + + #endif + + if (!Archive) + return S_FALSE; + return S_OK; +} + + + + +HRESULT CArc::OpenStream(const COpenOptions &op) +{ + RINOK(OpenStream2(op)) + // PrintNumber("op.formatIndex 3", op.formatIndex); + + if (Archive) + { + GetRawProps.Release(); + GetRootProps.Release(); + Archive->QueryInterface(IID_IArchiveGetRawProps, (void **)&GetRawProps); + Archive->QueryInterface(IID_IArchiveGetRootProps, (void **)&GetRootProps); + + RINOK(Archive_GetArcProp_Bool(Archive, kpidIsTree, IsTree)) + RINOK(Archive_GetArcProp_Bool(Archive, kpidIsDeleted, Ask_Deleted)) + RINOK(Archive_GetArcProp_Bool(Archive, kpidIsAltStream, Ask_AltStream)) + RINOK(Archive_GetArcProp_Bool(Archive, kpidIsAux, Ask_Aux)) + RINOK(Archive_GetArcProp_Bool(Archive, kpidINode, Ask_INode)) + RINOK(Archive_GetArcProp_Bool(Archive, kpidReadOnly, IsReadOnly)) + + const UString fileName = ExtractFileNameFromPath(Path); + UString extension; + { + int dotPos = fileName.ReverseFind_Dot(); + if (dotPos >= 0) + extension = fileName.Ptr((unsigned)(dotPos + 1)); + } + + DefaultName.Empty(); + if (FormatIndex >= 0) + { + const CArcInfoEx &ai = op.codecs->Formats[(unsigned)FormatIndex]; + if (ai.Exts.Size() == 0) + DefaultName = GetDefaultName2(fileName, UString(), UString()); + else + { + int subExtIndex = ai.FindExtension(extension); + if (subExtIndex < 0) + subExtIndex = 0; + const CArcExtInfo &extInfo = ai.Exts[(unsigned)subExtIndex]; + DefaultName = GetDefaultName2(fileName, extInfo.Ext, extInfo.AddExt); + } + } + } + + return S_OK; +} + +#ifdef Z7_SFX + +#ifdef _WIN32 + #define k_ExeExt ".exe" + static const unsigned k_ExeExt_Len = 4; +#else + #define k_ExeExt "" + static const unsigned k_ExeExt_Len = 0; +#endif + +#endif + +HRESULT CArc::OpenStreamOrFile(COpenOptions &op) +{ + CMyComPtr fileStream; + CMyComPtr seqStream; + CInFileStream *fileStreamSpec = NULL; + + if (op.stdInMode) + { +#if 1 + seqStream = new CStdInFileStream; +#else + if (!CreateStdInStream(seqStream)) + return GetLastError_noZero_HRESULT(); +#endif + op.seqStream = seqStream; + } + else if (!op.stream) + { + fileStreamSpec = new CInFileStream; + fileStream = fileStreamSpec; + Path = filePath; + if (!fileStreamSpec->Open(us2fs(Path))) + return GetLastError_noZero_HRESULT(); + op.stream = fileStream; + #ifdef Z7_SFX + IgnoreSplit = true; + #endif + } + + /* + if (callback) + { + UInt64 fileSize; + RINOK(InStream_GetSize_SeekToEnd(op.stream, fileSize)); + RINOK(op.callback->SetTotal(NULL, &fileSize)) + } + */ + + HRESULT res = OpenStream(op); + IgnoreSplit = false; + + #ifdef Z7_SFX + + if (res != S_FALSE + || !fileStreamSpec + || !op.callbackSpec + || NonOpen_ErrorInfo.IsArc_After_NonOpen()) + return res; + + { + if (filePath.Len() > k_ExeExt_Len + && StringsAreEqualNoCase_Ascii(filePath.RightPtr(k_ExeExt_Len), k_ExeExt)) + { + const UString path2 = filePath.Left(filePath.Len() - k_ExeExt_Len); + FOR_VECTOR (i, op.codecs->Formats) + { + const CArcInfoEx &ai = op.codecs->Formats[i]; + if (ai.Is_Split()) + continue; + UString path3 = path2; + path3.Add_Dot(); + path3 += ai.GetMainExt(); // "7z" for SFX. + Path = path3; + Path += ".001"; + bool isOk = op.callbackSpec->SetSecondFileInfo(us2fs(Path)); + if (!isOk) + { + Path = path3; + isOk = op.callbackSpec->SetSecondFileInfo(us2fs(Path)); + } + if (isOk) + { + if (fileStreamSpec->Open(us2fs(Path))) + { + op.stream = fileStream; + NonOpen_ErrorInfo.ClearErrors_Full(); + if (OpenStream(op) == S_OK) + return S_OK; + } + } + } + } + } + + #endif + + return res; +} + +void CArchiveLink::KeepModeForNextOpen() +{ + for (unsigned i = Arcs.Size(); i != 0;) + { + i--; + CMyComPtr keep; + Arcs[i].Archive->QueryInterface(IID_IArchiveKeepModeForNextOpen, (void **)&keep); + if (keep) + keep->KeepModeForNextOpen(); + } +} + +HRESULT CArchiveLink::Close() +{ + for (unsigned i = Arcs.Size(); i != 0;) + { + i--; + RINOK(Arcs[i].Close()) + } + IsOpen = false; + // ErrorsText.Empty(); + return S_OK; +} + +void CArchiveLink::Release() +{ + // NonOpenErrorFormatIndex = -1; + NonOpen_ErrorInfo.ClearErrors(); + NonOpen_ArcPath.Empty(); + while (!Arcs.IsEmpty()) + Arcs.DeleteBack(); +} + +/* +void CArchiveLink::Set_ErrorsText() +{ + FOR_VECTOR(i, Arcs) + { + const CArc &arc = Arcs[i]; + if (!arc.ErrorFlagsText.IsEmpty()) + { + if (!ErrorsText.IsEmpty()) + ErrorsText.Add_LF(); + ErrorsText += GetUnicodeString(arc.ErrorFlagsText); + } + if (!arc.ErrorMessage.IsEmpty()) + { + if (!ErrorsText.IsEmpty()) + ErrorsText.Add_LF(); + ErrorsText += arc.ErrorMessage; + } + + if (!arc.WarningMessage.IsEmpty()) + { + if (!ErrorsText.IsEmpty()) + ErrorsText.Add_LF(); + ErrorsText += arc.WarningMessage; + } + } +} +*/ + +HRESULT CArchiveLink::Open(COpenOptions &op) +{ + Release(); + if (op.types->Size() >= 32) + return E_NOTIMPL; + + HRESULT resSpec; + + for (;;) + { + resSpec = S_OK; + + op.openType = COpenType(); + if (op.types->Size() >= 1) + { + COpenType latest; + if (Arcs.Size() < op.types->Size()) + latest = (*op.types)[op.types->Size() - Arcs.Size() - 1]; + else + { + latest = (*op.types)[0]; + if (!latest.Recursive) + break; + } + op.openType = latest; + } + else if (Arcs.Size() >= 32) + break; + + /* + op.formatIndex = -1; + if (op.types->Size() >= 1) + { + int latest; + if (Arcs.Size() < op.types->Size()) + latest = (*op.types)[op.types->Size() - Arcs.Size() - 1]; + else + { + latest = (*op.types)[0]; + if (latest != -2 && latest != -3) + break; + } + if (latest >= 0) + op.formatIndex = latest; + else if (latest == -1 || latest == -2) + { + // default + } + else if (latest == -3) + op.formatIndex = -2; + else + op.formatIndex = latest + 2; + } + else if (Arcs.Size() >= 32) + break; + */ + + if (Arcs.IsEmpty()) + { + CArc arc; + arc.filePath = op.filePath; + arc.Path = op.filePath; + arc.SubfileIndex = (UInt32)(Int32)-1; + HRESULT result = arc.OpenStreamOrFile(op); + if (result != S_OK) + { + if (result == S_FALSE) + { + NonOpen_ErrorInfo = arc.NonOpen_ErrorInfo; + // NonOpenErrorFormatIndex = arc.ErrorFormatIndex; + NonOpen_ArcPath = arc.Path; + } + return result; + } + Arcs.Add(arc); + continue; + } + + // PrintNumber("op.formatIndex 11", op.formatIndex); + + const CArc &arc = Arcs.Back(); + + if (op.types->Size() > Arcs.Size()) + resSpec = E_NOTIMPL; + + UInt32 mainSubfile; + { + NCOM::CPropVariant prop; + RINOK(arc.Archive->GetArchiveProperty(kpidMainSubfile, &prop)) + if (prop.vt == VT_UI4) + mainSubfile = prop.ulVal; + else + break; + UInt32 numItems; + RINOK(arc.Archive->GetNumberOfItems(&numItems)) + if (mainSubfile >= numItems) + break; + } + + + CMyComPtr getStream; + if (arc.Archive->QueryInterface(IID_IInArchiveGetStream, (void **)&getStream) != S_OK || !getStream) + break; + + CMyComPtr subSeqStream; + if (getStream->GetStream(mainSubfile, &subSeqStream) != S_OK || !subSeqStream) + break; + + CMyComPtr subStream; + if (subSeqStream.QueryInterface(IID_IInStream, &subStream) != S_OK || !subStream) + break; + + CArc arc2; + RINOK(arc.GetItem_Path(mainSubfile, arc2.Path)) + + bool zerosTailIsAllowed; + RINOK(Archive_GetItemBoolProp(arc.Archive, mainSubfile, kpidZerosTailIsAllowed, zerosTailIsAllowed)) + + + if (op.callback) + { + Z7_DECL_CMyComPtr_QI_FROM( + IArchiveOpenSetSubArchiveName, + setSubArchiveName, op.callback) + if (setSubArchiveName) + setSubArchiveName->SetSubArchiveName(arc2.Path); + } + + arc2.SubfileIndex = mainSubfile; + + // CIntVector incl; + CIntVector excl; + + COpenOptions op2; + #ifndef Z7_SFX + op2.props = op.props; + #endif + op2.codecs = op.codecs; + // op2.types = &incl; + op2.openType = op.openType; + op2.openType.ZerosTailIsAllowed = zerosTailIsAllowed; + op2.excludedFormats = ! + op2.stdInMode = false; + op2.stream = subStream; + op2.filePath = arc2.Path; + op2.callback = op.callback; + op2.callbackSpec = op.callbackSpec; + + + HRESULT result = arc2.OpenStream(op2); + resSpec = (op.types->Size() == 0 ? S_OK : S_FALSE); + if (result == S_FALSE) + { + NonOpen_ErrorInfo = arc2.ErrorInfo; + NonOpen_ArcPath = arc2.Path; + break; + } + RINOK(result) + RINOK(arc.GetItem_MTime(mainSubfile, arc2.MTime)) + Arcs.Add(arc2); + } + IsOpen = !Arcs.IsEmpty(); + return resSpec; +} + +HRESULT CArchiveLink::Open2(COpenOptions &op, IOpenCallbackUI *callbackUI) +{ + VolumesSize = 0; + COpenCallbackImp *openCallbackSpec = new COpenCallbackImp; + CMyComPtr callback = openCallbackSpec; + openCallbackSpec->Callback = callbackUI; + + FString prefix, name; + + if (!op.stream && !op.stdInMode) + { + NFile::NDir::GetFullPathAndSplit(us2fs(op.filePath), prefix, name); + RINOK(openCallbackSpec->Init2(prefix, name)) + } + else + { + openCallbackSpec->SetSubArchiveName(op.filePath); + } + + op.callback = callback; + op.callbackSpec = openCallbackSpec; + + HRESULT res = Open(op); + + PasswordWasAsked = openCallbackSpec->PasswordWasAsked; + // Password = openCallbackSpec->Password; + + RINOK(res) + // VolumePaths.Add(fs2us(prefix + name)); + + FOR_VECTOR (i, openCallbackSpec->FileNames_WasUsed) + { + if (openCallbackSpec->FileNames_WasUsed[i]) + { + VolumePaths.Add(fs2us(prefix) + openCallbackSpec->FileNames[i]); + VolumesSize += openCallbackSpec->FileSizes[i]; + } + } + // VolumesSize = openCallbackSpec->TotalSize; + return S_OK; +} + +HRESULT CArc::ReOpen(const COpenOptions &op, IArchiveOpenCallback *openCallback_Additional) +{ + ErrorInfo.ClearErrors(); + ErrorInfo.ErrorFormatIndex = -1; + + UInt64 fileSize = 0; + if (op.stream) + { + RINOK(InStream_SeekToBegin(op.stream)) + RINOK(InStream_AtBegin_GetSize(op.stream, fileSize)) + // RINOK(InStream_GetSize_SeekToBegin(op.stream, fileSize)) + } + FileSize = fileSize; + + CMyComPtr stream2; + Int64 globalOffset = GetGlobalOffset(); + if (globalOffset <= 0) + stream2 = op.stream; + else + { + CTailInStream *tailStreamSpec = new CTailInStream; + stream2 = tailStreamSpec; + tailStreamSpec->Stream = op.stream; + tailStreamSpec->Offset = (UInt64)globalOffset; + tailStreamSpec->Init(); + RINOK(tailStreamSpec->SeekToStart()) + } + + // There are archives with embedded STUBs (like ZIP), so we must support signature scanning + // But for another archives we can use 0 here. So the code can be fixed !!! + UInt64 maxStartPosition = kMaxCheckStartPosition; + IArchiveOpenCallback *openCallback = openCallback_Additional; + if (!openCallback) + openCallback = op.callback; + HRESULT res = Archive->Open(stream2, &maxStartPosition, openCallback); + + if (res == S_OK) + { + RINOK(ReadBasicProps(Archive, (UInt64)globalOffset, res)) + ArcStreamOffset = (UInt64)globalOffset; + if (ArcStreamOffset != 0) + InStream = op.stream; + } + return res; +} + +HRESULT CArchiveLink::Open3(COpenOptions &op, IOpenCallbackUI *callbackUI) +{ + HRESULT res = Open2(op, callbackUI); + if (callbackUI) + { + RINOK(callbackUI->Open_Finished()) + } + return res; +} + +HRESULT CArchiveLink::ReOpen(COpenOptions &op) +{ + if (Arcs.Size() > 1) + return E_NOTIMPL; + + CObjectVector inc; + CIntVector excl; + + op.types = &inc; + op.excludedFormats = ! + op.stdInMode = false; + op.stream = NULL; + if (Arcs.Size() == 0) // ??? + return Open2(op, NULL); + + /* if archive is multivolume (unsupported here still) + COpenCallbackImp object will exist after Open stage. */ + COpenCallbackImp *openCallbackSpec = new COpenCallbackImp; + CMyComPtr openCallbackNew = openCallbackSpec; + + openCallbackSpec->Callback = NULL; + openCallbackSpec->ReOpenCallback = op.callback; + { + FString dirPrefix, fileName; + NFile::NDir::GetFullPathAndSplit(us2fs(op.filePath), dirPrefix, fileName); + RINOK(openCallbackSpec->Init2(dirPrefix, fileName)) + } + + + CInFileStream *fileStreamSpec = new CInFileStream; + CMyComPtr stream(fileStreamSpec); + if (!fileStreamSpec->Open(us2fs(op.filePath))) + return GetLastError_noZero_HRESULT(); + op.stream = stream; + + CArc &arc = Arcs[0]; + const HRESULT res = arc.ReOpen(op, openCallbackNew); + + openCallbackSpec->ReOpenCallback = NULL; + + PasswordWasAsked = openCallbackSpec->PasswordWasAsked; + // Password = openCallbackSpec->Password; + + IsOpen = (res == S_OK); + return res; +} + +#ifndef Z7_SFX + +bool ParseComplexSize(const wchar_t *s, UInt64 &result); +bool ParseComplexSize(const wchar_t *s, UInt64 &result) +{ + result = 0; + const wchar_t *end; + UInt64 number = ConvertStringToUInt64(s, &end); + if (end == s) + return false; + if (*end == 0) + { + result = number; + return true; + } + if (end[1] != 0) + return false; + unsigned numBits; + switch (MyCharLower_Ascii(*end)) + { + case 'b': result = number; return true; + case 'k': numBits = 10; break; + case 'm': numBits = 20; break; + case 'g': numBits = 30; break; + case 't': numBits = 40; break; + default: return false; + } + if (number >= ((UInt64)1 << (64 - numBits))) + return false; + result = number << numBits; + return true; +} + +static bool ParseTypeParams(const UString &s, COpenType &type) +{ + if (s[0] == 0) + return true; + if (s[1] == 0) + { + switch ((unsigned)(Byte)s[0]) + { + case 'e': type.EachPos = true; return true; + case 'a': type.CanReturnArc = true; return true; + case 'r': type.Recursive = true; return true; + default: break; + } + return false; + } + if (s[0] == 's') + { + UInt64 result; + if (!ParseComplexSize(s.Ptr(1), result)) + return false; + type.MaxStartOffset = result; + type.MaxStartOffset_Defined = true; + return true; + } + + return false; +} + +static bool ParseType(CCodecs &codecs, const UString &s, COpenType &type) +{ + int pos2 = s.Find(L':'); + + { + UString name; + if (pos2 < 0) + { + name = s; + pos2 = (int)s.Len(); + } + else + { + name = s.Left((unsigned)pos2); + pos2++; + } + + int index = codecs.FindFormatForArchiveType(name); + type.Recursive = false; + + if (index < 0) + { + if (name[0] == '*') + { + if (name[1] != 0) + return false; + } + else if (name[0] == '#') + { + if (name[1] != 0) + return false; + type.CanReturnArc = false; + type.CanReturnParser = true; + } + else if (name.IsEqualTo_Ascii_NoCase("hash")) + { + // type.CanReturnArc = false; + // type.CanReturnParser = false; + type.IsHashType = true; + } + else + return false; + } + + type.FormatIndex = index; + + } + + for (unsigned i = (unsigned)pos2; i < s.Len();) + { + int next = s.Find(L':', i); + if (next < 0) + next = (int)s.Len(); + const UString name = s.Mid(i, (unsigned)next - i); + if (name.IsEmpty()) + return false; + if (!ParseTypeParams(name, type)) + return false; + i = (unsigned)next + 1; + } + + return true; +} + +bool ParseOpenTypes(CCodecs &codecs, const UString &s, CObjectVector &types) +{ + types.Clear(); + bool isHashType = false; + for (unsigned pos = 0; pos < s.Len();) + { + int pos2 = s.Find(L'.', pos); + if (pos2 < 0) + pos2 = (int)s.Len(); + UString name = s.Mid(pos, (unsigned)pos2 - pos); + if (name.IsEmpty()) + return false; + COpenType type; + if (!ParseType(codecs, name, type)) + return false; + if (isHashType) + return false; + if (type.IsHashType) + isHashType = true; + types.Add(type); + pos = (unsigned)pos2 + 1; + } + return true; +} + +/* +bool IsHashType(const CObjectVector &types) +{ + if (types.Size() != 1) + return false; + return types[0].IsHashType; +} +*/ + + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/OpenArchive.h b/src/plugins/7zip/7za/cpp/7zip/UI/Common/OpenArchive.h new file mode 100644 index 00000000..5c3bfe5d --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/OpenArchive.h @@ -0,0 +1,469 @@ +// OpenArchive.h + +#ifndef ZIP7_INC_OPEN_ARCHIVE_H +#define ZIP7_INC_OPEN_ARCHIVE_H + +#include "../../../Windows/PropVariant.h" + +#include "ArchiveOpenCallback.h" +#include "LoadCodecs.h" +#include "Property.h" +#include "DirItem.h" + +#ifndef Z7_SFX + +#define SUPPORT_ALT_STREAMS + +#endif + +HRESULT Archive_GetItemBoolProp(IInArchive *arc, UInt32 index, PROPID propID, bool &result) throw(); +HRESULT Archive_IsItem_Dir(IInArchive *arc, UInt32 index, bool &result) throw(); +HRESULT Archive_IsItem_Aux(IInArchive *arc, UInt32 index, bool &result) throw(); +HRESULT Archive_IsItem_AltStream(IInArchive *arc, UInt32 index, bool &result) throw(); +HRESULT Archive_IsItem_Deleted(IInArchive *arc, UInt32 index, bool &deleted) throw(); + +#ifdef SUPPORT_ALT_STREAMS +int FindAltStreamColon_in_Path(const wchar_t *path); +#endif + +/* +struct COptionalOpenProperties +{ + UString FormatName; + CObjectVector Props; +}; +*/ + +#ifdef Z7_SFX +#define OPEN_PROPS_DECL +#else +#define OPEN_PROPS_DECL const CObjectVector *props; +// #define OPEN_PROPS_DECL , const CObjectVector *props +#endif + +struct COpenSpecFlags +{ + // bool CanReturnFull; + bool CanReturnFrontal; + bool CanReturnTail; + bool CanReturnMid; + + bool CanReturn_NonStart() const { return CanReturnTail || CanReturnMid; } + + COpenSpecFlags(): + // CanReturnFull(true), + CanReturnFrontal(false), + CanReturnTail(false), + CanReturnMid(false) + {} +}; + +struct COpenType +{ + int FormatIndex; + + COpenSpecFlags SpecForcedType; + COpenSpecFlags SpecMainType; + COpenSpecFlags SpecWrongExt; + COpenSpecFlags SpecUnknownExt; + + bool Recursive; + + bool CanReturnArc; + bool CanReturnParser; + bool IsHashType; + bool EachPos; + + // bool SkipSfxStub; + // bool ExeAsUnknown; + + bool ZerosTailIsAllowed; + + bool MaxStartOffset_Defined; + UInt64 MaxStartOffset; + + const COpenSpecFlags &GetSpec(bool isForced, bool isMain, bool isUnknown) const + { + return isForced ? SpecForcedType : (isMain ? SpecMainType : (isUnknown ? SpecUnknownExt : SpecWrongExt)); + } + + COpenType(): + FormatIndex(-1), + Recursive(true), + CanReturnArc(true), + CanReturnParser(false), + IsHashType(false), + EachPos(false), + // SkipSfxStub(true), + // ExeAsUnknown(true), + ZerosTailIsAllowed(false), + MaxStartOffset_Defined(false), + MaxStartOffset(0) + { + SpecForcedType.CanReturnFrontal = true; + SpecForcedType.CanReturnTail = true; + SpecForcedType.CanReturnMid = true; + + SpecMainType.CanReturnFrontal = true; + + SpecUnknownExt.CanReturnTail = true; // for sfx + SpecUnknownExt.CanReturnMid = true; + SpecUnknownExt.CanReturnFrontal = true; // for alt streams of sfx with pad + + // ZerosTailIsAllowed = true; + } +}; + +struct COpenOptions +{ + CCodecs *codecs; + COpenType openType; + const CObjectVector *types; + const CIntVector *excludedFormats; + + IInStream *stream; + ISequentialInStream *seqStream; + IArchiveOpenCallback *callback; + COpenCallbackImp *callbackSpec; // it's used for SFX only + OPEN_PROPS_DECL + // bool openOnlySpecifiedByExtension, + + bool stdInMode; + UString filePath; + + COpenOptions(): + codecs(NULL), + types(NULL), + excludedFormats(NULL), + stream(NULL), + seqStream(NULL), + callback(NULL), + callbackSpec(NULL), + stdInMode(false) + {} + +}; + +UInt32 GetOpenArcErrorFlags(const NWindows::NCOM::CPropVariant &prop, bool *isDefinedProp = NULL); + +struct CArcErrorInfo +{ + bool ThereIsTail; + bool UnexpecedEnd; + bool IgnoreTail; // all are zeros + // bool NonZerosTail; + bool ErrorFlags_Defined; + UInt32 ErrorFlags; + UInt32 WarningFlags; + int ErrorFormatIndex; // - 1 means no Error. + // if FormatIndex == ErrorFormatIndex, the archive is open with offset + UInt64 TailSize; + + /* if CArc is Open OK with some format: + - ErrorFormatIndex shows error format index, if extension is incorrect + - other variables show message and warnings of archive that is open */ + + UString ErrorMessage; + UString WarningMessage; + + // call IsArc_After_NonOpen only if Open returns S_FALSE + bool IsArc_After_NonOpen() const + { + return (ErrorFlags_Defined && (ErrorFlags & kpv_ErrorFlags_IsNotArc) == 0); + } + + + CArcErrorInfo(): + ThereIsTail(false), + UnexpecedEnd(false), + IgnoreTail(false), + // NonZerosTail(false), + ErrorFlags_Defined(false), + ErrorFlags(0), + WarningFlags(0), + ErrorFormatIndex(-1), + TailSize(0) + {} + + void ClearErrors(); + + void ClearErrors_Full() + { + ErrorFormatIndex = -1; + ClearErrors(); + } + + bool IsThereErrorOrWarning() const + { + return ErrorFlags != 0 + || WarningFlags != 0 + || NeedTailWarning() + || UnexpecedEnd + || !ErrorMessage.IsEmpty() + || !WarningMessage.IsEmpty(); + } + + bool AreThereErrors() const { return ErrorFlags != 0 || UnexpecedEnd; } + bool AreThereWarnings() const { return WarningFlags != 0 || NeedTailWarning(); } + + bool NeedTailWarning() const { return !IgnoreTail && ThereIsTail; } + + UInt32 GetWarningFlags() const + { + UInt32 a = WarningFlags; + if (NeedTailWarning() && (ErrorFlags & kpv_ErrorFlags_DataAfterEnd) == 0) + a |= kpv_ErrorFlags_DataAfterEnd; + return a; + } + + UInt32 GetErrorFlags() const + { + UInt32 a = ErrorFlags; + if (UnexpecedEnd) + a |= kpv_ErrorFlags_UnexpectedEnd; + return a; + } +}; + +struct CReadArcItem +{ + UString Path; // Path from root (including alt stream name, if alt stream) + UStringVector PathParts; // without altStream name, path from root or from _baseParentFolder, if _use_baseParentFolder_mode + + #ifdef SUPPORT_ALT_STREAMS + UString MainPath; + /* MainPath = Path for non-AltStream, + MainPath = Path of parent, if there is parent for AltStream. */ + UString AltStreamName; + bool IsAltStream; + bool WriteToAltStreamIfColon; + #endif + + bool IsDir; + bool MainIsDir; + UInt32 ParentIndex; // use it, if IsAltStream + + #ifndef Z7_SFX + bool _use_baseParentFolder_mode; + int _baseParentFolder; + #endif + + CReadArcItem() + { + #ifdef SUPPORT_ALT_STREAMS + WriteToAltStreamIfColon = false; + #endif + + #ifndef Z7_SFX + _use_baseParentFolder_mode = false; + _baseParentFolder = -1; + #endif + } +}; + + + + +class CArc +{ + HRESULT PrepareToOpen(const COpenOptions &op, unsigned formatIndex, CMyComPtr &archive); + HRESULT CheckZerosTail(const COpenOptions &op, UInt64 offset); + HRESULT OpenStream2(const COpenOptions &options); + + #ifndef Z7_SFX + // parts.Back() can contain alt stream name "nams:AltName" + HRESULT GetItem_PathToParent(UInt32 index, UInt32 parent, UStringVector &parts) const; + #endif + +public: + CMyComPtr Archive; + CMyComPtr InStream; + // we use InStream in 2 cases (ArcStreamOffset != 0): + // 1) if we use additional cache stream + // 2) we reopen sfx archive with CTailInStream + + CMyComPtr GetRawProps; + CMyComPtr GetRootProps; + + bool IsParseArc; + + bool IsTree; + bool IsReadOnly; + + bool Ask_Deleted; + bool Ask_AltStream; + bool Ask_Aux; + bool Ask_INode; + + bool IgnoreSplit; // don't try split handler + + UString Path; + UString filePath; + UString DefaultName; + int FormatIndex; // -1 means Parser + UInt32 SubfileIndex; // (UInt32)(Int32)-1; means no subfile + + // CFiTime MTime; + // bool MTime_Defined; + CArcTime MTime; + + Int64 Offset; // it's offset of start of archive inside stream that is open by Archive Handler + UInt64 PhySize; + // UInt64 OkPhySize; + bool PhySize_Defined; + // bool OkPhySize_Defined; + UInt64 FileSize; + UInt64 AvailPhySize; // PhySize, but it's reduced if exceed end of file + + CArcErrorInfo ErrorInfo; // for OK archives + CArcErrorInfo NonOpen_ErrorInfo; // ErrorInfo for mainArchive (false OPEN) + + UInt64 GetEstmatedPhySize() const { return PhySize_Defined ? PhySize : FileSize; } + + UInt64 ArcStreamOffset; // offset of stream that is open by Archive Handler + Int64 GetGlobalOffset() const { return (Int64)ArcStreamOffset + Offset; } // it's global offset of archive + + // AString ErrorFlagsText; + + // void Set_ErrorFlagsText(); + + CArc(): + // MTime_Defined(false), + IsTree(false), + IsReadOnly(false), + Ask_Deleted(false), + Ask_AltStream(false), + Ask_Aux(false), + Ask_INode(false), + IgnoreSplit(false) + {} + + HRESULT ReadBasicProps(IInArchive *archive, UInt64 startPos, HRESULT openRes); + + HRESULT Close() + { + InStream.Release(); + return Archive->Close(); + } + + HRESULT GetItem_Path(UInt32 index, UString &result) const; + HRESULT GetItem_DefaultPath(UInt32 index, UString &result) const; + + // GetItemPath2 adds [DELETED] dir prefix for deleted items. + HRESULT GetItem_Path2(UInt32 index, UString &result) const; + + HRESULT GetItem(UInt32 index, CReadArcItem &item) const; + + HRESULT GetItem_Size(UInt32 index, UInt64 &size, bool &defined) const; + + /* if (GetProperty() returns vt==VT_EMPTY), this function sets + timestamp from archive file timestamp (MTime). + So (at) will be set in most cases (at.Def == true) + if (at.Prec == 0) + { + it means that (Prec == 0) was returned for (kpidMTime), + and no value was returned for (kpidTimeType). + it can mean Windows precision or unknown precision. + } + */ + HRESULT GetItem_MTime(UInt32 index, CArcTime &at) const; + + HRESULT IsItem_Anti(UInt32 index, bool &result) const + { return Archive_GetItemBoolProp(Archive, index, kpidIsAnti, result); } + + + HRESULT OpenStream(const COpenOptions &options); + HRESULT OpenStreamOrFile(COpenOptions &options); + + HRESULT ReOpen(const COpenOptions &options, IArchiveOpenCallback *openCallback_Additional); + + HRESULT CreateNewTailStream(CMyComPtr &stream); + + bool IsHashHandler(const COpenOptions &options) const + { + if (FormatIndex < 0) + return false; + return options.codecs->Formats[(unsigned)FormatIndex].Flags_HashHandler(); + } +}; + +struct CArchiveLink +{ + CObjectVector Arcs; + UStringVector VolumePaths; + UInt64 VolumesSize; + bool IsOpen; + + bool PasswordWasAsked; + // UString Password; + + // int NonOpenErrorFormatIndex; // - 1 means no Error. + UString NonOpen_ArcPath; + + CArcErrorInfo NonOpen_ErrorInfo; + + // UString ErrorsText; + // void Set_ErrorsText(); + + CArchiveLink(): + VolumesSize(0), + IsOpen(false), + PasswordWasAsked(false) + {} + + void KeepModeForNextOpen(); + HRESULT Close(); + void Release(); + ~CArchiveLink() { Release(); } + + const CArc *GetArc() const { return &Arcs.Back(); } + IInArchive *GetArchive() const { return Arcs.Back().Archive; } + IArchiveGetRawProps *GetArchiveGetRawProps() const { return Arcs.Back().GetRawProps; } + IArchiveGetRootProps *GetArchiveGetRootProps() const { return Arcs.Back().GetRootProps; } + + /* + Open() opens archive and COpenOptions::callback + Open2() uses COpenCallbackImp that implements Volumes and password callback + Open3() calls Open2() and callbackUI->Open_Finished(); + Open_Strict() returns S_FALSE also in case, if there is non-open expected nested archive. + */ + + HRESULT Open(COpenOptions &options); + HRESULT Open2(COpenOptions &options, IOpenCallbackUI *callbackUI); + HRESULT Open3(COpenOptions &options, IOpenCallbackUI *callbackUI); + + HRESULT Open_Strict(COpenOptions &options, IOpenCallbackUI *callbackUI) + { + HRESULT result = Open3(options, callbackUI); + if (result == S_OK && NonOpen_ErrorInfo.ErrorFormatIndex >= 0) + result = S_FALSE; + return result; + } + + HRESULT ReOpen(COpenOptions &options); +}; + +bool ParseOpenTypes(CCodecs &codecs, const UString &s, CObjectVector &types); + +// bool IsHashType(const CObjectVector &types); + + +struct CDirPathSortPair +{ + unsigned Len; + unsigned Index; + + void SetNumSlashes(const FChar *s); + + int Compare(const CDirPathSortPair &a) const + { + // We need sorting order where parent items will be after child items + if (Len < a.Len) return 1; + if (Len > a.Len) return -1; + if (Index < a.Index) return -1; + if (Index > a.Index) return 1; + return 0; + } +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/PropIDUtils.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Common/PropIDUtils.cpp new file mode 100644 index 00000000..d73680b1 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/PropIDUtils.cpp @@ -0,0 +1,745 @@ +// PropIDUtils.cpp + +#include "StdAfx.h" + +#include "../../../../C/CpuArch.h" + +#include "../../../Common/IntToString.h" +#include "../../../Common/StringConvert.h" + +#include "../../../Windows/FileIO.h" +#include "../../../Windows/PropVariantConv.h" + +#include "../../PropID.h" + +#include "PropIDUtils.h" + +#ifndef Z7_SFX +#define Get16(x) GetUi16(x) +#define Get32(x) GetUi32(x) +#endif + +using namespace NWindows; + +static const unsigned kNumWinAtrribFlags = 30; +static const char g_WinAttribChars[kNumWinAtrribFlags + 1] = "RHS8DAdNTsLCOIEVvX.PU.M......B"; + +/* +FILE_ATTRIBUTE_ + +0 READONLY +1 HIDDEN +2 SYSTEM +3 (Volume label - obsolete) +4 DIRECTORY +5 ARCHIVE +6 DEVICE +7 NORMAL +8 TEMPORARY +9 SPARSE_FILE +10 REPARSE_POINT +11 COMPRESSED +12 OFFLINE +13 NOT_CONTENT_INDEXED (I - Win10 attrib/Explorer) +14 ENCRYPTED +15 INTEGRITY_STREAM (V - ReFS Win8/Win2012) +16 VIRTUAL (reserved) +17 NO_SCRUB_DATA (X - ReFS Win8/Win2012 attrib) +18 RECALL_ON_OPEN or EA +19 PINNED +20 UNPINNED +21 STRICTLY_SEQUENTIAL (10.0.16267) +22 RECALL_ON_DATA_ACCESS +29 STRICTLY_SEQUENTIAL (10.0.17134+) (SMR Blob) +*/ + + +static const char kPosixTypes[16] = { '0', 'p', 'c', '3', 'd', '5', 'b', '7', '-', '9', 'l', 'B', 's', 'D', 'E', 'F' }; +#define MY_ATTR_CHAR(a, n, c) (((a) & (1 << (n))) ? c : '-') + +static void ConvertPosixAttribToString(char *s, UInt32 a) throw() +{ + s[0] = kPosixTypes[(a >> 12) & 0xF]; + for (int i = 6; i >= 0; i -= 3) + { + s[7 - i] = MY_ATTR_CHAR(a, i + 2, 'r'); + s[8 - i] = MY_ATTR_CHAR(a, i + 1, 'w'); + s[9 - i] = MY_ATTR_CHAR(a, i + 0, 'x'); + } + if ((a & 0x800) != 0) s[3] = ((a & (1 << 6)) ? 's' : 'S'); // S_ISUID + if ((a & 0x400) != 0) s[6] = ((a & (1 << 3)) ? 's' : 'S'); // S_ISGID + if ((a & 0x200) != 0) s[9] = ((a & (1 << 0)) ? 't' : 'T'); // S_ISVTX + s[10] = 0; + + a &= ~(UInt32)0xFFFF; + if (a != 0) + { + s[10] = ' '; + ConvertUInt32ToHex8Digits(a, s + 11); + } +} + + +void ConvertWinAttribToString(char *s, UInt32 wa) throw() +{ + /* + some programs store posix attributes in high 16 bits. + p7zip - stores additional 0x8000 flag marker. + macos - stores additional 0x4000 flag marker. + info-zip - no additional marker. + But this code works with Attrib from internal 7zip code. + So we expect that 0x8000 marker is set, if there are posix attributes. + (DT_UNKNOWN == 0) type in high bits is possible in some case for linux files. + 0x8000 flag is possible also in ReFS (Windows)? + */ + + const bool isPosix = ( + (wa & 0x8000) != 0 // FILE_ATTRIBUTE_UNIX_EXTENSION; + // && (wa & 0xFFFF0000u) != 0 + ); + + UInt32 posix = 0; + if (isPosix) + { + posix = wa >> 16; + if ((wa & 0xF0000000u) != 0) + wa &= (UInt32)0x3FFF; + } + + for (unsigned i = 0; i < kNumWinAtrribFlags; i++) + { + const UInt32 flag = (UInt32)1 << i; + if (wa & flag) + { + const char c = g_WinAttribChars[i]; + if (c != '.') + { + wa &= ~flag; + // if (i != 7) // we can disable N (NORMAL) printing + *s++ = c; + } + } + } + + if (wa != 0) + { + *s++ = ' '; + ConvertUInt32ToHex8Digits(wa, s); + s += strlen(s); + } + + *s = 0; + + if (isPosix) + { + *s++ = ' '; + ConvertPosixAttribToString(s, posix); + } +} + + +void ConvertPropertyToShortString2(char *dest, const PROPVARIANT &prop, PROPID propID, int level) throw() +{ + *dest = 0; + + if (prop.vt == VT_FILETIME) + { + const FILETIME &ft = prop.filetime; + unsigned ns100 = 0; + int numDigits = kTimestampPrintLevel_NTFS; + const unsigned prec = prop.wReserved1; + const unsigned ns100_Temp = prop.wReserved2; + if (prec != 0 + && prec <= k_PropVar_TimePrec_1ns + && ns100_Temp < 100 + && prop.wReserved3 == 0) + { + ns100 = ns100_Temp; + if (prec == k_PropVar_TimePrec_Unix || + prec == k_PropVar_TimePrec_DOS) + numDigits = 0; + else if (prec == k_PropVar_TimePrec_HighPrec) + numDigits = 9; + else + { + numDigits = (int)prec - (int)k_PropVar_TimePrec_Base; + if ( + // numDigits < kTimestampPrintLevel_DAY // for debuf + numDigits < kTimestampPrintLevel_SEC + ) + + numDigits = kTimestampPrintLevel_NTFS; + } + } + if (ft.dwHighDateTime == 0 && ft.dwLowDateTime == 0 && ns100 == 0) + return; + if (level > numDigits) + level = numDigits; + ConvertUtcFileTimeToString2(ft, ns100, dest, level); + return; + } + + switch (propID) + { + case kpidCRC: + { + if (prop.vt != VT_UI4) + break; + ConvertUInt32ToHex8Digits(prop.ulVal, dest); + return; + } + case kpidAttrib: + { + if (prop.vt != VT_UI4) + break; + const UInt32 a = prop.ulVal; + + /* + if ((a & 0x8000) && (a & 0x7FFF) == 0) + ConvertPosixAttribToString(dest, a >> 16); + else + */ + ConvertWinAttribToString(dest, a); + return; + } + case kpidPosixAttrib: + { + if (prop.vt != VT_UI4) + break; + ConvertPosixAttribToString(dest, prop.ulVal); + return; + } + case kpidINode: + { + if (prop.vt != VT_UI8) + break; + ConvertUInt32ToString((UInt32)(prop.uhVal.QuadPart >> 48), dest); + dest += strlen(dest); + *dest++ = '-'; + const UInt64 low = prop.uhVal.QuadPart & (((UInt64)1 << 48) - 1); + ConvertUInt64ToString(low, dest); + return; + } + case kpidVa: + { + UInt64 v = 0; + if (prop.vt == VT_UI4) + v = prop.ulVal; + else if (prop.vt == VT_UI8) + v = (UInt64)prop.uhVal.QuadPart; + else + break; + dest[0] = '0'; + dest[1] = 'x'; + ConvertUInt64ToHex(v, dest + 2); + return; + } + + /* + case kpidDevice: + { + UInt64 v = 0; + if (prop.vt == VT_UI4) + v = prop.ulVal; + else if (prop.vt == VT_UI8) + v = (UInt64)prop.uhVal.QuadPart; + else + break; + ConvertUInt32ToString(MY_dev_major(v), dest); + dest += strlen(dest); + *dest++ = ','; + ConvertUInt32ToString(MY_dev_minor(v), dest); + return; + } + */ + default: break; + } + + ConvertPropVariantToShortString(prop, dest); +} + +void ConvertPropertyToString2(UString &dest, const PROPVARIANT &prop, PROPID propID, int level) +{ + if (prop.vt == VT_BSTR) + { + dest.SetFromBstr(prop.bstrVal); + return; + } + char temp[64]; + ConvertPropertyToShortString2(temp, prop, propID, level); + dest = temp; +} + +#ifndef Z7_SFX + +static inline void AddHexToString(AString &res, unsigned v) +{ + res.Add_Char((char)GET_HEX_CHAR_UPPER(v >> 4)); + res.Add_Char((char)GET_HEX_CHAR_UPPER(v & 15)); +} + +/* +static AString Data_To_Hex(const Byte *data, size_t size) +{ + AString s; + for (size_t i = 0; i < size; i++) + AddHexToString(s, data[i]); + return s; +} +*/ + +static const char * const sidNames[] = +{ + "0" + , "Dialup" + , "Network" + , "Batch" + , "Interactive" + , "Logon" // S-1-5-5-X-Y + , "Service" + , "Anonymous" + , "Proxy" + , "EnterpriseDC" + , "Self" + , "AuthenticatedUsers" + , "RestrictedCode" + , "TerminalServer" + , "RemoteInteractiveLogon" + , "ThisOrganization" + , "16" + , "IUserIIS" + , "LocalSystem" + , "LocalService" + , "NetworkService" + , "Domains" +}; + +struct CSecID2Name +{ + UInt32 n; + const char *sz; +}; + +static int FindPairIndex(const CSecID2Name * pairs, unsigned num, UInt32 id) +{ + for (unsigned i = 0; i < num; i++) + if (pairs[i].n == id) + return (int)i; + return -1; +} + +static const CSecID2Name sid_32_Names[] = +{ + { 544, "Administrators" }, + { 545, "Users" }, + { 546, "Guests" }, + { 547, "PowerUsers" }, + { 548, "AccountOperators" }, + { 549, "ServerOperators" }, + { 550, "PrintOperators" }, + { 551, "BackupOperators" }, + { 552, "Replicators" }, + { 553, "Backup Operators" }, + { 554, "PreWindows2000CompatibleAccess" }, + { 555, "RemoteDesktopUsers" }, + { 556, "NetworkConfigurationOperators" }, + { 557, "IncomingForestTrustBuilders" }, + { 558, "PerformanceMonitorUsers" }, + { 559, "PerformanceLogUsers" }, + { 560, "WindowsAuthorizationAccessGroup" }, + { 561, "TerminalServerLicenseServers" }, + { 562, "DistributedCOMUsers" }, + { 569, "CryptographicOperators" }, + { 573, "EventLogReaders" }, + { 574, "CertificateServiceDCOMAccess" } +}; + +static const CSecID2Name sid_21_Names[] = +{ + { 500, "Administrator" }, + { 501, "Guest" }, + { 502, "KRBTGT" }, + { 512, "DomainAdmins" }, + { 513, "DomainUsers" }, + { 515, "DomainComputers" }, + { 516, "DomainControllers" }, + { 517, "CertPublishers" }, + { 518, "SchemaAdmins" }, + { 519, "EnterpriseAdmins" }, + { 520, "GroupPolicyCreatorOwners" }, + { 553, "RASandIASServers" }, + { 553, "RASandIASServers" }, + { 571, "AllowedRODCPasswordReplicationGroup" }, + { 572, "DeniedRODCPasswordReplicationGroup" } +}; + +struct CServicesToName +{ + UInt32 n[5]; + const char *sz; +}; + +static const CServicesToName services_to_name[] = +{ + { { 0x38FB89B5, 0xCBC28419, 0x6D236C5C, 0x6E770057, 0x876402C0 } , "TrustedInstaller" } +}; + +static void ParseSid(AString &s, const Byte *p, size_t lim /* , unsigned &sidSize */) +{ + // sidSize = 0; + if (lim < 8) + { + s += "ERROR"; + return; + } + if (p[0] != 1) // rev + { + s += "UNSUPPORTED"; + return; + } + const unsigned num = p[1]; + const unsigned sidSize_Loc = 8 + num * 4; + if (sidSize_Loc > lim) + { + s += "ERROR"; + return; + } + // sidSize = sidSize_Loc; + const UInt32 authority = GetBe32(p + 4); + + if (p[2] == 0 && p[3] == 0 && authority == 5 && num >= 1) + { + const UInt32 v0 = Get32(p + 8); + if (v0 < Z7_ARRAY_SIZE(sidNames)) + { + s += sidNames[v0]; + return; + } + if (v0 == 32 && num == 2) + { + const UInt32 v1 = Get32(p + 12); + const int index = FindPairIndex(sid_32_Names, Z7_ARRAY_SIZE(sid_32_Names), v1); + if (index >= 0) + { + s += sid_32_Names[(unsigned)index].sz; + return; + } + } + if (v0 == 21 && num == 5) + { + UInt32 v4 = Get32(p + 8 + 4 * 4); + const int index = FindPairIndex(sid_21_Names, Z7_ARRAY_SIZE(sid_21_Names), v4); + if (index >= 0) + { + s += sid_21_Names[(unsigned)index].sz; + return; + } + } + if (v0 == 80 && num == 6) + { + for (unsigned i = 0; i < Z7_ARRAY_SIZE(services_to_name); i++) + { + const CServicesToName &sn = services_to_name[i]; + int j; + for (j = 0; j < 5 && sn.n[j] == Get32(p + 8 + 4 + j * 4); j++); + if (j == 5) + { + s += sn.sz; + return; + } + } + } + } + + s += "S-1-"; + if (p[2] == 0 && p[3] == 0) + s.Add_UInt32(authority); + else + { + s += "0x"; + for (int i = 2; i < 8; i++) + AddHexToString(s, p[i]); + } + for (UInt32 i = 0; i < num; i++) + { + s.Add_Minus(); + s.Add_UInt32(Get32(p + 8 + i * 4)); + } +} + +static void ParseOwner(AString &s, const Byte *p, size_t size, UInt32 pos) +{ + if (pos > size) + { + s += "ERROR"; + return; + } + // unsigned sidSize = 0; + ParseSid(s, p + pos, size - pos /* , sidSize */); +} + +static void ParseAcl(AString &s, const Byte *p, size_t size, const char *strName, UInt32 flags, UInt32 offset) +{ + const unsigned control = Get16(p + 2); + if ((flags & control) == 0) + return; + const UInt32 pos = Get32(p + offset); + s.Add_Space(); + s += strName; + if (pos >= size) + return; + p += pos; + size -= (size_t)pos; + if (size < 8) + return; + if (Get16(p) != 2) // revision + return; + const UInt32 num = Get32(p + 4); + s.Add_UInt32(num); + + /* + UInt32 aclSize = Get16(p + 2); + if (num >= (1 << 16)) + return; + if (aclSize > size) + return; + size = aclSize; + size -= 8; + p += 8; + for (UInt32 i = 0 ; i < num; i++) + { + if (size <= 8) + return; + // Byte type = p[0]; + // Byte flags = p[1]; + // UInt32 aceSize = Get16(p + 2); + // UInt32 mask = Get32(p + 4); + p += 8; + size -= 8; + + UInt32 sidSize = 0; + s.Add_Space(); + ParseSid(s, p, size, sidSize); + if (sidSize == 0) + return; + p += sidSize; + size -= sidSize; + } + + // the tail can contain zeros. So (size != 0) is not ERROR + // if (size != 0) s += " ERROR"; + */ +} + +/* +#define MY_SE_OWNER_DEFAULTED (0x0001) +#define MY_SE_GROUP_DEFAULTED (0x0002) +*/ +#define MY_SE_DACL_PRESENT (0x0004) +/* +#define MY_SE_DACL_DEFAULTED (0x0008) +*/ +#define MY_SE_SACL_PRESENT (0x0010) +/* +#define MY_SE_SACL_DEFAULTED (0x0020) +#define MY_SE_DACL_AUTO_INHERIT_REQ (0x0100) +#define MY_SE_SACL_AUTO_INHERIT_REQ (0x0200) +#define MY_SE_DACL_AUTO_INHERITED (0x0400) +#define MY_SE_SACL_AUTO_INHERITED (0x0800) +#define MY_SE_DACL_PROTECTED (0x1000) +#define MY_SE_SACL_PROTECTED (0x2000) +#define MY_SE_RM_CONTROL_VALID (0x4000) +#define MY_SE_SELF_RELATIVE (0x8000) +*/ + +void ConvertNtSecureToString(const Byte *data, size_t size, AString &s) +{ + s.Empty(); + if (size < 20 || size > (1 << 18)) + { + s += "ERROR"; + return; + } + if (Get16(data) != 1) // revision + { + s += "UNSUPPORTED"; + return; + } + ParseOwner(s, data, size, Get32(data + 4)); + s.Add_Space(); + ParseOwner(s, data, size, Get32(data + 8)); + ParseAcl(s, data, size, "s:", MY_SE_SACL_PRESENT, 12); + ParseAcl(s, data, size, "d:", MY_SE_DACL_PRESENT, 16); + s.Add_Space(); + s.Add_UInt32((UInt32)size); + // s.Add_LF(); + // s += Data_To_Hex(data, size); +} + +#ifdef _WIN32 + +static bool CheckSid(const Byte *data, size_t size, UInt32 pos) throw() +{ + if (pos >= size) + return false; + size -= pos; + if (size < 8) + return false; + if (data[pos] != 1) // rev + return false; + const unsigned num = data[pos + 1]; + return (8 + num * 4 <= size); +} + +static bool CheckAcl(const Byte *p, size_t size, UInt32 flags, size_t offset) throw() +{ + const unsigned control = Get16(p + 2); + if ((flags & control) == 0) + return true; + const UInt32 pos = Get32(p + offset); + if (pos >= size) + return false; + p += pos; + size -= pos; + if (size < 8) + return false; + const unsigned aclSize = Get16(p + 2); + return (aclSize <= size); +} + +bool CheckNtSecure(const Byte *data, size_t size) throw() +{ + if (size < 20) + return false; + if (Get16(data) != 1) // revision + return true; // windows function can handle such error, so we allow it + if (size > (1 << 18)) + return false; + if (!CheckSid(data, size, Get32(data + 4))) return false; + if (!CheckSid(data, size, Get32(data + 8))) return false; + if (!CheckAcl(data, size, MY_SE_SACL_PRESENT, 12)) return false; + if (!CheckAcl(data, size, MY_SE_DACL_PRESENT, 16)) return false; + return true; +} + +#endif + + + +// IO_REPARSE_TAG_* + +static const CSecID2Name k_ReparseTags[] = +{ + { 0xA0000003, "MOUNT_POINT" }, + { 0xC0000004, "HSM" }, + { 0x80000005, "DRIVE_EXTENDER" }, + { 0x80000006, "HSM2" }, + { 0x80000007, "SIS" }, + { 0x80000008, "WIM" }, + { 0x80000009, "CSV" }, + { 0x8000000A, "DFS" }, + { 0x8000000B, "FILTER_MANAGER" }, + { 0xA000000C, "SYMLINK" }, + { 0xA0000010, "IIS_CACHE" }, + { 0x80000012, "DFSR" }, + { 0x80000013, "DEDUP" }, + { 0xC0000014, "APPXSTRM" }, + { 0x80000014, "NFS" }, + { 0x80000015, "FILE_PLACEHOLDER" }, + { 0x80000016, "DFM" }, + { 0x80000017, "WOF" }, + { 0x80000018, "WCI" }, + { 0x8000001B, "APPEXECLINK" }, + { 0xA000001D, "LX_SYMLINK" }, + { 0x80000023, "AF_UNIX" }, + { 0x80000024, "LX_FIFO" }, + { 0x80000025, "LX_CHR" }, + { 0x80000026, "LX_BLK" } +}; + +bool ConvertNtReparseToString(const Byte *data, size_t size, UString &s) +{ + s.Empty(); + NFile::CReparseAttr attr; + + if (attr.Parse(data, size)) + { + if (attr.IsSymLink_WSL()) + { + s += "WSL: "; + s += attr.GetPath(); + } + else + { + if (!attr.IsSymLink_Win()) + s += "Junction: "; + s += attr.GetPath(); + if (s.IsEmpty()) + s += "Link: "; + if (!attr.IsOkNamePair()) + { + s += " : "; + s += attr.PrintName; + } + } + if (attr.MinorError) + s += " : MINOR_ERROR"; + return true; + // s.Add_Space(); // for debug + } + + if (size < 8) + return false; + const UInt32 tag = Get32(data); + const UInt32 len = Get16(data + 4); + if (len + 8 > size) + return false; + if (Get16(data + 6) != 0) // padding + return false; + + /* + #define my_IO_REPARSE_TAG_DEDUP (0x80000013L) + if (tag == my_IO_REPARSE_TAG_DEDUP) + { + } + */ + + { + const int index = FindPairIndex(k_ReparseTags, Z7_ARRAY_SIZE(k_ReparseTags), tag); + if (index >= 0) + s += k_ReparseTags[(unsigned)index].sz; + else + { + s += "REPARSE:"; + char hex[16]; + ConvertUInt32ToHex8Digits(tag, hex); + s += hex; + } + } + + s.Add_Colon(); + s.Add_UInt32(len); + + if (len != 0) + { + s.Add_Space(); + + data += 8; + + for (UInt32 i = 0; i < len; i++) + { + if (i >= 16) + { + s += "..."; + break; + } + const unsigned b = data[i]; + s.Add_Char((char)GET_HEX_CHAR_UPPER(b >> 4)); + s.Add_Char((char)GET_HEX_CHAR_UPPER(b & 15)); + } + } + + return true; +} + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/PropIDUtils.h b/src/plugins/7zip/7za/cpp/7zip/UI/Common/PropIDUtils.h new file mode 100644 index 00000000..dc22793d --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/PropIDUtils.h @@ -0,0 +1,18 @@ +// PropIDUtils.h + +#ifndef ZIP7_INC_PROPID_UTILS_H +#define ZIP7_INC_PROPID_UTILS_H + +#include "../../../Common/MyString.h" + +// provide at least 64 bytes for buffer including zero-end +void ConvertPropertyToShortString2(char *dest, const PROPVARIANT &propVariant, PROPID propID, int level = 0) throw(); +void ConvertPropertyToString2(UString &dest, const PROPVARIANT &propVariant, PROPID propID, int level = 0); + +bool ConvertNtReparseToString(const Byte *data, size_t size, UString &s); +void ConvertNtSecureToString(const Byte *data, size_t size, AString &s); +bool CheckNtSecure(const Byte *data, size_t size) throw(); + +void ConvertWinAttribToString(char *s, UInt32 wa) throw(); + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/Property.h b/src/plugins/7zip/7za/cpp/7zip/UI/Common/Property.h new file mode 100644 index 00000000..04628098 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/Property.h @@ -0,0 +1,14 @@ +// Property.h + +#ifndef ZIP7_INC_7Z_PROPERTY_H +#define ZIP7_INC_7Z_PROPERTY_H + +#include "../../../Common/MyString.h" + +struct CProperty +{ + UString Name; + UString Value; +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/SetProperties.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Common/SetProperties.cpp new file mode 100644 index 00000000..5a05beb5 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/SetProperties.cpp @@ -0,0 +1,88 @@ +// SetProperties.cpp + +#include "StdAfx.h" + +#include "../../../Common/MyCom.h" +#include "../../../Common/MyString.h" +#include "../../../Common/StringToInt.h" + +#include "../../../Windows/PropVariant.h" + +#include "../../Archive/IArchive.h" + +#include "SetProperties.h" + +using namespace NWindows; +using namespace NCOM; + +static void ParseNumberString(const UString &s, NCOM::CPropVariant &prop) +{ + const wchar_t *end; + const UInt64 result = ConvertStringToUInt64(s, &end); + if (*end != 0 || s.IsEmpty()) + prop = s; + else if (result <= (UInt32)0xFFFFFFFF) + prop = (UInt32)result; + else + prop = result; +} + + +struct CPropPropetiesVector +{ + CPropVariant *values; + CPropPropetiesVector(unsigned num) + { + values = new CPropVariant[num]; + } + ~CPropPropetiesVector() + { + delete []values; + } +}; + + +HRESULT SetProperties(IUnknown *unknown, const CObjectVector &properties) +{ + if (properties.IsEmpty()) + return S_OK; + Z7_DECL_CMyComPtr_QI_FROM( + ISetProperties, + setProperties, unknown) + if (!setProperties) + return S_OK; + + UStringVector realNames; + CPropPropetiesVector values(properties.Size()); + { + unsigned i; + for (i = 0; i < properties.Size(); i++) + { + const CProperty &property = properties[i]; + NCOM::CPropVariant propVariant; + UString name = property.Name; + if (property.Value.IsEmpty()) + { + if (!name.IsEmpty()) + { + const wchar_t c = name.Back(); + if (c == L'-') + propVariant = false; + else if (c == L'+') + propVariant = true; + if (propVariant.vt != VT_EMPTY) + name.DeleteBack(); + } + } + else + ParseNumberString(property.Value, propVariant); + realNames.Add(name); + values.values[i] = propVariant; + } + CRecordVector names; + for (i = 0; i < realNames.Size(); i++) + names.Add((const wchar_t *)realNames[i]); + + return setProperties->SetProperties(names.ConstData(), values.values, names.Size()); + } +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/SetProperties.h b/src/plugins/7zip/7za/cpp/7zip/UI/Common/SetProperties.h new file mode 100644 index 00000000..0676c45d --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/SetProperties.h @@ -0,0 +1,10 @@ +// SetProperties.h + +#ifndef ZIP7_INC_SETPROPERTIES_H +#define ZIP7_INC_SETPROPERTIES_H + +#include "Property.h" + +HRESULT SetProperties(IUnknown *unknown, const CObjectVector &properties); + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/SortUtils.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Common/SortUtils.cpp new file mode 100644 index 00000000..5f29249b --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/SortUtils.cpp @@ -0,0 +1,25 @@ +// SortUtils.cpp + +#include "StdAfx.h" + +#include "../../../Common/Wildcard.h" + +#include "SortUtils.h" + +static int CompareStrings(const unsigned *p1, const unsigned *p2, void *param) +{ + const UStringVector &strings = *(const UStringVector *)param; + return CompareFileNames(strings[*p1], strings[*p2]); +} + +void SortFileNames(const UStringVector &strings, CUIntVector &indices) +{ + const unsigned numItems = strings.Size(); + indices.ClearAndSetSize(numItems); + if (numItems == 0) + return; + unsigned *vals = &indices[0]; + for (unsigned i = 0; i < numItems; i++) + vals[i] = i; + indices.Sort(CompareStrings, (void *)&strings); +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/SortUtils.h b/src/plugins/7zip/7za/cpp/7zip/UI/Common/SortUtils.h new file mode 100644 index 00000000..07aa24d2 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/SortUtils.h @@ -0,0 +1,10 @@ +// SortUtils.h + +#ifndef ZIP7_INC_SORT_UTLS_H +#define ZIP7_INC_SORT_UTLS_H + +#include "../../../Common/MyString.h" + +void SortFileNames(const UStringVector &strings, CUIntVector &indices); + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/StdAfx.h b/src/plugins/7zip/7za/cpp/7zip/UI/Common/StdAfx.h new file mode 100644 index 00000000..035267cc --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/StdAfx.h @@ -0,0 +1,11 @@ +// StdAfx.h + +#ifndef ZIP7_INC_STDAFX_H +#define ZIP7_INC_STDAFX_H + +#if defined(_MSC_VER) && _MSC_VER >= 1800 +#pragma warning(disable : 4464) // relative include path contains '..' +#endif +#include "../../../Common/Common.h" + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/TempFiles.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Common/TempFiles.cpp new file mode 100644 index 00000000..ad16e36f --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/TempFiles.cpp @@ -0,0 +1,20 @@ +// TempFiles.cpp + +#include "StdAfx.h" + +#include "../../../Windows/FileDir.h" + +#include "TempFiles.h" + +using namespace NWindows; +using namespace NFile; + +void CTempFiles::Clear() +{ + while (!Paths.IsEmpty()) + { + if (NeedDeleteFiles) + NDir::DeleteFileAlways(Paths.Back()); + Paths.DeleteBack(); + } +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/TempFiles.h b/src/plugins/7zip/7za/cpp/7zip/UI/Common/TempFiles.h new file mode 100644 index 00000000..83c741fc --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/TempFiles.h @@ -0,0 +1,19 @@ +// TempFiles.h + +#ifndef ZIP7_INC_TEMP_FILES_H +#define ZIP7_INC_TEMP_FILES_H + +#include "../../../Common/MyString.h" + +class CTempFiles +{ + void Clear(); +public: + FStringVector Paths; + bool NeedDeleteFiles; + + CTempFiles(): NeedDeleteFiles(true) {} + ~CTempFiles() { Clear(); } +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/Update.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Common/Update.cpp new file mode 100644 index 00000000..1c2754e9 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/Update.cpp @@ -0,0 +1,1931 @@ +// Update.cpp + +#include "StdAfx.h" + +// #include + +#include "Update.h" + +#include "../../../Common/StringConvert.h" + +#include "../../../Windows/DLL.h" +#include "../../../Windows/FileDir.h" +#include "../../../Windows/FileFind.h" +#include "../../../Windows/FileName.h" +#include "../../../Windows/PropVariant.h" +#include "../../../Windows/PropVariantConv.h" +#include "../../../Windows/TimeUtils.h" + +#include "../../Common/FileStreams.h" +#include "../../Common/LimitedStreams.h" +#include "../../Common/MultiOutStream.h" +#include "../../Common/StreamUtils.h" + +#include "../../Compress/CopyCoder.h" + +#include "../Common/DirItem.h" +#include "../Common/EnumDirItems.h" +#include "../Common/OpenArchive.h" +#include "../Common/UpdateProduce.h" + +#include "EnumDirItems.h" +#include "SetProperties.h" +#include "TempFiles.h" +#include "UpdateCallback.h" + +static const char * const kUpdateIsNotSupoorted = + "update operations are not supported for this archive"; + +static const char * const kUpdateIsNotSupported_MultiVol = + "Updating for multivolume archives is not implemented"; + +using namespace NWindows; +using namespace NCOM; +using namespace NFile; +using namespace NDir; +using namespace NName; + +#ifdef _WIN32 +static CFSTR const kTempFolderPrefix = FTEXT("7zE"); +#endif + +void CUpdateErrorInfo::SetFromLastError(const char *message) +{ + SystemError = ::GetLastError(); + Message = message; +} + +HRESULT CUpdateErrorInfo::SetFromLastError(const char *message, const FString &fileName) +{ + SetFromLastError(message); + FileNames.Add(fileName); + return Get_HRESULT_Error(); +} + +HRESULT CUpdateErrorInfo::SetFromError_DWORD(const char *message, const FString &fileName, DWORD error) +{ + Message = message; + FileNames.Add(fileName); + SystemError = error; + return Get_HRESULT_Error(); +} + + +using namespace NUpdateArchive; + +struct CMultiOutStream_Rec +{ + CMultiOutStream *Spec; + CMyComPtr Ref; +}; + +struct CMultiOutStream_Bunch +{ + CObjectVector Items; + + HRESULT Destruct() + { + HRESULT hres = S_OK; + FOR_VECTOR (i, Items) + { + CMultiOutStream_Rec &rec = Items[i]; + if (rec.Ref) + { + const HRESULT hres2 = rec.Spec->Destruct(); + if (hres == S_OK) + hres = hres2; + } + } + Items.Clear(); + return hres; + } + + void DisableDeletion() + { + FOR_VECTOR (i, Items) + { + CMultiOutStream_Rec &rec = Items[i]; + if (rec.Ref) + rec.Spec->NeedDelete = false; + } + } +}; + + +void CArchivePath::ParseFromPath(const UString &path, EArcNameMode mode) +{ + OriginalPath = path; + + SplitPathToParts_2(path, Prefix, Name); + + if (mode == k_ArcNameMode_Add) + return; + + if (mode != k_ArcNameMode_Exact) + { + int dotPos = Name.ReverseFind_Dot(); + if (dotPos < 0) + return; + if ((unsigned)dotPos == Name.Len() - 1) + Name.DeleteBack(); + else + { + const UString ext = Name.Ptr((unsigned)(dotPos + 1)); + if (BaseExtension.IsEqualTo_NoCase(ext)) + { + BaseExtension = ext; + Name.DeleteFrom((unsigned)dotPos); + return; + } + } + } + + BaseExtension.Empty(); +} + +UString CArchivePath::GetFinalPath() const +{ + UString path = GetPathWithoutExt(); + if (!BaseExtension.IsEmpty()) + { + path.Add_Dot(); + path += BaseExtension; + } + return path; +} + +UString CArchivePath::GetFinalVolPath() const +{ + UString path = GetPathWithoutExt(); + // if BaseExtension is empty, we must ignore VolExtension also. + if (!BaseExtension.IsEmpty()) + { + path.Add_Dot(); + path += VolExtension; + } + return path; +} + +FString CArchivePath::GetTempPath() const +{ + FString path = TempPrefix; + path += us2fs(Name); + if (!BaseExtension.IsEmpty()) + { + path.Add_Dot(); + path += us2fs(BaseExtension); + } + path += ".tmp"; + path += TempPostfix; + return path; +} + +static const char * const kDefaultArcType = "7z"; +static const char * const kDefaultArcExt = "7z"; +static const char * const kSFXExtension = + #ifdef _WIN32 + "exe"; + #else + ""; + #endif + +bool CUpdateOptions::InitFormatIndex(const CCodecs *codecs, + const CObjectVector &types, const UString &arcPath) +{ + if (types.Size() > 1) + return false; + // int arcTypeIndex = -1; + if (types.Size() != 0) + { + MethodMode.Type = types[0]; + MethodMode.Type_Defined = true; + } + if (MethodMode.Type.FormatIndex < 0) + { + // MethodMode.Type = -1; + MethodMode.Type = COpenType(); + if (ArcNameMode != k_ArcNameMode_Add) + { + MethodMode.Type.FormatIndex = codecs->FindFormatForArchiveName(arcPath); + if (MethodMode.Type.FormatIndex >= 0) + MethodMode.Type_Defined = true; + } + } + return true; +} + +bool CUpdateOptions::SetArcPath(const CCodecs *codecs, const UString &arcPath) +{ + UString typeExt; + int formatIndex = MethodMode.Type.FormatIndex; + if (formatIndex < 0) + { + typeExt = kDefaultArcExt; + } + else + { + const CArcInfoEx &arcInfo = codecs->Formats[(unsigned)formatIndex]; + if (!arcInfo.UpdateEnabled) + return false; + typeExt = arcInfo.GetMainExt(); + } + UString ext = typeExt; + if (SfxMode) + ext = kSFXExtension; + ArchivePath.BaseExtension = ext; + ArchivePath.VolExtension = typeExt; + ArchivePath.ParseFromPath(arcPath, ArcNameMode); + FOR_VECTOR (i, Commands) + { + CUpdateArchiveCommand &uc = Commands[i]; + uc.ArchivePath.BaseExtension = ext; + uc.ArchivePath.VolExtension = typeExt; + uc.ArchivePath.ParseFromPath(uc.UserArchivePath, ArcNameMode); + } + return true; +} + + +struct CUpdateProduceCallbackImp Z7_final: public IUpdateProduceCallback +{ + const CObjectVector *_arcItems; + CDirItemsStat *_stat; + IUpdateCallbackUI *_callback; + + CUpdateProduceCallbackImp( + const CObjectVector *a, + CDirItemsStat *stat, + IUpdateCallbackUI *callback): + _arcItems(a), + _stat(stat), + _callback(callback) {} + + virtual HRESULT ShowDeleteFile(unsigned arcIndex) Z7_override; +}; + + +HRESULT CUpdateProduceCallbackImp::ShowDeleteFile(unsigned arcIndex) +{ + const CArcItem &ai = (*_arcItems)[arcIndex]; + { + CDirItemsStat &stat = *_stat; + if (ai.IsDir) + stat.NumDirs++; + else if (ai.IsAltStream) + { + stat.NumAltStreams++; + stat.AltStreamsSize += ai.Size; + } + else + { + stat.NumFiles++; + stat.FilesSize += ai.Size; + } + } + return _callback->ShowDeleteFile(ai.Name, ai.IsDir); +} + +bool CRenamePair::Prepare() +{ + if (RecursedType != NRecursedType::kNonRecursed) + return false; + if (!WildcardParsing) + return true; + return !DoesNameContainWildcard(OldName); +} + +extern bool g_CaseSensitive; + +static unsigned CompareTwoNames(const wchar_t *s1, const wchar_t *s2) +{ + for (unsigned i = 0;; i++) + { + wchar_t c1 = s1[i]; + wchar_t c2 = s2[i]; + if (c1 == 0 || c2 == 0) + return i; + if (c1 == c2) + continue; + if (!g_CaseSensitive && (MyCharUpper(c1) == MyCharUpper(c2))) + continue; + if (IsPathSepar(c1) && IsPathSepar(c2)) + continue; + return i; + } +} + +bool CRenamePair::GetNewPath(bool isFolder, const UString &src, UString &dest) const +{ + unsigned num = CompareTwoNames(OldName, src); + if (OldName[num] == 0) + { + if (src[num] != 0 && !IsPathSepar(src[num]) && num != 0 && !IsPathSepar(src[num - 1])) + return false; + } + else + { + // OldName[num] != 0 + // OldName = "1\1a.txt" + // src = "1" + + if (!isFolder + || src[num] != 0 + || !IsPathSepar(OldName[num]) + || OldName[num + 1] != 0) + return false; + } + dest = NewName + src.Ptr(num); + return true; +} + +#ifdef SUPPORT_ALT_STREAMS +int FindAltStreamColon_in_Path(const wchar_t *path); +#endif + + + +static HRESULT Compress( + const CUpdateOptions &options, + bool isUpdatingItself, + CCodecs *codecs, + const CActionSet &actionSet, + const CArc *arc, + CArchivePath &archivePath, + const CObjectVector &arcItems, + Byte *processedItemsStatuses, + const CDirItems &dirItems, + const CDirItem *parentDirItem, + CTempFiles &tempFiles, + CMultiOutStream_Bunch &multiStreams, + CUpdateErrorInfo &errorInfo, + IUpdateCallbackUI *callback, + CFinishArchiveStat &st) +{ + CMyComPtr outArchive; + int formatIndex = options.MethodMode.Type.FormatIndex; + + if (arc) + { + formatIndex = arc->FormatIndex; + if (formatIndex < 0) + return E_NOTIMPL; + CMyComPtr archive2 = arc->Archive; + HRESULT result = archive2.QueryInterface(IID_IOutArchive, &outArchive); + if (result != S_OK) + throw kUpdateIsNotSupoorted; + } + else + { + RINOK(codecs->CreateOutArchive((unsigned)formatIndex, outArchive)) + + #ifdef Z7_EXTERNAL_CODECS + { + CMyComPtr setCompressCodecsInfo; + outArchive.QueryInterface(IID_ISetCompressCodecsInfo, (void **)&setCompressCodecsInfo); + if (setCompressCodecsInfo) + { + RINOK(setCompressCodecsInfo->SetCompressCodecsInfo(codecs)) + } + } + #endif + } + + if (!outArchive) + throw kUpdateIsNotSupoorted; + + // we need to set properties to get fileTimeType. + RINOK(SetProperties(outArchive, options.MethodMode.Properties)) + + NFileTimeType::EEnum fileTimeType; + { + /* + how we compare file_in_archive::MTime with dirItem.MTime + for GetUpdatePairInfoList(): + + if (kpidMTime is not defined), external MTime of archive is used. + + before 22.00: + if (kpidTimeType is defined) + { + kpidTimeType is used as precision. + (kpidTimeType > kDOS) is not allowed. + } + else GetFileTimeType() value is used as precision. + + 22.00: + if (kpidMTime is defined) + { + if (kpidMTime::precision != 0), then kpidMTime::precision is used as precision. + else + { + if (kpidTimeType is defined), kpidTimeType is used as precision. + else GetFileTimeType() value is used as precision. + } + } + else external MTime of archive is used as precision. + */ + + UInt32 value; + RINOK(outArchive->GetFileTimeType(&value)) + + // we support any future fileType here. + fileTimeType = (NFileTimeType::EEnum)value; + + /* + old 21.07 code: + switch (value) + { + case NFileTimeType::kWindows: + case NFileTimeType::kUnix: + case NFileTimeType::kDOS: + fileTimeType = (NFileTimeType::EEnum)value; + break; + default: + return E_FAIL; + } + */ + } + + // bool noTimestampExpected = false; + { + const CArcInfoEx &arcInfo = codecs->Formats[(unsigned)formatIndex]; + + // if (arcInfo.Flags_KeepName()) noTimestampExpected = true; + if (arcInfo.Is_Xz() || + arcInfo.Is_BZip2()) + { + /* 7-zip before 22.00 returns NFileTimeType::kUnix for xz and bzip2, + but we want to set timestamp without reduction to unix. */ + // noTimestampExpected = true; + fileTimeType = NFileTimeType::kNotDefined; // it means not defined + } + + if (options.AltStreams.Val && !arcInfo.Flags_AltStreams()) + return E_NOTIMPL; + if (options.NtSecurity.Val && !arcInfo.Flags_NtSecurity()) + return E_NOTIMPL; + if (options.DeleteAfterCompressing && arcInfo.Flags_HashHandler()) + return E_NOTIMPL; + } + + CRecordVector updatePairs2; + + UStringVector newNames; + + CArcToDoStat stat2; + + if (options.RenameMode || options.RenamePairs.Size() != 0) + { + FOR_VECTOR (i, arcItems) + { + const CArcItem &ai = arcItems[i]; + bool needRename = false; + UString dest; + + if (ai.Censored) + { + FOR_VECTOR (j, options.RenamePairs) + { + const CRenamePair &rp = options.RenamePairs[j]; + if (rp.GetNewPath(ai.IsDir, ai.Name, dest)) + { + needRename = true; + break; + } + + #ifdef SUPPORT_ALT_STREAMS + if (ai.IsAltStream) + { + int colonPos = FindAltStreamColon_in_Path(ai.Name); + if (colonPos >= 0) + { + UString mainName = ai.Name.Left((unsigned)colonPos); + /* + actually we must improve that code to support cases + with folder renaming like: rn arc dir1\ dir2\ + */ + if (rp.GetNewPath(false, mainName, dest)) + { + needRename = true; + dest.Add_Colon(); + dest += ai.Name.Ptr((unsigned)(colonPos + 1)); + break; + } + } + } + #endif + } + } + + CUpdatePair2 up2; + up2.SetAs_NoChangeArcItem(ai.IndexInServer); + if (needRename) + { + up2.NewProps = true; + RINOK(arc->IsItem_Anti(i, up2.IsAnti)) + up2.NewNameIndex = (int)newNames.Add(dest); + } + updatePairs2.Add(up2); + } + } + else + { + CRecordVector updatePairs; + GetUpdatePairInfoList(dirItems, arcItems, fileTimeType, updatePairs); // must be done only once!!! + CUpdateProduceCallbackImp upCallback(&arcItems, &stat2.DeleteData, callback); + + UpdateProduce(updatePairs, actionSet, updatePairs2, isUpdatingItself ? &upCallback : NULL); + } + + { + FOR_VECTOR (i, updatePairs2) + { + const CUpdatePair2 &up = updatePairs2[i]; + + // 17.01: anti-item is (up.NewData && (p.UseArcProps in most cases)) + + if (up.NewData && !up.UseArcProps) + { + if (up.ExistOnDisk()) + { + CDirItemsStat2 &stat = stat2.NewData; + const CDirItem &di = dirItems.Items[(unsigned)up.DirIndex]; + if (di.IsDir()) + { + if (up.IsAnti) + stat.Anti_NumDirs++; + else + stat.NumDirs++; + } + #ifdef _WIN32 + else if (di.IsAltStream) + { + if (up.IsAnti) + stat.Anti_NumAltStreams++; + else + { + stat.NumAltStreams++; + stat.AltStreamsSize += di.Size; + } + } + #endif + else + { + if (up.IsAnti) + stat.Anti_NumFiles++; + else + { + stat.NumFiles++; + stat.FilesSize += di.Size; + } + } + } + } + else if (up.ArcIndex >= 0) + { + CDirItemsStat2 &stat = *(up.NewData ? &stat2.NewData : &stat2.OldData); + const CArcItem &ai = arcItems[(unsigned)up.ArcIndex]; + if (ai.IsDir) + { + if (up.IsAnti) + stat.Anti_NumDirs++; + else + stat.NumDirs++; + } + else if (ai.IsAltStream) + { + if (up.IsAnti) + stat.Anti_NumAltStreams++; + else + { + stat.NumAltStreams++; + stat.AltStreamsSize += ai.Size; + } + } + else + { + if (up.IsAnti) + stat.Anti_NumFiles++; + else + { + stat.NumFiles++; + stat.FilesSize += ai.Size; + } + } + } + } + RINOK(callback->SetNumItems(stat2)) + } + + CArchiveUpdateCallback *updateCallbackSpec = new CArchiveUpdateCallback; + CMyComPtr updateCallback(updateCallbackSpec); + + updateCallbackSpec->PreserveATime = options.PreserveATime; + updateCallbackSpec->ShareForWrite = options.OpenShareForWrite; + updateCallbackSpec->StopAfterOpenError = options.StopAfterOpenError; + updateCallbackSpec->StdInMode = options.StdInMode; + updateCallbackSpec->Callback = callback; + + if (arc) + { + // we set Archive to allow to transfer GetProperty requests back to DLL. + updateCallbackSpec->Archive = arc->Archive; + } + + updateCallbackSpec->DirItems = &dirItems; + updateCallbackSpec->ParentDirItem = parentDirItem; + + updateCallbackSpec->StoreNtSecurity = options.NtSecurity.Val; + updateCallbackSpec->StoreHardLinks = options.HardLinks.Val; + updateCallbackSpec->StoreSymLinks = options.SymLinks.Val; + updateCallbackSpec->StoreOwnerName = options.StoreOwnerName.Val; + updateCallbackSpec->StoreOwnerId = options.StoreOwnerId.Val; + + updateCallbackSpec->Arc = arc; + updateCallbackSpec->ArcItems = &arcItems; + updateCallbackSpec->UpdatePairs = &updatePairs2; + + updateCallbackSpec->ProcessedItemsStatuses = processedItemsStatuses; + + { + const UString arcPath = archivePath.GetFinalPath(); + updateCallbackSpec->ArcFileName = ExtractFileNameFromPath(arcPath); + } + + if (options.RenamePairs.Size() != 0) + updateCallbackSpec->NewNames = &newNames; + + if (options.SetArcMTime) + { + // updateCallbackSpec->Need_ArcMTime_Report = true; + updateCallbackSpec->Need_LatestMTime = true; + } + + CMyComPtr outSeekStream; + CMyComPtr outStream; + + if (!options.StdOutMode) + { + FString dirPrefix; + if (!GetOnlyDirPrefix(us2fs(archivePath.GetFinalPath()), dirPrefix)) + throw 1417161; + CreateComplexDir(dirPrefix); + } + + COutFileStream *outStreamSpec = NULL; + CStdOutFileStream *stdOutFileStreamSpec = NULL; + CMultiOutStream *volStreamSpec = NULL; + + if (options.VolumesSizes.Size() == 0) + { + if (options.StdOutMode) + { + stdOutFileStreamSpec = new CStdOutFileStream; + outStream = stdOutFileStreamSpec; + } + else + { + outStreamSpec = new COutFileStream; + outSeekStream = outStreamSpec; + outStream = outSeekStream; + bool isOK = false; + FString realPath; + + for (unsigned i = 0; i < (1 << 16); i++) + { + if (archivePath.Temp) + { + if (i > 0) + { + archivePath.TempPostfix.Empty(); + archivePath.TempPostfix.Add_UInt32(i); + } + realPath = archivePath.GetTempPath(); + } + else + realPath = us2fs(archivePath.GetFinalPath()); + if (outStreamSpec->Create_NEW(realPath)) + { + tempFiles.Paths.Add(realPath); + isOK = true; + break; + } + if (::GetLastError() != ERROR_FILE_EXISTS) + break; + if (!archivePath.Temp) + break; + } + + if (!isOK) + return errorInfo.SetFromLastError("cannot open file", realPath); + } + } + else + { + if (options.StdOutMode) + return E_FAIL; + if (arc && arc->GetGlobalOffset() > 0) + return E_NOTIMPL; + + volStreamSpec = new CMultiOutStream(); + outSeekStream = volStreamSpec; + outStream = outSeekStream; + volStreamSpec->Prefix = us2fs(archivePath.GetFinalVolPath()); + volStreamSpec->Prefix.Add_Dot(); + volStreamSpec->Init(options.VolumesSizes); + { + CMultiOutStream_Rec &rec = multiStreams.Items.AddNew(); + rec.Spec = volStreamSpec; + rec.Ref = rec.Spec; + } + + /* + updateCallbackSpec->VolumesSizes = volumesSizes; + updateCallbackSpec->VolName = archivePath.Prefix + archivePath.Name; + if (!archivePath.VolExtension.IsEmpty()) + updateCallbackSpec->VolExt = UString('.') + archivePath.VolExtension; + */ + } + + if (options.SfxMode) + { + CInFileStream *sfxStreamSpec = new CInFileStream; + CMyComPtr sfxStream(sfxStreamSpec); + if (!sfxStreamSpec->Open(options.SfxModule)) + return errorInfo.SetFromLastError("cannot open SFX module", options.SfxModule); + + CMyComPtr sfxOutStream; + COutFileStream *outStreamSpec2 = NULL; + if (options.VolumesSizes.Size() == 0) + sfxOutStream = outStream; + else + { + outStreamSpec2 = new COutFileStream; + sfxOutStream = outStreamSpec2; + const FString realPath = us2fs(archivePath.GetFinalPath()); + if (!outStreamSpec2->Create_NEW(realPath)) + return errorInfo.SetFromLastError("cannot open file", realPath); + } + + { + UInt64 sfxSize; + RINOK(sfxStreamSpec->GetSize(&sfxSize)) + RINOK(callback->WriteSfx(fs2us(options.SfxModule), sfxSize)) + } + + RINOK(NCompress::CopyStream(sfxStream, sfxOutStream, NULL)) + + if (outStreamSpec2) + { + RINOK(outStreamSpec2->Close()) + } + } + + CMyComPtr tailStream; + + if (options.SfxMode || !arc || arc->ArcStreamOffset == 0) + tailStream = outStream; + else + { + // Int64 globalOffset = arc->GetGlobalOffset(); + RINOK(InStream_SeekToBegin(arc->InStream)) + RINOK(NCompress::CopyStream_ExactSize(arc->InStream, outStream, arc->ArcStreamOffset, NULL)) + if (options.StdOutMode) + tailStream = outStream; + else + { + CTailOutStream *tailStreamSpec = new CTailOutStream; + tailStream = tailStreamSpec; + tailStreamSpec->Stream = outSeekStream; + tailStreamSpec->Offset = arc->ArcStreamOffset; + tailStreamSpec->Init(); + } + } + + CFiTime ft; + FiTime_Clear(ft); + bool ft_Defined = false; + { + FOR_VECTOR (i, updatePairs2) + { + const CUpdatePair2 &pair2 = updatePairs2[i]; + CFiTime ft2; + FiTime_Clear(ft2); + bool ft2_Defined = false; + /* we use full precision of dirItem, if dirItem is defined + and (dirItem will be used or dirItem is sameTime in dir and arc */ + if (pair2.DirIndex >= 0 && + (pair2.NewProps || pair2.IsSameTime)) + { + ft2 = dirItems.Items[(unsigned)pair2.DirIndex].MTime; + ft2_Defined = true; + } + else if (pair2.UseArcProps && pair2.ArcIndex >= 0) + { + const CArcItem &arcItem = arcItems[(unsigned)pair2.ArcIndex]; + if (arcItem.MTime.Def) + { + arcItem.MTime.Write_To_FiTime(ft2); + ft2_Defined = true; + } + } + if (ft2_Defined) + { + if (!ft_Defined || Compare_FiTime(&ft, &ft2) < 0) + { + ft = ft2; + ft_Defined = true; + } + } + } + /* + if (fileTimeType != NFileTimeType::kNotDefined) + FiTime_Normalize_With_Prec(ft, fileTimeType); + */ + } + + if (volStreamSpec && options.SetArcMTime && ft_Defined) + { + volStreamSpec->MTime = ft; + volStreamSpec->MTime_Defined = true; + } + + HRESULT result = outArchive->UpdateItems(tailStream, updatePairs2.Size(), updateCallback); + // callback->Finalize(); + RINOK(result) + + if (!updateCallbackSpec->AreAllFilesClosed()) + { + errorInfo.Message = "There are unclosed input files:"; + errorInfo.FileNames = updateCallbackSpec->_openFiles_Paths; + return E_FAIL; + } + + if (options.SetArcMTime) + { + // bool needNormalizeAfterStream; + // needParse; + /* + if (updateCallbackSpec->ArcMTime_WasReported) + { + isDefined = updateCallbackSpec->Reported_ArcMTime.Def; + if (isDefined) + updateCallbackSpec->Reported_ArcMTime.Write_To_FiTime(ft); + else + fileTimeType = NFileTimeType::kNotDefined; + } + if (!isDefined) + */ + { + if (updateCallbackSpec->LatestMTime_Defined) + { + // CArcTime at = StreamCallback_ArcMTime; + // updateCallbackSpec->StreamCallback_ArcMTime.Write_To_FiTime(ft); + // we must normalize with precision from archive; + if (!ft_Defined || Compare_FiTime(&ft, &updateCallbackSpec->LatestMTime) < 0) + ft = updateCallbackSpec->LatestMTime; + ft_Defined = true; + } + /* + if (fileTimeType != NFileTimeType::kNotDefined) + FiTime_Normalize_With_Prec(ft, fileTimeType); + */ + } + // if (ft.dwLowDateTime != 0 || ft.dwHighDateTime != 0) + if (ft_Defined) + { + // we ignore set time errors here. + // note that user could move some finished volumes to another folder. + if (outStreamSpec) + outStreamSpec->SetMTime(&ft); + else if (volStreamSpec) + volStreamSpec->SetMTime_Final(ft); + } + } + + if (callback) + { + UInt64 size = 0; + if (outStreamSpec) + outStreamSpec->GetSize(&size); + else if (stdOutFileStreamSpec) + size = stdOutFileStreamSpec->GetSize(); + else + size = volStreamSpec->GetSize(); + + st.OutArcFileSize = size; + } + + if (outStreamSpec) + result = outStreamSpec->Close(); + else if (volStreamSpec) + { + result = volStreamSpec->FinalFlush_and_CloseFiles(st.NumVolumes); + st.IsMultiVolMode = true; + } + + RINOK(result) + + if (processedItemsStatuses) + { + FOR_VECTOR (i, updatePairs2) + { + const CUpdatePair2 &up = updatePairs2[i]; + if (up.NewData && up.DirIndex >= 0) + { + const CDirItem &di = dirItems.Items[(unsigned)up.DirIndex]; + if (di.AreReparseData() || (!di.IsDir() && di.Size == 0)) + processedItemsStatuses[(unsigned)up.DirIndex] = 1; + } + } + } + + return result; +} + + + +static bool Censor_AreAllAllowed(const NWildcard::CCensor &censor) +{ + if (censor.Pairs.Size() != 1) + return false; + const NWildcard::CPair &pair = censor.Pairs[0]; + /* Censor_CheckPath() ignores (CPair::Prefix). + So we also ignore (CPair::Prefix) here */ + // if (!pair.Prefix.IsEmpty()) return false; + return pair.Head.AreAllAllowed(); +} + +bool CensorNode_CheckPath2(const NWildcard::CCensorNode &node, const CReadArcItem &item, bool &include); + +static bool Censor_CheckPath(const NWildcard::CCensor &censor, const CReadArcItem &item) +{ + bool finded = false; + FOR_VECTOR (i, censor.Pairs) + { + /* (CPair::Prefix) in not used for matching items in archive. + So we ignore (CPair::Prefix) here */ + bool include; + if (CensorNode_CheckPath2(censor.Pairs[i].Head, item, include)) + { + // Check it and FIXME !!!! + // here we can exclude item via some Pair, that is still allowed by another Pair + if (!include) + return false; + finded = true; + } + } + return finded; +} + +static HRESULT EnumerateInArchiveItems( + // bool storeStreamsMode, + const NWildcard::CCensor &censor, + const CArc &arc, + CObjectVector &arcItems) +{ + arcItems.Clear(); + UInt32 numItems; + IInArchive *archive = arc.Archive; + RINOK(archive->GetNumberOfItems(&numItems)) + arcItems.ClearAndReserve(numItems); + + CReadArcItem item; + + const bool allFilesAreAllowed = Censor_AreAllAllowed(censor); + + for (UInt32 i = 0; i < numItems; i++) + { + CArcItem ai; + + RINOK(arc.GetItem(i, item)) + ai.Name = item.Path; + ai.IsDir = item.IsDir; + ai.IsAltStream = + #ifdef SUPPORT_ALT_STREAMS + item.IsAltStream; + #else + false; + #endif + + /* + if (!storeStreamsMode && ai.IsAltStream) + continue; + */ + if (allFilesAreAllowed) + ai.Censored = true; + else + ai.Censored = Censor_CheckPath(censor, item); + + // ai.MTime will be set to archive MTime, if not present in archive item + RINOK(arc.GetItem_MTime(i, ai.MTime)) + RINOK(arc.GetItem_Size(i, ai.Size, ai.Size_Defined)) + + ai.IndexInServer = i; + arcItems.AddInReserved(ai); + } + return S_OK; +} + +#if defined(_WIN32) && !defined(UNDER_CE) + +#if defined(__MINGW32__) || defined(__MINGW64__) +#include +#else +#include +#endif + +extern "C" { + +#ifdef MAPI_FORCE_UNICODE + +#define Z7_WIN_LPMAPISENDMAILW LPMAPISENDMAILW +#define Z7_WIN_MapiFileDescW MapiFileDescW +#define Z7_WIN_MapiMessageW MapiMessageW +#define Z7_WIN_MapiRecipDescW MapiRecipDescW + +#else + +typedef struct +{ + ULONG ulReserved; + ULONG ulRecipClass; + PWSTR lpszName; + PWSTR lpszAddress; + ULONG ulEIDSize; + PVOID lpEntryID; +} Z7_WIN_MapiRecipDescW, *Z7_WIN_lpMapiRecipDescW; + +typedef struct +{ + ULONG ulReserved; + ULONG flFlags; + ULONG nPosition; + PWSTR lpszPathName; + PWSTR lpszFileName; + PVOID lpFileType; +} Z7_WIN_MapiFileDescW, *Z7_WIN_lpMapiFileDescW; + +typedef struct +{ + ULONG ulReserved; + PWSTR lpszSubject; + PWSTR lpszNoteText; + PWSTR lpszMessageType; + PWSTR lpszDateReceived; + PWSTR lpszConversationID; + FLAGS flFlags; + Z7_WIN_lpMapiRecipDescW lpOriginator; + ULONG nRecipCount; + Z7_WIN_lpMapiRecipDescW lpRecips; + ULONG nFileCount; + Z7_WIN_lpMapiFileDescW lpFiles; +} Z7_WIN_MapiMessageW, *Z7_WIN_lpMapiMessageW; + +typedef ULONG (FAR PASCAL Z7_WIN_MAPISENDMAILW)( + LHANDLE lhSession, + ULONG_PTR ulUIParam, + Z7_WIN_lpMapiMessageW lpMessage, + FLAGS flFlags, + ULONG ulReserved +); +typedef Z7_WIN_MAPISENDMAILW FAR *Z7_WIN_LPMAPISENDMAILW; + +#endif // MAPI_FORCE_UNICODE +} +#endif // _WIN32 + + +struct C_CopyFileProgress_to_IUpdateCallbackUI2 Z7_final: + public ICopyFileProgress +{ + IUpdateCallbackUI2 *Callback; + HRESULT CallbackResult; + // bool Disable_Break; + + virtual DWORD CopyFileProgress(UInt64 total, UInt64 current) Z7_override + { + const HRESULT res = Callback->MoveArc_Progress(total, current); + CallbackResult = res; + // if (Disable_Break && res == E_ABORT) res = S_OK; + return res == S_OK ? PROGRESS_CONTINUE : PROGRESS_CANCEL; + } + + C_CopyFileProgress_to_IUpdateCallbackUI2( + IUpdateCallbackUI2 *callback) : + Callback(callback), + CallbackResult(S_OK) + // , Disable_Break(false) + {} +}; + + +HRESULT UpdateArchive( + CCodecs *codecs, + const CObjectVector &types, + const UString &cmdArcPath2, + NWildcard::CCensor &censor, + CUpdateOptions &options, + CUpdateErrorInfo &errorInfo, + IOpenCallbackUI *openCallback, + IUpdateCallbackUI2 *callback, + bool needSetPath) +{ + if (options.StdOutMode && options.EMailMode) + return E_FAIL; + + if (types.Size() > 1) + return E_NOTIMPL; + + bool renameMode = !options.RenamePairs.IsEmpty(); + if (renameMode) + { + if (options.Commands.Size() != 1) + return E_FAIL; + } + + if (options.DeleteAfterCompressing) + { + if (options.Commands.Size() != 1) + return E_NOTIMPL; + const CActionSet &as = options.Commands[0].ActionSet; + for (unsigned i = 2; i < NPairState::kNumValues; i++) + if (as.StateActions[i] != NPairAction::kCompress) + return E_NOTIMPL; + } + + censor.AddPathsToCensor(options.PathMode); + #ifdef _WIN32 + ConvertToLongNames(censor); + #endif + censor.ExtendExclude(); + + + if (options.VolumesSizes.Size() > 0 && (options.EMailMode /* || options.SfxMode */)) + return E_NOTIMPL; + + if (options.SfxMode) + { + CProperty property; + property.Name = "rsfx"; + options.MethodMode.Properties.Add(property); + if (options.SfxModule.IsEmpty()) + { + errorInfo.Message = "SFX file is not specified"; + return E_FAIL; + } + bool found = false; + if (options.SfxModule.Find(FCHAR_PATH_SEPARATOR) < 0) + { + const FString fullName = NDLL::GetModuleDirPrefix() + options.SfxModule; + if (NFind::DoesFileExist_FollowLink(fullName)) + { + options.SfxModule = fullName; + found = true; + } + } + if (!found) + { + if (!NFind::DoesFileExist_FollowLink(options.SfxModule)) + return errorInfo.SetFromLastError("cannot find specified SFX module", options.SfxModule); + } + } + + CArchiveLink arcLink; + + + if (needSetPath) + { + if (!options.InitFormatIndex(codecs, types, cmdArcPath2) || + !options.SetArcPath(codecs, cmdArcPath2)) + return E_NOTIMPL; + } + + UString arcPath = options.ArchivePath.GetFinalPath(); + + if (!options.VolumesSizes.IsEmpty()) + { + arcPath = options.ArchivePath.GetFinalVolPath(); + arcPath += ".001"; + } + + if (cmdArcPath2.IsEmpty()) + { + if (options.MethodMode.Type.FormatIndex < 0) + throw "type of archive is not specified"; + } + else + { + NFind::CFileInfo fi; + if (!fi.Find_FollowLink(us2fs(arcPath))) + { + if (renameMode) + throw "can't find archive"; + if (options.MethodMode.Type.FormatIndex < 0) + { + if (!options.SetArcPath(codecs, cmdArcPath2)) + return E_NOTIMPL; + } + } + else + { + if (fi.IsDir()) + return errorInfo.SetFromError_DWORD("There is a folder with the name of archive", + us2fs(arcPath), + #ifdef _WIN32 + ERROR_ACCESS_DENIED + #else + EISDIR + #endif + ); + #ifdef _WIN32 + if (fi.IsDevice) + return E_NOTIMPL; + #endif + + if (!options.StdOutMode && options.UpdateArchiveItself) + if (fi.IsReadOnly()) + { + return errorInfo.SetFromError_DWORD("The file is read-only", + us2fs(arcPath), + #ifdef _WIN32 + ERROR_ACCESS_DENIED + #else + EACCES + #endif + ); + } + + if (options.VolumesSizes.Size() > 0) + { + errorInfo.FileNames.Add(us2fs(arcPath)); + // errorInfo.SystemError = (DWORD)E_NOTIMPL; + errorInfo.Message = kUpdateIsNotSupported_MultiVol; + return E_NOTIMPL; + } + CObjectVector types2; + // change it. + if (options.MethodMode.Type_Defined) + types2.Add(options.MethodMode.Type); + // We need to set Properties to open archive only in some cases (WIM archives). + + CIntVector excl; + COpenOptions op; + #ifndef Z7_SFX + op.props = &options.MethodMode.Properties; + #endif + op.codecs = codecs; + op.types = &types2; + op.excludedFormats = ! + op.stdInMode = false; + op.stream = NULL; + op.filePath = arcPath; + + RINOK(callback->StartOpenArchive(arcPath)) + + HRESULT result = arcLink.Open_Strict(op, openCallback); + + if (result == E_ABORT) + return result; + + HRESULT res2 = callback->OpenResult(codecs, arcLink, arcPath, result); + /* + if (result == S_FALSE) + return E_FAIL; + */ + RINOK(res2) + RINOK(result) + + if (arcLink.VolumePaths.Size() > 1) + { + // errorInfo.SystemError = (DWORD)E_NOTIMPL; + errorInfo.Message = kUpdateIsNotSupported_MultiVol; + return E_NOTIMPL; + } + + CArc &arc = arcLink.Arcs.Back(); + arc.MTime.Def = + #ifdef _WIN32 + !fi.IsDevice; + #else + true; + #endif + if (arc.MTime.Def) + arc.MTime.Set_From_FiTime(fi.MTime); + + if (arc.ErrorInfo.ThereIsTail) + { + // errorInfo.SystemError = (DWORD)E_NOTIMPL; + errorInfo.Message = "There is some data block after the end of the archive"; + return E_NOTIMPL; + } + if (options.MethodMode.Type.FormatIndex < 0) + { + options.MethodMode.Type.FormatIndex = arcLink.GetArc()->FormatIndex; + if (!options.SetArcPath(codecs, cmdArcPath2)) + return E_NOTIMPL; + } + } + } + + if (options.MethodMode.Type.FormatIndex < 0) + { + options.MethodMode.Type.FormatIndex = codecs->FindFormatForArchiveType((UString)kDefaultArcType); + if (options.MethodMode.Type.FormatIndex < 0) + return E_NOTIMPL; + } + + const bool thereIsInArchive = arcLink.IsOpen; + if (!thereIsInArchive && renameMode) + return E_FAIL; + + CDirItems dirItems; + dirItems.Callback = callback; + + CDirItem parentDirItem; + CDirItem *parentDirItem_Ptr = NULL; + + /* + FStringVector requestedPaths; + FStringVector *requestedPaths_Ptr = NULL; + if (options.DeleteAfterCompressing) + requestedPaths_Ptr = &requestedPaths; + */ + + if (options.StdInMode) + { + CDirItem di; + // di.ClearBase(); + // di.Size = (UInt64)(Int64)-1; + if (!di.SetAs_StdInFile()) + return GetLastError_noZero_HRESULT(); + di.Name = options.StdInFileName; + // di.Attrib_IsDefined = false; + // NTime::GetCurUtc_FiTime(di.MTime); + // di.CTime = di.ATime = di.MTime; + dirItems.Items.Add(di); + } + else + { + bool needScanning = false; + + if (!renameMode) + FOR_VECTOR (i, options.Commands) + if (options.Commands[i].ActionSet.NeedScanning()) + needScanning = true; + + if (needScanning) + { + RINOK(callback->StartScanning()) + + dirItems.SymLinks = options.SymLinks.Val; + + #if defined(_WIN32) && !defined(UNDER_CE) + dirItems.ReadSecure = options.NtSecurity.Val; + #endif + + dirItems.ScanAltStreams = options.AltStreams.Val; + dirItems.ExcludeDirItems = censor.ExcludeDirItems; + dirItems.ExcludeFileItems = censor.ExcludeFileItems; + + dirItems.ShareForWrite = options.OpenShareForWrite; + + #ifndef _WIN32 + dirItems.StoreOwnerName = options.StoreOwnerName.Val; + #endif + + const HRESULT res = EnumerateItems(censor, + options.PathMode, + UString(), // options.AddPathPrefix, + dirItems); + + if (res != S_OK) + { + if (res != E_ABORT) + errorInfo.Message = "Scanning error"; + return res; + } + + RINOK(callback->FinishScanning(dirItems.Stat)) + + // 22.00: we don't need parent folder, if absolute path mode + if (options.PathMode != NWildcard::k_AbsPath) + if (censor.Pairs.Size() == 1) + { + NFind::CFileInfo fi; + FString prefix = us2fs(censor.Pairs[0].Prefix); + prefix.Add_Dot(); + // UString prefix = censor.Pairs[0].Prefix; + /* + if (prefix.Back() == WCHAR_PATH_SEPARATOR) + { + prefix.DeleteBack(); + } + */ + if (fi.Find(prefix)) + if (fi.IsDir()) + { + parentDirItem.Copy_From_FileInfoBase(fi); + parentDirItem_Ptr = &parentDirItem; + + int secureIndex = -1; + #if defined(_WIN32) && !defined(UNDER_CE) + if (options.NtSecurity.Val) + dirItems.AddSecurityItem(prefix, secureIndex); + #endif + parentDirItem.SecureIndex = secureIndex; + } + } + } + } + + FString tempDirPrefix; + bool usesTempDir = false; + + #ifdef _WIN32 + CTempDir tempDirectory; + if (options.EMailMode && options.EMailRemoveAfter) + { + tempDirectory.Create(kTempFolderPrefix); + tempDirPrefix = tempDirectory.GetPath(); + NormalizeDirPathPrefix(tempDirPrefix); + usesTempDir = true; + } + #endif + + CTempFiles tempFiles; + + bool createTempFile = false; + + if (!options.StdOutMode && options.UpdateArchiveItself) + { + CArchivePath &ap = options.Commands[0].ArchivePath; + ap = options.ArchivePath; + // if ((archive != 0 && !usesTempDir) || !options.WorkingDir.IsEmpty()) + if ((thereIsInArchive || !options.WorkingDir.IsEmpty()) && !usesTempDir && options.VolumesSizes.Size() == 0) + { + createTempFile = true; + ap.Temp = true; + if (!options.WorkingDir.IsEmpty()) + ap.TempPrefix = options.WorkingDir; + else + ap.TempPrefix = us2fs(ap.Prefix); + NormalizeDirPathPrefix(ap.TempPrefix); + } + } + + unsigned ci; + + + // self including protection + if (options.DeleteAfterCompressing) + { + for (ci = 0; ci < options.Commands.Size(); ci++) + { + CArchivePath &ap = options.Commands[ci].ArchivePath; + const FString path = us2fs(ap.GetFinalPath()); + // maybe we must compare absolute paths path here + FOR_VECTOR (i, dirItems.Items) + { + const FString phyPath = dirItems.GetPhyPath(i); + if (phyPath == path) + { + UString s; + s = "It is not allowed to include archive to itself"; + s.Add_LF(); + s += fs2us(path); + throw s; + } + } + } + } + + + for (ci = 0; ci < options.Commands.Size(); ci++) + { + CArchivePath &ap = options.Commands[ci].ArchivePath; + if (usesTempDir) + { + // Check it + ap.Prefix = fs2us(tempDirPrefix); + // ap.Temp = true; + // ap.TempPrefix = tempDirPrefix; + } + if (!options.StdOutMode && + (ci > 0 || !createTempFile)) + { + const FString path = us2fs(ap.GetFinalPath()); + if (NFind::DoesFileOrDirExist(path)) + { + errorInfo.SystemError = ERROR_FILE_EXISTS; + errorInfo.Message = "The file already exists"; + errorInfo.FileNames.Add(path); + return errorInfo.Get_HRESULT_Error(); + } + } + } + + CObjectVector arcItems; + if (thereIsInArchive) + { + RINOK(EnumerateInArchiveItems( + // options.StoreAltStreams, + censor, arcLink.Arcs.Back(), arcItems)) + } + + /* + FStringVector processedFilePaths; + FStringVector *processedFilePaths_Ptr = NULL; + if (options.DeleteAfterCompressing) + processedFilePaths_Ptr = &processedFilePaths; + */ + + CByteBuffer processedItems; + if (options.DeleteAfterCompressing) + { + const unsigned num = dirItems.Items.Size(); + processedItems.Alloc(num); + for (unsigned i = 0; i < num; i++) + processedItems[i] = 0; + } + + CMultiOutStream_Bunch multiStreams; + + /* + #ifndef Z7_NO_CRYPTO + if (arcLink.PasswordWasAsked) + { + // We set password, if open have requested password + RINOK(callback->SetPassword(arcLink.Password)); + } + #endif + */ + + for (ci = 0; ci < options.Commands.Size(); ci++) + { + const CArc *arc = thereIsInArchive ? arcLink.GetArc() : NULL; + CUpdateArchiveCommand &command = options.Commands[ci]; + UString name; + bool isUpdating; + + if (options.StdOutMode) + { + name = "stdout"; + isUpdating = thereIsInArchive; + } + else + { + name = command.ArchivePath.GetFinalPath(); + isUpdating = (ci == 0 && options.UpdateArchiveItself && thereIsInArchive); + } + + RINOK(callback->StartArchive(name, isUpdating)) + + CFinishArchiveStat st; + + RINOK(Compress(options, + isUpdating, + codecs, + command.ActionSet, + arc, + command.ArchivePath, + arcItems, + options.DeleteAfterCompressing ? (Byte *)processedItems : NULL, + + dirItems, + parentDirItem_Ptr, + + tempFiles, + multiStreams, + errorInfo, callback, st)) + + RINOK(callback->FinishArchive(st)) + } + + + if (thereIsInArchive) + { + RINOK(arcLink.Close()) + arcLink.Release(); + } + + multiStreams.DisableDeletion(); + RINOK(multiStreams.Destruct()) + + // here we disable deleting of temp archives. + // note: archive moving can fail, or it can be interrupted, + // if we move new temp update from another volume. + // And we still want to keep temp archive in that case, + // because we will have deleted original archive. + tempFiles.NeedDeleteFiles = false; + // tempFiles.Paths.Clear(); + + if (createTempFile) + { + try + { + CArchivePath &ap = options.Commands[0].ArchivePath; + const FString &tempPath = ap.GetTempPath(); + + // DWORD attrib = 0; + if (thereIsInArchive) + { + // attrib = NFind::GetFileAttrib(us2fs(arcPath)); + if (!DeleteFileAlways(us2fs(arcPath))) + return errorInfo.SetFromLastError("cannot delete the file", us2fs(arcPath)); + } + + UInt64 totalArcSize = 0; + { + NFind::CFileInfo fi; + if (fi.Find(tempPath)) + totalArcSize = fi.Size; + } + RINOK(callback->MoveArc_Start(fs2us(tempPath), arcPath, + totalArcSize, BoolToInt(thereIsInArchive))) + + C_CopyFileProgress_to_IUpdateCallbackUI2 prox(callback); + // if we update archive, we have removed original archive. + // So if we break archive moving, we will have only temporary archive. + // We can disable breaking here: + // prox.Disable_Break = thereIsInArchive; + + if (!MyMoveFile_with_Progress(tempPath, us2fs(arcPath), &prox)) + { + errorInfo.SystemError = ::GetLastError(); + errorInfo.Message = "cannot move the file"; + if (errorInfo.SystemError == ERROR_INVALID_PARAMETER) + { + if (totalArcSize > (UInt32)(Int32)-1) + { + // bool isFsDetected = false; + // if (NSystem::Is_File_LimitedBy_4GB(us2fs(arcPath), isFsDetected) || !isFsDetected) + { + errorInfo.Message.Add_LF(); + errorInfo.Message += "Archive file size exceeds 4 GB"; + } + } + } + // if there was no input archive, and we have operation breaking. + // then we can remove temporary archive, because we still have original uncompressed files. + if (!thereIsInArchive + && prox.CallbackResult == E_ABORT) + tempFiles.NeedDeleteFiles = true; + errorInfo.FileNames.Add(tempPath); + errorInfo.FileNames.Add(us2fs(arcPath)); + RINOK(prox.CallbackResult) + return errorInfo.Get_HRESULT_Error(); + } + + // MoveArc_Finish() can return delayed user break (E_ABORT) status, + // if callback callee ignored interruption to finish archive creation operation. + RINOK(callback->MoveArc_Finish()) + + /* + if (attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_READONLY)) + { + DWORD attrib2 = NFind::GetFileAttrib(us2fs(arcPath)); + if (attrib2 != INVALID_FILE_ATTRIBUTES) + NDir::SetFileAttrib(us2fs(arcPath), attrib2 | FILE_ATTRIBUTE_READONLY); + } + */ + } + catch(...) + { + throw; + } + } + + + #if defined(_WIN32) && !defined(UNDER_CE) + +Z7_DIAGNOSTIC_IGNORE_CAST_FUNCTION + + if (options.EMailMode) + { + NDLL::CLibrary mapiLib; + if (!mapiLib.Load(FTEXT("Mapi32.dll"))) + { + errorInfo.SetFromLastError("cannot load Mapi32.dll"); + return errorInfo.Get_HRESULT_Error(); + } + + FStringVector fullPaths; + unsigned i; + + for (i = 0; i < options.Commands.Size(); i++) + { + CArchivePath &ap = options.Commands[i].ArchivePath; + const FString finalPath = us2fs(ap.GetFinalPath()); + FString arcPath2; + if (!MyGetFullPathName(finalPath, arcPath2)) + return errorInfo.SetFromLastError("GetFullPathName error", finalPath); + fullPaths.Add(arcPath2); + } + + /* + LPMAPISENDDOCUMENTS fnSend = (LPMAPISENDDOCUMENTS)mapiLib.GetProc("MAPISendDocuments"); + if (fnSend == 0) + { + errorInfo.SetFromLastError)("7-Zip cannot find MAPISendDocuments function"); + return errorInfo.Get_HRESULT_Error(); + } + */ + const + Z7_WIN_LPMAPISENDMAILW sendMailW = Z7_GET_PROC_ADDRESS( + Z7_WIN_LPMAPISENDMAILW, mapiLib.Get_HMODULE(), + "MAPISendMailW"); + if (sendMailW) + { + + CCurrentDirRestorer curDirRestorer; + + UStringVector paths; + UStringVector names; + + for (i = 0; i < fullPaths.Size(); i++) + { + const UString arcPath2 = fs2us(fullPaths[i]); + const UString fileName = ExtractFileNameFromPath(arcPath2); + paths.Add(arcPath2); + names.Add(fileName); + // Warning!!! MAPISendDocuments function changes Current directory + // fnSend(0, ";", (LPSTR)(LPCSTR)path, (LPSTR)(LPCSTR)name, 0); + } + + CRecordVector files; + files.ClearAndSetSize(paths.Size()); + + for (i = 0; i < paths.Size(); i++) + { + Z7_WIN_MapiFileDescW &f = files[i]; + memset(&f, 0, sizeof(f)); + f.nPosition = 0xFFFFFFFF; + f.lpszPathName = paths[i].Ptr_non_const(); + f.lpszFileName = names[i].Ptr_non_const(); + } + + { + Z7_WIN_MapiMessageW m; + memset(&m, 0, sizeof(m)); + m.nFileCount = files.Size(); + m.lpFiles = files.NonConstData(); + + const UString addr (options.EMailAddress); + Z7_WIN_MapiRecipDescW rec; + if (!addr.IsEmpty()) + { + memset(&rec, 0, sizeof(rec)); + rec.ulRecipClass = MAPI_TO; + rec.lpszAddress = addr.Ptr_non_const(); + m.nRecipCount = 1; + m.lpRecips = &rec; + } + + sendMailW((LHANDLE)0, 0, &m, MAPI_DIALOG, 0); + } + } + else + { + const + LPMAPISENDMAIL sendMail = Z7_GET_PROC_ADDRESS( + LPMAPISENDMAIL, mapiLib.Get_HMODULE(), + "MAPISendMail"); + if (!sendMail) + { + errorInfo.SetFromLastError("7-Zip cannot find MAPISendMail function"); + return errorInfo.Get_HRESULT_Error(); + } + + CCurrentDirRestorer curDirRestorer; + + AStringVector paths; + AStringVector names; + + for (i = 0; i < fullPaths.Size(); i++) + { + const UString arcPath2 = fs2us(fullPaths[i]); + const UString fileName = ExtractFileNameFromPath(arcPath2); + paths.Add(GetAnsiString(arcPath2)); + names.Add(GetAnsiString(fileName)); + // const AString path (GetAnsiString(arcPath2)); + // const AString name (GetAnsiString(fileName)); + // Warning!!! MAPISendDocuments function changes Current directory + // fnSend(0, ";", (LPSTR)(LPCSTR)path, (LPSTR)(LPCSTR)name, 0); + } + + CRecordVector files; + files.ClearAndSetSize(paths.Size()); + + for (i = 0; i < paths.Size(); i++) + { + MapiFileDesc &f = files[i]; + memset(&f, 0, sizeof(f)); + f.nPosition = 0xFFFFFFFF; + f.lpszPathName = paths[i].Ptr_non_const(); + f.lpszFileName = names[i].Ptr_non_const(); + } + + { + MapiMessage m; + memset(&m, 0, sizeof(m)); + m.nFileCount = files.Size(); + m.lpFiles = files.NonConstData(); + + const AString addr (GetAnsiString(options.EMailAddress)); + MapiRecipDesc rec; + if (!addr.IsEmpty()) + { + memset(&rec, 0, sizeof(rec)); + rec.ulRecipClass = MAPI_TO; + rec.lpszAddress = addr.Ptr_non_const(); + m.nRecipCount = 1; + m.lpRecips = &rec; + } + + sendMail((LHANDLE)0, 0, &m, MAPI_DIALOG, 0); + } + } + } + + #endif + + if (options.DeleteAfterCompressing) + { + CRecordVector pairs; + FStringVector foldersNames; + + unsigned i; + + for (i = 0; i < dirItems.Items.Size(); i++) + { + const CDirItem &dirItem = dirItems.Items[i]; + const FString phyPath = dirItems.GetPhyPath(i); + if (dirItem.IsDir()) + { + CDirPathSortPair pair; + pair.Index = i; + pair.SetNumSlashes(phyPath); + pairs.Add(pair); + } + else + { + // 21.04: we have set processedItems[*] before for all required items + if (processedItems[i] != 0 + // || dirItem.Size == 0 + // || dirItem.AreReparseData() + ) + { + NFind::CFileInfo fileInfo; + /* if (!SymLinks), we follow link here, similar to (dirItem) filling */ + if (fileInfo.Find(phyPath, !options.SymLinks.Val)) + { + bool is_SameSize = false; + if (options.SymLinks.Val && dirItem.AreReparseData()) + { + /* (dirItem.Size = dirItem.ReparseData.Size()) was set before. + So we don't compare sizes for that case here */ + is_SameSize = fileInfo.IsOsSymLink(); + } + else + is_SameSize = (fileInfo.Size == dirItem.Size); + + if (is_SameSize + && Compare_FiTime(&fileInfo.MTime, &dirItem.MTime) == 0 + && Compare_FiTime(&fileInfo.CTime, &dirItem.CTime) == 0) + { + RINOK(callback->DeletingAfterArchiving(phyPath, false)) + DeleteFileAlways(phyPath); + } + } + } + else + { + // file was skipped by some reason. We can throw error for debug: + /* + errorInfo.SystemError = 0; + errorInfo.Message = "file was not processed"; + errorInfo.FileNames.Add(phyPath); + return E_FAIL; + */ + } + } + } + + pairs.Sort2(); + + for (i = 0; i < pairs.Size(); i++) + { + const FString phyPath = dirItems.GetPhyPath(pairs[i].Index); + if (NFind::DoesDirExist(phyPath)) + { + RINOK(callback->DeletingAfterArchiving(phyPath, true)) + RemoveDirAlways_if_Empty(phyPath); + } + } + + RINOK(callback->FinishDeletingAfterArchiving()) + } + + return S_OK; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/Update.h b/src/plugins/7zip/7za/cpp/7zip/UI/Common/Update.h new file mode 100644 index 00000000..ae141e5e --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/Update.h @@ -0,0 +1,221 @@ +// Update.h + +#ifndef ZIP7_INC_COMMON_UPDATE_H +#define ZIP7_INC_COMMON_UPDATE_H + +#include "../../../Common/Wildcard.h" + +#include "ArchiveOpenCallback.h" +#include "LoadCodecs.h" +#include "OpenArchive.h" +#include "Property.h" +#include "UpdateAction.h" +#include "UpdateCallback.h" + +enum EArcNameMode +{ + k_ArcNameMode_Smart, + k_ArcNameMode_Exact, + k_ArcNameMode_Add +}; + +struct CArchivePath +{ + UString OriginalPath; + + UString Prefix; // path(folder) prefix including slash + UString Name; // base name + UString BaseExtension; // archive type extension or "exe" extension + UString VolExtension; // archive type extension for volumes + + bool Temp; + FString TempPrefix; // path(folder) for temp location + FString TempPostfix; + + CArchivePath(): Temp(false) {} + + void ParseFromPath(const UString &path, EArcNameMode mode); + UString GetPathWithoutExt() const { return Prefix + Name; } + UString GetFinalPath() const; + UString GetFinalVolPath() const; + FString GetTempPath() const; +}; + +struct CUpdateArchiveCommand +{ + UString UserArchivePath; + CArchivePath ArchivePath; + NUpdateArchive::CActionSet ActionSet; +}; + +struct CCompressionMethodMode +{ + bool Type_Defined; + COpenType Type; + CObjectVector Properties; + + CCompressionMethodMode(): Type_Defined(false) {} +}; + +namespace NRecursedType { enum EEnum +{ + kRecursed, + kWildcardOnlyRecursed, + kNonRecursed +};} + +struct CRenamePair +{ + UString OldName; + UString NewName; + bool WildcardParsing; + NRecursedType::EEnum RecursedType; + + CRenamePair(): WildcardParsing(true), RecursedType(NRecursedType::kNonRecursed) {} + + bool Prepare(); + bool GetNewPath(bool isFolder, const UString &src, UString &dest) const; +}; + +struct CUpdateOptions +{ + bool UpdateArchiveItself; + bool SfxMode; + + bool PreserveATime; + bool OpenShareForWrite; + bool StopAfterOpenError; + + bool StdInMode; + bool StdOutMode; + + bool EMailMode; + bool EMailRemoveAfter; + + bool DeleteAfterCompressing; + bool SetArcMTime; + bool RenameMode; + + CBoolPair NtSecurity; + CBoolPair AltStreams; + CBoolPair HardLinks; + CBoolPair SymLinks; + + CBoolPair StoreOwnerId; + CBoolPair StoreOwnerName; + + EArcNameMode ArcNameMode; + NWildcard::ECensorPathMode PathMode; + + CCompressionMethodMode MethodMode; + + CObjectVector Commands; + CArchivePath ArchivePath; + + FString SfxModule; + UString StdInFileName; + UString EMailAddress; + FString WorkingDir; + // UString AddPathPrefix; + + CObjectVector RenamePairs; + CRecordVector VolumesSizes; + + bool InitFormatIndex(const CCodecs *codecs, const CObjectVector &types, const UString &arcPath); + bool SetArcPath(const CCodecs *codecs, const UString &arcPath); + + CUpdateOptions(): + UpdateArchiveItself(true), + SfxMode(false), + + PreserveATime(false), + OpenShareForWrite(false), + StopAfterOpenError(false), + + StdInMode(false), + StdOutMode(false), + + EMailMode(false), + EMailRemoveAfter(false), + + DeleteAfterCompressing(false), + SetArcMTime(false), + RenameMode(false), + + ArcNameMode(k_ArcNameMode_Smart), + PathMode(NWildcard::k_RelatPath) + + {} + + void SetActionCommand_Add() + { + Commands.Clear(); + CUpdateArchiveCommand c; + c.ActionSet = NUpdateArchive::k_ActionSet_Add; + Commands.Add(c); + } +}; + + +struct CUpdateErrorInfo +{ + DWORD SystemError; // it's DWORD (WRes) only; + AString Message; + FStringVector FileNames; + + bool ThereIsError() const { return SystemError != 0 || !Message.IsEmpty() || !FileNames.IsEmpty(); } + HRESULT Get_HRESULT_Error() const { return SystemError == 0 ? E_FAIL : HRESULT_FROM_WIN32(SystemError); } + void SetFromLastError(const char *message); + HRESULT SetFromLastError(const char *message, const FString &fileName); + HRESULT SetFromError_DWORD(const char *message, const FString &fileName, DWORD error); + + CUpdateErrorInfo(): SystemError(0) {} +}; + +struct CFinishArchiveStat +{ + UInt64 OutArcFileSize; + unsigned NumVolumes; + bool IsMultiVolMode; + + CFinishArchiveStat(): OutArcFileSize(0), NumVolumes(0), IsMultiVolMode(false) {} +}; + +Z7_PURE_INTERFACES_BEGIN + +// INTERFACE_IUpdateCallbackUI(x) +// INTERFACE_IDirItemsCallback(x) + +#define Z7_IFACEN_IUpdateCallbackUI2(x) \ + virtual HRESULT OpenResult(const CCodecs *codecs, const CArchiveLink &arcLink, const wchar_t *name, HRESULT result) x \ + virtual HRESULT StartScanning() x \ + virtual HRESULT FinishScanning(const CDirItemsStat &st) x \ + virtual HRESULT StartOpenArchive(const wchar_t *name) x \ + virtual HRESULT StartArchive(const wchar_t *name, bool updating) x \ + virtual HRESULT FinishArchive(const CFinishArchiveStat &st) x \ + virtual HRESULT DeletingAfterArchiving(const FString &path, bool isDir) x \ + virtual HRESULT FinishDeletingAfterArchiving() x \ + virtual HRESULT MoveArc_Start(const wchar_t *srcTempPath, const wchar_t *destFinalPath, UInt64 size, Int32 updateMode) x \ + virtual HRESULT MoveArc_Progress(UInt64 total, UInt64 current) x \ + virtual HRESULT MoveArc_Finish() x \ + +DECLARE_INTERFACE(IUpdateCallbackUI2): + public IUpdateCallbackUI, + public IDirItemsCallback +{ + Z7_IFACE_PURE(IUpdateCallbackUI2) +}; +Z7_PURE_INTERFACES_END + +HRESULT UpdateArchive( + CCodecs *codecs, + const CObjectVector &types, + const UString &cmdArcPath2, + NWildcard::CCensor &censor, + CUpdateOptions &options, + CUpdateErrorInfo &errorInfo, + IOpenCallbackUI *openCallback, + IUpdateCallbackUI2 *callback, + bool needSetPath); + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/UpdateAction.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Common/UpdateAction.cpp new file mode 100644 index 00000000..a80db721 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/UpdateAction.cpp @@ -0,0 +1,64 @@ +// UpdateAction.cpp + +#include "StdAfx.h" + +#include "UpdateAction.h" + +namespace NUpdateArchive { + +const CActionSet k_ActionSet_Add = +{{ + NPairAction::kCopy, + NPairAction::kCopy, + NPairAction::kCompress, + NPairAction::kCompress, + NPairAction::kCompress, + NPairAction::kCompress, + NPairAction::kCompress +}}; + +const CActionSet k_ActionSet_Update = +{{ + NPairAction::kCopy, + NPairAction::kCopy, + NPairAction::kCompress, + NPairAction::kCopy, + NPairAction::kCompress, + NPairAction::kCopy, + NPairAction::kCompress +}}; + +const CActionSet k_ActionSet_Fresh = +{{ + NPairAction::kCopy, + NPairAction::kCopy, + NPairAction::kIgnore, + NPairAction::kCopy, + NPairAction::kCompress, + NPairAction::kCopy, + NPairAction::kCompress +}}; + +const CActionSet k_ActionSet_Sync = +{{ + NPairAction::kCopy, + NPairAction::kIgnore, + NPairAction::kCompress, + NPairAction::kCopy, + NPairAction::kCompress, + NPairAction::kCopy, + NPairAction::kCompress, +}}; + +const CActionSet k_ActionSet_Delete = +{{ + NPairAction::kCopy, + NPairAction::kIgnore, + NPairAction::kIgnore, + NPairAction::kIgnore, + NPairAction::kIgnore, + NPairAction::kIgnore, + NPairAction::kIgnore +}}; + +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/UpdateAction.h b/src/plugins/7zip/7za/cpp/7zip/UI/Common/UpdateAction.h new file mode 100644 index 00000000..6a3565e7 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/UpdateAction.h @@ -0,0 +1,66 @@ +// UpdateAction.h + +#ifndef ZIP7_INC_UPDATE_ACTION_H +#define ZIP7_INC_UPDATE_ACTION_H + +namespace NUpdateArchive { + + namespace NPairState + { + const unsigned kNumValues = 7; + enum EEnum + { + kNotMasked = 0, + kOnlyInArchive, + kOnlyOnDisk, + kNewInArchive, + kOldInArchive, + kSameFiles, + kUnknowNewerFiles + }; + } + + namespace NPairAction + { + enum EEnum + { + kIgnore = 0, + kCopy, + kCompress, + kCompressAsAnti + }; + } + + struct CActionSet + { + NPairAction::EEnum StateActions[NPairState::kNumValues]; + + bool IsEqualTo(const CActionSet &a) const + { + for (unsigned i = 0; i < NPairState::kNumValues; i++) + if (StateActions[i] != a.StateActions[i]) + return false; + return true; + } + + bool NeedScanning() const + { + unsigned i; + for (i = 0; i < NPairState::kNumValues; i++) + if (StateActions[i] == NPairAction::kCompress) + return true; + for (i = 1; i < NPairState::kNumValues; i++) + if (StateActions[i] != NPairAction::kIgnore) + return true; + return false; + } + }; + + extern const CActionSet k_ActionSet_Add; + extern const CActionSet k_ActionSet_Update; + extern const CActionSet k_ActionSet_Fresh; + extern const CActionSet k_ActionSet_Sync; + extern const CActionSet k_ActionSet_Delete; +} + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/UpdateCallback.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Common/UpdateCallback.cpp new file mode 100644 index 00000000..e2f18664 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/UpdateCallback.cpp @@ -0,0 +1,1069 @@ +// UpdateCallback.cpp + +#include "StdAfx.h" + +// #include + +#ifndef _WIN32 +// #include +// #include +// for major()/minor(): +#if defined(__APPLE__) || defined(__DragonFly__) || \ + defined(BSD) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) +#include +#else +#include +#endif + +#endif // _WIN32 + +#ifndef Z7_ST +#include "../../../Windows/Synchronization.h" +#endif + +#include "../../../Common/ComTry.h" +#include "../../../Common/IntToString.h" +#include "../../../Common/StringConvert.h" +#include "../../../Common/Wildcard.h" +#include "../../../Common/UTFConvert.h" + +#include "../../../Windows/FileDir.h" +#include "../../../Windows/FileName.h" +#include "../../../Windows/PropVariant.h" + +#include "../../Common/StreamObjects.h" +#include "../../Archive/Common/ItemNameUtils.h" + +#include "UpdateCallback.h" + +#if defined(_WIN32) && !defined(UNDER_CE) +#define Z7_USE_SECURITY_CODE +#include "../../../Windows/SecurityUtils.h" +#endif + +using namespace NWindows; +using namespace NFile; + +#ifndef Z7_ST +static NSynchronization::CCriticalSection g_CriticalSection; +#define MT_LOCK NSynchronization::CCriticalSectionLock lock(g_CriticalSection); +#else +#define MT_LOCK +#endif + + +#ifdef Z7_USE_SECURITY_CODE +bool InitLocalPrivileges(); +#endif + +CArchiveUpdateCallback::CArchiveUpdateCallback(): + PreserveATime(false), + ShareForWrite(false), + StopAfterOpenError(false), + StdInMode(false), + + KeepOriginalItemNames(false), + StoreNtSecurity(false), + StoreHardLinks(false), + StoreSymLinks(false), + + #ifndef _WIN32 + StoreOwnerId(false), + StoreOwnerName(false), + #endif + + /* + , Need_ArcMTime_Report(false), + , ArcMTime_WasReported(false), + */ + Need_LatestMTime(false), + LatestMTime_Defined(false), + + Callback(NULL), + + DirItems(NULL), + ParentDirItem(NULL), + + Arc(NULL), + ArcItems(NULL), + UpdatePairs(NULL), + NewNames(NULL), + Comment(NULL), + CommentIndex(-1), + + ProcessedItemsStatuses(NULL), + _hardIndex_From((UInt32)(Int32)-1) +{ + #ifdef Z7_USE_SECURITY_CODE + _saclEnabled = InitLocalPrivileges(); + #endif +} + + +Z7_COM7F_IMF(CArchiveUpdateCallback::SetTotal(UInt64 size)) +{ + COM_TRY_BEGIN + return Callback->SetTotal(size); + COM_TRY_END +} + +Z7_COM7F_IMF(CArchiveUpdateCallback::SetCompleted(const UInt64 *completeValue)) +{ + COM_TRY_BEGIN + return Callback->SetCompleted(completeValue); + COM_TRY_END +} + +Z7_COM7F_IMF(CArchiveUpdateCallback::SetRatioInfo(const UInt64 *inSize, const UInt64 *outSize)) +{ + COM_TRY_BEGIN + return Callback->SetRatioInfo(inSize, outSize); + COM_TRY_END +} + + +/* +static const CStatProp kProps[] = +{ + { NULL, kpidPath, VT_BSTR}, + { NULL, kpidIsDir, VT_BOOL}, + { NULL, kpidSize, VT_UI8}, + { NULL, kpidCTime, VT_FILETIME}, + { NULL, kpidATime, VT_FILETIME}, + { NULL, kpidMTime, VT_FILETIME}, + { NULL, kpidAttrib, VT_UI4}, + { NULL, kpidIsAnti, VT_BOOL} +}; + +Z7_COM7F_IMF(CArchiveUpdateCallback::EnumProperties(IEnumSTATPROPSTG **) +{ + return CStatPropEnumerator::CreateEnumerator(kProps, Z7_ARRAY_SIZE(kProps), enumerator); +} +*/ + +Z7_COM7F_IMF(CArchiveUpdateCallback::GetUpdateItemInfo(UInt32 index, + Int32 *newData, Int32 *newProps, UInt32 *indexInArchive)) +{ + COM_TRY_BEGIN + RINOK(Callback->CheckBreak()) + const CUpdatePair2 &up = (*UpdatePairs)[index]; + if (newData) *newData = BoolToInt(up.NewData); + if (newProps) *newProps = BoolToInt(up.NewProps); + if (indexInArchive) + { + *indexInArchive = (UInt32)(Int32)-1; + if (up.ExistInArchive()) + *indexInArchive = ArcItems ? (*ArcItems)[(unsigned)up.ArcIndex].IndexInServer : (UInt32)(Int32)up.ArcIndex; + } + return S_OK; + COM_TRY_END +} + + +Z7_COM7F_IMF(CArchiveUpdateCallback::GetRootProp(PROPID propID, PROPVARIANT *value)) +{ + NCOM::CPropVariant prop; + switch (propID) + { + case kpidIsDir: prop = true; break; + case kpidAttrib: if (ParentDirItem) prop = ParentDirItem->GetWinAttrib(); break; + case kpidCTime: if (ParentDirItem) PropVariant_SetFrom_FiTime(prop, ParentDirItem->CTime); break; + case kpidATime: if (ParentDirItem) PropVariant_SetFrom_FiTime(prop, ParentDirItem->ATime); break; + case kpidMTime: if (ParentDirItem) PropVariant_SetFrom_FiTime(prop, ParentDirItem->MTime); break; + case kpidArcFileName: if (!ArcFileName.IsEmpty()) prop = ArcFileName; break; + default: break; + } + prop.Detach(value); + return S_OK; +} + +Z7_COM7F_IMF(CArchiveUpdateCallback::GetParent(UInt32 /* index */, UInt32 *parent, UInt32 *parentType)) +{ + *parentType = NParentType::kDir; + *parent = (UInt32)(Int32)-1; + return S_OK; +} + +Z7_COM7F_IMF(CArchiveUpdateCallback::GetNumRawProps(UInt32 *numProps)) +{ + *numProps = 0; + if (StoreNtSecurity) + *numProps = 1; + return S_OK; +} + +Z7_COM7F_IMF(CArchiveUpdateCallback::GetRawPropInfo(UInt32 /* index */, BSTR *name, PROPID *propID)) +{ + *name = NULL; + *propID = kpidNtSecure; + return S_OK; +} + +Z7_COM7F_IMF(CArchiveUpdateCallback::GetRootRawProp(PROPID + propID + , const void **data, UInt32 *dataSize, UInt32 *propType)) +{ + #ifndef Z7_USE_SECURITY_CODE + UNUSED_VAR(propID) + #endif + + *data = NULL; + *dataSize = 0; + *propType = 0; + if (!StoreNtSecurity) + return S_OK; + #ifdef Z7_USE_SECURITY_CODE + if (propID == kpidNtSecure) + { + if (StdInMode) + return S_OK; + + if (ParentDirItem) + { + if (ParentDirItem->SecureIndex < 0) + return S_OK; + const CByteBuffer &buf = DirItems->SecureBlocks.Bufs[(unsigned)ParentDirItem->SecureIndex]; + *data = buf; + *dataSize = (UInt32)buf.Size(); + *propType = NPropDataType::kRaw; + return S_OK; + } + + if (Arc && Arc->GetRootProps) + return Arc->GetRootProps->GetRootRawProp(propID, data, dataSize, propType); + } + #endif + return S_OK; +} + + +Z7_COM7F_IMF(CArchiveUpdateCallback::GetRawProp(UInt32 index, PROPID propID, const void **data, UInt32 *dataSize, UInt32 *propType)) +{ + *data = NULL; + *dataSize = 0; + *propType = 0; + + if (propID == kpidNtSecure || + propID == kpidNtReparse) + { + if (StdInMode) + return S_OK; + + const CUpdatePair2 &up = (*UpdatePairs)[index]; + if (up.UseArcProps && up.ExistInArchive() && Arc->GetRawProps) + return Arc->GetRawProps->GetRawProp( + ArcItems ? (*ArcItems)[(unsigned)up.ArcIndex].IndexInServer : (UInt32)(Int32)up.ArcIndex, + propID, data, dataSize, propType); + { + /* + if (!up.NewData) + return E_FAIL; + */ + if (up.IsAnti) + return S_OK; + + #if defined(_WIN32) && !defined(UNDER_CE) + const CDirItem &di = DirItems->Items[(unsigned)up.DirIndex]; + #endif + + #ifdef Z7_USE_SECURITY_CODE + if (propID == kpidNtSecure) + { + if (!StoreNtSecurity) + return S_OK; + if (di.SecureIndex < 0) + return S_OK; + const CByteBuffer &buf = DirItems->SecureBlocks.Bufs[(unsigned)di.SecureIndex]; + *data = buf; + *dataSize = (UInt32)buf.Size(); + *propType = NPropDataType::kRaw; + } + else + #endif + if (propID == kpidNtReparse) + { + if (!StoreSymLinks) + return S_OK; + #if defined(_WIN32) && !defined(UNDER_CE) + // we use ReparseData2 instead of ReparseData for WIM format + const CByteBuffer *buf = &di.ReparseData2; + if (buf->Size() == 0) + buf = &di.ReparseData; + if (buf->Size() != 0) + { + *data = *buf; + *dataSize = (UInt32)buf->Size(); + *propType = NPropDataType::kRaw; + } + #endif + } + + return S_OK; + } + } + + return S_OK; +} + +#if defined(_WIN32) && !defined(UNDER_CE) + +static UString GetRelativePath(const UString &to, const UString &from, bool isWSL) +{ + UStringVector partsTo, partsFrom; + SplitPathToParts(to, partsTo); + SplitPathToParts(from, partsFrom); + + unsigned i; + for (i = 0;; i++) + { + if (i + 1 >= partsFrom.Size() || + i + 1 >= partsTo.Size()) + break; + if (CompareFileNames(partsFrom[i], partsTo[i]) != 0) + break; + } + + if (i == 0) + { +#ifdef _WIN32 + if (isWSL || + (NName::IsDrivePath(to) || + NName::IsDrivePath(from))) + return to; +#endif + } + + UString s; + unsigned k; + + for (k = i + 1; k < partsFrom.Size(); k++) + s += ".." STRING_PATH_SEPARATOR; + + for (k = i; k < partsTo.Size(); k++) + { + if (k != i) + s.Add_PathSepar(); + s += partsTo[k]; + } + + return s; +} + +#endif + +Z7_COM7F_IMF(CArchiveUpdateCallback::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) +{ + COM_TRY_BEGIN + const CUpdatePair2 &up = (*UpdatePairs)[index]; + NCOM::CPropVariant prop; + + if (up.NewData) + { + /* + if (propID == kpidIsHardLink) + { + prop = _isHardLink; + prop.Detach(value); + return S_OK; + } + */ + if (propID == kpidSymLink) + { + if (index == _hardIndex_From) + { + prop.Detach(value); + return S_OK; + } + +#if !defined(UNDER_CE) + if (up.DirIndex >= 0) + { + const CDirItem &di = DirItems->Items[(unsigned)up.DirIndex]; + if (di.ReparseData.Size()) + { +#ifdef _WIN32 + CReparseAttr attr; + if (attr.Parse(di.ReparseData, di.ReparseData.Size())) + { + UString path = attr.GetPath(); + if (!path.IsEmpty()) + { + bool isWSL = attr.IsSymLink_WSL(); + if (isWSL) + NArchive::NItemName::ReplaceToWinSlashes(path, true); // useBackslashReplacement + // it's expected that (path) now uses windows slashes. + // CReparseAttr::IsRelative_Win() returns true if FLAG_RELATIVE is set + // CReparseAttr::IsRelative_Win() returns true for "\dir1\path" + // but we want to store real relative paths without "\" root prefix. + // so we parse path instead of IsRelative_Win() calling. + if (// attr.IsRelative_Win() || + (isWSL ? + IS_PATH_SEPAR(path[0]) : + NName::IsAbsolutePath(path))) + { + // (path) is abolute path or relative to root: "\path" + // we try to convert (path) to relative path for writing to archive. + const FString phyPath = DirItems->GetPhyPath((unsigned)up.DirIndex); + FString fullPath; + if (NDir::MyGetFullPathName(phyPath, fullPath)) + { + if (IS_PATH_SEPAR(path[0]) && + !IS_PATH_SEPAR(path[1])) + { + // path is relative to root of (fullPath): "\path" + const unsigned prefixSize = NName::GetRootPrefixSize(fullPath); + if (prefixSize) + { + path.DeleteFrontal(1); + path.Insert(0, fs2us(fullPath.Left(prefixSize))); + // we have changed "\" prefix to drive prefix "c:\" in (path). + // (path) is Windows path now. + isWSL = false; + } + } + } + path = GetRelativePath(path, fs2us(fullPath), isWSL); + } +#if WCHAR_PATH_SEPARATOR != L'/' + // 7-Zip's TAR handler in Windows replaces windows slashes to linux slashes. + // so we can return any slashes to TAR handler. + // or we can convert to linux slashes here, + // because input IInArchive handler uses linux slashes for kpidSymLink. + // path.Replace(WCHAR_PATH_SEPARATOR, L'/'); +#endif + if (!path.IsEmpty()) + prop = path; + } + } +#else // ! _WIN32 + AString utf; + utf.SetFrom_CalcLen((const char *)(const Byte *)di.ReparseData, (unsigned)di.ReparseData.Size()); + #if 0 // 0 - for debug + // it's expected that link data uses system codepage. + // fs2us() ignores conversion errors. But we want correct path + UString us (fs2us(utf)); + #else + UString us; + if (ConvertUTF8ToUnicode(utf, us)) + #endif + { + if (!us.IsEmpty()) + prop = us; + } +#endif // ! _WIN32 + } + prop.Detach(value); + return S_OK; + } +#endif // !defined(UNDER_CE) + } + else if (propID == kpidHardLink) + { + if (index == _hardIndex_From) + { + const CKeyKeyValPair &pair = _map[_hardIndex_To]; + const CUpdatePair2 &up2 = (*UpdatePairs)[pair.Value]; + const UString path = DirItems->GetLogPath((unsigned)up2.DirIndex); +#if WCHAR_PATH_SEPARATOR != L'/' + // 7-Zip's TAR handler in Windows replaces windows slashes to linux slashes. + // path.Replace(WCHAR_PATH_SEPARATOR, L'/'); +#endif + prop = path; + prop.Detach(value); + return S_OK; + } + if (up.DirIndex >= 0) + { + prop.Detach(value); + return S_OK; + } + } + } // if (up.NewData) + + if (up.IsAnti + && propID != kpidIsDir + && propID != kpidPath + && propID != kpidIsAltStream) + { + switch (propID) + { + case kpidSize: prop = (UInt64)0; break; + case kpidIsAnti: prop = true; break; + default: break; + } + } + else if (propID == kpidPath && up.NewNameIndex >= 0) + prop = (*NewNames)[(unsigned)up.NewNameIndex]; + else if (propID == kpidComment + && CommentIndex >= 0 + && (unsigned)CommentIndex == index + && Comment) + prop = *Comment; + else if (propID == kpidShortName && up.NewNameIndex >= 0 && up.IsMainRenameItem) + { + // we can generate new ShortName here; + } + else if ((up.UseArcProps || (KeepOriginalItemNames && (propID == kpidPath || propID == kpidIsAltStream))) + && up.ExistInArchive() && Archive) + return Archive->GetProperty(ArcItems ? (*ArcItems)[(unsigned)up.ArcIndex].IndexInServer : (UInt32)(Int32)up.ArcIndex, propID, value); + else if (up.ExistOnDisk()) + { + const CDirItem &di = DirItems->Items[(unsigned)up.DirIndex]; + switch (propID) + { + case kpidPath: prop = DirItems->GetLogPath((unsigned)up.DirIndex); break; + case kpidIsDir: prop = di.IsDir(); break; + case kpidSize: prop = (UInt64)(di.IsDir() ? (UInt64)0 : di.Size); break; + case kpidCTime: PropVariant_SetFrom_FiTime(prop, di.CTime); break; + case kpidATime: PropVariant_SetFrom_FiTime(prop, di.ATime); break; + case kpidMTime: PropVariant_SetFrom_FiTime(prop, di.MTime); break; + case kpidAttrib: /* if (di.Attrib_IsDefined) */ prop = (UInt32)di.GetWinAttrib(); break; + case kpidPosixAttrib: /* if (di.Attrib_IsDefined) */ prop = (UInt32)di.GetPosixAttrib(); break; + + #if defined(_WIN32) + case kpidIsAltStream: prop = di.IsAltStream; break; + // case kpidShortName: prop = di.ShortName; break; + #else + + #if defined(__APPLE__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wsign-conversion" + #endif + + case kpidDeviceMajor: + /* + printf("\ndi.mode = %o\n", di.mode); + printf("\nst.st_rdev major = %d\n", (unsigned)major(di.rdev)); + printf("\nst.st_rdev minor = %d\n", (unsigned)minor(di.rdev)); + */ + if (S_ISCHR(di.mode) || S_ISBLK(di.mode)) + prop = (UInt32)major(di.rdev); + break; + + case kpidDeviceMinor: + if (S_ISCHR(di.mode) || S_ISBLK(di.mode)) + prop = (UInt32)minor(di.rdev); + break; + + #if defined(__APPLE__) + #pragma GCC diagnostic pop + #endif + + // case kpidDevice: if (S_ISCHR(di.mode) || S_ISBLK(di.mode)) prop = (UInt64)(di.rdev); break; + + case kpidUserId: if (StoreOwnerId) prop = (UInt32)di.uid; break; + case kpidGroupId: if (StoreOwnerId) prop = (UInt32)di.gid; break; + case kpidUser: + if (di.OwnerNameIndex >= 0) + prop = DirItems->OwnerNameMap.Strings[(unsigned)di.OwnerNameIndex]; + break; + case kpidGroup: + if (di.OwnerGroupIndex >= 0) + prop = DirItems->OwnerGroupMap.Strings[(unsigned)di.OwnerGroupIndex]; + break; + #endif + default: break; + } + } + prop.Detach(value); + return S_OK; + COM_TRY_END +} + +#ifndef Z7_ST +static NSynchronization::CCriticalSection g_CS; +#endif + +void CArchiveUpdateCallback::UpdateProcessedItemStatus(unsigned dirIndex) +{ + if (ProcessedItemsStatuses) + { + #ifndef Z7_ST + NSynchronization::CCriticalSectionLock lock(g_CS); + #endif + ProcessedItemsStatuses[dirIndex] = 1; + } +} + +Z7_COM7F_IMF(CArchiveUpdateCallback::GetStream2(UInt32 index, ISequentialInStream **inStream, UInt32 mode)) +{ + COM_TRY_BEGIN + *inStream = NULL; + const CUpdatePair2 &up = (*UpdatePairs)[index]; + if (!up.NewData) + return E_FAIL; + + RINOK(Callback->CheckBreak()) + // RINOK(Callback->Finalize()); + + bool isDir = IsDir(up); + + if (up.IsAnti) + { + UString name; + if (up.ArcIndex >= 0) + name = (*ArcItems)[(unsigned)up.ArcIndex].Name; + else if (up.DirIndex >= 0) + name = DirItems->GetLogPath((unsigned)up.DirIndex); + RINOK(Callback->GetStream(name, isDir, true, mode)) + + /* 9.33: fixed. Handlers expect real stream object for files, even for anti-file. + so we return empty stream */ + + if (!isDir) + { + CBufInStream *inStreamSpec = new CBufInStream(); + CMyComPtr inStreamLoc = inStreamSpec; + inStreamSpec->Init(NULL, 0); + *inStream = inStreamLoc.Detach(); + } + return S_OK; + } + + RINOK(Callback->GetStream(DirItems->GetLogPath((unsigned)up.DirIndex), isDir, false, mode)) + + if (isDir) + return S_OK; + + if (StdInMode) + { + if (mode != NUpdateNotifyOp::kAdd && + mode != NUpdateNotifyOp::kUpdate) + return S_OK; + +#if 1 + CStdInFileStream *inStreamSpec = new CStdInFileStream; + CMyComPtr inStreamLoc(inStreamSpec); +#else + CMyComPtr inStreamLoc; + if (!CreateStdInStream(inStreamLoc)) + return GetLastError_noZero_HRESULT(); +#endif + *inStream = inStreamLoc.Detach(); + } + else + { + #if !defined(UNDER_CE) + const CDirItem &di = DirItems->Items[(unsigned)up.DirIndex]; + if (di.AreReparseData()) + { + /* + // we still need DeviceIoControlOut() instead of Read + if (!inStreamSpec->File.OpenReparse(path)) + { + return Callback->OpenFileError(path, ::GetLastError()); + } + */ + // 20.03: we use Reparse Data instead of real data + + CBufInStream *inStreamSpec = new CBufInStream(); + CMyComPtr inStreamLoc = inStreamSpec; + inStreamSpec->Init(di.ReparseData, di.ReparseData.Size()); + *inStream = inStreamLoc.Detach(); + + UpdateProcessedItemStatus((unsigned)up.DirIndex); + return S_OK; + } + #endif // !defined(UNDER_CE) + + CInFileStream *inStreamSpec = new CInFileStream; + CMyComPtr inStreamLoc(inStreamSpec); + + /* + // for debug: + #ifdef _WIN32 + inStreamSpec->StoreOwnerName = true; + inStreamSpec->OwnerName = "user_name"; + inStreamSpec->OwnerName += di.Name; + inStreamSpec->OwnerName += "11111111112222222222222333333333333"; + inStreamSpec->OwnerGroup = "gname_"; + inStreamSpec->OwnerGroup += inStreamSpec->OwnerName; + #endif + */ + + #ifndef _WIN32 + inStreamSpec->StoreOwnerId = StoreOwnerId; + inStreamSpec->StoreOwnerName = StoreOwnerName; + + // if (StoreOwner) + { + inStreamSpec->_uid = di.uid; + inStreamSpec->_gid = di.gid; + if (di.OwnerNameIndex >= 0) + inStreamSpec->OwnerName = DirItems->OwnerNameMap.Strings[(unsigned)di.OwnerNameIndex]; + if (di.OwnerGroupIndex >= 0) + inStreamSpec->OwnerGroup = DirItems->OwnerGroupMap.Strings[(unsigned)di.OwnerGroupIndex]; + } + #endif + + inStreamSpec->SupportHardLinks = StoreHardLinks; + const bool preserveATime = (PreserveATime + || mode == NUpdateNotifyOp::kAnalyze); // 22.00 : we don't change access time in Analyze pass. + inStreamSpec->Set_PreserveATime(preserveATime); + + const FString path = DirItems->GetPhyPath((unsigned)up.DirIndex); + _openFiles_Indexes.Add(index); + _openFiles_Paths.Add(path); + // _openFiles_Streams.Add(inStreamSpec); + + /* 21.02 : we set Callback/CallbackRef after _openFiles_Indexes adding + for correct working if exception was raised in GetPhyPath */ + inStreamSpec->Callback = this; + inStreamSpec->CallbackRef = index; + + if (!inStreamSpec->OpenShared(path, ShareForWrite)) + { + bool isOpen = false; + if (preserveATime) + { + inStreamSpec->Set_PreserveATime(false); + isOpen = inStreamSpec->OpenShared(path, ShareForWrite); + } + if (!isOpen) + { + const DWORD error = ::GetLastError(); + const HRESULT hres = Callback->OpenFileError(path, error); + if (hres == S_OK || hres == S_FALSE) + if (StopAfterOpenError || + // v23: we check also for some critical errors: + #ifdef _WIN32 + error == ERROR_NO_SYSTEM_RESOURCES + #else + error == EMFILE + #endif + ) + { + if (error == 0) + return E_FAIL; + return HRESULT_FROM_WIN32(error); + } + return hres; + } + } + + /* + { + // for debug: + Byte b = 0; + UInt32 processedSize = 0; + if (inStreamSpec->Read(&b, 1, &processedSize) != S_OK || + processedSize != 1) + return E_FAIL; + } + */ + + if (Need_LatestMTime) + { + inStreamSpec->ReloadProps(); + } + + // #if defined(Z7_FILE_STREAMS_USE_WIN_FILE) || !defined(_WIN32) + if (StoreHardLinks) + { + CStreamFileProps props; + if (inStreamSpec->GetProps2(&props) == S_OK) + { + if (props.NumLinks > 1) + { + CKeyKeyValPair pair; + pair.Key1 = props.VolID; + pair.Key2 = props.FileID_Low; + pair.Value = index; + const unsigned numItems = _map.Size(); + const unsigned pairIndex = _map.AddToUniqueSorted2(pair); + if (numItems == _map.Size()) + { + // const CKeyKeyValPair &pair2 = _map.Pairs[pairIndex]; + _hardIndex_From = index; + _hardIndex_To = pairIndex; + // we could return NULL as stream, but it's better to return real stream + // return S_OK; + } + } + } + } + // #endif + + UpdateProcessedItemStatus((unsigned)up.DirIndex); + *inStream = inStreamLoc.Detach(); + } + + return S_OK; + COM_TRY_END +} + +Z7_COM7F_IMF(CArchiveUpdateCallback::SetOperationResult(Int32 opRes)) +{ + COM_TRY_BEGIN + return Callback->SetOperationResult(opRes); + COM_TRY_END +} + +Z7_COM7F_IMF(CArchiveUpdateCallback::GetStream(UInt32 index, ISequentialInStream **inStream)) +{ + COM_TRY_BEGIN + return GetStream2(index, inStream, + (*UpdatePairs)[index].ArcIndex < 0 ? + NUpdateNotifyOp::kAdd : + NUpdateNotifyOp::kUpdate); + COM_TRY_END +} + +Z7_COM7F_IMF(CArchiveUpdateCallback::ReportOperation(UInt32 indexType, UInt32 index, UInt32 op)) +{ + COM_TRY_BEGIN + + // if (op == NUpdateNotifyOp::kOpFinished) return Callback->ReportFinished(indexType, index); + + bool isDir = false; + + if (indexType == NArchive::NEventIndexType::kOutArcIndex) + { + UString name; + if (index != (UInt32)(Int32)-1) + { + const CUpdatePair2 &up = (*UpdatePairs)[index]; + if (up.ExistOnDisk()) + { + name = DirItems->GetLogPath((unsigned)up.DirIndex); + isDir = DirItems->Items[(unsigned)up.DirIndex].IsDir(); + } + } + return Callback->ReportUpdateOperation(op, name.IsEmpty() ? NULL : name.Ptr(), isDir); + } + + wchar_t temp[16]; + UString s2; + const wchar_t *s = NULL; + + if (indexType == NArchive::NEventIndexType::kInArcIndex) + { + if (index != (UInt32)(Int32)-1) + { + if (ArcItems) + { + const CArcItem &ai = (*ArcItems)[index]; + s = ai.Name; + isDir = ai.IsDir; + } + else if (Arc) + { + RINOK(Arc->GetItem_Path(index, s2)) + s = s2; + RINOK(Archive_IsItem_Dir(Arc->Archive, index, isDir)) + } + } + } + else if (indexType == NArchive::NEventIndexType::kBlockIndex) + { + temp[0] = '#'; + ConvertUInt32ToString(index, temp + 1); + s = temp; + } + + if (!s) + s = L""; + + return Callback->ReportUpdateOperation(op, s, isDir); + + COM_TRY_END +} + +Z7_COM7F_IMF(CArchiveUpdateCallback::ReportExtractResult(UInt32 indexType, UInt32 index, Int32 opRes)) +{ + COM_TRY_BEGIN + + bool isEncrypted = false; + wchar_t temp[16]; + UString s2; + const wchar_t *s = NULL; + + if (indexType == NArchive::NEventIndexType::kOutArcIndex) + { + /* + UString name; + if (index != (UInt32)(Int32)-1) + { + const CUpdatePair2 &up = (*UpdatePairs)[index]; + if (up.ExistOnDisk()) + { + s2 = DirItems->GetLogPath(up.DirIndex); + s = s2; + } + } + */ + return E_FAIL; + } + + if (indexType == NArchive::NEventIndexType::kInArcIndex) + { + if (index != (UInt32)(Int32)-1) + { + if (ArcItems) + s = (*ArcItems)[index].Name; + else if (Arc) + { + RINOK(Arc->GetItem_Path(index, s2)) + s = s2; + } + if (Archive) + { + RINOK(Archive_GetItemBoolProp(Archive, index, kpidEncrypted, isEncrypted)) + } + } + } + else if (indexType == NArchive::NEventIndexType::kBlockIndex) + { + temp[0] = '#'; + ConvertUInt32ToString(index, temp + 1); + s = temp; + } + + return Callback->ReportExtractResult(opRes, BoolToInt(isEncrypted), s); + + COM_TRY_END +} + + +/* +Z7_COM7F_IMF(CArchiveUpdateCallback::DoNeedArcProp(PROPID propID, Int32 *answer)) +{ + *answer = 0; + if (Need_ArcMTime_Report && propID == kpidComboMTime) + *answer = 1; + return S_OK; +} + +Z7_COM7F_IMF(CArchiveUpdateCallback::ReportProp(UInt32 indexType, UInt32 index, PROPID propID, const PROPVARIANT *value)) +{ + if (indexType == NArchive::NEventIndexType::kArcProp) + { + if (propID == kpidComboMTime) + { + ArcMTime_WasReported = true; + if (value->vt == VT_FILETIME) + { + Reported_ArcMTime.Set_From_Prop(*value); + Reported_ArcMTime.Def = true; + } + else + { + Reported_ArcMTime.Clear(); + if (value->vt != VT_EMPTY) + return E_FAIL; // for debug + } + } + } + return Callback->ReportProp(indexType, index, propID, value); +} + +Z7_COM7F_IMF(CArchiveUpdateCallback::ReportRawProp(UInt32 indexType, UInt32 index, + PROPID propID, const void *data, UInt32 dataSize, UInt32 propType)) +{ + return Callback->ReportRawProp(indexType, index, propID, data, dataSize, propType); +} + +Z7_COM7F_IMF(CArchiveUpdateCallback::ReportFinished(UInt32 indexType, UInt32 index, Int32 opRes)) +{ + return Callback->ReportFinished(indexType, index, opRes); +} +*/ + +Z7_COM7F_IMF(CArchiveUpdateCallback::GetVolumeSize(UInt32 index, UInt64 *size)) +{ + if (VolumesSizes.Size() == 0) + return S_FALSE; + if (index >= (UInt32)VolumesSizes.Size()) + index = VolumesSizes.Size() - 1; + *size = VolumesSizes[index]; + return S_OK; +} + +Z7_COM7F_IMF(CArchiveUpdateCallback::GetVolumeStream(UInt32 index, ISequentialOutStream **volumeStream)) +{ + COM_TRY_BEGIN + char temp[16]; + ConvertUInt32ToString(index + 1, temp); + FString res (temp); + while (res.Len() < 2) + res.InsertAtFront(FTEXT('0')); + FString fileName = VolName; + fileName.Add_Dot(); + fileName += res; + fileName += VolExt; + COutFileStream *streamSpec = new COutFileStream; + CMyComPtr streamLoc(streamSpec); + if (!streamSpec->Create_NEW(fileName)) + return GetLastError_noZero_HRESULT(); + *volumeStream = streamLoc.Detach(); + return S_OK; + COM_TRY_END +} + +Z7_COM7F_IMF(CArchiveUpdateCallback::CryptoGetTextPassword2(Int32 *passwordIsDefined, BSTR *password)) +{ + COM_TRY_BEGIN + return Callback->CryptoGetTextPassword2(passwordIsDefined, password); + COM_TRY_END +} + +Z7_COM7F_IMF(CArchiveUpdateCallback::CryptoGetTextPassword(BSTR *password)) +{ + COM_TRY_BEGIN + return Callback->CryptoGetTextPassword(password); + COM_TRY_END +} + +HRESULT CArchiveUpdateCallback::InFileStream_On_Error(UINT_PTR val, DWORD error) +{ + #ifdef _WIN32 // FIX IT !!! + // why did we check only for ERROR_LOCK_VIOLATION ? + // if (error == ERROR_LOCK_VIOLATION) + #endif + { + MT_LOCK + const UInt32 index = (UInt32)val; + FOR_VECTOR(i, _openFiles_Indexes) + { + if (_openFiles_Indexes[i] == index) + { + RINOK(Callback->ReadingFileError(_openFiles_Paths[i], error)) + break; + } + } + } + return HRESULT_FROM_WIN32(error); +} + +void CArchiveUpdateCallback::InFileStream_On_Destroy(CInFileStream *stream, UINT_PTR val) +{ + MT_LOCK + if (Need_LatestMTime) + { + if (stream->_info_WasLoaded) + { + const CFiTime &ft = ST_MTIME(stream->_info); + if (!LatestMTime_Defined + || Compare_FiTime(&LatestMTime, &ft) < 0) + LatestMTime = ft; + LatestMTime_Defined = true; + } + } + const UInt32 index = (UInt32)val; + FOR_VECTOR(i, _openFiles_Indexes) + { + if (_openFiles_Indexes[i] == index) + { + _openFiles_Indexes.Delete(i); + _openFiles_Paths.Delete(i); + // _openFiles_Streams.Delete(i); + return; + } + } + /* 21.02 : this function can be called in destructor. + And destructor can be called after some exception. + If we don't want to throw exception in desctructors or after another exceptions, + we must disable the code below that raises new exception. + */ + // throw 20141125; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/UpdateCallback.h b/src/plugins/7zip/7za/cpp/7zip/UI/Common/UpdateCallback.h new file mode 100644 index 00000000..379b8146 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/UpdateCallback.h @@ -0,0 +1,197 @@ +// UpdateCallback.h + +#ifndef ZIP7_INC_UPDATE_CALLBACK_H +#define ZIP7_INC_UPDATE_CALLBACK_H + +#include "../../../Common/MyCom.h" + +#include "../../Common/FileStreams.h" + +#include "../../IPassword.h" +#include "../../ICoder.h" + +#include "../Common/UpdatePair.h" +#include "../Common/UpdateProduce.h" + +#include "OpenArchive.h" + +struct CArcToDoStat +{ + CDirItemsStat2 NewData; + CDirItemsStat2 OldData; + CDirItemsStat2 DeleteData; + + UInt64 Get_NumDataItems_Total() const + { + return NewData.Get_NumDataItems2() + OldData.Get_NumDataItems2(); + } +}; + + +Z7_PURE_INTERFACES_BEGIN + +#define Z7_IFACEN_IUpdateCallbackUI(x) \ + virtual HRESULT WriteSfx(const wchar_t *name, UInt64 size) x \ + virtual HRESULT SetTotal(UInt64 size) x \ + virtual HRESULT SetCompleted(const UInt64 *completeValue) x \ + virtual HRESULT SetRatioInfo(const UInt64 *inSize, const UInt64 *outSize) x \ + virtual HRESULT CheckBreak() x \ + /* virtual HRESULT Finalize() x */ \ + virtual HRESULT SetNumItems(const CArcToDoStat &stat) x \ + virtual HRESULT GetStream(const wchar_t *name, bool isDir, bool isAnti, UInt32 mode) x \ + virtual HRESULT OpenFileError(const FString &path, DWORD systemError) x \ + virtual HRESULT ReadingFileError(const FString &path, DWORD systemError) x \ + virtual HRESULT SetOperationResult(Int32 opRes) x \ + virtual HRESULT ReportExtractResult(Int32 opRes, Int32 isEncrypted, const wchar_t *name) x \ + virtual HRESULT ReportUpdateOperation(UInt32 op, const wchar_t *name, bool isDir) x \ + /* virtual HRESULT SetPassword(const UString &password) x */ \ + virtual HRESULT CryptoGetTextPassword2(Int32 *passwordIsDefined, BSTR *password) x \ + virtual HRESULT CryptoGetTextPassword(BSTR *password) x \ + virtual HRESULT ShowDeleteFile(const wchar_t *name, bool isDir) x \ + + /* + virtual HRESULT ReportProp(UInt32 indexType, UInt32 index, PROPID propID, const PROPVARIANT *value) x \ + virtual HRESULT ReportRawProp(UInt32 indexType, UInt32 index, PROPID propID, const void *data, UInt32 dataSize, UInt32 propType) x \ + virtual HRESULT ReportFinished(UInt32 indexType, UInt32 index, Int32 opRes) x \ + */ + + /* virtual HRESULT CloseProgress() { return S_OK; } */ + +Z7_IFACE_DECL_PURE(IUpdateCallbackUI) +Z7_PURE_INTERFACES_END + +struct CKeyKeyValPair +{ + UInt64 Key1; + UInt64 Key2; + unsigned Value; + + int Compare(const CKeyKeyValPair &a) const + { + if (Key1 < a.Key1) return -1; + if (Key1 > a.Key1) return 1; + return MyCompare(Key2, a.Key2); + } +}; + + +class CArchiveUpdateCallback Z7_final: + public IArchiveUpdateCallback2, + public IArchiveUpdateCallbackFile, + // public IArchiveUpdateCallbackArcProp, + public IArchiveExtractCallbackMessage2, + public IArchiveGetRawProps, + public IArchiveGetRootProps, + public ICryptoGetTextPassword2, + public ICryptoGetTextPassword, + public ICompressProgressInfo, + public IInFileStream_Callback, + public CMyUnknownImp +{ + Z7_COM_QI_BEGIN2(IArchiveUpdateCallback2) + Z7_COM_QI_ENTRY(IArchiveUpdateCallbackFile) + // Z7_COM_QI_ENTRY(IArchiveUpdateCallbackArcProp) + Z7_COM_QI_ENTRY(IArchiveExtractCallbackMessage2) + Z7_COM_QI_ENTRY(IArchiveGetRawProps) + Z7_COM_QI_ENTRY(IArchiveGetRootProps) + Z7_COM_QI_ENTRY(ICryptoGetTextPassword2) + Z7_COM_QI_ENTRY(ICryptoGetTextPassword) + Z7_COM_QI_ENTRY(ICompressProgressInfo) + Z7_COM_QI_END + Z7_COM_ADDREF_RELEASE + + Z7_IFACE_COM7_IMP(ICompressProgressInfo) + + Z7_IFACE_COM7_IMP(IProgress) + Z7_IFACE_COM7_IMP(IArchiveUpdateCallback) + Z7_IFACE_COM7_IMP(IArchiveUpdateCallback2) + Z7_IFACE_COM7_IMP(IArchiveUpdateCallbackFile) + // Z7_IFACE_COM7_IMP(IArchiveUpdateCallbackArcProp) + Z7_IFACE_COM7_IMP(IArchiveExtractCallbackMessage2) + Z7_IFACE_COM7_IMP(IArchiveGetRawProps) + Z7_IFACE_COM7_IMP(IArchiveGetRootProps) + Z7_IFACE_COM7_IMP(ICryptoGetTextPassword2) + Z7_IFACE_COM7_IMP(ICryptoGetTextPassword) + + + void UpdateProcessedItemStatus(unsigned dirIndex); + +public: + bool PreserveATime; + bool ShareForWrite; + bool StopAfterOpenError; + bool StdInMode; + + bool KeepOriginalItemNames; + bool StoreNtSecurity; + bool StoreHardLinks; + bool StoreSymLinks; + + bool StoreOwnerId; + bool StoreOwnerName; + + bool Need_LatestMTime; + bool LatestMTime_Defined; + + /* + bool Need_ArcMTime_Report; + bool ArcMTime_WasReported; + */ + + CRecordVector _openFiles_Indexes; + FStringVector _openFiles_Paths; + // CRecordVector< CInFileStream* > _openFiles_Streams; + + bool AreAllFilesClosed() const { return _openFiles_Indexes.IsEmpty(); } + virtual HRESULT InFileStream_On_Error(UINT_PTR val, DWORD error) Z7_override; + virtual void InFileStream_On_Destroy(CInFileStream *stream, UINT_PTR val) Z7_override; + + IUpdateCallbackUI *Callback; + + const CDirItems *DirItems; + const CDirItem *ParentDirItem; + + const CArc *Arc; + CMyComPtr Archive; + const CObjectVector *ArcItems; + const CRecordVector *UpdatePairs; + + CRecordVector VolumesSizes; + FString VolName; + FString VolExt; + UString ArcFileName; // without path prefix + + const UStringVector *NewNames; + const UString *Comment; + int CommentIndex; + + /* + CArcTime Reported_ArcMTime; + */ + CFiTime LatestMTime; + + Byte *ProcessedItemsStatuses; + + + CArchiveUpdateCallback(); + + bool IsDir(const CUpdatePair2 &up) const + { + if (up.DirIndex >= 0) + return DirItems->Items[(unsigned)up.DirIndex].IsDir(); + else if (up.ArcIndex >= 0) + return (*ArcItems)[(unsigned)up.ArcIndex].IsDir; + return false; + } + +private: + #if defined(_WIN32) && !defined(UNDER_CE) + bool _saclEnabled; + #endif + CRecordVector _map; + + UInt32 _hardIndex_From; + UInt32 _hardIndex_To; +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/UpdatePair.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Common/UpdatePair.cpp new file mode 100644 index 00000000..8fc56159 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/UpdatePair.cpp @@ -0,0 +1,303 @@ +// UpdatePair.cpp + +#include "StdAfx.h" + +#include +// #include + +#include "../../../Common/Wildcard.h" + +#include "../../../Windows/TimeUtils.h" + +#include "SortUtils.h" +#include "UpdatePair.h" + +using namespace NWindows; +using namespace NTime; + + +/* + a2.Prec = + { + 0 (k_PropVar_TimePrec_0): + if GetProperty(kpidMTime) returned 0 and + GetProperty(kpidTimeType) did not returned VT_UI4. + 7z, wim, tar in 7-Zip before v21) + in that case we use + (prec) that is set by IOutArchive::GetFileTimeType() + } +*/ + +static int MyCompareTime(unsigned prec, const CFiTime &f1, const CArcTime &a2) +{ + // except of precision, we also have limitation, when timestamp is out of range + + /* if (Prec) in archive item is defined, then use global (prec) */ + if (a2.Prec != k_PropVar_TimePrec_0) + prec = a2.Prec; + + CArcTime a1; + a1.Set_From_FiTime(f1); + /* Set_From_FiTime() must set full form precision: + k_PropVar_TimePrec_Base + numDigits + windows: 7 digits, non-windows: 9 digits */ + + if (prec == k_PropVar_TimePrec_DOS) + { + const UInt32 dosTime1 = a1.Get_DosTime(); + const UInt32 dosTime2 = a2.Get_DosTime(); + return MyCompare(dosTime1, dosTime2); + } + + if (prec == k_PropVar_TimePrec_Unix) + { + const Int64 u2 = FileTime_To_UnixTime64(a2.FT); + if (u2 == 0 || u2 == (UInt32)0xFFFFFFFF) + { + // timestamp probably was saturated in archive to 32-bit + // so we use saturated 32-bit value for disk file too. + UInt32 u1; + FileTime_To_UnixTime(a1.FT, u1); + const UInt32 u2_32 = (UInt32)u2; + return MyCompare(u1, u2_32); + } + + const Int64 u1 = FileTime_To_UnixTime64(a1.FT); + return MyCompare(u1, u2); + // prec = k_PropVar_TimePrec_Base; // for debug + } + + if (prec == k_PropVar_TimePrec_0) + prec = k_PropVar_TimePrec_Base + 7; + else if (prec == k_PropVar_TimePrec_HighPrec) + prec = k_PropVar_TimePrec_Base + 9; + else if (prec < k_PropVar_TimePrec_Base) + prec = k_PropVar_TimePrec_Base; + else if (prec > k_PropVar_TimePrec_Base + 9) + prec = k_PropVar_TimePrec_Base + 7; + + // prec now is full form: k_PropVar_TimePrec_Base + numDigits; + if (prec > a1.Prec && a1.Prec >= k_PropVar_TimePrec_Base) + prec = a1.Prec; + + const unsigned numDigits = prec - k_PropVar_TimePrec_Base; + if (numDigits >= 7) + { + const int comp = CompareFileTime(&a1.FT, &a2.FT); + if (comp != 0 || numDigits == 7) + return comp; + return MyCompare(a1.Ns100, a2.Ns100); + } + UInt32 d = 1; + for (unsigned k = numDigits; k < 7; k++) + d *= 10; + const UInt64 v1 = a1.Get_FILETIME_as_UInt64() / d * d; + const UInt64 v2 = a2.Get_FILETIME_as_UInt64() / d * d; + // printf("\ndelta=%d numDigits=%d\n", (unsigned)(v1- v2), numDigits); + return MyCompare(v1, v2); +} + + + +static const char * const k_Duplicate_inArc_Message = "Duplicate filename in archive:"; +static const char * const k_Duplicate_inDir_Message = "Duplicate filename on disk:"; +static const char * const k_NotCensoredCollision_Message = "Internal file name collision (file on disk, file in archive):"; + +Z7_ATTR_NORETURN +static +void ThrowError(const char *message, const UString &s1, const UString &s2) +{ + UString m (message); + m.Add_LF(); m += s1; + m.Add_LF(); m += s2; + throw m; +} + +static int CompareArcItemsBase(const CArcItem &ai1, const CArcItem &ai2) +{ + const int res = CompareFileNames(ai1.Name, ai2.Name); + if (res != 0) + return res; + if (ai1.IsDir != ai2.IsDir) + return ai1.IsDir ? -1 : 1; + return 0; +} + +static int CompareArcItems(const unsigned *p1, const unsigned *p2, void *param) +{ + const unsigned i1 = *p1; + const unsigned i2 = *p2; + const CObjectVector &arcItems = *(const CObjectVector *)param; + const int res = CompareArcItemsBase(arcItems[i1], arcItems[i2]); + if (res != 0) + return res; + return MyCompare(i1, i2); +} + +void GetUpdatePairInfoList( + const CDirItems &dirItems, + const CObjectVector &arcItems, + NFileTimeType::EEnum fileTimeType, + CRecordVector &updatePairs) +{ + CUIntVector dirIndices, arcIndices; + + const unsigned numDirItems = dirItems.Items.Size(); + const unsigned numArcItems = arcItems.Size(); + + CIntArr duplicatedArcItem(numArcItems); + { + int *vals = &duplicatedArcItem[0]; + for (unsigned i = 0; i < numArcItems; i++) + vals[i] = 0; + } + + { + arcIndices.ClearAndSetSize(numArcItems); + if (numArcItems != 0) + { + unsigned *vals = &arcIndices[0]; + for (unsigned i = 0; i < numArcItems; i++) + vals[i] = i; + } + arcIndices.Sort(CompareArcItems, (void *)&arcItems); + for (unsigned i = 0; i + 1 < numArcItems; i++) + if (CompareArcItemsBase( + arcItems[arcIndices[i]], + arcItems[arcIndices[i + 1]]) == 0) + { + duplicatedArcItem[i] = 1; + duplicatedArcItem[i + 1] = -1; + } + } + + UStringVector dirNames; + { + dirNames.ClearAndReserve(numDirItems); + unsigned i; + for (i = 0; i < numDirItems; i++) + dirNames.AddInReserved(dirItems.GetLogPath(i)); + SortFileNames(dirNames, dirIndices); + for (i = 0; i + 1 < numDirItems; i++) + { + const UString &s1 = dirNames[dirIndices[i]]; + const UString &s2 = dirNames[dirIndices[i + 1]]; + if (CompareFileNames(s1, s2) == 0) + ThrowError(k_Duplicate_inDir_Message, s1, s2); + } + } + + unsigned dirIndex = 0; + unsigned arcIndex = 0; + + int prevHostFile = -1; + const UString *prevHostName = NULL; + + while (dirIndex < numDirItems || arcIndex < numArcItems) + { + CUpdatePair pair; + pair.Construct(); + + int dirIndex2 = -1; + int arcIndex2 = -1; + const CDirItem *di = NULL; + const CArcItem *ai = NULL; + + int compareResult = -1; + const UString *name = NULL; + + if (dirIndex < numDirItems) + { + dirIndex2 = (int)dirIndices[dirIndex]; + di = &dirItems.Items[(unsigned)dirIndex2]; + } + + if (arcIndex < numArcItems) + { + arcIndex2 = (int)arcIndices[arcIndex]; + ai = &arcItems[(unsigned)arcIndex2]; + compareResult = 1; + if (dirIndex < numDirItems) + { + compareResult = CompareFileNames(dirNames[(unsigned)dirIndex2], ai->Name); + if (compareResult == 0) + { + if (di->IsDir() != ai->IsDir) + compareResult = (ai->IsDir ? 1 : -1); + } + } + } + + if (compareResult < 0) + { + name = &dirNames[(unsigned)dirIndex2]; + pair.State = NUpdateArchive::NPairState::kOnlyOnDisk; + pair.DirIndex = dirIndex2; + dirIndex++; + } + else if (compareResult > 0) + { + name = &ai->Name; + pair.State = ai->Censored ? + NUpdateArchive::NPairState::kOnlyInArchive: + NUpdateArchive::NPairState::kNotMasked; + pair.ArcIndex = arcIndex2; + arcIndex++; + } + else + { + const int dupl = duplicatedArcItem[arcIndex]; + if (dupl != 0) + ThrowError(k_Duplicate_inArc_Message, ai->Name, arcItems[arcIndices[(unsigned)((int)arcIndex + dupl)]].Name); + + name = &dirNames[(unsigned)dirIndex2]; + if (!ai->Censored) + ThrowError(k_NotCensoredCollision_Message, *name, ai->Name); + + pair.DirIndex = dirIndex2; + pair.ArcIndex = arcIndex2; + + int compResult = 0; + if (ai->MTime.Def) + { + compResult = MyCompareTime((unsigned)fileTimeType, di->MTime, ai->MTime); + } + switch (compResult) + { + case -1: pair.State = NUpdateArchive::NPairState::kNewInArchive; break; + case 1: pair.State = NUpdateArchive::NPairState::kOldInArchive; break; + default: + pair.State = (ai->Size_Defined && di->Size == ai->Size) ? + NUpdateArchive::NPairState::kSameFiles : + NUpdateArchive::NPairState::kUnknowNewerFiles; + } + + dirIndex++; + arcIndex++; + } + + if ( + #ifdef _WIN32 + (di && di->IsAltStream) || + #endif + (ai && ai->IsAltStream)) + { + if (prevHostName) + { + const unsigned hostLen = prevHostName->Len(); + if (name->Len() > hostLen) + if ((*name)[hostLen] == ':' && CompareFileNames(*prevHostName, name->Left(hostLen)) == 0) + pair.HostIndex = prevHostFile; + } + } + else + { + prevHostFile = (int)updatePairs.Size(); + prevHostName = name; + } + + updatePairs.Add(pair); + } + + updatePairs.ReserveDown(); +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/UpdatePair.h b/src/plugins/7zip/7za/cpp/7zip/UI/Common/UpdatePair.h new file mode 100644 index 00000000..a2855c41 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/UpdatePair.h @@ -0,0 +1,27 @@ +// UpdatePair.h + +#ifndef ZIP7_INC_UPDATE_PAIR_H +#define ZIP7_INC_UPDATE_PAIR_H + +#include "DirItem.h" +#include "UpdateAction.h" + +#include "../../Archive/IArchive.h" + +struct CUpdatePair +{ + NUpdateArchive::NPairState::EEnum State; + int ArcIndex; + int DirIndex; + int HostIndex; // >= 0 for alt streams only, contains index of host pair + + void Construct() { ArcIndex = -1; DirIndex = -1; HostIndex = -1; } +}; + +void GetUpdatePairInfoList( + const CDirItems &dirItems, + const CObjectVector &arcItems, + NFileTimeType::EEnum fileTimeType, + CRecordVector &updatePairs); + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/UpdateProduce.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Common/UpdateProduce.cpp new file mode 100644 index 00000000..9b66d36e --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/UpdateProduce.cpp @@ -0,0 +1,75 @@ +// UpdateProduce.cpp + +#include "StdAfx.h" + +#include "UpdateProduce.h" + +using namespace NUpdateArchive; + +static const char * const kUpdateActionSetCollision = "Internal collision in update action set"; + +void UpdateProduce( + const CRecordVector &updatePairs, + const CActionSet &actionSet, + CRecordVector &operationChain, + IUpdateProduceCallback *callback) +{ + FOR_VECTOR (i, updatePairs) + { + const CUpdatePair &pair = updatePairs[i]; + + CUpdatePair2 up2; + up2.Construct(); + up2.DirIndex = pair.DirIndex; + up2.ArcIndex = pair.ArcIndex; + up2.NewData = up2.NewProps = true; + up2.UseArcProps = false; + + switch ((int)actionSet.StateActions[(unsigned)pair.State]) + { + case NPairAction::kIgnore: + if (pair.ArcIndex >= 0 && callback) + callback->ShowDeleteFile((unsigned)pair.ArcIndex); + continue; + + case NPairAction::kCopy: + if (pair.State == NPairState::kOnlyOnDisk) + throw kUpdateActionSetCollision; + if (pair.State == NPairState::kOnlyInArchive) + { + if (pair.HostIndex >= 0) + { + /* + ignore alt stream if + 1) no such alt stream in Disk + 2) there is Host file in disk + */ + if (updatePairs[(unsigned)pair.HostIndex].DirIndex >= 0) + continue; + } + } + up2.NewData = up2.NewProps = false; + up2.UseArcProps = true; + break; + + case NPairAction::kCompress: + if (pair.State == NPairState::kOnlyInArchive || + pair.State == NPairState::kNotMasked) + throw kUpdateActionSetCollision; + break; + + case NPairAction::kCompressAsAnti: + up2.IsAnti = true; + up2.UseArcProps = (pair.ArcIndex >= 0); + break; + + default: throw 123; // break; // is unexpected case + } + + up2.IsSameTime = ((unsigned)pair.State == NUpdateArchive::NPairState::kSameFiles); + + operationChain.Add(up2); + } + + operationChain.ReserveDown(); +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/UpdateProduce.h b/src/plugins/7zip/7za/cpp/7zip/UI/Common/UpdateProduce.h new file mode 100644 index 00000000..3d163815 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/UpdateProduce.h @@ -0,0 +1,60 @@ +// UpdateProduce.h + +#ifndef ZIP7_INC_UPDATE_PRODUCE_H +#define ZIP7_INC_UPDATE_PRODUCE_H + +#include "UpdatePair.h" + +struct CUpdatePair2 +{ + bool NewData; + bool NewProps; + bool UseArcProps; // if (UseArcProps && NewProps), we want to change only some properties. + bool IsAnti; // if (!IsAnti) we use other ways to detect Anti status + + int DirIndex; + int ArcIndex; + int NewNameIndex; + + bool IsMainRenameItem; + bool IsSameTime; + + void Construct() + { + NewData = false; + NewProps = false; + UseArcProps = false; + IsAnti = false; + DirIndex = -1; + ArcIndex = -1; + NewNameIndex = -1; + IsMainRenameItem = false; + IsSameTime = false; + } + + void SetAs_NoChangeArcItem(unsigned arcIndex) // int + { + Construct(); + UseArcProps = true; + ArcIndex = (int)arcIndex; + } + + bool ExistOnDisk() const { return DirIndex != -1; } + bool ExistInArchive() const { return ArcIndex != -1; } +}; + +Z7_PURE_INTERFACES_BEGIN + +DECLARE_INTERFACE(IUpdateProduceCallback) +{ + virtual HRESULT ShowDeleteFile(unsigned arcIndex) = 0; +}; +Z7_PURE_INTERFACES_END + +void UpdateProduce( + const CRecordVector &updatePairs, + const NUpdateArchive::CActionSet &actionSet, + CRecordVector &operationChain, + IUpdateProduceCallback *callback); + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/WorkDir.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Common/WorkDir.cpp new file mode 100644 index 00000000..a4929671 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/WorkDir.cpp @@ -0,0 +1,84 @@ +// WorkDir.cpp + +#include "StdAfx.h" + +#include "../../../Windows/FileName.h" +#include "../../../Windows/FileSystem.h" + +#include "WorkDir.h" + +using namespace NWindows; +using namespace NFile; +using namespace NDir; + +FString GetWorkDir(const NWorkDir::CInfo &workDirInfo, const FString &path, FString &fileName) +{ + NWorkDir::NMode::EEnum mode = workDirInfo.Mode; + + #if defined(_WIN32) && !defined(UNDER_CE) + if (workDirInfo.ForRemovableOnly) + { + mode = NWorkDir::NMode::kCurrent; + const FString prefix = path.Left(3); + if (NName::IsDrivePath(prefix)) + { + const UINT driveType = NSystem::MyGetDriveType(prefix); + if (driveType == DRIVE_CDROM || driveType == DRIVE_REMOVABLE) + mode = workDirInfo.Mode; + } + /* + CParsedPath parsedPath; + parsedPath.ParsePath(archiveName); + UINT driveType = GetDriveType(parsedPath.Prefix); + if ((driveType != DRIVE_CDROM) && (driveType != DRIVE_REMOVABLE)) + mode = NZipSettings::NWorkDir::NMode::kCurrent; + */ + } + #endif + + const int pos = path.ReverseFind_PathSepar() + 1; + fileName = path.Ptr((unsigned)pos); + + FString tempDir; + switch ((int)mode) + { + case NWorkDir::NMode::kCurrent: + tempDir = path.Left((unsigned)pos); + break; + case NWorkDir::NMode::kSpecified: + tempDir = workDirInfo.Path; + break; + // case NWorkDir::NMode::kSystem: + default: + if (!MyGetTempPath(tempDir)) + throw 141717; + break; + } + NName::NormalizeDirPathPrefix(tempDir); + return tempDir; +} + +HRESULT CWorkDirTempFile::CreateTempFile(const FString &originalPath) +{ + NWorkDir::CInfo workDirInfo; + workDirInfo.Load(); + FString namePart; + FString path = GetWorkDir(workDirInfo, originalPath, namePart); + CreateComplexDir(path); + path += namePart; + _outStreamSpec = new COutFileStream; + OutStream = _outStreamSpec; + if (!_tempFile.Create(path, &_outStreamSpec->File)) + return GetLastError_noZero_HRESULT(); + _originalPath = originalPath; + return S_OK; +} + +HRESULT CWorkDirTempFile::MoveToOriginal(bool deleteOriginal, + NWindows::NFile::NDir::ICopyFileProgress *progress) +{ + OutStream.Release(); + if (!_tempFile.MoveTo(_originalPath, deleteOriginal, progress)) + return GetLastError_noZero_HRESULT(); + return S_OK; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/WorkDir.h b/src/plugins/7zip/7za/cpp/7zip/UI/Common/WorkDir.h new file mode 100644 index 00000000..fed8c4a7 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/WorkDir.h @@ -0,0 +1,30 @@ +// WorkDir.h + +#ifndef ZIP7_INC_WORK_DIR_H +#define ZIP7_INC_WORK_DIR_H + +#include "../../../Windows/FileDir.h" + +#include "../../Common/FileStreams.h" + +#include "ZipRegistry.h" + +FString GetWorkDir(const NWorkDir::CInfo &workDirInfo, const FString &path, FString &fileName); + +class CWorkDirTempFile MY_UNCOPYABLE +{ + FString _originalPath; + NWindows::NFile::NDir::CTempFile _tempFile; + COutFileStream *_outStreamSpec; +public: + CMyComPtr OutStream; + + const FString &Get_OriginalFilePath() const { return _originalPath; } + const FString &Get_TempFilePath() const { return _tempFile.GetPath(); } + + HRESULT CreateTempFile(const FString &originalPath); + HRESULT MoveToOriginal(bool deleteOriginal, + NWindows::NFile::NDir::ICopyFileProgress *progress = NULL); +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/ZipRegistry.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Common/ZipRegistry.cpp new file mode 100644 index 00000000..936b8881 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/ZipRegistry.cpp @@ -0,0 +1,599 @@ +// ZipRegistry.cpp + +#include "StdAfx.h" + +#include "../../../../C/CpuArch.h" + +#include "../../../Common/IntToString.h" +#include "../../../Common/StringToInt.h" + +#include "../../../Windows/FileDir.h" +#include "../../../Windows/Registry.h" +#include "../../../Windows/Synchronization.h" + +// #include "../Explorer/ContextMenuFlags.h" +#include "ZipRegistry.h" + +using namespace NWindows; +using namespace NRegistry; + +static NSynchronization::CCriticalSection g_CS; +#define CS_LOCK NSynchronization::CCriticalSectionLock lock(g_CS); + +static LPCTSTR const kCuPrefix = TEXT("Software") TEXT(STRING_PATH_SEPARATOR) TEXT("7-Zip") TEXT(STRING_PATH_SEPARATOR); + +static CSysString GetKeyPath(LPCTSTR path) { return kCuPrefix + (CSysString)path; } + +static LONG OpenMainKey(CKey &key, LPCTSTR keyName) +{ + return key.Open(HKEY_CURRENT_USER, GetKeyPath(keyName), KEY_READ); +} + +static LONG CreateMainKey(CKey &key, LPCTSTR keyName) +{ + return key.Create(HKEY_CURRENT_USER, GetKeyPath(keyName)); +} + +static void Key_Set_UInt32(CKey &key, LPCTSTR name, UInt32 value) +{ + if (value == (UInt32)(Int32)-1) + key.DeleteValue(name); + else + key.SetValue(name, value); +} + + +static void Key_Get_UInt32(CKey &key, LPCTSTR name, UInt32 &value) +{ + value = (UInt32)(Int32)-1; + key.GetValue_UInt32_IfOk(name, value); +} + + +static void Key_Set_BoolPair(CKey &key, LPCTSTR name, const CBoolPair &b) +{ + if (b.Def) + key.SetValue(name, b.Val); +} + +static void Key_Set_bool_if_Changed(CKey &key, LPCTSTR name, bool val) +{ + bool oldVal = false; + if (key.GetValue_bool_IfOk(name, oldVal) == ERROR_SUCCESS) + if (val == oldVal) + return; + key.SetValue(name, val); +} + +static void Key_Set_BoolPair_Delete_IfNotDef(CKey &key, LPCTSTR name, const CBoolPair &b) +{ + if (b.Def) + Key_Set_bool_if_Changed(key, name, b.Val); + else + key.DeleteValue(name); +} + +static void Key_Get_BoolPair(CKey &key, LPCTSTR name, CBoolPair &b) +{ + b.Val = false; + b.Def = (key.GetValue_bool_IfOk(name, b.Val) == ERROR_SUCCESS); +} + +static void Key_Get_BoolPair_true(CKey &key, LPCTSTR name, CBoolPair &b) +{ + b.Val = true; + b.Def = (key.GetValue_bool_IfOk(name, b.Val) == ERROR_SUCCESS); +} + +namespace NExtract +{ + +static LPCTSTR const kKeyName = TEXT("Extraction"); + +static LPCTSTR const kExtractMode = TEXT("ExtractMode"); +static LPCTSTR const kOverwriteMode = TEXT("OverwriteMode"); +static LPCTSTR const kShowPassword = TEXT("ShowPassword"); +static LPCTSTR const kPathHistory = TEXT("PathHistory"); +static LPCTSTR const kSplitDest = TEXT("SplitDest"); +static LPCTSTR const kElimDup = TEXT("ElimDup"); +// static LPCTSTR const kAltStreams = TEXT("AltStreams"); +static LPCTSTR const kNtSecur = TEXT("Security"); +static LPCTSTR const kMemLimit = TEXT("MemLimit"); + +void CInfo::Save() const +{ + CS_LOCK + CKey key; + CreateMainKey(key, kKeyName); + + if (PathMode_Force) + key.SetValue(kExtractMode, (UInt32)PathMode); + if (OverwriteMode_Force) + key.SetValue(kOverwriteMode, (UInt32)OverwriteMode); + + Key_Set_BoolPair(key, kSplitDest, SplitDest); + Key_Set_BoolPair(key, kElimDup, ElimDup); + // Key_Set_BoolPair(key, kAltStreams, AltStreams); + Key_Set_BoolPair(key, kNtSecur, NtSecurity); + Key_Set_BoolPair(key, kShowPassword, ShowPassword); + + key.RecurseDeleteKey(kPathHistory); + key.SetValue_Strings(kPathHistory, Paths); +} + +void Save_ShowPassword(bool showPassword) +{ + CS_LOCK + CKey key; + CreateMainKey(key, kKeyName); + key.SetValue(kShowPassword, showPassword); +} + +void Save_LimitGB(UInt32 limit_GB) +{ + CS_LOCK + CKey key; + CreateMainKey(key, kKeyName); + Key_Set_UInt32(key, kMemLimit, limit_GB); +} + +void CInfo::Load() +{ + PathMode = NPathMode::kCurPaths; + PathMode_Force = false; + OverwriteMode = NOverwriteMode::kAsk; + OverwriteMode_Force = false; + + SplitDest.Val = true; + + Paths.Clear(); + + CS_LOCK + CKey key; + if (OpenMainKey(key, kKeyName) != ERROR_SUCCESS) + return; + + key.GetValue_Strings(kPathHistory, Paths); + UInt32 v; + if (key.GetValue_UInt32_IfOk(kExtractMode, v) == ERROR_SUCCESS && v <= NPathMode::kAbsPaths) + { + PathMode = (NPathMode::EEnum)v; + PathMode_Force = true; + } + if (key.GetValue_UInt32_IfOk(kOverwriteMode, v) == ERROR_SUCCESS && v <= NOverwriteMode::kRenameExisting) + { + OverwriteMode = (NOverwriteMode::EEnum)v; + OverwriteMode_Force = true; + } + + Key_Get_BoolPair_true(key, kSplitDest, SplitDest); + + Key_Get_BoolPair(key, kElimDup, ElimDup); + // Key_Get_BoolPair(key, kAltStreams, AltStreams); + Key_Get_BoolPair(key, kNtSecur, NtSecurity); + Key_Get_BoolPair(key, kShowPassword, ShowPassword); +} + +bool Read_ShowPassword() +{ + CS_LOCK + CKey key; + bool showPassword = false; + if (OpenMainKey(key, kKeyName) != ERROR_SUCCESS) + return showPassword; + key.GetValue_bool_IfOk(kShowPassword, showPassword); + return showPassword; +} + +UInt32 Read_LimitGB() +{ + CS_LOCK + CKey key; + UInt32 v = (UInt32)(Int32)-1; + if (OpenMainKey(key, kKeyName) == ERROR_SUCCESS) + key.GetValue_UInt32_IfOk(kMemLimit, v); + return v; +} + +} + +namespace NCompression +{ + +static LPCTSTR const kKeyName = TEXT("Compression"); + +static LPCTSTR const kArcHistory = TEXT("ArcHistory"); +static LPCWSTR const kArchiver = L"Archiver"; +static LPCTSTR const kShowPassword = TEXT("ShowPassword"); +static LPCTSTR const kEncryptHeaders = TEXT("EncryptHeaders"); + +static LPCTSTR const kOptionsKeyName = TEXT("Options"); + +static LPCTSTR const kLevel = TEXT("Level"); +static LPCTSTR const kDictionary = TEXT("Dictionary"); +// static LPCTSTR const kDictionaryChain = TEXT("DictionaryChain"); +static LPCTSTR const kOrder = TEXT("Order"); +static LPCTSTR const kBlockSize = TEXT("BlockSize"); +static LPCTSTR const kNumThreads = TEXT("NumThreads"); +static LPCWSTR const kMethod = L"Method"; +static LPCWSTR const kOptions = L"Options"; +static LPCWSTR const kEncryptionMethod = L"EncryptionMethod"; + +static LPCTSTR const kNtSecur = TEXT("Security"); +static LPCTSTR const kAltStreams = TEXT("AltStreams"); +static LPCTSTR const kHardLinks = TEXT("HardLinks"); +static LPCTSTR const kSymLinks = TEXT("SymLinks"); +static LPCTSTR const kPreserveATime = TEXT("PreserveATime"); + +static LPCTSTR const kTimePrec = TEXT("TimePrec"); +static LPCTSTR const kMTime = TEXT("MTime"); +static LPCTSTR const kATime = TEXT("ATime"); +static LPCTSTR const kCTime = TEXT("CTime"); +static LPCTSTR const kSetArcMTime = TEXT("SetArcMTime"); + +static void SetRegString(CKey &key, LPCWSTR name, const UString &value) +{ + if (value.IsEmpty()) + key.DeleteValue(name); + else + key.SetValue(name, value); +} + +static void GetRegString(CKey &key, LPCWSTR name, UString &value) +{ + if (key.QueryValue(name, value) != ERROR_SUCCESS) + value.Empty(); +} + +static LPCWSTR const kMemUse = L"MemUse" + #if defined(MY_CPU_SIZEOF_POINTER) && (MY_CPU_SIZEOF_POINTER == 4) + L"32"; + #else + L"64"; + #endif + +void CInfo::Save() const +{ + CS_LOCK + + CKey key; + CreateMainKey(key, kKeyName); + + Key_Set_BoolPair_Delete_IfNotDef (key, kNtSecur, NtSecurity); + Key_Set_BoolPair_Delete_IfNotDef (key, kAltStreams, AltStreams); + Key_Set_BoolPair_Delete_IfNotDef (key, kHardLinks, HardLinks); + Key_Set_BoolPair_Delete_IfNotDef (key, kSymLinks, SymLinks); + Key_Set_BoolPair_Delete_IfNotDef (key, kPreserveATime, PreserveATime); + + key.SetValue(kShowPassword, ShowPassword); + key.SetValue(kLevel, (UInt32)Level); + key.SetValue(kArchiver, ArcType); + key.SetValue(kShowPassword, ShowPassword); + key.SetValue(kEncryptHeaders, EncryptHeaders); + key.RecurseDeleteKey(kArcHistory); + key.SetValue_Strings(kArcHistory, ArcPaths); + + key.RecurseDeleteKey(kOptionsKeyName); + { + CKey optionsKey; + optionsKey.Create(key, kOptionsKeyName); + FOR_VECTOR (i, Formats) + { + const CFormatOptions &fo = Formats[i]; + CKey fk; + fk.Create(optionsKey, fo.FormatID); + + SetRegString(fk, kMethod, fo.Method); + SetRegString(fk, kOptions, fo.Options); + SetRegString(fk, kEncryptionMethod, fo.EncryptionMethod); + SetRegString(fk, kMemUse, fo.MemUse); + + Key_Set_UInt32(fk, kLevel, fo.Level); + Key_Set_UInt32(fk, kDictionary, fo.Dictionary); + // Key_Set_UInt32(fk, kDictionaryChain, fo.DictionaryChain); + Key_Set_UInt32(fk, kOrder, fo.Order); + Key_Set_UInt32(fk, kBlockSize, fo.BlockLogSize); + Key_Set_UInt32(fk, kNumThreads, fo.NumThreads); + + Key_Set_UInt32(fk, kTimePrec, fo.TimePrec); + Key_Set_BoolPair_Delete_IfNotDef (fk, kMTime, fo.MTime); + Key_Set_BoolPair_Delete_IfNotDef (fk, kATime, fo.ATime); + Key_Set_BoolPair_Delete_IfNotDef (fk, kCTime, fo.CTime); + Key_Set_BoolPair_Delete_IfNotDef (fk, kSetArcMTime, fo.SetArcMTime); + } + } +} + +void CInfo::Load() +{ + ArcPaths.Clear(); + Formats.Clear(); + + Level = 5; + ArcType = L"7z"; + ShowPassword = false; + EncryptHeaders = false; + + CS_LOCK + CKey key; + + if (OpenMainKey(key, kKeyName) != ERROR_SUCCESS) + return; + + Key_Get_BoolPair(key, kNtSecur, NtSecurity); + Key_Get_BoolPair(key, kAltStreams, AltStreams); + Key_Get_BoolPair(key, kHardLinks, HardLinks); + Key_Get_BoolPair(key, kSymLinks, SymLinks); + Key_Get_BoolPair(key, kPreserveATime, PreserveATime); + + key.GetValue_Strings(kArcHistory, ArcPaths); + + { + CKey optionsKey; + if (optionsKey.Open(key, kOptionsKeyName, KEY_READ) == ERROR_SUCCESS) + { + CSysStringVector formatIDs; + optionsKey.EnumKeys(formatIDs); + FOR_VECTOR (i, formatIDs) + { + CKey fk; + CFormatOptions fo; + fo.FormatID = formatIDs[i]; + if (fk.Open(optionsKey, fo.FormatID, KEY_READ) == ERROR_SUCCESS) + { + GetRegString(fk, kMethod, fo.Method); + GetRegString(fk, kOptions, fo.Options); + GetRegString(fk, kEncryptionMethod, fo.EncryptionMethod); + GetRegString(fk, kMemUse, fo.MemUse); + + Key_Get_UInt32(fk, kLevel, fo.Level); + Key_Get_UInt32(fk, kDictionary, fo.Dictionary); + // Key_Get_UInt32(fk, kDictionaryChain, fo.DictionaryChain); + Key_Get_UInt32(fk, kOrder, fo.Order); + Key_Get_UInt32(fk, kBlockSize, fo.BlockLogSize); + Key_Get_UInt32(fk, kNumThreads, fo.NumThreads); + + Key_Get_UInt32(fk, kTimePrec, fo.TimePrec); + Key_Get_BoolPair(fk, kMTime, fo.MTime); + Key_Get_BoolPair(fk, kATime, fo.ATime); + Key_Get_BoolPair(fk, kCTime, fo.CTime); + Key_Get_BoolPair(fk, kSetArcMTime, fo.SetArcMTime); + + Formats.Add(fo); + } + } + } + } + + UString a; + if (key.QueryValue(kArchiver, a) == ERROR_SUCCESS) + ArcType = a; + key.GetValue_UInt32_IfOk(kLevel, Level); + key.GetValue_bool_IfOk(kShowPassword, ShowPassword); + key.GetValue_bool_IfOk(kEncryptHeaders, EncryptHeaders); +} + + +static bool ParseMemUse(const wchar_t *s, CMemUse &mu) +{ + mu.Clear(); + + bool percentMode = false; + { + const wchar_t c = *s; + if (MyCharLower_Ascii(c) == 'p') + { + percentMode = true; + s++; + } + } + const wchar_t *end; + UInt64 number = ConvertStringToUInt64(s, &end); + if (end == s) + return false; + + wchar_t c = *end; + + if (percentMode) + { + if (c != 0) + return false; + mu.IsPercent = true; + mu.Val = number; + return true; + } + + if (c == 0) + { + mu.Val = number; + return true; + } + + c = MyCharLower_Ascii(c); + + const wchar_t c1 = end[1]; + + if (c == '%') + { + if (c1 != 0) + return false; + mu.IsPercent = true; + mu.Val = number; + return true; + } + + if (c == 'b') + { + if (c1 != 0) + return false; + mu.Val = number; + return true; + } + + if (c1 != 0) + if (MyCharLower_Ascii(c1) != 'b' || end[2] != 0) + return false; + + unsigned numBits; + switch (c) + { + case 'k': numBits = 10; break; + case 'm': numBits = 20; break; + case 'g': numBits = 30; break; + case 't': numBits = 40; break; + default: return false; + } + if (number >= ((UInt64)1 << (64 - numBits))) + return false; + mu.Val = number << numBits; + return true; +} + + +void CMemUse::Parse(const UString &s) +{ + IsDefined = ParseMemUse(s, *this); +} + +/* +void MemLimit_Save(const UString &s) +{ + CS_LOCK + CKey key; + CreateMainKey(key, kKeyName); + SetRegString(key, kMemUse, s); +} + +bool MemLimit_Load(NCompression::CMemUse &mu) +{ + mu.Clear(); + UString a; + { + CS_LOCK + CKey key; + if (OpenMainKey(key, kKeyName) != ERROR_SUCCESS) + return false; + if (key.QueryValue(kMemUse, a) != ERROR_SUCCESS) + return false; + } + if (a.IsEmpty()) + return false; + mu.Parse(a); + return mu.IsDefined; +} +*/ + +} + +static LPCTSTR const kOptionsInfoKeyName = TEXT("Options"); + +namespace NWorkDir +{ +static LPCTSTR const kWorkDirType = TEXT("WorkDirType"); +static LPCWSTR const kWorkDirPath = L"WorkDirPath"; +static LPCTSTR const kTempRemovableOnly = TEXT("TempRemovableOnly"); + + +void CInfo::Save()const +{ + CS_LOCK + CKey key; + CreateMainKey(key, kOptionsInfoKeyName); + key.SetValue(kWorkDirType, (UInt32)Mode); + key.SetValue(kWorkDirPath, fs2us(Path)); + key.SetValue(kTempRemovableOnly, ForRemovableOnly); +} + +void CInfo::Load() +{ + SetDefault(); + + CS_LOCK + CKey key; + if (OpenMainKey(key, kOptionsInfoKeyName) != ERROR_SUCCESS) + return; + + UInt32 dirType; + if (key.GetValue_UInt32_IfOk(kWorkDirType, dirType) != ERROR_SUCCESS) + return; + switch (dirType) + { + case NMode::kSystem: + case NMode::kCurrent: + case NMode::kSpecified: + Mode = (NMode::EEnum)dirType; + } + UString pathU; + if (key.QueryValue(kWorkDirPath, pathU) == ERROR_SUCCESS) + Path = us2fs(pathU); + else + { + Path.Empty(); + if (Mode == NMode::kSpecified) + Mode = NMode::kSystem; + } + key.GetValue_bool_IfOk(kTempRemovableOnly, ForRemovableOnly); +} + +} + +static LPCTSTR const kCascadedMenu = TEXT("CascadedMenu"); +static LPCTSTR const kContextMenu = TEXT("ContextMenu"); +static LPCTSTR const kMenuIcons = TEXT("MenuIcons"); +static LPCTSTR const kElimDup = TEXT("ElimDupExtract"); +static LPCTSTR const kWriteZoneId = TEXT("WriteZoneIdExtract"); + +void CContextMenuInfo::Save() const +{ + CS_LOCK + CKey key; + CreateMainKey(key, kOptionsInfoKeyName); + + Key_Set_BoolPair(key, kCascadedMenu, Cascaded); + Key_Set_BoolPair(key, kMenuIcons, MenuIcons); + Key_Set_BoolPair(key, kElimDup, ElimDup); + + Key_Set_UInt32(key, kWriteZoneId, WriteZone); + + if (Flags_Def) + key.SetValue(kContextMenu, Flags); +} + +void CContextMenuInfo::Load() +{ + Cascaded.Val = true; + Cascaded.Def = false; + + MenuIcons.Val = false; + MenuIcons.Def = false; + + ElimDup.Val = true; + ElimDup.Def = false; + + WriteZone = (UInt32)(Int32)-1; + + /* we can disable email items by default, + because email code doesn't work in some systems */ + Flags = (UInt32)(Int32)-1 + /* + & ~NContextMenuFlags::kCompressEmail + & ~NContextMenuFlags::kCompressTo7zEmail + & ~NContextMenuFlags::kCompressToZipEmail + */ + ; + Flags_Def = false; + + CS_LOCK + + CKey key; + if (OpenMainKey(key, kOptionsInfoKeyName) != ERROR_SUCCESS) + return; + + Key_Get_BoolPair_true(key, kCascadedMenu, Cascaded); + Key_Get_BoolPair_true(key, kElimDup, ElimDup); + Key_Get_BoolPair(key, kMenuIcons, MenuIcons); + + Key_Get_UInt32(key, kWriteZoneId, WriteZone); + + Flags_Def = (key.GetValue_UInt32_IfOk(kContextMenu, Flags) == ERROR_SUCCESS); +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Common/ZipRegistry.h b/src/plugins/7zip/7za/cpp/7zip/UI/Common/ZipRegistry.h new file mode 100644 index 00000000..ce084f4e --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Common/ZipRegistry.h @@ -0,0 +1,212 @@ +// ZipRegistry.h + +#ifndef ZIP7_INC_ZIP_REGISTRY_H +#define ZIP7_INC_ZIP_REGISTRY_H + +#include "../../../Common/MyTypes.h" +#include "../../../Common/MyString.h" + +#include "../../Common/MethodProps.h" + +#include "ExtractMode.h" + +/* +CBoolPair::Def in writing functions means: + if ( CBoolPair::Def ), we write CBoolPair::Val + if ( !CBoolPair::Def ) + { + in NCompression functions we delete registry value + in another functions we do nothing + } +*/ + +namespace NExtract +{ + struct CInfo + { + NPathMode::EEnum PathMode; + NOverwriteMode::EEnum OverwriteMode; + bool PathMode_Force; + bool OverwriteMode_Force; + + CBoolPair SplitDest; + CBoolPair ElimDup; + // CBoolPair AltStreams; + CBoolPair NtSecurity; + CBoolPair ShowPassword; + + UStringVector Paths; + + void Save() const; + void Load(); + }; + + void Save_ShowPassword(bool showPassword); + bool Read_ShowPassword(); + + void Save_LimitGB(UInt32 limit_GB); + UInt32 Read_LimitGB(); +} + +namespace NCompression +{ + struct CMemUse + { + // UString Str; + bool IsDefined; + bool IsPercent; + UInt64 Val; + + CMemUse(): + IsDefined(false), + IsPercent(false), + Val(0) + {} + + void Clear() + { + // Str.Empty(); + IsDefined = false; + IsPercent = false; + Val = 0; + } + + UInt64 GetBytes(UInt64 ramSize) const + { + if (!IsPercent) + return Val; + return Calc_From_Val_Percents(ramSize, Val); + } + void Parse(const UString &s); + }; + + struct CFormatOptions + { + UInt32 Level; + UInt32 Dictionary; + // UInt32 DictionaryChain; + UInt32 Order; + UInt32 BlockLogSize; + UInt32 NumThreads; + + UInt32 TimePrec; + CBoolPair MTime; + CBoolPair ATime; + CBoolPair CTime; + CBoolPair SetArcMTime; + + CSysString FormatID; + UString Method; + UString Options; + UString EncryptionMethod; + UString MemUse; + + void Reset_TimePrec() + { + TimePrec = (UInt32)(Int32)-1; + } + + bool IsSet_TimePrec() const + { + return TimePrec != (UInt32)(Int32)-1; + } + + + void Reset_BlockLogSize() + { + BlockLogSize = (UInt32)(Int32)-1; + } + + void ResetForLevelChange() + { + BlockLogSize = NumThreads = Level = Dictionary = Order = (UInt32)(Int32)-1; + // DictionaryChain = (UInt32)(Int32)-1; + Method.Empty(); + // Options.Empty(); + // EncryptionMethod.Empty(); + } + CFormatOptions() + { + // TimePrec = 0; + Reset_TimePrec(); + ResetForLevelChange(); + } + }; + + struct CInfo + { + UInt32 Level; + bool ShowPassword; + bool EncryptHeaders; + + CBoolPair NtSecurity; + CBoolPair AltStreams; + CBoolPair HardLinks; + CBoolPair SymLinks; + + CBoolPair PreserveATime; + + UString ArcType; + UStringVector ArcPaths; + + CObjectVector Formats; + + void Save() const; + void Load(); + }; +} + +namespace NWorkDir +{ + namespace NMode + { + enum EEnum + { + kSystem, + kCurrent, + kSpecified + }; + } + struct CInfo + { + NMode::EEnum Mode; + bool ForRemovableOnly; + FString Path; + + void SetForRemovableOnlyDefault() { ForRemovableOnly = true; } + void SetDefault() + { + Mode = NMode::kSystem; + Path.Empty(); + SetForRemovableOnlyDefault(); + } + + void Save() const; + void Load(); + }; +} + + +struct CContextMenuInfo +{ + CBoolPair Cascaded; + CBoolPair MenuIcons; + CBoolPair ElimDup; + + bool Flags_Def; + UInt32 Flags; + UInt32 WriteZone; + + /* + CContextMenuInfo(): + Flags_Def(0), + WriteZone((UInt32)(Int32)-1), + Flags((UInt32)(Int32)-1) + {} + */ + + void Save() const; + void Load(); +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Console/BenchCon.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Console/BenchCon.cpp new file mode 100644 index 00000000..113f584a --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Console/BenchCon.cpp @@ -0,0 +1,41 @@ +// BenchCon.cpp + +#include "StdAfx.h" + +#include "../Common/Bench.h" + +#include "BenchCon.h" +#include "ConsoleClose.h" + +struct CPrintBenchCallback Z7_final: public IBenchPrintCallback +{ + FILE *_file; + + void Print(const char *s) Z7_override; + void NewLine() Z7_override; + HRESULT CheckBreak() Z7_override; +}; + +void CPrintBenchCallback::Print(const char *s) +{ + fputs(s, _file); +} + +void CPrintBenchCallback::NewLine() +{ + fputc('\n', _file); +} + +HRESULT CPrintBenchCallback::CheckBreak() +{ + return NConsoleClose::TestBreakSignal() ? E_ABORT: S_OK; +} + +HRESULT BenchCon(DECL_EXTERNAL_CODECS_LOC_VARS + const CObjectVector &props, UInt32 numIterations, FILE *f) +{ + CPrintBenchCallback callback; + callback._file = f; + return Bench(EXTERNAL_CODECS_LOC_VARS + &callback, NULL, props, numIterations, true); +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Console/BenchCon.h b/src/plugins/7zip/7za/cpp/7zip/UI/Console/BenchCon.h new file mode 100644 index 00000000..844cc2a7 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Console/BenchCon.h @@ -0,0 +1,14 @@ +// BenchCon.h + +#ifndef ZIP7_INC_BENCH_CON_H +#define ZIP7_INC_BENCH_CON_H + +#include + +#include "../../Common/CreateCoder.h" +#include "../../UI/Common/Property.h" + +HRESULT BenchCon(DECL_EXTERNAL_CODECS_LOC_VARS + const CObjectVector &props, UInt32 numIterations, FILE *f); + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Console/Console.dsp b/src/plugins/7zip/7za/cpp/7zip/UI/Console/Console.dsp new file mode 100644 index 00000000..ff2e5af7 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Console/Console.dsp @@ -0,0 +1,1052 @@ +# Microsoft Developer Studio Project File - Name="Console" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=Console - Win32 DebugU +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "Console.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "Console.mak" CFG="Console - Win32 DebugU" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "Console - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "Console - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE "Console - Win32 ReleaseU" (based on "Win32 (x86) Console Application") +!MESSAGE "Console - Win32 DebugU" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "Console - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /Gr /MD /W4 /WX /GX /O1 /D "NDEBUG" /D "_MBCS" /D "WIN32" /D "_CONSOLE" /D "Z7_LONG_PATH" /D "Z7_EXTERNAL_CODECS" /D "Z7_DEVICE_FILE" /FAcs /Yu"StdAfx.h" /FD /GF /c +# ADD BASE RSC /l 0x419 /d "NDEBUG" +# ADD RSC /l 0x419 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 /out:"C:\UTIL\7z.exe" /OPT:NOWIN98 +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "Console - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /Gr /MTd /W4 /WX /Gm /GX /ZI /Od /D "_DEBUG" /D "_MBCS" /D "WIN32" /D "_CONSOLE" /D "Z7_LONG_PATH" /D "Z7_EXTERNAL_CODECS" /D "Z7_DEVICE_FILE" /FAcs /Yu"StdAfx.h" /FD /GZ /c +# ADD BASE RSC /l 0x419 /d "_DEBUG" +# ADD RSC /l 0x419 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /out:"C:\UTIL\7z.exe" /pdbtype:sept /ignore:4033 +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "Console - Win32 ReleaseU" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Console___Win32_ReleaseU" +# PROP BASE Intermediate_Dir "Console___Win32_ReleaseU" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "ReleaseU" +# PROP Intermediate_Dir "ReleaseU" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /Gz /MD /W3 /GX /O1 /I "../../../" /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /Yu"StdAfx.h" /FD /c +# ADD CPP /nologo /Gr /MD /W4 /WX /GX /O1 /D "NDEBUG" /D "_UNICODE" /D "UNICODE" /D "WIN32" /D "_CONSOLE" /D "Z7_LONG_PATH" /D "Z7_EXTERNAL_CODECS" /D "Z7_DEVICE_FILE" /Yu"StdAfx.h" /FD /c +# ADD BASE RSC /l 0x419 /d "NDEBUG" +# ADD RSC /l 0x419 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 /out:"C:\UTIL\7z.exe" +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 /out:"C:\UTIL\7zn.exe" /OPT:NOWIN98 +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "Console - Win32 DebugU" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Console___Win32_DebugU" +# PROP BASE Intermediate_Dir "Console___Win32_DebugU" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "DebugU" +# PROP Intermediate_Dir "DebugU" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /Gz /W3 /Gm /GX /ZI /Od /I "../../../" /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /Yu"StdAfx.h" /FD /GZ /c +# ADD CPP /nologo /Gr /MTd /W4 /WX /Gm /GX /ZI /Od /D "_DEBUG" /D "_UNICODE" /D "UNICODE" /D "WIN32" /D "_CONSOLE" /D "Z7_LONG_PATH" /D "Z7_EXTERNAL_CODECS" /D "Z7_DEVICE_FILE" /Yu"StdAfx.h" /FD /GZ /c +# ADD BASE RSC /l 0x419 /d "_DEBUG" +# ADD RSC /l 0x419 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /out:"C:\UTIL\7z.exe" /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /out:"C:\UTIL\7z.exe" /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "Console - Win32 Release" +# Name "Console - Win32 Debug" +# Name "Console - Win32 ReleaseU" +# Name "Console - Win32 DebugU" +# Begin Group "Spec" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=.\resource.rc +# End Source File +# Begin Source File + +SOURCE=.\StdAfx.cpp +# ADD CPP /Yc"StdAfx.h" +# End Source File +# Begin Source File + +SOURCE=.\StdAfx.h +# End Source File +# End Group +# Begin Group "Console" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=.\BenchCon.cpp +# End Source File +# Begin Source File + +SOURCE=.\BenchCon.h +# End Source File +# Begin Source File + +SOURCE=.\ConsoleClose.cpp +# End Source File +# Begin Source File + +SOURCE=.\ConsoleClose.h +# End Source File +# Begin Source File + +SOURCE=.\ExtractCallbackConsole.cpp +# End Source File +# Begin Source File + +SOURCE=.\ExtractCallbackConsole.h +# End Source File +# Begin Source File + +SOURCE=.\HashCon.cpp +# End Source File +# Begin Source File + +SOURCE=.\HashCon.h +# End Source File +# Begin Source File + +SOURCE=.\List.cpp +# End Source File +# Begin Source File + +SOURCE=.\List.h +# End Source File +# Begin Source File + +SOURCE=.\Main.cpp +# End Source File +# Begin Source File + +SOURCE=.\MainAr.cpp +# End Source File +# Begin Source File + +SOURCE=.\OpenCallbackConsole.cpp +# End Source File +# Begin Source File + +SOURCE=.\OpenCallbackConsole.h +# End Source File +# Begin Source File + +SOURCE=.\PercentPrinter.cpp +# End Source File +# Begin Source File + +SOURCE=.\PercentPrinter.h +# End Source File +# Begin Source File + +SOURCE=.\UpdateCallbackConsole.cpp +# End Source File +# Begin Source File + +SOURCE=.\UpdateCallbackConsole.h +# End Source File +# Begin Source File + +SOURCE=.\UserInputUtils.cpp +# End Source File +# Begin Source File + +SOURCE=.\UserInputUtils.h +# End Source File +# End Group +# Begin Group "Windows" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\Windows\DLL.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\DLL.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\ErrorMsg.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\ErrorMsg.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileDir.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileDir.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileFind.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileFind.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileIO.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileIO.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileLink.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileMapping.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileName.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileName.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileSystem.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileSystem.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\MemoryLock.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\MemoryLock.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\NtCheck.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\PropVariant.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\PropVariant.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\PropVariantConv.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\PropVariantConv.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Registry.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Registry.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\SecurityUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Synchronization.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\System.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\System.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\SystemInfo.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\SystemInfo.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Thread.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\TimeUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\TimeUtils.h +# End Source File +# End Group +# Begin Group "Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\Common\CommandLineParser.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\CommandLineParser.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Common.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Common0.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\ComTry.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\CRC.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Defs.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\DynLimBuf.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\DynLimBuf.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\IntToString.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\IntToString.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\ListFileUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\ListFileUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyBuffer.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyBuffer2.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyCom.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyGuidDef.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyInitGuid.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyString.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyString.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyTypes.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyVector.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyVector.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyWindows.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\NewHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\NewHandler.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StdInStream.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StdInStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StdOutStream.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StdOutStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StringConvert.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StringConvert.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StringToInt.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StringToInt.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\UTFConvert.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\UTFConvert.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Wildcard.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Wildcard.h +# End Source File +# End Group +# Begin Group "UI Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\Common\ArchiveCommandLine.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\ArchiveCommandLine.h +# End Source File +# Begin Source File + +SOURCE=..\Common\ArchiveExtractCallback.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\ArchiveExtractCallback.h +# End Source File +# Begin Source File + +SOURCE=..\Common\ArchiveOpenCallback.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\ArchiveOpenCallback.h +# End Source File +# Begin Source File + +SOURCE=..\Common\Bench.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\Bench.h +# End Source File +# Begin Source File + +SOURCE=..\Common\DefaultName.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\DefaultName.h +# End Source File +# Begin Source File + +SOURCE=..\Common\DirItem.h +# End Source File +# Begin Source File + +SOURCE=..\Common\EnumDirItems.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\EnumDirItems.h +# End Source File +# Begin Source File + +SOURCE=..\Common\ExitCode.h +# End Source File +# Begin Source File + +SOURCE=..\Common\Extract.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\Extract.h +# End Source File +# Begin Source File + +SOURCE=..\Common\ExtractingFilePath.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\ExtractingFilePath.h +# End Source File +# Begin Source File + +SOURCE=..\Common\ExtractMode.h +# End Source File +# Begin Source File + +SOURCE=..\Common\HashCalc.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\HashCalc.h +# End Source File +# Begin Source File + +SOURCE=..\Common\IFileExtractCallback.h +# End Source File +# Begin Source File + +SOURCE=..\Common\LoadCodecs.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\LoadCodecs.h +# End Source File +# Begin Source File + +SOURCE=..\Common\OpenArchive.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\OpenArchive.h +# End Source File +# Begin Source File + +SOURCE=..\Common\Property.h +# End Source File +# Begin Source File + +SOURCE=..\Common\PropIDUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\PropIDUtils.h +# End Source File +# Begin Source File + +SOURCE=..\Common\SetProperties.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\SetProperties.h +# End Source File +# Begin Source File + +SOURCE=..\Common\SortUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\SortUtils.h +# End Source File +# Begin Source File + +SOURCE=..\Common\TempFiles.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\TempFiles.h +# End Source File +# Begin Source File + +SOURCE=..\Common\Update.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\Update.h +# End Source File +# Begin Source File + +SOURCE=..\Common\UpdateAction.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\UpdateAction.h +# End Source File +# Begin Source File + +SOURCE=..\Common\UpdateCallback.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\UpdateCallback.h +# End Source File +# Begin Source File + +SOURCE=..\Common\UpdatePair.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\UpdatePair.h +# End Source File +# Begin Source File + +SOURCE=..\Common\UpdateProduce.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\UpdateProduce.h +# End Source File +# Begin Source File + +SOURCE=..\Common\ZipRegistry.h +# End Source File +# End Group +# Begin Group "7-zip Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Common\CreateCoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\CreateCoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FilePathAutoRename.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FilePathAutoRename.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FileStreams.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FileStreams.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FilterCoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FilterCoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\LimitedStreams.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\LimitedStreams.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\MethodProps.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\MethodProps.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\MultiOutStream.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\MultiOutStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\ProgressUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\ProgressUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\PropId.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\RegisterArc.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamObjects.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamObjects.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\UniqBlocks.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\UniqBlocks.h +# End Source File +# End Group +# Begin Group "Compress" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Compress\CopyCoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\CopyCoder.h +# End Source File +# End Group +# Begin Group "C" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\..\C\7zCrc.c + +!IF "$(CFG)" == "Console - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Console - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Console - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "Console - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zCrc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zTypes.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zVersion.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zWindows.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Alloc.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Alloc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Compiler.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\CpuArch.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\CpuArch.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\DllSecur.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\DllSecur.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Precomp.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Sort.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Sort.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Threads.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Threads.h +# End Source File +# End Group +# Begin Group "ArchiveCommon" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Archive\Common\ItemNameUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\ItemNameUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\OutStreamWithCRC.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\OutStreamWithCRC.h +# End Source File +# End Group +# Begin Group "Asm" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\..\Asm\x86\7zAsm.asm +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\Asm\x86\7zCrcOpt.asm + +!IF "$(CFG)" == "Console - Win32 Release" + +# Begin Custom Build +OutDir=.\Release +InputPath=..\..\..\..\Asm\x86\7zCrcOpt.asm +InputName=7zCrcOpt + +"$(OutDir)\$(InputName).obj" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + ml.exe -c -Fo$(OutDir)\$(InputName).obj $(InputPath) + +# End Custom Build + +!ELSEIF "$(CFG)" == "Console - Win32 Debug" + +# Begin Custom Build +OutDir=.\Debug +InputPath=..\..\..\..\Asm\x86\7zCrcOpt.asm +InputName=7zCrcOpt + +"$(OutDir)\$(InputName).obj" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + ml.exe -c -omf -Fo$(OutDir)\$(InputName).obj $(InputPath) + +# End Custom Build + +!ELSEIF "$(CFG)" == "Console - Win32 ReleaseU" + +# Begin Custom Build +OutDir=.\ReleaseU +InputPath=..\..\..\..\Asm\x86\7zCrcOpt.asm +InputName=7zCrcOpt + +"$(OutDir)\$(InputName).obj" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + ml.exe -c -Fo$(OutDir)\$(InputName).obj $(InputPath) + +# End Custom Build + +!ELSEIF "$(CFG)" == "Console - Win32 DebugU" + +# Begin Custom Build +OutDir=.\DebugU +InputPath=..\..\..\..\Asm\x86\7zCrcOpt.asm +InputName=7zCrcOpt + +"$(OutDir)\$(InputName).obj" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + ml.exe -c -omf -Fo$(OutDir)\$(InputName).obj $(InputPath) + +# End Custom Build + +!ENDIF + +# End Source File +# End Group +# Begin Group "Interface" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Archive\IArchive.h +# End Source File +# Begin Source File + +SOURCE=..\..\ICoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\IDecl.h +# End Source File +# Begin Source File + +SOURCE=..\..\IPassword.h +# End Source File +# Begin Source File + +SOURCE=..\..\IProgress.h +# End Source File +# Begin Source File + +SOURCE=..\..\IStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\PropID.h +# End Source File +# End Group +# End Target +# End Project diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Console/Console.dsw b/src/plugins/7zip/7za/cpp/7zip/UI/Console/Console.dsw new file mode 100644 index 00000000..0d93da2f --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Console/Console.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "Console"=".\Console.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Console/Console.mak b/src/plugins/7zip/7za/cpp/7zip/UI/Console/Console.mak new file mode 100644 index 00000000..4b83a3f6 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Console/Console.mak @@ -0,0 +1,45 @@ +MY_CONSOLE = 1 + +!IFNDEF UNDER_CE +CFLAGS = $(CFLAGS) -DZ7_DEVICE_FILE +!ENDIF + +CONSOLE_OBJS = \ + $O\BenchCon.obj \ + $O\ConsoleClose.obj \ + $O\ExtractCallbackConsole.obj \ + $O\HashCon.obj \ + $O\List.obj \ + $O\Main.obj \ + $O\MainAr.obj \ + $O\OpenCallbackConsole.obj \ + $O\PercentPrinter.obj \ + $O\UpdateCallbackConsole.obj \ + $O\UserInputUtils.obj \ + +UI_COMMON_OBJS = \ + $O\ArchiveCommandLine.obj \ + $O\ArchiveExtractCallback.obj \ + $O\ArchiveOpenCallback.obj \ + $O\Bench.obj \ + $O\DefaultName.obj \ + $O\EnumDirItems.obj \ + $O\Extract.obj \ + $O\ExtractingFilePath.obj \ + $O\HashCalc.obj \ + $O\LoadCodecs.obj \ + $O\OpenArchive.obj \ + $O\PropIDUtils.obj \ + $O\SetProperties.obj \ + $O\SortUtils.obj \ + $O\TempFiles.obj \ + $O\Update.obj \ + $O\UpdateAction.obj \ + $O\UpdateCallback.obj \ + $O\UpdatePair.obj \ + $O\UpdateProduce.obj \ + +C_OBJS = $(C_OBJS) \ + $O\DllSecur.obj \ + +# we need empty line after last line above diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Console/Console.manifest b/src/plugins/7zip/7za/cpp/7zip/UI/Console/Console.manifest new file mode 100644 index 00000000..c932b28c --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Console/Console.manifest @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + +true + \ No newline at end of file diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Console/ConsoleClose.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Console/ConsoleClose.cpp new file mode 100644 index 00000000..a184ffb8 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Console/ConsoleClose.cpp @@ -0,0 +1,98 @@ +// ConsoleClose.cpp + +#include "StdAfx.h" + +#include "ConsoleClose.h" + +#ifndef UNDER_CE + +#ifdef _WIN32 +#include "../../../Common/MyWindows.h" +#else +#include +#include +#endif + +namespace NConsoleClose { + +unsigned g_BreakCounter = 0; +static const unsigned kBreakAbortThreshold = 3; + +#ifdef _WIN32 + +static BOOL WINAPI HandlerRoutine(DWORD ctrlType) +{ + if (ctrlType == CTRL_LOGOFF_EVENT) + { + // printf("\nCTRL_LOGOFF_EVENT\n"); + return TRUE; + } + + if (++g_BreakCounter < kBreakAbortThreshold) + return TRUE; + return FALSE; + /* + switch (ctrlType) + { + case CTRL_C_EVENT: + case CTRL_BREAK_EVENT: + if (g_BreakCounter < kBreakAbortThreshold) + return TRUE; + } + return FALSE; + */ +} + +CCtrlHandlerSetter::CCtrlHandlerSetter() +{ + if (!SetConsoleCtrlHandler(HandlerRoutine, TRUE)) + throw 1019; // "SetConsoleCtrlHandler fails"; +} + +CCtrlHandlerSetter::~CCtrlHandlerSetter() +{ + if (!SetConsoleCtrlHandler(HandlerRoutine, FALSE)) + { + // warning for throw in destructor. + // throw "SetConsoleCtrlHandler fails"; + } +} + +#else // _WIN32 + +static void HandlerRoutine(int) +{ + if (++g_BreakCounter < kBreakAbortThreshold) + return; + exit(EXIT_FAILURE); +} + +CCtrlHandlerSetter::CCtrlHandlerSetter() +{ + memo_sig_int = signal(SIGINT, HandlerRoutine); // CTRL-C + if (memo_sig_int == SIG_ERR) + throw "SetConsoleCtrlHandler fails (SIGINT)"; + memo_sig_term = signal(SIGTERM, HandlerRoutine); // for kill -15 (before "kill -9") + if (memo_sig_term == SIG_ERR) + throw "SetConsoleCtrlHandler fails (SIGTERM)"; +} + +CCtrlHandlerSetter::~CCtrlHandlerSetter() +{ + signal(SIGINT, memo_sig_int); // CTRL-C + signal(SIGTERM, memo_sig_term); // kill {pid} +} + +#endif // _WIN32 + +/* +void CheckCtrlBreak() +{ + if (TestBreakSignal()) + throw CCtrlBreakException(); +} +*/ + +} + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Console/ConsoleClose.h b/src/plugins/7zip/7za/cpp/7zip/UI/Console/ConsoleClose.h new file mode 100644 index 00000000..b0d99b48 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Console/ConsoleClose.h @@ -0,0 +1,39 @@ +// ConsoleClose.h + +#ifndef ZIP7_INC_CONSOLE_CLOSE_H +#define ZIP7_INC_CONSOLE_CLOSE_H + +namespace NConsoleClose { + +// class CCtrlBreakException {}; + +#ifdef UNDER_CE + +inline bool TestBreakSignal() { return false; } +struct CCtrlHandlerSetter {}; + +#else + +extern unsigned g_BreakCounter; + +inline bool TestBreakSignal() +{ + return (g_BreakCounter != 0); +} + +class CCtrlHandlerSetter Z7_final +{ + #ifndef _WIN32 + void (*memo_sig_int)(int); + void (*memo_sig_term)(int); + #endif +public: + CCtrlHandlerSetter(); + ~CCtrlHandlerSetter(); +}; + +#endif + +} + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Console/ExtractCallbackConsole.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Console/ExtractCallbackConsole.cpp new file mode 100644 index 00000000..862bc517 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Console/ExtractCallbackConsole.cpp @@ -0,0 +1,954 @@ +// ExtractCallbackConsole.cpp + +#include "StdAfx.h" + +#include "../../../Common/IntToString.h" +#include "../../../Common/Wildcard.h" + +#include "../../../Windows/FileDir.h" +#include "../../../Windows/FileFind.h" +#include "../../../Windows/TimeUtils.h" +#include "../../../Windows/ErrorMsg.h" +#include "../../../Windows/PropVariantConv.h" + +#ifndef Z7_ST +#include "../../../Windows/Synchronization.h" +#endif + +#include "../../Common/FilePathAutoRename.h" + +#include "../Common/ExtractingFilePath.h" + +#include "ConsoleClose.h" +#include "ExtractCallbackConsole.h" +#include "UserInputUtils.h" + +using namespace NWindows; +using namespace NFile; +using namespace NDir; + +static HRESULT CheckBreak2() +{ + return NConsoleClose::TestBreakSignal() ? E_ABORT : S_OK; +} + +static const char * const kError = "ERROR: "; + + +void CExtractScanConsole::StartScanning() +{ + if (NeedPercents()) + _percent.Command = "Scan"; +} + +HRESULT CExtractScanConsole::ScanProgress(const CDirItemsStat &st, const FString &path, bool /* isDir */) +{ + if (NeedPercents()) + { + _percent.Files = st.NumDirs + st.NumFiles; + _percent.Completed = st.GetTotalBytes(); + _percent.FileName = fs2us(path); + _percent.Print(); + } + + return CheckBreak2(); +} + +HRESULT CExtractScanConsole::ScanError(const FString &path, DWORD systemError) +{ + // 22.00: + // ScanErrors.AddError(path, systemError); + + ClosePercentsAndFlush(); + + if (_se) + { + *_se << endl << kError << NError::MyFormatMessage(systemError) << endl; + _se->NormalizePrint_UString_Path(fs2us(path)); + *_se << endl << endl; + _se->Flush(); + } + return HRESULT_FROM_WIN32(systemError); + + // 22.00: commented + // CommonError(path, systemError, true); + // return S_OK; +} + + +void Print_UInt64_and_String(AString &s, UInt64 val, const char *name); +void Print_UInt64_and_String(AString &s, UInt64 val, const char *name) +{ + char temp[32]; + ConvertUInt64ToString(val, temp); + s += temp; + s.Add_Space(); + s += name; +} + +void PrintSize_bytes_Smart(AString &s, UInt64 val); +void PrintSize_bytes_Smart(AString &s, UInt64 val) +{ + Print_UInt64_and_String(s, val, "bytes"); + + if (val == 0) + return; + + unsigned numBits = 10; + char c = 'K'; + char temp[4] = { 'K', 'i', 'B', 0 }; + if (val >= ((UInt64)10 << 30)) { numBits = 30; c = 'G'; } + else if (val >= ((UInt64)10 << 20)) { numBits = 20; c = 'M'; } + temp[0] = c; + s += " ("; + Print_UInt64_and_String(s, ((val + ((UInt64)1 << numBits) - 1) >> numBits), temp); + s.Add_Char(')'); +} + +static void PrintSize_bytes_Smart_comma(AString &s, UInt64 val) +{ + if (val == (UInt64)(Int64)-1) + return; + s += ", "; + PrintSize_bytes_Smart(s, val); +} + + + +void Print_DirItemsStat(AString &s, const CDirItemsStat &st); +void Print_DirItemsStat(AString &s, const CDirItemsStat &st) +{ + if (st.NumDirs != 0) + { + Print_UInt64_and_String(s, st.NumDirs, st.NumDirs == 1 ? "folder" : "folders"); + s += ", "; + } + Print_UInt64_and_String(s, st.NumFiles, st.NumFiles == 1 ? "file" : "files"); + PrintSize_bytes_Smart_comma(s, st.FilesSize); + if (st.NumAltStreams != 0) + { + s.Add_LF(); + Print_UInt64_and_String(s, st.NumAltStreams, "alternate streams"); + PrintSize_bytes_Smart_comma(s, st.AltStreamsSize); + } +} + + +void Print_DirItemsStat2(AString &s, const CDirItemsStat2 &st); +void Print_DirItemsStat2(AString &s, const CDirItemsStat2 &st) +{ + Print_DirItemsStat(s, (CDirItemsStat &)st); + bool needLF = true; + if (st.Anti_NumDirs != 0) + { + if (needLF) + s.Add_LF(); + needLF = false; + Print_UInt64_and_String(s, st.Anti_NumDirs, st.Anti_NumDirs == 1 ? "anti-folder" : "anti-folders"); + } + if (st.Anti_NumFiles != 0) + { + if (needLF) + s.Add_LF(); + else + s += ", "; + needLF = false; + Print_UInt64_and_String(s, st.Anti_NumFiles, st.Anti_NumFiles == 1 ? "anti-file" : "anti-files"); + } + if (st.Anti_NumAltStreams != 0) + { + if (needLF) + s.Add_LF(); + else + s += ", "; + needLF = false; + Print_UInt64_and_String(s, st.Anti_NumAltStreams, "anti-alternate-streams"); + } +} + + +void CExtractScanConsole::PrintStat(const CDirItemsStat &st) +{ + if (_so) + { + AString s; + Print_DirItemsStat(s, st); + *_so << s << endl; + } +} + + + + + + + +#ifndef Z7_ST +static NSynchronization::CCriticalSection g_CriticalSection; +#define MT_LOCK NSynchronization::CCriticalSectionLock lock(g_CriticalSection); +#else +#define MT_LOCK +#endif + + +static const char * const kTestString = "T"; +static const char * const kExtractString = "-"; +static const char * const kSkipString = "."; +static const char * const kReadString = "H"; + +// static const char * const kCantAutoRename = "cannot create file with auto name\n"; +// static const char * const kCantRenameFile = "cannot rename existing file\n"; +// static const char * const kCantDeleteOutputFile = "cannot delete output file "; + +static const char * const kMemoryExceptionMessage = "Can't allocate required memory!"; + +static const char * const kExtracting = "Extracting archive: "; +static const char * const kTesting = "Testing archive: "; + +static const char * const kEverythingIsOk = "Everything is Ok"; +static const char * const kNoFiles = "No files to process"; + +static const char * const kUnsupportedMethod = "Unsupported Method"; +static const char * const kCrcFailed = "CRC Failed"; +static const char * const kCrcFailedEncrypted = "CRC Failed in encrypted file. Wrong password?"; +static const char * const kDataError = "Data Error"; +static const char * const kDataErrorEncrypted = "Data Error in encrypted file. Wrong password?"; +static const char * const kUnavailableData = "Unavailable data"; +static const char * const kUnexpectedEnd = "Unexpected end of data"; +static const char * const kDataAfterEnd = "There are some data after the end of the payload data"; +static const char * const kIsNotArc = "Is not archive"; +static const char * const kHeadersError = "Headers Error"; +static const char * const kWrongPassword = "Wrong password"; + +static const char * const k_ErrorFlagsMessages[] = +{ + "Is not archive" + , "Headers Error" + , "Headers Error in encrypted archive. Wrong password?" + , "Unavailable start of archive" + , "Unconfirmed start of archive" + , "Unexpected end of archive" + , "There are data after the end of archive" + , "Unsupported method" + , "Unsupported feature" + , "Data Error" + , "CRC Error" +}; + +Z7_COM7F_IMF(CExtractCallbackConsole::SetTotal(UInt64 size)) +{ + MT_LOCK + + if (NeedPercents()) + { + _percent.Total = size; + _percent.Print(); + } + return CheckBreak2(); +} + +Z7_COM7F_IMF(CExtractCallbackConsole::SetCompleted(const UInt64 *completeValue)) +{ + MT_LOCK + + if (NeedPercents()) + { + if (completeValue) + _percent.Completed = *completeValue; + _percent.Print(); + } + return CheckBreak2(); +} + +static const char * const kTab = " "; + +static void PrintFileInfo(CStdOutStream *_so, const wchar_t *path, const FILETIME *ft, const UInt64 *size) +{ + *_so << kTab << "Path: "; + _so->NormalizePrint_wstr_Path(path); + *_so << endl; + if (size && *size != (UInt64)(Int64)-1) + { + AString s; + PrintSize_bytes_Smart(s, *size); + *_so << kTab << "Size: " << s << endl; + } + if (ft) + { + char temp[64]; + if (ConvertUtcFileTimeToString(*ft, temp, kTimestampPrintLevel_SEC)) + *_so << kTab << "Modified: " << temp << endl; + } +} + +Z7_COM7F_IMF(CExtractCallbackConsole::AskOverwrite( + const wchar_t *existName, const FILETIME *existTime, const UInt64 *existSize, + const wchar_t *newName, const FILETIME *newTime, const UInt64 *newSize, + Int32 *answer)) +{ + MT_LOCK + + RINOK(CheckBreak2()) + + ClosePercentsAndFlush(); + + if (_so) + { + *_so << endl << "Would you like to replace the existing file:\n"; + PrintFileInfo(_so, existName, existTime, existSize); + *_so << "with the file from archive:\n"; + PrintFileInfo(_so, newName, newTime, newSize); + } + + NUserAnswerMode::EEnum overwriteAnswer = ScanUserYesNoAllQuit(_so); + + switch ((int)overwriteAnswer) + { + case NUserAnswerMode::kQuit: return E_ABORT; + case NUserAnswerMode::kNo: *answer = NOverwriteAnswer::kNo; break; + case NUserAnswerMode::kNoAll: *answer = NOverwriteAnswer::kNoToAll; break; + case NUserAnswerMode::kYesAll: *answer = NOverwriteAnswer::kYesToAll; break; + case NUserAnswerMode::kYes: *answer = NOverwriteAnswer::kYes; break; + case NUserAnswerMode::kAutoRenameAll: *answer = NOverwriteAnswer::kAutoRename; break; + case NUserAnswerMode::kEof: return E_ABORT; + case NUserAnswerMode::kError: return E_FAIL; + default: return E_FAIL; + } + + if (_so) + { + *_so << endl; + if (NeedFlush) + _so->Flush(); + } + + return CheckBreak2(); +} + +Z7_COM7F_IMF(CExtractCallbackConsole::PrepareOperation(const wchar_t *name, Int32 isFolder, Int32 askExtractMode, const UInt64 *position)) +{ + MT_LOCK + + _currentName = name; + + const char *s; + unsigned requiredLevel = 1; + + switch (askExtractMode) + { + case NArchive::NExtract::NAskMode::kExtract: s = kExtractString; break; + case NArchive::NExtract::NAskMode::kTest: s = kTestString; break; + case NArchive::NExtract::NAskMode::kSkip: s = kSkipString; requiredLevel = 2; break; + case NArchive::NExtract::NAskMode::kReadExternal: s = kReadString; requiredLevel = 0; break; + default: s = "???"; requiredLevel = 2; + } + + const bool show2 = (LogLevel >= requiredLevel && _so); + + if (show2) + { + ClosePercents_for_so(); + + _tempA = s; + if (name) + _tempA.Add_Space(); + *_so << _tempA; + + _tempU.Empty(); + if (name) + { + _tempU = name; + _so->Normalize_UString_Path(_tempU); + // 21.04 + if (isFolder) + { + if (!_tempU.IsEmpty() && _tempU.Back() != WCHAR_PATH_SEPARATOR) + _tempU.Add_PathSepar(); + } + } + _so->PrintUString(_tempU, _tempA); + if (position) + *_so << " <" << *position << ">"; + *_so << endl; + + if (NeedFlush) + _so->Flush(); + // _so->Flush(); // for debug only + } + + if (NeedPercents()) + { + if (PercentsNameLevel >= 1) + { + _percent.FileName.Empty(); + _percent.Command.Empty(); + if (PercentsNameLevel > 1 || !show2) + { + _percent.Command = s; + if (name) + _percent.FileName = name; + } + } + _percent.Print(); + } + + return CheckBreak2(); +} + +Z7_COM7F_IMF(CExtractCallbackConsole::MessageError(const wchar_t *message)) +{ + MT_LOCK + + RINOK(CheckBreak2()) + + NumFileErrors_in_Current++; + NumFileErrors++; + + ClosePercentsAndFlush(); + if (_se) + { + *_se << kError << message << endl; + _se->Flush(); + } + + return CheckBreak2(); +} + +void SetExtractErrorMessage(Int32 opRes, Int32 encrypted, AString &dest); +void SetExtractErrorMessage(Int32 opRes, Int32 encrypted, AString &dest) +{ + dest.Empty(); + const char *s = NULL; + + switch (opRes) + { + case NArchive::NExtract::NOperationResult::kUnsupportedMethod: + s = kUnsupportedMethod; + break; + case NArchive::NExtract::NOperationResult::kCRCError: + s = (encrypted ? kCrcFailedEncrypted : kCrcFailed); + break; + case NArchive::NExtract::NOperationResult::kDataError: + s = (encrypted ? kDataErrorEncrypted : kDataError); + break; + case NArchive::NExtract::NOperationResult::kUnavailable: + s = kUnavailableData; + break; + case NArchive::NExtract::NOperationResult::kUnexpectedEnd: + s = kUnexpectedEnd; + break; + case NArchive::NExtract::NOperationResult::kDataAfterEnd: + s = kDataAfterEnd; + break; + case NArchive::NExtract::NOperationResult::kIsNotArc: + s = kIsNotArc; + break; + case NArchive::NExtract::NOperationResult::kHeadersError: + s = kHeadersError; + break; + case NArchive::NExtract::NOperationResult::kWrongPassword: + s = kWrongPassword; + break; + default: break; + } + + dest += kError; + if (s) + dest += s; + else + { + dest += "Error #"; + dest.Add_UInt32((UInt32)opRes); + } +} + +Z7_COM7F_IMF(CExtractCallbackConsole::SetOperationResult(Int32 opRes, Int32 encrypted)) +{ + MT_LOCK + + if (opRes == NArchive::NExtract::NOperationResult::kOK) + { + if (NeedPercents()) + { + _percent.Command.Empty(); + _percent.FileName.Empty(); + _percent.Files++; + } + } + else + { + NumFileErrors_in_Current++; + NumFileErrors++; + + if (_se) + { + ClosePercentsAndFlush(); + + AString s; + SetExtractErrorMessage(opRes, encrypted, s); + + *_se << s; + if (!_currentName.IsEmpty()) + { + *_se << " : "; + _se->NormalizePrint_UString_Path(_currentName); + } + *_se << endl; + _se->Flush(); + } + } + + return CheckBreak2(); +} + +Z7_COM7F_IMF(CExtractCallbackConsole::ReportExtractResult(Int32 opRes, Int32 encrypted, const wchar_t *name)) +{ + if (opRes != NArchive::NExtract::NOperationResult::kOK) + { + _currentName = name; + return SetOperationResult(opRes, encrypted); + } + + return CheckBreak2(); +} + + + +#ifndef Z7_NO_CRYPTO + +HRESULT CExtractCallbackConsole::SetPassword(const UString &password) +{ + PasswordIsDefined = true; + Password = password; + return S_OK; +} + +Z7_COM7F_IMF(CExtractCallbackConsole::CryptoGetTextPassword(BSTR *password)) +{ + COM_TRY_BEGIN + MT_LOCK + return Open_CryptoGetTextPassword(password); + COM_TRY_END +} + +#endif + + +#ifndef Z7_SFX + +void CExtractCallbackConsole::PrintTo_se_Path_WithTitle(const UString &path, const char *title) +{ + *_se << title; + _se->NormalizePrint_UString_Path(path); + *_se << endl; +} + +void CExtractCallbackConsole::Add_ArchiveName_Error() +{ + if (_needWriteArchivePath) + { + PrintTo_se_Path_WithTitle(_currentArchivePath, "Archive: "); + _needWriteArchivePath = false; + } +} + + +Z7_COM7F_IMF(CExtractCallbackConsole::RequestMemoryUse( + UInt32 flags, UInt32 /* indexType */, UInt32 /* index */, const wchar_t *path, + UInt64 requiredSize, UInt64 *allowedSize, UInt32 *answerFlags)) +{ + if ((flags & NRequestMemoryUseFlags::k_IsReport) == 0 + && requiredSize <= *allowedSize) + { +#if 0 + // it's expected, that *answerFlags was set to NRequestMemoryAnswerFlags::k_Allow already, + // because it's default answer for (requiredSize <= *allowedSize) case. + // optional code: + *answerFlags = NRequestMemoryAnswerFlags::k_Allow; +#endif + } + else + { + if ((flags & NRequestMemoryUseFlags::k_NoErrorMessage) == 0) + if (_se) + { + const UInt64 num_GB_allowed = (*allowedSize + ((1u << 30) - 1)) >> 30; + const UInt64 num_GB_required = (requiredSize + ((1u << 30) - 1)) >> 30; + ClosePercentsAndFlush(); + Add_ArchiveName_Error(); + if (path) + PrintTo_se_Path_WithTitle(path, "File: "); + *_se << "The extraction operation requires big amount memory (RAM):" << endl + << " " << num_GB_required << " GB : required memory usage size" << endl + << " " << num_GB_allowed << " GB : allowed memory usage limit" << endl + << " Use -smemx{size}g switch to set allowed memory usage limit for extraction." << endl; + *_se << "ERROR: Memory usage limit was exceeded." << endl; + const char *m = NULL; + // if (indexType == NArchive::NEventIndexType::kNoIndex) + if ((flags & NRequestMemoryUseFlags::k_SkipArc_IsExpected) || + (flags & NRequestMemoryUseFlags::k_Report_SkipArc)) + m = "Archive unpacking was skipped."; +/* + else if ((flags & NRequestMemoryUseFlags::k_SkipBigFiles_IsExpected) || + (flags & NRequestMemoryUseFlags::k_Report_SkipBigFiles)) + m = "Extraction for some files will be skipped."; + else if ((flags & NRequestMemoryUseFlags::k_SkipBigFile_IsExpected) || + (flags & NRequestMemoryUseFlags::k_Report_SkipBigFile)) + m = "File extraction was skipped."; +*/ + if (m) + *_se << m; + _se->Flush(); + } + + if ((flags & NRequestMemoryUseFlags::k_IsReport) == 0) + { + // default answer can be k_Allow, if limit was not forced, + // so we change answer to non-allowed here. + *answerFlags = NRequestMemoryAnswerFlags::k_Limit_Exceeded; + if (flags & NRequestMemoryUseFlags::k_SkipArc_IsExpected) + *answerFlags |= NRequestMemoryAnswerFlags::k_SkipArc; +/* + else if (flags & NRequestMemoryUseFlags::k_SkipBigFile_IsExpected) + *answerFlags |= NRequestMemoryAnswerFlags::k_SkipBigFile; + else if (flags & NRequestMemoryUseFlags::k_SkipBigFiles_IsExpected) + *answerFlags |= NRequestMemoryAnswerFlags::k_SkipBigFiles; +*/ + } + } + return CheckBreak2(); +} + +#endif + + +HRESULT CExtractCallbackConsole::BeforeOpen(const wchar_t *name, bool testMode) +{ + _currentArchivePath = name; + _needWriteArchivePath = true; + + RINOK(CheckBreak2()) + + NumTryArcs++; + ThereIsError_in_Current = false; + ThereIsWarning_in_Current = false; + NumFileErrors_in_Current = 0; + + ClosePercents_for_so(); + if (_so) + { + *_so << endl << (testMode ? kTesting : kExtracting); + _so->NormalizePrint_wstr_Path(name); + *_so << endl; + } + + if (NeedPercents()) + _percent.Command = "Open"; + return S_OK; +} + +HRESULT Print_OpenArchive_Props(CStdOutStream &so, const CCodecs *codecs, const CArchiveLink &arcLink); +HRESULT Print_OpenArchive_Error(CStdOutStream &so, const CCodecs *codecs, const CArchiveLink &arcLink); + +static AString GetOpenArcErrorMessage(UInt32 errorFlags) +{ + AString s; + + for (unsigned i = 0; i < Z7_ARRAY_SIZE(k_ErrorFlagsMessages); i++) + { + UInt32 f = (1 << i); + if ((errorFlags & f) == 0) + continue; + const char *m = k_ErrorFlagsMessages[i]; + if (!s.IsEmpty()) + s.Add_LF(); + s += m; + errorFlags &= ~f; + } + + if (errorFlags != 0) + { + char sz[16]; + sz[0] = '0'; + sz[1] = 'x'; + ConvertUInt32ToHex(errorFlags, sz + 2); + if (!s.IsEmpty()) + s.Add_LF(); + s += sz; + } + + return s; +} + +void PrintErrorFlags(CStdOutStream &so, const char *s, UInt32 errorFlags); +void PrintErrorFlags(CStdOutStream &so, const char *s, UInt32 errorFlags) +{ + if (errorFlags == 0) + return; + so << s << endl << GetOpenArcErrorMessage(errorFlags) << endl; +} + +static void Add_Messsage_Pre_ArcType(UString &s, const char *pre, const wchar_t *arcType) +{ + s.Add_LF(); + s += pre; + s += " as ["; + s += arcType; + s += "] archive"; +} + +void Print_ErrorFormatIndex_Warning(CStdOutStream *_so, const CCodecs *codecs, const CArc &arc); +void Print_ErrorFormatIndex_Warning(CStdOutStream *_so, const CCodecs *codecs, const CArc &arc) +{ + const CArcErrorInfo &er = arc.ErrorInfo; + + *_so << "WARNING:\n"; + _so->NormalizePrint_UString_Path(arc.Path); + UString s; + if (arc.FormatIndex == er.ErrorFormatIndex) + { + s.Add_LF(); + s += "The archive is open with offset"; + } + else + { + Add_Messsage_Pre_ArcType(s, "Cannot open the file", codecs->GetFormatNamePtr(er.ErrorFormatIndex)); + Add_Messsage_Pre_ArcType(s, "The file is open", codecs->GetFormatNamePtr(arc.FormatIndex)); + } + + *_so << s << endl << endl; +} + + +HRESULT CExtractCallbackConsole::OpenResult( + const CCodecs *codecs, const CArchiveLink &arcLink, + const wchar_t *name, HRESULT result) +{ + _currentArchivePath = name; + _needWriteArchivePath = true; + + ClosePercents(); + + if (NeedPercents()) + { + _percent.Files = 0; + _percent.Command.Empty(); + _percent.FileName.Empty(); + } + + + ClosePercentsAndFlush(); + + FOR_VECTOR (level, arcLink.Arcs) + { + const CArc &arc = arcLink.Arcs[level]; + const CArcErrorInfo &er = arc.ErrorInfo; + + const UInt32 errorFlags = er.GetErrorFlags(); + + if (errorFlags != 0 || !er.ErrorMessage.IsEmpty()) + { + if (_se) + { + *_se << endl; + if (level != 0) + { + _se->NormalizePrint_UString_Path(arc.Path); + *_se << endl; + } + } + + if (errorFlags != 0) + { + if (_se) + PrintErrorFlags(*_se, "ERRORS:", errorFlags); + NumOpenArcErrors++; + ThereIsError_in_Current = true; + } + + if (!er.ErrorMessage.IsEmpty()) + { + if (_se) + { + *_se << "ERRORS:" << endl; + _se->NormalizePrint_UString(er.ErrorMessage); + *_se << endl; + } + NumOpenArcErrors++; + ThereIsError_in_Current = true; + } + + if (_se) + { + *_se << endl; + _se->Flush(); + } + } + + UInt32 warningFlags = er.GetWarningFlags(); + + if (warningFlags != 0 || !er.WarningMessage.IsEmpty()) + { + if (_so) + { + *_so << endl; + if (level != 0) + { + _so->NormalizePrint_UString_Path(arc.Path); + *_so << endl; + } + } + + if (warningFlags != 0) + { + if (_so) + PrintErrorFlags(*_so, "WARNINGS:", warningFlags); + NumOpenArcWarnings++; + ThereIsWarning_in_Current = true; + } + + if (!er.WarningMessage.IsEmpty()) + { + if (_so) + { + *_so << "WARNINGS:" << endl; + _so->NormalizePrint_UString(er.WarningMessage); + *_so << endl; + } + NumOpenArcWarnings++; + ThereIsWarning_in_Current = true; + } + + if (_so) + { + *_so << endl; + if (NeedFlush) + _so->Flush(); + } + } + + + if (er.ErrorFormatIndex >= 0) + { + if (_so) + { + Print_ErrorFormatIndex_Warning(_so, codecs, arc); + if (NeedFlush) + _so->Flush(); + } + ThereIsWarning_in_Current = true; + } + } + + if (result == S_OK) + { + if (_so) + { + RINOK(Print_OpenArchive_Props(*_so, codecs, arcLink)) + *_so << endl; + } + } + else + { + NumCantOpenArcs++; + if (_so) + _so->Flush(); + if (_se) + { + *_se << kError; + _se->NormalizePrint_wstr_Path(name); + *_se << endl; + const HRESULT res = Print_OpenArchive_Error(*_se, codecs, arcLink); + RINOK(res) + if (result == S_FALSE) + { + } + else + { + if (result == E_OUTOFMEMORY) + *_se << "Can't allocate required memory"; + else + *_se << NError::MyFormatMessage(result); + *_se << endl; + } + _se->Flush(); + } + } + + + return CheckBreak2(); +} + +HRESULT CExtractCallbackConsole::ThereAreNoFiles() +{ + ClosePercents_for_so(); + + if (_so) + { + *_so << endl << kNoFiles << endl; + if (NeedFlush) + _so->Flush(); + } + return CheckBreak2(); +} + +HRESULT CExtractCallbackConsole::ExtractResult(HRESULT result) +{ + MT_LOCK + + if (NeedPercents()) + { + _percent.ClosePrint(true); + _percent.Command.Empty(); + _percent.FileName.Empty(); + } + + if (_so) + _so->Flush(); + + if (result == S_OK) + { + if (NumFileErrors_in_Current == 0 && !ThereIsError_in_Current) + { + if (ThereIsWarning_in_Current) + NumArcsWithWarnings++; + else + NumOkArcs++; + if (_so) + *_so << kEverythingIsOk << endl; + } + else + { + NumArcsWithError++; + if (_so) + { + *_so << endl; + if (NumFileErrors_in_Current != 0) + *_so << "Sub items Errors: " << NumFileErrors_in_Current << endl; + } + } + if (_so && NeedFlush) + _so->Flush(); + } + else + { + // we don't update NumArcsWithError, if error is not related to archive data. + if (result == E_ABORT + || result == HRESULT_FROM_WIN32(ERROR_DISK_FULL)) + return result; + NumArcsWithError++; + + if (_se) + { + *_se << endl << kError; + if (result == E_OUTOFMEMORY) + *_se << kMemoryExceptionMessage; + else + *_se << NError::MyFormatMessage(result); + *_se << endl; + _se->Flush(); + } + } + + return CheckBreak2(); +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Console/ExtractCallbackConsole.h b/src/plugins/7zip/7za/cpp/7zip/UI/Console/ExtractCallbackConsole.h new file mode 100644 index 00000000..4e453480 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Console/ExtractCallbackConsole.h @@ -0,0 +1,211 @@ +// ExtractCallbackConsole.h + +#ifndef ZIP7_INC_EXTRACT_CALLBACK_CONSOLE_H +#define ZIP7_INC_EXTRACT_CALLBACK_CONSOLE_H + +#include "../../../Common/StdOutStream.h" + +#include "../../IPassword.h" + +#include "../../Archive/IArchive.h" + +#include "../Common/ArchiveExtractCallback.h" + +#include "PercentPrinter.h" + +#include "OpenCallbackConsole.h" + +/* +struct CErrorPathCodes2 +{ + FStringVector Paths; + CRecordVector Codes; + + void AddError(const FString &path, DWORD systemError) + { + Paths.Add(path); + Codes.Add(systemError); + } + void Clear() + { + Paths.Clear(); + Codes.Clear(); + } +}; +*/ + +class CExtractScanConsole Z7_final: public IDirItemsCallback +{ + Z7_IFACE_IMP(IDirItemsCallback) + + CStdOutStream *_so; + CStdOutStream *_se; + CPercentPrinter _percent; + + // CErrorPathCodes2 ScanErrors; + + bool NeedPercents() const { return _percent._so && !_percent.DisablePrint; } + + void ClosePercentsAndFlush() + { + if (NeedPercents()) + _percent.ClosePrint(true); + if (_so) + _so->Flush(); + } + +public: + + void Init( + CStdOutStream *outStream, + CStdOutStream *errorStream, + CStdOutStream *percentStream, + bool disablePercents) + { + _so = outStream; + _se = errorStream; + _percent._so = percentStream; + _percent.DisablePrint = disablePercents; + } + + void SetWindowWidth(unsigned width) { _percent.MaxLen = width - 1; } + + void StartScanning(); + + void CloseScanning() + { + if (NeedPercents()) + _percent.ClosePrint(true); + } + + void PrintStat(const CDirItemsStat &st); +}; + + + + +class CExtractCallbackConsole Z7_final: + public IFolderArchiveExtractCallback, + public IExtractCallbackUI, + // public IArchiveExtractCallbackMessage, + public IFolderArchiveExtractCallback2, + #ifndef Z7_NO_CRYPTO + public ICryptoGetTextPassword, + #endif + #ifndef Z7_SFX + public IArchiveRequestMemoryUseCallback, + #endif + + public COpenCallbackConsole, + public CMyUnknownImp +{ + Z7_COM_QI_BEGIN2(IFolderArchiveExtractCallback) + // Z7_COM_QI_ENTRY(IArchiveExtractCallbackMessage) + Z7_COM_QI_ENTRY(IFolderArchiveExtractCallback2) + #ifndef Z7_NO_CRYPTO + Z7_COM_QI_ENTRY(ICryptoGetTextPassword) + #endif + #ifndef Z7_SFX + Z7_COM_QI_ENTRY(IArchiveRequestMemoryUseCallback) + #endif + + Z7_COM_QI_END + Z7_COM_ADDREF_RELEASE + + Z7_IFACE_COM7_IMP(IProgress) + Z7_IFACE_COM7_IMP(IFolderArchiveExtractCallback) + Z7_IFACE_IMP(IExtractCallbackUI) + // Z7_IFACE_COM7_IMP(IArchiveExtractCallbackMessage) + Z7_IFACE_COM7_IMP(IFolderArchiveExtractCallback2) + #ifndef Z7_NO_CRYPTO + Z7_IFACE_COM7_IMP(ICryptoGetTextPassword) + #endif + #ifndef Z7_SFX + Z7_IFACE_COM7_IMP(IArchiveRequestMemoryUseCallback) + #endif + + bool _needWriteArchivePath; + +public: + bool ThereIsError_in_Current; + bool ThereIsWarning_in_Current; + bool NeedFlush; + +private: + AString _tempA; + UString _tempU; + + UString _currentArchivePath; + UString _currentName; + +#ifndef Z7_SFX + void PrintTo_se_Path_WithTitle(const UString &path, const char *title); + void Add_ArchiveName_Error(); +#endif + + void ClosePercents_for_so() + { + if (NeedPercents() && _so == _percent._so) + _percent.ClosePrint(false); + } + + void ClosePercentsAndFlush() + { + if (NeedPercents()) + _percent.ClosePrint(true); + if (_so) + _so->Flush(); + } +public: + UInt64 NumTryArcs; + + UInt64 NumOkArcs; + UInt64 NumCantOpenArcs; + UInt64 NumArcsWithError; + UInt64 NumArcsWithWarnings; + + UInt64 NumOpenArcErrors; + UInt64 NumOpenArcWarnings; + + UInt64 NumFileErrors; + UInt64 NumFileErrors_in_Current; + + unsigned PercentsNameLevel; + unsigned LogLevel; + + CExtractCallbackConsole(): + _needWriteArchivePath(true), + NeedFlush(false), + PercentsNameLevel(1), + LogLevel(0) + {} + + void SetWindowWidth(unsigned width) { _percent.MaxLen = width - 1; } + + void Init( + CStdOutStream *outStream, + CStdOutStream *errorStream, + CStdOutStream *percentStream, + bool disablePercents) + { + COpenCallbackConsole::Init(outStream, errorStream, percentStream, disablePercents); + + NumTryArcs = 0; + + ThereIsError_in_Current = false; + ThereIsWarning_in_Current = false; + + NumOkArcs = 0; + NumCantOpenArcs = 0; + NumArcsWithError = 0; + NumArcsWithWarnings = 0; + + NumOpenArcErrors = 0; + NumOpenArcWarnings = 0; + + NumFileErrors = 0; + NumFileErrors_in_Current = 0; + } +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Console/HashCon.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Console/HashCon.cpp new file mode 100644 index 00000000..078a625a --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Console/HashCon.cpp @@ -0,0 +1,426 @@ +// HashCon.cpp + +#include "StdAfx.h" + +#include "../../../Common/IntToString.h" + +#include "../../../Windows/FileName.h" + +#include "ConsoleClose.h" +#include "HashCon.h" + +static const char * const kEmptyFileAlias = "[Content]"; + +static const char * const kScanningMessage = "Scanning"; + +static HRESULT CheckBreak2() +{ + return NConsoleClose::TestBreakSignal() ? E_ABORT : S_OK; +} + +HRESULT CHashCallbackConsole::CheckBreak() +{ + return CheckBreak2(); +} + +HRESULT CHashCallbackConsole::StartScanning() +{ + if (PrintHeaders && _so) + *_so << kScanningMessage << endl; + if (NeedPercents()) + { + _percent.ClearCurState(); + _percent.Command = "Scan"; + } + return CheckBreak2(); +} + +HRESULT CHashCallbackConsole::ScanProgress(const CDirItemsStat &st, const FString &path, bool isDir) +{ + if (NeedPercents()) + { + _percent.Files = st.NumDirs + st.NumFiles + st.NumAltStreams; + _percent.Completed = st.GetTotalBytes(); + _percent.FileName = fs2us(path); + if (isDir) + NWindows::NFile::NName::NormalizeDirPathPrefix(_percent.FileName); + _percent.Print(); + } + return CheckBreak2(); +} + +HRESULT CHashCallbackConsole::ScanError(const FString &path, DWORD systemError) +{ + return ScanError_Base(path, systemError); +} + +void Print_DirItemsStat(AString &s, const CDirItemsStat &st); + +HRESULT CHashCallbackConsole::FinishScanning(const CDirItemsStat &st) +{ + if (NeedPercents()) + { + _percent.ClosePrint(true); + _percent.ClearCurState(); + } + if (PrintHeaders && _so) + { + Print_DirItemsStat(_s, st); + *_so << _s << endl << endl; + } + return CheckBreak2(); +} + +HRESULT CHashCallbackConsole::SetNumFiles(UInt64 /* numFiles */) +{ + return CheckBreak2(); +} + +HRESULT CHashCallbackConsole::SetTotal(UInt64 size) +{ + if (NeedPercents()) + { + _percent.Total = size; + _percent.Print(); + } + return CheckBreak2(); +} + +HRESULT CHashCallbackConsole::SetCompleted(const UInt64 *completeValue) +{ + if (completeValue && NeedPercents()) + { + _percent.Completed = *completeValue; + _percent.Print(); + } + return CheckBreak2(); +} + +static void AddMinuses(AString &s, unsigned num) +{ + for (unsigned i = 0; i < num; i++) + s.Add_Minus(); +} + +static void AddSpaces_if_Positive(AString &s, int num) +{ + for (int i = 0; i < num; i++) + s.Add_Space(); +} + +static void SetSpacesAndNul(char *s, unsigned num) +{ + for (unsigned i = 0; i < num; i++) + s[i] = ' '; + s[num] = 0; +} + +static void SetSpacesAndNul_if_Positive(char *s, int num) +{ + if (num < 0) + return; + for (int i = 0; i < num; i++) + s[i] = ' '; + s[num] = 0; +} + +static const unsigned kSizeField_Len = 13; +static const unsigned kNameField_Len = 12; + +static const unsigned kHashColumnWidth_Min = 4 * 2; + +static unsigned GetColumnWidth(unsigned digestSize) +{ + unsigned width = digestSize * 2; + return width < kHashColumnWidth_Min ? kHashColumnWidth_Min: width; +} + + +AString CHashCallbackConsole::GetFields() const +{ + AString s (PrintFields); + if (s.IsEmpty()) + s = "hsn"; + s.MakeLower_Ascii(); + return s; +} + + +void CHashCallbackConsole::PrintSeparatorLine(const CObjectVector &hashers) +{ + _s.Empty(); + const AString fields = GetFields(); + for (unsigned pos = 0; pos < fields.Len(); pos++) + { + const char c = fields[pos]; + if (c == 'h') + { + for (unsigned i = 0; i < hashers.Size(); i++) + { + AddSpace(); + const CHasherState &h = hashers[i]; + AddMinuses(_s, GetColumnWidth(h.DigestSize)); + } + } + else if (c == 's') + { + AddSpace(); + AddMinuses(_s, kSizeField_Len); + } + else if (c == 'n') + { + AddSpacesBeforeName(); + AddMinuses(_s, kNameField_Len); + } + } + + *_so << _s << endl; +} + + +HRESULT CHashCallbackConsole::BeforeFirstFile(const CHashBundle &hb) +{ + if (PrintHeaders && _so) + { + _s.Empty(); + ClosePercents_for_so(); + + const AString fields = GetFields(); + for (unsigned pos = 0; pos < fields.Len(); pos++) + { + const char c = fields[pos]; + if (c == 'h') + { + FOR_VECTOR (i, hb.Hashers) + { + AddSpace(); + const CHasherState &h = hb.Hashers[i]; + _s += h.Name; + AddSpaces_if_Positive(_s, (int)GetColumnWidth(h.DigestSize) - (int)h.Name.Len()); + } + } + + else if (c == 's') + { + AddSpace(); + const AString s2 ("Size"); + AddSpaces_if_Positive(_s, (int)kSizeField_Len - (int)s2.Len()); + _s += s2; + } + else if (c == 'n') + { + AddSpacesBeforeName(); + _s += "Name"; + } + } + + *_so << _s << endl; + PrintSeparatorLine(hb.Hashers); + } + + return CheckBreak2(); +} + +HRESULT CHashCallbackConsole::OpenFileError(const FString &path, DWORD systemError) +{ + return OpenFileError_Base(path, systemError); +} + +HRESULT CHashCallbackConsole::GetStream(const wchar_t *name, bool isDir) +{ + _fileName = name; + if (isDir) + NWindows::NFile::NName::NormalizeDirPathPrefix(_fileName); + + if (NeedPercents()) + { + if (PrintNameInPercents) + { + _percent.FileName.Empty(); + if (name) + _percent.FileName = name; + } + _percent.Print(); + } + return CheckBreak2(); +} + + +static const unsigned k_DigestStringSize = k_HashCalc_DigestSize_Max * 2 + k_HashCalc_ExtraSize * 2 + 16; + + + +void CHashCallbackConsole::PrintResultLine(UInt64 fileSize, + const CObjectVector &hashers, unsigned digestIndex, bool showHash, + const AString &path) +{ + ClosePercents_for_so(); + + _s.Empty(); + const AString fields = GetFields(); + + for (unsigned pos = 0; pos < fields.Len(); pos++) + { + const char c = fields[pos]; + if (c == 'h') + { + FOR_VECTOR (i, hashers) + { + AddSpace(); + const CHasherState &h = hashers[i]; + char s[k_DigestStringSize]; + s[0] = 0; + if (showHash) + h.WriteToString(digestIndex, s); + const unsigned len = (unsigned)strlen(s); + SetSpacesAndNul_if_Positive(s + len, (int)GetColumnWidth(h.DigestSize) - (int)len); + _s += s; + } + } + else if (c == 's') + { + AddSpace(); + char s[kSizeField_Len + 32]; + char *p = s; + SetSpacesAndNul(s, kSizeField_Len); + if (showHash) + { + p = s + kSizeField_Len; + ConvertUInt64ToString(fileSize, p); + const int numSpaces = (int)kSizeField_Len - (int)strlen(p); + if (numSpaces > 0) + p -= (unsigned)numSpaces; + } + _s += p; + } + else if (c == 'n') + { + AddSpacesBeforeName(); + _s += path; + } + } + + *_so << _s; +} + + +HRESULT CHashCallbackConsole::SetOperationResult(UInt64 fileSize, const CHashBundle &hb, bool showHash) +{ + if (_so) + { + AString s; + if (_fileName.IsEmpty()) + s = kEmptyFileAlias; + else + { + UString temp (_fileName); + _so->Normalize_UString_Path(temp); + _so->Convert_UString_to_AString(temp, s); + } + PrintResultLine(fileSize, hb.Hashers, k_HashCalc_Index_Current, showHash, s); + + /* + PrintResultLine(fileSize, hb.Hashers, k_HashCalc_Index_Current, showHash); + if (PrintName) + { + if (_fileName.IsEmpty()) + *_so << kEmptyFileAlias; + else + _so->NormalizePrint_UString(_fileName); + } + */ + // if (PrintNewLine) + *_so << endl; + } + + if (NeedPercents()) + { + _percent.Files++; + _percent.Print(); + } + + return CheckBreak2(); +} + +static const char * const k_DigestTitles[] = +{ + " : " + , " for data: " + , " for data and names: " + , " for streams and names: " +}; + +static void PrintSum(CStdOutStream &so, const CHasherState &h, unsigned digestIndex) +{ + so << h.Name; + + { + AString temp; + AddSpaces_if_Positive(temp, 6 - (int)h.Name.Len()); + so << temp; + } + + so << k_DigestTitles[digestIndex]; + + char s[k_DigestStringSize]; + // s[0] = 0; + h.WriteToString(digestIndex, s); + so << s << endl; +} + +void PrintHashStat(CStdOutStream &so, const CHashBundle &hb) +{ + FOR_VECTOR (i, hb.Hashers) + { + const CHasherState &h = hb.Hashers[i]; + PrintSum(so, h, k_HashCalc_Index_DataSum); + if (hb.NumFiles != 1 || hb.NumDirs != 0) + PrintSum(so, h, k_HashCalc_Index_NamesSum); + if (hb.NumAltStreams != 0) + PrintSum(so, h, k_HashCalc_Index_StreamsSum); + so << endl; + } +} + +void CHashCallbackConsole::PrintProperty(const char *name, UInt64 value) +{ + char s[32]; + s[0] = ':'; + s[1] = ' '; + ConvertUInt64ToString(value, s + 2); + *_so << name << s << endl; +} + +HRESULT CHashCallbackConsole::AfterLastFile(CHashBundle &hb) +{ + ClosePercents2(); + + if (PrintHeaders && _so) + { + PrintSeparatorLine(hb.Hashers); + + PrintResultLine(hb.FilesSize, hb.Hashers, k_HashCalc_Index_DataSum, true, AString()); + + *_so << endl << endl; + + if (hb.NumFiles != 1 || hb.NumDirs != 0) + { + if (hb.NumDirs != 0) + PrintProperty("Folders", hb.NumDirs); + PrintProperty("Files", hb.NumFiles); + } + + PrintProperty("Size", hb.FilesSize); + + if (hb.NumAltStreams != 0) + { + PrintProperty("Alternate streams", hb.NumAltStreams); + PrintProperty("Alternate streams size", hb.AltStreamsSize); + } + + *_so << endl; + PrintHashStat(*_so, hb); + } + + return S_OK; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Console/HashCon.h b/src/plugins/7zip/7za/cpp/7zip/UI/Console/HashCon.h new file mode 100644 index 00000000..ebccb6ff --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Console/HashCon.h @@ -0,0 +1,58 @@ +// HashCon.h + +#ifndef ZIP7_INC_HASH_CON_H +#define ZIP7_INC_HASH_CON_H + +#include "../Common/HashCalc.h" + +#include "UpdateCallbackConsole.h" + +class CHashCallbackConsole Z7_final: + public IHashCallbackUI, + public CCallbackConsoleBase +{ + Z7_IFACE_IMP(IDirItemsCallback) + Z7_IFACE_IMP(IHashCallbackUI) + + UString _fileName; + AString _s; + + void AddSpace() + { + _s.Add_Space_if_NotEmpty(); + } + + void AddSpacesBeforeName() + { + if (!_s.IsEmpty()) + { + _s.Add_Space(); + _s.Add_Space(); + } + } + + void PrintSeparatorLine(const CObjectVector &hashers); + void PrintResultLine(UInt64 fileSize, + const CObjectVector &hashers, unsigned digestIndex, bool showHash, const AString &path); + void PrintProperty(const char *name, UInt64 value); + +public: + bool PrintNameInPercents; + bool PrintHeaders; + // bool PrintSize; + // bool PrintNewLine; // set it too (false), if you need only hash for single file without LF char. + AString PrintFields; + + AString GetFields() const; + + CHashCallbackConsole(): + PrintNameInPercents(true), + PrintHeaders(false) + // , PrintSize(true), + // , PrintNewLine(true) + {} +}; + +void PrintHashStat(CStdOutStream &so, const CHashBundle &hb); + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Console/List.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Console/List.cpp new file mode 100644 index 00000000..2d9f5a35 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Console/List.cpp @@ -0,0 +1,1417 @@ +// List.cpp + +#include "StdAfx.h" + +#include "../../../Common/IntToString.h" +#include "../../../Common/MyCom.h" +#include "../../../Common/StdOutStream.h" +#include "../../../Common/StringConvert.h" +#include "../../../Common/UTFConvert.h" + +#include "../../../Windows/ErrorMsg.h" +#include "../../../Windows/FileDir.h" +#include "../../../Windows/PropVariant.h" +#include "../../../Windows/PropVariantConv.h" + +#include "../Common/OpenArchive.h" +#include "../Common/PropIDUtils.h" + +#include "ConsoleClose.h" +#include "List.h" +#include "OpenCallbackConsole.h" + +using namespace NWindows; +using namespace NCOM; + +extern CStdOutStream *g_StdStream; +extern CStdOutStream *g_ErrStream; + +static const char * const kPropIdToName[] = +{ + "0" + , "1" + , "2" + , "Path" + , "Name" + , "Extension" + , "Folder" + , "Size" + , "Packed Size" + , "Attributes" + , "Created" + , "Accessed" + , "Modified" + , "Solid" + , "Commented" + , "Encrypted" + , "Split Before" + , "Split After" + , "Dictionary Size" + , "CRC" + , "Type" + , "Anti" + , "Method" + , "Host OS" + , "File System" + , "User" + , "Group" + , "Block" + , "Comment" + , "Position" + , "Path Prefix" + , "Folders" + , "Files" + , "Version" + , "Volume" + , "Multivolume" + , "Offset" + , "Links" + , "Blocks" + , "Volumes" + , "Time Type" + , "64-bit" + , "Big-endian" + , "CPU" + , "Physical Size" + , "Headers Size" + , "Checksum" + , "Characteristics" + , "Virtual Address" + , "ID" + , "Short Name" + , "Creator Application" + , "Sector Size" + , "Mode" + , "Symbolic Link" + , "Error" + , "Total Size" + , "Free Space" + , "Cluster Size" + , "Label" + , "Local Name" + , "Provider" + , "NT Security" + , "Alternate Stream" + , "Aux" + , "Deleted" + , "Tree" + , "SHA-1" + , "SHA-256" + , "Error Type" + , "Errors" + , "Errors" + , "Warnings" + , "Warning" + , "Streams" + , "Alternate Streams" + , "Alternate Streams Size" + , "Virtual Size" + , "Unpack Size" + , "Total Physical Size" + , "Volume Index" + , "SubType" + , "Short Comment" + , "Code Page" + , "Is not archive type" + , "Physical Size can't be detected" + , "Zeros Tail Is Allowed" + , "Tail Size" + , "Embedded Stub Size" + , "Link" + , "Hard Link" + , "iNode" + , "Stream ID" + , "Read-only" + , "Out Name" + , "Copy Link" + , "ArcFileName" + , "IsHash" + , "Metadata Changed" + , "User ID" + , "Group ID" + , "Device Major" + , "Device Minor" + , "Dev Major" + , "Dev Minor" +}; + +static const char kEmptyAttribChar = '.'; + +static const char * const kListing = "Listing archive: "; + +static const char * const kString_Files = "files"; +static const char * const kString_Dirs = "folders"; +static const char * const kString_AltStreams = "alternate streams"; +static const char * const kString_Streams = "streams"; + +static const char * const kError = "ERROR: "; + +static void GetAttribString(UInt32 wa, bool isDir, bool allAttribs, char *s) +{ + if (isDir) + wa |= FILE_ATTRIBUTE_DIRECTORY; + if (allAttribs) + { + ConvertWinAttribToString(s, wa); + return; + } + s[0] = ((wa & FILE_ATTRIBUTE_DIRECTORY) != 0) ? 'D': kEmptyAttribChar; + s[1] = ((wa & FILE_ATTRIBUTE_READONLY) != 0) ? 'R': kEmptyAttribChar; + s[2] = ((wa & FILE_ATTRIBUTE_HIDDEN) != 0) ? 'H': kEmptyAttribChar; + s[3] = ((wa & FILE_ATTRIBUTE_SYSTEM) != 0) ? 'S': kEmptyAttribChar; + s[4] = ((wa & FILE_ATTRIBUTE_ARCHIVE) != 0) ? 'A': kEmptyAttribChar; + s[5] = 0; +} + +enum EAdjustment +{ + kLeft, + kCenter, + kRight +}; + +struct CFieldInfo +{ + PROPID PropID; + bool IsRawProp; + UString NameU; + AString NameA; + EAdjustment TitleAdjustment; + EAdjustment TextAdjustment; + unsigned PrefixSpacesWidth; + unsigned Width; +}; + +struct CFieldInfoInit +{ + PROPID PropID; + const char *Name; + EAdjustment TitleAdjustment; + EAdjustment TextAdjustment; + unsigned PrefixSpacesWidth; + unsigned Width; +}; + +static const CFieldInfoInit kStandardFieldTable[] = +{ + { kpidMTime, " Date Time", kLeft, kLeft, 0, 19 }, + { kpidAttrib, "Attr", kRight, kCenter, 1, 5 }, + { kpidSize, "Size", kRight, kRight, 1, 12 }, + { kpidPackSize, "Compressed", kRight, kRight, 1, 12 }, + { kpidPath, "Name", kLeft, kLeft, 2, 24 } +}; + +static const unsigned kNumSpacesMax = 32; // it must be larger than max CFieldInfoInit.Width +static const char * const g_Spaces = +" " ; + +static void PrintSpaces(unsigned numSpaces) +{ + if (numSpaces > 0 && numSpaces <= kNumSpacesMax) + g_StdOut << g_Spaces + (kNumSpacesMax - numSpaces); +} + +static void PrintSpacesToString(char *dest, unsigned numSpaces) +{ + unsigned i; + for (i = 0; i < numSpaces; i++) + dest[i] = ' '; + dest[i] = 0; +} + +// extern int g_CodePage; + +static void PrintUString(EAdjustment adj, unsigned width, const UString &s, AString &temp) +{ + /* + // we don't need multibyte align. + int codePage = g_CodePage; + if (codePage == -1) + codePage = CP_OEMCP; + if (codePage == CP_UTF8) + ConvertUnicodeToUTF8(s, temp); + else + UnicodeStringToMultiByte2(temp, s, (UINT)codePage); + */ + + unsigned numSpaces = 0; + + if (width > s.Len()) + { + numSpaces = width - s.Len(); + unsigned numLeftSpaces = 0; + switch ((int)adj) + { + // case kLeft: numLeftSpaces = 0; break; + case kCenter: numLeftSpaces = numSpaces / 2; break; + case kRight: numLeftSpaces = numSpaces; break; + // case kLeft: + default: break; + } + PrintSpaces(numLeftSpaces); + numSpaces -= numLeftSpaces; + } + + g_StdOut.PrintUString(s, temp); + PrintSpaces(numSpaces); +} + +static void PrintString(EAdjustment adj, unsigned width, const char *s) +{ + unsigned numSpaces = 0; + unsigned len = (unsigned)strlen(s); + + if (width > len) + { + numSpaces = width - len; + unsigned numLeftSpaces = 0; + switch ((int)adj) + { + // case kLeft: numLeftSpaces = 0; break; + case kCenter: numLeftSpaces = numSpaces / 2; break; + case kRight: numLeftSpaces = numSpaces; break; + // case kLeft: + default: break; + } + PrintSpaces(numLeftSpaces); + numSpaces -= numLeftSpaces; + } + + g_StdOut << s; + PrintSpaces(numSpaces); +} + +static void PrintStringToString(char *dest, EAdjustment adj, unsigned width, const char *textString) +{ + unsigned numSpaces = 0; + unsigned len = (unsigned)strlen(textString); + + if (width > len) + { + numSpaces = width - len; + unsigned numLeftSpaces = 0; + switch ((int)adj) + { + // case kLeft: numLeftSpaces = 0; break; + case kCenter: numLeftSpaces = numSpaces / 2; break; + case kRight: numLeftSpaces = numSpaces; break; + // case kLeft: + default: break; + } + PrintSpacesToString(dest, numLeftSpaces); + dest += numLeftSpaces; + numSpaces -= numLeftSpaces; + } + + memcpy(dest, textString, len); + dest += len; + PrintSpacesToString(dest, numSpaces); +} + +struct CListUInt64Def +{ + UInt64 Val; + bool Def; + + CListUInt64Def(): Val(0), Def(false) {} + void Add(UInt64 v) { Val += v; Def = true; } + void Add(const CListUInt64Def &v) { if (v.Def) Add(v.Val); } +}; + + +struct CListFileTimeDef: public CArcTime +{ + void Update(const CListFileTimeDef &t) + { + if (t.Def && (!Def || CompareWith(t) < 0)) + (*this) = t; + } +}; + + + +struct CListStat +{ + CListUInt64Def Size; + CListUInt64Def PackSize; + CListFileTimeDef MTime; + UInt64 NumFiles; + + CListStat(): NumFiles(0) {} + void Update(const CListStat &st) + { + Size.Add(st.Size); + PackSize.Add(st.PackSize); + MTime.Update(st.MTime); + NumFiles += st.NumFiles; + } + void SetSizeDefIfNoFiles() { if (NumFiles == 0) Size.Def = true; } +}; + +struct CListStat2 +{ + CListStat MainFiles; + CListStat AltStreams; + UInt64 NumDirs; + + CListStat2(): NumDirs(0) {} + + void Update(const CListStat2 &st) + { + MainFiles.Update(st.MainFiles); + AltStreams.Update(st.AltStreams); + NumDirs += st.NumDirs; + } + UInt64 GetNumStreams() const { return MainFiles.NumFiles + AltStreams.NumFiles; } + CListStat &GetStat(bool altStreamsMode) { return altStreamsMode ? AltStreams : MainFiles; } +}; + +class CFieldPrinter +{ + CObjectVector _fields; + + void AddProp(const wchar_t *name, PROPID propID, bool isRawProp); +public: + const CArc *Arc; + bool TechMode; + UString FilePath; + AString TempAString; + UString TempWString; + bool IsDir; + + AString LinesString; + + void Clear() { _fields.Clear(); LinesString.Empty(); } + void Init(const CFieldInfoInit *standardFieldTable, unsigned numItems); + + HRESULT AddMainProps(IInArchive *archive); + HRESULT AddRawProps(IArchiveGetRawProps *getRawProps); + + void PrintTitle(); + void PrintTitleLines(); + HRESULT PrintItemInfo(UInt32 index, const CListStat &st); + void PrintSum(const CListStat &st, UInt64 numDirs, const char *str); + void PrintSum(const CListStat2 &stat2); +}; + +void CFieldPrinter::Init(const CFieldInfoInit *standardFieldTable, unsigned numItems) +{ + Clear(); + for (unsigned i = 0; i < numItems; i++) + { + CFieldInfo &f = _fields.AddNew(); + const CFieldInfoInit &fii = standardFieldTable[i]; + f.PropID = fii.PropID; + f.IsRawProp = false; + f.NameA = fii.Name; + f.TitleAdjustment = fii.TitleAdjustment; + f.TextAdjustment = fii.TextAdjustment; + f.PrefixSpacesWidth = fii.PrefixSpacesWidth; + f.Width = fii.Width; + + unsigned k; + for (k = 0; k < fii.PrefixSpacesWidth; k++) + LinesString.Add_Space(); + for (k = 0; k < fii.Width; k++) + LinesString.Add_Minus(); + } +} + +static void GetPropName(PROPID propID, const wchar_t *name, AString &nameA, UString &nameU) +{ + if (propID < Z7_ARRAY_SIZE(kPropIdToName)) + { + nameA = kPropIdToName[propID]; + return; + } + if (name) + nameU = name; + else + { + nameA.Empty(); + nameA.Add_UInt32(propID); + } +} + +void CFieldPrinter::AddProp(const wchar_t *name, PROPID propID, bool isRawProp) +{ + CFieldInfo f; + f.PropID = propID; + f.IsRawProp = isRawProp; + GetPropName(propID, name, f.NameA, f.NameU); + f.NameU += " = "; + if (!f.NameA.IsEmpty()) + f.NameA += " = "; + else + { + const UString &s = f.NameU; + AString sA; + unsigned i; + for (i = 0; i < s.Len(); i++) + { + const wchar_t c = s[i]; + if (c >= 0x80) + break; + sA.Add_Char((char)c); + } + if (i == s.Len()) + f.NameA = sA; + } + _fields.Add(f); +} + +HRESULT CFieldPrinter::AddMainProps(IInArchive *archive) +{ + UInt32 numProps; + RINOK(archive->GetNumberOfProperties(&numProps)) + for (UInt32 i = 0; i < numProps; i++) + { + CMyComBSTR name; + PROPID propID; + VARTYPE vt; + RINOK(archive->GetPropertyInfo(i, &name, &propID, &vt)) + AddProp(name, propID, false); + } + return S_OK; +} + +HRESULT CFieldPrinter::AddRawProps(IArchiveGetRawProps *getRawProps) +{ + UInt32 numProps; + RINOK(getRawProps->GetNumRawProps(&numProps)) + for (UInt32 i = 0; i < numProps; i++) + { + CMyComBSTR name; + PROPID propID; + RINOK(getRawProps->GetRawPropInfo(i, &name, &propID)) + AddProp(name, propID, true); + } + return S_OK; +} + +void CFieldPrinter::PrintTitle() +{ + FOR_VECTOR (i, _fields) + { + const CFieldInfo &f = _fields[i]; + PrintSpaces(f.PrefixSpacesWidth); + PrintString(f.TitleAdjustment, ((f.PropID == kpidPath) ? 0: f.Width), f.NameA); + } +} + +void CFieldPrinter::PrintTitleLines() +{ + g_StdOut << LinesString; +} + +static void PrintTime(char *dest, const CListFileTimeDef &t, bool showNS) +{ + *dest = 0; + if (t.IsZero()) + return; + int prec = kTimestampPrintLevel_SEC; + unsigned flags = 0; + if (showNS) // techmode + { + prec = kTimestampPrintLevel_NTFS; + if (t.Prec != 0) + { + prec = t.GetNumDigits(); + if (prec < kTimestampPrintLevel_DAY) + prec = kTimestampPrintLevel_NTFS; + } + } + else + { + // we want same default number of characters, so we disable 'Z' marker: + flags = kTimestampPrintFlags_DisableZ; + } + + ConvertUtcFileTimeToString2(t.FT, t.Ns100, dest, prec, flags); +} + +#ifndef Z7_SFX + +#endif + +#define MY_ENDL endl + +inline bool IsPropId_for_PathString(UInt32 propId) +{ + return (propId == kpidPath + // || propId == kpidName + || propId == kpidSymLink + || propId == kpidHardLink + || propId == kpidCopyLink); +} + +HRESULT CFieldPrinter::PrintItemInfo(UInt32 index, const CListStat &st) +{ + char temp[128]; + size_t tempPos = 0; + + bool techMode = this->TechMode; + /* + if (techMode) + { + g_StdOut << "Index = "; + g_StdOut << (UInt64)index; + g_StdOut << endl; + } + */ + FOR_VECTOR (i, _fields) + { + const CFieldInfo &f = _fields[i]; + + if (!techMode) + { + PrintSpacesToString(temp + tempPos, f.PrefixSpacesWidth); + tempPos += f.PrefixSpacesWidth; + } + + if (techMode) + { + if (!f.NameA.IsEmpty()) + g_StdOut << f.NameA; + else + g_StdOut << f.NameU; + } + + if (f.PropID == kpidPath) + { + if (!techMode) + g_StdOut << temp; + g_StdOut.NormalizePrint_UString_Path(FilePath, TempWString, TempAString); + if (techMode) + g_StdOut << MY_ENDL; + continue; + } + + const unsigned width = f.Width; + + if (f.IsRawProp) + { + #ifndef Z7_SFX + + const void *data; + UInt32 dataSize; + UInt32 propType; + RINOK(Arc->GetRawProps->GetRawProp(index, f.PropID, &data, &dataSize, &propType)) + + if (dataSize != 0) + { + bool needPrint = true; + + if (f.PropID == kpidNtSecure) + { + if (propType != NPropDataType::kRaw) + return E_FAIL; + #ifndef Z7_SFX + ConvertNtSecureToString((const Byte *)data, dataSize, TempAString); + g_StdOut << TempAString; + needPrint = false; + #endif + } + else if (f.PropID == kpidNtReparse) + { + UString s; + if (ConvertNtReparseToString((const Byte *)data, dataSize, s)) + { + needPrint = false; + g_StdOut.PrintUString(s, TempAString); + } + } + + if (needPrint) + { + if (propType != NPropDataType::kRaw) + return E_FAIL; + + const UInt32 kMaxDataSize = 64; + + if (dataSize > kMaxDataSize) + { + g_StdOut << "data:"; + g_StdOut << dataSize; + } + else + { + char hexStr[kMaxDataSize * 2 + 4]; + ConvertDataToHex_Lower(hexStr, (const Byte *)data, dataSize); + g_StdOut << hexStr; + } + } + } + + #endif + } + else + { + CPropVariant prop; + switch (f.PropID) + { + case kpidSize: if (st.Size.Def) prop = st.Size.Val; break; + case kpidPackSize: if (st.PackSize.Def) prop = st.PackSize.Val; break; + case kpidMTime: + { + const CListFileTimeDef &mtime = st.MTime; + if (mtime.Def) + prop.SetAsTimeFrom_FT_Prec_Ns100(mtime.FT, mtime.Prec, mtime.Ns100); + break; + } + default: + RINOK(Arc->Archive->GetProperty(index, f.PropID, &prop)) + } + if (f.PropID == kpidAttrib && (prop.vt == VT_EMPTY || prop.vt == VT_UI4)) + { + GetAttribString((prop.vt == VT_EMPTY) ? 0 : prop.ulVal, IsDir, techMode, temp + tempPos); + if (techMode) + g_StdOut << temp + tempPos; + else + tempPos += strlen(temp + tempPos); + } + else if (prop.vt == VT_EMPTY) + { + if (!techMode) + { + PrintSpacesToString(temp + tempPos, width); + tempPos += width; + } + } + else if (prop.vt == VT_FILETIME) + { + CListFileTimeDef t; + t.Set_From_Prop(prop); + PrintTime(temp + tempPos, t, techMode); + if (techMode) + g_StdOut << temp + tempPos; + else + { + size_t len = strlen(temp + tempPos); + tempPos += len; + if (len < (unsigned)f.Width) + { + len = f.Width - len; + PrintSpacesToString(temp + tempPos, (unsigned)len); + tempPos += len; + } + } + } + else if (prop.vt == VT_BSTR) + { + TempWString.SetFromBstr(prop.bstrVal); + // do we need multi-line support here ? + if (IsPropId_for_PathString(f.PropID)) + g_StdOut.Normalize_UString_Path(TempWString); + else + g_StdOut.Normalize_UString(TempWString); + if (techMode) + g_StdOut.PrintUString(TempWString, TempAString); + else + PrintUString(f.TextAdjustment, width, TempWString, TempAString); + } + else + { + char s[64]; + ConvertPropertyToShortString2(s, prop, f.PropID); + if (techMode) + g_StdOut << s; + else + { + PrintStringToString(temp + tempPos, f.TextAdjustment, width, s); + tempPos += strlen(temp + tempPos); + } + } + } + if (techMode) + g_StdOut << MY_ENDL; + } + g_StdOut << MY_ENDL; + return S_OK; +} + +static void PrintNumber(EAdjustment adj, unsigned width, const CListUInt64Def &value) +{ + char s[32]; + s[0] = 0; + if (value.Def) + ConvertUInt64ToString(value.Val, s); + PrintString(adj, width, s); +} + +void Print_UInt64_and_String(AString &s, UInt64 val, const char *name); + +void CFieldPrinter::PrintSum(const CListStat &st, UInt64 numDirs, const char *str) +{ + FOR_VECTOR (i, _fields) + { + const CFieldInfo &f = _fields[i]; + PrintSpaces(f.PrefixSpacesWidth); + if (f.PropID == kpidSize) + PrintNumber(f.TextAdjustment, f.Width, st.Size); + else if (f.PropID == kpidPackSize) + PrintNumber(f.TextAdjustment, f.Width, st.PackSize); + else if (f.PropID == kpidMTime) + { + char s[64]; + s[0] = 0; + if (st.MTime.Def) + PrintTime(s, st.MTime, false); // showNS + PrintString(f.TextAdjustment, f.Width, s); + } + else if (f.PropID == kpidPath) + { + AString s; + Print_UInt64_and_String(s, st.NumFiles, str); + if (numDirs != 0) + { + s += ", "; + Print_UInt64_and_String(s, numDirs, kString_Dirs); + } + PrintString(f.TextAdjustment, 0, s); + } + else + PrintString(f.TextAdjustment, f.Width, ""); + } + g_StdOut << endl; +} + +void CFieldPrinter::PrintSum(const CListStat2 &stat2) +{ + PrintSum(stat2.MainFiles, stat2.NumDirs, kString_Files); + if (stat2.AltStreams.NumFiles != 0) + { + PrintSum(stat2.AltStreams, 0, kString_AltStreams); + CListStat st = stat2.MainFiles; + st.Update(stat2.AltStreams); + PrintSum(st, 0, kString_Streams); + } +} + +static HRESULT GetUInt64Value(IInArchive *archive, UInt32 index, PROPID propID, CListUInt64Def &value) +{ + value.Val = 0; + value.Def = false; + CPropVariant prop; + RINOK(archive->GetProperty(index, propID, &prop)) + value.Def = ConvertPropVariantToUInt64(prop, value.Val); + return S_OK; +} + +static HRESULT GetItemMTime(IInArchive *archive, UInt32 index, CListFileTimeDef &t) +{ + /* maybe we could call CArc::GetItemMTime(UInt32 index, CArcTime &ft, bool &defined) here + that can set default timestamp, if not defined */ + t.Clear(); + // t.Def = false; + CPropVariant prop; + RINOK(archive->GetProperty(index, kpidMTime, &prop)) + if (prop.vt == VT_FILETIME) + t.Set_From_Prop(prop); + else if (prop.vt != VT_EMPTY) + return E_FAIL; + return S_OK; +} + +static void PrintPropNameAndNumber(CStdOutStream &so, const char *name, UInt64 val) +{ + so << name << ": " << val << endl; +} + +static void PrintPropName_and_Eq(CStdOutStream &so, PROPID propID) +{ + const char *s; + char temp[16]; + if (propID < Z7_ARRAY_SIZE(kPropIdToName)) + s = kPropIdToName[propID]; + else + { + ConvertUInt32ToString(propID, temp); + s = temp; + } + so << s << " = "; +} + +static void PrintPropNameAndNumber(CStdOutStream &so, PROPID propID, UInt64 val) +{ + PrintPropName_and_Eq(so, propID); + so << val << endl; +} + +static void PrintPropNameAndNumber_Signed(CStdOutStream &so, PROPID propID, Int64 val) +{ + PrintPropName_and_Eq(so, propID); + so << val << endl; +} + + +static void UString_Replace_CRLF_to_LF(UString &s) +{ + // s.Replace(L"\r\n", L"\n"); + wchar_t *src = s.GetBuf(); + wchar_t *dest = src; + for (;;) + { + wchar_t c = *src++; + if (c == 0) + break; + if (c == '\r' && *src == '\n') + { + src++; + c = '\n'; + } + *dest++ = c; + } + s.ReleaseBuf_SetEnd((unsigned)(size_t)(dest - s.GetBuf())); +} + +static void PrintPropVal_MultiLine(CStdOutStream &so, const wchar_t *val) +{ + UString s (val); + if (s.Find(L'\n') >= 0) + { + so << endl; + so << "{"; + so << endl; + UString_Replace_CRLF_to_LF(s); + UString temp; + unsigned start = 0; + for (;;) + { + unsigned size = s.Len() - start; + if (size == 0) + break; + const int next = s.Find(L'\n', start); + if (next >= 0) + size = (unsigned)next - start; + temp.SetFrom(s.Ptr() + start, size); + so.NormalizePrint_UString(temp); + so << endl; + if (next < 0) + break; + start = (unsigned)next + 1; + } + so << "}"; + } + else + { + so.NormalizePrint_UString(s); + } + so << endl; +} + + +static void PrintPropPair(CStdOutStream &so, const char *name, const wchar_t *val, bool multiLine, bool isPath = false) +{ + so << name << " = "; + if (multiLine) + { + PrintPropVal_MultiLine(so, val); + return; + } + UString s (val); + if (isPath) + so.Normalize_UString_Path(s); + else + so.Normalize_UString(s); + so << s; + so << endl; +} + + +static void PrintPropPair_Path(CStdOutStream &so, const wchar_t *path) +{ + PrintPropPair(so, "Path", path, + false, // multiLine + true); // isPath +} + +static void PrintPropertyPair2(CStdOutStream &so, PROPID propID, const wchar_t *name, const CPropVariant &prop) +{ + UString s; + const int levelTopLimit = 9; // 1ns level + ConvertPropertyToString2(s, prop, propID, levelTopLimit); + if (!s.IsEmpty()) + { + AString nameA; + UString nameU; + GetPropName(propID, name, nameA, nameU); + if (!nameA.IsEmpty()) + so << nameA; + else + so << nameU; + so << " = "; + PrintPropVal_MultiLine(so, s); + } +} + +static HRESULT PrintArcProp(CStdOutStream &so, IInArchive *archive, PROPID propID, const wchar_t *name) +{ + CPropVariant prop; + RINOK(archive->GetArchiveProperty(propID, &prop)) + PrintPropertyPair2(so, propID, name, prop); + return S_OK; +} + +static void PrintArcTypeError(CStdOutStream &so, const UString &type, bool isWarning) +{ + so << "Open " << (isWarning ? "WARNING" : "ERROR") + << ": Cannot open the file as [" + << type + << "] archive" + << endl; +} + +int Find_FileName_InSortedVector(const UStringVector &fileName, const UString& name); + +void PrintErrorFlags(CStdOutStream &so, const char *s, UInt32 errorFlags); + +static void ErrorInfo_Print(CStdOutStream &so, const CArcErrorInfo &er) +{ + PrintErrorFlags(so, "ERRORS:", er.GetErrorFlags()); + if (!er.ErrorMessage.IsEmpty()) + PrintPropPair(so, "ERROR", er.ErrorMessage, true); + + PrintErrorFlags(so, "WARNINGS:", er.GetWarningFlags()); + if (!er.WarningMessage.IsEmpty()) + PrintPropPair(so, "WARNING", er.WarningMessage, true); +} + +HRESULT Print_OpenArchive_Props(CStdOutStream &so, const CCodecs *codecs, const CArchiveLink &arcLink); +HRESULT Print_OpenArchive_Props(CStdOutStream &so, const CCodecs *codecs, const CArchiveLink &arcLink) +{ + FOR_VECTOR (r, arcLink.Arcs) + { + const CArc &arc = arcLink.Arcs[r]; + const CArcErrorInfo &er = arc.ErrorInfo; + + so << "--\n"; + PrintPropPair_Path(so, arc.Path); + if (er.ErrorFormatIndex >= 0) + { + if (er.ErrorFormatIndex == arc.FormatIndex) + so << "Warning: The archive is open with offset" << endl; + else + PrintArcTypeError(so, codecs->GetFormatNamePtr(er.ErrorFormatIndex), true); + } + PrintPropPair(so, "Type", codecs->GetFormatNamePtr(arc.FormatIndex), false); + + ErrorInfo_Print(so, er); + + Int64 offset = arc.GetGlobalOffset(); + if (offset != 0) + PrintPropNameAndNumber_Signed(so, kpidOffset, offset); + IInArchive *archive = arc.Archive; + RINOK(PrintArcProp(so, archive, kpidPhySize, NULL)) + if (er.TailSize != 0) + PrintPropNameAndNumber(so, kpidTailSize, er.TailSize); + { + UInt32 numProps; + RINOK(archive->GetNumberOfArchiveProperties(&numProps)) + + for (UInt32 j = 0; j < numProps; j++) + { + CMyComBSTR name; + PROPID propID; + VARTYPE vt; + RINOK(archive->GetArchivePropertyInfo(j, &name, &propID, &vt)) + RINOK(PrintArcProp(so, archive, propID, name)) + } + } + + if (r != arcLink.Arcs.Size() - 1) + { + UInt32 numProps; + so << "----\n"; + if (archive->GetNumberOfProperties(&numProps) == S_OK) + { + UInt32 mainIndex = arcLink.Arcs[r + 1].SubfileIndex; + for (UInt32 j = 0; j < numProps; j++) + { + CMyComBSTR name; + PROPID propID; + VARTYPE vt; + RINOK(archive->GetPropertyInfo(j, &name, &propID, &vt)) + CPropVariant prop; + RINOK(archive->GetProperty(mainIndex, propID, &prop)) + PrintPropertyPair2(so, propID, name, prop); + } + } + } + } + return S_OK; +} + +HRESULT Print_OpenArchive_Error(CStdOutStream &so, const CCodecs *codecs, const CArchiveLink &arcLink); +HRESULT Print_OpenArchive_Error(CStdOutStream &so, const CCodecs *codecs, const CArchiveLink &arcLink) +{ + #ifndef Z7_NO_CRYPTO + if (arcLink.PasswordWasAsked) + so << "Cannot open encrypted archive. Wrong password?"; + else + #endif + { + if (arcLink.NonOpen_ErrorInfo.ErrorFormatIndex >= 0) + { + so.NormalizePrint_UString_Path(arcLink.NonOpen_ArcPath); + so << endl; + PrintArcTypeError(so, codecs->Formats[(unsigned)arcLink.NonOpen_ErrorInfo.ErrorFormatIndex].Name, false); + } + else + so << "Cannot open the file as archive"; + } + + so << endl; + so << endl; + ErrorInfo_Print(so, arcLink.NonOpen_ErrorInfo); + + return S_OK; +} + +bool CensorNode_CheckPath(const NWildcard::CCensorNode &node, const CReadArcItem &item); + +HRESULT ListArchives( + const CListOptions &listOptions, + CCodecs *codecs, + const CObjectVector &types, + const CIntVector &excludedFormats, + bool stdInMode, + UStringVector &arcPaths, UStringVector &arcPathsFull, + bool processAltStreams, bool showAltStreams, + const NWildcard::CCensorNode &wildcardCensor, + bool enableHeaders, bool techMode, + #ifndef Z7_NO_CRYPTO + bool &passwordEnabled, UString &password, + #endif + #ifndef Z7_SFX + const CObjectVector *props, + #endif + UInt64 &numErrors, + UInt64 &numWarnings) +{ + bool allFilesAreAllowed = wildcardCensor.AreAllAllowed(); + + numErrors = 0; + numWarnings = 0; + + CFieldPrinter fp; + if (!techMode) + fp.Init(kStandardFieldTable, Z7_ARRAY_SIZE(kStandardFieldTable)); + + CListStat2 stat2total; + + CBoolArr skipArcs(arcPaths.Size()); + unsigned arcIndex; + for (arcIndex = 0; arcIndex < arcPaths.Size(); arcIndex++) + skipArcs[arcIndex] = false; + UInt64 numVolumes = 0; + UInt64 numArcs = 0; + UInt64 totalArcSizes = 0; + + HRESULT lastError = 0; + + for (arcIndex = 0; arcIndex < arcPaths.Size(); arcIndex++) + { + if (skipArcs[arcIndex]) + continue; + const UString &arcPath = arcPaths[arcIndex]; + UInt64 arcPackSize = 0; + + if (!stdInMode) + { + NFile::NFind::CFileInfo fi; + if (!fi.Find_FollowLink(us2fs(arcPath))) + { + DWORD errorCode = GetLastError(); + if (errorCode == 0) + errorCode = ERROR_FILE_NOT_FOUND; + lastError = HRESULT_FROM_WIN32(errorCode); + g_StdOut.Flush(); + if (g_ErrStream) + { + *g_ErrStream << endl << kError << NError::MyFormatMessage(errorCode) << endl; + g_ErrStream->NormalizePrint_UString_Path(arcPath); + *g_ErrStream << endl << endl; + } + numErrors++; + continue; + } + if (fi.IsDir()) + { + g_StdOut.Flush(); + if (g_ErrStream) + { + *g_ErrStream << endl << kError; + g_ErrStream->NormalizePrint_UString_Path(arcPath); + *g_ErrStream << " is not a file" << endl << endl; + } + numErrors++; + continue; + } + arcPackSize = fi.Size; + totalArcSizes += arcPackSize; + } + + CArchiveLink arcLink; + + COpenCallbackConsole openCallback; + openCallback.Init(&g_StdOut, g_ErrStream, NULL, listOptions.DisablePercents); + + #ifndef Z7_NO_CRYPTO + + openCallback.PasswordIsDefined = passwordEnabled; + openCallback.Password = password; + + #endif + + /* + CObjectVector optPropsVector; + COptionalOpenProperties &optProps = optPropsVector.AddNew(); + optProps.Props = *props; + */ + + COpenOptions options; + #ifndef Z7_SFX + options.props = props; + #endif + options.codecs = codecs; + options.types = &types; + options.excludedFormats = &excludedFormats; + options.stdInMode = stdInMode; + options.stream = NULL; + options.filePath = arcPath; + + if (enableHeaders) + { + g_StdOut << endl << kListing; + g_StdOut.NormalizePrint_UString_Path(arcPath); + g_StdOut << endl << endl; + } + + HRESULT result = arcLink.Open_Strict(options, &openCallback); + + if (result != S_OK) + { + if (result == E_ABORT) + return result; + if (result != S_FALSE) + lastError = result; + g_StdOut.Flush(); + if (g_ErrStream) + { + *g_ErrStream << endl << kError; + g_ErrStream->NormalizePrint_UString_Path(arcPath); + *g_ErrStream << " : "; + if (result == S_FALSE) + { + Print_OpenArchive_Error(*g_ErrStream, codecs, arcLink); + } + else + { + *g_ErrStream << "opening : "; + if (result == E_OUTOFMEMORY) + *g_ErrStream << "Can't allocate required memory"; + else + *g_ErrStream << NError::MyFormatMessage(result); + } + *g_ErrStream << endl; + } + numErrors++; + continue; + } + + { + FOR_VECTOR (r, arcLink.Arcs) + { + const CArcErrorInfo &arc = arcLink.Arcs[r].ErrorInfo; + if (!arc.WarningMessage.IsEmpty()) + numWarnings++; + if (arc.AreThereWarnings()) + numWarnings++; + if (arc.ErrorFormatIndex >= 0) + numWarnings++; + if (arc.AreThereErrors()) + { + numErrors++; + // break; + } + if (!arc.ErrorMessage.IsEmpty()) + numErrors++; + } + } + + numArcs++; + numVolumes++; + + if (!stdInMode) + { + numVolumes += arcLink.VolumePaths.Size(); + totalArcSizes += arcLink.VolumesSize; + FOR_VECTOR (v, arcLink.VolumePaths) + { + int index = Find_FileName_InSortedVector(arcPathsFull, arcLink.VolumePaths[v]); + if (index >= 0 && (unsigned)index > arcIndex) + skipArcs[(unsigned)index] = true; + } + } + + + if (enableHeaders) + { + RINOK(Print_OpenArchive_Props(g_StdOut, codecs, arcLink)) + + g_StdOut << endl; + if (techMode) + g_StdOut << "----------\n"; + } + + if (enableHeaders && !techMode) + { + fp.PrintTitle(); + g_StdOut << endl; + fp.PrintTitleLines(); + g_StdOut << endl; + } + + const CArc &arc = arcLink.Arcs.Back(); + fp.Arc = &arc; + fp.TechMode = techMode; + IInArchive *archive = arc.Archive; + if (techMode) + { + fp.Clear(); + RINOK(fp.AddMainProps(archive)) + if (arc.GetRawProps) + { + RINOK(fp.AddRawProps(arc.GetRawProps)) + } + } + + CListStat2 stat2; + + UInt32 numItems; + RINOK(archive->GetNumberOfItems(&numItems)) + + CReadArcItem item; + UStringVector pathParts; + + for (UInt32 i = 0; i < numItems; i++) + { + if (NConsoleClose::TestBreakSignal()) + return E_ABORT; + + HRESULT res = arc.GetItem_Path2(i, fp.FilePath); + + if (stdInMode && res == E_INVALIDARG) + break; + RINOK(res) + + if (arc.Ask_Aux) + { + bool isAux; + RINOK(Archive_IsItem_Aux(archive, i, isAux)) + if (isAux) + continue; + } + + bool isAltStream = false; + if (arc.Ask_AltStream) + { + RINOK(Archive_IsItem_AltStream(archive, i, isAltStream)) + if (isAltStream && !processAltStreams) + continue; + } + + RINOK(Archive_IsItem_Dir(archive, i, fp.IsDir)) + + if (fp.IsDir ? listOptions.ExcludeDirItems : listOptions.ExcludeFileItems) + continue; + + if (!allFilesAreAllowed) + { + if (isAltStream) + { + RINOK(arc.GetItem(i, item)) + if (!CensorNode_CheckPath(wildcardCensor, item)) + continue; + } + else + { + SplitPathToParts(fp.FilePath, pathParts); + bool include; + if (!wildcardCensor.CheckPathVect(pathParts, !fp.IsDir, include)) + continue; + if (!include) + continue; + } + } + + CListStat st; + + RINOK(GetUInt64Value(archive, i, kpidSize, st.Size)) + RINOK(GetUInt64Value(archive, i, kpidPackSize, st.PackSize)) + RINOK(GetItemMTime(archive, i, st.MTime)) + + if (fp.IsDir) + stat2.NumDirs++; + else + st.NumFiles = 1; + stat2.GetStat(isAltStream).Update(st); + + if (isAltStream && !showAltStreams) + continue; + RINOK(fp.PrintItemInfo(i, st)) + } + + UInt64 numStreams = stat2.GetNumStreams(); + if (!stdInMode + && !stat2.MainFiles.PackSize.Def + && !stat2.AltStreams.PackSize.Def) + { + if (arcLink.VolumePaths.Size() != 0) + arcPackSize += arcLink.VolumesSize; + stat2.MainFiles.PackSize.Add((numStreams == 0) ? 0 : arcPackSize); + } + + stat2.MainFiles.SetSizeDefIfNoFiles(); + stat2.AltStreams.SetSizeDefIfNoFiles(); + + if (enableHeaders && !techMode) + { + fp.PrintTitleLines(); + g_StdOut << endl; + fp.PrintSum(stat2); + } + + if (enableHeaders) + { + if (arcLink.NonOpen_ErrorInfo.ErrorFormatIndex >= 0) + { + g_StdOut << "----------\n"; + PrintPropPair_Path(g_StdOut, arcLink.NonOpen_ArcPath); + PrintArcTypeError(g_StdOut, codecs->Formats[(unsigned)arcLink.NonOpen_ErrorInfo.ErrorFormatIndex].Name, false); + } + } + + stat2total.Update(stat2); + + g_StdOut.Flush(); + } + + if (enableHeaders && !techMode && (arcPaths.Size() > 1 || numVolumes > 1)) + { + g_StdOut << endl; + fp.PrintTitleLines(); + g_StdOut << endl; + fp.PrintSum(stat2total); + g_StdOut << endl; + PrintPropNameAndNumber(g_StdOut, "Archives", numArcs); + PrintPropNameAndNumber(g_StdOut, "Volumes", numVolumes); + PrintPropNameAndNumber(g_StdOut, "Total archives size", totalArcSizes); + } + + if (numErrors == 1 && lastError != 0) + return lastError; + + return S_OK; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Console/List.h b/src/plugins/7zip/7za/cpp/7zip/UI/Console/List.h new file mode 100644 index 00000000..d87f512b --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Console/List.h @@ -0,0 +1,42 @@ +// List.h + +#ifndef ZIP7_INC_LIST_H +#define ZIP7_INC_LIST_H + +#include "../../../Common/Wildcard.h" + +#include "../Common/LoadCodecs.h" + +struct CListOptions +{ + bool ExcludeDirItems; + bool ExcludeFileItems; + bool DisablePercents; + + CListOptions(): + ExcludeDirItems(false), + ExcludeFileItems(false), + DisablePercents(false) + {} +}; + +HRESULT ListArchives( + const CListOptions &listOptions, + CCodecs *codecs, + const CObjectVector &types, + const CIntVector &excludedFormats, + bool stdInMode, + UStringVector &archivePaths, UStringVector &archivePathsFull, + bool processAltStreams, bool showAltStreams, + const NWildcard::CCensorNode &wildcardCensor, + bool enableHeaders, bool techMode, + #ifndef Z7_NO_CRYPTO + bool &passwordEnabled, UString &password, + #endif + #ifndef Z7_SFX + const CObjectVector *props, + #endif + UInt64 &errors, + UInt64 &numWarnings); + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Console/Main.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Console/Main.cpp new file mode 100644 index 00000000..521fe772 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Console/Main.cpp @@ -0,0 +1,1657 @@ +// Main.cpp + +#include "StdAfx.h" + +#include "../../../Common/MyWindows.h" + +#ifdef _WIN32 + +#ifndef Z7_OLD_WIN_SDK + +#if defined(__MINGW32__) || defined(__MINGW64__) +#include +#else +#include +#endif + +#else // Z7_OLD_WIN_SDK + +typedef struct { + DWORD cb; + DWORD PageFaultCount; + SIZE_T PeakWorkingSetSize; + SIZE_T WorkingSetSize; + SIZE_T QuotaPeakPagedPoolUsage; + SIZE_T QuotaPagedPoolUsage; + SIZE_T QuotaPeakNonPagedPoolUsage; + SIZE_T QuotaNonPagedPoolUsage; + SIZE_T PagefileUsage; + SIZE_T PeakPagefileUsage; +} PROCESS_MEMORY_COUNTERS; +typedef PROCESS_MEMORY_COUNTERS *PPROCESS_MEMORY_COUNTERS; + +#endif // Z7_OLD_WIN_SDK + +#else // _WIN32 +#include +#include +#include +#include +#endif // _WIN32 + +#include "../../../../C/CpuArch.h" + +#include "../../../Common/MyInitGuid.h" + +#include "../../../Common/CommandLineParser.h" +#include "../../../Common/IntToString.h" +#include "../../../Common/MyException.h" +#include "../../../Common/StdInStream.h" +#include "../../../Common/StdOutStream.h" +#include "../../../Common/StringConvert.h" +#include "../../../Common/StringToInt.h" +#include "../../../Common/UTFConvert.h" + +#include "../../../Windows/ErrorMsg.h" +#include "../../../Windows/TimeUtils.h" +#include "../../../Windows/FileDir.h" + +#include "../Common/ArchiveCommandLine.h" +#include "../Common/Bench.h" +#include "../Common/ExitCode.h" +#include "../Common/Extract.h" + +#ifdef Z7_EXTERNAL_CODECS +#include "../Common/LoadCodecs.h" +#endif + +#include "../../Common/RegisterCodec.h" + +#include "BenchCon.h" +#include "ConsoleClose.h" +#include "ExtractCallbackConsole.h" +#include "HashCon.h" +#include "List.h" +#include "OpenCallbackConsole.h" +#include "UpdateCallbackConsole.h" + +#ifdef Z7_PROG_VARIANT_R +#include "../../../../C/7zVersion.h" +#else +#include "../../MyVersion.h" +#endif + +using namespace NWindows; +using namespace NFile; +using namespace NCommandLineParser; + +#ifdef _WIN32 +extern +HINSTANCE g_hInstance; +HINSTANCE g_hInstance = NULL; +#endif + +extern CStdOutStream *g_StdStream; +extern CStdOutStream *g_ErrStream; + +extern unsigned g_NumCodecs; +extern const CCodecInfo *g_Codecs[]; + +extern unsigned g_NumHashers; +extern const CHasherInfo *g_Hashers[]; + +#ifdef Z7_EXTERNAL_CODECS +extern +const CExternalCodecs *g_ExternalCodecs_Ptr; +const CExternalCodecs *g_ExternalCodecs_Ptr; +#endif + +DECLARE_AND_SET_CLIENT_VERSION_VAR + +#if defined(Z7_PROG_VARIANT_Z) + #define PROG_POSTFIX "z" + #define PROG_POSTFIX_2 " (z)" +#elif defined(Z7_PROG_VARIANT_R) + #define PROG_POSTFIX "r" + #define PROG_POSTFIX_2 " (r)" +#elif defined(Z7_PROG_VARIANT_A) || !defined(Z7_EXTERNAL_CODECS) + #define PROG_POSTFIX "a" + #define PROG_POSTFIX_2 " (a)" +#else + #define PROG_POSTFIX "" + #define PROG_POSTFIX_2 "" +#endif + + +static const char * const kCopyrightString = "\n7-Zip" + PROG_POSTFIX_2 + " " MY_VERSION_CPU + " : " MY_COPYRIGHT_DATE "\n"; + +static const char * const kHelpString = + "Usage: 7z" + PROG_POSTFIX + " [...] [...] [@listfile]\n" + "\n" + "\n" + " a : Add files to archive\n" + " b : Benchmark\n" + " d : Delete files from archive\n" + " e : Extract files from archive (without using directory names)\n" + " h : Calculate hash values for files\n" + " i : Show information about supported formats\n" + " l : List contents of archive\n" + " rn : Rename files in archive\n" + " t : Test integrity of archive\n" + " u : Update files to archive\n" + " x : eXtract files with full paths\n" + "\n" + "\n" + " -- : Stop switches and @listfile parsing\n" + " -ai[r[-|0]][m[-|2]][w[-]]{@listfile|!wildcard} : Include archives\n" + " -ax[r[-|0]][m[-|2]][w[-]]{@listfile|!wildcard} : eXclude archives\n" + " -ao{a|s|t|u} : set Overwrite mode\n" + " -an : disable archive_name field\n" + " -bb[0-3] : set output log level\n" + " -bd : disable progress indicator\n" + " -bs{o|e|p}{0|1|2} : set output stream for output/error/progress line\n" + " -bt : show execution time statistics\n" + " -i[r[-|0]][m[-|2]][w[-]]{@listfile|!wildcard} : Include filenames\n" + " -m{Parameters} : set compression Method\n" + " -mmt[N] : set number of CPU threads\n" + " -mx[N] : set compression level: -mx1 (fastest) ... -mx9 (ultra)\n" + " -o{Directory} : set Output directory\n" + #ifndef Z7_NO_CRYPTO + " -p{Password} : set Password\n" + #endif + " -r[-|0] : Recurse subdirectories for name search\n" + " -sa{a|e|s} : set Archive name mode\n" + " -scc{UTF-8|WIN|DOS} : set charset for console input/output\n" + " -scs{UTF-8|UTF-16LE|UTF-16BE|WIN|DOS|{id}} : set charset for list files\n" + " -scrc[CRC32|CRC64|SHA256" +#ifndef Z7_PROG_VARIANT_R + "|SHA1|XXH64" +#ifdef Z7_PROG_VARIANT_Z + "|BLAKE2SP" +#endif +#endif + "|*] : set hash function for x, e, h commands\n" + " -sdel : delete files after compression\n" + " -seml[.] : send archive by email\n" + " -sfx[{name}] : Create SFX archive\n" + " -si[{name}] : read data from stdin\n" + " -slp : set Large Pages mode\n" + " -slt : show technical information for l (List) command\n" + " -snh : store hard links as links\n" + " -snl : store symbolic links as links\n" + " -sni : store NT security information\n" + " -sns[-] : store NTFS alternate streams\n" + " -so : write data to stdout\n" + " -spd : disable wildcard matching for file names\n" + " -spe : eliminate duplication of root folder for extract command\n" + " -spf[2] : use fully qualified file paths\n" + " -ssc[-] : set sensitive case mode\n" + " -sse : stop archive creating, if it can't open some input file\n" + " -ssp : do not change Last Access Time of source files while archiving\n" + " -ssw : compress shared files\n" + " -stl : set archive timestamp from the most recently modified file\n" + " -stm{HexMask} : set CPU thread affinity mask (hexadecimal number)\n" + " -stx{Type} : exclude archive type\n" + " -t{Type} : Set type of archive\n" + " -u[-][p#][q#][r#][x#][y#][z#][!newArchiveName] : Update options\n" + " -v{Size}[b|k|m|g] : Create volumes\n" + " -w[{path}] : assign Work directory. Empty path means a temporary directory\n" + " -x[r[-|0]][m[-|2]][w[-]]{@listfile|!wildcard} : eXclude filenames\n" + " -y : assume Yes on all queries\n"; + +// --------------------------- +// exception messages + +static const char * const kEverythingIsOk = "Everything is Ok"; +static const char * const kUserErrorMessage = "Incorrect command line"; +static const char * const kNoFormats = "7-Zip cannot find the code that works with archives."; +static const char * const kUnsupportedArcTypeMessage = "Unsupported archive type"; +// static const char * const kUnsupportedUpdateArcType = "Can't create archive for that type"; + +#ifndef Z7_EXTRACT_ONLY +#define kDefaultSfxModule "7zCon.sfx" +#endif + +Z7_ATTR_NORETURN +static void ShowMessageAndThrowException(LPCSTR message, NExitCode::EEnum code) +{ + if (g_ErrStream) + *g_ErrStream << endl << "ERROR: " << message << endl; + throw code; +} + + +#ifdef _WIN32 +#define ShowProgInfo(so) +#else +static void ShowProgInfo(CStdOutStream *so) +{ + if (!so) + return; + + *so + + /* + #ifdef __DATE__ + << " " << __DATE__ + #endif + #ifdef __TIME__ + << " " << __TIME__ + #endif + */ + + << " " << (unsigned)(sizeof(void *)) * 8 << "-bit" + + #ifdef __ILP32__ + << " ILP32" + #endif + + #ifdef __ARM_ARCH + << " arm_v:" << __ARM_ARCH + #if (__ARM_ARCH == 8) + // for macos: + #if defined(__ARM_ARCH_8_9__) + << ".9" + #elif defined(__ARM_ARCH_8_8__) + << ".8" + #elif defined(__ARM_ARCH_8_7__) + << ".7" + #elif defined(__ARM_ARCH_8_6__) + << ".6" + #elif defined(__ARM_ARCH_8_5__) + << ".5" + #elif defined(__ARM_ARCH_8_4__) + << ".4" + #elif defined(__ARM_ARCH_8_3__) + << ".3" + #elif defined(__ARM_ARCH_8_2__) + << ".2" + #elif defined(__ARM_ARCH_8_1__) + << ".1" + #endif + #endif + + #if defined(__ARM_ARCH_PROFILE) && \ + ( __ARM_ARCH_PROFILE >= 'A' && __ARM_ARCH_PROFILE <= 'Z' \ + || __ARM_ARCH_PROFILE >= 65 && __ARM_ARCH_PROFILE <= 65 + 25) + << "-" << (char)__ARM_ARCH_PROFILE + #endif + + #ifdef __ARM_ARCH_ISA_THUMB + << " thumb:" << __ARM_ARCH_ISA_THUMB + #endif + #endif + + #ifdef _MIPS_ARCH + << " mips_arch:" << _MIPS_ARCH + #endif + #ifdef __mips_isa_rev + << " mips_isa_rev:" << __mips_isa_rev + #endif + + #ifdef __iset__ + << " e2k_v:" << __iset__ + #endif + ; + + + + #ifdef ENV_HAVE_LOCALE + *so << " locale=" << GetLocale(); + #endif + #ifndef _WIN32 + { + const bool is_IsNativeUTF8 = IsNativeUTF8(); + if (!is_IsNativeUTF8) + *so << " UTF8=" << (is_IsNativeUTF8 ? "+" : "-"); + } + if (!g_ForceToUTF8) + *so << " use-UTF8=" << (g_ForceToUTF8 ? "+" : "-"); + { + const unsigned wchar_t_size = (unsigned)sizeof(wchar_t); + if (wchar_t_size != 4) + *so << " wchar_t=" << wchar_t_size * 8 << "-bit"; + } + { + const unsigned off_t_size = (unsigned)sizeof(off_t); + if (off_t_size != 8) + *so << " Files=" << off_t_size * 8 << "-bit"; + } + #endif + + { + const UInt32 numCpus = NWindows::NSystem::GetNumberOfProcessors(); + *so << " Threads:" << numCpus; + const UInt64 openMAX= NWindows::NSystem::Get_File_OPEN_MAX(); + *so << " OPEN_MAX:" << openMAX; + { + FString temp; + NDir::MyGetTempPath(temp); + if (!temp.IsEqualTo(STRING_PATH_SEPARATOR "tmp" STRING_PATH_SEPARATOR)) + *so << " temp_path:" << temp; + } + } + + #ifdef Z7_7ZIP_ASM + *so << ", ASM"; + #endif + + /* + { + AString s; + GetCpuName(s); + s.Trim(); + *so << ", " << s; + } + + #ifdef __ARM_FEATURE_CRC32 + << " CRC32" + #endif + + + #if (defined MY_CPU_X86_OR_AMD64 || defined(MY_CPU_ARM_OR_ARM64)) + if (CPU_IsSupported_AES()) *so << ",AES"; + #endif + + #ifdef MY_CPU_ARM_OR_ARM64 + if (CPU_IsSupported_CRC32()) *so << ",CRC32"; + #if defined(_WIN32) + if (CPU_IsSupported_CRYPTO()) *so << ",CRYPTO"; + #else + if (CPU_IsSupported_SHA1()) *so << ",SHA1"; + if (CPU_IsSupported_SHA2()) *so << ",SHA2"; + #endif + #endif + */ + + *so << endl; +} +#endif + +static void ShowCopyrightAndHelp(CStdOutStream *so, bool needHelp) +{ + if (!so) + return; + *so << kCopyrightString; + // *so << "# CPUs: " << (UInt64)NWindows::NSystem::GetNumberOfProcessors() << endl; + ShowProgInfo(so); + *so << endl; + if (needHelp) + *so << kHelpString; +} + + +static void PrintStringRight(CStdOutStream &so, const char *s, unsigned size) +{ + unsigned len = MyStringLen(s); + for (unsigned i = len; i < size; i++) + so << ' '; + so << s; +} + +static void PrintUInt32(CStdOutStream &so, UInt32 val, unsigned size) +{ + char s[16]; + ConvertUInt32ToString(val, s); + PrintStringRight(so, s, size); +} + +#ifdef Z7_EXTERNAL_CODECS +static void PrintNumber(CStdOutStream &so, UInt32 val, unsigned numDigits) +{ + AString s; + s.Add_UInt32(val); + while (s.Len() < numDigits) + s.InsertAtFront('0'); + so << s; +} +#endif + +static void PrintLibIndex(CStdOutStream &so, int libIndex) +{ + if (libIndex >= 0) + PrintUInt32(so, (UInt32)libIndex, 2); + else + so << " "; + so << ' '; +} + +static void PrintString(CStdOutStream &so, const UString &s, unsigned size) +{ + unsigned len = s.Len(); + so << s; + for (unsigned i = len; i < size; i++) + so << ' '; +} + +static void PrintWarningsPaths(const CErrorPathCodes &pc, CStdOutStream &so) +{ + FOR_VECTOR(i, pc.Paths) + { + so.NormalizePrint_UString_Path(fs2us(pc.Paths[i])); + so << " : "; + so << NError::MyFormatMessage(pc.Codes[i]) << endl; + } + so << "----------------" << endl; +} + +static int WarningsCheck(HRESULT result, const CCallbackConsoleBase &callback, + const CUpdateErrorInfo &errorInfo, + CStdOutStream *so, + CStdOutStream *se, + bool showHeaders) +{ + int exitCode = NExitCode::kSuccess; + + if (callback.ScanErrors.Paths.Size() != 0) + { + if (se) + { + *se << endl; + *se << "Scan WARNINGS for files and folders:" << endl << endl; + PrintWarningsPaths(callback.ScanErrors, *se); + *se << "Scan WARNINGS: " << callback.ScanErrors.Paths.Size(); + *se << endl; + } + exitCode = NExitCode::kWarning; + } + + if (result != S_OK || errorInfo.ThereIsError()) + { + if (se) + { + UString message; + if (!errorInfo.Message.IsEmpty()) + { + message += errorInfo.Message.Ptr(); + message.Add_LF(); + } + { + FOR_VECTOR(i, errorInfo.FileNames) + { + message += fs2us(errorInfo.FileNames[i]); + message.Add_LF(); + } + } + if (errorInfo.SystemError != 0) + { + message += NError::MyFormatMessage(errorInfo.SystemError); + message.Add_LF(); + } + if (!message.IsEmpty()) + *se << L"\nError:\n" << message; + } + + // we will work with (result) later + // throw CSystemException(result); + return NExitCode::kFatalError; + } + + unsigned numErrors = callback.FailedFiles.Paths.Size(); + if (numErrors == 0) + { + if (showHeaders) + if (callback.ScanErrors.Paths.Size() == 0) + if (so) + { + if (se) + se->Flush(); + *so << kEverythingIsOk << endl; + } + } + else + { + if (se) + { + *se << endl; + *se << "WARNINGS for files:" << endl << endl; + PrintWarningsPaths(callback.FailedFiles, *se); + *se << "WARNING: Cannot open " << numErrors << " file"; + if (numErrors > 1) + *se << 's'; + *se << endl; + } + exitCode = NExitCode::kWarning; + } + + return exitCode; +} + +static void ThrowException_if_Error(HRESULT res) +{ + if (res != S_OK) + throw CSystemException(res); +} + +static void PrintNum(UInt64 val, unsigned numDigits, char c = ' ') +{ + char temp[64]; + char *p = temp + 32; + ConvertUInt64ToString(val, p); + unsigned len = MyStringLen(p); + for (; len < numDigits; len++) + *--p = c; + *g_StdStream << p; +} + +#ifdef _WIN32 + +static void PrintTime(const char *s, UInt64 val, UInt64 total) +{ + *g_StdStream << endl << s << " Time ="; + const UInt32 kFreq = 10000000; + UInt64 sec = val / kFreq; + PrintNum(sec, 6); + *g_StdStream << '.'; + UInt32 ms = (UInt32)(val - (sec * kFreq)) / (kFreq / 1000); + PrintNum(ms, 3, '0'); + + while (val > ((UInt64)1 << 56)) + { + val >>= 1; + total >>= 1; + } + + UInt64 percent = 0; + if (total != 0) + percent = val * 100 / total; + *g_StdStream << " ="; + PrintNum(percent, 5); + *g_StdStream << '%'; +} + +#ifndef UNDER_CE + +#define SHIFT_SIZE_VALUE(x, num) (((x) + (1 << (num)) - 1) >> (num)) + +static void PrintMemUsage(const char *s, UInt64 val) +{ + *g_StdStream << " " << s << " Memory ="; + PrintNum(SHIFT_SIZE_VALUE(val, 20), 7); + *g_StdStream << " MB"; + /* + *g_StdStream << " ="; + PrintNum(SHIFT_SIZE_VALUE(val, 10), 9); + *g_StdStream << " KB"; + */ + #ifdef Z7_LARGE_PAGES + AString lp; + Add_LargePages_String(lp); + if (!lp.IsEmpty()) + *g_StdStream << lp; + #endif +} + +EXTERN_C_BEGIN +typedef BOOL (WINAPI *Func_GetProcessMemoryInfo)(HANDLE Process, + PPROCESS_MEMORY_COUNTERS ppsmemCounters, DWORD cb); +typedef BOOL (WINAPI *Func_QueryProcessCycleTime)(HANDLE Process, PULONG64 CycleTime); +EXTERN_C_END + +#endif + +static inline UInt64 GetTime64(const FILETIME &t) { return ((UInt64)t.dwHighDateTime << 32) | t.dwLowDateTime; } + +static void PrintStat() +{ + FILETIME creationTimeFT, exitTimeFT, kernelTimeFT, userTimeFT; + if (! + #ifdef UNDER_CE + ::GetThreadTimes(::GetCurrentThread() + #else + // NT 3.5 + ::GetProcessTimes(::GetCurrentProcess() + #endif + , &creationTimeFT, &exitTimeFT, &kernelTimeFT, &userTimeFT)) + return; + FILETIME curTimeFT; + NTime::GetCurUtc_FiTime(curTimeFT); + + #ifndef UNDER_CE + +Z7_DIAGNOSTIC_IGNORE_CAST_FUNCTION + + PROCESS_MEMORY_COUNTERS m; + memset(&m, 0, sizeof(m)); + BOOL memDefined = FALSE; + BOOL cycleDefined = FALSE; + ULONG64 cycleTime = 0; + { + /* NT 4.0: GetProcessMemoryInfo() in Psapi.dll + Win7: new function K32GetProcessMemoryInfo() in kernel32.dll + It's faster to call kernel32.dll code than Psapi.dll code + GetProcessMemoryInfo() requires Psapi.lib + Psapi.lib in SDK7+ can link to K32GetProcessMemoryInfo in kernel32.dll + The program with K32GetProcessMemoryInfo will not work on systems before Win7 + // memDefined = GetProcessMemoryInfo(GetCurrentProcess(), &m, sizeof(m)); + */ + const HMODULE kern = ::GetModuleHandleW(L"kernel32.dll"); + Func_GetProcessMemoryInfo + my_GetProcessMemoryInfo = Z7_GET_PROC_ADDRESS( + Func_GetProcessMemoryInfo, kern, + "K32GetProcessMemoryInfo"); + if (!my_GetProcessMemoryInfo) + { + const HMODULE lib = LoadLibraryW(L"Psapi.dll"); + if (lib) + my_GetProcessMemoryInfo = Z7_GET_PROC_ADDRESS( + Func_GetProcessMemoryInfo, lib, + "GetProcessMemoryInfo"); + } + if (my_GetProcessMemoryInfo) + memDefined = my_GetProcessMemoryInfo(GetCurrentProcess(), &m, sizeof(m)); + // FreeLibrary(lib); + const + Func_QueryProcessCycleTime + my_QueryProcessCycleTime = Z7_GET_PROC_ADDRESS( + Func_QueryProcessCycleTime, kern, + "QueryProcessCycleTime"); + if (my_QueryProcessCycleTime) + cycleDefined = my_QueryProcessCycleTime(GetCurrentProcess(), &cycleTime); + } + + #endif + + UInt64 curTime = GetTime64(curTimeFT); + UInt64 creationTime = GetTime64(creationTimeFT); + UInt64 kernelTime = GetTime64(kernelTimeFT); + UInt64 userTime = GetTime64(userTimeFT); + + UInt64 totalTime = curTime - creationTime; + + PrintTime("Kernel ", kernelTime, totalTime); + + const UInt64 processTime = kernelTime + userTime; + + #ifndef UNDER_CE + if (cycleDefined) + { + *g_StdStream << " Cnt:"; + PrintNum(cycleTime / 1000000, 15); + *g_StdStream << " MCycles"; + } + #endif + + PrintTime("User ", userTime, totalTime); + + #ifndef UNDER_CE + if (cycleDefined) + { + *g_StdStream << " Freq (cnt/ptime):"; + UInt64 us = processTime / 10; + if (us == 0) + us = 1; + PrintNum(cycleTime / us, 6); + *g_StdStream << " MHz"; + } + #endif + + PrintTime("Process", processTime, totalTime); + #ifndef UNDER_CE + if (memDefined) PrintMemUsage("Virtual ", m.PeakPagefileUsage); + #endif + + PrintTime("Global ", totalTime, totalTime); + #ifndef UNDER_CE + if (memDefined) PrintMemUsage("Physical", m.PeakWorkingSetSize); + #endif + *g_StdStream << endl; +} + + +#else // ! _WIN32 + +static UInt64 Get_timeofday_us() +{ + struct timeval now; + if (gettimeofday(&now, NULL) == 0) + return (UInt64)now.tv_sec * 1000000 + (UInt64)now.tv_usec; + return 0; +} + +static void PrintTime(const char *s, UInt64 val, UInt64 total_us, UInt64 kFreq) +{ + *g_StdStream << endl << s << " Time ="; + + { + UInt64 sec, ms; + + if (kFreq == 0) + { + sec = val / 1000000; + ms = val % 1000000 / 1000; + } + else + { + sec = val / kFreq; + ms = (UInt32)((val - (sec * kFreq)) * 1000 / kFreq); + } + + PrintNum(sec, 6); + *g_StdStream << '.'; + PrintNum(ms, 3, '0'); + } + + if (total_us == 0) + return; + + UInt64 percent = 0; + if (kFreq == 0) + percent = val * 100 / total_us; + else + { + const UInt64 kMaxVal = (UInt64)(Int64)-1; + UInt32 m = 100000000; + for (;;) + { + if (m == 0 || kFreq == 0) + break; + if (kMaxVal / m > val && + kMaxVal / kFreq > total_us) + break; + if (val > m) + val >>= 1; + else + m >>= 1; + if (kFreq > total_us) + kFreq >>= 1; + else + total_us >>= 1; + } + const UInt64 total = kFreq * total_us; + if (total != 0) + percent = val * m / total; + } + *g_StdStream << " ="; + PrintNum(percent, 5); + *g_StdStream << '%'; +} + +static void PrintStat(const UInt64 startTime) +{ + tms t; + /* clock_t res = */ times(&t); + const UInt64 totalTime = Get_timeofday_us() - startTime; + const UInt64 kFreq = (UInt64)sysconf(_SC_CLK_TCK); + PrintTime("Kernel ", (UInt64)t.tms_stime, totalTime, kFreq); + PrintTime("User ", (UInt64)t.tms_utime, totalTime, kFreq); + PrintTime("Process", (UInt64)t.tms_utime + (UInt64)t.tms_stime, totalTime, kFreq); + PrintTime("Global ", totalTime, totalTime, 0); + *g_StdStream << endl; +} + +#endif // ! _WIN32 + + + + + +static void PrintHexId(CStdOutStream &so, UInt64 id) +{ + char s[32]; + ConvertUInt64ToHex(id, s); + PrintStringRight(so, s, 8); +} + +#ifndef _WIN32 +#include +void Set_ModuleDirPrefix_From_ProgArg0(const char *s); +#endif + +int Main2( + #ifndef _WIN32 + int numArgs, char *args[] + #endif +); +int Main2( + #ifndef _WIN32 + int numArgs, char *args[] + #endif +) +{ + #if defined(MY_CPU_SIZEOF_POINTER) + { unsigned k = sizeof(void *); if (k != MY_CPU_SIZEOF_POINTER) throw "incorrect MY_CPU_PTR_SIZE"; } + #endif + + #if defined(_WIN32) && !defined(UNDER_CE) + SetFileApisToOEM(); + #endif + +#if 1 && !defined(_WIN32) + { + // We increase RLIMIT_NOFILE because a high limit makes it easier for 7-Zip to work with multi-volume archives. + struct rlimit a; + const int kType = RLIMIT_NOFILE; + if (!getrlimit(kType, &a)) + { + // printf("\nrlim_cur=%8lld rlim_max=%8lld \n", (long long)a.rlim_cur, (long long)a.rlim_max); + unsigned newVal = 1 << 12; // rlim_t : it's new soft limit + if (newVal > a.rlim_cur && a.rlim_cur < a.rlim_max) + { + if (newVal > a.rlim_max) + newVal = (unsigned)a.rlim_max; + a.rlim_cur = newVal; + setrlimit(kType, &a); + // if (!getrlimit(kType, &a)) printf("\nrlim_cur=%8lld rlim_max=%8lld \n", (long long)a.rlim_cur, (long long)a.rlim_max); + } + } + } +#endif + + #ifdef ENV_HAVE_LOCALE + // printf("\nBefore SetLocale() : %s\n", IsNativeUtf8() ? "NATIVE UTF-8" : "IS NOT NATIVE UTF-8"); + MY_SetLocale(); + // printf("\nAfter SetLocale() : %s\n", IsNativeUtf8() ? "NATIVE UTF-8" : "IS NOT NATIVE UTF-8"); + #endif + + #ifndef _WIN32 + const UInt64 startTime = Get_timeofday_us(); + #endif + + /* + { + g_StdOut << "DWORD:" << (unsigned)sizeof(DWORD); + g_StdOut << " LONG:" << (unsigned)sizeof(LONG); + g_StdOut << " long:" << (unsigned)sizeof(long); + #ifdef _WIN64 + // g_StdOut << " long long:" << (unsigned)sizeof(long long); + #endif + g_StdOut << " int:" << (unsigned)sizeof(int); + g_StdOut << " void*:" << (unsigned)sizeof(void *); + g_StdOut << endl; + } + */ + + UStringVector commandStrings; + + #ifdef _WIN32 + NCommandLineParser::SplitCommandLine(GetCommandLineW(), commandStrings); + #else + { + if (numArgs > 0) + Set_ModuleDirPrefix_From_ProgArg0(args[0]); + + for (int i = 0; i < numArgs; i++) + { + AString a (args[i]); +#if 0 + printf("\n%d %s :", i, a.Ptr()); + for (unsigned k = 0; k < a.Len(); k++) + printf(" %2x", (unsigned)(Byte)a[k]); +#endif + const UString s = MultiByteToUnicodeString(a); + commandStrings.Add(s); + } + // printf("\n"); + } + + #endif + + #ifndef UNDER_CE + if (commandStrings.Size() > 0) + commandStrings.Delete(0); + #endif + + if (commandStrings.Size() == 0) + { + ShowCopyrightAndHelp(g_StdStream, true); + return 0; + } + + CArcCmdLineOptions options; + + CArcCmdLineParser parser; + + parser.Parse1(commandStrings, options); + + g_StdOut.IsTerminalMode = options.IsStdOutTerminal; + g_StdErr.IsTerminalMode = options.IsStdErrTerminal; + + if (options.Number_for_Out != k_OutStream_stdout) + g_StdStream = (options.Number_for_Out == k_OutStream_stderr ? &g_StdErr : NULL); + + if (options.Number_for_Errors != k_OutStream_stderr) + g_ErrStream = (options.Number_for_Errors == k_OutStream_stdout ? &g_StdOut : NULL); + + CStdOutStream *percentsStream = NULL; + if (options.Number_for_Percents != k_OutStream_disabled) + percentsStream = (options.Number_for_Percents == k_OutStream_stderr) ? &g_StdErr : &g_StdOut; + + if (options.HelpMode) + { + ShowCopyrightAndHelp(g_StdStream, true); + return 0; + } + + if (options.EnableHeaders) + { + if (g_StdStream) + { + ShowCopyrightAndHelp(g_StdStream, false); + if (!parser.Parse1Log.IsEmpty()) + *g_StdStream << parser.Parse1Log; + } + } + + parser.Parse2(options); + + { + int cp = options.ConsoleCodePage; + + int stdout_cp = cp; + int stderr_cp = cp; + int stdin_cp = cp; + + /* + // these cases are complicated. + // maybe we must use CRT functions instead of console WIN32. + // different Windows/CRT versions also can work different ways. + // so the following code was not enabled: + if (cp == -1) + { + // we set CodePage only if stream is attached to terminal + // maybe we should set CodePage even if is not terminal? + #ifdef _WIN32 + { + UINT ccp = GetConsoleOutputCP(); + if (ccp != 0) + { + if (options.IsStdOutTerminal) stdout_cp = ccp; + if (options.IsStdErrTerminal) stderr_cp = ccp; + } + } + if (options.IsInTerminal) + { + UINT ccp = GetConsoleCP(); + if (ccp != 0) stdin_cp = ccp; + } + #endif + } + */ + + if (stdout_cp != -1) g_StdOut.CodePage = stdout_cp; + if (stderr_cp != -1) g_StdErr.CodePage = stderr_cp; + if (stdin_cp != -1) g_StdIn.CodePage = stdin_cp; + } + g_StdOut.ListPathSeparatorSlash = options.ListPathSeparatorSlash; + g_StdErr.ListPathSeparatorSlash = options.ListPathSeparatorSlash; + + unsigned percentsNameLevel = 1; + if (options.LogLevel == 0 || options.Number_for_Percents != options.Number_for_Out) + percentsNameLevel = 2; + + unsigned consoleWidth = 80; + + if (percentsStream) + { + #ifdef _WIN32 + + #if !defined(UNDER_CE) + CONSOLE_SCREEN_BUFFER_INFO consoleInfo; + if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo)) + consoleWidth = (USHORT)consoleInfo.dwSize.X; + #endif + + #else + +#if !defined(__sun) + struct winsize w; + if (ioctl(0, TIOCGWINSZ, &w) == 0) + consoleWidth = w.ws_col; +#endif + #endif + } + + CREATE_CODECS_OBJECT + + codecs->CaseSensitive_Change = options.CaseSensitive_Change; + codecs->CaseSensitive = options.CaseSensitive; + ThrowException_if_Error(codecs->Load()); + Codecs_AddHashArcHandler(codecs); + + #ifdef Z7_EXTERNAL_CODECS + { + g_ExternalCodecs_Ptr = &_externalCodecs; + UString s; + codecs->GetCodecsErrorMessage(s); + if (!s.IsEmpty()) + { + CStdOutStream &so = (g_StdStream ? *g_StdStream : g_StdOut); + so << endl << s << endl; + } + } + #endif + + const bool isExtractGroupCommand = options.Command.IsFromExtractGroup(); + + if (codecs->Formats.Size() == 0 && + (isExtractGroupCommand + || options.Command.CommandType == NCommandType::kList + || options.Command.IsFromUpdateGroup())) + { + #ifdef Z7_EXTERNAL_CODECS + if (!codecs->MainDll_ErrorPath.IsEmpty()) + { + UString s ("Can't load module: "); + s += fs2us(codecs->MainDll_ErrorPath); + throw s; + } + #endif + throw kNoFormats; + } + + CObjectVector types; + if (!ParseOpenTypes(*codecs, options.ArcType, types)) + { + throw kUnsupportedArcTypeMessage; + } + + + CIntVector excludedFormats; + FOR_VECTOR (k, options.ExcludedArcTypes) + { + CIntVector tempIndices; + if (!codecs->FindFormatForArchiveType(options.ExcludedArcTypes[k], tempIndices) + || tempIndices.Size() != 1) + throw kUnsupportedArcTypeMessage; + + + + excludedFormats.AddToUniqueSorted(tempIndices[0]); + // excludedFormats.Sort(); + } + + #ifdef Z7_EXTERNAL_CODECS + if (isExtractGroupCommand + || options.Command.IsFromUpdateGroup() + || options.Command.CommandType == NCommandType::kHash + || options.Command.CommandType == NCommandType::kBenchmark) + ThrowException_if_Error(_externalCodecs.Load()); + #endif + + int retCode = NExitCode::kSuccess; + HRESULT hresultMain = S_OK; + + // bool showStat = options.ShowTime; + + /* + if (!options.EnableHeaders || + options.TechMode) + showStat = false; + */ + + + if (options.Command.CommandType == NCommandType::kInfo) + { + CStdOutStream &so = (g_StdStream ? *g_StdStream : g_StdOut); + unsigned i; + + #ifdef Z7_EXTERNAL_CODECS + so << endl << "Libs:" << endl; + for (i = 0; i < codecs->Libs.Size(); i++) + { + PrintLibIndex(so, (int)i); + const CCodecLib &lib = codecs->Libs[i]; + // if (lib.Version != 0) + so << ": " << (lib.Version >> 16) << "."; + PrintNumber(so, lib.Version & 0xffff, 2); + so << " : " << lib.Path << endl; + } + #endif + + so << endl << "Formats:" << endl; + + const char * const kArcFlags = "KSNFMGOPBELHXCc+a+m+r+"; + const char * const kArcTimeFlags = "wudn"; + const unsigned kNumArcFlags = (unsigned)strlen(kArcFlags); + const unsigned kNumArcTimeFlags = (unsigned)strlen(kArcTimeFlags); + + for (i = 0; i < codecs->Formats.Size(); i++) + { + const CArcInfoEx &arc = codecs->Formats[i]; + + #ifdef Z7_EXTERNAL_CODECS + PrintLibIndex(so, arc.LibIndex); + #else + so << " "; + #endif + + so << (char)(arc.UpdateEnabled ? 'C' : ' '); + + { + unsigned b; + for (b = 0; b < kNumArcFlags; b++) + so << (char)((arc.Flags & ((UInt32)1 << b)) != 0 ? kArcFlags[b] : '.'); + so << ' '; + } + + if (arc.TimeFlags != 0) + { + unsigned b; + for (b = 0; b < kNumArcTimeFlags; b++) + so << (char)((arc.TimeFlags & ((UInt32)1 << b)) != 0 ? kArcTimeFlags[b] : '.'); + so << arc.Get_DefaultTimePrec(); + so << ' '; + } + + so << ' '; + PrintString(so, arc.Name, 8); + so << ' '; + UString s; + + FOR_VECTOR (t, arc.Exts) + { + if (t != 0) + s.Add_Space(); + const CArcExtInfo &ext = arc.Exts[t]; + s += ext.Ext; + if (!ext.AddExt.IsEmpty()) + { + s += " ("; + s += ext.AddExt; + s.Add_Char(')'); + } + } + + PrintString(so, s, 13); + so << ' '; + + if (arc.SignatureOffset != 0) + so << "offset=" << arc.SignatureOffset << ' '; + + // so << "numSignatures = " << arc.Signatures.Size() << " "; + + FOR_VECTOR(si, arc.Signatures) + { + if (si != 0) + so << " || "; + + const CByteBuffer &sig = arc.Signatures[si]; + + for (size_t j = 0; j < sig.Size(); j++) + { + if (j != 0) + so << ' '; + const unsigned b = sig.ConstData()[j]; + if (b > 0x20 && b < 0x80) + { + so << (char)b; + } + else + { + so << GET_HEX_CHAR_UPPER(b >> 4); + so << GET_HEX_CHAR_UPPER(b & 15); + } + } + } + so << endl; + } + + so << endl << "Codecs:" << endl; // << "Lib ID Name" << endl; + + for (i = 0; i < g_NumCodecs; i++) + { + const CCodecInfo &cod = *g_Codecs[i]; + + PrintLibIndex(so, -1); + + if (cod.NumStreams == 1) + so << ' '; + else + so << cod.NumStreams; + + so << (char)(cod.CreateEncoder ? 'E' : ' '); + so << (char)(cod.CreateDecoder ? 'D' : ' '); + so << (char)(cod.IsFilter ? 'F' : ' '); + + so << ' '; + PrintHexId(so, cod.Id); + so << ' ' << cod.Name << endl; + } + + + #ifdef Z7_EXTERNAL_CODECS + + UInt32 numMethods; + if (_externalCodecs.GetCodecs->GetNumMethods(&numMethods) == S_OK) + for (UInt32 j = 0; j < numMethods; j++) + { + PrintLibIndex(so, codecs->GetCodec_LibIndex(j)); + + UInt32 numStreams = codecs->GetCodec_NumStreams(j); + if (numStreams == 1) + so << ' '; + else + so << numStreams; + + so << (char)(codecs->GetCodec_EncoderIsAssigned(j) ? 'E' : ' '); + so << (char)(codecs->GetCodec_DecoderIsAssigned(j) ? 'D' : ' '); + { + bool isFilter_Assigned; + const bool isFilter = codecs->GetCodec_IsFilter(j, isFilter_Assigned); + so << (char)(isFilter ? 'F' : isFilter_Assigned ? ' ' : '*'); + } + + + so << ' '; + UInt64 id; + HRESULT res = codecs->GetCodec_Id(j, id); + if (res != S_OK) + id = (UInt64)(Int64)-1; + PrintHexId(so, id); + so << ' ' << codecs->GetCodec_Name(j) << endl; + } + + #endif + + + so << endl << "Hashers:" << endl; // << " L Size ID Name" << endl; + + for (i = 0; i < g_NumHashers; i++) + { + const CHasherInfo &codec = *g_Hashers[i]; + PrintLibIndex(so, -1); + PrintUInt32(so, codec.DigestSize, 4); + so << ' '; + PrintHexId(so, codec.Id); + so << ' ' << codec.Name << endl; + } + + #ifdef Z7_EXTERNAL_CODECS + + numMethods = _externalCodecs.GetHashers->GetNumHashers(); + for (UInt32 j = 0; j < numMethods; j++) + { + PrintLibIndex(so, codecs->GetHasherLibIndex(j)); + PrintUInt32(so, codecs->GetHasherDigestSize(j), 4); + so << ' '; + PrintHexId(so, codecs->GetHasherId(j)); + so << ' ' << codecs->GetHasherName(j) << endl; + } + + #endif + + } + else if (options.Command.CommandType == NCommandType::kBenchmark) + { + CStdOutStream &so = (g_StdStream ? *g_StdStream : g_StdOut); + hresultMain = BenchCon(EXTERNAL_CODECS_VARS_L + options.Properties, options.NumIterations, (FILE *)so); + if (hresultMain == S_FALSE) + { + so << endl; + if (g_ErrStream) + *g_ErrStream << "\nDecoding ERROR\n"; + retCode = NExitCode::kFatalError; + hresultMain = S_OK; + } + } + else if (isExtractGroupCommand || options.Command.CommandType == NCommandType::kList) + { + UStringVector ArchivePathsSorted; + UStringVector ArchivePathsFullSorted; + + if (options.StdInMode) + { + ArchivePathsSorted.Add(options.ArcName_for_StdInMode); + ArchivePathsFullSorted.Add(options.ArcName_for_StdInMode); + } + else + { + CExtractScanConsole scan; + + scan.Init(options.EnableHeaders ? g_StdStream : NULL, + g_ErrStream, percentsStream, + options.DisablePercents); + scan.SetWindowWidth(consoleWidth); + + if (g_StdStream && options.EnableHeaders) + *g_StdStream << "Scanning the drive for archives:" << endl; + + CDirItemsStat st; + + scan.StartScanning(); + + hresultMain = EnumerateDirItemsAndSort( + options.arcCensor, + NWildcard::k_RelatPath, + UString(), // addPathPrefix + ArchivePathsSorted, + ArchivePathsFullSorted, + st, + &scan); + + scan.CloseScanning(); + + if (hresultMain == S_OK) + { + if (options.EnableHeaders) + scan.PrintStat(st); + } + else + { + /* + if (res != E_ABORT) + { + throw CSystemException(res); + // errorInfo.Message = "Scanning error"; + } + return res; + */ + } + } + + if (hresultMain == S_OK) { + if (isExtractGroupCommand) + { + CExtractCallbackConsole *ecs = new CExtractCallbackConsole; + CMyComPtr extractCallback = ecs; + + #ifndef Z7_NO_CRYPTO + ecs->PasswordIsDefined = options.PasswordEnabled; + ecs->Password = options.Password; + #endif + + ecs->Init(g_StdStream, g_ErrStream, percentsStream, options.DisablePercents); + ecs->MultiArcMode = (ArchivePathsSorted.Size() > 1); + + ecs->LogLevel = options.LogLevel; + ecs->PercentsNameLevel = percentsNameLevel; + + if (percentsStream) + ecs->SetWindowWidth(consoleWidth); + + /* + COpenCallbackConsole openCallback; + openCallback.Init(g_StdStream, g_ErrStream); + + #ifndef Z7_NO_CRYPTO + openCallback.PasswordIsDefined = options.PasswordEnabled; + openCallback.Password = options.Password; + #endif + */ + + CExtractOptions eo; + (CExtractOptionsBase &)eo = options.ExtractOptions; + + eo.StdInMode = options.StdInMode; + eo.StdOutMode = options.StdOutMode; + eo.YesToAll = options.YesToAll; + eo.TestMode = options.Command.IsTestCommand(); + + #ifndef Z7_SFX + eo.Properties = options.Properties; + #endif + + UString errorMessage; + CDecompressStat stat; + CHashBundle hb; + IHashCalc *hashCalc = NULL; + + if (!options.HashMethods.IsEmpty()) + { + hashCalc = &hb; + ThrowException_if_Error(hb.SetMethods(EXTERNAL_CODECS_VARS_L options.HashMethods)); + // hb.Init(); + } + + hresultMain = Extract( + // EXTERNAL_CODECS_VARS_L + codecs, + types, + excludedFormats, + ArchivePathsSorted, + ArchivePathsFullSorted, + options.Censor.Pairs.Front().Head, + eo, + ecs, ecs, ecs, + hashCalc, errorMessage, stat); + + ecs->ClosePercents(); + + if (!errorMessage.IsEmpty()) + { + if (g_ErrStream) + *g_ErrStream << endl << "ERROR:" << endl << errorMessage << endl; + if (hresultMain == S_OK) + hresultMain = E_FAIL; + } + + CStdOutStream *so = g_StdStream; + + bool isError = false; + + if (so) + { + *so << endl; + + if (ecs->NumTryArcs > 1) + { + *so << "Archives: " << ecs->NumTryArcs << endl; + *so << "OK archives: " << ecs->NumOkArcs << endl; + } + } + + if (ecs->NumCantOpenArcs != 0) + { + isError = true; + if (so) + *so << "Can't open as archive: " << ecs->NumCantOpenArcs << endl; + } + + if (ecs->NumArcsWithError != 0) + { + isError = true; + if (so) + *so << "Archives with Errors: " << ecs->NumArcsWithError << endl; + } + + if (so) + { + if (ecs->NumArcsWithWarnings != 0) + *so << "Archives with Warnings: " << ecs->NumArcsWithWarnings << endl; + + if (ecs->NumOpenArcWarnings != 0) + { + *so << endl; + if (ecs->NumOpenArcWarnings != 0) + *so << "Warnings: " << ecs->NumOpenArcWarnings << endl; + } + } + + if (ecs->NumOpenArcErrors != 0) + { + isError = true; + if (so) + { + *so << endl; + if (ecs->NumOpenArcErrors != 0) + *so << "Open Errors: " << ecs->NumOpenArcErrors << endl; + } + } + + if (isError) + retCode = NExitCode::kFatalError; + + if (so) { + if (ecs->NumArcsWithError != 0 || ecs->NumFileErrors != 0) + { + // if (ecs->NumArchives > 1) + { + *so << endl; + if (ecs->NumFileErrors != 0) + *so << "Sub items Errors: " << ecs->NumFileErrors << endl; + } + } + else if (hresultMain == S_OK) + { + if (stat.NumFolders != 0) + *so << "Folders: " << stat.NumFolders << endl; + if (stat.NumFiles != 1 || stat.NumFolders != 0 || stat.NumAltStreams != 0) + *so << "Files: " << stat.NumFiles << endl; + if (stat.NumAltStreams != 0) + { + *so << "Alternate Streams: " << stat.NumAltStreams << endl; + *so << "Alternate Streams Size: " << stat.AltStreams_UnpackSize << endl; + } + + *so + << "Size: " << stat.UnpackSize << endl + << "Compressed: " << stat.PackSize << endl; + if (hashCalc) + { + *so << endl; + PrintHashStat(*so, hb); + } + } + } // if (so) + } + else // if_(!isExtractGroupCommand) + { + UInt64 numErrors = 0; + UInt64 numWarnings = 0; + + // options.ExtractNtOptions.StoreAltStreams = true, if -sns[-] is not definmed + + CListOptions lo; + lo.ExcludeDirItems = options.Censor.ExcludeDirItems; + lo.ExcludeFileItems = options.Censor.ExcludeFileItems; + lo.DisablePercents = options.DisablePercents; + + hresultMain = ListArchives( + lo, + codecs, + types, + excludedFormats, + options.StdInMode, + ArchivePathsSorted, + ArchivePathsFullSorted, + options.ExtractOptions.NtOptions.AltStreams.Val, + options.AltStreams.Val, // we don't want to show AltStreams by default + options.Censor.Pairs.Front().Head, + options.EnableHeaders, + options.TechMode, + #ifndef Z7_NO_CRYPTO + options.PasswordEnabled, + options.Password, + #endif + &options.Properties, + numErrors, numWarnings); + + if (options.EnableHeaders) + if (numWarnings > 0) + g_StdOut << endl << "Warnings: " << numWarnings << endl; + + if (numErrors > 0) + { + if (options.EnableHeaders) + g_StdOut << endl << "Errors: " << numErrors << endl; + retCode = NExitCode::kFatalError; + } + } // if_(isExtractGroupCommand) + } // if_(hresultMain == S_OK) + } + else if (options.Command.IsFromUpdateGroup()) + { + #ifdef Z7_EXTRACT_ONLY + throw "update commands are not implemented"; + #else + CUpdateOptions &uo = options.UpdateOptions; + if (uo.SfxMode && uo.SfxModule.IsEmpty()) + uo.SfxModule = kDefaultSfxModule; + + COpenCallbackConsole openCallback; + openCallback.Init(g_StdStream, g_ErrStream, percentsStream, options.DisablePercents); + + #ifndef Z7_NO_CRYPTO + bool passwordIsDefined = + (options.PasswordEnabled && !options.Password.IsEmpty()); + openCallback.PasswordIsDefined = passwordIsDefined; + openCallback.Password = options.Password; + #endif + + CUpdateCallbackConsole callback; + callback.LogLevel = options.LogLevel; + callback.PercentsNameLevel = percentsNameLevel; + + if (percentsStream) + callback.SetWindowWidth(consoleWidth); + + #ifndef Z7_NO_CRYPTO + callback.PasswordIsDefined = passwordIsDefined; + callback.AskPassword = (options.PasswordEnabled && options.Password.IsEmpty()); + callback.Password = options.Password; + #endif + + callback.StdOutMode = uo.StdOutMode; + callback.Init( + // NULL, + g_StdStream, g_ErrStream, percentsStream, options.DisablePercents); + + CUpdateErrorInfo errorInfo; + + /* + if (!uo.Init(codecs, types, options.ArchiveName)) + throw kUnsupportedUpdateArcType; + */ + hresultMain = UpdateArchive(codecs, + types, + options.ArchiveName, + options.Censor, + uo, + errorInfo, &openCallback, &callback, true); + + callback.ClosePercents2(); + + CStdOutStream *se = g_StdStream; + if (!se) + se = g_ErrStream; + + retCode = WarningsCheck(hresultMain, callback, errorInfo, + g_StdStream, se, + true // options.EnableHeaders + ); + #endif + } + else if (options.Command.CommandType == NCommandType::kHash) + { + const CHashOptions &uo = options.HashOptions; + + CHashCallbackConsole callback; + if (percentsStream) + callback.SetWindowWidth(consoleWidth); + + callback.Init(g_StdStream, g_ErrStream, percentsStream, options.DisablePercents); + callback.PrintHeaders = options.EnableHeaders; + callback.PrintFields = options.ListFields; + + AString errorInfoString; + hresultMain = HashCalc(EXTERNAL_CODECS_VARS_L + options.Censor, uo, + errorInfoString, &callback); + CUpdateErrorInfo errorInfo; + errorInfo.Message = errorInfoString; + CStdOutStream *se = g_StdStream; + if (!se) + se = g_ErrStream; + retCode = WarningsCheck(hresultMain, callback, errorInfo, g_StdStream, se, options.EnableHeaders); + } + else + ShowMessageAndThrowException(kUserErrorMessage, NExitCode::kUserError); + + if (options.ShowTime && g_StdStream) + PrintStat( + #ifndef _WIN32 + startTime + #endif + ); + + ThrowException_if_Error(hresultMain); + + return retCode; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Console/MainAr.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Console/MainAr.cpp new file mode 100644 index 00000000..490950b4 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Console/MainAr.cpp @@ -0,0 +1,235 @@ +// MainAr.cpp + +#include "StdAfx.h" + +#ifdef _WIN32 +#include "../../../../C/DllSecur.h" +#endif +#include "../../../../C/CpuArch.h" + +#include "../../../Common/MyException.h" +#include "../../../Common/StdOutStream.h" + +#include "../../../Windows/ErrorMsg.h" +#include "../../../Windows/NtCheck.h" + +#include "../Common/ArchiveCommandLine.h" +#include "../Common/ExitCode.h" + +#include "ConsoleClose.h" + +using namespace NWindows; + +extern +CStdOutStream *g_StdStream; +CStdOutStream *g_StdStream = NULL; +extern +CStdOutStream *g_ErrStream; +CStdOutStream *g_ErrStream = NULL; + +extern int Main2( + #ifndef _WIN32 + int numArgs, char *args[] + #endif +); + +static const char * const kException_CmdLine_Error_Message = "Command Line Error:"; +static const char * const kExceptionErrorMessage = "ERROR:"; +static const char * const kUserBreakMessage = "Break signaled"; +static const char * const kMemoryExceptionMessage = "ERROR: Can't allocate required memory!"; +static const char * const kUnknownExceptionMessage = "Unknown Error"; +static const char * const kInternalExceptionMessage = "\n\nInternal Error #"; + +static void FlushStreams() +{ + if (g_StdStream) + g_StdStream->Flush(); +} + +static void PrintError(const char *message) +{ + FlushStreams(); + if (g_ErrStream) + *g_ErrStream << "\n\n" << message << endl; +} + +#if defined(_WIN32) && defined(_UNICODE) && !defined(_WIN64) && !defined(UNDER_CE) +#define NT_CHECK_FAIL_ACTION *g_StdStream << "Unsupported Windows version"; return NExitCode::kFatalError; +#endif + +static inline bool CheckIsa() +{ + // __try + { + // some compilers (e2k) support SSE/AVX, but cpuid() can be unavailable or return lower isa support +#ifdef MY_CPU_X86_OR_AMD64 + #if 0 && (defined(__AVX512F__) && defined(__AVX512VL__)) + if (!CPU_IsSupported_AVX512F_AVX512VL()) + return false; + #elif defined(__AVX2__) + if (!CPU_IsSupported_AVX2()) + return false; + #elif defined(__AVX__) + if (!CPU_IsSupported_AVX()) + return false; + #elif defined(__SSE2__) && !defined(MY_CPU_AMD64) || defined(_M_IX86_FP) && (_M_IX86_FP >= 2) + if (!CPU_IsSupported_SSE2()) + return false; + #elif defined(__SSE__) && !defined(MY_CPU_AMD64) || defined(_M_IX86_FP) && (_M_IX86_FP >= 1) + if (!CPU_IsSupported_SSE() || + !CPU_IsSupported_CMOV()) + return false; + #endif +#endif + /* + __asm + { + _emit 0fH + _emit 038H + _emit 0cbH + _emit (0c0H + 0 * 8 + 0) + } + */ + return true; + } + /* + __except (EXCEPTION_EXECUTE_HANDLER) + { + return false; + } + */ +} + +int Z7_CDECL main +( + #ifndef _WIN32 + int numArgs, char *args[] + #endif +) +{ + g_ErrStream = &g_StdErr; + g_StdStream = &g_StdOut; + + // #if (defined(_MSC_VER) && defined(_M_IX86)) + if (!CheckIsa()) + { + PrintError("ERROR: processor doesn't support required ISA extension"); + return NExitCode::kFatalError; + } + // #endif + + NT_CHECK + + NConsoleClose::CCtrlHandlerSetter ctrlHandlerSetter; + int res = 0; + + try + { + #ifdef _WIN32 + My_SetDefaultDllDirectories(); + #endif + + res = Main2( + #ifndef _WIN32 + numArgs, args + #endif + ); + } + catch(const CNewException &) + { + PrintError(kMemoryExceptionMessage); + return (NExitCode::kMemoryError); + } +/* + catch(const NConsoleClose::CCtrlBreakException &) + { + PrintError(kUserBreakMessage); + return (NExitCode::kUserBreak); + } +*/ + catch(const CMessagePathException &e) + { + PrintError(kException_CmdLine_Error_Message); + if (g_ErrStream) + *g_ErrStream << e << endl; + return (NExitCode::kUserError); + } + catch(const CSystemException &systemError) + { + if (systemError.ErrorCode == E_OUTOFMEMORY) + { + PrintError(kMemoryExceptionMessage); + return (NExitCode::kMemoryError); + } + if (systemError.ErrorCode == E_ABORT) + { + PrintError(kUserBreakMessage); + return (NExitCode::kUserBreak); + } + if (g_ErrStream) + { + PrintError("System ERROR:"); + *g_ErrStream << NError::MyFormatMessage(systemError.ErrorCode) << endl; + } + return (NExitCode::kFatalError); + } + catch(NExitCode::EEnum exitCode) + { + FlushStreams(); + if (g_ErrStream) + *g_ErrStream << kInternalExceptionMessage << exitCode << endl; + return (exitCode); + } + catch(const UString &s) + { + if (g_ErrStream) + { + PrintError(kExceptionErrorMessage); + *g_ErrStream << s << endl; + } + return (NExitCode::kFatalError); + } + catch(const AString &s) + { + if (g_ErrStream) + { + PrintError(kExceptionErrorMessage); + *g_ErrStream << s << endl; + } + return (NExitCode::kFatalError); + } + catch(const char *s) + { + if (g_ErrStream) + { + PrintError(kExceptionErrorMessage); + *g_ErrStream << s << endl; + } + return (NExitCode::kFatalError); + } + catch(const wchar_t *s) + { + if (g_ErrStream) + { + PrintError(kExceptionErrorMessage); + *g_ErrStream << s << endl; + } + return (NExitCode::kFatalError); + } + catch(int t) + { + if (g_ErrStream) + { + FlushStreams(); + *g_ErrStream << kInternalExceptionMessage << t << endl; + return (NExitCode::kFatalError); + } + } + catch(...) + { + PrintError(kUnknownExceptionMessage); + return (NExitCode::kFatalError); + } + + return res; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Console/OpenCallbackConsole.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Console/OpenCallbackConsole.cpp new file mode 100644 index 00000000..1e7adf5d --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Console/OpenCallbackConsole.cpp @@ -0,0 +1,115 @@ +// OpenCallbackConsole.cpp + +#include "StdAfx.h" + +#include "OpenCallbackConsole.h" + +#include "ConsoleClose.h" +#include "UserInputUtils.h" + +static HRESULT CheckBreak2() +{ + return NConsoleClose::TestBreakSignal() ? E_ABORT : S_OK; +} + +HRESULT COpenCallbackConsole::Open_CheckBreak() +{ + return CheckBreak2(); +} + +HRESULT COpenCallbackConsole::Open_SetTotal(const UInt64 *files, const UInt64 *bytes) +{ + if (!MultiArcMode && NeedPercents()) + { + if (files) + { + _totalFilesDefined = true; + // _totalFiles = *files; + _percent.Total = *files; + } + else + _totalFilesDefined = false; + + if (bytes) + { + // _totalBytesDefined = true; + _totalBytes = *bytes; + if (!files) + _percent.Total = *bytes; + } + else + { + // _totalBytesDefined = false; + if (!files) + _percent.Total = _totalBytes; + } + } + + return CheckBreak2(); +} + +HRESULT COpenCallbackConsole::Open_SetCompleted(const UInt64 *files, const UInt64 *bytes) +{ + if (!MultiArcMode && NeedPercents()) + { + if (files) + { + _percent.Files = *files; + if (_totalFilesDefined) + _percent.Completed = *files; + } + + if (bytes) + { + if (!_totalFilesDefined) + _percent.Completed = *bytes; + } + _percent.Print(); + } + + return CheckBreak2(); +} + +HRESULT COpenCallbackConsole::Open_Finished() +{ + ClosePercents(); + return S_OK; +} + + +#ifndef Z7_NO_CRYPTO + +HRESULT COpenCallbackConsole::Open_CryptoGetTextPassword(BSTR *password) +{ + *password = NULL; + RINOK(CheckBreak2()) + + if (!PasswordIsDefined) + { + ClosePercents(); + RINOK(GetPassword_HRESULT(_so, Password)) + PasswordIsDefined = true; + } + return StringToBstr(Password, password); +} + +/* +HRESULT COpenCallbackConsole::Open_GetPasswordIfAny(bool &passwordIsDefined, UString &password) +{ + passwordIsDefined = PasswordIsDefined; + password = Password; + return S_OK; +} + +bool COpenCallbackConsole::Open_WasPasswordAsked() +{ + return PasswordWasAsked; +} + +void COpenCallbackConsole::Open_Clear_PasswordWasAsked_Flag () +{ + PasswordWasAsked = false; +} +*/ + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Console/OpenCallbackConsole.h b/src/plugins/7zip/7za/cpp/7zip/UI/Console/OpenCallbackConsole.h new file mode 100644 index 00000000..5e7c19cc --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Console/OpenCallbackConsole.h @@ -0,0 +1,73 @@ +// OpenCallbackConsole.h + +#ifndef ZIP7_INC_OPEN_CALLBACK_CONSOLE_H +#define ZIP7_INC_OPEN_CALLBACK_CONSOLE_H + +#include "../../../Common/StdOutStream.h" + +#include "../Common/ArchiveOpenCallback.h" + +#include "PercentPrinter.h" + +class COpenCallbackConsole: public IOpenCallbackUI +{ +protected: + CPercentPrinter _percent; + + CStdOutStream *_so; + CStdOutStream *_se; + + // UInt64 _totalFiles; + UInt64 _totalBytes; + bool _totalFilesDefined; + // bool _totalBytesDefined; + + bool NeedPercents() const { return _percent._so && !_percent.DisablePrint; } + +public: + + bool MultiArcMode; + + void ClosePercents() + { + if (NeedPercents()) + _percent.ClosePrint(true); + } + + COpenCallbackConsole(): + _totalBytes(0), + _totalFilesDefined(false), + // _totalBytesDefined(false), + MultiArcMode(false) + + #ifndef Z7_NO_CRYPTO + , PasswordIsDefined(false) + // , PasswordWasAsked(false) + #endif + + {} + + virtual ~COpenCallbackConsole() {} + + void Init( + CStdOutStream *outStream, + CStdOutStream *errorStream, + CStdOutStream *percentStream, + bool disablePercents) + { + _so = outStream; + _se = errorStream; + _percent._so = percentStream; + _percent.DisablePrint = disablePercents; + } + + Z7_IFACE_IMP(IOpenCallbackUI) + + #ifndef Z7_NO_CRYPTO + bool PasswordIsDefined; + // bool PasswordWasAsked; + UString Password; + #endif +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Console/PercentPrinter.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Console/PercentPrinter.cpp new file mode 100644 index 00000000..cfdab030 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Console/PercentPrinter.cpp @@ -0,0 +1,186 @@ +// PercentPrinter.cpp + +#include "StdAfx.h" + +#include "../../../Common/IntToString.h" + +#include "PercentPrinter.h" + +static const unsigned kPercentsSize = 4; + +CPercentPrinter::~CPercentPrinter() +{ + ClosePrint(false); +} + +void CPercentPrinterState::ClearCurState() +{ + Completed = 0; + Total = ((UInt64)(Int64)-1); + Files = 0; + Command.Empty(); + FileName.Empty(); +} + +void CPercentPrinter::ClosePrint(bool needFlush) +{ + unsigned num = _printedString.Len(); + if (num != 0) + { + + unsigned i; + + /* '\r' in old MAC OS means "new line". + So we can't use '\r' in some systems */ + + #ifdef _WIN32 + char *start = _temp.GetBuf(num + 2); + char *p = start; + *p++ = '\r'; + for (i = 0; i < num; i++) *p++ = ' '; + *p++ = '\r'; + #else + char *start = _temp.GetBuf(num * 3); + char *p = start; + for (i = 0; i < num; i++) *p++ = '\b'; + for (i = 0; i < num; i++) *p++ = ' '; + for (i = 0; i < num; i++) *p++ = '\b'; + #endif + + *p = 0; + _temp.ReleaseBuf_SetLen((unsigned)(p - start)); + *_so << _temp; + } + if (needFlush) + _so->Flush(); + _printedString.Empty(); +} + +void CPercentPrinter::GetPercents() +{ + char s[32]; + unsigned size; + { + char c = '%'; + UInt64 val = 0; + if (Total == (UInt64)(Int64)-1 || + (Total == 0 && Completed != 0)) + { + val = Completed >> 20; + c = 'M'; + } + else if (Total != 0) + val = Completed * 100 / Total; + ConvertUInt64ToString(val, s); + size = (unsigned)strlen(s); + s[size++] = c; + s[size] = 0; + } + + while (size < kPercentsSize) + { + _s.Add_Space(); + size++; + } + + _s += s; +} + +void CPercentPrinter::Print() +{ + if (DisablePrint) + return; + DWORD tick = 0; + if (_tickStep != 0) + tick = GetTickCount(); + + bool onlyPercentsChanged = false; + + if (!_printedString.IsEmpty()) + { + if (_tickStep != 0 && (UInt32)(tick - _prevTick) < _tickStep) + return; + + CPercentPrinterState &st = *this; + if (_printedState.Command == st.Command + && _printedState.FileName == st.FileName + && _printedState.Files == st.Files) + { + if (_printedState.Total == st.Total + && _printedState.Completed == st.Completed) + return; + onlyPercentsChanged = true; + } + } + + _s.Empty(); + + GetPercents(); + + if (onlyPercentsChanged && _s == _printedPercents) + return; + + _printedPercents = _s; + + if (Files != 0) + { + char s[32]; + ConvertUInt64ToString(Files, s); + // unsigned size = (unsigned)strlen(s); + // for (; size < 3; size++) _s.Add_Space(); + _s.Add_Space(); + _s += s; + // _s += "f"; + } + + + if (!Command.IsEmpty()) + { + _s.Add_Space(); + _s += Command; + } + + if (!FileName.IsEmpty() && _s.Len() < MaxLen) + { + _s.Add_Space(); + + _tempU = FileName; + _so->Normalize_UString_Path(_tempU); + _so->Convert_UString_to_AString(_tempU, _temp); + if (_s.Len() + _temp.Len() > MaxLen) + { + unsigned len = FileName.Len(); + for (; len != 0;) + { + unsigned delta = len / 8; + if (delta == 0) + delta = 1; + len -= delta; + _tempU = FileName; + _tempU.Delete(len / 2, _tempU.Len() - len); + _tempU.Insert(len / 2, L" . "); + _so->Normalize_UString_Path(_tempU); + _so->Convert_UString_to_AString(_tempU, _temp); + if (_s.Len() + _temp.Len() <= MaxLen) + break; + } + if (len == 0) + _temp.Empty(); + } + _s += _temp; + } + + if (_printedString != _s) + { + ClosePrint(false); + *_so << _s; + if (NeedFlush) + _so->Flush(); + _printedString = _s; + } + + _printedState = *this; + + if (_tickStep != 0) + _prevTick = tick; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Console/PercentPrinter.h b/src/plugins/7zip/7za/cpp/7zip/UI/Console/PercentPrinter.h new file mode 100644 index 00000000..379aa1b7 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Console/PercentPrinter.h @@ -0,0 +1,66 @@ +// PercentPrinter.h + +#ifndef ZIP7_INC_PERCENT_PRINTER_H +#define ZIP7_INC_PERCENT_PRINTER_H + +#include "../../../Common/StdOutStream.h" + +struct CPercentPrinterState +{ + UInt64 Completed; + UInt64 Total; + + UInt64 Files; + + AString Command; + UString FileName; + + void ClearCurState(); + + CPercentPrinterState(): + Completed(0), + Total((UInt64)(Int64)-1), + Files(0) + {} +}; + +class CPercentPrinter: public CPercentPrinterState +{ +public: + CStdOutStream *_so; + bool DisablePrint; + bool NeedFlush; + unsigned MaxLen; + +private: + UInt32 _tickStep; + DWORD _prevTick; + + AString _s; + + AString _printedString; + AString _temp; + UString _tempU; + + CPercentPrinterState _printedState; + AString _printedPercents; + + void GetPercents(); + +public: + + CPercentPrinter(UInt32 tickStep = 200): + DisablePrint(false), + NeedFlush(true), + MaxLen(80 - 1), + _tickStep(tickStep), + _prevTick(0) + {} + + ~CPercentPrinter(); + + void ClosePrint(bool needFlush); + void Print(); +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Console/StdAfx.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Console/StdAfx.cpp new file mode 100644 index 00000000..d0feea85 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Console/StdAfx.cpp @@ -0,0 +1,3 @@ +// StdAfx.cpp + +#include "StdAfx.h" diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Console/StdAfx.h b/src/plugins/7zip/7za/cpp/7zip/UI/Console/StdAfx.h new file mode 100644 index 00000000..035267cc --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Console/StdAfx.h @@ -0,0 +1,11 @@ +// StdAfx.h + +#ifndef ZIP7_INC_STDAFX_H +#define ZIP7_INC_STDAFX_H + +#if defined(_MSC_VER) && _MSC_VER >= 1800 +#pragma warning(disable : 4464) // relative include path contains '..' +#endif +#include "../../../Common/Common.h" + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Console/UpdateCallbackConsole.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Console/UpdateCallbackConsole.cpp new file mode 100644 index 00000000..d94ad2a9 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Console/UpdateCallbackConsole.cpp @@ -0,0 +1,1012 @@ +// UpdateCallbackConsole.cpp + +#include "StdAfx.h" + +#include "../../../Common/IntToString.h" + +#include "../../../Windows/ErrorMsg.h" +#include "../../../Windows/FileName.h" + +#ifndef Z7_ST +#include "../../../Windows/Synchronization.h" +#endif + +// #include "../Common/PropIDUtils.h" + +#include "ConsoleClose.h" +#include "UserInputUtils.h" +#include "UpdateCallbackConsole.h" + +using namespace NWindows; + +#ifndef Z7_ST +static NSynchronization::CCriticalSection g_CriticalSection; +#define MT_LOCK NSynchronization::CCriticalSectionLock lock(g_CriticalSection); +#else +#define MT_LOCK +#endif + +static const wchar_t * const kEmptyFileAlias = L"[Content]"; + +static const char * const kOpenArchiveMessage = "Open archive: "; +static const char * const kCreatingArchiveMessage = "Creating archive: "; +static const char * const kUpdatingArchiveMessage = "Updating archive: "; +static const char * const kScanningMessage = "Scanning the drive:"; + +static const char * const kError = "ERROR: "; +static const char * const kWarning = "WARNING: "; + +static HRESULT CheckBreak2() +{ + return NConsoleClose::TestBreakSignal() ? E_ABORT : S_OK; +} + +HRESULT Print_OpenArchive_Props(CStdOutStream &so, const CCodecs *codecs, const CArchiveLink &arcLink); +HRESULT Print_OpenArchive_Error(CStdOutStream &so, const CCodecs *codecs, const CArchiveLink &arcLink); + +void PrintErrorFlags(CStdOutStream &so, const char *s, UInt32 errorFlags); + +void Print_ErrorFormatIndex_Warning(CStdOutStream *_so, const CCodecs *codecs, const CArc &arc); + +HRESULT CUpdateCallbackConsole::OpenResult( + const CCodecs *codecs, const CArchiveLink &arcLink, + const wchar_t *name, HRESULT result) +{ + ClosePercents2(); + + FOR_VECTOR (level, arcLink.Arcs) + { + const CArc &arc = arcLink.Arcs[level]; + const CArcErrorInfo &er = arc.ErrorInfo; + + const UInt32 errorFlags = er.GetErrorFlags(); + + if (errorFlags != 0 || !er.ErrorMessage.IsEmpty()) + { + if (_se) + { + *_se << endl; + if (level != 0) + { + _se->NormalizePrint_UString_Path(arc.Path); + *_se << endl; + } + } + + if (errorFlags != 0) + { + if (_se) + PrintErrorFlags(*_se, "ERRORS:", errorFlags); + } + + if (!er.ErrorMessage.IsEmpty()) + { + if (_se) + { + *_se << "ERRORS:" << endl; + _se->NormalizePrint_UString(er.ErrorMessage); + *_se << endl; + } + } + + if (_se) + { + *_se << endl; + _se->Flush(); + } + } + + UInt32 warningFlags = er.GetWarningFlags(); + + if (warningFlags != 0 || !er.WarningMessage.IsEmpty()) + { + if (_so) + { + *_so << endl; + if (level != 0) + { + _so->NormalizePrint_UString_Path(arc.Path); + *_so << arc.Path << endl; + } + } + + if (warningFlags != 0) + { + if (_so) + PrintErrorFlags(*_so, "WARNINGS:", warningFlags); + } + + if (!er.WarningMessage.IsEmpty()) + { + if (_so) + { + *_so << "WARNINGS:" << endl; + _so->NormalizePrint_UString(er.WarningMessage); + *_so << endl; + } + } + + if (_so) + { + *_so << endl; + if (NeedFlush) + _so->Flush(); + } + } + + + if (er.ErrorFormatIndex >= 0) + { + if (_so) + { + Print_ErrorFormatIndex_Warning(_so, codecs, arc); + if (NeedFlush) + _so->Flush(); + } + } + } + + if (result == S_OK) + { + if (_so) + { + RINOK(Print_OpenArchive_Props(*_so, codecs, arcLink)) + *_so << endl; + } + } + else + { + if (_so) + _so->Flush(); + if (_se) + { + *_se << kError; + _se->NormalizePrint_wstr_Path(name); + *_se << endl; + HRESULT res = Print_OpenArchive_Error(*_se, codecs, arcLink); + RINOK(res) + _se->Flush(); + } + } + + return S_OK; +} + +HRESULT CUpdateCallbackConsole::StartScanning() +{ + if (_so) + *_so << kScanningMessage << endl; + _percent.Command = "Scan "; + return S_OK; +} + +HRESULT CUpdateCallbackConsole::ScanProgress(const CDirItemsStat &st, const FString &path, bool /* isDir */) +{ + if (NeedPercents()) + { + _percent.Files = st.NumDirs + st.NumFiles + st.NumAltStreams; + _percent.Completed = st.GetTotalBytes(); + _percent.FileName = fs2us(path); + _percent.Print(); + } + + return CheckBreak(); +} + +void CCallbackConsoleBase::CommonError(const FString &path, DWORD systemError, bool isWarning) +{ + ClosePercents2(); + + if (_se) + { + if (_so) + _so->Flush(); + + *_se << endl << (isWarning ? kWarning : kError) + << NError::MyFormatMessage(systemError) + << endl; + _se->NormalizePrint_UString_Path(fs2us(path)); + *_se << endl << endl; + _se->Flush(); + } +} + +/* +void CCallbackConsoleBase::CommonError(const char *message) +{ + ClosePercents2(); + + if (_se) + { + if (_so) + _so->Flush(); + + *_se << endl << kError << message << endl; + _se->Flush(); + } +} +*/ + + +HRESULT CCallbackConsoleBase::ScanError_Base(const FString &path, DWORD systemError) +{ + MT_LOCK + + ScanErrors.AddError(path, systemError); + CommonError(path, systemError, true); + + return S_OK; +} + +HRESULT CCallbackConsoleBase::OpenFileError_Base(const FString &path, DWORD systemError) +{ + MT_LOCK + FailedFiles.AddError(path, systemError); + NumNonOpenFiles++; + /* + if (systemError == ERROR_SHARING_VIOLATION) + { + */ + CommonError(path, systemError, true); + return S_FALSE; + /* + } + return systemError; + */ +} + +HRESULT CCallbackConsoleBase::ReadingFileError_Base(const FString &path, DWORD systemError) +{ + MT_LOCK + CommonError(path, systemError, false); + return HRESULT_FROM_WIN32(systemError); +} + +HRESULT CUpdateCallbackConsole::ScanError(const FString &path, DWORD systemError) +{ + return ScanError_Base(path, systemError); +} + + +static void PrintPropPair(AString &s, const char *name, UInt64 val) +{ + char temp[32]; + ConvertUInt64ToString(val, temp); + s += name; + s += ": "; + s += temp; +} + +void PrintSize_bytes_Smart(AString &s, UInt64 val); +void Print_DirItemsStat(AString &s, const CDirItemsStat &st); +void Print_DirItemsStat2(AString &s, const CDirItemsStat2 &st); + +HRESULT CUpdateCallbackConsole::FinishScanning(const CDirItemsStat &st) +{ + if (NeedPercents()) + { + _percent.ClosePrint(true); + _percent.ClearCurState(); + } + + if (_so) + { + AString s; + Print_DirItemsStat(s, st); + *_so << s << endl << endl; + } + return S_OK; +} + +static const char * const k_StdOut_ArcName = "StdOut"; + +HRESULT CUpdateCallbackConsole::StartOpenArchive(const wchar_t *name) +{ + if (_so) + { + *_so << kOpenArchiveMessage; + if (name) + _so->NormalizePrint_wstr_Path(name); + else + *_so << k_StdOut_ArcName; + *_so << endl; + } + return S_OK; +} + +HRESULT CUpdateCallbackConsole::StartArchive(const wchar_t *name, bool updating) +{ + if (NeedPercents()) + _percent.ClosePrint(true); + + _percent.ClearCurState(); + NumNonOpenFiles = 0; + + if (_so) + { + *_so << (updating ? kUpdatingArchiveMessage : kCreatingArchiveMessage); + if (name) + _so->NormalizePrint_wstr_Path(name); + else + *_so << k_StdOut_ArcName; + *_so << endl << endl; + } + return S_OK; +} + +HRESULT CUpdateCallbackConsole::FinishArchive(const CFinishArchiveStat &st) +{ + ClosePercents2(); + + if (_so) + { + AString s; + // Print_UInt64_and_String(s, _percent.Files == 1 ? "file" : "files", _percent.Files); + PrintPropPair(s, "Files read from disk", _percent.Files - NumNonOpenFiles); + s.Add_LF(); + s += "Archive size: "; + PrintSize_bytes_Smart(s, st.OutArcFileSize); + s.Add_LF(); + if (st.IsMultiVolMode) + { + s += "Volumes: "; + s.Add_UInt32(st.NumVolumes); + s.Add_LF(); + } + *_so << endl; + *_so << s; + // *_so << endl; + } + + return S_OK; +} + +HRESULT CUpdateCallbackConsole::WriteSfx(const wchar_t *name, UInt64 size) +{ + if (_so) + { + *_so << "Write SFX: "; + *_so << name; + AString s (" : "); + PrintSize_bytes_Smart(s, size); + *_so << s << endl; + } + return S_OK; +} + + + +HRESULT CUpdateCallbackConsole::MoveArc_UpdateStatus() +{ + if (NeedPercents()) + { + AString &s = _percent.Command; + s = " : "; + s.Add_UInt64(_arcMoving_percents); + s.Add_Char('%'); + const bool totalDefined = (_arcMoving_total != 0 && _arcMoving_total != (UInt64)(Int64)-1); + if (_arcMoving_current != 0 || totalDefined) + { + s += " : "; + s.Add_UInt64(_arcMoving_current >> 20); + s += " MiB"; + } + if (totalDefined) + { + s += " / "; + s.Add_UInt64((_arcMoving_total + ((1 << 20) - 1)) >> 20); + s += " MiB"; + } + s += " : temporary archive moving ..."; + _percent.Print(); + } + + // we ignore single Ctrl-C, if (_arcMoving_updateMode) mode + // because we want to get good final archive instead of temp archive. + if (NConsoleClose::g_BreakCounter == 1 && _arcMoving_updateMode) + return S_OK; + return CheckBreak(); +} + + +HRESULT CUpdateCallbackConsole::MoveArc_Start( + const wchar_t *srcTempPath, const wchar_t *destFinalPath, + UInt64 size, Int32 updateMode) +{ +#if 0 // 1 : for debug + if (LogLevel > 0 && _so) + { + ClosePercents_for_so(); + *_so << "Temporary archive moving:" << endl; + _tempU = srcTempPath; + _so->Normalize_UString_Path(_tempU); + _so->PrintUString(_tempU, _tempA); + *_so << endl; + _tempU = destFinalPath; + _so->Normalize_UString_Path(_tempU); + _so->PrintUString(_tempU, _tempA); + *_so << endl; + } +#else + UNUSED_VAR(srcTempPath) + UNUSED_VAR(destFinalPath) +#endif + + _arcMoving_updateMode = updateMode; + _arcMoving_total = size; + _arcMoving_current = 0; + _arcMoving_percents = 0; + return MoveArc_UpdateStatus(); +} + + +HRESULT CUpdateCallbackConsole::MoveArc_Progress(UInt64 totalSize, UInt64 currentSize) +{ +#if 0 // 1 : for debug + if (_so) + { + ClosePercents_for_so(); + *_so << totalSize << " : " << currentSize << endl; + } +#endif + + UInt64 percents = 0; + if (totalSize != 0) + { + if (totalSize < ((UInt64)1 << 57)) + percents = currentSize * 100 / totalSize; + else + percents = currentSize / (totalSize / 100); + } + +#ifdef _WIN32 + // Sleep(300); // for debug +#endif + // totalSize = (UInt64)(Int64)-1; // for debug + + if (percents == _arcMoving_percents) + return CheckBreak(); + _arcMoving_current = currentSize; + _arcMoving_total = totalSize; + _arcMoving_percents = percents; + return MoveArc_UpdateStatus(); +} + + +HRESULT CUpdateCallbackConsole::MoveArc_Finish() +{ + // _arcMoving_percents = 0; + if (NeedPercents()) + { + _percent.Command.Empty(); + _percent.Print(); + } + // it can return delayed user break (E_ABORT) status, + // if it ignored single CTRL+C in MoveArc_Progress(). + return CheckBreak(); +} + + + +HRESULT CUpdateCallbackConsole::DeletingAfterArchiving(const FString &path, bool /* isDir */) +{ + if (LogLevel > 0 && _so) + { + ClosePercents_for_so(); + + if (!DeleteMessageWasShown) + { + if (_so) + *_so << endl << ": Removing files after including to archive" << endl; + } + + { + { + _tempA = "Removing"; + _tempA.Add_Space(); + *_so << _tempA; + _tempU = fs2us(path); + _so->Normalize_UString_Path(_tempU); + _so->PrintUString(_tempU, _tempA); + *_so << endl; + if (NeedFlush) + _so->Flush(); + } + } + } + + if (!DeleteMessageWasShown) + { + if (NeedPercents()) + { + _percent.ClearCurState(); + } + DeleteMessageWasShown = true; + } + else + { + _percent.Files++; + } + + if (NeedPercents()) + { + // if (!FullLog) + { + _percent.Command = "Removing"; + _percent.FileName = fs2us(path); + } + _percent.Print(); + } + + return S_OK; +} + + +HRESULT CUpdateCallbackConsole::FinishDeletingAfterArchiving() +{ + ClosePercents2(); + if (_so && DeleteMessageWasShown) + *_so << endl; + return S_OK; +} + +HRESULT CUpdateCallbackConsole::CheckBreak() +{ + return CheckBreak2(); +} + +/* +HRESULT CUpdateCallbackConsole::Finalize() +{ + // MT_LOCK + return S_OK; +} +*/ + + +void static PrintToDoStat(CStdOutStream *_so, const CDirItemsStat2 &stat, const char *name) +{ + AString s; + Print_DirItemsStat2(s, stat); + *_so << name << ": " << s << endl; +} + +HRESULT CUpdateCallbackConsole::SetNumItems(const CArcToDoStat &stat) +{ + if (_so) + { + ClosePercents_for_so(); + if (!stat.DeleteData.IsEmpty()) + { + *_so << endl; + PrintToDoStat(_so, stat.DeleteData, "Delete data from archive"); + } + if (!stat.OldData.IsEmpty()) + PrintToDoStat(_so, stat.OldData, "Keep old data in archive"); + // if (!stat.NewData.IsEmpty()) + { + PrintToDoStat(_so, stat.NewData, "Add new data to archive"); + } + *_so << endl; + } + return S_OK; +} + +HRESULT CUpdateCallbackConsole::SetTotal(UInt64 size) +{ + MT_LOCK + if (NeedPercents()) + { + _percent.Total = size; + _percent.Print(); + } + return S_OK; +} + +HRESULT CUpdateCallbackConsole::SetCompleted(const UInt64 *completeValue) +{ + MT_LOCK + if (completeValue) + { + if (NeedPercents()) + { + _percent.Completed = *completeValue; + _percent.Print(); + } + } + return CheckBreak2(); +} + +HRESULT CUpdateCallbackConsole::SetRatioInfo(const UInt64 * /* inSize */, const UInt64 * /* outSize */) +{ + return CheckBreak2(); +} + +HRESULT CCallbackConsoleBase::PrintProgress(const wchar_t *name, bool isDir, const char *command, bool showInLog) +{ + MT_LOCK + + bool show2 = (showInLog && _so); + + if (show2) + { + ClosePercents_for_so(); + + _tempA = command; + if (name) + _tempA.Add_Space(); + *_so << _tempA; + + _tempU.Empty(); + if (name) + { + _tempU = name; + if (isDir) + NWindows::NFile::NName::NormalizeDirPathPrefix(_tempU); + _so->Normalize_UString_Path(_tempU); + } + _so->PrintUString(_tempU, _tempA); + *_so << endl; + if (NeedFlush) + _so->Flush(); + } + + if (NeedPercents()) + { + if (PercentsNameLevel >= 1) + { + _percent.FileName.Empty(); + _percent.Command.Empty(); + if (PercentsNameLevel > 1 || !show2) + { + _percent.Command = command; + if (name) + _percent.FileName = name; + } + } + _percent.Print(); + } + + return CheckBreak2(); +} + + +/* +void CCallbackConsoleBase::PrintInfoLine(const UString &s) +{ + if (LogLevel < 1000) + return; + + MT_LOCK + + const bool show2 = (_so != NULL); + + if (show2) + { + ClosePercents_for_so(); + _so->PrintUString(s, _tempA); + *_so << endl; + if (NeedFlush) + _so->Flush(); + } +} +*/ + +HRESULT CUpdateCallbackConsole::GetStream(const wchar_t *name, bool isDir, bool isAnti, UInt32 mode) +{ + if (StdOutMode) + return S_OK; + + if (!name || name[0] == 0) + name = kEmptyFileAlias; + + unsigned requiredLevel = 1; + + const char *s; + if (mode == NUpdateNotifyOp::kAdd || + mode == NUpdateNotifyOp::kUpdate) + { + if (isAnti) + s = "Anti"; + else if (mode == NUpdateNotifyOp::kAdd) + s = "+"; + else + s = "U"; + } + else + { + requiredLevel = 3; + if (mode == NUpdateNotifyOp::kAnalyze) + s = "A"; + else + s = "Reading"; + } + + return PrintProgress(name, isDir, s, LogLevel >= requiredLevel); +} + +HRESULT CUpdateCallbackConsole::OpenFileError(const FString &path, DWORD systemError) +{ + return OpenFileError_Base(path, systemError); +} + +HRESULT CUpdateCallbackConsole::ReadingFileError(const FString &path, DWORD systemError) +{ + return ReadingFileError_Base(path, systemError); +} + +HRESULT CUpdateCallbackConsole::SetOperationResult(Int32 /* opRes */) +{ + MT_LOCK + _percent.Files++; + /* + if (opRes != NArchive::NUpdate::NOperationResult::kOK) + { + if (opRes == NArchive::NUpdate::NOperationResult::kError_FileChanged) + { + CommonError("Input file changed"); + } + } + */ + return S_OK; +} + +void SetExtractErrorMessage(Int32 opRes, Int32 encrypted, AString &dest); + +HRESULT CUpdateCallbackConsole::ReportExtractResult(Int32 opRes, Int32 isEncrypted, const wchar_t *name) +{ + // if (StdOutMode) return S_OK; + + if (opRes != NArchive::NExtract::NOperationResult::kOK) + { + ClosePercents2(); + + if (_se) + { + if (_so) + _so->Flush(); + + AString s; + SetExtractErrorMessage(opRes, isEncrypted, s); + *_se << s << " : " << endl; + _se->NormalizePrint_wstr_Path(name); + *_se << endl << endl; + _se->Flush(); + } + return S_OK; + } + return S_OK; +} + + +HRESULT CUpdateCallbackConsole::ReportUpdateOperation(UInt32 op, const wchar_t *name, bool isDir) +{ + // if (StdOutMode) return S_OK; + + char temp[16]; + const char *s; + + unsigned requiredLevel = 1; + + switch (op) + { + case NUpdateNotifyOp::kAdd: s = "+"; break; + case NUpdateNotifyOp::kUpdate: s = "U"; break; + case NUpdateNotifyOp::kAnalyze: s = "A"; requiredLevel = 3; break; + case NUpdateNotifyOp::kReplicate: s = "="; requiredLevel = 3; break; + case NUpdateNotifyOp::kRepack: s = "R"; requiredLevel = 2; break; + case NUpdateNotifyOp::kSkip: s = "."; requiredLevel = 2; break; + case NUpdateNotifyOp::kDelete: s = "D"; requiredLevel = 3; break; + case NUpdateNotifyOp::kHeader: s = "Header creation"; requiredLevel = 100; break; + case NUpdateNotifyOp::kInFileChanged: s = "Size of input file was changed:"; requiredLevel = 10; break; + // case NUpdateNotifyOp::kOpFinished: s = "Finished"; requiredLevel = 100; break; + default: + { + temp[0] = 'o'; + temp[1] = 'p'; + ConvertUInt64ToString(op, temp + 2); + s = temp; + } + } + + return PrintProgress(name, isDir, s, LogLevel >= requiredLevel); +} + +/* +HRESULT CUpdateCallbackConsole::SetPassword(const UString & + #ifndef Z7_NO_CRYPTO + password + #endif + ) +{ + #ifndef Z7_NO_CRYPTO + PasswordIsDefined = true; + Password = password; + #endif + return S_OK; +} +*/ + +HRESULT CUpdateCallbackConsole::CryptoGetTextPassword2(Int32 *passwordIsDefined, BSTR *password) +{ + COM_TRY_BEGIN + + *password = NULL; + + #ifdef Z7_NO_CRYPTO + + *passwordIsDefined = false; + return S_OK; + + #else + + if (!PasswordIsDefined) + { + if (AskPassword) + { + RINOK(GetPassword_HRESULT(_so, Password)) + PasswordIsDefined = true; + } + } + *passwordIsDefined = BoolToInt(PasswordIsDefined); + return StringToBstr(Password, password); + + #endif + + COM_TRY_END +} + +HRESULT CUpdateCallbackConsole::CryptoGetTextPassword(BSTR *password) +{ + COM_TRY_BEGIN + + *password = NULL; + + #ifdef Z7_NO_CRYPTO + + return E_NOTIMPL; + + #else + + if (!PasswordIsDefined) + { + { + RINOK(GetPassword_HRESULT(_so, Password)) + PasswordIsDefined = true; + } + } + return StringToBstr(Password, password); + + #endif + COM_TRY_END +} + +HRESULT CUpdateCallbackConsole::ShowDeleteFile(const wchar_t *name, bool isDir) +{ + if (StdOutMode) + return S_OK; + + if (LogLevel > 7) + { + if (!name || name[0] == 0) + name = kEmptyFileAlias; + return PrintProgress(name, isDir, "D", true); + } + return S_OK; +} + +/* +void GetPropName(PROPID propID, const wchar_t *name, AString &nameA, UString &nameU); + +static void GetPropName(PROPID propID, UString &nameU) +{ + AString nameA; + GetPropName(propID, NULL, nameA, nameU); + // if (!nameA.IsEmpty()) + nameU = nameA; +} + + +static void AddPropNamePrefix(UString &s, PROPID propID) +{ + UString name; + GetPropName(propID, name); + s += name; + s += " = "; +} + +void CCallbackConsoleBase::PrintPropInfo(UString &s, PROPID propID, const PROPVARIANT *value) +{ + AddPropNamePrefix(s, propID); + { + UString dest; + const int level = 9; // we show up to ns precision level + ConvertPropertyToString2(dest, *value, propID, level); + s += dest; + } + PrintInfoLine(s); +} + +static void Add_IndexType_Index(UString &s, UInt32 indexType, UInt32 index) +{ + if (indexType == NArchive::NEventIndexType::kArcProp) + { + } + else + { + if (indexType == NArchive::NEventIndexType::kBlockIndex) + { + s += "#"; + } + else if (indexType == NArchive::NEventIndexType::kOutArcIndex) + { + } + else + { + s += "indexType_"; + s.Add_UInt32(indexType); + s.Add_Space(); + } + s.Add_UInt32(index); + } + s += ": "; +} + +HRESULT CUpdateCallbackConsole::ReportProp(UInt32 indexType, UInt32 index, PROPID propID, const PROPVARIANT *value) +{ + UString s; + Add_IndexType_Index(s, indexType, index); + PrintPropInfo(s, propID, value); + return S_OK; +} + +static inline char GetHex(Byte value) +{ + return (char)((value < 10) ? ('0' + value) : ('a' + (value - 10))); +} + +static void AddHexToString(UString &dest, const Byte *data, UInt32 size) +{ + for (UInt32 i = 0; i < size; i++) + { + Byte b = data[i]; + dest += GetHex((Byte)((b >> 4) & 0xF)); + dest += GetHex((Byte)(b & 0xF)); + } +} + +void HashHexToString(char *dest, const Byte *data, UInt32 size); + +HRESULT CUpdateCallbackConsole::ReportRawProp(UInt32 indexType, UInt32 index, + PROPID propID, const void *data, UInt32 dataSize, UInt32 propType) +{ + UString s; + propType = propType; + Add_IndexType_Index(s, indexType, index); + AddPropNamePrefix(s, propID); + if (propID == kpidChecksum) + { + char temp[k_HashCalc_DigestSize_Max + 8]; + HashHexToString(temp, (const Byte *)data, dataSize); + s += temp; + } + else + AddHexToString(s, (const Byte *)data, dataSize); + PrintInfoLine(s); + return S_OK; +} + +HRESULT CUpdateCallbackConsole::ReportFinished(UInt32 indexType, UInt32 index, Int32 opRes) +{ + UString s; + Add_IndexType_Index(s, indexType, index); + s += "finished"; + if (opRes != NArchive::NUpdate::NOperationResult::kOK) + { + s += ": "; + s.Add_UInt32(opRes); + } + PrintInfoLine(s); + return S_OK; +} +*/ diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Console/UpdateCallbackConsole.h b/src/plugins/7zip/7za/cpp/7zip/UI/Console/UpdateCallbackConsole.h new file mode 100644 index 00000000..a3863711 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Console/UpdateCallbackConsole.h @@ -0,0 +1,148 @@ +// UpdateCallbackConsole.h + +#ifndef ZIP7_INC_UPDATE_CALLBACK_CONSOLE_H +#define ZIP7_INC_UPDATE_CALLBACK_CONSOLE_H + +#include "../../../Common/StdOutStream.h" + +#include "../Common/Update.h" + +#include "PercentPrinter.h" + +struct CErrorPathCodes +{ + FStringVector Paths; + CRecordVector Codes; + + void AddError(const FString &path, DWORD systemError) + { + Paths.Add(path); + Codes.Add(systemError); + } + void Clear() + { + Paths.Clear(); + Codes.Clear(); + } +}; + + +class CCallbackConsoleBase +{ + void CommonError(const FString &path, DWORD systemError, bool isWarning); + +protected: + CStdOutStream *_so; + CStdOutStream *_se; + + HRESULT ScanError_Base(const FString &path, DWORD systemError); + HRESULT OpenFileError_Base(const FString &name, DWORD systemError); + HRESULT ReadingFileError_Base(const FString &name, DWORD systemError); + +public: + bool StdOutMode; + bool NeedFlush; + unsigned PercentsNameLevel; + unsigned LogLevel; + +protected: + AString _tempA; + UString _tempU; + CPercentPrinter _percent; + +public: + CErrorPathCodes FailedFiles; + CErrorPathCodes ScanErrors; + UInt64 NumNonOpenFiles; + + CCallbackConsoleBase(): + StdOutMode(false), + NeedFlush(false), + PercentsNameLevel(1), + LogLevel(0), + NumNonOpenFiles(0) + {} + + bool NeedPercents() const { return _percent._so != NULL; } + void SetWindowWidth(unsigned width) { _percent.MaxLen = width - 1; } + + void Init( + CStdOutStream *outStream, + CStdOutStream *errorStream, + CStdOutStream *percentStream, + bool disablePercents) + { + FailedFiles.Clear(); + + _so = outStream; + _se = errorStream; + _percent._so = percentStream; + _percent.DisablePrint = disablePercents; + } + + void ClosePercents2() + { + if (NeedPercents()) + _percent.ClosePrint(true); + } + + void ClosePercents_for_so() + { + if (NeedPercents() && _so == _percent._so) + _percent.ClosePrint(false); + } + + HRESULT PrintProgress(const wchar_t *name, bool isDir, const char *command, bool showInLog); + + // void PrintInfoLine(const UString &s); + // void PrintPropInfo(UString &s, PROPID propID, const PROPVARIANT *value); +}; + + +class CUpdateCallbackConsole Z7_final: + public IUpdateCallbackUI2, + public CCallbackConsoleBase +{ + // void PrintPropPair(const char *name, const wchar_t *val); + Z7_IFACE_IMP(IUpdateCallbackUI) + Z7_IFACE_IMP(IDirItemsCallback) + Z7_IFACE_IMP(IUpdateCallbackUI2) + + HRESULT MoveArc_UpdateStatus(); + + UInt64 _arcMoving_total; + UInt64 _arcMoving_current; + UInt64 _arcMoving_percents; + Int32 _arcMoving_updateMode; + +public: + bool DeleteMessageWasShown; + + #ifndef Z7_NO_CRYPTO + bool PasswordIsDefined; + bool AskPassword; + UString Password; + #endif + + CUpdateCallbackConsole(): + _arcMoving_total(0) + , _arcMoving_current(0) + , _arcMoving_percents(0) + , _arcMoving_updateMode(0) + , DeleteMessageWasShown(false) + #ifndef Z7_NO_CRYPTO + , PasswordIsDefined(false) + , AskPassword(false) + #endif + {} + + /* + void Init(CStdOutStream *outStream) + { + CCallbackConsoleBase::Init(outStream); + } + */ + // ~CUpdateCallbackConsole() { if (NeedPercents()) _percent.ClosePrint(); } +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Console/UserInputUtils.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Console/UserInputUtils.cpp new file mode 100644 index 00000000..2adf9dfe --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Console/UserInputUtils.cpp @@ -0,0 +1,118 @@ +// UserInputUtils.cpp + +#include "StdAfx.h" + +#include "../../../Common/StdInStream.h" +#include "../../../Common/StringConvert.h" + +#include "UserInputUtils.h" + +static const char kYes = 'y'; +static const char kNo = 'n'; +static const char kYesAll = 'a'; +static const char kNoAll = 's'; +static const char kAutoRenameAll = 'u'; +static const char kQuit = 'q'; + +static const char * const kFirstQuestionMessage = "? "; +static const char * const kHelpQuestionMessage = + "(Y)es / (N)o / (A)lways / (S)kip all / A(u)to rename all / (Q)uit? "; + +// return true if pressed Quite; + +NUserAnswerMode::EEnum ScanUserYesNoAllQuit(CStdOutStream *outStream) +{ + if (outStream) + *outStream << kFirstQuestionMessage; + for (;;) + { + if (outStream) + { + *outStream << kHelpQuestionMessage; + outStream->Flush(); + } + AString scannedString; + if (!g_StdIn.ScanAStringUntilNewLine(scannedString)) + return NUserAnswerMode::kError; + if (g_StdIn.Error()) + return NUserAnswerMode::kError; + scannedString.Trim(); + if (scannedString.IsEmpty() && g_StdIn.Eof()) + return NUserAnswerMode::kEof; + + if (scannedString.Len() == 1) + switch (::MyCharLower_Ascii(scannedString[0])) + { + case kYes: return NUserAnswerMode::kYes; + case kNo: return NUserAnswerMode::kNo; + case kYesAll: return NUserAnswerMode::kYesAll; + case kNoAll: return NUserAnswerMode::kNoAll; + case kAutoRenameAll: return NUserAnswerMode::kAutoRenameAll; + case kQuit: return NUserAnswerMode::kQuit; + default: break; + } + } +} + +#ifdef _WIN32 +#ifndef UNDER_CE +#define MY_DISABLE_ECHO +#endif +#endif + +static bool GetPassword(CStdOutStream *outStream, UString &psw) +{ + if (outStream) + { + *outStream << "\nEnter password" + #ifdef MY_DISABLE_ECHO + " (will not be echoed)" + #endif + ":"; + outStream->Flush(); + } + + #ifdef MY_DISABLE_ECHO + + const HANDLE console = GetStdHandle(STD_INPUT_HANDLE); + + /* + GetStdHandle() returns + INVALID_HANDLE_VALUE: If the function fails. + NULL : If an application does not have associated standard handles, + such as a service running on an interactive desktop, + and has not redirected them. */ + bool wasChanged = false; + DWORD mode = 0; + if (console != INVALID_HANDLE_VALUE && console != NULL) + if (GetConsoleMode(console, &mode)) + wasChanged = (SetConsoleMode(console, mode & ~(DWORD)ENABLE_ECHO_INPUT) != 0); + const bool res = g_StdIn.ScanUStringUntilNewLine(psw); + if (wasChanged) + SetConsoleMode(console, mode); + + #else + + const bool res = g_StdIn.ScanUStringUntilNewLine(psw); + + #endif + + if (outStream) + { + *outStream << endl; + outStream->Flush(); + } + + return res; +} + +HRESULT GetPassword_HRESULT(CStdOutStream *outStream, UString &psw) +{ + if (!GetPassword(outStream, psw)) + return E_INVALIDARG; + if (g_StdIn.Error()) + return E_FAIL; + if (g_StdIn.Eof() && psw.IsEmpty()) + return E_ABORT; + return S_OK; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Console/UserInputUtils.h b/src/plugins/7zip/7za/cpp/7zip/UI/Console/UserInputUtils.h new file mode 100644 index 00000000..695a3e66 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Console/UserInputUtils.h @@ -0,0 +1,27 @@ +// UserInputUtils.h + +#ifndef ZIP7_INC_USER_INPUT_UTILS_H +#define ZIP7_INC_USER_INPUT_UTILS_H + +#include "../../../Common/StdOutStream.h" + +namespace NUserAnswerMode { + +enum EEnum +{ + kYes, + kNo, + kYesAll, + kNoAll, + kAutoRenameAll, + kQuit, + kEof, + kError +}; +} + +NUserAnswerMode::EEnum ScanUserYesNoAllQuit(CStdOutStream *outStream); +// bool GetPassword(CStdOutStream *outStream, UString &psw); +HRESULT GetPassword_HRESULT(CStdOutStream *outStream, UString &psw); + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Console/makefile b/src/plugins/7zip/7za/cpp/7zip/UI/Console/makefile new file mode 100644 index 00000000..d449b38a --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Console/makefile @@ -0,0 +1,68 @@ +PROG = 7z.exe +CFLAGS = $(CFLAGS) \ + -DZ7_EXTERNAL_CODECS \ + +COMMON_OBJS = \ + $O\CommandLineParser.obj \ + $O\CRC.obj \ + $O\DynLimBuf.obj \ + $O\IntToString.obj \ + $O\ListFileUtils.obj \ + $O\NewHandler.obj \ + $O\StdInStream.obj \ + $O\StdOutStream.obj \ + $O\MyString.obj \ + $O\StringConvert.obj \ + $O\StringToInt.obj \ + $O\UTFConvert.obj \ + $O\MyVector.obj \ + $O\Wildcard.obj \ + +WIN_OBJS = \ + $O\DLL.obj \ + $O\ErrorMsg.obj \ + $O\FileDir.obj \ + $O\FileFind.obj \ + $O\FileIO.obj \ + $O\FileLink.obj \ + $O\FileName.obj \ + $O\FileSystem.obj \ + $O\MemoryLock.obj \ + $O\PropVariant.obj \ + $O\PropVariantConv.obj \ + $O\Registry.obj \ + $O\System.obj \ + $O\SystemInfo.obj \ + $O\TimeUtils.obj \ + +7ZIP_COMMON_OBJS = \ + $O\CreateCoder.obj \ + $O\FilePathAutoRename.obj \ + $O\FileStreams.obj \ + $O\FilterCoder.obj \ + $O\LimitedStreams.obj \ + $O\MethodProps.obj \ + $O\MultiOutStream.obj \ + $O\ProgressUtils.obj \ + $O\PropId.obj \ + $O\StreamObjects.obj \ + $O\StreamUtils.obj \ + $O\UniqBlocks.obj \ + +AR_COMMON_OBJS = \ + $O\ItemNameUtils.obj \ + $O\OutStreamWithCRC.obj \ + +COMPRESS_OBJS = \ + $O\CopyCoder.obj \ + +C_OBJS = $(C_OBJS) \ + $O\Alloc.obj \ + $O\CpuArch.obj \ + $O\Threads.obj \ + +!include "../../Crc.mak" +!include "../../Sort.mak" +!include "Console.mak" + +!include "../../7zip.mak" diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Console/makefile.gcc b/src/plugins/7zip/7za/cpp/7zip/UI/Console/makefile.gcc new file mode 100644 index 00000000..952641e9 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Console/makefile.gcc @@ -0,0 +1,184 @@ +PROG = 7z +IS_NOT_STANDALONE = 1 + +# IS_X64 = 1 +# USE_ASM = 1 +# ST_MODE = 1 + + +LOCAL_FLAGS_ST = +MT_OBJS = + +ifdef SystemDrive +IS_MINGW = 1 +else +ifdef SYSTEMDRIVE +# ifdef OS +IS_MINGW = 1 +endif +endif + +ifdef ST_MODE + +LOCAL_FLAGS_ST = -DZ7_ST + +ifdef IS_MINGW +MT_OBJS = \ + $O/Threads.o \ + +endif + +else + +MT_OBJS = \ + $O/Synchronization.o \ + $O/Threads.o \ + +endif + + + +LOCAL_FLAGS_SYS= + +ifdef IS_MINGW + +LOCAL_FLAGS_SYS = \ + -DZ7_DEVICE_FILE \ + +SYS_OBJS = \ + $O/FileSystem.o \ + $O/Registry.o \ + $O/MemoryLock.o \ + $O/DllSecur.o \ + $O/resource.o \ + +else + +SYS_OBJS = \ + $O/MyWindows.o \ + +endif + + + +LOCAL_FLAGS = \ + $(LOCAL_FLAGS_SYS) \ + $(LOCAL_FLAGS_ST) \ + -DZ7_EXTERNAL_CODECS \ + + + +CONSOLE_OBJS = \ + $O/BenchCon.o \ + $O/ConsoleClose.o \ + $O/ExtractCallbackConsole.o \ + $O/HashCon.o \ + $O/List.o \ + $O/Main.o \ + $O/MainAr.o \ + $O/OpenCallbackConsole.o \ + $O/PercentPrinter.o \ + $O/UpdateCallbackConsole.o \ + $O/UserInputUtils.o \ + +UI_COMMON_OBJS = \ + $O/ArchiveCommandLine.o \ + $O/ArchiveExtractCallback.o \ + $O/ArchiveOpenCallback.o \ + $O/Bench.o \ + $O/DefaultName.o \ + $O/EnumDirItems.o \ + $O/Extract.o \ + $O/ExtractingFilePath.o \ + $O/HashCalc.o \ + $O/LoadCodecs.o \ + $O/OpenArchive.o \ + $O/PropIDUtils.o \ + $O/SetProperties.o \ + $O/SortUtils.o \ + $O/TempFiles.o \ + $O/Update.o \ + $O/UpdateAction.o \ + $O/UpdateCallback.o \ + $O/UpdatePair.o \ + $O/UpdateProduce.o \ + +COMMON_OBJS = \ + $O/CommandLineParser.o \ + $O/CRC.o \ + $O/CrcReg.o \ + $O/DynLimBuf.o \ + $O/IntToString.o \ + $O/ListFileUtils.o \ + $O/NewHandler.o \ + $O/StdInStream.o \ + $O/StdOutStream.o \ + $O/MyString.o \ + $O/StringConvert.o \ + $O/StringToInt.o \ + $O/UTFConvert.o \ + $O/MyVector.o \ + $O/Wildcard.o \ + +WIN_OBJS = \ + $O/DLL.o \ + $O/ErrorMsg.o \ + $O/FileDir.o \ + $O/FileFind.o \ + $O/FileIO.o \ + $O/FileLink.o \ + $O/FileName.o \ + $O/PropVariant.o \ + $O/PropVariantConv.o \ + $O/System.o \ + $O/SystemInfo.o \ + $O/TimeUtils.o \ + +7ZIP_COMMON_OBJS = \ + $O/CreateCoder.o \ + $O/CWrappers.o \ + $O/FilePathAutoRename.o \ + $O/FileStreams.o \ + $O/InBuffer.o \ + $O/InOutTempBuffer.o \ + $O/FilterCoder.o \ + $O/LimitedStreams.o \ + $O/MethodId.o \ + $O/MethodProps.o \ + $O/MultiOutStream.o \ + $O/OffsetStream.o \ + $O/OutBuffer.o \ + $O/ProgressUtils.o \ + $O/PropId.o \ + $O/StreamObjects.o \ + $O/StreamUtils.o \ + $O/UniqBlocks.o \ + +COMPRESS_OBJS = \ + $O/CopyCoder.o \ + +AR_COMMON_OBJS = \ + $O/ItemNameUtils.o \ + +C_OBJS = \ + $O/Alloc.o \ + $O/CpuArch.o \ + $O/Sort.o \ + $O/7zCrc.o \ + $O/7zCrcOpt.o \ + + +OBJS = \ + $(C_OBJS) \ + $(MT_OBJS) \ + $(COMMON_OBJS) \ + $(WIN_OBJS) \ + $(SYS_OBJS) \ + $(COMPRESS_OBJS) \ + $(AR_COMMON_OBJS) \ + $(7ZIP_COMMON_OBJS) \ + $(UI_COMMON_OBJS) \ + $(CONSOLE_OBJS) \ + + +include ../../7zip_gcc.mak diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Console/resource.rc b/src/plugins/7zip/7za/cpp/7zip/UI/Console/resource.rc new file mode 100644 index 00000000..414427fb --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Console/resource.rc @@ -0,0 +1,7 @@ +#include "../../MyVersionInfo.rc" + +MY_VERSION_INFO_APP("7-Zip Console" , "7z") + +#ifndef UNDER_CE +1 24 MOVEABLE PURE "Console.manifest" +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/7-zip.dll.manifest b/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/7-zip.dll.manifest new file mode 100644 index 00000000..cba1c5df --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/7-zip.dll.manifest @@ -0,0 +1 @@ +7-Zip Extension. diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/ContextMenu.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/ContextMenu.cpp new file mode 100644 index 00000000..0630d78e --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/ContextMenu.cpp @@ -0,0 +1,1837 @@ +// ContextMenu.cpp + +#include "StdAfx.h" + +#include "../../../Common/ComTry.h" +#include "../../../Common/IntToString.h" +#include "../../../Common/StringConvert.h" + +#include "../../../Windows/COM.h" +#include "../../../Windows/DLL.h" +#include "../../../Windows/FileDir.h" +#include "../../../Windows/FileName.h" +#include "../../../Windows/Menu.h" +#include "../../../Windows/ProcessUtils.h" + +// for IS_INTRESOURCE(): +#include "../../../Windows/Window.h" + +#include "../../PropID.h" + +#include "../Common/ArchiveName.h" +#include "../Common/CompressCall.h" +#include "../Common/ExtractingFilePath.h" +#include "../Common/ZipRegistry.h" + +#include "../FileManager/FormatUtils.h" +#include "../FileManager/LangUtils.h" +#include "../FileManager/PropertyName.h" + +#include "ContextMenu.h" +#include "ContextMenuFlags.h" +#include "MyMessages.h" + +#include "resource.h" + + +// #define SHOW_DEBUG_CTX_MENU + +#ifdef SHOW_DEBUG_CTX_MENU +#include +#endif + +using namespace NWindows; +using namespace NFile; +using namespace NDir; + +#ifndef UNDER_CE +#define EMAIL_SUPPORT 1 +#endif + +extern LONG g_DllRefCount; + +#ifdef _WIN32 +extern HINSTANCE g_hInstance; +#endif + +#ifdef UNDER_CE + #define MY_IS_INTRESOURCE(_r) ((((ULONG_PTR)(_r)) >> 16) == 0) +#else + #define MY_IS_INTRESOURCE(_r) IS_INTRESOURCE(_r) +#endif + + +#ifdef SHOW_DEBUG_CTX_MENU + +static void PrintStringA(const char *name, LPCSTR ptr) +{ + AString m; + m += name; + m += ": "; + char s[32]; + sprintf(s, "%p", (const void *)ptr); + m += s; + if (!MY_IS_INTRESOURCE(ptr)) + { + m += ": \""; + m += ptr; + m += "\""; + } + OutputDebugStringA(m); +} + +#if !defined(UNDER_CE) +static void PrintStringW(const char *name, LPCWSTR ptr) +{ + UString m; + m += name; + m += ": "; + char s[32]; + sprintf(s, "%p", (const void *)ptr); + m += s; + if (!MY_IS_INTRESOURCE(ptr)) + { + m += ": \""; + m += ptr; + m += "\""; + } + OutputDebugStringW(m); +} +#endif + +static void Print_Ptr(const void *p, const char *s) +{ + char temp[32]; + sprintf(temp, "%p", (const void *)p); + AString m; + m += temp; + m.Add_Space(); + m += s; + OutputDebugStringA(m); +} + +static void Print_Number(UInt32 number, const char *s) +{ + AString m; + m.Add_UInt32(number); + m.Add_Space(); + m += s; + OutputDebugStringA(m); +} + +#define ODS(sz) { Print_Ptr(this, sz); } +#define ODS_U(s) { OutputDebugStringW(s); } +#define ODS_(op) { op; } +#define ODS_SPRF_s(x) { char s[256]; x; OutputDebugStringA(s); } + +#else + +#define ODS(sz) +#define ODS_U(s) +#define ODS_(op) +#define ODS_SPRF_s(x) + +#endif + + +/* +DOCs: In Windows 7 and later, the number of items passed to + a verb is limited to 16 when a shortcut menu is queried. + The verb is then re-created and re-initialized with the full + selection when that verb is invoked. +win10 tests: + if (the number of selected file/dir objects > 16) + { + Explorer does the following actions: + - it creates ctx_menu_1 IContextMenu object + - it calls ctx_menu_1->Initialize() with list of only up to 16 items + - it calls ctx_menu_1->QueryContextMenu(menu_1) + - if (some menu command is pressed) + { + - it gets shown string from selected menu item : shown_menu_1_string + - it creates another ctx_menu_2 IContextMenu object + - it calls ctx_menu_2->Initialize() with list of all items + - it calls ctx_menu_2->QueryContextMenu(menu_2) + - if there is menu item with shown_menu_1_string string in menu_2, + Explorer calls ctx_menu_2->InvokeCommand() for that item. + Explorer probably doesn't use VERB from first object ctx_menu_1. + So we must provide same shown menu strings for both objects: + ctx_menu_1 and ctx_menu_2. + } + } +*/ + + +CZipContextMenu::CZipContextMenu(): + _isMenuForFM(true), + _fileNames_WereReduced(true), + _dropMode(false), + _bitmap(NULL), + _writeZone((UInt32)(Int32)-1), + IsSeparator(false), + IsRoot(true), + CurrentSubCommand(0) +{ + ODS("== CZipContextMenu()"); + InterlockedIncrement(&g_DllRefCount); +} + +CZipContextMenu::~CZipContextMenu() +{ + ODS("== ~CZipContextMenu"); + if (_bitmap) + DeleteObject(_bitmap); + InterlockedDecrement(&g_DllRefCount); +} + +// IShellExtInit + +/* +IShellExtInit::Initialize() + pidlFolder: + - for property sheet extension: + NULL + - for shortcut menu extensions: + pidl of folder that contains the item whose shortcut menu is being displayed: + - for nondefault drag-and-drop menu extensions: + pidl of target folder: for nondefault drag-and-drop menu extensions + pidlFolder == NULL in (win10): for context menu +*/ + +Z7_COMWF_B CZipContextMenu::Initialize(LPCITEMIDLIST pidlFolder, LPDATAOBJECT dataObject, HKEY /* hkeyProgID */) +{ + COM_TRY_BEGIN + ODS("==== CZipContextMenu::Initialize START") + _isMenuForFM = false; + _fileNames_WereReduced = true; + _dropMode = false; + _attribs.Clear(); + _fileNames.Clear(); + _dropPath.Empty(); + + if (pidlFolder) + { + ODS("==== CZipContextMenu::Initialize (pidlFolder != 0)") + #ifndef UNDER_CE + if (NShell::GetPathFromIDList(pidlFolder, _dropPath)) + { + ODS("==== CZipContextMenu::Initialize path from (pidl):") + ODS_U(_dropPath); + /* win10 : path with "\\\\?\\\" prefix is returned by GetPathFromIDList, if path is long + we can remove super prefix here. But probably prefix + is not problem for following 7-zip code. + so we don't remove super prefix */ + NFile::NName::If_IsSuperPath_RemoveSuperPrefix(_dropPath); + NName::NormalizeDirPathPrefix(_dropPath); + _dropMode = !_dropPath.IsEmpty(); + } + else + #endif + _dropPath.Empty(); + } + + if (!dataObject) + return E_INVALIDARG; + + #ifndef UNDER_CE + + RINOK(NShell::DataObject_GetData_HDROP_or_IDLIST_Names(dataObject, _fileNames)) + // for (unsigned y = 0; y < 10000; y++) + if (NShell::DataObject_GetData_FILE_ATTRS(dataObject, _attribs) != S_OK) + _attribs.Clear(); + + #endif + + ODS_SPRF_s(sprintf(s, "==== CZipContextMenu::Initialize END _files=%d", + _fileNames.Size())) + + return S_OK; + COM_TRY_END +} + + +///////////////////////////// +// IContextMenu + +static LPCSTR const kMainVerb = "SevenZip"; +static LPCSTR const kOpenCascadedVerb = "SevenZip.OpenWithType."; +static LPCSTR const kCheckSumCascadedVerb = "SevenZip.Checksum"; + + +struct CContextMenuCommand +{ + UInt32 flag; + CZipContextMenu::enum_CommandInternalID CommandInternalID; + LPCSTR Verb; + UINT ResourceID; +}; + +#define CMD_REC(cns, verb, ids) { NContextMenuFlags::cns, CZipContextMenu::cns, verb, ids } + +static const CContextMenuCommand g_Commands[] = +{ + CMD_REC( kOpen, "Open", IDS_CONTEXT_OPEN), + CMD_REC( kExtract, "Extract", IDS_CONTEXT_EXTRACT), + CMD_REC( kExtractHere, "ExtractHere", IDS_CONTEXT_EXTRACT_HERE), + CMD_REC( kExtractTo, "ExtractTo", IDS_CONTEXT_EXTRACT_TO), + CMD_REC( kTest, "Test", IDS_CONTEXT_TEST), + CMD_REC( kCompress, "Compress", IDS_CONTEXT_COMPRESS), + CMD_REC( kCompressEmail, "CompressEmail", IDS_CONTEXT_COMPRESS_EMAIL), + CMD_REC( kCompressTo7z, "CompressTo7z", IDS_CONTEXT_COMPRESS_TO), + CMD_REC( kCompressTo7zEmail, "CompressTo7zEmail", IDS_CONTEXT_COMPRESS_TO_EMAIL), + CMD_REC( kCompressToZip, "CompressToZip", IDS_CONTEXT_COMPRESS_TO), + CMD_REC( kCompressToZipEmail, "CompressToZipEmail", IDS_CONTEXT_COMPRESS_TO_EMAIL) +}; + + +struct CHashCommand +{ + CZipContextMenu::enum_CommandInternalID CommandInternalID; + LPCSTR UserName; + LPCSTR MethodName; +}; + +static const CHashCommand g_HashCommands[] = +{ + { CZipContextMenu::kHash_CRC32, "CRC-32", "CRC32" }, + { CZipContextMenu::kHash_CRC64, "CRC-64", "CRC64" }, + { CZipContextMenu::kHash_XXH64, "XXH64", "XXH64" }, + { CZipContextMenu::kHash_MD5, "MD5", "MD5" }, + { CZipContextMenu::kHash_SHA1, "SHA-1", "SHA1" }, + { CZipContextMenu::kHash_SHA256, "SHA-256", "SHA256" }, + { CZipContextMenu::kHash_SHA384, "SHA-384", "SHA384" }, + { CZipContextMenu::kHash_SHA512, "SHA-512", "SHA512" }, + { CZipContextMenu::kHash_SHA3_256, "SHA3-256", "SHA3-256" }, + { CZipContextMenu::kHash_BLAKE2SP, "BLAKE2sp", "BLAKE2sp" }, + { CZipContextMenu::kHash_All, "*", "*" }, + { CZipContextMenu::kHash_Generate_SHA256, "SHA-256 -> file.sha256", "SHA256" }, + { CZipContextMenu::kHash_TestArc, "Checksum : Test", "Hash" } +}; + + +static int FindCommand(CZipContextMenu::enum_CommandInternalID &id) +{ + for (unsigned i = 0; i < Z7_ARRAY_SIZE(g_Commands); i++) + if (g_Commands[i].CommandInternalID == id) + return (int)i; + return -1; +} + + +void CZipContextMenu::FillCommand(enum_CommandInternalID id, UString &mainString, CCommandMapItem &cmi) const +{ + mainString.Empty(); + const int i = FindCommand(id); + if (i < 0) + throw 201908; + const CContextMenuCommand &command = g_Commands[(unsigned)i]; + cmi.CommandInternalID = command.CommandInternalID; + cmi.Verb = kMainVerb; + cmi.Verb += command.Verb; + // cmi.HelpString = cmi.Verb; + LangString(command.ResourceID, mainString); + cmi.UserString = mainString; +} + + +static UString LangStringAlt(UInt32 id, const char *altString) +{ + UString s = LangString(id); + if (s.IsEmpty()) + s = altString; + return s; +} + + +void CZipContextMenu::AddCommand(enum_CommandInternalID id, UString &mainString, CCommandMapItem &cmi) +{ + FillCommand(id, mainString, cmi); + _commandMap.Add(cmi); +} + + + +/* +note: old msdn article: +Duplicate Menu Items In the File Menu For a Shell Context Menu Extension (214477) +---------- + On systems with Shell32.dll version 4.71 or higher, a context menu extension + for a file folder that inserts one or more pop-up menus results in duplicates + of these menu items. + This occurs when the file menu is activated more than once for the selected object. + +CAUSE + In a context menu extension, if pop-up menus are inserted using InsertMenu + or AppendMenu, then the ID for the pop-up menu item cannot be specified. + Instead, this field should take in the HMENU of the pop-up menu. + Because the ID is not specified for the pop-up menu item, the Shell does + not keep track of the menu item if the file menu is pulled down multiple times. + As a result, the pop-up menu items are added multiple times in the context menu. + + This problem occurs only when the file menu is pulled down, and does not happen + when the context menu is invoked by using the right button or the context menu key. +RESOLUTION + To work around this problem, use InsertMenuItem and specify the ID of the + pop-up menu item in the wID member of the MENUITEMINFO structure. +*/ + +static void MyInsertMenu(CMenu &menu, unsigned pos, UINT id, const UString &s, HBITMAP bitmap) +{ + if (!menu) + return; + CMenuItem mi; + mi.fType = MFT_STRING; + mi.fMask = MIIM_TYPE | MIIM_ID; + if (bitmap) + mi.fMask |= MIIM_CHECKMARKS; + mi.wID = id; + mi.StringValue = s; + mi.hbmpUnchecked = bitmap; + // mi.hbmpChecked = bitmap; // do we need hbmpChecked ??? + if (!menu.InsertItem(pos, true, mi)) + throw 20190816; + + // SetMenuItemBitmaps also works + // ::SetMenuItemBitmaps(menu, pos, MF_BYPOSITION, bitmap, NULL); +} + + +static void MyAddSubMenu( + CObjectVector &_commandMap, + const char *verb, + CMenu &menu, unsigned pos, UINT id, const UString &s, HMENU hSubMenu, HBITMAP bitmap) +{ + CZipContextMenu::CCommandMapItem cmi; + cmi.CommandInternalID = CZipContextMenu::kCommandNULL; + cmi.Verb = verb; + cmi.IsPopup = true; + // cmi.HelpString = verb; + cmi.UserString = s; + _commandMap.Add(cmi); + + if (!menu) + return; + + CMenuItem mi; + mi.fType = MFT_STRING; + mi.fMask = MIIM_SUBMENU | MIIM_TYPE | MIIM_ID; + if (bitmap) + mi.fMask |= MIIM_CHECKMARKS; + mi.wID = id; + mi.hSubMenu = hSubMenu; + mi.hbmpUnchecked = bitmap; + + mi.StringValue = s; + if (!menu.InsertItem(pos, true, mi)) + throw 20190817; +} + + +static const char * const kArcExts[] = +{ + "7z" + , "bz2" + , "gz" + , "rar" + , "zip" +}; + +static bool IsItArcExt(const UString &ext) +{ + for (unsigned i = 0; i < Z7_ARRAY_SIZE(kArcExts); i++) + if (ext.IsEqualTo_Ascii_NoCase(kArcExts[i])) + return true; + return false; +} + +UString GetSubFolderNameForExtract(const UString &arcName); +UString GetSubFolderNameForExtract(const UString &arcName) +{ + int dotPos = arcName.ReverseFind_Dot(); + if (dotPos < 0) + return Get_Correct_FsFile_Name(arcName) + L'~'; + + const UString ext = arcName.Ptr(dotPos + 1); + UString res = arcName.Left(dotPos); + res.TrimRight(); + dotPos = res.ReverseFind_Dot(); + if (dotPos > 0) + { + const UString ext2 = res.Ptr(dotPos + 1); + if ((ext.IsEqualTo_Ascii_NoCase("001") && IsItArcExt(ext2)) + || (ext.IsEqualTo_Ascii_NoCase("rar") && + ( ext2.IsEqualTo_Ascii_NoCase("part001") + || ext2.IsEqualTo_Ascii_NoCase("part01") + || ext2.IsEqualTo_Ascii_NoCase("part1")))) + res.DeleteFrom(dotPos); + res.TrimRight(); + } + return Get_Correct_FsFile_Name(res); +} + +static void ReduceString(UString &s) +{ + const unsigned kMaxSize = 64; + if (s.Len() <= kMaxSize) + return; + s.Delete(kMaxSize / 2, s.Len() - kMaxSize); + s.Insert(kMaxSize / 2, L" ... "); +} + +static UString GetQuotedReducedString(const UString &s) +{ + UString s2 = s; + ReduceString(s2); + s2.Replace(L"&", L"&&"); + return GetQuotedString(s2); +} + +static void MyFormatNew_ReducedName(UString &s, const UString &name) +{ + s = MyFormatNew(s, GetQuotedReducedString(name)); +} + +static const char * const kExtractExcludeExtensions = + " 3gp" + " aac ans ape asc asm asp aspx avi awk" + " bas bat bmp" + " c cs cls clw cmd cpp csproj css ctl cxx" + " def dep dlg dsp dsw" + " eps" + " f f77 f90 f95 fla flac frm" + " gif" + " h hpp hta htm html hxx" + " ico idl inc ini inl" + " java jpeg jpg js" + " la lnk log" + " mak manifest wmv mov mp3 mp4 mpe mpeg mpg m4a" + " ofr ogg" + " pac pas pdf php php3 php4 php5 phptml pl pm png ps py pyo" + " ra rb rc reg rka rm rtf" + " sed sh shn shtml sln sql srt swa" + " tcl tex tiff tta txt" + " vb vcproj vbs" + " mkv wav webm wma wv" + " xml xsd xsl xslt" + " "; + +/* +static const char * const kNoOpenAsExtensions = + " 7z arj bz2 cab chm cpio flv gz lha lzh lzma rar swm tar tbz2 tgz wim xar xz z zip "; +*/ + +static const char * const kOpenTypes[] = +{ + "" + , "*" + , "#" + , "#:e" + // , "#:a" + , "7z" + , "zip" + , "cab" + , "rar" +}; + + +bool FindExt(const char *p, const UString &name, CStringFinder &finder); +bool FindExt(const char *p, const UString &name, CStringFinder &finder) +{ + const int dotPos = name.ReverseFind_Dot(); + int len = (int)name.Len() - (dotPos + 1); + if (len == 0 || len > 32 || dotPos < 0) + return false; + return finder.FindWord_In_LowCaseAsciiList_NoCase(p, name.Ptr(dotPos + 1)); +} + +/* returns false, if extraction of that file extension is not expected */ +static bool DoNeedExtract(const UString &name, CStringFinder &finder) +{ + // for (int y = 0; y < 1000; y++) FindExt(kExtractExcludeExtensions, name); + return !FindExt(kExtractExcludeExtensions, name, finder); +} + +// we must use diferent Verbs for Popup subMenu. +void CZipContextMenu::AddMapItem_ForSubMenu(const char *verb) +{ + CCommandMapItem cmi; + cmi.CommandInternalID = kCommandNULL; + cmi.Verb = verb; + // cmi.HelpString = verb; + _commandMap.Add(cmi); +} + + +static HRESULT RETURN_WIN32_LastError_AS_HRESULT() +{ + DWORD lastError = ::GetLastError(); + if (lastError == 0) + return E_FAIL; + return HRESULT_FROM_WIN32(lastError); +} + + +/* + we add CCommandMapItem to _commandMap for each new Menu ID. + so then we use _commandMap[offset]. + That way we can execute commands that have menu item. + Another non-implemented way: + We can return the number off all possible commands in QueryContextMenu(). + so the caller could call InvokeCommand() via string verb even + without using menu items. +*/ + + +Z7_COMWF_B CZipContextMenu::QueryContextMenu(HMENU hMenu, UINT indexMenu, + UINT commandIDFirst, UINT commandIDLast, UINT flags) +{ + ODS("+ QueryContextMenu()") + COM_TRY_BEGIN + try { + + _commandMap.Clear(); + + ODS_SPRF_s(sprintf(s, "QueryContextMenu: index=%u first=%u last=%u flags=%x _files=%u", + indexMenu, commandIDFirst, commandIDLast, flags, _fileNames.Size())) + /* + for (UInt32 i = 0; i < _fileNames.Size(); i++) + { + ODS_U(_fileNames[i]) + } + */ + + #define MAKE_HRESULT_SUCCESS_FAC0(code) (HRESULT)(code) + + if (_fileNames.Size() == 0) + { + return MAKE_HRESULT_SUCCESS_FAC0(0); + // return E_INVALIDARG; + } + + if (commandIDFirst > commandIDLast) + return E_INVALIDARG; + + UINT currentCommandID = commandIDFirst; + + if ((flags & 0x000F) != CMF_NORMAL + && (flags & CMF_VERBSONLY) == 0 + && (flags & CMF_EXPLORE) == 0) + return MAKE_HRESULT_SUCCESS_FAC0(currentCommandID - commandIDFirst); + // return MAKE_HRESULT_SUCCESS_FAC0(currentCommandID); + // 19.01 : we changed from (currentCommandID) to (currentCommandID - commandIDFirst) + // why it was so before? + +#ifdef Z7_LANG + LoadLangOneTime(); +#endif + + CMenu popupMenu; + CMenuDestroyer menuDestroyer; + + ODS("### 40") + CContextMenuInfo ci; + ci.Load(); + ODS("### 44") + + _elimDup = ci.ElimDup; + _writeZone = ci.WriteZone; + + HBITMAP bitmap = NULL; + if (ci.MenuIcons.Val) + { + ODS("### 45") + if (!_bitmap) + _bitmap = ::LoadBitmap(g_hInstance, MAKEINTRESOURCE(IDB_MENU_LOGO)); + bitmap = _bitmap; + } + + UINT subIndex = indexMenu; + + ODS("### 50") + + if (ci.Cascaded.Val) + { + if (hMenu) + if (!popupMenu.CreatePopup()) + return RETURN_WIN32_LastError_AS_HRESULT(); + menuDestroyer.Attach(popupMenu); + + /* 9.31: we commented the following code. Probably we don't need. + Check more systems. Maybe it was for old Windows? */ + /* + AddMapItem_ForSubMenu(); + currentCommandID++; + */ + subIndex = 0; + } + else + { + popupMenu.Attach(hMenu); + CMenuItem mi; + mi.fType = MFT_SEPARATOR; + mi.fMask = MIIM_TYPE; + if (hMenu) + popupMenu.InsertItem(subIndex++, true, mi); + } + + const UInt32 contextMenuFlags = ci.Flags; + + NFind::CFileInfo fi0; + FString folderPrefix; + + if (_fileNames.Size() > 0) + { + const UString &fileName = _fileNames.Front(); + + #if defined(_WIN32) && !defined(UNDER_CE) + if (NName::IsDevicePath(us2fs(fileName))) + { + // CFileInfo::Find can be slow for device files. So we don't call it. + // we need only name here. + fi0.Name = us2fs(fileName.Ptr(NName::kDevicePathPrefixSize)); + folderPrefix = + #ifdef UNDER_CE + "\\"; + #else + "C:\\"; + #endif + } + else + #endif + { + if (!fi0.Find(us2fs(fileName))) + { + throw 20190820; + // return RETURN_WIN32_LastError_AS_HRESULT(); + } + GetOnlyDirPrefix(us2fs(fileName), folderPrefix); + } + } + + ODS("### 100") + + UString mainString; + CStringFinder finder; + UStringVector fileNames_Reduced; + const unsigned k_Explorer_NumReducedItems = 16; + const bool needReduce = !_isMenuForFM && (_fileNames.Size() >= k_Explorer_NumReducedItems); + _fileNames_WereReduced = needReduce; + // _fileNames_WereReduced = true; // for debug; + const UStringVector *fileNames = &_fileNames; + if (needReduce) + { + for (unsigned i = 0; i < k_Explorer_NumReducedItems + && i < _fileNames.Size(); i++) + fileNames_Reduced.Add(_fileNames[i]); + fileNames = &fileNames_Reduced; + } + + /* + if (_fileNames.Size() == k_Explorer_NumReducedItems) // for debug + { + for (int i = 0; i < 10; i++) + { + CCommandMapItem cmi; + AddCommand(kCompressToZipEmail, mainString, cmi); + MyInsertMenu(popupMenu, subIndex++, currentCommandID++, mainString, bitmap); + } + } + */ + + if (_fileNames.Size() == 1 && currentCommandID + 14 <= commandIDLast) + { + if (!fi0.IsDir() && DoNeedExtract(fs2us(fi0.Name), finder)) + { + // Open + const bool thereIsMainOpenItem = ((contextMenuFlags & NContextMenuFlags::kOpen) != 0); + if (thereIsMainOpenItem) + { + CCommandMapItem cmi; + AddCommand(kOpen, mainString, cmi); + MyInsertMenu(popupMenu, subIndex++, currentCommandID++, mainString, bitmap); + } + if ((contextMenuFlags & NContextMenuFlags::kOpenAs) != 0 + // && (!thereIsMainOpenItem || !FindExt(kNoOpenAsExtensions, fi0.Name)) + && hMenu // we want to reduce number of menu items below 16 + ) + { + CMenu subMenu; + if (!hMenu || subMenu.CreatePopup()) + { + MyAddSubMenu(_commandMap, kOpenCascadedVerb, popupMenu, subIndex++, currentCommandID++, LangString(IDS_CONTEXT_OPEN), subMenu, bitmap); + _commandMap.Back().CtxCommandType = CtxCommandType_OpenRoot; + + UINT subIndex2 = 0; + for (unsigned i = (thereIsMainOpenItem ? 1 : 0); i < Z7_ARRAY_SIZE(kOpenTypes); i++) + { + CCommandMapItem cmi; + if (i == 0) + FillCommand(kOpen, mainString, cmi); + else + { + mainString = kOpenTypes[i]; + cmi.CommandInternalID = kOpen; + cmi.Verb = kMainVerb; + cmi.Verb += ".Open."; + cmi.Verb += mainString; + // cmi.HelpString = cmi.Verb; + cmi.ArcType = mainString; + cmi.CtxCommandType = CtxCommandType_OpenChild; + } + _commandMap.Add(cmi); + Set_UserString_in_LastCommand(mainString); + MyInsertMenu(subMenu, subIndex2++, currentCommandID++, mainString, bitmap); + } + + subMenu.Detach(); + } + } + } + } + + ODS("### 150") + + if (_fileNames.Size() > 0 && currentCommandID + 10 <= commandIDLast) + { + ODS("### needExtract list START") + const bool needExtendedVerbs = ((flags & Z7_WIN_CMF_EXTENDEDVERBS) != 0); + // || _isMenuForFM; + bool needExtract = true; + bool areDirs = fi0.IsDir() || (unsigned)_attribs.FirstDirIndex < k_Explorer_NumReducedItems; + if (!needReduce) + areDirs = areDirs || (_attribs.FirstDirIndex != -1); + if (areDirs) + needExtract = false; + + if (!needExtendedVerbs) + if (needExtract) + { + UString name; + const unsigned numItemsCheck = fileNames->Size(); + for (unsigned i = 0; i < numItemsCheck; i++) + { + const UString &a = (*fileNames)[i]; + const int slash = a.ReverseFind_PathSepar(); + name = a.Ptr(slash + 1); + // for (int y = 0; y < 600; y++) // for debug + const bool needExtr2 = DoNeedExtract(name, finder); + if (!needExtr2) + { + needExtract = needExtr2; + break; + } + } + } + ODS("### needExtract list END") + + if (needExtract) + { + { + UString baseFolder = fs2us(folderPrefix); + if (_dropMode) + baseFolder = _dropPath; + + UString specFolder ('*'); + if (_fileNames.Size() == 1) + specFolder = GetSubFolderNameForExtract(fs2us(fi0.Name)); + specFolder.Add_PathSepar(); + + if ((contextMenuFlags & NContextMenuFlags::kExtract) != 0) + { + // Extract + CCommandMapItem cmi; + cmi.Folder = baseFolder + specFolder; + AddCommand(kExtract, mainString, cmi); + MyInsertMenu(popupMenu, subIndex++, currentCommandID++, mainString, bitmap); + } + + if ((contextMenuFlags & NContextMenuFlags::kExtractHere) != 0) + { + // Extract Here + CCommandMapItem cmi; + cmi.Folder = baseFolder; + AddCommand(kExtractHere, mainString, cmi); + MyInsertMenu(popupMenu, subIndex++, currentCommandID++, mainString, bitmap); + } + + if ((contextMenuFlags & NContextMenuFlags::kExtractTo) != 0) + { + // Extract To + CCommandMapItem cmi; + UString s; + cmi.Folder = baseFolder + specFolder; + AddCommand(kExtractTo, s, cmi); + MyFormatNew_ReducedName(s, specFolder); + Set_UserString_in_LastCommand(s); + MyInsertMenu(popupMenu, subIndex++, currentCommandID++, s, bitmap); + } + } + + if ((contextMenuFlags & NContextMenuFlags::kTest) != 0) + { + // Test + CCommandMapItem cmi; + AddCommand(kTest, mainString, cmi); + // if (_fileNames.Size() == 16) mainString += "_[16]"; // for debug + MyInsertMenu(popupMenu, subIndex++, currentCommandID++, mainString, bitmap); + } + } + + ODS("### CreateArchiveName START") + UString arcName_base; + const UString arcName = CreateArchiveName( + *fileNames, + false, // isHash + fileNames->Size() == 1 ? &fi0 : NULL, + arcName_base); + ODS("### CreateArchiveName END") + UString arcName_Show = arcName; + if (needReduce) + { + /* we need same arcName_Show for two calls from Explorer: + 1) reduced call (only first 16 items) + 2) full call with all items (can be >= 16 items) + (fileNames) array was reduced to 16 items. + So we will have same (arcName) in both reduced and full calls. + If caller (Explorer) uses (reduce_to_first_16_items) scheme, + we can use (arcName) here instead of (arcName_base). + (arcName_base) has no number in name. + */ + arcName_Show = arcName_base; // we can comment that line + /* we use "_" in archive name as sign to user + that shows that final archive name can be changed. */ + arcName_Show += "_"; + } + + UString arcName_7z = arcName; + arcName_7z += ".7z"; + UString arcName_7z_Show = arcName_Show; + arcName_7z_Show += ".7z"; + UString arcName_zip = arcName; + arcName_zip += ".zip"; + UString arcName_zip_Show = arcName_Show; + arcName_zip_Show += ".zip"; + + + // Compress + if ((contextMenuFlags & NContextMenuFlags::kCompress) != 0) + { + CCommandMapItem cmi; + if (_dropMode) + cmi.Folder = _dropPath; + else + cmi.Folder = fs2us(folderPrefix); + cmi.ArcName = arcName; + AddCommand(kCompress, mainString, cmi); + MyInsertMenu(popupMenu, subIndex++, currentCommandID++, mainString, bitmap); + } + + #ifdef EMAIL_SUPPORT + // CompressEmail + if ((contextMenuFlags & NContextMenuFlags::kCompressEmail) != 0 && !_dropMode) + { + CCommandMapItem cmi; + cmi.ArcName = arcName; + AddCommand(kCompressEmail, mainString, cmi); + MyInsertMenu(popupMenu, subIndex++, currentCommandID++, mainString, bitmap); + } + #endif + + // CompressTo7z + if (contextMenuFlags & NContextMenuFlags::kCompressTo7z && + !arcName_7z.IsEqualTo_NoCase(fs2us(fi0.Name))) + { + CCommandMapItem cmi; + UString s; + if (_dropMode) + cmi.Folder = _dropPath; + else + cmi.Folder = fs2us(folderPrefix); + cmi.ArcName = arcName_7z; + cmi.ArcType = "7z"; + AddCommand(kCompressTo7z, s, cmi); + MyFormatNew_ReducedName(s, arcName_7z_Show); + Set_UserString_in_LastCommand(s); + MyInsertMenu(popupMenu, subIndex++, currentCommandID++, s, bitmap); + } + + #ifdef EMAIL_SUPPORT + // CompressTo7zEmail + if ((contextMenuFlags & NContextMenuFlags::kCompressTo7zEmail) != 0 && !_dropMode) + { + CCommandMapItem cmi; + UString s; + cmi.ArcName = arcName_7z; + cmi.ArcType = "7z"; + AddCommand(kCompressTo7zEmail, s, cmi); + MyFormatNew_ReducedName(s, arcName_7z_Show); + Set_UserString_in_LastCommand(s); + MyInsertMenu(popupMenu, subIndex++, currentCommandID++, s, bitmap); + } + #endif + + // CompressToZip + if (contextMenuFlags & NContextMenuFlags::kCompressToZip && + !arcName_zip.IsEqualTo_NoCase(fs2us(fi0.Name))) + { + CCommandMapItem cmi; + UString s; + if (_dropMode) + cmi.Folder = _dropPath; + else + cmi.Folder = fs2us(folderPrefix); + cmi.ArcName = arcName_zip; + cmi.ArcType = "zip"; + AddCommand(kCompressToZip, s, cmi); + MyFormatNew_ReducedName(s, arcName_zip_Show); + Set_UserString_in_LastCommand(s); + MyInsertMenu(popupMenu, subIndex++, currentCommandID++, s, bitmap); + } + + #ifdef EMAIL_SUPPORT + // CompressToZipEmail + if ((contextMenuFlags & NContextMenuFlags::kCompressToZipEmail) != 0 && !_dropMode) + { + CCommandMapItem cmi; + UString s; + cmi.ArcName = arcName_zip; + cmi.ArcType = "zip"; + AddCommand(kCompressToZipEmail, s, cmi); + MyFormatNew_ReducedName(s, arcName_zip_Show); + Set_UserString_in_LastCommand(s); + MyInsertMenu(popupMenu, subIndex++, currentCommandID++, s, bitmap); + } + #endif + } + + ODS("### 300") + + // don't use InsertMenu: See MSDN: + // PRB: Duplicate Menu Items In the File Menu For a Shell Context Menu Extension + // ID: Q214477 + + if (ci.Cascaded.Val) + { + CMenu menu; + menu.Attach(hMenu); + menuDestroyer.Disable(); + MyAddSubMenu(_commandMap, kMainVerb, menu, indexMenu++, currentCommandID++, (UString)"7-Zip", + popupMenu, // popupMenu.Detach(), + bitmap); + } + else + { + // popupMenu.Detach(); + indexMenu = subIndex; + } + + ODS("### 350") + + const bool needCrc = ((contextMenuFlags & + (NContextMenuFlags::kCRC | + NContextMenuFlags::kCRC_Cascaded)) != 0); + + if ( + // !_isMenuForFM && // 21.04: we don't hide CRC SHA menu in 7-Zip FM + needCrc + && currentCommandID + 1 < commandIDLast) + { + CMenu subMenu; + // CMenuDestroyer menuDestroyer_CRC; + + UINT subIndex_CRC = 0; + + if (!hMenu || subMenu.CreatePopup()) + { + // menuDestroyer_CRC.Attach(subMenu); + const bool insertHashMenuTo7zipMenu = (ci.Cascaded.Val + && (contextMenuFlags & NContextMenuFlags::kCRC_Cascaded) != 0); + + CMenu menu; + { + unsigned indexInParent; + if (insertHashMenuTo7zipMenu) + { + indexInParent = subIndex; + menu.Attach(popupMenu); + } + else + { + indexInParent = indexMenu; + menu.Attach(hMenu); + // menuDestroyer_CRC.Disable(); + } + MyAddSubMenu(_commandMap, kCheckSumCascadedVerb, menu, indexInParent++, currentCommandID++, (UString)"CRC SHA", subMenu, + /* insertHashMenuTo7zipMenu ? NULL : */ bitmap); + _commandMap.Back().CtxCommandType = CtxCommandType_CrcRoot; + if (!insertHashMenuTo7zipMenu) + indexMenu = indexInParent; + } + + ODS("### HashCommands") + + for (unsigned i = 0; i < Z7_ARRAY_SIZE(g_HashCommands); i++) + { + if (currentCommandID >= commandIDLast) + break; + const CHashCommand &hc = g_HashCommands[i]; + CCommandMapItem cmi; + cmi.CommandInternalID = hc.CommandInternalID; + cmi.Verb = kCheckSumCascadedVerb; + cmi.Verb.Add_Dot(); + UString s; + s += hc.UserName; + + if (hc.CommandInternalID == kHash_Generate_SHA256) + { + cmi.Verb += "Generate"; + { + popupMenu.Attach(hMenu); + CMenuItem mi; + mi.fType = MFT_SEPARATOR; + mi.fMask = MIIM_TYPE; + subMenu.InsertItem(subIndex_CRC++, true, mi); + } + + UString name; + UString showName; + ODS("### Hash CreateArchiveName Start") + // for (int y = 0; y < 10000; y++) // for debug + // if (fileNames->Size() == 1) name = fs2us(fi0.Name); else + name = CreateArchiveName( + *fileNames, + true, // isHash + fileNames->Size() == 1 ? &fi0 : NULL, + showName); + if (needReduce) + showName += "_"; + else + showName = name; + + ODS("### Hash CreateArchiveName END") + name += ".sha256"; + showName += ".sha256"; + cmi.Folder = fs2us(folderPrefix); + cmi.ArcName = name; + s = "SHA-256 -> "; + s += showName; + } + else if (hc.CommandInternalID == kHash_TestArc) + { + cmi.Verb += "Test"; + s = LangStringAlt(IDS_CONTEXT_TEST, "Test archive"); + s += " : "; + s += GetNameOfProperty(kpidChecksum, UString("Checksum")); + } + else + cmi.Verb += "Calc"; + + cmi.Verb.Add_Dot(); + cmi.Verb += hc.MethodName; + + // cmi.HelpString = cmi.Verb; + cmi.UserString = s; + cmi.CtxCommandType = CtxCommandType_CrcChild; + _commandMap.Add(cmi); + MyInsertMenu(subMenu, subIndex_CRC++, currentCommandID++, s, bitmap); + ODS("### 380") + } + + subMenu.Detach(); + } + } + + popupMenu.Detach(); + /* + if (!ci.Cascaded.Val) + indexMenu = subIndex; + */ + const unsigned numCommands = currentCommandID - commandIDFirst; + ODS("+ QueryContextMenu() END") + ODS_SPRF_s(sprintf(s, "Commands=%u currentCommandID - commandIDFirst = %u", + _commandMap.Size(), numCommands)) + if (_commandMap.Size() != numCommands) + throw 20190818; + /* + FOR_VECTOR (k, _commandMap) + { + ODS_U(_commandMap[k].Verb); + } + */ + } + catch(...) + { + ODS_SPRF_s(sprintf(s, "catch() exception: Commands=%u", _commandMap.Size())) + if (_commandMap.Size() == 0) + throw; + } + /* we added some menu items already : num_added_menu_items, + So we MUST return (number_of_defined_ids), where (number_of_defined_ids >= num_added_menu_items) + This will prevent incorrect menu working, when same IDs can be + assigned in multiple menu items from different subhandlers. + And we must add items to _commandMap before adding to menu. + */ + return MAKE_HRESULT_SUCCESS_FAC0(_commandMap.Size()); + COM_TRY_END +} + + +int CZipContextMenu::FindVerb(const UString &verb) const +{ + FOR_VECTOR (i, _commandMap) + if (_commandMap[i].Verb == verb) + return (int)i; + return -1; +} + +static UString Get7zFmPath() +{ + return fs2us(NWindows::NDLL::GetModuleDirPrefix()) + L"7zFM.exe"; +} + + +Z7_COMWF_B CZipContextMenu::InvokeCommand(LPCMINVOKECOMMANDINFO commandInfo) +{ + COM_TRY_BEGIN + + ODS("==== CZipContextMenu::InvokeCommand()") + + #ifdef SHOW_DEBUG_CTX_MENU + + ODS_SPRF_s(sprintf(s, ": InvokeCommand: cbSize=%u flags=%x ", + (unsigned)commandInfo->cbSize, (unsigned)commandInfo->fMask)) + + PrintStringA("Verb", commandInfo->lpVerb); + PrintStringA("Parameters", commandInfo->lpParameters); + PrintStringA("Directory", commandInfo->lpDirectory); + #endif + + int commandOffset = -1; + + // xp64 / Win10 : explorer.exe sends 0 in lpVerbW + // MSDN: if (IS_INTRESOURCE(lpVerbW)), we must use LOWORD(lpVerb) as command offset + + // FIXME: old MINGW doesn't define CMINVOKECOMMANDINFOEX / CMIC_MASK_UNICODE + #if !defined(UNDER_CE) && defined(CMIC_MASK_UNICODE) + bool unicodeVerb = false; + if (commandInfo->cbSize == sizeof(CMINVOKECOMMANDINFOEX) && + (commandInfo->fMask & CMIC_MASK_UNICODE) != 0) + { + LPCMINVOKECOMMANDINFOEX commandInfoEx = (LPCMINVOKECOMMANDINFOEX)commandInfo; + if (!MY_IS_INTRESOURCE(commandInfoEx->lpVerbW)) + { + unicodeVerb = true; + commandOffset = FindVerb(commandInfoEx->lpVerbW); + } + + #ifdef SHOW_DEBUG_CTX_MENU + PrintStringW("VerbW", commandInfoEx->lpVerbW); + PrintStringW("ParametersW", commandInfoEx->lpParametersW); + PrintStringW("DirectoryW", commandInfoEx->lpDirectoryW); + PrintStringW("TitleW", commandInfoEx->lpTitleW); + PrintStringA("Title", commandInfoEx->lpTitle); + #endif + } + if (!unicodeVerb) + #endif + { + ODS("use non-UNICODE verb") + // if (HIWORD(commandInfo->lpVerb) == 0) + if (MY_IS_INTRESOURCE(commandInfo->lpVerb)) + commandOffset = LOWORD(commandInfo->lpVerb); + else + commandOffset = FindVerb(GetUnicodeString(commandInfo->lpVerb)); + } + + ODS_SPRF_s(sprintf(s, "commandOffset=%d", commandOffset)) + + if (/* commandOffset < 0 || */ (unsigned)commandOffset >= _commandMap.Size()) + return E_INVALIDARG; + const CCommandMapItem &cmi = _commandMap[(unsigned)commandOffset]; + return InvokeCommandCommon(cmi); + COM_TRY_END +} + + +HRESULT CZipContextMenu::InvokeCommandCommon(const CCommandMapItem &cmi) +{ + const enum_CommandInternalID cmdID = cmi.CommandInternalID; + + try + { + switch (cmdID) + { + case kOpen: + { + UString params; + params = GetQuotedString(_fileNames[0]); + if (!cmi.ArcType.IsEmpty()) + { + params += " -t"; + params += cmi.ArcType; + } + MyCreateProcess(Get7zFmPath(), params); + break; + } + case kExtract: + case kExtractHere: + case kExtractTo: + { + if (_attribs.FirstDirIndex != -1) + { + ShowErrorMessageRes(IDS_SELECT_FILES); + break; + } + ExtractArchives(_fileNames, cmi.Folder, + (cmdID == kExtract), // showDialog + (cmdID == kExtractTo) && _elimDup.Val, // elimDup + _writeZone + ); + break; + } + case kTest: + { + TestArchives(_fileNames); + break; + } + case kCompress: + case kCompressEmail: + case kCompressTo7z: + case kCompressTo7zEmail: + case kCompressToZip: + case kCompressToZipEmail: + { + UString arcName = cmi.ArcName; + if (_fileNames_WereReduced) + { + UString arcName_base; + arcName = CreateArchiveName( + _fileNames, + false, // isHash + NULL, // fi0 + arcName_base); + const char *postfix = NULL; + if (cmdID == kCompressTo7z || + cmdID == kCompressTo7zEmail) + postfix = ".7z"; + else if ( + cmdID == kCompressToZip || + cmdID == kCompressToZipEmail) + postfix = ".zip"; + if (postfix) + arcName += postfix; + } + + const bool email = + cmdID == kCompressEmail || + cmdID == kCompressTo7zEmail || + cmdID == kCompressToZipEmail; + const bool showDialog = + cmdID == kCompress || + cmdID == kCompressEmail; + const bool addExtension = showDialog; + CompressFiles(cmi.Folder, + arcName, cmi.ArcType, + addExtension, + _fileNames, email, showDialog, + false // waitFinish + ); + break; + } + + case kHash_CRC32: + case kHash_CRC64: + case kHash_XXH64: + case kHash_MD5: + case kHash_SHA1: + case kHash_SHA256: + case kHash_SHA384: + case kHash_SHA512: + case kHash_SHA3_256: + case kHash_BLAKE2SP: + case kHash_All: + case kHash_Generate_SHA256: + case kHash_TestArc: + { + for (unsigned i = 0; i < Z7_ARRAY_SIZE(g_HashCommands); i++) + { + const CHashCommand &hc = g_HashCommands[i]; + if (hc.CommandInternalID == cmdID) + { + if (cmdID == kHash_TestArc) + { + TestArchives(_fileNames, true); // hashMode + break; + } + UString generateName; + if (cmdID == kHash_Generate_SHA256) + { + generateName = cmi.ArcName; + if (_fileNames_WereReduced) + { + UString arcName_base; + generateName = CreateArchiveName(_fileNames, + true, // isHash + NULL, // fi0 + arcName_base); + generateName += ".sha256"; + } + } + CalcChecksum(_fileNames, (UString)hc.MethodName, + cmi.Folder, generateName); + break; + } + } + break; + } + case kCommandNULL: + break; + } + } + catch(...) + { + ShowErrorMessage(NULL, L"Error"); + } + return S_OK; +} + + + +static void MyCopyString_isUnicode(void *dest, UINT size, const UString &src, bool writeInUnicode) +{ + if (size != 0) + size--; + if (writeInUnicode) + { + UString s = src; + s.DeleteFrom(size); + MyStringCopy((wchar_t *)dest, s); + ODS_U(s) + } + else + { + AString s = GetAnsiString(src); + s.DeleteFrom(size); + MyStringCopy((char *)dest, s); + } +} + + +Z7_COMWF_B CZipContextMenu::GetCommandString( + #ifdef Z7_OLD_WIN_SDK + UINT + #else + UINT_PTR + #endif + commandOffset, + UINT uType, + UINT * /* pwReserved */ , LPSTR pszName, UINT cchMax) +{ + COM_TRY_BEGIN + + ODS("GetCommandString") + + const int cmdOffset = (int)commandOffset; + + ODS_SPRF_s(sprintf(s, "GetCommandString: cmdOffset=%d uType=%d cchMax = %d", + cmdOffset, uType, cchMax)) + + if ((uType | GCS_UNICODE) == GCS_VALIDATEW) + { + if (/* cmdOffset < 0 || */ (unsigned)cmdOffset >= _commandMap.Size()) + return S_FALSE; + return S_OK; + } + + if (/* cmdOffset < 0 || */ (unsigned)cmdOffset >= _commandMap.Size()) + { + ODS("------ cmdOffset: E_INVALIDARG") + return E_INVALIDARG; + } + + // we use Verb as HelpString + if (cchMax != 0) + if ((uType | GCS_UNICODE) == GCS_VERBW || + (uType | GCS_UNICODE) == GCS_HELPTEXTW) + { + const CCommandMapItem &cmi = _commandMap[(unsigned)cmdOffset]; + MyCopyString_isUnicode(pszName, cchMax, cmi.Verb, (uType & GCS_UNICODE) != 0); + return S_OK; + } + + return E_INVALIDARG; + + COM_TRY_END +} + + + +// ---------- IExplorerCommand ---------- + +static HRESULT WINAPI My_SHStrDupW(LPCWSTR src, LPWSTR *dest) +{ + if (src) + { + const SIZE_T size = (wcslen(src) + 1) * sizeof(WCHAR); + WCHAR *p = (WCHAR *)CoTaskMemAlloc(size); + if (p) + { + memcpy(p, src, size); + *dest = p; + return S_OK; + } + } + *dest = NULL; + return E_OUTOFMEMORY; +} + + +#define CZipExplorerCommand CZipContextMenu + +class CCoTaskWSTR +{ + LPWSTR m_str; + Z7_CLASS_NO_COPY(CCoTaskWSTR) +public: + CCoTaskWSTR(): m_str(NULL) {} + ~CCoTaskWSTR() { ::CoTaskMemFree(m_str); } + LPWSTR* operator&() { return &m_str; } + operator LPCWSTR () const { return m_str; } + // operator LPCOLESTR() const { return m_str; } + operator bool() const { return m_str != NULL; } + // bool operator!() const { return m_str == NULL; } + + /* + void Wipe_and_Free() + { + if (m_str) + { + memset(m_str, 0, ::SysStringLen(m_str) * sizeof(*m_str)); + Empty(); + } + } + */ + +private: + /* + CCoTaskWSTR(LPCOLESTR src) { m_str = ::CoTaskMemAlloc(src); } + + CCoTaskWSTR& operator=(LPCOLESTR src) + { + ::CoTaskMemFree(m_str); + m_str = ::SysAllocString(src); + return *this; + } + + + void Empty() + { + ::CoTaskMemFree(m_str); + m_str = NULL; + } + */ +}; + +static HRESULT LoadPaths(IShellItemArray *psiItemArray, UStringVector &paths) +{ + if (psiItemArray) + { + DWORD numItems = 0; + RINOK(psiItemArray->GetCount(&numItems)) + { + ODS_(Print_Number(numItems, " ==== LoadPaths START === ")) + for (DWORD i = 0; i < numItems; i++) + { + CMyComPtr item; + RINOK(psiItemArray->GetItemAt(i, &item)) + if (item) + { + CCoTaskWSTR displayName; + if (item->GetDisplayName(SIGDN_FILESYSPATH, &displayName) == S_OK + && (bool)displayName) + { + ODS_U(displayName) + paths.Add((LPCWSTR)displayName); + } + } + } + ODS_(Print_Number(numItems, " ==== LoadPaths END === ")) + } + } + return S_OK; +} + + +void CZipExplorerCommand::LoadItems(IShellItemArray *psiItemArray) +{ + SubCommands.Clear(); + _fileNames.Clear(); + { + UStringVector paths; + if (LoadPaths(psiItemArray, paths) != S_OK) + return; + _fileNames = paths; + } + const HRESULT res = QueryContextMenu( + NULL, // hMenu, + 0, // indexMenu, + 0, // commandIDFirst, + 0 + 999, // commandIDLast, + CMF_NORMAL); + + if (FAILED(res)) + return /* res */; + + CZipExplorerCommand *crcHandler = NULL; + CZipExplorerCommand *openHandler = NULL; + + bool useCascadedCrc = true; // false; + bool useCascadedOpen = true; // false; + + for (unsigned i = 0; i < _commandMap.Size(); i++) + { + const CCommandMapItem &cmi = _commandMap[i]; + + if (cmi.IsPopup) + if (!cmi.IsSubMenu()) + continue; + + // if (cmi.IsSubMenu()) continue // for debug + + CZipContextMenu *shellExt = new CZipContextMenu(); + shellExt->IsRoot = false; + + if (cmi.CtxCommandType == CtxCommandType_CrcRoot && !useCascadedCrc) + shellExt->IsSeparator = true; + + { + CZipExplorerCommand *handler = this; + if (cmi.CtxCommandType == CtxCommandType_CrcChild && crcHandler) + handler = crcHandler; + else if (cmi.CtxCommandType == CtxCommandType_OpenChild && openHandler) + handler = openHandler; + handler->SubCommands.AddNew() = shellExt; + } + + shellExt->_commandMap_Cur.Add(cmi); + + ODS_U(cmi.UserString) + + if (cmi.CtxCommandType == CtxCommandType_CrcRoot && useCascadedCrc) + crcHandler = shellExt; + if (cmi.CtxCommandType == CtxCommandType_OpenRoot && useCascadedOpen) + { + // ODS("cmi.CtxCommandType == CtxCommandType_OpenRoot"); + openHandler = shellExt; + } + } +} + + +Z7_COMWF_B CZipExplorerCommand::GetTitle(IShellItemArray *psiItemArray, LPWSTR *ppszName) +{ + ODS("- GetTitle()") + // COM_TRY_BEGIN + if (IsSeparator) + { + *ppszName = NULL; + return S_FALSE; + } + + UString name; + if (IsRoot) + { + LoadItems(psiItemArray); + name = "7-Zip"; // "New" + } + else + name = "7-Zip item"; + + if (!_commandMap_Cur.IsEmpty()) + { + const CCommandMapItem &mi = _commandMap_Cur[0]; + // s += mi.Verb; + // s += " : "; + name = mi.UserString; + } + + return My_SHStrDupW(name, ppszName); + // return S_OK; + // COM_TRY_END +} + + +Z7_COMWF_B CZipExplorerCommand::GetIcon(IShellItemArray * /* psiItemArray */, LPWSTR *ppszIcon) +{ + ODS("- GetIcon()") + // COM_TRY_BEGIN + *ppszIcon = NULL; + // return E_NOTIMPL; + UString imageName = fs2us(NWindows::NDLL::GetModuleDirPrefix()); + // imageName += "7zG.exe"; + imageName += "7-zip.dll"; + // imageName += ",190"; + return My_SHStrDupW(imageName, ppszIcon); + // COM_TRY_END +} + + +Z7_COMWF_B CZipExplorerCommand::GetToolTip (IShellItemArray * /* psiItemArray */, LPWSTR *ppszInfotip) +{ + // COM_TRY_BEGIN + ODS("- GetToolTip()") + *ppszInfotip = NULL; + return E_NOTIMPL; + // COM_TRY_END +} + + +Z7_COMWF_B CZipExplorerCommand::GetCanonicalName(GUID *pguidCommandName) +{ + // COM_TRY_BEGIN + ODS("- GetCanonicalName()") + *pguidCommandName = GUID_NULL; + return E_NOTIMPL; + // COM_TRY_END +} + + +Z7_COMWF_B CZipExplorerCommand::GetState(IShellItemArray * /* psiItemArray */, BOOL /* fOkToBeSlow */, EXPCMDSTATE *pCmdState) +{ + // COM_TRY_BEGIN + ODS("- GetState()") + *pCmdState = ECS_ENABLED; + return S_OK; + // COM_TRY_END +} + + + + +Z7_COMWF_B CZipExplorerCommand::Invoke(IShellItemArray *psiItemArray, IBindCtx * /* pbc */) +{ + COM_TRY_BEGIN + + if (_commandMap_Cur.IsEmpty()) + return E_INVALIDARG; + + ODS("- Invoke()") + _fileNames.Clear(); + UStringVector paths; + RINOK(LoadPaths(psiItemArray, paths)) + _fileNames = paths; + return InvokeCommandCommon(_commandMap_Cur[0]); + + COM_TRY_END +} + + +Z7_COMWF_B CZipExplorerCommand::GetFlags(EXPCMDFLAGS *pFlags) +{ + ODS("- GetFlags()") + // COM_TRY_BEGIN + EXPCMDFLAGS f = ECF_DEFAULT; + if (IsSeparator) + f = ECF_ISSEPARATOR; + else if (IsRoot) + f = ECF_HASSUBCOMMANDS; + else + { + if (!_commandMap_Cur.IsEmpty()) + { + // const CCommandMapItem &cmi = ; + if (_commandMap_Cur[0].IsSubMenu()) + { + // ODS("ECF_HASSUBCOMMANDS") + f = ECF_HASSUBCOMMANDS; + } + } + } + *pFlags = f; + return S_OK; + // COM_TRY_END +} + + +Z7_COMWF_B CZipExplorerCommand::EnumSubCommands(IEnumExplorerCommand **ppEnum) +{ + ODS("- EnumSubCommands()") + // COM_TRY_BEGIN + *ppEnum = NULL; + + if (!_commandMap_Cur.IsEmpty() && _commandMap_Cur[0].IsSubMenu()) + { + } + else + { + if (!IsRoot) + return E_NOTIMPL; + if (SubCommands.IsEmpty()) + { + return E_NOTIMPL; + } + } + + // shellExt-> + return QueryInterface(IID_IEnumExplorerCommand, (void **)ppEnum); + + // return S_OK; + // COM_TRY_END +} + + +Z7_COMWF_B CZipContextMenu::Next(ULONG celt, IExplorerCommand **pUICommand, ULONG *pceltFetched) +{ + ODS("CZipContextMenu::Next()") + ODS_(Print_Number(celt, "celt")) + ODS_(Print_Number(CurrentSubCommand, "CurrentSubCommand")) + ODS_(Print_Number(SubCommands.Size(), "SubCommands.Size()")) + + COM_TRY_BEGIN + ULONG fetched = 0; + + ULONG i; + for (i = 0; i < celt; i++) + { + pUICommand[i] = NULL; + } + + for (i = 0; i < celt && CurrentSubCommand < SubCommands.Size(); i++) + { + pUICommand[i] = SubCommands[CurrentSubCommand++]; + pUICommand[i]->AddRef(); + fetched++; + } + + if (pceltFetched) + *pceltFetched = fetched; + + ODS(fetched == celt ? " === OK === " : "=== ERROR ===") + + // we return S_FALSE for (fetched == 0) + return (fetched == celt) ? S_OK : S_FALSE; + COM_TRY_END +} + + +Z7_COMWF_B CZipContextMenu::Skip(ULONG /* celt */) +{ + ODS("CZipContextMenu::Skip()") + return E_NOTIMPL; +} + + +Z7_COMWF_B CZipContextMenu::Reset(void) +{ + ODS("CZipContextMenu::Reset()") + CurrentSubCommand = 0; + return S_OK; +} + + +Z7_COMWF_B CZipContextMenu::Clone(IEnumExplorerCommand **ppenum) +{ + ODS("CZipContextMenu::Clone()") + *ppenum = NULL; + return E_NOTIMPL; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/ContextMenu.h b/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/ContextMenu.h new file mode 100644 index 00000000..2759967d --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/ContextMenu.h @@ -0,0 +1,173 @@ +// ContextMenu.h + +#ifndef ZIP7_INC_CONTEXT_MENU_H +#define ZIP7_INC_CONTEXT_MENU_H + +#include "../../../Windows/Shell.h" + +#include "MyExplorerCommand.h" + +#include "../FileManager/MyCom2.h" + +#ifdef CMF_EXTENDEDVERBS +#define Z7_WIN_CMF_EXTENDEDVERBS CMF_EXTENDEDVERBS +#else +#define Z7_WIN_CMF_EXTENDEDVERBS 0x00000100 +#endif + +enum enum_CtxCommandType +{ + CtxCommandType_Normal, + CtxCommandType_OpenRoot, + CtxCommandType_OpenChild, + CtxCommandType_CrcRoot, + CtxCommandType_CrcChild +}; + + +class CZipContextMenu Z7_final: + public IContextMenu, + public IShellExtInit, + public IExplorerCommand, + public IEnumExplorerCommand, + public CMyUnknownImp +{ + Z7_COM_UNKNOWN_IMP_4_MT( + IContextMenu, + IShellExtInit, + IExplorerCommand, + IEnumExplorerCommand + ) + + // IShellExtInit + STDMETHOD(Initialize)(LPCITEMIDLIST pidlFolder, LPDATAOBJECT dataObject, HKEY hkeyProgID) Z7_override; + + // IContextMenu + STDMETHOD(QueryContextMenu)(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags) Z7_override; + STDMETHOD(InvokeCommand)(LPCMINVOKECOMMANDINFO lpici) Z7_override; + STDMETHOD(GetCommandString)( + #ifdef Z7_OLD_WIN_SDK + UINT + #else + UINT_PTR + #endif + idCmd, UINT uType, UINT *pwReserved, LPSTR pszName, UINT cchMax) Z7_override; + + // IExplorerCommand + STDMETHOD (GetTitle) (IShellItemArray *psiItemArray, LPWSTR *ppszName) Z7_override; + STDMETHOD (GetIcon) (IShellItemArray *psiItemArray, LPWSTR *ppszIcon) Z7_override; + STDMETHOD (GetToolTip) (IShellItemArray *psiItemArray, LPWSTR *ppszInfotip) Z7_override; + STDMETHOD (GetCanonicalName) (GUID *pguidCommandName) Z7_override; + STDMETHOD (GetState) (IShellItemArray *psiItemArray, BOOL fOkToBeSlow, EXPCMDSTATE *pCmdState) Z7_override; + STDMETHOD (Invoke) (IShellItemArray *psiItemArray, IBindCtx *pbc) Z7_override; + STDMETHOD (GetFlags) (EXPCMDFLAGS *pFlags) Z7_override; + STDMETHOD (EnumSubCommands) (IEnumExplorerCommand **ppEnum) Z7_override; + + // IEnumExplorerCommand + STDMETHOD (Next) (ULONG celt, IExplorerCommand **pUICommand, ULONG *pceltFetched) Z7_override; + STDMETHOD (Skip) (ULONG celt) Z7_override; + STDMETHOD (Reset) (void) Z7_override; + STDMETHOD (Clone) (IEnumExplorerCommand **ppenum) Z7_override; + +public: + + enum enum_CommandInternalID + { + kCommandNULL, + kOpen, + kExtract, + kExtractHere, + kExtractTo, + kTest, + kCompress, + kCompressEmail, + kCompressTo7z, + kCompressTo7zEmail, + kCompressToZip, + kCompressToZipEmail, + kHash_CRC32, + kHash_CRC64, + kHash_XXH64, + kHash_MD5, + kHash_SHA1, + kHash_SHA256, + kHash_SHA384, + kHash_SHA512, + kHash_SHA3_256, + kHash_BLAKE2SP, + kHash_All, + kHash_Generate_SHA256, + kHash_TestArc + }; + +public: + void Init_For_7zFM() + { + // _isMenuForFM = true; + // _fileNames_WereReduced = false; + } + + void LoadItems(IShellItemArray *psiItemArray); + + CZipContextMenu(); + ~CZipContextMenu(); + + struct CCommandMapItem + { + enum_CommandInternalID CommandInternalID; + UString Verb; + UString UserString; + // UString HelpString; + UString Folder; + UString ArcName; + UString ArcType; + bool IsPopup; + enum_CtxCommandType CtxCommandType; + + CCommandMapItem(): + IsPopup(false), + CtxCommandType(CtxCommandType_Normal) + {} + + bool IsSubMenu() const + { + return + CtxCommandType == CtxCommandType_CrcRoot || + CtxCommandType == CtxCommandType_OpenRoot; + } + }; + + UStringVector _fileNames; + NWindows::NShell::CFileAttribs _attribs; + +private: + bool _isMenuForFM; + bool _fileNames_WereReduced; // = true, if only first 16 items were used in QueryContextMenu() + bool _dropMode; + UString _dropPath; + CObjectVector _commandMap; + CObjectVector _commandMap_Cur; + + HBITMAP _bitmap; + UInt32 _writeZone; + CBoolPair _elimDup; + + bool IsSeparator; + bool IsRoot; + CObjectVector< CMyComPtr > SubCommands; + unsigned CurrentSubCommand; + + void Set_UserString_in_LastCommand(const UString &s) + { + _commandMap.Back().UserString = s; + } + + int FindVerb(const UString &verb) const; + void FillCommand(enum_CommandInternalID id, UString &mainString, CCommandMapItem &cmi) const; + void AddCommand(enum_CommandInternalID id, UString &mainString, CCommandMapItem &cmi); + void AddMapItem_ForSubMenu(const char *ver); + + HRESULT InvokeCommandCommon(const CCommandMapItem &cmi); +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/ContextMenuFlags.h b/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/ContextMenuFlags.h new file mode 100644 index 00000000..50c177e9 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/ContextMenuFlags.h @@ -0,0 +1,27 @@ +// ContextMenuFlags.h + +#ifndef ZIP7_INC_CONTEXT_MENU_FLAGS_H +#define ZIP7_INC_CONTEXT_MENU_FLAGS_H + +namespace NContextMenuFlags +{ + const UInt32 kExtract = 1 << 0; + const UInt32 kExtractHere = 1 << 1; + const UInt32 kExtractTo = 1 << 2; + + const UInt32 kTest = 1 << 4; + const UInt32 kOpen = 1 << 5; + const UInt32 kOpenAs = 1 << 6; + + const UInt32 kCompress = 1 << 8; + const UInt32 kCompressTo7z = 1 << 9; + const UInt32 kCompressEmail = 1 << 10; + const UInt32 kCompressTo7zEmail = 1 << 11; + const UInt32 kCompressToZip = 1 << 12; + const UInt32 kCompressToZipEmail = 1 << 13; + + const UInt32 kCRC_Cascaded = (UInt32)1 << 30; + const UInt32 kCRC = (UInt32)1 << 31; +} + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/DllExportsExplorer.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/DllExportsExplorer.cpp new file mode 100644 index 00000000..c92c983d --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/DllExportsExplorer.cpp @@ -0,0 +1,268 @@ +// DLLExportsExplorer.cpp +// +// Notes: +// Win2000: +// If I register at HKCR\Folder\ShellEx then DLL is locked. +// otherwise it unloads after explorer closing. +// but if I call menu for desktop items it's locked all the time + +#include "StdAfx.h" + +#include "../../../Common/MyWindows.h" + +#if defined(__clang__) && __clang_major__ >= 4 +#pragma GCC diagnostic ignored "-Wnonportable-system-include-path" +#endif +// : in new Windows Kit 10.0.2**** (NTDDI_WIN10_MN is defined) +// : in another Windows Kit versions +#if defined(NTDDI_WIN10_MN) || defined(__MINGW32__) || defined(__MINGW64__) +#include +#else +#include +#endif + +#if defined(__MINGW32__) || defined(__MINGW64__) +#include +#else +#include +#endif + +#include "../../../Common/MyInitGuid.h" + +#include "../../../Common/ComTry.h" + +#include "../../../Windows/DLL.h" +#include "../../../Windows/ErrorMsg.h" +#include "../../../Windows/NtCheck.h" +#include "../../../Windows/Registry.h" + +#include "../FileManager/IFolder.h" + +#include "ContextMenu.h" + +static LPCTSTR const k_ShellExtName = TEXT("7-Zip Shell Extension"); +static LPCTSTR const k_Approved = TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved"); + +// {23170F69-40C1-278A-1000-000100020000} +static LPCTSTR const k_Clsid = TEXT("{23170F69-40C1-278A-1000-000100020000}"); + +Z7_DEFINE_GUID(CLSID_CZipContextMenu, + k_7zip_GUID_Data1, + k_7zip_GUID_Data2, + k_7zip_GUID_Data3_Common, + 0x10, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00); + +using namespace NWindows; + +extern +HINSTANCE g_hInstance; +HINSTANCE g_hInstance = NULL; + +extern +HWND g_HWND; +HWND g_HWND = NULL; + +extern +LONG g_DllRefCount; +LONG g_DllRefCount = 0; // Reference count of this DLL. + +extern +bool g_DisableUserQuestions; +bool g_DisableUserQuestions; + + +// #define ODS(sz) OutputDebugStringW(L#sz) +#define ODS(sz) + +class CShellExtClassFactory Z7_final: + public IClassFactory, + public CMyUnknownImp +{ + Z7_COM_UNKNOWN_IMP_1_MT(IClassFactory) + + STDMETHOD(CreateInstance)(LPUNKNOWN, REFIID, void**) Z7_override Z7_final; + STDMETHOD(LockServer)(BOOL) Z7_override Z7_final; +public: + CShellExtClassFactory() { InterlockedIncrement(&g_DllRefCount); } + ~CShellExtClassFactory() { InterlockedDecrement(&g_DllRefCount); } +}; + +Z7_COMWF_B CShellExtClassFactory::CreateInstance(LPUNKNOWN pUnkOuter, + REFIID riid, void **ppvObj) +{ + ODS("CShellExtClassFactory::CreateInstance()\r\n"); + /* + char s[64]; + ConvertUInt32ToHex(riid.Data1, s); + OutputDebugStringA(s); + */ + *ppvObj = NULL; + if (pUnkOuter) + return CLASS_E_NOAGGREGATION; + + CZipContextMenu *shellExt; + try + { + shellExt = new CZipContextMenu(); + } + catch(...) { return E_OUTOFMEMORY; } + if (!shellExt) + return E_OUTOFMEMORY; + + IContextMenu *ctxm = shellExt; + const HRESULT res = ctxm->QueryInterface(riid, ppvObj); + if (res != S_OK) + delete shellExt; + return res; +} + + +Z7_COMWF_B CShellExtClassFactory::LockServer(BOOL /* fLock */) +{ + return S_OK; // Check it +} + + +#if defined(_UNICODE) && !defined(_WIN64) && !defined(UNDER_CE) +#define NT_CHECK_FAIL_ACTION return FALSE; +#endif + +extern "C" +BOOL WINAPI DllMain( + #ifdef UNDER_CE + HANDLE hInstance + #else + HINSTANCE hInstance + #endif + , DWORD dwReason, LPVOID); + +extern "C" +BOOL WINAPI DllMain( + #ifdef UNDER_CE + HANDLE hInstance + #else + HINSTANCE hInstance + #endif + , DWORD dwReason, LPVOID) +{ + if (dwReason == DLL_PROCESS_ATTACH) + { + g_hInstance = (HINSTANCE)hInstance; + ODS("In DLLMain, DLL_PROCESS_ATTACH\r\n"); + NT_CHECK + } + else if (dwReason == DLL_PROCESS_DETACH) + { + ODS("In DLLMain, DLL_PROCESS_DETACH\r\n"); + } + return TRUE; +} + + +// Used to determine whether the DLL can be unloaded by OLE + +STDAPI DllCanUnloadNow(void) +{ + ODS("In DLLCanUnloadNow\r\n"); + /* + if (g_DllRefCount == 0) + ODS( "g_DllRefCount == 0"); + else + ODS( "g_DllRefCount != 0"); + */ + return (g_DllRefCount == 0 ? S_OK : S_FALSE); +} + +STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv) +{ + ODS("In DllGetClassObject\r\n"); + *ppv = NULL; + if (IsEqualIID(rclsid, CLSID_CZipContextMenu)) + { + CShellExtClassFactory *cf; + try + { + cf = new CShellExtClassFactory; + } + catch(...) { return E_OUTOFMEMORY; } + if (!cf) + return E_OUTOFMEMORY; + IClassFactory *cf2 = cf; + const HRESULT res = cf2->QueryInterface(riid, ppv); + if (res != S_OK) + delete cf; + return res; + } + return CLASS_E_CLASSNOTAVAILABLE; + // return _Module.GetClassObject(rclsid, riid, ppv); +} + + +static BOOL RegisterServer() +{ + ODS("RegisterServer\r\n"); + FString modulePath; + if (!NDLL::MyGetModuleFileName(modulePath)) + return FALSE; + const UString modulePathU = fs2us(modulePath); + + CSysString s ("CLSID\\"); + s += k_Clsid; + + { + NRegistry::CKey key; + if (key.Create(HKEY_CLASSES_ROOT, s, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE) != NOERROR) + return FALSE; + key.SetValue(NULL, k_ShellExtName); + NRegistry::CKey keyInproc; + if (keyInproc.Create(key, TEXT("InprocServer32"), NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE) != NOERROR) + return FALSE; + keyInproc.SetValue(NULL, modulePathU); + keyInproc.SetValue(TEXT("ThreadingModel"), TEXT("Apartment")); + } + + #if !defined(_WIN64) && !defined(UNDER_CE) + if (IsItWindowsNT()) + #endif + { + NRegistry::CKey key; + if (key.Create(HKEY_LOCAL_MACHINE, k_Approved, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE) == NOERROR) + key.SetValue(k_Clsid, k_ShellExtName); + } + + ODS("RegisterServer :: return TRUE"); + return TRUE; +} + +STDAPI DllRegisterServer(void) +{ + return RegisterServer() ? S_OK: SELFREG_E_CLASS; +} + +static BOOL UnregisterServer() +{ + CSysString s ("CLSID\\"); + s += k_Clsid; + + RegDeleteKey(HKEY_CLASSES_ROOT, s + TEXT("\\InprocServer32")); + RegDeleteKey(HKEY_CLASSES_ROOT, s); + + #if !defined(_WIN64) && !defined(UNDER_CE) + if (IsItWindowsNT()) + #endif + { + HKEY hKey; + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, k_Approved, 0, KEY_SET_VALUE, &hKey) == NOERROR) + { + RegDeleteValue(hKey, k_Clsid); + RegCloseKey(hKey); + } + } + + return TRUE; +} + +STDAPI DllUnregisterServer(void) +{ + return UnregisterServer() ? S_OK: SELFREG_E_CLASS; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/Explorer.def b/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/Explorer.def new file mode 100644 index 00000000..034a269d --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/Explorer.def @@ -0,0 +1,9 @@ +; 7-zip.def + +LIBRARY "7-zip" + +EXPORTS + DllCanUnloadNow PRIVATE + DllGetClassObject PRIVATE + DllRegisterServer PRIVATE + DllUnregisterServer PRIVATE diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/Explorer.dsp b/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/Explorer.dsp new file mode 100644 index 00000000..2ed491f4 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/Explorer.dsp @@ -0,0 +1,620 @@ +# Microsoft Developer Studio Project File - Name="Explorer" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=Explorer - Win32 DebugU +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "Explorer.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "Explorer.mak" CFG="Explorer - Win32 DebugU" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "Explorer - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "Explorer - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "Explorer - Win32 ReleaseU" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "Explorer - Win32 DebugU" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "Explorer - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 1 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "EXPLORER_EXPORTS" /YX /FD /c +# ADD CPP /nologo /Gz /MD /W4 /WX /GX /O1 /D "NDEBUG" /D "_MBCS" /D "WIN32" /D "_WINDOWS" /D "_USRDLL" /D "EXPLORER_EXPORTS" /D "Z7_LANG" /D "Z7_LONG_PATH" /FAcs /Yu"StdAfx.h" /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "NDEBUG" +# ADD RSC /l 0x419 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib comctl32.lib htmlhelp.lib /nologo /dll /machine:I386 /out:"C:\Util\7-Zip.dll" /opt:NOWIN98 +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "Explorer - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 1 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "EXPLORER_EXPORTS" /YX /FD /GZ /c +# ADD CPP /nologo /Gz /MTd /W4 /WX /Gm /GX /ZI /Od /D "_DEBUG" /D "_MBCS" /D "WIN32" /D "_WINDOWS" /D "_USRDLL" /D "EXPLORER_EXPORTS" /D "Z7_LANG" /D "Z7_LONG_PATH" /Yu"StdAfx.h" /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "_DEBUG" +# ADD RSC /l 0x419 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib comctl32.lib htmlhelp.lib /nologo /dll /debug /machine:I386 /out:"C:\Util\7-Zip.dll" /pdbtype:sept + +!ELSEIF "$(CFG)" == "Explorer - Win32 ReleaseU" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "ReleaseU" +# PROP BASE Intermediate_Dir "ReleaseU" +# PROP BASE Ignore_Export_Lib 1 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "ReleaseU" +# PROP Intermediate_Dir "ReleaseU" +# PROP Ignore_Export_Lib 1 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MD /W3 /GX /O2 /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "_USRDLL" /D "EXPLORER_EXPORTS" /D "Z7_LANG" /D "_MBCS" /Yu"StdAfx.h" /FD /c +# ADD CPP /nologo /Gz /MD /W4 /WX /GX /O1 /D "NDEBUG" /D "_UNICODE" /D "UNICODE" /D "WIN32" /D "_WINDOWS" /D "_USRDLL" /D "EXPLORER_EXPORTS" /D "Z7_LANG" /D "Z7_LONG_PATH" /Yu"StdAfx.h" /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib comctl32.lib htmlhelp.lib /nologo /dll /machine:I386 /out:"C:\Program Files\7-ZIP\7-Zip.dll" /opt:NOWIN98 +# SUBTRACT BASE LINK32 /pdb:none +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib comctl32.lib htmlhelp.lib /nologo /dll /machine:I386 /out:"C:\Util\7-Zip.dll" /opt:NOWIN98 +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "Explorer - Win32 DebugU" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "DebugU" +# PROP BASE Intermediate_Dir "DebugU" +# PROP BASE Ignore_Export_Lib 1 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "DebugU" +# PROP Intermediate_Dir "DebugU" +# PROP Ignore_Export_Lib 1 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "_USRDLL" /D "EXPLORER_EXPORTS" /D "Z7_LANG" /D "_MBCS" /Yu"StdAfx.h" /FD /GZ /c +# ADD CPP /nologo /Gz /MTd /W4 /WX /Gm /GX /ZI /Od /D "_DEBUG" /D "_UNICODE" /D "UNICODE" /D "WIN32" /D "_WINDOWS" /D "_USRDLL" /D "EXPLORER_EXPORTS" /D "Z7_LANG" /D "Z7_LONG_PATH" /Yu"StdAfx.h" /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "_DEBUG" +# ADD RSC /l 0x419 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib comctl32.lib htmlhelp.lib /nologo /dll /debug /machine:I386 /out:"C:\Program Files\7-ZIP\7-Zip.dll" /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib comctl32.lib htmlhelp.lib /nologo /dll /debug /machine:I386 /out:"C:\Util\7-Zip.dll" /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "Explorer - Win32 Release" +# Name "Explorer - Win32 Debug" +# Name "Explorer - Win32 ReleaseU" +# Name "Explorer - Win32 DebugU" +# Begin Group "Spec" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=.\DllExportsExplorer.cpp +# End Source File +# Begin Source File + +SOURCE=.\Explorer.def +# End Source File +# Begin Source File + +SOURCE=.\resource.h +# End Source File +# Begin Source File + +SOURCE=.\resource.rc +# End Source File +# Begin Source File + +SOURCE=.\StdAfx.cpp +# ADD CPP /Yc"StdAfx.h" +# End Source File +# Begin Source File + +SOURCE=.\StdAfx.h +# End Source File +# End Group +# Begin Group "UI Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\Common\ArchiveName.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\ArchiveName.h +# End Source File +# Begin Source File + +SOURCE=..\Common\CompressCall.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\CompressCall.h +# End Source File +# Begin Source File + +SOURCE=..\Common\ExtractingFilePath.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\ExtractingFilePath.h +# End Source File +# Begin Source File + +SOURCE=..\Common\ZipRegistry.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\ZipRegistry.h +# End Source File +# End Group +# Begin Group "Engine" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=.\ContextMenu.cpp +# End Source File +# Begin Source File + +SOURCE=.\ContextMenu.h +# End Source File +# Begin Source File + +SOURCE=.\MyExplorerCommand.h +# End Source File +# Begin Source File + +SOURCE=.\MyMessages.cpp +# End Source File +# Begin Source File + +SOURCE=.\MyMessages.h +# End Source File +# End Group +# Begin Group "FileManager" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\FileManager\FormatUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\FileManager\FormatUtils.h +# End Source File +# Begin Source File + +SOURCE=..\FileManager\HelpUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\FileManager\HelpUtils.h +# End Source File +# Begin Source File + +SOURCE=..\FileManager\IFolder.h +# End Source File +# Begin Source File + +SOURCE=..\FileManager\LangUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\FileManager\LangUtils.h +# End Source File +# Begin Source File + +SOURCE=..\FileManager\MyCom2.h +# End Source File +# Begin Source File + +SOURCE=..\FileManager\ProgramLocation.cpp +# End Source File +# Begin Source File + +SOURCE=..\FileManager\ProgramLocation.h +# End Source File +# Begin Source File + +SOURCE=..\FileManager\PropertyName.cpp +# End Source File +# Begin Source File + +SOURCE=..\FileManager\PropertyName.h +# End Source File +# Begin Source File + +SOURCE=..\FileManager\RegistryUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\FileManager\RegistryUtils.h +# End Source File +# End Group +# Begin Group "C" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\..\C\7zTypes.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zWindows.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Compiler.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\CpuArch.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\CpuArch.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Sort.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Sort.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Threads.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Threads.h +# End Source File +# End Group +# Begin Group "Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\Common\Common.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\IntToString.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\IntToString.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Lang.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Lang.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyCom.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyString.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyString.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyVector.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyVector.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyWindows.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\NewHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\NewHandler.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Random.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Random.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StringConvert.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StringConvert.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StringToInt.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StringToInt.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Types.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\UTFConvert.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\UTFConvert.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Wildcard.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Wildcard.h +# End Source File +# End Group +# Begin Group "Windows" + +# PROP Default_Filter "" +# Begin Group "Control" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\Windows\Control\Dialog.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\Dialog.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\ListView.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\ListView.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\PropertyPage.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\PropertyPage.h +# End Source File +# End Group +# Begin Source File + +SOURCE=..\..\..\Windows\COM.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\DLL.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\DLL.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\ErrorMsg.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\ErrorMsg.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileDir.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileDir.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileFind.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileFind.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileIO.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileIO.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileMapping.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileName.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileName.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\MemoryGlobal.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\MemoryGlobal.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\MemoryLock.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\MemoryLock.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Menu.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Menu.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\ProcessUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\ProcessUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Registry.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Registry.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\ResourceString.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\ResourceString.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Shell.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Shell.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Synchronization.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\TimeUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\TimeUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Window.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Window.h +# End Source File +# End Group +# Begin Source File + +SOURCE=".\7-zip.dll.manifest" +# End Source File +# Begin Source File + +SOURCE=.\ContextMenuFlags.h +# End Source File +# Begin Source File + +SOURCE=..\FileManager\FM.ico +# End Source File +# End Target +# End Project diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/Explorer.dsw b/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/Explorer.dsw new file mode 100644 index 00000000..beb8df7b --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/Explorer.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "Explorer"=".\Explorer.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/MenuLogo.bmp b/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/MenuLogo.bmp new file mode 100644 index 00000000..906a6c5c Binary files /dev/null and b/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/MenuLogo.bmp differ diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/MyExplorerCommand.h b/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/MyExplorerCommand.h new file mode 100644 index 00000000..41ac8d67 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/MyExplorerCommand.h @@ -0,0 +1,217 @@ +// MyExplorerCommand.h + +#ifndef ZIP7_INC_MY_EXPLORER_COMMAND_H +#define ZIP7_INC_MY_EXPLORER_COMMAND_H + +#if _MSC_VER >= 1910 +#define USE_SYS_shobjidl_core +#endif + +#ifdef USE_SYS_shobjidl_core + +// #include + +#else + +/* IShellItem is defined: + ShObjIdl.h : old Windows SDK + ShObjIdl_core.h : new Windows 10 SDK */ + +#ifndef Z7_OLD_WIN_SDK +#include +#endif + +#ifndef __IShellItem_INTERFACE_DEFINED__ +#define __IShellItem_INTERFACE_DEFINED__ + +// For MINGW we define IShellItem + +// #error Stop_Compiling__NOT_DEFINED__IShellItem_INTERFACE_DEFINED__ + +typedef +enum +{ SIGDN_NORMALDISPLAY = 0, + SIGDN_PARENTRELATIVEPARSING = 0x80018001, + SIGDN_PARENTRELATIVEFORADDRESSBAR = 0x8001c001, + SIGDN_DESKTOPABSOLUTEPARSING = 0x80028000, + SIGDN_PARENTRELATIVEEDITING = 0x80031001, + SIGDN_DESKTOPABSOLUTEEDITING = 0x8004c000, + SIGDN_FILESYSPATH = 0x80058000, + SIGDN_URL = 0x80068000 +} SIGDN; + + +typedef DWORD SICHINTF; +typedef ULONG SFGAOF; + +struct IShellItem : public IUnknown +{ + virtual HRESULT STDMETHODCALLTYPE BindToHandler(IBindCtx *pbc, REFGUID rbhid, REFIID riid, void **ppvOut) = 0; + virtual HRESULT STDMETHODCALLTYPE GetParent(IShellItem **ppsi) = 0; + virtual HRESULT STDMETHODCALLTYPE GetDisplayName(SIGDN sigdnName, LPOLESTR *ppszName) = 0; + virtual HRESULT STDMETHODCALLTYPE GetAttributes(SFGAOF sfgaoMask, SFGAOF *psfgaoAttribs) = 0; + virtual HRESULT STDMETHODCALLTYPE Compare(IShellItem *psi, SICHINTF hint, int *piOrder) = 0; +}; + +#endif // __IShellItem_INTERFACE_DEFINED__ + + + +#ifndef __IShellItemArray_INTERFACE_DEFINED__ +#define __IShellItemArray_INTERFACE_DEFINED__ + +// propsys.h + +typedef /* [v1_enum] */ +enum GETPROPERTYSTOREFLAGS +{ + GPS_DEFAULT = 0, + GPS_HANDLERPROPERTIESONLY = 0x1, + GPS_READWRITE = 0x2, + GPS_TEMPORARY = 0x4, + GPS_FASTPROPERTIESONLY = 0x8, + GPS_OPENSLOWITEM = 0x10, + GPS_DELAYCREATION = 0x20, + GPS_BESTEFFORT = 0x40, + GPS_NO_OPLOCK = 0x80, + GPS_PREFERQUERYPROPERTIES = 0x100, + GPS_EXTRINSICPROPERTIES = 0x200, + GPS_EXTRINSICPROPERTIESONLY = 0x400, + GPS_VOLATILEPROPERTIES = 0x800, + GPS_VOLATILEPROPERTIESONLY = 0x1000, + GPS_MASK_VALID = 0x1fff +} GETPROPERTYSTOREFLAGS; + +// DEFINE_ENUM_FLAG_OPERATORS(GETPROPERTYSTOREFLAGS) + + +#ifndef PROPERTYKEY_DEFINED +#define PROPERTYKEY_DEFINED + +typedef +struct +{ + GUID fmtid; + DWORD pid; +} PROPERTYKEY; + +#endif // PROPERTYKEY_DEFINED + +// propkeydef.h +#define REFPROPERTYKEY const PROPERTYKEY & + +#ifdef INITGUID +#define DEFINE_PROPERTYKEY(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8, pid) EXTERN_C const PROPERTYKEY DECLSPEC_SELECTANY name = { { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }, pid } +#else +#define DEFINE_PROPERTYKEY(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8, pid) EXTERN_C const PROPERTYKEY name +#endif // INITGUID + + +// +typedef /* [v1_enum] */ +enum SIATTRIBFLAGS +{ + SIATTRIBFLAGS_AND = 0x1, + SIATTRIBFLAGS_OR = 0x2, + SIATTRIBFLAGS_APPCOMPAT = 0x3, + SIATTRIBFLAGS_MASK = 0x3, + SIATTRIBFLAGS_ALLITEMS = 0x4000 +} SIATTRIBFLAGS; + +// DEFINE_ENUM_FLAG_OPERATORS(SIATTRIBFLAGS) + + +// MIDL_INTERFACE("70629033-e363-4a28-a567-0db78006e6d7") +DEFINE_GUID(IID_IEnumShellItems, 0x70629033, 0xe363, 0xe363, 0xa5, 0x67, 0x0d, 0xb7, 0x80, 0x06, 0xe6, 0xd7); + +struct IEnumShellItems : public IUnknown +{ + STDMETHOD (Next) (ULONG celt, IShellItem **rgelt, ULONG *pceltFetched) = 0; + STDMETHOD (Skip) (ULONG celt) = 0; + STDMETHOD (Reset) (void) = 0; + STDMETHOD (Clone) (IEnumShellItems **ppenum) = 0; +}; + + +// MIDL_INTERFACE("b63ea76d-1f85-456f-a19c-48159efa858b") +DEFINE_GUID(IID_IShellItemArray, 0xb63ea76d, 0x1f85, 0x456f, 0xa1, 0x9c, 0x48, 0x15, 0x9e, 0xfa, 0x85, 0x8b); + +struct IShellItemArray : public IUnknown +{ + STDMETHOD (BindToHandler) (IBindCtx *pbc, REFGUID bhid, REFIID riid, void **ppvOut) = 0; + STDMETHOD (GetPropertyStore) (GETPROPERTYSTOREFLAGS flags, REFIID riid, void **ppv) = 0; + STDMETHOD (GetPropertyDescriptionList) (REFPROPERTYKEY keyType, REFIID riid, void **ppv) = 0; + STDMETHOD (GetAttributes) ( SIATTRIBFLAGS AttribFlags, SFGAOF sfgaoMask, SFGAOF *psfgaoAttribs) = 0; + STDMETHOD (GetCount) (DWORD *pdwNumItems) = 0; + STDMETHOD (GetItemAt) (DWORD dwIndex, IShellItem **ppsi) = 0; + STDMETHOD (EnumItems) (IEnumShellItems **ppenumShellItems) = 0; +}; + + +#ifndef __IEnumExplorerCommand_INTERFACE_DEFINED__ +#define __IEnumExplorerCommand_INTERFACE_DEFINED__ + +struct IExplorerCommand; + +// MIDL_INTERFACE("a88826f8-186f-4987-aade-ea0cef8fbfe8") +DEFINE_GUID(IID_IEnumExplorerCommand , 0xa88826f8, 0x186f, 0x4987, 0xaa, 0xde, 0xea, 0x0c, 0xef, 0x8f, 0xbf, 0xe8); + +struct IEnumExplorerCommand : public IUnknown +{ + STDMETHOD (Next) (ULONG celt, IExplorerCommand **pUICommand, ULONG *pceltFetched) = 0; + STDMETHOD (Skip) (ULONG celt) = 0; + STDMETHOD (Reset) (void) = 0; + STDMETHOD (Clone) (IEnumExplorerCommand **ppenum) = 0; +}; + + +enum _EXPCMDSTATE +{ + ECS_ENABLED = 0, + ECS_DISABLED = 0x1, + ECS_HIDDEN = 0x2, + ECS_CHECKBOX = 0x4, + ECS_CHECKED = 0x8, + ECS_RADIOCHECK = 0x10 +}; + +typedef DWORD EXPCMDSTATE; + +/* [v1_enum] */ +enum _EXPCMDFLAGS +{ + ECF_DEFAULT = 0, + ECF_HASSUBCOMMANDS = 0x1, + ECF_HASSPLITBUTTON = 0x2, + ECF_HIDELABEL = 0x4, + ECF_ISSEPARATOR = 0x8, + ECF_HASLUASHIELD = 0x10, + ECF_SEPARATORBEFORE = 0x20, + ECF_SEPARATORAFTER = 0x40, + ECF_ISDROPDOWN = 0x80, + ECF_TOGGLEABLE = 0x100, + ECF_AUTOMENUICONS = 0x200 +}; +typedef DWORD EXPCMDFLAGS; + + +// MIDL_INTERFACE("a08ce4d0-fa25-44ab-b57c-c7b1c323e0b9") +DEFINE_GUID(IID_IExplorerCommand, 0xa08ce4d0, 0xfa25, 0x44ab, 0xb5, 0x7c, 0xc7, 0xb1, 0xc3, 0x23, 0xe0, 0xb9); + +struct IExplorerCommand : public IUnknown +{ + STDMETHOD (GetTitle) (IShellItemArray *psiItemArray, LPWSTR *ppszName) = 0; + STDMETHOD (GetIcon) (IShellItemArray *psiItemArray, LPWSTR *ppszIcon) = 0; + STDMETHOD (GetToolTip) (IShellItemArray *psiItemArray, LPWSTR *ppszInfotip) = 0; + STDMETHOD (GetCanonicalName) (GUID *pguidCommandName) = 0; + STDMETHOD (GetState) (IShellItemArray *psiItemArray, BOOL fOkToBeSlow, EXPCMDSTATE *pCmdState) = 0; + STDMETHOD (Invoke) (IShellItemArray *psiItemArray, IBindCtx *pbc) = 0; + STDMETHOD (GetFlags) (EXPCMDFLAGS *pFlags) = 0; + STDMETHOD (EnumSubCommands) (IEnumExplorerCommand **ppEnum) = 0; +}; + +#endif // IShellItemArray +#endif // __IEnumExplorerCommand_INTERFACE_DEFINED__ +#endif // USE_SYS_shobjidl_core + +#endif // __MY_EXPLORER_COMMAND_H diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/MyMessages.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/MyMessages.cpp new file mode 100644 index 00000000..c079e310 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/MyMessages.cpp @@ -0,0 +1,43 @@ +// MyMessages.cpp + +#include "StdAfx.h" + +#include "MyMessages.h" + +#include "../../../Windows/ErrorMsg.h" +#include "../../../Windows/ResourceString.h" + +#include "../FileManager/LangUtils.h" + +using namespace NWindows; + +extern bool g_DisableUserQuestions; + +void ShowErrorMessage(HWND window, LPCWSTR message) +{ + if (!g_DisableUserQuestions) + ::MessageBoxW(window, message, L"7-Zip", MB_OK | MB_ICONSTOP); +} + +void ShowErrorMessageHwndRes(HWND window, UInt32 resID) +{ + UString s = LangString(resID); + if (s.IsEmpty()) + s.Add_UInt32(resID); + ShowErrorMessage(window, s); +} + +void ShowErrorMessageRes(UInt32 resID) +{ + ShowErrorMessageHwndRes(NULL, resID); +} + +static void ShowErrorMessageDWORD(HWND window, DWORD errorCode) +{ + ShowErrorMessage(window, NError::MyFormatMessage(errorCode)); +} + +void ShowLastErrorMessage(HWND window) +{ + ShowErrorMessageDWORD(window, ::GetLastError()); +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/MyMessages.h b/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/MyMessages.h new file mode 100644 index 00000000..47d10db7 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/MyMessages.h @@ -0,0 +1,16 @@ +// MyMessages.h + +#ifndef ZIP7_INC_MY_MESSAGES_H +#define ZIP7_INC_MY_MESSAGES_H + +#include "../../../Common/MyString.h" + +void ShowErrorMessage(HWND window, LPCWSTR message); +inline void ShowErrorMessage(LPCWSTR message) { ShowErrorMessage(NULL, message); } + +void ShowErrorMessageHwndRes(HWND window, UInt32 langID); +void ShowErrorMessageRes(UInt32 langID); + +void ShowLastErrorMessage(HWND window = NULL); + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/RegistryContextMenu.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/RegistryContextMenu.cpp new file mode 100644 index 00000000..6563d89b --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/RegistryContextMenu.cpp @@ -0,0 +1,225 @@ +// RegistryContextMenu.cpp + +#include "StdAfx.h" + +#include "../../../Common/StringConvert.h" + +#include "../../../Windows/Registry.h" + +#include "RegistryContextMenu.h" + +using namespace NWindows; +using namespace NRegistry; + +#ifndef UNDER_CE + +// does extension can work, if Approved is removed ? +// CLISID (and Approved ?) items are separated for 32-bit and 64-bit code. +// shellex items shared by 32-bit and 64-bit code? + +#define k_Clsid_A "{23170F69-40C1-278A-1000-000100020000}" + +static LPCTSTR const k_Clsid = TEXT(k_Clsid_A); +static LPCTSTR const k_ShellExtName = TEXT("7-Zip Shell Extension"); + +static LPCTSTR const k_Approved = TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved"); +static LPCTSTR const k_Inproc = TEXT("InprocServer32"); + +static LPCSTR const k_KeyPostfix_ContextMenu = "\\shellex\\ContextMenuHandlers\\7-Zip"; +static LPCSTR const k_KeyPostfix_DragDrop = "\\shellex\\DragDropHandlers\\7-Zip"; + +static LPCSTR const k_KeyName_File = "*"; +static LPCSTR const k_KeyName_Folder = "Folder"; +static LPCSTR const k_KeyName_Directory = "Directory"; +static LPCSTR const k_KeyName_Drive = "Drive"; + +static LPCSTR const k_shellex_Prefixes[] = +{ + k_KeyName_File, + k_KeyName_Folder, + k_KeyName_Directory, + k_KeyName_Drive +}; + +static const bool k_shellex_Statuses[2][4] = +{ + { true, true, true, false }, + { false, false, true, true } +}; + + +// RegDeleteKeyExW is supported starting from win2003sp1/xp-pro-x64 +// Z7_WIN32_WINNT_MIN < 0x0600 // Vista +#if !defined(Z7_WIN32_WINNT_MIN) \ + || Z7_WIN32_WINNT_MIN < 0x0502 /* < win2003 */ \ + || Z7_WIN32_WINNT_MIN == 0x0502 && !defined(_M_AMD64) +#define Z7_USE_DYN_RegDeleteKeyExW +#endif + +#ifdef Z7_USE_DYN_RegDeleteKeyExW +Z7_DIAGNOSTIC_IGNORE_CAST_FUNCTION +typedef +// WINADVAPI +LONG (APIENTRY *Func_RegDeleteKeyExW)(HKEY hKey, LPCWSTR lpSubKey, REGSAM samDesired, DWORD Reserved); +static Func_RegDeleteKeyExW func_RegDeleteKeyExW; + +static void Init_RegDeleteKeyExW() +{ + if (!func_RegDeleteKeyExW) + func_RegDeleteKeyExW = Z7_GET_PROC_ADDRESS( + Func_RegDeleteKeyExW, GetModuleHandleW(L"advapi32.dll"), + "RegDeleteKeyExW"); +} +#define INIT_REG_WOW if (wow != 0) Init_RegDeleteKeyExW(); +#else +#define INIT_REG_WOW +#endif + + +static LONG MyRegistry_DeleteKey(HKEY parentKey, LPCTSTR name, UInt32 wow) +{ + if (wow == 0) + return RegDeleteKey(parentKey, name); + +#ifdef Z7_USE_DYN_RegDeleteKeyExW + if (!func_RegDeleteKeyExW) + return E_NOTIMPL; + return func_RegDeleteKeyExW +#else + return RegDeleteKeyExW +#endif + (parentKey, GetUnicodeString(name), wow, 0); +} + +static LONG MyRegistry_DeleteKey_HKCR(LPCTSTR name, UInt32 wow) +{ + return MyRegistry_DeleteKey(HKEY_CLASSES_ROOT, name, wow); +} + +// static NSynchronization::CCriticalSection g_CS; + +static AString Get_ContextMenuHandler_KeyName(LPCSTR keyName) + { return (AString)keyName + k_KeyPostfix_ContextMenu; } + +/* +static CSysString Get_DragDropHandler_KeyName(LPCTSTR keyName) + { return (AString)keyName + k_KeyPostfix_DragDrop); } +*/ + +static bool CheckHandlerCommon(const AString &keyName, UInt32 wow) +{ + CKey key; + if (key.Open(HKEY_CLASSES_ROOT, (CSysString)keyName, KEY_READ | wow) != ERROR_SUCCESS) + return false; + CSysString value; + if (key.QueryValue(NULL, value) != ERROR_SUCCESS) + return false; + return StringsAreEqualNoCase_Ascii(value, k_Clsid_A); +} + +bool CheckContextMenuHandler(const UString &path, UInt32 wow) +{ + // NSynchronization::CCriticalSectionLock lock(g_CS); + + CSysString s ("CLSID\\"); + s += k_Clsid_A; + s += "\\InprocServer32"; + + { + NRegistry::CKey key; + if (key.Open(HKEY_CLASSES_ROOT, s, KEY_READ | wow) != ERROR_SUCCESS) + return false; + UString regPath; + if (key.QueryValue(NULL, regPath) != ERROR_SUCCESS) + return false; + if (!path.IsEqualTo_NoCase(regPath)) + return false; + } + + return + CheckHandlerCommon(Get_ContextMenuHandler_KeyName(k_KeyName_File), wow); + /* + && CheckHandlerCommon(Get_ContextMenuHandler_KeyName(k_KeyName_Directory), wow) + // && CheckHandlerCommon(Get_ContextMenuHandler_KeyName(k_KeyName_Folder)) + + && CheckHandlerCommon(Get_DragDropHandler_KeyName(k_KeyName_Directory), wow) + && CheckHandlerCommon(Get_DragDropHandler_KeyName(k_KeyName_Drive), wow); + */ +} + + +static LONG MyCreateKey(CKey &key, HKEY parentKey, LPCTSTR keyName, UInt32 wow) +{ + return key.Create(parentKey, keyName, REG_NONE, + REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS | wow); +} + +LONG SetContextMenuHandler(bool setMode, const UString &path, UInt32 wow) +{ + // NSynchronization::CCriticalSectionLock lock(g_CS); + + INIT_REG_WOW + + LONG res; + + { + CSysString s ("CLSID\\"); + s += k_Clsid_A; + + if (setMode) + { + { + CKey key; + res = MyCreateKey(key, HKEY_CLASSES_ROOT, s, wow); + if (res == ERROR_SUCCESS) + { + key.SetValue(NULL, k_ShellExtName); + CKey keyInproc; + res = MyCreateKey(keyInproc, key, k_Inproc, wow); + if (res == ERROR_SUCCESS) + { + res = keyInproc.SetValue(NULL, path); + keyInproc.SetValue(TEXT("ThreadingModel"), TEXT("Apartment")); + } + } + } + + { + CKey key; + if (MyCreateKey(key, HKEY_LOCAL_MACHINE, k_Approved, wow) == ERROR_SUCCESS) + key.SetValue(k_Clsid, k_ShellExtName); + } + } + else + { + CSysString s2 (s); + s2 += "\\InprocServer32"; + + MyRegistry_DeleteKey_HKCR(s2, wow); + res = MyRegistry_DeleteKey_HKCR(s, wow); + } + } + + // shellex items probably are shared beween 32-bit and 64-bit apps. So we don't delete items for delete operation. + if (setMode) + for (unsigned i = 0; i < 2; i++) + { + for (unsigned k = 0; k < Z7_ARRAY_SIZE(k_shellex_Prefixes); k++) + { + CSysString s (k_shellex_Prefixes[k]); + s += (i == 0 ? k_KeyPostfix_ContextMenu : k_KeyPostfix_DragDrop); + if (k_shellex_Statuses[i][k]) + { + CKey key; + MyCreateKey(key, HKEY_CLASSES_ROOT, s, wow); + key.SetValue(NULL, k_Clsid); + } + else + MyRegistry_DeleteKey_HKCR(s, wow); + } + } + + return res; +} + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/RegistryContextMenu.h b/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/RegistryContextMenu.h new file mode 100644 index 00000000..bf3bb5b7 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/RegistryContextMenu.h @@ -0,0 +1,13 @@ +// RegistryContextMenu.h + +#ifndef ZIP7_INC_REGISTRY_CONTEXT_MENU_H +#define ZIP7_INC_REGISTRY_CONTEXT_MENU_H + +#ifndef UNDER_CE + +bool CheckContextMenuHandler(const UString &path, UInt32 wow = 0); +LONG SetContextMenuHandler(bool setMode, const UString &path, UInt32 wow = 0); + +#endif + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/StdAfx.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/StdAfx.cpp new file mode 100644 index 00000000..d0feea85 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/StdAfx.cpp @@ -0,0 +1,3 @@ +// StdAfx.cpp + +#include "StdAfx.h" diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/StdAfx.h b/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/StdAfx.h new file mode 100644 index 00000000..130db8aa --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/StdAfx.h @@ -0,0 +1,6 @@ +// StdAfx.h + +#if _MSC_VER >= 1800 +#pragma warning(disable : 4464) // relative include path contains '..' +#endif +#include "../FileManager/StdAfx.h" diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/makefile b/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/makefile new file mode 100644 index 00000000..ff7c41a4 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/makefile @@ -0,0 +1,76 @@ +PROG = 7-zip.dll +DEF_FILE = Explorer.def +CFLAGS = $(CFLAGS) \ + -DZ7_LANG \ + +!IFDEF UNDER_CE +LIBS = $(LIBS) Commctrl.lib +!ELSE +LIBS = $(LIBS) htmlhelp.lib comdlg32.lib Mpr.lib Gdi32.lib +!ENDIF + +EXPLORER_OBJS = \ + $O\DllExportsExplorer.obj \ + $O\ContextMenu.obj \ + $O\MyMessages.obj \ + +COMMON_OBJS = \ + $O\IntToString.obj \ + $O\Lang.obj \ + $O\MyString.obj \ + $O\MyVector.obj \ + $O\NewHandler.obj \ + $O\Random.obj \ + $O\StringConvert.obj \ + $O\StringToInt.obj \ + $O\UTFConvert.obj \ + $O\Wildcard.obj \ + +WIN_OBJS = \ + $O\DLL.obj \ + $O\ErrorMsg.obj \ + $O\FileDir.obj \ + $O\FileFind.obj \ + $O\FileIO.obj \ + $O\FileName.obj \ + $O\MemoryLock.obj \ + $O\Menu.obj \ + $O\ProcessUtils.obj \ + $O\Registry.obj \ + $O\ResourceString.obj \ + $O\Shell.obj \ + $O\Synchronization.obj \ + $O\TimeUtils.obj \ + $O\Window.obj \ + +!IFDEF UNDER_CE + +WIN_OBJS = $(WIN_OBJS) \ + $O\CommonDialog.obj \ + +!ENDIF + +WIN_CTRL_OBJS = \ + $O\Dialog.obj \ + $O\ListView.obj \ + +UI_COMMON_OBJS = \ + $O\ArchiveName.obj \ + $O\CompressCall.obj \ + $O\ExtractingFilePath.obj \ + $O\ZipRegistry.obj \ + +FM_OBJS = \ + $O\FormatUtils.obj \ + $O\HelpUtils.obj \ + $O\LangUtils.obj \ + $O\ProgramLocation.obj \ + $O\PropertyName.obj \ + $O\RegistryUtils.obj \ + +C_OBJS = \ + $O\CpuArch.obj \ + $O\Threads.obj \ + +!include "../../Sort.mak" +!include "../../7zip.mak" diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/resource.h b/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/resource.h new file mode 100644 index 00000000..bbb28b16 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/resource.h @@ -0,0 +1,15 @@ +#define IDS_CONTEXT_FOLDER 2320 +#define IDS_CONTEXT_ARCHIVE 2321 +#define IDS_CONTEXT_OPEN 2322 +#define IDS_CONTEXT_EXTRACT 2323 +#define IDS_CONTEXT_COMPRESS 2324 +#define IDS_CONTEXT_TEST 2325 +#define IDS_CONTEXT_EXTRACT_HERE 2326 +#define IDS_CONTEXT_EXTRACT_TO 2327 +#define IDS_CONTEXT_COMPRESS_TO 2328 +#define IDS_CONTEXT_COMPRESS_EMAIL 2329 +#define IDS_CONTEXT_COMPRESS_TO_EMAIL 2330 + +#define IDS_SELECT_FILES 3015 + +#define IDB_MENU_LOGO 190 diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/resource.rc b/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/resource.rc new file mode 100644 index 00000000..acfa6e5a --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/resource.rc @@ -0,0 +1,10 @@ +#include "../../MyVersionInfo.rc" +#include "resource2.rc" + +MY_VERSION_INFO_DLL("7-Zip Shell Extension", "7-zip") + +#ifndef UNDER_CE +1 24 MOVEABLE PURE "7-zip.dll.manifest" +#endif + +IDI_ICON ICON "../FileManager/FM.ico" diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/resource2.rc b/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/resource2.rc new file mode 100644 index 00000000..de34824b --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Explorer/resource2.rc @@ -0,0 +1,19 @@ +#include "resource.h" + +STRINGTABLE +BEGIN + IDS_CONTEXT_FOLDER "" + IDS_CONTEXT_ARCHIVE "" + IDS_CONTEXT_OPEN "Open archive" + IDS_CONTEXT_EXTRACT "Extract files..." + IDS_CONTEXT_COMPRESS "Add to archive..." + IDS_CONTEXT_TEST "Test archive" + IDS_CONTEXT_EXTRACT_HERE "Extract Here" + IDS_CONTEXT_EXTRACT_TO "Extract to {0}" + IDS_CONTEXT_COMPRESS_TO "Add to {0}" + IDS_CONTEXT_COMPRESS_EMAIL "Compress and email..." + IDS_CONTEXT_COMPRESS_TO_EMAIL "Compress to {0} and email" + IDS_SELECT_FILES "You must select one or more files" +END + +IDB_MENU_LOGO BITMAP "../../UI/Explorer/MenuLogo.bmp" diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Far/ExtractEngine.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Far/ExtractEngine.cpp new file mode 100644 index 00000000..05e92081 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Far/ExtractEngine.cpp @@ -0,0 +1,274 @@ +// ExtractEngine.h + +#include "StdAfx.h" + +#ifndef Z7_ST +#include "../../../Windows/Synchronization.h" +#endif + +#include "../../../Common/StringConvert.h" + +#include "ExtractEngine.h" +#include "FarUtils.h" +#include "Messages.h" +#include "OverwriteDialogFar.h" + +using namespace NWindows; +using namespace NFar; + +#ifndef Z7_ST +static NSynchronization::CCriticalSection g_CriticalSection; +#define MT_LOCK NSynchronization::CCriticalSectionLock lock(g_CriticalSection); +#else +#define MT_LOCK +#endif + + +static HRESULT CheckBreak2() +{ + return WasEscPressed() ? E_ABORT : S_OK; +} + +extern void PrintMessage(const char *message); + +void CExtractCallbackImp::Init( + UINT codePage, + CProgressBox *progressBox, + bool passwordIsDefined, + const UString &password) +{ + m_PasswordIsDefined = passwordIsDefined; + m_Password = password; + m_CodePage = codePage; + _percent = progressBox; +} + +Z7_COM7F_IMF(CExtractCallbackImp::SetTotal(UInt64 size)) +{ + MT_LOCK + + if (_percent) + { + _percent->Total = size; + _percent->Print(); + } + return CheckBreak2(); +} + +Z7_COM7F_IMF(CExtractCallbackImp::SetCompleted(const UInt64 *completeValue)) +{ + MT_LOCK + + if (_percent) + { + if (completeValue) + _percent->Completed = *completeValue; + _percent->Print(); + } + return CheckBreak2(); +} + +Z7_COM7F_IMF(CExtractCallbackImp::AskOverwrite( + const wchar_t *existName, const FILETIME *existTime, const UInt64 *existSize, + const wchar_t *newName, const FILETIME *newTime, const UInt64 *newSize, + Int32 *answer)) +{ + MT_LOCK + + NOverwriteDialog::CFileInfo oldFileInfo, newFileInfo; + oldFileInfo.TimeIsDefined = (existTime != NULL); + if (oldFileInfo.TimeIsDefined) + oldFileInfo.Time = *existTime; + oldFileInfo.SizeIsDefined = (existSize != NULL); + if (oldFileInfo.SizeIsDefined) + oldFileInfo.Size = *existSize; + oldFileInfo.Name = existName; + + newFileInfo.TimeIsDefined = (newTime != NULL); + if (newFileInfo.TimeIsDefined) + newFileInfo.Time = *newTime; + newFileInfo.SizeIsDefined = (newSize != NULL); + if (newFileInfo.SizeIsDefined) + newFileInfo.Size = *newSize; + newFileInfo.Name = newName; + + NOverwriteDialog::NResult::EEnum result = + NOverwriteDialog::Execute(oldFileInfo, newFileInfo); + + switch ((int)result) + { + case NOverwriteDialog::NResult::kCancel: + // *answer = NOverwriteAnswer::kCancel; + // break; + return E_ABORT; + case NOverwriteDialog::NResult::kNo: + *answer = NOverwriteAnswer::kNo; + break; + case NOverwriteDialog::NResult::kNoToAll: + *answer = NOverwriteAnswer::kNoToAll; + break; + case NOverwriteDialog::NResult::kYesToAll: + *answer = NOverwriteAnswer::kYesToAll; + break; + case NOverwriteDialog::NResult::kYes: + *answer = NOverwriteAnswer::kYes; + break; + case NOverwriteDialog::NResult::kAutoRename: + *answer = NOverwriteAnswer::kAutoRename; + break; + default: + return E_FAIL; + } + + return CheckBreak2(); +} + +static const char * const kTestString = "Testing"; +static const char * const kExtractString = "Extracting"; +static const char * const kSkipString = "Skipping"; +static const char * const kReadString = "Reading"; + +Z7_COM7F_IMF(CExtractCallbackImp::PrepareOperation(const wchar_t *name, Int32 /* isFolder */, Int32 askExtractMode, const UInt64 * /* position */)) +{ + MT_LOCK + + m_CurrentFilePath = name; + const char *s; + + switch (askExtractMode) + { + case NArchive::NExtract::NAskMode::kExtract: s = kExtractString; break; + case NArchive::NExtract::NAskMode::kTest: s = kTestString; break; + case NArchive::NExtract::NAskMode::kSkip: s = kSkipString; break; + case NArchive::NExtract::NAskMode::kReadExternal: s = kReadString; break; + default: s = "???"; // return E_FAIL; + } + + if (_percent) + { + _percent->Command = s; + _percent->FileName = name; + _percent->Print(); + } + + return CheckBreak2(); +} + +Z7_COM7F_IMF(CExtractCallbackImp::MessageError(const wchar_t *message)) +{ + MT_LOCK + + AString s (UnicodeStringToMultiByte(message, CP_OEMCP)); + if (g_StartupInfo.ShowErrorMessage((const char *)s) == -1) + return E_ABORT; + + return CheckBreak2(); +} + +void SetExtractErrorMessage(Int32 opRes, Int32 encrypted, AString &s); +void SetExtractErrorMessage(Int32 opRes, Int32 encrypted, AString &s) +{ + s.Empty(); + + switch (opRes) + { + case NArchive::NExtract::NOperationResult::kOK: + return; + default: + { + UINT messageID = 0; + switch (opRes) + { + case NArchive::NExtract::NOperationResult::kUnsupportedMethod: + messageID = NMessageID::kExtractUnsupportedMethod; + break; + case NArchive::NExtract::NOperationResult::kCRCError: + messageID = encrypted ? + NMessageID::kExtractCRCFailedEncrypted : + NMessageID::kExtractCRCFailed; + break; + case NArchive::NExtract::NOperationResult::kDataError: + messageID = encrypted ? + NMessageID::kExtractDataErrorEncrypted : + NMessageID::kExtractDataError; + break; + } + if (messageID != 0) + { + s = g_StartupInfo.GetMsgString((int)messageID); + s.Replace((AString)" '%s'", AString()); + } + else if (opRes == NArchive::NExtract::NOperationResult::kUnavailable) + s = "Unavailable data"; + else if (opRes == NArchive::NExtract::NOperationResult::kUnexpectedEnd) + s = "Unexpected end of data"; + else if (opRes == NArchive::NExtract::NOperationResult::kDataAfterEnd) + s = "There are some data after the end of the payload data"; + else if (opRes == NArchive::NExtract::NOperationResult::kIsNotArc) + s = "Is not archive"; + else if (opRes == NArchive::NExtract::NOperationResult::kHeadersError) + s = "kHeaders Error"; + else if (opRes == NArchive::NExtract::NOperationResult::kWrongPassword) + s = "Wrong Password"; + else + { + s = "Error #"; + s.Add_UInt32((UInt32)opRes); + } + } + } +} + +Z7_COM7F_IMF(CExtractCallbackImp::SetOperationResult(Int32 opRes, Int32 encrypted)) +{ + MT_LOCK + + if (opRes == NArchive::NExtract::NOperationResult::kOK) + { + if (_percent) + { + _percent->Command.Empty(); + _percent->FileName.Empty(); + _percent->Files++; + } + } + else + { + AString s; + SetExtractErrorMessage(opRes, encrypted, s); + if (PrintErrorMessage(s, m_CurrentFilePath) == -1) + return E_ABORT; + } + + return CheckBreak2(); +} + + +Z7_COM7F_IMF(CExtractCallbackImp::ReportExtractResult(Int32 opRes, Int32 encrypted, const wchar_t *name)) +{ + MT_LOCK + + if (opRes != NArchive::NExtract::NOperationResult::kOK) + { + AString s; + SetExtractErrorMessage(opRes, encrypted, s); + if (PrintErrorMessage(s, name) == -1) + return E_ABORT; + } + + return CheckBreak2(); +} + +extern HRESULT GetPassword(UString &password); + +Z7_COM7F_IMF(CExtractCallbackImp::CryptoGetTextPassword(BSTR *password)) +{ + MT_LOCK + + if (!m_PasswordIsDefined) + { + RINOK(GetPassword(m_Password)) + m_PasswordIsDefined = true; + } + return StringToBstr(m_Password, password); +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Far/ExtractEngine.h b/src/plugins/7zip/7za/cpp/7zip/UI/Far/ExtractEngine.h new file mode 100644 index 00000000..2bee2ee4 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Far/ExtractEngine.h @@ -0,0 +1,43 @@ +// ExtractEngine.h + +#ifndef ZIP7_INC_EXTRACT_ENGINE_H +#define ZIP7_INC_EXTRACT_ENGINE_H + +#include "../../../Common/MyCom.h" +#include "../../../Common/MyString.h" + +#include "../../IPassword.h" +#include "../Agent/IFolderArchive.h" + +#include "ProgressBox.h" + +Z7_CLASS_IMP_COM_3( + CExtractCallbackImp + , IFolderArchiveExtractCallback + , IFolderArchiveExtractCallback2 + , ICryptoGetTextPassword +) + Z7_IFACE_COM7_IMP(IProgress) + + UString m_CurrentFilePath; + + CProgressBox *_percent; + UINT m_CodePage; + + bool m_PasswordIsDefined; + UString m_Password; + + void CreateComplexDirectory(const UStringVector &dirPathParts); + /* + void GetPropertyValue(LPITEMIDLIST anItemIDList, PROPID aPropId, + PROPVARIANT *aValue); + bool IsEncrypted(LPITEMIDLIST anItemIDList); + */ + void AddErrorMessage(LPCTSTR message); +public: + void Init(UINT codePage, + CProgressBox *progressBox, + bool passwordIsDefined, const UString &password); +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Far/Far.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Far/Far.cpp new file mode 100644 index 00000000..962af97b --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Far/Far.cpp @@ -0,0 +1,588 @@ +// Far.cpp +// Test Align for updating !!!!!!!!!!!!!!!!!! + +#include "StdAfx.h" + +#include "../../../Common/MyWindows.h" +#include "../../../Common/MyInitGuid.h" + +#include "../../../Common/StringConvert.h" + +#include "../../../Windows/FileDir.h" +#include "../../../Windows/NtCheck.h" + +#include "../../Common/FileStreams.h" + +#include "Messages.h" +#include "Plugin.h" +#include "ProgressBox.h" + +using namespace NWindows; +using namespace NFile; +using namespace NDir; +using namespace NFar; + +static const DWORD kShowProgressTime_ms = 100; + +static const char * const kCommandPrefix = "7-zip"; +static const char * const kRegisrtryMainKeyName = NULL; // "" +static LPCTSTR const kRegisrtryValueNameEnabled = TEXT("UsedByDefault3"); +static const char * const kHelpTopicConfig = "Config"; +static bool kPluginEnabledDefault = true; + +extern +HINSTANCE g_hInstance; +HINSTANCE g_hInstance; + +namespace NFar { + +extern +const char *g_PluginName_for_Error; +const char *g_PluginName_for_Error = "7-Zip"; + +} + +#if defined(_UNICODE) && !defined(_WIN64) && !defined(UNDER_CE) +#define NT_CHECK_FAIL_ACTION return FALSE; +#endif + +BOOL WINAPI DllMain( + #ifdef UNDER_CE + HANDLE + #else + HINSTANCE + #endif + hInstance, DWORD dwReason, LPVOID); +BOOL WINAPI DllMain( + #ifdef UNDER_CE + HANDLE + #else + HINSTANCE + #endif + hInstance, DWORD dwReason, LPVOID) +{ + if (dwReason == DLL_PROCESS_ATTACH) + { + // OutputDebugStringA("7-Zip FAR DLL_PROCESS_ATTACH"); + g_hInstance = (HINSTANCE)hInstance; + NT_CHECK + } + if (dwReason == DLL_PROCESS_DETACH) + { + // OutputDebugStringA("7-Zip FAR DLL_PROCESS_DETACH"); + } + return TRUE; +} + +static struct COptions +{ + bool Enabled; +} g_Options; + +static const char * const kPliginNameForRegistry = "7-ZIP"; + +EXTERN_C void WINAPI ExitFAR() +{ + /* WIN32: + it's not allowed to call FreeLibrary() from FreeLibrary(). + So we try to free all DLLs before destructors */ + // OutputDebugStringA("-- ExitFAR --- START"); + + FreeGlobalCodecs(); + + // OutputDebugStringA("-- ExitFAR --- END"); +} + +EXTERN_C void WINAPI SetStartupInfo(const PluginStartupInfo *info) +{ + MY_TRY_BEGIN + g_StartupInfo.Init(*info, kPliginNameForRegistry); + g_Options.Enabled = g_StartupInfo.QueryRegKeyValue( + HKEY_CURRENT_USER, kRegisrtryMainKeyName, + kRegisrtryValueNameEnabled, kPluginEnabledDefault); + + // OutputDebugStringA("SetStartupInfo"); + // LoadGlobalCodecs(); + + MY_TRY_END1("SetStartupInfo") +} + +Z7_CLASS_IMP_COM_3( + COpenArchiveCallback + , IArchiveOpenCallback + , IProgress + , ICryptoGetTextPassword +) + // DWORD m_StartTickValue; + bool m_MessageBoxIsShown; + + bool _numFilesTotalDefined; + bool _numBytesTotalDefined; +public: + bool PasswordIsDefined; + UString Password; + +private: + CProgressBox _progressBox; +public: + + COpenArchiveCallback() + {} + + void Init() + { + PasswordIsDefined = false; + + _numFilesTotalDefined = false; + _numBytesTotalDefined = false; + + m_MessageBoxIsShown = false; + + _progressBox.Init( + // g_StartupInfo.GetMsgString(NMessageID::kWaitTitle), + g_StartupInfo.GetMsgString(NMessageID::kReading)); + } + void ShowMessage(); +}; + +static HRESULT CheckBreak2() +{ + return WasEscPressed() ? E_ABORT : S_OK; +} + +void COpenArchiveCallback::ShowMessage() +{ + if (!m_MessageBoxIsShown) + { + DWORD currentTime = GetTickCount(); + if (currentTime - _progressBox.StartTick < kShowProgressTime_ms) + return; + m_MessageBoxIsShown = true; + } + + _progressBox.UseBytesForPercents = !_numFilesTotalDefined; + _progressBox.Print(); +} + +Z7_COM7F_IMF(COpenArchiveCallback::SetTotal(const UInt64 *numFiles, const UInt64 *numBytes)) +{ + _numFilesTotalDefined = (numFiles != NULL); + if (_numFilesTotalDefined) + _progressBox.FilesTotal = *numFiles; + + _numBytesTotalDefined = (numBytes != NULL); + if (_numBytesTotalDefined) + _progressBox.Total = *numBytes; + + return CheckBreak2(); +} + +Z7_COM7F_IMF(COpenArchiveCallback::SetCompleted(const UInt64 *numFiles, const UInt64 *numBytes)) +{ + if (numFiles) + _progressBox.Files = *numFiles; + + if (numBytes) + _progressBox.Completed = *numBytes; + + ShowMessage(); + return CheckBreak2(); +} + + +Z7_COM7F_IMF(COpenArchiveCallback::SetTotal(const UInt64 /* total */)) +{ + return CheckBreak2(); +} + +Z7_COM7F_IMF(COpenArchiveCallback::SetCompleted(const UInt64 * /* completed */)) +{ + ShowMessage(); + return CheckBreak2(); +} + +HRESULT GetPassword(UString &password); +HRESULT GetPassword(UString &password) +{ + if (WasEscPressed()) + return E_ABORT; + password.Empty(); + CInitDialogItem initItems[]= + { + { DI_DOUBLEBOX, 3, 1, 72, 4, false, false, 0, false, NMessageID::kGetPasswordTitle, NULL, NULL }, + { DI_TEXT, 5, 2, 0, 0, false, false, DIF_SHOWAMPERSAND, false, NMessageID::kEnterPasswordForFile, NULL, NULL }, + { DI_PSWEDIT, 5, 3, 70, 3, true, false, 0, true, -1, "", NULL } + }; + + const int kNumItems = Z7_ARRAY_SIZE(initItems); + FarDialogItem dialogItems[kNumItems]; + g_StartupInfo.InitDialogItems(initItems, dialogItems, kNumItems); + + // sprintf(DialogItems[1].Data,GetMsg(MGetPasswordForFile),FileName); + if (g_StartupInfo.ShowDialog(76, 6, NULL, dialogItems, kNumItems) < 0) + return E_ABORT; + + password = MultiByteToUnicodeString(dialogItems[2].Data, CP_OEMCP); + return S_OK; +} + +Z7_COM7F_IMF(COpenArchiveCallback::CryptoGetTextPassword(BSTR *password)) +{ + if (!PasswordIsDefined) + { + RINOK(GetPassword(Password)) + PasswordIsDefined = true; + } + return StringToBstr(Password, password); +} + +/* +HRESULT OpenArchive(const CSysString &fileName, + IInFolderArchive **archiveHandlerResult, + CArchiverInfo &archiverInfoResult, + UString &defaultName, + IArchiveOpenCallback *openArchiveCallback) +{ + HRESULT OpenArchive(const CSysString &fileName, + IInArchive **archive, + CArchiverInfo &archiverInfoResult, + IArchiveOpenCallback *openArchiveCallback); +} +*/ + +static HANDLE MyOpenFilePluginW(const wchar_t *name, bool isAbortCodeSupported) +{ + FString normalizedName = us2fs(name); + normalizedName.Trim(); + FString fullName; + MyGetFullPathName(normalizedName, fullName); + NFind::CFileInfo fileInfo; + if (!fileInfo.Find(fullName)) + return INVALID_HANDLE_VALUE; + if (fileInfo.IsDir()) + return INVALID_HANDLE_VALUE; + + + CMyComPtr archiveHandler; + + // CArchiverInfo archiverInfoResult; + // ::OutputDebugStringA("before OpenArchive\n"); + + CScreenRestorer screenRestorer; + { + screenRestorer.Save(); + } + + COpenArchiveCallback *openArchiveCallbackSpec = new COpenArchiveCallback; + CMyComPtr uiCallback = openArchiveCallbackSpec; + + /* COpenCallbackImp object will exist after Open stage for multivolume archioves */ + COpenCallbackImp *impSpec = new COpenCallbackImp; + CMyComPtr impCallback = impSpec; + impSpec->ReOpenCallback = openArchiveCallbackSpec; // we set pointer without reference counter + + // if ((opMode & OPM_SILENT) == 0 && (opMode & OPM_FIND ) == 0) + openArchiveCallbackSpec->Init(); + { + FString dirPrefix, fileName; + GetFullPathAndSplit(fullName, dirPrefix, fileName); + impSpec->Init2(dirPrefix, fileName); + } + + // ::OutputDebugStringA("before OpenArchive\n"); + + CAgent *agent = new CAgent; + archiveHandler = agent; + CMyComBSTR archiveType; + HRESULT result = archiveHandler->Open(NULL, + GetUnicodeString(fullName, CP_OEMCP), UString(), &archiveType, impCallback); + /* + HRESULT result = ::OpenArchive(fullName, &archiveHandler, + archiverInfoResult, defaultName, openArchiveCallback); + */ + if (result == E_ABORT) + { + // fixed 18.06: + // OpenFilePlugin() is allowed to return (HANDLE)-2 as abort code + // OpenPlugin() is not allowed to return (HANDLE)-2. + return isAbortCodeSupported ? (HANDLE)-2 : INVALID_HANDLE_VALUE; + } + + UString errorMessage = agent->GetErrorMessage(); + if (!errorMessage.IsEmpty()) + g_StartupInfo.ShowErrorMessage(UnicodeStringToMultiByte(errorMessage, CP_OEMCP)); + + if (result != S_OK) + { + if (result == S_FALSE) + return INVALID_HANDLE_VALUE; + ShowSysErrorMessage(result); + return INVALID_HANDLE_VALUE; + } + + // ::OutputDebugStringA("after OpenArchive\n"); + + CPlugin *plugin = new CPlugin( + fullName, + // defaultName, + agent, + (const wchar_t *)archiveType + ); + + plugin->PasswordIsDefined = openArchiveCallbackSpec->PasswordIsDefined; + plugin->Password = openArchiveCallbackSpec->Password; + + // OutputDebugStringA("--- OpenFilePlugin ---- END"); + return (HANDLE)(plugin); +} + +static HANDLE MyOpenFilePlugin(const char *name, bool isAbortCodeSupported) +{ + UINT codePage = + #ifdef UNDER_CE + CP_OEMCP; + #else + ::AreFileApisANSI() ? CP_ACP : CP_OEMCP; + #endif + return MyOpenFilePluginW(GetUnicodeString(name, codePage), isAbortCodeSupported); +} + +EXTERN_C HANDLE WINAPI OpenFilePlugin(char *name, const Byte * /* data */, int /* dataSize */) +{ + MY_TRY_BEGIN + // OutputDebugStringA("--- OpenFilePlugin"); + if (name == NULL || (!g_Options.Enabled)) + { + // if (!Opt.ProcessShiftF1) + return(INVALID_HANDLE_VALUE); + } + return MyOpenFilePlugin(name, true); // isAbortCodeSupported + MY_TRY_END2("OpenFilePlugin", INVALID_HANDLE_VALUE) +} + +/* +EXTERN_C HANDLE WINAPI OpenFilePluginW(const wchar_t *name,const Byte *Data,int DataSize,int OpMode) +{ + MY_TRY_BEGIN + if (name == NULL || (!g_Options.Enabled)) + { + // if (!Opt.ProcessShiftF1) + return(INVALID_HANDLE_VALUE); + } + return MyOpenFilePluginW(name); + ::OutputDebugStringA("OpenFilePluginW\n"); + MY_TRY_END2("OpenFilePluginW", INVALID_HANDLE_VALUE); +} +*/ + +EXTERN_C HANDLE WINAPI OpenPlugin(int openFrom, INT_PTR item) +{ + MY_TRY_BEGIN + + if (openFrom == OPEN_COMMANDLINE) + { + AString fileName ((const char *)item); + if (fileName.IsEmpty()) + return INVALID_HANDLE_VALUE; + if (fileName.Len() >= 2 + && fileName[0] == '\"' + && fileName.Back() == '\"') + { + fileName.DeleteBack(); + fileName.DeleteFrontal(1); + } + return MyOpenFilePlugin(fileName, false); // isAbortCodeSupported + } + + if (openFrom == OPEN_PLUGINSMENU) + { + switch (item) + { + case 0: + { + PluginPanelItem pluginPanelItem; + if (!g_StartupInfo.ControlGetActivePanelCurrentItemInfo(pluginPanelItem)) + throw 142134; + return MyOpenFilePlugin(pluginPanelItem.FindData.cFileName, false); // isAbortCodeSupported + } + + case 1: + { + CObjectVector pluginPanelItem; + if (!g_StartupInfo.ControlGetActivePanelSelectedOrCurrentItems(pluginPanelItem)) + throw 142134; + HRESULT res = CompressFiles(pluginPanelItem); + if (res != S_OK && res != E_ABORT) + { + ShowSysErrorMessage(res); + } + // if (res == S_OK) + { + /* int t = */ g_StartupInfo.ControlClearPanelSelection(); + g_StartupInfo.ControlRequestActivePanel(FCTL_UPDATEPANEL, NULL); + g_StartupInfo.ControlRequestActivePanel(FCTL_REDRAWPANEL, NULL); + g_StartupInfo.ControlRequestActivePanel(FCTL_UPDATEANOTHERPANEL, NULL); + g_StartupInfo.ControlRequestActivePanel(FCTL_REDRAWANOTHERPANEL, NULL); + } + return INVALID_HANDLE_VALUE; + } + + default: + throw 4282215; + } + } + + return INVALID_HANDLE_VALUE; + MY_TRY_END2("OpenPlugin", INVALID_HANDLE_VALUE) +} + +EXTERN_C void WINAPI ClosePlugin(HANDLE plugin) +{ + // OutputDebugStringA("-- ClosePlugin --- START"); + // MY_TRY_BEGIN + delete (CPlugin *)plugin; + // OutputDebugStringA("-- ClosePlugin --- END"); + // MY_TRY_END1("ClosePlugin"); +} + +EXTERN_C int WINAPI GetFindData(HANDLE plugin, struct PluginPanelItem **panelItems, int *itemsNumber, int opMode) +{ + MY_TRY_BEGIN + return(((CPlugin *)plugin)->GetFindData(panelItems, itemsNumber, opMode)); + MY_TRY_END2("GetFindData", FALSE) +} + +EXTERN_C void WINAPI FreeFindData(HANDLE plugin, struct PluginPanelItem *panelItems, int itemsNumber) +{ + // MY_TRY_BEGIN + ((CPlugin *)plugin)->FreeFindData(panelItems, itemsNumber); + // MY_TRY_END1("FreeFindData"); +} + +EXTERN_C int WINAPI GetFiles(HANDLE plugin, struct PluginPanelItem *panelItems, + int itemsNumber, int move, char *destPath, int opMode) +{ + MY_TRY_BEGIN + return(((CPlugin *)plugin)->GetFiles(panelItems, (unsigned)itemsNumber, move, destPath, opMode)); + MY_TRY_END2("GetFiles", NFileOperationReturnCode::kError) +} + +EXTERN_C int WINAPI SetDirectory(HANDLE plugin, const char *dir, int opMode) +{ + MY_TRY_BEGIN + return(((CPlugin *)plugin)->SetDirectory(dir, opMode)); + MY_TRY_END2("SetDirectory", FALSE) +} + +EXTERN_C void WINAPI GetPluginInfo(struct PluginInfo *info) +{ + MY_TRY_BEGIN + + info->StructSize = sizeof(*info); + info->Flags = 0; + info->DiskMenuStrings = NULL; + info->DiskMenuNumbers = NULL; + info->DiskMenuStringsNumber = 0; + static char *pluginMenuStrings[2]; + pluginMenuStrings[0] = const_cast(g_StartupInfo.GetMsgString(NMessageID::kOpenArchiveMenuString)); + pluginMenuStrings[1] = const_cast(g_StartupInfo.GetMsgString(NMessageID::kCreateArchiveMenuString)); + info->PluginMenuStrings = (char **)pluginMenuStrings; + info->PluginMenuStringsNumber = 2; + static char *pluginCfgStrings[1]; + pluginCfgStrings[0] = const_cast(g_StartupInfo.GetMsgString(NMessageID::kOpenArchiveMenuString)); + info->PluginConfigStrings = (char **)pluginCfgStrings; + info->PluginConfigStringsNumber = Z7_ARRAY_SIZE(pluginCfgStrings); + info->CommandPrefix = const_cast(kCommandPrefix); + MY_TRY_END1("GetPluginInfo") +} + +EXTERN_C int WINAPI Configure(int /* itemNumber */) +{ + MY_TRY_BEGIN + + const int kEnabledCheckBoxIndex = 1; + + const int kYSize = 7; + + struct CInitDialogItem initItems[]= + { + { DI_DOUBLEBOX, 3, 1, 72, kYSize - 2, false, false, 0, false, NMessageID::kConfigTitle, NULL, NULL }, + { DI_CHECKBOX, 5, 2, 0, 0, true, g_Options.Enabled, 0, false, NMessageID::kConfigPluginEnabled, NULL, NULL }, + { DI_TEXT, 5, 3, 0, 0, false, false, DIF_BOXCOLOR | DIF_SEPARATOR, false, -1, "", NULL }, + { DI_BUTTON, 0, kYSize - 3, 0, 0, false, false, DIF_CENTERGROUP, true, NMessageID::kOk, NULL, NULL }, + { DI_BUTTON, 0, kYSize - 3, 0, 0, false, false, DIF_CENTERGROUP, false, NMessageID::kCancel, NULL, NULL }, + }; + + const int kNumDialogItems = Z7_ARRAY_SIZE(initItems); + const int kOkButtonIndex = kNumDialogItems - 2; + + FarDialogItem dialogItems[kNumDialogItems]; + g_StartupInfo.InitDialogItems(initItems, dialogItems, kNumDialogItems); + + int askCode = g_StartupInfo.ShowDialog(76, kYSize, + kHelpTopicConfig, dialogItems, kNumDialogItems); + + if (askCode != kOkButtonIndex) + return (FALSE); + + g_Options.Enabled = BOOLToBool(dialogItems[kEnabledCheckBoxIndex].Selected); + + g_StartupInfo.SetRegKeyValue(HKEY_CURRENT_USER, kRegisrtryMainKeyName, + kRegisrtryValueNameEnabled, g_Options.Enabled); + return(TRUE); + MY_TRY_END2("Configure", FALSE) +} + +EXTERN_C void WINAPI GetOpenPluginInfo(HANDLE plugin,struct OpenPluginInfo *info) +{ + MY_TRY_BEGIN + ((CPlugin *)plugin)->GetOpenPluginInfo(info); + MY_TRY_END1("GetOpenPluginInfo") +} + +EXTERN_C int WINAPI PutFiles(HANDLE plugin, struct PluginPanelItem *panelItems, int itemsNumber, int move, int opMode) +{ + MY_TRY_BEGIN + return (((CPlugin *)plugin)->PutFiles(panelItems, (unsigned)itemsNumber, move, opMode)); + MY_TRY_END2("PutFiles", NFileOperationReturnCode::kError) +} + +EXTERN_C int WINAPI DeleteFiles(HANDLE plugin, PluginPanelItem *panelItems, int itemsNumber, int opMode) +{ + MY_TRY_BEGIN + return (((CPlugin *)plugin)->DeleteFiles(panelItems, (unsigned)itemsNumber, opMode)); + MY_TRY_END2("DeleteFiles", FALSE) +} + +EXTERN_C int WINAPI ProcessKey(HANDLE plugin, int key, unsigned controlState) +{ + MY_TRY_BEGIN + /* FIXME: after folder creation with F7, it doesn't reload new file list + We need some to reload it */ + return (((CPlugin *)plugin)->ProcessKey(key, controlState)); + MY_TRY_END2("ProcessKey", FALSE) +} + +/* +struct MakeDirectoryInfo +{ + size_t StructSize; + HANDLE hPanel; + const wchar_t *Name; + OPERATION_MODES OpMode; + void* Instance; +}; + +typedef INT_PTR MY_intptr_t; + +MY_intptr_t WINAPI MakeDirectoryW(struct MakeDirectoryInfo *Info) +{ + MY_TRY_BEGIN + if (Info->StructSize < sizeof(MakeDirectoryInfo)) + { + return 0; + } + return 0; + MY_TRY_END2("MakeDirectoryW", FALSE); +} +*/ diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Far/Far.def b/src/plugins/7zip/7za/cpp/7zip/UI/Far/Far.def new file mode 100644 index 00000000..1de9acdf --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Far/Far.def @@ -0,0 +1,35 @@ +; 7-ZipFar.def : Declares the module parameters for the DLL. + +LIBRARY "7-ZipFar" + +EXPORTS + ExitFAR + SetStartupInfo + OpenPlugin + OpenFilePlugin + ClosePlugin + GetFindData + FreeFindData + SetDirectory + GetPluginInfo + Configure + GetOpenPluginInfo + GetFiles + PutFiles + DeleteFiles + ProcessKey + + ;SetStartupInfoW + ;OpenPluginW + ;OpenFilePluginW + ;ClosePluginW + ;GetFindDataW + ;FreeFindDataW + ;SetDirectoryW + ;GetPluginInfoW + ;ConfigureW + ;GetOpenPluginInfoW + ;GetFilesW + ;PutFilesW + ;DeleteFilesW + ;ProcessKeyW diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Far/Far.dsp b/src/plugins/7zip/7za/cpp/7zip/UI/Far/Far.dsp new file mode 100644 index 00000000..823c728d --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Far/Far.dsp @@ -0,0 +1,831 @@ +# Microsoft Developer Studio Project File - Name="Far" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=Far - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "Far.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "Far.mak" CFG="Far - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "Far - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "Far - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "Far - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 1 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "FAR_EXPORTS" /YX /FD /c +# ADD CPP /nologo /Gz /MD /W4 /WX /GX /O1 /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "FAR_EXPORTS" /D "Z7_EXTERNAL_CODECS" /Yu"StdAfx.h" /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "NDEBUG" +# ADD RSC /l 0x419 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 /out:"C:\Progs\Far\Plugins\7-Zip\7-ZipFar.dll" /opt:NOWIN98 +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "Far - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 1 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "FAR_EXPORTS" /YX /FD /GZ /c +# ADD CPP /nologo /Gz /MTd /W4 /WX /Gm /GX /ZI /Od /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "FAR_EXPORTS" /D "Z7_EXTERNAL_CODECS" /Yu"StdAfx.h" /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "_DEBUG" +# ADD RSC /l 0x419 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /out:"C:\Progs\Far\Plugins\7-Zip\7-ZipFar.dll" /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "Far - Win32 Release" +# Name "Far - Win32 Debug" +# Begin Group "Spec" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=.\Far.def +# End Source File +# Begin Source File + +SOURCE=.\resource.rc +# End Source File +# Begin Source File + +SOURCE=.\StdAfx.cpp +# ADD CPP /Yc"StdAfx.h" +# End Source File +# Begin Source File + +SOURCE=.\StdAfx.h +# End Source File +# End Group +# Begin Group "Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\Common\Common.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\CRC.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\DynLimBuf.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\DynLimBuf.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\IntToString.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\IntToString.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyCom.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyString.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyString.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyTypes.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyVector.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyVector.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyWindows.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\NewHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\NewHandler.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StringConvert.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StringConvert.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StringToInt.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StringToInt.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\UTFConvert.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\UTFConvert.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Wildcard.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Wildcard.h +# End Source File +# End Group +# Begin Group "Plugin" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=.\ExtractEngine.cpp +# End Source File +# Begin Source File + +SOURCE=.\ExtractEngine.h +# End Source File +# Begin Source File + +SOURCE=.\Far.cpp +# End Source File +# Begin Source File + +SOURCE=.\Messages.h +# End Source File +# Begin Source File + +SOURCE=.\OverwriteDialogFar.cpp +# End Source File +# Begin Source File + +SOURCE=.\OverwriteDialogFar.h +# End Source File +# Begin Source File + +SOURCE=.\Plugin.cpp +# End Source File +# Begin Source File + +SOURCE=.\Plugin.h +# End Source File +# Begin Source File + +SOURCE=.\PluginDelete.cpp +# End Source File +# Begin Source File + +SOURCE=.\PluginRead.cpp +# End Source File +# Begin Source File + +SOURCE=.\PluginWrite.cpp +# End Source File +# Begin Source File + +SOURCE=.\resource.h +# End Source File +# Begin Source File + +SOURCE=.\UpdateCallbackFar.cpp +# End Source File +# Begin Source File + +SOURCE=.\UpdateCallbackFar.h +# End Source File +# End Group +# Begin Group "Far" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=.\FarPlugin.h +# End Source File +# Begin Source File + +SOURCE=.\FarUtils.cpp +# End Source File +# Begin Source File + +SOURCE=.\FarUtils.h +# End Source File +# Begin Source File + +SOURCE=.\ProgressBox.cpp +# End Source File +# Begin Source File + +SOURCE=.\ProgressBox.h +# End Source File +# End Group +# Begin Group "Windows" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\Windows\Defs.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\DLL.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\DLL.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\ErrorMsg.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\ErrorMsg.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileDir.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileDir.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileFind.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileFind.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileIO.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileIO.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileLink.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileName.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileName.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileSystem.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileSystem.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\PropVariant.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\PropVariant.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\PropVariantConv.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\PropVariantConv.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Registry.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Registry.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\ResourceString.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\ResourceString.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Synchronization.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Synchronization.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\System.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\System.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\TimeUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\TimeUtils.h +# End Source File +# End Group +# Begin Group "UI Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\Common\ArchiveExtractCallback.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\ArchiveExtractCallback.h +# End Source File +# Begin Source File + +SOURCE=..\Common\ArchiveOpenCallback.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\ArchiveOpenCallback.h +# End Source File +# Begin Source File + +SOURCE=..\Common\DefaultName.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\DefaultName.h +# End Source File +# Begin Source File + +SOURCE=..\Common\DirItem.h +# End Source File +# Begin Source File + +SOURCE=..\Common\EnumDirItems.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\EnumDirItems.h +# End Source File +# Begin Source File + +SOURCE=..\Common\ExtractingFilePath.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\ExtractingFilePath.h +# End Source File +# Begin Source File + +SOURCE=..\Common\ExtractMode.h +# End Source File +# Begin Source File + +SOURCE=..\Common\HandlerLoader.h +# End Source File +# Begin Source File + +SOURCE=..\Common\HashCalc.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\HashCalc.h +# End Source File +# Begin Source File + +SOURCE=..\Common\LoadCodecs.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\LoadCodecs.h +# End Source File +# Begin Source File + +SOURCE=..\Common\OpenArchive.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\OpenArchive.h +# End Source File +# Begin Source File + +SOURCE=..\Common\PropIDUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\PropIDUtils.h +# End Source File +# Begin Source File + +SOURCE=..\Common\SetProperties.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\SetProperties.h +# End Source File +# Begin Source File + +SOURCE=..\Common\SortUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\SortUtils.h +# End Source File +# Begin Source File + +SOURCE=..\Common\UpdateAction.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\UpdateAction.h +# End Source File +# Begin Source File + +SOURCE=..\Common\UpdateCallback.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\UpdateCallback.h +# End Source File +# Begin Source File + +SOURCE=..\Common\UpdatePair.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\UpdatePair.h +# End Source File +# Begin Source File + +SOURCE=..\Common\UpdateProduce.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\UpdateProduce.h +# End Source File +# Begin Source File + +SOURCE=..\Common\WorkDir.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\WorkDir.h +# End Source File +# Begin Source File + +SOURCE=..\Common\ZipRegistry.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\ZipRegistry.h +# End Source File +# End Group +# Begin Group "Agent" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\Agent\Agent.cpp +# End Source File +# Begin Source File + +SOURCE=..\Agent\Agent.h +# End Source File +# Begin Source File + +SOURCE=..\Agent\AgentOut.cpp +# End Source File +# Begin Source File + +SOURCE=..\Agent\AgentProxy.cpp +# End Source File +# Begin Source File + +SOURCE=..\Agent\AgentProxy.h +# End Source File +# Begin Source File + +SOURCE=..\Agent\ArchiveFolder.cpp +# End Source File +# Begin Source File + +SOURCE=..\Agent\ArchiveFolderOut.cpp +# End Source File +# Begin Source File + +SOURCE=..\Agent\IFolderArchive.h +# End Source File +# Begin Source File + +SOURCE=..\Agent\UpdateCallbackAgent.cpp +# End Source File +# Begin Source File + +SOURCE=..\Agent\UpdateCallbackAgent.h +# End Source File +# End Group +# Begin Group "Compress" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Compress\CopyCoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\CopyCoder.h +# End Source File +# End Group +# Begin Group "7-zip Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Common\CreateCoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\CreateCoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FilePathAutoRename.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FilePathAutoRename.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FileStreams.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FileStreams.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FilterCoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FilterCoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\LimitedStreams.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\LimitedStreams.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\MethodProps.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\MethodProps.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\ProgressUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\ProgressUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\PropId.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamObjects.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamObjects.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\UniqBlocks.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\UniqBlocks.h +# End Source File +# End Group +# Begin Group "C" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\..\C\7zCrc.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zCrc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zCrcOpt.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zTypes.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zWindows.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Alloc.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Alloc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Compiler.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\CpuArch.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Sort.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Sort.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Threads.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Threads.h +# End Source File +# End Group +# Begin Group "Arc Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Archive\Common\ItemNameUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\ItemNameUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\OutStreamWithCRC.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\OutStreamWithCRC.h +# End Source File +# End Group +# Begin Group "Interface" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Archive\IArchive.h +# End Source File +# Begin Source File + +SOURCE=..\..\ICoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\IDecl.h +# End Source File +# Begin Source File + +SOURCE=..\Common\IFileExtractCallback.h +# End Source File +# Begin Source File + +SOURCE=..\FileManager\IFolder.h +# End Source File +# Begin Source File + +SOURCE=..\..\IPassword.h +# End Source File +# Begin Source File + +SOURCE=..\..\IProgress.h +# End Source File +# Begin Source File + +SOURCE=..\..\PropID.h +# End Source File +# End Group +# End Target +# End Project diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Far/Far.dsw b/src/plugins/7zip/7za/cpp/7zip/UI/Far/Far.dsw new file mode 100644 index 00000000..f4ef0801 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Far/Far.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "Far"=.\Far.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Far/FarPlugin.h b/src/plugins/7zip/7za/cpp/7zip/UI/Far/FarPlugin.h new file mode 100644 index 00000000..68c767b3 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Far/FarPlugin.h @@ -0,0 +1,560 @@ +// FarPlugin.h + +// #include "plugin.hpp" + +const int kInfoPanelLineSize = 80; + +// #define __FAR_PLUGIN_H + +#ifdef UNDER_CE +typedef struct { + union { + WCHAR UnicodeChar; + CHAR AsciiChar; + } Char; + WORD Attributes; +} CHAR_INFO, *PCHAR_INFO; +#endif + +#ifndef ZIP7_INC_FAR_PLUGIN_H +#define ZIP7_INC_FAR_PLUGIN_H + +#ifndef _WIN64 +#if defined(__BORLANDC__) && (__BORLANDC <= 0x520) + #pragma option -a1 +#elif defined(__GNUC__) || (defined(__WATCOMC__) && (__WATCOMC__ < 1100)) + #pragma pack(1) +#else + #pragma pack(push,1) +#endif +#endif + + // #if _MSC_VER + #define _export + // #endif + +#define NM 260 + +struct FarFindData +{ + DWORD dwFileAttributes; + FILETIME ftCreationTime; + FILETIME ftLastAccessTime; + FILETIME ftLastWriteTime; + DWORD nFileSizeHigh; + DWORD nFileSizeLow; + DWORD dwReserved0; + DWORD dwReserved1; + char cFileName[ MAX_PATH ]; + char cAlternateFileName[ 14 ]; +}; + +struct PluginPanelItem +{ + FarFindData FindData; + DWORD PackSizeHigh; + DWORD PackSize; + DWORD Flags; + DWORD NumberOfLinks; + char *Description; + char *Owner; + char **CustomColumnData; + int CustomColumnNumber; + DWORD_PTR UserData; + DWORD CRC32; + DWORD_PTR Reserved[2]; +}; + +#define PPIF_PROCESSDESCR 0x80000000 +#define PPIF_SELECTED 0x40000000 +#define PPIF_USERDATA 0x20000000 + +enum { + FMENU_SHOWAMPERSAND=1, + FMENU_WRAPMODE=2, + FMENU_AUTOHIGHLIGHT=4, + FMENU_REVERSEAUTOHIGHLIGHT=8 +}; + + +typedef int (WINAPI *FARAPIMENU)( + INT_PTR PluginNumber, + int X, + int Y, + int MaxHeight, + unsigned Flags, + char *Title, + char *Bottom, + char *HelpTopic, + int *BreakKeys, + int *BreakCode, + struct FarMenuItem *Item, + int ItemsNumber +); + +typedef int (WINAPI *FARAPIDIALOG)( + INT_PTR PluginNumber, + int X1, + int Y1, + int X2, + int Y2, + char *HelpTopic, + struct FarDialogItem *Item, + int ItemsNumber +); + +enum { + FMSG_WARNING = 0x00000001, + FMSG_ERRORTYPE = 0x00000002, + FMSG_KEEPBACKGROUND = 0x00000004, + FMSG_DOWN = 0x00000008, + FMSG_LEFTALIGN = 0x00000010, + + FMSG_ALLINONE = 0x00000020, + + FMSG_MB_OK = 0x00010000, + FMSG_MB_OKCANCEL = 0x00020000, + FMSG_MB_ABORTRETRYIGNORE = 0x00030000, + FMSG_MB_YESNO = 0x00040000, + FMSG_MB_YESNOCANCEL = 0x00050000, + FMSG_MB_RETRYCANCEL = 0x00060000 +}; + +typedef int (WINAPI *FARAPIMESSAGE)( + INT_PTR PluginNumber, + unsigned Flags, + const char *HelpTopic, + const char * const *Items, + int ItemsNumber, + int ButtonsNumber +); + +typedef char* (WINAPI *FARAPIGETMSG)( + INT_PTR PluginNumber, + int MsgId +); + + +enum DialogItemTypes { + DI_TEXT, + DI_VTEXT, + DI_SINGLEBOX, + DI_DOUBLEBOX, + DI_EDIT, + DI_PSWEDIT, + DI_FIXEDIT, + DI_BUTTON, + DI_CHECKBOX, + DI_RADIOBUTTON +}; + +enum FarDialogItemFlags { + DIF_COLORMASK = 0xff, + DIF_SETCOLOR = 0x100, + DIF_BOXCOLOR = 0x200, + DIF_GROUP = 0x400, + DIF_LEFTTEXT = 0x800, + DIF_MOVESELECT = 0x1000, + DIF_SHOWAMPERSAND = 0x2000, + DIF_CENTERGROUP = 0x4000, + DIF_NOBRACKETS = 0x8000, + DIF_SEPARATOR = 0x10000, + DIF_EDITOR = 0x20000, + DIF_HISTORY = 0x40000 +}; + +struct FarDialogItem +{ + int Type; + int X1,Y1,X2,Y2; + int Focus; + union + { + int Selected; + const char *History; + const char *Mask; + struct FarList *ListItems; + int ListPos; + CHAR_INFO *VBuf; + }; + unsigned Flags; + int DefaultButton; + char Data[512]; +}; + + +struct FarMenuItem +{ + char Text[128]; + int Selected; + int Checked; + int Separator; +}; + + +enum {FCTL_CLOSEPLUGIN,FCTL_GETPANELINFO,FCTL_GETANOTHERPANELINFO, + FCTL_UPDATEPANEL,FCTL_UPDATEANOTHERPANEL, + FCTL_REDRAWPANEL,FCTL_REDRAWANOTHERPANEL, + FCTL_SETANOTHERPANELDIR,FCTL_GETCMDLINE,FCTL_SETCMDLINE, + FCTL_SETSELECTION,FCTL_SETANOTHERSELECTION, + FCTL_SETVIEWMODE,FCTL_SETANOTHERVIEWMODE,FCTL_INSERTCMDLINE, + FCTL_SETUSERSCREEN,FCTL_SETPANELDIR,FCTL_SETCMDLINEPOS, + FCTL_GETCMDLINEPOS +}; + +enum {PTYPE_FILEPANEL,PTYPE_TREEPANEL,PTYPE_QVIEWPANEL,PTYPE_INFOPANEL}; + +struct PanelInfo +{ + int PanelType; + int Plugin; + RECT PanelRect; + struct PluginPanelItem *PanelItems; + int ItemsNumber; + struct PluginPanelItem *SelectedItems; + int SelectedItemsNumber; + int CurrentItem; + int TopPanelItem; + int Visible; + int Focus; + int ViewMode; + char ColumnTypes[80]; + char ColumnWidths[80]; + char CurDir[NM]; + int ShortNames; + int SortMode; + DWORD Flags; + DWORD Reserved; +}; + + +struct PanelRedrawInfo +{ + int CurrentItem; + int TopPanelItem; +}; + + +typedef int (WINAPI *FARAPICONTROL)( + HANDLE hPlugin, + int Command, + void *Param +); + +typedef HANDLE (WINAPI *FARAPISAVESCREEN)(int X1,int Y1,int X2,int Y2); + +typedef void (WINAPI *FARAPIRESTORESCREEN)(HANDLE hScreen); + +typedef int (WINAPI *FARAPIGETDIRLIST)( + char *Dir, + struct PluginPanelItem **pPanelItem, + int *pItemsNumber +); + +typedef int (WINAPI *FARAPIGETPLUGINDIRLIST)( + INT_PTR PluginNumber, + HANDLE hPlugin, + char *Dir, + struct PluginPanelItem **pPanelItem, + int *pItemsNumber +); + +typedef void (WINAPI *FARAPIFREEDIRLIST)(struct PluginPanelItem *PanelItem); + +enum VIEWER_FLAGS { + VF_NONMODAL=1,VF_DELETEONCLOSE=2 +}; + +typedef int (WINAPI *FARAPIVIEWER)( + char *FileName, + char *Title, + int X1, + int Y1, + int X2, + int Y2, + DWORD Flags +); + +typedef int (WINAPI *FARAPIEDITOR)( + char *FileName, + char *Title, + int X1, + int Y1, + int X2, + int Y2, + DWORD Flags, + int StartLine, + int StartChar +); + +typedef int (WINAPI *FARAPICMPNAME)( + char *Pattern, + char *String, + int SkipPath +); + + +#define FCT_DETECT 0x40000000 + +struct CharTableSet +{ + char DecodeTable[256]; + char EncodeTable[256]; + char UpperTable[256]; + char LowerTable[256]; + char TableName[128]; +}; + +typedef int (WINAPI *FARAPICHARTABLE)( + int Command, + char *Buffer, + int BufferSize +); + +typedef void (WINAPI *FARAPITEXT)( + int X, + int Y, + int Color, + char *Str +); + + +typedef int (WINAPI *FARAPIEDITORCONTROL)( + int Command, + void *Param +); + +struct PluginStartupInfo +{ + int StructSize; + char ModuleName[NM]; + INT_PTR ModuleNumber; + char *RootKey; + FARAPIMENU Menu; + FARAPIDIALOG Dialog; + FARAPIMESSAGE Message; + FARAPIGETMSG GetMsg; + FARAPICONTROL Control; + FARAPISAVESCREEN SaveScreen; + FARAPIRESTORESCREEN RestoreScreen; + FARAPIGETDIRLIST GetDirList; + FARAPIGETPLUGINDIRLIST GetPluginDirList; + FARAPIFREEDIRLIST FreeDirList; + FARAPIVIEWER Viewer; + FARAPIEDITOR Editor; + FARAPICMPNAME CmpName; + FARAPICHARTABLE CharTable; + FARAPITEXT Text; + FARAPIEDITORCONTROL EditorControl; +}; + + +enum PLUGIN_FLAGS { + PF_PRELOAD = 0x0001, + PF_DISABLEPANELS = 0x0002, + PF_EDITOR = 0x0004, + PF_VIEWER = 0x0008 +}; + + +struct PluginInfo +{ + int StructSize; + DWORD Flags; + char **DiskMenuStrings; + int *DiskMenuNumbers; + int DiskMenuStringsNumber; + char **PluginMenuStrings; + int PluginMenuStringsNumber; + char **PluginConfigStrings; + int PluginConfigStringsNumber; + char *CommandPrefix; +}; + +struct InfoPanelLine +{ + char Text[kInfoPanelLineSize]; + char Data[kInfoPanelLineSize]; + int Separator; +}; + + +struct PanelMode +{ + char *ColumnTypes; + char *ColumnWidths; + char **ColumnTitles; + int FullScreen; + int DetailedStatus; + int AlignExtensions; + int CaseConversion; + char *StatusColumnTypes; + char *StatusColumnWidths; + DWORD Reserved[2]; +}; + + +enum OPENPLUGININFO_FLAGS { + OPIF_USEFILTER = 0x0001, + OPIF_USESORTGROUPS = 0x0002, + OPIF_USEHIGHLIGHTING = 0x0004, + OPIF_ADDDOTS = 0x0008, + OPIF_RAWSELECTION = 0x0010, + OPIF_REALNAMES = 0x0020, + OPIF_SHOWNAMESONLY = 0x0040, + OPIF_SHOWRIGHTALIGNNAMES = 0x0080, + OPIF_SHOWPRESERVECASE = 0x0100, + OPIF_FINDFOLDERS = 0x0200, + OPIF_COMPAREFATTIME = 0x0400, + OPIF_EXTERNALGET = 0x0800, + OPIF_EXTERNALPUT = 0x1000, + OPIF_EXTERNALDELETE = 0x2000, + OPIF_EXTERNALMKDIR = 0x4000, + OPIF_USEATTRHIGHLIGHTING = 0x8000 +}; + + +enum OPENPLUGININFO_SORTMODES { + SM_DEFAULT,SM_UNSORTED,SM_NAME,SM_EXT,SM_MTIME,SM_CTIME, + SM_ATIME,SM_SIZE,SM_DESCR,SM_OWNER,SM_COMPRESSEDSIZE,SM_NUMLINKS +}; + + +struct KeyBarTitles +{ + char *Titles[12]; + char *CtrlTitles[12]; + char *AltTitles[12]; + char *ShiftTitles[12]; +}; + + +struct OpenPluginInfo +{ + int StructSize; + DWORD Flags; + const char *HostFile; + const char *CurDir; + const char *Format; + const char *PanelTitle; + const struct InfoPanelLine *InfoLines; + int InfoLinesNumber; + const char * const *DescrFiles; + int DescrFilesNumber; + const struct PanelMode *PanelModesArray; + int PanelModesNumber; + int StartPanelMode; + int StartSortMode; + int StartSortOrder; + const struct KeyBarTitles *KeyBar; + const char *ShortcutData; + // long Reserverd; +}; + +enum { + OPEN_DISKMENU, + OPEN_PLUGINSMENU, + OPEN_FINDLIST, + OPEN_SHORTCUT, + OPEN_COMMANDLINE, + OPEN_EDITOR, + OPEN_VIEWER +}; + +enum {PKF_CONTROL=1,PKF_ALT=2,PKF_SHIFT=4}; + +enum FAR_EVENTS { + FE_CHANGEVIEWMODE, + FE_REDRAW, + FE_IDLE, + FE_CLOSE, + FE_BREAK, + FE_COMMAND +}; + +enum OPERATION_MODES { + OPM_SILENT=1, + OPM_FIND=2, + OPM_VIEW=4, + OPM_EDIT=8, + OPM_TOPLEVEL=16, + OPM_DESCR=32 +}; + +#ifndef _WIN64 +#if defined(__BORLANDC__) && (__BORLANDC <= 0x520) + #pragma option -a. +#elif defined(__GNUC__) || (defined(__WATCOMC__) && (__WATCOMC__ < 1100)) + #pragma pack() +#else + #pragma pack(pop) +#endif +#endif + +/* +EXTERN_C_BEGIN + + void WINAPI _export ClosePluginW(HANDLE hPlugin); + int WINAPI _export CompareW(HANDLE hPlugin,const struct PluginPanelItem *Item1,const struct PluginPanelItem *Item2,unsigned Mode); + int WINAPI _export ConfigureW(int ItemNumber); + int WINAPI _export DeleteFilesW(HANDLE hPlugin,struct PluginPanelItem *PanelItem,int ItemsNumber,int OpMode); + void WINAPI _export ExitFARW(void); + void WINAPI _export FreeFindDataW(HANDLE hPlugin,struct PluginPanelItem *PanelItem,int ItemsNumber); + void WINAPI _export FreeVirtualFindDataW(HANDLE hPlugin,struct PluginPanelItem *PanelItem,int ItemsNumber); + int WINAPI _export GetFilesW(HANDLE hPlugin,struct PluginPanelItem *PanelItem,int ItemsNumber,int Move,const wchar_t **DestPath,int OpMode); + int WINAPI _export GetFindDataW(HANDLE hPlugin,struct PluginPanelItem **pPanelItem,int *pItemsNumber,int OpMode); + int WINAPI _export GetMinFarVersionW(void); + void WINAPI _export GetOpenPluginInfoW(HANDLE hPlugin,struct OpenPluginInfo *Info); + void WINAPI _export GetPluginInfoW(struct PluginInfo *Info); + int WINAPI _export GetVirtualFindDataW(HANDLE hPlugin,struct PluginPanelItem **pPanelItem,int *pItemsNumber,const wchar_t *Path); + int WINAPI _export MakeDirectoryW(HANDLE hPlugin,const wchar_t **Name,int OpMode); + HANDLE WINAPI _export OpenFilePluginW(const wchar_t *Name,const unsigned char *Data,int DataSize,int OpMode); + HANDLE WINAPI _export OpenPluginW(int OpenFrom,INT_PTR Item); + int WINAPI _export ProcessDialogEventW(int Event,void *Param); + int WINAPI _export ProcessEditorEventW(int Event,void *Param); + int WINAPI _export ProcessEditorInputW(const INPUT_RECORD *Rec); + int WINAPI _export ProcessEventW(HANDLE hPlugin,int Event,void *Param); + int WINAPI _export ProcessHostFileW(HANDLE hPlugin,struct PluginPanelItem *PanelItem,int ItemsNumber,int OpMode); + int WINAPI _export ProcessKeyW(HANDLE hPlugin,int Key,unsigned ControlState); + int WINAPI _export ProcessSynchroEventW(int Event,void *Param); + int WINAPI _export ProcessViewerEventW(int Event,void *Param); + int WINAPI _export PutFilesW(HANDLE hPlugin,struct PluginPanelItem *PanelItem,int ItemsNumber,int Move,const wchar_t *SrcPath,int OpMode); + int WINAPI _export SetDirectoryW(HANDLE hPlugin,const wchar_t *Dir,int OpMode); + int WINAPI _export SetFindListW(HANDLE hPlugin,const struct PluginPanelItem *PanelItem,int ItemsNumber); + void WINAPI _export SetStartupInfoW(const struct PluginStartupInfo *Info); + +EXTERN_C_END +*/ +EXTERN_C_BEGIN + + void WINAPI _export ClosePlugin(HANDLE hPlugin); + int WINAPI _export Compare(HANDLE hPlugin,const struct PluginPanelItem *Item1,const struct PluginPanelItem *Item2,unsigned Mode); + int WINAPI _export Configure(int ItemNumber); + int WINAPI _export DeleteFiles(HANDLE hPlugin,struct PluginPanelItem *PanelItem,int ItemsNumber,int OpMode); + void WINAPI _export ExitFAR(void); + void WINAPI _export FreeFindData(HANDLE hPlugin,struct PluginPanelItem *PanelItem,int ItemsNumber); + void WINAPI _export FreeVirtualFindData(HANDLE hPlugin,struct PluginPanelItem *PanelItem,int ItemsNumber); + int WINAPI _export GetFiles(HANDLE hPlugin,struct PluginPanelItem *PanelItem,int ItemsNumber,int Move,char *DestPath,int OpMode); + int WINAPI _export GetFindData(HANDLE hPlugin,struct PluginPanelItem **pPanelItem,int *pItemsNumber,int OpMode); + int WINAPI _export GetMinFarVersion(void); + void WINAPI _export GetOpenPluginInfo(HANDLE hPlugin,struct OpenPluginInfo *Info); + void WINAPI _export GetPluginInfo(struct PluginInfo *Info); + int WINAPI _export GetVirtualFindData(HANDLE hPlugin,struct PluginPanelItem **pPanelItem,int *pItemsNumber,const char *Path); + int WINAPI _export MakeDirectory(HANDLE hPlugin,char *Name,int OpMode); + HANDLE WINAPI _export OpenFilePlugin(char *Name,const BYTE *Data,int DataSize); + HANDLE WINAPI _export OpenPlugin(int OpenFrom,INT_PTR Item); + int WINAPI _export ProcessDialogEvent(int Event,void *Param); + int WINAPI _export ProcessEditorEvent(int Event,void *Param); + int WINAPI _export ProcessEditorInput(const INPUT_RECORD *Rec); + int WINAPI _export ProcessEvent(HANDLE hPlugin,int Event,void *Param); + int WINAPI _export ProcessHostFile(HANDLE hPlugin,struct PluginPanelItem *PanelItem,int ItemsNumber,int OpMode); + int WINAPI _export ProcessKey(HANDLE hPlugin,int Key,unsigned ControlState); + int WINAPI _export ProcessViewerEvent(int Event,void *Param); + int WINAPI _export PutFiles(HANDLE hPlugin,struct PluginPanelItem *PanelItem,int ItemsNumber,int Move,int OpMode); + int WINAPI _export SetDirectory(HANDLE hPlugin,const char *Dir,int OpMode); + int WINAPI _export SetFindList(HANDLE hPlugin,const struct PluginPanelItem *PanelItem,int ItemsNumber); + void WINAPI _export SetStartupInfo(const struct PluginStartupInfo *Info); + +EXTERN_C_END + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Far/FarUtils.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Far/FarUtils.cpp new file mode 100644 index 00000000..1d331e95 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Far/FarUtils.cpp @@ -0,0 +1,523 @@ +// FarUtils.cpp + +#include "StdAfx.h" + +// #include "../../../Common/IntToString.h" +#include "../../../Common/StringConvert.h" + +#ifndef UNDER_CE +#include "../../../Windows/Console.h" +#endif +#include "../../../Windows/WinDefs.h" +#include "../../../Windows/ErrorMsg.h" + +#include "FarUtils.h" + +using namespace NWindows; + +namespace NFar { + +CStartupInfo g_StartupInfo; + +const char kRegistryKeyDelimiter = '\\'; + +void CStartupInfo::Init(const PluginStartupInfo &pluginStartupInfo, + const char *pluginNameForRegistry) +{ + m_Data = pluginStartupInfo; + m_RegistryPath = pluginStartupInfo.RootKey; + m_RegistryPath += kRegistryKeyDelimiter; + m_RegistryPath += pluginNameForRegistry; +} + +const char *CStartupInfo::GetMsgString(int messageId) +{ + return (const char*)m_Data.GetMsg(m_Data.ModuleNumber, messageId); +} + +int CStartupInfo::ShowMessage(UInt32 flags, + const char *helpTopic, const char **items, unsigned numItems, int numButtons) +{ + return m_Data.Message(m_Data.ModuleNumber, flags, helpTopic, + items, (int)numItems, numButtons); +} + +namespace NMessageID +{ + enum + { + kOk, + kCancel, + kWarning, + kError + }; +} + +int CStartupInfo::ShowWarningWithOk(const char **items, unsigned numItems) +{ + return ShowMessage(FMSG_WARNING | FMSG_MB_OK, NULL, items, numItems, 0); +} + +extern const char *g_PluginName_for_Error; + +void CStartupInfo::SetErrorTitle(AString &s) +{ + if (g_PluginName_for_Error) + { + s += g_PluginName_for_Error; + s += ": "; + } + s += GetMsgString(NMessageID::kError); +} + +/* +int CStartupInfo::ShowErrorMessage(const char *message) +{ + AString s; + SetErrorTitle(s); + const char *items[]= { s, message }; + return ShowWarningWithOk(items, Z7_ARRAY_SIZE(items)); +} +*/ + +int CStartupInfo::ShowErrorMessage2(const char *m1, const char *m2) +{ + AString s; + SetErrorTitle(s); + const char *items[]= { s, m1, m2 }; + return ShowWarningWithOk(items, Z7_ARRAY_SIZE(items)); +} + +static void SplitString(const AString &src, AStringVector &destStrings) +{ + destStrings.Clear(); + AString s; + unsigned len = src.Len(); + if (len == 0) + return; + for (unsigned i = 0; i < len; i++) + { + char c = src[i]; + if (c == '\n') + { + if (!s.IsEmpty()) + { + destStrings.Add(s); + s.Empty(); + } + } + else + s += c; + } + if (!s.IsEmpty()) + destStrings.Add(s); +} + +int CStartupInfo::ShowErrorMessage(const char *message) +{ + AStringVector strings; + SplitString((AString)message, strings); + const unsigned kNumStringsMax = 20; + const char *items[kNumStringsMax + 1]; + unsigned pos = 0; + items[pos++] = GetMsgString(NMessageID::kError); + for (unsigned i = 0; i < strings.Size() && pos < kNumStringsMax; i++) + items[pos++] = strings[i]; + items[pos++] = GetMsgString(NMessageID::kOk); + + return ShowMessage(FMSG_WARNING, NULL, items, pos, 1); +} + +/* +int CStartupInfo::ShowMessageLines(const char *message) +{ + AString s = GetMsgString(NMessageID::kError); + s.Add_LF(); + s += message; + return ShowMessage(FMSG_WARNING | FMSG_MB_OK | FMSG_ALLINONE, NULL, + (const char **)(const char *)s, 1, 0); +} +*/ + +int CStartupInfo::ShowMessage(int messageId) +{ + return ShowErrorMessage(GetMsgString(messageId)); +} + +int CStartupInfo::ShowDialog(int X1, int Y1, int X2, int Y2, + const char *helpTopic, struct FarDialogItem *items, unsigned numItems) +{ + return m_Data.Dialog(m_Data.ModuleNumber, X1, Y1, X2, Y2, const_cast(helpTopic), + items, (int)numItems); +} + +int CStartupInfo::ShowDialog(int sizeX, int sizeY, + const char *helpTopic, struct FarDialogItem *items, unsigned numItems) +{ + return ShowDialog(-1, -1, sizeX, sizeY, helpTopic, items, numItems); +} + +inline static BOOL GetBOOLValue(bool v) { return (v? TRUE: FALSE); } + +void CStartupInfo::InitDialogItems(const CInitDialogItem *srcItems, + FarDialogItem *destItems, unsigned numItems) +{ + for (unsigned i = 0; i < numItems; i++) + { + const CInitDialogItem &srcItem = srcItems[i]; + FarDialogItem &destItem = destItems[i]; + + destItem.Type = srcItem.Type; + destItem.X1 = srcItem.X1; + destItem.Y1 = srcItem.Y1; + destItem.X2 = srcItem.X2; + destItem.Y2 = srcItem.Y2; + destItem.Focus = GetBOOLValue(srcItem.Focus); + if (srcItem.HistoryName != NULL) + destItem.History = srcItem.HistoryName; + else + destItem.Selected = GetBOOLValue(srcItem.Selected); + destItem.Flags = srcItem.Flags; + destItem.DefaultButton = GetBOOLValue(srcItem.DefaultButton); + + if (srcItem.DataMessageId < 0) + MyStringCopy(destItem.Data, srcItem.DataString); + else + MyStringCopy(destItem.Data, GetMsgString(srcItem.DataMessageId)); + + /* + if ((unsigned)Init[i].Data < 0xFFF) + MyStringCopy(destItem.Data, GetMsg((unsigned)srcItem.Data)); + else + MyStringCopy(destItem.Data,srcItem.Data); + */ + } +} + +// -------------------------------------------- + +HANDLE CStartupInfo::SaveScreen(int X1, int Y1, int X2, int Y2) +{ + return m_Data.SaveScreen(X1, Y1, X2, Y2); +} + +HANDLE CStartupInfo::SaveScreen() +{ + return SaveScreen(0, 0, -1, -1); +} + +void CStartupInfo::RestoreScreen(HANDLE handle) +{ + m_Data.RestoreScreen(handle); +} + +CSysString CStartupInfo::GetFullKeyName(const char *keyName) const +{ + AString s (m_RegistryPath); + if (keyName && *keyName) + { + s += kRegistryKeyDelimiter; + s += keyName; + } + return (CSysString)s; +} + + +LONG CStartupInfo::CreateRegKey(HKEY parentKey, + const char *keyName, NRegistry::CKey &destKey) const +{ + return destKey.Create(parentKey, GetFullKeyName(keyName)); +} + +LONG CStartupInfo::OpenRegKey(HKEY parentKey, + const char *keyName, NRegistry::CKey &destKey) const +{ + return destKey.Open(parentKey, GetFullKeyName(keyName)); +} + +void CStartupInfo::SetRegKeyValue(HKEY parentKey, const char *keyName, + LPCTSTR valueName, LPCTSTR value) const +{ + NRegistry::CKey regKey; + CreateRegKey(parentKey, keyName, regKey); + regKey.SetValue(valueName, value); +} + +void CStartupInfo::SetRegKeyValue(HKEY parentKey, const char *keyName, + LPCTSTR valueName, UInt32 value) const +{ + NRegistry::CKey regKey; + CreateRegKey(parentKey, keyName, regKey); + regKey.SetValue(valueName, value); +} + +void CStartupInfo::SetRegKeyValue(HKEY parentKey, const char *keyName, + LPCTSTR valueName, bool value) const +{ + NRegistry::CKey regKey; + CreateRegKey(parentKey, keyName, regKey); + regKey.SetValue(valueName, value); +} + +/* +CSysString CStartupInfo::QueryRegKeyValue(HKEY parentKey, const char *keyName, + LPCTSTR valueName, const CSysString &valueDefault) const +{ + CSysString value; + NRegistry::CKey regKey; + if (OpenRegKey(parentKey, keyName, regKey) != ERROR_SUCCESS + || regKey.QueryValue(valueName, value) != ERROR_SUCCESS) + value = valueDefault; + return value; +} + +UInt32 CStartupInfo::QueryRegKeyValue(HKEY parentKey, const char *keyName, + LPCTSTR valueName, UInt32 valueDefault) const +{ + NRegistry::CKey regKey; + if (OpenRegKey(parentKey, keyName, regKey) != ERROR_SUCCESS) + return valueDefault; + + UInt32 value; + if (regKey.GetValue_UInt32_IfOk(valueName, value) != ERROR_SUCCESS) + return valueDefault; + + return value; +} +*/ + +bool CStartupInfo::QueryRegKeyValue(HKEY parentKey, const char *keyName, + LPCTSTR valueName, bool valueDefault) const +{ + NRegistry::CKey regKey; + if (OpenRegKey(parentKey, keyName, regKey) != ERROR_SUCCESS) + return valueDefault; + + bool value; + if (regKey.GetValue_bool_IfOk(valueName, value) != ERROR_SUCCESS) + return valueDefault; + + return value; +} + +bool CStartupInfo::Control(HANDLE pluginHandle, int command, void *param) +{ + return BOOLToBool(m_Data.Control(pluginHandle, command, param)); +} + +bool CStartupInfo::ControlRequestActivePanel(int command, void *param) +{ + return Control(INVALID_HANDLE_VALUE, command, param); +} + +bool CStartupInfo::ControlGetActivePanelInfo(PanelInfo &panelInfo) +{ + return ControlRequestActivePanel(FCTL_GETPANELINFO, &panelInfo); +} + +bool CStartupInfo::ControlSetSelection(const PanelInfo &panelInfo) +{ + return ControlRequestActivePanel(FCTL_SETSELECTION, (void *)&panelInfo); +} + +bool CStartupInfo::ControlGetActivePanelCurrentItemInfo( + PluginPanelItem &pluginPanelItem) +{ + PanelInfo panelInfo; + if (!ControlGetActivePanelInfo(panelInfo)) + return false; + if (panelInfo.ItemsNumber <= 0) + throw "There are no items"; + pluginPanelItem = panelInfo.PanelItems[panelInfo.CurrentItem]; + return true; +} + +bool CStartupInfo::ControlGetActivePanelSelectedOrCurrentItems( + CObjectVector &pluginPanelItems) +{ + pluginPanelItems.Clear(); + PanelInfo panelInfo; + if (!ControlGetActivePanelInfo(panelInfo)) + return false; + if (panelInfo.ItemsNumber <= 0) + throw "There are no items"; + if (panelInfo.SelectedItemsNumber == 0) + pluginPanelItems.Add(panelInfo.PanelItems[panelInfo.CurrentItem]); + else + for (int i = 0; i < panelInfo.SelectedItemsNumber; i++) + pluginPanelItems.Add(panelInfo.SelectedItems[i]); + return true; +} + +bool CStartupInfo::ControlClearPanelSelection() +{ + PanelInfo panelInfo; + if (!ControlGetActivePanelInfo(panelInfo)) + return false; + for (int i = 0; i < panelInfo.ItemsNumber; i++) + panelInfo.PanelItems[i].Flags &= ~(DWORD)PPIF_SELECTED; + return ControlSetSelection(panelInfo); +} + +//////////////////////////////////////////////// +// menu function + +int CStartupInfo::Menu( + int x, + int y, + int maxHeight, + unsigned flags, + const char *title, + const char *aBottom, + const char *helpTopic, + int *breakKeys, + int *breakCode, + struct FarMenuItem *items, + unsigned numItems) +{ + return m_Data.Menu(m_Data.ModuleNumber, x, y, maxHeight, flags, + const_cast(title), + const_cast(aBottom), + const_cast(helpTopic), + breakKeys, breakCode, items, (int)numItems); +} + +int CStartupInfo::Menu( + unsigned flags, + const char *title, + const char *helpTopic, + struct FarMenuItem *items, + unsigned numItems) +{ + return Menu(-1, -1, 0, flags, title, NULL, helpTopic, NULL, + NULL, items, numItems); +} + +int CStartupInfo::Menu( + unsigned flags, + const char *title, + const char *helpTopic, + const AStringVector &items, + int selectedItem) +{ + CRecordVector farMenuItems; + FOR_VECTOR (i, items) + { + FarMenuItem item; + item.Checked = 0; + item.Separator = 0; + item.Selected = ((int)i == selectedItem); + const AString reducedString (items[i].Left(Z7_ARRAY_SIZE(item.Text) - 1)); + MyStringCopy(item.Text, reducedString); + farMenuItems.Add(item); + } + return Menu(flags, title, helpTopic, farMenuItems.NonConstData(), farMenuItems.Size()); +} + + +////////////////////////////////// +// CScreenRestorer + +CScreenRestorer::~CScreenRestorer() +{ + Restore(); +} +void CScreenRestorer::Save() +{ + if (m_Saved) + return; + m_HANDLE = g_StartupInfo.SaveScreen(); + m_Saved = true; +} + +void CScreenRestorer::Restore() +{ + if (m_Saved) + { + g_StartupInfo.RestoreScreen(m_HANDLE); + m_Saved = false; + } +} + +int PrintErrorMessage(const char *message, unsigned code) +{ + AString s (message); + s += " #"; + s.Add_UInt32((UInt32)code); + return g_StartupInfo.ShowErrorMessage(s); +} + +int PrintErrorMessage(const char *message, const char *text) +{ + return g_StartupInfo.ShowErrorMessage2(message, text); +} + + +void ReduceString(UString &s, unsigned size) +{ + if (s.Len() > size) + { + if (size > 5) + size -= 5; + s.Delete(size / 2, s.Len() - size); + s.Insert(size / 2, L" ... "); + } +} + +int PrintErrorMessage(const char *message, const wchar_t *name, unsigned maxLen) +{ + UString s = name; + ReduceString(s, maxLen); + return PrintErrorMessage(message, UnicodeStringToMultiByte(s, CP_OEMCP)); +} + +int ShowSysErrorMessage(DWORD errorCode) +{ + const UString message = NError::MyFormatMessage(errorCode); + return g_StartupInfo.ShowErrorMessage(UnicodeStringToMultiByte(message, CP_OEMCP)); +} + +int ShowLastErrorMessage() +{ + return ShowSysErrorMessage(::GetLastError()); +} + +int ShowSysErrorMessage(DWORD errorCode, const wchar_t *name) +{ + const UString s = NError::MyFormatMessage(errorCode); + return g_StartupInfo.ShowErrorMessage2( + UnicodeStringToMultiByte(s, CP_OEMCP), + UnicodeStringToMultiByte(name, CP_OEMCP)); +} + + +bool WasEscPressed() +{ + #ifdef UNDER_CE + return false; + #else + NConsole::CIn inConsole; + HANDLE handle = ::GetStdHandle(STD_INPUT_HANDLE); + if (handle == INVALID_HANDLE_VALUE) + return true; + inConsole.Attach(handle); + for (;;) + { + DWORD numEvents; + if (!inConsole.GetNumberOfEvents(numEvents)) + return true; + if (numEvents == 0) + return false; + + INPUT_RECORD event; + if (!inConsole.ReadEvent(event, numEvents)) + return true; + if (event.EventType == KEY_EVENT && + event.Event.KeyEvent.bKeyDown && + event.Event.KeyEvent.wVirtualKeyCode == VK_ESCAPE) + return true; + } + #endif +} + +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Far/FarUtils.h b/src/plugins/7zip/7za/cpp/7zip/UI/Far/FarUtils.h new file mode 100644 index 00000000..fefbc22c --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Far/FarUtils.h @@ -0,0 +1,203 @@ +// FarUtils.h + +#ifndef ZIP7_INC_FAR_UTILS_H +#define ZIP7_INC_FAR_UTILS_H + +#include "FarPlugin.h" + +#include "../../../Windows/Registry.h" + +namespace NFar { + +namespace NFileOperationReturnCode +{ + enum EEnum + { + kInterruptedByUser = -1, + kError = 0, + kSuccess = 1 + }; +} + +namespace NEditorReturnCode +{ + enum EEnum + { + kOpenError = 0, + kFileWasChanged = 1, + kFileWasNotChanged = 2, + kInterruptedByUser = 3 + }; +} + +struct CInitDialogItem +{ + DialogItemTypes Type; + int X1,Y1,X2,Y2; + bool Focus; + bool Selected; + UInt32 Flags; //FarDialogItemFlags Flags; + bool DefaultButton; + int DataMessageId; + const char *DataString; + const char *HistoryName; + // void InitToFarDialogItem(struct FarDialogItem &anItemDest); +}; + +class CStartupInfo +{ + PluginStartupInfo m_Data; + AString m_RegistryPath; + + CSysString GetFullKeyName(const char *keyName) const; + LONG CreateRegKey(HKEY parentKey, + const char *keyName, NWindows::NRegistry::CKey &destKey) const; + LONG OpenRegKey(HKEY parentKey, + const char *keyName, NWindows::NRegistry::CKey &destKey) const; + +public: + void Init(const PluginStartupInfo &pluginStartupInfo, + const char *pluginNameForRegistry); + const char *GetMsgString(int messageId); + + int ShowMessage(UInt32 flags, const char *helpTopic, + const char **items, unsigned numItems, int numButtons); + int ShowWarningWithOk(const char **items, unsigned numItems); + + void SetErrorTitle(AString &s); + int ShowErrorMessage(const char *message); + int ShowErrorMessage2(const char *m1, const char *m2); + // int ShowMessageLines(const char *messageLines); + int ShowMessage(int messageId); + + int ShowDialog(int X1, int Y1, int X2, int Y2, + const char *helpTopic, struct FarDialogItem *items, unsigned numItems); + int ShowDialog(int sizeX, int sizeY, + const char *helpTopic, struct FarDialogItem *items, unsigned numItems); + + void InitDialogItems(const CInitDialogItem *srcItems, + FarDialogItem *destItems, unsigned numItems); + + HANDLE SaveScreen(int X1, int Y1, int X2, int Y2); + HANDLE SaveScreen(); + void RestoreScreen(HANDLE handle); + + void SetRegKeyValue(HKEY parentKey, const char *keyName, + const LPCTSTR valueName, LPCTSTR value) const; + void SetRegKeyValue(HKEY hRoot, const char *keyName, + const LPCTSTR valueName, UInt32 value) const; + void SetRegKeyValue(HKEY hRoot, const char *keyName, + const LPCTSTR valueName, bool value) const; + + CSysString QueryRegKeyValue(HKEY parentKey, const char *keyName, + LPCTSTR valueName, const CSysString &valueDefault) const; + + UInt32 QueryRegKeyValue(HKEY parentKey, const char *keyName, + LPCTSTR valueName, UInt32 valueDefault) const; + + bool QueryRegKeyValue(HKEY parentKey, const char *keyName, + LPCTSTR valueName, bool valueDefault) const; + + bool Control(HANDLE plugin, int command, void *param); + bool ControlRequestActivePanel(int command, void *param); + bool ControlGetActivePanelInfo(PanelInfo &panelInfo); + bool ControlSetSelection(const PanelInfo &panelInfo); + bool ControlGetActivePanelCurrentItemInfo(PluginPanelItem &pluginPanelItem); + bool ControlGetActivePanelSelectedOrCurrentItems( + CObjectVector &pluginPanelItems); + + bool ControlClearPanelSelection(); + + int Menu( + int x, + int y, + int maxHeight, + unsigned flags, + const char *title, + const char *aBottom, + const char *helpTopic, + int *breakKeys, + int *breakCode, + FarMenuItem *items, + unsigned numItems); + int Menu( + unsigned flags, + const char *title, + const char *helpTopic, + FarMenuItem *items, + unsigned numItems); + + int Menu( + unsigned flags, + const char *title, + const char *helpTopic, + const AStringVector &items, + int selectedItem); + + int Editor(const char *fileName, const char *title, + int X1, int Y1, int X2, int Y2, DWORD flags, int startLine, int startChar) + { return m_Data.Editor(const_cast(fileName), const_cast(title), X1, Y1, X2, Y2, + flags, startLine, startChar); } + int Editor(const char *fileName) + { return Editor(fileName, NULL, 0, 0, -1, -1, 0, -1, -1); } + + int Viewer(const char *fileName, const char *title, + int X1, int Y1, int X2, int Y2, DWORD flags) + { return m_Data.Viewer(const_cast(fileName), const_cast(title), X1, Y1, X2, Y2, flags); } + int Viewer(const char *fileName) + { return Viewer(fileName, NULL, 0, 0, -1, -1, VF_NONMODAL); } + +}; + +class CScreenRestorer +{ + bool m_Saved; + HANDLE m_HANDLE; +public: + CScreenRestorer(): m_Saved(false) {} + ~CScreenRestorer(); + void Save(); + void Restore(); +}; + + +extern CStartupInfo g_StartupInfo; + + +int PrintErrorMessage(const char *message, unsigned code); +int PrintErrorMessage(const char *message, const char *text); +int PrintErrorMessage(const char *message, const wchar_t *name, unsigned maxLen = 70); + +#define MY_TRY_BEGIN try { + +#define MY_TRY_END1(x) }\ + catch(unsigned n) { PrintErrorMessage(x, n); return; }\ + catch(const CSysString &s) { PrintErrorMessage(x, s); return; }\ + catch(const char *s) { PrintErrorMessage(x, s); return; }\ + catch(...) { g_StartupInfo.ShowErrorMessage(x); return; } + +#define MY_TRY_END2(x, y) }\ + catch(unsigned n) { PrintErrorMessage(x, n); return y; }\ + catch(const AString &s) { PrintErrorMessage(x, s); return y; }\ + catch(const char *s) { PrintErrorMessage(x, s); return y; }\ + catch(const UString &s) { PrintErrorMessage(x, s); return y; }\ + catch(const wchar_t *s) { PrintErrorMessage(x, s); return y; }\ + catch(...) { g_StartupInfo.ShowErrorMessage(x); return y; } + + +int ShowSysErrorMessage(DWORD errorCode); +int ShowSysErrorMessage(DWORD errorCode, const wchar_t *name); +int ShowLastErrorMessage(); + +inline int ShowSysErrorMessage(HRESULT errorCode) + { return ShowSysErrorMessage((DWORD)errorCode); } +inline int ShowSysErrorMessage(HRESULT errorCode, const wchar_t *name) + { return ShowSysErrorMessage((DWORD)errorCode, name); } + +bool WasEscPressed(); + +void ReduceString(UString &s, unsigned size); + +} + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Far/Messages.h b/src/plugins/7zip/7za/cpp/7zip/UI/Far/Messages.h new file mode 100644 index 00000000..f6b20a3d --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Far/Messages.h @@ -0,0 +1,136 @@ +// Far/Messages.h + +#ifndef ZIP7_INC_FAR_MESSAGES_H +#define ZIP7_INC_FAR_MESSAGES_H + +#include "../../PropID.h" + +namespace NMessageID { + +const unsigned k_Last_PropId_supported_by_plugin = kpidDevMinor; + +enum EEnum +{ + kOk, + kCancel, + + kWarning, + kError, + + kArchiveType, + + kProperties, + + kYes, + kNo, + + kGetPasswordTitle, + kEnterPasswordForFile, + + kExtractTitle, + kExtractTo, + + kExtractPathMode, + kExtractPathFull, + kExtractPathCurrent, + kExtractPathNo, + + kExtractOwerwriteMode, + kExtractOwerwriteAsk, + kExtractOwerwritePrompt, + kExtractOwerwriteSkip, + kExtractOwerwriteAutoRename, + kExtractOwerwriteAutoRenameExisting, + + kExtractFilesMode, + kExtractFilesSelected, + kExtractFilesAll, + + kExtractPassword, + + kExtractExtract, + kExtractCancel, + + kExtractCanNotOpenOutputFile, + + kExtractUnsupportedMethod, + kExtractCRCFailed, + kExtractDataError, + kExtractCRCFailedEncrypted, + kExtractDataErrorEncrypted, + + kOverwriteTitle, + kOverwriteMessage1, + kOverwriteMessageWouldYouLike, + kOverwriteMessageWithtTisOne, + + kOverwriteBytes, + kOverwriteModifiedOn, + + kOverwriteYes, + kOverwriteYesToAll, + kOverwriteNo, + kOverwriteNoToAll, + kOverwriteAutoRename, + kOverwriteCancel, + + kUpdateNotSupportedForThisArchive, + + kDeleteTitle, + kDeleteFile, + kDeleteFiles, + kDeleteNumberOfFiles, + kDeleteDelete, + kDeleteCancel, + + kUpdateTitle, + kUpdateAddToArchive, + + kUpdateMethod, + kUpdateMethod_Store, + kUpdateMethod_Fastest, + kUpdateMethod_Fast, + kUpdateMethod_Normal, + kUpdateMethod_Maximum, + kUpdateMethod_Ultra, + + kUpdateMode, + kUpdateMode_Add, + kUpdateMode_Update, + kUpdateMode_Fresh, + kUpdateMode_Sync, + + kUpdateAdd, + kUpdateSelectArchiver, + + kUpdateSelectArchiverMenuTitle, + + // kArcReadFiles, + + kWaitTitle, + + kReading, + kExtracting, + kDeleting, + kUpdating, + + // kReadingList, + + kMoveIsNotSupported, + + kOpenArchiveMenuString, + kCreateArchiveMenuString, + + kConfigTitle, + + kConfigPluginEnabled, + + // ---------- IDs for Properies (kpid*) ---------- + kNoProperty, + k_Last_MessageID_for_Property = kNoProperty + k_Last_PropId_supported_by_plugin + // ---------- +}; + +} + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Far/OverwriteDialogFar.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Far/OverwriteDialogFar.cpp new file mode 100644 index 00000000..4d1b971f --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Far/OverwriteDialogFar.cpp @@ -0,0 +1,145 @@ +// OverwriteDialogFar.cpp + +#include "StdAfx.h" + +#include + +#include "../../../Common/StringConvert.h" +#include "../../../Common/IntToString.h" + +#include "../../../Windows/FileName.h" +#include "../../../Windows/PropVariantConv.h" + +#include "FarUtils.h" +#include "Messages.h" + +#include "OverwriteDialogFar.h" + +using namespace NWindows; +using namespace NFar; + +namespace NOverwriteDialog { + +struct CFileInfoStrings +{ + AString Size; + AString Time; +}; + +static void SetFileInfoStrings(const CFileInfo &fileInfo, + CFileInfoStrings &fileInfoStrings) +{ + char buffer[256]; + + if (fileInfo.SizeIsDefined) + { + ConvertUInt64ToString(fileInfo.Size, buffer); + fileInfoStrings.Size = buffer; + fileInfoStrings.Size.Add_Space(); + fileInfoStrings.Size += g_StartupInfo.GetMsgString(NMessageID::kOverwriteBytes); + } + else + { + fileInfoStrings.Size = ""; + } + + fileInfoStrings.Time.Empty(); + if (fileInfo.TimeIsDefined) + { + char timeString[64]; + ConvertUtcFileTimeToString(fileInfo.Time, timeString); + fileInfoStrings.Time = g_StartupInfo.GetMsgString(NMessageID::kOverwriteModifiedOn); + fileInfoStrings.Time.Add_Space(); + fileInfoStrings.Time += timeString; + } +} + +static void ReduceString2(UString &s, unsigned size) +{ + if (!s.IsEmpty() && s.Back() == ' ') + { + // s += (wchar_t)(0x2423); + s.InsertAtFront(L'\"'); + s += L'\"'; + } + ReduceString(s, size); +} + +NResult::EEnum Execute(const CFileInfo &oldFileInfo, const CFileInfo &newFileInfo) +{ + const int kYSize = 22; + const int kXSize = 76; + + CFileInfoStrings oldFileInfoStrings; + CFileInfoStrings newFileInfoStrings; + + SetFileInfoStrings(oldFileInfo, oldFileInfoStrings); + SetFileInfoStrings(newFileInfo, newFileInfoStrings); + + const UString &oldName2 = oldFileInfo.Name; + const UString &newName2 = newFileInfo.Name; + + int slashPos = oldName2.ReverseFind_PathSepar(); + UString pref1 = oldName2.Left(slashPos + 1); + UString name1 = oldName2.Ptr(slashPos + 1); + + slashPos = newName2.ReverseFind_PathSepar(); + UString pref2 = newName2.Left(slashPos + 1); + UString name2 = newName2.Ptr(slashPos + 1); + + const unsigned kNameOffset = 2; + { + const unsigned maxNameLen = kXSize - 9 - 2; + ReduceString(pref1, maxNameLen); + ReduceString(pref2, maxNameLen); + ReduceString2(name1, maxNameLen - kNameOffset); + ReduceString2(name2, maxNameLen - kNameOffset); + } + + const AString pref1A (UnicodeStringToMultiByte(pref1, CP_OEMCP)); + const AString pref2A (UnicodeStringToMultiByte(pref2, CP_OEMCP)); + const AString name1A (UnicodeStringToMultiByte(name1, CP_OEMCP)); + const AString name2A (UnicodeStringToMultiByte(name2, CP_OEMCP)); + + const struct CInitDialogItem initItems[]={ + { DI_DOUBLEBOX, 3, 1, kXSize - 4, kYSize - 2, false, false, 0, false, NMessageID::kOverwriteTitle, NULL, NULL }, + { DI_TEXT, 5, 2, 0, 0, false, false, 0, false, NMessageID::kOverwriteMessage1, NULL, NULL }, + + { DI_TEXT, 3, 3, 0, 0, false, false, DIF_BOXCOLOR|DIF_SEPARATOR, false, -1, "", NULL }, + + { DI_TEXT, 5, 4, 0, 0, false, false, 0, false, NMessageID::kOverwriteMessageWouldYouLike, NULL, NULL }, + + { DI_TEXT, 7, 6, 0, 0, false, false, 0, false, -1, pref1A, NULL }, + { DI_TEXT, 7 + kNameOffset, 7, 0, 0, false, false, 0, false, -1, name1A, NULL }, + { DI_TEXT, 7, 8, 0, 0, false, false, 0, false, -1, oldFileInfoStrings.Size, NULL }, + { DI_TEXT, 7, 9, 0, 0, false, false, 0, false, -1, oldFileInfoStrings.Time, NULL }, + + { DI_TEXT, 5, 11, 0, 0, false, false, 0, false, NMessageID::kOverwriteMessageWithtTisOne, NULL, NULL }, + + { DI_TEXT, 7, 13, 0, 0, false, false, 0, false, -1, pref2A, NULL }, + { DI_TEXT, 7 + kNameOffset, 14, 0, 0, false, false, 0, false, -1, name2A, NULL }, + { DI_TEXT, 7, 15, 0, 0, false, false, 0, false, -1, newFileInfoStrings.Size, NULL }, + { DI_TEXT, 7, 16, 0, 0, false, false, 0, false, -1, newFileInfoStrings.Time, NULL }, + + { DI_TEXT, 3, kYSize - 5, 0, 0, false, false, DIF_BOXCOLOR|DIF_SEPARATOR, false, -1, "", NULL }, + + { DI_BUTTON, 0, kYSize - 4, 0, 0, true, false, DIF_CENTERGROUP, true, NMessageID::kOverwriteYes, NULL, NULL }, + { DI_BUTTON, 0, kYSize - 4, 0, 0, false, false, DIF_CENTERGROUP, false, NMessageID::kOverwriteYesToAll, NULL, NULL }, + { DI_BUTTON, 0, kYSize - 4, 0, 0, false, false, DIF_CENTERGROUP, false, NMessageID::kOverwriteNo, NULL, NULL }, + { DI_BUTTON, 0, kYSize - 4, 0, 0, false, false, DIF_CENTERGROUP, false, NMessageID::kOverwriteNoToAll, NULL, NULL }, + { DI_BUTTON, 0, kYSize - 3, 0, 0, false, false, DIF_CENTERGROUP, false, NMessageID::kOverwriteAutoRename, NULL, NULL }, + { DI_BUTTON, 0, kYSize - 3, 0, 0, false, false, DIF_CENTERGROUP, false, NMessageID::kOverwriteCancel, NULL, NULL } + }; + + const int kNumDialogItems = Z7_ARRAY_SIZE(initItems); + FarDialogItem aDialogItems[kNumDialogItems]; + g_StartupInfo.InitDialogItems(initItems, aDialogItems, kNumDialogItems); + const int anAskCode = g_StartupInfo.ShowDialog(kXSize, kYSize, + NULL, aDialogItems, kNumDialogItems); + const int kButtonStartPos = kNumDialogItems - 6; + if (anAskCode >= kButtonStartPos && anAskCode < kNumDialogItems) + return NResult::EEnum(anAskCode - kButtonStartPos); + return NResult::kCancel; +} + +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Far/OverwriteDialogFar.h b/src/plugins/7zip/7za/cpp/7zip/UI/Far/OverwriteDialogFar.h new file mode 100644 index 00000000..bc6e92bc --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Far/OverwriteDialogFar.h @@ -0,0 +1,37 @@ +// OverwriteDialogFar.h + +#ifndef ZIP7_INC_OVERWRITE_DIALOG_FAR_H +#define ZIP7_INC_OVERWRITE_DIALOG_FAR_H + +#include "../../../Common/MyString.h" +#include "../../../Common/MyTypes.h" + +namespace NOverwriteDialog { + +struct CFileInfo +{ + bool SizeIsDefined; + bool TimeIsDefined; + UInt64 Size; + FILETIME Time; + UString Name; +}; + +namespace NResult +{ + enum EEnum + { + kYes, + kYesToAll, + kNo, + kNoToAll, + kAutoRename, + kCancel + }; +} + +NResult::EEnum Execute(const CFileInfo &oldFileInfo, const CFileInfo &newFileInfo); + +} + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Far/Plugin.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Far/Plugin.cpp new file mode 100644 index 00000000..f04f712e --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Far/Plugin.cpp @@ -0,0 +1,937 @@ +// Plugin.cpp + +#include "StdAfx.h" + +#include "../../../Common/IntToString.h" +#include "../../../Common/StringConvert.h" +#include "../../../Common/Wildcard.h" + +#include "../../../Windows/FileDir.h" +#include "../../../Windows/PropVariantConv.h" + +#include "../Common/PropIDUtils.h" + +#include "FarUtils.h" +#include "Messages.h" +#include "Plugin.h" + +using namespace NWindows; +using namespace NFile; +using namespace NDir; +using namespace NFar; + +// This function is used by CAgentFolder +int CompareFileNames_ForFolderList(const wchar_t *s1, const wchar_t *s2); +int CompareFileNames_ForFolderList(const wchar_t *s1, const wchar_t *s2) +{ + return MyStringCompareNoCase(s1, s2); +} + +CPlugin::CPlugin(const FString &fileName, CAgent *agent, UString archiveTypeName): + _agent(agent), + m_FileName(fileName), + _archiveTypeName(archiveTypeName), + PasswordIsDefined(false) +{ + m_ArchiveHandler = agent; + if (!m_FileInfo.Find(m_FileName)) + throw "error"; + m_ArchiveHandler->BindToRootFolder(&_folder); +} + +CPlugin::~CPlugin() {} + +static void MyGetFileTime(IFolderFolder *folder, UInt32 itemIndex, + PROPID propID, FILETIME &fileTime) +{ + NCOM::CPropVariant prop; + if (folder->GetProperty(itemIndex, propID, &prop) != S_OK) + throw 271932; + if (prop.vt == VT_EMPTY) + { + fileTime.dwHighDateTime = 0; + fileTime.dwLowDateTime = 0; + } + else + { + if (prop.vt != VT_FILETIME) + throw 4191730; + fileTime = prop.filetime; + } +} + +#define kDotsReplaceString "[[..]]" + +static void CopyStrLimited(char *dest, const AString &src, unsigned len) +{ + len--; + if (src.Len() < len) + len = src.Len(); + memcpy(dest, src, sizeof(dest[0]) * len); + dest[len] = 0; +} + +#define COPY_STR_LIMITED(dest, src) CopyStrLimited(dest, src, Z7_ARRAY_SIZE(dest)) + +void CPlugin::ReadPluginPanelItem(PluginPanelItem &panelItem, UInt32 itemIndex) +{ + NCOM::CPropVariant prop; + if (_folder->GetProperty(itemIndex, kpidName, &prop) != S_OK) + throw 271932; + + if (prop.vt != VT_BSTR) + throw 272340; + + AString oemString (UnicodeStringToMultiByte(prop.bstrVal, CP_OEMCP)); + if (oemString.IsEqualTo("..")) + oemString = kDotsReplaceString; + + COPY_STR_LIMITED(panelItem.FindData.cFileName, oemString); + panelItem.FindData.cAlternateFileName[0] = 0; + + if (_folder->GetProperty(itemIndex, kpidAttrib, &prop) != S_OK) + throw 271932; + if (prop.vt == VT_UI4) + panelItem.FindData.dwFileAttributes = prop.ulVal; + else if (prop.vt == VT_EMPTY) + panelItem.FindData.dwFileAttributes = m_FileInfo.Attrib; + else + throw 21631; + + if (_folder->GetProperty(itemIndex, kpidIsDir, &prop) != S_OK) + throw 271932; + if (prop.vt == VT_BOOL) + { + if (VARIANT_BOOLToBool(prop.boolVal)) + panelItem.FindData.dwFileAttributes |= FILE_ATTRIBUTE_DIRECTORY; + } + else if (prop.vt != VT_EMPTY) + throw 21632; + + if (_folder->GetProperty(itemIndex, kpidSize, &prop) != S_OK) + throw 271932; + UInt64 length = 0; + ConvertPropVariantToUInt64(prop, length); + panelItem.FindData.nFileSizeLow = (UInt32)length; + panelItem.FindData.nFileSizeHigh = (UInt32)(length >> 32); + + MyGetFileTime(_folder, itemIndex, kpidCTime, panelItem.FindData.ftCreationTime); + MyGetFileTime(_folder, itemIndex, kpidATime, panelItem.FindData.ftLastAccessTime); + MyGetFileTime(_folder, itemIndex, kpidMTime, panelItem.FindData.ftLastWriteTime); + + if (panelItem.FindData.ftLastWriteTime.dwHighDateTime == 0 && + panelItem.FindData.ftLastWriteTime.dwLowDateTime == 0) + panelItem.FindData.ftLastWriteTime = m_FileInfo.MTime; + + if (_folder->GetProperty(itemIndex, kpidPackSize, &prop) != S_OK) + throw 271932; + length = 0; + ConvertPropVariantToUInt64(prop, length); + panelItem.PackSize = UInt32(length); + panelItem.PackSizeHigh = UInt32(length >> 32); + + panelItem.Flags = 0; + panelItem.NumberOfLinks = 0; + + panelItem.Description = NULL; + panelItem.Owner = NULL; + panelItem.CustomColumnData = NULL; + panelItem.CustomColumnNumber = 0; + + panelItem.CRC32 = 0; + panelItem.Reserved[0] = 0; + panelItem.Reserved[1] = 0; +} + + +static const UInt32 k_NumFiles_Max = 0x7fffffff - 15; + +int CPlugin::GetFindData(PluginPanelItem **panelItems, int *itemsNumber, int opMode) +{ + *panelItems = NULL; + *itemsNumber = 0; + // CScreenRestorer screenRestorer; + if ((opMode & OPM_SILENT) == 0 && (opMode & OPM_FIND ) == 0) + { + /* + screenRestorer.Save(); + const char *msgItems[]= + { + g_StartupInfo.GetMsgString(NMessageID::kWaitTitle), + g_StartupInfo.GetMsgString(NMessageID::kReadingList) + }; + g_StartupInfo.ShowMessage(0, NULL, msgItems, Z7_ARRAY_SIZE(msgItems), 0); + */ + } + + UInt32 numItems; + if (_folder->GetNumberOfItems(&numItems) != S_OK) + return FALSE; + if (numItems > k_NumFiles_Max) + return FALSE; + + Z7_ARRAY_NEW(*panelItems, PluginPanelItem, numItems) + memset(*panelItems, 0, (size_t)numItems * sizeof(PluginPanelItem)); + try + { + for (UInt32 i = 0; i < numItems; i++) + { + PluginPanelItem &panelItem = (*panelItems)[i]; + ReadPluginPanelItem(panelItem, i); + panelItem.UserData = i; + } + } + catch(...) + { + delete [](*panelItems); + *panelItems = NULL; + throw; + } + *itemsNumber = (int)numItems; + return(TRUE); +} + +void CPlugin::FreeFindData(struct PluginPanelItem *panelItems, int itemsNumber) +{ + for (int i = 0; i < itemsNumber; i++) + if (panelItems[i].Description != NULL) + delete []panelItems[i].Description; + delete []panelItems; +} + +void CPlugin::EnterToDirectory(const UString &dirName) +{ + CMyComPtr newFolder; + UString s = dirName; + if (dirName.IsEqualTo(kDotsReplaceString)) + s = ".."; + _folder->BindToFolder(s, &newFolder); + if (!newFolder) + { + if (dirName.IsEmpty()) + return; + else + throw 40325; + } + _folder = newFolder; +} + +int CPlugin::SetDirectory(const char *aszDir, int /* opMode */) +{ + UString path = MultiByteToUnicodeString(aszDir, CP_OEMCP); + if (path.IsEqualTo(STRING_PATH_SEPARATOR)) + { + _folder.Release(); + m_ArchiveHandler->BindToRootFolder(&_folder); + } + else if (path.IsEqualTo("..")) + { + CMyComPtr newFolder; + _folder->BindToParentFolder(&newFolder); + if (!newFolder) + throw 40312; + _folder = newFolder; + } + else if (path.IsEmpty()) + EnterToDirectory(path); + else + { + if (path[0] == WCHAR_PATH_SEPARATOR) + { + _folder.Release(); + m_ArchiveHandler->BindToRootFolder(&_folder); + path.DeleteFrontal(1); + } + UStringVector pathParts; + SplitPathToParts(path, pathParts); + FOR_VECTOR (i, pathParts) + EnterToDirectory(pathParts[i]); + } + SetCurrentDirVar(); + return TRUE; +} + +void CPlugin::GetPathParts(UStringVector &pathParts) +{ + pathParts.Clear(); + CMyComPtr folderItem = _folder; + for (;;) + { + CMyComPtr newFolder; + folderItem->BindToParentFolder(&newFolder); + if (!newFolder) + break; + NCOM::CPropVariant prop; + if (folderItem->GetFolderProperty(kpidName, &prop) == S_OK) + if (prop.vt == VT_BSTR) + pathParts.Insert(0, (const wchar_t *)prop.bstrVal); + folderItem = newFolder; + } +} + +void CPlugin::SetCurrentDirVar() +{ + m_CurrentDir.Empty(); + + /* + // kpidPath path has tail slash, but we don't need it for compatibility with default FAR style + NCOM::CPropVariant prop; + if (_folder->GetFolderProperty(kpidPath, &prop) == S_OK) + if (prop.vt == VT_BSTR) + { + m_CurrentDir = (wchar_t *)prop.bstrVal; + // if (!m_CurrentDir.IsEmpty()) + } + m_CurrentDir.InsertAtFront(WCHAR_PATH_SEPARATOR); + */ + + UStringVector pathParts; + GetPathParts(pathParts); + FOR_VECTOR (i, pathParts) + { + m_CurrentDir.Add_PathSepar(); + m_CurrentDir += pathParts[i]; + } +} + +static const char * const kPluginFormatName = "7-ZIP"; + + +static int FindPropNameID(PROPID propID) +{ + if (propID > NMessageID::k_Last_PropId_supported_by_plugin) + return -1; + return NMessageID::kNoProperty + (int)propID; +} + +/* +struct CPropertyIDInfo +{ + PROPID PropID; + const char *FarID; + int Width; + // char CharID; +}; + +static CPropertyIDInfo kPropertyIDInfos[] = +{ + { kpidName, "N", 0}, + { kpidSize, "S", 8}, + { kpidPackSize, "P", 8}, + { kpidAttrib, "A", 0}, + { kpidCTime, "DC", 14}, + { kpidATime, "DA", 14}, + { kpidMTime, "DM", 14}, + + { kpidSolid, NULL, 0, 'S'}, + { kpidEncrypted, NULL, 0, 'P'}, + + { kpidDictionarySize, IDS_PROPERTY_DICTIONARY_SIZE }, + { kpidSplitBefore, NULL, 'B'}, + { kpidSplitAfter, NULL, 'A'}, + { kpidComment, NULL, 'C'}, + { kpidCRC, IDS_PROPERTY_CRC } + // { kpidType, L"Type" } +}; + +static const int kNumPropertyIDInfos = Z7_ARRAY_SIZE(kPropertyIDInfos); + +static int FindPropertyInfo(PROPID propID) +{ + for (int i = 0; i < kNumPropertyIDInfos; i++) + if (kPropertyIDInfos[i].PropID == propID) + return i; + return -1; +} +*/ + +// char *g_Titles[] = { "a", "f", "v" }; +/* +static void SmartAddToString(AString &destString, const char *srcString) +{ + if (!destString.IsEmpty()) + destString += ','; + destString += srcString; +} +*/ + +/* +void CPlugin::AddColumn(PROPID propID) +{ + int index = FindPropertyInfo(propID); + if (index >= 0) + { + for (int i = 0; i < m_ProxyHandler->m_InternalProperties.Size(); i++) + { + const CArchiveItemProperty &aHandlerProperty = m_ProxyHandler->m_InternalProperties[i]; + if (aHandlerProperty.ID == propID) + break; + } + if (i == m_ProxyHandler->m_InternalProperties.Size()) + return; + + const CPropertyIDInfo &propertyIDInfo = kPropertyIDInfos[index]; + SmartAddToString(PanelModeColumnTypes, propertyIDInfo.FarID); + char tmp[32]; + itoa(propertyIDInfo.Width, tmp, 10); + SmartAddToString(PanelModeColumnWidths, tmp); + return; + } +} +*/ + +static AString GetNameOfProp(PROPID propID, const wchar_t *name) +{ + int farID = FindPropNameID(propID); + if (farID >= 0) + return (AString)g_StartupInfo.GetMsgString(farID); + if (name) + return UnicodeStringToMultiByte(name, CP_OEMCP); + char s[16]; + ConvertUInt32ToString(propID, s); + return (AString)s; +} + +static AString GetNameOfProp2(PROPID propID, const wchar_t *name) +{ + AString s (GetNameOfProp(propID, name)); + if (s.Len() > (kInfoPanelLineSize - 1)) + s.DeleteFrom(kInfoPanelLineSize - 1); + return s; +} + +static AString ConvertSizeToString(UInt64 value) +{ + char s[32]; + ConvertUInt64ToString(value, s); + unsigned i = MyStringLen(s); + unsigned pos = Z7_ARRAY_SIZE(s); + s[--pos] = 0; + while (i > 3) + { + s[--pos] = s[--i]; + s[--pos] = s[--i]; + s[--pos] = s[--i]; + s[--pos] = ' '; + } + while (i > 0) + s[--pos] = s[--i]; + return (AString)(s + pos); +} + +static AString PropToString(const NCOM::CPropVariant &prop, PROPID propID) +{ + if (prop.vt == VT_BSTR) + { + AString s (UnicodeStringToMultiByte(prop.bstrVal, CP_OEMCP)); + s.Replace((char)0xA, ' '); + s.Replace((char)0xD, ' '); + return s; + } + if (prop.vt == VT_BOOL) + { + int messageID = VARIANT_BOOLToBool(prop.boolVal) ? + NMessageID::kYes : NMessageID::kNo; + return (AString)g_StartupInfo.GetMsgString(messageID); + } + if (prop.vt != VT_EMPTY) + { + if ((prop.vt == VT_UI8 || prop.vt == VT_UI4) && ( + propID == kpidSize || + propID == kpidPackSize || + propID == kpidNumSubDirs || + propID == kpidNumSubFiles || + propID == kpidNumBlocks || + propID == kpidPhySize || + propID == kpidHeadersSize || + propID == kpidClusterSize || + propID == kpidUnpackSize + )) + { + UInt64 v = 0; + ConvertPropVariantToUInt64(prop, v); + return ConvertSizeToString(v); + } + { + char sz[64]; + ConvertPropertyToShortString2(sz, prop, propID); + return (AString)sz; + } + } + return AString(); +} + +static AString PropToString2(const NCOM::CPropVariant &prop, PROPID propID) +{ + AString s (PropToString(prop, propID)); + if (s.Len() > (kInfoPanelLineSize - 1)) + s.DeleteFrom(kInfoPanelLineSize - 1); + return s; +} + +static void AddPropertyString(InfoPanelLine *lines, unsigned &numItems, PROPID propID, const wchar_t *name, + const NCOM::CPropVariant &prop) +{ + if (prop.vt != VT_EMPTY) + { + AString val (PropToString2(prop, propID)); + if (!val.IsEmpty()) + { + InfoPanelLine &item = lines[numItems++]; + COPY_STR_LIMITED(item.Text, GetNameOfProp2(propID, name)); + COPY_STR_LIMITED(item.Data, val); + } + } +} + +static void InsertSeparator(InfoPanelLine *lines, unsigned &numItems) +{ + if (numItems < kNumInfoLinesMax) + { + InfoPanelLine &item = lines[numItems++]; + *item.Text = 0; + *item.Data = 0; + item.Separator = TRUE; + } +} + +void CPlugin::GetOpenPluginInfo(struct OpenPluginInfo *info) +{ + info->StructSize = sizeof(*info); + info->Flags = OPIF_USEFILTER | OPIF_USESORTGROUPS | OPIF_USEHIGHLIGHTING | + OPIF_ADDDOTS | OPIF_COMPAREFATTIME; + + COPY_STR_LIMITED(m_FileNameBuffer, UnicodeStringToMultiByte(fs2us(m_FileName), CP_OEMCP)); + info->HostFile = m_FileNameBuffer; // test it it is not static + + COPY_STR_LIMITED(m_CurrentDirBuffer, UnicodeStringToMultiByte(m_CurrentDir, CP_OEMCP)); + info->CurDir = m_CurrentDirBuffer; + + info->Format = kPluginFormatName; + + { + UString name; + { + FString dirPrefix, fileName; + GetFullPathAndSplit(m_FileName, dirPrefix, fileName); + name = fs2us(fileName); + } + + m_PannelTitle = ' '; + m_PannelTitle += _archiveTypeName; + m_PannelTitle.Add_Colon(); + m_PannelTitle += name; + m_PannelTitle.Add_Space(); + if (!m_CurrentDir.IsEmpty()) + { + // m_PannelTitle += '\\'; + m_PannelTitle += m_CurrentDir; + } + + COPY_STR_LIMITED(m_PannelTitleBuffer, UnicodeStringToMultiByte(m_PannelTitle, CP_OEMCP)); + info->PanelTitle = m_PannelTitleBuffer; + + } + + memset(m_InfoLines, 0, sizeof(m_InfoLines)); + m_InfoLines[0].Text[0] = 0; + m_InfoLines[0].Separator = TRUE; + + MyStringCopy(m_InfoLines[1].Text, g_StartupInfo.GetMsgString(NMessageID::kArchiveType)); + MyStringCopy(m_InfoLines[1].Data, (const char *)UnicodeStringToMultiByte(_archiveTypeName, CP_OEMCP)); + + unsigned numItems = 2; + + { + CMyComPtr folderProperties; + _folder.QueryInterface(IID_IFolderProperties, &folderProperties); + if (folderProperties) + { + UInt32 numProps; + if (folderProperties->GetNumberOfFolderProperties(&numProps) == S_OK) + { + for (UInt32 i = 0; i < numProps && numItems < kNumInfoLinesMax; i++) + { + CMyComBSTR name; + PROPID propID; + VARTYPE vt; + if (folderProperties->GetFolderPropertyInfo(i, &name, &propID, &vt) != S_OK) + continue; + NCOM::CPropVariant prop; + if (_folder->GetFolderProperty(propID, &prop) != S_OK || prop.vt == VT_EMPTY) + continue; + + InfoPanelLine &item = m_InfoLines[numItems++]; + COPY_STR_LIMITED(item.Text, GetNameOfProp2(propID, name)); + COPY_STR_LIMITED(item.Data, PropToString2(prop, propID)); + } + } + } + } + + /* + if (numItems < kNumInfoLinesMax) + { + InsertSeparator(m_InfoLines, numItems); + } + */ + + { + CMyComPtr getFolderArcProps; + _folder.QueryInterface(IID_IGetFolderArcProps, &getFolderArcProps); + if (getFolderArcProps) + { + CMyComPtr getProps; + getFolderArcProps->GetFolderArcProps(&getProps); + if (getProps) + { + UInt32 numLevels; + if (getProps->GetArcNumLevels(&numLevels) != S_OK) + numLevels = 0; + for (UInt32 level2 = 0; level2 < numLevels; level2++) + { + { + UInt32 level = numLevels - 1 - level2; + UInt32 numProps; + if (getProps->GetArcNumProps(level, &numProps) == S_OK) + { + InsertSeparator(m_InfoLines, numItems); + for (Int32 i = -3; i < (Int32)numProps && numItems < kNumInfoLinesMax; i++) + { + CMyComBSTR name; + PROPID propID; + VARTYPE vt; + switch (i) + { + case -3: propID = kpidPath; break; + case -2: propID = kpidType; break; + case -1: propID = kpidError; break; + default: + if (getProps->GetArcPropInfo(level, (UInt32)i, &name, &propID, &vt) != S_OK) + continue; + } + NCOM::CPropVariant prop; + if (getProps->GetArcProp(level, propID, &prop) != S_OK) + continue; + AddPropertyString(m_InfoLines, numItems, propID, name, prop); + } + } + } + if (level2 != numLevels - 1) + { + UInt32 level = numLevels - 1 - level2; + UInt32 numProps; + if (getProps->GetArcNumProps2(level, &numProps) == S_OK) + { + InsertSeparator(m_InfoLines, numItems); + for (Int32 i = 0; i < (Int32)numProps && numItems < kNumInfoLinesMax; i++) + { + CMyComBSTR name; + PROPID propID; + VARTYPE vt; + if (getProps->GetArcPropInfo2(level, (UInt32)i, &name, &propID, &vt) != S_OK) + continue; + NCOM::CPropVariant prop; + if (getProps->GetArcProp2(level, propID, &prop) != S_OK) + continue; + AddPropertyString(m_InfoLines, numItems, propID, name, prop); + } + } + } + } + } + } + } + + //m_InfoLines[1].Separator = 0; + + info->InfoLines = m_InfoLines; + info->InfoLinesNumber = (int)numItems; + + + info->DescrFiles = NULL; + info->DescrFilesNumber = 0; + + PanelModeColumnTypes.Empty(); + PanelModeColumnWidths.Empty(); + + /* + AddColumn(kpidName); + AddColumn(kpidSize); + AddColumn(kpidPackSize); + AddColumn(kpidMTime); + AddColumn(kpidCTime); + AddColumn(kpidATime); + AddColumn(kpidAttrib); + + _panelMode.ColumnTypes = (char *)(const char *)PanelModeColumnTypes; + _panelMode.ColumnWidths = (char *)(const char *)PanelModeColumnWidths; + _panelMode.ColumnTitles = NULL; + _panelMode.FullScreen = TRUE; + _panelMode.DetailedStatus = FALSE; + _panelMode.AlignExtensions = FALSE; + _panelMode.CaseConversion = FALSE; + _panelMode.StatusColumnTypes = "N"; + _panelMode.StatusColumnWidths = "0"; + _panelMode.Reserved[0] = 0; + _panelMode.Reserved[1] = 0; + + info->PanelModesArray = &_panelMode; + info->PanelModesNumber = 1; + */ + + info->PanelModesArray = NULL; + info->PanelModesNumber = 0; + + info->StartPanelMode = 0; + info->StartSortMode = 0; + info->KeyBar = NULL; + info->ShortcutData = NULL; +} + +struct CArchiveItemProperty +{ + AString Name; + PROPID ID; + VARTYPE Type; +}; + +static inline char GetHex_A_minus10(unsigned v, unsigned a10) +{ + return (char)(v < 10 ? v + '0' : v + a10); +} + +HRESULT CPlugin::ShowAttributesWindow() +{ + PluginPanelItem pluginPanelItem; + if (!g_StartupInfo.ControlGetActivePanelCurrentItemInfo(pluginPanelItem)) + return S_FALSE; + if (strcmp(pluginPanelItem.FindData.cFileName, "..") == 0 && + NFind::NAttributes::IsDir(pluginPanelItem.FindData.dwFileAttributes)) + return S_FALSE; + const UInt32 itemIndex = (UInt32)pluginPanelItem.UserData; + + CObjectVector properties; + UInt32 numProps; + RINOK(_folder->GetNumberOfProperties(&numProps)) + unsigned i; + for (i = 0; i < numProps; i++) + { + CMyComBSTR name; + PROPID propID; + VARTYPE vt; + RINOK(_folder->GetPropertyInfo(i, &name, &propID, &vt)) + CArchiveItemProperty prop; + prop.Type = vt; + prop.ID = propID; + if (prop.ID == kpidPath) + prop.ID = kpidName; + prop.Name = GetNameOfProp(propID, name); + properties.Add(prop); + } + + int size = 2; + CRecordVector initDialogItems; + + int xSize = 70; + { + const CInitDialogItem idi = + { DI_DOUBLEBOX, 3, 1, xSize - 4, size - 2, false, false, 0, false, NMessageID::kProperties, NULL, NULL }; + initDialogItems.Add(idi); + } + + AStringVector values; + + const int kStartY = 3; + + for (i = 0; i < properties.Size(); i++) + { + const CArchiveItemProperty &property = properties[i]; + + const int startY = kStartY + (int)values.Size(); + + { + CInitDialogItem idi = + { DI_TEXT, 5, startY, 0, 0, false, false, 0, false, 0, NULL, NULL }; + idi.DataMessageId = FindPropNameID(property.ID); + if (idi.DataMessageId < 0) + idi.DataString = property.Name; + initDialogItems.Add(idi); + } + + NCOM::CPropVariant prop; + RINOK(_folder->GetProperty(itemIndex, property.ID, &prop)) + values.Add(PropToString(prop, property.ID)); + + { + const CInitDialogItem idi = + { DI_TEXT, 30, startY, 0, 0, false, false, 0, false, -1, NULL, NULL }; + initDialogItems.Add(idi); + } + } + + CMyComPtr _folderRawProps; + _folder.QueryInterface(IID_IArchiveGetRawProps, &_folderRawProps); + + CObjectVector properties2; + + if (_folderRawProps) + { + _folderRawProps->GetNumRawProps(&numProps); + + for (i = 0; i < numProps; i++) + { + CMyComBSTR name; + PROPID propID; + if (_folderRawProps->GetRawPropInfo(i, &name, &propID) != S_OK) + continue; + CArchiveItemProperty prop; + prop.Type = VT_EMPTY; + prop.ID = propID; + if (prop.ID == kpidPath) + prop.ID = kpidName; + prop.Name = GetNameOfProp(propID, name); + properties2.Add(prop); + } + + for (i = 0; i < properties2.Size(); i++) + { + const CArchiveItemProperty &property = properties2[i]; + CMyComBSTR name; + + const void *data; + UInt32 dataSize; + UInt32 propType; + if (_folderRawProps->GetRawProp(itemIndex, property.ID, &data, &dataSize, &propType) != S_OK) + continue; + + if (dataSize != 0) + { + AString s; + if (property.ID == kpidNtSecure) + ConvertNtSecureToString((const Byte *)data, dataSize, s); + else + { + const UInt32 kMaxDataSize = 64; + if (dataSize > kMaxDataSize) + { + s += "data:"; + s.Add_UInt32(dataSize); + } + else + { + const unsigned a = dataSize <= 8 + && (property.ID == kpidCRC || property.ID == kpidChecksum) + ? 'A' - 10 : 'a' - 10; + for (UInt32 k = 0; k < dataSize; k++) + { + const unsigned b = ((const Byte *)data)[k]; + s += GetHex_A_minus10(b >> 4, a); + s += GetHex_A_minus10(b & 15, a); + } + } + } + + const int startY = kStartY + (int)values.Size(); + + { + CInitDialogItem idi = + { DI_TEXT, 5, startY, 0, 0, false, false, 0, false, 0, NULL, NULL }; + idi.DataMessageId = FindPropNameID(property.ID); + if (idi.DataMessageId < 0) + idi.DataString = property.Name; + initDialogItems.Add(idi); + } + + values.Add(s); + + { + const CInitDialogItem idi = + { DI_TEXT, 30, startY, 0, 0, false, false, 0, false, -1, NULL, NULL }; + initDialogItems.Add(idi); + } + } + } + } + + const unsigned numLines = values.Size(); + for (i = 0; i < numLines; i++) + { + CInitDialogItem &idi = initDialogItems[1 + i * 2 + 1]; + idi.DataString = values[i]; + } + + const unsigned numDialogItems = initDialogItems.Size(); + + CObjArray dialogItems(numDialogItems); + g_StartupInfo.InitDialogItems(initDialogItems.ConstData(), dialogItems, numDialogItems); + + unsigned maxLen = 0; + + for (i = 0; i < numLines; i++) + { + FarDialogItem &dialogItem = dialogItems[1 + i * 2]; + unsigned len = (unsigned)strlen(dialogItem.Data); + if (len > maxLen) + maxLen = len; + } + + unsigned maxLen2 = 0; + const unsigned kSpace = 10; + + for (i = 0; i < numLines; i++) + { + FarDialogItem &dialogItem = dialogItems[1 + i * 2 + 1]; + const unsigned len = (unsigned)strlen(dialogItem.Data); + if (len > maxLen2) + maxLen2 = len; + dialogItem.X1 = (int)(maxLen + kSpace); + } + + size = (int)numLines + 6; + xSize = (int)(maxLen + kSpace + maxLen2 + 5); + FarDialogItem &firstDialogItem = dialogItems[0]; + firstDialogItem.Y2 = size - 2; + firstDialogItem.X2 = xSize - 4; + + /* int askCode = */ g_StartupInfo.ShowDialog(xSize, size, NULL, dialogItems, numDialogItems); + return S_OK; +} + +int CPlugin::ProcessKey(int key, unsigned controlState) +{ + if (key == VK_F7 && controlState == 0) + { + CreateFolder(); + return TRUE; + } + + if (controlState == PKF_CONTROL && key == 'A') + { + HRESULT result = ShowAttributesWindow(); + if (result == S_OK) + return TRUE; + if (result == S_FALSE) + return FALSE; + throw "Error"; + } + + if ((controlState & PKF_ALT) != 0 && key == VK_F6) + { + FString folderPath; + if (!GetOnlyDirPrefix(m_FileName, folderPath)) + return FALSE; + PanelInfo panelInfo; + g_StartupInfo.ControlGetActivePanelInfo(panelInfo); + GetFilesReal(panelInfo.SelectedItems, + (unsigned)panelInfo.SelectedItemsNumber, FALSE, + UnicodeStringToMultiByte(fs2us(folderPath), CP_OEMCP), OPM_SILENT, true); + g_StartupInfo.Control(this, FCTL_UPDATEPANEL, NULL); + g_StartupInfo.Control(this, FCTL_REDRAWPANEL, NULL); + g_StartupInfo.Control(this, FCTL_UPDATEANOTHERPANEL, NULL); + g_StartupInfo.Control(this, FCTL_REDRAWANOTHERPANEL, NULL); + return TRUE; + } + + return FALSE; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Far/Plugin.h b/src/plugins/7zip/7za/cpp/7zip/UI/Far/Plugin.h new file mode 100644 index 00000000..84f6bed3 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Far/Plugin.h @@ -0,0 +1,94 @@ +// 7zip/Far/Plugin.h + +#ifndef ZIP7_INC_7ZIP_FAR_PLUGIN_H +#define ZIP7_INC_7ZIP_FAR_PLUGIN_H + +#include "../../../Common/MyCom.h" + +// #include "../../../Windows/COM.h" +#include "../../../Windows/FileFind.h" +#include "../../../Windows/PropVariant.h" + +#include "../Common/WorkDir.h" + +#include "../Agent/Agent.h" + +#include "FarUtils.h" + +const UInt32 kNumInfoLinesMax = 64; + +class CPlugin +{ + CAgent *_agent; + CMyComPtr m_ArchiveHandler; + CMyComPtr _folder; + + // NWindows::NCOM::CComInitializer m_ComInitializer; + UString m_CurrentDir; + + UString m_PannelTitle; + FString m_FileName; + NWindows::NFile::NFind::CFileInfo m_FileInfo; + + UString _archiveTypeName; + + InfoPanelLine m_InfoLines[kNumInfoLinesMax]; + + char m_FileNameBuffer[1024]; + char m_CurrentDirBuffer[1024]; + char m_PannelTitleBuffer[1024]; + + AString PanelModeColumnTypes; + AString PanelModeColumnWidths; + // PanelMode _panelMode; + void AddColumn(PROPID aPropID); + + void EnterToDirectory(const UString &dirName); + void GetPathParts(UStringVector &pathParts); + void SetCurrentDirVar(); + // HRESULT AfterUpdate(CWorkDirTempFile &tempFile, const UStringVector &pathVector); + +public: + + bool PasswordIsDefined; + UString Password; + + CPlugin(const FString &fileName, CAgent *agent, UString archiveTypeName); + ~CPlugin(); + + void ReadPluginPanelItem(PluginPanelItem &panelItem, UInt32 itemIndex); + + int GetFindData(PluginPanelItem **panelItems,int *itemsNumber,int opMode); + void FreeFindData(PluginPanelItem *panelItem,int ItemsNumber); + int SetDirectory(const char *aszDir, int opMode); + void GetOpenPluginInfo(struct OpenPluginInfo *info); + int DeleteFiles(PluginPanelItem *panelItems, unsigned itemsNumber, int opMode); + + HRESULT ExtractFiles( + bool decompressAllItems, + const UInt32 *indices, + UInt32 numIndices, + bool silent, + NExtract::NPathMode::EEnum pathMode, + NExtract::NOverwriteMode::EEnum overwriteMode, + const UString &destPath, + bool passwordIsDefined, const UString &password); + + NFar::NFileOperationReturnCode::EEnum GetFiles(struct PluginPanelItem *panelItem, unsigned itemsNumber, + int move, char *destPath, int opMode); + + NFar::NFileOperationReturnCode::EEnum GetFilesReal(struct PluginPanelItem *panelItems, + unsigned itemsNumber, int move, const char *_aDestPath, int opMode, bool showBox); + + NFar::NFileOperationReturnCode::EEnum PutFiles(struct PluginPanelItem *panelItems, unsigned itemsNumber, + int move, int opMode); + HRESULT CreateFolder(); + + HRESULT ShowAttributesWindow(); + + int ProcessKey(int key, unsigned controlState); +}; + +HRESULT CompressFiles(const CObjectVector &pluginPanelItems); + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Far/PluginCommon.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Far/PluginCommon.cpp new file mode 100644 index 00000000..e1d44582 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Far/PluginCommon.cpp @@ -0,0 +1,50 @@ +// SevenZip/Plugin.cpp + +#include "StdAfx.h" + +#include "Plugin.h" + +/* +void CPlugin::AddRealIndexOfFile(const CArchiveFolderItem &aFolder, + int anIndexInVector, vector &aRealIndexes) +{ + const CArchiveFolderFileItem &anItem = aFolder.m_FileSubItems[anIndexInVector]; + int aHandlerItemIndex = m_ProxyHandler->GetHandlerItemIndex(anItem.m_Properties); + if (aHandlerItemIndex < 0) + throw "error"; + aRealIndexes.push_back(aHandlerItemIndex); +} + +void CPlugin::AddRealIndexes(const CArchiveFolderItem &anItem, + vector &aRealIndexes) +{ + int aHandlerItemIndex = m_ProxyHandler->GetHandlerItemIndex(anItem.m_Properties); + if (aHandlerItemIndex >= 0) // test -1 value + aRealIndexes.push_back(aHandlerItemIndex); + for (int i = 0; i < anItem.m_DirSubItems.Size(); i++) + AddRealIndexes(anItem.m_DirSubItems[i], aRealIndexes); + for (i = 0; i < anItem.m_FileSubItems.Size(); i++) + AddRealIndexOfFile(anItem, i , aRealIndexes); +} + + +void CPlugin::GetRealIndexes(PluginPanelItem *aPanelItems, int anItemsNumber, + vector &aRealIndexes) +{ + aRealIndexes.clear(); + for (int i = 0; i < anItemsNumber; i++) + { + int anIndex = aPanelItems[i].UserData; + if (anIndex < m_FolderItem->m_DirSubItems.Size()) + { + const CArchiveFolderItem &anItem = m_FolderItem->m_DirSubItems[anIndex]; + AddRealIndexes(anItem, aRealIndexes); + } + else + AddRealIndexOfFile(*m_FolderItem, anIndex - m_FolderItem->m_DirSubItems.Size(), + aRealIndexes); + } + sort(aRealIndexes.begin(), aRealIndexes.end()); +} + +*/ diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Far/PluginDelete.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Far/PluginDelete.cpp new file mode 100644 index 00000000..fdade1a3 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Far/PluginDelete.cpp @@ -0,0 +1,144 @@ +// PluginDelete.cpp + +#include "StdAfx.h" + +#include + +#include "../../../Common/StringConvert.h" +#include "FarUtils.h" + +#include "Messages.h" +#include "Plugin.h" +#include "UpdateCallbackFar.h" + +using namespace NFar; + +int CPlugin::DeleteFiles(PluginPanelItem *panelItems, unsigned numItems, int opMode) +{ + if (numItems == 0) + return FALSE; + if (_agent->IsThere_ReadOnlyArc()) + { + g_StartupInfo.ShowMessage(NMessageID::kUpdateNotSupportedForThisArchive); + return FALSE; + } + if ((opMode & OPM_SILENT) == 0) + { + const char *msgItems[]= + { + g_StartupInfo.GetMsgString(NMessageID::kDeleteTitle), + g_StartupInfo.GetMsgString(NMessageID::kDeleteFiles), + g_StartupInfo.GetMsgString(NMessageID::kDeleteDelete), + g_StartupInfo.GetMsgString(NMessageID::kDeleteCancel) + }; + + // char msg[1024]; + AString str1; + + if (numItems == 1) + { + str1 = g_StartupInfo.GetMsgString(NMessageID::kDeleteFile); + AString name (panelItems[0].FindData.cFileName); + const unsigned kSizeLimit = 48; + if (name.Len() > kSizeLimit) + { + UString s = MultiByteToUnicodeString(name, CP_OEMCP); + ReduceString(s, kSizeLimit); + name = UnicodeStringToMultiByte(s, CP_OEMCP); + } + str1.Replace(AString ("%.40s"), name); + msgItems[1] = str1; + // sprintf(msg, g_StartupInfo.GetMsgString(NMessageID::kDeleteFile), panelItems[0].FindData.cFileName); + // msgItems[2] = msg; + } + else if (numItems > 1) + { + str1 = g_StartupInfo.GetMsgString(NMessageID::kDeleteNumberOfFiles); + { + AString n; + n.Add_UInt32(numItems); + str1.Replace(AString ("%d"), n); + } + msgItems[1] = str1; + // sprintf(msg, g_StartupInfo.GetMsgString(NMessageID::kDeleteNumberOfFiles), numItems); + // msgItems[1] = msg; + } + if (g_StartupInfo.ShowMessage(FMSG_WARNING, NULL, msgItems, Z7_ARRAY_SIZE(msgItems), 2) != 0) + return (FALSE); + } + + CScreenRestorer screenRestorer; + CProgressBox progressBox; + CProgressBox *progressBoxPointer = NULL; + if ((opMode & OPM_SILENT) == 0 && (opMode & OPM_FIND ) == 0) + { + screenRestorer.Save(); + + progressBoxPointer = &progressBox; + progressBox.Init( + // g_StartupInfo.GetMsgString(NMessageID::kWaitTitle), + g_StartupInfo.GetMsgString(NMessageID::kDeleting)); + } + + /* + CWorkDirTempFile tempFile; + if (tempFile.CreateTempFile(m_FileName) != S_OK) + return FALSE; + */ + + CObjArray indices(numItems); + unsigned i; + for (i = 0; i < numItems; i++) + indices[i] = (UInt32)panelItems[i].UserData; + + /* + UStringVector pathVector; + GetPathParts(pathVector); + + CMyComPtr outArchive; + HRESULT result = m_ArchiveHandler.QueryInterface(IID_IOutFolderArchive, &outArchive); + if (result != S_OK) + { + g_StartupInfo.ShowMessage(NMessageID::kUpdateNotSupportedForThisArchive); + return FALSE; + } + */ + + CUpdateCallback100Imp *updateCallbackSpec = new CUpdateCallback100Imp; + CMyComPtr updateCallback(updateCallbackSpec); + + updateCallbackSpec->Init(/* m_ArchiveHandler, */ progressBoxPointer); + updateCallbackSpec->PasswordIsDefined = PasswordIsDefined; + updateCallbackSpec->Password = Password; + + /* + outArchive->SetFolder(_folder); + result = outArchive->DeleteItems(tempFile.OutStream, indices, numItems, updateCallback); + updateCallback.Release(); + outArchive.Release(); + + if (result == S_OK) + { + result = AfterUpdate(tempFile, pathVector); + } + */ + + HRESULT result; + { + CMyComPtr folderOperations; + result = _folder.QueryInterface(IID_IFolderOperations, &folderOperations); + if (folderOperations) + result = folderOperations->Delete(indices, numItems, updateCallback); + else if (result != S_OK) + result = E_FAIL; + } + + if (result != S_OK) + { + ShowSysErrorMessage(result); + return FALSE; + } + + SetCurrentDirVar(); + return TRUE; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Far/PluginRead.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Far/PluginRead.cpp new file mode 100644 index 00000000..7e81dddb --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Far/PluginRead.cpp @@ -0,0 +1,301 @@ +// PluginRead.cpp + +#include "StdAfx.h" + +#include "Plugin.h" + +#include "Messages.h" + +#include "../../../Common/StringConvert.h" + +#include "../../../Windows/FileName.h" +#include "../../../Windows/FileFind.h" +#include "../../../Windows/FileDir.h" + +#include "../Common/ZipRegistry.h" + +#include "ExtractEngine.h" + +using namespace NWindows; +using namespace NFile; +using namespace NDir; +using namespace NFar; + +static const char * const kHelpTopicExtrFromSevenZip = "Extract"; + +static const char kDirDelimiter = CHAR_PATH_SEPARATOR; + +static const char * const kExractPathHistoryName = "7-ZipExtractPath"; + +HRESULT CPlugin::ExtractFiles( + bool decompressAllItems, + const UInt32 *indices, + UInt32 numIndices, + bool silent, + NExtract::NPathMode::EEnum pathMode, + NExtract::NOverwriteMode::EEnum overwriteMode, + const UString &destPath, + bool passwordIsDefined, const UString &password) +{ + if (_agent->_isHashHandler) + { + g_StartupInfo.ShowMessage(NMessageID::kMoveIsNotSupported); + return NFileOperationReturnCode::kError; + } + + CScreenRestorer screenRestorer; + CProgressBox progressBox; + CProgressBox *progressBoxPointer = NULL; + if (!silent) + { + screenRestorer.Save(); + + progressBoxPointer = &progressBox; + progressBox.Init( + // g_StartupInfo.GetMsgString(NMessageID::kWaitTitle), + g_StartupInfo.GetMsgString(NMessageID::kExtracting)); + } + + + CExtractCallbackImp *extractCallbackSpec = new CExtractCallbackImp; + CMyComPtr extractCallback(extractCallbackSpec); + + extractCallbackSpec->Init( + CP_OEMCP, + progressBoxPointer, + /* + GetDefaultName(m_FileName, m_ArchiverInfo.Extension), + m_FileInfo.MTime, m_FileInfo.Attributes, + */ + passwordIsDefined, password); + + if (decompressAllItems) + return m_ArchiveHandler->Extract(pathMode, overwriteMode, + destPath, BoolToInt(false), extractCallback); + else + { + CMyComPtr archiveFolder; + _folder.QueryInterface(IID_IArchiveFolder, &archiveFolder); + + return archiveFolder->Extract(indices, numIndices, + BoolToInt(true), // includeAltStreams + BoolToInt(false), // replaceAltStreamChars + pathMode, overwriteMode, + destPath, BoolToInt(false), extractCallback); + } +} + +NFileOperationReturnCode::EEnum CPlugin::GetFiles(struct PluginPanelItem *panelItems, + unsigned itemsNumber, int move, char *destPath, int opMode) +{ + return GetFilesReal(panelItems, itemsNumber, move, + destPath, opMode, (opMode & OPM_SILENT) == 0); +} + +NFileOperationReturnCode::EEnum CPlugin::GetFilesReal(struct PluginPanelItem *panelItems, + unsigned itemsNumber, int move, const char *destPathLoc, int opMode, bool showBox) +{ + if (move != 0) + { + g_StartupInfo.ShowMessage(NMessageID::kMoveIsNotSupported); + return NFileOperationReturnCode::kError; + } + + AString destPath (destPathLoc); + UString destPathU = GetUnicodeString(destPath, CP_OEMCP); + NName::NormalizeDirPathPrefix(destPathU); + destPath = UnicodeStringToMultiByte(destPathU, CP_OEMCP); + + // bool extractSelectedFiles = true; + + NExtract::CInfo extractionInfo; + extractionInfo.PathMode = NExtract::NPathMode::kCurPaths; + extractionInfo.OverwriteMode = NExtract::NOverwriteMode::kOverwrite; + + const bool silent = (opMode & OPM_SILENT) != 0; + bool decompressAllItems = false; + UString password = Password; + bool passwordIsDefined = PasswordIsDefined; + + if (!silent) + { + const int kPathIndex = 2; + + extractionInfo.Load(); + + const int kPathModeRadioIndex = 4; + const int kOverwriteModeRadioIndex = kPathModeRadioIndex + 4; + const int kNumOverwriteOptions = 6; + const int kFilesModeIndex = kOverwriteModeRadioIndex + kNumOverwriteOptions; + const int kXSize = 76; + const int kYSize = 19; + const int kPasswordYPos = 12; + + const int kXMid = kXSize / 2; + + AString oemPassword (UnicodeStringToMultiByte(password, CP_OEMCP)); + + struct CInitDialogItem initItems[]={ + { DI_DOUBLEBOX, 3, 1, kXSize - 4, kYSize - 2, false, false, 0, false, NMessageID::kExtractTitle, NULL, NULL }, + { DI_TEXT, 5, 2, 0, 0, false, false, 0, false, NMessageID::kExtractTo, NULL, NULL }, + + { DI_EDIT, 5, 3, kXSize - 6, 3, true, false, DIF_HISTORY, false, -1, destPath, kExractPathHistoryName}, + // { DI_EDIT, 5, 3, kXSize - 6, 3, true, false, 0, false, -1, destPath, NULL}, + + { DI_SINGLEBOX, 4, 5, kXMid - 2, 5 + 4, false, false, 0, false, NMessageID::kExtractPathMode, NULL, NULL }, + { DI_RADIOBUTTON, 6, 6, 0, 0, false, + extractionInfo.PathMode == NExtract::NPathMode::kFullPaths, + DIF_GROUP, false, NMessageID::kExtractPathFull, NULL, NULL }, + { DI_RADIOBUTTON, 6, 7, 0, 0, false, + extractionInfo.PathMode == NExtract::NPathMode::kCurPaths, + 0, false, NMessageID::kExtractPathCurrent, NULL, NULL }, + { DI_RADIOBUTTON, 6, 8, 0, 0, false, + extractionInfo.PathMode == NExtract::NPathMode::kNoPaths, + false, 0, NMessageID::kExtractPathNo, NULL, NULL }, + + { DI_SINGLEBOX, kXMid, 5, kXSize - 6, 5 + kNumOverwriteOptions, false, false, 0, false, NMessageID::kExtractOwerwriteMode, NULL, NULL }, + { DI_RADIOBUTTON, kXMid + 2, 6, 0, 0, false, + extractionInfo.OverwriteMode == NExtract::NOverwriteMode::kAsk, + DIF_GROUP, false, NMessageID::kExtractOwerwriteAsk, NULL, NULL }, + { DI_RADIOBUTTON, kXMid + 2, 7, 0, 0, false, + extractionInfo.OverwriteMode == NExtract::NOverwriteMode::kOverwrite, + 0, false, NMessageID::kExtractOwerwritePrompt, NULL, NULL }, + { DI_RADIOBUTTON, kXMid + 2, 8, 0, 0, false, + extractionInfo.OverwriteMode == NExtract::NOverwriteMode::kSkip, + 0, false, NMessageID::kExtractOwerwriteSkip, NULL, NULL }, + { DI_RADIOBUTTON, kXMid + 2, 9, 0, 0, false, + extractionInfo.OverwriteMode == NExtract::NOverwriteMode::kRename, + 0, false, NMessageID::kExtractOwerwriteAutoRename, NULL, NULL }, + { DI_RADIOBUTTON, kXMid + 2, 10, 0, 0, false, + extractionInfo.OverwriteMode == NExtract::NOverwriteMode::kRenameExisting, + 0, false, NMessageID::kExtractOwerwriteAutoRenameExisting, NULL, NULL }, + + { DI_SINGLEBOX, 4, 10, kXMid- 2, 10 + 3, false, false, 0, false, NMessageID::kExtractFilesMode, NULL, NULL }, + { DI_RADIOBUTTON, 6, 11, 0, 0, false, true, DIF_GROUP, false, NMessageID::kExtractFilesSelected, NULL, NULL }, + { DI_RADIOBUTTON, 6, 12, 0, 0, false, false, 0, false, NMessageID::kExtractFilesAll, NULL, NULL }, + + { DI_SINGLEBOX, kXMid, kPasswordYPos, kXSize - 6, kPasswordYPos + 2, false, false, 0, false, NMessageID::kExtractPassword, NULL, NULL }, + { DI_PSWEDIT, kXMid + 2, kPasswordYPos + 1, kXSize - 8, 12, false, false, 0, false, -1, oemPassword, NULL}, + + { DI_TEXT, 3, kYSize - 4, 0, 0, false, false, DIF_BOXCOLOR|DIF_SEPARATOR, false, -1, "", NULL }, + + + { DI_BUTTON, 0, kYSize - 3, 0, 0, false, false, DIF_CENTERGROUP, true, NMessageID::kExtractExtract, NULL, NULL }, + { DI_BUTTON, 0, kYSize - 3, 0, 0, false, false, DIF_CENTERGROUP, false, NMessageID::kExtractCancel, NULL, NULL } + }; + + const unsigned kNumDialogItems = Z7_ARRAY_SIZE(initItems); + const unsigned kOkButtonIndex = kNumDialogItems - 2; + const unsigned kPasswordIndex = kNumDialogItems - 4; + + FarDialogItem dialogItems[kNumDialogItems]; + g_StartupInfo.InitDialogItems(initItems, dialogItems, kNumDialogItems); + for (;;) + { + int askCode = g_StartupInfo.ShowDialog(kXSize, kYSize, + kHelpTopicExtrFromSevenZip, dialogItems, kNumDialogItems); + if (askCode != kOkButtonIndex) + return NFileOperationReturnCode::kInterruptedByUser; + destPath = dialogItems[kPathIndex].Data; + destPathU = GetUnicodeString(destPath, CP_OEMCP); + destPathU.Trim(); + if (destPathU.IsEmpty()) + { + #ifdef UNDER_CE + destPathU = "\\"; + #else + FString destPathF = us2fs(destPathU); + if (!GetCurrentDir(destPathF)) + throw 318016; + NName::NormalizeDirPathPrefix(destPathF); + destPathU = fs2us(destPathF); + #endif + break; + } + else + { + if (destPathU.Back() == kDirDelimiter) + break; + } + g_StartupInfo.ShowErrorMessage("You must specify directory path"); + } + + if (dialogItems[kPathModeRadioIndex].Selected) + extractionInfo.PathMode = NExtract::NPathMode::kFullPaths; + else if (dialogItems[kPathModeRadioIndex + 1].Selected) + extractionInfo.PathMode = NExtract::NPathMode::kCurPaths; + else if (dialogItems[kPathModeRadioIndex + 2].Selected) + extractionInfo.PathMode = NExtract::NPathMode::kNoPaths; + else + throw 31806; + + if (dialogItems[kOverwriteModeRadioIndex].Selected) + extractionInfo.OverwriteMode = NExtract::NOverwriteMode::kAsk; + else if (dialogItems[kOverwriteModeRadioIndex + 1].Selected) + extractionInfo.OverwriteMode = NExtract::NOverwriteMode::kOverwrite; + else if (dialogItems[kOverwriteModeRadioIndex + 2].Selected) + extractionInfo.OverwriteMode = NExtract::NOverwriteMode::kSkip; + else if (dialogItems[kOverwriteModeRadioIndex + 3].Selected) + extractionInfo.OverwriteMode = NExtract::NOverwriteMode::kRename; + else if (dialogItems[kOverwriteModeRadioIndex + 4].Selected) + extractionInfo.OverwriteMode = NExtract::NOverwriteMode::kRenameExisting; + else + throw 31806; + + if (dialogItems[kFilesModeIndex].Selected) + decompressAllItems = false; + else if (dialogItems[kFilesModeIndex + 1].Selected) + decompressAllItems = true; + else + throw 31806; + + extractionInfo.Save(); + + if (dialogItems[kFilesModeIndex].Selected) + { + // extractSelectedFiles = true; + } + else if (dialogItems[kFilesModeIndex + 1].Selected) + { + // extractSelectedFiles = false; + } + else + throw 31806; + + oemPassword = dialogItems[kPasswordIndex].Data; + password = MultiByteToUnicodeString(oemPassword, CP_OEMCP); + passwordIsDefined = !password.IsEmpty(); + } + + CreateComplexDir(us2fs(destPathU)); + + /* + vector realIndices; + if (!decompressAllItems) + GetRealIndexes(panelItems, itemsNumber, realIndices); + */ + CObjArray indices(itemsNumber); + for (unsigned i = 0; i < itemsNumber; i++) + indices[i] = (UInt32)panelItems[i].UserData; + + const HRESULT result = ExtractFiles(decompressAllItems, indices, itemsNumber, + !showBox, extractionInfo.PathMode, extractionInfo.OverwriteMode, + destPathU, + passwordIsDefined, password); + // HRESULT result = ExtractFiles(decompressAllItems, realIndices, !showBox, + // extractionInfo, destPath, passwordIsDefined, password); + if (result != S_OK) + { + if (result == E_ABORT) + return NFileOperationReturnCode::kInterruptedByUser; + ShowSysErrorMessage(result); + return NFileOperationReturnCode::kError; + } + + // if (move != 0) + // { + // if (DeleteFiles(panelItems, itemsNumber, opMode) == FALSE) + // return NFileOperationReturnCode::kError; + // } + return NFileOperationReturnCode::kSuccess; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Far/PluginWrite.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Far/PluginWrite.cpp new file mode 100644 index 00000000..8a000496 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Far/PluginWrite.cpp @@ -0,0 +1,840 @@ +// PluginWrite.cpp + +#include "StdAfx.h" + +#include + +#include "Plugin.h" + +#include "../../../Common/StringConvert.h" +#include "../../../Common/Wildcard.h" + +#include "../../../Windows/FileName.h" +#include "../../../Windows/FileFind.h" + +#include "../Common/ZipRegistry.h" + +#include "../Agent/Agent.h" + +#include "ProgressBox.h" +#include "Messages.h" +#include "UpdateCallbackFar.h" + +using namespace NWindows; +using namespace NFile; +using namespace NDir; +using namespace NFar; + +using namespace NUpdateArchive; + +static const char * const kHelpTopic = "Update"; + +static const char * const kArchiveHistoryKeyName = "7-ZipArcName"; + +static const UInt32 g_MethodMap[] = { 0, 1, 3, 5, 7, 9 }; + +static HRESULT SetOutProperties(IOutFolderArchive *outArchive, UInt32 method) +{ + CMyComPtr setProperties; + if (outArchive->QueryInterface(IID_ISetProperties, (void **)&setProperties) == S_OK) + { + /* + UStringVector realNames; + realNames.Add(UString("x")); + NCOM::CPropVariant value = (UInt32)method; + CRecordVector names; + FOR_VECTOR (i, realNames) + names.Add(realNames[i]); + RINOK(setProperties->SetProperties(&names.Front(), &value, names.Size())); + */ + NCOM::CPropVariant value = (UInt32)method; + const wchar_t *name = L"x"; + RINOK(setProperties->SetProperties(&name, &value, 1)) + } + return S_OK; +} + +/* +HRESULT CPlugin::AfterUpdate(CWorkDirTempFile &tempFile, const UStringVector &pathVector) +{ + _folder.Release(); + m_ArchiveHandler->Close(); + + RINOK(tempFile.MoveToOriginal(true)); + + RINOK(m_ArchiveHandler->ReOpen(NULL)); // check it + + m_ArchiveHandler->BindToRootFolder(&_folder); + FOR_VECTOR (i, pathVector) + { + CMyComPtr newFolder; + _folder->BindToFolder(pathVector[i], &newFolder); + if (!newFolder) + break; + _folder = newFolder; + } + return S_OK; +} +*/ + +NFileOperationReturnCode::EEnum CPlugin::PutFiles( + struct PluginPanelItem *panelItems, unsigned numItems, + int moveMode, int opMode) +{ + if (moveMode != 0 + && _agent->_isHashHandler) + { + g_StartupInfo.ShowMessage(NMessageID::kMoveIsNotSupported); + return NFileOperationReturnCode::kError; + } + + if (numItems <= 0) + return NFileOperationReturnCode::kError; + + if (_agent->IsThere_ReadOnlyArc()) + { + g_StartupInfo.ShowMessage(NMessageID::kUpdateNotSupportedForThisArchive); + return NFileOperationReturnCode::kError; + } + + const int kYSize = 14; + const int kXMid = 38; + + NCompression::CInfo compressionInfo; + compressionInfo.Load(); + + unsigned methodIndex = 0; + + unsigned i; + for (i = Z7_ARRAY_SIZE(g_MethodMap); i != 0;) + { + i--; + if (compressionInfo.Level >= g_MethodMap[i]) + { + methodIndex = i; + break; + } + } + + const int kMethodRadioIndex = 2; + const int kModeRadioIndex = kMethodRadioIndex + 7; + + struct CInitDialogItem initItems[]={ + { DI_DOUBLEBOX, 3, 1, 72, kYSize - 2, false, false, 0, false, NMessageID::kUpdateTitle, NULL, NULL }, + + { DI_SINGLEBOX, 4, 2, kXMid - 2, 2 + 7, false, false, 0, false, NMessageID::kUpdateMethod, NULL, NULL }, + + { DI_RADIOBUTTON, 6, 3, 0, 0, methodIndex == 0, methodIndex == 0, DIF_GROUP, false, NMessageID::kUpdateMethod_Store, NULL, NULL }, + { DI_RADIOBUTTON, 6, 4, 0, 0, methodIndex == 1, methodIndex == 1, 0, false, NMessageID::kUpdateMethod_Fastest, NULL, NULL }, + { DI_RADIOBUTTON, 6, 5, 0, 0, methodIndex == 2, methodIndex == 2, 0, false, NMessageID::kUpdateMethod_Fast, NULL, NULL }, + { DI_RADIOBUTTON, 6, 6, 0, 0, methodIndex == 3, methodIndex == 3, 0, false, NMessageID::kUpdateMethod_Normal, NULL, NULL }, + { DI_RADIOBUTTON, 6, 7, 0, 0, methodIndex == 4, methodIndex == 4, 0, false, NMessageID::kUpdateMethod_Maximum, NULL, NULL }, + { DI_RADIOBUTTON, 6, 8, 0, 0, methodIndex == 5, methodIndex == 5, 0, false, NMessageID::kUpdateMethod_Ultra, NULL, NULL }, + + { DI_SINGLEBOX, kXMid, 2, 70, 2 + 5, false, false, 0, false, NMessageID::kUpdateMode, NULL, NULL }, + + { DI_RADIOBUTTON, kXMid + 2, 3, 0, 0, false, true, DIF_GROUP, false, NMessageID::kUpdateMode_Add, NULL, NULL }, + { DI_RADIOBUTTON, kXMid + 2, 4, 0, 0, false, false, 0, false, NMessageID::kUpdateMode_Update, NULL, NULL }, + { DI_RADIOBUTTON, kXMid + 2, 5, 0, 0, false, false, 0, false, NMessageID::kUpdateMode_Fresh, NULL, NULL }, + { DI_RADIOBUTTON, kXMid + 2, 6, 0, 0, false, false, 0, false, NMessageID::kUpdateMode_Sync, NULL, NULL }, + + { DI_TEXT, 3, kYSize - 4, 0, 0, false, false, DIF_BOXCOLOR|DIF_SEPARATOR, false, -1, "", NULL }, + + { DI_BUTTON, 0, kYSize - 3, 0, 0, false, false, DIF_CENTERGROUP, true, NMessageID::kUpdateAdd, NULL, NULL }, + { DI_BUTTON, 0, kYSize - 3, 0, 0, false, false, DIF_CENTERGROUP, false, NMessageID::kCancel, NULL, NULL } + }; + + const int kNumDialogItems = Z7_ARRAY_SIZE(initItems); + const int kOkButtonIndex = kNumDialogItems - 2; + FarDialogItem dialogItems[kNumDialogItems]; + g_StartupInfo.InitDialogItems(initItems, dialogItems, kNumDialogItems); + const int askCode = g_StartupInfo.ShowDialog(76, kYSize, + kHelpTopic, dialogItems, kNumDialogItems); + if (askCode != kOkButtonIndex) + return NFileOperationReturnCode::kInterruptedByUser; + + compressionInfo.Level = g_MethodMap[0]; + for (i = 0; i < Z7_ARRAY_SIZE(g_MethodMap); i++) + if (dialogItems[kMethodRadioIndex + i].Selected) + compressionInfo.Level = g_MethodMap[i]; + + const CActionSet *actionSet; + + if (dialogItems[kModeRadioIndex ].Selected) actionSet = &k_ActionSet_Add; + else if (dialogItems[kModeRadioIndex + 1].Selected) actionSet = &k_ActionSet_Update; + else if (dialogItems[kModeRadioIndex + 2].Selected) actionSet = &k_ActionSet_Fresh; + else if (dialogItems[kModeRadioIndex + 3].Selected) actionSet = &k_ActionSet_Sync; + else throw 51751; + + compressionInfo.Save(); + + CWorkDirTempFile tempFile; + if (tempFile.CreateTempFile(m_FileName) != S_OK) + return NFileOperationReturnCode::kError; + + + /* + CSysStringVector fileNames; + for (int i = 0; i < numItems; i++) + { + const PluginPanelItem &panelItem = panelItems[i]; + CSysString fullName; + if (!MyGetFullPathName(panelItem.FindData.cFileName, fullName)) + return NFileOperationReturnCode::kError; + fileNames.Add(fullName); + } + */ + + CScreenRestorer screenRestorer; + CProgressBox progressBox; + CProgressBox *progressBoxPointer = NULL; + if ((opMode & OPM_SILENT) == 0 && (opMode & OPM_FIND ) == 0) + { + screenRestorer.Save(); + + progressBoxPointer = &progressBox; + progressBox.Init( + // g_StartupInfo.GetMsgString(NMessageID::kWaitTitle), + g_StartupInfo.GetMsgString(NMessageID::kUpdating)); + } + + UStringVector pathVector; + GetPathParts(pathVector); + + UStringVector fileNames; + fileNames.ClearAndReserve(numItems); + for (i = 0; i < (unsigned)numItems; i++) + fileNames.AddInReserved(MultiByteToUnicodeString(panelItems[i].FindData.cFileName, CP_OEMCP)); + CObjArray fileNamePointers(numItems); + for (i = 0; i < (unsigned)numItems; i++) + fileNamePointers[i] = fileNames[i]; + + CMyComPtr outArchive; + HRESULT result = m_ArchiveHandler.QueryInterface(IID_IOutFolderArchive, &outArchive); + if (result != S_OK) + { + g_StartupInfo.ShowMessage(NMessageID::kUpdateNotSupportedForThisArchive); + return NFileOperationReturnCode::kError; + } + + /* + BYTE actionSetByte[NUpdateArchive::NPairState::kNumValues]; + for (i = 0; i < NUpdateArchive::NPairState::kNumValues; i++) + actionSetByte[i] = (BYTE)actionSet->StateActions[i]; + */ + + CUpdateCallback100Imp *updateCallbackSpec = new CUpdateCallback100Imp; + CMyComPtr updateCallback(updateCallbackSpec ); + + updateCallbackSpec->Init(/* m_ArchiveHandler, */ progressBoxPointer); + updateCallbackSpec->PasswordIsDefined = PasswordIsDefined; + updateCallbackSpec->Password = Password; + + if (!_agent->_isHashHandler) + { + if (SetOutProperties(outArchive, compressionInfo.Level) != S_OK) + return NFileOperationReturnCode::kError; + } + + /* + outArchive->SetFolder(_folder); + outArchive->SetFiles(L"", fileNamePointers, numItems); + // FStringVector requestedPaths; + // FStringVector processedPaths; + result = outArchive->DoOperation2( + // &requestedPaths, &processedPaths, + NULL, NULL, + tempFile.OutStream, actionSetByte, NULL, updateCallback); + updateCallback.Release(); + outArchive.Release(); + + if (result == S_OK) + { + result = AfterUpdate(tempFile, pathVector); + } + */ + + { + result = _agent->SetFiles(L"", fileNamePointers, numItems); + if (result == S_OK) + { + CAgentFolder *agentFolder = NULL; + { + CMyComPtr afi; + _folder.QueryInterface(IID_IArchiveFolderInternal, &afi); + if (afi) + afi->GetAgentFolder(&agentFolder); + } + if (agentFolder) + result = agentFolder->CommonUpdateOperation(AGENT_OP_Uni, + (moveMode != 0), NULL, actionSet, NULL, 0, updateCallback); + else + result = E_FAIL; + } + } + + if (result != S_OK) + { + ShowSysErrorMessage(result); + return NFileOperationReturnCode::kError; + } + + return NFileOperationReturnCode::kSuccess; +} + +namespace NPathType +{ + enum EEnum + { + kLocal, + kUNC + }; + EEnum GetPathType(const UString &path); +} + +struct CParsedPath +{ + UString Prefix; // Disk or UNC with slash + UStringVector PathParts; + void ParsePath(const UString &path); + UString MergePath() const; +}; + +static const char kDirDelimiter = CHAR_PATH_SEPARATOR; +static const wchar_t kDiskDelimiter = L':'; + +namespace NPathType +{ + EEnum GetPathType(const UString &path) + { + if (path.Len() <= 2) + return kLocal; + if (path[0] == kDirDelimiter && path[1] == kDirDelimiter) + return kUNC; + return kLocal; + } +} + +void CParsedPath::ParsePath(const UString &path) +{ + int curPos = 0; + switch (NPathType::GetPathType(path)) + { + case NPathType::kLocal: + { + const int posDiskDelimiter = path.Find(kDiskDelimiter); + if (posDiskDelimiter >= 0) + { + curPos = posDiskDelimiter + 1; + if ((int)path.Len() > curPos) + if (path[curPos] == kDirDelimiter) + curPos++; + } + break; + } + case NPathType::kUNC: + { + // the bug was fixed: + curPos = path.Find((wchar_t)kDirDelimiter, 2); + if (curPos < 0) + curPos = (int)path.Len(); + else + curPos++; + } + } + Prefix = path.Left(curPos); + SplitPathToParts(path.Ptr(curPos), PathParts); +} + +UString CParsedPath::MergePath() const +{ + UString result = Prefix; + FOR_VECTOR (i, PathParts) + { + if (i != 0) + // result += kDirDelimiter; + result.Add_PathSepar(); + result += PathParts[i]; + } + return result; +} + + +static void SetArcName(UString &arcName, const CArcInfoEx &arcInfo) +{ + if (!arcInfo.Flags_KeepName()) + { + int dotPos = arcName.ReverseFind_Dot(); + int slashPos = arcName.ReverseFind_PathSepar(); + if (dotPos > slashPos + 1) + arcName.DeleteFrom(dotPos); + } + arcName.Add_Dot(); + arcName += arcInfo.GetMainExt(); +} + +HRESULT CompressFiles(const CObjectVector &pluginPanelItems) +{ + if (pluginPanelItems.Size() == 0) + return E_FAIL; + + UStringVector fileNames; + { + FOR_VECTOR (i, pluginPanelItems) + { + const PluginPanelItem &panelItem = pluginPanelItems[i]; + if (strcmp(panelItem.FindData.cFileName, "..") == 0 && + NFind::NAttributes::IsDir(panelItem.FindData.dwFileAttributes)) + return E_FAIL; + if (strcmp(panelItem.FindData.cFileName, ".") == 0 && + NFind::NAttributes::IsDir(panelItem.FindData.dwFileAttributes)) + return E_FAIL; + FString fullPath; + FString fileNameUnicode = us2fs(MultiByteToUnicodeString(panelItem.FindData.cFileName, CP_OEMCP)); + if (!MyGetFullPathName(fileNameUnicode, fullPath)) + return E_FAIL; + fileNames.Add(fs2us(fullPath)); + } + } + + NCompression::CInfo compressionInfo; + compressionInfo.Load(); + + int archiverIndex = -1; + + /* + CCodecs *codecs = new CCodecs; + CMyComPtr compressCodecsInfo = codecs; + if (codecs->Load() != S_OK) + throw "Can't load 7-Zip codecs"; + */ + + if (LoadGlobalCodecs() != S_OK) + throw "Can't load 7-Zip codecs"; + + CCodecs *codecs = g_CodecsObj; + + { + FOR_VECTOR (i, codecs->Formats) + { + const CArcInfoEx &arcInfo = codecs->Formats[i]; + if (arcInfo.UpdateEnabled) + { + if (archiverIndex == -1) + archiverIndex = (int)i; + if (MyStringCompareNoCase(arcInfo.Name, compressionInfo.ArcType) == 0) + archiverIndex = (int)i; + } + } + } + + if (archiverIndex < 0) + throw "there is no output handler"; + + UString resultPath; + { + CParsedPath parsedPath; + parsedPath.ParsePath(fileNames.Front()); + if (parsedPath.PathParts.Size() == 0) + return E_FAIL; + if (fileNames.Size() == 1 || parsedPath.PathParts.Size() == 1) + { + // CSysString pureName, dot, extension; + resultPath = parsedPath.PathParts.Back(); + } + else + { + parsedPath.PathParts.DeleteBack(); + resultPath = parsedPath.PathParts.Back(); + } + } + UString archiveNameSrc = resultPath; + UString arcName = archiveNameSrc; + + int prevFormat = archiverIndex; + SetArcName(arcName, codecs->Formats[archiverIndex]); + + const CActionSet *actionSet = &k_ActionSet_Add; + + for (;;) + { + AString archiveNameA (UnicodeStringToMultiByte(arcName, CP_OEMCP)); + const int kYSize = 16; + const int kXMid = 38; + + const int kArchiveNameIndex = 2; + const int kMethodRadioIndex = kArchiveNameIndex + 2; + const int kModeRadioIndex = kMethodRadioIndex + 7; + + // char updateAddToArchiveString[512]; + AString str1; + { + const CArcInfoEx &arcInfo = codecs->Formats[archiverIndex]; + const AString s (UnicodeStringToMultiByte(arcInfo.Name, CP_OEMCP)); + str1 = g_StartupInfo.GetMsgString(NMessageID::kUpdateAddToArchive); + str1.Replace(AString ("%s"), s); + /* + sprintf(updateAddToArchiveString, + g_StartupInfo.GetMsgString(NMessageID::kUpdateAddToArchive), (const char *)s); + */ + } + + unsigned methodIndex = 0; + unsigned i; + for (i = Z7_ARRAY_SIZE(g_MethodMap); i != 0;) + { + i--; + if (compressionInfo.Level >= g_MethodMap[i]) + { + methodIndex = i; + break; + } + } + + const struct CInitDialogItem initItems[]= + { + { DI_DOUBLEBOX, 3, 1, 72, kYSize - 2, false, false, 0, false, NMessageID::kUpdateTitle, NULL, NULL }, + + { DI_TEXT, 5, 2, 0, 0, false, false, 0, false, -1, str1, NULL }, + + { DI_EDIT, 5, 3, 70, 3, true, false, DIF_HISTORY, false, -1, archiveNameA, kArchiveHistoryKeyName}, + // { DI_EDIT, 5, 3, 70, 3, true, false, 0, false, -1, arcName, NULL}, + + { DI_SINGLEBOX, 4, 4, kXMid - 2, 4 + 7, false, false, 0, false, NMessageID::kUpdateMethod, NULL, NULL }, + + { DI_RADIOBUTTON, 6, 5, 0, 0, false, methodIndex == 0, DIF_GROUP, false, NMessageID::kUpdateMethod_Store, NULL, NULL }, + { DI_RADIOBUTTON, 6, 6, 0, 0, false, methodIndex == 1, 0, false, NMessageID::kUpdateMethod_Fastest, NULL, NULL }, + { DI_RADIOBUTTON, 6, 7, 0, 0, false, methodIndex == 2, 0, false, NMessageID::kUpdateMethod_Fast, NULL, NULL }, + { DI_RADIOBUTTON, 6, 8, 0, 0, false, methodIndex == 3, 0, false, NMessageID::kUpdateMethod_Normal, NULL, NULL }, + { DI_RADIOBUTTON, 6, 9, 0, 0, false, methodIndex == 4, 0, false, NMessageID::kUpdateMethod_Maximum, NULL, NULL }, + { DI_RADIOBUTTON, 6,10, 0, 0, false, methodIndex == 5, 0, false, NMessageID::kUpdateMethod_Ultra, NULL, NULL }, + + { DI_SINGLEBOX, kXMid, 4, 70, 4 + 5, false, false, 0, false, NMessageID::kUpdateMode, NULL, NULL }, + + { DI_RADIOBUTTON, kXMid + 2, 5, 0, 0, false, actionSet == &k_ActionSet_Add, DIF_GROUP, false, NMessageID::kUpdateMode_Add, NULL, NULL }, + { DI_RADIOBUTTON, kXMid + 2, 6, 0, 0, false, actionSet == &k_ActionSet_Update, 0, false, NMessageID::kUpdateMode_Update, NULL, NULL }, + { DI_RADIOBUTTON, kXMid + 2, 7, 0, 0, false, actionSet == &k_ActionSet_Fresh, 0, false, NMessageID::kUpdateMode_Fresh, NULL, NULL }, + { DI_RADIOBUTTON, kXMid + 2, 8, 0, 0, false, actionSet == &k_ActionSet_Sync, 0, false, NMessageID::kUpdateMode_Sync, NULL, NULL }, + + { DI_TEXT, 3, kYSize - 4, 0, 0, false, false, DIF_BOXCOLOR|DIF_SEPARATOR, false, -1, "", NULL }, + + { DI_BUTTON, 0, kYSize - 3, 0, 0, false, false, DIF_CENTERGROUP, true, NMessageID::kUpdateAdd, NULL, NULL }, + { DI_BUTTON, 0, kYSize - 3, 0, 0, false, false, DIF_CENTERGROUP, false, NMessageID::kUpdateSelectArchiver, NULL, NULL }, + { DI_BUTTON, 0, kYSize - 3, 0, 0, false, false, DIF_CENTERGROUP, false, NMessageID::kCancel, NULL, NULL } + }; + + const int kNumDialogItems = Z7_ARRAY_SIZE(initItems); + + const int kOkButtonIndex = kNumDialogItems - 3; + const int kSelectarchiverButtonIndex = kNumDialogItems - 2; + + FarDialogItem dialogItems[kNumDialogItems]; + g_StartupInfo.InitDialogItems(initItems, dialogItems, kNumDialogItems); + int askCode = g_StartupInfo.ShowDialog(76, kYSize, + kHelpTopic, dialogItems, kNumDialogItems); + + archiveNameA = dialogItems[kArchiveNameIndex].Data; + archiveNameA.Trim(); + MultiByteToUnicodeString2(arcName, archiveNameA, CP_OEMCP); + + compressionInfo.Level = g_MethodMap[0]; + for (i = 0; i < Z7_ARRAY_SIZE(g_MethodMap); i++) + if (dialogItems[kMethodRadioIndex + i].Selected) + compressionInfo.Level = g_MethodMap[i]; + + if (dialogItems[kModeRadioIndex ].Selected) actionSet = &k_ActionSet_Add; + else if (dialogItems[kModeRadioIndex + 1].Selected) actionSet = &k_ActionSet_Update; + else if (dialogItems[kModeRadioIndex + 2].Selected) actionSet = &k_ActionSet_Fresh; + else if (dialogItems[kModeRadioIndex + 3].Selected) actionSet = &k_ActionSet_Sync; + else throw 51751; + + if (askCode == kSelectarchiverButtonIndex) + { + CUIntVector indices; + AStringVector archiverNames; + FOR_VECTOR (k, codecs->Formats) + { + const CArcInfoEx &arc = codecs->Formats[k]; + if (arc.UpdateEnabled) + { + indices.Add(k); + archiverNames.Add(GetOemString(arc.Name)); + } + } + + const int index = g_StartupInfo.Menu(FMENU_AUTOHIGHLIGHT, + g_StartupInfo.GetMsgString(NMessageID::kUpdateSelectArchiverMenuTitle), + NULL, archiverNames, archiverIndex); + if (index >= 0) + { + const CArcInfoEx &prevArchiverInfo = codecs->Formats[prevFormat]; + if (prevArchiverInfo.Flags_KeepName()) + { + const UString &prevExtension = prevArchiverInfo.GetMainExt(); + const unsigned prevExtensionLen = prevExtension.Len(); + if (arcName.Len() >= prevExtensionLen && + MyStringCompareNoCase(arcName.RightPtr(prevExtensionLen), prevExtension) == 0) + { + const unsigned pos = arcName.Len() - prevExtensionLen; + if (pos > 2) + { + if (arcName[pos - 1] == '.') + arcName.DeleteFrom(pos - 1); + } + } + } + + archiverIndex = (int)indices[index]; + const CArcInfoEx &arcInfo = codecs->Formats[archiverIndex]; + prevFormat = archiverIndex; + + if (arcInfo.Flags_KeepName()) + arcName = archiveNameSrc; + SetArcName(arcName, arcInfo); + } + continue; + } + + if (askCode != kOkButtonIndex) + return E_ABORT; + + break; + } + + const CArcInfoEx &archiverInfoFinal = codecs->Formats[archiverIndex]; + compressionInfo.ArcType = archiverInfoFinal.Name; + compressionInfo.Save(); + + NWorkDir::CInfo workDirInfo; + workDirInfo.Load(); + + FString fullArcName; + if (!MyGetFullPathName(us2fs(arcName), fullArcName)) + return E_FAIL; + + CWorkDirTempFile tempFile; + RINOK(tempFile.CreateTempFile(fullArcName)) + CScreenRestorer screenRestorer; + CProgressBox progressBox; + CProgressBox *progressBoxPointer = NULL; + + screenRestorer.Save(); + + progressBoxPointer = &progressBox; + progressBox.Init( + // g_StartupInfo.GetMsgString(NMessageID::kWaitTitle), + g_StartupInfo.GetMsgString(NMessageID::kUpdating)); + + + NFind::CFileInfo fileInfo; + + CMyComPtr outArchive; + + CMyComPtr archiveHandler; + if (fileInfo.Find(fullArcName)) + { + if (fileInfo.IsDir()) + throw "There is Directory with such name"; + + CAgent *agentSpec = new CAgent; + archiveHandler = agentSpec; + // CLSID realClassID; + CMyComBSTR archiveType; + RINOK(archiveHandler->Open(NULL, + GetUnicodeString(fullArcName, CP_OEMCP), UString(), + // &realClassID, + &archiveType, + NULL)) + + if (MyStringCompareNoCase(archiverInfoFinal.Name, (const wchar_t *)archiveType) != 0) + throw "Type of existing archive differs from specified type"; + const HRESULT result = archiveHandler.QueryInterface( + IID_IOutFolderArchive, &outArchive); + if (result != S_OK) + { + g_StartupInfo.ShowMessage(NMessageID::kUpdateNotSupportedForThisArchive); + return E_FAIL; + } + } + else + { + // HRESULT result = outArchive.CoCreateInstance(classID); + CAgent *agentSpec = new CAgent; + outArchive = agentSpec; + + /* + HRESULT result = outArchive.CoCreateInstance(CLSID_CAgentArchiveHandler); + if (result != S_OK) + { + g_StartupInfo.ShowMessage(NMessageID::kUpdateNotSupportedForThisArchive); + return E_FAIL; + } + */ + } + + CObjArray fileNamePointers(fileNames.Size()); + + unsigned i; + for (i = 0; i < fileNames.Size(); i++) + fileNamePointers[i] = fileNames[i]; + + outArchive->SetFolder(NULL); + outArchive->SetFiles(L"", fileNamePointers, fileNames.Size()); + BYTE actionSetByte[NUpdateArchive::NPairState::kNumValues]; + for (i = 0; i < NUpdateArchive::NPairState::kNumValues; i++) + actionSetByte[i] = (BYTE)actionSet->StateActions[i]; + + CUpdateCallback100Imp *updateCallbackSpec = new CUpdateCallback100Imp; + CMyComPtr updateCallback(updateCallbackSpec ); + + updateCallbackSpec->Init(/* archiveHandler, */ progressBoxPointer); + + + RINOK(SetOutProperties(outArchive, compressionInfo.Level)) + + // FStringVector requestedPaths; + // FStringVector processedPaths; + HRESULT result = outArchive->DoOperation( + // &requestedPaths, &processedPaths, + NULL, NULL, + codecs, archiverIndex, + tempFile.OutStream, actionSetByte, + NULL, updateCallback); + updateCallback.Release(); + outArchive.Release(); + + if (result != S_OK) + { + ShowSysErrorMessage(result); + return result; + } + + if (archiveHandler) + { + archiveHandler->Close(); + } + + result = tempFile.MoveToOriginal(archiveHandler != NULL); + if (result != S_OK) + { + ShowSysErrorMessage(result); + return result; + } + return S_OK; +} + + +static const char * const k_CreateFolder_History = "NewFolder"; // we use default FAR folder name + +HRESULT CPlugin::CreateFolder() +{ + if (_agent->IsThere_ReadOnlyArc()) + { + g_StartupInfo.ShowMessage(NMessageID::kUpdateNotSupportedForThisArchive); + return TRUE; + } + + UString destPathU; + { + const int kXSize = 60; + const int kYSize = 8; + const int kPathIndex = 2; + + AString destPath ("New Folder"); + + const struct CInitDialogItem initItems[]={ + { DI_DOUBLEBOX, 3, 1, kXSize - 4, kYSize - 2, false, false, 0, false, + -1, "Create Folder", NULL }, + + { DI_TEXT, 5, 2, 0, 0, false, false, 0, false, -1, "Folder name:", NULL }, + + { DI_EDIT, 5, 3, kXSize - 6, 3, true, false, DIF_HISTORY, false, -1, destPath, k_CreateFolder_History }, + + { DI_BUTTON, 0, kYSize - 3, 0, 0, false, false, DIF_CENTERGROUP, true, NMessageID::kOk, NULL, NULL }, + { DI_BUTTON, 0, kYSize - 3, 0, 0, false, false, DIF_CENTERGROUP, false, NMessageID::kCancel, NULL, NULL } + }; + + const int kNumDialogItems = Z7_ARRAY_SIZE(initItems); + const int kOkButtonIndex = kNumDialogItems - 2; + + FarDialogItem dialogItems[kNumDialogItems]; + g_StartupInfo.InitDialogItems(initItems, dialogItems, kNumDialogItems); + for (;;) + { + int askCode = g_StartupInfo.ShowDialog(kXSize, kYSize, + NULL, // kHelpTopic + dialogItems, kNumDialogItems); + if (askCode != kOkButtonIndex) + return E_ABORT; + destPath = dialogItems[kPathIndex].Data; + destPathU = GetUnicodeString(destPath, CP_OEMCP); + destPathU.Trim(); + if (!destPathU.IsEmpty()) + break; + g_StartupInfo.ShowErrorMessage("You must specify folder name"); + } + + } + + CScreenRestorer screenRestorer; + CProgressBox progressBox; + CProgressBox *progressBoxPointer = NULL; + // if ((opMode & OPM_SILENT) == 0 && (opMode & OPM_FIND ) == 0) + { + screenRestorer.Save(); + + progressBoxPointer = &progressBox; + progressBox.Init( + // g_StartupInfo.GetMsgString(NMessageID::kWaitTitle), + g_StartupInfo.GetMsgString(NMessageID::kDeleting)); + } + + CUpdateCallback100Imp *updateCallbackSpec = new CUpdateCallback100Imp; + CMyComPtr updateCallback(updateCallbackSpec); + + updateCallbackSpec->Init(/* m_ArchiveHandler, */ progressBoxPointer); + updateCallbackSpec->PasswordIsDefined = PasswordIsDefined; + updateCallbackSpec->Password = Password; + + HRESULT result; + { + CMyComPtr folderOperations; + result = _folder.QueryInterface(IID_IFolderOperations, &folderOperations); + if (folderOperations) + result = folderOperations->CreateFolder(destPathU, updateCallback); + else if (result != S_OK) + result = E_FAIL; + } + + if (result != S_OK) + { + ShowSysErrorMessage(result); + return result; + } + + g_StartupInfo.Control(this, FCTL_UPDATEPANEL, (void *)1); + g_StartupInfo.Control(this, FCTL_REDRAWPANEL, NULL); + + PanelInfo panelInfo; + + if (g_StartupInfo.ControlGetActivePanelInfo(panelInfo)) + { + const AString destPath (GetOemString(destPathU)); + + for (int i = 0; i < panelInfo.ItemsNumber; i++) + { + const PluginPanelItem &pi = panelInfo.PanelItems[i]; + if (strcmp(destPath, pi.FindData.cFileName) == 0) + { + PanelRedrawInfo panelRedrawInfo; + panelRedrawInfo.CurrentItem = i; + panelRedrawInfo.TopPanelItem = 0; + g_StartupInfo.Control(this, FCTL_REDRAWPANEL, &panelRedrawInfo); + break; + } + } + } + + SetCurrentDirVar(); + return S_OK; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Far/ProgressBox.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Far/ProgressBox.cpp new file mode 100644 index 00000000..ab25a104 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Far/ProgressBox.cpp @@ -0,0 +1,305 @@ +// ProgressBox.cpp + +#include "StdAfx.h" + +#include "../../../Common/IntToString.h" +#include "../../../Common/StringConvert.h" + +#include "FarUtils.h" +#include "ProgressBox.h" + +void CPercentPrinterState::ClearCurState() +{ + Completed = 0; + Total = ((UInt64)(Int64)-1); + Files = 0; + FilesTotal = 0; + Command.Empty(); + FileName.Empty(); +} + +void CProgressBox::Init(const char *title) +{ + _title = title; + _wasPrinted = false; + StartTick = GetTickCount(); + _prevTick = StartTick; + _prevElapsedSec = 0; +} + +static unsigned GetPower32(UInt32 val) +{ + const unsigned kStart = 32; + UInt32 mask = ((UInt32)1 << (kStart - 1)); + for (unsigned i = kStart;; i--) + { + if (i == 0 || (val & mask) != 0) + return i; + mask >>= 1; + } +} + +static unsigned GetPower64(UInt64 val) +{ + UInt32 high = (UInt32)(val >> 32); + if (high == 0) + return GetPower32((UInt32)val); + return GetPower32(high) + 32; +} + +static UInt64 MyMultAndDiv(UInt64 mult1, UInt64 mult2, UInt64 divider) +{ + unsigned pow1 = GetPower64(mult1); + unsigned pow2 = GetPower64(mult2); + while (pow1 + pow2 > 64) + { + if (pow1 > pow2) { pow1--; mult1 >>= 1; } + else { pow2--; mult2 >>= 1; } + divider >>= 1; + } + UInt64 res = mult1 * mult2; + if (divider != 0) + res /= divider; + return res; +} + +#define UINT_TO_STR_2(val) { s[0] = (char)('0' + (val) / 10); s[1] = (char)('0' + (val) % 10); s += 2; } + +static void GetTimeString(UInt64 timeValue, char *s) +{ + const UInt64 hours = timeValue / 3600; + UInt32 seconds = (UInt32)(timeValue - hours * 3600); + const UInt32 minutes = seconds / 60; + seconds %= 60; + if (hours > 99) + { + ConvertUInt64ToString(hours, s); + for (; *s != 0; s++); + } + else + { + const UInt32 hours32 = (UInt32)hours; + UINT_TO_STR_2(hours32) + } + *s++ = ':'; UINT_TO_STR_2(minutes) + *s++ = ':'; UINT_TO_STR_2(seconds) + *s = 0; +} + +void CProgressBox::ReduceString(const UString &src, AString &dest) +{ + UnicodeStringToMultiByte2(dest, src, CP_OEMCP); + + if (dest.Len() <= MaxLen) + return; + unsigned len = FileName.Len(); + for (; len != 0;) + { + unsigned delta = len / 8; + if (delta == 0) + delta = 1; + len -= delta; + _tempU = FileName; + _tempU.Delete(len / 2, FileName.Len() - len); + _tempU.Insert(len / 2, L" . "); + UnicodeStringToMultiByte2(dest, _tempU, CP_OEMCP); + if (dest.Len() <= MaxLen) + return; + } + dest.Empty(); +} + +static void Print_UInt64_and_String(AString &s, UInt64 val, const char *name) +{ + char temp[32]; + ConvertUInt64ToString(val, temp); + s += temp; + s.Add_Space(); + s += name; +} + + +static void PrintSize_bytes_Smart(AString &s, UInt64 val) +{ + // Print_UInt64_and_String(s, val, "bytes"); + { + char temp[32]; + ConvertUInt64ToString(val, temp); + s += temp; + } + + if (val == 0) + return; + + unsigned numBits = 10; + char c = 'K'; + char temp[4] = { 'K', 'i', 'B', 0 }; + if (val >= ((UInt64)10 << 30)) { numBits = 30; c = 'G'; } + else if (val >= ((UInt64)10 << 20)) { numBits = 20; c = 'M'; } + temp[0] = c; + s += " ("; + Print_UInt64_and_String(s, ((val + ((UInt64)1 << numBits) - 1) >> numBits), temp); + s += ')'; +} + + +static const unsigned kPercentsSize = 4; + +void CProgressBox::Print() +{ + DWORD tick = GetTickCount(); + DWORD elapsedTicks = tick - StartTick; + DWORD elapsedSec = elapsedTicks / 1000; + + if (_wasPrinted) + { + if (elapsedSec == _prevElapsedSec) + { + if ((UInt32)(tick - _prevTick) < _tickStep) + return; + if (_printedState.IsEqualTo((const CPercentPrinterState &)*this)) + return; + } + } + + UInt64 cur = Completed; + UInt64 total = Total; + + if (!UseBytesForPercents) + { + cur = Files; + total = FilesTotal; + } + + { + _timeStr.Empty(); + _timeStr = "Elapsed time: "; + char s[40]; + GetTimeString(elapsedSec, s); + _timeStr += s; + + if (cur != 0) + { + UInt64 remainingTime = 0; + if (cur < total) + remainingTime = MyMultAndDiv(elapsedTicks, total - cur, cur); + UInt64 remainingSec = remainingTime / 1000; + _timeStr += " Remaining time: "; + + GetTimeString(remainingSec, s); + _timeStr += s; + } + } + + + { + _perc.Empty(); + char s[32]; + unsigned size; + { + UInt64 val = 0; + if (total != (UInt64)(Int64)-1 && total != 0) + val = cur * 100 / Total; + + ConvertUInt64ToString(val, s); + size = (unsigned)strlen(s); + s[size++] = '%'; + s[size] = 0; + } + + unsigned len = size; + while (len < kPercentsSize) + len = kPercentsSize; + len++; + + if (len < MaxLen) + { + unsigned numChars = MaxLen - len; + unsigned filled = 0; + if (total != (UInt64)(Int64)-1 && total != 0) + filled = (unsigned)(cur * numChars / total); + if (filled > numChars) + filled = numChars; + unsigned i = 0; + for (i = 0; i < filled; i++) + _perc += (char)(Byte)0xDB; // '='; + for (; i < numChars; i++) + _perc += (char)(Byte)0xB0; // '.'; + } + + _perc.Add_Space(); + while (size < kPercentsSize) + { + _perc.Add_Space(); + size++; + } + _perc += s; + } + + _files.Empty(); + if (Files != 0 || FilesTotal != 0) + { + _files += "Files: "; + char s[32]; + // if (Files != 0) + { + ConvertUInt64ToString(Files, s); + _files += s; + } + if (FilesTotal != 0) + { + _files += " / "; + ConvertUInt64ToString(FilesTotal, s); + _files += s; + } + } + + _sizesStr.Empty(); + if (Total != 0) + { + _sizesStr += "Size: "; + PrintSize_bytes_Smart(_sizesStr, Completed); + if (Total != 0 && Total != (UInt64)(Int64)-1) + { + _sizesStr += " / "; + PrintSize_bytes_Smart(_sizesStr, Total); + } + } + + _name1.Empty(); + _name2.Empty(); + + if (!FileName.IsEmpty()) + { + _name1U.Empty(); + _name2U.Empty(); + + /* + if (_isDir) + s1 = _filePath; + else + */ + { + const int slashPos = FileName.ReverseFind_PathSepar(); + if (slashPos >= 0) + { + _name1U.SetFrom(FileName, (unsigned)(slashPos + 1)); + _name2U = FileName.Ptr(slashPos + 1); + } + else + _name2U = FileName; + } + ReduceString(_name1U, _name1); + ReduceString(_name2U, _name2); + } + + { + const char *strings[] = { _title, _timeStr, _files, _sizesStr, Command, _name1, _name2, _perc }; + NFar::g_StartupInfo.ShowMessage(FMSG_LEFTALIGN, NULL, strings, Z7_ARRAY_SIZE(strings), 0); + } + + _wasPrinted = true; + _printedState = *this; + _prevTick = tick; + _prevElapsedSec = elapsedSec; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Far/ProgressBox.h b/src/plugins/7zip/7za/cpp/7zip/UI/Far/ProgressBox.h new file mode 100644 index 00000000..f6b36c45 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Far/ProgressBox.h @@ -0,0 +1,85 @@ +// ProgressBox.h + +#ifndef ZIP7_INC_PROGRESS_BOX_H +#define ZIP7_INC_PROGRESS_BOX_H + +#include "../../../Common/MyString.h" +#include "../../../Common/MyTypes.h" + +struct CPercentPrinterState +{ + UInt64 Completed; + UInt64 Total; + + UInt64 Files; + UInt64 FilesTotal; + + AString Command; + UString FileName; + + void ClearCurState(); + + bool IsEqualTo(const CPercentPrinterState &s) const + { + return + Completed == s.Completed + && Total == s.Total + && Files == s.Files + && FilesTotal == s.FilesTotal + && Command == s.Command + && FileName == s.FileName; + } + + CPercentPrinterState(): + Completed(0), + Total((UInt64)(Int64)-1), + Files(0), + FilesTotal(0) + {} +}; + +class CProgressBox: public CPercentPrinterState +{ + UInt32 _tickStep; + DWORD _prevTick; + DWORD _prevElapsedSec; + + bool _wasPrinted; +public: + bool UseBytesForPercents; + DWORD StartTick; + unsigned MaxLen; + +private: + UString _tempU; + UString _name1U; + UString _name2U; + + CPercentPrinterState _printedState; + + AString _title; + + AString _timeStr; + AString _files; + AString _sizesStr; + AString _name1; + AString _name2; + AString _perc; + + void ReduceString(const UString &src, AString &dest); + +public: + + CProgressBox(UInt32 tickStep = 200): + _tickStep(tickStep), + _prevTick(0), + UseBytesForPercents(true), + StartTick(0), + MaxLen(60) + {} + + void Init(const char *title); + void Print(); +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Far/StdAfx.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Far/StdAfx.cpp new file mode 100644 index 00000000..d0feea85 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Far/StdAfx.cpp @@ -0,0 +1,3 @@ +// StdAfx.cpp + +#include "StdAfx.h" diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Far/StdAfx.h b/src/plugins/7zip/7za/cpp/7zip/UI/Far/StdAfx.h new file mode 100644 index 00000000..035267cc --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Far/StdAfx.h @@ -0,0 +1,11 @@ +// StdAfx.h + +#ifndef ZIP7_INC_STDAFX_H +#define ZIP7_INC_STDAFX_H + +#if defined(_MSC_VER) && _MSC_VER >= 1800 +#pragma warning(disable : 4464) // relative include path contains '..' +#endif +#include "../../../Common/Common.h" + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Far/UpdateCallbackFar.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/Far/UpdateCallbackFar.cpp new file mode 100644 index 00000000..16702d3b --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Far/UpdateCallbackFar.cpp @@ -0,0 +1,327 @@ +// UpdateCallbackFar.cpp + +#include "StdAfx.h" + +#ifndef Z7_ST +#include "../../../Windows/Synchronization.h" +#endif + +#include "../../../Common/StringConvert.h" + +#include "FarUtils.h" +#include "UpdateCallbackFar.h" + +using namespace NWindows; +using namespace NFar; + +#ifndef Z7_ST +static NSynchronization::CCriticalSection g_CriticalSection; +#define MT_LOCK NSynchronization::CCriticalSectionLock lock(g_CriticalSection); +#else +#define MT_LOCK +#endif + +static HRESULT CheckBreak2() +{ + return WasEscPressed() ? E_ABORT : S_OK; +} + + +Z7_COM7F_IMF(CUpdateCallback100Imp::ScanProgress(UInt64 numFolders, UInt64 numFiles, UInt64 totalSize, const wchar_t *path, Int32 /* isDir */)) +{ + MT_LOCK + + if (_percent) + { + _percent->FilesTotal = numFolders + numFiles; + _percent->Total = totalSize; + _percent->Command = "Scanning"; + _percent->FileName = path; + _percent->Print(); + _percent->Print(); + } + return CheckBreak2(); +} + +Z7_COM7F_IMF(CUpdateCallback100Imp::ScanError(const wchar_t *path, HRESULT errorCode)) +{ + if (ShowSysErrorMessage(errorCode, path) == -1) + return E_ABORT; + return CheckBreak2(); +} + +Z7_COM7F_IMF(CUpdateCallback100Imp::SetNumFiles(UInt64 numFiles)) +{ + MT_LOCK + + if (_percent) + { + _percent->FilesTotal = numFiles; + _percent->Print(); + } + return CheckBreak2(); +} + + +Z7_COM7F_IMF(CUpdateCallback100Imp::SetTotal(const UInt64 * /* files */, const UInt64 * /* bytes */)) +{ + return S_OK; +} + +Z7_COM7F_IMF(CUpdateCallback100Imp::SetCompleted(const UInt64 * /* files */, const UInt64 * /* bytes */)) +{ + MT_LOCK + return CheckBreak2(); +} + + + +Z7_COM7F_IMF(CUpdateCallback100Imp::SetTotal(UInt64 size)) +{ + MT_LOCK + + if (_percent) + { + _percent->Total = size; + _percent->Print(); + } + return CheckBreak2(); +} + +Z7_COM7F_IMF(CUpdateCallback100Imp::SetCompleted(const UInt64 *completeValue)) +{ + MT_LOCK + + if (_percent) + { + if (completeValue) + _percent->Completed = *completeValue; + _percent->Print(); + } + return CheckBreak2(); +} + +Z7_COM7F_IMF(CUpdateCallback100Imp::CompressOperation(const wchar_t *name)) +{ + MT_LOCK + + if (_percent) + { + _percent->Command = "Adding"; + _percent->FileName = name; + _percent->Print(); + } + return CheckBreak2(); +} + +Z7_COM7F_IMF(CUpdateCallback100Imp::DeleteOperation(const wchar_t *name)) +{ + MT_LOCK + + if (_percent) + { + _percent->Command = "Deleting"; + _percent->FileName = name; + _percent->Print(); + } + return CheckBreak2(); +} + +Z7_COM7F_IMF(CUpdateCallback100Imp::OperationResult(Int32 /* opRes */)) +{ + MT_LOCK + + if (_percent) + { + _percent->Files++; + } + return CheckBreak2(); +} + +Z7_COM7F_IMF(CUpdateCallback100Imp::UpdateErrorMessage(const wchar_t *message)) +{ + MT_LOCK + + if (g_StartupInfo.ShowErrorMessage(UnicodeStringToMultiByte(message, CP_OEMCP)) == -1) + return E_ABORT; + return CheckBreak2(); +} + +Z7_COM7F_IMF(CUpdateCallback100Imp::OpenFileError(const wchar_t *path, HRESULT errorCode)) +{ + if (ShowSysErrorMessage(errorCode, path) == -1) + return E_ABORT; + return CheckBreak2(); +} + +Z7_COM7F_IMF(CUpdateCallback100Imp::ReadingFileError(const wchar_t *path, HRESULT errorCode)) +{ + if (ShowSysErrorMessage(errorCode, path) == -1) + return E_ABORT; + return CheckBreak2(); +} + +void SetExtractErrorMessage(Int32 opRes, Int32 encrypted, AString &s); + +Z7_COM7F_IMF(CUpdateCallback100Imp::ReportExtractResult(Int32 opRes, Int32 isEncrypted, const wchar_t *name)) +{ + MT_LOCK + + if (opRes != NArchive::NExtract::NOperationResult::kOK) + { + AString s; + SetExtractErrorMessage(opRes, isEncrypted, s); + if (PrintErrorMessage(s, name) == -1) + return E_ABORT; + } + + return CheckBreak2(); +} + + +Z7_COM7F_IMF(CUpdateCallback100Imp::ReportUpdateOperation(UInt32 op, const wchar_t *name, Int32 /* isDir */)) +{ + const char *s; + switch (op) + { + case NUpdateNotifyOp::kAdd: s = "Adding"; break; + case NUpdateNotifyOp::kUpdate: s = "Updating"; break; + case NUpdateNotifyOp::kAnalyze: s = "Analyzing"; break; + case NUpdateNotifyOp::kReplicate: s = "Replicating"; break; + case NUpdateNotifyOp::kRepack: s = "Repacking"; break; + case NUpdateNotifyOp::kSkip: s = "Skipping"; break; + case NUpdateNotifyOp::kHeader: s = "Header creating"; break; + case NUpdateNotifyOp::kDelete: s = "Deleting"; break; + default: s = "Unknown operation"; + } + + MT_LOCK + + if (_percent) + { + _percent->Command = s; + _percent->FileName.Empty(); + if (name) + _percent->FileName = name; + _percent->Print(); + } + + return CheckBreak2(); +} + + +HRESULT CUpdateCallback100Imp::MoveArc_UpdateStatus() +{ + MT_LOCK + + if (_percent) + { + AString s; + s.Add_UInt64(_arcMoving_percents); + // status.Add_Space(); + s.Add_Char('%'); + const bool totalDefined = (_arcMoving_total != 0 && _arcMoving_total != (UInt64)(Int64)-1); + if (_arcMoving_current != 0 || totalDefined) + { + s += " : "; + s.Add_UInt64(_arcMoving_current >> 20); + s += " MiB"; + } + if (totalDefined) + { + s += " / "; + s.Add_UInt64((_arcMoving_total + ((1 << 20) - 1)) >> 20); + s += " MiB"; + } + s += " : temporary archive moving ..."; + _percent->Command = s; + _percent->Print(); + } + + return CheckBreak2(); +} + + +Z7_COM7F_IMF(CUpdateCallback100Imp::MoveArc_Start(const wchar_t *srcTempPath, const wchar_t * /* destFinalPath */ , UInt64 size, Int32 /* updateMode */)) +{ + MT_LOCK + + _arcMoving_total = size; + _arcMoving_current = 0; + _arcMoving_percents = 0; + // _arcMoving_updateMode = updateMode; + // _name2 = fs2us(destFinalPath); + if (_percent) + _percent->FileName = srcTempPath; + return MoveArc_UpdateStatus(); +} + +Z7_COM7F_IMF(CUpdateCallback100Imp::MoveArc_Progress(UInt64 totalSize, UInt64 currentSize)) +{ + UInt64 percents = 0; + if (totalSize != 0) + { + if (totalSize < ((UInt64)1 << 57)) + percents = currentSize * 100 / totalSize; + else + percents = currentSize / (totalSize / 100); + } + +#ifdef _WIN32 + // Sleep(300); // for debug +#endif + if (percents == _arcMoving_percents) + return CheckBreak2(); + _arcMoving_total = totalSize; + _arcMoving_current = currentSize; + _arcMoving_percents = percents; + // if (_arcMoving_percents > 100) return E_FAIL; + return MoveArc_UpdateStatus(); +} + + +Z7_COM7F_IMF(CUpdateCallback100Imp::MoveArc_Finish()) +{ + // _arcMoving_percents = 0; + if (_percent) + { + _percent->Command.Empty(); + _percent->FileName.Empty(); + _percent->Print(); + } + return CheckBreak2(); +} + + +Z7_COM7F_IMF(CUpdateCallback100Imp::Before_ArcReopen()) +{ + // fixme: we can use Clear_Stop_Status() here + return CheckBreak2(); +} + + +extern HRESULT GetPassword(UString &password); + +Z7_COM7F_IMF(CUpdateCallback100Imp::CryptoGetTextPassword(BSTR *password)) +{ + MT_LOCK + + *password = NULL; + if (!PasswordIsDefined) + { + RINOK(GetPassword(Password)) + PasswordIsDefined = true; + } + return StringToBstr(Password, password); +} + +Z7_COM7F_IMF(CUpdateCallback100Imp::CryptoGetTextPassword2(Int32 *passwordIsDefined, BSTR *password)) +{ + MT_LOCK + + *password = NULL; + *passwordIsDefined = BoolToInt(PasswordIsDefined); + if (!PasswordIsDefined) + return S_OK; + return StringToBstr(Password, password); +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Far/UpdateCallbackFar.h b/src/plugins/7zip/7za/cpp/7zip/UI/Far/UpdateCallbackFar.h new file mode 100644 index 00000000..8d2c8b8a --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Far/UpdateCallbackFar.h @@ -0,0 +1,58 @@ +// UpdateCallbackFar.h + +#ifndef ZIP7_INC_UPDATE_CALLBACK_FAR_H +#define ZIP7_INC_UPDATE_CALLBACK_FAR_H + +#include "../../../Common/MyCom.h" + +#include "../../IPassword.h" + +#include "../Agent/IFolderArchive.h" + +#include "ProgressBox.h" + +Z7_CLASS_IMP_COM_7( + CUpdateCallback100Imp + , IFolderArchiveUpdateCallback + , IFolderArchiveUpdateCallback2 + , IFolderArchiveUpdateCallback_MoveArc + , IFolderScanProgress + , ICryptoGetTextPassword2 + , ICryptoGetTextPassword + , IArchiveOpenCallback +) + Z7_IFACE_COM7_IMP(IProgress) + + // CMyComPtr _archiveHandler; + CProgressBox *_percent; + // UInt64 _total; + + HRESULT MoveArc_UpdateStatus(); + +private: + UInt64 _arcMoving_total; + UInt64 _arcMoving_current; + UInt64 _arcMoving_percents; + // Int32 _arcMoving_updateMode; + +public: + bool PasswordIsDefined; + UString Password; + + CUpdateCallback100Imp() + // : _total(0) + {} + void Init(/* IInFolderArchive *archiveHandler, */ CProgressBox *progressBox) + { + // _archiveHandler = archiveHandler; + _percent = progressBox; + PasswordIsDefined = false; + Password.Empty(); + _arcMoving_total = 0; + _arcMoving_current = 0; + _arcMoving_percents = 0; + // _arcMoving_updateMode = 0; + } +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Far/makefile b/src/plugins/7zip/7za/cpp/7zip/UI/Far/makefile new file mode 100644 index 00000000..0c6759c9 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Far/makefile @@ -0,0 +1,105 @@ +PROG = 7-ZipFar.dll +DEF_FILE = Far.def +CFLAGS = $(CFLAGS) \ + -DZ7_EXTERNAL_CODECS \ + +!IFNDEF UNDER_CE +!ENDIF + +CURRENT_OBJS = \ + $O\ExtractEngine.obj \ + $O\FarUtils.obj \ + $O\Far.obj \ + $O\OverwriteDialogFar.obj \ + $O\Plugin.obj \ + $O\PluginCommon.obj \ + $O\PluginDelete.obj \ + $O\PluginRead.obj \ + $O\PluginWrite.obj \ + $O\ProgressBox.obj \ + $O\UpdateCallbackFar.obj \ + +COMMON_OBJS = \ + $O\DynLimBuf.obj \ + $O\IntToString.obj \ + $O\NewHandler.obj \ + $O\MyString.obj \ + $O\StringConvert.obj \ + $O\StringToInt.obj \ + $O\MyVector.obj \ + $O\UTFConvert.obj \ + $O\Wildcard.obj \ + +WIN_OBJS = \ + $O\DLL.obj \ + $O\ErrorMsg.obj \ + $O\FileDir.obj \ + $O\FileFind.obj \ + $O\FileIO.obj \ + $O\FileLink.obj \ + $O\FileName.obj \ + $O\FileSystem.obj \ + $O\PropVariant.obj \ + $O\PropVariantConv.obj \ + $O\Registry.obj \ + $O\ResourceString.obj \ + $O\Synchronization.obj \ + $O\System.obj \ + $O\TimeUtils.obj \ + +7ZIP_COMMON_OBJS = \ + $O\CreateCoder.obj \ + $O\FilePathAutoRename.obj \ + $O\FileStreams.obj \ + $O\FilterCoder.obj \ + $O\LimitedStreams.obj \ + $O\MethodProps.obj \ + $O\ProgressUtils.obj \ + $O\PropId.obj \ + $O\StreamObjects.obj \ + $O\StreamUtils.obj \ + $O\UniqBlocks.obj \ + +UI_COMMON_OBJS = \ + $O\ArchiveExtractCallback.obj \ + $O\ArchiveOpenCallback.obj \ + $O\DefaultName.obj \ + $O\EnumDirItems.obj \ + $O\ExtractingFilePath.obj \ + $O\HashCalc.obj \ + $O\LoadCodecs.obj \ + $O\OpenArchive.obj \ + $O\PropIDUtils.obj \ + $O\SetProperties.obj \ + $O\SortUtils.obj \ + $O\UpdateAction.obj \ + $O\UpdateCallback.obj \ + $O\UpdatePair.obj \ + $O\UpdateProduce.obj \ + $O\WorkDir.obj \ + $O\ZipRegistry.obj \ + +AR_COMMON_OBJS = \ + $O\ItemNameUtils.obj \ + $O\OutStreamWithCRC.obj \ + +AGENT_OBJS = \ + $O\Agent.obj \ + $O\AgentOut.obj \ + $O\AgentProxy.obj \ + $O\ArchiveFolder.obj \ + $O\ArchiveFolderOut.obj \ + $O\UpdateCallbackAgent.obj \ + +COMPRESS_OBJS = \ + $O\CopyCoder.obj \ + +C_OBJS = \ + $O\Alloc.obj \ + $O\CpuArch.obj \ + $O\Threads.obj \ + +!include "../../Crc.mak" +!include "../../Sort.mak" + +!include "../../7zip.mak" diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/Far/resource.rc b/src/plugins/7zip/7za/cpp/7zip/UI/Far/resource.rc new file mode 100644 index 00000000..a5c2e2f3 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/Far/resource.rc @@ -0,0 +1,3 @@ +#include "../../MyVersionInfo.rc" + +MY_VERSION_INFO_DLL("7-Zip Plugin for FAR Manager", "7-ZipFar") diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/7zFM.exe.manifest b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/7zFM.exe.manifest new file mode 100644 index 00000000..69c7f0bf --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/7zFM.exe.manifest @@ -0,0 +1,23 @@ + + + + 7-Zip File Manager. + + + + + + + + + + + + + true + + + + +true + diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/7zipLogo.ico b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/7zipLogo.ico new file mode 100644 index 00000000..973241c8 Binary files /dev/null and b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/7zipLogo.ico differ diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/AboutDialog.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/AboutDialog.cpp new file mode 100644 index 00000000..efc74cc8 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/AboutDialog.cpp @@ -0,0 +1,81 @@ +// AboutDialog.cpp + +#include "StdAfx.h" + +#include "../../../../C/CpuArch.h" + +#include "../../MyVersion.h" + +#include "../Common/LoadCodecs.h" + +#include "AboutDialog.h" +#include "PropertyNameRes.h" + +#include "HelpUtils.h" +#include "LangUtils.h" + +#ifdef Z7_LANG +static const UInt32 kLangIDs[] = +{ + IDT_ABOUT_INFO +}; +#endif + +#define kHomePageURL TEXT("https://www.7-zip.org/") +#define kHelpTopic "start.htm" + +#define LLL_(quote) L##quote +#define LLL(quote) LLL_(quote) + +extern CCodecs *g_CodecsObj; + +bool CAboutDialog::OnInit() +{ + #ifdef Z7_EXTERNAL_CODECS + if (g_CodecsObj) + { + UString s; + g_CodecsObj->GetCodecsErrorMessage(s); + if (!s.IsEmpty()) + MessageBoxW(GetParent(), s, L"7-Zip", MB_ICONERROR); + } + #endif + + #ifdef Z7_LANG + LangSetWindowText(*this, IDD_ABOUT); + LangSetDlgItems(*this, kLangIDs, Z7_ARRAY_SIZE(kLangIDs)); + #endif + SetItemText(IDT_ABOUT_VERSION, UString("7-Zip " MY_VERSION_CPU)); + SetItemText(IDT_ABOUT_DATE, LLL(MY_DATE)); + + NormalizePosition(); + return CModalDialog::OnInit(); +} + +void CAboutDialog::OnHelp() +{ + ShowHelpWindow(kHelpTopic); +} + +bool CAboutDialog::OnButtonClicked(unsigned buttonID, HWND buttonHWND) +{ + LPCTSTR url; + switch (buttonID) + { + case IDB_ABOUT_HOMEPAGE: url = kHomePageURL; break; + default: + return CModalDialog::OnButtonClicked(buttonID, buttonHWND); + } + + #ifdef UNDER_CE + SHELLEXECUTEINFO s; + memset(&s, 0, sizeof(s)); + s.cbSize = sizeof(s); + s.lpFile = url; + ::ShellExecuteEx(&s); + #else + ::ShellExecute(NULL, NULL, url, NULL, NULL, SW_SHOWNORMAL); + #endif + + return true; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/AboutDialog.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/AboutDialog.h new file mode 100644 index 00000000..1d11d749 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/AboutDialog.h @@ -0,0 +1,19 @@ +// AboutDialog.h + +#ifndef ZIP7_INC_ABOUT_DIALOG_H +#define ZIP7_INC_ABOUT_DIALOG_H + +#include "../../../Windows/Control/Dialog.h" + +#include "AboutDialogRes.h" + +class CAboutDialog: public NWindows::NControl::CModalDialog +{ +public: + virtual bool OnInit() Z7_override; + virtual void OnHelp() Z7_override; + virtual bool OnButtonClicked(unsigned buttonID, HWND buttonHWND) Z7_override; + INT_PTR Create(HWND wndParent = NULL) { return CModalDialog::Create(IDD_ABOUT, wndParent); } +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/AboutDialog.rc b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/AboutDialog.rc new file mode 100644 index 00000000..b235df0b --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/AboutDialog.rc @@ -0,0 +1,26 @@ +#include "AboutDialogRes.h" +#include "../../GuiCommon.rc" +#include "../../MyVersion.h" + +#define xc 144 +#define yc 144 + +#define y 93 + +IDI_LOGO ICON "../../UI/FileManager/7zipLogo.ico" + +#ifndef SS_REALSIZEIMAGE +#define SS_REALSIZEIMAGE 0x800 +#endif + +IDD_ABOUT DIALOG 0, 0, xs, ys MY_MODAL_DIALOG_STYLE MY_FONT +CAPTION "About 7-Zip" +{ + DEFPUSHBUTTON "OK", IDOK, bx1, by, bxs, bys + PUSHBUTTON "www.7-zip.org", IDB_ABOUT_HOMEPAGE, bx2, by, bxs, bys + ICON IDI_LOGO, -1, m, m, 32, 32, SS_REALSIZEIMAGE + LTEXT "", IDT_ABOUT_VERSION, m, 54, xc, 8 + LTEXT "", IDT_ABOUT_DATE, m, 67, xc, 8 + LTEXT MY_COPYRIGHT, -1, m, 80, xc, 8 + LTEXT "7-Zip is free software", IDT_ABOUT_INFO, m, y, xc, (by - y - 1) +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/AboutDialogRes.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/AboutDialogRes.h new file mode 100644 index 00000000..b4165580 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/AboutDialogRes.h @@ -0,0 +1,8 @@ +#define IDD_ABOUT 2900 + +#define IDT_ABOUT_INFO 2901 + +#define IDI_LOGO 100 +#define IDT_ABOUT_VERSION 101 +#define IDT_ABOUT_DATE 102 +#define IDB_ABOUT_HOMEPAGE 110 diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/Add.bmp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/Add.bmp new file mode 100644 index 00000000..a8577fc7 Binary files /dev/null and b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/Add.bmp differ diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/Add2.bmp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/Add2.bmp new file mode 100644 index 00000000..252fc253 Binary files /dev/null and b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/Add2.bmp differ diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/AltStreamsFolder.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/AltStreamsFolder.cpp new file mode 100644 index 00000000..685ac707 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/AltStreamsFolder.cpp @@ -0,0 +1,946 @@ +// AltStreamsFolder.cpp + +#include "StdAfx.h" + +#ifdef __MINGW32_VERSION +// #if !defined(_MSC_VER) && (__GNUC__) && (__GNUC__ < 10) +// for old mingw +#include +#else +#ifndef Z7_OLD_WIN_SDK + #if !defined(_M_IA64) + #include + #endif +#else +typedef LONG NTSTATUS; +typedef struct _IO_STATUS_BLOCK { + union { + NTSTATUS Status; + PVOID Pointer; + }; + ULONG_PTR Information; +} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK; +#endif +#endif + +#include "../../../Common/ComTry.h" +#include "../../../Common/StringConvert.h" +#include "../../../Common/Wildcard.h" + +#include "../../../Windows/DLL.h" +#include "../../../Windows/ErrorMsg.h" +#include "../../../Windows/FileDir.h" +#include "../../../Windows/FileIO.h" +#include "../../../Windows/FileName.h" +#include "../../../Windows/PropVariant.h" + +#include "../Common/ExtractingFilePath.h" + +#include "../Agent/IFolderArchive.h" + +#include "AltStreamsFolder.h" +#include "FSDrives.h" +#include "FSFolder.h" + +#include "SysIconUtils.h" + +using namespace NWindows; +using namespace NFile; +using namespace NFind; +using namespace NDir; +using namespace NName; + +#ifndef USE_UNICODE_FSTRING +int CompareFileNames_ForFolderList(const FChar *s1, const FChar *s2); +#endif + +#ifndef UNDER_CE + +namespace NFsFolder +{ +bool MyGetCompressedFileSizeW(CFSTR path, UInt64 &size); +} + +#endif + +namespace NAltStreamsFolder { + +static const Byte kProps[] = +{ + kpidName, + kpidSize, + kpidPackSize +}; + +static unsigned GetFsParentPrefixSize(const FString &path) +{ + if (IsNetworkShareRootPath(path)) + return 0; + const unsigned prefixSize = GetRootPrefixSize(path); + if (prefixSize == 0 || prefixSize >= path.Len()) + return 0; + FString parentPath = path; + int pos = parentPath.ReverseFind_PathSepar(); + if (pos < 0) + return 0; + if (pos == (int)parentPath.Len() - 1) + { + parentPath.DeleteBack(); + pos = parentPath.ReverseFind_PathSepar(); + if (pos < 0) + return 0; + } + if ((unsigned)pos + 1 < prefixSize) + return 0; + return (unsigned)pos + 1; +} + +HRESULT CAltStreamsFolder::Init(const FString &path /* , IFolderFolder *parentFolder */) +{ + // _parentFolder = parentFolder; + if (path.Back() != ':') + return E_FAIL; + + _pathPrefix = path; + _pathBaseFile = path; + _pathBaseFile.DeleteBack(); + + { + CFileInfo fi; + if (!fi.Find(_pathBaseFile)) + return GetLastError_noZero_HRESULT(); + } + + unsigned prefixSize = GetFsParentPrefixSize(_pathBaseFile); + if (prefixSize == 0) + return S_OK; + FString parentPath = _pathBaseFile; + parentPath.DeleteFrom(prefixSize); + + _findChangeNotification.FindFirst(parentPath, false, + FILE_NOTIFY_CHANGE_FILE_NAME + | FILE_NOTIFY_CHANGE_DIR_NAME + | FILE_NOTIFY_CHANGE_ATTRIBUTES + | FILE_NOTIFY_CHANGE_SIZE + | FILE_NOTIFY_CHANGE_LAST_WRITE + /* + | FILE_NOTIFY_CHANGE_LAST_ACCESS + | FILE_NOTIFY_CHANGE_CREATION + | FILE_NOTIFY_CHANGE_SECURITY + */ + ); + /* + if (_findChangeNotification.IsHandleAllocated()) + return S_OK; + return GetLastError(); + */ + return S_OK; +} + +Z7_COM7F_IMF(CAltStreamsFolder::LoadItems()) +{ + Int32 dummy; + WasChanged(&dummy); + Clear(); + + CStreamEnumerator enumerator(_pathBaseFile); + + CStreamInfo si; + for (;;) + { + bool found; + if (!enumerator.Next(si, found)) + { + // if (GetLastError() == ERROR_ACCESS_DENIED) + // break; + // return E_FAIL; + break; + } + if (!found) + break; + if (si.IsMainStream()) + continue; + CAltStream ss; + ss.Name = si.GetReducedName(); + if (!ss.Name.IsEmpty() && ss.Name[0] == ':') + ss.Name.Delete(0); + + ss.Size = si.Size; + ss.PackSize_Defined = false; + ss.PackSize = si.Size; + Streams.Add(ss); + } + + return S_OK; +} + +Z7_COM7F_IMF(CAltStreamsFolder::GetNumberOfItems(UInt32 *numItems)) +{ + *numItems = Streams.Size(); + return S_OK; +} + +#ifdef USE_UNICODE_FSTRING + +Z7_COM7F_IMF(CAltStreamsFolder::GetItemPrefix(UInt32 /* index */, const wchar_t **name, unsigned *len)) +{ + *name = NULL; + *len = 0; + return S_OK; +} + +Z7_COM7F_IMF(CAltStreamsFolder::GetItemName(UInt32 index, const wchar_t **name, unsigned *len)) +{ + *name = NULL; + *len = 0; + { + const CAltStream &ss = Streams[index]; + *name = ss.Name; + *len = ss.Name.Len(); + } + return S_OK; +} + +Z7_COM7F_IMF2(UInt64, CAltStreamsFolder::GetItemSize(UInt32 index)) +{ + return Streams[index].Size; +} + +#endif + + +Z7_COM7F_IMF(CAltStreamsFolder::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) +{ + NCOM::CPropVariant prop; + { + CAltStream &ss = Streams[index]; + switch (propID) + { + case kpidIsDir: prop = false; break; + case kpidIsAltStream: prop = true; break; + case kpidName: prop = ss.Name; break; + case kpidSize: prop = ss.Size; break; + case kpidPackSize: + #ifdef UNDER_CE + prop = ss.Size; + #else + if (!ss.PackSize_Defined) + { + ss.PackSize_Defined = true; + if (!NFsFolder::MyGetCompressedFileSizeW(_pathPrefix + us2fs(ss.Name), ss.PackSize)) + ss.PackSize = ss.Size; + } + prop = ss.PackSize; + #endif + break; + } + } + + prop.Detach(value); + return S_OK; +} + + +// returns Position of extension including '.' + +static inline const wchar_t *GetExtensionPtr(const UString &name) +{ + const int dotPos = name.ReverseFind_Dot(); + return name.Ptr(dotPos < 0 ? name.Len() : (unsigned)dotPos); +} + +Z7_COM7F_IMF2(Int32, CAltStreamsFolder::CompareItems(UInt32 index1, UInt32 index2, PROPID propID, Int32 /* propIsRaw */)) +{ + const CAltStream &ss1 = Streams[index1]; + const CAltStream &ss2 = Streams[index2]; + + switch (propID) + { + case kpidName: + { + return CompareFileNames_ForFolderList(ss1.Name, ss2.Name); + // return MyStringCompareNoCase(ss1.Name, ss2.Name); + } + case kpidSize: + return MyCompare(ss1.Size, ss2.Size); + case kpidPackSize: + { + #ifdef UNDER_CE + return MyCompare(ss1.Size, ss2.Size); + #else + // PackSize can be undefined here + return MyCompare( + ss1.PackSize, + ss2.PackSize); + #endif + } + + case kpidExtension: + return CompareFileNames_ForFolderList( + GetExtensionPtr(ss1.Name), + GetExtensionPtr(ss2.Name)); + } + + return 0; +} + +Z7_COM7F_IMF(CAltStreamsFolder::BindToFolder(UInt32 /* index */, IFolderFolder **resultFolder)) +{ + *resultFolder = NULL; + return E_INVALIDARG; +} + +Z7_COM7F_IMF(CAltStreamsFolder::BindToFolder(const wchar_t * /* name */, IFolderFolder **resultFolder)) +{ + *resultFolder = NULL; + return E_INVALIDARG; +} + +// static CFSTR const kSuperPrefix = FTEXT("\\\\?\\"); + +Z7_COM7F_IMF(CAltStreamsFolder::BindToParentFolder(IFolderFolder **resultFolder)) +{ + *resultFolder = NULL; + /* + if (_parentFolder) + { + CMyComPtr parentFolder = _parentFolder; + *resultFolder = parentFolder.Detach(); + return S_OK; + } + */ + + if (IsDriveRootPath_SuperAllowed(_pathBaseFile)) + { + CFSDrives *drivesFolderSpec = new CFSDrives; + CMyComPtr drivesFolder = drivesFolderSpec; + drivesFolderSpec->Init(); + *resultFolder = drivesFolder.Detach(); + return S_OK; + } + + /* + parentPath.DeleteFrom(pos + 1); + + if (parentPath == kSuperPrefix) + { + #ifdef UNDER_CE + *resultFolder = 0; + #else + CFSDrives *drivesFolderSpec = new CFSDrives; + CMyComPtr drivesFolder = drivesFolderSpec; + drivesFolderSpec->Init(false, true); + *resultFolder = drivesFolder.Detach(); + #endif + return S_OK; + } + + FString parentPathReduced = parentPath.Left(pos); + + #ifndef UNDER_CE + pos = parentPathReduced.ReverseFind_PathSepar(); + if (pos == 1) + { + if (!IS_PATH_SEPAR_CHAR(parentPath[0])) + return E_FAIL; + CNetFolder *netFolderSpec = new CNetFolder; + CMyComPtr netFolder = netFolderSpec; + netFolderSpec->Init(fs2us(parentPath)); + *resultFolder = netFolder.Detach(); + return S_OK; + } + #endif + + CFSFolder *parentFolderSpec = new CFSFolder; + CMyComPtr parentFolder = parentFolderSpec; + RINOK(parentFolderSpec->Init(parentPath, 0)); + *resultFolder = parentFolder.Detach(); + */ + + return S_OK; +} + +IMP_IFolderFolder_Props(CAltStreamsFolder) + +Z7_COM7F_IMF(CAltStreamsFolder::GetFolderProperty(PROPID propID, PROPVARIANT *value)) +{ + COM_TRY_BEGIN + NWindows::NCOM::CPropVariant prop; + switch (propID) + { + case kpidType: prop = "AltStreamsFolder"; break; + case kpidPath: prop = fs2us(_pathPrefix); break; + } + prop.Detach(value); + return S_OK; + COM_TRY_END +} + +Z7_COM7F_IMF(CAltStreamsFolder::WasChanged(Int32 *wasChanged)) +{ + bool wasChangedMain = false; + for (;;) + { + if (!_findChangeNotification.IsHandleAllocated()) + { + *wasChanged = BoolToInt(false); + return S_OK; + } + + const DWORD waitResult = ::WaitForSingleObject(_findChangeNotification, 0); + const bool wasChangedLoc = (waitResult == WAIT_OBJECT_0); + if (wasChangedLoc) + { + _findChangeNotification.FindNext(); + wasChangedMain = true; + } + else + break; + } + *wasChanged = BoolToInt(wasChangedMain); + return S_OK; +} + +Z7_COM7F_IMF(CAltStreamsFolder::Clone(IFolderFolder **resultFolder)) +{ + CAltStreamsFolder *folderSpec = new CAltStreamsFolder; + CMyComPtr folderNew = folderSpec; + folderSpec->Init(_pathPrefix); + *resultFolder = folderNew.Detach(); + return S_OK; +} + +void CAltStreamsFolder::GetAbsPath(const wchar_t *name, FString &absPath) +{ + absPath.Empty(); + if (!IsAbsolutePath(name)) + absPath += _pathPrefix; + absPath += us2fs(name); +} + + + +static HRESULT SendMessageError(IFolderOperationsExtractCallback *callback, + const wchar_t *message, const FString &fileName) +{ + UString s = message; + s += " : "; + s += fs2us(fileName); + return callback->ShowMessage(s); +} + +static HRESULT SendMessageError(IFolderArchiveUpdateCallback *callback, + const wchar_t *message, const FString &fileName) +{ + UString s = message; + s += " : "; + s += fs2us(fileName); + return callback->UpdateErrorMessage(s); +} + +static HRESULT SendMessageError(IFolderOperationsExtractCallback *callback, + const char *message, const FString &fileName) +{ + return SendMessageError(callback, MultiByteToUnicodeString(message), fileName); +} + +/* +static HRESULT SendMessageError(IFolderArchiveUpdateCallback *callback, + const char *message, const FString &fileName) +{ + return SendMessageError(callback, MultiByteToUnicodeString(message), fileName); +} +*/ + +Z7_COM7F_IMF(CAltStreamsFolder::CreateFolder(const wchar_t * /* name */, IProgress * /* progress */)) +{ + return E_NOTIMPL; +} + +Z7_COM7F_IMF(CAltStreamsFolder::CreateFile(const wchar_t *name, IProgress * /* progress */)) +{ + FString absPath; + GetAbsPath(name, absPath); + NIO::COutFile outFile; + if (!outFile.Create_NEW(absPath)) + return GetLastError_noZero_HRESULT(); + return S_OK; +} + +static UString GetLastErrorMessage() +{ + return NError::MyFormatMessage(GetLastError_noZero_HRESULT()); +} + +static HRESULT UpdateFile(NFsFolder::CCopyStateIO &state, CFSTR inPath, CFSTR outPath, IFolderArchiveUpdateCallback *callback) +{ + if (NFind::DoesFileOrDirExist(outPath)) + { + RINOK(SendMessageError(callback, NError::MyFormatMessage(ERROR_ALREADY_EXISTS), FString(outPath))) + CFileInfo fi; + if (fi.Find(inPath)) + { + if (state.TotalSize >= fi.Size) + state.TotalSize -= fi.Size; + } + return S_OK; + } + + { + if (callback) + RINOK(callback->CompressOperation(fs2us(inPath))) + RINOK(state.MyCopyFile(inPath, outPath)) + if (state.ErrorFileIndex >= 0) + { + if (state.ErrorMessage.IsEmpty()) + state.ErrorMessage = GetLastErrorMessage(); + FString errorName; + if (state.ErrorFileIndex == 0) + errorName = inPath; + else + errorName = outPath; + if (callback) + RINOK(SendMessageError(callback, state.ErrorMessage, errorName)) + } + if (callback) + RINOK(callback->OperationResult(0)) + } + + return S_OK; +} + +EXTERN_C_BEGIN + +typedef enum +{ + Z7_WIN_FileRenameInformation = 10 +} +Z7_WIN_FILE_INFORMATION_CLASS; + + +typedef struct +{ + // #if (_WIN32_WINNT >= _WIN32_WINNT_WIN10_RS1) + union + { + BOOLEAN ReplaceIfExists; // FileRenameInformation + ULONG Flags; // FileRenameInformationEx + } DUMMYUNIONNAME; + // #else + // BOOLEAN ReplaceIfExists; + // #endif + HANDLE RootDirectory; + ULONG FileNameLength; + WCHAR FileName[1]; +} Z7_WIN_FILE_RENAME_INFORMATION; + +#if (_WIN32_WINNT >= 0x0500) && !defined(_M_IA64) +#define Z7_WIN_NTSTATUS NTSTATUS +#define Z7_WIN_IO_STATUS_BLOCK IO_STATUS_BLOCK +#else +typedef LONG Z7_WIN_NTSTATUS; +typedef struct +{ + union + { + Z7_WIN_NTSTATUS Status; + PVOID Pointer; + } DUMMYUNIONNAME; + ULONG_PTR Information; +} Z7_WIN_IO_STATUS_BLOCK; +#endif + +typedef Z7_WIN_NTSTATUS (WINAPI *Func_NtSetInformationFile)( + HANDLE FileHandle, + Z7_WIN_IO_STATUS_BLOCK *IoStatusBlock, + PVOID FileInformation, + ULONG Length, + Z7_WIN_FILE_INFORMATION_CLASS FileInformationClass); + +// NTAPI +typedef ULONG (WINAPI *Func_RtlNtStatusToDosError)(Z7_WIN_NTSTATUS Status); + +#define MY_STATUS_SUCCESS 0 + +EXTERN_C_END + +// static Func_NtSetInformationFile f_NtSetInformationFile; +// static bool g_NtSetInformationFile_WasRequested = false; +Z7_DIAGNOSTIC_IGNORE_CAST_FUNCTION + +Z7_COM7F_IMF(CAltStreamsFolder::Rename(UInt32 index, const wchar_t *newName, IProgress *progress)) +{ + const CAltStream &ss = Streams[index]; + const FString srcPath = _pathPrefix + us2fs(ss.Name); + + const HMODULE ntdll = ::GetModuleHandleW(L"ntdll.dll"); + // if (!g_NtSetInformationFile_WasRequested) { + // g_NtSetInformationFile_WasRequested = true; + const + Func_NtSetInformationFile + f_NtSetInformationFile = Z7_GET_PROC_ADDRESS( + Func_NtSetInformationFile, ntdll, + "NtSetInformationFile"); + if (f_NtSetInformationFile) + { + NIO::CInFile inFile; + if (inFile.Open_for_FileRenameInformation(srcPath)) + { + UString destPath (':'); + destPath += newName; + const ULONG len = (ULONG)sizeof(wchar_t) * destPath.Len(); + CByteBuffer buffer(sizeof(Z7_WIN_FILE_RENAME_INFORMATION) + len); + // buffer is 4 bytes larger than required. + Z7_WIN_FILE_RENAME_INFORMATION *fri = (Z7_WIN_FILE_RENAME_INFORMATION *)(void *)(Byte *)buffer; + memset(fri, 0, sizeof(Z7_WIN_FILE_RENAME_INFORMATION)); + /* DOCS: If ReplaceIfExists is set to TRUE, the rename operation will succeed only + if a stream with the same name does not exist or is a zero-length data stream. */ + fri->ReplaceIfExists = FALSE; + fri->RootDirectory = NULL; + fri->FileNameLength = len; + memcpy(fri->FileName, destPath.Ptr(), len); + Z7_WIN_IO_STATUS_BLOCK iosb; + const Z7_WIN_NTSTATUS status = f_NtSetInformationFile (inFile.GetHandle(), + &iosb, fri, (ULONG)buffer.Size(), Z7_WIN_FileRenameInformation); + if (status != MY_STATUS_SUCCESS) + { + const + Func_RtlNtStatusToDosError + f_RtlNtStatusToDosError = Z7_GET_PROC_ADDRESS( + Func_RtlNtStatusToDosError, ntdll, + "RtlNtStatusToDosError"); + if (f_RtlNtStatusToDosError) + { + const ULONG res = f_RtlNtStatusToDosError(status); + if (res != ERROR_MR_MID_NOT_FOUND) + return HRESULT_FROM_WIN32(res); + } + } + return status; + } + } + + CMyComPtr callback; + if (progress) + { + RINOK(progress->QueryInterface(IID_IFolderArchiveUpdateCallback, (void **)&callback)) + } + + if (callback) + { + RINOK(callback->SetNumFiles(1)) + RINOK(callback->SetTotal(ss.Size)) + } + + NFsFolder::CCopyStateIO state; + state.Progress = progress; + state.TotalSize = 0; + state.DeleteSrcFile = true; + + const FString destPath = _pathPrefix + us2fs(newName); + return UpdateFile(state, srcPath, destPath, callback); +} + +Z7_COM7F_IMF(CAltStreamsFolder::Delete(const UInt32 *indices, UInt32 numItems,IProgress *progress)) +{ + RINOK(progress->SetTotal(numItems)) + for (UInt32 i = 0; i < numItems; i++) + { + const CAltStream &ss = Streams[indices[i]]; + const FString fullPath = _pathPrefix + us2fs(ss.Name); + const bool result = DeleteFileAlways(fullPath); + if (!result) + return GetLastError_noZero_HRESULT(); + const UInt64 completed = i; + RINOK(progress->SetCompleted(&completed)) + } + return S_OK; +} + +Z7_COM7F_IMF(CAltStreamsFolder::SetProperty(UInt32 /* index */, PROPID /* propID */, + const PROPVARIANT * /* value */, IProgress * /* progress */)) +{ + return E_NOTIMPL; +} + +Z7_COM7F_IMF(CAltStreamsFolder::GetSystemIconIndex(UInt32 index, Int32 *iconIndex)) +{ + const CAltStream &ss = Streams[index]; + return Shell_GetFileInfo_SysIconIndex_for_Path_return_HRESULT( + _pathPrefix + us2fs(ss.Name), + FILE_ATTRIBUTE_ARCHIVE, + iconIndex); +} + +/* +Z7_CLASS_IMP_COM_1( + CGetProp + , IGetProp +) +public: + // const CArc *Arc; + // UInt32 IndexInArc; + UString Name; // relative path + UInt64 Size; +}; + +Z7_COM7F_IMF(CGetProp::GetProp(PROPID propID, PROPVARIANT *value)) +{ + if (propID == kpidName) + { + COM_TRY_BEGIN + NCOM::CPropVariant prop; + prop = Name; + prop.Detach(value); + return S_OK; + COM_TRY_END + } + if (propID == kpidSize) + { + NCOM::CPropVariant prop = Size; + prop.Detach(value); + return S_OK; + } + NCOM::CPropVariant prop; + prop.Detach(value); + return S_OK; +} +*/ + +static HRESULT CopyStream( + NFsFolder::CCopyStateIO &state, + const FString &srcPath, + const CFileInfo &srcFileInfo, + const CAltStream &srcAltStream, + const FString &destPathSpec, + IFolderOperationsExtractCallback *callback) +{ + FString destPath = destPathSpec; + if (CompareFileNames(destPath, srcPath) == 0) + { + RINOK(SendMessageError(callback, "Cannot copy file onto itself", destPath)) + return E_ABORT; + } + + Int32 writeAskResult; + CMyComBSTR destPathResult; + RINOK(callback->AskWrite( + fs2us(srcPath), + BoolToInt(false), + &srcFileInfo.MTime, &srcAltStream.Size, + fs2us(destPath), + &destPathResult, + &writeAskResult)) + + if (IntToBool(writeAskResult)) + { + RINOK(callback->SetCurrentFilePath(fs2us(srcPath))) + FString destPathNew (us2fs((LPCOLESTR)destPathResult)); + RINOK(state.MyCopyFile(srcPath, destPathNew)) + if (state.ErrorFileIndex >= 0) + { + if (state.ErrorMessage.IsEmpty()) + state.ErrorMessage = GetLastErrorMessage(); + FString errorName; + if (state.ErrorFileIndex == 0) + errorName = srcPath; + else + errorName = destPathNew; + RINOK(SendMessageError(callback, state.ErrorMessage, errorName)) + return E_ABORT; + } + state.StartPos += state.CurrentSize; + } + else + { + if (state.TotalSize >= srcAltStream.Size) + { + state.TotalSize -= srcAltStream.Size; + RINOK(state.Progress->SetTotal(state.TotalSize)) + } + } + return S_OK; +} + +Z7_COM7F_IMF(CAltStreamsFolder::CopyTo(Int32 moveMode, const UInt32 *indices, UInt32 numItems, + Int32 /* includeAltStreams */, Int32 /* replaceAltStreamColon */, + const wchar_t *path, IFolderOperationsExtractCallback *callback)) +{ + if (numItems == 0) + return S_OK; + + /* + Z7_DECL_CMyComPtr_QI_FROM( + IFolderExtractToStreamCallback, + ExtractToStreamCallback, callback) + if (ExtractToStreamCallback) + { + Int32 useStreams = 0; + if (ExtractToStreamCallback->UseExtractToStream(&useStreams) != S_OK) + useStreams = 0; + if (useStreams == 0) + ExtractToStreamCallback.Release(); + } + */ + + UInt64 totalSize = 0; + { + UInt32 i; + for (i = 0; i < numItems; i++) + { + totalSize += Streams[indices[i]].Size; + } + RINOK(callback->SetTotal(totalSize)) + RINOK(callback->SetNumFiles(numItems)) + } + + /* + if (ExtractToStreamCallback) + { + CGetProp *GetProp_Spec = new CGetProp; + CMyComPtr GetProp= GetProp_Spec; + + for (UInt32 i = 0; i < numItems; i++) + { + UInt32 index = indices[i]; + const CAltStream &ss = Streams[index]; + GetProp_Spec->Name = ss.Name; + GetProp_Spec->Size = ss.Size; + CMyComPtr outStream; + RINOK(ExtractToStreamCallback->GetStream7(GetProp_Spec->Name, BoolToInt(false), &outStream, + NArchive::NExtract::NAskMode::kExtract, GetProp)); // isDir + FString srcPath; + GetFullPath(ss, srcPath); + RINOK(ExtractToStreamCallback->PrepareOperation7(NArchive::NExtract::NAskMode::kExtract)); + RINOK(ExtractToStreamCallback->SetOperationResult7(NArchive::NExtract::NOperationResult::kOK, BoolToInt(false))); // _encrypted + // RINOK(CopyStream(state, srcPath, fi, ss, destPath2, callback, completedSize)); + } + return S_OK; + } + */ + + FString destPath (us2fs(path)); + if (destPath.IsEmpty() /* && !ExtractToStreamCallback */) + return E_INVALIDARG; + + const bool isAltDest = NName::IsAltPathPrefix(destPath); + const bool isDirectPath = (!isAltDest && !IsPathSepar(destPath.Back())); + + if (isDirectPath) + { + if (numItems > 1) + return E_INVALIDARG; + } + + CFileInfo fi; + if (!fi.Find(_pathBaseFile)) + return GetLastError_noZero_HRESULT(); + + NFsFolder::CCopyStateIO state; + state.Progress = callback; + state.DeleteSrcFile = IntToBool(moveMode); + state.TotalSize = totalSize; + + for (UInt32 i = 0; i < numItems; i++) + { + const UInt32 index = indices[i]; + const CAltStream &ss = Streams[index]; + FString destPath2 = destPath; + if (!isDirectPath) + destPath2 += us2fs(Get_Correct_FsFile_Name(ss.Name)); + FString srcPath; + GetFullPath(ss, srcPath); + RINOK(CopyStream(state, srcPath, fi, ss, destPath2, callback)) + } + + return S_OK; +} + +Z7_COM7F_IMF(CAltStreamsFolder::CopyFrom(Int32 /* moveMode */, const wchar_t * /* fromFolderPath */, + const wchar_t * const * /* itemsPaths */, UInt32 /* numItems */, IProgress * /* progress */)) +{ + /* + if (numItems == 0) + return S_OK; + + CMyComPtr callback; + if (progress) + { + RINOK(progress->QueryInterface(IID_IFolderArchiveUpdateCallback, (void **)&callback)); + } + + if (CompareFileNames(fromFolderPath, fs2us(_pathPrefix)) == 0) + { + RINOK(SendMessageError(callback, "Cannot copy file onto itself", _pathPrefix)); + return E_ABORT; + } + + if (callback) + RINOK(callback->SetNumFiles(numItems)); + + UInt64 totalSize = 0; + + UInt32 i; + + FString path; + for (i = 0; i < numItems; i++) + { + path = us2fs(fromFolderPath); + path += us2fs(itemsPaths[i]); + + CFileInfo fi; + if (!fi.Find(path)) + return ::GetLastError(); + if (fi.IsDir()) + return E_NOTIMPL; + totalSize += fi.Size; + } + + RINOK(progress->SetTotal(totalSize)); + + // UInt64 completedSize = 0; + + NFsFolder::CCopyStateIO state; + state.Progress = progress; + state.DeleteSrcFile = IntToBool(moveMode); + state.TotalSize = totalSize; + + // we need to clear READ-ONLY of parent before creating alt stream + { + DWORD attrib = GetFileAttrib(_pathBaseFile); + if (attrib != INVALID_FILE_ATTRIBUTES + && (attrib & FILE_ATTRIBUTE_READONLY) != 0) + { + if (!SetFileAttrib(_pathBaseFile, attrib & ~FILE_ATTRIBUTE_READONLY)) + { + if (callback) + { + RINOK(SendMessageError(callback, GetLastErrorMessage(), _pathBaseFile)); + return S_OK; + } + return Return_LastError_or_FAIL(); + } + } + } + + for (i = 0; i < numItems; i++) + { + path = us2fs(fromFolderPath); + path += us2fs(itemsPaths[i]); + + FString destPath = _pathPrefix + us2fs(itemsPaths[i]); + + RINOK(UpdateFile(state, path, destPath, callback)); + } + + return S_OK; + */ + return E_NOTIMPL; +} + +Z7_COM7F_IMF(CAltStreamsFolder::CopyFromFile(UInt32 /* index */, const wchar_t * /* fullFilePath */, IProgress * /* progress */)) +{ + return E_NOTIMPL; +} + +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/AltStreamsFolder.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/AltStreamsFolder.h new file mode 100644 index 00000000..ccb6d6f4 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/AltStreamsFolder.h @@ -0,0 +1,92 @@ +// AltStreamsFolder.h + +#ifndef ZIP7_INC_ALT_STREAMS_FOLDER_H +#define ZIP7_INC_ALT_STREAMS_FOLDER_H + +#include "../../../Common/MyCom.h" + +#include "../../../Windows/FileFind.h" + +#include "../../Archive/IArchive.h" + +#include "IFolder.h" + +namespace NAltStreamsFolder { + +class CAltStreamsFolder; + +struct CAltStream +{ + UInt64 Size; + UInt64 PackSize; + bool PackSize_Defined; + UString Name; +}; + + +class CAltStreamsFolder Z7_final: + public IFolderFolder, + public IFolderCompare, + #ifdef USE_UNICODE_FSTRING + public IFolderGetItemName, + #endif + public IFolderWasChanged, + public IFolderOperations, + // public IFolderOperationsDeleteToRecycleBin, + public IFolderClone, + public IFolderGetSystemIconIndex, + public CMyUnknownImp +{ + Z7_COM_QI_BEGIN2(IFolderFolder) + Z7_COM_QI_ENTRY(IFolderCompare) + #ifdef USE_UNICODE_FSTRING + Z7_COM_QI_ENTRY(IFolderGetItemName) + #endif + Z7_COM_QI_ENTRY(IFolderWasChanged) + // Z7_COM_QI_ENTRY(IFolderOperationsDeleteToRecycleBin) + Z7_COM_QI_ENTRY(IFolderOperations) + Z7_COM_QI_ENTRY(IFolderClone) + Z7_COM_QI_ENTRY(IFolderGetSystemIconIndex) + Z7_COM_QI_END + Z7_COM_ADDREF_RELEASE + + Z7_IFACE_COM7_IMP(IFolderFolder) + Z7_IFACE_COM7_IMP(IFolderCompare) + #ifdef USE_UNICODE_FSTRING + Z7_IFACE_COM7_IMP(IFolderGetItemName) + #endif + Z7_IFACE_COM7_IMP(IFolderWasChanged) + Z7_IFACE_COM7_IMP(IFolderOperations) + Z7_IFACE_COM7_IMP(IFolderClone) + Z7_IFACE_COM7_IMP(IFolderGetSystemIconIndex) + + FString _pathBaseFile; // folder + FString _pathPrefix; // folder: + + CObjectVector Streams; + // CMyComPtr _parentFolder; + + NWindows::NFile::NFind::CFindChangeNotification _findChangeNotification; + + HRESULT GetItemFullSize(unsigned index, UInt64 &size, IProgress *progress); + void GetAbsPath(const wchar_t *name, FString &absPath); + +public: + // path must be with ':' at tail + HRESULT Init(const FString &path /* , IFolderFolder *parentFolder */); + + void GetFullPath(const CAltStream &item, FString &path) const + { + path = _pathPrefix; + path += us2fs(item.Name); + } + + void Clear() + { + Streams.Clear(); + } +}; + +} + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/App.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/App.cpp new file mode 100644 index 00000000..5b7d6167 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/App.cpp @@ -0,0 +1,1004 @@ +// App.cpp + +#include "StdAfx.h" + +#include "resource.h" +#include "OverwriteDialogRes.h" + +#include "../../../Windows/FileName.h" +#include "../../../Windows/PropVariantConv.h" + +/* +#include "Windows/COM.h" +#include "Windows/Error.h" +#include "Windows/FileDir.h" + +#include "Windows/PropVariant.h" +#include "Windows/Thread.h" +*/ + +#include "App.h" +#include "CopyDialog.h" +#include "ExtractCallback.h" +#include "FormatUtils.h" +#include "IFolder.h" +#include "LangUtils.h" +#include "MyLoadMenu.h" +#include "RegistryUtils.h" +#include "ViewSettings.h" + +#include "PropertyNameRes.h" + +using namespace NWindows; +using namespace NFile; +using namespace NDir; +using namespace NFind; +using namespace NName; + +extern HINSTANCE g_hInstance; + +#define kTempDirPrefix FTEXT("7zE") + +void CPanelCallbackImp::OnTab() +{ + if (g_App.NumPanels != 1) + _app->Panels[1 - _index].SetFocusToList(); + _app->RefreshTitle(); +} + +void CPanelCallbackImp::SetFocusToPath(unsigned index) +{ + unsigned newPanelIndex = index; + if (g_App.NumPanels == 1) + newPanelIndex = g_App.LastFocusedPanel; + _app->RefreshTitle(); + _app->Panels[newPanelIndex]._headerComboBox.SetFocus(); + _app->Panels[newPanelIndex]._headerComboBox.ShowDropDown(); +} + + +void CPanelCallbackImp::OnCopy(bool move, bool copyToSame) { _app->OnCopy(move, copyToSame, _index); } +void CPanelCallbackImp::OnSetSameFolder() { _app->OnSetSameFolder(_index); } +void CPanelCallbackImp::OnSetSubFolder() { _app->OnSetSubFolder(_index); } +void CPanelCallbackImp::PanelWasFocused() { _app->SetFocusedPanel(_index); _app->RefreshTitlePanel(_index); } +void CPanelCallbackImp::DragBegin() { _app->DragBegin(_index); } +void CPanelCallbackImp::DragEnd() { _app->DragEnd(); } +void CPanelCallbackImp::RefreshTitle(bool always) { _app->RefreshTitlePanel(_index, always); } + +void CApp::ReloadLangItems() +{ + LangString(IDS_N_SELECTED_ITEMS, LangString_N_SELECTED_ITEMS); +} + +void CApp::SetListSettings() +{ + CFmSettings st; + st.Load(); + + ShowSystemMenu = st.ShowSystemMenu; + + DWORD extendedStyle = LVS_EX_HEADERDRAGDROP; + if (st.FullRow) + extendedStyle |= LVS_EX_FULLROWSELECT; + if (st.ShowGrid) + extendedStyle |= LVS_EX_GRIDLINES; + + if (st.SingleClick) + { + extendedStyle |= LVS_EX_ONECLICKACTIVATE | LVS_EX_TRACKSELECT; + /* + if (ReadUnderline()) + extendedStyle |= LVS_EX_UNDERLINEHOT; + */ + } + + for (unsigned i = 0; i < kNumPanelsMax; i++) + { + CPanel &panel = Panels[i]; + panel._mySelectMode = st.AlternativeSelection; + panel._showDots = st.ShowDots; + panel._showRealFileIcons = st.ShowRealFileIcons; + panel._exStyle = extendedStyle; + + LONG_PTR style = panel._listView.GetStyle(); + if (st.AlternativeSelection) + style |= LVS_SINGLESEL; + else + style &= ~(LONG_PTR)(DWORD)LVS_SINGLESEL; + panel._listView.SetStyle(style); + panel.SetExtendedStyle(); + } +} + +#ifndef ILC_COLOR32 +#define ILC_COLOR32 0x0020 +#endif + +HRESULT CApp::CreateOnePanel(unsigned panelIndex, const UString &mainPath, const UString &arcFormat, + bool needOpenArc, + COpenResult &openRes) +{ + if (Panels[panelIndex].PanelCreated) + return S_OK; + + m_PanelCallbackImp[panelIndex].Init(this, panelIndex); + + UString path; + if (mainPath.IsEmpty()) + { + if (!::ReadPanelPath(panelIndex, path)) + path.Empty(); + } + else + path = mainPath; + + const unsigned id = 1000 + 100 * panelIndex; // check it + + return Panels[panelIndex].Create(_window, _window, + id, path, arcFormat, &m_PanelCallbackImp[panelIndex], &AppState, + needOpenArc, + openRes); +} + + +static void CreateToolbar(HWND parent, + NControl::CImageList &imageList, + NControl::CToolBar &toolBar, + bool largeButtons) +{ + toolBar.Attach(::CreateWindowEx(0, TOOLBARCLASSNAME, NULL, 0 + | WS_CHILD + | WS_VISIBLE + | TBSTYLE_FLAT + | TBSTYLE_TOOLTIPS + | TBSTYLE_WRAPABLE + // | TBSTYLE_AUTOSIZE + // | CCS_NORESIZE + #ifdef UNDER_CE + | CCS_NODIVIDER + | CCS_NOPARENTALIGN + #endif + ,0,0,0,0, parent, NULL, g_hInstance, NULL)); + + // TB_BUTTONSTRUCTSIZE message, which is required for + // backward compatibility. + toolBar.ButtonStructSize(); + + imageList.Create( + largeButtons ? 48: 24, + largeButtons ? 36: 24, + ILC_MASK | ILC_COLOR32, 0, 0); + toolBar.SetImageList(0, imageList); +} + + +struct CButtonInfo +{ + int CommandID; + UINT BitmapResID; + UINT Bitmap2ResID; + UINT StringResID; + + UString GetText() const { return LangString(StringResID); } +}; + +static const CButtonInfo g_StandardButtons[] = +{ + { IDM_COPY_TO, IDB_COPY, IDB_COPY2, IDS_BUTTON_COPY }, + { IDM_MOVE_TO, IDB_MOVE, IDB_MOVE2, IDS_BUTTON_MOVE }, + { IDM_DELETE, IDB_DELETE, IDB_DELETE2, IDS_BUTTON_DELETE } , + { IDM_PROPERTIES, IDB_INFO, IDB_INFO2, IDS_BUTTON_INFO } +}; + +static const CButtonInfo g_ArchiveButtons[] = +{ + { kMenuCmdID_Toolbar_Add, IDB_ADD, IDB_ADD2, IDS_ADD }, + { kMenuCmdID_Toolbar_Extract, IDB_EXTRACT, IDB_EXTRACT2, IDS_EXTRACT }, + { kMenuCmdID_Toolbar_Test, IDB_TEST, IDB_TEST2, IDS_TEST } +}; + +static bool SetButtonText(int commandID, const CButtonInfo *buttons, unsigned numButtons, UString &s) +{ + for (unsigned i = 0; i < numButtons; i++) + { + const CButtonInfo &b = buttons[i]; + if (b.CommandID == commandID) + { + s = b.GetText(); + return true; + } + } + return false; +} + +static void SetButtonText(int commandID, UString &s) +{ + if (SetButtonText(commandID, g_StandardButtons, Z7_ARRAY_SIZE(g_StandardButtons), s)) + return; + SetButtonText(commandID, g_ArchiveButtons, Z7_ARRAY_SIZE(g_ArchiveButtons), s); +} + +static void AddButton( + NControl::CImageList &imageList, + NControl::CToolBar &toolBar, + const CButtonInfo &butInfo, bool showText, bool large) +{ + TBBUTTON but; + but.iBitmap = 0; + but.idCommand = butInfo.CommandID; + but.fsState = TBSTATE_ENABLED; + but.fsStyle = TBSTYLE_BUTTON; + but.dwData = 0; + + UString s = butInfo.GetText(); + but.iString = 0; + if (showText) + but.iString = (INT_PTR)(LPCWSTR)s; + + but.iBitmap = imageList.GetImageCount(); + HBITMAP b = ::LoadBitmap(g_hInstance, + large ? + MAKEINTRESOURCE(butInfo.BitmapResID): + MAKEINTRESOURCE(butInfo.Bitmap2ResID)); + if (b) + { + imageList.AddMasked(b, RGB(255, 0, 255)); + ::DeleteObject(b); + } + #ifdef _UNICODE + toolBar.AddButton(1, &but); + #else + toolBar.AddButtonW(1, &but); + #endif +} + +void CApp::ReloadToolbars() +{ + _buttonsImageList.Destroy(); + _toolBar.Destroy(); + + + if (ShowArchiveToolbar || ShowStandardToolbar) + { + CreateToolbar(_window, _buttonsImageList, _toolBar, LargeButtons); + unsigned i; + if (ShowArchiveToolbar) + for (i = 0; i < Z7_ARRAY_SIZE(g_ArchiveButtons); i++) + AddButton(_buttonsImageList, _toolBar, g_ArchiveButtons[i], ShowButtonsLables, LargeButtons); + if (ShowStandardToolbar) + for (i = 0; i < Z7_ARRAY_SIZE(g_StandardButtons); i++) + AddButton(_buttonsImageList, _toolBar, g_StandardButtons[i], ShowButtonsLables, LargeButtons); + + _toolBar.AutoSize(); + } +} + +void CApp::SaveToolbarChanges() +{ + SaveToolbar(); + ReloadToolbars(); + MoveSubWindows(); +} + + +HRESULT CApp::Create(HWND hwnd, const UString &mainPath, const UString &arcFormat, int xSizes[2], bool needOpenArc, COpenResult &openRes) +{ + _window.Attach(hwnd); + + #ifdef UNDER_CE + _commandBar.Create(g_hInstance, hwnd, 1); + #endif + + MyLoadMenu(false); // needResetMenu + + #ifdef UNDER_CE + _commandBar.AutoSize(); + #endif + + ReadToolbar(); + ReloadToolbars(); + + unsigned i; + for (i = 0; i < kNumPanelsMax; i++) + Panels[i].PanelCreated = false; + + AppState.Read(); + + SetListSettings(); + + if (LastFocusedPanel >= kNumPanelsMax) + LastFocusedPanel = 0; + // ShowDeletedFiles = Read_ShowDeleted(); + + CListMode listMode; + listMode.Read(); + + for (i = 0; i < kNumPanelsMax; i++) + { + CPanel &panel = Panels[i]; + panel._listViewMode = listMode.Panels[i]; + panel._xSize = xSizes[i]; + panel._flatModeForArc = ReadFlatView(i); + } + + for (i = 0; i < kNumPanelsMax; i++) + { + unsigned panelIndex = i; + if (needOpenArc && LastFocusedPanel == 1) + panelIndex = 1 - i; + + bool isMainPanel = (panelIndex == LastFocusedPanel); + + if (NumPanels > 1 || isMainPanel) + { + if (NumPanels == 1) + Panels[panelIndex]._xSize = xSizes[0] + xSizes[1]; + + COpenResult openRes2; + UString path; + if (isMainPanel) + path = mainPath; + + RINOK(CreateOnePanel(panelIndex, path, arcFormat, + isMainPanel && needOpenArc, + *(isMainPanel ? &openRes : &openRes2))) + + if (isMainPanel) + { + if (needOpenArc && !openRes.ArchiveIsOpened) + return S_OK; + } + } + } + + SetFocusedPanel(LastFocusedPanel); + Panels[LastFocusedPanel].SetFocusToList(); + return S_OK; +} + + +HRESULT CApp::SwitchOnOffOnePanel() +{ + if (NumPanels == 1) + { + NumPanels++; + COpenResult openRes; + RINOK(CreateOnePanel(1 - LastFocusedPanel, UString(), UString(), + false, // needOpenArc + openRes)) + Panels[1 - LastFocusedPanel].Enable(true); + Panels[1 - LastFocusedPanel].Show(SW_SHOWNORMAL); + } + else + { + NumPanels--; + Panels[1 - LastFocusedPanel].Enable(false); + Panels[1 - LastFocusedPanel].Show(SW_HIDE); + } + MoveSubWindows(); + return S_OK; +} + +void CApp::Save() +{ + AppState.Save(); + CListMode listMode; + + for (unsigned i = 0; i < kNumPanelsMax; i++) + { + const CPanel &panel = Panels[i]; + UString path; + if (panel._parentFolders.IsEmpty()) + path = panel._currentFolderPrefix; + else + path = panel._parentFolders[0].ParentFolderPath; + // GetFolderPath(panel._parentFolders[0].ParentFolder); + SavePanelPath(i, path); + listMode.Panels[i] = panel.GetListViewMode(); + SaveFlatView(i, panel._flatModeForArc); + } + + listMode.Save(); + // Save_ShowDeleted(ShowDeletedFiles); +} + +void CApp::ReleaseApp() +{ + // 24.09: ReleasePanel() will stop panel timer processing. + // but we want to stop timer processing for all panels + // before ReleasePanel() calling. + unsigned i; + for (i = 0; i < kNumPanelsMax; i++) + Panels[i].Disable_Processing_Timer_Notify_StatusBar(); + // It's for unloading COM dll's: don't change it. + for (i = 0; i < kNumPanelsMax; i++) + Panels[i].ReleasePanel(); +} + +// reduces path to part that exists on disk (or root prefix of path) +// output path is normalized (with WCHAR_PATH_SEPARATOR) +static void Reduce_Path_To_RealFileSystemPath(UString &path) +{ + unsigned prefixSize = GetRootPrefixSize(path); + + while (!path.IsEmpty()) + { + if (NFind::DoesDirExist_FollowLink(us2fs(path))) + { + NName::NormalizeDirPathPrefix(path); + break; + } + int pos = path.ReverseFind_PathSepar(); + if (pos < 0) + { + path.Empty(); + break; + } + path.DeleteFrom((unsigned)(pos + 1)); + if ((unsigned)pos + 1 == prefixSize) + break; + path.DeleteFrom((unsigned)pos); + } +} + +// returns: true, if such dir exists or is root +/* +static bool CheckFolderPath(const UString &path) +{ + UString pathReduced = path; + Reduce_Path_To_RealFileSystemPath(pathReduced); + return (pathReduced == path); +} +*/ + +extern UString ConvertSizeToString(UInt64 value); + +static void AddSizeValue(UString &s, UInt64 size) +{ + s += MyFormatNew(IDS_FILE_SIZE, ConvertSizeToString(size)); +} + +static void AddValuePair1(UString &s, UINT resourceID, UInt64 size) +{ + AddLangString(s, resourceID); + s += ": "; + AddSizeValue(s, size); + s.Add_LF(); +} + +void AddValuePair2(UString &s, UINT resourceID, UInt64 num, UInt64 size); +void AddValuePair2(UString &s, UINT resourceID, UInt64 num, UInt64 size) +{ + if (num == 0) + return; + AddLangString(s, resourceID); + s += ": "; + s += ConvertSizeToString(num); + + if (size != (UInt64)(Int64)-1) + { + s += " ( "; + AddSizeValue(s, size); + s += " )"; + } + s.Add_LF(); +} + +static void AddPropValueToSum(IFolderFolder *folder, UInt32 index, PROPID propID, UInt64 &sum) +{ + if (sum == (UInt64)(Int64)-1) + return; + NCOM::CPropVariant prop; + folder->GetProperty(index, propID, &prop); + UInt64 val = 0; + if (ConvertPropVariantToUInt64(prop, val)) + sum += val; + else + sum = (UInt64)(Int64)-1; +} + +UString CPanel::GetItemsInfoString(const CRecordVector &indices) +{ + UString info; + UInt64 numDirs, numFiles, filesSize, foldersSize; + numDirs = numFiles = filesSize = foldersSize = 0; + + unsigned i; + for (i = 0; i < indices.Size(); i++) + { + const UInt32 index = indices[i]; + if (IsItem_Folder(index)) + { + AddPropValueToSum(_folder, index, kpidSize, foldersSize); + numDirs++; + } + else + { + AddPropValueToSum(_folder, index, kpidSize, filesSize); + numFiles++; + } + } + + AddValuePair2(info, IDS_PROP_FOLDERS, numDirs, foldersSize); + AddValuePair2(info, IDS_PROP_FILES, numFiles, filesSize); + int numDefined = ((foldersSize != (UInt64)(Int64)-1) && foldersSize != 0) ? 1: 0; + numDefined += ((filesSize != (UInt64)(Int64)-1) && filesSize != 0) ? 1: 0; + if (numDefined == 2) + AddValuePair1(info, IDS_PROP_SIZE, filesSize + foldersSize); + + info.Add_LF(); + info += _currentFolderPrefix; + + for (i = 0; i < indices.Size() && (int)i < (int)kCopyDialog_NumInfoLines - 6; i++) + { + info.Add_LF(); + info += " "; + const UInt32 index = indices[i]; + info += GetItemRelPath(index); + if (IsItem_Folder(index)) + info.Add_PathSepar(); + } + if (i != indices.Size()) + { + info.Add_LF(); + info += " ..."; + } + return info; +} + +bool IsCorrectFsName(const UString &name); + + + +/* Returns true, if path is path that can be used as path for File System functions +*/ + +/* +static bool IsFsPath(const FString &path) +{ + if (!IsAbsolutePath(path)) + return false; + unsigned prefixSize = GetRootPrefixSize(path); +} +*/ + +void CApp::OnCopy(bool move, bool copyToSame, unsigned srcPanelIndex) +{ + const unsigned destPanelIndex = (NumPanels <= 1) ? srcPanelIndex : (1 - srcPanelIndex); + CPanel &srcPanel = Panels[srcPanelIndex]; + CPanel &destPanel = Panels[destPanelIndex]; + + CPanel::CDisableTimerProcessing disableTimerProcessing1(destPanel); + CPanel::CDisableTimerProcessing disableTimerProcessing2(srcPanel); + + if (move) + { + if (!srcPanel.CheckBeforeUpdate(IDS_MOVE)) + return; + } + else if (!srcPanel.DoesItSupportOperations()) + { + srcPanel.MessageBox_Error_UnsupportOperation(); + return; + } + + CRecordVector indices; + UString destPath; + bool useDestPanel = false; + + { + if (copyToSame) + { + const int focusedItem = srcPanel._listView.GetFocusedItem(); + if (focusedItem < 0) + return; + const unsigned realIndex = srcPanel.GetRealItemIndex(focusedItem); + if (realIndex == kParentIndex) + return; + indices.Add(realIndex); + destPath = srcPanel.GetItemName(realIndex); + } + else + { + srcPanel.Get_ItemIndices_OperSmart(indices); + if (indices.Size() == 0) + return; + destPath = destPanel.GetFsPath(); + if (NumPanels == 1) + Reduce_Path_To_RealFileSystemPath(destPath); + } + } + + UStringVector copyFolders; + ReadCopyHistory(copyFolders); + + const bool useFullItemPaths = srcPanel.Is_IO_FS_Folder(); // maybe we need flat also here ?? + + { + CCopyDialog copyDialog; + + copyDialog.Strings = copyFolders; + copyDialog.Value = destPath; + LangString(move ? IDS_MOVE : IDS_COPY, copyDialog.Title); + LangString(move ? IDS_MOVE_TO : IDS_COPY_TO, copyDialog.Static); + copyDialog.Info = srcPanel.GetItemsInfoString(indices); + + if (copyDialog.Create(srcPanel.GetParent()) != IDOK) + return; + + destPath = copyDialog.Value; + } + + { + if (destPath.IsEmpty()) + { + srcPanel.MessageBox_Error_UnsupportOperation(); + return; + } + + UString correctName; + if (!srcPanel.CorrectFsPath(destPath, correctName)) + { + srcPanel.MessageBox_Error_HRESULT(E_INVALIDARG); + return; + } + + if (IsAbsolutePath(destPath)) + destPath.Empty(); + else + destPath = srcPanel.GetFsPath(); + destPath += correctName; + + #if defined(_WIN32) && !defined(UNDER_CE) + if (destPath.Len() != 0 && destPath[0] == '\\') + if (destPath.Len() == 1 || destPath[1] != '\\') + { + srcPanel.MessageBox_Error_UnsupportOperation(); + return; + } + #endif + + bool possibleToUseDestPanel = false; + + if (CompareFileNames(destPath, destPanel.GetFsPath()) == 0) + { + if (NumPanels == 1 || CompareFileNames(destPath, srcPanel.GetFsPath()) == 0) + { + srcPanel.MessageBox_Error(L"Cannot copy files onto itself"); + return; + } + + if (destPanel.DoesItSupportOperations()) + possibleToUseDestPanel = true; + } + + bool destIsFsPath = false; + + if (possibleToUseDestPanel) + { + if (destPanel.IsFSFolder() || destPanel.IsAltStreamsFolder()) + destIsFsPath = true; + else if (destPanel.IsFSDrivesFolder() || destPanel.IsRootFolder()) + { + srcPanel.MessageBox_Error_UnsupportOperation(); + return; + } + } + else + { + if (IsAltPathPrefix(us2fs(destPath))) + { + // we allow alt streams dest only to alt stream folder in second panel + srcPanel.MessageBox_Error_UnsupportOperation(); + return; + /* + FString basePath = us2fs(destPath); + basePath.DeleteBack(); + if (!DoesFileOrDirExist(basePath)) + { + srcPanel.MessageBoxError2Lines(basePath, ERROR_FILE_NOT_FOUND); // GetLastError() + return; + } + destIsFsPath = true; + */ + } + else + { + if (indices.Size() == 1 && + !destPath.IsEmpty() && !IS_PATH_SEPAR(destPath.Back())) + { + int pos = destPath.ReverseFind_PathSepar(); + if (pos < 0) + { + srcPanel.MessageBox_Error_UnsupportOperation(); + return; + } + { + /* + #ifdef _WIN32 + UString name = destPath.Ptr(pos + 1); + if (name.Find(L':') >= 0) + { + srcPanel.MessageBox_Error_UnsupportOperation(); + return; + } + #endif + */ + UString prefix = destPath.Left(pos + 1); + if (!CreateComplexDir(us2fs(prefix))) + { + const HRESULT lastError = GetLastError_noZero_HRESULT(); + srcPanel.MessageBox_Error_2Lines_Message_HRESULT(prefix, lastError); + return; + } + } + // bool isFolder = srcPanael.IsItem_Folder(indices[0]); + } + else + { + NName::NormalizeDirPathPrefix(destPath); + if (!CreateComplexDir(us2fs(destPath))) + { + const HRESULT lastError = GetLastError_noZero_HRESULT(); + srcPanel.MessageBox_Error_2Lines_Message_HRESULT(destPath, lastError); + return; + } + } + destIsFsPath = true; + } + } + + if (!destIsFsPath) + useDestPanel = true; + + AddUniqueStringToHeadOfList(copyFolders, destPath); + while (copyFolders.Size() > 20) + copyFolders.DeleteBack(); + SaveCopyHistory(copyFolders); + } + + bool useSrcPanel = !useDestPanel || !srcPanel.Is_IO_FS_Folder(); + + bool useTemp = useSrcPanel && useDestPanel; + if (useTemp && NumPanels == 1) + { + srcPanel.MessageBox_Error_UnsupportOperation(); + return; + } + + CTempDir tempDirectory; + FString tempDirPrefix; + if (useTemp) + { + tempDirectory.Create(kTempDirPrefix); + tempDirPrefix = tempDirectory.GetPath(); + NFile::NName::NormalizeDirPathPrefix(tempDirPrefix); + } + + CSelectedState srcSelState; + CSelectedState destSelState; + srcPanel.SaveSelectedState(srcSelState); + destPanel.SaveSelectedState(destSelState); + + CPanel::CDisableNotify disableNotify1(destPanel); + CPanel::CDisableNotify disableNotify2(srcPanel); + + HRESULT result = S_OK; + + if (useSrcPanel) + { + CCopyToOptions options; + // options.src_Is_IO_FS_Folder = useFullItemPaths; + options.folder = useTemp ? fs2us(tempDirPrefix) : destPath; + options.moveMode = move; + options.includeAltStreams = true; + options.replaceAltStreamChars = false; + options.showErrorMessages = true; + + result = srcPanel.CopyTo(options, indices, NULL); + } + + if (result == S_OK && useDestPanel) + { + UStringVector filePaths; + UString folderPrefix; + + if (useTemp) + folderPrefix = fs2us(tempDirPrefix); + else + folderPrefix = srcPanel.GetFsPath(); + + filePaths.ClearAndReserve(indices.Size()); + + FOR_VECTOR (i, indices) + { + UInt32 index = indices[i]; + UString s; + if (useFullItemPaths) + s = srcPanel.GetItemRelPath2(index); + else + s = srcPanel.GetItemName_for_Copy(index); + filePaths.AddInReserved(s); + } + + result = destPanel.CopyFrom(move, folderPrefix, filePaths, true, NULL); + } + + if (result != S_OK) + { + // disableNotify1.Restore(); + // disableNotify2.Restore(); + // For Password: + // srcPanel.SetFocusToList(); + // srcPanel.InvalidateList(NULL, true); + + if (result != E_ABORT) + srcPanel.MessageBox_Error_HRESULT(result); + // return; + } + + RefreshTitleAlways(); + + if (copyToSame || move) + { + srcPanel.RefreshListCtrl(srcSelState); + } + + if (!copyToSame) + { + destPanel.RefreshListCtrl(destSelState); + srcPanel.KillSelection(); + } + + disableNotify1.Restore(); + disableNotify2.Restore(); + srcPanel.SetFocusToList(); +} + +void CApp::OnSetSameFolder(unsigned srcPanelIndex) +{ + if (NumPanels <= 1) + return; + const CPanel &srcPanel = Panels[srcPanelIndex]; + CPanel &destPanel = Panels[1 - srcPanelIndex]; + destPanel.BindToPathAndRefresh(srcPanel._currentFolderPrefix); +} + +void CApp::OnSetSubFolder(unsigned srcPanelIndex) +{ + if (NumPanels <= 1) + return; + const CPanel &srcPanel = Panels[srcPanelIndex]; + CPanel &destPanel = Panels[1 - srcPanelIndex]; + + const int focusedItem = srcPanel._listView.GetFocusedItem(); + if (focusedItem < 0) + return; + const unsigned realIndex = srcPanel.GetRealItemIndex(focusedItem); + if (!srcPanel.IsItem_Folder(realIndex)) + return; + + // destPanel.BindToFolder(srcPanel._currentFolderPrefix + srcPanel.GetItemName(realIndex) + WCHAR_PATH_SEPARATOR); + + CMyComPtr newFolder; + if (realIndex == kParentIndex) + { + if (srcPanel._folder->BindToParentFolder(&newFolder) != S_OK) + return; + if (!newFolder) + { + { + const UString parentPrefix = srcPanel.GetParentDirPrefix(); + COpenResult openRes; + destPanel.BindToPath(parentPrefix, UString(), openRes); + } + destPanel.RefreshListCtrl(); + return; + } + } + else + { + if (srcPanel._folder->BindToFolder(realIndex, &newFolder) != S_OK) + return; + } + + if (!newFolder) + return; + + destPanel.CloseOpenFolders(); + destPanel.SetNewFolder(newFolder); + destPanel.RefreshListCtrl(); +} + +/* +int CApp::GetFocusedPanelIndex() const +{ + return LastFocusedPanel; + HWND hwnd = ::GetFocus(); + for (;;) + { + if (hwnd == 0) + return 0; + for (unsigned i = 0; i < kNumPanelsMax; i++) + { + if (PanelsCreated[i] && + ((HWND)Panels[i] == hwnd || Panels[i]._listView == hwnd)) + return i; + } + hwnd = GetParent(hwnd); + } +} +*/ + +static UString g_ToolTipBuffer; +static CSysString g_ToolTipBufferSys; + +void CApp::OnNotify(int /* ctrlID */, LPNMHDR pnmh) +{ + { + if (pnmh->code == TTN_GETDISPINFO) + { + LPNMTTDISPINFO info = (LPNMTTDISPINFO)pnmh; + info->hinst = NULL; + g_ToolTipBuffer.Empty(); + SetButtonText((int)info->hdr.idFrom, g_ToolTipBuffer); + g_ToolTipBufferSys = GetSystemString(g_ToolTipBuffer); + info->lpszText = g_ToolTipBufferSys.Ptr_non_const(); + return; + } + #ifndef _UNICODE + if (pnmh->code == TTN_GETDISPINFOW) + { + LPNMTTDISPINFOW info = (LPNMTTDISPINFOW)pnmh; + info->hinst = NULL; + g_ToolTipBuffer.Empty(); + SetButtonText((int)info->hdr.idFrom, g_ToolTipBuffer); + info->lpszText = g_ToolTipBuffer.Ptr_non_const(); + return; + } + #endif + } +} + +void CApp::RefreshTitle(bool always) +{ + UString path = GetFocusedPanel()._currentFolderPrefix; + if (path.IsEmpty()) + path = "7-Zip"; // LangString(IDS_APP_TITLE); + if (!always && path == PrevTitle) + return; + PrevTitle = path; + NWindows::MySetWindowText(_window, path); +} + +void CApp::RefreshTitlePanel(unsigned panelIndex, bool always) +{ + if (panelIndex != GetFocusedPanelIndex()) + return; + RefreshTitle(always); +} + +static void AddUniqueStringToHead(UStringVector &list, const UString &s) +{ + for (unsigned i = 0; i < list.Size();) + if (s.IsEqualTo_NoCase(list[i])) + list.Delete(i); + else + i++; + list.Insert(0, s); +} + + +void CFolderHistory::Normalize() +{ + const unsigned kMaxSize = 100; + if (Strings.Size() > kMaxSize) + Strings.DeleteFrom(kMaxSize); +} + +void CFolderHistory::AddString(const UString &s) +{ + NSynchronization::CCriticalSectionLock lock(_criticalSection); + AddUniqueStringToHead(Strings, s); + Normalize(); +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/App.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/App.h new file mode 100644 index 00000000..cf74d6a5 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/App.h @@ -0,0 +1,314 @@ +// App.h + +#ifndef ZIP7_INC_APP_H +#define ZIP7_INC_APP_H + +#include "../../../Windows/Control/CommandBar.h" +#include "../../../Windows/Control/ImageList.h" + +#include "AppState.h" +#include "Panel.h" + +class CApp; + +extern CApp g_App; +extern HWND g_HWND; + +const unsigned kNumPanelsMax = 2; + +extern bool g_IsSmallScreen; + +// must be larger than context menu IDs +const int kMenuCmdID_Toolbar_Start = 1070; +const int kMenuCmdID_Plugin_Start = 1100; + +enum +{ + kMenuCmdID_Toolbar_Add = kMenuCmdID_Toolbar_Start, + kMenuCmdID_Toolbar_Extract, + kMenuCmdID_Toolbar_Test, + kMenuCmdID_Toolbar_End +}; + +class CPanelCallbackImp Z7_final: public CPanelCallback +{ + CApp *_app; + unsigned _index; +public: + void Init(CApp *app, unsigned index) + { + _app = app; + _index = index; + } + virtual void OnTab() Z7_override; + virtual void SetFocusToPath(unsigned index) Z7_override; + virtual void OnCopy(bool move, bool copyToSame) Z7_override; + virtual void OnSetSameFolder() Z7_override; + virtual void OnSetSubFolder() Z7_override; + virtual void PanelWasFocused() Z7_override; + virtual void DragBegin() Z7_override; + virtual void DragEnd() Z7_override; + virtual void RefreshTitle(bool always) Z7_override; +}; + + +class CDropTarget; + +class CApp +{ +public: + NWindows::CWindow _window; + bool ShowSystemMenu; + bool AutoRefresh_Mode; + // bool ShowDeletedFiles; + unsigned NumPanels; + unsigned LastFocusedPanel; + + bool ShowStandardToolbar; + bool ShowArchiveToolbar; + bool ShowButtonsLables; + bool LargeButtons; + + CAppState AppState; + CPanelCallbackImp m_PanelCallbackImp[kNumPanelsMax]; + CPanel Panels[kNumPanelsMax]; + + NWindows::NControl::CImageList _buttonsImageList; + + #ifdef UNDER_CE + NWindows::NControl::CCommandBar _commandBar; + #endif + NWindows::NControl::CToolBar _toolBar; + + CDropTarget *_dropTargetSpec; + CMyComPtr _dropTarget; + + UString LangString_N_SELECTED_ITEMS; + + void ReloadLangItems(); + + CApp(): + _window(NULL), + AutoRefresh_Mode(true), + NumPanels(2), + LastFocusedPanel(0) + { + SetPanels_AutoRefresh_Mode(); + } + + void CreateDragTarget(); + void SetFocusedPanel(unsigned index); + void DragBegin(unsigned panelIndex); + void DragEnd(); + + void OnCopy(bool move, bool copyToSame, unsigned srcPanelIndex); + void OnSetSameFolder(unsigned srcPanelIndex); + void OnSetSubFolder(unsigned srcPanelIndex); + + HRESULT CreateOnePanel(unsigned panelIndex, const UString &mainPath, const UString &arcFormat, bool needOpenArc, COpenResult &openRes); + HRESULT Create(HWND hwnd, const UString &mainPath, const UString &arcFormat, int xSizes[2], bool needOpenArc, COpenResult &openRes); + void Read(); + void Save(); + void ReleaseApp(); + + // void SetFocus(int panelIndex) { Panels[panelIndex].SetFocusToList(); } + void SetFocusToLastItem() { Panels[LastFocusedPanel].SetFocusToLastRememberedItem(); } + unsigned GetFocusedPanelIndex() const { return LastFocusedPanel; } + bool IsPanelVisible(unsigned index) const { return (NumPanels > 1 || index == LastFocusedPanel); } + CPanel &GetFocusedPanel() { return Panels[GetFocusedPanelIndex()]; } + + // File Menu + void OpenItem() { GetFocusedPanel().OpenSelectedItems(true); } + void OpenItemInside(const wchar_t *type) { GetFocusedPanel().OpenFocusedItemAsInternal(type); } + void OpenItemOutside() { GetFocusedPanel().OpenSelectedItems(false); } + void EditItem(bool useEditor) { GetFocusedPanel().EditItem(useEditor); } + void Rename() { GetFocusedPanel().RenameFile(); } + void CopyTo() { OnCopy(false, false, GetFocusedPanelIndex()); } + void MoveTo() { OnCopy(true, false, GetFocusedPanelIndex()); } + void Delete(bool toRecycleBin) { GetFocusedPanel().DeleteItems(toRecycleBin); } + HRESULT CalculateCrc2(const UString &methodName); + void CalculateCrc(const char *methodName); + + void DiffFiles(const UString &path1, const UString &path2); + void DiffFiles(); + + void VerCtrl(unsigned id); + + void Split(); + void Combine(); + void Properties() { GetFocusedPanel().Properties(); } + void Comment() { GetFocusedPanel().ChangeComment(); } + + #ifndef UNDER_CE + void Link(); + void OpenAltStreams() { GetFocusedPanel().OpenAltStreams(); } + #endif + + void CreateFolder() { GetFocusedPanel().CreateFolder(); } + void CreateFile() { GetFocusedPanel().CreateFile(); } + + // Edit + void EditCut() { GetFocusedPanel().EditCut(); } + void EditCopy() { GetFocusedPanel().EditCopy(); } + void EditPaste() { GetFocusedPanel().EditPaste(); } + + void SelectAll(bool selectMode) { GetFocusedPanel().SelectAll(selectMode); } + void InvertSelection() { GetFocusedPanel().InvertSelection(); } + void SelectSpec(bool selectMode) { GetFocusedPanel().SelectSpec(selectMode); } + void SelectByType(bool selectMode) { GetFocusedPanel().SelectByType(selectMode); } + + void Refresh_StatusBar() { GetFocusedPanel().Refresh_StatusBar(); } + + void SetListViewMode(UInt32 index) { GetFocusedPanel().SetListViewMode(index); } + UInt32 GetListViewMode() { return GetFocusedPanel().GetListViewMode(); } + PROPID GetSortID() { return GetFocusedPanel().GetSortID(); } + + void SortItemsWithPropID(PROPID propID) { GetFocusedPanel().SortItemsWithPropID(propID); } + + void OpenRootFolder() { GetFocusedPanel().OpenDrivesFolder(); } + void OpenParentFolder() { GetFocusedPanel().OpenParentFolder(); } + void FoldersHistory() { GetFocusedPanel().FoldersHistory(); } + void RefreshView() { GetFocusedPanel().OnReload(); } + void RefreshAllPanels() + { + for (unsigned i = 0; i < NumPanels; i++) + { + unsigned index = i; + if (NumPanels == 1) + index = LastFocusedPanel; + Panels[index].OnReload(); + } + } + + /* + void SysIconsWereChanged() + { + for (unsigned i = 0; i < NumPanels; i++) + { + unsigned index = i; + if (NumPanels == 1) + index = LastFocusedPanel; + Panels[index].SysIconsWereChanged(); + } + } + */ + + void SetListSettings(); + HRESULT SwitchOnOffOnePanel(); + + CIntVector _timestampLevels; + + bool GetFlatMode() { return Panels[LastFocusedPanel].GetFlatMode(); } + + int GetTimestampLevel() const { return Panels[LastFocusedPanel]._timestampLevel; } + void SetTimestampLevel(int level) + { + for (unsigned i = 0; i < kNumPanelsMax; i++) + { + CPanel &panel = Panels[i]; + panel._timestampLevel = level; + } + RedrawListItems_InPanels(); + } + + void RedrawListItems_InPanels() + { + for (unsigned i = 0; i < kNumPanelsMax; i++) + { + CPanel &panel = Panels[i]; + if (panel.PanelCreated) + panel.RedrawListItems(); + } + } + + // bool Get_ShowNtfsStrems_Mode() { return Panels[LastFocusedPanel].Get_ShowNtfsStrems_Mode(); } + + void ChangeFlatMode() { Panels[LastFocusedPanel].ChangeFlatMode(); } + // void Change_ShowNtfsStrems_Mode() { Panels[LastFocusedPanel].Change_ShowNtfsStrems_Mode(); } + // void Change_ShowDeleted() { ShowDeletedFiles = !ShowDeletedFiles; } + + bool Get_AutoRefresh_Mode() + { + // return Panels[LastFocusedPanel].Get_ShowNtfsStrems_Mode(); + return AutoRefresh_Mode; + } + void Change_AutoRefresh_Mode() + { + AutoRefresh_Mode = !AutoRefresh_Mode; + SetPanels_AutoRefresh_Mode(); + } + void SetPanels_AutoRefresh_Mode() + { + for (unsigned i = 0; i < kNumPanelsMax; i++) + Panels[i].Set_AutoRefresh_Mode(AutoRefresh_Mode); + } + + void OpenBookmark(unsigned index) { GetFocusedPanel().OpenBookmark(index); } + void SetBookmark(unsigned index) { GetFocusedPanel().SetBookmark(index); } + + void ReloadToolbars(); + void ReadToolbar() + { + const UInt32 mask = ReadToolbarsMask(); + if (mask & ((UInt32)1 << 31)) + { + ShowButtonsLables = !g_IsSmallScreen; + LargeButtons = false; + ShowStandardToolbar = ShowArchiveToolbar = true; + } + else + { + ShowButtonsLables = ((mask & 1) != 0); + LargeButtons = ((mask & 2) != 0); + ShowStandardToolbar = ((mask & 4) != 0); + ShowArchiveToolbar = ((mask & 8) != 0); + } + } + void SaveToolbar() + { + UInt32 mask = 0; + if (ShowButtonsLables) mask |= 1; + if (LargeButtons) mask |= 2; + if (ShowStandardToolbar) mask |= 4; + if (ShowArchiveToolbar) mask |= 8; + SaveToolbarsMask(mask); + } + + void SaveToolbarChanges(); + + void SwitchStandardToolbar() + { + ShowStandardToolbar = !ShowStandardToolbar; + SaveToolbarChanges(); + } + void SwitchArchiveToolbar() + { + ShowArchiveToolbar = !ShowArchiveToolbar; + SaveToolbarChanges(); + } + void SwitchButtonsLables() + { + ShowButtonsLables = !ShowButtonsLables; + SaveToolbarChanges(); + } + void SwitchLargeButtons() + { + LargeButtons = !LargeButtons; + SaveToolbarChanges(); + } + + void AddToArchive() { GetFocusedPanel().AddToArchive(); } + void ExtractArchives() { GetFocusedPanel().ExtractArchives(); } + void TestArchives() { GetFocusedPanel().TestArchives(); } + + void OnNotify(int ctrlID, LPNMHDR pnmh); + + UString PrevTitle; + void RefreshTitle(bool always = false); + void RefreshTitleAlways() { RefreshTitle(true); } + void RefreshTitlePanel(unsigned panelIndex, bool always = false); + + void MoveSubWindows(); +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/AppState.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/AppState.h new file mode 100644 index 00000000..6630b962 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/AppState.h @@ -0,0 +1,95 @@ +// AppState.h + +#ifndef ZIP7_INC_APP_STATE_H +#define ZIP7_INC_APP_STATE_H + +#include "../../../Windows/Synchronization.h" + +#include "ViewSettings.h" + +class CFastFolders +{ + NWindows::NSynchronization::CCriticalSection _criticalSection; +public: + UStringVector Strings; + void SetString(unsigned index, const UString &s) + { + NWindows::NSynchronization::CCriticalSectionLock lock(_criticalSection); + while (Strings.Size() <= index) + Strings.AddNew(); + Strings[index] = s; + } + UString GetString(unsigned index) + { + NWindows::NSynchronization::CCriticalSectionLock lock(_criticalSection); + if (index >= Strings.Size()) + return UString(); + return Strings[index]; + } + void Save() + { + NWindows::NSynchronization::CCriticalSectionLock lock(_criticalSection); + SaveFastFolders(Strings); + } + void Read() + { + NWindows::NSynchronization::CCriticalSectionLock lock(_criticalSection); + ReadFastFolders(Strings); + } +}; + +class CFolderHistory +{ + NWindows::NSynchronization::CCriticalSection _criticalSection; + UStringVector Strings; + + void Normalize(); + +public: + + void GetList(UStringVector &foldersHistory) + { + NWindows::NSynchronization::CCriticalSectionLock lock(_criticalSection); + foldersHistory = Strings; + } + + void AddString(const UString &s); + + void RemoveAll() + { + NWindows::NSynchronization::CCriticalSectionLock lock(_criticalSection); + Strings.Clear(); + } + + void Save() + { + NWindows::NSynchronization::CCriticalSectionLock lock(_criticalSection); + SaveFolderHistory(Strings); + } + + void Read() + { + NWindows::NSynchronization::CCriticalSectionLock lock(_criticalSection); + ReadFolderHistory(Strings); + Normalize(); + } +}; + +struct CAppState +{ + CFastFolders FastFolders; + CFolderHistory FolderHistory; + + void Save() + { + FastFolders.Save(); + FolderHistory.Save(); + } + void Read() + { + FastFolders.Read(); + FolderHistory.Read(); + } +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/BrowseDialog.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/BrowseDialog.cpp new file mode 100644 index 00000000..b12d8e81 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/BrowseDialog.cpp @@ -0,0 +1,1132 @@ +// BrowseDialog.cpp + +#include "StdAfx.h" + +#include "../../../Common/MyWindows.h" + +#include "../../../Common/IntToString.h" + +#ifndef UNDER_CE +#include "../../../Windows/CommonDialog.h" +#include "../../../Windows/Shell.h" +#endif + +#include "../../../Windows/FileName.h" +#include "../../../Windows/FileFind.h" + +#ifdef UNDER_CE +#include +#endif + +#include "BrowseDialog.h" + +#define USE_MY_BROWSE_DIALOG + +#ifdef USE_MY_BROWSE_DIALOG + +#include "../../../Common/Defs.h" +#include "../../../Common/Wildcard.h" + +#include "../../../Windows/FileDir.h" +#include "../../../Windows/PropVariantConv.h" + +#include "../../../Windows/Control/ComboBox.h" +#include "../../../Windows/Control/Dialog.h" +#include "../../../Windows/Control/Edit.h" +#include "../../../Windows/Control/ListView.h" + +#include "BrowseDialogRes.h" +#include "PropertyNameRes.h" +#include "SysIconUtils.h" + +#ifndef Z7_SFX +#include "RegistryUtils.h" +#endif + +#endif // USE_MY_BROWSE_DIALOG + +#include "ComboDialog.h" +#include "LangUtils.h" + +#include "resource.h" + +using namespace NWindows; +using namespace NFile; +using namespace NName; +using namespace NFind; + +static void MessageBox_Error_Global(HWND wnd, const wchar_t *message) +{ + ::MessageBoxW(wnd, message, L"7-Zip", MB_ICONERROR); +} + +#ifdef USE_MY_BROWSE_DIALOG + +#if 0 +extern HINSTANCE g_hInstance; +#endif +extern bool g_LVN_ITEMACTIVATE_Support; + +static const int kParentIndex = -1; +static const UINT k_Message_RefreshPathEdit = WM_APP + 1; + +extern UString HResultToMessage(HRESULT errorCode); + +static void MessageBox_HResError(HWND wnd, HRESULT errorCode, const wchar_t *name) +{ + UString s = HResultToMessage(errorCode); + if (name) + { + s.Add_LF(); + s += name; + } + MessageBox_Error_Global(wnd, s); +} + +class CBrowseDialog: public NControl::CModalDialog +{ + NControl::CListView _list; + NControl::CEdit _pathEdit; + NControl::CComboBox _filterCombo; + + CObjectVector _files; + + CExtToIconMap _extToIconMap; + int _sortIndex; + bool _ascending; + #ifndef Z7_SFX + bool _showDots; + #endif + UString _topDirPrefix; // we don't open parent of that folder + UString DirPrefix; + + virtual bool OnInit() Z7_override; + virtual bool OnSize(WPARAM wParam, int xSize, int ySize) Z7_override; + virtual bool OnMessage(UINT message, WPARAM wParam, LPARAM lParam) Z7_override; + virtual bool OnNotify(UINT controlID, LPNMHDR header) Z7_override; + virtual bool OnCommand(unsigned code, unsigned itemID, LPARAM lParam) Z7_override; + virtual bool OnButtonClicked(unsigned buttonID, HWND buttonHWND) Z7_override; + virtual void OnOK() Z7_override; + + bool OnKeyDown(LPNMLVKEYDOWN keyDownInfo); + + void Post_RefreshPathEdit() { PostMsg(k_Message_RefreshPathEdit); } + + bool GetParentPath(const UString &path, UString &parentPrefix, UString &name); + // Reload changes DirPrefix. Don't send DirPrefix in pathPrefix parameter + HRESULT Reload(const UString &pathPrefix, const UString &selectedName); + HRESULT Reload(); + + void OpenParentFolder(); + void SetPathEditText(); + void OnCreateDir(); + void OnItemEnter(); + void FinishOnOK(); + + int GetRealItemIndex(int indexInListView) const + { + LPARAM param; + if (!_list.GetItemParam((unsigned)indexInListView, param)) + return (int)-1; + return (int)param; + } + +public: + + bool SaveMode; + bool FolderMode; + int FilterIndex; // [in / out] + CObjectVector Filters; + + UString FilePath; // [in / out] + UString Title; + + CBrowseDialog(): + #ifndef Z7_SFX + _showDots(false), + #endif + SaveMode(false) + , FolderMode(false) + , FilterIndex(-1) + {} + INT_PTR Create(HWND parent = NULL) { return CModalDialog::Create(IDD_BROWSE, parent); } + int CompareItems(LPARAM lParam1, LPARAM lParam2) const; +}; + + +bool CBrowseDialog::OnInit() +{ + #ifdef Z7_LANG + LangSetDlgItems(*this, NULL, 0); + #endif + if (!Title.IsEmpty()) + SetText(Title); + _list.Attach(GetItem(IDL_BROWSE)); + _filterCombo.Attach(GetItem(IDC_BROWSE_FILTER)); + _pathEdit.Attach(GetItem(IDE_BROWSE_PATH)); + + #ifndef UNDER_CE + _list.SetUnicodeFormat(); + #endif + + #ifndef Z7_SFX + CFmSettings st; + st.Load(); + if (st.SingleClick) + _list.SetExtendedListViewStyle(LVS_EX_ONECLICKACTIVATE | LVS_EX_TRACKSELECT); + _showDots = st.ShowDots; + #endif + + { + /* + Filters.Clear(); // for debug + if (Filters.IsEmpty() && !FolderMode) + { + CBrowseFilterInfo &f = Filters.AddNew(); + const UString mask("*.*"); + f.Masks.Add(mask); + // f.Description = "("; + f.Description += mask; + // f.Description += ")"; + } + */ + + FOR_VECTOR (i, Filters) + { + _filterCombo.AddString(Filters[i].Description); + } + + if (Filters.Size() <= 1) + { + if (FolderMode) + HideItem(IDC_BROWSE_FILTER); + else + EnableItem(IDC_BROWSE_FILTER, false); + } + + if (/* FilterIndex >= 0 && */ (unsigned)FilterIndex < Filters.Size()) + _filterCombo.SetCurSel(FilterIndex); + } + + _list.SetImageList(Shell_Get_SysImageList_smallIcons(true), LVSIL_SMALL); + _list.SetImageList(Shell_Get_SysImageList_smallIcons(false), LVSIL_NORMAL); + + _list.InsertColumn(0, LangString(IDS_PROP_NAME), 100); + _list.InsertColumn(1, LangString(IDS_PROP_MTIME), 100); + { + LV_COLUMNW column; + column.iSubItem = 2; + column.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM; + column.fmt = LVCFMT_RIGHT; + column.cx = 100; + const UString s = LangString(IDS_PROP_SIZE); + column.pszText = s.Ptr_non_const(); + _list.InsertColumn(2, &column); + } + + _list.InsertItem(0, L"12345678901234567" + #ifndef UNDER_CE + L"1234567890" + #endif + ); + _list.SetSubItem(0, 1, L"2009-09-09" + #ifndef UNDER_CE + L" 09:09" + #endif + ); + _list.SetSubItem(0, 2, L"9999 MB"); + for (int i = 0; i < 3; i++) + _list.SetColumnWidthAuto(i); + _list.DeleteAllItems(); + + _ascending = true; + _sortIndex = 0; + + NormalizeSize(); + + _topDirPrefix.Empty(); + { + unsigned rootSize = GetRootPrefixSize(FilePath); + #if defined(_WIN32) && !defined(UNDER_CE) + // We can go up from root folder to drives list + if (IsDrivePath(FilePath)) + rootSize = 0; + else if (IsSuperPath(FilePath)) + { + if (IsDrivePath(FilePath.Ptr(kSuperPathPrefixSize))) + rootSize = kSuperPathPrefixSize; + } + #endif + _topDirPrefix.SetFrom(FilePath, rootSize); + } + + UString name; + if (!GetParentPath(FilePath, DirPrefix, name)) + DirPrefix = _topDirPrefix; + + for (;;) + { + UString baseFolder = DirPrefix; + if (Reload(baseFolder, name) == S_OK) + break; + name.Empty(); + if (DirPrefix.IsEmpty()) + break; + UString parent, name2; + GetParentPath(DirPrefix, parent, name2); + DirPrefix = parent; + } + + if (name.IsEmpty()) + name = FilePath; + if (FolderMode) + NormalizeDirPathPrefix(name); + _pathEdit.SetText(name); + + #ifndef UNDER_CE + /* If we clear UISF_HIDEFOCUS, the focus rectangle in ListView will be visible, + even if we use mouse for pressing the button to open this dialog. */ + PostMsg(Z7_WIN_WM_UPDATEUISTATE, MAKEWPARAM(Z7_WIN_UIS_CLEAR, Z7_WIN_UISF_HIDEFOCUS)); + #endif + +#if 0 + { + const HWND hwndTool = GetItem(IDB_BROWSE_CREATE_DIR); + if (hwndTool) + { + // Create the tooltip: + const HWND hwndTip = CreateWindowEx(0, TOOLTIPS_CLASS, NULL, + WS_POPUP | TTS_ALWAYSTIP + // | TTS_BALLOON + , CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + *this, NULL, g_hInstance, NULL); + if (hwndTip) + { + // Associate the tooltip with the tool: + TOOLINFOW toolInfo; + memset(&toolInfo, 0, sizeof(toolInfo)); + toolInfo.cbSize = sizeof(toolInfo); + toolInfo.hwnd = *this; + toolInfo.uFlags = TTF_IDISHWND | TTF_SUBCLASS; + toolInfo.uId = (UINT_PTR)hwndTool; + UString s; +#ifdef Z7_LANG + LangString_OnlyFromLangFile(IDM_CREATE_FOLDER, s); + s.RemoveChar(L'&'); + if (s.IsEmpty()) +#endif + s = "Create Folder"; + toolInfo.lpszText = s.Ptr_non_const(); + SendMessage(hwndTip, TTM_ADDTOOLW, 0, (LPARAM)&toolInfo); + } + } + } +#endif + return CModalDialog::OnInit(); +} + +bool CBrowseDialog::OnSize(WPARAM /* wParam */, int xSize, int ySize) +{ + int mx, my; + { + RECT r; + GetClientRectOfItem(IDB_BROWSE_PARENT, r); + mx = r.left; + my = r.top; + } + InvalidateRect(NULL); + + int xLim = xSize - mx; + { + RECT r; + GetClientRectOfItem(IDT_BROWSE_FOLDER, r); + MoveItem(IDT_BROWSE_FOLDER, r.left, r.top, xLim - r.left, RECT_SIZE_Y(r)); + } + + int bx1, bx2, by; + GetItemSizes(IDCANCEL, bx1, by); + GetItemSizes(IDOK, bx2, by); + int y = ySize - my - by; + int x = xLim - bx1; + MoveItem(IDCANCEL, x, y, bx1, by); + MoveItem(IDOK, x - mx - bx2, y, bx2, by); + + // Y_Size of ComboBox is tricky. So we use Y_Size of _pathEdit instead + + int yPathSize; + { + RECT r; + GetClientRectOfItem(IDE_BROWSE_PATH, r); + yPathSize = RECT_SIZE_Y(r); + _pathEdit.Move(r.left, y - my - yPathSize - my - yPathSize, xLim - r.left, yPathSize); + } + + { + RECT r; + GetClientRectOfItem(IDC_BROWSE_FILTER, r); + _filterCombo.Move(r.left, y - my - yPathSize, xLim - r.left, RECT_SIZE_Y(r)); + } + + { + RECT r; + GetClientRectOfItem(IDL_BROWSE, r); + _list.Move(r.left, r.top, xLim - r.left, y - my - yPathSize - my - yPathSize - my - r.top); + } + + return false; +} + +bool CBrowseDialog::OnMessage(UINT message, WPARAM wParam, LPARAM lParam) +{ + if (message == k_Message_RefreshPathEdit) + { + SetPathEditText(); + return true; + } + return CModalDialog::OnMessage(message, wParam, lParam); +} + + +bool CBrowseDialog::OnCommand(unsigned code, unsigned itemID, LPARAM lParam) +{ + if (code == CBN_SELCHANGE) + { + switch (itemID) + { + case IDC_BROWSE_FILTER: + { + Reload(); + return true; + } + } + } + return CModalDialog::OnCommand(code, itemID, lParam); +} + + +bool CBrowseDialog::OnNotify(UINT /* controlID */, LPNMHDR header) +{ + if (header->hwndFrom != _list) + return false; + switch (header->code) + { + case LVN_ITEMACTIVATE: + if (g_LVN_ITEMACTIVATE_Support) + OnItemEnter(); + break; + case NM_DBLCLK: + case NM_RETURN: // probably it's unused + if (!g_LVN_ITEMACTIVATE_Support) + OnItemEnter(); + break; + case LVN_COLUMNCLICK: + { + const int index = LPNMLISTVIEW(header)->iSubItem; + if (index == _sortIndex) + _ascending = !_ascending; + else + { + _ascending = (index == 0); + _sortIndex = index; + } + Reload(); + return false; + } + case LVN_KEYDOWN: + { + bool boolResult = OnKeyDown(LPNMLVKEYDOWN(header)); + Post_RefreshPathEdit(); + return boolResult; + } + case NM_RCLICK: + case NM_CLICK: + case LVN_BEGINDRAG: + Post_RefreshPathEdit(); + break; + } + return false; +} + +bool CBrowseDialog::OnKeyDown(LPNMLVKEYDOWN keyDownInfo) +{ + const bool ctrl = IsKeyDown(VK_CONTROL); + + switch (keyDownInfo->wVKey) + { + case VK_BACK: + OpenParentFolder(); + return true; + case 'R': + if (ctrl) + { + Reload(); + return true; + } + return false; + case VK_F7: + OnCreateDir(); + return true; + } + return false; +} + + +bool CBrowseDialog::OnButtonClicked(unsigned buttonID, HWND buttonHWND) +{ + switch (buttonID) + { + case IDB_BROWSE_PARENT: OpenParentFolder(); break; + case IDB_BROWSE_CREATE_DIR: OnCreateDir(); break; + default: return CModalDialog::OnButtonClicked(buttonID, buttonHWND); + } + _list.SetFocus(); + return true; +} + +void CBrowseDialog::OnOK() +{ + /* When we press "Enter" in listview, Windows sends message to first Button. + We check that message was from ListView; */ + if (GetFocus() == _list) + { + OnItemEnter(); + return; + } + FinishOnOK(); +} + + +bool CBrowseDialog::GetParentPath(const UString &path, UString &parentPrefix, UString &name) +{ + parentPrefix.Empty(); + name.Empty(); + if (path.IsEmpty()) + return false; + if (_topDirPrefix == path) + return false; + UString s = path; + if (IS_PATH_SEPAR(s.Back())) + s.DeleteBack(); + if (s.IsEmpty()) + return false; + if (IS_PATH_SEPAR(s.Back())) + return false; + const unsigned pos1 = (unsigned)(s.ReverseFind_PathSepar() + 1); + parentPrefix.SetFrom(s, pos1); + name = s.Ptr(pos1); + return true; +} + +int CBrowseDialog::CompareItems(LPARAM lParam1, LPARAM lParam2) const +{ + if (lParam1 == lParam2) return 0; + if (lParam1 == kParentIndex) return -1; + if (lParam2 == kParentIndex) return 1; + + const CFileInfo &f1 = _files[(int)lParam1]; + const CFileInfo &f2 = _files[(int)lParam2]; + + const bool isDir2 = f2.IsDir(); + if (f1.IsDir()) + { + if (!isDir2) return -1; + } + else if (isDir2) return 1; + + int res = 0; + switch (_sortIndex) + { + case 0: res = CompareFileNames(fs2us(f1.Name), fs2us(f2.Name)); break; + case 1: res = CompareFileTime(&f1.MTime, &f2.MTime); break; + case 2: res = MyCompare(f1.Size, f2.Size); break; + } + return _ascending ? res: -res; +} + +static int CALLBACK CompareItems2(LPARAM lParam1, LPARAM lParam2, LPARAM lpData) +{ + return ((CBrowseDialog *)lpData)->CompareItems(lParam1, lParam2); +} + +wchar_t *Browse_ConvertSizeToString(UInt64 v, wchar_t *s); +wchar_t *Browse_ConvertSizeToString(UInt64 v, wchar_t *s) +{ + char c = 0; + if (v >= ((UInt64)10000 << 20)) { v >>= 30; c = 'G'; } + else if (v >= ((UInt64)10000 << 10)) { v >>= 20; c = 'M'; } + else if (v >= ((UInt64)10000 << 0)) { v >>= 10; c = 'K'; } + s = ConvertUInt64ToString(v, s); + if (c != 0) + { + *s++ = ' '; + *s++ = (wchar_t)c; + *s++ = 'B'; + *s = 0; + } + return s; +} + +// Reload changes DirPrefix. Don't send DirPrefix in pathPrefix parameter + +HRESULT CBrowseDialog::Reload(const UString &pathPrefix, const UString &selectedName) +{ + CObjectVector files; + + #ifndef UNDER_CE + bool isDrive = false; + if (pathPrefix.IsEmpty() || pathPrefix.IsEqualTo(kSuperPathPrefix)) + { + isDrive = true; + FStringVector drives; + if (!MyGetLogicalDriveStrings(drives)) + return GetLastError_noZero_HRESULT(); + FOR_VECTOR (i, drives) + { + const FString &d = drives[i]; + if (d.Len() < 2 || d.Back() != '\\') + return E_FAIL; + CFileInfo &fi = files.AddNew(); + fi.SetAsDir(); + fi.Name = d; + fi.Name.DeleteBack(); + } + } + else + #endif + { + const UStringVector *masks = NULL; + if (!Filters.IsEmpty() && _filterCombo.GetCount() > 0) + { + const int selected = _filterCombo.GetCurSel(); + // GetItemData_of_CurSel(); // we don't use data field + if (/* selected >= 0 && */ (unsigned)selected < Filters.Size()) + { + const UStringVector &m = Filters[selected].Masks; + if (m.Size() > 1 || (m.Size() == 1 + && !m[0].IsEqualTo("*.*") + && !m[0].IsEqualTo("*"))) + masks = &m; + } + } + CEnumerator enumerator; + enumerator.SetDirPrefix(us2fs(pathPrefix)); + CFileInfo fi; + for (;;) + { + bool found; + if (!enumerator.Next(fi, found)) + return GetLastError_noZero_HRESULT(); + if (!found) + break; + if (!fi.IsDir()) + { + if (FolderMode) + continue; + if (masks) + { + unsigned i; + const unsigned numMasks = masks->Size(); + for (i = 0; i < numMasks; i++) + if (DoesWildcardMatchName((*masks)[i], fs2us(fi.Name))) + break; + if (i == numMasks) + continue; + } + } + files.Add(fi); + } + } + + DirPrefix = pathPrefix; + + _files = files; + + SetItemText(IDT_BROWSE_FOLDER, DirPrefix); + + _list.SetRedraw(false); + _list.DeleteAllItems(); + + LVITEMW item; + + unsigned index = 0; + int cursorIndex = -1; + + #ifndef Z7_SFX + if (_showDots && _topDirPrefix != DirPrefix) + { + item.iItem = (int)index; + const UString itemName (".."); + if (selectedName.IsEmpty()) + cursorIndex = (int)index; + item.mask = LVIF_TEXT | LVIF_PARAM | LVIF_IMAGE; + unsigned subItem = 0; + item.iSubItem = (int)(subItem++); + item.lParam = kParentIndex; + item.pszText = itemName.Ptr_non_const(); + item.iImage = _extToIconMap.GetIconIndex(FILE_ATTRIBUTE_DIRECTORY, DirPrefix); + if (item.iImage < 0) + item.iImage = 0; + _list.InsertItem(&item); + _list.SetSubItem(index, subItem++, L""); + _list.SetSubItem(index, subItem++, L""); + index++; + } + #endif + + for (unsigned i = 0; i < _files.Size(); i++, index++) + { + item.iItem = (int)index; + const CFileInfo &fi = _files[i]; + const UString name = fs2us(fi.Name); + if (!selectedName.IsEmpty() && CompareFileNames(name, selectedName) == 0) + cursorIndex = (int)index; + item.mask = LVIF_TEXT | LVIF_PARAM | LVIF_IMAGE; + unsigned subItem = 0; + item.iSubItem = (int)(subItem++); + item.lParam = (LPARAM)i; + item.pszText = name.Ptr_non_const(); + + const UString fullPath = DirPrefix + name; + #ifndef UNDER_CE + if (isDrive) + { + item.iImage = Shell_GetFileInfo_SysIconIndex_for_Path( + fi.Name + FCHAR_PATH_SEPARATOR, + FILE_ATTRIBUTE_DIRECTORY); + } + else + #endif + item.iImage = _extToIconMap.GetIconIndex(fi.Attrib, fullPath); + if (item.iImage < 0) + item.iImage = 0; + _list.InsertItem(&item); + wchar_t s[64]; + { + s[0] = 0; + if (!FILETIME_IsZero(fi.MTime)) + ConvertUtcFileTimeToString(fi.MTime, s, + #ifndef UNDER_CE + kTimestampPrintLevel_MIN + #else + kTimestampPrintLevel_DAY + #endif + ); + _list.SetSubItem(index, subItem++, s); + } + { + s[0] = 0; + if (!fi.IsDir()) + Browse_ConvertSizeToString(fi.Size, s); + _list.SetSubItem(index, subItem++, s); + } + } + + if (_list.GetItemCount() > 0 && cursorIndex >= 0) + _list.SetItemState_FocusedSelected(cursorIndex); + _list.SortItems(CompareItems2, (LPARAM)this); + if (_list.GetItemCount() > 0 && cursorIndex < 0) + _list.SetItemState(0, LVIS_FOCUSED, LVIS_FOCUSED); + _list.EnsureVisible(_list.GetFocusedItem(), false); + _list.SetRedraw(true); + _list.InvalidateRect(NULL, true); + return S_OK; +} + +HRESULT CBrowseDialog::Reload() +{ + UString selected; + const int index = _list.GetNextSelectedItem(-1); + if (index >= 0) + { + const int fileIndex = GetRealItemIndex(index); + if (fileIndex != kParentIndex) + selected = fs2us(_files[fileIndex].Name); + } + const UString dirPathTemp = DirPrefix; + return Reload(dirPathTemp, selected); +} + +void CBrowseDialog::OpenParentFolder() +{ + UString parent, selected; + if (GetParentPath(DirPrefix, parent, selected)) + { + Reload(parent, selected); + SetPathEditText(); + } +} + +void CBrowseDialog::SetPathEditText() +{ + const int index = _list.GetNextSelectedItem(-1); + if (index < 0) + { + if (FolderMode) + _pathEdit.SetText(DirPrefix); + return; + } + const int fileIndex = GetRealItemIndex(index); + if (fileIndex == kParentIndex) + { + if (FolderMode) + _pathEdit.SetText(L".." WSTRING_PATH_SEPARATOR); + return; + } + const CFileInfo &file = _files[fileIndex]; + if (file.IsDir()) + { + if (!FolderMode) + return; + _pathEdit.SetText(fs2us(file.Name) + WCHAR_PATH_SEPARATOR); + } + else + _pathEdit.SetText(fs2us(file.Name)); +} + +void CBrowseDialog::OnCreateDir() +{ + UString name; + { + UString enteredName; + Dlg_CreateFolder((HWND)*this, enteredName); + if (enteredName.IsEmpty()) + return; + if (!CorrectFsPath(DirPrefix, enteredName, name)) + { + MessageBox_HResError((HWND)*this, ERROR_INVALID_NAME, name); + return; + } + } + if (name.IsEmpty()) + return; + + FString destPath; + if (GetFullPath(us2fs(DirPrefix), us2fs(name), destPath)) + { + if (!NDir::CreateComplexDir(destPath)) + { + MessageBox_HResError((HWND)*this, GetLastError_noZero_HRESULT(), fs2us(destPath)); + } + else + { + UString tempPath = DirPrefix; + Reload(tempPath, name); + SetPathEditText(); + } + _list.SetFocus(); + } +} + +void CBrowseDialog::OnItemEnter() +{ + const int index = _list.GetNextSelectedItem(-1); + if (index < 0) + return; + const int fileIndex = GetRealItemIndex(index); + if (fileIndex == kParentIndex) + OpenParentFolder(); + else + { + const CFileInfo &file = _files[fileIndex]; + if (!file.IsDir()) + { + if (!FolderMode) + FinishOnOK(); + /* + MessageBox_Error_Global(*this, FolderMode ? + L"You must select some folder": + L"You must select some file"); + */ + return; + } + UString s = DirPrefix; + s += fs2us(file.Name); + s.Add_PathSepar(); + const HRESULT res = Reload(s, UString()); + if (res != S_OK) + MessageBox_HResError(*this, res, s); + SetPathEditText(); + } +} + +void CBrowseDialog::FinishOnOK() +{ + UString s; + _pathEdit.GetText(s); + FString destPath; + if (!GetFullPath(us2fs(DirPrefix), us2fs(s), destPath)) + { + MessageBox_HResError((HWND)*this, ERROR_INVALID_NAME, s); + return; + } + FilePath = fs2us(destPath); + if (FolderMode) + NormalizeDirPathPrefix(FilePath); + FilterIndex = _filterCombo.GetCurSel(); + End(IDOK); +} + +#endif // USE_MY_BROWSE_DIALOG + + + +bool MyBrowseForFolder(HWND owner, LPCWSTR title, LPCWSTR path, UString &resultPath) +{ + resultPath.Empty(); + + #ifndef UNDER_CE + +#ifdef USE_MY_BROWSE_DIALOG + if (!IsSuperOrDevicePath(path)) + if (MyStringLen(path) < MAX_PATH) +#endif + return NShell::BrowseForFolder(owner, title, path, resultPath); + + #endif // UNDER_CE + + #ifdef USE_MY_BROWSE_DIALOG + + CBrowseDialog dialog; + dialog.FolderMode = true; + if (title) + dialog.Title = title; + if (path) + dialog.FilePath = path; + if (dialog.Create(owner) != IDOK) + return false; + resultPath = dialog.FilePath; + return true; + + #endif +} + + +// LPCWSTR filterDescription, LPCWSTR filter, + +bool CBrowseInfo::BrowseForFile(const CObjectVector &filters) +{ +#ifndef UNDER_CE +#ifdef USE_MY_BROWSE_DIALOG + /* win10: + GetOpenFileName() for FilePath doesn't support super prefix "\\\\?\\" + GetOpenFileName() for FilePath doesn't support long path + */ + if (!IsSuperOrDevicePath(FilePath)) + // if (filters.Size() > 100) // for debug +#endif + { + const UString filePath_Store = FilePath; + UString dirPrefix; + { + FString prefix, name; + if (NDir::GetFullPathAndSplit(us2fs(FilePath), prefix, name)) + { + dirPrefix = fs2us(prefix); + FilePath = fs2us(name); + } + } + UStringVector filters2; + FOR_VECTOR (i, filters) + { + const CBrowseFilterInfo &fi = filters[i]; + filters2.Add(fi.Description); + UString s; + FOR_VECTOR (k, fi.Masks) + { + if (k != 0) + s += ";"; + s += fi.Masks[k]; + } + filters2.Add(s); + } + if (CommonDlg_BrowseForFile(!dirPrefix.IsEmpty() ? dirPrefix.Ptr(): NULL, filters2)) + return true; + FilePath = filePath_Store; + + #ifdef UNDER_CE + return false; + #else + // maybe we must use GetLastError in WinCE. + const DWORD errorCode = CommDlgExtendedError(); + #ifdef USE_MY_BROWSE_DIALOG + // FNERR_INVALIDFILENAME is expected error, if long path was used + if (errorCode != FNERR_INVALIDFILENAME + || FilePath.Len() < MAX_PATH) + #endif + { + if (errorCode == 0) // cancel or close on dialog + return false; + const char *message = NULL; + if (errorCode == FNERR_INVALIDFILENAME) + message = "Invalid file name"; + UString s ("Open Dialog Error:"); + s.Add_LF(); + if (message) + s += message; + else + { + char temp[16]; + ConvertUInt32ToHex8Digits(errorCode, temp); + s += "Error #"; + s += temp; + } + s.Add_LF(); + s += FilePath; + MessageBox_Error_Global(hwndOwner, s); + } + #endif // UNDER_CE + } + +#endif // UNDER_CE + +#ifdef USE_MY_BROWSE_DIALOG + + CBrowseDialog dialog; + + dialog.FolderMode = false; + dialog.SaveMode = SaveMode; + dialog.FilterIndex = FilterIndex; + dialog.Filters = filters; + + if (lpstrTitle) + dialog.Title = lpstrTitle; + dialog.FilePath = FilePath; + if (dialog.Create(hwndOwner) != IDOK) + return false; + FilePath = dialog.FilePath; + FilterIndex = dialog.FilterIndex; +#endif + + return true; +} + + +#ifdef _WIN32 + +static void RemoveDotsAndSpaces(UString &path) +{ + while (!path.IsEmpty()) + { + wchar_t c = path.Back(); + if (c != ' ' && c != '.') + return; + path.DeleteBack(); + } +} + + +bool CorrectFsPath(const UString &relBase, const UString &path2, UString &result) +{ + result.Empty(); + + UString path = path2; + #ifdef _WIN32 + path.Replace(L'/', WCHAR_PATH_SEPARATOR); + #endif + unsigned start = 0; + UString base; + + if (IsAbsolutePath(path)) + { + #if defined(_WIN32) && !defined(UNDER_CE) + if (IsSuperOrDevicePath(path)) + { + result = path; + return true; + } + #endif + start = GetRootPrefixSize(path); + } + else + { + #if defined(_WIN32) && !defined(UNDER_CE) + if (IsSuperOrDevicePath(relBase)) + { + result = path; + return true; + } + #endif + base = relBase; + } + + /* We can't use backward, since we must change only disk paths */ + /* + for (;;) + { + if (path.Len() <= start) + break; + if (DoesFileOrDirExist(us2fs(path))) + break; + if (path.Back() == WCHAR_PATH_SEPARATOR) + { + path.DeleteBack(); + result.Insert(0, WCHAR_PATH_SEPARATOR); + } + int pos = path.ReverseFind(WCHAR_PATH_SEPARATOR) + 1; + UString cur = path.Ptr(pos); + RemoveDotsAndSpaces(cur); + result.Insert(0, cur); + path.DeleteFrom(pos); + } + result.Insert(0, path); + return true; + */ + + result += path.Left(start); + bool checkExist = true; + UString cur; + + for (;;) + { + if (start == path.Len()) + break; + const int slashPos = path.Find(WCHAR_PATH_SEPARATOR, start); + cur.SetFrom(path.Ptr(start), (slashPos < 0 ? path.Len() : (unsigned)slashPos) - start); + if (checkExist) + { + CFileInfo fi; + if (fi.Find(us2fs(base + result + cur))) + { + if (!fi.IsDir()) + { + result = path; + break; + } + } + else + checkExist = false; + } + if (!checkExist) + RemoveDotsAndSpaces(cur); + result += cur; + if (slashPos < 0) + break; + start = (unsigned)(slashPos + 1); + result.Add_PathSepar(); + } + + return true; +} + +#else + +bool CorrectFsPath(const UString & /* relBase */, const UString &path, UString &result) +{ + result = path; + return true; +} + +#endif + +bool Dlg_CreateFolder(HWND wnd, UString &destName) +{ + destName.Empty(); + CComboDialog dlg; + LangString(IDS_CREATE_FOLDER, dlg.Title); + LangString(IDS_CREATE_FOLDER_NAME, dlg.Static); + LangString(IDS_CREATE_FOLDER_DEFAULT_NAME, dlg.Value); + if (dlg.Create(wnd) != IDOK) + return false; + destName = dlg.Value; + return true; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/BrowseDialog.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/BrowseDialog.h new file mode 100644 index 00000000..2ad8d544 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/BrowseDialog.h @@ -0,0 +1,32 @@ +// BrowseDialog.h + +#ifndef ZIP7_INC_BROWSE_DIALOG_H +#define ZIP7_INC_BROWSE_DIALOG_H + +#include "../../../Windows/CommonDialog.h" + +bool MyBrowseForFolder(HWND owner, LPCWSTR title, LPCWSTR path, UString &resultPath); + +struct CBrowseFilterInfo +{ + UStringVector Masks; + UString Description; +}; + +struct CBrowseInfo: public NWindows::CCommonDialogInfo +{ + bool BrowseForFile(const CObjectVector &filters); +}; + + +/* CorrectFsPath removes undesirable characters in names (dots and spaces at the end of file) + But it doesn't change "bad" name in any of the following cases: + - path is Super Path (with \\?\ prefix) + - path is relative and relBase is Super Path + - there is file or dir in filesystem with specified "bad" name */ + +bool CorrectFsPath(const UString &relBase, const UString &path, UString &result); + +bool Dlg_CreateFolder(HWND wnd, UString &destName); + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/BrowseDialog.rc b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/BrowseDialog.rc new file mode 100644 index 00000000..04a6ad61 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/BrowseDialog.rc @@ -0,0 +1,25 @@ +#include "BrowseDialogRes.h" +#include "../../GuiCommon.rc" + +#define xc 256 +#define yc 320 + +#define k_BROWSE_y_CtrlSize 14 + +#define k_BROWSE_y_List 24 + +IDD_BROWSE DIALOG 0, 0, xs, ys MY_MODAL_RESIZE_DIALOG_STYLE MY_FONT +CAPTION "7-Zip: Browse" +{ + EDITTEXT IDE_BROWSE_PATH, m, by - m - k_BROWSE_y_CtrlSize - k_BROWSE_y_CtrlSize - m, xc, k_BROWSE_y_CtrlSize, ES_AUTOHSCROLL + COMBOBOX IDC_BROWSE_FILTER, m, by - m - k_BROWSE_y_CtrlSize, xc, 30, MY_COMBO + + PUSHBUTTON "OK", IDOK, bx2, by, bxs, bys + PUSHBUTTON "Cancel", IDCANCEL, bx1, by, bxs, bys + PUSHBUTTON "<--", IDB_BROWSE_PARENT, m, m, 24, bys + PUSHBUTTON "+", IDB_BROWSE_CREATE_DIR, m + 32, m, 24, bys + LTEXT "", IDT_BROWSE_FOLDER, m + 64, m + 3, xc - 20, 8 + CONTROL "List1", IDL_BROWSE, "SysListView32", + LVS_REPORT | LVS_SHOWSELALWAYS | LVS_SHAREIMAGELISTS | LVS_SINGLESEL | WS_BORDER | WS_TABSTOP, + m, m + k_BROWSE_y_List, xc, yc - bys - m - k_BROWSE_y_List - k_BROWSE_y_CtrlSize - m - k_BROWSE_y_CtrlSize - m +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/BrowseDialog2.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/BrowseDialog2.cpp new file mode 100644 index 00000000..f8bfcb5f --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/BrowseDialog2.cpp @@ -0,0 +1,1873 @@ +// BrowseDialog2.cpp + +#include "StdAfx.h" + +#ifdef UNDER_CE +#include +#endif + +#include + +#include "../../../Common/IntToString.h" +#include "../../../Common/MyCom.h" +#include "../../../Common/StringConvert.h" +#include "../../../Common/Wildcard.h" + +#include "../../../Windows/DLL.h" +#include "../../../Windows/FileFind.h" +#include "../../../Windows/FileDir.h" +#include "../../../Windows/FileName.h" +#include "../../../Windows/Menu.h" +#include "../../../Windows/ProcessUtils.h" +#include "../../../Windows/PropVariantConv.h" +#include "../../../Windows/Shell.h" +#include "../../../Windows/Control/ComboBox.h" +#include "../../../Windows/Control/Dialog.h" +#include "../../../Windows/Control/Edit.h" +#include "../../../Windows/Control/ListView.h" + +#include "../Explorer/MyMessages.h" + +#ifndef Z7_NO_REGISTRY +#include "HelpUtils.h" +#endif + +#include "../Common/PropIDUtils.h" + +#include "PropertyNameRes.h" +#include "RegistryUtils.h" +#include "SysIconUtils.h" +#include "FormatUtils.h" +#include "LangUtils.h" + +#include "resource.h" +#include "BrowseDialog2Res.h" +#include "BrowseDialog2.h" + +using namespace NWindows; +using namespace NFile; +using namespace NName; +using namespace NFind; + +#ifndef _UNICODE +extern bool g_IsNT; +#endif + +extern bool g_LVN_ITEMACTIVATE_Support; + +static const int kParentIndex = -1; +// static const UINT k_Message_RefreshPathEdit = WM_APP + 1; + + +static const wchar_t * const k_Message_Link_operation_was_Blocked = + L"link openning was blocked by 7-Zip"; + +extern UString HResultToMessage(HRESULT errorCode); + +static void MessageBox_HResError(HWND wnd, HRESULT errorCode, const wchar_t *name) +{ + UString s = HResultToMessage(errorCode); + if (name) + { + s.Add_LF(); + s += name; + } + ShowErrorMessage(wnd, s); +} + +static void MessageBox_LastError_path(HWND wnd, const FString &path) +{ + const HRESULT hres = GetLastError_noZero_HRESULT(); + MessageBox_HResError(wnd, hres, fs2us(path)); +} + + +static const UInt32 k_EnumerateDirsLimit = 200; +static const UInt32 k_EnumerateFilesLimit = 2000; + +struct CBrowseItem +{ + unsigned MainFileIndex; + int SubFileIndex; + bool WasInterrupted; + UInt32 NumFiles; + UInt32 NumDirs; + UInt32 NumRootItems; + UInt64 Size; + + void Construct() + { + // MainFileIndex = 0; + SubFileIndex = -1; + WasInterrupted = false; + NumFiles = 0; + NumDirs = 0; + NumRootItems = 0; + Size = 0; + } +}; + + +struct CBrowseEnumerator +{ + FString Path; // folder path without slash at the end + CFileInfo fi; // temp + CFileInfo fi_SubFile; + CBrowseItem bi; + + CBrowseEnumerator() + { + bi.Construct(); + } + void Enumerate(unsigned level); + bool NeedInterrupt() const + { + return bi.NumFiles >= k_EnumerateFilesLimit + || bi.NumDirs >= k_EnumerateDirsLimit; + } +}; + +void CBrowseEnumerator::Enumerate(unsigned level) +{ + Path.Add_PathSepar(); + const unsigned len = Path.Len(); + CObjectVector names; + { + CEnumerator enumerator; + enumerator.SetDirPrefix(Path); + while (enumerator.Next(fi)) + { + if (level == 0) + { + if (bi.NumRootItems == 0) + fi_SubFile = fi; + bi.NumRootItems++; + } + + if (fi.IsDir()) + { + bi.NumDirs++; + if (!fi.HasReparsePoint()) + names.Add(fi.Name); + } + else + { + bi.NumFiles++; + bi.Size += fi.Size; + } + + if (level != 0 || bi.NumRootItems > 1) + if (NeedInterrupt()) + { + bi.WasInterrupted = true; + return; + } + } + } + + FOR_VECTOR (i, names) + { + if (NeedInterrupt()) + { + bi.WasInterrupted = true; + return; + } + Path.DeleteFrom(len); + Path += names[i]; + Enumerate(level + 1); + } +} + + + + +class CBrowseDialog2: public NControl::CModalDialog +{ + CRecordVector _items; + CObjectVector _files; + + NControl::CListView _list; + // NControl::CEdit _pathEdit; + NControl::CComboBox _filterCombo; + + CExtToIconMap _extToIconMap; + int _sortIndex; + int _columnIndex_fileNameInDir; + int _columnIndex_NumFiles; + int _columnIndex_NumDirs; + bool _ascending; + #ifndef Z7_SFX + bool _showDots; + #endif + UString _topDirPrefix; // we don't open parent of that folder + UString DirPrefix; + + virtual bool OnInit() Z7_override; + virtual bool OnSize(WPARAM wParam, int xSize, int ySize) Z7_override; + virtual bool OnMessage(UINT message, WPARAM wParam, LPARAM lParam) Z7_override; + virtual bool OnNotify(UINT controlID, LPNMHDR header) Z7_override; + // virtual bool OnCommand(unsigned code, unsigned itemID, LPARAM lParam) Z7_override; + virtual bool OnButtonClicked(unsigned buttonID, HWND buttonHWND) Z7_override; + virtual void OnOK() Z7_override; + + bool OnKeyDown(LPNMLVKEYDOWN keyDownInfo); + + // void Post_RefreshPathEdit() { PostMsg(k_Message_RefreshPathEdit); } + bool GetParentPath(const UString &path, UString &parentPrefix, UString &name); + // Reload changes DirPrefix. Don't send DirPrefix in pathPrefix parameter + HRESULT Reload(const UString &pathPrefix, const UStringVector &selectedNames, const UString &focusedName); + HRESULT Reload(const UString &pathPrefix, const UString &selectedNames); + HRESULT Reload(); + + void ChangeSorting_and_Reload(int columnIndex); + + const CFileInfo & Get_MainFileInfo_for_realIndex(unsigned realIndex) const + { + return _files[_items[realIndex].MainFileIndex]; + } + + const FString & Get_MainFileName_for_realIndex(unsigned realIndex) const + { + return Get_MainFileInfo_for_realIndex(realIndex).Name; + } + + void Reload_WithErrorMessage(); + void OpenParentFolder(); + // void SetPathEditText(); + void PrintFileProps(UString &s, const CFileInfo &file); + void Show_FileProps_Window(const CFileInfo &file); + void OnItemEnter(); + // void FinishOnOK(); + void OnDelete(/* bool toRecycleBin */); + virtual void OnHelp() Z7_override; + bool OnContextMenu(HANDLE windowHandle, int xPos, int yPos); + + int GetRealItemIndex(int indexInListView) const + { + LPARAM param; + if (!_list.GetItemParam((unsigned)indexInListView, param)) + return (int)-1; + return (int)param; + } + + void GetSelected_RealIndexes(CUIntVector &vector); + +public: + // bool TempMode; + // bool Show_Non7zDirs_InTemp; + // int FilterIndex; // [in / out] + // CObjectVector Filters; + + UString TempFolderPath; // with slash + UString Title; + + bool IsExactTempFolder(const UString &pathPrefix) const + { + return CompareFileNames(pathPrefix, TempFolderPath) == 0; + } + + CBrowseDialog2(): + #ifndef Z7_SFX + _showDots(false) + #endif + // , TempMode(false) + // Show_Non7zDirs_InTemp(false), + // FilterIndex(-1) + {} + INT_PTR Create(HWND parent = NULL) { return CModalDialog::Create(IDD_BROWSE2, parent); } + int CompareItems(LPARAM lParam1, LPARAM lParam2) const; +}; + + +#ifdef Z7_LANG +static const UInt32 kLangIDs[] = +{ + IDS_BUTTON_DELETE, + IDM_VIEW_REFRESH +}; +#endif + +bool CBrowseDialog2::OnInit() +{ + #ifdef Z7_LANG + LangSetDlgItems(*this, kLangIDs, Z7_ARRAY_SIZE(kLangIDs)); + #endif + if (!Title.IsEmpty()) + SetText(Title); + + _list.Attach(GetItem(IDL_BROWSE2)); + _filterCombo.Attach(GetItem(IDC_BROWSE2_FILTER)); + + _ascending = true; + _sortIndex = 0; + _columnIndex_fileNameInDir = -1; + _columnIndex_NumFiles = -1; + _columnIndex_NumDirs = -1; + // _pathEdit.Attach(GetItem(IDE_BROWSE_PATH)); + + #ifndef UNDER_CE + _list.SetUnicodeFormat(); + #endif + + #ifndef Z7_SFX + { + CFmSettings st; + st.Load(); + + DWORD extendedStyle = 0; + if (st.FullRow) + extendedStyle |= LVS_EX_FULLROWSELECT; + if (st.ShowGrid) + extendedStyle |= LVS_EX_GRIDLINES; + if (st.SingleClick) + { + extendedStyle |= LVS_EX_ONECLICKACTIVATE | LVS_EX_TRACKSELECT; + /* + if (ReadUnderline()) + extendedStyle |= LVS_EX_UNDERLINEHOT; + */ + } + if (extendedStyle) + _list.SetExtendedListViewStyle(extendedStyle); + _showDots = st.ShowDots; + } + #endif + + { + /* + Filters.Clear(); // for debug + if (Filters.IsEmpty() && !FolderMode) + { + CBrowseFilterInfo &f = Filters.AddNew(); + const UString mask("*.*"); + f.Masks.Add(mask); + // f.Description = "("; + f.Description += mask; + // f.Description += ")"; + } + */ + _filterCombo.AddString(L"7-Zip temp files (7z*)"); + _filterCombo.SetCurSel(0); + EnableItem(IDC_BROWSE2_FILTER, false); +#if 0 + FOR_VECTOR (i, Filters) + { + _filterCombo.AddString(Filters[i].Description); + } + if (Filters.Size() <= 1) + { + EnableItem(IDC_BROWSE_FILTER, false); + } + if (/* FilterIndex >= 0 && */ (unsigned)FilterIndex < Filters.Size()) + _filterCombo.SetCurSel(FilterIndex); +#endif + } + + _list.SetImageList(Shell_Get_SysImageList_smallIcons(true), LVSIL_SMALL); + _list.SetImageList(Shell_Get_SysImageList_smallIcons(false), LVSIL_NORMAL); + + unsigned columnIndex = 0; + _list.InsertColumn(columnIndex++, LangString(IDS_PROP_NAME), 100); + _list.InsertColumn(columnIndex++, LangString(IDS_PROP_MTIME), 100); + { + LV_COLUMNW column; + column.iSubItem = (int)columnIndex; + column.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM; + column.fmt = LVCFMT_RIGHT; + column.cx = 100; + UString s = LangString(IDS_PROP_SIZE); + column.pszText = s.Ptr_non_const(); + _list.InsertColumn(columnIndex++, &column); + + // if (TempMode) + { + _columnIndex_NumFiles = (int)columnIndex; + s = LangString(IDS_PROP_FILES); + column.pszText = s.Ptr_non_const(); + _list.InsertColumn(columnIndex++, &column); + + _columnIndex_NumDirs = (int)columnIndex; + s = LangString(IDS_PROP_FOLDERS); + column.pszText = s.Ptr_non_const(); + _list.InsertColumn(columnIndex++, &column); + + _columnIndex_fileNameInDir = (int)columnIndex; + s = LangString(IDS_PROP_NAME); + s += "-2"; + _list.InsertColumn(columnIndex++, s, 100); + } + } + + _list.InsertItem(0, L"12345678901234567" + #ifndef UNDER_CE + L"1234567890" + #endif + ); + _list.SetSubItem(0, 1, L"2009-09-09" + #ifndef UNDER_CE + L" 09:09:09" + #endif + ); + _list.SetSubItem(0, 2, L"99999 MB+"); + + if (_columnIndex_NumFiles >= 0) + _list.SetSubItem(0, (unsigned)_columnIndex_NumFiles, L"123456789+"); + if (_columnIndex_NumDirs >= 0) + _list.SetSubItem(0, (unsigned)_columnIndex_NumDirs, L"123456789+"); + if (_columnIndex_fileNameInDir >= 0) + _list.SetSubItem(0, (unsigned)_columnIndex_fileNameInDir, L"12345678901234567890"); + + for (unsigned i = 0; i < columnIndex; i++) + _list.SetColumnWidthAuto((int)i); + _list.DeleteAllItems(); + + // if (TempMode) + { + _sortIndex = 1; // for MTime column + // _ascending = false; + } + + + NormalizeSize(); + + _topDirPrefix.Empty(); + { + unsigned rootSize = GetRootPrefixSize(TempFolderPath); + #if defined(_WIN32) && !defined(UNDER_CE) + // We can go up from root folder to drives list + if (IsDrivePath(TempFolderPath)) + rootSize = 0; + else if (IsSuperPath(TempFolderPath)) + { + if (IsDrivePath(TempFolderPath.Ptr(kSuperPathPrefixSize))) + rootSize = kSuperPathPrefixSize; + } + #endif + _topDirPrefix.SetFrom(TempFolderPath, rootSize); + } + + if (Reload(TempFolderPath, UString()) != S_OK) + { + // return false; + } +/* + UString name; + DirPrefix = TempFolderPath; + for (;;) + { + UString baseFolder = DirPrefix; + if (Reload(baseFolder, name) == S_OK) + break; + name.Empty(); + if (DirPrefix.IsEmpty()) + break; + UString parent, name2; + GetParentPath(DirPrefix, parent, name2); + DirPrefix = parent; + } +*/ + + #ifndef UNDER_CE + /* If we clear UISF_HIDEFOCUS, the focus rectangle in ListView will be visible, + even if we use mouse for pressing the button to open this dialog. */ + PostMsg(Z7_WIN_WM_UPDATEUISTATE, MAKEWPARAM(Z7_WIN_UIS_CLEAR, Z7_WIN_UISF_HIDEFOCUS)); + #endif + + /* + */ + + return CModalDialog::OnInit(); +} + + +bool CBrowseDialog2::OnSize(WPARAM /* wParam */, int xSize, int ySize) +{ + int mx, my; + { + RECT r; + GetClientRectOfItem(IDS_BUTTON_DELETE, r); + mx = r.left; + my = r.top; + } + InvalidateRect(NULL); + + const int xLim = xSize - mx; + { + RECT r; + GetClientRectOfItem(IDT_BROWSE2_FOLDER, r); + MoveItem(IDT_BROWSE2_FOLDER, r.left, r.top, xLim - r.left, RECT_SIZE_Y(r)); + } + + int bx1, bx2, by; + GetItemSizes(IDCLOSE, bx1, by); + GetItemSizes(IDHELP, bx2, by); + const int y = ySize - my - by; + const int x = xLim - bx1; + MoveItem(IDCLOSE, x - mx - bx2, y, bx1, by); + MoveItem(IDHELP, x, y, bx2, by); + /* + int yPathSize; + { + RECT r; + GetClientRectOfItem(IDE_BROWSE_PATH, r); + yPathSize = RECT_SIZE_Y(r); + _pathEdit.Move(r.left, y - my - yPathSize - my - yPathSize, xLim - r.left, yPathSize); + } + */ + // Y_Size of ComboBox is tricky. Can we use it? + int yFilterSize; + { + RECT r; + GetClientRectOfItem(IDC_BROWSE2_FILTER, r); + yFilterSize = RECT_SIZE_Y(r); + _filterCombo.Move(r.left, y - my - yFilterSize, xLim - r.left, yFilterSize); + } + { + RECT r; + GetClientRectOfItem(IDL_BROWSE2, r); + _list.Move(r.left, r.top, xLim - r.left, y - my - yFilterSize - my - r.top); + } + return false; +} + + +bool CBrowseDialog2::OnMessage(UINT message, WPARAM wParam, LPARAM lParam) +{ + /* + if (message == k_Message_RefreshPathEdit) + { + // SetPathEditText(); + return true; + } + */ + if (message == WM_CONTEXTMENU) + { + if (OnContextMenu((HANDLE)wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))) + return true; + } + return CModalDialog::OnMessage(message, wParam, lParam); +} + + +/* +bool CBrowseDialog2::OnCommand(unsigned code, unsigned itemID, LPARAM lParam) +{ + if (code == CBN_SELCHANGE) + { + switch (itemID) + { + case IDC_BROWSE2_FILTER: + { + Reload(); + return true; + } + } + } + return CModalDialog::OnCommand(code, itemID, lParam); +} +*/ + +bool CBrowseDialog2::OnNotify(UINT /* controlID */, LPNMHDR header) +{ + if (header->hwndFrom != _list) + { + if (::GetParent(header->hwndFrom) == _list) + { + // NMHDR:code is UINT + // NM_RCLICK is unsigned in windows sdk + // NM_RCLICK is int in MinGW + if (header->code == (UINT)NM_RCLICK) + { +#ifdef UNDER_CE +#define MY_NMLISTVIEW_NMITEMACTIVATE NMLISTVIEW +#else +#define MY_NMLISTVIEW_NMITEMACTIVATE NMITEMACTIVATE +#endif + MY_NMLISTVIEW_NMITEMACTIVATE *itemActivate = (MY_NMLISTVIEW_NMITEMACTIVATE *)header; + if (itemActivate->hdr.hwndFrom == HWND(_list)) + return false; + /* + POINT point; + ::GetCursorPos(&point); + ShowColumnsContextMenu(point.x, point.y); + */ + // we want to disable menu for columns. + // to return the value from a dialog procedure we must + // call SetMsgResult(val) and return true; + // NM_RCLICK : Return nonzero to not allow the default processing + SetMsgResult(TRUE); // do not allow default processing + return true; + } + } + return false; + } + + switch (header->code) + { + case LVN_ITEMACTIVATE: + if (g_LVN_ITEMACTIVATE_Support) + OnItemEnter(); + break; + case NM_DBLCLK: + case NM_RETURN: // probably it's unused + if (!g_LVN_ITEMACTIVATE_Support) + OnItemEnter(); + break; + case LVN_COLUMNCLICK: + { + const int index = LPNMLISTVIEW(header)->iSubItem; + ChangeSorting_and_Reload(index); + return false; + } + case LVN_KEYDOWN: + { + bool boolResult = OnKeyDown(LPNMLVKEYDOWN(header)); + // Post_RefreshPathEdit(); + return boolResult; + } + /* + case NM_RCLICK: + case NM_CLICK: + case LVN_BEGINDRAG: + Post_RefreshPathEdit(); + break; + */ + } + + return false; +} + +bool CBrowseDialog2::OnKeyDown(LPNMLVKEYDOWN keyDownInfo) +{ + const bool ctrl = IsKeyDown(VK_CONTROL); + // const bool alt = IsKeyDown(VK_MENU); + // const bool leftCtrl = IsKeyDown(VK_LCONTROL); + // const bool rightCtrl = IsKeyDown(VK_RCONTROL); + // const bool shift = IsKeyDown(VK_SHIFT); + + switch (keyDownInfo->wVKey) + { + case VK_BACK: + OpenParentFolder(); + return true; + case 'R': + if (ctrl) + { + Reload_WithErrorMessage(); + return true; + } + return false; + case VK_F3: + case VK_F5: + case VK_F6: + if (ctrl) + { + int index = 0; // name + if (keyDownInfo->wVKey == VK_F5) index = 1; // MTime + else if (keyDownInfo->wVKey == VK_F6) index = 2; // Size + ChangeSorting_and_Reload(index); + Reload_WithErrorMessage(); + return true; + } + return false; + case 'A': + if (ctrl) + { + // if (TempMode) + _list.SelectAll(); + return true; + } + return false; + + case VK_DELETE: + // if (TempMode) + OnDelete(/* !shift */); + return true; +#if 0 + case VK_NEXT: + case VK_PRIOR: + { + if (ctrl && !alt && !shift) + { + if (keyDownInfo->wVKey == VK_NEXT) + OnItemEnter(); + else + OpenParentFolder(); + SetMsgResult(TRUE); // to disable processing + return true; + } + break; + } +#endif + } + return false; +} + + +bool CBrowseDialog2::OnButtonClicked(unsigned buttonID, HWND buttonHWND) +{ + switch (buttonID) + { + case IDB_BROWSE2_PARENT: OpenParentFolder(); break; + case IDS_BUTTON_DELETE: + { + OnDelete(/* !IsKeyDown(VK_SHIFT) */); + break; + } + case IDM_VIEW_REFRESH: + { + Reload_WithErrorMessage(); + break; + } + default: return CModalDialog::OnButtonClicked(buttonID, buttonHWND); + } + _list.SetFocus(); + return true; +} + + + +static void PrintPropsPrefix(UString &s, UInt32 id) +{ + s.Add_LF(); + s += " "; + AddLangString(s, id); + s += ": "; +} + +wchar_t *Browse_ConvertSizeToString(UInt64 v, wchar_t *s); + +static void Browse_ConvertSizeToString(const CBrowseItem &bi, wchar_t *s) +{ + s = Browse_ConvertSizeToString(bi.Size, s); + if (bi.WasInterrupted) + { + *s++ = '+'; + *s = 0; + } +} + +void AddSizeValue(UString &s, UInt64 value); + +static void PrintProps_Size(UString &s, UInt64 size) +{ + PrintPropsPrefix(s, IDS_PROP_SIZE); +#if 1 + AddSizeValue(s, size); +#else + s.Add_UInt64(size); + if (size >= 10000) + { + s += " ("; + wchar_t temp[64]; + Browse_ConvertSizeToString(size, temp); + s += temp; + s.Add_Char(')'); + } +#endif +} + +static void PrintProps_MTime(UString &s, const CFileInfo &fi) +{ + PrintPropsPrefix(s, IDS_PROP_MTIME); + char t[64]; + ConvertUtcFileTimeToString(fi.MTime, t); + s += t; +} + + +static void PrintProps_Name(UString &s, const CFileInfo &fi) +{ + s += fs2us(fi.Name); + if (fi.IsDir()) + s.Add_PathSepar(); +} + +static void PrintProps_Attrib(UString &s, const CFileInfo &fi) +{ + PrintPropsPrefix(s, IDS_PROP_ATTRIBUTES); + char props[64]; + ConvertWinAttribToString(props, fi.Attrib); + s += props; +#if 0 + if (fi.HasReparsePoint()) + { + s.Add_LF(); + s += "IsLink: +"; + } +#endif +} + +static void PrintProps(UString &s, const CBrowseItem &bi, + const CFileInfo &fi, const CFileInfo *fi2) +{ + PrintProps_Name(s, fi); + PrintProps_Attrib(s, fi); + if (bi.NumDirs != 0) + { + PrintPropsPrefix(s, IDS_PROP_FOLDERS); + s.Add_UInt32(bi.NumDirs); + if (bi.WasInterrupted) + s += "+"; + } + if (bi.NumFiles != 0) + { + PrintPropsPrefix(s, IDS_PROP_FILES); + s.Add_UInt32(bi.NumFiles); + if (bi.WasInterrupted) + s += "+"; + } + { + PrintProps_Size(s, bi.Size); + if (bi.WasInterrupted) + s += "+"; + } + + PrintProps_MTime(s, fi); + + if (fi2) + { + s.Add_LF(); + s += "----------------"; + s.Add_LF(); + PrintProps_Name(s, *fi2); + PrintProps_Attrib(s, *fi2); + if (!fi2->IsDir()) + PrintProps_Size(s, fi2->Size); + PrintProps_MTime(s, *fi2); + } +} + + +void CBrowseDialog2::GetSelected_RealIndexes(CUIntVector &vector) +{ + vector.Clear(); + int index = -1; + for (;;) + { + index = _list.GetNextSelectedItem(index); + if (index < 0) + break; + const int realIndex = GetRealItemIndex(index); + if (realIndex >= 0) + vector.Add((unsigned)realIndex); + } +} + + +void CBrowseDialog2::PrintFileProps(UString &s, const CFileInfo &file) +{ + CFileInfo file2; + FString path = us2fs(DirPrefix); + path += file.Name; + if (!file2.Find(path)) + { + MessageBox_LastError_path(*this, path); + Reload_WithErrorMessage(); + return; + } + CBrowseEnumerator enumer; + enumer.bi.Size = file2.Size; + if (file2.IsDir() && !file2.HasReparsePoint()) + { + enumer.Path = path; + enumer.Enumerate(0); // level + } + PrintProps(s, enumer.bi, file2, + enumer.bi.NumRootItems == 1 ? &enumer.fi_SubFile : NULL); +} + + +void CBrowseDialog2::Show_FileProps_Window(const CFileInfo &file) +{ + UString s; + PrintFileProps(s, file); + MessageBoxW(*this, s, LangString(IDS_PROPERTIES), MB_OK); +} + + +void CBrowseDialog2::OnDelete(/* bool toRecycleBin */) +{ +#if 1 + // we don't want deleting in non temp folders + if (!DirPrefix.IsPrefixedBy(TempFolderPath)) + return; +#endif + + CUIntVector indices; + GetSelected_RealIndexes(indices); + if (indices.Size() == 0) + return; + { + UInt32 titleID, messageID; + UString messageParam; + UString s2; + if (indices.Size() == 1) + { + const unsigned index = indices[0]; + const CBrowseItem &bi = _items[index]; + const CFileInfo &file = _files[bi.MainFileIndex]; + PrintFileProps(s2, file); + messageParam = fs2us(file.Name); + if (file.IsDir()) + { + titleID = IDS_CONFIRM_FOLDER_DELETE; + messageID = IDS_WANT_TO_DELETE_FOLDER; + } + else + { + titleID = IDS_CONFIRM_FILE_DELETE; + messageID = IDS_WANT_TO_DELETE_FILE; + } + } + else + { + titleID = IDS_CONFIRM_ITEMS_DELETE; + messageID = IDS_WANT_TO_DELETE_ITEMS; + messageParam = NumberToString(indices.Size()); + + for (UInt32 i = 0; i < indices.Size(); i++) + { + if (i >= 10) + { + s2 += "..."; + break; + } + const CBrowseItem &bi = _items[indices[i]]; + const CFileInfo &fi = _files[bi.MainFileIndex]; + PrintProps_Name(s2, fi); + s2.Add_LF(); + } + } + UString s = MyFormatNew(messageID, messageParam); + if (!s2.IsEmpty()) + { + s.Add_LF(); + s.Add_LF(); + s += s2; + } + if (::MessageBoxW((HWND)*this, s, LangString(titleID), + MB_YESNOCANCEL | MB_ICONQUESTION) != IDYES) + return; + } + + for (UInt32 i = 0; i < indices.Size(); i++) + { + const unsigned index = indices[i]; + bool result = true; + const CBrowseItem &bi = _items[index]; + const CFileInfo &fi = _files[bi.MainFileIndex]; + if (fi.Name.IsEmpty()) + return; // some error + const FString fullPath = us2fs(DirPrefix) + fi.Name; + if (fi.IsDir()) + result = NFile::NDir::RemoveDirWithSubItems(fullPath); + else + result = NFile::NDir::DeleteFileAlways(fullPath); + if (!result) + { + MessageBox_LastError_path(*this, fullPath); + return; + } + } + + Reload_WithErrorMessage(); +} + + +#ifndef Z7_NO_REGISTRY +#define kHelpTopic "fm/temp.htm" +void CBrowseDialog2::OnHelp() +{ + ShowHelpWindow(kHelpTopic); + CModalDialog::OnHelp(); +} +#endif + + +HRESULT ShellFolder_ParseDisplayName(IShellFolder *shellFolder, + HWND hwnd, const UString &path, LPITEMIDLIST *ppidl); + +HRESULT StartApplication(const UString &dir, const UString &path, HWND window, CProcess &process); +HRESULT StartApplication(const UString &dir, const UString &path, HWND window, CProcess &process) +{ + UString path2 = path; + UINT32 result; + { +#ifdef _WIN32 + NShell::CItemIDList pidl; + // SHELLEXECUTEINFO::pidl is more accurate way than SHELLEXECUTEINFO::lpFile + { + CMyComPtr desktop; + if (SHGetDesktopFolder(&desktop) == S_OK && desktop) + if (ShellFolder_ParseDisplayName(desktop, + NULL, // HWND : do we need (window) or NULL here? + path, + &pidl) != S_OK) + pidl.Detach(); + } + { + const int dot = path2.ReverseFind_Dot(); + const int separ = path2.ReverseFind_PathSepar(); + if (separ != (int)path2.Len() - 1) + if (dot < 0 || dot < separ) + path2.Add_Dot(); + } +#endif // _WIN32 + +#ifndef _UNICODE + if (g_IsNT) + { + SHELLEXECUTEINFOW execInfo; + memset(&execInfo, 0, sizeof(execInfo)); + // execInfo.hwnd = NULL; + // execInfo.lpVerb = NULL; + // execInfo.lpFile = NULL; + // execInfo.lpDirectory = NULL; + // execInfo.lpParameters = NULL; + // execInfo.hProcess = NULL; + execInfo.cbSize = sizeof(execInfo); + execInfo.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_DDEWAIT; + if (!dir.IsEmpty()) + execInfo.lpDirectory = dir; + execInfo.nShow = SW_SHOWNORMAL; + + if ((LPCITEMIDLIST)pidl) + { + execInfo.lpIDList = pidl; + execInfo.fMask |= SEE_MASK_IDLIST; + } + else + execInfo.lpFile = path2; + +typedef BOOL (WINAPI * Func_ShellExecuteExW)(LPSHELLEXECUTEINFOW lpExecInfo); +Z7_DIAGNOSTIC_IGNORE_CAST_FUNCTION + const + Func_ShellExecuteExW + f_ShellExecuteExW = Z7_GET_PROC_ADDRESS( + Func_ShellExecuteExW, ::GetModuleHandleW(L"shell32.dll"), + "ShellExecuteExW"); + if (!f_ShellExecuteExW) + return 0; + f_ShellExecuteExW(&execInfo); + result = (UINT32)(UINT_PTR)execInfo.hInstApp; + process.Attach(execInfo.hProcess); + } + else +#endif + { + SHELLEXECUTEINFO execInfo; + memset(&execInfo, 0, sizeof(execInfo)); + // execInfo.hwnd = NULL; + // execInfo.lpVerb = NULL; + // execInfo.lpFile = NULL; + // execInfo.lpDirectory = NULL; + // execInfo.lpParameters = NULL; + // execInfo.hProcess = NULL; + execInfo.cbSize = sizeof(execInfo); + execInfo.fMask = SEE_MASK_NOCLOSEPROCESS + #ifndef UNDER_CE + | SEE_MASK_FLAG_DDEWAIT + #endif + ; + execInfo.nShow = SW_SHOWNORMAL; + const CSysString sysPath (GetSystemString(path2)); + const CSysString sysDir (GetSystemString(dir)); + #ifndef UNDER_CE + if (!sysDir.IsEmpty()) + execInfo.lpDirectory = sysDir; + #endif + + if ((LPCITEMIDLIST)pidl) + { + execInfo.lpIDList = pidl; + execInfo.fMask |= SEE_MASK_IDLIST; + } + else + execInfo.lpFile = sysPath; + ::ShellExecuteEx(&execInfo); + result = (UINT32)(UINT_PTR)execInfo.hInstApp; + process.Attach(execInfo.hProcess); + } + // DEBUG_PRINT_NUM("-- ShellExecuteEx -- execInfo.hInstApp = ", result) + } + if (result <= 32) + { + switch (result) + { + case SE_ERR_NOASSOC: + MessageBox_HResError(window, + GetLastError_noZero_HRESULT(), + NULL + // L"There is no application associated with the given file name extension", + ); + } + return E_FAIL; // fixed in 15.13. Can we use it for any Windows version? + } + return S_OK; +} + +void StartApplicationDontWait(const UString &dir, const UString &path, HWND window); +void StartApplicationDontWait(const UString &dir, const UString &path, HWND window) +{ + CProcess process; + StartApplication(dir, path, window, process); +} + + +static UString GetQuotedString2(const UString &s) +{ + UString s2 ('\"'); + s2 += s; + s2.Add_Char('\"'); + return s2; +} + + +bool CBrowseDialog2::OnContextMenu(HANDLE windowHandle, int xPos, int yPos) +{ + if (windowHandle != _list) + return false; + + CUIntVector indices; + GetSelected_RealIndexes(indices); + + // negative x,y are possible for multi-screen modes. + // x=-1 && y=-1 for keyboard call (SHIFT+F10 and others). +#if 1 // 0 : for debug + if (xPos == -1 && yPos == -1) +#endif + { +/* + if (indices.Size() == 0) + { + xPos = 0; + yPos = 0; + } + else +*/ + { + const int itemIndex = _list.GetFocusedItem(); + if (itemIndex == -1) + return false; + RECT rect; + if (!_list.GetItemRect(itemIndex, &rect, LVIR_ICON)) + return false; + // rect : rect of file icon relative to listVeiw. + xPos = (rect.left + rect.right) / 2; + yPos = (rect.top + rect.bottom) / 2; + RECT r; + GetClientRectOfItem(IDL_BROWSE2, r); + if (yPos < 0 || yPos >= RECT_SIZE_Y(r)) + yPos = 0; + } + POINT point = {xPos, yPos}; + _list.ClientToScreen(&point); + xPos = point.x; + yPos = point.y; + } + + const UInt32 k_CmdId_Delete = 1; + const UInt32 k_CmdId_Open_Explorer = 2; + const UInt32 k_CmdId_Open_7zip = 3; + const UInt32 k_CmdId_Props = 4; + int menuResult; + { + CMenu menu; + CMenuDestroyer menuDestroyer(menu); + menu.CreatePopup(); + + unsigned numMenuItems = 0; + // unsigned defaultCmd = 0; + + for (unsigned cmd = k_CmdId_Delete; cmd <= k_CmdId_Props; cmd++) + { + if (cmd == k_CmdId_Delete) + { + if (/* !TempMode || */ indices.Size() == 0) + continue; + // defaultCmd = cmd; + } + else if (indices.Size() > 1) + break; + + + if (numMenuItems != 0) + { + if (cmd == k_CmdId_Open_Explorer) + menu.AppendItem(MF_SEPARATOR, 0, (LPCTSTR)NULL); + if (cmd == k_CmdId_Props) + menu.AppendItem(MF_SEPARATOR, 0, (LPCTSTR)NULL); + } + + const UINT flags = MF_STRING; + UString s; + if (cmd == k_CmdId_Delete) + { + s = LangString(IDS_BUTTON_DELETE); + s += "\tDelete"; + } + else if (cmd == k_CmdId_Open_Explorer) + { + s = LangString(IDM_OPEN_OUTSIDE); + if (s.IsEmpty()) + s = "Open Outside"; + s += "\tShift+Enter"; + } + else if (cmd == k_CmdId_Open_7zip) + { + s = LangString(IDM_OPEN_OUTSIDE); + if (s.IsEmpty()) + s = "Open Outside"; + s += " : 7-Zip"; + } + else if (cmd == k_CmdId_Props) + { + s = LangString(IDS_PROPERTIES); + if (s.IsEmpty()) + s = "Properties"; + s += "\tAlt+Enter"; + } + else + break; + s.RemoveChar(L'&'); + menu.AppendItem(flags, cmd, s); + numMenuItems++; + } + // default item is useless for us + /* + if (defaultCmd != 0) + SetMenuDefaultItem(menu, (unsigned)defaultCmd, + FALSE); // byPos + */ + /* hwnd for TrackPopupMenuEx(): DOCS: + A handle to the window that owns the shortcut menu. + This window receives all messages from the menu. + The window does not receive a WM_COMMAND message from the menu + until the function returns. + If you specify TPM_NONOTIFY in the fuFlags parameter, + the function does not send messages to the window identified by hwnd. + */ + if (numMenuItems == 0) + return true; + menuResult = menu.Track(TPM_LEFTALIGN | TPM_RETURNCMD | TPM_NONOTIFY, + xPos, yPos, *this); + /* menu.Track() return value is zero, if the user cancels + the menu without making a selection, or if an error occurs */ + if (menuResult <= 0) + return true; + } + + if (menuResult == k_CmdId_Delete) + { + OnDelete(/* !IsKeyDown(VK_SHIFT) */); + return true; + } + + if (indices.Size() <= 1) + { + UString fullPath = DirPrefix; + if (indices.Size() != 0) + { + const CBrowseItem &bi = _items[indices[0]]; + const CFileInfo &file = _files[bi.MainFileIndex]; + if (file.HasReparsePoint()) + { + // we don't want external program was used to work with Links + ShowErrorMessage(*this, k_Message_Link_operation_was_Blocked); + return true; + } + fullPath += fs2us(file.Name); + } + if (menuResult == k_CmdId_Open_Explorer) + { + StartApplicationDontWait(DirPrefix, fullPath, (HWND)*this); + return true; + } + + if (menuResult == k_CmdId_Open_7zip) + { + UString imageName = fs2us(NWindows::NDLL::GetModuleDirPrefix()); + imageName += "7zFM.exe"; + WRes wres; + { + CProcess process; + wres = process.Create(imageName, GetQuotedString2(fullPath), NULL); // curDir + } + if (wres != 0) + { + const HRESULT hres = HRESULT_FROM_WIN32(wres); + MessageBox_HResError(*this, hres, imageName); + } + return true; + } + + if (indices.Size() == 1) + if (menuResult == k_CmdId_Props) + { + const CBrowseItem &bi = _items[indices[0]]; + const CFileInfo &file = _files[bi.MainFileIndex]; + Show_FileProps_Window(file); + return true; + } + } + + return true; +} + + + +struct CWaitCursor2 +{ + HCURSOR _waitCursor; + HCURSOR _oldCursor; + + CWaitCursor2(): + _waitCursor(NULL), + _oldCursor(NULL) + {} + void Set() + { + if (!_waitCursor) + { + _waitCursor = LoadCursor(NULL, IDC_WAIT); + if (_waitCursor) + _oldCursor = SetCursor(_waitCursor); + } + } + ~CWaitCursor2() + { + if (_waitCursor) + SetCursor(_oldCursor); + } +}; + + +void CBrowseDialog2::OnOK() +{ + /* DOCS: + If a dialog box or one of its controls currently has the input focus, + then pressing the ENTER key causes Windows to send a WM_COMMAND message + with the idItem (wParam) parameter set to the ID of the default command button. + If the dialog box does not have a default command button, + then the idItem parameter is set to IDOK by default. + + We process IDOK here for Enter pressing, because we have no DEFPUSHBUTTON. + */ + if (GetFocus() == _list) + { + OnItemEnter(); + return; + } + // Enter can be pressed in another controls (Edit). + // So we don't need End() call here +} + + +bool CBrowseDialog2::GetParentPath(const UString &path, UString &parentPrefix, UString &name) +{ + parentPrefix.Empty(); + name.Empty(); + if (path.IsEmpty()) + return false; + if (_topDirPrefix == path) + return false; + UString s = path; + if (IS_PATH_SEPAR(s.Back())) + s.DeleteBack(); + if (s.IsEmpty()) + return false; + if (IS_PATH_SEPAR(s.Back())) + return false; + const unsigned pos1 = (unsigned)(s.ReverseFind_PathSepar() + 1); + parentPrefix.SetFrom(s, pos1); + name = s.Ptr(pos1); + return true; +} + + +int CBrowseDialog2::CompareItems(LPARAM lParam1, LPARAM lParam2) const +{ + if (lParam1 == lParam2) return 0; + if (lParam1 == kParentIndex) return -1; + if (lParam2 == kParentIndex) return 1; + + const int index1 = (int)lParam1; + const int index2 = (int)lParam2; + + const CBrowseItem &item1 = _items[index1]; + const CBrowseItem &item2 = _items[index2]; + + const CFileInfo &f1 = _files[item1.MainFileIndex]; + const CFileInfo &f2 = _files[item2.MainFileIndex]; + + const bool isDir2 = f2.IsDir(); + if (f1.IsDir()) + { + if (!isDir2) return -1; + } + else if (isDir2) return 1; + + const int res2 = MyCompare(index1, index2); + int res = 0; + switch (_sortIndex) + { + case 0: res = CompareFileNames(fs2us(f1.Name), fs2us(f2.Name)); break; + case 1: res = CompareFileTime(&f1.MTime, &f2.MTime); break; + case 2: res = MyCompare(item1.Size, item2.Size); break; + case 3: res = MyCompare(item1.NumFiles, item2.NumFiles); break; + case 4: res = MyCompare(item1.NumDirs, item2.NumDirs); break; + case 5: + { + const int sub1 = item1.SubFileIndex; + const int sub2 = item2.SubFileIndex; + if (sub1 < 0) + { + if (sub2 >= 0) + res = -1; + } + else if (sub2 < 0) + res = 1; + else + res = CompareFileNames(fs2us(_files[sub1].Name), fs2us(_files[sub2].Name)); + break; + } + } + if (res == 0) + res = res2; + return _ascending ? res: -res; +} + +static int CALLBACK CompareItems2(LPARAM lParam1, LPARAM lParam2, LPARAM lpData) +{ + return ((CBrowseDialog2 *)lpData)->CompareItems(lParam1, lParam2); +} + + +static const FChar *FindNonHexChar_F(const FChar *s) throw() +{ + for (;;) + { + const FChar c = (FChar)*s++; // pointer can go 1 byte after end + if ( (c < '0' || c > '9') + && (c < 'a' || c > 'z') + && (c < 'A' || c > 'Z')) + return s - 1; + } +} + + +void CBrowseDialog2::Reload_WithErrorMessage() +{ + const HRESULT res = Reload(); + if (res != S_OK) + MessageBox_HResError(*this, res, DirPrefix); +} + +void CBrowseDialog2::ChangeSorting_and_Reload(int columnIndex) +{ + if (columnIndex == _sortIndex) + _ascending = !_ascending; + else + { + _ascending = (columnIndex == 0 || columnIndex == _columnIndex_fileNameInDir); // for name columns + _sortIndex = columnIndex; + } + Reload_WithErrorMessage(); +} + + +// Reload changes DirPrefix. Don't send DirPrefix in pathPrefix parameter +HRESULT CBrowseDialog2::Reload(const UString &pathPrefix, const UString &selectedName) +{ + UStringVector selectedVector; + if (!selectedName.IsEmpty()) + selectedVector.Add(selectedName); + return Reload(pathPrefix, selectedVector, selectedName); +} + + +HRESULT CBrowseDialog2::Reload(const UString &pathPrefix, const UStringVector &selectedVector2, const UString &focusedName) +{ + UStringVector selectedVector = selectedVector2; + selectedVector.Sort(); + CObjectVector files; + CRecordVector items; + CWaitCursor2 waitCursor; + + #ifndef UNDER_CE + bool isDrive = false; + if (pathPrefix.IsEmpty() || pathPrefix.IsEqualTo(kSuperPathPrefix)) + { + isDrive = true; + FStringVector drives; + if (!MyGetLogicalDriveStrings(drives)) + return GetLastError_noZero_HRESULT(); + FOR_VECTOR (i, drives) + { + const FString &d = drives[i]; + if (d.Len() < 2 || d.Back() != '\\') + return E_FAIL; + CBrowseItem item; + item.Construct(); + item.MainFileIndex = files.Size(); + CFileInfo &fi = files.AddNew(); + fi.SetAsDir(); + fi.Name = d; + fi.Name.DeleteBack(); + items.Add(item); + } + } + else + #endif + { + { + CEnumerator enumerator; + enumerator.SetDirPrefix(us2fs(pathPrefix)); + CFileInfo fi; + FString tail; + + const bool isTempFolder = ( + // TempMode && + IsExactTempFolder(pathPrefix) + ); + for (;;) + { + { + bool found; + if (!enumerator.Next(fi, found)) + return GetLastError_noZero_HRESULT(); + if (!found) + break; + } + if (isTempFolder) + { + // if (!Show_Non7zDirs_InTemp) + { + if (!fi.Name.IsPrefixedBy_Ascii_NoCase("7z")) + continue; + tail = fi.Name.Ptr(2); + if ( !tail.IsPrefixedBy_Ascii_NoCase("E") // drag and drop / Copy / create to email + && !tail.IsPrefixedBy_Ascii_NoCase("O") // open + && !tail.IsPrefixedBy_Ascii_NoCase("S")) // SFXSetup + continue; + const FChar *beg = tail.Ptr(1); + const FChar *end = FindNonHexChar_F(beg); + if (end - beg != 8 || *end != 0) + continue; + } + } + CBrowseItem item; + item.Construct(); + item.MainFileIndex = files.Size(); + item.Size = fi.Size; + files.Add(fi); + items.Add(item); + } + } + + UInt64 cnt = items.Size(); + // if (TempMode) + { + FOR_VECTOR (i, items) + { + CBrowseItem &item = items[i]; + const CFileInfo &fi = files[item.MainFileIndex]; + if (!fi.IsDir() || fi.HasReparsePoint()) + continue; + + CBrowseEnumerator enumer; + // we need to keep MainFileIndex and Size value of item: + enumer.bi = item; // don't change it + enumer.Path = us2fs(pathPrefix); + enumer.Path += fi.Name; + enumer.Enumerate(0); // level + item = enumer.bi; + if (item.NumRootItems == 1) + { + item.SubFileIndex = (int)files.Size(); + files.Add(enumer.fi_SubFile); + } + cnt += item.NumDirs; + cnt += item.NumFiles; + if (cnt > 1000) + waitCursor.Set(); + } + } + } + _items = items; + _files = files; + + DirPrefix = pathPrefix; + + EnableItem(IDB_BROWSE2_PARENT, !IsExactTempFolder(pathPrefix)); + + SetItemText(IDT_BROWSE2_FOLDER, DirPrefix); + + _list.SetRedraw(false); + _list.DeleteAllItems(); + + LVITEMW item; + + unsigned index = 0; + int cursorIndex = -1; + + #ifndef Z7_SFX + if (_showDots && _topDirPrefix != DirPrefix) + { + item.iItem = (int)index; + const UString itemName (".."); + if (focusedName == itemName) + cursorIndex = (int)index; + /* + if (selectedVector.IsEmpty() + // && focusedName.IsEmpty() + // && focusedName == ".." + ) + cursorIndex = (int)index; + */ + item.mask = LVIF_TEXT | LVIF_PARAM | LVIF_IMAGE; + unsigned subItem = 0; + item.iSubItem = (int)(subItem++); + item.lParam = kParentIndex; + item.pszText = itemName.Ptr_non_const(); + item.iImage = _extToIconMap.GetIconIndex(FILE_ATTRIBUTE_DIRECTORY, DirPrefix); + if (item.iImage < 0) + item.iImage = 0; + _list.InsertItem(&item); +#if 0 + for (int k = 1; k < 6; k++) + _list.SetSubItem(index, subItem++, L"2"); +#endif + index++; + } + #endif + + for (unsigned i = 0; i < _items.Size(); i++, index++) + { + item.iItem = (int)index; + const CBrowseItem &bi = _items[i]; + const CFileInfo &fi = _files[bi.MainFileIndex]; + const UString name = fs2us(fi.Name); + // if (!selectedName.IsEmpty() && CompareFileNames(name, selectedName) == 0) + item.mask = LVIF_TEXT | LVIF_PARAM | LVIF_IMAGE; + item.state = 0; + if (selectedVector.FindInSorted(name) != -1) + { + /* + if (cursorIndex == -1) + cursorIndex = (int)index; + */ + item.mask |= LVIF_STATE; + item.state |= LVIS_SELECTED; + } + if (focusedName == name) + { + if (cursorIndex == -1) + cursorIndex = (int)index; + item.mask |= LVIF_STATE; + item.state |= LVIS_FOCUSED; + } + + unsigned subItem = 0; + item.iSubItem = (int)(subItem++); + item.lParam = (LPARAM)i; + item.pszText = name.Ptr_non_const(); + + const UString fullPath = DirPrefix + name; + #ifndef UNDER_CE + if (isDrive) + { + item.iImage = Shell_GetFileInfo_SysIconIndex_for_Path( + fi.Name + FCHAR_PATH_SEPARATOR, + FILE_ATTRIBUTE_DIRECTORY); + } + else + #endif + item.iImage = _extToIconMap.GetIconIndex(fi.Attrib, fullPath); + if (item.iImage < 0) + item.iImage = 0; + _list.InsertItem(&item); + wchar_t s[64]; + { + s[0] = 0; + if (!FILETIME_IsZero(fi.MTime)) + ConvertUtcFileTimeToString(fi.MTime, s, + #ifndef UNDER_CE + kTimestampPrintLevel_MIN + #else + kTimestampPrintLevel_DAY + #endif + ); + _list.SetSubItem(index, subItem++, s); + } + { + s[0] = 0; + Browse_ConvertSizeToString(bi, s); + _list.SetSubItem(index, subItem++, s); + } + if (_columnIndex_NumFiles >= 0) + { + UString s2; + if (fi.HasReparsePoint()) + { + s2 = "Link"; + } + else if (bi.NumFiles != 0) + { + s2.Add_UInt32(bi.NumFiles); + if (bi.WasInterrupted) + s2 += "+"; + } + _list.SetSubItem(index, subItem, s2); + } + subItem++; + if (_columnIndex_NumDirs >= 0 && bi.NumDirs != 0) + { + UString s2; + s2.Add_UInt32(bi.NumDirs); + if (bi.WasInterrupted) + s2 += "+"; + _list.SetSubItem(index, subItem, s2); + } + subItem++; + if (_columnIndex_fileNameInDir >= 0 && bi.SubFileIndex >= 0) + { + _list.SetSubItem(index, subItem, fs2us(_files[bi.SubFileIndex].Name)); + } + subItem++; + } + + if (_list.GetItemCount() > 0 && cursorIndex >= 0) + { + // _list.SetItemState_FocusedSelected(cursorIndex); + // _list.SetItemState_Focused(cursorIndex); + } + _list.SortItems(CompareItems2, (LPARAM)this); + if (_list.GetItemCount() > 0 && cursorIndex < 0) + { + if (selectedVector.IsEmpty()) + _list.SetItemState_FocusedSelected(0); + else + _list.SetItemState(0, LVIS_FOCUSED, LVIS_FOCUSED); + } + _list.EnsureVisible(_list.GetFocusedItem(), false); + _list.SetRedraw(true); + _list.InvalidateRect(NULL, true); + return S_OK; +} + + + +HRESULT CBrowseDialog2::Reload() +{ + UStringVector selected; + { + CUIntVector indexes; + GetSelected_RealIndexes(indexes); + FOR_VECTOR (i, indexes) + selected.Add(fs2us(Get_MainFileName_for_realIndex(indexes[i]))); + } + UString focusedName; + const int focusedItem = _list.GetFocusedItem(); + if (focusedItem >= 0) + { + const int realIndex = GetRealItemIndex(focusedItem); + if (realIndex != kParentIndex) + focusedName = fs2us(Get_MainFileName_for_realIndex((unsigned)realIndex)); + } + const UString dirPathTemp = DirPrefix; + return Reload(dirPathTemp, selected, focusedName); +} + + +void CBrowseDialog2::OpenParentFolder() +{ +#if 1 // 0 : for debug + // we don't allow to go to parent of TempFolder. + // if (TempMode) + { + if (IsExactTempFolder(DirPrefix)) + return; + } +#endif + + UString parent, selected; + if (GetParentPath(DirPrefix, parent, selected)) + Reload(parent, selected); +} + + +void CBrowseDialog2::OnItemEnter() +{ + const bool alt = IsKeyDown(VK_MENU); + const bool ctrl = IsKeyDown(VK_CONTROL); + const bool shift = IsKeyDown(VK_SHIFT); + + const int index = _list.GetNextSelectedItem(-1); + if (index < 0) + return; + if (_list.GetNextSelectedItem(index) >= 0) + return; // more than one selected + const int realIndex = GetRealItemIndex(index); + if (realIndex == kParentIndex) + OpenParentFolder(); + else + { + const CBrowseItem &bi = _items[realIndex]; + const CFileInfo &file = _files[bi.MainFileIndex]; + if (alt) + { + Show_FileProps_Window(file); + return; + } + if (file.HasReparsePoint()) + { + // we don't want Link open operation, + // because user can think that it's usual folder/file (non-link). + ShowErrorMessage(*this, k_Message_Link_operation_was_Blocked); + return; + } + bool needExternal = true; + if (file.IsDir()) + { + if (!shift || alt || ctrl) // open folder in Explorer: + needExternal = false; + } + const UString fullPath = DirPrefix + fs2us(file.Name); + if (needExternal) + { + StartApplicationDontWait(DirPrefix, fullPath, (HWND)*this); + return; + } + UString s = fullPath; + s.Add_PathSepar(); + const HRESULT res = Reload(s, UString()); + if (res != S_OK) + MessageBox_HResError(*this, res, s); + // SetPathEditText(); + } +} + + +void MyBrowseForTempFolder(HWND owner) +{ + FString tempPathF; + if (!NFile::NDir::MyGetTempPath(tempPathF) || tempPathF.IsEmpty()) + { + MessageBox_LastError_path(owner, tempPathF); + return; + } + CBrowseDialog2 dialog; + + LangString_OnlyFromLangFile(IDM_TEMP_DIR, dialog.Title); + dialog.Title.Replace(L"...", L""); + if (dialog.Title.IsEmpty()) + dialog.Title = "Delete Temporary Files"; + + dialog.TempFolderPath = fs2us(tempPathF); + dialog.Create(owner); + // we can exit from dialog with 2 ways: + // IDCANCEL : Esc Key, or close icons + // IDCLOSE : with Close button +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/BrowseDialog2.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/BrowseDialog2.h new file mode 100644 index 00000000..6d5fb237 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/BrowseDialog2.h @@ -0,0 +1,10 @@ +// BrowseDialog2.h + +#ifndef ZIP7_INC_BROWSE_DIALOG2_H +#define ZIP7_INC_BROWSE_DIALOG2_H + +#include "../../../Windows/Window.h" + +void MyBrowseForTempFolder(HWND owner); + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/BrowseDialog2.rc b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/BrowseDialog2.rc new file mode 100644 index 00000000..2ce2e458 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/BrowseDialog2.rc @@ -0,0 +1,32 @@ +#include "BrowseDialog2Res.h" +#include "../../GuiCommon.rc" +// #include "resource.h" + +#define xc 450 +#define yc 328 + +#define k_BROWSE2_y_CtrlSize 14 + +#define k_BROWSE2_y_List 40 + +IDD_BROWSE2 DIALOG 0, 0, xs, ys MY_MODAL_RESIZE_DIALOG_STYLE MY_FONT +CAPTION "7-Zip: Browse Temp Files" +{ +// EDITTEXT IDE_BROWSE_PATH, m, by - m - k_BROWSE_y_CtrlSize - k_BROWSE_y_CtrlSize - m, xc, k_BROWSE_y_CtrlSize, ES_AUTOHSCROLL + COMBOBOX IDC_BROWSE2_FILTER, m, by - m - k_BROWSE2_y_CtrlSize, xc, 30, MY_COMBO + +// DON'T USE DEFPUSHBUTTON here, because we use IDOK as default action for Enter key. + PUSHBUTTON "Close", IDCLOSE, bx2, by, bxs, bys, WS_GROUP + PUSHBUTTON "Help", IDHELP, bx1, by, bxs, bys + + PUSHBUTTON "Delete", IDS_BUTTON_DELETE, m , m, 64, bys + PUSHBUTTON "Refresh", IDM_VIEW_REFRESH, m + 64 + 8, m, 64, bys + + PUSHBUTTON "<--", IDB_BROWSE2_PARENT, m, m + 21, 24, bys +// LTEXT "", IDT_BROWSE_FOLDER, m + 30, m + 24, xc - 30, 8 + EDITTEXT IDT_BROWSE2_FOLDER, m + 28, m + 22, xc - 28, 14, ES_AUTOHSCROLL | ES_READONLY + + CONTROL "List1", IDL_BROWSE2, "SysListView32", + LVS_REPORT | LVS_SHOWSELALWAYS | LVS_SHAREIMAGELISTS | WS_BORDER | WS_TABSTOP, + m, m + k_BROWSE2_y_List, xc, yc - bys - m - k_BROWSE2_y_List - k_BROWSE2_y_CtrlSize - m +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/BrowseDialog2Res.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/BrowseDialog2Res.h new file mode 100644 index 00000000..add91589 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/BrowseDialog2Res.h @@ -0,0 +1,9 @@ +#define IDD_BROWSE2 93 + +#define IDL_BROWSE2 100 +#define IDT_BROWSE2_FOLDER 101 +// #define IDE_BROWSE2_PATH 102 +#define IDC_BROWSE2_FILTER 103 + +#define IDB_BROWSE2_PARENT 110 +// #define IDB_BROWSE2_CREATE_DIR 112 diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/BrowseDialogRes.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/BrowseDialogRes.h new file mode 100644 index 00000000..aff84ec9 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/BrowseDialogRes.h @@ -0,0 +1,9 @@ +#define IDD_BROWSE 95 + +#define IDL_BROWSE 100 +#define IDT_BROWSE_FOLDER 101 +#define IDE_BROWSE_PATH 102 +#define IDC_BROWSE_FILTER 103 + +#define IDB_BROWSE_PARENT 110 +#define IDB_BROWSE_CREATE_DIR 112 diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ClassDefs.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ClassDefs.cpp new file mode 100644 index 00000000..5c269043 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ClassDefs.cpp @@ -0,0 +1,12 @@ +// ClassDefs.cpp + +#include "StdAfx.h" + +#include "../../../Common/MyWindows.h" + +#include "../../../Common/MyInitGuid.h" + +#include "../Agent/Agent.h" + +#include "MyWindowsNew.h" +#include "../Explorer/MyExplorerCommand.h" diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ComboDialog.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ComboDialog.cpp new file mode 100644 index 00000000..921972ea --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ComboDialog.cpp @@ -0,0 +1,64 @@ +// ComboDialog.cpp + +#include "StdAfx.h" +#include "ComboDialog.h" + +#include "../../../Windows/Control/Static.h" + +#ifdef Z7_LANG +#include "LangUtils.h" +#endif + +using namespace NWindows; + +bool CComboDialog::OnInit() +{ + #ifdef Z7_LANG + LangSetDlgItems(*this, NULL, 0); + #endif + _comboBox.Attach(GetItem(IDC_COMBO)); + + /* + // why it doesn't work ? + DWORD style = _comboBox.GetStyle(); + if (Sorted) + style |= CBS_SORT; + else + style &= ~CBS_SORT; + _comboBox.SetStyle(style); + */ + SetText(Title); + + NControl::CStatic staticContol; + staticContol.Attach(GetItem(IDT_COMBO)); + staticContol.SetText(Static); + _comboBox.SetText(Value); + FOR_VECTOR (i, Strings) + _comboBox.AddString(Strings[i]); + NormalizeSize(); + return CModalDialog::OnInit(); +} + +bool CComboDialog::OnSize(WPARAM /* wParam */, int xSize, int ySize) +{ + int mx, my; + GetMargins(8, mx, my); + int bx1, bx2, by; + GetItemSizes(IDCANCEL, bx1, by); + GetItemSizes(IDOK, bx2, by); + int y = ySize - my - by; + int x = xSize - mx - bx1; + + InvalidateRect(NULL); + + MoveItem(IDCANCEL, x, y, bx1, by); + MoveItem(IDOK, x - mx - bx2, y, bx2, by); + ChangeSubWindowSizeX(_comboBox, xSize - mx * 2); + return false; +} + +void CComboDialog::OnOK() +{ + _comboBox.GetText(Value); + CModalDialog::OnOK(); +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ComboDialog.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ComboDialog.h new file mode 100644 index 00000000..bb0fda80 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ComboDialog.h @@ -0,0 +1,28 @@ +// ComboDialog.h + +#ifndef ZIP7_INC_COMBO_DIALOG_H +#define ZIP7_INC_COMBO_DIALOG_H + +#include "../../../Windows/Control/ComboBox.h" +#include "../../../Windows/Control/Dialog.h" + +#include "ComboDialogRes.h" + +class CComboDialog: public NWindows::NControl::CModalDialog +{ + NWindows::NControl::CComboBox _comboBox; + virtual void OnOK() Z7_override; + virtual bool OnInit() Z7_override; + virtual bool OnSize(WPARAM wParam, int xSize, int ySize) Z7_override; +public: + // bool Sorted; + UString Title; + UString Static; + UString Value; + UStringVector Strings; + + // CComboDialog(): Sorted(false) {}; + INT_PTR Create(HWND parentWindow = NULL) { return CModalDialog::Create(IDD_COMBO, parentWindow); } +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ComboDialog.rc b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ComboDialog.rc new file mode 100644 index 00000000..fddb7484 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ComboDialog.rc @@ -0,0 +1,16 @@ +#include "ComboDialogRes.h" +#include "../../GuiCommon.rc" + +#define xc 240 +#define yc 64 + +IDD_COMBO DIALOG 0, 0, xs, ys MY_MODAL_RESIZE_DIALOG_STYLE MY_FONT +CAPTION "Combo" +{ + LTEXT "", IDT_COMBO, m, m, xc, 8 + COMBOBOX IDC_COMBO, m, 20, xc, 65, MY_COMBO_WITH_EDIT + OK_CANCEL +} + +#undef xc +#undef yc diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ComboDialogRes.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ComboDialogRes.h new file mode 100644 index 00000000..a044797e --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ComboDialogRes.h @@ -0,0 +1,4 @@ +#define IDD_COMBO 98 + +#define IDT_COMBO 100 +#define IDC_COMBO 101 diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/Copy.bmp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/Copy.bmp new file mode 100644 index 00000000..0f28a324 Binary files /dev/null and b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/Copy.bmp differ diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/Copy2.bmp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/Copy2.bmp new file mode 100644 index 00000000..ba88ded0 Binary files /dev/null and b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/Copy2.bmp differ diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/CopyDialog.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/CopyDialog.cpp new file mode 100644 index 00000000..9bc01d06 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/CopyDialog.cpp @@ -0,0 +1,103 @@ +// CopyDialog.cpp + +#include "StdAfx.h" + +#include "../../../Windows/FileName.h" + +#include "../../../Windows/Control/Static.h" + +#include "BrowseDialog.h" +#include "CopyDialog.h" +#include "LangUtils.h" + +using namespace NWindows; + +bool CCopyDialog::OnInit() +{ + #ifdef Z7_LANG + LangSetDlgItems(*this, NULL, 0); + #endif + _path.Attach(GetItem(IDC_COPY)); + SetText(Title); + + NControl::CStatic staticContol; + staticContol.Attach(GetItem(IDT_COPY)); + staticContol.SetText(Static); + #ifdef UNDER_CE + // we do it, since WinCE selects Value\something instead of Value !!!! + _path.AddString(Value); + #endif + FOR_VECTOR (i, Strings) + _path.AddString(Strings[i]); + _path.SetText(Value); + SetItemText(IDT_COPY_INFO, Info); + NormalizeSize(true); + return CModalDialog::OnInit(); +} + +bool CCopyDialog::OnSize(WPARAM /* wParam */, int xSize, int ySize) +{ + int mx, my; + GetMargins(8, mx, my); + int bx1, bx2, by; + GetItemSizes(IDCANCEL, bx1, by); + GetItemSizes(IDOK, bx2, by); + const int y = ySize - my - by; + const int x = xSize - mx - bx1; + + InvalidateRect(NULL); + + { + RECT r; + GetClientRectOfItem(IDB_COPY_SET_PATH, r); + const int bx = RECT_SIZE_X(r); + MoveItem(IDB_COPY_SET_PATH, xSize - mx - bx, r.top, bx, RECT_SIZE_Y(r)); + ChangeSubWindowSizeX(_path, xSize - mx - mx - bx - mx); + } + + { + RECT r; + GetClientRectOfItem(IDT_COPY_INFO, r); + NControl::CStatic staticContol; + staticContol.Attach(GetItem(IDT_COPY_INFO)); + const int yPos = r.top; + staticContol.Move(mx, yPos, xSize - mx * 2, y - 2 - yPos); + } + + MoveItem(IDCANCEL, x, y, bx1, by); + MoveItem(IDOK, x - mx - bx2, y, bx2, by); + + return false; +} + +bool CCopyDialog::OnButtonClicked(unsigned buttonID, HWND buttonHWND) +{ + switch (buttonID) + { + case IDB_COPY_SET_PATH: + OnButtonSetPath(); + return true; + } + return CModalDialog::OnButtonClicked(buttonID, buttonHWND); +} + +void CCopyDialog::OnButtonSetPath() +{ + UString currentPath; + _path.GetText(currentPath); + + const UString title = LangString(IDS_SET_FOLDER); + + UString resultPath; + if (!MyBrowseForFolder(*this, title, currentPath, resultPath)) + return; + NFile::NName::NormalizeDirPathPrefix(resultPath); + _path.SetCurSel(-1); + _path.SetText(resultPath); +} + +void CCopyDialog::OnOK() +{ + _path.GetText(Value); + CModalDialog::OnOK(); +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/CopyDialog.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/CopyDialog.h new file mode 100644 index 00000000..37824208 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/CopyDialog.h @@ -0,0 +1,31 @@ +// CopyDialog.h + +#ifndef ZIP7_INC_COPY_DIALOG_H +#define ZIP7_INC_COPY_DIALOG_H + +#include "../../../Windows/Control/ComboBox.h" +#include "../../../Windows/Control/Dialog.h" + +#include "CopyDialogRes.h" + +const int kCopyDialog_NumInfoLines = 11; + +class CCopyDialog: public NWindows::NControl::CModalDialog +{ + NWindows::NControl::CComboBox _path; + virtual void OnOK() Z7_override; + virtual bool OnInit() Z7_override; + virtual bool OnSize(WPARAM wParam, int xSize, int ySize) Z7_override; + virtual bool OnButtonClicked(unsigned buttonID, HWND buttonHWND) Z7_override; + void OnButtonSetPath(); +public: + UString Title; + UString Static; + UString Value; + UString Info; + UStringVector Strings; + + INT_PTR Create(HWND parentWindow = NULL) { return CModalDialog::Create(IDD_COPY, parentWindow); } +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/CopyDialog.rc b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/CopyDialog.rc new file mode 100644 index 00000000..73d3ea80 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/CopyDialog.rc @@ -0,0 +1,20 @@ +#include "CopyDialogRes.h" +#include "../../GuiCommon.rc" + +#define xc 320 +#define yc 144 + +#define y 40 + +IDD_COPY DIALOG 0, 0, xs, ys MY_MODAL_RESIZE_DIALOG_STYLE MY_FONT +CAPTION "Copy" +{ + LTEXT "", IDT_COPY, m, m, xc, 8 + COMBOBOX IDC_COPY, m, 20, xc - bxsDots - m, 65, MY_COMBO_WITH_EDIT + PUSHBUTTON "...", IDB_COPY_SET_PATH, xs - m - bxsDots, 18, bxsDots, bys, WS_GROUP + LTEXT "", IDT_COPY_INFO, m, y, xc, by - y - 1, SS_NOPREFIX | SS_LEFTNOWORDWRAP + OK_CANCEL +} + +#undef xc +#undef yc diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/CopyDialogRes.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/CopyDialogRes.h new file mode 100644 index 00000000..85f5a39a --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/CopyDialogRes.h @@ -0,0 +1,8 @@ +#define IDD_COPY 96 + +#define IDT_COPY 100 +#define IDC_COPY 101 +#define IDB_COPY_SET_PATH 102 +#define IDT_COPY_INFO 103 + +#define IDS_SET_FOLDER 6007 diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/Delete.bmp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/Delete.bmp new file mode 100644 index 00000000..d1004d82 Binary files /dev/null and b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/Delete.bmp differ diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/Delete2.bmp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/Delete2.bmp new file mode 100644 index 00000000..60e08c6a Binary files /dev/null and b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/Delete2.bmp differ diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/DialogSize.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/DialogSize.h new file mode 100644 index 00000000..9f2270bd --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/DialogSize.h @@ -0,0 +1,16 @@ +// DialogSize.h + +#ifndef ZIP7_INC_DIALOG_SIZE_H +#define ZIP7_INC_DIALOG_SIZE_H + +#include "../../../Windows/Control/Dialog.h" + +#ifdef UNDER_CE +#define BIG_DIALOG_SIZE(x, y) bool isBig = NWindows::NControl::IsDialogSizeOK(x, y); +#define SIZED_DIALOG(big) (isBig ? big : big ## _2) +#else +#define BIG_DIALOG_SIZE(x, y) +#define SIZED_DIALOG(big) big +#endif + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/EditDialog.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/EditDialog.cpp new file mode 100644 index 00000000..e97d9ea5 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/EditDialog.cpp @@ -0,0 +1,57 @@ +// EditDialog.cpp + +#include "StdAfx.h" + +#include "EditDialog.h" + +#ifdef Z7_LANG +#include "LangUtils.h" +#endif + +bool CEditDialog::OnInit() +{ + #ifdef Z7_LANG + LangSetDlgItems(*this, NULL, 0); + #endif + _edit.Attach(GetItem(IDE_EDIT)); + + SetText(Title); + _edit.SetText(Text); + + NormalizeSize(); + return CModalDialog::OnInit(); +} + +// #define MY_CLOSE_BUTTON_ID IDCANCEL +#define MY_CLOSE_BUTTON_ID IDCLOSE + +bool CEditDialog::OnSize(WPARAM /* wParam */, int xSize, int ySize) +{ + int mx, my; + GetMargins(8, mx, my); + int bx1, by; + GetItemSizes(MY_CLOSE_BUTTON_ID, bx1, by); + + // int bx2; + // GetItemSizes(IDOK, bx2, by); + + const int y = ySize - my - by; + const int x = xSize - mx - bx1; + + /* + RECT rect; + GetClientRect(&rect); + rect.top = y - my; + InvalidateRect(&rect); + */ + InvalidateRect(NULL); + + MoveItem(MY_CLOSE_BUTTON_ID, x, y, bx1, by); + // MoveItem(IDOK, x - mx - bx2, y, bx2, by); + /* + if (wParam == SIZE_MAXSHOW || wParam == SIZE_MAXIMIZED || wParam == SIZE_MAXHIDE) + mx = 0; + */ + _edit.Move(mx, my, xSize - mx * 2, y - my * 2); + return false; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/EditDialog.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/EditDialog.h new file mode 100644 index 00000000..6970b14d --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/EditDialog.h @@ -0,0 +1,25 @@ +// EditDialog.h + +#ifndef ZIP7_INC_EDIT_DIALOG_H +#define ZIP7_INC_EDIT_DIALOG_H + +#include "../../../Windows/Control/Dialog.h" +#include "../../../Windows/Control/Edit.h" + +#include "EditDialogRes.h" + +class CEditDialog: public NWindows::NControl::CModalDialog +{ + NWindows::NControl::CEdit _edit; + virtual bool OnInit() Z7_override; + virtual bool OnSize(WPARAM wParam, int xSize, int ySize) Z7_override; +public: + UString Title; + UString Text; + + INT_PTR Create(HWND wndParent = NULL) { return CModalDialog::Create(IDD_EDIT_DLG, wndParent); } + + CEditDialog() {} +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/EditDialog.rc b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/EditDialog.rc new file mode 100644 index 00000000..cdb0b445 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/EditDialog.rc @@ -0,0 +1,15 @@ +#include "EditDialogRes.h" +#include "../../GuiCommon.rc" + +#define xc 320 +#define yc 240 + +IDD_EDIT_DLG DIALOG 0, 0, xs, ys MY_MODAL_RESIZE_DIALOG_STYLE MY_FONT +CAPTION "Edit" +{ + // OK_CANCEL + MY_BUTTON__CLOSE + + EDITTEXT IDE_EDIT, m, m, xc, yc - bys - m, + ES_MULTILINE | ES_READONLY | WS_VSCROLL | WS_HSCROLL | ES_WANTRETURN +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/EditDialogRes.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/EditDialogRes.h new file mode 100644 index 00000000..58c5ca91 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/EditDialogRes.h @@ -0,0 +1,2 @@ +#define IDD_EDIT_DLG 94 +#define IDE_EDIT 100 diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/EditPage.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/EditPage.cpp new file mode 100644 index 00000000..a2a03215 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/EditPage.cpp @@ -0,0 +1,159 @@ +// EditPage.cpp + +#include "StdAfx.h" + +#include "EditPage.h" +#include "EditPageRes.h" + +#include "BrowseDialog.h" +#include "HelpUtils.h" +#include "LangUtils.h" +#include "RegistryUtils.h" + +using namespace NWindows; + +#ifdef Z7_LANG +static const UInt32 kLangIDs[] = +{ + IDT_EDIT_EDITOR, + IDT_EDIT_DIFF +}; + +static const UInt32 kLangIDs_Colon[] = +{ + IDT_EDIT_VIEWER +}; +#endif + +#define kEditTopic "FM/options.htm#editor" + +bool CEditPage::OnInit() +{ + _initMode = true; + + #ifdef Z7_LANG + LangSetDlgItems(*this, kLangIDs, Z7_ARRAY_SIZE(kLangIDs)); + LangSetDlgItems_Colon(*this, kLangIDs_Colon, Z7_ARRAY_SIZE(kLangIDs_Colon)); + #endif + + _ctrls[0].Ctrl = IDE_EDIT_VIEWER; _ctrls[0].Button = IDB_EDIT_VIEWER; + _ctrls[1].Ctrl = IDE_EDIT_EDITOR; _ctrls[1].Button = IDB_EDIT_EDITOR; + _ctrls[2].Ctrl = IDE_EDIT_DIFF; _ctrls[2].Button = IDB_EDIT_DIFF; + + for (unsigned i = 0; i < 3; i++) + { + CEditPageCtrl &c = _ctrls[i]; + c.WasChanged = false; + c.Edit.Attach(GetItem(c.Ctrl)); + UString path; + if (i < 2) + ReadRegEditor(i > 0, path); + else + ReadRegDiff(path); + c.Edit.SetText(path); + } + + _initMode = false; + + return CPropertyPage::OnInit(); +} + +LONG CEditPage::OnApply() +{ + for (unsigned i = 0; i < 3; i++) + { + CEditPageCtrl &c = _ctrls[i]; + if (c.WasChanged) + { + UString path; + c.Edit.GetText(path); + if (i < 2) + SaveRegEditor(i > 0, path); + else + SaveRegDiff(path); + c.WasChanged = false; + } + } + + return PSNRET_NOERROR; +} + +void CEditPage::OnNotifyHelp() +{ + ShowHelpWindow(kEditTopic); +} + +void SplitCmdLineSmart(const UString &cmd, UString &prg, UString ¶ms); + +static void Edit_BrowseForFile(NWindows::NControl::CEdit &edit, HWND hwnd) +{ + UString cmd; + edit.GetText(cmd); + + UString param; + UString prg; + + SplitCmdLineSmart(cmd, prg, param); + + CObjectVector filters; + CBrowseFilterInfo &bfi = filters.AddNew(); + bfi.Description = "*.exe"; + bfi.Masks.Add(UString("*.exe")); + + CBrowseInfo bi; + bi.FilterIndex = 0; + bi.FilePath = prg; + bi.hwndOwner = hwnd; + + if (bi.BrowseForFile(filters)) + { + cmd = bi.FilePath; + cmd.Trim(); + /* + if (!param.IsEmpty() && !resPath.IsEmpty()) + { + cmd.InsertAtFront(L'\"'); + cmd += L'\"'; + cmd.Add_Space(); + cmd += param; + } + */ + + edit.SetText(cmd); + // Changed(); + } +} + +bool CEditPage::OnButtonClicked(unsigned buttonID, HWND buttonHWND) +{ + for (unsigned i = 0; i < 3; i++) + { + CEditPageCtrl &c = _ctrls[i]; + if (buttonID == c.Button) + { + Edit_BrowseForFile(c.Edit, *this); + return true; + } + } + + return CPropertyPage::OnButtonClicked(buttonID, buttonHWND); +} + +bool CEditPage::OnCommand(unsigned code, unsigned itemID, LPARAM param) +{ + if (!_initMode && code == EN_CHANGE) + { + for (unsigned i = 0; i < 3; i++) + { + CEditPageCtrl &c = _ctrls[i]; + if (itemID == c.Ctrl) + { + c.WasChanged = true; + Changed(); + return true; + } + } + } + + return CPropertyPage::OnCommand(code, itemID, param); +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/EditPage.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/EditPage.h new file mode 100644 index 00000000..a70fad7f --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/EditPage.h @@ -0,0 +1,30 @@ +// EditPage.h + +#ifndef ZIP7_INC_EDIT_PAGE_H +#define ZIP7_INC_EDIT_PAGE_H + +#include "../../../Windows/Control/PropertyPage.h" +#include "../../../Windows/Control/Edit.h" + +struct CEditPageCtrl +{ + NWindows::NControl::CEdit Edit; + bool WasChanged; + unsigned Ctrl; + unsigned Button; +}; + +class CEditPage: public NWindows::NControl::CPropertyPage +{ + CEditPageCtrl _ctrls[3]; + + bool _initMode; +public: + virtual bool OnInit() Z7_override; + virtual void OnNotifyHelp() Z7_override; + virtual bool OnCommand(unsigned code, unsigned itemID, LPARAM param) Z7_override; + virtual LONG OnApply() Z7_override; + virtual bool OnButtonClicked(unsigned buttonID, HWND buttonHWND) Z7_override; +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/EditPage.rc b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/EditPage.rc new file mode 100644 index 00000000..56c1ec0a --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/EditPage.rc @@ -0,0 +1,19 @@ +#include "EditPageRes.h" +#include "../../GuiCommon.rc" + +#define xc OPTIONS_PAGE_XC_SIZE +#define yc 100 + +IDD_EDIT MY_PAGE +#include "EditPage2.rc" + +#ifdef UNDER_CE + +#undef xc + +#define xc SMALL_PAGE_SIZE_X + +IDD_EDIT_2 MY_PAGE +#include "EditPage2.rc" + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/EditPage2.rc b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/EditPage2.rc new file mode 100644 index 00000000..2d6554fb --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/EditPage2.rc @@ -0,0 +1,14 @@ +CAPTION "Editor" +{ + LTEXT "&View:", IDT_EDIT_VIEWER, m, m, xc, 8 + EDITTEXT IDE_EDIT_VIEWER, m, m + 12, xc - m - bxsDots, 14, ES_AUTOHSCROLL + PUSHBUTTON "...", IDB_EDIT_VIEWER, xs - m - bxsDots, m + 11, bxsDots, bys + + LTEXT "&Editor:", IDT_EDIT_EDITOR, m, m + 32, xc, 8 + EDITTEXT IDE_EDIT_EDITOR, m, m + 44, xc - m - bxsDots, 14, ES_AUTOHSCROLL + PUSHBUTTON "...", IDB_EDIT_EDITOR, xs - m - bxsDots, m + 43, bxsDots, bys + + LTEXT "&Diff:", IDT_EDIT_DIFF, m, m + 64, xc, 8 + EDITTEXT IDE_EDIT_DIFF, m, m + 76, xc - m - bxsDots, 14, ES_AUTOHSCROLL + PUSHBUTTON "...", IDB_EDIT_DIFF, xs - m - bxsDots, m + 75, bxsDots, bys +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/EditPageRes.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/EditPageRes.h new file mode 100644 index 00000000..017d7024 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/EditPageRes.h @@ -0,0 +1,15 @@ +#define IDD_EDIT 2103 +#define IDD_EDIT_2 12103 + +#define IDT_EDIT_VIEWER 543 +#define IDT_EDIT_EDITOR 2104 +#define IDT_EDIT_DIFF 2105 + +#define IDE_EDIT_VIEWER 100 +#define IDB_EDIT_VIEWER 101 + +#define IDE_EDIT_EDITOR 102 +#define IDB_EDIT_EDITOR 103 + +#define IDE_EDIT_DIFF 104 +#define IDB_EDIT_DIFF 105 diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/EnumFormatEtc.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/EnumFormatEtc.cpp new file mode 100644 index 00000000..fc2fd6c4 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/EnumFormatEtc.cpp @@ -0,0 +1,107 @@ +// EnumFormatEtc.cpp + +#include "StdAfx.h" + +#include "EnumFormatEtc.h" +#include "../../IDecl.h" +#include "MyCom2.h" + +class CEnumFormatEtc Z7_final: + public IEnumFORMATETC, + public CMyUnknownImp +{ + Z7_COM_UNKNOWN_IMP_1_MT(IEnumFORMATETC) + + STDMETHOD(Next)(ULONG celt, FORMATETC *rgelt, ULONG *pceltFetched) Z7_override; + STDMETHOD(Skip)(ULONG celt) Z7_override; + STDMETHOD(Reset)(void) Z7_override; + STDMETHOD(Clone)(IEnumFORMATETC **ppEnumFormatEtc) Z7_override; + + LONG m_RefCount; + ULONG m_NumFormats; + FORMATETC *m_Formats; + ULONG m_Index; +public: + CEnumFormatEtc(const FORMATETC *pFormatEtc, ULONG numFormats); + ~CEnumFormatEtc(); +}; + +static void DeepCopyFormatEtc(FORMATETC *dest, const FORMATETC *src) +{ + *dest = *src; + if (src->ptd) + { + dest->ptd = (DVTARGETDEVICE*)CoTaskMemAlloc(sizeof(DVTARGETDEVICE)); + *(dest->ptd) = *(src->ptd); + } +} + +CEnumFormatEtc::CEnumFormatEtc(const FORMATETC *pFormatEtc, ULONG numFormats) +{ + m_RefCount = 1; + m_Index = 0; + m_NumFormats = 0; + m_Formats = new FORMATETC[numFormats]; + // if (m_Formats) + { + m_NumFormats = numFormats; + for (ULONG i = 0; i < numFormats; i++) + DeepCopyFormatEtc(&m_Formats[i], &pFormatEtc[i]); + } +} + +CEnumFormatEtc::~CEnumFormatEtc() +{ + if (m_Formats) + { + for (ULONG i = 0; i < m_NumFormats; i++) + if (m_Formats[i].ptd) + CoTaskMemFree(m_Formats[i].ptd); + delete []m_Formats; + } +} + +Z7_COMWF_B CEnumFormatEtc::Next(ULONG celt, FORMATETC *pFormatEtc, ULONG *pceltFetched) +{ + ULONG copied = 0; + if (celt == 0 || !pFormatEtc) + return E_INVALIDARG; + while (m_Index < m_NumFormats && copied < celt) + { + DeepCopyFormatEtc(&pFormatEtc[copied], &m_Formats[m_Index]); + copied++; + m_Index++; + } + if (pceltFetched) + *pceltFetched = copied; + return (copied == celt) ? S_OK : S_FALSE; +} + +Z7_COMWF_B CEnumFormatEtc::Skip(ULONG celt) +{ + m_Index += celt; + return (m_Index <= m_NumFormats) ? S_OK : S_FALSE; +} + +Z7_COMWF_B CEnumFormatEtc::Reset(void) +{ + m_Index = 0; + return S_OK; +} + +Z7_COMWF_B CEnumFormatEtc::Clone(IEnumFORMATETC ** ppEnumFormatEtc) +{ + HRESULT hResult = CreateEnumFormatEtc(m_NumFormats, m_Formats, ppEnumFormatEtc); + if (hResult == S_OK) + ((CEnumFormatEtc *)*ppEnumFormatEtc)->m_Index = m_Index; + return hResult; +} + +// replacement for SHCreateStdEnumFmtEtc +HRESULT CreateEnumFormatEtc(UINT numFormats, const FORMATETC *formats, IEnumFORMATETC **enumFormat) +{ + if (numFormats == 0 || !formats || !enumFormat) + return E_INVALIDARG; + *enumFormat = new CEnumFormatEtc(formats, numFormats); + return (*enumFormat) ? S_OK : E_OUTOFMEMORY; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/EnumFormatEtc.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/EnumFormatEtc.h new file mode 100644 index 00000000..12df225d --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/EnumFormatEtc.h @@ -0,0 +1,10 @@ +// EnumFormatEtc.h + +#ifndef ZIP7_INC_ENUMFORMATETC_H +#define ZIP7_INC_ENUMFORMATETC_H + +#include "../../../Common/MyWindows.h" + +HRESULT CreateEnumFormatEtc(UINT numFormats, const FORMATETC *formats, IEnumFORMATETC **enumFormat); + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/Extract.bmp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/Extract.bmp new file mode 100644 index 00000000..0aeba923 Binary files /dev/null and b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/Extract.bmp differ diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/Extract2.bmp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/Extract2.bmp new file mode 100644 index 00000000..a7e57753 Binary files /dev/null and b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/Extract2.bmp differ diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ExtractCallback.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ExtractCallback.cpp new file mode 100644 index 00000000..da25969c --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ExtractCallback.cpp @@ -0,0 +1,1272 @@ +// ExtractCallback.cpp + +#include "StdAfx.h" + +#include "../../../Common/ComTry.h" +#include "../../../Common/IntToString.h" +#include "../../../Common/Lang.h" +#include "../../../Common/StringConvert.h" + +#include "../../../Windows/ErrorMsg.h" +#include "../../../Windows/FileDir.h" +#include "../../../Windows/FileFind.h" +#include "../../../Windows/PropVariantConv.h" + +#include "../../Common/FilePathAutoRename.h" +#include "../../Common/StreamUtils.h" +#include "../Common/ExtractingFilePath.h" + +#ifndef Z7_SFX +#include "../Common/ZipRegistry.h" +#endif + +#include "../GUI/ExtractRes.h" +#include "resourceGui.h" + +#include "ExtractCallback.h" +#include "FormatUtils.h" +#include "LangUtils.h" +#include "MemDialog.h" +#include "OverwriteDialog.h" +#ifndef Z7_NO_CRYPTO +#include "PasswordDialog.h" +#endif +#include "PropertyName.h" + +using namespace NWindows; +using namespace NFile; +using namespace NFind; + +extern bool g_DisableUserQuestions; + +CExtractCallbackImp::~CExtractCallbackImp() {} + +void CExtractCallbackImp::Init() +{ + LangString(IDS_PROGRESS_EXTRACTING, _lang_Extracting); + LangString(IDS_PROGRESS_TESTING, _lang_Testing); + LangString(IDS_PROGRESS_SKIPPING, _lang_Skipping); + _lang_Reading = "Reading"; + + NumArchiveErrors = 0; + ThereAreMessageErrors = false; + #ifndef Z7_SFX + NumFolders = NumFiles = 0; + NeedAddFile = false; + #endif +} + +void CExtractCallbackImp::AddError_Message(LPCWSTR s) +{ + ThereAreMessageErrors = true; + ProgressDialog->Sync.AddError_Message(s); +} + +void CExtractCallbackImp::AddError_Message_ShowArcPath(LPCWSTR s) +{ + Add_ArchiveName_Error(); + AddError_Message(s); +} + + +#ifndef Z7_SFX + +Z7_COM7F_IMF(CExtractCallbackImp::SetNumFiles(UInt64 numFiles)) +{ + #ifdef Z7_SFX + UNUSED_VAR(numFiles) + #else + ProgressDialog->Sync.Set_NumFilesTotal(numFiles); + #endif + return S_OK; +} + +#endif + +Z7_COM7F_IMF(CExtractCallbackImp::SetTotal(UInt64 total)) +{ + ProgressDialog->Sync.Set_NumBytesTotal(total); + return S_OK; +} + +Z7_COM7F_IMF(CExtractCallbackImp::SetCompleted(const UInt64 *value)) +{ + return ProgressDialog->Sync.Set_NumBytesCur(value); +} + +HRESULT CExtractCallbackImp::Open_CheckBreak() +{ + return ProgressDialog->Sync.CheckStop(); +} + +HRESULT CExtractCallbackImp::Open_SetTotal(const UInt64 *files, const UInt64 *bytes) +{ + HRESULT res = S_OK; + if (!MultiArcMode) + { + if (files) + { + _totalFiles_Defined = true; + // res = ProgressDialog->Sync.Set_NumFilesTotal(*files); + } + else + _totalFiles_Defined = false; + + if (bytes) + { + _totalBytes_Defined = true; + ProgressDialog->Sync.Set_NumBytesTotal(*bytes); + } + else + _totalBytes_Defined = false; + } + + return res; +} + +HRESULT CExtractCallbackImp::Open_SetCompleted(const UInt64 *files, const UInt64 *bytes) +{ + if (!MultiArcMode) + { + if (files) + { + ProgressDialog->Sync.Set_NumFilesCur(*files); + } + + if (bytes) + { + } + } + + return ProgressDialog->Sync.CheckStop(); +} + +HRESULT CExtractCallbackImp::Open_Finished() +{ + return ProgressDialog->Sync.CheckStop(); +} + +#ifndef Z7_NO_CRYPTO + +HRESULT CExtractCallbackImp::Open_CryptoGetTextPassword(BSTR *password) +{ + return CryptoGetTextPassword(password); +} + +/* +HRESULT CExtractCallbackImp::Open_GetPasswordIfAny(bool &passwordIsDefined, UString &password) +{ + passwordIsDefined = PasswordIsDefined; + password = Password; + return S_OK; +} + +bool CExtractCallbackImp::Open_WasPasswordAsked() +{ + return PasswordWasAsked; +} + +void CExtractCallbackImp::Open_Clear_PasswordWasAsked_Flag() +{ + PasswordWasAsked = false; +} +*/ + +#endif + + +#ifndef Z7_SFX +Z7_COM7F_IMF(CExtractCallbackImp::SetRatioInfo(const UInt64 *inSize, const UInt64 *outSize)) +{ + ProgressDialog->Sync.Set_Ratio(inSize, outSize); + return S_OK; +} +#endif + +/* +Z7_COM7F_IMF(CExtractCallbackImp::SetTotalFiles(UInt64 total) +{ + ProgressDialog->Sync.SetNumFilesTotal(total); + return S_OK; +} + +Z7_COM7F_IMF(CExtractCallbackImp::SetCompletedFiles(const UInt64 *value) +{ + if (value != NULL) + ProgressDialog->Sync.SetNumFilesCur(*value); + return S_OK; +} +*/ + +Z7_COM7F_IMF(CExtractCallbackImp::AskOverwrite( + const wchar_t *existName, const FILETIME *existTime, const UInt64 *existSize, + const wchar_t *newName, const FILETIME *newTime, const UInt64 *newSize, + Int32 *answer)) +{ + COverwriteDialog dialog; + + dialog.OldFileInfo.SetTime2(existTime); + dialog.OldFileInfo.SetSize2(existSize); + dialog.OldFileInfo.Path = existName; + dialog.OldFileInfo.Is_FileSystemFile = true; + + dialog.NewFileInfo.SetTime2(newTime); + dialog.NewFileInfo.SetSize2(newSize); + dialog.NewFileInfo.Path = newName; + dialog.NewFileInfo.Is_FileSystemFile = Src_Is_IO_FS_Folder; + + ProgressDialog->WaitCreating(); + const INT_PTR writeAnswer = dialog.Create(*ProgressDialog); + + switch (writeAnswer) + { + case IDCANCEL: *answer = NOverwriteAnswer::kCancel; return E_ABORT; + case IDYES: *answer = NOverwriteAnswer::kYes; break; + case IDNO: *answer = NOverwriteAnswer::kNo; break; + case IDB_YES_TO_ALL: *answer = NOverwriteAnswer::kYesToAll; break; + case IDB_NO_TO_ALL: *answer = NOverwriteAnswer::kNoToAll; break; + case IDB_AUTO_RENAME: *answer = NOverwriteAnswer::kAutoRename; break; + default: return E_FAIL; + } + return S_OK; +} + + +Z7_COM7F_IMF(CExtractCallbackImp::PrepareOperation(const wchar_t *name, Int32 isFolder, Int32 askExtractMode, const UInt64 * /* position */)) +{ + _isFolder = IntToBool(isFolder); + _currentFilePath = name; + + const UString *msg = &_lang_Empty; + switch (askExtractMode) + { + case NArchive::NExtract::NAskMode::kExtract: msg = &_lang_Extracting; break; + case NArchive::NExtract::NAskMode::kTest: msg = &_lang_Testing; break; + case NArchive::NExtract::NAskMode::kSkip: msg = &_lang_Skipping; break; + case NArchive::NExtract::NAskMode::kReadExternal: msg = &_lang_Reading; break; + // default: s = "Unknown operation"; + } + + return ProgressDialog->Sync.Set_Status2(*msg, name, IntToBool(isFolder)); +} + +Z7_COM7F_IMF(CExtractCallbackImp::MessageError(const wchar_t *s)) +{ + AddError_Message(s); + return S_OK; +} + +HRESULT CExtractCallbackImp::MessageError(const char *message, const FString &path) +{ + ThereAreMessageErrors = true; + ProgressDialog->Sync.AddError_Message_Name(GetUnicodeString(message), fs2us(path)); + return S_OK; +} + +#ifndef Z7_SFX + +Z7_COM7F_IMF(CExtractCallbackImp::ShowMessage(const wchar_t *s)) +{ + AddError_Message(s); + return S_OK; +} + +#endif + +void SetExtractErrorMessage(Int32 opRes, Int32 encrypted, const wchar_t *fileName, UString &s); +void SetExtractErrorMessage(Int32 opRes, Int32 encrypted, const wchar_t *fileName, UString &s) +{ + s.Empty(); + + if (opRes == NArchive::NExtract::NOperationResult::kOK) + return; + + #ifndef Z7_SFX + UINT messageID = 0; + #endif + UINT id = 0; + + switch (opRes) + { + case NArchive::NExtract::NOperationResult::kUnsupportedMethod: + #ifndef Z7_SFX + messageID = IDS_EXTRACT_MESSAGE_UNSUPPORTED_METHOD; + #endif + id = IDS_EXTRACT_MSG_UNSUPPORTED_METHOD; + break; + case NArchive::NExtract::NOperationResult::kDataError: + #ifndef Z7_SFX + messageID = encrypted ? + IDS_EXTRACT_MESSAGE_DATA_ERROR_ENCRYPTED: + IDS_EXTRACT_MESSAGE_DATA_ERROR; + #endif + id = IDS_EXTRACT_MSG_DATA_ERROR; + break; + case NArchive::NExtract::NOperationResult::kCRCError: + #ifndef Z7_SFX + messageID = encrypted ? + IDS_EXTRACT_MESSAGE_CRC_ERROR_ENCRYPTED: + IDS_EXTRACT_MESSAGE_CRC_ERROR; + #endif + id = IDS_EXTRACT_MSG_CRC_ERROR; + break; + case NArchive::NExtract::NOperationResult::kUnavailable: + id = IDS_EXTRACT_MSG_UNAVAILABLE_DATA; + break; + case NArchive::NExtract::NOperationResult::kUnexpectedEnd: + id = IDS_EXTRACT_MSG_UEXPECTED_END; + break; + case NArchive::NExtract::NOperationResult::kDataAfterEnd: + id = IDS_EXTRACT_MSG_DATA_AFTER_END; + break; + case NArchive::NExtract::NOperationResult::kIsNotArc: + id = IDS_EXTRACT_MSG_IS_NOT_ARC; + break; + case NArchive::NExtract::NOperationResult::kHeadersError: + id = IDS_EXTRACT_MSG_HEADERS_ERROR; + break; + case NArchive::NExtract::NOperationResult::kWrongPassword: + id = IDS_EXTRACT_MSG_WRONG_PSW_CLAIM; + break; + /* + default: + messageID = IDS_EXTRACT_MESSAGE_UNKNOWN_ERROR; + break; + */ + } + + UString msg; + + #ifndef Z7_SFX + UString msgOld; + #ifdef Z7_LANG + if (id != 0) + LangString_OnlyFromLangFile(id, msg); + if (messageID != 0 && msg.IsEmpty()) + LangString_OnlyFromLangFile(messageID, msgOld); + #endif + if (msg.IsEmpty() && !msgOld.IsEmpty()) + s = MyFormatNew(msgOld, fileName); + else + #endif + { + if (msg.IsEmpty() && id != 0) + LangString(id, msg); + if (!msg.IsEmpty()) + s += msg; + else + { + s += "Error #"; + s.Add_UInt32((UInt32)opRes); + } + + if (encrypted && opRes != NArchive::NExtract::NOperationResult::kWrongPassword) + { + // s += " : "; + // AddLangString(s, IDS_EXTRACT_MSG_ENCRYPTED); + s += " : "; + AddLangString(s, IDS_EXTRACT_MSG_WRONG_PSW_GUESS); + } + s += " : "; + s += fileName; + } +} + +Z7_COM7F_IMF(CExtractCallbackImp::SetOperationResult(Int32 opRes, Int32 encrypted)) +{ + switch (opRes) + { + case NArchive::NExtract::NOperationResult::kOK: + break; + default: + { + UString s; + SetExtractErrorMessage(opRes, encrypted, _currentFilePath, s); + AddError_Message_ShowArcPath(s); + } + } + + _currentFilePath.Empty(); + #ifndef Z7_SFX + if (_isFolder) + NumFolders++; + else + NumFiles++; + ProgressDialog->Sync.Set_NumFilesCur(NumFiles); + #endif + + return S_OK; +} + +Z7_COM7F_IMF(CExtractCallbackImp::ReportExtractResult(Int32 opRes, Int32 encrypted, const wchar_t *name)) +{ + if (opRes != NArchive::NExtract::NOperationResult::kOK) + { + UString s; + SetExtractErrorMessage(opRes, encrypted, name, s); + AddError_Message_ShowArcPath(s); + } + return S_OK; +} + +//////////////////////////////////////// +// IExtractCallbackUI + +HRESULT CExtractCallbackImp::BeforeOpen(const wchar_t *name, bool /* testMode */) +{ + _currentArchivePath = name; + _needWriteArchivePath = true; + #ifndef Z7_SFX + RINOK(ProgressDialog->Sync.CheckStop()) + ProgressDialog->Sync.Set_TitleFileName(name); + #endif + return S_OK; +} + +HRESULT CExtractCallbackImp::SetCurrentFilePath2(const wchar_t *path) +{ + _currentFilePath = path; + #ifndef Z7_SFX + ProgressDialog->Sync.Set_FilePath(path); + #endif + return S_OK; +} + +#ifndef Z7_SFX + +Z7_COM7F_IMF(CExtractCallbackImp::SetCurrentFilePath(const wchar_t *path)) +{ + #ifndef Z7_SFX + if (NeedAddFile) + NumFiles++; + NeedAddFile = true; + ProgressDialog->Sync.Set_NumFilesCur(NumFiles); + #endif + return SetCurrentFilePath2(path); +} + +#endif + +UString HResultToMessage(HRESULT errorCode); + +static const UInt32 k_ErrorFlagsIds[] = +{ + IDS_EXTRACT_MSG_IS_NOT_ARC, + IDS_EXTRACT_MSG_HEADERS_ERROR, + IDS_EXTRACT_MSG_HEADERS_ERROR, + IDS_OPEN_MSG_UNAVAILABLE_START, + IDS_OPEN_MSG_UNCONFIRMED_START, + IDS_EXTRACT_MSG_UEXPECTED_END, + IDS_EXTRACT_MSG_DATA_AFTER_END, + IDS_EXTRACT_MSG_UNSUPPORTED_METHOD, + IDS_OPEN_MSG_UNSUPPORTED_FEATURE, + IDS_EXTRACT_MSG_DATA_ERROR, + IDS_EXTRACT_MSG_CRC_ERROR +}; + +static void AddNewLineString(UString &s, const UString &m) +{ + s += m; + s.Add_LF(); +} + +UString GetOpenArcErrorMessage(UInt32 errorFlags); +UString GetOpenArcErrorMessage(UInt32 errorFlags) +{ + UString s; + + for (unsigned i = 0; i < Z7_ARRAY_SIZE(k_ErrorFlagsIds); i++) + { + const UInt32 f = (UInt32)1 << i; + if ((errorFlags & f) == 0) + continue; + const UInt32 id = k_ErrorFlagsIds[i]; + UString m = LangString(id); + if (m.IsEmpty()) + continue; + if (f == kpv_ErrorFlags_EncryptedHeadersError) + { + m += " : "; + AddLangString(m, IDS_EXTRACT_MSG_WRONG_PSW_GUESS); + } + if (!s.IsEmpty()) + s.Add_LF(); + s += m; + errorFlags &= ~f; + } + + if (errorFlags != 0) + { + char sz[16]; + sz[0] = '0'; + sz[1] = 'x'; + ConvertUInt32ToHex(errorFlags, sz + 2); + if (!s.IsEmpty()) + s.Add_LF(); + s += sz; + } + + return s; +} + +static void ErrorInfo_Print(UString &s, const CArcErrorInfo &er) +{ + const UInt32 errorFlags = er.GetErrorFlags(); + const UInt32 warningFlags = er.GetWarningFlags(); + + if (errorFlags != 0) + AddNewLineString(s, GetOpenArcErrorMessage(errorFlags)); + + if (!er.ErrorMessage.IsEmpty()) + AddNewLineString(s, er.ErrorMessage); + + if (warningFlags != 0) + { + s += GetNameOfProperty(kpidWarningFlags, L"Warnings"); + s.Add_Colon(); + s.Add_LF(); + AddNewLineString(s, GetOpenArcErrorMessage(warningFlags)); + } + + if (!er.WarningMessage.IsEmpty()) + { + s += GetNameOfProperty(kpidWarning, L"Warning"); + s += ": "; + s += er.WarningMessage; + s.Add_LF(); + } +} + +static UString GetBracedType(const wchar_t *type) +{ + UString s ('['); + s += type; + s.Add_Char(']'); + return s; +} + +void OpenResult_GUI(UString &s, const CCodecs *codecs, const CArchiveLink &arcLink, const wchar_t *name, HRESULT result); +void OpenResult_GUI(UString &s, const CCodecs *codecs, const CArchiveLink &arcLink, const wchar_t *name, HRESULT result) +{ + FOR_VECTOR (level, arcLink.Arcs) + { + const CArc &arc = arcLink.Arcs[level]; + const CArcErrorInfo &er = arc.ErrorInfo; + + if (!er.IsThereErrorOrWarning() && er.ErrorFormatIndex < 0) + continue; + + if (s.IsEmpty()) + { + s += name; + s.Add_LF(); + } + + if (level != 0) + { + AddNewLineString(s, arc.Path); + } + + ErrorInfo_Print(s, er); + + if (er.ErrorFormatIndex >= 0) + { + AddNewLineString(s, GetNameOfProperty(kpidWarning, L"Warning")); + if (arc.FormatIndex == er.ErrorFormatIndex) + { + AddNewLineString(s, LangString(IDS_IS_OPEN_WITH_OFFSET)); + } + else + { + AddNewLineString(s, MyFormatNew(IDS_CANT_OPEN_AS_TYPE, GetBracedType(codecs->GetFormatNamePtr(er.ErrorFormatIndex)))); + AddNewLineString(s, MyFormatNew(IDS_IS_OPEN_AS_TYPE, GetBracedType(codecs->GetFormatNamePtr(arc.FormatIndex)))); + } + } + } + + if (arcLink.NonOpen_ErrorInfo.ErrorFormatIndex >= 0 || result != S_OK) + { + s += name; + s.Add_LF(); + if (!arcLink.Arcs.IsEmpty()) + AddNewLineString(s, arcLink.NonOpen_ArcPath); + + if (arcLink.NonOpen_ErrorInfo.ErrorFormatIndex >= 0 || result == S_FALSE) + { + UINT id = IDS_CANT_OPEN_ARCHIVE; + UString param; + if (arcLink.PasswordWasAsked) + id = IDS_CANT_OPEN_ENCRYPTED_ARCHIVE; + else if (arcLink.NonOpen_ErrorInfo.ErrorFormatIndex >= 0) + { + id = IDS_CANT_OPEN_AS_TYPE; + param = GetBracedType(codecs->GetFormatNamePtr(arcLink.NonOpen_ErrorInfo.ErrorFormatIndex)); + } + UString s2 = MyFormatNew(id, param); + s2.Replace(L" ''", L""); + s2.Replace(L"''", L""); + s += s2; + } + else + s += HResultToMessage(result); + + s.Add_LF(); + ErrorInfo_Print(s, arcLink.NonOpen_ErrorInfo); + } + + if (!s.IsEmpty() && s.Back() == '\n') + s.DeleteBack(); +} + +HRESULT CExtractCallbackImp::OpenResult(const CCodecs *codecs, const CArchiveLink &arcLink, const wchar_t *name, HRESULT result) +{ + _currentArchivePath = name; + _needWriteArchivePath = true; + + UString s; + OpenResult_GUI(s, codecs, arcLink, name, result); + if (!s.IsEmpty()) + { + NumArchiveErrors++; + AddError_Message(s); + _needWriteArchivePath = false; + } + + return S_OK; +} + +HRESULT CExtractCallbackImp::ThereAreNoFiles() +{ + return S_OK; +} + +void CExtractCallbackImp::Add_ArchiveName_Error() +{ + if (_needWriteArchivePath) + { + if (!_currentArchivePath.IsEmpty()) + AddError_Message(_currentArchivePath); + _needWriteArchivePath = false; + } +} + +HRESULT CExtractCallbackImp::ExtractResult(HRESULT result) +{ + #ifndef Z7_SFX + ProgressDialog->Sync.Set_FilePath(L""); + #endif + + if (result == S_OK) + return result; + NumArchiveErrors++; + if (result == E_ABORT + || result == HRESULT_FROM_WIN32(ERROR_DISK_FULL) + ) + return result; + + Add_ArchiveName_Error(); + if (!_currentFilePath.IsEmpty()) + MessageError(_currentFilePath); + MessageError(NError::MyFormatMessage(result)); + return S_OK; +} + +#ifndef Z7_NO_CRYPTO + +HRESULT CExtractCallbackImp::SetPassword(const UString &password) +{ + PasswordIsDefined = true; + Password = password; + return S_OK; +} + +Z7_COM7F_IMF(CExtractCallbackImp::CryptoGetTextPassword(BSTR *password)) +{ + PasswordWasAsked = true; + if (!PasswordIsDefined) + { + CPasswordDialog dialog; + #ifndef Z7_SFX + const bool showPassword = NExtract::Read_ShowPassword(); + dialog.ShowPassword = showPassword; + #endif + ProgressDialog->WaitCreating(); + if (dialog.Create(*ProgressDialog) != IDOK) + return E_ABORT; + Password = dialog.Password; + PasswordIsDefined = true; + #ifndef Z7_SFX + if (dialog.ShowPassword != showPassword) + NExtract::Save_ShowPassword(dialog.ShowPassword); + #endif + } + return StringToBstr(Password, password); +} + +#endif + +#ifndef Z7_SFX + +Z7_COM7F_IMF(CExtractCallbackImp::AskWrite( + const wchar_t *srcPath, Int32 srcIsFolder, + const FILETIME *srcTime, const UInt64 *srcSize, + const wchar_t *destPath, + BSTR *destPathResult, + Int32 *writeAnswer)) +{ + UString destPathResultTemp = destPath; + + // RINOK(StringToBstr(destPath, destPathResult)); + + *destPathResult = NULL; + *writeAnswer = BoolToInt(false); + + FString destPathSys = us2fs(destPath); + const bool srcIsFolderSpec = IntToBool(srcIsFolder); + CFileInfo destFileInfo; + + if (destFileInfo.Find(destPathSys)) + { + if (srcIsFolderSpec) + { + if (!destFileInfo.IsDir()) + { + RINOK(MessageError("Cannot replace file with folder with same name", destPathSys)) + return E_ABORT; + } + *writeAnswer = BoolToInt(false); + return S_OK; + } + + if (destFileInfo.IsDir()) + { + RINOK(MessageError("Cannot replace folder with file with same name", destPathSys)) + *writeAnswer = BoolToInt(false); + return S_OK; + } + + switch ((int)OverwriteMode) + { + case NExtract::NOverwriteMode::kSkip: + return S_OK; + case NExtract::NOverwriteMode::kAsk: + { + Int32 overwriteResult; + UString destPathSpec = destPath; + const int slashPos = destPathSpec.ReverseFind_PathSepar(); + destPathSpec.DeleteFrom((unsigned)(slashPos + 1)); + destPathSpec += fs2us(destFileInfo.Name); + + RINOK(AskOverwrite( + destPathSpec, + &destFileInfo.MTime, &destFileInfo.Size, + srcPath, + srcTime, srcSize, + &overwriteResult)) + + switch (overwriteResult) + { + case NOverwriteAnswer::kCancel: return E_ABORT; + case NOverwriteAnswer::kNo: return S_OK; + case NOverwriteAnswer::kNoToAll: OverwriteMode = NExtract::NOverwriteMode::kSkip; return S_OK; + case NOverwriteAnswer::kYes: break; + case NOverwriteAnswer::kYesToAll: OverwriteMode = NExtract::NOverwriteMode::kOverwrite; break; + case NOverwriteAnswer::kAutoRename: OverwriteMode = NExtract::NOverwriteMode::kRename; break; + default: + return E_FAIL; + } + break; + } + default: + break; + } + + if (OverwriteMode == NExtract::NOverwriteMode::kRename) + { + if (!AutoRenamePath(destPathSys)) + { + RINOK(MessageError("Cannot create name for file", destPathSys)) + return E_ABORT; + } + destPathResultTemp = fs2us(destPathSys); + } + else + { + if (NFind::DoesFileExist_Raw(destPathSys)) + if (!NDir::DeleteFileAlways(destPathSys)) + if (GetLastError() != ERROR_FILE_NOT_FOUND) + { + RINOK(MessageError("Cannot delete output file", destPathSys)) + return E_ABORT; + } + } + } + *writeAnswer = BoolToInt(true); + return StringToBstr(destPathResultTemp, destPathResult); +} + + +Z7_COM7F_IMF(CExtractCallbackImp::UseExtractToStream(Int32 *res)) +{ + *res = BoolToInt(StreamMode); + return S_OK; +} + +static HRESULT GetTime(IGetProp *getProp, PROPID propID, FILETIME &ft, bool &ftDefined) +{ + ftDefined = false; + NCOM::CPropVariant prop; + RINOK(getProp->GetProp(propID, &prop)) + if (prop.vt == VT_FILETIME) + { + ft = prop.filetime; + ftDefined = (ft.dwHighDateTime != 0 || ft.dwLowDateTime != 0); + } + else if (prop.vt != VT_EMPTY) + return E_FAIL; + return S_OK; +} + + +static HRESULT GetItemBoolProp(IGetProp *getProp, PROPID propID, bool &result) +{ + NCOM::CPropVariant prop; + result = false; + RINOK(getProp->GetProp(propID, &prop)) + if (prop.vt == VT_BOOL) + result = VARIANT_BOOLToBool(prop.boolVal); + else if (prop.vt != VT_EMPTY) + return E_FAIL; + return S_OK; +} + + +Z7_COM7F_IMF(CExtractCallbackImp::GetStream7(const wchar_t *name, + Int32 isDir, + ISequentialOutStream **outStream, Int32 askExtractMode, + IGetProp *getProp)) +{ + COM_TRY_BEGIN + *outStream = NULL; + _newVirtFileWasAdded = false; + _hashStream_WasUsed = false; + _needUpdateStat = false; + _isFolder = IntToBool(isDir); + _curSize_Defined = false; + _curSize = 0; + + if (_hashStream) + _hashStream->ReleaseStream(); + + _filePath = name; + + UInt64 size = 0; + bool size_Defined; + { + NCOM::CPropVariant prop; + RINOK(getProp->GetProp(kpidSize, &prop)) + size_Defined = ConvertPropVariantToUInt64(prop, size); + } + if (size_Defined) + { + _curSize = size; + _curSize_Defined = true; + } + + GetItemBoolProp(getProp, kpidIsAltStream, _isAltStream); + if (!ProcessAltStreams && _isAltStream) + return S_OK; + + if (isDir) // we don't support dir items extraction in this code + return S_OK; + + if (askExtractMode != NArchive::NExtract::NAskMode::kExtract && + askExtractMode != NArchive::NExtract::NAskMode::kTest) + return S_OK; + + _needUpdateStat = true; + + CMyComPtr outStreamLoc; + + if (VirtFileSystem && askExtractMode == NArchive::NExtract::NAskMode::kExtract) + { + if (!VirtFileSystemSpec->Files.IsEmpty()) + VirtFileSystemSpec->MaxTotalAllocSize -= VirtFileSystemSpec->Files.Back().Data.Size(); + CVirtFile &file = VirtFileSystemSpec->Files.AddNew(); + _newVirtFileWasAdded = true; + // file.IsDir = _isFolder; + file.IsAltStream = _isAltStream; + file.WrittenSize = 0; + file.ExpectedSize = 0; + if (size_Defined) + file.ExpectedSize = size; + + if (VirtFileSystemSpec->Index_of_MainExtractedFile_in_Files < 0) + if (!file.IsAltStream || VirtFileSystemSpec->IsAltStreamFile) + VirtFileSystemSpec->Index_of_MainExtractedFile_in_Files = + (int)(VirtFileSystemSpec->Files.Size() - 1); + + /* if we open only AltStream, then (name) contains only name without "fileName:" prefix */ + file.BaseName = name; + + if (file.IsAltStream + && !VirtFileSystemSpec->IsAltStreamFile + && file.BaseName.IsPrefixedBy_NoCase(VirtFileSystemSpec->FileName)) + { + const unsigned colonPos = VirtFileSystemSpec->FileName.Len(); + if (file.BaseName[colonPos] == ':') + { + file.ColonWasUsed = true; + file.AltStreamName = name + (size_t)colonPos + 1; + file.BaseName.DeleteFrom(colonPos); + if (Is_ZoneId_StreamName(file.AltStreamName)) + { + if (VirtFileSystemSpec->Index_of_ZoneBuf_AltStream_in_Files < 0) + VirtFileSystemSpec->Index_of_ZoneBuf_AltStream_in_Files = + (int)(VirtFileSystemSpec->Files.Size() - 1); + } + } + } + RINOK(GetTime(getProp, kpidCTime, file.CTime, file.CTime_Defined)) + RINOK(GetTime(getProp, kpidATime, file.ATime, file.ATime_Defined)) + RINOK(GetTime(getProp, kpidMTime, file.MTime, file.MTime_Defined)) + { + NCOM::CPropVariant prop; + RINOK(getProp->GetProp(kpidAttrib, &prop)) + if (prop.vt == VT_UI4) + { + file.Attrib = prop.ulVal; + file.Attrib_Defined = true; + } + } + outStreamLoc = VirtFileSystem; + } + + if (_hashStream) + { + _hashStream->SetStream(outStreamLoc); + outStreamLoc = _hashStream; + _hashStream->Init(true); + _hashStream_WasUsed = true; + } + + if (outStreamLoc) + *outStream = outStreamLoc.Detach(); + return S_OK; + COM_TRY_END +} + +Z7_COM7F_IMF(CExtractCallbackImp::PrepareOperation7(Int32 askExtractMode)) +{ + COM_TRY_BEGIN + _needUpdateStat = ( + askExtractMode == NArchive::NExtract::NAskMode::kExtract + || askExtractMode == NArchive::NExtract::NAskMode::kTest + || askExtractMode == NArchive::NExtract::NAskMode::kReadExternal + ); + + /* + _extractMode = false; + switch (askExtractMode) + { + case NArchive::NExtract::NAskMode::kExtract: + if (_testMode) + askExtractMode = NArchive::NExtract::NAskMode::kTest; + else + _extractMode = true; + break; + }; + */ + return SetCurrentFilePath2(_filePath); + COM_TRY_END +} + +Z7_COM7F_IMF(CExtractCallbackImp::SetOperationResult8(Int32 opRes, Int32 encrypted, UInt64 size)) +{ + COM_TRY_BEGIN + if (VirtFileSystem && _newVirtFileWasAdded) + { + // FIXME: probably we must request file size from VirtFileSystem + // _curSize = VirtFileSystem->GetLastFileSize() + // _curSize_Defined = true; + RINOK(VirtFileSystemSpec->CloseMemFile()) + } + if (_hashStream && _hashStream_WasUsed) + { + _hashStream->_hash->Final(_isFolder, _isAltStream, _filePath); + _curSize = _hashStream->GetSize(); + _curSize_Defined = true; + _hashStream->ReleaseStream(); + _hashStream_WasUsed = false; + } + else if (_hashCalc && _needUpdateStat) + { + _hashCalc->SetSize(size); // (_curSize) before 21.04 + _hashCalc->Final(_isFolder, _isAltStream, _filePath); + } + return SetOperationResult(opRes, encrypted); + COM_TRY_END +} + + +Z7_COM7F_IMF(CExtractCallbackImp::RequestMemoryUse( + UInt32 flags, UInt32 indexType, UInt32 /* index */, const wchar_t *path, + UInt64 requiredSize, UInt64 *allowedSize, UInt32 *answerFlags)) +{ + UInt32 limit_GB = (UInt32)((*allowedSize + ((1u << 30) - 1)) >> 30); + + if ((flags & NRequestMemoryUseFlags::k_IsReport) == 0) + { + UInt64 limit_bytes = *allowedSize; + const UInt32 limit_GB_Registry = NExtract::Read_LimitGB(); + if (limit_GB_Registry != 0 && limit_GB_Registry != (UInt32)(Int32)-1) + { + const UInt64 limit_bytes_Registry = (UInt64)limit_GB_Registry << 30; + // registry_WasForced = true; + if ((flags & NRequestMemoryUseFlags::k_AllowedSize_WasForced) == 0 + || limit_bytes < limit_bytes_Registry) + { + limit_bytes = limit_bytes_Registry; + limit_GB = limit_GB_Registry; + } + } + *allowedSize = limit_bytes; + if (requiredSize <= limit_bytes) + { + *answerFlags = NRequestMemoryAnswerFlags::k_Allow; + return S_OK; + } + // default answer can be k_Allow, if limit was not forced, + // so we change answer to non-allowed here, + // because user has chance to change limit in GUI. + *answerFlags = NRequestMemoryAnswerFlags::k_Limit_Exceeded; + if (flags & NRequestMemoryUseFlags::k_SkipArc_IsExpected) + *answerFlags |= NRequestMemoryAnswerFlags::k_SkipArc; + } + + const UInt32 required_GB = (UInt32)((requiredSize + ((1u << 30) - 1)) >> 30); + + CMemDialog dialog; + dialog.Limit_GB = limit_GB; + dialog.Required_GB = required_GB; + dialog.TestMode = TestMode; + if (MultiArcMode) + dialog.ArcPath = _currentArchivePath; + if (path) + dialog.FilePath = path; + + if (!g_DisableUserQuestions + && (flags & NRequestMemoryUseFlags::k_IsReport) == 0) + { + if (_remember) + dialog.SkipArc = _skipArc; + else + { + dialog.ShowRemember = + (MultiArcMode + || indexType != NArchive::NEventIndexType::kNoIndex + || path); + ProgressDialog->WaitCreating(); + if (dialog.Create(*ProgressDialog) != IDCONTINUE) + { + *answerFlags = NRequestMemoryAnswerFlags::k_Stop; + return E_ABORT; + } + if (dialog.NeedSave) + NExtract::Save_LimitGB(dialog.Limit_GB); + if (dialog.Remember) + { + _remember = true; + _skipArc = dialog.SkipArc; + } + } + + *allowedSize = (UInt64)dialog.Limit_GB << 30; + if (!dialog.SkipArc) + { + *answerFlags = NRequestMemoryAnswerFlags::k_Allow; + return S_OK; + } + *answerFlags = + NRequestMemoryAnswerFlags::k_SkipArc + | NRequestMemoryAnswerFlags::k_Limit_Exceeded; + flags |= NRequestMemoryUseFlags::k_Report_SkipArc; + } + + if ((flags & NRequestMemoryUseFlags::k_NoErrorMessage) == 0) + { + UString s ("ERROR: "); + dialog.AddInfoMessage_To_String(s); + s.Add_LF(); + // if (indexType == NArchive::NEventIndexType::kNoIndex) + if ((flags & NRequestMemoryUseFlags::k_SkipArc_IsExpected) || + (flags & NRequestMemoryUseFlags::k_Report_SkipArc)) + AddLangString(s, IDS_MSG_ARC_UNPACKING_WAS_SKIPPED); +/* + else + AddLangString(, IDS_MSG_ARC_FILES_UNPACKING_WAS_SKIPPED); +*/ + AddError_Message_ShowArcPath(s); + } + +/* + if ((flags & NRequestMemoryUseFlags::k_IsReport) == 0) + *answerFlags |= NRequestMemoryAnswerFlags::k_Limit_Exceeded; +*/ + return S_OK; +} + + +Z7_COM7F_IMF(CVirtFileSystem::Write(const void *data, UInt32 size, UInt32 *processedSize)) +{ + if (processedSize) + *processedSize = 0; + if (size == 0) + return S_OK; + if (!_wasSwitchedToFsMode) + { + CVirtFile &file = Files.Back(); + const size_t rem = file.Data.Size() - file.WrittenSize; + bool useMem = true; + if (rem < size) + { + UInt64 b = 0; + if (file.Data.Size() == 0) + b = file.ExpectedSize; + UInt64 a = (UInt64)file.WrittenSize + size; + if (b < a) + b = a; + a = (UInt64)file.Data.Size() * 2; + if (b < a) + b = a; + useMem = false; + if (b <= MaxTotalAllocSize) + useMem = file.Data.ReAlloc_KeepData((size_t)b, file.WrittenSize); + } + +#if 0 // 1 for debug : FLUSHING TO FS + useMem = false; +#endif + + if (useMem) + { + memcpy(file.Data + file.WrittenSize, data, size); + file.WrittenSize += size; + if (processedSize) + *processedSize = (UInt32)size; + return S_OK; + } + _wasSwitchedToFsMode = true; + } + + if (!_newVirtFileStream_IsReadyToWrite) // we check for _newVirtFileStream_IsReadyToWrite to optimize execution + { + RINOK(FlushToDisk(false)) + } + + if (_needWriteToRealFile) + return _outFileStream.Interface()->Write(data, size, processedSize); + if (processedSize) + *processedSize = size; + return S_OK; +} + + +HRESULT CVirtFileSystem::FlushToDisk(bool closeLast) +{ + while (_numFlushed < Files.Size()) + { + CVirtFile &file = Files[_numFlushed]; + const FString basePath = DirPrefix + us2fs(Get_Correct_FsFile_Name(file.BaseName)); + FString path = basePath; + + if (file.ColonWasUsed) + { + if (ZoneBuf.Size() != 0 + && Is_ZoneId_StreamName(file.AltStreamName)) + { + // it's expected that + // CArchiveExtractCallback::GetStream() have excluded + // ZoneId alt stream from extraction already. + // But we exclude alt stream extraction here too. + _numFlushed++; + continue; + } + path.Add_Colon(); + path += us2fs(Get_Correct_FsFile_Name(file.AltStreamName)); + } + + if (!_newVirtFileStream_IsReadyToWrite) + { + if (file.ColonWasUsed) + { + NFind::CFileInfo parentFi; + if (parentFi.Find(basePath) + && parentFi.IsReadOnly()) + { + _altStream_NeedRestore_Attrib_bool = true; + _altStream_NeedRestore_AttribVal = parentFi.Attrib; + NDir::SetFileAttrib(basePath, parentFi.Attrib & ~(DWORD)FILE_ATTRIBUTE_READONLY); + } + } + _outFileStream.Create_if_Empty(); + _needWriteToRealFile = _outFileStream->Create_NEW(path); + if (!_needWriteToRealFile) + { + if (!file.ColonWasUsed) + return GetLastError_noZero_HRESULT(); // it's main file and we can't ignore such error. + // (file.ColonWasUsed == true) + // So it's additional alt stream. + // And we ignore file creation error for additional alt stream. + // ShowErrorMessage(UString("Can't create file ") + fs2us(path)); + } + _newVirtFileStream_IsReadyToWrite = true; + // _openFilePath = path; + HRESULT hres = S_OK; + if (_needWriteToRealFile) + hres = WriteStream(_outFileStream, file.Data, file.WrittenSize); + // we free allocated memory buffer after data flushing: + file.WrittenSize = 0; + file.Data.Free(); + RINOK(hres) + } + + if (_numFlushed == Files.Size() - 1 && !closeLast) + break; + + if (_needWriteToRealFile) + { + if (file.CTime_Defined || + file.ATime_Defined || + file.MTime_Defined) + _outFileStream->SetTime( + file.CTime_Defined ? &file.CTime : NULL, + file.ATime_Defined ? &file.ATime : NULL, + file.MTime_Defined ? &file.MTime : NULL); + _outFileStream->Close(); + } + + _numFlushed++; + _newVirtFileStream_IsReadyToWrite = false; + + if (_needWriteToRealFile) + { + if (!file.ColonWasUsed + && ZoneBuf.Size() != 0) + WriteZoneFile_To_BaseFile(path, ZoneBuf); + if (file.Attrib_Defined) + NDir::SetFileAttrib_PosixHighDetect(path, file.Attrib); + // _openFilePath.Empty(); + _needWriteToRealFile = false; + } + + if (_altStream_NeedRestore_Attrib_bool) + { + _altStream_NeedRestore_Attrib_bool = false; + NDir::SetFileAttrib(basePath, _altStream_NeedRestore_AttribVal); + } + } + return S_OK; +} + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ExtractCallback.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ExtractCallback.h new file mode 100644 index 00000000..8b4dcb39 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ExtractCallback.h @@ -0,0 +1,347 @@ +// ExtractCallback.h + +#ifndef ZIP7_INC_EXTRACT_CALLBACK_H +#define ZIP7_INC_EXTRACT_CALLBACK_H + +#include "../../../../C/Alloc.h" + +#include "../../../Common/MyCom.h" +#include "../../../Common/StringConvert.h" + +#ifndef Z7_SFX +#include "../Agent/IFolderArchive.h" +#endif + +#include "../Common/ArchiveExtractCallback.h" +#include "../Common/ArchiveOpenCallback.h" + +#ifndef Z7_NO_CRYPTO +#include "../../IPassword.h" +#endif + +#ifndef Z7_SFX +#include "IFolder.h" +#endif + +#include "ProgressDialog2.h" + +#ifndef Z7_SFX + +class CGrowBuf +{ + Byte *_items; + size_t _size; + + Z7_CLASS_NO_COPY(CGrowBuf) + +public: + void Free() + { + MyFree(_items); + _items = NULL; + _size = 0; + } + + // newSize >= keepSize + bool ReAlloc_KeepData(size_t newSize, size_t keepSize) + { + void *buf = NULL; + if (newSize) + { + buf = MyAlloc(newSize); + if (!buf) + return false; + } + if (keepSize) + memcpy(buf, _items, keepSize); + MyFree(_items); + _items = (Byte *)buf; + _size = newSize; + return true; + } + + CGrowBuf(): _items(NULL), _size(0) {} + ~CGrowBuf() { MyFree(_items); } + + operator Byte *() { return _items; } + operator const Byte *() const { return _items; } + size_t Size() const { return _size; } +}; + + +struct CVirtFile +{ + CGrowBuf Data; + + UInt64 ExpectedSize; // size from props request. 0 if unknown + size_t WrittenSize; // size of written data in (Data) buffer + // use (WrittenSize) only if (CVirtFileSystem::_newVirtFileStream_IsReadyToWrite == false) + UString BaseName; // original name of file inside archive, + // It's not path. So any path separators + // should be treated as part of name (or as incorrect chars) + UString AltStreamName; + + bool CTime_Defined; + bool ATime_Defined; + bool MTime_Defined; + bool Attrib_Defined; + + // bool IsDir; + bool IsAltStream; + bool ColonWasUsed; + DWORD Attrib; + + FILETIME CTime; + FILETIME ATime; + FILETIME MTime; + + CVirtFile(): + CTime_Defined(false), + ATime_Defined(false), + MTime_Defined(false), + Attrib_Defined(false), + // IsDir(false), + IsAltStream(false), + ColonWasUsed(false) + {} +}; + + +/* + We use CVirtFileSystem only for single file extraction: + It supports the following cases and names: + - "fileName" : single file + - "fileName" item (main base file) and additional "fileName:altStream" items + - "altStream" : single item without "fileName:" prefix. + If file is flushed to disk, it uses Get_Correct_FsFile_Name(name). +*/ + +Z7_CLASS_IMP_NOQIB_1( + CVirtFileSystem, + ISequentialOutStream +) + unsigned _numFlushed; +public: + bool IsAltStreamFile; // in: + // = true, if extracting file is alt stream without "fileName:" prefix. + // = false, if extracting file is normal file, but additional + // alt streams "fileName:altStream" items are possible. +private: + bool _newVirtFileStream_IsReadyToWrite; // it can non real file (if can't open alt stream) + bool _needWriteToRealFile; // we need real writing to open file. + bool _wasSwitchedToFsMode; + bool _altStream_NeedRestore_Attrib_bool; + DWORD _altStream_NeedRestore_AttribVal; + + CMyComPtr2 _outFileStream; +public: + CObjectVector Files; + size_t MaxTotalAllocSize; // remain size, including Files.Back() + FString DirPrefix; // files will be flushed to this FS directory. + UString FileName; // name of file that will be extracted. + // it can be name of alt stream without "fileName:" prefix, if (IsAltStreamFile == trye). + // we use that name to detect altStream part in "FileName:altStream". + CByteBuffer ZoneBuf; + int Index_of_MainExtractedFile_in_Files; // out: index in Files. == -1, if expected file was not extracted + int Index_of_ZoneBuf_AltStream_in_Files; // out: index in Files. == -1, if no zonbuf alt stream + + + CVirtFileSystem() + { + _numFlushed = 0; + IsAltStreamFile = false; + _newVirtFileStream_IsReadyToWrite = false; + _needWriteToRealFile = false; + _wasSwitchedToFsMode = false; + _altStream_NeedRestore_Attrib_bool = false; + MaxTotalAllocSize = (size_t)0 - 1; + Index_of_MainExtractedFile_in_Files = -1; + Index_of_ZoneBuf_AltStream_in_Files = -1; + } + + bool WasStreamFlushedToFS() const { return _wasSwitchedToFsMode; } + + HRESULT CloseMemFile() + { + if (_wasSwitchedToFsMode) + return FlushToDisk(true); // closeLast + CVirtFile &file = Files.Back(); + if (file.Data.Size() != file.WrittenSize) + file.Data.ReAlloc_KeepData(file.WrittenSize, file.WrittenSize); + return S_OK; + } + + HRESULT FlushToDisk(bool closeLast); +}; + +#endif + + + +class CExtractCallbackImp Z7_final: + public IFolderArchiveExtractCallback, + /* IExtractCallbackUI: + before v23.00 : it included IFolderArchiveExtractCallback + since v23.00 : it doesn't include IFolderArchiveExtractCallback + */ + public IExtractCallbackUI, // NON-COM interface since 23.00 + public IOpenCallbackUI, // NON-COM interface + public IFolderArchiveExtractCallback2, + #ifndef Z7_SFX + public IFolderOperationsExtractCallback, + public IFolderExtractToStreamCallback, + public ICompressProgressInfo, + public IArchiveRequestMemoryUseCallback, + #endif + #ifndef Z7_NO_CRYPTO + public ICryptoGetTextPassword, + #endif + public CMyUnknownImp +{ + Z7_COM_QI_BEGIN2(IFolderArchiveExtractCallback) + Z7_COM_QI_ENTRY(IFolderArchiveExtractCallback2) + #ifndef Z7_SFX + Z7_COM_QI_ENTRY(IFolderOperationsExtractCallback) + Z7_COM_QI_ENTRY(IFolderExtractToStreamCallback) + Z7_COM_QI_ENTRY(ICompressProgressInfo) + Z7_COM_QI_ENTRY(IArchiveRequestMemoryUseCallback) + #endif + #ifndef Z7_NO_CRYPTO + Z7_COM_QI_ENTRY(ICryptoGetTextPassword) + #endif + Z7_COM_QI_END + Z7_COM_ADDREF_RELEASE + + Z7_IFACE_IMP(IExtractCallbackUI) + Z7_IFACE_IMP(IOpenCallbackUI) + Z7_IFACE_COM7_IMP(IProgress) + Z7_IFACE_COM7_IMP(IFolderArchiveExtractCallback) + Z7_IFACE_COM7_IMP(IFolderArchiveExtractCallback2) + #ifndef Z7_SFX + Z7_IFACE_COM7_IMP(IFolderOperationsExtractCallback) + Z7_IFACE_COM7_IMP(IFolderExtractToStreamCallback) + Z7_IFACE_COM7_IMP(ICompressProgressInfo) + Z7_IFACE_COM7_IMP(IArchiveRequestMemoryUseCallback) + #endif + #ifndef Z7_NO_CRYPTO + Z7_IFACE_COM7_IMP(ICryptoGetTextPassword) + #endif + + bool _needWriteArchivePath; + bool _isFolder; + bool _totalFiles_Defined; + bool _totalBytes_Defined; +public: + bool MultiArcMode; + bool ProcessAltStreams; + bool StreamMode; // set to true, if you want the callee to call GetStream7() + bool ThereAreMessageErrors; + bool Src_Is_IO_FS_Folder; + +#ifndef Z7_NO_CRYPTO + bool PasswordIsDefined; + bool PasswordWasAsked; +#endif + +private: +#ifndef Z7_SFX + bool _needUpdateStat; + bool _newVirtFileWasAdded; + bool _isAltStream; + // bool _extractMode; + // bool _testMode; + bool _hashStream_WasUsed; + bool _curSize_Defined; + bool NeedAddFile; + + bool _remember; + bool _skipArc; +#endif + +public: + bool YesToAll; + bool TestMode; + + UInt32 NumArchiveErrors; + NExtract::NOverwriteMode::EEnum OverwriteMode; + +private: + UString _currentArchivePath; + UString _currentFilePath; + UString _filePath; // virtual path than will be sent via IFolderExtractToStreamCallback + +#ifndef Z7_SFX + UInt64 _curSize; + CMyComPtr2 _hashStream; + IHashCalc *_hashCalc; // it's for stat in Test operation +#endif + +public: + CProgressDialog *ProgressDialog; + +#ifndef Z7_SFX + CVirtFileSystem *VirtFileSystemSpec; + CMyComPtr VirtFileSystem; + UInt64 NumFolders; + UInt64 NumFiles; +#endif + +#ifndef Z7_NO_CRYPTO + UString Password; +#endif + + UString _lang_Extracting; + UString _lang_Testing; + UString _lang_Skipping; + UString _lang_Reading; + UString _lang_Empty; + + CExtractCallbackImp(): + _totalFiles_Defined(false) + , _totalBytes_Defined(false) + , MultiArcMode(false) + , ProcessAltStreams(true) + , StreamMode(false) + , ThereAreMessageErrors(false) + , Src_Is_IO_FS_Folder(false) +#ifndef Z7_NO_CRYPTO + , PasswordIsDefined(false) + , PasswordWasAsked(false) +#endif +#ifndef Z7_SFX + , _remember(false) + , _skipArc(false) +#endif + , YesToAll(false) + , TestMode(false) + , OverwriteMode(NExtract::NOverwriteMode::kAsk) +#ifndef Z7_SFX + , _hashCalc(NULL) +#endif + {} + + ~CExtractCallbackImp(); + void Init(); + + HRESULT SetCurrentFilePath2(const wchar_t *filePath); + void AddError_Message(LPCWSTR message); + void AddError_Message_ShowArcPath(LPCWSTR message); + HRESULT MessageError(const char *message, const FString &path); + void Add_ArchiveName_Error(); + + #ifndef Z7_SFX + void SetHashCalc(IHashCalc *hashCalc) { _hashCalc = hashCalc; } + + void SetHashMethods(IHashCalc *hash) + { + if (!hash) + return; + _hashStream.Create_if_Empty(); + _hashStream->_hash = hash; + } + #endif + + bool IsOK() const { return NumArchiveErrors == 0 && !ThereAreMessageErrors; } +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FM.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FM.cpp new file mode 100644 index 00000000..f415f4df --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FM.cpp @@ -0,0 +1,1223 @@ +// FM.cpp + +#include "StdAfx.h" + +#include "../../../Common/MyWindows.h" + +#if defined(__MINGW32__) || defined(__MINGW64__) +#include +#else +#include +#endif + +#include "../../../../C/Compiler.h" +#include "../../../../C/Alloc.h" +#ifdef _WIN32 +#include "../../../../C/DllSecur.h" +#endif + +#include "../../../Common/StringConvert.h" +#include "../../../Common/StringToInt.h" + +#include "../../../Windows/ErrorMsg.h" +#include "../../../Windows/MemoryLock.h" +#include "../../../Windows/NtCheck.h" +#include "../../../Windows/System.h" + +#ifndef UNDER_CE +#include "../../../Windows/SecurityUtils.h" +#endif + +#include "../GUI/ExtractRes.h" + +#include "resource.h" + +#include "App.h" +#include "FormatUtils.h" +#include "LangUtils.h" +#include "MyLoadMenu.h" +#include "Panel.h" +#include "RegistryUtils.h" +#include "StringUtils.h" +#include "ViewSettings.h" + +using namespace NWindows; +using namespace NFile; +using namespace NFind; + +// #define MAX_LOADSTRING 100 + +extern +bool g_DisableUserQuestions; +bool g_DisableUserQuestions; + +extern +bool g_RAM_Size_Defined; +bool g_RAM_Size_Defined; + +extern +bool g_LargePagesMode; +bool g_LargePagesMode = false; +// static bool g_OpenArchive = false; + +static bool g_Maximized = false; + +extern +size_t g_RAM_Size; +size_t g_RAM_Size; + +#ifdef _WIN32 +extern +HINSTANCE g_hInstance; +HINSTANCE g_hInstance; +#endif + +HWND g_HWND; + +static UString g_MainPath; +static UString g_ArcFormat; + +// HRESULT LoadGlobalCodecs(); +void FreeGlobalCodecs(); + +#ifndef UNDER_CE + +#ifdef Z7_USE_DYN_ComCtl32Version +Z7_DIAGNOSTIC_IGNORE_CAST_FUNCTION + +DWORD g_ComCtl32Version; + +static DWORD GetDllVersion(LPCTSTR dllName) +{ + DWORD dwVersion = 0; + const HMODULE hmodule = LoadLibrary(dllName); + if (hmodule) + { + const + DLLGETVERSIONPROC f_DllGetVersion = Z7_GET_PROC_ADDRESS( + DLLGETVERSIONPROC, hmodule, + "DllGetVersion"); + if (f_DllGetVersion) + { + DLLVERSIONINFO dvi; + ZeroMemory(&dvi, sizeof(dvi)); + dvi.cbSize = sizeof(dvi); + const HRESULT hr = f_DllGetVersion(&dvi); + if (SUCCEEDED(hr)) + dwVersion = (DWORD)MAKELONG(dvi.dwMinorVersion, dvi.dwMajorVersion); + } + FreeLibrary(hmodule); + } + return dwVersion; +} + +#endif +#endif + +bool g_IsSmallScreen = false; + +extern +bool g_LVN_ITEMACTIVATE_Support; +bool g_LVN_ITEMACTIVATE_Support = true; +// LVN_ITEMACTIVATE replaces both NM_DBLCLK & NM_RETURN +// Windows 2000 +// NT/98 + IE 3 (g_ComCtl32Version >= 4.70) + + +static const int kNumDefaultPanels = 1; +static const int kSplitterWidth = 4; +static const int kSplitterRateMax = 1 << 16; +static const int kPanelSizeMin = 120; + + +class CSplitterPos +{ + int _ratio; // 10000 is max + int _pos; + int _fullWidth; + void SetRatioFromPos(HWND hWnd) + { _ratio = (_pos + kSplitterWidth / 2) * kSplitterRateMax / + MyMax(GetWidth(hWnd), 1); } +public: + int GetPos() const + { return _pos; } + int GetWidth(HWND hWnd) const + { + RECT rect; + ::GetClientRect(hWnd, &rect); + return rect.right; + } + void SetRatio(HWND hWnd, int aRatio) + { + _ratio = aRatio; + SetPosFromRatio(hWnd); + } + void SetPosPure(HWND hWnd, int pos) + { + int posMax = GetWidth(hWnd) - kSplitterWidth; + if (posMax < kPanelSizeMin * 2) + pos = posMax / 2; + else + { + if (pos > posMax - kPanelSizeMin) + pos = posMax - kPanelSizeMin; + else if (pos < kPanelSizeMin) + pos = kPanelSizeMin; + } + _pos = pos; + } + void SetPos(HWND hWnd, int pos) + { + _fullWidth = GetWidth(hWnd); + SetPosPure(hWnd, pos); + SetRatioFromPos(hWnd); + } + void SetPosFromRatio(HWND hWnd) + { + int fullWidth = GetWidth(hWnd); + if (_fullWidth != fullWidth && fullWidth != 0) + { + _fullWidth = fullWidth; + SetPosPure(hWnd, GetWidth(hWnd) * _ratio / kSplitterRateMax - kSplitterWidth / 2); + } + } +}; + +static bool g_CanChangeSplitter = false; +static UInt32 g_SplitterPos = 0; +static CSplitterPos g_Splitter; +static bool g_PanelsInfoDefined = false; +static bool g_WindowWasCreated = false; + +static int g_StartCaptureMousePos; +static int g_StartCaptureSplitterPos; + +CApp g_App; + +LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); + +static const wchar_t * const kWindowClass = L"7-Zip::FM"; + +#ifdef UNDER_CE +#define WS_OVERLAPPEDWINDOW ( \ + WS_OVERLAPPED | \ + WS_CAPTION | \ + WS_SYSMENU | \ + WS_THICKFRAME | \ + WS_MINIMIZEBOX | \ + WS_MAXIMIZEBOX) +#endif + + +/* +typedef HRESULT (WINAPI *Func_SetWindowTheme)( + HWND hwnd, + LPCWSTR pszSubAppName, + LPCWSTR pszSubIdList +); + +typedef BOOL (WINAPI *Func_AllowDarkModeForWindow)( + HWND a_HWND, BOOL a_Allow); + +enum PreferredAppMode +{ + Default, + AllowDark, + ForceDark, + ForceLight, + Max +}; +// ordinal 135, in 1903 +typedef BOOL (WINAPI *Func_SetPreferredAppMode)(PreferredAppMode appMode); + +typedef HRESULT (WINAPI *Func_DwmSetWindowAttribute)( + HWND hwnd, + DWORD dwAttribute, + LPCVOID pvAttribute, + DWORD cbAttribute +); +*/ + +static BOOL InitInstance(int nCmdShow) +{ + CWindow wnd; + + // LoadString(hInstance, IDS_CLASS, windowClass, MAX_LOADSTRING); + + UString title ("7-Zip"); // LangString(IDS_APP_TITLE, 0x03000000); + + /* + //If it is already running, then focus on the window + hWnd = FindWindow(windowClass, title); + if (hWnd) + { + SetForegroundWindow ((HWND) (((DWORD)hWnd) | 0x01)); + return 0; + } + */ + + WNDCLASSW wc; + + // wc.style = CS_HREDRAW | CS_VREDRAW; + wc.style = 0; + wc.lpfnWndProc = (WNDPROC) WndProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = g_hInstance; + wc.hIcon = LoadIcon(g_hInstance, MAKEINTRESOURCE(IDI_ICON)); + + // wc.hCursor = LoadCursor (NULL, IDC_ARROW); + wc.hCursor = ::LoadCursor(NULL, IDC_SIZEWE); + // wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH); + wc.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1); + + wc.lpszMenuName = + #ifdef UNDER_CE + 0 + #else + MAKEINTRESOURCEW(IDM_MENU) + #endif + ; + + wc.lpszClassName = kWindowClass; + + if (MyRegisterClass(&wc) == 0) + return FALSE; + + // RECT rect; + // GetClientRect(hWnd, &rect); + + DWORD style = WS_OVERLAPPEDWINDOW; + // DWORD style = 0; + + CWindowInfo info; + info.maximized = false; + int x, y, xSize, ySize; + x = y = xSize = ySize = CW_USEDEFAULT; + bool windowPosIsRead; + info.Read(windowPosIsRead, g_PanelsInfoDefined); + + if (windowPosIsRead) + { + x = info.rect.left; + y = info.rect.top; + + xSize = RECT_SIZE_X(info.rect); + ySize = RECT_SIZE_Y(info.rect); + } + + + if (g_PanelsInfoDefined) + { + g_SplitterPos = info.splitterPos; + if (info.numPanels < 1 || info.numPanels > 2) + info.numPanels = kNumDefaultPanels; + if (info.currentPanel >= 2) + info.currentPanel = 0; + } + else + { + info.numPanels = kNumDefaultPanels; + info.currentPanel = 0; + } + + g_App.NumPanels = info.numPanels; + g_App.LastFocusedPanel = info.currentPanel; + + if (!wnd.Create(kWindowClass, title, style, + x, y, xSize, ySize, NULL, NULL, g_hInstance, NULL)) + return FALSE; + + /* + // doesn't work + { + const HMODULE hmodule = LoadLibrary("UxTheme.dll"); + if (hmodule) + { + { + const + Func_AllowDarkModeForWindow f = Z7_GET_PROC_ADDRESS( + Func_AllowDarkModeForWindow, hmodule, + MAKEINTRESOURCEA(133)); + if (f) + { + BOOL res = f((HWND)wnd, TRUE); + res = res; + } + } + { + const + Func_SetPreferredAppMode f = Z7_GET_PROC_ADDRESS( + Func_SetPreferredAppMode, hmodule, + MAKEINTRESOURCEA(135)); + if (f) + { + f(ForceDark); + } + } + { + const + Func_SetWindowTheme f = Z7_GET_PROC_ADDRESS( + Func_SetWindowTheme, hmodule, + "SetWindowTheme"); + if (f) + { + // HRESULT hres = f((HWND)wnd, L"DarkMode_Explorer", NULL); + HRESULT hres = f((HWND)wnd, L"Explorer", NULL); + hres = hres; + } + } + FreeLibrary(hmodule); + } + } + { + const HMODULE hmodule = LoadLibrary("Dwmapi.dll"); + if (hmodule) + { + const + Func_DwmSetWindowAttribute f = Z7_GET_PROC_ADDRESS( + Func_DwmSetWindowAttribute, hmodule, + "DwmSetWindowAttribute"); + if (f) + { + #ifndef Z7_WIN_DWMWA_USE_IMMERSIVE_DARK_MODE + #define Z7_WIN_DWMWA_USE_IMMERSIVE_DARK_MODE 20 + #endif + BOOL value = TRUE; + f((HWND)wnd, Z7_WIN_DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value)); + } + FreeLibrary(hmodule); + } + } + */ + + if (nCmdShow == SW_SHOWNORMAL || + nCmdShow == SW_SHOW + #ifndef UNDER_CE + || nCmdShow == SW_SHOWDEFAULT + #endif + ) + { + if (info.maximized) + nCmdShow = SW_SHOWMAXIMIZED; + else + nCmdShow = SW_SHOWNORMAL; + } + + if (nCmdShow == SW_SHOWMAXIMIZED) + g_Maximized = true; + + #ifndef UNDER_CE + WINDOWPLACEMENT placement; + placement.length = sizeof(placement); + if (wnd.GetPlacement(&placement)) + { + if (windowPosIsRead) + placement.rcNormalPosition = info.rect; + placement.showCmd = (UINT)nCmdShow; + wnd.SetPlacement(&placement); + } + else + #endif + wnd.Show(nCmdShow); + + return TRUE; +} + +/* +static void GetCommands(const UString &aCommandLine, UString &aCommands) +{ + UString aProgramName; + aCommands.Empty(); + bool aQuoteMode = false; + for (int i = 0; i < aCommandLine.Length(); i++) + { + wchar_t aChar = aCommandLine[i]; + if (aChar == L'\"') + aQuoteMode = !aQuoteMode; + else if (aChar == L' ' && !aQuoteMode) + { + if (!aQuoteMode) + { + i++; + break; + } + } + else + aProgramName += aChar; + } + aCommands = aCommandLine.Ptr(i); +} +*/ + +#if defined(_WIN32) && !defined(_WIN64) && !defined(UNDER_CE) + +extern +bool g_Is_Wow64; +bool g_Is_Wow64; + +typedef BOOL (WINAPI *Func_IsWow64Process)(HANDLE, PBOOL); + +static void Set_Wow64() +{ + g_Is_Wow64 = false; + const + Func_IsWow64Process fn = Z7_GET_PROC_ADDRESS( + Func_IsWow64Process, GetModuleHandleA("kernel32.dll"), + "IsWow64Process"); + if (fn) + { + BOOL isWow; + if (fn(GetCurrentProcess(), &isWow)) + g_Is_Wow64 = (isWow != FALSE); + } +} + +#endif + +#if _MSC_VER > 1400 /* && _MSC_VER <= 1900 */ + // GetVersion was declared deprecated + #pragma warning(disable : 4996) +#endif +#ifdef __clang__ + #pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +bool IsLargePageSupported(); +bool IsLargePageSupported() +{ + #ifdef _WIN64 + return true; + #else + + DWORD v = GetVersion(); + // low byte is major version: + // next byte is minor version: + v = ((v & 0xff) << 8) | ((v >> 8) & 0xFF); + return (v > 0x501); + // if ((Byte)v < 5) return false; + // if ((Byte)v > 5) return true; + // return ((Byte)(v >> 8) > 1); + /* large pages work in 5.1 (XP-32bit) if it's (g_Is_Wow64) mode; + but here we don't enable them in (XP-32bit). */ + #endif +} + +#ifndef UNDER_CE + +static void SetMemoryLock() +{ + if (!IsLargePageSupported()) + return; + // if (ReadLockMemoryAdd()) + NSecurity::AddLockMemoryPrivilege(); + + if (ReadLockMemoryEnable()) + if (NSecurity::Get_LargePages_RiskLevel() == 0) + { + // note: child processes can inherit that Privilege + g_LargePagesMode = NSecurity::EnablePrivilege_LockMemory(); + } +} + +extern +bool g_SymLink_Supported; +bool g_SymLink_Supported = false; + +static void Set_SymLink_Supported() +{ + // g_SymLink_Supported = false; + const DWORD v = GetVersion(); + // low byte is major version: + if ((Byte)v < 6) + return; + g_SymLink_Supported = true; + // if (g_SymLink_Supported) + { + NSecurity::EnablePrivilege_SymLink(); + } +} + +#endif + +/* +static const int kNumSwitches = 1; + +namespace NKey { +enum Enum +{ + kOpenArachive = 0 +}; + +} + +static const CSwitchForm kSwitchForms[kNumSwitches] = + { + { L"SOA", NSwitchType::kSimple, false }, + }; +*/ + +// int APIENTRY WinMain2(HINSTANCE hInstance, HINSTANCE /* hPrevInstance */, LPSTR /* lpCmdLine */, int /* nCmdShow */); + +static void ErrorMessage(const wchar_t *s) +{ + MessageBoxW(NULL, s, L"7-Zip", MB_ICONERROR); +} + +static void ErrorMessage(const char *s) +{ + ErrorMessage(GetUnicodeString(s)); +} + + +#if defined(_UNICODE) && !defined(_WIN64) && !defined(UNDER_CE) +#define NT_CHECK_FAIL_ACTION ErrorMessage("Unsupported Windows version"); return 1; +#endif + +static int WINAPI WinMain2(int nCmdShow) +{ + g_RAM_Size_Defined = NSystem::GetRamSize(g_RAM_Size); + + #ifdef _WIN32 + + /* + #ifndef _WIN64 + #ifndef UNDER_CE + { + HMODULE hMod = GetModuleHandle("Kernel32.dll"); + if (hMod) + { + typedef BOOL (WINAPI *PSETDEP)(DWORD); + #define MY_PROCESS_DEP_ENABLE 1 + PSETDEP procSet = (PSETDEP)GetProcAddress(hMod,"SetProcessDEPPolicy"); + if (procSet) + procSet(MY_PROCESS_DEP_ENABLE); + + typedef BOOL (WINAPI *HSI)(HANDLE, HEAP_INFORMATION_CLASS ,PVOID, SIZE_T); + HSI hsi = (HSI)GetProcAddress(hMod, "HeapSetInformation"); + #define MY_HeapEnableTerminationOnCorruption ((HEAP_INFORMATION_CLASS)1) + if (hsi) + hsi(NULL, MY_HeapEnableTerminationOnCorruption, NULL, 0); + } + } + #endif + #endif + */ + + NT_CHECK + #ifdef Z7_LARGE_PAGES + z7_LargePage_Set(0, 0, 0); + #endif + + #endif + + #ifdef Z7_LANG + LoadLangOneTime(); + #endif + + InitCommonControls(); + +#ifdef Z7_USE_DYN_ComCtl32Version + g_ComCtl32Version = ::GetDllVersion(TEXT("comctl32.dll")); + g_LVN_ITEMACTIVATE_Support = (g_ComCtl32Version >= MAKELONG(71, 4)); +#endif + + #if defined(_WIN32) && !defined(_WIN64) && !defined(UNDER_CE) + Set_Wow64(); + #endif + + + g_IsSmallScreen = !NWindows::NControl::IsDialogSizeOK(200, 200); + + // OleInitialize is required for drag and drop. + #ifndef UNDER_CE + OleInitialize(NULL); + #endif + // Maybe needs CoInitializeEx also ? + // NCOM::CComInitializer comInitializer; + + UString commandsString; + // MessageBoxW(NULL, GetCommandLineW(), L"", 0); + + #ifdef UNDER_CE + commandsString = GetCommandLineW(); + #else + UString programString; + SplitStringToTwoStrings(GetCommandLineW(), programString, commandsString); + #endif + + commandsString.Trim(); + UString paramString, tailString; + SplitStringToTwoStrings(commandsString, paramString, tailString); + paramString.Trim(); + tailString.Trim(); + if (tailString.IsPrefixedBy("-t")) + g_ArcFormat = tailString.Ptr(2); + + /* + UStringVector switches; + for (;;) + { + if (tailString.IsEmpty()) + break; + UString s1, s2; + SplitStringToTwoStrings(tailString, s1, s2); + if (s2.IsEmpty()) + { + tailString.Trim(); + switches.Add(tailString); + break; + } + s1.Trim(); + switches.Add(s1); + tailString = s2; + } + + FOR_VECTOR(i, switches) + { + const UString &sw = switches[i]; + if (sw.IsPrefixedBy(L"-t")) + g_ArcFormat = sw.Ptr(2); + // + else if (sw.IsPrefixedBy(L"-stp")) + { + const wchar_t *end; + UInt32 val = ConvertStringToUInt32(sw.Ptr(4), &end); + if (*end != 0) + throw 111; + g_TypeParseLevel = val; + } + else + // + throw 112; + } + */ + + if (!paramString.IsEmpty()) + { + g_MainPath = paramString; + // return WinMain2(hInstance, hPrevInstance, lpCmdLine, nCmdShow); + + // MessageBoxW(NULL, paramString, L"", 0); + } + /* + UStringVector commandStrings; + NCommandLineParser::SplitCommandLine(GetCommandLineW(), commandStrings); + NCommandLineParser::CParser parser(kNumSwitches); + try + { + parser.ParseStrings(kSwitchForms, commandStrings); + const UStringVector &nonSwitchStrings = parser.NonSwitchStrings; + if (nonSwitchStrings.Size() > 1) + { + g_MainPath = nonSwitchStrings[1]; + // g_OpenArchive = parser[NKey::kOpenArachive].ThereIs; + CFileInfoW fileInfo; + if (FindFile(g_MainPath, fileInfo)) + { + if (!fileInfo.IsDir()) + g_OpenArchive = true; + } + } + } + catch(...) { } + */ + + + #if defined(_WIN32) && !defined(UNDER_CE) + SetMemoryLock(); + Set_SymLink_Supported(); + #endif + + g_App.ReloadLangItems(); + + MSG msg; + if (!InitInstance (nCmdShow)) + return FALSE; + + // we will load Global_Codecs at first use instead. + /* + OutputDebugStringW(L"Before LoadGlobalCodecs"); + LoadGlobalCodecs(); + OutputDebugStringW(L"After LoadGlobalCodecs"); + */ + + #ifndef _UNICODE + if (g_IsNT) + { + HACCEL hAccels = LoadAcceleratorsW(g_hInstance, MAKEINTRESOURCEW(IDR_ACCELERATOR1)); + while (GetMessageW(&msg, NULL, 0, 0)) + { + if (TranslateAcceleratorW(g_HWND, hAccels, &msg) == 0) + { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + } + } + else + #endif + { + HACCEL hAccels = LoadAccelerators(g_hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR1)); + while (GetMessage(&msg, NULL, 0, 0)) + { + if (TranslateAccelerator(g_HWND, hAccels, &msg) == 0) + { + // if (g_Hwnd != NULL || !IsDialogMessage(g_Hwnd, &msg)) + // if (!IsDialogMessage(g_Hwnd, &msg)) + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + } + + // Destructor of g_CodecsReleaser can release DLLs. + // But we suppose that it's better to release DLLs here (before destructor). + FreeGlobalCodecs(); + + g_HWND = NULL; + #ifndef UNDER_CE + OleUninitialize(); + #endif + return (int)msg.wParam; +} + +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE /* hPrevInstance */, + #ifdef UNDER_CE + LPWSTR + #else + LPSTR + #endif + /* lpCmdLine */, int nCmdShow) +{ + g_hInstance = hInstance; + + try + { + try + { + #ifdef _WIN32 + My_SetDefaultDllDirectories(); + #endif + return WinMain2(nCmdShow); + } + catch (...) + { + g_ExitEventLauncher.Exit(true); + throw; + } + } + catch(const CNewException &) + { + ErrorMessage(LangString(IDS_MEM_ERROR)); + return 1; + } + catch(const UString &s) + { + ErrorMessage(s); + return 1; + } + catch(const AString &s) + { + ErrorMessage(s.Ptr()); + return 1; + } + catch(const wchar_t *s) + { + ErrorMessage(s); + return 1; + } + catch(const char *s) + { + ErrorMessage(s); + return 1; + } + catch(int v) + { + AString e ("Error: "); + e.Add_UInt32((unsigned)v); + ErrorMessage(e); + return 1; + } + catch(...) + { + ErrorMessage("Unknown error"); + return 1; + } +} + +static void SaveWindowInfo(HWND aWnd) +{ + CWindowInfo info; + + #ifdef UNDER_CE + + if (!::GetWindowRect(aWnd, &info.rect)) + return; + info.maximized = g_Maximized; + + #else + + WINDOWPLACEMENT placement; + placement.length = sizeof(placement); + if (!::GetWindowPlacement(aWnd, &placement)) + return; + info.rect = placement.rcNormalPosition; + info.maximized = BOOLToBool(::IsZoomed(aWnd)); + + #endif + + info.numPanels = g_App.NumPanels; + info.currentPanel = g_App.LastFocusedPanel; + info.splitterPos = (unsigned)g_Splitter.GetPos(); + + info.Save(); +} + +static void ExecuteCommand(UINT commandID) +{ + CPanel::CDisableTimerProcessing disableTimerProcessing1(g_App.Panels[0]); + CPanel::CDisableTimerProcessing disableTimerProcessing2(g_App.Panels[1]); + + switch (commandID) + { + case kMenuCmdID_Toolbar_Add: g_App.AddToArchive(); break; + case kMenuCmdID_Toolbar_Extract: g_App.ExtractArchives(); break; + case kMenuCmdID_Toolbar_Test: g_App.TestArchives(); break; + } +} + +LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + switch (message) + { + case WM_COMMAND: + { + unsigned wmId = LOWORD(wParam); + unsigned wmEvent = HIWORD(wParam); + if ((HWND) lParam != NULL && wmEvent != 0) + break; + if (wmId >= kMenuCmdID_Toolbar_Start && wmId < kMenuCmdID_Toolbar_End) + { + ExecuteCommand(wmId); + return 0; + } + if (OnMenuCommand(hWnd, wmId)) + return 0; + break; + } + case WM_INITMENUPOPUP: + OnMenuActivating(hWnd, HMENU(wParam), LOWORD(lParam)); + break; + + /* + It doesn't help + case WM_EXITMENULOOP: + { + OnMenuUnActivating(hWnd); + break; + } + case WM_UNINITMENUPOPUP: + OnMenuUnActivating(hWnd, HMENU(wParam), lParam); + break; + */ + + case WM_CREATE: + { + g_HWND = hWnd; + /* + INITCOMMONCONTROLSEX icex; + icex.dwSize = sizeof(INITCOMMONCONTROLSEX); + icex.dwICC = ICC_BAR_CLASSES; + InitCommonControlsEx(&icex); + + // Toolbar buttons used to create the first 4 buttons. + TBBUTTON tbb [ ] = + { + // {0, 0, TBSTATE_ENABLED, BTNS_SEP, 0L, 0}, + // {VIEW_PARENTFOLDER, kParentFolderID, TBSTATE_ENABLED, BTNS_BUTTON, 0L, 0}, + // {0, 0, TBSTATE_ENABLED, BTNS_SEP, 0L, 0}, + {VIEW_NEWFOLDER, ID_FILE_CREATEFOLDER, TBSTATE_ENABLED, BTNS_BUTTON, 0L, 0}, + }; + + int baseID = 100; + NWindows::NControl::CToolBar aToolBar; + aToolBar.Attach(::CreateToolbarEx (hWnd, + WS_CHILD | WS_BORDER | WS_VISIBLE | TBSTYLE_TOOLTIPS, // | TBSTYLE_FLAT + baseID + 2, 11, + (HINSTANCE)HINST_COMMCTRL, IDB_VIEW_SMALL_COLOR, + (LPCTBBUTTON)&tbb, Z7_ARRAY_SIZE(tbb), + 0, 0, 100, 30, sizeof (TBBUTTON))); + */ + // HCURSOR cursor = ::LoadCursor(0, IDC_SIZEWE); + // ::SetCursor(cursor); + + if (g_PanelsInfoDefined) + g_Splitter.SetPos(hWnd, (int)g_SplitterPos); + else + { + g_Splitter.SetRatio(hWnd, kSplitterRateMax / 2); + g_SplitterPos = (unsigned)g_Splitter.GetPos(); + } + + RECT rect; + ::GetClientRect(hWnd, &rect); + const int xSize = rect.right; + int xSizes[2]; + xSizes[0] = g_Splitter.GetPos(); + xSizes[1] = xSize - kSplitterWidth - xSizes[0]; + if (xSizes[1] < 0) + xSizes[1] = 0; + + g_App.CreateDragTarget(); + + COpenResult openRes; + bool needOpenArc = false; + + UString fullPath = g_MainPath; + if (!fullPath.IsEmpty() /* && g_OpenArchive */) + { + if (!NFile::NName::IsAbsolutePath(fullPath)) + { + FString fullPathF; + if (NFile::NName::GetFullPath(us2fs(fullPath), fullPathF)) + fullPath = fs2us(fullPathF); + } + if (NFile::NFind::DoesFileExist_FollowLink(us2fs(fullPath))) + needOpenArc = true; + } + + HRESULT res = g_App.Create(hWnd, fullPath, g_ArcFormat, xSizes, + needOpenArc, + openRes); + + if (res == E_ABORT) + return -1; + + if ((needOpenArc && !openRes.ArchiveIsOpened) || res != S_OK) + { + UString m ("Error"); + if (res == S_FALSE || res == S_OK) + { + m = MyFormatNew(openRes.Encrypted ? + IDS_CANT_OPEN_ENCRYPTED_ARCHIVE : + IDS_CANT_OPEN_ARCHIVE, + fullPath); + } + else if (res != S_OK) + m = HResultToMessage(res); + if (!openRes.ErrorMessage.IsEmpty()) + { + m.Add_LF(); + m += openRes.ErrorMessage; + } + ErrorMessage(m); + return -1; + } + + g_WindowWasCreated = true; + + // g_SplitterPos = 0; + + // ::DragAcceptFiles(hWnd, TRUE); + RegisterDragDrop(hWnd, g_App._dropTarget); + + break; + } + + case WM_CLOSE: + { + // why do we use WA_INACTIVE here ? + SendMessage(hWnd, WM_ACTIVATE, MAKEWPARAM(WA_INACTIVE, 0), (LPARAM)hWnd); + g_ExitEventLauncher.Exit(false); + // ::DragAcceptFiles(hWnd, FALSE); + RevokeDragDrop(hWnd); + g_App._dropTarget.Release(); + + if (g_WindowWasCreated) + g_App.Save(); + + g_App.ReleaseApp(); + + if (g_WindowWasCreated) + SaveWindowInfo(hWnd); + + g_ExitEventLauncher.Exit(true); + // default DefWindowProc will call DestroyWindow / WM_DESTROY + break; + } + + case WM_DESTROY: + { + PostQuitMessage(0); + break; + } + + // case WM_MOVE: break; + + case WM_LBUTTONDOWN: + g_StartCaptureMousePos = LOWORD(lParam); + g_StartCaptureSplitterPos = g_Splitter.GetPos(); + ::SetCapture(hWnd); + break; + + case WM_LBUTTONUP: + { + ::ReleaseCapture(); + break; + } + + case WM_MOUSEMOVE: + { + if ((wParam & MK_LBUTTON) != 0 && ::GetCapture() == hWnd) + { + g_Splitter.SetPos(hWnd, g_StartCaptureSplitterPos + + (short)LOWORD(lParam) - g_StartCaptureMousePos); + g_App.MoveSubWindows(); + } + break; + } + + case WM_SIZE: + { + if (g_CanChangeSplitter) + g_Splitter.SetPosFromRatio(hWnd); + else + { + g_Splitter.SetPos(hWnd, (int)g_SplitterPos ); + g_CanChangeSplitter = true; + } + + g_Maximized = (wParam == SIZE_MAXIMIZED) || (wParam == SIZE_MAXSHOW); + + g_App.MoveSubWindows(); + /* + int xSize = LOWORD(lParam); + int ySize = HIWORD(lParam); + // int xSplitter = 2; + int xWidth = g_SplitPos; + // int xSplitPos = xWidth; + g_Panel[0]._listView.MoveWindow(0, 0, xWidth, ySize); + g_Panel[1]._listView.MoveWindow(xSize - xWidth, 0, xWidth, ySize); + */ + return 0; + // break; + } + + case WM_SETFOCUS: + // g_App.SetFocus(g_App.LastFocusedPanel); + g_App.SetFocusToLastItem(); + break; + + /* + case WM_ACTIVATE: + { + int fActive = LOWORD(wParam); + switch (fActive) + { + case WA_INACTIVE: + { + // g_FocusIndex = g_App.LastFocusedPanel; + // g_App.LastFocusedPanel = g_App.GetFocusedPanelIndex(); + // return 0; + } + } + break; + } + */ + + /* + case kLangWasChangedMessage: + MyLoadMenu(); + return 0; + */ + + /* + case WM_SETTINGCHANGE: + break; + */ + + case WM_NOTIFY: + { + g_App.OnNotify((int)wParam, (LPNMHDR)lParam); + break; + } + + /* + case WM_DROPFILES: + { + g_App.GetFocusedPanel().CompressDropFiles((HDROP)wParam); + return 0 ; + } + */ + } + #ifndef _UNICODE + if (g_IsNT) + return DefWindowProcW(hWnd, message, wParam, lParam); + else + #endif + return DefWindowProc(hWnd, message, wParam, lParam); + +} + +static int Window_GetRealHeight(NWindows::CWindow &w) +{ + RECT rect; + w.GetWindowRect(&rect); + int res = RECT_SIZE_Y(rect); + #ifndef UNDER_CE + WINDOWPLACEMENT placement; + if (w.GetPlacement(&placement)) + res += placement.rcNormalPosition.top; + #endif + return res; +} + +void CApp::MoveSubWindows() +{ + HWND hWnd = _window; + RECT rect; + if (!hWnd) + return; + ::GetClientRect(hWnd, &rect); + int xSize = rect.right; + if (xSize == 0) + return; + int headerSize = 0; + + #ifdef UNDER_CE + _commandBar.AutoSize(); + { + _commandBar.Show(true); // maybe we need it for + headerSize += _commandBar.Height(); + } + #endif + + if (_toolBar) + { + _toolBar.AutoSize(); + #ifdef UNDER_CE + int h2 = Window_GetRealHeight(_toolBar); + _toolBar.Move(0, headerSize, xSize, h2); + #endif + headerSize += Window_GetRealHeight(_toolBar); + } + + int ySize = MyMax((int)(rect.bottom - headerSize), 0); + + if (NumPanels > 1) + { + Panels[0].Move(0, headerSize, g_Splitter.GetPos(), ySize); + int xWidth1 = g_Splitter.GetPos() + kSplitterWidth; + Panels[1].Move(xWidth1, headerSize, xSize - xWidth1, ySize); + } + else + { + /* + int otherPanel = 1 - LastFocusedPanel; + if (PanelsCreated[otherPanel]) + Panels[otherPanel].Move(0, headerSize, 0, ySize); + */ + Panels[LastFocusedPanel].Move(0, headerSize, xSize, ySize); + } +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FM.dsp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FM.dsp new file mode 100644 index 00000000..4c549d6f --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FM.dsp @@ -0,0 +1,1699 @@ +# Microsoft Developer Studio Project File - Name="FM" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Application" 0x0101 + +CFG=FM - Win32 DebugU +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "FM.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "FM.mak" CFG="FM - Win32 DebugU" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "FM - Win32 Release" (based on "Win32 (x86) Application") +!MESSAGE "FM - Win32 Debug" (based on "Win32 (x86) Application") +!MESSAGE "FM - Win32 ReleaseU" (based on "Win32 (x86) Application") +!MESSAGE "FM - Win32 DebugU" (based on "Win32 (x86) Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "FM - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /Gz /MD /W4 /WX /GX /O1 /D "NDEBUG" /D "_MBCS" /D "WIN32" /D "_WINDOWS" /D "Z7_LANG" /D "Z7_LONG_PATH" /D "Z7_EXTERNAL_CODECS" /D "Z7_DEVICE_FILE" /FAcs /Yu"StdAfx.h" /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib comctl32.lib Mpr.lib htmlhelp.lib Urlmon.lib /nologo /subsystem:windows /machine:I386 /out:"C:\Util\7zFM.exe" /opt:NOWIN98 +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "FM - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /Gz /MDd /W4 /WX /Gm /GX /ZI /Od /D "_DEBUG" /D "_MBCS" /D "WIN32" /D "_WINDOWS" /D "Z7_LANG" /D "Z7_LONG_PATH" /D "Z7_EXTERNAL_CODECS" /D "Z7_DEVICE_FILE" /Yu"StdAfx.h" /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib comctl32.lib Mpr.lib htmlhelp.lib Urlmon.lib /nologo /subsystem:windows /debug /machine:I386 /out:"C:\Util\7zFM.exe" /pdbtype:sept + +!ELSEIF "$(CFG)" == "FM - Win32 ReleaseU" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "ReleaseU" +# PROP BASE Intermediate_Dir "ReleaseU" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "ReleaseU" +# PROP Intermediate_Dir "ReleaseU" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MD /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /Yu"StdAfx.h" /FD /c +# ADD CPP /nologo /Gz /MD /W4 /WX /GX /O1 /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "Z7_LANG" /D "Z7_LONG_PATH" /D "Z7_EXTERNAL_CODECS" /D "Z7_DEVICE_FILE" /Yu"StdAfx.h" /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib comctl32.lib /nologo /subsystem:windows /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib comctl32.lib Mpr.lib htmlhelp.lib Urlmon.lib /nologo /subsystem:windows /machine:I386 /out:"C:\Util\7zFM.exe" /opt:NOWIN98 +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "FM - Win32 DebugU" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "DebugU" +# PROP BASE Intermediate_Dir "DebugU" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "DebugU" +# PROP Intermediate_Dir "DebugU" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /Yu"StdAfx.h" /FD /GZ /c +# ADD CPP /nologo /Gz /MDd /W4 /WX /Gm /GX /ZI /Od /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "Z7_LANG" /D "Z7_LONG_PATH" /D "Z7_EXTERNAL_CODECS" /D "Z7_DEVICE_FILE" /Yu"StdAfx.h" /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib comctl32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib comctl32.lib Mpr.lib htmlhelp.lib Urlmon.lib /nologo /subsystem:windows /debug /machine:I386 /out:"C:\Util\7zFM.exe" /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "FM - Win32 Release" +# Name "FM - Win32 Debug" +# Name "FM - Win32 ReleaseU" +# Name "FM - Win32 DebugU" +# Begin Group "Spec" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=.\7zipLogo.ico +# End Source File +# Begin Source File + +SOURCE=.\add.bmp +# End Source File +# Begin Source File + +SOURCE=.\ClassDefs.cpp +# End Source File +# Begin Source File + +SOURCE=.\Copy.bmp +# End Source File +# Begin Source File + +SOURCE=.\Delete.bmp +# End Source File +# Begin Source File + +SOURCE=.\Extract.bmp +# End Source File +# Begin Source File + +SOURCE=.\FM.ico +# End Source File +# Begin Source File + +SOURCE=.\Move.bmp +# End Source File +# Begin Source File + +SOURCE=.\MyWindowsNew.h +# End Source File +# Begin Source File + +SOURCE=.\Parent.bmp +# End Source File +# Begin Source File + +SOURCE=.\Properties.bmp +# End Source File +# Begin Source File + +SOURCE=.\resource.rc +# ADD BASE RSC /l 0x419 +# ADD RSC /l 0x409 +# End Source File +# Begin Source File + +SOURCE=.\StdAfx.cpp +# ADD CPP /Yc"StdAfx.h" +# End Source File +# Begin Source File + +SOURCE=.\StdAfx.h +# End Source File +# Begin Source File + +SOURCE=.\Test.bmp +# End Source File +# End Group +# Begin Group "Folders" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=.\AltStreamsFolder.cpp +# End Source File +# Begin Source File + +SOURCE=.\AltStreamsFolder.h +# End Source File +# Begin Source File + +SOURCE=.\FSDrives.cpp +# End Source File +# Begin Source File + +SOURCE=.\FSDrives.h +# End Source File +# Begin Source File + +SOURCE=.\FSFolder.cpp +# End Source File +# Begin Source File + +SOURCE=.\FSFolder.h +# End Source File +# Begin Source File + +SOURCE=.\FSFolderCopy.cpp +# End Source File +# Begin Source File + +SOURCE=.\IFolder.h +# End Source File +# Begin Source File + +SOURCE=.\NetFolder.cpp +# End Source File +# Begin Source File + +SOURCE=.\NetFolder.h +# End Source File +# Begin Source File + +SOURCE=.\RootFolder.cpp +# End Source File +# Begin Source File + +SOURCE=.\RootFolder.h +# End Source File +# End Group +# Begin Group "Registry" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=.\RegistryAssociations.cpp +# End Source File +# Begin Source File + +SOURCE=.\RegistryAssociations.h +# End Source File +# Begin Source File + +SOURCE=.\RegistryPlugins.cpp +# End Source File +# Begin Source File + +SOURCE=.\RegistryPlugins.h +# End Source File +# Begin Source File + +SOURCE=.\RegistryUtils.cpp +# End Source File +# Begin Source File + +SOURCE=.\RegistryUtils.h +# End Source File +# Begin Source File + +SOURCE=.\ViewSettings.cpp +# End Source File +# Begin Source File + +SOURCE=.\ViewSettings.h +# End Source File +# End Group +# Begin Group "Panel" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=.\App.cpp +# End Source File +# Begin Source File + +SOURCE=.\App.h +# End Source File +# Begin Source File + +SOURCE=.\AppState.h +# End Source File +# Begin Source File + +SOURCE=.\EnumFormatEtc.cpp +# End Source File +# Begin Source File + +SOURCE=.\EnumFormatEtc.h +# End Source File +# Begin Source File + +SOURCE=.\FileFolderPluginOpen.cpp +# End Source File +# Begin Source File + +SOURCE=.\FileFolderPluginOpen.h +# End Source File +# Begin Source File + +SOURCE=.\Panel.cpp +# End Source File +# Begin Source File + +SOURCE=.\Panel.h +# End Source File +# Begin Source File + +SOURCE=.\PanelCopy.cpp +# End Source File +# Begin Source File + +SOURCE=.\PanelCrc.cpp +# End Source File +# Begin Source File + +SOURCE=.\PanelDrag.cpp +# End Source File +# Begin Source File + +SOURCE=.\PanelFolderChange.cpp +# End Source File +# Begin Source File + +SOURCE=.\PanelItemOpen.cpp +# End Source File +# Begin Source File + +SOURCE=.\PanelItems.cpp +# End Source File +# Begin Source File + +SOURCE=.\PanelKey.cpp +# End Source File +# Begin Source File + +SOURCE=.\PanelListNotify.cpp +# End Source File +# Begin Source File + +SOURCE=.\PanelMenu.cpp +# End Source File +# Begin Source File + +SOURCE=.\PanelOperations.cpp +# End Source File +# Begin Source File + +SOURCE=.\PanelSelect.cpp +# End Source File +# Begin Source File + +SOURCE=.\PanelSort.cpp +# End Source File +# Begin Source File + +SOURCE=.\PanelSplitFile.cpp +# End Source File +# Begin Source File + +SOURCE=.\VerCtrl.cpp +# End Source File +# End Group +# Begin Group "Dialog" + +# PROP Default_Filter "" +# Begin Group "Options" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=.\EditPage.cpp +# End Source File +# Begin Source File + +SOURCE=.\EditPage.h +# End Source File +# Begin Source File + +SOURCE=.\FoldersPage.cpp +# End Source File +# Begin Source File + +SOURCE=.\FoldersPage.h +# End Source File +# Begin Source File + +SOURCE=.\LangPage.cpp +# End Source File +# Begin Source File + +SOURCE=.\LangPage.h +# End Source File +# Begin Source File + +SOURCE=.\MenuPage.cpp +# End Source File +# Begin Source File + +SOURCE=.\MenuPage.h +# End Source File +# Begin Source File + +SOURCE=.\OptionsDialog.cpp +# End Source File +# Begin Source File + +SOURCE=.\SettingsPage.cpp +# End Source File +# Begin Source File + +SOURCE=.\SettingsPage.h +# End Source File +# Begin Source File + +SOURCE=.\SystemPage.cpp +# End Source File +# Begin Source File + +SOURCE=.\SystemPage.h +# End Source File +# End Group +# Begin Source File + +SOURCE=.\AboutDialog.cpp +# End Source File +# Begin Source File + +SOURCE=.\AboutDialog.h +# End Source File +# Begin Source File + +SOURCE=.\BrowseDialog.cpp +# End Source File +# Begin Source File + +SOURCE=.\BrowseDialog.h +# End Source File +# Begin Source File + +SOURCE=.\BrowseDialog2.cpp +# End Source File +# Begin Source File + +SOURCE=.\BrowseDialog2.h +# End Source File +# Begin Source File + +SOURCE=.\ComboDialog.cpp +# End Source File +# Begin Source File + +SOURCE=.\ComboDialog.h +# End Source File +# Begin Source File + +SOURCE=CopyDialog.cpp +# End Source File +# Begin Source File + +SOURCE=CopyDialog.h +# End Source File +# Begin Source File + +SOURCE=.\DialogSize.h +# End Source File +# Begin Source File + +SOURCE=.\EditDialog.cpp +# End Source File +# Begin Source File + +SOURCE=.\EditDialog.h +# End Source File +# Begin Source File + +SOURCE=.\LinkDialog.cpp +# End Source File +# Begin Source File + +SOURCE=.\LinkDialog.h +# End Source File +# Begin Source File + +SOURCE=.\ListViewDialog.cpp +# End Source File +# Begin Source File + +SOURCE=.\ListViewDialog.h +# End Source File +# Begin Source File + +SOURCE=.\MemDialog.cpp +# End Source File +# Begin Source File + +SOURCE=.\MemDialog.h +# End Source File +# Begin Source File + +SOURCE=MessagesDialog.cpp +# End Source File +# Begin Source File + +SOURCE=MessagesDialog.h +# End Source File +# Begin Source File + +SOURCE=OverwriteDialog.cpp +# End Source File +# Begin Source File + +SOURCE=OverwriteDialog.h +# End Source File +# Begin Source File + +SOURCE=.\PasswordDialog.cpp +# End Source File +# Begin Source File + +SOURCE=.\PasswordDialog.h +# End Source File +# Begin Source File + +SOURCE=.\ProgressDialog2.cpp +# End Source File +# Begin Source File + +SOURCE=.\ProgressDialog2.h +# End Source File +# Begin Source File + +SOURCE=.\SplitDialog.cpp +# End Source File +# Begin Source File + +SOURCE=.\SplitDialog.h +# End Source File +# End Group +# Begin Group "FM Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=.\ExtractCallback.cpp +# End Source File +# Begin Source File + +SOURCE=.\ExtractCallback.h +# End Source File +# Begin Source File + +SOURCE=.\FormatUtils.cpp +# End Source File +# Begin Source File + +SOURCE=.\FormatUtils.h +# End Source File +# Begin Source File + +SOURCE=.\HelpUtils.cpp +# End Source File +# Begin Source File + +SOURCE=.\HelpUtils.h +# End Source File +# Begin Source File + +SOURCE=.\LangUtils.cpp +# End Source File +# Begin Source File + +SOURCE=.\LangUtils.h +# End Source File +# Begin Source File + +SOURCE=.\ProgramLocation.cpp +# End Source File +# Begin Source File + +SOURCE=.\ProgramLocation.h +# End Source File +# Begin Source File + +SOURCE=.\UpdateCallback100.cpp +# End Source File +# Begin Source File + +SOURCE=.\UpdateCallback100.h +# End Source File +# End Group +# Begin Group "7-Zip Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Common\CreateCoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\CreateCoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FilePathAutoRename.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FilePathAutoRename.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FileStreams.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FileStreams.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FilterCoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FilterCoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\LimitedStreams.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\LimitedStreams.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\MethodProps.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\MethodProps.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\ProgressUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\ProgressUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\PropId.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamObjects.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamObjects.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\UniqBlocks.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\UniqBlocks.h +# End Source File +# End Group +# Begin Group "C" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\..\C\7zTypes.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zVersion.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zWindows.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Alloc.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Alloc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Compiler.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\CpuArch.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\CpuArch.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\DllSecur.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\DllSecur.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Precomp.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Sort.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Sort.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Threads.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Threads.h +# End Source File +# End Group +# Begin Group "Windows" + +# PROP Default_Filter "" +# Begin Group "Control" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\Windows\Control\ComboBox.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\ComboBox.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\CommandBar.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\Dialog.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\Dialog.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\Edit.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\ImageList.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\ListView.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\ListView.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\ProgressBar.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\PropertyPage.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\PropertyPage.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\ReBar.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\Static.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\StatusBar.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\ToolBar.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\Window2.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\Window2.h +# End Source File +# End Group +# Begin Source File + +SOURCE=..\..\..\Windows\Clipboard.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Clipboard.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\COM.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\CommonDialog.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\CommonDialog.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Defs.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Device.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\DLL.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\DLL.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\ErrorMsg.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\ErrorMsg.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileDir.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileDir.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileFind.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileFind.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileIO.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileIO.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileLink.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileMapping.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileName.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileName.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileSystem.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileSystem.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Handle.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\MemoryGlobal.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\MemoryGlobal.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\MemoryLock.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\MemoryLock.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Menu.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Menu.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Net.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Net.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\NtCheck.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\ProcessUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\ProcessUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\PropVariant.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\PropVariant.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\PropVariantConv.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\PropVariantConv.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Registry.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Registry.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\ResourceString.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\ResourceString.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\SecurityUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\SecurityUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Shell.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Shell.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Synchronization.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Synchronization.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\System.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\System.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Thread.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\TimeUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\TimeUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Window.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Window.h +# End Source File +# End Group +# Begin Group "Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\Common\AutoPtr.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Common.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Common0.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\ComTry.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Defs.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\DynamicBuffer.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\DynLimBuf.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\DynLimBuf.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Exception.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\IntToString.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\IntToString.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Lang.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Lang.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyBuffer.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyCom.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyInitGuid.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyString.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyString.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyTypes.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyVector.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyVector.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyWindows.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\NewHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\NewHandler.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Random.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Random.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StringConvert.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StringConvert.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StringToInt.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StringToInt.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\UTFConvert.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\UTFConvert.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Wildcard.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Wildcard.h +# End Source File +# End Group +# Begin Group "UI" + +# PROP Default_Filter "" +# Begin Group "UI Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\Common\ArchiveExtractCallback.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\ArchiveExtractCallback.h +# End Source File +# Begin Source File + +SOURCE=..\Common\ArchiveName.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\ArchiveName.h +# End Source File +# Begin Source File + +SOURCE=..\Common\ArchiveOpenCallback.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\ArchiveOpenCallback.h +# End Source File +# Begin Source File + +SOURCE=..\Common\CompressCall.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\CompressCall.h +# End Source File +# Begin Source File + +SOURCE=..\Common\DefaultName.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\DefaultName.h +# End Source File +# Begin Source File + +SOURCE=..\Common\DirItem.h +# End Source File +# Begin Source File + +SOURCE=..\Common\EnumDirItems.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\EnumDirItems.h +# End Source File +# Begin Source File + +SOURCE=..\Common\ExitCode.h +# End Source File +# Begin Source File + +SOURCE=..\Common\ExtractingFilePath.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\ExtractingFilePath.h +# End Source File +# Begin Source File + +SOURCE=..\Common\ExtractMode.h +# End Source File +# Begin Source File + +SOURCE=..\Common\HashCalc.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\HashCalc.h +# End Source File +# Begin Source File + +SOURCE=..\Common\IFileExtractCallback.h +# End Source File +# Begin Source File + +SOURCE=..\Common\LoadCodecs.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\LoadCodecs.h +# End Source File +# Begin Source File + +SOURCE=..\Common\OpenArchive.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\OpenArchive.h +# End Source File +# Begin Source File + +SOURCE=..\Common\Property.h +# End Source File +# Begin Source File + +SOURCE=..\Common\PropIDUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\PropIDUtils.h +# End Source File +# Begin Source File + +SOURCE=..\Common\SetProperties.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\SetProperties.h +# End Source File +# Begin Source File + +SOURCE=..\Common\SortUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\SortUtils.h +# End Source File +# Begin Source File + +SOURCE=..\Common\StdAfx.h +# End Source File +# Begin Source File + +SOURCE=..\Common\UpdateAction.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\UpdateAction.h +# End Source File +# Begin Source File + +SOURCE=..\Common\UpdateCallback.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\UpdateCallback.h +# End Source File +# Begin Source File + +SOURCE=..\Common\UpdatePair.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\UpdatePair.h +# End Source File +# Begin Source File + +SOURCE=..\Common\UpdateProduce.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\UpdateProduce.h +# End Source File +# Begin Source File + +SOURCE=..\Common\WorkDir.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\WorkDir.h +# End Source File +# Begin Source File + +SOURCE=..\Common\ZipRegistry.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\ZipRegistry.h +# End Source File +# End Group +# Begin Group "Agent" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\Agent\Agent.cpp +# End Source File +# Begin Source File + +SOURCE=..\Agent\Agent.h +# End Source File +# Begin Source File + +SOURCE=..\Agent\AgentOut.cpp +# End Source File +# Begin Source File + +SOURCE=..\Agent\AgentProxy.cpp +# End Source File +# Begin Source File + +SOURCE=..\Agent\AgentProxy.h +# End Source File +# Begin Source File + +SOURCE=..\Agent\ArchiveFolder.cpp +# End Source File +# Begin Source File + +SOURCE=..\Agent\ArchiveFolderOpen.cpp +# End Source File +# Begin Source File + +SOURCE=..\Agent\ArchiveFolderOut.cpp +# End Source File +# Begin Source File + +SOURCE=..\Agent\IFolderArchive.h +# End Source File +# Begin Source File + +SOURCE=..\Agent\UpdateCallbackAgent.cpp +# End Source File +# Begin Source File + +SOURCE=..\Agent\UpdateCallbackAgent.h +# End Source File +# End Group +# Begin Group "Explorer" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\Explorer\ContextMenu.cpp +# End Source File +# Begin Source File + +SOURCE=..\Explorer\ContextMenu.h +# End Source File +# Begin Source File + +SOURCE=..\Explorer\ContextMenuFlags.h +# End Source File +# Begin Source File + +SOURCE=..\Explorer\MyExplorerCommand.h +# End Source File +# Begin Source File + +SOURCE=..\Explorer\MyMessages.cpp +# End Source File +# Begin Source File + +SOURCE=..\Explorer\MyMessages.h +# End Source File +# Begin Source File + +SOURCE=..\Explorer\RegistryContextMenu.cpp +# End Source File +# Begin Source File + +SOURCE=..\Explorer\RegistryContextMenu.h +# End Source File +# End Group +# Begin Group "GUI" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\GUI\HashGUI.cpp +# End Source File +# Begin Source File + +SOURCE=..\GUI\HashGUI.h +# End Source File +# Begin Source File + +SOURCE=..\GUI\UpdateCallbackGUI2.cpp +# End Source File +# Begin Source File + +SOURCE=..\GUI\UpdateCallbackGUI2.h +# End Source File +# End Group +# End Group +# Begin Group "Compress" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Compress\CopyCoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\CopyCoder.h +# End Source File +# End Group +# Begin Group "Interface" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Archive\IArchive.h +# End Source File +# Begin Source File + +SOURCE=..\..\ICoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\IDecl.h +# End Source File +# Begin Source File + +SOURCE=..\..\IPassword.h +# End Source File +# Begin Source File + +SOURCE=..\..\IProgress.h +# End Source File +# Begin Source File + +SOURCE=..\..\IStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\PropID.h +# End Source File +# End Group +# Begin Group "ArchiveCommon" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Archive\Common\ItemNameUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\ItemNameUtils.h +# End Source File +# End Group +# Begin Source File + +SOURCE=.\7zFM.exe.manifest +# End Source File +# Begin Source File + +SOURCE=.\7zipLogo.ico +# End Source File +# Begin Source File + +SOURCE=.\Add2.bmp +# End Source File +# Begin Source File + +SOURCE=.\Copy2.bmp +# End Source File +# Begin Source File + +SOURCE=.\Delete2.bmp +# End Source File +# Begin Source File + +SOURCE=.\Extract2.bmp +# End Source File +# Begin Source File + +SOURCE=.\FilePlugins.cpp +# End Source File +# Begin Source File + +SOURCE=.\FilePlugins.h +# End Source File +# Begin Source File + +SOURCE=.\FM.cpp +# End Source File +# Begin Source File + +SOURCE=.\Info.bmp +# End Source File +# Begin Source File + +SOURCE=.\Info2.bmp +# End Source File +# Begin Source File + +SOURCE=.\Move2.bmp +# End Source File +# Begin Source File + +SOURCE=.\MyCom2.h +# End Source File +# Begin Source File + +SOURCE=.\MyLoadMenu.cpp +# End Source File +# Begin Source File + +SOURCE=.\MyLoadMenu.h +# End Source File +# Begin Source File + +SOURCE=.\OpenCallback.cpp +# End Source File +# Begin Source File + +SOURCE=.\OpenCallback.h +# End Source File +# Begin Source File + +SOURCE=.\PluginInterface.h +# End Source File +# Begin Source File + +SOURCE=.\PluginLoader.h +# End Source File +# Begin Source File + +SOURCE=.\PropertyName.cpp +# End Source File +# Begin Source File + +SOURCE=.\PropertyName.h +# End Source File +# Begin Source File + +SOURCE=.\resource.h +# End Source File +# Begin Source File + +SOURCE=.\SplitUtils.cpp +# End Source File +# Begin Source File + +SOURCE=.\SplitUtils.h +# End Source File +# Begin Source File + +SOURCE=.\StringUtils.cpp +# End Source File +# Begin Source File + +SOURCE=.\StringUtils.h +# End Source File +# Begin Source File + +SOURCE=.\SysIconUtils.cpp +# End Source File +# Begin Source File + +SOURCE=.\SysIconUtils.h +# End Source File +# Begin Source File + +SOURCE=.\Test2.bmp +# End Source File +# Begin Source File + +SOURCE=.\TextPairs.cpp +# End Source File +# Begin Source File + +SOURCE=.\TextPairs.h +# End Source File +# End Target +# End Project diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FM.dsw b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FM.dsw new file mode 100644 index 00000000..1c955d95 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FM.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "FM"=.\FM.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FM.ico b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FM.ico new file mode 100644 index 00000000..3a0a34da Binary files /dev/null and b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FM.ico differ diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FM.mak b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FM.mak new file mode 100644 index 00000000..bee78882 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FM.mak @@ -0,0 +1,102 @@ +CFLAGS = $(CFLAGS) \ + -DZ7_LANG \ + +!IFDEF UNDER_CE +LIBS = $(LIBS) ceshell.lib Commctrl.lib +!ELSE +LIBS = $(LIBS) comctl32.lib htmlhelp.lib comdlg32.lib Mpr.lib Gdi32.lib +CFLAGS = $(CFLAGS) -DZ7_DEVICE_FILE +LFLAGS = $(LFLAGS) /DELAYLOAD:mpr.dll +LIBS = $(LIBS) delayimp.lib +!ENDIF + +FM_OBJS = \ + $O\App.obj \ + $O\BrowseDialog.obj \ + $O\BrowseDialog2.obj \ + $O\ClassDefs.obj \ + $O\EnumFormatEtc.obj \ + $O\ExtractCallback.obj \ + $O\FileFolderPluginOpen.obj \ + $O\FilePlugins.obj \ + $O\FM.obj \ + $O\FoldersPage.obj \ + $O\FormatUtils.obj \ + $O\FSFolder.obj \ + $O\FSFolderCopy.obj \ + $O\HelpUtils.obj \ + $O\LangUtils.obj \ + $O\MemDialog.obj \ + $O\MenuPage.obj \ + $O\MyLoadMenu.obj \ + $O\OpenCallback.obj \ + $O\OptionsDialog.obj \ + $O\Panel.obj \ + $O\PanelCopy.obj \ + $O\PanelCrc.obj \ + $O\PanelDrag.obj \ + $O\PanelFolderChange.obj \ + $O\PanelItemOpen.obj \ + $O\PanelItems.obj \ + $O\PanelKey.obj \ + $O\PanelListNotify.obj \ + $O\PanelMenu.obj \ + $O\PanelOperations.obj \ + $O\PanelSelect.obj \ + $O\PanelSort.obj \ + $O\PanelSplitFile.obj \ + $O\ProgramLocation.obj \ + $O\PropertyName.obj \ + $O\RegistryAssociations.obj \ + $O\RegistryUtils.obj \ + $O\RootFolder.obj \ + $O\SplitUtils.obj \ + $O\StringUtils.obj \ + $O\SysIconUtils.obj \ + $O\TextPairs.obj \ + $O\UpdateCallback100.obj \ + $O\ViewSettings.obj \ + $O\AboutDialog.obj \ + $O\ComboDialog.obj \ + $O\CopyDialog.obj \ + $O\EditDialog.obj \ + $O\EditPage.obj \ + $O\LangPage.obj \ + $O\ListViewDialog.obj \ + $O\MessagesDialog.obj \ + $O\OverwriteDialog.obj \ + $O\PasswordDialog.obj \ + $O\ProgressDialog2.obj \ + $O\SettingsPage.obj \ + $O\SplitDialog.obj \ + $O\SystemPage.obj \ + $O\VerCtrl.obj \ + +!IFNDEF UNDER_CE + +FM_OBJS = $(FM_OBJS) \ + $O\AltStreamsFolder.obj \ + $O\FSDrives.obj \ + $O\LinkDialog.obj \ + $O\NetFolder.obj \ + +WIN_OBJS = $(WIN_OBJS) \ + $O\FileSystem.obj \ + $O\Net.obj \ + $O\SecurityUtils.obj \ + +!ENDIF + +C_OBJS = $(C_OBJS) \ + $O\DllSecur.obj \ + +AGENT_OBJS = \ + $O\Agent.obj \ + $O\AgentOut.obj \ + $O\AgentProxy.obj \ + $O\ArchiveFolder.obj \ + $O\ArchiveFolderOpen.obj \ + $O\ArchiveFolderOut.obj \ + $O\UpdateCallbackAgent.obj \ + +# we need empty line after last line above diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FSDrives.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FSDrives.cpp new file mode 100644 index 00000000..70354c7f --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FSDrives.cpp @@ -0,0 +1,512 @@ +// FSDrives.cpp + +#include "StdAfx.h" + +#include "../../../../C/Alloc.h" + +#include "../../../Common/ComTry.h" +#include "../../../Common/Defs.h" +#include "../../../Common/IntToString.h" +#include "../../../Common/StringConvert.h" + +#include "../../../Windows/FileDir.h" +#include "../../../Windows/FileIO.h" +#include "../../../Windows/FileName.h" +#include "../../../Windows/FileSystem.h" +#include "../../../Windows/PropVariant.h" + +#include "../../PropID.h" + +#include "FSDrives.h" +#include "FSFolder.h" +#include "LangUtils.h" +#include "SysIconUtils.h" + +#include "resource.h" + +using namespace NWindows; +using namespace NFile; +using namespace NFind; + +static const char * const kVolPrefix = "\\\\.\\"; +static const char * const kSuperPrefix = "\\\\?\\"; + +FString CDriveInfo::GetDeviceFileIoName() const +{ + FString f (kVolPrefix); + f += Name; + return f; +} + +struct CPhysTempBuffer +{ + void *buffer; + CPhysTempBuffer(): buffer(NULL) {} + ~CPhysTempBuffer() { MidFree(buffer); } +}; + +static HRESULT CopyFileSpec(CFSTR fromPath, CFSTR toPath, + bool writeToDisk, UInt64 fileSize, + UInt32 bufferSize, UInt64 progressStart, IProgress *progress) +{ + NIO::CInFile inFile; + if (!inFile.Open(fromPath)) + return GetLastError_noZero_HRESULT(); + if (fileSize == (UInt64)(Int64)-1) + { + if (!inFile.GetLength(fileSize)) + return GetLastError_noZero_HRESULT(); + } + + NIO::COutFile outFile; + if (writeToDisk) + { + if (!outFile.Open(toPath, FILE_SHARE_WRITE, OPEN_EXISTING, 0)) + return GetLastError_noZero_HRESULT(); + } + else + if (!outFile.Create_ALWAYS(toPath)) + return GetLastError_noZero_HRESULT(); + + CPhysTempBuffer tempBuffer; + tempBuffer.buffer = MidAlloc(bufferSize); + if (!tempBuffer.buffer) + return E_OUTOFMEMORY; + + for (UInt64 pos = 0; pos < fileSize;) + { + { + const UInt64 progressCur = progressStart + pos; + RINOK(progress->SetCompleted(&progressCur)) + } + const UInt64 rem = fileSize - pos; + UInt32 curSize = (UInt32)MyMin(rem, (UInt64)bufferSize); + UInt32 processedSize; + if (!inFile.Read(tempBuffer.buffer, curSize, processedSize)) + return GetLastError_noZero_HRESULT(); + if (processedSize == 0) + break; + curSize = processedSize; + if (writeToDisk) + { + const UInt32 kMask = 0x1FF; + curSize = (curSize + kMask) & ~kMask; + if (curSize > bufferSize) + return E_FAIL; + } + if (!outFile.Write(tempBuffer.buffer, curSize, processedSize)) + return GetLastError_noZero_HRESULT(); + if (curSize != processedSize) + return E_FAIL; + pos += curSize; + } + + return S_OK; +} + +static const Byte kProps[] = +{ + kpidName, + // kpidOutName, + kpidTotalSize, + kpidFreeSpace, + kpidType, + kpidVolumeName, + kpidFileSystem, + kpidClusterSize +}; + +static const char * const kDriveTypes[] = +{ + "Unknown" + , "No Root Dir" + , "Removable" + , "Fixed" + , "Remote" + , "CD-ROM" + , "RAM disk" +}; + +Z7_COM7F_IMF(CFSDrives::LoadItems()) +{ + _drives.Clear(); + + FStringVector driveStrings; + MyGetLogicalDriveStrings(driveStrings); + + FOR_VECTOR (i, driveStrings) + { + CDriveInfo di; + const FString &driveName = driveStrings[i]; + di.FullSystemName = driveName; + if (!driveName.IsEmpty()) + di.Name.SetFrom(driveName, driveName.Len() - 1); + di.ClusterSize = 0; + di.DriveSize = 0; + di.FreeSpace = 0; + di.DriveType = NSystem::MyGetDriveType(driveName); + bool needRead = true; + + if (di.DriveType == DRIVE_CDROM || di.DriveType == DRIVE_REMOVABLE) + { + /* + DWORD dwSerialNumber;` + if (!::GetVolumeInformation(di.FullSystemName, + NULL, 0, &dwSerialNumber, NULL, NULL, NULL, 0)) + */ + { + needRead = false; + } + } + + if (needRead) + { + DWORD volumeSerialNumber, maximumComponentLength, fileSystemFlags; + NSystem::MyGetVolumeInformation(driveName, + di.VolumeName, + &volumeSerialNumber, &maximumComponentLength, &fileSystemFlags, + di.FileSystemName); + + NSystem::MyGetDiskFreeSpace(driveName, + di.ClusterSize, di.DriveSize, di.FreeSpace); + di.KnownSizes = true; + di.KnownSize = true; + } + + _drives.Add(di); + } + + if (_volumeMode) + { + // we must use IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS + for (unsigned n = 0; n < 16; n++) // why 16 ? + { + FString name ("PhysicalDrive"); + name.Add_UInt32(n); + FString fullPath (kVolPrefix); + fullPath += name; + CFileInfo fi; + if (!fi.Find(fullPath)) + continue; + + CDriveInfo di; + di.Name = name; + // if (_volumeMode == true) we use CDriveInfo::FullSystemName only in GetSystemIconIndex(). + // And we need name without "\\\\.\\" prefix in GetSystemIconIndex(). + // So we don't set di.FullSystemName = fullPath; + di.FullSystemName = name; + di.ClusterSize = 0; + di.DriveSize = fi.Size; + di.FreeSpace = 0; + di.DriveType = 0; + di.IsPhysicalDrive = true; + di.KnownSize = true; + _drives.Add(di); + } + } + + return S_OK; +} + +Z7_COM7F_IMF(CFSDrives::GetNumberOfItems(UInt32 *numItems)) +{ + *numItems = _drives.Size(); + return S_OK; +} + +Z7_COM7F_IMF(CFSDrives::GetProperty(UInt32 itemIndex, PROPID propID, PROPVARIANT *value)) +{ + if (itemIndex >= _drives.Size()) + return E_INVALIDARG; + NCOM::CPropVariant prop; + const CDriveInfo &di = _drives[itemIndex]; + switch (propID) + { + case kpidIsDir: prop = !_volumeMode; break; + case kpidName: prop = fs2us(di.Name); break; + case kpidOutName: + if (!di.Name.IsEmpty() && di.Name.Back() == ':') + { + FString s = di.Name; + s.DeleteBack(); + AddExt(s, itemIndex); + prop = fs2us(s); + } + break; + + case kpidTotalSize: if (di.KnownSize) prop = di.DriveSize; break; + case kpidFreeSpace: if (di.KnownSizes) prop = di.FreeSpace; break; + case kpidClusterSize: if (di.KnownSizes) prop = di.ClusterSize; break; + case kpidType: + if (di.DriveType < Z7_ARRAY_SIZE(kDriveTypes)) + prop = kDriveTypes[di.DriveType]; + break; + case kpidVolumeName: prop = di.VolumeName; break; + case kpidFileSystem: prop = di.FileSystemName; break; + } + prop.Detach(value); + return S_OK; +} + +HRESULT CFSDrives::BindToFolderSpec(CFSTR name, IFolderFolder **resultFolder) +{ + *resultFolder = NULL; + if (_volumeMode) + return S_OK; + NFsFolder::CFSFolder *fsFolderSpec = new NFsFolder::CFSFolder; + CMyComPtr subFolder = fsFolderSpec; + FString path; + if (_superMode) + path = kSuperPrefix; + path += name; + RINOK(fsFolderSpec->Init(path)) + *resultFolder = subFolder.Detach(); + return S_OK; +} + +Z7_COM7F_IMF(CFSDrives::BindToFolder(UInt32 index, IFolderFolder **resultFolder)) +{ + *resultFolder = NULL; + if (index >= _drives.Size()) + return E_INVALIDARG; + const CDriveInfo &di = _drives[index]; + /* + if (_volumeMode) + { + *resultFolder = 0; + CPhysDriveFolder *folderSpec = new CPhysDriveFolder; + CMyComPtr subFolder = folderSpec; + RINOK(folderSpec->Init(di.Name)); + *resultFolder = subFolder.Detach(); + return S_OK; + } + */ + return BindToFolderSpec(di.FullSystemName, resultFolder); +} + +Z7_COM7F_IMF(CFSDrives::BindToFolder(const wchar_t *name, IFolderFolder **resultFolder)) +{ + return BindToFolderSpec(us2fs(name), resultFolder); +} + +Z7_COM7F_IMF(CFSDrives::BindToParentFolder(IFolderFolder **resultFolder)) +{ + *resultFolder = NULL; + return S_OK; +} + +IMP_IFolderFolder_Props(CFSDrives) + +Z7_COM7F_IMF(CFSDrives::GetFolderProperty(PROPID propID, PROPVARIANT *value)) +{ + COM_TRY_BEGIN + NCOM::CPropVariant prop; + switch (propID) + { + case kpidType: prop = "FSDrives"; break; + case kpidPath: + if (_volumeMode) + prop = kVolPrefix; + else if (_superMode) + prop = kSuperPrefix; + else + prop = (UString)LangString(IDS_COMPUTER) + WCHAR_PATH_SEPARATOR; + break; + } + prop.Detach(value); + return S_OK; + COM_TRY_END +} + + +Z7_COM7F_IMF(CFSDrives::GetSystemIconIndex(UInt32 index, Int32 *iconIndex)) +{ + *iconIndex = -1; + const CDriveInfo &di = _drives[index]; + return Shell_GetFileInfo_SysIconIndex_for_Path_return_HRESULT( + di.FullSystemName, + _volumeMode ? + FILE_ATTRIBUTE_ARCHIVE: + FILE_ATTRIBUTE_DIRECTORY, + iconIndex); +} + +void CFSDrives::AddExt(FString &s, unsigned index) const +{ + s.Add_Dot(); + const CDriveInfo &di = _drives[index]; + UString n = di.FileSystemName; + n.MakeLower_Ascii(); + const char *ext; + if (di.DriveType == DRIVE_CDROM) + ext = "iso"; + else + { + unsigned i; + for (i = 0; i < n.Len(); i++) + { + const wchar_t c = n[i]; + if (c < 'a' || c > 'z') + break; + } + if (i != 0) + { + n.DeleteFrom(i); + s += us2fs(n); + return; + } + ext = "img"; + } + /* + if (n.IsPrefixedBy_Ascii_NoCase("NTFS")) ext = "ntfs"; + else if (n.IsPrefixedBy_Ascii_NoCase("UDF")) ext = "udf"; + else if (n.IsPrefixedBy_Ascii_NoCase("exFAT")) ext = "exfat"; + */ + s += ext; +} + +HRESULT CFSDrives::GetFileSize(unsigned index, UInt64& fileSize) const +{ +#ifdef Z7_DEVICE_FILE + NIO::CInFile inFile; + if (!inFile.Open(_drives[index].GetDeviceFileIoName())) + return GetLastError_noZero_HRESULT(); + if (inFile.SizeDefined) + { + fileSize = inFile.Size; + return S_OK; + } +#else + UNUSED_VAR(index) +#endif + fileSize = 0; + return E_FAIL; +} + +Z7_COM7F_IMF(CFSDrives::CopyTo(Int32 moveMode, const UInt32 *indices, UInt32 numItems, + Int32 /* includeAltStreams */, Int32 /* replaceAltStreamColon */, + const wchar_t *path, IFolderOperationsExtractCallback *callback)) +{ + if (numItems == 0) + return S_OK; + if (moveMode) + return E_NOTIMPL; + if (!_volumeMode) + return E_NOTIMPL; + + UInt64 totalSize = 0; + UInt32 i; + for (i = 0; i < numItems; i++) + { + const CDriveInfo &di = _drives[indices[i]]; + if (di.KnownSize) + totalSize += di.DriveSize; + } + RINOK(callback->SetTotal(totalSize)) + RINOK(callback->SetNumFiles(numItems)) + + const FString destPath = us2fs(path); + if (destPath.IsEmpty()) + return E_INVALIDARG; + + const bool isAltDest = NName::IsAltPathPrefix(destPath); + const bool isDirectPath = (!isAltDest && !IsPathSepar(destPath.Back())); + + if (isDirectPath) + { + if (numItems > 1) + return E_INVALIDARG; + } + + UInt64 completedSize = 0; + RINOK(callback->SetCompleted(&completedSize)) + for (i = 0; i < numItems; i++) + { + const unsigned index = indices[i]; + const CDriveInfo &di = _drives[index]; + FString destPath2 = destPath; + + if (!isDirectPath) + { + FString destName = di.Name; + if (!destName.IsEmpty() && destName.Back() == ':') + { + destName.DeleteBack(); + AddExt(destName, index); + } + destPath2 += destName; + } + + const FString srcPath = di.GetDeviceFileIoName(); + + UInt64 fileSize = 0; + if (GetFileSize(index, fileSize) != S_OK) + { + return E_FAIL; + } + if (!di.KnownSize) + { + totalSize += fileSize; + RINOK(callback->SetTotal(totalSize)) + } + + Int32 writeAskResult; + CMyComBSTR destPathResult; + RINOK(callback->AskWrite(fs2us(srcPath), BoolToInt(false), NULL, &fileSize, + fs2us(destPath2), &destPathResult, &writeAskResult)) + + if (!IntToBool(writeAskResult)) + { + if (totalSize >= fileSize) + totalSize -= fileSize; + RINOK(callback->SetTotal(totalSize)) + continue; + } + + RINOK(callback->SetCurrentFilePath(fs2us(srcPath))) + + const UInt32 kBufferSize = (4 << 20); + const UInt32 bufferSize = (di.DriveType == DRIVE_REMOVABLE) ? (18 << 10) * 4 : kBufferSize; + RINOK(CopyFileSpec(srcPath, us2fs(destPathResult), false, fileSize, bufferSize, completedSize, callback)) + completedSize += fileSize; + } + + return S_OK; +} + +Z7_COM7F_IMF(CFSDrives::CopyFrom(Int32 /* moveMode */, const wchar_t * /* fromFolderPath */, + const wchar_t * const * /* itemsPaths */, UInt32 /* numItems */, IProgress * /* progress */)) +{ + return E_NOTIMPL; +} + +Z7_COM7F_IMF(CFSDrives::CopyFromFile(UInt32 /* index */, const wchar_t * /* fullFilePath */, IProgress * /* progress */)) +{ + return E_NOTIMPL; +} + +Z7_COM7F_IMF(CFSDrives::CreateFolder(const wchar_t * /* name */, IProgress * /* progress */)) +{ + return E_NOTIMPL; +} + +Z7_COM7F_IMF(CFSDrives::CreateFile(const wchar_t * /* name */, IProgress * /* progress */)) +{ + return E_NOTIMPL; +} + +Z7_COM7F_IMF(CFSDrives::Rename(UInt32 /* index */, const wchar_t * /* newName */, IProgress * /* progress */)) +{ + return E_NOTIMPL; +} + +Z7_COM7F_IMF(CFSDrives::Delete(const UInt32 * /* indices */, UInt32 /* numItems */, IProgress * /* progress */)) +{ + return E_NOTIMPL; +} + +Z7_COM7F_IMF(CFSDrives::SetProperty(UInt32 /* index */, PROPID /* propID */, + const PROPVARIANT * /* value */, IProgress * /* progress */)) +{ + return E_NOTIMPL; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FSDrives.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FSDrives.h new file mode 100644 index 00000000..8ae831cf --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FSDrives.h @@ -0,0 +1,52 @@ +// FSDrives.h + +#ifndef ZIP7_INC_FS_DRIVES_H +#define ZIP7_INC_FS_DRIVES_H + +#include "../../../Common/MyCom.h" +#include "../../../Common/MyString.h" + +#include "IFolder.h" + +struct CDriveInfo +{ + FString Name; + FString FullSystemName; + UInt64 DriveSize; + UInt64 FreeSpace; + UInt64 ClusterSize; + // UString Type; + UString VolumeName; + UString FileSystemName; + UINT DriveType; + + bool KnownSize; + bool KnownSizes; + bool IsPhysicalDrive; + + FString GetDeviceFileIoName() const; + CDriveInfo(): KnownSize(false), KnownSizes(false), IsPhysicalDrive(false) {} +}; + +Z7_CLASS_IMP_NOQIB_3( + CFSDrives + , IFolderFolder + , IFolderOperations + , IFolderGetSystemIconIndex +) + CObjectVector _drives; + bool _volumeMode; + bool _superMode; + + HRESULT BindToFolderSpec(CFSTR name, IFolderFolder **resultFolder); + void AddExt(FString &s, unsigned index) const; + HRESULT GetFileSize(unsigned index, UInt64 &fileSize) const; +public: + void Init(bool volMode = false, bool superMode = false) + { + _volumeMode = volMode; + _superMode = superMode; + } +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FSFolder.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FSFolder.cpp new file mode 100644 index 00000000..51dfaa94 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FSFolder.cpp @@ -0,0 +1,1199 @@ +// FSFolder.cpp + +#include "StdAfx.h" + +#ifdef __MINGW32_VERSION +// #if !defined(_MSC_VER) && (__GNUC__) && (__GNUC__ < 10) +// for old mingw +#include +#else +#ifndef Z7_OLD_WIN_SDK + #if !defined(_M_IA64) + #include + #endif +#else +typedef LONG NTSTATUS; +typedef struct _IO_STATUS_BLOCK { + union { + NTSTATUS Status; + PVOID Pointer; + }; + ULONG_PTR Information; +} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK; +#endif +#endif + +#include "../../../Common/ComTry.h" +#include "../../../Common/Defs.h" +#include "../../../Common/StringConvert.h" +#include "../../../Common/UTFConvert.h" + +#include "../../../Windows/DLL.h" +#include "../../../Windows/FileDir.h" +#include "../../../Windows/FileIO.h" +#include "../../../Windows/FileName.h" +#include "../../../Windows/PropVariant.h" + +#include "../../PropID.h" + +#include "FSDrives.h" +#include "FSFolder.h" + +#ifndef UNDER_CE +#include "NetFolder.h" +#endif + +#include "SysIconUtils.h" + +#if !defined(_WIN32_WINNT) || _WIN32_WINNT < 0x0501 +#ifdef _APISETFILE_ +// Windows SDK 8.1 defines in fileapi.h the function GetCompressedFileSizeW only if _WIN32_WINNT >= 0x0501 +// But real support version for that function is NT 3.1 (probably) +// So we must define GetCompressedFileSizeW +EXTERN_C_BEGIN +WINBASEAPI DWORD WINAPI GetCompressedFileSizeW(LPCWSTR lpFileName, LPDWORD lpFileSizeHigh); +EXTERN_C_END +#endif +#endif + +using namespace NWindows; +using namespace NFile; +using namespace NFind; +using namespace NDir; +using namespace NName; + +#ifndef USE_UNICODE_FSTRING +int CompareFileNames_ForFolderList(const FChar *s1, const FChar *s2); +int CompareFileNames_ForFolderList(const FChar *s1, const FChar *s2) +{ + return CompareFileNames_ForFolderList(fs2us(s1), fs2us(s2)); +} +#endif + +namespace NFsFolder { + +static const Byte kProps[] = +{ + kpidName, + kpidSize, + kpidMTime, + kpidCTime, + kpidATime, + #ifdef FS_SHOW_LINKS_INFO + kpidChangeTime, + #endif + kpidAttrib, + kpidPackSize, + #ifdef FS_SHOW_LINKS_INFO + kpidINode, + kpidLinks, + #endif + kpidComment, + kpidNumSubDirs, + kpidNumSubFiles, + kpidPrefix +}; + +HRESULT CFSFolder::Init(const FString &path /* , IFolderFolder *parentFolder */) +{ + // _parentFolder = parentFolder; + _path = path; + + #ifdef _WIN32 + + _findChangeNotification.FindFirst(_path, false, + FILE_NOTIFY_CHANGE_FILE_NAME + | FILE_NOTIFY_CHANGE_DIR_NAME + | FILE_NOTIFY_CHANGE_ATTRIBUTES + | FILE_NOTIFY_CHANGE_SIZE + | FILE_NOTIFY_CHANGE_LAST_WRITE + /* + | FILE_NOTIFY_CHANGE_LAST_ACCESS + | FILE_NOTIFY_CHANGE_CREATION + | FILE_NOTIFY_CHANGE_SECURITY + */ + ); + + if (!_findChangeNotification.IsHandleAllocated()) + { + const HRESULT lastError = GetLastError_noZero_HRESULT(); + CFindFile findFile; + CFileInfo fi; + FString path2 = _path; + path2.Add_Char('*'); // CHAR_ANY_MASK; + if (!findFile.FindFirst(path2, fi)) + return lastError; + } + + #endif + + return S_OK; +} + + +HRESULT CFsFolderStat::Enumerate() +{ + if (Progress) + { + RINOK(Progress->SetCompleted(NULL)) + } + Path.Add_PathSepar(); + const unsigned len = Path.Len(); + CEnumerator enumerator; + enumerator.SetDirPrefix(Path); + CDirEntry fi; + while (enumerator.Next(fi)) + { + if (fi.IsDir()) + { + NumFolders++; + Path.DeleteFrom(len); + Path += fi.Name; + RINOK(Enumerate()) + } + else + { + NumFiles++; + Size += fi.Size; + } + } + return S_OK; +} + +#ifndef UNDER_CE + +bool MyGetCompressedFileSizeW(CFSTR path, UInt64 &size); +bool MyGetCompressedFileSizeW(CFSTR path, UInt64 &size) +{ + DWORD highPart; + DWORD lowPart = INVALID_FILE_SIZE; + IF_USE_MAIN_PATH + { + lowPart = ::GetCompressedFileSizeW(fs2us(path), &highPart); + if (lowPart != INVALID_FILE_SIZE || ::GetLastError() == NO_ERROR) + { + size = ((UInt64)highPart << 32) | lowPart; + return true; + } + } + #ifdef Z7_LONG_PATH + if (USE_SUPER_PATH) + { + UString superPath; + if (GetSuperPath(path, superPath, USE_MAIN_PATH)) + { + lowPart = ::GetCompressedFileSizeW(superPath, &highPart); + if (lowPart != INVALID_FILE_SIZE || ::GetLastError() == NO_ERROR) + { + size = ((UInt64)highPart << 32) | lowPart; + return true; + } + } + } + #endif + return false; +} + +#endif + +HRESULT CFSFolder::LoadSubItems(int dirItem, const FString &relPrefix) +{ + const unsigned startIndex = Folders.Size(); + { + CEnumerator enumerator; + enumerator.SetDirPrefix(_path + relPrefix); + CDirItem fi; + fi.FolderStat_Defined = false; + fi.NumFolders = 0; + fi.NumFiles = 0; + fi.Parent = dirItem; + + while (enumerator.Next(fi)) + { + if (fi.IsDir()) + { + fi.Size = 0; + if (_flatMode) + Folders.Add(relPrefix + fi.Name + FCHAR_PATH_SEPARATOR); + } + else + { + /* + fi.PackSize_Defined = true; + if (!MyGetCompressedFileSizeW(_path + relPrefix + fi.Name, fi.PackSize)) + fi.PackSize = fi.Size; + */ + } + + #ifndef UNDER_CE + + fi.Reparse.Free(); + fi.PackSize_Defined = false; + + #ifdef FS_SHOW_LINKS_INFO + fi.FileInfo_Defined = false; + fi.FileInfo_WasRequested = false; + fi.FileIndex = 0; + fi.NumLinks = 0; + fi.ChangeTime_Defined = false; + fi.ChangeTime_WasRequested = false; + #endif + + fi.PackSize = fi.Size; + + #ifdef FS_SHOW_LINKS_INFO + if (fi.HasReparsePoint()) + { + fi.FileInfo_WasRequested = true; + BY_HANDLE_FILE_INFORMATION info; + NIO::GetReparseData(_path + relPrefix + fi.Name, fi.Reparse, &info); + fi.NumLinks = info.nNumberOfLinks; + fi.FileIndex = (((UInt64)info.nFileIndexHigh) << 32) + info.nFileIndexLow; + fi.FileInfo_Defined = true; + } + #endif + + #endif // UNDER_CE + + /* unsigned fileIndex = */ Files.Add(fi); + + #if defined(_WIN32) && !defined(UNDER_CE) + /* + if (_scanAltStreams) + { + CStreamEnumerator enumerator(_path + relPrefix + fi.Name); + CStreamInfo si; + for (;;) + { + bool found; + if (!enumerator.Next(si, found)) + { + // if (GetLastError() == ERROR_ACCESS_DENIED) + // break; + // return E_FAIL; + break; + } + if (!found) + break; + if (si.IsMainStream()) + continue; + CAltStream ss; + ss.Parent = fileIndex; + ss.Name = si.GetReducedName(); + ss.Size = si.Size; + ss.PackSize_Defined = false; + ss.PackSize = si.Size; + Streams.Add(ss); + } + } + */ + #endif + } + } + if (!_flatMode) + return S_OK; + + const unsigned endIndex = Folders.Size(); + for (unsigned i = startIndex; i < endIndex; i++) + LoadSubItems((int)i, Folders[i]); + return S_OK; +} + +Z7_COM7F_IMF(CFSFolder::LoadItems()) +{ + Int32 dummy; + WasChanged(&dummy); + Clear(); + RINOK(LoadSubItems(-1, FString())) + _commentsAreLoaded = false; + return S_OK; +} + +static CFSTR const kDescriptionFileName = FTEXT("descript.ion"); + +bool CFSFolder::LoadComments() +{ + _comments.Clear(); + _commentsAreLoaded = true; + NIO::CInFile file; + if (!file.Open(_path + kDescriptionFileName)) + return false; + UInt64 len; + if (!file.GetLength(len)) + return false; + if (len >= (1 << 28)) + return false; + AString s; + char *p = s.GetBuf((unsigned)(size_t)len); + size_t processedSize; + if (!file.ReadFull(p, (unsigned)(size_t)len, processedSize)) + return false; + s.ReleaseBuf_CalcLen((unsigned)(size_t)len); + if (processedSize != len) + return false; + file.Close(); + UString unicodeString; + if (!ConvertUTF8ToUnicode(s, unicodeString)) + return false; + return _comments.ReadFromString(unicodeString); +} + +bool CFSFolder::SaveComments() +{ + AString utf; + { + UString unicode; + _comments.SaveToString(unicode); + ConvertUnicodeToUTF8(unicode, utf); + } + if (!utf.IsAscii()) + utf.Insert(0, "\xEF\xBB\xBF" "\r\n"); + + FString path = _path + kDescriptionFileName; + // We must set same attrib. COutFile::CreateAlways can fail, if file has another attrib. + DWORD attrib = FILE_ATTRIBUTE_NORMAL; + { + CFileInfo fi; + if (fi.Find(path)) + attrib = fi.Attrib; + } + NIO::COutFile file; + if (!file.Create_ALWAYS_with_Attribs(path, attrib)) + return false; + UInt32 processed; + file.Write(utf, utf.Len(), processed); + _commentsAreLoaded = false; + return true; +} + +Z7_COM7F_IMF(CFSFolder::GetNumberOfItems(UInt32 *numItems)) +{ + *numItems = Files.Size() /* + Streams.Size() */; + return S_OK; +} + +#ifdef USE_UNICODE_FSTRING + +Z7_COM7F_IMF(CFSFolder::GetItemPrefix(UInt32 index, const wchar_t **name, unsigned *len)) +{ + *name = NULL; + *len = 0; + /* + if (index >= Files.Size()) + index = Streams[index - Files.Size()].Parent; + */ + CDirItem &fi = Files[index]; + if (fi.Parent >= 0) + { + const FString &fo = Folders[fi.Parent]; + USE_UNICODE_FSTRING + *name = fo; + *len = fo.Len(); + } + return S_OK; +} + +Z7_COM7F_IMF(CFSFolder::GetItemName(UInt32 index, const wchar_t **name, unsigned *len)) +{ + *name = NULL; + *len = 0; + if (index < Files.Size()) + { + CDirItem &fi = Files[index]; + *name = fi.Name; + *len = fi.Name.Len(); + return S_OK; + } + else + { + // const CAltStream &ss = Streams[index - Files.Size()]; + // *name = ss.Name; + // *len = ss.Name.Len(); + // + // change it; + } + return S_OK; +} + +Z7_COM7F_IMF2(UInt64, CFSFolder::GetItemSize(UInt32 index)) +{ + /* + if (index >= Files.Size()) + return Streams[index - Files.Size()].Size; + */ + CDirItem &fi = Files[index]; + return fi.IsDir() ? 0 : fi.Size; +} + +#endif + + +#ifdef FS_SHOW_LINKS_INFO + +bool CFSFolder::ReadFileInfo(CDirItem &di) +{ + di.FileInfo_WasRequested = true; + BY_HANDLE_FILE_INFORMATION info; + memset(&info, 0, sizeof(info)); // for vc6-O2 + if (!NIO::CFileBase::GetFileInformation(_path + GetRelPath(di), &info)) + return false; + di.NumLinks = info.nNumberOfLinks; + di.FileIndex = (((UInt64)info.nFileIndexHigh) << 32) + info.nFileIndexLow; + di.FileInfo_Defined = true; + return true; +} + + +EXTERN_C_BEGIN + +typedef struct +{ + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + ULONG FileAttributes; + UInt32 Reserved; // it's expected for alignment +} +Z7_WIN_FILE_BASIC_INFORMATION; + + +typedef enum +{ + Z7_WIN_FileDirectoryInformation = 1, + Z7_WIN_FileFullDirectoryInformation, + Z7_WIN_FileBothDirectoryInformation, + Z7_WIN_FileBasicInformation +} +Z7_WIN_FILE_INFORMATION_CLASS; + + +#if defined(_WIN32_WINNT) && (_WIN32_WINNT >= 0x0500) && !defined(_M_IA64) +#define Z7_WIN_NTSTATUS NTSTATUS +#define Z7_WIN_IO_STATUS_BLOCK IO_STATUS_BLOCK +#else +typedef LONG Z7_WIN_NTSTATUS; +typedef struct +{ + union + { + Z7_WIN_NTSTATUS Status; + PVOID Pointer; + } DUMMYUNIONNAME; + ULONG_PTR Information; +} Z7_WIN_IO_STATUS_BLOCK; +#endif + + +typedef Z7_WIN_NTSTATUS (WINAPI * Func_NtQueryInformationFile)( + HANDLE handle, Z7_WIN_IO_STATUS_BLOCK *io, + void *ptr, LONG len, Z7_WIN_FILE_INFORMATION_CLASS cls); + +#define MY_STATUS_SUCCESS 0 + +EXTERN_C_END + +static Func_NtQueryInformationFile f_NtQueryInformationFile; +static bool g_NtQueryInformationFile_WasRequested = false; + +Z7_DIAGNOSTIC_IGNORE_CAST_FUNCTION + +void CFSFolder::ReadChangeTime(CDirItem &di) +{ + di.ChangeTime_WasRequested = true; + + if (!g_NtQueryInformationFile_WasRequested) + { + g_NtQueryInformationFile_WasRequested = true; + f_NtQueryInformationFile = Z7_GET_PROC_ADDRESS( + Func_NtQueryInformationFile, ::GetModuleHandleW(L"ntdll.dll"), + "NtQueryInformationFile"); + } + if (!f_NtQueryInformationFile) + return; + + NIO::CInFile file; + if (!file.Open_for_ReadAttributes(_path + GetRelPath(di))) + return; + Z7_WIN_FILE_BASIC_INFORMATION fbi; + Z7_WIN_IO_STATUS_BLOCK IoStatusBlock; + const Z7_WIN_NTSTATUS status = f_NtQueryInformationFile(file.GetHandle(), &IoStatusBlock, + &fbi, sizeof(fbi), Z7_WIN_FileBasicInformation); + if (status != MY_STATUS_SUCCESS) + return; + if (IoStatusBlock.Information != sizeof(fbi)) + return; + di.ChangeTime.dwLowDateTime = fbi.ChangeTime.u.LowPart; + di.ChangeTime.dwHighDateTime = (DWORD)fbi.ChangeTime.u.HighPart; + di.ChangeTime_Defined = true; +} + +#endif // FS_SHOW_LINKS_INFO + + +Z7_COM7F_IMF(CFSFolder::GetProperty(UInt32 index, PROPID propID, PROPVARIANT *value)) +{ + NCOM::CPropVariant prop; + /* + if (index >= Files.Size()) + { + CAltStream &ss = Streams[index - Files.Size()]; + CDirItem &fi = Files[ss.Parent]; + switch (propID) + { + case kpidIsDir: prop = false; break; + case kpidIsAltStream: prop = true; break; + case kpidName: prop = fs2us(fi.Name) + ss.Name; break; + case kpidSize: prop = ss.Size; break; + case kpidPackSize: + #ifdef UNDER_CE + prop = ss.Size; + #else + if (!ss.PackSize_Defined) + { + ss.PackSize_Defined = true; + if (!MyGetCompressedFileSizeW(_path + GetRelPath(fi) + us2fs(ss.Name), ss.PackSize)) + ss.PackSize = ss.Size; + } + prop = ss.PackSize; + #endif + break; + case kpidComment: break; + default: index = ss.Parent; + } + if (index >= Files.Size()) + { + prop.Detach(value); + return S_OK; + } + } + */ + CDirItem &fi = Files[index]; + switch (propID) + { + case kpidIsDir: prop = fi.IsDir(); break; + case kpidIsAltStream: prop = false; break; + case kpidName: prop = fs2us(fi.Name); break; + case kpidSize: if (!fi.IsDir() || fi.FolderStat_Defined) prop = fi.Size; break; + case kpidPackSize: + #ifdef UNDER_CE + prop = fi.Size; + #else + if (!fi.PackSize_Defined) + { + fi.PackSize_Defined = true; + if (fi.IsDir () || !MyGetCompressedFileSizeW(_path + GetRelPath(fi), fi.PackSize)) + fi.PackSize = fi.Size; + } + prop = fi.PackSize; + #endif + break; + + #ifdef FS_SHOW_LINKS_INFO + + case kpidLinks: + #ifdef UNDER_CE + // prop = fi.NumLinks; + #else + if (!fi.FileInfo_WasRequested) + ReadFileInfo(fi); + if (fi.FileInfo_Defined) + prop = fi.NumLinks; + #endif + break; + + case kpidINode: + #ifdef UNDER_CE + // prop = fi.FileIndex; + #else + if (!fi.FileInfo_WasRequested) + ReadFileInfo(fi); + if (fi.FileInfo_Defined) + prop = fi.FileIndex; + #endif + break; + + case kpidChangeTime: + if (!fi.ChangeTime_WasRequested) + ReadChangeTime(fi); + if (fi.ChangeTime_Defined) + prop = fi.ChangeTime; + break; + + #endif + + case kpidAttrib: prop = (UInt32)fi.Attrib; break; + case kpidCTime: prop = fi.CTime; break; + case kpidATime: prop = fi.ATime; break; + case kpidMTime: prop = fi.MTime; break; + case kpidComment: + { + if (!_commentsAreLoaded) + LoadComments(); + UString comment; + if (_comments.GetValue(fs2us(GetRelPath(fi)), comment)) + { + int pos = comment.Find((wchar_t)4); + if (pos >= 0) + comment.DeleteFrom((unsigned)pos); + prop = comment; + } + break; + } + case kpidPrefix: + if (fi.Parent >= 0) + prop = fs2us(Folders[fi.Parent]); + break; + case kpidNumSubDirs: if (fi.IsDir() && fi.FolderStat_Defined) prop = fi.NumFolders; break; + case kpidNumSubFiles: if (fi.IsDir() && fi.FolderStat_Defined) prop = fi.NumFiles; break; + } + prop.Detach(value); + return S_OK; +} + + +// ---------- IArchiveGetRawProps ---------- + + +Z7_COM7F_IMF(CFSFolder::GetNumRawProps(UInt32 *numProps)) +{ + *numProps = 1; + return S_OK; +} + +Z7_COM7F_IMF(CFSFolder::GetRawPropInfo(UInt32 /* index */, BSTR *name, PROPID *propID)) +{ + *name = NULL; + *propID = kpidNtReparse; + return S_OK; +} + +Z7_COM7F_IMF(CFSFolder::GetParent(UInt32 /* index */, UInt32 * /* parent */, UInt32 * /* parentType */)) +{ + return E_FAIL; +} + +Z7_COM7F_IMF(CFSFolder::GetRawProp(UInt32 index, PROPID propID, + const void **data, UInt32 *dataSize, UInt32 *propType)) +{ + #ifdef UNDER_CE + UNUSED(index) + UNUSED(propID) + #endif + + *data = NULL; + *dataSize = 0; + *propType = 0; + + #ifndef UNDER_CE + if (propID == kpidNtReparse) + { + const CDirItem &fi = Files[index]; + const CByteBuffer &buf = fi.Reparse; + if (buf.Size() == 0) + return S_OK; + *data = buf; + *dataSize = (UInt32)buf.Size(); + *propType = NPropDataType::kRaw; + return S_OK; + } + #endif + + return S_OK; +} + + +// returns Position of extension including '.' + +static inline CFSTR GetExtensionPtr(const FString &name) +{ + const int dotPos = name.ReverseFind_Dot(); + return name.Ptr((dotPos < 0) ? name.Len() : (unsigned)dotPos); +} + +Z7_COM7F_IMF2(Int32, CFSFolder::CompareItems(UInt32 index1, UInt32 index2, PROPID propID, Int32 /* propIsRaw */)) +{ + /* + const CAltStream *ss1 = NULL; + const CAltStream *ss2 = NULL; + if (index1 >= Files.Size()) { ss1 = &Streams[index1 - Files.Size()]; index1 = ss1->Parent; } + if (index2 >= Files.Size()) { ss2 = &Streams[index2 - Files.Size()]; index2 = ss2->Parent; } + */ + CDirItem &fi1 = Files[index1]; + CDirItem &fi2 = Files[index2]; + + switch (propID) + { + case kpidName: + { + const int comp = CompareFileNames_ForFolderList(fi1.Name, fi2.Name); + /* + if (comp != 0) + return comp; + if (!ss1) + return ss2 ? -1 : 0; + if (!ss2) + return 1; + return MyStringCompareNoCase(ss1->Name, ss2->Name); + */ + return comp; + } + case kpidSize: + return MyCompare( + /* ss1 ? ss1->Size : */ fi1.Size, + /* ss2 ? ss2->Size : */ fi2.Size); + case kpidAttrib: return MyCompare(fi1.Attrib, fi2.Attrib); + case kpidCTime: return CompareFileTime(&fi1.CTime, &fi2.CTime); + case kpidATime: return CompareFileTime(&fi1.ATime, &fi2.ATime); + case kpidMTime: return CompareFileTime(&fi1.MTime, &fi2.MTime); + case kpidIsDir: + { + const bool isDir1 = /* ss1 ? false : */ fi1.IsDir(); + const bool isDir2 = /* ss2 ? false : */ fi2.IsDir(); + if (isDir1 == isDir2) + return 0; + return isDir1 ? -1 : 1; + } + case kpidPackSize: + { + #ifdef UNDER_CE + return MyCompare(fi1.Size, fi2.Size); + #else + // PackSize can be undefined here + return MyCompare( + /* ss1 ? ss1->PackSize : */ fi1.PackSize, + /* ss2 ? ss2->PackSize : */ fi2.PackSize); + #endif + } + + #ifdef FS_SHOW_LINKS_INFO + case kpidINode: + { + #ifndef UNDER_CE + if (!fi1.FileInfo_WasRequested) ReadFileInfo(fi1); + if (!fi2.FileInfo_WasRequested) ReadFileInfo(fi2); + return MyCompare( + fi1.FileIndex, + fi2.FileIndex); + #endif + } + case kpidLinks: + { + #ifndef UNDER_CE + if (!fi1.FileInfo_WasRequested) ReadFileInfo(fi1); + if (!fi2.FileInfo_WasRequested) ReadFileInfo(fi2); + return MyCompare( + fi1.NumLinks, + fi2.NumLinks); + #endif + } + #endif + + case kpidComment: + { + // change it ! + UString comment1, comment2; + _comments.GetValue(fs2us(GetRelPath(fi1)), comment1); + _comments.GetValue(fs2us(GetRelPath(fi2)), comment2); + return MyStringCompareNoCase(comment1, comment2); + } + case kpidPrefix: + if (fi1.Parent == fi2.Parent) + return 0; + if (fi1.Parent < 0) return -1; + if (fi2.Parent < 0) return 1; + return CompareFileNames_ForFolderList( + Folders[fi1.Parent], + Folders[fi2.Parent]); + case kpidExtension: + return CompareFileNames_ForFolderList( + GetExtensionPtr(fi1.Name), + GetExtensionPtr(fi2.Name)); + } + + return 0; +} + +HRESULT CFSFolder::BindToFolderSpec(CFSTR name, IFolderFolder **resultFolder) +{ + *resultFolder = NULL; + CFSFolder *folderSpec = new CFSFolder; + CMyComPtr subFolder = folderSpec; + RINOK(folderSpec->Init(_path + name + FCHAR_PATH_SEPARATOR)) + *resultFolder = subFolder.Detach(); + return S_OK; +} + +/* +void CFSFolder::GetPrefix(const CDirItem &item, FString &prefix) const +{ + if (item.Parent >= 0) + prefix = Folders[item.Parent]; + else + prefix.Empty(); +} +*/ + +/* +void CFSFolder::GetPrefix(const CDirItem &item, FString &prefix) const +{ + int parent = item.Parent; + + unsigned len = 0; + + while (parent >= 0) + { + const CDirItem &cur = Files[parent]; + len += cur.Name.Len() + 1; + parent = cur.Parent; + } + + wchar_t *p = prefix.GetBuf_SetEnd(len) + len; + parent = item.Parent; + + while (parent >= 0) + { + const CDirItem &cur = Files[parent]; + *(--p) = FCHAR_PATH_SEPARATOR; + p -= cur.Name.Len(); + wmemcpy(p, cur.Name, cur.Name.Len()); + parent = cur.Parent; + } +} +*/ + +FString CFSFolder::GetRelPath(const CDirItem &item) const +{ + if (item.Parent < 0) + return item.Name; + return Folders[item.Parent] + item.Name; +} + +Z7_COM7F_IMF(CFSFolder::BindToFolder(UInt32 index, IFolderFolder **resultFolder)) +{ + *resultFolder = NULL; + const CDirItem &fi = Files[index]; + if (!fi.IsDir()) + return E_INVALIDARG; + return BindToFolderSpec(GetRelPath(fi), resultFolder); +} + +Z7_COM7F_IMF(CFSFolder::BindToFolder(const wchar_t *name, IFolderFolder **resultFolder)) +{ + return BindToFolderSpec(us2fs(name), resultFolder); +} + +Z7_COM7F_IMF(CFSFolder::BindToParentFolder(IFolderFolder **resultFolder)) +{ + *resultFolder = NULL; + /* + if (_parentFolder) + { + CMyComPtr parentFolder = _parentFolder; + *resultFolder = parentFolder.Detach(); + return S_OK; + } + */ + if (_path.IsEmpty()) + return E_INVALIDARG; + + #ifndef UNDER_CE + + if (IsDriveRootPath_SuperAllowed(_path)) + { + CFSDrives *drivesFolderSpec = new CFSDrives; + CMyComPtr drivesFolder = drivesFolderSpec; + drivesFolderSpec->Init(false, IsSuperPath(_path)); + *resultFolder = drivesFolder.Detach(); + return S_OK; + } + + int pos = _path.ReverseFind_PathSepar(); + if (pos < 0 || pos != (int)_path.Len() - 1) + return E_FAIL; + FString parentPath = _path.Left((unsigned)pos); + pos = parentPath.ReverseFind_PathSepar(); + parentPath.DeleteFrom((unsigned)(pos + 1)); + + if (NName::IsDrivePath_SuperAllowed(parentPath)) + { + CFSFolder *parentFolderSpec = new CFSFolder; + CMyComPtr parentFolder = parentFolderSpec; + if (parentFolderSpec->Init(parentPath) == S_OK) + { + *resultFolder = parentFolder.Detach(); + return S_OK; + } + } + + /* + FString parentPathReduced = parentPath.Left(pos); + + pos = parentPathReduced.ReverseFind_PathSepar(); + if (pos == 1) + { + if (!IS_PATH_SEPAR_CHAR(parentPath[0])) + return E_FAIL; + CNetFolder *netFolderSpec = new CNetFolder; + CMyComPtr netFolder = netFolderSpec; + netFolderSpec->Init(fs2us(parentPath)); + *resultFolder = netFolder.Detach(); + return S_OK; + } + */ + + #endif + + return S_OK; +} + +Z7_COM7F_IMF(CFSFolder::GetNumberOfProperties(UInt32 *numProperties)) +{ + *numProperties = Z7_ARRAY_SIZE(kProps); + if (!_flatMode) + (*numProperties)--; + return S_OK; +} + +IMP_IFolderFolder_GetProp(CFSFolder::GetPropertyInfo, kProps) + +Z7_COM7F_IMF(CFSFolder::GetFolderProperty(PROPID propID, PROPVARIANT *value)) +{ + COM_TRY_BEGIN + NWindows::NCOM::CPropVariant prop; + switch (propID) + { + case kpidType: prop = "FSFolder"; break; + case kpidPath: prop = fs2us(_path); break; + } + prop.Detach(value); + return S_OK; + COM_TRY_END +} + +Z7_COM7F_IMF(CFSFolder::WasChanged(Int32 *wasChanged)) +{ + bool wasChangedMain = false; + + #ifdef _WIN32 + + for (;;) + { + if (!_findChangeNotification.IsHandleAllocated()) + break; + DWORD waitResult = ::WaitForSingleObject(_findChangeNotification, 0); + if (waitResult != WAIT_OBJECT_0) + break; + _findChangeNotification.FindNext(); + wasChangedMain = true; + } + + #endif + + *wasChanged = BoolToInt(wasChangedMain); + return S_OK; +} + +Z7_COM7F_IMF(CFSFolder::Clone(IFolderFolder **resultFolder)) +{ + CFSFolder *fsFolderSpec = new CFSFolder; + CMyComPtr folderNew = fsFolderSpec; + fsFolderSpec->Init(_path); + *resultFolder = folderNew.Detach(); + return S_OK; +} + + +/* +HRESULT CFSFolder::GetItemFullSize(unsigned index, UInt64 &size, IProgress *progress) +{ + if (index >= Files.Size()) + { + size = Streams[index - Files.Size()].Size; + return S_OK; + } + const CDirItem &fi = Files[index]; + if (fi.IsDir()) + { + UInt64 numFolders = 0, numFiles = 0; + size = 0; + return GetFolderSize(_path + GetRelPath(fi), numFolders, numFiles, size, progress); + } + size = fi.Size; + return S_OK; +} + +Z7_COM7F_IMF(CFSFolder::GetItemFullSize(UInt32 index, PROPVARIANT *value, IProgress *progress) +{ + NCOM::CPropVariant prop; + UInt64 size = 0; + HRESULT result = GetItemFullSize(index, size, progress); + prop = size; + prop.Detach(value); + return result; +} +*/ + +Z7_COM7F_IMF(CFSFolder::CalcItemFullSize(UInt32 index, IProgress *progress)) +{ + if (index >= Files.Size()) + return S_OK; + CDirItem &fi = Files[index]; + if (!fi.IsDir()) + return S_OK; + CFsFolderStat stat(_path + GetRelPath(fi), progress); + RINOK(stat.Enumerate()) + fi.Size = stat.Size; + fi.NumFolders = stat.NumFolders; + fi.NumFiles = stat.NumFiles; + fi.FolderStat_Defined = true; + return S_OK; +} + +void CFSFolder::GetAbsPath(const wchar_t *name, FString &absPath) +{ + absPath.Empty(); + if (!IsAbsolutePath(name)) + absPath += _path; + absPath += us2fs(name); +} + +Z7_COM7F_IMF(CFSFolder::CreateFolder(const wchar_t *name, IProgress * /* progress */)) +{ + FString absPath; + GetAbsPath(name, absPath); + if (CreateDir(absPath)) + return S_OK; + if (::GetLastError() != ERROR_ALREADY_EXISTS) + if (CreateComplexDir(absPath)) + return S_OK; + return GetLastError_noZero_HRESULT(); +} + +Z7_COM7F_IMF(CFSFolder::CreateFile(const wchar_t *name, IProgress * /* progress */)) +{ + FString absPath; + GetAbsPath(name, absPath); + NIO::COutFile outFile; + if (!outFile.Create_NEW(absPath)) + return GetLastError_noZero_HRESULT(); + return S_OK; +} + +Z7_COM7F_IMF(CFSFolder::Rename(UInt32 index, const wchar_t *newName, IProgress * /* progress */)) +{ + if (index >= Files.Size()) + return E_NOTIMPL; + const CDirItem &fi = Files[index]; + // FString prefix; + // GetPrefix(fi, prefix); + FString fullPrefix = _path; + if (fi.Parent >= 0) + fullPrefix += Folders[fi.Parent]; + if (!MyMoveFile(fullPrefix + fi.Name, fullPrefix + us2fs(newName))) + return GetLastError_noZero_HRESULT(); + return S_OK; +} + +Z7_COM7F_IMF(CFSFolder::Delete(const UInt32 *indices, UInt32 numItems,IProgress *progress)) +{ + RINOK(progress->SetTotal(numItems)) + // int prevDeletedFileIndex = -1; + for (UInt32 i = 0; i < numItems; i++) + { + // Sleep(200); + UInt32 index = indices[i]; + bool result = true; + /* + if (index >= Files.Size()) + { + const CAltStream &ss = Streams[index - Files.Size()]; + if (prevDeletedFileIndex != ss.Parent) + { + const CDirItem &fi = Files[ss.Parent]; + result = DeleteFileAlways(_path + GetRelPath(fi) + us2fs(ss.Name)); + } + } + else + */ + { + const CDirItem &fi = Files[index]; + const FString fullPath = _path + GetRelPath(fi); + // prevDeletedFileIndex = index; + if (fi.IsDir()) + result = RemoveDirWithSubItems(fullPath); + else + result = DeleteFileAlways(fullPath); + } + if (!result) + return GetLastError_noZero_HRESULT(); + const UInt64 completed = i; + RINOK(progress->SetCompleted(&completed)) + } + return S_OK; +} + +Z7_COM7F_IMF(CFSFolder::SetProperty(UInt32 index, PROPID propID, + const PROPVARIANT *value, IProgress * /* progress */)) +{ + if (index >= Files.Size()) + return E_INVALIDARG; + CDirItem &fi = Files[index]; + if (fi.Parent >= 0) + return E_NOTIMPL; + switch (propID) + { + case kpidComment: + { + UString filename = fs2us(fi.Name); + filename.Trim(); + if (value->vt == VT_EMPTY) + _comments.DeletePair(filename); + else if (value->vt == VT_BSTR) + { + CTextPair pair; + pair.ID = filename; + pair.ID.Trim(); + pair.Value.SetFromBstr(value->bstrVal); + pair.Value.Trim(); + if (pair.Value.IsEmpty()) + _comments.DeletePair(filename); + else + _comments.AddPair(pair); + } + else + return E_INVALIDARG; + SaveComments(); + break; + } + default: + return E_NOTIMPL; + } + return S_OK; +} + +Z7_COM7F_IMF(CFSFolder::GetSystemIconIndex(UInt32 index, Int32 *iconIndex)) +{ + *iconIndex = -1; + if (index >= Files.Size()) + return E_INVALIDARG; + const CDirItem &fi = Files[index]; + return Shell_GetFileInfo_SysIconIndex_for_Path_return_HRESULT( + _path + GetRelPath(fi), fi.Attrib, iconIndex); +} + +Z7_COM7F_IMF(CFSFolder::SetFlatMode(Int32 flatMode)) +{ + _flatMode = IntToBool(flatMode); + return S_OK; +} + +/* +Z7_COM7F_IMF(CFSFolder::SetShowNtfsStreamsMode(Int32 showStreamsMode) +{ + _scanAltStreams = IntToBool(showStreamsMode); + return S_OK; +} +*/ + +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FSFolder.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FSFolder.h new file mode 100644 index 00000000..e2edf5fd --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FSFolder.h @@ -0,0 +1,220 @@ +// FSFolder.h + +#ifndef ZIP7_INC_FS_FOLDER_H +#define ZIP7_INC_FS_FOLDER_H + +#include "../../../Common/MyCom.h" +#include "../../../Common/MyBuffer.h" + +#include "../../../Windows/FileFind.h" + +#include "../../Archive/IArchive.h" + +#include "IFolder.h" +#include "TextPairs.h" + +namespace NFsFolder { + +class CFSFolder; + +#define FS_SHOW_LINKS_INFO +// #define FS_SHOW_CHANGE_TIME + +struct CDirItem: public NWindows::NFile::NFind::CFileInfo +{ +#ifndef UNDER_CE + UInt64 PackSize; +#endif + +#ifdef FS_SHOW_LINKS_INFO + FILETIME ChangeTime; + UInt64 FileIndex; + UInt32 NumLinks; + bool FileInfo_Defined; + bool FileInfo_WasRequested; + bool ChangeTime_Defined; + bool ChangeTime_WasRequested; +#endif + +#ifndef UNDER_CE + bool PackSize_Defined; +#endif + + bool FolderStat_Defined; + int Parent; + +#ifndef UNDER_CE + CByteBuffer Reparse; +#endif + + UInt64 NumFolders; + UInt64 NumFiles; +}; + +/* +struct CAltStream +{ + UInt64 Size; + UInt64 PackSize; + bool PackSize_Defined; + int Parent; + UString Name; +}; +*/ + +struct CFsFolderStat +{ + UInt64 NumFolders; + UInt64 NumFiles; + UInt64 Size; + IProgress *Progress; + FString Path; + + CFsFolderStat(): NumFolders(0), NumFiles(0), Size(0), Progress(NULL) {} + CFsFolderStat(const FString &path, IProgress *progress = NULL): + NumFolders(0), NumFiles(0), Size(0), Progress(progress), Path(path) {} + + HRESULT Enumerate(); +}; + +class CFSFolder Z7_final: + public IFolderFolder, + public IArchiveGetRawProps, + public IFolderCompare, + #ifdef USE_UNICODE_FSTRING + public IFolderGetItemName, + #endif + public IFolderWasChanged, + public IFolderOperations, + // public IFolderOperationsDeleteToRecycleBin, + public IFolderCalcItemFullSize, + public IFolderClone, + public IFolderGetSystemIconIndex, + public IFolderSetFlatMode, + // public IFolderSetShowNtfsStreamsMode, + public CMyUnknownImp +{ + Z7_COM_QI_BEGIN2(IFolderFolder) + Z7_COM_QI_ENTRY(IArchiveGetRawProps) + Z7_COM_QI_ENTRY(IFolderCompare) + #ifdef USE_UNICODE_FSTRING + Z7_COM_QI_ENTRY(IFolderGetItemName) + #endif + Z7_COM_QI_ENTRY(IFolderWasChanged) + // Z7_COM_QI_ENTRY(IFolderOperationsDeleteToRecycleBin) + Z7_COM_QI_ENTRY(IFolderOperations) + Z7_COM_QI_ENTRY(IFolderCalcItemFullSize) + Z7_COM_QI_ENTRY(IFolderClone) + Z7_COM_QI_ENTRY(IFolderGetSystemIconIndex) + Z7_COM_QI_ENTRY(IFolderSetFlatMode) + // Z7_COM_QI_ENTRY(IFolderSetShowNtfsStreamsMode) + Z7_COM_QI_END + Z7_COM_ADDREF_RELEASE + + Z7_IFACE_COM7_IMP(IFolderFolder) + Z7_IFACE_COM7_IMP(IArchiveGetRawProps) + Z7_IFACE_COM7_IMP(IFolderCompare) + #ifdef USE_UNICODE_FSTRING + Z7_IFACE_COM7_IMP(IFolderGetItemName) + #endif + Z7_IFACE_COM7_IMP(IFolderWasChanged) + Z7_IFACE_COM7_IMP(IFolderOperations) + Z7_IFACE_COM7_IMP(IFolderCalcItemFullSize) + Z7_IFACE_COM7_IMP(IFolderClone) + Z7_IFACE_COM7_IMP(IFolderGetSystemIconIndex) + Z7_IFACE_COM7_IMP(IFolderSetFlatMode) + // Z7_IFACE_COM7_IMP(IFolderSetShowNtfsStreamsMode) + + bool _flatMode; + bool _commentsAreLoaded; + // bool _scanAltStreams; + + FString _path; + CObjectVector Files; + FStringVector Folders; + // CObjectVector Streams; + // CMyComPtr _parentFolder; + + CPairsStorage _comments; + + #ifdef _WIN32 + NWindows::NFile::NFind::CFindChangeNotification _findChangeNotification; + #endif + + // HRESULT GetItemFullSize(unsigned index, UInt64 &size, IProgress *progress); + void GetAbsPath(const wchar_t *name, FString &absPath); + HRESULT BindToFolderSpec(CFSTR name, IFolderFolder **resultFolder); + + bool LoadComments(); + bool SaveComments(); + HRESULT LoadSubItems(int dirItem, const FString &path); + + #ifdef FS_SHOW_LINKS_INFO + bool ReadFileInfo(CDirItem &di); + void ReadChangeTime(CDirItem &di); + #endif + +public: + HRESULT Init(const FString &path /* , IFolderFolder *parentFolder */); + #if !defined(_WIN32) || defined(UNDER_CE) + HRESULT InitToRoot() { return Init((FString) FSTRING_PATH_SEPARATOR /* , NULL */); } + #endif + + CFSFolder(): + _flatMode(false), + _commentsAreLoaded(false) + // , _scanAltStreams(false) + {} + + void GetFullPath(const CDirItem &item, FString &path) const + { + // FString prefix; + // GetPrefix(item, prefix); + path = _path; + if (item.Parent >= 0) + path += Folders[item.Parent]; + path += item.Name; + } + + // void GetPrefix(const CDirItem &item, FString &prefix) const; + + FString GetRelPath(const CDirItem &item) const; + + void Clear() + { + Files.Clear(); + Folders.Clear(); + // Streams.Clear(); + } +}; + +struct CCopyStateIO +{ + IProgress *Progress; + UInt64 TotalSize; + UInt64 StartPos; + UInt64 CurrentSize; + bool DeleteSrcFile; + + int ErrorFileIndex; + UString ErrorMessage; + + CCopyStateIO(): TotalSize(0), StartPos(0), DeleteSrcFile(false) {} + + HRESULT MyCopyFile(CFSTR inPath, CFSTR outPath, DWORD attrib = INVALID_FILE_ATTRIBUTES); +}; + +HRESULT SendLastErrorMessage(IFolderOperationsExtractCallback *callback, const FString &fileName); + +/* destDirPrefix is allowed to be: + "full_path\" or "full_path:" for alt streams */ + +HRESULT CopyFileSystemItems( + const UStringVector &itemsPaths, + const FString &destDirPrefix, + bool moveMode, + IFolderOperationsExtractCallback *callback); + +} + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FSFolderCopy.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FSFolderCopy.cpp new file mode 100644 index 00000000..3582be0c --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FSFolderCopy.cpp @@ -0,0 +1,871 @@ +// FSFolderCopy.cpp + +#include "StdAfx.h" + +#include "../../../Common/MyWindows.h" + +#include "../../../Common/Defs.h" +#include "../../../Common/StringConvert.h" +#include "../../../Common/Wildcard.h" + +#include "../../../Windows/DLL.h" +#include "../../../Windows/ErrorMsg.h" +#include "../../../Windows/FileDir.h" +#include "../../../Windows/FileName.h" + +#include "../../Common/FilePathAutoRename.h" + +#include "FSFolder.h" + +using namespace NWindows; +using namespace NFile; +using namespace NDir; +using namespace NName; +using namespace NFind; + +#ifndef _UNICODE +extern bool g_IsNT; +#endif + +namespace NFsFolder { + +static const char * const k_CannotCopyDirToAltStream = "Cannot copy folder as alternate stream"; + + +HRESULT CCopyStateIO::MyCopyFile(CFSTR inPath, CFSTR outPath, DWORD attrib) +{ + ErrorFileIndex = -1; + ErrorMessage.Empty(); + CurrentSize = 0; + + { + const size_t kBufSize = 1 << 16; + CByteArr buf(kBufSize); + + NIO::CInFile inFile; + NIO::COutFile outFile; + + if (!inFile.Open(inPath)) + { + ErrorFileIndex = 0; + return S_OK; + } + + if (!outFile.Create_ALWAYS(outPath)) + { + ErrorFileIndex = 1; + return S_OK; + } + + for (;;) + { + UInt32 num; + if (!inFile.Read(buf, kBufSize, num)) + { + ErrorFileIndex = 0; + return S_OK; + } + if (num == 0) + break; + + UInt32 written = 0; + if (!outFile.Write(buf, num, written)) + { + ErrorFileIndex = 1; + return S_OK; + } + if (written != num) + { + ErrorMessage = "Write error"; + return S_OK; + } + CurrentSize += num; + if (Progress) + { + UInt64 completed = StartPos + CurrentSize; + RINOK(Progress->SetCompleted(&completed)) + } + } + } + + /* SetFileAttrib("path:alt_stream_name") sets attributes for main file "path". + But we don't want to change attributes of main file, when we write alt stream. + So we need INVALID_FILE_ATTRIBUTES for alt stream here */ + + if (attrib != INVALID_FILE_ATTRIBUTES) + SetFileAttrib(outPath, attrib); + + if (DeleteSrcFile) + { + if (!DeleteFileAlways(inPath)) + { + ErrorFileIndex = 0; + return S_OK; + } + } + + return S_OK; +} + + +/* +static bool IsItWindows2000orHigher() +{ + OSVERSIONINFO versionInfo; + versionInfo.dwOSVersionInfoSize = sizeof(versionInfo); + if (!::GetVersionEx(&versionInfo)) + return false; + return (versionInfo.dwPlatformId == VER_PLATFORM_WIN32_NT) && + (versionInfo.dwMajorVersion >= 5); +} +*/ + +struct CProgressInfo +{ + UInt64 TotalSize; + UInt64 StartPos; + UInt64 FileSize; + IProgress *Progress; + HRESULT ProgressResult; + + void Init() { ProgressResult = S_OK; } +}; + +#ifndef PROGRESS_CONTINUE + +#define PROGRESS_CONTINUE 0 +#define PROGRESS_CANCEL 1 + +#define COPY_FILE_FAIL_IF_EXISTS 0x00000001 + +typedef +DWORD +(WINAPI* LPPROGRESS_ROUTINE)( + LARGE_INTEGER TotalFileSize, + LARGE_INTEGER TotalBytesTransferred, + LARGE_INTEGER StreamSize, + LARGE_INTEGER StreamBytesTransferred, + DWORD dwStreamNumber, + DWORD dwCallbackReason, + HANDLE hSourceFile, + HANDLE hDestinationFile, + LPVOID lpData + ); + +#endif + +static DWORD CALLBACK CopyProgressRoutine( + LARGE_INTEGER TotalFileSize, // file size + LARGE_INTEGER TotalBytesTransferred, // bytes transferred + LARGE_INTEGER /* StreamSize */, // bytes in stream + LARGE_INTEGER /* StreamBytesTransferred */, // bytes transferred for stream + DWORD /* dwStreamNumber */, // current stream + DWORD /* dwCallbackReason */, // callback reason + HANDLE /* hSourceFile */, // handle to source file + HANDLE /* hDestinationFile */, // handle to destination file + LPVOID lpData // from CopyFileEx +) +{ + // StreamSize = StreamSize; + // StreamBytesTransferred = StreamBytesTransferred; + // dwStreamNumber = dwStreamNumber; + // dwCallbackReason = dwCallbackReason; + + CProgressInfo &pi = *(CProgressInfo *)lpData; + + if ((UInt64)TotalFileSize.QuadPart > pi.FileSize) + { + pi.TotalSize += (UInt64)TotalFileSize.QuadPart - pi.FileSize; + pi.FileSize = (UInt64)TotalFileSize.QuadPart; + pi.ProgressResult = pi.Progress->SetTotal(pi.TotalSize); + } + const UInt64 completed = pi.StartPos + (UInt64)TotalBytesTransferred.QuadPart; + pi.ProgressResult = pi.Progress->SetCompleted(&completed); + return (pi.ProgressResult == S_OK ? PROGRESS_CONTINUE : PROGRESS_CANCEL); +} + +#if !defined(Z7_WIN32_WINNT_MIN) || Z7_WIN32_WINNT_MIN < 0x0500 // win2000 +#define Z7_USE_DYN_MoveFileWithProgressW +#endif + +#ifdef Z7_USE_DYN_MoveFileWithProgressW +// nt4 +typedef BOOL (WINAPI * Func_CopyFileExA)( + IN LPCSTR lpExistingFileName, + IN LPCSTR lpNewFileName, + IN LPPROGRESS_ROUTINE lpProgressRoutine OPTIONAL, + IN LPVOID lpData OPTIONAL, + IN LPBOOL pbCancel OPTIONAL, + IN DWORD dwCopyFlags + ); + +// nt4 +typedef BOOL (WINAPI * Func_CopyFileExW)( + IN LPCWSTR lpExistingFileName, + IN LPCWSTR lpNewFileName, + IN LPPROGRESS_ROUTINE lpProgressRoutine OPTIONAL, + IN LPVOID lpData OPTIONAL, + IN LPBOOL pbCancel OPTIONAL, + IN DWORD dwCopyFlags + ); + +// win2000 +typedef BOOL (WINAPI * Func_MoveFileWithProgressW)( + IN LPCWSTR lpExistingFileName, + IN LPCWSTR lpNewFileName, + IN LPPROGRESS_ROUTINE lpProgressRoutine OPTIONAL, + IN LPVOID lpData OPTIONAL, + IN DWORD dwFlags + ); +#endif + +struct CCopyState +{ + CProgressInfo ProgressInfo; + IFolderOperationsExtractCallback *Callback; + bool MoveMode; + bool UseReadWriteMode; + bool IsAltStreamsDest; + +#ifdef Z7_USE_DYN_MoveFileWithProgressW +private: + Func_CopyFileExW my_CopyFileExW; + #ifndef UNDER_CE + Func_MoveFileWithProgressW my_MoveFileWithProgressW; + #endif + #ifndef _UNICODE + Func_CopyFileExA my_CopyFileExA; + #endif +public: + CCopyState(); +#endif + + bool CopyFile_NT(const wchar_t *oldFile, const wchar_t *newFile); + bool CopyFile_Sys(CFSTR oldFile, CFSTR newFile); + bool MoveFile_Sys(CFSTR oldFile, CFSTR newFile); + + HRESULT CallProgress(); + + bool IsCallbackProgressError() { return ProgressInfo.ProgressResult != S_OK; } +}; + +HRESULT CCopyState::CallProgress() +{ + return ProgressInfo.Progress->SetCompleted(&ProgressInfo.StartPos); +} + +#ifdef Z7_USE_DYN_MoveFileWithProgressW + +CCopyState::CCopyState() +{ + my_CopyFileExW = NULL; + #ifndef UNDER_CE + my_MoveFileWithProgressW = NULL; + #endif + #ifndef _UNICODE + my_CopyFileExA = NULL; + if (!g_IsNT) + { + my_CopyFileExA = Z7_GET_PROC_ADDRESS( + Func_CopyFileExA, ::GetModuleHandleA("kernel32.dll"), + "CopyFileExA"); + } + else + #endif + { + const HMODULE module = ::GetModuleHandleW( + #ifdef UNDER_CE + L"coredll.dll" + #else + L"kernel32.dll" + #endif + ); + my_CopyFileExW = Z7_GET_PROC_ADDRESS( + Func_CopyFileExW, module, + "CopyFileExW"); + #ifndef UNDER_CE + my_MoveFileWithProgressW = Z7_GET_PROC_ADDRESS( + Func_MoveFileWithProgressW, module, + "MoveFileWithProgressW"); + #endif + } +} + +#endif + +/* WinXP-64: + CopyFileW(fromFile, toFile:altStream) + OK - there are NO alt streams in fromFile + ERROR_INVALID_PARAMETER - there are alt streams in fromFile +*/ + +bool CCopyState::CopyFile_NT(const wchar_t *oldFile, const wchar_t *newFile) +{ + BOOL cancelFlag = FALSE; +#ifdef Z7_USE_DYN_MoveFileWithProgressW + if (my_CopyFileExW) +#endif + return BOOLToBool( +#ifdef Z7_USE_DYN_MoveFileWithProgressW + my_CopyFileExW +#else + CopyFileExW +#endif + (oldFile, newFile, CopyProgressRoutine, + &ProgressInfo, &cancelFlag, COPY_FILE_FAIL_IF_EXISTS)); +#ifdef Z7_USE_DYN_MoveFileWithProgressW + return BOOLToBool(::CopyFileW(oldFile, newFile, TRUE)); +#endif +} + +bool CCopyState::CopyFile_Sys(CFSTR oldFile, CFSTR newFile) +{ + #ifndef _UNICODE + if (!g_IsNT) + { +#ifdef Z7_USE_DYN_MoveFileWithProgressW + if (my_CopyFileExA) +#endif + { + BOOL cancelFlag = FALSE; + if ( +#ifdef Z7_USE_DYN_MoveFileWithProgressW + my_CopyFileExA +#else + CopyFileExA +#endif + (fs2fas(oldFile), fs2fas(newFile), + CopyProgressRoutine, &ProgressInfo, &cancelFlag, COPY_FILE_FAIL_IF_EXISTS)) + return true; + if (::GetLastError() != ERROR_CALL_NOT_IMPLEMENTED) + return false; + } + return BOOLToBool(::CopyFile(fs2fas(oldFile), fs2fas(newFile), TRUE)); + } + else + #endif + { + IF_USE_MAIN_PATH_2(oldFile, newFile) + { + if (CopyFile_NT(fs2us(oldFile), fs2us(newFile))) + return true; + } + #ifdef Z7_LONG_PATH + if (USE_SUPER_PATH_2) + { + if (IsCallbackProgressError()) + return false; + UString superPathOld, superPathNew; + if (!GetSuperPaths(oldFile, newFile, superPathOld, superPathNew, USE_MAIN_PATH_2)) + return false; + if (CopyFile_NT(superPathOld, superPathNew)) + return true; + } + #endif + return false; + } +} + +bool CCopyState::MoveFile_Sys(CFSTR oldFile, CFSTR newFile) +{ + #ifndef UNDER_CE + // if (IsItWindows2000orHigher()) + // { +#ifdef Z7_USE_DYN_MoveFileWithProgressW + if (my_MoveFileWithProgressW) +#endif + { + IF_USE_MAIN_PATH_2(oldFile, newFile) + { + if ( +#ifdef Z7_USE_DYN_MoveFileWithProgressW + my_MoveFileWithProgressW +#else + MoveFileWithProgressW +#endif + (fs2us(oldFile), fs2us(newFile), CopyProgressRoutine, + &ProgressInfo, MOVEFILE_COPY_ALLOWED)) + return true; + } + #ifdef Z7_LONG_PATH + if ((!(USE_MAIN_PATH_2) || ::GetLastError() != ERROR_CALL_NOT_IMPLEMENTED) && USE_SUPER_PATH_2) + { + if (IsCallbackProgressError()) + return false; + UString superPathOld, superPathNew; + if (!GetSuperPaths(oldFile, newFile, superPathOld, superPathNew, USE_MAIN_PATH_2)) + return false; + if ( +#ifdef Z7_USE_DYN_MoveFileWithProgressW + my_MoveFileWithProgressW +#else + MoveFileWithProgressW +#endif + (superPathOld, superPathNew, CopyProgressRoutine, + &ProgressInfo, MOVEFILE_COPY_ALLOWED)) + return true; + } + #endif + if (::GetLastError() != ERROR_CALL_NOT_IMPLEMENTED) + return false; + } + // } + // else + #endif + return MyMoveFile(oldFile, newFile); +} + +static HRESULT SendMessageError(IFolderOperationsExtractCallback *callback, + const wchar_t *message, const FString &fileName) +{ + UString s = message; + s += " : "; + s += fs2us(fileName); + return callback->ShowMessage(s); +} + +static HRESULT SendMessageError(IFolderOperationsExtractCallback *callback, + const char *message, const FString &fileName) +{ + return SendMessageError(callback, MultiByteToUnicodeString(message), fileName); +} + +static DWORD Return_LastError_or_FAIL() +{ + DWORD errorCode = GetLastError(); + if (errorCode == 0) + errorCode = (DWORD)E_FAIL; + return errorCode; +} + +static UString GetLastErrorMessage() +{ + return NError::MyFormatMessage(Return_LastError_or_FAIL()); +} + +HRESULT SendLastErrorMessage(IFolderOperationsExtractCallback *callback, const FString &fileName) +{ + return SendMessageError(callback, GetLastErrorMessage(), fileName); +} + +static HRESULT CopyFile_Ask( + CCopyState &state, + const FString &srcPath, + const CFileInfo &srcFileInfo, + const FString &destPath) +{ + if (CompareFileNames(destPath, srcPath) == 0) + { + RINOK(SendMessageError(state.Callback, + state.MoveMode ? + "Cannot move file onto itself" : + "Cannot copy file onto itself" + , destPath)) + return E_ABORT; + } + + Int32 writeAskResult; + CMyComBSTR destPathResult; + RINOK(state.Callback->AskWrite( + fs2us(srcPath), + BoolToInt(false), + &srcFileInfo.MTime, &srcFileInfo.Size, + fs2us(destPath), + &destPathResult, + &writeAskResult)) + + if (IntToBool(writeAskResult)) + { + FString destPathNew = us2fs((LPCOLESTR)destPathResult); + RINOK(state.Callback->SetCurrentFilePath(fs2us(srcPath))) + + if (state.UseReadWriteMode) + { + NFsFolder::CCopyStateIO state2; + state2.Progress = state.Callback; + state2.DeleteSrcFile = state.MoveMode; + state2.TotalSize = state.ProgressInfo.TotalSize; + state2.StartPos = state.ProgressInfo.StartPos; + + RINOK(state2.MyCopyFile(srcPath, destPathNew, + state.IsAltStreamsDest ? INVALID_FILE_ATTRIBUTES: srcFileInfo.Attrib)) + + if (state2.ErrorFileIndex >= 0) + { + if (state2.ErrorMessage.IsEmpty()) + state2.ErrorMessage = GetLastErrorMessage(); + FString errorName; + if (state2.ErrorFileIndex == 0) + errorName = srcPath; + else + errorName = destPathNew; + RINOK(SendMessageError(state.Callback, state2.ErrorMessage, errorName)) + return E_ABORT; + } + state.ProgressInfo.StartPos += state2.CurrentSize; + } + else + { + state.ProgressInfo.FileSize = srcFileInfo.Size; + bool res; + if (state.MoveMode) + res = state.MoveFile_Sys(srcPath, destPathNew); + else + res = state.CopyFile_Sys(srcPath, destPathNew); + RINOK(state.ProgressInfo.ProgressResult) + if (!res) + { + const DWORD errorCode = GetLastError(); + UString errorMessage = NError::MyFormatMessage(Return_LastError_or_FAIL()); + if (errorCode == ERROR_INVALID_PARAMETER) + { + NFind::CFileInfo fi; + if (fi.Find(srcPath) && + fi.Size > (UInt32)(Int32)-1) + { + // bool isFsDetected = false; + // if (NSystem::Is_File_LimitedBy_4GB(destPathNew, isFsDetected) || !isFsDetected) + errorMessage += " File size exceeds 4 GB"; + } + } + + // GetLastError() is ERROR_REQUEST_ABORTED in case of PROGRESS_CANCEL. + RINOK(SendMessageError(state.Callback, errorMessage, destPathNew)) + return E_ABORT; + } + state.ProgressInfo.StartPos += state.ProgressInfo.FileSize; + } + } + else + { + if (state.ProgressInfo.TotalSize >= srcFileInfo.Size) + { + state.ProgressInfo.TotalSize -= srcFileInfo.Size; + RINOK(state.ProgressInfo.Progress->SetTotal(state.ProgressInfo.TotalSize)) + } + } + return state.CallProgress(); +} + +static FString CombinePath(const FString &folderPath, const FString &fileName) +{ + FString s (folderPath); + s.Add_PathSepar(); // FCHAR_PATH_SEPARATOR + s += fileName; + return s; +} + +static bool IsDestChild(const FString &src, const FString &dest) +{ + unsigned len = src.Len(); + if (dest.Len() < len) + return false; + if (dest.Len() != len && dest[len] != FCHAR_PATH_SEPARATOR) + return false; + return CompareFileNames(dest.Left(len), src) == 0; +} + +static HRESULT CopyFolder( + CCopyState &state, + const FString &srcPath, // without TAIL separator + const FString &destPath) // without TAIL separator +{ + RINOK(state.CallProgress()) + + if (IsDestChild(srcPath, destPath)) + { + RINOK(SendMessageError(state.Callback, + state.MoveMode ? + "Cannot copy folder onto itself" : + "Cannot move folder onto itself" + , destPath)) + return E_ABORT; + } + + if (state.MoveMode) + { + if (state.MoveFile_Sys(srcPath, destPath)) + return S_OK; + + // MSDN: MoveFile() fails for dirs on different volumes. + } + + if (!CreateComplexDir(destPath)) + { + RINOK(SendMessageError(state.Callback, "Cannot create folder", destPath)) + return E_ABORT; + } + + CEnumerator enumerator; + enumerator.SetDirPrefix(CombinePath(srcPath, FString())); + + for (;;) + { + NFind::CFileInfo fi; + bool found; + if (!enumerator.Next(fi, found)) + { + SendLastErrorMessage(state.Callback, srcPath); + return S_OK; + } + if (!found) + break; + const FString srcPath2 = CombinePath(srcPath, fi.Name); + const FString destPath2 = CombinePath(destPath, fi.Name); + if (fi.IsDir()) + { + RINOK(CopyFolder(state, srcPath2, destPath2)) + } + else + { + RINOK(CopyFile_Ask(state, srcPath2, fi, destPath2)) + } + } + + if (state.MoveMode) + { + if (!RemoveDir(srcPath)) + { + RINOK(SendMessageError(state.Callback, "Cannot remove folder", srcPath)) + return E_ABORT; + } + } + + return S_OK; +} + +Z7_COM7F_IMF(CFSFolder::CopyTo(Int32 moveMode, const UInt32 *indices, UInt32 numItems, + Int32 /* includeAltStreams */, Int32 /* replaceAltStreamColon */, + const wchar_t *path, IFolderOperationsExtractCallback *callback)) +{ + if (numItems == 0) + return S_OK; + + const FString destPath = us2fs(path); + if (destPath.IsEmpty()) + return E_INVALIDARG; + + const bool isAltDest = NName::IsAltPathPrefix(destPath); + const bool isDirectPath = (!isAltDest && !IsPathSepar(destPath.Back())); + + if (isDirectPath) + if (numItems > 1) + return E_INVALIDARG; + + CFsFolderStat stat; + stat.Progress = callback; + + UInt32 i; + for (i = 0; i < numItems; i++) + { + const UInt32 index = indices[i]; + /* + if (index >= Files.Size()) + { + size += Streams[index - Files.Size()].Size; + // numFiles++; + continue; + } + */ + const CDirItem &fi = Files[index]; + if (fi.IsDir()) + { + if (!isAltDest) + { + stat.Path = _path; + stat.Path += GetRelPath(fi); + RINOK(stat.Enumerate()) + } + stat.NumFolders++; + } + else + { + stat.NumFiles++; + stat.Size += fi.Size; + } + } + + /* + if (stat.NumFolders != 0 && isAltDest) + return E_NOTIMPL; + */ + + RINOK(callback->SetTotal(stat.Size)) + RINOK(callback->SetNumFiles(stat.NumFiles)) + + UInt64 completedSize = 0; + RINOK(callback->SetCompleted(&completedSize)) + + CCopyState state; + state.ProgressInfo.TotalSize = stat.Size; + state.ProgressInfo.StartPos = 0; + state.ProgressInfo.Progress = callback; + state.ProgressInfo.Init(); + state.Callback = callback; + state.MoveMode = IntToBool(moveMode); + state.IsAltStreamsDest = isAltDest; + /* CopyFileW(fromFile, toFile:altStream) returns ERROR_INVALID_PARAMETER, + if there are alt streams in fromFile. + So we don't use CopyFileW() for alt Streams. */ + state.UseReadWriteMode = isAltDest; + + for (i = 0; i < numItems; i++) + { + const UInt32 index = indices[i]; + if (index >= (UInt32)Files.Size()) + continue; + const CDirItem &fi = Files[index]; + FString destPath2 = destPath; + if (!isDirectPath) + destPath2 += fi.Name; + FString srcPath; + GetFullPath(fi, srcPath); + + if (fi.IsDir()) + { + if (isAltDest) + { + RINOK(SendMessageError(callback, k_CannotCopyDirToAltStream, srcPath)) + } + else + { + RINOK(CopyFolder(state, srcPath, destPath2)) + } + } + else + { + RINOK(CopyFile_Ask(state, srcPath, fi, destPath2)) + } + } + return S_OK; +} + + + +/* we can call CopyFileSystemItems() from CDropTarget::Drop() */ + +HRESULT CopyFileSystemItems( + const UStringVector &itemsPaths, + const FString &destDirPrefix, + bool moveMode, + IFolderOperationsExtractCallback *callback) +{ + if (itemsPaths.IsEmpty()) + return S_OK; + + if (destDirPrefix.IsEmpty()) + return E_INVALIDARG; + + const bool isAltDest = NName::IsAltPathPrefix(destDirPrefix); + + CFsFolderStat stat; + stat.Progress = callback; + + { + FOR_VECTOR (i, itemsPaths) + { + const UString &path = itemsPaths[i]; + CFileInfo fi; + if (!fi.Find(us2fs(path))) + continue; + if (fi.IsDir()) + { + if (!isAltDest) + { + stat.Path = us2fs(path); + RINOK(stat.Enumerate()) + } + stat.NumFolders++; + } + else + { + stat.NumFiles++; + stat.Size += fi.Size; + } + } + } + + /* + if (stat.NumFolders != 0 && isAltDest) + return E_NOTIMPL; + */ + + RINOK(callback->SetTotal(stat.Size)) + // RINOK(progress->SetNumFiles(stat.NumFiles)); + + UInt64 completedSize = 0; + RINOK(callback->SetCompleted(&completedSize)) + + CCopyState state; + state.ProgressInfo.TotalSize = stat.Size; + state.ProgressInfo.StartPos = 0; + state.ProgressInfo.Progress = callback; + state.ProgressInfo.Init(); + state.Callback = callback; + state.MoveMode = moveMode; + state.IsAltStreamsDest = isAltDest; + /* CopyFileW(fromFile, toFile:altStream) returns ERROR_INVALID_PARAMETER, + if there are alt streams in fromFile. + So we don't use CopyFileW() for alt Streams. */ + state.UseReadWriteMode = isAltDest; + + FOR_VECTOR (i, itemsPaths) + { + const UString path = itemsPaths[i]; + CFileInfo fi; + + if (!fi.Find(us2fs(path))) + { + RINOK(SendMessageError(callback, "Cannot find the file", us2fs(path))) + continue; + } + + FString destPath = destDirPrefix; + destPath += fi.Name; + + if (fi.IsDir()) + { + if (isAltDest) + { + RINOK(SendMessageError(callback, k_CannotCopyDirToAltStream, us2fs(path))) + } + else + { + RINOK(CopyFolder(state, us2fs(path), destPath)) + } + } + else + { + RINOK(CopyFile_Ask(state, us2fs(path), fi, destPath)) + } + } + return S_OK; +} + + +/* we don't use CFSFolder::CopyFrom() because the caller of CopyFrom() + is optimized for IFolderArchiveUpdateCallback interface, + but we want to use IFolderOperationsExtractCallback interface instead */ + +Z7_COM7F_IMF(CFSFolder::CopyFrom(Int32 /* moveMode */, const wchar_t * /* fromFolderPath */, + const wchar_t * const * /* itemsPaths */, UInt32 /* numItems */, IProgress * /* progress */)) +{ + /* + Z7_DECL_CMyComPtr_QI_FROM( + IFolderOperationsExtractCallback, + callback, progress) + if (!callback) + return E_NOTIMPL; + return CopyFileSystemItems(_path, + moveMode, fromDirPrefix, + itemsPaths, numItems, callback); + */ + return E_NOTIMPL; +} + +Z7_COM7F_IMF(CFSFolder::CopyFromFile(UInt32 /* index */, const wchar_t * /* fullFilePath */, IProgress * /* progress */)) +{ + return E_NOTIMPL; +} + +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FileFolderPluginOpen.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FileFolderPluginOpen.cpp new file mode 100644 index 00000000..1c33464c --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FileFolderPluginOpen.cpp @@ -0,0 +1,394 @@ +// FileFolderPluginOpen.cpp + +#include "StdAfx.h" + +#include "resource.h" + +#include "../../../Windows/FileName.h" +#include "../../../Windows/Thread.h" + +#include "../Agent/Agent.h" +#include "../GUI/ExtractRes.h" + +#include "FileFolderPluginOpen.h" +#include "FormatUtils.h" +#include "LangUtils.h" +#include "OpenCallback.h" +#include "PluginLoader.h" +#include "PropertyName.h" +#include "RegistryPlugins.h" + +using namespace NWindows; + +struct CThreadArchiveOpen +{ + UString Path; + UString ArcFormat; + CMyComPtr InStream; + CMyComPtr FolderManager; + CMyComPtr OpenCallbackProgress; + + COpenArchiveCallback *OpenCallbackSpec; + /* + CMyComPtr + // CMyComPtr + // CMyComPtr + OpenCallbackSpec_Ref; + */ + + CMyComPtr Folder; + HRESULT Result; + + void Process() + { + try + { + CProgressCloser closer(OpenCallbackSpec->ProgressDialog); + Result = FolderManager->OpenFolderFile(InStream, Path, ArcFormat, &Folder, OpenCallbackProgress); + } + catch(...) { Result = E_FAIL; } + } + + static THREAD_FUNC_DECL MyThreadFunction(void *param) + { + ((CThreadArchiveOpen *)param)->Process(); + return 0; + } +}; + +/* +static int FindPlugin(const CObjectVector &plugins, const UString &pluginName) +{ + for (int i = 0; i < plugins.Size(); i++) + if (plugins[i].Name.CompareNoCase(pluginName) == 0) + return i; + return -1; +} +*/ + +static void SplitNameToPureNameAndExtension(const FString &fullName, + FString &pureName, FString &extensionDelimiter, FString &extension) +{ + const int index = fullName.ReverseFind_Dot(); + if (index < 0) + { + pureName = fullName; + extensionDelimiter.Empty(); + extension.Empty(); + } + else + { + pureName.SetFrom(fullName, (unsigned)index); + extensionDelimiter = '.'; + extension = fullName.Ptr((unsigned)index + 1); + } +} + + +struct CArcLevelInfo +{ + UString Error; + UString Path; + UString Type; + UString ErrorType; + UString ErrorFlags; +}; + + +struct CArcLevelsInfo +{ + CObjectVector Levels; // LastLevel Is NON-OPEN +}; + + +UString GetOpenArcErrorMessage(UInt32 errorFlags); + + +static void GetFolderLevels(CMyComPtr &folder, CArcLevelsInfo &levels) +{ + levels.Levels.Clear(); + + CMyComPtr getFolderArcProps; + folder.QueryInterface(IID_IGetFolderArcProps, &getFolderArcProps); + + if (!getFolderArcProps) + return; + CMyComPtr arcProps; + getFolderArcProps->GetFolderArcProps(&arcProps); + if (!arcProps) + return; + + UInt32 numLevels; + if (arcProps->GetArcNumLevels(&numLevels) != S_OK) + numLevels = 0; + + for (UInt32 level = 0; level <= numLevels; level++) + { + const PROPID propIDs[] = { kpidError, kpidPath, kpidType, kpidErrorType }; + + CArcLevelInfo lev; + + for (Int32 i = 0; i < 4; i++) + { + CMyComBSTR name; + NCOM::CPropVariant prop; + if (arcProps->GetArcProp(level, propIDs[i], &prop) != S_OK) + continue; + if (prop.vt != VT_EMPTY) + { + UString *s = NULL; + switch (propIDs[i]) + { + case kpidError: s = &lev.Error; break; + case kpidPath: s = &lev.Path; break; + case kpidType: s = &lev.Type; break; + case kpidErrorType: s = &lev.ErrorType; break; + } + *s = (prop.vt == VT_BSTR) ? prop.bstrVal : L"?"; + } + } + + { + NCOM::CPropVariant prop; + if (arcProps->GetArcProp(level, kpidErrorFlags, &prop) == S_OK) + { + UInt32 flags = GetOpenArcErrorFlags(prop); + if (flags != 0) + lev.ErrorFlags = GetOpenArcErrorMessage(flags); + } + } + + levels.Levels.Add(lev); + } +} + +static UString GetBracedType(const wchar_t *type) +{ + UString s ('['); + s += type; + s.Add_Char(']'); + return s; +} + +static void GetFolderError(CMyComPtr &folder, UString &open_Errors, UString &nonOpen_Errors) +{ + CArcLevelsInfo levs; + GetFolderLevels(folder, levs); + open_Errors.Empty(); + nonOpen_Errors.Empty(); + + FOR_VECTOR (i, levs.Levels) + { + bool isNonOpenLevel = (i == 0); + const CArcLevelInfo &lev = levs.Levels[levs.Levels.Size() - 1 - i]; + + UString m; + + if (!lev.ErrorType.IsEmpty()) + { + m = MyFormatNew(IDS_CANT_OPEN_AS_TYPE, GetBracedType(lev.ErrorType)); + if (!isNonOpenLevel) + { + m.Add_LF(); + m += MyFormatNew(IDS_IS_OPEN_AS_TYPE, GetBracedType(lev.Type)); + } + } + + if (!lev.Error.IsEmpty()) + { + if (!m.IsEmpty()) + m.Add_LF(); + m += GetBracedType(lev.Type); + m += " : "; + m += GetNameOfProperty(kpidError, L"Error"); + m += " : "; + m += lev.Error; + } + + if (!lev.ErrorFlags.IsEmpty()) + { + if (!m.IsEmpty()) + m.Add_LF(); + m += GetNameOfProperty(kpidErrorFlags, L"Errors"); + m += ": "; + m += lev.ErrorFlags; + } + + if (!m.IsEmpty()) + { + if (isNonOpenLevel) + { + UString &s = nonOpen_Errors; + s += lev.Path; + s.Add_LF(); + s += m; + } + else + { + UString &s = open_Errors; + if (!s.IsEmpty()) + s += "--------------------\n"; + s += lev.Path; + s.Add_LF(); + s += m; + } + } + } +} + +#ifdef _MSC_VER +#pragma warning(error : 4702) // unreachable code +#endif + +HRESULT CFfpOpen::OpenFileFolderPlugin(IInStream *inStream, + const FString &path, const UString &arcFormat, HWND parentWindow) +{ + /* + CObjectVector plugins; + ReadFileFolderPluginInfoList(plugins); + */ + + FString extension, name, pureName, dot; + + const int slashPos = path.ReverseFind_PathSepar(); + FString dirPrefix; + FString fileName; + if (slashPos >= 0) + { + dirPrefix.SetFrom(path, (unsigned)(slashPos + 1)); + fileName = path.Ptr((unsigned)(slashPos + 1)); + } + else + fileName = path; + + SplitNameToPureNameAndExtension(fileName, pureName, dot, extension); + + /* + if (!extension.IsEmpty()) + { + CExtInfo extInfo; + if (ReadInternalAssociation(extension, extInfo)) + { + for (int i = extInfo.Plugins.Size() - 1; i >= 0; i--) + { + int pluginIndex = FindPlugin(plugins, extInfo.Plugins[i]); + if (pluginIndex >= 0) + { + const CPluginInfo plugin = plugins[pluginIndex]; + plugins.Delete(pluginIndex); + plugins.Insert(0, plugin); + } + } + } + } + */ + + ErrorMessage.Empty(); + + // FOR_VECTOR (i, plugins) + // { + /* + const CPluginInfo &plugin = plugins[i]; + if (!plugin.ClassID_Defined && !plugin.FilePath.IsEmpty()) + continue; + */ + CPluginLibrary library; + + CThreadArchiveOpen t; + + // if (plugin.FilePath.IsEmpty()) + t.FolderManager = new CArchiveFolderManager; + /* + else if (library.LoadAndCreateManager(plugin.FilePath, plugin.ClassID, &t.FolderManager) != S_OK) + continue; + */ + + COpenArchiveCallback OpenCallbackSpec_loc; + t.OpenCallbackSpec = &OpenCallbackSpec_loc; + /* + t.OpenCallbackSpec = new COpenArchiveCallback; + t.OpenCallbackSpec_Ref = t.OpenCallbackSpec; + */ + t.OpenCallbackSpec->PasswordIsDefined = Encrypted; + t.OpenCallbackSpec->Password = Password; + t.OpenCallbackSpec->ParentWindow = parentWindow; + + /* COpenCallbackImp object will exist after Open stage for multivolume archives */ + COpenCallbackImp *openCallbackSpec = new COpenCallbackImp; + t.OpenCallbackProgress = openCallbackSpec; + // openCallbackSpec->Callback_Ref = t.OpenCallbackSpec; + // we set pointer without reference counter: + openCallbackSpec->Callback = + // openCallbackSpec->ReOpenCallback = + t.OpenCallbackSpec; + + if (inStream) + openCallbackSpec->SetSubArchiveName(fs2us(fileName)); + else + { + RINOK(openCallbackSpec->Init2(dirPrefix, fileName)) + } + + t.InStream = inStream; + t.Path = fs2us(path); + t.ArcFormat = arcFormat; + + const UString progressTitle = LangString(IDS_OPENNING); + { + CProgressDialog &pd = t.OpenCallbackSpec->ProgressDialog; + pd.MainWindow = parentWindow; + pd.MainTitle = "7-Zip"; // LangString(IDS_APP_TITLE); + pd.MainAddTitle = progressTitle + L' '; + pd.WaitMode = true; + } + + { + NWindows::CThread thread; + const WRes wres = thread.Create(CThreadArchiveOpen::MyThreadFunction, &t); + if (wres != 0) + return HRESULT_FROM_WIN32(wres); + t.OpenCallbackSpec->StartProgressDialog(progressTitle, thread); + } + + /* + if archive is multivolume: + COpenCallbackImp object will exist after Open stage. + COpenCallbackImp object will be deleted when last reference + from each volume object (CInFileStreamVol) will be closed (when archive will be closed). + */ + t.OpenCallbackProgress.Release(); + + if (t.Result != S_FALSE && t.Result != S_OK) + return t.Result; + + if (t.Folder) + { + UString open_Errors, nonOpen_Errors; + GetFolderError(t.Folder, open_Errors, nonOpen_Errors); + if (!nonOpen_Errors.IsEmpty()) + { + ErrorMessage = nonOpen_Errors; + // if (t.Result != S_OK) return t.Result; + /* if there are good open leves, and non0open level, + we could force error as critical error and return error here + but it's better to allow to open such rachives */ + // return S_FALSE; + } + } + + // if (openCallbackSpec->PasswordWasAsked) + { + Encrypted = t.OpenCallbackSpec->PasswordIsDefined; + Password = t.OpenCallbackSpec->Password; + } + + if (t.Result == S_OK) + { + Library.Attach(library.Detach()); + // Folder.Attach(t.Folder.Detach()); + Folder = t.Folder; + } + + return t.Result; + // } +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FileFolderPluginOpen.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FileFolderPluginOpen.h new file mode 100644 index 00000000..88027659 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FileFolderPluginOpen.h @@ -0,0 +1,27 @@ +// FileFolderPluginOpen.h + +#ifndef ZIP7_INC_FILE_FOLDER_PLUGIN_OPEN_H +#define ZIP7_INC_FILE_FOLDER_PLUGIN_OPEN_H + +#include "../../../Windows/DLL.h" + +struct CFfpOpen +{ + Z7_CLASS_NO_COPY(CFfpOpen) +public: + // out: + bool Encrypted; + UString Password; + + NWindows::NDLL::CLibrary Library; + CMyComPtr Folder; + UString ErrorMessage; + + CFfpOpen(): Encrypted (false) {} + + HRESULT OpenFileFolderPlugin(IInStream *inStream, + const FString &path, const UString &arcFormat, HWND parentWindow); +}; + + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FilePlugins.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FilePlugins.cpp new file mode 100644 index 00000000..cf20970f --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FilePlugins.cpp @@ -0,0 +1,78 @@ +// FilePlugins.cpp + +#include "StdAfx.h" + +#include "../Agent/Agent.h" + +#include "FilePlugins.h" +#include "PluginLoader.h" +#include "StringUtils.h" + +int CExtDatabase::FindExt(const UString &ext) const +{ + FOR_VECTOR (i, Exts) + if (Exts[i].Ext.IsEqualTo_NoCase(ext)) + return (int)i; + return -1; +} + +void CExtDatabase::Read() +{ + /* + ReadFileFolderPluginInfoList(Plugins); + FOR_VECTOR (pluginIndex, Plugins) + */ + { + // const CPluginInfo &plugin = Plugins[pluginIndex]; + + CPluginLibrary pluginLib; + CMyComPtr folderManager; + + // if (plugin.FilePath.IsEmpty()) + folderManager = new CArchiveFolderManager; + /* + else + { + if (!plugin.ClassID_Defined) + continue; + if (pluginLib.LoadAndCreateManager(plugin.FilePath, plugin.ClassID, &folderManager) != S_OK) + continue; + } + */ + CMyComBSTR extBSTR; + if (folderManager->GetExtensions(&extBSTR) != S_OK) + return; + UStringVector exts; + SplitString((const wchar_t *)extBSTR, exts); + FOR_VECTOR (i, exts) + { + const UString &ext = exts[i]; + #ifdef UNDER_CE + if (ext == L"cab") + continue; + #endif + + Int32 iconIndex; + CMyComBSTR iconPath; + CPluginToIcon plugPair; + // plugPair.PluginIndex = pluginIndex; + if (folderManager->GetIconPath(ext, &iconPath, &iconIndex) == S_OK) + if (iconPath) + { + plugPair.IconPath = (const wchar_t *)iconPath; + plugPair.IconIndex = iconIndex; + } + + const int index = FindExt(ext); + if (index >= 0) + Exts[index].Plugins.Add(plugPair); + else + { + CExtPlugins extInfo; + extInfo.Plugins.Add(plugPair); + extInfo.Ext = ext; + Exts.Add(extInfo); + } + } + } +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FilePlugins.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FilePlugins.h new file mode 100644 index 00000000..db8ec396 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FilePlugins.h @@ -0,0 +1,33 @@ +// FilePlugins.h + +#ifndef ZIP7_INC_FILE_PLUGINS_H +#define ZIP7_INC_FILE_PLUGINS_H + +#include "RegistryPlugins.h" + +struct CPluginToIcon +{ + // unsigned PluginIndex; + int IconIndex; + UString IconPath; + + CPluginToIcon(): IconIndex(-1) {} +}; + +struct CExtPlugins +{ + UString Ext; + CObjectVector Plugins; +}; + +class CExtDatabase +{ + int FindExt(const UString &ext) const; +public: + CObjectVector Exts; + // CObjectVector Plugins; + + void Read(); +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FoldersPage.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FoldersPage.cpp new file mode 100644 index 00000000..7e74635b --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FoldersPage.cpp @@ -0,0 +1,172 @@ +// FoldersPage.cpp + +#include "StdAfx.h" + +#include "FoldersPageRes.h" +#include "FoldersPage.h" + +#include "../FileManager/BrowseDialog.h" +#include "../FileManager/HelpUtils.h" +#include "../FileManager/LangUtils.h" + +using namespace NWindows; + +#ifdef Z7_LANG +static const UInt32 kLangIDs[] = +{ + IDT_FOLDERS_WORKING_FOLDER, + IDR_FOLDERS_WORK_SYSTEM, + IDR_FOLDERS_WORK_CURRENT, + IDR_FOLDERS_WORK_SPECIFIED, + IDX_FOLDERS_WORK_FOR_REMOVABLE +}; +#endif + +static const unsigned kWorkModeButtons[] = +{ + IDR_FOLDERS_WORK_SYSTEM, + IDR_FOLDERS_WORK_CURRENT, + IDR_FOLDERS_WORK_SPECIFIED +}; + +#define kFoldersTopic "fm/options.htm#folders" + +static const unsigned kNumWorkModeButtons = Z7_ARRAY_SIZE(kWorkModeButtons); + +bool CFoldersPage::OnInit() +{ + _initMode = true; + _needSave = false; + + #ifdef Z7_LANG + LangSetDlgItems(*this, kLangIDs, Z7_ARRAY_SIZE(kLangIDs)); + #endif + m_WorkDirInfo.Load(); + + CheckButton(IDX_FOLDERS_WORK_FOR_REMOVABLE, m_WorkDirInfo.ForRemovableOnly); + + CheckRadioButton( + kWorkModeButtons[0], + kWorkModeButtons[kNumWorkModeButtons - 1], + kWorkModeButtons[m_WorkDirInfo.Mode]); + + m_WorkPath.Init(*this, IDE_FOLDERS_WORK_PATH); + + m_WorkPath.SetText(fs2us(m_WorkDirInfo.Path)); + + MyEnableControls(); + + _initMode = false; + return CPropertyPage::OnInit(); +} + +int CFoldersPage::GetWorkMode() const +{ + for (unsigned i = 0; i < kNumWorkModeButtons; i++) + if (IsButtonCheckedBool(kWorkModeButtons[i])) + return (int)i; + throw 0; +} + +void CFoldersPage::MyEnableControls() +{ + bool enablePath = (GetWorkMode() == NWorkDir::NMode::kSpecified); + m_WorkPath.Enable(enablePath); + EnableItem(IDB_FOLDERS_WORK_PATH, enablePath); +} + +void CFoldersPage::GetWorkDir(NWorkDir::CInfo &workDirInfo) +{ + UString s; + m_WorkPath.GetText(s); + workDirInfo.Path = us2fs(s); + workDirInfo.ForRemovableOnly = IsButtonCheckedBool(IDX_FOLDERS_WORK_FOR_REMOVABLE); + workDirInfo.Mode = NWorkDir::NMode::EEnum(GetWorkMode()); +} + +/* +bool CFoldersPage::WasChanged() +{ + NWorkDir::CInfo workDirInfo; + GetWorkDir(workDirInfo); + return (workDirInfo.Mode != m_WorkDirInfo.Mode || + workDirInfo.ForRemovableOnly != m_WorkDirInfo.ForRemovableOnly || + workDirInfo.Path.Compare(m_WorkDirInfo.Path) != 0); +} +*/ + +void CFoldersPage::ModifiedEvent() +{ + if (!_initMode) + { + _needSave = true; + Changed(); + } + /* + if (WasChanged()) + Changed(); + else + UnChanged(); + */ +} + +bool CFoldersPage::OnButtonClicked(unsigned buttonID, HWND buttonHWND) +{ + for (unsigned i = 0; i < kNumWorkModeButtons; i++) + if (buttonID == kWorkModeButtons[i]) + { + MyEnableControls(); + ModifiedEvent(); + return true; + } + + switch (buttonID) + { + case IDB_FOLDERS_WORK_PATH: + OnFoldersWorkButtonPath(); + return true; + case IDX_FOLDERS_WORK_FOR_REMOVABLE: + break; + default: + return CPropertyPage::OnButtonClicked(buttonID, buttonHWND); + } + + ModifiedEvent(); + return true; +} + +bool CFoldersPage::OnCommand(unsigned code, unsigned itemID, LPARAM lParam) +{ + if (code == EN_CHANGE && itemID == IDE_FOLDERS_WORK_PATH) + { + ModifiedEvent(); + return true; + } + return CPropertyPage::OnCommand(code, itemID, lParam); +} + +void CFoldersPage::OnFoldersWorkButtonPath() +{ + UString currentPath; + m_WorkPath.GetText(currentPath); + UString title = LangString(IDS_FOLDERS_SET_WORK_PATH_TITLE); + UString resultPath; + if (MyBrowseForFolder(*this, title, currentPath, resultPath)) + m_WorkPath.SetText(resultPath); +} + +LONG CFoldersPage::OnApply() +{ + if (_needSave) + { + GetWorkDir(m_WorkDirInfo); + m_WorkDirInfo.Save(); + _needSave = false; + } + return PSNRET_NOERROR; +} + +void CFoldersPage::OnNotifyHelp() +{ + ShowHelpWindow(kFoldersTopic); +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FoldersPage.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FoldersPage.h new file mode 100644 index 00000000..09b6cddb --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FoldersPage.h @@ -0,0 +1,32 @@ +// FoldersPage.h + +#ifndef ZIP7_INC_FOLDERS_PAGE_H +#define ZIP7_INC_FOLDERS_PAGE_H + +#include "../../../Windows/Control/PropertyPage.h" + +#include "../Common/ZipRegistry.h" + +class CFoldersPage : public NWindows::NControl::CPropertyPage +{ + NWorkDir::CInfo m_WorkDirInfo; + NWindows::NControl::CDialogChildControl m_WorkPath; + + bool _needSave; + bool _initMode; + + void MyEnableControls(); + void ModifiedEvent(); + + void OnFoldersWorkButtonPath(); + int GetWorkMode() const; + void GetWorkDir(NWorkDir::CInfo &workDirInfo); + // bool WasChanged(); + virtual bool OnInit() Z7_override; + virtual bool OnCommand(unsigned code, unsigned itemID, LPARAM lParam) Z7_override; + virtual void OnNotifyHelp() Z7_override; + virtual LONG OnApply() Z7_override; + virtual bool OnButtonClicked(unsigned buttonID, HWND buttonHWND) Z7_override; +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FoldersPage.rc b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FoldersPage.rc new file mode 100644 index 00000000..3317a088 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FoldersPage.rc @@ -0,0 +1,23 @@ +#include "FoldersPageRes.h" +#include "../../GuiCommon.rc" + +#define xc OPTIONS_PAGE_XC_SIZE +#define yc 100 + +IDD_FOLDERS MY_PAGE +#include "FoldersPage2.rc" + +#ifdef UNDER_CE + +#undef xc +#define xc SMALL_PAGE_SIZE_X + +IDD_FOLDERS_2 MY_PAGE +#include "FoldersPage2.rc" + +#endif + +STRINGTABLE +BEGIN + IDS_FOLDERS_SET_WORK_PATH_TITLE "Specify a location for temporary archive files." +END diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FoldersPage2.rc b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FoldersPage2.rc new file mode 100644 index 00000000..70f832e4 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FoldersPage2.rc @@ -0,0 +1,11 @@ +CAPTION "Folders" +BEGIN + // GROUPBOX "&Working folder", IDT_FOLDERS_WORKING_FOLDER, m, m, xc, 98 + LTEXT "&Working folder", IDT_FOLDERS_WORKING_FOLDER, m, m, xc, 8 + MY_CONTROL_AUTORADIOBUTTON_GROUP ( "&System temp folder", IDR_FOLDERS_WORK_SYSTEM, m, 20, xc) + MY_CONTROL_AUTORADIOBUTTON ( "&Current", IDR_FOLDERS_WORK_CURRENT, m, 34, xc) + MY_CONTROL_AUTORADIOBUTTON ( "Specified:", IDR_FOLDERS_WORK_SPECIFIED, m, 48, xc) + EDITTEXT IDE_FOLDERS_WORK_PATH, m + m, 62, xc - m - m - bxsDots, 14, ES_AUTOHSCROLL + PUSHBUTTON "...", IDB_FOLDERS_WORK_PATH, xs - m - bxsDots, 61, bxsDots, bys + MY_CONTROL_CHECKBOX ( "Use for removable drives only", IDX_FOLDERS_WORK_FOR_REMOVABLE, m, 86, xc) +END diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FoldersPageRes.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FoldersPageRes.h new file mode 100644 index 00000000..ba9ab73e --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FoldersPageRes.h @@ -0,0 +1,12 @@ +#define IDD_FOLDERS 2400 +#define IDD_FOLDERS_2 12400 + +#define IDT_FOLDERS_WORKING_FOLDER 2401 +#define IDR_FOLDERS_WORK_SYSTEM 2402 +#define IDR_FOLDERS_WORK_CURRENT 2403 +#define IDR_FOLDERS_WORK_SPECIFIED 2404 +#define IDX_FOLDERS_WORK_FOR_REMOVABLE 2405 +#define IDS_FOLDERS_SET_WORK_PATH_TITLE 2406 + +#define IDE_FOLDERS_WORK_PATH 100 +#define IDB_FOLDERS_WORK_PATH 101 diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FormatUtils.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FormatUtils.cpp new file mode 100644 index 00000000..2143c3f1 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FormatUtils.cpp @@ -0,0 +1,28 @@ +// FormatUtils.cpp + +#include "StdAfx.h" + +#include "../../../Common/IntToString.h" + +#include "FormatUtils.h" + +#include "LangUtils.h" + +UString NumberToString(UInt64 number) +{ + wchar_t numberString[32]; + ConvertUInt64ToString(number, numberString); + return numberString; +} + +UString MyFormatNew(const UString &format, const UString &argument) +{ + UString result = format; + result.Replace(L"{0}", argument); + return result; +} + +UString MyFormatNew(UINT resourceID, const UString &argument) +{ + return MyFormatNew(LangString(resourceID), argument); +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FormatUtils.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FormatUtils.h new file mode 100644 index 00000000..1db08eff --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/FormatUtils.h @@ -0,0 +1,14 @@ +// FormatUtils.h + +#ifndef ZIP7_INC_FORMAT_UTILS_H +#define ZIP7_INC_FORMAT_UTILS_H + +#include "../../../Common/MyTypes.h" +#include "../../../Common/MyString.h" + +UString NumberToString(UInt64 number); + +UString MyFormatNew(const UString &format, const UString &argument); +UString MyFormatNew(UINT resourceID, const UString &argument); + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/HelpUtils.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/HelpUtils.cpp new file mode 100644 index 00000000..d5b6a580 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/HelpUtils.cpp @@ -0,0 +1,78 @@ +// HelpUtils.cpp + +#include "StdAfx.h" + +#include "HelpUtils.h" + +#if defined(UNDER_CE) || defined(__MINGW32_VERSION) + +void ShowHelpWindow(LPCSTR) +{ +} + +#else + +/* USE_EXTERNAL_HELP creates new help process window for each HtmlHelp() call. + HtmlHelp() call uses one window. */ + +#if defined(__MINGW32_VERSION) /* || defined(Z7_OLD_WIN_SDK) */ +#define USE_EXTERNAL_HELP +#endif + +// #define USE_EXTERNAL_HELP + +#ifdef USE_EXTERNAL_HELP + +#include "../../../Windows/ProcessUtils.h" +#include "../../../Windows/FileDir.h" +#include "../../../Windows/FileName.h" + +#else +#include +#endif + +#include "../../../Common/StringConvert.h" + +#include "../../../Windows/DLL.h" + +#define kHelpFileName "7-zip.chm::/" + +void ShowHelpWindow(LPCSTR topicFile) +{ + FString path = NWindows::NDLL::GetModuleDirPrefix(); + path += kHelpFileName; + path += topicFile; + #ifdef USE_EXTERNAL_HELP + FString prog; + + #ifdef UNDER_CE + prog = "\\Windows\\"; + #else + if (!NWindows::NFile::NDir::GetWindowsDir(prog)) + return; + NWindows::NFile::NName::NormalizeDirPathPrefix(prog); + #endif + prog += "hh.exe"; + + UString params; + params += '"'; + params += fs2us(path); + params += '"'; + + NWindows::CProcess process; + const WRes wres = process.Create(fs2us(prog), params, NULL); // curDir); + if (wres != 0) + { + /* + HRESULT hres = HRESULT_FROM_WIN32(wres); + ErrorMessageHRESULT(hres, imageName); + return hres; + */ + } + #else + // HWND hwnd = NULL; + HtmlHelp(NULL, GetSystemString(fs2us(path)), HH_DISPLAY_TOPIC, 0); + #endif +} + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/HelpUtils.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/HelpUtils.h new file mode 100644 index 00000000..d7bdf453 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/HelpUtils.h @@ -0,0 +1,10 @@ +// HelpUtils.h + +#ifndef ZIP7_INC_HELP_UTILS_H +#define ZIP7_INC_HELP_UTILS_H + +#include "../../../Common/MyString.h" + +void ShowHelpWindow(LPCSTR topicFile); + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/IFolder.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/IFolder.h new file mode 100644 index 00000000..1ebdf7e5 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/IFolder.h @@ -0,0 +1,187 @@ +// IFolder.h + +#ifndef ZIP7_INC_IFOLDER_H +#define ZIP7_INC_IFOLDER_H + +#include "../../IProgress.h" +#include "../../IStream.h" + +Z7_PURE_INTERFACES_BEGIN + +#define Z7_IFACE_CONSTR_FOLDER_SUB(i, base, n) \ + Z7_DECL_IFACE_7ZIP_SUB(i, base, 8, n) \ + { Z7_IFACE_COM7_PURE(i) }; + +#define Z7_IFACE_CONSTR_FOLDER(i, n) \ + Z7_IFACE_CONSTR_FOLDER_SUB(i, IUnknown, n) + +namespace NPlugin +{ + enum + { + kName = 0, + kType, + kClassID, + kOptionsClassID + }; +} + +#define Z7_IFACEM_IFolderFolder(x) \ + x(LoadItems()) \ + x(GetNumberOfItems(UInt32 *numItems)) \ + x(GetProperty(UInt32 itemIndex, PROPID propID, PROPVARIANT *value)) \ + x(BindToFolder(UInt32 index, IFolderFolder **resultFolder)) \ + x(BindToFolder(const wchar_t *name, IFolderFolder **resultFolder)) \ + x(BindToParentFolder(IFolderFolder **resultFolder)) \ + x(GetNumberOfProperties(UInt32 *numProperties)) \ + x(GetPropertyInfo(UInt32 index, BSTR *name, PROPID *propID, VARTYPE *varType)) \ + x(GetFolderProperty(PROPID propID, PROPVARIANT *value)) \ + +Z7_IFACE_CONSTR_FOLDER(IFolderFolder, 0x00) + +/* + IFolderAltStreams:: + BindToAltStreams((UInt32)(Int32)-1, ... ) means alt streams of that folder +*/ + +#define Z7_IFACEM_IFolderAltStreams(x) \ + x(BindToAltStreams(UInt32 index, IFolderFolder **resultFolder)) \ + x(BindToAltStreams(const wchar_t *name, IFolderFolder **resultFolder)) \ + x(AreAltStreamsSupported(UInt32 index, Int32 *isSupported)) \ + +Z7_IFACE_CONSTR_FOLDER(IFolderAltStreams, 0x17) + +#define Z7_IFACEM_IFolderWasChanged(x) \ + x(WasChanged(Int32 *wasChanged)) +Z7_IFACE_CONSTR_FOLDER(IFolderWasChanged, 0x04) + + /* x(SetTotalFiles(UInt64 total)) */ \ + /* x(SetCompletedFiles(const UInt64 *completedValue)) */ \ +#define Z7_IFACEM_IFolderOperationsExtractCallback(x) \ + x(AskWrite( \ + const wchar_t *srcPath, \ + Int32 srcIsFolder, \ + const FILETIME *srcTime, \ + const UInt64 *srcSize, \ + const wchar_t *destPathRequest, \ + BSTR *destPathResult, \ + Int32 *writeAnswer)) \ + x(ShowMessage(const wchar_t *message)) \ + x(SetCurrentFilePath(const wchar_t *filePath)) \ + x(SetNumFiles(UInt64 numFiles)) \ + +Z7_IFACE_CONSTR_FOLDER_SUB(IFolderOperationsExtractCallback, IProgress, 0x0B) + + +#define Z7_IFACEM_IFolderOperations(x) \ + x(CreateFolder(const wchar_t *name, IProgress *progress)) \ + x(CreateFile(const wchar_t *name, IProgress *progress)) \ + x(Rename(UInt32 index, const wchar_t *newName, IProgress *progress)) \ + x(Delete(const UInt32 *indices, UInt32 numItems, IProgress *progress)) \ + x(CopyTo(Int32 moveMode, const UInt32 *indices, UInt32 numItems, \ + Int32 includeAltStreams, Int32 replaceAltStreamCharsMode, \ + const wchar_t *path, IFolderOperationsExtractCallback *callback)) \ + x(CopyFrom(Int32 moveMode, const wchar_t *fromFolderPath, \ + const wchar_t * const *itemsPaths, UInt32 numItems, IProgress *progress)) \ + x(SetProperty(UInt32 index, PROPID propID, const PROPVARIANT *value, IProgress *progress)) \ + x(CopyFromFile(UInt32 index, const wchar_t *fullFilePath, IProgress *progress)) \ + +Z7_IFACE_CONSTR_FOLDER(IFolderOperations, 0x13) + +/* +FOLDER_INTERFACE2(IFolderOperationsDeleteToRecycleBin, 0x06, 0x03) +{ + x(DeleteToRecycleBin(const UInt32 *indices, UInt32 numItems, IProgress *progress)) \ +}; +*/ + +#define Z7_IFACEM_IFolderGetSystemIconIndex(x) \ + x(GetSystemIconIndex(UInt32 index, Int32 *iconIndex)) +Z7_IFACE_CONSTR_FOLDER(IFolderGetSystemIconIndex, 0x07) + +#define Z7_IFACEM_IFolderGetItemFullSize(x) \ + x(GetItemFullSize(UInt32 index, PROPVARIANT *value, IProgress *progress)) +Z7_IFACE_CONSTR_FOLDER(IFolderGetItemFullSize, 0x08) + +#define Z7_IFACEM_IFolderCalcItemFullSize(x) \ + x(CalcItemFullSize(UInt32 index, IProgress *progress)) +Z7_IFACE_CONSTR_FOLDER(IFolderCalcItemFullSize, 0x14) + +#define Z7_IFACEM_IFolderClone(x) \ + x(Clone(IFolderFolder **resultFolder)) +Z7_IFACE_CONSTR_FOLDER(IFolderClone, 0x09) + +#define Z7_IFACEM_IFolderSetFlatMode(x) \ + x(SetFlatMode(Int32 flatMode)) +Z7_IFACE_CONSTR_FOLDER(IFolderSetFlatMode, 0x0A) + +/* +#define Z7_IFACEM_IFolderSetShowNtfsStreamsMode(x) \ + x(SetShowNtfsStreamsMode(Int32 showStreamsMode)) +Z7_IFACE_CONSTR_FOLDER(IFolderSetShowNtfsStreamsMode, 0xFA) +*/ + +#define Z7_IFACEM_IFolderProperties(x) \ + x(GetNumberOfFolderProperties(UInt32 *numProperties)) \ + x(GetFolderPropertyInfo(UInt32 index, BSTR *name, PROPID *propID, VARTYPE *varType)) \ + +Z7_IFACE_CONSTR_FOLDER(IFolderProperties, 0x0E) + +#define Z7_IFACEM_IFolderArcProps(x) \ + x(GetArcNumLevels(UInt32 *numLevels)) \ + x(GetArcProp(UInt32 level, PROPID propID, PROPVARIANT *value)) \ + x(GetArcNumProps(UInt32 level, UInt32 *numProps)) \ + x(GetArcPropInfo(UInt32 level, UInt32 index, BSTR *name, PROPID *propID, VARTYPE *varType)) \ + x(GetArcProp2(UInt32 level, PROPID propID, PROPVARIANT *value)) \ + x(GetArcNumProps2(UInt32 level, UInt32 *numProps)) \ + x(GetArcPropInfo2(UInt32 level, UInt32 index, BSTR *name, PROPID *propID, VARTYPE *varType)) \ + +Z7_IFACE_CONSTR_FOLDER(IFolderArcProps, 0x10) + +#define Z7_IFACEM_IGetFolderArcProps(x) \ + x(GetFolderArcProps(IFolderArcProps **object)) +Z7_IFACE_CONSTR_FOLDER(IGetFolderArcProps, 0x11) + +#define Z7_IFACEM_IFolderCompare(x) \ + x##2(Int32, CompareItems(UInt32 index1, UInt32 index2, PROPID propID, Int32 propIsRaw)) +Z7_IFACE_CONSTR_FOLDER(IFolderCompare, 0x15) + +#define Z7_IFACEM_IFolderGetItemName(x) \ + x(GetItemName(UInt32 index, const wchar_t **name, unsigned *len)) \ + x(GetItemPrefix(UInt32 index, const wchar_t **name, unsigned *len)) \ + x##2(UInt64, GetItemSize(UInt32 index)) \ + +Z7_IFACE_CONSTR_FOLDER(IFolderGetItemName, 0x16) + + +#define Z7_IFACEM_IFolderManager(x) \ + x(OpenFolderFile(IInStream *inStream, const wchar_t *filePath, const wchar_t *arcFormat, IFolderFolder **resultFolder, IProgress *progress)) \ + x(GetExtensions(BSTR *extensions)) \ + x(GetIconPath(const wchar_t *ext, BSTR *iconPath, Int32 *iconIndex)) \ + + // x(GetTypes(BSTR *types)) + // x(CreateFolderFile(const wchar_t *type, const wchar_t *filePath, IProgress *progress)) + +Z7_DECL_IFACE_7ZIP(IFolderManager, 9, 5) + { Z7_IFACE_COM7_PURE(IFolderManager) }; + +/* + const CMy_STATPROPSTG_2 &srcItem = k[index]; \ + *propID = srcItem.propid; *varType = srcItem.vt; *name = 0; return S_OK; } \ +*/ +#define IMP_IFolderFolder_GetProp(fn, k) \ + Z7_COM7F_IMF(fn(UInt32 index, BSTR *name, PROPID *propID, VARTYPE *varType)) \ + { if (index >= Z7_ARRAY_SIZE(k)) return E_INVALIDARG; \ + *propID = k[index]; *varType = k7z_PROPID_To_VARTYPE[(unsigned)*propID]; *name = NULL; return S_OK; } \ + +#define IMP_IFolderFolder_Props(c) \ + Z7_COM7F_IMF(c::GetNumberOfProperties(UInt32 *numProperties)) \ + { *numProperties = Z7_ARRAY_SIZE(kProps); return S_OK; } \ + IMP_IFolderFolder_GetProp(c::GetPropertyInfo, kProps) + + +int CompareFileNames_ForFolderList(const wchar_t *s1, const wchar_t *s2); +// int CompareFileNames_ForFolderList(const FChar *s1, const FChar *s2); + +Z7_PURE_INTERFACES_END +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/Info.bmp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/Info.bmp new file mode 100644 index 00000000..d769a661 Binary files /dev/null and b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/Info.bmp differ diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/Info2.bmp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/Info2.bmp new file mode 100644 index 00000000..af724d27 Binary files /dev/null and b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/Info2.bmp differ diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/LangPage.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/LangPage.cpp new file mode 100644 index 00000000..5f9ab8a1 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/LangPage.cpp @@ -0,0 +1,359 @@ +// LangPage.cpp + +#include "StdAfx.h" + +#include "../../../Common/Lang.h" + +#include "../../../Windows/FileFind.h" +#include "../../../Windows/ResourceString.h" + +#include "HelpUtils.h" +#include "LangPage.h" +#include "LangPageRes.h" +#include "LangUtils.h" +#include "RegistryUtils.h" + +using namespace NWindows; + + +static const unsigned k_NumLangLines_EN = 443; + +#ifdef Z7_LANG +static const UInt32 kLangIDs[] = +{ + IDT_LANG_LANG +}; +#endif + +#define kLangTopic "fm/options.htm#language" + + +struct CLangListRecord +{ + int Order; + unsigned LangInfoIndex; + bool IsSelected; + UString Mark; + UString Name; + + CLangListRecord(): Order (10), IsSelected(false) {} + int Compare(const CLangListRecord &a) const + { + if (Order < a.Order) return -1; + if (Order > a.Order) return 1; + return MyStringCompareNoCase(Name, a.Name); + } +}; + + +static void NativeLangString(UString &dest, const wchar_t *s) +{ + dest += " : "; + dest += s; +} + +bool LangOpen(CLang &lang, CFSTR fileName); + +bool CLangPage::OnInit() +{ +#ifdef Z7_LANG + LangSetDlgItems(*this, kLangIDs, Z7_ARRAY_SIZE(kLangIDs)); +#endif + _langCombo.Attach(GetItem(IDC_LANG_LANG)); + + + unsigned listRecords_SelectedIndex = 0; + + CObjectVector listRecords; + { + CLangListRecord listRecord; + listRecord.Order = 0; + listRecord.Mark = "---"; + listRecord.Name = MyLoadString(IDS_LANG_ENGLISH); + NativeLangString(listRecord.Name, MyLoadString(IDS_LANG_NATIVE)); + listRecord.LangInfoIndex = _langs.Size(); + listRecords.Add(listRecord); + } + + AStringVector names; + unsigned subLangIndex = 0; + Lang_GetShortNames_for_DefaultLang(names, subLangIndex); + + const FString dirPrefix = GetLangDirPrefix(); + NFile::NFind::CEnumerator enumerator; + enumerator.SetDirPrefix(dirPrefix); + NFile::NFind::CFileInfo fi; + + CLang lang_en; + { + CLangInfo &langInfo = _langs.AddNew(); + langInfo.Name = "-"; + if (LangOpen(lang_en, dirPrefix + FTEXT("en.ttt"))) + { + langInfo.NumLines = lang_en._ids.Size(); + // langInfo.Comments = lang_en.Comments; + } + else + langInfo.NumLines = k_NumLangLines_EN; + NumLangLines_EN = langInfo.NumLines; + } + + CLang lang; + UString error; + UString n; + + while (enumerator.Next(fi)) + { + if (fi.IsDir()) + continue; + const unsigned kExtSize = 4; + if (fi.Name.Len() < kExtSize) + continue; + const unsigned pos = fi.Name.Len() - kExtSize; + if (!StringsAreEqualNoCase_Ascii(fi.Name.Ptr(pos), ".txt")) + { + // if (!StringsAreEqualNoCase_Ascii(fi.Name.Ptr(pos), ".ttt")) + continue; + } + + if (!LangOpen(lang, dirPrefix + fi.Name)) + { + error.Add_Space_if_NotEmpty(); + error += fs2us(fi.Name); + continue; + } + + const UString shortName = fs2us(fi.Name.Left(pos)); + + CLangListRecord listRecord; + if (!names.IsEmpty()) + { + for (unsigned i = 0; i < names.Size(); i++) + if (shortName.IsEqualTo_Ascii_NoCase(names[i])) + { + if (subLangIndex == i || names.Size() == 1) + { + listRecord.Mark = "***"; + // listRecord.Order = 1; + } + else + { + listRecord.Mark = "+++"; + // listRecord.Order = 2; + } + break; + } + if (listRecord.Mark.IsEmpty()) + { + const int minusPos = shortName.Find(L'-'); + if (minusPos >= 0) + { + const UString shortName2 = shortName.Left(minusPos); + if (shortName2.IsEqualTo_Ascii_NoCase(names[0])) + { + listRecord.Mark = "+++"; + // listRecord.Order = 3; + } + } + } + } + UString s = shortName; + const wchar_t *eng = lang.Get(IDS_LANG_ENGLISH); + if (eng) + s = eng; + const wchar_t *native = lang.Get(IDS_LANG_NATIVE); + if (native) + NativeLangString(s, native); + + listRecord.Name = s; + listRecord.LangInfoIndex = _langs.Size(); + listRecords.Add(listRecord); + if (g_LangID.IsEqualTo_NoCase(shortName)) + listRecords_SelectedIndex = listRecords.Size() - 1; + + CLangInfo &langInfo = _langs.AddNew(); + langInfo.Comments = lang.Comments; + langInfo.Name = shortName; + unsigned numLines = lang._ids.Size(); + if (!lang_en.IsEmpty()) + { + numLines = 0; + unsigned i1 = 0; + unsigned i2 = 0; + for (;;) + { + UInt32 id1 = (UInt32)0 - 1; + UInt32 id2 = (UInt32)0 - 1; + bool id1_defined = false; + bool id2_defined = false; + if (i1 < lang_en._ids.Size()) + { + id1 = lang_en._ids[i1]; + id1_defined = true; + } + if (i2 < lang._ids.Size()) + { + id2 = lang._ids[i2]; + id2_defined = true; + } + + bool id1_is_smaller = true; + if (id1_defined) + { + if (id2_defined) + { + if (id1 == id2) + { + i1++; + i2++; + numLines++; + continue; + } + if (id1 > id2) + id1_is_smaller = false; + } + } + else if (!id2_defined) + break; + else + id1_is_smaller = false; + + n.Empty(); + if (id1_is_smaller) + { + n.Add_UInt32(id1); + n += " : "; + n += lang_en.Get_by_index(i1); + langInfo.MissingLines.Add(n); + i1++; + } + else + { + n.Add_UInt32(id2); + n += " : "; + n += lang.Get_by_index(i2); + langInfo.ExtraLines.Add(n); + i2++; + } + } + } + langInfo.NumLines = numLines + langInfo.ExtraLines.Size(); + } + + listRecords[listRecords_SelectedIndex].IsSelected = true; + + listRecords.Sort(); + FOR_VECTOR (i, listRecords) + { + const CLangListRecord &rec= listRecords[i]; + UString temp = rec.Name; + if (!rec.Mark.IsEmpty()) + { + temp += " "; + temp += rec.Mark; + } + const int index = (int)_langCombo.AddString_SetItemData(temp, (LPARAM)rec.LangInfoIndex); + if (rec.IsSelected) + _langCombo.SetCurSel(index); + } + + ShowLangInfo(); + + if (!error.IsEmpty()) + MessageBoxW(NULL, error, L"Error in Lang file", MB_ICONERROR); + return CPropertyPage::OnInit(); +} + +LONG CLangPage::OnApply() +{ + if (_needSave) + { + const int pathIndex = (int)_langCombo.GetItemData_of_CurSel(); + if ((unsigned)pathIndex < _langs.Size()) + SaveRegLang(_langs[pathIndex].Name); + } + _needSave = false; + #ifdef Z7_LANG + ReloadLang(); + #endif + LangWasChanged = true; + return PSNRET_NOERROR; +} + +void CLangPage::OnNotifyHelp() +{ + ShowHelpWindow(kLangTopic); +} + +bool CLangPage::OnCommand(unsigned code, unsigned itemID, LPARAM param) +{ + if (code == CBN_SELCHANGE && itemID == IDC_LANG_LANG) + { + _needSave = true; + Changed(); + ShowLangInfo(); + return true; + } + return CPropertyPage::OnCommand(code, itemID, param); +} + +static void AddVectorToString(UString &s, const UStringVector &v) +{ + UString a; + FOR_VECTOR (i, v) + { + if (i >= 50) + break; + a = v[i]; + if (a.Len() > 1500) + continue; + if (a[0] == ';') + { + a.DeleteFrontal(1); + a.Trim(); + } + s += a; + s.Add_LF(); + } +} + +static void AddVectorToString2(UString &s, const char *name, const UStringVector &v) +{ + if (v.IsEmpty()) + return; + s.Add_LF(); + s += "------ "; + s += name; + s += ": "; + s.Add_UInt32(v.Size()); + s += " :"; + s.Add_LF(); + AddVectorToString(s, v); +} + +void CLangPage::ShowLangInfo() +{ + UString s; + const int pathIndex = (int)_langCombo.GetItemData_of_CurSel(); + if ((unsigned)pathIndex < _langs.Size()) + { + const CLangInfo &langInfo = _langs[pathIndex]; + const unsigned numLines = langInfo.NumLines; + s += langInfo.Name; + s += " : "; + s.Add_UInt32(numLines); + if (NumLangLines_EN != 0) + { + s += " / "; + s.Add_UInt32(NumLangLines_EN); + s += " = "; + s.Add_UInt32(numLines * 100 / NumLangLines_EN); + s += "%"; + } + s.Add_LF(); + AddVectorToString(s, langInfo.Comments); + AddVectorToString2(s, "Missing lines", langInfo.MissingLines); + AddVectorToString2(s, "Extra lines", langInfo.ExtraLines); + } + SetItemText(IDT_LANG_INFO, s); +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/LangPage.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/LangPage.h new file mode 100644 index 00000000..e37ba058 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/LangPage.h @@ -0,0 +1,36 @@ +// LangPage.h + +#ifndef ZIP7_INC_LANG_PAGE_H +#define ZIP7_INC_LANG_PAGE_H + +#include "../../../Windows/Control/PropertyPage.h" +#include "../../../Windows/Control/ComboBox.h" + +struct CLangInfo +{ + unsigned NumLines; + UString Name; + UStringVector Comments; + UStringVector MissingLines; + UStringVector ExtraLines; +}; + +class CLangPage: public NWindows::NControl::CPropertyPage +{ + NWindows::NControl::CComboBox _langCombo; + CObjectVector _langs; + unsigned NumLangLines_EN; + bool _needSave; + + void ShowLangInfo(); +public: + bool LangWasChanged; + + CLangPage(): _needSave(false), LangWasChanged(false) {} + virtual bool OnInit() Z7_override; + virtual void OnNotifyHelp() Z7_override; + virtual bool OnCommand(unsigned code, unsigned itemID, LPARAM param) Z7_override; + virtual LONG OnApply() Z7_override; +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/LangPage.rc b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/LangPage.rc new file mode 100644 index 00000000..60d730f5 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/LangPage.rc @@ -0,0 +1,40 @@ +#include "LangPageRes.h" +#include "../../GuiCommon.rc" + +#define xc OPTIONS_PAGE_XC_SIZE +#define yc OPTIONS_PAGE_YC_SIZE + +#define y 32 + +IDD_LANG DIALOG MY_PAGE_POSTFIX +CAPTION "Language" +{ + LTEXT "Language:", IDT_LANG_LANG, m, m, xc, 8 + COMBOBOX IDC_LANG_LANG, m, 20, 160, yc - 20, MY_COMBO // MY_COMBO_SORTED + LTEXT "", IDT_LANG_INFO, m, m + y, xc, yc - y, SS_NOPREFIX +} + + +#ifdef UNDER_CE + +#undef m +#undef xc + +#define m 4 +#define xc (SMALL_PAGE_SIZE_X + 8) + +IDD_LANG_2 MY_PAGE +CAPTION "Language" +{ + LTEXT "Language:", IDT_LANG_LANG, m, m, xc, 8 + COMBOBOX IDC_LANG_LANG, m, 20, xc, yc - 20, MY_COMBO // MY_COMBO_SORTED +} + +#endif + + +STRINGTABLE +BEGIN + IDS_LANG_ENGLISH "English" + IDS_LANG_NATIVE "English" +END diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/LangPageRes.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/LangPageRes.h new file mode 100644 index 00000000..a1ad30fa --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/LangPageRes.h @@ -0,0 +1,9 @@ +#define IDD_LANG 2101 +#define IDD_LANG_2 12101 + +#define IDS_LANG_ENGLISH 1 +#define IDS_LANG_NATIVE 2 + +#define IDT_LANG_LANG 2102 +#define IDC_LANG_LANG 100 +#define IDT_LANG_INFO 101 diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/LangUtils.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/LangUtils.cpp new file mode 100644 index 00000000..47121928 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/LangUtils.cpp @@ -0,0 +1,333 @@ +// LangUtils.cpp + +#include "StdAfx.h" + +#include "../../../Common/Lang.h" + +#include "../../../Windows/DLL.h" +#include "../../../Windows/Synchronization.h" +#include "../../../Windows/Window.h" + +#include "LangUtils.h" +#include "RegistryUtils.h" + +using namespace NWindows; + +#ifndef _UNICODE +extern bool g_IsNT; +#endif + +UString g_LangID; + +// static +CLang g_Lang; +static bool g_Loaded = false; +static NSynchronization::CCriticalSection g_CriticalSection; + +bool LangOpen(CLang &lang, CFSTR fileName); +bool LangOpen(CLang &lang, CFSTR fileName) +{ + return lang.Open(fileName, "7-Zip"); +} + +FString GetLangDirPrefix() +{ + return NDLL::GetModuleDirPrefix() + FTEXT("Lang") FSTRING_PATH_SEPARATOR; +} + +#ifdef Z7_LANG + +void LoadLangOneTime() +{ + NSynchronization::CCriticalSectionLock lock(g_CriticalSection); + if (g_Loaded) + return; + g_Loaded = true; + ReloadLang(); +} + +void LangSetDlgItemText(HWND dialog, UInt32 controlID, UInt32 langID) +{ + const wchar_t *s = g_Lang.Get(langID); + if (s) + { + CWindow window(GetDlgItem(dialog, (int)controlID)); + window.SetText(s); + } +} + +#ifndef IDCONTINUE +#define IDCONTINUE 11 +#endif + +static const CIDLangPair kLangPairs[] = +{ + { IDOK, 401 }, + { IDCANCEL, 402 }, + { IDYES, 406 }, + { IDNO, 407 }, + { IDCLOSE, 408 }, + { IDHELP, 409 }, + { IDCONTINUE, 411 } +}; + + +void LangSetDlgItems(HWND dialog, const UInt32 *ids, unsigned numItems) +{ + unsigned i; + for (i = 0; i < Z7_ARRAY_SIZE(kLangPairs); i++) + { + const CIDLangPair &pair = kLangPairs[i]; + CWindow window(GetDlgItem(dialog, (int)pair.ControlID)); + if (window) + { + const wchar_t *s = g_Lang.Get(pair.LangID); + if (s) + window.SetText(s); + } + } + + for (i = 0; i < numItems; i++) + { + const UInt32 id = ids[i]; + LangSetDlgItemText(dialog, id, id); + } +} + +void LangSetDlgItems_Colon(HWND dialog, const UInt32 *ids, unsigned numItems) +{ + for (unsigned i = 0; i < numItems; i++) + { + const UInt32 id = ids[i]; + const wchar_t *s = g_Lang.Get(id); + if (s) + { + CWindow window(GetDlgItem(dialog, (int)id)); + UString s2 = s; + s2.Add_Colon(); + window.SetText(s2); + } + } +} + +void LangSetDlgItems_RemoveColon(HWND dialog, const UInt32 *ids, unsigned numItems) +{ + for (unsigned i = 0; i < numItems; i++) + { + const UInt32 id = ids[i]; + const wchar_t *s = g_Lang.Get(id); + if (s) + { + CWindow window(GetDlgItem(dialog, (int)id)); + UString s2 = s; + if (!s2.IsEmpty() && s2.Back() == ':') + s2.DeleteBack(); + window.SetText(s2); + } + } +} + +void LangSetWindowText(HWND window, UInt32 langID) +{ + const wchar_t *s = g_Lang.Get(langID); + if (s) + MySetWindowText(window, s); +} + +UString LangString(UInt32 langID) +{ + const wchar_t *s = g_Lang.Get(langID); + if (s) + return s; + return MyLoadString(langID); +} + +void AddLangString(UString &s, UInt32 langID) +{ + s += LangString(langID); +} + +void LangString(UInt32 langID, UString &dest) +{ + const wchar_t *s = g_Lang.Get(langID); + if (s) + { + dest = s; + return; + } + MyLoadString(langID, dest); +} + +void LangString_OnlyFromLangFile(UInt32 langID, UString &dest) +{ + dest.Empty(); + const wchar_t *s = g_Lang.Get(langID); + if (s) + dest = s; +} + +static const char * const kLangs = + "ar.bg.ca.zh.-tw.-cn.cs.da.de.el.en.es.fi.fr.he.hu.is." + "it.ja.ko.nl.no.=nb.=nn.pl.pt.-br.rm.ro.ru.sr.=hr.-spl.-spc.=hr.=bs.sk.sq.sv.th.tr." + "ur.id.uk.be.sl.et.lv.lt.tg.fa.vi.hy.az.eu.hsb.mk." + "st.ts.tn.ve.xh.zu.af.ka.fo.hi.mt.se.ga.yi.ms.kk." + "ky.sw.tk.uz.-latn.-cyrl.tt.bn.pa.-in.gu.or.ta.te.kn.ml.as.mr.sa." + "mn.=mn.=mng.bo.cy.kh.lo.my.gl.kok..sd.syr.si..iu.am.tzm." + "ks.ne.fy.ps.tl.dv..ff.ha..yo.qu.st.ba.lb.kl." + "ig.kr.om.ti.gn..la.so.ii..arn..moh..br.." + "ug.mi.oc.co." + // "gsw.sah.qut.rw.wo....prs...." + // ".gd." + ; + +static void FindShortNames(UInt32 primeLang, AStringVector &names) +{ + UInt32 index = 0; + for (const char *p = kLangs; *p != 0;) + { + const char *p2 = p; + for (; *p2 != '.'; p2++); + bool isSub = (p[0] == '-' || p[0] == '='); + if (!isSub) + index++; + if (index >= primeLang) + { + if (index > primeLang) + break; + AString s; + if (isSub) + { + if (p[0] == '-') + s = names[0]; + else + p++; + } + while (p != p2) + s.Add_Char((char)(Byte)*p++); + names.Add(s); + } + p = p2 + 1; + } +} + +/* +#include "../../../Common/IntToString.h" + +static struct CC1Lang +{ + CC1Lang() + { + for (int i = 1; i < 150; i++) + { + UString s; + char ttt[32]; + ConvertUInt32ToHex(i, ttt); + s += ttt; + UStringVector names; + FindShortNames(i, names); + + FOR_VECTOR (k, names) + { + s.Add_Space(); + s += names[k]; + } + OutputDebugStringW(s); + } + } +} g_cc1; +*/ + +// typedef LANGID (WINAPI *GetUserDefaultUILanguageP)(); + +void Lang_GetShortNames_for_DefaultLang(AStringVector &names, unsigned &subLang) +{ + names.Clear(); + subLang = 0; + // Region / Administative / Language for non-Unicode programs: + const LANGID sysLang = GetSystemDefaultLangID(); + + // Region / Formats / Format: + const LANGID userLang = GetUserDefaultLangID(); + + if (PRIMARYLANGID(sysLang) != + PRIMARYLANGID(userLang)) + return; + const LANGID langID = userLang; + + // const LANGID langID = MAKELANGID(0x1a, 1); // for debug + + /* + LANGID sysUILang; // english in XP64 + LANGID userUILang; // english in XP64 + + GetUserDefaultUILanguageP fn = (GetUserDefaultUILanguageP)GetProcAddress( + GetModuleHandle("kernel32"), "GetUserDefaultUILanguage"); + if (fn) + userUILang = fn(); + fn = (GetUserDefaultUILanguageP)GetProcAddress( + GetModuleHandle("kernel32"), "GetSystemDefaultUILanguage"); + if (fn) + sysUILang = fn(); + */ + + const WORD primLang = (WORD)(PRIMARYLANGID(langID)); + subLang = SUBLANGID(langID); + FindShortNames(primLang, names); +} + + +static void OpenDefaultLang() +{ + AStringVector names; + unsigned subLang; + Lang_GetShortNames_for_DefaultLang(names, subLang); + { + const FString dirPrefix (GetLangDirPrefix()); + for (unsigned i = 0; i < 2; i++) + { + const unsigned index = (i == 0 ? subLang : 0); + if (index < names.Size()) + { + const AString &name = names[index]; + if (!name.IsEmpty()) + { + FString path (dirPrefix); + path += name; + path += ".txt"; + if (LangOpen(g_Lang, path)) + { + g_LangID = name; + return; + } + } + } + } + } +} + +void ReloadLang() +{ + g_Lang.Clear(); + ReadRegLang(g_LangID); + if (g_LangID.IsEmpty()) + { +#ifndef _UNICODE + if (g_IsNT) +#endif + OpenDefaultLang(); + return; + } + if (g_LangID.Len() > 1 || g_LangID[0] != L'-') + { + FString s = us2fs(g_LangID); + if (s.ReverseFind_PathSepar() < 0) + { + if (s.ReverseFind_Dot() < 0) + s += ".txt"; + s.Insert(0, GetLangDirPrefix()); + LangOpen(g_Lang, s); + } + } +} + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/LangUtils.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/LangUtils.h new file mode 100644 index 00000000..d53d270e --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/LangUtils.h @@ -0,0 +1,48 @@ +// LangUtils.h + +#ifndef ZIP7_INC_LANG_UTILS_H +#define ZIP7_INC_LANG_UTILS_H + +#include "../../../Common/Lang.h" + +#include "../../../Windows/ResourceString.h" + +extern UString g_LangID; +extern CLang g_Lang; + +#ifdef Z7_LANG + +struct CIDLangPair +{ + UInt32 ControlID; + UInt32 LangID; +}; + +void ReloadLang(); +void LoadLangOneTime(); + +void LangSetDlgItemText(HWND dialog, UInt32 controlID, UInt32 langID); +void LangSetDlgItems(HWND dialog, const UInt32 *ids, unsigned numItems); +void LangSetDlgItems_Colon(HWND dialog, const UInt32 *ids, unsigned numItems); +void LangSetDlgItems_RemoveColon(HWND dialog, const UInt32 *ids, unsigned numItems); +void LangSetWindowText(HWND window, UInt32 langID); + +UString LangString(UInt32 langID); +void AddLangString(UString &s, UInt32 langID); +void LangString(UInt32 langID, UString &dest); +void LangString_OnlyFromLangFile(UInt32 langID, UString &dest); + +#else + +inline UString LangString(UInt32 langID) { return NWindows::MyLoadString(langID); } +inline void LangString(UInt32 langID, UString &dest) { NWindows::MyLoadString(langID, dest); } +inline void AddLangString(UString &s, UInt32 langID) { s += NWindows::MyLoadString(langID); } + +#endif + +FString GetLangDirPrefix(); +// bool LangOpen(CLang &lang, CFSTR fileName); + +void Lang_GetShortNames_for_DefaultLang(AStringVector &names, unsigned &subLang); + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/LinkDialog.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/LinkDialog.cpp new file mode 100644 index 00000000..a92ee4d3 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/LinkDialog.cpp @@ -0,0 +1,402 @@ +// LinkDialog.cpp + +#include "StdAfx.h" + +#include "../../../Windows/ErrorMsg.h" +#include "../../../Windows/FileDir.h" +#include "../../../Windows/FileFind.h" +#include "../../../Windows/FileIO.h" +#include "../../../Windows/FileName.h" + +#include "LangUtils.h" + +#include "BrowseDialog.h" +#include "CopyDialogRes.h" +#include "LinkDialog.h" +#include "resourceGui.h" + +#include "App.h" + +#include "resource.h" + +extern bool g_SymLink_Supported; + +using namespace NWindows; +using namespace NFile; + +#ifdef Z7_LANG +static const UInt32 kLangIDs[] = +{ + IDB_LINK_LINK, + IDT_LINK_PATH_FROM, + IDT_LINK_PATH_TO, + IDG_LINK_TYPE, + IDR_LINK_TYPE_HARD, + IDR_LINK_TYPE_SYM_FILE, + IDR_LINK_TYPE_SYM_DIR, + IDR_LINK_TYPE_JUNCTION, + IDR_LINK_TYPE_WSL +}; +#endif + + +static bool GetSymLink(CFSTR path, CReparseAttr &attr, UString &errorMessage) +{ + CByteBuffer buf; + if (!NIO::GetReparseData(path, buf, NULL)) + return false; + if (!attr.Parse(buf, buf.Size())) + { + SetLastError(attr.ErrorCode); + return false; + } + CByteBuffer data2; + FillLinkData(data2, attr.GetPath(), + !attr.IsMountPoint(), attr.IsSymLink_WSL()); + if (data2.Size() == 0) + { + errorMessage = "Cannot reproduce reparse point"; + return false; + } + if (data2 != buf) + { + errorMessage = "mismatch for reproduced reparse point"; + return false; + } + return true; +} + + +static const unsigned k_LinkType_Buttons[] = +{ + IDR_LINK_TYPE_HARD, + IDR_LINK_TYPE_SYM_FILE, + IDR_LINK_TYPE_SYM_DIR, + IDR_LINK_TYPE_JUNCTION, + IDR_LINK_TYPE_WSL +}; + +void CLinkDialog::Set_LinkType_Radio(unsigned idb) +{ + CheckRadioButton( + k_LinkType_Buttons[0], + k_LinkType_Buttons[Z7_ARRAY_SIZE(k_LinkType_Buttons) - 1], + idb); +} + +bool CLinkDialog::OnInit() +{ + #ifdef Z7_LANG + LangSetWindowText(*this, IDD_LINK); + LangSetDlgItems(*this, kLangIDs, Z7_ARRAY_SIZE(kLangIDs)); + #endif + + _pathFromCombo.Attach(GetItem(IDC_LINK_PATH_FROM)); + _pathToCombo.Attach(GetItem(IDC_LINK_PATH_TO)); + + if (!FilePath.IsEmpty()) + { + NFind::CFileInfo fi; + unsigned linkType = 0; + if (!fi.Find(us2fs(FilePath))) + linkType = IDR_LINK_TYPE_SYM_FILE; + else + { + if (fi.HasReparsePoint()) + { + CReparseAttr attr; + UString error; + const bool res = GetSymLink(us2fs(FilePath), attr, error); + if (!res && error.IsEmpty()) + { + const DWORD lastError = GetLastError(); + if (lastError) + error = NError::MyFormatMessage(lastError); + } + + UString s = attr.GetPath(); + if (!attr.IsSymLink_WSL()) + if (!attr.IsOkNamePair()) + { + s += " : "; + s += attr.PrintName; + } + + if (!res) + { + s.Insert(0, L"ERROR: "); + if (!error.IsEmpty()) + { + s += " : "; + s += error; + } + } + + + SetItemText(IDT_LINK_PATH_TO_CUR, s); + + const UString destPath = attr.GetPath(); + _pathFromCombo.SetText(FilePath); + _pathToCombo.SetText(destPath); + + // if (res) + { + if (attr.IsMountPoint()) + linkType = IDR_LINK_TYPE_JUNCTION; + else if (attr.IsSymLink_WSL()) + linkType = IDR_LINK_TYPE_WSL; + else if (attr.IsSymLink_Win()) + { + linkType = + fi.IsDir() ? + IDR_LINK_TYPE_SYM_DIR : + IDR_LINK_TYPE_SYM_FILE; + // if (attr.IsRelative()) linkType = IDR_LINK_TYPE_SYM_RELATIVE; + } + + if (linkType != 0) + Set_LinkType_Radio(linkType); + } + } + else + { + // no ReparsePoint + _pathFromCombo.SetText(AnotherPath); + _pathToCombo.SetText(FilePath); + if (fi.IsDir()) + linkType = g_SymLink_Supported ? + IDR_LINK_TYPE_SYM_DIR : + IDR_LINK_TYPE_JUNCTION; + else + linkType = IDR_LINK_TYPE_HARD; + } + } + if (linkType != 0) + Set_LinkType_Radio(linkType); + } + + NormalizeSize(); + return CModalDialog::OnInit(); +} + +bool CLinkDialog::OnSize(WPARAM /* wParam */, int xSize, int ySize) +{ + int mx, my; + GetMargins(8, mx, my); + int bx1, bx2, by; + GetItemSizes(IDCANCEL, bx1, by); + GetItemSizes(IDB_LINK_LINK, bx2, by); + int yPos = ySize - my - by; + int xPos = xSize - mx - bx1; + + InvalidateRect(NULL); + + { + RECT r, r2; + GetClientRectOfItem(IDB_LINK_PATH_FROM, r); + GetClientRectOfItem(IDB_LINK_PATH_TO, r2); + int bx = RECT_SIZE_X(r); + int newButtonXpos = xSize - mx - bx; + + MoveItem(IDB_LINK_PATH_FROM, newButtonXpos, r.top, bx, RECT_SIZE_Y(r)); + MoveItem(IDB_LINK_PATH_TO, newButtonXpos, r2.top, bx, RECT_SIZE_Y(r2)); + + int newComboXsize = newButtonXpos - mx - mx; + ChangeSubWindowSizeX(_pathFromCombo, newComboXsize); + ChangeSubWindowSizeX(_pathToCombo, newComboXsize); + } + + MoveItem(IDCANCEL, xPos, yPos, bx1, by); + MoveItem(IDB_LINK_LINK, xPos - mx - bx2, yPos, bx2, by); + + return false; +} + +bool CLinkDialog::OnButtonClicked(unsigned buttonID, HWND buttonHWND) +{ + switch (buttonID) + { + case IDB_LINK_PATH_FROM: + OnButton_SetPath(false); + return true; + case IDB_LINK_PATH_TO: + OnButton_SetPath(true); + return true; + case IDB_LINK_LINK: + OnButton_Link(); + return true; + } + return CModalDialog::OnButtonClicked(buttonID, buttonHWND); +} + +void CLinkDialog::OnButton_SetPath(bool to) +{ + UString currentPath; + NWindows::NControl::CComboBox &combo = to ? + _pathToCombo : + _pathFromCombo; + combo.GetText(currentPath); + // UString title = "Specify a location for output folder"; + const UString title = LangString(IDS_SET_FOLDER); + + UString resultPath; + if (!MyBrowseForFolder(*this, title, currentPath, resultPath)) + return; + NName::NormalizeDirPathPrefix(resultPath); + combo.SetCurSel(-1); + combo.SetText(resultPath); +} + +void CLinkDialog::ShowError(const wchar_t *s) +{ + ::MessageBoxW(*this, s, L"7-Zip", MB_ICONERROR); +} + +void CLinkDialog::ShowLastErrorMessage() +{ + ShowError(NError::MyFormatMessage(GetLastError())); +} + +void CLinkDialog::OnButton_Link() +{ + UString from, to; + _pathFromCombo.GetText(from); + _pathToCombo.GetText(to); + + if (from.IsEmpty()) + return; + if (!NName::IsAbsolutePath(from)) + from.Insert(0, CurDirPrefix); + + unsigned idb = 0; + for (unsigned i = 0;; i++) + { + if (i >= Z7_ARRAY_SIZE(k_LinkType_Buttons)) + return; + idb = k_LinkType_Buttons[i]; + if (IsButtonCheckedBool(idb)) + break; + } + + NFind::CFileInfo info1, info2; + const bool finded1 = info1.Find(us2fs(from)); + const bool finded2 = info2.Find(us2fs(to)); + + const bool isDirLink = ( + idb == IDR_LINK_TYPE_SYM_DIR || + idb == IDR_LINK_TYPE_JUNCTION); + + const bool isWSL = (idb == IDR_LINK_TYPE_WSL); + + if (!isWSL) + if ((finded1 && info1.IsDir() != isDirLink) || + (finded2 && info2.IsDir() != isDirLink)) + { + ShowError(L"Incorrect link type"); + return; + } + + if (idb == IDR_LINK_TYPE_HARD) + { + if (!NDir::MyCreateHardLink(us2fs(from), us2fs(to))) + { + ShowLastErrorMessage(); + return; + } + } + else + { + if (finded1 && !info1.IsDir() && !info1.HasReparsePoint() && info1.Size != 0) + { + UString s ("WARNING: reparse point will hide the data of existing file"); + s.Add_LF(); + s += from; + ShowError(s); + return; + } + + CByteBuffer data; + const bool isSymLink = (idb != IDR_LINK_TYPE_JUNCTION); + FillLinkData(data, to, isSymLink, isWSL); + if (data.Size() == 0) + { + ShowError(L"Incorrect link"); + return; + } + + CReparseAttr attr; + if (!attr.Parse(data, data.Size())) + { + ShowError(L"Internal conversion error"); + return; + } + + bool res; + if (to.IsEmpty()) + { + // res = NIO::SetReparseData(us2fs(from), isDirLink, NULL, 0); + res = NIO::DeleteReparseData(us2fs(from)); + } + else + res = NIO::SetReparseData(us2fs(from), isDirLink, data, (DWORD)data.Size()); + + if (!res) + { + ShowLastErrorMessage(); + return; + } + } + + End(IDOK); +} + +void CApp::Link() +{ + const unsigned srcPanelIndex = GetFocusedPanelIndex(); + CPanel &srcPanel = Panels[srcPanelIndex]; + if (!srcPanel.IsFSFolder()) + { + srcPanel.MessageBox_Error_UnsupportOperation(); + return; + } + CRecordVector indices; + srcPanel.Get_ItemIndices_Operated(indices); + if (indices.IsEmpty()) + return; + if (indices.Size() != 1) + { + srcPanel.MessageBox_Error_LangID(IDS_SELECT_ONE_FILE); + return; + } + const UInt32 index = indices[0]; + const UString itemName = srcPanel.GetItemName(index); + + const UString fsPrefix = srcPanel.GetFsPath(); + const UString srcPath = fsPrefix + srcPanel.GetItemPrefix(index); + UString path = srcPath; + { + const unsigned destPanelIndex = (NumPanels <= 1) ? srcPanelIndex : (1 - srcPanelIndex); + CPanel &destPanel = Panels[destPanelIndex]; + if (NumPanels > 1) + if (destPanel.IsFSFolder()) + path = destPanel.GetFsPath(); + } + + CSelectedState srcSelState; + srcPanel.SaveSelectedState(srcSelState); + + CLinkDialog dlg; + dlg.CurDirPrefix = fsPrefix; + dlg.FilePath = srcPath + itemName; + dlg.AnotherPath = path; + + if (dlg.Create(srcPanel.GetParent()) != IDOK) + return; + + // we refresh srcPanel to show changes in "Link" (kpidNtReparse) column. + // maybe we should refresh another panel also? + if (srcPanel._visibleColumns.FindItem_for_PropID(kpidNtReparse) >= 0) + srcPanel.RefreshListCtrl(srcSelState); + + RefreshTitleAlways(); +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/LinkDialog.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/LinkDialog.h new file mode 100644 index 00000000..dd768f7e --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/LinkDialog.h @@ -0,0 +1,34 @@ +// LinkDialog.h + +#ifndef ZIP7_INC_LINK_DIALOG_H +#define ZIP7_INC_LINK_DIALOG_H + +#include "../../../Windows/Control/Dialog.h" +#include "../../../Windows/Control/ComboBox.h" + +#include "LinkDialogRes.h" + +class CLinkDialog: public NWindows::NControl::CModalDialog +{ + NWindows::NControl::CComboBox _pathFromCombo; + NWindows::NControl::CComboBox _pathToCombo; + + virtual bool OnInit() Z7_override; + virtual bool OnSize(WPARAM wParam, int xSize, int ySize) Z7_override; + virtual bool OnButtonClicked(unsigned buttonID, HWND buttonHWND) Z7_override; + void OnButton_SetPath(bool to); + void OnButton_Link(); + + void ShowLastErrorMessage(); + void ShowError(const wchar_t *s); + void Set_LinkType_Radio(unsigned idb); +public: + UString CurDirPrefix; + UString FilePath; + UString AnotherPath; + + INT_PTR Create(HWND parentWindow = NULL) + { return CModalDialog::Create(IDD_LINK, parentWindow); } +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/LinkDialog.rc b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/LinkDialog.rc new file mode 100644 index 00000000..a9e220ba --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/LinkDialog.rc @@ -0,0 +1,38 @@ +#include "LinkDialogRes.h" +#include "../../GuiCommon.rc" + +#define xc 288 +#define yc 214 + +#undef xRadioSize +#define xRadioSize xc - m - 2 + +IDD_LINK DIALOG 0, 0, xs, ys MY_MODAL_RESIZE_DIALOG_STYLE MY_FONT +CAPTION "Link" +BEGIN + LTEXT "Link from:", IDT_LINK_PATH_FROM, m, m, xc, 8 + COMBOBOX IDC_LINK_PATH_FROM, m, 20, xc - bxsDots - m, 64, MY_COMBO_WITH_EDIT + PUSHBUTTON "...", IDB_LINK_PATH_FROM, xs - m - bxsDots, 18, bxsDots, bys, WS_GROUP + + LTEXT "Link to:", IDT_LINK_PATH_TO, m, 48, xc, 8 + COMBOBOX IDC_LINK_PATH_TO, m, 60, xc - bxsDots - m, 64, MY_COMBO_WITH_EDIT + PUSHBUTTON "...", IDB_LINK_PATH_TO, xs - m - bxsDots, 58, bxsDots, bys, WS_GROUP + + LTEXT "", IDT_LINK_PATH_TO_CUR, m, 78, xc, 8 + + GROUPBOX "Link Type", IDG_LINK_TYPE, m, 104, xc, 90 + + CONTROL "Hard Link", IDR_LINK_TYPE_HARD, "Button", BS_AUTORADIOBUTTON | WS_GROUP, + m + m, 120, xRadioSize, 10 + CONTROL "File Symbolic Link", IDR_LINK_TYPE_SYM_FILE, "Button", BS_AUTORADIOBUTTON, + m + m, 134, xRadioSize, 10 + CONTROL "Directory Symbolic Link", IDR_LINK_TYPE_SYM_DIR, "Button", BS_AUTORADIOBUTTON, + m + m, 148, xRadioSize, 10 + CONTROL "Directory Junction", IDR_LINK_TYPE_JUNCTION, "Button", BS_AUTORADIOBUTTON, + m + m, 162, xRadioSize, 10 + CONTROL "WSL", IDR_LINK_TYPE_WSL, "Button", BS_AUTORADIOBUTTON, + m + m, 176, xRadioSize, 10 + + DEFPUSHBUTTON "Link", IDB_LINK_LINK, bx2, by, bxs, bys + PUSHBUTTON "Cancel", IDCANCEL, bx1, by, bxs, bys +END diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/LinkDialogRes.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/LinkDialogRes.h new file mode 100644 index 00000000..3f7b3f23 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/LinkDialogRes.h @@ -0,0 +1,22 @@ +#define IDD_LINK 7700 + +#define IDB_LINK_LINK 7701 + +#define IDT_LINK_PATH_FROM 7702 +#define IDT_LINK_PATH_TO 7703 + +#define IDG_LINK_TYPE 7710 +#define IDR_LINK_TYPE_HARD 7711 +#define IDR_LINK_TYPE_SYM_FILE 7712 +#define IDR_LINK_TYPE_SYM_DIR 7713 +#define IDR_LINK_TYPE_JUNCTION 7714 +#define IDR_LINK_TYPE_WSL 7715 + + +#define IDC_LINK_PATH_FROM 100 +#define IDC_LINK_PATH_TO 101 + +#define IDT_LINK_PATH_TO_CUR 102 + +#define IDB_LINK_PATH_FROM 103 +#define IDB_LINK_PATH_TO 104 diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ListViewDialog.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ListViewDialog.cpp new file mode 100644 index 00000000..6767e4c0 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ListViewDialog.cpp @@ -0,0 +1,321 @@ +// ListViewDialog.cpp + +#include "StdAfx.h" + +#include "../../../Windows/Clipboard.h" + +#include "EditDialog.h" +#include "ListViewDialog.h" +#include "RegistryUtils.h" + +#ifdef Z7_LANG +#include "LangUtils.h" +#endif + +using namespace NWindows; + +static const unsigned kOneStringMaxSize = 1024; + + +static void ListView_GetSelected(NControl::CListView &listView, CUIntVector &vector) +{ + vector.Clear(); + int index = -1; + for (;;) + { + index = listView.GetNextSelectedItem(index); + if (index < 0) + break; + vector.Add((unsigned)index); + } +} + + +bool CListViewDialog::OnInit() +{ + #ifdef Z7_LANG + LangSetDlgItems(*this, NULL, 0); + #endif + _listView.Attach(GetItem(IDL_LISTVIEW)); + + if (NumColumns > 1) + { + LONG_PTR style = _listView.GetStyle(); + style &= ~(LONG_PTR)LVS_NOCOLUMNHEADER; + _listView.SetStyle(style); + } + + CFmSettings st; + st.Load(); + + DWORD exStyle = 0; + + if (st.SingleClick) + exStyle |= LVS_EX_ONECLICKACTIVATE | LVS_EX_TRACKSELECT; + + exStyle |= LVS_EX_FULLROWSELECT; + if (exStyle != 0) + _listView.SetExtendedListViewStyle(exStyle); + + + SetText(Title); + + const int kWidth = 400; + + LVCOLUMN columnInfo; + columnInfo.mask = LVCF_FMT | LVCF_WIDTH | LVCF_SUBITEM; + columnInfo.fmt = LVCFMT_LEFT; + columnInfo.iSubItem = 0; + columnInfo.cx = kWidth; + columnInfo.pszText = NULL; // (TCHAR *)(const TCHAR *)""; // "Property" + + if (NumColumns > 1) + { + columnInfo.cx = 100; + /* + // Windows always uses LVCFMT_LEFT for first column. + // if we need LVCFMT_RIGHT, we can create dummy column and then remove it + + // columnInfo.mask |= LVCF_TEXT; + _listView.InsertColumn(0, &columnInfo); + + columnInfo.iSubItem = 1; + columnInfo.fmt = LVCFMT_RIGHT; + _listView.InsertColumn(1, &columnInfo); + _listView.DeleteColumn(0); + */ + } + // else + _listView.InsertColumn(0, &columnInfo); + + if (NumColumns > 1) + { + // columnInfo.fmt = LVCFMT_LEFT; + columnInfo.cx = kWidth - columnInfo.cx; + columnInfo.iSubItem = 1; + // columnInfo.pszText = NULL; // (TCHAR *)(const TCHAR *)""; // "Value" + _listView.InsertColumn(1, &columnInfo); + } + + + UString s; + + FOR_VECTOR (i, Strings) + { + _listView.InsertItem(i, Strings[i]); + + if (NumColumns > 1 && i < Values.Size()) + { + s = Values[i]; + if (s.Len() > kOneStringMaxSize) + { + s.DeleteFrom(kOneStringMaxSize); + s += " ..."; + } + s.Replace(L"\r\n", L" "); + s.Replace(L"\n", L" "); + _listView.SetSubItem(i, 1, s); + } + } + + if (SelectFirst && Strings.Size() > 0) + _listView.SetItemState_FocusedSelected(0); + + _listView.SetColumnWidthAuto(0); + if (NumColumns > 1) + _listView.SetColumnWidthAuto(1); + StringsWereChanged = false; + + NormalizeSize(); + return CModalDialog::OnInit(); +} + +bool CListViewDialog::OnSize(WPARAM /* wParam */, int xSize, int ySize) +{ + int mx, my; + GetMargins(8, mx, my); + int bx1, bx2, by; + GetItemSizes(IDCANCEL, bx1, by); + GetItemSizes(IDOK, bx2, by); + int y = ySize - my - by; + int x = xSize - mx - bx1; + + /* + RECT rect; + GetClientRect(&rect); + rect.top = y - my; + InvalidateRect(&rect); + */ + InvalidateRect(NULL); + + MoveItem(IDCANCEL, x, y, bx1, by); + MoveItem(IDOK, x - mx - bx2, y, bx2, by); + /* + if (wParam == SIZE_MAXSHOW || wParam == SIZE_MAXIMIZED || wParam == SIZE_MAXHIDE) + mx = 0; + */ + _listView.Move(mx, my, xSize - mx * 2, y - my * 2); + return false; +} + + +extern bool g_LVN_ITEMACTIVATE_Support; + +void CListViewDialog::CopyToClipboard() +{ + CUIntVector indexes; + ListView_GetSelected(_listView, indexes); + UString s; + + FOR_VECTOR (i, indexes) + { + unsigned index = indexes[i]; + s += Strings[index]; + if (NumColumns > 1 && index < Values.Size()) + { + const UString &v = Values[index]; + // if (!v.IsEmpty()) + { + s += ": "; + s += v; + } + } + // if (indexes.Size() > 1) + { + s += + #ifdef _WIN32 + "\r\n" + #else + "\n" + #endif + ; + } + } + + ClipboardSetText(*this, s); +} + + +void CListViewDialog::ShowItemInfo() +{ + CUIntVector indexes; + ListView_GetSelected(_listView, indexes); + if (indexes.Size() != 1) + return; + unsigned index = indexes[0]; + + CEditDialog dlg; + if (NumColumns == 1) + dlg.Text = Strings[index]; + else + { + dlg.Title = Strings[index]; + if (index < Values.Size()) + dlg.Text = Values[index]; + } + + #ifdef _WIN32 + if (dlg.Text.Find(L'\r') < 0) + dlg.Text.Replace(L"\n", L"\r\n"); + #endif + + dlg.Create(*this); +} + + +void CListViewDialog::DeleteItems() +{ + for (;;) + { + const int index = _listView.GetNextSelectedItem(-1); + if (index < 0) + break; + StringsWereChanged = true; + _listView.DeleteItem((unsigned)index); + if ((unsigned)index < Strings.Size()) + Strings.Delete((unsigned)index); + if ((unsigned)index < Values.Size()) + Values.Delete((unsigned)index); + } + const int focusedIndex = _listView.GetFocusedItem(); + if (focusedIndex >= 0) + _listView.SetItemState_FocusedSelected(focusedIndex); + _listView.SetColumnWidthAuto(0); +} + + +void CListViewDialog::OnEnter() +{ + if (IsKeyDown(VK_MENU) + || NumColumns > 1) + { + ShowItemInfo(); + return; + } + OnOK(); +} + +bool CListViewDialog::OnNotify(UINT /* controlID */, LPNMHDR header) +{ + if (header->hwndFrom != _listView) + return false; + switch (header->code) + { + case LVN_ITEMACTIVATE: + if (g_LVN_ITEMACTIVATE_Support) + { + OnEnter(); + return true; + } + break; + case NM_DBLCLK: + case NM_RETURN: // probabably it's unused + if (!g_LVN_ITEMACTIVATE_Support) + { + OnEnter(); + return true; + } + break; + + case LVN_KEYDOWN: + { + LPNMLVKEYDOWN keyDownInfo = LPNMLVKEYDOWN(header); + switch (keyDownInfo->wVKey) + { + case VK_DELETE: + { + if (!DeleteIsAllowed) + return false; + DeleteItems(); + return true; + } + case 'A': + { + if (IsKeyDown(VK_CONTROL)) + { + _listView.SelectAll(); + return true; + } + break; + } + case VK_INSERT: + case 'C': + { + if (IsKeyDown(VK_CONTROL)) + { + CopyToClipboard(); + return true; + } + break; + } + } + } + } + return false; +} + +void CListViewDialog::OnOK() +{ + FocusedItemIndex = _listView.GetFocusedItem(); + CModalDialog::OnOK(); +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ListViewDialog.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ListViewDialog.h new file mode 100644 index 00000000..5f0b66de --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ListViewDialog.h @@ -0,0 +1,46 @@ +// ListViewDialog.h + +#ifndef ZIP7_INC_LISTVIEW_DIALOG_H +#define ZIP7_INC_LISTVIEW_DIALOG_H + +#include "../../../Windows/Control/Dialog.h" +#include "../../../Windows/Control/ListView.h" + +#include "ListViewDialogRes.h" + +class CListViewDialog: public NWindows::NControl::CModalDialog +{ + NWindows::NControl::CListView _listView; + virtual void OnOK() Z7_override; + virtual bool OnInit() Z7_override; + virtual bool OnSize(WPARAM wParam, int xSize, int ySize) Z7_override; + virtual bool OnNotify(UINT controlID, LPNMHDR header) Z7_override; + void CopyToClipboard(); + void DeleteItems(); + void ShowItemInfo(); + void OnEnter(); +public: + UString Title; + + bool SelectFirst; + bool DeleteIsAllowed; + bool StringsWereChanged; + + UStringVector Strings; + UStringVector Values; + + int FocusedItemIndex; + unsigned NumColumns; + + INT_PTR Create(HWND wndParent = NULL) { return CModalDialog::Create(IDD_LISTVIEW, wndParent); } + + CListViewDialog(): + SelectFirst(false), + DeleteIsAllowed(false), + StringsWereChanged(false), + FocusedItemIndex(-1), + NumColumns(1) + {} +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ListViewDialog.rc b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ListViewDialog.rc new file mode 100644 index 00000000..4343b755 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ListViewDialog.rc @@ -0,0 +1,14 @@ +#include "ListViewDialogRes.h" +#include "../../GuiCommon.rc" + +#define xc 480 +#define yc 320 + +IDD_LISTVIEW DIALOG 0, 0, xs, ys MY_MODAL_RESIZE_DIALOG_STYLE MY_FONT +CAPTION "ListView" +{ + CONTROL "List1", IDL_LISTVIEW, "SysListView32", LVS_REPORT | LVS_SHOWSELALWAYS | + LVS_AUTOARRANGE | LVS_NOCOLUMNHEADER | WS_BORDER | WS_TABSTOP, + m, m, xc, yc - bys - m + OK_CANCEL +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ListViewDialogRes.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ListViewDialogRes.h new file mode 100644 index 00000000..9abdb9d2 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ListViewDialogRes.h @@ -0,0 +1,2 @@ +#define IDD_LISTVIEW 99 +#define IDL_LISTVIEW 100 diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MemDialog.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MemDialog.cpp new file mode 100644 index 00000000..5d26d3a1 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MemDialog.cpp @@ -0,0 +1,218 @@ +// MemDialog.cpp + +#include "StdAfx.h" + +#include + +#include "MemDialog.h" + +#include "../../../Common/StringToInt.h" +#include "../../../Windows/System.h" +#include "../../../Windows/ErrorMsg.h" + +#include "../Explorer/MyMessages.h" +#include "../GUI/ExtractRes.h" + +#include "resourceGui.h" + +#ifdef Z7_LANG +#include "LangUtils.h" +#endif + +#ifdef Z7_LANG +static const UInt32 kLangIDs[] = +{ + IDX_MEM_SAVE_LIMIT, + IDX_MEM_REMEMBER, + IDG_MEM_ACTION, + IDR_MEM_ACTION_ALLOW, + IDR_MEM_ACTION_SKIP_ARC + // , IDR_MEM_SKIP_FILE +}; +#endif + +static const unsigned k_Action_Buttons[] = +{ + IDR_MEM_ACTION_ALLOW, + IDR_MEM_ACTION_SKIP_ARC + // , IDR_MEM_SKIP_FILE +}; + + +void CMemDialog::EnableSpin(bool enable) +{ + EnableItem(IDC_MEM_SPIN, enable); + EnableItem(IDE_MEM_SPIN_EDIT, enable); +} + + +static void AddSize_GB(UString &s, UInt32 size_GB, UInt32 id) +{ + s.Add_LF(); + s += " "; + s.Add_UInt32(size_GB); + s += " GB : "; + AddLangString(s, id); +} + +void CMemDialog::AddInfoMessage_To_String(UString &s, const UInt32 *ramSize_GB) +{ + AddLangString(s, IDS_MEM_REQUIRES_BIG_MEM); + AddSize_GB(s, Required_GB, IDS_MEM_REQUIRED_MEM_SIZE); + AddSize_GB(s, Limit_GB, IDS_MEM_CURRENT_MEM_LIMIT); + if (ramSize_GB) + AddSize_GB(s, *ramSize_GB, IDS_MEM_RAM_SIZE); + if (!FilePath.IsEmpty()) + { + s.Add_LF(); + s += "File: "; + s += FilePath; + } +} + +/* +int CMemDialog::AddAction(UINT id) +{ + const int index = (int)m_Action.AddString(LangString(id)); + m_Action.SetItemData(index, (LPARAM)id); + return index; +} +*/ + +bool CMemDialog::OnInit() +{ + #ifdef Z7_LANG + LangSetWindowText(*this, IDD_MEM); + LangSetDlgItems(*this, kLangIDs, Z7_ARRAY_SIZE(kLangIDs)); + #endif + + // m_Action.Attach(GetItem(IDC_MEM_ACTION)); + + size_t ramSize = (size_t)sizeof(size_t) << 29; + const bool ramSize_defined = NWindows::NSystem::GetRamSize(ramSize); + // ramSize *= 10; // for debug + + UInt32 ramSize_GB = (UInt32)(((UInt64)ramSize + (1u << 29)) >> 30); + if (ramSize_GB == 0) + ramSize_GB = 1; + + const bool is_Allowed = (!ramSize_defined || ramSize > ((UInt64)Required_GB << 30)); + { + UString s; + if (!is_Allowed) + { + AddLangString(s, IDS_MEM_ERROR); + s.Add_LF(); + } + AddInfoMessage_To_String(s, is_Allowed ? NULL : &ramSize_GB); + if (!ArcPath.IsEmpty()) + // for (int i = 0; i < 10; i++) + { + s.Add_LF(); + AddLangString(s, TestMode ? + IDS_PROGRESS_TESTING : + IDS_PROGRESS_EXTRACTING); + s += ": "; + s += ArcPath; + } + SetItemText(IDT_MEM_MESSAGE, s); + + s = "GB"; + if (ramSize_defined) + { + s += " / "; + s.Add_UInt32(ramSize_GB); + s += " GB (RAM)"; + } + SetItemText(IDT_MEM_GB, s); + } + const UINT valMin = 1; + UINT valMax = 64; // 64GB for RAR7 + if (ramSize_defined /* && ramSize_GB > valMax */) + { + const UINT k_max_val = 1u << 14; + if (ramSize_GB >= k_max_val) + valMax = k_max_val; + else if (ramSize_GB > 1) + valMax = (UINT)ramSize_GB - 1; + else + valMax = 1; + } + + SendItemMessage(IDC_MEM_SPIN, UDM_SETRANGE, 0, MAKELPARAM(valMax, valMin)); // Sets the controls direction + // UDM_SETPOS doesn't set value larger than max value (valMax) of range: + SendItemMessage(IDC_MEM_SPIN, UDM_SETPOS, 0, Required_GB); + { + UString s; + s.Add_UInt32(Required_GB); + SetItemText(IDE_MEM_SPIN_EDIT, s); + } + + EnableSpin(false); + + /* + AddAction(IDB_ALLOW_OPERATION); + m_Action.SetCurSel(0); + AddAction(IDB_MEM_SKIP_ARC); + AddAction(IDB_MEM_SKIP_FILE); + */ + + const UINT buttonId = is_Allowed ? + IDR_MEM_ACTION_ALLOW : + IDR_MEM_ACTION_SKIP_ARC; + + CheckRadioButton( + k_Action_Buttons[0], + k_Action_Buttons[Z7_ARRAY_SIZE(k_Action_Buttons) - 1], + buttonId); + /* + if (!ShowSkipFile) + HideItem(IDR_MEM_SKIP_FILE); + */ + if (!ShowRemember) + HideItem(IDX_MEM_REMEMBER); + return CModalDialog::OnInit(); +} + + +bool CMemDialog::OnButtonClicked(unsigned buttonID, HWND buttonHWND) +{ + if (buttonID == IDX_MEM_SAVE_LIMIT) + { + EnableSpin(IsButtonCheckedBool(IDX_MEM_SAVE_LIMIT)); + return true; + } + return CDialog::OnButtonClicked(buttonID, buttonHWND); +} + + +void CMemDialog::OnContinue() +{ + Remember = IsButtonCheckedBool(IDX_MEM_REMEMBER); + NeedSave = IsButtonCheckedBool(IDX_MEM_SAVE_LIMIT); + SkipArc = IsButtonCheckedBool(IDR_MEM_ACTION_SKIP_ARC); + if (NeedSave) + { +#if 0 + // UDM_GETPOS doesn't support value outside of range that was set: + LRESULT lresult = SendItemMessage(IDC_MEM_SPIN, UDM_GETPOS, 0, 0); + const UInt32 val = LOWORD(lresult); + if (HIWORD(lresult) != 0) // the value outside of allowed range +#else + UString s; + GetItemText(IDE_MEM_SPIN_EDIT, s); + const wchar_t *end; + const UInt32 val = ConvertStringToUInt32(s.Ptr(), &end); + if (s.IsEmpty() || *end != 0 || val > (1u << 30)) +#endif + { + ShowErrorMessage(*this, + NWindows::NError::MyFormatMessage(E_INVALIDARG) + // L"Incorrect value" + ); + return; + } + Limit_GB = val; + } + CModalDialog::OnContinue(); +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MemDialog.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MemDialog.h new file mode 100644 index 00000000..67f6b334 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MemDialog.h @@ -0,0 +1,48 @@ +// MemDialog.h + +#ifndef ZIP7_INC_MEM_DIALOG_H +#define ZIP7_INC_MEM_DIALOG_H + +#include "../../../Windows/Control/Dialog.h" +// #include "../../../Windows/Control/ComboBox.h" + +#include "MemDialogRes.h" + +class CMemDialog: public NWindows::NControl::CModalDialog +{ + // NWindows::NControl::CComboBox m_Action; + // we can disable default OnOK() when we press Enter + // virtual void OnOK() Z7_override { } + virtual void OnContinue() Z7_override; + virtual bool OnInit() Z7_override; + virtual bool OnButtonClicked(unsigned buttonID, HWND buttonHWND) Z7_override; + void EnableSpin(bool enable); + // int AddAction(UINT id); +public: + bool NeedSave; + bool Remember; + bool SkipArc; + bool TestMode; + bool ShowRemember; + // bool ShowSkipFile; + UInt32 Required_GB; + UInt32 Limit_GB; + UString ArcPath; + UString FilePath; + + void AddInfoMessage_To_String(UString &s, const UInt32 *ramSize_GB = NULL); + + CMemDialog(): + NeedSave(false), + Remember(false), + SkipArc(false), + TestMode(false), + ShowRemember(true), + // ShowSkipFile(true), + Required_GB(4), + Limit_GB(4) + {} + INT_PTR Create(HWND parentWindow = NULL) { return CModalDialog::Create(IDD_MEM, parentWindow); } +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MemDialog.rc b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MemDialog.rc new file mode 100644 index 00000000..d8bcb544 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MemDialog.rc @@ -0,0 +1,49 @@ +#include "MemDialogRes.h" +#include "../../GuiCommon.rc" + +#define xc 320 +#define yc 200 + +#define spin_x_size 50 +#define info_y_size 72 +#define save_y (m + info_y_size + 4) +#define spin_y (save_y + 16) + +#define xsg (xc - m - m) +#define xg (m + m) +#define yg (spin_y + 20) + +IDD_MEM DIALOG MY_MODAL_DIALOG_POSTFIX +CAPTION "Memory usage request" +BEGIN + LTEXT "", IDT_MEM_MESSAGE, m, m, xc, info_y_size, SS_NOPREFIX + CONTROL "Change allowed limit for next operations", IDX_MEM_SAVE_LIMIT, MY_CHECKBOX, + m, save_y, xc, 10 + + MY_CONTROL_EDIT_WITH_SPIN( + IDE_MEM_SPIN_EDIT, + IDC_MEM_SPIN, + "4", m + 10, spin_y, spin_x_size) + + LTEXT "GB", IDT_MEM_GB, m + 10 + spin_x_size + 8, spin_y + 2, 160, 10 + + GROUPBOX "Action", IDG_MEM_ACTION, m, yg, xc, 62 + + MY_CONTROL_AUTORADIOBUTTON_GROUP ( + "&Allow archive unpacking", IDR_MEM_ACTION_ALLOW, + xg, yg + 14, xsg) + MY_CONTROL_AUTORADIOBUTTON ( + "&Skip archive unpacking", IDR_MEM_ACTION_SKIP_ARC, + xg, yg + 28, xsg) +// CONTROL "&Skip file extracting", IDR_MEM_SKIP_FILE, MY_AUTORADIOBUTTON, +// xg, yg + 42, xsg, 10 + + CONTROL "&Repeat selected action for current operation", IDX_MEM_REMEMBER, MY_CHECKBOX, + xg + 10, yg + 44, xsg - 10, 10 + + CONTINUE_CANCEL +END + +#undef save_y +#undef spin_y +#undef spin_x_size diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MemDialogRes.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MemDialogRes.h new file mode 100644 index 00000000..9ece82d3 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MemDialogRes.h @@ -0,0 +1,13 @@ +#define IDD_MEM 7800 + +#define IDX_MEM_SAVE_LIMIT 7801 +#define IDX_MEM_REMEMBER 7802 +#define IDG_MEM_ACTION 7803 + +#define IDR_MEM_ACTION_ALLOW 7820 +#define IDR_MEM_ACTION_SKIP_ARC 7821 + +#define IDT_MEM_MESSAGE 101 +#define IDE_MEM_SPIN_EDIT 110 +#define IDC_MEM_SPIN 111 +#define IDT_MEM_GB 112 diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MenuPage.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MenuPage.cpp new file mode 100644 index 00000000..61dd8cb0 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MenuPage.cpp @@ -0,0 +1,439 @@ +// MenuPage.cpp + +#include "StdAfx.h" + +#include "../Common/ZipRegistry.h" + +#include "../../../Windows/DLL.h" +#include "../../../Windows/ErrorMsg.h" +#include "../../../Windows/FileFind.h" + +#include "../Explorer/ContextMenuFlags.h" +#include "../Explorer/RegistryContextMenu.h" +#include "../Explorer/resource.h" + +#include "../FileManager/PropertyNameRes.h" + +#include "../GUI/ExtractDialogRes.h" + +#include "FormatUtils.h" +#include "HelpUtils.h" +#include "LangUtils.h" +#include "MenuPage.h" +#include "MenuPageRes.h" + + +using namespace NWindows; +using namespace NContextMenuFlags; + +#ifdef Z7_LANG +static const UInt32 kLangIDs[] = +{ + IDX_SYSTEM_INTEGRATE_TO_MENU, + IDX_SYSTEM_CASCADED_MENU, + IDX_SYSTEM_ICON_IN_MENU, + IDX_EXTRACT_ELIM_DUP, + IDT_SYSTEM_ZONE, + IDT_SYSTEM_CONTEXT_MENU_ITEMS +}; +#endif + +#define kMenuTopic "fm/options.htm#sevenZip" + +struct CContextMenuItem +{ + unsigned ControlID; + UInt32 Flag; +}; + +static const CContextMenuItem kMenuItems[] = +{ + { IDS_CONTEXT_OPEN, kOpen }, + { IDS_CONTEXT_OPEN, kOpenAs }, + { IDS_CONTEXT_EXTRACT, kExtract }, + { IDS_CONTEXT_EXTRACT_HERE, kExtractHere }, + { IDS_CONTEXT_EXTRACT_TO, kExtractTo }, + + { IDS_CONTEXT_TEST, kTest }, + + { IDS_CONTEXT_COMPRESS, kCompress }, + { IDS_CONTEXT_COMPRESS_TO, kCompressTo7z }, + { IDS_CONTEXT_COMPRESS_TO, kCompressToZip }, + + #ifndef UNDER_CE + { IDS_CONTEXT_COMPRESS_EMAIL, kCompressEmail }, + { IDS_CONTEXT_COMPRESS_TO_EMAIL, kCompressTo7zEmail }, + { IDS_CONTEXT_COMPRESS_TO_EMAIL, kCompressToZipEmail }, + #endif + + { IDS_PROP_CHECKSUM, kCRC }, + { IDS_PROP_CHECKSUM, kCRC_Cascaded }, +}; + + +#if !defined(_WIN64) +extern bool g_Is_Wow64; +#endif + +#ifndef KEY_WOW64_64KEY + #define KEY_WOW64_64KEY (0x0100) +#endif + +#ifndef KEY_WOW64_32KEY + #define KEY_WOW64_32KEY (0x0200) +#endif + + +static void LoadLang_Spec(UString &s, UInt32 id, const char *eng) +{ + LangString(id, s); + if (s.IsEmpty()) + s = eng; + s.RemoveChar(L'&'); +} + + +bool CMenuPage::OnInit() +{ + _initMode = true; + + Clear_MenuChanged(); + +#ifdef Z7_LANG + LangSetDlgItems(*this, kLangIDs, Z7_ARRAY_SIZE(kLangIDs)); +#endif + + #ifdef UNDER_CE + + HideItem(IDX_SYSTEM_INTEGRATE_TO_MENU); + HideItem(IDX_SYSTEM_INTEGRATE_TO_MENU_2); + + #else + + { + UString s; + { + CWindow window(GetItem(IDX_SYSTEM_INTEGRATE_TO_MENU)); + window.GetText(s); + } + UString bit64 = LangString(IDS_PROP_BIT64); + if (bit64.IsEmpty()) + bit64 = "64-bit"; + #ifdef _WIN64 + bit64.Replace(L"64", L"32"); + #endif + s.Add_Space(); + s.Add_Char('('); + s += bit64; + s.Add_Char(')'); + SetItemText(IDX_SYSTEM_INTEGRATE_TO_MENU_2, s); + } + + const FString prefix = NDLL::GetModuleDirPrefix(); + + _dlls[0].ctrl = IDX_SYSTEM_INTEGRATE_TO_MENU; + _dlls[1].ctrl = IDX_SYSTEM_INTEGRATE_TO_MENU_2; + + _dlls[0].wow = 0; + _dlls[1].wow = + #ifdef _WIN64 + KEY_WOW64_32KEY + #else + KEY_WOW64_64KEY + #endif + ; + + for (unsigned d = 0; d < 2; d++) + { + CShellDll &dll = _dlls[d]; + + dll.wasChanged = false; + + #ifndef _WIN64 + if (d != 0 && !g_Is_Wow64) + { + HideItem(dll.ctrl); + continue; + } + #endif + + FString &path = dll.Path; + path = prefix; + path += (d == 0 ? "7-zip.dll" : + #ifdef _WIN64 + "7-zip32.dll" + #else + "7-zip64.dll" + #endif + ); + + + if (!NFile::NFind::DoesFileExist_Raw(path)) + { + path.Empty(); + EnableItem(dll.ctrl, false); + } + else + { + dll.prevValue = CheckContextMenuHandler(fs2us(path), dll.wow); + CheckButton(dll.ctrl, dll.prevValue); + } + } + + #endif + + + CContextMenuInfo ci; + ci.Load(); + + CheckButton(IDX_SYSTEM_CASCADED_MENU, ci.Cascaded.Val); + CheckButton(IDX_SYSTEM_ICON_IN_MENU, ci.MenuIcons.Val); + CheckButton(IDX_EXTRACT_ELIM_DUP, ci.ElimDup.Val); + + _listView.Attach(GetItem(IDL_SYSTEM_OPTIONS)); + _zoneCombo.Attach(GetItem(IDC_SYSTEM_ZONE)); + + { + unsigned wz = ci.WriteZone; + if (wz == (UInt32)(Int32)-1) + wz = 0; + for (unsigned i = 0; i <= 3; i++) + { + unsigned val = i; + UString s; + if (i == 3) + { + if (wz < 3) + break; + val = wz; + } + else + { + #define MY_IDYES 406 + #define MY_IDNO 407 + if (i == 0) + LoadLang_Spec(s, MY_IDNO, "No"); + else if (i == 1) + LoadLang_Spec(s, MY_IDYES, "Yes"); + else + LangString(IDT_ZONE_FOR_OFFICE, s); + } + if (s.IsEmpty()) + s.Add_UInt32(val); + if (i == 0) + s.Insert(0, L"* "); + const int index = (int)_zoneCombo.AddString_SetItemData(s, (LPARAM)val); + if (val == wz) + _zoneCombo.SetCurSel(index); + } + } + + + const UInt32 newFlags = LVS_EX_CHECKBOXES | LVS_EX_FULLROWSELECT; + _listView.SetExtendedListViewStyle(newFlags, newFlags); + + _listView.InsertColumn(0, L"", 200); + + for (unsigned i = 0; i < Z7_ARRAY_SIZE(kMenuItems); i++) + { + const CContextMenuItem &menuItem = kMenuItems[i]; + + UString s = LangString(menuItem.ControlID); + if (menuItem.Flag == kCRC) + s = "CRC SHA"; + else if (menuItem.Flag == kCRC_Cascaded) + s = "7-Zip > CRC SHA"; + if (menuItem.Flag == kOpenAs + || menuItem.Flag == kCRC + || menuItem.Flag == kCRC_Cascaded) + s += " >"; + + switch (menuItem.ControlID) + { + case IDS_CONTEXT_EXTRACT_TO: + { + s = MyFormatNew(s, LangString(IDS_CONTEXT_FOLDER)); + break; + } + case IDS_CONTEXT_COMPRESS_TO: + case IDS_CONTEXT_COMPRESS_TO_EMAIL: + { + UString s2 = LangString(IDS_CONTEXT_ARCHIVE); + switch (menuItem.Flag) + { + case kCompressTo7z: + case kCompressTo7zEmail: + s2 += (".7z"); + break; + case kCompressToZip: + case kCompressToZipEmail: + s2 += (".zip"); + break; + } + s = MyFormatNew(s, s2); + break; + } + } + + const int itemIndex = _listView.InsertItem(i, s); + _listView.SetCheckState((unsigned)itemIndex, ((ci.Flags & menuItem.Flag) != 0)); + } + + _listView.SetColumnWidthAuto(0); + _initMode = false; + + return CPropertyPage::OnInit(); +} + + +#ifndef UNDER_CE + +static void ShowMenuErrorMessage(const wchar_t *m, HWND hwnd) +{ + MessageBoxW(hwnd, m, L"7-Zip", MB_ICONERROR); +} + +#endif + + +LONG CMenuPage::OnApply() +{ + #ifndef UNDER_CE + + for (unsigned d = 2; d != 0;) + { + d--; + CShellDll &dll = _dlls[d]; + if (dll.wasChanged && !dll.Path.IsEmpty()) + { + const bool newVal = IsButtonCheckedBool(dll.ctrl); + const LONG res = SetContextMenuHandler(newVal, fs2us(dll.Path), dll.wow); + if (res != ERROR_SUCCESS && (dll.prevValue != newVal || newVal)) + ShowMenuErrorMessage(NError::MyFormatMessage(res), *this); + dll.prevValue = CheckContextMenuHandler(fs2us(dll.Path), dll.wow); + CheckButton(dll.ctrl, dll.prevValue); + dll.wasChanged = false; + } + } + + #endif + + if (_cascaded_Changed + || _menuIcons_Changed + || _elimDup_Changed + || _writeZone_Changed + || _flags_Changed) + { + CContextMenuInfo ci; + ci.Cascaded.Val = IsButtonCheckedBool(IDX_SYSTEM_CASCADED_MENU); + ci.Cascaded.Def = _cascaded_Changed; + + ci.MenuIcons.Val = IsButtonCheckedBool(IDX_SYSTEM_ICON_IN_MENU); + ci.MenuIcons.Def = _menuIcons_Changed; + + ci.ElimDup.Val = IsButtonCheckedBool(IDX_EXTRACT_ELIM_DUP); + ci.ElimDup.Def = _elimDup_Changed; + + { + int zoneIndex = (int)_zoneCombo.GetItemData_of_CurSel(); + if (zoneIndex <= 0) + zoneIndex = -1; + ci.WriteZone = (UInt32)(Int32)zoneIndex; + } + + ci.Flags = 0; + + for (unsigned i = 0; i < Z7_ARRAY_SIZE(kMenuItems); i++) + if (_listView.GetCheckState(i)) + ci.Flags |= kMenuItems[i].Flag; + + ci.Flags_Def = _flags_Changed; + ci.Save(); + + Clear_MenuChanged(); + } + + // UnChanged(); + + return PSNRET_NOERROR; +} + +void CMenuPage::OnNotifyHelp() +{ + ShowHelpWindow(kMenuTopic); +} + +bool CMenuPage::OnButtonClicked(unsigned buttonID, HWND buttonHWND) +{ + switch (buttonID) + { + #ifndef UNDER_CE + case IDX_SYSTEM_INTEGRATE_TO_MENU: + case IDX_SYSTEM_INTEGRATE_TO_MENU_2: + { + for (unsigned d = 0; d < 2; d++) + { + CShellDll &dll = _dlls[d]; + if (buttonID == dll.ctrl && !dll.Path.IsEmpty()) + dll.wasChanged = true; + } + break; + } + #endif + + case IDX_SYSTEM_CASCADED_MENU: _cascaded_Changed = true; break; + case IDX_SYSTEM_ICON_IN_MENU: _menuIcons_Changed = true; break; + case IDX_EXTRACT_ELIM_DUP: _elimDup_Changed = true; break; + // case IDX_EXTRACT_WRITE_ZONE: _writeZone_Changed = true; break; + + default: + return CPropertyPage::OnButtonClicked(buttonID, buttonHWND); + } + + Changed(); + return true; +} + + +bool CMenuPage::OnCommand(unsigned code, unsigned itemID, LPARAM param) +{ + if (code == CBN_SELCHANGE && itemID == IDC_SYSTEM_ZONE) + { + _writeZone_Changed = true; + Changed(); + return true; + } + return CPropertyPage::OnCommand(code, itemID, param); +} + + +bool CMenuPage::OnNotify(UINT controlID, LPNMHDR lParam) +{ + if (lParam->hwndFrom == HWND(_listView)) + { + switch (lParam->code) + { + case (LVN_ITEMCHANGED): + return OnItemChanged((const NMLISTVIEW *)lParam); + } + } + return CPropertyPage::OnNotify(controlID, lParam); +} + + +bool CMenuPage::OnItemChanged(const NMLISTVIEW *info) +{ + if (_initMode) + return true; + if ((info->uChanged & LVIF_STATE) != 0) + { + UINT oldState = info->uOldState & LVIS_STATEIMAGEMASK; + UINT newState = info->uNewState & LVIS_STATEIMAGEMASK; + if (oldState != newState) + { + _flags_Changed = true; + Changed(); + } + } + return true; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MenuPage.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MenuPage.h new file mode 100644 index 00000000..3b62a8bd --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MenuPage.h @@ -0,0 +1,57 @@ +// MenuPage.h + +#ifndef ZIP7_INC_MENU_PAGE_H +#define ZIP7_INC_MENU_PAGE_H + +#include "../../../Windows/Control/PropertyPage.h" +#include "../../../Windows/Control/ComboBox.h" +#include "../../../Windows/Control/ListView.h" + +struct CShellDll +{ + FString Path; + bool wasChanged; + bool prevValue; + unsigned ctrl; + UInt32 wow; + + CShellDll(): wasChanged (false), prevValue(false), ctrl(0), wow(0) {} +}; + +class CMenuPage: public NWindows::NControl::CPropertyPage +{ + bool _initMode; + + bool _cascaded_Changed; + bool _menuIcons_Changed; + bool _elimDup_Changed; + bool _writeZone_Changed; + bool _flags_Changed; + + void Clear_MenuChanged() + { + _cascaded_Changed = false; + _menuIcons_Changed = false; + _elimDup_Changed = false; + _writeZone_Changed = false; + _flags_Changed = false; + } + + #ifndef UNDER_CE + CShellDll _dlls[2]; + #endif + + NWindows::NControl::CListView _listView; + NWindows::NControl::CComboBox _zoneCombo; + + virtual bool OnInit() Z7_override; + virtual void OnNotifyHelp() Z7_override; + virtual bool OnNotify(UINT controlID, LPNMHDR lParam) Z7_override; + virtual LONG OnApply() Z7_override; + virtual bool OnButtonClicked(unsigned buttonID, HWND buttonHWND) Z7_override; + virtual bool OnCommand(unsigned code, unsigned itemID, LPARAM param) Z7_override; + + bool OnItemChanged(const NMLISTVIEW* info); +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MenuPage.rc b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MenuPage.rc new file mode 100644 index 00000000..66162f64 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MenuPage.rc @@ -0,0 +1,24 @@ +#include "MenuPageRes.h" +#include "../../GuiCommon.rc" + +#define xc OPTIONS_PAGE_XC_SIZE +#define yc OPTIONS_PAGE_YC_SIZE + +IDD_MENU MY_PAGE +#include "MenuPage2.rc" + +#ifdef UNDER_CE + +#undef m +#undef xc +#undef yc + +#define m 4 +#define xc (SMALL_PAGE_SIZE_X + 8) + +#define yc 112 + +IDD_MENU_2 MY_PAGE +#include "MenuPage2.rc" + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MenuPage2.rc b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MenuPage2.rc new file mode 100644 index 00000000..df0cc545 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MenuPage2.rc @@ -0,0 +1,28 @@ +#include "../GUI/ExtractDialogRes.h" + +#define y 96 + +#define zoneX 100 + +CAPTION "7-Zip" +BEGIN + MY_CONTROL_CHECKBOX ( "Integrate 7-Zip to shell context menu", IDX_SYSTEM_INTEGRATE_TO_MENU, m, m, xc) + MY_CONTROL_CHECKBOX ( "(32-bit)", IDX_SYSTEM_INTEGRATE_TO_MENU_2, m, m + 14, xc) + MY_CONTROL_CHECKBOX ( "Cascaded context menu", IDX_SYSTEM_CASCADED_MENU, m, m + 28, xc) + MY_CONTROL_CHECKBOX ( "Icons in context menu", IDX_SYSTEM_ICON_IN_MENU, m, m + 42, xc) + MY_CONTROL_CHECKBOX ( "Eliminate duplication of root folder", IDX_EXTRACT_ELIM_DUP, m, m + 56, xc) + + LTEXT "Propagate Zone.Id stream:", IDT_SYSTEM_ZONE, m, m + 70, xc - zoneX, 8 + COMBOBOX IDC_SYSTEM_ZONE, m + xc - zoneX, m + 70 - 2, zoneX, 50, MY_COMBO + + LTEXT "Context menu items:", IDT_SYSTEM_CONTEXT_MENU_ITEMS, m, m + 84, xc, 8 + CONTROL "List", IDL_SYSTEM_OPTIONS, "SysListView32", + LVS_REPORT | LVS_SINGLESEL | LVS_NOCOLUMNHEADER | WS_BORDER | WS_TABSTOP, + m, m + y, xc, yc - y +END + + +STRINGTABLE +BEGIN + IDT_ZONE_FOR_OFFICE "For Office files" +END diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MenuPageRes.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MenuPageRes.h new file mode 100644 index 00000000..e2cf7985 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MenuPageRes.h @@ -0,0 +1,15 @@ +#define IDD_MENU 2300 +#define IDD_MENU_2 12300 + +#define IDX_SYSTEM_INTEGRATE_TO_MENU 2301 +#define IDX_SYSTEM_CASCADED_MENU 2302 +#define IDT_SYSTEM_CONTEXT_MENU_ITEMS 2303 +#define IDX_SYSTEM_ICON_IN_MENU 2304 + +#define IDX_SYSTEM_INTEGRATE_TO_MENU_2 2310 + +#define IDT_SYSTEM_ZONE 3440 +#define IDT_ZONE_FOR_OFFICE 3441 + +#define IDL_SYSTEM_OPTIONS 100 +#define IDC_SYSTEM_ZONE 101 diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MessagesDialog.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MessagesDialog.cpp new file mode 100644 index 00000000..57e56bcf --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MessagesDialog.cpp @@ -0,0 +1,76 @@ +// MessagesDialog.cpp + +#include "StdAfx.h" + +#include "../../../Common/IntToString.h" + +#include "../../../Windows/ResourceString.h" + +#include "MessagesDialog.h" + +#include "LangUtils.h" + +#include "ProgressDialog2Res.h" + +using namespace NWindows; + +void CMessagesDialog::AddMessageDirect(LPCWSTR message) +{ + const unsigned i = (unsigned)_messageList.GetItemCount(); + wchar_t sz[16]; + ConvertUInt32ToString(i, sz); + _messageList.InsertItem(i, sz); + _messageList.SetSubItem(i, 1, message); +} + +void CMessagesDialog::AddMessage(LPCWSTR message) +{ + UString s = message; + while (!s.IsEmpty()) + { + const int pos = s.Find(L'\n'); + if (pos < 0) + break; + AddMessageDirect(s.Left(pos)); + s.DeleteFrontal((unsigned)pos + 1); + } + AddMessageDirect(s); +} + +bool CMessagesDialog::OnInit() +{ + #ifdef Z7_LANG + LangSetWindowText(*this, IDD_MESSAGES); + LangSetDlgItems(*this, NULL, 0); + SetItemText(IDOK, LangString(IDS_CLOSE)); + #endif + _messageList.Attach(GetItem(IDL_MESSAGE)); + _messageList.SetUnicodeFormat(); + + _messageList.InsertColumn(0, L"", 30); + _messageList.InsertColumn(1, LangString(IDS_MESSAGE), 600); + + FOR_VECTOR (i, *Messages) + AddMessage((*Messages)[i]); + + _messageList.SetColumnWidthAuto(0); + _messageList.SetColumnWidthAuto(1); + NormalizeSize(); + return CModalDialog::OnInit(); +} + +bool CMessagesDialog::OnSize(WPARAM /* wParam */, int xSize, int ySize) +{ + int mx, my; + GetMargins(8, mx, my); + int bx, by; + GetItemSizes(IDOK, bx, by); + int y = ySize - my - by; + int x = xSize - mx - bx; + + InvalidateRect(NULL); + + MoveItem(IDOK, x, y, bx, by); + _messageList.Move(mx, my, xSize - mx * 2, y - my * 2); + return false; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MessagesDialog.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MessagesDialog.h new file mode 100644 index 00000000..40b0379b --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MessagesDialog.h @@ -0,0 +1,25 @@ +// MessagesDialog.h + +#ifndef ZIP7_INC_MESSAGES_DIALOG_H +#define ZIP7_INC_MESSAGES_DIALOG_H + +#include "../../../Windows/Control/Dialog.h" +#include "../../../Windows/Control/ListView.h" + +#include "MessagesDialogRes.h" + +class CMessagesDialog: public NWindows::NControl::CModalDialog +{ + NWindows::NControl::CListView _messageList; + + void AddMessageDirect(LPCWSTR message); + void AddMessage(LPCWSTR message); + virtual bool OnInit() Z7_override; + virtual bool OnSize(WPARAM wParam, int xSize, int ySize) Z7_override; +public: + const UStringVector *Messages; + + INT_PTR Create(HWND parent = NULL) { return CModalDialog::Create(IDD_MESSAGES, parent); } +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MessagesDialog.rc b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MessagesDialog.rc new file mode 100644 index 00000000..49b73e84 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MessagesDialog.rc @@ -0,0 +1,14 @@ +#include "MessagesDialogRes.h" +#include "../../GuiCommon.rc" + +#define xc 440 +#define yc 160 + +IDD_MESSAGES DIALOG 0, 0, xs, ys MY_MODAL_RESIZE_DIALOG_STYLE MY_FONT +CAPTION "7-Zip: Diagnostic messages" +{ + DEFPUSHBUTTON "&Close", IDOK, bx, by, bxs, bys + CONTROL "List1", IDL_MESSAGE, "SysListView32", + LVS_REPORT | LVS_SHOWSELALWAYS | LVS_NOSORTHEADER | WS_BORDER | WS_TABSTOP, + m, m, xc, yc - bys - m +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MessagesDialogRes.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MessagesDialogRes.h new file mode 100644 index 00000000..c8fffff6 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MessagesDialogRes.h @@ -0,0 +1,3 @@ +#define IDD_MESSAGES 6602 +#define IDS_MESSAGE 6603 +#define IDL_MESSAGE 100 diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/Move.bmp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/Move.bmp new file mode 100644 index 00000000..eb5f20f9 Binary files /dev/null and b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/Move.bmp differ diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/Move2.bmp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/Move2.bmp new file mode 100644 index 00000000..58679eff Binary files /dev/null and b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/Move2.bmp differ diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MyCom2.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MyCom2.h new file mode 100644 index 00000000..a0cbd453 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MyCom2.h @@ -0,0 +1,46 @@ +// MyCom2.h + +#ifndef ZIP7_INC_MYCOM2_H +#define ZIP7_INC_MYCOM2_H + +#include "../../../Common/MyCom.h" + +#define Z7_COM_UNKNOWN_IMP_SPEC_MT2(i1, i) \ + Z7_COM_QI_BEGIN \ + Z7_COM_QI_ENTRY_UNKNOWN(i1) \ + i \ + Z7_COM_QI_END_MT \ + Z7_COM_ADDREF_RELEASE_MT + + +#define Z7_COM_UNKNOWN_IMP_1_MT(i) \ + Z7_COM_UNKNOWN_IMP_SPEC_MT2( \ + i, \ + Z7_COM_QI_ENTRY(i) \ + ) + +#define Z7_COM_UNKNOWN_IMP_2_MT(i1, i2) \ + Z7_COM_UNKNOWN_IMP_SPEC_MT2( \ + i1, \ + Z7_COM_QI_ENTRY(i1) \ + Z7_COM_QI_ENTRY(i2) \ + ) + +#define Z7_COM_UNKNOWN_IMP_3_MT(i1, i2, i3) \ + Z7_COM_UNKNOWN_IMP_SPEC_MT2( \ + i1, \ + Z7_COM_QI_ENTRY(i1) \ + Z7_COM_QI_ENTRY(i2) \ + Z7_COM_QI_ENTRY(i3) \ + ) + +#define Z7_COM_UNKNOWN_IMP_4_MT(i1, i2, i3, i4) \ + Z7_COM_UNKNOWN_IMP_SPEC_MT2( \ + i1, \ + Z7_COM_QI_ENTRY(i1) \ + Z7_COM_QI_ENTRY(i2) \ + Z7_COM_QI_ENTRY(i3) \ + Z7_COM_QI_ENTRY(i4) \ + ) + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MyLoadMenu.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MyLoadMenu.cpp new file mode 100644 index 00000000..f190929f --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MyLoadMenu.cpp @@ -0,0 +1,964 @@ +// MyLoadMenu.cpp + +#include "StdAfx.h" + +#include "../../../Windows/FileDir.h" +#include "../../../Windows/Menu.h" +#include "../../../Windows/TimeUtils.h" +#include "../../../Windows/Control/Dialog.h" + +#include "../../PropID.h" + +#include "../Common/CompressCall.h" + +#include "AboutDialog.h" +#include "App.h" +#include "BrowseDialog2.h" +#include "HelpUtils.h" +#include "LangUtils.h" +#include "MyLoadMenu.h" +#include "RegistryUtils.h" + +#include "PropertyNameRes.h" +#include "resource.h" + +using namespace NWindows; + +static const UINT k_MenuID_OpenBookmark = 830; +static const UINT k_MenuID_SetBookmark = 810; +static const UINT k_MenuID_TimePopup = IDM_VIEW_TIME_POPUP; +static const UINT k_MenuID_Time = IDM_VIEW_TIME; + +#if 0 +// static const UINT k_MenuID_Bookmark_Temp = 850; +#endif + +extern HINSTANCE g_hInstance; + +#define kFMHelpTopic "FM/index.htm" + +extern void OptionsDialog(HWND hwndOwner, HINSTANCE hInstance); + +enum +{ + k_MenuIndex_File = 0, + k_MenuIndex_Edit, + k_MenuIndex_View, + k_MenuIndex_Bookmarks +}; + +#ifdef Z7_LANG +static const UInt32 k_LangID_TopMenuItems[] = +{ + IDM_FILE, + IDM_EDIT, + IDM_VIEW, + IDM_FAVORITES, + IDM_TOOLS, + IDM_HELP +}; + +static const UInt32 k_LangID_Toolbars = IDM_VIEW_TOOLBARS; +static const UInt32 k_LangID_AddToFavorites = IDM_ADD_TO_FAVORITES; + +static const CIDLangPair kIDLangPairs[] = +{ + { IDCLOSE, 557 }, // IDM_EXIT + { IDM_VIEW_ARANGE_BY_NAME, IDS_PROP_NAME }, + { IDM_VIEW_ARANGE_BY_TYPE, IDS_PROP_FILE_TYPE }, + { IDM_VIEW_ARANGE_BY_DATE, IDS_PROP_MTIME }, + { IDM_VIEW_ARANGE_BY_SIZE, IDS_PROP_SIZE } +}; + +static int FindLangItem(unsigned controlID) +{ + for (unsigned i = 0; i < Z7_ARRAY_SIZE(kIDLangPairs); i++) + if (kIDLangPairs[i].ControlID == controlID) + return (int)i; + return -1; +} +#endif + +static unsigned GetSortControlID(PROPID propID) +{ + switch (propID) + { + case kpidName: return IDM_VIEW_ARANGE_BY_NAME; + case kpidExtension: return IDM_VIEW_ARANGE_BY_TYPE; + case kpidMTime: return IDM_VIEW_ARANGE_BY_DATE; + case kpidSize: return IDM_VIEW_ARANGE_BY_SIZE; + case kpidNoProperty: return IDM_VIEW_ARANGE_NO_SORT; + } + return IDM_VIEW_ARANGE_BY_NAME; + // IDM_VIEW_ARANGE_NO_SORT; + // return -1; +} + +/* +#if _MSC_VER > 1400 +// GetVersion was declared deprecated +#pragma warning(disable : 4996) +#endif + +static bool g_IsNew_fMask = false; +static class CInit_fMask +{ +public: + CInit_fMask() + { + DWORD v = GetVersion(); + v = ((v & 0xff) << 8) | ((v >> 8) & 0xFF); + g_IsNew_fMask = (v > 0x400); // (win98/win2000) or newer + } +} g_Init_fMask; +static UINT Get_fMask_for_String() + { return g_IsNew_fMask ? MIIM_STRING : MIIM_TYPE; } +static UINT Get_fMask_for_FType_and_String() + { return g_IsNew_fMask ? (MIIM_STRING | MIIM_FTYPE) : MIIM_TYPE; } +*/ + +/* +We can use new MIIM_STRING / MIIM_FTYPE flags in the following conditions: + 1) we run at new Windows (win98/win2000) or newer + 2) also we probably must set MENUITEMINFO::cbSize as sizeof of full + (MENUITEMINFO) that was compiled with (WINVER >= 0x0500) +But it's simpler to use old MIIM_TYPE without these complex checks. +*/ + +// /* +static inline UINT Get_fMask_for_String() { return MIIM_TYPE; } +static inline UINT Get_fMask_for_FType_and_String() { return MIIM_TYPE; } +// */ + +static bool Is_MenuItem_TimePopup(const CMenuItem &item) +{ + return item.wID == k_MenuID_TimePopup || + item.StringValue.IsPrefixedBy_Ascii_NoCase("20"); +} + +#ifdef Z7_LANG +static void MyChangeMenu(HMENU menuLoc, unsigned menuID, unsigned level, unsigned menuIndex) +{ + CMenu menu; + menu.Attach(menuLoc); + + for (unsigned i = 0;; i++) + { + CMenuItem item; + /* here we can use + Get_fMask_for_String() or + Get_fMask_for_FType_and_String() + We want to change only String of menu item. + It's not required to change (fType) of menu item. + We can look (fType) to check for SEPARATOR item. + But String of separator is empty and (wID == 0). + So we can check for SEPARATOR without (fType) requesting. + So it's enough to use Get_fMask_for_String() here */ + item.fMask = + Get_fMask_for_String() + // Get_fMask_for_FType_and_String() + | MIIM_SUBMENU | MIIM_ID; + if (!menu.GetItem(i, true, item)) + break; + { + UString newString; + if (item.hSubMenu) + { + /* in win10: + MENU+POPUP: + (wID == item.hSubMenu) + MENUEX+POPUP where ID is not set: + (wID == 0) + MENU+SEPARATOR + (wID == 0) + */ + UInt32 langID = item.wID; + if (langID >= (1 << 16)) + { + // here we try to exclude the case (wID == item.hSubMenu) if (MENU+POPUP) + continue; + } + if (langID == 0) + { + if (level == 0) + { + if (i < Z7_ARRAY_SIZE(k_LangID_TopMenuItems)) + langID = k_LangID_TopMenuItems[i]; + } + else if (level == 1) + { + if (menuID == IDM_FAVORITES || (menuID == 0 && menuIndex == k_MenuIndex_Bookmarks)) + langID = k_LangID_AddToFavorites; + else if (menuID == IDM_VIEW || (menuID == 0 && menuIndex == k_MenuIndex_View)) + { + if (Is_MenuItem_TimePopup(item)) + langID = k_MenuID_TimePopup; + else + langID = k_LangID_Toolbars; + } + } + } + if (langID == k_MenuID_TimePopup) + continue; + if (langID != k_LangID_AddToFavorites) + MyChangeMenu(item.hSubMenu, langID, level + 1, i); + if (langID == 0) + continue; + LangString_OnlyFromLangFile(langID, newString); + if (newString.IsEmpty()) + continue; + } + else + { + if (item.fMask & (MIIM_TYPE | MIIM_FTYPE)) + if (item.IsSeparator()) + continue; + if (item.StringValue.IsEmpty()) + continue; + const int langPos = FindLangItem(item.wID); + // we don't need lang change for CRC items!!! + const UInt32 langID = langPos >= 0 ? kIDLangPairs[langPos].LangID : item.wID; + if (langID == 0) + continue; + + if (langID == IDM_OPEN_INSIDE_ONE || + langID == IDM_OPEN_INSIDE_PARSER) + { + LangString_OnlyFromLangFile(IDM_OPEN_INSIDE, newString); + if (newString.IsEmpty()) + continue; + newString.Replace(L"&", L""); + const int tabPos = newString.Find(L"\t"); + if (tabPos >= 0) + newString.DeleteFrom(tabPos); + newString += (langID == IDM_OPEN_INSIDE_ONE ? " *" : " #"); + } + else if (langID == IDM_BENCHMARK2) + { + LangString_OnlyFromLangFile(IDM_BENCHMARK, newString); + if (newString.IsEmpty()) + continue; + newString.Replace(L"&", L""); + const int tabPos = newString.Find(L"\t"); + if (tabPos >= 0) + newString.DeleteFrom(tabPos); + newString += " 2"; + } + else + { + LangString_OnlyFromLangFile(langID, newString); + } + + if (newString.IsEmpty()) + continue; + + const int tabPos = item.StringValue.ReverseFind(L'\t'); + if (tabPos >= 0) + newString += item.StringValue.Ptr(tabPos); + } + + { + item.StringValue = newString; + // we want to change only String + item.fMask = Get_fMask_for_String(); + menu.SetItem(i, true, item); + } + } + } +} +#endif + +static CMenu g_FileMenu; + +static struct CFileMenuDestroyer +{ + ~CFileMenuDestroyer() { if ((HMENU)g_FileMenu) g_FileMenu.Destroy(); } +} g_FileMenuDestroyer; + + +static void CopyMenu(HMENU srcMenuSpec, HMENU destMenuSpec); + +static void CopyPopMenu_IfRequired(CMenuItem &item) +{ + /* if (item.hSubMenu) is defined + { + - it creates new (popup) menu + - it copies menu items from old item.hSubMenu menu to new (popup) menu + - it sets item.hSubMenu to handle of created (popup) menu + } */ + if (item.hSubMenu) + { + CMenu popup; + popup.CreatePopup(); + CopyMenu(item.hSubMenu, popup); + item.hSubMenu = popup; + } +} + +/* destMenuSpec must be non-NULL handle to created empty popup menu */ +static void CopyMenu(HMENU srcMenuSpec, HMENU destMenuSpec) +{ + CMenu srcMenu; + srcMenu.Attach(srcMenuSpec); + CMenu destMenu; + destMenu.Attach(destMenuSpec); + unsigned startPos = 0; + for (unsigned i = 0;; i++) + { + CMenuItem item; + item.fMask = MIIM_SUBMENU | MIIM_STATE | MIIM_ID | Get_fMask_for_FType_and_String(); + if (!srcMenu.GetItem(i, true, item)) + break; + CopyPopMenu_IfRequired(item); + if (destMenu.InsertItem(startPos, true, item)) + startPos++; + } +} + + +/* use for (needResetMenu): + false : for call from program window creation code + true : for another calls : (from Options language change) +*/ +void MyLoadMenu(bool needResetMenu) +{ + #ifdef UNDER_CE + + const HMENU oldMenu = g_App._commandBar.GetMenu(0); + if (oldMenu) + ::DestroyMenu(oldMenu); + /* BOOL b = */ g_App._commandBar.InsertMenubar(g_hInstance, IDM_MENU, 0); + const HMENU baseMenu = g_App._commandBar.GetMenu(0); + // if (startInit) + // SetIdsForSubMenus(baseMenu, 0, 0); + if (!g_LangID.IsEmpty()) + MyChangeMenu(baseMenu, 0, 0); + g_App._commandBar.DrawMenuBar(0); + + #else // UNDER_CE + + const HWND hWnd = g_HWND; + bool menuWasChanged = false; + /* + We must reload to english default menu for at least two cases: + - if some submenu was changed (File or another submenu can be changed after menu activating). + - for change from non-english lang to another partial non-english lang, + where we still need some english strings. + But we reload menu to default menu everytime except of program starting stage. + That scheme is simpler than complex checks for exact conditions for menu reload. + */ + if (needResetMenu) + { + const HMENU oldMenu = ::GetMenu(hWnd); + const HMENU newMenu = ::LoadMenu(g_hInstance, MAKEINTRESOURCE(IDM_MENU)); + // docs for SetMenu(): the window is redrawn to reflect the menu change. + if (newMenu && ::SetMenu(hWnd, newMenu)) + ::DestroyMenu(oldMenu); + menuWasChanged = true; + } + const HMENU baseMenu = ::GetMenu(hWnd); + // if (startInit) + // SetIdsForSubMenus(baseMenu, 0, 0); + #ifdef Z7_LANG + if (!g_Lang.IsEmpty()) // !g_LangID.IsEmpty() && + { + MyChangeMenu(baseMenu, 0, 0, 0); + menuWasChanged = true; + } + #endif + + if (menuWasChanged) + ::DrawMenuBar(hWnd); + + #endif // UNDER_CE + + // menuWasChanged = false; // for debug + if (menuWasChanged || !(HMENU)g_FileMenu) + { + if ((HMENU)g_FileMenu) + g_FileMenu.Destroy(); + g_FileMenu.CreatePopup(); + CopyMenu(::GetSubMenu(baseMenu, k_MenuIndex_File), g_FileMenu); + } +} + +void OnMenuActivating(HWND /* hWnd */, HMENU hMenu, int position) +{ + HMENU mainMenu = + #ifdef UNDER_CE + g_App._commandBar.GetMenu(0); + #else + ::GetMenu(g_HWND) + #endif + ; + + if (::GetSubMenu(mainMenu, position) != hMenu) + return; + + if (position == k_MenuIndex_File) + { + CMenu menu; + menu.Attach(hMenu); + menu.RemoveAllItems(); + g_App.GetFocusedPanel().CreateFileMenu(hMenu); + } + else if (position == k_MenuIndex_Edit) + { + /* + CMenu menu; + menu.Attach(hMenu); + menu.EnableItem(IDM_EDIT_CUT, MF_ENABLED); + menu.EnableItem(IDM_EDIT_COPY, MF_ENABLED); + menu.EnableItem(IDM_EDIT_PASTE, IsClipboardFormatAvailableHDROP() ? MF_ENABLED : MF_GRAYED); + */ + } + else if (position == k_MenuIndex_View) + { + // View; + CMenu menu; + menu.Attach(hMenu); + menu.CheckRadioItem( + IDM_VIEW_LARGE_ICONS, IDM_VIEW_DETAILS, + IDM_VIEW_LARGE_ICONS + g_App.GetListViewMode(), MF_BYCOMMAND); + + menu.CheckRadioItem( + IDM_VIEW_ARANGE_BY_NAME, + IDM_VIEW_ARANGE_NO_SORT, + GetSortControlID(g_App.GetSortID()), + MF_BYCOMMAND); + + menu.CheckItemByID(IDM_VIEW_TWO_PANELS, g_App.NumPanels == 2); + menu.CheckItemByID(IDM_VIEW_FLAT_VIEW, g_App.GetFlatMode()); + menu.CheckItemByID(IDM_VIEW_ARCHIVE_TOOLBAR, g_App.ShowArchiveToolbar); + menu.CheckItemByID(IDM_VIEW_STANDARD_TOOLBAR, g_App.ShowStandardToolbar); + menu.CheckItemByID(IDM_VIEW_TOOLBARS_LARGE_BUTTONS, g_App.LargeButtons); + menu.CheckItemByID(IDM_VIEW_TOOLBARS_SHOW_BUTTONS_TEXT, g_App.ShowButtonsLables); + menu.CheckItemByID(IDM_VIEW_AUTO_REFRESH, g_App.Get_AutoRefresh_Mode()); + // menu.CheckItemByID(IDM_VIEW_SHOW_STREAMS, g_App.Get_ShowNtfsStrems_Mode()); + // menu.CheckItemByID(IDM_VIEW_SHOW_DELETED, g_App.ShowDeletedFiles); + + for (unsigned i = 0;; i++) + { + CMenuItem item; + item.fMask = Get_fMask_for_String() | MIIM_SUBMENU | MIIM_ID; + item.fType = MFT_STRING; + if (!menu.GetItem(i, true, item)) + break; + if (item.hSubMenu && Is_MenuItem_TimePopup(item)) + { + FILETIME ft; + NTime::GetCurUtcFileTime(ft); + + { + wchar_t s[64]; + s[0] = 0; + if (ConvertUtcFileTimeToString(ft, s, kTimestampPrintLevel_DAY)) + item.StringValue = s; + } + + item.fMask = Get_fMask_for_String() | MIIM_ID; + item.fType = MFT_STRING; + item.wID = k_MenuID_TimePopup; + menu.SetItem(i, true, item); + + CMenu subMenu; + subMenu.Attach(menu.GetSubMenu((int)i)); + subMenu.RemoveAllItems(); + + const int k_TimeLevels[] = + { + kTimestampPrintLevel_DAY, + kTimestampPrintLevel_MIN, + kTimestampPrintLevel_SEC, + // 1,2,3,4,5,6, + kTimestampPrintLevel_NTFS, + kTimestampPrintLevel_NS + }; + + unsigned last = k_MenuID_Time; + unsigned selectedCommand = 0; + g_App._timestampLevels.Clear(); + unsigned id = k_MenuID_Time; + + for (unsigned k = 0; k < Z7_ARRAY_SIZE(k_TimeLevels); k++) + { + wchar_t s[64]; + s[0] = 0; + const int timestampLevel = k_TimeLevels[k]; + if (ConvertUtcFileTimeToString(ft, s, timestampLevel)) + { + if (subMenu.AppendItem(MF_STRING, id, s)) + { + last = id; + g_App._timestampLevels.Add(timestampLevel); + if (g_App.GetTimestampLevel() == timestampLevel) + selectedCommand = id; + id++; + } + } + } + if (selectedCommand != 0) + menu.CheckRadioItem(k_MenuID_Time, last, selectedCommand, MF_BYCOMMAND); + + if (subMenu.AppendItem(MF_STRING, IDM_VIEW_TIME_UTC, L"UTC")) + subMenu.CheckItemByID(IDM_VIEW_TIME_UTC, g_Timestamp_Show_UTC); + } + } + } + else if (position == k_MenuIndex_Bookmarks) + { + CMenu menu; + menu.Attach(hMenu); + + CMenu subMenu; + subMenu.Attach(menu.GetSubMenu(0)); + subMenu.RemoveAllItems(); + unsigned i; + + for (i = 0; i < 10; i++) + { + UString s = LangString(IDS_BOOKMARK); + s.Add_Space(); + const char c = (char)(L'0' + i); + s.Add_Char(c); + s += "\tAlt+Shift+"; + s.Add_Char(c); + subMenu.AppendItem(MF_STRING, k_MenuID_SetBookmark + i, s); + } + + menu.RemoveAllItemsFrom(2); + + for (i = 0; i < 10; i++) + { + UString s = g_App.AppState.FastFolders.GetString(i); + const int kMaxSize = 100; + const int kFirstPartSize = kMaxSize / 2; + if (s.Len() > kMaxSize) + { + s.Delete(kFirstPartSize, s.Len() - kMaxSize); + s.Insert(kFirstPartSize, L" ... "); + } + if (s.IsEmpty()) + s = '-'; + s += "\tAlt+"; + s.Add_Char((char)('0' + i)); + menu.AppendItem(MF_STRING, k_MenuID_OpenBookmark + i, s); + } +#if 0 + { + FString tempPathF; + if (NFile::NDir::MyGetTempPath(tempPathF)) + { + menu.AppendItem(MF_SEPARATOR, 0, (LPCTSTR)NULL); + UString s; + s = "Temp : "; + s += fs2us(tempPathF); + menu.AppendItem(MF_STRING, k_MenuID_Bookmark_Temp, s); + } + } +#endif + } +} + +/* +It doesn't help +void OnMenuUnActivating(HWND hWnd, HMENU hMenu, int id) +{ + if (::GetSubMenu(::GetMenu(g_HWND), 0) != hMenu) + return; +} +*/ + +static const unsigned g_Zvc_IDs[] = +{ + IDM_VER_EDIT, + IDM_VER_COMMIT, + IDM_VER_REVERT, + IDM_VER_DIFF +}; + +static const char * const g_Zvc_Strings[] = +{ + "Ver Edit (&1)" + , "Ver Commit" + , "Ver Revert" + , "Ver Diff (&0)" +}; + +void CFileMenu::Load(HMENU hMenu, unsigned startPos) +{ + CMenu destMenu; + destMenu.Attach(hMenu); + + UString diffPath; + ReadRegDiff(diffPath); + + unsigned numRealItems = startPos; + + const bool isBigScreen = NControl::IsDialogSizeOK(40, 200, g_HWND); + + for (unsigned i = 0;; i++) + { + CMenuItem item; + + item.fMask = MIIM_SUBMENU | MIIM_STATE | MIIM_ID | Get_fMask_for_FType_and_String(); + item.fType = MFT_STRING; + + if (!g_FileMenu.GetItem(i, true, item)) + break; + + { + if (!programMenu && item.wID == IDCLOSE) + continue; + + if (item.wID == IDM_DIFF && diffPath.IsEmpty()) + continue; + + if (item.wID == IDM_OPEN_INSIDE_ONE || item.wID == IDM_OPEN_INSIDE_PARSER) + { + // We use diff as "super mode" marker for additional commands. + /* + if (diffPath.IsEmpty()) + continue; + */ + } + + if (item.wID == IDM_BENCHMARK2) + { + // We use diff as "super mode" marker for additional commands. + if (diffPath.IsEmpty()) + continue; + } + + bool isOneFsFile = (isFsFolder && numItems == 1 && allAreFiles); + bool disable = (!isOneFsFile && (item.wID == IDM_SPLIT || item.wID == IDM_COMBINE)); + + if (readOnly) + { + switch (item.wID) + { + case IDM_RENAME: + case IDM_MOVE_TO: + case IDM_DELETE: + case IDM_COMMENT: + case IDM_CREATE_FOLDER: + case IDM_CREATE_FILE: + disable = true; + } + } + + if (isHashFolder) + { + switch (item.wID) + { + case IDM_OPEN: + case IDM_OPEN_INSIDE: + case IDM_OPEN_INSIDE_ONE: + case IDM_OPEN_INSIDE_PARSER: + case IDM_OPEN_OUTSIDE: + case IDM_FILE_VIEW: + case IDM_FILE_EDIT: + // case IDM_RENAME: + case IDM_COPY_TO: + case IDM_MOVE_TO: + // case IDM_DELETE: + case IDM_COMMENT: + case IDM_CREATE_FOLDER: + case IDM_CREATE_FILE: + case IDM_LINK: + case IDM_DIFF: + disable = true; + } + } + + + if (item.wID == IDM_LINK && numItems != 1) + disable = true; + + if (item.wID == IDM_ALT_STREAMS) + disable = !isAltStreamsSupported; + + if (!isBigScreen && (disable || item.IsSeparator())) + continue; + + CopyPopMenu_IfRequired(item); + if (destMenu.InsertItem(startPos, true, item)) + { + if (disable) + destMenu.EnableItem(startPos, MF_BYPOSITION | MF_GRAYED); + startPos++; + } + + if (!item.IsSeparator()) + numRealItems = startPos; + } + } + + UString vercPath; + if (!diffPath.IsEmpty() && isFsFolder && allAreFiles && numItems == 1) + ReadReg_VerCtrlPath(vercPath); + + if (!vercPath.IsEmpty()) + { + NFile::NFind::CFileInfo fi; + if (fi.Find(FilePath) && fi.Size < ((UInt32)1 << 31) && !fi.IsDir()) + { + for (unsigned k = 0; k < Z7_ARRAY_SIZE(g_Zvc_IDs); k++) + { + const unsigned id = g_Zvc_IDs[k]; + if (fi.IsReadOnly()) + { + if (id == IDM_VER_COMMIT || + id == IDM_VER_REVERT || + id == IDM_VER_DIFF) + continue; + } + else + { + if (id == IDM_VER_EDIT) + continue; + } + + CMenuItem item; + UString s (g_Zvc_Strings[k]); + if (destMenu.AppendItem(MF_STRING, id, s)) + { + startPos++; + numRealItems = startPos; + } + } + } + } + + destMenu.RemoveAllItemsFrom(numRealItems); +} + +bool ExecuteFileCommand(unsigned id) +{ + if (id >= kMenuCmdID_Plugin_Start) + { + g_App.GetFocusedPanel().InvokePluginCommand(id); + g_App.GetFocusedPanel()._sevenZipContextMenu.Release(); + g_App.GetFocusedPanel()._systemContextMenu.Release(); + return true; + } + + switch (id) + { + // File + case IDM_OPEN: g_App.OpenItem(); break; + + case IDM_OPEN_INSIDE: g_App.OpenItemInside(NULL); break; + case IDM_OPEN_INSIDE_ONE: g_App.OpenItemInside(L"*"); break; + case IDM_OPEN_INSIDE_PARSER: g_App.OpenItemInside(L"#"); break; + + case IDM_OPEN_OUTSIDE: g_App.OpenItemOutside(); break; + case IDM_FILE_VIEW: g_App.EditItem(false); break; + case IDM_FILE_EDIT: g_App.EditItem(true); break; + case IDM_RENAME: g_App.Rename(); break; + case IDM_COPY_TO: g_App.CopyTo(); break; + case IDM_MOVE_TO: g_App.MoveTo(); break; + case IDM_DELETE: g_App.Delete(!IsKeyDown(VK_SHIFT)); break; + + case IDM_HASH_ALL: g_App.CalculateCrc("*"); break; + case IDM_CRC32: g_App.CalculateCrc("CRC32"); break; + case IDM_CRC64: g_App.CalculateCrc("CRC64"); break; + case IDM_XXH64: g_App.CalculateCrc("XXH64"); break; + case IDM_MD5: g_App.CalculateCrc("MD5"); break; + case IDM_SHA1: g_App.CalculateCrc("SHA1"); break; + case IDM_SHA256: g_App.CalculateCrc("SHA256"); break; + case IDM_SHA384: g_App.CalculateCrc("SHA384"); break; + case IDM_SHA512: g_App.CalculateCrc("SHA512"); break; + case IDM_SHA3_256: g_App.CalculateCrc("SHA3-256"); break; + case IDM_BLAKE2SP: g_App.CalculateCrc("BLAKE2sp"); break; + + case IDM_DIFF: g_App.DiffFiles(); break; + + case IDM_VER_EDIT: + case IDM_VER_COMMIT: + case IDM_VER_REVERT: + case IDM_VER_DIFF: + g_App.VerCtrl(id); break; + + case IDM_SPLIT: g_App.Split(); break; + case IDM_COMBINE: g_App.Combine(); break; + case IDM_PROPERTIES: g_App.Properties(); break; + case IDM_COMMENT: g_App.Comment(); break; + case IDM_CREATE_FOLDER: g_App.CreateFolder(); break; + case IDM_CREATE_FILE: g_App.CreateFile(); break; + #ifndef UNDER_CE + case IDM_LINK: g_App.Link(); break; + case IDM_ALT_STREAMS: g_App.OpenAltStreams(); break; + #endif + default: return false; + } + return true; +} + +static void MyBenchmark(bool totalMode) +{ + CPanel::CDisableTimerProcessing disableTimerProcessing1(g_App.Panels[0]); + CPanel::CDisableTimerProcessing disableTimerProcessing2(g_App.Panels[1]); + Benchmark(totalMode); +} + +bool OnMenuCommand(HWND hWnd, unsigned id) +{ + if (ExecuteFileCommand(id)) + return true; + + switch (id) + { + // File + case IDCLOSE: + // SendMessage(hWnd, WM_ACTIVATE, MAKEWPARAM(WA_INACTIVE, 0), (LPARAM)hWnd); + // g_ExitEventLauncher.Exit(false); + SendMessage(hWnd, WM_CLOSE, 0, 0); + break; + + // Edit + /* + case IDM_EDIT_CUT: + g_App.EditCut(); + break; + case IDM_EDIT_COPY: + g_App.EditCopy(); + break; + case IDM_EDIT_PASTE: + g_App.EditPaste(); + break; + */ + case IDM_SELECT_ALL: + g_App.SelectAll(true); + g_App.Refresh_StatusBar(); + break; + case IDM_DESELECT_ALL: + g_App.SelectAll(false); + g_App.Refresh_StatusBar(); + break; + case IDM_INVERT_SELECTION: + g_App.InvertSelection(); + g_App.Refresh_StatusBar(); + break; + case IDM_SELECT: + g_App.SelectSpec(true); + g_App.Refresh_StatusBar(); + break; + case IDM_DESELECT: + g_App.SelectSpec(false); + g_App.Refresh_StatusBar(); + break; + case IDM_SELECT_BY_TYPE: + g_App.SelectByType(true); + g_App.Refresh_StatusBar(); + break; + case IDM_DESELECT_BY_TYPE: + g_App.SelectByType(false); + g_App.Refresh_StatusBar(); + break; + + //View + case IDM_VIEW_LARGE_ICONS: + case IDM_VIEW_SMALL_ICONS: + case IDM_VIEW_LIST: + case IDM_VIEW_DETAILS: + { + UINT index = id - IDM_VIEW_LARGE_ICONS; + if (index < 4) + { + g_App.SetListViewMode(index); + /* + CMenu menu; + menu.Attach(::GetSubMenu(::GetMenu(hWnd), k_MenuIndex_View)); + menu.CheckRadioItem(IDM_VIEW_LARGE_ICONS, IDM_VIEW_DETAILS, + id, MF_BYCOMMAND); + */ + } + break; + } + case IDM_VIEW_ARANGE_BY_NAME: g_App.SortItemsWithPropID(kpidName); break; + case IDM_VIEW_ARANGE_BY_TYPE: g_App.SortItemsWithPropID(kpidExtension); break; + case IDM_VIEW_ARANGE_BY_DATE: g_App.SortItemsWithPropID(kpidMTime); break; + case IDM_VIEW_ARANGE_BY_SIZE: g_App.SortItemsWithPropID(kpidSize); break; + case IDM_VIEW_ARANGE_NO_SORT: g_App.SortItemsWithPropID(kpidNoProperty); break; + + case IDM_OPEN_ROOT_FOLDER: g_App.OpenRootFolder(); break; + case IDM_OPEN_PARENT_FOLDER: g_App.OpenParentFolder(); break; + case IDM_FOLDERS_HISTORY: g_App.FoldersHistory(); break; + case IDM_VIEW_FLAT_VIEW: g_App.ChangeFlatMode(); break; + case IDM_VIEW_REFRESH: g_App.RefreshView(); break; + case IDM_VIEW_AUTO_REFRESH: g_App.Change_AutoRefresh_Mode(); break; + + // case IDM_VIEW_SHOW_STREAMS: g_App.Change_ShowNtfsStrems_Mode(); break; + /* + case IDM_VIEW_SHOW_DELETED: + { + g_App.Change_ShowDeleted(); + bool isChecked = g_App.ShowDeletedFiles; + Save_ShowDeleted(isChecked); + } + */ + + case IDM_VIEW_TWO_PANELS: g_App.SwitchOnOffOnePanel(); break; + case IDM_VIEW_STANDARD_TOOLBAR: g_App.SwitchStandardToolbar(); break; + case IDM_VIEW_ARCHIVE_TOOLBAR: g_App.SwitchArchiveToolbar(); break; + + case IDM_VIEW_TOOLBARS_SHOW_BUTTONS_TEXT: g_App.SwitchButtonsLables(); break; + case IDM_VIEW_TOOLBARS_LARGE_BUTTONS: g_App.SwitchLargeButtons(); break; + + case IDM_VIEW_TIME_UTC: + g_Timestamp_Show_UTC = !g_Timestamp_Show_UTC; + g_App.RedrawListItems_InPanels(); + break; + + // Tools + case IDM_OPTIONS: OptionsDialog(hWnd, g_hInstance); break; + + case IDM_BENCHMARK: MyBenchmark(false); break; + case IDM_BENCHMARK2: MyBenchmark(true); break; + + // Help + case IDM_HELP_CONTENTS: + ShowHelpWindow(kFMHelpTopic); + break; + case IDM_ABOUT: + { + CAboutDialog dialog; + dialog.Create(hWnd); + break; + } + + case IDM_TEMP_DIR: + { + /* + CPanel &panel = g_App.GetFocusedPanel(); + FString tempPathF; + if (NFile::NDir::MyGetTempPath(tempPathF)) + panel.BindToPathAndRefresh(tempPathF); + */ + MyBrowseForTempFolder(g_HWND); + break; + } + + default: + { + if (id >= k_MenuID_OpenBookmark && id <= k_MenuID_OpenBookmark + 9) + { + g_App.OpenBookmark(id - k_MenuID_OpenBookmark); + return true; + } + else if (id >= k_MenuID_SetBookmark && id <= k_MenuID_SetBookmark + 9) + { + g_App.SetBookmark(id - k_MenuID_SetBookmark); + return true; + } + else if (id >= k_MenuID_Time && (unsigned)id < k_MenuID_Time + g_App._timestampLevels.Size()) + { + g_App.SetTimestampLevel(g_App._timestampLevels[id - k_MenuID_Time]); + return true; + } + return false; + } + } + return true; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MyLoadMenu.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MyLoadMenu.h new file mode 100644 index 00000000..e71dbbf6 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MyLoadMenu.h @@ -0,0 +1,40 @@ +// MyLoadMenu.h + +#ifndef ZIP7_INC_MY_LOAD_MENU_H +#define ZIP7_INC_MY_LOAD_MENU_H + +void OnMenuActivating(HWND hWnd, HMENU hMenu, int position); +// void OnMenuUnActivating(HWND hWnd, HMENU hMenu, int id); +// void OnMenuUnActivating(HWND hWnd); + +bool OnMenuCommand(HWND hWnd, unsigned id); +void MyLoadMenu(bool needResetMenu); + +struct CFileMenu +{ + bool programMenu; + bool readOnly; + bool isHashFolder; + bool isFsFolder; + bool allAreFiles; + bool isAltStreamsSupported; + unsigned numItems; + + FString FilePath; + + CFileMenu(): + programMenu(false), + readOnly(false), + isHashFolder(false), + isFsFolder(false), + allAreFiles(false), + isAltStreamsSupported(true), + numItems(0) + {} + + void Load(HMENU hMenu, unsigned startPos); +}; + +bool ExecuteFileCommand(unsigned id); + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MyWindowsNew.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MyWindowsNew.h new file mode 100644 index 00000000..325babcd --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/MyWindowsNew.h @@ -0,0 +1,119 @@ +// MyWindowsNew.h + +#ifndef ZIP7_INC_MY_WINDOWS_NEW_H +#define ZIP7_INC_MY_WINDOWS_NEW_H + +#if defined(__MINGW32__) || defined(__MINGW64__) || defined(__MINGW32_VERSION) +#include + +#if defined(__MINGW32_VERSION) && !defined(__ITaskbarList3_INTERFACE_DEFINED__) +// for old mingw +extern "C" { +DEFINE_GUID(IID_ITaskbarList3, 0xEA1AFB91, 0x9E28, 0x4B86, 0x90, 0xE9, 0x9E, 0x9F, 0x8A, 0x5E, 0xEF, 0xAF); +DEFINE_GUID(CLSID_TaskbarList, 0x56fdf344, 0xfd6d, 0x11d0, 0x95,0x8a, 0x00,0x60,0x97,0xc9,0xa0,0x90); +} +#endif + +#else // is not __MINGW* + +#ifndef Z7_OLD_WIN_SDK +#include +#else + +#ifndef HIMAGELIST +struct _IMAGELIST; +typedef struct _IMAGELIST* HIMAGELIST; +#endif + +#ifndef __ITaskbarList_INTERFACE_DEFINED__ +#define __ITaskbarList_INTERFACE_DEFINED__ +DEFINE_GUID(IID_ITaskbarList, 0x56FDF342, 0xFD6D, 0x11d0, 0x95, 0x8A, 0x00, 0x60, 0x97, 0xC9, 0xA0, 0x90); +struct ITaskbarList: public IUnknown +{ + STDMETHOD(HrInit)(void) = 0; + STDMETHOD(AddTab)(HWND hwnd) = 0; + STDMETHOD(DeleteTab)(HWND hwnd) = 0; + STDMETHOD(ActivateTab)(HWND hwnd) = 0; + STDMETHOD(SetActiveAlt)(HWND hwnd) = 0; +}; +#endif // __ITaskbarList_INTERFACE_DEFINED__ + +#ifndef __ITaskbarList2_INTERFACE_DEFINED__ +#define __ITaskbarList2_INTERFACE_DEFINED__ +DEFINE_GUID(IID_ITaskbarList2, 0x602D4995, 0xB13A, 0x429b, 0xA6, 0x6E, 0x19, 0x35, 0xE4, 0x4F, 0x43, 0x17); +struct ITaskbarList2: public ITaskbarList +{ + STDMETHOD(MarkFullscreenWindow)(HWND hwnd, BOOL fFullscreen) = 0; +}; +#endif // __ITaskbarList2_INTERFACE_DEFINED__ + +#endif // Z7_OLD_WIN_SDK + + +#ifndef __ITaskbarList3_INTERFACE_DEFINED__ +#define __ITaskbarList3_INTERFACE_DEFINED__ + +typedef enum THUMBBUTTONFLAGS +{ + THBF_ENABLED = 0, + THBF_DISABLED = 0x1, + THBF_DISMISSONCLICK = 0x2, + THBF_NOBACKGROUND = 0x4, + THBF_HIDDEN = 0x8, + THBF_NONINTERACTIVE = 0x10 +} THUMBBUTTONFLAGS; + +typedef enum THUMBBUTTONMASK +{ + THB_BITMAP = 0x1, + THB_ICON = 0x2, + THB_TOOLTIP = 0x4, + THB_FLAGS = 0x8 +} THUMBBUTTONMASK; + +// #include + +typedef struct THUMBBUTTON +{ + THUMBBUTTONMASK dwMask; + UINT iId; + UINT iBitmap; + HICON hIcon; + WCHAR szTip[260]; + THUMBBUTTONFLAGS dwFlags; +} THUMBBUTTON; + +typedef struct THUMBBUTTON *LPTHUMBBUTTON; + +typedef enum TBPFLAG +{ + TBPF_NOPROGRESS = 0, + TBPF_INDETERMINATE = 0x1, + TBPF_NORMAL = 0x2, + TBPF_ERROR = 0x4, + TBPF_PAUSED = 0x8 +} TBPFLAG; + +DEFINE_GUID(IID_ITaskbarList3, 0xEA1AFB91, 0x9E28, 0x4B86, 0x90, 0xE9, 0x9E, 0x9F, 0x8A, 0x5E, 0xEF, 0xAF); + +struct ITaskbarList3: public ITaskbarList2 +{ + STDMETHOD(SetProgressValue)(HWND hwnd, ULONGLONG ullCompleted, ULONGLONG ullTotal) = 0; + STDMETHOD(SetProgressState)(HWND hwnd, TBPFLAG tbpFlags) = 0; + STDMETHOD(RegisterTab)(HWND hwndTab, HWND hwndMDI) = 0; + STDMETHOD(UnregisterTab)(HWND hwndTab) = 0; + STDMETHOD(SetTabOrder)(HWND hwndTab, HWND hwndInsertBefore) = 0; + STDMETHOD(SetTabActive)(HWND hwndTab, HWND hwndMDI, DWORD dwReserved) = 0; + STDMETHOD(ThumbBarAddButtons)(HWND hwnd, UINT cButtons, LPTHUMBBUTTON pButton) = 0; + STDMETHOD(ThumbBarUpdateButtons)(HWND hwnd, UINT cButtons, LPTHUMBBUTTON pButton) = 0; + STDMETHOD(ThumbBarSetImageList)(HWND hwnd, HIMAGELIST himl) = 0; + STDMETHOD(SetOverlayIcon)(HWND hwnd, HICON hIcon, LPCWSTR pszDescription) = 0; + STDMETHOD(SetThumbnailTooltip)(HWND hwnd, LPCWSTR pszTip) = 0; + STDMETHOD(SetThumbnailClip)(HWND hwnd, RECT *prcClip) = 0; +}; + +#endif // __ITaskbarList3_INTERFACE_DEFINED__ + +#endif // __MINGW* + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/NetFolder.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/NetFolder.cpp new file mode 100644 index 00000000..e91e67fc --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/NetFolder.cpp @@ -0,0 +1,276 @@ +// NetFolder.cpp + +#include "StdAfx.h" + +#include "../../../Windows/PropVariant.h" + +#include "../../PropID.h" + +#include "FSFolder.h" +#include "NetFolder.h" +#include "SysIconUtils.h" + +using namespace NWindows; +using namespace NNet; + +static const Byte kProps[] = +{ + kpidName, + kpidLocalName, + kpidComment, + kpidProvider +}; + +void CNetFolder::Init(const UString &path) +{ + /* + if (path.Len() > 2) + { + if (path[0] == L'\\' && path[1] == L'\\') + { + CResource netResource; + netResource.RemoteName = GetSystemString(path.Left(path.Len() - 1)); + netResource.Scope = RESOURCE_GLOBALNET; + netResource.Type = RESOURCETYPE_DISK; + netResource.DisplayType = RESOURCEDISPLAYTYPE_SERVER; + netResource.Usage = RESOURCEUSAGE_CONTAINER; + Init(&netResource, 0, path); + return; + } + } + Init(0, 0 , L""); + */ + CResourceW resource; + resource.RemoteNameIsDefined = true; + if (!path.IsEmpty()) + resource.RemoteName.SetFrom(path, path.Len() - 1); + resource.ProviderIsDefined = false; + resource.LocalNameIsDefined = false; + resource.CommentIsDefined = false; + resource.Type = RESOURCETYPE_DISK; + resource.Scope = RESOURCE_GLOBALNET; + resource.Usage = 0; + resource.DisplayType = 0; + CResourceW destResource; + UString systemPathPart; + DWORD result = GetResourceInformation(resource, destResource, systemPathPart); + if (result == NO_ERROR) + Init(&destResource, NULL, path); + else + Init(NULL, NULL , L""); + return; +} + +void CNetFolder::Init(const NWindows::NNet::CResourceW *netResource, + IFolderFolder *parentFolder, const UString &path) +{ + _path = path; + if (!netResource) + _netResourcePointer = NULL; + else + { + _netResource = *netResource; + _netResourcePointer = &_netResource; + + // if (_netResource.DisplayType == RESOURCEDISPLAYTYPE_SERVER) + _path = _netResource.RemoteName; + + /* WinXP-64: When we move UP from Network share without _parentFolder chain, + we can get empty _netResource.RemoteName. Do we need to use Provider there ? */ + if (_path.IsEmpty()) + _path = _netResource.Provider; + + if (!_path.IsEmpty()) + _path.Add_PathSepar(); + } + _parentFolder = parentFolder; +} + +Z7_COM7F_IMF(CNetFolder::LoadItems()) +{ + _items.Clear(); + CEnum enumerator; + + for (;;) + { + DWORD result = enumerator.Open( + RESOURCE_GLOBALNET, + RESOURCETYPE_DISK, + 0, // enumerate all resources + _netResourcePointer + ); + if (result == NO_ERROR) + break; + if (result != ERROR_ACCESS_DENIED) + return HRESULT_FROM_WIN32(result); + if (_netResourcePointer) + result = AddConnection2(_netResource, + NULL, NULL, CONNECT_INTERACTIVE); + if (result != NO_ERROR) + return HRESULT_FROM_WIN32(result); + } + + for (;;) + { + CResourceEx resource; + const DWORD result = enumerator.Next(resource); + if (result == NO_ERROR) + { + if (!resource.RemoteNameIsDefined) // For Win 98, I don't know what's wrong + resource.RemoteName = resource.Comment; + resource.Name = resource.RemoteName; + const int pos = resource.Name.ReverseFind_PathSepar(); + if (pos >= 0) + { + // _path = resource.Name.Left(pos + 1); + resource.Name.DeleteFrontal((unsigned)pos + 1); + } + _items.Add(resource); + } + else if (result == ERROR_NO_MORE_ITEMS) + break; + else + return HRESULT_FROM_WIN32(result); + } + + /* + It's too slow for some systems. + if (_netResourcePointer && _netResource.DisplayType == RESOURCEDISPLAYTYPE_SERVER) + { + for (char c = 'a'; c <= 'z'; c++) + { + CResourceEx resource; + resource.Name = UString(wchar_t(c)) + L'$'; + resource.RemoteNameIsDefined = true; + resource.RemoteName = _path + resource.Name; + + NFile::NFind::CFindFile findFile; + NFile::NFind::CFileInfo fileInfo; + if (!findFile.FindFirst(us2fs(resource.RemoteName) + FString(FCHAR_PATH_SEPARATOR) + FCHAR_ANY_MASK, fileInfo)) + continue; + resource.Usage = RESOURCEUSAGE_CONNECTABLE; + resource.LocalNameIsDefined = false; + resource.CommentIsDefined = false; + resource.ProviderIsDefined = false; + _items.Add(resource); + } + } + */ + return S_OK; +} + + +Z7_COM7F_IMF(CNetFolder::GetNumberOfItems(UInt32 *numItems)) +{ + *numItems = _items.Size(); + return S_OK; +} + +Z7_COM7F_IMF(CNetFolder::GetProperty(UInt32 itemIndex, PROPID propID, PROPVARIANT *value)) +{ + NCOM::CPropVariant prop; + const CResourceEx &item = _items[itemIndex]; + switch (propID) + { + case kpidIsDir: prop = true; break; + case kpidName: + // if (item.RemoteNameIsDefined) + prop = item.Name; + break; + case kpidLocalName: if (item.LocalNameIsDefined) prop = item.LocalName; break; + case kpidComment: if (item.CommentIsDefined) prop = item.Comment; break; + case kpidProvider: if (item.ProviderIsDefined) prop = item.Provider; break; + } + prop.Detach(value); + return S_OK; +} + +Z7_COM7F_IMF(CNetFolder::BindToFolder(UInt32 index, IFolderFolder **resultFolder)) +{ + *resultFolder = NULL; + const CResourceEx &resource = _items[index]; + if (resource.Usage == RESOURCEUSAGE_CONNECTABLE || + resource.DisplayType == RESOURCEDISPLAYTYPE_SHARE) + { + NFsFolder::CFSFolder *fsFolderSpec = new NFsFolder::CFSFolder; + CMyComPtr subFolder = fsFolderSpec; + RINOK(fsFolderSpec->Init(us2fs(resource.RemoteName + WCHAR_PATH_SEPARATOR))) // , this + *resultFolder = subFolder.Detach(); + } + else + { + CNetFolder *netFolder = new CNetFolder; + CMyComPtr subFolder = netFolder; + netFolder->Init(&resource, this, resource.Name + WCHAR_PATH_SEPARATOR); + *resultFolder = subFolder.Detach(); + } + return S_OK; +} + +Z7_COM7F_IMF(CNetFolder::BindToFolder(const wchar_t * /* name */, IFolderFolder ** /* resultFolder */)) +{ + return E_NOTIMPL; +} + +Z7_COM7F_IMF(CNetFolder::BindToParentFolder(IFolderFolder **resultFolder)) +{ + *resultFolder = NULL; + if (_parentFolder) + { + CMyComPtr parentFolder = _parentFolder; + *resultFolder = parentFolder.Detach(); + return S_OK; + } + if (_netResourcePointer) + { + CResourceW resourceParent; + const DWORD result = GetResourceParent(_netResource, resourceParent); + if (result != NO_ERROR) + return HRESULT_FROM_WIN32(result); + if (!_netResource.RemoteNameIsDefined) + return S_OK; + + CNetFolder *netFolder = new CNetFolder; + CMyComPtr subFolder = netFolder; + netFolder->Init(&resourceParent, NULL, WSTRING_PATH_SEPARATOR); + *resultFolder = subFolder.Detach(); + } + return S_OK; +} + +IMP_IFolderFolder_Props(CNetFolder) + +Z7_COM7F_IMF(CNetFolder::GetFolderProperty(PROPID propID, PROPVARIANT *value)) +{ + NWindows::NCOM::CPropVariant prop; + switch (propID) + { + case kpidType: prop = "NetFolder"; break; + case kpidPath: prop = _path; break; + } + prop.Detach(value); + return S_OK; +} + +Z7_COM7F_IMF(CNetFolder::GetSystemIconIndex(UInt32 index, Int32 *iconIndex)) +{ + *iconIndex = -1; + if (index >= _items.Size()) + return E_INVALIDARG; + const CResourceW &resource = _items[index]; + if (resource.DisplayType == RESOURCEDISPLAYTYPE_SERVER || + resource.Usage == RESOURCEUSAGE_CONNECTABLE) + { + return Shell_GetFileInfo_SysIconIndex_for_Path_return_HRESULT( + us2fs(resource.RemoteName), FILE_ATTRIBUTE_DIRECTORY, iconIndex); + } + else + { +#if 0 + return S_FALSE; +#else + return Shell_GetFileInfo_SysIconIndex_for_Path_return_HRESULT( + FTEXT("__DIR__"), FILE_ATTRIBUTE_DIRECTORY, iconIndex); +#endif + } +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/NetFolder.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/NetFolder.h new file mode 100644 index 00000000..352f5bd8 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/NetFolder.h @@ -0,0 +1,36 @@ +// NetFolder.h + +#ifndef ZIP7_INC_NET_FOLDER_H +#define ZIP7_INC_NET_FOLDER_H + +#include "../../../Common/MyCom.h" + +#include "../../../Windows/Net.h" + +#include "IFolder.h" + +struct CResourceEx: public NWindows::NNet::CResourceW +{ + UString Name; +}; + +Z7_CLASS_IMP_NOQIB_2( + CNetFolder + , IFolderFolder + , IFolderGetSystemIconIndex +) + NWindows::NNet::CResourceW _netResource; + NWindows::NNet::CResourceW *_netResourcePointer; + + CObjectVector _items; + + CMyComPtr _parentFolder; + UString _path; +public: + CNetFolder(): _netResourcePointer(NULL) {} + void Init(const UString &path); + void Init(const NWindows::NNet::CResourceW *netResource, + IFolderFolder *parentFolder, const UString &path); +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/OpenCallback.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/OpenCallback.cpp new file mode 100644 index 00000000..e3cb2ecf --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/OpenCallback.cpp @@ -0,0 +1,86 @@ +// OpenCallback.cpp + +#include "StdAfx.h" + +#include "../../../Common/ComTry.h" +#include "../../../Common/StringConvert.h" + +#include "../../../Windows/FileName.h" +#include "../../../Windows/PropVariant.h" + +#include "../../Common/FileStreams.h" + +#include "../Common/ZipRegistry.h" + +#include "OpenCallback.h" +#include "PasswordDialog.h" + +using namespace NWindows; + +HRESULT COpenArchiveCallback::Open_SetTotal(const UInt64 *numFiles, const UInt64 *numBytes) +// Z7_COM7F_IMF(COpenArchiveCallback::SetTotal(const UInt64 *numFiles, const UInt64 *numBytes)) +{ + // COM_TRY_BEGIN + RINOK(ProgressDialog.Sync.CheckStop()) + { + // NSynchronization::CCriticalSectionLock lock(_criticalSection); + ProgressDialog.Sync.Set_NumFilesTotal(numFiles ? *numFiles : (UInt64)(Int64)-1); + // if (numFiles) + { + ProgressDialog.Sync.Set_FilesProgressMode(numFiles != NULL); + } + if (numBytes) + ProgressDialog.Sync.Set_NumBytesTotal(*numBytes); + } + return S_OK; + // COM_TRY_END +} + +HRESULT COpenArchiveCallback::Open_SetCompleted(const UInt64 *numFiles, const UInt64 *numBytes) +// Z7_COM7F_IMF(COpenArchiveCallback::SetCompleted(const UInt64 *numFiles, const UInt64 *numBytes)) +{ + // COM_TRY_BEGIN + // NSynchronization::CCriticalSectionLock lock(_criticalSection); + if (numFiles) + ProgressDialog.Sync.Set_NumFilesCur(*numFiles); + if (numBytes) + ProgressDialog.Sync.Set_NumBytesCur(*numBytes); + return ProgressDialog.Sync.CheckStop(); + // COM_TRY_END +} + +HRESULT COpenArchiveCallback::Open_CheckBreak() +{ + return ProgressDialog.Sync.CheckStop(); +} + +HRESULT COpenArchiveCallback::Open_Finished() +{ + return ProgressDialog.Sync.CheckStop(); +} + +#ifndef Z7_NO_CRYPTO +HRESULT COpenArchiveCallback::Open_CryptoGetTextPassword(BSTR *password) +// Z7_COM7F_IMF(COpenArchiveCallback::CryptoGetTextPassword(BSTR *password)) +{ + // COM_TRY_BEGIN + PasswordWasAsked = true; + if (!PasswordIsDefined) + { + CPasswordDialog dialog; + bool showPassword = NExtract::Read_ShowPassword(); + dialog.ShowPassword = showPassword; + + ProgressDialog.WaitCreating(); + if (dialog.Create(ProgressDialog) != IDOK) + return E_ABORT; + + Password = dialog.Password; + PasswordIsDefined = true; + if (dialog.ShowPassword != showPassword) + NExtract::Save_ShowPassword(dialog.ShowPassword); + } + return StringToBstr(Password, password); + // COM_TRY_END +} +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/OpenCallback.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/OpenCallback.h new file mode 100644 index 00000000..8f4638cf --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/OpenCallback.h @@ -0,0 +1,69 @@ +// OpenCallback.h + +#ifndef ZIP7_INC_OPEN_CALLBACK_H +#define ZIP7_INC_OPEN_CALLBACK_H + +#include "../Common/ArchiveOpenCallback.h" + +#ifdef Z7_SFX +#include "ProgressDialog.h" +#else +#include "ProgressDialog2.h" +#endif + +/* we can use IArchiveOpenCallback or IOpenCallbackUI here */ + +class COpenArchiveCallback Z7_final: + /* + public IArchiveOpenCallback, + public IProgress, + public ICryptoGetTextPassword, + public CMyUnknownImp + */ + public IOpenCallbackUI +{ + // NWindows::NSynchronization::CCriticalSection _criticalSection; +public: + bool PasswordIsDefined; + bool PasswordWasAsked; + UString Password; + HWND ParentWindow; + CProgressDialog ProgressDialog; + + /* + Z7_COM_UNKNOWN_IMP_3( + IArchiveOpenVolumeCallback, + IProgress + ICryptoGetTextPassword + ) + + Z7_IFACE_COM7_IMP(IProgress) + Z7_IFACE_COM7_IMP(IArchiveOpenCallback) + // ICryptoGetTextPassword + Z7_COM7F_IMP(CryptoGetTextPassword(BSTR *password)) + */ + + Z7_IFACE_IMP(IOpenCallbackUI) + + COpenArchiveCallback(): + ParentWindow(NULL) + { + // _subArchiveMode = false; + PasswordIsDefined = false; + PasswordWasAsked = false; + } + /* + void Init() + { + PasswordIsDefined = false; + _subArchiveMode = false; + } + */ + + INT_PTR StartProgressDialog(const UString &title, NWindows::CThread &thread) + { + return ProgressDialog.Create(title, thread, ParentWindow); + } +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/OptionsDialog.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/OptionsDialog.cpp new file mode 100644 index 00000000..b71c3233 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/OptionsDialog.cpp @@ -0,0 +1,89 @@ +// OptionsDialog.cpp + +#include "StdAfx.h" + +#include "../../../Windows/Control/Dialog.h" +#include "../../../Windows/Control/PropertyPage.h" + +#include "DialogSize.h" + +#include "EditPage.h" +#include "EditPageRes.h" +#include "FoldersPage.h" +#include "FoldersPageRes.h" +#include "LangPage.h" +#include "LangPageRes.h" +#include "MenuPage.h" +#include "MenuPageRes.h" +#include "SettingsPage.h" +#include "SettingsPageRes.h" +#include "SystemPage.h" +#include "SystemPageRes.h" + +#include "App.h" +#include "LangUtils.h" +#include "MyLoadMenu.h" + +#include "resource.h" + +using namespace NWindows; + +void OptionsDialog(HWND hwndOwner, HINSTANCE hInstance); +void OptionsDialog(HWND hwndOwner, HINSTANCE /* hInstance */) +{ + CSystemPage systemPage; + CMenuPage menuPage; + CFoldersPage foldersPage; + CEditPage editPage; + CSettingsPage settingsPage; + CLangPage langPage; + + CObjectVector pages; + BIG_DIALOG_SIZE(200, 200); + + const UINT pageIDs[] = { + SIZED_DIALOG(IDD_SYSTEM), + SIZED_DIALOG(IDD_MENU), + SIZED_DIALOG(IDD_FOLDERS), + SIZED_DIALOG(IDD_EDIT), + SIZED_DIALOG(IDD_SETTINGS), + SIZED_DIALOG(IDD_LANG) }; + + NControl::CPropertyPage *pagePointers[] = { &systemPage, &menuPage, &foldersPage, &editPage, &settingsPage, &langPage }; + + for (unsigned i = 0; i < Z7_ARRAY_SIZE(pageIDs); i++) + { + NControl::CPageInfo &page = pages.AddNew(); + page.ID = pageIDs[i]; + #ifdef Z7_LANG + LangString_OnlyFromLangFile(page.ID, page.Title); + #endif + page.Page = pagePointers[i]; + } + + const INT_PTR res = NControl::MyPropertySheet(pages, hwndOwner, LangString(IDS_OPTIONS)); + + if (res != -1 && res != 0) + { + if (langPage.LangWasChanged) + { + // g_App._window.SetText(LangString(IDS_APP_TITLE, 0x03000000)); + MyLoadMenu(true); // needResetMenu + g_App.ReloadToolbars(); + g_App.MoveSubWindows(); // we need it to change list window aafter _toolBar.AutoSize(); + g_App.ReloadLangItems(); + } + + /* + if (systemPage.WasChanged) + { + // probably it doesn't work, since image list is locked? + g_App.SysIconsWereChanged(); + } + */ + + g_App.SetListSettings(); + g_App.RefreshAllPanels(); + // ::PostMessage(hwndOwner, kLangWasChangedMessage, 0 , 0); + } +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/OverwriteDialog.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/OverwriteDialog.cpp new file mode 100644 index 00000000..b15c7029 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/OverwriteDialog.cpp @@ -0,0 +1,288 @@ +// OverwriteDialog.cpp + +#include "StdAfx.h" + +#include "../../../Common/IntToString.h" +#include "../../../Common/StringConvert.h" + +#include "../../../Windows/FileFind.h" +#include "../../../Windows/PropVariantConv.h" +#include "../../../Windows/ResourceString.h" + +#include "../../../Windows/Control/Static.h" + +#include "FormatUtils.h" +#include "LangUtils.h" +#include "OverwriteDialog.h" + +#include "PropertyNameRes.h" + +using namespace NWindows; + +#ifdef Z7_LANG +static const UInt32 kLangIDs[] = +{ + IDT_OVERWRITE_HEADER, + IDT_OVERWRITE_QUESTION_BEGIN, + IDT_OVERWRITE_QUESTION_END, + IDB_YES_TO_ALL, + IDB_NO_TO_ALL, + IDB_AUTO_RENAME +}; +#endif + +static const unsigned kCurrentFileNameSizeLimit = 72; + +void COverwriteDialog::ReduceString(UString &s) +{ + const unsigned size = +#ifdef UNDER_CE + !_isBig ? 30 : // kCurrentFileNameSizeLimit2 +#endif + kCurrentFileNameSizeLimit; + + if (s.Len() > size) + { + s.Delete(size / 2, s.Len() - size); + s.Insert(size / 2, L" ... "); + } + if (!s.IsEmpty() && s.Back() == ' ') + { + // s += (wchar_t)(0x2423); // visible space + s.InsertAtFront(L'\"'); + s.Add_Char('\"'); + } +} + + +void COverwriteDialog::SetItemIcon(unsigned iconID, HICON hIcon) +{ + NControl::CStatic staticContol; + staticContol.Attach(GetItem(iconID)); + hIcon = staticContol.SetIcon(hIcon); + if (hIcon) + DestroyIcon(hIcon); +} + +void AddSizeValue(UString &s, UInt64 value); +void AddSizeValue(UString &s, UInt64 value) +{ + { + wchar_t sz[32]; + ConvertUInt64ToString(value, sz); + s += MyFormatNew(IDS_FILE_SIZE, sz); + } + if (value >= (1 << 10)) + { + char c; + if (value >= ((UInt64)10 << 30)) { value >>= 30; c = 'G'; } + else if (value >= (10 << 20)) { value >>= 20; c = 'M'; } + else { value >>= 10; c = 'K'; } + s += " : "; + s.Add_UInt64(value); + s.Add_Space(); + s.Add_Char(c); + s += "iB"; + } +} + + +void COverwriteDialog::SetFileInfoControl( + const NOverwriteDialog::CFileInfo &fileInfo, + unsigned textID, + unsigned iconID, + unsigned iconID_2) +{ + { + const UString &path = fileInfo.Path; + const int slashPos = path.ReverseFind_PathSepar(); + UString s = path.Left((unsigned)(slashPos + 1)); + ReduceString(s); + s.Add_LF(); + { + UString s2 = path.Ptr((unsigned)(slashPos + 1)); + ReduceString(s2); + s += s2; + } + s.Add_LF(); + if (fileInfo.Size_IsDefined) + AddSizeValue(s, fileInfo.Size); + s.Add_LF(); + if (fileInfo.Time_IsDefined) + { + AddLangString(s, IDS_PROP_MTIME); + s += ": "; + char t[64]; + ConvertUtcFileTimeToString(fileInfo.Time, t); + s += t; + } + SetItemText(textID, s); + } +/* + SHGetFileInfo(): + DOCs: If uFlags does not contain SHGFI_EXETYPE or SHGFI_SYSICONINDEX, + the return value is nonzero if successful, or zero otherwise. + We don't use SHGFI_EXETYPE or SHGFI_SYSICONINDEX here. + win10: we call with SHGFI_ICON flag set. + it returns 0: if error : (shFileInfo::*) members are not set. + it returns non_0, if successful, and retrieve: + { shFileInfo.hIcon != NULL : the handle to icon (must be destroyed by our code) + shFileInfo.iIcon is index of the icon image within the system image list. + } + Note: + If we send path to ".exe" file, + SHGFI_USEFILEATTRIBUTES flag is ignored, and it tries to open file. + and return icon from that exe file. + So we still need to reduce path, if want to get raw icon of exe file. + + if (name.Len() >= MAX_PATH)) + { + it can return: + return 0. + return 1 and: + { shFileInfo.hIcon != NULL : is some default icon for file + shFileInfo.iIcon == 0 + } + return results (0 or 1) can depend from: + - unicode/non-unicode + - (SHGFI_USEFILEATTRIBUTES) flag + - exact file extension (.exe). + } +*/ + int iconIndex = -1; + for (unsigned i = 0; i < 2; i++) + { + CSysString name = GetSystemString(fileInfo.Path); + if (i != 0) + { + if (!fileInfo.Is_FileSystemFile) + break; + if (name.Len() < 4 || + (!StringsAreEqualNoCase_Ascii(name.RightPtr(4), ".exe") && + !StringsAreEqualNoCase_Ascii(name.RightPtr(4), ".ico"))) + break; + // if path for ".exe" file is long, it returns default icon (shFileInfo.iIcon == 0). + // We don't want to show that default icon. + // But we will check for default icon later instead of MAX_PATH check here. + // if (name.Len() >= MAX_PATH) break; // optional + } + else + { + // we need only file extension with dot + const int separ = name.ReverseFind_PathSepar(); + name.DeleteFrontal((unsigned)(separ + 1)); + // if (name.Len() >= MAX_PATH) + { + const int dot = name.ReverseFind_Dot(); + if (dot >= 0) + name.DeleteFrontal((unsigned)dot); + // else name.Empty(); to set default name below + } + // name.Empty(); // for debug + } + + if (name.IsEmpty()) + { + // If we send empty name, SHGetFileInfo() returns some strange icon. + // So we use common dummy name without extension, + // and SHGetFileInfo() will return default icon (iIcon == 0) + name = "__file__"; + } + + DWORD attrib = FILE_ATTRIBUTE_ARCHIVE; + if (fileInfo.Is_FileSystemFile) + { + NFile::NFind::CFileInfo fi; + if (fi.Find(us2fs(fileInfo.Path)) && !fi.IsAltStream && !fi.IsDir()) + attrib = fi.Attrib; + } + + SHFILEINFO shFileInfo; + // ZeroMemory(&shFileInfo, sizeof(shFileInfo)); // optional + shFileInfo.hIcon = NULL; // optional + shFileInfo.iIcon = -1; // optional + // memset(&shFileInfo, 1, sizeof(shFileInfo)); // for debug + const DWORD_PTR res = ::SHGetFileInfo(name, attrib, + &shFileInfo, sizeof(shFileInfo), + SHGFI_ICON | SHGFI_LARGEICON | SHGFI_SHELLICONSIZE | + // (i == 0 ? SHGFI_USEFILEATTRIBUTES : 0) + SHGFI_USEFILEATTRIBUTES + // we use SHGFI_USEFILEATTRIBUTES for second icon, because + // it still returns real icon from exe files + ); + if (res && shFileInfo.hIcon) + { + // we don't show second icon, if icon index (iIcon) is same + // as first icon index of first shown icon (exe file without icon) + if ( shFileInfo.iIcon >= 0 + && shFileInfo.iIcon != iconIndex + && (shFileInfo.iIcon != 0 || i == 0)) // we don't want default icon for second icon + { + iconIndex = shFileInfo.iIcon; + SetItemIcon(i == 0 ? iconID : iconID_2, shFileInfo.hIcon); + } + else + DestroyIcon(shFileInfo.hIcon); + } + } +} + + + +bool COverwriteDialog::OnInit() +{ + #ifdef Z7_LANG + LangSetWindowText(*this, IDD_OVERWRITE); + LangSetDlgItems(*this, kLangIDs, Z7_ARRAY_SIZE(kLangIDs)); + #endif + SetFileInfoControl(OldFileInfo, + IDT_OVERWRITE_OLD_FILE_SIZE_TIME, + IDI_OVERWRITE_OLD_FILE, + IDI_OVERWRITE_OLD_FILE_2); + SetFileInfoControl(NewFileInfo, + IDT_OVERWRITE_NEW_FILE_SIZE_TIME, + IDI_OVERWRITE_NEW_FILE, + IDI_OVERWRITE_NEW_FILE_2); + NormalizePosition(); + + if (!ShowExtraButtons) + { + HideItem(IDB_YES_TO_ALL); + HideItem(IDB_NO_TO_ALL); + HideItem(IDB_AUTO_RENAME); + } + + if (DefaultButton_is_NO) + { + PostMsg(DM_SETDEFID, IDNO); + HWND h = GetItem(IDNO); + PostMsg(WM_NEXTDLGCTL, (WPARAM)h, TRUE); + // ::SetFocus(h); + } + + return CModalDialog::OnInit(); +} + +bool COverwriteDialog::OnDestroy() +{ + SetItemIcon(IDI_OVERWRITE_OLD_FILE, NULL); + SetItemIcon(IDI_OVERWRITE_OLD_FILE_2, NULL); + SetItemIcon(IDI_OVERWRITE_NEW_FILE, NULL); + SetItemIcon(IDI_OVERWRITE_NEW_FILE_2, NULL); + return false; // we return (false) to perform default dialog operation +} + +bool COverwriteDialog::OnButtonClicked(unsigned buttonID, HWND buttonHWND) +{ + switch (buttonID) + { + case IDYES: + case IDNO: + case IDB_YES_TO_ALL: + case IDB_NO_TO_ALL: + case IDB_AUTO_RENAME: + End((INT_PTR)buttonID); + return true; + } + return CModalDialog::OnButtonClicked(buttonID, buttonHWND); +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/OverwriteDialog.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/OverwriteDialog.h new file mode 100644 index 00000000..9f0801d9 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/OverwriteDialog.h @@ -0,0 +1,89 @@ +// OverwriteDialog.h + +#ifndef ZIP7_INC_OVERWRITE_DIALOG_H +#define ZIP7_INC_OVERWRITE_DIALOG_H + +#include "../../../Windows/Control/Dialog.h" + +#include "DialogSize.h" +#include "OverwriteDialogRes.h" + +namespace NOverwriteDialog +{ + struct CFileInfo + { + bool Size_IsDefined; + bool Time_IsDefined; + bool Is_FileSystemFile; + UInt64 Size; + FILETIME Time; + UString Path; + + void SetTime(const FILETIME &t) + { + Time = t; + Time_IsDefined = true; + } + + void SetTime2(const FILETIME *t) + { + if (!t) + Time_IsDefined = false; + else + SetTime(*t); + } + + void SetSize(UInt64 size) + { + Size = size; + Size_IsDefined = true; + } + + void SetSize2(const UInt64 *size) + { + if (!size) + Size_IsDefined = false; + else + SetSize(*size); + } + + CFileInfo(): + Size_IsDefined(false), + Time_IsDefined(false), + Is_FileSystemFile(false) + {} + }; +} + +class COverwriteDialog: public NWindows::NControl::CModalDialog +{ +#ifdef UNDER_CE + bool _isBig; +#endif + + void SetItemIcon(unsigned iconID, HICON hIcon); + void SetFileInfoControl(const NOverwriteDialog::CFileInfo &fileInfo, unsigned textID, unsigned iconID, unsigned iconID_2); + virtual bool OnInit() Z7_override; + virtual bool OnDestroy() Z7_override; + virtual bool OnButtonClicked(unsigned buttonID, HWND buttonHWND) Z7_override; + void ReduceString(UString &s); + +public: + bool ShowExtraButtons; + bool DefaultButton_is_NO; + NOverwriteDialog::CFileInfo OldFileInfo; + NOverwriteDialog::CFileInfo NewFileInfo; + + COverwriteDialog(): ShowExtraButtons(true), DefaultButton_is_NO(false) {} + + INT_PTR Create(HWND parent = NULL) + { +#ifdef UNDER_CE + BIG_DIALOG_SIZE(280, 200); + _isBig = isBig; +#endif + return CModalDialog::Create(SIZED_DIALOG(IDD_OVERWRITE), parent); + } +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/OverwriteDialog.rc b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/OverwriteDialog.rc new file mode 100644 index 00000000..112d5d89 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/OverwriteDialog.rc @@ -0,0 +1,93 @@ +#include "OverwriteDialogRes.h" +#include "../../GuiCommon.rc" + +#define xc 340 +#define yc 200 + +#undef iconSize +#define iconSize 24 + +#undef x +#undef fx +#undef fy +#define x (m + iconSize + m) +#define fx (xc - iconSize - m) +#define fy 50 + +#define bSizeBig 104 +#undef bx1 +#define bx1 (xs - m - bSizeBig) + +IDD_OVERWRITE DIALOG 0, 0, xs, ys MY_MODAL_DIALOG_STYLE MY_FONT +CAPTION "Confirm File Replace" +BEGIN + LTEXT "Destination folder already contains processed file.", IDT_OVERWRITE_HEADER, m, 7, xc, 8 + LTEXT "Would you like to replace the existing file", IDT_OVERWRITE_QUESTION_BEGIN, m, 28, xc, 8 + + ICON "", IDI_OVERWRITE_OLD_FILE, m, 44, iconSize, iconSize + ICON "", IDI_OVERWRITE_OLD_FILE_2, m, 44 + iconSize, iconSize, iconSize + LTEXT "", IDT_OVERWRITE_OLD_FILE_SIZE_TIME, x, 44, fx, fy, SS_NOPREFIX + + LTEXT "with this one?", IDT_OVERWRITE_QUESTION_END, m, 98, xc, 8 + + ICON "", IDI_OVERWRITE_NEW_FILE, m, 114, iconSize, iconSize + ICON "", IDI_OVERWRITE_NEW_FILE_2, m, 114 + iconSize, iconSize, iconSize + LTEXT "", IDT_OVERWRITE_NEW_FILE_SIZE_TIME, x, 114, fx, fy, SS_NOPREFIX + + PUSHBUTTON "&Yes", IDYES, bx3, by2, bxs, bys + PUSHBUTTON "Yes to &All", IDB_YES_TO_ALL, bx2, by2, bxs, bys + PUSHBUTTON "A&uto Rename", IDB_AUTO_RENAME, bx1, by2, bSizeBig, bys + PUSHBUTTON "&No", IDNO, bx3, by1, bxs, bys + PUSHBUTTON "No to A&ll", IDB_NO_TO_ALL, bx2, by1, bxs, bys + PUSHBUTTON "&Cancel", IDCANCEL, xs - m - bxs, by1, bxs, bys +END + + +#ifdef UNDER_CE + +#undef m +#undef xc +#undef yc + +#define m 4 +#define xc 152 +#define yc 144 + +#undef fy +#define fy 40 + +#undef bxs +#define bxs 48 + +#undef bx1 + +#define bx1 (xs - m - bxs) + +IDD_OVERWRITE_2 DIALOG 0, 0, xs, ys MY_MODAL_DIALOG_STYLE MY_FONT +CAPTION "Confirm File Replace" +BEGIN + LTEXT "Would you like to replace the existing file", IDT_OVERWRITE_QUESTION_BEGIN, m, m, xc, 8 + + ICON "", IDI_OVERWRITE_OLD_FILE, m, 20, iconSize, iconSize + LTEXT "", IDT_OVERWRITE_OLD_FILE_SIZE_TIME, x, 20, fx, fy, SS_NOPREFIX + + LTEXT "with this one?", IDT_OVERWRITE_QUESTION_END, m, 60, xc, 8 + + ICON "", IDI_OVERWRITE_NEW_FILE, m, 72, iconSize, iconSize + LTEXT "", IDT_OVERWRITE_NEW_FILE_SIZE_TIME, x, 72, fx, fy, SS_NOPREFIX + + PUSHBUTTON "&Yes", IDYES, bx3, by2, bxs, bys + PUSHBUTTON "Yes to &All", IDB_YES_TO_ALL, bx2, by2, bxs, bys + PUSHBUTTON "A&uto Rename", IDB_AUTO_RENAME, bx1, by2, bxs, bys + PUSHBUTTON "&No", IDNO, bx3, by1, bxs, bys + PUSHBUTTON "No to A&ll", IDB_NO_TO_ALL, bx2, by1, bxs, bys + PUSHBUTTON "&Cancel", IDCANCEL, bx1, by1, bxs, bys +END + +#endif + + +STRINGTABLE +BEGIN + IDS_FILE_SIZE "{0} bytes" +END diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/OverwriteDialogRes.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/OverwriteDialogRes.h new file mode 100644 index 00000000..24beb333 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/OverwriteDialogRes.h @@ -0,0 +1,19 @@ +#define IDD_OVERWRITE 3500 +#define IDD_OVERWRITE_2 13500 + +#define IDT_OVERWRITE_HEADER 3501 +#define IDT_OVERWRITE_QUESTION_BEGIN 3502 +#define IDT_OVERWRITE_QUESTION_END 3503 +#define IDS_FILE_SIZE 3504 + +#define IDB_AUTO_RENAME 3505 +#define IDB_YES_TO_ALL 440 +#define IDB_NO_TO_ALL 441 + +#define IDI_OVERWRITE_OLD_FILE 100 +#define IDI_OVERWRITE_OLD_FILE_2 101 +#define IDT_OVERWRITE_OLD_FILE_SIZE_TIME 102 + +#define IDI_OVERWRITE_NEW_FILE 110 +#define IDI_OVERWRITE_NEW_FILE_2 111 +#define IDT_OVERWRITE_NEW_FILE_SIZE_TIME 112 diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/Panel.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/Panel.cpp new file mode 100644 index 00000000..84bd88c5 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/Panel.cpp @@ -0,0 +1,1177 @@ +// Panel.cpp + +#include "StdAfx.h" + +#include + +#include "../../../Common/IntToString.h" +#include "../../../Common/StringConvert.h" + +#include "../../../Windows/ErrorMsg.h" +#include "../../../Windows/FileName.h" +#include "../../../Windows/PropVariant.h" +#include "../../../Windows/Thread.h" + +#include "../../PropID.h" + +#include "resource.h" +#include "../GUI/ExtractRes.h" + +#include "../Common/ArchiveName.h" +#include "../Common/CompressCall.h" +#include "../Common/ZipRegistry.h" + +#include "../Agent/IFolderArchive.h" + +#include "App.h" +#include "ExtractCallback.h" +#include "FSFolder.h" +#include "FormatUtils.h" +#include "LangUtils.h" +#include "Panel.h" +#include "RootFolder.h" + +#include "PropertyNameRes.h" + +using namespace NWindows; +using namespace NControl; + +#ifndef _UNICODE +extern bool g_IsNT; +#endif + +static const UINT_PTR kTimerID = 1; +static const UINT kTimerElapse = 1000; + +static DWORD kStyles[4] = { LVS_ICON, LVS_SMALLICON, LVS_LIST, LVS_REPORT }; + +// static const int kCreateFolderID = 101; + +extern HINSTANCE g_hInstance; + +void CPanel::ReleasePanel() +{ + Disable_Processing_Timer_Notify_StatusBar(); + // It's for unloading COM dll's: don't change it. + CloseOpenFolders(); + _sevenZipContextMenu.Release(); + _systemContextMenu.Release(); +} + +CPanel::~CPanel() +{ + CloseOpenFolders(); +} + +HWND CPanel::GetParent() const +{ + const HWND h = CWindow2::GetParent(); + return h ? h : _mainWindow; +} + +#define kClassName L"7-Zip::Panel" + + +HRESULT CPanel::Create(HWND mainWindow, HWND parentWindow, UINT id, + const UString ¤tFolderPrefix, + const UString &arcFormat, + CPanelCallback *panelCallback, CAppState *appState, + bool needOpenArc, + COpenResult &openRes) +{ + _mainWindow = mainWindow; + _processTimer = true; + _processNotify = true; + _processStatusBar = true; + + _panelCallback = panelCallback; + _appState = appState; + // _index = index; + _baseID = id; + _comboBoxID = _baseID + 3; + _statusBarID = _comboBoxID + 1; + + UString cfp = currentFolderPrefix; + + if (!currentFolderPrefix.IsEmpty()) + if (currentFolderPrefix[0] == L'.') + { + FString cfpF; + if (NFile::NDir::MyGetFullPathName(us2fs(currentFolderPrefix), cfpF)) + cfp = fs2us(cfpF); + } + + RINOK(BindToPath(cfp, arcFormat, openRes)) + + if (needOpenArc && !openRes.ArchiveIsOpened) + return S_OK; + + if (!CreateEx(0, kClassName, NULL, WS_CHILD | WS_VISIBLE, + 0, 0, _xSize, 260, + parentWindow, (HMENU)(UINT_PTR)id, g_hInstance)) + return E_FAIL; + PanelCreated = true; + + return S_OK; +} + +// extern UInt32 g_NumMessages; + +LRESULT CPanel::OnMessage(UINT message, WPARAM wParam, LPARAM lParam) +{ + // g_NumMessages++; + switch (message) + { + case kShiftSelectMessage: + OnShiftSelectMessage(); + return 0; + case kReLoadMessage: + RefreshListCtrl(_selectedState); + return 0; + case kSetFocusToListView: + _listView.SetFocus(); + return 0; + case kOpenItemChanged: + return OnOpenItemChanged(lParam); + case kRefresh_StatusBar: + if (_processStatusBar) + Refresh_StatusBar(); + return 0; + #ifdef UNDER_CE + case kRefresh_HeaderComboBox: + LoadFullPathAndShow(); + return 0; + #endif + case WM_TIMER: + OnTimer(); + return 0; + case WM_CONTEXTMENU: + if (OnContextMenu(HANDLE(wParam), GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam))) + return 0; + break; + /* + case WM_DROPFILES: + CompressDropFiles(HDROP(wParam)); + return 0; + */ + } + return CWindow2::OnMessage(message, wParam, lParam); +} + +LRESULT CMyListView::OnMessage(UINT message, WPARAM wParam, LPARAM lParam) +{ + if (message == WM_CHAR) + { + UINT scanCode = (UINT)((lParam >> 16) & 0xFF); + bool extended = ((lParam & 0x1000000) != 0); + UINT virtualKey = MapVirtualKey(scanCode, 1); + if (virtualKey == VK_MULTIPLY || virtualKey == VK_ADD || + virtualKey == VK_SUBTRACT) + return 0; + if ((wParam == '/' && extended) + || wParam == '\\' || wParam == '/') + { + _panel->OpenDrivesFolder(); + return 0; + } + } + else if (message == WM_SYSCHAR) + { + // For Alt+Enter Beep disabling + UINT scanCode = (UINT)(lParam >> 16) & 0xFF; + UINT virtualKey = MapVirtualKey(scanCode, 1); + if (virtualKey == VK_RETURN || virtualKey == VK_MULTIPLY || + virtualKey == VK_ADD || virtualKey == VK_SUBTRACT) + return 0; + } + /* + else if (message == WM_SYSKEYDOWN) + { + // return 0; + } + */ + else if (message == WM_KEYDOWN) + { + bool alt = IsKeyDown(VK_MENU); + bool ctrl = IsKeyDown(VK_CONTROL); + bool shift = IsKeyDown(VK_SHIFT); + switch (wParam) + { + /* + case VK_RETURN: + { + if (shift && !alt && !ctrl) + { + _panel->OpenSelectedItems(false); + return 0; + } + break; + } + */ + case VK_NEXT: + { + if (ctrl && !alt && !shift) + { + _panel->OpenFocusedItemAsInternal(); + return 0; + } + break; + } + case VK_PRIOR: + if (ctrl && !alt && !shift) + { + _panel->OpenParentFolder(); + return 0; + } + } + } + #ifdef UNDER_CE + else if (message == WM_KEYUP) + { + if (wParam == VK_F2) // it's VK_TSOFT2 + { + // Activate Menu + ::PostMessage(g_HWND, WM_SYSCOMMAND, SC_KEYMENU, 0); + return 0; + } + } + #endif + else if (message == WM_SETFOCUS) + { + _panel->_lastFocusedIsList = true; + _panel->_panelCallback->PanelWasFocused(); + } + return CListView2::OnMessage(message, wParam, lParam); +} + +/* +static LRESULT APIENTRY ComboBoxSubclassProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + CWindow tempDialog(hwnd); + CMyComboBox *w = (CMyComboBox *)(tempDialog.GetUserDataLongPtr()); + if (w == NULL) + return 0; + return w->OnMessage(message, wParam, lParam); +} + +LRESULT CMyComboBox::OnMessage(UINT message, WPARAM wParam, LPARAM lParam) +{ + return CallWindowProc(_origWindowProc, *this, message, wParam, lParam); +} +*/ + +#ifndef UNDER_CE + +static LRESULT APIENTRY ComboBoxEditSubclassProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + CWindow tempDialog(hwnd); + CMyComboBoxEdit *w = (CMyComboBoxEdit *)(tempDialog.GetUserDataLongPtr()); + if (w == NULL) + return 0; + return w->OnMessage(message, wParam, lParam); +} + +#endif + +LRESULT CMyComboBoxEdit::OnMessage(UINT message, WPARAM wParam, LPARAM lParam) +{ + // See MSDN / Subclassing a Combo Box / Creating a Combo-box Toolbar + switch (message) + { + case WM_SYSKEYDOWN: + switch (wParam) + { + case VK_F1: + case VK_F2: + { + // check ALT + if ((lParam & (1<<29)) == 0) + break; + bool alt = IsKeyDown(VK_MENU); + bool ctrl = IsKeyDown(VK_CONTROL); + bool shift = IsKeyDown(VK_SHIFT); + if (alt && !ctrl && !shift) + { + _panel->_panelCallback->SetFocusToPath(wParam == VK_F1 ? 0 : 1); + return 0; + } + break; + } + } + break; + case WM_KEYDOWN: + switch (wParam) + { + case VK_TAB: + // SendMessage(hwndMain, WM_ENTER, 0, 0); + _panel->SetFocusToList(); + return 0; + case VK_F9: + { + bool alt = IsKeyDown(VK_MENU); + bool ctrl = IsKeyDown(VK_CONTROL); + bool shift = IsKeyDown(VK_SHIFT); + if (!alt && !ctrl && !shift) + { + g_App.SwitchOnOffOnePanel(); + return 0; + } + break; + } + case 'W': + { + bool ctrl = IsKeyDown(VK_CONTROL); + if (ctrl) + { + PostMessage(g_HWND, WM_COMMAND, IDCLOSE, 0); + return 0; + } + break; + } + } + break; + case WM_CHAR: + switch (wParam) + { + case VK_TAB: + case VK_ESCAPE: + return 0; + } + } + #ifndef _UNICODE + if (g_IsNT) + return CallWindowProcW(_origWindowProc, *this, message, wParam, lParam); + else + #endif + return CallWindowProc(_origWindowProc, *this, message, wParam, lParam); +} + + +/* + REBARBANDINFO in vista (_WIN32_WINNT >= 0x0600) has additional fields + we want 2000/xp compatibility. + so we must use reduced structure, if we compile with (_WIN32_WINNT >= 0x0600) + Also there are additional fields, if (_WIN32_IE >= 0x0400). + but (_WIN32_IE >= 0x0400) is expected. + note: + in x64 (64-bit): + { + (108 == REBARBANDINFO_V6_SIZE) + (112 == sizeof(REBARBANDINFO) // for (_WIN32_WINNT < 0x0600) + (128 == sizeof(REBARBANDINFO) // for (_WIN32_WINNT >= 0x0600) + there is difference in sizes, because REBARBANDINFO size was + not aligned for 8-bytes in (_WIN32_WINNT < 0x0600). + We hope that WinVista+ support support both (108 and 112) sizes. + But does WinXP-x64 support (108 == REBARBANDINFO_V6_SIZE)? + { + 96 LPARAM lParam; + 104 UINT cxHeader; + #if (_WIN32_WINNT >= 0x0600) + 108 RECT rcChevronLocation; + 124 UINT uChevronState; + #endif + } +*/ + +#if defined(_WIN32_WINNT) && (_WIN32_WINNT >= 0x0600) && defined(REBARBANDINFOA_V6_SIZE) + #define my_compatib_REBARBANDINFO_size REBARBANDINFO_V6_SIZE +#else + #define my_compatib_REBARBANDINFO_size sizeof(REBARBANDINFO) +#endif + + +bool CPanel::OnCreate(CREATESTRUCT * /* createStruct */) +{ + // _virtualMode = false; + // _sortIndex = 0; + _sortID = kpidName; + _ascending = true; + _lastFocusedIsList = true; + + DWORD style = WS_CHILD | WS_VISIBLE; // | WS_BORDER ; // | LVS_SHAREIMAGELISTS; // | LVS_SHOWSELALWAYS; + + style |= LVS_SHAREIMAGELISTS; + // style |= LVS_AUTOARRANGE; + style |= WS_CLIPCHILDREN; + style |= WS_CLIPSIBLINGS; + + const UInt32 kNumListModes = Z7_ARRAY_SIZE(kStyles); + if (_listViewMode >= kNumListModes) + _listViewMode = kNumListModes - 1; + + style |= kStyles[_listViewMode] + | WS_TABSTOP + | LVS_EDITLABELS; + if (_mySelectMode) + style |= LVS_SINGLESEL; + + /* + if (_virtualMode) + style |= LVS_OWNERDATA; + */ + + DWORD exStyle; + exStyle = WS_EX_CLIENTEDGE; + + if (!_listView.CreateEx(exStyle, style, 0, 0, 116, 260, + *this, (HMENU)(UINT_PTR)(_baseID + 1), g_hInstance, NULL)) + return false; + + _listView.SetUnicodeFormat(); + _listView._panel = this; + _listView.SetWindowProc(); + + _listView.SetImageList(Shell_Get_SysImageList_smallIcons(true), LVSIL_SMALL); + _listView.SetImageList(Shell_Get_SysImageList_smallIcons(false), LVSIL_NORMAL); + + // _exStyle |= LVS_EX_HEADERDRAGDROP; + // DWORD extendedStyle = _listView.GetExtendedListViewStyle(); + // extendedStyle |= _exStyle; + // _listView.SetExtendedListViewStyle(extendedStyle); + SetExtendedStyle(); + + _listView.Show(SW_SHOW); + _listView.InvalidateRect(NULL, true); + _listView.Update(); + + // Ensure that the common control DLL is loaded. + INITCOMMONCONTROLSEX icex; + + icex.dwSize = sizeof(INITCOMMONCONTROLSEX); + icex.dwICC = ICC_BAR_CLASSES; + InitCommonControlsEx(&icex); + + const TBBUTTON tbb[] = + { + // {0, 0, TBSTATE_ENABLED, BTNS_SEP, 0L, 0}, + {VIEW_PARENTFOLDER, kParentFolderID, TBSTATE_ENABLED, BTNS_BUTTON, { 0, 0 }, 0, 0 }, + // {0, 0, TBSTATE_ENABLED, BTNS_SEP, 0L, 0}, + // {VIEW_NEWFOLDER, kCreateFolderID, TBSTATE_ENABLED, BTNS_BUTTON, 0L, 0}, + }; + +#ifdef Z7_USE_DYN_ComCtl32Version + if (g_ComCtl32Version >= MAKELONG(71, 4)) +#endif + { + icex.dwSize = sizeof(INITCOMMONCONTROLSEX); + icex.dwICC = ICC_COOL_CLASSES | ICC_BAR_CLASSES; + InitCommonControlsEx(&icex); + + // if there is no CCS_NOPARENTALIGN, there is space of some pixels after rebar (Incorrect GetWindowRect ?) + + _headerReBar.Attach(::CreateWindowEx(WS_EX_TOOLWINDOW, + REBARCLASSNAME, + NULL, WS_VISIBLE | WS_BORDER | WS_CHILD | + WS_CLIPCHILDREN | WS_CLIPSIBLINGS + | CCS_NODIVIDER + | CCS_NOPARENTALIGN + | CCS_TOP + | RBS_VARHEIGHT + | RBS_BANDBORDERS + ,0,0,0,0, *this, NULL, g_hInstance, NULL)); + } + + DWORD toolbarStyle = WS_CHILD | WS_VISIBLE ; + if (_headerReBar) + { + toolbarStyle |= 0 + // | WS_CLIPCHILDREN + // | WS_CLIPSIBLINGS + + | TBSTYLE_TOOLTIPS + | CCS_NODIVIDER + | CCS_NORESIZE + | TBSTYLE_FLAT + ; + } + + _headerToolBar.Attach(::CreateToolbarEx ((*this), toolbarStyle, + _baseID + 2, 11, + (HINSTANCE)HINST_COMMCTRL, + IDB_VIEW_SMALL_COLOR, + (LPCTBBUTTON)&tbb, Z7_ARRAY_SIZE(tbb), + 0, 0, 0, 0, sizeof (TBBUTTON))); + + #ifndef UNDER_CE + // Load ComboBoxEx class + icex.dwSize = sizeof(INITCOMMONCONTROLSEX); + icex.dwICC = ICC_USEREX_CLASSES; + InitCommonControlsEx(&icex); + #endif + + _headerComboBox.CreateEx(0, + #ifdef UNDER_CE + WC_COMBOBOXW + #else + WC_COMBOBOXEXW + #endif + , NULL, + WS_BORDER | WS_VISIBLE |WS_CHILD | CBS_DROPDOWN | CBS_AUTOHSCROLL, + 0, 0, 100, 620, + (_headerReBar ? _headerToolBar : (HWND)*this), + (HMENU)(UINT_PTR)(_comboBoxID), + g_hInstance, NULL); + +#ifndef UNDER_CE + _headerComboBox.SetUnicodeFormat(true); + _headerComboBox.SetImageList(Shell_Get_SysImageList_smallIcons(true)); + _headerComboBox.SetExtendedStyle(CBES_EX_PATHWORDBREAKPROC, CBES_EX_PATHWORDBREAKPROC); + /* + _headerComboBox.SetUserDataLongPtr(LONG_PTR(&_headerComboBox)); + _headerComboBox._panel = this; + _headerComboBox._origWindowProc = + (WNDPROC)_headerComboBox.SetLongPtr(GWLP_WNDPROC, + LONG_PTR(ComboBoxSubclassProc)); + */ + _comboBoxEdit.Attach(_headerComboBox.GetEditControl()); + // _comboBoxEdit.SendMessage(CCM_SETUNICODEFORMAT, (WPARAM)(BOOL)TRUE, 0); + _comboBoxEdit.SetUserDataLongPtr(LONG_PTR(&_comboBoxEdit)); + _comboBoxEdit._panel = this; + #ifndef _UNICODE + if (g_IsNT) + _comboBoxEdit._origWindowProc = + (WNDPROC)_comboBoxEdit.SetLongPtrW(GWLP_WNDPROC, LONG_PTR(ComboBoxEditSubclassProc)); + else + #endif + _comboBoxEdit._origWindowProc = + (WNDPROC)_comboBoxEdit.SetLongPtr(GWLP_WNDPROC, LONG_PTR(ComboBoxEditSubclassProc)); +#endif + + if (_headerReBar) + { + REBARINFO rbi; + rbi.cbSize = sizeof(REBARINFO); // Required when using this struct. + rbi.fMask = 0; + rbi.himl = (HIMAGELIST)NULL; + _headerReBar.SetBarInfo(&rbi); + + // Send the TB_BUTTONSTRUCTSIZE message, which is required for + // backward compatibility. + // _headerToolBar.SendMessage(TB_BUTTONSTRUCTSIZE, (WPARAM)sizeof(TBBUTTON), 0); + SIZE size; + _headerToolBar.GetMaxSize(&size); + + REBARBANDINFO rbBand; + memset(&rbBand, 0, sizeof(rbBand)); + // rbBand.cbSize = sizeof(rbBand); // for debug + // rbBand.cbSize = REBARBANDINFO_V3_SIZE; // for debug + rbBand.cbSize = my_compatib_REBARBANDINFO_size; + rbBand.fMask = RBBIM_STYLE | RBBIM_CHILD | RBBIM_CHILDSIZE | RBBIM_SIZE; + rbBand.fStyle = RBBS_NOGRIPPER; + rbBand.cxMinChild = (UINT)size.cx; + rbBand.cyMinChild = (UINT)size.cy; + rbBand.cyChild = (UINT)size.cy; + rbBand.cx = (UINT)size.cx; + rbBand.hwndChild = _headerToolBar; + _headerReBar.InsertBand(-1, &rbBand); + + RECT rc; + ::GetWindowRect(_headerComboBox, &rc); + rbBand.cxMinChild = 30; + rbBand.cyMinChild = (UINT)(rc.bottom - rc.top); + rbBand.cx = 1000; + rbBand.hwndChild = _headerComboBox; + _headerReBar.InsertBand(-1, &rbBand); + // _headerReBar.MaximizeBand(1, false); + } + + _statusBar.Create(WS_CHILD | WS_VISIBLE, L"Status", (*this), _statusBarID); + // _statusBar2.Create(WS_CHILD | WS_VISIBLE, L"Status", (*this), _statusBarID + 1); + + const int sizes[] = {220, 320, 420, -1}; + _statusBar.SetParts(4, sizes); + // _statusBar2.SetParts(5, sizes); + + /* + RECT rect; + GetClientRect(&rect); + OnSize(0, RECT_SIZE_X(rect), RECT_SIZE_Y(rect)); + */ + + SetTimer(kTimerID, kTimerElapse); + + // InitListCtrl(); + RefreshListCtrl(); + + return true; +} + +void CPanel::OnDestroy() +{ + SaveListViewInfo(); + CWindow2::OnDestroy(); +} + +void CPanel::ChangeWindowSize(int xSize, int ySize) +{ + if (!(HWND)*this) + return; + int kHeaderSize; + int kStatusBarSize; + // int kStatusBar2Size; + RECT rect; + if (_headerReBar) + _headerReBar.GetWindowRect(&rect); + else + _headerToolBar.GetWindowRect(&rect); + + kHeaderSize = RECT_SIZE_Y(rect); + + _statusBar.GetWindowRect(&rect); + kStatusBarSize = RECT_SIZE_Y(rect); + + // _statusBar2.GetWindowRect(&rect); + // kStatusBar2Size = RECT_SIZE_Y(rect); + + int yListViewSize = MyMax(ySize - kHeaderSize - kStatusBarSize, 0); + const int kStartXPos = 32; + if (_headerReBar) + { + } + else + { + _headerToolBar.Move(0, 0, xSize, 0); + _headerComboBox.Move(kStartXPos, 2, + MyMax(xSize - kStartXPos - 10, kStartXPos), 0); + } + _listView.Move(0, kHeaderSize, xSize, yListViewSize); + _statusBar.Move(0, kHeaderSize + yListViewSize, xSize, kStatusBarSize); + // _statusBar2.MoveWindow(0, kHeaderSize + yListViewSize + kStatusBarSize, xSize, kStatusBar2Size); + // _statusBar.MoveWindow(0, 100, xSize, kStatusBarSize); + // _statusBar2.MoveWindow(0, 200, xSize, kStatusBar2Size); +} + +bool CPanel::OnSize(WPARAM /* wParam */, int xSize, int ySize) +{ + if (!(HWND)*this) + return true; + if (_headerReBar) + _headerReBar.Move(0, 0, xSize, 0); + ChangeWindowSize(xSize, ySize); + return true; +} + +bool CPanel::OnNotifyReBar(LPNMHDR header, LRESULT & /* result */) +{ + switch (header->code) + { + case RBN_HEIGHTCHANGE: + { + RECT rect; + GetWindowRect(&rect); + ChangeWindowSize(RECT_SIZE_X(rect), RECT_SIZE_Y(rect)); + return false; + } + } + return false; +} + +/* +UInt32 g_OnNotify = 0; +UInt32 g_LVIF_TEXT = 0; +UInt32 g_Time = 0; + +void Print_OnNotify(const char *name) +{ + char s[256]; + DWORD tim = GetTickCount(); + sprintf(s, + "Time = %7u ms, Notify = %9u, TEXT = %9u, %s", + tim - g_Time, + g_OnNotify, + g_LVIF_TEXT, + name); + g_Time = tim; + OutputDebugStringA(s); + g_OnNotify = 0; + g_LVIF_TEXT = 0; +} +*/ + +bool CPanel::OnNotify(UINT /* controlID */, LPNMHDR header, LRESULT &result) +{ + /* + g_OnNotify++; + + if (header->hwndFrom == _listView) + { + if (header->code == LVN_GETDISPINFOW) + { + LV_DISPINFOW *dispInfo = (LV_DISPINFOW *)header; + if ((dispInfo->item.mask & LVIF_TEXT)) + g_LVIF_TEXT++; + } + } + */ + + if (!_processNotify) + return false; + + if (header->hwndFrom == _headerComboBox) + return OnNotifyComboBox(header, result); + else if (header->hwndFrom == _headerReBar) + return OnNotifyReBar(header, result); + else if (header->hwndFrom == _listView) + return OnNotifyList(header, result); + else if (::GetParent(header->hwndFrom) == _listView) + { + // NMHDR:code is UINT + // NM_RCLICK is unsigned in windows sdk + // NM_RCLICK is int in MinGW + if (header->code == (UINT)NM_RCLICK) + return OnRightClick((MY_NMLISTVIEW_NMITEMACTIVATE *)header, result); + } + return false; +} + +bool CPanel::OnCommand(unsigned code, unsigned itemID, LPARAM lParam, LRESULT &result) +{ + if (itemID == kParentFolderID) + { + OpenParentFolder(); + result = 0; + return true; + } + /* + if (itemID == kCreateFolderID) + { + CreateFolder(); + result = 0; + return true; + } + */ + if (itemID == _comboBoxID) + { + if (OnComboBoxCommand(code, lParam, result)) + return true; + } + return CWindow2::OnCommand(code, itemID, lParam, result); +} + + + +/* +void CPanel::MessageBox_Info(LPCWSTR message, LPCWSTR caption) const + { ::MessageBoxW((HWND)*this, message, caption, MB_OK); } +void CPanel::MessageBox_Warning(LPCWSTR message) const + { ::MessageBoxW((HWND)*this, message, L"7-Zip", MB_OK | MB_ICONWARNING); } +*/ + +void CPanel::MessageBox_Error_Caption(LPCWSTR message, LPCWSTR caption) const + { ::MessageBoxW((HWND)*this, message, caption, MB_OK | MB_ICONSTOP); } + +void CPanel::MessageBox_Error(LPCWSTR message) const + { MessageBox_Error_Caption(message, L"7-Zip"); } + +static UString ErrorHResult_To_Message(HRESULT errorCode) +{ + if (errorCode == 0) + errorCode = E_FAIL; + return HResultToMessage(errorCode); +} + +void CPanel::MessageBox_Error_HRESULT_Caption(HRESULT errorCode, LPCWSTR caption) const +{ + MessageBox_Error_Caption(ErrorHResult_To_Message(errorCode), caption); +} + +void CPanel::MessageBox_Error_HRESULT(HRESULT errorCode) const + { MessageBox_Error_HRESULT_Caption(errorCode, L"7-Zip"); } + +void CPanel::MessageBox_Error_2Lines_Message_HRESULT(LPCWSTR message, HRESULT errorCode) const +{ + UString m = message; + m.Add_LF(); + m += ErrorHResult_To_Message(errorCode); + MessageBox_Error(m); +} + +void CPanel::MessageBox_LastError(LPCWSTR caption) const + { MessageBox_Error_HRESULT_Caption(GetLastError_noZero_HRESULT(), caption); } + +void CPanel::MessageBox_LastError() const + { MessageBox_LastError(L"7-Zip"); } + +void CPanel::MessageBox_Error_LangID(UINT resourceID) const + { MessageBox_Error(LangString(resourceID)); } + +void CPanel::MessageBox_Error_UnsupportOperation() const + { MessageBox_Error_LangID(IDS_OPERATION_IS_NOT_SUPPORTED); } + + + + +void CPanel::SetFocusToList() +{ + _listView.SetFocus(); + // SetCurrentPathText(); +} + +void CPanel::SetFocusToLastRememberedItem() +{ + if (_lastFocusedIsList) + SetFocusToList(); + else + _headerComboBox.SetFocus(); +} + +UString CPanel::GetFolderTypeID() const +{ + { + NCOM::CPropVariant prop; + if (_folder->GetFolderProperty(kpidType, &prop) == S_OK) + if (prop.vt == VT_BSTR) + return (const wchar_t *)prop.bstrVal; + } + return UString(); +} + +bool CPanel::IsFolderTypeEqTo(const char *s) const +{ + return StringsAreEqual_Ascii(GetFolderTypeID(), s); +} + +bool CPanel::IsRootFolder() const { return IsFolderTypeEqTo("RootFolder"); } +bool CPanel::IsFSFolder() const { return IsFolderTypeEqTo("FSFolder"); } +bool CPanel::IsFSDrivesFolder() const { return IsFolderTypeEqTo("FSDrives"); } +bool CPanel::IsAltStreamsFolder() const { return IsFolderTypeEqTo("AltStreamsFolder"); } +bool CPanel::IsArcFolder() const +{ + return GetFolderTypeID().IsPrefixedBy_Ascii_NoCase("7-Zip"); +} + +bool CPanel::IsHashFolder() const +{ + if (_folder) + { + NCOM::CPropVariant prop; + if (_folder->GetFolderProperty(kpidIsHash, &prop) == S_OK) + if (prop.vt == VT_BOOL) + return VARIANT_BOOLToBool(prop.boolVal); + } + return false; +} + +UString CPanel::GetFsPath() const +{ + if (IsFSDrivesFolder() && !IsDeviceDrivesPrefix() && !IsSuperDrivesPrefix()) + return UString(); + return _currentFolderPrefix; +} + +UString CPanel::GetDriveOrNetworkPrefix() const +{ + if (!IsFSFolder()) + return UString(); + UString drive = GetFsPath(); + drive.DeleteFrom(NFile::NName::GetRootPrefixSize(drive)); + return drive; +} + +void CPanel::SetListViewMode(UInt32 index) +{ + if (index >= 4) + return; + _listViewMode = index; + const LONG_PTR oldStyle = _listView.GetStyle(); + const DWORD newStyle = kStyles[index]; + + // DWORD tickCount1 = GetTickCount(); + if ((oldStyle & LVS_TYPEMASK) != (LONG_PTR)newStyle) + _listView.SetStyle((oldStyle & ~(LONG_PTR)(DWORD)LVS_TYPEMASK) | (LONG_PTR)newStyle); + // RefreshListCtrlSaveFocused(); + /* + DWORD tickCount2 = GetTickCount(); + char s[256]; + sprintf(s, "SetStyle = %5d", + tickCount2 - tickCount1 + ); + OutputDebugStringA(s); + */ + +} + +void CPanel::ChangeFlatMode() +{ + _flatMode = !_flatMode; + if (!_parentFolders.IsEmpty()) + _flatModeForArc = _flatMode; + else + _flatModeForDisk = _flatMode; + RefreshListCtrl_SaveFocused(); +} + +/* +void CPanel::Change_ShowNtfsStrems_Mode() +{ + _showNtfsStrems_Mode = !_showNtfsStrems_Mode; + if (!_parentFolders.IsEmpty()) + _showNtfsStrems_ModeForArc = _showNtfsStrems_Mode; + else + _showNtfsStrems_ModeForDisk = _showNtfsStrems_Mode; + RefreshListCtrlSaveFocused(); +} +*/ + +void CPanel::Post_Refresh_StatusBar() +{ + if (_processStatusBar) + PostMsg(kRefresh_StatusBar); +} + +void CPanel::AddToArchive() +{ + if (!Is_IO_FS_Folder()) + { + MessageBox_Error_UnsupportOperation(); + return; + } + CRecordVector indices; + Get_ItemIndices_Operated(indices); + if (indices.Size() == 0) + { + MessageBox_Error_LangID(IDS_SELECT_FILES); + return; + } + UString destCurDirPrefix = GetFsPath(); + if (IsFSDrivesFolder()) + destCurDirPrefix = ROOT_FS_FOLDER; + UStringVector names; + GetFilePaths(indices, names); + UString baseName; + const UString arcName = CreateArchiveName(names, + false, // isHash + NULL, // CFileInfo *fi + baseName); + const HRESULT res = CompressFiles(destCurDirPrefix, arcName, L"", + true, // addExtension + names, + false, // email + true, // showDialog + false); // waitFinish + if (res != S_OK) + { + if (destCurDirPrefix.Len() >= MAX_PATH) + MessageBox_Error_LangID(IDS_MESSAGE_UNSUPPORTED_OPERATION_FOR_LONG_PATH_FOLDER); + } + // KillSelection(); +} + +// function from ContextMenu.cpp +UString GetSubFolderNameForExtract(const UString &arcPath); + +static UString GetSubFolderNameForExtract2(const UString &arcPath) +{ + int slashPos = arcPath.ReverseFind_PathSepar(); + UString s; + UString name = arcPath; + if (slashPos >= 0) + { + s = arcPath.Left((unsigned)(slashPos + 1)); + name = arcPath.Ptr((unsigned)(slashPos + 1)); + } + s += GetSubFolderNameForExtract(name); + return s; +} + + +int CPanel::FindDir_InOperatedList(const CRecordVector &operatedIndices) const +{ + const bool *isDirVector = _isDirVector.ConstData(); + const UInt32 *indices = operatedIndices.ConstData(); + const unsigned numItems = operatedIndices.Size(); + for (unsigned i = 0; i < numItems; i++) + if (isDirVector[indices[i]]) + return (int)i; + return -1; +} + + +void CPanel::GetFilePaths(const CRecordVector &operatedIndices, UStringVector &paths) const +{ + paths.ClearAndReserve(operatedIndices.Size()); + UString path = GetFsPath(); + const unsigned prefixLen = path.Len(); + const UInt32 *indices = operatedIndices.ConstData(); + const unsigned numItems = operatedIndices.Size(); + // for (unsigned y = 0; y < 10000; y++, paths.Clear()) + for (unsigned i = 0; i < numItems; i++) + { + path.DeleteFrom(prefixLen); + Add_ItemRelPath2_To_String(indices[i], path); + // ODS_U(path) + paths.AddInReserved(path); + } +} + + +void CPanel::ExtractArchives() +{ + if (!_parentFolders.IsEmpty()) + { + _panelCallback->OnCopy(false, false); + return; + } + CRecordVector indices; + Get_ItemIndices_Operated(indices); + if (indices.IsEmpty() || FindDir_InOperatedList(indices) != -1) + { + MessageBox_Error_LangID(IDS_SELECT_FILES); + return; + } + UStringVector paths; + GetFilePaths(indices, paths); + UString outFolder = GetFsPath(); + if (indices.Size() == 1) + outFolder += GetSubFolderNameForExtract2(GetItemRelPath(indices[0])); + else + outFolder.Add_Char('*'); + outFolder.Add_PathSepar(); + + CContextMenuInfo ci; + ci.Load(); + + ::ExtractArchives(paths, outFolder + , true // showDialog + , false // elimDup + , ci.WriteZone + ); +} + +/* +static void AddValuePair(UINT resourceID, UInt64 value, UString &s) +{ + AddLangString(s, resourceID); + char sz[32]; + s += ": "; + ConvertUInt64ToString(value, sz); + s += sz; + s.Add_LF(); +} + +// now we don't need CThreadTest, since now we call CopyTo for "test command + +class CThreadTest: public CProgressThreadVirt +{ + HRESULT ProcessVirt(); +public: + CRecordVector Indices; + CExtractCallbackImp *ExtractCallbackSpec; + CMyComPtr ExtractCallback; + CMyComPtr ArchiveFolder; +}; + +HRESULT CThreadTest::ProcessVirt() +{ + RINOK(ArchiveFolder->Extract(&Indices[0], Indices.Size(), + true, // includeAltStreams + false, // replaceAltStreamColon + NExtract::NPathMode::kFullPathnames, + NExtract::NOverwriteMode::kAskBefore, + NULL, // path + BoolToInt(true), // testMode + ExtractCallback)); + if (ExtractCallbackSpec->IsOK()) + { + UString s; + AddValuePair(IDS_PROP_FOLDERS, ExtractCallbackSpec->NumFolders, s); + AddValuePair(IDS_PROP_FILES, ExtractCallbackSpec->NumFiles, s); + // AddValuePair(IDS_PROP_SIZE, ExtractCallbackSpec->UnpackSize, s); + // AddSizePair(IDS_COMPRESSED_COLON, Stat.PackSize, s); + s.Add_LF(); + AddLangString(s, IDS_MESSAGE_NO_ERRORS); + FinalMessage.OkMessage.Message = s; + } + return S_OK; +} + +static void AddSizePair(UInt32 langID, UInt64 value, UString &s) +{ + char sz[32]; + AddLangString(s, langID); + s += L' '; + ConvertUInt64ToString(value, sz); + s += sz; + ConvertUInt64ToString(value >> 20, sz); + s += " ("; + s += sz; + s += " MB)"; + s.Add_LF(); +} +*/ + +void CPanel::TestArchives() +{ + CRecordVector indices; + Get_ItemIndices_OperSmart(indices); + CMyComPtr archiveFolder; + _folder.QueryInterface(IID_IArchiveFolder, &archiveFolder); + if (archiveFolder) + { + CCopyToOptions options; + options.streamMode = true; + options.showErrorMessages = true; + options.testMode = true; + + UStringVector messages; + HRESULT res = CopyTo(options, indices, &messages); + if (res != S_OK) + { + if (res != E_ABORT) + MessageBox_Error_HRESULT(res); + } + return; + + /* + { + CThreadTest extracter; + + extracter.ArchiveFolder = archiveFolder; + extracter.ExtractCallbackSpec = new CExtractCallbackImp; + extracter.ExtractCallback = extracter.ExtractCallbackSpec; + extracter.ExtractCallbackSpec->ProgressDialog = &extracter.ProgressDialog; + if (!_parentFolders.IsEmpty()) + { + const CFolderLink &fl = _parentFolders.Back(); + extracter.ExtractCallbackSpec->PasswordIsDefined = fl.UsePassword; + extracter.ExtractCallbackSpec->Password = fl.Password; + } + + if (indices.IsEmpty()) + return; + + extracter.Indices = indices; + + const UString title = LangString(IDS_PROGRESS_TESTING); + + extracter.ProgressDialog.CompressingMode = false; + extracter.ProgressDialog.MainWindow = GetParent(); + extracter.ProgressDialog.MainTitle = "7-Zip"; // LangString(IDS_APP_TITLE); + extracter.ProgressDialog.MainAddTitle = title + L' '; + + extracter.ExtractCallbackSpec->OverwriteMode = NExtract::NOverwriteMode::kAskBefore; + extracter.ExtractCallbackSpec->Init(); + + if (extracter.Create(title, GetParent()) != S_OK) + return; + + } + RefreshTitleAlways(); + return; + */ + } + + if (!IsFSFolder()) + { + MessageBox_Error_UnsupportOperation(); + return; + } + UStringVector paths; + GetFilePaths(indices, paths); + if (paths.IsEmpty()) + { + MessageBox_Error_LangID(IDS_SELECT_FILES); + return; + } + ::TestArchives(paths); +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/Panel.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/Panel.h new file mode 100644 index 00000000..9ef09265 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/Panel.h @@ -0,0 +1,1020 @@ +// Panel.h + +#ifndef ZIP7_INC_PANEL_H +#define ZIP7_INC_PANEL_H + +#include "../../../Common/MyWindows.h" + +#if defined(__MINGW32__) || defined(__MINGW64__) +#include +#else +#include +#endif + +#include "../../../../C/Alloc.h" + +#include "../../../Common/Defs.h" +#include "../../../Common/MyCom.h" + +#include "../../../Windows/DLL.h" +#include "../../../Windows/FileDir.h" +#include "../../../Windows/FileFind.h" +#include "../../../Windows/FileName.h" +#include "../../../Windows/Handle.h" +#include "../../../Windows/PropVariantConv.h" +#include "../../../Windows/Synchronization.h" + +#include "../../../Windows/Control/ComboBox.h" +#include "../../../Windows/Control/Edit.h" +#include "../../../Windows/Control/ListView.h" +#include "../../../Windows/Control/ReBar.h" +#include "../../../Windows/Control/Static.h" +#include "../../../Windows/Control/StatusBar.h" +#include "../../../Windows/Control/ToolBar.h" +#include "../../../Windows/Control/Window2.h" + +#include "../../Archive/IArchive.h" + +#include "ExtractCallback.h" + +#include "AppState.h" +#include "IFolder.h" +#include "MyCom2.h" +#include "ProgressDialog2.h" +#include "SysIconUtils.h" + +#ifdef UNDER_CE +#define NON_CE_VAR(_v_) +#else +#define NON_CE_VAR(_v_) _v_ +#endif + +const int kParentFolderID = 100; + +const unsigned kParentIndex = (unsigned)(int)-1; +const UInt32 kParentIndex_UInt32 = (UInt32)(Int32)kParentIndex; + +#if !defined(_WIN32) || defined(UNDER_CE) +#define ROOT_FS_FOLDER L"\\" +#else +#define ROOT_FS_FOLDER L"C:\\" +#endif + +#if !defined(Z7_WIN32_WINNT_MIN) || Z7_WIN32_WINNT_MIN < 0x0500 // < win2000 +#define Z7_USE_DYN_ComCtl32Version +extern DWORD g_ComCtl32Version; +#endif + +Z7_PURE_INTERFACES_BEGIN + +DECLARE_INTERFACE(CPanelCallback) +{ + virtual void OnTab() = 0; + virtual void SetFocusToPath(unsigned index) = 0; + virtual void OnCopy(bool move, bool copyToSame) = 0; + virtual void OnSetSameFolder() = 0; + virtual void OnSetSubFolder() = 0; + virtual void PanelWasFocused() = 0; + virtual void DragBegin() = 0; + virtual void DragEnd() = 0; + virtual void RefreshTitle(bool always) = 0; +}; +Z7_PURE_INTERFACES_END + +void PanelCopyItems(); + + +struct CPropColumn +{ + int Order; + PROPID ID; + VARTYPE Type; + bool IsVisible; + bool IsRawProp; + UInt32 Width; + UString Name; + + bool IsEqualTo(const CPropColumn &a) const + { + return Order == a.Order + && ID == a.ID + && Type == a.Type + && IsVisible == a.IsVisible + && IsRawProp == a.IsRawProp + && Width == a.Width + && Name == a.Name; + } + + int Compare(const CPropColumn &a) const { return MyCompare(Order, a.Order); } + + int Compare_NameFirst(const CPropColumn &a) const + { + if (ID == kpidName) + { + if (a.ID != kpidName) + return -1; + } + else if (a.ID == kpidName) + return 1; + return MyCompare(Order, a.Order); + } +}; + + +class CPropColumns: public CObjectVector +{ +public: + int FindItem_for_PropID(PROPID id) const + { + FOR_VECTOR (i, (*this)) + if ((*this)[i].ID == id) + return (int)i; + return -1; + } + + bool IsEqualTo(const CPropColumns &props) const + { + if (Size() != props.Size()) + return false; + FOR_VECTOR (i, (*this)) + if (!(*this)[i].IsEqualTo(props[i])) + return false; + return true; + } +}; + + +struct CTempFileInfo +{ + UInt32 FileIndex; // index of file in folder + bool NeedDelete; + UString RelPath; // Relative path of file from Folder + FString FolderPath; + FString FilePath; + NWindows::NFile::NFind::CFileInfo FileInfo; + + CTempFileInfo(): FileIndex((UInt32)(Int32)-1), NeedDelete(false) {} + void DeleteDirAndFile() const + { + if (NeedDelete) + { + NWindows::NFile::NDir::DeleteFileAlways(FilePath); + NWindows::NFile::NDir::RemoveDir(FolderPath); + } + } + bool WasChanged_from_TempFileInfo(const NWindows::NFile::NFind::CFileInfo &newFileInfo) const + { + return newFileInfo.Size != FileInfo.Size || + CompareFileTime(&newFileInfo.MTime, &FileInfo.MTime) != 0; + } +}; + + +struct CFolderLink: public CTempFileInfo +{ + bool IsVirtual; // == true (if archive was open via IInStream): + // archive was open from another archive, + // archive size meets the size conditions derived from g_RAM_Size. + // VirtFileSystem was used + // archive was fully extracted to memory. + bool UsePassword; + NWindows::NDLL::CLibrary Library; + CMyComPtr ParentFolder; // can be NULL, if parent is FS folder (in _parentFolders[0]) + UString ParentFolderPath; // including tail slash (doesn't include paths parts of parent in next level) + UString Password; + UString VirtualPath; // without tail slash + CByteBuffer ZoneBuf; // ZoneBuf for virtaul stream (IsVirtual) + + CFolderLink(): IsVirtual(false), UsePassword(false) {} + bool WasChanged_from_FolderLink(const NWindows::NFile::NFind::CFileInfo &newFileInfo) const + { + // we call it, if we have two real files. + // if archive was virtual, it means that we have updated that virtual to real file. + return IsVirtual || CTempFileInfo::WasChanged_from_TempFileInfo(newFileInfo); + } +}; + + +enum MyMessages +{ + // we can use WM_USER, since we have defined new window class. + // so we don't need WM_APP. + kShiftSelectMessage = WM_USER + 1, + kReLoadMessage, + kSetFocusToListView, + kOpenItemChanged, + kRefresh_StatusBar + #ifdef UNDER_CE + , kRefresh_HeaderComboBox + #endif +}; + +UString GetFolderPath(IFolderFolder *folder); + +class CPanel; + +class CMyListView Z7_final: public NWindows::NControl::CListView2 +{ + // ~CMyListView() ZIP7_eq_delete; + // CMyListView() ZIP7_eq_delete; +public: + // CMyListView() {} + // ~CMyListView() Z7_DESTRUCTOR_override {} // change it + CPanel *_panel; + LRESULT OnMessage(UINT message, WPARAM wParam, LPARAM lParam) Z7_override; +}; + +/* +class CMyComboBox: public NWindows::NControl::CComboBoxEx +{ +public: + WNDPROC _origWindowProc; + CPanel *_panel; + LRESULT OnMessage(UINT message, WPARAM wParam, LPARAM lParam); +}; +*/ +class CMyComboBoxEdit: public NWindows::NControl::CEdit +{ +public: + WNDPROC _origWindowProc; + CPanel *_panel; + LRESULT OnMessage(UINT message, WPARAM wParam, LPARAM lParam); +}; + +struct CSelectedState +{ + int FocusedItem; + bool SelectFocused; + bool FocusedName_Defined; + bool CalledFromTimer; + UString FocusedName; + UStringVector SelectedNames; + + CSelectedState(): + FocusedItem(-1), + SelectFocused(true), + FocusedName_Defined(false), + CalledFromTimer(false) + {} +}; + +#ifdef UNDER_CE +#define MY_NMLISTVIEW_NMITEMACTIVATE NMLISTVIEW +#else +#define MY_NMLISTVIEW_NMITEMACTIVATE NMITEMACTIVATE +#endif + +struct CCopyToOptions +{ + bool streamMode; + bool moveMode; + bool testMode; + bool includeAltStreams; + bool replaceAltStreamChars; + bool showErrorMessages; + + bool NeedRegistryZone; + NExtract::NZoneIdMode::EEnum ZoneIdMode; + CByteBuffer ZoneBuf; + + UString folder; + + UStringVector hashMethods; + + CVirtFileSystem *VirtFileSystemSpec; + // ISequentialOutStream *VirtFileSystem; + + CCopyToOptions(): + streamMode(false), + moveMode(false), + testMode(false), + includeAltStreams(true), + replaceAltStreamChars(false), + showErrorMessages(false), + NeedRegistryZone(true), + ZoneIdMode(NExtract::NZoneIdMode::kNone), + VirtFileSystemSpec(NULL) + // , VirtFileSystem(NULL) + {} +}; + + + +struct COpenResult +{ + // bool needOpenArc; + // out: + bool ArchiveIsOpened; + bool Encrypted; + UString ErrorMessage; + + COpenResult(): + // needOpenArc(false), + ArchiveIsOpened(false), Encrypted(false) {} +}; + + + + +class CPanel Z7_final: public NWindows::NControl::CWindow2 +{ + bool _thereAre_ListView_Items; + // bool _virtualMode; + bool _enableItemChangeNotify; + bool _thereAreDeletedItems; + bool _markDeletedItems; + bool _dontShowMode; + bool _needSaveInfo; + +public: + bool PanelCreated; + bool _mySelectMode; + bool _showDots; + bool _showRealFileIcons; + bool _flatMode; + bool _flatModeForArc; + bool _flatModeForDisk; + bool _selectionIsDefined; + // bool _showNtfsStrems_Mode; + // bool _showNtfsStrems_ModeForDisk; + // bool _showNtfsStrems_ModeForArc; + + bool _selectMark; + bool _lastFocusedIsList; + + bool _processTimer; + bool _processNotify; + bool _processStatusBar; + +public: + bool _ascending; + PROPID _sortID; + // int _sortIndex; + Int32 _isRawSortProp; + + CMyListView _listView; + CPanelCallback *_panelCallback; + +private: + + // CExtToIconMap _extToIconMap; + UINT _baseID; + unsigned _comboBoxID; + UINT _statusBarID; + +public: + DWORD _exStyle; + // CUIntVector _realIndices; + int _timestampLevel; + UInt32 _listViewMode; + int _xSize; +private: + int _startGroupSelect; + int _prevFocusedItem; + + CAppState *_appState; + + virtual bool OnCommand(unsigned code, unsigned itemID, LPARAM lParam, LRESULT &result) Z7_override; + virtual LRESULT OnMessage(UINT message, WPARAM wParam, LPARAM lParam) Z7_override; + virtual bool OnCreate(CREATESTRUCT *createStruct) Z7_override; + virtual bool OnSize(WPARAM wParam, int xSize, int ySize) Z7_override; + virtual void OnDestroy() Z7_override; + virtual bool OnNotify(UINT controlID, LPNMHDR lParam, LRESULT &result) Z7_override; + + void AddComboBoxItem(const UString &name, int iconIndex, unsigned indent, bool addToList); + + bool OnComboBoxCommand(UINT code, LPARAM param, LRESULT &result); + + #ifndef UNDER_CE + + LRESULT OnNotifyComboBoxEnter(const UString &s); + bool OnNotifyComboBoxEndEdit(PNMCBEENDEDITW info, LRESULT &result); + #ifndef _UNICODE + bool OnNotifyComboBoxEndEdit(PNMCBEENDEDIT info, LRESULT &result); + #endif + + #endif + + bool OnNotifyReBar(LPNMHDR lParam, LRESULT &result); + bool OnNotifyComboBox(LPNMHDR lParam, LRESULT &result); + void OnItemChanged(NMLISTVIEW *item); + void OnNotifyActivateItems(); + bool OnNotifyList(LPNMHDR lParam, LRESULT &result); + void OnDrag(LPNMLISTVIEW nmListView, bool isRightButton = false); + bool OnKeyDown(LPNMLVKEYDOWN keyDownInfo, LRESULT &result); + BOOL OnBeginLabelEdit(LV_DISPINFOW * lpnmh); + BOOL OnEndLabelEdit(LV_DISPINFOW * lpnmh); + void OnColumnClick(LPNMLISTVIEW info); + bool OnCustomDraw(LPNMLVCUSTOMDRAW lplvcd, LRESULT &result); + + + void ChangeWindowSize(int xSize, int ySize); + HRESULT InitColumns(); + void DeleteColumn(unsigned index); + void AddColumn(const CPropColumn &prop); + + void SetFocusedSelectedItem(int index, bool select); + + void OnShiftSelectMessage(); + void OnArrowWithShift(); + + void OnInsert(); + // void OnUpWithShift(); + // void OnDownWithShift(); + // UString GetFileType(UInt32 index); + LRESULT SetItemText(LVITEMW &item); + // CRecordVector m_ColumnsPropIDs; + +public: + HWND _mainWindow; + + NWindows::NControl::CReBar _headerReBar; + NWindows::NControl::CToolBar _headerToolBar; + NWindows::NControl:: + #ifdef UNDER_CE + CComboBox + #else + CComboBoxEx + #endif + _headerComboBox; + UStringVector ComboBoxPaths; + // CMyComboBox _headerComboBox; + CMyComboBoxEdit _comboBoxEdit; + NWindows::NControl::CStatusBar _statusBar; + // NWindows::NControl::CStatusBar _statusBar2; + + CBoolVector _selectedStatusVector; + CSelectedState _selectedState; + + UString _currentFolderPrefix; + + CObjectVector _parentFolders; + NWindows::NDLL::CLibrary _library; + + CMyComPtr _folder; + CBoolVector _isDirVector; + CMyComPtr _folderCompare; + CMyComPtr _folderGetItemName; + CMyComPtr _folderRawProps; + CMyComPtr _folderAltStreams; + CMyComPtr _folderOperations; + + // for drag and drop highliting + int m_DropHighlighted_SelectionIndex; + // int m_SubFolderIndex; // realIndex of item in m_Panel list (if drop cursor to that item) + UString m_DropHighlighted_SubFolderName; // name of folder in m_Panel list (if drop cursor to that folder) + + // CMyComPtr _folderGetSystemIconIndex; + UStringVector _fastFolders; + + UString _typeIDString; + CListViewInfo _listViewInfo; + + CPropColumns _columns; + CPropColumns _visibleColumns; + + CMyComPtr _sevenZipContextMenu; + CMyComPtr _systemContextMenu; + + void UpdateSelection(); + void SelectSpec(bool selectMode); + void SelectByType(bool selectMode); + void SelectAll(bool selectMode); + void InvertSelection(); + + void RedrawListItems() + { + _listView.RedrawAllItems(); + } + void DeleteListItems() + { + if (_thereAre_ListView_Items) + { + const bool b = _enableItemChangeNotify; + _enableItemChangeNotify = false; + _listView.DeleteAllItems(); + _thereAre_ListView_Items = false; + _enableItemChangeNotify = b; + } + } + + // void SysIconsWereChanged() { _extToIconMap.Clear(); } + + void DeleteItems(bool toRecycleBin); + void CreateFolder(); + void CreateFile(); + bool CorrectFsPath(const UString &path, UString &result); + // bool IsPathForPlugin(const UString &path); + + + HWND GetParent() const; + + UInt32 GetRealIndex(const LVITEMW &item) const + { + /* + if (_virtualMode) + return _realIndices[item.iItem]; + */ + return (UInt32)item.lParam; + } + + unsigned GetRealItemIndex(int indexInListView) const + { + /* + if (_virtualMode) + return indexInListView; + */ + LPARAM param; + if (!_listView.GetItemParam((unsigned)indexInListView, param)) + throw 1; + return (unsigned)param; + } + + void ReleaseFolder(); + void SetNewFolder(IFolderFolder *newFolder); + void GetSelectedNames(UStringVector &selectedNames); + void SaveSelectedState(CSelectedState &s); + HRESULT RefreshListCtrl(const CSelectedState &s); + HRESULT RefreshListCtrl_SaveFocused(bool onTimer = false); + + // UInt32 GetItem_Attrib(UInt32 itemIndex) const; + + bool GetItem_BoolProp(UInt32 itemIndex, PROPID propID) const; + + bool IsItem_Deleted(unsigned itemIndex) const; + bool IsItem_Folder(unsigned itemIndex) const; + bool IsItem_AltStream(unsigned itemIndex) const; + + UString GetItemName(unsigned itemIndex) const; + UString GetItemName_for_Copy(unsigned itemIndex) const; + void GetItemName(unsigned itemIndex, UString &s) const; + UString GetItemPrefix(unsigned itemIndex) const; + UString GetItemRelPath(unsigned itemIndex) const; + UString GetItemRelPath2(unsigned itemIndex) const; + + void Add_ItemRelPath2_To_String(unsigned itemIndex, UString &s) const; + + UString GetItemFullPath(unsigned itemIndex) const; + UInt64 GetItem_UInt64Prop(unsigned itemIndex, PROPID propID) const; + UInt64 GetItemSize(unsigned itemIndex) const; + + //////////////////////// + // PanelFolderChange.cpp + + void SetToRootFolder(); + HRESULT BindToPath(const UString &fullPath, const UString &arcFormat, COpenResult &openRes); // can be prefix + HRESULT BindToPathAndRefresh(const UString &path); + void OpenDrivesFolder(); + + void SetBookmark(unsigned index); + void OpenBookmark(unsigned index); + + void LoadFullPath(); + void LoadFullPathAndShow(); + void FoldersHistory(); + void OpenParentFolder(); + void CloseOneLevel(); + void CloseOpenFolders(); + void OpenRootFolder(); + + UString GetParentDirPrefix() const; + + HRESULT Create(HWND mainWindow, HWND parentWindow, + UINT id, + const UString ¤tFolderPrefix, + const UString &arcFormat, + CPanelCallback *panelCallback, + CAppState *appState, + bool needOpenArc, + COpenResult &openRes); + + void SetFocusToList(); + void SetFocusToLastRememberedItem(); + + + void SaveListViewInfo(); + + CPanel() : + _thereAre_ListView_Items(false), + // _virtualMode(false), + _enableItemChangeNotify(true), + _thereAreDeletedItems(false), + _markDeletedItems(true), + _dontShowMode(false), + _needSaveInfo(false), + + PanelCreated(false), + _mySelectMode(false), + _showDots(false), + _showRealFileIcons(false), + _flatMode(false), + _flatModeForArc(false), + _flatModeForDisk(false), + _selectionIsDefined(false), + // _showNtfsStrems_Mode(false), + // _showNtfsStrems_ModeForDisk(false), + // _showNtfsStrems_ModeForArc(false), + + _exStyle(0), + _timestampLevel(kTimestampPrintLevel_MIN), + _listViewMode(3), + _xSize(300), + _startGroupSelect(0), + m_DropHighlighted_SelectionIndex(-1) + {} + + ~CPanel() Z7_DESTRUCTOR_override; + + void ReleasePanel(); + + void SetExtendedStyle() + { + if (_listView) + _listView.SetExtendedListViewStyle(_exStyle); + } + + void SetSortRawStatus(); + void OnLeftClick(MY_NMLISTVIEW_NMITEMACTIVATE *itemActivate); + bool OnRightClick(MY_NMLISTVIEW_NMITEMACTIVATE *itemActivate, LRESULT &result); + void ShowColumnsContextMenu(int x, int y); + + void OnTimer(); + void OnReload(bool onTimer = false); + bool OnContextMenu(HANDLE windowHandle, int xPos, int yPos); + + HRESULT CreateShellContextMenu( + const CRecordVector &operatedIndices, + CMyComPtr &systemContextMenu); + + void CreateSystemMenu(HMENU menu, + bool showExtendedVerbs, + const CRecordVector &operatedIndices, + CMyComPtr &systemContextMenu); + + void CreateSevenZipMenu(HMENU menu, + bool showExtendedVerbs, + const CRecordVector &operatedIndices, + int firstDirIndex, + CMyComPtr &sevenZipContextMenu); + + void CreateFileMenu(HMENU menu, + CMyComPtr &sevenZipContextMenu, + CMyComPtr &systemContextMenu, + bool programMenu); + + void CreateFileMenu(HMENU menu); + bool InvokePluginCommand(unsigned id); + bool InvokePluginCommand(unsigned id, IContextMenu *sevenZipContextMenu, + IContextMenu *systemContextMenu); + + void InvokeSystemCommand(const char *command); + void Properties(); + void EditCut(); + void EditCopy(); + void EditPaste(); + + + // void SortItems(int index); + void SortItemsWithPropID(PROPID propID); + + void Get_ItemIndices_Selected(CRecordVector &indices) const; + void Get_ItemIndices_Operated(CRecordVector &indices) const; + void Get_ItemIndices_All(CRecordVector &indices) const; + void Get_ItemIndices_OperSmart(CRecordVector &indices) const; + // void GetOperatedListViewIndices(CRecordVector &indices) const; + void KillSelection(); + + UString GetFolderTypeID() const; + + bool IsFolderTypeEqTo(const char *s) const; + bool IsRootFolder() const; + bool IsFSFolder() const; + bool IsFSDrivesFolder() const; + bool IsAltStreamsFolder() const; + bool IsArcFolder() const; + bool IsHashFolder() const; + + /* + c:\Dir + Computer\ + \\?\ + \\.\ + */ + bool Is_IO_FS_Folder() const + { + return IsFSFolder() || IsFSDrivesFolder() || IsAltStreamsFolder(); + } + + bool Is_Slow_Icon_Folder() const + { + return IsFSFolder() || IsAltStreamsFolder(); + } + + // bool IsFsOrDrivesFolder() const { return IsFSFolder() || IsFSDrivesFolder(); } + bool IsDeviceDrivesPrefix() const { return _currentFolderPrefix.IsEqualTo("\\\\.\\"); } + bool IsSuperDrivesPrefix() const { return _currentFolderPrefix.IsEqualTo("\\\\?\\"); } + + /* + c:\Dir + Computer\ + \\?\ + */ + bool IsFsOrPureDrivesFolder() const { return IsFSFolder() || (IsFSDrivesFolder() && !IsDeviceDrivesPrefix()); } + + /* + c:\Dir + Computer\ + \\?\ + \\SERVER\ + */ + bool IsFolder_with_FsItems() const + { + if (IsFsOrPureDrivesFolder()) + return true; + #if defined(_WIN32) && !defined(UNDER_CE) + FString prefix = us2fs(GetFsPath()); + return (prefix.Len() == NWindows::NFile::NName::GetNetworkServerPrefixSize(prefix)); + #else + return false; + #endif + } + + UString GetFsPath() const; + UString GetDriveOrNetworkPrefix() const; + + bool DoesItSupportOperations() const { return _folderOperations != NULL; } + bool IsThereReadOnlyFolder() const; + bool CheckBeforeUpdate(UINT resourceID); + + void Disable_Processing_Timer_Notify_StatusBar() + { + _processTimer = false; + _processNotify = false; + _processStatusBar = false; + } + + class CDisableTimerProcessing + { + Z7_CLASS_NO_COPY(CDisableTimerProcessing) + + bool _processTimer; + CPanel &_panel; + + public: + + CDisableTimerProcessing(CPanel &panel): _panel(panel) { Disable(); } + ~CDisableTimerProcessing() { Restore(); } + void Disable() + { + _processTimer = _panel._processTimer; + _panel._processTimer = false; + } + void Restore() + { + _panel._processTimer = _processTimer; + } + }; + + class CDisableTimerProcessing2 + { + Z7_CLASS_NO_COPY(CDisableTimerProcessing2) + + bool _processTimer; + CPanel *_panel; + + public: + + CDisableTimerProcessing2(CPanel *panel): _processTimer(true), _panel(panel) { Disable(); } + ~CDisableTimerProcessing2() { Restore(); } + void Disable() + { + if (_panel) + { + _processTimer = _panel->_processTimer; + _panel->_processTimer = false; + } + } + void Restore() + { + if (_panel) + { + _panel->_processTimer = _processTimer; + _panel = NULL; + } + } + }; + + class CDisableNotify + { + Z7_CLASS_NO_COPY(CDisableNotify) + + bool _processNotify; + bool _processStatusBar; + + CPanel &_panel; + + public: + + CDisableNotify(CPanel &panel): _panel(panel) { Disable(); } + ~CDisableNotify() { Restore(); } + void Disable() + { + _processNotify = _panel._processNotify; + _processStatusBar = _panel._processStatusBar; + _panel._processNotify = false; + _panel._processStatusBar = false; + } + void SetMemMode_Enable() + { + _processNotify = true; + _processStatusBar = true; + } + void Restore() + { + _panel._processNotify = _processNotify; + _panel._processStatusBar = _processStatusBar; + } + }; + + void InvalidateList() { _listView.InvalidateRect(NULL, true); } + + HRESULT RefreshListCtrl(); + + + // void MessageBox_Info(LPCWSTR message, LPCWSTR caption) const; + // void MessageBox_Warning(LPCWSTR message) const; + void MessageBox_Error_Caption(LPCWSTR message, LPCWSTR caption) const; + void MessageBox_Error(LPCWSTR message) const; + void MessageBox_Error_HRESULT_Caption(HRESULT errorCode, LPCWSTR caption) const; + void MessageBox_Error_HRESULT(HRESULT errorCode) const; + void MessageBox_Error_2Lines_Message_HRESULT(LPCWSTR message, HRESULT errorCode) const; + void MessageBox_LastError(LPCWSTR caption) const; + void MessageBox_LastError() const; + void MessageBox_Error_LangID(UINT resourceID) const; + void MessageBox_Error_UnsupportOperation() const; + // void MessageBoxErrorForUpdate(HRESULT errorCode, UINT resourceID); + + + void OpenAltStreams(); + + void OpenFocusedItemAsInternal(const wchar_t *type = NULL); + void OpenSelectedItems(bool internal); + + void OpenFolderExternal(unsigned index); + + void OpenFolder(unsigned index); + HRESULT OpenParentArchiveFolder(); + + HRESULT OpenAsArc(IInStream *inStream, + const CTempFileInfo &tempFileInfo, + const UString &virtualFilePath, + const UString &arcFormat, + COpenResult &openRes); + + HRESULT OpenAsArc_Msg(IInStream *inStream, + const CTempFileInfo &tempFileInfo, + const UString &virtualFilePath, + const UString &arcFormat + // , bool showErrorMessage + ); + + HRESULT OpenAsArc_Name(const UString &relPath, const UString &arcFormat + // , bool showErrorMessage + ); + HRESULT OpenAsArc_Index(unsigned index, const wchar_t *type /* = NULL */ + // , bool showErrorMessage + ); + + void OpenItemInArchive(unsigned index, bool tryInternal, bool tryExternal, + bool editMode, bool useEditor, const wchar_t *type = NULL); + + HRESULT OnOpenItemChanged(UInt32 index, const wchar_t *fullFilePath, bool usePassword, const UString &password); + LRESULT OnOpenItemChanged(LPARAM lParam); + + bool IsVirus_Message(const UString &name); + void OpenItem(unsigned index, bool tryInternal, bool tryExternal, const wchar_t *type = NULL); + void EditItem(bool useEditor); + void EditItem(unsigned index, bool useEditor); + + void RenameFile(); + void ChangeComment(); + + void SetListViewMode(UInt32 index); + UInt32 GetListViewMode() const { return _listViewMode; } + PROPID GetSortID() const { return _sortID; } + + void ChangeFlatMode(); + void Change_ShowNtfsStrems_Mode(); + bool GetFlatMode() const { return _flatMode; } + // bool Get_ShowNtfsStrems_Mode() const { return _showNtfsStrems_Mode; } + + bool AutoRefresh_Mode; + void Set_AutoRefresh_Mode(bool mode) + { + AutoRefresh_Mode = mode; + } + + void Post_Refresh_StatusBar(); + void Refresh_StatusBar(); + + void AddToArchive(); + + int FindDir_InOperatedList(const CRecordVector &indices) const; + void GetFilePaths(const CRecordVector &indices, UStringVector &paths) const; + void ExtractArchives(); + void TestArchives(); + + void Get_ZoneId_Stream_from_ParentFolders(CByteBuffer &buf); + + HRESULT CopyTo(CCopyToOptions &options, + const CRecordVector &indices, + UStringVector *messages, + bool &usePassword, UString &password, + const UStringVector *filePaths = NULL); + + HRESULT CopyTo(CCopyToOptions &options, + const CRecordVector &indices, + UStringVector *messages) + { + bool usePassword = false; + UString password; + if (!_parentFolders.IsEmpty()) + { + const CFolderLink &fl = _parentFolders.Back(); + usePassword = fl.UsePassword; + password = fl.Password; + } + return CopyTo(options, indices, messages, usePassword, password); + } + + HRESULT CopyFsItems(CCopyToOptions &options, + const UStringVector &filePaths, + UStringVector *messages) + { + bool usePassword = false; + UString password; + CRecordVector indices; + return CopyTo(options, indices, messages, usePassword, password, &filePaths); + } + + + HRESULT CopyFrom(bool moveMode, const UString &folderPrefix, const UStringVector &filePaths, + bool showErrorMessages, UStringVector *messages); + + void CopyFromNoAsk(bool moveMode, const UStringVector &filePaths); + + void CompressDropFiles( + const UStringVector &filePaths, + const UString &folderPath, + bool createNewArchive, + bool moveMode, + UInt32 sourceFlags, + UInt32 &targetFlags); + + void RefreshTitle(bool always = false) { _panelCallback->RefreshTitle(always); } + void RefreshTitleAlways() { RefreshTitle(true); } + + UString GetItemsInfoString(const CRecordVector &indices); +}; + + +class CMyBuffer +{ + void *_data; +public: + CMyBuffer(): _data(NULL) {} + operator void *() { return _data; } + bool Allocate(size_t size) + { + if (_data) + return false; + _data = ::MidAlloc(size); + return _data != NULL; + } + ~CMyBuffer() { ::MidFree(_data); } +}; + +struct CExitEventLauncher +{ + NWindows::NSynchronization::CManualResetEvent _exitEvent; + bool _needExit; + unsigned _numActiveThreads; + CRecordVector< ::CThread > _threads; + + CExitEventLauncher() + { + _needExit = false; + if (_exitEvent.Create(false) != S_OK) + throw 9387173; + _needExit = true; + _numActiveThreads = 0; + } + + ~CExitEventLauncher() { Exit(true); } + + void Exit(bool hardExit); +}; + +extern CExitEventLauncher g_ExitEventLauncher; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PanelCopy.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PanelCopy.cpp new file mode 100644 index 00000000..f070be9b --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PanelCopy.cpp @@ -0,0 +1,477 @@ +/// PanelCopy.cpp + +#include "StdAfx.h" + +#include "../Common/ZipRegistry.h" + +#include "../GUI/HashGUI.h" + +#include "FSFolder.h" +#include "ExtractCallback.h" +#include "LangUtils.h" +#include "Panel.h" +#include "UpdateCallback100.h" + +#include "resource.h" + + +class CPanelCopyThread: public CProgressThreadVirt +{ + bool ResultsWereShown; + bool NeedShowRes; + + HRESULT ProcessVirt() Z7_override; + virtual void ProcessWasFinished_GuiVirt() Z7_override; +public: + const CCopyToOptions *options; + const UStringVector *CopyFrom_Paths; + CMyComPtr FolderOperations; + CRecordVector Indices; + CExtractCallbackImp *ExtractCallbackSpec; + CMyComPtr ExtractCallback; + + CHashBundle Hash; + // UString FirstFilePath; + + // HRESULT Result2; + + void ShowFinalResults(HWND hwnd); + + CPanelCopyThread(): + ResultsWereShown(false), + NeedShowRes(false), + CopyFrom_Paths(NULL) + // , Result2(E_FAIL) + {} +}; + +void CPanelCopyThread::ShowFinalResults(HWND hwnd) +{ + if (NeedShowRes) + if (!ResultsWereShown) + { + ResultsWereShown = true; + ShowHashResults(Hash, hwnd); + } +} + +void CPanelCopyThread::ProcessWasFinished_GuiVirt() +{ + ShowFinalResults(*this); +} + +HRESULT CPanelCopyThread::ProcessVirt() +{ + /* + CMyComPtr iReplace; + FolderOperations.QueryInterface(IID_IFolderSetReplaceAltStreamCharsMode, &iReplace); + if (iReplace) + { + RINOK(iReplace->SetReplaceAltStreamCharsMode(ReplaceAltStreamChars ? 1 : 0)); + } + */ + + HRESULT result2; + + if (FolderOperations) + { + { + CMyComPtr setZoneMode; + FolderOperations.QueryInterface(IID_IFolderSetZoneIdMode, &setZoneMode); + if (setZoneMode) + { + RINOK(setZoneMode->SetZoneIdMode(options->ZoneIdMode)) + } + } + { + CMyComPtr setZoneFile; + FolderOperations.QueryInterface(IID_IFolderSetZoneIdFile, &setZoneFile); + if (setZoneFile) + { + RINOK(setZoneFile->SetZoneIdFile(options->ZoneBuf, (UInt32)options->ZoneBuf.Size())) + } + } + } + + if (CopyFrom_Paths) + { + result2 = NFsFolder::CopyFileSystemItems( + *CopyFrom_Paths, + us2fs(options->folder), + options->moveMode, + (IFolderOperationsExtractCallback *)ExtractCallbackSpec); + } + else if (options->testMode) + { + CMyComPtr archiveFolder; + FolderOperations.QueryInterface(IID_IArchiveFolder, &archiveFolder); + if (!archiveFolder) + return E_NOTIMPL; + CMyComPtr extractCallback2; + RINOK(ExtractCallback.QueryInterface(IID_IFolderArchiveExtractCallback, &extractCallback2)) + NExtract::NPathMode::EEnum pathMode = + NExtract::NPathMode::kCurPaths; + // NExtract::NPathMode::kFullPathnames; + result2 = archiveFolder->Extract(Indices.ConstData(), Indices.Size(), + BoolToInt(options->includeAltStreams), + BoolToInt(options->replaceAltStreamChars), + pathMode, NExtract::NOverwriteMode::kAsk, + options->folder, BoolToInt(true), extractCallback2); + } + else + result2 = FolderOperations->CopyTo( + BoolToInt(options->moveMode), + Indices.ConstData(), Indices.Size(), + BoolToInt(options->includeAltStreams), + BoolToInt(options->replaceAltStreamChars), + options->folder, ExtractCallback); + + if (result2 == S_OK && !ExtractCallbackSpec->ThereAreMessageErrors) + { + if (!options->hashMethods.IsEmpty()) + NeedShowRes = true; + else if (options->testMode) + { + CProgressMessageBoxPair &pair = GetMessagePair(false); // GetMessagePair(ExtractCallbackSpec->Hash.NumErrors != 0); + AddHashBundleRes(pair.Message, Hash); + } + } + + return result2; +} + + +/* +#ifdef Z7_EXTERNAL_CODECS + +static void ThrowException_if_Error(HRESULT res) +{ + if (res != S_OK) + throw CSystemException(res); +} + +#endif +*/ + +void CPanel::Get_ZoneId_Stream_from_ParentFolders(CByteBuffer &buf) +{ + // we suppose that ZoneId of top parent has priority over ZoneId from childs. + FOR_VECTOR (i, _parentFolders) + { + // _parentFolders[0] = is top level archive + // _parentFolders[1 ... ].isVirtual == true is possible + // if extracted size meets size conditions derived from g_RAM_Size. + const CFolderLink &fl = _parentFolders[i]; + if (fl.IsVirtual) + { + if (fl.ZoneBuf.Size() != 0) + { + buf = fl.ZoneBuf; + return; + } + } + else if (!fl.FilePath.IsEmpty()) + { + ReadZoneFile_Of_BaseFile(fl.FilePath, buf); + if (buf.Size() != 0) + return; + } + } +} + +HRESULT CPanel::CopyTo(CCopyToOptions &options, + const CRecordVector &indices, + UStringVector *messages, + bool &usePassword, UString &password, + const UStringVector *filePaths) +{ + if (options.NeedRegistryZone && !options.testMode) + { + CContextMenuInfo ci; + ci.Load(); + if (ci.WriteZone != (UInt32)(Int32)-1) + options.ZoneIdMode = (NExtract::NZoneIdMode::EEnum)(int)(Int32)ci.WriteZone; + } + + if (options.ZoneBuf.Size() == 0 + && options.ZoneIdMode != NExtract::NZoneIdMode::kNone) + Get_ZoneId_Stream_from_ParentFolders(options.ZoneBuf); + + if (IsHashFolder()) + { + if (!options.testMode) + return E_NOTIMPL; + } + + if (!filePaths) + if (!_folderOperations) + { + const UString errorMessage = LangString(IDS_OPERATION_IS_NOT_SUPPORTED); + if (options.showErrorMessages) + MessageBox_Error(errorMessage); + else if (messages) + messages->Add(errorMessage); + return E_FAIL; + } + + HRESULT res = S_OK; + + { + /* + #ifdef Z7_EXTERNAL_CODECS + CExternalCodecs g_ExternalCodecs; + #endif + */ + /* extracter.Hash uses g_ExternalCodecs + extracter must be declared after g_ExternalCodecs for correct destructor order !!! */ + + CPanelCopyThread extracter; + + extracter.ExtractCallbackSpec = new CExtractCallbackImp; + extracter.ExtractCallback = extracter.ExtractCallbackSpec; + extracter.ExtractCallbackSpec->Src_Is_IO_FS_Folder = + IsFSFolder() || IsAltStreamsFolder(); + // options.src_Is_IO_FS_Folder; + extracter.options = &options; + extracter.ExtractCallbackSpec->ProgressDialog = &extracter; + extracter.CompressingMode = false; + + extracter.ExtractCallbackSpec->StreamMode = options.streamMode; + + + if (indices.Size() == 1) + { + extracter.Hash.FirstFileName = GetItemRelPath(indices[0]); + extracter.Hash.MainName = extracter.Hash.FirstFileName; + } + + if (options.VirtFileSystemSpec) + { + extracter.ExtractCallbackSpec->VirtFileSystem = options.VirtFileSystemSpec; + extracter.ExtractCallbackSpec->VirtFileSystemSpec = options.VirtFileSystemSpec; + } + extracter.ExtractCallbackSpec->ProcessAltStreams = options.includeAltStreams; + + if (!options.hashMethods.IsEmpty()) + { + /* this code is used when we call CRC calculation for files in side archive + But new code uses global codecs so we don't need to call LoadGlobalCodecs again */ + + /* + #ifdef Z7_EXTERNAL_CODECS + ThrowException_if_Error(LoadGlobalCodecs()); + #endif + */ + + extracter.Hash.SetMethods(EXTERNAL_CODECS_VARS_G options.hashMethods); + extracter.ExtractCallbackSpec->SetHashMethods(&extracter.Hash); + } + else if (options.testMode) + { + extracter.ExtractCallbackSpec->SetHashCalc(&extracter.Hash); + } + + // extracter.Hash.Init(); + + UString title; + { + UInt32 titleID = IDS_COPYING; + if (options.moveMode) + titleID = IDS_MOVING; + else if (!options.hashMethods.IsEmpty() && options.streamMode) + { + titleID = IDS_CHECKSUM_CALCULATING; + if (options.hashMethods.Size() == 1) + { + const UString &s = options.hashMethods[0]; + if (!s.IsEqualTo("*")) + title = s; + } + } + else if (options.testMode) + titleID = IDS_PROGRESS_TESTING; + + if (title.IsEmpty()) + title = LangString(titleID); + } + + const UString progressWindowTitle ("7-Zip"); // LangString(IDS_APP_TITLE); + + extracter.MainWindow = GetParent(); + extracter.MainTitle = progressWindowTitle; + extracter.MainAddTitle = title + L' '; + + extracter.ExtractCallbackSpec->OverwriteMode = NExtract::NOverwriteMode::kAsk; + extracter.ExtractCallbackSpec->Init(); + + extracter.CopyFrom_Paths = filePaths; + if (!filePaths) + { + extracter.Indices = indices; + extracter.FolderOperations = _folderOperations; + } + + extracter.ExtractCallbackSpec->PasswordIsDefined = usePassword; + extracter.ExtractCallbackSpec->Password = password; + + RINOK(extracter.Create(title, GetParent())) + + + if (messages) + *messages = extracter.Sync.Messages; + + // res = extracter.Result2; + res = extracter.Result; + + if (res == S_OK && extracter.ExtractCallbackSpec->IsOK()) + { + usePassword = extracter.ExtractCallbackSpec->PasswordIsDefined; + password = extracter.ExtractCallbackSpec->Password; + } + + extracter.ShowFinalResults(_window); + + } + + RefreshTitleAlways(); + return res; +} + + +struct CThreadUpdate +{ + CMyComPtr FolderOperations; + UString FolderPrefix; + UStringVector FileNames; + CRecordVector FileNamePointers; + CProgressDialog ProgressDialog; + CMyComPtr UpdateCallback; + CUpdateCallback100Imp *UpdateCallbackSpec; + HRESULT Result; + bool MoveMode; + + void Process() + { + try + { + CProgressCloser closer(ProgressDialog); + Result = FolderOperations->CopyFrom( + MoveMode, + FolderPrefix, + FileNamePointers.ConstData(), + FileNamePointers.Size(), + UpdateCallback); + } + catch(...) { Result = E_FAIL; } + } + static THREAD_FUNC_DECL MyThreadFunction(void *param) + { + ((CThreadUpdate *)param)->Process(); + return 0; + } +}; + + +HRESULT CPanel::CopyFrom(bool moveMode, const UString &folderPrefix, const UStringVector &filePaths, + bool showErrorMessages, UStringVector *messages) +{ + if (IsHashFolder()) + { + if (moveMode) + return E_NOTIMPL; + } + // CDisableNotify disableNotify(*this); + + HRESULT res; + if (!_folderOperations) + res = E_NOINTERFACE; + else + { + CThreadUpdate updater; + updater.MoveMode = moveMode; + updater.UpdateCallbackSpec = new CUpdateCallback100Imp; + updater.UpdateCallback = updater.UpdateCallbackSpec; + updater.UpdateCallbackSpec->Init(); + + updater.UpdateCallbackSpec->ProgressDialog = &updater.ProgressDialog; + + const UString title = LangString(IDS_COPYING); + const UString progressWindowTitle ("7-Zip"); // LangString(IDS_APP_TITLE); + + updater.ProgressDialog.MainWindow = GetParent(); + updater.ProgressDialog.MainTitle = progressWindowTitle; + updater.ProgressDialog.MainAddTitle = title + L' '; + + { + if (!_parentFolders.IsEmpty()) + { + const CFolderLink &fl = _parentFolders.Back(); + updater.UpdateCallbackSpec->PasswordIsDefined = fl.UsePassword; + updater.UpdateCallbackSpec->Password = fl.Password; + } + } + + updater.FolderOperations = _folderOperations; + updater.FolderPrefix = folderPrefix; + updater.FileNames.ClearAndReserve(filePaths.Size()); + unsigned i; + for (i = 0; i < filePaths.Size(); i++) + updater.FileNames.AddInReserved(filePaths[i]); + updater.FileNamePointers.ClearAndReserve(updater.FileNames.Size()); + for (i = 0; i < updater.FileNames.Size(); i++) + updater.FileNamePointers.AddInReserved(updater.FileNames[i]); + + { + NWindows::CThread thread; + const WRes wres = thread.Create(CThreadUpdate::MyThreadFunction, &updater); + if (wres != 0) + return HRESULT_FROM_WIN32(wres); + updater.ProgressDialog.Create(title, thread, GetParent()); + } + + if (messages) + *messages = updater.ProgressDialog.Sync.Messages; + + res = updater.Result; + } + + if (res == E_NOINTERFACE) + { + const UString errorMessage = LangString(IDS_OPERATION_IS_NOT_SUPPORTED); + if (showErrorMessages) + MessageBox_Error(errorMessage); + else if (messages) + messages->Add(errorMessage); + return E_ABORT; + } + + RefreshTitleAlways(); + return res; +} + +void CPanel::CopyFromNoAsk(bool moveMode, const UStringVector &filePaths) +{ + CDisableTimerProcessing disableTimerProcessing(*this); + + CSelectedState srcSelState; + SaveSelectedState(srcSelState); + + CDisableNotify disableNotify(*this); + + const HRESULT result = CopyFrom(moveMode, L"", filePaths, true, NULL); + + if (result != S_OK) + { + disableNotify.Restore(); + // For Password: + SetFocusToList(); + if (result != E_ABORT) + MessageBox_Error_HRESULT(result); + return; + } + + RefreshListCtrl(srcSelState); + + disableNotify.Restore(); + SetFocusToList(); +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PanelCrc.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PanelCrc.cpp new file mode 100644 index 00000000..df0b7338 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PanelCrc.cpp @@ -0,0 +1,422 @@ +// PanelCrc.cpp + +#include "StdAfx.h" + +#include "../../../Common/MyException.h" + +#include "../../../Windows/FileFind.h" +#include "../../../Windows/FileIO.h" +#include "../../../Windows/FileName.h" + +#include "../Common/LoadCodecs.h" + +#include "../GUI/HashGUI.h" + +#include "App.h" +#include "LangUtils.h" + +#include "resource.h" + +using namespace NWindows; +using namespace NFile; + +#ifdef Z7_EXTERNAL_CODECS +extern CExternalCodecs g_ExternalCodecs; +HRESULT LoadGlobalCodecs(); +#endif + +static const UInt32 kBufSize = (1 << 15); + +struct CDirEnumerator +{ + bool EnterToDirs; + FString BasePrefix; + FString BasePrefix_for_Open; + FStringVector FilePaths; + + CObjectVector Enumerators; + FStringVector Prefixes; + unsigned Index; + + CDirEnumerator(): EnterToDirs(false), Index(0) {} + + void Init(); + DWORD GetNextFile(NFind::CFileInfo &fi, bool &filled, FString &resPath); +}; + +void CDirEnumerator::Init() +{ + Enumerators.Clear(); + Prefixes.Clear(); + Index = 0; +} + +static DWORD GetNormalizedError() +{ + const DWORD error = GetLastError(); + return (error == 0) ? (DWORD)E_FAIL : error; +} + +DWORD CDirEnumerator::GetNextFile(NFind::CFileInfo &fi, bool &filled, FString &resPath) +{ + filled = false; + resPath.Empty(); + + for (;;) + { + #if defined(_WIN32) && !defined(UNDER_CE) + bool isRootPrefix = (BasePrefix.IsEmpty() || (NName::IsSuperPath(BasePrefix) && BasePrefix[NName::kSuperPathPrefixSize] == 0)); + #endif + + if (Enumerators.IsEmpty()) + { + if (Index >= FilePaths.Size()) + return S_OK; + const FString &path = FilePaths[Index++]; + const int pos = path.ReverseFind_PathSepar(); + if (pos >= 0) + resPath.SetFrom(path, (unsigned)pos + 1); + + #if defined(_WIN32) && !defined(UNDER_CE) + if (isRootPrefix && path.Len() == 2 && NName::IsDrivePath2(path)) + { + // we use "c:" item as directory item + fi.ClearBase(); + fi.Name = path; + fi.SetAsDir(); + fi.Size = 0; + } + else + #endif + if (!fi.Find(BasePrefix + path)) + { + const DWORD error = GetNormalizedError(); + resPath = path; + return error; + } + + break; + } + + bool found; + + if (Enumerators.Back().Next(fi, found)) + { + if (found) + { + resPath = Prefixes.Back(); + break; + } + } + else + { + const DWORD error = GetNormalizedError(); + resPath = Prefixes.Back(); + Enumerators.DeleteBack(); + Prefixes.DeleteBack(); + return error; + } + + Enumerators.DeleteBack(); + Prefixes.DeleteBack(); + } + + resPath += fi.Name; + + if (EnterToDirs && fi.IsDir()) + { + FString s = resPath; + s.Add_PathSepar(); + Prefixes.Add(s); + Enumerators.AddNew().SetDirPrefix(BasePrefix + s); + } + + filled = true; + return S_OK; +} + + + +class CThreadCrc: public CProgressThreadVirt +{ + bool ResultsWereShown; + bool WasFinished; + + HRESULT ProcessVirt() Z7_override; + virtual void ProcessWasFinished_GuiVirt() Z7_override; +public: + CDirEnumerator Enumerator; + CHashBundle Hash; + // FString FirstFilePath; + + void SetStatus(const UString &s); + void AddErrorMessage(DWORD systemError, const FChar *name); + void ShowFinalResults(HWND hwnd); + + CThreadCrc(): + ResultsWereShown(false), + WasFinished(false) + {} +}; + +void CThreadCrc::ShowFinalResults(HWND hwnd) +{ + if (WasFinished) + if (!ResultsWereShown) + { + ResultsWereShown = true; + ShowHashResults(Hash, hwnd); + } +} + +void CThreadCrc::ProcessWasFinished_GuiVirt() +{ + ShowFinalResults(*this); +} + +void CThreadCrc::AddErrorMessage(DWORD systemError, const FChar *name) +{ + Sync.AddError_Code_Name(HRESULT_FROM_WIN32(systemError), fs2us(Enumerator.BasePrefix + name)); + Hash.NumErrors++; +} + +void CThreadCrc::SetStatus(const UString &s2) +{ + UString s = s2; + if (!Enumerator.BasePrefix.IsEmpty()) + { + s.Add_Space_if_NotEmpty(); + s += fs2us(Enumerator.BasePrefix); + } + Sync.Set_Status(s); +} + +HRESULT CThreadCrc::ProcessVirt() +{ + // Hash.Init(); + + CMyBuffer buf; + if (!buf.Allocate(kBufSize)) + return E_OUTOFMEMORY; + + CProgressSync &sync = Sync; + + SetStatus(LangString(IDS_SCANNING)); + + Enumerator.Init(); + + FString path; + NFind::CFileInfo fi; + UInt64 numFiles = 0; + UInt64 numItems = 0, numItems_Prev = 0; + UInt64 totalSize = 0; + + for (;;) + { + bool filled; + const DWORD error = Enumerator.GetNextFile(fi, filled, path); + if (error != 0) + { + AddErrorMessage(error, path); + continue; + } + if (!filled) + break; + if (!fi.IsDir()) + { + totalSize += fi.Size; + numFiles++; + } + numItems++; + bool needPrint = false; + // if (fi.IsDir()) + { + if (numItems - numItems_Prev >= 100) + { + needPrint = true; + numItems_Prev = numItems; + } + } + /* + else if (numFiles - numFiles_Prev >= 200) + { + needPrint = true; + numFiles_Prev = numFiles; + } + */ + if (needPrint) + { + RINOK(sync.ScanProgress(numFiles, totalSize, path, fi.IsDir())) + } + } + RINOK(sync.ScanProgress(numFiles, totalSize, FString(), false)) + // sync.SetNumFilesTotal(numFiles); + // sync.SetProgress(totalSize, 0); + // SetStatus(LangString(IDS_CHECKSUM_CALCULATING)); + // sync.SetCurFilePath(L""); + SetStatus(L""); + + Enumerator.Init(); + + FString tempPath; + bool isFirstFile = true; + UInt64 errorsFilesSize = 0; + + for (;;) + { + bool filled; + DWORD error = Enumerator.GetNextFile(fi, filled, path); + if (error != 0) + { + AddErrorMessage(error, path); + continue; + } + if (!filled) + break; + + error = 0; + Hash.InitForNewFile(); + if (!fi.IsDir()) + { + NIO::CInFile inFile; + tempPath = Enumerator.BasePrefix_for_Open; + tempPath += path; + if (!inFile.Open(tempPath)) + { + error = GetNormalizedError(); + AddErrorMessage(error, path); + continue; + } + if (isFirstFile) + { + Hash.FirstFileName = fs2us(path); + isFirstFile = false; + } + sync.Set_FilePath(fs2us(path)); + sync.Set_NumFilesCur(Hash.NumFiles); + UInt64 progress_Prev = 0; + for (;;) + { + UInt32 size; + if (!inFile.Read(buf, kBufSize, size)) + { + error = GetNormalizedError(); + AddErrorMessage(error, path); + UInt64 errorSize = 0; + if (inFile.GetLength(errorSize)) + errorsFilesSize += errorSize; + break; + } + if (size == 0) + break; + Hash.Update(buf, size); + if (Hash.CurSize - progress_Prev >= ((UInt32)1 << 21)) + { + RINOK(sync.Set_NumBytesCur(errorsFilesSize + Hash.FilesSize + Hash.CurSize)) + progress_Prev = Hash.CurSize; + } + } + } + if (error == 0) + Hash.Final(fi.IsDir(), false, fs2us(path)); + RINOK(sync.Set_NumBytesCur(errorsFilesSize + Hash.FilesSize)) + } + RINOK(sync.Set_NumBytesCur(errorsFilesSize + Hash.FilesSize)) + sync.Set_NumFilesCur(Hash.NumFiles); + if (Hash.NumFiles != 1) + sync.Set_FilePath(L""); + SetStatus(L""); + + CProgressMessageBoxPair &pair = GetMessagePair(Hash.NumErrors != 0); + WasFinished = true; + LangString(IDS_CHECKSUM_INFORMATION, pair.Title); + return S_OK; +} + + + +HRESULT CApp::CalculateCrc2(const UString &methodName) +{ + unsigned srcPanelIndex = GetFocusedPanelIndex(); + CPanel &srcPanel = Panels[srcPanelIndex]; + + CRecordVector indices; + srcPanel.Get_ItemIndices_OperSmart(indices); + if (indices.IsEmpty()) + return S_OK; + + if (!srcPanel.Is_IO_FS_Folder()) + { + CCopyToOptions options; + options.streamMode = true; + options.showErrorMessages = true; + options.hashMethods.Add(methodName); + options.NeedRegistryZone = false; + + UStringVector messages; + return srcPanel.CopyTo(options, indices, &messages); + } + + #ifdef Z7_EXTERNAL_CODECS + + LoadGlobalCodecs(); + + #endif + + { + CThreadCrc t; + + { + UStringVector methods; + methods.Add(methodName); + RINOK(t.Hash.SetMethods(EXTERNAL_CODECS_VARS_G methods)) + } + + FOR_VECTOR (i, indices) + t.Enumerator.FilePaths.Add(us2fs(srcPanel.GetItemRelPath(indices[i]))); + + if (t.Enumerator.FilePaths.Size() == 1) + t.Hash.MainName = fs2us(t.Enumerator.FilePaths[0]); + + UString basePrefix = srcPanel.GetFsPath(); + UString basePrefix2 = basePrefix; + if (basePrefix2.Back() == ':') + { + const int pos = basePrefix2.ReverseFind_PathSepar(); + if (pos >= 0) + basePrefix2.DeleteFrom((unsigned)(pos + 1)); + } + + t.Enumerator.BasePrefix = us2fs(basePrefix); + t.Enumerator.BasePrefix_for_Open = us2fs(basePrefix2); + + t.Enumerator.EnterToDirs = !GetFlatMode(); + + t.ShowCompressionInfo = false; + + const UString title = LangString(IDS_CHECKSUM_CALCULATING); + + t.MainWindow = _window; + t.MainTitle = "7-Zip"; // LangString(IDS_APP_TITLE); + t.MainAddTitle = title; + t.MainAddTitle.Add_Space(); + + RINOK(t.Create(title, _window)) + + t.ShowFinalResults(_window); + } + + RefreshTitleAlways(); + return S_OK; +} + +void CApp::CalculateCrc(const char *methodName) +{ + HRESULT res = CalculateCrc2(UString(methodName)); + if (res != S_OK && res != E_ABORT) + { + unsigned srcPanelIndex = GetFocusedPanelIndex(); + CPanel &srcPanel = Panels[srcPanelIndex]; + srcPanel.MessageBox_Error_HRESULT(res); + } +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PanelDrag.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PanelDrag.cpp new file mode 100644 index 00000000..f9b0a6c7 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PanelDrag.cpp @@ -0,0 +1,3006 @@ +// PanelDrag.cpp + +#include "StdAfx.h" + +#ifdef UNDER_CE +#include +#endif + +#include "../../../../C/7zVersion.h" +#include "../../../../C/CpuArch.h" + +#include "../../../Common/StringConvert.h" +#include "../../../Common/Wildcard.h" + +#include "../../../Windows/COM.h" +#include "../../../Windows/MemoryGlobal.h" +#include "../../../Windows/Menu.h" +#include "../../../Windows/FileDir.h" +#include "../../../Windows/FileName.h" +#include "../../../Windows/Shell.h" + +#include "../Common/ArchiveName.h" +#include "../Common/CompressCall.h" +#include "../Common/ExtractingFilePath.h" + +#include "MessagesDialog.h" + +#include "App.h" +#include "EnumFormatEtc.h" +#include "FormatUtils.h" +#include "LangUtils.h" + +#include "resource.h" +#include "../Explorer/resource.h" + +using namespace NWindows; +using namespace NFile; +using namespace NDir; + +#ifndef _UNICODE +extern bool g_IsNT; +#endif + +#define PRF(x) +#define PRF_W(x) +// #define PRF2(x) +#define PRF3(x) +#define PRF3_W(x) +#define PRF4(x) +// #define PRF4(x) OutputDebugStringA(x) +// #define PRF4_W(x) OutputDebugStringW(x) + +// #define SHOW_DEBUG_DRAG + +#ifdef SHOW_DEBUG_DRAG + +#define PRF_(x) { x; } + +static void Print_Point(const char *name, DWORD keyState, const POINTL &pt, DWORD effect) +{ + AString s (name); + s += " x="; s.Add_UInt32((unsigned)pt.x); + s += " y="; s.Add_UInt32((unsigned)pt.y); + s += " k="; s.Add_UInt32(keyState); + s += " e="; s.Add_UInt32(effect); + PRF4(s); +} + +#else + +#define PRF_(x) + +#endif + + +#define kTempDirPrefix FTEXT("7zE") + +// all versions: k_Format_7zip_SetTargetFolder format to transfer folder path from target to source +static LPCTSTR const k_Format_7zip_SetTargetFolder = TEXT("7-Zip::SetTargetFolder"); +// new v23 formats: +static LPCTSTR const k_Format_7zip_SetTransfer = TEXT("7-Zip::SetTransfer"); +static LPCTSTR const k_Format_7zip_GetTransfer = TEXT("7-Zip::GetTransfer"); + +/* + Win10: clipboard formats. + There are about 16K free ids (formats) per system that can be + registered with RegisterClipboardFormat() with different names. + Probably that 16K ids space is common for ids registering for both + formats: RegisterClipboardFormat(), and registered window classes: + RegisterClass(). But ids for window classes will be deleted from + the list after process finishing. And registered clipboard + formats probably will be deleted from the list only after reboot. +*/ + +// static bool const g_CreateArchive_for_Drag_from_7zip = false; +// static bool const g_CreateArchive_for_Drag_from_Explorer = true; + // = false; // for debug + +/* +How DoDragDrop() works: +{ + IDropSource::QueryContinueDrag() (keyState & MK_LBUTTON) != 0 + IDropTarget::Enter() + IDropSource::GiveFeedback() + IDropTarget::DragOver() + IDropSource::GiveFeedback() + + for() + { + IDropSource::QueryContinueDrag() (keyState & MK_LBUTTON) != 0 + IDropTarget::DragOver() (keyState & MK_LBUTTON) != 0 + IDropSource::GiveFeedback() + } + + { + // DoDragDrop() in Win10 before calling // QueryContinueDrag() + // with (*(keyState & MK_LBUTTON) == 0) probably calls: + // 1) IDropTarget::DragOver() with same point values (x,y), but (keyState & MK_LBUTTON) != 0) + // 2) IDropSource::GiveFeedback(). + // so DropSource can know exact GiveFeedback(effect) mode just before LBUTTON releasing. + + if (IDropSource::QueryContinueDrag() for (keyState & MK_LBUTTON) == 0 + returns DRAGDROP_S_DROP), it will call + IDropTarget::Drop() + } + or + { + IDropSource::QueryContinueDrag() + IDropTarget::DragLeave() + IDropSource::GiveFeedback(0) + } + or + { + if (IDropSource::QueryContinueDrag() + returns DRAGDROP_S_CANCEL) + IDropTarget::DragLeave() + } +} +*/ + + +// ---------- CDropTarget ---------- + +static const UInt32 k_Struct_Id_SetTranfer = 2; // it's our selected id +static const UInt32 k_Struct_Id_GetTranfer = 3; // it's our selected id + +static const UInt64 k_Program_Id = 1; // "7-Zip" + +enum E_Program_ISA +{ + k_Program_ISA_x86 = 2, + k_Program_ISA_x64 = 3, + k_Program_ISA_armt = 4, + k_Program_ISA_arm64 = 5, + k_Program_ISA_arm32 = 6, + k_Program_ISA_ia64 = 9 +}; + +#define k_Program_Ver ((MY_VER_MAJOR << 16) | MY_VER_MINOR) + + +// k_SourceFlags_* are flags that are sent from Source to Target + +static const UInt32 k_SourceFlags_DoNotProcessInTarget = 1 << 1; +/* Do not process in Target. Source will process operation instead of Target. + By default Target processes Drop opearation. */ +// static const UInt32 k_SourceFlags_ProcessInTarget = 1 << 2; + +static const UInt32 k_SourceFlags_DoNotWaitFinish = 1 << 3; +static const UInt32 k_SourceFlags_WaitFinish = 1 << 4; +/* usually Source needs WaitFinish, if temp files were created. */ + +static const UInt32 k_SourceFlags_TempFiles = 1 << 6; +static const UInt32 k_SourceFlags_NamesAreParent = 1 << 7; +/* if returned path list for GetData(CF_HDROP) contains + path of parent temp folder instead of final paths of items + that will be extracted later from archive */ + +static const UInt32 k_SourceFlags_SetTargetFolder = 1 << 8; +/* SetData::("SetTargetFolder") was called (with empty or non-empty string) */ + +static const UInt32 k_SourceFlags_SetTargetFolder_NonEmpty = 1 << 9; +/* SetData::("SetTargetFolder") was called with non-empty string */ + +static const UInt32 k_SourceFlags_NeedExtractOpToFs = 1 << 10; + +static const UInt32 k_SourceFlags_Copy_WasCalled = 1 << 11; + +static const UInt32 k_SourceFlags_LeftButton = 1 << 14; +static const UInt32 k_SourceFlags_RightButton = 1 << 15; + + +static const UInt32 k_TargetFlags_WasCanceled = 1 << 0; +static const UInt32 k_TargetFlags_MustBeProcessedBySource = 1 << 1; +static const UInt32 k_TargetFlags_WasProcessed = 1 << 2; +static const UInt32 k_TargetFlags_DoNotWaitFinish = 1 << 3; +static const UInt32 k_TargetFlags_WaitFinish = 1 << 4; +static const UInt32 k_TargetFlags_MenuWasShown = 1 << 16; + +struct CDataObject_TransferBase +{ + UInt32 Struct_Id; + UInt32 Struct_Size; + + UInt64 Program_Id; + UInt32 Program_Ver_Main; + UInt32 Program_Ver_Build; + UInt32 Program_ISA; + UInt32 Program_Flags; + + UInt32 ProcessId; + UInt32 _reserved1[7]; + +protected: + void Init_Program(); +}; + + +void CDataObject_TransferBase::Init_Program() +{ + Program_Id = k_Program_Id; + Program_ISA = + #if defined(MY_CPU_AMD64) + k_Program_ISA_x64 + #elif defined(MY_CPU_X86) + k_Program_ISA_x86 + #elif defined(MY_CPU_ARM64) + k_Program_ISA_arm64 + #elif defined(MY_CPU_ARM32) + k_Program_ISA_arm32 + #elif defined(MY_CPU_ARMT) || defined(MY_CPU_ARM) + k_Program_ISA_armt + #elif defined(MY_CPU_IA64) + k_Program_ISA_ia64 + #else + 0 + #endif + ; + Program_Flags = sizeof(size_t); + Program_Ver_Main = k_Program_Ver; + // Program_Ver_Build = 0; + ProcessId = GetCurrentProcessId(); +} + + +#if defined(__GNUC__) && !defined(__clang__) +/* 'void* memset(void*, int, size_t)' clearing an object + of non-trivial type 'struct CDataObject_SetTransfer' */ +#pragma GCC diagnostic ignored "-Wclass-memaccess" +#endif + + +struct CDataObject_GetTransfer: +public CDataObject_TransferBase +{ + UInt32 Flags; + + UInt32 _reserved2[11]; + + CDataObject_GetTransfer() + { + memset(this, 0, sizeof(*this)); + Init_Program(); + Struct_Id = k_Struct_Id_GetTranfer; + Struct_Size = sizeof(*this); + } + + bool Check() const + { + return Struct_Size >= sizeof(*this) && Struct_Id == k_Struct_Id_GetTranfer; + } +}; + + +enum Enum_FolderType +{ + k_FolderType_None, + k_FolderType_Unknown = 1, + k_FolderType_Fs = 2, + k_FolderType_AltStreams = 3, + k_FolderType_Archive = 4 +}; + +struct CTargetTransferInfo +{ + UInt32 Flags; + UInt32 FuncType; + + UInt32 KeyState; + UInt32 OkEffects; + POINTL Point; + + UInt32 Cmd_Effect; + UInt32 Cmd_Type; + UInt32 FolderType; + UInt32 _reserved3[3]; + + CTargetTransferInfo() + { + memset(this, 0, sizeof(*this)); + } +}; + +struct CDataObject_SetTransfer: +public CDataObject_TransferBase +{ + CTargetTransferInfo Target; + + void Init() + { + memset(this, 0, sizeof(*this)); + Init_Program(); + Struct_Id = k_Struct_Id_SetTranfer; + Struct_Size = sizeof(*this); + } + + bool Check() const + { + return Struct_Size >= sizeof(*this) && Struct_Id == k_Struct_Id_SetTranfer; + } +}; + + + + + +enum Enum_DragTargetMode +{ + k_DragTargetMode_None = 0, + k_DragTargetMode_Leave = 1, + k_DragTargetMode_Enter = 2, + k_DragTargetMode_Over = 3, + k_DragTargetMode_Drop_Begin = 4, + k_DragTargetMode_Drop_End = 5 +}; + + +// ---- menu ---- + +namespace NDragMenu { + +enum Enum_CmdId +{ + k_None = 0, + k_Cancel = 1, + k_Copy_Base = 2, // to fs + k_Copy_ToArc = 3, + k_AddToArc = 4 + /* + k_OpenArc = 8, + k_TestArc = 9, + k_ExtractFiles = 10, + k_ExtractHere = 11 + */ +}; + +struct CCmdLangPair +{ + unsigned CmdId_and_Flags; + unsigned LangId; +}; + +static const UInt32 k_MenuFlags_CmdMask = (1 << 7) - 1; +static const UInt32 k_MenuFlag_Copy = 1 << 14; +static const UInt32 k_MenuFlag_Move = 1 << 15; +// #define IDS_CANCEL (IDCANCEL + 400) +#define IDS_CANCEL 402 + +static const CCmdLangPair g_Pairs[] = +{ + { k_Copy_Base | k_MenuFlag_Copy, IDS_COPY }, + { k_Copy_Base | k_MenuFlag_Move, IDS_MOVE }, + { k_Copy_ToArc | k_MenuFlag_Copy, IDS_COPY_TO }, + // { k_Copy_ToArc | k_MenuFlag_Move, IDS_MOVE_TO }, // IDS_CONTEXT_COMPRESS_TO + // { k_OpenArc, IDS_CONTEXT_OPEN }, + // { k_ExtractFiles, IDS_CONTEXT_EXTRACT }, + // { k_ExtractHere, IDS_CONTEXT_EXTRACT_HERE }, + // { k_TestArc, IDS_CONTEXT_TEST }, + { k_AddToArc | k_MenuFlag_Copy, IDS_CONTEXT_COMPRESS }, + { k_Cancel, IDS_CANCEL } +}; + +} + + +class CDropTarget Z7_final: + public IDropTarget, + public CMyUnknownImp +{ + Z7_COM_UNKNOWN_IMP_1_MT(IDropTarget) + STDMETHOD(DragEnter)(IDataObject *dataObject, DWORD keyState, POINTL pt, DWORD *effect) Z7_override; + STDMETHOD(DragOver)(DWORD keyState, POINTL pt, DWORD *effect) Z7_override; + STDMETHOD(DragLeave)() Z7_override; + STDMETHOD(Drop)(IDataObject *dataObject, DWORD keyState, POINTL pt, DWORD *effect) Z7_override; + + bool m_IsRightButton; + bool m_GetTransfer_WasSuccess; + bool m_DropIsAllowed; // = true, if data IDataObject can return CF_HDROP (so we can get list of paths) + bool m_PanelDropIsAllowed; // = false, if current target_panel is source_panel. + // check it only if m_DropIsAllowed == true + // we use it to show icon effect that drop is not allowed here. + + CMyComPtr m_DataObject; // we set it in DragEnter() + UStringVector m_SourcePaths; + + // int m_DropHighlighted_SelectionIndex; + // int m_SubFolderIndex; // realIndex of item in m_Panel list (if drop cursor to that item) + // UString m_DropHighlighted_SubFolderName; // name of folder in m_Panel list (if drop cursor to that folder) + + CPanel *m_Panel; + bool m_IsAppTarget; // true, if we want to drop to app window (not to panel) + + bool m_TargetPath_WasSent_ToDataObject; // true, if TargetPath was sent + bool m_TargetPath_NonEmpty_WasSent_ToDataObject; // true, if non-empty TargetPath was sent + bool m_Transfer_WasSent_ToDataObject; // true, if Transfer was sent + UINT m_Format_7zip_SetTargetFolder; + UINT m_Format_7zip_SetTransfer; + UINT m_Format_7zip_GetTransfer; + + UInt32 m_ProcessId; // for sending + + bool IsItSameDrive() const; + + // void Try_QueryGetData(IDataObject *dataObject); + void LoadNames_From_DataObject(IDataObject *dataObject); + + UInt32 GetFolderType() const; + bool IsFsFolderPath() const; + DWORD GetEffect(DWORD keyState, POINTL pt, DWORD allowedEffect) const; + void RemoveSelection(); + void PositionCursor(const POINTL &ptl); + UString GetTargetPath() const; + bool SendToSource_TargetPath_enable(IDataObject *dataObject, bool enablePath); + bool SendToSource_UInt32(IDataObject *dataObject, UINT format, UInt32 value); + bool SendToSource_TransferInfo(IDataObject *dataObject, + const CTargetTransferInfo &info); + void SendToSource_auto(IDataObject *dataObject, + const CTargetTransferInfo &info); + void SendToSource_Drag(CTargetTransferInfo &info) + { + SendToSource_auto(m_DataObject, info); + } + + void ClearState(); + +public: + CDropTarget(); + + CApp *App; + int SrcPanelIndex; // index of D&D source_panel + int TargetPanelIndex; // what panel to use as target_panel of Application +}; + + + + +// ---------- CDataObject ---------- + +/* + Some programs (like Sticky Notes in Win10) do not like + virtual non-existing items (files/dirs) in CF_HDROP format. + So we use two versions of CF_HDROP data: + m_hGlobal_HDROP_Pre : the list contains only destination path of temp directory. + That directory later will be filled with extracted items. + m_hGlobal_HDROP_Final : the list contains paths of all root items that + will be created in temp directory by archive extraction operation, + or the list of existing fs items, if source is filesystem directory. + + The DRAWBACK: some programs (like Edge in Win10) can use names from IDataObject::GetData() + call that was called before IDropSource::QueryContinueDrag() where we set (UseFinalGlobal = true) + So such programs will use non-relevant m_hGlobal_HDROP_Pre item, + instead of m_hGlobal_HDROP_Final items. +*/ + +class CDataObject Z7_final: + public IDataObject, + public CMyUnknownImp +{ + Z7_COM_UNKNOWN_IMP_1_MT(IDataObject) + + Z7_COMWF_B GetData(LPFORMATETC pformatetcIn, LPSTGMEDIUM medium) Z7_override; + Z7_COMWF_B GetDataHere(LPFORMATETC pformatetc, LPSTGMEDIUM medium) Z7_override; + Z7_COMWF_B QueryGetData(LPFORMATETC pformatetc) Z7_override; + + Z7_COMWF_B GetCanonicalFormatEtc(LPFORMATETC /* pformatetc */, LPFORMATETC pformatetcOut) Z7_override + { + if (!pformatetcOut) + return E_INVALIDARG; + pformatetcOut->ptd = NULL; + return E_NOTIMPL; + } + + Z7_COMWF_B SetData(LPFORMATETC etc, STGMEDIUM *medium, BOOL release) Z7_override; + Z7_COMWF_B EnumFormatEtc(DWORD drection, LPENUMFORMATETC *enumFormatEtc) Z7_override; + + Z7_COMWF_B DAdvise(FORMATETC * /* etc */, DWORD /* advf */, LPADVISESINK /* pAdvSink */, DWORD * /* pdwConnection */) Z7_override + { return OLE_E_ADVISENOTSUPPORTED; } + Z7_COMWF_B DUnadvise(DWORD /* dwConnection */) Z7_override + { return OLE_E_ADVISENOTSUPPORTED; } + Z7_COMWF_B EnumDAdvise(LPENUMSTATDATA *ppenumAdvise) Z7_override + { + if (ppenumAdvise) + *ppenumAdvise = NULL; + return OLE_E_ADVISENOTSUPPORTED; + } + + bool m_PerformedDropEffect_WasSet; + bool m_LogicalPerformedDropEffect_WasSet; + bool m_DestDirPrefix_FromTarget_WasSet; +public: + bool m_Transfer_WasSet; +private: + // GetData formats (source to target): + FORMATETC m_Etc; + // UINT m_Format_FileOpFlags; + // UINT m_Format_PreferredDropEffect; + + // SetData() formats (target to source): + // 7-Zip's format: + UINT m_Format_7zip_SetTargetFolder; + UINT m_Format_7zip_SetTransfer; + UINT m_Format_7zip_GetTransfer; // for GetData() + + UINT m_Format_PerformedDropEffect; + UINT m_Format_LogicalPerformedDropEffect; + UINT m_Format_DisableDragText; + UINT m_Format_IsShowingLayered; + UINT m_Format_IsShowingText; + UINT m_Format_DropDescription; + UINT m_Format_TargetCLSID; + + DWORD m_PerformedDropEffect; + DWORD m_LogicalPerformedDropEffect; + + void CopyFromPanelTo_Folder(); + HRESULT SetData2(const FORMATETC *formatetc, const STGMEDIUM *medium); + +public: + bool IsRightButton; + bool IsTempFiles; + + bool UsePreGlobal; + bool DoNotProcessInTarget; + + bool NeedCall_Copy; + bool Copy_WasCalled; + + NMemory::CGlobal m_hGlobal_HDROP_Pre; + NMemory::CGlobal m_hGlobal_HDROP_Final; + // NMemory::CGlobal m_hGlobal_FileOpFlags; + // NMemory::CGlobal m_hGlobal_PreferredDropEffect; + + CPanel *Panel; + CRecordVector Indices; + + UString SrcDirPrefix_Temp; // FS directory with source files or Temp + UString DestDirPrefix_FromTarget; + /* destination Path that was sent by Target via SetData(). + it can be altstreams prefix. + if (!DestDirPrefix_FromTarget.IsEmpty()) m_Panel->CompressDropFiles() was not called by Target. + So we must do drop actions in Source */ + HRESULT Copy_HRESULT; + UStringVector Messages; + + CDataObject(); +public: + CDataObject_SetTransfer m_Transfer; +}; + + +// for old mingw: +#ifndef CFSTR_LOGICALPERFORMEDDROPEFFECT +#define CFSTR_LOGICALPERFORMEDDROPEFFECT TEXT("Logical Performed DropEffect") +#endif +#ifndef CFSTR_TARGETCLSID +#define CFSTR_TARGETCLSID TEXT("TargetCLSID") // HGLOBAL with a CLSID of the drop target +#endif + + + +CDataObject::CDataObject() +{ + // GetData formats (source to target): + // and we use CF_HDROP format to transfer file paths from source to target: + m_Etc.cfFormat = CF_HDROP; + m_Etc.ptd = NULL; + m_Etc.dwAspect = DVASPECT_CONTENT; + m_Etc.lindex = -1; + m_Etc.tymed = TYMED_HGLOBAL; + + // m_Format_FileOpFlags = RegisterClipboardFormat(TEXT("FileOpFlags")); + // m_Format_PreferredDropEffect = RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT); // "Preferred DropEffect" + + // SetData() formats (target to source): + m_Format_7zip_SetTargetFolder = RegisterClipboardFormat(k_Format_7zip_SetTargetFolder); + m_Format_7zip_SetTransfer = RegisterClipboardFormat(k_Format_7zip_SetTransfer); + m_Format_7zip_GetTransfer = RegisterClipboardFormat(k_Format_7zip_GetTransfer); + + m_Format_PerformedDropEffect = RegisterClipboardFormat(CFSTR_PERFORMEDDROPEFFECT); // "Performed DropEffect" + m_Format_LogicalPerformedDropEffect = RegisterClipboardFormat(CFSTR_LOGICALPERFORMEDDROPEFFECT); // "Logical Performed DropEffect" + m_Format_DisableDragText = RegisterClipboardFormat(TEXT("DisableDragText")); + m_Format_IsShowingLayered = RegisterClipboardFormat(TEXT("IsShowingLayered")); + m_Format_IsShowingText = RegisterClipboardFormat(TEXT("IsShowingText")); + m_Format_DropDescription = RegisterClipboardFormat(TEXT("DropDescription")); + m_Format_TargetCLSID = RegisterClipboardFormat(CFSTR_TARGETCLSID); + + m_PerformedDropEffect = 0; + m_LogicalPerformedDropEffect = 0; + + m_PerformedDropEffect_WasSet = false; + m_LogicalPerformedDropEffect_WasSet = false; + + m_DestDirPrefix_FromTarget_WasSet = false; + m_Transfer_WasSet = false; + + IsRightButton = false; + IsTempFiles = false; + + UsePreGlobal = false; + DoNotProcessInTarget = false; + + NeedCall_Copy = false; + Copy_WasCalled = false; + + Copy_HRESULT = S_OK; +} + + + +void CDataObject::CopyFromPanelTo_Folder() +{ + try + { + CCopyToOptions options; + options.folder = SrcDirPrefix_Temp; + /* 15.13: fixed problem with mouse cursor for password window. + DoDragDrop() probably calls SetCapture() to some hidden window. + But it's problem, if we show some modal window, like MessageBox. + So we return capture to our window. + If you know better way to solve the problem, please notify 7-Zip developer. + */ + // MessageBoxW(*Panel, L"test", L"test", 0); + /* HWND oldHwnd = */ SetCapture(*Panel); + Copy_WasCalled = true; + Copy_HRESULT = E_FAIL; + Copy_HRESULT = Panel->CopyTo(options, Indices, &Messages); + // do we need to restore capture? + // ReleaseCapture(); + // oldHwnd = SetCapture(oldHwnd); + } + catch(...) + { + Copy_HRESULT = E_FAIL; + } +} + + +#ifdef SHOW_DEBUG_DRAG + +static void PrintFormat2(AString &s, unsigned format) +{ + s += " "; + s += "= format="; + s.Add_UInt32(format); + s += " "; + const int k_len = 512; + CHAR temp[k_len]; + if (GetClipboardFormatNameA(format, temp, k_len) && strlen(temp) != 0) + s += temp; +} + +static void PrintFormat(const char *title, unsigned format) +{ + AString s (title); + PrintFormat2(s, format); + PRF4(s); +} + +static void PrintFormat_AndData(const char *title, unsigned format, const void *data, size_t size) +{ + AString s (title); + PrintFormat2(s, format); + s += " size="; + s.Add_UInt32((UInt32)size); + for (size_t i = 0; i < size && i < 16; i++) + { + s += " "; + s.Add_UInt32(((const Byte *)data)[i]); + } + PRF4(s); +} + +static void PrintFormat_GUIDToStringW(const void *p) +{ + const GUID *guid = (const GUID *)p; + UString s; + const unsigned kSize = 48; + StringFromGUID2(*guid, s.GetBuf(kSize), kSize); + s.ReleaseBuf_CalcLen(kSize); + PRF3_W(s); +} + +// Vista +typedef enum +{ + MY_DROPIMAGE_INVALID = -1, // no image preference (use default) + MY_DROPIMAGE_NONE = 0, // red "no" circle + MY_DROPIMAGE_COPY = DROPEFFECT_COPY, // plus for copy + MY_DROPIMAGE_MOVE = DROPEFFECT_MOVE, // movement arrow for move + MY_DROPIMAGE_LINK = DROPEFFECT_LINK, // link arrow for link + MY_DROPIMAGE_LABEL = 6, // tag icon to indicate metadata will be changed + MY_DROPIMAGE_WARNING = 7, // yellow exclamation, something is amiss with the operation + MY_DROPIMAGE_NOIMAGE = 8 // no image at all +} MY_DROPIMAGETYPE; + +typedef struct { + MY_DROPIMAGETYPE type; + WCHAR szMessage[MAX_PATH]; + WCHAR szInsert[MAX_PATH]; +} MY_DROPDESCRIPTION; + +#endif + + +/* +IDataObject::SetData(LPFORMATETC etc, STGMEDIUM *medium, BOOL release) +====================================================================== + + Main purpose of CDataObject is to transfer data from source to target + of drag and drop operation. + But also CDataObject can be used to transfer data in backward direction + from target to source (even if target and source are different processes). + There are some predefined Explorer's formats to transfer some data from target to source. + And 7-Zip uses 7-Zip's format k_Format_7zip_SetTargetFolder to transfer + destination directory path from target to source. + + Our CDataObject::SetData() function here is used only to transfer data from target to source. + Usual source_to_target data is filled to m_hGlobal_* objects directly without SetData() calling. + +The main problem of SetData() is ownership of medium for (release == TRUE) case. + +SetData(,, release = TRUE) from different processes (DropSource and DropTarget) +=============================================================================== +{ + MS DOCs about (STGMEDIUM *medium) ownership: + The data object called does not take ownership of the data + until it has successfully received it and no error code is returned. + + Each of processes (Source and Target) has own copy of medium allocated. + Windows code creates proxy IDataObject object in Target process to transferr + SetData() call between Target and Source processes via special proxies: + DropTarget -> + proxy_DataObject_in_Target -> + proxy_in_Source -> + DataObject_in_Source + when Target calls SetData() with proxy_DataObject_in_Target, + the system and proxy_in_Source + - allocates proxy-medium-in-Source process + - copies medium data from Target to that proxy-medium-in-Source + - sends proxy-medium-in-Source to DataObject_in_Source->SetData(). + + after returning from SetData() to Target process: + Win10 proxy_DataObject_in_Target releases original medium in Target process, + only if SetData() in Source returns S_OK. It's consistent with DOCs above. + + for unsupported cfFormat: + [DropSource is 7-Zip 22.01 (old) : (etc->cfFormat != m_Format_7zip_SetTargetFolder && release == TRUE)] + (DropSource is WinRAR case): + Source doesn't release medium and returns error (for example, E_NOTIMPL) + { + Then Win10 proxy_in_Source also doesn't release proxy-medium-in-Source. + So there is memory leak in Source process. + Probably Win10 proxy_in_Source tries to avoid possible double releasing + that can be more fatal than memory leak. + + Then Win10 proxy_DataObject_in_Target also doesn't release + original medium, that was allocated by DropTarget. + So if DropTarget also doesn't release medium, there is memory leak in + DropTarget process too. + DropTarget is Win10-Explorer probably doesn't release medium in that case. + } + + [DropSource is 7-Zip 22.01 (old) : (etc->cfFormat == m_Format_7zip_SetTargetFolder && release == TRUE)] + DropSource returns S_OK and doesn't release medium: + { + then there is memory leak in DropSource process only. + } + + (DropSource is 7-Zip v23 (new)): + (DropSource is Win10-Explorer case) + { + Win10-Explorer-DropSource probably always releases medium, + and then it always returns S_OK. + So Win10 proxy_DataObject_in_Target also releases + original medium, that was allocated by DropTarget. + So there is no memory leak in Source and Target processes. + } + + if (DropTarget is Win10-Explorer) + { + Explorer Target uses SetData(,, (release = TRUE)) and + Explorer Target probably doesn't free memory after SetData(), + even if SetData(,, (release = TRUE)) returns E_NOTIMPL; + } + + if (DropSource is Win10-Explorer) + { + (release == FALSE) doesn't work, and SetData() returns E_NOTIMPL; + (release == TRUE) works, and SetData() returns S_OK, and + it returns S_OK even for formats unsupported by Explorer. + } + + To be more compatible with DOCs and Win10-Explorer and to avoid memory leaks, + we use the following scheme for our IDataObject::SetData(,, release == TRUE) + in DropSource code: + if (release == TRUE) { our SetData() always releases medium + with ReleaseStgMedium() and returns S_OK; } + The DRAWBACK of that scheme: + The caller always receives S_OK, + so the caller doesn't know about any error in SetData() in that case. + +for 7zip-Target to 7zip-Source calls: + we use (release == FALSE) + So we avoid (release == TRUE) memory leak problems, + and we can get real return code from SetData(). + +for 7zip-Target to Explorer-Source calls: + we use (release == TRUE). + beacuse Explorer-Source doesn't accept (release == FALSE). +} +*/ + +/* +https://github.com/MicrosoftDocs/win32/blob/docs/desktop-src/shell/datascenarios.md +CFSTR_PERFORMEDDROPEFFECT: + is used by the target to inform the data object through its + IDataObject::SetData method of the outcome of a data transfer. +CFSTR_PREFERREDDROPEFFECT: + is used by the source to specify whether its preferred method of data transfer is move or copy. +*/ + +Z7_COMWF_B CDataObject::SetData(LPFORMATETC etc, STGMEDIUM *medium, BOOL release) +{ + try { + const HRESULT hres = SetData2(etc, medium); + // PrintFormat(release ? "SetData RELEASE=TRUE" : "SetData RELEASE=FALSE" , etc->cfFormat); + if (release) + { + /* + const DWORD tymed = medium->tymed; + IUnknown *pUnkForRelease = medium->pUnkForRelease; + */ + // medium->tymed = NULL; // for debug + // return E_NOTIMPL; // for debug + ReleaseStgMedium(medium); + /* ReleaseStgMedium() will change STGMEDIUM::tymed to (TYMED_NULL = 0). + but we also can clear (medium.hGlobal = NULL), + to prevent some incorrect releasing, if the caller will try to release the data */ + /* + if (medium->tymed == TYMED_NULL && tymed == TYMED_HGLOBAL && !pUnkForRelease) + medium->hGlobal = NULL; + */ + // do we need return S_OK; for (tymed != TYMED_HGLOBAL) cases ? + /* we return S_OK here to shows that we take ownership of the data in (medium), + so the caller will not try to release (medium) */ + return S_OK; // to be more compatible with Win10-Explorer and DOCs. + } + return hres; + } catch(...) { return E_FAIL; } +} + + + +HRESULT CDataObject::SetData2(const FORMATETC *etc, const STGMEDIUM *medium) +{ + // PRF3("== CDataObject::SetData()"); + + HRESULT hres = S_OK; + + if (etc->cfFormat == 0) + return DV_E_FORMATETC; + if (etc->tymed != TYMED_HGLOBAL) + return E_NOTIMPL; // DV_E_TYMED; + if (etc->dwAspect != DVASPECT_CONTENT) + return E_NOTIMPL; // DV_E_DVASPECT; + if (medium->tymed != TYMED_HGLOBAL) + return E_NOTIMPL; // DV_E_TYMED; + + if (!medium->hGlobal) + return S_OK; + + if (etc->cfFormat == m_Format_7zip_SetTargetFolder) + { + DestDirPrefix_FromTarget.Empty(); + m_DestDirPrefix_FromTarget_WasSet = true; + } + else if (etc->cfFormat == m_Format_7zip_SetTransfer) + m_Transfer_WasSet = false; + + const size_t size = GlobalSize(medium->hGlobal); + // GlobalLock() can return NULL, if memory block has a zero size + if (size == 0) + return S_OK; + const void *src = (const Byte *)GlobalLock(medium->hGlobal); + if (!src) + return E_FAIL; + + PRF_(PrintFormat_AndData("SetData", etc->cfFormat, src, size)) + + if (etc->cfFormat == m_Format_7zip_SetTargetFolder) + { + /* this is our registered k_Format_7zip_SetTargetFolder format. + so it's call from 7-zip's CDropTarget */ + /* 7-zip's CDropTarget calls SetData() for m_Format_7zip_SetTargetFolder + with (release == FALSE) */ + const size_t num = size / sizeof(wchar_t); + if (size != num * sizeof(wchar_t)) + return E_FAIL; + // if (num == 0) return S_OK; + // GlobalLock() can return NULL, if memory block has a zero-byte size + const wchar_t *s = (const wchar_t *)src; + UString &dest = DestDirPrefix_FromTarget; + for (size_t i = 0; i < num; i++) + { + const wchar_t c = s[i]; + if (c == 0) + break; + dest += c; + } + // PRF_(PrintFormat_AndData("SetData", etc->cfFormat, src, size)) + PRF3_W(DestDirPrefix_FromTarget); + } + else if (etc->cfFormat == m_Format_7zip_SetTransfer) + { + /* 7-zip's CDropTarget calls SetData() for m_Format_7zip_SetTransfer + with (release == FALSE) */ + if (size < sizeof(CDataObject_SetTransfer)) + return E_FAIL; + const CDataObject_SetTransfer *t = (const CDataObject_SetTransfer *)src; + if (!t->Check()) + return E_FAIL; + m_Transfer = *t; + if (t->Target.FuncType != k_DragTargetMode_Leave) + m_Transfer_WasSet = true; + bool needProcessBySource = !DestDirPrefix_FromTarget.IsEmpty(); + if (t->Target.FuncType == k_DragTargetMode_Drop_Begin) + { + if (t->Target.Cmd_Type != NDragMenu::k_Copy_Base + // || t->Target.Cmd_Effect != DROPEFFECT_COPY + ) + needProcessBySource = false; + } + if (t->Target.FuncType == k_DragTargetMode_Drop_End) + { + if (t->Target.Flags & k_TargetFlags_MustBeProcessedBySource) + needProcessBySource = true; + else if (t->Target.Flags & k_TargetFlags_WasProcessed) + needProcessBySource = false; + } + DoNotProcessInTarget = needProcessBySource; + } + else + { + // SetData() from Explorer Target: + if (etc->cfFormat == m_Format_PerformedDropEffect) + { + m_PerformedDropEffect_WasSet = false; + if (size == sizeof(DWORD)) + { + m_PerformedDropEffect = *(const DWORD *)src; + m_PerformedDropEffect_WasSet = true; + } + } + else if (etc->cfFormat == m_Format_LogicalPerformedDropEffect) + { + m_LogicalPerformedDropEffect_WasSet = false; + if (size == sizeof(DWORD)) + { + m_LogicalPerformedDropEffect = *(const DWORD *)src; + m_LogicalPerformedDropEffect_WasSet = true; + } + } + else if (etc->cfFormat == m_Format_DropDescription) + { + // drop description contains only name of dest folder without full path + #ifdef SHOW_DEBUG_DRAG + if (size == sizeof(MY_DROPDESCRIPTION)) + { + // const MY_DROPDESCRIPTION *s = (const MY_DROPDESCRIPTION *)src; + // PRF3_W(s->szMessage); + // PRF3_W(s->szInsert); + } + #endif + } + else if (etc->cfFormat == m_Format_TargetCLSID) + { + // it's called after call QueryContinueDrag() (keyState & MK_LBUTTON) == 0 + // Shell File System Folder (explorer) guid: F3364BA0-65B9-11CE-A9BA-00AA004AE837 + #ifdef SHOW_DEBUG_DRAG + if (size == 16) + { + PrintFormat_GUIDToStringW((const Byte *)src); + } + #endif + } + else if (etc->cfFormat == m_Format_DisableDragText) + { + // (size == 4) (UInt32 value) + // value==0 : if drag to folder item or folder + // value==1 : if drag to file or non list_view */ + } + else if ( + etc->cfFormat == m_Format_IsShowingLayered || + etc->cfFormat == m_Format_IsShowingText) + { + // (size == 4) (UInt32 value) value==0 : + } + else + hres = DV_E_FORMATETC; + // hres = E_NOTIMPL; // for debug + // hres = DV_E_FORMATETC; // for debug + } + + GlobalUnlock(medium->hGlobal); + return hres; +} + + + +static HGLOBAL DuplicateGlobalMem(HGLOBAL srcGlobal) +{ + /* GlobalSize() returns 0: If the specified handle + is not valid or if the object has been discarded */ + const SIZE_T size = GlobalSize(srcGlobal); + if (size == 0) + return NULL; + // GlobalLock() can return NULL, if memory block has a zero-byte size + const void *src = GlobalLock(srcGlobal); + if (!src) + return NULL; + HGLOBAL destGlobal = GlobalAlloc(GHND | GMEM_SHARE, size); + if (destGlobal) + { + void *dest = GlobalLock(destGlobal); + if (!dest) + { + GlobalFree(destGlobal); + destGlobal = NULL; + } + else + { + memcpy(dest, src, size); + GlobalUnlock(destGlobal); + } + } + GlobalUnlock(srcGlobal); + return destGlobal; +} + + +static bool Medium_CopyFrom(LPSTGMEDIUM medium, const void *data, size_t size) +{ + medium->tymed = TYMED_NULL; + medium->pUnkForRelease = NULL; + medium->hGlobal = NULL; + const HGLOBAL global = GlobalAlloc(GHND | GMEM_SHARE, size); + if (!global) + return false; + void *dest = GlobalLock(global); + if (!dest) + { + GlobalFree(global); + return false; + } + memcpy(dest, data, size); + GlobalUnlock(global); + medium->hGlobal = global; + medium->tymed = TYMED_HGLOBAL; + return true; +} + + +Z7_COMWF_B CDataObject::GetData(LPFORMATETC etc, LPSTGMEDIUM medium) +{ + try { + PRF_(PrintFormat("-- GetData", etc->cfFormat)) + + medium->tymed = TYMED_NULL; + medium->pUnkForRelease = NULL; + medium->hGlobal = NULL; + + if (NeedCall_Copy && !Copy_WasCalled) + CopyFromPanelTo_Folder(); + + // PRF3("+ CDataObject::GetData"); + // PrintFormat(etc->cfFormat); + HGLOBAL global; + RINOK(QueryGetData(etc)) + + /* + if (etc->cfFormat == m_Format_FileOpFlags) + global = m_hGlobal_FileOpFlags; + else if (etc->cfFormat == m_Format_PreferredDropEffect) + { + // Explorer requests PreferredDropEffect only if Move/Copy selection is possible: + // Shift is not pressed and Ctrl is not pressed + PRF3("------ CDataObject::GetData() PreferredDropEffect"); + global = m_hGlobal_PreferredDropEffect; + } + else + */ + if (etc->cfFormat == m_Etc.cfFormat) // CF_HDROP + global = UsePreGlobal ? m_hGlobal_HDROP_Pre : m_hGlobal_HDROP_Final; + else if (etc->cfFormat == m_Format_7zip_GetTransfer) + { + CDataObject_GetTransfer transfer; + if (m_DestDirPrefix_FromTarget_WasSet) + { + transfer.Flags |= k_SourceFlags_SetTargetFolder; + } + if (!DestDirPrefix_FromTarget.IsEmpty()) + { + transfer.Flags |= k_SourceFlags_SetTargetFolder_NonEmpty; + } + if (IsTempFiles) + { + transfer.Flags |= k_SourceFlags_TempFiles; + transfer.Flags |= k_SourceFlags_WaitFinish; + transfer.Flags |= k_SourceFlags_NeedExtractOpToFs; + if (UsePreGlobal) + transfer.Flags |= k_SourceFlags_NamesAreParent; + } + else + transfer.Flags |= k_SourceFlags_DoNotWaitFinish; + + if (IsRightButton) + transfer.Flags |= k_SourceFlags_RightButton; + else + transfer.Flags |= k_SourceFlags_LeftButton; + + if (DoNotProcessInTarget) + transfer.Flags |= k_SourceFlags_DoNotProcessInTarget; + if (Copy_WasCalled) + transfer.Flags |= k_SourceFlags_Copy_WasCalled; + + if (Medium_CopyFrom(medium, &transfer, sizeof(transfer))) + return S_OK; + return E_OUTOFMEMORY; + } + else + return DV_E_FORMATETC; + + if (!global) + return DV_E_FORMATETC; + medium->tymed = m_Etc.tymed; + medium->hGlobal = DuplicateGlobalMem(global); + if (!medium->hGlobal) + return E_OUTOFMEMORY; + return S_OK; + } catch(...) { return E_FAIL; } +} + +Z7_COMWF_B CDataObject::GetDataHere(LPFORMATETC /* etc */, LPSTGMEDIUM /* medium */) +{ + PRF3("CDataObject::GetDataHere()"); + // Seems Windows doesn't call it, so we will not implement it. + return E_UNEXPECTED; +} + + +/* + IDataObject::QueryGetData() Determines whether the data object is capable of + rendering the data as specified. Objects attempting a paste or drop + operation can call this method before calling IDataObject::GetData + to get an indication of whether the operation may be successful. + + The client of a data object calls QueryGetData to determine whether + passing the specified FORMATETC structure to a subsequent call to + IDataObject::GetData is likely to be successful. + + we check Try_QueryGetData with CF_HDROP +*/ + +Z7_COMWF_B CDataObject::QueryGetData(LPFORMATETC etc) +{ + PRF3("-- CDataObject::QueryGetData()"); + if ( etc->cfFormat == m_Etc.cfFormat // CF_HDROP + || etc->cfFormat == m_Format_7zip_GetTransfer + // || (etc->cfFormat == m_Format_FileOpFlags && (HGLOBAL)m_hGlobal_FileOpFlags) + // || (etc->cfFormat == m_Format_PreferredDropEffect && (HGLOBAL)m_hGlobal_PreferredDropEffect) + ) + { + } + else + return DV_E_FORMATETC; + if (etc->dwAspect != m_Etc.dwAspect) + return DV_E_DVASPECT; + /* GetData(): It is possible to specify more than one medium by using the Boolean OR + operator, allowing the method to choose the best medium among those specified. */ + if ((etc->tymed & m_Etc.tymed) == 0) + return DV_E_TYMED; + return S_OK; +} + +Z7_COMWF_B CDataObject::EnumFormatEtc(DWORD direction, LPENUMFORMATETC FAR* enumFormatEtc) +{ + // we don't enumerate for DATADIR_SET. Seems it can work without it. + if (direction != DATADIR_GET) + return E_NOTIMPL; + // we don't enumerate for m_Format_FileOpFlags also. Seems it can work without it. + return CreateEnumFormatEtc(1, &m_Etc, enumFormatEtc); +} + + + +//////////////////////////////////////////////////////// + +class CDropSource Z7_final: + public IDropSource, + public CMyUnknownImp +{ + Z7_COM_UNKNOWN_IMP_1_MT(IDropSource) + STDMETHOD(QueryContinueDrag)(BOOL escapePressed, DWORD keyState) Z7_override; + STDMETHOD(GiveFeedback)(DWORD effect) Z7_override; + + DWORD m_Effect; +public: + CDataObject *DataObjectSpec; + CMyComPtr DataObject; + + HRESULT DragProcessing_HRESULT; + bool DragProcessing_WasFinished; + + CDropSource(): + m_Effect(DROPEFFECT_NONE), + // Panel(NULL), + DragProcessing_HRESULT(S_OK), + DragProcessing_WasFinished(false) + {} +}; + +// static bool g_Debug = 0; + + +Z7_COMWF_B CDropSource::QueryContinueDrag(BOOL escapePressed, DWORD keyState) +{ + // try { + + /* Determines whether a drag-and-drop operation should be continued, canceled, or completed. + escapePressed : Indicates whether the Esc key has been pressed + since the previous call to QueryContinueDrag + or to DoDragDrop if this is the first call to QueryContinueDrag: + TRUE : the end user has pressed the escape key; + FALSE : it has not been pressed. + keyState : The current state of the keyboard modifier keys on the keyboard. + Possible values can be a combination of any of the flags: + MK_CONTROL, MK_SHIFT, MK_ALT, MK_BUTTON, MK_LBUTTON, MK_MBUTTON, and MK_RBUTTON. + */ + #ifdef SHOW_DEBUG_DRAG + { + AString s ("CDropSource::QueryContinueDrag()"); + s.Add_Space(); + s += "keystate="; + s.Add_UInt32(keyState); + PRF4(s); + } + #endif + + /* + if ((keyState & MK_LBUTTON) == 0) + { + // PRF4("CDropSource::QueryContinueDrag() (keyState & MK_LBUTTON) == 0"); + g_Debug = true; + } + else + { + // PRF4("CDropSource::QueryContinueDrag() (keyState & MK_LBUTTON) != 0"); + } + */ + + if (escapePressed) + { + // The drag operation should be canceled with no drop operation occurring. + DragProcessing_WasFinished = true; + DragProcessing_HRESULT = DRAGDROP_S_CANCEL; + return DRAGDROP_S_CANCEL; + } + + if (DragProcessing_WasFinished) + return DragProcessing_HRESULT; + + if ((keyState & MK_RBUTTON) != 0) + { + if (!DataObjectSpec->IsRightButton) + { + DragProcessing_WasFinished = true; + DragProcessing_HRESULT = DRAGDROP_S_CANCEL; + return DRAGDROP_S_CANCEL; + } + return S_OK; + } + + if ((keyState & MK_LBUTTON) != 0) + { + if (DataObjectSpec->IsRightButton) + { + DragProcessing_WasFinished = true; + DragProcessing_HRESULT = DRAGDROP_S_CANCEL; + return DRAGDROP_S_CANCEL; + } + /* The drag operation should continue. This result occurs if no errors are detected, + the mouse button starting the drag-and-drop operation has not been released, + and the Esc key has not been detected. */ + return S_OK; + } + { + // the mouse button starting the drag-and-drop operation has been released. + + /* Win10 probably calls DragOver()/GiveFeedback() just before LBUTTON releasing. + so m_Effect is effect returned by DropTarget::DragOver() + just before LBUTTON releasing. + So here we can use Effect sent to last GiveFeedback() */ + + if (m_Effect == DROPEFFECT_NONE) + { + DragProcessing_WasFinished = true; + DragProcessing_HRESULT = DRAGDROP_S_CANCEL; + // Drop target cannot accept the data. So we cancel drag and drop + // maybe return DRAGDROP_S_DROP also OK here ? + // return DRAGDROP_S_DROP; // for debug + return DRAGDROP_S_CANCEL; + } + + // we switch to real names for items that will be created in temp folder + DataObjectSpec->UsePreGlobal = false; + DataObjectSpec->Copy_HRESULT = S_OK; + // MoveMode = (((keyState & MK_SHIFT) != 0) && MoveIsAllowed); + /* + if (DataObjectSpec->IsRightButton) + return DRAGDROP_S_DROP; + */ + + if (DataObjectSpec->IsTempFiles) + { + if (!DataObjectSpec->DestDirPrefix_FromTarget.IsEmpty()) + { + /* we know the destination Path. + So we can copy or extract items later in Source with simpler code. */ + DataObjectSpec->DoNotProcessInTarget = true; + // return DRAGDROP_S_CANCEL; + } + else + { + DataObjectSpec->NeedCall_Copy = true; + /* + if (Copy_HRESULT != S_OK || !Messages.IsEmpty()) + { + DragProcessing_WasFinished = true; + DragProcessing_HRESULT = DRAGDROP_S_CANCEL; + return DRAGDROP_S_CANCEL; + } + */ + } + } + DragProcessing_HRESULT = DRAGDROP_S_DROP; + DragProcessing_WasFinished = true; + return DRAGDROP_S_DROP; + } + // } catch(...) { return E_FAIL; } +} + + +Z7_COMWF_B CDropSource::GiveFeedback(DWORD effect) +{ + // PRF3("CDropSource::GiveFeedback"); + /* Enables a source application to give visual feedback to the end user + during a drag-and-drop operation by providing the DoDragDrop function + with an enumeration value specifying the visual effect. + in (effect): + The DROPEFFECT value returned by the most recent call to + IDropTarget::DragEnter, + IDropTarget::DragOver, + or DROPEFFECT_NONE after IDropTarget::DragLeave. + 0: DROPEFFECT_NONE + 1: DROPEFFECT_COPY + 2: DROPEFFECT_MOVE + 4: DROPEFFECT_LINK + 0x80000000: DROPEFFECT_SCROLL + The dwEffect parameter can include DROPEFFECT_SCROLL, indicating that the + source should put up the drag-scrolling variation of the appropriate pointer. + */ + m_Effect = effect; + + #ifdef SHOW_DEBUG_DRAG + AString w ("GiveFeedback effect="); + if (effect & DROPEFFECT_SCROLL) + w += " SCROLL "; + w.Add_UInt32(effect & ~DROPEFFECT_SCROLL); + // if (g_Debug) + PRF4(w); + #endif + + /* S_OK : no special drag and drop cursors. + Maybe it's for case where we created custom custom cursors. + DRAGDROP_S_USEDEFAULTCURSORS: Indicates successful completion of the method, + and requests OLE to update the cursor using the OLE-provided default cursors. */ + // return S_OK; // for debug + return DRAGDROP_S_USEDEFAULTCURSORS; +} + + + +/* +static bool Global_SetUInt32(NMemory::CGlobal &hg, const UInt32 v) +{ + if (!hg.Alloc(GHND | GMEM_SHARE, sizeof(v))) + return false; + NMemory::CGlobalLock dropLock(hg); + *(UInt32 *)dropLock.GetPointer() = v; + return true; +} +*/ + +static bool CopyNamesToHGlobal(NMemory::CGlobal &hgDrop, const UStringVector &names) +{ + size_t totalLen = 1; + + #ifndef _UNICODE + if (!g_IsNT) + { + AStringVector namesA; + unsigned i; + for (i = 0; i < names.Size(); i++) + namesA.Add(GetSystemString(names[i])); + for (i = 0; i < namesA.Size(); i++) + totalLen += namesA[i].Len() + 1; + + if (!hgDrop.Alloc(GHND | GMEM_SHARE, totalLen * sizeof(CHAR) + sizeof(DROPFILES))) + return false; + + NMemory::CGlobalLock dropLock(hgDrop); + DROPFILES *dropFiles = (DROPFILES *)dropLock.GetPointer(); + if (!dropFiles) + return false; + dropFiles->fNC = FALSE; + dropFiles->pt.x = 0; + dropFiles->pt.y = 0; + dropFiles->pFiles = sizeof(DROPFILES); + dropFiles->fWide = FALSE; + CHAR *p = (CHAR *) (void *) ((BYTE *)dropFiles + sizeof(DROPFILES)); + for (i = 0; i < namesA.Size(); i++) + { + const AString &s = namesA[i]; + const unsigned fullLen = s.Len() + 1; + MyStringCopy(p, (const char *)s); + p += fullLen; + totalLen -= fullLen; + } + *p = 0; + } + else + #endif + { + unsigned i; + for (i = 0; i < names.Size(); i++) + totalLen += names[i].Len() + 1; + + if (!hgDrop.Alloc(GHND | GMEM_SHARE, totalLen * sizeof(WCHAR) + sizeof(DROPFILES))) + return false; + + NMemory::CGlobalLock dropLock(hgDrop); + DROPFILES *dropFiles = (DROPFILES *)dropLock.GetPointer(); + if (!dropFiles) + return false; + /* fNC: + TRUE : pt specifies the screen coordinates of a point in a window's nonclient area. + FALSE : pt specifies the client coordinates of a point in the client area. + */ + dropFiles->fNC = FALSE; + dropFiles->pt.x = 0; + dropFiles->pt.y = 0; + dropFiles->pFiles = sizeof(DROPFILES); + dropFiles->fWide = TRUE; + WCHAR *p = (WCHAR *) (void *) ((BYTE *)dropFiles + sizeof(DROPFILES)); + for (i = 0; i < names.Size(); i++) + { + const UString &s = names[i]; + const unsigned fullLen = s.Len() + 1; + MyStringCopy(p, (const WCHAR *)s); + p += fullLen; + totalLen -= fullLen; + } + *p = 0; + } + // if (totalLen != 1) return false; + return true; +} + + +void CPanel::OnDrag(LPNMLISTVIEW /* nmListView */, bool isRightButton) +{ + PRF("CPanel::OnDrag"); + if (!DoesItSupportOperations()) + return; + + CDisableTimerProcessing disableTimerProcessing2(*this); + + CRecordVector indices; + Get_ItemIndices_Operated(indices); + if (indices.Size() == 0) + return; + + // CSelectedState selState; + // SaveSelectedState(selState); + + const bool isFSFolder = IsFSFolder(); + // why we don't allow drag with rightButton from archive? + if (!isFSFolder && isRightButton) + return; + + UString dirPrefix; + CTempDir tempDirectory; + + CDataObject *dataObjectSpec = new CDataObject; + CMyComPtr dataObject = dataObjectSpec; + dataObjectSpec->IsRightButton = isRightButton; + + { + /* we can change confirmation mode and another options. + Explorer target requests that FILEOP_FLAGS value. */ + /* + const FILEOP_FLAGS fopFlags = + FOF_NOCONFIRMATION + | FOF_NOCONFIRMMKDIR + | FOF_NOERRORUI + | FOF_SILENT; + // | FOF_SIMPLEPROGRESS; // it doesn't work as expected in Win10 + Global_SetUInt32(dataObjectSpec->m_hGlobal_FileOpFlags, fopFlags); + // dataObjectSpec->m_hGlobal_FileOpFlags.Free(); // for debug : disable these options + */ + } + { + /* we can change Preferred DropEffect. + Explorer target requests that FILEOP_FLAGS value. */ + /* + const DWORD effect = DROPEFFECT_MOVE; // DROPEFFECT_COPY; + Global_SetUInt32(dataObjectSpec->m_hGlobal_PreferredDropEffect, effect); + */ + } + if (isFSFolder) + { + dirPrefix = GetFsPath(); // why this in 22.01 ? + dataObjectSpec->UsePreGlobal = false; + // dataObjectSpec->IsTempFiles = false; + } + else + { + if (!tempDirectory.Create(kTempDirPrefix)) + { + MessageBox_Error(L"Can't create temp folder"); + return; + } + dirPrefix = fs2us(tempDirectory.GetPath()); + { + UStringVector names; + names.Add(dirPrefix); + dataObjectSpec->IsTempFiles = true; + dataObjectSpec->UsePreGlobal = true; + if (!CopyNamesToHGlobal(dataObjectSpec->m_hGlobal_HDROP_Pre, names)) + return; + } + NFile::NName::NormalizeDirPathPrefix(dirPrefix); + /* + { + FString path2 = dirPrefix; + path2 += "1.txt"; + CopyFileW(L"C:\\1\\1.txt", path2, FALSE); + } + */ + } + + { + UStringVector names; + // names variable is USED for drag and drop from 7-zip to Explorer or to 7-zip archive folder. + // names variable is NOT USED for drag and drop from 7-zip to 7-zip File System folder. + FOR_VECTOR (i, indices) + { + const UInt32 index = indices[i]; + UString s; + if (isFSFolder) + s = GetItemRelPath(index); + else + { + s = GetItemName(index); + /* + // We use (keepAndReplaceEmptyPrefixes = true) in CAgentFolder::Extract + // So the following code is not required. + // Maybe we also can change IFolder interface and send some flag also. + if (s.IsEmpty()) + { + // Correct_FsFile_Name("") returns "_". + // If extracting code removes empty folder prefixes from path (as it was in old version), + // Explorer can't find "_" folder in temp folder. + // We can ask Explorer to copy parent temp folder "7zE" instead. + names.Clear(); + names.Add(dirPrefix2); + break; + } + */ + s = Get_Correct_FsFile_Name(s); + } + names.Add(dirPrefix + s); + } + if (!CopyNamesToHGlobal(dataObjectSpec->m_hGlobal_HDROP_Final, names)) + return; + } + + CDropSource *dropSourceSpec = new CDropSource; + CMyComPtr dropSource = dropSourceSpec; + dataObjectSpec->Panel = this; + dataObjectSpec->Indices = indices; + dataObjectSpec->SrcDirPrefix_Temp = dirPrefix; + + dropSourceSpec->DataObjectSpec = dataObjectSpec; + dropSourceSpec->DataObject = dataObjectSpec; + + + /* + CTime - file creation timestamp. + There are two operations in Windows with Drag and Drop: + COPY_OPERATION : icon with Plus sign : CTime will be set as current_time. + MOVE_OPERATION : icon without Plus sign : CTime will be preserved. + + Note: if we call DoDragDrop() with (effectsOK = DROPEFFECT_MOVE), then + it will use MOVE_OPERATION and CTime will be preserved. + But MoveFile() function doesn't preserve CTime, if different volumes are used. + Why it's so? + Does DoDragDrop() use some another function (not MoveFile())? + + if (effectsOK == DROPEFFECT_COPY) it works as COPY_OPERATION + + if (effectsOK == DROPEFFECT_MOVE) drag works as MOVE_OPERATION + + if (effectsOK == (DROPEFFECT_COPY | DROPEFFECT_MOVE)) + { + if we drag file to same volume, then Windows suggests: + CTRL - COPY_OPERATION + [default] - MOVE_OPERATION + + if we drag file to another volume, then Windows suggests + [default] - COPY_OPERATION + SHIFT - MOVE_OPERATION + } + + We want to use MOVE_OPERATION for extracting from archive (open in 7-Zip) to Explorer: + It has the following advantages: + 1) it uses fast MOVE_OPERATION instead of slow COPY_OPERATION and DELETE, if same volume. + 2) it preserves CTime + + Some another programs support only COPY_OPERATION. + So we can use (DROPEFFECT_COPY | DROPEFFECT_MOVE) + + Also another program can return from DoDragDrop() before + files using. But we delete temp folder after DoDragDrop(), + and another program can't open input files in that case. + + We create objects: + IDropSource *dropSource + IDataObject *dataObject + if DropTarget is 7-Zip window, then 7-Zip's + IDropTarget::DragOver() sets DestDirPrefix_FromTarget in IDataObject. + and + IDropSource::QueryContinueDrag() sets DoNotProcessInTarget, if DestDirPrefix_FromTarget is not empty. + So we can detect destination path after DoDragDrop(). + Now we don't know any good way to detect destination path for D&D to Explorer. + */ + + /* + DWORD effectsOK = DROPEFFECT_COPY; + if (moveIsAllowed) + effectsOK |= DROPEFFECT_MOVE; + */ + const bool moveIsAllowed = isFSFolder; + _panelCallback->DragBegin(); + PRF("=== DoDragDrop()"); + DWORD effect = 0; + // 18.04: was changed + const DWORD effectsOK = DROPEFFECT_MOVE | DROPEFFECT_COPY; + // effectsOK |= (1 << 8); // for debug + HRESULT res = ::DoDragDrop(dataObject, dropSource, effectsOK, &effect); + PRF("=== After DoDragDrop()"); + _panelCallback->DragEnd(); + + /* + Win10 drag and drop to Explorer: + DoDragDrop() output variables: + for MOVE operation: + { + effect == DROPEFFECT_NONE; + dropSourceSpec->m_PerformedDropEffect == DROPEFFECT_MOVE; + } + for COPY operation: + { + effect == DROPEFFECT_COPY; + dropSourceSpec->m_PerformedDropEffect == DROPEFFECT_COPY; + } + DOCs: The source inspects the two values that can be returned by the target. + If both are set to DROPEFFECT_MOVE, it completes the unoptimized move + by deleting the original data. Otherwise, the target did an optimized + move and the original data has been deleted. + + We didn't see "unoptimized move" case (two values of DROPEFFECT_MOVE), + where we still need to delete source files. + So we don't delete files after DoDragDrop(). + + Also DOCs say for "optimized move": + The target also calls the data object's IDataObject::SetData method and passes + it a CFSTR_PERFORMEDDROPEFFECT format identifier set to DROPEFFECT_NONE. + but actually in Win10 we always have + (dropSourceSpec->m_PerformedDropEffect == DROPEFFECT_MOVE) + for any MOVE operation. + */ + + const bool canceled = (res == DRAGDROP_S_CANCEL); + + CDisableNotify disableNotify(*this); + + if (res == DRAGDROP_S_DROP) + { + /* DRAGDROP_S_DROP is returned. It means that + - IDropTarget::Drop() was called, + - IDropTarget::Drop() returned (ret_code >= 0) + */ + res = dataObjectSpec->Copy_HRESULT; + bool need_Process = dataObjectSpec->DoNotProcessInTarget; + if (dataObjectSpec->m_Transfer_WasSet) + { + if (dataObjectSpec->m_Transfer.Target.FuncType == k_DragTargetMode_Drop_End) + { + if (dataObjectSpec->m_Transfer.Target.Flags & k_TargetFlags_MustBeProcessedBySource) + need_Process = true; + } + } + + if (need_Process) + if (!dataObjectSpec->DestDirPrefix_FromTarget.IsEmpty()) + { + if (!NFile::NName::IsAltStreamPrefixWithColon(dataObjectSpec->DestDirPrefix_FromTarget)) + NFile::NName::NormalizeDirPathPrefix(dataObjectSpec->DestDirPrefix_FromTarget); + CCopyToOptions options; + options.folder = dataObjectSpec->DestDirPrefix_FromTarget; + // if MOVE is not allowed, we just use COPY operation + /* it was 7-zip's Target that set non-empty dataObjectSpec->DestDirPrefix_FromTarget. + it means that target didn't completed operation, + and we can use (effect) value returned by target via DoDragDrop(). + as indicator of type of operation + */ + // options.moveMode = (moveIsAllowed && effect == DROPEFFECT_MOVE) // before v23.00: + options.moveMode = moveIsAllowed; + if (moveIsAllowed) + { + if (dataObjectSpec->m_Transfer_WasSet) + options.moveMode = ( + dataObjectSpec->m_Transfer.Target.Cmd_Effect == DROPEFFECT_MOVE); + else + options.moveMode = (effect == DROPEFFECT_MOVE); + // we expect (DROPEFFECT_MOVE) as indicator of move operation for Drag&Drop MOVE ver 22.01. + } + res = CopyTo(options, indices, &dataObjectSpec->Messages); + } + /* + if (effect & DROPEFFECT_MOVE) + RefreshListCtrl(selState); + */ + } + else + { + // we ignore E_UNEXPECTED that is returned if we drag file to printer + if (res != DRAGDROP_S_CANCEL + && res != S_OK + && res != E_UNEXPECTED) + MessageBox_Error_HRESULT(res); + res = dataObjectSpec->Copy_HRESULT; + } + + if (!dataObjectSpec->Messages.IsEmpty()) + { + CMessagesDialog messagesDialog; + messagesDialog.Messages = &dataObjectSpec->Messages; + messagesDialog.Create((*this)); + } + + if (res != S_OK && res != E_ABORT) + { + // we restore Notify before MessageBox_Error_HRESULT. So we will see files selection + disableNotify.Restore(); + // SetFocusToList(); + MessageBox_Error_HRESULT(res); + } + if (res == S_OK && dataObjectSpec->Messages.IsEmpty() && !canceled) + KillSelection(); +} + + + + + +CDropTarget::CDropTarget(): + m_IsRightButton(false), + m_GetTransfer_WasSuccess(false), + m_DropIsAllowed(false), + m_PanelDropIsAllowed(false), + // m_DropHighlighted_SelectionIndex(-1), + // m_SubFolderIndex(-1), + m_Panel(NULL), + m_IsAppTarget(false), + m_TargetPath_WasSent_ToDataObject(false), + m_TargetPath_NonEmpty_WasSent_ToDataObject(false), + m_Transfer_WasSent_ToDataObject(false), + App(NULL), + SrcPanelIndex(-1), + TargetPanelIndex(-1) +{ + m_Format_7zip_SetTargetFolder = RegisterClipboardFormat(k_Format_7zip_SetTargetFolder); + m_Format_7zip_SetTransfer = RegisterClipboardFormat(k_Format_7zip_SetTransfer); + m_Format_7zip_GetTransfer = RegisterClipboardFormat(k_Format_7zip_GetTransfer); + + m_ProcessId = GetCurrentProcessId(); + // m_TransactionId = ((UInt64)m_ProcessId << 32) + 1; + // ClearState(); +} + +// clear internal state +void CDropTarget::ClearState() +{ + m_DataObject.Release(); + m_SourcePaths.Clear(); + + m_IsRightButton = false; + + m_GetTransfer_WasSuccess = false; + m_DropIsAllowed = false; + + m_PanelDropIsAllowed = false; + // m_SubFolderIndex = -1; + // m_DropHighlighted_SubFolderName.Empty(); + m_Panel = NULL; + m_IsAppTarget = false; + m_TargetPath_WasSent_ToDataObject = false; + m_TargetPath_NonEmpty_WasSent_ToDataObject = false; + m_Transfer_WasSent_ToDataObject = false; +} + +/* + IDataObject::QueryGetData() Determines whether the data object is capable of + rendering the data as specified. Objects attempting a paste or drop + operation can call this method before calling IDataObject::GetData + to get an indication of whether the operation may be successful. + + The client of a data object calls QueryGetData to determine whether + passing the specified FORMATETC structure to a subsequent call to + IDataObject::GetData is likely to be successful. + + We check Try_QueryGetData with CF_HDROP +*/ +/* +void CDropTarget::Try_QueryGetData(IDataObject *dataObject) +{ + FORMATETC etc = { CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; + m_DropIsAllowed = (dataObject->QueryGetData(&etc) == S_OK); +} +*/ + +static void ListView_SetItemState_DropHighlighted( + NControl::CListView &listView, int index, bool highlighted) +{ + // LVIS_DROPHILITED : The item is highlighted as a drag-and-drop target + /* + LVITEM item; + item.mask = LVIF_STATE; + item.iItem = index; + item.iSubItem = 0; + item.state = enable ? LVIS_DROPHILITED : 0; + item.stateMask = LVIS_DROPHILITED; + item.pszText = NULL; + listView.SetItem(&item); + */ + listView.SetItemState(index, highlighted ? LVIS_DROPHILITED : 0, LVIS_DROPHILITED); +} + +// Removes DropHighlighted state in ListView item, if it was set before +void CDropTarget::RemoveSelection() +{ + if (m_Panel) + { + m_Panel->m_DropHighlighted_SubFolderName.Empty(); + if (m_Panel->m_DropHighlighted_SelectionIndex >= 0) + { + ListView_SetItemState_DropHighlighted(m_Panel->_listView, + m_Panel->m_DropHighlighted_SelectionIndex, false); + m_Panel->m_DropHighlighted_SelectionIndex = -1; + } + } +} + +#ifdef UNDER_CE +#define ChildWindowFromPointEx(hwndParent, pt, uFlags) ChildWindowFromPoint(hwndParent, pt) +#endif + + +/* + PositionCursor() function sets m_Panel under cursor drop, and + m_SubFolderIndex/m_DropHighlighted_SubFolderName, if drop to some folder in Panel list. +*/ +/* +PositionCursor() uses as input variables: + m_DropIsAllowed must be set before PositionCursor() + if (m_DropHighlighted_SelectionIndex >= 0 && m_Panel) it uses m_Panel and removes previous selection +PositionCursor() sets + m_PanelDropIsAllowed + m_Panel + m_IsAppTarget + m_SubFolderIndex + m_DropHighlighted_SubFolderName + m_DropHighlighted_SelectionIndex +*/ +void CDropTarget::PositionCursor(const POINTL &ptl) +{ + RemoveSelection(); + + // m_SubFolderIndex = -1; + // m_DropHighlighted_SubFolderName.Empty(); + m_IsAppTarget = true; + m_Panel = NULL; + m_PanelDropIsAllowed = false; + + if (!m_DropIsAllowed) + return; + + POINT pt; + pt.x = ptl.x; + pt.y = ptl.y; + { + POINT pt2 = pt; + if (App->_window.ScreenToClient(&pt2)) + for (unsigned i = 0; i < kNumPanelsMax; i++) + if (App->IsPanelVisible(i)) + { + CPanel *panel = &App->Panels[i]; + if (panel->IsEnabled()) + if (::ChildWindowFromPointEx(App->_window, pt2, + CWP_SKIPINVISIBLE | CWP_SKIPDISABLED) == (HWND)*panel) + { + m_Panel = panel; + m_IsAppTarget = false; + if ((int)i == SrcPanelIndex) + return; // we don't allow to drop to source panel + break; + } + } + } + + m_PanelDropIsAllowed = true; + + if (!m_Panel) + { + if (TargetPanelIndex >= 0) + m_Panel = &App->Panels[TargetPanelIndex]; + // we don't need to find item in panel + return; + } + + // we will try to find and highlight drop folder item in listView under cursor + /* + m_PanelDropIsAllowed = m_Panel->DoesItSupportOperations(); + if (!m_PanelDropIsAllowed) + return; + */ + /* now we don't allow drop to subfolder under cursor, if dest panel is archive. + Another code must be fixed for that case, where we must use m_SubFolderIndex/m_DropHighlighted_SubFolderName */ + if (!m_Panel->IsFsOrPureDrivesFolder()) + return; + + if (::WindowFromPoint(pt) != (HWND)m_Panel->_listView) + return; + + LVHITTESTINFO info; + m_Panel->_listView.ScreenToClient(&pt); + info.pt = pt; + const int index = ListView_HitTest(m_Panel->_listView, &info); + if (index < 0) + return; + const unsigned realIndex = m_Panel->GetRealItemIndex(index); + if (realIndex == kParentIndex) + return; + if (!m_Panel->IsItem_Folder(realIndex)) + return; + // m_SubFolderIndex = (int)realIndex; + m_Panel->m_DropHighlighted_SubFolderName = m_Panel->GetItemName(realIndex); + ListView_SetItemState_DropHighlighted(m_Panel->_listView, index, true); + m_Panel->m_DropHighlighted_SelectionIndex = index; +} + + +/* returns true, if !m_IsAppTarget + and target is FS folder or altStream folder +*/ + +UInt32 CDropTarget::GetFolderType() const +{ + if (m_IsAppTarget || !m_Panel) + return k_FolderType_None; + if (m_Panel->IsFSFolder() || + (m_Panel->IsFSDrivesFolder() + && m_Panel->m_DropHighlighted_SelectionIndex >= 0)) + return k_FolderType_Fs; + if (m_Panel->IsAltStreamsFolder()) + return k_FolderType_AltStreams; + if (m_Panel->IsArcFolder()) + return k_FolderType_Archive; + return k_FolderType_Unknown; +} + +bool CDropTarget::IsFsFolderPath() const +{ + if (m_IsAppTarget || !m_Panel) + return false; + if (m_Panel->IsFSFolder()) + return true; + if (m_Panel->IsAltStreamsFolder()) + return true; + return m_Panel->IsFSDrivesFolder() && + m_Panel->m_DropHighlighted_SelectionIndex >= 0; +} + + +#define INIT_FORMATETC_HGLOBAL(type) { (type), NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL } + +static bool DataObject_GetData_GetTransfer(IDataObject *dataObject, + UINT a_Format_7zip_GetTransfer, CDataObject_GetTransfer &transfer) +{ + FORMATETC etc = INIT_FORMATETC_HGLOBAL((CLIPFORMAT)a_Format_7zip_GetTransfer); + NCOM::CStgMedium medium; + const HRESULT res = dataObject->GetData(&etc, &medium); + if (res != S_OK) + return false; + if (medium.tymed != TYMED_HGLOBAL) + return false; + const size_t size = GlobalSize(medium.hGlobal); + if (size < sizeof(transfer)) + return false; + NMemory::CGlobalLock dropLock(medium.hGlobal); + const CDataObject_GetTransfer *t = (const CDataObject_GetTransfer *)dropLock.GetPointer(); + if (!t) + return false; + if (!t->Check()) // isSetData + return false; + transfer = *t; + return true; +} + +/* + returns true, if all m_SourcePaths[] items are same drive + as destination drop path in m_Panel +*/ +bool CDropTarget::IsItSameDrive() const +{ + if (!m_Panel) + return false; + if (!IsFsFolderPath()) + return false; + + UString drive; + + if (m_Panel->IsFSFolder()) + { + drive = m_Panel->GetDriveOrNetworkPrefix(); + if (drive.IsEmpty()) + return false; + } + else if (m_Panel->IsFSDrivesFolder() + && m_Panel->m_DropHighlighted_SelectionIndex >= 0) + { + drive = m_Panel->m_DropHighlighted_SubFolderName; + drive.Add_PathSepar(); + } + else + return false; + + if (m_SourcePaths.Size() == 0) + return false; + + FOR_VECTOR (i, m_SourcePaths) + { + if (!m_SourcePaths[i].IsPrefixedBy_NoCase(drive)) + return false; + } + return true; +} + + +/* + There are 2 different actions, when we drag to 7-Zip: + 1) if target panel is "7-Zip" FS and any of the 2 cases: + - Drag from any non "7-Zip" program; + or + - Drag from "7-Zip" to non-panel area of "7-Zip". + We want to create new archive for that operation with "Add to Archive" window. + 2) all another operations work as usual file COPY/MOVE + - Drag from "7-Zip" FS to "7-Zip" FS. + COPY/MOVE are supported. + - Drag to open archive in 7-Zip. + We want to update archive. + We replace COPY to MOVE. + - Drag from "7-Zip" archive to "7-Zip" FS. + We replace COPY to MOVE. +*/ + +// we try to repeat Explorer's effects. +// out: 0 - means that use default effect +static DWORD GetEffect_ForKeys(DWORD keyState) +{ + if (keyState & MK_CONTROL) + { + if (keyState & MK_ALT) + return 0; + if (keyState & MK_SHIFT) + return DROPEFFECT_LINK; // CONTROL + SHIFT + return DROPEFFECT_COPY; // CONTROL + } + // no CONTROL + if (keyState & MK_SHIFT) + { + if (keyState & MK_ALT) + return 0; + return DROPEFFECT_MOVE; // SHIFT + } + // no CONTROL, no SHIFT + if (keyState & MK_ALT) + return DROPEFFECT_LINK; // ALT + return 0; +} + + +/* GetEffect() uses m_TargetPath_WasSentToDataObject + to disale MOVE operation, if Source is not 7-Zip +*/ +DWORD CDropTarget::GetEffect(DWORD keyState, POINTL /* pt */, DWORD allowedEffect) const +{ + // (DROPEFFECT_NONE == 0) + if (!m_DropIsAllowed || !m_PanelDropIsAllowed) + return 0; + if (!IsFsFolderPath() || !m_TargetPath_WasSent_ToDataObject) + { + // we don't allow MOVE, if Target is archive or Source is not 7-Zip + // disabled for debug: + // allowedEffect &= ~DROPEFFECT_MOVE; + } + DWORD effect; + { + effect = GetEffect_ForKeys(keyState); + if (effect == DROPEFFECT_LINK) + return 0; + effect &= allowedEffect; + } + if (effect == 0) + { + if (allowedEffect & DROPEFFECT_COPY) + effect = DROPEFFECT_COPY; + if (allowedEffect & DROPEFFECT_MOVE) + { + /* MOVE operation can be optimized. So MOVE is preferred way + for default action, if Source and Target are at same drive */ + if (IsItSameDrive()) + effect = DROPEFFECT_MOVE; + } + } + return effect; +} + + +/* returns: + - target folder path prefix, if target is FS folder + - empty string, if target is not FS folder +*/ +UString CDropTarget::GetTargetPath() const +{ + if (!IsFsFolderPath()) + return UString(); + UString path = m_Panel->GetFsPath(); + if (/* m_SubFolderIndex >= 0 && */ + !m_Panel->m_DropHighlighted_SubFolderName.IsEmpty()) + { + path += m_Panel->m_DropHighlighted_SubFolderName; + path.Add_PathSepar(); + } + return path; +} + + +/* +if IDropSource is Win10-Explorer +-------------------------------- + As in MS DOCs: + The source inspects the two (effect) values that can be returned by the target: + 1) SetData(CFSTR_PERFORMEDDROPEFFECT) + 2) returned value (*effect) by + CDropTarget::Drop(IDataObject *dataObject, DWORD keyState, + POINTL pt, DWORD *effect) + If both are set to DROPEFFECT_MOVE, Explorer completes the unoptimized move by deleting + the original data. + // Otherwise, the target did an optimized move and the original data has been deleted. +*/ + + +/* + Send targetPath from target to dataObject (to Source) + input: set (enablePath = false) to send empty path + returns true, if SetData() returns S_OK : (source is 7-zip) + returns false, if SetData() doesn't return S_OK : (source is Explorer) +*/ +bool CDropTarget::SendToSource_TargetPath_enable(IDataObject *dataObject, bool enablePath) +{ + m_TargetPath_NonEmpty_WasSent_ToDataObject = false; + UString path; + if (enablePath) + path = GetTargetPath(); + PRF("CDropTarget::SetPath"); + PRF_W(path); + if (!dataObject || m_Format_7zip_SetTargetFolder == 0) + return false; + FORMATETC etc = INIT_FORMATETC_HGLOBAL((CLIPFORMAT)m_Format_7zip_SetTargetFolder); + STGMEDIUM medium; + medium.tymed = etc.tymed; + medium.pUnkForRelease = NULL; + const size_t num = path.Len() + 1; // + (1 << 19) // for debug + medium.hGlobal = GlobalAlloc(GHND | GMEM_SHARE, num * sizeof(wchar_t)); + if (!medium.hGlobal) + return false; + // Sleep(1000); + wchar_t *dest = (wchar_t *)GlobalLock(medium.hGlobal); + // Sleep(1000); + bool res = false; + if (dest) + { + MyStringCopy(dest, (const wchar_t *)path); + GlobalUnlock(medium.hGlobal); + // OutputDebugString("m_DataObject->SetData"); + const BOOL release = FALSE; // that way is more simple for correct releasing. + // TRUE; // for debug : is not good for some cases. + /* If DropSource is Win10-Explorer, dataObject->SetData() returns E_NOTIMPL; */ + const HRESULT hres = dataObject->SetData(&etc, &medium, release); + // Sleep(1000); + res = (hres == S_OK); + } + + ReleaseStgMedium(&medium); + if (res && !path.IsEmpty()) + m_TargetPath_NonEmpty_WasSent_ToDataObject = true; + // Sleep(1000); + return res; +} + + +void CDropTarget::SendToSource_auto(IDataObject *dataObject, + const CTargetTransferInfo &info) +{ + /* we try to send target path to Source. + If Source is 7-Zip, then it will accept k_Format_7zip_SetTargetFolder. + That sent path will be non-Empty, if this target is FS folder and drop is allowed */ + bool need_Send = false; + if ( info.FuncType == k_DragTargetMode_Enter + || info.FuncType == k_DragTargetMode_Over + || (info.FuncType == k_DragTargetMode_Drop_Begin + // && targetOp_Cmd != NDragMenu::k_None + && info.Cmd_Type != NDragMenu::k_Cancel)) + // if (!g_CreateArchive_for_Drag_from_7zip) + need_Send = m_DropIsAllowed && m_PanelDropIsAllowed && IsFsFolderPath(); + m_TargetPath_WasSent_ToDataObject = SendToSource_TargetPath_enable(dataObject, need_Send); + SendToSource_TransferInfo(dataObject, info); +} + + +bool CDropTarget::SendToSource_TransferInfo(IDataObject *dataObject, + const CTargetTransferInfo &info) +{ + m_Transfer_WasSent_ToDataObject = false; + PRF("CDropTarget::SendToSource_TransferInfo"); + + if (!dataObject || m_Format_7zip_SetTransfer == 0) + return false; + FORMATETC etc = INIT_FORMATETC_HGLOBAL((CLIPFORMAT)m_Format_7zip_SetTransfer); + STGMEDIUM medium; + medium.tymed = etc.tymed; + medium.pUnkForRelease = NULL; + CDataObject_SetTransfer transfer; + const size_t size = sizeof(transfer); // + (1 << 19) // for debug + // OutputDebugString("GlobalAlloc"); + medium.hGlobal = GlobalAlloc(GHND | GMEM_SHARE, size); + // Sleep(1000); + if (!medium.hGlobal) + return false; + // OutputDebugString("GlobalLock"); + void *dest = (wchar_t *)GlobalLock(medium.hGlobal); + // Sleep(1000); + bool res = false; + if (dest) + { + transfer.Init(); + transfer.Target = info; + + memcpy(dest, &transfer, sizeof(transfer)); + GlobalUnlock(medium.hGlobal); + // OutputDebugString("m_DataObject->SetData"); + const BOOL release = FALSE; // that way is more simple for correct releasing. + // TRUE; // for debug : is not good for some cases + const HRESULT hres = dataObject->SetData(&etc, &medium, release); + res = (hres == S_OK); + } + + ReleaseStgMedium(&medium); + if (res) + m_Transfer_WasSent_ToDataObject = true; + return res; +} + + +bool CDropTarget::SendToSource_UInt32(IDataObject *dataObject, UINT format, UInt32 value) +{ + PRF("CDropTarget::Send_UInt32 (Performed)"); + + if (!dataObject || format == 0) + return false; + FORMATETC etc = INIT_FORMATETC_HGLOBAL((CLIPFORMAT)format); + STGMEDIUM medium; + medium.tymed = etc.tymed; + medium.pUnkForRelease = NULL; + const size_t size = 4; + medium.hGlobal = GlobalAlloc(GHND | GMEM_SHARE, size); + if (!medium.hGlobal) + return false; + void *dest = GlobalLock(medium.hGlobal); + bool res = false; + if (dest) + { + *(UInt32 *)dest = value; + GlobalUnlock(medium.hGlobal); + // OutputDebugString("m_DataObject->SetData"); + const BOOL release = TRUE; + // FALSE; // for debug + /* If DropSource is Win10-Explorer, then (release == FALSE) doesn't work + and dataObject->SetData() returns E_NOTIMPL; + So we use release = TRUE; here */ + const HRESULT hres = dataObject->SetData(&etc, &medium, release); + // we return here without calling ReleaseStgMedium(). + return (hres == S_OK); + // Sleep(1000); + /* + if (we use release = TRUE), we expect that + - SetData() will release medium, and + - SetData() will set STGMEDIUM::tymed to (TYMED_NULL = 0). + but some "incorrect" SetData() implementations can keep STGMEDIUM::tymed unchanged. + And it's not safe to call ReleaseStgMedium() here for that case, + because DropSource also could release medium. + We can reset (medium.tymed = TYMED_NULL) manually here to disable + unsafe medium releasing in ReleaseStgMedium(). + */ + /* + if (release) + { + medium.tymed = TYMED_NULL; + medium.pUnkForRelease = NULL; + medium.hGlobal = NULL; + } + res = (hres == S_OK); + */ + } + ReleaseStgMedium(&medium); + return res; +} + + +void CDropTarget::LoadNames_From_DataObject(IDataObject *dataObject) +{ + // "\\\\.\\" prefix is possible for long names + m_DropIsAllowed = NShell::DataObject_GetData_HDROP_or_IDLIST_Names(dataObject, m_SourcePaths) == S_OK; +} + + +Z7_COMWF_B CDropTarget::DragEnter(IDataObject *dataObject, DWORD keyState, POINTL pt, DWORD *effect) +{ + /* *(effect): + - on input : value of the dwOKEffects parameter of the DoDragDrop() function. + - on return : must contain one of the DROPEFFECT flags, which indicates + what the result of the drop operation would be. + (pt): the current cursor coordinates in screen coordinates. + */ + PRF_(Print_Point("CDropTarget::DragEnter", keyState, pt, *effect)) + try { + + if ((keyState & (MK_RBUTTON | MK_MBUTTON)) != 0) + m_IsRightButton = true; + + LoadNames_From_DataObject(dataObject); + // Try_QueryGetData(dataObject); + // we will use (m_DataObject) later in DragOver() and DragLeave(). + m_DataObject = dataObject; + // return DragOver(keyState, pt, effect); + PositionCursor(pt); + CTargetTransferInfo target; + target.FuncType = k_DragTargetMode_Enter; + target.KeyState = keyState; + target.Point = pt; + target.OkEffects = *effect; + SendToSource_Drag(target); + + CDataObject_GetTransfer transfer; + m_GetTransfer_WasSuccess = DataObject_GetData_GetTransfer( + dataObject, m_Format_7zip_GetTransfer, transfer); + if (m_GetTransfer_WasSuccess) + { + if (transfer.Flags & k_SourceFlags_LeftButton) + m_IsRightButton = false; + else if (transfer.Flags & k_SourceFlags_RightButton) + m_IsRightButton = true; + } + + *effect = GetEffect(keyState, pt, *effect); + return S_OK; + } catch(...) { return E_FAIL; } +} + + +Z7_COMWF_B CDropTarget::DragOver(DWORD keyState, POINTL pt, DWORD *effect) +{ + PRF_(Print_Point("CDropTarget::DragOver", keyState, pt, *effect)) + /* + For efficiency reasons, a data object is not passed in IDropTarget::DragOver. + The data object passed in the most recent call to IDropTarget::DragEnter + is available and can be used. + + When IDropTarget::DragOver has completed its operation, the DoDragDrop + function calls IDropSource::GiveFeedback so the source application can display + the appropriate visual feedback to the user. + */ + /* + we suppose that it's unexpected that (keyState) shows that mouse + button is not pressed, because such cases will be processed by + IDropSource::QueryContinueDrag() that returns DRAGDROP_S_DROP or DRAGDROP_S_CANCEL. + So DragOver() will not be called. + */ + + if ((keyState & MK_LBUTTON) == 0) + { + PRF4("CDropTarget::DragOver() (keyState & MK_LBUTTON) == 0"); + // g_Debug = true; + } + + try { + /* we suppose that source names were not changed after DragEnter() + so we don't request GetNames_From_DataObject() for each call of DragOver() */ + PositionCursor(pt); + CTargetTransferInfo target; + target.FuncType = k_DragTargetMode_Over; + target.KeyState = keyState; + target.Point = pt; + target.OkEffects = *effect; + SendToSource_Drag(target); + *effect = GetEffect(keyState, pt, *effect); + // *effect = 1 << 8; // for debug + return S_OK; + } catch(...) { return E_FAIL; } +} + + +Z7_COMWF_B CDropTarget::DragLeave() +{ + PRF4("CDropTarget::DragLeave"); + try { + RemoveSelection(); + // we send empty TargetPath to 7-Zip Source to clear value of TargetPath that was sent before + + CTargetTransferInfo target; + target.FuncType = k_DragTargetMode_Leave; + /* + target.KeyState = 0; + target.Point = pt; + pt.x = 0; // -1 + pt.y = 0; // -1 + target.Effect = 0; + */ + SendToSource_Drag(target); + ClearState(); + return S_OK; + } catch(...) { return E_FAIL; } +} + + +static unsigned Drag_OnContextMenu(int xPos, int yPos, UInt32 cmdFlags); + +/* + We suppose that there was DragEnter/DragOver for same (POINTL pt) before Drop(). + But we can work without DragEnter/DragOver too. +*/ +Z7_COMWF_B CDropTarget::Drop(IDataObject *dataObject, DWORD keyState, + POINTL pt, DWORD *effect) +{ + PRF_(Print_Point("CDropTarget::Drop", keyState, pt, *effect)) + /* Drop() is called after SourceDrop::QueryContinueDrag() returned DRAGDROP_S_DROP. + So it's possible that Source have done some operations already. + */ + HRESULT hres = S_OK; + bool needDrop_by_Source = false; + DWORD opEffect = DROPEFFECT_NONE; + + try { + // we don't need m_DataObject reference anymore, because we use local (dataObject) + m_DataObject.Release(); + + /* in normal case : we called LoadNames_From_DataObject() in DragEnter() already. + But if by some reason DragEnter() was not called, + we need to call LoadNames_From_DataObject() before PositionCursor(). + */ + if (!m_DropIsAllowed) LoadNames_From_DataObject(dataObject); + PositionCursor(pt); + + CPanel::CDisableTimerProcessing2 disableTimerProcessing(m_Panel); + // CDisableNotify disableNotify2(m_Panel); + + UInt32 cmd = NDragMenu::k_None; + UInt32 cmdEffect = DROPEFFECT_NONE; + bool menu_WasShown = false; + if (m_IsRightButton && m_Panel) + { + UInt32 flagsMask; + if (m_Panel->IsArcFolder()) + flagsMask = (UInt32)1 << NDragMenu::k_Copy_ToArc; + else + { + flagsMask = (UInt32)1 << NDragMenu::k_AddToArc; + if (IsFsFolderPath()) + flagsMask |= (UInt32)1 << NDragMenu::k_Copy_Base; + } + // flagsMask |= (UInt32)1 << NDragMenu::k_Cancel; + const UInt32 cmd32 = Drag_OnContextMenu(pt.x, pt.y, flagsMask); + cmd = cmd32 & NDragMenu::k_MenuFlags_CmdMask; + if (cmd32 & NDragMenu::k_MenuFlag_Copy) + cmdEffect = DROPEFFECT_COPY; + else if (cmd32 & NDragMenu::k_MenuFlag_Move) + cmdEffect = DROPEFFECT_MOVE; + opEffect = cmdEffect; + menu_WasShown = true; + } + else + { + opEffect = GetEffect(keyState, pt, *effect); + if (m_IsAppTarget) + cmd = NDragMenu::k_AddToArc; + else if (m_Panel) + { + if (IsFsFolderPath()) + { + const bool is7zip = m_TargetPath_WasSent_ToDataObject; + bool createNewArchive = false; + if (is7zip) + createNewArchive = false; // g_CreateArchive_for_Drag_from_7zip; + else + createNewArchive = true; // g_CreateArchive_for_Drag_from_Explorer; + + if (createNewArchive) + cmd = NDragMenu::k_AddToArc; + else + { + if (opEffect != 0) + cmd = NDragMenu::k_Copy_Base; + cmdEffect = opEffect; + } + } + else + { + /* if we are inside open archive: + if archive support operations -> we will call operations + if archive doesn't support operations -> we will create new archove + */ + if (m_Panel->IsArcFolder() + || m_Panel->DoesItSupportOperations()) + { + cmd = NDragMenu::k_Copy_ToArc; + // we don't want move to archive operation here. + // so we force to DROPEFFECT_COPY. + if (opEffect != DROPEFFECT_NONE) + opEffect = DROPEFFECT_COPY; + cmdEffect = opEffect; + } + else + cmd = NDragMenu::k_AddToArc; + } + } + } + + if (cmd == 0) + cmd = NDragMenu::k_AddToArc; + + if (cmd == NDragMenu::k_AddToArc) + { + opEffect = DROPEFFECT_COPY; + cmdEffect = DROPEFFECT_COPY; + } + + if (m_Panel) + if (cmd == NDragMenu::k_Copy_ToArc) + { + const UString title = LangString(IDS_CONFIRM_FILE_COPY); + UString s = LangString(cmdEffect == DROPEFFECT_MOVE ? + IDS_MOVE_TO : IDS_COPY_TO); + s.Add_LF(); + // s += "\'"; + s += m_Panel->_currentFolderPrefix; + // s += "\'"; + s.Add_LF(); + AddLangString(s, IDS_WANT_TO_COPY_FILES); + s += " ?"; + const int res = ::MessageBoxW(*m_Panel, s, title, MB_YESNOCANCEL | MB_ICONQUESTION); + if (res != IDYES) + cmd = NDragMenu::k_Cancel; + } + + CTargetTransferInfo target; + target.FuncType = k_DragTargetMode_Drop_Begin; + target.KeyState = keyState; + target.Point = pt; + target.OkEffects = *effect; + target.Flags = 0; + + target.Cmd_Effect = cmdEffect; + target.Cmd_Type = cmd; + target.FolderType = GetFolderType(); + + if (cmd == NDragMenu::k_Cancel) + target.Flags |= k_TargetFlags_WasCanceled; + if (menu_WasShown) + target.Flags |= k_TargetFlags_MenuWasShown; + + SendToSource_auto(dataObject, target); + + CDataObject_GetTransfer transfer; + m_GetTransfer_WasSuccess = DataObject_GetData_GetTransfer( + dataObject, m_Format_7zip_GetTransfer, transfer); + + /* The Source (for example, 7-zip) could change file names when drop was confirmed. + So we must reload source file paths here */ + if (cmd != NDragMenu::k_Cancel) + LoadNames_From_DataObject(dataObject); + + if (cmd == NDragMenu::k_Cancel) + { + opEffect = DROPEFFECT_NONE; + cmdEffect = DROPEFFECT_NONE; + } + else + { + if (m_GetTransfer_WasSuccess) + needDrop_by_Source = ((transfer.Flags & k_SourceFlags_DoNotProcessInTarget) != 0); + if (!needDrop_by_Source) + { + bool moveMode = (cmdEffect == DROPEFFECT_MOVE); + bool needDrop = false; + if (m_IsRightButton && m_Panel) + needDrop = true; + if (m_DropIsAllowed && m_PanelDropIsAllowed) + { + /* if non-empty TargetPath was sent successfully to DataObject, + then the Source is 7-Zip, and that 7zip-Source can copy to FS operation. + So we can disable Drop operation here for such case. + */ + needDrop_by_Source = (cmd != NDragMenu::k_AddToArc + && m_TargetPath_WasSent_ToDataObject + && m_TargetPath_NonEmpty_WasSent_ToDataObject); + needDrop = !(needDrop_by_Source); + } + if (needDrop) + { + UString path = GetTargetPath(); + if (m_IsAppTarget && m_Panel) + if (m_Panel->IsFSFolder()) + path = m_Panel->GetFsPath(); + + UInt32 sourceFlags = 0; + if (m_GetTransfer_WasSuccess) + sourceFlags = transfer.Flags; + + if (menu_WasShown) + target.Flags |= k_TargetFlags_MenuWasShown; + + target.Flags |= k_TargetFlags_WasProcessed; + + RemoveSelection(); + // disableTimerProcessing.Restore(); + m_Panel->CompressDropFiles(m_SourcePaths, path, + (cmd == NDragMenu::k_AddToArc), // createNewArchive, + moveMode, sourceFlags, + target.Flags + ); + } + } + } // end of if (cmd != NDragMenu::k_Cancel) + { + /* note that, if (we send CFSTR_PERFORMEDDROPEFFECT as DROPEFFECT_MOVE + and Drop() returns (*effect == DROPEFFECT_MOVE), then + Win10-Explorer-Source will try to remove files just after Drop() exit. + But our CompressFiles() could be run without waiting finishing. + DOCs say, that we must send CFSTR_PERFORMEDDROPEFFECT + - DROPEFFECT_NONE : for optimized move + - DROPEFFECT_MOVE : for unoptimized move. + But actually Win10-Explorer-Target sends (DROPEFFECT_MOVE) for move operation. + And it still works as in optimized mode, because "unoptimized" deleting by Source will be performed + if both conditions are met: + 1) DROPEFFECT_MOVE is sent to (CFSTR_PERFORMEDDROPEFFECT) and + 2) (*effect == DROPEFFECT_MOVE) is returend by Drop(). + We don't want to send DROPEFFECT_MOVE here to protect from + deleting file by Win10-Explorer. + We are not sure that allfile fieree processed by move. + */ + + // for debug: we test the case when source tries to delete original files + // bool res; + // only CFSTR_PERFORMEDDROPEFFECT affects file removing in Win10-Explorer. + // res = SendToSource_UInt32(dataObject, RegisterClipboardFormat(CFSTR_LOGICALPERFORMEDDROPEFFECT), DROPEFFECT_MOVE); // for debug + /* res = */ SendToSource_UInt32(dataObject, + RegisterClipboardFormat(CFSTR_PERFORMEDDROPEFFECT), + cmd == NDragMenu::k_Cancel ? DROPEFFECT_NONE : DROPEFFECT_COPY); + // res = res; + } + RemoveSelection(); + + target.FuncType = k_DragTargetMode_Drop_End; + target.Cmd_Type = cmd; + if (needDrop_by_Source) + target.Flags |= k_TargetFlags_MustBeProcessedBySource; + + SendToSource_TransferInfo(dataObject, target); + } catch(...) { hres = E_FAIL; } + + ClearState(); + // *effect |= (1 << 10); // for debug + // *effect = DROPEFFECT_COPY; // for debug + + /* + if we return (*effect == DROPEFFECT_MOVE) here, + Explorer-Source at some conditions can treat it as (unoptimized move) mode, + and Explorer-Source will remove source files after DoDragDrop() + in that (unoptimized move) mode. + We want to avoid such (unoptimized move) cases. + So we don't return (*effect == DROPEFFECT_MOVE), here if Source is not 7-Zip. + If source is 7-Zip that will do acual opeartion, then we can return DROPEFFECT_MOVE. + */ + if (hres != S_OK || (opEffect == DROPEFFECT_MOVE && !needDrop_by_Source)) + { + // opEffect = opEffect; + // opEffect = DROPEFFECT_NONE; // for debug disabled + } + + *effect = opEffect; + /* if (hres < 0), DoDragDrop() also will return (hres). + if (hres >= 0), DoDragDrop() will return DRAGDROP_S_DROP; + */ + return hres; +} + + + +// ---------- CPanel ---------- + + +static bool Is_Path1_Prefixed_by_Path2(const UString &path, const UString &prefix) +{ + const unsigned len = prefix.Len(); + if (path.Len() < len) + return false; + return CompareFileNames(path.Left(len), prefix) == 0; +} + +static bool IsFolderInTemp(const UString &path) +{ + FString tempPathF; + if (!MyGetTempPath(tempPathF)) + return false; + const UString tempPath = fs2us(tempPathF); + if (tempPath.IsEmpty()) + return false; + return Is_Path1_Prefixed_by_Path2(path, tempPath); +} + +static bool AreThereNamesFromTemp(const UStringVector &filePaths) +{ + FString tempPathF; + if (!MyGetTempPath(tempPathF)) + return false; + const UString tempPath = fs2us(tempPathF); + if (tempPath.IsEmpty()) + return false; + FOR_VECTOR (i, filePaths) + if (Is_Path1_Prefixed_by_Path2(filePaths[i], tempPath)) + return true; + return false; +} + + +/* + empty folderPath means create new Archive to path of first fileName. + createNewArchive == true : show "Add to archive ..." dialog with external program + folderPath.IsEmpty() : create archive in folder of filePaths[0]. + createNewArchive == false : + folderPath.IsEmpty() : copy to archive folder that is open in panel + !folderPath.IsEmpty() : CopyFsItems() to folderPath. +*/ +void CPanel::CompressDropFiles( + const UStringVector &filePaths, + const UString &folderPath, + bool createNewArchive, + bool moveMode, + UInt32 sourceFlags, + UInt32 &targetFlags + ) +{ + if (filePaths.Size() == 0) + return; + // createNewArchive = false; // for debug + + if (createNewArchive) + { + UString folderPath2 = folderPath; + // folderPath2.Empty(); // for debug + if (folderPath2.IsEmpty()) + { + { + FString folderPath2F; + GetOnlyDirPrefix(us2fs(filePaths.Front()), folderPath2F); + folderPath2 = fs2us(folderPath2F); + } + if (IsFolderInTemp(folderPath2)) + { + /* we don't want archive to be created in temp directory. + so we change the path to root folder (non-temp) */ + folderPath2 = ROOT_FS_FOLDER; + } + } + + UString arcName_base; + const UString arcName = CreateArchiveName(filePaths, + false, // isHash + NULL, // CFileInfo *fi + arcName_base); + + bool needWait; + if (sourceFlags & k_SourceFlags_WaitFinish) + needWait = true; + else if (sourceFlags & k_SourceFlags_DoNotWaitFinish) + needWait = false; + else if (sourceFlags & k_SourceFlags_TempFiles) + needWait = true; + else + needWait = AreThereNamesFromTemp(filePaths); + + targetFlags |= (needWait ? + k_TargetFlags_WaitFinish : + k_TargetFlags_DoNotWaitFinish); + + CompressFiles(folderPath2, arcName, + L"", // arcType + true, // addExtension + filePaths, + false, // email + true, // showDialog + needWait); + } + else + { + targetFlags |= k_TargetFlags_WaitFinish; + if (!folderPath.IsEmpty()) + { + CCopyToOptions options; + options.moveMode = moveMode; + options.folder = folderPath; + options.showErrorMessages = true; // showErrorMessages is not used for this operation + options.NeedRegistryZone = false; + options.ZoneIdMode = NExtract::NZoneIdMode::kNone; + // maybe we need more options here: FIXME + /* HRESULT hres = */ CopyFsItems(options, + filePaths, + NULL // UStringVector *messages + ); + // hres = hres; + } + else + { + CopyFromNoAsk(moveMode, filePaths); + } + } +} + + + +static unsigned Drag_OnContextMenu(int xPos, int yPos, UInt32 cmdFlags) +{ + CMenu menu; + CMenuDestroyer menuDestroyer(menu); + /* + Esc key in shown menu doesn't work if we call Drag_OnContextMenu from ::Drop(). + We call SetFocus() tp solve that problem. + But the focus will be changed to Target Window after Drag and Drop. + Is it OK to use SetFocus() here ? + Is there another way to enable Esc key ? + */ + // _listView.SetFocus(); // for debug + ::SetFocus(g_HWND); + menu.CreatePopup(); + /* + int defaultCmd; // = NDragMenu::k_Move; + defaultCmd = NDragMenu::k_None; + */ + for (unsigned i = 0; i < Z7_ARRAY_SIZE(NDragMenu::g_Pairs); i++) + { + const NDragMenu::CCmdLangPair &pair = NDragMenu::g_Pairs[i]; + const UInt32 cmdAndFlags = pair.CmdId_and_Flags; + const UInt32 cmdId = cmdAndFlags & NDragMenu::k_MenuFlags_CmdMask; + if (cmdId != NDragMenu::k_Cancel) + if ((cmdFlags & ((UInt32)1 << cmdId)) == 0) + continue; + const UINT flags = MF_STRING; + /* + if (prop.IsVisible) + flags |= MF_CHECKED; + if (i == 0) + flags |= MF_GRAYED; + */ + // MF_DEFAULT doesn't work + // if (i == 2) flags |= MF_DEFAULT; + // if (i == 4) flags |= MF_HILITE; + // if (cmd == defaultCmd) flags |= MF_HILITE; + UString name = LangString(pair.LangId); + if (name.IsEmpty()) + { + if (cmdId == NDragMenu::k_Cancel) + name = "Cancel"; + else + name.Add_UInt32(pair.LangId); + } + if (cmdId == NDragMenu::k_Copy_ToArc) + { + // UString destPath = _currentFolderPrefix; + /* + UString destPath = LangString(IDS_CONTEXT_ARCHIVE); + name = MyFormatNew(name, destPath); + */ + name.Add_Space(); + AddLangString(name, IDS_CONTEXT_ARCHIVE); + } + if (cmdId == NDragMenu::k_Cancel) + menu.AppendItem(MF_SEPARATOR, 0, (LPCTSTR)NULL); + menu.AppendItem(flags, cmdAndFlags, name); + } + /* + if (defaultCmd != 0) + SetMenuDefaultItem(menu, (unsigned)defaultCmd, + FALSE); // byPos + */ + int menuResult = menu.Track( + TPM_LEFTALIGN | TPM_RETURNCMD | TPM_NONOTIFY, + xPos, yPos, + g_HWND + // _listView // for debug + ); + /* menu.Track() return value is zero, if the user cancels + the menu without making a selection, or if an error occurs */ + if (menuResult <= 0) + menuResult = NDragMenu::k_Cancel; + return (unsigned)menuResult; +} + + + +void CApp::CreateDragTarget() +{ + _dropTargetSpec = new CDropTarget(); + _dropTarget = _dropTargetSpec; + _dropTargetSpec->App = (this); +} + +void CApp::SetFocusedPanel(unsigned index) +{ + LastFocusedPanel = index; + _dropTargetSpec->TargetPanelIndex = (int)LastFocusedPanel; +} + +void CApp::DragBegin(unsigned panelIndex) +{ + _dropTargetSpec->TargetPanelIndex = (int)(NumPanels > 1 ? 1 - panelIndex : panelIndex); + _dropTargetSpec->SrcPanelIndex = (int)panelIndex; +} + +void CApp::DragEnd() +{ + _dropTargetSpec->TargetPanelIndex = (int)LastFocusedPanel; + _dropTargetSpec->SrcPanelIndex = -1; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PanelFolderChange.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PanelFolderChange.cpp new file mode 100644 index 00000000..b0fb53e1 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PanelFolderChange.cpp @@ -0,0 +1,1122 @@ +// PanelFolderChange.cpp + +#include "StdAfx.h" + +#include "../../../Common/StringConvert.h" +#include "../../../Common/Wildcard.h" + +#include "../../../Windows/FileName.h" +#include "../../../Windows/FileDir.h" +#include "../../../Windows/PropVariant.h" + +#include "../../PropID.h" + +#ifdef UNDER_CE +#include "FSFolder.h" +#else +#include "FSDrives.h" +#endif +#include "LangUtils.h" +#include "ListViewDialog.h" +#include "Panel.h" +#include "RootFolder.h" +#include "ViewSettings.h" + +#include "resource.h" + +using namespace NWindows; +using namespace NFile; +using namespace NFind; + +void CPanel::ReleaseFolder() +{ + DeleteListItems(); + + _folder.Release(); + + _folderCompare.Release(); + _folderGetItemName.Release(); + _folderRawProps.Release(); + _folderAltStreams.Release(); + _folderOperations.Release(); + + _thereAreDeletedItems = false; +} + +void CPanel::SetNewFolder(IFolderFolder *newFolder) +{ + ReleaseFolder(); + _folder = newFolder; + if (_folder) + { + _folder.QueryInterface(IID_IFolderCompare, &_folderCompare); + _folder.QueryInterface(IID_IFolderGetItemName, &_folderGetItemName); + _folder.QueryInterface(IID_IArchiveGetRawProps, &_folderRawProps); + _folder.QueryInterface(IID_IFolderAltStreams, &_folderAltStreams); + _folder.QueryInterface(IID_IFolderOperations, &_folderOperations); + } +} + +void CPanel::SetToRootFolder() +{ + ReleaseFolder(); + _library.Free(); + + CRootFolder *rootFolderSpec = new CRootFolder; + SetNewFolder(rootFolderSpec); + rootFolderSpec->Init(); +} + + +static bool DoesNameContainWildcard_SkipRoot(const UString &path) +{ + return DoesNameContainWildcard(path.Ptr(NName::GetRootPrefixSize(path))); +} + +HRESULT CPanel::BindToPath(const UString &fullPath, const UString &arcFormat, COpenResult &openRes) +{ + UString path = fullPath; + #ifdef _WIN32 + path.Replace(L'/', WCHAR_PATH_SEPARATOR); + #endif + + openRes.ArchiveIsOpened = false; + openRes.Encrypted = false; + + CDisableTimerProcessing disableTimerProcessing(*this); + CDisableNotify disableNotify(*this); + + for (; !_parentFolders.IsEmpty(); CloseOneLevel()) + { + // ---------- we try to use open archive ---------- + + const CFolderLink &link = _parentFolders.Back(); + const UString &virtPath = link.VirtualPath; + if (!path.IsPrefixedBy(virtPath)) + continue; + UString relatPath = path.Ptr(virtPath.Len()); + if (!relatPath.IsEmpty()) + { + if (!IS_PATH_SEPAR(relatPath[0])) + continue; + else + relatPath.Delete(0); + } + + UString relatPath2 = relatPath; + if (!relatPath2.IsEmpty() && !IS_PATH_SEPAR(relatPath2.Back())) + relatPath2.Add_PathSepar(); + + for (;;) + { + const UString foldPath = GetFolderPath(_folder); + if (relatPath2 == foldPath) + break; + if (relatPath.IsPrefixedBy(foldPath)) + { + path = relatPath.Ptr(foldPath.Len()); + break; + } + CMyComPtr newFolder; + if (_folder->BindToParentFolder(&newFolder) != S_OK) + throw 20140918; + if (!newFolder) // we exit from loop above if (relatPath.IsPrefixedBy(empty path for root folder) + throw 20140918; + SetNewFolder(newFolder); + } + break; + } + + if (_parentFolders.IsEmpty()) + { + // ---------- we open file or folder from file system ---------- + + CloseOpenFolders(); + UString sysPath = path; + /* we will Empty() sysPath variable, if we need to BindToFolder() + directly with (path) variable */ + const unsigned prefixSize = NName::GetRootPrefixSize(sysPath); + if (prefixSize == 0 || sysPath[prefixSize] == 0) + sysPath.Empty(); + + #if defined(_WIN32) && !defined(UNDER_CE) + if (!sysPath.IsEmpty() && sysPath.Back() == ':' && + (sysPath.Len() != 2 || !NName::IsDrivePath2(sysPath))) + { + // if base item for alt streams prefix "base:" exists, we will use it + UString baseFile = sysPath; + baseFile.DeleteBack(); + if (NFind::DoesFileOrDirExist(us2fs(baseFile))) + sysPath.Empty(); + } + #endif + + CFileInfo fileInfo; + + while (!sysPath.IsEmpty()) + { + if (sysPath.Len() <= prefixSize) + { + path.DeleteFrom(prefixSize); + sysPath.Empty(); + break; + } + + fileInfo.ClearBase(); + if (IsPathSepar(sysPath.Back())) + { + /* Windows 10 by default doesn't allow look "Local Settings" that is junction to "AppData\Local", + but it does allow look "Local Settings\Temp\*" + 22.02: at first we try to use paths with slashes "path\" */ + CFileInfo fi; + // CFindFile findFile; + // FString path2 = us2fs(sysPath); + // path2 += '*'; // CHAR_ANY_MASK; + // if (findFile.FindFirst(path2, fi)) + CEnumerator enumerator; + enumerator.SetDirPrefix(us2fs(sysPath)); + bool found = false; + if (enumerator.Next(fi, found)) + { + // sysPath.DeleteBack(); + fileInfo.SetAsDir(); + fileInfo.Size = 0; + fileInfo.Name.Empty(); + break; + } + sysPath.DeleteBack(); + continue; + } + + if (fileInfo.Find(us2fs(sysPath))) + break; + int pos = sysPath.ReverseFind_PathSepar(); + if (pos < 0) + { + sysPath.Empty(); + break; + } + { + if ((unsigned)pos != sysPath.Len() - 1) + pos++; + sysPath.DeleteFrom((unsigned)pos); + } + } + + SetToRootFolder(); + + CMyComPtr newFolder; + + if (sysPath.IsEmpty()) + { + _folder->BindToFolder(path, &newFolder); + } + else if (fileInfo.IsDir()) + { + #ifdef _WIN32 + if (DoesNameContainWildcard_SkipRoot(sysPath)) + { + FString dirPrefix, fileName; + NDir::GetFullPathAndSplit(us2fs(sysPath), dirPrefix, fileName); + if (DoesNameContainWildcard_SkipRoot(fs2us(dirPrefix))) + return E_INVALIDARG; + sysPath = fs2us(dirPrefix + fileInfo.Name); + } + #endif + + NName::NormalizeDirPathPrefix(sysPath); + _folder->BindToFolder(sysPath, &newFolder); + } + else + { + FString dirPrefix, fileName; + + NDir::GetFullPathAndSplit(us2fs(sysPath), dirPrefix, fileName); + + HRESULT res = S_OK; + + #ifdef _WIN32 + if (DoesNameContainWildcard_SkipRoot(fs2us(dirPrefix))) + return E_INVALIDARG; + + if (DoesNameContainWildcard(fs2us(fileName))) + res = S_FALSE; + else + #endif + { + CTempFileInfo tfi; + tfi.RelPath = fs2us(fileName); + tfi.FolderPath = dirPrefix; + tfi.FilePath = us2fs(sysPath); + res = OpenAsArc(NULL, tfi, sysPath, arcFormat, openRes); + } + + if (res == S_FALSE) + _folder->BindToFolder(fs2us(dirPrefix), &newFolder); + else + { + RINOK(res) + openRes.ArchiveIsOpened = true; + _parentFolders.Back().ParentFolderPath = fs2us(dirPrefix); + path.DeleteFrontal(sysPath.Len()); + if (!path.IsEmpty() && IS_PATH_SEPAR(path[0])) + path.Delete(0); + } + } + + if (newFolder) + { + SetNewFolder(newFolder); + // LoadFullPath(); + return S_OK; + } + } + + { + // ---------- we open folder remPath in archive and sub archives ---------- + + for (unsigned curPos = 0; curPos != path.Len();) + { + UString s = path.Ptr(curPos); + const int slashPos = NName::FindSepar(s); + unsigned skipLen = s.Len(); + if (slashPos >= 0) + { + s.DeleteFrom((unsigned)slashPos); + skipLen = (unsigned)slashPos + 1; + } + + CMyComPtr newFolder; + _folder->BindToFolder(s, &newFolder); + if (newFolder) + curPos += skipLen; + else if (_folderAltStreams) + { + const int pos = s.Find(L':'); + if (pos >= 0) + { + UString baseName = s; + baseName.DeleteFrom((unsigned)pos); + if (_folderAltStreams->BindToAltStreams(baseName, &newFolder) == S_OK && newFolder) + curPos += (unsigned)pos + 1; + } + } + + if (!newFolder) + break; + + SetNewFolder(newFolder); + } + } + + return S_OK; +} + +HRESULT CPanel::BindToPathAndRefresh(const UString &path) +{ + CDisableTimerProcessing disableTimerProcessing(*this); + CDisableNotify disableNotify(*this); + COpenResult openRes; + UString s = path; + + #ifdef _WIN32 + if (!s.IsEmpty() && s[0] == '\"' && s.Back() == '\"') + { + s.DeleteBack(); + s.Delete(0); + } + #endif + + HRESULT res = BindToPath(s, UString(), openRes); + RefreshListCtrl(); + return res; +} + +void CPanel::SetBookmark(unsigned index) +{ + _appState->FastFolders.SetString(index, _currentFolderPrefix); +} + +void CPanel::OpenBookmark(unsigned index) +{ + BindToPathAndRefresh(_appState->FastFolders.GetString(index)); +} + +UString GetFolderPath(IFolderFolder *folder) +{ + { + NCOM::CPropVariant prop; + if (folder->GetFolderProperty(kpidPath, &prop) == S_OK) + if (prop.vt == VT_BSTR) + return (wchar_t *)prop.bstrVal; + } + return UString(); +} + +void CPanel::LoadFullPath() +{ + _currentFolderPrefix.Empty(); + FOR_VECTOR (i, _parentFolders) + { + const CFolderLink &folderLink = _parentFolders[i]; + _currentFolderPrefix += folderLink.ParentFolderPath; + // GetFolderPath(folderLink.ParentFolder); + _currentFolderPrefix += folderLink.RelPath; + _currentFolderPrefix.Add_PathSepar(); + } + if (_folder) + _currentFolderPrefix += GetFolderPath(_folder); +} + + + +static int GetRealIconIndex_for_DirPath(CFSTR path, DWORD attrib) +{ + attrib |= FILE_ATTRIBUTE_DIRECTORY; // optional + int index = -1; + if (Shell_GetFileInfo_SysIconIndex_for_Path_attrib_iconIndexRef(path, attrib, index)) + if (index >= 0) + return index; + return g_Ext_to_Icon_Map.GetIconIndex_DIR(attrib); +} + + +extern UString RootFolder_GetName_Computer(int &iconIndex); +extern UString RootFolder_GetName_Network(int &iconIndex); +extern UString RootFolder_GetName_Documents(int &iconIndex); + + +static int Find_FileExtension_DotPos_in_path(const wchar_t *path) +{ + int dotPos = -1; + unsigned i; + for (i = 0;; i++) + { + const wchar_t c = path[i]; + if (c == 0) + return dotPos; + if (c == '.') + dotPos = (int)i; + else if (IS_PATH_SEPAR(c) || c == ':') + dotPos = -1; + } +} + + +void CPanel::LoadFullPathAndShow() +{ + LoadFullPath(); + _appState->FolderHistory.AddString(_currentFolderPrefix); + + _headerComboBox.SetText(_currentFolderPrefix); + + #ifndef UNDER_CE + + COMBOBOXEXITEM item; + item.mask = 0; + item.iImage = -1; + + UString path = _currentFolderPrefix; + // path = "\\\\.\\PhysicalDrive1\\"; // for debug + // path = "\\\\.\\y:\\"; // for debug + if (!path.IsEmpty()) + { + const unsigned rootPrefixSize = NName::GetRootPrefixSize(path); + if (rootPrefixSize == 0 && path[0] != '\\') + { + int iconIndex = -1; + UString name_Computer = RootFolder_GetName_Computer(iconIndex); + name_Computer.Add_PathSepar(); + if (path == name_Computer + || path.IsEqualTo("\\\\?\\")) + item.iImage = iconIndex; + else + { + UString name = RootFolder_GetName_Network(iconIndex); + name.Add_PathSepar(); + if (path == name) + item.iImage = iconIndex; + } + } + + if (item.iImage < 0) + { + if (rootPrefixSize == 0 || rootPrefixSize == path.Len()) + { + DWORD attrib = FILE_ATTRIBUTE_DIRECTORY; + CFileInfo info; + if (info.Find(us2fs(path))) + attrib = info.Attrib; + NName::If_IsSuperPath_RemoveSuperPrefix(path); + item.iImage = GetRealIconIndex_for_DirPath(us2fs(path), attrib); + } + else if (rootPrefixSize == NName::kDevicePathPrefixSize + && NName::IsDevicePath(us2fs(path.Left(path.Len() - 1)))) + { + if (path.IsPrefixedBy_Ascii_NoCase("\\\\.\\")) + path.DeleteFrontal(4); + if (path.Len() > 3) // is not "c:\\" + { + // PhysicalDrive + if (path.Back() == '\\') + path.DeleteBack(); + } + item.iImage = Shell_GetFileInfo_SysIconIndex_for_Path(us2fs(path), FILE_ATTRIBUTE_ARCHIVE); + } + else + { + if (path.Back() == '\\') + path.DeleteBack(); + bool need_Fs_Check = true; + bool is_File = false; + if (!_parentFolders.IsEmpty()) + { + const CFolderLink &link = _parentFolders.Back(); + if (link.VirtualPath == path) + { + is_File = true; + if (_parentFolders.Size() != 1) + need_Fs_Check = false; + } + else + need_Fs_Check = false; + } + if (need_Fs_Check) + { + CFileInfo info; + const bool finded = info.Find(us2fs(path)); + DWORD attrib = FILE_ATTRIBUTE_DIRECTORY; + if (finded) + attrib = info.Attrib; + item.iImage = Shell_GetFileInfo_SysIconIndex_for_Path(us2fs(path), attrib); + } + if (item.iImage <= 0 && is_File) + { + int dotPos = Find_FileExtension_DotPos_in_path(path); + if (dotPos < 0) + dotPos = (int)path.Len(); + item.iImage = g_Ext_to_Icon_Map.GetIconIndex(FILE_ATTRIBUTE_ARCHIVE, path.Ptr(dotPos)); + } + } + } + } + + if (item.iImage < 0) + item.iImage = g_Ext_to_Icon_Map.GetIconIndex_DIR(); + // if (item.iImage < 0) item.iImage = 0; + // item.iImage = -1; // for debug + if (item.iImage >= 0) + { + item.iSelectedImage = item.iImage; + item.mask |= (CBEIF_IMAGE | CBEIF_SELECTEDIMAGE); + } + item.iItem = -1; + _headerComboBox.SetItem(&item); + + #endif + + RefreshTitle(); +} + +#ifndef UNDER_CE +LRESULT CPanel::OnNotifyComboBoxEnter(const UString &s) +{ + if (BindToPathAndRefresh(GetUnicodeString(s)) == S_OK) + { + PostMsg(kSetFocusToListView); + return TRUE; + } + return FALSE; +} + +bool CPanel::OnNotifyComboBoxEndEdit(PNMCBEENDEDITW info, LRESULT &result) +{ + if (info->iWhy == CBENF_ESCAPE) + { + _headerComboBox.SetText(_currentFolderPrefix); + PostMsg(kSetFocusToListView); + result = FALSE; + return true; + } + + /* + if (info->iWhy == CBENF_DROPDOWN) + { + result = FALSE; + return true; + } + */ + + if (info->iWhy == CBENF_RETURN) + { + // When we use Edit control and press Enter. + UString s; + _headerComboBox.GetText(s); + result = OnNotifyComboBoxEnter(s); + return true; + } + return false; +} +#endif + +#ifndef _UNICODE +bool CPanel::OnNotifyComboBoxEndEdit(PNMCBEENDEDIT info, LRESULT &result) +{ + if (info->iWhy == CBENF_ESCAPE) + { + _headerComboBox.SetText(_currentFolderPrefix); + PostMsg(kSetFocusToListView); + result = FALSE; + return true; + } + /* + if (info->iWhy == CBENF_DROPDOWN) + { + result = FALSE; + return true; + } + */ + + if (info->iWhy == CBENF_RETURN) + { + UString s; + _headerComboBox.GetText(s); + // GetUnicodeString(info->szText) + result = OnNotifyComboBoxEnter(s); + return true; + } + return false; +} +#endif + +void CPanel::AddComboBoxItem(const UString &name, int iconIndex, unsigned indent, bool addToList) +{ + #ifdef UNDER_CE + + UString s; + iconIndex = iconIndex; + for (unsigned i = 0; i < indent; i++) + s += " "; + _headerComboBox.AddString(s + name); + + #else + + COMBOBOXEXITEMW item; + item.mask = CBEIF_TEXT | CBEIF_INDENT; + if (iconIndex < 0) + iconIndex = g_Ext_to_Icon_Map.GetIconIndex_DIR(); + item.iSelectedImage = item.iImage = iconIndex; + if (iconIndex >= 0) + item.mask |= (CBEIF_IMAGE | CBEIF_SELECTEDIMAGE); + item.iItem = -1; + item.iIndent = (int)indent; + item.pszText = name.Ptr_non_const(); + _headerComboBox.InsertItem(&item); + + #endif + + if (addToList) + { + UString s = name; + s.Add_PathSepar(); + ComboBoxPaths.Add(s); + } +} + + +bool CPanel::OnComboBoxCommand(UINT code, LPARAM /* param */, LRESULT &result) +{ + result = FALSE; + switch (code) + { + case CBN_DROPDOWN: + { + ComboBoxPaths.Clear(); + _headerComboBox.ResetContent(); + + UString sumPath; + UStringVector pathParts; + unsigned indent = 0; + { + UString path = _currentFolderPrefix; + // path = "\\\\.\\y:\\"; // for debug + UString prefix0; + if (path.IsPrefixedBy_Ascii_NoCase("\\\\")) + { + const int separ = FindCharPosInString(path.Ptr(2), '\\'); + if (separ > 0 + && (separ > 1 || path[2] != '.')) // "\\\\.\\" will be processed later + { + const UString s = path.Left(2 + separ); + prefix0 = s; + prefix0.Add_PathSepar(); + AddComboBoxItem(s, + GetRealIconIndex_for_DirPath(us2fs(prefix0), FILE_ATTRIBUTE_DIRECTORY), + indent++, + false); // addToList + ComboBoxPaths.Add(prefix0); + } + } + + unsigned rootPrefixSize = NName::GetRootPrefixSize(path); + + sumPath = path; + + if (rootPrefixSize <= prefix0.Len()) + { + rootPrefixSize = prefix0.Len(); + sumPath.DeleteFrom(rootPrefixSize); + } + else + { + // rootPrefixSize > prefix0.Len() + sumPath.DeleteFrom(rootPrefixSize); + + CFileInfo info; + DWORD attrib = FILE_ATTRIBUTE_DIRECTORY; + if (info.Find(us2fs(sumPath)) && info.IsDir()) + attrib = info.Attrib; + UString s = sumPath.Ptr(prefix0.Len()); + if (!s.IsEmpty()) + { + const wchar_t c = s.Back(); + if (IS_PATH_SEPAR(c)) + s.DeleteBack(); + } + UString path_for_icon = sumPath; + NName::If_IsSuperPath_RemoveSuperPrefix(path_for_icon); + + AddComboBoxItem(s, + GetRealIconIndex_for_DirPath(us2fs(path_for_icon), attrib), + indent++, + false); // addToList + ComboBoxPaths.Add(sumPath); + } + + path.DeleteFrontal(rootPrefixSize); + SplitPathToParts(path, pathParts); + } + + // it's expected that pathParts.Back() is empty, because _currentFolderPrefix has PathSeparator. + unsigned next_Arc_index = 0; + int iconIndex_Computer; + const UString name_Computer = RootFolder_GetName_Computer(iconIndex_Computer); + + // const bool is_devicePrefix = (sumPath.IsEqualTo("\\\\.\\")); + + if (pathParts.Size() > 1) + if (!sumPath.IsEmpty() + || pathParts.Size() != 2 + || pathParts[0] != name_Computer) + for (unsigned i = 0; i + 1 < pathParts.Size(); i++) + { + UString name = pathParts[i]; + sumPath += name; + + bool isRootDir_inLink = false; + if (next_Arc_index < _parentFolders.Size()) + { + const CFolderLink &link = _parentFolders[next_Arc_index]; + if (link.VirtualPath == sumPath) + { + isRootDir_inLink = true; + next_Arc_index++; + } + } + + int iconIndex = -1; + DWORD attrib = isRootDir_inLink ? + FILE_ATTRIBUTE_ARCHIVE: + FILE_ATTRIBUTE_DIRECTORY; + if (next_Arc_index == 0 + || (next_Arc_index == 1 && isRootDir_inLink)) + { + if (i == 0 && NName::IsDevicePath(us2fs(sumPath))) + { + UString path = name; + path.Add_PathSepar(); + attrib = FILE_ATTRIBUTE_ARCHIVE; + // FILE_ATTRIBUTE_DIRECTORY; + } + else + { + CFileInfo info; + if (info.Find(us2fs(sumPath))) + attrib = info.Attrib; + } + iconIndex = Shell_GetFileInfo_SysIconIndex_for_Path(us2fs(sumPath), attrib); + } + + if (iconIndex < 0) + iconIndex = g_Ext_to_Icon_Map.GetIconIndex(attrib, name); + // iconIndex = -1; // for debug + if (iconIndex < 0 && isRootDir_inLink) + iconIndex = 0; // default file + + sumPath.Add_PathSepar(); + + ComboBoxPaths.Add(sumPath); + if (name.IsEmpty()) + name.Add_PathSepar(); + AddComboBoxItem(name, iconIndex, indent++, + false); // addToList + } + +#ifndef UNDER_CE + + { + int iconIndex; + const UString name = RootFolder_GetName_Documents(iconIndex); + // iconIndex = -1; // for debug + AddComboBoxItem(name, iconIndex, 0, true); + } + AddComboBoxItem(name_Computer, iconIndex_Computer, 0, true); + { + FStringVector driveStrings; + MyGetLogicalDriveStrings(driveStrings); + FOR_VECTOR (i, driveStrings) + { + FString s = driveStrings[i]; + ComboBoxPaths.Add(fs2us(s)); + int iconIndex2 = GetRealIconIndex_for_DirPath(s, FILE_ATTRIBUTE_DIRECTORY); + if (!s.IsEmpty()) + { + const FChar c = s.Back(); + if (IS_PATH_SEPAR(c)) + s.DeleteBack(); + } + // iconIndex2 = -1; // for debug + AddComboBoxItem(fs2us(s), iconIndex2, 1, false); + } + } + { + int iconIndex; + const UString name = RootFolder_GetName_Network(iconIndex); + AddComboBoxItem(name, iconIndex, 0, true); + } + +#endif + + return false; + } + + case CBN_SELENDOK: + { + int index = _headerComboBox.GetCurSel(); + if (index >= 0) + { + const UString path = ComboBoxPaths[index]; + _headerComboBox.SetCurSel(-1); + // _headerComboBox.SetText(pass); // it's fix for selecting by mouse. + if (BindToPathAndRefresh(path) == S_OK) + { + PostMsg(kSetFocusToListView); + #ifdef UNDER_CE + PostMsg(kRefresh_HeaderComboBox); + #endif + return true; + } + } + return false; + } + /* + case CBN_CLOSEUP: + { + LoadFullPathAndShow(); + true; + + } + case CBN_SELCHANGE: + { + // LoadFullPathAndShow(); + return true; + } + */ + } + return false; +} + +bool CPanel::OnNotifyComboBox(LPNMHDR NON_CE_VAR(header), LRESULT & NON_CE_VAR(result)) +{ + #ifndef UNDER_CE + switch (header->code) + { + case CBEN_BEGINEDIT: + { + _lastFocusedIsList = false; + _panelCallback->PanelWasFocused(); + break; + } + #ifndef _UNICODE + case CBEN_ENDEDIT: + { + return OnNotifyComboBoxEndEdit((PNMCBEENDEDIT)header, result); + } + #endif + case CBEN_ENDEDITW: + { + return OnNotifyComboBoxEndEdit((PNMCBEENDEDITW)header, result); + } + } + #endif + return false; +} + + +void CPanel::FoldersHistory() +{ + CListViewDialog listViewDialog; + listViewDialog.DeleteIsAllowed = true; + listViewDialog.SelectFirst = true; + LangString(IDS_FOLDERS_HISTORY, listViewDialog.Title); + _appState->FolderHistory.GetList(listViewDialog.Strings); + if (listViewDialog.Create(GetParent()) != IDOK) + return; + UString selectString; + if (listViewDialog.StringsWereChanged) + { + _appState->FolderHistory.RemoveAll(); + for (int i = (int)listViewDialog.Strings.Size() - 1; i >= 0; i--) + _appState->FolderHistory.AddString(listViewDialog.Strings[i]); + if (listViewDialog.FocusedItemIndex >= 0) + selectString = listViewDialog.Strings[listViewDialog.FocusedItemIndex]; + } + else + { + if (listViewDialog.FocusedItemIndex >= 0) + selectString = listViewDialog.Strings[listViewDialog.FocusedItemIndex]; + } + if (listViewDialog.FocusedItemIndex >= 0) + BindToPathAndRefresh(selectString); +} + + +UString CPanel::GetParentDirPrefix() const +{ + UString s; + if (!_currentFolderPrefix.IsEmpty()) + { + wchar_t c = _currentFolderPrefix.Back(); + if (IS_PATH_SEPAR(c) || c == ':') + { + s = _currentFolderPrefix; + s.DeleteBack(); + if (!s.IsEqualTo("\\\\.") && + !s.IsEqualTo("\\\\?")) + { + int pos = s.ReverseFind_PathSepar(); + if (pos >= 0) + s.DeleteFrom((unsigned)(pos + 1)); + } + } + } + return s; +} + + +void CPanel::OpenParentFolder() +{ + LoadFullPath(); // Maybe we don't need it ?? + + UString parentFolderPrefix; + UString focusedName; + + if (!_currentFolderPrefix.IsEmpty()) + { + wchar_t c = _currentFolderPrefix.Back(); + if (IS_PATH_SEPAR(c) || c == ':') + { + focusedName = _currentFolderPrefix; + focusedName.DeleteBack(); + /* + if (c == ':' && !focusedName.IsEmpty() && IS_PATH_SEPAR(focusedName.Back())) + { + focusedName.DeleteBack(); + } + else + */ + if (!focusedName.IsEqualTo("\\\\.") && + !focusedName.IsEqualTo("\\\\?")) + { + const int pos = focusedName.ReverseFind_PathSepar(); + if (pos >= 0) + { + parentFolderPrefix = focusedName; + parentFolderPrefix.DeleteFrom((unsigned)(pos + 1)); + focusedName.DeleteFrontal((unsigned)(pos + 1)); + } + } + } + } + + CDisableTimerProcessing disableTimerProcessing(*this); + CDisableNotify disableNotify(*this); + + CMyComPtr newFolder; + _folder->BindToParentFolder(&newFolder); + + // newFolder.Release(); // for test + + if (newFolder) + SetNewFolder(newFolder); + else + { + bool needSetFolder = true; + if (!_parentFolders.IsEmpty()) + { + { + const CFolderLink &link = _parentFolders.Back(); + parentFolderPrefix = link.ParentFolderPath; + focusedName = link.RelPath; + } + CloseOneLevel(); + needSetFolder = (!_folder); + } + + if (needSetFolder) + { + { + COpenResult openRes; + BindToPath(parentFolderPrefix, UString(), openRes); + } + } + } + + CSelectedState state; + state.FocusedName = focusedName; + state.FocusedName_Defined = true; + /* + if (!focusedName.IsEmpty()) + state.SelectedNames.Add(focusedName); + */ + LoadFullPath(); + // ::SetCurrentDirectory(::_currentFolderPrefix); + RefreshListCtrl(state); + // _listView.EnsureVisible(_listView.GetFocusedItem(), false); +} + + +void CPanel::CloseOneLevel() +{ + ReleaseFolder(); + _library.Free(); + { + CFolderLink &link = _parentFolders.Back(); + if (link.ParentFolder) + SetNewFolder(link.ParentFolder); + _library.Attach(link.Library.Detach()); + } + if (_parentFolders.Size() > 1) + OpenParentArchiveFolder(); + _parentFolders.DeleteBack(); + if (_parentFolders.IsEmpty()) + _flatMode = _flatModeForDisk; +} + +void CPanel::CloseOpenFolders() +{ + while (!_parentFolders.IsEmpty()) + CloseOneLevel(); + _flatMode = _flatModeForDisk; + ReleaseFolder(); + _library.Free(); +} + +void CPanel::OpenRootFolder() +{ + CDisableTimerProcessing disableTimerProcessing(*this); + CDisableNotify disableNotify(*this); + _parentFolders.Clear(); + SetToRootFolder(); + RefreshListCtrl(); + // ::SetCurrentDirectory(::_currentFolderPrefix); + /* + BeforeChangeFolder(); + _currentFolderPrefix.Empty(); + AfterChangeFolder(); + SetCurrentPathText(); + RefreshListCtrl(UString(), 0, UStringVector()); + _listView.EnsureVisible(_listView.GetFocusedItem(), false); + */ +} + +void CPanel::OpenDrivesFolder() +{ + CloseOpenFolders(); + #ifdef UNDER_CE + NFsFolder::CFSFolder *folderSpec = new NFsFolder::CFSFolder; + SetNewFolder(folderSpec); + folderSpec->InitToRoot(); + #else + CFSDrives *folderSpec = new CFSDrives; + SetNewFolder(folderSpec); + folderSpec->Init(); + #endif + RefreshListCtrl(); +} + +void CPanel::OpenFolder(unsigned index) +{ + if (index == kParentIndex) + { + OpenParentFolder(); + return; + } + CMyComPtr newFolder; + const HRESULT res = _folder->BindToFolder((unsigned)index, &newFolder); + if (res != 0) + { + MessageBox_Error_HRESULT(res); + return; + } + if (!newFolder) + return; + SetNewFolder(newFolder); + LoadFullPath(); + RefreshListCtrl(); + // 17.02: fixed : now we don't select first item + // _listView.SetItemState_Selected(_listView.GetFocusedItem()); + _listView.EnsureVisible(_listView.GetFocusedItem(), false); +} + +void CPanel::OpenAltStreams() +{ + CRecordVector indices; + Get_ItemIndices_Operated(indices); + Int32 realIndex = -1; + if (indices.Size() > 1) + return; + if (indices.Size() == 1) + realIndex = (Int32)indices[0]; + + if (_folderAltStreams) + { + CMyComPtr newFolder; + _folderAltStreams->BindToAltStreams((UInt32)realIndex, &newFolder); + if (newFolder) + { + CDisableTimerProcessing disableTimerProcessing(*this); + CDisableNotify disableNotify(*this); + SetNewFolder(newFolder); + RefreshListCtrl(); + return; + } + return; + } + + #if defined(_WIN32) && !defined(UNDER_CE) + UString path; + if (realIndex >= 0) + path = GetItemFullPath((UInt32)realIndex); + else + { + path = GetFsPath(); + if (!NName::IsDriveRootPath_SuperAllowed(us2fs(path))) + if (!path.IsEmpty() && IS_PATH_SEPAR(path.Back())) + path.DeleteBack(); + } + + path.Add_Colon(); + BindToPathAndRefresh(path); + #endif +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PanelItemOpen.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PanelItemOpen.cpp new file mode 100644 index 00000000..9d783688 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PanelItemOpen.cpp @@ -0,0 +1,1813 @@ +// PanelItemOpen.cpp + +#include "StdAfx.h" + +#include "../../../Common/MyWindows.h" + +#include + +#include "../../../Common/IntToString.h" + +#include "../../../Common/AutoPtr.h" + +#include "../../../Windows/ProcessUtils.h" +#include "../../../Windows/FileName.h" +#include "../../../Windows/PropVariant.h" +#include "../../../Windows/PropVariantConv.h" + +#include "../../Common/FileStreams.h" +#include "../../Common/StreamObjects.h" + +#include "../Common/ExtractingFilePath.h" + +#include "App.h" + +#include "FileFolderPluginOpen.h" +#include "FormatUtils.h" +#include "LangUtils.h" +#include "PropertyNameRes.h" +#include "RegistryUtils.h" +#include "UpdateCallback100.h" + +#include "../GUI/ExtractRes.h" + +#include "resource.h" + +using namespace NWindows; +using namespace NSynchronization; +using namespace NFile; +using namespace NDir; + +extern bool g_RAM_Size_Defined; +extern size_t g_RAM_Size; + +#ifndef _UNICODE +extern bool g_IsNT; +#endif + +#define kTempDirPrefix FTEXT("7zO") + +// #define SHOW_DEBUG_INFO + +#ifdef SHOW_DEBUG_INFO + #define DEBUG_PRINT(s) OutputDebugStringA(s); + #define DEBUG_PRINT_W(s) OutputDebugStringW(s); + #define DEBUG_PRINT_NUM(s, num) { char ttt[32]; ConvertUInt32ToString(num, ttt); OutputDebugStringA(s); OutputDebugStringA(ttt); } +#else + #define DEBUG_PRINT(s) + #define DEBUG_PRINT_W(s) + #define DEBUG_PRINT_NUM(s, num) +#endif + + + +#ifndef UNDER_CE + +class CProcessSnapshot +{ + HANDLE _handle; +public: + CProcessSnapshot(): _handle(INVALID_HANDLE_VALUE) {} + ~CProcessSnapshot() { Close(); } + + bool Close() + { + if (_handle == INVALID_HANDLE_VALUE) + return true; + if (!::CloseHandle(_handle)) + return false; + _handle = INVALID_HANDLE_VALUE; + return true; + } + + bool Create() + { + _handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + return (_handle != INVALID_HANDLE_VALUE); + } + + bool GetFirstProcess(PROCESSENTRY32 *pe) { return BOOLToBool(Process32First(_handle, pe)); } + bool GetNextProcess(PROCESSENTRY32 *pe) { return BOOLToBool(Process32Next(_handle, pe)); } +}; + +#endif + + +/* +struct COpenExtProg +{ + const char *Ext; + const char *Prog; +}; + +static const COpenExtProg g_Progs[] = +{ + { "jpeg jpg png bmp gif", "Microsoft.Photos.exe" }, + { "html htm pdf", "MicrosoftEdge.exe" }, + // , { "rrr", "notepad.exe" } +}; + +static bool FindExtProg(const char *exts, const char *ext) +{ + unsigned len = (unsigned)strlen(ext); + for (;;) + { + const char *p = exts; + for (;; p++) + { + const char c = *p; + if (c == 0 || c == ' ') + break; + } + if (len == (unsigned)(p - exts) && IsString1PrefixedByString2(exts, ext)) + return true; + if (*p == 0) + return false; + exts = p + 1; + } +} + +class CPossibleProgs +{ +public: + AStringVector ProgNames; + + void SetFromExtension(const char *ext) // ext must be low case + { + ProgNames.Clear(); + for (unsigned i = 0; i < Z7_ARRAY_SIZE(g_Progs); i++) + if (FindExtProg(g_Progs[i].Ext, ext)) + { + ProgNames.Add(g_Progs[i].Prog); + } + } + + bool IsFromList(const UString &progName) const + { + FOR_VECTOR (i, ProgNames) + if (progName.IsEqualTo_Ascii_NoCase(ProgNames[i])) + return true; + return false; + } +}; +*/ + + +#ifndef UNDER_CE + +EXTERN_C_BEGIN + +/* +GetProcessImageFileName + returns the path in device form, rather than drive letters: + \Device\HarddiskVolume1\WINDOWS\SysWOW64\notepad.exe + +GetModuleFileNameEx works only after Sleep(something). Why? + returns the path + C:\WINDOWS\system32\NOTEPAD.EXE +*/ + +/* Kernel32.dll: Win7, Win2008R2; + Psapi.dll: (if PSAPI_VERSION=1) on Win7 and Win2008R2; + Psapi.dll: XP, Win2003, Vista, 2008; +*/ + +typedef DWORD (WINAPI *Func_GetProcessImageFileNameW)( + HANDLE hProcess, LPWSTR lpFilename, DWORD nSize); + +typedef DWORD (WINAPI *Func_GetModuleFileNameExW)( + HANDLE hProcess, HMODULE hModule, LPWSTR lpFilename, DWORD nSize); + +typedef DWORD (WINAPI *Func_GetProcessId)(HANDLE process); + +EXTERN_C_END + + +static HMODULE g_Psapi_dll_module; + +/* +static void My_GetProcessFileName_2(HANDLE hProcess, UString &path) +{ + path.Empty(); + const unsigned maxPath = 1024; + WCHAR temp[maxPath + 1]; + + const char *func_name = "GetModuleFileNameExW"; + Func_GetModuleFileNameExW my_func = (Func_GetModuleFileNameExW) + ::GetProcAddress(::GetModuleHandleA("kernel32.dll"), func_name); + if (!my_func) + { + if (!g_Psapi_dll_module) + g_Psapi_dll_module = LoadLibraryW(L"Psapi.dll"); + if (g_Psapi_dll_module) + my_func = (Func_GetModuleFileNameExW)::GetProcAddress(g_Psapi_dll_module, func_name); + } + if (my_func) + { + // DWORD num = GetModuleFileNameEx(hProcess, NULL, temp, maxPath); + DWORD num = my_func(hProcess, NULL, temp, maxPath); + if (num != 0) + path = temp; + } + // FreeLibrary(lib); +} +*/ + +Z7_DIAGNOSTIC_IGNORE_CAST_FUNCTION + +static void My_GetProcessFileName(HANDLE hProcess, UString &path) +{ + path.Empty(); + const unsigned maxPath = 1024; + WCHAR temp[maxPath + 1]; + + const char *func_name = + "GetProcessImageFileNameW"; + Func_GetProcessImageFileNameW my_func = Z7_GET_PROC_ADDRESS( + Func_GetProcessImageFileNameW, ::GetModuleHandleA("kernel32.dll"), func_name); + + if (!my_func) + { + if (!g_Psapi_dll_module) + g_Psapi_dll_module = LoadLibraryW(L"Psapi.dll"); + if (g_Psapi_dll_module) + my_func = Z7_GET_PROC_ADDRESS( + Func_GetProcessImageFileNameW, g_Psapi_dll_module, func_name); + } + + if (my_func) + { + const DWORD num = + // GetProcessImageFileNameW(hProcess, temp, maxPath); + my_func(hProcess, temp, maxPath); + if (num != 0) + path = temp; + } + // FreeLibrary(lib); +} + +struct CSnapshotProcess +{ + DWORD Id; + DWORD ParentId; + UString Name; +}; + +static void GetSnapshot(CObjectVector &items) +{ + items.Clear(); + + CProcessSnapshot snapshot; + if (!snapshot.Create()) + return; + + DEBUG_PRINT("snapshot.Create() OK"); + PROCESSENTRY32 pe; + CSnapshotProcess item; + memset(&pe, 0, sizeof(pe)); + pe.dwSize = sizeof(pe); + BOOL res = snapshot.GetFirstProcess(&pe); + while (res) + { + item.Id = pe.th32ProcessID; + item.ParentId = pe.th32ParentProcessID; + item.Name = GetUnicodeString(pe.szExeFile); + items.Add(item); + res = snapshot.GetNextProcess(&pe); + } +} + +#endif + + +class CChildProcesses +{ + #ifndef UNDER_CE + CRecordVector _ids; + #endif + +public: + // bool ProgsWereUsed; + CRecordVector Handles; + CRecordVector NeedWait; + // UStringVector Names; + + #ifndef UNDER_CE + UString Path; + #endif + + // CChildProcesses(): ProgsWereUsed(false) {} + ~CChildProcesses() { CloseAll(); } + void DisableWait(unsigned index) { NeedWait[index] = false; } + + void CloseAll() + { + FOR_VECTOR (i, Handles) + { + HANDLE h = Handles[i]; + if (h != NULL) + CloseHandle(h); + } + + Handles.Clear(); + NeedWait.Clear(); + // Names.Clear(); + + #ifndef UNDER_CE + // Path.Empty(); + _ids.Clear(); + #endif + } + + void SetMainProcess(HANDLE h) + { + #ifndef UNDER_CE + const + Func_GetProcessId func = Z7_GET_PROC_ADDRESS( + Func_GetProcessId, ::GetModuleHandleA("kernel32.dll"), + "GetProcessId"); + if (func) + { + const DWORD id = func(h); + if (id != 0) + _ids.AddToUniqueSorted(id); + } + + My_GetProcessFileName(h, Path); + DEBUG_PRINT_W(Path); + + #endif + + Handles.Add(h); + NeedWait.Add(true); + } + + #ifndef UNDER_CE + + void Update(bool needFindProcessByPath /* , const CPossibleProgs &progs */) + { + /* + if (_ids.IsEmpty()) + return; + */ + + CObjectVector sps; + GetSnapshot(sps); + + const int separ = Path.ReverseFind_PathSepar(); + const UString mainName = Path.Ptr((unsigned)(separ + 1)); + if (mainName.IsEmpty()) + needFindProcessByPath = false; + + const DWORD currentProcessId = GetCurrentProcessId(); + + for (;;) + { + bool wasAdded = false; + + FOR_VECTOR (i, sps) + { + const CSnapshotProcess &sp = sps[i]; + const DWORD id = sp.Id; + + if (id == currentProcessId) + continue; + if (_ids.FindInSorted(id) >= 0) + continue; + + bool isSameName = false; + const UString &name = sp.Name; + + if (needFindProcessByPath) + isSameName = mainName.IsEqualTo_NoCase(name); + + bool needAdd = false; + // bool isFromProgs = false; + + if (isSameName || _ids.FindInSorted(sp.ParentId) >= 0) + needAdd = true; + /* + else if (progs.IsFromList(name)) + { + needAdd = true; + isFromProgs = true; + } + */ + + if (needAdd) + { + DEBUG_PRINT("----- OpenProcess -----"); + DEBUG_PRINT_W(name); + HANDLE hProcess = OpenProcess(SYNCHRONIZE, FALSE, id); + if (hProcess) + { + DEBUG_PRINT("----- OpenProcess OK -----"); + // if (!isFromProgs) + _ids.AddToUniqueSorted(id); + Handles.Add(hProcess); + NeedWait.Add(true); + // Names.Add(name); + wasAdded = true; + // ProgsWereUsed = isFromProgs; + } + } + } + + if (!wasAdded) + break; + } + } + + #endif +}; + + +struct CTmpProcessInfo: public CTempFileInfo +{ + CChildProcesses Processes; + HWND Window; + UString FullPathFolderPrefix; + bool UsePassword; + UString Password; + + bool ReadOnly; + + CTmpProcessInfo(): UsePassword(false), ReadOnly(false) {} +}; + + +class CTmpProcessInfoRelease +{ + CTmpProcessInfo *_tmpProcessInfo; +public: + bool _needDelete; + CTmpProcessInfoRelease(CTmpProcessInfo &tpi): + _tmpProcessInfo(&tpi), _needDelete(true) {} + ~CTmpProcessInfoRelease() + { + if (_needDelete) + _tmpProcessInfo->DeleteDirAndFile(); + } +}; + +void GetFolderError(CMyComPtr &folder, UString &s); + + +HRESULT CPanel::OpenAsArc(IInStream *inStream, + const CTempFileInfo &tempFileInfo, + const UString &virtualFilePath, + const UString &arcFormat, + COpenResult &openRes) +{ + openRes.Encrypted = false; + CFolderLink folderLink; + (CTempFileInfo &)folderLink = tempFileInfo; + + if (inStream) + folderLink.IsVirtual = true; + else + { + if (!folderLink.FileInfo.Find(folderLink.FilePath)) + return GetLastError_noZero_HRESULT(); + if (folderLink.FileInfo.IsDir()) + return S_FALSE; + folderLink.IsVirtual = false; + } + + folderLink.VirtualPath = virtualFilePath; + + CFfpOpen ffp; + const HRESULT res = ffp.OpenFileFolderPlugin(inStream, + folderLink.FilePath.IsEmpty() ? us2fs(virtualFilePath) : folderLink.FilePath, + arcFormat, GetParent()); + + openRes.Encrypted = ffp.Encrypted; + openRes.ErrorMessage = ffp.ErrorMessage; + + RINOK(res) + + folderLink.Password = ffp.Password; + folderLink.UsePassword = ffp.Encrypted; + + if (_folder) + folderLink.ParentFolderPath = GetFolderPath(_folder); + else + folderLink.ParentFolderPath = _currentFolderPrefix; + + if (!_parentFolders.IsEmpty()) + folderLink.ParentFolder = _folder; + + _parentFolders.Add(folderLink); + _parentFolders.Back().Library.Attach(_library.Detach()); + + ReleaseFolder(); + _library.Free(); + SetNewFolder(ffp.Folder); + _library.Attach(ffp.Library.Detach()); + + _flatMode = _flatModeForArc; + + _thereAreDeletedItems = false; + + if (!openRes.ErrorMessage.IsEmpty()) + MessageBox_Error(openRes.ErrorMessage); + /* + UString s; + GetFolderError(_folder, s); + if (!s.IsEmpty()) + MessageBox_Error(s); + */ + // we don't show error here by some reasons: + // after MessageBox_Warning it throws exception in nested archives in Debug Mode. why ?. + // MessageBox_Warning(L"test error"); + + return S_OK; +} + + +HRESULT CPanel::OpenAsArc_Msg(IInStream *inStream, + const CTempFileInfo &tempFileInfo, + const UString &virtualFilePath, + const UString &arcFormat + // , bool &encrypted + // , bool showErrorMessage + ) +{ + COpenResult opRes; + + HRESULT res = OpenAsArc(inStream, tempFileInfo, virtualFilePath, arcFormat, opRes); + + if (res == S_OK) + return res; + if (res == E_ABORT) + return res; + + // if (showErrorMessage) + if (opRes.Encrypted || res != S_FALSE) // 17.01 : we show message also for (res != S_FALSE) + { + UString message; + if (res == S_FALSE) + { + message = MyFormatNew( + opRes.Encrypted ? + IDS_CANT_OPEN_ENCRYPTED_ARCHIVE : + IDS_CANT_OPEN_ARCHIVE, + virtualFilePath); + } + else + message = HResultToMessage(res); + MessageBox_Error(message); + } + + return res; +} + + +HRESULT CPanel::OpenAsArc_Name(const UString &relPath, const UString &arcFormat + // , bool &encrypted, + // , bool showErrorMessage + ) +{ + CTempFileInfo tfi; + tfi.RelPath = relPath; + tfi.FolderPath = us2fs(GetFsPath()); + const UString fullPath = GetFsPath() + relPath; + tfi.FilePath = us2fs(fullPath); + return OpenAsArc_Msg(NULL, tfi, fullPath, arcFormat /* , encrypted, showErrorMessage */); +} + + +HRESULT CPanel::OpenAsArc_Index(unsigned index, const wchar_t *type + // , bool showErrorMessage + ) +{ + CDisableTimerProcessing disableTimerProcessing1(*this); + CDisableNotify disableNotify(*this); + + HRESULT res = OpenAsArc_Name(GetItemRelPath2(index), type ? type : L"" /* , encrypted, showErrorMessage */); + if (res != S_OK) + { + RefreshTitle(true); // in case of error we must refresh changed title of 7zFM + return res; + } + RefreshListCtrl(); + return S_OK; +} + + +HRESULT CPanel::OpenParentArchiveFolder() +{ + CDisableTimerProcessing disableTimerProcessing(*this); + CDisableNotify disableNotify(*this); + if (_parentFolders.Size() < 2) + return S_OK; + const CFolderLink &folderLinkPrev = _parentFolders[_parentFolders.Size() - 2]; + const CFolderLink &folderLink = _parentFolders.Back(); + NFind::CFileInfo newFileInfo; + if (newFileInfo.Find(folderLink.FilePath)) + { + if (folderLink.WasChanged_from_FolderLink(newFileInfo)) + { + const UString message = MyFormatNew(IDS_WANT_UPDATE_MODIFIED_FILE, folderLink.RelPath); + if (::MessageBoxW((HWND)*this, message, L"7-Zip", MB_YESNOCANCEL | MB_ICONQUESTION) == IDYES) + { + if (OnOpenItemChanged(folderLink.FileIndex, fs2us(folderLink.FilePath), + folderLinkPrev.UsePassword, folderLinkPrev.Password) != S_OK) + { + ::MessageBoxW((HWND)*this, MyFormatNew(IDS_CANNOT_UPDATE_FILE, + fs2us(folderLink.FilePath)), L"7-Zip", MB_OK | MB_ICONSTOP); + return S_OK; + } + } + } + } + folderLink.DeleteDirAndFile(); + return S_OK; +} + + +static const char * const kExeExtensions = + " exe bat ps1 com lnk" + " "; + +static const char * const kStartExtensions = + #ifdef UNDER_CE + " cab" + #endif + " exe bat ps1 com lnk" + " chm" + " msi doc dot xls ppt pps wps wpt wks xlr wdb vsd pub" + + " docx docm dotx dotm xlsx xlsm xltx xltm xlsb xps" + " xlam pptx pptm potx potm ppam ppsx ppsm vsdx xsn" + " mpp" + " msg" + " dwf" + + " flv swf" + + " epub" + " odt ods" + " wb3" + " pdf" + " ps" + " txt" + " xml xsd xsl xslt hxk hxc htm html xhtml xht mht mhtml htw asp aspx css cgi jsp shtml" + " h hpp hxx c cpp cxx m mm go swift" + " awk sed hta js json php php3 php4 php5 phptml pl pm py pyo rb tcl ts vbs" + " asm" + " mak clw csproj vcproj sln dsp dsw" + " "; + +// bool FindExt(const char *p, const UString &name, AString &s); +bool FindExt(const char *p, const UString &name, CStringFinder &finder); + +static bool DoItemAlwaysStart(const UString &name) +{ + CStringFinder finder; + return FindExt(kStartExtensions, name, finder); +} + +UString GetQuotedString(const UString &s); + + +void SplitCmdLineSmart(const UString &cmd, UString &prg, UString ¶ms); +void SplitCmdLineSmart(const UString &cmd, UString &prg, UString ¶ms) +{ + params.Empty(); + prg = cmd; + prg.Trim(); + if (prg.Len() >= 2 && prg[0] == L'"') + { + int pos = prg.Find(L'"', 1); + if (pos >= 0) + { + if ((unsigned)(pos + 1) == prg.Len() || prg[pos + 1] == ' ') + { + params = prg.Ptr((unsigned)(pos + 1)); + params.Trim(); + prg.DeleteFrom((unsigned)pos); + prg.DeleteFrontal(1); + } + } + } +} + + +static WRes StartAppWithParams(const UString &cmd, const UStringVector ¶mVector, CProcess &process) +{ + UString param; + UString prg; + + SplitCmdLineSmart(cmd, prg, param); + + param.Trim(); + + // int pos = params.Find(L"%1"); + + FOR_VECTOR (i, paramVector) + { + if (!param.IsEmpty() && param.Back() != ' ') + param.Add_Space(); + param += GetQuotedString(paramVector[i]); + } + + return process.Create(prg, param, NULL); +} + + +static HRESULT StartEditApplication(const UString &path, bool useEditor, HWND window, CProcess &process) +{ + UString command; + ReadRegEditor(useEditor, command); + if (command.IsEmpty()) + { + #ifdef UNDER_CE + command = "\\Windows\\"; + #else + FString winDir; + if (!GetWindowsDir(winDir)) + return 0; + NName::NormalizeDirPathPrefix(winDir); + command = fs2us(winDir); + #endif + command += "notepad.exe"; + } + + UStringVector params; + params.Add(path); + + const WRes res = StartAppWithParams(command, params, process); + if (res != 0) + ::MessageBoxW(window, LangString(IDS_CANNOT_START_EDITOR), L"7-Zip", MB_OK | MB_ICONSTOP); + return HRESULT_FROM_WIN32(res); +} + + +void CApp::DiffFiles() +{ + const CPanel &panel = GetFocusedPanel(); + + if (!panel.Is_IO_FS_Folder()) + { + panel.MessageBox_Error_UnsupportOperation(); + return; + } + + CRecordVector indices; + panel.Get_ItemIndices_Selected(indices); + + UString path1, path2; + if (indices.Size() == 2) + { + path1 = panel.GetItemFullPath(indices[0]); + path2 = panel.GetItemFullPath(indices[1]); + } + else if (indices.Size() == 1 && NumPanels >= 2) + { + const CPanel &destPanel = Panels[1 - LastFocusedPanel]; + + if (!destPanel.Is_IO_FS_Folder()) + { + panel.MessageBox_Error_UnsupportOperation(); + return; + } + + path1 = panel.GetItemFullPath(indices[0]); + CRecordVector indices2; + destPanel.Get_ItemIndices_Selected(indices2); + if (indices2.Size() == 1) + path2 = destPanel.GetItemFullPath(indices2[0]); + else + { + UString relPath = panel.GetItemRelPath2(indices[0]); + if (panel._flatMode && !destPanel._flatMode) + relPath = panel.GetItemName(indices[0]); + path2 = destPanel._currentFolderPrefix + relPath; + } + } + else + return; + + DiffFiles(path1, path2); +} + +void CApp::DiffFiles(const UString &path1, const UString &path2) +{ + UString command; + ReadRegDiff(command); + if (command.IsEmpty()) + return; + + UStringVector params; + params.Add(path1); + params.Add(path2); + + WRes res; + { + CProcess process; + res = StartAppWithParams(command, params, process); + } + if (res == 0) + return; + ::MessageBoxW(_window, LangString(IDS_CANNOT_START_EDITOR), L"7-Zip", MB_OK | MB_ICONSTOP); +} + + +HRESULT StartApplication(const UString &dir, const UString &path, HWND window, CProcess &process); +void StartApplicationDontWait(const UString &dir, const UString &path, HWND window); + +void CPanel::EditItem(unsigned index, bool useEditor) +{ + if (!_parentFolders.IsEmpty()) + { + OpenItemInArchive(index, false, true, true, useEditor); + return; + } + CProcess process; + StartEditApplication(GetItemFullPath(index), useEditor, + // (HWND)*this, + GetParent(), + process); +} + + +void CPanel::OpenFolderExternal(unsigned index) +{ + UString prefix = GetFsPath(); + UString path = prefix; + + if (index == kParentIndex) + { + if (prefix.IsEmpty()) + return; + const wchar_t c = prefix.Back(); + if (!IS_PATH_SEPAR(c) && c != ':') + return; + prefix.DeleteBack(); + int pos = prefix.ReverseFind_PathSepar(); + if (pos < 0) + return; + prefix.DeleteFrom((unsigned)(pos + 1)); + path = prefix; + } + else + { + path += GetItemRelPath(index); + path.Add_PathSepar(); + } + + StartApplicationDontWait(prefix, path, + // (HWND)*this + GetParent() + ); +} + + +bool CPanel::IsVirus_Message(const UString &name) +{ + UString name2; + + const wchar_t cRLO = (wchar_t)0x202E; + bool isVirus = false; + bool isSpaceError = false; + name2 = name; + + if (name2.Find(cRLO) >= 0) + { + const UString badString(cRLO); + name2.Replace(badString, L"[RLO]"); + isVirus = true; + } + { + const wchar_t * const kVirusSpaces = L" "; + // const unsigned kNumSpaces = strlen(kVirusSpaces); + for (;;) + { + int pos = name2.Find(kVirusSpaces); + if (pos < 0) + break; + isVirus = true; + isSpaceError = true; + name2.Replace(kVirusSpaces, L" "); + } + } + + #ifdef _WIN32 + { + unsigned i; + for (i = name2.Len(); i != 0;) + { + wchar_t c = name2[i - 1]; + if (c != '.' && c != ' ') + break; + i--; + name2.ReplaceOneCharAtPos(i, '_'); + } + if (i != name2.Len()) + { + CStringFinder finder; + UString name3 = name2; + name3.DeleteFrom(i); + if (FindExt(kExeExtensions, name3, finder)) + isVirus = true; + } + } + #endif + + if (!isVirus) + return false; + + UString s = LangString(IDS_VIRUS); + + if (!isSpaceError) + { + const int pos1 = s.Find(L'('); + if (pos1 >= 0) + { + const int pos2 = s.Find(L')', (unsigned)pos1 + 1); + if (pos2 >= 0) + { + s.Delete((unsigned)pos1, (unsigned)pos2 + 1 - (unsigned)pos1); + if (pos1 > 0 && s[pos1 - 1] == ' ' && s[pos1] == '.') + s.Delete(pos1 - 1); + } + } + } + + UString name3 = name; + name3.Replace(L'\n', L'_'); + name2.Replace(L'\n', L'_'); + + s.Add_LF(); s += name2; + s.Add_LF(); s += name3; + + MessageBox_Error(s); + return true; +} + + +void CPanel::OpenItem(unsigned index, bool tryInternal, bool tryExternal, const wchar_t *type) +{ + CDisableTimerProcessing disableTimerProcessing(*this); + const UString name = GetItemRelPath2(index); + + if (tryExternal) + if (IsVirus_Message(name)) + return; + + if (!_parentFolders.IsEmpty()) + { + OpenItemInArchive(index, tryInternal, tryExternal, false, false, type); + return; + } + + CDisableNotify disableNotify(*this); + UString prefix = GetFsPath(); + UString fullPath = prefix + name; + + if (tryInternal) + if (!tryExternal || !DoItemAlwaysStart(name)) + { + HRESULT res = OpenAsArc_Index(index, type + // , true + ); + disableNotify.Restore(); // we must restore to allow text notification update + InvalidateList(); + if (res == S_OK || res == E_ABORT) + return; + if (res != S_FALSE) + { + MessageBox_Error_HRESULT(res); + return; + } + } + + if (tryExternal) + { + // SetCurrentDirectory opens HANDLE to folder!!! + // NDirectory::MySetCurrentDirectory(prefix); + StartApplicationDontWait(prefix, fullPath, + // (HWND)*this + GetParent() + ); + } +} + +class CThreadCopyFrom: public CProgressThreadVirt +{ + HRESULT ProcessVirt() Z7_override; +public: + UString FullPath; + UInt32 ItemIndex; + + CMyComPtr FolderOperations; + CMyComPtr UpdateCallback; + CUpdateCallback100Imp *UpdateCallbackSpec; +}; + +HRESULT CThreadCopyFrom::ProcessVirt() +{ + return FolderOperations->CopyFromFile(ItemIndex, FullPath, UpdateCallback); +} + +HRESULT CPanel::OnOpenItemChanged(UInt32 index, const wchar_t *fullFilePath, + bool usePassword, const UString &password) +{ + if (!_folderOperations) + { + MessageBox_Error_UnsupportOperation(); + return E_FAIL; + } + + CThreadCopyFrom t; + t.UpdateCallbackSpec = new CUpdateCallback100Imp; + t.UpdateCallback = t.UpdateCallbackSpec; + t.UpdateCallbackSpec->ProgressDialog = &t; + t.ItemIndex = index; + t.FullPath = fullFilePath; + t.FolderOperations = _folderOperations; + + t.UpdateCallbackSpec->Init(); + t.UpdateCallbackSpec->PasswordIsDefined = usePassword; + t.UpdateCallbackSpec->Password = password; + + + RINOK(t.Create(GetItemName(index), (HWND)*this)) + return t.Result; +} + +LRESULT CPanel::OnOpenItemChanged(LPARAM lParam) +{ + // DEBUG_PRINT_NUM("OnOpenItemChanged", GetCurrentThreadId()); + + CTmpProcessInfo &tpi = *(CTmpProcessInfo *)lParam; + if (tpi.FullPathFolderPrefix != _currentFolderPrefix) + return 0; + UInt32 fileIndex = tpi.FileIndex; + UInt32 numItems; + _folder->GetNumberOfItems(&numItems); + + // This code is not 100% OK for cases when there are several files with + // tpi.RelPath name and there are changes in archive before update. + // So tpi.FileIndex can point to another file. + + if (fileIndex >= numItems || GetItemRelPath(fileIndex) != tpi.RelPath) + { + UInt32 i; + for (i = 0; i < numItems; i++) + if (GetItemRelPath(i) == tpi.RelPath) + break; + if (i == numItems) + return 0; + fileIndex = i; + } + + CSelectedState state; + SaveSelectedState(state); + + CDisableNotify disableNotify(*this); // do we need it?? + + HRESULT result = OnOpenItemChanged(fileIndex, fs2us(tpi.FilePath), tpi.UsePassword, tpi.Password); + RefreshListCtrl(state); + if (result != S_OK) + return 0; + return 1; +} + + +CExitEventLauncher g_ExitEventLauncher; + +void CExitEventLauncher::Exit(bool hardExit) +{ + if (_needExit) + { + _exitEvent.Set(); + _needExit = false; + } + + if (_numActiveThreads == 0) + return; + + FOR_VECTOR (i, _threads) + { + ::CThread &th = _threads[i]; + if (Thread_WasCreated(&th)) + { + const DWORD waitResult = WaitForSingleObject(th, hardExit ? 100 : INFINITE); + // Thread_Wait(&th); + // if (waitResult == WAIT_TIMEOUT) wait = 1; + if (!hardExit && waitResult != WAIT_OBJECT_0) + continue; + Thread_Close(&th); + _numActiveThreads--; + } + } +} + + + +static THREAD_FUNC_DECL MyThreadFunction(void *param) +{ + DEBUG_PRINT("==== MyThreadFunction ===="); + + CMyUniquePtr tpi((CTmpProcessInfo *)param); + CChildProcesses &processes = tpi->Processes; + + const bool mainProcessWasSet = !processes.Handles.IsEmpty(); + + bool isComplexMode = true; + + if (!processes.Handles.IsEmpty()) + { + + const DWORD startTime = GetTickCount(); + + /* + CPossibleProgs progs; + { + const UString &name = tpi->RelPath; + int slashPos = name.ReverseFind_PathSepar(); + int dotPos = name.ReverseFind_Dot(); + if (dotPos > slashPos) + { + const UString ext = name.Ptr(dotPos + 1); + AString extA = UnicodeStringToMultiByte(ext); + extA.MakeLower_Ascii(); + progs.SetFromExtension(extA); + } + } + */ + + bool firstPass = true; + + for (;;) + { + CRecordVector handles; + CUIntVector indices; + + FOR_VECTOR (i, processes.Handles) + { + if (handles.Size() > 60) + break; + if (processes.NeedWait[i]) + { + handles.Add(processes.Handles[i]); + indices.Add(i); + } + } + + bool needFindProcessByPath = false; + + if (handles.IsEmpty()) + { + if (!firstPass) + break; + } + else + { + handles.Add(g_ExitEventLauncher._exitEvent); + + DWORD waitResult = WaitForMultiObj_Any_Infinite(handles.Size(), handles.ConstData()); + + waitResult -= WAIT_OBJECT_0; + + if (waitResult >= handles.Size() - 1) + { + processes.CloseAll(); + /* + if (waitResult == handles.Size() - 1) + { + // exit event + // we want to delete temp files, if progs were used + if (processes.ProgsWereUsed) + break; + } + */ + return waitResult >= (DWORD)handles.Size() ? 1 : 0; + } + + if (firstPass && indices.Size() == 1) + { + const DWORD curTime = GetTickCount() - startTime; + + /* + if (curTime > 5 * 1000) + progs.ProgNames.Clear(); + */ + + needFindProcessByPath = (curTime < 2 * 1000); + + if (needFindProcessByPath) + { + NFind::CFileInfo newFileInfo; + if (newFileInfo.Find(tpi->FilePath)) + if (tpi->WasChanged_from_TempFileInfo(newFileInfo)) + needFindProcessByPath = false; + } + + DEBUG_PRINT_NUM(" -- firstPass -- time = ", curTime) + } + + processes.DisableWait(indices[(unsigned)waitResult]); + } + + firstPass = false; + + // Sleep(300); + #ifndef UNDER_CE + processes.Update(needFindProcessByPath /* , progs */); + #endif + } + + + const DWORD curTime = GetTickCount() - startTime; + + DEBUG_PRINT_NUM("after time = ", curTime) + + processes.CloseAll(); + + isComplexMode = (curTime < 2 * 1000); + + } + + bool needCheckTimestamp = true; + + for (;;) + { + NFind::CFileInfo newFileInfo; + + if (!newFileInfo.Find(tpi->FilePath)) + break; + + if (mainProcessWasSet) + { + if (tpi->WasChanged_from_TempFileInfo(newFileInfo)) + { + UString m = MyFormatNew(IDS_CANNOT_UPDATE_FILE, fs2us(tpi->FilePath)); + if (tpi->ReadOnly) + { + m.Add_LF(); + AddLangString(m, IDS_PROP_READ_ONLY); + m.Add_LF(); + m += tpi->FullPathFolderPrefix; + ::MessageBoxW(g_HWND, m, L"7-Zip", MB_OK | MB_ICONSTOP); + return 0; + } + { + const UString message = MyFormatNew(IDS_WANT_UPDATE_MODIFIED_FILE, tpi->RelPath); + if (::MessageBoxW(g_HWND, message, L"7-Zip", MB_YESNOCANCEL | MB_ICONQUESTION) == IDYES) + { + // DEBUG_PRINT_NUM("SendMessage", GetCurrentThreadId()); + if (SendMessage(tpi->Window, kOpenItemChanged, 0, (LONG_PTR)tpi.get()) != 1) + { + ::MessageBoxW(g_HWND, m, L"7-Zip", MB_OK | MB_ICONSTOP); + return 0; + } + } + needCheckTimestamp = false; + break; + } + } + + if (!isComplexMode) + break; + } + + // DEBUG_PRINT("WaitForSingleObject"); + DWORD waitResult = ::WaitForSingleObject(g_ExitEventLauncher._exitEvent, INFINITE); + // DEBUG_PRINT("---"); + + if (waitResult == WAIT_OBJECT_0) + break; + + return 1; + } + + { + NFind::CFileInfo newFileInfo; + const bool finded = newFileInfo.Find(tpi->FilePath); + if (!needCheckTimestamp + || !finded + || !tpi->WasChanged_from_TempFileInfo(newFileInfo)) + { + DEBUG_PRINT("Delete Temp file"); + tpi->DeleteDirAndFile(); + } + } + + return 0; +} + + +/* +#if defined(_WIN32) && !defined(UNDER_CE) +static const FChar * const k_ZoneId_StreamName = FTEXT(":Zone.Identifier"); +#endif + + +#ifndef UNDER_CE + +static void ReadZoneFile(CFSTR fileName, CByteBuffer &buf) +{ + buf.Free(); + NIO::CInFile file; + if (!file.Open(fileName)) + return; + UInt64 fileSize; + if (!file.GetLength(fileSize)) + return; + if (fileSize == 0 || fileSize >= ((UInt32)1 << 20)) + return; + buf.Alloc((size_t)fileSize); + size_t processed; + if (file.ReadFull(buf, (size_t)fileSize, processed) && processed == fileSize) + return; + buf.Free(); +} + +static bool WriteZoneFile(CFSTR fileName, const CByteBuffer &buf) +{ + NIO::COutFile file; + if (!file.Create(fileName, true)) + return false; + UInt32 processed; + if (!file.Write(buf, (UInt32)buf.Size(), processed)) + return false; + return processed == buf.Size(); +} + +#endif +*/ + +/* +Z7_CLASS_IMP_COM_1( + CBufSeqOutStream_WithFile + , ISequentialOutStream +) + Byte *_buffer; + size_t _size; + size_t _pos; + + size_t _fileWritePos; + bool fileMode; +public: + + bool IsStreamInMem() const { return !fileMode; } + size_t GetMemStreamWrittenSize() const { return _pos; } + + // ISequentialOutStream *FileStream; + FString FilePath; + COutFileStream *outFileStreamSpec; + CMyComPtr outFileStream; + + CBufSeqOutStream_WithFile(): outFileStreamSpec(NULL) {} + + void Init(Byte *buffer, size_t size) + { + fileMode = false; + _buffer = buffer; + _pos = 0; + _size = size; + _fileWritePos = 0; + } + + HRESULT FlushToFile(); + size_t GetPos() const { return _pos; } +}; + +static const UInt32 kBlockSize = ((UInt32)1 << 31); + +STDMETHODIMP CBufSeqOutStream_WithFile::Write(const void *data, UInt32 size, UInt32 *processedSize) +{ + if (processedSize) + *processedSize = 0; + if (!fileMode) + { + if (_size - _pos >= size) + { + if (size != 0) + { + memcpy(_buffer + _pos, data, size); + _pos += size; + } + if (processedSize) + *processedSize = (UInt32)size; + return S_OK; + } + + fileMode = true; + } + RINOK(FlushToFile()); + return outFileStream->Write(data, size, processedSize); +} + +HRESULT CBufSeqOutStream_WithFile::FlushToFile() +{ + if (!outFileStream) + { + outFileStreamSpec = new COutFileStream; + outFileStream = outFileStreamSpec; + if (!outFileStreamSpec->Create(FilePath, false)) + { + outFileStream.Release(); + return E_FAIL; + // MessageBoxMyError(UString("Can't create file ") + fs2us(tempFilePath)); + } + } + while (_fileWritePos != _pos) + { + size_t cur = _pos - _fileWritePos; + UInt32 curSize = (cur < kBlockSize) ? (UInt32)cur : kBlockSize; + UInt32 processedSizeLoc = 0; + HRESULT res = outFileStream->Write(_buffer + _fileWritePos, curSize, &processedSizeLoc); + _fileWritePos += processedSizeLoc; + RINOK(res); + if (processedSizeLoc == 0) + return E_FAIL; + } + return S_OK; +} +*/ + +/* +static HRESULT GetTime(IFolderFolder *folder, UInt32 index, PROPID propID, FILETIME &filetime, bool &filetimeIsDefined) +{ + filetimeIsDefined = false; + NCOM::CPropVariant prop; + RINOK(folder->GetProperty(index, propID, &prop)); + if (prop.vt == VT_FILETIME) + { + filetime = prop.filetime; + filetimeIsDefined = (filetime.dwHighDateTime != 0 || filetime.dwLowDateTime != 0); + } + else if (prop.vt != VT_EMPTY) + return E_FAIL; + return S_OK; +} +*/ + + +/* +tryInternal tryExternal + false false : unused + false true : external + true false : internal + true true : smart based on file extension: + !alwaysStart(name) : both + alwaysStart(name) : external +*/ + +void CPanel::OpenItemInArchive(unsigned index, bool tryInternal, bool tryExternal, bool editMode, bool useEditor, const wchar_t *type) +{ + // we don't want to change hash data here + if (IsHashFolder()) + return; + + const UString name = GetItemName(index); + const UString relPath = GetItemRelPath(index); + + if (tryExternal) + if (IsVirus_Message(name)) + return; + + if (!_folderOperations) + { + MessageBox_Error_UnsupportOperation(); + return; + } + + bool tryAsArchive = tryInternal && (!tryExternal || !DoItemAlwaysStart(name)); + + const UString fullVirtPath = _currentFolderPrefix + relPath; + + CTempDir tempDirectory; + if (!tempDirectory.Create(kTempDirPrefix)) + { + MessageBox_LastError(); + return; + } + + FString tempDir = tempDirectory.GetPath(); + FString tempDirNorm = tempDir; + NName::NormalizeDirPathPrefix(tempDirNorm); + const FString tempFilePath = tempDirNorm + us2fs(Get_Correct_FsFile_Name(name)); + + CTempFileInfo tempFileInfo; + tempFileInfo.FileIndex = index; + tempFileInfo.RelPath = relPath; + tempFileInfo.FolderPath = tempDir; + tempFileInfo.FilePath = tempFilePath; + tempFileInfo.NeedDelete = true; + + if (tryAsArchive) + { + CMyComPtr getStream; + _folder.QueryInterface(IID_IInArchiveGetStream, &getStream); + if (getStream) + { + CMyComPtr subSeqStream; + getStream->GetStream(index, &subSeqStream); + if (subSeqStream) + { + CMyComPtr subStream; + subSeqStream.QueryInterface(IID_IInStream, &subStream); + if (subStream) + { + HRESULT res = OpenAsArc_Msg(subStream, tempFileInfo, fullVirtPath, type ? type : L"" + // , true // showErrorMessage + ); + if (res == S_OK) + { + tempDirectory.DisableDeleting(); + RefreshListCtrl(); + return; + } + if (res == E_ABORT || res != S_FALSE) + return; + if (!tryExternal) + return; + tryAsArchive = false; + } + } + } + } + + + CRecordVector indices; + indices.Add(index); + + UStringVector messages; + + bool usePassword = false; + UString password; + if (!_parentFolders.IsEmpty()) + { + const CFolderLink &fl = _parentFolders.Back(); + usePassword = fl.UsePassword; + password = fl.Password; + } + + /* + #if defined(_WIN32) && !defined(UNDER_CE) + CByteBuffer zoneBuf; + #ifndef _UNICODE + if (g_IsNT) + #endif + if (!_parentFolders.IsEmpty()) + { + const CFolderLink &fl = _parentFolders.Front(); + if (!fl.IsVirtual && !fl.FilePath.IsEmpty()) + ReadZoneFile(fl.FilePath + k_ZoneId_StreamName, zoneBuf); + } + #endif + */ + + + CVirtFileSystem *virtFileSystemSpec = NULL; + CMyComPtr virtFileSystem; + + const bool isAltStream = IsItem_AltStream(index); + + CCopyToOptions options; + options.includeAltStreams = true; + options.replaceAltStreamChars = isAltStream; + { + // CContextMenuInfo ci; + // ci.Load(); + // if (ci.WriteZone != (UInt32)(Int32)-1) + // we use kAll when we unpack just one file. + options.ZoneIdMode = NExtract::NZoneIdMode::kAll; + options.NeedRegistryZone = false; + } + + if (tryAsArchive) + { + // actually we want to get sum: size of main file plus sizes of altStreams. + // but now there is no interface to get altStreams sizes. + NCOM::CPropVariant prop; + _folder->GetProperty(index, kpidSize, &prop); + const size_t fileLimit = g_RAM_Size_Defined ? + g_RAM_Size >> MyMax(_parentFolders.Size() + 1, 8u): + 1u << 22; + UInt64 fileSize = 0; + if (!ConvertPropVariantToUInt64(prop, fileSize)) + fileSize = fileLimit; +#if 0 // 1 : for debug + fileLimit = 1; +#endif + + if (fileSize <= fileLimit) + { + options.streamMode = true; + virtFileSystemSpec = new CVirtFileSystem; + virtFileSystem = virtFileSystemSpec; + virtFileSystemSpec->FileName = name; + virtFileSystemSpec->IsAltStreamFile = isAltStream; + +#if defined(_WIN32) && !defined(UNDER_CE) +#ifndef _UNICODE + if (g_IsNT) +#endif + { + Get_ZoneId_Stream_from_ParentFolders(virtFileSystemSpec->ZoneBuf); + options.ZoneBuf = virtFileSystemSpec->ZoneBuf; + } +#endif + + virtFileSystemSpec->MaxTotalAllocSize = (size_t)fileSize + + (1 << 16); // we allow additional total size for small alt streams. + virtFileSystemSpec->DirPrefix = tempDirNorm; + // options.VirtFileSystem = virtFileSystem; + options.VirtFileSystemSpec = virtFileSystemSpec; + } + } + + options.folder = fs2us(tempDirNorm); + options.showErrorMessages = true; + + const HRESULT result = CopyTo(options, indices, &messages, usePassword, password); + + if (!_parentFolders.IsEmpty()) + { + CFolderLink &fl = _parentFolders.Back(); + fl.UsePassword = usePassword; + fl.Password = password; + } + + if (!messages.IsEmpty()) + return; + if (result != S_OK) + { + if (result != E_ABORT) + MessageBox_Error_HRESULT(result); + return; + } + + if (virtFileSystemSpec && !virtFileSystemSpec->WasStreamFlushedToFS()) + { + int index_in_Files = virtFileSystemSpec->Index_of_MainExtractedFile_in_Files; + if (index_in_Files < 0) + { + if (virtFileSystemSpec->Files.Size() != 1) + { + MessageBox_Error_HRESULT(E_FAIL); + return; + } + // it's not expected case that index was not set, but we support that case + index_in_Files = 0; + } + { + const CVirtFile &file = virtFileSystemSpec->Files[index_in_Files]; + CMyComPtr2_Create bufInStream; + bufInStream->Init(file.Data, file.WrittenSize, virtFileSystem); + const HRESULT res = OpenAsArc_Msg(bufInStream, tempFileInfo, + fullVirtPath, type ? type : L"" + // , encrypted + // , true // showErrorMessage + ); + if (res == S_OK) + { + if (virtFileSystemSpec->Index_of_ZoneBuf_AltStream_in_Files >= 0 + && !_parentFolders.IsEmpty()) + { + const CVirtFile &fileZone = virtFileSystemSpec->Files[ + virtFileSystemSpec->Index_of_ZoneBuf_AltStream_in_Files]; + _parentFolders.Back().ZoneBuf.CopyFrom(fileZone.Data, fileZone.WrittenSize); + } + + tempDirectory.DisableDeleting(); + RefreshListCtrl(); + return; + } + if (res == E_ABORT || res != S_FALSE) + return; + if (!tryExternal) + return; + tryAsArchive = false; + if (virtFileSystemSpec->FlushToDisk(true) != S_OK) + return; + } + } + + + /* + #if defined(_WIN32) && !defined(UNDER_CE) + if (zoneBuf.Size() != 0) + { + if (NFind::DoesFileExist_Raw(tempFilePath)) + { + WriteZoneFile(tempFilePath + k_ZoneId_StreamName, zoneBuf); + } + } + #endif + */ + + + if (tryAsArchive) + { + const HRESULT res = OpenAsArc_Msg(NULL, tempFileInfo, fullVirtPath, type ? type : L"" + // , encrypted + // , true // showErrorMessage + ); + if (res == S_OK) + { + tempDirectory.DisableDeleting(); + RefreshListCtrl(); + return; + } + if (res == E_ABORT || res != S_FALSE) + return; + } + + if (!tryExternal) + return; + + CMyUniquePtr tpi(new CTmpProcessInfo()); + tpi->FolderPath = tempDir; + tpi->FilePath = tempFilePath; + tpi->NeedDelete = true; + tpi->UsePassword = usePassword; + tpi->Password = password; + tpi->ReadOnly = IsThereReadOnlyFolder(); + if (IsHashFolder()) + tpi->ReadOnly = true; + + if (!tpi->FileInfo.Find(tempFilePath)) + return; + + CTmpProcessInfoRelease tmpProcessInfoRelease(*tpi); + + CProcess process; + HRESULT res; + if (editMode) + res = StartEditApplication(fs2us(tempFilePath), useEditor, + // (HWND)*this, + GetParent(), + process); + else + res = StartApplication(fs2us(tempDirNorm), fs2us(tempFilePath), + // (HWND)*this, + GetParent(), + process); + + if ((HANDLE)process == NULL) + { + // win7 / win10 work so for some extensions (pdf, html ..); + DEBUG_PRINT("#### (HANDLE)process == 0"); + // return; + if (res != S_OK) + return; + } + + tpi->Window = (HWND)*this; + tpi->FullPathFolderPrefix = _currentFolderPrefix; + tpi->FileIndex = index; + tpi->RelPath = relPath; + + if ((HANDLE)process) + tpi->Processes.SetMainProcess(process.Detach()); + + ::CThread th; + if (Thread_Create(&th, MyThreadFunction, tpi.get()) != 0) + throw 271824; + g_ExitEventLauncher._threads.Add(th); + g_ExitEventLauncher._numActiveThreads++; + + tempDirectory.DisableDeleting(); + tpi.release(); + tmpProcessInfoRelease._needDelete = false; +} + + +/* +static const UINT64 kTimeLimit = UINT64(10000000) * 3600 * 24; + +static bool CheckDeleteItem(UINT64 currentFileTime, UINT64 folderFileTime) +{ + return (currentFileTime - folderFileTime > kTimeLimit && + folderFileTime - currentFileTime > kTimeLimit); +} + +void DeleteOldTempFiles() +{ + UString tempPath; + if (!MyGetTempPath(tempPath)) + throw 1; + + UINT64 currentFileTime; + NTime::GetCurUtcFileTime(currentFileTime); + UString searchWildCard = tempPath + kTempDirPrefix + L"*.tmp"; + searchWildCard += WCHAR(NName::kAnyStringWildcard); + NFind::CEnumeratorW enumerator(searchWildCard); + NFind::CFileInfo fileInfo; + while (enumerator.Next(fileInfo)) + { + if (!fileInfo.IsDir()) + continue; + const UINT64 &cTime = *(const UINT64 *)(&fileInfo.CTime); + if (CheckDeleteItem(cTime, currentFileTime)) + RemoveDirectoryWithSubItems(tempPath + fileInfo.Name); + } +} +*/ diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PanelItems.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PanelItems.cpp new file mode 100644 index 00000000..868ad227 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PanelItems.cpp @@ -0,0 +1,1453 @@ +// PanelItems.cpp + +#include "StdAfx.h" + +#include "../../../../C/Sort.h" + +#include "../../../Windows/FileName.h" +#include "../../../Windows/Menu.h" +#include "../../../Windows/PropVariant.h" +#include "../../../Windows/PropVariantConv.h" + +#include "../../PropID.h" + +#include "../Common/ExtractingFilePath.h" + +#include "resource.h" + +#include "LangUtils.h" +#include "Panel.h" +#include "PropertyName.h" +#include "RootFolder.h" + +using namespace NWindows; + +static bool GetColumnVisible(PROPID propID, bool isFsFolder) +{ + if (isFsFolder) + { + switch (propID) + { + case kpidATime: + case kpidChangeTime: + case kpidAttrib: + case kpidPackSize: + case kpidINode: + case kpidLinks: + case kpidNtReparse: + return false; + } + } + return true; +} + +static unsigned GetColumnWidth(PROPID propID, VARTYPE /* varType */) +{ + switch (propID) + { + case kpidName: return 160; + } + return 100; +} + +static int GetColumnAlign(PROPID propID, VARTYPE varType) +{ + switch (propID) + { + case kpidCTime: + case kpidATime: + case kpidMTime: + case kpidChangeTime: + return LVCFMT_LEFT; + } + + switch (varType) + { + case VT_UI1: + case VT_I2: + case VT_UI2: + case VT_I4: + case VT_INT: + case VT_UI4: + case VT_UINT: + case VT_I8: + case VT_UI8: + case VT_BOOL: + return LVCFMT_RIGHT; + + case VT_EMPTY: + case VT_I1: + case VT_FILETIME: + case VT_BSTR: + return LVCFMT_LEFT; + + default: + return LVCFMT_CENTER; + } +} + + +static int ItemProperty_Compare_NameFirst(void *const *a1, void *const *a2, void * /* param */) +{ + return (*(*((const CPropColumn *const *)a1))).Compare_NameFirst + (*(*((const CPropColumn *const *)a2))); +} + +HRESULT CPanel::InitColumns() +{ + SaveListViewInfo(); + + // DeleteListItems(); + _selectedStatusVector.Clear(); + + { + // ReadListViewInfo(); + const UString oldType = _typeIDString; + _typeIDString = GetFolderTypeID(); + // an empty _typeIDString is allowed. + + // we read registry only for new FolderTypeID + if (!_needSaveInfo || _typeIDString != oldType) + _listViewInfo.Read(_typeIDString); + + // folders with same FolderTypeID can have different columns + // so we still read columns for that case. + // if (_needSaveInfo && _typeIDString == oldType) return S_OK; + } + + // PROPID sortID; + /* + if (_listViewInfo.SortIndex >= 0) + sortID = _listViewInfo.Columns[_listViewInfo.SortIndex].PropID; + */ + // sortID = _listViewInfo.SortID; + + _ascending = _listViewInfo.Ascending; + + _columns.Clear(); + + const bool isFsFolder = IsFSFolder() || IsAltStreamsFolder(); + + { + UInt32 numProps; + _folder->GetNumberOfProperties(&numProps); + + for (UInt32 i = 0; i < numProps; i++) + { + CMyComBSTR name; + PROPID propID; + VARTYPE varType; + HRESULT res = _folder->GetPropertyInfo(i, &name, &propID, &varType); + + if (res != S_OK) + { + /* We can return ERROR, but in that case, other code will not be called, + and user can see empty window without error message. So we just ignore that field */ + continue; + } + if (propID == kpidIsDir) + continue; + CPropColumn prop; + prop.Type = varType; + prop.ID = propID; + prop.Name = GetNameOfProperty(propID, name); + prop.Order = -1; + prop.IsVisible = GetColumnVisible(propID, isFsFolder); + prop.Width = GetColumnWidth(propID, varType); + prop.IsRawProp = false; + _columns.Add(prop); + } + + /* + { + // debug column + CPropColumn prop; + prop.Type = VT_BSTR; + prop.ID = 2000; + prop.Name = "Debug"; + prop.Order = -1; + prop.IsVisible = true; + prop.Width = 300; + prop.IsRawProp = false; + _columns.Add(prop); + } + */ + } + + if (_folderRawProps) + { + UInt32 numProps; + _folderRawProps->GetNumRawProps(&numProps); + + for (UInt32 i = 0; i < numProps; i++) + { + CMyComBSTR name; + PROPID propID; + const HRESULT res = _folderRawProps->GetRawPropInfo(i, &name, &propID); + if (res != S_OK) + continue; + CPropColumn prop; + prop.Type = VT_EMPTY; + prop.ID = propID; + prop.Name = GetNameOfProperty(propID, name); + prop.Order = -1; + prop.IsVisible = GetColumnVisible(propID, isFsFolder); + prop.Width = GetColumnWidth(propID, VT_BSTR); + prop.IsRawProp = true; + _columns.Add(prop); + } + } + + unsigned order = 0; + unsigned i; + + for (i = 0; i < _listViewInfo.Columns.Size(); i++) + { + const CColumnInfo &columnInfo = _listViewInfo.Columns[i]; + const int index = _columns.FindItem_for_PropID(columnInfo.PropID); + if (index >= 0) + { + CPropColumn &item = _columns[index]; + if (item.Order >= 0) + continue; // we ignore duplicated items + bool isVisible = columnInfo.IsVisible; + // we enable kpidName, if it was disabled by some incorrect code + if (columnInfo.PropID == kpidName) + isVisible = true; + item.IsVisible = isVisible; + item.Width = columnInfo.Width; + if (isVisible) + item.Order = (int)(order++); + continue; + } + } + + for (i = 0; i < _columns.Size(); i++) + { + CPropColumn &item = _columns[i]; + if (item.IsVisible && item.Order < 0) + item.Order = (int)(order++); + } + + for (i = 0; i < _columns.Size(); i++) + { + CPropColumn &item = _columns[i]; + if (item.Order < 0) + item.Order = (int)(order++); + } + + CPropColumns newColumns; + + for (i = 0; i < _columns.Size(); i++) + { + const CPropColumn &prop = _columns[i]; + if (prop.IsVisible) + newColumns.Add(prop); + } + + + /* + _sortIndex = 0; + if (_listViewInfo.SortIndex >= 0) + { + int sortIndex = _columns.FindItem_for_PropID(sortID); + if (sortIndex >= 0) + _sortIndex = sortIndex; + } + */ + + if (_listViewInfo.IsLoaded) + _sortID = _listViewInfo.SortID; + else + { + _sortID = 0; + if (IsFSFolder() || IsAltStreamsFolder() || IsArcFolder()) + _sortID = kpidName; + } + + /* There are restrictions in ListView control: + 1) main column (kpidName) must have (LV_COLUMNW::iSubItem = 0) + So we need special sorting for columns. + 2) when we add new column, LV_COLUMNW::iOrder cannot be larger than already inserted columns) + So we set column order after all columns are added. + */ + newColumns.Sort(ItemProperty_Compare_NameFirst, NULL); + + if (newColumns.IsEqualTo(_visibleColumns)) + return S_OK; + + CIntArr columns(newColumns.Size()); + for (i = 0; i < newColumns.Size(); i++) + columns[i] = -1; + + bool orderError = false; + + for (i = 0; i < newColumns.Size(); i++) + { + const CPropColumn &prop = newColumns[i]; + if (prop.Order < (int)newColumns.Size() && columns[prop.Order] == -1) + columns[prop.Order] = (int)i; + else + orderError = true; + } + + for (;;) + { + const unsigned numColumns = _visibleColumns.Size(); + if (numColumns == 0) + break; + DeleteColumn(numColumns - 1); + } + + for (i = 0; i < newColumns.Size(); i++) + AddColumn(newColumns[i]); + + // columns[0], columns[1], .... should be displayed from left to right: + if (!orderError) + _listView.SetColumnOrderArray(_visibleColumns.Size(), columns); + + _needSaveInfo = true; + + return S_OK; +} + + +void CPanel::DeleteColumn(unsigned index) +{ + _visibleColumns.Delete(index); + _listView.DeleteColumn(index); +} + +void CPanel::AddColumn(const CPropColumn &prop) +{ + const unsigned index = _visibleColumns.Size(); + + LV_COLUMNW column; + column.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM | LVCF_ORDER; + column.cx = (int)prop.Width; + column.fmt = GetColumnAlign(prop.ID, prop.Type); + column.iOrder = (int)index; // must be <= _listView.ItemCount + column.iSubItem = (int)index; // must be <= _listView.ItemCount + column.pszText = const_cast((const wchar_t *)prop.Name); + + _visibleColumns.Add(prop); + _listView.InsertColumn(index, &column); +} + + +HRESULT CPanel::RefreshListCtrl() +{ + CSelectedState state; + return RefreshListCtrl(state); +} + +int CALLBACK CompareItems(LPARAM lParam1, LPARAM lParam2, LPARAM lpData); + + +void CPanel::GetSelectedNames(UStringVector &selectedNames) +{ + CRecordVector indices; + Get_ItemIndices_Selected(indices); + selectedNames.ClearAndReserve(indices.Size()); + FOR_VECTOR (i, indices) + selectedNames.AddInReserved(GetItemRelPath(indices[i])); + + /* + for (int i = 0; i < _listView.GetItemCount(); i++) + { + const int kSize = 1024; + WCHAR name[kSize + 1]; + LVITEMW item; + item.iItem = i; + item.pszText = name; + item.cchTextMax = kSize; + item.iSubItem = 0; + item.mask = LVIF_TEXT | LVIF_PARAM; + if (!_listView.GetItem(&item)) + continue; + const unsigned realIndex = GetRealIndex(item); + if (realIndex == kParentIndex) + continue; + if (_selectedStatusVector[realIndex]) + selectedNames.Add(item.pszText); + } + */ + selectedNames.Sort(); +} + +void CPanel::SaveSelectedState(CSelectedState &s) +{ + s.FocusedName_Defined = false; + s.FocusedName.Empty(); + s.SelectFocused = true; // false; + s.SelectedNames.Clear(); + s.FocusedItem = _listView.GetFocusedItem(); + { + if (s.FocusedItem >= 0) + { + const unsigned realIndex = GetRealItemIndex(s.FocusedItem); + if (realIndex != kParentIndex) + { + s.FocusedName = GetItemRelPath(realIndex); + s.FocusedName_Defined = true; + + s.SelectFocused = _listView.IsItemSelected(s.FocusedItem); + + /* + const int kSize = 1024; + WCHAR name[kSize + 1]; + LVITEMW item; + item.iItem = focusedItem; + item.pszText = name; + item.cchTextMax = kSize; + item.iSubItem = 0; + item.mask = LVIF_TEXT; + if (_listView.GetItem(&item)) + focusedName = item.pszText; + */ + } + } + } + GetSelectedNames(s.SelectedNames); +} + +/* +HRESULT CPanel::RefreshListCtrl(const CSelectedState &s) +{ + bool selectFocused = s.SelectFocused; + if (_mySelectMode) + selectFocused = true; + return RefreshListCtrl2( + s.FocusedItem >= 0, // allowEmptyFocusedName + s.FocusedName, s.FocusedItem, selectFocused, s.SelectedNames); +} +*/ + +HRESULT CPanel::RefreshListCtrl_SaveFocused(bool onTimer) +{ + CSelectedState state; + SaveSelectedState(state); + state.CalledFromTimer = onTimer; + return RefreshListCtrl(state); +} + +void CPanel::SetFocusedSelectedItem(int index, bool select) +{ + UINT state = LVIS_FOCUSED; + if (select) + state |= LVIS_SELECTED; + _listView.SetItemState(index, state, state); + if (!_mySelectMode && select) + { + const unsigned realIndex = GetRealItemIndex(index); + if (realIndex != kParentIndex) + _selectedStatusVector[realIndex] = true; + } +} + +// #define PRINT_STAT + +#ifdef PRINT_STAT + void Print_OnNotify(const char *name); +#else + #define Print_OnNotify(x) +#endif + + + +/* + +extern UInt32 g_NumGroups; +extern DWORD g_start_tick; +extern DWORD g_prev_tick; +extern DWORD g_Num_SetItemText; +extern UInt32 g_NumMessages; +*/ + +HRESULT CPanel::RefreshListCtrl(const CSelectedState &state) +{ + m_DropHighlighted_SelectionIndex = -1; + m_DropHighlighted_SubFolderName.Empty(); + + if (!_folder) + return S_OK; + + /* + g_start_tick = GetTickCount(); + g_Num_SetItemText = 0; + g_NumMessages = 0; + */ + + _dontShowMode = false; + if (!state.CalledFromTimer) + LoadFullPathAndShow(); + // OutputDebugStringA("=======\n"); + // OutputDebugStringA("s1 \n"); + CDisableTimerProcessing timerProcessing(*this); + CDisableNotify disableNotify(*this); + + int focusedPos = state.FocusedItem; + if (focusedPos < 0) + focusedPos = 0; + + _listView.SetRedraw(false); + // m_RedrawEnabled = false; + + LVITEMW item; + ZeroMemory(&item, sizeof(item)); + + // DWORD tickCount0 = GetTickCount(); + + // _enableItemChangeNotify = false; + DeleteListItems(); + _enableItemChangeNotify = true; + + int listViewItemCount = 0; + + _selectedStatusVector.Clear(); + // _realIndices.Clear(); + _startGroupSelect = 0; + + _selectionIsDefined = false; + + // m_Files.Clear(); + + /* + if (!_folder) + { + // throw 1; + SetToRootFolder(); + } + */ + + _headerToolBar.EnableButton(kParentFolderID, !IsRootFolder()); + + { + CMyComPtr folderSetFlatMode; + _folder.QueryInterface(IID_IFolderSetFlatMode, &folderSetFlatMode); + if (folderSetFlatMode) + folderSetFlatMode->SetFlatMode(BoolToInt(_flatMode)); + } + + /* + { + CMyComPtr setShow; + _folder.QueryInterface(IID_IFolderSetShowNtfsStreamsMode, &setShow); + if (setShow) + setShow->SetShowNtfsStreamsMode(BoolToInt(_showNtfsStrems_Mode)); + } + */ + + _isDirVector.Clear(); + // DWORD tickCount1 = GetTickCount(); + IFolderFolder *folder = _folder; + RINOK(_folder->LoadItems()) + // DWORD tickCount2 = GetTickCount(); + // OutputDebugString(TEXT("Start Dir\n")); + RINOK(InitColumns()) + + UInt32 numItems; + _folder->GetNumberOfItems(&numItems); + { + NCOM::CPropVariant prop; + _isDirVector.ClearAndSetSize(numItems); + bool *vec = _isDirVector.NonConstData(); + HRESULT hres = S_OK; + unsigned i; + for (i = 0; i < numItems; i++) + { + hres = folder->GetProperty(i, kpidIsDir, &prop); + if (hres != S_OK) + break; + bool v = false; + if (prop.vt == VT_BOOL) + v = VARIANT_BOOLToBool(prop.boolVal); + else if (prop.vt != VT_EMPTY) + break; + vec[i] = v; + } + if (i != numItems) + { + _isDirVector.Clear(); + if (hres == S_OK) + hres = E_FAIL; + } + RINOK(hres) + } + + const bool showDots = _showDots && !IsRootFolder(); + + _listView.SetItemCount(numItems + (showDots ? 1 : 0)); + + _selectedStatusVector.ClearAndReserve(numItems); + int cursorIndex = -1; + + CMyComPtr folderGetSystemIconIndex; +#if 1 // 0 : for debug local icons loading + if (!Is_Slow_Icon_Folder() || _showRealFileIcons) + _folder.QueryInterface(IID_IFolderGetSystemIconIndex, &folderGetSystemIconIndex); +#endif + + const bool isFSDrivesFolder = IsFSDrivesFolder(); + const bool isArcFolder = IsArcFolder(); + + if (!IsFSFolder()) + { + CMyComPtr getFolderArcProps; + _folder.QueryInterface(IID_IGetFolderArcProps, &getFolderArcProps); + _thereAreDeletedItems = false; + if (getFolderArcProps) + { + CMyComPtr arcProps; + getFolderArcProps->GetFolderArcProps(&arcProps); + if (arcProps) + { + UInt32 numLevels; + if (arcProps->GetArcNumLevels(&numLevels) != S_OK) + numLevels = 0; + NCOM::CPropVariant prop; + if (arcProps->GetArcProp(numLevels - 1, kpidIsDeleted, &prop) == S_OK) + if (prop.vt == VT_BOOL && VARIANT_BOOLToBool(prop.boolVal)) + _thereAreDeletedItems = true; + } + } + } + + _thereAre_ListView_Items = true; + + // OutputDebugStringA("\n\n"); + + Print_OnNotify("===== Before Load"); + + // #define USE_EMBED_ITEM + + if (showDots) + { + const UString itemName (".."); + item.iItem = listViewItemCount; + if (itemName == state.FocusedName) + cursorIndex = listViewItemCount; + item.mask = LVIF_TEXT | LVIF_PARAM | LVIF_IMAGE; + int subItem = 0; + item.iSubItem = subItem++; + item.lParam = (LPARAM)(int)kParentIndex; + #ifdef USE_EMBED_ITEM + item.pszText = const_cast((const wchar_t *)itemName); + #else + item.pszText = LPSTR_TEXTCALLBACKW; + #endif + // const UInt32 attrib = FILE_ATTRIBUTE_DIRECTORY; + item.iImage = g_Ext_to_Icon_Map.GetIconIndex_DIR(); + // g_Ext_to_Icon_Map.GetIconIndex(attrib, itemName); + if (item.iImage < 0) + item.iImage = 0; + if (_listView.InsertItem(&item) == -1) + return E_FAIL; + listViewItemCount++; + } + + // OutputDebugStringA("S1\n"); + + UString correctedName; + UString itemName; + UString relPath; + + for (UInt32 i = 0; i < numItems; i++) + { + const wchar_t *name = NULL; + unsigned nameLen = 0; + + if (_folderGetItemName) + _folderGetItemName->GetItemName(i, &name, &nameLen); + if (!name) + { + GetItemName(i, itemName); + name = itemName; + nameLen = itemName.Len(); + } + + bool selected = false; + + if (state.FocusedName_Defined || !state.SelectedNames.IsEmpty()) + { + relPath.Empty(); + // relPath += GetItemPrefix(i); + if (_flatMode) + { + const wchar_t *prefix = NULL; + if (_folderGetItemName) + { + unsigned prefixLen = 0; + _folderGetItemName->GetItemPrefix(i, &prefix, &prefixLen); + if (prefix) + relPath = prefix; + } + if (!prefix) + { + NCOM::CPropVariant prop; + if (_folder->GetProperty(i, kpidPrefix, &prop) != S_OK) + throw 2723400; + if (prop.vt == VT_BSTR) + relPath.SetFromBstr(prop.bstrVal); + } + } + relPath += name; + if (relPath == state.FocusedName) + cursorIndex = listViewItemCount; + if (state.SelectedNames.FindInSorted(relPath) != -1) + selected = true; + } + + _selectedStatusVector.AddInReserved(selected); + + item.mask = LVIF_TEXT | LVIF_PARAM | LVIF_IMAGE; + + if (!_mySelectMode) + if (selected) + { + item.mask |= LVIF_STATE; + item.state = LVIS_SELECTED; + } + + int subItem = 0; + item.iItem = listViewItemCount; + + item.iSubItem = subItem++; + item.lParam = (LPARAM)i; + + /* + int finish = nameLen - 4; + int j; + for (j = 0; j < finish; j++) + { + if (name[j ] == ' ' && + name[j + 1] == ' ' && + name[j + 2] == ' ' && + name[j + 3] == ' ' && + name[j + 4] == ' ') + break; + } + if (j < finish) + { + correctedName.Empty(); + correctedName = "virus"; + int pos = 0; + for (;;) + { + int posNew = itemName.Find(L" ", pos); + if (posNew < 0) + { + correctedName += itemName.Ptr(pos); + break; + } + correctedName += itemName.Mid(pos, posNew - pos); + correctedName += " ... "; + pos = posNew; + while (itemName[++pos] == ' '); + } + item.pszText = const_cast((const wchar_t *)correctedName); + } + else + */ + { + #ifdef USE_EMBED_ITEM + item.pszText = const_cast((const wchar_t *)name); + #else + item.pszText = LPSTR_TEXTCALLBACKW; + #endif + /* LPSTR_TEXTCALLBACKW works, but in some cases there are problems, + since we block notify handler. + LPSTR_TEXTCALLBACKW can be 2-3 times faster for loading in this loop. */ + } + + bool defined = false; + item.iImage = -1; + + if (folderGetSystemIconIndex) + { + const HRESULT res = folderGetSystemIconIndex->GetSystemIconIndex(i, &item.iImage); + if (res == S_OK) + { + // item.iImage = -1; // for debug + defined = (item.iImage > 0); +#if 0 // 0: can be slower: 2 attempts for some paths. + // 1: faster, but we can get default icon for some cases (where non default icon is possible) + + if (item.iImage == 0) + { + // (item.iImage == 0) means default icon. + // But (item.iImage == 0) also can be returned for exe/ico files, + // if filePath is LONG PATH (path_len() >= MAX_PATH). + // Also we want to show split icon (.001) for any split extension: 001 002 003. + // Are there another cases for (item.iImage == 0) for files with known extensions? + // We don't want to do second attempt to request icon, + // if it also will return (item.iImage == 0). + + int dotPos = -1; + for (unsigned k = 0;; k++) + { + const wchar_t c = name[k]; + if (c == 0) + break; + if (c == '.') + dotPos = (int)i; + // we don't need IS_PATH_SEPAR check, because we have only (fileName) doesn't include path prefix. + // if (IS_PATH_SEPAR(c) || c == ':') dotPos = -1; + } + defined = true; + if (dotPos >= 0) + { +#if 0 + const wchar_t *ext = name + dotPos; + if (StringsAreEqualNoCase_Ascii(ext, ".exe") || + StringsAreEqualNoCase_Ascii(ext, ".ico")) +#endif + defined = false; + } + } +#endif + } + } + + if (!defined) + { + UInt32 attrib = 0; + { + NCOM::CPropVariant prop; + RINOK(_folder->GetProperty(i, kpidAttrib, &prop)) + if (prop.vt == VT_UI4) + { + attrib = prop.ulVal; + if (isArcFolder) + { + // if attrib (high 16-bits) is supposed from posix, + // we keep only low bits (basic Windows attrib flags): + if (attrib & 0xF0000000) + attrib &= 0x3FFF; + } + } + } + if (IsItem_Folder(i)) + attrib |= FILE_ATTRIBUTE_DIRECTORY; + else + attrib &= ~(UInt32)FILE_ATTRIBUTE_DIRECTORY; + + item.iImage = -1; + if (isFSDrivesFolder) + { + FString fs (us2fs((UString)name)); + fs.Add_PathSepar(); + item.iImage = Shell_GetFileInfo_SysIconIndex_for_Path(fs, attrib); + // item.iImage = 0; // for debug + } + if (item.iImage < 0) // <= 0 check? + item.iImage = g_Ext_to_Icon_Map.GetIconIndex(attrib, name); + } + + // item.iImage = -1; // for debug + if (item.iImage < 0) + item.iImage = 0; // default image + if (_listView.InsertItem(&item) == -1) + return E_FAIL; + listViewItemCount++; + } + + /* + xp-64: there is different order when Windows calls CPanel::OnNotify for _listView modes: + Details : after whole code + List : 2 times: + 1) - ListView.SotRedraw() + 2) - after whole code + Small Icons : + Large icons : 2 times: + 1) - ListView.Sort() + 2) - after whole code (calls with reverse order of items) + + So we need to allow Notify(), when windows requests names during the following code. + */ + + Print_OnNotify("after Load"); + + disableNotify.SetMemMode_Enable(); + disableNotify.Restore(); + + if (_listView.GetItemCount() > 0 && cursorIndex >= 0) + SetFocusedSelectedItem(cursorIndex, state.SelectFocused); + + Print_OnNotify("after SetFocusedSelectedItem"); + + SetSortRawStatus(); + _listView.SortItems(CompareItems, (LPARAM)this); + + Print_OnNotify("after Sort"); + + if (cursorIndex < 0 && _listView.GetItemCount() > 0) + { + if (focusedPos >= _listView.GetItemCount()) + focusedPos = _listView.GetItemCount() - 1; + // we select item only in showDots mode. + SetFocusedSelectedItem(focusedPos, showDots && (focusedPos == 0)); + } + + // m_RedrawEnabled = true; + + Print_OnNotify("after SetFocusedSelectedItem2"); + + _listView.EnsureVisible(_listView.GetFocusedItem(), false); + + // disableNotify.SetMemMode_Enable(); + // disableNotify.Restore(); + + Print_OnNotify("after EnsureVisible"); + + _listView.SetRedraw(true); + + Print_OnNotify("after SetRedraw"); + + _listView.InvalidateRect(NULL, true); + + Print_OnNotify("after InvalidateRect"); + /* + _listView.UpdateWindow(); + */ + Refresh_StatusBar(); + /* + char s[256]; + sprintf(s, + // "attribMap = %5d, extMap = %5d, " + "delete = %5d, load = %5d, list = %5d, sort = %5d, end = %5d", + // g_Ext_to_Icon_Map._attribMap.Size(), + // g_Ext_to_Icon_Map._extMap.Size(), + tickCount1 - tickCount0, + tickCount2 - tickCount1, + tickCount3 - tickCount2, + tickCount4 - tickCount3, + tickCount5 - tickCount4 + ); + sprintf(s, + "5 = %5d, 6 = %5d, 7 = %5d, 8 = %5d, 9 = %5d", + tickCount5 - tickCount4, + tickCount6 - tickCount5, + tickCount7 - tickCount6, + tickCount8 - tickCount7, + tickCount9 - tickCount8 + ); + OutputDebugStringA(s); + */ + return S_OK; +} + + +void CPanel::Get_ItemIndices_Selected(CRecordVector &indices) const +{ + indices.Clear(); + /* + int itemIndex = -1; + while ((itemIndex = _listView.GetNextSelectedItem(itemIndex)) != -1) + { + LPARAM param; + if (_listView.GetItemParam(itemIndex, param)) + indices.Add(param); + } + HeapSort(&indices.Front(), indices.Size()); + */ + const bool *v = _selectedStatusVector.ConstData(); + const unsigned size = _selectedStatusVector.Size(); + for (unsigned i = 0; i < size; i++) + if (v[i]) + indices.Add(i); +} + + +void CPanel::Get_ItemIndices_Operated(CRecordVector &indices) const +{ + Get_ItemIndices_Selected(indices); + if (!indices.IsEmpty()) + return; + if (_listView.GetSelectedCount() == 0) + return; + const int focusedItem = _listView.GetFocusedItem(); + if (focusedItem >= 0) + { + if (_listView.IsItemSelected(focusedItem)) + { + const unsigned realIndex = GetRealItemIndex(focusedItem); + if (realIndex != kParentIndex) + indices.Add(realIndex); + } + } +} + +void CPanel::Get_ItemIndices_All(CRecordVector &indices) const +{ + indices.Clear(); + UInt32 numItems; + if (_folder->GetNumberOfItems(&numItems) != S_OK) + return; + indices.ClearAndSetSize(numItems); + UInt32 *vec = indices.NonConstData(); + for (UInt32 i = 0; i < numItems; i++) + vec[i] = i; +} + +void CPanel::Get_ItemIndices_OperSmart(CRecordVector &indices) const +{ + Get_ItemIndices_Operated(indices); + if (indices.IsEmpty() || (indices.Size() == 1 && indices[0] == (UInt32)(Int32)-1)) + Get_ItemIndices_All(indices); +} + +/* +void CPanel::GetOperatedListViewIndices(CRecordVector &indices) const +{ + indices.Clear(); + int numItems = _listView.GetItemCount(); + for (int i = 0; i < numItems; i++) + { + const unsigned realIndex = GetRealItemIndex(i); + if (realIndex >= 0) + if (_selectedStatusVector[realIndex]) + indices.Add(i); + } + if (indices.IsEmpty()) + { + const int focusedItem = _listView.GetFocusedItem(); + if (focusedItem >= 0) + indices.Add(focusedItem); + } +} +*/ + +void CPanel::EditItem(bool useEditor) +{ + if (!useEditor) + { + CMyComPtr calcItemFullSize; + _folder.QueryInterface(IID_IFolderCalcItemFullSize, &calcItemFullSize); + if (calcItemFullSize) + { + bool needRefresh = false; + CRecordVector indices; + Get_ItemIndices_Operated(indices); + FOR_VECTOR (i, indices) + { + UInt32 index = indices[i]; + if (IsItem_Folder(index)) + { + calcItemFullSize->CalcItemFullSize(index, NULL); + needRefresh = true; + } + } + if (needRefresh) + { + // _listView.RedrawItem(0); + // _listView.RedrawAllItems(); + InvalidateList(); + return; + } + } + } + + + const int focusedItem = _listView.GetFocusedItem(); + if (focusedItem < 0) + return; + const unsigned realIndex = GetRealItemIndex(focusedItem); + if (realIndex == kParentIndex) + return; + if (!IsItem_Folder(realIndex)) + EditItem(realIndex, useEditor); +} + +void CPanel::OpenFocusedItemAsInternal(const wchar_t *type) +{ + const int focusedItem = _listView.GetFocusedItem(); + if (focusedItem < 0) + return; + const unsigned realIndex = GetRealItemIndex(focusedItem); + if (IsItem_Folder(realIndex)) + OpenFolder(realIndex); + else + OpenItem(realIndex, true, false, type); +} + +void CPanel::OpenSelectedItems(bool tryInternal) +{ + CRecordVector indices; + Get_ItemIndices_Operated(indices); + if (indices.Size() > 20) + { + MessageBox_Error_LangID(IDS_TOO_MANY_ITEMS); + return; + } + + const int focusedItem = _listView.GetFocusedItem(); + if (focusedItem >= 0) + { + const unsigned realIndex = GetRealItemIndex(focusedItem); + if (realIndex == kParentIndex && (tryInternal || indices.Size() == 0) && _listView.IsItemSelected(focusedItem)) + indices.Insert(0, realIndex); + } + + bool dirIsStarted = false; + FOR_VECTOR (i, indices) + { + UInt32 index = indices[i]; + // CFileInfo &aFile = m_Files[index]; + if (IsItem_Folder(index)) + { + if (!dirIsStarted) + { + if (tryInternal) + { + OpenFolder(index); + dirIsStarted = true; + break; + } + else + OpenFolderExternal(index); + } + } + else + OpenItem(index, (tryInternal && indices.Size() == 1), true); + } +} + +UString CPanel::GetItemName(unsigned itemIndex) const +{ + if (itemIndex == kParentIndex) + return L".."; + NCOM::CPropVariant prop; + if (_folder->GetProperty(itemIndex, kpidName, &prop) != S_OK) + throw 2723400; + if (prop.vt != VT_BSTR) + throw 2723401; + return prop.bstrVal; +} + +UString CPanel::GetItemName_for_Copy(unsigned itemIndex) const +{ + if (itemIndex == kParentIndex) + return L".."; + UString s; + { + NCOM::CPropVariant prop; + if (_folder->GetProperty(itemIndex, kpidOutName, &prop) == S_OK) + { + if (prop.vt == VT_BSTR) + s = prop.bstrVal; + else if (prop.vt != VT_EMPTY) + throw 2723401; + } + if (s.IsEmpty()) + s = GetItemName(itemIndex); + } + return Get_Correct_FsFile_Name(s); +} + +void CPanel::GetItemName(unsigned itemIndex, UString &s) const +{ + if (itemIndex == kParentIndex) + { + s = ".."; + return; + } + NCOM::CPropVariant prop; + if (_folder->GetProperty(itemIndex, kpidName, &prop) != S_OK) + throw 2723400; + if (prop.vt != VT_BSTR) + throw 2723401; + s.SetFromBstr(prop.bstrVal); +} + +UString CPanel::GetItemPrefix(unsigned itemIndex) const +{ + if (itemIndex == kParentIndex) + return UString(); + NCOM::CPropVariant prop; + if (_folder->GetProperty(itemIndex, kpidPrefix, &prop) != S_OK) + throw 2723400; + UString prefix; + if (prop.vt == VT_BSTR) + prefix.SetFromBstr(prop.bstrVal); + return prefix; +} + +UString CPanel::GetItemRelPath(unsigned itemIndex) const +{ + return GetItemPrefix(itemIndex) + GetItemName(itemIndex); +} + +UString CPanel::GetItemRelPath2(unsigned itemIndex) const +{ + UString s = GetItemRelPath(itemIndex); + #if defined(_WIN32) && !defined(UNDER_CE) + if (s.Len() == 2 && NFile::NName::IsDrivePath2(s)) + { + if (IsFSDrivesFolder() && !IsDeviceDrivesPrefix()) + s.Add_PathSepar(); + } + #endif + return s; +} + + +void CPanel::Add_ItemRelPath2_To_String(unsigned itemIndex, UString &s) const +{ + if (itemIndex == kParentIndex) + { + s += ".."; + return; + } + + const unsigned start = s.Len(); + NCOM::CPropVariant prop; + if (_folder->GetProperty(itemIndex, kpidPrefix, &prop) != S_OK) + throw 2723400; + if (prop.vt == VT_BSTR) + s += prop.bstrVal; + + const wchar_t *name = NULL; + unsigned nameLen = 0; + + if (_folderGetItemName) + _folderGetItemName->GetItemName(itemIndex, &name, &nameLen); + if (name) + s += name; + else + { + prop.Clear(); + if (_folder->GetProperty(itemIndex, kpidName, &prop) != S_OK) + throw 2723400; + if (prop.vt != VT_BSTR) + throw 2723401; + s += prop.bstrVal; + } + + #if defined(_WIN32) && !defined(UNDER_CE) + if (s.Len() - start == 2 && NFile::NName::IsDrivePath2(s.Ptr(start))) + { + if (IsFSDrivesFolder() && !IsDeviceDrivesPrefix()) + s.Add_PathSepar(); + } + #endif +} + + +UString CPanel::GetItemFullPath(unsigned itemIndex) const +{ + return GetFsPath() + GetItemRelPath2(itemIndex); +} + +bool CPanel::GetItem_BoolProp(UInt32 itemIndex, PROPID propID) const +{ + NCOM::CPropVariant prop; + if (_folder->GetProperty(itemIndex, propID, &prop) != S_OK) + throw 2723400; + if (prop.vt == VT_BOOL) + return VARIANT_BOOLToBool(prop.boolVal); + if (prop.vt == VT_EMPTY) + return false; + throw 2723401; +} + +bool CPanel::IsItem_Deleted(unsigned itemIndex) const +{ + if (itemIndex == kParentIndex) + return false; + return GetItem_BoolProp(itemIndex, kpidIsDeleted); +} + +bool CPanel::IsItem_Folder(unsigned itemIndex) const +{ + if (itemIndex == kParentIndex) + return true; + if (itemIndex < _isDirVector.Size()) + return _isDirVector[itemIndex]; + return GetItem_BoolProp(itemIndex, kpidIsDir); +} + +bool CPanel::IsItem_AltStream(unsigned itemIndex) const +{ + if (itemIndex == kParentIndex) + return false; + return GetItem_BoolProp(itemIndex, kpidIsAltStream); +} + +UInt64 CPanel::GetItem_UInt64Prop(unsigned itemIndex, PROPID propID) const +{ + if (itemIndex == kParentIndex) + return 0; + NCOM::CPropVariant prop; + if (_folder->GetProperty(itemIndex, propID, &prop) != S_OK) + throw 2723400; + UInt64 val = 0; + if (ConvertPropVariantToUInt64(prop, val)) + return val; + return 0; +} + +UInt64 CPanel::GetItemSize(unsigned itemIndex) const +{ + if (itemIndex == kParentIndex) + return 0; + if (_folderGetItemName) + return _folderGetItemName->GetItemSize(itemIndex); + return GetItem_UInt64Prop(itemIndex, kpidSize); +} + +void CPanel::SaveListViewInfo() +{ + if (!_needSaveInfo) + return; + + unsigned i; + + for (i = 0; i < _visibleColumns.Size(); i++) + { + CPropColumn &prop = _visibleColumns[i]; + LVCOLUMN winColumnInfo; + winColumnInfo.mask = LVCF_ORDER | LVCF_WIDTH; + if (!_listView.GetColumn(i, &winColumnInfo)) + throw 1; + prop.Order = winColumnInfo.iOrder; + prop.Width = (UInt32)(Int32)winColumnInfo.cx; + } + + CListViewInfo viewInfo; + + // PROPID sortPropID = _columns[_sortIndex].ID; + const PROPID sortPropID = _sortID; + + // we save columns as "sorted by order" to registry + + CPropColumns sortedProperties = _visibleColumns; + + sortedProperties.Sort(); + + for (i = 0; i < sortedProperties.Size(); i++) + { + const CPropColumn &prop = sortedProperties[i]; + CColumnInfo columnInfo; + columnInfo.IsVisible = prop.IsVisible; + columnInfo.PropID = prop.ID; + columnInfo.Width = prop.Width; + viewInfo.Columns.Add(columnInfo); + } + + for (i = 0; i < _columns.Size(); i++) + { + const CPropColumn &prop = _columns[i]; + if (sortedProperties.FindItem_for_PropID(prop.ID) < 0) + { + CColumnInfo columnInfo; + columnInfo.IsVisible = false; + columnInfo.PropID = prop.ID; + columnInfo.Width = prop.Width; + viewInfo.Columns.Add(columnInfo); + } + } + + viewInfo.SortID = sortPropID; + viewInfo.Ascending = _ascending; + viewInfo.IsLoaded = true; + if (!_listViewInfo.IsEqual(viewInfo)) + { + viewInfo.Save(_typeIDString); + _listViewInfo = viewInfo; + } +} + + +bool CPanel::OnRightClick(MY_NMLISTVIEW_NMITEMACTIVATE *itemActivate, LRESULT &result) +{ + if (itemActivate->hdr.hwndFrom == HWND(_listView)) + return false; + POINT point; + ::GetCursorPos(&point); + ShowColumnsContextMenu(point.x, point.y); + result = TRUE; + return true; +} + +void CPanel::ShowColumnsContextMenu(int x, int y) +{ + CMenu menu; + CMenuDestroyer menuDestroyer(menu); + + menu.CreatePopup(); + + const int kCommandStart = 100; + FOR_VECTOR (i, _columns) + { + const CPropColumn &prop = _columns[i]; + UINT flags = MF_STRING; + if (prop.IsVisible) + flags |= MF_CHECKED; + if (i == 0) + flags |= MF_GRAYED; + menu.AppendItem(flags, kCommandStart + i, prop.Name); + } + + const int menuResult = menu.Track(TPM_LEFTALIGN | TPM_RETURNCMD | TPM_NONOTIFY, x, y, _listView); + + if (menuResult >= kCommandStart && menuResult <= kCommandStart + (int)_columns.Size()) + { + const unsigned index = (unsigned)(menuResult - kCommandStart); + CPropColumn &prop = _columns[index]; + prop.IsVisible = !prop.IsVisible; + + if (prop.IsVisible) + { + prop.Order = (int)_visibleColumns.Size(); + AddColumn(prop); + } + else + { + const int visibleIndex = _visibleColumns.FindItem_for_PropID(prop.ID); + if (visibleIndex >= 0) + { + /* + if (_sortIndex == index) + { + _sortIndex = 0; + _ascending = true; + } + */ + if (_sortID == prop.ID) + { + _sortID = kpidName; + _ascending = true; + } + DeleteColumn((unsigned)visibleIndex); + } + } + } +} + +void CPanel::OnReload(bool onTimer) +{ + const HRESULT res = RefreshListCtrl_SaveFocused(onTimer); + if (res != S_OK) + MessageBox_Error_HRESULT(res); +} + +void CPanel::OnTimer() +{ + if (!_processTimer) + return; + if (!AutoRefresh_Mode) + return; + if (!_folder) // it's unexpected case, but we use it as additional protection. + return; + { + CMyComPtr folderWasChanged; + _folder.QueryInterface(IID_IFolderWasChanged, &folderWasChanged); + if (!folderWasChanged) + return; + Int32 wasChanged; + if (folderWasChanged->WasChanged(&wasChanged) != S_OK || wasChanged == 0) + return; + } + OnReload(true); // onTimer +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PanelKey.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PanelKey.cpp new file mode 100644 index 00000000..a4ea489a --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PanelKey.cpp @@ -0,0 +1,357 @@ +// PanelKey.cpp + +#include "StdAfx.h" + +#include "Panel.h" +#include "HelpUtils.h" + +#include "../../PropID.h" +#include "App.h" + +using namespace NWindows; + +// #define kHelpTopic "FM/index.htm" + +struct CVKeyPropIDPair +{ + WORD VKey; + PROPID PropID; +}; + +static const CVKeyPropIDPair g_VKeyPropIDPairs[] = +{ + { VK_F3, kpidName }, + { VK_F4, kpidExtension }, + { VK_F5, kpidMTime }, + { VK_F6, kpidSize }, + { VK_F7, kpidNoProperty } +}; + +static int FindVKeyPropIDPair(WORD vKey) +{ + for (unsigned i = 0; i < Z7_ARRAY_SIZE(g_VKeyPropIDPairs); i++) + if (g_VKeyPropIDPairs[i].VKey == vKey) + return (int)i; + return -1; +} + + +bool CPanel::OnKeyDown(LPNMLVKEYDOWN keyDownInfo, LRESULT &result) +{ + if (keyDownInfo->wVKey == VK_TAB && keyDownInfo->hdr.hwndFrom == _listView) + { + _panelCallback->OnTab(); + return false; + } + const bool alt = IsKeyDown(VK_MENU); + const bool ctrl = IsKeyDown(VK_CONTROL); + // const bool leftCtrl = IsKeyDown(VK_LCONTROL); + const bool rightCtrl = IsKeyDown(VK_RCONTROL); + const bool shift = IsKeyDown(VK_SHIFT); + result = 0; + + if (keyDownInfo->wVKey >= '0' && + keyDownInfo->wVKey <= '9' && + (rightCtrl || alt)) + { + const unsigned index = (unsigned)(keyDownInfo->wVKey - '0'); + if (shift) + { + SetBookmark(index); + return true; + } + else + { + OpenBookmark(index); + return true; + } + } + + if ((keyDownInfo->wVKey == VK_F2 || + keyDownInfo->wVKey == VK_F1) + && alt && !ctrl && !shift) + { + _panelCallback->SetFocusToPath(keyDownInfo->wVKey == VK_F1 ? 0 : 1); + return true; + } + + if ((keyDownInfo->wVKey == VK_F9) && !alt && !ctrl && !shift) + { + g_App.SwitchOnOffOnePanel(); + } + + if (keyDownInfo->wVKey >= VK_F3 && keyDownInfo->wVKey <= VK_F12 && ctrl) + { + const int index = FindVKeyPropIDPair(keyDownInfo->wVKey); + if (index >= 0) + SortItemsWithPropID(g_VKeyPropIDPairs[index].PropID); + } + + switch (keyDownInfo->wVKey) + { + case VK_SHIFT: + { + _selectionIsDefined = false; + _prevFocusedItem = _listView.GetFocusedItem(); + break; + } + /* + case VK_F1: + { + // ShowHelpWindow(NULL, kHelpTopic); + break; + } + */ + case VK_F2: + { + if (!alt && !ctrl &&!shift) + { + RenameFile(); + return true; + } + break; + } + case VK_F3: + { + if (!alt && !ctrl && !shift) + { + EditItem(false); + return true; + } + break; + } + case VK_F4: + { + if (!alt && !ctrl && !shift) + { + EditItem(true); + return true; + } + if (!alt && !ctrl && shift) + { + CreateFile(); + return true; + } + break; + } + case VK_F5: + { + if (!alt && !ctrl) + { + _panelCallback->OnCopy(false, shift); + return true; + } + break; + } + case VK_F6: + { + if (!alt && !ctrl) + { + _panelCallback->OnCopy(true, shift); + return true; + } + break; + } + case VK_F7: + { + if (!alt && !ctrl && !shift) + { + /* we can process F7 via menu ACCELERATOR. + But menu loading can be slow in case of UNC paths and system menu. + So we use don't use ACCELERATOR */ + CreateFolder(); + return true; + } + break; + } + case VK_DELETE: + { + DeleteItems(!shift); + return true; + } + case VK_INSERT: + { + if (!alt) + { + if (ctrl && !shift) + { + EditCopy(); + return true; + } + if (shift && !ctrl) + { + EditPaste(); + return true; + } + if (!shift && !ctrl && _mySelectMode) + { + OnInsert(); + return true; + } + } + return false; + } + case VK_DOWN: + { + if (shift) + OnArrowWithShift(); + return false; + } + case VK_UP: + { + if (alt) + _panelCallback->OnSetSameFolder(); + else if (shift) + OnArrowWithShift(); + return false; + } + case VK_RIGHT: + { + if (alt) + _panelCallback->OnSetSubFolder(); + else if (shift) + OnArrowWithShift(); + return false; + } + case VK_LEFT: + { + if (alt) + _panelCallback->OnSetSubFolder(); + else if (shift) + OnArrowWithShift(); + return false; + } + case VK_NEXT: + { + if (ctrl && !alt && !shift) + { + // EnterToFocused(); + return true; + } + break; + } + case VK_ADD: + { + if (alt) + SelectByType(true); + else if (shift) + SelectAll(true); + else if (!ctrl) + SelectSpec(true); + return true; + } + case VK_SUBTRACT: + { + if (alt) + SelectByType(false); + else if (shift) + SelectAll(false); + else + SelectSpec(false); + return true; + } + /* + case VK_DELETE: + CommandDelete(); + return 0; + case VK_F1: + CommandHelp(); + return 0; + */ + case VK_BACK: + OpenParentFolder(); + return true; + /* + case VK_DIVIDE: + case '\\': + case '/': + case VK_OEM_5: + { + // OpenRootFolder(); + OpenDrivesFolder(); + + return true; + } + */ + case 'A': + if (ctrl) + { + SelectAll(true); + return true; + } + return false; + case 'X': + if (ctrl) + { + EditCut(); + return true; + } + return false; + case 'C': + if (ctrl) + { + EditCopy(); + return true; + } + return false; + case 'V': + if (ctrl) + { + EditPaste(); + return true; + } + return false; + case 'N': + if (ctrl) + { + CreateFile(); + return true; + } + return false; + case 'R': + if (ctrl) + { + OnReload(); + return true; + } + return false; + case 'W': + if (ctrl) + { + // SendMessage(); + PostMessage(g_HWND, WM_COMMAND, IDCLOSE, 0); + return true; + } + return false; + case 'Z': + if (ctrl) + { + ChangeComment(); + return true; + } + return false; + case '1': + case '2': + case '3': + case '4': + if (ctrl) + { + const unsigned styleIndex = (unsigned)(keyDownInfo->wVKey - '1'); + SetListViewMode(styleIndex); + return true; + } + return false; + case VK_MULTIPLY: + { + InvertSelection(); + return true; + } + case VK_F12: + if (alt && !ctrl && !shift) + { + FoldersHistory(); + return true; + } + } + return false; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PanelListNotify.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PanelListNotify.cpp new file mode 100644 index 00000000..05ab36bc --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PanelListNotify.cpp @@ -0,0 +1,838 @@ +// PanelListNotify.cpp + +#include "StdAfx.h" + +#include "resource.h" + +#include "../../../Common/IntToString.h" +#include "../../../Common/StringConvert.h" + +#include "../../../Windows/PropVariant.h" +#include "../../../Windows/PropVariantConv.h" + +#include "../Common/PropIDUtils.h" +#include "../../PropID.h" + +#include "App.h" +#include "Panel.h" +#include "FormatUtils.h" + +using namespace NWindows; + +/* Unicode characters for space: +0x009C STRING TERMINATOR +0x00B7 Middle dot +0x237D Shouldered open box +0x2420 Symbol for space +0x2422 Blank symbol +0x2423 Open box +*/ + +#define SPACE_REPLACE_CHAR (wchar_t)(0x2423) +#define SPACE_TERMINATOR_CHAR (wchar_t)(0x9C) + +#define INT_TO_STR_SPEC(v) \ + while (v >= 10) { temp[i++] = (Byte)('0' + (unsigned)(v % 10)); v /= 10; } \ + *s++ = (Byte)('0' + (unsigned)v); + +static void ConvertSizeToString(UInt64 val, wchar_t *s) throw() +{ + Byte temp[32]; + unsigned i = 0; + + if (val <= (UInt32)0xFFFFFFFF) + { + UInt32 val32 = (UInt32)val; + INT_TO_STR_SPEC(val32) + } + else + { + INT_TO_STR_SPEC(val) + } + + if (i < 3) + { + if (i != 0) + { + *s++ = temp[(size_t)i - 1]; + if (i == 2) + *s++ = temp[0]; + } + *s = 0; + return; + } + + unsigned r = i % 3; + if (r != 0) + { + s[0] = temp[--i]; + if (r == 2) + s[1] = temp[--i]; + s += r; + } + + do + { + s[0] = ' '; + s[1] = temp[(size_t)i - 1]; + s[2] = temp[(size_t)i - 2]; + s[3] = temp[(size_t)i - 3]; + s += 4; + } + while (i -= 3); + + *s = 0; +} + +UString ConvertSizeToString(UInt64 value); +UString ConvertSizeToString(UInt64 value) +{ + wchar_t s[32]; + ConvertSizeToString(value, s); + return s; +} + +bool IsSizeProp(UINT propID) throw(); +bool IsSizeProp(UINT propID) throw() +{ + switch (propID) + { + case kpidSize: + case kpidPackSize: + case kpidNumSubDirs: + case kpidNumSubFiles: + case kpidOffset: + case kpidLinks: + case kpidNumBlocks: + case kpidNumVolumes: + case kpidPhySize: + case kpidHeadersSize: + case kpidTotalSize: + case kpidFreeSpace: + case kpidClusterSize: + case kpidNumErrors: + case kpidNumStreams: + case kpidNumAltStreams: + case kpidAltStreamsSize: + case kpidVirtualSize: + case kpidUnpackSize: + case kpidTotalPhySize: + case kpidTailSize: + case kpidEmbeddedStubSize: + return true; + } + return false; +} + + + +/* +#include + +UInt64 GetCpuTicks() +{ + #ifdef _WIN64 + return __rdtsc(); + #else + UInt32 lowVal, highVal; + __asm RDTSC; + __asm mov lowVal, EAX; + __asm mov highVal, EDX; + return ((UInt64)highVal << 32) | lowVal; + #endif +} + +UInt32 g_NumGroups; +UInt64 g_start_tick; +UInt64 g_prev_tick; +DWORD g_Num_SetItemText; +UInt32 g_NumMessages; +*/ + +LRESULT CPanel::SetItemText(LVITEMW &item) +{ + if (_dontShowMode) + return 0; + UInt32 realIndex = GetRealIndex(item); + + // g_Num_SetItemText++; + + /* + if ((item.mask & LVIF_IMAGE) != 0) + { + bool defined = false; + CComPtr folderGetSystemIconIndex; + _folder.QueryInterface(&folderGetSystemIconIndex); + if (folderGetSystemIconIndex) + { + folderGetSystemIconIndex->GetSystemIconIndex(index, &item.iImage); + defined = (item.iImage > 0); + } + if (!defined) + { + NCOM::CPropVariant prop; + _folder->GetProperty(index, kpidAttrib, &prop); + UINT32 attrib = 0; + if (prop.vt == VT_UI4) + attrib = prop.ulVal; + else if (IsItemFolder(index)) + attrib |= FILE_ATTRIBUTE_DIRECTORY; + if (_currentFolderPrefix.IsEmpty()) + throw 1; + else + item.iImage = _extToIconMap.GetIconIndex(attrib, GetSystemString(GetItemName(index))); + } + // item.iImage = 1; + } + */ + + if ((item.mask & LVIF_TEXT) == 0) + return 0; + + LPWSTR text = item.pszText; + + if (item.cchTextMax > 0) + text[0] = 0; + + if (item.cchTextMax <= 1) + return 0; + + // item.cchTextMax > 1 + + const CPropColumn &property = _visibleColumns[item.iSubItem]; + PROPID propID = property.ID; + + if (realIndex == kParentIndex_UInt32) + { + if (propID == kpidName) + { + if (item.cchTextMax > 2) + { + text[0] = '.'; + text[1] = '.'; + text[2] = 0; + } + } + return 0; + } + + /* + // List-view in report-view in Windows 10 is slow (50+ ms) for page change. + // that code shows the time of page reload for items + // if you know how to improve the speed of list view refresh, notify 7-Zip developer + + // if (propID == 2000) + // if (propID == kpidName) + { + // debug column; + // DWORD dw = GetCpuTicks(); + UInt64 dw = GetCpuTicks(); + UInt64 deltaLast = dw - g_prev_tick; + #define conv_ticks(t) ((unsigned)((t) / 100000)) + if (deltaLast > 1000u * 1000 * 1000) + { + UInt64 deltaFull = g_prev_tick - g_start_tick; + char s[128]; + sprintf(s, "%d", conv_ticks(deltaFull)); + OutputDebugStringA(s); + g_start_tick = dw; + g_NumGroups++; + } + g_prev_tick = dw; + UString u; + char s[128]; + UInt64 deltaFull = dw - g_start_tick; + // for (int i = 0; i < 100000; i++) + sprintf(s, "%d %d %d-%d ", g_NumMessages, g_Num_SetItemText, g_NumGroups, conv_ticks(deltaFull)); + // sprintf(s, "%d-%d ", g_NumGroups, conv_ticks(deltaFull)); + u = s; + lstrcpyW(text, u.Ptr()); + text += u.Len(); + + // dw = GetCpuTicks(); + // deltaFull = dw - g_prev_tick; + // sprintf(s, "-%d ", conv_ticks(deltaFull)); + // u = s; + // lstrcpyW(text, u.Ptr()); + // text += u.Len(); + + if (propID != kpidName) + return 0; + } + */ + + + if (property.IsRawProp) + { + const void *data; + UInt32 dataSize; + UInt32 propType; + RINOK(_folderRawProps->GetRawProp(realIndex, propID, &data, &dataSize, &propType)) + unsigned limit = (unsigned)item.cchTextMax - 1; + // limit != 0 + if (dataSize == 0) + return 0; + + if (propID == kpidNtReparse) + { + UString s; + ConvertNtReparseToString((const Byte *)data, dataSize, s); + if (!s.IsEmpty()) + { + unsigned i; + for (i = 0; i < limit; i++) + { + const wchar_t c = s[i]; + if (c == 0) + break; + text[i] = c; + } + text[i] = 0; + return 0; + } + } + else if (propID == kpidNtSecure) + { + AString s; + ConvertNtSecureToString((const Byte *)data, dataSize, s); + if (!s.IsEmpty()) + { + unsigned i; + for (i = 0; i < limit; i++) + { + const wchar_t c = (Byte)s[i]; + if (c == 0) + break; + text[i] = c; + } + text[i] = 0; + return 0; + } + } + { + const unsigned kMaxDataSize = 64; + if (dataSize > kMaxDataSize) + { + char temp[32]; + MyStringCopy(temp, "data:"); + ConvertUInt32ToString(dataSize, temp + 5); + unsigned i; + for (i = 0; i < limit; i++) + { + wchar_t c = (Byte)temp[i]; + if (c == 0) + break; + *text++ = c; + } + *text = 0; + } + else + { + const char * const k_Hex = + (dataSize <= 8 + && (propID == kpidCRC || propID == kpidChecksum)) + ? k_Hex_Upper : k_Hex_Lower; + limit /= 2; + if (limit > dataSize) + limit = dataSize; + const Byte *data2 = (const Byte *)data; + do + { + const size_t b = *data2++; + text[0] = (Byte)k_Hex[b >> 4]; + text[1] = (Byte)k_Hex[b & 15]; + text += 2; + } + while (--limit); + *text = 0; + } + } + return 0; + } + /* + { + NCOM::CPropVariant prop; + if (propID == kpidType) + string = GetFileType(index); + else + { + HRESULT result = m_ArchiveFolder->GetProperty(index, propID, &prop); + if (result != S_OK) + { + // PrintMessage("GetPropertyValue error"); + return 0; + } + string = ConvertPropertyToString(prop, propID, false); + } + } + */ + // const NFind::CFileInfo &aFileInfo = m_Files[index]; + + NCOM::CPropVariant prop; + /* + bool needRead = true; + if (propID == kpidSize) + { + CComPtr getItemFullSize; + if (_folder.QueryInterface(&getItemFullSize) == S_OK) + { + if (getItemFullSize->GetItemFullSize(index, &prop) == S_OK) + needRead = false; + } + } + if (needRead) + */ + + if (item.cchTextMax < 32) + return 0; + + if (propID == kpidName) + { + if (_folderGetItemName) + { + const wchar_t *name = NULL; + unsigned nameLen = 0; + _folderGetItemName->GetItemName(realIndex, &name, &nameLen); + + if (name) + { + unsigned dest = 0; + const unsigned limit = (unsigned)item.cchTextMax - 1; + + for (unsigned i = 0; dest < limit;) + { + const wchar_t c = name[i++]; + if (c == 0) + break; + text[dest++] = c; + + if (c != ' ') + { + if (c != 0x202E) // RLO + continue; + text[(size_t)dest - 1] = '_'; + continue; + } + + if (name[i] != ' ') + continue; + + unsigned t = 1; + for (; name[i + t] == ' '; t++); + + if (t >= 4 && dest + 4 < limit) + { + text[dest++] = '.'; + text[dest++] = '.'; + text[dest++] = '.'; + text[dest++] = ' '; + i += t; + } + } + + if (dest == 0) + text[dest++]= '_'; + + #ifdef _WIN32 + else if (text[(size_t)dest - 1] == ' ') + { + if (dest < limit) + text[dest++] = SPACE_TERMINATOR_CHAR; + else + text[dest - 1] = SPACE_REPLACE_CHAR; + } + #endif + + text[dest] = 0; + // OutputDebugStringW(text); + return 0; + } + } + } + + if (propID == kpidPrefix) + { + if (_folderGetItemName) + { + const wchar_t *name = NULL; + unsigned nameLen = 0; + _folderGetItemName->GetItemPrefix(realIndex, &name, &nameLen); + if (name) + { + unsigned dest = 0; + const unsigned limit = (unsigned)item.cchTextMax - 1; + for (unsigned i = 0; dest < limit;) + { + const wchar_t c = name[i++]; + if (c == 0) + break; + text[dest++] = c; + } + text[dest] = 0; + return 0; + } + } + } + + const HRESULT res = _folder->GetProperty(realIndex, propID, &prop); + + if (res != S_OK) + { + MyStringCopy(text, L"Error: "); + // s = UString("Error: ") + HResultToMessage(res); + } + else if ((prop.vt == VT_UI8 || prop.vt == VT_UI4 || prop.vt == VT_UI2) && IsSizeProp(propID)) + { + UInt64 v = 0; + ConvertPropVariantToUInt64(prop, v); + ConvertSizeToString(v, text); + } + else if (prop.vt == VT_BSTR) + { + const unsigned limit = (unsigned)item.cchTextMax - 1; + const wchar_t *src = prop.bstrVal; + unsigned i; + for (i = 0; i < limit; i++) + { + wchar_t c = src[i]; + if (c == 0) break; + if (c == 0xA) c = ' '; + if (c == 0xD) c = ' '; + text[i] = c; + } + text[i] = 0; + } + else + { + char temp[64]; + ConvertPropertyToShortString2(temp, prop, propID, _timestampLevel); + unsigned i; + const unsigned limit = (unsigned)item.cchTextMax - 1; + for (i = 0; i < limit; i++) + { + const wchar_t c = (Byte)temp[i]; + if (c == 0) + break; + text[i] = c; + } + text[i] = 0; + } + + return 0; +} + +void CPanel::OnItemChanged(NMLISTVIEW *item) +{ + const unsigned index = (unsigned)item->lParam; + if (index == kParentIndex) + return; + const bool oldSelected = (item->uOldState & LVIS_SELECTED) != 0; + const bool newSelected = (item->uNewState & LVIS_SELECTED) != 0; + // Don't change this code. It works only with such check + if (oldSelected != newSelected) + _selectedStatusVector[index] = newSelected; +} + +extern bool g_LVN_ITEMACTIVATE_Support; + +void CPanel::OnNotifyActivateItems() +{ + bool alt = IsKeyDown(VK_MENU); + bool ctrl = IsKeyDown(VK_CONTROL); + bool shift = IsKeyDown(VK_SHIFT); + if (!shift && alt && !ctrl) + Properties(); + else + OpenSelectedItems(!shift || alt || ctrl); +} + +bool CPanel::OnNotifyList(LPNMHDR header, LRESULT &result) +{ + switch (header->code) + { + case LVN_ITEMCHANGED: + { + if (_enableItemChangeNotify) + { + if (!_mySelectMode) + OnItemChanged((LPNMLISTVIEW)header); + + // Post_Refresh_StatusBar(); + /* 9.26: we don't call Post_Refresh_StatusBar. + it was very slow if we select big number of files + and then clead slection by selecting just new file. + probably it called slow Refresh_StatusBar for each item deselection. + I hope Refresh_StatusBar still will be called for each key / mouse action. + */ + } + return false; + } + /* + + case LVN_ODSTATECHANGED: + { + break; + } + */ + + case LVN_GETDISPINFOW: + { + LV_DISPINFOW *dispInfo = (LV_DISPINFOW *)header; + + //is the sub-item information being requested? + + if ((dispInfo->item.mask & LVIF_TEXT) != 0 || + (dispInfo->item.mask & LVIF_IMAGE) != 0) + SetItemText(dispInfo->item); + { + // 20.03: + result = 0; + return true; + // old 7-Zip: + // return false; + } + } + case LVN_KEYDOWN: + { + LPNMLVKEYDOWN keyDownInfo = LPNMLVKEYDOWN(header); + bool boolResult = OnKeyDown(keyDownInfo, result); + switch (keyDownInfo->wVKey) + { + case VK_CONTROL: + case VK_SHIFT: + case VK_MENU: + break; + default: + Post_Refresh_StatusBar(); + } + return boolResult; + } + + case LVN_COLUMNCLICK: + OnColumnClick(LPNMLISTVIEW(header)); + return false; + + case LVN_ITEMACTIVATE: + if (g_LVN_ITEMACTIVATE_Support) + { + OnNotifyActivateItems(); + return false; + } + break; + case NM_DBLCLK: + case NM_RETURN: + if (!g_LVN_ITEMACTIVATE_Support) + { + OnNotifyActivateItems(); + return false; + } + break; + + case NM_RCLICK: + Post_Refresh_StatusBar(); + break; + + /* + return OnRightClick((LPNMITEMACTIVATE)header, result); + */ + /* + case NM_CLICK: + SendRefreshStatusBarMessage(); + return 0; + + // TODO : Handler default action... + return 0; + case LVN_ITEMCHANGED: + { + NMLISTVIEW *pNMLV = (NMLISTVIEW *) lpnmh; + SelChange(pNMLV); + return TRUE; + } + case NM_SETFOCUS: + return onSetFocus(NULL); + case NM_KILLFOCUS: + return onKillFocus(NULL); + */ + case NM_CLICK: + { + // we need SetFocusToList, if we drag-select items from other panel. + SetFocusToList(); + Post_Refresh_StatusBar(); + if (_mySelectMode) +#ifdef Z7_USE_DYN_ComCtl32Version + if (g_ComCtl32Version >= MAKELONG(71, 4)) +#endif + OnLeftClick((MY_NMLISTVIEW_NMITEMACTIVATE *)header); + return false; + } + case LVN_BEGINLABELEDITW: + result = OnBeginLabelEdit((LV_DISPINFOW *)header); + return true; + case LVN_ENDLABELEDITW: + result = OnEndLabelEdit((LV_DISPINFOW *)header); + return true; + + case NM_CUSTOMDRAW: + { + if (_mySelectMode || (_markDeletedItems && _thereAreDeletedItems)) + return OnCustomDraw((LPNMLVCUSTOMDRAW)header, result); + break; + } + case LVN_BEGINDRAG: + { + OnDrag((LPNMLISTVIEW)header, false); + Post_Refresh_StatusBar(); + break; + } + case LVN_BEGINRDRAG: + { + OnDrag((LPNMLISTVIEW)header, true); + Post_Refresh_StatusBar(); + break; + } + // case LVN_BEGINRDRAG: + } + return false; +} + +bool CPanel::OnCustomDraw(LPNMLVCUSTOMDRAW lplvcd, LRESULT &result) +{ + switch (lplvcd->nmcd.dwDrawStage) + { + case CDDS_PREPAINT : + result = CDRF_NOTIFYITEMDRAW; + return true; + + case CDDS_ITEMPREPAINT: + /* + SelectObject(lplvcd->nmcd.hdc, + GetFontForItem(lplvcd->nmcd.dwItemSpec, + lplvcd->nmcd.lItemlParam) ); + lplvcd->clrText = GetColorForItem(lplvcd->nmcd.dwItemSpec, + lplvcd->nmcd.lItemlParam); + lplvcd->clrTextBk = GetBkColorForItem(lplvcd->nmcd.dwItemSpec, + lplvcd->nmcd.lItemlParam); + */ + const unsigned realIndex = (unsigned)lplvcd->nmcd.lItemlParam; + lplvcd->clrTextBk = _listView.GetBkColor(); + if (_mySelectMode) + { + if (realIndex != kParentIndex && _selectedStatusVector[realIndex]) + lplvcd->clrTextBk = RGB(255, 192, 192); + } + + if (_markDeletedItems && _thereAreDeletedItems) + { + if (IsItem_Deleted(realIndex)) + lplvcd->clrText = RGB(255, 0, 0); + } + // lplvcd->clrText = RGB(0, 0, 0); + // result = CDRF_NEWFONT; + result = CDRF_NOTIFYITEMDRAW; + return true; + + // return false; + // return true; + /* + case CDDS_SUBITEM | CDDS_ITEMPREPAINT: + if (lplvcd->iSubItem == 0) + { + // lplvcd->clrText = RGB(255, 0, 0); + lplvcd->clrTextBk = RGB(192, 192, 192); + } + else + { + lplvcd->clrText = RGB(0, 0, 0); + lplvcd->clrTextBk = RGB(255, 255, 255); + } + return true; + */ + + /* At this point, you can change the background colors for the item + and any subitems and return CDRF_NEWFONT. If the list-view control + is in report mode, you can simply return CDRF_NOTIFYSUBITEMREDRAW + to customize the item's subitems individually */ + } + return false; +} + +void CPanel::Refresh_StatusBar() +{ + /* + g_name_cnt++; + char s[256]; + sprintf(s, "g_name_cnt = %8d", g_name_cnt); + OutputDebugStringA(s); + */ + // DWORD dw = GetTickCount(); + + CRecordVector indices; + Get_ItemIndices_Operated(indices); + + { + UString s; + s.Add_UInt32(indices.Size()); + s += " / "; + s.Add_UInt32(_selectedStatusVector.Size()); + + // UString s1 = MyFormatNew(g_App.LangString_N_SELECTED_ITEMS, NumberToString(indices.Size())); + // UString s1 = MyFormatNew(IDS_N_SELECTED_ITEMS, NumberToString(indices.Size())); + _statusBar.SetText(0, MyFormatNew(g_App.LangString_N_SELECTED_ITEMS, s)); + // _statusBar.SetText(0, MyFormatNew(IDS_N_SELECTED_ITEMS, NumberToString(indices.Size()))); + } + + { + wchar_t selectSizeString[32]; + selectSizeString[0] = 0; + + if (!indices.IsEmpty()) + { + // for (unsigned ttt = 0; ttt < 1000; ttt++) { + UInt64 totalSize = 0; + FOR_VECTOR (i, indices) + totalSize += GetItemSize(indices[i]); + ConvertSizeToString(totalSize, selectSizeString); + // } + } + _statusBar.SetText(1, selectSizeString); + } + + const int focusedItem = _listView.GetFocusedItem(); + wchar_t sizeString[32]; + sizeString[0] = 0; + wchar_t dateString[32]; + dateString[0] = 0; + if (focusedItem >= 0 && _listView.GetSelectedCount() > 0) + { + const unsigned realIndex = GetRealItemIndex(focusedItem); + if (realIndex != kParentIndex) + { + ConvertSizeToString(GetItemSize(realIndex), sizeString); + NCOM::CPropVariant prop; + if (_folder->GetProperty(realIndex, kpidMTime, &prop) == S_OK) + { + char dateString2[64]; + dateString2[0] = 0; + ConvertPropertyToShortString2(dateString2, prop, kpidMTime); + for (unsigned i = 0;; i++) + { + char c = dateString2[i]; + dateString[i] = (Byte)c; + if (c == 0) + break; + } + } + } + } + _statusBar.SetText(2, sizeString); + _statusBar.SetText(3, dateString); + + // _statusBar.SetText(4, nameString); + // _statusBar2.SetText(1, MyFormatNew(L"{0} bytes", NumberToStringW(totalSize))); + // } + /* + dw = GetTickCount() - dw; + sprintf(s, "status = %8d ms", dw); + OutputDebugStringA(s); + */ +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PanelMenu.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PanelMenu.cpp new file mode 100644 index 00000000..e6558438 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PanelMenu.cpp @@ -0,0 +1,1158 @@ +#include "StdAfx.h" + +#include "../../../Common/IntToString.h" +#include "../../../Common/StringConvert.h" + +#include "../../../Windows/COM.h" +#include "../../../Windows/Clipboard.h" +#include "../../../Windows/Menu.h" +#include "../../../Windows/PropVariant.h" +#include "../../../Windows/PropVariantConv.h" + +#include "../../PropID.h" +#include "../Common/PropIDUtils.h" +#include "../Explorer/ContextMenu.h" + +#include "App.h" +#include "FormatUtils.h" +#include "LangUtils.h" +#include "ListViewDialog.h" +#include "MyLoadMenu.h" +#include "PropertyName.h" + +#include "PropertyNameRes.h" +#include "resource.h" + +// #define SHOW_DEBUG_PANEL_MENU + +using namespace NWindows; + +extern +LONG g_DllRefCount; +LONG g_DllRefCount = 0; + +static const UINT kSevenZipStartMenuID = kMenuCmdID_Plugin_Start; +static const UINT kSystemStartMenuID = kMenuCmdID_Plugin_Start + 400; + + +#ifdef SHOW_DEBUG_PANEL_MENU +static void Print_Ptr(void *p, const char *s) +{ + char temp[32]; + ConvertUInt64ToHex((UInt64)(void *)p, temp); + AString m; + m += temp; + m.Add_Space(); + m += s; + OutputDebugStringA(m); +} +#define ODS(sz) { Print_Ptr(this, sz); } +#define ODS_U(s) { OutputDebugStringW(s); } +#else +#define ODS(sz) +#define ODS_U(s) +#endif + + +void CPanel::InvokeSystemCommand(const char *command) +{ + NCOM::CComInitializer comInitializer; + if (!IsFsOrPureDrivesFolder()) + return; + CRecordVector operatedIndices; + Get_ItemIndices_Operated(operatedIndices); + if (operatedIndices.IsEmpty()) + return; + CMyComPtr contextMenu; + if (CreateShellContextMenu(operatedIndices, contextMenu) != S_OK) + return; + + CMINVOKECOMMANDINFO ci; + ZeroMemory(&ci, sizeof(ci)); + ci.cbSize = sizeof(CMINVOKECOMMANDINFO); + ci.hwnd = GetParent(); + ci.lpVerb = command; + contextMenu->InvokeCommand(&ci); +} + +static const char * const kSeparator = "------------------------"; +static const char * const kSeparatorSmall = "----------------"; + +extern UString ConvertSizeToString(UInt64 value) throw(); +bool IsSizeProp(UINT propID) throw(); + +UString GetOpenArcErrorMessage(UInt32 errorFlags); + + +static void AddListAscii(CListViewDialog &dialog, const char *s) +{ + dialog.Strings.Add((UString)s); + dialog.Values.AddNew(); +} + +static void AddSeparator(CListViewDialog &dialog) +{ + AddListAscii(dialog, kSeparator); +} + +static void AddSeparatorSmall(CListViewDialog &dialog) +{ + AddListAscii(dialog, kSeparatorSmall); +} + +static void AddPropertyPair(const UString &name, const UString &val, CListViewDialog &dialog) +{ + dialog.Strings.Add(name); + dialog.Values.Add(val); +} + + +static void AddPropertyString(PROPID propID, const wchar_t *nameBSTR, + const NCOM::CPropVariant &prop, CListViewDialog &dialog) +{ + if (prop.vt != VT_EMPTY) + { + UString val; + + if (propID == kpidErrorFlags || + propID == kpidWarningFlags) + { + UInt32 flags = GetOpenArcErrorFlags(prop); + if (flags == 0) + return; + if (flags != 0) + val = GetOpenArcErrorMessage(flags); + } + + if (val.IsEmpty()) + { + if ((prop.vt == VT_UI8 || prop.vt == VT_UI4 || prop.vt == VT_UI2) && IsSizeProp(propID)) + { + UInt64 v = 0; + ConvertPropVariantToUInt64(prop, v); + val = ConvertSizeToString(v); + } + else + ConvertPropertyToString2(val, prop, propID, 9); // we send 9 - is ns precision + } + + if (!val.IsEmpty()) + { + if (propID == kpidErrorType) + { + AddPropertyPair(L"Open WARNING:", L"Cannot open the file as expected archive type", dialog); + } + AddPropertyPair(GetNameOfProperty(propID, nameBSTR), val, dialog); + } + } +} + + +static void AddPropertyString(PROPID propID, UInt64 val, CListViewDialog &dialog) +{ + NCOM::CPropVariant prop = val; + AddPropertyString(propID, NULL, prop, dialog); +} + + +static const Byte kSpecProps[] = +{ + kpidPath, + kpidType, + kpidErrorType, + kpidError, + kpidErrorFlags, + kpidWarning, + kpidWarningFlags, + kpidOffset, + kpidPhySize, + kpidTailSize +}; + +void CPanel::Properties() +{ + CMyComPtr getFolderArcProps; + _folder.QueryInterface(IID_IGetFolderArcProps, &getFolderArcProps); + if (!getFolderArcProps) + { + InvokeSystemCommand("properties"); + return; + } + + { + CListViewDialog message; + // message.DeleteIsAllowed = false; + // message.SelectFirst = false; + + CRecordVector operatedIndices; + Get_ItemIndices_Operated(operatedIndices); + + if (operatedIndices.Size() == 1) + { + UInt32 index = operatedIndices[0]; + // message += "Item:\n"); + UInt32 numProps; + if (_folder->GetNumberOfProperties(&numProps) == S_OK) + { + for (UInt32 i = 0; i < numProps; i++) + { + CMyComBSTR name; + PROPID propID; + VARTYPE varType; + + if (_folder->GetPropertyInfo(i, &name, &propID, &varType) != S_OK) + continue; + + NCOM::CPropVariant prop; + if (_folder->GetProperty(index, propID, &prop) != S_OK) + continue; + AddPropertyString(propID, name, prop, message); + } + } + + + if (_folderRawProps) + { + _folderRawProps->GetNumRawProps(&numProps); + for (UInt32 i = 0; i < numProps; i++) + { + CMyComBSTR name; + PROPID propID; + if (_folderRawProps->GetRawPropInfo(i, &name, &propID) != S_OK) + continue; + + const void *data; + UInt32 dataSize; + UInt32 propType; + if (_folderRawProps->GetRawProp(index, propID, &data, &dataSize, &propType) != S_OK) + continue; + + if (dataSize != 0) + { + AString s; + if (propID == kpidNtSecure) + ConvertNtSecureToString((const Byte *)data, dataSize, s); + else + { + const unsigned kMaxDataSize = 1 << 8; + if (dataSize > kMaxDataSize) + { + s += "data:"; + s.Add_UInt32(dataSize); + } + else + { + char temp[kMaxDataSize * 2 + 2]; + if (dataSize <= 8 && (propID == kpidCRC || propID == kpidChecksum)) + ConvertDataToHex_Upper(temp, (const Byte *)data, dataSize); + else + ConvertDataToHex_Lower(temp, (const Byte *)data, dataSize); + s += temp; + } + } + AddPropertyPair(GetNameOfProperty(propID, name), (UString)s.Ptr(), message); + } + } + } + + AddSeparator(message); + } + else if (operatedIndices.Size() >= 1) + { + UInt64 packSize = 0; + UInt64 unpackSize = 0; + UInt64 numFiles = 0; + UInt64 numDirs = 0; + + FOR_VECTOR (i, operatedIndices) + { + const UInt32 index = operatedIndices[i]; + unpackSize += GetItemSize(index); + packSize += GetItem_UInt64Prop(index, kpidPackSize); + if (IsItem_Folder(index)) + { + numDirs++; + numDirs += GetItem_UInt64Prop(index, kpidNumSubDirs); + numFiles += GetItem_UInt64Prop(index, kpidNumSubFiles); + } + else + numFiles++; + } + { + wchar_t temp[32]; + ConvertUInt32ToString(operatedIndices.Size(), temp); + AddPropertyPair(L"", MyFormatNew(g_App.LangString_N_SELECTED_ITEMS, temp), message); + } + + if (numDirs != 0) + AddPropertyString(kpidNumSubDirs, numDirs, message); + if (numFiles != 0) + AddPropertyString(kpidNumSubFiles, numFiles, message); + AddPropertyString(kpidSize, unpackSize, message); + AddPropertyString(kpidPackSize, packSize, message); + + AddSeparator(message); + } + + + /* + AddLangString(message, IDS_PROP_FILE_TYPE); + message += kPropValueSeparator; + message += GetFolderTypeID(); + message.Add_LF(); + */ + + { + NCOM::CPropVariant prop; + if (_folder->GetFolderProperty(kpidPath, &prop) == S_OK) + { + AddPropertyString(kpidName, L"Path", prop, message); + } + } + + CMyComPtr folderProperties; + _folder.QueryInterface(IID_IFolderProperties, &folderProperties); + if (folderProperties) + { + UInt32 numProps; + if (folderProperties->GetNumberOfFolderProperties(&numProps) == S_OK) + { + for (UInt32 i = 0; i < numProps; i++) + { + CMyComBSTR name; + PROPID propID; + VARTYPE vt; + if (folderProperties->GetFolderPropertyInfo(i, &name, &propID, &vt) != S_OK) + continue; + NCOM::CPropVariant prop; + if (_folder->GetFolderProperty(propID, &prop) != S_OK) + continue; + AddPropertyString(propID, name, prop, message); + } + } + } + + if (getFolderArcProps) + { + CMyComPtr getProps; + getFolderArcProps->GetFolderArcProps(&getProps); + if (getProps) + { + UInt32 numLevels; + if (getProps->GetArcNumLevels(&numLevels) != S_OK) + numLevels = 0; + for (UInt32 level2 = 0; level2 < numLevels; level2++) + { + { + UInt32 level = numLevels - 1 - level2; + UInt32 numProps; + if (getProps->GetArcNumProps(level, &numProps) == S_OK) + { + const int kNumSpecProps = Z7_ARRAY_SIZE(kSpecProps); + + AddSeparator(message); + + for (Int32 i = -(int)kNumSpecProps; i < (Int32)numProps; i++) + { + CMyComBSTR name; + PROPID propID; + VARTYPE vt; + if (i < 0) + propID = kSpecProps[i + kNumSpecProps]; + else if (getProps->GetArcPropInfo(level, (UInt32)i, &name, &propID, &vt) != S_OK) + continue; + NCOM::CPropVariant prop; + if (getProps->GetArcProp(level, propID, &prop) != S_OK) + continue; + AddPropertyString(propID, name, prop, message); + } + } + } + + if (level2 < numLevels - 1) + { + const UInt32 level = numLevels - 1 - level2; + UInt32 numProps; + if (getProps->GetArcNumProps2(level, &numProps) == S_OK) + { + AddSeparatorSmall(message); + for (UInt32 i = 0; i < numProps; i++) + { + CMyComBSTR name; + PROPID propID; + VARTYPE vt; + if (getProps->GetArcPropInfo2(level, i, &name, &propID, &vt) != S_OK) + continue; + NCOM::CPropVariant prop; + if (getProps->GetArcProp2(level, propID, &prop) != S_OK) + continue; + AddPropertyString(propID, name, prop, message); + } + } + } + } + + { + // we ERROR message for NonOpen level + bool needSep = true; + const int kNumSpecProps = Z7_ARRAY_SIZE(kSpecProps); + for (Int32 i = -(int)kNumSpecProps; i < 0; i++) + { + CMyComBSTR name; + const PROPID propID = kSpecProps[i + kNumSpecProps]; + NCOM::CPropVariant prop; + if (getProps->GetArcProp(numLevels, propID, &prop) != S_OK) + continue; + if (needSep) + { + AddSeparator(message); + AddSeparator(message); + needSep = false; + } + AddPropertyString(propID, name, prop, message); + } + } + + } + } + + message.Title = LangString(IDS_PROPERTIES); + message.NumColumns = 2; + message.Create(GetParent()); + } +} + + + +void CPanel::EditCut() +{ + // InvokeSystemCommand("cut"); +} + +void CPanel::EditCopy() +{ + /* + CMyComPtr getFolderArcProps; + _folder.QueryInterface(IID_IGetFolderArcProps, &getFolderArcProps); + if (!getFolderArcProps) + { + InvokeSystemCommand("copy"); + return; + } + */ + UString s; + CRecordVector indices; + Get_ItemIndices_Selected(indices); + FOR_VECTOR (i, indices) + { + if (i != 0) + s += "\xD\n"; + s += GetItemName(indices[i]); + } + ClipboardSetText(_mainWindow, s); +} + +void CPanel::EditPaste() +{ + /* + UStringVector names; + ClipboardGetFileNames(names); + CopyFromNoAsk(names); + UString s; + for (int i = 0; i < names.Size(); i++) + { + s += L' '; + s += names[i]; + } + + MessageBoxW(0, s, L"", 0); + */ + + // InvokeSystemCommand("paste"); +} + + + +struct CFolderPidls +{ + LPITEMIDLIST parent; + CRecordVector items; + + CFolderPidls(): parent(NULL) {} + ~CFolderPidls() + { + FOR_VECTOR (i, items) + CoTaskMemFree(items[i]); + CoTaskMemFree(parent); + } +}; + + +HRESULT ShellFolder_ParseDisplayName(IShellFolder *shellFolder, + HWND hwnd, const UString &path, LPITEMIDLIST *ppidl); +HRESULT ShellFolder_ParseDisplayName(IShellFolder *shellFolder, + HWND hwnd, const UString &path, LPITEMIDLIST *ppidl) +{ + ULONG eaten = 0; + return shellFolder->ParseDisplayName(hwnd, NULL, + path.Ptr_non_const(), &eaten, ppidl, NULL); +} + + +HRESULT CPanel::CreateShellContextMenu( + const CRecordVector &operatedIndices, + CMyComPtr &systemContextMenu) +{ + ODS("==== CPanel::CreateShellContextMenu"); + systemContextMenu.Release(); + UString folderPath = GetFsPath(); + + CMyComPtr desktopFolder; + RINOK(::SHGetDesktopFolder(&desktopFolder)) + if (!desktopFolder) + { + // ShowMessage("Failed to get Desktop folder"); + return E_FAIL; + } + + CFolderPidls pidls; + // NULL is allowed for parentHWND in ParseDisplayName() + const HWND parentHWND_for_ParseDisplayName = GetParent(); + // if (folderPath.IsEmpty()), then ParseDisplayName returns pidls of "My Computer" + /* win10: ParseDisplayName() supports folder path with tail slash + ParseDisplayName() returns { + E_INVALIDARG : path with super path prefix "\\\\?\\" + ERROR_FILE_NOT_FOUND : path for network share (\\server\path1\long path2") larger than MAX_PATH + } */ + const HRESULT res = ShellFolder_ParseDisplayName(desktopFolder, + parentHWND_for_ParseDisplayName, + folderPath, &pidls.parent); + if (res != S_OK) + { + ODS_U(folderPath); + if (res != E_INVALIDARG) + return res; + if (!NFile::NName::If_IsSuperPath_RemoveSuperPrefix(folderPath)) + return res; + RINOK(ShellFolder_ParseDisplayName(desktopFolder, + parentHWND_for_ParseDisplayName, + folderPath, &pidls.parent)) + } + if (!pidls.parent) + return E_FAIL; + + /* + UString path2; + NShell::GetPathFromIDList(pidls.parent, path2); + ODS_U(path2); + */ + + if (operatedIndices.IsEmpty()) + { + // how to get IContextMenu, if there are no selected files? + return E_FAIL; + + /* + xp64 : + 1) we can't use GetUIObjectOf() with (numItems == 0), it throws exception + 2) we can't use desktopFolder->GetUIObjectOf() with absolute pidls of folder + context menu items are different in that case: + "Open / Explorer" for folder + "Delete" for "My Computer" icon + "Preperties" for "System" + */ + /* + parentFolder = desktopFolder; + pidls.items.AddInReserved(pidls.parent); + pidls.parent = NULL; + */ + + // CreateViewObject() doesn't show all context menu items + /* + HRESULT res = parentFolder->CreateViewObject( + GetParent(), IID_IContextMenu, (void**)&systemContextMenu); + */ + } + + CMyComPtr parentFolder; + RINOK(desktopFolder->BindToObject(pidls.parent, + NULL, IID_IShellFolder, (void**)&parentFolder)) + if (!parentFolder) + return E_FAIL; + + ODS("==== CPanel::CreateShellContextMenu pidls START"); + + pidls.items.ClearAndReserve(operatedIndices.Size()); + UString fileName; + FOR_VECTOR (i, operatedIndices) + { + fileName.Empty(); + Add_ItemRelPath2_To_String(operatedIndices[i], fileName); + /* ParseDisplayName() in win10 returns: + E_INVALIDARG : if empty name, or path with dots only: "." , ".." + HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) : if there is no such file + */ + LPITEMIDLIST pidl = NULL; + RINOK(ShellFolder_ParseDisplayName(parentFolder, + parentHWND_for_ParseDisplayName, + fileName, &pidl)) + if (!pidl) + return E_FAIL; + pidls.items.AddInReserved(pidl); + } + + ODS("==== CPanel::CreateShellContextMenu pidls END"); + // Get IContextMenu for items + RINOK(parentFolder->GetUIObjectOf(GetParent(), + pidls.items.Size(), (LPCITEMIDLIST *)(void *)pidls.items.ConstData(), + IID_IContextMenu, NULL, (void**)&systemContextMenu)) + ODS("==== CPanel::CreateShellContextMenu GetUIObjectOf finished"); + if (!systemContextMenu) + { + // ShowMessage("Unable to get context menu interface"); + return E_FAIL; + } + return S_OK; +} + + +// #define SHOW_DEBUG_FM_CTX_MENU + +#ifdef SHOW_DEBUG_FM_CTX_MENU + +static void PrintHex(UString &s, UInt32 v) +{ + char sz[32]; + ConvertUInt32ToHex(v, sz); + s += sz; +} + +static void PrintContextStr(UString &s, IContextMenu *ctxm, unsigned i, unsigned id, const char *name) +{ + s += " | "; + s += name; + s += ": "; + UString s1; + { + char buf[256]; + buf[0] = 0; + const HRESULT res = ctxm->GetCommandString(i, id, + NULL, buf, Z7_ARRAY_SIZE(buf) - 1); + if (res != S_OK) + { + PrintHex(s1, res); + s1.Add_Space(); + } + s1 += GetUnicodeString(buf); + } + UString s2; + { + wchar_t buf2[256]; + buf2[0] = 0; + const HRESULT res = ctxm->GetCommandString(i, id | GCS_UNICODE, + NULL, (char *)buf2, Z7_ARRAY_SIZE(buf2) - sizeof(wchar_t)); + if (res != S_OK) + { + PrintHex(s2, res); + s2.Add_Space(); + } + s2 += buf2; + } + s += s1; + if (s2.Compare(s1) != 0) + { + s += " Unicode: "; + s += s2; + } +} + +static void PrintAllContextItems(IContextMenu *ctxm, unsigned num) +{ + for (unsigned i = 0; i < num; i++) + { + UString s; + s.Add_UInt32(i); + s += ": "; + PrintContextStr(s, ctxm, i, GCS_VALIDATEA, "valid"); + PrintContextStr(s, ctxm, i, GCS_VERBA, "verb"); + PrintContextStr(s, ctxm, i, GCS_HELPTEXTA, "helptext"); + OutputDebugStringW(s); + } +} + +#endif + + +void CPanel::CreateSystemMenu(HMENU menuSpec, + bool showExtendedVerbs, + const CRecordVector &operatedIndices, + CMyComPtr &systemContextMenu) +{ + systemContextMenu.Release(); + + CreateShellContextMenu(operatedIndices, systemContextMenu); + + if (!systemContextMenu) + return; + + /* + // Set up a CMINVOKECOMMANDINFO structure. + CMINVOKECOMMANDINFO ci; + ZeroMemory(&ci, sizeof(ci)); + ci.cbSize = sizeof(CMINVOKECOMMANDINFO); + ci.hwnd = GetParent(); + */ + + /* + if (Sender == GoBtn) + { + // Verbs that can be used are cut, paste, + // properties, delete, and so on. + String action; + if (CutRb->Checked) + action = "cut"; + else if (CopyRb->Checked) + action = "copy"; + else if (DeleteRb->Checked) + action = "delete"; + else if (PropertiesRb->Checked) + action = "properties"; + + ci.lpVerb = action.c_str(); + result = cm->InvokeCommand(&ci); + if (result) + ShowMessage( + "Error copying file to clipboard."); + + } + else + */ + { + // HMENU hMenu = CreatePopupMenu(); + CMenu popupMenu; + CMenuDestroyer menuDestroyer(popupMenu); + if (!popupMenu.CreatePopup()) + throw 210503; + const HMENU hMenu = popupMenu; + DWORD flags = CMF_EXPLORE; + if (showExtendedVerbs) + flags |= Z7_WIN_CMF_EXTENDEDVERBS; + ODS("=== systemContextMenu->QueryContextMenu START"); + const HRESULT res = systemContextMenu->QueryContextMenu(hMenu, 0, kSystemStartMenuID, 0x7FFF, flags); + ODS("=== systemContextMenu->QueryContextMenu END"); + if (SUCCEEDED(res)) + { + #ifdef SHOW_DEBUG_FM_CTX_MENU + PrintAllContextItems(systemContextMenu, (unsigned)res); + #endif + + CMenu menu; + menu.Attach(menuSpec); + CMenuItem menuItem; + menuItem.fMask = MIIM_SUBMENU | MIIM_TYPE | MIIM_ID; + menuItem.fType = MFT_STRING; + menuItem.hSubMenu = popupMenu.Detach(); + menuDestroyer.Disable(); + LangString(IDS_SYSTEM, menuItem.StringValue); + menu.InsertItem(0, true, menuItem); + } + /* + if (Cmd < 100 && Cmd != 0) + { + ci.lpVerb = MAKEINTRESOURCE(Cmd - 1); + ci.lpParameters = ""; + ci.lpDirectory = ""; + ci.nShow = SW_SHOWNORMAL; + cm->InvokeCommand(&ci); + } + // If Cmd is > 100 then it's one of our + // inserted menu items. + else + // Find the menu item. + for (int i = 0; i < popupMenu1->Items->Count; i++) + { + TMenuItem* menu = popupMenu1->Items->Items[i]; + // Call its OnClick handler. + if (menu->Command == Cmd - 100) + menu->OnClick(this); + } + // Release the memory allocated for the menu. + DestroyMenu(hMenu); + */ + } +} + +void CPanel::CreateFileMenu(HMENU menuSpec) +{ + CreateFileMenu(menuSpec, _sevenZipContextMenu, _systemContextMenu, true); // programMenu +} + +void CPanel::CreateSevenZipMenu(HMENU menuSpec, + bool showExtendedVerbs, + const CRecordVector &operatedIndices, + int firstDirIndex, + CMyComPtr &sevenZipContextMenu) +{ + sevenZipContextMenu.Release(); + + CMenu menu; + menu.Attach(menuSpec); + // CMenuDestroyer menuDestroyer(menu); + // menu.CreatePopup(); + + CZipContextMenu *contextMenuSpec = new CZipContextMenu; + CMyComPtr contextMenu = contextMenuSpec; + // if (contextMenu.CoCreateInstance(CLSID_CZipContextMenu, IID_IContextMenu) == S_OK) + { + /* + CMyComPtr initContextMenu; + if (contextMenu.QueryInterface(IID_IInitContextMenu, &initContextMenu) != S_OK) + return; + */ + ODS("=== FileName List Add START") + // for (unsigned y = 0; y < 10000; y++, contextMenuSpec->_fileNames.Clear()) + GetFilePaths(operatedIndices, contextMenuSpec->_fileNames); + ODS("=== FileName List Add END") + contextMenuSpec->Init_For_7zFM(); + contextMenuSpec->_attribs.FirstDirIndex = firstDirIndex; + { + DWORD flags = CMF_EXPLORE; + if (showExtendedVerbs) + flags |= Z7_WIN_CMF_EXTENDEDVERBS; + const HRESULT res = contextMenu->QueryContextMenu(menu, + 0, // indexMenu + kSevenZipStartMenuID, // first + kSystemStartMenuID - 1, // last + flags); + ODS("=== contextMenu->QueryContextMenu END") + const bool sevenZipMenuCreated = SUCCEEDED(res); + if (sevenZipMenuCreated) + { + // if (res != 0) + { + // some "non-good" implementation of QueryContextMenu() could add some items to menu, but it return 0. + // so we still allow these items + sevenZipContextMenu = contextMenu; + #ifdef SHOW_DEBUG_FM_CTX_MENU + PrintAllContextItems(contextMenu, (unsigned)res); + #endif + } + } + else + { + // MessageBox_Error_HRESULT_Caption(res, L"QueryContextMenu"); + } + // int code = HRESULT_CODE(res); + // int nextItemID = code; + } + } +} + +static bool IsReadOnlyFolder(IFolderFolder *folder) +{ + if (!folder) + return false; + + bool res = false; + { + NCOM::CPropVariant prop; + if (folder->GetFolderProperty(kpidReadOnly, &prop) == S_OK) + if (prop.vt == VT_BOOL) + res = VARIANT_BOOLToBool(prop.boolVal); + } + return res; +} + +bool CPanel::IsThereReadOnlyFolder() const +{ + if (!_folderOperations) + return true; + if (IsReadOnlyFolder(_folder)) + return true; + FOR_VECTOR (i, _parentFolders) + { + if (IsReadOnlyFolder(_parentFolders[i].ParentFolder)) + return true; + } + return false; +} + +bool CPanel::CheckBeforeUpdate(UINT resourceID) +{ + if (!_folderOperations) + { + MessageBox_Error_UnsupportOperation(); + // resourceID = resourceID; + // MessageBoxErrorForUpdate(E_NOINTERFACE, resourceID); + return false; + } + + for (int i = (int)_parentFolders.Size(); i >= 0; i--) + { + IFolderFolder *folder; + if (i == (int)_parentFolders.Size()) + folder = _folder; + else + folder = _parentFolders[i].ParentFolder; + + if (!IsReadOnlyFolder(folder)) + continue; + + UString s; + AddLangString(s, resourceID); + s.Add_LF(); + AddLangString(s, IDS_OPERATION_IS_NOT_SUPPORTED); + s.Add_LF(); + if (i == 0) + s += GetFolderPath(folder); + else + s += _parentFolders[i - 1].VirtualPath; + s.Add_LF(); + AddLangString(s, IDS_PROP_READ_ONLY); + MessageBox_Error(s); + return false; + } + + return true; +} + +void CPanel::CreateFileMenu(HMENU menuSpec, + CMyComPtr &sevenZipContextMenu, + CMyComPtr &systemContextMenu, + bool programMenu) +{ + sevenZipContextMenu.Release(); + systemContextMenu.Release(); + + const bool showExtendedVerbs = IsKeyDown(VK_SHIFT); + + CRecordVector operatedIndices; + Get_ItemIndices_Operated(operatedIndices); + const int firstDirIndex = FindDir_InOperatedList(operatedIndices); + + CMenu menu; + menu.Attach(menuSpec); + + if (!IsArcFolder()) + { + CreateSevenZipMenu(menu, showExtendedVerbs, operatedIndices, firstDirIndex, sevenZipContextMenu); + // CreateSystemMenu is very slow if you call it inside ZIP archive with big number of files + // Windows probably can parse items inside ZIP archive. + if (g_App.ShowSystemMenu) + CreateSystemMenu(menu, showExtendedVerbs, operatedIndices, systemContextMenu); + } + + /* + if (menu.GetItemCount() > 0) + menu.AppendItem(MF_SEPARATOR, 0, (LPCTSTR)0); + */ + + CFileMenu fm; + + fm.readOnly = IsThereReadOnlyFolder(); + fm.isHashFolder = IsHashFolder(); + fm.isFsFolder = Is_IO_FS_Folder(); + fm.programMenu = programMenu; + fm.allAreFiles = (firstDirIndex == -1); + fm.numItems = operatedIndices.Size(); + + fm.isAltStreamsSupported = false; + + if (fm.numItems == 1) + fm.FilePath = us2fs(GetItemFullPath(operatedIndices[0])); + + if (_folderAltStreams) + { + if (operatedIndices.Size() <= 1) + { + UInt32 realIndex = (UInt32)(Int32)-1; + if (operatedIndices.Size() == 1) + realIndex = operatedIndices[0]; + Int32 val = 0; + if (_folderAltStreams->AreAltStreamsSupported(realIndex, &val) == S_OK) + fm.isAltStreamsSupported = IntToBool(val); + } + } + else + { + if (fm.numItems == 0) + fm.isAltStreamsSupported = IsFSFolder(); + else + fm.isAltStreamsSupported = IsFolder_with_FsItems(); + } + + fm.Load(menu, (unsigned)menu.GetItemCount()); +} + +bool CPanel::InvokePluginCommand(unsigned id) +{ + return InvokePluginCommand(id, _sevenZipContextMenu, _systemContextMenu); +} + +#if defined(_MSC_VER) && !defined(UNDER_CE) +#define use_CMINVOKECOMMANDINFOEX +/* CMINVOKECOMMANDINFOEX depends from (_WIN32_IE >= 0x0400) */ +#endif + +bool CPanel::InvokePluginCommand(unsigned id, + IContextMenu *sevenZipContextMenu, IContextMenu *systemContextMenu) +{ + UInt32 offset; + const bool isSystemMenu = (id >= kSystemStartMenuID); + if (isSystemMenu) + { + if (!systemContextMenu) + return false; + offset = id - kSystemStartMenuID; + } + else + { + if (!sevenZipContextMenu) + return false; + offset = id - kSevenZipStartMenuID; + } + + #ifdef use_CMINVOKECOMMANDINFOEX + CMINVOKECOMMANDINFOEX + #else + CMINVOKECOMMANDINFO + #endif + commandInfo; + + memset(&commandInfo, 0, sizeof(commandInfo)); + commandInfo.cbSize = sizeof(commandInfo); + + commandInfo.fMask = 0 + #ifdef use_CMINVOKECOMMANDINFOEX + | CMIC_MASK_UNICODE + #endif + ; + + commandInfo.hwnd = GetParent(); + commandInfo.lpVerb = (LPCSTR)(MAKEINTRESOURCE(offset)); + commandInfo.lpParameters = NULL; + // 19.01: fixed CSysString to AString + // MSDN suggest to send NULL: lpDirectory: This member is always NULL for menu items inserted by a Shell extension. + const AString currentFolderA (GetAnsiString(_currentFolderPrefix)); + commandInfo.lpDirectory = (LPCSTR)(currentFolderA); + commandInfo.nShow = SW_SHOW; + + #ifdef use_CMINVOKECOMMANDINFOEX + + commandInfo.lpParametersW = NULL; + commandInfo.lpTitle = ""; + + /* + system ContextMenu handler supports ContextMenu subhandlers. + so InvokeCommand() converts (command_offset) from global number to subhandler number. + XP-64 / win10: + system ContextMenu converts (command_offset) in lpVerb only, + and it keeps lpVerbW unchanged. + also explorer.exe sends 0 in lpVerbW. + We try to keep compatibility with Windows Explorer here. + */ + commandInfo.lpVerbW = NULL; + + const UString currentFolderUnicode = _currentFolderPrefix; + commandInfo.lpDirectoryW = currentFolderUnicode; + commandInfo.lpTitleW = L""; + // commandInfo.ptInvoke.x = xPos; + // commandInfo.ptInvoke.y = yPos; + commandInfo.ptInvoke.x = 0; + commandInfo.ptInvoke.y = 0; + + #endif + + HRESULT result; + if (isSystemMenu) + result = systemContextMenu->InvokeCommand(LPCMINVOKECOMMANDINFO(&commandInfo)); + else + result = sevenZipContextMenu->InvokeCommand(LPCMINVOKECOMMANDINFO(&commandInfo)); + if (result == NOERROR) + { + KillSelection(); + return true; + } + else + MessageBox_Error_HRESULT_Caption(result, L"InvokeCommand"); + return false; +} + +bool CPanel::OnContextMenu(HANDLE windowHandle, int xPos, int yPos) +{ + if (::GetParent((HWND)windowHandle) == _listView) + { + ShowColumnsContextMenu(xPos, yPos); + return true; + } + + if (windowHandle != _listView) + return false; + /* + POINT point; + point.x = xPos; + point.y = yPos; + if (!_listView.ScreenToClient(&point)) + return false; + + LVHITTESTINFO info; + info.pt = point; + int index = _listView.HitTest(&info); + */ + + CRecordVector operatedIndices; + Get_ItemIndices_Operated(operatedIndices); + + // negative x,y are possible for multi-screen modes. + // x=-1 && y=-1 for keyboard call (SHIFT+F10 and others). + if (xPos == -1 && yPos == -1) + { + if (operatedIndices.Size() == 0) + { + xPos = 0; + yPos = 0; + } + else + { + int itemIndex = _listView.GetNextItem(-1, LVNI_FOCUSED); + if (itemIndex == -1) + return false; + RECT rect; + if (!_listView.GetItemRect(itemIndex, &rect, LVIR_ICON)) + return false; + xPos = (rect.left + rect.right) / 2; + yPos = (rect.top + rect.bottom) / 2; + } + POINT point = {xPos, yPos}; + _listView.ClientToScreen(&point); + xPos = point.x; + yPos = point.y; + } + + CMenu menu; + CMenuDestroyer menuDestroyer(menu); + menu.CreatePopup(); + + CMyComPtr sevenZipContextMenu; + CMyComPtr systemContextMenu; + CreateFileMenu(menu, sevenZipContextMenu, systemContextMenu, false); // programMenu + + const unsigned id = (unsigned)menu.Track(TPM_LEFTALIGN + #ifndef UNDER_CE + | TPM_RIGHTBUTTON + #endif + | TPM_RETURNCMD | TPM_NONOTIFY, + xPos, yPos, _listView); + + if (id == 0) + return true; + + if (id >= kMenuCmdID_Plugin_Start) + { + InvokePluginCommand(id, sevenZipContextMenu, systemContextMenu); + return true; + } + if (ExecuteFileCommand(id)) + return true; + return true; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PanelOperations.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PanelOperations.cpp new file mode 100644 index 00000000..427464b1 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PanelOperations.cpp @@ -0,0 +1,533 @@ +// PanelOperations.cpp + +#include "StdAfx.h" + +#include "../../../Common/DynamicBuffer.h" +#include "../../../Common/StringConvert.h" +#include "../../../Common/Wildcard.h" + +#include "../../../Windows/COM.h" +#include "../../../Windows/FileName.h" +#include "../../../Windows/PropVariant.h" + +#include "ComboDialog.h" + +#include "FSFolder.h" +#include "FormatUtils.h" +#include "LangUtils.h" +#include "Panel.h" +#include "UpdateCallback100.h" + +#include "resource.h" + +using namespace NWindows; +using namespace NFile; +using namespace NName; + +#ifndef _UNICODE +extern bool g_IsNT; +#endif + +enum EFolderOpType +{ + FOLDER_TYPE_CREATE_FOLDER = 0, + FOLDER_TYPE_DELETE = 1, + FOLDER_TYPE_RENAME = 2 +}; + +class CThreadFolderOperations: public CProgressThreadVirt +{ + HRESULT ProcessVirt() Z7_override; +public: + EFolderOpType OpType; + UString Name; + UInt32 Index; + CRecordVector Indices; + + CMyComPtr FolderOperations; + CMyComPtr UpdateCallback; + CUpdateCallback100Imp *UpdateCallbackSpec; + + CThreadFolderOperations(EFolderOpType opType): OpType(opType) {} + HRESULT DoOperation(CPanel &panel, const UString &progressTitle, const UString &titleError); +}; + +HRESULT CThreadFolderOperations::ProcessVirt() +{ + NCOM::CComInitializer comInitializer; + switch ((int)OpType) + { + case FOLDER_TYPE_CREATE_FOLDER: + return FolderOperations->CreateFolder(Name, UpdateCallback); + case FOLDER_TYPE_DELETE: + return FolderOperations->Delete(Indices.ConstData(), Indices.Size(), UpdateCallback); + case FOLDER_TYPE_RENAME: + return FolderOperations->Rename(Index, Name, UpdateCallback); + default: + return E_FAIL; + } +} + + +HRESULT CThreadFolderOperations::DoOperation(CPanel &panel, const UString &progressTitle, const UString &titleError) +{ + UpdateCallbackSpec = new CUpdateCallback100Imp; + UpdateCallback = UpdateCallbackSpec; + UpdateCallbackSpec->ProgressDialog = this; + + WaitMode = true; + Sync.FinalMessage.ErrorMessage.Title = titleError; + + UpdateCallbackSpec->Init(); + + if (!panel._parentFolders.IsEmpty()) + { + const CFolderLink &fl = panel._parentFolders.Back(); + UpdateCallbackSpec->PasswordIsDefined = fl.UsePassword; + UpdateCallbackSpec->Password = fl.Password; + } + + MainWindow = panel._mainWindow; // panel.GetParent() + MainTitle = "7-Zip"; // LangString(IDS_APP_TITLE); + MainAddTitle = progressTitle + L' '; + + RINOK(Create(progressTitle, MainWindow)) + return Result; +} + +#ifndef _UNICODE +typedef int (WINAPI * Func_SHFileOperationW)(LPSHFILEOPSTRUCTW lpFileOp); +#endif + +/* +void CPanel::MessageBoxErrorForUpdate(HRESULT errorCode, UINT resourceID) +{ + if (errorCode == E_NOINTERFACE) + MessageBox_Error_UnsupportOperation(); + else + MessageBox_Error_HRESULT_Caption(errorCode, LangString(resourceID)); +} +*/ + +void CPanel::DeleteItems(bool NON_CE_VAR(toRecycleBin)) +{ + CDisableTimerProcessing disableTimerProcessing(*this); + CRecordVector indices; + Get_ItemIndices_Operated(indices); + if (indices.IsEmpty()) + return; + CSelectedState state; + SaveSelectedState(state); + + #ifndef UNDER_CE + // WM6 / SHFileOperationW doesn't ask user! So we use internal delete + if (IsFSFolder() && toRecycleBin) + { + bool useInternalDelete = false; + #ifndef _UNICODE + if (!g_IsNT) + { + CDynamicBuffer buffer; + FOR_VECTOR (i, indices) + { + const AString path (GetSystemString(GetItemFullPath(indices[i]))); + buffer.AddData(path, path.Len() + 1); + } + *buffer.GetCurPtrAndGrow(1) = 0; + SHFILEOPSTRUCTA fo; + fo.hwnd = GetParent(); + fo.wFunc = FO_DELETE; + fo.pFrom = (const CHAR *)buffer; + fo.pTo = NULL; + fo.fFlags = 0; + if (toRecycleBin) + fo.fFlags |= FOF_ALLOWUNDO; + // fo.fFlags |= FOF_NOCONFIRMATION; + // fo.fFlags |= FOF_NOERRORUI; + // fo.fFlags |= FOF_SILENT; + // fo.fFlags |= FOF_WANTNUKEWARNING; + fo.fAnyOperationsAborted = FALSE; + fo.hNameMappings = NULL; + fo.lpszProgressTitle = NULL; + /* int res = */ ::SHFileOperationA(&fo); + } + else + #endif + { + CDynamicBuffer buffer; + unsigned maxLen = 0; + const UString prefix = GetFsPath(); + FOR_VECTOR (i, indices) + { + // L"\\\\?\\") doesn't work here. + const UString path = prefix + GetItemRelPath2(indices[i]); + if (path.Len() > maxLen) + maxLen = path.Len(); + buffer.AddData(path, path.Len() + 1); + } + *buffer.GetCurPtrAndGrow(1) = 0; + if (maxLen >= MAX_PATH) + { + if (toRecycleBin) + { + MessageBox_Error_LangID(IDS_ERROR_LONG_PATH_TO_RECYCLE); + return; + } + useInternalDelete = true; + } + else + { + SHFILEOPSTRUCTW fo; + fo.hwnd = GetParent(); + fo.wFunc = FO_DELETE; + fo.pFrom = (const WCHAR *)buffer; + fo.pTo = NULL; + fo.fFlags = 0; + if (toRecycleBin) + fo.fFlags |= FOF_ALLOWUNDO; + fo.fAnyOperationsAborted = FALSE; + fo.hNameMappings = NULL; + fo.lpszProgressTitle = NULL; + // int res; + #ifdef _UNICODE + /* res = */ ::SHFileOperationW(&fo); + #else +Z7_DIAGNOSTIC_IGNORE_CAST_FUNCTION + const + Func_SHFileOperationW + f_SHFileOperationW = Z7_GET_PROC_ADDRESS( + Func_SHFileOperationW, ::GetModuleHandleW(L"shell32.dll"), + "SHFileOperationW"); + if (!f_SHFileOperationW) + return; + /* res = */ f_SHFileOperationW(&fo); + #endif + } + } + /* + if (fo.fAnyOperationsAborted) + MessageBox_Error_HRESULT_Caption(result, LangString(IDS_ERROR_DELETING)); + */ + if (!useInternalDelete) + { + RefreshListCtrl(state); + return; + } + } + #endif + + // DeleteItemsInternal + + if (!CheckBeforeUpdate(IDS_ERROR_DELETING)) + return; + + UInt32 titleID, messageID; + UString messageParam; + if (indices.Size() == 1) + { + const unsigned index = indices[0]; + messageParam = GetItemRelPath2(index); + if (IsItem_Folder(index)) + { + titleID = IDS_CONFIRM_FOLDER_DELETE; + messageID = IDS_WANT_TO_DELETE_FOLDER; + } + else + { + titleID = IDS_CONFIRM_FILE_DELETE; + messageID = IDS_WANT_TO_DELETE_FILE; + } + } + else + { + titleID = IDS_CONFIRM_ITEMS_DELETE; + messageID = IDS_WANT_TO_DELETE_ITEMS; + messageParam = NumberToString(indices.Size()); + } + if (::MessageBoxW(GetParent(), MyFormatNew(messageID, messageParam), LangString(titleID), + MB_YESNOCANCEL | MB_ICONQUESTION) != IDYES) + return; + + CDisableNotify disableNotify(*this); + { + CThreadFolderOperations op(FOLDER_TYPE_DELETE); + op.FolderOperations = _folderOperations; + op.Indices = indices; + op.DoOperation(*this, + LangString(IDS_DELETING), + LangString(IDS_ERROR_DELETING)); + } + RefreshTitleAlways(); + RefreshListCtrl(state); +} + +BOOL CPanel::OnBeginLabelEdit(LV_DISPINFOW * lpnmh) +{ + const unsigned realIndex = GetRealIndex(lpnmh->item); + if (realIndex == kParentIndex) + return TRUE; + if (IsThereReadOnlyFolder()) + return TRUE; + return FALSE; +} + +static bool IsCorrectFsName(const UString &name) +{ + const UString lastPart = name.Ptr((unsigned)(name.ReverseFind_PathSepar() + 1)); + return + !lastPart.IsEqualTo(".") && + !lastPart.IsEqualTo(".."); +} + +bool CorrectFsPath(const UString &relBase, const UString &path, UString &result); + +bool CPanel::CorrectFsPath(const UString &path2, UString &result) +{ + return ::CorrectFsPath(GetFsPath(), path2, result); +} + +BOOL CPanel::OnEndLabelEdit(LV_DISPINFOW * lpnmh) +{ + if (lpnmh->item.pszText == NULL) + return FALSE; + CDisableTimerProcessing disableTimerProcessing2(*this); + + if (!CheckBeforeUpdate(IDS_ERROR_RENAMING)) + return FALSE; + + UString newName = lpnmh->item.pszText; + if (!IsCorrectFsName(newName)) + { + MessageBox_Error_HRESULT(E_INVALIDARG); + return FALSE; + } + + if (IsFSFolder()) + { + UString correctName; + if (!CorrectFsPath(newName, correctName)) + { + MessageBox_Error_HRESULT(E_INVALIDARG); + return FALSE; + } + newName = correctName; + } + + SaveSelectedState(_selectedState); + + const unsigned realIndex = GetRealIndex(lpnmh->item); + if (realIndex == kParentIndex) + return FALSE; + const UString prefix = GetItemPrefix(realIndex); + const UString oldName = GetItemName(realIndex); + + CDisableNotify disableNotify(*this); + { + CThreadFolderOperations op(FOLDER_TYPE_RENAME); + op.FolderOperations = _folderOperations; + op.Index = realIndex; + op.Name = newName; + const HRESULT res = op.DoOperation(*this, + LangString(IDS_RENAMING), + LangString(IDS_ERROR_RENAMING)); + // fixed in 9.26: we refresh list even after errors + // (it's more safe, since error can be at different stages, so list can be incorrect). + if (res == S_OK) + _selectedState.FocusedName = prefix + newName; + else + { + _selectedState.FocusedName = prefix + oldName; + // return FALSE; + } + } + + // Can't use RefreshListCtrl here. + // RefreshListCtrlSaveFocused(); + _selectedState.FocusedName_Defined = true; + _selectedState.SelectFocused = true; + + // We need clear all items to disable GetText before Reload: + // number of items can change. + // DeleteListItems(); + // But seems it can still call GetText (maybe for current item) + // so we can't delete items. + + _dontShowMode = true; + + PostMsg(kReLoadMessage); + return TRUE; +} + +bool Dlg_CreateFolder(HWND wnd, UString &destName); + +void CPanel::CreateFolder() +{ + if (IsHashFolder()) + return; + + if (!CheckBeforeUpdate(IDS_CREATE_FOLDER_ERROR)) + return; + + CDisableTimerProcessing disableTimerProcessing2(*this); + CSelectedState state; + SaveSelectedState(state); + + UString newName; + if (!Dlg_CreateFolder(GetParent(), newName)) + return; + + if (!IsCorrectFsName(newName)) + { + MessageBox_Error_HRESULT(E_INVALIDARG); + return; + } + + if (IsFSFolder()) + { + UString correctName; + if (!CorrectFsPath(newName, correctName)) + { + MessageBox_Error_HRESULT(E_INVALIDARG); + return; + } + newName = correctName; + } + + HRESULT res; + CDisableNotify disableNotify(*this); + { + CThreadFolderOperations op(FOLDER_TYPE_CREATE_FOLDER); + op.FolderOperations = _folderOperations; + op.Name = newName; + res = op.DoOperation(*this, + LangString(IDS_CREATE_FOLDER), + LangString(IDS_CREATE_FOLDER_ERROR)); + /* + // fixed for 9.26: we must refresh always + if (res != S_OK) + return; + */ + } + if (res == S_OK) + { + int pos = newName.Find(WCHAR_PATH_SEPARATOR); + if (pos >= 0) + newName.DeleteFrom((unsigned)(pos)); + if (!_mySelectMode) + state.SelectedNames.Clear(); + state.FocusedName = newName; + state.FocusedName_Defined = true; + state.SelectFocused = true; + } + RefreshTitleAlways(); + RefreshListCtrl(state); +} + +void CPanel::CreateFile() +{ + if (IsHashFolder()) + return; + + if (!CheckBeforeUpdate(IDS_CREATE_FILE_ERROR)) + return; + + CDisableTimerProcessing disableTimerProcessing2(*this); + CSelectedState state; + SaveSelectedState(state); + CComboDialog dlg; + LangString(IDS_CREATE_FILE, dlg.Title); + LangString(IDS_CREATE_FILE_NAME, dlg.Static); + LangString(IDS_CREATE_FILE_DEFAULT_NAME, dlg.Value); + + if (dlg.Create(GetParent()) != IDOK) + return; + + CDisableNotify disableNotify(*this); + + UString newName = dlg.Value; + + if (IsFSFolder()) + { + UString correctName; + if (!CorrectFsPath(newName, correctName)) + { + MessageBox_Error_HRESULT(E_INVALIDARG); + return; + } + newName = correctName; + } + + const HRESULT result = _folderOperations->CreateFile(newName, NULL); + if (result != S_OK) + { + MessageBox_Error_HRESULT_Caption(result, LangString(IDS_CREATE_FILE_ERROR)); + // MessageBoxErrorForUpdate(result, IDS_CREATE_FILE_ERROR); + return; + } + const int pos = newName.Find(WCHAR_PATH_SEPARATOR); + if (pos >= 0) + newName.DeleteFrom((unsigned)pos); + if (!_mySelectMode) + state.SelectedNames.Clear(); + state.FocusedName = newName; + state.FocusedName_Defined = true; + state.SelectFocused = true; + RefreshListCtrl(state); +} + +void CPanel::RenameFile() +{ + if (!CheckBeforeUpdate(IDS_ERROR_RENAMING)) + return; + int index = _listView.GetFocusedItem(); + if (index >= 0) + _listView.EditLabel(index); +} + +void CPanel::ChangeComment() +{ + if (IsHashFolder()) + return; + if (!CheckBeforeUpdate(IDS_COMMENT)) + return; + CDisableTimerProcessing disableTimerProcessing2(*this); + const int index = _listView.GetFocusedItem(); + if (index < 0) + return; + const unsigned realIndex = GetRealItemIndex(index); + if (realIndex == kParentIndex) + return; + CSelectedState state; + SaveSelectedState(state); + UString comment; + { + NCOM::CPropVariant propVariant; + if (_folder->GetProperty(realIndex, kpidComment, &propVariant) != S_OK) + return; + if (propVariant.vt == VT_BSTR) + comment = propVariant.bstrVal; + else if (propVariant.vt != VT_EMPTY) + return; + } + const UString name = GetItemRelPath2(realIndex); + CComboDialog dlg; + dlg.Title = name; + dlg.Title += " : "; + AddLangString(dlg.Title, IDS_COMMENT); + dlg.Value = comment; + LangString(IDS_COMMENT2, dlg.Static); + if (dlg.Create(GetParent()) != IDOK) + return; + NCOM::CPropVariant propVariant (dlg.Value); + + CDisableNotify disableNotify(*this); + const HRESULT result = _folderOperations->SetProperty(realIndex, kpidComment, &propVariant, NULL); + if (result != S_OK) + { + if (result == E_NOINTERFACE) + MessageBox_Error_UnsupportOperation(); + else + MessageBox_Error_HRESULT_Caption(result, L"Set Comment Error"); + } + RefreshListCtrl(state); +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PanelSelect.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PanelSelect.cpp new file mode 100644 index 00000000..f7a16dd5 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PanelSelect.cpp @@ -0,0 +1,317 @@ +// PanelSelect.cpp + +#include "StdAfx.h" + +#include "resource.h" + +#include "../../../Common/StringConvert.h" +#include "../../../Common/Wildcard.h" + +#include "ComboDialog.h" +#include "LangUtils.h" +#include "Panel.h" + +void CPanel::OnShiftSelectMessage() +{ + if (!_mySelectMode) + return; + const int focusedItem = _listView.GetFocusedItem(); + if (focusedItem < 0) + return; + if (!_selectionIsDefined) + return; + int startItem = MyMin(focusedItem, _prevFocusedItem); + int finishItem = MyMax(focusedItem, _prevFocusedItem); + + int numItems = _listView.GetItemCount(); + for (int i = 0; i < numItems; i++) + { + const unsigned realIndex = GetRealItemIndex(i); + if (realIndex == kParentIndex) + continue; + if (i >= startItem && i <= finishItem) + if (_selectedStatusVector[realIndex] != _selectMark) + { + _selectedStatusVector[realIndex] = _selectMark; + _listView.RedrawItem(i); + } + } + + _prevFocusedItem = focusedItem; +} + +void CPanel::OnArrowWithShift() +{ + if (!_mySelectMode) + return; + const int focusedItem = _listView.GetFocusedItem(); + if (focusedItem < 0) + return; + const unsigned realIndex = GetRealItemIndex(focusedItem); + + if (_selectionIsDefined) + { + if (realIndex != kParentIndex) + _selectedStatusVector[realIndex] = _selectMark; + } + else + { + if (realIndex == kParentIndex) + { + _selectionIsDefined = true; + _selectMark = true; + } + else + { + _selectionIsDefined = true; + _selectMark = !_selectedStatusVector[realIndex]; + _selectedStatusVector[realIndex] = _selectMark; + } + } + + _prevFocusedItem = focusedItem; + PostMsg(kShiftSelectMessage); + _listView.RedrawItem(focusedItem); +} + +void CPanel::OnInsert() +{ + /* + const int kState = CDIS_MARKED; // LVIS_DROPHILITED; + UINT state = (_listView.GetItemState(focusedItem, LVIS_CUT) == 0) ? + LVIS_CUT : 0; + _listView.SetItemState(focusedItem, state, LVIS_CUT); + // _listView.SetItemState_Selected(focusedItem); + */ + + const int focusedItem = _listView.GetFocusedItem(); + if (focusedItem < 0) + return; + + const unsigned realIndex = GetRealItemIndex(focusedItem); + if (realIndex != kParentIndex) + { + bool isSelected = !_selectedStatusVector[realIndex]; + _selectedStatusVector[realIndex] = isSelected; + if (!_mySelectMode) + _listView.SetItemState_Selected(focusedItem, isSelected); + _listView.RedrawItem(focusedItem); + } + + int nextIndex = focusedItem + 1; + if (nextIndex < _listView.GetItemCount()) + { + _listView.SetItemState_FocusedSelected(nextIndex); + _listView.EnsureVisible(nextIndex, false); + } +} + +/* +void CPanel::OnUpWithShift() +{ + const int focusedItem = _listView.GetFocusedItem(); + if (focusedItem < 0) + return; + const int index = GetRealItemIndex(focusedItem); + if (index == kParentIndex) + return; + _selectedStatusVector[index] = !_selectedStatusVector[index]; + _listView.RedrawItem(index); +} + +void CPanel::OnDownWithShift() +{ + const int focusedItem = _listView.GetFocusedItem(); + if (focusedItem < 0) + return; + const int index = GetRealItemIndex(focusedItem); + if (index == kParentIndex) + return; + _selectedStatusVector[index] = !_selectedStatusVector[index]; + _listView.RedrawItem(index); +} +*/ + +void CPanel::UpdateSelection() +{ + if (!_mySelectMode) + { + bool enableTemp = _enableItemChangeNotify; + _enableItemChangeNotify = false; + int numItems = _listView.GetItemCount(); + for (int i = 0; i < numItems; i++) + { + const unsigned realIndex = GetRealItemIndex(i); + if (realIndex != kParentIndex) + _listView.SetItemState_Selected(i, _selectedStatusVector[realIndex]); + } + _enableItemChangeNotify = enableTemp; + } + _listView.RedrawAllItems(); +} + + +void CPanel::SelectSpec(bool selectMode) +{ + CComboDialog dlg; + LangString(selectMode ? IDS_SELECT : IDS_DESELECT, dlg.Title ); + LangString(IDS_SELECT_MASK, dlg.Static); + dlg.Value = '*'; + if (dlg.Create(GetParent()) != IDOK) + return; + const UString &mask = dlg.Value; + FOR_VECTOR (i, _selectedStatusVector) + if (DoesWildcardMatchName(mask, GetItemName(i))) + _selectedStatusVector[i] = selectMode; + UpdateSelection(); +} + +void CPanel::SelectByType(bool selectMode) +{ + const int focusedItem = _listView.GetFocusedItem(); + if (focusedItem < 0) + return; + const unsigned realIndex = GetRealItemIndex(focusedItem); + UString name = GetItemName(realIndex); + bool isItemFolder = IsItem_Folder(realIndex); + + if (isItemFolder) + { + FOR_VECTOR (i, _selectedStatusVector) + if (IsItem_Folder(i) == isItemFolder) + _selectedStatusVector[i] = selectMode; + } + else + { + int pos = name.ReverseFind_Dot(); + if (pos < 0) + { + FOR_VECTOR (i, _selectedStatusVector) + if (IsItem_Folder(i) == isItemFolder && GetItemName(i).ReverseFind_Dot() < 0) + _selectedStatusVector[i] = selectMode; + } + else + { + UString mask ('*'); + mask += name.Ptr((unsigned)pos); + FOR_VECTOR (i, _selectedStatusVector) + if (IsItem_Folder(i) == isItemFolder && DoesWildcardMatchName(mask, GetItemName(i))) + _selectedStatusVector[i] = selectMode; + } + } + + UpdateSelection(); +} + +void CPanel::SelectAll(bool selectMode) +{ + FOR_VECTOR (i, _selectedStatusVector) + _selectedStatusVector[i] = selectMode; + UpdateSelection(); +} + +void CPanel::InvertSelection() +{ + if (!_mySelectMode) + { + /* + unsigned numSelected = 0; + FOR_VECTOR (i, _selectedStatusVector) + if (_selectedStatusVector[i]) + numSelected++; + */ + // 17.02: fixed : now we invert item even, if single item is selected + /* + if (numSelected == 1) + { + int focused = _listView.GetFocusedItem(); + if (focused >= 0) + { + const unsigned realIndex = GetRealItemIndex(focused); + if (realIndex >= 0) + if (_selectedStatusVector[realIndex]) + _selectedStatusVector[realIndex] = false; + } + } + */ + } + FOR_VECTOR (i, _selectedStatusVector) + _selectedStatusVector[i] = !_selectedStatusVector[i]; + UpdateSelection(); +} + +void CPanel::KillSelection() +{ + SelectAll(false); + // ver 20.01: now we don't like that focused will be selected item. + // So the following code was disabled: + /* + if (!_mySelectMode) + { + int focused = _listView.GetFocusedItem(); + if (focused >= 0) + { + // CPanel::OnItemChanged notify for LVIS_SELECTED change doesn't work here. Why? + // so we change _selectedStatusVector[realIndex] here. + const unsigned realIndex = GetRealItemIndex(focused); + if (realIndex != kParentIndex) + _selectedStatusVector[realIndex] = true; + _listView.SetItemState_Selected(focused); + } + } + */ +} + +void CPanel::OnLeftClick(MY_NMLISTVIEW_NMITEMACTIVATE *itemActivate) +{ + if (itemActivate->hdr.hwndFrom != HWND(_listView)) + return; + // It will work only for Version 4.71 (IE 4); + int indexInList = itemActivate->iItem; + if (indexInList < 0) + return; + + #ifndef UNDER_CE + if ((itemActivate->uKeyFlags & LVKF_SHIFT) != 0) + { + // int focusedIndex = _listView.GetFocusedItem(); + const int focusedIndex = _startGroupSelect; + if (focusedIndex < 0) + return; + const int startItem = MyMin(focusedIndex, indexInList); + const int finishItem = MyMax(focusedIndex, indexInList); + + const int numItems = _listView.GetItemCount(); + for (int i = 0; i < numItems; i++) + { + const unsigned realIndex = GetRealItemIndex(i); + if (realIndex == kParentIndex) + continue; + const bool selected = (i >= startItem && i <= finishItem); + if (_selectedStatusVector[realIndex] != selected) + { + _selectedStatusVector[realIndex] = selected; + _listView.RedrawItem(i); + } + } + } + else + #endif + { + _startGroupSelect = indexInList; + + #ifndef UNDER_CE + if ((itemActivate->uKeyFlags & LVKF_CONTROL) != 0) + { + const unsigned realIndex = GetRealItemIndex(indexInList); + if (realIndex != kParentIndex) + { + _selectedStatusVector[realIndex] = !_selectedStatusVector[realIndex]; + _listView.RedrawItem(indexInList); + } + } + #endif + } + + return; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PanelSort.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PanelSort.cpp new file mode 100644 index 00000000..57ac8776 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PanelSort.cpp @@ -0,0 +1,288 @@ +// PanelSort.cpp + +#include "StdAfx.h" + +#include "../../../../C/CpuArch.h" +#include "../../../Windows/PropVariant.h" + +#include "../../PropID.h" + +#include "Panel.h" + +using namespace NWindows; + +int CompareFileNames_ForFolderList(const wchar_t *s1, const wchar_t *s2) +{ + for (;;) + { + wchar_t c1 = *s1; + wchar_t c2 = *s2; + if ((c1 >= '0' && c1 <= '9') && + (c2 >= '0' && c2 <= '9')) + { + for (; *s1 == '0'; s1++); + for (; *s2 == '0'; s2++); + size_t len1 = 0; + size_t len2 = 0; + for (; (s1[len1] >= '0' && s1[len1] <= '9'); len1++); + for (; (s2[len2] >= '0' && s2[len2] <= '9'); len2++); + if (len1 < len2) return -1; + if (len1 > len2) return 1; + for (; len1 > 0; s1++, s2++, len1--) + { + if (*s1 == *s2) continue; + return (*s1 < *s2) ? -1 : 1; + } + c1 = *s1; + c2 = *s2; + } + s1++; + s2++; + if (c1 != c2) + { + // Probably we need to change the order for special characters like in Explorer. + wchar_t u1 = MyCharUpper(c1); + wchar_t u2 = MyCharUpper(c2); + if (u1 < u2) return -1; + if (u1 > u2) return 1; + } + if (c1 == 0) return 0; + } +} + +static int CompareFileNames_Le16(const Byte *s1, unsigned size1, const Byte *s2, unsigned size2) +{ + size1 &= ~1u; + size2 &= ~1u; + for (unsigned i = 0;; i += 2) + { + if (i >= size1) + return (i >= size2) ? 0 : -1; + if (i >= size2) + return 1; + UInt16 c1 = GetUi16(s1 + i); + UInt16 c2 = GetUi16(s2 + i); + if (c1 == c2) + { + if (c1 == 0) + return 0; + continue; + } + if (c1 < c2) + return -1; + return 1; + } +} + +static inline const wchar_t *GetExtensionPtr(const UString &name) +{ + const int dotPos = name.ReverseFind_Dot(); + return name.Ptr(dotPos < 0 ? name.Len() : (unsigned)dotPos); +} + +void CPanel::SetSortRawStatus() +{ + _isRawSortProp = 0; // false; + FOR_VECTOR (i, _columns) + { + const CPropColumn &prop = _columns[i]; + if (prop.ID == _sortID) + { + _isRawSortProp = prop.IsRawProp ? 1 : 0; + return; + } + } +} + + +static int CALLBACK CompareItems2(const LPARAM lParam1, const LPARAM lParam2, + const CPanel * const panel, const PROPID propID, const Int32 isRawProp) +{ + if (propID == kpidNoProperty) + return MyCompare(lParam1, lParam2); + + if (isRawProp) + { + // Sha1, Checksum, NtSecurity, NtReparse + const void *data1; + const void *data2; + UInt32 dataSize1; + UInt32 dataSize2; + UInt32 propType1; + UInt32 propType2; + if (panel->_folderRawProps->GetRawProp((UInt32)lParam1, propID, &data1, &dataSize1, &propType1) != 0) return 0; + if (panel->_folderRawProps->GetRawProp((UInt32)lParam2, propID, &data2, &dataSize2, &propType2) != 0) return 0; + if (dataSize1 == 0) + return (dataSize2 == 0) ? 0 : -1; + if (dataSize2 == 0) + return 1; + if (propType1 != NPropDataType::kRaw) return 0; + if (propType2 != NPropDataType::kRaw) return 0; + if (propID == kpidNtReparse) + { + NFile::CReparseShortInfo r1; r1.Parse((const Byte *)data1, dataSize1); + NFile::CReparseShortInfo r2; r2.Parse((const Byte *)data2, dataSize2); + return CompareFileNames_Le16( + (const Byte *)data1 + r1.Offset, r1.Size, + (const Byte *)data2 + r2.Offset, r2.Size); + } + } + + if (panel->_folderCompare) + return panel->_folderCompare->CompareItems((UInt32)lParam1, (UInt32)lParam2, propID, isRawProp); + + switch (propID) + { + // if (panel->_sortIndex == 0) + case kpidName: + { + const UString name1 = panel->GetItemName((unsigned)lParam1); + const UString name2 = panel->GetItemName((unsigned)lParam2); + const int res = CompareFileNames_ForFolderList(name1, name2); + /* + if (res != 0 || !panel->_flatMode) + return res; + const UString prefix1 = panel->GetItemPrefix(lParam1); + const UString prefix2 = panel->GetItemPrefix(lParam2); + return res = CompareFileNames_ForFolderList(prefix1, prefix2); + */ + return res; + } + case kpidExtension: + { + const UString name1 = panel->GetItemName((unsigned)lParam1); + const UString name2 = panel->GetItemName((unsigned)lParam2); + return CompareFileNames_ForFolderList( + GetExtensionPtr(name1), + GetExtensionPtr(name2)); + } + } + /* + if (panel->_sortIndex == 1) + return MyCompare(file1.Size, file2.Size); + return ::CompareFileTime(&file1.MTime, &file2.MTime); + */ + + // PROPID propID = panel->_columns[panel->_sortIndex].ID; + + NCOM::CPropVariant prop1, prop2; + // Name must be first property + panel->_folder->GetProperty((UInt32)lParam1, propID, &prop1); + panel->_folder->GetProperty((UInt32)lParam2, propID, &prop2); + if (prop1.vt != prop2.vt) + return MyCompare(prop1.vt, prop2.vt); + if (prop1.vt == VT_BSTR) + return MyStringCompareNoCase(prop1.bstrVal, prop2.bstrVal); + return prop1.Compare(prop2); +} + +int CALLBACK CompareItems(LPARAM lParam1, LPARAM lParam2, LPARAM lpData); +int CALLBACK CompareItems(LPARAM lParam1, LPARAM lParam2, LPARAM lpData) +{ + if (lpData == 0) return 0; + if (lParam1 == (int)kParentIndex) return -1; + if (lParam2 == (int)kParentIndex) return 1; + + const CPanel *panel = (CPanel*)lpData; + + const bool isDir1 = panel->IsItem_Folder((unsigned)lParam1); + const bool isDir2 = panel->IsItem_Folder((unsigned)lParam2); + if (isDir1 != isDir2) + return isDir1 ? -1 : 1; + + /* + we have up to 3 iterations: + 1: prop, + 2: kpidName, kpidPrefix + 3: prop, kpidName, kpidPrefix + 3: kpidPrefix, kpidName, kpidPrefix : is some rare case + */ + PROPID propID = panel->_sortID; + int res = 0; + for (unsigned iter = 0; iter < 3; iter++) + { + res = CompareItems2(lParam1, lParam2, panel, propID, + iter ? 0 : panel->_isRawSortProp); + if (res) + break; + if (propID == kpidName) + { + // if (!_flatMode.IsEmpty()) break; // !_flatMode ; + propID = kpidPrefix; + continue; + } + if (iter) + break; + propID = kpidName; + } + if (res == 0) + res = MyCompare(lParam1, lParam2); // order of LoadSubItems() + return panel->_ascending ? res: -res; +} + + +/* +void CPanel::SortItems(int index) +{ + if (index == _sortIndex) + _ascending = !_ascending; + else + { + _sortIndex = index; + _ascending = true; + switch (_columns[_sortIndex].ID) + { + case kpidSize: + case kpidPackedSize: + case kpidCTime: + case kpidATime: + case kpidMTime: + _ascending = false; + break; + } + } + _listView.SortItems(CompareItems, (LPARAM)this); + _listView.EnsureVisible(_listView.GetFocusedItem(), false); +} + +void CPanel::SortItemsWithPropID(PROPID propID) +{ + int index = _columns.FindItem_for_PropID(propID); + if (index >= 0) + SortItems(index); +} +*/ + +void CPanel::SortItemsWithPropID(PROPID propID) +{ + if (propID == _sortID) + _ascending = !_ascending; + else + { + _sortID = propID; + _ascending = true; + switch (propID) + { + case kpidSize: + case kpidPackSize: + case kpidCTime: + case kpidATime: + case kpidMTime: + _ascending = false; + break; + } + } + SetSortRawStatus(); + _listView.SortItems(CompareItems, (LPARAM)this); + _listView.EnsureVisible(_listView.GetFocusedItem(), false); +} + + +void CPanel::OnColumnClick(LPNMLISTVIEW info) +{ + /* + int index = _columns.FindItem_for_PropID(_visibleColumns[info->iSubItem].ID); + SortItems(index); + */ + SortItemsWithPropID(_visibleColumns[info->iSubItem].ID); +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PanelSplitFile.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PanelSplitFile.cpp new file mode 100644 index 00000000..c452d41c --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PanelSplitFile.cpp @@ -0,0 +1,562 @@ +// PanelSplitFile.cpp + +#include "StdAfx.h" + +#include "../../../Common/IntToString.h" + +#include "../../../Windows/ErrorMsg.h" +#include "../../../Windows/FileName.h" + +#include "../GUI/ExtractRes.h" + +#include "resource.h" + +#include "App.h" +#include "CopyDialog.h" +#include "FormatUtils.h" +#include "LangUtils.h" +#include "SplitDialog.h" +#include "SplitUtils.h" + +#include "PropertyNameRes.h" + +using namespace NWindows; +using namespace NFile; +using namespace NDir; + +static const char * const g_Message_FileWriteError = "File write error"; + +struct CVolSeqName +{ + UString UnchangedPart; + UString ChangedPart; + CVolSeqName(): ChangedPart("000") {} + + void SetNumDigits(UInt64 numVolumes) + { + ChangedPart = "000"; + while (numVolumes > 999) + { + numVolumes /= 10; + ChangedPart.Add_Char('0'); + } + } + + bool ParseName(const UString &name) + { + if (name.Len() < 2) + return false; + if (name.Back() != L'1' || name[name.Len() - 2] != L'0') + return false; + + unsigned pos = name.Len() - 2; + for (; pos > 0 && name[pos - 1] == '0'; pos--); + UnchangedPart.SetFrom(name, pos); + ChangedPart = name.Ptr(pos); + return true; + } + + UString GetNextName(); +}; + + +UString CVolSeqName::GetNextName() +{ + for (int i = (int)ChangedPart.Len() - 1; i >= 0; i--) + { + const wchar_t c = ChangedPart[i]; + if (c != L'9') + { + ChangedPart.ReplaceOneCharAtPos((unsigned)i, (wchar_t)(c + 1)); + break; + } + ChangedPart.ReplaceOneCharAtPos((unsigned)i, L'0'); + if (i == 0) + ChangedPart.InsertAtFront(L'1'); + } + return UnchangedPart + ChangedPart; +} + +class CThreadSplit: public CProgressThreadVirt +{ + HRESULT ProcessVirt() Z7_override; +public: + FString FilePath; + FString VolBasePath; + UInt64 NumVolumes; + CRecordVector VolumeSizes; +}; + + +class CPreAllocOutFile +{ + UInt64 _preAllocSize; +public: + NIO::COutFile File; + UInt64 Written; + + CPreAllocOutFile(): _preAllocSize(0), Written(0) {} + + ~CPreAllocOutFile() + { + SetCorrectFileLength(); + } + + void PreAlloc(UInt64 preAllocSize) + { + _preAllocSize = 0; + if (File.SetLength(preAllocSize)) + _preAllocSize = preAllocSize; + File.SeekToBegin(); + } + + bool Write(const void *data, UInt32 size, UInt32 &processedSize) throw() + { + bool res = File.Write(data, size, processedSize); + Written += processedSize; + return res; + } + + void Close() + { + SetCorrectFileLength(); + Written = 0; + _preAllocSize = 0; + File.Close(); + } + + void SetCorrectFileLength() + { + if (Written < _preAllocSize) + { + File.SetLength(Written); + _preAllocSize = 0; + } + } +}; + + +static const UInt32 kBufSize = (1 << 20); + +HRESULT CThreadSplit::ProcessVirt() +{ + NIO::CInFile inFile; + if (!inFile.Open(FilePath)) + return GetLastError_noZero_HRESULT(); + + CPreAllocOutFile outFile; + + CMyBuffer buffer; + if (!buffer.Allocate(kBufSize)) + return E_OUTOFMEMORY; + + CVolSeqName seqName; + seqName.SetNumDigits(NumVolumes); + + UInt64 length; + if (!inFile.GetLength(length)) + return GetLastError_noZero_HRESULT(); + + CProgressSync &sync = Sync; + sync.Set_NumBytesTotal(length); + + UInt64 pos = 0; + UInt64 prev = 0; + UInt64 numFiles = 0; + unsigned volIndex = 0; + + for (;;) + { + UInt64 volSize; + if (volIndex < VolumeSizes.Size()) + volSize = VolumeSizes[volIndex]; + else + volSize = VolumeSizes.Back(); + + UInt32 needSize = kBufSize; + { + const UInt64 rem = volSize - outFile.Written; + if (needSize > rem) + needSize = (UInt32)rem; + } + UInt32 processedSize; + if (!inFile.Read(buffer, needSize, processedSize)) + return GetLastError_noZero_HRESULT(); + if (processedSize == 0) + return S_OK; + needSize = processedSize; + + if (outFile.Written == 0) + { + FString name = VolBasePath; + name.Add_Dot(); + name += us2fs(seqName.GetNextName()); + sync.Set_FilePath(fs2us(name)); + if (!outFile.File.Create_NEW(name)) + { + const HRESULT res = GetLastError_noZero_HRESULT(); + AddErrorPath(name); + return res; + } + UInt64 expectSize = volSize; + if (pos < length) + { + const UInt64 rem = length - pos; + if (expectSize > rem) + expectSize = rem; + } + outFile.PreAlloc(expectSize); + } + + if (!outFile.Write(buffer, needSize, processedSize)) + return GetLastError_noZero_HRESULT(); + if (needSize != processedSize) + throw g_Message_FileWriteError; + + pos += processedSize; + + if (outFile.Written == volSize) + { + outFile.Close(); + sync.Set_NumFilesCur(++numFiles); + if (volIndex < VolumeSizes.Size()) + volIndex++; + } + + if (pos - prev >= ((UInt32)1 << 22) || outFile.Written == 0) + { + RINOK(sync.Set_NumBytesCur(pos)) + prev = pos; + } + } +} + + +void CApp::Split() +{ + const unsigned srcPanelIndex = GetFocusedPanelIndex(); + CPanel &srcPanel = Panels[srcPanelIndex]; + if (!srcPanel.Is_IO_FS_Folder()) + { + srcPanel.MessageBox_Error_UnsupportOperation(); + return; + } + CRecordVector indices; + srcPanel.Get_ItemIndices_Operated(indices); + if (indices.IsEmpty()) + return; + if (indices.Size() != 1) + { + srcPanel.MessageBox_Error_LangID(IDS_SELECT_ONE_FILE); + return; + } + const unsigned index = indices[0]; + if (srcPanel.IsItem_Folder(index)) + { + srcPanel.MessageBox_Error_LangID(IDS_SELECT_ONE_FILE); + return; + } + const UString itemName = srcPanel.GetItemName(index); + + const UString srcPath = srcPanel.GetFsPath() + srcPanel.GetItemPrefix(index); + UString path = srcPath; + unsigned destPanelIndex = (NumPanels <= 1) ? srcPanelIndex : (1 - srcPanelIndex); + CPanel &destPanel = Panels[destPanelIndex]; + if (NumPanels > 1) + if (destPanel.IsFSFolder()) + path = destPanel.GetFsPath(); + CSplitDialog splitDialog; + splitDialog.FilePath = srcPanel.GetItemRelPath(index); + splitDialog.Path = path; + if (splitDialog.Create(srcPanel.GetParent()) != IDOK) + return; + + NFind::CFileInfo fileInfo; + if (!fileInfo.Find(us2fs(srcPath + itemName))) + { + srcPanel.MessageBox_Error(L"Cannot find file"); + return; + } + if (fileInfo.Size <= splitDialog.VolumeSizes.FrontItem()) + { + srcPanel.MessageBox_Error_LangID(IDS_SPLIT_VOL_MUST_BE_SMALLER); + return; + } + const UInt64 numVolumes = GetNumberOfVolumes(fileInfo.Size, splitDialog.VolumeSizes); + if (numVolumes >= 100) + { + wchar_t s[32]; + ConvertUInt64ToString(numVolumes, s); + if (::MessageBoxW(srcPanel, MyFormatNew(IDS_SPLIT_CONFIRM_MESSAGE, s), + LangString(IDS_SPLIT_CONFIRM_TITLE), + MB_YESNOCANCEL | MB_ICONQUESTION) != IDYES) + return; + } + + path = splitDialog.Path; + NName::NormalizeDirPathPrefix(path); + if (!CreateComplexDir(us2fs(path))) + { + const HRESULT lastError = GetLastError_noZero_HRESULT(); + srcPanel.MessageBox_Error_2Lines_Message_HRESULT(MyFormatNew(IDS_CANNOT_CREATE_FOLDER, path), lastError); + return; + } + + { + CThreadSplit spliter; + spliter.NumVolumes = numVolumes; + + CProgressDialog &progressDialog = spliter; + + const UString progressWindowTitle ("7-Zip"); // LangString(IDS_APP_TITLE, 0x03000000); + const UString title = LangString(IDS_SPLITTING); + + progressDialog.ShowCompressionInfo = false; + + progressDialog.MainWindow = _window; + progressDialog.MainTitle = progressWindowTitle; + progressDialog.MainAddTitle = title; + progressDialog.MainAddTitle.Add_Space(); + progressDialog.Sync.Set_TitleFileName(itemName); + + + spliter.FilePath = us2fs(srcPath + itemName); + spliter.VolBasePath = us2fs(path + srcPanel.GetItemName_for_Copy(index)); + spliter.VolumeSizes = splitDialog.VolumeSizes; + + // if (splitDialog.VolumeSizes.Size() == 0) return; + + // CPanel::CDisableTimerProcessing disableTimerProcessing1(srcPanel); + // CPanel::CDisableTimerProcessing disableTimerProcessing2(destPanel); + + if (spliter.Create(title, _window) != 0) + return; + } + RefreshTitleAlways(); + + + // disableNotify.Restore(); + // disableNotify.Restore(); + // srcPanel.SetFocusToList(); + // srcPanel.RefreshListCtrlSaveFocused(); +} + + +class CThreadCombine: public CProgressThreadVirt +{ + HRESULT ProcessVirt() Z7_override; +public: + FString InputDirPrefix; + FStringVector Names; + FString OutputPath; + UInt64 TotalSize; +}; + +HRESULT CThreadCombine::ProcessVirt() +{ + NIO::COutFile outFile; + if (!outFile.Create_NEW(OutputPath)) + { + const HRESULT res = GetLastError_noZero_HRESULT(); + AddErrorPath(OutputPath); + return res; + } + + CProgressSync &sync = Sync; + sync.Set_NumBytesTotal(TotalSize); + + CMyBuffer bufferObject; + if (!bufferObject.Allocate(kBufSize)) + return E_OUTOFMEMORY; + Byte *buffer = (Byte *)(void *)bufferObject; + UInt64 pos = 0; + FOR_VECTOR (i, Names) + { + NIO::CInFile inFile; + const FString nextName = InputDirPrefix + Names[i]; + if (!inFile.Open(nextName)) + { + const HRESULT res = GetLastError_noZero_HRESULT(); + AddErrorPath(nextName); + return res; + } + sync.Set_FilePath(fs2us(nextName)); + for (;;) + { + UInt32 processedSize; + if (!inFile.Read(buffer, kBufSize, processedSize)) + { + const HRESULT res = GetLastError_noZero_HRESULT(); + AddErrorPath(nextName); + return res; + } + if (processedSize == 0) + break; + const UInt32 needSize = processedSize; + if (!outFile.Write(buffer, needSize, processedSize)) + { + const HRESULT res = GetLastError_noZero_HRESULT(); + AddErrorPath(OutputPath); + return res; + } + if (needSize != processedSize) + throw g_Message_FileWriteError; + pos += processedSize; + RINOK(sync.Set_NumBytesCur(pos)) + } + } + return S_OK; +} + +extern void AddValuePair2(UString &s, UINT resourceID, UInt64 num, UInt64 size); + +static void AddInfoFileName(UString &dest, const UString &name) +{ + dest += "\n "; + dest += name; +} + +void CApp::Combine() +{ + const unsigned srcPanelIndex = GetFocusedPanelIndex(); + CPanel &srcPanel = Panels[srcPanelIndex]; + if (!srcPanel.IsFSFolder()) + { + srcPanel.MessageBox_Error_LangID(IDS_OPERATION_IS_NOT_SUPPORTED); + return; + } + CRecordVector indices; + srcPanel.Get_ItemIndices_Operated(indices); + if (indices.IsEmpty()) + return; + const unsigned index = indices[0]; + if (indices.Size() != 1 || srcPanel.IsItem_Folder(index)) + { + srcPanel.MessageBox_Error_LangID(IDS_COMBINE_SELECT_ONE_FILE); + return; + } + const UString itemName = srcPanel.GetItemName(index); + + UString srcPath = srcPanel.GetFsPath() + srcPanel.GetItemPrefix(index); + UString path = srcPath; + unsigned destPanelIndex = (NumPanels <= 1) ? srcPanelIndex : (1 - srcPanelIndex); + CPanel &destPanel = Panels[destPanelIndex]; + if (NumPanels > 1) + if (destPanel.IsFSFolder()) + path = destPanel.GetFsPath(); + + CVolSeqName volSeqName; + if (!volSeqName.ParseName(itemName)) + { + srcPanel.MessageBox_Error_LangID(IDS_COMBINE_CANT_DETECT_SPLIT_FILE); + return; + } + + { + CThreadCombine combiner; + + UString nextName = itemName; + combiner.TotalSize = 0; + for (;;) + { + NFind::CFileInfo fileInfo; + if (!fileInfo.Find(us2fs(srcPath + nextName)) || fileInfo.IsDir()) + break; + combiner.Names.Add(us2fs(nextName)); + combiner.TotalSize += fileInfo.Size; + nextName = volSeqName.GetNextName(); + } + if (combiner.Names.Size() == 1) + { + srcPanel.MessageBox_Error_LangID(IDS_COMBINE_CANT_FIND_MORE_THAN_ONE_PART); + return; + } + + if (combiner.TotalSize == 0) + { + srcPanel.MessageBox_Error(L"No data"); + return; + } + + UString info; + AddValuePair2(info, IDS_PROP_FILES, combiner.Names.Size(), combiner.TotalSize); + + info.Add_LF(); + info += srcPath; + + unsigned i; + for (i = 0; i < combiner.Names.Size() && i < 2; i++) + AddInfoFileName(info, fs2us(combiner.Names[i])); + if (i != combiner.Names.Size()) + { + if (i + 1 != combiner.Names.Size()) + AddInfoFileName(info, L"..."); + AddInfoFileName(info, fs2us(combiner.Names.Back())); + } + + { + CCopyDialog copyDialog; + copyDialog.Value = path; + LangString(IDS_COMBINE, copyDialog.Title); + copyDialog.Title.Add_Space(); + copyDialog.Title += srcPanel.GetItemRelPath(index); + LangString(IDS_COMBINE_TO, copyDialog.Static); + copyDialog.Info = info; + if (copyDialog.Create(srcPanel.GetParent()) != IDOK) + return; + path = copyDialog.Value; + } + + NName::NormalizeDirPathPrefix(path); + if (!CreateComplexDir(us2fs(path))) + { + const HRESULT lastError = GetLastError_noZero_HRESULT(); + srcPanel.MessageBox_Error_2Lines_Message_HRESULT(MyFormatNew(IDS_CANNOT_CREATE_FOLDER, path), lastError); + return; + } + + UString outName = volSeqName.UnchangedPart; + while (!outName.IsEmpty()) + { + if (outName.Back() != L'.') + break; + outName.DeleteBack(); + } + if (outName.IsEmpty()) + outName = "file"; + + NFind::CFileInfo fileInfo; + UString destFilePath = path + outName; + combiner.OutputPath = us2fs(destFilePath); + if (fileInfo.Find(combiner.OutputPath)) + { + srcPanel.MessageBox_Error(MyFormatNew(IDS_FILE_EXIST, destFilePath)); + return; + } + + CProgressDialog &progressDialog = combiner; + progressDialog.ShowCompressionInfo = false; + + const UString progressWindowTitle ("7-Zip"); // LangString(IDS_APP_TITLE, 0x03000000); + const UString title = LangString(IDS_COMBINING); + + progressDialog.MainWindow = _window; + progressDialog.MainTitle = progressWindowTitle; + progressDialog.MainAddTitle = title; + progressDialog.MainAddTitle.Add_Space(); + + combiner.InputDirPrefix = us2fs(srcPath); + + // CPanel::CDisableTimerProcessing disableTimerProcessing1(srcPanel); + // CPanel::CDisableTimerProcessing disableTimerProcessing2(destPanel); + + if (combiner.Create(title, _window) != 0) + return; + } + RefreshTitleAlways(); + + // disableNotify.Restore(); + // disableNotify.Restore(); + // srcPanel.SetFocusToList(); + // srcPanel.RefreshListCtrlSaveFocused(); +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PasswordDialog.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PasswordDialog.cpp new file mode 100644 index 00000000..bf995802 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PasswordDialog.cpp @@ -0,0 +1,58 @@ +// PasswordDialog.cpp + +#include "StdAfx.h" + +#include "PasswordDialog.h" + +#ifdef Z7_LANG +#include "LangUtils.h" +#endif + +#ifdef Z7_LANG +static const UInt32 kLangIDs[] = +{ + IDT_PASSWORD_ENTER, + IDX_PASSWORD_SHOW +}; +#endif + +void CPasswordDialog::ReadControls() +{ + _passwordEdit.GetText(Password); + ShowPassword = IsButtonCheckedBool(IDX_PASSWORD_SHOW); +} + +void CPasswordDialog::SetTextSpec() +{ + _passwordEdit.SetPasswordChar(ShowPassword ? 0: TEXT('*')); + _passwordEdit.SetText(Password); +} + +bool CPasswordDialog::OnInit() +{ + #ifdef Z7_LANG + LangSetWindowText(*this, IDD_PASSWORD); + LangSetDlgItems(*this, kLangIDs, Z7_ARRAY_SIZE(kLangIDs)); + #endif + _passwordEdit.Attach(GetItem(IDE_PASSWORD_PASSWORD)); + CheckButton(IDX_PASSWORD_SHOW, ShowPassword); + SetTextSpec(); + return CModalDialog::OnInit(); +} + +bool CPasswordDialog::OnButtonClicked(unsigned buttonID, HWND buttonHWND) +{ + if (buttonID == IDX_PASSWORD_SHOW) + { + ReadControls(); + SetTextSpec(); + return true; + } + return CDialog::OnButtonClicked(buttonID, buttonHWND); +} + +void CPasswordDialog::OnOK() +{ + ReadControls(); + CModalDialog::OnOK(); +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PasswordDialog.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PasswordDialog.h new file mode 100644 index 00000000..e05c4adf --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PasswordDialog.h @@ -0,0 +1,28 @@ +// PasswordDialog.h + +#ifndef ZIP7_INC_PASSWORD_DIALOG_H +#define ZIP7_INC_PASSWORD_DIALOG_H + +#include "../../../Windows/Control/Dialog.h" +#include "../../../Windows/Control/Edit.h" + +#include "PasswordDialogRes.h" + +class CPasswordDialog: public NWindows::NControl::CModalDialog +{ + NWindows::NControl::CEdit _passwordEdit; + + virtual void OnOK() Z7_override; + virtual bool OnInit() Z7_override; + virtual bool OnButtonClicked(unsigned buttonID, HWND buttonHWND) Z7_override; + void SetTextSpec(); + void ReadControls(); +public: + UString Password; + bool ShowPassword; + + CPasswordDialog(): ShowPassword(false) {} + INT_PTR Create(HWND parentWindow = NULL) { return CModalDialog::Create(IDD_PASSWORD, parentWindow); } +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PasswordDialog.rc b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PasswordDialog.rc new file mode 100644 index 00000000..fa42f929 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PasswordDialog.rc @@ -0,0 +1,18 @@ +#include "PasswordDialogRes.h" +#include "../../GuiCommon.rc" + +#ifdef UNDER_CE +#define xc 140 +#else +#define xc 200 +#endif +#define yc 72 + +IDD_PASSWORD DIALOG 0, 0, xs, ys MY_MODAL_DIALOG_STYLE MY_FONT +CAPTION "Enter password" +BEGIN + LTEXT "&Enter password:", IDT_PASSWORD_ENTER, m, m, xc, 8 + EDITTEXT IDE_PASSWORD_PASSWORD, m, 20, xc, 14, ES_PASSWORD | ES_AUTOHSCROLL + CONTROL "&Show password", IDX_PASSWORD_SHOW, MY_CHECKBOX, m, 42, xc, 10 + OK_CANCEL +END diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PasswordDialogRes.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PasswordDialogRes.h new file mode 100644 index 00000000..1fe32e10 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PasswordDialogRes.h @@ -0,0 +1,5 @@ +#define IDD_PASSWORD 3800 +#define IDT_PASSWORD_ENTER 3801 +#define IDX_PASSWORD_SHOW 3803 + +#define IDE_PASSWORD_PASSWORD 120 diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PluginInterface.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PluginInterface.h new file mode 100644 index 00000000..dcb1b4b8 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PluginInterface.h @@ -0,0 +1,32 @@ +// PluginInterface.h + +#ifndef ZIP7_INC_PLUGIN_INTERFACE_H +#define ZIP7_INC_PLUGIN_INTERFACE_H + +/* +#include "../../../../C/7zTypes.h" +#include "../../IDecl.h" + +#define Z7_IFACE_CONSTR_PLUGIN(i, n) \ + Z7_DECL_IFACE_7ZIP(i, 0x0A, n) \ + { Z7_IFACE_COM7_PURE(i) }; + +#define Z7_IFACEM_IInitContextMenu(x) \ + x(InitContextMenu(const wchar_t *folder, const wchar_t * const *names, UInt32 numFiles)) \ + +Z7_IFACE_CONSTR_PLUGIN(IInitContextMenu, 0x00) + +#define Z7_IFACEM_IPluginOptionsCallback(x) \ + x(GetProgramFolderPath(BSTR *value)) \ + x(GetProgramPath(BSTR *value)) \ + x(GetRegistryCUPath(BSTR *value)) \ + +Z7_IFACE_CONSTR_PLUGIN(IPluginOptionsCallback, 0x01) + +#define Z7_IFACEM_IPluginOptions(x) \ + x(PluginOptions(HWND hWnd, IPluginOptionsCallback *callback)) \ + // x(GetFileExtensions(BSTR *extensions)) + +Z7_IFACE_CONSTR_PLUGIN(IPluginOptions, 0x02) +*/ +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PluginLoader.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PluginLoader.h new file mode 100644 index 00000000..476a7b74 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PluginLoader.h @@ -0,0 +1,33 @@ +// PluginLoader.h + +#ifndef ZIP7_INC_PLUGIN_LOADER_H +#define ZIP7_INC_PLUGIN_LOADER_H + +#include "../../../Windows/DLL.h" + +#include "IFolder.h" + +Z7_DIAGNOSTIC_IGNORE_CAST_FUNCTION + +class CPluginLibrary: public NWindows::NDLL::CLibrary +{ +public: + HRESULT CreateManager(REFGUID clsID, IFolderManager **manager) + { + const + Func_CreateObject createObject = Z7_GET_PROC_ADDRESS( + Func_CreateObject, Get_HMODULE(), + "CreateObject"); + if (!createObject) + return GetLastError_noZero_HRESULT(); + return createObject(&clsID, &IID_IFolderManager, (void **)manager); + } + HRESULT LoadAndCreateManager(CFSTR filePath, REFGUID clsID, IFolderManager **manager) + { + if (!Load(filePath)) + return GetLastError_noZero_HRESULT(); + return CreateManager(clsID, manager); + } +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ProgramLocation.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ProgramLocation.cpp new file mode 100644 index 00000000..50ca5ca5 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ProgramLocation.cpp @@ -0,0 +1,3 @@ +// ProgramLocation.cpp + +#include "StdAfx.h" diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ProgramLocation.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ProgramLocation.h new file mode 100644 index 00000000..0cd9c747 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ProgramLocation.h @@ -0,0 +1,6 @@ +// ProgramLocation.h + +#ifndef ZIP7_INC_PROGRAM_LOCATION_H +#define ZIP7_INC_PROGRAM_LOCATION_H + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ProgressDialog.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ProgressDialog.cpp new file mode 100644 index 00000000..fc6f5599 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ProgressDialog.cpp @@ -0,0 +1,201 @@ +// ProgressDialog.cpp + +#include "StdAfx.h" + +#include "../../../Common/IntToString.h" + +#include "resource.h" + +#include "ProgressDialog.h" + +using namespace NWindows; + +extern HINSTANCE g_hInstance; + +static const UINT_PTR kTimerID = 3; +static const UINT kTimerElapse = 100; + +#ifdef Z7_LANG +#include "LangUtils.h" +#endif + +HRESULT CProgressSync::ProcessStopAndPause() +{ + for (;;) + { + if (GetStopped()) + return E_ABORT; + if (!GetPaused()) + break; + ::Sleep(100); + } + return S_OK; +} + +#ifndef Z7_SFX +CProgressDialog::~CProgressDialog() +{ + AddToTitle(L""); +} +void CProgressDialog::AddToTitle(LPCWSTR s) +{ + if (MainWindow != 0) + MySetWindowText(MainWindow, UString(s) + MainTitle); +} +#endif + + +#define UNDEFINED_VAL ((UInt64)(Int64)-1) + +bool CProgressDialog::OnInit() +{ + _range = UNDEFINED_VAL; + _prevPercentValue = UNDEFINED_VAL; + + _wasCreated = true; + _dialogCreatedEvent.Set(); + + #ifdef Z7_LANG + LangSetDlgItems(*this, NULL, 0); + #endif + + m_ProgressBar.Attach(GetItem(IDC_PROGRESS1)); + + if (IconID >= 0) + { + HICON icon = LoadIcon(g_hInstance, MAKEINTRESOURCE(IconID)); + SetIcon(ICON_BIG, icon); + } + + _timer = SetTimer(kTimerID, kTimerElapse); + SetText(_title); + CheckNeedClose(); + return CModalDialog::OnInit(); +} + +void CProgressDialog::OnCancel() { Sync.SetStopped(true); } +void CProgressDialog::OnOK() { } + +void CProgressDialog::SetRange(UInt64 range) +{ + _range = range; + _peviousPos = (UInt64)(Int64)-1; + _converter.Init(range); + m_ProgressBar.SetRange32(0 , _converter.Count(range)); // Test it for 100% +} + +void CProgressDialog::SetPos(UInt64 pos) +{ + bool redraw = true; + if (pos < _range && pos > _peviousPos) + { + UInt64 posDelta = pos - _peviousPos; + if (posDelta < (_range >> 10)) + redraw = false; + } + if (redraw) + { + m_ProgressBar.SetPos(_converter.Count(pos)); // Test it for 100% + _peviousPos = pos; + } +} + +bool CProgressDialog::OnTimer(WPARAM /* timerID */, LPARAM /* callback */) +{ + if (Sync.GetPaused()) + return true; + + CheckNeedClose(); + + UInt64 total, completed; + Sync.GetProgress(total, completed); + if (total != _range) + SetRange(total); + SetPos(completed); + + if (total == 0) + total = 1; + + const UInt64 percentValue = completed * 100 / total; + if (percentValue != _prevPercentValue) + { + wchar_t s[64]; + ConvertUInt64ToString(percentValue, s); + UString title = s; + title += "% "; + SetText(title + _title); + #ifndef Z7_SFX + AddToTitle(title + MainAddTitle); + #endif + _prevPercentValue = percentValue; + } + return true; +} + +bool CProgressDialog::OnMessage(UINT message, WPARAM wParam, LPARAM lParam) +{ + switch (message) + { + case kCloseMessage: + { + if (_timer) + { + KillTimer(kTimerID); + _timer = 0; + } + if (_inCancelMessageBox) + { + _externalCloseMessageWasReceived = true; + break; + } + return OnExternalCloseMessage(); + } + /* + case WM_SETTEXT: + { + if (_timer == 0) + return true; + } + */ + } + return CModalDialog::OnMessage(message, wParam, lParam); +} + +bool CProgressDialog::OnButtonClicked(unsigned buttonID, HWND buttonHWND) +{ + switch (buttonID) + { + case IDCANCEL: + { + bool paused = Sync.GetPaused(); + Sync.SetPaused(true); + _inCancelMessageBox = true; + int res = ::MessageBoxW(*this, L"Are you sure you want to cancel?", _title, MB_YESNOCANCEL); + _inCancelMessageBox = false; + Sync.SetPaused(paused); + if (res == IDCANCEL || res == IDNO) + { + if (_externalCloseMessageWasReceived) + OnExternalCloseMessage(); + return true; + } + break; + } + } + return CModalDialog::OnButtonClicked(buttonID, buttonHWND); +} + +void CProgressDialog::CheckNeedClose() +{ + if (_needClose) + { + PostMsg(kCloseMessage); + _needClose = false; + } +} + +bool CProgressDialog::OnExternalCloseMessage() +{ + End(0); + return true; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ProgressDialog.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ProgressDialog.h new file mode 100644 index 00000000..1fe9587a --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ProgressDialog.h @@ -0,0 +1,171 @@ +// ProgressDialog.h + +#ifndef ZIP7_INC_PROGRESS_DIALOG_H +#define ZIP7_INC_PROGRESS_DIALOG_H + +#include "../../../Windows/Synchronization.h" +#include "../../../Windows/Thread.h" + +#include "../../../Windows/Control/Dialog.h" +#include "../../../Windows/Control/ProgressBar.h" + +#include "ProgressDialogRes.h" + +class CProgressSync +{ + NWindows::NSynchronization::CCriticalSection _cs; + bool _stopped; + bool _paused; + UInt64 _total; + UInt64 _completed; +public: + CProgressSync(): _stopped(false), _paused(false), _total(1), _completed(0) {} + + HRESULT ProcessStopAndPause(); + bool GetStopped() + { + NWindows::NSynchronization::CCriticalSectionLock lock(_cs); + return _stopped; + } + void SetStopped(bool value) + { + NWindows::NSynchronization::CCriticalSectionLock lock(_cs); + _stopped = value; + } + bool GetPaused() + { + NWindows::NSynchronization::CCriticalSectionLock lock(_cs); + return _paused; + } + void SetPaused(bool value) + { + NWindows::NSynchronization::CCriticalSectionLock lock(_cs); + _paused = value; + } + void SetProgress(UInt64 total, UInt64 completed) + { + NWindows::NSynchronization::CCriticalSectionLock lock(_cs); + _total = total; + _completed = completed; + } + void SetPos(UInt64 completed) + { + NWindows::NSynchronization::CCriticalSectionLock lock(_cs); + _completed = completed; + } + void GetProgress(UInt64 &total, UInt64 &completed) + { + NWindows::NSynchronization::CCriticalSectionLock lock(_cs); + total = _total; + completed = _completed; + } +}; + +class CU64ToI32Converter +{ + UInt64 _numShiftBits; +public: + void Init(UInt64 range) + { + // Windows CE doesn't like big number here. + for (_numShiftBits = 0; range > (1 << 15); _numShiftBits++) + range >>= 1; + } + int Count(UInt64 value) { return int(value >> _numShiftBits); } +}; + +class CProgressDialog: public NWindows::NControl::CModalDialog +{ +private: + UINT_PTR _timer; + + UString _title; + CU64ToI32Converter _converter; + UInt64 _peviousPos; + UInt64 _range; + NWindows::NControl::CProgressBar m_ProgressBar; + + UInt64 _prevPercentValue; + + bool _wasCreated; + bool _needClose; + bool _inCancelMessageBox; + bool _externalCloseMessageWasReceived; + + virtual bool OnButtonClicked(unsigned buttonID, HWND buttonHWND) Z7_override; + virtual bool OnTimer(WPARAM timerID, LPARAM callback) Z7_override; + virtual bool OnInit() Z7_override; + virtual void OnCancel() Z7_override; + virtual void OnOK() Z7_override; + virtual bool OnMessage(UINT message, WPARAM wParam, LPARAM lParam) Z7_override; + + void SetRange(UInt64 range); + void SetPos(UInt64 pos); + + NWindows::NSynchronization::CManualResetEvent _dialogCreatedEvent; + #ifndef Z7_SFX + void AddToTitle(LPCWSTR string); + #endif + + void WaitCreating() { _dialogCreatedEvent.Lock(); } + void CheckNeedClose(); + bool OnExternalCloseMessage(); +public: + CProgressSync Sync; + int IconID; + + #ifndef Z7_SFX + HWND MainWindow; + UString MainTitle; + UString MainAddTitle; + ~CProgressDialog(); + #endif + + CProgressDialog(): _timer(0) + #ifndef Z7_SFX + ,MainWindow(NULL) + #endif + { + IconID = -1; + _wasCreated = false; + _needClose = false; + _inCancelMessageBox = false; + _externalCloseMessageWasReceived = false; + + if (_dialogCreatedEvent.Create() != S_OK) + throw 1334987; + } + + INT_PTR Create(const UString &title, NWindows::CThread &thread, HWND wndParent = NULL) + { + _title = title; + INT_PTR res = CModalDialog::Create(IDD_PROGRESS, wndParent); + thread.Wait_Close(); + return res; + } + + enum + { + kCloseMessage = WM_APP + 1 + }; + + void ProcessWasFinished() + { + WaitCreating(); + if (_wasCreated) + PostMsg(kCloseMessage); + else + _needClose = true; + } +}; + + +class CProgressCloser +{ + CProgressDialog *_p; +public: + CProgressCloser(CProgressDialog &p) : _p(&p) {} + ~CProgressCloser() { _p->ProcessWasFinished(); } +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ProgressDialog.rc b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ProgressDialog.rc new file mode 100644 index 00000000..55d99233 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ProgressDialog.rc @@ -0,0 +1,12 @@ +#include "ProgressDialogRes.h" +#include "../../GuiCommon.rc" + +#define xc 172 +#define yc 44 + +IDD_PROGRESS DIALOG 0, 0, xs, ys MY_MODAL_DIALOG_STYLE MY_FONT +CAPTION "Progress" +BEGIN + PUSHBUTTON "Cancel", IDCANCEL, bx, by, bxs, bys + CONTROL "Progress1", IDC_PROGRESS1, "msctls_progress32", PBS_SMOOTH | WS_BORDER, m, m, xc, 14 +END diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ProgressDialog2.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ProgressDialog2.cpp new file mode 100644 index 00000000..a070a0a3 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ProgressDialog2.cpp @@ -0,0 +1,1483 @@ +// ProgressDialog2.cpp + +#include "StdAfx.h" + +#ifdef Z7_OLD_WIN_SDK +#include +#endif + +#include "../../../Common/IntToString.h" +#include "../../../Common/StringConvert.h" + +#include "../../../Windows/Clipboard.h" +#include "../../../Windows/ErrorMsg.h" + +#include "../GUI/ExtractRes.h" + +#include "LangUtils.h" + +#include "DialogSize.h" +#include "ProgressDialog2.h" +#include "ProgressDialog2Res.h" + +using namespace NWindows; + +extern HINSTANCE g_hInstance; +extern bool g_DisableUserQuestions; + +static const UINT_PTR kTimerID = 3; + +static const UINT kCloseMessage = WM_APP + 1; +// we can't use WM_USER, since WM_USER can be used by standard Windows procedure for Dialog + +static const UINT kTimerElapse = + #ifdef UNDER_CE + 500 + #else + 200 + #endif + ; + +static const UINT kCreateDelay = + #ifdef UNDER_CE + 2500 + #else + 500 + #endif + ; + +static const DWORD kPauseSleepTime = 100; + +#ifdef Z7_LANG + +static const UInt32 kLangIDs[] = +{ + IDT_PROGRESS_ELAPSED, + IDT_PROGRESS_REMAINING, + IDT_PROGRESS_TOTAL, + IDT_PROGRESS_SPEED, + IDT_PROGRESS_PROCESSED, + IDT_PROGRESS_RATIO, + IDT_PROGRESS_ERRORS, + IDB_PROGRESS_BACKGROUND, + IDB_PAUSE +}; + +static const UInt32 kLangIDs_Colon[] = +{ + IDT_PROGRESS_PACKED, + IDT_PROGRESS_FILES +}; + +#endif + + +#define UNDEFINED_VAL ((UInt64)(Int64)-1) +#define INIT_AS_UNDEFINED(v) v = UNDEFINED_VAL; +#define IS_UNDEFINED_VAL(v) ((v) == UNDEFINED_VAL) +#define IS_DEFINED_VAL(v) ((v) != UNDEFINED_VAL) + +CProgressSync::CProgressSync(): + _stopped(false), + _paused(false), + _filesProgressMode(false), + _isDir(false), + _totalBytes(UNDEFINED_VAL), _completedBytes(0), + _totalFiles(UNDEFINED_VAL), _curFiles(0), + _inSize(UNDEFINED_VAL), + _outSize(UNDEFINED_VAL) + {} + +#define CHECK_STOP if (_stopped) return E_ABORT; if (!_paused) return S_OK; +#define CRITICAL_LOCK NSynchronization::CCriticalSectionLock lock(_cs); + +bool CProgressSync::Get_Paused() +{ + CRITICAL_LOCK + return _paused; +} + +HRESULT CProgressSync::CheckStop() +{ + for (;;) + { + { + CRITICAL_LOCK + CHECK_STOP + } + ::Sleep(kPauseSleepTime); + } +} + +void CProgressSync::Clear_Stop_Status() +{ + CRITICAL_LOCK + if (_stopped) + _stopped = false; +} + +HRESULT CProgressSync::ScanProgress(UInt64 numFiles, UInt64 totalSize, const FString &fileName, bool isDir) +{ + { + CRITICAL_LOCK + _totalFiles = numFiles; + _totalBytes = totalSize; + _filePath = fs2us(fileName); + _isDir = isDir; + // _completedBytes = 0; + CHECK_STOP + } + return CheckStop(); +} + +HRESULT CProgressSync::Set_NumFilesTotal(UInt64 val) +{ + { + CRITICAL_LOCK + _totalFiles = val; + CHECK_STOP + } + return CheckStop(); +} + +void CProgressSync::Set_NumBytesTotal(UInt64 val) +{ + CRITICAL_LOCK + _totalBytes = val; +} + +void CProgressSync::Set_NumFilesCur(UInt64 val) +{ + CRITICAL_LOCK + _curFiles = val; +} + +HRESULT CProgressSync::Set_NumBytesCur(const UInt64 *val) +{ + { + CRITICAL_LOCK + if (val) + _completedBytes = *val; + CHECK_STOP + } + return CheckStop(); +} + +HRESULT CProgressSync::Set_NumBytesCur(UInt64 val) +{ + { + CRITICAL_LOCK + _completedBytes = val; + CHECK_STOP + } + return CheckStop(); +} + +void CProgressSync::Set_Ratio(const UInt64 *inSize, const UInt64 *outSize) +{ + CRITICAL_LOCK + if (inSize) + _inSize = *inSize; + if (outSize) + _outSize = *outSize; +} + +void CProgressSync::Set_TitleFileName(const UString &fileName) +{ + CRITICAL_LOCK + _titleFileName = fileName; +} + +void CProgressSync::Set_Status(const UString &s) +{ + CRITICAL_LOCK + _status = s; +} + +HRESULT CProgressSync::Set_Status2(const UString &s, const wchar_t *path, bool isDir) +{ + { + CRITICAL_LOCK + _status = s; + if (path) + _filePath = path; + else + _filePath.Empty(); + _isDir = isDir; + } + return CheckStop(); +} + +void CProgressSync::Set_FilePath(const wchar_t *path, bool isDir) +{ + CRITICAL_LOCK + if (path) + _filePath = path; + else + _filePath.Empty(); + _isDir = isDir; +} + + +void CProgressSync::AddError_Message(const wchar_t *message) +{ + CRITICAL_LOCK + Messages.Add(message); +} + +void CProgressSync::AddError_Message_Name(const wchar_t *message, const wchar_t *name) +{ + UString s; + if (name && *name != 0) + s += name; + if (message && *message != 0) + { + if (!s.IsEmpty()) + s.Add_LF(); + s += message; + if (!s.IsEmpty() && s.Back() == L'\n') + s.DeleteBack(); + } + AddError_Message(s); +} + +void CProgressSync::AddError_Code_Name(HRESULT systemError, const wchar_t *name) +{ + UString s = NError::MyFormatMessage(systemError); + if (systemError == 0) + s = "Error"; + AddError_Message_Name(s, name); +} + +CProgressDialog::CProgressDialog(): + _isDir(false), + _wasCreated(false), + _needClose(false), + _errorsWereDisplayed(false), + _waitCloseByCancelButton(false), + _cancelWasPressed(false), + _inCancelMessageBox(false), + _externalCloseMessageWasReceived(false), + _background(false), + WaitMode(false), + MessagesDisplayed(false), + CompressingMode(true), + ShowCompressionInfo(true), + _numPostedMessages(0), + _numAutoSizeMessages(0), + _numMessages(0), + _timer(0), + IconID(-1), + MainWindow(NULL) +{ + + if (_dialogCreatedEvent.Create() != S_OK) + throw 1334987; + if (_createDialogEvent.Create() != S_OK) + throw 1334987; + // #ifdef __ITaskbarList3_INTERFACE_DEFINED__ + CoCreateInstance(CLSID_TaskbarList, NULL, CLSCTX_INPROC_SERVER, IID_ITaskbarList3, (void**)&_taskbarList); + if (_taskbarList) + _taskbarList->HrInit(); + // #endif +} + +#ifndef Z7_SFX + +CProgressDialog::~CProgressDialog() +{ + // #ifdef __ITaskbarList3_INTERFACE_DEFINED__ + SetTaskbarProgressState(TBPF_NOPROGRESS); + // #endif + AddToTitle(L""); +} +void CProgressDialog::AddToTitle(LPCWSTR s) +{ + if (MainWindow) + { + CWindow window(MainWindow); + window.SetText((UString)s + MainTitle); + } +} + +#endif + + +void CProgressDialog::SetTaskbarProgressState() +{ + // #ifdef __ITaskbarList3_INTERFACE_DEFINED__ + if (_taskbarList && _hwndForTaskbar) + { + TBPFLAG tbpFlags; + if (Sync.Get_Paused()) + tbpFlags = TBPF_PAUSED; + else + tbpFlags = _errorsWereDisplayed ? TBPF_ERROR: TBPF_NORMAL; + SetTaskbarProgressState(tbpFlags); + } + // #endif +} + +static const unsigned kTitleFileNameSizeLimit = 36; +static const unsigned kCurrentFileNameSizeLimit = 82; + +static void ReduceString(UString &s, unsigned size) +{ + if (s.Len() <= size) + return; + s.Delete(size / 2, s.Len() - size); + s.Insert(size / 2, L" ... "); +} + +void CProgressDialog::EnableErrorsControls(bool enable) +{ + ShowItem_Bool(IDT_PROGRESS_ERRORS, enable); + ShowItem_Bool(IDT_PROGRESS_ERRORS_VAL, enable); + ShowItem_Bool(IDL_PROGRESS_MESSAGES, enable); +} + +bool CProgressDialog::OnInit() +{ + _hwndForTaskbar = MainWindow; + if (!_hwndForTaskbar) + _hwndForTaskbar = GetParent(); + if (!_hwndForTaskbar) + _hwndForTaskbar = *this; + + INIT_AS_UNDEFINED(_progressBar_Range) + INIT_AS_UNDEFINED(_progressBar_Pos) + + INIT_AS_UNDEFINED(_prevPercentValue) + INIT_AS_UNDEFINED(_prevElapsedSec) + INIT_AS_UNDEFINED(_prevRemainingSec) + + INIT_AS_UNDEFINED(_prevSpeed) + _prevSpeed_MoveBits = 0; + + _prevTime = ::GetTickCount(); + _elapsedTime = 0; + + INIT_AS_UNDEFINED(_totalBytes_Prev) + INIT_AS_UNDEFINED(_processed_Prev) + INIT_AS_UNDEFINED(_packed_Prev) + INIT_AS_UNDEFINED(_ratio_Prev) + + _filesStr_Prev.Empty(); + _filesTotStr_Prev.Empty(); + + m_ProgressBar.Attach(GetItem(IDC_PROGRESS1)); + _messageList.Attach(GetItem(IDL_PROGRESS_MESSAGES)); + _messageList.SetUnicodeFormat(); + _messageList.SetExtendedListViewStyle(LVS_EX_FULLROWSELECT); + + _wasCreated = true; + _dialogCreatedEvent.Set(); + + #ifdef Z7_LANG + LangSetDlgItems(*this, kLangIDs, Z7_ARRAY_SIZE(kLangIDs)); + LangSetDlgItems_Colon(*this, kLangIDs_Colon, Z7_ARRAY_SIZE(kLangIDs_Colon)); + #endif + + CWindow window(GetItem(IDB_PROGRESS_BACKGROUND)); + window.GetText(_background_String); + _backgrounded_String = _background_String; + _backgrounded_String.RemoveChar(L'&'); + + window = GetItem(IDB_PAUSE); + window.GetText(_pause_String); + + LangString(IDS_PROGRESS_FOREGROUND, _foreground_String); + LangString(IDS_CONTINUE, _continue_String); + LangString(IDS_PROGRESS_PAUSED, _paused_String); + + SetText(_title); + SetPauseText(); + SetPriorityText(); + + _messageList.InsertColumn(0, L"", 40); + _messageList.InsertColumn(1, L"", 460); + _messageList.SetColumnWidthAuto(0); + _messageList.SetColumnWidthAuto(1); + + EnableErrorsControls(false); + + GetItemSizes(IDCANCEL, _buttonSizeX, _buttonSizeY); + _numReduceSymbols = kCurrentFileNameSizeLimit; + NormalizeSize(true); + + if (!ShowCompressionInfo) + { + HideItem(IDT_PROGRESS_PACKED); + HideItem(IDT_PROGRESS_PACKED_VAL); + HideItem(IDT_PROGRESS_RATIO); + HideItem(IDT_PROGRESS_RATIO_VAL); + } + + if (IconID >= 0) + { + HICON icon = LoadIcon(g_hInstance, MAKEINTRESOURCE(IconID)); + // SetIcon(ICON_SMALL, icon); + SetIcon(ICON_BIG, icon); + } + _timer = SetTimer(kTimerID, kTimerElapse); + #ifdef UNDER_CE + Foreground(); + #endif + + CheckNeedClose(); + + SetTaskbarProgressState(); + + return CModalDialog::OnInit(); +} + +static const UINT kIDs[] = +{ + IDT_PROGRESS_ELAPSED, IDT_PROGRESS_ELAPSED_VAL, + IDT_PROGRESS_REMAINING, IDT_PROGRESS_REMAINING_VAL, + IDT_PROGRESS_FILES, IDT_PROGRESS_FILES_VAL, + 0, IDT_PROGRESS_FILES_TOTAL, + IDT_PROGRESS_ERRORS, IDT_PROGRESS_ERRORS_VAL, + + IDT_PROGRESS_TOTAL, IDT_PROGRESS_TOTAL_VAL, + IDT_PROGRESS_SPEED, IDT_PROGRESS_SPEED_VAL, + IDT_PROGRESS_PROCESSED, IDT_PROGRESS_PROCESSED_VAL, + IDT_PROGRESS_PACKED, IDT_PROGRESS_PACKED_VAL, + IDT_PROGRESS_RATIO, IDT_PROGRESS_RATIO_VAL +}; + +bool CProgressDialog::OnSize(WPARAM /* wParam */, int xSize, int ySize) +{ + int sY; + int sStep; + int mx, my; + { + RECT r; + GetClientRectOfItem(IDT_PROGRESS_ELAPSED, r); + mx = r.left; + my = r.top; + sY = RECT_SIZE_Y(r); + GetClientRectOfItem(IDT_PROGRESS_REMAINING, r); + sStep = r.top - my; + } + + InvalidateRect(NULL); + + const int xSizeClient = xSize - mx * 2; + + { + unsigned i; + for (i = 800; i > 40; i = i * 9 / 10) + if (Units_To_Pixels_X((int)i) <= xSizeClient) + break; + _numReduceSymbols = i / 4; + } + + int yPos = ySize - my - _buttonSizeY; + + ChangeSubWindowSizeX(GetItem(IDT_PROGRESS_STATUS), xSize - mx * 2); + ChangeSubWindowSizeX(GetItem(IDT_PROGRESS_FILE_NAME), xSize - mx * 2); + ChangeSubWindowSizeX(GetItem(IDC_PROGRESS1), xSize - mx * 2); + + int bSizeX = _buttonSizeX; + int mx2 = mx; + for (;; mx2--) + { + const int bSize2 = bSizeX * 3 + mx2 * 2; + if (bSize2 <= xSizeClient) + break; + if (mx2 < 5) + { + bSizeX = (xSizeClient - mx2 * 2) / 3; + break; + } + } + if (bSizeX < 2) + bSizeX = 2; + + { + RECT r; + GetClientRectOfItem(IDL_PROGRESS_MESSAGES, r); + const int y = r.top; + int ySize2 = yPos - my - y; + const int kMinYSize = _buttonSizeY + _buttonSizeY * 3 / 4; + int xx = xSize - mx * 2; + if (ySize2 < kMinYSize) + { + ySize2 = kMinYSize; + if (xx > bSizeX * 2) + xx -= bSizeX; + } + + _messageList.Move(mx, y, xx, ySize2); + } + + { + int xPos = xSize - mx; + xPos -= bSizeX; + MoveItem(IDCANCEL, xPos, yPos, bSizeX, _buttonSizeY); + xPos -= (mx2 + bSizeX); + MoveItem(IDB_PAUSE, xPos, yPos, bSizeX, _buttonSizeY); + xPos -= (mx2 + bSizeX); + MoveItem(IDB_PROGRESS_BACKGROUND, xPos, yPos, bSizeX, _buttonSizeY); + } + + int valueSize; + int labelSize; + int padSize; + + labelSize = Units_To_Pixels_X(MY_PROGRESS_LABEL_UNITS_MIN); + valueSize = Units_To_Pixels_X(MY_PROGRESS_VAL_UNITS); + padSize = Units_To_Pixels_X(MY_PROGRESS_PAD_UNITS); + const int requiredSize = (labelSize + valueSize) * 2 + padSize; + + int gSize; + { + if (requiredSize < xSizeClient) + { + const int incr = (xSizeClient - requiredSize) / 3; + labelSize += incr; + } + else + labelSize = (xSizeClient - valueSize * 2 - padSize) / 2; + if (labelSize < 0) + labelSize = 0; + + gSize = labelSize + valueSize; + padSize = xSizeClient - gSize * 2; + } + + labelSize = gSize - valueSize; + + yPos = my; + for (unsigned i = 0; i < Z7_ARRAY_SIZE(kIDs); i += 2) + { + int x = mx; + const unsigned kNumColumn1Items = 5 * 2; + if (i >= kNumColumn1Items) + { + if (i == kNumColumn1Items) + yPos = my; + x = mx + gSize + padSize; + } + if (kIDs[i] != 0) + MoveItem(kIDs[i], x, yPos, labelSize, sY); + MoveItem(kIDs[i + 1], x + labelSize, yPos, valueSize, sY); + yPos += sStep; + } + return false; +} + +void CProgressDialog::OnCancel() { Sync.Set_Stopped(true); } +void CProgressDialog::OnOK() { } + +void CProgressDialog::SetProgressRange(UInt64 range) +{ + if (range == _progressBar_Range) + return; + _progressBar_Range = range; + INIT_AS_UNDEFINED(_progressBar_Pos) + _progressConv.Init(range); + m_ProgressBar.SetRange32(0, _progressConv.Count(range)); +} + +void CProgressDialog::SetProgressPos(UInt64 pos) +{ + if (pos >= _progressBar_Range || + pos <= _progressBar_Pos || + pos - _progressBar_Pos >= (_progressBar_Range >> 10)) + { + m_ProgressBar.SetPos(_progressConv.Count(pos)); + // #ifdef __ITaskbarList3_INTERFACE_DEFINED__ + if (_taskbarList && _hwndForTaskbar) + _taskbarList->SetProgressValue(_hwndForTaskbar, pos, _progressBar_Range); + // #endif + _progressBar_Pos = pos; + } +} + +#define UINT_TO_STR_2(val) { s[0] = (wchar_t)('0' + (val) / 10); s[1] = (wchar_t)('0' + (val) % 10); s += 2; } + +void GetTimeString(UInt64 timeValue, wchar_t *s); +void GetTimeString(UInt64 timeValue, wchar_t *s) +{ + UInt64 hours = timeValue / 3600; + UInt32 seconds = (UInt32)(timeValue - hours * 3600); + UInt32 minutes = seconds / 60; + seconds %= 60; + if (hours > 99) + { + ConvertUInt64ToString(hours, s); + for (; *s != 0; s++); + } + else + { + UInt32 hours32 = (UInt32)hours; + UINT_TO_STR_2(hours32) + } + *s++ = ':'; UINT_TO_STR_2(minutes) + *s++ = ':'; UINT_TO_STR_2(seconds) + *s = 0; +} + +static void ConvertSizeToString(UInt64 v, wchar_t *s) +{ + Byte c = 0; + if (v >= ((UInt64)100000 << 20)) { v >>= 30; c = 'G'; } + else if (v >= ((UInt64)100000 << 10)) { v >>= 20; c = 'M'; } + else if (v >= ((UInt64)100000 << 0)) { v >>= 10; c = 'K'; } + ConvertUInt64ToString(v, s); + if (c != 0) + { + s += MyStringLen(s); + *s++ = ' '; + *s++ = c; + *s++ = 'B'; + *s++ = 0; + } +} + +void CProgressDialog::ShowSize(unsigned id, UInt64 val, UInt64 &prev) +{ + if (val == prev) + return; + prev = val; + wchar_t s[40]; + s[0] = 0; + if (IS_DEFINED_VAL(val)) + ConvertSizeToString(val, s); + SetItemText(id, s); +} + +static void GetChangedString(const UString &newStr, UString &prevStr, bool &hasChanged) +{ + hasChanged = !(prevStr == newStr); + if (hasChanged) + prevStr = newStr; +} + +static unsigned GetPower32(UInt32 val) +{ + const unsigned kStart = 32; + UInt32 mask = ((UInt32)1 << (kStart - 1)); + for (unsigned i = kStart;; i--) + { + if (i == 0 || (val & mask) != 0) + return i; + mask >>= 1; + } +} + +static unsigned GetPower64(UInt64 val) +{ + UInt32 high = (UInt32)(val >> 32); + if (high == 0) + return GetPower32((UInt32)val); + return GetPower32(high) + 32; +} + +static UInt64 MyMultAndDiv(UInt64 mult1, UInt64 mult2, UInt64 divider) +{ + unsigned pow1 = GetPower64(mult1); + unsigned pow2 = GetPower64(mult2); + while (pow1 + pow2 > 64) + { + if (pow1 > pow2) { pow1--; mult1 >>= 1; } + else { pow2--; mult2 >>= 1; } + divider >>= 1; + } + UInt64 res = mult1 * mult2; + if (divider != 0) + res /= divider; + return res; +} + +void CProgressDialog::UpdateStatInfo(bool showAll) +{ + UInt64 total, completed, totalFiles, completedFiles, inSize, outSize; + bool filesProgressMode; + + bool titleFileName_Changed; + bool curFilePath_Changed; + bool status_Changed; + unsigned numErrors; + { + NSynchronization::CCriticalSectionLock lock(Sync._cs); + total = Sync._totalBytes; + completed = Sync._completedBytes; + totalFiles = Sync._totalFiles; + completedFiles = Sync._curFiles; + inSize = Sync._inSize; + outSize = Sync._outSize; + filesProgressMode = Sync._filesProgressMode; + + GetChangedString(Sync._titleFileName, _titleFileName, titleFileName_Changed); + GetChangedString(Sync._filePath, _filePath, curFilePath_Changed); + GetChangedString(Sync._status, _status, status_Changed); + if (_isDir != Sync._isDir) + { + curFilePath_Changed = true; + _isDir = Sync._isDir; + } + numErrors = Sync.Messages.Size(); + } + + UInt32 curTime = ::GetTickCount(); + + const UInt64 progressTotal = filesProgressMode ? totalFiles : total; + const UInt64 progressCompleted = filesProgressMode ? completedFiles : completed; + { + if (IS_UNDEFINED_VAL(progressTotal)) + { + // SetPos(0); + // SetRange(progressCompleted); + } + else + { + if (_progressBar_Pos != 0 || progressCompleted != 0 || + (_progressBar_Range == 0 && progressTotal != 0)) + { + SetProgressRange(progressTotal); + SetProgressPos(progressCompleted); + } + } + } + + ShowSize(IDT_PROGRESS_TOTAL_VAL, total, _totalBytes_Prev); + + _elapsedTime += (curTime - _prevTime); + _prevTime = curTime; + UInt64 elapsedSec = _elapsedTime / 1000; + bool elapsedChanged = false; + if (elapsedSec != _prevElapsedSec) + { + _prevElapsedSec = elapsedSec; + elapsedChanged = true; + wchar_t s[40]; + GetTimeString(elapsedSec, s); + SetItemText(IDT_PROGRESS_ELAPSED_VAL, s); + } + + bool needSetTitle = false; + if (elapsedChanged || showAll) + { + if (numErrors > _numPostedMessages) + { + UpdateMessagesDialog(); + wchar_t s[32]; + ConvertUInt64ToString(numErrors, s); + SetItemText(IDT_PROGRESS_ERRORS_VAL, s); + if (!_errorsWereDisplayed) + { + _errorsWereDisplayed = true; + EnableErrorsControls(true); + SetTaskbarProgressState(); + } + } + + if (progressCompleted != 0) + { + if (IS_UNDEFINED_VAL(progressTotal)) + { + if (IS_DEFINED_VAL(_prevRemainingSec)) + { + INIT_AS_UNDEFINED(_prevRemainingSec) + SetItemText(IDT_PROGRESS_REMAINING_VAL, L""); + } + } + else + { + UInt64 remainingTime = 0; + if (progressCompleted < progressTotal) + remainingTime = MyMultAndDiv(_elapsedTime, progressTotal - progressCompleted, progressCompleted); + UInt64 remainingSec = remainingTime / 1000; + if (remainingSec != _prevRemainingSec) + { + _prevRemainingSec = remainingSec; + wchar_t s[40]; + GetTimeString(remainingSec, s); + SetItemText(IDT_PROGRESS_REMAINING_VAL, s); + } + } + { + const UInt64 elapsedTime = (_elapsedTime == 0) ? 1 : _elapsedTime; + // 22.02: progressCompleted can be for number of files + UInt64 v = (completed * 1000) / elapsedTime; + Byte c = 0; + unsigned moveBits = 0; + if (v >= ((UInt64)10000 << 10)) { moveBits = 20; c = 'M'; } + else if (v >= ((UInt64)10000 << 0)) { moveBits = 10; c = 'K'; } + v >>= moveBits; + if (moveBits != _prevSpeed_MoveBits || v != _prevSpeed) + { + _prevSpeed_MoveBits = moveBits; + _prevSpeed = v; + wchar_t s[40]; + ConvertUInt64ToString(v, s); + unsigned pos = MyStringLen(s); + s[pos++] = ' '; + if (moveBits != 0) + s[pos++] = c; + s[pos++] = 'B'; + s[pos++] = '/'; + s[pos++] = 's'; + s[pos++] = 0; + SetItemText(IDT_PROGRESS_SPEED_VAL, s); + } + } + } + + { + UInt64 percent = 0; + { + if (IS_DEFINED_VAL(progressTotal)) + { + percent = progressCompleted * 100; + if (progressTotal != 0) + percent /= progressTotal; + } + } + if (percent != _prevPercentValue) + { + _prevPercentValue = percent; + needSetTitle = true; + } + } + + { + wchar_t s[64]; + + ConvertUInt64ToString(completedFiles, s); + if (_filesStr_Prev != s) + { + _filesStr_Prev = s; + SetItemText(IDT_PROGRESS_FILES_VAL, s); + } + + s[0] = 0; + if (IS_DEFINED_VAL(totalFiles)) + { + MyStringCopy(s, L" / "); + ConvertUInt64ToString(totalFiles, s + MyStringLen(s)); + } + if (_filesTotStr_Prev != s) + { + _filesTotStr_Prev = s; + SetItemText(IDT_PROGRESS_FILES_TOTAL, s); + } + } + + const UInt64 packSize = CompressingMode ? outSize : inSize; + const UInt64 unpackSize = CompressingMode ? inSize : outSize; + + if (IS_UNDEFINED_VAL(unpackSize) && + IS_UNDEFINED_VAL(packSize)) + { + ShowSize(IDT_PROGRESS_PROCESSED_VAL, completed, _processed_Prev); + ShowSize(IDT_PROGRESS_PACKED_VAL, UNDEFINED_VAL, _packed_Prev); + } + else + { + ShowSize(IDT_PROGRESS_PROCESSED_VAL, unpackSize, _processed_Prev); + ShowSize(IDT_PROGRESS_PACKED_VAL, packSize, _packed_Prev); + + if (IS_DEFINED_VAL(packSize) && + IS_DEFINED_VAL(unpackSize) && + unpackSize != 0) + { + wchar_t s[32]; + UInt64 ratio = packSize * 100 / unpackSize; + if (_ratio_Prev != ratio) + { + _ratio_Prev = ratio; + ConvertUInt64ToString(ratio, s); + MyStringCat(s, L"%"); + SetItemText(IDT_PROGRESS_RATIO_VAL, s); + } + } + } + } + + if (needSetTitle || titleFileName_Changed) + SetTitleText(); + + if (status_Changed) + { + UString s = _status; + ReduceString(s, _numReduceSymbols); + SetItemText(IDT_PROGRESS_STATUS, s); + } + + if (curFilePath_Changed) + { + UString s1, s2; + if (_isDir) + s1 = _filePath; + else + { + int slashPos = _filePath.ReverseFind_PathSepar(); + if (slashPos >= 0) + { + s1.SetFrom(_filePath, (unsigned)(slashPos + 1)); + s2 = _filePath.Ptr((unsigned)(slashPos + 1)); + } + else + s2 = _filePath; + } + ReduceString(s1, _numReduceSymbols); + ReduceString(s2, _numReduceSymbols); + s1.Add_LF(); + s1 += s2; + SetItemText(IDT_PROGRESS_FILE_NAME, s1); + } +} + +bool CProgressDialog::OnTimer(WPARAM /* timerID */, LPARAM /* callback */) +{ + if (Sync.Get_Paused()) + return true; + CheckNeedClose(); + UpdateStatInfo(false); + return true; +} + +struct CWaitCursor +{ + HCURSOR _waitCursor; + HCURSOR _oldCursor; + CWaitCursor() + { + _waitCursor = LoadCursor(NULL, IDC_WAIT); + if (_waitCursor != NULL) + _oldCursor = SetCursor(_waitCursor); + } + ~CWaitCursor() + { + if (_waitCursor != NULL) + SetCursor(_oldCursor); + } +}; + +INT_PTR CProgressDialog::Create(const UString &title, NWindows::CThread &thread, HWND wndParent) +{ + INT_PTR res = 0; + try + { + if (WaitMode) + { + CWaitCursor waitCursor; + HANDLE h[] = { thread, _createDialogEvent }; + + const DWORD res2 = WaitForMultipleObjects(Z7_ARRAY_SIZE(h), h, FALSE, kCreateDelay); + if (res2 == WAIT_OBJECT_0 && !Sync.ThereIsMessage()) + return 0; + } + _title = title; + BIG_DIALOG_SIZE(360, 192); + res = CModalDialog::Create(SIZED_DIALOG(IDD_PROGRESS), wndParent); + } + catch(...) + { + _wasCreated = true; + _dialogCreatedEvent.Set(); + } + thread.Wait_Close(); + if (!MessagesDisplayed) + if (!g_DisableUserQuestions) + MessageBoxW(wndParent, L"Progress Error", L"7-Zip", MB_ICONERROR); + return res; +} + +bool CProgressDialog::OnExternalCloseMessage() +{ + // it doesn't work if there is MessageBox. + // #ifdef __ITaskbarList3_INTERFACE_DEFINED__ + SetTaskbarProgressState(TBPF_NOPROGRESS); + // #endif + // AddToTitle(L"Finished "); + // SetText(L"Finished2 "); + + UpdateStatInfo(true); + + SetItemText(IDCANCEL, LangString(IDS_CLOSE)); + ::SendMessage(GetItem(IDCANCEL), BM_SETSTYLE, BS_DEFPUSHBUTTON, MAKELPARAM(TRUE, 0)); + HideItem(IDB_PROGRESS_BACKGROUND); + HideItem(IDB_PAUSE); + + ProcessWasFinished_GuiVirt(); + + bool thereAreMessages; + CProgressFinalMessage fm; + { + NSynchronization::CCriticalSectionLock lock(Sync._cs); + thereAreMessages = !Sync.Messages.IsEmpty(); + fm = Sync.FinalMessage; + } + + if (!fm.ErrorMessage.Message.IsEmpty()) + { + MessagesDisplayed = true; + if (fm.ErrorMessage.Title.IsEmpty()) + fm.ErrorMessage.Title = "7-Zip"; + if (!g_DisableUserQuestions) + MessageBoxW(*this, fm.ErrorMessage.Message, fm.ErrorMessage.Title, MB_ICONERROR); + } + else if (!thereAreMessages) + { + MessagesDisplayed = true; + + if (!fm.OkMessage.Message.IsEmpty()) + { + if (fm.OkMessage.Title.IsEmpty()) + fm.OkMessage.Title = "7-Zip"; + if (!g_DisableUserQuestions) + MessageBoxW(*this, fm.OkMessage.Message, fm.OkMessage.Title, MB_OK); + } + } + + if (!g_DisableUserQuestions) + if (thereAreMessages && !_cancelWasPressed) + { + _waitCloseByCancelButton = true; + UpdateMessagesDialog(); + return true; + } + + End(0); + return true; +} + +bool CProgressDialog::OnMessage(UINT message, WPARAM wParam, LPARAM lParam) +{ + switch (message) + { + case kCloseMessage: + { + if (_timer) + { + /* 21.03 : KillTimer(kTimerID) instead of KillTimer(_timer). + But (_timer == kTimerID) in Win10. So it worked too */ + KillTimer(kTimerID); + _timer = 0; + } + if (_inCancelMessageBox) + { + /* if user is in MessageBox(), we will call OnExternalCloseMessage() + later, when MessageBox() will be closed */ + _externalCloseMessageWasReceived = true; + break; + } + return OnExternalCloseMessage(); + } + /* + case WM_SETTEXT: + { + if (_timer == 0) + return true; + break; + } + */ + } + return CModalDialog::OnMessage(message, wParam, lParam); +} + +void CProgressDialog::SetTitleText() +{ + UString s; + if (Sync.Get_Paused()) + { + s += _paused_String; + s.Add_Space(); + } + if (IS_DEFINED_VAL(_prevPercentValue)) + { + s.Add_UInt64(_prevPercentValue); + s.Add_Char('%'); + } + if (_background) + { + s.Add_Space(); + s += _backgrounded_String; + } + + s.Add_Space(); + #ifndef Z7_SFX + { + unsigned len = s.Len(); + s += MainAddTitle; + AddToTitle(s); + s.DeleteFrom(len); + } + #endif + + s += _title; + if (!_titleFileName.IsEmpty()) + { + UString fileName = _titleFileName; + ReduceString(fileName, kTitleFileNameSizeLimit); + s.Add_Space(); + s += fileName; + } + SetText(s); +} + +void CProgressDialog::SetPauseText() +{ + SetItemText(IDB_PAUSE, Sync.Get_Paused() ? _continue_String : _pause_String); + SetTitleText(); +} + +void CProgressDialog::OnPauseButton() +{ + bool paused = !Sync.Get_Paused(); + Sync.Set_Paused(paused); + UInt32 curTime = ::GetTickCount(); + if (paused) + _elapsedTime += (curTime - _prevTime); + SetTaskbarProgressState(); + _prevTime = curTime; + SetPauseText(); +} + +void CProgressDialog::SetPriorityText() +{ + SetItemText(IDB_PROGRESS_BACKGROUND, _background ? + _foreground_String : + _background_String); + SetTitleText(); +} + +void CProgressDialog::OnPriorityButton() +{ + _background = !_background; + #ifndef UNDER_CE + SetPriorityClass(GetCurrentProcess(), _background ? IDLE_PRIORITY_CLASS : NORMAL_PRIORITY_CLASS); + #endif + SetPriorityText(); +} + +void CProgressDialog::AddMessageDirect(LPCWSTR message, bool needNumber) +{ + wchar_t sz[16]; + sz[0] = 0; + if (needNumber) + ConvertUInt32ToString(_numMessages + 1, sz); + const unsigned itemIndex = _messageStrings.Size(); // _messageList.GetItemCount(); + if (_messageList.InsertItem(itemIndex, sz) == (int)itemIndex) + { + _messageList.SetSubItem(itemIndex, 1, message); + _messageStrings.Add(message); + } +} + +void CProgressDialog::AddMessage(LPCWSTR message) +{ + UString s = message; + bool needNumber = true; + while (!s.IsEmpty()) + { + const int pos = s.Find(L'\n'); + if (pos < 0) + break; + AddMessageDirect(s.Left((unsigned)pos), needNumber); + needNumber = false; + s.DeleteFrontal((unsigned)pos + 1); + } + AddMessageDirect(s, needNumber); + _numMessages++; +} + +static unsigned GetNumDigits(unsigned val) +{ + unsigned i = 0; + for (;;) + { + i++; + val /= 10; + if (val == 0) + return i; + } +} + +void CProgressDialog::UpdateMessagesDialog() +{ + UStringVector messages; + { + NSynchronization::CCriticalSectionLock lock(Sync._cs); + const unsigned num = Sync.Messages.Size(); + if (num > _numPostedMessages) + { + messages.ClearAndReserve(num - _numPostedMessages); + for (unsigned i = _numPostedMessages; i < num; i++) + messages.AddInReserved(Sync.Messages[i]); + _numPostedMessages = num; + } + } + if (!messages.IsEmpty()) + { + FOR_VECTOR (i, messages) + AddMessage(messages[i]); + // SetColumnWidthAuto() can be slow for big number of files. + if (_numPostedMessages < 1000000 || _numAutoSizeMessages < 100) + if (_numAutoSizeMessages < 100 || + GetNumDigits(_numPostedMessages) > + GetNumDigits(_numAutoSizeMessages)) + { + _messageList.SetColumnWidthAuto(0); + _messageList.SetColumnWidthAuto(1); + _numAutoSizeMessages = _numPostedMessages; + } + } +} + + +bool CProgressDialog::OnButtonClicked(unsigned buttonID, HWND buttonHWND) +{ + switch (buttonID) + { + // case IDOK: // if IDCANCEL is not DEFPUSHBUTTON + case IDCANCEL: + { + if (_waitCloseByCancelButton) + { + MessagesDisplayed = true; + End(IDCLOSE); + break; + } + + if (_cancelWasPressed) + return true; + + const bool paused = Sync.Get_Paused(); + + if (!paused) + { + OnPauseButton(); + } + + _inCancelMessageBox = true; + const int res = ::MessageBoxW(*this, LangString(IDS_PROGRESS_ASK_CANCEL), _title, MB_YESNOCANCEL); + _inCancelMessageBox = false; + if (res == IDYES) + _cancelWasPressed = true; + + if (!paused) + { + OnPauseButton(); + } + + if (_externalCloseMessageWasReceived) + { + /* we have received kCloseMessage while we were in MessageBoxW(). + so we call OnExternalCloseMessage() here. + it can show MessageBox and it can close dialog */ + OnExternalCloseMessage(); + return true; + } + + if (!_cancelWasPressed) + return true; + + MessagesDisplayed = true; + // we will call Sync.Set_Stopped(true) in OnButtonClicked() : OnCancel() + break; + } + + case IDB_PAUSE: + OnPauseButton(); + return true; + case IDB_PROGRESS_BACKGROUND: + OnPriorityButton(); + return true; + } + return CModalDialog::OnButtonClicked(buttonID, buttonHWND); +} + +void CProgressDialog::CheckNeedClose() +{ + if (_needClose) + { + PostMsg(kCloseMessage); + _needClose = false; + } +} + +void CProgressDialog::ProcessWasFinished() +{ + // Set Window title here. + if (!WaitMode) + WaitCreating(); + + if (_wasCreated) + PostMsg(kCloseMessage); + else + _needClose = true; +} + + +bool CProgressDialog::OnNotify(UINT /* controlID */, LPNMHDR header) +{ + if (header->hwndFrom != _messageList) + return false; + switch (header->code) + { + case LVN_KEYDOWN: + { + LPNMLVKEYDOWN keyDownInfo = LPNMLVKEYDOWN(header); + switch (keyDownInfo->wVKey) + { + case 'A': + { + if (IsKeyDown(VK_CONTROL)) + { + _messageList.SelectAll(); + return true; + } + break; + } + case VK_INSERT: + case 'C': + { + if (IsKeyDown(VK_CONTROL)) + { + CopyToClipboard(); + return true; + } + break; + } + } + } + } + return false; +} + + +static void ListView_GetSelected(NControl::CListView &listView, CUIntVector &vector) +{ + vector.Clear(); + int index = -1; + for (;;) + { + index = listView.GetNextSelectedItem(index); + if (index < 0) + break; + vector.Add((unsigned)index); + } +} + + +void CProgressDialog::CopyToClipboard() +{ + CUIntVector indexes; + ListView_GetSelected(_messageList, indexes); + UString s; + unsigned numIndexes = indexes.Size(); + if (numIndexes == 0) + numIndexes = (unsigned)_messageList.GetItemCount(); + + for (unsigned i = 0; i < numIndexes; i++) + { + const unsigned index = (i < indexes.Size() ? indexes[i] : i); + // s.Add_UInt32(index); + // s += ": "; + s += _messageStrings[index]; + { + s += + #ifdef _WIN32 + "\r\n" + #else + "\n" + #endif + ; + } + } + + ClipboardSetText(*this, s); +} + + +static THREAD_FUNC_DECL MyThreadFunction(void *param) +{ + CProgressThreadVirt *p = (CProgressThreadVirt *)param; + try + { + p->Process(); + p->ThreadFinishedOK = true; + } + catch (...) { p->Result = E_FAIL; } + return 0; +} + + +HRESULT CProgressThreadVirt::Create(const UString &title, HWND parentWindow) +{ + NWindows::CThread thread; + const WRes wres = thread.Create(MyThreadFunction, this); + if (wres != 0) + return HRESULT_FROM_WIN32(wres); + CProgressDialog::Create(title, thread, parentWindow); + return S_OK; +} + +static void AddMessageToString(UString &dest, const UString &src) +{ + if (!src.IsEmpty()) + { + if (!dest.IsEmpty()) + dest.Add_LF(); + dest += src; + } +} + +void CProgressThreadVirt::Process() +{ + CProgressCloser closer(*this); + UString m; + try { Result = ProcessVirt(); } + catch(const wchar_t *s) { m = s; } + catch(const UString &s) { m = s; } + catch(const char *s) { m = GetUnicodeString(s); } + catch(int v) + { + m = "Error #"; + m.Add_UInt32((unsigned)v); + } + catch(...) { m = "Error"; } + if (Result != E_ABORT) + { + if (m.IsEmpty() && Result != S_OK) + m = HResultToMessage(Result); + } + AddMessageToString(m, FinalMessage.ErrorMessage.Message); + + { + FOR_VECTOR(i, ErrorPaths) + { + if (i >= 32) + break; + AddMessageToString(m, fs2us(ErrorPaths[i])); + } + } + + CProgressSync &sync = Sync; + NSynchronization::CCriticalSectionLock lock(sync._cs); + if (m.IsEmpty()) + { + if (!FinalMessage.OkMessage.Message.IsEmpty()) + sync.FinalMessage.OkMessage = FinalMessage.OkMessage; + } + else + { + sync.FinalMessage.ErrorMessage.Message = m; + if (Result == S_OK) + Result = E_FAIL; + } +} + +UString HResultToMessage(HRESULT errorCode) +{ + if (errorCode == E_OUTOFMEMORY) + return LangString(IDS_MEM_ERROR); + else + return NError::MyFormatMessage(errorCode); +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ProgressDialog2.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ProgressDialog2.h new file mode 100644 index 00000000..60a5ca62 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ProgressDialog2.h @@ -0,0 +1,355 @@ +// ProgressDialog2.h + +#ifndef ZIP7_INC_PROGRESS_DIALOG_2_H +#define ZIP7_INC_PROGRESS_DIALOG_2_H + +#include "../../../Common/MyCom.h" + +#include "../../../Windows/ErrorMsg.h" +#include "../../../Windows/Synchronization.h" +#include "../../../Windows/Thread.h" + +#include "../../../Windows/Control/Dialog.h" +#include "../../../Windows/Control/ListView.h" +#include "../../../Windows/Control/ProgressBar.h" + +#include "MyWindowsNew.h" + +struct CProgressMessageBoxPair +{ + UString Title; + UString Message; +}; + +struct CProgressFinalMessage +{ + CProgressMessageBoxPair ErrorMessage; + CProgressMessageBoxPair OkMessage; + + bool ThereIsMessage() const { return !ErrorMessage.Message.IsEmpty() || !OkMessage.Message.IsEmpty(); } +}; + +class CProgressSync +{ + bool _stopped; + bool _paused; +public: + bool _filesProgressMode; + bool _isDir; + UInt64 _totalBytes; + UInt64 _completedBytes; + UInt64 _totalFiles; + UInt64 _curFiles; + UInt64 _inSize; + UInt64 _outSize; + + UString _titleFileName; + UString _status; + UString _filePath; + + UStringVector Messages; + CProgressFinalMessage FinalMessage; + + NWindows::NSynchronization::CCriticalSection _cs; + + CProgressSync(); + + bool Get_Stopped() + { + NWindows::NSynchronization::CCriticalSectionLock lock(_cs); + return _stopped; + } + void Set_Stopped(bool val) + { + NWindows::NSynchronization::CCriticalSectionLock lock(_cs); + _stopped = val; + } + + bool Get_Paused(); + void Set_Paused(bool val) + { + NWindows::NSynchronization::CCriticalSectionLock lock(_cs); + _paused = val; + } + + void Set_FilesProgressMode(bool filesProgressMode) + { + NWindows::NSynchronization::CCriticalSectionLock lock(_cs); + _filesProgressMode = filesProgressMode; + } + + HRESULT CheckStop(); + void Clear_Stop_Status(); + HRESULT ScanProgress(UInt64 numFiles, UInt64 totalSize, const FString &fileName, bool isDir = false); + + HRESULT Set_NumFilesTotal(UInt64 val); + void Set_NumBytesTotal(UInt64 val); + void Set_NumFilesCur(UInt64 val); + HRESULT Set_NumBytesCur(const UInt64 *val); + HRESULT Set_NumBytesCur(UInt64 val); + void Set_Ratio(const UInt64 *inSize, const UInt64 *outSize); + + void Set_TitleFileName(const UString &fileName); + void Set_Status(const UString &s); + HRESULT Set_Status2(const UString &s, const wchar_t *path, bool isDir = false); + void Set_FilePath(const wchar_t *path, bool isDir = false); + + void AddError_Message(const wchar_t *message); + void AddError_Message_Name(const wchar_t *message, const wchar_t *name); + // void AddError_Code_Name(DWORD systemError, const wchar_t *name); + void AddError_Code_Name(HRESULT systemError, const wchar_t *name); + + bool ThereIsMessage() const { return !Messages.IsEmpty() || FinalMessage.ThereIsMessage(); } +}; + + +class CProgressDialog: public NWindows::NControl::CModalDialog +{ + bool _isDir; + bool _wasCreated; + bool _needClose; + bool _errorsWereDisplayed; + bool _waitCloseByCancelButton; + bool _cancelWasPressed; + bool _inCancelMessageBox; + bool _externalCloseMessageWasReceived; + bool _background; +public: + bool WaitMode; + bool MessagesDisplayed; // = true if user pressed OK on all messages or there are no messages. + bool CompressingMode; + bool ShowCompressionInfo; + +private: + unsigned _numPostedMessages; + unsigned _numAutoSizeMessages; + unsigned _numMessages; + + UString _titleFileName; + UString _filePath; + UString _status; + + UString _background_String; + UString _backgrounded_String; + UString _foreground_String; + UString _pause_String; + UString _continue_String; + UString _paused_String; + + int _buttonSizeX; + int _buttonSizeY; + + UINT_PTR _timer; + + UString _title; + + class CU64ToI32Converter + { + unsigned _numShiftBits; + UInt64 _range; + public: + CU64ToI32Converter(): _numShiftBits(0), _range(1) {} + void Init(UInt64 range) + { + _range = range; + // Windows CE doesn't like big number for ProgressBar. + for (_numShiftBits = 0; range >= ((UInt32)1 << 15); _numShiftBits++) + range >>= 1; + } + int Count(UInt64 val) + { + int res = (int)(val >> _numShiftBits); + if (val == _range) + res++; + return res; + } + }; + + CU64ToI32Converter _progressConv; + UInt64 _progressBar_Pos; + UInt64 _progressBar_Range; + + NWindows::NControl::CProgressBar m_ProgressBar; + NWindows::NControl::CListView _messageList; + + UStringVector _messageStrings; + + // #ifdef __ITaskbarList3_INTERFACE_DEFINED__ + CMyComPtr _taskbarList; + // #endif + HWND _hwndForTaskbar; + + UInt32 _prevTime; + UInt64 _elapsedTime; + + UInt64 _prevPercentValue; + UInt64 _prevElapsedSec; + UInt64 _prevRemainingSec; + + UInt64 _totalBytes_Prev; + UInt64 _processed_Prev; + UInt64 _packed_Prev; + UInt64 _ratio_Prev; + + UString _filesStr_Prev; + UString _filesTotStr_Prev; + + unsigned _numReduceSymbols; + unsigned _prevSpeed_MoveBits; + UInt64 _prevSpeed; + + // #ifdef __ITaskbarList3_INTERFACE_DEFINED__ + void SetTaskbarProgressState(TBPFLAG tbpFlags) + { + if (_taskbarList && _hwndForTaskbar) + _taskbarList->SetProgressState(_hwndForTaskbar, tbpFlags); + } + // #endif + void SetTaskbarProgressState(); + + void UpdateStatInfo(bool showAll); + void SetProgressRange(UInt64 range); + void SetProgressPos(UInt64 pos); + virtual bool OnTimer(WPARAM timerID, LPARAM callback) Z7_override; + virtual bool OnInit() Z7_override; + virtual bool OnSize(WPARAM wParam, int xSize, int ySize) Z7_override; + virtual void OnCancel() Z7_override; + virtual void OnOK() Z7_override; + virtual bool OnNotify(UINT /* controlID */, LPNMHDR header) Z7_override; + void CopyToClipboard(); + + NWindows::NSynchronization::CManualResetEvent _createDialogEvent; + NWindows::NSynchronization::CManualResetEvent _dialogCreatedEvent; + #ifndef Z7_SFX + void AddToTitle(LPCWSTR string); + #endif + + void SetPauseText(); + void SetPriorityText(); + void OnPauseButton(); + void OnPriorityButton(); + bool OnButtonClicked(unsigned buttonID, HWND buttonHWND) Z7_override; + bool OnMessage(UINT message, WPARAM wParam, LPARAM lParam) Z7_override; + + void SetTitleText(); + void ShowSize(unsigned id, UInt64 val, UInt64 &prev); + + void UpdateMessagesDialog(); + + void AddMessageDirect(LPCWSTR message, bool needNumber); + void AddMessage(LPCWSTR message); + + bool OnExternalCloseMessage(); + void EnableErrorsControls(bool enable); + + void ShowAfterMessages(HWND wndParent); + + void CheckNeedClose(); + +public: + CProgressSync Sync; + int IconID; + HWND MainWindow; + #ifndef Z7_SFX + UString MainTitle; + UString MainAddTitle; + ~CProgressDialog() Z7_DESTRUCTOR_override; + #endif + + CProgressDialog(); + void WaitCreating() + { + _createDialogEvent.Set(); + _dialogCreatedEvent.Lock(); + } + + INT_PTR Create(const UString &title, NWindows::CThread &thread, HWND wndParent = NULL); + + + /* how it works: + 1) the working thread calls ProcessWasFinished() + that sends kCloseMessage message to CProgressDialog (GUI) thread + 2) CProgressDialog (GUI) thread receives kCloseMessage message and + calls ProcessWasFinished_GuiVirt(); + So we can implement ProcessWasFinished_GuiVirt() and show special + results window in GUI thread with CProgressDialog as parent window + */ + + void ProcessWasFinished(); + virtual void ProcessWasFinished_GuiVirt() {} +}; + + +class CProgressCloser +{ + CProgressDialog *_p; +public: + CProgressCloser(CProgressDialog &p) : _p(&p) {} + ~CProgressCloser() { _p->ProcessWasFinished(); } +}; + + +class CProgressThreadVirt: public CProgressDialog +{ +protected: + FStringVector ErrorPaths; + CProgressFinalMessage FinalMessage; + + // error if any of HRESULT, ErrorMessage, ErrorPath + virtual HRESULT ProcessVirt() = 0; +public: + HRESULT Result; + bool ThreadFinishedOK; // if there is no fatal exception + + void Process(); + void AddErrorPath(const FString &path) { ErrorPaths.Add(path); } + + HRESULT Create(const UString &title, HWND parentWindow = NULL); + CProgressThreadVirt(): Result(E_FAIL), ThreadFinishedOK(false) {} + + CProgressMessageBoxPair &GetMessagePair(bool isError) { return isError ? FinalMessage.ErrorMessage : FinalMessage.OkMessage; } +}; + +UString HResultToMessage(HRESULT errorCode); + +/* +how it works: + +client code inherits CProgressThreadVirt and calls +CProgressThreadVirt::Create() +{ + it creates new thread that calls CProgressThreadVirt::Process(); + it creates modal progress dialog window with ProgressDialog.Create() +} + +CProgressThreadVirt::Process() +{ + { + Result = ProcessVirt(); // virtual function that must implement real work + } + if (exceptions) or FinalMessage.ErrorMessage.Message + { + set message to ProgressDialog.Sync.FinalMessage.ErrorMessage.Message + } + else if (FinalMessage.OkMessage.Message) + { + set message to ProgressDialog.Sync.FinalMessage.OkMessage + } + + PostMsg(kCloseMessage); +} + + +CProgressDialog::OnExternalCloseMessage() +{ + if (ProgressDialog.Sync.FinalMessage) + { + WorkWasFinishedVirt(); + Show (ProgressDialog.Sync.FinalMessage) + MessagesDisplayed = true; + } +} + +*/ + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ProgressDialog2.rc b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ProgressDialog2.rc new file mode 100644 index 00000000..4d0e0c7b --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ProgressDialog2.rc @@ -0,0 +1,40 @@ +#include "ProgressDialog2Res.h" +#include "../../GuiCommon.rc" + +#undef DIALOG_ID +#define DIALOG_ID IDD_PROGRESS +#define xc 360 +#define k 11 +#define z1s 16 + +#include "ProgressDialog2a.rc" + +#ifdef UNDER_CE + +#include "../../GuiCommon.rc" + + +#undef DIALOG_ID +#undef m +#undef k +#undef z1s + +#define DIALOG_ID IDD_PROGRESS_2 +#define m 4 +#define k 8 +#define z1s 12 + +#define xc 280 + +#include "ProgressDialog2a.rc" + +#endif + +STRINGTABLE DISCARDABLE +{ + IDS_PROGRESS_PAUSED "Paused" + IDS_PROGRESS_FOREGROUND "&Foreground" + IDS_CONTINUE "&Continue" + IDS_PROGRESS_ASK_CANCEL "Are you sure you want to cancel?" + IDS_CLOSE "&Close" +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ProgressDialog2Res.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ProgressDialog2Res.h new file mode 100644 index 00000000..736c7179 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ProgressDialog2Res.h @@ -0,0 +1,49 @@ +#define IDD_PROGRESS 97 +#define IDD_PROGRESS_2 10097 + +#define IDS_CLOSE 408 +#define IDS_CONTINUE 411 + +#define IDB_PROGRESS_BACKGROUND 444 +#define IDS_PROGRESS_FOREGROUND 445 +#define IDB_PAUSE 446 +#define IDS_PROGRESS_PAUSED 447 +#define IDS_PROGRESS_ASK_CANCEL 448 + +#define IDT_PROGRESS_PACKED 1008 +#define IDT_PROGRESS_FILES 1032 + +#define IDT_PROGRESS_ELAPSED 3900 +#define IDT_PROGRESS_REMAINING 3901 +#define IDT_PROGRESS_TOTAL 3902 +#define IDT_PROGRESS_SPEED 3903 +#define IDT_PROGRESS_PROCESSED 3904 +#define IDT_PROGRESS_RATIO 3905 +#define IDT_PROGRESS_ERRORS 3906 + +#define IDC_PROGRESS1 100 +#define IDL_PROGRESS_MESSAGES 101 +#define IDT_PROGRESS_FILE_NAME 102 +#define IDT_PROGRESS_STATUS 103 + +#define IDT_PROGRESS_PACKED_VAL 110 +#define IDT_PROGRESS_FILES_VAL 111 +#define IDT_PROGRESS_FILES_TOTAL 112 + +#define IDT_PROGRESS_ELAPSED_VAL 120 +#define IDT_PROGRESS_REMAINING_VAL 121 +#define IDT_PROGRESS_TOTAL_VAL 122 +#define IDT_PROGRESS_SPEED_VAL 123 +#define IDT_PROGRESS_PROCESSED_VAL 124 +#define IDT_PROGRESS_RATIO_VAL 125 +#define IDT_PROGRESS_ERRORS_VAL 126 + + +#ifdef UNDER_CE +#define MY_PROGRESS_VAL_UNITS 44 +#else +#define MY_PROGRESS_VAL_UNITS 72 +#endif +#define MY_PROGRESS_LABEL_UNITS_MIN 60 +#define MY_PROGRESS_LABEL_UNITS_START 90 +#define MY_PROGRESS_PAD_UNITS 4 diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ProgressDialog2a.rc b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ProgressDialog2a.rc new file mode 100644 index 00000000..dc7d797f --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ProgressDialog2a.rc @@ -0,0 +1,85 @@ +#undef bxs +#define bxs 80 + +#define x0s MY_PROGRESS_LABEL_UNITS_START +#define x1s MY_PROGRESS_VAL_UNITS +#define x2s MY_PROGRESS_LABEL_UNITS_START +#define x3s MY_PROGRESS_VAL_UNITS + +#define x1 (m + x0s) +#define x3 (xs - m - x3s) +#define x2 (x3 - x2s) + +#undef y0 +#undef y1 +#undef y2 +#undef y3 +#undef y4 + +#undef z0 +#undef z1 +#undef z2 +#undef z3 + +#define y0 m +#define y1 (y0 + k) +#define y2 (y1 + k) +#define y3 (y2 + k) +#define y4 (y3 + k) + +#define z3 (y4 + k + 1) + +#define z2 (z3 + k + 1) +#define z2s 24 + +#define z1 (z2 + z2s) + +#define z0 (z1 + z1s + m) +#define z0s 48 + +#define yc (z0 + z0s + bys) + + +DIALOG_ID DIALOG 0, 0, xs, ys MY_MODAL_RESIZE_DIALOG_STYLE MY_FONT +CAPTION "Progress" +{ + DEFPUSHBUTTON "&Background", IDB_PROGRESS_BACKGROUND, bx3, by, bxs, bys + PUSHBUTTON "&Pause", IDB_PAUSE, bx2, by, bxs, bys + PUSHBUTTON "Cancel", IDCANCEL, bx1, by, bxs, bys + + + LTEXT "Elapsed time:", IDT_PROGRESS_ELAPSED, m, y0, x0s, 8 + LTEXT "Remaining time:", IDT_PROGRESS_REMAINING, m, y1, x0s, 8 + LTEXT "Files:", IDT_PROGRESS_FILES, m, y2, x0s, 8 + + LTEXT "Errors:", IDT_PROGRESS_ERRORS, m, y4, x0s, 8 + + + LTEXT "Total size:", IDT_PROGRESS_TOTAL, x2, y0, x2s, 8 + LTEXT "Speed:", IDT_PROGRESS_SPEED, x2, y1, x2s, 8 + LTEXT "Processed:", IDT_PROGRESS_PROCESSED,x2, y2, x2s, 8 + LTEXT "Compressed size:" , IDT_PROGRESS_PACKED, x2, y3, x2s, 8 + LTEXT "Compression ratio:", IDT_PROGRESS_RATIO, x2, y4, x2s, 8 + + + RTEXT "", IDT_PROGRESS_ELAPSED_VAL, x1, y0, x1s, MY_TEXT_NOPREFIX + RTEXT "", IDT_PROGRESS_REMAINING_VAL, x1, y1, x1s, MY_TEXT_NOPREFIX + RTEXT "", IDT_PROGRESS_FILES_VAL, x1, y2, x1s, MY_TEXT_NOPREFIX + RTEXT "", IDT_PROGRESS_FILES_TOTAL, x1, y3, x1s, MY_TEXT_NOPREFIX + RTEXT "", IDT_PROGRESS_ERRORS_VAL, x1, y4, x1s, MY_TEXT_NOPREFIX + + RTEXT "", IDT_PROGRESS_TOTAL_VAL, x3, y0, x3s, MY_TEXT_NOPREFIX + RTEXT "", IDT_PROGRESS_SPEED_VAL, x3, y1, x3s, MY_TEXT_NOPREFIX + RTEXT "", IDT_PROGRESS_PROCESSED_VAL, x3, y2, x3s, MY_TEXT_NOPREFIX + RTEXT "", IDT_PROGRESS_PACKED_VAL, x3, y3, x3s, MY_TEXT_NOPREFIX + RTEXT "", IDT_PROGRESS_RATIO_VAL, x3, y4, x3s, MY_TEXT_NOPREFIX + + LTEXT "", IDT_PROGRESS_STATUS, m, z3, xc, MY_TEXT_NOPREFIX + CONTROL "", IDT_PROGRESS_FILE_NAME, "Static", SS_NOPREFIX | SS_LEFTNOWORDWRAP, m, z2, xc, z2s + + CONTROL "Progress1", IDC_PROGRESS1, "msctls_progress32", PBS_SMOOTH | WS_BORDER, m, z1, xc, z1s + + CONTROL "List1", IDL_PROGRESS_MESSAGES, "SysListView32", + LVS_REPORT | LVS_SHOWSELALWAYS | LVS_NOCOLUMNHEADER | LVS_NOSORTHEADER | WS_BORDER | WS_TABSTOP, + m, z0, xc, z0s +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ProgressDialogRes.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ProgressDialogRes.h new file mode 100644 index 00000000..cbf3beb2 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ProgressDialogRes.h @@ -0,0 +1,3 @@ +#define IDD_PROGRESS 97 + +#define IDC_PROGRESS1 100 diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PropertyName.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PropertyName.cpp new file mode 100644 index 00000000..838b6e3f --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PropertyName.cpp @@ -0,0 +1,23 @@ +// PropertyName.cpp + +#include "StdAfx.h" + +#include "../../../Common/IntToString.h" + +#include "LangUtils.h" +#include "PropertyName.h" + +UString GetNameOfProperty(PROPID propID, const wchar_t *name) +{ + if (propID < 1000) + { + UString s = LangString(1000 + propID); + if (!s.IsEmpty()) + return s; + } + if (name) + return name; + wchar_t temp[16]; + ConvertUInt32ToString(propID, temp); + return temp; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PropertyName.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PropertyName.h new file mode 100644 index 00000000..fa6e5c54 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PropertyName.h @@ -0,0 +1,10 @@ +// PropertyName.h + +#ifndef ZIP7_INC_PROPERTY_NAME_H +#define ZIP7_INC_PROPERTY_NAME_H + +#include "../../../Common/MyString.h" + +UString GetNameOfProperty(PROPID propID, const wchar_t *name); + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PropertyName.rc b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PropertyName.rc new file mode 100644 index 00000000..618875bf --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PropertyName.rc @@ -0,0 +1,109 @@ +#include "PropertyNameRes.h" + +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +STRINGTABLE +BEGIN + IDS_PROP_PATH "Path" + IDS_PROP_NAME "Name" + IDS_PROP_EXTENSION "Extension" + IDS_PROP_IS_FOLDER "Folder" + IDS_PROP_SIZE "Size" + IDS_PROP_PACKED_SIZE "Packed Size" + IDS_PROP_ATTRIBUTES "Attributes" + IDS_PROP_CTIME "Created" + IDS_PROP_ATIME "Accessed" + IDS_PROP_MTIME "Modified" + IDS_PROP_SOLID "Solid" + IDS_PROP_C0MMENTED "Commented" + IDS_PROP_ENCRYPTED "Encrypted" + IDS_PROP_SPLIT_BEFORE "Split Before" + IDS_PROP_SPLIT_AFTER "Split After" + IDS_PROP_DICTIONARY_SIZE "Dictionary" + IDS_PROP_CRC "CRC" + IDS_PROP_FILE_TYPE "Type" + IDS_PROP_ANTI "Anti" + IDS_PROP_METHOD "Method" + IDS_PROP_HOST_OS "Host OS" + IDS_PROP_FILE_SYSTEM "File System" + IDS_PROP_USER "User" + IDS_PROP_GROUP "Group" + IDS_PROP_BLOCK "Block" + IDS_PROP_COMMENT "Comment" + IDS_PROP_POSITION "Position" + IDS_PROP_PREFIX "Path Prefix" + IDS_PROP_FOLDERS "Folders" + IDS_PROP_FILES "Files" + IDS_PROP_VERSION "Version" + IDS_PROP_VOLUME "Volume" + IDS_PROP_IS_VOLUME "Multivolume" + IDS_PROP_OFFSET "Offset" + IDS_PROP_LINKS "Links" + IDS_PROP_NUM_BLOCKS "Blocks" + IDS_PROP_NUM_VOLUMES "Volumes" + + IDS_PROP_BIT64 "64-bit" + IDS_PROP_BIG_ENDIAN "Big-endian" + IDS_PROP_CPU "CPU" + IDS_PROP_PHY_SIZE "Physical Size" + IDS_PROP_HEADERS_SIZE "Headers Size" + IDS_PROP_CHECKSUM "Checksum" + IDS_PROP_CHARACTS "Characteristics" + IDS_PROP_VA "Virtual Address" + IDS_PROP_ID "ID" + IDS_PROP_SHORT_NAME "Short Name" + IDS_PROP_CREATOR_APP "Creator Application" + IDS_PROP_SECTOR_SIZE "Sector Size" + IDS_PROP_POSIX_ATTRIB "Mode" + IDS_PROP_SYM_LINK "Symbolic Link" + IDS_PROP_ERROR "Error" + IDS_PROP_TOTAL_SIZE "Total Size" + IDS_PROP_FREE_SPACE "Free Space" + IDS_PROP_CLUSTER_SIZE "Cluster Size" + IDS_PROP_VOLUME_NAME "Label" + IDS_PROP_LOCAL_NAME "Local Name" + IDS_PROP_PROVIDER "Provider" + IDS_PROP_NT_SECURITY "NT Security" + IDS_PROP_ALT_STREAM "Alternate Stream" + IDS_PROP_AUX "Aux" + IDS_PROP_DELETED "Deleted" + IDS_PROP_IS_TREE "Is Tree" + IDS_PROP_SHA1 "SHA-1" + IDS_PROP_SHA256 "SHA-256" + IDS_PROP_ERROR_TYPE "Error Type" + IDS_PROP_NUM_ERRORS "Errors" + IDS_PROP_ERROR_FLAGS "Errors" + IDS_PROP_WARNING_FLAGS "Warnings" + IDS_PROP_WARNING "Warning" + IDS_PROP_NUM_STREAMS "Streams" + IDS_PROP_NUM_ALT_STREAMS "Alternate Streams" + IDS_PROP_ALT_STREAMS_SIZE "Alternate Streams Size" + IDS_PROP_VIRTUAL_SIZE "Virtual Size" + IDS_PROP_UNPACK_SIZE "Unpack Size" + IDS_PROP_TOTAL_PHY_SIZE "Total Physical Size" + IDS_PROP_VOLUME_INDEX "Volume Index" + IDS_PROP_SUBTYPE "SubType" + IDS_PROP_SHORT_COMMENT "Short Comment" + IDS_PROP_CODE_PAGE "Code Page" + IDS_PROP_IS_NOT_ARC_TYPE "Is not archive type" + IDS_PROP_PHY_SIZE_CANT_BE_DETECTED "Physical Size can't be detected" + IDS_PROP_ZEROS_TAIL_IS_ALLOWED "Zeros Tail Is Allowed" + IDS_PROP_TAIL_SIZE "Tail Size" + IDS_PROP_EMB_STUB_SIZE "Embedded Stub Size" + IDS_PROP_NT_REPARSE "Link" + IDS_PROP_HARD_LINK "Hard Link" + IDS_PROP_INODE "iNode" + IDS_PROP_STREAM_ID "Stream ID" + IDS_PROP_READ_ONLY "Read-only" + IDS_PROP_OUT_NAME "Out Name" + IDS_PROP_COPY_LINK "Copy Link" + IDS_PROP_ARC_FILE_NAME "ArcFileName" + IDS_PROP_IS_HASH "IsHash" + IDS_PROP_CHANGE_TIME "Metadata Changed" + IDS_PROP_USER_ID "User ID" + IDS_PROP_GROUP_ID "Group ID" + IDS_PROP_DEVICE_MAJOR "Device Major" + IDS_PROP_DEVICE_MINOR "Device Minor" + IDS_PROP_DEV_MAJOR "Dev Major" + IDS_PROP_DEV_MINOR "Dev Minor" +END diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PropertyNameRes.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PropertyNameRes.h new file mode 100644 index 00000000..913887e4 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/PropertyNameRes.h @@ -0,0 +1,104 @@ + + +#define IDS_PROP_PATH 1003 +#define IDS_PROP_NAME 1004 +#define IDS_PROP_EXTENSION 1005 +#define IDS_PROP_IS_FOLDER 1006 +#define IDS_PROP_SIZE 1007 +#define IDS_PROP_PACKED_SIZE 1008 +#define IDS_PROP_ATTRIBUTES 1009 +#define IDS_PROP_CTIME 1010 +#define IDS_PROP_ATIME 1011 +#define IDS_PROP_MTIME 1012 +#define IDS_PROP_SOLID 1013 +#define IDS_PROP_C0MMENTED 1014 +#define IDS_PROP_ENCRYPTED 1015 +#define IDS_PROP_SPLIT_BEFORE 1016 +#define IDS_PROP_SPLIT_AFTER 1017 +#define IDS_PROP_DICTIONARY_SIZE 1018 +#define IDS_PROP_CRC 1019 +#define IDS_PROP_FILE_TYPE 1020 +#define IDS_PROP_ANTI 1021 +#define IDS_PROP_METHOD 1022 +#define IDS_PROP_HOST_OS 1023 +#define IDS_PROP_FILE_SYSTEM 1024 +#define IDS_PROP_USER 1025 +#define IDS_PROP_GROUP 1026 +#define IDS_PROP_BLOCK 1027 +#define IDS_PROP_COMMENT 1028 +#define IDS_PROP_POSITION 1029 +#define IDS_PROP_PREFIX 1030 +#define IDS_PROP_FOLDERS 1031 +#define IDS_PROP_FILES 1032 +#define IDS_PROP_VERSION 1033 +#define IDS_PROP_VOLUME 1034 +#define IDS_PROP_IS_VOLUME 1035 +#define IDS_PROP_OFFSET 1036 +#define IDS_PROP_LINKS 1037 +#define IDS_PROP_NUM_BLOCKS 1038 +#define IDS_PROP_NUM_VOLUMES 1039 + +#define IDS_PROP_BIT64 1041 +#define IDS_PROP_BIG_ENDIAN 1042 +#define IDS_PROP_CPU 1043 +#define IDS_PROP_PHY_SIZE 1044 +#define IDS_PROP_HEADERS_SIZE 1045 +#define IDS_PROP_CHECKSUM 1046 +#define IDS_PROP_CHARACTS 1047 +#define IDS_PROP_VA 1048 +#define IDS_PROP_ID 1049 +#define IDS_PROP_SHORT_NAME 1050 +#define IDS_PROP_CREATOR_APP 1051 +#define IDS_PROP_SECTOR_SIZE 1052 +#define IDS_PROP_POSIX_ATTRIB 1053 +#define IDS_PROP_SYM_LINK 1054 +#define IDS_PROP_ERROR 1055 +#define IDS_PROP_TOTAL_SIZE 1056 +#define IDS_PROP_FREE_SPACE 1057 +#define IDS_PROP_CLUSTER_SIZE 1058 +#define IDS_PROP_VOLUME_NAME 1059 +#define IDS_PROP_LOCAL_NAME 1060 +#define IDS_PROP_PROVIDER 1061 +#define IDS_PROP_NT_SECURITY 1062 +#define IDS_PROP_ALT_STREAM 1063 +#define IDS_PROP_AUX 1064 +#define IDS_PROP_DELETED 1065 +#define IDS_PROP_IS_TREE 1066 +#define IDS_PROP_SHA1 1067 +#define IDS_PROP_SHA256 1068 +#define IDS_PROP_ERROR_TYPE 1069 +#define IDS_PROP_NUM_ERRORS 1070 +#define IDS_PROP_ERROR_FLAGS 1071 +#define IDS_PROP_WARNING_FLAGS 1072 +#define IDS_PROP_WARNING 1073 +#define IDS_PROP_NUM_STREAMS 1074 +#define IDS_PROP_NUM_ALT_STREAMS 1075 +#define IDS_PROP_ALT_STREAMS_SIZE 1076 +#define IDS_PROP_VIRTUAL_SIZE 1077 +#define IDS_PROP_UNPACK_SIZE 1078 +#define IDS_PROP_TOTAL_PHY_SIZE 1079 +#define IDS_PROP_VOLUME_INDEX 1080 +#define IDS_PROP_SUBTYPE 1081 +#define IDS_PROP_SHORT_COMMENT 1082 +#define IDS_PROP_CODE_PAGE 1083 +#define IDS_PROP_IS_NOT_ARC_TYPE 1084 +#define IDS_PROP_PHY_SIZE_CANT_BE_DETECTED 1085 +#define IDS_PROP_ZEROS_TAIL_IS_ALLOWED 1086 +#define IDS_PROP_TAIL_SIZE 1087 +#define IDS_PROP_EMB_STUB_SIZE 1088 +#define IDS_PROP_NT_REPARSE 1089 +#define IDS_PROP_HARD_LINK 1090 +#define IDS_PROP_INODE 1091 +#define IDS_PROP_STREAM_ID 1092 +#define IDS_PROP_READ_ONLY 1093 +#define IDS_PROP_OUT_NAME 1094 +#define IDS_PROP_COPY_LINK 1095 +#define IDS_PROP_ARC_FILE_NAME 1096 +#define IDS_PROP_IS_HASH 1097 +#define IDS_PROP_CHANGE_TIME 1098 +#define IDS_PROP_USER_ID 1099 +#define IDS_PROP_GROUP_ID 1100 +#define IDS_PROP_DEVICE_MAJOR 1101 +#define IDS_PROP_DEVICE_MINOR 1102 +#define IDS_PROP_DEV_MAJOR 1103 +#define IDS_PROP_DEV_MINOR 1104 diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/RegistryAssociations.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/RegistryAssociations.cpp new file mode 100644 index 00000000..c4465da7 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/RegistryAssociations.cpp @@ -0,0 +1,167 @@ +// RegistryAssociations.cpp + +#include "StdAfx.h" + +#include "../../../Common/IntToString.h" +#include "../../../Common/StringConvert.h" +#include "../../../Common/StringToInt.h" + +#include "../../../Windows/Registry.h" + +#include "RegistryAssociations.h" + +using namespace NWindows; +using namespace NRegistry; + +namespace NRegistryAssoc { + +// static NSynchronization::CCriticalSection g_CriticalSection; + +static const TCHAR * const kClasses = TEXT("Software\\Classes\\"); +// static const TCHAR * const kShellNewKeyName = TEXT("ShellNew"); +// static const TCHAR * const kShellNewDataValueName = TEXT("Data"); +static const TCHAR * const kDefaultIconKeyName = TEXT("DefaultIcon"); +static const TCHAR * const kShellKeyName = TEXT("shell"); +static const TCHAR * const kOpenKeyName = TEXT("open"); +static const TCHAR * const kCommandKeyName = TEXT("command"); +static const char * const k7zipPrefix = "7-Zip."; + +static CSysString GetExtProgramKeyName(const CSysString &ext) +{ + return CSysString(k7zipPrefix) + ext; +} + +static CSysString GetFullKeyPath(HKEY hkey, const CSysString &name) +{ + CSysString s; + if (hkey != HKEY_CLASSES_ROOT) + s = kClasses; + return s + name; +} + +static CSysString GetExtKeyPath(HKEY hkey, const CSysString &ext) +{ + return GetFullKeyPath(hkey, (TEXT(".")) + ext); +} + +bool CShellExtInfo::ReadFromRegistry(HKEY hkey, const CSysString &ext) +{ + ProgramKey.Empty(); + IconPath.Empty(); + IconIndex = -1; + // NSynchronization::CCriticalSectionLock lock(g_CriticalSection); + { + CKey extKey; + if (extKey.Open(hkey, GetExtKeyPath(hkey, ext), KEY_READ) != ERROR_SUCCESS) + return false; + if (extKey.QueryValue(NULL, ProgramKey) != ERROR_SUCCESS) + return false; + } + { + CKey iconKey; + + if (iconKey.Open(hkey, GetFullKeyPath(hkey, ProgramKey + CSysString(CHAR_PATH_SEPARATOR) + kDefaultIconKeyName), KEY_READ) == ERROR_SUCCESS) + { + UString value; + if (iconKey.QueryValue(NULL, value) == ERROR_SUCCESS) + { + const int pos = value.ReverseFind(L','); + IconPath = value; + if (pos >= 0) + { + const wchar_t *end; + const Int32 index = ConvertStringToInt32((const wchar_t *)value + pos + 1, &end); + if (*end == 0) + { + // 9.31: if there is no icon index, we use -1. Is it OK? + if (pos != (int)value.Len() - 1) + IconIndex = (int)index; + IconPath.SetFrom(value, (unsigned)pos); + } + } + } + } + } + return true; +} + +bool CShellExtInfo::IsIt7Zip() const +{ + return ProgramKey.IsPrefixedBy_Ascii_NoCase(k7zipPrefix); +} + +LONG DeleteShellExtensionInfo(HKEY hkey, const CSysString &ext) +{ + // NSynchronization::CCriticalSectionLock lock(g_CriticalSection); + CKey rootKey; + rootKey.Attach(hkey); + LONG res = rootKey.RecurseDeleteKey(GetExtKeyPath(hkey, ext)); + // then we delete only 7-Zip.* key. + rootKey.RecurseDeleteKey(GetFullKeyPath(hkey, GetExtProgramKeyName(ext))); + rootKey.Detach(); + return res; +} + +LONG AddShellExtensionInfo(HKEY hkey, + const CSysString &ext, + const UString &programTitle, + const UString &programOpenCommand, + const UString &iconPath, int iconIndex + // , const void *shellNewData, int shellNewDataSize + ) +{ + LONG res = 0; + DeleteShellExtensionInfo(hkey, ext); + // NSynchronization::CCriticalSectionLock lock(g_CriticalSection); + CSysString programKeyName; + { + CSysString ext2 (ext); + if (iconIndex < 0) + ext2 = "*"; + programKeyName = GetExtProgramKeyName(ext2); + } + { + CKey extKey; + res = extKey.Create(hkey, GetExtKeyPath(hkey, ext)); + extKey.SetValue(NULL, programKeyName); + /* + if (shellNewData != NULL) + { + CKey shellNewKey; + shellNewKey.Create(extKey, kShellNewKeyName); + shellNewKey.SetValue(kShellNewDataValueName, shellNewData, shellNewDataSize); + } + */ + } + CKey programKey; + programKey.Create(hkey, GetFullKeyPath(hkey, programKeyName)); + programKey.SetValue(NULL, programTitle); + { + CKey iconKey; + UString iconPathFull = iconPath; + if (iconIndex < 0) + iconIndex = 0; + // if (iconIndex >= 0) + { + iconPathFull.Add_Char(','); + iconPathFull.Add_UInt32((UInt32)iconIndex); + } + iconKey.Create(programKey, kDefaultIconKeyName); + iconKey.SetValue(NULL, iconPathFull); + } + + CKey shellKey; + shellKey.Create(programKey, kShellKeyName); + shellKey.SetValue(NULL, TEXT("")); + + CKey openKey; + openKey.Create(shellKey, kOpenKeyName); + openKey.SetValue(NULL, TEXT("")); + + CKey commandKey; + commandKey.Create(openKey, kCommandKeyName); + commandKey.SetValue(NULL, programOpenCommand); + return res; +} + +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/RegistryAssociations.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/RegistryAssociations.h new file mode 100644 index 00000000..3d7b63db --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/RegistryAssociations.h @@ -0,0 +1,31 @@ +// RegistryAssociations.h + +#ifndef ZIP7_INC_REGISTRY_ASSOCIATIONS_H +#define ZIP7_INC_REGISTRY_ASSOCIATIONS_H + +#include "../../../Common/MyString.h" + +namespace NRegistryAssoc { + + struct CShellExtInfo + { + CSysString ProgramKey; + UString IconPath; + int IconIndex; + + bool ReadFromRegistry(HKEY hkey, const CSysString &ext); + bool IsIt7Zip() const; + }; + + LONG DeleteShellExtensionInfo(HKEY hkey, const CSysString &ext); + + LONG AddShellExtensionInfo(HKEY hkey, + const CSysString &ext, + const UString &programTitle, + const UString &programOpenCommand, + const UString &iconPath, int iconIndex + // , const void *shellNewData, int shellNewDataSize + ); +} + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/RegistryPlugins.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/RegistryPlugins.cpp new file mode 100644 index 00000000..87e5fa16 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/RegistryPlugins.cpp @@ -0,0 +1,145 @@ +// RegistryPlugins.cpp + +#include "StdAfx.h" + +/* +#include "../../../Windows/DLL.h" +#include "../../../Windows/FileFind.h" +#include "../../../Windows/PropVariant.h" + +#include "IFolder.h" +*/ +#include "RegistryPlugins.h" + +// using namespace NWindows; +// using namespace NFile; + +/* +typedef UINT32 (WINAPI * Func_GetPluginProperty)(PROPID propID, PROPVARIANT *value); + +static bool ReadPluginInfo(CPluginInfo &plugin, bool needCheckDll) +{ + if (needCheckDll) + { + NDLL::CLibrary lib; + if (!lib.LoadEx(plugin.FilePath, LOAD_LIBRARY_AS_DATAFILE)) + return false; + } + NDLL::CLibrary lib; + if (!lib.Load(plugin.FilePath)) + return false; + const + Func_GetPluginProperty + f_GetPluginProperty = ZIP7_GET_PROC_ADDRESS( + Func_GetPluginProperty, lib.Get_HMODULE(), + "GetPluginProperty"); + if (!f_GetPluginProperty) + return false; + + NCOM::CPropVariant prop; + if (f_GetPluginProperty(NPlugin::kType, &prop) != S_OK) + return false; + if (prop.vt == VT_EMPTY) + plugin.Type = kPluginTypeFF; + else if (prop.vt == VT_UI4) + plugin.Type = (EPluginType)prop.ulVal; + else + return false; + prop.Clear(); + + if (f_GetPluginProperty(NPlugin::kName, &prop) != S_OK) + return false; + if (prop.vt != VT_BSTR) + return false; + plugin.Name = prop.bstrVal; + prop.Clear(); + + if (f_GetPluginProperty(NPlugin::kClassID, &prop) != S_OK) + return false; + if (prop.vt == VT_EMPTY) + plugin.ClassID_Defined = false; + else if (prop.vt != VT_BSTR) + return false; + else + { + plugin.ClassID_Defined = true; + plugin.ClassID = *(const GUID *)(const void *)prop.bstrVal; + } + prop.Clear(); + return true; +*/ + +/* +{ + if (f_GetPluginProperty(NPlugin::kOptionsClassID, &prop) != S_OK) + return false; + if (prop.vt == VT_EMPTY) + plugin.OptionsClassID_Defined = false; + else if (prop.vt != VT_BSTR) + return false; + else + { + plugin.OptionsClassID_Defined = true; + plugin.OptionsClassID = *(const GUID *)(const void *)prop.bstrVal; + } +} +*/ + + /* + { + // very old 7-zip used agent plugin in "7-zip.dll" + // but then agent code was moved to 7zfm. + // so now we don't need to load "7-zip.dll" here + CPluginInfo plugin; + plugin.FilePath = baseFolderPrefix + FTEXT("7-zip.dll"); + if (::ReadPluginInfo(plugin, false)) + if (plugin.Type == kPluginTypeFF) + plugins.Add(plugin); + } + */ + /* + FString folderPath = NDLL::GetModuleDirPrefix(); + folderPath += "Plugins" STRING_PATH_SEPARATOR; + NFind::CEnumerator enumerator; + enumerator.SetDirPrefix(folderPath); + NFind::CFileInfo fi; + while (enumerator.Next(fi)) + { + if (fi.IsDir()) + continue; + CPluginInfo plugin; + plugin.FilePath = folderPath + fi.Name; + if (::ReadPluginInfo(plugin, true)) + if (plugin.Type == kPluginTypeFF) + plugins.Add(plugin); + } + */ + + /* + ReadPluginInfoList(plugins); + for (unsigned i = 0; i < plugins.Size();) + if (plugins[i].Type != kPluginTypeFF) + plugins.Delete(i); + else + i++; + */ + +/* +void ReadFileFolderPluginInfoList(CObjectVector &plugins) +{ + plugins.Clear(); + { + } + + { + CPluginInfo &plugin = plugins.AddNew(); + // p.FilePath.Empty(); + plugin.Type = kPluginTypeFF; + plugin.Name = "7-Zip"; + // plugin.ClassID = CLSID_CAgentArchiveHandler; + // plugin.ClassID_Defined = true; + // plugin.ClassID_Defined = false; + // plugin.OptionsClassID_Defined = false; + } +} +*/ diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/RegistryPlugins.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/RegistryPlugins.h new file mode 100644 index 00000000..1cb765a8 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/RegistryPlugins.h @@ -0,0 +1,29 @@ +// RegistryPlugins.h + +#ifndef ZIP7_INC_REGISTRY_PLUGINS_H +#define ZIP7_INC_REGISTRY_PLUGINS_H + +#include "../../../Common/MyString.h" + +/* +enum EPluginType +{ + kPluginTypeFF = 0 +}; + +struct CPluginInfo +{ + EPluginType Type; + // bool ClassID_Defined; + // bool OptionsClassID_Defined; + // FString FilePath; + // UString Name; + // CLSID ClassID; + // CLSID OptionsClassID; +}; + +// void ReadPluginInfoList(CObjectVector &plugins); +// void ReadFileFolderPluginInfoList(CObjectVector &plugins); +*/ + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/RegistryUtils.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/RegistryUtils.cpp new file mode 100644 index 00000000..0284591e --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/RegistryUtils.cpp @@ -0,0 +1,194 @@ +// RegistryUtils.cpp + +#include "StdAfx.h" + +#include "../../../Common/IntToString.h" + +#include "../../../Windows/Registry.h" + +#include "RegistryUtils.h" + +using namespace NWindows; +using namespace NRegistry; + +#define REG_PATH_7Z TEXT("Software") TEXT(STRING_PATH_SEPARATOR) TEXT("7-Zip") + +static LPCTSTR const kCUBasePath = REG_PATH_7Z; +static LPCTSTR const kCU_FMPath = REG_PATH_7Z TEXT(STRING_PATH_SEPARATOR) TEXT("FM"); +// static LPCTSTR const kLM_Path = REG_PATH_7Z TEXT(STRING_PATH_SEPARATOR) TEXT("FM"); + +static LPCWSTR const kLangValueName = L"Lang"; + +static LPCWSTR const kViewer = L"Viewer"; +static LPCWSTR const kEditor = L"Editor"; +static LPCWSTR const kDiff = L"Diff"; +static LPCWSTR const kVerCtrlPath = L"7vc"; + +static LPCTSTR const kShowDots = TEXT("ShowDots"); +static LPCTSTR const kShowRealFileIcons = TEXT("ShowRealFileIcons"); +static LPCTSTR const kFullRow = TEXT("FullRow"); +static LPCTSTR const kShowGrid = TEXT("ShowGrid"); +static LPCTSTR const kSingleClick = TEXT("SingleClick"); +static LPCTSTR const kAlternativeSelection = TEXT("AlternativeSelection"); +// static LPCTSTR const kUnderline = TEXT("Underline"); + +static LPCTSTR const kShowSystemMenu = TEXT("ShowSystemMenu"); + +// static LPCTSTR const kLockMemoryAdd = TEXT("LockMemoryAdd"); +static LPCTSTR const kLargePages = TEXT("LargePages"); + +static LPCTSTR const kFlatViewName = TEXT("FlatViewArc"); +// static LPCTSTR const kShowDeletedFiles = TEXT("ShowDeleted"); + +static void SaveCuString(LPCTSTR keyPath, LPCWSTR valuePath, LPCWSTR value) +{ + CKey key; + key.Create(HKEY_CURRENT_USER, keyPath); + key.SetValue(valuePath, value); +} + +static void ReadCuString(LPCTSTR keyPath, LPCWSTR valuePath, UString &res) +{ + res.Empty(); + CKey key; + if (key.Open(HKEY_CURRENT_USER, keyPath, KEY_READ) == ERROR_SUCCESS) + key.QueryValue(valuePath, res); +} + +void SaveRegLang(const UString &path) { SaveCuString(kCUBasePath, kLangValueName, path); } +void ReadRegLang(UString &path) { ReadCuString(kCUBasePath, kLangValueName, path); } + +void SaveRegEditor(bool useEditor, const UString &path) { SaveCuString(kCU_FMPath, useEditor ? kEditor : kViewer, path); } +void ReadRegEditor(bool useEditor, UString &path) { ReadCuString(kCU_FMPath, useEditor ? kEditor : kViewer, path); } + +void SaveRegDiff(const UString &path) { SaveCuString(kCU_FMPath, kDiff, path); } +void ReadRegDiff(UString &path) { ReadCuString(kCU_FMPath, kDiff, path); } + +void ReadReg_VerCtrlPath(UString &path) { ReadCuString(kCU_FMPath, kVerCtrlPath, path); } + +static void Save7ZipOption(LPCTSTR value, bool enabled) +{ + CKey key; + key.Create(HKEY_CURRENT_USER, kCUBasePath); + key.SetValue(value, enabled); +} + +static void SaveOption(LPCTSTR value, bool enabled) +{ + CKey key; + key.Create(HKEY_CURRENT_USER, kCU_FMPath); + key.SetValue(value, enabled); +} + +static bool Read7ZipOption(LPCTSTR value, bool defaultValue) +{ + CKey key; + if (key.Open(HKEY_CURRENT_USER, kCUBasePath, KEY_READ) == ERROR_SUCCESS) + { + bool enabled; + if (key.GetValue_bool_IfOk(value, enabled) == ERROR_SUCCESS) + return enabled; + } + return defaultValue; +} + +static void ReadOption(CKey &key, LPCTSTR name, bool &dest) +{ + key.GetValue_bool_IfOk(name, dest); +} + +/* +static void SaveLmOption(LPCTSTR value, bool enabled) +{ + CKey key; + key.Create(HKEY_LOCAL_MACHINE, kLM_Path); + key.SetValue(value, enabled); +} + +static bool ReadLmOption(LPCTSTR value, bool defaultValue) +{ + CKey key; + if (key.Open(HKEY_LOCAL_MACHINE, kLM_Path, KEY_READ) == ERROR_SUCCESS) + { + bool enabled; + if (key.QueryValue(value, enabled) == ERROR_SUCCESS) + return enabled; + } + return defaultValue; +} +*/ + +void CFmSettings::Save() const +{ + SaveOption(kShowDots, ShowDots); + SaveOption(kShowRealFileIcons, ShowRealFileIcons); + SaveOption(kFullRow, FullRow); + SaveOption(kShowGrid, ShowGrid); + SaveOption(kSingleClick, SingleClick); + SaveOption(kAlternativeSelection, AlternativeSelection); + // SaveOption(kUnderline, Underline); + + SaveOption(kShowSystemMenu, ShowSystemMenu); +} + +void CFmSettings::Load() +{ + ShowDots = false; + ShowRealFileIcons = false; + /* if (FullRow == false), we can use mouse click on another columns + to select group of files. We need to implement additional + way to select files in any column as in Explorer. + Then we can enable (FullRow == true) default mode. */ + // FullRow = true; + FullRow = false; + ShowGrid = false; + SingleClick = false; + AlternativeSelection = false; + // Underline = false; + + ShowSystemMenu = false; + + CKey key; + if (key.Open(HKEY_CURRENT_USER, kCU_FMPath, KEY_READ) == ERROR_SUCCESS) + { + ReadOption(key, kShowDots, ShowDots); + ReadOption(key, kShowRealFileIcons, ShowRealFileIcons); + ReadOption(key, kFullRow, FullRow); + ReadOption(key, kShowGrid, ShowGrid); + ReadOption(key, kSingleClick, SingleClick); + ReadOption(key, kAlternativeSelection, AlternativeSelection); + // ReadOption(key, kUnderline, Underline); + + ReadOption(key, kShowSystemMenu, ShowSystemMenu ); + } +} + + +// void SaveLockMemoryAdd(bool enable) { SaveLmOption(kLockMemoryAdd, enable); } +// bool ReadLockMemoryAdd() { return ReadLmOption(kLockMemoryAdd, true); } + +void SaveLockMemoryEnable(bool enable) { Save7ZipOption(kLargePages, enable); } +bool ReadLockMemoryEnable() { return Read7ZipOption(kLargePages, false); } + +static CSysString GetFlatViewName(UInt32 panelIndex) +{ + TCHAR panelString[16]; + ConvertUInt32ToString(panelIndex, panelString); + return (CSysString)kFlatViewName + panelString; +} + +void SaveFlatView(UInt32 panelIndex, bool enable) { SaveOption(GetFlatViewName(panelIndex), enable); } + +bool ReadFlatView(UInt32 panelIndex) +{ + bool enabled = false; + CKey key; + if (key.Open(HKEY_CURRENT_USER, kCU_FMPath, KEY_READ) == ERROR_SUCCESS) + ReadOption(key, GetFlatViewName(panelIndex), enabled); + return enabled; +} + +/* +void Save_ShowDeleted(bool enable) { SaveOption(kShowDeletedFiles, enable); } +bool Read_ShowDeleted() { return ReadOption(kShowDeletedFiles, false); } +*/ diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/RegistryUtils.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/RegistryUtils.h new file mode 100644 index 00000000..8b4cdf0a --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/RegistryUtils.h @@ -0,0 +1,50 @@ +// RegistryUtils.h + +#ifndef ZIP7_INC_REGISTRY_UTILS_H +#define ZIP7_INC_REGISTRY_UTILS_H + +#include "../../../Common/MyTypes.h" +#include "../../../Common/MyString.h" + +void SaveRegLang(const UString &path); +void ReadRegLang(UString &path); + +void SaveRegEditor(bool useEditor, const UString &path); +void ReadRegEditor(bool useEditor, UString &path); + +void SaveRegDiff(const UString &path); +void ReadRegDiff(UString &path); + +void ReadReg_VerCtrlPath(UString &path); + +struct CFmSettings +{ + bool ShowDots; + bool ShowRealFileIcons; + bool FullRow; + bool ShowGrid; + bool SingleClick; + bool AlternativeSelection; + // bool Underline; + + bool ShowSystemMenu; + + void Save() const; + void Load(); +}; + +// void SaveLockMemoryAdd(bool enable); +// bool ReadLockMemoryAdd(); + +bool ReadLockMemoryEnable(); +void SaveLockMemoryEnable(bool enable); + +void SaveFlatView(UInt32 panelIndex, bool enable); +bool ReadFlatView(UInt32 panelIndex); + +/* +void Save_ShowDeleted(bool enable); +bool Read_ShowDeleted(); +*/ + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/RootFolder.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/RootFolder.cpp new file mode 100644 index 00000000..b512f3ba --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/RootFolder.cpp @@ -0,0 +1,342 @@ +// RootFolder.cpp + +#include "StdAfx.h" + +#include "../../../Common/MyWindows.h" + +#if defined(__MINGW32__) || defined(__MINGW64__) +#include +#else +#include +#endif + +#include "../../../Common/StringConvert.h" + +#include "../../../Windows/DLL.h" +#include "../../../Windows/FileName.h" +#include "../../../Windows/PropVariant.h" + +#include "../../PropID.h" + +#if defined(_WIN32) && !defined(UNDER_CE) +#define USE_WIN_PATHS +#endif + +static const unsigned kNumRootFolderItems = + #ifdef USE_WIN_PATHS + 4 + #else + 1 + #endif + ; + + +#include "FSFolder.h" +#include "LangUtils.h" +#ifdef USE_WIN_PATHS +#include "NetFolder.h" +#include "FSDrives.h" +#include "AltStreamsFolder.h" +#endif +#include "RootFolder.h" +#include "SysIconUtils.h" + +#include "resource.h" + +using namespace NWindows; + +static const Byte kProps[] = +{ + kpidName +}; + +UString RootFolder_GetName_Computer(int &iconIndex); +UString RootFolder_GetName_Computer(int &iconIndex) +{ + #ifdef USE_WIN_PATHS + iconIndex = Shell_GetFileInfo_SysIconIndex_for_CSIDL(CSIDL_DRIVES); + #else + iconIndex = Shell_GetFileInfo_SysIconIndex_for_Path(FSTRING_PATH_SEPARATOR, FILE_ATTRIBUTE_DIRECTORY); + #endif + return LangString(IDS_COMPUTER); +} + +UString RootFolder_GetName_Network(int &iconIndex); +UString RootFolder_GetName_Network(int &iconIndex) +{ + iconIndex = Shell_GetFileInfo_SysIconIndex_for_CSIDL(CSIDL_NETWORK); + return LangString(IDS_NETWORK); +} + +UString RootFolder_GetName_Documents(int &iconIndex); +UString RootFolder_GetName_Documents(int &iconIndex) +{ + iconIndex = Shell_GetFileInfo_SysIconIndex_for_CSIDL(CSIDL_PERSONAL); + return LangString(IDS_DOCUMENTS); +} + +enum +{ + ROOT_INDEX_COMPUTER = 0 + #ifdef USE_WIN_PATHS + , ROOT_INDEX_DOCUMENTS + , ROOT_INDEX_NETWORK + , ROOT_INDEX_VOLUMES + #endif +}; + +#ifdef USE_WIN_PATHS +static const char * const kVolPrefix = "\\\\."; +#endif + +void CRootFolder::Init() +{ + _names[ROOT_INDEX_COMPUTER] = RootFolder_GetName_Computer(_iconIndices[ROOT_INDEX_COMPUTER]); + #ifdef USE_WIN_PATHS + _names[ROOT_INDEX_DOCUMENTS] = RootFolder_GetName_Documents(_iconIndices[ROOT_INDEX_DOCUMENTS]); + _names[ROOT_INDEX_NETWORK] = RootFolder_GetName_Network(_iconIndices[ROOT_INDEX_NETWORK]); + _names[ROOT_INDEX_VOLUMES] = kVolPrefix; + _iconIndices[ROOT_INDEX_VOLUMES] = Shell_GetFileInfo_SysIconIndex_for_CSIDL(CSIDL_DRIVES); + #endif +} + +Z7_COM7F_IMF(CRootFolder::LoadItems()) +{ + Init(); + return S_OK; +} + +Z7_COM7F_IMF(CRootFolder::GetNumberOfItems(UInt32 *numItems)) +{ + *numItems = kNumRootFolderItems; + return S_OK; +} + +Z7_COM7F_IMF(CRootFolder::GetProperty(UInt32 itemIndex, PROPID propID, PROPVARIANT *value)) +{ + NCOM::CPropVariant prop; + switch (propID) + { + case kpidIsDir: prop = true; break; + case kpidName: prop = _names[itemIndex]; break; + } + prop.Detach(value); + return S_OK; +} + +#if !defined(Z7_WIN32_WINNT_MIN) || Z7_WIN32_WINNT_MIN < 0x0400 // nt4 +#define Z7_USE_DYN_SHGetSpecialFolderPath +#endif + +#ifdef Z7_USE_DYN_SHGetSpecialFolderPath +typedef BOOL (WINAPI *Func_SHGetSpecialFolderPathW)(HWND hwnd, LPWSTR pszPath, int csidl, BOOL fCreate); +typedef BOOL (WINAPI *Func_SHGetSpecialFolderPathA)(HWND hwnd, LPSTR pszPath, int csidl, BOOL fCreate); +#endif + +static UString GetMyDocsPath() +{ + UString us; + WCHAR s[MAX_PATH + 1]; +#ifdef Z7_USE_DYN_SHGetSpecialFolderPath +#ifdef UNDER_CE + #define shell_name TEXT("coredll.dll") +#else + #define shell_name TEXT("shell32.dll") +#endif + Func_SHGetSpecialFolderPathW getW = Z7_GET_PROC_ADDRESS( + Func_SHGetSpecialFolderPathW, GetModuleHandle(shell_name), + "SHGetSpecialFolderPathW"); + if (getW && getW +#else + if (SHGetSpecialFolderPathW +#endif + (NULL, s, CSIDL_PERSONAL, FALSE)) + us = s; + #ifndef _UNICODE + else + { + CHAR s2[MAX_PATH + 1]; +#ifdef Z7_USE_DYN_SHGetSpecialFolderPath + Func_SHGetSpecialFolderPathA getA = Z7_GET_PROC_ADDRESS( + Func_SHGetSpecialFolderPathA, ::GetModuleHandleA("shell32.dll"), + "SHGetSpecialFolderPathA"); + if (getA && getA +#else + if (SHGetSpecialFolderPathA +#endif + (NULL, s2, CSIDL_PERSONAL, FALSE)) + us = GetUnicodeString(s2); + } + #endif + NFile::NName::NormalizeDirPathPrefix(us); + return us; +} + +Z7_COM7F_IMF(CRootFolder::BindToFolder(UInt32 index, IFolderFolder **resultFolder)) +{ + *resultFolder = NULL; + CMyComPtr subFolder; + + #ifdef USE_WIN_PATHS + if (index == ROOT_INDEX_COMPUTER || index == ROOT_INDEX_VOLUMES) + { + CFSDrives *fsDrivesSpec = new CFSDrives; + subFolder = fsDrivesSpec; + fsDrivesSpec->Init(index == ROOT_INDEX_VOLUMES); + } + else if (index == ROOT_INDEX_NETWORK) + { + CNetFolder *netFolderSpec = new CNetFolder; + subFolder = netFolderSpec; + netFolderSpec->Init(NULL, NULL, _names[ROOT_INDEX_NETWORK] + WCHAR_PATH_SEPARATOR); + } + else if (index == ROOT_INDEX_DOCUMENTS) + { + UString s = GetMyDocsPath(); + if (!s.IsEmpty()) + { + NFsFolder::CFSFolder *fsFolderSpec = new NFsFolder::CFSFolder; + subFolder = fsFolderSpec; + RINOK(fsFolderSpec->Init(us2fs(s))) + } + } + #else + if (index == ROOT_INDEX_COMPUTER) + { + NFsFolder::CFSFolder *fsFolder = new NFsFolder::CFSFolder; + subFolder = fsFolder; + fsFolder->InitToRoot(); + } + #endif + else + return E_INVALIDARG; + + *resultFolder = subFolder.Detach(); + return S_OK; +} + +static bool AreEqualNames(const UString &path, const wchar_t *name) +{ + unsigned len = MyStringLen(name); + if (len > path.Len() || len + 1 < path.Len()) + return false; + if (len + 1 == path.Len() && !IS_PATH_SEPAR(path[len])) + return false; + return path.IsPrefixedBy(name); +} + +Z7_COM7F_IMF(CRootFolder::BindToFolder(const wchar_t *name, IFolderFolder **resultFolder)) +{ + *resultFolder = NULL; + UString name2 = name; + name2.Trim(); + + if (name2.IsEmpty()) + { + CRootFolder *rootFolderSpec = new CRootFolder; + CMyComPtr rootFolder = rootFolderSpec; + rootFolderSpec->Init(); + *resultFolder = rootFolder.Detach(); + return S_OK; + } + + for (unsigned i = 0; i < kNumRootFolderItems; i++) + if (AreEqualNames(name2, _names[i])) + return BindToFolder((UInt32)i, resultFolder); + + #ifdef USE_WIN_PATHS + if (AreEqualNames(name2, L"My Documents") || + AreEqualNames(name2, L"Documents")) + return BindToFolder((UInt32)ROOT_INDEX_DOCUMENTS, resultFolder); + #else + if (name2.IsEqualTo(STRING_PATH_SEPARATOR)) + return BindToFolder((UInt32)ROOT_INDEX_COMPUTER, resultFolder); + #endif + + if (AreEqualNames(name2, L"My Computer") || + AreEqualNames(name2, L"Computer")) + return BindToFolder((UInt32)ROOT_INDEX_COMPUTER, resultFolder); + + if (name2.IsEqualTo(STRING_PATH_SEPARATOR)) + { + CMyComPtr subFolder = this; + *resultFolder = subFolder.Detach(); + return S_OK; + } + + if (name2.Len() < 2) + return E_INVALIDARG; + + CMyComPtr subFolder; + + #ifdef USE_WIN_PATHS + if (name2.IsPrefixedBy_Ascii_NoCase(kVolPrefix)) + { + CFSDrives *folderSpec = new CFSDrives; + subFolder = folderSpec; + folderSpec->Init(true); + } + else if (name2.IsEqualTo(NFile::NName::kSuperPathPrefix)) + { + CFSDrives *folderSpec = new CFSDrives; + subFolder = folderSpec; + folderSpec->Init(false, true); + } + else if (name2.Back() == ':' + && (name2.Len() != 2 || !NFile::NName::IsDrivePath2(name2))) + { + NAltStreamsFolder::CAltStreamsFolder *folderSpec = new NAltStreamsFolder::CAltStreamsFolder; + subFolder = folderSpec; + if (folderSpec->Init(us2fs(name2)) != S_OK) + return E_INVALIDARG; + } + else + #endif + { + NFile::NName::NormalizeDirPathPrefix(name2); + NFsFolder::CFSFolder *fsFolderSpec = new NFsFolder::CFSFolder; + subFolder = fsFolderSpec; + if (fsFolderSpec->Init(us2fs(name2)) != S_OK) + { + #ifdef USE_WIN_PATHS + if (IS_PATH_SEPAR(name2[0])) + { + CNetFolder *netFolderSpec = new CNetFolder; + subFolder = netFolderSpec; + netFolderSpec->Init(name2); + } + else + #endif + return E_INVALIDARG; + } + } + + *resultFolder = subFolder.Detach(); + return S_OK; +} + +Z7_COM7F_IMF(CRootFolder::BindToParentFolder(IFolderFolder **resultFolder)) +{ + *resultFolder = NULL; + return S_OK; +} + +IMP_IFolderFolder_Props(CRootFolder) + +Z7_COM7F_IMF(CRootFolder::GetFolderProperty(PROPID propID, PROPVARIANT *value)) +{ + NCOM::CPropVariant prop; + switch (propID) + { + case kpidType: prop = "RootFolder"; break; + case kpidPath: prop = ""; break; + } + prop.Detach(value); + return S_OK; +} + +Z7_COM7F_IMF(CRootFolder::GetSystemIconIndex(UInt32 index, Int32 *iconIndex)) +{ + *iconIndex = _iconIndices[index]; + return S_OK; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/RootFolder.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/RootFolder.h new file mode 100644 index 00000000..3f0a31b3 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/RootFolder.h @@ -0,0 +1,24 @@ +// RootFolder.h + +#ifndef ZIP7_INC_ROOT_FOLDER_H +#define ZIP7_INC_ROOT_FOLDER_H + +#include "../../../Common/MyCom.h" +#include "../../../Common/MyString.h" + +#include "IFolder.h" + +const unsigned kNumRootFolderItems_Max = 4; + +Z7_CLASS_IMP_NOQIB_2( + CRootFolder + , IFolderFolder + , IFolderGetSystemIconIndex +) + UString _names[kNumRootFolderItems_Max]; + int _iconIndices[kNumRootFolderItems_Max]; +public: + void Init(); +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SettingsPage.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SettingsPage.cpp new file mode 100644 index 00000000..8b5983af --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SettingsPage.cpp @@ -0,0 +1,453 @@ +// SettingsPage.cpp + +#include "StdAfx.h" + +#include + +// #include "../../../Common/IntToString.h" +// #include "../../../Common/StringConvert.h" +#include "../../../Common/StringToInt.h" + +#ifndef UNDER_CE +#include "../../../Windows/ErrorMsg.h" +#include "../../../Windows/MemoryLock.h" +#include "../../../Windows/System.h" +#endif + +#include "../Explorer/MyMessages.h" + +#include "../Common/ZipRegistry.h" + +#include "HelpUtils.h" +#include "LangUtils.h" +#include "RegistryUtils.h" +#include "SettingsPage.h" +#include "SettingsPageRes.h" + +using namespace NWindows; + +#ifdef Z7_LANG +static const UInt32 kLangIDs[] = +{ + IDX_SETTINGS_SHOW_DOTS, + IDX_SETTINGS_SHOW_REAL_FILE_ICONS, + IDX_SETTINGS_SHOW_SYSTEM_MENU, + IDX_SETTINGS_FULL_ROW, + IDX_SETTINGS_SHOW_GRID, + IDX_SETTINGS_SINGLE_CLICK, + IDX_SETTINGS_ALTERNATIVE_SELECTION, + IDX_SETTINGS_LARGE_PAGES, + IDT_MEM_USAGE_EXTRACT + // , IDT_COMPRESS_MEMORY +}; +#endif + +#define kSettingsTopic "FM/options.htm#settings" + +extern bool IsLargePageSupported(); + +/* +static void AddMemSize(UString &res, UInt64 size, bool needRound = false) +{ + char c; + unsigned moveBits = 0; + if (needRound) + { + UInt64 rn = 0; + if (size >= (1 << 31)) + rn = (1 << 28) - 1; + UInt32 kRound = (1 << 20) - 1; + if (rn < kRound) + rn = kRound; + size += rn; + size &= ~rn; + } + if (size >= ((UInt64)1 << 31) && (size & 0x3FFFFFFF) == 0) + { moveBits = 30; c = 'G'; } + else + { moveBits = 20; c = 'M'; } + res.Add_UInt64(size >> moveBits); + res.Add_Space(); + if (moveBits != 0) + res += c; + res += 'B'; +} + + +int CSettingsPage::AddMemComboItem(UInt64 size, UInt64 percents, bool isDefault) +{ + UString sUser; + UString sRegistry; + if (size == 0) + { + UString s; + s.Add_UInt64(percents); + s += '%'; + if (isDefault) + sUser = "* "; + else + sRegistry = s; + sUser += s; + } + else + { + AddMemSize(sUser, size); + sRegistry = sUser; + for (;;) + { + int pos = sRegistry.Find(L' '); + if (pos < 0) + break; + sRegistry.Delete(pos); + } + if (!sRegistry.IsEmpty()) + if (sRegistry.Back() == 'B') + sRegistry.DeleteBack(); + } + const int index = (int)_memCombo.AddString(sUser); + _memCombo.SetItemData(index, _memLimitStrings.Size()); + _memLimitStrings.Add(sRegistry); + return index; +} +*/ + +bool CSettingsPage::OnInit() +{ + _initMode = true; + _wasChanged = false; + _largePages_wasChanged = false; + _memx_wasChanged = false; + /* + _wasChanged_MemLimit = false; + _memLimitStrings.Clear(); + _memCombo.Attach(GetItem(IDC_SETTINGS_MEM)); + */ + +#ifdef Z7_LANG + LangSetDlgItems(*this, kLangIDs, Z7_ARRAY_SIZE(kLangIDs)); +#endif + + CFmSettings st; + st.Load(); + + CheckButton(IDX_SETTINGS_SHOW_DOTS, st.ShowDots); + CheckButton(IDX_SETTINGS_SHOW_REAL_FILE_ICONS, st.ShowRealFileIcons); + CheckButton(IDX_SETTINGS_FULL_ROW, st.FullRow); + CheckButton(IDX_SETTINGS_SHOW_GRID, st.ShowGrid); + CheckButton(IDX_SETTINGS_SINGLE_CLICK, st.SingleClick); + CheckButton(IDX_SETTINGS_ALTERNATIVE_SELECTION, st.AlternativeSelection); + // CheckButton(IDX_SETTINGS_UNDERLINE, st.Underline); + + CheckButton(IDX_SETTINGS_SHOW_SYSTEM_MENU, st.ShowSystemMenu); + + if (IsLargePageSupported()) + CheckButton(IDX_SETTINGS_LARGE_PAGES, ReadLockMemoryEnable()); + else + EnableItem(IDX_SETTINGS_LARGE_PAGES, false); + + + /* + NCompression::CMemUse mu; + bool needSetCur = NCompression::MemLimit_Load(mu); + UInt64 curMemLimit; + { + AddMemComboItem(0, 90, true); + _memCombo.SetCurSel(0); + } + if (mu.IsPercent) + { + const int index = AddMemComboItem(0, mu.Val); + _memCombo.SetCurSel(index); + needSetCur = false; + } + { + _ramSize = (size_t)sizeof(size_t) << 29; + _ramSize_Defined = NSystem::GetRamSize(_ramSize); + UString s; + if (_ramSize_Defined) + { + s += "/ "; + AddMemSize(s, _ramSize, true); + } + SetItemText(IDT_SETTINGS_MEM_RAM, s); + + curMemLimit = mu.GetBytes(_ramSize); + + // size = 100 << 20; // for debug only; + for (unsigned i = (27) * 2;; i++) + { + UInt64 size = (UInt64)(2 + (i & 1)) << (i / 2); + if (i > (20 + sizeof(size_t) * 3 * 1 - 1) * 2) + size = (UInt64)(Int64)-1; + if (needSetCur && (size >= curMemLimit)) + { + const int index = AddMemComboItem(curMemLimit); + _memCombo.SetCurSel(index); + needSetCur = false; + if (size == curMemLimit) + continue; + } + if (size == (UInt64)(Int64)-1) + break; + AddMemComboItem(size); + } + } + */ + + // EnableSubItems(); + + + { + size_t ramSize = (size_t)sizeof(size_t) << 29; + const bool ramSize_defined = NWindows::NSystem::GetRamSize(ramSize); + // ramSize *= 10; // for debug + UInt32 ramSize_GB = (UInt32)(((UInt64)ramSize + (1u << 29)) >> 30); + if (ramSize_GB == 0) + ramSize_GB = 1; + UString s ("GB"); + if (ramSize_defined) + { + s += " / "; + s.Add_UInt64(ramSize_GB); + s += " GB (RAM)"; + } + SetItemText(IDT_SETTINGS_MEM_GB, s); + + const UINT valMin = 1; + UINT valMax = 64; // 64GB for RAR7 + if (ramSize_defined /* && ramSize_GB > valMax */) + { + const UINT k_max_val = 1u << 14; + if (ramSize_GB >= k_max_val) + valMax = k_max_val; + else if (ramSize_GB > 1) + valMax = (UINT)ramSize_GB - 1; + else + valMax = 1; + } + + UInt32 limit = NExtract::Read_LimitGB(); + if (limit != 0 && limit != (UInt32)(Int32)-1) + CheckButton(IDX_SETTINGS_MEM_SET, true); + else + { + limit = 4; + EnableSpin(false); + } + SendItemMessage(IDC_SETTINGS_MEM_SPIN, UDM_SETRANGE, 0, MAKELPARAM(valMax, valMin)); // Sets the controls direction + // UDM_SETPOS doesn't set value larger than max value (valMax) of range: + SendItemMessage(IDC_SETTINGS_MEM_SPIN, UDM_SETPOS, 0, limit); + s.Empty(); + s.Add_UInt32(limit); + SetItemText(IDE_SETTINGS_MEM_SPIN_EDIT, s); + } + + _initMode = false; + return CPropertyPage::OnInit(); +} + + +void CSettingsPage::EnableSpin(bool enable) +{ + EnableItem(IDC_SETTINGS_MEM_SPIN, enable); + EnableItem(IDE_SETTINGS_MEM_SPIN_EDIT, enable); +} + + +/* +void CSettingsPage::EnableSubItems() +{ + EnableItem(IDX_SETTINGS_UNDERLINE, IsButtonCheckedBool(IDX_SETTINGS_SINGLE_CLICK)); +} +*/ + +/* +static void AddSize_MB(UString &s, UInt64 size) +{ + s.Add_UInt64((size + (1 << 20) - 1) >> 20); + s += " MB"; +} +*/ + +LONG CSettingsPage::OnApply() +{ + if (_wasChanged) + { + CFmSettings st; + st.ShowDots = IsButtonCheckedBool(IDX_SETTINGS_SHOW_DOTS); + st.ShowRealFileIcons = IsButtonCheckedBool(IDX_SETTINGS_SHOW_REAL_FILE_ICONS); + st.FullRow = IsButtonCheckedBool(IDX_SETTINGS_FULL_ROW); + st.ShowGrid = IsButtonCheckedBool(IDX_SETTINGS_SHOW_GRID); + st.SingleClick = IsButtonCheckedBool(IDX_SETTINGS_SINGLE_CLICK); + st.AlternativeSelection = IsButtonCheckedBool(IDX_SETTINGS_ALTERNATIVE_SELECTION); + // st.Underline = IsButtonCheckedBool(IDX_SETTINGS_UNDERLINE); + + st.ShowSystemMenu = IsButtonCheckedBool(IDX_SETTINGS_SHOW_SYSTEM_MENU); + + st.Save(); + _wasChanged = false; + } + + #ifndef UNDER_CE + if (_largePages_wasChanged) + { + if (IsLargePageSupported()) + { + const bool enable = IsButtonCheckedBool(IDX_SETTINGS_LARGE_PAGES); + NSecurity::EnablePrivilege_LockMemory(enable); + SaveLockMemoryEnable(enable); + } + _largePages_wasChanged = false; + } + #endif + + if (_memx_wasChanged) + { + UInt32 val = (UInt32)(Int32)-1; + if (IsButtonCheckedBool(IDX_SETTINGS_MEM_SET)) + { + UString s; + GetItemText(IDE_SETTINGS_MEM_SPIN_EDIT, s); + const wchar_t *end; + val = ConvertStringToUInt32(s.Ptr(), &end); + if (s.IsEmpty() || *end != 0 || val > (1u << 30)) + { + // L"Incorrect value" + ShowErrorMessage(*this, NError::MyFormatMessage(E_INVALIDARG)); + return PSNRET_INVALID; + } + } + NExtract::Save_LimitGB(val); + _memx_wasChanged = false; + } + + /* + if (_wasChanged_MemLimit) + { + const unsigned index = (int)_memCombo.GetItemData_of_CurSel(); + const UString str = _memLimitStrings[index]; + + bool needSave = true; + + NCompression::CMemUse mu; + + if (_ramSize_Defined) + mu.Parse(str); + if (mu.IsDefined) + { + const UInt64 usage64 = mu.GetBytes(_ramSize); + if (_ramSize <= usage64) + { + UString s2 = LangString(IDT_COMPRESS_MEMORY); + if (s2.IsEmpty()) + GetItemText(IDT_COMPRESS_MEMORY, s2); + UString s; + + s += "The selected value is not safe for system performance."; + s.Add_LF(); + s += "The memory consumption for compression operation will exceed RAM size."; + s.Add_LF(); + s.Add_LF(); + AddSize_MB(s, usage64); + + if (!s2.IsEmpty()) + { + s += " : "; + s += s2; + } + + s.Add_LF(); + AddSize_MB(s, _ramSize); + s += " : RAM"; + + s.Add_LF(); + s.Add_LF(); + s += "Are you sure you want set that unsafe value for memory usage?"; + + int res = MessageBoxW(*this, s, L"7-Zip", MB_YESNOCANCEL | MB_ICONQUESTION); + if (res != IDYES) + needSave = false; + } + } + + if (needSave) + { + NCompression::MemLimit_Save(str); + _wasChanged_MemLimit = false; + } + else + return PSNRET_INVALID_NOCHANGEPAGE; + } + */ + + return PSNRET_NOERROR; +} + +void CSettingsPage::OnNotifyHelp() +{ + ShowHelpWindow(kSettingsTopic); +} + +bool CSettingsPage::OnCommand(unsigned code, unsigned itemID, LPARAM param) +{ + if (!_initMode) + { + if (code == EN_CHANGE && itemID == IDE_SETTINGS_MEM_SPIN_EDIT) + { + _memx_wasChanged = true; + Changed(); + } + /* + if (code == CBN_SELCHANGE) + { + switch (itemID) + { + case IDC_SETTINGS_MEM: + { + _wasChanged_MemLimit = true; + Changed(); + break; + } + } + } + */ + } + return CPropertyPage::OnCommand(code, itemID, param); +} + +bool CSettingsPage::OnButtonClicked(unsigned buttonID, HWND buttonHWND) +{ + switch (buttonID) + { + case IDX_SETTINGS_SINGLE_CLICK: + /* + EnableSubItems(); + break; + */ + case IDX_SETTINGS_SHOW_DOTS: + case IDX_SETTINGS_SHOW_SYSTEM_MENU: + case IDX_SETTINGS_SHOW_REAL_FILE_ICONS: + case IDX_SETTINGS_FULL_ROW: + case IDX_SETTINGS_SHOW_GRID: + case IDX_SETTINGS_ALTERNATIVE_SELECTION: + _wasChanged = true; + break; + + case IDX_SETTINGS_LARGE_PAGES: + _largePages_wasChanged = true; + break; + + case IDX_SETTINGS_MEM_SET: + { + _memx_wasChanged = true; + EnableSpin(IsButtonCheckedBool(IDX_SETTINGS_MEM_SET)); + break; + } + + default: + return CPropertyPage::OnButtonClicked(buttonID, buttonHWND); + } + + Changed(); + return true; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SettingsPage.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SettingsPage.h new file mode 100644 index 00000000..586b2535 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SettingsPage.h @@ -0,0 +1,37 @@ +// SettingsPage.h + +#ifndef ZIP7_INC_SETTINGS_PAGE_H +#define ZIP7_INC_SETTINGS_PAGE_H + +#include "../../../Windows/Control/PropertyPage.h" +// #include "../../../Windows/Control/ComboBox.h" +#include "../../../Windows/Control/Edit.h" + +class CSettingsPage: public NWindows::NControl::CPropertyPage +{ + bool _wasChanged; + bool _largePages_wasChanged; + bool _memx_wasChanged; + bool _initMode; + /* + bool _wasChanged_MemLimit; + NWindows::NControl::CComboBox _memCombo; + UStringVector _memLimitStrings; + UInt64 _ramSize; + UInt64 _ramSize_Defined; + + int AddMemComboItem(UInt64 size, UInt64 percents = 0, bool isDefault = false); + */ + + // void EnableSubItems(); + bool OnCommand(unsigned code, unsigned itemID, LPARAM param) Z7_override; + virtual bool OnButtonClicked(unsigned buttonID, HWND buttonHWND) Z7_override; + virtual bool OnInit() Z7_override; + virtual void OnNotifyHelp() Z7_override; + virtual LONG OnApply() Z7_override; + + void EnableSpin(bool enable); +public: +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SettingsPage.rc b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SettingsPage.rc new file mode 100644 index 00000000..59ea5ee2 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SettingsPage.rc @@ -0,0 +1,22 @@ +#include "SettingsPageRes.h" +#include "../../GuiCommon.rc" + +#define xc OPTIONS_PAGE_XC_SIZE +#define yc OPTIONS_PAGE_YC_SIZE + +IDD_SETTINGS MY_PAGE +#include "SettingsPage2.rc" + + +#ifdef UNDER_CE + +#undef m +#undef xc + +#define m 4 +#define xc (SMALL_PAGE_SIZE_X + 8) + +IDD_SETTINGS_2 MY_PAGE +#include "SettingsPage2.rc" + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SettingsPage2.rc b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SettingsPage2.rc new file mode 100644 index 00000000..f67ee809 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SettingsPage2.rc @@ -0,0 +1,37 @@ +// #define g1xs 60 +// #include "MemDialogRes.h" + +#define save_y 144 +#define spin_y (save_y + 12) +#define spin_x_size 50 + +CAPTION "Settings" +BEGIN + MY_CONTROL_CHECKBOX ( "Show "".."" item", IDX_SETTINGS_SHOW_DOTS, m, 8, xc) + MY_CONTROL_CHECKBOX ( "Show real file &icons", IDX_SETTINGS_SHOW_REAL_FILE_ICONS, m, 22, xc) + MY_CONTROL_CHECKBOX ( "&Full row select", IDX_SETTINGS_FULL_ROW, m, 36, xc) + MY_CONTROL_CHECKBOX ( "Show &grid lines", IDX_SETTINGS_SHOW_GRID, m, 50, xc) + MY_CONTROL_CHECKBOX ( "&Single-click to open an item", IDX_SETTINGS_SINGLE_CLICK, m, 64, xc) + MY_CONTROL_CHECKBOX ( "&Alternative selection mode", IDX_SETTINGS_ALTERNATIVE_SELECTION, m, 78, xc) + + MY_CONTROL_CHECKBOX ( "Show system &menu", IDX_SETTINGS_SHOW_SYSTEM_MENU, m, 100, xc) + + MY_CONTROL_CHECKBOX ( "Use &large memory pages", IDX_SETTINGS_LARGE_PAGES, m, 122, xc) + + LTEXT "Maximum amount of RAM memory usage allowed to unpack archives:", + IDT_MEM_USAGE_EXTRACT, m, save_y, xc, 8 + MY_CONTROL_CHECKBOX_COLON(IDX_SETTINGS_MEM_SET, m + 10, spin_y + 1) + MY_CONTROL_EDIT_WITH_SPIN( + IDE_SETTINGS_MEM_SPIN_EDIT, + IDC_SETTINGS_MEM_SPIN, + "4", m + 30, spin_y, spin_x_size) + LTEXT "GB", IDT_SETTINGS_MEM_GB, m + 30 + spin_x_size + 8, spin_y + 2, xc - m - 30 - spin_x_size - 8, 8 + + // LTEXT "Memory usage for Compressing:", IDT_COMPRESS_MEMORY, m, 140, xc, 8 + // COMBOBOX IDC_SETTINGS_MEM, m , 152, g1xs, yc - 152, MY_COMBO + // LTEXT "/ RAM", IDT_SETTINGS_MEM_RAM, m + g1xs + m, 154, xc - g1xs - m, MY_TEXT_NOPREFIX +END + +#undef save_y +#undef spin_y +#undef spin_x_size diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SettingsPageRes.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SettingsPageRes.h new file mode 100644 index 00000000..205daee7 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SettingsPageRes.h @@ -0,0 +1,23 @@ +#define IDD_SETTINGS 2500 +#define IDD_SETTINGS_2 12500 + +#define IDX_SETTINGS_SHOW_DOTS 2501 +#define IDX_SETTINGS_SHOW_REAL_FILE_ICONS 2502 +#define IDX_SETTINGS_SHOW_SYSTEM_MENU 2503 +#define IDX_SETTINGS_FULL_ROW 2504 +#define IDX_SETTINGS_SHOW_GRID 2505 +#define IDX_SETTINGS_SINGLE_CLICK 2506 +#define IDX_SETTINGS_ALTERNATIVE_SELECTION 2507 +#define IDX_SETTINGS_LARGE_PAGES 2508 + +#define IDT_MEM_USAGE_EXTRACT 7816 + +#define IDX_SETTINGS_MEM_SET 100 +#define IDE_SETTINGS_MEM_SPIN_EDIT 101 +#define IDC_SETTINGS_MEM_SPIN 102 +#define IDT_SETTINGS_MEM_GB 103 + +// #define IDT_SETTINGS_MEM 100 +// #define IDC_SETTINGS_MEM 101 +// #define IDT_SETTINGS_MEM_RAM 102 +// #define IDT_COMPRESS_MEMORY 4017 diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SplitDialog.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SplitDialog.cpp new file mode 100644 index 00000000..21d812c5 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SplitDialog.cpp @@ -0,0 +1,114 @@ +// SplitDialog.cpp + +#include "StdAfx.h" + +#include "../../../Windows/FileName.h" + +#include "LangUtils.h" + +#include "BrowseDialog.h" +#include "CopyDialogRes.h" +#include "SplitDialog.h" +#include "SplitUtils.h" +#include "resourceGui.h" + +using namespace NWindows; + +#ifdef Z7_LANG +static const UInt32 kLangIDs[] = +{ + IDT_SPLIT_PATH, + IDT_SPLIT_VOLUME +}; +#endif + + +bool CSplitDialog::OnInit() +{ + #ifdef Z7_LANG + LangSetWindowText(*this, IDD_SPLIT); + LangSetDlgItems(*this, kLangIDs, Z7_ARRAY_SIZE(kLangIDs)); + #endif + _pathCombo.Attach(GetItem(IDC_SPLIT_PATH)); + _volumeCombo.Attach(GetItem(IDC_SPLIT_VOLUME)); + + if (!FilePath.IsEmpty()) + { + UString title; + GetText(title); + title.Add_Space(); + title += FilePath; + SetText(title); + } + _pathCombo.SetText(Path); + AddVolumeItems(_volumeCombo); + _volumeCombo.SetCurSel(0); + NormalizeSize(); + return CModalDialog::OnInit(); +} + +bool CSplitDialog::OnSize(WPARAM /* wParam */, int xSize, int ySize) +{ + int mx, my; + GetMargins(8, mx, my); + int bx1, bx2, by; + GetItemSizes(IDCANCEL, bx1, by); + GetItemSizes(IDOK, bx2, by); + int yPos = ySize - my - by; + int xPos = xSize - mx - bx1; + + InvalidateRect(NULL); + + { + RECT r; + GetClientRectOfItem(IDB_SPLIT_PATH, r); + int bx = RECT_SIZE_X(r); + MoveItem(IDB_SPLIT_PATH, xSize - mx - bx, r.top, bx, RECT_SIZE_Y(r)); + ChangeSubWindowSizeX(_pathCombo, xSize - mx - mx - bx - mx); + } + + MoveItem(IDCANCEL, xPos, yPos, bx1, by); + MoveItem(IDOK, xPos - mx - bx2, yPos, bx2, by); + + return false; +} + +bool CSplitDialog::OnButtonClicked(unsigned buttonID, HWND buttonHWND) +{ + switch (buttonID) + { + case IDB_SPLIT_PATH: + OnButtonSetPath(); + return true; + } + return CModalDialog::OnButtonClicked(buttonID, buttonHWND); +} + +void CSplitDialog::OnButtonSetPath() +{ + UString currentPath; + _pathCombo.GetText(currentPath); + // UString title = "Specify a location for output folder"; + const UString title = LangString(IDS_SET_FOLDER); + + UString resultPath; + if (!MyBrowseForFolder(*this, title, currentPath, resultPath)) + return; + NFile::NName::NormalizeDirPathPrefix(resultPath); + _pathCombo.SetCurSel(-1); + _pathCombo.SetText(resultPath); +} + +void CSplitDialog::OnOK() +{ + _pathCombo.GetText(Path); + UString volumeString; + _volumeCombo.GetText(volumeString); + volumeString.Trim(); + if (!ParseVolumeSizes(volumeString, VolumeSizes) || VolumeSizes.Size() == 0) + { + ::MessageBoxW(*this, LangString(IDS_INCORRECT_VOLUME_SIZE), L"7-Zip", MB_ICONERROR); + return; + } + CModalDialog::OnOK(); +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SplitDialog.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SplitDialog.h new file mode 100644 index 00000000..f8971365 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SplitDialog.h @@ -0,0 +1,28 @@ +// SplitDialog.h + +#ifndef ZIP7_INC_SPLIT_DIALOG_H +#define ZIP7_INC_SPLIT_DIALOG_H + +#include "../../../Windows/Control/Dialog.h" +#include "../../../Windows/Control/ComboBox.h" + +#include "SplitDialogRes.h" + +class CSplitDialog: public NWindows::NControl::CModalDialog +{ + NWindows::NControl::CComboBox _pathCombo; + NWindows::NControl::CComboBox _volumeCombo; + virtual void OnOK() Z7_override; + virtual bool OnInit() Z7_override; + virtual bool OnSize(WPARAM wParam, int xSize, int ySize) Z7_override; + virtual bool OnButtonClicked(unsigned buttonID, HWND buttonHWND) Z7_override; + void OnButtonSetPath(); +public: + UString FilePath; + UString Path; + CRecordVector VolumeSizes; + INT_PTR Create(HWND parentWindow = NULL) + { return CModalDialog::Create(IDD_SPLIT, parentWindow); } +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SplitDialog.rc b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SplitDialog.rc new file mode 100644 index 00000000..5a026e8a --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SplitDialog.rc @@ -0,0 +1,16 @@ +#include "SplitDialogRes.h" +#include "../../GuiCommon.rc" + +#define xc 288 +#define yc 96 + +IDD_SPLIT DIALOG 0, 0, xs, ys MY_MODAL_RESIZE_DIALOG_STYLE MY_FONT +CAPTION "Split File" +BEGIN + LTEXT "&Split to:", IDT_SPLIT_PATH, m, m, xc, 8 + COMBOBOX IDC_SPLIT_PATH, m, 20, xc - bxsDots - m, 64, MY_COMBO_WITH_EDIT + PUSHBUTTON "...", IDB_SPLIT_PATH, xs - m - bxsDots, 18, bxsDots, bys, WS_GROUP + LTEXT "Split to &volumes, bytes:", IDT_SPLIT_VOLUME, m, 44, xc, 8 + COMBOBOX IDC_SPLIT_VOLUME, m, 56, 96, 52, MY_COMBO_WITH_EDIT + OK_CANCEL +END diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SplitDialogRes.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SplitDialogRes.h new file mode 100644 index 00000000..50584a14 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SplitDialogRes.h @@ -0,0 +1,8 @@ +#define IDD_SPLIT 7300 + +#define IDT_SPLIT_PATH 7301 +#define IDT_SPLIT_VOLUME 7302 + +#define IDC_SPLIT_PATH 100 +#define IDB_SPLIT_PATH 101 +#define IDC_SPLIT_VOLUME 102 diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SplitUtils.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SplitUtils.cpp new file mode 100644 index 00000000..1982afba --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SplitUtils.cpp @@ -0,0 +1,96 @@ +// SplitUtils.cpp + +#include "StdAfx.h" + +#include "../../../Common/StringToInt.h" + +#include "SplitUtils.h" + +bool ParseVolumeSizes(const UString &s, CRecordVector &values) +{ + values.Clear(); + bool prevIsNumber = false; + for (unsigned i = 0; i < s.Len();) + { + wchar_t c = s[i++]; + if (c == L' ') + continue; + if (c == L'-') + return true; + if (prevIsNumber) + { + prevIsNumber = false; + unsigned numBits = 0; + switch (MyCharLower_Ascii(c)) + { + case 'b': continue; + case 'k': numBits = 10; break; + case 'm': numBits = 20; break; + case 'g': numBits = 30; break; + case 't': numBits = 40; break; + } + if (numBits != 0) + { + UInt64 &val = values.Back(); + if (val >= ((UInt64)1 << (64 - numBits))) + return false; + val <<= numBits; + + for (; i < s.Len(); i++) + if (s[i] == L' ') + break; + continue; + } + } + i--; + const wchar_t *start = s.Ptr(i); + const wchar_t *end; + UInt64 val = ConvertStringToUInt64(start, &end); + if (start == end) + return false; + if (val == 0) + return false; + values.Add(val); + prevIsNumber = true; + i += (unsigned)(end - start); + } + return true; +} + + +static const char * const k_Sizes[] = +{ + "10M" + , "100M" + , "1000M" + , "650M - CD" + , "700M - CD" + , "4092M - FAT" + , "4480M - DVD" // 4489 MiB limit + , "8128M - DVD DL" // 8147 MiB limit + , "23040M - BD" // 23866 MiB limit + // , "1457664 - 3.5\" floppy" +}; + +void AddVolumeItems(NWindows::NControl::CComboBox &combo) +{ + for (unsigned i = 0; i < Z7_ARRAY_SIZE(k_Sizes); i++) + combo.AddString(CSysString(k_Sizes[i])); +} + +UInt64 GetNumberOfVolumes(UInt64 size, const CRecordVector &volSizes) +{ + if (size == 0 || volSizes.Size() == 0) + return 1; + FOR_VECTOR (i, volSizes) + { + UInt64 volSize = volSizes[i]; + if (volSize >= size) + return i + 1; + size -= volSize; + } + UInt64 volSize = volSizes.Back(); + if (volSize == 0) + return (UInt64)(Int64)-1; + return volSizes.Size() + (size - 1) / volSize + 1; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SplitUtils.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SplitUtils.h new file mode 100644 index 00000000..d1d44e46 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SplitUtils.h @@ -0,0 +1,15 @@ +// SplitUtils.h + +#ifndef ZIP7_INC_SPLIT_UTILS_H +#define ZIP7_INC_SPLIT_UTILS_H + +#include "../../../Common/MyTypes.h" +#include "../../../Common/MyString.h" + +#include "../../../Windows/Control/ComboBox.h" + +bool ParseVolumeSizes(const UString &s, CRecordVector &values); +void AddVolumeItems(NWindows::NControl::CComboBox &volumeCombo); +UInt64 GetNumberOfVolumes(UInt64 size, const CRecordVector &volSizes); + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/StdAfx.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/StdAfx.cpp new file mode 100644 index 00000000..d0feea85 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/StdAfx.cpp @@ -0,0 +1,3 @@ +// StdAfx.cpp + +#include "StdAfx.h" diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/StdAfx.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/StdAfx.h new file mode 100644 index 00000000..7790eae6 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/StdAfx.h @@ -0,0 +1,67 @@ +// StdAfx.h + +#ifndef ZIP7_INC_STDAFX_H +#define ZIP7_INC_STDAFX_H + +#if defined(_MSC_VER) && _MSC_VER >= 1800 +#pragma warning(disable : 4464) // relative include path contains '..' +#endif + +#include "../../../Common/Common.h" + +#endif + +/* +WINVER and _WIN32_WINNT + +MSVC6 / 2003sdk: +{ + doesn't set _WIN32_WINNT + if WINVER is not set sets WINVER to value: + 0x0400 : MSVC6 + 0x0501 : Windows Server 2003 PSDK / 2003 R2 PSDK +} + +SDK for Win7 (and later) +{ + sets _WIN32_WINNT if it's not set. + sets WINVER if it's not set. + includes that does: +#if !defined(_WIN32_WINNT) && !defined(_CHICAGO_) + #define _WIN32_WINNT 0x0601 // in win7 sdk + #define _WIN32_WINNT 0x0A00 // in win10 sdk +#endif +#ifndef WINVER + #ifdef _WIN32_WINNT + #define WINVER _WIN32_WINNT + else + #define WINVER 0x0601 // in win7 sdk + #define WINVER 0x0A00 // in win10 sdk + endif +#endif +} + +Some GUI structures defined by windows will be larger, +If (_WIN32_WINNT) value is larger. + +Also if we send sizeof(win_gui_struct) to some windows function, +and we compile that code with big (_WIN32_WINNT) value, +the window function in old Windows can fail, if that old Windows +doesn't understand new big version of (win_gui_struct) compiled +with big (_WIN32_WINNT) value. + +So it's better to define smallest (_WIN32_WINNT) value here. +In 7-Zip FM we use some functions that require (_WIN32_WINNT == 0x0500). +So it's simpler to define (_WIN32_WINNT == 0x0500) here. +If we define (_WIN32_WINNT == 0x0400) here, we need some manual +declarations for functions and macros that require (0x0500) functions. +Also libs must contain these (0x0500+) functions. + +Some code in 7-zip FM uses also CommCtrl.h structures +that depend from (_WIN32_IE) value. But default +(_WIN32_IE) value from probably is OK for us. +So we don't set _WIN32_IE here. +default _WIN32_IE value set by : + 0x501 2003sdk + 0xa00 win10 sdk +*/ diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/StringUtils.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/StringUtils.cpp new file mode 100644 index 00000000..4641d151 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/StringUtils.cpp @@ -0,0 +1,39 @@ +// StringUtils.cpp + +#include "StdAfx.h" + +#include "StringUtils.h" + +void SplitStringToTwoStrings(const UString &src, UString &dest1, UString &dest2) +{ + dest1.Empty(); + dest2.Empty(); + bool quoteMode = false; + for (unsigned i = 0; i < src.Len(); i++) + { + const wchar_t c = src[i]; + if (c == '\"') + quoteMode = !quoteMode; + else if (c == ' ' && !quoteMode) + { + dest2 = src.Ptr(i + 1); + return; + } + else + dest1 += c; + } +} + +/* +UString JoinStrings(const UStringVector &srcStrings) +{ + UString s; + FOR_VECTOR (i, srcStrings) + { + if (i != 0) + s.Add_Space(); + s += srcStrings[i]; + } + return s; +} +*/ diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/StringUtils.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/StringUtils.h new file mode 100644 index 00000000..37aad3ae --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/StringUtils.h @@ -0,0 +1,11 @@ +// StringUtils.h + +#ifndef ZIP7_INC_STRING_UTILS_H +#define ZIP7_INC_STRING_UTILS_H + +#include "../../../Common/MyString.h" + +void SplitStringToTwoStrings(const UString &src, UString &dest1, UString &dest2); +// UString JoinStrings(const UStringVector &srcStrings); + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SysIconUtils.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SysIconUtils.cpp new file mode 100644 index 00000000..72fe5e72 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SysIconUtils.cpp @@ -0,0 +1,351 @@ +// SysIconUtils.cpp + +#include "StdAfx.h" + +#ifndef _UNICODE +#include "../../../Common/StringConvert.h" +#endif + +#include "../../../Windows/FileDir.h" + +#include "SysIconUtils.h" + +#if defined(__MINGW32__) || defined(__MINGW64__) +#include +#else +#include +#endif + +#ifndef _UNICODE +extern bool g_IsNT; +#endif + +CExtToIconMap g_Ext_to_Icon_Map; + +int Shell_GetFileInfo_SysIconIndex_for_CSIDL(int csidl) +{ + LPITEMIDLIST pidl = NULL; + SHGetSpecialFolderLocation(NULL, csidl, &pidl); + if (pidl) + { + SHFILEINFO shFileInfo; + shFileInfo.iIcon = -1; + const DWORD_PTR res = SHGetFileInfo((LPCTSTR)(const void *)(pidl), + FILE_ATTRIBUTE_DIRECTORY, + &shFileInfo, sizeof(shFileInfo), + SHGFI_PIDL | SHGFI_SYSICONINDEX); + /* + IMalloc *pMalloc; + SHGetMalloc(&pMalloc); + if (pMalloc) + { + pMalloc->Free(pidl); + pMalloc->Release(); + } + */ + // we use OLE2.dll function here + CoTaskMemFree(pidl); + if (res) + return shFileInfo.iIcon; + } + return -1; +} + +#ifndef _UNICODE +Z7_DIAGNOSTIC_IGNORE_CAST_FUNCTION +typedef DWORD_PTR (WINAPI * Func_SHGetFileInfoW)(LPCWSTR pszPath, DWORD attrib, SHFILEINFOW *psfi, UINT cbFileInfo, UINT uFlags); + +static struct C_SHGetFileInfo_Init +{ + Func_SHGetFileInfoW f_SHGetFileInfoW; + C_SHGetFileInfo_Init() + { + f_SHGetFileInfoW = Z7_GET_PROC_ADDRESS( + Func_SHGetFileInfoW, ::GetModuleHandleW(L"shell32.dll"), + "SHGetFileInfoW"); + // f_SHGetFileInfoW = NULL; // for debug + } +} g_SHGetFileInfo_Init; +#endif + +#ifdef _UNICODE +#define My_SHGetFileInfoW SHGetFileInfoW +#else +static DWORD_PTR My_SHGetFileInfoW(LPCWSTR pszPath, DWORD attrib, SHFILEINFOW *psfi, UINT cbFileInfo, UINT uFlags) +{ + if (!g_SHGetFileInfo_Init.f_SHGetFileInfoW) + return 0; + return g_SHGetFileInfo_Init.f_SHGetFileInfoW(pszPath, attrib, psfi, cbFileInfo, uFlags); +} +#endif + +DWORD_PTR Shell_GetFileInfo_SysIconIndex_for_Path_attrib_iconIndexRef( + CFSTR path, DWORD attrib, int &iconIndex) +{ +#ifndef _UNICODE + if (!g_IsNT || !g_SHGetFileInfo_Init.f_SHGetFileInfoW) + { + SHFILEINFO shFileInfo; + // ZeroMemory(&shFileInfo, sizeof(shFileInfo)); + shFileInfo.iIcon = -1; // optional + const DWORD_PTR res = ::SHGetFileInfo(fs2fas(path), + attrib ? attrib : FILE_ATTRIBUTE_ARCHIVE, + &shFileInfo, sizeof(shFileInfo), + SHGFI_USEFILEATTRIBUTES | SHGFI_SYSICONINDEX); + iconIndex = shFileInfo.iIcon; + return res; + } + else +#endif + { + SHFILEINFOW shFileInfo; + // ZeroMemory(&shFileInfo, sizeof(shFileInfo)); + shFileInfo.iIcon = -1; // optional + const DWORD_PTR res = ::My_SHGetFileInfoW(fs2us(path), + attrib ? attrib : FILE_ATTRIBUTE_ARCHIVE, + &shFileInfo, sizeof(shFileInfo), + SHGFI_USEFILEATTRIBUTES | SHGFI_SYSICONINDEX); + // (shFileInfo.iIcon == 0) returned for unknown extensions and files without extension + iconIndex = shFileInfo.iIcon; + // we use SHGFI_USEFILEATTRIBUTES, and + // (res != 0) is expected for main cases, even if there are no such file. + // (res == 0) for path with kSuperPrefix "\\?\" + // Also SHGFI_USEFILEATTRIBUTES still returns icon inside exe. + // So we can use SHGFI_USEFILEATTRIBUTES for any case. + // UString temp = fs2us(path); // for debug + // UString tempName = temp.Ptr(temp.ReverseFind_PathSepar() + 1); // for debug + // iconIndex = -1; // for debug + return res; + } +} + +int Shell_GetFileInfo_SysIconIndex_for_Path(CFSTR path, DWORD attrib) +{ + int iconIndex = -1; + if (!Shell_GetFileInfo_SysIconIndex_for_Path_attrib_iconIndexRef( + path, attrib, iconIndex)) + iconIndex = -1; + return iconIndex; +} + + +HRESULT Shell_GetFileInfo_SysIconIndex_for_Path_return_HRESULT( + CFSTR path, DWORD attrib, Int32 *iconIndex) +{ + *iconIndex = -1; + int iconIndexTemp; + if (Shell_GetFileInfo_SysIconIndex_for_Path_attrib_iconIndexRef( + path, attrib, iconIndexTemp)) + { + *iconIndex = iconIndexTemp; + return S_OK; + } + return GetLastError_noZero_HRESULT(); +} + +/* +DWORD_PTR Shell_GetFileInfo_SysIconIndex_for_Path(const UString &fileName, DWORD attrib, int &iconIndex, UString *typeName) +{ + #ifndef _UNICODE + if (!g_IsNT) + { + SHFILEINFO shFileInfo; + shFileInfo.szTypeName[0] = 0; + DWORD_PTR res = ::SHGetFileInfoA(GetSystemString(fileName), FILE_ATTRIBUTE_ARCHIVE | attrib, &shFileInfo, + sizeof(shFileInfo), SHGFI_USEFILEATTRIBUTES | SHGFI_SYSICONINDEX | SHGFI_TYPENAME); + if (typeName) + *typeName = GetUnicodeString(shFileInfo.szTypeName); + iconIndex = shFileInfo.iIcon; + return res; + } + else + #endif + { + SHFILEINFOW shFileInfo; + shFileInfo.szTypeName[0] = 0; + DWORD_PTR res = ::My_SHGetFileInfoW(fileName, FILE_ATTRIBUTE_ARCHIVE | attrib, &shFileInfo, + sizeof(shFileInfo), SHGFI_USEFILEATTRIBUTES | SHGFI_SYSICONINDEX | SHGFI_TYPENAME); + if (typeName) + *typeName = shFileInfo.szTypeName; + iconIndex = shFileInfo.iIcon; + return res; + } +} +*/ + +static int FindInSorted_Attrib(const CRecordVector &vect, DWORD attrib, unsigned &insertPos) +{ + unsigned left = 0, right = vect.Size(); + while (left != right) + { + const unsigned mid = (left + right) / 2; + const DWORD midAttrib = vect[mid].Attrib; + if (attrib == midAttrib) + return (int)mid; + if (attrib < midAttrib) + right = mid; + else + left = mid + 1; + } + insertPos = left; + return -1; +} + +static int FindInSorted_Ext(const CObjectVector &vect, const wchar_t *ext, unsigned &insertPos) +{ + unsigned left = 0, right = vect.Size(); + while (left != right) + { + const unsigned mid = (left + right) / 2; + const int compare = MyStringCompareNoCase(ext, vect[mid].Ext); + if (compare == 0) + return (int)mid; + if (compare < 0) + right = mid; + else + left = mid + 1; + } + insertPos = left; + return -1; +} + + +// bool DoItemAlwaysStart(const UString &name); + +int CExtToIconMap::GetIconIndex(DWORD attrib, const wchar_t *fileName /*, UString *typeName */) +{ + int dotPos = -1; + unsigned i; + for (i = 0;; i++) + { + const wchar_t c = fileName[i]; + if (c == 0) + break; + if (c == '.') + dotPos = (int)i; + // we don't need IS_PATH_SEPAR check, because (fileName) doesn't include path prefix. + // if (IS_PATH_SEPAR(c) || c == ':') dotPos = -1; + } + + /* + if (MyStringCompareNoCase(fileName, L"$Recycle.Bin") == 0) + { + char s[256]; + sprintf(s, "SPEC i = %3d, attr = %7x", _attribMap.Size(), attrib); + OutputDebugStringA(s); + OutputDebugStringW(fileName); + } + */ + + if ((attrib & FILE_ATTRIBUTE_DIRECTORY) || dotPos < 0) + for (unsigned k = 0;; k++) + { + if (k >= 2) + return -1; + unsigned insertPos = 0; + const int index = FindInSorted_Attrib(_attribMap, attrib, insertPos); + if (index >= 0) + { + // if (typeName) *typeName = _attribMap[index].TypeName; + return _attribMap[(unsigned)index].IconIndex; + } + CAttribIconPair pair; + pair.IconIndex = Shell_GetFileInfo_SysIconIndex_for_Path( + #ifdef UNDER_CE + FTEXT("\\") + #endif + FTEXT("__DIR__") + , attrib + // , pair.TypeName + ); + if (_attribMap.Size() < (1u << 16) // we limit cache size + || attrib < (1u << 15)) // we want to put all items with basic attribs to cache + { + /* + char s[256]; + sprintf(s, "i = %3d, attr = %7x", _attribMap.Size(), attrib); + OutputDebugStringA(s); + */ + pair.Attrib = attrib; + _attribMap.Insert(insertPos, pair); + // if (typeName) *typeName = pair.TypeName; + return pair.IconIndex; + } + if (pair.IconIndex >= 0) + return pair.IconIndex; + attrib = (attrib & FILE_ATTRIBUTE_DIRECTORY) ? + FILE_ATTRIBUTE_DIRECTORY : + FILE_ATTRIBUTE_ARCHIVE; + } + + CObjectVector &map = + (attrib & FILE_ATTRIBUTE_COMPRESSED) ? + _extMap_Compressed : _extMap_Normal; + const wchar_t *ext = fileName + dotPos + 1; + unsigned insertPos = 0; + const int index = FindInSorted_Ext(map, ext, insertPos); + if (index >= 0) + { + const CExtIconPair &pa = map[index]; + // if (typeName) *typeName = pa.TypeName; + return pa.IconIndex; + } + + for (i = 0;; i++) + { + const wchar_t c = ext[i]; + if (c == 0) + break; + if (c < L'0' || c > L'9') + break; + } + if (i != 0 && ext[i] == 0) + { + // Shell_GetFileInfo_SysIconIndex_for_Path is too slow for big number of split extensions: .001, .002, .003 + if (!SplitIconIndex_Defined) + { + Shell_GetFileInfo_SysIconIndex_for_Path_attrib_iconIndexRef( + #ifdef UNDER_CE + FTEXT("\\") + #endif + FTEXT("__FILE__.001"), FILE_ATTRIBUTE_ARCHIVE, SplitIconIndex); + SplitIconIndex_Defined = true; + } + return SplitIconIndex; + } + + CExtIconPair pair; + pair.Ext = ext; + pair.IconIndex = Shell_GetFileInfo_SysIconIndex_for_Path( + us2fs(fileName + dotPos), + attrib & FILE_ATTRIBUTE_COMPRESSED ? + FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_COMPRESSED: + FILE_ATTRIBUTE_ARCHIVE); + if (map.Size() < (1u << 16) // we limit cache size + // || DoItemAlwaysStart(fileName + dotPos) // we want some popular extensions in cache + ) + map.Insert(insertPos, pair); + // if (typeName) *typeName = pair.TypeName; + return pair.IconIndex; +} + + +HIMAGELIST Shell_Get_SysImageList_smallIcons(bool smallIcons) +{ + SHFILEINFO shFileInfo; + // shFileInfo.hIcon = NULL; // optional + const DWORD_PTR res = SHGetFileInfo(TEXT(""), + /* FILE_ATTRIBUTE_ARCHIVE | */ + FILE_ATTRIBUTE_DIRECTORY, + &shFileInfo, sizeof(shFileInfo), + SHGFI_USEFILEATTRIBUTES | + SHGFI_SYSICONINDEX | + (smallIcons ? SHGFI_SMALLICON : SHGFI_LARGEICON)); +#if 0 + // (shFileInfo.hIcon == NULL), because we don't use SHGFI_ICON. + // so DestroyIcon() is not required + if (res && shFileInfo.hIcon) // unexpected + DestroyIcon(shFileInfo.hIcon); +#endif + return (HIMAGELIST)res; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SysIconUtils.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SysIconUtils.h new file mode 100644 index 00000000..975ce255 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SysIconUtils.h @@ -0,0 +1,65 @@ +// SysIconUtils.h + +#ifndef ZIP7_INC_SYS_ICON_UTILS_H +#define ZIP7_INC_SYS_ICON_UTILS_H + +#include "../../../Common/MyWindows.h" + +#include + +#include "../../../Common/MyString.h" + +struct CExtIconPair +{ + UString Ext; + int IconIndex; + // UString TypeName; + // int Compare(const CExtIconPair &a) const { return MyStringCompareNoCase(Ext, a.Ext); } +}; + +struct CAttribIconPair +{ + DWORD Attrib; + int IconIndex; + // UString TypeName; + // int Compare(const CAttribIconPair &a) const { return Ext.Compare(a.Ext); } +}; + + +struct CExtToIconMap +{ + CRecordVector _attribMap; + CObjectVector _extMap_Normal; + CObjectVector _extMap_Compressed; + int SplitIconIndex; + int SplitIconIndex_Defined; + + CExtToIconMap(): SplitIconIndex_Defined(false) {} + + void Clear() + { + SplitIconIndex_Defined = false; + _extMap_Normal.Clear(); + _extMap_Compressed.Clear(); + _attribMap.Clear(); + } + int GetIconIndex_DIR(DWORD attrib = FILE_ATTRIBUTE_DIRECTORY) + { + return GetIconIndex(attrib, L"__DIR__"); + } + int GetIconIndex(DWORD attrib, const wchar_t *fileName /* , UString *typeName */); +}; + +extern CExtToIconMap g_Ext_to_Icon_Map; + +DWORD_PTR Shell_GetFileInfo_SysIconIndex_for_Path_attrib_iconIndexRef( + CFSTR path, DWORD attrib, int &iconIndex); +HRESULT Shell_GetFileInfo_SysIconIndex_for_Path_return_HRESULT( + CFSTR path, DWORD attrib, Int32 *iconIndex); +int Shell_GetFileInfo_SysIconIndex_for_Path(CFSTR path, DWORD attrib); + +int Shell_GetFileInfo_SysIconIndex_for_CSIDL(int csidl); + +HIMAGELIST Shell_Get_SysImageList_smallIcons(bool smallIcons); + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SystemPage.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SystemPage.cpp new file mode 100644 index 00000000..efa98a8c --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SystemPage.cpp @@ -0,0 +1,472 @@ +// SystemPage.cpp + +#include "StdAfx.h" + +#include "../../../Common/MyWindows.h" + +#if defined(__MINGW32__) || defined(__MINGW64__) +#include +#else +#include +#endif + +#include "../../../Common/Defs.h" +#include "../../../Common/StringConvert.h" + +#include "../../../Windows/DLL.h" +#include "../../../Windows/ErrorMsg.h" + +#include "HelpUtils.h" +#include "IFolder.h" +#include "LangUtils.h" +#include "PropertyNameRes.h" +#include "SystemPage.h" +#include "SystemPageRes.h" + +using namespace NWindows; + +#ifndef _UNICODE +extern bool g_IsNT; +#endif + +#ifdef Z7_LANG +static const UInt32 kLangIDs[] = +{ + IDT_SYSTEM_ASSOCIATE +}; +#endif + +#define kSystemTopic "FM/options.htm#system" + +CSysString CModifiedExtInfo::GetString() const +{ + const char *s; + if (State == kExtState_7Zip) + s = "7-Zip"; + else if (State == kExtState_Clear) + s = ""; + else if (Other7Zip) + s = "[7-Zip]"; + else + return ProgramKey; + return CSysString (s); +} + + +int CSystemPage::AddIcon(const UString &iconPath, int iconIndex) +{ + if (iconPath.IsEmpty()) + return -1; + if (iconIndex == -1) + iconIndex = 0; + + HICON hicon; + + #ifdef UNDER_CE + ExtractIconExW(iconPath, iconIndex, NULL, &hicon, 1); + if (!hicon) + #else + // we expand path from REG_EXPAND_SZ registry item. + UString path; + const DWORD size = MAX_PATH + 10; + const DWORD needLen = ::ExpandEnvironmentStringsW(iconPath, path.GetBuf(size + 2), size); + path.ReleaseBuf_CalcLen(size); + if (needLen == 0 || needLen >= size) + path = iconPath; + const UINT num = ExtractIconExW(path, iconIndex, NULL, &hicon, 1); + if (num != 1 || !hicon) + #endif + return -1; + + _imageList.AddIcon(hicon); + DestroyIcon(hicon); + return (int)(_numIcons++); +} + + +void CSystemPage::RefreshListItem(unsigned group, unsigned listIndex) +{ + const CAssoc &assoc = _items[GetRealIndex(listIndex)]; + _listView.SetSubItem(listIndex, group + 1, assoc.Pair[group].GetString()); + LVITEMW newItem; + memset(&newItem, 0, sizeof(newItem)); + newItem.iItem = (int)listIndex; + newItem.mask = LVIF_IMAGE; + newItem.iImage = assoc.GetIconIndex(); + _listView.SetItem(&newItem); +} + + +void CSystemPage::ChangeState(unsigned group, const CUIntVector &indices) +{ + if (indices.IsEmpty()) + return; + + bool thereAreClearItems = false; + unsigned counters[3] = { 0, 0, 0 }; + + unsigned i; + for (i = 0; i < indices.Size(); i++) + { + const CModifiedExtInfo &mi = _items[GetRealIndex(indices[i])].Pair[group]; + int state = kExtState_7Zip; + if (mi.State == kExtState_7Zip) + state = kExtState_Clear; + else if (mi.State == kExtState_Clear) + { + thereAreClearItems = true; + if (mi.Other) + state = kExtState_Other; + } + counters[state]++; + } + + int state = kExtState_Clear; + if (counters[kExtState_Other] != 0) + state = kExtState_Other; + else if (counters[kExtState_7Zip] != 0) + state = kExtState_7Zip; + + for (i = 0; i < indices.Size(); i++) + { + unsigned listIndex = indices[i]; + CAssoc &assoc = _items[GetRealIndex(listIndex)]; + CModifiedExtInfo &mi = assoc.Pair[group]; + bool change = false; + + switch (state) + { + case kExtState_Clear: change = true; break; + case kExtState_Other: change = mi.Other; break; + default: change = !(mi.Other && thereAreClearItems); break; + } + + if (change) + { + mi.State = state; + RefreshListItem(group, listIndex); + } + } + + _needSave = true; + Changed(); +} + + +bool CSystemPage::OnInit() +{ + _needSave = false; + +#ifdef Z7_LANG + LangSetDlgItems(*this, kLangIDs, Z7_ARRAY_SIZE(kLangIDs)); +#endif + + _listView.Attach(GetItem(IDL_SYSTEM_ASSOCIATE)); + _listView.SetUnicodeFormat(); + DWORD newFlags = LVS_EX_FULLROWSELECT; + _listView.SetExtendedListViewStyle(newFlags, newFlags); + + _numIcons = 0; + _imageList.Create(16, 16, ILC_MASK | ILC_COLOR32, 0, 0); + + _listView.SetImageList(_imageList, LVSIL_SMALL); + + _listView.InsertColumn(0, LangString(IDS_PROP_FILE_TYPE), 80); + + UString s; + + #if NUM_EXT_GROUPS == 1 + s = "Program"; + #else + #ifndef UNDER_CE + const unsigned kSize = 256; + BOOL res; + + DWORD size = kSize; + + #ifndef _UNICODE + if (!g_IsNT) + { + AString s2; + res = GetUserNameA(s2.GetBuf(size), &size); + s2.ReleaseBuf_CalcLen(MyMin((unsigned)size, kSize)); + s = GetUnicodeString(s2); + } + else + #endif + { + res = GetUserNameW(s.GetBuf(size), &size); + s.ReleaseBuf_CalcLen(MyMin((unsigned)size, kSize)); + } + + if (!res) + #endif + s = "Current User"; + #endif + + LV_COLUMNW ci; + ci.mask = LVCF_TEXT | LVCF_FMT | LVCF_WIDTH | LVCF_SUBITEM; + ci.cx = 152; + ci.fmt = LVCFMT_CENTER; + ci.pszText = s.Ptr_non_const(); + ci.iSubItem = 1; + _listView.InsertColumn(1, &ci); + + #if NUM_EXT_GROUPS > 1 + { + LangString(IDS_SYSTEM_ALL_USERS, s); + ci.pszText = s.Ptr_non_const(); + ci.iSubItem = 2; + _listView.InsertColumn(2, &ci); + } + #endif + + _extDB.Read(); + _items.Clear(); + + FOR_VECTOR (i, _extDB.Exts) + { + const CExtPlugins &extInfo = _extDB.Exts[i]; + + LVITEMW item; + item.iItem = (int)i; + item.mask = LVIF_TEXT | LVIF_PARAM | LVIF_IMAGE; + item.lParam = (LPARAM)i; + item.iSubItem = 0; + // ListView always uses internal iImage that is 0 by default? + // so we always use LVIF_IMAGE. + item.iImage = -1; + item.pszText = extInfo.Ext.Ptr_non_const(); + + CAssoc assoc; + const CPluginToIcon &plug = extInfo.Plugins[0]; + assoc.SevenZipImageIndex = AddIcon(plug.IconPath, plug.IconIndex); + + CSysString texts[NUM_EXT_GROUPS]; + unsigned g; + for (g = 0; g < NUM_EXT_GROUPS; g++) + { + CModifiedExtInfo &mi = assoc.Pair[g]; + mi.ReadFromRegistry(GetHKey(g), GetSystemString(extInfo.Ext)); + mi.SetState(plug.IconPath); + mi.ImageIndex = AddIcon(mi.IconPath, mi.IconIndex); + texts[g] = mi.GetString(); + } + item.iImage = assoc.GetIconIndex(); + const int itemIndex = _listView.InsertItem(&item); + for (g = 0; g < NUM_EXT_GROUPS; g++) + _listView.SetSubItem((unsigned)itemIndex, 1 + g, texts[g]); + _items.Add(assoc); + } + + if (_listView.GetItemCount() > 0) + _listView.SetItemState(0, LVIS_FOCUSED, LVIS_FOCUSED); + + return CPropertyPage::OnInit(); +} + + +static UString GetProgramCommand() +{ + UString s ('\"'); + s += fs2us(NDLL::GetModuleDirPrefix()); + s += "7zFM.exe\" \"%1\""; + return s; +} + + +LONG CSystemPage::OnApply() +{ + if (!_needSave) + return PSNRET_NOERROR; + + const UString command = GetProgramCommand(); + + LONG res = 0; + + FOR_VECTOR (listIndex, _extDB.Exts) + { + unsigned realIndex = GetRealIndex(listIndex); + const CExtPlugins &extInfo = _extDB.Exts[realIndex]; + CAssoc &assoc = _items[realIndex]; + + for (unsigned g = 0; g < NUM_EXT_GROUPS; g++) + { + CModifiedExtInfo &mi = assoc.Pair[g]; + HKEY key = GetHKey(g); + + if (mi.OldState != mi.State) + { + LONG res2 = 0; + + if (mi.State == kExtState_7Zip) + { + UString title = extInfo.Ext; + title += " Archive"; + const CPluginToIcon &plug = extInfo.Plugins[0]; + res2 = NRegistryAssoc::AddShellExtensionInfo(key, GetSystemString(extInfo.Ext), + title, command, plug.IconPath, plug.IconIndex); + } + else if (mi.State == kExtState_Clear) + res2 = NRegistryAssoc::DeleteShellExtensionInfo(key, GetSystemString(extInfo.Ext)); + + if (res == 0) + res = res2; + if (res2 == 0) + mi.OldState = mi.State; + + mi.State = mi.OldState; + RefreshListItem(g, listIndex); + } + } + } + + #ifndef UNDER_CE + SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL); + #endif + + WasChanged = true; + + _needSave = false; + + if (res != 0) + MessageBoxW(*this, NError::MyFormatMessage(res), L"7-Zip", MB_ICONERROR); + + return PSNRET_NOERROR; +} + + +void CSystemPage::OnNotifyHelp() +{ + ShowHelpWindow(kSystemTopic); +} + + +bool CSystemPage::OnButtonClicked(unsigned buttonID, HWND buttonHWND) +{ + switch (buttonID) + { + /* + case IDC_SYSTEM_SELECT_ALL: + _listView.SelectAll(); + return true; + */ + case IDB_SYSTEM_CURRENT: + case IDB_SYSTEM_ALL: + ChangeState(buttonID == IDB_SYSTEM_CURRENT ? 0 : 1); + return true; + } + return CPropertyPage::OnButtonClicked(buttonID, buttonHWND); +} + + +bool CSystemPage::OnNotify(UINT controlID, LPNMHDR lParam) +{ + if (lParam->hwndFrom == HWND(_listView)) + { + switch (lParam->code) + { + case NM_RETURN: + { + ChangeState(0); + return true; + } + + case NM_CLICK: + { + #ifdef UNDER_CE + NMLISTVIEW *item = (NMLISTVIEW *)lParam; + #else + NMITEMACTIVATE *item = (NMITEMACTIVATE *)lParam; + if (item->uKeyFlags == 0) + #endif + { + if (item->iItem >= 0) + { + // unsigned realIndex = GetRealIndex(item->iItem); + if (item->iSubItem >= 1 && item->iSubItem <= 2) + { + CUIntVector indices; + indices.Add((unsigned)item->iItem); + ChangeState(item->iSubItem < 2 ? 0 : 1, indices); + } + } + } + break; + } + + case LVN_KEYDOWN: + { + if (OnListKeyDown(LPNMLVKEYDOWN(lParam))) + return true; + break; + } + + /* + case NM_RCLICK: + case NM_DBLCLK: + case LVN_BEGINRDRAG: + // PostMessage(kRefreshpluginsListMessage, 0); + PostMessage(kUpdateDatabase, 0); + break; + */ + } + } + return CPropertyPage::OnNotify(controlID, lParam); +} + + +void CSystemPage::ChangeState(unsigned group) +{ + CUIntVector indices; + + int itemIndex = -1; + while ((itemIndex = _listView.GetNextSelectedItem(itemIndex)) != -1) + indices.Add((unsigned)itemIndex); + + if (indices.IsEmpty()) + FOR_VECTOR (i, _items) + indices.Add(i); + + ChangeState(group, indices); +} + + +bool CSystemPage::OnListKeyDown(LPNMLVKEYDOWN keyDownInfo) +{ + bool ctrl = IsKeyDown(VK_CONTROL); + bool alt = IsKeyDown(VK_MENU); + + if (alt) + return false; + + if ((ctrl && keyDownInfo->wVKey == 'A') + || (!ctrl && keyDownInfo->wVKey == VK_MULTIPLY)) + { + _listView.SelectAll(); + return true; + } + + switch (keyDownInfo->wVKey) + { + case VK_SPACE: + case VK_ADD: + case VK_SUBTRACT: + case VK_SEPARATOR: + case VK_DIVIDE: + + #ifndef UNDER_CE + case VK_OEM_PLUS: + case VK_OEM_MINUS: + #endif + + if (!ctrl) + { + ChangeState(keyDownInfo->wVKey == VK_SPACE ? 0 : 1); + return true; + } + break; + } + + return false; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SystemPage.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SystemPage.h new file mode 100644 index 00000000..6f2ed0c1 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SystemPage.h @@ -0,0 +1,126 @@ +// SystemPage.h + +#ifndef ZIP7_INC_SYSTEM_PAGE_H +#define ZIP7_INC_SYSTEM_PAGE_H + +#include "../../../Windows/Control/ImageList.h" +#include "../../../Windows/Control/ListView.h" +#include "../../../Windows/Control/PropertyPage.h" + +#include "FilePlugins.h" +#include "RegistryAssociations.h" + +enum EExtState +{ + kExtState_Clear = 0, + kExtState_Other, + kExtState_7Zip +}; + +struct CModifiedExtInfo: public NRegistryAssoc::CShellExtInfo +{ + int OldState; + int State; + int ImageIndex; + bool Other; + bool Other7Zip; + + CModifiedExtInfo(): ImageIndex(-1) {} + + CSysString GetString() const; + + void SetState(const UString &iconPath) + { + State = kExtState_Clear; + Other = false; + Other7Zip = false; + if (!ProgramKey.IsEmpty()) + { + State = kExtState_Other; + Other = true; + if (IsIt7Zip()) + { + Other7Zip = !iconPath.IsEqualTo_NoCase(IconPath); + if (!Other7Zip) + { + State = kExtState_7Zip; + Other = false; + } + } + } + OldState = State; + } +}; + +struct CAssoc +{ + CModifiedExtInfo Pair[2]; + int SevenZipImageIndex; + + int GetIconIndex() const + { + for (unsigned i = 0; i < 2; i++) + { + const CModifiedExtInfo &pair = Pair[i]; + if (pair.State == kExtState_Clear) + continue; + if (pair.State == kExtState_7Zip) + return SevenZipImageIndex; + if (pair.ImageIndex != -1) + return pair.ImageIndex; + } + return -1; + } +}; + +#ifdef UNDER_CE + #define NUM_EXT_GROUPS 1 +#else + #define NUM_EXT_GROUPS 2 +#endif + +class CSystemPage: public NWindows::NControl::CPropertyPage +{ + CExtDatabase _extDB; + CObjectVector _items; + + unsigned _numIcons; + NWindows::NControl::CImageList _imageList; + NWindows::NControl::CListView _listView; + + bool _needSave; + + HKEY GetHKey(unsigned + #if NUM_EXT_GROUPS != 1 + group + #endif + ) const + { + #if NUM_EXT_GROUPS == 1 + return HKEY_CLASSES_ROOT; + #else + return group == 0 ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE; + #endif + } + + int AddIcon(const UString &path, int iconIndex); + unsigned GetRealIndex(unsigned listIndex) const { return listIndex; } + void RefreshListItem(unsigned group, unsigned listIndex); + void ChangeState(unsigned group, const CUIntVector &indices); + void ChangeState(unsigned group); + + bool OnListKeyDown(LPNMLVKEYDOWN keyDownInfo); + +public: + bool WasChanged; + + CSystemPage(): WasChanged(false) {} + + virtual bool OnInit() Z7_override; + virtual void OnNotifyHelp() Z7_override; + virtual bool OnNotify(UINT controlID, LPNMHDR lParam) Z7_override; + virtual LONG OnApply() Z7_override; + virtual bool OnButtonClicked(unsigned buttonID, HWND buttonHWND) Z7_override; +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SystemPage.rc b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SystemPage.rc new file mode 100644 index 00000000..108e5241 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SystemPage.rc @@ -0,0 +1,43 @@ +#include "SystemPageRes.h" +#include "../../GuiCommon.rc" + +#define xc OPTIONS_PAGE_XC_SIZE +#define yc OPTIONS_PAGE_YC_SIZE + +IDD_SYSTEM DIALOG MY_PAGE_POSTFIX +CAPTION "System" +BEGIN + LTEXT "Associate 7-Zip with:", IDT_SYSTEM_ASSOCIATE, m, m, xc, 8 + PUSHBUTTON "+", IDB_SYSTEM_CURRENT, 92, m + 12, 40, bys + PUSHBUTTON "+", IDB_SYSTEM_ALL, 194, m + 12, 40, bys + CONTROL "List1", IDL_SYSTEM_ASSOCIATE, "SysListView32", + LVS_REPORT | LVS_SHOWSELALWAYS | LVS_SHAREIMAGELISTS | LVS_NOSORTHEADER | WS_BORDER | WS_TABSTOP, + m, m + 32, xc, (yc - 32) +END + +#ifdef UNDER_CE + +#undef m +#undef xc +#undef yc + +#define m 4 +#define xc (SMALL_PAGE_SIZE_X + 8) +#define yc (128 + 8) + +IDD_SYSTEM_2 DIALOG MY_PAGE_POSTFIX +CAPTION "System" +BEGIN + LTEXT "Associate 7-Zip with:", IDT_SYSTEM_ASSOCIATE, m, m, xc, 8 + PUSHBUTTON "+", IDB_SYSTEM_CURRENT, 60, m + 12, 40, bys + CONTROL "List1", IDL_SYSTEM_ASSOCIATE, "SysListView32", + LVS_REPORT | LVS_SHOWSELALWAYS | LVS_SHAREIMAGELISTS | LVS_NOSORTHEADER | WS_BORDER | WS_TABSTOP, + m, m + 32, xc, (yc - 32) +END + +#endif + +STRINGTABLE +BEGIN + IDS_SYSTEM_ALL_USERS "All users" +END diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SystemPageRes.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SystemPageRes.h new file mode 100644 index 00000000..c8944482 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/SystemPageRes.h @@ -0,0 +1,9 @@ +#define IDD_SYSTEM 2200 +#define IDD_SYSTEM_2 12200 + +#define IDT_SYSTEM_ASSOCIATE 2201 +#define IDS_SYSTEM_ALL_USERS 2202 + +#define IDL_SYSTEM_ASSOCIATE 100 +#define IDB_SYSTEM_CURRENT 101 +#define IDB_SYSTEM_ALL 102 diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/Test.bmp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/Test.bmp new file mode 100644 index 00000000..ef85ba23 Binary files /dev/null and b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/Test.bmp differ diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/Test2.bmp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/Test2.bmp new file mode 100644 index 00000000..99b7dbf0 Binary files /dev/null and b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/Test2.bmp differ diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/TextPairs.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/TextPairs.cpp new file mode 100644 index 00000000..6bf84cf8 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/TextPairs.cpp @@ -0,0 +1,193 @@ +// TextPairs.cpp + +#include "StdAfx.h" + +#include "TextPairs.h" + +static const wchar_t kNewLineChar = '\n'; +static const wchar_t kQuoteChar = '\"'; + +static const wchar_t kBOM = (wchar_t)0xFEFF; + +static bool IsSeparatorChar(wchar_t c) +{ + return (c == ' ' || c == '\t'); +} + +static void RemoveCr(UString &s) +{ + s.RemoveChar(L'\x0D'); +} + +static UString GetIDString(const wchar_t *srcString, unsigned &finishPos) +{ + UString result; + bool quotes = false; + for (finishPos = 0;;) + { + wchar_t c = srcString[finishPos]; + if (c == 0) + break; + finishPos++; + bool isSeparatorChar = IsSeparatorChar(c); + if (c == kNewLineChar || (isSeparatorChar && !quotes) + || (c == kQuoteChar && quotes)) + break; + else if (c == kQuoteChar) + quotes = true; + else + result += c; + } + result.Trim(); + RemoveCr(result); + return result; +} + +static UString GetValueString(const wchar_t *srcString, unsigned &finishPos) +{ + UString result; + for (finishPos = 0;;) + { + wchar_t c = srcString[finishPos]; + if (c == 0) + break; + finishPos++; + if (c == kNewLineChar) + break; + result += c; + } + result.Trim(); + RemoveCr(result); + return result; +} + +static bool GetTextPairs(const UString &srcString, CObjectVector &pairs) +{ + pairs.Clear(); + unsigned pos = 0; + + if (srcString.Len() > 0) + { + if (srcString[0] == kBOM) + pos++; + } + while (pos < srcString.Len()) + { + unsigned finishPos; + UString id = GetIDString((const wchar_t *)srcString + pos, finishPos); + pos += finishPos; + if (id.IsEmpty()) + continue; + UString value = GetValueString((const wchar_t *)srcString + pos, finishPos); + pos += finishPos; + if (!id.IsEmpty()) + { + CTextPair pair; + pair.ID = id; + pair.Value = value; + pairs.Add(pair); + } + } + return true; +} + +static int ComparePairIDs(const UString &s1, const UString &s2) + { return MyStringCompareNoCase(s1, s2); } + +static int ComparePairItems(const CTextPair &p1, const CTextPair &p2) + { return ComparePairIDs(p1.ID, p2.ID); } + +static int ComparePairItems(void *const *a1, void *const *a2, void * /* param */) + { return ComparePairItems(**(const CTextPair *const *)a1, **(const CTextPair *const *)a2); } + +void CPairsStorage::Sort() { Pairs.Sort(ComparePairItems, NULL); } + +int CPairsStorage::FindID(const UString &id, unsigned &insertPos) const +{ + unsigned left = 0, right = Pairs.Size(); + while (left != right) + { + const unsigned mid = (left + right) / 2; + const int compResult = ComparePairIDs(id, Pairs[mid].ID); + if (compResult == 0) + { + insertPos = mid; // to disable GCC warning + return (int)mid; + } + if (compResult < 0) + right = mid; + else + left = mid + 1; + } + insertPos = left; + return -1; +} + +int CPairsStorage::FindID(const UString &id) const +{ + unsigned pos; + return FindID(id, pos); +} + +void CPairsStorage::AddPair(const CTextPair &pair) +{ + unsigned insertPos; + const int pos = FindID(pair.ID, insertPos); + if (pos >= 0) + Pairs[pos] = pair; + else + Pairs.Insert(insertPos, pair); +} + +void CPairsStorage::DeletePair(const UString &id) +{ + const int pos = FindID(id); + if (pos >= 0) + Pairs.Delete((unsigned)pos); +} + +bool CPairsStorage::GetValue(const UString &id, UString &value) const +{ + value.Empty(); + const int pos = FindID(id); + if (pos < 0) + return false; + value = Pairs[pos].Value; + return true; +} + +UString CPairsStorage::GetValue(const UString &id) const +{ + const int pos = FindID(id); + if (pos < 0) + return UString(); + return Pairs[pos].Value; +} + +bool CPairsStorage::ReadFromString(const UString &text) +{ + bool result = ::GetTextPairs(text, Pairs); + if (result) + Sort(); + else + Pairs.Clear(); + return result; +} + +void CPairsStorage::SaveToString(UString &text) const +{ + FOR_VECTOR (i, Pairs) + { + const CTextPair &pair = Pairs[i]; + bool multiWord = (pair.ID.Find(L' ') >= 0); + if (multiWord) + text.Add_Char('\"'); + text += pair.ID; + if (multiWord) + text.Add_Char('\"'); + text.Add_Space(); + text += pair.Value; + text.Add_Char('\x0D'); + text.Add_LF(); + } +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/TextPairs.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/TextPairs.h new file mode 100644 index 00000000..d18233d1 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/TextPairs.h @@ -0,0 +1,32 @@ +// TextPairs.h + +#ifndef ZIP7_INC_FM_TEXT_PAIRS_H +#define ZIP7_INC_FM_TEXT_PAIRS_H + +#include "../../../Common/MyString.h" + +struct CTextPair +{ + UString ID; + UString Value; +}; + +class CPairsStorage +{ + CObjectVector Pairs; + + int FindID(const UString &id, unsigned &insertPos) const; + int FindID(const UString &id) const; + void Sort(); +public: + void Clear() { Pairs.Clear(); } + bool ReadFromString(const UString &text); + void SaveToString(UString &text) const; + + bool GetValue(const UString &id, UString &value) const; + UString GetValue(const UString &id) const; + void AddPair(const CTextPair &pair); + void DeletePair(const UString &id); +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/UpdateCallback100.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/UpdateCallback100.cpp new file mode 100644 index 00000000..0796eba3 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/UpdateCallback100.cpp @@ -0,0 +1,147 @@ +// UpdateCallback100.cpp + +#include "StdAfx.h" + +#include "../../../Windows/ErrorMsg.h" + +#include "../GUI/resource3.h" + +#include "LangUtils.h" +#include "UpdateCallback100.h" + +Z7_COM7F_IMF(CUpdateCallback100Imp::ScanProgress(UInt64 /* numFolders */, UInt64 numFiles, UInt64 totalSize, const wchar_t *path, Int32 /* isDir */)) +{ + return ProgressDialog->Sync.ScanProgress(numFiles, totalSize, us2fs(path)); +} + +Z7_COM7F_IMF(CUpdateCallback100Imp::ScanError(const wchar_t *path, HRESULT errorCode)) +{ + ProgressDialog->Sync.AddError_Code_Name(errorCode, path); + return S_OK; +} + +Z7_COM7F_IMF(CUpdateCallback100Imp::SetNumFiles(UInt64 numFiles)) +{ + return ProgressDialog->Sync.Set_NumFilesTotal(numFiles); +} + +Z7_COM7F_IMF(CUpdateCallback100Imp::SetTotal(UInt64 size)) +{ + ProgressDialog->Sync.Set_NumBytesTotal(size); + return S_OK; +} + +Z7_COM7F_IMF(CUpdateCallback100Imp::SetCompleted(const UInt64 *completed)) +{ + return ProgressDialog->Sync.Set_NumBytesCur(completed); +} + +Z7_COM7F_IMF(CUpdateCallback100Imp::SetRatioInfo(const UInt64 *inSize, const UInt64 *outSize)) +{ + ProgressDialog->Sync.Set_Ratio(inSize, outSize); + return S_OK; +} + +Z7_COM7F_IMF(CUpdateCallback100Imp::CompressOperation(const wchar_t *name)) +{ + return SetOperation_Base(NUpdateNotifyOp::kAdd, name, false); +} + +Z7_COM7F_IMF(CUpdateCallback100Imp::DeleteOperation(const wchar_t *name)) +{ + return SetOperation_Base(NUpdateNotifyOp::kDelete, name, false); +} + +Z7_COM7F_IMF(CUpdateCallback100Imp::OperationResult(Int32 /* operationResult */)) +{ + ProgressDialog->Sync.Set_NumFilesCur(++NumFiles); + return S_OK; +} + +void SetExtractErrorMessage(Int32 opRes, Int32 encrypted, const wchar_t *fileName, UString &s); + +Z7_COM7F_IMF(CUpdateCallback100Imp::ReportExtractResult(Int32 opRes, Int32 isEncrypted, const wchar_t *name)) +{ + if (opRes != NArchive::NExtract::NOperationResult::kOK) + { + UString s; + SetExtractErrorMessage(opRes, isEncrypted, name, s); + ProgressDialog->Sync.AddError_Message(s); + } + return S_OK; +} + +Z7_COM7F_IMF(CUpdateCallback100Imp::ReportUpdateOperation(UInt32 notifyOp, const wchar_t *name, Int32 isDir)) +{ + return SetOperation_Base(notifyOp, name, IntToBool(isDir)); +} + +Z7_COM7F_IMF(CUpdateCallback100Imp::UpdateErrorMessage(const wchar_t *message)) +{ + ProgressDialog->Sync.AddError_Message(message); + return S_OK; +} + +Z7_COM7F_IMF(CUpdateCallback100Imp::OpenFileError(const wchar_t *path, HRESULT errorCode)) +{ + ProgressDialog->Sync.AddError_Code_Name(errorCode, path); + return S_OK; +} + +Z7_COM7F_IMF(CUpdateCallback100Imp::ReadingFileError(const wchar_t *path, HRESULT errorCode)) +{ + ProgressDialog->Sync.AddError_Code_Name(errorCode, path); + return S_OK; +} + +Z7_COM7F_IMF(CUpdateCallback100Imp::CryptoGetTextPassword2(Int32 *passwordIsDefined, BSTR *password)) +{ + *password = NULL; + *passwordIsDefined = BoolToInt(PasswordIsDefined); + if (!PasswordIsDefined) + return S_OK; + return StringToBstr(Password, password); +} + +Z7_COM7F_IMF(CUpdateCallback100Imp::SetTotal(const UInt64 * /* files */, const UInt64 * /* bytes */)) +{ + return S_OK; +} + +Z7_COM7F_IMF(CUpdateCallback100Imp::SetCompleted(const UInt64 * /* files */, const UInt64 * /* bytes */)) +{ + return ProgressDialog->Sync.CheckStop(); +} + + +Z7_COM7F_IMF(CUpdateCallback100Imp::MoveArc_Start(const wchar_t *srcTempPath, const wchar_t *destFinalPath, UInt64 size, Int32 updateMode)) +{ + return MoveArc_Start_Base(srcTempPath, destFinalPath, size, updateMode); +} + +Z7_COM7F_IMF(CUpdateCallback100Imp::MoveArc_Progress(UInt64 totalSize, UInt64 currentSize)) +{ + return MoveArc_Progress_Base(totalSize, currentSize); +} + +Z7_COM7F_IMF(CUpdateCallback100Imp::MoveArc_Finish()) +{ + return MoveArc_Finish_Base(); +} + +Z7_COM7F_IMF(CUpdateCallback100Imp::Before_ArcReopen()) +{ + ProgressDialog->Sync.Clear_Stop_Status(); + return S_OK; +} + + +Z7_COM7F_IMF(CUpdateCallback100Imp::CryptoGetTextPassword(BSTR *password)) +{ + *password = NULL; + if (!PasswordIsDefined) + { + RINOK(ShowAskPasswordDialog()) + } + return StringToBstr(Password, password); +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/UpdateCallback100.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/UpdateCallback100.h new file mode 100644 index 00000000..adae94fa --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/UpdateCallback100.h @@ -0,0 +1,49 @@ +// UpdateCallback100.h + +#ifndef ZIP7_INC_UPDATE_CALLBACK100_H +#define ZIP7_INC_UPDATE_CALLBACK100_H + +#include "../../../Common/MyCom.h" + +#include "../../IPassword.h" + +#include "../Agent/IFolderArchive.h" + +#include "../GUI/UpdateCallbackGUI2.h" + +#include "ProgressDialog2.h" + +class CUpdateCallback100Imp Z7_final: + public IFolderArchiveUpdateCallback, + public IFolderArchiveUpdateCallback2, + public IFolderArchiveUpdateCallback_MoveArc, + public IFolderScanProgress, + public ICryptoGetTextPassword2, + public ICryptoGetTextPassword, + public IArchiveOpenCallback, + public ICompressProgressInfo, + public CUpdateCallbackGUI2, + public CMyUnknownImp +{ + Z7_COM_UNKNOWN_IMP_8( + IFolderArchiveUpdateCallback, + IFolderArchiveUpdateCallback2, + IFolderArchiveUpdateCallback_MoveArc, + IFolderScanProgress, + ICryptoGetTextPassword2, + ICryptoGetTextPassword, + IArchiveOpenCallback, + ICompressProgressInfo) + + Z7_IFACE_COM7_IMP(IProgress) + Z7_IFACE_COM7_IMP(IFolderArchiveUpdateCallback) + Z7_IFACE_COM7_IMP(IFolderArchiveUpdateCallback2) + Z7_IFACE_COM7_IMP(IFolderArchiveUpdateCallback_MoveArc) + Z7_IFACE_COM7_IMP(IFolderScanProgress) + Z7_IFACE_COM7_IMP(ICryptoGetTextPassword2) + Z7_IFACE_COM7_IMP(ICryptoGetTextPassword) + Z7_IFACE_COM7_IMP(IArchiveOpenCallback) + Z7_IFACE_COM7_IMP(ICompressProgressInfo) +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/VerCtrl.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/VerCtrl.cpp new file mode 100644 index 00000000..c1ca643a --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/VerCtrl.cpp @@ -0,0 +1,428 @@ +// VerCtrl.cpp + +#include "StdAfx.h" + +#include "../../../Common/StringToInt.h" + +#include "../../../Windows/FileName.h" +#include "../../../Windows/FileFind.h" + +#include "App.h" +#include "RegistryUtils.h" +#include "OverwriteDialog.h" + +#include "resource.h" + +using namespace NWindows; +using namespace NFile; +using namespace NFind; +using namespace NDir; + +static FString ConvertPath_to_Ctrl(const FString &path) +{ + FString s = path; + s.Replace(FChar(':'), FChar('_')); + return s; +} + +struct CFileDataInfo +{ + CByteBuffer Data; + BY_HANDLE_FILE_INFORMATION Info; + bool IsOpen; + + CFileDataInfo(): IsOpen (false) {} + UInt64 GetSize() const { return (((UInt64)Info.nFileSizeHigh) << 32) + Info.nFileSizeLow; } + bool Read(const FString &path); +}; + + +bool CFileDataInfo::Read(const FString &path) +{ + IsOpen = false; + NIO::CInFile file; + if (!file.Open(path)) + return false; + if (!file.GetFileInformation(&Info)) + return false; + + const UInt64 size = GetSize(); + const size_t size2 = (size_t)size; + if (size2 != size || size2 > (1 << 28)) + { + SetLastError(1); + return false; + } + + Data.Alloc(size2); + + size_t processedSize; + if (!file.ReadFull(Data, size2, processedSize)) + return false; + if (processedSize != size2) + { + SetLastError(1); + return false; + } + IsOpen = true; + return true; +} + + +static bool CreateComplexDir_for_File(const FString &path) +{ + FString resDirPrefix; + FString resFileName; + if (!GetFullPathAndSplit(path, resDirPrefix, resFileName)) + return false; + return CreateComplexDir(resDirPrefix); +} + + +static bool ParseNumberString(const FString &s, UInt32 &number) +{ + const FChar *end; + UInt64 result = ConvertStringToUInt64(s, &end); + if (*end != 0 || s.IsEmpty() || result > (UInt32)0x7FFFFFFF) + return false; + number = (UInt32)result; + return true; +} + + +static void WriteFile(const FString &path, bool createAlways, const CFileDataInfo &fdi, const CPanel &panel) +{ + NIO::COutFile outFile; + if (!outFile.Create_ALWAYS_or_NEW(path, createAlways)) // (createAlways = false) means CREATE_NEW + { + panel.MessageBox_LastError(); + return; + } + UInt32 processedSize; + if (!outFile.Write(fdi.Data, (UInt32)fdi.Data.Size(), processedSize)) + { + panel.MessageBox_LastError(); + return; + } + if (processedSize != fdi.Data.Size()) + { + panel.MessageBox_Error(L"Write error"); + return; + } + if (!outFile.SetTime( + &fdi.Info.ftCreationTime, + &fdi.Info.ftLastAccessTime, + &fdi.Info.ftLastWriteTime)) + { + panel.MessageBox_LastError(); + return; + } + + if (!SetFileAttrib(path, fdi.Info.dwFileAttributes)) + { + panel.MessageBox_LastError(); + return; + } +} + + +static UInt64 FILETIME_to_UInt64(const FILETIME &ft) +{ + return ft.dwLowDateTime | ((UInt64)ft.dwHighDateTime << 32); +} + +static void UInt64_TO_FILETIME(UInt64 v, FILETIME &ft) +{ + ft.dwLowDateTime = (DWORD)v; + ft.dwHighDateTime = (DWORD)(v >> 32); +} + + +void CApp::VerCtrl(unsigned id) +{ + const CPanel &panel = GetFocusedPanel(); + + if (!panel.Is_IO_FS_Folder()) + { + panel.MessageBox_Error_UnsupportOperation(); + return; + } + + CRecordVector indices; + panel.Get_ItemIndices_Selected(indices); + + if (indices.Size() != 1) + { + // panel.MessageBox_Error_UnsupportOperation(); + return; + } + + const FString path = us2fs(panel.GetItemFullPath(indices[0])); + + UString vercPath; + ReadReg_VerCtrlPath(vercPath); + if (vercPath.IsEmpty()) + return; + NName::NormalizeDirPathPrefix(vercPath); + + FString dirPrefix; + FString fileName; + if (!GetFullPathAndSplit(path, dirPrefix, fileName)) + { + panel.MessageBox_LastError(); + return; + } + + const FString dirPrefix2 = us2fs(vercPath) + ConvertPath_to_Ctrl(dirPrefix); + const FString path2 = dirPrefix2 + fileName; + + bool sameTime = false; + bool sameData = false; + bool areIdentical = false; + + CFileDataInfo fdi, fdi2; + if (!fdi.Read(path)) + { + panel.MessageBox_LastError(); + return; + } + + if (fdi2.Read(path2)) + { + sameData = (fdi.Data == fdi2.Data); + sameTime = (CompareFileTime(&fdi.Info.ftLastWriteTime, &fdi2.Info.ftLastWriteTime) == 0); + areIdentical = (sameData && sameTime); + } + + const bool isReadOnly = NAttributes::IsReadOnly(fdi.Info.dwFileAttributes); + + if (id == IDM_VER_EDIT) + { + if (!isReadOnly) + { + panel.MessageBox_Error(L"File is not read-only"); + return; + } + + if (!areIdentical) + { + if (fdi2.IsOpen) + { + NFind::CEnumerator enumerator; + FString d2 = dirPrefix2; + d2 += "_7vc"; + d2.Add_PathSepar(); + d2 += fileName; + d2.Add_PathSepar(); + enumerator.SetDirPrefix(d2); + NFind::CDirEntry fi; + Int32 maxVal = -1; + while (enumerator.Next(fi)) + { + UInt32 val; + if (!ParseNumberString(fi.Name, val)) + continue; + if ((Int32)val > maxVal) + maxVal = (Int32)val; + } + + UInt32 next = (UInt32)maxVal + 1; + if (maxVal < 0) + { + next = 1; + if (!::CreateComplexDir_for_File(path2)) + { + panel.MessageBox_LastError(); + return; + } + } + + // we rename old file2 to some name; + FString path_num = d2; + { + AString t; + t.Add_UInt32((UInt32)next); + while (t.Len() < 3) + t.InsertAtFront('0'); + path_num += t; + } + + if (maxVal < 0) + { + if (!::CreateComplexDir_for_File(path_num)) + { + panel.MessageBox_LastError(); + return; + } + } + + if (!NDir::MyMoveFile(path2, path_num)) + { + panel.MessageBox_LastError(); + return; + } + } + else + { + if (!::CreateComplexDir_for_File(path2)) + { + panel.MessageBox_LastError(); + return; + } + } + /* + if (!::CopyFile(fs2fas(path), fs2fas(path2), TRUE)) + { + panel.MessageBox_LastError(); + return; + } + */ + WriteFile(path2, + false, // (createAlways = false) means CREATE_NEW + fdi, panel); + } + + if (!SetFileAttrib(path, fdi.Info.dwFileAttributes & ~(DWORD)FILE_ATTRIBUTE_READONLY)) + { + panel.MessageBox_LastError(); + return; + } + + return; + } + + if (isReadOnly) + { + panel.MessageBox_Error(L"File is read-only"); + return; + } + + if (id == IDM_VER_COMMIT) + { + if (sameData) + { + if (!sameTime) + { + panel.MessageBox_Error( + L"Same data, but different timestamps.\n" + L"Use `Revert` to recover timestamp."); + return; + } + } + + const UInt64 timeStampOriginal = FILETIME_to_UInt64(fdi.Info.ftLastWriteTime); + UInt64 timeStamp2 = 0; + if (fdi2.IsOpen) + timeStamp2 = FILETIME_to_UInt64(fdi2.Info.ftLastWriteTime); + + if (timeStampOriginal > timeStamp2) + { + const UInt64 k_Ntfs_prec = 10000000; + UInt64 timeStamp = timeStampOriginal; + const UInt32 k_precs[] = { 60 * 60, 60, 2, 1 }; + for (unsigned i = 0; i < Z7_ARRAY_SIZE(k_precs); i++) + { + timeStamp = timeStampOriginal; + const UInt64 prec = k_Ntfs_prec * k_precs[i]; + // timeStamp += prec - 1; // for rounding up + timeStamp /= prec; + timeStamp *= prec; + if (timeStamp > timeStamp2) + break; + } + + if (timeStamp != timeStampOriginal + && timeStamp > timeStamp2) + { + FILETIME mTime; + UInt64_TO_FILETIME(timeStamp, mTime); + // NDir::SetFileAttrib(path, 0); + { + NIO::COutFile outFile; + if (!outFile.Open_EXISTING(path)) + { + panel.MessageBox_LastError(); + return; + // if (::GetLastError() != ERROR_SUCCESS) + // throw "open error"; + } + else + { + const UInt64 cTime = FILETIME_to_UInt64(fdi.Info.ftCreationTime); + if (cTime > timeStamp) + outFile.SetTime(&mTime, NULL, &mTime); + else + outFile.SetMTime(&mTime); + } + } + } + } + + if (!SetFileAttrib(path, fdi.Info.dwFileAttributes | FILE_ATTRIBUTE_READONLY)) + { + panel.MessageBox_LastError(); + return; + } + return; + } + + if (id == IDM_VER_REVERT) + { + if (!fdi2.IsOpen) + { + panel.MessageBox_Error(L"No file to revert"); + return; + } + if (!sameData || !sameTime) + { + if (!sameData) + { + /* + UString m; + m = "Are you sure you want to revert file ?"; + m.Add_LF(); + m += path; + if (::MessageBoxW(panel.GetParent(), m, L"Version Control: File Revert", MB_OKCANCEL | MB_ICONQUESTION) != IDOK) + return; + */ + COverwriteDialog dialog; + + dialog.OldFileInfo.SetTime(fdi.Info.ftLastWriteTime); + dialog.OldFileInfo.SetSize(fdi.GetSize()); + dialog.OldFileInfo.Path = fs2us(path); + + dialog.NewFileInfo.SetTime(fdi2.Info.ftLastWriteTime); + dialog.NewFileInfo.SetSize(fdi2.GetSize()); + dialog.NewFileInfo.Path = fs2us(path2); + + dialog.ShowExtraButtons = false; + dialog.DefaultButton_is_NO = true; + + INT_PTR writeAnswer = dialog.Create(panel.GetParent()); + + if (writeAnswer != IDYES) + return; + } + + WriteFile(path, + true, // (createAlways = true) means CREATE_ALWAYS + fdi2, panel); + } + else + { + if (!SetFileAttrib(path, fdi2.Info.dwFileAttributes | FILE_ATTRIBUTE_READONLY)) + { + panel.MessageBox_LastError(); + return; + } + } + return; + } + + // if (id == IDM_VER_DIFF) + { + if (!fdi2.IsOpen) + return; + DiffFiles(fs2us(path2), fs2us(path)); + } +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ViewSettings.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ViewSettings.cpp new file mode 100644 index 00000000..4a8f58da --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ViewSettings.cpp @@ -0,0 +1,315 @@ +// ViewSettings.cpp + +#include "StdAfx.h" + +#include "../../../../C/CpuArch.h" + +#include "../../../Common/IntToString.h" +#include "../../../Common/StringConvert.h" + +#include "../../../Windows/Registry.h" +#include "../../../Windows/Synchronization.h" + +#include "ViewSettings.h" + +using namespace NWindows; +using namespace NRegistry; + +#define REG_PATH_FM TEXT("Software") TEXT(STRING_PATH_SEPARATOR) TEXT("7-Zip") TEXT(STRING_PATH_SEPARATOR) TEXT("FM") + +static LPCTSTR const kCUBasePath = REG_PATH_FM; +static LPCTSTR const kCulumnsKeyName = REG_PATH_FM TEXT(STRING_PATH_SEPARATOR) TEXT("Columns"); + +static LPCTSTR const kPositionValueName = TEXT("Position"); +static LPCTSTR const kPanelsInfoValueName = TEXT("Panels"); +static LPCTSTR const kToolbars = TEXT("Toolbars"); + +static LPCWSTR const kPanelPathValueName = L"PanelPath"; + +static LPCTSTR const kListMode = TEXT("ListMode"); +static LPCTSTR const kFolderHistoryValueName = TEXT("FolderHistory"); +static LPCTSTR const kFastFoldersValueName = TEXT("FolderShortcuts"); +static LPCTSTR const kCopyHistoryValueName = TEXT("CopyHistory"); + +static NSynchronization::CCriticalSection g_CS; + +#define Set32(p, v) SetUi32(((Byte *)p), v) +#define SetBool(p, v) Set32(p, ((v) ? 1 : 0)) + +#define Get32(p, dest) dest = GetUi32((const Byte *)p); +#define Get32_LONG(p, dest) dest = (LONG)GetUi32((const Byte *)p); +#define GetBool(p, dest) dest = (GetUi32(p) != 0); + +/* +struct CColumnHeader +{ + UInt32 Version; + UInt32 SortID; + UInt32 Ascending; // bool +}; +*/ + +static const UInt32 kListViewHeaderSize = 3 * 4; +static const UInt32 kColumnInfoSize = 3 * 4; +static const UInt32 kListViewVersion = 1; + +void CListViewInfo::Save(const UString &id) const +{ + const UInt32 dataSize = kListViewHeaderSize + kColumnInfoSize * Columns.Size(); + CByteArr buf(dataSize); + + Set32(buf, kListViewVersion) + Set32(buf + 4, SortID) + SetBool(buf + 8, Ascending) + FOR_VECTOR (i, Columns) + { + const CColumnInfo &column = Columns[i]; + Byte *p = buf + kListViewHeaderSize + i * kColumnInfoSize; + Set32(p, column.PropID) + SetBool(p + 4, column.IsVisible) + Set32(p + 8, column.Width) + } + { + NSynchronization::CCriticalSectionLock lock(g_CS); + CKey key; + key.Create(HKEY_CURRENT_USER, kCulumnsKeyName); + key.SetValue(GetSystemString(id), (const Byte *)buf, dataSize); + } +} + +void CListViewInfo::Read(const UString &id) +{ + Clear(); + CByteBuffer buf; + { + NSynchronization::CCriticalSectionLock lock(g_CS); + CKey key; + if (key.Open(HKEY_CURRENT_USER, kCulumnsKeyName, KEY_READ) != ERROR_SUCCESS) + return; + if (key.QueryValue_Binary(GetSystemString(id), buf) != ERROR_SUCCESS) + return; + } + unsigned size = (unsigned)buf.Size(); + if (size < kListViewHeaderSize) + return; + UInt32 version; + Get32(buf, version) + if (version != kListViewVersion) + return; + Get32(buf + 4, SortID) + GetBool(buf + 8, Ascending) + + IsLoaded = true; + + size -= kListViewHeaderSize; + if (size % kColumnInfoSize != 0) + return; + if (size > 1000 * kColumnInfoSize) + return; + const unsigned numItems = size / kColumnInfoSize; + Columns.ClearAndReserve(numItems); + for (unsigned i = 0; i < numItems; i++) + { + CColumnInfo column; + const Byte *p = buf + kListViewHeaderSize + i * kColumnInfoSize; + Get32(p, column.PropID) + GetBool(p + 4, column.IsVisible) + Get32(p + 8, column.Width) + Columns.AddInReserved(column); + } +} + + +/* +struct CWindowPosition +{ + RECT Rect; + UInt32 Maximized; // bool +}; + +struct CPanelsInfo +{ + UInt32 NumPanels; + UInt32 CurrentPanel; + UInt32 SplitterPos; +}; +*/ + +static const UInt32 kWindowPositionHeaderSize = 5 * 4; +static const UInt32 kPanelsInfoHeaderSize = 3 * 4; + +void CWindowInfo::Save() const +{ + NSynchronization::CCriticalSectionLock lock(g_CS); + CKey key; + key.Create(HKEY_CURRENT_USER, kCUBasePath); + { + Byte buf[kWindowPositionHeaderSize]; + Set32(buf, (UInt32)rect.left) + Set32(buf + 4, (UInt32)rect.top) + Set32(buf + 8, (UInt32)rect.right) + Set32(buf + 12, (UInt32)rect.bottom) + SetBool(buf + 16, maximized) + key.SetValue(kPositionValueName, buf, kWindowPositionHeaderSize); + } + { + Byte buf[kPanelsInfoHeaderSize]; + Set32(buf, numPanels) + Set32(buf + 4, currentPanel) + Set32(buf + 8, splitterPos) + key.SetValue(kPanelsInfoValueName, buf, kPanelsInfoHeaderSize); + } +} + +static bool QueryBuf(CKey &key, LPCTSTR name, CByteBuffer &buf, UInt32 dataSize) +{ + return key.QueryValue_Binary(name, buf) == ERROR_SUCCESS && buf.Size() == dataSize; +} + +void CWindowInfo::Read(bool &windowPosDefined, bool &panelInfoDefined) +{ + windowPosDefined = false; + panelInfoDefined = false; + NSynchronization::CCriticalSectionLock lock(g_CS); + CKey key; + if (key.Open(HKEY_CURRENT_USER, kCUBasePath, KEY_READ) != ERROR_SUCCESS) + return; + CByteBuffer buf; + if (QueryBuf(key, kPositionValueName, buf, kWindowPositionHeaderSize)) + { + Get32_LONG(buf, rect.left) + Get32_LONG(buf + 4, rect.top) + Get32_LONG(buf + 8, rect.right) + Get32_LONG(buf + 12, rect.bottom) + GetBool(buf + 16, maximized) + windowPosDefined = true; + } + if (QueryBuf(key, kPanelsInfoValueName, buf, kPanelsInfoHeaderSize)) + { + Get32(buf, numPanels) + Get32(buf + 4, currentPanel) + Get32(buf + 8, splitterPos) + panelInfoDefined = true; + } + return; +} + + +static void SaveUi32Val(const TCHAR *name, UInt32 value) +{ + CKey key; + key.Create(HKEY_CURRENT_USER, kCUBasePath); + key.SetValue(name, value); +} + +static bool ReadUi32Val(const TCHAR *name, UInt32 &value) +{ + CKey key; + if (key.Open(HKEY_CURRENT_USER, kCUBasePath, KEY_READ) != ERROR_SUCCESS) + return false; + return key.GetValue_UInt32_IfOk(name, value) == ERROR_SUCCESS; +} + +void SaveToolbarsMask(UInt32 toolbarMask) +{ + SaveUi32Val(kToolbars, toolbarMask); +} + +static const UInt32 kDefaultToolbarMask = ((UInt32)1 << 31) | 8 | 4 | 1; + +UInt32 ReadToolbarsMask() +{ + UInt32 mask; + if (!ReadUi32Val(kToolbars, mask)) + return kDefaultToolbarMask; + return mask; +} + + +void CListMode::Save() const +{ + UInt32 t = 0; + for (int i = 0; i < 2; i++) + t |= (Panels[i] & 0xFF) << (i * 8); + SaveUi32Val(kListMode, t); +} + +void CListMode::Read() +{ + Init(); + UInt32 t; + if (!ReadUi32Val(kListMode, t)) + return; + for (int i = 0; i < 2; i++) + { + Panels[i] = t & 0xFF; + t >>= 8; + } +} + +static UString GetPanelPathName(UInt32 panelIndex) +{ + UString s (kPanelPathValueName); + s.Add_UInt32(panelIndex); + return s; +} + +void SavePanelPath(UInt32 panel, const UString &path) +{ + NSynchronization::CCriticalSectionLock lock(g_CS); + CKey key; + key.Create(HKEY_CURRENT_USER, kCUBasePath); + key.SetValue(GetPanelPathName(panel), path); +} + +bool ReadPanelPath(UInt32 panel, UString &path) +{ + NSynchronization::CCriticalSectionLock lock(g_CS); + CKey key; + if (key.Open(HKEY_CURRENT_USER, kCUBasePath, KEY_READ) != ERROR_SUCCESS) + return false; + return (key.QueryValue(GetPanelPathName(panel), path) == ERROR_SUCCESS); +} + + +static void SaveStringList(LPCTSTR valueName, const UStringVector &folders) +{ + NSynchronization::CCriticalSectionLock lock(g_CS); + CKey key; + key.Create(HKEY_CURRENT_USER, kCUBasePath); + key.SetValue_Strings(valueName, folders); +} + +static void ReadStringList(LPCTSTR valueName, UStringVector &folders) +{ + folders.Clear(); + NSynchronization::CCriticalSectionLock lock(g_CS); + CKey key; + if (key.Open(HKEY_CURRENT_USER, kCUBasePath, KEY_READ) == ERROR_SUCCESS) + key.GetValue_Strings(valueName, folders); +} + +void SaveFolderHistory(const UStringVector &folders) + { SaveStringList(kFolderHistoryValueName, folders); } +void ReadFolderHistory(UStringVector &folders) + { ReadStringList(kFolderHistoryValueName, folders); } + +void SaveFastFolders(const UStringVector &folders) + { SaveStringList(kFastFoldersValueName, folders); } +void ReadFastFolders(UStringVector &folders) + { ReadStringList(kFastFoldersValueName, folders); } + +void SaveCopyHistory(const UStringVector &folders) + { SaveStringList(kCopyHistoryValueName, folders); } +void ReadCopyHistory(UStringVector &folders) + { ReadStringList(kCopyHistoryValueName, folders); } + +void AddUniqueStringToHeadOfList(UStringVector &list, const UString &s) +{ + for (unsigned i = 0; i < list.Size();) + if (s.IsEqualTo_NoCase(list[i])) + list.Delete(i); + else + i++; + list.Insert(0, s); +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ViewSettings.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ViewSettings.h new file mode 100644 index 00000000..02af0a15 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/ViewSettings.h @@ -0,0 +1,115 @@ +// ViewSettings.h + +#ifndef ZIP7_INC_VIEW_SETTINGS_H +#define ZIP7_INC_VIEW_SETTINGS_H + +#include "../../../Common/MyTypes.h" +#include "../../../Common/MyString.h" + +struct CColumnInfo +{ + PROPID PropID; + bool IsVisible; + UInt32 Width; + + bool IsEqual(const CColumnInfo &a) const + { + return PropID == a.PropID && IsVisible == a.IsVisible && Width == a.Width; + } +}; + +struct CListViewInfo +{ + CRecordVector Columns; + PROPID SortID; + bool Ascending; + bool IsLoaded; + + void Clear() + { + SortID = 0; + Ascending = true; + IsLoaded = false; + Columns.Clear(); + } + + CListViewInfo(): + SortID(0), + Ascending(true), + IsLoaded(false) + {} + + /* + int FindColumnWithID(PROPID propID) const + { + FOR_VECTOR (i, Columns) + if (Columns[i].PropID == propID) + return i; + return -1; + } + */ + + bool IsEqual(const CListViewInfo &info) const + { + if (Columns.Size() != info.Columns.Size() || + SortID != info.SortID || + Ascending != info.Ascending) + return false; + FOR_VECTOR (i, Columns) + if (!Columns[i].IsEqual(info.Columns[i])) + return false; + return true; + } + + void Save(const UString &id) const; + void Read(const UString &id); +}; + + +struct CWindowInfo +{ + RECT rect; + bool maximized; + + UInt32 numPanels; + UInt32 currentPanel; + UInt32 splitterPos; + + void Save() const; + void Read(bool &windowPosDefined, bool &panelInfoDefined); +}; + +void SaveToolbarsMask(UInt32 toolbarMask); +UInt32 ReadToolbarsMask(); + +const UInt32 kListMode_Report = 3; + +struct CListMode +{ + UInt32 Panels[2]; + + void Init() { Panels[0] = Panels[1] = kListMode_Report; } + CListMode() { Init(); } + + void Save() const ; + void Read(); +}; + + + +void SavePanelPath(UInt32 panel, const UString &path); +bool ReadPanelPath(UInt32 panel, UString &path); + + +void SaveFolderHistory(const UStringVector &folders); +void ReadFolderHistory(UStringVector &folders); + +void SaveFastFolders(const UStringVector &folders); +void ReadFastFolders(UStringVector &folders); + +void SaveCopyHistory(const UStringVector &folders); +void ReadCopyHistory(UStringVector &folders); + +void AddUniqueStringToHeadOfList(UStringVector &list, const UString &s); + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/makefile b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/makefile new file mode 100644 index 00000000..24dc4ca6 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/makefile @@ -0,0 +1,110 @@ +PROG = 7zFM.exe +CFLAGS = $(CFLAGS) \ + -DZ7_EXTERNAL_CODECS \ + +# -DZ7_NO_LARGE_PAGES + +!include "FM.mak" + +COMMON_OBJS = \ + $O\DynLimBuf.obj \ + $O\IntToString.obj \ + $O\Lang.obj \ + $O\MyString.obj \ + $O\MyVector.obj \ + $O\NewHandler.obj \ + $O\Random.obj \ + $O\StringConvert.obj \ + $O\StringToInt.obj \ + $O\UTFConvert.obj \ + $O\Wildcard.obj \ + +WIN_OBJS = $(WIN_OBJS) \ + $O\Clipboard.obj \ + $O\CommonDialog.obj \ + $O\DLL.obj \ + $O\ErrorMsg.obj \ + $O\FileDir.obj \ + $O\FileFind.obj \ + $O\FileIO.obj \ + $O\FileLink.obj \ + $O\FileName.obj \ + $O\MemoryGlobal.obj \ + $O\MemoryLock.obj \ + $O\Menu.obj \ + $O\ProcessUtils.obj \ + $O\PropVariant.obj \ + $O\PropVariantConv.obj \ + $O\Registry.obj \ + $O\ResourceString.obj \ + $O\Shell.obj \ + $O\Synchronization.obj \ + $O\System.obj \ + $O\TimeUtils.obj \ + $O\Window.obj \ + + +WIN_CTRL_OBJS = \ + $O\ComboBox.obj \ + $O\Dialog.obj \ + $O\ListView.obj \ + $O\PropertyPage.obj \ + $O\Window2.obj \ + +7ZIP_COMMON_OBJS = \ + $O\CreateCoder.obj \ + $O\FilePathAutoRename.obj \ + $O\FileStreams.obj \ + $O\FilterCoder.obj \ + $O\LimitedStreams.obj \ + $O\MethodProps.obj \ + $O\ProgressUtils.obj \ + $O\PropId.obj \ + $O\StreamObjects.obj \ + $O\StreamUtils.obj \ + $O\UniqBlocks.obj \ + +UI_COMMON_OBJS = \ + $O\ArchiveExtractCallback.obj \ + $O\ArchiveName.obj \ + $O\ArchiveOpenCallback.obj \ + $O\CompressCall.obj \ + $O\DefaultName.obj \ + $O\EnumDirItems.obj \ + $O\ExtractingFilePath.obj \ + $O\HashCalc.obj \ + $O\LoadCodecs.obj \ + $O\OpenArchive.obj \ + $O\PropIDUtils.obj \ + $O\SetProperties.obj \ + $O\SortUtils.obj \ + $O\UpdateAction.obj \ + $O\UpdateCallback.obj \ + $O\UpdatePair.obj \ + $O\UpdateProduce.obj \ + $O\WorkDir.obj \ + $O\ZipRegistry.obj \ + +EXPLORER_OBJS = \ + $O\ContextMenu.obj \ + $O\MyMessages.obj \ + $O\RegistryContextMenu.obj \ + +GUI_OBJS = \ + $O\HashGUI.obj \ + $O\UpdateCallbackGUI2.obj \ + +COMPRESS_OBJS = \ + $O\CopyCoder.obj \ + +AR_COMMON_OBJS = \ + $O\ItemNameUtils.obj \ + + +C_OBJS = $(C_OBJS) \ + $O\Alloc.obj \ + $O\CpuArch.obj \ + $O\Threads.obj \ + +!include "../../Sort.mak" +!include "../../7zip.mak" diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/resource.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/resource.h new file mode 100644 index 00000000..36c4b531 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/resource.h @@ -0,0 +1,199 @@ +#include "resourceGui.h" + +#define IDR_MENUBAR1 70 +#define IDM_MENU 71 +#define IDR_ACCELERATOR1 72 + +#define IDB_ADD 100 +#define IDB_EXTRACT 101 +#define IDB_TEST 102 +#define IDB_COPY 103 +#define IDB_MOVE 104 +#define IDB_DELETE 105 +#define IDB_INFO 106 + +#define IDB_ADD2 150 +#define IDB_EXTRACT2 151 +#define IDB_TEST2 152 +#define IDB_COPY2 153 +#define IDB_MOVE2 154 +#define IDB_DELETE2 155 +#define IDB_INFO2 156 + +#define IDM_HASH_ALL 101 +#define IDM_CRC32 102 +#define IDM_CRC64 103 +#define IDM_SHA1 104 +#define IDM_SHA256 105 +#define IDM_SHA384 106 +#define IDM_SHA512 107 +#define IDM_SHA3_256 108 +#define IDM_XXH64 120 +#define IDM_BLAKE2SP 121 +#define IDM_MD5 122 + +#define IDM_FILE 500 +#define IDM_EDIT 501 +#define IDM_VIEW 502 +#define IDM_FAVORITES 503 +#define IDM_TOOLS 504 +#define IDM_HELP 505 + +#define IDM_OPEN 540 +#define IDM_OPEN_INSIDE 541 +#define IDM_OPEN_OUTSIDE 542 +#define IDM_FILE_VIEW 543 +#define IDM_FILE_EDIT 544 +#define IDM_RENAME 545 +#define IDM_COPY_TO 546 +#define IDM_MOVE_TO 547 +#define IDM_DELETE 548 +#define IDM_SPLIT 549 +#define IDM_COMBINE 550 +#define IDM_PROPERTIES 551 +#define IDM_COMMENT 552 +#define IDM_CRC 553 +#define IDM_DIFF 554 +#define IDM_CREATE_FOLDER 555 +#define IDM_CREATE_FILE 556 +// #define IDM_EXIT 557 +#define IDM_LINK 558 +#define IDM_ALT_STREAMS 559 + +#define IDM_VER_EDIT 580 +#define IDM_VER_COMMIT 581 +#define IDM_VER_REVERT 582 +#define IDM_VER_DIFF 583 + +#define IDM_OPEN_INSIDE_ONE 590 +#define IDM_OPEN_INSIDE_PARSER 591 + +#define IDM_SELECT_ALL 600 +#define IDM_DESELECT_ALL 601 +#define IDM_INVERT_SELECTION 602 +#define IDM_SELECT 603 +#define IDM_DESELECT 604 +#define IDM_SELECT_BY_TYPE 605 +#define IDM_DESELECT_BY_TYPE 606 + +#define IDM_VIEW_LARGE_ICONS 700 +#define IDM_VIEW_SMALL_ICONS 701 +#define IDM_VIEW_LIST 702 +#define IDM_VIEW_DETAILS 703 + +#define IDM_VIEW_ARANGE_BY_NAME 710 +#define IDM_VIEW_ARANGE_BY_TYPE 711 +#define IDM_VIEW_ARANGE_BY_DATE 712 +#define IDM_VIEW_ARANGE_BY_SIZE 713 + +#define IDM_VIEW_ARANGE_NO_SORT 730 +#define IDM_VIEW_FLAT_VIEW 731 +#define IDM_VIEW_TWO_PANELS 732 +#define IDM_VIEW_TOOLBARS 733 +#define IDM_OPEN_ROOT_FOLDER 734 +#define IDM_OPEN_PARENT_FOLDER 735 +#define IDM_FOLDERS_HISTORY 736 +#define IDM_VIEW_REFRESH 737 +#define IDM_VIEW_AUTO_REFRESH 738 +// #define IDM_VIEW_SHOW_DELETED 739 +// #define IDM_VIEW_SHOW_STREAMS 740 + +#define IDM_VIEW_ARCHIVE_TOOLBAR 750 +#define IDM_VIEW_STANDARD_TOOLBAR 751 +#define IDM_VIEW_TOOLBARS_LARGE_BUTTONS 752 +#define IDM_VIEW_TOOLBARS_SHOW_BUTTONS_TEXT 753 + +#define IDM_VIEW_TIME_POPUP 760 +#define IDM_VIEW_TIME 761 +#define IDM_VIEW_TIME_UTC 799 + +#define IDM_ADD_TO_FAVORITES 800 +#define IDS_BOOKMARK 801 + +#define IDM_OPTIONS 900 +#define IDM_BENCHMARK 901 +#define IDM_BENCHMARK2 902 +#define IDM_TEMP_DIR 910 + +#define IDM_HELP_CONTENTS 960 +#define IDM_ABOUT 961 + +#define IDS_OPTIONS 2100 + +#define IDS_N_SELECTED_ITEMS 3002 + +#define IDS_FILE_EXIST 3008 +#define IDS_WANT_UPDATE_MODIFIED_FILE 3009 +#define IDS_CANNOT_UPDATE_FILE 3010 +#define IDS_CANNOT_START_EDITOR 3011 +#define IDS_VIRUS 3012 +#define IDS_MESSAGE_UNSUPPORTED_OPERATION_FOR_LONG_PATH_FOLDER 3013 +#define IDS_SELECT_ONE_FILE 3014 +#define IDS_SELECT_FILES 3015 +#define IDS_TOO_MANY_ITEMS 3016 + +#define IDS_COPY 6000 +#define IDS_MOVE 6001 +#define IDS_COPY_TO 6002 +#define IDS_MOVE_TO 6003 +#define IDS_COPYING 6004 +// #define IDS_MOVING 6005 +#define IDS_RENAMING 6006 + +#define IDS_OPERATION_IS_NOT_SUPPORTED 6008 +#define IDS_ERROR_RENAMING 6009 +#define IDS_CONFIRM_FILE_COPY 6010 +#define IDS_WANT_TO_COPY_FILES 6011 + +#define IDS_CONFIRM_FILE_DELETE 6100 +#define IDS_CONFIRM_FOLDER_DELETE 6101 +#define IDS_CONFIRM_ITEMS_DELETE 6102 +#define IDS_WANT_TO_DELETE_FILE 6103 +#define IDS_WANT_TO_DELETE_FOLDER 6104 +#define IDS_WANT_TO_DELETE_ITEMS 6105 +#define IDS_DELETING 6106 +#define IDS_ERROR_DELETING 6107 +#define IDS_ERROR_LONG_PATH_TO_RECYCLE 6108 + +#define IDS_CREATE_FOLDER 6300 +#define IDS_CREATE_FILE 6301 +#define IDS_CREATE_FOLDER_NAME 6302 +#define IDS_CREATE_FILE_NAME 6303 +#define IDS_CREATE_FOLDER_DEFAULT_NAME 6304 +#define IDS_CREATE_FILE_DEFAULT_NAME 6305 +#define IDS_CREATE_FOLDER_ERROR 6306 +#define IDS_CREATE_FILE_ERROR 6307 + +#define IDS_COMMENT 6400 +#define IDS_COMMENT2 6401 +#define IDS_SELECT 6402 +#define IDS_DESELECT 6403 +#define IDS_SELECT_MASK 6404 + +#define IDS_PROPERTIES 6600 +#define IDS_FOLDERS_HISTORY 6601 + +#define IDS_COMPUTER 7100 +#define IDS_NETWORK 7101 +#define IDS_DOCUMENTS 7102 +#define IDS_SYSTEM 7103 + +#define IDS_ADD 7200 +#define IDS_EXTRACT 7201 +#define IDS_TEST 7202 +#define IDS_BUTTON_COPY 7203 +#define IDS_BUTTON_MOVE 7204 +#define IDS_BUTTON_DELETE 7205 +#define IDS_BUTTON_INFO 7206 + +#define IDS_SPLITTING 7303 +#define IDS_SPLIT_CONFIRM_TITLE 7304 +#define IDS_SPLIT_CONFIRM_MESSAGE 7305 +#define IDS_SPLIT_VOL_MUST_BE_SMALLER 7306 + +#define IDS_COMBINE 7400 +#define IDS_COMBINE_TO 7401 +#define IDS_COMBINING 7402 +#define IDS_COMBINE_SELECT_ONE_FILE 7403 +#define IDS_COMBINE_CANT_DETECT_SPLIT_FILE 7404 +#define IDS_COMBINE_CANT_FIND_MORE_THAN_ONE_PART 7405 diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/resource.rc b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/resource.rc new file mode 100644 index 00000000..d9fc6f25 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/resource.rc @@ -0,0 +1,296 @@ +#include "../../MyVersionInfo.rc" +#include "../../GuiCommon.rc" +#include "resource.h" + +MY_VERSION_INFO_APP("7-Zip File Manager", "7zFM") + +IDR_ACCELERATOR1 ACCELERATORS +BEGIN +// "N", IDM_CREATE_FILE, VIRTKEY, CONTROL, NOINVERT + VK_F1, IDM_HELP_CONTENTS, VIRTKEY, NOINVERT + VK_F12, IDM_FOLDERS_HISTORY, VIRTKEY, ALT, NOINVERT +// VK_F7, IDM_CREATE_FOLDER, VIRTKEY, NOINVERT +END + +// for MENUEX: +// /* +#define MY_MENUITEM_SEPARATOR MENUITEM "", 0, MFT_SEPARATOR +#define MY_MFT_MENUBREAK MFT_MENUBREAK +#define MY_MFT_MENUBARBREAK MFT_MENUBARBREAK +#define MY_MFS_CHECKED MFT_STRING, MFS_CHECKED +#define MY_MENUITEM_ID(x) , x +// */ + +// for MENU: +/* +#define MY_MENUITEM_SEPARATOR MENUITEM SEPARATOR +#define MY_MFT_MENUBREAK MENUBREAK +#define MY_MFT_MENUBARBREAK MENUBARBREAK +#define MY_MFS_CHECKED CHECKED +#define MY_MENUITEM_ID(x) +*/ + +IDM_MENU MENUEX +BEGIN + POPUP "&File" MY_MENUITEM_ID(IDM_FILE) + BEGIN + MENUITEM "&Open\tEnter", IDM_OPEN + MENUITEM "Open &Inside\tCtrl+PgDn", IDM_OPEN_INSIDE + MENUITEM "Open Inside *", IDM_OPEN_INSIDE_ONE + MENUITEM "Open Inside #", IDM_OPEN_INSIDE_PARSER + MENUITEM "Open O&utside\tShift+Enter", IDM_OPEN_OUTSIDE + MENUITEM "&View\tF3", IDM_FILE_VIEW + MENUITEM "&Edit\tF4", IDM_FILE_EDIT + MY_MENUITEM_SEPARATOR + MENUITEM "Rena&me\tF2", IDM_RENAME + MENUITEM "&Copy To...\tF5", IDM_COPY_TO + MENUITEM "&Move To...\tF6", IDM_MOVE_TO + MENUITEM "&Delete\tDel", IDM_DELETE + MY_MENUITEM_SEPARATOR + MENUITEM "&Split file...", IDM_SPLIT + MENUITEM "Com&bine files...", IDM_COMBINE + MY_MENUITEM_SEPARATOR + MENUITEM "P&roperties\tAlt+Enter", IDM_PROPERTIES + MENUITEM "Comme&nt...\tCtrl+Z", IDM_COMMENT + // MENUITEM "Calculate checksum", IDM_CRC + POPUP "CRC" MY_MENUITEM_ID(0) + BEGIN + MENUITEM "CRC-32", IDM_CRC32 + MENUITEM "CRC-64", IDM_CRC64 + MENUITEM "XXH64", IDM_XXH64 + MENUITEM "MD5", IDM_MD5 + MENUITEM "SHA-1", IDM_SHA1 + MENUITEM "SHA-256", IDM_SHA256 + MENUITEM "SHA-384", IDM_SHA384 + MENUITEM "SHA-512", IDM_SHA512 + MENUITEM "SHA3-256", IDM_SHA3_256 + MENUITEM "BLAKE2sp", IDM_BLAKE2SP + MENUITEM "*", IDM_HASH_ALL + END + MENUITEM "Di&ff", IDM_DIFF + MY_MENUITEM_SEPARATOR + MENUITEM "Create Folder\tF7", IDM_CREATE_FOLDER + MENUITEM "Create File\tCtrl+N", IDM_CREATE_FILE + MY_MENUITEM_SEPARATOR + MENUITEM "&Link...", IDM_LINK + MENUITEM "&Alternate streams", IDM_ALT_STREAMS + MY_MENUITEM_SEPARATOR + MENUITEM "E&xit\tAlt+F4", IDCLOSE + END + POPUP "&Edit" MY_MENUITEM_ID(IDM_EDIT) + BEGIN + // MENUITEM "Cu&t\tCtrl+X", IDM_EDIT_CUT, GRAYED + // MENUITEM "&Copy\tCtrl+C", IDM_EDIT_COPY, GRAYED + // MENUITEM "&Paste\tCtrl+V", IDM_EDIT_PASTE, GRAYED + // MY_MENUITEM_SEPARATOR + MENUITEM "Select &All\tShift+[Grey +]", IDM_SELECT_ALL + MENUITEM "Deselect All\tShift+[Grey -]", IDM_DESELECT_ALL + MENUITEM "&Invert Selection\tGrey *", IDM_INVERT_SELECTION + MENUITEM "Select...\tGrey +", IDM_SELECT + MENUITEM "Deselect...\tGrey -", IDM_DESELECT + MENUITEM "", 0, MY_MFT_MENUBARBREAK + MENUITEM "Select by Type\tAlt+[Grey+]", IDM_SELECT_BY_TYPE + MENUITEM "Deselect by Type\tAlt+[Grey -]", IDM_DESELECT_BY_TYPE + END + POPUP "&View" MY_MENUITEM_ID(IDM_VIEW) + BEGIN + MENUITEM "Lar&ge Icons\tCtrl+1", IDM_VIEW_LARGE_ICONS + MENUITEM "S&mall Icons\tCtrl+2", IDM_VIEW_SMALL_ICONS + MENUITEM "&List\tCtrl+3", IDM_VIEW_LIST + MENUITEM "&Details\tCtrl+4", IDM_VIEW_DETAILS, MY_MFS_CHECKED + MY_MENUITEM_SEPARATOR + MENUITEM "Name\tCtrl+F3", IDM_VIEW_ARANGE_BY_NAME + MENUITEM "Type\tCtrl+F4", IDM_VIEW_ARANGE_BY_TYPE + MENUITEM "Date\tCtrl+F5", IDM_VIEW_ARANGE_BY_DATE + MENUITEM "Size\tCtrl+F6", IDM_VIEW_ARANGE_BY_SIZE + MENUITEM "Unsorted\tCtrl+F7", IDM_VIEW_ARANGE_NO_SORT + MY_MENUITEM_SEPARATOR + MENUITEM "Flat View", IDM_VIEW_FLAT_VIEW + MENUITEM "&2 Panels\tF9", IDM_VIEW_TWO_PANELS + + POPUP "2017" MY_MENUITEM_ID(IDM_VIEW_TIME_POPUP) + BEGIN + MENUITEM "Time", IDM_VIEW_TIME + END + + POPUP "Toolbars" MY_MENUITEM_ID(IDM_VIEW_TOOLBARS) + BEGIN + MENUITEM "Archive Toolbar", IDM_VIEW_ARCHIVE_TOOLBAR + MENUITEM "Standard Toolbar", IDM_VIEW_STANDARD_TOOLBAR + MY_MENUITEM_SEPARATOR + MENUITEM "Large Buttons", IDM_VIEW_TOOLBARS_LARGE_BUTTONS + MENUITEM "Show Buttons Text", IDM_VIEW_TOOLBARS_SHOW_BUTTONS_TEXT + END + MENUITEM "Open Root Folder\t\\", IDM_OPEN_ROOT_FOLDER + MENUITEM "Up One Level\tBackspace", IDM_OPEN_PARENT_FOLDER + MENUITEM "Folders History...\tAlt+F12", IDM_FOLDERS_HISTORY + MENUITEM "&Refresh\tCtrl+R", IDM_VIEW_REFRESH + MENUITEM "Auto Refresh", IDM_VIEW_AUTO_REFRESH + + // MENUITEM "Show NTFS streams", IDM_VIEW_SHOW_STREAMS + // MENUITEM "Show deleted files", IDM_VIEW_SHOW_DELETED + + END + POPUP "F&avorites" MY_MENUITEM_ID(IDM_FAVORITES) + BEGIN + POPUP "&Add folder to Favorites as" MY_MENUITEM_ID(IDM_ADD_TO_FAVORITES) + BEGIN + MY_MENUITEM_SEPARATOR + END + MY_MENUITEM_SEPARATOR + END + POPUP "&Tools" MY_MENUITEM_ID(IDM_TOOLS) + BEGIN + MENUITEM "&Options...", IDM_OPTIONS + MY_MENUITEM_SEPARATOR + MENUITEM "&Benchmark", IDM_BENCHMARK + #ifdef UNDER_CE + MENUITEM "Benchmark 2", IDM_BENCHMARK2 + #endif + MY_MENUITEM_SEPARATOR + MENUITEM "Delete Temporary Files...", IDM_TEMP_DIR + #ifndef UNDER_CE + END + POPUP "&Help" MY_MENUITEM_ID(IDM_HELP) + BEGIN + MENUITEM "&Contents...\tF1", IDM_HELP_CONTENTS + #endif + MY_MENUITEM_SEPARATOR + MENUITEM "&About 7-Zip...", IDM_ABOUT + END +END + + +IDI_ICON ICON "../../UI/FileManager/FM.ico" + +#ifndef UNDER_CE +1 24 MOVEABLE PURE "../../UI/FileManager/7zFM.exe.manifest" +#endif + +IDB_ADD BITMAP "../../UI/FileManager/Add.bmp" +IDB_EXTRACT BITMAP "../../UI/FileManager/Extract.bmp" +IDB_TEST BITMAP "../../UI/FileManager/Test.bmp" +IDB_COPY BITMAP "../../UI/FileManager/Copy.bmp" +IDB_MOVE BITMAP "../../UI/FileManager/Move.bmp" +IDB_DELETE BITMAP "../../UI/FileManager/Delete.bmp" +IDB_INFO BITMAP "../../UI/FileManager/Info.bmp" +IDB_ADD2 BITMAP "../../UI/FileManager/Add2.bmp" +IDB_EXTRACT2 BITMAP "../../UI/FileManager/Extract2.bmp" +IDB_TEST2 BITMAP "../../UI/FileManager/Test2.bmp" +IDB_COPY2 BITMAP "../../UI/FileManager/Copy2.bmp" +IDB_MOVE2 BITMAP "../../UI/FileManager/Move2.bmp" +IDB_DELETE2 BITMAP "../../UI/FileManager/Delete2.bmp" +IDB_INFO2 BITMAP "../../UI/FileManager/Info2.bmp" + + +STRINGTABLE +BEGIN + IDS_BOOKMARK "Bookmark" + + IDS_OPTIONS "Options" + + IDS_N_SELECTED_ITEMS "{0} object(s) selected" + + IDS_FILE_EXIST "File {0} is already exist" + IDS_WANT_UPDATE_MODIFIED_FILE "File '{0}' was modified.\nDo you want to update it in the archive?" + IDS_CANNOT_UPDATE_FILE "Cannot update file\n'{0}'" + IDS_CANNOT_START_EDITOR "Cannot start editor." + IDS_VIRUS "The file looks like a virus (the file name contains long spaces in name)." + IDS_MESSAGE_UNSUPPORTED_OPERATION_FOR_LONG_PATH_FOLDER "The operation cannot be called from a folder that has a long path." + IDS_SELECT_ONE_FILE "You must select one file" + // IDS_SELECT_FILES "You must select one or more files" + IDS_TOO_MANY_ITEMS "Too many items" + + IDS_COPY "Copy" + IDS_MOVE "Move" + IDS_COPY_TO "Copy to:" + IDS_MOVE_TO "Move to:" + IDS_COPYING "Copying..." +// IDS_MOVING "Moving..." + IDS_RENAMING "Renaming..." + + IDS_OPERATION_IS_NOT_SUPPORTED "Operation is not supported." + IDS_ERROR_RENAMING "Error Renaming File or Folder" + IDS_CONFIRM_FILE_COPY "Confirm File Copy" + IDS_WANT_TO_COPY_FILES "Are you sure you want to copy files to archive" + + IDS_CONFIRM_FILE_DELETE "Confirm File Delete" + IDS_CONFIRM_FOLDER_DELETE "Confirm Folder Delete" + IDS_CONFIRM_ITEMS_DELETE "Confirm Multiple File Delete" + IDS_WANT_TO_DELETE_FILE "Are you sure you want to delete '{0}'?" + IDS_WANT_TO_DELETE_FOLDER "Are you sure you want to delete the folder '{0}' and all its contents?" + IDS_WANT_TO_DELETE_ITEMS "Are you sure you want to delete these {0} items?" + IDS_DELETING "Deleting..." + IDS_ERROR_DELETING "Error Deleting File or Folder" + IDS_ERROR_LONG_PATH_TO_RECYCLE "The system cannot move a file with long path to the Recycle Bin" + + IDS_CREATE_FOLDER "Create Folder" + IDS_CREATE_FILE "Create File" + IDS_CREATE_FOLDER_NAME "Folder name:" + IDS_CREATE_FILE_NAME "File Name:" + IDS_CREATE_FOLDER_DEFAULT_NAME "New Folder" + IDS_CREATE_FILE_DEFAULT_NAME "New File" + IDS_CREATE_FOLDER_ERROR "Error Creating Folder" + IDS_CREATE_FILE_ERROR "Error Creating File" + + IDS_COMMENT "Comment" + IDS_COMMENT2 "&Comment:" + IDS_SELECT "Select" + IDS_DESELECT "Deselect" + IDS_SELECT_MASK "Mask:" + + IDS_PROPERTIES "Properties" + IDS_FOLDERS_HISTORY "Folders History" + + IDS_COMPUTER "Computer" + IDS_NETWORK "Network" + IDS_DOCUMENTS "Documents" + IDS_SYSTEM "System" + + IDS_ADD "Add" + IDS_EXTRACT "Extract" + IDS_TEST "Test" + IDS_BUTTON_COPY "Copy" + IDS_BUTTON_MOVE "Move" + IDS_BUTTON_DELETE "Delete" + IDS_BUTTON_INFO "Info" + + IDS_SPLITTING "Splitting..." + IDS_SPLIT_CONFIRM_TITLE "Confirm Splitting" + IDS_SPLIT_CONFIRM_MESSAGE "Are you sure you want to split file into {0} volumes?" + IDS_SPLIT_VOL_MUST_BE_SMALLER "Volume size must be smaller than size of original file" + + IDS_COMBINE "Combine Files" + IDS_COMBINE_TO "&Combine to:" + IDS_COMBINING "Combining..." + IDS_COMBINE_SELECT_ONE_FILE "Select only first part of split file" + IDS_COMBINE_CANT_DETECT_SPLIT_FILE "Cannot detect file as split file" + IDS_COMBINE_CANT_FIND_MORE_THAN_ONE_PART "Cannot find more than one part of split file" + +END + +#include "AboutDialog.rc" +#include "BrowseDialog.rc" +#include "BrowseDialog2.rc" +#include "ComboDialog.rc" +#include "CopyDialog.rc" +#include "EditDialog.rc" +#include "EditPage.rc" +#include "FoldersPage.rc" +#include "LangPage.rc" +#include "LinkDialog.rc" +#include "ListViewDialog.rc" +#include "MemDialog.rc" +#include "MenuPage.rc" +#include "MessagesDialog.rc" +#include "OverwriteDialog.rc" +#include "PasswordDialog.rc" +#include "ProgressDialog2.rc" +#include "PropertyName.rc" +#include "SettingsPage.rc" +#include "SplitDialog.rc" +#include "SystemPage.rc" +#include "../GUI/Extract.rc" +#include "../GUI/resource3.rc" +#include "../Explorer/resource2.rc" +#include "resourceGui.rc" diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/resourceGui.h b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/resourceGui.h new file mode 100644 index 00000000..2e1bab39 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/resourceGui.h @@ -0,0 +1,25 @@ +#define IDI_ICON 1 + +#define IDS_MESSAGE_NO_ERRORS 3001 + +#define IDS_PROGRESS_TESTING 3302 +#define IDS_OPENNING 3303 +#define IDS_SCANNING 3304 + +#define IDS_MOVING 6005 + +#define IDS_CHECKSUM_CALCULATING 7500 +#define IDS_CHECKSUM_INFORMATION 7501 +#define IDS_CHECKSUM_CRC_DATA 7502 +#define IDS_CHECKSUM_CRC_DATA_NAMES 7503 +#define IDS_CHECKSUM_CRC_STREAMS_NAMES 7504 + +#define IDS_INCORRECT_VOLUME_SIZE 7307 + +#define IDS_MEM_REQUIRES_BIG_MEM 7811 +#define IDS_MEM_REQUIRED_MEM_SIZE 7812 +#define IDS_MEM_CURRENT_MEM_LIMIT 7813 +#define IDS_MEM_USAGE_LIMIT_SET_BY_7ZIP 7814 +#define IDS_MEM_RAM_SIZE 7815 + +#define IDS_MSG_ARC_UNPACKING_WAS_SKIPPED 7822 diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/resourceGui.rc b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/resourceGui.rc new file mode 100644 index 00000000..ad0d1f47 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/FileManager/resourceGui.rc @@ -0,0 +1,30 @@ +#include "resourceGui.h" + +STRINGTABLE +BEGIN + IDS_MESSAGE_NO_ERRORS "There are no errors" + + IDS_PROGRESS_TESTING "Testing" + + IDS_MOVING "Moving..." + + IDS_CHECKSUM_CALCULATING "Checksum calculating..." + IDS_CHECKSUM_INFORMATION "Checksum information" + IDS_CHECKSUM_CRC_DATA "CRC checksum for data:" + IDS_CHECKSUM_CRC_DATA_NAMES "CRC checksum for data and names:" + IDS_CHECKSUM_CRC_STREAMS_NAMES "CRC checksum for streams and names:" + + IDS_INCORRECT_VOLUME_SIZE "Incorrect volume size" + + IDS_OPENNING "Opening..." + IDS_SCANNING "Scanning..." + IDS_MEM_REQUIRES_BIG_MEM "The operation requires big amount of memory (RAM)." + IDS_MEM_REQUIRED_MEM_SIZE "required memory usage size" + IDS_MEM_CURRENT_MEM_LIMIT "allowed memory usage limit" + IDS_MEM_USAGE_LIMIT_SET_BY_7ZIP "memory usage limit set by 7-Zip" + IDS_MEM_RAM_SIZE "RAM size" + + IDS_MSG_ARC_UNPACKING_WAS_SKIPPED "Archive extraction was skipped." +// IDS_MSG_ARC_FILES_UNPACKING_WAS_SKIPPED "Files extraction was skipped." + +END diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/GUI/7zG.exe.manifest b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/7zG.exe.manifest new file mode 100644 index 00000000..ae964d82 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/7zG.exe.manifest @@ -0,0 +1,23 @@ + + + + 7-Zip GUI. + + + + + + + + + + + + + true + + + + +true + diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/GUI/BenchmarkDialog.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/BenchmarkDialog.cpp new file mode 100644 index 00000000..980161ff --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/BenchmarkDialog.cpp @@ -0,0 +1,1921 @@ +// BenchmarkDialog.cpp + +#include "StdAfx.h" + +#include "../../../../C/CpuArch.h" + +#include "../../../Common/Defs.h" +#include "../../../Common/IntToString.h" +#include "../../../Common/MyException.h" +#include "../../../Common/StringConvert.h" +#include "../../../Common/StringToInt.h" + +#include "../../../Windows/Synchronization.h" +#include "../../../Windows/System.h" +#include "../../../Windows/Thread.h" +#include "../../../Windows/SystemInfo.h" + +#include "../../../Windows/Control/ComboBox.h" +#include "../../../Windows/Control/Edit.h" + +#include "../../Common/MethodProps.h" + +#include "../FileManager/DialogSize.h" +#include "../FileManager/HelpUtils.h" +#include "../FileManager/LangUtils.h" +#include "../FileManager/resourceGui.h" + +#include "../../MyVersion.h" + +#include "../Common/Bench.h" + +#include "BenchmarkDialogRes.h" +#include "BenchmarkDialog.h" + +using namespace NWindows; + +#define kHelpTopic "fm/benchmark.htm" + +static const UINT_PTR kTimerID = 4; +static const UINT kTimerElapse = 1000; // 1000 + +// use PRINT_ITER_TIME to show time of each iteration in log box +// #define PRINT_ITER_TIME + +static const unsigned kRatingVector_NumBundlesMax = 20; + +enum MyBenchMessages +{ + k_Message_Finished = WM_APP + 1 +}; + +enum My_Message_WPARAM +{ + k_Msg_WPARM_Thread_Finished = 0, + k_Msg_WPARM_Iter_Finished, + k_Msg_WPARM_Enc1_Finished +}; + + +struct CBenchPassResult +{ + CTotalBenchRes Enc; + CTotalBenchRes Dec; +#ifdef PRINT_ITER_TIME + DWORD Ticks; +#endif + // CBenchInfo EncInfo; // for debug + // CBenchPassResult() {}; +}; + + +struct CTotalBenchRes2: public CTotalBenchRes +{ + UInt64 UnpackSize; + + void Init() + { + CTotalBenchRes::Init(); + UnpackSize = 0; + } + + void SetFrom_BenchInfo(const CBenchInfo &info) + { + NumIterations2 = 1; + Generate_From_BenchInfo(info); + UnpackSize = info.Get_UnpackSize_Full(); + } + + void Update_With_Res2(const CTotalBenchRes2 &r) + { + Update_With_Res(r); + UnpackSize += r.UnpackSize; + } +}; + + +struct CSyncData +{ + UInt32 NumPasses_Finished; +#ifdef PRINT_ITER_TIME + DWORD TotalTicks; +#endif + int RatingVector_DeletedIndex; + // UInt64 RatingVector_NumDeleted; + + bool BenchWasFinished; // all passes were finished + bool NeedPrint_Freq; + bool NeedPrint_RatingVector; + bool NeedPrint_Enc_1; + bool NeedPrint_Enc; + bool NeedPrint_Dec_1; + bool NeedPrint_Dec; + bool NeedPrint_Tot; // intermediate Total was updated after current pass + + // UInt64 NumEncProgress; // for debug + // UInt64 NumDecProgress; // for debug + // CBenchInfo EncInfo; // for debug + + CTotalBenchRes2 Enc_BenchRes_1; + CTotalBenchRes2 Enc_BenchRes; + + CTotalBenchRes2 Dec_BenchRes_1; + CTotalBenchRes2 Dec_BenchRes; + + void Init(); +}; + + +void CSyncData::Init() +{ + NumPasses_Finished = 0; + + // NumEncProgress = 0; + // NumDecProgress = 0; + + Enc_BenchRes.Init(); + Enc_BenchRes_1.Init(); + Dec_BenchRes.Init(); + Dec_BenchRes_1.Init(); + + #ifdef PRINT_ITER_TIME + TotalTicks = 0; + #endif + + RatingVector_DeletedIndex = -1; + // RatingVector_NumDeleted = 0; + + BenchWasFinished = + NeedPrint_Freq = + NeedPrint_RatingVector = + NeedPrint_Enc_1 = + NeedPrint_Enc = + NeedPrint_Dec_1 = + NeedPrint_Dec = + NeedPrint_Tot = false; +} + + +struct CBenchProgressSync +{ + bool Exit; // GUI asks BenchThread to Exit, and BenchThread reads that variable + bool TextWasChanged; + + UInt32 NumThreads; + UInt64 DictSize; + UInt32 NumPasses_Limit; + int Level; + + AString Text; + + /* BenchFinish_Task_HRESULT - for result from benchmark code + BenchFinish_Thread_HRESULT - for Exceptions and service errors + these arreos must be shown even if user escapes benchmark */ + HRESULT BenchFinish_Task_HRESULT; + HRESULT BenchFinish_Thread_HRESULT; + + UInt32 NumFreqThreadsPrev; + UString FreqString_Sync; + UString FreqString_GUI; + + // must be written by benchmark thread, read by GUI thread */ + CRecordVector RatingVector; + CSyncData sd; + + NWindows::NSynchronization::CCriticalSection CS; + + CBenchProgressSync() + { + NumPasses_Limit = 1; + } + + void Init(); + + void SendExit() + { + NWindows::NSynchronization::CCriticalSectionLock lock(CS); + Exit = true; + } +}; + + +void CBenchProgressSync::Init() +{ + Exit = false; + + BenchFinish_Task_HRESULT = S_OK; + BenchFinish_Thread_HRESULT = S_OK; + + sd.Init(); + RatingVector.Clear(); + + NumFreqThreadsPrev = 0; + FreqString_Sync.Empty(); + FreqString_GUI.Empty(); + + Text.Empty(); + TextWasChanged = true; +} + + + +struct CMyFont +{ + HFONT _font; + CMyFont(): _font(NULL) {} + ~CMyFont() + { + if (_font) + DeleteObject(_font); + } + void Create(const LOGFONT *lplf) + { + _font = CreateFontIndirect(lplf); + } +}; + + +class CBenchmarkDialog; + +struct CThreadBenchmark +{ + CBenchmarkDialog *BenchmarkDialog; + DECL_EXTERNAL_CODECS_LOC_VARS_DECL + // HRESULT Result; + + HRESULT Process(); + static THREAD_FUNC_DECL MyThreadFunction(void *param) + { + /* ((CThreadBenchmark *)param)->Result = */ + ((CThreadBenchmark *)param)->Process(); + return 0; + } +}; + + +class CBenchmarkDialog: + public NWindows::NControl::CModalDialog +{ + bool _finishTime_WasSet; + + bool WasStopped_in_GUI; + bool ExitWasAsked_in_GUI; + bool NeedRestart; + + bool RamSize_Defined; + +public: + bool TotalMode; + +private: + + NWindows::NControl::CComboBox m_Dictionary; + NWindows::NControl::CComboBox m_NumThreads; + NWindows::NControl::CComboBox m_NumPasses; + NWindows::NControl::CEdit _consoleEdit; + UINT_PTR _timer; + + UInt32 _startTime; + UInt32 _finishTime; + + CMyFont _font; + + size_t RamSize; + size_t RamSize_Limit; + + UInt32 NumPasses_Finished_Prev; + + UString ElapsedSec_Prev; + + void InitSyncNew() + { + NumPasses_Finished_Prev = (UInt32)(Int32)-1; + ElapsedSec_Prev.Empty(); + Sync.Init(); + } + + virtual bool OnInit() Z7_override; + virtual bool OnDestroy() Z7_override; + virtual bool OnSize(WPARAM /* wParam */, int xSize, int ySize) Z7_override; + virtual bool OnMessage(UINT message, WPARAM wParam, LPARAM lParam) Z7_override; + virtual bool OnCommand(unsigned code, unsigned itemID, LPARAM lParam) Z7_override; + virtual void OnHelp() Z7_override; + virtual void OnCancel() Z7_override; + virtual bool OnTimer(WPARAM timerID, LPARAM callback) Z7_override; + virtual bool OnButtonClicked(unsigned buttonID, HWND buttonHWND) Z7_override; + + void Disable_Stop_Button(); + void OnStopButton(); + void RestartBenchmark(); + void StartBenchmark(); + + void UpdateGui(); + + void PrintTime(); + void PrintRating(UInt64 rating, UINT controlID); + void PrintUsage(UInt64 usage, UINT controlID); + void PrintBenchRes(const CTotalBenchRes2 &info, const UINT ids[]); + + UInt32 GetNumberOfThreads(); + size_t OnChangeDictionary(); + + void SetItemText_Number(unsigned itemID, UInt64 val, LPCTSTR post = NULL); + void Print_MemUsage(UString &s, UInt64 memUsage) const; + bool IsMemoryUsageOK(UInt64 memUsage) const + { return memUsage + (1 << 20) <= RamSize_Limit; } + + void MyKillTimer(); + + void SendExit_Status(const wchar_t *message) + { + SetItemText(IDT_BENCH_ERROR_MESSAGE, message); + Sync.SendExit(); + } + +public: + CBenchProgressSync Sync; + + CObjectVector Props; + + CSysString Bench2Text; + + NWindows::CThread _thread; + CThreadBenchmark _threadBenchmark; + + CBenchmarkDialog(): + WasStopped_in_GUI(false), + ExitWasAsked_in_GUI(false), + NeedRestart(false), + TotalMode(false), + _timer(0) + {} + + ~CBenchmarkDialog() Z7_DESTRUCTOR_override; + + bool PostMsg_Finish(WPARAM wparam) + { + if ((HWND)*this) + return PostMsg(k_Message_Finished, wparam); + // the (HWND)*this is NULL only for some internal code failure + return true; + } + + INT_PTR Create(HWND wndParent = NULL) + { + BIG_DIALOG_SIZE(332, 228); + return CModalDialog::Create(TotalMode ? IDD_BENCH_TOTAL : SIZED_DIALOG(IDD_BENCH), wndParent); + } + void MessageBoxError(LPCWSTR message) + { + MessageBoxW(*this, message, L"7-Zip", MB_ICONERROR); + } + void MessageBoxError_Status(LPCWSTR message) + { + UString s ("ERROR: "); + s += message; + MessageBoxError(s); + SetItemText(IDT_BENCH_ERROR_MESSAGE, s); + } +}; + + + + + + + + + +UString HResultToMessage(HRESULT errorCode); + +#ifdef Z7_LANG +static const UInt32 kLangIDs[] = +{ + IDT_BENCH_DICTIONARY, + IDT_BENCH_MEMORY, + IDT_BENCH_NUM_THREADS, + IDT_BENCH_SIZE, + IDT_BENCH_RATING_LABEL, + IDT_BENCH_USAGE_LABEL, + IDT_BENCH_RPU_LABEL, + IDG_BENCH_COMPRESSING, + IDG_BENCH_DECOMPRESSING, + IDG_BENCH_TOTAL_RATING, + IDT_BENCH_CURRENT, + IDT_BENCH_RESULTING, + IDT_BENCH_ELAPSED, + IDT_BENCH_PASSES, + IDB_STOP, + IDB_RESTART +}; + +static const UInt32 kLangIDs_RemoveColon[] = +{ + IDT_BENCH_SPEED +}; + +#endif + +static LPCTSTR const kProcessingString = TEXT("..."); +static LPCTSTR const kGB = TEXT(" GB"); +static LPCTSTR const kMB = TEXT(" MB"); +static LPCTSTR const kKB = TEXT(" KB"); +// static LPCTSTR const kMIPS = TEXT(" MIPS"); +static LPCTSTR const kKBs = TEXT(" KB/s"); + +static const unsigned kMinDicLogSize = 18; + +static const UInt32 kMinDicSize = (UInt32)1 << kMinDicLogSize; +static const size_t kMaxDicSize = (size_t)1 << (22 + sizeof(size_t) / 4 * 5); +// static const size_t kMaxDicSize = (size_t)1 << 16; + /* + #ifdef MY_CPU_64BIT + (UInt32)(Int32)-1; // we can use it, if we want 4 GB buffer + // (UInt32)15 << 28; + #else + (UInt32)1 << 27; + #endif + */ + + +static int ComboBox_Add_UInt32(NWindows::NControl::CComboBox &cb, UInt32 v) +{ + WCHAR s[16]; + ConvertUInt32ToString(v, s); + return (int)cb.AddString_SetItemData(s, (LPARAM)v); +} + + +bool CBenchmarkDialog::OnInit() +{ + #ifdef Z7_LANG + LangSetWindowText(*this, IDD_BENCH); + LangSetDlgItems(*this, kLangIDs, Z7_ARRAY_SIZE(kLangIDs)); + LangSetDlgItems_RemoveColon(*this, kLangIDs_RemoveColon, Z7_ARRAY_SIZE(kLangIDs_RemoveColon)); + LangSetDlgItemText(*this, IDT_BENCH_CURRENT2, IDT_BENCH_CURRENT); + LangSetDlgItemText(*this, IDT_BENCH_RESULTING2, IDT_BENCH_RESULTING); + #endif + + InitSyncNew(); + + if (TotalMode) + { + _consoleEdit.Attach(GetItem(IDE_BENCH2_EDIT)); + LOGFONT f; + memset(&f, 0, sizeof(f)); + f.lfHeight = 14; + f.lfWidth = 0; + f.lfWeight = FW_DONTCARE; + f.lfCharSet = DEFAULT_CHARSET; + f.lfOutPrecision = OUT_DEFAULT_PRECIS; + f.lfClipPrecision = CLIP_DEFAULT_PRECIS; + f.lfQuality = DEFAULT_QUALITY; + + f.lfPitchAndFamily = FIXED_PITCH; + // MyStringCopy(f.lfFaceName, TEXT("")); + // f.lfFaceName[0] = 0; + _font.Create(&f); + if (_font._font) + _consoleEdit.SendMsg(WM_SETFONT, (WPARAM)_font._font, TRUE); + } + + UInt32 numCPUs = 1; // process threads + UInt32 numCPUs_Sys = 1; // system threads + + { + NSystem::CProcessAffinity threadsInfo; + threadsInfo.InitST(); +#ifndef Z7_ST + threadsInfo.Get_and_return_NumProcessThreads_and_SysThreads(numCPUs, numCPUs_Sys); +#endif + + AString s ("/ "); + s.Add_UInt32(numCPUs); + s += GetProcessThreadsInfo(threadsInfo); + SetItemTextA(IDT_BENCH_HARDWARE_THREADS, s); + + { + AString s2; + GetSysInfo(s, s2); + SetItemTextA(IDT_BENCH_SYS1, s); + if (s != s2 && !s2.IsEmpty()) + SetItemTextA(IDT_BENCH_SYS2, s2); + + GetCpuName_MultiLine(s, s2); // s2==registers + SetItemTextA(IDT_BENCH_CPU, s); + } + { + GetOsInfoText(s); + s += " : "; + AddCpuFeatures(s); + SetItemTextA(IDT_BENCH_CPU_FEATURE, s); + } + + s = "7-Zip " MY_VERSION_CPU; + SetItemTextA(IDT_BENCH_VER, s); + } + + + // ----- Num Threads ---------- + + UInt32 numThreads = Sync.NumThreads; + if (numThreads == (UInt32)(Int32)-1) + numThreads = numCPUs; + numThreads &= ~(UInt32)1; + if (numThreads == 0) + numThreads = 1; + numThreads = MyMin(numThreads, (UInt32)(1u << 14)); + + m_NumThreads.Attach(GetItem(IDC_BENCH_NUM_THREADS)); + if (numCPUs_Sys == 0) + numCPUs_Sys = 1; + const UInt32 numTheads_Combo = numCPUs_Sys * 2; + UInt32 v = 1; + int cur = 0; + for (; v <= numTheads_Combo;) + { + int index = ComboBox_Add_UInt32(m_NumThreads, v); + const UInt32 vNext = v + (v < 2 ? 1 : 2); + if (v <= numThreads) + if (numThreads < vNext || vNext > numTheads_Combo) + { + if (v != numThreads) + index = ComboBox_Add_UInt32(m_NumThreads, numThreads); + cur = index; + } + v = vNext; + } + m_NumThreads.SetCurSel(cur); + Sync.NumThreads = GetNumberOfThreads(); + + + // ----- Dictionary ---------- + + m_Dictionary.Attach(GetItem(IDC_BENCH_DICTIONARY)); + + RamSize = (UInt64)(sizeof(size_t)) << 29; + RamSize_Defined = NSystem::GetRamSize(RamSize); + + + #ifdef UNDER_CE + const UInt32 kNormalizedCeSize = (16 << 20); + if (RamSize > kNormalizedCeSize && RamSize < (33 << 20)) + RamSize = kNormalizedCeSize; + #endif + RamSize_Limit = RamSize / 16 * 15; + + if (Sync.DictSize == (UInt64)(Int64)-1) + { + unsigned dicSizeLog = 25; + #ifdef UNDER_CE + dicSizeLog = 20; + #endif + if (RamSize_Defined) + for (; dicSizeLog > kBenchMinDicLogSize; dicSizeLog--) + if (IsMemoryUsageOK(GetBenchMemoryUsage( + Sync.NumThreads, Sync.Level, (UInt64)1 << dicSizeLog, TotalMode))) + break; + Sync.DictSize = (UInt64)1 << dicSizeLog; + } + + if (Sync.DictSize < kMinDicSize) Sync.DictSize = kMinDicSize; + if (Sync.DictSize > kMaxDicSize) Sync.DictSize = kMaxDicSize; + + cur = 0; + for (unsigned i = (kMinDicLogSize - 1) * 2; i <= (32 - 1) * 2; i++) + { + const size_t dict = (size_t)(2 + (i & 1)) << (i / 2); + // if (i == (32 - 1) * 2) dict = kMaxDicSize; + TCHAR s[32]; + const TCHAR *post; + UInt32 d; + if (dict >= ((UInt32)1 << 31)) { d = (UInt32)(dict >> 30); post = kGB; } + else if (dict >= ((UInt32)1 << 21)) { d = (UInt32)(dict >> 20); post = kMB; } + else { d = (UInt32)(dict >> 10); post = kKB; } + ConvertUInt32ToString(d, s); + lstrcat(s, post); + const int index = (int)m_Dictionary.AddString(s); + m_Dictionary.SetItemData(index, (LPARAM)dict); + if (dict <= Sync.DictSize) + cur = index; + if (dict >= kMaxDicSize) + break; + } + m_Dictionary.SetCurSel(cur); + + + // ----- Num Passes ---------- + + m_NumPasses.Attach(GetItem(IDC_BENCH_NUM_PASSES)); + cur = 0; + v = 1; + for (;;) + { + int index = ComboBox_Add_UInt32(m_NumPasses, v); + const bool isLast = (v >= 10000000); + UInt32 vNext = v * 10; + if (v < 2) vNext = 2; + else if (v < 5) vNext = 5; + else if (v < 10) vNext = 10; + + if (v <= Sync.NumPasses_Limit) + if (isLast || Sync.NumPasses_Limit < vNext) + { + if (v != Sync.NumPasses_Limit) + index = ComboBox_Add_UInt32(m_NumPasses, Sync.NumPasses_Limit); + cur = index; + } + v = vNext; + if (isLast) + break; + } + m_NumPasses.SetCurSel(cur); + + if (TotalMode) + NormalizeSize(true); + else + NormalizePosition(); + + RestartBenchmark(); + + return CModalDialog::OnInit(); +} + + +bool CBenchmarkDialog::OnSize(WPARAM /* wParam */, int xSize, int ySize) +{ + int mx, my; + GetMargins(8, mx, my); + + if (!TotalMode) + { + RECT rect; + GetClientRectOfItem(IDT_BENCH_LOG, rect); + int x = xSize - rect.left - mx; + int y = ySize - rect.top - my; + if (x < 0) x = 0; + if (y < 0) y = 0; + MoveItem(IDT_BENCH_LOG, rect.left, rect.top, x, y, true); + return false; + } + + int bx1, bx2, by; + + GetItemSizes(IDCANCEL, bx1, by); + GetItemSizes(IDHELP, bx2, by); + + { + int y = ySize - my - by; + int x = xSize - mx - bx1; + + InvalidateRect(NULL); + + MoveItem(IDCANCEL, x, y, bx1, by); + MoveItem(IDHELP, x - mx - bx2, y, bx2, by); + } + + if (_consoleEdit) + { + int yPos = ySize - my - by; + RECT rect; + GetClientRectOfItem(IDE_BENCH2_EDIT, rect); + int y = rect.top; + int ySize2 = yPos - my - y; + const int kMinYSize = 20; + int xx = xSize - mx * 2; + if (ySize2 < kMinYSize) + { + ySize2 = kMinYSize; + } + _consoleEdit.Move(mx, y, xx, ySize2); + } + return false; +} + + +UInt32 CBenchmarkDialog::GetNumberOfThreads() +{ + return (UInt32)m_NumThreads.GetItemData_of_CurSel(); +} + + +#define UINT_TO_STR_3(s, val) { \ + s[0] = (wchar_t)('0' + (val) / 100); \ + s[1] = (wchar_t)('0' + (val) % 100 / 10); \ + s[2] = (wchar_t)('0' + (val) % 10); \ + s += 3; s[0] = 0; } + +static WCHAR *NumberToDot3(UInt64 val, WCHAR *s) +{ + s = ConvertUInt64ToString(val / 1000, s); + const UInt32 rem = (UInt32)(val % 1000); + *s++ = '.'; + UINT_TO_STR_3(s, rem) + return s; +} + +void CBenchmarkDialog::SetItemText_Number(unsigned itemID, UInt64 val, LPCTSTR post) +{ + TCHAR s[64]; + ConvertUInt64ToString(val, s); + if (post) + lstrcat(s, post); + SetItemText(itemID, s); +} + +static void AddSize_MB(UString &s, UInt64 size) +{ + s.Add_UInt64((size + (1 << 20) - 1) >> 20); + s += kMB; +} + +void CBenchmarkDialog::Print_MemUsage(UString &s, UInt64 memUsage) const +{ + AddSize_MB(s, memUsage); + if (RamSize_Defined) + { + s += " / "; + AddSize_MB(s, RamSize); + } +} + +size_t CBenchmarkDialog::OnChangeDictionary() +{ + const size_t dict = (size_t)m_Dictionary.GetItemData_of_CurSel(); + const UInt64 memUsage = GetBenchMemoryUsage(GetNumberOfThreads(), + Sync.Level, + dict, + false); // totalBench mode + + UString s; + Print_MemUsage(s, memUsage); + + #ifdef Z7_LARGE_PAGES + { + AString s2; + Add_LargePages_String(s2); + if (!s2.IsEmpty()) + { + s.Add_Space(); + s += s2; + } + } + #endif + + SetItemText(IDT_BENCH_MEMORY_VAL, s); + + return dict; +} + + +static const UInt32 g_IDs[] = +{ + IDT_BENCH_COMPRESS_SIZE1, + IDT_BENCH_COMPRESS_SIZE2, + IDT_BENCH_COMPRESS_USAGE1, + IDT_BENCH_COMPRESS_USAGE2, + IDT_BENCH_COMPRESS_SPEED1, + IDT_BENCH_COMPRESS_SPEED2, + IDT_BENCH_COMPRESS_RATING1, + IDT_BENCH_COMPRESS_RATING2, + IDT_BENCH_COMPRESS_RPU1, + IDT_BENCH_COMPRESS_RPU2, + + IDT_BENCH_DECOMPR_SIZE1, + IDT_BENCH_DECOMPR_SIZE2, + IDT_BENCH_DECOMPR_SPEED1, + IDT_BENCH_DECOMPR_SPEED2, + IDT_BENCH_DECOMPR_RATING1, + IDT_BENCH_DECOMPR_RATING2, + IDT_BENCH_DECOMPR_USAGE1, + IDT_BENCH_DECOMPR_USAGE2, + IDT_BENCH_DECOMPR_RPU1, + IDT_BENCH_DECOMPR_RPU2, + + IDT_BENCH_TOTAL_USAGE_VAL, + IDT_BENCH_TOTAL_RATING_VAL, + IDT_BENCH_TOTAL_RPU_VAL +}; + + +static const unsigned k_Ids_Enc_1[] = { + IDT_BENCH_COMPRESS_USAGE1, + IDT_BENCH_COMPRESS_SPEED1, + IDT_BENCH_COMPRESS_RPU1, + IDT_BENCH_COMPRESS_RATING1, + IDT_BENCH_COMPRESS_SIZE1 }; + +static const unsigned k_Ids_Enc[] = { + IDT_BENCH_COMPRESS_USAGE2, + IDT_BENCH_COMPRESS_SPEED2, + IDT_BENCH_COMPRESS_RPU2, + IDT_BENCH_COMPRESS_RATING2, + IDT_BENCH_COMPRESS_SIZE2 }; + +static const unsigned k_Ids_Dec_1[] = { + IDT_BENCH_DECOMPR_USAGE1, + IDT_BENCH_DECOMPR_SPEED1, + IDT_BENCH_DECOMPR_RPU1, + IDT_BENCH_DECOMPR_RATING1, + IDT_BENCH_DECOMPR_SIZE1 }; + +static const unsigned k_Ids_Dec[] = { + IDT_BENCH_DECOMPR_USAGE2, + IDT_BENCH_DECOMPR_SPEED2, + IDT_BENCH_DECOMPR_RPU2, + IDT_BENCH_DECOMPR_RATING2, + IDT_BENCH_DECOMPR_SIZE2 }; + +static const unsigned k_Ids_Tot[] = { + IDT_BENCH_TOTAL_USAGE_VAL, + 0, + IDT_BENCH_TOTAL_RPU_VAL, + IDT_BENCH_TOTAL_RATING_VAL, + 0 }; + + +void CBenchmarkDialog::MyKillTimer() +{ + if (_timer != 0) + { + KillTimer(kTimerID); + _timer = 0; + } +} + + +bool CBenchmarkDialog::OnDestroy() +{ + /* actually timer was removed before. + also the timer must be removed by Windows, when window will be removed. */ + MyKillTimer(); // it's optional code + return false; // we return (false) to perform default dialog operation +} + +void SetErrorMessage_MemUsage(UString &s, UInt64 reqSize, UInt64 ramSize, UInt64 ramLimit, const UString &usageString); + +void CBenchmarkDialog::StartBenchmark() +{ + NeedRestart = false; + WasStopped_in_GUI = false; + + SetItemText_Empty(IDT_BENCH_ERROR_MESSAGE); + + MyKillTimer(); // optional code. timer was killed before + + const size_t dict = OnChangeDictionary(); + const UInt32 numThreads = GetNumberOfThreads(); + const UInt32 numPasses = (UInt32)m_NumPasses.GetItemData_of_CurSel(); + + for (unsigned i = 0; i < Z7_ARRAY_SIZE(g_IDs); i++) + SetItemText(g_IDs[i], kProcessingString); + + SetItemText_Empty(IDT_BENCH_LOG); + SetItemText_Empty(IDT_BENCH_ELAPSED_VAL); + SetItemText_Empty(IDT_BENCH_ERROR_MESSAGE); + + const UInt64 memUsage = GetBenchMemoryUsage(numThreads, Sync.Level, dict, + false); // totalBench + if (!IsMemoryUsageOK(memUsage)) + { + UString s2; + LangString_OnlyFromLangFile(IDS_MEM_REQUIRED_MEM_SIZE, s2); + if (s2.IsEmpty()) + { + s2 = LangString(IDT_BENCH_MEMORY); + if (s2.IsEmpty()) + GetItemText(IDT_BENCH_MEMORY, s2); + s2.RemoveChar(L':'); + } + UString s; + SetErrorMessage_MemUsage(s, memUsage, RamSize, RamSize_Limit, s2); + MessageBoxError_Status(s); + return; + } + + EnableItem(IDB_STOP, true); + + _startTime = GetTickCount(); + _finishTime = _startTime; + _finishTime_WasSet = false; + + { + NWindows::NSynchronization::CCriticalSectionLock lock(Sync.CS); + InitSyncNew(); + Sync.DictSize = dict; + Sync.NumThreads = numThreads; + Sync.NumPasses_Limit = numPasses; + } + + PrintTime(); + + _timer = SetTimer(kTimerID, kTimerElapse); + if (_thread.Create(CThreadBenchmark::MyThreadFunction, &_threadBenchmark) != 0) + { + MyKillTimer(); + MessageBoxError_Status(L"Can't create thread"); + } + return; +} + + +void CBenchmarkDialog::RestartBenchmark() +{ + if (ExitWasAsked_in_GUI) + return; + + if (_thread.IsCreated()) + { + NeedRestart = true; + SendExit_Status(L"Stop for restart ..."); + } + else + StartBenchmark(); +} + + +void CBenchmarkDialog::Disable_Stop_Button() +{ + // if we disable focused button, then focus will be lost + if (GetFocus() == GetItem(IDB_STOP)) + { + // SendMsg_NextDlgCtl_Prev(); + SendMsg_NextDlgCtl_CtlId(IDB_RESTART); + } + EnableItem(IDB_STOP, false); +} + + +void CBenchmarkDialog::OnStopButton() +{ + if (ExitWasAsked_in_GUI) + return; + + Disable_Stop_Button(); + + WasStopped_in_GUI = true; + if (_thread.IsCreated()) + { + SendExit_Status(L"Stop ..."); + } +} + + + +void CBenchmarkDialog::OnCancel() +{ + ExitWasAsked_in_GUI = true; + + /* + SendMsg_NextDlgCtl_Prev(); + EnableItem(IDCANCEL, false); + */ + + if (_thread.IsCreated()) + SendExit_Status(L"Cancel ..."); + else + CModalDialog::OnCancel(); +} + + +void CBenchmarkDialog::OnHelp() +{ + ShowHelpWindow(kHelpTopic); +} + + + +// void GetTimeString(UInt64 timeValue, wchar_t *s); + +void CBenchmarkDialog::PrintTime() +{ + const UInt32 curTime = + _finishTime_WasSet ? + _finishTime : + ::GetTickCount(); + + const UInt32 elapsedTime = (curTime - _startTime); + + WCHAR s[64]; + + WCHAR *p = ConvertUInt32ToString(elapsedTime / 1000, s); + + if (_finishTime_WasSet) + { + *p++ = '.'; + UINT_TO_STR_3(p, elapsedTime % 1000) + } + + // p = NumberToDot3((UInt64)elapsedTime, s); + + MyStringCopy(p, L" s"); + + // if (WasStopped_in_GUI) wcscat(s, L" X"); // for debug + + if (s == ElapsedSec_Prev) + return; + + ElapsedSec_Prev = s; + + // static cnt = 0; cnt++; wcscat(s, L" "); + // UString s2; s2.Add_UInt32(cnt); wcscat(s, s2.Ptr()); + + SetItemText(IDT_BENCH_ELAPSED_VAL, s); +} + + +static UInt64 GetMips(UInt64 ips) +{ + return (ips + 500000) / 1000000; +} + + +static UInt64 GetUsagePercents(UInt64 usage) +{ + return Benchmark_GetUsage_Percents(usage); +} + + +static UInt32 GetRating(const CTotalBenchRes &info) +{ + UInt64 numIter = info.NumIterations2; + if (numIter == 0) + numIter = 1000000; + const UInt64 rating64 = GetMips(info.Rating / numIter); + // return rating64; + UInt32 rating32 = (UInt32)rating64; + if (rating32 != rating64) + rating32 = (UInt32)(Int32)-1; + return rating32; +} + + +static void AddUsageString(UString &s, const CTotalBenchRes &info) +{ + UInt64 numIter = info.NumIterations2; + if (numIter == 0) + numIter = 1000000; + UInt64 usage = GetUsagePercents(info.Usage / numIter); + + wchar_t w[32]; + wchar_t *p = ConvertUInt64ToString(usage, w); + p[0] = '%'; + p[1] = 0; + unsigned len = (unsigned)(size_t)(p - w); + while (len < 5) + { + s.Add_Space(); + len++; + } + s += w; +} + + +static void Add_Dot3String(UString &s, UInt64 val) +{ + WCHAR temp[32]; + NumberToDot3(val, temp); + s += temp; +} + + +static void AddRatingString(UString &s, const CTotalBenchRes &info) +{ + // AddUsageString(s, info); + // s.Add_Space(); + // s.Add_UInt32(GetRating(info)); + Add_Dot3String(s, GetRating(info)); +} + + +static void AddRatingsLine(UString &s, const CTotalBenchRes &enc, const CTotalBenchRes &dec + #ifdef PRINT_ITER_TIME + , DWORD ticks + #endif + ) +{ + // AddUsageString(s, enc); s.Add_Space(); + + AddRatingString(s, enc); + s += " "; + AddRatingString(s, dec); + + CTotalBenchRes tot_BenchRes; + tot_BenchRes.SetSum(enc, dec); + + s += " "; + AddRatingString(s, tot_BenchRes); + + s.Add_Space(); AddUsageString(s, tot_BenchRes); + + + #ifdef PRINT_ITER_TIME + s.Add_Space(); + { + Add_Dot3String(s, ticks; + s += " s"; + // s.Add_UInt32(ticks); s += " ms"; + } + #endif +} + + +void CBenchmarkDialog::PrintRating(UInt64 rating, UINT controlID) +{ + // SetItemText_Number(controlID, GetMips(rating), kMIPS); + WCHAR s[64]; + MyStringCopy(NumberToDot3(GetMips(rating), s), L" GIPS"); + SetItemText(controlID, s); +} + +void CBenchmarkDialog::PrintUsage(UInt64 usage, UINT controlID) +{ + SetItemText_Number(controlID, GetUsagePercents(usage), TEXT("%")); +} + + +// void SetItemText_Number + +void CBenchmarkDialog::PrintBenchRes( + const CTotalBenchRes2 &info, + const UINT ids[]) +{ + if (info.NumIterations2 == 0) + return; + if (ids[1] != 0) + SetItemText_Number(ids[1], (info.Speed >> 10) / info.NumIterations2, kKBs); + PrintRating(info.Rating / info.NumIterations2, ids[3]); + PrintRating(info.RPU / info.NumIterations2, ids[2]); + PrintUsage(info.Usage / info.NumIterations2, ids[0]); + if (ids[4] != 0) + { + UInt64 val = info.UnpackSize; + LPCTSTR kPostfix; + if (val >= ((UInt64)1 << 40)) + { + kPostfix = kGB; + val >>= 30; + } + else + { + kPostfix = kMB; + val >>= 20; + } + SetItemText_Number(ids[4], val, kPostfix); + } +} + + +// static UInt32 k_Message_Finished_cnt = 0; +// static UInt32 k_OnTimer_cnt = 0; + +bool CBenchmarkDialog::OnMessage(UINT message, WPARAM wParam, LPARAM lParam) +{ + if (message != k_Message_Finished) + return CModalDialog::OnMessage(message, wParam, lParam); + + { + if (wParam == k_Msg_WPARM_Thread_Finished) + { + _finishTime = GetTickCount(); + _finishTime_WasSet = true; + MyKillTimer(); + + if (_thread.Wait_Close() != 0) + { + MessageBoxError_Status(L"Thread Wait Error"); + } + + if (!WasStopped_in_GUI) + { + WasStopped_in_GUI = true; + Disable_Stop_Button(); + } + + HRESULT res = Sync.BenchFinish_Thread_HRESULT; + if (res != S_OK) + // if (!ExitWasAsked_in_GUI || res != E_ABORT) + MessageBoxError_Status(HResultToMessage(res)); + + if (ExitWasAsked_in_GUI) + { + // SetItemText(IDT_BENCH_ERROR_MESSAGE, "before CModalDialog::OnCancel()"); + // Sleep (2000); + // MessageBoxError(L"test"); + CModalDialog::OnCancel(); + return true; + } + + SetItemText_Empty(IDT_BENCH_ERROR_MESSAGE); + + res = Sync.BenchFinish_Task_HRESULT; + if (res != S_OK) + { + if (!WasStopped_in_GUI || res != E_ABORT) + { + UString m; + if (res == S_FALSE) + m = "Decoding error"; + else if (res == CLASS_E_CLASSNOTAVAILABLE) + m = "Can't find 7z.dll"; + else + m = HResultToMessage(res); + MessageBoxError_Status(m); + } + } + + if (NeedRestart) + { + StartBenchmark(); + return true; + } + } + // k_Message_Finished_cnt++; + UpdateGui(); + return true; + } +} + + +bool CBenchmarkDialog::OnTimer(WPARAM timerID, LPARAM /* callback */) +{ + // k_OnTimer_cnt++; + if (timerID == kTimerID) + UpdateGui(); + return true; +} + + +void CBenchmarkDialog::UpdateGui() +{ + PrintTime(); + + if (TotalMode) + { + bool wasChanged = false; + { + NWindows::NSynchronization::CCriticalSectionLock lock(Sync.CS); + + if (Sync.TextWasChanged) + { + wasChanged = true; + Bench2Text += Sync.Text; + Sync.Text.Empty(); + Sync.TextWasChanged = false; + } + } + if (wasChanged) + _consoleEdit.SetText(Bench2Text); + return; + } + + CSyncData sd; + CRecordVector RatingVector; + + { + NWindows::NSynchronization::CCriticalSectionLock lock(Sync.CS); + sd = Sync.sd; + + if (sd.NeedPrint_RatingVector) + RatingVector = Sync.RatingVector; + + if (sd.NeedPrint_Freq) + { + Sync.FreqString_GUI = Sync.FreqString_Sync; + sd.NeedPrint_RatingVector = true; + } + + Sync.sd.NeedPrint_RatingVector = false; + Sync.sd.NeedPrint_Enc_1 = false; + Sync.sd.NeedPrint_Enc = false; + Sync.sd.NeedPrint_Dec_1 = false; + Sync.sd.NeedPrint_Dec = false; + Sync.sd.NeedPrint_Tot = false; + Sync.sd.NeedPrint_Freq = false; + } + + if (sd.NumPasses_Finished != NumPasses_Finished_Prev) + { + SetItemText_Number(IDT_BENCH_PASSES_VAL, sd.NumPasses_Finished, TEXT(" /")); + NumPasses_Finished_Prev = sd.NumPasses_Finished; + } + + if (sd.NeedPrint_Enc_1) PrintBenchRes(sd.Enc_BenchRes_1, k_Ids_Enc_1); + if (sd.NeedPrint_Enc) PrintBenchRes(sd.Enc_BenchRes, k_Ids_Enc); + if (sd.NeedPrint_Dec_1) PrintBenchRes(sd.Dec_BenchRes_1, k_Ids_Dec_1); + if (sd.NeedPrint_Dec) PrintBenchRes(sd.Dec_BenchRes, k_Ids_Dec); + + if (sd.BenchWasFinished && sd.NeedPrint_Tot) + { + CTotalBenchRes2 tot_BenchRes = sd.Enc_BenchRes; + tot_BenchRes.Update_With_Res2(sd.Dec_BenchRes); + PrintBenchRes(tot_BenchRes, k_Ids_Tot); + } + + + if (sd.NeedPrint_RatingVector) + // for (unsigned k = 0; k < 1; k++) + { + UString s; + s += Sync.FreqString_GUI; + if (!RatingVector.IsEmpty()) + { + if (!s.IsEmpty()) + s.Add_LF(); + s += "Compr Decompr Total CPU" + #ifdef PRINT_ITER_TIME + " Time" + #endif + ; + s.Add_LF(); + } + // s += "GIPS GIPS GIPS % s"; s.Add_LF(); + for (unsigned i = 0; i < RatingVector.Size(); i++) + { + if (i != 0) + s.Add_LF(); + if ((int)i == sd.RatingVector_DeletedIndex) + { + s += "..."; + s.Add_LF(); + } + const CBenchPassResult &pair = RatingVector[i]; + /* + s += "g:"; s.Add_UInt32((UInt32)pair.EncInfo.GlobalTime); + s += " u:"; s.Add_UInt32((UInt32)pair.EncInfo.UserTime); + s.Add_Space(); + */ + AddRatingsLine(s, pair.Enc, pair.Dec + #ifdef PRINT_ITER_TIME + , pair.Ticks + #endif + ); + /* + { + UInt64 v = i + 1; + if (sd.RatingVector_DeletedIndex >= 0 && i >= (unsigned)sd.RatingVector_DeletedIndex) + v += sd.RatingVector_NumDeleted; + char temp[64]; + ConvertUInt64ToString(v, temp); + s += " : "; + s += temp; + } + */ + } + + if (sd.BenchWasFinished) + { + s.Add_LF(); + s += "-------------"; + s.Add_LF(); + { + // average time is not correct because of freq detection in first iteration + AddRatingsLine(s, sd.Enc_BenchRes, sd.Dec_BenchRes + #ifdef PRINT_ITER_TIME + , (DWORD)(sd.TotalTicks / (sd.NumPasses_Finished ? sd.NumPasses_Finished : 1)) + #endif + ); + } + } + // s.Add_LF(); s += "OnTimer: "; s.Add_UInt32(k_OnTimer_cnt); + // s.Add_LF(); s += "finished Message: "; s.Add_UInt32(k_Message_Finished_cnt); + // static cnt = 0; cnt++; s.Add_LF(); s += "Print: "; s.Add_UInt32(cnt); + // s.Add_LF(); s += "NumEncProgress: "; s.Add_UInt32((UInt32)sd.NumEncProgress); + // s.Add_LF(); s += "NumDecProgress: "; s.Add_UInt32((UInt32)sd.NumDecProgress); + SetItemText(IDT_BENCH_LOG, s); + } +} + + +bool CBenchmarkDialog::OnCommand(unsigned code, unsigned itemID, LPARAM lParam) +{ + if (code == CBN_SELCHANGE && + (itemID == IDC_BENCH_DICTIONARY || + itemID == IDC_BENCH_NUM_PASSES || + itemID == IDC_BENCH_NUM_THREADS)) + { + RestartBenchmark(); + return true; + } + return CModalDialog::OnCommand(code, itemID, lParam); +} + + +bool CBenchmarkDialog::OnButtonClicked(unsigned buttonID, HWND buttonHWND) +{ + switch (buttonID) + { + case IDB_RESTART: + RestartBenchmark(); + return true; + case IDB_STOP: + OnStopButton(); + return true; + } + return CModalDialog::OnButtonClicked(buttonID, buttonHWND); +} + + + + + +// ---------- Benchmark Thread ---------- + +struct CBenchCallback Z7_final: public IBenchCallback +{ + UInt64 dictionarySize; + CBenchProgressSync *Sync; + CBenchmarkDialog *BenchmarkDialog; + + HRESULT SetEncodeResult(const CBenchInfo &info, bool final) Z7_override; + HRESULT SetDecodeResult(const CBenchInfo &info, bool final) Z7_override; +}; + +HRESULT CBenchCallback::SetEncodeResult(const CBenchInfo &info, bool final) +{ + bool needPost = false; + { + NSynchronization::CCriticalSectionLock lock(Sync->CS); + if (Sync->Exit) + return E_ABORT; + CSyncData &sd = Sync->sd; + // sd.NumEncProgress++; + CTotalBenchRes2 &br = sd.Enc_BenchRes_1; + { + UInt64 dictSize = Sync->DictSize; + if (final) + { + // sd.EncInfo = info; + } + else + { + /* if (!final), then CBenchInfo::NumIterations means totalNumber of threads. + so we can reduce the dictionary */ + if (dictSize > info.UnpackSize) + dictSize = info.UnpackSize; + } + br.Rating = info.GetRating_LzmaEnc(dictSize); + } + br.SetFrom_BenchInfo(info); + sd.NeedPrint_Enc_1 = true; + if (final) + { + sd.Enc_BenchRes.Update_With_Res2(br); + sd.NeedPrint_Enc = true; + needPost = true; + } + } + + if (needPost) + BenchmarkDialog->PostMsg(k_Message_Finished, k_Msg_WPARM_Enc1_Finished); + + return S_OK; +} + + +HRESULT CBenchCallback::SetDecodeResult(const CBenchInfo &info, bool final) +{ + NSynchronization::CCriticalSectionLock lock(Sync->CS); + if (Sync->Exit) + return E_ABORT; + CSyncData &sd = Sync->sd; + // sd.NumDecProgress++; + CTotalBenchRes2 &br = sd.Dec_BenchRes_1; + br.Rating = info.GetRating_LzmaDec(); + br.SetFrom_BenchInfo(info); + sd.NeedPrint_Dec_1 = true; + if (final) + sd.Dec_BenchRes.Update_With_Res2(br); + return S_OK; +} + + +struct CBenchCallback2 Z7_final: public IBenchPrintCallback +{ + CBenchProgressSync *Sync; + bool TotalMode; + + void Print(const char *s) Z7_override; + void NewLine() Z7_override; + HRESULT CheckBreak() Z7_override; +}; + +void CBenchCallback2::Print(const char *s) +{ + if (TotalMode) + { + NSynchronization::CCriticalSectionLock lock(Sync->CS); + Sync->Text += s; + Sync->TextWasChanged = true; + } +} + +void CBenchCallback2::NewLine() +{ + Print("\xD\n"); +} + +HRESULT CBenchCallback2::CheckBreak() +{ + if (Sync->Exit) + return E_ABORT; + return S_OK; +} + + + +struct CFreqCallback Z7_final: public IBenchFreqCallback +{ + CBenchmarkDialog *BenchmarkDialog; + + virtual HRESULT AddCpuFreq(unsigned numThreads, UInt64 freq, UInt64 usage) Z7_override; + virtual HRESULT FreqsFinished(unsigned numThreads) Z7_override; +}; + +HRESULT CFreqCallback::AddCpuFreq(unsigned numThreads, UInt64 freq, UInt64 usage) +{ + HRESULT res; + { + CBenchProgressSync &sync = BenchmarkDialog->Sync; + NSynchronization::CCriticalSectionLock lock(sync.CS); + UString &s = sync.FreqString_Sync; + if (sync.NumFreqThreadsPrev != numThreads) + { + sync.NumFreqThreadsPrev = numThreads; + if (!s.IsEmpty()) + s.Add_LF(); + s.Add_UInt32(numThreads); + s += "T Frequency (MHz):"; + s.Add_LF(); + } + s.Add_Space(); + if (numThreads != 1) + { + s.Add_UInt64(GetUsagePercents(usage)); + s.Add_Char('%'); + s.Add_Space(); + } + s.Add_UInt64(GetMips(freq)); + // BenchmarkDialog->Sync.sd.NeedPrint_Freq = true; + res = sync.Exit ? E_ABORT : S_OK; + } + // BenchmarkDialog->PostMsg(k_Message_Finished, k_Msg_WPARM_Enc1_Finished); + return res; +} + +HRESULT CFreqCallback::FreqsFinished(unsigned /* numThreads */) +{ + HRESULT res; + { + CBenchProgressSync &sync = BenchmarkDialog->Sync; + NSynchronization::CCriticalSectionLock lock(sync.CS); + sync.sd.NeedPrint_Freq = true; + BenchmarkDialog->PostMsg(k_Message_Finished, k_Msg_WPARM_Enc1_Finished); + res = sync.Exit ? E_ABORT : S_OK; + } + BenchmarkDialog->PostMsg(k_Message_Finished, k_Msg_WPARM_Enc1_Finished); + return res; +} + + + +// define USE_DUMMY only for debug +// #define USE_DUMMY +#ifdef USE_DUMMY +static unsigned dummy = 1; +static unsigned Dummy(unsigned limit) +{ + unsigned sum = 0; + for (unsigned k = 0; k < limit; k++) + { + sum += dummy; + if (sum == 0) + break; + } + return sum; +} +#endif + + +HRESULT CThreadBenchmark::Process() +{ + /* the first benchmark pass can be slow, + if we run benchmark while the window is being created, + and (no freq detecion loop) && (dictionary is small) (-mtic is small) */ + + // Sleep(300); // for debug + #ifdef USE_DUMMY + Dummy(1000 * 1000 * 1000); // for debug + #endif + + CBenchProgressSync &sync = BenchmarkDialog->Sync; + HRESULT finishHRESULT = S_OK; + + try + { + for (UInt32 passIndex = 0;; passIndex++) + { + // throw 1; // to debug + // throw CSystemException(E_INVALIDARG); // to debug + + UInt64 dictionarySize; + UInt32 numThreads; + { + NSynchronization::CCriticalSectionLock lock(sync.CS); + if (sync.Exit) + break; + dictionarySize = sync.DictSize; + numThreads = sync.NumThreads; + } + + #ifdef PRINT_ITER_TIME + const DWORD startTick = GetTickCount(); + #endif + + CBenchCallback callback; + + callback.dictionarySize = dictionarySize; + callback.Sync = &sync; + callback.BenchmarkDialog = BenchmarkDialog; + + CBenchCallback2 callback2; + callback2.TotalMode = BenchmarkDialog->TotalMode; + callback2.Sync = &sync; + + CFreqCallback freqCallback; + freqCallback.BenchmarkDialog = BenchmarkDialog; + + HRESULT result; + + try + { + CObjectVector props; + + props = BenchmarkDialog->Props; + + if (BenchmarkDialog->TotalMode) + { + props = BenchmarkDialog->Props; + } + else + { + { + CProperty prop; + prop.Name = "mt"; + prop.Value.Add_UInt32(numThreads); + props.Add(prop); + } + { + CProperty prop; + prop.Name = 'd'; + prop.Name.Add_UInt32((UInt32)(dictionarySize >> 10)); + prop.Name.Add_Char('k'); + props.Add(prop); + } + } + + result = Bench(EXTERNAL_CODECS_LOC_VARS + BenchmarkDialog->TotalMode ? &callback2 : NULL, + BenchmarkDialog->TotalMode ? NULL : &callback, + props, 1, false, + (!BenchmarkDialog->TotalMode) && passIndex == 0 ? &freqCallback: NULL); + + // result = S_FALSE; // for debug; + // throw 1; + } + catch(...) + { + result = E_FAIL; + } + + #ifdef PRINT_ITER_TIME + const DWORD numTicks = GetTickCount() - startTick; + #endif + + bool finished = true; + + NSynchronization::CCriticalSectionLock lock(sync.CS); + + if (result != S_OK) + { + sync.BenchFinish_Task_HRESULT = result; + break; + } + + { + CSyncData &sd = sync.sd; + + sd.NumPasses_Finished++; + #ifdef PRINT_ITER_TIME + sd.TotalTicks += numTicks; + #endif + + if (BenchmarkDialog->TotalMode) + break; + + { + CTotalBenchRes tot_BenchRes = sd.Enc_BenchRes_1; + tot_BenchRes.Update_With_Res(sd.Dec_BenchRes_1); + + sd.NeedPrint_RatingVector = true; + { + CBenchPassResult pair; + // pair.EncInfo = sd.EncInfo; // for debug + pair.Enc = sd.Enc_BenchRes_1; + pair.Dec = sd.Dec_BenchRes_1; + #ifdef PRINT_ITER_TIME + pair.Ticks = numTicks; + #endif + sync.RatingVector.Add(pair); + // pair.Dec_Defined = true; + } + } + + sd.NeedPrint_Dec = true; + sd.NeedPrint_Tot = true; + + if (sync.RatingVector.Size() > kRatingVector_NumBundlesMax) + { + // sd.RatingVector_NumDeleted++; + sd.RatingVector_DeletedIndex = (int)(kRatingVector_NumBundlesMax / 4); + sync.RatingVector.Delete((unsigned)(sd.RatingVector_DeletedIndex)); + } + + if (sync.sd.NumPasses_Finished < sync.NumPasses_Limit) + finished = false; + else + { + sync.sd.BenchWasFinished = true; + // BenchmarkDialog->_finishTime = GetTickCount(); + // return 0; + } + } + + if (BenchmarkDialog->TotalMode) + break; + + /* + if (newTick - prevTick < 1000) + numSameTick++; + if (numSameTick > 5 || finished) + { + prevTick = newTick; + numSameTick = 0; + */ + // for (unsigned i = 0; i < 1; i++) + { + // we suppose that PostMsg messages will be processed in order. + if (!BenchmarkDialog->PostMsg_Finish(k_Msg_WPARM_Iter_Finished)) + { + finished = true; + finishHRESULT = E_FAIL; + // throw 1234567; + } + } + if (finished) + break; + } + // return S_OK; + } + catch(CSystemException &e) + { + finishHRESULT = e.ErrorCode; + // BenchmarkDialog->MessageBoxError(HResultToMessage(e.ErrorCode)); + // return E_FAIL; + } + catch(...) + { + finishHRESULT = E_FAIL; + // BenchmarkDialog->MessageBoxError(HResultToMessage(E_FAIL)); + // return E_FAIL; + } + + if (finishHRESULT != S_OK) + { + NSynchronization::CCriticalSectionLock lock(sync.CS); + sync.BenchFinish_Thread_HRESULT = finishHRESULT; + } + if (!BenchmarkDialog->PostMsg_Finish(k_Msg_WPARM_Thread_Finished)) + { + // sync.BenchFinish_Thread_HRESULT = E_FAIL; + } + return 0; +} + + + +static void ParseNumberString(const UString &s, NCOM::CPropVariant &prop) +{ + const wchar_t *end; + UInt64 result = ConvertStringToUInt64(s, &end); + if (*end != 0 || s.IsEmpty()) + prop = s; + else if (result <= (UInt32)0xFFFFFFFF) + prop = (UInt32)result; + else + prop = result; +} + + +HRESULT Benchmark( + DECL_EXTERNAL_CODECS_LOC_VARS + const CObjectVector &props, UInt32 numIterations, HWND hwndParent) +{ + CBenchmarkDialog bd; + + bd.TotalMode = false; + bd.Props = props; + if (numIterations == 0) + numIterations = 1; + bd.Sync.NumPasses_Limit = numIterations; + bd.Sync.DictSize = (UInt64)(Int64)-1; + bd.Sync.NumThreads = (UInt32)(Int32)-1; + bd.Sync.Level = -1; + + COneMethodInfo method; + + UInt32 numCPUs = 1; + #ifndef Z7_ST + numCPUs = NSystem::GetNumberOfProcessors(); + #endif + UInt32 numThreads = numCPUs; + + FOR_VECTOR (i, props) + { + const CProperty &prop = props[i]; + UString name = prop.Name; + name.MakeLower_Ascii(); + if (name.IsEqualTo_Ascii_NoCase("m") && prop.Value.IsEqualTo("*")) + { + bd.TotalMode = true; + continue; + } + + NCOM::CPropVariant propVariant; + if (!prop.Value.IsEmpty()) + ParseNumberString(prop.Value, propVariant); + if (name.IsPrefixedBy("mt")) + { + #ifndef Z7_ST + RINOK(ParseMtProp(name.Ptr(2), propVariant, numCPUs, numThreads)) + if (numThreads != numCPUs) + bd.Sync.NumThreads = numThreads; + #endif + continue; + } + /* + if (name.IsEqualTo("time")) + { + // UInt32 testTime = 4; + // RINOK(ParsePropToUInt32(L"", propVariant, testTime)); + continue; + } + RINOK(method.ParseMethodFromPROPVARIANT(name, propVariant)); + */ + // here we need to parse DictSize property, and ignore unknown properties + method.ParseMethodFromPROPVARIANT(name, propVariant); + } + + if (bd.TotalMode) + { + // bd.Bench2Text.Empty(); + bd.Bench2Text = "7-Zip " MY_VERSION_CPU; + // bd.Bench2Text.Add_Char((char)0xD); + bd.Bench2Text.Add_LF(); + } + + { + UInt64 dict; + if (method.Get_DicSize(dict)) + bd.Sync.DictSize = dict; + } + bd.Sync.Level = (int)method.GetLevel(); + + // Dummy(1000 * 1000 * 1); + + { + CThreadBenchmark &benchmarker = bd._threadBenchmark; + #ifdef Z7_EXTERNAL_CODECS + benchmarker._externalCodecs = _externalCodecs; + #endif + benchmarker.BenchmarkDialog = &bd; + } + + bd.Create(hwndParent); + + return S_OK; +} + + +CBenchmarkDialog::~CBenchmarkDialog() +{ + if (_thread.IsCreated()) + { + /* the following code will be not executed in normal code flow. + it can be called, if there is some internal failure in dialog code. */ + Attach(NULL); + MessageBoxError(L"The flaw in benchmark thread code"); + Sync.SendExit(); + _thread.Wait_Close(); + } +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/GUI/BenchmarkDialog.h b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/BenchmarkDialog.h new file mode 100644 index 00000000..63c6dede --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/BenchmarkDialog.h @@ -0,0 +1,15 @@ +// BenchmarkDialog.h + +#ifndef ZIP7_INC_BENCHMARK_DIALOG_H +#define ZIP7_INC_BENCHMARK_DIALOG_H + +#include "../../Common/CreateCoder.h" +#include "../../UI/Common/Property.h" + +const UInt32 k_NumBenchIterations_Default = 10; + +HRESULT Benchmark( + DECL_EXTERNAL_CODECS_LOC_VARS + const CObjectVector &props, UInt32 numIterations, HWND hwndParent = NULL); + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/GUI/BenchmarkDialog.rc b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/BenchmarkDialog.rc new file mode 100644 index 00000000..5df7ff26 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/BenchmarkDialog.rc @@ -0,0 +1,288 @@ +#include "BenchmarkDialogRes.h" +#include "../../GuiCommon.rc" + +#define xc 332 +#define yc 248 + +#undef g0xs +#undef g1x +#undef g1xs +#undef g2xs +#undef g3x +#undef g3xs +#undef g4x + +#define gs 160 +#define gSpace 24 + +#define g0xs 90 +#define g1xs 48 +#define g1x (m + g0xs) +#define gc2x (g1x + g1xs + m) +#define gc2xs 80 + +#define g4x (m + m) + +#define sRating 58 +#define sSpeed 60 +#define sUsage 46 +#define sRpu 58 +#define sSize 52 +// #define sFreq 34 + +#define xRating (xs - m - m - sRating) +#define xRpu (xRating - sRpu) +#define xUsage (xRpu - sUsage) +#define xSpeed (xUsage - sSpeed) +#define xSize (xSpeed - sSize) + +// #define xFreq (xUsage - sFreq) + +#define sLabel (xSize - g4x) +#define sTotalRating (sUsage + sRpu + sRating + m + m) +#define xTotalRating (xs - m - sTotalRating) + +#define sPasses 60 + +#define g2xs 60 +#define g3xs 64 +#define g3x (m + g2xs) + +#undef GROUP_Y_SIZE +#undef GROUP_Y2_SIZE +#ifdef UNDER_CE +#define GROUP_Y_SIZE 8 +#define GROUP_Y2_SIZE 8 +#else +#define GROUP_Y_SIZE 40 +#define GROUP_Y2_SIZE 32 +#endif + +#define g7xs bx1 - m - g0xs - g1xs - m + +#define sLog 140 + 0 + +// MY_MODAL_DIALOG_STYLE +IDD_BENCH DIALOG 0, 0, xs + sLog, ys MY_MODAL_RESIZE_DIALOG_STYLE | WS_MINIMIZEBOX +CAPTION "Benchmark" +MY_FONT +BEGIN + PUSHBUTTON "&Restart", IDB_RESTART, bx1, m, bxs, bys + PUSHBUTTON "&Stop", IDB_STOP, bx1, m + bys + 6, bxs, bys + + PUSHBUTTON "&Help", IDHELP, bx2, by, bxs, bys + PUSHBUTTON "Cancel", IDCANCEL, bx1, by, bxs, bys + + LTEXT "&Dictionary size:", IDT_BENCH_DICTIONARY, m, m + 1, g0xs, 8 + COMBOBOX IDC_BENCH_DICTIONARY, g1x, m, g1xs, 140, MY_COMBO + + LTEXT "Memory usage:", IDT_BENCH_MEMORY, gc2x, m - 2, g7xs, 8 + LTEXT "", IDT_BENCH_MEMORY_VAL, gc2x, m + 8, g7xs, MY_TEXT_NOPREFIX + + LTEXT "&Number of CPU threads:", IDT_BENCH_NUM_THREADS, m, 30, g0xs, 8 + COMBOBOX IDC_BENCH_NUM_THREADS, g1x, 29, g1xs, 140, MY_COMBO + LTEXT "", IDT_BENCH_HARDWARE_THREADS, gc2x, 30, g7xs, 24, SS_NOPREFIX + + RTEXT "Size", IDT_BENCH_SIZE, xSize, 54, sSize, MY_TEXT_NOPREFIX + RTEXT "CPU Usage", IDT_BENCH_USAGE_LABEL, xUsage, 54, sUsage, MY_TEXT_NOPREFIX + RTEXT "Speed", IDT_BENCH_SPEED, xSpeed, 54, sSpeed, MY_TEXT_NOPREFIX + RTEXT "Rating / Usage", IDT_BENCH_RPU_LABEL, xRpu, 54, sRpu, MY_TEXT_NOPREFIX + RTEXT "Rating", IDT_BENCH_RATING_LABEL, xRating, 54, sRating, MY_TEXT_NOPREFIX + + GROUPBOX "Compressing", IDG_BENCH_COMPRESSING, m, 64, xc, GROUP_Y_SIZE + + LTEXT "Current", IDT_BENCH_CURRENT, g4x, 76, sLabel, MY_TEXT_NOPREFIX + RTEXT "", IDT_BENCH_COMPRESS_SIZE1, xSize, 76, sSize, MY_TEXT_NOPREFIX + RTEXT "", IDT_BENCH_COMPRESS_USAGE1, xUsage, 76, sUsage, MY_TEXT_NOPREFIX + RTEXT "", IDT_BENCH_COMPRESS_SPEED1, xSpeed, 76, sSpeed, MY_TEXT_NOPREFIX + RTEXT "", IDT_BENCH_COMPRESS_RPU1, xRpu, 76, sRpu, MY_TEXT_NOPREFIX + RTEXT "", IDT_BENCH_COMPRESS_RATING1, xRating, 76, sRating, MY_TEXT_NOPREFIX + + LTEXT "Resulting", IDT_BENCH_RESULTING, g4x, 89, sLabel, MY_TEXT_NOPREFIX + RTEXT "", IDT_BENCH_COMPRESS_SIZE2, xSize, 89, sSize, MY_TEXT_NOPREFIX + RTEXT "", IDT_BENCH_COMPRESS_USAGE2, xUsage, 89, sUsage, MY_TEXT_NOPREFIX + RTEXT "", IDT_BENCH_COMPRESS_SPEED2, xSpeed, 89, sSpeed, MY_TEXT_NOPREFIX + RTEXT "", IDT_BENCH_COMPRESS_RPU2, xRpu, 89, sRpu, MY_TEXT_NOPREFIX + RTEXT "", IDT_BENCH_COMPRESS_RATING2, xRating, 89, sRating, MY_TEXT_NOPREFIX + + GROUPBOX "Decompressing", IDG_BENCH_DECOMPRESSING, m, 111, xc, GROUP_Y_SIZE + + LTEXT "Current", IDT_BENCH_CURRENT2, g4x, 123, sLabel, MY_TEXT_NOPREFIX + RTEXT "", IDT_BENCH_DECOMPR_SIZE1, xSize, 123, sSize, MY_TEXT_NOPREFIX + RTEXT "", IDT_BENCH_DECOMPR_USAGE1, xUsage, 123, sUsage, MY_TEXT_NOPREFIX + RTEXT "", IDT_BENCH_DECOMPR_SPEED1, xSpeed, 123, sSpeed, MY_TEXT_NOPREFIX + RTEXT "", IDT_BENCH_DECOMPR_RPU1, xRpu, 123, sRpu, MY_TEXT_NOPREFIX + RTEXT "", IDT_BENCH_DECOMPR_RATING1, xRating, 123, sRating, MY_TEXT_NOPREFIX + + LTEXT "Resulting", IDT_BENCH_RESULTING2, g4x, 136, sLabel, MY_TEXT_NOPREFIX + RTEXT "", IDT_BENCH_DECOMPR_SIZE2, xSize, 136, sSize, MY_TEXT_NOPREFIX + RTEXT "", IDT_BENCH_DECOMPR_USAGE2, xUsage, 136, sUsage, MY_TEXT_NOPREFIX + RTEXT "", IDT_BENCH_DECOMPR_SPEED2, xSpeed, 136, sSpeed, MY_TEXT_NOPREFIX + RTEXT "", IDT_BENCH_DECOMPR_RPU2, xRpu, 136, sRpu, MY_TEXT_NOPREFIX + RTEXT "", IDT_BENCH_DECOMPR_RATING2, xRating, 136, sRating, MY_TEXT_NOPREFIX + + RTEXT "", IDT_BENCH_ERROR_MESSAGE, m, 155, xc, MY_TEXT_NOPREFIX + + GROUPBOX "Total Rating", IDG_BENCH_TOTAL_RATING, xTotalRating, 163, sTotalRating, GROUP_Y2_SIZE + + RTEXT "", IDT_BENCH_TOTAL_USAGE_VAL, xUsage, 176, sUsage, MY_TEXT_NOPREFIX + RTEXT "", IDT_BENCH_TOTAL_RPU_VAL, xRpu, 176, sRpu, MY_TEXT_NOPREFIX + RTEXT "", IDT_BENCH_TOTAL_RATING_VAL, xRating, 176, sRating, MY_TEXT_NOPREFIX + + + // RTEXT "", IDT_BENCH_CPU, m + sPasses, 202, xc - sPasses, 16, SS_NOPREFIX + RTEXT "", IDT_BENCH_CPU, m + 0, 202, xc - 0, 16, SS_NOPREFIX + RTEXT "", IDT_BENCH_VER, m + xc - 100, 222, 100, MY_TEXT_NOPREFIX + + LTEXT "", IDT_BENCH_CPU_FEATURE, m, 222, xc - 100, 16, SS_NOPREFIX // - 100 + LTEXT "", IDT_BENCH_SYS1, m, 238, xc - 140, MY_TEXT_NOPREFIX + LTEXT "", IDT_BENCH_SYS2, m, 248, xc - 140, MY_TEXT_NOPREFIX + + LTEXT "", IDT_BENCH_LOG, m + xc + m, m, sLog - m, yc, SS_LEFTNOWORDWRAP | SS_NOPREFIX + + + LTEXT "Elapsed time:", IDT_BENCH_ELAPSED, m, 163, g2xs, 8 +// LTEXT "Size:", IDT_BENCH_SIZE, m, 176, g2xs, 8 + LTEXT "Passes:", IDT_BENCH_PASSES, m, 176, g2xs, 8 + COMBOBOX IDC_BENCH_NUM_PASSES, m, 187, sPasses, 140, MY_COMBO + + RTEXT "", IDT_BENCH_ELAPSED_VAL, g3x, 163, g3xs, MY_TEXT_NOPREFIX + // RTEXT "", IDT_BENCH_SIZE_VAL, g3x, 176, g3xs, MY_TEXT_NOPREFIX + RTEXT "", IDT_BENCH_PASSES_VAL, g3x, 176, g3xs, MY_TEXT_NOPREFIX + +END + +#ifdef UNDER_CE + +#undef m +#define m 4 + +#undef xc +#undef yc + +#define xc 154 +#define yc 160 + +#undef g0xs +#undef g1x +#undef g1xs +#undef g2xs +#undef g3x +#undef g3xs + +#undef bxs +#undef bys + +#define bxs 60 +#define bys 14 + +#undef gs +#undef gSpace + +#define gs 160 +#define gSpace 24 + +#define g0xs (xc - bxs) +#define g1xs 44 + +#undef g4x +#define g4x (m) + +#undef xRpu +#undef xUsage +#undef xRating +#undef xTotalRating + +#undef sRpu +#undef sRating +#undef sUsage +#undef sLabel +#undef sTotalRating + +#define sRating 40 +#define sUsage 24 +#define sRpu 40 + +#define xRating (xs - m - sRating) +#define xRpu (xRating - sRpu) +#define xUsage (xRpu - sUsage) + +#define sLabel (xUsage - g4x) +#define sTotalRating (sRpu + sRating) +#define xTotalRating (xs - m - sTotalRating) + +#define g3xs 32 +#define g3x (xRpu - g3xs) +#define g2xs (g3x - m) + + +IDD_BENCH_2 DIALOG 0, 0, xs, ys MY_MODAL_DIALOG_STYLE | WS_MINIMIZEBOX +CAPTION "Benchmark" +MY_FONT +BEGIN + PUSHBUTTON "&Restart", IDB_RESTART, bx1, m, bxs, bys + PUSHBUTTON "&Stop", IDB_STOP, bx1, m + bys + m, bxs, bys + + PUSHBUTTON "Cancel", IDCANCEL, bx1, by, bxs, bys + + LTEXT "&Dictionary size:", IDT_BENCH_DICTIONARY, m, m, g0xs, 8 + COMBOBOX IDC_BENCH_DICTIONARY, m, m + 11, g1xs, 140, MY_COMBO + + LTEXT "&Number of CPU threads:", IDT_BENCH_NUM_THREADS, m, 31, g0xs, 8 + COMBOBOX IDC_BENCH_NUM_THREADS, m, 42, g1xs, 140, MY_COMBO + + LTEXT "", IDT_BENCH_MEMORY_VAL, m + g1xs + 8, m + 13, xc - bxs - g1xs - 8, 8 + LTEXT "", IDT_BENCH_HARDWARE_THREADS, m + g1xs + 8, 44, xc - bxs - g1xs - 8, 8 + + LTEXT "Current", IDT_BENCH_CURRENT, g4x, 70, sLabel, 8 + RTEXT "", IDT_BENCH_COMPRESS_USAGE1, xUsage, 70, sUsage, 8 + RTEXT "", IDT_BENCH_COMPRESS_RPU1, xRpu, 70, sRpu, 8 + RTEXT "", IDT_BENCH_COMPRESS_RATING1, xRating, 70, sRating, 8 + + LTEXT "Resulting", IDT_BENCH_RESULTING, g4x, 80, sLabel, 8 + RTEXT "", IDT_BENCH_COMPRESS_USAGE2, xUsage, 80, sUsage, 8 + RTEXT "", IDT_BENCH_COMPRESS_RPU2, xRpu, 80, sRpu, 8 + RTEXT "", IDT_BENCH_COMPRESS_RATING2, xRating, 80, sRating, 8 + + LTEXT "Compressing", IDG_BENCH_COMPRESSING, m, 60, xc - bxs, 8 + + LTEXT "Current", IDT_BENCH_CURRENT2, g4x, 104, sLabel, 8 + RTEXT "", IDT_BENCH_DECOMPR_USAGE1, xUsage, 104, sUsage, 8 + RTEXT "", IDT_BENCH_DECOMPR_RPU1, xRpu, 104, sRpu, 8 + RTEXT "", IDT_BENCH_DECOMPR_RATING1, xRating, 104, sRating, 8 + + LTEXT "Resulting", IDT_BENCH_RESULTING2, g4x, 114, sLabel, 8 + RTEXT "", IDT_BENCH_DECOMPR_USAGE2, xUsage, 114, sUsage, 8 + RTEXT "", IDT_BENCH_DECOMPR_RPU2, xRpu, 114, sRpu, 8 + RTEXT "", IDT_BENCH_DECOMPR_RATING2, xRating, 114, sRating, 8 + + LTEXT "Decompressing", IDG_BENCH_DECOMPRESSING, m, 94, xc, 8 + + RTEXT "", IDT_BENCH_TOTAL_RPU_VAL, xRpu, 140, sRpu, 8 + RTEXT "", IDT_BENCH_TOTAL_RATING_VAL, xRating, 140, sRating, 8 + + LTEXT "Elapsed time:", IDT_BENCH_ELAPSED, m, 130, g2xs, 8 + LTEXT "Size:", IDT_BENCH_SIZE, m, 140, g2xs, 8 + LTEXT "Passes:", IDT_BENCH_PASSES, m, 150, g2xs, 8 + + RTEXT "", IDT_BENCH_ELAPSED_VAL, g3x, 130, g3xs, 8 + RTEXT "", IDT_BENCH_SIZE_VAL, g3x, 140, g3xs, 8 + RTEXT "", IDT_BENCH_PASSES_VAL, g3x, 150, g3xs, 8 +END + +#endif + +#include "../../GuiCommon.rc" + +#define xc 360 +#define yc 260 + +IDD_BENCH_TOTAL DIALOG 0, 0, xs, ys MY_MODAL_RESIZE_DIALOG_STYLE MY_FONT +CAPTION "Benchmark" +{ + LTEXT "Elapsed time:", IDT_BENCH_ELAPSED, m, m, 58, 8 + RTEXT "", IDT_BENCH_ELAPSED_VAL, m + 58, m, 38, 8 + EDITTEXT IDE_BENCH2_EDIT, m, m + 14, xc, yc - bys - m - 14, ES_MULTILINE | ES_READONLY | ES_AUTOVSCROLL | WS_VSCROLL | WS_HSCROLL + PUSHBUTTON "&Help", IDHELP, bx2, by, bxs, bys + PUSHBUTTON "Cancel", IDCANCEL, bx1, by, bxs, bys +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/GUI/BenchmarkDialogRes.h b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/BenchmarkDialogRes.h new file mode 100644 index 00000000..b7d54b77 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/BenchmarkDialogRes.h @@ -0,0 +1,79 @@ +#define IDD_BENCH 7600 +#define IDD_BENCH_2 17600 +#define IDD_BENCH_TOTAL 7699 + +#define IDE_BENCH2_EDIT 100 + +#define IDC_BENCH_DICTIONARY 101 +#define IDT_BENCH_MEMORY_VAL 102 +#define IDC_BENCH_NUM_THREADS 103 +#define IDT_BENCH_HARDWARE_THREADS 104 + +#define IDT_BENCH_VER 105 +#define IDT_BENCH_CPU 106 +#define IDT_BENCH_SYS1 107 +#define IDT_BENCH_SYS2 108 +#define IDT_BENCH_CPU_FEATURE 109 + +#define IDT_BENCH_COMPRESS_SPEED1 110 +#define IDT_BENCH_COMPRESS_SPEED2 111 +#define IDT_BENCH_COMPRESS_RATING1 112 +#define IDT_BENCH_COMPRESS_RATING2 113 +#define IDT_BENCH_COMPRESS_USAGE1 114 +#define IDT_BENCH_COMPRESS_USAGE2 115 +#define IDT_BENCH_COMPRESS_RPU1 116 +#define IDT_BENCH_COMPRESS_RPU2 117 + +#define IDT_BENCH_DECOMPR_SPEED1 118 +#define IDT_BENCH_DECOMPR_SPEED2 119 +#define IDT_BENCH_DECOMPR_RATING1 120 +#define IDT_BENCH_DECOMPR_RATING2 121 +#define IDT_BENCH_DECOMPR_USAGE1 122 +#define IDT_BENCH_DECOMPR_USAGE2 123 +#define IDT_BENCH_DECOMPR_RPU1 124 +#define IDT_BENCH_DECOMPR_RPU2 125 + +#define IDT_BENCH_TOTAL_RATING_VAL 130 +#define IDT_BENCH_TOTAL_RPU_VAL 131 +#define IDT_BENCH_TOTAL_USAGE_VAL 133 + +#define IDT_BENCH_ELAPSED_VAL 140 +// #define IDT_BENCH_SIZE_VAL 141 +#define IDT_BENCH_PASSES_VAL 142 +#define IDC_BENCH_NUM_PASSES 143 + +#define IDT_BENCH_LOG 160 +#define IDT_BENCH_ERROR_MESSAGE 161 + +#define IDT_BENCH_COMPRESS_SIZE1 170 +#define IDT_BENCH_COMPRESS_SIZE2 171 +#define IDT_BENCH_DECOMPR_SIZE1 172 +#define IDT_BENCH_DECOMPR_SIZE2 173 + +// #define IDT_BENCH_FREQ_CUR 150 +// #define IDT_BENCH_FREQ_RES 151 + +#define IDB_STOP 442 +#define IDB_RESTART 443 + +#define IDT_BENCH_DICTIONARY 4006 +#define IDT_BENCH_NUM_THREADS 4009 + +#define IDT_BENCH_SIZE 1007 +#define IDT_BENCH_ELAPSED 3900 +#define IDT_BENCH_SPEED 3903 + +#define IDT_BENCH_MEMORY 7601 + +#define IDG_BENCH_COMPRESSING 7602 +#define IDG_BENCH_DECOMPRESSING 7603 +#define IDG_BENCH_TOTAL_RATING 7605 + +#define IDT_BENCH_RATING_LABEL 7604 +#define IDT_BENCH_CURRENT 7606 +#define IDT_BENCH_RESULTING 7607 +#define IDT_BENCH_USAGE_LABEL 7608 +#define IDT_BENCH_RPU_LABEL 7609 +#define IDT_BENCH_PASSES 7610 +#define IDT_BENCH_CURRENT2 (7606+50) +#define IDT_BENCH_RESULTING2 (7607+50) diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/GUI/CompressDialog.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/CompressDialog.cpp new file mode 100644 index 00000000..53e56fe2 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/CompressDialog.cpp @@ -0,0 +1,3821 @@ +// CompressDialog.cpp + +#include "StdAfx.h" + +#include "../../../../C/CpuArch.h" + +#include "../../../Common/IntToString.h" +#include "../../../Common/StringConvert.h" + +#include "../../../Windows/FileDir.h" +#include "../../../Windows/FileName.h" +#include "../../../Windows/System.h" + +#include "../../Common/MethodProps.h" + +#include "../FileManager/BrowseDialog.h" +#include "../FileManager/FormatUtils.h" +#include "../FileManager/HelpUtils.h" +#include "../FileManager/PropertyName.h" +#include "../FileManager/SplitUtils.h" +#include "../FileManager/resourceGui.h" + +#include "../Explorer/MyMessages.h" + +#include "../Common/ZipRegistry.h" + +#include "CompressDialog.h" + +#ifndef _UNICODE +extern bool g_IsNT; +#endif + +#include "../FileManager/LangUtils.h" + +#include "CompressDialogRes.h" +#include "ExtractRes.h" +#include "resource2.h" + +// #define PRINT_PARAMS + +#ifdef Z7_LANG + +// #define IDS_OPTIONS 2100 + +static const UInt32 kLangIDs[] = +{ + IDT_COMPRESS_ARCHIVE, + IDT_COMPRESS_UPDATE_MODE, + IDT_COMPRESS_FORMAT, + IDT_COMPRESS_LEVEL, + IDT_COMPRESS_METHOD, + IDT_COMPRESS_DICTIONARY, + IDT_COMPRESS_ORDER, + IDT_COMPRESS_SOLID, + IDT_COMPRESS_THREADS, + IDT_COMPRESS_PARAMETERS, + + IDB_COMPRESS_OPTIONS, // IDS_OPTIONS + + IDG_COMPRESS_OPTIONS, + IDX_COMPRESS_SFX, + IDX_COMPRESS_SHARED, + IDX_COMPRESS_DEL, + + IDT_COMPRESS_MEMORY, + IDT_COMPRESS_MEMORY_DE, + + IDG_COMPRESS_ENCRYPTION, + IDT_COMPRESS_ENCRYPTION_METHOD, + IDX_COMPRESS_ENCRYPT_FILE_NAMES, + + IDT_PASSWORD_ENTER, + IDT_PASSWORD_REENTER, + IDX_PASSWORD_SHOW, + + IDT_SPLIT_TO_VOLUMES, + IDT_COMPRESS_PATH_MODE, +}; +#endif + +using namespace NWindows; +using namespace NFile; +using namespace NName; +using namespace NDir; + +static const unsigned kHistorySize = 20; + +static const UInt32 kSolidLog_NoSolid = 0; +static const UInt32 kSolidLog_FullSolid = 64; + +static const UInt32 kLzmaMaxDictSize = (UInt32)15 << 28; + +static const UINT k_Message_ArcChanged = WM_APP + 1; + +/* +static const UInt32 kZstd_MAX_DictSize = (UInt32)1 << MY_ZSTD_WINDOWLOG_MAX; +*/ + +/* The top value for windowLog_Chain: + (MY_ZSTD_CHAINLOG_MAX - 1): in BT mode + (MY_ZSTD_CHAINLOG_MAX) : in non-BT mode. But such big value is useless in most cases. + So we always reduce top value to (MY_ZSTD_CHAINLOG_MAX - 1) */ +/* +static const unsigned kMaxDictChain = MY_ZSTD_CHAINLOG_MAX - 1; +static const UInt32 kZstd_MAX_DictSize_Chain = (UInt32)1 << kMaxDictChain; +*/ + +static LPCSTR const kExeExt = ".exe"; + +static const UInt32 g_Levels[] = +{ + IDS_METHOD_STORE, + IDS_METHOD_FASTEST, + 0, + IDS_METHOD_FAST, + 0, + IDS_METHOD_NORMAL, + 0, + IDS_METHOD_MAXIMUM, + 0, + IDS_METHOD_ULTRA +}; + +enum EMethodID +{ + kCopy, + kLZMA, + kLZMA2, + kPPMd, + kBZip2, + kDeflate, + kDeflate64, + kPPMdZip, + // kZSTD, + kSha256, + kSha1, + kCrc32, + kCrc64, + kGnu, + kPosix +}; + +static LPCSTR const kMethodsNames[] = +{ + "Copy" + , "LZMA" + , "LZMA2" + , "PPMd" + , "BZip2" + , "Deflate" + , "Deflate64" + , "PPMd" + // , "ZSTD" + , "SHA256" + , "SHA1" + , "CRC32" + , "CRC64" + , "GNU" + , "POSIX" +}; + +static const EMethodID g_7zMethods[] = +{ + kLZMA2, + kLZMA, + kPPMd, + kBZip2 + , kDeflate + , kDeflate64 + // , kZSTD + , kCopy +}; + +static const EMethodID g_7zSfxMethods[] = +{ + kCopy, + kLZMA, + kLZMA2, + kPPMd +}; + +static const EMethodID g_ZipMethods[] = +{ + kDeflate, + kDeflate64, + kBZip2, + kLZMA, + kPPMdZip + // , kZSTD +}; + +static const EMethodID g_GZipMethods[] = +{ + kDeflate +}; + +static const EMethodID g_BZip2Methods[] = +{ + kBZip2 +}; + +static const EMethodID g_XzMethods[] = +{ + kLZMA2 +}; + +/* +static const EMethodID g_ZstdMethods[] = +{ + kZSTD +}; +*/ + +/* +static const EMethodID g_SwfcMethods[] = +{ + kDeflate + // kLZMA +}; +*/ + +static const EMethodID g_TarMethods[] = +{ + kGnu, + kPosix +}; + +static const EMethodID g_HashMethods[] = +{ + kSha256 + , kSha1 + // , kCrc32 + // , kCrc64 +}; + +static const UInt32 kFF_Filter = 1 << 0; +static const UInt32 kFF_Solid = 1 << 1; +static const UInt32 kFF_MultiThread = 1 << 2; +static const UInt32 kFF_Encrypt = 1 << 3; +static const UInt32 kFF_EncryptFileNames = 1 << 4; +static const UInt32 kFF_MemUse = 1 << 5; +static const UInt32 kFF_SFX = 1 << 6; + +/* +static const UInt32 kFF_Time_Win = 1 << 10; +static const UInt32 kFF_Time_Unix = 1 << 11; +static const UInt32 kFF_Time_DOS = 1 << 12; +static const UInt32 kFF_Time_1ns = 1 << 13; +*/ + +struct CFormatInfo +{ + LPCSTR Name; + UInt32 LevelsMask; + unsigned NumMethods; + const EMethodID *MethodIDs; + + UInt32 Flags; + + bool Filter_() const { return (Flags & kFF_Filter) != 0; } + bool Solid_() const { return (Flags & kFF_Solid) != 0; } + bool MultiThread_() const { return (Flags & kFF_MultiThread) != 0; } + bool Encrypt_() const { return (Flags & kFF_Encrypt) != 0; } + bool EncryptFileNames_() const { return (Flags & kFF_EncryptFileNames) != 0; } + bool MemUse_() const { return (Flags & kFF_MemUse) != 0; } + bool SFX_() const { return (Flags & kFF_SFX) != 0; } +}; + +#define METHODS_PAIR(x) Z7_ARRAY_SIZE(x), x + +static const CFormatInfo g_Formats[] = +{ + { + "", + // (1 << 0) | (1 << 1) | (1 << 3) | (1 << 5) | (1 << 7) | (1 << 9), + ((UInt32)1 << 10) - 1, + // (UInt32)(Int32)-1, + 0, NULL, + kFF_MultiThread | kFF_MemUse + }, + { + "7z", + // (1 << 0) | (1 << 1) | (1 << 3) | (1 << 5) | (1 << 7) | (1 << 9), + (1 << 10) - 1, + METHODS_PAIR(g_7zMethods), + kFF_Filter | kFF_Solid | kFF_MultiThread | kFF_Encrypt | + kFF_EncryptFileNames | kFF_MemUse | kFF_SFX + // | kFF_Time_Win + }, + { + "Zip", + (1 << 0) | (1 << 1) | (1 << 3) | (1 << 5) | (1 << 7) | (1 << 9), + METHODS_PAIR(g_ZipMethods), + kFF_MultiThread | kFF_Encrypt | kFF_MemUse + // | kFF_Time_Win | kFF_Time_Unix | kFF_Time_DOS + }, + { + "GZip", + (1 << 1) | (1 << 5) | (1 << 7) | (1 << 9), + METHODS_PAIR(g_GZipMethods), + kFF_MemUse + // | kFF_Time_Unix + }, + { + "BZip2", + (1 << 1) | (1 << 3) | (1 << 5) | (1 << 7) | (1 << 9), + METHODS_PAIR(g_BZip2Methods), + kFF_MultiThread | kFF_MemUse + }, + { + "xz", + // (1 << 1) | (1 << 3) | (1 << 5) | (1 << 7) | (1 << 9), + (1 << 10) - 1 - (1 << 0), // store (1 << 0) is not supported + METHODS_PAIR(g_XzMethods), + kFF_Solid | kFF_MultiThread | kFF_MemUse + }, + /* + { + "zstd", + // (1 << (MY_ZSTD_LEVEL_MAX + 1)) - 1, + (1 << (9 + 1)) - 1, + METHODS_PAIR(g_ZstdMethods), + // kFF_Solid | + kFF_MultiThread + | kFF_MemUse + }, + */ +/* + { + "Swfc", + (1 << 1) | (1 << 3) | (1 << 5) | (1 << 7) | (1 << 9), + METHODS_PAIR(g_SwfcMethods), + 0 + }, +*/ + { + "Tar", + (1 << 0), + METHODS_PAIR(g_TarMethods), + 0 + // kFF_Time_Unix | kFF_Time_Win // | kFF_Time_1ns + }, + { + "wim", + (1 << 0), + 0, NULL, + 0 + // | kFF_Time_Win + }, + { + "Hash", + (0 << 0), + METHODS_PAIR(g_HashMethods), + 0 + } +}; + +static bool IsMethodSupportedBySfx(int methodID) +{ + for (unsigned i = 0; i < Z7_ARRAY_SIZE(g_7zSfxMethods); i++) + if (methodID == g_7zSfxMethods[i]) + return true; + return false; +} + + +static const + // NCompressDialog::NUpdateMode::EEnum + int + k_UpdateMode_Vals[] = +{ + NCompressDialog::NUpdateMode::kAdd, + NCompressDialog::NUpdateMode::kUpdate, + NCompressDialog::NUpdateMode::kFresh, + NCompressDialog::NUpdateMode::kSync +}; + +static const UInt32 k_UpdateMode_IDs[] = +{ + IDS_COMPRESS_UPDATE_MODE_ADD, + IDS_COMPRESS_UPDATE_MODE_UPDATE, + IDS_COMPRESS_UPDATE_MODE_FRESH, + IDS_COMPRESS_UPDATE_MODE_SYNC +}; + +static const + // NWildcard::ECensorPathMode + int + k_PathMode_Vals[] = +{ + NWildcard::k_RelatPath, + NWildcard::k_FullPath, + NWildcard::k_AbsPath, +}; + +static const UInt32 k_PathMode_IDs[] = +{ + IDS_PATH_MODE_RELAT, + IDS_EXTRACT_PATHS_FULL, + IDS_EXTRACT_PATHS_ABS +}; + +void AddComboItems(NControl::CComboBox &combo, const UInt32 *langIDs, unsigned numItems, const int *values, int curVal); + +void CCompressDialog::SetMethods(const CObjectVector &userCodecs) +{ + ExternalMethods.Clear(); + { + FOR_VECTOR (i, userCodecs) + { + const CCodecInfoUser &c = userCodecs[i]; + if (!c.EncoderIsAssigned + || !c.IsFilter_Assigned + || c.IsFilter + || c.NumStreams != 1) + continue; + unsigned k; + for (k = 0; k < Z7_ARRAY_SIZE(g_7zMethods); k++) + if (c.Name.IsEqualTo_Ascii_NoCase(kMethodsNames[g_7zMethods[k]])) + break; + if (k != Z7_ARRAY_SIZE(g_7zMethods)) + continue; + ExternalMethods.Add(c.Name); + } + } +} + + +bool CCompressDialog::OnInit() +{ + #ifdef Z7_LANG + LangSetWindowText(*this, IDD_COMPRESS); + LangSetDlgItems(*this, kLangIDs, Z7_ARRAY_SIZE(kLangIDs)); + // LangSetDlgItemText(*this, IDB_COMPRESS_OPTIONS, IDS_OPTIONS); // IDG_COMPRESS_OPTIONS + #endif + + { + size_t size = (size_t)sizeof(size_t) << 29; + _ramSize_Defined = NSystem::GetRamSize(size); + // size = (UInt64)3 << 62; // for debug only; + { + // we use reduced limit for 32-bit version: + unsigned bits = sizeof(size_t) * 8; + if (bits == 32) + { + const UInt32 limit2 = (UInt32)7 << 28; + if (size > limit2) + size = limit2; + } + } + _ramSize = size; + const size_t kMinUseSize = 1 << 26; + if (size < kMinUseSize) + size = kMinUseSize; + _ramSize_Reduced = size; + + // 80% - is auto usage limit in handlers + _ramUsage_Auto = Calc_From_Val_Percents(size, 80); + } + + _password1Control.Attach(GetItem(IDE_COMPRESS_PASSWORD1)); + _password2Control.Attach(GetItem(IDE_COMPRESS_PASSWORD2)); + _password1Control.SetText(Info.Password); + _password2Control.SetText(Info.Password); + _encryptionMethod.Attach(GetItem(IDC_COMPRESS_ENCRYPTION_METHOD)); + _default_encryptionMethod_Index = -1; + + m_ArchivePath.Attach(GetItem(IDC_COMPRESS_ARCHIVE)); + m_Format.Attach(GetItem(IDC_COMPRESS_FORMAT)); // that combo has CBS_SORT style in resources + m_Level.Attach(GetItem(IDC_COMPRESS_LEVEL)); + m_Method.Attach(GetItem(IDC_COMPRESS_METHOD)); + m_Dictionary.Attach(GetItem(IDC_COMPRESS_DICTIONARY)); + + /* + { + RECT r; + GetClientRectOfItem(IDC_COMPRESS_DICTIONARY, r); + _dictionaryCombo_left = r.left; + } + */ + _dictionaryCombo_left = 0; // 230; + + // m_Dictionary_Chain.Attach(GetItem(IDC_COMPRESS_DICTIONARY2)); + m_Order.Attach(GetItem(IDC_COMPRESS_ORDER)); + m_Solid.Attach(GetItem(IDC_COMPRESS_SOLID)); + m_NumThreads.Attach(GetItem(IDC_COMPRESS_THREADS)); + m_MemUse.Attach(GetItem(IDC_COMPRESS_MEM_USE)); + + m_UpdateMode.Attach(GetItem(IDC_COMPRESS_UPDATE_MODE)); + m_PathMode.Attach(GetItem(IDC_COMPRESS_PATH_MODE)); + + m_Volume.Attach(GetItem(IDC_COMPRESS_VOLUME)); + m_Params.Attach(GetItem(IDE_COMPRESS_PARAMETERS)); + + AddVolumeItems(m_Volume); + + m_RegistryInfo.Load(); + CheckButton(IDX_PASSWORD_SHOW, m_RegistryInfo.ShowPassword); + CheckButton(IDX_COMPRESS_ENCRYPT_FILE_NAMES, m_RegistryInfo.EncryptHeaders); + + UpdatePasswordControl(); + + { + const bool needSetMain = (Info.FormatIndex < 0); + FOR_VECTOR(i, ArcIndices) + { + const unsigned arcIndex = ArcIndices[i]; + const CArcInfoEx &ai = (*ArcFormats)[arcIndex]; + const int index = (int)m_Format.AddString_SetItemData(ai.Name, (LPARAM)arcIndex); + if (!needSetMain) + { + if (Info.FormatIndex == (int)arcIndex) + m_Format.SetCurSel(index); + continue; + } + if (i == 0 || ai.Name.IsEqualTo_NoCase(m_RegistryInfo.ArcType)) + { + m_Format.SetCurSel(index); + Info.FormatIndex = (int)arcIndex; + } + } + } + + CheckButton(IDX_COMPRESS_SFX, Info.SFXMode); + + { + UString fileName; + SetArcPathFields(Info.ArcPath, fileName, true); + StartDirPrefix = DirPrefix; + SetArchiveName(fileName); + } + + for (unsigned i = 0; i < m_RegistryInfo.ArcPaths.Size() && i < kHistorySize; i++) + m_ArchivePath.AddString(m_RegistryInfo.ArcPaths[i]); + + AddComboItems(m_UpdateMode, k_UpdateMode_IDs, Z7_ARRAY_SIZE(k_UpdateMode_IDs), + k_UpdateMode_Vals, Info.UpdateMode); + + AddComboItems(m_PathMode, k_PathMode_IDs, Z7_ARRAY_SIZE(k_PathMode_IDs), + k_PathMode_Vals, Info.PathMode); + + CheckButton(IDX_COMPRESS_SHARED, Info.OpenShareForWrite); + CheckButton(IDX_COMPRESS_DEL, Info.DeleteAfterCompressing); + + FormatChanged(false); // isChanged + + // OnButtonSFX(); + + NormalizePosition(); + + return CModalDialog::OnInit(); +} + +/* +namespace NCompressDialog +{ + bool CInfo::GetFullPathName(UString &result) const + { + #ifndef UNDER_CE + // NDirectory::MySetCurrentDirectory(CurrentDirPrefix); + #endif + FString resultF; + bool res = MyGetFullPathName(us2fs(ArchiveName), resultF); + result = fs2us(resultF); + return res; + } +} +*/ + +void CCompressDialog::UpdatePasswordControl() +{ + const bool showPassword = IsShowPasswordChecked(); + const TCHAR c = showPassword ? (TCHAR)0: TEXT('*'); + _password1Control.SetPasswordChar((WPARAM)c); + _password2Control.SetPasswordChar((WPARAM)c); + UString password; + _password1Control.GetText(password); + _password1Control.SetText(password); + _password2Control.GetText(password); + _password2Control.SetText(password); + + ShowItem_Bool(IDT_PASSWORD_REENTER, !showPassword); + _password2Control.Show_Bool(!showPassword); +} + +bool CCompressDialog::OnButtonClicked(unsigned buttonID, HWND buttonHWND) +{ + switch (buttonID) + { + case IDB_COMPRESS_SET_ARCHIVE: + { + OnButtonSetArchive(); + return true; + } + case IDX_COMPRESS_SFX: + { + SetMethod(GetMethodID()); + OnButtonSFX(); + SetMemoryUsage(); + return true; + } + case IDX_PASSWORD_SHOW: + { + UpdatePasswordControl(); + return true; + } + case IDB_COMPRESS_OPTIONS: + { + COptionsDialog dialog(this); + if (dialog.Create(*this) == IDOK) + ShowOptionsString(); + return true; + } + } + return CModalDialog::OnButtonClicked(buttonID, buttonHWND); +} + +void CCompressDialog::CheckSFXControlsEnable() +{ + const CFormatInfo &fi = g_Formats[GetStaticFormatIndex()]; + bool enable = fi.SFX_(); + if (enable) + { + const int methodID = GetMethodID(); + enable = (methodID == -1 || IsMethodSupportedBySfx(methodID)); + } + if (!enable) + CheckButton(IDX_COMPRESS_SFX, false); + EnableItem(IDX_COMPRESS_SFX, enable); +} + +/* +void CCompressDialog::CheckVolumeEnable() +{ + bool isSFX = IsSFX(); + m_Volume.Enable(!isSFX); + if (isSFX) + m_Volume.SetText(TEXT("")); +} +*/ + +void CCompressDialog::EnableMultiCombo(unsigned id) +{ + NWindows::NControl::CComboBox combo; + combo.Attach(GetItem(id)); + const bool enable = (combo.GetCount() > 1); + EnableItem(id, enable); +} + +static LRESULT ComboBox_AddStringAscii(NControl::CComboBox &cb, const char *s) +{ + return cb.AddString((CSysString)s); +} + +static LRESULT ComboBox_AddStringAscii_SetItemData(NControl::CComboBox &cb, + const char *s, LPARAM lParam) +{ + const LRESULT index = ComboBox_AddStringAscii(cb, s); + if (index >= 0) // optional check + cb.SetItemData((int)index, lParam); + return index; +} + +static void Combine_Two_BoolPairs(const CBoolPair &b1, const CBoolPair &b2, CBool1 &res) +{ + if (!b1.Def && b2.Def) + res.Val = b2.Val; + else + res.Val = b1.Val; +} + +#define SET_GUI_BOOL(name) \ + Combine_Two_BoolPairs(Info. name, m_RegistryInfo. name, name) + + +static void Set_Final_BoolPairs( + const CBool1 &gui, + CBoolPair &cmd, + CBoolPair ®) +{ + if (!cmd.Def) + { + reg.Val = gui.Val; + reg.Def = gui.Val; + } + if (gui.Supported) + { + cmd.Val = gui.Val; + cmd.Def = gui.Val; + } + else + cmd.Init(); +} + +#define SET_FINAL_BOOL_PAIRS(name) \ + Set_Final_BoolPairs(name, Info. name, m_RegistryInfo. name) + +void CCompressDialog::FormatChanged(bool isChanged) +{ + SetLevel(); + SetSolidBlockSize(); + SetParams(); + SetMemUseCombo(); + SetNumThreads(); + + const CFormatInfo &fi = g_Formats[GetStaticFormatIndex()]; + Info.SolidIsSpecified = fi.Solid_(); + Info.EncryptHeadersIsAllowed = fi.EncryptFileNames_(); + + /* + const bool multiThreadEnable = fi.MultiThread; + Info.MultiThreadIsAllowed = multiThreadEnable; + EnableItem(IDC_COMPRESS_SOLID, fi.Solid); + EnableItem(IDC_COMPRESS_THREADS, multiThreadEnable); + const bool methodEnable = (fi.MethodIDs != NULL); + EnableItem(IDC_COMPRESS_METHOD, methodEnable); + EnableMultiCombo(IDC_COMPRESS_DICTIONARY, methodEnable); + EnableItem(IDC_COMPRESS_ORDER, methodEnable); + */ + + CheckSFXControlsEnable(); + + { + if (!isChanged) + { + SET_GUI_BOOL (SymLinks); + SET_GUI_BOOL (HardLinks); + SET_GUI_BOOL (AltStreams); + SET_GUI_BOOL (NtSecurity); + SET_GUI_BOOL (PreserveATime); + } + + PreserveATime.Supported = true; + + { + const CArcInfoEx &ai = Get_ArcInfoEx(); + SymLinks.Supported = ai.Flags_SymLinks(); + HardLinks.Supported = ai.Flags_HardLinks(); + AltStreams.Supported = ai.Flags_AltStreams(); + NtSecurity.Supported = ai.Flags_NtSecurity(); + } + + ShowOptionsString(); + } + // CheckVolumeEnable(); + + const bool encrypt = fi.Encrypt_(); + EnableItem(IDG_COMPRESS_ENCRYPTION, encrypt); + + EnableItem(IDT_PASSWORD_ENTER, encrypt); + EnableItem(IDT_PASSWORD_REENTER, encrypt); + EnableItem(IDE_COMPRESS_PASSWORD1, encrypt); + EnableItem(IDE_COMPRESS_PASSWORD2, encrypt); + EnableItem(IDX_PASSWORD_SHOW, encrypt); + + EnableItem(IDT_COMPRESS_ENCRYPTION_METHOD, encrypt); + EnableItem(IDC_COMPRESS_ENCRYPTION_METHOD, encrypt); + EnableItem(IDX_COMPRESS_ENCRYPT_FILE_NAMES, fi.EncryptFileNames_()); + + ShowItem_Bool(IDX_COMPRESS_ENCRYPT_FILE_NAMES, fi.EncryptFileNames_()); + + SetEncryptionMethod(); + SetMemoryUsage(); +} + + +bool CCompressDialog::IsSFX() +{ + return IsWindowEnabled(GetItem(IDX_COMPRESS_SFX)) + && IsButtonCheckedBool(IDX_COMPRESS_SFX); +} + +static int GetExtDotPos(const UString &s) +{ + const int dotPos = s.ReverseFind_Dot(); + if (dotPos > s.ReverseFind_PathSepar() + 1) + return dotPos; + return -1; +} + +void CCompressDialog::OnButtonSFX() +{ + UString fileName; + m_ArchivePath.GetText(fileName); + const int dotPos = GetExtDotPos(fileName); + if (IsSFX()) + { + if (dotPos >= 0) + fileName.DeleteFrom(dotPos); + fileName += kExeExt; + m_ArchivePath.SetText(fileName); + } + else + { + if (dotPos >= 0) + { + const UString ext = fileName.Ptr(dotPos); + if (ext.IsEqualTo_Ascii_NoCase(kExeExt)) + { + fileName.DeleteFrom(dotPos); + m_ArchivePath.SetText(fileName); + } + } + SetArchiveName2(false); // it's for OnInit + } + + // CheckVolumeEnable(); +} + + +bool CCompressDialog::GetFinalPath_Smart(UString &resPath) const +{ + resPath.Empty(); + UString name; + m_ArchivePath.GetText(name); + name.Trim(); + FString fullPath; + UString dirPrefx = DirPrefix; + if (dirPrefx.IsEmpty()) + dirPrefx = StartDirPrefix; + const bool res = !dirPrefx.IsEmpty() ? + NName::GetFullPath(us2fs(dirPrefx), us2fs(name), fullPath): + NName::GetFullPath( us2fs(name), fullPath); + if (res) + resPath = fs2us(fullPath); + return res; +} + + +bool CCompressDialog::SetArcPathFields(const UString &path) +{ + UString name; + return SetArcPathFields(path, name, true); // always +} + + +bool CCompressDialog::SetArcPathFields(const UString &path, UString &name, bool always) +{ + FString resDirPrefix; + FString resFileName; + const bool res = GetFullPathAndSplit(us2fs(path), resDirPrefix, resFileName); + if (res) + { + DirPrefix = fs2us(resDirPrefix); + name = fs2us(resFileName); + } + else + { + if (!always) + return false; + DirPrefix.Empty(); + name = path; + } + SetItemText(IDT_COMPRESS_ARCHIVE_FOLDER, DirPrefix); + m_ArchivePath.SetText(name); + return res; +} + + +static const wchar_t * const k_IncorrectPathMessage = L"Incorrect archive path"; + +static void AddFilter(CObjectVector &filters, + const UString &description, const UString &ext) +{ + CBrowseFilterInfo &f = filters.AddNew(); + UString mask ("*."); + mask += ext; + f.Masks.Add(mask); + f.Description = description; + f.Description += " ("; + f.Description += mask; + f.Description += ")"; +} + + +static const char * const k_DontSave_Exts = + "xpi odt ods docx xlsx "; + +void CCompressDialog::OnButtonSetArchive() +{ + UString path; + if (!GetFinalPath_Smart(path)) + { + ShowErrorMessage(*this, k_IncorrectPathMessage); + return; + } + + int filterIndex; + CObjectVector filters; + unsigned numFormats = 0; + + const bool isSFX = IsSFX(); + if (isSFX) + { + filterIndex = 0; + const UString ext ("exe"); + AddFilter(filters, ext, ext); + } + else + { + filterIndex = m_Format.GetCurSel(); + numFormats = (unsigned)m_Format.GetCount(); + + // filters [0, ... numFormats - 1] corresponds to items in m_Format combo + UString desc; + UStringVector masks; + CStringFinder finder; + + for (unsigned i = 0; i < numFormats; i++) + { + const CArcInfoEx &ai = (*ArcFormats)[(unsigned)m_Format.GetItemData(i)]; + CBrowseFilterInfo &f = filters.AddNew(); + f.Description = ai.Name; + f.Description += " ("; + bool needSpace_desc = false; + + FOR_VECTOR (k, ai.Exts) + { + const UString &ext = ai.Exts[k].Ext; + UString mask ("*."); + mask += ext; + + if (finder.FindWord_In_LowCaseAsciiList_NoCase(k_DontSave_Exts, ext)) + continue; + + f.Masks.Add(mask); + masks.Add(mask); + if (needSpace_desc) + f.Description.Add_Space(); + needSpace_desc = true; + f.Description += ext; + } + f.Description += ")"; + // we use only main ext in desc to reduce the size of list + if (i != 0) + desc.Add_Space(); + desc += ai.GetMainExt(); + } + + CBrowseFilterInfo &f = filters.AddNew(); + f.Description = LangString(IDT_COMPRESS_ARCHIVE); // IDS_ARCHIVES_COLON; + if (f.Description.IsEmpty()) + GetItemText(IDT_COMPRESS_ARCHIVE, f.Description); + f.Description.RemoveChar(L'&'); + // f.Description = "archive"; + f.Description += " ("; + f.Description += desc; + f.Description += ")"; + f.Masks = masks; + } + + AddFilter(filters, LangString(IDS_OPEN_TYPE_ALL_FILES), UString("*")); + if (filterIndex < 0) + filterIndex = (int)filters.Size() - 1; + + const UString title = LangString(IDS_COMPRESS_SET_ARCHIVE_BROWSE); + CBrowseInfo bi; + bi.lpstrTitle = title; + bi.SaveMode = true; + bi.FilterIndex = filterIndex; + bi.hwndOwner = *this; + bi.FilePath = path; + + if (!bi.BrowseForFile(filters)) + return; + + path = bi.FilePath; + + if (isSFX) + { + const int dotPos = GetExtDotPos(path); + if (dotPos >= 0) + path.DeleteFrom(dotPos); + path += kExeExt; + } + else + // if (bi.FilterIndex >= 0) + // if (bi.FilterIndex != filterIndex) + if ((unsigned)bi.FilterIndex < numFormats) + { + // archive format was confirmed. So we try to set format extension + bool needAddExt = true; + const CArcInfoEx &ai = (*ArcFormats)[(unsigned)m_Format.GetItemData((unsigned)bi.FilterIndex)]; + const int dotPos = GetExtDotPos(path); + if (dotPos >= 0) + { + const UString ext = path.Ptr(dotPos + 1); + if (ai.FindExtension(ext) >= 0) + needAddExt = false; + } + if (needAddExt) + { + if (path.IsEmpty() || path.Back() != '.') + path.Add_Dot(); + path += ai.GetMainExt(); + } + } + + SetArcPathFields(path); + + if (!isSFX) + if ((unsigned)bi.FilterIndex < numFormats) + if (bi.FilterIndex != m_Format.GetCurSel()) + { + m_Format.SetCurSel(bi.FilterIndex); + SaveOptionsInMem(); + FormatChanged(true); // isChanged + return; + } + + ArcPath_WasChanged(path); +} + + +// in ExtractDialog.cpp +extern void AddUniqueString(UStringVector &strings, const UString &srcString); + +static bool IsAsciiString(const UString &s) +{ + for (unsigned i = 0; i < s.Len(); i++) + { + const wchar_t c = s[i]; + if (c < 0x20 || c > 0x7F) + return false; + } + return true; +} + + +static void AddSize_MB(UString &s, UInt64 size) +{ + s.Add_LF(); + const UInt64 v2 = size + ((UInt32)1 << 20) - 1; + if (size < v2) + size = v2; + s.Add_UInt64(size >> 20); + s += " MB : "; +} + +static void AddSize_MB_id(UString &s, UInt64 size, UInt32 id) +{ + AddSize_MB(s, size); + AddLangString(s, id); +} + +void SetErrorMessage_MemUsage(UString &s, UInt64 reqSize, UInt64 ramSize, UInt64 ramLimit, const UString &usageString); +void SetErrorMessage_MemUsage(UString &s, UInt64 reqSize, UInt64 ramSize, UInt64 ramLimit, const UString &usageString) +{ + AddLangString(s, IDS_MEM_OPERATION_BLOCKED); + s.Add_LF(); + AddLangString(s, IDS_MEM_REQUIRES_BIG_MEM); + s.Add_LF(); + AddSize_MB(s, reqSize); + s += usageString; + AddSize_MB_id(s, ramSize, IDS_MEM_RAM_SIZE); + // if (ramLimit != 0) + { + AddSize_MB_id(s, ramLimit, IDS_MEM_USAGE_LIMIT_SET_BY_7ZIP); + } + s.Add_LF(); + s.Add_LF(); + AddLangString(s, IDS_MEM_ERROR); +} + + +void CCompressDialog::OnOK() +{ + _password1Control.GetText(Info.Password); + if (IsZipFormat()) + { + if (!IsAsciiString(Info.Password)) + { + ShowErrorMessageHwndRes(*this, IDS_PASSWORD_USE_ASCII); + return; + } + UString method = GetEncryptionMethodSpec(); + if (method.IsPrefixedBy_Ascii_NoCase("aes")) + { + if (Info.Password.Len() > 99) + { + ShowErrorMessageHwndRes(*this, IDS_PASSWORD_TOO_LONG); + return; + } + } + } + if (!IsShowPasswordChecked()) + { + UString password2; + _password2Control.GetText(password2); + if (password2 != Info.Password) + { + ShowErrorMessageHwndRes(*this, IDS_PASSWORD_NOT_MATCH); + return; + } + } + + { + UInt64 decompressMem; + const UInt64 memUsage = GetMemoryUsage_DecompMem(decompressMem); + if (memUsage != (UInt64)(Int64)-1) + { + const UInt64 limit = Get_MemUse_Bytes(); + if (memUsage > limit) + { + UString s2; + LangString_OnlyFromLangFile(IDS_MEM_REQUIRED_MEM_SIZE, s2); + if (s2.IsEmpty()) + { + s2 = LangString(IDT_COMPRESS_MEMORY); + if (s2.IsEmpty()) + GetItemText(IDT_COMPRESS_MEMORY, s2); + s2.RemoveChar(L':'); + } + UString s; + SetErrorMessage_MemUsage(s, memUsage, _ramSize, limit, s2); + MessageBoxError(s); + return; + } + } + } + + SaveOptionsInMem(); + + UStringVector arcPaths; + { + UString s; + if (!GetFinalPath_Smart(s)) + { + ShowErrorMessage(*this, k_IncorrectPathMessage); + return; + } + Info.ArcPath = s; + AddUniqueString(arcPaths, s); + } + + Info.UpdateMode = (NCompressDialog::NUpdateMode::EEnum)k_UpdateMode_Vals[m_UpdateMode.GetCurSel()]; + Info.PathMode = (NWildcard::ECensorPathMode)k_PathMode_Vals[m_PathMode.GetCurSel()]; + + Info.Level = GetLevelSpec(); + Info.Dict64 = GetDictSpec(); + // Info.Dict64_Chain = GetDictChainSpec(); + Info.Order = GetOrderSpec(); + Info.OrderMode = GetOrderMode(); + Info.NumThreads = GetNumThreadsSpec(); + + Info.MemUsage.Clear(); + { + const UString mus = Get_MemUse_Spec(); + if (!mus.IsEmpty()) + { + NCompression::CMemUse mu; + mu.Parse(mus); + if (mu.IsDefined) + Info.MemUsage = mu; + } + } + + { + // Info.SolidIsSpecified = g_Formats[GetStaticFormatIndex()].Solid; + const UInt32 solidLogSize = GetBlockSizeSpec(); + Info.SolidBlockSize = 0; + if (solidLogSize == (UInt32)(Int32)-1) + Info.SolidIsSpecified = false; + else if (solidLogSize > 0) + Info.SolidBlockSize = (solidLogSize >= 64) ? + (UInt64)(Int64)-1 : + ((UInt64)1 << solidLogSize); + } + + Info.Method = GetMethodSpec(); + Info.EncryptionMethod = GetEncryptionMethodSpec(); + Info.FormatIndex = (int)GetFormatIndex(); + Info.SFXMode = IsSFX(); + Info.OpenShareForWrite = IsButtonCheckedBool(IDX_COMPRESS_SHARED); + Info.DeleteAfterCompressing = IsButtonCheckedBool(IDX_COMPRESS_DEL); + + m_RegistryInfo.EncryptHeaders = + Info.EncryptHeaders = IsButtonCheckedBool(IDX_COMPRESS_ENCRYPT_FILE_NAMES); + + + /* (Info) is for saving to registry: + (CBoolPair::Val) will be set as (false), if it was (false) + in registry at dialog creation, and user didn't click checkbox. + in another case (CBoolPair::Val) will be set as (true) */ + + { + /* Info properties could be for another archive types. + so we disable unsupported properties in Info */ + // const CArcInfoEx &ai = Get_ArcInfoEx(); + + SET_FINAL_BOOL_PAIRS (SymLinks); + SET_FINAL_BOOL_PAIRS (HardLinks); + SET_FINAL_BOOL_PAIRS (AltStreams); + SET_FINAL_BOOL_PAIRS (NtSecurity); + + SET_FINAL_BOOL_PAIRS (PreserveATime); + } + + { + const NCompression::CFormatOptions &fo = Get_FormatOptions(); + + Info.TimePrec = fo.TimePrec; + Info.MTime = fo.MTime; + Info.CTime = fo.CTime; + Info.ATime = fo.ATime; + Info.SetArcMTime = fo.SetArcMTime; + } + + m_Params.GetText(Info.Options); + + UString volumeString; + m_Volume.GetText(volumeString); + volumeString.Trim(); + Info.VolumeSizes.Clear(); + + if (!volumeString.IsEmpty()) + { + if (!ParseVolumeSizes(volumeString, Info.VolumeSizes)) + { + ShowErrorMessageHwndRes(*this, IDS_INCORRECT_VOLUME_SIZE); + return; + } + if (!Info.VolumeSizes.IsEmpty()) + { + const UInt64 volumeSize = Info.VolumeSizes.Back(); + if (volumeSize < (100 << 10)) + { + wchar_t s[32]; + ConvertUInt64ToString(volumeSize, s); + if (::MessageBoxW(*this, MyFormatNew(IDS_SPLIT_CONFIRM, s), + L"7-Zip", MB_YESNOCANCEL | MB_ICONQUESTION) != IDYES) + return; + } + } + } + + if (Info.FormatIndex >= 0) + m_RegistryInfo.ArcType = (*ArcFormats)[Info.FormatIndex].Name; + m_RegistryInfo.ShowPassword = IsShowPasswordChecked(); + + FOR_VECTOR (i, m_RegistryInfo.ArcPaths) + { + if (arcPaths.Size() >= kHistorySize) + break; + AddUniqueString(arcPaths, m_RegistryInfo.ArcPaths[i]); + } + m_RegistryInfo.ArcPaths = arcPaths; + + m_RegistryInfo.Save(); + + CModalDialog::OnOK(); +} + +#define kHelpTopic "fm/plugins/7-zip/add.htm" +#define kHelpTopic_Options "fm/plugins/7-zip/add.htm#options" + +void CCompressDialog::OnHelp() +{ + ShowHelpWindow(kHelpTopic); +} + + +void CCompressDialog::ArcPath_WasChanged(const UString &path) +{ + const int dotPos = GetExtDotPos(path); + if (dotPos < 0) + return; + const UString ext = path.Ptr(dotPos + 1); + { + const CArcInfoEx &ai = Get_ArcInfoEx(); + if (ai.FindExtension(ext) >= 0) + return; + } + + const unsigned count = (unsigned)m_Format.GetCount(); + for (unsigned i = 0; i < count; i++) + { + const CArcInfoEx &ai = (*ArcFormats)[(unsigned)m_Format.GetItemData(i)]; + if (ai.FindExtension(ext) >= 0) + { + m_Format.SetCurSel(i); + SaveOptionsInMem(); + FormatChanged(true); // isChanged + return; + } + } +} + + +bool CCompressDialog::OnMessage(UINT message, WPARAM wParam, LPARAM lParam) +{ + switch (message) + { + case k_Message_ArcChanged: + { + // UString path; + // m_ArchivePath.GetText(path); + const int select = m_ArchivePath.GetCurSel(); + if ((unsigned)select < m_RegistryInfo.ArcPaths.Size()) + // if (path == m_RegistryInfo.ArcPaths[select]) + { + const UString &path = m_RegistryInfo.ArcPaths[select]; + SetArcPathFields(path); + // ArcPath_WasChanged(path); + } + return 0; + } + } + return CModalDialog::OnMessage(message, wParam, lParam); +} + + +bool CCompressDialog::OnCommand(unsigned code, unsigned itemID, LPARAM lParam) +{ + if (code == CBN_SELCHANGE) + { + switch (itemID) + { + case IDC_COMPRESS_ARCHIVE: + { + /* CBN_SELCHANGE is called before actual value of combo text will be changed. + So GetText() here returns old value (before change) of combo text. + So here we can change all controls except of m_ArchivePath. + */ + const int select = m_ArchivePath.GetCurSel(); + if ((unsigned)select < m_RegistryInfo.ArcPaths.Size()) + { + // DirPrefix.Empty(); + // SetItemText(IDT_COMPRESS_ARCHIVE_FOLDER, DirPrefix); + const UString &path = m_RegistryInfo.ArcPaths[select]; + // SetArcPathFields(path); + ArcPath_WasChanged(path); + // we use PostMessage(k_Message_ArcChanged) here that later will change m_ArchivePath control + PostMsg(k_Message_ArcChanged); + } + return true; + } + + case IDC_COMPRESS_FORMAT: + { + const bool isSFX = IsSFX(); + SaveOptionsInMem(); + FormatChanged(true); // isChanged + SetArchiveName2(isSFX); + return true; + } + + case IDC_COMPRESS_LEVEL: + { + Get_FormatOptions().ResetForLevelChange(); + + SetMethod(); // call it if level changes method + + // call the following if level change keeps old method + /* + { + // try to keep old method + SetMethod(GetMethodID()); + MethodChanged(); + } + */ + + SetSolidBlockSize(); + SetNumThreads(); + CheckSFXNameChange(); + SetMemoryUsage(); + return true; + } + + case IDC_COMPRESS_METHOD: + { + MethodChanged(); + SetSolidBlockSize(); + SetNumThreads(); + CheckSFXNameChange(); + SetMemoryUsage(); + if (Get_ArcInfoEx().Flags_HashHandler()) + SetArchiveName2(false); + + return true; + } + + case IDC_COMPRESS_DICTIONARY: + // case IDC_COMPRESS_DICTIONARY2: + { + /* we want to change the reported threads for Auto line + and keep selected NumThreads option + So we save selected NumThreads option in memory */ + SaveOptionsInMem(); + const UInt32 blockSizeLog = GetBlockSizeSpec(); + if (// blockSizeLog != (UInt32)(Int32)-1 && + blockSizeLog != kSolidLog_NoSolid + && blockSizeLog != kSolidLog_FullSolid) + { + Get_FormatOptions().Reset_BlockLogSize(); + // SetSolidBlockSize(true); + } + + SetDictionary2(); + SetSolidBlockSize(); + SetNumThreads(); // we want to change the reported threads for Auto line only + SetMemoryUsage(); + return true; + } + + case IDC_COMPRESS_ORDER: + { + #ifdef PRINT_PARAMS + Print_Params(); + #endif + return true; + } + + case IDC_COMPRESS_SOLID: + { + SetMemoryUsage(); + return true; + } + + case IDC_COMPRESS_THREADS: + { + SetMemoryUsage(); + return true; + } + + case IDC_COMPRESS_MEM_USE: + { + /* we want to change the reported threads for Auto line + and keep selected NumThreads option + So we save selected NumThreads option in memory */ + SaveOptionsInMem(); + + SetNumThreads(); // we want to change the reported threads for Auto line only + SetMemoryUsage(); + return true; + } + } + } + return CModalDialog::OnCommand(code, itemID, lParam); +} + +void CCompressDialog::CheckSFXNameChange() +{ + const bool isSFX = IsSFX(); + CheckSFXControlsEnable(); + if (isSFX != IsSFX()) + SetArchiveName2(isSFX); +} + +void CCompressDialog::SetArchiveName2(bool prevWasSFX) +{ + UString fileName; + m_ArchivePath.GetText(fileName); + const CArcInfoEx &prevArchiverInfo = (*ArcFormats)[m_PrevFormat]; + if (prevArchiverInfo.Flags_KeepName() || Info.KeepName) + { + UString prevExtension; + if (prevWasSFX) + prevExtension = kExeExt; + else + { + prevExtension.Add_Dot(); + prevExtension += prevArchiverInfo.GetMainExt(); + } + const unsigned prevExtensionLen = prevExtension.Len(); + if (fileName.Len() >= prevExtensionLen) + if (StringsAreEqualNoCase(fileName.RightPtr(prevExtensionLen), prevExtension)) + fileName.DeleteFrom(fileName.Len() - prevExtensionLen); + } + SetArchiveName(fileName); +} + +// if type.KeepName then use OriginalFileName +// else if !KeepName remove extension +// add new extension + +void CCompressDialog::SetArchiveName(const UString &name) +{ + UString fileName = name; + Info.FormatIndex = (int)GetFormatIndex(); + const CArcInfoEx &ai = (*ArcFormats)[Info.FormatIndex]; + m_PrevFormat = Info.FormatIndex; + if (ai.Flags_KeepName()) + { + fileName = OriginalFileName; + } + else + { + if (!Info.KeepName) + { + int dotPos = GetExtDotPos(fileName); + if (dotPos >= 0) + fileName.DeleteFrom(dotPos); + } + } + + if (IsSFX()) + fileName += kExeExt; + else + { + fileName.Add_Dot(); + UString ext = ai.GetMainExt(); + if (ai.Flags_HashHandler()) + { + UString estimatedName; + GetMethodSpec(estimatedName); + if (!estimatedName.IsEmpty()) + { + ext = estimatedName; + ext.MakeLower_Ascii(); + } + } + fileName += ext; + } + m_ArchivePath.SetText(fileName); +} + + +int CCompressDialog::FindRegistryFormat(const UString &name) +{ + FOR_VECTOR (i, m_RegistryInfo.Formats) + { + const NCompression::CFormatOptions &fo = m_RegistryInfo.Formats[i]; + if (name.IsEqualTo_NoCase(GetUnicodeString(fo.FormatID))) + return (int)i; + } + return -1; +} + + +unsigned CCompressDialog::FindRegistryFormat_Always(const UString &name) +{ + const int index = FindRegistryFormat(name); + if (index >= 0) + return (unsigned)index; + { + NCompression::CFormatOptions fo; + fo.FormatID = GetSystemString(name); + return m_RegistryInfo.Formats.Add(fo); + } +} + + +NCompression::CFormatOptions &CCompressDialog::Get_FormatOptions() +{ + const CArcInfoEx &ai = Get_ArcInfoEx(); + return m_RegistryInfo.Formats[FindRegistryFormat_Always(ai.Name)]; +} + + +unsigned CCompressDialog::GetStaticFormatIndex() +{ + const CArcInfoEx &ai = Get_ArcInfoEx(); + for (unsigned i = 0; i < Z7_ARRAY_SIZE(g_Formats); i++) + if (ai.Name.IsEqualTo_Ascii_NoCase(g_Formats[i].Name)) + return i; + return 0; // -1; +} + +void CCompressDialog::SetNearestSelectComboBox(NControl::CComboBox &comboBox, UInt32 value) +{ + for (int i = comboBox.GetCount() - 1; i >= 0; i--) + if ((UInt32)comboBox.GetItemData(i) <= value) + { + comboBox.SetCurSel(i); + return; + } + if (comboBox.GetCount() > 0) + comboBox.SetCurSel(0); +} + +void CCompressDialog::SetLevel2() +{ + m_Level.ResetContent(); + const CFormatInfo &fi = g_Formats[GetStaticFormatIndex()]; + const CArcInfoEx &ai = Get_ArcInfoEx(); + UInt32 level = 5; + { + int index = FindRegistryFormat(ai.Name); + if (index >= 0) + { + const NCompression::CFormatOptions &fo = m_RegistryInfo.Formats[index]; + if (fo.Level <= 9) + level = fo.Level; + else if (fo.Level == (UInt32)(Int32)-1) + level = 5; + else + level = 9; + } + } + + const bool isZstd = ai.Is_Zstd(); + + for (unsigned i = 0; i < sizeof(UInt32) * 8; i++) + { + const UInt32 mask = fi.LevelsMask >> i; + // if (mask == 0) break; + if (mask & 1) + { + UString s; + s.Add_UInt32(i); + if (i < Z7_ARRAY_SIZE(g_Levels)) + { + const UInt32 langID = g_Levels[i]; + // if (fi.LevelsMask < (1 << (MY_ZSTD_LEVEL_MAX + 1)) - 1) + if (langID) + if (i != 0 || !isZstd) + { + s += " - "; + AddLangString(s, langID); + } + } + m_Level.AddString_SetItemData(s, (LPARAM)i); + } + } + SetNearestSelectComboBox(m_Level, level); +} + + +static const char * const k_Auto_Prefix = "* "; + +static void Modify_Auto(AString &s) +{ + s.Insert(0, k_Auto_Prefix); +} + +void CCompressDialog::SetMethod2(int keepMethodId) +{ + m_Method.ResetContent(); + _auto_MethodId = -1; + const CFormatInfo &fi = g_Formats[GetStaticFormatIndex()]; + const CArcInfoEx &ai = Get_ArcInfoEx(); + if (GetLevel() == 0 && !ai.Flags_HashHandler()) + { + if (!ai.Is_Tar() && + !ai.Is_Zstd()) + { + MethodChanged(); + return; + } + } + UString defaultMethod; + { + const int index = FindRegistryFormat(ai.Name); + if (index >= 0) + { + const NCompression::CFormatOptions &fo = m_RegistryInfo.Formats[index]; + defaultMethod = fo.Method; + } + } + const bool isSfx = IsSFX(); + bool weUseSameMethod = false; + + const bool is7z = ai.Is_7z(); + + for (unsigned m = 0;; m++) + { + int methodID; + const char *method; + if (m < fi.NumMethods) + { + methodID = fi.MethodIDs[m]; + method = kMethodsNames[methodID]; + if (is7z) + if (methodID == kCopy + || methodID == kDeflate + || methodID == kDeflate64 + ) + continue; + } + else + { + if (!is7z) + break; + const unsigned extIndex = m - fi.NumMethods; + if (extIndex >= ExternalMethods.Size()) + break; + methodID = (int)(Z7_ARRAY_SIZE(kMethodsNames) + extIndex); + method = ExternalMethods[extIndex].Ptr(); + } + if (isSfx) + if (!IsMethodSupportedBySfx(methodID)) + continue; + + AString s (method); + int writtenMethodId = methodID; + if (m == 0) + { + _auto_MethodId = methodID; + writtenMethodId = -1; + Modify_Auto(s); + } + const int itemIndex = (int)ComboBox_AddStringAscii_SetItemData(m_Method, + s, writtenMethodId); + if (keepMethodId == methodID) + { + m_Method.SetCurSel(itemIndex); + weUseSameMethod = true; + continue; + } + if ((defaultMethod.IsEqualTo_Ascii_NoCase(method) || m == 0) && !weUseSameMethod) + m_Method.SetCurSel(itemIndex); + } + + if (!weUseSameMethod) + MethodChanged(); +} + + + +bool CCompressDialog::IsZipFormat() +{ + return Get_ArcInfoEx().Is_Zip(); +} + +bool CCompressDialog::IsXzFormat() +{ + return Get_ArcInfoEx().Is_Xz(); +} + +void CCompressDialog::SetEncryptionMethod() +{ + _encryptionMethod.ResetContent(); + _default_encryptionMethod_Index = -1; + const CArcInfoEx &ai = Get_ArcInfoEx(); + if (ai.Is_7z()) + { + ComboBox_AddStringAscii(_encryptionMethod, "AES-256"); + _encryptionMethod.SetCurSel(0); + _default_encryptionMethod_Index = 0; + } + else if (ai.Is_Zip()) + { + const int index = FindRegistryFormat(ai.Name); + UString encryptionMethod; + if (index >= 0) + { + const NCompression::CFormatOptions &fo = m_RegistryInfo.Formats[index]; + encryptionMethod = fo.EncryptionMethod; + } + int sel = 0; + // if (ZipCryptoIsAllowed) + { + ComboBox_AddStringAscii(_encryptionMethod, "ZipCrypto"); + sel = (encryptionMethod.IsPrefixedBy_Ascii_NoCase("aes") ? 1 : 0); + _default_encryptionMethod_Index = 0; + } + ComboBox_AddStringAscii(_encryptionMethod, "AES-256"); + _encryptionMethod.SetCurSel(sel); + } +} + + +int CCompressDialog::GetMethodID_RAW() +{ + if (m_Method.GetCount() <= 0) + return -1; + return (int)(Int32)(UInt32)m_Method.GetItemData_of_CurSel(); +} + +int CCompressDialog::GetMethodID() +{ + int raw = GetMethodID_RAW(); + if (raw < 0) + return _auto_MethodId; + return raw; +} + + +UString CCompressDialog::GetMethodSpec(UString &estimatedName) +{ + estimatedName.Empty(); + if (m_Method.GetCount() < 1) + return estimatedName; + const int methodIdRaw = GetMethodID_RAW(); + int methodId = methodIdRaw; + if (methodIdRaw < 0) + methodId = _auto_MethodId; + UString s; + if (methodId >= 0) + { + if ((unsigned)methodId < Z7_ARRAY_SIZE(kMethodsNames)) + estimatedName = kMethodsNames[methodId]; + else + estimatedName = ExternalMethods[(unsigned)methodId - (unsigned)Z7_ARRAY_SIZE(kMethodsNames)]; + if (methodIdRaw >= 0) + s = estimatedName; + } + return s; +} + + +UString CCompressDialog::GetMethodSpec() +{ + UString estimatedName; + UString s = GetMethodSpec(estimatedName); + return s; +} + +bool CCompressDialog::IsMethodEqualTo(const UString &s) +{ + UString estimatedName; + const UString shortName = GetMethodSpec(estimatedName); + if (s.IsEmpty()) + return shortName.IsEmpty(); + return s.IsEqualTo_NoCase(estimatedName); +} + + +UString CCompressDialog::GetEncryptionMethodSpec() +{ + UString s; + if (_encryptionMethod.GetCount() > 0 + && _encryptionMethod.GetCurSel() != _default_encryptionMethod_Index) + { + _encryptionMethod.GetText(s); + s.RemoveChar(L'-'); + } + return s; +} + + +static const size_t k_Auto_Dict = (size_t)0 - 1; + +static int Combo_AddDict2(NWindows::NControl::CComboBox &cb, size_t sizeReal, size_t sizeShow) +{ + char c = 0; + unsigned moveBits = 0; + if ((sizeShow & 0xFFFFF) == 0) { moveBits = 20; c = 'M'; } + else if ((sizeShow & 0x3FF) == 0) { moveBits = 10; c = 'K'; } + AString s; + s.Add_UInt64(sizeShow >> moveBits); + s.Add_Space(); + if (c != 0) + s.Add_Char(c); + s.Add_Char('B'); + if (sizeReal == k_Auto_Dict) + Modify_Auto(s); + return (int)ComboBox_AddStringAscii_SetItemData(cb, s, (LPARAM)sizeReal); +} + +int CCompressDialog::AddDict2(size_t sizeReal, size_t sizeShow) +{ + return Combo_AddDict2(m_Dictionary, sizeReal, sizeShow); +} + +int CCompressDialog::AddDict(size_t size) +{ + return AddDict2(size, size); +} + +/* +int CCompressDialog::AddDict_Chain(size_t size) +{ + return Combo_AddDict2(m_Dictionary_Chain, size, size); +} +*/ + +void CCompressDialog::SetDictionary2() +{ + m_Dictionary.ResetContent(); + // m_Dictionary_Chain.ResetContent(); + + // _auto_Dict = (UInt32)1 << 24; // we can use this dictSize to calculate _auto_Solid for unknown method for 7z + _auto_Dict = (UInt32)(Int32)-1; // for debug + // _auto_Dict_Chain = (UInt32)(Int32)-1; // for debug + + const CArcInfoEx &ai = Get_ArcInfoEx(); + UInt32 defaultDict = (UInt32)(Int32)-1; + // UInt32 defaultDict_Chain = (UInt32)(Int32)-1; + { + const int index = FindRegistryFormat(ai.Name); + if (index >= 0) + { + const NCompression::CFormatOptions &fo = m_RegistryInfo.Formats[index]; + if (IsMethodEqualTo(fo.Method)) + { + defaultDict = fo.Dictionary; + // defaultDict_Chain = fo.DictionaryChain; + } + } + } + + const int methodID = GetMethodID(); + const UInt32 level = GetLevel2(); + + { + RECT r, rLabel; + GetClientRectOfItem(IDT_COMPRESS_DICTIONARY, rLabel); + GetClientRectOfItem(IDC_COMPRESS_DICTIONARY, r); + if (_dictionaryCombo_left == 0) + _dictionaryCombo_left = r.left; + + // bool showDict2; + int newLableRight; + int newDictLeft; + + /* + if (methodID == kZSTD) + { + showDict2 = true; + newDictLeft = _dictionaryCombo_left; + RECT r2; + GetClientRectOfItem(IDC_COMPRESS_DICTIONARY2, r2); + newLableRight = r2.left; + } + else + */ + { + // showDict2 = false; + RECT rBig; + GetClientRectOfItem(IDC_COMPRESS_METHOD, rBig); + newDictLeft= rBig.left; + newLableRight = newDictLeft; + } + + if (newLableRight != rLabel.right) + { + rLabel.right = newLableRight; + MoveItem_RECT(IDT_COMPRESS_DICTIONARY, rLabel); + InvalidateRect(&rLabel); + } + if (newDictLeft != r.left) + { + r.left = newDictLeft; + MoveItem_RECT(IDC_COMPRESS_DICTIONARY, r); + // InvalidateRect(&r); + } + // ShowItem_Bool(IDC_COMPRESS_DICTIONARY2, showDict2); + } + + if (methodID < 0) + return; + + switch (methodID) + { + case kLZMA: + case kLZMA2: + { + { + _auto_Dict = level <= 4 ? + (UInt32)1 << (level * 2 + 16) : + level <= sizeof(size_t) / 2 + 4 ? + (UInt32)1 << (level + 20) : + (UInt32)1 << (sizeof(size_t) / 2 + 24); + } + + // we use threshold 3.75 GiB to switch to kLzmaMaxDictSize. + if (defaultDict != (UInt32)(Int32)-1 + && defaultDict >= ((UInt32)15 << 28)) + defaultDict = kLzmaMaxDictSize; + + const size_t kLzmaMaxDictSize_Up = (size_t)1 << (20 + sizeof(size_t) / 4 * 6); + + int curSel = AddDict2(k_Auto_Dict, _auto_Dict); + + for (unsigned i = (16 - 1) * 2; i <= (32 - 1) * 2; i++) + { + if (i < (20 - 1) * 2 + && i != (16 - 1) * 2 + && i != (18 - 1) * 2) + continue; + if (i == (20 - 1) * 2 + 1) + continue; + const size_t dict_up = (size_t)(2 + (i & 1)) << (i / 2); + size_t dict = dict_up; + if (dict_up >= kLzmaMaxDictSize) + dict = kLzmaMaxDictSize; // we reduce dictionary + + const int index = AddDict(dict); + // AddDict2(dict, dict_up); // for debug : we show 4 GB + + // const UInt32 numThreads = 2; + // const UInt64 memUsage = GetMemoryUsageComp_Threads_Dict(numThreads, dict); + if (defaultDict != (UInt32)(Int32)-1) + if (dict <= defaultDict || curSel <= 0) + // if (!maxRamSize_Defined || memUsage <= maxRamSize) + curSel = index; + if (dict_up >= kLzmaMaxDictSize_Up) + break; + } + + m_Dictionary.SetCurSel(curSel); + break; + } + + /* + case kZSTD: + { + if (defaultDict != (UInt32)(Int32)-1 && + defaultDict > kZstd_MAX_DictSize) + defaultDict = kZstd_MAX_DictSize; + + if (defaultDict_Chain != (UInt32)(Int32)-1 && + defaultDict_Chain > kZstd_MAX_DictSize_Chain) + defaultDict_Chain = kZstd_MAX_DictSize_Chain; + + { + CZstdEncProps props; + ZstdEncProps_Init(&props); + // props.level_zstd = level; + props.level_7z = level; + ZstdEncProps_Set_WindowSize(&props, defaultDict != (UInt32)(Int32)-1 ? defaultDict: 0); + ZstdEncProps_NormalizeFull(&props); + _auto_Dict_Chain = (UInt32)1 << props.windowLog_Chain; + } + { + CZstdEncProps props; + ZstdEncProps_Init(&props); + // props.level_zstd = level; + props.level_7z = level; + ZstdEncProps_Set_WindowChainSize(&props, defaultDict_Chain != (UInt32)(Int32)-1 ? defaultDict_Chain: 0); + ZstdEncProps_NormalizeFull(&props); + _auto_Dict = (UInt32)1 << props.windowLog; + } + + // if there is collision of two window sizes, we reduce dict_Chain + if (defaultDict != (UInt32)(Int32)-1 && + defaultDict_Chain != (UInt32)(Int32)-1 && + defaultDict < defaultDict_Chain) + defaultDict_Chain = defaultDict; + + { + int curSel = AddDict2(k_Auto_Dict, _auto_Dict); + + // defaultDict = 12 << 10; // for debug + const UInt32 kWinStart = 18; + if (defaultDict != 0 && defaultDict < ((UInt32)1 << kWinStart)) + curSel = AddDict(defaultDict); + + for (unsigned i = kWinStart; i <= MY_ZSTD_WINDOWLOG_MAX; i++) + { + const size_t dict = (size_t)1 << i; + const int index = AddDict(dict); + if (defaultDict != (UInt32)(Int32)-1) + if (dict <= defaultDict || curSel <= 0) + curSel = index; + } + m_Dictionary.SetCurSel(curSel); + } + + { + int curSel = Combo_AddDict2(m_Dictionary_Chain, k_Auto_Dict, _auto_Dict_Chain); + + // defaultDict_Chain = 10 << 10; // for debug + const UInt32 kWinChainStart = 15; + if (defaultDict_Chain != 0 && defaultDict_Chain < ((UInt32)1 << kWinChainStart)) + curSel = AddDict_Chain(defaultDict_Chain); + + for (unsigned i = kWinChainStart; i <= kMaxDictChain; i++) + { + const size_t dict = (size_t)1 << i; + if (defaultDict != (UInt32)(Int32)-1 && dict > defaultDict) + break; + const int index = AddDict_Chain(dict); + if (defaultDict_Chain != (UInt32)(Int32)-1) + if (dict <= defaultDict_Chain || curSel <= 0) + curSel = index; + } + m_Dictionary_Chain.SetCurSel(curSel); + } + + break; + } + */ + + case kPPMd: + { + _auto_Dict = (UInt32)1 << (level + 19); + + const UInt32 kPpmd_Default_4g = (UInt32)0 - ((UInt32)1 << 10); + const size_t kPpmd_MaxDictSize_Up = (size_t)1 << (29 + sizeof(size_t) / 8); + + if (defaultDict != (UInt32)(Int32)-1 + && defaultDict >= ((UInt32)15 << 28)) // threshold + defaultDict = kPpmd_Default_4g; + + int curSel = AddDict2(k_Auto_Dict, _auto_Dict); + + for (unsigned i = (20 - 1) * 2; i <= (32 - 1) * 2; i++) + { + if (i == (20 - 1) * 2 + 1) + continue; + + const size_t dict_up = (size_t)(2 + (i & 1)) << (i / 2); + size_t dict = dict_up; + if (dict_up >= kPpmd_Default_4g) + dict = kPpmd_Default_4g; + + const int index = AddDict2(dict, dict_up); + // AddDict2((UInt32)((UInt32)0 - 2), dict_up); // for debug + // AddDict(dict_up); // for debug + // const UInt64 memUsage = GetMemoryUsageComp_Threads_Dict(1, dict); + if (defaultDict != (UInt32)(Int32)-1) + if (dict <= defaultDict || curSel <= 0) + // if (!maxRamSize_Defined || memUsage <= maxRamSize) + curSel = index; + if (dict_up >= kPpmd_MaxDictSize_Up) + break; + } + m_Dictionary.SetCurSel(curSel); + break; + } + + case kPPMdZip: + { + _auto_Dict = (UInt32)1 << (level + 19); + + int curSel = AddDict2(k_Auto_Dict, _auto_Dict); + + for (unsigned i = 20; i <= 28; i++) + { + const UInt32 dict = (UInt32)1 << i; + const int index = AddDict(dict); + // const UInt64 memUsage = GetMemoryUsageComp_Threads_Dict(1, dict); + if (defaultDict != (UInt32)(Int32)-1) + if (dict <= defaultDict || curSel <= 0) + // if (!maxRamSize_Defined || memUsage <= maxRamSize) + curSel = index; + } + m_Dictionary.SetCurSel(curSel); + break; + } + + case kDeflate: + case kDeflate64: + { + const UInt32 dict = (methodID == kDeflate ? (UInt32)(1 << 15) : (UInt32)(1 << 16)); + _auto_Dict = dict; + AddDict2(k_Auto_Dict, _auto_Dict); + m_Dictionary.SetCurSel(0); + // EnableItem(IDC_COMPRESS_DICTIONARY, false); + break; + } + + case kBZip2: + { + { + if (level >= 5) _auto_Dict = (900 << 10); + else if (level >= 3) _auto_Dict = (500 << 10); + else _auto_Dict = (100 << 10); + } + + int curSel = AddDict2(k_Auto_Dict, _auto_Dict); + + for (unsigned i = 1; i <= 9; i++) + { + const UInt32 dict = ((UInt32)i * 100) << 10; + AddDict(dict); + // AddDict2(i * 100000, dict); + if (defaultDict != (UInt32)(Int32)-1) + if (i <= defaultDict / 100000 || curSel <= 0) + curSel = m_Dictionary.GetCount() - 1; + } + m_Dictionary.SetCurSel(curSel); + break; + } + + case kCopy: + { + _auto_Dict = 0; + AddDict(0); + m_Dictionary.SetCurSel(0); + break; + } + } +} + + +UInt32 CCompressDialog::GetComboValue(NWindows::NControl::CComboBox &c, int defMax) +{ + if (c.GetCount() <= defMax) + return (UInt32)(Int32)-1; + return (UInt32)c.GetItemData_of_CurSel(); +} + + +UInt64 CCompressDialog::GetComboValue_64(NWindows::NControl::CComboBox &c, int defMax) +{ + if (c.GetCount() <= defMax) + return (UInt64)(Int64)-1; + // LRESULT is signed. so we cast it to unsigned size_t at first: + LRESULT val = c.GetItemData_of_CurSel(); + if (val == (LPARAM)(INT_PTR)(-1)) + return (UInt64)(Int64)-1; + return (UInt64)(size_t)c.GetItemData_of_CurSel(); +} + +UInt32 CCompressDialog::GetLevel2() +{ + UInt32 level = GetLevel(); + if (level == (UInt32)(Int32)-1) + level = 5; + return level; +} + + +int CCompressDialog::AddOrder(UInt32 size) +{ + char s[32]; + ConvertUInt32ToString(size, s); + return (int)ComboBox_AddStringAscii_SetItemData(m_Order, s, (LPARAM)size); +} + +int CCompressDialog::AddOrder_Auto() +{ + AString s; + s.Add_UInt32(_auto_Order); + Modify_Auto(s); + return (int)ComboBox_AddStringAscii_SetItemData(m_Order, s, (LPARAM)(INT_PTR)(-1)); +} + +void CCompressDialog::SetOrder2() +{ + m_Order.ResetContent(); + + _auto_Order = 1; + + const CArcInfoEx &ai = Get_ArcInfoEx(); + UInt32 defaultOrder = (UInt32)(Int32)-1; + + { + const int index = FindRegistryFormat(ai.Name); + if (index >= 0) + { + const NCompression::CFormatOptions &fo = m_RegistryInfo.Formats[index]; + if (IsMethodEqualTo(fo.Method)) + defaultOrder = fo.Order; + } + } + + const int methodID = GetMethodID(); + const UInt32 level = GetLevel2(); + if (methodID < 0) + return; + + switch (methodID) + { + case kLZMA: + case kLZMA2: + { + _auto_Order = (level < 7 ? 32 : 64); + int curSel = AddOrder_Auto(); + for (unsigned i = 2 * 2; i < 8 * 2; i++) + { + UInt32 order = ((UInt32)(2 + (i & 1)) << (i / 2)); + if (order > 256) + order = 273; + const int index = AddOrder(order); + if (defaultOrder != (UInt32)(Int32)-1) + if (order <= defaultOrder || curSel <= 0) + curSel = index; + } + m_Order.SetCurSel(curSel); + break; + } + + /* + case kZSTD: + { + { + CZstdEncProps props; + ZstdEncProps_Init(&props); + // props.level_zstd = level; + props.level_7z = level; + ZstdEncProps_NormalizeFull(&props); + _auto_Order = props.targetLength; + if (props.strategy < ZSTD_strategy_btopt) + { + // ZSTD_strategy_fast uses targetLength to change fast level. + // targetLength probably is used only in ZSTD_strategy_btopt and higher + break; + } + } + int curSel = AddOrder_Auto(); + + for (unsigned i = 6; i <= 9 * 2; i++) + { + UInt32 order = ((UInt32)(2 + (i & 1)) << (i / 2)); + // if (order > 999) order = 999; + const int index = AddOrder(order); + if (defaultOrder != (UInt32)(Int32)-1) + if (order <= defaultOrder || curSel <= 0) + curSel = index; + } + m_Order.SetCurSel(curSel); + break; + } + */ + + case kDeflate: + case kDeflate64: + { + { + if (level >= 9) _auto_Order = 128; + else if (level >= 7) _auto_Order = 64; + else _auto_Order = 32; + } + int curSel = AddOrder_Auto(); + for (unsigned i = 2 * 2; i < 8 * 2; i++) + { + UInt32 order = ((UInt32)(2 + (i & 1)) << (i / 2)); + if (order > 256) + order = (methodID == kDeflate64 ? 257 : 258); + const int index = AddOrder(order); + if (defaultOrder != (UInt32)(Int32)-1) + if (order <= defaultOrder || curSel <= 0) + curSel = index; + } + + m_Order.SetCurSel(curSel); + break; + } + + case kPPMd: + { + { + if (level >= 9) _auto_Order = 32; + else if (level >= 7) _auto_Order = 16; + else if (level >= 5) _auto_Order = 6; + else _auto_Order = 4; + } + + int curSel = AddOrder_Auto(); + + for (unsigned i = 0;; i++) + { + UInt32 order = i + 2; + if (i >= 2) + order = (4 + ((i - 2) & 3)) << ((i - 2) / 4); + const int index = AddOrder(order); + if (defaultOrder != (UInt32)(Int32)-1) + if (order <= defaultOrder || curSel <= 0) + curSel = index; + if (order >= 32) + break; + } + m_Order.SetCurSel(curSel); + break; + } + + case kPPMdZip: + { + _auto_Order = level + 3; + int curSel = AddOrder_Auto(); + for (unsigned i = 2; i <= 16; i++) + { + const int index = AddOrder(i); + if (defaultOrder != (UInt32)(Int32)-1) + if (i <= defaultOrder || curSel <= 0) + curSel = index; + } + m_Order.SetCurSel(curSel); + break; + } + + // case kBZip2: + default: + break; + } +} + +bool CCompressDialog::GetOrderMode() +{ + switch (GetMethodID()) + { + case kPPMd: + case kPPMdZip: + return true; + } + return false; +} + + +static UInt64 Get_Lzma2_ChunkSize(UInt64 dict) +{ + // we use same default chunk sizes as defined in 7z encoder and lzma2 encoder + UInt64 cs = (UInt64)dict << 2; + const UInt32 kMinSize = (UInt32)1 << 20; + const UInt32 kMaxSize = (UInt32)1 << 28; + if (cs < kMinSize) cs = kMinSize; + if (cs > kMaxSize) cs = kMaxSize; + if (cs < dict) cs = dict; + cs += (kMinSize - 1); + cs &= ~(UInt64)(kMinSize - 1); + return cs; +} + + +static void Add_Size(AString &s, UInt64 val) +{ + unsigned moveBits = 0; + char c = 0; + if ((val & 0x3FFFFFFF) == 0) { moveBits = 30; c = 'G'; } + else if ((val & 0xFFFFF) == 0) { moveBits = 20; c = 'M'; } + else if ((val & 0x3FF) == 0) { moveBits = 10; c = 'K'; } + s.Add_UInt64(val >> moveBits); + s.Add_Space(); + if (moveBits != 0) + s.Add_Char(c); + s.Add_Char('B'); +} + + +void CCompressDialog::SetSolidBlockSize2() +{ + m_Solid.ResetContent(); + _auto_Solid = 1 << 20; + + const CFormatInfo &fi = g_Formats[GetStaticFormatIndex()]; + if (!fi.Solid_()) + return; + + const UInt32 level = GetLevel2(); + if (level == 0) + return; + + UInt64 dict = GetDict2(); + if (dict == (UInt64)(Int64)-1) + { + dict = 1 << 25; // default dict for unknown methods + // return; + } + + + UInt32 defaultBlockSize = (UInt32)(Int32)-1; + + const CArcInfoEx &ai = Get_ArcInfoEx(); + + /* + if (usePrevDictionary) + defaultBlockSize = GetBlockSizeSpec(); + else + */ + { + const int index = FindRegistryFormat(ai.Name); + if (index >= 0) + { + const NCompression::CFormatOptions &fo = m_RegistryInfo.Formats[index]; + if (IsMethodEqualTo(fo.Method)) + defaultBlockSize = fo.BlockLogSize; + } + } + + const bool is7z = ai.Is_7z(); + + const UInt64 cs = Get_Lzma2_ChunkSize(dict); + + // Solid Block Size + UInt64 blockSize = cs; // for xz + + if (is7z) + { + // we use same default block sizes as defined in 7z encoder + UInt64 kMaxSize = (UInt64)1 << 32; + const int methodId = GetMethodID(); + if (methodId == kLZMA2) + { + blockSize = cs << 6; + kMaxSize = (UInt64)1 << 34; + } + else + { + UInt64 dict2 = dict; + if (methodId == kBZip2) + { + dict2 /= 100000; + if (dict2 < 1) + dict2 = 1; + dict2 *= 100000; + } + blockSize = dict2 << 7; + } + + const UInt32 kMinSize = (UInt32)1 << 24; + if (blockSize < kMinSize) blockSize = kMinSize; + if (blockSize > kMaxSize) blockSize = kMaxSize; + } + + _auto_Solid = blockSize; + + int curSel; + { + AString s; + Add_Size(s, _auto_Solid); + Modify_Auto(s); + curSel = (int)ComboBox_AddStringAscii_SetItemData(m_Solid, s, (LPARAM)(UInt32)(Int32)-1); + } + + if (is7z) + { + UString s ('-'); + // kSolidLog_NoSolid = 0 for xz means default blockSize + if (is7z) + LangString(IDS_COMPRESS_NON_SOLID, s); + const int index = (int)m_Solid.AddString_SetItemData(s, (LPARAM)(UInt32)kSolidLog_NoSolid); + if (defaultBlockSize == kSolidLog_NoSolid) + curSel = index; + } + + for (unsigned i = 20; i <= 36; i++) + { + AString s; + Add_Size(s, (UInt64)1 << i); + const int index = (int)ComboBox_AddStringAscii_SetItemData(m_Solid, s, (LPARAM)(UInt32)i); + if (defaultBlockSize != (UInt32)(Int32)-1) + if (i <= defaultBlockSize || index <= 1) + curSel = index; + } + + { + const int index = (int)m_Solid.AddString_SetItemData( + LangString(IDS_COMPRESS_SOLID), (LPARAM)kSolidLog_FullSolid); + if (defaultBlockSize == kSolidLog_FullSolid) + curSel = index; + } + + m_Solid.SetCurSel(curSel); +} + + +/* +static void ZstdEncProps_SetDictProps_From_CompressDialog(CZstdEncProps *props, CCompressDialog &cd) +{ + { + const UInt64 d64 = cd.GetDictSpec(); + UInt32 d32 = 0; // 0 is default for ZstdEncProps::windowLog + if (d64 != (UInt64)(Int64)-1) + { + d32 = (UInt32)d64; + if (d32 != d64) + d32 = (UInt32)(Int32)-2; + } + ZstdEncProps_Set_WindowSize(props, d32); + } + { + const UInt64 d64 = cd.GetDictChainSpec(); + UInt32 d32 = 0; // 0 is default for ZstdEncProps::windowLog_Chain + if (d64 != (UInt64)(Int64)-1) + { + d32 = (UInt32)d64; + if (d32 != d64) + d32 = (UInt32)(Int32)-2; + } + ZstdEncProps_Set_WindowChainSize(props, d32); + } +} + +static bool Is_Zstd_Mt_Supported() +{ + if (!GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "InitializeConditionVariable")) + return false; + return true; +} +*/ + +static const char * const k_ST_Threads = " (ST)"; + +void CCompressDialog::SetNumThreads2() +{ + _auto_NumThreads = 1; + + m_NumThreads.ResetContent(); + const CFormatInfo &fi = g_Formats[GetStaticFormatIndex()]; + if (!fi.MultiThread_()) + return; + + UInt32 numCPUs = 1; // process threads + UInt32 numHardwareThreads = 1; // system threads + NSystem::CProcessAffinity threadsInfo; + threadsInfo.InitST(); +#ifndef Z7_ST + threadsInfo.Get_and_return_NumProcessThreads_and_SysThreads(numCPUs, numHardwareThreads); +#endif + + AString s ("/ "); + { + s.Add_UInt32(numCPUs); + if (numCPUs != numHardwareThreads) + { + s += " / "; + s.Add_UInt32(numHardwareThreads); + } + SetItemTextA(IDT_COMPRESS_HARDWARE_THREADS, s.Ptr()); + } + + UInt32 defaultValue = numCPUs; + bool useAutoThreads = true; + + { + const CArcInfoEx &ai = Get_ArcInfoEx(); + const int index = FindRegistryFormat(ai.Name); + if (index >= 0) + { + const NCompression::CFormatOptions &fo = m_RegistryInfo.Formats[index]; + if (IsMethodEqualTo(fo.Method) && fo.NumThreads != (UInt32)(Int32)-1) + { + defaultValue = fo.NumThreads; + useAutoThreads = false; + } + } + } + + // const UInt32 num_ZSTD_threads_MAX = Is_Zstd_Mt_Supported() ? MY_ZSTDMT_NBWORKERS_MAX : 0; + + const int methodID = GetMethodID(); + const bool isZip = IsZipFormat(); + + UInt32 numAlgoThreadsMax = numHardwareThreads * 2; // for unknow methods + if (isZip) + numAlgoThreadsMax = + 8 << (sizeof(size_t) / 2); // 32 threads for 32-bit : 128 threads for 64-bit + else if (IsXzFormat()) + numAlgoThreadsMax = 256 * 2; // MTCODER_THREADS_MAX * 2 + else switch (methodID) + { + case kLZMA: numAlgoThreadsMax = 2; break; + case kLZMA2: numAlgoThreadsMax = 256 * 2; break; // MTCODER_THREADS_MAX * 2 + case kBZip2: numAlgoThreadsMax = 64; break; + // case kZSTD: numAlgoThreadsMax = num_ZSTD_threads_MAX; break; + case kCopy: + case kPPMd: + case kDeflate: + case kDeflate64: + case kPPMdZip: + numAlgoThreadsMax = 1; + } + UInt32 autoThreads = numCPUs; + if (autoThreads > numAlgoThreadsMax) + autoThreads = numAlgoThreadsMax; + + const UInt64 memUse_Limit = Get_MemUse_Bytes(); + + if (_ramSize_Defined) + if (autoThreads > 1 + // || (autoThreads == 0 && methodID == kZSTD) + ) + { + if (isZip) + { + for (; autoThreads > 1; autoThreads--) + { + const UInt64 dict64 = GetDict2(); + UInt64 decompressMemory; + const UInt64 usage = GetMemoryUsage_Threads_Dict_DecompMem(autoThreads, dict64, decompressMemory); + if (usage <= memUse_Limit) + break; + } + } + else if (methodID == kLZMA2) + { + const UInt64 dict64 = GetDict2(); + const UInt32 numThreads1 = (GetLevel2() >= 5 ? 2 : 1); + UInt32 numBlockThreads = autoThreads / numThreads1; + for (; numBlockThreads > 1; numBlockThreads--) + { + autoThreads = numBlockThreads * numThreads1; + UInt64 decompressMemory; + const UInt64 usage = GetMemoryUsage_Threads_Dict_DecompMem(autoThreads, dict64, decompressMemory); + if (usage <= memUse_Limit) + break; + } + autoThreads = numBlockThreads * numThreads1; + } + /* + else if (methodID == kZSTD) + { + if (num_ZSTD_threads_MAX != 0) + { + CZstdEncProps props; + ZstdEncProps_Init(&props); + // props.level_zstd = level; + props.level_7z = GetLevel2(); + ZstdEncProps_SetDictProps_From_CompressDialog(&props, *this); + autoThreads = ZstdEncProps_GetNumThreads_for_MemUsageLimit(&props, memUse_Limit, autoThreads); + } + } + */ + } + + _auto_NumThreads = autoThreads; + + int curSel = -1; + { + s.Empty(); + s.Add_UInt32(autoThreads); + if (autoThreads == 0) s += k_ST_Threads; + Modify_Auto(s); + const int index = (int)ComboBox_AddStringAscii_SetItemData(m_NumThreads, + s, (LPARAM)(INT_PTR)(-1)); + if (useAutoThreads) + curSel = index; + } + + if (numAlgoThreadsMax != autoThreads || autoThreads != 1) + for (UInt32 i = + // (methodID == kZSTD) ? 0 : + 1; + i <= numHardwareThreads * 2 && i <= numAlgoThreadsMax; i++) + { + s.Empty(); + s.Add_UInt32(i); + if (i == 0) s += k_ST_Threads; + const int index = (int)ComboBox_AddStringAscii_SetItemData(m_NumThreads, + s, (LPARAM)(UInt32)i); + if (!useAutoThreads && i == defaultValue) + curSel = index; + } + + m_NumThreads.SetCurSel(curSel); +} + + +static void AddMemSize(UString &res, UInt64 size) +{ + char c; + unsigned moveBits = 0; + if (size >= ((UInt64)1 << 31) && (size & 0x3FFFFFFF) == 0) + { moveBits = 30; c = 'G'; } + else // if (size >= ((UInt32)1 << 21) && (size & 0xFFFFF) == 0) + { moveBits = 20; c = 'M'; } + // else { moveBits = 10; c = 'K'; } + res.Add_UInt64(size >> moveBits); + res.Add_Space(); + if (moveBits != 0) + res.Add_Char(c); + res.Add_Char('B'); +} + + +int CCompressDialog::AddMemComboItem(UInt64 val, bool isPercent, bool isDefault) +{ + UString sUser; + UString sRegistry; + if (isPercent) + { + UString s; + s.Add_UInt64(val); + s.Add_Char('%'); + if (isDefault) + sUser = k_Auto_Prefix; + else + sRegistry = s; + sUser += s; + } + else + { + AddMemSize(sUser, val); + sRegistry = sUser; + for (;;) + { + const int pos = sRegistry.Find(L' '); + if (pos < 0) + break; + sRegistry.Delete(pos); + } + if (!sRegistry.IsEmpty()) + if (sRegistry.Back() == 'B') + sRegistry.DeleteBack(); + } + const unsigned dataIndex = _memUse_Strings.Add(sRegistry); + return (int)m_MemUse.AddString_SetItemData(sUser, (LPARAM)dataIndex); +} + + + +void CCompressDialog::SetMemUseCombo() +{ + _memUse_Strings.Clear(); + m_MemUse.ResetContent(); + const CFormatInfo &fi = g_Formats[GetStaticFormatIndex()]; + + { + const bool enable = fi.MemUse_(); + ShowItem_Bool(IDT_COMPRESS_MEMORY, enable); + ShowItem_Bool(IDT_COMPRESS_MEMORY_VALUE, enable); + ShowItem_Bool(IDT_COMPRESS_MEMORY_DE, enable); + ShowItem_Bool(IDT_COMPRESS_MEMORY_DE_VALUE, enable); + ShowItem_Bool(IDC_COMPRESS_MEM_USE, enable); + EnableItem(IDC_COMPRESS_MEM_USE, enable); + if (!enable) + return; + } + + UInt64 curMem_Bytes = 0; + UInt64 curMem_Percents = 0; + bool needSetCur_Bytes = false; + bool needSetCur_Percents = false; + { + const NCompression::CFormatOptions &fo = Get_FormatOptions(); + if (!fo.MemUse.IsEmpty()) + { + NCompression::CMemUse mu; + mu.Parse(fo.MemUse); + if (mu.IsDefined) + { + if (mu.IsPercent) + { + curMem_Percents = mu.Val; + needSetCur_Percents = true; + } + else + { + curMem_Bytes = mu.GetBytes(_ramSize_Reduced); + needSetCur_Bytes = true; + } + } + } + } + + + // 80% - is auto usage limit in handlers + AddMemComboItem(80, true, true); + m_MemUse.SetCurSel(0); + + { + for (unsigned i = 10;; i += 10) + { + UInt64 size = i; + if (i > 100) + size = (UInt64)(Int64)-1; + if (needSetCur_Percents && size >= curMem_Percents) + { + const int index = AddMemComboItem(curMem_Percents, true); + m_MemUse.SetCurSel(index); + needSetCur_Percents = false; + if (size == curMem_Percents) + continue; + } + if (size == (UInt64)(Int64)-1) + break; + AddMemComboItem(size, true); + } + } + { + for (unsigned i = (27) * 2;; i++) + { + UInt64 size = (UInt64)(2 + (i & 1)) << (i / 2); + if (i > (20 + sizeof(size_t) * 3 - 1) * 2) + size = (UInt64)(Int64)-1; + if (needSetCur_Bytes && size >= curMem_Bytes) + { + const int index = AddMemComboItem(curMem_Bytes); + m_MemUse.SetCurSel(index); + needSetCur_Bytes = false; + if (size == curMem_Bytes) + continue; + } + if (size == (UInt64)(Int64)-1) + break; + AddMemComboItem(size); + } + } +} + + +UString CCompressDialog::Get_MemUse_Spec() +{ + if (m_MemUse.GetCount() < 1) + return UString(); + return _memUse_Strings[(unsigned)m_MemUse.GetItemData_of_CurSel()]; +} + + +UInt64 CCompressDialog::Get_MemUse_Bytes() +{ + const UString mus = Get_MemUse_Spec(); + NCompression::CMemUse mu; + if (!mus.IsEmpty()) + { + mu.Parse(mus); + if (mu.IsDefined) + return mu.GetBytes(_ramSize_Reduced); + } + return _ramUsage_Auto; // _ramSize_Reduced; // _ramSize;; +} + + + +UInt64 CCompressDialog::GetMemoryUsage_DecompMem(UInt64 &decompressMemory) +{ + return GetMemoryUsage_Dict_DecompMem(GetDict2(), decompressMemory); +} + + +/* +we could use that function to reduce the dictionary if small RAM +UInt64 CCompressDialog::GetMemoryUsageComp_Threads_Dict(UInt32 numThreads, UInt64 dict64) +{ + UInt64 decompressMemory; + return GetMemoryUsage_Threads_Dict_DecompMem(numThreads, dict64, decompressMemory); +} +*/ + + +UInt64 CCompressDialog::GetMemoryUsage_Dict_DecompMem(UInt64 dict64, UInt64 &decompressMemory) +{ + return GetMemoryUsage_Threads_Dict_DecompMem(GetNumThreads2(), dict64, decompressMemory); +} + +UInt64 CCompressDialog::GetMemoryUsage_Threads_Dict_DecompMem(UInt32 numThreads, UInt64 dict64, UInt64 &decompressMemory) +{ + decompressMemory = (UInt64)(Int64)-1; + + const UInt32 level = GetLevel2(); + if (level == 0 && !Get_ArcInfoEx().Is_Zstd()) + { + decompressMemory = (1 << 20); + return decompressMemory; + } + UInt64 size = 0; + + const CFormatInfo &fi = g_Formats[GetStaticFormatIndex()]; + if (fi.Filter_() && level >= 9) + size += (12 << 20) * 2 + (5 << 20); + // UInt32 numThreads = GetNumThreads2(); + + UInt32 numMainZipThreads = 1; + + if (IsZipFormat()) + { + UInt32 numSubThreads = 1; + if (GetMethodID() == kLZMA && numThreads > 1 && level >= 5) + numSubThreads = 2; + numMainZipThreads = numThreads / numSubThreads; + if (numMainZipThreads > 1) + size += (UInt64)numMainZipThreads * ((size_t)sizeof(size_t) << 23); + else + numMainZipThreads = 1; + } + + const int methodId = GetMethodID(); + + if (dict64 == (UInt64)(Int64)-1 + // && methodId != kZSTD + ) + return (UInt64)(Int64)-1; + + + switch (methodId) + { + case kLZMA: + case kLZMA2: + { + const UInt32 dict = (dict64 >= kLzmaMaxDictSize ? kLzmaMaxDictSize : (UInt32)dict64); + UInt32 hs = dict - 1; + hs |= (hs >> 1); + hs |= (hs >> 2); + hs |= (hs >> 4); + hs |= (hs >> 8); + hs >>= 1; + if (hs >= (1 << 24)) + hs >>= 1; + hs |= (1 << 16) - 1; + // if (numHashBytes >= 5) + if (level < 5) + hs |= (256 << 10) - 1; + hs++; + UInt64 size1 = (UInt64)hs * 4; + size1 += (UInt64)dict * 4; + if (level >= 5) + size1 += (UInt64)dict * 4; + size1 += (2 << 20); + + UInt32 numThreads1 = 1; + if (numThreads > 1 && level >= 5) + { + size1 += (2 << 20) + (4 << 20); + numThreads1 = 2; + } + + UInt32 numBlockThreads = numThreads / numThreads1; + + UInt64 chunkSize = 0; // it's solid chunk + + if (methodId != kLZMA && numBlockThreads != 1) + { + chunkSize = Get_Lzma2_ChunkSize(dict); + + if (IsXzFormat()) + { + UInt32 blockSizeLog = GetBlockSizeSpec(); + if (blockSizeLog != (UInt32)(Int32)-1) + { + if (blockSizeLog == kSolidLog_FullSolid) + { + numBlockThreads = 1; + chunkSize = 0; + } + else if (blockSizeLog != kSolidLog_NoSolid) + chunkSize = (UInt64)1 << blockSizeLog; + } + } + } + + if (chunkSize == 0) + { + const UInt32 kBlockSizeMax = (UInt32)0 - (UInt32)(1 << 16); + UInt64 blockSize = (UInt64)dict + (1 << 16) + + (numThreads1 > 1 ? (1 << 20) : 0); + blockSize += (blockSize >> (blockSize < ((UInt32)1 << 30) ? 1 : 2)); + if (blockSize >= kBlockSizeMax) + blockSize = kBlockSizeMax; + size += numBlockThreads * (size1 + blockSize); + } + else + { + size += numBlockThreads * (size1 + chunkSize); + const UInt32 numPackChunks = numBlockThreads + (numBlockThreads / 8) + 1; + if (chunkSize < ((UInt32)1 << 26)) numBlockThreads++; + if (chunkSize < ((UInt32)1 << 24)) numBlockThreads++; + if (chunkSize < ((UInt32)1 << 22)) numBlockThreads++; + size += numPackChunks * chunkSize; + } + + decompressMemory = dict + (2 << 20); + return size; + } + + /* + case kZSTD: + { + CZstdEncProps props; + ZstdEncProps_Init(&props); + // props.level_zstd = level; + props.level_7z = level; + props.nbWorkers = numThreads; + ZstdEncProps_SetDictProps_From_CompressDialog(&props, *this); + ZstdEncProps_NormalizeFull(&props); + size = ZstdEncProps_GetMemUsage(&props); + decompressMemory = (UInt64)1 << props.windowLog; + return size; + } + */ + + case kPPMd: + { + decompressMemory = dict64 + (2 << 20); + return size + decompressMemory; + } + + case kDeflate: + case kDeflate64: + { + UInt64 size1 = 3 << 20; + // if (level >= 7) + size1 += (1 << 20); + size += size1 * numMainZipThreads; + decompressMemory = (2 << 20); + return size; + } + + case kBZip2: + { + decompressMemory = (7 << 20); + UInt64 memForOneThread = (10 << 20); + return size + memForOneThread * numThreads; + } + + case kPPMdZip: + { + decompressMemory = dict64 + (2 << 20); + return size + (UInt64)decompressMemory * numThreads; + } + } + + return (UInt64)(Int64)-1; +} + + + +static void AddMemUsage(UString &s, UInt64 v) +{ + const char *post; + if (v <= ((UInt64)16 << 30)) + { + v = (v + (1 << 20) - 1) >> 20; + post = "MB"; + } + else if (v <= ((UInt64)64 << 40)) + { + v = (v + (1 << 30) - 1) >> 30; + post = "GB"; + } + else + { + const UInt64 v2 = v + ((UInt64)1 << 40) - 1; + if (v <= v2) + v = v2; + v >>= 40; + post = "TB"; + } + s.Add_UInt64(v); + s.Add_Space(); + s += post; +} + + +void CCompressDialog::PrintMemUsage(UINT res, UInt64 value) +{ + if (value == (UInt64)(Int64)-1) + { + SetItemText(res, TEXT("?")); + return; + } + UString s; + AddMemUsage(s, value); + if (res == IDT_COMPRESS_MEMORY_VALUE) + { + const UString mus = Get_MemUse_Spec(); + NCompression::CMemUse mu; + if (!mus.IsEmpty()) + mu.Parse(mus); + if (mu.IsDefined) + { + s += " / "; + AddMemUsage(s, mu.GetBytes(_ramSize_Reduced)); + } + else if (_ramSize_Defined) + { + s += " / "; + AddMemUsage(s, _ramUsage_Auto); + } + + if (_ramSize_Defined) + { + s += " / "; + AddMemUsage(s, _ramSize); + } + } + SetItemText(res, s); +} + + +void CCompressDialog::SetMemoryUsage() +{ + UInt64 decompressMem; + const UInt64 memUsage = GetMemoryUsage_DecompMem(decompressMem); + PrintMemUsage(IDT_COMPRESS_MEMORY_VALUE, memUsage); + PrintMemUsage(IDT_COMPRESS_MEMORY_DE_VALUE, decompressMem); + #ifdef PRINT_PARAMS + Print_Params(); + #endif +} + + + +#ifdef PRINT_PARAMS + +static const char kPropDelimeter = ' '; // ':' + +static void AddPropName(AString &s, const char *name) +{ + if (!s.IsEmpty()) + s += kPropDelimeter; + s += name; +} + +static void AddProp(AString &s, const char *name, unsigned v) +{ + AddPropName(s, name); + s.Add_UInt32(v); +} + +static void AddProp_switch(AString &s, const char *name, E_ZSTD_paramSwitch_e e) +{ + AddPropName(s, name); + s += e == k_ZSTD_ps_enable ? "" : "-"; +} + +static void PrintPropAsLog(AString &s, const char *name, size_t v) +{ + AddPropName(s, name); + for (unsigned i = 0; i < sizeof(size_t) * 8; i++) + { + if (((size_t)1 << i) == v) + { + s.Add_UInt32(i); + return; + } + } + char c = 'b'; + if ((v & 0x3FFFFFFF) == 0) { v >>= 30; c = 'G'; } + else if ((v & 0xFFFFF) == 0) { v >>= 20; c = 'M'; } + else if ((v & 0x3FF) == 0) { v >>= 10; c = 'K'; } + s.Add_UInt64(v); + s += c; +} + +static void ZstdEncProps_Print(CZstdEncProps *props, AString &s) +{ + if (props->level_zstd >= 0) + AddProp(s, "zx", props->level_zstd); + else + AddProp(s, "zf", -(props->level_zstd)); + AddProp(s, "a", props->strategy); + AddProp(s, "d", props->windowLog); + AddProp(s, "zclog", props->chainLog); + AddProp(s, "zhb", props->hashLog); + AddProp(s, "mml", props->minMatch); + AddProp(s, "mcb", props->searchLog); + AddProp(s, "fb", props->targetLength); + AddProp(s, "mt", props->nbWorkers); + PrintPropAsLog(s, "c", props->jobSize); + AddProp(s, "zov", props->overlapLog); + PrintPropAsLog(s, "ztps", props->targetPrefixSize); + AddProp_switch(s, "zmfr", props->useRowMatchFinder); + if (props->ldmParams.enableLdm == k_ZSTD_ps_enable) + { + AddProp_switch(s, "zle", props->ldmParams.enableLdm); + AddProp(s, "zlhb", props->ldmParams.hashLog); + AddProp(s, "zlbb", props->ldmParams.bucketSizeLog); + AddProp(s, "zlmml", props->ldmParams.minMatchLength); + AddProp(s, "zlhrb", props->ldmParams.hashRateLog); + } +} + +void CCompressDialog::Print_Params() +{ + { + CZstdEncProps props; + ZstdEncProps_Init(&props); + // props.level_zstd = level; + props.level_7z = GetLevel2(); + ZstdEncProps_SetDictProps_From_CompressDialog(&props, *this); + { + UInt32 order = GetOrderSpec(); + if (order != (UInt32)(Int32)-1) + props.targetLength = GetOrderSpec(); + } + props.nbWorkers = GetNumThreads2(); + // props.windowLog = 18; // for debug + ZstdEncProps_NormalizeFull(&props); + AString s; + ZstdEncProps_Print(&props, s); + SetItemTextA(IDT_COMPRESS_PARAMS_INFO, s); + } +} + +#endif // PRINT_PARAMS + + + +void CCompressDialog::SetParams() +{ + const CArcInfoEx &ai = Get_ArcInfoEx(); + m_Params.SetText(TEXT("")); + const int index = FindRegistryFormat(ai.Name); + if (index >= 0) + { + const NCompression::CFormatOptions &fo = m_RegistryInfo.Formats[index]; + m_Params.SetText(fo.Options); + } +} + +void CCompressDialog::SaveOptionsInMem() +{ + /* these options are for (Info.FormatIndex). + If it's called just after format changing, + then it's format that was selected before format changing + So we store previous format properties */ + + m_Params.GetText(Info.Options); + Info.Options.Trim(); + + const CArcInfoEx &ai = (*ArcFormats)[Info.FormatIndex]; + const unsigned index = FindRegistryFormat_Always(ai.Name); + NCompression::CFormatOptions &fo = m_RegistryInfo.Formats[index]; + fo.Options = Info.Options; + fo.Level = GetLevelSpec(); + { + const UInt64 dict64 = GetDictSpec(); + UInt32 dict32; + if (dict64 == (UInt64)(Int64)-1) + dict32 = (UInt32)(Int32)-1; + else + { + dict32 = (UInt32)dict64; + if (dict64 != dict32) + { + /* here we must write 32-bit value for registry that indicates big_value + (UInt32)(Int32)-1 : is used as marker for default size + (UInt32)(Int32)-2 : it can be used to indicate big value (4 GiB) + the value must be larger than threshold + */ + dict32 = (UInt32)(Int32)-2; + // dict32 = kLzmaMaxDictSize; // it must be larger than threshold + } + } + fo.Dictionary = dict32; + } + /* + { + const UInt64 dict64 = GetDictChainSpec(); + UInt32 dict32; + if (dict64 == (UInt64)(Int64)-1) + dict32 = (UInt32)(Int32)-1; + else + { + dict32 = (UInt32)dict64; + if (dict64 != dict32) + { + dict32 = (UInt32)(Int32)-2; + // dict32 = k_Zstd_MAX_DictSize; // it must be larger than threshold + } + } + fo.DictionaryChain = dict32; + } + */ + + fo.Order = GetOrderSpec(); + fo.Method = GetMethodSpec(); + fo.EncryptionMethod = GetEncryptionMethodSpec(); + fo.NumThreads = GetNumThreadsSpec(); + fo.BlockLogSize = GetBlockSizeSpec(); + fo.MemUse = Get_MemUse_Spec(); +} + + +unsigned CCompressDialog::GetFormatIndex() +{ + return (unsigned)m_Format.GetItemData_of_CurSel(); +} + + + +static void AddText_from_BoolPair(AString &s, const char *name, const CBoolPair &bp) +{ + if (bp.Def) + { + s.Add_OptSpaced(name); + if (!bp.Val) + s += "-"; + } + /* + else if (bp.Val) + { + s.Add_OptSpaced("["); + s += name; + s += "]"; + } + */ +} + + +static void AddText_from_Bool1(AString &s, const char *name, const CBool1 &b) +{ + if (b.Supported && b.Val) + s.Add_OptSpaced(name); +} + + +void CCompressDialog::ShowOptionsString() +{ + NCompression::CFormatOptions &fo = Get_FormatOptions(); + + AString s; + if (fo.IsSet_TimePrec()) + { + s.Add_OptSpaced("tp"); + s.Add_UInt32(fo.TimePrec); + } + AddText_from_BoolPair(s, "tm", fo.MTime); + AddText_from_BoolPair(s, "tc", fo.CTime); + AddText_from_BoolPair(s, "ta", fo.ATime); + AddText_from_BoolPair(s, "-stl", fo.SetArcMTime); + + // const CArcInfoEx &ai = Get_ArcInfoEx(); + AddText_from_Bool1(s, "SL", SymLinks); + AddText_from_Bool1(s, "HL", HardLinks); + AddText_from_Bool1(s, "AS", AltStreams); + AddText_from_Bool1(s, "Sec", NtSecurity); + + // AddText_from_Bool1(s, "Preserve", PreserveATime); + + SetItemText(IDT_COMPRESS_OPTIONS, GetUnicodeString(s)); +} + + + + + +// ---------- OPTIONS ---------- + + +void COptionsDialog::CheckButton_Bool1(UINT id, const CBool1 &b1) +{ + CheckButton(id, b1.Val); +} + +void COptionsDialog::GetButton_Bool1(UINT id, CBool1 &b1) +{ + b1.Val = IsButtonCheckedBool(id); +} + + +void COptionsDialog::CheckButton_BoolBox( + bool supported, const CBoolPair &b2, CBoolBox &bb) +{ + const bool isSet = b2.Def; + const bool val = isSet ? b2.Val : bb.DefaultVal; + + bb.IsSupported = supported; + + CheckButton (bb.Set_Id, isSet); + ShowItem_Bool (bb.Set_Id, supported); + CheckButton (bb.Id, val); + EnableItem (bb.Id, isSet); + ShowItem_Bool (bb.Id, supported); +} + +void COptionsDialog::GetButton_BoolBox(CBoolBox &bb) +{ + // we save value for invisible buttons too + bb.BoolPair.Val = IsButtonCheckedBool (bb.Id); + bb.BoolPair.Def = IsButtonCheckedBool (bb.Set_Id); +} + + +void COptionsDialog::Store_TimeBoxes() +{ + TimePrec = GetPrecSpec(); + GetButton_BoolBox (MTime); + GetButton_BoolBox (CTime); + GetButton_BoolBox (ATime); + GetButton_BoolBox (ZTime); +} + + +UInt32 COptionsDialog::GetComboValue(NWindows::NControl::CComboBox &c, int defMax) +{ + if (c.GetCount() <= defMax) + return (UInt32)(Int32)-1; + return (UInt32)c.GetItemData_of_CurSel(); +} + +static const unsigned kTimePrec_Win = 0; +static const unsigned kTimePrec_Unix = 1; +static const unsigned kTimePrec_DOS = 2; +static const unsigned kTimePrec_1ns = 3; + +static void AddTimeOption(UString &s, UInt32 val, const UString &unit, const char *sys = NULL) +{ + // s += " : "; + s.Add_UInt32(val); + s.Add_Space(); + s += unit; + if (sys) + { + s += " : "; + s += sys; + } +} + +int COptionsDialog::AddPrec(unsigned prec, bool isDefault) +{ + UString s; + UInt32 writePrec = prec; + if (isDefault) + { + // s += "* "; + // writePrec = (UInt32)(Int32)-1; + } + if (prec == kTimePrec_Win) AddTimeOption(s, 100, NsString, "Windows"); + else if (prec == kTimePrec_Unix) AddTimeOption(s, 1, SecString, "Unix"); + else if (prec == kTimePrec_DOS) AddTimeOption(s, 2, SecString, "DOS"); + else if (prec == kTimePrec_1ns) AddTimeOption(s, 1, NsString, "Linux"); + else if (prec == k_PropVar_TimePrec_Base) AddTimeOption(s, 1, SecString); + else if (prec >= k_PropVar_TimePrec_Base) + { + UInt32 d = 1; + for (unsigned i = prec; i < k_PropVar_TimePrec_Base + 9; i++) + d *= 10; + AddTimeOption(s, d, NsString); + } + else + s.Add_UInt32(prec); + return (int)m_Prec.AddString_SetItemData(s, (LPARAM)writePrec); +} + + +void COptionsDialog::SetPrec() +{ + // const CFormatInfo &fi = g_Formats[cd->GetStaticFormatIndex()]; + const CArcInfoEx &ai = cd->Get_ArcInfoEx(); + + // UInt32 flags = fi.Flags; + + UInt32 flags = ai.Get_TimePrecFlags(); + UInt32 defaultPrec = ai.Get_DefaultTimePrec(); + if (defaultPrec != 0) + flags |= ((UInt32)1 << defaultPrec); + + // const NCompression::CFormatOptions &fo = cd->Get_FormatOptions(); + + // unsigned defaultPrec = kTimePrec_Win; + + if (ai.Is_GZip()) + defaultPrec = kTimePrec_Unix; + + { + UString s; + s += GetNameOfProperty(kpidType, L"type"); + s += ": "; + s += ai.Name; + if (ai.Is_Tar()) + { + const int methodID = cd->GetMethodID(); + + // for debug + // defaultPrec = kTimePrec_Unix; + // flags = (UInt32)1 << kTimePrec_Unix; + + s.Add_Colon(); + if (methodID >= 0 && (unsigned)methodID < Z7_ARRAY_SIZE(kMethodsNames)) + s += kMethodsNames[methodID]; + if (methodID == kPosix) + { + // for debug + // flags |= (UInt32)1 << kTimePrec_Win; + // flags |= (UInt32)1 << kTimePrec_1ns; + } + } + else + { + // if (is_for_MethodChanging) return; + } + + SetItemText(IDT_COMPRESS_TIME_INFO, s); + } + + m_Prec.ResetContent(); + _auto_Prec = defaultPrec; + + unsigned selectedPrec = defaultPrec; + { + // if (TimePrec >= kTimePrec_Win && TimePrec <= kTimePrec_DOS) + if ((Int32)TimePrec >= 0) + selectedPrec = TimePrec; + } + + int curSel = -1; + int defaultPrecIndex = -1; + for (unsigned prec = 0; + // prec <= k_PropVar_TimePrec_HighPrec; + prec <= k_PropVar_TimePrec_1ns; + prec++) + { + if (((flags >> prec) & 1) == 0) + continue; + const bool isDefault = (defaultPrec == prec); + const int index = AddPrec(prec, isDefault); + if (isDefault) + defaultPrecIndex = index; + if (selectedPrec == prec) + curSel = index; + } + + if (curSel < 0 && selectedPrec > kTimePrec_DOS) + curSel = AddPrec(selectedPrec, false); // isDefault + if (curSel < 0) + curSel = defaultPrecIndex; + if (curSel >= 0) + m_Prec.SetCurSel(curSel); + + { + const bool isSet = IsSet_TimePrec(); + const int count = m_Prec.GetCount(); + const bool showPrec = (count != 0); + ShowItem_Bool(IDC_COMPRESS_TIME_PREC, showPrec); + ShowItem_Bool(IDT_COMPRESS_TIME_PREC, showPrec); + EnableItem(IDC_COMPRESS_TIME_PREC, isSet && (count > 1)); + + CheckButton(IDX_COMPRESS_PREC_SET, isSet); + const bool setIsSupported = isSet || (count > 1); + EnableItem(IDX_COMPRESS_PREC_SET, setIsSupported); + ShowItem_Bool(IDX_COMPRESS_PREC_SET, setIsSupported); + } + + SetTimeMAC(); +} + + +void COptionsDialog::SetTimeMAC() +{ + const CArcInfoEx &ai = cd->Get_ArcInfoEx(); + + const + bool m_allow = ai.Flags_MTime(); + bool c_allow = ai.Flags_CTime(); + bool a_allow = ai.Flags_ATime(); + + if (ai.Is_Tar()) + { + const int methodID = cd->GetMethodID(); + c_allow = false; + a_allow = false; + if (methodID == kPosix) + { + // c_allow = true; // do we need it as change time ? + a_allow = true; + } + } + + if (ai.Is_Zip()) + { + // const int methodID = GetMethodID(); + UInt32 prec = GetPrec(); + if (prec == (UInt32)(Int32)-1) + prec = _auto_Prec; + if (prec != kTimePrec_Win) + { + c_allow = false; + a_allow = false; + } + } + + + /* + MTime.DefaultVal = true; + CTime.DefaultVal = false; + ATime.DefaultVal = false; + */ + + MTime.DefaultVal = ai.Flags_MTime_Default(); + CTime.DefaultVal = ai.Flags_CTime_Default(); + ATime.DefaultVal = ai.Flags_ATime_Default(); + + ZTime.DefaultVal = false; + + const NCompression::CFormatOptions &fo = cd->Get_FormatOptions(); + + CheckButton_BoolBox (m_allow, fo.MTime, MTime ); + CheckButton_BoolBox (c_allow, fo.CTime, CTime ); + CheckButton_BoolBox (a_allow, fo.ATime, ATime ); + CheckButton_BoolBox (true, fo.SetArcMTime, ZTime); + + if (m_allow && !fo.MTime.Def) + { + const bool isSingleFile = ai.Flags_KeepName(); + if (!isSingleFile) + { + // we can hide changing checkboxes for MTime here: + ShowItem_Bool (MTime.Set_Id, false); + EnableItem (MTime.Id, false); + } + } + // On_CheckBoxSet_Prec_Clicked(); + // const bool isSingleFile = ai.Flags_KeepName(); + // mtime for Gz can be +} + + + +void COptionsDialog::On_CheckBoxSet_Prec_Clicked() +{ + const bool isSet = IsButtonCheckedBool(IDX_COMPRESS_PREC_SET); + if (!isSet) + { + // We save current MAC boxes to memory before SetPrec() + Store_TimeBoxes(); + Reset_TimePrec(); + SetPrec(); + } + EnableItem(IDC_COMPRESS_TIME_PREC, isSet); +} + +void COptionsDialog::On_CheckBoxSet_Clicked(const CBoolBox &bb) +{ + const bool isSet = IsButtonCheckedBool(bb.Set_Id); + if (!isSet) + CheckButton(bb.Id, bb.DefaultVal); + EnableItem(bb.Id, isSet); +} + + + + +#ifdef Z7_LANG +static const UInt32 kLangIDs_Options[] = +{ + IDX_COMPRESS_NT_SYM_LINKS, + IDX_COMPRESS_NT_HARD_LINKS, + IDX_COMPRESS_NT_ALT_STREAMS, + IDX_COMPRESS_NT_SECUR, + + IDG_COMPRESS_TIME, + IDT_COMPRESS_TIME_PREC, + IDX_COMPRESS_MTIME, + IDX_COMPRESS_CTIME, + IDX_COMPRESS_ATIME, + IDX_COMPRESS_ZTIME, + IDX_COMPRESS_PRESERVE_ATIME +}; +#endif + + +bool COptionsDialog::OnInit() +{ + #ifdef Z7_LANG + LangSetWindowText(*this, IDB_COMPRESS_OPTIONS); // IDS_OPTIONS + LangSetDlgItems(*this, kLangIDs_Options, Z7_ARRAY_SIZE(kLangIDs_Options)); + // LangSetDlgItemText(*this, IDB_COMPRESS_TIME_DEFAULT, IDB_COMPRESS_TIME_DEFAULT); + // LangSetDlgItemText(*this, IDX_COMPRESS_TIME_DEFAULT, IDX_COMPRESS_TIME_DEFAULT); + #endif + + LangString(IDS_COMPRESS_SEC, SecString); + if (SecString.IsEmpty()) + SecString = "sec"; + LangString(IDS_COMPRESS_NS, NsString); + if (NsString.IsEmpty()) + NsString = "ns"; + + { + // const CArcInfoEx &ai = cd->Get_ArcInfoEx(); + + ShowItem_Bool ( IDX_COMPRESS_NT_SYM_LINKS, cd->SymLinks.Supported); + ShowItem_Bool ( IDX_COMPRESS_NT_HARD_LINKS, cd->HardLinks.Supported); + ShowItem_Bool ( IDX_COMPRESS_NT_ALT_STREAMS, cd->AltStreams.Supported); + ShowItem_Bool ( IDX_COMPRESS_NT_SECUR, cd->NtSecurity.Supported); + + ShowItem_Bool ( IDG_COMPRESS_NTFS, + cd->SymLinks.Supported + || cd->HardLinks.Supported + || cd->AltStreams.Supported + || cd->NtSecurity.Supported); + } + + /* we read property from two sources: + 1) command line : (Info) + 2) registry : (m_RegistryInfo) + (Info) has priority, if both are no defined */ + + CheckButton_Bool1 ( IDX_COMPRESS_NT_SYM_LINKS, cd->SymLinks); + CheckButton_Bool1 ( IDX_COMPRESS_NT_HARD_LINKS, cd->HardLinks); + CheckButton_Bool1 ( IDX_COMPRESS_NT_ALT_STREAMS, cd->AltStreams); + CheckButton_Bool1 ( IDX_COMPRESS_NT_SECUR, cd->NtSecurity); + + CheckButton_Bool1 (IDX_COMPRESS_PRESERVE_ATIME, cd->PreserveATime); + + m_Prec.Attach (GetItem(IDC_COMPRESS_TIME_PREC)); + + MTime.SetIDs ( IDX_COMPRESS_MTIME, IDX_COMPRESS_MTIME_SET); + CTime.SetIDs ( IDX_COMPRESS_CTIME, IDX_COMPRESS_CTIME_SET); + ATime.SetIDs ( IDX_COMPRESS_ATIME, IDX_COMPRESS_ATIME_SET); + ZTime.SetIDs ( IDX_COMPRESS_ZTIME, IDX_COMPRESS_ZTIME_SET); + + { + const NCompression::CFormatOptions &fo = cd->Get_FormatOptions(); + TimePrec = fo.TimePrec; + MTime.BoolPair = fo.MTime; + CTime.BoolPair = fo.CTime; + ATime.BoolPair = fo.ATime; + ZTime.BoolPair = fo.SetArcMTime; + } + + SetPrec(); + + NormalizePosition(); + + return CModalDialog::OnInit(); +} + + +bool COptionsDialog::OnCommand(unsigned code, unsigned itemID, LPARAM lParam) +{ + if (code == CBN_SELCHANGE) + { + switch (itemID) + { + case IDC_COMPRESS_TIME_PREC: + { + Store_TimeBoxes(); + SetTimeMAC(); // for zip/tar + return true; + } + } + } + return CModalDialog::OnCommand(code, itemID, lParam); +} + + +bool COptionsDialog::OnButtonClicked(unsigned buttonID, HWND buttonHWND) +{ + switch (buttonID) + { + case IDX_COMPRESS_PREC_SET: { On_CheckBoxSet_Prec_Clicked(); return true; } + case IDX_COMPRESS_MTIME_SET: { On_CheckBoxSet_Clicked (MTime); return true; } + case IDX_COMPRESS_CTIME_SET: { On_CheckBoxSet_Clicked (CTime); return true; } + case IDX_COMPRESS_ATIME_SET: { On_CheckBoxSet_Clicked (ATime); return true; } + case IDX_COMPRESS_ZTIME_SET: { On_CheckBoxSet_Clicked (ZTime); return true; } + } + return CModalDialog::OnButtonClicked(buttonID, buttonHWND); +} + + +void COptionsDialog::OnOK() +{ + GetButton_Bool1 (IDX_COMPRESS_NT_SYM_LINKS, cd->SymLinks); + GetButton_Bool1 (IDX_COMPRESS_NT_HARD_LINKS, cd->HardLinks); + GetButton_Bool1 (IDX_COMPRESS_NT_ALT_STREAMS, cd->AltStreams); + GetButton_Bool1 (IDX_COMPRESS_NT_SECUR, cd->NtSecurity); + GetButton_Bool1 (IDX_COMPRESS_PRESERVE_ATIME, cd->PreserveATime); + + Store_TimeBoxes(); + { + NCompression::CFormatOptions &fo = cd->Get_FormatOptions(); + fo.TimePrec = TimePrec; + fo.MTime = MTime.BoolPair; + fo.CTime = CTime.BoolPair; + fo.ATime = ATime.BoolPair; + fo.SetArcMTime = ZTime.BoolPair; + } + + CModalDialog::OnOK(); +} + +void COptionsDialog::OnHelp() +{ + ShowHelpWindow(kHelpTopic_Options); +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/GUI/CompressDialog.h b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/CompressDialog.h new file mode 100644 index 00000000..e0f3aa53 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/CompressDialog.h @@ -0,0 +1,483 @@ +// CompressDialog.h + +#ifndef ZIP7_INC_COMPRESS_DIALOG_H +#define ZIP7_INC_COMPRESS_DIALOG_H + +#include "../../../Common/Wildcard.h" + +#include "../../../Windows/Control/ComboBox.h" +#include "../../../Windows/Control/Edit.h" + +#include "../Common/LoadCodecs.h" +#include "../Common/ZipRegistry.h" + +#include "../FileManager/DialogSize.h" + +#include "CompressDialogRes.h" + +namespace NCompressDialog +{ + namespace NUpdateMode + { + enum EEnum + { + kAdd, + kUpdate, + kFresh, + kSync + }; + } + + struct CInfo + { + NUpdateMode::EEnum UpdateMode; + NWildcard::ECensorPathMode PathMode; + + bool SolidIsSpecified; + // bool MultiThreadIsAllowed; + UInt64 SolidBlockSize; + UInt32 NumThreads; + + NCompression::CMemUse MemUsage; + + CRecordVector VolumeSizes; + + UInt32 Level; + UString Method; + UInt64 Dict64; + // UInt64 Dict64_Chain; + bool OrderMode; + UInt32 Order; + UString Options; + + UString EncryptionMethod; + + bool SFXMode; + bool OpenShareForWrite; + bool DeleteAfterCompressing; + + CBoolPair SymLinks; + CBoolPair HardLinks; + CBoolPair AltStreams; + CBoolPair NtSecurity; + + CBoolPair PreserveATime; + + UInt32 TimePrec; + CBoolPair MTime; + CBoolPair CTime; + CBoolPair ATime; + CBoolPair SetArcMTime; + + UString ArcPath; // in: Relative or abs ; out: Relative or abs + + // FString CurrentDirPrefix; + bool KeepName; + + bool GetFullPathName(UString &result) const; + + int FormatIndex; + + UString Password; + bool EncryptHeadersIsAllowed; + bool EncryptHeaders; + + CInfo(): + UpdateMode(NCompressDialog::NUpdateMode::kAdd), + PathMode(NWildcard::k_RelatPath), + SFXMode(false), + OpenShareForWrite(false), + DeleteAfterCompressing(false), + FormatIndex(-1) + { + Level = Order = (UInt32)(Int32)-1; + NumThreads = (UInt32)(Int32)-1; + SolidIsSpecified = false; + Dict64 = (UInt64)(Int64)(-1); + // Dict64_Chain = (UInt64)(Int64)(-1); + OrderMode = false; + Method.Empty(); + Options.Empty(); + EncryptionMethod.Empty(); + TimePrec = (UInt32)(Int32)(-1); + } + }; +} + + +struct CBool1 +{ + bool Val; + bool Supported; + + CBool1(): Val(false), Supported(false) {} + + void Init() + { + Val = false; + Supported = false; + } + + void SetTrueTrue() + { + Val = true; + Supported = true; + } + + void SetVal_as_Supported(bool val) + { + Val = val; + Supported = true; + } + + /* + bool IsVal_True_and_Defined() const + { + return Def && Val; + } + */ +}; + + +class CCompressDialog: public NWindows::NControl::CModalDialog +{ +public: + CBool1 SymLinks; + CBool1 HardLinks; + CBool1 AltStreams; + CBool1 NtSecurity; + CBool1 PreserveATime; +private: + bool _ramSize_Defined; + + NWindows::NControl::CComboBox m_ArchivePath; + NWindows::NControl::CComboBox m_Format; + NWindows::NControl::CComboBox m_Level; + NWindows::NControl::CComboBox m_Method; + NWindows::NControl::CComboBox m_Dictionary; + // NWindows::NControl::CComboBox m_Dictionary_Chain; + NWindows::NControl::CComboBox m_Order; + NWindows::NControl::CComboBox m_Solid; + NWindows::NControl::CComboBox m_NumThreads; + NWindows::NControl::CComboBox m_MemUse; + NWindows::NControl::CComboBox m_Volume; + + int _dictionaryCombo_left; + + UStringVector _memUse_Strings; + + NWindows::NControl::CDialogChildControl m_Params; + + NWindows::NControl::CComboBox m_UpdateMode; + NWindows::NControl::CComboBox m_PathMode; + + NWindows::NControl::CEdit _password1Control; + NWindows::NControl::CEdit _password2Control; + NWindows::NControl::CComboBox _encryptionMethod; + + int _auto_MethodId; + UInt32 _auto_Dict; // (UInt32)(Int32)-1 means unknown + UInt32 _auto_Dict_Chain; // (UInt32)(Int32)-1 means unknown + UInt32 _auto_Order; + UInt64 _auto_Solid; + UInt32 _auto_NumThreads; + + int _default_encryptionMethod_Index; + + int m_PrevFormat; + UString DirPrefix; + UString StartDirPrefix; + + size_t _ramSize; // full RAM size avail + size_t _ramSize_Reduced; // full for 64-bit and reduced for 32-bit + UInt64 _ramUsage_Auto; + +public: + NCompression::CInfo m_RegistryInfo; + + void SetArchiveName(const UString &name); + int FindRegistryFormat(const UString &name); + unsigned FindRegistryFormat_Always(const UString &name); + + const CArcInfoEx &Get_ArcInfoEx() + { + return (*ArcFormats)[GetFormatIndex()]; + } + + NCompression::CFormatOptions &Get_FormatOptions(); + + void CheckSFXNameChange(); + void SetArchiveName2(bool prevWasSFX); + + unsigned GetStaticFormatIndex(); + + void SetNearestSelectComboBox(NWindows::NControl::CComboBox &comboBox, UInt32 value); + + void SetLevel2(); + void SetLevel() + { + SetLevel2(); + EnableMultiCombo(IDC_COMPRESS_LEVEL); + SetMethod(); + } + + void SetMethod2(int keepMethodId); + void SetMethod(int keepMethodId = -1) + { + SetMethod2(keepMethodId); + EnableMultiCombo(IDC_COMPRESS_METHOD); + } + + void MethodChanged() + { + SetDictionary2(); + EnableMultiCombo(IDC_COMPRESS_DICTIONARY); + // EnableMultiCombo(IDC_COMPRESS_DICTIONARY2); + SetOrder2(); + EnableMultiCombo(IDC_COMPRESS_ORDER); + } + + int GetMethodID_RAW(); + int GetMethodID(); + + UString GetMethodSpec(UString &estimatedName); + UString GetMethodSpec(); + bool IsMethodEqualTo(const UString &s); + UString GetEncryptionMethodSpec(); + + bool IsZipFormat(); + bool IsXzFormat(); + + void SetEncryptionMethod(); + + int AddDict2(size_t sizeReal, size_t sizeShow); + int AddDict(size_t size); + // int AddDict_Chain(size_t size); + + void SetDictionary2(); + + UInt32 GetComboValue(NWindows::NControl::CComboBox &c, int defMax = 0); + UInt64 GetComboValue_64(NWindows::NControl::CComboBox &c, int defMax = 0); + + UInt32 GetLevel() { return GetComboValue(m_Level); } + UInt32 GetLevelSpec() { return GetComboValue(m_Level, 1); } + UInt32 GetLevel2(); + + UInt64 GetDictSpec() { return GetComboValue_64(m_Dictionary, 1); } + // UInt64 GetDictChainSpec() { return GetComboValue_64(m_Dictionary_Chain, 1); } + + UInt64 GetDict2() + { + UInt64 num = GetDictSpec(); + if (num == (UInt64)(Int64)-1) + { + if (_auto_Dict == (UInt32)(Int32)-1) + return (UInt64)(Int64)-1; // unknown + num = _auto_Dict; + } + return num; + } + + // UInt32 GetOrder() { return GetComboValue(m_Order); } + UInt32 GetOrderSpec() { return GetComboValue(m_Order, 1); } + UInt32 GetNumThreadsSpec() { return GetComboValue(m_NumThreads, 1); } + + UInt32 GetNumThreads2() + { + UInt32 num = GetNumThreadsSpec(); + if (num == (UInt32)(Int32)-1) + num = _auto_NumThreads; + return num; + } + + UInt32 GetBlockSizeSpec() { return GetComboValue(m_Solid, 1); } + + /* + UInt32 GetPrecSpec() { return GetComboValue(m_Prec, 1); } + UInt32 GetPrec() { return GetComboValue(m_Prec, 0); } + */ + + + int AddOrder(UInt32 size); + int AddOrder_Auto(); + + void SetOrder2(); + + bool GetOrderMode(); + + void SetSolidBlockSize2(); + void SetSolidBlockSize(/* bool useDictionary = false */) + { + SetSolidBlockSize2(); + EnableMultiCombo(IDC_COMPRESS_SOLID); + } + + void SetNumThreads2(); + void SetNumThreads() + { + SetNumThreads2(); + EnableMultiCombo(IDC_COMPRESS_THREADS); + } + + int AddMemComboItem(UInt64 val, bool isPercent = false, bool isDefault = false); + void SetMemUseCombo(); + UString Get_MemUse_Spec(); + UInt64 Get_MemUse_Bytes(); + + UInt64 GetMemoryUsage_Dict_DecompMem(UInt64 dict, UInt64 &decompressMemory); + UInt64 GetMemoryUsage_Threads_Dict_DecompMem(UInt32 numThreads, UInt64 dict, UInt64 &decompressMemory); + UInt64 GetMemoryUsage_DecompMem(UInt64 &decompressMemory); + UInt64 GetMemoryUsageComp_Threads_Dict(UInt32 numThreads, UInt64 dict64); + + void PrintMemUsage(UINT res, UInt64 value); + void SetMemoryUsage(); + void Print_Params(); + + void SetParams(); + + void SaveOptionsInMem(); + + void UpdatePasswordControl(); + bool IsShowPasswordChecked() const { return IsButtonCheckedBool(IDX_PASSWORD_SHOW); } + + unsigned GetFormatIndex(); + bool SetArcPathFields(const UString &path, UString &name, bool always); + bool SetArcPathFields(const UString &path); + bool GetFinalPath_Smart(UString &resPath) const; + void ArcPath_WasChanged(const UString &newPath); + + void CheckSFXControlsEnable(); + // void CheckVolumeEnable(); + void EnableMultiCombo(unsigned id); + void FormatChanged(bool isChanged); + + void OnButtonSetArchive(); + bool IsSFX(); + void OnButtonSFX(); + + virtual bool OnInit() Z7_override; + virtual bool OnMessage(UINT message, WPARAM wParam, LPARAM lParam) Z7_override; + virtual bool OnCommand(unsigned code, unsigned itemID, LPARAM lParam) Z7_override; + virtual bool OnButtonClicked(unsigned buttonID, HWND buttonHWND) Z7_override; + virtual void OnOK() Z7_override; + virtual void OnHelp() Z7_override; + + void MessageBoxError(LPCWSTR message) + { + MessageBoxW(*this, message, L"7-Zip", MB_ICONERROR); + } + + void ShowOptionsString(); + +public: + const CObjectVector *ArcFormats; + CUIntVector ArcIndices; // can not be empty, must contain Info.FormatIndex, if Info.FormatIndex >= 0 + AStringVector ExternalMethods; + + void SetMethods(const CObjectVector &userCodecs); + + NCompressDialog::CInfo Info; + UString OriginalFileName; // for bzip2, gzip2 + + INT_PTR Create(HWND wndParent = NULL) + { + BIG_DIALOG_SIZE(400, 320); + return CModalDialog::Create(SIZED_DIALOG(IDD_COMPRESS), wndParent); + } + + CCompressDialog() {} +}; + + + + +class COptionsDialog: public NWindows::NControl::CModalDialog +{ + struct CBoolBox + { + bool IsSupported; + bool DefaultVal; + CBoolPair BoolPair; + + unsigned Id; + unsigned Set_Id; + + void SetIDs(unsigned id, unsigned set_Id) + { + Id = id; + Set_Id = set_Id; + } + + CBoolBox(): + IsSupported(false), + DefaultVal(false) + {} + }; + + CCompressDialog *cd; + + NWindows::NControl::CComboBox m_Prec; + + UInt32 _auto_Prec; + UInt32 TimePrec; + + void Reset_TimePrec() { TimePrec = (UInt32)(Int32)-1; } + bool IsSet_TimePrec() const { return TimePrec != (UInt32)(Int32)-1; } + + CBoolBox MTime; + CBoolBox CTime; + CBoolBox ATime; + CBoolBox ZTime; + + UString SecString; + UString NsString; + + + void CheckButton_Bool1(UINT id, const CBool1 &b1); + void GetButton_Bool1(UINT id, CBool1 &b1); + void CheckButton_BoolBox(bool supported, const CBoolPair &b2, CBoolBox &bb); + void GetButton_BoolBox(CBoolBox &bb); + + void Store_TimeBoxes(); + + UInt32 GetComboValue(NWindows::NControl::CComboBox &c, int defMax = 0); + UInt32 GetPrecSpec() + { + UInt32 prec = GetComboValue(m_Prec, 1); + if (prec == _auto_Prec) + prec = (UInt32)(Int32)-1; + return prec; + } + UInt32 GetPrec() { return GetComboValue(m_Prec, 0); } + + // void OnButton_TimeDefault(); + int AddPrec(unsigned prec, bool isDefault); + void SetPrec(); + void SetTimeMAC(); + + void On_CheckBoxSet_Prec_Clicked(); + void On_CheckBoxSet_Clicked(const CBoolBox &bb); + + virtual bool OnInit() Z7_override; + virtual bool OnCommand(unsigned code, unsigned itemID, LPARAM lParam) Z7_override; + virtual bool OnButtonClicked(unsigned buttonID, HWND buttonHWND) Z7_override; + virtual void OnOK() Z7_override; + virtual void OnHelp() Z7_override; + +public: + + INT_PTR Create(HWND wndParent = NULL) + { + BIG_DIALOG_SIZE(240, 232); + return CModalDialog::Create(SIZED_DIALOG(IDD_COMPRESS_OPTIONS), wndParent); + } + + COptionsDialog(CCompressDialog *cdLoc): + cd(cdLoc) + // , TimePrec(0) + { + Reset_TimePrec(); + } +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/GUI/CompressDialog.rc b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/CompressDialog.rc new file mode 100644 index 00000000..df1516c3 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/CompressDialog.rc @@ -0,0 +1,233 @@ +#include "CompressDialogRes.h" +#include "../../GuiCommon.rc" + +#define xc 400 +#define yc 320 + +#undef gSize +#undef gSpace +#undef g0xs +#undef g1x +#undef g1xs +#undef g2xs +#undef g3x +#undef g3xs +#undef g4x +#undef g4x2 +#undef g4xs +#undef g4xs2 + +#define gSize 192 +#define gSpace 24 + + +#define g1xs 88 +#define g0xs (gSize - g1xs) +#define g1x (m + g0xs) + +#define g3xs 52 +#define g2xs (gSize - g3xs) +#define g3x (m + g2xs) + +#define g4x (m + gSize + gSpace) +#define g4x2 (g4x + m) +#define g4xs (xc - gSize - gSpace) +#define g4xs2 (g4xs - m - m) + +#define yOpt 80 + +#define xArcFolderOffs 40 + +#undef GROUP_Y_SIZE +#undef GROUP_Y_SIZE_ENCRYPT +#ifdef UNDER_CE +#define GROUP_Y_SIZE 8 +#define GROUP_Y_SIZE_ENCRYPT 8 +#else +#define GROUP_Y_SIZE 64 +#define GROUP_Y_SIZE_ENCRYPT 128 +#endif + +// #define DICT_SIZE_SPACE 8 +// #define DICT_SIZE 54 +// #define DICT_x (g1x + g1xs - DICT_SIZE) +// #define DICT2_x (DICT_x - DICT_SIZE_SPACE - DICT_SIZE) + +#define yPsw (yOpt + GROUP_Y_SIZE + 8) + +IDD_COMPRESS DIALOG 0, 0, xs, ys MY_MODAL_DIALOG_STYLE MY_FONT +CAPTION "Add to Archive" +BEGIN + LTEXT "", IDT_COMPRESS_ARCHIVE_FOLDER, m + xArcFolderOffs, m, xc - xArcFolderOffs, 8 + LTEXT "&Archive:", IDT_COMPRESS_ARCHIVE, m, 12, xArcFolderOffs, 8 + COMBOBOX IDC_COMPRESS_ARCHIVE, m + xArcFolderOffs, 18, xc - bxsDots - 12 - xArcFolderOffs, 126, MY_COMBO_WITH_EDIT + PUSHBUTTON "...", IDB_COMPRESS_SET_ARCHIVE, xs - m - bxsDots, 16, bxsDots, bys, WS_GROUP + + LTEXT "Archive &format:", IDT_COMPRESS_FORMAT, m, 41, g0xs, 8 + COMBOBOX IDC_COMPRESS_FORMAT, g1x, 39, g1xs, 80, MY_COMBO | CBS_SORT + + LTEXT "Compression &level:", IDT_COMPRESS_LEVEL, m, 62, g0xs, 8 + COMBOBOX IDC_COMPRESS_LEVEL, g1x, 60, g1xs, 80, MY_COMBO + + LTEXT "Compression &method:", IDT_COMPRESS_METHOD, m, 83, g0xs, 8 + COMBOBOX IDC_COMPRESS_METHOD, g1x, 81, g1xs, 80, MY_COMBO + + LTEXT "&Dictionary size:", IDT_COMPRESS_DICTIONARY, m, 104, g0xs, 8 + COMBOBOX IDC_COMPRESS_DICTIONARY, g1x, 102, g1xs, 167, MY_COMBO + // LTEXT "&Dictionary size:", IDT_COMPRESS_DICTIONARY, m, 104, DICT_x - m, 16 // 8, SS_LEFTNOWORDWRAP + // LTEXT "", IDT_COMPRESS_PARAMS_INFO, m, 283, xs, MY_TEXT_NOPREFIX + // CTEXT "-", 0, DICT_x - DICT_SIZE_SPACE, 104, DICT_SIZE_SPACE, 8 + // COMBOBOX IDC_COMPRESS_DICTIONARY2, DICT2_x, 102, DICT_SIZE, 140, MY_COMBO + // COMBOBOX IDC_COMPRESS_DICTIONARY, DICT_x, 102, DICT_SIZE, 140, MY_COMBO + + LTEXT "&Word size:", IDT_COMPRESS_ORDER, m, 125, g0xs, 8 + COMBOBOX IDC_COMPRESS_ORDER, g1x, 123, g1xs, 140, MY_COMBO + + LTEXT "&Solid Block size:", IDT_COMPRESS_SOLID, m, 146, g0xs, 8 + COMBOBOX IDC_COMPRESS_SOLID, g1x, 144, g1xs, 140, MY_COMBO + + LTEXT "Number of CPU &threads:", IDT_COMPRESS_THREADS, m, 167, g0xs, 8 + COMBOBOX IDC_COMPRESS_THREADS, g1x, 165, g1xs - 40, 140, MY_COMBO + RTEXT "", IDT_COMPRESS_HARDWARE_THREADS, g1x + g1xs - 40, 167, 40, 16, SS_NOPREFIX + + + LTEXT "Memory usage for Compressing:", IDT_COMPRESS_MEMORY, m, 184, g2xs, 8 + COMBOBOX IDC_COMPRESS_MEM_USE, g3x, 188, g3xs, 140, MY_COMBO + LTEXT "", IDT_COMPRESS_MEMORY_VALUE, m, 194, g2xs, MY_TEXT_NOPREFIX + + LTEXT "Memory usage for Decompressing:", IDT_COMPRESS_MEMORY_DE, m, 208, g2xs, 8 + RTEXT "", IDT_COMPRESS_MEMORY_DE_VALUE, g3x, 208, g3xs, MY_TEXT_NOPREFIX + + + LTEXT "Split to &volumes, bytes:", IDT_SPLIT_TO_VOLUMES, m, 225, gSize, 8 + COMBOBOX IDC_COMPRESS_VOLUME, m, 237, gSize, 73, MY_COMBO_WITH_EDIT + + LTEXT "Parameters:", IDT_COMPRESS_PARAMETERS, m, 256, gSize, 8 + EDITTEXT IDE_COMPRESS_PARAMETERS, m, 268, gSize, 14, ES_AUTOHSCROLL + + PUSHBUTTON "Options", IDB_COMPRESS_OPTIONS, m, 292, bxs, bys + LTEXT "", IDT_COMPRESS_OPTIONS, m + bxs + m, 294, gSize - bxs - m, 16, SS_NOPREFIX + + + LTEXT "&Update mode:", IDT_COMPRESS_UPDATE_MODE, g4x, 41, 80, 8 + COMBOBOX IDC_COMPRESS_UPDATE_MODE, g4x + 84, 39, g4xs - 84, 80, MY_COMBO + + LTEXT "Path mode:", IDT_COMPRESS_PATH_MODE, g4x, 61, 80, 8 + COMBOBOX IDC_COMPRESS_PATH_MODE, g4x + 84, 59, g4xs - 84, 80, MY_COMBO + + + GROUPBOX "Options", IDG_COMPRESS_OPTIONS, g4x, yOpt, g4xs, GROUP_Y_SIZE + + CONTROL "Create SF&X archive", IDX_COMPRESS_SFX, MY_CHECKBOX, + g4x2, yOpt + 14, g4xs2, 10 + CONTROL "Compress shared files", IDX_COMPRESS_SHARED, MY_CHECKBOX, + g4x2, yOpt + 30, g4xs2, 10 + CONTROL "Delete files after compression", IDX_COMPRESS_DEL, MY_CHECKBOX, + g4x2, yOpt + 46, g4xs2, 10 + + + GROUPBOX "Encryption", IDG_COMPRESS_ENCRYPTION, g4x, yPsw, g4xs, GROUP_Y_SIZE_ENCRYPT + + LTEXT "Enter &password:", IDT_PASSWORD_ENTER, g4x2, yPsw + 14, g4xs2, 8 + EDITTEXT IDE_COMPRESS_PASSWORD1, g4x2, yPsw + 26, g4xs2, 14, ES_PASSWORD | ES_AUTOHSCROLL + LTEXT "Reenter password:", IDT_PASSWORD_REENTER, g4x2, yPsw + 46, g4xs2, 8 + EDITTEXT IDE_COMPRESS_PASSWORD2, g4x2, yPsw + 58, g4xs2, 14, ES_PASSWORD | ES_AUTOHSCROLL + + CONTROL "Show Password", IDX_PASSWORD_SHOW, MY_CHECKBOX, + g4x2, yPsw + 79, g4xs2, 10 + + LTEXT "&Encryption method:", IDT_COMPRESS_ENCRYPTION_METHOD, g4x2, yPsw + 95, 100, 8 + COMBOBOX IDC_COMPRESS_ENCRYPTION_METHOD, g4x2 + 100, yPsw + 93, g4xs2 - 100, 198, MY_COMBO + + CONTROL "Encrypt file &names", IDX_COMPRESS_ENCRYPT_FILE_NAMES, MY_CHECKBOX, + g4x2, yPsw + 111, g4xs2, 10 + + DEFPUSHBUTTON "OK", IDOK, bx3, by, bxs, bys, WS_GROUP + PUSHBUTTON "Cancel", IDCANCEL, bx2, by, bxs, bys + PUSHBUTTON "Help", IDHELP, bx1, by, bxs, bys +END + + +#ifdef UNDER_CE + +#undef m +#undef xc +#undef yc + +#define m 4 +#define xc 152 +#define yc 160 + + +IDD_COMPRESS_2 DIALOG 0, 0, xs, ys MY_MODAL_DIALOG_STYLE MY_FONT +CAPTION "Add to Archive" +MY_FONT +BEGIN + COMBOBOX IDC_COMPRESS_ARCHIVE, m, m, xc - bxsDots - m, 126, MY_COMBO_WITH_EDIT + PUSHBUTTON "...", IDB_COMPRESS_SET_ARCHIVE, xs - m - bxsDots, m, bxsDots, 12, WS_GROUP + + COMBOBOX IDC_COMPRESS_FORMAT, m , 22, 32, 80, MY_COMBO | CBS_SORT + COMBOBOX IDC_COMPRESS_LEVEL, m + 36, 22, 68, 80, MY_COMBO + COMBOBOX IDC_COMPRESS_METHOD, m + 108, 22, 44, 80, MY_COMBO + + COMBOBOX IDC_COMPRESS_DICTIONARY, m, 40, 40, 80, MY_COMBO + COMBOBOX IDC_COMPRESS_ORDER, m + 44, 40, 32, 80, MY_COMBO + COMBOBOX IDC_COMPRESS_SOLID, m + 80, 40, 40, 80, MY_COMBO + COMBOBOX IDC_COMPRESS_THREADS, m + 124, 40, 28, 80, MY_COMBO + + LTEXT "Split to &volumes, bytes:", IDT_SPLIT_TO_VOLUMES, m, 60, 32, 8 + COMBOBOX IDC_COMPRESS_VOLUME, m + 32, 58, 44, 73, MY_COMBO_WITH_EDIT + LTEXT "Parameters:", IDT_COMPRESS_PARAMETERS, m + 80, 60, 48, 8 + EDITTEXT IDE_COMPRESS_PARAMETERS, m + 128, 58, 24, 13, ES_AUTOHSCROLL + + COMBOBOX IDC_COMPRESS_UPDATE_MODE, m, 76, 88, 80, MY_COMBO + CONTROL "SF&X", IDX_COMPRESS_SFX, MY_CHECKBOX, m + 92, 77, 60, 10 + + CONTROL "Compress shared files", IDX_COMPRESS_SHARED, MY_CHECKBOX, m, 94, xc, 10 + + LTEXT "Enter &password:", IDT_PASSWORD_ENTER, m, 112, 60, 8 + EDITTEXT IDE_COMPRESS_PASSWORD1, m + 60, 110, 44, 13, ES_PASSWORD | ES_AUTOHSCROLL + CONTROL "Show Password", IDX_PASSWORD_SHOW, MY_CHECKBOX, m + 108, 112, 44, 10 + + COMBOBOX IDC_COMPRESS_ENCRYPTION_METHOD, m, 128, 48, 198, MY_COMBO + CONTROL "Encrypt file &names", IDX_COMPRESS_ENCRYPT_FILE_NAMES, MY_CHECKBOX, m + 52, 130, 100, 10 + + OK_CANCEL +END + +#endif + +STRINGTABLE +BEGIN + IDS_PASSWORD_NOT_MATCH "Passwords do not match" + IDS_PASSWORD_USE_ASCII "Use only English letters, numbers and special characters (!, #, $, ...) for password." + IDS_PASSWORD_TOO_LONG "Password is too long" + + IDS_METHOD_STORE "Store" + IDS_METHOD_FASTEST "Fastest" + IDS_METHOD_FAST "Fast" + IDS_METHOD_NORMAL "Normal" + IDS_METHOD_MAXIMUM "Maximum" + IDS_METHOD_ULTRA "Ultra" + + IDS_COMPRESS_UPDATE_MODE_ADD "Add and replace files" + IDS_COMPRESS_UPDATE_MODE_UPDATE "Update and add files" + IDS_COMPRESS_UPDATE_MODE_FRESH "Freshen existing files" + IDS_COMPRESS_UPDATE_MODE_SYNC "Synchronize files" + + IDS_OPEN_TYPE_ALL_FILES "All Files" + IDS_COMPRESS_SET_ARCHIVE_BROWSE "Browse" + + IDS_COMPRESS_NON_SOLID "Non-solid" + IDS_COMPRESS_SOLID "Solid" + + IDS_SPLIT_CONFIRM "Specified volume size: {0} bytes.\nAre you sure you want to split archive into such volumes?" + + IDS_COMPRESS_SEC "sec" + IDS_COMPRESS_NS "ns" + + IDS_MEM_OPERATION_BLOCKED "The operation was blocked by 7-Zip." +END + + +#include "CompressOptionsDialog.rc" diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/GUI/CompressDialogRes.h b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/CompressDialogRes.h new file mode 100644 index 00000000..d04d4b9c --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/CompressDialogRes.h @@ -0,0 +1,125 @@ +#define IDD_COMPRESS 4000 +#define IDD_COMPRESS_2 14000 +#define IDD_COMPRESS_OPTIONS 14001 + +#define IDC_COMPRESS_ARCHIVE 100 +#define IDB_COMPRESS_SET_ARCHIVE 101 +#define IDC_COMPRESS_LEVEL 102 +#define IDC_COMPRESS_UPDATE_MODE 103 +#define IDC_COMPRESS_FORMAT 104 +#define IDC_COMPRESS_VOLUME 105 +#define IDC_COMPRESS_METHOD 106 +#define IDC_COMPRESS_DICTIONARY 107 +#define IDC_COMPRESS_ORDER 108 +#define IDC_COMPRESS_SOLID 109 +#define IDC_COMPRESS_THREADS 110 +#define IDE_COMPRESS_PARAMETERS 111 + +#define IDT_COMPRESS_HARDWARE_THREADS 112 +#define IDT_COMPRESS_MEMORY_VALUE 113 +#define IDT_COMPRESS_MEMORY_DE_VALUE 114 + +#define IDG_COMPRESS_NTFS 115 +#define IDC_COMPRESS_PATH_MODE 116 +#define IDC_COMPRESS_MEM_USE 117 +// #define IDC_COMPRESS_DICTIONARY2 118 + +#define IDE_COMPRESS_PASSWORD1 120 +#define IDE_COMPRESS_PASSWORD2 121 +#define IDC_COMPRESS_ENCRYPTION_METHOD 122 + +#define IDT_COMPRESS_ARCHIVE_FOLDER 130 + +// #define IDB_COMPRESS_OPTIONS 140 +#define IDB_COMPRESS_OPTIONS 2100 +#define IDT_COMPRESS_OPTIONS 141 +// #define IDT_COMPRESS_PARAMS_INFO 142 + +#define IDT_COMPRESS_PATH_MODE 3410 + +#define IDT_PASSWORD_ENTER 3801 +#define IDT_PASSWORD_REENTER 3802 +#define IDX_PASSWORD_SHOW 3803 +#define IDS_PASSWORD_NOT_MATCH 3804 +#define IDS_PASSWORD_USE_ASCII 3805 +#define IDS_PASSWORD_TOO_LONG 3806 + +#define IDT_COMPRESS_ARCHIVE 4001 +#define IDT_COMPRESS_UPDATE_MODE 4002 +#define IDT_COMPRESS_FORMAT 4003 +#define IDT_COMPRESS_LEVEL 4004 +#define IDT_COMPRESS_METHOD 4005 +#define IDT_COMPRESS_DICTIONARY 4006 +#define IDT_COMPRESS_ORDER 4007 +#define IDT_COMPRESS_SOLID 4008 +#define IDT_COMPRESS_THREADS 4009 +#define IDT_COMPRESS_PARAMETERS 4010 +#define IDG_COMPRESS_OPTIONS 4011 + +#define IDX_COMPRESS_SFX 4012 +#define IDX_COMPRESS_SHARED 4013 + +#define IDG_COMPRESS_ENCRYPTION 4014 +#define IDT_COMPRESS_ENCRYPTION_METHOD 4015 +#define IDX_COMPRESS_ENCRYPT_FILE_NAMES 4016 + +#define IDT_COMPRESS_MEMORY 4017 +#define IDT_COMPRESS_MEMORY_DE 4018 + +#define IDX_COMPRESS_DEL 4019 + +#define IDX_COMPRESS_NT_SYM_LINKS 4040 +#define IDX_COMPRESS_NT_HARD_LINKS 4041 +#define IDX_COMPRESS_NT_ALT_STREAMS 4042 +#define IDX_COMPRESS_NT_SECUR 4043 + +#define IDS_METHOD_STORE 4050 +#define IDS_METHOD_FASTEST 4051 +#define IDS_METHOD_FAST 4052 +#define IDS_METHOD_NORMAL 4053 +#define IDS_METHOD_MAXIMUM 4054 +#define IDS_METHOD_ULTRA 4055 + +#define IDS_COMPRESS_UPDATE_MODE_ADD 4060 +#define IDS_COMPRESS_UPDATE_MODE_UPDATE 4061 +#define IDS_COMPRESS_UPDATE_MODE_FRESH 4062 +#define IDS_COMPRESS_UPDATE_MODE_SYNC 4063 + +#define IDS_COMPRESS_SET_ARCHIVE_BROWSE 4070 +#define IDS_OPEN_TYPE_ALL_FILES 4071 +#define IDS_COMPRESS_NON_SOLID 4072 +#define IDS_COMPRESS_SOLID 4073 + +#define IDT_SPLIT_TO_VOLUMES 7302 +#define IDS_INCORRECT_VOLUME_SIZE 7307 +#define IDS_SPLIT_CONFIRM 7308 + + +// Options Dialog + +#define IDG_COMPRESS_TIME 4080 +#define IDT_COMPRESS_TIME_PREC 4081 +#define IDX_COMPRESS_MTIME 4082 +#define IDX_COMPRESS_CTIME 4083 +#define IDX_COMPRESS_ATIME 4084 +#define IDX_COMPRESS_ZTIME 4085 +#define IDX_COMPRESS_PRESERVE_ATIME 4086 + +#define IDS_COMPRESS_SEC 4090 +#define IDS_COMPRESS_NS 4091 + +#define IDC_COMPRESS_TIME_PREC 190 +#define IDT_COMPRESS_TIME_INFO 191 + +#define IDX_COMPRESS_PREC_SET 201 +#define IDX_COMPRESS_MTIME_SET 202 +#define IDX_COMPRESS_CTIME_SET 203 +#define IDX_COMPRESS_ATIME_SET 204 +#define IDX_COMPRESS_ZTIME_SET 205 + +// #define IDX_COMPRESS_NT_SYM_LINKS_SET 210 +// #define IDX_COMPRESS_NT_HARD_LINKS_SET 211 +// #define IDX_COMPRESS_NT_ALT_STREAMS_SET 212 +// #define IDX_COMPRESS_NT_SECUR_SET 213 + +#define IDS_MEM_OPERATION_BLOCKED 7810 diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/GUI/CompressOptionsDialog.rc b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/CompressOptionsDialog.rc new file mode 100644 index 00000000..682775e9 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/CompressOptionsDialog.rc @@ -0,0 +1,79 @@ +#include "CompressDialogRes.h" +#include "../../GuiCommon.rc" + +#define xc 240 +#define yc 232 + +#define g5x m +#define g5x2 (g5x + m) +#define g5xs (xc) +#define g5xs2 (g5xs - m - m) + +#define ntPosX g5x2 +#define ntPosY m +#define ntSizeX g5xs2 +#define precSizeX 76 + +#define ntSizeY 72 +#define timePosY (ntPosY + ntSizeY + 20) + + +IDD_COMPRESS_OPTIONS DIALOG 0, 0, xs, ys MY_MODAL_DIALOG_STYLE MY_FONT +CAPTION "Options" +BEGIN + GROUPBOX "NTFS", IDG_COMPRESS_NTFS, g5x, ntPosY, g5xs, ntSizeY + + MY_CONTROL_CHECKBOX ("Store symbolic links", IDX_COMPRESS_NT_SYM_LINKS, + ntPosX, ntPosY + 12, ntSizeX) + MY_CONTROL_CHECKBOX ("Store hard links", IDX_COMPRESS_NT_HARD_LINKS, + ntPosX, ntPosY + 26, ntSizeX) + MY_CONTROL_CHECKBOX ("Store alternate data streams", IDX_COMPRESS_NT_ALT_STREAMS, + ntPosX, ntPosY + 40, ntSizeX) + MY_CONTROL_CHECKBOX ("Store file security", IDX_COMPRESS_NT_SECUR, + ntPosX, ntPosY + 54, ntSizeX) + + LTEXT "", IDT_COMPRESS_TIME_INFO, g5x, timePosY - 14, g5xs, 8 + + + GROUPBOX "Time", IDG_COMPRESS_TIME, g5x, timePosY, g5xs, 112 + +// MY_CONTROL_CHECKBOX ("Default", IDX_COMPRESS_TIME_DEFAULT, +// ntPosX, timePosY + 10, ntSizeX) + + MY_CONTROL_CHECKBOX_COLON (IDX_COMPRESS_PREC_SET, ntPosX, timePosY + 14) + LTEXT "Timestamp precision:", IDT_COMPRESS_TIME_PREC, + ntPosX + cboxColonSize, timePosY + 14, ntSizeX - precSizeX - cboxColonSize, 8 + COMBOBOX IDC_COMPRESS_TIME_PREC, ntPosX + ntSizeX - precSizeX, timePosY + 12, precSizeX, 70, MY_COMBO + + // PUSHBUTTON "Default", IDB_COMPRESS_TIME_DEFAULT, ntPosX + ntSizeX - bxs, timePosY + 22, bxs, bys, WS_GROUP + + MY_CONTROL_CHECKBOX_COLON (IDX_COMPRESS_MTIME_SET, ntPosX, timePosY + 28) + MY_CONTROL_CHECKBOX ("Store modification time", IDX_COMPRESS_MTIME, + ntPosX + cboxColonSize, timePosY + 28, ntSizeX - cboxColonSize) + + MY_CONTROL_CHECKBOX_COLON (IDX_COMPRESS_CTIME_SET, ntPosX, timePosY + 42) + MY_CONTROL_CHECKBOX ("Store creation time", IDX_COMPRESS_CTIME, + ntPosX + cboxColonSize, timePosY + 42, ntSizeX - cboxColonSize) + + MY_CONTROL_CHECKBOX_COLON (IDX_COMPRESS_ATIME_SET, ntPosX, timePosY + 56) + MY_CONTROL_CHECKBOX ("Store last access time", IDX_COMPRESS_ATIME, + ntPosX + cboxColonSize, timePosY + 56, ntSizeX - cboxColonSize) + + MY_CONTROL_CHECKBOX_2LINES(colonString, + IDX_COMPRESS_ZTIME_SET, + ntPosX, timePosY + 72, cboxColonSize) + MY_CONTROL_CHECKBOX_2LINES( + "Set archive time to latest file time", + IDX_COMPRESS_ZTIME, + ntPosX + cboxColonSize, timePosY + 72, ntSizeX - cboxColonSize) + + MY_CONTROL_CHECKBOX_2LINES( + "Do not change source files last access time", + IDX_COMPRESS_PRESERVE_ATIME, + ntPosX, timePosY + 92, ntSizeX) + + + DEFPUSHBUTTON "OK", IDOK, bx3, by, bxs, bys, WS_GROUP + PUSHBUTTON "Cancel", IDCANCEL, bx2, by, bxs, bys + PUSHBUTTON "Help", IDHELP, bx1, by, bxs, bys +END diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/GUI/Extract.rc b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/Extract.rc new file mode 100644 index 00000000..36bfb009 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/Extract.rc @@ -0,0 +1,59 @@ +#include "ExtractRes.h" + +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +STRINGTABLE DISCARDABLE +BEGIN + IDS_MEM_ERROR "The system cannot allocate the required amount of memory" + IDS_CANNOT_CREATE_FOLDER "Cannot create folder '{0}'" + IDS_UPDATE_NOT_SUPPORTED "Update operations are not supported for this archive." + IDS_CANT_OPEN_ARCHIVE "Cannot open file '{0}' as archive" + IDS_CANT_OPEN_ENCRYPTED_ARCHIVE "Cannot open encrypted archive '{0}'. Wrong password?" + IDS_UNSUPPORTED_ARCHIVE_TYPE "Unsupported archive type" + + IDS_CANT_OPEN_AS_TYPE "Cannot open the file as {0} archive" + IDS_IS_OPEN_AS_TYPE "The file is open as {0} archive" + IDS_IS_OPEN_WITH_OFFSET "The archive is open with offset" + + IDS_PROGRESS_EXTRACTING "Extracting" + + IDS_PROGRESS_SKIPPING "Skipping" + + IDS_EXTRACT_SET_FOLDER "Specify a location for extracted files." + + IDS_EXTRACT_PATHS_FULL "Full pathnames" + IDS_EXTRACT_PATHS_NO "No pathnames" + IDS_EXTRACT_PATHS_ABS "Absolute pathnames" + IDS_PATH_MODE_RELAT "Relative pathnames" + + IDS_EXTRACT_OVERWRITE_ASK "Ask before overwrite" + IDS_EXTRACT_OVERWRITE_WITHOUT_PROMPT "Overwrite without prompt" + IDS_EXTRACT_OVERWRITE_SKIP_EXISTING "Skip existing files" + IDS_EXTRACT_OVERWRITE_RENAME "Auto rename" + IDS_EXTRACT_OVERWRITE_RENAME_EXISTING "Auto rename existing files" + + IDS_EXTRACT_MESSAGE_UNSUPPORTED_METHOD "Unsupported compression method for '{0}'." + IDS_EXTRACT_MESSAGE_DATA_ERROR "Data error in '{0}'. File is broken" + IDS_EXTRACT_MESSAGE_CRC_ERROR "CRC failed in '{0}'. File is broken." + IDS_EXTRACT_MESSAGE_DATA_ERROR_ENCRYPTED "Data error in encrypted file '{0}'. Wrong password?" + IDS_EXTRACT_MESSAGE_CRC_ERROR_ENCRYPTED "CRC failed in encrypted file '{0}'. Wrong password?" + + IDS_EXTRACT_MSG_WRONG_PSW_GUESS "Wrong password?" + // IDS_EXTRACT_MSG_ENCRYPTED "Encrypted file" + + IDS_EXTRACT_MSG_UNSUPPORTED_METHOD "Unsupported compression method" + IDS_EXTRACT_MSG_DATA_ERROR "Data error" + IDS_EXTRACT_MSG_CRC_ERROR "CRC failed" + IDS_EXTRACT_MSG_UNAVAILABLE_DATA "Unavailable data" + IDS_EXTRACT_MSG_UEXPECTED_END "Unexpected end of data" + IDS_EXTRACT_MSG_DATA_AFTER_END "There are some data after the end of the payload data" + IDS_EXTRACT_MSG_IS_NOT_ARC "Is not archive" + IDS_EXTRACT_MSG_HEADERS_ERROR "Headers Error" + IDS_EXTRACT_MSG_WRONG_PSW_CLAIM "Wrong password" + + IDS_OPEN_MSG_UNAVAILABLE_START "Unavailable start of archive" + IDS_OPEN_MSG_UNCONFIRMED_START "Unconfirmed start of archive" + // IDS_OPEN_MSG_ERROR_FLAGS + 5 "Unexpected end of archive" + // IDS_OPEN_MSG_ERROR_FLAGS + 6 "There are data after the end of archive" + IDS_OPEN_MSG_UNSUPPORTED_FEATURE "Unsupported feature" +END diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/GUI/ExtractDialog.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/ExtractDialog.cpp new file mode 100644 index 00000000..467cf18f --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/ExtractDialog.cpp @@ -0,0 +1,420 @@ +// ExtractDialog.cpp + +#include "StdAfx.h" + +#include "../../../Common/StringConvert.h" +#include "../../../Common/Wildcard.h" + +#include "../../../Windows/FileName.h" +#include "../../../Windows/FileDir.h" +#include "../../../Windows/ResourceString.h" + +#ifndef Z7_NO_REGISTRY +#include "../FileManager/HelpUtils.h" +#endif + + +#include "../FileManager/BrowseDialog.h" +#include "../FileManager/LangUtils.h" +#include "../FileManager/resourceGui.h" + +#include "ExtractDialog.h" +#include "ExtractDialogRes.h" +#include "ExtractRes.h" + +using namespace NWindows; +using namespace NFile; +using namespace NName; + +extern HINSTANCE g_hInstance; + +#ifndef Z7_SFX + +static const UInt32 kPathMode_IDs[] = +{ + IDS_EXTRACT_PATHS_FULL, + IDS_EXTRACT_PATHS_NO, + IDS_EXTRACT_PATHS_ABS +}; + +static const UInt32 kOverwriteMode_IDs[] = +{ + IDS_EXTRACT_OVERWRITE_ASK, + IDS_EXTRACT_OVERWRITE_WITHOUT_PROMPT, + IDS_EXTRACT_OVERWRITE_SKIP_EXISTING, + IDS_EXTRACT_OVERWRITE_RENAME, + IDS_EXTRACT_OVERWRITE_RENAME_EXISTING +}; + +static const + // NExtract::NPathMode::EEnum + int + kPathModeButtonsVals[] = +{ + NExtract::NPathMode::kFullPaths, + NExtract::NPathMode::kNoPaths, + NExtract::NPathMode::kAbsPaths +}; + +static const + int + // NExtract::NOverwriteMode::EEnum + kOverwriteButtonsVals[] = +{ + NExtract::NOverwriteMode::kAsk, + NExtract::NOverwriteMode::kOverwrite, + NExtract::NOverwriteMode::kSkip, + NExtract::NOverwriteMode::kRename, + NExtract::NOverwriteMode::kRenameExisting +}; + +#endif + +#ifdef Z7_LANG + +static const UInt32 kLangIDs[] = +{ + IDT_EXTRACT_EXTRACT_TO, + IDT_EXTRACT_PATH_MODE, + IDT_EXTRACT_OVERWRITE_MODE, + // IDX_EXTRACT_ALT_STREAMS, + IDX_EXTRACT_NT_SECUR, + IDX_EXTRACT_ELIM_DUP, + IDG_PASSWORD, + IDX_PASSWORD_SHOW +}; +#endif + +// static const int kWildcardsButtonIndex = 2; + +#ifndef Z7_NO_REGISTRY +static const unsigned kHistorySize = 16; +#endif + +#ifndef Z7_SFX + +// it's used in CompressDialog also +void AddComboItems(NControl::CComboBox &combo, const UInt32 *langIDs, unsigned numItems, const int *values, int curVal); +void AddComboItems(NControl::CComboBox &combo, const UInt32 *langIDs, unsigned numItems, const int *values, int curVal) +{ + unsigned curSel = 0; + for (unsigned i = 0; i < numItems; i++) + { + UString s = LangString(langIDs[i]); + s.RemoveChar(L'&'); + combo.AddString_SetItemData(s, (LPARAM)i); + if (values[i] == curVal) + curSel = i; + } + combo.SetCurSel(curSel); +} + +// it's used in CompressDialog also +bool GetBoolsVal(const CBoolPair &b1, const CBoolPair &b2); +bool GetBoolsVal(const CBoolPair &b1, const CBoolPair &b2) +{ + if (b1.Def) return b1.Val; + if (b2.Def) return b2.Val; + return b1.Val; +} + +void CExtractDialog::CheckButton_TwoBools(UINT id, const CBoolPair &b1, const CBoolPair &b2) +{ + CheckButton(id, GetBoolsVal(b1, b2)); +} + +void CExtractDialog::GetButton_Bools(UINT id, CBoolPair &b1, CBoolPair &b2) +{ + const bool val = IsButtonCheckedBool(id); + const bool oldVal = GetBoolsVal(b1, b2); + if (val != oldVal) + b1.Def = b2.Def = true; + b1.Val = b2.Val = val; +} + +#endif + +bool CExtractDialog::OnInit() +{ + #ifdef Z7_LANG + { + UString s; + LangString_OnlyFromLangFile(IDD_EXTRACT, s); + if (s.IsEmpty()) + GetText(s); + if (!ArcPath.IsEmpty()) + { + s += " : "; + s += ArcPath; + } + SetText(s); + // LangSetWindowText(*this, IDD_EXTRACT); + LangSetDlgItems(*this, kLangIDs, Z7_ARRAY_SIZE(kLangIDs)); + } + #endif + + #ifndef Z7_SFX + _passwordControl.Attach(GetItem(IDE_EXTRACT_PASSWORD)); + _passwordControl.SetText(Password); + _passwordControl.SetPasswordChar(TEXT('*')); + _pathName.Attach(GetItem(IDE_EXTRACT_NAME)); + #endif + + #ifdef Z7_NO_REGISTRY + + PathMode = NExtract::NPathMode::kFullPaths; + OverwriteMode = NExtract::NOverwriteMode::kAsk; + + #else + + _info.Load(); + + if (_info.PathMode == NExtract::NPathMode::kCurPaths) + _info.PathMode = NExtract::NPathMode::kFullPaths; + + if (!PathMode_Force && _info.PathMode_Force) + PathMode = _info.PathMode; + if (!OverwriteMode_Force && _info.OverwriteMode_Force) + OverwriteMode = _info.OverwriteMode; + + // CheckButton_TwoBools(IDX_EXTRACT_ALT_STREAMS, AltStreams, _info.AltStreams); + CheckButton_TwoBools(IDX_EXTRACT_NT_SECUR, NtSecurity, _info.NtSecurity); + CheckButton_TwoBools(IDX_EXTRACT_ELIM_DUP, ElimDup, _info.ElimDup); + + CheckButton(IDX_PASSWORD_SHOW, _info.ShowPassword.Val); + UpdatePasswordControl(); + + #endif + + _path.Attach(GetItem(IDC_EXTRACT_PATH)); + + UString pathPrefix = DirPath; + + #ifndef Z7_SFX + + if (_info.SplitDest.Val) + { + CheckButton(IDX_EXTRACT_NAME_ENABLE, true); + UString pathName; + SplitPathToParts_Smart(DirPath, pathPrefix, pathName); + if (pathPrefix.IsEmpty()) + pathPrefix = pathName; + else + _pathName.SetText(pathName); + } + else + ShowItem_Bool(IDE_EXTRACT_NAME, false); + + #endif + + _path.SetText(pathPrefix); + + #ifndef Z7_NO_REGISTRY + for (unsigned i = 0; i < _info.Paths.Size() && i < kHistorySize; i++) + _path.AddString(_info.Paths[i]); + #endif + + /* + if (_info.Paths.Size() > 0) + _path.SetCurSel(0); + else + _path.SetCurSel(-1); + */ + + #ifndef Z7_SFX + + _pathMode.Attach(GetItem(IDC_EXTRACT_PATH_MODE)); + _overwriteMode.Attach(GetItem(IDC_EXTRACT_OVERWRITE_MODE)); + + AddComboItems(_pathMode, kPathMode_IDs, Z7_ARRAY_SIZE(kPathMode_IDs), kPathModeButtonsVals, PathMode); + AddComboItems(_overwriteMode, kOverwriteMode_IDs, Z7_ARRAY_SIZE(kOverwriteMode_IDs), kOverwriteButtonsVals, OverwriteMode); + + #endif + + HICON icon = LoadIcon(g_hInstance, MAKEINTRESOURCE(IDI_ICON)); + SetIcon(ICON_BIG, icon); + + // CWindow filesWindow = GetItem(IDC_EXTRACT_RADIO_FILES); + // filesWindow.Enable(_enableFilesButton); + + NormalizePosition(); + + return CModalDialog::OnInit(); +} + +#ifndef Z7_SFX +void CExtractDialog::UpdatePasswordControl() +{ + _passwordControl.SetPasswordChar(IsShowPasswordChecked() ? 0 : TEXT('*')); + UString password; + _passwordControl.GetText(password); + _passwordControl.SetText(password); +} +#endif + +bool CExtractDialog::OnButtonClicked(unsigned buttonID, HWND buttonHWND) +{ + switch (buttonID) + { + case IDB_EXTRACT_SET_PATH: + OnButtonSetPath(); + return true; + #ifndef Z7_SFX + case IDX_EXTRACT_NAME_ENABLE: + ShowItem_Bool(IDE_EXTRACT_NAME, IsButtonCheckedBool(IDX_EXTRACT_NAME_ENABLE)); + return true; + case IDX_PASSWORD_SHOW: + { + UpdatePasswordControl(); + return true; + } + #endif + } + return CModalDialog::OnButtonClicked(buttonID, buttonHWND); +} + +void CExtractDialog::OnButtonSetPath() +{ + UString currentPath; + _path.GetText(currentPath); + UString title = LangString(IDS_EXTRACT_SET_FOLDER); + UString resultPath; + if (!MyBrowseForFolder(*this, title, currentPath, resultPath)) + return; + #ifndef Z7_NO_REGISTRY + _path.SetCurSel(-1); + #endif + _path.SetText(resultPath); +} + +void AddUniqueString(UStringVector &list, const UString &s); +void AddUniqueString(UStringVector &list, const UString &s) +{ + FOR_VECTOR (i, list) + if (s.IsEqualTo_NoCase(list[i])) + return; + list.Add(s); +} + +void CExtractDialog::OnOK() +{ + #ifndef Z7_SFX + int pathMode2 = kPathModeButtonsVals[_pathMode.GetCurSel()]; + if (PathMode != NExtract::NPathMode::kCurPaths || + pathMode2 != NExtract::NPathMode::kFullPaths) + PathMode = (NExtract::NPathMode::EEnum)pathMode2; + + OverwriteMode = (NExtract::NOverwriteMode::EEnum)kOverwriteButtonsVals[_overwriteMode.GetCurSel()]; + + // _filesMode = (NExtractionDialog::NFilesMode::EEnum)GetFilesMode(); + + _passwordControl.GetText(Password); + + #endif + + #ifndef Z7_NO_REGISTRY + + // GetButton_Bools(IDX_EXTRACT_ALT_STREAMS, AltStreams, _info.AltStreams); + GetButton_Bools(IDX_EXTRACT_NT_SECUR, NtSecurity, _info.NtSecurity); + GetButton_Bools(IDX_EXTRACT_ELIM_DUP, ElimDup, _info.ElimDup); + + bool showPassword = IsShowPasswordChecked(); + if (showPassword != _info.ShowPassword.Val) + { + _info.ShowPassword.Def = true; + _info.ShowPassword.Val = showPassword; + } + + if (_info.PathMode != pathMode2) + { + _info.PathMode_Force = true; + _info.PathMode = (NExtract::NPathMode::EEnum)pathMode2; + /* + // we allow kAbsPaths in registry. + if (_info.PathMode == NExtract::NPathMode::kAbsPaths) + _info.PathMode = NExtract::NPathMode::kFullPaths; + */ + } + + if (!OverwriteMode_Force && _info.OverwriteMode != OverwriteMode) + _info.OverwriteMode_Force = true; + _info.OverwriteMode = OverwriteMode; + + + #else + + ElimDup.Val = IsButtonCheckedBool(IDX_EXTRACT_ELIM_DUP); + + #endif + + UString s; + + #ifdef Z7_NO_REGISTRY + + _path.GetText(s); + + #else + + int currentItem = _path.GetCurSel(); + if (currentItem == CB_ERR) + { + _path.GetText(s); + if (_path.GetCount() >= (int)kHistorySize) + currentItem = _path.GetCount() - 1; + } + else + _path.GetLBText(currentItem, s); + + #endif + + s.Trim(); + NName::NormalizeDirPathPrefix(s); + + #ifndef Z7_SFX + + const bool splitDest = IsButtonCheckedBool(IDX_EXTRACT_NAME_ENABLE); + if (splitDest) + { + UString pathName; + _pathName.GetText(pathName); + pathName.Trim(); + s += pathName; + NName::NormalizeDirPathPrefix(s); + } + if (splitDest != _info.SplitDest.Val) + { + _info.SplitDest.Def = true; + _info.SplitDest.Val = splitDest; + } + + #endif + + DirPath = s; + + #ifndef Z7_NO_REGISTRY + _info.Paths.Clear(); + #ifndef Z7_SFX + AddUniqueString(_info.Paths, s); + #endif + for (int i = 0; i < _path.GetCount(); i++) + if (i != currentItem) + { + UString sTemp; + _path.GetLBText(i, sTemp); + sTemp.Trim(); + AddUniqueString(_info.Paths, sTemp); + } + _info.Save(); + #endif + + CModalDialog::OnOK(); +} + +#ifndef Z7_NO_REGISTRY +#define kHelpTopic "fm/plugins/7-zip/extract.htm" +void CExtractDialog::OnHelp() +{ + ShowHelpWindow(kHelpTopic); + CModalDialog::OnHelp(); +} +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/GUI/ExtractDialog.h b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/ExtractDialog.h new file mode 100644 index 00000000..1565fb8a --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/ExtractDialog.h @@ -0,0 +1,113 @@ +// ExtractDialog.h + +#ifndef ZIP7_INC_EXTRACT_DIALOG_H +#define ZIP7_INC_EXTRACT_DIALOG_H + +#include "ExtractDialogRes.h" + +#include "../../../Windows/Control/ComboBox.h" +#include "../../../Windows/Control/Edit.h" + +#include "../Common/ExtractMode.h" + +#include "../FileManager/DialogSize.h" + +#ifndef Z7_NO_REGISTRY +#include "../Common/ZipRegistry.h" +#endif + +namespace NExtractionDialog +{ + /* + namespace NFilesMode + { + enum EEnum + { + kSelected, + kAll, + kSpecified + }; + } + */ +} + +class CExtractDialog: public NWindows::NControl::CModalDialog +{ + #ifdef Z7_NO_REGISTRY + NWindows::NControl::CDialogChildControl _path; + #else + NWindows::NControl::CComboBox _path; + #endif + + #ifndef Z7_SFX + NWindows::NControl::CEdit _pathName; + NWindows::NControl::CEdit _passwordControl; + NWindows::NControl::CComboBox _pathMode; + NWindows::NControl::CComboBox _overwriteMode; + #endif + + #ifndef Z7_SFX + // int GetFilesMode() const; + void UpdatePasswordControl(); + #endif + + void OnButtonSetPath(); + + void CheckButton_TwoBools(UINT id, const CBoolPair &b1, const CBoolPair &b2); + void GetButton_Bools(UINT id, CBoolPair &b1, CBoolPair &b2); + virtual bool OnInit() Z7_override; + virtual bool OnButtonClicked(unsigned buttonID, HWND buttonHWND) Z7_override; + virtual void OnOK() Z7_override; + + #ifndef Z7_NO_REGISTRY + + virtual void OnHelp() Z7_override; + + NExtract::CInfo _info; + + #endif + + bool IsShowPasswordChecked() const { return IsButtonCheckedBool(IDX_PASSWORD_SHOW); } +public: + // bool _enableSelectedFilesButton; + // bool _enableFilesButton; + // NExtractionDialog::NFilesMode::EEnum FilesMode; + + UString DirPath; + UString ArcPath; + + #ifndef Z7_SFX + UString Password; + #endif + bool PathMode_Force; + bool OverwriteMode_Force; + NExtract::NPathMode::EEnum PathMode; + NExtract::NOverwriteMode::EEnum OverwriteMode; + + #ifndef Z7_SFX + // CBoolPair AltStreams; + CBoolPair NtSecurity; + #endif + + CBoolPair ElimDup; + + INT_PTR Create(HWND aWndParent = NULL) + { + #ifdef Z7_SFX + BIG_DIALOG_SIZE(240, 64); + #else + BIG_DIALOG_SIZE(300, 160); + #endif + return CModalDialog::Create(SIZED_DIALOG(IDD_EXTRACT), aWndParent); + } + + CExtractDialog(): + PathMode_Force(false), + OverwriteMode_Force(false) + { + ElimDup.Val = true; + } + +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/GUI/ExtractDialog.rc b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/ExtractDialog.rc new file mode 100644 index 00000000..3728b96d --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/ExtractDialog.rc @@ -0,0 +1,98 @@ +#include "ExtractDialogRes.h" +#include "../../GuiCommon.rc" + +#define xc 336 +#define yc 168 + +#undef g1xs +#undef g2x +#undef g2x2 +#undef g2xs +#undef g2xs2 + +#define g1xs 160 + +#define gSpace 20 +#define g2x (m + g1xs + gSpace) +#define g2x2 (g2x + m) +#define g2xs (xc - g1xs - gSpace) +#define g2xs2 (g2xs - m - m) + +#undef GROUP_Y_SIZE +#ifdef UNDER_CE +#define GROUP_Y_SIZE 8 +#else +#define GROUP_Y_SIZE 56 +#endif + +IDD_EXTRACT DIALOG 0, 0, xs, ys MY_MODAL_DIALOG_STYLE MY_FONT +CAPTION "Extract" +BEGIN + LTEXT "E&xtract to:", IDT_EXTRACT_EXTRACT_TO, m, m, xc, 8 + COMBOBOX IDC_EXTRACT_PATH, m, m + 12, xc - bxsDots - 12, 100, MY_COMBO_WITH_EDIT + PUSHBUTTON "...", IDB_EXTRACT_SET_PATH, xs - m - bxsDots, m + 12 - 2, bxsDots, bys, WS_GROUP + + CONTROL "", IDX_EXTRACT_NAME_ENABLE, MY_CHECKBOX, m, m + 34, 12, 10 + EDITTEXT IDE_EXTRACT_NAME, m + 12 + 2, m + 32, g1xs - 12 - 2, 14, ES_AUTOHSCROLL + + LTEXT "Path mode:", IDT_EXTRACT_PATH_MODE, m, m + 52, g1xs, 8 + COMBOBOX IDC_EXTRACT_PATH_MODE, m, m + 64, g1xs, 140, MY_COMBO + + CONTROL "Eliminate duplication of root folder", IDX_EXTRACT_ELIM_DUP, MY_CHECKBOX, + m, m + 84, g1xs, 10 + + LTEXT "Overwrite mode:", IDT_EXTRACT_OVERWRITE_MODE, m, m + 104, g1xs, 8 + COMBOBOX IDC_EXTRACT_OVERWRITE_MODE, m, m + 116, g1xs, 140, MY_COMBO + + + GROUPBOX "Password", IDG_PASSWORD, g2x, m + 36, g2xs, GROUP_Y_SIZE + EDITTEXT IDE_EXTRACT_PASSWORD, g2x2, m + 50, g2xs2, 14, ES_PASSWORD | ES_AUTOHSCROLL + CONTROL "Show Password", IDX_PASSWORD_SHOW, MY_CHECKBOX, g2x2, m + 72, g2xs2, 10 + +// CONTROL "Restore alternate data streams", IDX_EXTRACT_ALT_STREAMS, MY_CHECKBOX, +// g2x, m + 104, g2xs, 10 + CONTROL "Restore file security", IDX_EXTRACT_NT_SECUR, MY_CHECKBOX, + g2x, m + 104, g2xs, 10 + + DEFPUSHBUTTON "OK", IDOK, bx3, by, bxs, bys, WS_GROUP + PUSHBUTTON "Cancel", IDCANCEL, bx2, by, bxs, bys + PUSHBUTTON "Help", IDHELP, bx1, by, bxs, bys +END + + +#ifdef UNDER_CE + +#undef m +#define m 4 + +#undef xc +#undef yc + +#define xc 152 +#define yc 128 + +#undef g1xs + +#define g1xs 64 + +IDD_EXTRACT_2 DIALOG 0, 0, xs, ys MY_MODAL_DIALOG_STYLE MY_FONT +CAPTION "Extract" +BEGIN + LTEXT "E&xtract to:", IDT_EXTRACT_EXTRACT_TO, m, m, xc - bxsDots - 8, 8 + COMBOBOX IDC_EXTRACT_PATH, m, m + 12, xc - bxsDots - 8, 100, MY_COMBO_WITH_EDIT + PUSHBUTTON "...", IDB_EXTRACT_SET_PATH, xs - m - bxsDots, m + 12 - 3, bxsDots, bys, WS_GROUP + + LTEXT "Path mode:", IDT_EXTRACT_PATH_MODE, m, m + 36, g1xs, 8 + COMBOBOX IDC_EXTRACT_PATH_MODE, m + g1xs, m + 36, xc - g1xs, 100, MY_COMBO + + LTEXT "Overwrite mode:", IDT_EXTRACT_OVERWRITE_MODE, m, m + 56, g1xs, 8 + COMBOBOX IDC_EXTRACT_OVERWRITE_MODE, m + g1xs, m + 56, xc - g1xs, 100, MY_COMBO + + LTEXT "Password", IDG_PASSWORD, m, m + 76, g1xs, 8 + EDITTEXT IDE_EXTRACT_PASSWORD, m + g1xs, m + 76, xc - g1xs, 14, ES_PASSWORD | ES_AUTOHSCROLL + CONTROL "Show Password", IDX_PASSWORD_SHOW, MY_CHECKBOX, m, m + 92, xc, 10 + + OK_CANCEL +END + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/GUI/ExtractDialogRes.h b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/ExtractDialogRes.h new file mode 100644 index 00000000..ed12bfb3 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/ExtractDialogRes.h @@ -0,0 +1,24 @@ +#define IDD_EXTRACT 3400 +#define IDD_EXTRACT_2 13400 + +#define IDC_EXTRACT_PATH 100 +#define IDB_EXTRACT_SET_PATH 101 +#define IDC_EXTRACT_PATH_MODE 102 +#define IDC_EXTRACT_OVERWRITE_MODE 103 + +#define IDE_EXTRACT_PASSWORD 120 + +#define IDE_EXTRACT_NAME 130 +#define IDX_EXTRACT_NAME_ENABLE 131 + + +#define IDT_EXTRACT_EXTRACT_TO 3401 +#define IDT_EXTRACT_PATH_MODE 3410 +#define IDT_EXTRACT_OVERWRITE_MODE 3420 + +#define IDX_EXTRACT_ELIM_DUP 3430 +#define IDX_EXTRACT_NT_SECUR 3431 +// #define IDX_EXTRACT_ALT_STREAMS 3432 + +#define IDX_PASSWORD_SHOW 3803 +#define IDG_PASSWORD 3807 diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/GUI/ExtractGUI.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/ExtractGUI.cpp new file mode 100644 index 00000000..bafff2ba --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/ExtractGUI.cpp @@ -0,0 +1,297 @@ +// ExtractGUI.cpp + +#include "StdAfx.h" + +#include "../../../Common/IntToString.h" +#include "../../../Common/StringConvert.h" + +#include "../../../Windows/FileDir.h" +#include "../../../Windows/FileFind.h" +#include "../../../Windows/FileName.h" +#include "../../../Windows/Thread.h" + +#include "../FileManager/ExtractCallback.h" +#include "../FileManager/FormatUtils.h" +#include "../FileManager/LangUtils.h" +#include "../FileManager/resourceGui.h" +#include "../FileManager/OverwriteDialogRes.h" + +#include "../Common/ArchiveExtractCallback.h" +#include "../Common/PropIDUtils.h" + +#include "../Explorer/MyMessages.h" + +#include "resource2.h" +#include "ExtractRes.h" + +#include "ExtractDialog.h" +#include "ExtractGUI.h" +#include "HashGUI.h" + +#include "../FileManager/PropertyNameRes.h" + +using namespace NWindows; +using namespace NFile; +using namespace NDir; + +static const wchar_t * const kIncorrectOutDir = L"Incorrect output directory path"; + +#ifndef Z7_SFX + +static void AddValuePair(UString &s, UINT resourceID, UInt64 value, bool addColon = true) +{ + AddLangString(s, resourceID); + if (addColon) + s.Add_Colon(); + s.Add_Space(); + s.Add_UInt64(value); + s.Add_LF(); +} + +static void AddSizePair(UString &s, UINT resourceID, UInt64 value) +{ + AddLangString(s, resourceID); + s += ": "; + AddSizeValue(s, value); + s.Add_LF(); +} + +#endif + +class CThreadExtracting: public CProgressThreadVirt +{ + HRESULT ProcessVirt() Z7_override; +public: + /* + #ifdef Z7_EXTERNAL_CODECS + const CExternalCodecs *externalCodecs; + #endif + */ + + CCodecs *codecs; + CExtractCallbackImp *ExtractCallbackSpec; + const CObjectVector *FormatIndices; + const CIntVector *ExcludedFormatIndices; + + UStringVector *ArchivePaths; + UStringVector *ArchivePathsFull; + const NWildcard::CCensorNode *WildcardCensor; + const CExtractOptions *Options; + + #ifndef Z7_SFX + CHashBundle *HashBundle; + virtual void ProcessWasFinished_GuiVirt() Z7_override; + #endif + + CMyComPtr FolderArchiveExtractCallback; + UString Title; + + CPropNameValPairs Pairs; +}; + + +#ifndef Z7_SFX +void CThreadExtracting::ProcessWasFinished_GuiVirt() +{ + if (HashBundle && !Pairs.IsEmpty()) + ShowHashResults(Pairs, *this); +} +#endif + +HRESULT CThreadExtracting::ProcessVirt() +{ + CDecompressStat Stat; + + #ifndef Z7_SFX + /* + if (HashBundle) + HashBundle->Init(); + */ + #endif + + HRESULT res = Extract( + /* + #ifdef Z7_EXTERNAL_CODECS + externalCodecs, + #endif + */ + codecs, + *FormatIndices, *ExcludedFormatIndices, + *ArchivePaths, *ArchivePathsFull, + *WildcardCensor, *Options, + ExtractCallbackSpec, ExtractCallbackSpec, FolderArchiveExtractCallback, + #ifndef Z7_SFX + HashBundle, + #endif + FinalMessage.ErrorMessage.Message, Stat); + + #ifndef Z7_SFX + if (res == S_OK && ExtractCallbackSpec->IsOK()) + { + if (HashBundle) + { + AddValuePair(Pairs, IDS_ARCHIVES_COLON, Stat.NumArchives); + AddSizeValuePair(Pairs, IDS_PROP_PACKED_SIZE, Stat.PackSize); + AddHashBundleRes(Pairs, *HashBundle); + } + else if (Options->TestMode) + { + UString s; + + AddValuePair(s, IDS_ARCHIVES_COLON, Stat.NumArchives, false); + AddSizePair(s, IDS_PROP_PACKED_SIZE, Stat.PackSize); + + if (Stat.NumFolders != 0) + AddValuePair(s, IDS_PROP_FOLDERS, Stat.NumFolders); + AddValuePair(s, IDS_PROP_FILES, Stat.NumFiles); + AddSizePair(s, IDS_PROP_SIZE, Stat.UnpackSize); + if (Stat.NumAltStreams != 0) + { + s.Add_LF(); + AddValuePair(s, IDS_PROP_NUM_ALT_STREAMS, Stat.NumAltStreams); + AddSizePair(s, IDS_PROP_ALT_STREAMS_SIZE, Stat.AltStreams_UnpackSize); + } + s.Add_LF(); + AddLangString(s, IDS_MESSAGE_NO_ERRORS); + FinalMessage.OkMessage.Title = Title; + FinalMessage.OkMessage.Message = s; + } + } + #endif + + return res; +} + + + +HRESULT ExtractGUI( + // DECL_EXTERNAL_CODECS_LOC_VARS + CCodecs *codecs, + const CObjectVector &formatIndices, + const CIntVector &excludedFormatIndices, + UStringVector &archivePaths, + UStringVector &archivePathsFull, + const NWildcard::CCensorNode &wildcardCensor, + CExtractOptions &options, + #ifndef Z7_SFX + CHashBundle *hb, + #endif + bool showDialog, + bool &messageWasDisplayed, + CExtractCallbackImp *extractCallback, + HWND hwndParent) +{ + messageWasDisplayed = false; + + CThreadExtracting extracter; + /* + #ifdef Z7_EXTERNAL_CODECS + extracter.externalCodecs = _externalCodecs; + #endif + */ + extracter.codecs = codecs; + extracter.FormatIndices = &formatIndices; + extracter.ExcludedFormatIndices = &excludedFormatIndices; + + if (!options.TestMode) + { + FString outputDir = options.OutputDir; + #ifndef UNDER_CE + if (outputDir.IsEmpty()) + GetCurrentDir(outputDir); + #endif + if (showDialog) + { + CExtractDialog dialog; + FString outputDirFull; + if (!MyGetFullPathName(outputDir, outputDirFull)) + { + ShowErrorMessage(kIncorrectOutDir); + messageWasDisplayed = true; + return E_FAIL; + } + NName::NormalizeDirPathPrefix(outputDirFull); + + dialog.DirPath = fs2us(outputDirFull); + + dialog.OverwriteMode = options.OverwriteMode; + dialog.OverwriteMode_Force = options.OverwriteMode_Force; + dialog.PathMode = options.PathMode; + dialog.PathMode_Force = options.PathMode_Force; + dialog.ElimDup = options.ElimDup; + + if (archivePathsFull.Size() == 1) + dialog.ArcPath = archivePathsFull[0]; + + #ifndef Z7_SFX + // dialog.AltStreams = options.NtOptions.AltStreams; + dialog.NtSecurity = options.NtOptions.NtSecurity; + if (extractCallback->PasswordIsDefined) + dialog.Password = extractCallback->Password; + #endif + + if (dialog.Create(hwndParent) != IDOK) + return E_ABORT; + + outputDir = us2fs(dialog.DirPath); + + options.OverwriteMode = dialog.OverwriteMode; + options.PathMode = dialog.PathMode; + options.ElimDup = dialog.ElimDup; + + #ifndef Z7_SFX + // options.NtOptions.AltStreams = dialog.AltStreams; + options.NtOptions.NtSecurity = dialog.NtSecurity; + extractCallback->Password = dialog.Password; + extractCallback->PasswordIsDefined = !dialog.Password.IsEmpty(); + #endif + } + if (!MyGetFullPathName(outputDir, options.OutputDir)) + { + ShowErrorMessage(kIncorrectOutDir); + messageWasDisplayed = true; + return E_FAIL; + } + NName::NormalizeDirPathPrefix(options.OutputDir); + + /* + if (!CreateComplexDirectory(options.OutputDir)) + { + UString s = GetUnicodeString(NError::MyFormatMessage(GetLastError())); + UString s2 = MyFormatNew(IDS_CANNOT_CREATE_FOLDER, + #ifdef Z7_LANG + 0x02000603, + #endif + options.OutputDir); + s2.Add_LF(); + s2 += s; + MyMessageBox(s2); + return E_FAIL; + } + */ + } + + UString title = LangString(options.TestMode ? IDS_PROGRESS_TESTING : IDS_PROGRESS_EXTRACTING); + + extracter.Title = title; + extracter.ExtractCallbackSpec = extractCallback; + extracter.ExtractCallbackSpec->ProgressDialog = &extracter; + extracter.FolderArchiveExtractCallback = extractCallback; + extracter.ExtractCallbackSpec->Init(); + + extracter.CompressingMode = false; + + extracter.ArchivePaths = &archivePaths; + extracter.ArchivePathsFull = &archivePathsFull; + extracter.WildcardCensor = &wildcardCensor; + extracter.Options = &options; + #ifndef Z7_SFX + extracter.HashBundle = hb; + #endif + + extracter.IconID = IDI_ICON; + + RINOK(extracter.Create(title, hwndParent)) + messageWasDisplayed = extracter.ThreadFinishedOK && extracter.MessagesDisplayed; + return extracter.Result; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/GUI/ExtractGUI.h b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/ExtractGUI.h new file mode 100644 index 00000000..13ca6abf --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/ExtractGUI.h @@ -0,0 +1,39 @@ +// GUI/ExtractGUI.h + +#ifndef ZIP7_INC_EXTRACT_GUI_H +#define ZIP7_INC_EXTRACT_GUI_H + +#include "../Common/Extract.h" + +#include "../FileManager/ExtractCallback.h" + +/* + RESULT can be S_OK, even if there are errors!!! + if RESULT == S_OK, check extractCallback->IsOK() after ExtractGUI(). + + RESULT = E_ABORT - user break. + RESULT != E_ABORT: + { + messageWasDisplayed = true - message was displayed already. + messageWasDisplayed = false - there was some internal error, so you must show error message. + } +*/ + +HRESULT ExtractGUI( + // DECL_EXTERNAL_CODECS_LOC_VARS + CCodecs *codecs, + const CObjectVector &formatIndices, + const CIntVector &excludedFormatIndices, + UStringVector &archivePaths, + UStringVector &archivePathsFull, + const NWildcard::CCensorNode &wildcardCensor, + CExtractOptions &options, + #ifndef Z7_SFX + CHashBundle *hb, + #endif + bool showDialog, + bool &messageWasDisplayed, + CExtractCallbackImp *extractCallback, + HWND hwndParent = NULL); + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/GUI/ExtractRes.h b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/ExtractRes.h new file mode 100644 index 00000000..634ba6b5 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/ExtractRes.h @@ -0,0 +1,51 @@ +#define IDS_MEM_ERROR 3000 + +#define IDS_CANNOT_CREATE_FOLDER 3003 +#define IDS_UPDATE_NOT_SUPPORTED 3004 +#define IDS_CANT_OPEN_ARCHIVE 3005 +#define IDS_CANT_OPEN_ENCRYPTED_ARCHIVE 3006 +#define IDS_UNSUPPORTED_ARCHIVE_TYPE 3007 + +#define IDS_CANT_OPEN_AS_TYPE 3017 +#define IDS_IS_OPEN_AS_TYPE 3018 +#define IDS_IS_OPEN_WITH_OFFSET 3019 + +#define IDS_PROGRESS_EXTRACTING 3300 + +#define IDS_PROGRESS_SKIPPING 3325 + +#define IDS_EXTRACT_SET_FOLDER 3402 + +#define IDS_EXTRACT_PATHS_FULL 3411 +#define IDS_EXTRACT_PATHS_NO 3412 +#define IDS_EXTRACT_PATHS_ABS 3413 +#define IDS_PATH_MODE_RELAT 3414 + +#define IDS_EXTRACT_OVERWRITE_ASK 3421 +#define IDS_EXTRACT_OVERWRITE_WITHOUT_PROMPT 3422 +#define IDS_EXTRACT_OVERWRITE_SKIP_EXISTING 3423 +#define IDS_EXTRACT_OVERWRITE_RENAME 3424 +#define IDS_EXTRACT_OVERWRITE_RENAME_EXISTING 3425 + +#define IDS_EXTRACT_MESSAGE_UNSUPPORTED_METHOD 3700 +#define IDS_EXTRACT_MESSAGE_DATA_ERROR 3701 +#define IDS_EXTRACT_MESSAGE_CRC_ERROR 3702 +#define IDS_EXTRACT_MESSAGE_DATA_ERROR_ENCRYPTED 3703 +#define IDS_EXTRACT_MESSAGE_CRC_ERROR_ENCRYPTED 3704 + +#define IDS_EXTRACT_MSG_WRONG_PSW_GUESS 3710 +// #define IDS_EXTRACT_MSG_ENCRYPTED 3711 + +#define IDS_EXTRACT_MSG_UNSUPPORTED_METHOD 3721 +#define IDS_EXTRACT_MSG_DATA_ERROR 3722 +#define IDS_EXTRACT_MSG_CRC_ERROR 3723 +#define IDS_EXTRACT_MSG_UNAVAILABLE_DATA 3724 +#define IDS_EXTRACT_MSG_UEXPECTED_END 3725 +#define IDS_EXTRACT_MSG_DATA_AFTER_END 3726 +#define IDS_EXTRACT_MSG_IS_NOT_ARC 3727 +#define IDS_EXTRACT_MSG_HEADERS_ERROR 3728 +#define IDS_EXTRACT_MSG_WRONG_PSW_CLAIM 3729 + +#define IDS_OPEN_MSG_UNAVAILABLE_START 3763 +#define IDS_OPEN_MSG_UNCONFIRMED_START 3764 +#define IDS_OPEN_MSG_UNSUPPORTED_FEATURE 3768 diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/GUI/FM.ico b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/FM.ico new file mode 100644 index 00000000..3a0a34da Binary files /dev/null and b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/FM.ico differ diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/GUI/GUI.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/GUI.cpp new file mode 100644 index 00000000..6bb66936 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/GUI.cpp @@ -0,0 +1,494 @@ +// GUI.cpp + +#include "StdAfx.h" + +#ifdef _WIN32 +#include "../../../../C/DllSecur.h" +#endif + +#include "../../../Common/MyWindows.h" +#if defined(__MINGW32__) || defined(__MINGW64__) +#include +#else +#include +#endif + +#include "../../../Common/MyInitGuid.h" + +#include "../../../Common/CommandLineParser.h" +#include "../../../Common/MyException.h" +#include "../../../Common/StringConvert.h" + +#include "../../../Windows/FileDir.h" +#include "../../../Windows/NtCheck.h" + +#include "../Common/ArchiveCommandLine.h" +#include "../Common/ExitCode.h" + +#include "../FileManager/StringUtils.h" +#include "../FileManager/LangUtils.h" + +#include "BenchmarkDialog.h" +#include "ExtractGUI.h" +#include "HashGUI.h" +#include "UpdateGUI.h" + +#include "ExtractRes.h" + +using namespace NWindows; + +#ifdef Z7_EXTERNAL_CODECS +extern +const CExternalCodecs *g_ExternalCodecs_Ptr; +const CExternalCodecs *g_ExternalCodecs_Ptr; +#endif + +extern +HINSTANCE g_hInstance; +HINSTANCE g_hInstance; +extern +bool g_DisableUserQuestions; +bool g_DisableUserQuestions; + +#ifndef UNDER_CE + +#if !defined(Z7_WIN32_WINNT_MIN) || Z7_WIN32_WINNT_MIN < 0x0500 // win2000 +#define Z7_USE_DYN_ComCtl32Version +#endif + +#ifdef Z7_USE_DYN_ComCtl32Version +Z7_DIAGNOSTIC_IGNORE_CAST_FUNCTION + +extern +DWORD g_ComCtl32Version; +DWORD g_ComCtl32Version; + +static DWORD GetDllVersion(LPCTSTR dllName) +{ + DWORD dwVersion = 0; + const HMODULE hmodule = LoadLibrary(dllName); + if (hmodule) + { + const + DLLGETVERSIONPROC f_DllGetVersion = Z7_GET_PROC_ADDRESS( + DLLGETVERSIONPROC, hmodule, + "DllGetVersion"); + if (f_DllGetVersion) + { + DLLVERSIONINFO dvi; + ZeroMemory(&dvi, sizeof(dvi)); + dvi.cbSize = sizeof(dvi); + const HRESULT hr = (*f_DllGetVersion)(&dvi); + if (SUCCEEDED(hr)) + dwVersion = (DWORD)MAKELONG(dvi.dwMinorVersion, dvi.dwMajorVersion); + } + FreeLibrary(hmodule); + } + return dwVersion; +} + +#endif +#endif + +extern +bool g_LVN_ITEMACTIVATE_Support; +bool g_LVN_ITEMACTIVATE_Support = true; + +DECLARE_AND_SET_CLIENT_VERSION_VAR + +static void ErrorMessage(LPCWSTR message) +{ + if (!g_DisableUserQuestions) + MessageBoxW(NULL, message, L"7-Zip", MB_ICONERROR | MB_OK); +} + +static void ErrorMessage(const char *s) +{ + ErrorMessage(GetUnicodeString(s)); +} + +static void ErrorLangMessage(UINT resourceID) +{ + ErrorMessage(LangString(resourceID)); +} + +static const char * const kNoFormats = "7-Zip cannot find the code that works with archives."; + +static int ShowMemErrorMessage() +{ + ErrorLangMessage(IDS_MEM_ERROR); + return NExitCode::kMemoryError; +} + +static int ShowSysErrorMessage(HRESULT errorCode) +{ + if (errorCode == E_OUTOFMEMORY) + return ShowMemErrorMessage(); + ErrorMessage(HResultToMessage(errorCode)); + return NExitCode::kFatalError; +} + +static void ThrowException_if_Error(HRESULT res) +{ + if (res != S_OK) + throw CSystemException(res); +} + +static int Main2() +{ + UStringVector commandStrings; + NCommandLineParser::SplitCommandLine(GetCommandLineW(), commandStrings); + + #ifndef UNDER_CE + if (commandStrings.Size() > 0) + commandStrings.Delete(0); + #endif + if (commandStrings.Size() == 0) + { + MessageBoxW(NULL, L"Specify command", L"7-Zip", 0); + return 0; + } + + CArcCmdLineOptions options; + CArcCmdLineParser parser; + + parser.Parse1(commandStrings, options); + g_DisableUserQuestions = options.YesToAll; + parser.Parse2(options); + + CREATE_CODECS_OBJECT + + codecs->CaseSensitive_Change = options.CaseSensitive_Change; + codecs->CaseSensitive = options.CaseSensitive; + ThrowException_if_Error(codecs->Load()); + Codecs_AddHashArcHandler(codecs); + + #ifdef Z7_EXTERNAL_CODECS + { + g_ExternalCodecs_Ptr = &_externalCodecs; + UString s; + codecs->GetCodecsErrorMessage(s); + if (!s.IsEmpty()) + { + if (!g_DisableUserQuestions) + MessageBoxW(NULL, s, L"7-Zip", MB_ICONERROR); + } + + } + #endif + + const bool isExtractGroupCommand = options.Command.IsFromExtractGroup(); + + if (codecs->Formats.Size() == 0 && + (isExtractGroupCommand + + || options.Command.IsFromUpdateGroup())) + { + #ifdef Z7_EXTERNAL_CODECS + if (!codecs->MainDll_ErrorPath.IsEmpty()) + { + UString s ("7-Zip cannot load module: "); + s += fs2us(codecs->MainDll_ErrorPath); + throw s; + } + #endif + throw kNoFormats; + } + + CObjectVector formatIndices; + if (!ParseOpenTypes(*codecs, options.ArcType, formatIndices)) + { + ErrorLangMessage(IDS_UNSUPPORTED_ARCHIVE_TYPE); + return NExitCode::kFatalError; + } + + CIntVector excludedFormats; + FOR_VECTOR (k, options.ExcludedArcTypes) + { + CIntVector tempIndices; + if (!codecs->FindFormatForArchiveType(options.ExcludedArcTypes[k], tempIndices) + || tempIndices.Size() != 1) + { + ErrorLangMessage(IDS_UNSUPPORTED_ARCHIVE_TYPE); + return NExitCode::kFatalError; + } + excludedFormats.AddToUniqueSorted(tempIndices[0]); + // excludedFormats.Sort(); + } + + #ifdef Z7_EXTERNAL_CODECS + if (isExtractGroupCommand + || options.Command.IsFromUpdateGroup() + || options.Command.CommandType == NCommandType::kHash + || options.Command.CommandType == NCommandType::kBenchmark) + ThrowException_if_Error(_externalCodecs.Load()); + #endif + + if (options.Command.CommandType == NCommandType::kBenchmark) + { + HRESULT res = Benchmark( + EXTERNAL_CODECS_VARS_L + options.Properties, + options.NumIterations_Defined ? + options.NumIterations : + k_NumBenchIterations_Default); + /* + if (res == S_FALSE) + { + stdStream << "\nDecoding Error\n"; + return NExitCode::kFatalError; + } + */ + ThrowException_if_Error(res); + } + else if (isExtractGroupCommand) + { + UStringVector ArchivePathsSorted; + UStringVector ArchivePathsFullSorted; + + CExtractCallbackImp *ecs = new CExtractCallbackImp; + CMyComPtr extractCallback = ecs; + + #ifndef Z7_NO_CRYPTO + ecs->PasswordIsDefined = options.PasswordEnabled; + ecs->Password = options.Password; + #endif + + ecs->Init(); + + CExtractOptions eo; + (CExtractOptionsBase &)eo = options.ExtractOptions; + eo.StdInMode = options.StdInMode; + eo.StdOutMode = options.StdOutMode; + eo.YesToAll = options.YesToAll; + ecs->YesToAll = options.YesToAll; + eo.TestMode = options.Command.IsTestCommand(); + ecs->TestMode = eo.TestMode; + + #ifndef Z7_SFX + eo.Properties = options.Properties; + #endif + + bool messageWasDisplayed = false; + + #ifndef Z7_SFX + CHashBundle hb; + CHashBundle *hb_ptr = NULL; + + if (!options.HashMethods.IsEmpty()) + { + hb_ptr = &hb; + ThrowException_if_Error(hb.SetMethods(EXTERNAL_CODECS_VARS_L options.HashMethods)); + } + #endif + + { + CDirItemsStat st; + HRESULT hresultMain = EnumerateDirItemsAndSort( + options.arcCensor, + NWildcard::k_RelatPath, + UString(), // addPathPrefix + ArchivePathsSorted, + ArchivePathsFullSorted, + st, + NULL // &scan: change it!!!! + ); + if (hresultMain != S_OK) + { + /* + if (hresultMain != E_ABORT && messageWasDisplayed) + return NExitCode::kFatalError; + */ + throw CSystemException(hresultMain); + } + } + + ecs->MultiArcMode = (ArchivePathsSorted.Size() > 1); + + HRESULT result = ExtractGUI( + // EXTERNAL_CODECS_VARS_L + codecs, + formatIndices, excludedFormats, + ArchivePathsSorted, + ArchivePathsFullSorted, + options.Censor.Pairs.Front().Head, + eo, + #ifndef Z7_SFX + hb_ptr, + #endif + options.ShowDialog, messageWasDisplayed, ecs); + if (result != S_OK) + { + if (result != E_ABORT && messageWasDisplayed) + return NExitCode::kFatalError; + throw CSystemException(result); + } + if (!ecs->IsOK()) + return NExitCode::kFatalError; + } + else if (options.Command.IsFromUpdateGroup()) + { + #ifndef Z7_NO_CRYPTO + bool passwordIsDefined = options.PasswordEnabled && !options.Password.IsEmpty(); + #endif + + CUpdateCallbackGUI callback; + // callback.EnablePercents = options.EnablePercents; + + #ifndef Z7_NO_CRYPTO + callback.PasswordIsDefined = passwordIsDefined; + callback.AskPassword = options.PasswordEnabled && options.Password.IsEmpty(); + callback.Password = options.Password; + #endif + + // callback.StdOutMode = options.UpdateOptions.StdOutMode; + callback.Init(); + + if (!options.UpdateOptions.InitFormatIndex(codecs, formatIndices, options.ArchiveName) || + !options.UpdateOptions.SetArcPath(codecs, options.ArchiveName)) + { + ErrorLangMessage(IDS_UPDATE_NOT_SUPPORTED); + return NExitCode::kFatalError; + } + bool messageWasDisplayed = false; + HRESULT result = UpdateGUI( + codecs, formatIndices, + options.ArchiveName, + options.Censor, + options.UpdateOptions, + options.ShowDialog, + messageWasDisplayed, + &callback); + + if (result != S_OK) + { + if (result != E_ABORT && messageWasDisplayed) + return NExitCode::kFatalError; + throw CSystemException(result); + } + if (callback.FailedFiles.Size() > 0) + { + if (!messageWasDisplayed) + throw CSystemException(E_FAIL); + return NExitCode::kWarning; + } + } + else if (options.Command.CommandType == NCommandType::kHash) + { + bool messageWasDisplayed = false; + HRESULT result = HashCalcGUI(EXTERNAL_CODECS_VARS_L + options.Censor, options.HashOptions, messageWasDisplayed); + + if (result != S_OK) + { + if (result != E_ABORT && messageWasDisplayed) + return NExitCode::kFatalError; + throw CSystemException(result); + } + /* + if (callback.FailedFiles.Size() > 0) + { + if (!messageWasDisplayed) + throw CSystemException(E_FAIL); + return NExitCode::kWarning; + } + */ + } + else + { + throw "Unsupported command"; + } + return 0; +} + +#if defined(_UNICODE) && !defined(_WIN64) && !defined(UNDER_CE) +#define NT_CHECK_FAIL_ACTION ErrorMessage("Unsupported Windows version"); return NExitCode::kFatalError; +#endif + +int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE /* hPrevInstance */, + #ifdef UNDER_CE + LPWSTR + #else + LPSTR + #endif + /* lpCmdLine */, int /* nCmdShow */) +{ + g_hInstance = hInstance; + + #ifdef _WIN32 + NT_CHECK + #endif + + InitCommonControls(); + +#ifdef Z7_USE_DYN_ComCtl32Version + g_ComCtl32Version = ::GetDllVersion(TEXT("comctl32.dll")); + g_LVN_ITEMACTIVATE_Support = (g_ComCtl32Version >= MAKELONG(71, 4)); +#endif + + // OleInitialize is required for ProgressBar in TaskBar. + #ifndef UNDER_CE + OleInitialize(NULL); + #endif + + #ifdef Z7_LANG + LoadLangOneTime(); + #endif + + // setlocale(LC_COLLATE, ".ACP"); + try + { + #ifdef _WIN32 + My_SetDefaultDllDirectories(); + #endif + + return Main2(); + } + catch(const CNewException &) + { + return ShowMemErrorMessage(); + } + catch(const CMessagePathException &e) + { + ErrorMessage(e); + return NExitCode::kUserError; + } + catch(const CSystemException &systemError) + { + if (systemError.ErrorCode == E_ABORT) + return NExitCode::kUserBreak; + return ShowSysErrorMessage(systemError.ErrorCode); + } + catch(const UString &s) + { + ErrorMessage(s); + return NExitCode::kFatalError; + } + catch(const AString &s) + { + ErrorMessage(s); + return NExitCode::kFatalError; + } + catch(const wchar_t *s) + { + ErrorMessage(s); + return NExitCode::kFatalError; + } + catch(const char *s) + { + ErrorMessage(s); + return NExitCode::kFatalError; + } + catch(int v) + { + AString e ("Error: "); + e.Add_UInt32((unsigned)v); + ErrorMessage(e); + return NExitCode::kFatalError; + } + catch(...) + { + ErrorMessage("Unknown error"); + return NExitCode::kFatalError; + } +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/GUI/GUI.dsp b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/GUI.dsp new file mode 100644 index 00000000..2e25c067 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/GUI.dsp @@ -0,0 +1,1275 @@ +# Microsoft Developer Studio Project File - Name="GUI" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Application" 0x0101 + +CFG=GUI - Win32 DebugU +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "GUI.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "GUI.mak" CFG="GUI - Win32 DebugU" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "GUI - Win32 Release" (based on "Win32 (x86) Application") +!MESSAGE "GUI - Win32 Debug" (based on "Win32 (x86) Application") +!MESSAGE "GUI - Win32 ReleaseU" (based on "Win32 (x86) Application") +!MESSAGE "GUI - Win32 DebugU" (based on "Win32 (x86) Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "GUI - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /Yu"stdafx.h" /FD /c +# ADD CPP /nologo /Gr /MD /W4 /WX /GX /O1 /D "NDEBUG" /D "_MBCS" /D "WIN32" /D "_WINDOWS" /D "Z7_LANG" /D "Z7_LONG_PATH" /D "Z7_EXTERNAL_CODECS" /D "Z7_DEVICE_FILE" /FAcs /Yu"stdafx.h" /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "NDEBUG" +# ADD RSC /l 0x419 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib comctl32.lib htmlhelp.lib /nologo /subsystem:windows /machine:I386 /out:"C:\Util\7zg.exe" /opt:NOWIN98 +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "GUI - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /Yu"stdafx.h" /FD /GZ /c +# ADD CPP /nologo /Gr /MDd /W4 /WX /Gm /GX /ZI /Od /D "_DEBUG" /D "_MBCS" /D "WIN32" /D "_WINDOWS" /D "Z7_LANG" /D "Z7_LONG_PATH" /D "Z7_EXTERNAL_CODECS" /D "Z7_DEVICE_FILE" /Yu"stdafx.h" /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "_DEBUG" +# ADD RSC /l 0x419 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib comctl32.lib htmlhelp.lib /nologo /subsystem:windows /debug /machine:I386 /out:"C:\Util\7zg.exe" /pdbtype:sept + +!ELSEIF "$(CFG)" == "GUI - Win32 ReleaseU" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "ReleaseU" +# PROP BASE Intermediate_Dir "ReleaseU" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "ReleaseU" +# PROP Intermediate_Dir "ReleaseU" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MD /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /Yu"stdafx.h" /FD /c +# ADD CPP /nologo /Gr /MD /W4 /WX /GX /O1 /D "NDEBUG" /D "_UNICODE" /D "UNICODE" /D "WIN32" /D "_WINDOWS" /D "Z7_LANG" /D "Z7_LONG_PATH" /D "Z7_EXTERNAL_CODECS" /D "Z7_DEVICE_FILE" /Yu"stdafx.h" /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "NDEBUG" +# ADD RSC /l 0x419 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386 /out:"C:\UTIL\7zg.exe" +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib comctl32.lib htmlhelp.lib /nologo /subsystem:windows /machine:I386 /out:"C:\Util\7zg.exe" /opt:NOWIN98 +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "GUI - Win32 DebugU" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "DebugU" +# PROP BASE Intermediate_Dir "DebugU" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "DebugU" +# PROP Intermediate_Dir "DebugU" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MDd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /Yu"stdafx.h" /FD /GZ /c +# ADD CPP /nologo /Gr /MDd /W4 /WX /Gm /GX /ZI /Od /D "_DEBUG" /D "_UNICODE" /D "UNICODE" /D "WIN32" /D "_WINDOWS" /D "Z7_LANG" /D "Z7_LONG_PATH" /D "Z7_EXTERNAL_CODECS" /D "Z7_DEVICE_FILE" /Yu"stdafx.h" /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "_DEBUG" +# ADD RSC /l 0x419 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /out:"C:\UTIL\7zg.exe" /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib comctl32.lib htmlhelp.lib /nologo /subsystem:windows /debug /machine:I386 /out:"C:\Util\7zg.exe" /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "GUI - Win32 Release" +# Name "GUI - Win32 Debug" +# Name "GUI - Win32 ReleaseU" +# Name "GUI - Win32 DebugU" +# Begin Group "Spec" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=.\7zG.exe.manifest +# End Source File +# Begin Source File + +SOURCE=.\ExtractRes.h +# End Source File +# Begin Source File + +SOURCE=.\FM.ico +# End Source File +# Begin Source File + +SOURCE=.\resource.rc +# End Source File +# Begin Source File + +SOURCE=.\StdAfx.cpp +# ADD CPP /Yc"stdafx.h" +# End Source File +# Begin Source File + +SOURCE=.\StdAfx.h +# End Source File +# End Group +# Begin Group "UI Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\Common\ArchiveCommandLine.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\ArchiveCommandLine.h +# End Source File +# Begin Source File + +SOURCE=..\Common\ArchiveExtractCallback.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\ArchiveExtractCallback.h +# End Source File +# Begin Source File + +SOURCE=..\Common\ArchiveOpenCallback.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\ArchiveOpenCallback.h +# End Source File +# Begin Source File + +SOURCE=..\Common\Bench.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\Bench.h +# End Source File +# Begin Source File + +SOURCE=..\Common\DefaultName.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\DefaultName.h +# End Source File +# Begin Source File + +SOURCE=..\Common\DirItem.h +# End Source File +# Begin Source File + +SOURCE=..\Common\EnumDirItems.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\EnumDirItems.h +# End Source File +# Begin Source File + +SOURCE=..\Common\ExitCode.h +# End Source File +# Begin Source File + +SOURCE=..\Common\Extract.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\Extract.h +# End Source File +# Begin Source File + +SOURCE=..\Common\ExtractingFilePath.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\ExtractingFilePath.h +# End Source File +# Begin Source File + +SOURCE=..\Common\ExtractMode.h +# End Source File +# Begin Source File + +SOURCE=..\Common\HashCalc.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\HashCalc.h +# End Source File +# Begin Source File + +SOURCE=..\Common\IFileExtractCallback.h +# End Source File +# Begin Source File + +SOURCE=..\Common\LoadCodecs.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\LoadCodecs.h +# End Source File +# Begin Source File + +SOURCE=..\Common\OpenArchive.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\OpenArchive.h +# End Source File +# Begin Source File + +SOURCE=..\Common\Property.h +# End Source File +# Begin Source File + +SOURCE=..\Common\PropIDUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\PropIDUtils.h +# End Source File +# Begin Source File + +SOURCE=..\Common\SetProperties.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\SetProperties.h +# End Source File +# Begin Source File + +SOURCE=..\Common\SortUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\SortUtils.h +# End Source File +# Begin Source File + +SOURCE=..\Common\TempFiles.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\TempFiles.h +# End Source File +# Begin Source File + +SOURCE=..\Common\Update.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\Update.h +# End Source File +# Begin Source File + +SOURCE=..\Common\UpdateAction.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\UpdateAction.h +# End Source File +# Begin Source File + +SOURCE=..\Common\UpdateCallback.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\UpdateCallback.h +# End Source File +# Begin Source File + +SOURCE=..\Common\UpdatePair.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\UpdatePair.h +# End Source File +# Begin Source File + +SOURCE=..\Common\UpdateProduce.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\UpdateProduce.h +# End Source File +# Begin Source File + +SOURCE=..\Common\WorkDir.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\WorkDir.h +# End Source File +# Begin Source File + +SOURCE=..\Common\ZipRegistry.cpp +# End Source File +# Begin Source File + +SOURCE=..\Common\ZipRegistry.h +# End Source File +# End Group +# Begin Group "Explorer" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\Explorer\MyMessages.cpp +# End Source File +# Begin Source File + +SOURCE=..\Explorer\MyMessages.h +# End Source File +# End Group +# Begin Group "Dialogs" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=.\BenchmarkDialog.cpp +# End Source File +# Begin Source File + +SOURCE=.\BenchmarkDialog.h +# End Source File +# Begin Source File + +SOURCE=..\FileManager\BrowseDialog.cpp +# End Source File +# Begin Source File + +SOURCE=..\FileManager\BrowseDialog.h +# End Source File +# Begin Source File + +SOURCE=..\FileManager\ComboDialog.cpp +# End Source File +# Begin Source File + +SOURCE=..\FileManager\ComboDialog.h +# End Source File +# Begin Source File + +SOURCE=.\CompressDialog.cpp +# End Source File +# Begin Source File + +SOURCE=.\CompressDialog.h +# End Source File +# Begin Source File + +SOURCE=..\FileManager\EditDialog.cpp +# End Source File +# Begin Source File + +SOURCE=..\FileManager\EditDialog.h +# End Source File +# Begin Source File + +SOURCE=.\ExtractDialog.cpp +# End Source File +# Begin Source File + +SOURCE=.\ExtractDialog.h +# End Source File +# Begin Source File + +SOURCE=..\FileManager\ListViewDialog.cpp +# End Source File +# Begin Source File + +SOURCE=..\FileManager\ListViewDialog.h +# End Source File +# Begin Source File + +SOURCE=..\FileManager\MemDialog.cpp +# End Source File +# Begin Source File + +SOURCE=..\FileManager\MemDialog.h +# End Source File +# Begin Source File + +SOURCE=..\FileManager\OverwriteDialog.cpp +# End Source File +# Begin Source File + +SOURCE=..\FileManager\OverwriteDialog.h +# End Source File +# Begin Source File + +SOURCE=..\FileManager\PasswordDialog.cpp +# End Source File +# Begin Source File + +SOURCE=..\FileManager\PasswordDialog.h +# End Source File +# Begin Source File + +SOURCE=..\FileManager\ProgressDialog2.cpp +# End Source File +# Begin Source File + +SOURCE=..\FileManager\ProgressDialog2.h +# End Source File +# End Group +# Begin Group "FM Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\FileManager\ExtractCallback.cpp +# End Source File +# Begin Source File + +SOURCE=..\FileManager\ExtractCallback.h +# End Source File +# Begin Source File + +SOURCE=..\FileManager\FormatUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\FileManager\FormatUtils.h +# End Source File +# Begin Source File + +SOURCE=..\FileManager\HelpUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\FileManager\HelpUtils.h +# End Source File +# Begin Source File + +SOURCE=..\FileManager\LangUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\FileManager\LangUtils.h +# End Source File +# Begin Source File + +SOURCE=..\FileManager\OpenCallback.cpp +# End Source File +# Begin Source File + +SOURCE=..\FileManager\OpenCallback.h +# End Source File +# Begin Source File + +SOURCE=..\FileManager\ProgramLocation.cpp +# End Source File +# Begin Source File + +SOURCE=..\FileManager\ProgramLocation.h +# End Source File +# Begin Source File + +SOURCE=..\FileManager\PropertyName.cpp +# End Source File +# Begin Source File + +SOURCE=..\FileManager\PropertyName.h +# End Source File +# Begin Source File + +SOURCE=..\FileManager\RegistryUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\FileManager\RegistryUtils.h +# End Source File +# Begin Source File + +SOURCE=..\FileManager\SplitUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\FileManager\SplitUtils.h +# End Source File +# Begin Source File + +SOURCE=..\FileManager\StringUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\FileManager\StringUtils.h +# End Source File +# Begin Source File + +SOURCE=..\FileManager\SysIconUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\FileManager\SysIconUtils.h +# End Source File +# End Group +# Begin Group "Engine" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=.\ExtractGUI.cpp +# End Source File +# Begin Source File + +SOURCE=.\ExtractGUI.h +# End Source File +# Begin Source File + +SOURCE=.\GUI.cpp +# End Source File +# Begin Source File + +SOURCE=.\HashGUI.cpp +# End Source File +# Begin Source File + +SOURCE=.\HashGUI.h +# End Source File +# Begin Source File + +SOURCE=.\UpdateCallbackGUI.cpp +# End Source File +# Begin Source File + +SOURCE=.\UpdateCallbackGUI.h +# End Source File +# Begin Source File + +SOURCE=.\UpdateCallbackGUI2.cpp +# End Source File +# Begin Source File + +SOURCE=.\UpdateCallbackGUI2.h +# End Source File +# Begin Source File + +SOURCE=.\UpdateGUI.cpp +# End Source File +# Begin Source File + +SOURCE=.\UpdateGUI.h +# End Source File +# End Group +# Begin Group "7-zip Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Common\CreateCoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\CreateCoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FilePathAutoRename.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FilePathAutoRename.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FileStreams.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FileStreams.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FilterCoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\FilterCoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\LimitedStreams.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\LimitedStreams.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\MethodProps.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\MethodProps.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\MultiOutStream.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\MultiOutStream.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\ProgressUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\ProgressUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\PropId.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamObjects.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamObjects.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\StreamUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\Common\UniqBlocks.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Common\UniqBlocks.h +# End Source File +# End Group +# Begin Group "Compress" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Compress\CopyCoder.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Compress\CopyCoder.h +# End Source File +# End Group +# Begin Group "C" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\..\C\7zCrc.c + +!IF "$(CFG)" == "GUI - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "GUI - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "GUI - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "GUI - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zCrc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zCrcOpt.c + +!IF "$(CFG)" == "GUI - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "GUI - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "GUI - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "GUI - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zTypes.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zVersion.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\7zWindows.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Alloc.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Alloc.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Compiler.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\CpuArch.c + +!IF "$(CFG)" == "GUI - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "GUI - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "GUI - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "GUI - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\CpuArch.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\DllSecur.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\DllSecur.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Precomp.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Sort.c + +!IF "$(CFG)" == "GUI - Win32 Release" + +# ADD CPP /O2 +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "GUI - Win32 Debug" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "GUI - Win32 ReleaseU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ELSEIF "$(CFG)" == "GUI - Win32 DebugU" + +# SUBTRACT CPP /YX /Yc /Yu + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Sort.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Threads.c +# SUBTRACT CPP /YX /Yc /Yu +# End Source File +# Begin Source File + +SOURCE=..\..\..\..\C\Threads.h +# End Source File +# End Group +# Begin Group "Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\Common\CommandLineParser.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\CommandLineParser.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Common.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\CRC.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\DynLimBuf.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\DynLimBuf.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\IntToString.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\IntToString.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Lang.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Lang.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\ListFileUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\ListFileUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyBuffer.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyString.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyString.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyVector.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyVector.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\MyWindows.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\NewHandler.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\NewHandler.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StringConvert.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StringConvert.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StringToInt.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\StringToInt.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\UTFConvert.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\UTFConvert.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Wildcard.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Common\Wildcard.h +# End Source File +# End Group +# Begin Group "Windows" + +# PROP Default_Filter "" +# Begin Group "Control" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\..\Windows\Control\ComboBox.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\ComboBox.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\Dialog.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\Dialog.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\Edit.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\ListView.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\ListView.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\ProgressBar.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Control\Static.h +# End Source File +# End Group +# Begin Source File + +SOURCE=..\..\..\Windows\Clipboard.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Clipboard.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\COM.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\CommonDialog.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\CommonDialog.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\DLL.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\DLL.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\ErrorMsg.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\ErrorMsg.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileDir.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileDir.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileFind.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileFind.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileIO.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileIO.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileLink.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileMapping.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileName.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileName.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileSystem.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\FileSystem.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\MemoryGlobal.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\MemoryGlobal.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\MemoryLock.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\MemoryLock.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\PropVariant.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\PropVariant.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\PropVariantConv.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\PropVariantConv.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Registry.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Registry.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\ResourceString.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\ResourceString.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Shell.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Shell.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Synchronization.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Synchronization.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\System.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\System.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\SystemInfo.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\SystemInfo.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Thread.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\TimeUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\TimeUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Window.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\..\Windows\Window.h +# End Source File +# End Group +# Begin Group "Archive Common" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Archive\Common\ItemNameUtils.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\ItemNameUtils.h +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\OutStreamWithCRC.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\Archive\Common\OutStreamWithCRC.h +# End Source File +# End Group +# Begin Group "7-Zip" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=..\..\Archive\IArchive.h +# End Source File +# Begin Source File + +SOURCE=..\..\ICoder.h +# End Source File +# Begin Source File + +SOURCE=..\..\IDecl.h +# End Source File +# Begin Source File + +SOURCE=..\..\IStream.h +# End Source File +# End Group +# End Target +# End Project diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/GUI/GUI.dsw b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/GUI.dsw new file mode 100644 index 00000000..85d33484 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/GUI.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "GUI"=.\GUI.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/GUI/HashGUI.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/HashGUI.cpp new file mode 100644 index 00000000..231bab58 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/HashGUI.cpp @@ -0,0 +1,341 @@ +// HashGUI.cpp + +#include "StdAfx.h" + +#include "../../../Common/IntToString.h" +#include "../../../Common/StringConvert.h" + +#include "../../../Windows/ErrorMsg.h" + +#include "../FileManager/FormatUtils.h" +#include "../FileManager/LangUtils.h" +#include "../FileManager/ListViewDialog.h" +#include "../FileManager/OverwriteDialogRes.h" +#include "../FileManager/ProgressDialog2.h" +#include "../FileManager/ProgressDialog2Res.h" +#include "../FileManager/PropertyNameRes.h" +#include "../FileManager/resourceGui.h" + +#include "HashGUI.h" + +using namespace NWindows; + + + +class CHashCallbackGUI Z7_final: public CProgressThreadVirt, public IHashCallbackUI +{ + UInt64 NumFiles; + bool _curIsFolder; + UString FirstFileName; + // UString MainPath; + + CPropNameValPairs PropNameValPairs; + + HRESULT ProcessVirt() Z7_override; + virtual void ProcessWasFinished_GuiVirt() Z7_override; + +public: + const NWildcard::CCensor *censor; + const CHashOptions *options; + + DECL_EXTERNAL_CODECS_LOC_VARS_DECL + + Z7_IFACE_IMP(IDirItemsCallback) + Z7_IFACE_IMP(IHashCallbackUI) + + /* + void AddErrorMessage(DWORD systemError, const wchar_t *name) + { + Sync.AddError_Code_Name(systemError, name); + } + */ + void AddErrorMessage(HRESULT systemError, const wchar_t *name) + { + Sync.AddError_Code_Name(systemError, name); + } +}; + + +void AddValuePair(CPropNameValPairs &pairs, UINT resourceID, UInt64 value) +{ + CProperty &pair = pairs.AddNew(); + AddLangString(pair.Name, resourceID); + char sz[32]; + ConvertUInt64ToString(value, sz); + pair.Value = sz; +} + + +void AddSizeValuePair(CPropNameValPairs &pairs, UINT resourceID, UInt64 value) +{ + CProperty &pair = pairs.AddNew(); + LangString(resourceID, pair.Name); + AddSizeValue(pair.Value, value); +} + + +HRESULT CHashCallbackGUI::StartScanning() +{ + CProgressSync &sync = Sync; + sync.Set_Status(LangString(IDS_SCANNING)); + return CheckBreak(); +} + +HRESULT CHashCallbackGUI::ScanProgress(const CDirItemsStat &st, const FString &path, bool isDir) +{ + return Sync.ScanProgress(st.NumFiles, st.GetTotalBytes(), path, isDir); +} + +HRESULT CHashCallbackGUI::ScanError(const FString &path, DWORD systemError) +{ + AddErrorMessage(HRESULT_FROM_WIN32(systemError), fs2us(path)); + return CheckBreak(); +} + +HRESULT CHashCallbackGUI::FinishScanning(const CDirItemsStat &st) +{ + return ScanProgress(st, FString(), false); // isDir +} + +HRESULT CHashCallbackGUI::CheckBreak() +{ + return Sync.CheckStop(); +} + +HRESULT CHashCallbackGUI::SetNumFiles(UInt64 numFiles) +{ + CProgressSync &sync = Sync; + sync.Set_NumFilesTotal(numFiles); + return CheckBreak(); +} + +HRESULT CHashCallbackGUI::SetTotal(UInt64 size) +{ + CProgressSync &sync = Sync; + sync.Set_NumBytesTotal(size); + return CheckBreak(); +} + +HRESULT CHashCallbackGUI::SetCompleted(const UInt64 *completed) +{ + return Sync.Set_NumBytesCur(completed); +} + +HRESULT CHashCallbackGUI::BeforeFirstFile(const CHashBundle & /* hb */) +{ + return S_OK; +} + +HRESULT CHashCallbackGUI::GetStream(const wchar_t *name, bool isFolder) +{ + if (NumFiles == 0) + FirstFileName = name; + _curIsFolder = isFolder; + CProgressSync &sync = Sync; + sync.Set_FilePath(name, isFolder); + return CheckBreak(); +} + +HRESULT CHashCallbackGUI::OpenFileError(const FString &path, DWORD systemError) +{ + // if (systemError == ERROR_SHARING_VIOLATION) + { + AddErrorMessage(HRESULT_FROM_WIN32(systemError), fs2us(path)); + return S_FALSE; + } + // return systemError; +} + +HRESULT CHashCallbackGUI::SetOperationResult(UInt64 /* fileSize */, const CHashBundle & /* hb */, bool /* showHash */) +{ + CProgressSync &sync = Sync; + if (!_curIsFolder) + NumFiles++; + sync.Set_NumFilesCur(NumFiles); + return CheckBreak(); +} + +static const unsigned k_DigestStringSize = k_HashCalc_DigestSize_Max * 2 + k_HashCalc_ExtraSize * 2 + 16; + +static void AddHashString(CProperty &s, const CHasherState &h, unsigned digestIndex) +{ + char temp[k_DigestStringSize]; + h.WriteToString(digestIndex, temp); + s.Value = temp; +} + +static void AddHashResString(CPropNameValPairs &s, const CHasherState &h, unsigned digestIndex, UInt32 resID) +{ + CProperty &pair = s.AddNew(); + UString &s2 = pair.Name; + LangString(resID, s2); + UString name (h.Name); + s2.Replace(L"CRC", name); + s2.Replace(L":", L""); + AddHashString(pair, h, digestIndex); +} + + +void AddHashBundleRes(CPropNameValPairs &s, const CHashBundle &hb) +{ + if (hb.NumErrors != 0) + AddValuePair(s, IDS_PROP_NUM_ERRORS, hb.NumErrors); + + if (hb.NumFiles == 1 && hb.NumDirs == 0 && !hb.FirstFileName.IsEmpty()) + { + CProperty &pair = s.AddNew(); + LangString(IDS_PROP_NAME, pair.Name); + pair.Value = hb.FirstFileName; + } + else + { + if (!hb.MainName.IsEmpty()) + { + CProperty &pair = s.AddNew(); + LangString(IDS_PROP_NAME, pair.Name); + pair.Value = hb.MainName; + } + if (hb.NumDirs != 0) + AddValuePair(s, IDS_PROP_FOLDERS, hb.NumDirs); + AddValuePair(s, IDS_PROP_FILES, hb.NumFiles); + } + + AddSizeValuePair(s, IDS_PROP_SIZE, hb.FilesSize); + + if (hb.NumAltStreams != 0) + { + AddValuePair(s, IDS_PROP_NUM_ALT_STREAMS, hb.NumAltStreams); + AddSizeValuePair(s, IDS_PROP_ALT_STREAMS_SIZE, hb.AltStreamsSize); + } + + FOR_VECTOR (i, hb.Hashers) + { + const CHasherState &h = hb.Hashers[i]; + if (hb.NumFiles == 1 && hb.NumDirs == 0) + { + CProperty &pair = s.AddNew(); + pair.Name += h.Name; + AddHashString(pair, h, k_HashCalc_Index_DataSum); + } + else + { + AddHashResString(s, h, k_HashCalc_Index_DataSum, IDS_CHECKSUM_CRC_DATA); + AddHashResString(s, h, k_HashCalc_Index_NamesSum, IDS_CHECKSUM_CRC_DATA_NAMES); + } + if (hb.NumAltStreams != 0) + { + AddHashResString(s, h, k_HashCalc_Index_StreamsSum, IDS_CHECKSUM_CRC_STREAMS_NAMES); + } + } +} + + +void AddHashBundleRes(UString &s, const CHashBundle &hb) +{ + CPropNameValPairs pairs; + AddHashBundleRes(pairs, hb); + + FOR_VECTOR (i, pairs) + { + const CProperty &pair = pairs[i]; + s += pair.Name; + s += ": "; + s += pair.Value; + s.Add_LF(); + } + + if (hb.NumErrors == 0 && hb.Hashers.IsEmpty()) + { + s.Add_LF(); + AddLangString(s, IDS_MESSAGE_NO_ERRORS); + s.Add_LF(); + } +} + + +HRESULT CHashCallbackGUI::AfterLastFile(CHashBundle &hb) +{ + hb.FirstFileName = FirstFileName; + // MainPath + AddHashBundleRes(PropNameValPairs, hb); + + CProgressSync &sync = Sync; + sync.Set_NumFilesCur(hb.NumFiles); + + // CProgressMessageBoxPair &pair = GetMessagePair(hb.NumErrors != 0); + // pair.Message = s; + // LangString(IDS_CHECKSUM_INFORMATION, pair.Title); + + return S_OK; +} + + +HRESULT CHashCallbackGUI::ProcessVirt() +{ + NumFiles = 0; + AString errorInfo; + HRESULT res = HashCalc(EXTERNAL_CODECS_LOC_VARS + *censor, *options, errorInfo, this); + return res; +} + + +HRESULT HashCalcGUI( + DECL_EXTERNAL_CODECS_LOC_VARS + const NWildcard::CCensor &censor, + const CHashOptions &options, + bool &messageWasDisplayed) +{ + CHashCallbackGUI t; + #ifdef Z7_EXTERNAL_CODECS + t._externalCodecs = _externalCodecs; + #endif + t.censor = &censor; + t.options = &options; + + t.ShowCompressionInfo = false; + + const UString title = LangString(IDS_CHECKSUM_CALCULATING); + + t.MainTitle = "7-Zip"; // LangString(IDS_APP_TITLE); + t.MainAddTitle = title; + t.MainAddTitle.Add_Space(); + + RINOK(t.Create(title)) + messageWasDisplayed = t.ThreadFinishedOK && t.MessagesDisplayed; + return S_OK; +} + + +void ShowHashResults(const CPropNameValPairs &propPairs, HWND hwnd) +{ + CListViewDialog lv; + + FOR_VECTOR (i, propPairs) + { + const CProperty &pair = propPairs[i]; + lv.Strings.Add(pair.Name); + lv.Values.Add(pair.Value); + } + + lv.Title = LangString(IDS_CHECKSUM_INFORMATION); + lv.DeleteIsAllowed = true; + lv.SelectFirst = false; + lv.NumColumns = 2; + + lv.Create(hwnd); +} + + +void ShowHashResults(const CHashBundle &hb, HWND hwnd) +{ + CPropNameValPairs propPairs; + AddHashBundleRes(propPairs, hb); + ShowHashResults(propPairs, hwnd); +} + +void CHashCallbackGUI::ProcessWasFinished_GuiVirt() +{ + if (Result != E_ABORT) + ShowHashResults(PropNameValPairs, *this); +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/GUI/HashGUI.h b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/HashGUI.h new file mode 100644 index 00000000..1ec9c475 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/HashGUI.h @@ -0,0 +1,27 @@ +// HashGUI.h + +#ifndef ZIP7_INC_HASH_GUI_H +#define ZIP7_INC_HASH_GUI_H + +#include "../Common/HashCalc.h" +#include "../Common/Property.h" + +HRESULT HashCalcGUI( + DECL_EXTERNAL_CODECS_LOC_VARS + const NWildcard::CCensor &censor, + const CHashOptions &options, + bool &messageWasDisplayed); + +typedef CObjectVector CPropNameValPairs; + +void AddValuePair(CPropNameValPairs &pairs, UINT resourceID, UInt64 value); +void AddSizeValue(UString &s, UInt64 value); +void AddSizeValuePair(CPropNameValPairs &pairs, UINT resourceID, UInt64 value); + +void AddHashBundleRes(CPropNameValPairs &s, const CHashBundle &hb); +void AddHashBundleRes(UString &s, const CHashBundle &hb); + +void ShowHashResults(const CPropNameValPairs &propPairs, HWND hwnd); +void ShowHashResults(const CHashBundle &hb, HWND hwnd); + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/GUI/StdAfx.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/StdAfx.cpp new file mode 100644 index 00000000..d0feea85 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/StdAfx.cpp @@ -0,0 +1,3 @@ +// StdAfx.cpp + +#include "StdAfx.h" diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/GUI/StdAfx.h b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/StdAfx.h new file mode 100644 index 00000000..130db8aa --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/StdAfx.h @@ -0,0 +1,6 @@ +// StdAfx.h + +#if _MSC_VER >= 1800 +#pragma warning(disable : 4464) // relative include path contains '..' +#endif +#include "../FileManager/StdAfx.h" diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/GUI/UpdateCallbackGUI.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/UpdateCallbackGUI.cpp new file mode 100644 index 00000000..424c6e49 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/UpdateCallbackGUI.cpp @@ -0,0 +1,295 @@ +// UpdateCallbackGUI.cpp + +#include "StdAfx.h" + +#include "../../../Common/IntToString.h" +#include "../../../Common/StringConvert.h" + +#include "../../../Windows/PropVariant.h" + +#include "../FileManager/FormatUtils.h" +#include "../FileManager/LangUtils.h" + +#include "../FileManager/resourceGui.h" + +#include "resource2.h" + +#include "UpdateCallbackGUI.h" + +using namespace NWindows; + +// CUpdateCallbackGUI::~CUpdateCallbackGUI() {} + +void CUpdateCallbackGUI::Init() +{ + CUpdateCallbackGUI2::Init(); + FailedFiles.Clear(); +} + +void OpenResult_GUI(UString &s, const CCodecs *codecs, const CArchiveLink &arcLink, const wchar_t *name, HRESULT result); + +HRESULT CUpdateCallbackGUI::OpenResult( + const CCodecs *codecs, const CArchiveLink &arcLink, const wchar_t *name, HRESULT result) +{ + UString s; + OpenResult_GUI(s, codecs, arcLink, name, result); + if (!s.IsEmpty()) + { + ProgressDialog->Sync.AddError_Message(s); + } + + return S_OK; +} + +HRESULT CUpdateCallbackGUI::StartScanning() +{ + CProgressSync &sync = ProgressDialog->Sync; + sync.Set_Status(LangString(IDS_SCANNING)); + return S_OK; +} + +HRESULT CUpdateCallbackGUI::ScanError(const FString &path, DWORD systemError) +{ + FailedFiles.Add(path); + ProgressDialog->Sync.AddError_Code_Name(HRESULT_FROM_WIN32(systemError), fs2us(path)); + return S_OK; +} + +HRESULT CUpdateCallbackGUI::FinishScanning(const CDirItemsStat &st) +{ + CProgressSync &sync = ProgressDialog->Sync; + RINOK(ProgressDialog->Sync.ScanProgress(st.NumFiles + st.NumAltStreams, + st.GetTotalBytes(), FString(), true)) + sync.Set_Status(L""); + return S_OK; +} + +HRESULT CUpdateCallbackGUI::StartArchive(const wchar_t *name, bool /* updating */) +{ + CProgressSync &sync = ProgressDialog->Sync; + sync.Set_Status(LangString(IDS_PROGRESS_COMPRESSING)); + sync.Set_TitleFileName(name); + return S_OK; +} + +HRESULT CUpdateCallbackGUI::FinishArchive(const CFinishArchiveStat & /* st */) +{ + CProgressSync &sync = ProgressDialog->Sync; + sync.Set_Status(L""); + return S_OK; +} + +HRESULT CUpdateCallbackGUI::CheckBreak() +{ + return ProgressDialog->Sync.CheckStop(); +} + +HRESULT CUpdateCallbackGUI::ScanProgress(const CDirItemsStat &st, const FString &path, bool isDir) +{ + return ProgressDialog->Sync.ScanProgress(st.NumFiles + st.NumAltStreams, + st.GetTotalBytes(), path, isDir); +} + +/* +HRESULT CUpdateCallbackGUI::Finalize() +{ + return S_OK; +} +*/ + +HRESULT CUpdateCallbackGUI::SetNumItems(const CArcToDoStat &stat) +{ + ProgressDialog->Sync.Set_NumFilesTotal(stat.Get_NumDataItems_Total()); + return S_OK; +} + +HRESULT CUpdateCallbackGUI::SetTotal(UInt64 total) +{ + ProgressDialog->Sync.Set_NumBytesTotal(total); + return S_OK; +} + +HRESULT CUpdateCallbackGUI::SetCompleted(const UInt64 *completed) +{ + return ProgressDialog->Sync.Set_NumBytesCur(completed); +} + +HRESULT CUpdateCallbackGUI::SetRatioInfo(const UInt64 *inSize, const UInt64 *outSize) +{ + ProgressDialog->Sync.Set_Ratio(inSize, outSize); + return CheckBreak(); +} + +HRESULT CUpdateCallbackGUI::GetStream(const wchar_t *name, bool isDir, bool /* isAnti */, UInt32 mode) +{ + return SetOperation_Base(mode, name, isDir); +} + +HRESULT CUpdateCallbackGUI::OpenFileError(const FString &path, DWORD systemError) +{ + FailedFiles.Add(path); + // if (systemError == ERROR_SHARING_VIOLATION) + { + ProgressDialog->Sync.AddError_Code_Name(HRESULT_FROM_WIN32(systemError), fs2us(path)); + return S_FALSE; + } + // return systemError; +} + +HRESULT CUpdateCallbackGUI::SetOperationResult(Int32 /* operationResult */) +{ + NumFiles++; + ProgressDialog->Sync.Set_NumFilesCur(NumFiles); + return S_OK; +} + +void SetExtractErrorMessage(Int32 opRes, Int32 encrypted, const wchar_t *fileName, UString &s); + +HRESULT CUpdateCallbackGUI::ReportExtractResult(Int32 opRes, Int32 isEncrypted, const wchar_t *name) +{ + if (opRes != NArchive::NExtract::NOperationResult::kOK) + { + UString s; + SetExtractErrorMessage(opRes, isEncrypted, name, s); + ProgressDialog->Sync.AddError_Message(s); + } + return S_OK; +} + +HRESULT CUpdateCallbackGUI::ReportUpdateOperation(UInt32 op, const wchar_t *name, bool isDir) +{ + return SetOperation_Base(op, name, isDir); +} + +HRESULT CUpdateCallbackGUI::CryptoGetTextPassword2(Int32 *passwordIsDefined, BSTR *password) +{ + *password = NULL; + if (passwordIsDefined) + *passwordIsDefined = BoolToInt(PasswordIsDefined); + if (!PasswordIsDefined) + { + if (AskPassword) + { + RINOK(ShowAskPasswordDialog()) + } + } + if (passwordIsDefined) + *passwordIsDefined = BoolToInt(PasswordIsDefined); + return StringToBstr(Password, password); +} + +HRESULT CUpdateCallbackGUI::CryptoGetTextPassword(BSTR *password) +{ + return CryptoGetTextPassword2(NULL, password); +} + +/* +It doesn't work, since main stream waits Dialog +HRESULT CUpdateCallbackGUI::CloseProgress() +{ + ProgressDialog->MyClose(); + return S_OK; +} +*/ + + +HRESULT CUpdateCallbackGUI::Open_CheckBreak() +{ + return ProgressDialog->Sync.CheckStop(); +} + +HRESULT CUpdateCallbackGUI::Open_SetTotal(const UInt64 * /* numFiles */, const UInt64 * /* numBytes */) +{ + // if (numFiles != NULL) ProgressDialog->Sync.SetNumFilesTotal(*numFiles); + return S_OK; +} + +HRESULT CUpdateCallbackGUI::Open_SetCompleted(const UInt64 * /* numFiles */, const UInt64 * /* numBytes */) +{ + return ProgressDialog->Sync.CheckStop(); +} + +#ifndef Z7_NO_CRYPTO + +HRESULT CUpdateCallbackGUI::Open_CryptoGetTextPassword(BSTR *password) +{ + PasswordWasAsked = true; + return CryptoGetTextPassword2(NULL, password); +} + +/* +HRESULT CUpdateCallbackGUI::Open_GetPasswordIfAny(bool &passwordIsDefined, UString &password) +{ + passwordIsDefined = PasswordIsDefined; + password = Password; + return S_OK; +} + +bool CUpdateCallbackGUI::Open_WasPasswordAsked() +{ + return PasswordWasAsked; +} + +void CUpdateCallbackGUI::Open_Clear_PasswordWasAsked_Flag() +{ + PasswordWasAsked = false; +} +*/ + +HRESULT CUpdateCallbackGUI::ShowDeleteFile(const wchar_t *name, bool isDir) +{ + return SetOperation_Base(NUpdateNotifyOp::kDelete, name, isDir); +} + +HRESULT CUpdateCallbackGUI::FinishDeletingAfterArchiving() +{ + // ClosePercents2(); + return S_OK; +} + +HRESULT CUpdateCallbackGUI::DeletingAfterArchiving(const FString &path, bool isDir) +{ + return ProgressDialog->Sync.Set_Status2(_lang_Removing, fs2us(path), isDir); +} + + +HRESULT CUpdateCallbackGUI::MoveArc_Start(const wchar_t *srcTempPath, const wchar_t *destFinalPath, UInt64 totalSize, Int32 updateMode) +{ + return MoveArc_Start_Base(srcTempPath, destFinalPath, totalSize, updateMode); +} +HRESULT CUpdateCallbackGUI::MoveArc_Progress(UInt64 totalSize, UInt64 currentSize) +{ + return MoveArc_Progress_Base(totalSize, currentSize); +} +HRESULT CUpdateCallbackGUI::MoveArc_Finish() +{ + return MoveArc_Finish_Base(); +} + + +HRESULT CUpdateCallbackGUI::StartOpenArchive(const wchar_t * /* name */) +{ + return S_OK; +} + +HRESULT CUpdateCallbackGUI::ReadingFileError(const FString &path, DWORD systemError) +{ + FailedFiles.Add(path); + ProgressDialog->Sync.AddError_Code_Name(HRESULT_FROM_WIN32(systemError), fs2us(path)); + return S_OK; +} + +HRESULT CUpdateCallbackGUI::WriteSfx(const wchar_t * /* name */, UInt64 /* size */) +{ + CProgressSync &sync = ProgressDialog->Sync; + sync.Set_Status(L"WriteSfx"); + return S_OK; +} + +HRESULT CUpdateCallbackGUI::Open_Finished() +{ + // ClosePercents(); + return S_OK; +} + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/GUI/UpdateCallbackGUI.h b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/UpdateCallbackGUI.h new file mode 100644 index 00000000..998249a8 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/UpdateCallbackGUI.h @@ -0,0 +1,31 @@ +// UpdateCallbackGUI.h + +#ifndef ZIP7_INC_UPDATE_CALLBACK_GUI_H +#define ZIP7_INC_UPDATE_CALLBACK_GUI_H + +#include "../Common/Update.h" +#include "../Common/ArchiveOpenCallback.h" + +#include "UpdateCallbackGUI2.h" + +class CUpdateCallbackGUI Z7_final: + public IOpenCallbackUI, + public IUpdateCallbackUI2, + public CUpdateCallbackGUI2 +{ + Z7_IFACE_IMP(IOpenCallbackUI) + Z7_IFACE_IMP(IUpdateCallbackUI) + Z7_IFACE_IMP(IDirItemsCallback) + Z7_IFACE_IMP(IUpdateCallbackUI2) + +public: + bool AskPassword; + FStringVector FailedFiles; + + CUpdateCallbackGUI(): + AskPassword(false) + {} + void Init(); +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/GUI/UpdateCallbackGUI2.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/UpdateCallbackGUI2.cpp new file mode 100644 index 00000000..53fed91e --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/UpdateCallbackGUI2.cpp @@ -0,0 +1,130 @@ +// UpdateCallbackGUI2.cpp + +#include "StdAfx.h" + +#include "../FileManager/LangUtils.h" +#include "../FileManager/PasswordDialog.h" + +#include "resource2.h" +#include "resource3.h" +#include "ExtractRes.h" +#include "../FileManager/resourceGui.h" + +#include "UpdateCallbackGUI.h" + +using namespace NWindows; + +static const UINT k_UpdNotifyLangs[] = +{ + IDS_PROGRESS_ADD, + IDS_PROGRESS_UPDATE, + IDS_PROGRESS_ANALYZE, + IDS_PROGRESS_REPLICATE, + IDS_PROGRESS_REPACK, + IDS_PROGRESS_SKIPPING, + IDS_PROGRESS_DELETE, + IDS_PROGRESS_HEADER +}; + +void CUpdateCallbackGUI2::Init() +{ + NumFiles = 0; + + LangString(IDS_PROGRESS_REMOVE, _lang_Removing); + LangString(IDS_MOVING, _lang_Moving); + _lang_Ops.Clear(); + for (unsigned i = 0; i < Z7_ARRAY_SIZE(k_UpdNotifyLangs); i++) + _lang_Ops.Add(LangString(k_UpdNotifyLangs[i])); +} + +HRESULT CUpdateCallbackGUI2::SetOperation_Base(UInt32 notifyOp, const wchar_t *name, bool isDir) +{ + const UString *s = NULL; + if (notifyOp < _lang_Ops.Size()) + s = &(_lang_Ops[(unsigned)notifyOp]); + else + s = &_emptyString; + + return ProgressDialog->Sync.Set_Status2(*s, name, isDir); +} + + +HRESULT CUpdateCallbackGUI2::ShowAskPasswordDialog() +{ + CPasswordDialog dialog; + ProgressDialog->WaitCreating(); + if (dialog.Create(*ProgressDialog) != IDOK) + return E_ABORT; + Password = dialog.Password; + PasswordIsDefined = true; + return S_OK; +} + + +HRESULT CUpdateCallbackGUI2::MoveArc_UpdateStatus() +{ + UString s; + s.Add_UInt64(_arcMoving_percents); + s.Add_Char('%'); + + const bool totalDefined = (_arcMoving_total != 0 && _arcMoving_total != (UInt64)(Int64)-1); + if (totalDefined || _arcMoving_current != 0) + { + s += " : "; + s.Add_UInt64(_arcMoving_current >> 20); + s += " MiB"; + } + if (totalDefined) + { + s += " / "; + s.Add_UInt64((_arcMoving_total + ((1 << 20) - 1)) >> 20); + s += " MiB"; + } + + s += " : "; + s += _lang_Moving; + s += " : "; + // s.Add_Char('\"'); + s += _arcMoving_name1; + // s.Add_Char('\"'); + return ProgressDialog->Sync.Set_Status2(s, _arcMoving_name2, + false); // isDir +} + + +HRESULT CUpdateCallbackGUI2::MoveArc_Start_Base(const wchar_t *srcTempPath, const wchar_t *destFinalPath, UInt64 totalSize, Int32 updateMode) +{ + _arcMoving_percents = 0; + _arcMoving_total = totalSize; + _arcMoving_current = 0; + _arcMoving_updateMode = updateMode; + _arcMoving_name1 = srcTempPath; + _arcMoving_name2 = destFinalPath; + return MoveArc_UpdateStatus(); +} + + +HRESULT CUpdateCallbackGUI2::MoveArc_Progress_Base(UInt64 totalSize, UInt64 currentSize) +{ + _arcMoving_total = totalSize; + _arcMoving_current = currentSize; + UInt64 percents = 0; + if (totalSize != 0) + { + if (totalSize < ((UInt64)1 << 57)) + percents = currentSize * 100 / totalSize; + else + percents = currentSize / (totalSize / 100); + } + if (percents == _arcMoving_percents) + return ProgressDialog->Sync.CheckStop(); + // Sleep(300); // for debug + _arcMoving_percents = percents; + return MoveArc_UpdateStatus(); +} + + +HRESULT CUpdateCallbackGUI2::MoveArc_Finish_Base() +{ + return ProgressDialog->Sync.Set_Status2(L"", L"", false); +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/GUI/UpdateCallbackGUI2.h b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/UpdateCallbackGUI2.h new file mode 100644 index 00000000..56747ff0 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/UpdateCallbackGUI2.h @@ -0,0 +1,53 @@ +// UpdateCallbackGUI2.h + +#ifndef ZIP7_INC_UPDATE_CALLBACK_GUI2_H +#define ZIP7_INC_UPDATE_CALLBACK_GUI2_H + +#include "../FileManager/ProgressDialog2.h" + +class CUpdateCallbackGUI2 +{ +public: + CProgressDialog *ProgressDialog; +protected: + UString _arcMoving_name1; + UString _arcMoving_name2; + UInt64 _arcMoving_percents; + UInt64 _arcMoving_total; + UInt64 _arcMoving_current; + Int32 _arcMoving_updateMode; +public: + bool PasswordIsDefined; + bool PasswordWasAsked; + UInt64 NumFiles; + UString Password; +protected: + UStringVector _lang_Ops; + UString _lang_Removing; + UString _lang_Moving; + UString _emptyString; + + HRESULT MoveArc_UpdateStatus(); + HRESULT MoveArc_Start_Base(const wchar_t *srcTempPath, const wchar_t *destFinalPath, UInt64 /* totalSize */, Int32 updateMode); + HRESULT MoveArc_Progress_Base(UInt64 totalSize, UInt64 currentSize); + HRESULT MoveArc_Finish_Base(); + +public: + + CUpdateCallbackGUI2(): + _arcMoving_percents(0), + _arcMoving_total(0), + _arcMoving_current(0), + _arcMoving_updateMode(0), + PasswordIsDefined(false), + PasswordWasAsked(false), + NumFiles(0) + {} + + void Init(); + + HRESULT SetOperation_Base(UInt32 notifyOp, const wchar_t *name, bool isDir); + HRESULT ShowAskPasswordDialog(); +}; + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/GUI/UpdateGUI.cpp b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/UpdateGUI.cpp new file mode 100644 index 00000000..a600a8bc --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/UpdateGUI.cpp @@ -0,0 +1,605 @@ +// UpdateGUI.cpp + +#include "StdAfx.h" + +#include "../../../Common/IntToString.h" +#include "../../../Common/StringConvert.h" +#include "../../../Common/StringToInt.h" + +#include "../../../Windows/DLL.h" +#include "../../../Windows/FileDir.h" +#include "../../../Windows/FileName.h" +#include "../../../Windows/Thread.h" + +#include "../Common/WorkDir.h" + +#include "../Explorer/MyMessages.h" + +#include "../FileManager/LangUtils.h" +#include "../FileManager/StringUtils.h" +#include "../FileManager/resourceGui.h" + +#include "CompressDialog.h" +#include "UpdateGUI.h" + +#include "resource2.h" + +using namespace NWindows; +using namespace NFile; +using namespace NDir; + +static const char * const kDefaultSfxModule = "7z.sfx"; +static const char * const kSFXExtension = "exe"; + +extern void AddMessageToString(UString &dest, const UString &src); + +UString HResultToMessage(HRESULT errorCode); + +class CThreadUpdating: public CProgressThreadVirt +{ + HRESULT ProcessVirt() Z7_override; +public: + CCodecs *codecs; + const CObjectVector *formatIndices; + const UString *cmdArcPath; + CUpdateCallbackGUI *UpdateCallbackGUI; + NWildcard::CCensor *WildcardCensor; + CUpdateOptions *Options; + bool needSetPath; +}; + +HRESULT CThreadUpdating::ProcessVirt() +{ + CUpdateErrorInfo ei; + HRESULT res = UpdateArchive(codecs, *formatIndices, *cmdArcPath, + *WildcardCensor, *Options, + ei, UpdateCallbackGUI, UpdateCallbackGUI, needSetPath); + FinalMessage.ErrorMessage.Message = ei.Message.Ptr(); + ErrorPaths = ei.FileNames; + if (res != S_OK) + return res; + return HRESULT_FROM_WIN32(ei.SystemError); +} + + +// parse command line properties + +static bool ParseProp_Time_BoolPair(const CProperty &prop, const char *name, CBoolPair &bp) +{ + if (!prop.Name.IsPrefixedBy_Ascii_NoCase(name)) + return false; + const UString rem = prop.Name.Ptr((unsigned)strlen(name)); + UString val = prop.Value; + if (!rem.IsEmpty()) + { + if (!val.IsEmpty()) + return true; + val = rem; + } + bool res; + if (StringToBool(val, res)) + { + bp.Val = res; + bp.Def = true; + } + return true; +} + +static void ParseProp( + const CProperty &prop, + NCompressDialog::CInfo &di) +{ + if (ParseProp_Time_BoolPair(prop, "tm", di.MTime)) return; + if (ParseProp_Time_BoolPair(prop, "tc", di.CTime)) return; + if (ParseProp_Time_BoolPair(prop, "ta", di.ATime)) return; +} + +static void ParseProperties( + const CObjectVector &properties, + NCompressDialog::CInfo &di) +{ + FOR_VECTOR (i, properties) + { + ParseProp(properties[i], di); + } +} + + + + + +static void AddProp_UString(CObjectVector &properties, const char *name, const UString &value) +{ + CProperty prop; + prop.Name = name; + prop.Value = value; + properties.Add(prop); +} + +static void AddProp_UInt32(CObjectVector &properties, const char *name, UInt32 value) +{ + UString s; + s.Add_UInt32(value); + AddProp_UString(properties, name, s); +} + +static void AddProp_bool(CObjectVector &properties, const char *name, bool value) +{ + AddProp_UString(properties, name, UString(value ? "on": "off")); +} + + +static void AddProp_BoolPair(CObjectVector &properties, + const char *name, const CBoolPair &bp) +{ + if (bp.Def) + AddProp_bool(properties, name, bp.Val); +} + + + +static void SplitOptionsToStrings(const UString &src, UStringVector &strings) +{ + SplitString(src, strings); + FOR_VECTOR (i, strings) + { + UString &s = strings[i]; + if (s.Len() > 2 + && s[0] == '-' + && MyCharLower_Ascii(s[1]) == 'm') + s.DeleteFrontal(2); + } +} + +static bool IsThereMethodOverride(bool is7z, const UStringVector &strings) +{ + FOR_VECTOR (i, strings) + { + const UString &s = strings[i]; + if (is7z) + { + const wchar_t *end; + UInt64 n = ConvertStringToUInt64(s, &end); + if (n == 0 && *end == L'=') + return true; + } + else + { + if (s.Len() > 0) + if (s[0] == L'm' && s[1] == L'=') + return true; + } + } + return false; +} + +static void ParseAndAddPropertires(CObjectVector &properties, + const UStringVector &strings) +{ + FOR_VECTOR (i, strings) + { + const UString &s = strings[i]; + CProperty property; + const int index = s.Find(L'='); + if (index < 0) + property.Name = s; + else + { + property.Name.SetFrom(s, (unsigned)index); + property.Value = s.Ptr(index + 1); + } + properties.Add(property); + } +} + + +static void AddProp_Size(CObjectVector &properties, const char *name, const UInt64 size) +{ + UString s; + s.Add_UInt64(size); + s.Add_Char('b'); + AddProp_UString(properties, name, s); +} + + +static void SetOutProperties( + CObjectVector &properties, + const NCompressDialog::CInfo &di, + bool is7z, + bool setMethod) +{ + if (di.Level != (UInt32)(Int32)-1) + AddProp_UInt32(properties, "x", (UInt32)di.Level); + if (setMethod) + { + if (!di.Method.IsEmpty()) + AddProp_UString(properties, is7z ? "0": "m", di.Method); + if (di.Dict64 != (UInt64)(Int64)-1) + { + AString name; + if (is7z) + name = "0"; + name += (di.OrderMode ? "mem" : "d"); + AddProp_Size(properties, name, di.Dict64); + } + /* + if (di.Dict64_Chain != (UInt64)(Int64)-1) + { + AString name; + if (is7z) + name = "0"; + name += "dc"; + AddProp_Size(properties, name, di.Dict64_Chain); + } + */ + if (di.Order != (UInt32)(Int32)-1) + { + AString name; + if (is7z) + name = "0"; + name += (di.OrderMode ? "o" : "fb"); + AddProp_UInt32(properties, name, (UInt32)di.Order); + } + } + + if (!di.EncryptionMethod.IsEmpty()) + AddProp_UString(properties, "em", di.EncryptionMethod); + + if (di.EncryptHeadersIsAllowed) + AddProp_bool(properties, "he", di.EncryptHeaders); + + if (di.SolidIsSpecified) + AddProp_Size(properties, "s", di.SolidBlockSize); + + if ( + // di.MultiThreadIsAllowed && + di.NumThreads != (UInt32)(Int32)-1) + AddProp_UInt32(properties, "mt", di.NumThreads); + + const NCompression::CMemUse &memUse = di.MemUsage; + if (memUse.IsDefined) + { + const char *kMemUse = "memuse"; + if (memUse.IsPercent) + { + UString s; + // s += 'p'; // for debug: alternate percent method + s.Add_UInt64(memUse.Val); + s.Add_Char('%'); + AddProp_UString(properties, kMemUse, s); + } + else + AddProp_Size(properties, kMemUse, memUse.Val); + } + + AddProp_BoolPair(properties, "tm", di.MTime); + AddProp_BoolPair(properties, "tc", di.CTime); + AddProp_BoolPair(properties, "ta", di.ATime); + + if (di.TimePrec != (UInt32)(Int32)-1) + AddProp_UInt32(properties, "tp", di.TimePrec); +} + + +struct C_UpdateMode_ToAction_Pair +{ + NCompressDialog::NUpdateMode::EEnum UpdateMode; + const NUpdateArchive::CActionSet *ActionSet; +}; + +static const C_UpdateMode_ToAction_Pair g_UpdateMode_Pairs[] = +{ + { NCompressDialog::NUpdateMode::kAdd, &NUpdateArchive::k_ActionSet_Add }, + { NCompressDialog::NUpdateMode::kUpdate, &NUpdateArchive::k_ActionSet_Update }, + { NCompressDialog::NUpdateMode::kFresh, &NUpdateArchive::k_ActionSet_Fresh }, + { NCompressDialog::NUpdateMode::kSync, &NUpdateArchive::k_ActionSet_Sync } +}; + +static int FindActionSet(const NUpdateArchive::CActionSet &actionSet) +{ + for (unsigned i = 0; i < Z7_ARRAY_SIZE(g_UpdateMode_Pairs); i++) + if (actionSet.IsEqualTo(*g_UpdateMode_Pairs[i].ActionSet)) + return (int)i; + return -1; +} + +static int FindUpdateMode(NCompressDialog::NUpdateMode::EEnum mode) +{ + for (unsigned i = 0; i < Z7_ARRAY_SIZE(g_UpdateMode_Pairs); i++) + if (mode == g_UpdateMode_Pairs[i].UpdateMode) + return (int)i; + return -1; +} + + +static HRESULT ShowDialog( + CCodecs *codecs, + const CObjectVector &censor, + CUpdateOptions &options, + CUpdateCallbackGUI *callback, HWND hwndParent) +{ + if (options.Commands.Size() != 1) + throw "It must be one command"; + /* + FString currentDirPrefix; + #ifndef UNDER_CE + { + if (!MyGetCurrentDirectory(currentDirPrefix)) + return E_FAIL; + NName::NormalizeDirPathPrefix(currentDirPrefix); + } + #endif + */ + + bool oneFile = false; + NFind::CFileInfo fileInfo; + UString name; + + /* + if (censor.Pairs.Size() > 0) + { + const NWildcard::CPair &pair = censor.Pairs[0]; + if (pair.Head.IncludeItems.Size() > 0) + { + const NWildcard::CItem &item = pair.Head.IncludeItems[0]; + if (item.ForFile) + { + name = pair.Prefix; + FOR_VECTOR (i, item.PathParts) + { + if (i > 0) + name.Add_PathSepar(); + name += item.PathParts[i]; + } + if (fileInfo.Find(us2fs(name))) + { + if (censor.Pairs.Size() == 1 && pair.Head.IncludeItems.Size() == 1) + oneFile = !fileInfo.IsDir(); + } + } + } + } + */ + if (censor.Size() > 0) + { + const NWildcard::CCensorPath &cp = censor[0]; + if (cp.Include) + { + { + if (fileInfo.Find(us2fs(cp.Path))) + { + if (censor.Size() == 1) + oneFile = !fileInfo.IsDir(); + } + } + } + } + + + /* + // v23: we restore current dir in dialog code + #if defined(_WIN32) && !defined(UNDER_CE) + CCurrentDirRestorer curDirRestorer; + #endif + */ + + CCompressDialog dialog; + NCompressDialog::CInfo &di = dialog.Info; + dialog.ArcFormats = &codecs->Formats; + { + CObjectVector userCodecs; + codecs->Get_CodecsInfoUser_Vector(userCodecs); + dialog.SetMethods(userCodecs); + } + + if (options.MethodMode.Type_Defined) + di.FormatIndex = options.MethodMode.Type.FormatIndex; + + FOR_VECTOR (i, codecs->Formats) + { + const CArcInfoEx &ai = codecs->Formats[i]; + if (!ai.UpdateEnabled) + continue; + if (!oneFile && ai.Flags_KeepName()) + continue; + if ((int)i != di.FormatIndex) + { + if (ai.Flags_HashHandler()) + continue; + if (ai.Name.IsEqualTo_Ascii_NoCase("swfc")) + if (!oneFile || name.Len() < 4 || !StringsAreEqualNoCase_Ascii(name.RightPtr(4), ".swf")) + continue; + } + dialog.ArcIndices.Add(i); + } + if (dialog.ArcIndices.IsEmpty()) + { + ShowErrorMessage(L"No Update Engines"); + return E_FAIL; + } + + // di.ArchiveName = options.ArchivePath.GetFinalPath(); + di.ArcPath = options.ArchivePath.GetPathWithoutExt(); + dialog.OriginalFileName = fs2us(fileInfo.Name); + + di.PathMode = options.PathMode; + + // di.CurrentDirPrefix = currentDirPrefix; + di.SFXMode = options.SfxMode; + di.OpenShareForWrite = options.OpenShareForWrite; + di.DeleteAfterCompressing = options.DeleteAfterCompressing; + + di.SymLinks = options.SymLinks; + di.HardLinks = options.HardLinks; + di.AltStreams = options.AltStreams; + di.NtSecurity = options.NtSecurity; + if (options.SetArcMTime) + di.SetArcMTime.SetTrueTrue(); + if (options.PreserveATime) + di.PreserveATime.SetTrueTrue(); + + if (callback->PasswordIsDefined) + di.Password = callback->Password; + + di.KeepName = !oneFile; + + NUpdateArchive::CActionSet &actionSet = options.Commands.Front().ActionSet; + + { + int index = FindActionSet(actionSet); + if (index < 0) + return E_NOTIMPL; + di.UpdateMode = g_UpdateMode_Pairs[(unsigned)index].UpdateMode; + } + + ParseProperties(options.MethodMode.Properties, di); + + if (dialog.Create(hwndParent) != IDOK) + return E_ABORT; + + options.DeleteAfterCompressing = di.DeleteAfterCompressing; + + options.SymLinks = di.SymLinks; + options.HardLinks = di.HardLinks; + options.AltStreams = di.AltStreams; + options.NtSecurity = di.NtSecurity; + options.SetArcMTime = di.SetArcMTime.Val; + if (di.PreserveATime.Def) + options.PreserveATime = di.PreserveATime.Val; + + /* + #if defined(_WIN32) && !defined(UNDER_CE) + curDirRestorer.NeedRestore = dialog.CurrentDirWasChanged; + #endif + */ + + options.VolumesSizes = di.VolumeSizes; + /* + if (di.VolumeSizeIsDefined) + { + MyMessageBox(L"Splitting to volumes is not supported"); + return E_FAIL; + } + */ + + + { + int index = FindUpdateMode(di.UpdateMode); + if (index < 0) + return E_FAIL; + actionSet = *g_UpdateMode_Pairs[index].ActionSet; + } + + options.PathMode = di.PathMode; + + const CArcInfoEx &archiverInfo = codecs->Formats[di.FormatIndex]; + callback->PasswordIsDefined = (!di.Password.IsEmpty()); + if (callback->PasswordIsDefined) + callback->Password = di.Password; + + // we clear command line options, and fill options form Dialog + options.MethodMode.Properties.Clear(); + + const bool is7z = archiverInfo.Is_7z(); + + UStringVector optionStrings; + SplitOptionsToStrings(di.Options, optionStrings); + const bool methodOverride = IsThereMethodOverride(is7z, optionStrings); + + SetOutProperties(options.MethodMode.Properties, di, + is7z, + !methodOverride); // setMethod + + options.OpenShareForWrite = di.OpenShareForWrite; + ParseAndAddPropertires(options.MethodMode.Properties, optionStrings); + + if (di.SFXMode) + options.SfxMode = true; + options.MethodMode.Type = COpenType(); + options.MethodMode.Type_Defined = true; + options.MethodMode.Type.FormatIndex = di.FormatIndex; + + options.ArchivePath.VolExtension = archiverInfo.GetMainExt(); + if (di.SFXMode) + options.ArchivePath.BaseExtension = kSFXExtension; + else + options.ArchivePath.BaseExtension = options.ArchivePath.VolExtension; + options.ArchivePath.ParseFromPath(di.ArcPath, k_ArcNameMode_Smart); + + NWorkDir::CInfo workDirInfo; + workDirInfo.Load(); + options.WorkingDir.Empty(); + if (workDirInfo.Mode != NWorkDir::NMode::kCurrent) + { + FString fullPath; + MyGetFullPathName(us2fs(di.ArcPath), fullPath); + FString namePart; + options.WorkingDir = GetWorkDir(workDirInfo, fullPath, namePart); + CreateComplexDir(options.WorkingDir); + } + return S_OK; +} + +HRESULT UpdateGUI( + CCodecs *codecs, + const CObjectVector &formatIndices, + const UString &cmdArcPath, + NWildcard::CCensor &censor, + CUpdateOptions &options, + bool showDialog, + bool &messageWasDisplayed, + CUpdateCallbackGUI *callback, + HWND hwndParent) +{ + messageWasDisplayed = false; + bool needSetPath = true; + if (showDialog) + { + RINOK(ShowDialog(codecs, censor.CensorPaths, options, callback, hwndParent)) + needSetPath = false; + } + if (options.SfxMode && options.SfxModule.IsEmpty()) + { + options.SfxModule = NWindows::NDLL::GetModuleDirPrefix(); + options.SfxModule += kDefaultSfxModule; + } + + CThreadUpdating tu; + + tu.needSetPath = needSetPath; + + tu.codecs = codecs; + tu.formatIndices = &formatIndices; + tu.cmdArcPath = &cmdArcPath; + + tu.UpdateCallbackGUI = callback; + tu.UpdateCallbackGUI->ProgressDialog = &tu; + tu.UpdateCallbackGUI->Init(); + + UString title = LangString(IDS_PROGRESS_COMPRESSING); + if (!formatIndices.IsEmpty()) + { + const int fin = formatIndices[0].FormatIndex; + if (fin >= 0) + if (codecs->Formats[fin].Flags_HashHandler()) + title = LangString(IDS_CHECKSUM_CALCULATING); + } + + /* + if (hwndParent != 0) + { + tu.ProgressDialog.MainWindow = hwndParent; + // tu.ProgressDialog.MainTitle = fileName; + tu.ProgressDialog.MainAddTitle = title + L' '; + } + */ + + tu.WildcardCensor = &censor; + tu.Options = &options; + tu.IconID = IDI_ICON; + + RINOK(tu.Create(title, hwndParent)) + + messageWasDisplayed = tu.ThreadFinishedOK && tu.MessagesDisplayed; + return tu.Result; +} diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/GUI/UpdateGUI.h b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/UpdateGUI.h new file mode 100644 index 00000000..0c43a01b --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/UpdateGUI.h @@ -0,0 +1,33 @@ +// GUI/UpdateGUI.h + +#ifndef ZIP7_INC_UPDATE_GUI_H +#define ZIP7_INC_UPDATE_GUI_H + +#include "../Common/Update.h" + +#include "UpdateCallbackGUI.h" + +/* + callback->FailedFiles contains names of files for that there were problems. + RESULT can be S_OK, even if there are such warnings!!! + + RESULT = E_ABORT - user break. + RESULT != E_ABORT: + { + messageWasDisplayed = true - message was displayed already. + messageWasDisplayed = false - there was some internal error, so you must show error message. + } +*/ + +HRESULT UpdateGUI( + CCodecs *codecs, + const CObjectVector &formatIndices, + const UString &cmdArcPath2, + NWildcard::CCensor &censor, + CUpdateOptions &options, + bool showDialog, + bool &messageWasDisplayed, + CUpdateCallbackGUI *callback, + HWND hwndParent = NULL); + +#endif diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/GUI/makefile b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/makefile new file mode 100644 index 00000000..5bf2f78f --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/makefile @@ -0,0 +1,148 @@ +PROG = 7zG.exe +CFLAGS = $(CFLAGS) \ + -DZ7_LANG \ + -DZ7_EXTERNAL_CODECS \ + +!IFDEF UNDER_CE +LIBS = $(LIBS) ceshell.lib Commctrl.lib +!ELSE +LIBS = $(LIBS) comctl32.lib htmlhelp.lib comdlg32.lib gdi32.lib +CFLAGS = $(CFLAGS) -DZ7_DEVICE_FILE +!ENDIF + +GUI_OBJS = \ + $O\BenchmarkDialog.obj \ + $O\CompressDialog.obj \ + $O\ExtractDialog.obj \ + $O\ExtractGUI.obj \ + $O\GUI.obj \ + $O\HashGUI.obj \ + $O\UpdateCallbackGUI.obj \ + $O\UpdateCallbackGUI2.obj \ + $O\UpdateGUI.obj \ + +COMMON_OBJS = \ + $O\CommandLineParser.obj \ + $O\CRC.obj \ + $O\DynLimBuf.obj \ + $O\IntToString.obj \ + $O\Lang.obj \ + $O\ListFileUtils.obj \ + $O\MyString.obj \ + $O\MyVector.obj \ + $O\NewHandler.obj \ + $O\StringConvert.obj \ + $O\StringToInt.obj \ + $O\UTFConvert.obj \ + $O\Wildcard.obj \ + +WIN_OBJS = \ + $O\Clipboard.obj \ + $O\CommonDialog.obj \ + $O\DLL.obj \ + $O\ErrorMsg.obj \ + $O\FileDir.obj \ + $O\FileFind.obj \ + $O\FileIO.obj \ + $O\FileLink.obj \ + $O\FileName.obj \ + $O\FileSystem.obj \ + $O\MemoryGlobal.obj \ + $O\MemoryLock.obj \ + $O\PropVariant.obj \ + $O\PropVariantConv.obj \ + $O\Registry.obj \ + $O\ResourceString.obj \ + $O\Shell.obj \ + $O\Synchronization.obj \ + $O\System.obj \ + $O\SystemInfo.obj \ + $O\TimeUtils.obj \ + $O\Window.obj \ + +WIN_CTRL_OBJS = \ + $O\ComboBox.obj \ + $O\Dialog.obj \ + $O\ListView.obj \ + +7ZIP_COMMON_OBJS = \ + $O\CreateCoder.obj \ + $O\FilePathAutoRename.obj \ + $O\FileStreams.obj \ + $O\FilterCoder.obj \ + $O\LimitedStreams.obj \ + $O\MethodProps.obj \ + $O\MultiOutStream.obj \ + $O\ProgressUtils.obj \ + $O\PropId.obj \ + $O\StreamObjects.obj \ + $O\StreamUtils.obj \ + $O\UniqBlocks.obj \ + +UI_COMMON_OBJS = \ + $O\ArchiveCommandLine.obj \ + $O\ArchiveExtractCallback.obj \ + $O\ArchiveOpenCallback.obj \ + $O\Bench.obj \ + $O\DefaultName.obj \ + $O\EnumDirItems.obj \ + $O\Extract.obj \ + $O\ExtractingFilePath.obj \ + $O\HashCalc.obj \ + $O\LoadCodecs.obj \ + $O\OpenArchive.obj \ + $O\PropIDUtils.obj \ + $O\SetProperties.obj \ + $O\SortUtils.obj \ + $O\TempFiles.obj \ + $O\Update.obj \ + $O\UpdateAction.obj \ + $O\UpdateCallback.obj \ + $O\UpdatePair.obj \ + $O\UpdateProduce.obj \ + $O\WorkDir.obj \ + $O\ZipRegistry.obj \ + +AR_COMMON_OBJS = \ + $O\ItemNameUtils.obj \ + $O\OutStreamWithCRC.obj \ + +FM_OBJS = \ + $O\EditDialog.obj \ + $O\ExtractCallback.obj \ + $O\FormatUtils.obj \ + $O\HelpUtils.obj \ + $O\LangUtils.obj \ + $O\ListViewDialog.obj \ + $O\MemDialog.obj \ + $O\OpenCallback.obj \ + $O\ProgramLocation.obj \ + $O\PropertyName.obj \ + $O\RegistryUtils.obj \ + $O\SplitUtils.obj \ + $O\StringUtils.obj \ + $O\OverwriteDialog.obj \ + $O\PasswordDialog.obj \ + $O\ProgressDialog2.obj \ + +FM_OBJS = $(FM_OBJS) \ + $O\BrowseDialog.obj \ + $O\ComboDialog.obj \ + $O\SysIconUtils.obj \ + +EXPLORER_OBJS = \ + $O\MyMessages.obj \ + +COMPRESS_OBJS = \ + $O\CopyCoder.obj \ + +C_OBJS = \ + $O\Alloc.obj \ + $O\CpuArch.obj \ + $O\DllSecur.obj \ + $O\Threads.obj \ + +!include "../../Crc.mak" +!include "../../Sort.mak" + +!include "../../7zip.mak" diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/GUI/resource.rc b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/resource.rc new file mode 100644 index 00000000..08ef00ad --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/resource.rc @@ -0,0 +1,26 @@ +#include "../../MyVersionInfo.rc" +// #include + +#include "resource2.rc" +#include "resource3.rc" + +#include "../FileManager/resourceGui.rc" + +MY_VERSION_INFO(MY_VFT_APP, "7-Zip GUI", "7zg", "7zg.exe") + +IDI_ICON ICON "FM.ico" + +#ifndef UNDER_CE +1 24 MOVEABLE PURE "7zG.exe.manifest" +#endif + +#include "../FileManager/PropertyName.rc" +#include "../FileManager/MemDialog.rc" +#include "../FileManager/OverwriteDialog.rc" +#include "../FileManager/PasswordDialog.rc" +#include "../FileManager/ProgressDialog2.rc" +#include "Extract.rc" +#include "../FileManager/BrowseDialog.rc" +#include "../FileManager/ComboDialog.rc" +#include "../FileManager/EditDialog.rc" +#include "../FileManager/ListViewDialog.rc" diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/GUI/resource2.h b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/resource2.h new file mode 100644 index 00000000..cd882924 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/resource2.h @@ -0,0 +1,2 @@ +#define IDS_PROGRESS_COMPRESSING 3301 +#define IDS_ARCHIVES_COLON 3907 diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/GUI/resource2.rc b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/resource2.rc new file mode 100644 index 00000000..49534f50 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/resource2.rc @@ -0,0 +1,10 @@ +#include "ExtractDialog.rc" +#include "CompressDialog.rc" +#include "BenchmarkDialog.rc" +#include "resource2.h" + +STRINGTABLE +BEGIN + IDS_PROGRESS_COMPRESSING "Compressing" + IDS_ARCHIVES_COLON "Archives:" +END diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/GUI/resource3.h b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/resource3.h new file mode 100644 index 00000000..c25737fa --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/resource3.h @@ -0,0 +1,10 @@ +#define IDS_PROGRESS_REMOVE 3305 + +#define IDS_PROGRESS_ADD 3320 +#define IDS_PROGRESS_UPDATE 3321 +#define IDS_PROGRESS_ANALYZE 3322 +#define IDS_PROGRESS_REPLICATE 3323 +#define IDS_PROGRESS_REPACK 3324 + +#define IDS_PROGRESS_DELETE 3326 +#define IDS_PROGRESS_HEADER 3327 diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/GUI/resource3.rc b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/resource3.rc new file mode 100644 index 00000000..cfc8bc35 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/GUI/resource3.rc @@ -0,0 +1,15 @@ +#include "resource3.h" + +STRINGTABLE +BEGIN + IDS_PROGRESS_REMOVE "Removing" + + IDS_PROGRESS_ADD "Adding" + IDS_PROGRESS_UPDATE "Updating" + IDS_PROGRESS_ANALYZE "Analyzing" + IDS_PROGRESS_REPLICATE "Replicating" + IDS_PROGRESS_REPACK "Repacking" + + IDS_PROGRESS_DELETE "Deleting" + IDS_PROGRESS_HEADER "Header creating" +END diff --git a/src/plugins/7zip/7za/cpp/7zip/UI/makefile b/src/plugins/7zip/7za/cpp/7zip/UI/makefile new file mode 100644 index 00000000..1b0cdbe1 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/UI/makefile @@ -0,0 +1,12 @@ +DIRS = \ + Client7z\~ \ + Console\~ \ + Explorer\~ \ + Far\~ \ + FileManager\~ \ + GUI\~ \ + +all: $(DIRS) + +$(DIRS): +!include "../SubBuild.mak" diff --git a/src/plugins/7zip/7za/cpp/7zip/cmpl_clang.mak b/src/plugins/7zip/7za/cpp/7zip/cmpl_clang.mak new file mode 100644 index 00000000..e62e1e62 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/cmpl_clang.mak @@ -0,0 +1,3 @@ +include ../../var_clang.mak +include ../../warn_clang.mak +include makefile.gcc diff --git a/src/plugins/7zip/7za/cpp/7zip/cmpl_clang_arm64.mak b/src/plugins/7zip/7za/cpp/7zip/cmpl_clang_arm64.mak new file mode 100644 index 00000000..3f6b02bf --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/cmpl_clang_arm64.mak @@ -0,0 +1,3 @@ +include ../../var_clang_arm64.mak +include ../../warn_clang.mak +include makefile.gcc diff --git a/src/plugins/7zip/7za/cpp/7zip/cmpl_clang_x64.mak b/src/plugins/7zip/7za/cpp/7zip/cmpl_clang_x64.mak new file mode 100644 index 00000000..b61e2af6 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/cmpl_clang_x64.mak @@ -0,0 +1,3 @@ +include ../../var_clang_x64.mak +include ../../warn_clang.mak +include makefile.gcc diff --git a/src/plugins/7zip/7za/cpp/7zip/cmpl_clang_x86.mak b/src/plugins/7zip/7za/cpp/7zip/cmpl_clang_x86.mak new file mode 100644 index 00000000..0e5cb76c --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/cmpl_clang_x86.mak @@ -0,0 +1,3 @@ +include ../../var_clang_x86.mak +include ../../warn_clang.mak +include makefile.gcc diff --git a/src/plugins/7zip/7za/cpp/7zip/cmpl_gcc.mak b/src/plugins/7zip/7za/cpp/7zip/cmpl_gcc.mak new file mode 100644 index 00000000..7a1aef2e --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/cmpl_gcc.mak @@ -0,0 +1,3 @@ +include ../../var_gcc.mak +include ../../warn_gcc.mak +include makefile.gcc diff --git a/src/plugins/7zip/7za/cpp/7zip/cmpl_gcc_arm.mak b/src/plugins/7zip/7za/cpp/7zip/cmpl_gcc_arm.mak new file mode 100644 index 00000000..6671d58d --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/cmpl_gcc_arm.mak @@ -0,0 +1,3 @@ +include ../../var_gcc_arm.mak +include ../../warn_gcc.mak +include makefile.gcc diff --git a/src/plugins/7zip/7za/cpp/7zip/cmpl_gcc_arm64.mak b/src/plugins/7zip/7za/cpp/7zip/cmpl_gcc_arm64.mak new file mode 100644 index 00000000..53a85844 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/cmpl_gcc_arm64.mak @@ -0,0 +1,3 @@ +include ../../var_gcc_arm64.mak +include ../../warn_gcc.mak +include makefile.gcc diff --git a/src/plugins/7zip/7za/cpp/7zip/cmpl_gcc_x64.mak b/src/plugins/7zip/7za/cpp/7zip/cmpl_gcc_x64.mak new file mode 100644 index 00000000..500c30e4 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/cmpl_gcc_x64.mak @@ -0,0 +1,3 @@ +include ../../var_gcc_x64.mak +include ../../warn_gcc.mak +include makefile.gcc diff --git a/src/plugins/7zip/7za/cpp/7zip/cmpl_gcc_x86.mak b/src/plugins/7zip/7za/cpp/7zip/cmpl_gcc_x86.mak new file mode 100644 index 00000000..e7687070 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/cmpl_gcc_x86.mak @@ -0,0 +1,3 @@ +include ../../var_gcc_x86.mak +include ../../warn_gcc.mak +include makefile.gcc diff --git a/src/plugins/7zip/7za/cpp/7zip/cmpl_mac_arm64.mak b/src/plugins/7zip/7za/cpp/7zip/cmpl_mac_arm64.mak new file mode 100644 index 00000000..941028e9 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/cmpl_mac_arm64.mak @@ -0,0 +1,3 @@ +include ../../var_mac_arm64.mak +include ../../warn_clang_mac.mak +include makefile.gcc diff --git a/src/plugins/7zip/7za/cpp/7zip/cmpl_mac_x64.mak b/src/plugins/7zip/7za/cpp/7zip/cmpl_mac_x64.mak new file mode 100644 index 00000000..d3aa0396 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/cmpl_mac_x64.mak @@ -0,0 +1,3 @@ +include ../../var_mac_x64.mak +include ../../warn_clang_mac.mak +include makefile.gcc diff --git a/src/plugins/7zip/7za/cpp/7zip/var_clang.mak b/src/plugins/7zip/7za/cpp/7zip/var_clang.mak new file mode 100644 index 00000000..a6df26e7 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/var_clang.mak @@ -0,0 +1,11 @@ +PLATFORM= +O=b/c +IS_X64= +IS_X86= +IS_ARM64= +CROSS_COMPILE= +MY_ARCH= +USE_ASM= +CC=$(CROSS_COMPILE)clang +CXX=$(CROSS_COMPILE)clang++ +USE_CLANG=1 diff --git a/src/plugins/7zip/7za/cpp/7zip/var_clang_arm64.mak b/src/plugins/7zip/7za/cpp/7zip/var_clang_arm64.mak new file mode 100644 index 00000000..ec81d266 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/var_clang_arm64.mak @@ -0,0 +1,19 @@ +PLATFORM=arm64 +O=b/c_$(PLATFORM) +IS_X64= +IS_X86= +IS_ARM64=1 +CROSS_COMPILE= + +MY_ARCH=-march=armv8-a +MY_ARCH=-march=armv8-a+crypto+crc +MY_ARCH=-march=armv8.3-a+crypto+crc +MY_ARCH= + +USE_ASM= +USE_ASM=1 + +ASM_FLAGS=-Wno-unused-macros +CC=$(CROSS_COMPILE)clang +CXX=$(CROSS_COMPILE)clang++ +USE_CLANG=1 diff --git a/src/plugins/7zip/7za/cpp/7zip/var_clang_x64.mak b/src/plugins/7zip/7za/cpp/7zip/var_clang_x64.mak new file mode 100644 index 00000000..34e1b49c --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/var_clang_x64.mak @@ -0,0 +1,11 @@ +PLATFORM=x64 +O=b/c_$(PLATFORM) +IS_X64=1 +IS_X86= +IS_ARM64= +CROSS_COMPILE= +MY_ARCH= +USE_ASM=1 +CC=$(CROSS_COMPILE)clang +CXX=$(CROSS_COMPILE)clang++ +USE_CLANG=1 diff --git a/src/plugins/7zip/7za/cpp/7zip/var_clang_x86.mak b/src/plugins/7zip/7za/cpp/7zip/var_clang_x86.mak new file mode 100644 index 00000000..bd2317c2 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/var_clang_x86.mak @@ -0,0 +1,11 @@ +PLATFORM=x86 +O=b/c_$(PLATFORM) +IS_X64= +IS_X86=1 +IS_ARM64= +CROSS_COMPILE= +MY_ARCH=-m32 +USE_ASM=1 +CC=$(CROSS_COMPILE)clang +CXX=$(CROSS_COMPILE)clang++ +USE_CLANG=1 diff --git a/src/plugins/7zip/7za/cpp/7zip/var_gcc.mak b/src/plugins/7zip/7za/cpp/7zip/var_gcc.mak new file mode 100644 index 00000000..664491cf --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/var_gcc.mak @@ -0,0 +1,12 @@ +PLATFORM= +O=b/g +IS_X64= +IS_X86= +IS_ARM64= +CROSS_COMPILE= +MY_ARCH= +USE_ASM= +CC=$(CROSS_COMPILE)gcc +CXX=$(CROSS_COMPILE)g++ + +# -march=armv8-a+crc+crypto diff --git a/src/plugins/7zip/7za/cpp/7zip/var_gcc_arm.mak b/src/plugins/7zip/7za/cpp/7zip/var_gcc_arm.mak new file mode 100644 index 00000000..7527e407 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/var_gcc_arm.mak @@ -0,0 +1,34 @@ +PLATFORM=arm +O=b/g_$(PLATFORM) +IS_X64= +IS_X86= +IS_ARM64= + +CROSS_COMPILE_ABI= +CROSS_COMPILE_ABI=arm-linux-musleabi +CROSS_COMPILE_ABI=arm-linux-musleabihf +CROSS_COMPILE_ABI=aarch64-linux-musl +CROSS_COMPILE_ABI=arm-linux-gnueabi +CROSS_COMPILE_ABI=arm-linux-gnueabihf + +COMPILER_VER_POSTFIX=-12 +COMPILER_VER_POSTFIX= + +CROSS_COMPILE_PREFIX= + +CROSS_COMPILE=$(CROSS_COMPILE_PREFIX)$(CROSS_COMPILE_ABI)- +CROSS_COMPILE= + +MY_ARCH= +MY_ARCH=-mtune=cortex-a53 -march=armv7-a +MY_ARCH=-mtune=cortex-a53 -march=armv4 +MY_ARCH=-mtune=cortex-a53 + +USE_ASM= + +LDFLAGS_STATIC_3=-static +LDFLAGS_STATIC_3= + +CC=$(CROSS_COMPILE)gcc$(COMPILER_VER_POSTFIX) +CXX=$(CROSS_COMPILE)g++$(COMPILER_VER_POSTFIX) +# -marm -march=armv5t -march=armv6 -march=armv7-a -march=armv8-a -march=armv8-a+crc+crypto diff --git a/src/plugins/7zip/7za/cpp/7zip/var_gcc_arm64.mak b/src/plugins/7zip/7za/cpp/7zip/var_gcc_arm64.mak new file mode 100644 index 00000000..4bbb687d --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/var_gcc_arm64.mak @@ -0,0 +1,12 @@ +PLATFORM=arm64 +O=b/g_$(PLATFORM) +IS_X64= +IS_X86= +IS_ARM64=1 +CROSS_COMPILE= +MY_ARCH=-mtune=cortex-a53 +USE_ASM=1 +CC=$(CROSS_COMPILE)gcc +CXX=$(CROSS_COMPILE)g++ + +# -march=armv8-a+crc+crypto diff --git a/src/plugins/7zip/7za/cpp/7zip/var_gcc_x64.mak b/src/plugins/7zip/7za/cpp/7zip/var_gcc_x64.mak new file mode 100644 index 00000000..aeea2034 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/var_gcc_x64.mak @@ -0,0 +1,11 @@ +PLATFORM=x64 +O=b/g_$(PLATFORM) +IS_X64=1 +IS_X86= +IS_ARM64= +CROSS_COMPILE= +MY_ARCH=-mavx512f -mavx512vl +MY_ARCH= +USE_ASM=1 +CC=$(CROSS_COMPILE)gcc +CXX=$(CROSS_COMPILE)g++ diff --git a/src/plugins/7zip/7za/cpp/7zip/var_gcc_x86.mak b/src/plugins/7zip/7za/cpp/7zip/var_gcc_x86.mak new file mode 100644 index 00000000..f0718ec7 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/var_gcc_x86.mak @@ -0,0 +1,10 @@ +PLATFORM=x86 +O=b/g_$(PLATFORM) +IS_X64= +IS_X86=1 +IS_ARM64= +CROSS_COMPILE= +MY_ARCH=-m32 +USE_ASM=1 +CC=$(CROSS_COMPILE)gcc +CXX=$(CROSS_COMPILE)g++ diff --git a/src/plugins/7zip/7za/cpp/7zip/var_mac_arm64.mak b/src/plugins/7zip/7za/cpp/7zip/var_mac_arm64.mak new file mode 100644 index 00000000..746e6db1 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/var_mac_arm64.mak @@ -0,0 +1,13 @@ +PLATFORM=arm64 +O=b/m_$(PLATFORM) +IS_X64= +IS_X86= +IS_ARM64=1 +CROSS_COMPILE= +#use this code to reduce features +MY_ARCH=-arch arm64 -march=armv8-a +MY_ARCH=-arch arm64 +USE_ASM=1 +CC=$(CROSS_COMPILE)clang +CXX=$(CROSS_COMPILE)clang++ +USE_CLANG=1 diff --git a/src/plugins/7zip/7za/cpp/7zip/var_mac_x64.mak b/src/plugins/7zip/7za/cpp/7zip/var_mac_x64.mak new file mode 100644 index 00000000..13d7aa7f --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/var_mac_x64.mak @@ -0,0 +1,11 @@ +PLATFORM=x64 +O=b/m_$(PLATFORM) +IS_X64=1 +IS_X86= +IS_ARM64= +CROSS_COMPILE= +MY_ARCH=-arch x86_64 +USE_ASM= +CC=$(CROSS_COMPILE)clang +CXX=$(CROSS_COMPILE)clang++ +USE_CLANG=1 diff --git a/src/plugins/7zip/7za/cpp/7zip/warn_clang.mak b/src/plugins/7zip/7za/cpp/7zip/warn_clang.mak new file mode 100644 index 00000000..0d007307 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/warn_clang.mak @@ -0,0 +1,3 @@ +CFLAGS_WARN = -Weverything -Wfatal-errors +# CXX_STD_FLAGS = -std=c++11 +# CXX_STD_FLAGS = diff --git a/src/plugins/7zip/7za/cpp/7zip/warn_clang_mac.mak b/src/plugins/7zip/7za/cpp/7zip/warn_clang_mac.mak new file mode 100644 index 00000000..ed936c58 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/warn_clang_mac.mak @@ -0,0 +1,9 @@ +CFLAGS_WARN = -Weverything -Wfatal-errors -Wno-poison-system-directories +CXX_STD_FLAGS = -std=c++98 +CXX_STD_FLAGS = -std=c++11 +CXX_STD_FLAGS = -std=c++14 +CXX_STD_FLAGS = -std=c++17 +CXX_STD_FLAGS = -std=c++20 +CXX_STD_FLAGS = -std=c++23 + +CXX_STD_FLAGS = -std=c++11 diff --git a/src/plugins/7zip/7za/cpp/7zip/warn_gcc.mak b/src/plugins/7zip/7za/cpp/7zip/warn_gcc.mak new file mode 100644 index 00000000..6152ab12 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/7zip/warn_gcc.mak @@ -0,0 +1,55 @@ +CFLAGS_WARN_GCC_4_8 = \ + -Waddress \ + -Waggressive-loop-optimizations \ + -Wattributes \ + -Wcast-align \ + -Wcomment \ + -Wdiv-by-zero \ + -Wformat-contains-nul \ + -Winit-self \ + -Wint-to-pointer-cast \ + -Wunused \ + -Wunused-macros \ + +CFLAGS_WARN_GCC_5 = $(CFLAGS_WARN_GCC_4_8)\ + -Wbool-compare \ + +CFLAGS_WARN_GCC_6 = $(CFLAGS_WARN_GCC_5)\ + -Wduplicated-cond \ + +# -Wno-strict-aliasing + +CFLAGS_WARN_GCC_7 = $(CFLAGS_WARN_GCC_6)\ + -Wbool-operation \ + -Wconversion \ + -Wdangling-else \ + -Wduplicated-branches \ + -Wimplicit-fallthrough=5 \ + -Wint-in-bool-context \ + -Wmaybe-uninitialized \ + -Wmisleading-indentation \ + +CFLAGS_WARN_GCC_8 = $(CFLAGS_WARN_GCC_7)\ + -Wcast-align=strict \ + -Wmissing-attributes + +CFLAGS_WARN_GCC_9 = $(CFLAGS_WARN_GCC_8)\ + -Waddress-of-packed-member \ + +# In C: -Wsign-conversion enabled also by -Wconversion +# -Wno-sign-conversion \ + + +CFLAGS_WARN_GCC_PPMD_UNALIGNED = \ + -Wno-strict-aliasing \ + + +CFLAGS_WARN = $(CFLAGS_WARN_GCC_4_8) +CFLAGS_WARN = $(CFLAGS_WARN_GCC_5) +CFLAGS_WARN = $(CFLAGS_WARN_GCC_6) +CFLAGS_WARN = $(CFLAGS_WARN_GCC_7) +CFLAGS_WARN = $(CFLAGS_WARN_GCC_8) +CFLAGS_WARN = $(CFLAGS_WARN_GCC_9) + +# CXX_STD_FLAGS = -std=c++11 +# CXX_STD_FLAGS = diff --git a/src/plugins/7zip/7za/cpp/Build.mak b/src/plugins/7zip/7za/cpp/Build.mak new file mode 100644 index 00000000..d9fcfb52 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/Build.mak @@ -0,0 +1,258 @@ +LIBS = $(LIBS) oleaut32.lib ole32.lib + +# CFLAGS = $(CFLAGS) -DZ7_NO_UNICODE +!IFNDEF MY_NO_UNICODE +# CFLAGS = $(CFLAGS) -DUNICODE -D_UNICODE +!ENDIF + +!IF "$(CC)" != "clang-cl" +# for link time code generation: +# CFLAGS = $(CFLAGS) -GL +!ENDIF + +!IFNDEF O +!IFDEF PLATFORM +O=$(PLATFORM) +!ELSE +O=o +!ENDIF +!ENDIF + +!IF "$(CC)" != "clang-cl" +# CFLAGS = $(CFLAGS) -FAsc -Fa$O/asm/ +!ENDIF + +# LFLAGS = $(LFLAGS) /guard:cf + + +!IF "$(PLATFORM)" == "x64" +MY_ML = ml64 -WX +#-Dx64 +!ELSEIF "$(PLATFORM)" == "arm64" +MY_ML = armasm64 +!ELSEIF "$(PLATFORM)" == "arm" +MY_ML = armasm -WX +!ELSE +MY_ML = ml -WX +# -DABI_CDECL +!ENDIF + +# MY_ML = "$(MY_ML) -Fl$O\asm\ + + +!IFDEF UNDER_CE +RFLAGS = $(RFLAGS) -dUNDER_CE +!IFDEF MY_CONSOLE +LFLAGS = $(LFLAGS) /ENTRY:mainACRTStartup +!ENDIF +!ELSE +!IFDEF OLD_COMPILER +LFLAGS = $(LFLAGS) -OPT:NOWIN98 +!ENDIF +!IF "$(PLATFORM)" != "arm" && "$(PLATFORM)" != "arm64" +CFLAGS = $(CFLAGS) -Gr +!ENDIF +LIBS = $(LIBS) user32.lib advapi32.lib shell32.lib +!ENDIF + +!IF "$(PLATFORM)" == "arm" +COMPL_ASM = $(MY_ML) $** $O/$(*B).obj +!ELSEIF "$(PLATFORM)" == "arm64" +COMPL_ASM = $(MY_ML) $** $O/$(*B).obj +!ELSE +COMPL_ASM = $(MY_ML) -c -Fo$O/ $** +!ENDIF + +!IFDEF OLD_COMPILER +CFLAGS_WARN_LEVEL = -W4 +!ELSE +CFLAGS_WARN_LEVEL = -Wall +!ENDIF + +CFLAGS = $(CFLAGS) -nologo -c -Fo$O/ $(CFLAGS_WARN_LEVEL) -WX -EHsc -Gy -GR- -GF + +!IF "$(CC)" == "clang-cl" + +CFLAGS = $(CFLAGS) \ + -Werror \ + -Wall \ + -Wextra \ + -Weverything \ + -Wfatal-errors \ + +!ENDIF + +# !IFDEF MY_DYNAMIC_LINK +!IF "$(MY_DYNAMIC_LINK)" != "" +CFLAGS = $(CFLAGS) -MD +!ELSE +!IFNDEF MY_SINGLE_THREAD +CFLAGS = $(CFLAGS) -MT +!ENDIF +!ENDIF + + +CFLAGS = $(CFLAGS_COMMON) $(CFLAGS) + + +!IFNDEF OLD_COMPILER + +CFLAGS = $(CFLAGS) -GS- -Zc:wchar_t +!IFDEF VCTOOLSVERSION +!IF "$(VCTOOLSVERSION)" >= "14.00" +!IF "$(CC)" != "clang-cl" +CFLAGS = $(CFLAGS) -Zc:throwingNew +!ENDIF +!ENDIF +!ELSE +# -Zc:forScope is default in VS2010. so we need it only for older versions +CFLAGS = $(CFLAGS) -Zc:forScope +!ENDIF + +!IFNDEF UNDER_CE +!IF "$(CC)" != "clang-cl" +MP_NPROC = 16 +!IFDEF NUMBER_OF_PROCESSORS +!IF $(NUMBER_OF_PROCESSORS) < $(MP_NPROC) +MP_NPROC = $(NUMBER_OF_PROCESSORS) +!ENDIF +!ENDIF +CFLAGS = $(CFLAGS) -MP$(MP_NPROC) +!ENDIF +!IFNDEF PLATFORM +# CFLAGS = $(CFLAGS) -arch:IA32 +!ENDIF +!ENDIF + +!ENDIF + + +!IFDEF MY_CONSOLE +CFLAGS = $(CFLAGS) -D_CONSOLE +!ENDIF + +!IFNDEF UNDER_CE +!IF "$(PLATFORM)" == "arm" +CFLAGS = $(CFLAGS) -D_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE +!ENDIF +!ENDIF + +!IF "$(PLATFORM)" == "x64" +CFLAGS_O1 = $(CFLAGS) -O1 +!ELSE +CFLAGS_O1 = $(CFLAGS) -O1 +!ENDIF +CFLAGS_O2 = $(CFLAGS) -O2 + +LFLAGS = $(LFLAGS) -nologo -OPT:REF -OPT:ICF -INCREMENTAL:NO + +!IFNDEF UNDER_CE +LFLAGS = $(LFLAGS) /LARGEADDRESSAWARE +!ENDIF + +!IFDEF DEF_FILE +LFLAGS = $(LFLAGS) -DLL -DEF:$(DEF_FILE) +!ELSE +!IF defined(MY_FIXED) && "$(PLATFORM)" != "arm" && "$(PLATFORM)" != "arm64" +LFLAGS = $(LFLAGS) /FIXED +!ELSE +LFLAGS = $(LFLAGS) /FIXED:NO +!ENDIF +# /BASE:0x400000 +!ENDIF + +!IF "$(PLATFORM)" == "arm64" +# we can get better compression ratio with ARM64 filter if we change alignment to 4096 +# LFLAGS = $(LFLAGS) /FILEALIGN:4096 +!ENDIF + +!IFNDEF DEF_FILE +!IF "$(PLATFORM)" == "x86" || "$(PLATFORM)" == "arm" +LFLAGS = $(LFLAGS) /STACK:2097152 +!ELSE IF "$(PLATFORM)" == "x64" || "$(PLATFORM)" == "arm64" || "$(PLATFORM)" == "ia64" +LFLAGS = $(LFLAGS) /STACK:8388608 +!ENDIF +!ENDIF + +# !IF "$(PLATFORM)" == "x64" + +!IFDEF SUB_SYS_VER + +MY_SUB_SYS_VER=5.02 + +!IFDEF MY_CONSOLE +LFLAGS = $(LFLAGS) /SUBSYSTEM:console,$(MY_SUB_SYS_VER) +!ELSE +LFLAGS = $(LFLAGS) /SUBSYSTEM:windows,$(MY_SUB_SYS_VER) +!ENDIF + +!ENDIF + + +!IF "$(PLATFORM)" == "arm64" +CLANG_FLAGS_TARGET = --target=arm64-pc-windows-msvc +!ENDIF + +COMPL_CLANG_SPEC=clang-cl $(CLANG_FLAGS_TARGET) +COMPL_ASM_CLANG = $(COMPL_CLANG_SPEC) -nologo -c -Fo$O/ $(CFLAGS_WARN_LEVEL) -WX $** +# COMPL_C_CLANG = $(COMPL_CLANG_SPEC) $(CFLAGS_O2) + + +PROGPATH = $O\$(PROG) + +COMPL_O1 = $(CC) $(CFLAGS_O1) $** +COMPL_O2 = $(CC) $(CFLAGS_O2) $** +COMPL_PCH = $(CC) $(CFLAGS_O1) -Yc"StdAfx.h" -Fp$O/a.pch $** +COMPL = $(CC) $(CFLAGS_O1) -Yu"StdAfx.h" -Fp$O/a.pch $** +COMPLB = $(CC) $(CFLAGS_O1) -Yu"StdAfx.h" -Fp$O/a.pch $< +COMPLB_O2 = $(CC) $(CFLAGS_O2) $< +# COMPLB_O2 = $(CC) $(CFLAGS_O2) -Yu"StdAfx.h" -Fp$O/a.pch $< + +CFLAGS_C_ALL = $(CFLAGS_O2) $(CFLAGS_C_SPEC) + +CCOMPL_PCH = $(CC) $(CFLAGS_C_ALL) -Yc"Precomp.h" -Fp$O/a.pch $** +CCOMPL_USE = $(CC) $(CFLAGS_C_ALL) -Yu"Precomp.h" -Fp$O/a.pch $** +CCOMPLB_USE = $(CC) $(CFLAGS_C_ALL) -Yu"Precomp.h" -Fp$O/a.pch $< +CCOMPL = $(CC) $(CFLAGS_C_ALL) $** +CCOMPLB = $(CC) $(CFLAGS_C_ALL) $< + +!IF "$(CC)" == "clang-cl" +COMPL = $(COMPL) -FI StdAfx.h +COMPLB = $(COMPLB) -FI StdAfx.h +CCOMPL_USE = $(CCOMPL_USE) -FI Precomp.h +CCOMPLB_USE = $(CCOMPLB_USE) -FI Precomp.h +!ENDIF + +all: $(PROGPATH) + +clean: + -del /Q $(PROGPATH) $O\*.exe $O\*.dll $O\*.obj $O\*.lib $O\*.exp $O\*.res $O\*.pch $O\*.asm + +$O: + if not exist "$O" mkdir "$O" +$O/asm: + if not exist "$O/asm" mkdir "$O/asm" + +!IF "$(CC)" != "clang-cl" +# for link time code generation: +# LFLAGS = $(LFLAGS) -LTCG +!ENDIF + +$(PROGPATH): $O $O/asm $(OBJS) $(DEF_FILE) + link $(LFLAGS) -out:$(PROGPATH) $(OBJS) $(LIBS) + +!IFNDEF NO_DEFAULT_RES +$O\resource.res: $(*B).rc + rc $(RFLAGS) -fo$@ $** +!ENDIF +$O\StdAfx.obj: $(*B).cpp + $(COMPL_PCH) + +predef: empty.c + $(CCOMPL) /EP /Zc:preprocessor /PD +predef2: A.cpp + $(COMPL) -EP -Zc:preprocessor -PD +predef3: A.cpp + $(COMPL) -E -dM +predef4: A.cpp + $(COMPL_O2) -E diff --git a/src/plugins/7zip/7za/cpp/Common/AutoPtr.h b/src/plugins/7zip/7za/cpp/Common/AutoPtr.h index 006d3155..e3c57638 100644 --- a/src/plugins/7zip/7za/cpp/Common/AutoPtr.h +++ b/src/plugins/7zip/7za/cpp/Common/AutoPtr.h @@ -1,34 +1,45 @@ // Common/AutoPtr.h -#ifndef __COMMON_AUTOPTR_H -#define __COMMON_AUTOPTR_H +#ifndef ZIP7_INC_COMMON_AUTOPTR_H +#define ZIP7_INC_COMMON_AUTOPTR_H -template class CMyAutoPtr +template class CMyUniquePtr +// CMyAutoPtr { T *_p; -public: - CMyAutoPtr(T *p = 0) : _p(p) {} - CMyAutoPtr(CMyAutoPtr& p): _p(p.release()) {} - CMyAutoPtr& operator=(CMyAutoPtr& p) + + CMyUniquePtr(CMyUniquePtr& p); // : _p(p.release()) {} + CMyUniquePtr& operator=(T *p); + CMyUniquePtr& operator=(CMyUniquePtr& p); + /* { reset(p.release()); return (*this); } - ~CMyAutoPtr() { delete _p; } + */ + void reset(T* p = NULL) + { + if (p != _p) + delete _p; + _p = p; + } +public: + CMyUniquePtr(T *p = NULL) : _p(p) {} + ~CMyUniquePtr() { delete _p; } T& operator*() const { return *_p; } - // T* operator->() const { return (&**this); } + T* operator->() const { return _p; } + // operator bool() const { return _p != NULL; } T* get() const { return _p; } T* release() { T *tmp = _p; - _p = 0; + _p = NULL; return tmp; } - void reset(T* p = 0) + void Create_if_Empty() { - if (p != _p) - delete _p; - _p = p; + if (!_p) + _p = new T; } }; diff --git a/src/plugins/7zip/7za/cpp/Common/CRC.cpp b/src/plugins/7zip/7za/cpp/Common/CRC.cpp index 9a9f81fb..c6b7d5e4 100644 --- a/src/plugins/7zip/7za/cpp/Common/CRC.cpp +++ b/src/plugins/7zip/7za/cpp/Common/CRC.cpp @@ -4,4 +4,4 @@ #include "../../C/7zCrc.h" -struct CCRCTableInit { CCRCTableInit() { CrcGenerateTable(); } } g_CRCTableInit; +static struct CCRCTableInit { CCRCTableInit() { CrcGenerateTable(); } } g_CRCTableInit; diff --git a/src/plugins/7zip/7za/cpp/Common/C_FileIO.cpp b/src/plugins/7zip/7za/cpp/Common/C_FileIO.cpp index 6848d36f..4bd3fadc 100644 --- a/src/plugins/7zip/7za/cpp/Common/C_FileIO.cpp +++ b/src/plugins/7zip/7za/cpp/Common/C_FileIO.cpp @@ -1,103 +1,3 @@ // Common/C_FileIO.cpp -// OPENSAL_7ZIP_PATCH BEGIN -// fopen() -> _fopen() -// fclose() -> _fclose() -// ... -#ifdef _MSC_VER -#define _CRT_SECURE_NO_WARNINGS -#endif -// OPENSAL_7ZIP_PATCH END - -#include "C_FileIO.h" - -#include -#ifdef _WIN32 -#include -#else -#include -#endif - - - -namespace NC { -namespace NFile { -namespace NIO { - -bool CFileBase::OpenBinary(const char *name, int flags) -{ - #ifdef O_BINARY - flags |= O_BINARY; - #endif - Close(); - _handle = ::_open(name, flags, 0666); - return _handle != -1; -} - -bool CFileBase::Close() -{ - if (_handle == -1) - return true; - if (_close(_handle) != 0) - return false; - _handle = -1; - return true; -} - -bool CFileBase::GetLength(UInt64 &length) const -{ - off_t curPos = Seek(0, SEEK_CUR); - off_t lengthTemp = Seek(0, SEEK_END); - Seek(curPos, SEEK_SET); - length = (UInt64)lengthTemp; - return true; -} - -off_t CFileBase::Seek(off_t distanceToMove, int moveMethod) const -{ - return ::_lseek(_handle, distanceToMove, moveMethod); -} - -///////////////////////// -// CInFile - -bool CInFile::Open(const char *name) -{ - return CFileBase::OpenBinary(name, O_RDONLY); -} - -bool CInFile::OpenShared(const char *name, bool) -{ - return Open(name); -} - -ssize_t CInFile::Read(void *data, size_t size) -{ - return _read(_handle, data, (unsigned int)size); -} - -///////////////////////// -// COutFile - -bool COutFile::Create(const char *name, bool createAlways) -{ - if (createAlways) - { - Close(); - _handle = ::_creat(name, 0666); - return _handle != -1; - } - return OpenBinary(name, O_CREAT | O_EXCL | O_WRONLY); -} - -bool COutFile::Open(const char *name, DWORD creationDisposition) -{ - return Create(name, false); -} - -ssize_t COutFile::Write(const void *data, size_t size) -{ - return _write(_handle, data, (unsigned int)size); -} - -}}} +#include "StdAfx.h" diff --git a/src/plugins/7zip/7za/cpp/Common/C_FileIO.h b/src/plugins/7zip/7za/cpp/Common/C_FileIO.h index ff4ec162..12d94391 100644 --- a/src/plugins/7zip/7za/cpp/Common/C_FileIO.h +++ b/src/plugins/7zip/7za/cpp/Common/C_FileIO.h @@ -1,53 +1,6 @@ // Common/C_FileIO.h -#ifndef __COMMON_C_FILEIO_H -#define __COMMON_C_FILEIO_H - -#include -#include - -#include "MyTypes.h" -#include "MyWindows.h" - -#ifdef _WIN32 -#ifdef _MSC_VER -typedef size_t ssize_t; -#endif -#endif - -namespace NC { -namespace NFile { -namespace NIO { - -class CFileBase -{ -protected: - int _handle; - bool OpenBinary(const char *name, int flags); -public: - CFileBase(): _handle(-1) {}; - ~CFileBase() { Close(); } - bool Close(); - bool GetLength(UInt64 &length) const; - off_t Seek(off_t distanceToMove, int moveMethod) const; -}; - -class CInFile: public CFileBase -{ -public: - bool Open(const char *name); - bool OpenShared(const char *name, bool shareForWrite); - ssize_t Read(void *data, size_t size); -}; - -class COutFile: public CFileBase -{ -public: - bool Create(const char *name, bool createAlways); - bool Open(const char *name, DWORD creationDisposition); - ssize_t Write(const void *data, size_t size); -}; - -}}} +#ifndef ZIP7_INC_COMMON_C_FILEIO_H +#define ZIP7_INC_COMMON_C_FILEIO_H #endif diff --git a/src/plugins/7zip/7za/cpp/Common/CksumReg.cpp b/src/plugins/7zip/7za/cpp/Common/CksumReg.cpp new file mode 100644 index 00000000..8c9c4490 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/Common/CksumReg.cpp @@ -0,0 +1,55 @@ +// CksumReg.cpp + +#include "StdAfx.h" + +#include "../../C/CpuArch.h" + +#include "../Common/MyCom.h" + +#include "../7zip/Common/RegisterCodec.h" +#include "../7zip/Compress/BZip2Crc.h" + +Z7_CLASS_IMP_COM_1( + CCksumHasher + , IHasher +) + CBZip2Crc _crc; + UInt64 _size; +public: + // Byte _mtDummy[1 << 7]; + CCksumHasher() + { + _crc.Init(0); + _size = 0; + } +}; + +Z7_COM7F_IMF2(void, CCksumHasher::Init()) +{ + _crc.Init(0); + _size = 0; +} + +Z7_COM7F_IMF2(void, CCksumHasher::Update(const void *data, UInt32 size)) +{ + _size += size; + CBZip2Crc crc = _crc; + for (UInt32 i = 0; i < size; i++) + crc.UpdateByte(((const Byte *)data)[i]); + _crc = crc; +} + +Z7_COM7F_IMF2(void, CCksumHasher::Final(Byte *digest)) +{ + UInt64 size = _size; + CBZip2Crc crc = _crc; + while (size) + { + crc.UpdateByte((Byte)size); + size >>= 8; + } + const UInt32 val = crc.GetDigest(); + SetUi32(digest, val) +} + +REGISTER_HASHER(CCksumHasher, 0x203, "CKSUM", 4) diff --git a/src/plugins/7zip/7za/cpp/Common/ComTry.h b/src/plugins/7zip/7za/cpp/Common/ComTry.h index fb4ef045..84746a7d 100644 --- a/src/plugins/7zip/7za/cpp/Common/ComTry.h +++ b/src/plugins/7zip/7za/cpp/Common/ComTry.h @@ -1,7 +1,7 @@ // ComTry.h -#ifndef __COM_TRY_H -#define __COM_TRY_H +#ifndef ZIP7_INC_COM_TRY_H +#define ZIP7_INC_COM_TRY_H #include "MyWindows.h" // #include "Exception.h" @@ -10,7 +10,11 @@ #define COM_TRY_BEGIN try { #define COM_TRY_END } catch(...) { return E_OUTOFMEMORY; } - // catch(const CNewException &) { return E_OUTOFMEMORY; } +/* +#define COM_TRY_END } \ + catch(const CNewException &) { return E_OUTOFMEMORY; } \ + catch(...) { return HRESULT_FROM_WIN32(ERROR_NOACCESS); } \ +*/ // catch(const CSystemException &e) { return e.ErrorCode; } // catch(...) { return E_FAIL; } diff --git a/src/plugins/7zip/7za/cpp/Common/CommandLineParser.cpp b/src/plugins/7zip/7za/cpp/Common/CommandLineParser.cpp index 1c7f2654..319f27fc 100644 --- a/src/plugins/7zip/7za/cpp/Common/CommandLineParser.cpp +++ b/src/plugins/7zip/7za/cpp/Common/CommandLineParser.cpp @@ -4,22 +4,10 @@ #include "CommandLineParser.h" -static bool IsString1PrefixedByString2_NoCase(const wchar_t *u, const char *a) -{ - for (;;) - { - char c = *a; - if (c == 0) - return true; - if ((unsigned char)MyCharLower_Ascii(c) != MyCharLower_Ascii(*u)) - return false; - a++; - u++; - } -} - namespace NCommandLineParser { +#ifdef _WIN32 + bool SplitCommandLine(const UString &src, UString &dest1, UString &dest2) { dest1.Empty(); @@ -28,7 +16,7 @@ bool SplitCommandLine(const UString &src, UString &dest1, UString &dest2) unsigned i; for (i = 0; i < src.Len(); i++) { - wchar_t c = src[i]; + const wchar_t c = src[i]; if ((c == L' ' || c == L'\t') && !quoteMode) { dest2 = src.Ptr(i + 1); @@ -44,7 +32,35 @@ bool SplitCommandLine(const UString &src, UString &dest1, UString &dest2) void SplitCommandLine(const UString &s, UStringVector &parts) { - UString sTemp = s; +#if 0 +/* we don't use CommandLineToArgvW() because + it can remove tail backslash: + "1\" + converted to + 1" +*/ + parts.Clear(); + { + int nArgs; + LPWSTR *szArgList = CommandLineToArgvW(s, &nArgs); + if (szArgList) + { + for (int i = 0; i < nArgs; i++) + { + // printf("%2d: |%S|\n", i, szArglist[i]); + parts.Add(szArgList[i]); + } + LocalFree(szArgList); + return; + } + } +#endif +/* +#ifdef _UNICODE + throw 20240406; +#else +*/ + UString sTemp (s); sTemp.Trim(); parts.Clear(); for (;;) @@ -56,21 +72,22 @@ void SplitCommandLine(const UString &s, UStringVector &parts) break; sTemp = s2; } +// #endif } +#endif -static const char *kStopSwitchParsing = "--"; +static const char * const kStopSwitchParsing = "--"; static bool inline IsItSwitchChar(wchar_t c) { return (c == '-'); } -CParser::CParser(unsigned numSwitches): - _numSwitches(numSwitches), - _switches(0) +CParser::CParser(): + _switches(NULL), + StopSwitchIndex(-1) { - _switches = new CSwitchResult[numSwitches]; } CParser::~CParser() @@ -81,7 +98,7 @@ CParser::~CParser() // if (s) contains switch then function updates switch structures // out: true, if (s) is a switch -bool CParser::ParseString(const UString &s, const CSwitchForm *switchForms) +bool CParser::ParseString(const UString &s, const CSwitchForm *switchForms, unsigned numSwitches) { if (s.IsEmpty() || !IsItSwitchChar(s[0])) return false; @@ -90,16 +107,16 @@ bool CParser::ParseString(const UString &s, const CSwitchForm *switchForms) unsigned switchIndex = 0; int maxLen = -1; - for (unsigned i = 0; i < _numSwitches; i++) + for (unsigned i = 0; i < numSwitches; i++) { - const char *key = switchForms[i].Key; - unsigned switchLen = MyStringLen(key); + const char * const key = switchForms[i].Key; + const unsigned switchLen = MyStringLen(key); if ((int)switchLen <= maxLen || pos + switchLen > s.Len()) continue; - if (IsString1PrefixedByString2_NoCase((const wchar_t *)s + pos, key)) + if (IsString1PrefixedByString2_NoCase_Ascii((const wchar_t *)s + pos, key)) { switchIndex = i; - maxLen = switchLen; + maxLen = (int)switchLen; } } @@ -109,7 +126,7 @@ bool CParser::ParseString(const UString &s, const CSwitchForm *switchForms) return false; } - pos += maxLen; + pos += (unsigned)maxLen; CSwitchResult &sw = _switches[switchIndex]; const CSwitchForm &form = switchForms[switchIndex]; @@ -122,7 +139,7 @@ bool CParser::ParseString(const UString &s, const CSwitchForm *switchForms) sw.ThereIs = true; - int rem = s.Len() - pos; + const unsigned rem = s.Len() - pos; if (rem < form.MinLen) { ErrorMessage = "Too short switch:"; @@ -148,7 +165,7 @@ bool CParser::ParseString(const UString &s, const CSwitchForm *switchForms) case NSwitchType::kChar: if (rem == 1) { - wchar_t c = s[pos]; + const wchar_t c = s[pos]; if (c <= 0x7F) { sw.PostCharIndex = FindCharPosInString(form.PostCharSet, (char)c); @@ -161,8 +178,12 @@ bool CParser::ParseString(const UString &s, const CSwitchForm *switchForms) break; case NSwitchType::kString: - sw.PostStrings.Add((const wchar_t *)s + pos); + { + sw.PostStrings.Add(s.Ptr(pos)); return true; + } + // case NSwitchType::kSimple: + default: break; } if (pos != s.Len()) @@ -173,23 +194,30 @@ bool CParser::ParseString(const UString &s, const CSwitchForm *switchForms) return true; } -bool CParser::ParseStrings(const CSwitchForm *switchForms, const UStringVector &commandStrings) + +bool CParser::ParseStrings(const CSwitchForm *switchForms, unsigned numSwitches, const UStringVector &commandStrings) { + StopSwitchIndex = -1; + ErrorMessage.Empty(); ErrorLine.Empty(); - bool stopSwitch = false; + NonSwitchStrings.Clear(); + delete []_switches; + _switches = NULL; + _switches = new CSwitchResult[numSwitches]; + FOR_VECTOR (i, commandStrings) { const UString &s = commandStrings[i]; - if (!stopSwitch) + if (StopSwitchIndex < 0) { if (s.IsEqualTo(kStopSwitchParsing)) { - stopSwitch = true; + StopSwitchIndex = (int)NonSwitchStrings.Size(); continue; } if (!s.IsEmpty() && IsItSwitchChar(s[0])) { - if (ParseString(s, switchForms)) + if (ParseString(s, switchForms, numSwitches)) continue; ErrorLine = s; return false; diff --git a/src/plugins/7zip/7za/cpp/Common/CommandLineParser.h b/src/plugins/7zip/7za/cpp/Common/CommandLineParser.h index c9fd2956..fc6f028e 100644 --- a/src/plugins/7zip/7za/cpp/Common/CommandLineParser.h +++ b/src/plugins/7zip/7za/cpp/Common/CommandLineParser.h @@ -1,7 +1,7 @@ // Common/CommandLineParser.h -#ifndef __COMMON_COMMAND_LINE_PARSER_H -#define __COMMON_COMMAND_LINE_PARSER_H +#ifndef ZIP7_INC_COMMON_COMMAND_LINE_PARSER_H +#define ZIP7_INC_COMMON_COMMAND_LINE_PARSER_H #include "MyString.h" @@ -38,24 +38,24 @@ struct CSwitchResult int PostCharIndex; UStringVector PostStrings; - CSwitchResult(): ThereIs(false) {}; + CSwitchResult(): ThereIs(false) {} }; class CParser { - unsigned _numSwitches; CSwitchResult *_switches; - bool ParseString(const UString &s, const CSwitchForm *switchForms); + bool ParseString(const UString &s, const CSwitchForm *switchForms, unsigned numSwitches); public: UStringVector NonSwitchStrings; + int StopSwitchIndex; // NonSwitchStrings[StopSwitchIndex+] are after "--" AString ErrorMessage; UString ErrorLine; - CParser(unsigned numSwitches); + CParser(); ~CParser(); - bool ParseStrings(const CSwitchForm *switchForms, const UStringVector &commandStrings); - const CSwitchResult& operator[](size_t index) const { return _switches[index]; } + bool ParseStrings(const CSwitchForm *switchForms, unsigned numSwitches, const UStringVector &commandStrings); + const CSwitchResult& operator[](unsigned index) const { return _switches[index]; } }; } diff --git a/src/plugins/7zip/7za/cpp/Common/Common.h b/src/plugins/7zip/7za/cpp/Common/Common.h index 9dd30f4b..cde0c38a 100644 --- a/src/plugins/7zip/7za/cpp/Common/Common.h +++ b/src/plugins/7zip/7za/cpp/Common/Common.h @@ -1,13 +1,28 @@ // Common.h -#ifndef __COMMON_COMMON_H -#define __COMMON_COMMON_H +#if defined(_MSC_VER) && _MSC_VER >= 1800 +#pragma warning(disable : 4464) // relative include path contains '..' +#endif -#include "../../C/Compiler.h" +#ifndef ZIP7_INC_COMMON_H +#define ZIP7_INC_COMMON_H +#include "../../C/Precomp.h" +#include "Common0.h" #include "MyWindows.h" -#include "NewHandler.h" -#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[1])) +/* +This file is included to all cpp files in 7-Zip. +Each folder contains StdAfx.h file that includes "Common.h". +So 7-Zip includes "Common.h" in both modes: + with precompiled StdAfx.h +and + without precompiled StdAfx.h + +include "Common.h" before other h files of 7-zip, + if you need predefined macros. +do not include "Common.h", if you need only interfaces, + and you don't need predefined macros. +*/ #endif diff --git a/src/plugins/7zip/7za/cpp/Common/Common0.h b/src/plugins/7zip/7za/cpp/Common/Common0.h new file mode 100644 index 00000000..a6b842da --- /dev/null +++ b/src/plugins/7zip/7za/cpp/Common/Common0.h @@ -0,0 +1,331 @@ +// Common0.h + +#if defined(_MSC_VER) && _MSC_VER >= 1800 +#pragma warning(disable : 4464) // relative include path contains '..' +#endif + +#ifndef ZIP7_INC_COMMON0_H +#define ZIP7_INC_COMMON0_H + +#include "../../C/Compiler.h" + +/* +This file contains compiler related things for cpp files. +This file is included to all cpp files in 7-Zip via "Common.h". +Also this file is included in "IDecl.h" (that is included in interface files). +So external modules can use 7-Zip interfaces without +predefined macros defined in "Common.h". +*/ + +#ifdef _MSC_VER + #pragma warning(disable : 4710) // function not inlined + // 'CUncopyable::CUncopyable': + #pragma warning(disable : 4514) // unreferenced inline function has been removed + #if _MSC_VER < 1300 + #pragma warning(disable : 4702) // unreachable code + #pragma warning(disable : 4714) // function marked as __forceinline not inlined + #pragma warning(disable : 4786) // identifier was truncated to '255' characters in the debug information + #endif + #if _MSC_VER < 1400 + #pragma warning(disable : 4511) // copy constructor could not be generated // #pragma warning(disable : 4512) // assignment operator could not be generated + #pragma warning(disable : 4512) // assignment operator could not be generated + #endif + #if _MSC_VER > 1400 && _MSC_VER <= 1900 + // #pragma warning(disable : 4996) + // strcat: This function or variable may be unsafe + // GetVersion was declared deprecated + #endif + +#if _MSC_VER > 1200 +// -Wall warnings + +#if _MSC_VER <= 1600 +#pragma warning(disable : 4917) // 'OLE_HANDLE' : a GUID can only be associated with a class, interface or namespace +#endif + +// #pragma warning(disable : 4061) // enumerator '' in switch of enum '' is not explicitly handled by a case label +// #pragma warning(disable : 4266) // no override available for virtual member function from base ''; function is hidden +#pragma warning(disable : 4625) // copy constructor was implicitly defined as deleted +#pragma warning(disable : 4626) // assignment operator was implicitly defined as deleted +#if _MSC_VER >= 1600 && _MSC_VER < 1920 +#pragma warning(disable : 4571) // Informational: catch(...) semantics changed since Visual C++ 7.1; structured exceptions (SEH) are no longer caught +#endif +#if _MSC_VER >= 1600 +#pragma warning(disable : 4365) // 'initializing' : conversion from 'int' to 'unsigned int', signed / unsigned mismatch +#endif +#if _MSC_VER < 1800 +// we disable the warning, if we don't use 'final' in class +#pragma warning(disable : 4265) // class has virtual functions, but destructor is not virtual +#endif + +#if _MSC_VER >= 1900 +#pragma warning(disable : 5026) // move constructor was implicitly defined as deleted +#pragma warning(disable : 5027) // move assignment operator was implicitly defined as deleted +#endif +#if _MSC_VER >= 1912 +#pragma warning(disable : 5039) // pointer or reference to potentially throwing function passed to 'extern "C"' function under - EHc.Undefined behavior may occur if this function throws an exception. +#endif +#if _MSC_VER >= 1925 +// #pragma warning(disable : 5204) // 'ISequentialInStream' : class has virtual functions, but its trivial destructor is not virtual; instances of objects derived from this class may not be destructed correctly +#endif +#if _MSC_VER >= 1934 +// #pragma warning(disable : 5264) // const variable is not used +#endif + +#endif // _MSC_VER > 1200 +#endif // _MSC_VER + + +#if defined(_MSC_VER) // && !defined(__clang__) +#define Z7_DECLSPEC_NOTHROW __declspec(nothrow) +#elif defined(__clang__) || defined(__GNUC__) +#define Z7_DECLSPEC_NOTHROW __attribute__((nothrow)) +#else +#define Z7_DECLSPEC_NOTHROW +#endif + +/* +#if defined (_MSC_VER) && _MSC_VER >= 1900 \ + || defined(__clang__) && __clang_major__ >= 6 \ + || defined(__GNUC__) && __GNUC__ >= 6 + #define Z7_noexcept noexcept +#else + #define Z7_noexcept throw() +#endif +*/ + + +#if defined(__clang__) + +#if /* defined(_WIN32) && */ __clang_major__ >= 16 +#pragma GCC diagnostic ignored "-Wc++98-compat-pedantic" +#endif + +#if __clang_major__ >= 4 && __clang_major__ < 12 && !defined(_WIN32) +/* +if compiled with new GCC libstdc++, GCC libstdc++ can use: +13.2.0/include/c++/ + : #define _NEW + : #define _GLIBCXX_STDLIB_H 1 +*/ +#pragma GCC diagnostic ignored "-Wreserved-id-macro" +#endif + +// noexcept, final, = delete +#pragma GCC diagnostic ignored "-Wc++98-compat" +#if __clang_major__ >= 4 +// throw() dynamic exception specifications are deprecated +#pragma GCC diagnostic ignored "-Wdeprecated-dynamic-exception-spec" +#endif + +#if __clang_major__ <= 6 // check it +#pragma GCC diagnostic ignored "-Wsign-conversion" +#endif + +#pragma GCC diagnostic ignored "-Wold-style-cast" +#pragma GCC diagnostic ignored "-Wglobal-constructors" +#pragma GCC diagnostic ignored "-Wexit-time-destructors" + +#if defined(Z7_LLVM_CLANG_VERSION) && __clang_major__ >= 18 /* 18.1.0RC */ \ + || defined(Z7_APPLE_CLANG_VERSION) && __clang_major__ >= 16 // for APPLE=17 (LLVM=19) + #pragma GCC diagnostic ignored "-Wswitch-default" +#endif +// #pragma GCC diagnostic ignored "-Wunused-private-field" +// #pragma GCC diagnostic ignored "-Wnonportable-system-include-path" +// #pragma GCC diagnostic ignored "-Wsuggest-override" +// #pragma GCC diagnostic ignored "-Wsign-conversion" +// #pragma GCC diagnostic ignored "-Winconsistent-missing-override" +// #pragma GCC diagnostic ignored "-Wsuggest-destructor-override" +// #pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +// #pragma GCC diagnostic ignored "-Wdeprecated-copy-with-user-provided-dtor" +// #pragma GCC diagnostic ignored "-Wdeprecated-copy-dtor" +// #ifndef _WIN32 +// #pragma GCC diagnostic ignored "-Wweak-vtables" +// #endif +/* +#if defined(Z7_GCC_VERSION) && (Z7_GCC_VERSION >= 40400) \ + || defined(Z7_CLANG_VERSION) && (Z7_CLANG_VERSION >= 30000) +// enumeration values not explicitly handled in switch +#pragma GCC diagnostic ignored "-Wswitch-enum" +#endif +*/ +#endif // __clang__ + + +#ifdef __GNUC__ +// #pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor" +#endif + + +/* There is BUG in MSVC 6.0 compiler for operator new[]: + It doesn't check overflow, when it calculates size in bytes for allocated array. + So we can use Z7_ARRAY_NEW macro instead of new[] operator. */ + +#if defined(_MSC_VER) && (_MSC_VER == 1200) && !defined(_WIN64) + #define Z7_ARRAY_NEW(p, T, size) p = new T[((size) > (0xFFFFFFFFu - 0x7fu) / sizeof(T)) ? (0xFFFFFFFFu - 0x7fu) / sizeof(T) : (size)]; +#else + #define Z7_ARRAY_NEW(p, T, size) p = new T[size]; +#endif + +#if (defined(__GNUC__) && (__GNUC__ >= 8)) + #define Z7_ATTR_NORETURN __attribute__((noreturn)) +#elif (defined(__clang__) && (__clang_major__ >= 3)) + #if __has_feature(cxx_attributes) + #define Z7_ATTR_NORETURN [[noreturn]] + #else + #define Z7_ATTR_NORETURN __attribute__((noreturn)) + #endif +#elif (defined(_MSC_VER) && (_MSC_VER >= 1900)) + #define Z7_ATTR_NORETURN [[noreturn]] +#else + #define Z7_ATTR_NORETURN +#endif + + +// final in "GCC 4.7.0" +// In C++98 and C++03 code the alternative spelling __final can be used instead (this is a GCC extension.) + +#if defined (__cplusplus) && __cplusplus >= 201103L \ + || defined(_MSC_VER) && _MSC_VER >= 1800 \ + || defined(__clang__) && __clang_major__ >= 4 \ + /* || defined(__GNUC__) && __GNUC__ >= 9 */ + #define Z7_final final + #if defined(__clang__) && __cplusplus < 201103L + #pragma GCC diagnostic ignored "-Wc++11-extensions" + #endif +#elif defined (__cplusplus) && __cplusplus >= 199711L \ + && defined(__GNUC__) && __GNUC__ >= 4 && !defined(__clang__) + #define Z7_final __final +#else + #define Z7_final + #if defined(__clang__) && __clang_major__ >= 4 \ + || defined(__GNUC__) && __GNUC__ >= 4 + #pragma GCC diagnostic ignored "-Wnon-virtual-dtor" + #pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor" + #endif +#endif + +#define Z7_class_final(c) class c Z7_final + + +#if defined (__cplusplus) && __cplusplus >= 201103L \ + || (defined(_MSC_VER) && _MSC_VER >= 1800) + #define Z7_CPP_IS_SUPPORTED_default + #define Z7_eq_delete = delete + // #define Z7_DECL_DEFAULT_COPY_CONSTRUCTOR_IF_SUPPORTED(c) c(const c& k) = default; +#else + #define Z7_eq_delete + // #define Z7_DECL_DEFAULT_COPY_CONSTRUCTOR_IF_SUPPORTED(c) +#endif + + +#if defined(__cplusplus) && (__cplusplus >= 201103L) \ + || defined(_MSC_VER) && (_MSC_VER >= 1400) /* && (_MSC_VER != 1600) */ \ + || defined(__clang__) && __clang_major__ >= 4 + #if defined(_MSC_VER) && (_MSC_VER == 1600) /* && (_MSC_VER != 1600) */ + #pragma warning(disable : 4481) // nonstandard extension used: override specifier 'override' + #define Z7_DESTRUCTOR_override + #else + #define Z7_DESTRUCTOR_override override + #endif + #define Z7_override override +#else + #define Z7_override + #define Z7_DESTRUCTOR_override +#endif + + + +#define Z7_CLASS_NO_COPY(cls) \ + private: \ + cls(const cls &) Z7_eq_delete; \ + cls &operator=(const cls &) Z7_eq_delete; + +class CUncopyable +{ +protected: + CUncopyable() {} // allow constructor + // ~CUncopyable() {} + Z7_CLASS_NO_COPY(CUncopyable) +}; + +#define MY_UNCOPYABLE :private CUncopyable +// #define MY_UNCOPYABLE + + +// typedef void (*Z7_void_Function)(void); + +#if defined(__clang__) || defined(__GNUC__) +#define Z7_CAST_FUNC(t, e) reinterpret_cast(reinterpret_cast(e)) +#else +#define Z7_CAST_FUNC(t, e) reinterpret_cast(reinterpret_cast(e)) +// #define Z7_CAST_FUNC(t, e) reinterpret_cast(e) +#endif + +#define Z7_GET_PROC_ADDRESS(func_type, hmodule, func_name) \ + Z7_CAST_FUNC(func_type, GetProcAddress(hmodule, func_name)) + +// || defined(__clang__) +// || defined(__GNUC__) + +#if defined(_MSC_VER) && (_MSC_VER >= 1400) +#define Z7_DECLSPEC_NOVTABLE __declspec(novtable) +#else +#define Z7_DECLSPEC_NOVTABLE +#endif + +#ifdef __clang__ +#define Z7_PURE_INTERFACES_BEGIN \ +_Pragma("GCC diagnostic push") \ +_Pragma("GCC diagnostic ignored \"-Wnon-virtual-dtor\"") +_Pragma("GCC diagnostic ignored \"-Wweak-vtables\"") +#define Z7_PURE_INTERFACES_END \ +_Pragma("GCC diagnostic pop") +#else +#define Z7_PURE_INTERFACES_BEGIN +#define Z7_PURE_INTERFACES_END +#endif + +// NewHandler.h and NewHandler.cpp redefine operator new() to throw exceptions, if compiled with old MSVC compilers +#include "NewHandler.h" + +/* +// #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(a) Z7_ARRAY_SIZE(a) +#endif +*/ + +#endif // ZIP7_INC_COMMON0_H + + + +// #define Z7_REDEFINE_NULL + +#if defined(Z7_REDEFINE_NULL) /* && (!defined(__clang__) || defined(_MSC_VER)) */ + +// NULL is defined in +#include +#undef NULL + +#ifdef __cplusplus + #if defined (__cplusplus) && __cplusplus >= 201103L \ + || (defined(_MSC_VER) && _MSC_VER >= 1800) + #define NULL nullptr + #else + #define NULL 0 + #endif +#else + #define NULL ((void *)0) +#endif + +#else // Z7_REDEFINE_NULL + +#if defined(__clang__) && __clang_major__ >= 5 +#pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant" +#endif + +#endif // Z7_REDEFINE_NULL + +// for precompiler: +// #include "MyWindows.h" diff --git a/src/plugins/7zip/7za/cpp/Common/CrcReg.cpp b/src/plugins/7zip/7za/cpp/Common/CrcReg.cpp index 4b662f52..cd5d3da0 100644 --- a/src/plugins/7zip/7za/cpp/Common/CrcReg.cpp +++ b/src/plugins/7zip/7za/cpp/Common/CrcReg.cpp @@ -11,65 +11,44 @@ EXTERN_C_BEGIN -typedef UInt32 (MY_FAST_CALL *CRC_FUNC)(UInt32 v, const void *data, size_t size, const UInt32 *table); - -UInt32 MY_FAST_CALL CrcUpdateT1(UInt32 v, const void *data, size_t size, const UInt32 *table); - -extern CRC_FUNC g_CrcUpdate; -extern CRC_FUNC g_CrcUpdateT8; -extern CRC_FUNC g_CrcUpdateT4; - EXTERN_C_END -class CCrcHasher: - public IHasher, - public ICompressSetCoderProperties, - public CMyUnknownImp -{ +Z7_CLASS_IMP_COM_2( + CCrcHasher + , IHasher + , ICompressSetCoderProperties +) UInt32 _crc; - CRC_FUNC _updateFunc; - Byte mtDummy[1 << 7]; - + Z7_CRC_UPDATE_FUNC _updateFunc; + + Z7_CLASS_NO_COPY(CCrcHasher) + bool SetFunctions(UInt32 tSize); public: - CCrcHasher(): _crc(CRC_INIT_VAL) { SetFunctions(0); } + Byte _mtDummy[1 << 7]; // it's public to eliminate clang warning: unused private field - MY_UNKNOWN_IMP2(IHasher, ICompressSetCoderProperties) - INTERFACE_IHasher(;) - STDMETHOD(SetCoderProperties)(const PROPID *propIDs, const PROPVARIANT *props, UInt32 numProps); + CCrcHasher(): _crc(CRC_INIT_VAL) { SetFunctions(0); } }; bool CCrcHasher::SetFunctions(UInt32 tSize) { - _updateFunc = g_CrcUpdate; - - if (tSize == 1) - _updateFunc = CrcUpdateT1; - else if (tSize == 4) - { - if (g_CrcUpdateT4) - _updateFunc = g_CrcUpdateT4; - else - return false; - } - else if (tSize == 8) + const Z7_CRC_UPDATE_FUNC f = z7_GetFunc_CrcUpdate(tSize); + if (!f) { - if (g_CrcUpdateT8) - _updateFunc = g_CrcUpdateT8; - else - return false; + _updateFunc = CrcUpdate; + return false; } - + _updateFunc = f; return true; } -STDMETHODIMP CCrcHasher::SetCoderProperties(const PROPID *propIDs, const PROPVARIANT *coderProps, UInt32 numProps) +Z7_COM7F_IMF(CCrcHasher::SetCoderProperties(const PROPID *propIDs, const PROPVARIANT *coderProps, UInt32 numProps)) { for (UInt32 i = 0; i < numProps; i++) { - const PROPVARIANT &prop = coderProps[i]; if (propIDs[i] == NCoderPropID::kDefaultProp) { + const PROPVARIANT &prop = coderProps[i]; if (prop.vt != VT_UI4) return E_INVALIDARG; if (!SetFunctions(prop.ulVal)) @@ -79,20 +58,20 @@ STDMETHODIMP CCrcHasher::SetCoderProperties(const PROPID *propIDs, const PROPVAR return S_OK; } -STDMETHODIMP_(void) CCrcHasher::Init() throw() +Z7_COM7F_IMF2(void, CCrcHasher::Init()) { _crc = CRC_INIT_VAL; } -STDMETHODIMP_(void) CCrcHasher::Update(const void *data, UInt32 size) throw() +Z7_COM7F_IMF2(void, CCrcHasher::Update(const void *data, UInt32 size)) { - _crc = _updateFunc(_crc, data, size, g_CrcTable); + _crc = _updateFunc(_crc, data, size); } -STDMETHODIMP_(void) CCrcHasher::Final(Byte *digest) throw() +Z7_COM7F_IMF2(void, CCrcHasher::Final(Byte *digest)) { - UInt32 val = CRC_GET_DIGEST(_crc); - SetUi32(digest, val); + const UInt32 val = CRC_GET_DIGEST(_crc); + SetUi32(digest, val) } REGISTER_HASHER(CCrcHasher, 0x1, "CRC32", 4) diff --git a/src/plugins/7zip/7za/cpp/Common/Defs.h b/src/plugins/7zip/7za/cpp/Common/Defs.h index 1fbd78bd..e302f358 100644 --- a/src/plugins/7zip/7za/cpp/Common/Defs.h +++ b/src/plugins/7zip/7za/cpp/Common/Defs.h @@ -1,7 +1,7 @@ // Common/Defs.h -#ifndef __COMMON_DEFS_H -#define __COMMON_DEFS_H +#ifndef ZIP7_INC_COMMON_DEFS_H +#define ZIP7_INC_COMMON_DEFS_H template inline T MyMin(T a, T b) { return a < b ? a : b; } template inline T MyMax(T a, T b) { return a > b ? a : b; } @@ -10,6 +10,7 @@ template inline int MyCompare(T a, T b) { return a == b ? 0 : (a < b ? -1 : 1); } inline int BoolToInt(bool v) { return (v ? 1 : 0); } +inline unsigned BoolToUInt(bool v) { return (v ? 1u : 0u); } inline bool IntToBool(int v) { return (v != 0); } #endif diff --git a/src/plugins/7zip/7za/cpp/Common/DynLimBuf.cpp b/src/plugins/7zip/7za/cpp/Common/DynLimBuf.cpp index 1d1d99dc..1d92af39 100644 --- a/src/plugins/7zip/7za/cpp/Common/DynLimBuf.cpp +++ b/src/plugins/7zip/7za/cpp/Common/DynLimBuf.cpp @@ -7,7 +7,7 @@ CDynLimBuf::CDynLimBuf(size_t limit) throw() { - _chars = 0; + _chars = NULL; _pos = 0; _size = 0; _sizeLimit = limit; @@ -51,7 +51,7 @@ CDynLimBuf & CDynLimBuf::operator+=(char c) throw() _chars = newBuf; _size = n; } - _chars[_pos++] = c; + _chars[_pos++] = (Byte)c; return *this; } diff --git a/src/plugins/7zip/7za/cpp/Common/DynLimBuf.h b/src/plugins/7zip/7za/cpp/Common/DynLimBuf.h index 93aa144f..af22f076 100644 --- a/src/plugins/7zip/7za/cpp/Common/DynLimBuf.h +++ b/src/plugins/7zip/7za/cpp/Common/DynLimBuf.h @@ -1,7 +1,7 @@ // Common/DynLimBuf.h -#ifndef __COMMON_DYN_LIM_BUF_H -#define __COMMON_DYN_LIM_BUF_H +#ifndef ZIP7_INC_COMMON_DYN_LIM_BUF_H +#define ZIP7_INC_COMMON_DYN_LIM_BUF_H #include @@ -27,6 +27,7 @@ class CDynLimBuf ~CDynLimBuf() { MyFree(_chars); } size_t Len() const { return _pos; } + bool IsError() const { return _error; } void Empty() { _pos = 0; _error = false; } operator const Byte *() const { return _chars; } diff --git a/src/plugins/7zip/7za/cpp/Common/DynamicBuffer.h b/src/plugins/7zip/7za/cpp/Common/DynamicBuffer.h index 44e3df7f..b03d371c 100644 --- a/src/plugins/7zip/7za/cpp/Common/DynamicBuffer.h +++ b/src/plugins/7zip/7za/cpp/Common/DynamicBuffer.h @@ -1,7 +1,11 @@ // Common/DynamicBuffer.h -#ifndef __COMMON_DYNAMIC_BUFFER_H -#define __COMMON_DYNAMIC_BUFFER_H +#ifndef ZIP7_INC_COMMON_DYNAMIC_BUFFER_H +#define ZIP7_INC_COMMON_DYNAMIC_BUFFER_H + +#include + +#include "MyTypes.h" template class CDynamicBuffer { @@ -34,11 +38,19 @@ template class CDynamicBuffer } public: - CDynamicBuffer(): _items(0), _size(0), _pos(0) {} + CDynamicBuffer(): _items(NULL), _size(0), _pos(0) {} // operator T *() { return _items; } operator const T *() const { return _items; } ~CDynamicBuffer() { delete []_items; } + void Free() + { + delete []_items; + _items = NULL; + _size = 0; + _pos = 0; + } + T *GetCurPtrAndGrow(size_t addSize) { size_t rem = _size - _pos; @@ -54,11 +66,11 @@ template class CDynamicBuffer memcpy(GetCurPtrAndGrow(size), data, size * sizeof(T)); } - const size_t GetPos() const { return _pos; } + size_t GetPos() const { return _pos; } // void Empty() { _pos = 0; } }; -typedef CDynamicBuffer CByteDynamicBuffer; +typedef CDynamicBuffer CByteDynamicBuffer; #endif diff --git a/src/plugins/7zip/7za/cpp/Common/IntToString.cpp b/src/plugins/7zip/7za/cpp/Common/IntToString.cpp index ed217c72..0a7cb3b1 100644 --- a/src/plugins/7zip/7za/cpp/Common/IntToString.cpp +++ b/src/plugins/7zip/7za/cpp/Common/IntToString.cpp @@ -2,99 +2,138 @@ #include "StdAfx.h" +#include "../../C/CpuArch.h" + #include "IntToString.h" #define CONVERT_INT_TO_STR(charType, tempSize) \ - unsigned char temp[tempSize]; unsigned i = 0; \ - while (val >= 10) { temp[i++] = (unsigned char)('0' + (unsigned)(val % 10)); val /= 10; } \ - *s++ = (charType)('0' + (unsigned)val); \ - while (i != 0) { i--; *s++ = temp[i]; } \ - *s = 0; + if (val < 10) \ + *s++ = (charType)('0' + (unsigned)val); \ + else { \ + Byte temp[tempSize]; \ + size_t i = 0; \ + do { \ + temp[++i] = (Byte)('0' + (unsigned)(val % 10)); \ + val /= 10; } \ + while (val >= 10); \ + *s++ = (charType)('0' + (unsigned)val); \ + do { *s++ = (charType)temp[i]; } \ + while (--i); \ + } \ + *s = 0; \ + return s; -void ConvertUInt32ToString(UInt32 val, char *s) throw() +char * ConvertUInt32ToString(UInt32 val, char *s) throw() { - CONVERT_INT_TO_STR(char, 16); + CONVERT_INT_TO_STR(char, 16) } -void ConvertUInt64ToString(UInt64 val, char *s) throw() +char * ConvertUInt64ToString(UInt64 val, char *s) throw() { if (val <= (UInt32)0xFFFFFFFF) + return ConvertUInt32ToString((UInt32)val, s); + CONVERT_INT_TO_STR(char, 24) +} + +wchar_t * ConvertUInt32ToString(UInt32 val, wchar_t *s) throw() +{ + CONVERT_INT_TO_STR(wchar_t, 16) +} + +wchar_t * ConvertUInt64ToString(UInt64 val, wchar_t *s) throw() +{ + if (val <= (UInt32)0xFFFFFFFF) + return ConvertUInt32ToString((UInt32)val, s); + CONVERT_INT_TO_STR(wchar_t, 24) +} + +void ConvertInt64ToString(Int64 val, char *s) throw() +{ + if (val < 0) { - ConvertUInt32ToString((UInt32)val, s); - return; + *s++ = '-'; + val = -val; } - CONVERT_INT_TO_STR(char, 24); + ConvertUInt64ToString((UInt64)val, s); } +void ConvertInt64ToString(Int64 val, wchar_t *s) throw() +{ + if (val < 0) + { + *s++ = L'-'; + val = -val; + } + ConvertUInt64ToString((UInt64)val, s); +} + + void ConvertUInt64ToOct(UInt64 val, char *s) throw() { - UInt64 v = val; - unsigned i; - for (i = 1;; i++) { - v >>= 3; - if (v == 0) - break; + UInt64 v = val; + do + s++; + while (v >>= 3); } - s[i] = 0; + *s = 0; do { - unsigned t = (unsigned)(val & 0x7); - val >>= 3; - s[--i] = (char)('0' + t); + const unsigned t = (unsigned)val & 7; + *--s = (char)('0' + t); } - while (i); + while (val >>= 3); } +MY_ALIGN(16) const char k_Hex_Upper[16] = + { '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F' }; +MY_ALIGN(16) const char k_Hex_Lower[16] = + { '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f' }; + void ConvertUInt32ToHex(UInt32 val, char *s) throw() { - UInt32 v = val; - unsigned i; - for (i = 1;; i++) { - v >>= 4; - if (v == 0) - break; + UInt32 v = val; + do + s++; + while (v >>= 4); } - s[i] = 0; + *s = 0; do { - unsigned t = (unsigned)((val & 0xF)); - val >>= 4; - s[--i] = (char)((t < 10) ? ('0' + t) : ('A' + (t - 10))); + const unsigned t = (unsigned)val & 0xF; + *--s = GET_HEX_CHAR_UPPER(t); } - while (i); + while (val >>= 4); } void ConvertUInt64ToHex(UInt64 val, char *s) throw() { - UInt64 v = val; - unsigned i; - for (i = 1;; i++) { - v >>= 4; - if (v == 0) - break; + UInt64 v = val; + do + s++; + while (v >>= 4); } - s[i] = 0; + *s = 0; do { - unsigned t = (unsigned)((val & 0xF)); - val >>= 4; - s[--i] = (char)((t < 10) ? ('0' + t) : ('A' + (t - 10))); + const unsigned t = (unsigned)val & 0xF; + *--s = GET_HEX_CHAR_UPPER(t); } - while (i); + while (val >>= 4); } void ConvertUInt32ToHex8Digits(UInt32 val, char *s) throw() { s[8] = 0; - for (int i = 7; i >= 0; i--) + int i = 7; + do { - unsigned t = val & 0xF; - val >>= 4; - s[i] = (char)(((t < 10) ? ('0' + t) : ('A' + (t - 10)))); + { const unsigned t = (unsigned)val & 0xF; s[i--] = GET_HEX_CHAR_UPPER(t); } + { const unsigned t = (Byte)val >> 4; val >>= 8; s[i--] = GET_HEX_CHAR_UPPER(t); } } + while (i >= 0); } /* @@ -103,44 +142,74 @@ void ConvertUInt32ToHex8Digits(UInt32 val, wchar_t *s) s[8] = 0; for (int i = 7; i >= 0; i--) { - unsigned t = val & 0xF; + const unsigned t = (unsigned)val & 0xF; val >>= 4; - s[i] = (wchar_t)(((t < 10) ? ('0' + t) : ('A' + (t - 10)))); + s[i] = GET_HEX_CHAR(t); } } */ -void ConvertUInt32ToString(UInt32 val, wchar_t *s) throw() -{ - CONVERT_INT_TO_STR(wchar_t, 16); -} -void ConvertUInt64ToString(UInt64 val, wchar_t *s) throw() +MY_ALIGN(16) static const Byte k_Guid_Pos[] = + { 6,4,2,0, 11,9, 16,14, 19,21, 24,26,28,30,32,34 }; + +char *RawLeGuidToString(const Byte *g, char *s) throw() { - if (val <= (UInt32)0xFFFFFFFF) + s[ 8] = '-'; + s[13] = '-'; + s[18] = '-'; + s[23] = '-'; + s[36] = 0; + for (unsigned i = 0; i < 16; i++) { - ConvertUInt32ToString((UInt32)val, s); - return; + char *s2 = s + k_Guid_Pos[i]; + const unsigned v = g[i]; + s2[0] = GET_HEX_CHAR_UPPER(v >> 4); + s2[1] = GET_HEX_CHAR_UPPER(v & 0xF); } - CONVERT_INT_TO_STR(wchar_t, 24); + return s + 36; } -void ConvertInt64ToString(Int64 val, char *s) throw() +char *RawLeGuidToString_Braced(const Byte *g, char *s) throw() { - if (val < 0) + *s++ = '{'; + s = RawLeGuidToString(g, s); + *s++ = '}'; + *s = 0; + return s; +} + + +void ConvertDataToHex_Lower(char *dest, const Byte *src, size_t size) throw() +{ + if (size) { - *s++ = '-'; - val = -val; + const Byte *lim = src + size; + do + { + const unsigned b = *src++; + dest[0] = GET_HEX_CHAR_LOWER(b >> 4); + dest[1] = GET_HEX_CHAR_LOWER(b & 0xF); + dest += 2; + } + while (src != lim); } - ConvertUInt64ToString(val, s); + *dest = 0; } -void ConvertInt64ToString(Int64 val, wchar_t *s) throw() +void ConvertDataToHex_Upper(char *dest, const Byte *src, size_t size) throw() { - if (val < 0) + if (size) { - *s++ = L'-'; - val = -val; + const Byte *lim = src + size; + do + { + const unsigned b = *src++; + dest[0] = GET_HEX_CHAR_UPPER(b >> 4); + dest[1] = GET_HEX_CHAR_UPPER(b & 0xF); + dest += 2; + } + while (src != lim); } - ConvertUInt64ToString(val, s); + *dest = 0; } diff --git a/src/plugins/7zip/7za/cpp/Common/IntToString.h b/src/plugins/7zip/7za/cpp/Common/IntToString.h index 69605ab7..2f096d67 100644 --- a/src/plugins/7zip/7za/cpp/Common/IntToString.h +++ b/src/plugins/7zip/7za/cpp/Common/IntToString.h @@ -1,24 +1,54 @@ // Common/IntToString.h -#ifndef __COMMON_INT_TO_STRING_H -#define __COMMON_INT_TO_STRING_H +#ifndef ZIP7_INC_COMMON_INT_TO_STRING_H +#define ZIP7_INC_COMMON_INT_TO_STRING_H #include "MyTypes.h" -void ConvertUInt32ToString(UInt32 value, char *s) throw(); -void ConvertUInt64ToString(UInt64 value, char *s) throw(); +// return: the pointer to the "terminating" null character after written characters -void ConvertUInt32ToString(UInt32 value, wchar_t *s) throw(); -void ConvertUInt64ToString(UInt64 value, wchar_t *s) throw(); +char * ConvertUInt32ToString(UInt32 value, char *s) throw(); +char * ConvertUInt64ToString(UInt64 value, char *s) throw(); + +wchar_t * ConvertUInt32ToString(UInt32 value, wchar_t *s) throw(); +wchar_t * ConvertUInt64ToString(UInt64 value, wchar_t *s) throw(); +void ConvertInt64ToString(Int64 value, char *s) throw(); +void ConvertInt64ToString(Int64 value, wchar_t *s) throw(); void ConvertUInt64ToOct(UInt64 value, char *s) throw(); +extern const char k_Hex_Upper[16]; +extern const char k_Hex_Lower[16]; + +#define GET_HEX_CHAR_UPPER(t) (k_Hex_Upper[t]) +#define GET_HEX_CHAR_LOWER(t) (k_Hex_Lower[t]) +/* +// #define GET_HEX_CHAR_UPPER(t) ((char)(((t < 10) ? ('0' + t) : ('A' + (t - 10))))) +static inline unsigned GetHex_Lower(unsigned v) +{ + const unsigned v0 = v + '0'; + v += 'a' - 10; + if (v < 'a') + v = v0; + return v; +} +static inline char GetHex_Upper(unsigned v) +{ + return (char)((v < 10) ? ('0' + v) : ('A' + (v - 10))); +} +*/ + + void ConvertUInt32ToHex(UInt32 value, char *s) throw(); void ConvertUInt64ToHex(UInt64 value, char *s) throw(); void ConvertUInt32ToHex8Digits(UInt32 value, char *s) throw(); // void ConvertUInt32ToHex8Digits(UInt32 value, wchar_t *s) throw(); -void ConvertInt64ToString(Int64 value, char *s) throw(); -void ConvertInt64ToString(Int64 value, wchar_t *s) throw(); +// use RawLeGuid only for RAW bytes that contain stored GUID as Little-endian. +char *RawLeGuidToString(const Byte *guid, char *s) throw(); +char *RawLeGuidToString_Braced(const Byte *guid, char *s) throw(); + +void ConvertDataToHex_Lower(char *dest, const Byte *src, size_t size) throw(); +void ConvertDataToHex_Upper(char *dest, const Byte *src, size_t size) throw(); #endif diff --git a/src/plugins/7zip/7za/cpp/Common/Lang.cpp b/src/plugins/7zip/7za/cpp/Common/Lang.cpp index 8c525cf7..b0b4342b 100644 --- a/src/plugins/7zip/7za/cpp/Common/Lang.cpp +++ b/src/plugins/7zip/7za/cpp/Common/Lang.cpp @@ -10,87 +10,97 @@ void CLang::Clear() throw() { - delete []_text; - _text = 0; _ids.Clear(); _offsets.Clear(); + Comments.Clear(); + delete []_text; + _text = NULL; } -static const wchar_t *kLangSignature = L";!@Lang2@!UTF-8!"; +static const char * const kLangSignature = ";!@Lang2@!UTF-8!\n"; bool CLang::OpenFromString(const AString &s2) { - UString s; - if (!ConvertUTF8ToUnicode(s2, s)) + UString su; + if (!ConvertUTF8ToUnicode(s2, su)) return false; - unsigned i = 0; - if (s.IsEmpty()) + if (su.IsEmpty()) return false; - if (s[0] == 0xFEFF) - i++; - - for (const wchar_t *p = kLangSignature;; i++) + const wchar_t *s = su; + const wchar_t *sLim = s + su.Len(); + if (*s == 0xFEFF) + s++; + for (const char *p = kLangSignature;; s++) { - wchar_t c = *p++; + const Byte c = (Byte)(*p++); if (c == 0) break; - if (s[i] != c) + if (*s != c) return false; } - _text = new wchar_t[s.Len() - i + 1]; - wchar_t *text = _text; + wchar_t *text = new wchar_t[(size_t)(sLim - s) + 1]; + _text = text; - Int32 id = -100; - UInt32 pos = 0; + UString comment; + Int32 id = -1024; + unsigned pos = 0; - while (i < s.Len()) + while (s != sLim) { - unsigned start = pos; + const unsigned start = pos; do { - wchar_t c = s[i++]; + wchar_t c = *s++; if (c == '\n') break; if (c == '\\') { - if (i == s.Len()) + if (s == sLim) return false; - c = s[i++]; + c = *s++; switch (c) { case '\n': return false; case 'n': c = '\n'; break; case 't': c = '\t'; break; - case '\\': c = '\\'; break; + case '\\': /* c = '\\'; */ break; default: text[pos++] = L'\\'; break; } } text[pos++] = c; } - while (i < s.Len()); + while (s != sLim); { unsigned j = start; for (; j < pos; j++) - if (text[j] != ' ') + if (text[j] != ' ' && text[j] != '\t') break; if (j == pos) { id++; + pos = start; continue; } } + + // start != pos + text[pos++] = 0; + if (text[start] == ';') { - pos = start; + comment = text + start; + comment.TrimRight(); + if (comment.Len() != 1) + Comments.Add(comment); id++; + pos = start; continue; } - - text[pos++] = 0; + const wchar_t *end; - UInt32 id32 = ConvertStringToUInt32(text + start, &end); + const UInt32 id32 = ConvertStringToUInt32(text + start, &end); if (*end == 0) { if (id32 > ((UInt32)1 << 30) || (Int32)id32 < id) @@ -109,7 +119,7 @@ bool CLang::OpenFromString(const AString &s2) return true; } -bool CLang::Open(CFSTR fileName, const wchar_t *id) +bool CLang::Open(CFSTR fileName, const char *id) { Clear(); NWindows::NFile::NIO::CInFile file; @@ -122,10 +132,10 @@ bool CLang::Open(CFSTR fileName, const wchar_t *id) return false; AString s; - unsigned len = (unsigned)length; + const unsigned len = (unsigned)length; char *p = s.GetBuf(len); - UInt32 processed; - if (!file.Read(p, len, processed)) + size_t processed; + if (!file.ReadFull(p, len, processed)) return false; file.Close(); if (len != processed) @@ -134,7 +144,7 @@ bool CLang::Open(CFSTR fileName, const wchar_t *id) char *p2 = p; for (unsigned i = 0; i < len; i++) { - char c = p[i]; + const char c = p[i]; if (c == 0) break; if (c != 0x0D) @@ -146,7 +156,7 @@ bool CLang::Open(CFSTR fileName, const wchar_t *id) if (OpenFromString(s)) { const wchar_t *name = Get(0); - if (name && wcscmp(name, id) == 0) + if (name && StringsAreEqual_Ascii(name, id)) return true; } @@ -156,8 +166,8 @@ bool CLang::Open(CFSTR fileName, const wchar_t *id) const wchar_t *CLang::Get(UInt32 id) const throw() { - int index = _ids.FindInSorted(id); + const int index = _ids.FindInSorted(id); if (index < 0) return NULL; - return _text + (size_t)_offsets[index]; + return _text + (size_t)_offsets[(unsigned)index]; } diff --git a/src/plugins/7zip/7za/cpp/Common/Lang.h b/src/plugins/7zip/7za/cpp/Common/Lang.h index 22e42574..76d5418e 100644 --- a/src/plugins/7zip/7za/cpp/Common/Lang.h +++ b/src/plugins/7zip/7za/cpp/Common/Lang.h @@ -1,23 +1,30 @@ // Common/Lang.h -#ifndef __COMMON_LANG_H -#define __COMMON_LANG_H +#ifndef ZIP7_INC_COMMON_LANG_H +#define ZIP7_INC_COMMON_LANG_H #include "MyString.h" class CLang { wchar_t *_text; - CRecordVector _ids; - CRecordVector _offsets; bool OpenFromString(const AString &s); public: - CLang(): _text(0) {} + CRecordVector _ids; + CRecordVector _offsets; + UStringVector Comments; + + CLang(): _text(NULL) {} ~CLang() { Clear(); } - bool Open(CFSTR fileName, const wchar_t *id); + bool Open(CFSTR fileName, const char *id); void Clear() throw(); + bool IsEmpty() const { return _ids.IsEmpty(); } const wchar_t *Get(UInt32 id) const throw(); + const wchar_t *Get_by_index(unsigned index) const throw() + { + return _text + (size_t)_offsets[index]; + } }; #endif diff --git a/src/plugins/7zip/7za/cpp/Common/ListFileUtils.cpp b/src/plugins/7zip/7za/cpp/Common/ListFileUtils.cpp index 3bf4ec29..714b275d 100644 --- a/src/plugins/7zip/7za/cpp/Common/ListFileUtils.cpp +++ b/src/plugins/7zip/7za/cpp/Common/ListFileUtils.cpp @@ -4,14 +4,19 @@ #include "../../C/CpuArch.h" -#include "../Windows/FileIO.h" - #include "ListFileUtils.h" #include "MyBuffer.h" #include "StringConvert.h" #include "UTFConvert.h" -static const char kQuoteChar = '\"'; +#include "../Windows/FileIO.h" + +#define CSysInFile NWindows::NFile::NIO::CInFile +#define MY_GET_LAST_ERROR ::GetLastError() + + +#define kQuoteChar '\"' + static void AddName(UStringVector &strings, UString &s) { @@ -25,66 +30,92 @@ static void AddName(UStringVector &strings, UString &s) strings.Add(s); } -bool ReadNamesFromListFile(CFSTR fileName, UStringVector &strings, UINT codePage) + +static bool My_File_Read(CSysInFile &file, void *data, size_t size, DWORD &lastError) +{ + size_t processed; + if (!file.ReadFull(data, size, processed)) + { + lastError = MY_GET_LAST_ERROR; + return false; + } + if (processed != size) + { + lastError = 1; // error: size of listfile was changed + return false; + } + return true; +} + + +bool ReadNamesFromListFile2(CFSTR fileName, UStringVector &strings, UINT codePage, DWORD &lastError) { - NWindows::NFile::NIO::CInFile file; + lastError = 0; + CSysInFile file; if (!file.Open(fileName)) + { + lastError = MY_GET_LAST_ERROR; return false; + } UInt64 fileSize; if (!file.GetLength(fileSize)) + { + lastError = MY_GET_LAST_ERROR; return false; + } if (fileSize >= ((UInt32)1 << 31) - 32) return false; UString u; - if (codePage == MY__CP_UTF16 || codePage == MY__CP_UTF16BE) + if (codePage == Z7_WIN_CP_UTF16 || codePage == Z7_WIN_CP_UTF16BE) { if ((fileSize & 1) != 0) return false; CByteArr buf((size_t)fileSize); - UInt32 processed; - if (!file.Read(buf, (UInt32)fileSize, processed)) - return false; - if (processed != fileSize) + + if (!My_File_Read(file, buf, (size_t)fileSize, lastError)) return false; + file.Close(); - unsigned num = (unsigned)fileSize / 2; - wchar_t *p = u.GetBuf(num); - if (codePage == MY__CP_UTF16) - for (unsigned i = 0; i < num; i++) + const size_t num = (size_t)fileSize / 2; + wchar_t *p = u.GetBuf((unsigned)num); + if (codePage == Z7_WIN_CP_UTF16) + for (size_t i = 0; i < num; i++) { - wchar_t c = GetUi16(buf + i * 2); + const wchar_t c = GetUi16(buf.ConstData() + (size_t)i * 2); if (c == 0) return false; p[i] = c; } else - for (unsigned i = 0; i < num; i++) + for (size_t i = 0; i < num; i++) { - wchar_t c = (wchar_t)GetBe16(buf + i * 2); + const wchar_t c = (wchar_t)GetBe16(buf.ConstData() + (size_t)i * 2); if (c == 0) return false; p[i] = c; } p[num] = 0; - u.ReleaseBuf_SetLen(num); + u.ReleaseBuf_SetLen((unsigned)num); } else { AString s; char *p = s.GetBuf((unsigned)fileSize); - UInt32 processed; - if (!file.Read(p, (UInt32)fileSize, processed)) - return false; - if (processed != fileSize) + + if (!My_File_Read(file, p, (size_t)fileSize, lastError)) return false; + file.Close(); - s.ReleaseBuf_CalcLen((unsigned)processed); - if (s.Len() != processed) + s.ReleaseBuf_CalcLen((unsigned)fileSize); + if (s.Len() != fileSize) return false; // #ifdef CP_UTF8 if (codePage == CP_UTF8) { + // we must check UTF8 here, if convert function doesn't check + if (!CheckUTF8_AString(s)) + return false; if (!ConvertUTF8ToUnicode(s, u)) return false; } @@ -94,7 +125,7 @@ bool ReadNamesFromListFile(CFSTR fileName, UStringVector &strings, UINT codePage } const wchar_t kGoodBOM = 0xFEFF; - const wchar_t kBadBOM = 0xFFFE; + // const wchar_t kBadBOM = 0xFFFE; UString s; unsigned i = 0; @@ -102,9 +133,11 @@ bool ReadNamesFromListFile(CFSTR fileName, UStringVector &strings, UINT codePage for (; i < u.Len(); i++) { wchar_t c = u[i]; + /* if (c == kGoodBOM || c == kBadBOM) return false; - if (c == L'\n' || c == 0xD) + */ + if (c == '\n' || c == 0xD) { AddName(strings, s); s.Empty(); diff --git a/src/plugins/7zip/7za/cpp/Common/ListFileUtils.h b/src/plugins/7zip/7za/cpp/Common/ListFileUtils.h index e8d833fd..d43cc378 100644 --- a/src/plugins/7zip/7za/cpp/Common/ListFileUtils.h +++ b/src/plugins/7zip/7za/cpp/Common/ListFileUtils.h @@ -1,14 +1,18 @@ // Common/ListFileUtils.h -#ifndef __COMMON_LIST_FILE_UTILS_H -#define __COMMON_LIST_FILE_UTILS_H +#ifndef ZIP7_INC_COMMON_LIST_FILE_UTILS_H +#define ZIP7_INC_COMMON_LIST_FILE_UTILS_H #include "MyString.h" #include "MyTypes.h" -#define MY__CP_UTF16 1200 -#define MY__CP_UTF16BE 1201 +#define Z7_WIN_CP_UTF16 1200 +#define Z7_WIN_CP_UTF16BE 1201 -bool ReadNamesFromListFile(CFSTR fileName, UStringVector &strings, UINT codePage = CP_OEMCP); +// bool ReadNamesFromListFile(CFSTR fileName, UStringVector &strings, UINT codePage = CP_OEMCP); + + // = CP_OEMCP +bool ReadNamesFromListFile2(CFSTR fileName, UStringVector &strings, UINT codePage, + DWORD &lastError); #endif diff --git a/src/plugins/7zip/7za/cpp/Common/LzFindPrepare.cpp b/src/plugins/7zip/7za/cpp/Common/LzFindPrepare.cpp new file mode 100644 index 00000000..8845e4a5 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/Common/LzFindPrepare.cpp @@ -0,0 +1,7 @@ +// Sha256Prepare.cpp + +#include "StdAfx.h" + +#include "../../C/LzFind.h" + +static struct CLzFindPrepare { CLzFindPrepare() { LzFindPrepare(); } } g_CLzFindPrepare; diff --git a/src/plugins/7zip/7za/cpp/Common/Md5Reg.cpp b/src/plugins/7zip/7za/cpp/Common/Md5Reg.cpp new file mode 100644 index 00000000..026fd411 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/Common/Md5Reg.cpp @@ -0,0 +1,44 @@ +// Md5Reg.cpp + +#include "StdAfx.h" + +#include "../../C/Md5.h" + +#include "../Common/MyBuffer2.h" +#include "../Common/MyCom.h" + +#include "../7zip/Common/RegisterCodec.h" + +Z7_CLASS_IMP_COM_1( + CMd5Hasher + , IHasher +) + CAlignedBuffer1 _buf; +public: + Byte _mtDummy[1 << 7]; + + CMd5 *Md5() { return (CMd5 *)(void *)(Byte *)_buf; } +public: + CMd5Hasher(): + _buf(sizeof(CMd5)) + { + Md5_Init(Md5()); + } +}; + +Z7_COM7F_IMF2(void, CMd5Hasher::Init()) +{ + Md5_Init(Md5()); +} + +Z7_COM7F_IMF2(void, CMd5Hasher::Update(const void *data, UInt32 size)) +{ + Md5_Update(Md5(), (const Byte *)data, size); +} + +Z7_COM7F_IMF2(void, CMd5Hasher::Final(Byte *digest)) +{ + Md5_Final(Md5(), digest); +} + +REGISTER_HASHER(CMd5Hasher, 0x208, "MD5", MD5_DIGEST_SIZE) diff --git a/src/plugins/7zip/7za/cpp/Common/MyBuffer.h b/src/plugins/7zip/7za/cpp/Common/MyBuffer.h index d0378057..08c10a30 100644 --- a/src/plugins/7zip/7za/cpp/Common/MyBuffer.h +++ b/src/plugins/7zip/7za/cpp/Common/MyBuffer.h @@ -1,9 +1,15 @@ // Common/MyBuffer.h -#ifndef __COMMON_MY_BUFFER_H -#define __COMMON_MY_BUFFER_H +#ifndef ZIP7_INC_COMMON_MY_BUFFER_H +#define ZIP7_INC_COMMON_MY_BUFFER_H + +#include #include "Defs.h" +#include "MyTypes.h" + +/* 7-Zip now uses CBuffer only as CByteBuffer. + So there is no need to use Z7_ARRAY_NEW macro in CBuffer code. */ template class CBuffer { @@ -16,16 +22,23 @@ template class CBuffer if (_items) { delete []_items; - _items = 0; + _items = NULL; } _size = 0; } - CBuffer(): _items(0), _size(0) {}; - CBuffer(size_t size): _items(0), _size(0) { _items = new T[size]; _size = size; } - CBuffer(const CBuffer &buffer): _items(0), _size(0) + CBuffer(): _items(NULL), _size(0) {} + CBuffer(size_t size): _items(NULL), _size(0) { - size_t size = buffer._size; + if (size != 0) + { + _items = new T[size]; + _size = size; + } + } + CBuffer(const CBuffer &buffer): _items(NULL), _size(0) + { + const size_t size = buffer._size; if (size != 0) { _items = new T[size]; @@ -38,6 +51,12 @@ template class CBuffer operator T *() { return _items; } operator const T *() const { return _items; } + const T* ConstData() const { return _items; } + T* NonConstData() const { return _items; } + T* NonConstData() { return _items; } + // const T* Data() const { return _items; } + // T* Data() { return _items; } + size_t Size() const { return _size; } void Alloc(size_t size) @@ -88,6 +107,12 @@ template class CBuffer _size = newSize; } + void Wipe() + { + if (_size != 0) + memset(_items, 0, _size * sizeof(T)); + } + CBuffer& operator=(const CBuffer &buffer) { if (&buffer != this) @@ -119,9 +144,20 @@ bool operator!=(const CBuffer& b1, const CBuffer& b2) } -typedef CBuffer CCharBuffer; +// typedef CBuffer CCharBuffer; // typedef CBuffer CWCharBuffer; -typedef CBuffer CByteBuffer; +typedef CBuffer CByteBuffer; + + +class CByteBuffer_Wipe: public CByteBuffer +{ + Z7_CLASS_NO_COPY(CByteBuffer_Wipe) +public: + // CByteBuffer_Wipe(): CBuffer() {} + CByteBuffer_Wipe(size_t size): CBuffer(size) {} + ~CByteBuffer_Wipe() { Wipe(); } +}; + template class CObjArray @@ -129,31 +165,90 @@ template class CObjArray protected: T *_items; private: - // we disable constructors + // we disable copy CObjArray(const CObjArray &buffer); void operator=(const CObjArray &buffer); public: void Free() { delete []_items; - _items = 0; + _items = NULL; } - CObjArray(size_t size): _items(0) { if (size != 0) _items = new T[size]; } - CObjArray(): _items(0) {}; + CObjArray(size_t size): _items(NULL) + { + if (size != 0) + { + Z7_ARRAY_NEW(_items, T, size) + // _items = new T[size]; + } + } + CObjArray(): _items(NULL) {} ~CObjArray() { delete []_items; } operator T *() { return _items; } operator const T *() const { return _items; } + const T* ConstData() const { return _items; } + T* NonConstData() const { return _items; } + T* NonConstData() { return _items; } + // const T* Data() const { return _items; } + // T* Data() { return _items; } + + void Alloc(size_t newSize) + { + delete []_items; + _items = NULL; + Z7_ARRAY_NEW(_items, T, newSize) + // _items = new T[newSize]; + } +}; + + +/* CSmallObjArray can be used for Byte arrays + or for arrays whose total size in bytes does not exceed size_t ranges. + So there is no need to use Z7_ARRAY_NEW macro in CSmallObjArray code. */ +template class CSmallObjArray +{ +protected: + T *_items; +private: + // we disable copy + CSmallObjArray(const CSmallObjArray &buffer); + void operator=(const CSmallObjArray &buffer); +public: + void Free() + { + delete []_items; + _items = NULL; + } + CSmallObjArray(size_t size): _items(NULL) + { + if (size != 0) + { + // Z7_ARRAY_NEW(_items, T, size) + _items = new T[size]; + } + } + CSmallObjArray(): _items(NULL) {} + ~CSmallObjArray() { delete []_items; } + + operator T *() { return _items; } + operator const T *() const { return _items; } + const T* ConstData() const { return _items; } + T* NonConstData() const { return _items; } + T* NonConstData() { return _items; } + // const T* Data() const { return _items; } + // T* Data() { return _items; } void Alloc(size_t newSize) { delete []_items; - _items = 0; + _items = NULL; + // Z7_ARRAY_NEW(_items, T, newSize) _items = new T[newSize]; } }; -typedef CObjArray CByteArr; +typedef CSmallObjArray CByteArr; typedef CObjArray CBoolArr; typedef CObjArray CIntArr; typedef CObjArray CUIntArr; @@ -164,6 +259,7 @@ template class CObjArray2 T *_items; unsigned _size; + // we disable copy CObjArray2(const CObjArray2 &buffer); void operator=(const CObjArray2 &buffer); public: @@ -171,12 +267,12 @@ template class CObjArray2 void Free() { delete []_items; - _items = 0; + _items = NULL; _size = 0; } - CObjArray2(): _items(0), _size(0) {}; + CObjArray2(): _items(NULL), _size(0) {} /* - CObjArray2(const CObjArray2 &buffer): _items(0), _size(0) + CObjArray2(const CObjArray2 &buffer): _items(NULL), _size(0) { size_t newSize = buffer._size; if (newSize != 0) @@ -191,7 +287,7 @@ template class CObjArray2 } */ /* - CObjArray2(size_t size): _items(0), _size(0) + CObjArray2(size_t size): _items(NULL), _size(0) { if (size != 0) { @@ -216,7 +312,10 @@ template class CObjArray2 return; T *newBuffer = NULL; if (size != 0) - newBuffer = new T[size]; + { + Z7_ARRAY_NEW(newBuffer, T, size) + // newBuffer = new T[size]; + } delete []_items; _items = newBuffer; _size = size; diff --git a/src/plugins/7zip/7za/cpp/Common/MyBuffer2.h b/src/plugins/7zip/7za/cpp/Common/MyBuffer2.h new file mode 100644 index 00000000..1ec8ffb8 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/Common/MyBuffer2.h @@ -0,0 +1,185 @@ +// Common/MyBuffer2.h + +#ifndef ZIP7_INC_COMMON_MY_BUFFER2_H +#define ZIP7_INC_COMMON_MY_BUFFER2_H + +#include "../../C/Alloc.h" + +#include "MyTypes.h" + +class CMidBuffer +{ + Byte *_data; + size_t _size; + + Z7_CLASS_NO_COPY(CMidBuffer) + +public: + CMidBuffer(): _data(NULL), _size(0) {} + ~CMidBuffer() { ::MidFree(_data); } + + void Free() { ::MidFree(_data); _data = NULL; _size = 0; } + + bool IsAllocated() const { return _data != NULL; } + operator Byte *() { return _data; } + operator const Byte *() const { return _data; } + size_t Size() const { return _size; } + + void Alloc(size_t size) + { + if (!_data || size != _size) + { + ::MidFree(_data); + _size = 0; + _data = NULL; + _data = (Byte *)::MidAlloc(size); + if (_data) + _size = size; + } + } + + void AllocAtLeast(size_t size) + { + if (!_data || size > _size) + { + ::MidFree(_data); + const size_t kMinSize = (size_t)1 << 16; + if (size < kMinSize) + size = kMinSize; + _size = 0; + _data = NULL; + _data = (Byte *)::MidAlloc(size); + if (_data) + _size = size; + } + } +}; + + +class CAlignedBuffer1 +{ + Byte *_data; + + Z7_CLASS_NO_COPY(CAlignedBuffer1) + +public: + ~CAlignedBuffer1() + { + z7_AlignedFree(_data); + } + + CAlignedBuffer1(size_t size) + { + _data = NULL; + _data = (Byte *)z7_AlignedAlloc(size); + if (!_data) + throw 1; + } + + operator Byte *() { return _data; } + operator const Byte *() const { return _data; } +}; + + +class CAlignedBuffer +{ + Byte *_data; + size_t _size; + + Z7_CLASS_NO_COPY(CAlignedBuffer) + +public: + CAlignedBuffer(): _data(NULL), _size(0) {} + ~CAlignedBuffer() + { + z7_AlignedFree(_data); + } + + /* + CAlignedBuffer(size_t size): _size(0) + { + _data = NULL; + _data = (Byte *)z7_AlignedAlloc(size); + if (!_data) + throw 1; + _size = size; + } + */ + + void Free() + { + z7_AlignedFree(_data); + _data = NULL; + _size = 0; + } + + bool IsAllocated() const { return _data != NULL; } + operator Byte *() { return _data; } + operator const Byte *() const { return _data; } + size_t Size() const { return _size; } + + void Alloc(size_t size) + { + if (!_data || size != _size) + { + z7_AlignedFree(_data); + _size = 0; + _data = NULL; + _data = (Byte *)z7_AlignedAlloc(size); + if (_data) + _size = size; + } + } + + void AllocAtLeast(size_t size) + { + if (!_data || size > _size) + { + z7_AlignedFree(_data); + _size = 0; + _data = NULL; + _data = (Byte *)z7_AlignedAlloc(size); + if (_data) + _size = size; + } + } + + // (size <= size_max) + void AllocAtLeast_max(size_t size, size_t size_max) + { + if (!_data || size > _size) + { + z7_AlignedFree(_data); + _size = 0; + _data = NULL; + if (size_max < size) size_max = size; // optional check + const size_t delta = size / 2; + size += delta; + if (size < delta || size > size_max) + size = size_max; + _data = (Byte *)z7_AlignedAlloc(size); + if (_data) + _size = size; + } + } +}; + +/* + CMidAlignedBuffer must return aligned pointer. + - in Windows it uses CMidBuffer(): MidAlloc() : VirtualAlloc() + VirtualAlloc(): Memory allocated is automatically initialized to zero. + MidAlloc(0) returns NULL + - in non-Windows systems it uses g_AlignedAlloc. + g_AlignedAlloc::Alloc(size = 0) can return non NULL. +*/ + +typedef +#ifdef _WIN32 + CMidBuffer +#else + CAlignedBuffer +#endif + CMidAlignedBuffer; + + +#endif diff --git a/src/plugins/7zip/7za/cpp/Common/MyCom.h b/src/plugins/7zip/7za/cpp/Common/MyCom.h index 7c725c91..7dc21baa 100644 --- a/src/plugins/7zip/7za/cpp/Common/MyCom.h +++ b/src/plugins/7zip/7za/cpp/Common/MyCom.h @@ -1,14 +1,10 @@ // MyCom.h -#ifndef __MY_COM_H -#define __MY_COM_H +#ifndef ZIP7_INC_MY_COM_H +#define ZIP7_INC_MY_COM_H #include "MyWindows.h" -#include "NewHandler.h" - -#ifndef RINOK -#define RINOK(x) { HRESULT __result_ = (x); if (__result_ != S_OK) return __result_; } -#endif +#include "MyTypes.h" template class CMyComPtr @@ -21,6 +17,7 @@ class CMyComPtr ~CMyComPtr() { if (_p) _p->Release(); } void Release() { if (_p) { _p->Release(); _p = NULL; } } operator T*() const { return (T*)_p; } + T* Interface() const { return (T*)_p; } // T& operator*() const { return *_p; } T** operator&() { return &_p; } T* operator->() const { return _p; } @@ -67,10 +64,121 @@ class CMyComPtr template HRESULT QueryInterface(REFGUID iid, Q** pp) const throw() { + // if (*pp) throw 20220216; // for debug return _p->QueryInterface(iid, (void**)pp); } }; + +template +class CMyComPtr2 +{ + cls* _p; + + CMyComPtr2(const CMyComPtr2& lp); + CMyComPtr2(cls* p); + CMyComPtr2(iface* p); + iface* operator=(const CMyComPtr2& lp); + iface* operator=(cls* p); + iface* operator=(iface* p); +public: + CMyComPtr2(): _p(NULL) {} + ~CMyComPtr2() + { + if (_p) + { + iface *ip = _p; + ip->Release(); + } + } + // void Release() { if (_p) { (iface *)_p->Release(); _p = NULL; } } + cls* operator->() const { return _p; } + cls* ClsPtr() const { return _p; } + operator iface*() const + { + iface *ip = _p; + return ip; + } + iface* Interface() const + { + iface *ip = _p; + return ip; + } + // operator bool() const { return _p != NULL; } + bool IsDefined() const { return _p != NULL; } + void Create_if_Empty() + { + if (!_p) + { + _p = new cls; + iface *ip = _p; + ip->AddRef(); + } + } + iface* Detach() + { + iface *ip = _p; + _p = NULL; + return ip; + } + void SetFromCls(cls *src) + { + if (src) + { + iface *ip = src; + ip->AddRef(); + } + if (_p) + { + iface *ip = _p; + ip->Release(); + } + _p = src; + } +}; + + +template +class CMyComPtr2_Create +{ + cls* _p; + + CMyComPtr2_Create(const CMyComPtr2_Create& lp); + CMyComPtr2_Create(cls* p); + CMyComPtr2_Create(iface* p); + iface* operator=(const CMyComPtr2_Create& lp); + iface* operator=(cls* p); + iface* operator=(iface* p); +public: + CMyComPtr2_Create(): _p(new cls) + { + iface *ip = _p; + ip->AddRef(); + } + ~CMyComPtr2_Create() + { + iface *ip = _p; + ip->Release(); + } + cls* operator->() const { return _p; } + cls* ClsPtr() const { return _p; } + operator iface*() const + { + iface *ip = _p; + return ip; + } + iface* Interface() const + { + iface *ip = _p; + return ip; + } +}; + + +#define Z7_DECL_CMyComPtr_QI_FROM(i, v, unk) \ + CMyComPtr v; (unk)->QueryInterface(IID_ ## i, (void **)&v); + + ////////////////////////////////////////////////////////// inline HRESULT StringToBstr(LPCOLESTR src, BSTR *bstr) @@ -82,7 +190,7 @@ inline HRESULT StringToBstr(LPCOLESTR src, BSTR *bstr) class CMyComBSTR { BSTR m_str; - + Z7_CLASS_NO_COPY(CMyComBSTR) public: CMyComBSTR(): m_str(NULL) {} ~CMyComBSTR() { ::SysFreeString(m_str); } @@ -90,13 +198,23 @@ class CMyComBSTR operator LPCOLESTR() const { return m_str; } // operator bool() const { return m_str != NULL; } // bool operator!() const { return m_str == NULL; } + + void Wipe_and_Free() + { + if (m_str) + { + memset(m_str, 0, ::SysStringLen(m_str) * sizeof(*m_str)); + Empty(); + } + } + private: // operator BSTR() const { return m_str; } CMyComBSTR(LPCOLESTR src) { m_str = ::SysAllocString(src); } // CMyComBSTR(int nSize) { m_str = ::SysAllocStringLen(NULL, nSize); } // CMyComBSTR(int nSize, LPCOLESTR sz) { m_str = ::SysAllocStringLen(sz, nSize); } - CMyComBSTR(const CMyComBSTR& src) { m_str = src.MyCopy(); } + // CMyComBSTR(const CMyComBSTR& src) { m_str = src.MyCopy(); } /* CMyComBSTR(REFGUID src) @@ -108,6 +226,7 @@ class CMyComBSTR } */ + /* CMyComBSTR& operator=(const CMyComBSTR& src) { if (m_str != src.m_str) @@ -118,6 +237,7 @@ class CMyComBSTR } return *this; } + */ CMyComBSTR& operator=(LPCOLESTR src) { @@ -158,105 +278,416 @@ class CMyComBSTR } }; -////////////////////////////////////////////////////////// -class CMyUnknownImp +class CMyComBSTR_Wipe: public CMyComBSTR { + Z7_CLASS_NO_COPY(CMyComBSTR_Wipe) public: - ULONG __m_RefCount; - CMyUnknownImp(): __m_RefCount(0) {} + CMyComBSTR_Wipe(): CMyComBSTR() {} + ~CMyComBSTR_Wipe() { Wipe_and_Free(); } +}; + + + +/* + If CMyUnknownImp doesn't use virtual destructor, the code size is smaller. + But if some class_1 derived from CMyUnknownImp + uses Z7_COM_ADDREF_RELEASE and IUnknown::Release() + and some another class_2 is derived from class_1, + then class_1 must use virtual destructor: + virtual ~class_1(); + In that case, class_1::Release() calls correct destructor of class_2. + We can use virtual ~CMyUnknownImp() to disable warning + "class has virtual functions, but destructor is not virtual". + Also we can use virtual ~IUnknown() {} in MyWindows.h +*/ + +class CMyUnknownImp +{ + Z7_CLASS_NO_COPY(CMyUnknownImp) +protected: + ULONG _m_RefCount; + CMyUnknownImp(): _m_RefCount(0) {} - // virtual ~CMyUnknownImp() {}; + #ifdef _WIN32 + #if defined(__GNUC__) || defined(__clang__) + // virtual ~CMyUnknownImp() {} // to disable GCC/CLANG varnings + #endif + #endif }; -#define MY_QUERYINTERFACE_BEGIN STDMETHOD(QueryInterface) \ -(REFGUID iid, void **outObject) throw() { *outObject = NULL; -#define MY_QUERYINTERFACE_ENTRY(i) else if (iid == IID_ ## i) \ - { *outObject = (void *)(i *)this; } -#define MY_QUERYINTERFACE_ENTRY_UNKNOWN(i) if (iid == IID_IUnknown) \ - { *outObject = (void *)(IUnknown *)(i *)this; } +#define Z7_COM_QI_BEGIN \ + private: STDMETHOD(QueryInterface) (REFGUID iid, void **outObject) throw() Z7_override Z7_final \ + { *outObject = NULL; + +#define Z7_COM_QI_ENTRY(i) \ + else if (iid == IID_ ## i) \ + { i *ti = this; *outObject = ti; } +// { *outObject = (void *)(i *)this; } + +#define Z7_COM_QI_ENTRY_UNKNOWN_0 \ + if (iid == IID_IUnknown) \ + { IUnknown *tu = this; *outObject = tu; } -#define MY_QUERYINTERFACE_BEGIN2(i) MY_QUERYINTERFACE_BEGIN \ - MY_QUERYINTERFACE_ENTRY_UNKNOWN(i) \ - MY_QUERYINTERFACE_ENTRY(i) +#define Z7_COM_QI_ENTRY_UNKNOWN(i) \ + if (iid == IID_IUnknown) \ + { i *ti = this; IUnknown *tu = ti; *outObject = tu; } +// { *outObject = (void *)(IUnknown *)(i *)this; } -#define MY_QUERYINTERFACE_END else return E_NOINTERFACE; ++__m_RefCount; /* AddRef(); */ return S_OK; } +#define Z7_COM_QI_BEGIN2(i) \ + Z7_COM_QI_BEGIN \ + Z7_COM_QI_ENTRY_UNKNOWN(i) \ + Z7_COM_QI_ENTRY(i) -#define MY_ADDREF_RELEASE \ -STDMETHOD_(ULONG, AddRef)() throw() { return ++__m_RefCount; } \ -STDMETHOD_(ULONG, Release)() { if (--__m_RefCount != 0) \ - return __m_RefCount; delete this; return 0; } -#define MY_UNKNOWN_IMP_SPEC(i) \ - MY_QUERYINTERFACE_BEGIN \ +#define Z7_COM_ADDREF_RELEASE_MT \ + private: \ + STDMETHOD_(ULONG, AddRef)() Z7_override Z7_final \ + { return (ULONG)InterlockedIncrement((LONG *)&_m_RefCount); } \ + STDMETHOD_(ULONG, Release)() Z7_override Z7_final \ + { const LONG v = InterlockedDecrement((LONG *)&_m_RefCount); \ + if (v != 0) return (ULONG)v; \ + delete this; return 0; } + +#define Z7_COM_QI_END_MT \ + else return E_NOINTERFACE; \ + InterlockedIncrement((LONG *)&_m_RefCount); /* AddRef(); */ return S_OK; } + +// you can define Z7_COM_USE_ATOMIC, +// if you want to call Release() from different threads (for example, for .NET code) +// #define Z7_COM_USE_ATOMIC + +#if defined(Z7_COM_USE_ATOMIC) && !defined(Z7_ST) + +#ifndef _WIN32 +#if 0 +#include "../../C/Threads.h" +#else +EXTERN_C_BEGIN +LONG InterlockedIncrement(LONG volatile *addend); +LONG InterlockedDecrement(LONG volatile *addend); +EXTERN_C_END +#endif +#endif // _WIN32 + +#define Z7_COM_ADDREF_RELEASE Z7_COM_ADDREF_RELEASE_MT +#define Z7_COM_QI_END Z7_COM_QI_END_MT + +#else // !Z7_COM_USE_ATOMIC + +#define Z7_COM_ADDREF_RELEASE \ + private: \ + STDMETHOD_(ULONG, AddRef)() throw() Z7_override Z7_final \ + { return ++_m_RefCount; } \ + STDMETHOD_(ULONG, Release)() throw() Z7_override Z7_final \ + { if (--_m_RefCount != 0) return _m_RefCount; \ + delete this; return 0; } + +#define Z7_COM_QI_END \ + else return E_NOINTERFACE; \ + ++_m_RefCount; /* AddRef(); */ return S_OK; } + +#endif // !Z7_COM_USE_ATOMIC + + +#define Z7_COM_UNKNOWN_IMP_SPEC(i) \ + Z7_COM_QI_BEGIN \ i \ - MY_QUERYINTERFACE_END \ - MY_ADDREF_RELEASE + Z7_COM_QI_END \ + Z7_COM_ADDREF_RELEASE -#define MY_UNKNOWN_IMP MY_QUERYINTERFACE_BEGIN \ - MY_QUERYINTERFACE_ENTRY_UNKNOWN(IUnknown) \ - MY_QUERYINTERFACE_END \ - MY_ADDREF_RELEASE +#define Z7_COM_UNKNOWN_IMP_0 \ + Z7_COM_QI_BEGIN \ + Z7_COM_QI_ENTRY_UNKNOWN_0 \ + Z7_COM_QI_END \ + Z7_COM_ADDREF_RELEASE -#define MY_UNKNOWN_IMP1(i) MY_UNKNOWN_IMP_SPEC( \ - MY_QUERYINTERFACE_ENTRY_UNKNOWN(i) \ - MY_QUERYINTERFACE_ENTRY(i) \ +#define Z7_COM_UNKNOWN_IMP_1(i) \ + Z7_COM_UNKNOWN_IMP_SPEC( \ + Z7_COM_QI_ENTRY_UNKNOWN(i) \ + Z7_COM_QI_ENTRY(i) \ ) -#define MY_UNKNOWN_IMP2(i1, i2) MY_UNKNOWN_IMP_SPEC( \ - MY_QUERYINTERFACE_ENTRY_UNKNOWN(i1) \ - MY_QUERYINTERFACE_ENTRY(i1) \ - MY_QUERYINTERFACE_ENTRY(i2) \ +#define Z7_COM_UNKNOWN_IMP_2(i1, i2) \ + Z7_COM_UNKNOWN_IMP_SPEC( \ + Z7_COM_QI_ENTRY_UNKNOWN(i1) \ + Z7_COM_QI_ENTRY(i1) \ + Z7_COM_QI_ENTRY(i2) \ ) -#define MY_UNKNOWN_IMP3(i1, i2, i3) MY_UNKNOWN_IMP_SPEC( \ - MY_QUERYINTERFACE_ENTRY_UNKNOWN(i1) \ - MY_QUERYINTERFACE_ENTRY(i1) \ - MY_QUERYINTERFACE_ENTRY(i2) \ - MY_QUERYINTERFACE_ENTRY(i3) \ +#define Z7_COM_UNKNOWN_IMP_3(i1, i2, i3) \ + Z7_COM_UNKNOWN_IMP_SPEC( \ + Z7_COM_QI_ENTRY_UNKNOWN(i1) \ + Z7_COM_QI_ENTRY(i1) \ + Z7_COM_QI_ENTRY(i2) \ + Z7_COM_QI_ENTRY(i3) \ ) -#define MY_UNKNOWN_IMP4(i1, i2, i3, i4) MY_UNKNOWN_IMP_SPEC( \ - MY_QUERYINTERFACE_ENTRY_UNKNOWN(i1) \ - MY_QUERYINTERFACE_ENTRY(i1) \ - MY_QUERYINTERFACE_ENTRY(i2) \ - MY_QUERYINTERFACE_ENTRY(i3) \ - MY_QUERYINTERFACE_ENTRY(i4) \ +#define Z7_COM_UNKNOWN_IMP_4(i1, i2, i3, i4) \ + Z7_COM_UNKNOWN_IMP_SPEC( \ + Z7_COM_QI_ENTRY_UNKNOWN(i1) \ + Z7_COM_QI_ENTRY(i1) \ + Z7_COM_QI_ENTRY(i2) \ + Z7_COM_QI_ENTRY(i3) \ + Z7_COM_QI_ENTRY(i4) \ ) -#define MY_UNKNOWN_IMP5(i1, i2, i3, i4, i5) MY_UNKNOWN_IMP_SPEC( \ - MY_QUERYINTERFACE_ENTRY_UNKNOWN(i1) \ - MY_QUERYINTERFACE_ENTRY(i1) \ - MY_QUERYINTERFACE_ENTRY(i2) \ - MY_QUERYINTERFACE_ENTRY(i3) \ - MY_QUERYINTERFACE_ENTRY(i4) \ - MY_QUERYINTERFACE_ENTRY(i5) \ +#define Z7_COM_UNKNOWN_IMP_5(i1, i2, i3, i4, i5) \ + Z7_COM_UNKNOWN_IMP_SPEC( \ + Z7_COM_QI_ENTRY_UNKNOWN(i1) \ + Z7_COM_QI_ENTRY(i1) \ + Z7_COM_QI_ENTRY(i2) \ + Z7_COM_QI_ENTRY(i3) \ + Z7_COM_QI_ENTRY(i4) \ + Z7_COM_QI_ENTRY(i5) \ ) -#define MY_UNKNOWN_IMP6(i1, i2, i3, i4, i5, i6) MY_UNKNOWN_IMP_SPEC( \ - MY_QUERYINTERFACE_ENTRY_UNKNOWN(i1) \ - MY_QUERYINTERFACE_ENTRY(i1) \ - MY_QUERYINTERFACE_ENTRY(i2) \ - MY_QUERYINTERFACE_ENTRY(i3) \ - MY_QUERYINTERFACE_ENTRY(i4) \ - MY_QUERYINTERFACE_ENTRY(i5) \ - MY_QUERYINTERFACE_ENTRY(i6) \ +#define Z7_COM_UNKNOWN_IMP_6(i1, i2, i3, i4, i5, i6) \ + Z7_COM_UNKNOWN_IMP_SPEC( \ + Z7_COM_QI_ENTRY_UNKNOWN(i1) \ + Z7_COM_QI_ENTRY(i1) \ + Z7_COM_QI_ENTRY(i2) \ + Z7_COM_QI_ENTRY(i3) \ + Z7_COM_QI_ENTRY(i4) \ + Z7_COM_QI_ENTRY(i5) \ + Z7_COM_QI_ENTRY(i6) \ ) -#define MY_UNKNOWN_IMP7(i1, i2, i3, i4, i5, i6, i7) MY_UNKNOWN_IMP_SPEC( \ - MY_QUERYINTERFACE_ENTRY_UNKNOWN(i1) \ - MY_QUERYINTERFACE_ENTRY(i1) \ - MY_QUERYINTERFACE_ENTRY(i2) \ - MY_QUERYINTERFACE_ENTRY(i3) \ - MY_QUERYINTERFACE_ENTRY(i4) \ - MY_QUERYINTERFACE_ENTRY(i5) \ - MY_QUERYINTERFACE_ENTRY(i6) \ - MY_QUERYINTERFACE_ENTRY(i7) \ +#define Z7_COM_UNKNOWN_IMP_7(i1, i2, i3, i4, i5, i6, i7) \ + Z7_COM_UNKNOWN_IMP_SPEC( \ + Z7_COM_QI_ENTRY_UNKNOWN(i1) \ + Z7_COM_QI_ENTRY(i1) \ + Z7_COM_QI_ENTRY(i2) \ + Z7_COM_QI_ENTRY(i3) \ + Z7_COM_QI_ENTRY(i4) \ + Z7_COM_QI_ENTRY(i5) \ + Z7_COM_QI_ENTRY(i6) \ + Z7_COM_QI_ENTRY(i7) \ ) -const HRESULT k_My_HRESULT_WritingWasCut = 0x20000010; +#define Z7_COM_UNKNOWN_IMP_8(i1, i2, i3, i4, i5, i6, i7, i8) \ + Z7_COM_UNKNOWN_IMP_SPEC( \ + Z7_COM_QI_ENTRY_UNKNOWN(i1) \ + Z7_COM_QI_ENTRY(i1) \ + Z7_COM_QI_ENTRY(i2) \ + Z7_COM_QI_ENTRY(i3) \ + Z7_COM_QI_ENTRY(i4) \ + Z7_COM_QI_ENTRY(i5) \ + Z7_COM_QI_ENTRY(i6) \ + Z7_COM_QI_ENTRY(i7) \ + Z7_COM_QI_ENTRY(i8) \ + ) + + +#define Z7_IFACES_IMP_UNK_1(i1) \ + Z7_COM_UNKNOWN_IMP_1(i1) \ + Z7_IFACE_COM7_IMP(i1) \ + +#define Z7_IFACES_IMP_UNK_2(i1, i2) \ + Z7_COM_UNKNOWN_IMP_2(i1, i2) \ + Z7_IFACE_COM7_IMP(i1) \ + Z7_IFACE_COM7_IMP(i2) \ + +#define Z7_IFACES_IMP_UNK_3(i1, i2, i3) \ + Z7_COM_UNKNOWN_IMP_3(i1, i2, i3) \ + Z7_IFACE_COM7_IMP(i1) \ + Z7_IFACE_COM7_IMP(i2) \ + Z7_IFACE_COM7_IMP(i3) \ + +#define Z7_IFACES_IMP_UNK_4(i1, i2, i3, i4) \ + Z7_COM_UNKNOWN_IMP_4(i1, i2, i3, i4) \ + Z7_IFACE_COM7_IMP(i1) \ + Z7_IFACE_COM7_IMP(i2) \ + Z7_IFACE_COM7_IMP(i3) \ + Z7_IFACE_COM7_IMP(i4) \ + +#define Z7_IFACES_IMP_UNK_5(i1, i2, i3, i4, i5) \ + Z7_COM_UNKNOWN_IMP_5(i1, i2, i3, i4, i5) \ + Z7_IFACE_COM7_IMP(i1) \ + Z7_IFACE_COM7_IMP(i2) \ + Z7_IFACE_COM7_IMP(i3) \ + Z7_IFACE_COM7_IMP(i4) \ + Z7_IFACE_COM7_IMP(i5) \ + +#define Z7_IFACES_IMP_UNK_6(i1, i2, i3, i4, i5, i6) \ + Z7_COM_UNKNOWN_IMP_6(i1, i2, i3, i4, i5, i6) \ + Z7_IFACE_COM7_IMP(i1) \ + Z7_IFACE_COM7_IMP(i2) \ + Z7_IFACE_COM7_IMP(i3) \ + Z7_IFACE_COM7_IMP(i4) \ + Z7_IFACE_COM7_IMP(i5) \ + Z7_IFACE_COM7_IMP(i6) \ + +#define Z7_IFACES_IMP_UNK_7(i1, i2, i3, i4, i5, i6, i7) \ + Z7_COM_UNKNOWN_IMP_7(i1, i2, i3, i4, i5, i6, i7) \ + Z7_IFACE_COM7_IMP(i1) \ + Z7_IFACE_COM7_IMP(i2) \ + Z7_IFACE_COM7_IMP(i3) \ + Z7_IFACE_COM7_IMP(i4) \ + Z7_IFACE_COM7_IMP(i5) \ + Z7_IFACE_COM7_IMP(i6) \ + Z7_IFACE_COM7_IMP(i7) \ + + +#define Z7_CLASS_IMP_COM_0(c) \ + Z7_class_final(c) : \ + public IUnknown, \ + public CMyUnknownImp { \ + Z7_COM_UNKNOWN_IMP_0 \ + private: + +#define Z7_CLASS_IMP_COM_1(c, i1) \ + Z7_class_final(c) : \ + public i1, \ + public CMyUnknownImp { \ + Z7_IFACES_IMP_UNK_1(i1) \ + private: + +#define Z7_CLASS_IMP_COM_2(c, i1, i2) \ + Z7_class_final(c) : \ + public i1, \ + public i2, \ + public CMyUnknownImp { \ + Z7_IFACES_IMP_UNK_2(i1, i2) \ + private: + +#define Z7_CLASS_IMP_COM_3(c, i1, i2, i3) \ + Z7_class_final(c) : \ + public i1, \ + public i2, \ + public i3, \ + public CMyUnknownImp { \ + Z7_IFACES_IMP_UNK_3(i1, i2, i3) \ + private: + +#define Z7_CLASS_IMP_COM_4(c, i1, i2, i3, i4) \ + Z7_class_final(c) : \ + public i1, \ + public i2, \ + public i3, \ + public i4, \ + public CMyUnknownImp { \ + Z7_IFACES_IMP_UNK_4(i1, i2, i3, i4) \ + private: + +#define Z7_CLASS_IMP_COM_5(c, i1, i2, i3, i4, i5) \ + Z7_class_final(c) : \ + public i1, \ + public i2, \ + public i3, \ + public i4, \ + public i5, \ + public CMyUnknownImp { \ + Z7_IFACES_IMP_UNK_5(i1, i2, i3, i4, i5) \ + private: + +#define Z7_CLASS_IMP_COM_6(c, i1, i2, i3, i4, i5, i6) \ + Z7_class_final(c) : \ + public i1, \ + public i2, \ + public i3, \ + public i4, \ + public i5, \ + public i6, \ + public CMyUnknownImp { \ + Z7_IFACES_IMP_UNK_6(i1, i2, i3, i4, i5, i6) \ + private: + + +#define Z7_CLASS_IMP_COM_7(c, i1, i2, i3, i4, i5, i6, i7) \ + Z7_class_final(c) : \ + public i1, \ + public i2, \ + public i3, \ + public i4, \ + public i5, \ + public i6, \ + public i7, \ + public CMyUnknownImp { \ + Z7_IFACES_IMP_UNK_7(i1, i2, i3, i4, i5, i6, i7) \ + private: + + +/* +#define Z7_CLASS_IMP_NOQIB_0(c) \ + Z7_class_final(c) : \ + public IUnknown, \ + public CMyUnknownImp { \ + Z7_COM_UNKNOWN_IMP_0 \ + private: +*/ + +#define Z7_CLASS_IMP_NOQIB_1(c, i1) \ + Z7_class_final(c) : \ + public i1, \ + public CMyUnknownImp { \ + Z7_COM_UNKNOWN_IMP_0 \ + Z7_IFACE_COM7_IMP(i1) \ + private: + +#define Z7_CLASS_IMP_NOQIB_2(c, i1, i2) \ + Z7_class_final(c) : \ + public i1, \ + public i2, \ + public CMyUnknownImp { \ + Z7_COM_UNKNOWN_IMP_1(i2) \ + Z7_IFACE_COM7_IMP(i1) \ + Z7_IFACE_COM7_IMP(i2) \ + private: + +#define Z7_CLASS_IMP_NOQIB_3(c, i1, i2, i3) \ + Z7_class_final(c) : \ + public i1, \ + public i2, \ + public i3, \ + public CMyUnknownImp { \ + Z7_COM_UNKNOWN_IMP_2(i2, i3) \ + Z7_IFACE_COM7_IMP(i1) \ + Z7_IFACE_COM7_IMP(i2) \ + Z7_IFACE_COM7_IMP(i3) \ + private: + +#define Z7_CLASS_IMP_NOQIB_4(c, i1, i2, i3, i4) \ + Z7_class_final(c) : \ + public i1, \ + public i2, \ + public i3, \ + public i4, \ + public CMyUnknownImp { \ + Z7_COM_UNKNOWN_IMP_3(i2, i3, i4) \ + Z7_IFACE_COM7_IMP(i1) \ + Z7_IFACE_COM7_IMP(i2) \ + Z7_IFACE_COM7_IMP(i3) \ + Z7_IFACE_COM7_IMP(i4) \ + +/* +#define Z7_CLASS_IMP_NOQIB_5(c, i1, i2, i3, i4, i5) \ + Z7_class_final(c) : \ + public i1, \ + public i2, \ + public i3, \ + public i4, \ + public i5, \ + public CMyUnknownImp { \ + Z7_COM_UNKNOWN_IMP_4(i2, i3, i4, i5) \ + Z7_IFACE_COM7_IMP(i1) \ + Z7_IFACE_COM7_IMP(i2) \ + Z7_IFACE_COM7_IMP(i3) \ + Z7_IFACE_COM7_IMP(i4) \ + Z7_IFACE_COM7_IMP(i5) \ +*/ + + +#define Z7_CLASS_IMP_IInStream(c) \ + class c Z7_final : \ + public IInStream, \ + public CMyUnknownImp { \ + Z7_IFACES_IMP_UNK_2(ISequentialInStream, IInStream) \ + + +#define k_My_HRESULT_WritingWasCut 0x20000010 #endif diff --git a/src/plugins/7zip/7za/cpp/Common/MyException.h b/src/plugins/7zip/7za/cpp/Common/MyException.h index f0ad1115..06fbdea1 100644 --- a/src/plugins/7zip/7za/cpp/Common/MyException.h +++ b/src/plugins/7zip/7za/cpp/Common/MyException.h @@ -1,7 +1,7 @@ // Common/Exception.h -#ifndef __COMMON_EXCEPTION_H -#define __COMMON_EXCEPTION_H +#ifndef ZIP7_INC_COMMON_EXCEPTION_H +#define ZIP7_INC_COMMON_EXCEPTION_H #include "MyWindows.h" diff --git a/src/plugins/7zip/7za/cpp/Common/MyGuidDef.h b/src/plugins/7zip/7za/cpp/Common/MyGuidDef.h index 68745870..3aa5266a 100644 --- a/src/plugins/7zip/7za/cpp/Common/MyGuidDef.h +++ b/src/plugins/7zip/7za/cpp/Common/MyGuidDef.h @@ -1,15 +1,19 @@ // Common/MyGuidDef.h +// #pragma message "Common/MyGuidDef.h" + #ifndef GUID_DEFINED #define GUID_DEFINED +// #pragma message "GUID_DEFINED" + #include "MyTypes.h" typedef struct { UInt32 Data1; UInt16 Data2; UInt16 Data3; - unsigned char Data4[8]; + Byte Data4[8]; } GUID; #ifdef __cplusplus @@ -18,37 +22,42 @@ typedef struct { #define REFGUID const GUID * #endif +// typedef GUID IID; +typedef GUID CLSID; + #define REFCLSID REFGUID #define REFIID REFGUID #ifdef __cplusplus inline int operator==(REFGUID g1, REFGUID g2) { - for (int i = 0; i < (int)sizeof(g1); i++) - if (((unsigned char *)&g1)[i] != ((unsigned char *)&g2)[i]) + for (unsigned i = 0; i < sizeof(g1); i++) + if (((const Byte *)&g1)[i] != ((const Byte *)&g2)[i]) return 0; return 1; } inline int operator!=(REFGUID g1, REFGUID g2) { return !(g1 == g2); } #endif +#endif // GUID_DEFINED + +#ifndef EXTERN_C #ifdef __cplusplus - #define MY_EXTERN_C extern "C" + #define EXTERN_C extern "C" #else - #define MY_EXTERN_C extern + #define EXTERN_C extern #endif - #endif - #ifdef DEFINE_GUID #undef DEFINE_GUID #endif #ifdef INITGUID #define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ - MY_EXTERN_C const GUID name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } } + EXTERN_C const GUID name; \ + EXTERN_C const GUID name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } } #else #define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ - MY_EXTERN_C const GUID name + EXTERN_C const GUID name #endif diff --git a/src/plugins/7zip/7za/cpp/Common/MyInitGuid.h b/src/plugins/7zip/7za/cpp/Common/MyInitGuid.h index 279fba5d..3745c79a 100644 --- a/src/plugins/7zip/7za/cpp/Common/MyInitGuid.h +++ b/src/plugins/7zip/7za/cpp/Common/MyInitGuid.h @@ -1,7 +1,7 @@ // Common/MyInitGuid.h -#ifndef __COMMON_MY_INITGUID_H -#define __COMMON_MY_INITGUID_H +#ifndef ZIP7_INC_COMMON_MY_INITGUID_H +#define ZIP7_INC_COMMON_MY_INITGUID_H /* This file must be included only to one C++ file in project before @@ -19,12 +19,25 @@ Also we need IID_IUnknown that is initialized in some file for linking: Other: we define IID_IUnknown in this file */ +// #include "Common.h" +/* vc6 without sdk needs before , + but it doesn't work in new msvc. + So we include full "MyWindows.h" instead of */ +// #include +#include "MyWindows.h" + #ifdef _WIN32 +#ifdef __clang__ + // #pragma GCC diagnostic ignored "-Wmissing-variable-declarations" +#endif + #ifdef UNDER_CE #include #endif +// for vc6 without sdk we must define INITGUID here +#define INITGUID #include #ifdef UNDER_CE @@ -32,14 +45,13 @@ DEFINE_GUID(IID_IUnknown, 0x00000000, 0x0000, 0x0000, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46); #endif -#else +#else // _WIN32 #define INITGUID #include "MyGuidDef.h" DEFINE_GUID(IID_IUnknown, 0x00000000, 0x0000, 0x0000, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46); -#endif - +#endif // _WIN32 #endif diff --git a/src/plugins/7zip/7za/cpp/Common/MyLinux.h b/src/plugins/7zip/7za/cpp/Common/MyLinux.h index 1a918993..a8454d7f 100644 --- a/src/plugins/7zip/7za/cpp/Common/MyLinux.h +++ b/src/plugins/7zip/7za/cpp/Common/MyLinux.h @@ -1,7 +1,19 @@ // MyLinux.h -#ifndef __MY_LIN_LINUX_H -#define __MY_LIN_LINUX_H +#ifndef ZIP7_INC_COMMON_MY_LINUX_H +#define ZIP7_INC_COMMON_MY_LINUX_H + +// #include "../../C/7zTypes.h" + +#define MY_LIN_DT_UNKNOWN 0 +#define MY_LIN_DT_FIFO 1 +#define MY_LIN_DT_CHR 2 +#define MY_LIN_DT_DIR 4 +#define MY_LIN_DT_BLK 6 +#define MY_LIN_DT_REG 8 +#define MY_LIN_DT_LNK 10 +#define MY_LIN_DT_SOCK 12 +#define MY_LIN_DT_WHT 14 #define MY_LIN_S_IFMT 00170000 #define MY_LIN_S_IFSOCK 0140000 @@ -39,4 +51,25 @@ #define MY_LIN_S_IWOTH 00002 #define MY_LIN_S_IXOTH 00001 +/* +// major/minor encoding for makedev(): MMMMMmmmmmmMMMmm: + +inline UInt32 MY_dev_major(UInt64 dev) +{ + return ((UInt32)(dev >> 8) & (UInt32)0xfff) | ((UInt32)(dev >> 32) & ~(UInt32)0xfff); +} + +inline UInt32 MY_dev_minor(UInt64 dev) +{ + return ((UInt32)(dev) & 0xff) | ((UInt32)(dev >> 12) & ~0xff); +} + +inline UInt64 MY_dev_makedev(UInt32 __major, UInt32 __minor) +{ + return (__minor & 0xff) | ((__major & 0xfff) << 8) + | ((UInt64) (__minor & ~0xff) << 12) + | ((UInt64) (__major & ~0xfff) << 32); +} +*/ + #endif diff --git a/src/plugins/7zip/7za/cpp/Common/MyMap.cpp b/src/plugins/7zip/7za/cpp/Common/MyMap.cpp index 923846ae..d130aec7 100644 --- a/src/plugins/7zip/7za/cpp/Common/MyMap.cpp +++ b/src/plugins/7zip/7za/cpp/Common/MyMap.cpp @@ -76,7 +76,7 @@ bool CMap32::Set(UInt32 key, UInt32 value) unsigned i = kNumBitsMax - 1; for (; GetSubBit(key, i) == GetSubBit(n.Key, i); i--); n.Len = (UInt16)(kNumBitsMax - (1 + i)); - unsigned newBit = GetSubBit(key, i); + const unsigned newBit = GetSubBit(key, i); n.Values[newBit] = value; n.Keys[newBit] = key; return false; @@ -91,7 +91,7 @@ bool CMap32::Set(UInt32 key, UInt32 value) bitPos -= n.Len; if (GetSubBits(key, bitPos, n.Len) != GetSubBits(n.Key, bitPos, n.Len)) { - unsigned i = n.Len - 1; + unsigned i = (unsigned)n.Len - 1; for (; GetSubBit(key, bitPos + i) == GetSubBit(n.Key, bitPos + i); i--); CNode e2(n); @@ -103,11 +103,11 @@ bool CMap32::Set(UInt32 key, UInt32 value) n.IsLeaf[newBit] = 1; n.IsLeaf[1 - newBit] = 0; n.Keys[newBit] = key; - n.Keys[1 - newBit] = Nodes.Size(); + n.Keys[1 - newBit] = (UInt32)Nodes.Size(); Nodes.Add(e2); return false; } - unsigned bit = GetSubBit(key, --bitPos); + const unsigned bit = GetSubBit(key, --bitPos); if (n.IsLeaf[bit]) { @@ -121,7 +121,7 @@ bool CMap32::Set(UInt32 key, UInt32 value) CNode e2; - unsigned newBit = GetSubBit(key, i); + const unsigned newBit = GetSubBit(key, i); e2.Values[newBit] = value; e2.Values[1 - newBit] = n.Values[bit]; e2.IsLeaf[newBit] = e2.IsLeaf[1 - newBit] = 1; @@ -130,7 +130,7 @@ bool CMap32::Set(UInt32 key, UInt32 value) e2.Len = (UInt16)(bitPos - (1 + i)); n.IsLeaf[bit] = 0; - n.Keys[bit] = Nodes.Size(); + n.Keys[bit] = (UInt32)Nodes.Size(); Nodes.Add(e2); return false; diff --git a/src/plugins/7zip/7za/cpp/Common/MyMap.h b/src/plugins/7zip/7za/cpp/Common/MyMap.h index cbcbadd6..9ca55668 100644 --- a/src/plugins/7zip/7za/cpp/Common/MyMap.h +++ b/src/plugins/7zip/7za/cpp/Common/MyMap.h @@ -1,7 +1,7 @@ // MyMap.h -#ifndef __COMMON_MYMAP_H -#define __COMMON_MYMAP_H +#ifndef ZIP7_INC_COMMON_MY_MAP_H +#define ZIP7_INC_COMMON_MY_MAP_H #include "MyTypes.h" #include "MyVector.h" diff --git a/src/plugins/7zip/7za/cpp/Common/MyString.cpp b/src/plugins/7zip/7za/cpp/Common/MyString.cpp index 6e708d33..10e23318 100644 --- a/src/plugins/7zip/7za/cpp/Common/MyString.cpp +++ b/src/plugins/7zip/7za/cpp/Common/MyString.cpp @@ -8,6 +8,8 @@ #include #endif +#include "IntToString.h" + #if !defined(_UNICODE) || !defined(USE_UNICODE_FSTRING) #include "StringConvert.h" #endif @@ -28,6 +30,10 @@ inline const char* MyStringGetNextCharPointer(const char *p) throw() } */ +#define MY_STRING_NEW_char(_size_) MY_STRING_NEW(char, (_size_)) +#define MY_STRING_NEW_wchar_t(_size_) MY_STRING_NEW(wchar_t, (_size_)) + + int FindCharPosInString(const char *s, char c) throw() { for (const char *p = s;; p++) @@ -52,11 +58,22 @@ int FindCharPosInString(const wchar_t *s, wchar_t c) throw() } /* -void MyStringUpper_Ascii(wchar_t *s) +void MyStringUpper_Ascii(char *s) throw() { for (;;) { - wchar_t c = *s; + const char c = *s; + if (c == 0) + return; + *s++ = MyCharUpper_Ascii(c); + } +} + +void MyStringUpper_Ascii(wchar_t *s) throw() +{ + for (;;) + { + const wchar_t c = *s; if (c == 0) return; *s++ = MyCharUpper_Ascii(c); @@ -68,7 +85,7 @@ void MyStringLower_Ascii(char *s) throw() { for (;;) { - char c = *s; + const char c = *s; if (c == 0) return; *s++ = MyCharLower_Ascii(c); @@ -79,7 +96,7 @@ void MyStringLower_Ascii(wchar_t *s) throw() { for (;;) { - wchar_t c = *s; + const wchar_t c = *s; if (c == 0) return; *s++ = MyCharLower_Ascii(c); @@ -173,8 +190,8 @@ bool IsString1PrefixedByString2(const char *s1, const char *s2) throw() { for (;;) { - unsigned char c2 = (unsigned char)*s2++; if (c2 == 0) return true; - unsigned char c1 = (unsigned char)*s1++; if (c1 != c2) return false; + const char c2 = *s2++; if (c2 == 0) return true; + const char c1 = *s1++; if (c1 != c2) return false; } } @@ -182,8 +199,8 @@ bool StringsAreEqualNoCase(const wchar_t *s1, const wchar_t *s2) throw() { for (;;) { - wchar_t c1 = *s1++; - wchar_t c2 = *s2++; + const wchar_t c1 = *s1++; + const wchar_t c2 = *s2++; if (c1 != c2 && MyCharUpper(c1) != MyCharUpper(c2)) return false; if (c1 == 0) return true; } @@ -191,32 +208,17 @@ bool StringsAreEqualNoCase(const wchar_t *s1, const wchar_t *s2) throw() // ---------- ASCII ---------- -bool AString::IsPrefixedBy_Ascii_NoCase(const char *s) const throw() +bool StringsAreEqual_Ascii(const char *u, const char *a) throw() { - const char *s1 = _chars; for (;;) { - char c2 = *s++; - if (c2 == 0) - return true; - char c1 = *s1++; - if (MyCharLower_Ascii(c1) != - MyCharLower_Ascii(c2)) + const char c = *a; + if (c != *u) return false; - } -} - -bool UString::IsPrefixedBy_Ascii_NoCase(const char *s) const throw() -{ - const wchar_t *s1 = _chars; - for (;;) - { - char c2 = *s++; - if (c2 == 0) + if (c == 0) return true; - wchar_t c1 = *s1++; - if (MyCharLower_Ascii(c1) != (unsigned char)MyCharLower_Ascii(c2)) - return false; + a++; + u++; } } @@ -224,7 +226,7 @@ bool StringsAreEqual_Ascii(const wchar_t *u, const char *a) throw() { for (;;) { - unsigned char c = *a; + const unsigned char c = (unsigned char)*a; if (c != *u) return false; if (c == 0) @@ -238,8 +240,8 @@ bool StringsAreEqualNoCase_Ascii(const char *s1, const char *s2) throw() { for (;;) { - char c1 = *s1++; - char c2 = *s2++; + const char c1 = *s1++; + const char c2 = *s2++; if (c1 != c2 && MyCharLower_Ascii(c1) != MyCharLower_Ascii(c2)) return false; if (c1 == 0) @@ -251,8 +253,8 @@ bool StringsAreEqualNoCase_Ascii(const wchar_t *s1, const wchar_t *s2) throw() { for (;;) { - wchar_t c1 = *s1++; - wchar_t c2 = *s2++; + const wchar_t c1 = *s1++; + const wchar_t c2 = *s2++; if (c1 != c2 && MyCharLower_Ascii(c1) != MyCharLower_Ascii(c2)) return false; if (c1 == 0) @@ -264,8 +266,8 @@ bool StringsAreEqualNoCase_Ascii(const wchar_t *s1, const char *s2) throw() { for (;;) { - wchar_t c1 = *s1++; - char c2 = *s2++; + const wchar_t c1 = *s1++; + const char c2 = *s2++; if (c1 != (unsigned char)c2 && (c1 > 0x7F || MyCharLower_Ascii(c1) != (unsigned char)MyCharLower_Ascii(c2))) return false; if (c1 == 0) @@ -277,8 +279,39 @@ bool IsString1PrefixedByString2(const wchar_t *s1, const wchar_t *s2) throw() { for (;;) { - wchar_t c2 = *s2++; if (c2 == 0) return true; - wchar_t c1 = *s1++; if (c1 != c2) return false; + const wchar_t c2 = *s2++; if (c2 == 0) return true; + const wchar_t c1 = *s1++; if (c1 != c2) return false; + } +} + +bool IsString1PrefixedByString2(const wchar_t *s1, const char *s2) throw() +{ + for (;;) + { + const unsigned char c2 = (unsigned char)(*s2++); if (c2 == 0) return true; + const wchar_t c1 = *s1++; if (c1 != c2) return false; + } +} + +bool IsString1PrefixedByString2_NoCase_Ascii(const char *s1, const char *s2) throw() +{ + for (;;) + { + const char c2 = *s2++; if (c2 == 0) return true; + const char c1 = *s1++; + if (c1 != c2 && MyCharLower_Ascii(c1) != MyCharLower_Ascii(c2)) + return false; + } +} + +bool IsString1PrefixedByString2_NoCase_Ascii(const wchar_t *s1, const char *s2) throw() +{ + for (;;) + { + const char c2 = *s2++; if (c2 == 0) return true; + const wchar_t c1 = *s1++; + if (c1 != (unsigned char)c2 && MyCharLower_Ascii(c1) != (unsigned char)MyCharLower_Ascii(c2)) + return false; } } @@ -286,8 +319,8 @@ bool IsString1PrefixedByString2_NoCase(const wchar_t *s1, const wchar_t *s2) thr { for (;;) { - wchar_t c2 = *s2++; if (c2 == 0) return true; - wchar_t c1 = *s1++; + const wchar_t c2 = *s2++; if (c2 == 0) return true; + const wchar_t c1 = *s1++; if (c1 != c2 && MyCharUpper(c1) != MyCharUpper(c2)) return false; } @@ -298,12 +331,12 @@ int MyStringCompareNoCase(const wchar_t *s1, const wchar_t *s2) throw() { for (;;) { - wchar_t c1 = *s1++; - wchar_t c2 = *s2++; + const wchar_t c1 = *s1++; + const wchar_t c2 = *s2++; if (c1 != c2) { - wchar_t u1 = MyCharUpper(c1); - wchar_t u2 = MyCharUpper(c2); + const wchar_t u1 = MyCharUpper(c1); + const wchar_t u2 = MyCharUpper(c2); if (u1 < u2) return -1; if (u1 > u2) return 1; } @@ -339,61 +372,77 @@ void AString::InsertSpace(unsigned &index, unsigned size) MoveItems(index + size, index); } -#define k_Alloc_Len_Limit 0x40000000 +#define k_Alloc_Len_Limit (0x40000000 - 2) +// #define k_Alloc_Len_Limit (((unsigned)1 << (sizeof(unsigned) * 8 - 2)) - 2) void AString::ReAlloc(unsigned newLimit) { - if (newLimit < _len || newLimit >= k_Alloc_Len_Limit) throw 20130220; - // MY_STRING_REALLOC(_chars, char, newLimit + 1, _len + 1); - char *newBuf = MY_STRING_NEW(char, newLimit + 1); - memcpy(newBuf, _chars, (size_t)(_len + 1)); \ - MY_STRING_DELETE(_chars); + // MY_STRING_REALLOC(_chars, char, (size_t)newLimit + 1, (size_t)_len + 1); + char *newBuf = MY_STRING_NEW_char((size_t)newLimit + 1); + memcpy(newBuf, _chars, (size_t)_len + 1); + MY_STRING_DELETE(_chars) _chars = newBuf; _limit = newLimit; } +#define THROW_STRING_ALLOC_EXCEPTION { throw 20130220; } + +#define CHECK_STRING_ALLOC_LEN(len) \ + { if ((len) > k_Alloc_Len_Limit) THROW_STRING_ALLOC_EXCEPTION } + void AString::ReAlloc2(unsigned newLimit) { - if (newLimit >= k_Alloc_Len_Limit) throw 20130220; - // MY_STRING_REALLOC(_chars, char, newLimit + 1, 0); - char *newBuf = MY_STRING_NEW(char, newLimit + 1); + CHECK_STRING_ALLOC_LEN(newLimit) + // MY_STRING_REALLOC(_chars, char, (size_t)newLimit + 1, 0); + char *newBuf = MY_STRING_NEW_char((size_t)newLimit + 1); newBuf[0] = 0; - MY_STRING_DELETE(_chars); + MY_STRING_DELETE(_chars) _chars = newBuf; _limit = newLimit; + _len = 0; } void AString::SetStartLen(unsigned len) { - _chars = 0; - _chars = MY_STRING_NEW(char, len + 1); + _chars = NULL; + _chars = MY_STRING_NEW_char((size_t)len + 1); _len = len; _limit = len; } +Z7_NO_INLINE void AString::Grow_1() { unsigned next = _len; next += next / 2; next += 16; next &= ~(unsigned)15; - ReAlloc(next - 1); + next--; + if (next < _len || next > k_Alloc_Len_Limit) + next = k_Alloc_Len_Limit; + if (next <= _len) + THROW_STRING_ALLOC_EXCEPTION + ReAlloc(next); + // Grow(1); } void AString::Grow(unsigned n) { - unsigned freeSize = _limit - _len; + const unsigned freeSize = _limit - _len; if (n <= freeSize) return; - unsigned next = _len + n; next += next / 2; next += 16; next &= ~(unsigned)15; - ReAlloc(next - 1); + next--; + if (next < _len || next > k_Alloc_Len_Limit) + next = k_Alloc_Len_Limit; + if (next <= _len || next - _len < n) + THROW_STRING_ALLOC_EXCEPTION + ReAlloc(next); } -/* AString::AString(unsigned num, const char *s) { unsigned len = MyStringLen(s); @@ -403,7 +452,6 @@ AString::AString(unsigned num, const char *s) memcpy(_chars, s, num); _chars[num] = 0; } -*/ AString::AString(unsigned num, const AString &s) { @@ -421,7 +469,7 @@ AString::AString(const AString &s, char c) unsigned len = s.Len(); memcpy(chars, s, len); chars[len] = c; - chars[len + 1] = 0; + chars[(size_t)len + 1] = 0; } AString::AString(const char *s1, unsigned num1, const char *s2, unsigned num2) @@ -436,20 +484,23 @@ AString operator+(const AString &s1, const AString &s2) { return AString(s1, s1. AString operator+(const AString &s1, const char *s2) { return AString(s1, s1.Len(), s2, MyStringLen(s2)); } AString operator+(const char *s1, const AString &s2) { return AString(s1, MyStringLen(s1), s2, s2.Len()); } +static const unsigned kStartStringCapacity = 4; + AString::AString() { - _chars = 0; - _chars = MY_STRING_NEW(char, 4); + _chars = NULL; + _chars = MY_STRING_NEW_char(kStartStringCapacity); _len = 0; - _limit = 4 - 1; + _limit = kStartStringCapacity - 1; _chars[0] = 0; } AString::AString(char c) { SetStartLen(1); - _chars[0] = c; - _chars[1] = 0; + char *chars = _chars; + chars[0] = c; + chars[1] = 0; } AString::AString(const char *s) @@ -468,14 +519,15 @@ AString &AString::operator=(char c) { if (1 > _limit) { - char *newBuf = MY_STRING_NEW(char, 1 + 1); - MY_STRING_DELETE(_chars); + char *newBuf = MY_STRING_NEW_char(1 + 1); + MY_STRING_DELETE(_chars) _chars = newBuf; _limit = 1; } _len = 1; - _chars[0] = c; - _chars[1] = 0; + char *chars = _chars; + chars[0] = c; + chars[1] = 0; return *this; } @@ -484,8 +536,8 @@ AString &AString::operator=(const char *s) unsigned len = MyStringLen(s); if (len > _limit) { - char *newBuf = MY_STRING_NEW(char, len + 1); - MY_STRING_DELETE(_chars); + char *newBuf = MY_STRING_NEW_char((size_t)len + 1); + MY_STRING_DELETE(_chars) _chars = newBuf; _limit = len; } @@ -501,8 +553,8 @@ AString &AString::operator=(const AString &s) unsigned len = s._len; if (len > _limit) { - char *newBuf = MY_STRING_NEW(char, len + 1); - MY_STRING_DELETE(_chars); + char *newBuf = MY_STRING_NEW_char((size_t)len + 1); + MY_STRING_DELETE(_chars) _chars = newBuf; _limit = len; } @@ -526,8 +578,8 @@ void AString::SetFromWStr_if_Ascii(const wchar_t *s) } if (len > _limit) { - char *newBuf = MY_STRING_NEW(char, len + 1); - MY_STRING_DELETE(_chars); + char *newBuf = MY_STRING_NEW_char((size_t)len + 1); + MY_STRING_DELETE(_chars) _chars = newBuf; _limit = len; } @@ -550,8 +602,8 @@ void AString::SetFromBstr_if_Ascii(BSTR s) } if (len > _limit) { - char *newBuf = MY_STRING_NEW(char, len + 1); - MY_STRING_DELETE(_chars); + char *newBuf = MY_STRING_NEW_char((size_t)len + 1); + MY_STRING_DELETE(_chars) _chars = newBuf; _limit = len; } @@ -564,9 +616,14 @@ void AString::SetFromBstr_if_Ascii(BSTR s) } */ +void AString::Add_Char(char c) { operator+=(c); } void AString::Add_Space() { operator+=(' '); } void AString::Add_Space_if_NotEmpty() { if (!IsEmpty()) Add_Space(); } void AString::Add_LF() { operator+=('\n'); } +void AString::Add_Slash() { operator+=('/'); } +void AString::Add_Dot() { operator+=('.'); } +void AString::Add_Minus() { operator+=('-'); } +void AString::Add_Colon() { operator+=(':'); } AString &AString::operator+=(const char *s) { @@ -577,6 +634,12 @@ AString &AString::operator+=(const char *s) return *this; } +void AString::Add_OptSpaced(const char *s) +{ + Add_Space_if_NotEmpty(); + (*this) += s; +} + AString &AString::operator+=(const AString &s) { Grow(s._len); @@ -585,12 +648,37 @@ AString &AString::operator+=(const AString &s) return *this; } +void AString::Add_UInt32(UInt32 v) +{ + Grow(10); + _len = (unsigned)(ConvertUInt32ToString(v, _chars + _len) - _chars); +} + +void UString::Add_UInt64(UInt64 v) +{ + Grow(20); + _len = (unsigned)(ConvertUInt64ToString(v, _chars + _len) - _chars); +} + +void AString::AddFrom(const char *s, unsigned len) // no check +{ + if (len != 0) + { + Grow(len); + memcpy(_chars + _len, s, len); + len += _len; + _chars[len] = 0; + _len = len; + } +} + void AString::SetFrom(const char *s, unsigned len) // no check { if (len > _limit) { - char *newBuf = MY_STRING_NEW(char, len + 1); - MY_STRING_DELETE(_chars); + CHECK_STRING_ALLOC_LEN(len) + char *newBuf = MY_STRING_NEW_char((size_t)len + 1); + MY_STRING_DELETE(_chars) _chars = newBuf; _limit = len; } @@ -600,6 +688,12 @@ void AString::SetFrom(const char *s, unsigned len) // no check _len = len; } +void AString::SetFrom_Chars_SizeT(const char *s, size_t len) +{ + CHECK_STRING_ALLOC_LEN(len) + SetFrom(s, (unsigned)len); +} + void AString::SetFrom_CalcLen(const char *s, unsigned len) // no check { unsigned i; @@ -662,7 +756,7 @@ int AString::ReverseFind_PathSepar() const throw() const char *p = _chars + _len - 1; for (;;) { - char c = *p; + const char c = *p; if (IS_PATH_SEPAR(c)) return (int)(p - _chars); if (p == _chars) @@ -694,7 +788,7 @@ void AString::TrimRight() throw() unsigned i; for (i = _len; i != 0; i--) { - char c = p[i - 1]; + char c = p[(size_t)i - 1]; if (c != ' ' && c != '\n' && c != '\t') break; } @@ -780,12 +874,13 @@ void AString::Replace(char oldChar, char newChar) throw() return; // 0; // unsigned number = 0; int pos = 0; + char *chars = _chars; while ((unsigned)pos < _len) { - pos = Find(oldChar, pos); + pos = Find(oldChar, (unsigned)pos); if (pos < 0) break; - _chars[(unsigned)pos] = newChar; + chars[(unsigned)pos] = newChar; pos++; // number++; } @@ -798,17 +893,17 @@ void AString::Replace(const AString &oldString, const AString &newString) return; // 0; if (oldString == newString) return; // 0; - unsigned oldLen = oldString.Len(); - unsigned newLen = newString.Len(); + const unsigned oldLen = oldString.Len(); + const unsigned newLen = newString.Len(); // unsigned number = 0; int pos = 0; while ((unsigned)pos < _len) { - pos = Find(oldString, pos); + pos = Find(oldString, (unsigned)pos); if (pos < 0) break; - Delete(pos, oldLen); - Insert(pos, newString); + Delete((unsigned)pos, oldLen); + Insert((unsigned)pos, newString); pos += newLen; // number++; } @@ -893,53 +988,63 @@ void UString::InsertSpace(unsigned index, unsigned size) void UString::ReAlloc(unsigned newLimit) { - if (newLimit < _len || newLimit >= k_Alloc_Len_Limit) throw 20130221; - // MY_STRING_REALLOC(_chars, wchar_t, newLimit + 1, _len + 1); - wchar_t *newBuf = MY_STRING_NEW(wchar_t, newLimit + 1); + // MY_STRING_REALLOC(_chars, wchar_t, (size_t)newLimit + 1, (size_t)_len + 1); + wchar_t *newBuf = MY_STRING_NEW_wchar_t((size_t)newLimit + 1); wmemcpy(newBuf, _chars, _len + 1); - MY_STRING_DELETE(_chars); + MY_STRING_DELETE(_chars) _chars = newBuf; _limit = newLimit; } void UString::ReAlloc2(unsigned newLimit) { - if (newLimit >= k_Alloc_Len_Limit) throw 20130221; + CHECK_STRING_ALLOC_LEN(newLimit) // MY_STRING_REALLOC(_chars, wchar_t, newLimit + 1, 0); - wchar_t *newBuf = MY_STRING_NEW(wchar_t, newLimit + 1); + wchar_t *newBuf = MY_STRING_NEW_wchar_t((size_t)newLimit + 1); newBuf[0] = 0; - MY_STRING_DELETE(_chars); + MY_STRING_DELETE(_chars) _chars = newBuf; _limit = newLimit; + _len = 0; } void UString::SetStartLen(unsigned len) { - _chars = 0; - _chars = MY_STRING_NEW(wchar_t, len + 1); + _chars = NULL; + _chars = MY_STRING_NEW_wchar_t((size_t)len + 1); _len = len; _limit = len; } +Z7_NO_INLINE void UString::Grow_1() { unsigned next = _len; next += next / 2; next += 16; next &= ~(unsigned)15; - ReAlloc(next - 1); + next--; + if (next < _len || next > k_Alloc_Len_Limit) + next = k_Alloc_Len_Limit; + if (next <= _len) + THROW_STRING_ALLOC_EXCEPTION + ReAlloc(next); } void UString::Grow(unsigned n) { - unsigned freeSize = _limit - _len; + const unsigned freeSize = _limit - _len; if (n <= freeSize) return; - unsigned next = _len + n; next += next / 2; next += 16; next &= ~(unsigned)15; + next--; + if (next < _len || next > k_Alloc_Len_Limit) + next = k_Alloc_Len_Limit; + if (next <= _len || next - _len < n) + THROW_STRING_ALLOC_EXCEPTION ReAlloc(next - 1); } @@ -971,7 +1076,7 @@ UString::UString(const UString &s, wchar_t c) unsigned len = s.Len(); wmemcpy(chars, s, len); chars[len] = c; - chars[len + 1] = 0; + chars[(size_t)len + 1] = 0; } UString::UString(const wchar_t *s1, unsigned num1, const wchar_t *s2, unsigned num2) @@ -988,27 +1093,57 @@ UString operator+(const wchar_t *s1, const UString &s2) { return UString(s1, MyS UString::UString() { - _chars = 0; - _chars = MY_STRING_NEW(wchar_t, 4); + _chars = NULL; + _chars = MY_STRING_NEW_wchar_t(kStartStringCapacity); _len = 0; - _limit = 4 - 1; + _limit = kStartStringCapacity - 1; _chars[0] = 0; } UString::UString(wchar_t c) { SetStartLen(1); - _chars[0] = c; - _chars[1] = 0; + wchar_t *chars = _chars; + chars[0] = c; + chars[1] = 0; +} + +UString::UString(char c) +{ + SetStartLen(1); + wchar_t *chars = _chars; + chars[0] = (unsigned char)c; + chars[1] = 0; } UString::UString(const wchar_t *s) { - unsigned len = MyStringLen(s); + const unsigned len = MyStringLen(s); SetStartLen(len); wmemcpy(_chars, s, len + 1); } +UString::UString(const char *s) +{ + const unsigned len = MyStringLen(s); + SetStartLen(len); + wchar_t *chars = _chars; + for (unsigned i = 0; i < len; i++) + chars[i] = (unsigned char)s[i]; + chars[len] = 0; +} + +UString::UString(const AString &s) +{ + const unsigned len = s.Len(); + SetStartLen(len); + wchar_t *chars = _chars; + const char *s2 = s.Ptr(); + for (unsigned i = 0; i < len; i++) + chars[i] = (unsigned char)s2[i]; + chars[len] = 0; +} + UString::UString(const UString &s) { SetStartLen(s._len); @@ -1019,14 +1154,15 @@ UString &UString::operator=(wchar_t c) { if (1 > _limit) { - wchar_t *newBuf = MY_STRING_NEW(wchar_t, 1 + 1); - MY_STRING_DELETE(_chars); + wchar_t *newBuf = MY_STRING_NEW_wchar_t(1 + 1); + MY_STRING_DELETE(_chars) _chars = newBuf; _limit = 1; } _len = 1; - _chars[0] = c; - _chars[1] = 0; + wchar_t *chars = _chars; + chars[0] = c; + chars[1] = 0; return *this; } @@ -1035,8 +1171,8 @@ UString &UString::operator=(const wchar_t *s) unsigned len = MyStringLen(s); if (len > _limit) { - wchar_t *newBuf = MY_STRING_NEW(wchar_t, len + 1); - MY_STRING_DELETE(_chars); + wchar_t *newBuf = MY_STRING_NEW_wchar_t((size_t)len + 1); + MY_STRING_DELETE(_chars) _chars = newBuf; _limit = len; } @@ -1052,8 +1188,8 @@ UString &UString::operator=(const UString &s) unsigned len = s._len; if (len > _limit) { - wchar_t *newBuf = MY_STRING_NEW(wchar_t, len + 1); - MY_STRING_DELETE(_chars); + wchar_t *newBuf = MY_STRING_NEW_wchar_t((size_t)len + 1); + MY_STRING_DELETE(_chars) _chars = newBuf; _limit = len; } @@ -1062,74 +1198,91 @@ UString &UString::operator=(const UString &s) return *this; } -void UString::SetFromBstr(BSTR s) +void UString::SetFrom(const wchar_t *s, unsigned len) // no check { - unsigned len = ::SysStringLen(s); if (len > _limit) { - wchar_t *newBuf = MY_STRING_NEW(wchar_t, len + 1); - MY_STRING_DELETE(_chars); + CHECK_STRING_ALLOC_LEN(len) + wchar_t *newBuf = MY_STRING_NEW_wchar_t((size_t)len + 1); + MY_STRING_DELETE(_chars) _chars = newBuf; _limit = len; } - _len = len; - // if (s) - wmemcpy(_chars, s, len + 1); -} - -void UString::Add_Space() { operator+=(L' '); } -void UString::Add_Space_if_NotEmpty() { if (!IsEmpty()) Add_Space(); } - -void UString::Add_LF() -{ - if (_limit == _len) - Grow_1(); - unsigned len = _len; - wchar_t *chars = _chars; - chars[len++] = L'\n'; - chars[len] = 0; + if (len != 0) + wmemcpy(_chars, s, len); + _chars[len] = 0; _len = len; } -UString &UString::operator+=(const wchar_t *s) +void UString::SetFromBstr(LPCOLESTR s) { - unsigned len = MyStringLen(s); - Grow(len); - wmemcpy(_chars + _len, s, len + 1); - _len += len; - return *this; -} + unsigned len = ::SysStringLen((BSTR)(void *)(s)); -UString &UString::operator+=(const UString &s) -{ - Grow(s._len); - wmemcpy(_chars + _len, s._chars, s._len + 1); - _len += s._len; - return *this; -} + /* + #if WCHAR_MAX > 0xffff + size_t num_wchars = 0; + for (size_t i = 0; i < len;) + { + wchar_t c = s[i++]; + if (c >= 0xd800 && c < 0xdc00 && i + 1 != len) + { + wchar_t c2 = s[i]; + if (c2 >= 0xdc00 && c2 < 0xe000) + { + c = 0x10000 + ((c & 0x3ff) << 10) + (c2 & 0x3ff); + i++; + } + } + num_wchars++; + } + len = num_wchars; + #endif + */ -void UString::SetFrom(const wchar_t *s, unsigned len) // no check -{ if (len > _limit) { - wchar_t *newBuf = MY_STRING_NEW(wchar_t, len + 1); - MY_STRING_DELETE(_chars); + wchar_t *newBuf = MY_STRING_NEW_wchar_t((size_t)len + 1); + MY_STRING_DELETE(_chars) _chars = newBuf; _limit = len; } - if (len != 0) - wmemcpy(_chars, s, len); - _chars[len] = 0; _len = len; + + /* + #if WCHAR_MAX > 0xffff + + wchar_t *chars = _chars; + for (size_t i = 0; i <= len; i++) + { + wchar_t c = *s++; + if (c >= 0xd800 && c < 0xdc00 && i + 1 != len) + { + wchar_t c2 = *s; + if (c2 >= 0xdc00 && c2 < 0xe000) + { + s++; + c = 0x10000 + ((c & 0x3ff) << 10) + (c2 & 0x3ff); + } + } + chars[i] = c; + } + + #else + */ + + // if (s) + wmemcpy(_chars, s, len + 1); + + // #endif } -void UString::SetFromAscii(const char *s) +UString &UString::operator=(const char *s) { unsigned len = MyStringLen(s); if (len > _limit) { - wchar_t *newBuf = MY_STRING_NEW(wchar_t, len + 1); - MY_STRING_DELETE(_chars); + wchar_t *newBuf = MY_STRING_NEW_wchar_t((size_t)len + 1); + MY_STRING_DELETE(_chars) _chars = newBuf; _limit = len; } @@ -1138,9 +1291,36 @@ void UString::SetFromAscii(const char *s) chars[i] = (unsigned char)s[i]; chars[len] = 0; _len = len; + return *this; +} + +void UString::Add_Char(char c) { operator+=((wchar_t)(unsigned char)c); } +// void UString::Add_WChar(wchar_t c) { operator+=(c); } +void UString::Add_Dot() { operator+=(L'.'); } +void UString::Add_Space() { operator+=(L' '); } +void UString::Add_Minus() { operator+=(L'-'); } +void UString::Add_Colon() { operator+=(L':'); } +void UString::Add_LF() { operator+=(L'\n'); } +void UString::Add_Space_if_NotEmpty() { if (!IsEmpty()) Add_Space(); } + +UString &UString::operator+=(const wchar_t *s) +{ + unsigned len = MyStringLen(s); + Grow(len); + wmemcpy(_chars + _len, s, len + 1); + _len += len; + return *this; } -void UString::AddAscii(const char *s) +UString &UString::operator+=(const UString &s) +{ + Grow(s._len); + wmemcpy(_chars + _len, s._chars, s._len + 1); + _len += s._len; + return *this; +} + +UString &UString::operator+=(const char *s) { unsigned len = MyStringLen(s); Grow(len); @@ -1149,9 +1329,22 @@ void UString::AddAscii(const char *s) chars[i] = (unsigned char)s[i]; chars[len] = 0; _len += len; + return *this; } +void UString::Add_UInt32(UInt32 v) +{ + Grow(10); + _len = (unsigned)(ConvertUInt32ToString(v, _chars + _len) - _chars); +} + +void AString::Add_UInt64(UInt64 v) +{ + Grow(20); + _len = (unsigned)(ConvertUInt64ToString(v, _chars + _len) - _chars); +} + int UString::Find(const wchar_t *s, unsigned startIndex) const throw() { @@ -1188,31 +1381,26 @@ int UString::ReverseFind(wchar_t c) const throw() { if (_len == 0) return -1; - const wchar_t *p = _chars + _len - 1; - for (;;) + const wchar_t *p = _chars + _len; + do { - if (*p == c) + if (*(--p) == c) return (int)(p - _chars); - if (p == _chars) - return -1; - p--; } + while (p != _chars); + return -1; } int UString::ReverseFind_PathSepar() const throw() { - if (_len == 0) - return -1; - const wchar_t *p = _chars + _len - 1; - for (;;) + const wchar_t *p = _chars + _len; + while (p != _chars) { - wchar_t c = *p; + const wchar_t c = *(--p); if (IS_PATH_SEPAR(c)) return (int)(p - _chars); - if (p == _chars) - return -1; - p--; } + return -1; } void UString::TrimLeft() throw() @@ -1238,7 +1426,7 @@ void UString::TrimRight() throw() unsigned i; for (i = _len; i != 0; i--) { - wchar_t c = p[i - 1]; + wchar_t c = p[(size_t)i - 1]; if (c != ' ' && c != '\n' && c != '\t') break; } @@ -1259,7 +1447,7 @@ void UString::InsertAtFront(wchar_t c) } /* -void UString::Insert(unsigned index, wchar_t c) +void UString::Insert_wchar_t(unsigned index, wchar_t c) { InsertSpace(index, 1); _chars[index] = c; @@ -1324,12 +1512,13 @@ void UString::Replace(wchar_t oldChar, wchar_t newChar) throw() return; // 0; // unsigned number = 0; int pos = 0; + wchar_t *chars = _chars; while ((unsigned)pos < _len) { - pos = Find(oldChar, pos); + pos = Find(oldChar, (unsigned)pos); if (pos < 0) break; - _chars[(unsigned)pos] = newChar; + chars[(unsigned)pos] = newChar; pos++; // number++; } @@ -1348,11 +1537,11 @@ void UString::Replace(const UString &oldString, const UString &newString) int pos = 0; while ((unsigned)pos < _len) { - pos = Find(oldString, pos); + pos = Find(oldString, (unsigned)pos); if (pos < 0) break; - Delete(pos, oldLen); - Insert(pos, newString); + Delete((unsigned)pos, oldLen); + Insert((unsigned)pos, newString); pos += newLen; // number++; } @@ -1390,15 +1579,24 @@ void UString::DeleteFrontal(unsigned num) throw() void UString2::ReAlloc2(unsigned newLimit) { - if (newLimit >= k_Alloc_Len_Limit) throw 20130221; + // wrong (_len) is allowed after this function + CHECK_STRING_ALLOC_LEN(newLimit) // MY_STRING_REALLOC(_chars, wchar_t, newLimit + 1, 0); - _chars = MY_STRING_NEW(wchar_t, newLimit + 1); + if (_chars) + { + MY_STRING_DELETE(_chars) + _chars = NULL; + // _len = 0; + } + _chars = MY_STRING_NEW_wchar_t((size_t)newLimit + 1); + _chars[0] = 0; + // _len = newLimit; } void UString2::SetStartLen(unsigned len) { - _chars = 0; - _chars = MY_STRING_NEW(wchar_t, len + 1); + _chars = NULL; + _chars = MY_STRING_NEW_wchar_t((size_t)len + 1); _len = len; } @@ -1407,14 +1605,15 @@ void UString2::SetStartLen(unsigned len) UString2::UString2(wchar_t c) { SetStartLen(1); - _chars[0] = c; - _chars[1] = 0; + wchar_t *chars = _chars; + chars[0] = c; + chars[1] = 0; } */ UString2::UString2(const wchar_t *s) { - unsigned len = MyStringLen(s); + const unsigned len = MyStringLen(s); SetStartLen(len); wmemcpy(_chars, s, len + 1); } @@ -1433,14 +1632,15 @@ UString2 &UString2::operator=(wchar_t c) { if (1 > _len) { - wchar_t *newBuf = MY_STRING_NEW(wchar_t, 1 + 1); + wchar_t *newBuf = MY_STRING_NEW_wchar_t(1 + 1); if (_chars) - MY_STRING_DELETE(_chars); + MY_STRING_DELETE(_chars) _chars = newBuf; } _len = 1; - _chars[0] = c; - _chars[1] = 0; + wchar_t *chars = _chars; + chars[0] = c; + chars[1] = 0; return *this; } */ @@ -1450,9 +1650,9 @@ UString2 &UString2::operator=(const wchar_t *s) unsigned len = MyStringLen(s); if (len > _len) { - wchar_t *newBuf = MY_STRING_NEW(wchar_t, len + 1); + wchar_t *newBuf = MY_STRING_NEW_wchar_t((size_t)len + 1); if (_chars) - MY_STRING_DELETE(_chars); + MY_STRING_DELETE(_chars) _chars = newBuf; } _len = len; @@ -1465,9 +1665,9 @@ void UString2::SetFromAscii(const char *s) unsigned len = MyStringLen(s); if (len > _len) { - wchar_t *newBuf = MY_STRING_NEW(wchar_t, len + 1); + wchar_t *newBuf = MY_STRING_NEW_wchar_t((size_t)len + 1); if (_chars) - MY_STRING_DELETE(_chars); + MY_STRING_DELETE(_chars) _chars = newBuf; } wchar_t *chars = _chars; @@ -1484,9 +1684,9 @@ UString2 &UString2::operator=(const UString2 &s) unsigned len = s._len; if (len > _len) { - wchar_t *newBuf = MY_STRING_NEW(wchar_t, len + 1); + wchar_t *newBuf = MY_STRING_NEW_wchar_t((size_t)len + 1); if (_chars) - MY_STRING_DELETE(_chars); + MY_STRING_DELETE(_chars) _chars = newBuf; } _len = len; @@ -1524,15 +1724,19 @@ int MyStringCompareNoCase(const char *s1, const char *s2) } */ +#if !defined(USE_UNICODE_FSTRING) || !defined(_UNICODE) + static inline UINT GetCurrentCodePage() { #if defined(UNDER_CE) || !defined(_WIN32) return CP_ACP; #else - return CP_UTF8; + return ::AreFileApisANSI() ? CP_ACP : CP_OEMCP; #endif } +#endif + #ifdef USE_UNICODE_FSTRING #ifndef _UNICODE @@ -1542,18 +1746,28 @@ AString fs2fas(CFSTR s) return UnicodeStringToMultiByte(s, GetCurrentCodePage()); } +FString fas2fs(const char *s) +{ + return MultiByteToUnicodeString(s, GetCurrentCodePage()); +} + FString fas2fs(const AString &s) { return MultiByteToUnicodeString(s, GetCurrentCodePage()); } -#endif +#endif // _UNICODE -#else +#else // USE_UNICODE_FSTRING + +UString fs2us(const FChar *s) +{ + return MultiByteToUnicodeString(s, GetCurrentCodePage()); +} UString fs2us(const FString &s) { - return MultiByteToUnicodeString((AString)s, GetCurrentCodePage()); + return MultiByteToUnicodeString(s, GetCurrentCodePage()); } FString us2fs(const wchar_t *s) @@ -1561,4 +1775,68 @@ FString us2fs(const wchar_t *s) return UnicodeStringToMultiByte(s, GetCurrentCodePage()); } -#endif +#endif // USE_UNICODE_FSTRING + + +bool CStringFinder::FindWord_In_LowCaseAsciiList_NoCase(const char *p, const wchar_t *str) +{ + _temp.Empty(); + for (;;) + { + const wchar_t c = *str++; + if (c == 0) + break; + if (c <= 0x20 || c > 0x7f) + return false; + _temp.Add_Char((char)MyCharLower_Ascii((char)c)); + } + + while (*p != 0) + { + const char *s2 = _temp.Ptr(); + char c, c2; + do + { + c = *p++; + c2 = *s2++; + } + while (c == c2); + + if (c == ' ') + { + if (c2 == 0) + return true; + continue; + } + + while (*p++ != ' '); + } + + return false; +} + + +void SplitString(const UString &srcString, UStringVector &destStrings) +{ + destStrings.Clear(); + unsigned len = srcString.Len(); + if (len == 0) + return; + UString s; + for (unsigned i = 0; i < len; i++) + { + const wchar_t c = srcString[i]; + if (c == ' ') + { + if (!s.IsEmpty()) + { + destStrings.Add(s); + s.Empty(); + } + } + else + s += c; + } + if (!s.IsEmpty()) + destStrings.Add(s); +} diff --git a/src/plugins/7zip/7za/cpp/Common/MyString.h b/src/plugins/7zip/7za/cpp/Common/MyString.h index 745e993f..6d4665dc 100644 --- a/src/plugins/7zip/7za/cpp/Common/MyString.h +++ b/src/plugins/7zip/7za/cpp/Common/MyString.h @@ -1,7 +1,7 @@ -// Common/String.h +// Common/MyString.h -#ifndef __COMMON_STRING_H -#define __COMMON_STRING_H +#ifndef ZIP7_INC_COMMON_MY_STRING_H +#define ZIP7_INC_COMMON_MY_STRING_H #include @@ -10,10 +10,42 @@ #include #endif +#include "Common.h" #include "MyWindows.h" #include "MyTypes.h" #include "MyVector.h" + +/* if (DEBUG_FSTRING_INHERITS_ASTRING is defined), then + FString inherits from AString, so we can find bugs related to FString at compile time. + DON'T define DEBUG_FSTRING_INHERITS_ASTRING in release code */ + +// #define DEBUG_FSTRING_INHERITS_ASTRING + +#ifdef DEBUG_FSTRING_INHERITS_ASTRING +class FString; +#endif + + +#ifdef _MSC_VER + #ifdef _NATIVE_WCHAR_T_DEFINED + #define MY_NATIVE_WCHAR_T_DEFINED + #endif +#else + #define MY_NATIVE_WCHAR_T_DEFINED +#endif + +/* + native support for wchar_t: + _MSC_VER == 1600 : /Zc:wchar_t is not supported + _MSC_VER == 1310 (VS2003) + ? _MSC_VER == 1400 (VS2005) : wchar_t <- unsigned short + /Zc:wchar_t : wchar_t <- __wchar_t, _WCHAR_T_DEFINED and _NATIVE_WCHAR_T_DEFINED + _MSC_VER > 1400 (VS2008+) + /Zc:wchar_t[-] + /Zc:wchar_t is on by default +*/ + #ifdef _WIN32 #define IS_PATH_SEPAR(c) ((c) == '\\' || (c) == '/') #else @@ -39,7 +71,7 @@ inline char *MyStpCpy(char *dest, const char *src) { for (;;) { - char c = *src; + const char c = *src; *dest = c; if (c == 0) return dest; @@ -48,6 +80,13 @@ inline char *MyStpCpy(char *dest, const char *src) } } +inline void MyStringCat(char *dest, const char *src) +{ + for (; *dest != 0; dest++); + while ((*dest++ = *src++) != 0); + // MyStringCopy(dest + MyStringLen(dest), src); +} + inline unsigned MyStringLen(const wchar_t *s) { unsigned i; @@ -60,12 +99,20 @@ inline void MyStringCopy(wchar_t *dest, const wchar_t *src) while ((*dest++ = *src++) != 0); } +inline void MyStringCat(wchar_t *dest, const wchar_t *src) +{ + for (; *dest != 0; dest++); + while ((*dest++ = *src++) != 0); + // MyStringCopy(dest + MyStringLen(dest), src); +} + + /* inline wchar_t *MyWcpCpy(wchar_t *dest, const wchar_t *src) { for (;;) { - wchar_t c = *src; + const wchar_t c = *src; *dest = c; if (c == 0) return dest; @@ -88,13 +135,15 @@ int FindCharPosInString(const wchar_t *s, wchar_t c) throw(); #define STRING_UNICODE_THROW throw() #endif -/* + inline char MyCharUpper_Ascii(char c) { if (c >= 'a' && c <= 'z') - return (char)(c - 0x20); + return (char)((unsigned char)c - 0x20); return c; } + +/* inline wchar_t MyCharUpper_Ascii(wchar_t c) { if (c >= 'a' && c <= 'z') @@ -131,7 +180,7 @@ inline wchar_t MyCharUpper(wchar_t c) throw() return (wchar_t)MyCharUpper_WIN(c); #endif #else - return (wchar_t)towupper(c); + return (wchar_t)towupper((wint_t)c); #endif } @@ -158,6 +207,7 @@ inline wchar_t MyCharLower(wchar_t c) throw() // char *MyStringUpper(char *s) throw(); // char *MyStringLower(char *s) throw(); +// void MyStringUpper_Ascii(char *s) throw(); // void MyStringUpper_Ascii(wchar_t *s) throw(); void MyStringLower_Ascii(char *s) throw(); void MyStringLower_Ascii(wchar_t *s) throw(); @@ -168,21 +218,53 @@ bool StringsAreEqualNoCase(const wchar_t *s1, const wchar_t *s2) throw(); bool IsString1PrefixedByString2(const char *s1, const char *s2) throw(); bool IsString1PrefixedByString2(const wchar_t *s1, const wchar_t *s2) throw(); +bool IsString1PrefixedByString2(const wchar_t *s1, const char *s2) throw(); +bool IsString1PrefixedByString2_NoCase_Ascii(const char *s1, const char *s2) throw(); +bool IsString1PrefixedByString2_NoCase_Ascii(const wchar_t *u, const char *a) throw(); bool IsString1PrefixedByString2_NoCase(const wchar_t *s1, const wchar_t *s2) throw(); +#define MyStringCompare(s1, s2) wcscmp(s1, s2) int MyStringCompareNoCase(const wchar_t *s1, const wchar_t *s2) throw(); // int MyStringCompareNoCase_N(const wchar_t *s1, const wchar_t *s2, unsigned num) throw(); // ---------- ASCII ---------- // char values in ASCII strings must be less then 128 +bool StringsAreEqual_Ascii(const char *u, const char *a) throw(); bool StringsAreEqual_Ascii(const wchar_t *u, const char *a) throw(); bool StringsAreEqualNoCase_Ascii(const char *s1, const char *s2) throw(); bool StringsAreEqualNoCase_Ascii(const wchar_t *s1, const char *s2) throw(); bool StringsAreEqualNoCase_Ascii(const wchar_t *s1, const wchar_t *s2) throw(); -#define MY_STRING_DELETE(_p_) delete []_p_; +#define MY_STRING_DELETE(_p_) { delete [](_p_); } // #define MY_STRING_DELETE(_p_) my_delete(_p_); + +#define FORBID_STRING_OPS_2(cls, t) \ + void Find(t) const; \ + void Find(t, unsigned startIndex) const; \ + void ReverseFind(t) const; \ + void InsertAtFront(t); \ + void RemoveChar(t); \ + void Replace(t, t); \ + +#define FORBID_STRING_OPS(cls, t) \ + explicit cls(t); \ + explicit cls(const t *); \ + cls &operator=(t); \ + cls &operator=(const t *); \ + cls &operator+=(t); \ + cls &operator+=(const t *); \ + FORBID_STRING_OPS_2(cls, t) \ + +/* + cls &operator+(t); \ + cls &operator+(const t *); \ +*/ + +#define FORBID_STRING_OPS_AString(t) FORBID_STRING_OPS(AString, t) +#define FORBID_STRING_OPS_UString(t) FORBID_STRING_OPS(UString, t) +#define FORBID_STRING_OPS_UString2(t) FORBID_STRING_OPS(UString2, t) + class AString { char *_chars; @@ -199,15 +281,17 @@ class AString void ReAlloc(unsigned newLimit); void ReAlloc2(unsigned newLimit); void SetStartLen(unsigned len); + + Z7_NO_INLINE void Grow_1(); void Grow(unsigned n); - // AString(unsigned num, const char *s); + AString(unsigned num, const char *s); AString(unsigned num, const AString &s); AString(const AString &s, char c); // it's for String + char AString(const char *s1, unsigned num1, const char *s2, unsigned num2); - friend AString operator+(const AString &s, char c) { return AString(s, c); } ; + friend AString operator+(const AString &s, char c) { return AString(s, c); } // friend AString operator+(char c, const AString &s); // is not supported friend AString operator+(const AString &s1, const AString &s2); @@ -215,35 +299,48 @@ class AString friend AString operator+(const char *s1, const AString &s2); // ---------- forbidden functions ---------- - AString &operator+=(wchar_t c); - AString &operator=(wchar_t c); - AString(wchar_t c); - void Find(wchar_t c) const; - void Find(wchar_t c, unsigned startIndex) const; - void ReverseFind(wchar_t c) const; - void InsertAtFront(wchar_t c); - void RemoveChar(wchar_t ch); - void Replace(wchar_t oldChar, wchar_t newChar); + + #ifdef MY_NATIVE_WCHAR_T_DEFINED + FORBID_STRING_OPS_AString(wchar_t) + #endif + + FORBID_STRING_OPS_AString(signed char) + FORBID_STRING_OPS_AString(unsigned char) + FORBID_STRING_OPS_AString(short) + FORBID_STRING_OPS_AString(unsigned short) + FORBID_STRING_OPS_AString(int) + FORBID_STRING_OPS_AString(unsigned) + FORBID_STRING_OPS_AString(long) + FORBID_STRING_OPS_AString(unsigned long) + + #ifdef DEBUG_FSTRING_INHERITS_ASTRING + AString(const FString &s); + AString &operator=(const FString &s); + AString &operator+=(const FString &s); + #endif public: - AString(); - AString(char c); - AString(const char *s); + explicit AString(); + explicit AString(char c); + explicit AString(const char *s); AString(const AString &s); - ~AString() { MY_STRING_DELETE(_chars); } + ~AString() { MY_STRING_DELETE(_chars) } unsigned Len() const { return _len; } bool IsEmpty() const { return _len == 0; } void Empty() { _len = 0; _chars[0] = 0; } operator const char *() const { return _chars; } + char *Ptr_non_const() const { return _chars; } const char *Ptr() const { return _chars; } const char *Ptr(unsigned pos) const { return _chars + pos; } + const char *Ptr(int pos) const { return _chars + (unsigned)pos; } const char *RightPtr(unsigned num) const { return _chars + _len - num; } - char Back() const { return _chars[_len - 1]; } + char Back() const { return _chars[(size_t)_len - 1]; } void ReplaceOneCharAtPos(unsigned pos, char c) { _chars[pos] = c; } + char *GetBuf() { return _chars; } /* GetBuf(minLen): provides the buffer that can store at least (minLen) characters and additional null terminator. 9.35: GetBuf doesn't preserve old characters and terminator */ @@ -278,6 +375,8 @@ class AString void SetFromWStr_if_Ascii(const wchar_t *s); // void SetFromBstr_if_Ascii(BSTR s); +// private: + Z7_FORCE_INLINE AString &operator+=(char c) { if (_limit == _len) @@ -289,23 +388,35 @@ class AString _len = len; return *this; } - +public: void Add_Space(); void Add_Space_if_NotEmpty(); + void Add_OptSpaced(const char *s); + void Add_Char(char c); void Add_LF(); + void Add_Slash(); + void Add_Dot(); + void Add_Minus(); + void Add_Colon(); void Add_PathSepar() { operator+=(CHAR_PATH_SEPARATOR); } AString &operator+=(const char *s); AString &operator+=(const AString &s); - void AddAscii(const char *s) { operator+=(s); } + void Add_UInt32(UInt32 v); + void Add_UInt64(UInt64 v); + + void AddFrom(const char *s, unsigned len); // no check void SetFrom(const char *s, unsigned len); // no check + void SetFrom_Chars_SizeT(const char* s, size_t len); // no check + void SetFrom(const char* s, int len) // no check + { + SetFrom(s, (unsigned)len); // no check + } void SetFrom_CalcLen(const char *s, unsigned len); - // void SetFromAscii(const char *s) { operator+=(s); } AString Mid(unsigned startIndex, unsigned count) const { return AString(count, _chars + startIndex); } AString Left(unsigned count) const { return AString(count, *this); } - // void MakeUpper() { MyStringUpper(_chars); } // void MakeLower() { MyStringLower(_chars); } void MakeLower_Ascii() { MyStringLower_Ascii(_chars); } @@ -318,11 +429,11 @@ class AString // int CompareNoCase(const char *s) const { return MyStringCompareNoCase(_chars, s); } // int CompareNoCase(const AString &s) const { return MyStringCompareNoCase(_chars, s._chars); } bool IsPrefixedBy(const char *s) const { return IsString1PrefixedByString2(_chars, s); } - bool IsPrefixedBy_Ascii_NoCase(const char *s) const throw(); + bool IsPrefixedBy_Ascii_NoCase(const char *s) const { return IsString1PrefixedByString2_NoCase_Ascii(_chars, s); } bool IsAscii() const { - unsigned len = Len(); + const unsigned len = Len(); const char *s = _chars; for (unsigned i = 0; i < len; i++) if ((unsigned char)s[i] >= 0x80) @@ -332,9 +443,13 @@ class AString int Find(char c) const { return FindCharPosInString(_chars, c); } int Find(char c, unsigned startIndex) const { - int pos = FindCharPosInString(_chars + startIndex, c); + const int pos = FindCharPosInString(_chars + startIndex, c); return pos < 0 ? -1 : (int)startIndex + pos; } + int Find(char c, int startIndex) const + { + return Find(c, (unsigned)startIndex); + } int ReverseFind(char c) const throw(); int ReverseFind_Dot() const throw() { return ReverseFind('.'); } @@ -373,8 +488,35 @@ class AString _chars[index] = 0; } } + void DeleteFrom(int index) + { + DeleteFrom((unsigned)index); + } + + + void Wipe_and_Empty() + { + if (_chars) + { + memset(_chars, 0, (_limit + 1) * sizeof(*_chars)); + _len = 0; + } + } }; + +class AString_Wipe: public AString +{ + Z7_CLASS_NO_COPY(AString_Wipe) +public: + AString_Wipe(): AString() {} + // AString_Wipe(const AString &s): AString(s) {} + // AString_Wipe &operator=(const AString &s) { AString::operator=(s); return *this; } + // AString_Wipe &operator=(const char *s) { AString::operator=(s); return *this; } + ~AString_Wipe() { Wipe_and_Empty(); } +}; + + bool operator<(const AString &s1, const AString &s2); bool operator>(const AString &s1, const AString &s2); @@ -435,7 +577,7 @@ class UString UString(const UString &s, wchar_t c); // it's for String + char UString(const wchar_t *s1, unsigned num1, const wchar_t *s2, unsigned num2); - friend UString operator+(const UString &s, wchar_t c) { return UString(s, c); } ; + friend UString operator+(const UString &s, wchar_t c) { return UString(s, c); } // friend UString operator+(wchar_t c, const UString &s); // is not supported friend UString operator+(const UString &s1, const UString &s2); @@ -444,44 +586,61 @@ class UString // ---------- forbidden functions ---------- - UString &operator+=(char c); - UString &operator+=(unsigned char c); - UString &operator=(char c); - UString &operator=(unsigned char c); - UString(char c); - UString(unsigned char c); - void Find(char c) const; - void Find(unsigned char c) const; - void Find(char c, unsigned startIndex) const; - void Find(unsigned char c, unsigned startIndex) const; - void ReverseFind(char c) const; - void ReverseFind(unsigned char c) const; - void InsertAtFront(char c); - void InsertAtFront(unsigned char c); - void RemoveChar(char ch); - void RemoveChar(unsigned char ch); - void Replace(char oldChar, char newChar); - void Replace(unsigned char oldChar, unsigned char newChar); + FORBID_STRING_OPS_UString(signed char) + FORBID_STRING_OPS_UString(unsigned char) + FORBID_STRING_OPS_UString(short) + + #ifdef MY_NATIVE_WCHAR_T_DEFINED + FORBID_STRING_OPS_UString(unsigned short) + #endif + + FORBID_STRING_OPS_UString(int) + FORBID_STRING_OPS_UString(unsigned) + FORBID_STRING_OPS_UString(long) + FORBID_STRING_OPS_UString(unsigned long) + + FORBID_STRING_OPS_2(UString, char) + + #ifdef DEBUG_FSTRING_INHERITS_ASTRING + UString(const FString &s); + UString &operator=(const FString &s); + UString &operator+=(const FString &s); + #endif public: UString(); - UString(wchar_t c); + explicit UString(wchar_t c); + explicit UString(char c); + explicit UString(const char *s); + explicit UString(const AString &s); UString(const wchar_t *s); UString(const UString &s); - ~UString() { MY_STRING_DELETE(_chars); } + ~UString() { MY_STRING_DELETE(_chars) } unsigned Len() const { return _len; } bool IsEmpty() const { return _len == 0; } void Empty() { _len = 0; _chars[0] = 0; } operator const wchar_t *() const { return _chars; } + wchar_t *Ptr_non_const() const { return _chars; } const wchar_t *Ptr() const { return _chars; } + const wchar_t *Ptr(int pos) const { return _chars + (unsigned)pos; } const wchar_t *Ptr(unsigned pos) const { return _chars + pos; } const wchar_t *RightPtr(unsigned num) const { return _chars + _len - num; } - wchar_t Back() const { return _chars[_len - 1]; } + wchar_t Back() const { return _chars[(size_t)_len - 1]; } void ReplaceOneCharAtPos(unsigned pos, wchar_t c) { _chars[pos] = c; } + wchar_t *GetBuf() { return _chars; } + + /* + wchar_t *GetBuf_GetMaxAvail(unsigned &availBufLen) + { + availBufLen = _limit; + return _chars; + } + */ + wchar_t *GetBuf(unsigned minLen) { if (minLen > _limit) @@ -508,10 +667,16 @@ class UString } UString &operator=(wchar_t c); + UString &operator=(char c) { return (*this)=((wchar_t)(unsigned char)c); } UString &operator=(const wchar_t *s); UString &operator=(const UString &s); - void SetFromBstr(BSTR s); + void SetFrom(const wchar_t *s, unsigned len); // no check + void SetFromBstr(LPCOLESTR s); + UString &operator=(const char *s); + UString &operator=(const AString &s) { return operator=(s.Ptr()); } +// private: + Z7_FORCE_INLINE UString &operator+=(wchar_t c) { if (_limit == _len) @@ -524,21 +689,30 @@ class UString return *this; } +private: + UString &operator+=(char c); // { return (*this)+=((wchar_t)(unsigned char)c); } +public: + void Add_Char(char c); + // void Add_WChar(wchar_t c); void Add_Space(); void Add_Space_if_NotEmpty(); void Add_LF(); + void Add_Dot(); + void Add_Minus(); + void Add_Colon(); void Add_PathSepar() { operator+=(WCHAR_PATH_SEPARATOR); } UString &operator+=(const wchar_t *s); UString &operator+=(const UString &s); + UString &operator+=(const char *s); + UString &operator+=(const AString &s) { return operator+=(s.Ptr()); } - void SetFrom(const wchar_t *s, unsigned len); // no check - - void SetFromAscii(const char *s); - void AddAscii(const char *s); + void Add_UInt32(UInt32 v); + void Add_UInt64(UInt64 v); UString Mid(unsigned startIndex, unsigned count) const { return UString(count, _chars + startIndex); } UString Left(unsigned count) const { return UString(count, *this); } + UString Left(int count) const { return Left((unsigned)count); } // void MakeUpper() { MyStringUpper(_chars); } // void MakeUpper() { MyStringUpper_Ascii(_chars); } @@ -553,22 +727,23 @@ class UString // int CompareNoCase(const wchar_t *s) const { return MyStringCompareNoCase(_chars, s); } // int CompareNoCase(const UString &s) const { return MyStringCompareNoCase(_chars, s._chars); } bool IsPrefixedBy(const wchar_t *s) const { return IsString1PrefixedByString2(_chars, s); } + bool IsPrefixedBy(const char *s) const { return IsString1PrefixedByString2(_chars, s); } bool IsPrefixedBy_NoCase(const wchar_t *s) const { return IsString1PrefixedByString2_NoCase(_chars, s); } - bool IsPrefixedBy_Ascii_NoCase(const char *s) const throw(); + bool IsPrefixedBy_Ascii_NoCase(const char *s) const { return IsString1PrefixedByString2_NoCase_Ascii(_chars, s); } bool IsAscii() const { - unsigned len = Len(); + const unsigned len = Len(); const wchar_t *s = _chars; for (unsigned i = 0; i < len; i++) - if (s[i] >= 0x80) + if ((unsigned)(int)s[i] >= 0x80) return false; return true; } int Find(wchar_t c) const { return FindCharPosInString(_chars, c); } int Find(wchar_t c, unsigned startIndex) const { - int pos = FindCharPosInString(_chars + startIndex, c); + const int pos = FindCharPosInString(_chars + startIndex, c); return pos < 0 ? -1 : (int)startIndex + pos; } @@ -588,7 +763,7 @@ class UString } void InsertAtFront(wchar_t c); - // void Insert(unsigned index, wchar_t c); + // void Insert_wchar_t(unsigned index, wchar_t c); void Insert(unsigned index, const wchar_t *s); void Insert(unsigned index, const UString &s); @@ -597,10 +772,12 @@ class UString void Replace(wchar_t oldChar, wchar_t newChar) throw(); void Replace(const UString &oldString, const UString &newString); + void Delete(int index) throw() { Delete((unsigned)index); } void Delete(unsigned index) throw(); void Delete(unsigned index, unsigned count) throw(); void DeleteFrontal(unsigned num) throw(); void DeleteBack() { _chars[--_len] = 0; } + void DeleteFrom(int index) { DeleteFrom((unsigned)index); } void DeleteFrom(unsigned index) { if (index < _len) @@ -609,8 +786,30 @@ class UString _chars[index] = 0; } } + + void Wipe_and_Empty() + { + if (_chars) + { + memset(_chars, 0, (_limit + 1) * sizeof(*_chars)); + _len = 0; + } + } +}; + + +class UString_Wipe: public UString +{ + Z7_CLASS_NO_COPY(UString_Wipe) +public: + UString_Wipe(): UString() {} + // UString_Wipe(const UString &s): UString(s) {} + // UString_Wipe &operator=(const UString &s) { UString::operator=(s); return *this; } + // UString_Wipe &operator=(const wchar_t *s) { UString::operator=(s); return *this; } + ~UString_Wipe() { Wipe_and_Empty(); } }; + bool operator<(const UString &s1, const UString &s2); bool operator>(const UString &s1, const UString &s2); @@ -630,6 +829,12 @@ void operator==(const UString &s1, wchar_t c2); void operator+(wchar_t c, const UString &s); // this function can be OK, but we don't use it +void operator+(const AString &s1, const UString &s2); +void operator+(const UString &s1, const AString &s2); + +void operator+(const UString &s1, const char *s2); +void operator+(const char *s1, const UString &s2); + void operator+(const UString &s, char c); void operator+(const UString &s, unsigned char c); void operator+(char c, const UString &s); @@ -662,18 +867,28 @@ class UString2 // ---------- forbidden functions ---------- - UString2 &operator=(char c); - UString2 &operator=(unsigned char c); + FORBID_STRING_OPS_UString2(char) + FORBID_STRING_OPS_UString2(signed char) + FORBID_STRING_OPS_UString2(unsigned char) + FORBID_STRING_OPS_UString2(short) + UString2 &operator=(wchar_t c); - UString2(char c); - UString2(unsigned char c); + + UString2(const AString &s); + UString2 &operator=(const AString &s); + UString2 &operator+=(const AString &s); + + #ifdef DEBUG_FSTRING_INHERITS_ASTRING + UString2(const FString &s); + UString2 &operator=(const FString &s); + UString2 &operator+=(const FString &s); + #endif public: UString2(): _chars(NULL), _len(0) {} - // UString2(wchar_t c); UString2(const wchar_t *s); UString2(const UString2 &s); - ~UString2() { if (_chars) MY_STRING_DELETE(_chars); } + ~UString2() { if (_chars) { MY_STRING_DELETE(_chars) } } unsigned Len() const { return _len; } bool IsEmpty() const { return _len == 0; } @@ -682,6 +897,8 @@ class UString2 // operator const wchar_t *() const { return _chars; } const wchar_t *GetRawPtr() const { return _chars; } + int Compare(const wchar_t *s) const { return wcscmp(_chars, s); } + wchar_t *GetBuf(unsigned minLen) { if (!_chars || minLen > _len) @@ -741,47 +958,123 @@ typedef CObjectVector CSysStringVector; // ---------- FString ---------- +#ifndef DEBUG_FSTRING_INHERITS_ASTRING #ifdef _WIN32 -// OPENSAL_7ZIP_PATCH BEGIN -// Disable Unicode interface because Salamander is not Unicode-friendly -// #define USE_UNICODE_FSTRING -// OPENSAL_7ZIP_PATCH END + // Preserve the host's ANSI plug-in ABI until its file-name boundary is Unicode-safe. + // #define USE_UNICODE_FSTRING +#endif #endif #ifdef USE_UNICODE_FSTRING - #define __FTEXT(quote) L##quote + #define MY_FTEXT(quote) L##quote typedef wchar_t FChar; typedef UString FString; #define fs2us(_x_) (_x_) #define us2fs(_x_) (_x_) + FString fas2fs(const char *s); FString fas2fs(const AString &s); AString fs2fas(const FChar *s); -#else +#else // USE_UNICODE_FSTRING - #define __FTEXT(quote) quote + #define MY_FTEXT(quote) quote typedef char FChar; + + #ifdef DEBUG_FSTRING_INHERITS_ASTRING + + class FString: public AString + { + // FString &operator=(const char *s); + FString &operator=(const AString &s); + // FString &operator+=(const AString &s); + public: + FString(const AString &s): AString(s.Ptr()) {} + FString(const FString &s): AString(s.Ptr()) {} + FString(const char *s): AString(s) {} + FString() {} + FString &operator=(const FString &s) { AString::operator=((const AString &)s); return *this; } + FString &operator=(char c) { AString::operator=(c); return *this; } + FString &operator+=(char c) { AString::operator+=(c); return *this; } + FString &operator+=(const FString &s) { AString::operator+=((const AString &)s); return *this; } + FString Left(unsigned count) const { return FString(AString::Left(count)); } + }; + void operator+(const AString &s1, const FString &s2); + void operator+(const FString &s1, const AString &s2); + + inline FString operator+(const FString &s1, const FString &s2) + { + AString s =(const AString &)s1 + (const AString &)s2; + return FString(s.Ptr()); + // return FString((const AString &)s1 + (const AString &)s2); + } + inline FString operator+(const FString &s1, const FChar *s2) + { + return s1 + (FString)s2; + } + /* + inline FString operator+(const FChar *s1, const FString &s2) + { + return (FString)s1 + s2; + } + */ + + inline FString fas2fs(const char *s) { return FString(s); } + + #else // DEBUG_FSTRING_INHERITS_ASTRING typedef AString FString; + #define fas2fs(_x_) (_x_) + #endif // DEBUG_FSTRING_INHERITS_ASTRING + UString fs2us(const FChar *s); UString fs2us(const FString &s); FString us2fs(const wchar_t *s); - #define fas2fs(_x_) (_x_) #define fs2fas(_x_) (_x_) -#endif +#endif // USE_UNICODE_FSTRING -#define FTEXT(quote) __FTEXT(quote) +#define FTEXT(quote) MY_FTEXT(quote) #define FCHAR_PATH_SEPARATOR FTEXT(CHAR_PATH_SEPARATOR) #define FSTRING_PATH_SEPARATOR FTEXT(STRING_PATH_SEPARATOR) -#define FCHAR_ANY_MASK FTEXT('*') -#define FSTRING_ANY_MASK FTEXT("*") + +// #define FCHAR_ANY_MASK FTEXT('*') +// #define FSTRING_ANY_MASK FTEXT("*") + typedef const FChar *CFSTR; typedef CObjectVector FStringVector; + +class CStringFinder +{ + AString _temp; +public: + // list - is list of low case Ascii strings separated by space " ". + // the function returns true, if it can find exact word (str) in (list). + bool FindWord_In_LowCaseAsciiList_NoCase(const char *list, const wchar_t *str); +}; + +void SplitString(const UString &srcString, UStringVector &destStrings); + +#endif + + + +#if defined(_WIN32) + // #include + // WCHAR_MAX is defined as ((wchar_t)-1) + #define Z7_WCHART_IS_16BIT 1 +#elif (defined(WCHAR_MAX) && (WCHAR_MAX <= 0xffff)) \ + || (defined(__SIZEOF_WCHAR_T__) && (__SIZEOF_WCHAR_T__ == 2)) + #define Z7_WCHART_IS_16BIT 1 +#endif + +#if WCHAR_PATH_SEPARATOR == L'\\' +// WSL scheme +#define WCHAR_IN_FILE_NAME_BACKSLASH_REPLACEMENT ((wchar_t)((unsigned)(0xF000) + (unsigned)'\\')) +// #define WCHAR_IN_FILE_NAME_BACKSLASH_REPLACEMENT '_' #endif diff --git a/src/plugins/7zip/7za/cpp/Common/MyTypes.h b/src/plugins/7zip/7za/cpp/Common/MyTypes.h index 75806f37..eadc9a4d 100644 --- a/src/plugins/7zip/7za/cpp/Common/MyTypes.h +++ b/src/plugins/7zip/7za/cpp/Common/MyTypes.h @@ -1,11 +1,13 @@ // Common/MyTypes.h -#ifndef __COMMON_MY_TYPES_H -#define __COMMON_MY_TYPES_H +#ifndef ZIP7_INC_COMMON_MY_TYPES_H +#define ZIP7_INC_COMMON_MY_TYPES_H +#include "Common0.h" #include "../../C/7zTypes.h" -typedef int HRes; +// typedef int HRes; +// typedef HRESULT HRes; struct CBoolPair { @@ -25,11 +27,12 @@ struct CBoolPair Val = true; Def = true; } -}; -#define CLASS_NO_COPY(cls) \ - private: \ - cls(const cls &); \ - cls &operator=(const cls &); + void SetVal_as_Defined(bool val) + { + Val = val; + Def = true; + } +}; #endif diff --git a/src/plugins/7zip/7za/cpp/Common/MyUnknown.h b/src/plugins/7zip/7za/cpp/Common/MyUnknown.h index ff025cb5..75ee96fa 100644 --- a/src/plugins/7zip/7za/cpp/Common/MyUnknown.h +++ b/src/plugins/7zip/7za/cpp/Common/MyUnknown.h @@ -1,17 +1,8 @@ // MyUnknown.h -#ifndef __MY_UNKNOWN_H -#define __MY_UNKNOWN_H +#ifndef ZIP7_INC_MY_UNKNOWN_H +#define ZIP7_INC_MY_UNKNOWN_H #include "MyWindows.h" -/* -#ifdef _WIN32 -#include -#include -#else -#include "MyWindows.h" -#endif -*/ - #endif diff --git a/src/plugins/7zip/7za/cpp/Common/MyVector.h b/src/plugins/7zip/7za/cpp/Common/MyVector.h index 14f7b11a..a772785c 100644 --- a/src/plugins/7zip/7za/cpp/Common/MyVector.h +++ b/src/plugins/7zip/7za/cpp/Common/MyVector.h @@ -1,10 +1,14 @@ // Common/MyVector.h -#ifndef __COMMON_MY_VECTOR_H -#define __COMMON_MY_VECTOR_H +#ifndef ZIP7_INC_COMMON_MY_VECTOR_H +#define ZIP7_INC_COMMON_MY_VECTOR_H #include +#include "Common.h" + +const unsigned k_VectorSizeMax = ((unsigned)1 << 31) - 1; + template class CRecordVector { @@ -17,29 +21,41 @@ class CRecordVector memmove(_items + destIndex, _items + srcIndex, (size_t)(_size - srcIndex) * sizeof(T)); } - void ReserveOnePosition() + void ReAllocForNewCapacity(const unsigned newCapacity) { - if (_size == _capacity) - { - unsigned newCapacity = _capacity + (_capacity >> 2) + 1; - T *p = new T[newCapacity]; - if (_size != 0) - memcpy(p, _items, (size_t)_size * sizeof(T)); - delete []_items; - _items = p; - _capacity = newCapacity; - } + T *p; + Z7_ARRAY_NEW(p, T, newCapacity) + // p = new T[newCapacity]; + if (_size != 0) + memcpy(p, _items, (size_t)_size * sizeof(T)); + delete []_items; + _items = p; + _capacity = newCapacity; } public: - CRecordVector(): _items(0), _size(0), _capacity(0) {} + void ReserveOnePosition() + { + if (_size != _capacity) + return; + if (_capacity >= k_VectorSizeMax) + throw 2021; + const unsigned rem = k_VectorSizeMax - _capacity; + unsigned add = (_capacity >> 2) + 1; + if (add > rem) + add = rem; + ReAllocForNewCapacity(_capacity + add); + } + + CRecordVector(): _items(NULL), _size(0), _capacity(0) {} - CRecordVector(const CRecordVector &v): _items(0), _size(0), _capacity(0) + CRecordVector(const CRecordVector &v): _items(NULL), _size(0), _capacity(0) { - unsigned size = v.Size(); + const unsigned size = v.Size(); if (size != 0) { + // Z7_ARRAY_NEW(_items, T, size) _items = new T[size]; _size = size; _capacity = size; @@ -54,7 +70,8 @@ class CRecordVector { if (size != 0) { - _items = new T[size]; + Z7_ARRAY_NEW(_items, T, size) + // _items = new T[size]; _capacity = size; } } @@ -63,24 +80,30 @@ class CRecordVector { if (newCapacity > _capacity) { - T *p = new T[newCapacity]; - if (_size != 0) - memcpy(p, _items, (size_t)_size * sizeof(T)); - delete []_items; - _items = p; - _capacity = newCapacity; + if (newCapacity > k_VectorSizeMax) + throw 2021; + ReAllocForNewCapacity(newCapacity); } } + void ChangeSize_KeepData(unsigned newSize) + { + Reserve(newSize); + _size = newSize; + } + void ClearAndReserve(unsigned newCapacity) { Clear(); if (newCapacity > _capacity) { + if (newCapacity > k_VectorSizeMax) + throw 2021; delete []_items; _items = NULL; _capacity = 0; - _items = new T[newCapacity]; + Z7_ARRAY_NEW(_items, T, newCapacity) + // _items = new T[newCapacity]; _capacity = newCapacity; } } @@ -91,20 +114,6 @@ class CRecordVector _size = newSize; } - void ChangeSize_KeepData(unsigned newSize) - { - if (newSize > _capacity) - { - T *p = new T[newSize]; - if (_size != 0) - memcpy(p, _items, (size_t)_size * sizeof(T)); - delete []_items; - _items = p; - _capacity = newSize; - } - _size = newSize; - } - void ReserveDown() { if (_size == _capacity) @@ -112,6 +121,7 @@ class CRecordVector T *p = NULL; if (_size != 0) { + // Z7_ARRAY_NEW(p, T, _size) p = new T[_size]; memcpy(p, _items, (size_t)_size * sizeof(T)); } @@ -170,7 +180,7 @@ class CRecordVector { if (&v == this) return *this; - unsigned size = v.Size(); + const unsigned size = v.Size(); if (size > _capacity) { delete []_items; @@ -188,24 +198,45 @@ class CRecordVector CRecordVector& operator+=(const CRecordVector &v) { - unsigned size = v.Size(); - Reserve(_size + size); + const unsigned size = v.Size(); if (size != 0) + { + if (_size >= k_VectorSizeMax || size > k_VectorSizeMax - _size) + throw 2021; + const unsigned newSize = _size + size; + Reserve(newSize); memcpy(_items + _size, v._items, (size_t)size * sizeof(T)); - _size += size; + _size = newSize; + } return *this; } unsigned Add(const T item) { ReserveOnePosition(); - _items[_size] = item; - return _size++; + const unsigned size = _size; + _size = size + 1; + _items[size] = item; + return size; + } + + /* + unsigned Add2(const T &item) + { + ReserveOnePosition(); + const unsigned size = _size; + _size = size + 1; + _items[size] = item; + return size; } + */ - void AddInReserved(const T item) + unsigned AddInReserved(const T item) { - _items[_size++] = item; + const unsigned size = _size; + _size = size + 1; + _items[size] = item; + return size; } void Insert(unsigned index, const T item) @@ -216,11 +247,18 @@ class CRecordVector _size++; } + void InsertInReserved(unsigned index, const T item) + { + MoveItems(index + 1, index); + _items[index] = item; + _size++; + } + void MoveToFront(unsigned index) { if (index != 0) { - T temp = _items[index]; + const T temp = _items[index]; memmove(_items + 1, _items, (size_t)index * sizeof(T)); _items[0] = temp; } @@ -228,15 +266,31 @@ class CRecordVector const T& operator[](unsigned index) const { return _items[index]; } T& operator[](unsigned index) { return _items[index]; } + const T& operator[](int index) const { return _items[(unsigned)index]; } + T& operator[](int index) { return _items[(unsigned)index]; } + + const T* ConstData() const { return _items; } + T* NonConstData() const { return _items; } + T* NonConstData() { return _items; } + + const T* Data() const { return _items; } + T* Data() { return _items; } + + const T& FrontItem() const { return _items[0]; } + T& FrontItem() { return _items[0]; } + /* + const T Front() const { return _items[0]; } + T Front() { return _items[0]; } const T& Front() const { return _items[0]; } T& Front() { return _items[0]; } - const T& Back() const { return _items[_size - 1]; } - T& Back() { return _items[_size - 1]; } + */ + const T& Back() const { return _items[(size_t)_size - 1]; } + T& Back() { return _items[(size_t)_size - 1]; } /* void Swap(unsigned i, unsigned j) { - T temp = _items[i]; + const T temp = _items[i]; _items[i] = _items[j]; _items[j] = temp; } @@ -246,10 +300,11 @@ class CRecordVector { while (left != right) { - unsigned mid = (left + right) / 2; + // const unsigned mid = (unsigned)(((size_t)left + (size_t)right) / 2); + const unsigned mid = (left + right) / 2; const T midVal = (*this)[mid]; if (item == midVal) - return mid; + return (int)mid; if (item < midVal) right = mid; else @@ -262,11 +317,12 @@ class CRecordVector { while (left != right) { - unsigned mid = (left + right) / 2; + // const unsigned mid = (unsigned)(((size_t)left + (size_t)right) / 2); + const unsigned mid = (left + right) / 2; const T& midVal = (*this)[mid]; - int comp = item.Compare(midVal); + const int comp = item.Compare(midVal); if (comp == 0) - return mid; + return (int)mid; if (comp < 0) right = mid; else @@ -290,7 +346,8 @@ class CRecordVector unsigned left = 0, right = _size; while (left != right) { - unsigned mid = (left + right) / 2; + // const unsigned mid = (unsigned)(((size_t)left + (size_t)right) / 2); + const unsigned mid = (left + right) / 2; const T midVal = (*this)[mid]; if (item == midVal) return mid; @@ -308,9 +365,10 @@ class CRecordVector unsigned left = 0, right = _size; while (left != right) { - unsigned mid = (left + right) / 2; + // const unsigned mid = (unsigned)(((size_t)left + (size_t)right) / 2); + const unsigned mid = (left + right) / 2; const T& midVal = (*this)[mid]; - int comp = item.Compare(midVal); + const int comp = item.Compare(midVal); if (comp == 0) return mid; if (comp < 0) @@ -324,7 +382,7 @@ class CRecordVector static void SortRefDown(T* p, unsigned k, unsigned size, int (*compare)(const T*, const T*, void *), void *param) { - T temp = p[k]; + const T temp = p[k]; for (;;) { unsigned s = (k << 1); @@ -345,16 +403,16 @@ class CRecordVector unsigned size = _size; if (size <= 1) return; - T* p = (&Front()) - 1; + T* p = _items - 1; { unsigned i = size >> 1; do SortRefDown(p, i, size, compare, param); - while (--i != 0); + while (--i); } do { - T temp = p[size]; + const T temp = p[size]; p[size--] = p[1]; p[1] = temp; SortRefDown(p, 1, size, compare, param); @@ -364,13 +422,13 @@ class CRecordVector static void SortRefDown2(T* p, unsigned k, unsigned size) { - T temp = p[k]; + const T temp = p[k]; for (;;) { unsigned s = (k << 1); if (s > size) break; - if (s < size && p[s + 1].Compare(p[s]) > 0) + if (s < size && p[(size_t)s + 1].Compare(p[s]) > 0) s++; if (temp.Compare(p[s]) >= 0) break; @@ -385,16 +443,16 @@ class CRecordVector unsigned size = _size; if (size <= 1) return; - T* p = (&Front()) - 1; + T* p = _items - 1; { unsigned i = size >> 1; do SortRefDown2(p, i, size); - while (--i != 0); + while (--i); } do { - T temp = p[size]; + const T temp = p[size]; p[size--] = p[1]; p[1] = temp; SortRefDown2(p, 1, size); @@ -420,52 +478,83 @@ class CObjectVector // void Reserve(unsigned newCapacity) { _v.Reserve(newCapacity); } void ClearAndReserve(unsigned newCapacity) { Clear(); _v.ClearAndReserve(newCapacity); } - CObjectVector() {}; + CObjectVector() {} CObjectVector(const CObjectVector &v) { - unsigned size = v.Size(); + const unsigned size = v.Size(); _v.ConstructReserve(size); for (unsigned i = 0; i < size; i++) - _v.AddInReserved(new T(v[i])); + AddInReserved(v[i]); } CObjectVector& operator=(const CObjectVector &v) { if (&v == this) return *this; Clear(); - unsigned size = v.Size(); + const unsigned size = v.Size(); _v.Reserve(size); for (unsigned i = 0; i < size; i++) - _v.AddInReserved(new T(v[i])); + AddInReserved(v[i]); return *this; } CObjectVector& operator+=(const CObjectVector &v) { - unsigned size = v.Size(); - _v.Reserve(Size() + size); - for (unsigned i = 0; i < size; i++) - _v.AddInReserved(new T(v[i])); + const unsigned addSize = v.Size(); + if (addSize != 0) + { + const unsigned size = Size(); + if (size >= k_VectorSizeMax || addSize > k_VectorSizeMax - size) + throw 2021; + _v.Reserve(size + addSize); + for (unsigned i = 0; i < addSize; i++) + AddInReserved(v[i]); + } return *this; } const T& operator[](unsigned index) const { return *((T *)_v[index]); } T& operator[](unsigned index) { return *((T *)_v[index]); } + const T& operator[](int index) const { return *((T *)_v[(unsigned)index]); } + T& operator[](int index) { return *((T *)_v[(unsigned)index]); } const T& Front() const { return operator[](0); } T& Front() { return operator[](0); } - const T& Back() const { return operator[](_v.Size() - 1); } - T& Back() { return operator[](_v.Size() - 1); } + const T& Back() const { return *(T *)_v.Back(); } + T& Back() { return *(T *)_v.Back(); } void MoveToFront(unsigned index) { _v.MoveToFront(index); } - unsigned Add(const T& item) { return _v.Add(new T(item)); } + unsigned Add(const T& item) + { + _v.ReserveOnePosition(); + return AddInReserved(item); + } + + unsigned AddInReserved(const T& item) + { + return _v.AddInReserved(new T(item)); + } + + void ReserveOnePosition() + { + _v.ReserveOnePosition(); + } + + unsigned AddInReserved_Ptr_of_new(T *ptr) + { + return _v.AddInReserved(ptr); + } + + #define VECTOR_ADD_NEW_OBJECT(v, a) \ + (v).ReserveOnePosition(); \ + (v).AddInReserved_Ptr_of_new(new a); - void AddInReserved(const T& item) { _v.AddInReserved(new T(item)); } T& AddNew() { + _v.ReserveOnePosition(); T *p = new T; - _v.Add(p); + _v.AddInReserved(p); return *p; } @@ -476,12 +565,17 @@ class CObjectVector return *p; } - void Insert(unsigned index, const T& item) { _v.Insert(index, new T(item)); } + void Insert(unsigned index, const T& item) + { + _v.ReserveOnePosition(); + _v.InsertInReserved(index, new T(item)); + } T& InsertNew(unsigned index) { + _v.ReserveOnePosition(); T *p = new T; - _v.Insert(index, p); + _v.InsertInReserved(index, p); return *p; } @@ -506,7 +600,7 @@ class CObjectVector void DeleteFrom(unsigned index) { - unsigned size = _v.Size(); + const unsigned size = _v.Size(); for (unsigned i = index; i < size; i++) delete (T *)_v[i]; _v.DeleteFrom(index); @@ -521,7 +615,7 @@ class CObjectVector void DeleteBack() { - delete (T *)_v[_v.Size() - 1]; + delete (T *)_v.Back(); _v.DeleteBack(); } @@ -530,6 +624,7 @@ class CObjectVector delete (T *)_v[index]; _v.Delete(index); } + // void Delete(int index) { Delete((unsigned)index); } /* void Delete(unsigned index, unsigned num) @@ -556,11 +651,12 @@ class CObjectVector unsigned left = 0, right = Size(); while (left != right) { - unsigned mid = (left + right) / 2; + // const unsigned mid = (unsigned)(((size_t)left + (size_t)right) / 2); + const unsigned mid = (left + right) / 2; const T& midVal = (*this)[mid]; - int comp = item.Compare(midVal); + const int comp = item.Compare(midVal); if (comp == 0) - return mid; + return (int)mid; if (comp < 0) right = mid; else @@ -574,9 +670,10 @@ class CObjectVector unsigned left = 0, right = Size(); while (left != right) { - unsigned mid = (left + right) / 2; + // const unsigned mid = (unsigned)(((size_t)left + (size_t)right) / 2); + const unsigned mid = (left + right) / 2; const T& midVal = (*this)[mid]; - int comp = item.Compare(midVal); + const int comp = item.Compare(midVal); if (comp == 0) return mid; if (comp < 0) @@ -594,9 +691,10 @@ class CObjectVector unsigned left = 0, right = Size(); while (left != right) { - unsigned mid = (left + right) / 2; + // const unsigned mid = (unsigned)(((size_t)left + (size_t)right) / 2); + const unsigned mid = (left + right) / 2; const T& midVal = (*this)[mid]; - int comp = item.Compare(midVal); + const int comp = item.Compare(midVal); if (comp == 0) { right = mid + 1; @@ -616,9 +714,9 @@ class CObjectVector { _v.Sort(compare, param); } static int CompareObjectItems(void *const *a1, void *const *a2, void * /* param */) - { return (*(*((const T **)a1))).Compare(*(*((const T **)a2))); } + { return (*(*((const T *const *)a1))).Compare(*(*((const T *const *)a2))); } - void Sort() { _v.Sort(CompareObjectItems, 0); } + void Sort() { _v.Sort(CompareObjectItems, NULL); } }; #define FOR_VECTOR(_i_, _v_) for (unsigned _i_ = 0; _i_ < (_v_).Size(); _i_++) diff --git a/src/plugins/7zip/7za/cpp/Common/MyWindows.cpp b/src/plugins/7zip/7za/cpp/Common/MyWindows.cpp index 463c77c4..a1dfbefb 100644 --- a/src/plugins/7zip/7za/cpp/Common/MyWindows.cpp +++ b/src/plugins/7zip/7za/cpp/Common/MyWindows.cpp @@ -5,6 +5,10 @@ #ifndef _WIN32 #include +#include +#ifdef __GNUC__ +#include +#endif #include "MyWindows.h" @@ -38,11 +42,11 @@ BSTR SysAllocStringByteLen(LPCSTR s, UINT len) /* Original SysAllocStringByteLen in Win32 maybe fills only unaligned null OLECHAR at the end. We provide also aligned null OLECHAR at the end. */ - if (len >= (k_BstrSize_Max - sizeof(OLECHAR) - sizeof(OLECHAR) - sizeof(CBstrSizeType))) + if (len >= (k_BstrSize_Max - (UINT)sizeof(OLECHAR) - (UINT)sizeof(OLECHAR) - (UINT)sizeof(CBstrSizeType))) return NULL; - UINT size = (len + sizeof(OLECHAR) + sizeof(OLECHAR) - 1) & ~(sizeof(OLECHAR) - 1); - void *p = AllocateForBSTR(size + sizeof(CBstrSizeType)); + UINT size = (len + (UINT)sizeof(OLECHAR) + (UINT)sizeof(OLECHAR) - 1) & ~((UINT)sizeof(OLECHAR) - 1); + void *p = AllocateForBSTR(size + (UINT)sizeof(CBstrSizeType)); if (!p) return NULL; *(CBstrSizeType *)p = (CBstrSizeType)len; @@ -56,11 +60,11 @@ BSTR SysAllocStringByteLen(LPCSTR s, UINT len) BSTR SysAllocStringLen(const OLECHAR *s, UINT len) { - if (len >= (k_BstrSize_Max - sizeof(OLECHAR) - sizeof(CBstrSizeType)) / sizeof(OLECHAR)) + if (len >= (k_BstrSize_Max - (UINT)sizeof(OLECHAR) - (UINT)sizeof(CBstrSizeType)) / (UINT)sizeof(OLECHAR)) return NULL; - UINT size = len * sizeof(OLECHAR); - void *p = AllocateForBSTR(size + sizeof(CBstrSizeType) + sizeof(OLECHAR)); + UINT size = len * (UINT)sizeof(OLECHAR); + void *p = AllocateForBSTR(size + (UINT)sizeof(CBstrSizeType) + (UINT)sizeof(OLECHAR)); if (!p) return NULL; *(CBstrSizeType *)p = (CBstrSizeType)size; @@ -74,7 +78,7 @@ BSTR SysAllocStringLen(const OLECHAR *s, UINT len) BSTR SysAllocString(const OLECHAR *s) { if (!s) - return 0; + return NULL; const OLECHAR *s2 = s; while (*s2 != 0) s2++; @@ -84,21 +88,21 @@ BSTR SysAllocString(const OLECHAR *s) void SysFreeString(BSTR bstr) { if (bstr) - FreeForBSTR((CBstrSizeType *)bstr - 1); + FreeForBSTR((CBstrSizeType *)(void *)bstr - 1); } UINT SysStringByteLen(BSTR bstr) { if (!bstr) return 0; - return *((CBstrSizeType *)bstr - 1); + return *((CBstrSizeType *)(void *)bstr - 1); } UINT SysStringLen(BSTR bstr) { if (!bstr) return 0; - return *((CBstrSizeType *)bstr - 1) / sizeof(OLECHAR); + return *((CBstrSizeType *)(void *)bstr - 1) / (UINT)sizeof(OLECHAR); } @@ -139,7 +143,150 @@ LONG CompareFileTime(const FILETIME* ft1, const FILETIME* ft2) DWORD GetLastError() { - return 0; + return (DWORD)errno; +} + +void SetLastError(DWORD dw) +{ + errno = (int)dw; +} + + +static LONG TIME_GetBias() +{ + time_t utc = time(NULL); + struct tm *ptm = localtime(&utc); + int localdaylight = ptm->tm_isdst; /* daylight for local timezone */ + ptm = gmtime(&utc); + ptm->tm_isdst = localdaylight; /* use local daylight, not that of Greenwich */ + LONG bias = (int)(mktime(ptm)-utc); + return bias; +} + +#define TICKS_PER_SEC 10000000 +/* +#define SECS_PER_DAY (24 * 60 * 60) +#define SECS_1601_TO_1970 ((369 * 365 + 89) * (UInt64)SECS_PER_DAY) +#define TICKS_1601_TO_1970 (SECS_1601_TO_1970 * TICKS_PER_SEC) +*/ + +#define GET_TIME_64(pft) ((pft)->dwLowDateTime | ((UInt64)(pft)->dwHighDateTime << 32)) + +#define SET_FILETIME(ft, v64) \ + (ft)->dwLowDateTime = (DWORD)v64; \ + (ft)->dwHighDateTime = (DWORD)(v64 >> 32); + + +BOOL WINAPI FileTimeToLocalFileTime(const FILETIME *fileTime, FILETIME *localFileTime) +{ + UInt64 v = GET_TIME_64(fileTime); + v = (UInt64)((Int64)v - (Int64)TIME_GetBias() * TICKS_PER_SEC); + SET_FILETIME(localFileTime, v) + return TRUE; +} + +BOOL WINAPI LocalFileTimeToFileTime(const FILETIME *localFileTime, FILETIME *fileTime) +{ + UInt64 v = GET_TIME_64(localFileTime); + v = (UInt64)((Int64)v + (Int64)TIME_GetBias() * TICKS_PER_SEC); + SET_FILETIME(fileTime, v) + return TRUE; +} + +/* +VOID WINAPI GetSystemTimeAsFileTime(FILETIME *ft) +{ + UInt64 t = 0; + timeval tv; + if (gettimeofday(&tv, NULL) == 0) + { + t = tv.tv_sec * (UInt64)TICKS_PER_SEC + TICKS_1601_TO_1970; + t += tv.tv_usec * 10; + } + SET_FILETIME(ft, t) +} +*/ + +DWORD WINAPI GetTickCount(VOID) +{ + #ifndef _WIN32 + // gettimeofday() doesn't work in some MINGWs by unknown reason + timeval tv; + if (gettimeofday(&tv, NULL) == 0) + { + // tv_sec and tv_usec are (long) + return (DWORD)((UInt64)(Int64)tv.tv_sec * (UInt64)1000 + (UInt64)(Int64)tv.tv_usec / 1000); + } + #endif + return (DWORD)time(NULL) * 1000; +} + + +#define PERIOD_4 (4 * 365 + 1) +#define PERIOD_100 (PERIOD_4 * 25 - 1) +#define PERIOD_400 (PERIOD_100 * 4 + 1) + +BOOL WINAPI FileTimeToSystemTime(const FILETIME *ft, SYSTEMTIME *st) +{ + UInt32 v; + UInt64 v64 = GET_TIME_64(ft); + v64 /= 10000; + st->wMilliseconds = (WORD)(v64 % 1000); v64 /= 1000; + st->wSecond = (WORD)(v64 % 60); v64 /= 60; + st->wMinute = (WORD)(v64 % 60); v64 /= 60; + v = (UInt32)v64; + st->wHour = (WORD)(v % 24); v /= 24; + + // 1601-01-01 was Monday + st->wDayOfWeek = (WORD)((v + 1) % 7); + + UInt32 leaps, year, day, mon; + leaps = (3 * ((4 * v + (365 - 31 - 28) * 4 + 3) / PERIOD_400) + 3) / 4; + v += 28188 + leaps; + // leaps - the number of exceptions from PERIOD_4 rules starting from 1600-03-01 + // (1959 / 64) - converts day from 03-01 to month + year = (20 * v - 2442) / (5 * PERIOD_4); + day = v - (year * PERIOD_4) / 4; + mon = (64 * day) / 1959; + st->wDay = (WORD)(day - (1959 * mon) / 64); + mon -= 1; + year += 1524; + if (mon > 12) + { + mon -= 12; + year++; + } + st->wMonth = (WORD)mon; + st->wYear = (WORD)year; + + /* + unsigned year, mon; + unsigned char ms[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + unsigned t; + + year = (WORD)(1601 + v / PERIOD_400 * 400); + v %= PERIOD_400; + + t = v / PERIOD_100; if (t == 4) t = 3; year += t * 100; v -= t * PERIOD_100; + t = v / PERIOD_4; if (t == 25) t = 24; year += t * 4; v -= t * PERIOD_4; + t = v / 365; if (t == 4) t = 3; year += t; v -= t * 365; + + st->wYear = (WORD)year; + + if (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) + ms[1] = 29; + for (mon = 0;; mon++) + { + unsigned d = ms[mon]; + if (v < d) + break; + v -= d; + } + st->wDay = (WORD)(v + 1); + st->wMonth = (WORD)(mon + 1); + */ + + return TRUE; } #endif diff --git a/src/plugins/7zip/7za/cpp/Common/MyWindows.h b/src/plugins/7zip/7za/cpp/Common/MyWindows.h index 139a4e8b..da5370b0 100644 --- a/src/plugins/7zip/7za/cpp/Common/MyWindows.h +++ b/src/plugins/7zip/7za/cpp/Common/MyWindows.h @@ -1,24 +1,36 @@ // MyWindows.h -#ifndef __MY_WINDOWS_H -#define __MY_WINDOWS_H +#ifdef Z7_DEFINE_GUID +#undef Z7_DEFINE_GUID +#endif -#ifdef _WIN32 +#ifdef INITGUID + #define Z7_DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + EXTERN_C const GUID name; \ + EXTERN_C const GUID name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } } +#else + #define Z7_DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ + EXTERN_C const GUID name +#endif -#include -#ifdef UNDER_CE - #undef VARIANT_TRUE - #define VARIANT_TRUE ((VARIANT_BOOL)-1) -#endif +#ifndef ZIP7_INC_MY_WINDOWS_H +#define ZIP7_INC_MY_WINDOWS_H -#else +#ifdef _WIN32 + +#include "../../C/7zWindows.h" + +#else // _WIN32 #include // for wchar_t #include +// #include // for uintptr_t +#include "../../C/7zTypes.h" #include "MyGuidDef.h" +// WINAPI is __stdcall in Windows-MSVC in windef.h #define WINAPI typedef char CHAR; @@ -34,21 +46,28 @@ typedef unsigned short USHORT; typedef unsigned short WORD; typedef short VARIANT_BOOL; -typedef int INT; -typedef Int32 INT32; -typedef unsigned int UINT; -typedef UInt32 UINT32; -typedef INT32 LONG; // LONG, ULONG and DWORD must be 32-bit -typedef UINT32 ULONG; +#define LOWORD(l) ((WORD)((DWORD_PTR)(l) & 0xffff)) +#define HIWORD(l) ((WORD)((DWORD_PTR)(l) >> 16)) + +// MS uses long for BOOL, but long is 32-bit in MS. So we use int. +// typedef long BOOL; +typedef int BOOL; -#undef DWORD -typedef UINT32 DWORD; +#ifndef FALSE + #define FALSE 0 + #define TRUE 1 +#endif + +// typedef size_t ULONG_PTR; +// typedef size_t DWORD_PTR; +// typedef uintptr_t UINT_PTR; +// typedef ptrdiff_t UINT_PTR; typedef Int64 LONGLONG; typedef UInt64 ULONGLONG; -typedef struct _LARGE_INTEGER { LONGLONG QuadPart; } LARGE_INTEGER; -typedef struct _ULARGE_INTEGER { ULONGLONG QuadPart; } ULARGE_INTEGER; +typedef struct { LONGLONG QuadPart; } LARGE_INTEGER; +typedef struct { ULONGLONG QuadPart; } ULARGE_INTEGER; typedef const CHAR *LPCSTR; typedef CHAR TCHAR; @@ -60,59 +79,111 @@ typedef OLECHAR *BSTR; typedef const OLECHAR *LPCOLESTR; typedef OLECHAR *LPOLESTR; -typedef struct _FILETIME +typedef struct { DWORD dwLowDateTime; DWORD dwHighDateTime; } FILETIME; -#define HRESULT LONG -#define FAILED(Status) ((HRESULT)(Status)<0) +#define SUCCEEDED(hr) ((HRESULT)(hr) >= 0) +#define FAILED(hr) ((HRESULT)(hr) < 0) typedef ULONG PROPID; typedef LONG SCODE; + #define S_OK ((HRESULT)0x00000000L) #define S_FALSE ((HRESULT)0x00000001L) -#define E_NOTIMPL ((HRESULT)0x80004001L) +#define E_NOTIMPL ((HRESULT)0x80004001L) #define E_NOINTERFACE ((HRESULT)0x80004002L) -#define E_ABORT ((HRESULT)0x80004004L) -#define E_FAIL ((HRESULT)0x80004005L) -#define STG_E_INVALIDFUNCTION ((HRESULT)0x80030001L) -#define E_OUTOFMEMORY ((HRESULT)0x8007000EL) -#define E_INVALIDARG ((HRESULT)0x80070057L) +#define E_ABORT ((HRESULT)0x80004004L) +#define E_FAIL ((HRESULT)0x80004005L) +#define STG_E_INVALIDFUNCTION ((HRESULT)0x80030001L) +#define CLASS_E_CLASSNOTAVAILABLE ((HRESULT)0x80040111L) + #ifdef _MSC_VER #define STDMETHODCALLTYPE __stdcall +#define STDAPICALLTYPE __stdcall #else +// do we need __export here? #define STDMETHODCALLTYPE +#define STDAPICALLTYPE +#endif + +#define STDAPI EXTERN_C HRESULT STDAPICALLTYPE + +#ifndef DECLSPEC_NOTHROW +#define DECLSPEC_NOTHROW Z7_DECLSPEC_NOTHROW +#endif + +#ifndef DECLSPEC_NOVTABLE +#define DECLSPEC_NOVTABLE Z7_DECLSPEC_NOVTABLE +#endif + +#ifndef COM_DECLSPEC_NOTHROW +#ifdef COM_STDMETHOD_CAN_THROW + #define COM_DECLSPEC_NOTHROW +#else + #define COM_DECLSPEC_NOTHROW DECLSPEC_NOTHROW +#endif #endif -#define STDMETHOD_(t, f) virtual t STDMETHODCALLTYPE f -#define STDMETHOD(f) STDMETHOD_(HRESULT, f) -#define STDMETHODIMP_(type) type STDMETHODCALLTYPE -#define STDMETHODIMP STDMETHODIMP_(HRESULT) +#define DECLARE_INTERFACE(iface) struct DECLSPEC_NOVTABLE iface +#define DECLARE_INTERFACE_(iface, baseiface) struct DECLSPEC_NOVTABLE iface : public baseiface + +#define STDMETHOD_(t, f) virtual COM_DECLSPEC_NOTHROW t STDMETHODCALLTYPE f +#define STDMETHOD(f) STDMETHOD_(HRESULT, f) +#define STDMETHODIMP_(t) COM_DECLSPEC_NOTHROW t STDMETHODCALLTYPE +#define STDMETHODIMP STDMETHODIMP_(HRESULT) + #define PURE = 0 -#define MIDL_INTERFACE(x) struct +// #define MIDL_INTERFACE(x) struct + #ifdef __cplusplus +/* + p7zip and 7-Zip before v23 used virtual destructor in IUnknown, + if _WIN32 is not defined. + It used virtual destructor, because some compilers don't like virtual + interfaces without virtual destructor. + IUnknown in Windows (_WIN32) doesn't use virtual destructor in IUnknown. + We still can define Z7_USE_VIRTUAL_DESTRUCTOR_IN_IUNKNOWN here, + if we want to be compatible with old plugin interface of p7zip and 7-Zip before v23. + +v23: + In new 7-Zip v23 we try to be more compatible with original IUnknown from _WIN32. + So we do not define Z7_USE_VIRTUAL_DESTRUCTOR_IN_IUNKNOWN here, +*/ +// #define Z7_USE_VIRTUAL_DESTRUCTOR_IN_IUNKNOWN + +#ifdef Z7_USE_VIRTUAL_DESTRUCTOR_IN_IUNKNOWN +#if defined(__clang__) +#pragma GCC diagnostic ignored "-Winconsistent-missing-destructor-override" +#endif +#endif + +Z7_PURE_INTERFACES_BEGIN + DEFINE_GUID(IID_IUnknown, 0x00000000, 0x0000, 0x0000, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46); struct IUnknown { - STDMETHOD(QueryInterface) (REFIID iid, void **outObject) PURE; - STDMETHOD_(ULONG, AddRef)() PURE; - STDMETHOD_(ULONG, Release)() PURE; - #ifndef _WIN32 + STDMETHOD(QueryInterface) (REFIID iid, void **outObject) =0; + STDMETHOD_(ULONG, AddRef)() =0; + STDMETHOD_(ULONG, Release)() =0; + #ifdef Z7_USE_VIRTUAL_DESTRUCTOR_IN_IUNKNOWN virtual ~IUnknown() {} - #endif + #endif }; typedef IUnknown *LPUNKNOWN; -#endif +Z7_PURE_INTERFACES_END + +#endif // __cplusplus #define VARIANT_TRUE ((VARIANT_BOOL)-1) #define VARIANT_FALSE ((VARIANT_BOOL)0) @@ -134,6 +205,7 @@ enum VARENUM VT_VARIANT = 12, VT_UNKNOWN = 13, VT_DECIMAL = 14, + VT_I1 = 16, VT_UI1 = 17, VT_UI2 = 18, @@ -181,8 +253,8 @@ typedef PROPVARIANT tagVARIANT; typedef tagVARIANT VARIANT; typedef VARIANT VARIANTARG; -MY_EXTERN_C HRESULT VariantClear(VARIANTARG *prop); -MY_EXTERN_C HRESULT VariantCopy(VARIANTARG *dest, const VARIANTARG *src); +EXTERN_C HRESULT VariantClear(VARIANTARG *prop); +EXTERN_C HRESULT VariantCopy(VARIANTARG *dest, const VARIANTARG *src); typedef struct tagSTATPROPSTG { @@ -191,15 +263,21 @@ typedef struct tagSTATPROPSTG VARTYPE vt; } STATPROPSTG; -MY_EXTERN_C BSTR SysAllocStringByteLen(LPCSTR psz, UINT len); -MY_EXTERN_C BSTR SysAllocStringLen(const OLECHAR *sz, UINT len); -MY_EXTERN_C BSTR SysAllocString(const OLECHAR *sz); -MY_EXTERN_C void SysFreeString(BSTR bstr); -MY_EXTERN_C UINT SysStringByteLen(BSTR bstr); -MY_EXTERN_C UINT SysStringLen(BSTR bstr); +EXTERN_C BSTR SysAllocStringByteLen(LPCSTR psz, UINT len); +EXTERN_C BSTR SysAllocStringLen(const OLECHAR *sz, UINT len); +EXTERN_C BSTR SysAllocString(const OLECHAR *sz); +EXTERN_C void SysFreeString(BSTR bstr); +EXTERN_C UINT SysStringByteLen(BSTR bstr); +EXTERN_C UINT SysStringLen(BSTR bstr); + +EXTERN_C DWORD GetLastError(); +EXTERN_C void SetLastError(DWORD dwCode); +EXTERN_C LONG CompareFileTime(const FILETIME* ft1, const FILETIME* ft2); + +EXTERN_C DWORD GetCurrentThreadId(); +EXTERN_C DWORD GetCurrentProcessId(); -MY_EXTERN_C DWORD GetLastError(); -MY_EXTERN_C LONG CompareFileTime(const FILETIME* ft1, const FILETIME* ft2); +#define MAX_PATH 1024 #define CP_ACP 0 #define CP_OEMCP 1 @@ -212,5 +290,36 @@ typedef enum tagSTREAM_SEEK STREAM_SEEK_END = 2 } STREAM_SEEK; -#endif + + +typedef struct +{ + WORD wYear; + WORD wMonth; + WORD wDayOfWeek; + WORD wDay; + WORD wHour; + WORD wMinute; + WORD wSecond; + WORD wMilliseconds; +} SYSTEMTIME; + +BOOL WINAPI FileTimeToLocalFileTime(const FILETIME *fileTime, FILETIME *localFileTime); +BOOL WINAPI LocalFileTimeToFileTime(const FILETIME *localFileTime, FILETIME *fileTime); +BOOL WINAPI FileTimeToSystemTime(const FILETIME *fileTime, SYSTEMTIME *systemTime); +// VOID WINAPI GetSystemTimeAsFileTime(FILETIME *systemTimeAsFileTime); + +DWORD GetTickCount(); + + +/* +#define CREATE_NEW 1 +#define CREATE_ALWAYS 2 +#define OPEN_EXISTING 3 +#define OPEN_ALWAYS 4 +#define TRUNCATE_EXISTING 5 +*/ + +#endif // _WIN32 + #endif diff --git a/src/plugins/7zip/7za/cpp/Common/MyXml.cpp b/src/plugins/7zip/7za/cpp/Common/MyXml.cpp index a177e4a2..8364aae4 100644 --- a/src/plugins/7zip/7za/cpp/Common/MyXml.cpp +++ b/src/plugins/7zip/7za/cpp/Common/MyXml.cpp @@ -3,13 +3,14 @@ #include "StdAfx.h" #include "MyXml.h" +#include "StringToInt.h" static bool IsValidChar(char c) { return - c >= 'a' && c <= 'z' || - c >= 'A' && c <= 'Z' || - c >= '0' && c <= '9' || + (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || c == '-'; } @@ -20,35 +21,46 @@ static bool IsSpaceChar(char c) #define SKIP_SPACES(s) while (IsSpaceChar(*s)) s++; -int CXmlItem::FindProp(const AString &propName) const throw() +int CXmlItem::FindProp(const char *propName) const throw() { FOR_VECTOR (i, Props) - if (Props[i].Name == propName) - return i; + if (Props[i].Name.IsEqualTo(propName)) + return (int)i; return -1; } -AString CXmlItem::GetPropVal(const AString &propName) const +AString CXmlItem::GetPropVal(const char *propName) const { - int index = FindProp(propName); + const int index = FindProp(propName); if (index >= 0) - return Props[index].Value; + return Props[(unsigned)index].Value; return AString(); } -bool CXmlItem::IsTagged(const AString &tag) const throw() +bool CXmlItem::IsTagged(const char *tag) const throw() { - return (IsTag && Name == tag); + return (IsTag && Name.IsEqualTo(tag)); } -int CXmlItem::FindSubTag(const AString &tag) const throw() +int CXmlItem::FindSubTag(const char *tag) const throw() { FOR_VECTOR (i, SubItems) if (SubItems[i].IsTagged(tag)) - return i; + return (int)i; return -1; } +const CXmlItem *CXmlItem::FindSubTag_GetPtr(const char *tag) const throw() +{ + FOR_VECTOR (i, SubItems) + { + const CXmlItem *p = &SubItems[i]; + if (p->IsTagged(tag)) + return p; + } + return NULL; +} + AString CXmlItem::GetSubString() const { if (SubItems.Size() == 1) @@ -71,17 +83,17 @@ const AString * CXmlItem::GetSubStringPtr() const throw() return NULL; } -AString CXmlItem::GetSubStringForTag(const AString &tag) const +AString CXmlItem::GetSubStringForTag(const char *tag) const { - int index = FindSubTag(tag); - if (index >= 0) - return SubItems[index].GetSubString(); + const CXmlItem *item = FindSubTag_GetPtr(tag); + if (item) + return item->GetSubString(); return AString(); } const char * CXmlItem::ParseItem(const char *s, int numAllowedLevels) { - SKIP_SPACES(s); + SKIP_SPACES(s) const char *beg = s; for (;;) @@ -92,17 +104,20 @@ const char * CXmlItem::ParseItem(const char *s, int numAllowedLevels) } if (*s == 0) return NULL; - if (s != beg) { - IsTag = false; - Name.SetFrom(beg, (unsigned)(s - beg)); - return s; + const size_t num = (size_t)(s - beg); + if (num) + { + IsTag = false; + Name.SetFrom_Chars_SizeT(beg, num); + return s; + } } IsTag = true; s++; - SKIP_SPACES(s); + SKIP_SPACES(s) beg = s; for (;; s++) @@ -110,16 +125,16 @@ const char * CXmlItem::ParseItem(const char *s, int numAllowedLevels) break; if (s == beg || *s == 0) return NULL; - Name.SetFrom(beg, (unsigned)(s - beg)); + Name.SetFrom_Chars_SizeT(beg, (size_t)(s - beg)); for (;;) { beg = s; - SKIP_SPACES(s); + SKIP_SPACES(s) if (*s == '/') { s++; - // SKIP_SPACES(s); + // SKIP_SPACES(s) if (*s != '>') return NULL; return s + 1; @@ -132,7 +147,7 @@ const char * CXmlItem::ParseItem(const char *s, int numAllowedLevels) SubItems.Clear(); for (;;) { - SKIP_SPACES(s); + SKIP_SPACES(s) if (s[0] == '<' && s[1] == '/') break; CXmlItem &item = SubItems.AddNew(); @@ -142,11 +157,12 @@ const char * CXmlItem::ParseItem(const char *s, int numAllowedLevels) } s += 2; - unsigned len = Name.Len(); + const unsigned len = Name.Len(); + const char *name = Name.Ptr(); for (unsigned i = 0; i < len; i++) - if (s[i] != Name[i]) + if (*s++ != *name++) return NULL; - s += len; + // s += len; if (s[0] != '>') return NULL; return s + 1; @@ -166,13 +182,13 @@ const char * CXmlItem::ParseItem(const char *s, int numAllowedLevels) } if (s == beg) return NULL; - prop.Name.SetFrom(beg, (unsigned)(s - beg)); + prop.Name.SetFrom_Chars_SizeT(beg, (size_t)(s - beg)); - SKIP_SPACES(s); + SKIP_SPACES(s) if (*s != '=') return NULL; s++; - SKIP_SPACES(s); + SKIP_SPACES(s) if (*s != '\"') return NULL; s++; @@ -187,14 +203,14 @@ const char * CXmlItem::ParseItem(const char *s, int numAllowedLevels) break; s++; } - prop.Value.SetFrom(beg, (unsigned)(s - beg)); + prop.Value.SetFrom_Chars_SizeT(beg, (size_t)(s - beg)); s++; } } static const char * SkipHeader(const char *s, const char *startString, const char *endString) { - SKIP_SPACES(s); + SKIP_SPACES(s) if (IsString1PrefixedByString2(s, startString)) { s = strstr(s, endString); @@ -215,7 +231,7 @@ void CXmlItem::AppendTo(AString &s) const FOR_VECTOR (i, Props) { const CXmlProp &prop = Props[i]; - s += ' '; + s.Add_Space(); s += prop.Name; s += '='; s += '\"'; @@ -228,7 +244,7 @@ void CXmlItem::AppendTo(AString &s) const { const CXmlItem &item = SubItems[i]; if (i != 0 && !SubItems[i - 1].IsTag) - s += ' '; + s.Add_Space(); item.AppendTo(s); } if (IsTag) @@ -248,7 +264,7 @@ bool CXml::Parse(const char *s) s = Root.ParseItem(s, 1000); if (!s || !Root.IsTag) return false; - SKIP_SPACES(s); + SKIP_SPACES(s) return *s == 0; } @@ -258,3 +274,76 @@ void CXml::AppendTo(AString &s) const Root.AppendTo(s); } */ + + +void z7_xml_DecodeString(AString &temp) +{ + char * const beg = temp.GetBuf(); + char *dest = beg; + const char *p = beg; + for (;;) + { + char c = *p++; + if (c == 0) + break; + if (c == '&') + { + if (p[0] == '#') + { + const char *end; + const UInt32 number = ConvertStringToUInt32(p + 1, &end); + if (*end == ';' && number != 0 && number <= 127) + { + p = end + 1; + c = (char)number; + } + } + else if ( + p[0] == 'a' && + p[1] == 'm' && + p[2] == 'p' && + p[3] == ';') + { + p += 4; + } + else if ( + p[0] == 'l' && + p[1] == 't' && + p[2] == ';') + { + p += 3; + c = '<'; + } + else if ( + p[0] == 'g' && + p[1] == 't' && + p[2] == ';') + { + p += 3; + c = '>'; + } + else if ( + p[0] == 'a' && + p[1] == 'p' && + p[2] == 'o' && + p[3] == 's' && + p[4] == ';') + { + p += 5; + c = '\''; + } + else if ( + p[0] == 'q' && + p[1] == 'u' && + p[2] == 'o' && + p[3] == 't' && + p[4] == ';') + { + p += 5; + c = '\"'; + } + } + *dest++ = c; + } + temp.ReleaseBuf_SetEnd((unsigned)(dest - beg)); +} diff --git a/src/plugins/7zip/7za/cpp/Common/MyXml.h b/src/plugins/7zip/7za/cpp/Common/MyXml.h index 0e4b21d4..b22d7e40 100644 --- a/src/plugins/7zip/7za/cpp/Common/MyXml.h +++ b/src/plugins/7zip/7za/cpp/Common/MyXml.h @@ -1,7 +1,7 @@ // MyXml.h -#ifndef __MY_XML_H -#define __MY_XML_H +#ifndef ZIP7_INC_MY_XML_H +#define ZIP7_INC_MY_XML_H #include "MyString.h" @@ -21,14 +21,14 @@ class CXmlItem const char * ParseItem(const char *s, int numAllowedLevels); - bool IsTagged(const AString &tag) const throw(); - int FindProp(const AString &propName) const throw(); - AString GetPropVal(const AString &propName) const; + bool IsTagged(const char *tag) const throw(); + int FindProp(const char *propName) const throw(); + AString GetPropVal(const char *propName) const; AString GetSubString() const; const AString * GetSubStringPtr() const throw(); - int FindSubTag(const AString &tag) const throw(); - AString GetSubStringForTag(const AString &tag) const; - + int FindSubTag(const char *tag) const throw(); + const CXmlItem *FindSubTag_GetPtr(const char *tag) const throw(); + AString GetSubStringForTag(const char *tag) const; void AppendTo(AString &s) const; }; @@ -40,4 +40,6 @@ struct CXml // void AppendTo(AString &s) const; }; +void z7_xml_DecodeString(AString &s); + #endif diff --git a/src/plugins/7zip/7za/cpp/Common/NewHandler.cpp b/src/plugins/7zip/7za/cpp/Common/NewHandler.cpp index 522c2a75..173b8ebe 100644 --- a/src/plugins/7zip/7za/cpp/Common/NewHandler.cpp +++ b/src/plugins/7zip/7za/cpp/Common/NewHandler.cpp @@ -10,21 +10,23 @@ #ifndef DEBUG_MEMORY_LEAK -#ifdef _WIN32 +#ifdef Z7_REDEFINE_OPERATOR_NEW /* void * my_new(size_t size) { // void *p = ::HeapAlloc(::GetProcessHeap(), 0, size); + if (size == 0) + size = 1; void *p = ::malloc(size); - if (p == 0) + if (!p) throw CNewException(); return p; } void my_delete(void *p) throw() { - // if (p == 0) return; ::HeapFree(::GetProcessHeap(), 0, p); + // if (!p) return; ::HeapFree(::GetProcessHeap(), 0, p); ::free(p); } @@ -44,23 +46,64 @@ __cdecl #endif operator new(size_t size) { + /* by C++ specification: + if (size == 0), operator new(size) returns non_NULL pointer. + If (operator new(0) returns NULL), it's out of specification. + but some calling code can work correctly even in this case too. */ + // if (size == 0) return NULL; // for debug only. don't use it + + /* malloc(0) returns non_NULL in main compilers, as we need here. + But specification also allows malloc(0) to return NULL. + So we change (size=0) to (size=1) here to get real non_NULL pointer */ + if (size == 0) + size = 1; // void *p = ::HeapAlloc(::GetProcessHeap(), 0, size); + // void *p = ::MyAlloc(size); // note: MyAlloc(0) returns NULL void *p = ::malloc(size); - if (p == 0) + if (!p) throw CNewException(); return p; } + +#if defined(_MSC_VER) && _MSC_VER == 1600 +// vs2010 has no throw() by default ? +#pragma warning(push) +#pragma warning(disable : 4986) // 'operator delete': exception specification does not match previous declaration +#endif + void #ifdef _MSC_VER __cdecl #endif operator delete(void *p) throw() { - // if (p == 0) return; ::HeapFree(::GetProcessHeap(), 0, p); + // if (!p) return; ::HeapFree(::GetProcessHeap(), 0, p); + // MyFree(p); + ::free(p); +} + +/* we define operator delete(void *p, size_t n) because + vs2022 compiler uses delete(void *p, size_t n), and + we want to mix files from different compilers: + - old vc6 linker + - old vc6 complier + - new vs2022 complier +*/ +void +#ifdef _MSC_VER +__cdecl +#endif +operator delete(void *p, size_t n) throw() +{ + UNUSED_VAR(n) ::free(p); } +#if defined(_MSC_VER) && _MSC_VER == 1600 +#pragma warning(pop) +#endif + /* void * #ifdef _MSC_VER @@ -69,8 +112,10 @@ __cdecl operator new[](size_t size) { // void *p = ::HeapAlloc(::GetProcessHeap(), 0, size); + if (size == 0) + size = 1; void *p = ::malloc(size); - if (p == 0) + if (!p) throw CNewException(); return p; } @@ -81,7 +126,7 @@ __cdecl #endif operator delete[](void *p) throw() { - // if (p == 0) return; ::HeapFree(::GetProcessHeap(), 0, p); + // if (!p) return; ::HeapFree(::GetProcessHeap(), 0, p); ::free(p); } */ @@ -93,25 +138,10 @@ operator delete[](void *p) throw() #include // #pragma init_seg(lib) +/* const int kDebugSize = 1000000; static void *a[kDebugSize]; -static int index = 0; - -static int numAllocs = 0; -void * __cdecl operator new(size_t size) -{ - numAllocs++; - void *p = HeapAlloc(GetProcessHeap(), 0, size); - if (index < kDebugSize) - { - a[index] = p; - index++; - } - if (p == 0) - throw CNewException(); - printf("Alloc %6d, size = %8u\n", numAllocs, (unsigned)size); - return p; -} +static int g_index = 0; class CC { @@ -123,27 +153,174 @@ class CC } ~CC() { + printf("\nDestructor: %d\n", numAllocs); for (int i = 0; i < kDebugSize; i++) if (a[i] != 0) return; } } g_CC; +*/ +#ifdef _WIN32 +static bool wasInit = false; +static CRITICAL_SECTION cs; +#endif -void __cdecl operator delete(void *p) +static int numAllocs = 0; + +void * +#ifdef _MSC_VER +__cdecl +#endif +operator new(size_t size) { - if (p == 0) + #ifdef _WIN32 + if (!wasInit) + { + InitializeCriticalSection(&cs); + wasInit = true; + } + EnterCriticalSection(&cs); + + numAllocs++; + int loc = numAllocs; + void *p = HeapAlloc(GetProcessHeap(), 0, size); + /* + if (g_index < kDebugSize) + { + a[g_index] = p; + g_index++; + } + */ + printf("Alloc %6d, size = %8u\n", loc, (unsigned)size); + LeaveCriticalSection(&cs); + if (!p) + throw CNewException(); + return p; + #else + numAllocs++; + int loc = numAllocs; + if (size == 0) + size = 1; + void *p = malloc(size); + /* + if (g_index < kDebugSize) + { + a[g_index] = p; + g_index++; + } + */ + printf("Alloc %6d, size = %8u\n", loc, (unsigned)size); + if (!p) + throw CNewException(); + return p; + #endif +} + +void +#ifdef _MSC_VER +__cdecl +#endif +operator delete(void *p) throw() +{ + if (!p) return; + #ifdef _WIN32 + EnterCriticalSection(&cs); /* - for (int i = 0; i < index; i++) + for (int i = 0; i < g_index; i++) if (a[i] == p) a[i] = 0; */ HeapFree(GetProcessHeap(), 0, p); + // if (numAllocs == 0) numAllocs = numAllocs; // ERROR + numAllocs--; + // if (numAllocs == 0) numAllocs = numAllocs; // OK: all objects were deleted + printf("Free %d\n", numAllocs); + LeaveCriticalSection(&cs); + #else + free(p); numAllocs--; printf("Free %d\n", numAllocs); + #endif +} + +void +#ifdef _MSC_VER +__cdecl +#endif +operator delete(void *p, size_t n) throw(); +void +#ifdef _MSC_VER +__cdecl +#endif +operator delete(void *p, size_t n) throw() +{ + UNUSED_VAR(n) + printf("delete_WITH_SIZE=%u, ptr = %p\n", (unsigned)n, p); + operator delete(p); +} + +/* +void * +#ifdef _MSC_VER +__cdecl +#endif +operator new[](size_t size) +{ + printf("operator_new[] : "); + return operator new(size); +} + +void +#ifdef _MSC_VER +__cdecl +#endif +operator delete(void *p, size_t sz) throw(); + +void +#ifdef _MSC_VER +__cdecl +#endif +operator delete(void *p, size_t sz) throw() +{ + if (!p) + return; + printf("operator_delete_size : size=%d : ", (unsigned)sz); + operator delete(p); +} + +void +#ifdef _MSC_VER +__cdecl +#endif +operator delete[](void *p) throw() +{ + if (!p) + return; + printf("operator_delete[] : "); + operator delete(p); } +void +#ifdef _MSC_VER +__cdecl +#endif +operator delete[](void *p, size_t sz) throw(); + +void +#ifdef _MSC_VER +__cdecl +#endif +operator delete[](void *p, size_t sz) throw() +{ + if (!p) + return; + printf("operator_delete_size[] : size=%d : ", (unsigned)sz); + operator delete(p); +} +*/ + #endif /* diff --git a/src/plugins/7zip/7za/cpp/Common/NewHandler.h b/src/plugins/7zip/7za/cpp/Common/NewHandler.h index 84f59736..5ba64b7d 100644 --- a/src/plugins/7zip/7za/cpp/Common/NewHandler.h +++ b/src/plugins/7zip/7za/cpp/Common/NewHandler.h @@ -1,31 +1,42 @@ // Common/NewHandler.h -#ifndef __COMMON_NEW_HANDLER_H -#define __COMMON_NEW_HANDLER_H +#ifndef ZIP7_INC_COMMON_NEW_HANDLER_H +#define ZIP7_INC_COMMON_NEW_HANDLER_H /* -This file must be included before any code that uses operators "delete" or "new". -Also you must compile and link "NewHandler.cpp", if you use MSVC 6.0. -The operator "new" in MSVC 6.0 doesn't throw exception "bad_alloc". -So we define another version of operator "new" that throws "CNewException" on failure. +NewHandler.h and NewHandler.cpp allows to solve problem with compilers that +don't throw exception in operator new(). -If you use compiler that throws exception in "new" operator (GCC or new version of MSVC), -you can compile without "NewHandler.cpp". So standard exception "bad_alloc" will be used. +This file must be included before any code that uses operators new() or delete() +and you must compile and link "NewHandler.cpp", if you use some old MSVC compiler. -It's still allowed to use redefined version of operator "new" from "NewHandler.cpp" -with any compiler. 7-Zip's code can work with "bad_alloc" and "CNewException" exceptions. -But if you use some additional code (outside of 7-Zip's code), you must check -that redefined version of operator "new" (that throws CNewException) is not -problem for your code. +DOCs: + Since ISO C++98, operator new throws std::bad_alloc when memory allocation fails. + MSVC 6.0 returned a null pointer on an allocation failure. + Beginning in VS2002, operator new conforms to the standard and throws on failure. + + By default, the compiler also generates defensive null checks to prevent + these older-style allocators from causing an immediate crash on failure. + The /Zc:throwingNew option tells the compiler to leave out these null checks, + on the assumption that all linked memory allocators conform to the standard. + +The operator new() in some MSVC versions doesn't throw exception std::bad_alloc. +MSVC 6.0 (_MSC_VER == 1200) doesn't throw exception. +The code produced by some another MSVC compilers also can be linked +to library that doesn't throw exception. +We suppose that code compiled with VS2015+ (_MSC_VER >= 1900) throws exception std::bad_alloc. +For older _MSC_VER versions we redefine operator new() and operator delete(). +Our version of operator new() throws CNewException() exception on failure. -Also we declare delete(void *p) throw() that creates smaller code. +It's still allowed to use redefined version of operator new() from "NewHandler.cpp" +with any compiler. 7-Zip's code can work with std::bad_alloc and CNewException() exceptions. +But if you use some additional code (outside of 7-Zip's code), you must check +that redefined version of operator new() is not problem for your code. */ #include -class CNewException {}; - -#ifdef WIN32 +#ifdef _WIN32 // We can compile my_new and my_delete with _fastcall /* void * my_new(size_t size); @@ -34,21 +45,61 @@ void my_delete(void *p) throw(); */ #endif -#ifdef _WIN32 -// JRYFIXME - can we afford to suppress this? Igor notes above that we probably can. -/* + +#if defined(_MSC_VER) && (_MSC_VER < 1600) + // If you want to use default operator new(), you can disable the following line + #define Z7_REDEFINE_OPERATOR_NEW +#endif + + +#ifdef Z7_REDEFINE_OPERATOR_NEW + +// std::bad_alloc can require additional DLL dependency. +// So we don't define CNewException as std::bad_alloc here. + +class CNewException {}; + void * #ifdef _MSC_VER __cdecl #endif operator new(size_t size); +/* +#if 0 && defined(_MSC_VER) && _MSC_VER == 1600 + #define Z7_OPERATOR_DELETE_SPEC_THROW0 +#else + #define Z7_OPERATOR_DELETE_SPEC_THROW0 throw() +#endif +*/ +#if defined(_MSC_VER) && _MSC_VER == 1600 +#pragma warning(push) +#pragma warning(disable : 4986) // 'operator delete': exception specification does not match previous declaration +#endif + void #ifdef _MSC_VER __cdecl #endif operator delete(void *p) throw(); -*/ + +void +#ifdef _MSC_VER +__cdecl +#endif +operator delete(void *p, size_t n) throw(); + +#if defined(_MSC_VER) && _MSC_VER == 1600 +#pragma warning(pop) +#endif + + +#else + +#include + +#define CNewException std::bad_alloc + #endif /* diff --git a/src/plugins/7zip/7za/cpp/Common/Random.cpp b/src/plugins/7zip/7za/cpp/Common/Random.cpp index fbdb8c97..6ef9f1af 100644 --- a/src/plugins/7zip/7za/cpp/Common/Random.cpp +++ b/src/plugins/7zip/7za/cpp/Common/Random.cpp @@ -12,11 +12,11 @@ #include "Random.h" -void CRandom::Init(unsigned int seed) { srand(seed); } +void CRandom::Init(unsigned seed) { srand(seed); } void CRandom::Init() { - Init((unsigned int) + Init((unsigned) #ifdef _WIN32 GetTickCount() #else diff --git a/src/plugins/7zip/7za/cpp/Common/Random.h b/src/plugins/7zip/7za/cpp/Common/Random.h index e784e984..3fbb416a 100644 --- a/src/plugins/7zip/7za/cpp/Common/Random.h +++ b/src/plugins/7zip/7za/cpp/Common/Random.h @@ -1,13 +1,13 @@ // Common/Random.h -#ifndef __COMMON_RANDOM_H -#define __COMMON_RANDOM_H +#ifndef ZIP7_INC_COMMON_RANDOM_H +#define ZIP7_INC_COMMON_RANDOM_H class CRandom { public: void Init(); - void Init(unsigned int seed); + void Init(unsigned seed); int Generate() const; }; diff --git a/src/plugins/7zip/7za/cpp/Common/Sha1Prepare.cpp b/src/plugins/7zip/7za/cpp/Common/Sha1Prepare.cpp new file mode 100644 index 00000000..2652f009 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/Common/Sha1Prepare.cpp @@ -0,0 +1,7 @@ +// Sha1Prepare.cpp + +#include "StdAfx.h" + +#include "../../C/Sha1.h" + +static struct CSha1Prepare { CSha1Prepare() { Sha1Prepare(); } } g_Sha1Prepare; diff --git a/src/plugins/7zip/7za/cpp/Common/Sha1Reg.cpp b/src/plugins/7zip/7za/cpp/Common/Sha1Reg.cpp index 1400c989..64eef34a 100644 --- a/src/plugins/7zip/7za/cpp/Common/Sha1Reg.cpp +++ b/src/plugins/7zip/7za/cpp/Common/Sha1Reg.cpp @@ -4,37 +4,64 @@ #include "../../C/Sha1.h" +#include "../Common/MyBuffer2.h" #include "../Common/MyCom.h" #include "../7zip/Common/RegisterCodec.h" -class CSha1Hasher: - public IHasher, - public CMyUnknownImp -{ - CSha1 _sha; - Byte mtDummy[1 << 7]; +Z7_CLASS_IMP_COM_2( + CSha1Hasher + , IHasher + , ICompressSetCoderProperties +) + CAlignedBuffer1 _buf; +public: + Byte _mtDummy[1 << 7]; + CSha1 *Sha() { return (CSha1 *)(void *)(Byte *)_buf; } public: - CSha1Hasher() { Sha1_Init(&_sha); } - - MY_UNKNOWN_IMP1(IHasher) - INTERFACE_IHasher(;) + CSha1Hasher(): + _buf(sizeof(CSha1)) + { + Sha1_SetFunction(Sha(), 0); + Sha1_InitState(Sha()); + } }; -STDMETHODIMP_(void) CSha1Hasher::Init() throw() +Z7_COM7F_IMF2(void, CSha1Hasher::Init()) { - Sha1_Init(&_sha); + Sha1_InitState(Sha()); } -STDMETHODIMP_(void) CSha1Hasher::Update(const void *data, UInt32 size) throw() +Z7_COM7F_IMF2(void, CSha1Hasher::Update(const void *data, UInt32 size)) { - Sha1_Update(&_sha, (const Byte *)data, size); + Sha1_Update(Sha(), (const Byte *)data, size); } -STDMETHODIMP_(void) CSha1Hasher::Final(Byte *digest) throw() +Z7_COM7F_IMF2(void, CSha1Hasher::Final(Byte *digest)) +{ + Sha1_Final(Sha(), digest); +} + + +Z7_COM7F_IMF(CSha1Hasher::SetCoderProperties(const PROPID *propIDs, const PROPVARIANT *coderProps, UInt32 numProps)) { - Sha1_Final(&_sha, digest); + unsigned algo = 0; + for (UInt32 i = 0; i < numProps; i++) + { + if (propIDs[i] == NCoderPropID::kDefaultProp) + { + const PROPVARIANT &prop = coderProps[i]; + if (prop.vt != VT_UI4) + return E_INVALIDARG; + if (prop.ulVal > 2) + return E_NOTIMPL; + algo = (unsigned)prop.ulVal; + } + } + if (!Sha1_SetFunction(Sha(), algo)) + return E_NOTIMPL; + return S_OK; } REGISTER_HASHER(CSha1Hasher, 0x201, "SHA1", SHA1_DIGEST_SIZE) diff --git a/src/plugins/7zip/7za/cpp/Common/Sha256Prepare.cpp b/src/plugins/7zip/7za/cpp/Common/Sha256Prepare.cpp new file mode 100644 index 00000000..1ec242b5 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/Common/Sha256Prepare.cpp @@ -0,0 +1,7 @@ +// Sha256Prepare.cpp + +#include "StdAfx.h" + +#include "../../C/Sha256.h" + +static struct CSha256Prepare { CSha256Prepare() { Sha256Prepare(); } } g_Sha256Prepare; diff --git a/src/plugins/7zip/7za/cpp/Common/Sha256Reg.cpp b/src/plugins/7zip/7za/cpp/Common/Sha256Reg.cpp index 66941699..b5689c42 100644 --- a/src/plugins/7zip/7za/cpp/Common/Sha256Reg.cpp +++ b/src/plugins/7zip/7za/cpp/Common/Sha256Reg.cpp @@ -4,37 +4,64 @@ #include "../../C/Sha256.h" +#include "../Common/MyBuffer2.h" #include "../Common/MyCom.h" #include "../7zip/Common/RegisterCodec.h" -class CSha256Hasher: - public IHasher, - public CMyUnknownImp -{ - CSha256 _sha; - Byte mtDummy[1 << 7]; - +Z7_CLASS_IMP_COM_2( + CSha256Hasher + , IHasher + , ICompressSetCoderProperties +) + CAlignedBuffer1 _buf; public: - CSha256Hasher() { Sha256_Init(&_sha); } + Byte _mtDummy[1 << 7]; - MY_UNKNOWN_IMP1(IHasher) - INTERFACE_IHasher(;) + CSha256 *Sha() { return (CSha256 *)(void *)(Byte *)_buf; } +public: + CSha256Hasher(): + _buf(sizeof(CSha256)) + { + Sha256_SetFunction(Sha(), 0); + Sha256_InitState(Sha()); + } }; -STDMETHODIMP_(void) CSha256Hasher::Init() throw() +Z7_COM7F_IMF2(void, CSha256Hasher::Init()) { - Sha256_Init(&_sha); + Sha256_InitState(Sha()); } -STDMETHODIMP_(void) CSha256Hasher::Update(const void *data, UInt32 size) throw() +Z7_COM7F_IMF2(void, CSha256Hasher::Update(const void *data, UInt32 size)) { - Sha256_Update(&_sha, (const Byte *)data, size); + Sha256_Update(Sha(), (const Byte *)data, size); } -STDMETHODIMP_(void) CSha256Hasher::Final(Byte *digest) throw() +Z7_COM7F_IMF2(void, CSha256Hasher::Final(Byte *digest)) +{ + Sha256_Final(Sha(), digest); +} + + +Z7_COM7F_IMF(CSha256Hasher::SetCoderProperties(const PROPID *propIDs, const PROPVARIANT *coderProps, UInt32 numProps)) { - Sha256_Final(&_sha, digest); + unsigned algo = 0; + for (UInt32 i = 0; i < numProps; i++) + { + if (propIDs[i] == NCoderPropID::kDefaultProp) + { + const PROPVARIANT &prop = coderProps[i]; + if (prop.vt != VT_UI4) + return E_INVALIDARG; + if (prop.ulVal > 2) + return E_NOTIMPL; + algo = (unsigned)prop.ulVal; + } + } + if (!Sha256_SetFunction(Sha(), algo)) + return E_NOTIMPL; + return S_OK; } REGISTER_HASHER(CSha256Hasher, 0xA, "SHA256", SHA256_DIGEST_SIZE) diff --git a/src/plugins/7zip/7za/cpp/Common/Sha3Reg.cpp b/src/plugins/7zip/7za/cpp/Common/Sha3Reg.cpp new file mode 100644 index 00000000..cd2e288a --- /dev/null +++ b/src/plugins/7zip/7za/cpp/Common/Sha3Reg.cpp @@ -0,0 +1,76 @@ +// Sha3Reg.cpp + +#include "StdAfx.h" + +#include "../../C/Sha3.h" + +#include "../Common/MyBuffer2.h" +#include "../Common/MyCom.h" + +#include "../7zip/Common/RegisterCodec.h" + +Z7_CLASS_IMP_COM_1( + CSha3Hasher + , IHasher +) + unsigned _digestSize; + bool _isShake; + CAlignedBuffer1 _buf; +public: + Byte _mtDummy[1 << 7]; + + CSha3 *Sha() { return (CSha3 *)(void *)(Byte *)_buf; } +public: + CSha3Hasher(unsigned digestSize, bool isShake, unsigned blockSize): + _digestSize(digestSize), + _isShake(isShake), + _buf(sizeof(CSha3)) + { + CSha3 *p = Sha(); + Sha3_SET_blockSize(p, blockSize) + Sha3_Init(Sha()); + } +}; + +Z7_COM7F_IMF2(void, CSha3Hasher::Init()) +{ + Sha3_Init(Sha()); +} + +Z7_COM7F_IMF2(void, CSha3Hasher::Update(const void *data, UInt32 size)) +{ + Sha3_Update(Sha(), (const Byte *)data, size); +} + +Z7_COM7F_IMF2(void, CSha3Hasher::Final(Byte *digest)) +{ + Sha3_Final(Sha(), digest, _digestSize, _isShake); +} + +Z7_COM7F_IMF2(UInt32, CSha3Hasher::GetDigestSize()) +{ + return (UInt32)_digestSize; +} + + +#define REGISTER_SHA3_HASHER_2(cls, id, name, digestSize, isShake, digestSize_for_blockSize) \ + namespace N ## cls { \ + static IHasher *CreateHasherSpec() \ + { return new CSha3Hasher(digestSize / 8, isShake, \ + SHA3_BLOCK_SIZE_FROM_DIGEST_SIZE(digestSize_for_blockSize / 8)); } \ + static const CHasherInfo g_HasherInfo = { CreateHasherSpec, id, name, digestSize / 8 }; \ + struct REGISTER_HASHER_NAME(cls) { REGISTER_HASHER_NAME(cls)() { RegisterHasher(&g_HasherInfo); }}; \ + static REGISTER_HASHER_NAME(cls) g_RegisterHasher; } + +#define REGISTER_SHA3_HASHER( cls, id, name, size, isShake) \ + REGISTER_SHA3_HASHER_2(cls, id, name, size, isShake, size) + +// REGISTER_SHA3_HASHER (Sha3_224_Hasher, 0x230, "SHA3-224", 224, false) +REGISTER_SHA3_HASHER (Sha3_256_Hasher, 0x231, "SHA3-256", 256, false) +// REGISTER_SHA3_HASHER (Sha3_386_Hasher, 0x232, "SHA3-384", 384, false) +// REGISTER_SHA3_HASHER (Sha3_512_Hasher, 0x233, "SHA3-512", 512, false) +// REGISTER_SHA3_HASHER (Shake128_Hasher, 0x240, "SHAKE128", 128, true) +// REGISTER_SHA3_HASHER (Shake256_Hasher, 0x241, "SHAKE256", 256, true) +// REGISTER_SHA3_HASHER_2 (Shake128_512_Hasher, 0x248, "SHAKE128-256", 256, true, 128) // -1344 (max) +// REGISTER_SHA3_HASHER_2 (Shake256_512_Hasher, 0x249, "SHAKE256-512", 512, true, 256) // -1088 (max) +// Shake supports different digestSize values for same blockSize diff --git a/src/plugins/7zip/7za/cpp/Common/Sha512Prepare.cpp b/src/plugins/7zip/7za/cpp/Common/Sha512Prepare.cpp new file mode 100644 index 00000000..e7beff51 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/Common/Sha512Prepare.cpp @@ -0,0 +1,7 @@ +// Sha512Prepare.cpp + +#include "StdAfx.h" + +#include "../../C/Sha512.h" + +static struct CSha512Prepare { CSha512Prepare() { Sha512Prepare(); } } g_Sha512Prepare; diff --git a/src/plugins/7zip/7za/cpp/Common/Sha512Reg.cpp b/src/plugins/7zip/7za/cpp/Common/Sha512Reg.cpp new file mode 100644 index 00000000..21df6baa --- /dev/null +++ b/src/plugins/7zip/7za/cpp/Common/Sha512Reg.cpp @@ -0,0 +1,83 @@ +// Sha512Reg.cpp + +#include "StdAfx.h" + +#include "../../C/Sha512.h" + +#include "../Common/MyBuffer2.h" +#include "../Common/MyCom.h" + +#include "../7zip/Common/RegisterCodec.h" + +Z7_CLASS_IMP_COM_2( + CSha512Hasher + , IHasher + , ICompressSetCoderProperties +) + unsigned _digestSize; + CAlignedBuffer1 _buf; +public: + Byte _mtDummy[1 << 7]; + + CSha512 *Sha() { return (CSha512 *)(void *)(Byte *)_buf; } +public: + CSha512Hasher(unsigned digestSize): + _digestSize(digestSize), + _buf(sizeof(CSha512)) + { + Sha512_SetFunction(Sha(), 0); + Sha512_InitState(Sha(), _digestSize); + } +}; + +Z7_COM7F_IMF2(void, CSha512Hasher::Init()) +{ + Sha512_InitState(Sha(), _digestSize); +} + +Z7_COM7F_IMF2(void, CSha512Hasher::Update(const void *data, UInt32 size)) +{ + Sha512_Update(Sha(), (const Byte *)data, size); +} + +Z7_COM7F_IMF2(void, CSha512Hasher::Final(Byte *digest)) +{ + Sha512_Final(Sha(), digest, _digestSize); +} + +Z7_COM7F_IMF2(UInt32, CSha512Hasher::GetDigestSize()) +{ + return (UInt32)_digestSize; +} + +Z7_COM7F_IMF(CSha512Hasher::SetCoderProperties(const PROPID *propIDs, const PROPVARIANT *coderProps, UInt32 numProps)) +{ + unsigned algo = 0; + for (UInt32 i = 0; i < numProps; i++) + { + if (propIDs[i] == NCoderPropID::kDefaultProp) + { + const PROPVARIANT &prop = coderProps[i]; + if (prop.vt != VT_UI4) + return E_INVALIDARG; + if (prop.ulVal > 2) + return E_NOTIMPL; + algo = (unsigned)prop.ulVal; + } + } + if (!Sha512_SetFunction(Sha(), algo)) + return E_NOTIMPL; + return S_OK; +} + +#define REGISTER_SHA512_HASHER(cls, id, name, size) \ + namespace N ## cls { \ + static IHasher *CreateHasherSpec() { return new CSha512Hasher(size); } \ + static const CHasherInfo g_HasherInfo = { CreateHasherSpec, id, name, size }; \ + struct REGISTER_HASHER_NAME(cls) { REGISTER_HASHER_NAME(cls)() { RegisterHasher(&g_HasherInfo); }}; \ + static REGISTER_HASHER_NAME(cls) g_RegisterHasher; } + +// REGISTER_SHA512_HASHER (Sha512_224_Hasher, 0x220, "SHA512-224", SHA512_224_DIGEST_SIZE) +// REGISTER_SHA512_HASHER (Sha512_256_Hasher, 0x221, "SHA512-256", SHA512_256_DIGEST_SIZE) +REGISTER_SHA512_HASHER (Sha384Hasher, 0x222, "SHA384", SHA512_384_DIGEST_SIZE) +REGISTER_SHA512_HASHER (Sha512Hasher, 0x223, "SHA512", SHA512_DIGEST_SIZE) diff --git a/src/plugins/7zip/7za/cpp/Common/StdAfx.h b/src/plugins/7zip/7za/cpp/Common/StdAfx.h index 420f5c32..a5228b0a 100644 --- a/src/plugins/7zip/7za/cpp/Common/StdAfx.h +++ b/src/plugins/7zip/7za/cpp/Common/StdAfx.h @@ -1,7 +1,7 @@ // StdAfx.h -#ifndef __STDAFX_H -#define __STDAFX_H +#ifndef ZIP7_INC_STDAFX_H +#define ZIP7_INC_STDAFX_H #include "Common.h" diff --git a/src/plugins/7zip/7za/cpp/Common/StdInStream.cpp b/src/plugins/7zip/7za/cpp/Common/StdInStream.cpp index c35747bb..944be344 100644 --- a/src/plugins/7zip/7za/cpp/Common/StdInStream.cpp +++ b/src/plugins/7zip/7za/cpp/Common/StdInStream.cpp @@ -2,28 +2,34 @@ #include "StdAfx.h" +#ifdef _WIN32 #include +#endif #include "StdInStream.h" #include "StringConvert.h" #include "UTFConvert.h" -static const char kNewLineChar = '\n'; +// #define kEOFMessage "Unexpected end of input stream" +// #define kReadErrorMessage "Error reading input stream" +// #define kIllegalCharMessage "Illegal zero character in input stream" -static const char *kEOFMessage = "Unexpected end of input stream"; -static const char *kReadErrorMessage ="Error reading input stream"; -static const char *kIllegalCharMessage = "Illegal character in input stream"; - -static LPCTSTR kFileOpenMode = TEXT("r"); - -extern int g_CodePage; CStdInStream g_StdIn(stdin); +/* +#define kFileOpenMode TEXT("r") + bool CStdInStream::Open(LPCTSTR fileName) throw() { Close(); - _stream = _tfopen(fileName, kFileOpenMode); + _stream = + #ifdef _WIN32 + _tfopen + #else + fopen + #endif + (fileName, kFileOpenMode); _streamIsOpen = (_stream != 0); return _streamIsOpen; } @@ -35,60 +41,58 @@ bool CStdInStream::Close() throw() _streamIsOpen = (fclose(_stream) != 0); return !_streamIsOpen; } +*/ -AString CStdInStream::ScanStringUntilNewLine(bool allowEOF) +bool CStdInStream::ScanAStringUntilNewLine(AString &s) { - AString s; + s.Empty(); for (;;) { - int intChar = GetChar(); + const int intChar = GetChar(); if (intChar == EOF) - { - if (allowEOF) - break; - throw kEOFMessage; - } - char c = (char)intChar; + return true; + const char c = (char)intChar; if (c == 0) - throw kIllegalCharMessage; - if (c == kNewLineChar) - break; - s += c; + return false; + if (c == '\n') + return true; + s.Add_Char(c); } - return s; } -UString CStdInStream::ScanUStringUntilNewLine() +bool CStdInStream::ScanUStringUntilNewLine(UString &dest) { - AString s = ScanStringUntilNewLine(true); - int codePage = g_CodePage; + dest.Empty(); + AString s; + const bool res = ScanAStringUntilNewLine(s); + int codePage = CodePage; if (codePage == -1) codePage = CP_OEMCP; - UString dest; - if (codePage == CP_UTF8) + if ((unsigned)codePage == CP_UTF8) ConvertUTF8ToUnicode(s, dest); else - dest = MultiByteToUnicodeString(s, (UINT)codePage); - return dest; + MultiByteToUnicodeString2(dest, s, (UINT)(unsigned)codePage); + return res; } -void CStdInStream::ReadToString(AString &resultString) +/* +bool CStdInStream::ReadToString(AString &resultString) { resultString.Empty(); - int c; - while ((c = GetChar()) != EOF) - resultString += (char)c; -} - -bool CStdInStream::Eof() throw() -{ - return (feof(_stream) != 0); + for (;;) + { + int intChar = GetChar(); + if (intChar == EOF) + return !Error(); + char c = (char)intChar; + if (c == 0) + return false; + resultString += c; + } } +*/ int CStdInStream::GetChar() { - int c = fgetc(_stream); // getc() doesn't work in BeOS? - if (c == EOF && !Eof()) - throw kReadErrorMessage; - return c; + return fgetc(_stream); // getc() doesn't work in BeOS? } diff --git a/src/plugins/7zip/7za/cpp/Common/StdInStream.h b/src/plugins/7zip/7za/cpp/Common/StdInStream.h index 6d9ed670..81ca3bf6 100644 --- a/src/plugins/7zip/7za/cpp/Common/StdInStream.h +++ b/src/plugins/7zip/7za/cpp/Common/StdInStream.h @@ -1,7 +1,7 @@ // Common/StdInStream.h -#ifndef __COMMON_STD_IN_STREAM_H -#define __COMMON_STD_IN_STREAM_H +#ifndef ZIP7_INC_COMMON_STD_IN_STREAM_H +#define ZIP7_INC_COMMON_STD_IN_STREAM_H #include @@ -11,20 +11,33 @@ class CStdInStream { FILE *_stream; - bool _streamIsOpen; + // bool _streamIsOpen; public: - CStdInStream(): _stream(0), _streamIsOpen(false) {}; - CStdInStream(FILE *stream): _stream(stream), _streamIsOpen(false) {}; + int CodePage; + + CStdInStream(FILE *stream = NULL): + _stream(stream), + // _streamIsOpen(false), + CodePage(-1) + {} + + /* ~CStdInStream() { Close(); } bool Open(LPCTSTR fileName) throw(); bool Close() throw(); + */ + + // returns: + // false, if ZERO character in stream + // true, if EOF or '\n' + bool ScanAStringUntilNewLine(AString &s); + bool ScanUStringUntilNewLine(UString &s); + // bool ReadToString(AString &resultString); - AString ScanStringUntilNewLine(bool allowEOF = false); - void ReadToString(AString &resultString); - UString ScanUStringUntilNewLine(); + bool Eof() const throw() { return (feof(_stream) != 0); } + bool Error() const throw() { return (ferror(_stream) != 0); } - bool Eof() throw(); int GetChar(); }; diff --git a/src/plugins/7zip/7za/cpp/Common/StdOutStream.cpp b/src/plugins/7zip/7za/cpp/Common/StdOutStream.cpp index 6aed31a3..897ae609 100644 --- a/src/plugins/7zip/7za/cpp/Common/StdOutStream.cpp +++ b/src/plugins/7zip/7za/cpp/Common/StdOutStream.cpp @@ -2,22 +2,21 @@ #include "StdAfx.h" +#ifdef _WIN32 #include +#endif #include "IntToString.h" #include "StdOutStream.h" #include "StringConvert.h" #include "UTFConvert.h" -static const char kNewLineChar = '\n'; - -static const char *kFileOpenMode = "wt"; - -extern int g_CodePage; - CStdOutStream g_StdOut(stdout); CStdOutStream g_StdErr(stderr); +/* +// #define kFileOpenMode "wt" + bool CStdOutStream::Open(const char *fileName) throw() { Close(); @@ -36,6 +35,7 @@ bool CStdOutStream::Close() throw() _streamIsOpen = false; return true; } +*/ bool CStdOutStream::Flush() throw() { @@ -44,37 +44,202 @@ bool CStdOutStream::Flush() throw() CStdOutStream & endl(CStdOutStream & outStream) throw() { - return outStream << kNewLineChar; + return outStream << '\n'; } CStdOutStream & CStdOutStream::operator<<(const wchar_t *s) { - int codePage = g_CodePage; + AString temp; + UString s2(s); + PrintUString(s2, temp); + return *this; +} + +void CStdOutStream::PrintUString(const UString &s, AString &temp) +{ + Convert_UString_to_AString(s, temp); + *this << (const char *)temp; +} + +void CStdOutStream::Convert_UString_to_AString(const UString &src, AString &dest) +{ + int codePage = CodePage; if (codePage == -1) codePage = CP_OEMCP; - AString dest; - if (codePage == CP_UTF8) - ConvertUnicodeToUTF8(s, dest); + if ((unsigned)codePage == CP_UTF8) + ConvertUnicodeToUTF8(src, dest); else - UnicodeStringToMultiByte2(dest, s, (UINT)codePage); - return operator<<((const char *)dest); + UnicodeStringToMultiByte2(dest, src, (UINT)(unsigned)codePage); } -void StdOut_Convert_UString_to_AString(const UString &s, AString &temp) + +static const wchar_t kReplaceChar = '_'; + +/* +void CStdOutStream::Normalize_UString_LF_Allowed(UString &s) { - int codePage = g_CodePage; - if (codePage == -1) - codePage = CP_OEMCP; - if (codePage == CP_UTF8) - ConvertUnicodeToUTF8(s, temp); + if (!IsTerminalMode) + return; + + const unsigned len = s.Len(); + wchar_t *d = s.GetBuf(); + + for (unsigned i = 0; i < len; i++) + { + const wchar_t c = d[i]; + if (c == 0x1b || (c <= 13 && c >= 7 && c != '\n')) + d[i] = kReplaceChar; + } +} +*/ + +static inline bool IsDangerousTerminalChar(wchar_t c) +{ + // return ((c <= 13 && c >= 7) || c == 0x1b); +#if 0 && defined(WCHAR_MIN) && WCHAR_MIN < 0 + // wchar_t is signed type in posix usually. + // but negative numbers are not expected in wchar_t strings. + // if we want to show negative characters, use the following check: + if (c < 0) return false; // it's not expected case +#endif + if (c < 0x20) return true; + if (c < 0x7F) return false; + if (c < 0x9F + 1) return true; + // Unicode Bidirectional (BiDi) control characters: + if (c < 0x202A) return false; + if (c < 0x202E + 1) return true; + if (c < 0x2066) return false; + if (c < 0x2069 + 1) return true; + return false; +} + +void CStdOutStream::Normalize_UString(UString &s) +{ + const unsigned len = s.Len(); + wchar_t *d = s.GetBuf(); + + if (IsTerminalMode) + for (unsigned i = 0; i < len; i++) + { + if (IsDangerousTerminalChar(d[i])) + d[i] = kReplaceChar; + } else - UnicodeStringToMultiByte2(temp, s, (UINT)codePage); + for (unsigned i = 0; i < len; i++) + { + const wchar_t c = d[i]; + if (c == '\n' +#ifdef _WIN32 + || c == '\r' +#endif + ) + d[i] = kReplaceChar; + } } -void CStdOutStream::PrintUString(const UString &s, AString &temp) +void CStdOutStream::Normalize_UString_Path(UString &s) { - StdOut_Convert_UString_to_AString(s, temp); - *this << (const char *)temp; + if (ListPathSeparatorSlash.Def) + { +#ifdef _WIN32 + if (ListPathSeparatorSlash.Val) + s.Replace(L'\\', L'/'); +#else + if (!ListPathSeparatorSlash.Val) + s.Replace(L'/', L'\\'); +#endif + } + Normalize_UString(s); +} + + +/* +void CStdOutStream::Normalize_UString(UString &src) +{ + const wchar_t *s = src.Ptr(); + const unsigned len = src.Len(); + unsigned i; + for (i = 0; i < len; i++) + { + const wchar_t c = s[i]; +#if 0 && !defined(_WIN32) + if (c == '\\') // IsTerminalMode && + break; +#endif + if ((unsigned)c < 0x20) + break; + } + if (i == len) + return; + + UString temp; + for (i = 0; i < len; i++) + { + wchar_t c = s[i]; +#if 0 && !defined(_WIN32) + if (c == '\\') + temp += (wchar_t)L'\\'; + else +#endif + if ((unsigned)c < 0x20) + { + if (c == '\n' + || (IsTerminalMode && (c == 0x1b || (c <= 13 && c >= 7)))) + { +#if 1 || defined(_WIN32) + c = (wchar_t)kReplaceChar; +#else + temp += (wchar_t)L'\\'; + if (c == '\n') c = L'n'; + else if (c == '\r') c = L'r'; + else if (c == '\a') c = L'a'; + else if (c == '\b') c = L'b'; + else if (c == '\t') c = L't'; + else if (c == '\v') c = L'v'; + else if (c == '\f') c = L'f'; + else + { + temp += (wchar_t)(L'0' + (unsigned)c / 64); + temp += (wchar_t)(L'0' + (unsigned)c / 8 % 8); + c = (wchar_t)(L'0' + (unsigned)c % 8); + } +#endif + } + } + temp += c; + } + src = temp; +} +*/ + +void CStdOutStream::NormalizePrint_UString_Path(const UString &s, UString &tempU, AString &tempA) +{ + tempU = s; + Normalize_UString_Path(tempU); + PrintUString(tempU, tempA); +} + +void CStdOutStream::NormalizePrint_UString_Path(const UString &s) +{ + UString tempU; + AString tempA; + NormalizePrint_UString_Path(s, tempU, tempA); +} + +void CStdOutStream::NormalizePrint_wstr_Path(const wchar_t *s) +{ + UString tempU = s; + Normalize_UString_Path(tempU); + AString tempA; + PrintUString(tempU, tempA); +} + +void CStdOutStream::NormalizePrint_UString(const UString &s) +{ + UString tempU = s; + Normalize_UString(tempU); + AString tempA; + PrintUString(tempU, tempA); } CStdOutStream & CStdOutStream::operator<<(Int32 number) throw() diff --git a/src/plugins/7zip/7za/cpp/Common/StdOutStream.h b/src/plugins/7zip/7za/cpp/Common/StdOutStream.h index 1837bfb0..846b0db5 100644 --- a/src/plugins/7zip/7za/cpp/Common/StdOutStream.h +++ b/src/plugins/7zip/7za/cpp/Common/StdOutStream.h @@ -1,7 +1,7 @@ // Common/StdOutStream.h -#ifndef __COMMON_STD_OUT_STREAM_H -#define __COMMON_STD_OUT_STREAM_H +#ifndef ZIP7_INC_COMMON_STD_OUT_STREAM_H +#define ZIP7_INC_COMMON_STD_OUT_STREAM_H #include @@ -11,18 +11,36 @@ class CStdOutStream { FILE *_stream; - bool _streamIsOpen; + // bool _streamIsOpen; public: - CStdOutStream(): _stream(0), _streamIsOpen(false) {}; - CStdOutStream(FILE *stream): _stream(stream), _streamIsOpen(false) {}; - ~CStdOutStream() { Close(); } + bool IsTerminalMode; + CBoolPair ListPathSeparatorSlash; + int CodePage; + + CStdOutStream(FILE *stream = NULL): + _stream(stream), + // _streamIsOpen(false), + IsTerminalMode(false), + CodePage(-1) + { + ListPathSeparatorSlash.Val = +#ifdef _WIN32 + false; +#else + true; +#endif + } + + // ~CStdOutStream() { Close(); } // void AttachStdStream(FILE *stream) { _stream = stream; _streamIsOpen = false; } // bool IsDefined() const { return _stream != NULL; } operator FILE *() { return _stream; } + /* bool Open(const char *fileName) throw(); bool Close() throw(); + */ bool Flush() throw(); CStdOutStream & operator<<(CStdOutStream & (* func)(CStdOutStream &)) @@ -50,6 +68,15 @@ class CStdOutStream CStdOutStream & operator<<(const wchar_t *s); void PrintUString(const UString &s, AString &temp); + void Convert_UString_to_AString(const UString &src, AString &dest); + + void Normalize_UString(UString &s); + void Normalize_UString_Path(UString &s); + + void NormalizePrint_UString_Path(const UString &s, UString &tempU, AString &tempA); + void NormalizePrint_UString_Path(const UString &s); + void NormalizePrint_UString(const UString &s); + void NormalizePrint_wstr_Path(const wchar_t *s); }; CStdOutStream & endl(CStdOutStream & outStream) throw(); @@ -57,6 +84,4 @@ CStdOutStream & endl(CStdOutStream & outStream) throw(); extern CStdOutStream g_StdOut; extern CStdOutStream g_StdErr; -void StdOut_Convert_UString_to_AString(const UString &s, AString &temp); - #endif diff --git a/src/plugins/7zip/7za/cpp/Common/StringConvert.cpp b/src/plugins/7zip/7za/cpp/Common/StringConvert.cpp index 30d95aa7..79ff9e00 100644 --- a/src/plugins/7zip/7za/cpp/Common/StringConvert.cpp +++ b/src/plugins/7zip/7za/cpp/Common/StringConvert.cpp @@ -5,9 +5,18 @@ #include "StringConvert.h" #ifndef _WIN32 +// #include #include #endif +#if !defined(_WIN32) || defined(ENV_HAVE_LOCALE) +#include "UTFConvert.h" +#endif + +#ifdef ENV_HAVE_LOCALE +#include +#endif + static const char k_DefultChar = '_'; #ifdef _WIN32 @@ -71,7 +80,7 @@ void MultiByteToUnicodeString2(UString &dest, const AString &src, UINT codePage) d[i] = 0; dest.ReleaseBuf_SetLen(i); */ - unsigned len = MultiByteToWideChar(codePage, 0, src, src.Len(), NULL, 0); + unsigned len = (unsigned)MultiByteToWideChar(codePage, 0, src, (int)src.Len(), NULL, 0); if (len == 0) { if (GetLastError() != 0) @@ -79,7 +88,7 @@ void MultiByteToUnicodeString2(UString &dest, const AString &src, UINT codePage) } else { - len = MultiByteToWideChar(codePage, 0, src, src.Len(), dest.GetBuf(len), len); + len = (unsigned)MultiByteToWideChar(codePage, 0, src, (int)src.Len(), dest.GetBuf(len), (int)len); if (len == 0) throw 282228; dest.ReleaseBuf_SetEnd(len); @@ -175,7 +184,7 @@ static void UnicodeStringToMultiByte2(AString &dest, const UString &src, UINT co } */ - unsigned len = WideCharToMultiByte(codePage, 0, src, src.Len(), NULL, 0, NULL, NULL); + unsigned len = (unsigned)WideCharToMultiByte(codePage, 0, src, (int)src.Len(), NULL, 0, NULL, NULL); if (len == 0) { if (GetLastError() != 0) @@ -186,8 +195,8 @@ static void UnicodeStringToMultiByte2(AString &dest, const UString &src, UINT co BOOL defUsed = FALSE; bool isUtf = (codePage == CP_UTF8 || codePage == CP_UTF7); // defaultChar = defaultChar; - len = WideCharToMultiByte(codePage, 0, src, src.Len(), - dest.GetBuf(len), len, + len = (unsigned)WideCharToMultiByte(codePage, 0, src, (int)src.Len(), + dest.GetBuf(len), (int)len, (isUtf ? NULL : &defaultChar), (isUtf ? NULL : &defUsed) ); @@ -213,23 +222,138 @@ AString SystemStringToOemString(const CSysString &src) #endif */ -#else +#else // _WIN32 + +// #include +/* + if (wchar_t is 32-bit (#if WCHAR_MAX > 0xffff), + and utf-8 string contains big unicode character > 0xffff), + then we still use 16-bit surrogate pair in UString. + It simplifies another code where utf-16 encoding is used. + So we use surrogate-conversion code only in is file. +*/ + +/* + mbstowcs() returns error if there is error in utf-8 stream, + mbstowcs() returns error if there is single surrogates point (d800-dfff) in utf-8 stream +*/ + +/* +static void MultiByteToUnicodeString2_Native(UString &dest, const AString &src) +{ + dest.Empty(); + if (src.IsEmpty()) + return; + + const size_t limit = ((size_t)src.Len() + 1) * 2; + wchar_t *d = dest.GetBuf((unsigned)limit); + const size_t len = mbstowcs(d, src, limit); + if (len != (size_t)-1) + { + dest.ReleaseBuf_SetEnd((unsigned)len); + return; + } + dest.ReleaseBuf_SetEnd(0); +} +*/ + +bool g_ForceToUTF8 = true; // false; -void MultiByteToUnicodeString2(UString &dest, const AString &src, UINT /* codePage */) +void MultiByteToUnicodeString2(UString &dest, const AString &src, UINT codePage) { dest.Empty(); if (src.IsEmpty()) return; - size_t limit = ((size_t)src.Len() + 1) * 2; + if (codePage == CP_UTF8 || g_ForceToUTF8) + { +#if 1 + ConvertUTF8ToUnicode(src, dest); + return; +#endif + } + + const size_t limit = ((size_t)src.Len() + 1) * 2; wchar_t *d = dest.GetBuf((unsigned)limit); - size_t len = mbstowcs(d, src, limit); + const size_t len = mbstowcs(d, src, limit); if (len != (size_t)-1) { dest.ReleaseBuf_SetEnd((unsigned)len); + +#if WCHAR_MAX > 0xffff + d = dest.GetBuf(); + for (size_t i = 0;; i++) + { + wchar_t c = d[i]; + // printf("\ni=%2d c = %4x\n", (unsigned)i, (unsigned)c); + if (c == 0) + break; + if (c >= 0x10000 && c < 0x110000) + { + UString tempString = d + i; + const wchar_t *t = tempString.Ptr(); + + for (;;) + { + wchar_t w = *t++; + // printf("\nchar=%x\n", w); + if (w == 0) + break; + if (i == limit) + break; // unexpected error + if (w >= 0x10000 && w < 0x110000) + { +#if 1 + if (i + 1 == limit) + break; // unexpected error + w -= 0x10000; + d[i++] = (unsigned)0xd800 + (((unsigned)w >> 10) & 0x3ff); + w = 0xdc00 + (w & 0x3ff); +#else + // w = '_'; // for debug +#endif + } + d[i++] = w; + } + dest.ReleaseBuf_SetEnd((unsigned)i); + break; + } + } + +#endif + + /* + printf("\nMultiByteToUnicodeString2 (%d) %s\n", (int)src.Len(), src.Ptr()); + printf("char: "); + for (unsigned i = 0; i < src.Len(); i++) + printf (" %02x", (int)(Byte)src[i]); + printf("\n"); + printf("\n-> (%d) %ls\n", (int)dest.Len(), dest.Ptr()); + printf("wchar_t: "); + for (unsigned i = 0; i < dest.Len(); i++) + { + printf (" %02x", (int)dest[i]); + } + printf("\n"); + */ + return; } + + /* if there is mbstowcs() error, we have two ways: + + 1) change 0x80+ characters to some character: '_' + in that case we lose data, but we have correct UString() + and that scheme can show errors to user in early stages, + when file converted back to mbs() cannot be found + + 2) transfer bad characters in some UTF-16 range. + it can be non-original Unicode character. + but later we still can restore original character. + */ + + // printf("\nmbstowcs ERROR !!!!!! s=%s\n", src.Ptr()); { unsigned i; const char *s = (const char *)src; @@ -238,6 +362,8 @@ void MultiByteToUnicodeString2(UString &dest, const AString &src, UINT /* codePa Byte c = (Byte)s[i]; if (c == 0) break; + // we can use ascii compatibilty character '_' + // if (c > 0x7F) c = '_'; // we replace "bad: character d[i++] = (wchar_t)c; } d[i] = 0; @@ -245,43 +371,136 @@ void MultiByteToUnicodeString2(UString &dest, const AString &src, UINT /* codePa } } -static void UnicodeStringToMultiByte2(AString &dest, const UString &src, UINT /* codePage */, char defaultChar, bool &defaultCharWasUsed) +static void UnicodeStringToMultiByte2_Native(AString &dest, const UString &src) { dest.Empty(); - defaultCharWasUsed = false; if (src.IsEmpty()) return; - size_t limit = ((size_t)src.Len() + 1) * 6; + const size_t limit = ((size_t)src.Len() + 1) * 6; char *d = dest.GetBuf((unsigned)limit); - size_t len = wcstombs(d, src, limit); + + const size_t len = wcstombs(d, src, limit); + if (len != (size_t)-1) { dest.ReleaseBuf_SetEnd((unsigned)len); return; } + dest.ReleaseBuf_SetEnd(0); +} + + +static void UnicodeStringToMultiByte2(AString &dest, const UString &src2, UINT codePage, char defaultChar, bool &defaultCharWasUsed) +{ + // if (codePage == 1234567) // for debug purposes + if (codePage == CP_UTF8 || g_ForceToUTF8) + { +#if 1 + defaultCharWasUsed = false; + ConvertUnicodeToUTF8(src2, dest); + return; +#endif + } + + UString src = src2; +#if WCHAR_MAX > 0xffff + { + src.Empty(); + for (unsigned i = 0; i < src2.Len();) + { + wchar_t c = src2[i++]; + if (c >= 0xd800 && c < 0xdc00 && i != src2.Len()) + { + const wchar_t c2 = src2[i]; + if (c2 >= 0xdc00 && c2 < 0xe000) + { +#if 1 + // printf("\nSurragate [%d]: %4x %4x -> ", i, (int)c, (int)c2); + c = 0x10000 + ((c & 0x3ff) << 10) + (c2 & 0x3ff); + // printf("%4x\n", (int)c); + i++; +#else + // c = '_'; // for debug +#endif + } + } + src += c; + } + } +#endif + + dest.Empty(); + defaultCharWasUsed = false; + if (src.IsEmpty()) + return; + + const size_t len = wcstombs(NULL, src, 0); + + if (len != (size_t)-1) + { + const unsigned limit = ((unsigned)len); + if (limit == len) + { + char *d = dest.GetBuf(limit); + + /* + { + printf("\nwcstombs; len = %d %ls \n", (int)src.Len(), src.Ptr()); + for (unsigned i = 0; i < src.Len(); i++) + printf (" %02x", (int)src[i]); + printf("\n"); + printf("\ndest Limit = %d \n", limit); + } + */ + + const size_t len2 = wcstombs(d, src, len + 1); + + if (len2 != (size_t)-1 && len2 <= limit) + { + /* + printf("\nOK : destLen = %d : %s\n", (int)len, dest.Ptr()); + for (unsigned i = 0; i < len2; i++) + printf(" %02x", (int)(Byte)dest[i]); + printf("\n"); + */ + dest.ReleaseBuf_SetEnd((unsigned)len2); + return; + } + } + } { const wchar_t *s = (const wchar_t *)src; + char *d = dest.GetBuf(src.Len()); + unsigned i; for (i = 0;;) { wchar_t c = s[i]; if (c == 0) break; - if (c >= 0x100) + if (c >= + 0x100 + // 0x80 + ) { c = defaultChar; defaultCharWasUsed = true; } + d[i++] = (char)c; } d[i] = 0; dest.ReleaseBuf_SetLen(i); + /* + printf("\nUnicodeStringToMultiByte2; len = %d \n", (int)src.Len()); + printf("ERROR: %s\n", dest.Ptr()); + */ } } -#endif +#endif // _WIN32 UString MultiByteToUnicodeString(const AString &src, UINT codePage) @@ -291,6 +510,12 @@ UString MultiByteToUnicodeString(const AString &src, UINT codePage) return dest; } +UString MultiByteToUnicodeString(const char *src, UINT codePage) +{ + return MultiByteToUnicodeString(AString(src), codePage); +} + + void UnicodeStringToMultiByte2(AString &dest, const UString &src, UINT codePage) { bool defaultCharWasUsed; @@ -311,3 +536,227 @@ AString UnicodeStringToMultiByte(const UString &src, UINT codePage) UnicodeStringToMultiByte2(dest, src, codePage, k_DefultChar, defaultCharWasUsed); return dest; } + + + + +#if !defined(_WIN32) || defined(ENV_HAVE_LOCALE) + +#ifdef _WIN32 +#define U_to_A(a, b, c) UnicodeStringToMultiByte2 +// #define A_to_U(a, b, c) MultiByteToUnicodeString2 +#else +// void MultiByteToUnicodeString2_Native(UString &dest, const AString &src); +#define U_to_A(a, b, c) UnicodeStringToMultiByte2_Native(a, b) +// #define A_to_U(a, b, c) MultiByteToUnicodeString2_Native(a, b) +#endif + +bool IsNativeUTF8() +{ + UString u; + AString a, a2; + // for (unsigned c = 0x80; c < (UInt32)0x10000; c += (c >> 9) + 1) + for (unsigned c = 0x80; c < (UInt32)0xD000; c += (c >> 2) + 1) + { + u.Empty(); + u += (wchar_t)c; + /* + if (Unicode_Is_There_Utf16SurrogateError(u)) + continue; + #ifndef _WIN32 + if (Unicode_Is_There_BmpEscape(u)) + continue; + #endif + */ + ConvertUnicodeToUTF8(u, a); + U_to_A(a2, u, CP_OEMCP); + if (a != a2) + return false; + } + return true; +} + +#endif + + +#ifdef ENV_HAVE_LOCALE + +const char *GetLocale(void) +{ + #ifdef ENV_HAVE_LOCALE + // printf("\n\nsetlocale(LC_CTYPE, NULL) : return : "); + const char *s = setlocale(LC_CTYPE, NULL); + if (!s) + { + // printf("[NULL]\n"); + s = "C"; + } + else + { + // ubuntu returns "C" after program start + // printf("\"%s\"\n", s); + } + return s; + #elif defined(LOCALE_IS_UTF8) + return "utf8"; + #else + return "C"; + #endif +} + +#ifdef _WIN32 + static void Set_ForceToUTF8(bool) {} +#else + static void Set_ForceToUTF8(bool val) { g_ForceToUTF8 = val; } +#endif + +static bool Is_Default_Basic_Locale(const char *locale) +{ + const AString a (locale); + if (a.IsEqualTo_Ascii_NoCase("") + || a.IsEqualTo_Ascii_NoCase("C") + || a.IsEqualTo_Ascii_NoCase("POSIX")) + return true; + return false; +} + +static bool Is_Default_Basic_Locale() +{ + return Is_Default_Basic_Locale(GetLocale()); +} + + +void MY_SetLocale() +{ + #ifdef ENV_HAVE_LOCALE + /* + { + const char *s = GetLocale(); + printf("\nGetLocale() : returned : \"%s\"\n", s); + } + */ + + unsigned start = 0; + // unsigned lim = 0; + unsigned lim = 3; + + /* + #define MY_SET_LOCALE_FLAGS__FROM_ENV 1 + #define MY_SET_LOCALE_FLAGS__TRY_UTF8 2 + + unsigned flags = + MY_SET_LOCALE_FLAGS__FROM_ENV | + MY_SET_LOCALE_FLAGS__TRY_UTF8 + + if (flags != 0) + { + if (flags & MY_SET_LOCALE_FLAGS__FROM_ENV) + lim = (flags & MY_SET_LOCALE_FLAGS__TRY_UTF8) ? 3 : 1; + else + { + start = 1; + lim = 2; + } + } + */ + + for (unsigned i = start; i < lim; i++) + { + /* + man7: "If locale is an empty string, "", each part of the locale that + should be modified is set according to the environment variables. + for glibc: glibc, first from the user's environment variables: + 1) the environment variable LC_ALL, + 2) environment variable with the same name as the category (see the + 3) the environment variable LANG + The locale "C" or "POSIX" is a portable locale; it exists on all conforming systems. + + for WIN32 : MSDN : + Sets the locale to the default, which is the user-default + ANSI code page obtained from the operating system. + The locale name is set to the value returned by GetUserDefaultLocaleName. + The code page is set to the value returned by GetACP + */ + const char *newLocale = ""; + + #ifdef __APPLE__ + + /* look also CFLocale + there is no C.UTF-8 in macos + macos has UTF-8 locale only with some language like en_US.UTF-8 + what is best way to set UTF-8 locale in macos? */ + if (i == 1) + newLocale = "en_US.UTF-8"; + + /* file open with non-utf8 sequencies return + #define EILSEQ 92 // "Illegal byte sequence" + */ +#else + // newLocale = "C"; + if (i == 1) + { + newLocale = "C.UTF-8"; // main UTF-8 locale in ubuntu + // newLocale = ".utf8"; // supported in new Windows 10 build 17134 (April 2018 Update), the Universal C Runtime + // newLocale = "en_US.utf8"; // supported by ubuntu ? + // newLocale = "en_US.UTF-8"; + /* setlocale() in ubuntu allows locales with minor chracter changes in strings + "en_US.UTF-8" / "en_US.utf8" */ + } + +#endif + + // printf("\nsetlocale(LC_ALL, \"%s\") : returned: ", newLocale); + + // const char *s = + setlocale(LC_ALL, newLocale); + + /* + if (!s) + printf("NULL: can't set locale"); + else + printf("\"%s\"\n", s); + */ + + // request curent locale of program + const char *locale = GetLocale(); + if (locale) + { + AString a (locale); + a.MakeLower_Ascii(); + // if (a.Find("utf") >= 0) + { + if (IsNativeUTF8()) + { + Set_ForceToUTF8(true); + return; + } + } + if (!Is_Default_Basic_Locale(locale)) + { + // if there is some non-default and non-utf locale, we want to use it + break; // comment it for debug + } + } + } + + if (IsNativeUTF8()) + { + Set_ForceToUTF8(true); + return; + } + + if (Is_Default_Basic_Locale()) + { + Set_ForceToUTF8(true); + return; + } + + Set_ForceToUTF8(false); + + #elif defined(LOCALE_IS_UTF8) + // assume LC_CTYPE="utf8" + #else + // assume LC_CTYPE="C" + #endif +} +#endif diff --git a/src/plugins/7zip/7za/cpp/Common/StringConvert.h b/src/plugins/7zip/7za/cpp/Common/StringConvert.h index 92401245..2092a2da 100644 --- a/src/plugins/7zip/7za/cpp/Common/StringConvert.h +++ b/src/plugins/7zip/7za/cpp/Common/StringConvert.h @@ -1,77 +1,110 @@ // Common/StringConvert.h -#ifndef __COMMON_STRING_CONVERT_H -#define __COMMON_STRING_CONVERT_H +#ifndef ZIP7_INC_COMMON_STRING_CONVERT_H +#define ZIP7_INC_COMMON_STRING_CONVERT_H #include "MyString.h" #include "MyWindows.h" -UString MultiByteToUnicodeString(const AString &srcString, UINT codePage = CP_ACP); +UString MultiByteToUnicodeString(const AString &src, UINT codePage = CP_ACP); +UString MultiByteToUnicodeString(const char *src, UINT codePage = CP_ACP); // optimized versions that work faster for ASCII strings -void MultiByteToUnicodeString2(UString &dest, const AString &srcString, UINT codePage = CP_ACP); +void MultiByteToUnicodeString2(UString &dest, const AString &src, UINT codePage = CP_ACP); // void UnicodeStringToMultiByte2(AString &dest, const UString &s, UINT codePage, char defaultChar, bool &defaultCharWasUsed); -void UnicodeStringToMultiByte2(AString &dest, const UString &srcString, UINT codePage); - -AString UnicodeStringToMultiByte(const UString &srcString, UINT codePage, char defaultChar, bool &defaultCharWasUsed); -AString UnicodeStringToMultiByte(const UString &srcString, UINT codePage = CP_ACP); - -inline const wchar_t* GetUnicodeString(const wchar_t* unicodeString) - { return unicodeString; } -inline const UString& GetUnicodeString(const UString &unicodeString) - { return unicodeString; } -inline UString GetUnicodeString(const AString &ansiString) - { return MultiByteToUnicodeString(ansiString); } -inline UString GetUnicodeString(const AString &multiByteString, UINT codePage) - { return MultiByteToUnicodeString(multiByteString, codePage); } -inline const wchar_t* GetUnicodeString(const wchar_t* unicodeString, UINT) - { return unicodeString; } -inline const UString& GetUnicodeString(const UString &unicodeString, UINT) - { return unicodeString; } - -inline const char* GetAnsiString(const char* ansiString) - { return ansiString; } -inline const AString& GetAnsiString(const AString &ansiString) - { return ansiString; } -inline AString GetAnsiString(const UString &unicodeString) - { return UnicodeStringToMultiByte(unicodeString); } - -inline const char* GetOemString(const char* oemString) - { return oemString; } -inline const AString& GetOemString(const AString &oemString) - { return oemString; } -inline AString GetOemString(const UString &unicodeString) - { return UnicodeStringToMultiByte(unicodeString, CP_OEMCP); } +void UnicodeStringToMultiByte2(AString &dest, const UString &src, UINT codePage); +AString UnicodeStringToMultiByte(const UString &src, UINT codePage, char defaultChar, bool &defaultCharWasUsed); +AString UnicodeStringToMultiByte(const UString &src, UINT codePage = CP_ACP); + +inline const wchar_t* GetUnicodeString(const wchar_t *u) { return u; } +inline const UString& GetUnicodeString(const UString &u) { return u; } + +inline UString GetUnicodeString(const AString &a) { return MultiByteToUnicodeString(a); } +inline UString GetUnicodeString(const char *a) { return MultiByteToUnicodeString(a); } + +inline UString GetUnicodeString(const AString &a, UINT codePage) + { return MultiByteToUnicodeString(a, codePage); } +inline UString GetUnicodeString(const char *a, UINT codePage) + { return MultiByteToUnicodeString(a, codePage); } + +inline const wchar_t* GetUnicodeString(const wchar_t *u, UINT) { return u; } +inline const UString& GetUnicodeString(const UString &u, UINT) { return u; } + +inline const char* GetAnsiString(const char *a) { return a; } +inline const AString& GetAnsiString(const AString &a) { return a; } + +inline AString GetAnsiString(const wchar_t *u) { return UnicodeStringToMultiByte(UString(u)); } +inline AString GetAnsiString(const UString &u) { return UnicodeStringToMultiByte(u); } + +/* +inline const char* GetOemString(const char* oem) + { return oem; } +inline const AString& GetOemString(const AString &oem) + { return oem; } +*/ +const char* GetOemString(const char* oem); +const AString& GetOemString(const AString &oem); +inline AString GetOemString(const UString &u) + { return UnicodeStringToMultiByte(u, CP_OEMCP); } #ifdef _UNICODE - inline const wchar_t* GetSystemString(const wchar_t* unicodeString) - { return unicodeString;} - inline const UString& GetSystemString(const UString &unicodeString) - { return unicodeString;} - inline const wchar_t* GetSystemString(const wchar_t* unicodeString, UINT /* codePage */) - { return unicodeString;} - inline const UString& GetSystemString(const UString &unicodeString, UINT /* codePage */) - { return unicodeString;} - inline UString GetSystemString(const AString &multiByteString, UINT codePage) - { return MultiByteToUnicodeString(multiByteString, codePage);} - inline UString GetSystemString(const AString &multiByteString) - { return MultiByteToUnicodeString(multiByteString);} + inline const wchar_t* GetSystemString(const wchar_t *u) { return u;} + inline const UString& GetSystemString(const UString &u) { return u;} + inline const wchar_t* GetSystemString(const wchar_t *u, UINT /* codePage */) { return u;} + inline const UString& GetSystemString(const UString &u, UINT /* codePage */) { return u;} + + inline UString GetSystemString(const AString &a, UINT codePage) { return MultiByteToUnicodeString(a, codePage); } + inline UString GetSystemString(const char *a, UINT codePage) { return MultiByteToUnicodeString(a, codePage); } + inline UString GetSystemString(const AString &a) { return MultiByteToUnicodeString(a); } + inline UString GetSystemString(const char *a) { return MultiByteToUnicodeString(a); } #else - inline const char* GetSystemString(const char *ansiString) - { return ansiString; } - inline const AString& GetSystemString(const AString &multiByteString, UINT) - { return multiByteString; } - inline const char * GetSystemString(const char *multiByteString, UINT) - { return multiByteString; } - inline AString GetSystemString(const UString &unicodeString) - { return UnicodeStringToMultiByte(unicodeString); } - inline AString GetSystemString(const UString &unicodeString, UINT codePage) - { return UnicodeStringToMultiByte(unicodeString, codePage); } + inline const char* GetSystemString(const char *a) { return a; } + inline const AString& GetSystemString(const AString &a) { return a; } + inline const char* GetSystemString(const char *a, UINT) { return a; } + inline const AString& GetSystemString(const AString &a, UINT) { return a; } + + inline AString GetSystemString(const wchar_t *u) { return UnicodeStringToMultiByte(UString(u)); } + inline AString GetSystemString(const UString &u) { return UnicodeStringToMultiByte(u); } + inline AString GetSystemString(const UString &u, UINT codePage) { return UnicodeStringToMultiByte(u, codePage); } + + + + /* + inline AString GetSystemString(const wchar_t *u) + { + UString s; + s = u; + return UnicodeStringToMultiByte(s); + } + */ + #endif #ifndef UNDER_CE -AString SystemStringToOemString(const CSysString &srcString); +AString SystemStringToOemString(const CSysString &src); +#endif + + +#ifdef _WIN32 +/* we don't need locale functions in Windows + but we can define ENV_HAVE_LOCALE here for debug purposes */ +// #define ENV_HAVE_LOCALE +#else +#define ENV_HAVE_LOCALE +#endif + +#ifdef ENV_HAVE_LOCALE +void MY_SetLocale(); +const char *GetLocale(void); +#endif + +#if !defined(_WIN32) || defined(ENV_HAVE_LOCALE) +bool IsNativeUTF8(); +#endif + +#ifndef _WIN32 +extern bool g_ForceToUTF8; #endif #endif diff --git a/src/plugins/7zip/7za/cpp/Common/StringToInt.cpp b/src/plugins/7zip/7za/cpp/Common/StringToInt.cpp index dfa5cc3b..ba8a81b3 100644 --- a/src/plugins/7zip/7za/cpp/Common/StringToInt.cpp +++ b/src/plugins/7zip/7za/cpp/Common/StringToInt.cpp @@ -2,24 +2,57 @@ #include "StdAfx.h" +#include +#if defined(_MSC_VER) && (_MSC_VER >= 1600) +#include // for WCHAR_MAX in vs2022 +#endif + #include "StringToInt.h" static const UInt32 k_UInt32_max = 0xFFFFFFFF; static const UInt64 k_UInt64_max = UINT64_CONST(0xFFFFFFFFFFFFFFFF); // static const UInt64 k_UInt64_max = (UInt64)(Int64)-1; +#define DIGIT_TO_VALUE(charTypeUnsigned, digit) ((unsigned)(charTypeUnsigned)digit - '0') +// #define DIGIT_TO_VALUE(charTypeUnsigned, digit) ((unsigned)digit - '0') +// #define DIGIT_TO_VALUE(charTypeUnsigned, digit) ((unsigned)(digit - '0')) + #define CONVERT_STRING_TO_UINT_FUNC(uintType, charType, charTypeUnsigned) \ - uintType ConvertStringTo ## uintType(const charType *s, const charType **end) throw() { \ +uintType ConvertStringTo ## uintType(const charType *s, const charType **end) throw() { \ if (end) *end = s; \ uintType res = 0; \ for (;; s++) { \ - charTypeUnsigned c = (charTypeUnsigned)*s; \ + const unsigned v = DIGIT_TO_VALUE(charTypeUnsigned, *s); \ + if (v > 9) { if (end) *end = s; return res; } \ + if (res > (k_ ## uintType ## _max) / 10) return 0; \ + res *= 10; \ + if (res > (k_ ## uintType ## _max) - v) return 0; \ + res += v; }} + +// arm-linux-gnueabi GCC compilers give (WCHAR_MAX > UINT_MAX) by some unknown reason +// so we don't use this branch +#if 0 && WCHAR_MAX > UINT_MAX +/* + if (sizeof(wchar_t) > sizeof(unsigned) + we must use CONVERT_STRING_TO_UINT_FUNC_SLOW + But we just stop compiling instead. + We need some real cases to test this code. +*/ +#error Stop_Compiling_WCHAR_MAX_IS_LARGER_THAN_UINT_MAX +#define CONVERT_STRING_TO_UINT_FUNC_SLOW(uintType, charType, charTypeUnsigned) \ +uintType ConvertStringTo ## uintType(const charType *s, const charType **end) throw() { \ + if (end) *end = s; \ + uintType res = 0; \ + for (;; s++) { \ + const charTypeUnsigned c = (charTypeUnsigned)*s; \ if (c < '0' || c > '9') { if (end) *end = s; return res; } \ if (res > (k_ ## uintType ## _max) / 10) return 0; \ res *= 10; \ - unsigned v = (c - '0'); \ + const unsigned v = (unsigned)(c - '0'); \ if (res > (k_ ## uintType ## _max) - v) return 0; \ res += v; }} +#endif + CONVERT_STRING_TO_UINT_FUNC(UInt32, char, Byte) CONVERT_STRING_TO_UINT_FUNC(UInt32, wchar_t, wchar_t) @@ -33,112 +66,89 @@ Int32 ConvertStringToInt32(const wchar_t *s, const wchar_t **end) throw() const wchar_t *s2 = s; if (*s == '-') s2++; - if (*s2 == 0) - return 0; const wchar_t *end2; UInt32 res = ConvertStringToUInt32(s2, &end2); - if (*s == '-') + if (s2 == end2) + return 0; + if (s != s2) { - if (res > ((UInt32)1 << (32 - 1))) + if (res > (UInt32)1 << (32 - 1)) + return 0; + res = 0 - res; + } + else + { + if (res & (UInt32)1 << (32 - 1)) return 0; } - else if ((res & ((UInt32)1 << (32 - 1))) != 0) - return 0; if (end) *end = end2; - if (*s == '-') - return -(Int32)res; return (Int32)res; } -UInt32 ConvertOctStringToUInt32(const char *s, const char **end) throw() -{ - if (end) - *end = s; - UInt32 res = 0; - for (;; s++) - { - unsigned c = (unsigned char)*s; - if (c < '0' || c > '7') - { - if (end) - *end = s; - return res; - } - if ((res & (UInt32)7 << (32 - 3)) != 0) - return 0; - res <<= 3; - res |= (unsigned)(c - '0'); - } + +#define CONVERT_OCT_STRING_TO_UINT_FUNC(uintType) \ +uintType ConvertOctStringTo ## uintType(const char *s, const char **end) throw() \ +{ \ + if (end) *end = s; \ + uintType res = 0; \ + for (;; s++) { \ + const unsigned c = (unsigned)(Byte)*s - '0'; \ + if (c > 7) { \ + if (end) \ + *end = s; \ + return res; \ + } \ + if (res & (uintType)7 << (sizeof(uintType) * 8 - 3)) \ + return 0; \ + res <<= 3; \ + res |= c; \ + } \ } -UInt64 ConvertOctStringToUInt64(const char *s, const char **end) throw() -{ - if (end) - *end = s; - UInt64 res = 0; - for (;; s++) - { - unsigned c = (unsigned char)*s; - if (c < '0' || c > '7') - { - if (end) - *end = s; - return res; - } - if ((res & (UInt64)7 << (64 - 3)) != 0) - return 0; - res <<= 3; - res |= (unsigned)(c - '0'); - } +CONVERT_OCT_STRING_TO_UINT_FUNC(UInt32) +CONVERT_OCT_STRING_TO_UINT_FUNC(UInt64) + + +#define CONVERT_HEX_STRING_TO_UINT_FUNC(uintType) \ +uintType ConvertHexStringTo ## uintType(const char *s, const char **end) throw() \ +{ \ + if (end) *end = s; \ + uintType res = 0; \ + for (;; s++) { \ + unsigned c = (unsigned)(Byte)*s; \ + Z7_PARSE_HEX_DIGIT(c, { if (end) *end = s; return res; }) \ + if (res & (uintType)0xF << (sizeof(uintType) * 8 - 4)) \ + return 0; \ + res <<= 4; \ + res |= c; \ + } \ } -UInt32 ConvertHexStringToUInt32(const char *s, const char **end) throw() +CONVERT_HEX_STRING_TO_UINT_FUNC(UInt32) +CONVERT_HEX_STRING_TO_UINT_FUNC(UInt64) + +const char *FindNonHexChar(const char *s) throw() { - if (end) - *end = s; - UInt32 res = 0; - for (;; s++) + for (;;) { - unsigned c = (Byte)*s; - unsigned v; - if (c >= '0' && c <= '9') v = (c - '0'); - else if (c >= 'A' && c <= 'F') v = 10 + (c - 'A'); - else if (c >= 'a' && c <= 'f') v = 10 + (c - 'a'); - else - { - if (end) - *end = s; - return res; - } - if ((res & (UInt32)0xF << (32 - 4)) != 0) - return 0; - res <<= 4; - res |= v; + unsigned c = (Byte)*s++; // pointer can go 1 byte after end + c -= '0'; + if (c <= 9) + continue; + c -= 'A' - '0'; + c &= ~0x20u; + if (c > 5) + return s - 1; } } -UInt64 ConvertHexStringToUInt64(const char *s, const char **end) throw() +Byte *ParseHexString(const char *s, Byte *dest) throw() { - if (end) - *end = s; - UInt64 res = 0; - for (;; s++) + for (;;) { - unsigned c = (Byte)*s; - unsigned v; - if (c >= '0' && c <= '9') v = (c - '0'); - else if (c >= 'A' && c <= 'F') v = 10 + (c - 'A'); - else if (c >= 'a' && c <= 'f') v = 10 + (c - 'a'); - else - { - if (end) - *end = s; - return res; - } - if ((res & (UInt64)0xF << (64 - 4)) != 0) - return 0; - res <<= 4; - res |= v; + unsigned v0 = (Byte)s[0]; Z7_PARSE_HEX_DIGIT(v0, return dest;) + unsigned v1 = (Byte)s[1]; s += 2; Z7_PARSE_HEX_DIGIT(v1, return dest;) + *dest++ = (Byte)(v1 | (v0 << 4)); } } diff --git a/src/plugins/7zip/7za/cpp/Common/StringToInt.h b/src/plugins/7zip/7za/cpp/Common/StringToInt.h index 5c5d7d7f..31ab2747 100644 --- a/src/plugins/7zip/7za/cpp/Common/StringToInt.h +++ b/src/plugins/7zip/7za/cpp/Common/StringToInt.h @@ -1,7 +1,7 @@ // Common/StringToInt.h -#ifndef __COMMON_STRING_TO_INT_H -#define __COMMON_STRING_TO_INT_H +#ifndef ZIP7_INC_COMMON_STRING_TO_INT_H +#define ZIP7_INC_COMMON_STRING_TO_INT_H #include "MyTypes.h" @@ -10,6 +10,7 @@ UInt64 ConvertStringToUInt64(const char *s, const char **end) throw(); UInt32 ConvertStringToUInt32(const wchar_t *s, const wchar_t **end) throw(); UInt64 ConvertStringToUInt64(const wchar_t *s, const wchar_t **end) throw(); +// Int32 ConvertStringToInt32(const char *s, const char **end) throw(); Int32 ConvertStringToInt32(const wchar_t *s, const wchar_t **end) throw(); UInt32 ConvertOctStringToUInt32(const char *s, const char **end) throw(); @@ -18,4 +19,20 @@ UInt64 ConvertOctStringToUInt64(const char *s, const char **end) throw(); UInt32 ConvertHexStringToUInt32(const char *s, const char **end) throw(); UInt64 ConvertHexStringToUInt64(const char *s, const char **end) throw(); +#define Z7_PARSE_HEX_DIGIT(c, err_op) \ +{ c -= '0'; \ + if (c > 9) { \ + c -= 'A' - '0'; \ + c &= ~0x20u; \ + if (c > 5) { err_op } \ + c += 10; \ + } \ +} + +const char *FindNonHexChar(const char *s) throw(); + +// in: (dest != NULL) +// returns: pointer in dest array after last written byte +Byte *ParseHexString(const char *s, Byte *dest) throw(); + #endif diff --git a/src/plugins/7zip/7za/cpp/Common/TextConfig.cpp b/src/plugins/7zip/7za/cpp/Common/TextConfig.cpp index 9ae4074b..5714e46c 100644 --- a/src/plugins/7zip/7za/cpp/Common/TextConfig.cpp +++ b/src/plugins/7zip/7za/cpp/Common/TextConfig.cpp @@ -15,7 +15,7 @@ static AString GetIDString(const char *s, unsigned &finishPos) AString result; for (finishPos = 0; ; finishPos++) { - char c = s[finishPos]; + const char c = s[finishPos]; if (IsDelimitChar(c) || c == '=') break; result += c; @@ -35,7 +35,7 @@ static bool SkipSpaces(const AString &s, unsigned &pos) { for (; pos < s.Len(); pos++) { - char c = s[pos]; + const char c = s[pos]; if (!IsDelimitChar(c)) { if (c != ';') @@ -61,7 +61,7 @@ bool GetTextConfig(const AString &s, CObjectVector &pairs) break; CTextConfigPair pair; unsigned finishPos; - AString temp = GetIDString(((const char *)s) + pos, finishPos); + const AString temp (GetIDString(((const char *)s) + pos, finishPos)); if (!ConvertUTF8ToUnicode(temp, pair.ID)) return false; if (finishPos == 0) @@ -90,15 +90,14 @@ bool GetTextConfig(const AString &s, CObjectVector &pairs) c = s[pos++]; switch (c) { - case 'n': message += '\n'; break; - case 't': message += '\t'; break; - case '\\': message += '\\'; break; - case '\"': message += '\"'; break; - default: message += '\\'; message += c; break; + case 'n': c = '\n'; break; + case 't': c = '\t'; break; + case '\\': break; + case '\"': break; + default: message += '\\'; break; } } - else - message += c; + message += c; } if (!ConvertUTF8ToUnicode(message, pair.String)) return false; @@ -107,17 +106,17 @@ bool GetTextConfig(const AString &s, CObjectVector &pairs) return true; } -int FindTextConfigItem(const CObjectVector &pairs, const UString &id) throw() +int FindTextConfigItem(const CObjectVector &pairs, const char *id) throw() { FOR_VECTOR (i, pairs) - if (pairs[i].ID == id) - return i; + if (pairs[i].ID.IsEqualTo(id)) + return (int)i; return -1; } -UString GetTextConfigValue(const CObjectVector &pairs, const UString &id) +UString GetTextConfigValue(const CObjectVector &pairs, const char *id) { - int index = FindTextConfigItem(pairs, id); + const int index = FindTextConfigItem(pairs, id); if (index < 0) return UString(); return pairs[index].String; diff --git a/src/plugins/7zip/7za/cpp/Common/TextConfig.h b/src/plugins/7zip/7za/cpp/Common/TextConfig.h index 0129ad96..2263a442 100644 --- a/src/plugins/7zip/7za/cpp/Common/TextConfig.h +++ b/src/plugins/7zip/7za/cpp/Common/TextConfig.h @@ -1,7 +1,7 @@ // Common/TextConfig.h -#ifndef __COMMON_TEXT_CONFIG_H -#define __COMMON_TEXT_CONFIG_H +#ifndef ZIP7_INC_COMMON_TEXT_CONFIG_H +#define ZIP7_INC_COMMON_TEXT_CONFIG_H #include "MyString.h" @@ -13,7 +13,7 @@ struct CTextConfigPair bool GetTextConfig(const AString &text, CObjectVector &pairs); -int FindTextConfigItem(const CObjectVector &pairs, const UString &id) throw(); -UString GetTextConfigValue(const CObjectVector &pairs, const UString &id); +int FindTextConfigItem(const CObjectVector &pairs, const char *id) throw(); +UString GetTextConfigValue(const CObjectVector &pairs, const char *id); #endif diff --git a/src/plugins/7zip/7za/cpp/Common/UTFConvert.cpp b/src/plugins/7zip/7za/cpp/Common/UTFConvert.cpp index b772164a..509de76f 100644 --- a/src/plugins/7zip/7za/cpp/Common/UTFConvert.cpp +++ b/src/plugins/7zip/7za/cpp/Common/UTFConvert.cpp @@ -2,94 +2,354 @@ #include "StdAfx.h" +// #include + #include "MyTypes.h" #include "UTFConvert.h" -#ifdef _WIN32 -#define _WCHART_IS_16BIT 1 + +#ifndef Z7_WCHART_IS_16BIT +#ifndef __APPLE__ + // we define it if the system supports files with non-utf8 symbols: + #define MY_UTF8_RAW_NON_UTF8_SUPPORTED +#endif #endif /* - _UTF8_START(n) - is a base value for start byte (head), if there are (n) additional bytes after start byte + MY_UTF8_START(n) - is a base value for start byte (head), if there are (n) additional bytes after start byte - n : _UTF8_START(n) : Bits of code point + n : MY_UTF8_START(n) : Bits of code point 0 : 0x80 : : unused 1 : 0xC0 : 11 : 2 : 0xE0 : 16 : Basic Multilingual Plane 3 : 0xF0 : 21 : Unicode space - 3 : 0xF8 : 26 : - 5 : 0xFC : 31 : UCS-4 + 4 : 0xF8 : 26 : + 5 : 0xFC : 31 : UCS-4 : wcstombs() in ubuntu is limited to that value 6 : 0xFE : 36 : We can use it, if we want to encode any 32-bit value 7 : 0xFF : */ -#define _UTF8_START(n) (0x100 - (1 << (7 - (n)))) +#define MY_UTF8_START(n) (0x100 - (1 << (7 - (n)))) + +#define MY_UTF8_HEAD_PARSE2(n) \ + if (c < MY_UTF8_START((n) + 1)) \ + { numBytes = (n); val -= MY_UTF8_START(n); } + +#ifndef Z7_WCHART_IS_16BIT + +/* + if (wchar_t is 32-bit), we can support large points in long UTF-8 sequence, + when we convert wchar_t strings to UTF-8: + (_UTF8_NUM_TAIL_BYTES_MAX == 3) : (21-bits points) - Unicode + (_UTF8_NUM_TAIL_BYTES_MAX == 5) : (31-bits points) - UCS-4 + (_UTF8_NUM_TAIL_BYTES_MAX == 6) : (36-bit hack) +*/ + +#define MY_UTF8_NUM_TAIL_BYTES_MAX 5 +#endif + +/* +#define MY_UTF8_HEAD_PARSE \ + UInt32 val = c; \ + MY_UTF8_HEAD_PARSE2(1) \ + else MY_UTF8_HEAD_PARSE2(2) \ + else MY_UTF8_HEAD_PARSE2(3) \ + else MY_UTF8_HEAD_PARSE2(4) \ + else MY_UTF8_HEAD_PARSE2(5) \ + #if MY_UTF8_NUM_TAIL_BYTES_MAX >= 6 + else MY_UTF8_HEAD_PARSE2(6) + #endif +*/ + +#define MY_UTF8_HEAD_PARSE_MAX_3_BYTES \ + UInt32 val = c; \ + MY_UTF8_HEAD_PARSE2(1) \ + else MY_UTF8_HEAD_PARSE2(2) \ + else { numBytes = 3; val -= MY_UTF8_START(3); } + + +#define MY_UTF8_RANGE(n) (((UInt32)1) << ((n) * 5 + 6)) + + +#define START_POINT_FOR_SURROGATE 0x10000 + + +/* we use 128 bytes block in 16-bit BMP-PLANE to encode non-UTF-8 Escapes + Also we can use additional HIGH-PLANE (we use 21-bit points above 0x1f0000) + to simplify internal intermediate conversion in Linux: + RAW-UTF-8 <-> internal wchar_t utf-16 strings <-> RAW-UTF-UTF-8 +*/ + + +#if defined(Z7_WCHART_IS_16BIT) + +#define UTF_ESCAPE_PLANE 0 + +#else + +/* +we can place 128 ESCAPE chars to + ef 80 - ee be 80 (3-bytes utf-8) : similar to WSL + ef ff - ee bf bf + +1f ef 80 - f7 be be 80 (4-bytes utf-8) : last 4-bytes utf-8 plane (out of Unicode) +1f ef ff - f7 be bf bf (4-bytes utf-8) : last 4-bytes utf-8 plane (out of Unicode) +*/ + +// #define UTF_ESCAPE_PLANE_HIGH (0x1f << 16) +// #define UTF_ESCAPE_PLANE UTF_ESCAPE_PLANE_HIGH +#define UTF_ESCAPE_PLANE 0 + +/* + if (Z7_UTF_FLAG_FROM_UTF8_USE_ESCAPE is set) + { + if (UTF_ESCAPE_PLANE is UTF_ESCAPE_PLANE_HIGH) + { + we can restore any 8-bit Escape from ESCAPE-PLANE-21 plane. + But ESCAPE-PLANE-21 point cannot be stored to utf-16 (7z archive) + So we still need a way to extract 8-bit Escapes and BMP-Escapes-8 + from same BMP-Escapes-16 stored in 7z. + And if we want to restore any 8-bit from 7z archive, + we still must use Z7_UTF_FLAG_FROM_UTF8_BMP_ESCAPE_CONVERT for (utf-8 -> utf-16) + Also we need additional Conversions to tranform from utf-16 to utf-16-With-Escapes-21 + } + else (UTF_ESCAPE_PLANE == 0) + { + we must convert original 3-bytes utf-8 BMP-Escape point to sequence + of 3 BMP-Escape-16 points with Z7_UTF_FLAG_FROM_UTF8_BMP_ESCAPE_CONVERT + so we can extract original RAW-UTF-8 from UTFD-16 later. + } + } +*/ + +#endif + + + +#define UTF_ESCAPE_BASE 0xef00 + + +#ifdef UTF_ESCAPE_BASE +#define IS_ESCAPE_POINT(v, plane) (((v) & (UInt32)0xffffff80) == (plane) + UTF_ESCAPE_BASE + 0x80) +#endif + +#define IS_SURROGATE_POINT(v) (((v) & (UInt32)0xfffff800) == 0xd800) +#define IS_LOW_SURROGATE_POINT(v) (((v) & (UInt32)0xfffffc00) == 0xdc00) + + +#define UTF_ERROR_UTF8_CHECK \ + { NonUtf = true; continue; } + +void CUtf8Check::Check_Buf(const char *src, size_t size) throw() +{ + Clear(); + // Byte maxByte = 0; + + for (;;) + { + if (size == 0) + break; + + const Byte c = (Byte)(*src++); + size--; + + if (c == 0) + { + ZeroChar = true; + continue; + } + + /* + if (c > maxByte) + maxByte = c; + */ + + if (c < 0x80) + continue; + + if (c < 0xc0 + 2) + UTF_ERROR_UTF8_CHECK + + unsigned numBytes; + UInt32 val = c; + MY_UTF8_HEAD_PARSE2(1) + else MY_UTF8_HEAD_PARSE2(2) + else MY_UTF8_HEAD_PARSE2(3) + else MY_UTF8_HEAD_PARSE2(4) + else MY_UTF8_HEAD_PARSE2(5) + else + { + UTF_ERROR_UTF8_CHECK + } + + unsigned pos = 0; + do + { + if (pos == size) + break; + unsigned c2 = (Byte)src[pos]; + c2 -= 0x80; + if (c2 >= 0x40) + break; + val <<= 6; + val |= c2; + if (pos == 0) + if (val < (((unsigned)1 << 7) >> numBytes)) + break; + pos++; + } + while (--numBytes); + + if (numBytes != 0) + { + if (pos == size) + Truncated = true; + else + UTF_ERROR_UTF8_CHECK + } -#define _UTF8_HEAD_PARSE2(n) if (c < _UTF8_START((n) + 1)) { numBytes = (n); c -= _UTF8_START(n); } + #ifdef UTF_ESCAPE_BASE + if (IS_ESCAPE_POINT(val, 0)) + Escape = true; + #endif -#define _UTF8_HEAD_PARSE \ - _UTF8_HEAD_PARSE2(1) \ - else _UTF8_HEAD_PARSE2(2) \ - else _UTF8_HEAD_PARSE2(3) \ - else _UTF8_HEAD_PARSE2(4) \ - else _UTF8_HEAD_PARSE2(5) \ + if (MaxHighPoint < val) + MaxHighPoint = val; - // else _UTF8_HEAD_PARSE2(6) + if (IS_SURROGATE_POINT(val)) + SingleSurrogate = true; + + src += pos; + size -= pos; + } + // MaxByte = maxByte; +} + +bool Check_UTF8_Buf(const char *src, size_t size, bool allowReduced) throw() +{ + CUtf8Check check; + check.Check_Buf(src, size); + return check.IsOK(allowReduced); +} + +/* +bool CheckUTF8_chars(const char *src, bool allowReduced) throw() +{ + CUtf8Check check; + check.CheckBuf(src, strlen(src)); + return check.IsOK(allowReduced); +} +*/ + +bool CheckUTF8_AString(const AString &s) throw() +{ + CUtf8Check check; + check.Check_AString(s); + return check.IsOK(); +} + + +/* bool CheckUTF8(const char *src, bool allowReduced) throw() { + // return Check_UTF8_Buf(src, strlen(src), allowReduced); + for (;;) { - Byte c = *src++; + const Byte c = (Byte)(*src++); if (c == 0) return true; if (c < 0x80) continue; - if (c < 0xC0) // (c < 0xC0 + 2) // if we support only optimal encoding chars + if (c < 0xC0 + 2 || c >= 0xf5) return false; unsigned numBytes; - _UTF8_HEAD_PARSE + MY_UTF8_HEAD_PARSE else return false; - - UInt32 val = c; + unsigned pos = 0; + do { - Byte c2 = *src++; + Byte c2 = (Byte)(*src++); if (c2 < 0x80 || c2 >= 0xC0) return allowReduced && c2 == 0; val <<= 6; val |= (c2 - 0x80); + pos++; } while (--numBytes); - + + if (val < MY_UTF8_RANGE(pos - 1)) + return false; + if (val >= 0x110000) return false; } } +*/ + +// in case of UTF-8 error we have two ways: +// 21.01- : old : 0xfffd: REPLACEMENT CHARACTER : old version +// 21.02+ : new : 0xef00 + (c) : similar to WSL scheme for low symbols + +#define UTF_REPLACEMENT_CHAR 0xfffd + + + +#define UTF_ESCAPE(c) \ + ((flags & Z7_UTF_FLAG_FROM_UTF8_USE_ESCAPE) ? \ + UTF_ESCAPE_PLANE + UTF_ESCAPE_BASE + (c) : UTF_REPLACEMENT_CHAR) + +/* +#define UTF_HARD_ERROR_UTF8 + { if (dest) dest[destPos] = (wchar_t)UTF_ESCAPE(c); \ + destPos++; ok = false; continue; } +*/ + +// we ignore utf errors, and don't change (ok) variable! + +#define UTF_ERROR_UTF8 \ + { if (dest) dest[destPos] = (wchar_t)UTF_ESCAPE(c); \ + destPos++; continue; } + +// we store UTF-16 in wchar_t strings. So we use surrogates for big unicode points: +// for debug puposes only we can store UTF-32 in wchar_t: +// #define START_POINT_FOR_SURROGATE ((UInt32)0 - 1) -#define _ERROR_UTF8 \ - { if (dest) dest[destPos] = (wchar_t)0xFFFD; destPos++; ok = false; continue; } -static bool Utf8_To_Utf16(wchar_t *dest, size_t *destLen, const char *src, const char *srcLim) throw() +/* + WIN32 MultiByteToWideChar(CP_UTF8) emits 0xfffd point, if utf-8 error was found. + Ant it can emit single 0xfffd from 2 src bytes. + It doesn't emit single 0xfffd from 3-4 src bytes. + We can + 1) emit Escape point for each incorrect byte. So we can data recover later + 2) emit 0xfffd for each incorrect byte. + That scheme is similar to Escape scheme, but we emit 0xfffd + instead of each Escape point. + 3) emit single 0xfffd from 1-2 incorrect bytes, as WIN32 MultiByteToWideChar scheme +*/ + +static bool Utf8_To_Utf16(wchar_t *dest, size_t *destLen, const char *src, const char *srcLim, unsigned flags) throw() { size_t destPos = 0; bool ok = true; for (;;) { - Byte c; if (src == srcLim) { *destLen = destPos; return ok; } - c = *src++; + + const Byte c = (Byte)(*src++); if (c < 0x80) { @@ -98,113 +358,195 @@ static bool Utf8_To_Utf16(wchar_t *dest, size_t *destLen, const char *src, const destPos++; continue; } - if (c < 0xC0) - _ERROR_UTF8 + + if (c < 0xc0 + 2 + || c >= 0xf5) // it's limit for 0x140000 unicode codes : win32 compatibility + { + UTF_ERROR_UTF8 + } unsigned numBytes; - _UTF8_HEAD_PARSE - else - _ERROR_UTF8 - - UInt32 val = c; + MY_UTF8_HEAD_PARSE_MAX_3_BYTES + + unsigned pos = 0; do { - Byte c2; - if (src == srcLim) + if (src + pos == srcLim) break; - c2 = *src; - if (c2 < 0x80 || c2 >= 0xC0) + unsigned c2 = (Byte)src[pos]; + c2 -= 0x80; + if (c2 >= 0x40) break; - src++; val <<= 6; - val |= (c2 - 0x80); + val |= c2; + pos++; + if (pos == 1) + { + if (val < (((unsigned)1 << 7) >> numBytes)) + break; + if (numBytes == 2) + { + if (flags & Z7_UTF_FLAG_FROM_UTF8_SURROGATE_ERROR) + if ((val & (0xF800 >> 6)) == (0xd800 >> 6)) + break; + } + else if (numBytes == 3 && val >= (0x110000 >> 12)) + break; + } } while (--numBytes); if (numBytes != 0) - _ERROR_UTF8 + { + if ((flags & Z7_UTF_FLAG_FROM_UTF8_USE_ESCAPE) == 0) + { + // the following code to emit the 0xfffd chars as win32 Utf8 function. + // disable the folling line, if you need 0xfffd for each incorrect byte as in Escape mode + src += pos; + } + UTF_ERROR_UTF8 + } + + /* + if (val < MY_UTF8_RANGE(pos - 1)) + UTF_ERROR_UTF8 + */ + + #ifdef UTF_ESCAPE_BASE + + if ((flags & Z7_UTF_FLAG_FROM_UTF8_BMP_ESCAPE_CONVERT) + && IS_ESCAPE_POINT(val, 0)) + { + // We will emit 3 utf16-Escape-16-21 points from one Escape-16 point (3 bytes) + UTF_ERROR_UTF8 + } + + #endif - if (val < 0x10000) + /* + We don't expect virtual Escape-21 points in UTF-8 stream. + And we don't check for Escape-21. + So utf8-Escape-21 will be converted to another 3 utf16-Escape-21 points. + Maybe we could convert virtual utf8-Escape-21 to one utf16-Escape-21 point in some cases? + */ + + if (val < START_POINT_FOR_SURROGATE) { + /* + if ((flags & Z7_UTF_FLAG_FROM_UTF8_SURROGATE_ERROR) + && IS_SURROGATE_POINT(val)) + { + // We will emit 3 utf16-Escape-16-21 points from one Surrogate-16 point (3 bytes) + UTF_ERROR_UTF8 + } + */ if (dest) dest[destPos] = (wchar_t)val; destPos++; } else { - val -= 0x10000; - if (val >= 0x100000) - _ERROR_UTF8 + /* + if (val >= 0x110000) + { + // We will emit utf16-Escape-16-21 point from each source byte + UTF_ERROR_UTF8 + } + */ if (dest) { - dest[destPos + 0] = (wchar_t)(0xD800 + (val >> 10)); - dest[destPos + 1] = (wchar_t)(0xDC00 + (val & 0x3FF)); + dest[destPos + 0] = (wchar_t)(0xd800 - (0x10000 >> 10) + (val >> 10)); + dest[destPos + 1] = (wchar_t)(0xdc00 + (val & 0x3ff)); } destPos += 2; } + src += pos; } } -#define _UTF8_RANGE(n) (((UInt32)1) << ((n) * 5 + 6)) -#define _UTF8_HEAD(n, val) ((char)(_UTF8_START(n) + (val >> (6 * (n))))) -#define _UTF8_CHAR(n, val) ((char)(0x80 + (((val) >> (6 * (n))) & 0x3F))) -static size_t Utf16_To_Utf8_Calc(const wchar_t *src, const wchar_t *srcLim) +#define MY_UTF8_HEAD(n, val) ((char)(MY_UTF8_START(n) + (val >> (6 * (n))))) +#define MY_UTF8_CHAR(n, val) ((char)(0x80 + (((val) >> (6 * (n))) & 0x3F))) + +static size_t Utf16_To_Utf8_Calc(const wchar_t *src, const wchar_t *srcLim, unsigned flags) { - size_t size = srcLim - src; + size_t size = (size_t)(srcLim - src); for (;;) { if (src == srcLim) return size; - UInt32 val = *src++; + UInt32 val = (UInt32)(*src++); if (val < 0x80) continue; - if (val < _UTF8_RANGE(1)) + if (val < MY_UTF8_RANGE(1)) { size++; continue; } - if (val >= 0xD800 && val < 0xDC00 && src != srcLim) + #ifdef UTF_ESCAPE_BASE + + #if UTF_ESCAPE_PLANE != 0 + if (flags & Z7_UTF_FLAG_TO_UTF8_PARSE_HIGH_ESCAPE) + if (IS_ESCAPE_POINT(val, UTF_ESCAPE_PLANE)) + continue; + #endif + + if (flags & Z7_UTF_FLAG_TO_UTF8_EXTRACT_BMP_ESCAPE) + if (IS_ESCAPE_POINT(val, 0)) + continue; + + #endif + + if (IS_SURROGATE_POINT(val)) { - UInt32 c2 = *src; - if (c2 >= 0xDC00 && c2 < 0xE000) + // it's hack to UTF-8 encoding + + if (val < 0xdc00 && src != srcLim) { - src++; - size += 2; - continue; + const UInt32 c2 = (UInt32)*src; + if (c2 >= 0xdc00 && c2 < 0xe000) + src++; } + size += 2; + continue; } - #ifdef _WCHART_IS_16BIT + #ifdef Z7_WCHART_IS_16BIT size += 2; #else - if (val < _UTF8_RANGE(2)) size += 2; - else if (val < _UTF8_RANGE(3)) size += 3; - else if (val < _UTF8_RANGE(4)) size += 4; - else if (val < _UTF8_RANGE(5)) size += 5; - else size += 6; + if (val < MY_UTF8_RANGE(2)) size += 2; + else if (val < MY_UTF8_RANGE(3)) size += 3; + else if (val < MY_UTF8_RANGE(4)) size += 4; + else if (val < MY_UTF8_RANGE(5)) size += 5; + else + #if MY_UTF8_NUM_TAIL_BYTES_MAX >= 6 + size += 6; + #else + size += 3; + #endif #endif } } -static char *Utf16_To_Utf8(char *dest, const wchar_t *src, const wchar_t *srcLim) + +static char *Utf16_To_Utf8(char *dest, const wchar_t *src, const wchar_t *srcLim, unsigned flags) { for (;;) { if (src == srcLim) return dest; - UInt32 val = *src++; + UInt32 val = (UInt32)*src++; if (val < 0x80) { @@ -212,51 +554,97 @@ static char *Utf16_To_Utf8(char *dest, const wchar_t *src, const wchar_t *srcLim continue; } - if (val < _UTF8_RANGE(1)) + if (val < MY_UTF8_RANGE(1)) { - dest[0] = _UTF8_HEAD(1, val); - dest[1] = _UTF8_CHAR(0, val); + dest[0] = MY_UTF8_HEAD(1, val); + dest[1] = MY_UTF8_CHAR(0, val); dest += 2; continue; } - if (val >= 0xD800 && val < 0xDC00 && src != srcLim) - { - UInt32 c2 = *src; - if (c2 >= 0xDC00 && c2 < 0xE000) + #ifdef UTF_ESCAPE_BASE + + #if UTF_ESCAPE_PLANE != 0 + /* + if (wchar_t is 32-bit) + && (Z7_UTF_FLAG_TO_UTF8_PARSE_HIGH_ESCAPE is set) + && (point is virtual escape plane) + we extract 8-bit byte from virtual HIGH-ESCAPE PLANE. + */ + if (flags & Z7_UTF_FLAG_TO_UTF8_PARSE_HIGH_ESCAPE) + if (IS_ESCAPE_POINT(val, UTF_ESCAPE_PLANE)) { - src++; - val = (((val - 0xD800) << 10) | (c2 - 0xDC00)) + 0x10000; - dest[0] = _UTF8_HEAD(3, val); - dest[1] = _UTF8_CHAR(2, val); - dest[2] = _UTF8_CHAR(1, val); - dest[3] = _UTF8_CHAR(0, val); - dest += 4; + *dest++ = (char)(val); + continue; + } + #endif // UTF_ESCAPE_PLANE != 0 + + /* if (Z7_UTF_FLAG_TO_UTF8_EXTRACT_BMP_ESCAPE is defined) + we extract 8-bit byte from BMP-ESCAPE PLANE. */ + + if (flags & Z7_UTF_FLAG_TO_UTF8_EXTRACT_BMP_ESCAPE) + if (IS_ESCAPE_POINT(val, 0)) + { + *dest++ = (char)(val); continue; } - } - #ifndef _WCHART_IS_16BIT - if (val < _UTF8_RANGE(2)) + #endif // UTF_ESCAPE_BASE + + if (IS_SURROGATE_POINT(val)) + { + // it's hack to UTF-8 encoding + if (val < 0xdc00 && src != srcLim) + { + const UInt32 c2 = (UInt32)*src; + if (IS_LOW_SURROGATE_POINT(c2)) + { + src++; + val = (((val - 0xd800) << 10) | (c2 - 0xdc00)) + 0x10000; + dest[0] = MY_UTF8_HEAD(3, val); + dest[1] = MY_UTF8_CHAR(2, val); + dest[2] = MY_UTF8_CHAR(1, val); + dest[3] = MY_UTF8_CHAR(0, val); + dest += 4; + continue; + } + } + if (flags & Z7_UTF_FLAG_TO_UTF8_SURROGATE_ERROR) + val = UTF_REPLACEMENT_CHAR; // WIN32 function does it + } + + #ifndef Z7_WCHART_IS_16BIT + if (val < MY_UTF8_RANGE(2)) #endif { - dest[0] = _UTF8_HEAD(2, val); - dest[1] = _UTF8_CHAR(1, val); - dest[2] = _UTF8_CHAR(0, val); + dest[0] = MY_UTF8_HEAD(2, val); + dest[1] = MY_UTF8_CHAR(1, val); + dest[2] = MY_UTF8_CHAR(0, val); dest += 3; continue; } - #ifndef _WCHART_IS_16BIT + #ifndef Z7_WCHART_IS_16BIT - UInt32 b; + // we don't expect this case. so we can throw exception + // throw 20210407; + + char b; unsigned numBits; - if (val < _UTF8_RANGE(3)) { numBits = 6 * 3; b = _UTF8_HEAD(3, val); } - else if (val < _UTF8_RANGE(4)) { numBits = 6 * 4; b = _UTF8_HEAD(4, val); } - else if (val < _UTF8_RANGE(5)) { numBits = 6 * 5; b = _UTF8_HEAD(5, val); } - else { numBits = 6 * 6; b = _UTF8_START(6); } - - *dest++ = (Byte)b; + if (val < MY_UTF8_RANGE(3)) { numBits = 6 * 3; b = MY_UTF8_HEAD(3, val); } + else if (val < MY_UTF8_RANGE(4)) { numBits = 6 * 4; b = MY_UTF8_HEAD(4, val); } + else if (val < MY_UTF8_RANGE(5)) { numBits = 6 * 5; b = MY_UTF8_HEAD(5, val); } + #if MY_UTF8_NUM_TAIL_BYTES_MAX >= 6 + else { numBits = 6 * 6; b = (char)MY_UTF8_START(6); } + #else + else + { + val = UTF_REPLACEMENT_CHAR; + { numBits = 6 * 3; b = MY_UTF8_HEAD(3, val); } + } + #endif + + *dest++ = b; do { @@ -269,20 +657,207 @@ static char *Utf16_To_Utf8(char *dest, const wchar_t *src, const wchar_t *srcLim } } -bool ConvertUTF8ToUnicode(const AString &src, UString &dest) +bool Convert_UTF8_Buf_To_Unicode(const char *src, size_t srcSize, UString &dest, unsigned flags) { dest.Empty(); size_t destLen = 0; - Utf8_To_Utf16(NULL, &destLen, src, src.Ptr(src.Len())); - bool res = Utf8_To_Utf16(dest.GetBuf((unsigned)destLen), &destLen, src, src.Ptr(src.Len())); + Utf8_To_Utf16(NULL, &destLen, src, src + srcSize, flags); + bool res = Utf8_To_Utf16(dest.GetBuf((unsigned)destLen), &destLen, src, src + srcSize, flags); dest.ReleaseBuf_SetEnd((unsigned)destLen); return res; } -void ConvertUnicodeToUTF8(const UString &src, AString &dest) +bool ConvertUTF8ToUnicode_Flags(const AString &src, UString &dest, unsigned flags) +{ + return Convert_UTF8_Buf_To_Unicode(src, src.Len(), dest, flags); +} + + +static +unsigned g_UTF8_To_Unicode_Flags = + Z7_UTF_FLAG_FROM_UTF8_USE_ESCAPE + #ifndef Z7_WCHART_IS_16BIT + | Z7_UTF_FLAG_FROM_UTF8_SURROGATE_ERROR + #ifdef MY_UTF8_RAW_NON_UTF8_SUPPORTED + | Z7_UTF_FLAG_FROM_UTF8_BMP_ESCAPE_CONVERT + #endif + #endif + ; + + +/* +bool ConvertUTF8ToUnicode_boolRes(const AString &src, UString &dest) { + return ConvertUTF8ToUnicode_Flags(src, dest, g_UTF8_To_Unicode_Flags); +} +*/ + +bool ConvertUTF8ToUnicode(const AString &src, UString &dest) +{ + return ConvertUTF8ToUnicode_Flags(src, dest, g_UTF8_To_Unicode_Flags); +} + +void Print_UString(const UString &a); + +void ConvertUnicodeToUTF8_Flags(const UString &src, AString &dest, unsigned flags) +{ + /* + if (src.Len()== 24) + throw "202104"; + */ dest.Empty(); - size_t destLen = Utf16_To_Utf8_Calc(src, src.Ptr(src.Len())); - Utf16_To_Utf8(dest.GetBuf((unsigned)destLen), src, src.Ptr(src.Len())); + const size_t destLen = Utf16_To_Utf8_Calc(src, src.Ptr(src.Len()), flags); + char *destStart = dest.GetBuf((unsigned)destLen); + const char *destEnd = Utf16_To_Utf8(destStart, src, src.Ptr(src.Len()), flags); dest.ReleaseBuf_SetEnd((unsigned)destLen); + // printf("\nlen = %d\n", src.Len()); + if (destLen != (size_t)(destEnd - destStart)) + { + /* + // dest.ReleaseBuf_SetEnd((unsigned)(destEnd - destStart)); + printf("\nlen = %d\n", (unsigned)destLen); + printf("\n(destEnd - destStart) = %d\n", (unsigned)(destEnd - destStart)); + printf("\n"); + // Print_UString(src); + printf("\n"); + // printf("\nlen = %d\n", destLen); + */ + throw 20210406; + } +} + + + +unsigned g_Unicode_To_UTF8_Flags = + // Z7_UTF_FLAG_TO_UTF8_PARSE_HIGH_ESCAPE + 0 + #ifndef _WIN32 + #ifdef MY_UTF8_RAW_NON_UTF8_SUPPORTED + | Z7_UTF_FLAG_TO_UTF8_EXTRACT_BMP_ESCAPE + #else + | Z7_UTF_FLAG_TO_UTF8_SURROGATE_ERROR + #endif + #endif + ; + +void ConvertUnicodeToUTF8(const UString &src, AString &dest) +{ + ConvertUnicodeToUTF8_Flags(src, dest, g_Unicode_To_UTF8_Flags); +} + +void Convert_Unicode_To_UTF8_Buf(const UString &src, CByteBuffer &dest) +{ + const unsigned flags = g_Unicode_To_UTF8_Flags; + dest.Free(); + const size_t destLen = Utf16_To_Utf8_Calc(src, src.Ptr(src.Len()), flags); + dest.Alloc(destLen); + const char *destEnd = Utf16_To_Utf8((char *)(void *)(Byte *)dest, src, src.Ptr(src.Len()), flags); + if (destLen != (size_t)(destEnd - (char *)(void *)(Byte *)dest)) + throw 202104; +} + +/* + +#ifndef _WIN32 +void Convert_UTF16_To_UTF32(const UString &src, UString &dest) +{ + dest.Empty(); + for (size_t i = 0; i < src.Len();) + { + wchar_t c = src[i++]; + if (c >= 0xd800 && c < 0xdc00 && i < src.Len()) + { + const wchar_t c2 = src[i]; + if (c2 >= 0xdc00 && c2 < 0xe000) + { + // printf("\nSurragate [%d]: %4x %4x -> ", i, (int)c, (int)c2); + c = 0x10000 + ((c & 0x3ff) << 10) + (c2 & 0x3ff); + // printf("%4x\n", (int)c); + i++; + } + } + dest += c; + } +} + +void Convert_UTF32_To_UTF16(const UString &src, UString &dest) +{ + dest.Empty(); + for (size_t i = 0; i < src.Len();) + { + wchar_t w = src[i++]; + if (w >= 0x10000 && w < 0x110000) + { + w -= 0x10000; + dest += (wchar_t)((unsigned)0xd800 + (((unsigned)w >> 10) & 0x3ff)); + w = 0xdc00 + (w & 0x3ff); + } + dest += w; + } +} + +bool UTF32_IsThere_BigPoint(const UString &src) +{ + for (size_t i = 0; i < src.Len();) + { + const UInt32 c = (UInt32)src[i++]; + if (c >= 0x110000) + return true; + } + return false; +} + +bool Unicode_IsThere_BmpEscape(const UString &src) +{ + for (size_t i = 0; i < src.Len();) + { + const UInt32 c = (UInt32)src[i++]; + if (IS_ESCAPE_POINT(c, 0)) + return true; + } + return false; +} + + +#endif + +bool Unicode_IsThere_Utf16SurrogateError(const UString &src) +{ + for (size_t i = 0; i < src.Len();) + { + const UInt32 val = (UInt32)src[i++]; + if (IS_SURROGATE_POINT(val)) + { + // it's hack to UTF-8 encoding + if (val >= 0xdc00 || i == src.Len()) + return true; + const UInt32 c2 = (UInt32)*src; + if (!IS_LOW_SURROGATE_POINT(c2)) + return true; + } + } + return false; } +*/ + +#ifndef Z7_WCHART_IS_16BIT + +void Convert_UnicodeEsc16_To_UnicodeEscHigh +#if UTF_ESCAPE_PLANE == 0 + (UString &) {} +#else + (UString &s) +{ + const unsigned len = s.Len(); + for (unsigned i = 0; i < len; i++) + { + wchar_t c = s[i]; + if (IS_ESCAPE_POINT(c, 0)) + { + c += UTF_ESCAPE_PLANE; + s.ReplaceOneCharAtPos(i, c); + } + } +} +#endif +#endif diff --git a/src/plugins/7zip/7za/cpp/Common/UTFConvert.h b/src/plugins/7zip/7za/cpp/Common/UTFConvert.h index 827f3dcf..94a8024f 100644 --- a/src/plugins/7zip/7za/cpp/Common/UTFConvert.h +++ b/src/plugins/7zip/7za/cpp/Common/UTFConvert.h @@ -1,12 +1,384 @@ // Common/UTFConvert.h -#ifndef __COMMON_UTF_CONVERT_H -#define __COMMON_UTF_CONVERT_H +#ifndef ZIP7_INC_COMMON_UTF_CONVERT_H +#define ZIP7_INC_COMMON_UTF_CONVERT_H +#include "MyBuffer.h" #include "MyString.h" -bool CheckUTF8(const char *src, bool allowReduced = false) throw(); -bool ConvertUTF8ToUnicode(const AString &utfString, UString &resultString); -void ConvertUnicodeToUTF8(const UString &unicodeString, AString &resultString); +struct CUtf8Check +{ + // Byte MaxByte; // in original src stream + bool NonUtf; + bool ZeroChar; + bool SingleSurrogate; + bool Escape; + bool Truncated; + UInt32 MaxHighPoint; // only for points >= 0x80 + + CUtf8Check() { Clear(); } + + void Clear() + { + // MaxByte = 0; + NonUtf = false; + ZeroChar = false; + SingleSurrogate = false; + Escape = false; + Truncated = false; + MaxHighPoint = 0; + } + + void Update(const CUtf8Check &c) + { + if (c.NonUtf) NonUtf = true; + if (c.ZeroChar) ZeroChar = true; + if (c.SingleSurrogate) SingleSurrogate = true; + if (c.Escape) Escape = true; + if (c.Truncated) Truncated = true; + if (MaxHighPoint < c.MaxHighPoint) MaxHighPoint = c.MaxHighPoint; + } + + void PrintStatus(AString &s) const + { + s.Empty(); + + // s.Add_OptSpaced("MaxByte="); + // s.Add_UInt32(MaxByte); + + if (NonUtf) s.Add_OptSpaced("non-UTF8"); + if (ZeroChar) s.Add_OptSpaced("ZeroChar"); + if (SingleSurrogate) s.Add_OptSpaced("SingleSurrogate"); + if (Escape) s.Add_OptSpaced("Escape"); + if (Truncated) s.Add_OptSpaced("Truncated"); + + if (MaxHighPoint != 0) + { + s.Add_OptSpaced("MaxUnicode="); + s.Add_UInt32(MaxHighPoint); + } + } + + + bool IsOK(bool allowReduced = false) const + { + if (NonUtf || SingleSurrogate || ZeroChar) + return false; + if (MaxHighPoint >= 0x110000) + return false; + if (Truncated && !allowReduced) + return false; + return true; + } + + // it checks full buffer as specified in (size) and it doesn't stop on zero char + void Check_Buf(const char *src, size_t size) throw(); + + void Check_AString(const AString &s) throw() + { + Check_Buf(s.Ptr(), s.Len()); + } +}; + +/* +if (allowReduced == false) - all UTF-8 character sequences must be finished. +if (allowReduced == true) - it allows truncated last character-Utf8-sequence +*/ + +bool Check_UTF8_Buf(const char *src, size_t size, bool allowReduced) throw(); +bool CheckUTF8_AString(const AString &s) throw(); + +#define Z7_UTF_FLAG_FROM_UTF8_SURROGATE_ERROR (1 << 0) +#define Z7_UTF_FLAG_FROM_UTF8_USE_ESCAPE (1 << 1) +#define Z7_UTF_FLAG_FROM_UTF8_BMP_ESCAPE_CONVERT (1 << 2) + +/* +Z7_UTF_FLAG_FROM_UTF8_SURROGATE_ERROR + + if (flag is NOT set) + { + it processes SINGLE-SURROGATE-8 as valid Unicode point. + it converts SINGLE-SURROGATE-8 to SINGLE-SURROGATE-16 + Note: some sequencies of two SINGLE-SURROGATE-8 points + will generate correct SURROGATE-16-PAIR, and + that SURROGATE-16-PAIR later will be converted to correct + UTF8-SURROGATE-21 point. So we don't restore original + STR-8 sequence in that case. + } + + if (flag is set) + { + if (Z7_UTF_FLAG_FROM_UTF8_USE_ESCAPE is defined) + it generates ESCAPE for SINGLE-SURROGATE-8, + if (Z7_UTF_FLAG_FROM_UTF8_USE_ESCAPE is not defined) + it generates U+fffd for SINGLE-SURROGATE-8, + } + + +Z7_UTF_FLAG_FROM_UTF8_USE_ESCAPE + + if (flag is NOT set) + it generates (U+fffd) code for non-UTF-8 (invalid) characters + + if (flag is set) + { + It generates (ESCAPE) codes for NON-UTF-8 (invalid) characters. + And later we can restore original UTF-8-RAW characters from (ESCAPE-16-21) codes. + } + +Z7_UTF_FLAG_FROM_UTF8_BMP_ESCAPE_CONVERT + + if (flag is NOT set) + { + it process ESCAPE-8 points as another Unicode points. + In Linux: ESCAPE-16 will mean two different ESCAPE-8 seqences, + so we need HIGH-ESCAPE-PLANE-21 to restore UTF-8-RAW -> UTF-16 -> UTF-8-RAW + } + + if (flag is set) + { + it generates ESCAPE-16-21 for ESCAPE-8 points + so we can restore UTF-8-RAW -> UTF-16 -> UTF-8-RAW without HIGH-ESCAPE-PLANE-21. + } + + +Main USE CASES with UTF-8 <-> UTF-16 conversions: + + WIN32: UTF-16-RAW -> UTF-8 (Archive) -> UTF-16-RAW + { + set Z7_UTF_FLAG_FROM_UTF8_USE_ESCAPE + Do NOT set Z7_UTF_FLAG_FROM_UTF8_SURROGATE_ERROR + Do NOT set Z7_UTF_FLAG_FROM_UTF8_BMP_ESCAPE_CONVERT + + So we restore original SINGLE-SURROGATE-16 from single SINGLE-SURROGATE-8. + } + + Linux: UTF-8-RAW -> UTF-16 (Intermediate / Archive) -> UTF-8-RAW + { + we want restore original UTF-8-RAW sequence later from that ESCAPE-16. + Set the flags: + Z7_UTF_FLAG_FROM_UTF8_SURROGATE_ERROR + Z7_UTF_FLAG_FROM_UTF8_USE_ESCAPE + Z7_UTF_FLAG_FROM_UTF8_BMP_ESCAPE_CONVERT + } + + MacOS: UTF-8-RAW -> UTF-16 (Intermediate / Archive) -> UTF-8-RAW + { + we want to restore correct UTF-8 without any BMP processing: + Set the flags: + Z7_UTF_FLAG_FROM_UTF8_SURROGATE_ERROR + Z7_UTF_FLAG_FROM_UTF8_USE_ESCAPE + } + +*/ + +// zero char is not allowed in (src) buf +bool Convert_UTF8_Buf_To_Unicode(const char *src, size_t srcSize, UString &dest, unsigned flags = 0); + +bool ConvertUTF8ToUnicode_Flags(const AString &src, UString &dest, unsigned flags = 0); +bool ConvertUTF8ToUnicode(const AString &src, UString &dest); + +#define Z7_UTF_FLAG_TO_UTF8_SURROGATE_ERROR (1 << 8) +#define Z7_UTF_FLAG_TO_UTF8_EXTRACT_BMP_ESCAPE (1 << 9) +// #define Z7_UTF_FLAG_TO_UTF8_PARSE_HIGH_ESCAPE (1 << 10) + +/* +Z7_UTF_FLAG_TO_UTF8_SURROGATE_ERROR + + if (flag is NOT set) + { + we extract SINGLE-SURROGATE as normal UTF-8 + + In Windows : for UTF-16-RAW <-> UTF-8 (archive) <-> UTF-16-RAW in . + + In Linux : + use-case-1: UTF-8 -> UTF-16 -> UTF-8 doesn't generate UTF-16 SINGLE-SURROGATE, + if (Z7_UTF_FLAG_FROM_UTF8_SURROGATE_ERROR) is used. + use-case 2: UTF-16-7z (with SINGLE-SURROGATE from Windows) -> UTF-8 (Linux) + will generate SINGLE-SURROGATE-UTF-8 here. + } + + if (flag is set) + { + we generate UTF_REPLACEMENT_CHAR (0xfffd) for SINGLE_SURROGATE + it can be used for compatibility mode with WIN32 UTF function + or if we want UTF-8 stream without any errors + } + + +Z7_UTF_FLAG_TO_UTF8_EXTRACT_BMP_ESCAPE + + if (flag is NOT set) it doesn't extract raw 8-bit symbol from Escape-Plane-16 + if (flag is set) it extracts raw 8-bit symbol from Escape-Plane-16 + + in Linux we need some way to extract NON-UTF8 RAW 8-bits from BMP (UTF-16 7z archive): + if (we use High-Escape-Plane), we can transfer BMP escapes to High-Escape-Plane. + if (we don't use High-Escape-Plane), we must use Z7_UTF_FLAG_TO_UTF8_EXTRACT_BMP_ESCAPE. + + +Z7_UTF_FLAG_TO_UTF8_PARSE_HIGH_ESCAPE + // that flag affects the code only if (wchar_t is 32-bit) + // that mode with high-escape can be disabled now in UTFConvert.cpp + if (flag is NOT set) + it doesn't extract raw 8-bit symbol from High-Escape-Plane + if (flag is set) + it extracts raw 8-bit symbol from High-Escape-Plane + +Main use cases: + +WIN32 : UTF-16-RAW -> UTF-8 (archive) -> UTF-16-RAW + { + Do NOT set Z7_UTF_FLAG_TO_UTF8_EXTRACT_BMP_ESCAPE. + Do NOT set Z7_UTF_FLAG_TO_UTF8_SURROGATE_ERROR. + So we restore original UTF-16-RAW. + } + +Linix : UTF-8 with Escapes -> UTF-16 (7z archive) -> UTF-8 with Escapes + set Z7_UTF_FLAG_TO_UTF8_EXTRACT_BMP_ESCAPE to extract non-UTF from 7z archive + set Z7_UTF_FLAG_TO_UTF8_PARSE_HIGH_ESCAPE for intermediate UTF-16. + Note: high esacape mode can be ignored now in UTFConvert.cpp + +macOS: + the system doesn't support incorrect UTF-8 in file names. + set Z7_UTF_FLAG_TO_UTF8_SURROGATE_ERROR +*/ + +extern unsigned g_Unicode_To_UTF8_Flags; + +void ConvertUnicodeToUTF8_Flags(const UString &src, AString &dest, unsigned flags = 0); +void ConvertUnicodeToUTF8(const UString &src, AString &dest); + +void Convert_Unicode_To_UTF8_Buf(const UString &src, CByteBuffer &dest); + +/* +#ifndef _WIN32 +void Convert_UTF16_To_UTF32(const UString &src, UString &dest); +void Convert_UTF32_To_UTF16(const UString &src, UString &dest); +bool UTF32_IsThere_BigPoint(const UString &src); +bool Unicode_IsThere_BmpEscape(const UString &src); +#endif + +bool Unicode_IsThere_Utf16SurrogateError(const UString &src); +*/ + +#ifdef Z7_WCHART_IS_16BIT +#define Convert_UnicodeEsc16_To_UnicodeEscHigh(s) +#else +void Convert_UnicodeEsc16_To_UnicodeEscHigh(UString &s); +#endif + +/* +// #include "../../C/CpuArch.h" + +// ---------- Utf16 Little endian functions ---------- + +// We store 16-bit surrogates even in 32-bit WCHARs in Linux. +// So now we don't use the following code: + +#if WCHAR_MAX > 0xffff + +// void *p : pointer to src bytes stream +// size_t len : num Utf16 characters : it can include or not include NULL character + +inline size_t Utf16LE__Get_Num_WCHARs(const void *p, size_t len) +{ + #if WCHAR_MAX > 0xffff + size_t num_wchars = 0; + for (size_t i = 0; i < len; i++) + { + wchar_t c = GetUi16(p); + p = (const void *)((const Byte *)p + 2); + if (c >= 0xd800 && c < 0xdc00 && i + 1 != len) + { + wchar_t c2 = GetUi16(p); + if (c2 >= 0xdc00 && c2 < 0xe000) + { + c = 0x10000 + ((c & 0x3ff) << 10) + (c2 & 0x3ff); + p = (const void *)((const Byte *)p + 2); + i++; + } + } + num_wchars++; + } + return num_wchars; + #else + UNUSED_VAR(p) + return len; + #endif +} + +// #include + +inline wchar_t *Utf16LE__To_WCHARs_Sep(const void *p, size_t len, wchar_t *dest) +{ + for (size_t i = 0; i < len; i++) + { + wchar_t c = GetUi16(p); + p = (const void *)((const Byte *)p + 2); + + #if WCHAR_PATH_SEPARATOR != L'/' + if (c == L'/') + c = WCHAR_PATH_SEPARATOR; + #endif + + #if WCHAR_MAX > 0xffff + + if (c >= 0xd800 && c < 0xdc00 && i + 1 != len) + { + wchar_t c2 = GetUi16(p); + if (c2 >= 0xdc00 && c2 < 0xe000) + { + // printf("\nSurragate : %4x %4x -> ", (int)c, (int)c2); + c = 0x10000 + ((c & 0x3ff) << 10) + (c2 & 0x3ff); + p = (const void *)((const Byte *)p + 2); + i++; + // printf("%4x\n", (int)c); + } + } + + #endif + + *dest++ = c; + } + return dest; +} + + +inline size_t Get_Num_Utf16_chars_from_wchar_string(const wchar_t *p) +{ + size_t num = 0; + for (;;) + { + wchar_t c = *p++; + if (c == 0) + return num; + num += ((c >= 0x10000 && c < 0x110000) ? 2 : 1); + } + return num; +} + +inline Byte *wchars_to_Utf16LE(const wchar_t *p, Byte *dest) +{ + for (;;) + { + wchar_t c = *p++; + if (c == 0) + return dest; + if (c >= 0x10000 && c < 0x110000) + { + SetUi16(dest , (UInt16)(0xd800 + ((c >> 10) & 0x3FF))); + SetUi16(dest + 2, (UInt16)(0xdc00 + ( c & 0x3FF))); + dest += 4; + } + else + { + SetUi16(dest, c); + dest += 2; + } + } +} + +#endif +*/ #endif diff --git a/src/plugins/7zip/7za/cpp/Common/Wildcard.cpp b/src/plugins/7zip/7za/cpp/Common/Wildcard.cpp index 5e6ddfc6..b561a89c 100644 --- a/src/plugins/7zip/7za/cpp/Common/Wildcard.cpp +++ b/src/plugins/7zip/7za/cpp/Common/Wildcard.cpp @@ -4,9 +4,17 @@ #include "Wildcard.h" +extern +bool g_CaseSensitive; bool g_CaseSensitive = #ifdef _WIN32 false; + #elif defined (__APPLE__) + #ifdef TARGET_OS_IPHONE + true; + #else + false; + #endif #else true; #endif @@ -19,19 +27,92 @@ bool IsPath1PrefixedByPath2(const wchar_t *s1, const wchar_t *s2) return IsString1PrefixedByString2_NoCase(s1, s2); } +// #include + +/* +static int MyStringCompare_PathLinux(const wchar_t *s1, const wchar_t *s2) throw() +{ + for (;;) + { + wchar_t c1 = *s1++; + wchar_t c2 = *s2++; + if (c1 != c2) + { + if (c1 == 0) return -1; + if (c2 == 0) return 1; + if (c1 == '/') c1 = 0; + if (c2 == '/') c2 = 0; + if (c1 < c2) return -1; + if (c1 > c2) return 1; + continue; + } + if (c1 == 0) return 0; + } +} +*/ + +static int MyStringCompare_Path(const wchar_t *s1, const wchar_t *s2) throw() +{ + for (;;) + { + wchar_t c1 = *s1++; + wchar_t c2 = *s2++; + if (c1 != c2) + { + if (c1 == 0) return -1; + if (c2 == 0) return 1; + if (IS_PATH_SEPAR(c1)) c1 = 0; + if (IS_PATH_SEPAR(c2)) c2 = 0; + if (c1 < c2) return -1; + if (c1 > c2) return 1; + continue; + } + if (c1 == 0) return 0; + } +} + +static int MyStringCompareNoCase_Path(const wchar_t *s1, const wchar_t *s2) throw() +{ + for (;;) + { + wchar_t c1 = *s1++; + wchar_t c2 = *s2++; + if (c1 != c2) + { + if (c1 == 0) return -1; + if (c2 == 0) return 1; + if (IS_PATH_SEPAR(c1)) c1 = 0; + if (IS_PATH_SEPAR(c2)) c2 = 0; + c1 = MyCharUpper(c1); + c2 = MyCharUpper(c2); + if (c1 < c2) return -1; + if (c1 > c2) return 1; + continue; + } + if (c1 == 0) return 0; + } +} + int CompareFileNames(const wchar_t *s1, const wchar_t *s2) STRING_UNICODE_THROW { + /* + printf("\nCompareFileNames"); + printf("\n S1: %ls", s1); + printf("\n S2: %ls", s2); + printf("\n"); + */ + // 21.07 : we parse PATH_SEPARATOR so: 0 < PATH_SEPARATOR < 1 if (g_CaseSensitive) - return wcscmp(s1, s2); - return MyStringCompareNoCase(s1, s2); + return MyStringCompare_Path(s1, s2); + return MyStringCompareNoCase_Path(s1, s2); } #ifndef USE_UNICODE_FSTRING int CompareFileNames(const char *s1, const char *s2) { - if (g_CaseSensitive) - return wcscmp(fs2us(s1), fs2us(s2)); - return MyStringCompareNoCase(fs2us(s1), fs2us(s2)); + const UString u1 = fs2us(s1); + const UString u2 = fs2us(s2); + return CompareFileNames(u1, u2); } #endif @@ -44,8 +125,8 @@ static bool EnhancedMaskTest(const wchar_t *mask, const wchar_t *name) { for (;;) { - wchar_t m = *mask; - wchar_t c = *name; + const wchar_t m = *mask; + const wchar_t c = *name; if (m == 0) return (c == 0); if (m == '*') @@ -120,24 +201,16 @@ void SplitPathToParts_Smart(const UString &path, UString &dirPrefix, UString &na name = p; } +/* UString ExtractDirPrefixFromPath(const UString &path) { - const wchar_t *start = path; - const wchar_t *p = start + path.Len(); - for (; p != start; p--) - if (IsPathSepar(*(p - 1))) - break; - return path.Left((unsigned)(p - start)); + return path.Left(path.ReverseFind_PathSepar() + 1)); } +*/ UString ExtractFileNameFromPath(const UString &path) { - const wchar_t *start = path; - const wchar_t *p = start + path.Len(); - for (; p != start; p--) - if (IsPathSepar(*(p - 1))) - break; - return p; + return UString(path.Ptr((unsigned)(path.ReverseFind_PathSepar() + 1))); } @@ -182,7 +255,8 @@ ForDir nonrec [0, M) same as ForBoth-File bool CItem::AreAllAllowed() const { - return ForFile && ForDir && WildcardMatching && PathParts.Size() == 1 && PathParts.Front() == L"*"; + return ForFile && ForDir && WildcardMatching + && PathParts.Size() == 1 && PathParts.Front().IsEqualTo("*"); } bool CItem::CheckPath(const UStringVector &pathParts, bool isFile) const @@ -235,12 +309,12 @@ bool CItem::CheckPath(const UStringVector &pathParts, bool isFile) const { if (WildcardMatching) { - if (!DoesWildcardMatchName(PathParts[i], pathParts[i + d])) + if (!DoesWildcardMatchName(PathParts[i], pathParts[i + (unsigned)d])) break; } else { - if (CompareFileNames(PathParts[i], pathParts[i + d]) != 0) + if (CompareFileNames(PathParts[i], pathParts[i + (unsigned)d]) != 0) break; } } @@ -264,16 +338,14 @@ int CCensorNode::FindSubNode(const UString &name) const { FOR_VECTOR (i, SubNodes) if (CompareFileNames(SubNodes[i].Name, name) == 0) - return i; + return (int)i; return -1; } void CCensorNode::AddItemSimple(bool include, CItem &item) { - if (include) - IncludeItems.Add(item); - else - ExcludeItems.Add(item); + CObjectVector &items = include ? IncludeItems : ExcludeItems; + items.Add(item); } void CCensorNode::AddItem(bool include, CItem &item, int ignoreWildcardIndex) @@ -288,6 +360,7 @@ void CCensorNode::AddItem(bool include, CItem &item, int ignoreWildcardIndex) AddItemSimple(include, item); return; } + const UString &front = item.PathParts.Front(); // WIN32 doesn't support wildcards in file names @@ -298,23 +371,23 @@ void CCensorNode::AddItem(bool include, CItem &item, int ignoreWildcardIndex) AddItemSimple(include, item); return; } - int index = FindSubNode(front); - if (index < 0) - index = SubNodes.Add(CCensorNode(front, this)); + CCensorNode &subNode = Find_SubNode_Or_Add_New(front); item.PathParts.Delete(0); - SubNodes[index].AddItem(include, item, ignoreWildcardIndex - 1); + subNode.AddItem(include, item, ignoreWildcardIndex - 1); } -void CCensorNode::AddItem(bool include, const UString &path, bool recursive, bool forFile, bool forDir, bool wildcardMatching) +/* +void CCensorNode::AddItem(bool include, const UString &path, const CCensorPathProps &props) { CItem item; SplitPathToParts(path, item.PathParts); - item.Recursive = recursive; - item.ForFile = forFile; - item.ForDir = forDir; - item.WildcardMatching = wildcardMatching; + item.Recursive = props.Recursive; + item.ForFile = props.ForFile; + item.ForDir = props.ForDir; + item.WildcardMatching = props.WildcardMatching; AddItem(include, item); } +*/ bool CCensorNode::NeedCheckSubDirs() const { @@ -353,18 +426,19 @@ bool CCensorNode::CheckPathVect(const UStringVector &pathParts, bool isFile, boo include = false; return true; } - include = true; - bool finded = CheckPathCurrent(true, pathParts, isFile); - if (pathParts.Size() <= 1) - return finded; - int index = FindSubNode(pathParts.Front()); - if (index >= 0) + if (pathParts.Size() > 1) { - UStringVector pathParts2 = pathParts; - pathParts2.Delete(0); - if (SubNodes[index].CheckPathVect(pathParts2, isFile, include)) - return true; + int index = FindSubNode(pathParts.Front()); + if (index >= 0) + { + UStringVector pathParts2 = pathParts; + pathParts2.Delete(0); + if (SubNodes[(unsigned)index].CheckPathVect(pathParts2, isFile, include)) + return true; + } } + bool finded = CheckPathCurrent(true, pathParts, isFile); + include = finded; // if (!finded), then (true) is allowed also return finded; } @@ -400,14 +474,26 @@ bool CCensorNode::CheckPath(bool isAltStream, const UString &path, bool isFile) } */ -bool CCensorNode::CheckPathToRoot(bool include, UStringVector &pathParts, bool isFile) const +bool CCensorNode::CheckPathToRoot_Change(bool include, UStringVector &pathParts, bool isFile) const { if (CheckPathCurrent(include, pathParts, isFile)) return true; - if (Parent == 0) + if (!Parent) return false; pathParts.Insert(0, Name); - return Parent->CheckPathToRoot(include, pathParts, isFile); + return Parent->CheckPathToRoot_Change(include, pathParts, isFile); +} + +bool CCensorNode::CheckPathToRoot(bool include, const UStringVector &pathParts, bool isFile) const +{ + if (CheckPathCurrent(include, pathParts, isFile)) + return true; + if (!Parent) + return false; + UStringVector pathParts2; + pathParts2.Add(Name); + pathParts2 += pathParts; + return Parent->CheckPathToRoot_Change(include, pathParts2, isFile); } /* @@ -419,39 +505,21 @@ bool CCensorNode::CheckPathToRoot(bool include, const UString &path, bool isFile } */ -void CCensorNode::AddItem2(bool include, const UString &path, bool recursive, bool wildcardMatching) -{ - if (path.IsEmpty()) - return; - bool forFile = true; - bool forFolder = true; - UString path2 = path; - if (IsPathSepar(path.Back())) - { - path2.DeleteBack(); - forFile = false; - } - AddItem(include, path2, recursive, forFile, forFolder, wildcardMatching); -} - void CCensorNode::ExtendExclude(const CCensorNode &fromNodes) { ExcludeItems += fromNodes.ExcludeItems; FOR_VECTOR (i, fromNodes.SubNodes) { const CCensorNode &node = fromNodes.SubNodes[i]; - int subNodeIndex = FindSubNode(node.Name); - if (subNodeIndex < 0) - subNodeIndex = SubNodes.Add(CCensorNode(node.Name, this)); - SubNodes[subNodeIndex].ExtendExclude(node); + Find_SubNode_Or_Add_New(node.Name).ExtendExclude(node); } } -int CCensor::FindPrefix(const UString &prefix) const +int CCensor::FindPairForPrefix(const UString &prefix) const { FOR_VECTOR (i, Pairs) if (CompareFileNames(Pairs[i].Prefix, prefix) == 0) - return i; + return (int)i; return -1; } @@ -459,8 +527,10 @@ int CCensor::FindPrefix(const UString &prefix) const bool IsDriveColonName(const wchar_t *s) { - wchar_t c = s[0]; - return c != 0 && s[1] == ':' && s[2] == 0 && (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z'); + unsigned c = s[0]; + c |= 0x20; + c -= 'a'; + return c <= (unsigned)('z' - 'a') && s[1] == ':' && s[2] == 0; } unsigned GetNumPrefixParts_if_DrivePath(UStringVector &pathParts) @@ -473,7 +543,7 @@ unsigned GetNumPrefixParts_if_DrivePath(UStringVector &pathParts) { if (pathParts.Size() < 4 || !pathParts[1].IsEmpty() - || pathParts[2] != L"?") + || !pathParts[2].IsEqualTo("?")) return 0; testIndex = 3; } @@ -488,9 +558,12 @@ static unsigned GetNumPrefixParts(const UStringVector &pathParts) { if (pathParts.IsEmpty()) return 0; + + /* empty last part could be removed already from (pathParts), + if there was tail path separator (slash) in original full path string. */ #ifdef _WIN32 - + if (IsDriveColonName(pathParts[0])) return 1; if (!pathParts[0].IsEmpty()) @@ -502,11 +575,11 @@ static unsigned GetNumPrefixParts(const UStringVector &pathParts) return 1; if (pathParts.Size() == 2) return 2; - if (pathParts[2] == L".") + if (pathParts[2].IsEqualTo(".")) return 3; unsigned networkParts = 2; - if (pathParts[2] == L"?") + if (pathParts[2].IsEqualTo("?")) { if (pathParts.Size() == 3) return 3; @@ -531,7 +604,8 @@ static unsigned GetNumPrefixParts(const UStringVector &pathParts) #endif } -void CCensor::AddItem(ECensorPathMode pathMode, bool include, const UString &path, bool recursive, bool wildcardMatching) +void CCensor::AddItem(ECensorPathMode pathMode, bool include, const UString &path, + const CCensorPathProps &props) { if (path.IsEmpty()) throw "Empty file path"; @@ -539,12 +613,26 @@ void CCensor::AddItem(ECensorPathMode pathMode, bool include, const UString &pat UStringVector pathParts; SplitPathToParts(path, pathParts); + CCensorPathProps props2 = props; + bool forFile = true; - if (pathParts.Back().IsEmpty()) + bool forDir = true; + const UString &back = pathParts.Back(); + if (back.IsEmpty()) { + // we have tail path separator. So it's directory. + // we delete tail path separator here even for "\" and "c:\" forFile = false; pathParts.DeleteBack(); } + else + { + if (props.MarkMode == kMark_StrictFile + || (props.MarkMode == kMark_StrictFile_IfWildcard + && DoesNameContainWildcard(back))) + forDir = false; + } + UString prefix; @@ -555,12 +643,13 @@ void CCensor::AddItem(ECensorPathMode pathMode, bool include, const UString &pat if (pathParts.Size() >= 3 && pathParts[0].IsEmpty() && pathParts[1].IsEmpty() - && pathParts[2] == L"?") + && pathParts[2].IsEqualTo("?")) ignoreWildcardIndex = 2; // #endif if (pathMode != k_AbsPath) { + // detection of the number of Skip Parts for prefix ignoreWildcardIndex = -1; const unsigned numPrefixParts = GetNumPrefixParts(pathParts); @@ -568,6 +657,7 @@ void CCensor::AddItem(ECensorPathMode pathMode, bool include, const UString &pat if (pathMode != k_FullPath) { + // if absolute path, then all parts before last part will be in prefix if (numPrefixParts != 0 && pathParts.Size() > numPrefixParts) numSkipParts = pathParts.Size() - 1; } @@ -576,23 +666,26 @@ void CCensor::AddItem(ECensorPathMode pathMode, bool include, const UString &pat for (unsigned i = numPrefixParts; i < pathParts.Size(); i++) { const UString &part = pathParts[i]; - if (part == L".." || part == L".") - dotsIndex = i; + if (part.IsEqualTo("..") || part.IsEqualTo(".")) + dotsIndex = (int)i; } if (dotsIndex >= 0) + { if (dotsIndex == (int)pathParts.Size() - 1) numSkipParts = pathParts.Size(); else numSkipParts = pathParts.Size() - 1; + } } + // we split (pathParts) to (prefix) and (pathParts). for (unsigned i = 0; i < numSkipParts; i++) { { const UString &front = pathParts.Front(); // WIN32 doesn't support wildcards in file names - if (wildcardMatching) + if (props.WildcardMatching) if (i >= numPrefixParts && DoesNameContainWildcard(front)) break; prefix += front; @@ -602,30 +695,45 @@ void CCensor::AddItem(ECensorPathMode pathMode, bool include, const UString &pat } } - int index = FindPrefix(prefix); + int index = FindPairForPrefix(prefix); if (index < 0) - index = Pairs.Add(CPair(prefix)); + { + index = (int)Pairs.Size(); + Pairs.AddNew().Prefix = prefix; + } if (pathMode != k_AbsPath) { - if (pathParts.IsEmpty() || pathParts.Size() == 1 && pathParts[0].IsEmpty()) + if (pathParts.IsEmpty() || (pathParts.Size() == 1 && pathParts[0].IsEmpty())) { // we create universal item, if we skip all parts as prefix (like \ or L:\ ) pathParts.Clear(); - pathParts.Add(L"*"); + pathParts.Add(UString("*")); forFile = true; - wildcardMatching = true; - recursive = false; + forDir = true; + props2.WildcardMatching = true; + props2.Recursive = false; } } + /* + // not possible now + if (!forDir && !forFile) + { + UString s ("file path was blocked for files and directories: "); + s += path; + throw s; + // return; // for debug : ignore item (don't create Item) + } + */ + CItem item; item.PathParts = pathParts; - item.ForDir = true; + item.ForDir = forDir; item.ForFile = forFile; - item.Recursive = recursive; - item.WildcardMatching = wildcardMatching; - Pairs[index].Head.AddItem(include, item, ignoreWildcardIndex); + item.Recursive = props2.Recursive; + item.WildcardMatching = props2.WildcardMatching; + Pairs[(unsigned)index].Head.AddItem(include, item, ignoreWildcardIndex); } /* @@ -665,18 +773,17 @@ void CCensor::AddPathsToCensor(ECensorPathMode censorPathMode) FOR_VECTOR(i, CensorPaths) { const CCensorPath &cp = CensorPaths[i]; - AddItem(censorPathMode, cp.Include, cp.Path, cp.Recursive, cp.WildcardMatching); + AddItem(censorPathMode, cp.Include, cp.Path, cp.Props); } CensorPaths.Clear(); } -void CCensor::AddPreItem(bool include, const UString &path, bool recursive, bool wildcardMatching) +void CCensor::AddPreItem(bool include, const UString &path, const CCensorPathProps &props) { CCensorPath &cp = CensorPaths.AddNew(); cp.Path = path; cp.Include = include; - cp.Recursive = recursive; - cp.WildcardMatching = wildcardMatching; + cp.Props = props; } } diff --git a/src/plugins/7zip/7za/cpp/Common/Wildcard.h b/src/plugins/7zip/7za/cpp/Common/Wildcard.h index 512687d1..4f81da92 100644 --- a/src/plugins/7zip/7za/cpp/Common/Wildcard.h +++ b/src/plugins/7zip/7za/cpp/Common/Wildcard.h @@ -1,7 +1,7 @@ // Common/Wildcard.h -#ifndef __COMMON_WILDCARD_H -#define __COMMON_WILDCARD_H +#ifndef ZIP7_INC_COMMON_WILDCARD_H +#define ZIP7_INC_COMMON_WILDCARD_H #include "MyString.h" @@ -51,50 +51,117 @@ struct CItem bool CheckPath(const UStringVector &pathParts, bool isFile) const; }; -class CCensorNode + + +const Byte kMark_FileOrDir = 0; +const Byte kMark_StrictFile = 1; +const Byte kMark_StrictFile_IfWildcard = 2; + +struct CCensorPathProps +{ + bool Recursive; + bool WildcardMatching; + Byte MarkMode; + + CCensorPathProps(): + Recursive(false), + WildcardMatching(true), + MarkMode(kMark_FileOrDir) + {} +}; + + +class CCensorNode MY_UNCOPYABLE { CCensorNode *Parent; bool CheckPathCurrent(bool include, const UStringVector &pathParts, bool isFile) const; void AddItemSimple(bool include, CItem &item); public: - bool CheckPathVect(const UStringVector &pathParts, bool isFile, bool &include) const; + // bool ExcludeDirItems; + + CCensorNode(): + Parent(NULL) + // , ExcludeDirItems(false) + {} - CCensorNode(): Parent(0) { }; - CCensorNode(const UString &name, CCensorNode *parent): Name(name), Parent(parent) { }; + CCensorNode(const UString &name, CCensorNode *parent): + Parent(parent) + // , ExcludeDirItems(false) + , Name(name) + {} UString Name; // WIN32 doesn't support wildcards in file names CObjectVector SubNodes; CObjectVector IncludeItems; CObjectVector ExcludeItems; + CCensorNode &Find_SubNode_Or_Add_New(const UString &name) + { + int i = FindSubNode(name); + if (i >= 0) + return SubNodes[(unsigned)i]; + // return SubNodes.Add(CCensorNode(name, this)); + CCensorNode &node = SubNodes.AddNew(); + node.Parent = this; + node.Name = name; + return node; + } + bool AreAllAllowed() const; int FindSubNode(const UString &path) const; void AddItem(bool include, CItem &item, int ignoreWildcardIndex = -1); - void AddItem(bool include, const UString &path, bool recursive, bool forFile, bool forDir, bool wildcardMatching); - void AddItem2(bool include, const UString &path, bool recursive, bool wildcardMatching); + // void AddItem(bool include, const UString &path, const CCensorPathProps &props); + void Add_Wildcard() + { + CItem item; + item.PathParts.Add(L"*"); + item.Recursive = false; + item.ForFile = true; + item.ForDir = true; + item.WildcardMatching = true; + AddItem( + true // include + , item); + } + // NeedCheckSubDirs() returns true, if there are IncludeItems rules that affect items in subdirs bool NeedCheckSubDirs() const; bool AreThereIncludeItems() const; + /* + CheckPathVect() doesn't check path in Parent CCensorNode + so use CheckPathVect() for root CCensorNode + OUT: + returns (true) && (include = false) - file in exlude list + returns (true) && (include = true) - file in include list and is not in exlude list + returns (false) - file is not in (include/exlude) list + */ + bool CheckPathVect(const UStringVector &pathParts, bool isFile, bool &include) const; + // bool CheckPath2(bool isAltStream, const UString &path, bool isFile, bool &include) const; // bool CheckPath(bool isAltStream, const UString &path, bool isFile) const; - bool CheckPathToRoot(bool include, UStringVector &pathParts, bool isFile) const; + // CheckPathToRoot_Change() changes pathParts !!! + bool CheckPathToRoot_Change(bool include, UStringVector &pathParts, bool isFile) const; + bool CheckPathToRoot(bool include, const UStringVector &pathParts, bool isFile) const; + // bool CheckPathToRoot(const UString &path, bool isFile, bool include) const; void ExtendExclude(const CCensorNode &fromNodes); }; -struct CPair + +struct CPair MY_UNCOPYABLE { UString Prefix; CCensorNode Head; - CPair(const UString &prefix): Prefix(prefix) { }; + // CPair(const UString &prefix): Prefix(prefix) { }; }; + enum ECensorPathMode { k_RelatPath, // absolute prefix as Prefix, remain path in Tree @@ -102,48 +169,63 @@ enum ECensorPathMode k_AbsPath // full path in Tree }; + struct CCensorPath { UString Path; bool Include; - bool Recursive; - bool WildcardMatching; + CCensorPathProps Props; CCensorPath(): - Include(true), - Recursive(false), - WildcardMatching(true) - {} + Include(true) + {} }; -class CCensor + +class CCensor MY_UNCOPYABLE { - int FindPrefix(const UString &prefix) const; + int FindPairForPrefix(const UString &prefix) const; public: CObjectVector Pairs; + bool ExcludeDirItems; + bool ExcludeFileItems; + + CCensor(): + ExcludeDirItems(false), + ExcludeFileItems(false) + {} + CObjectVector CensorPaths; bool AllAreRelative() const { return (Pairs.Size() == 1 && Pairs.Front().Prefix.IsEmpty()); } - void AddItem(ECensorPathMode pathMode, bool include, const UString &path, bool recursive, bool wildcardMatching); + void AddItem(ECensorPathMode pathMode, bool include, const UString &path, const CCensorPathProps &props); // bool CheckPath(bool isAltStream, const UString &path, bool isFile) const; void ExtendExclude(); void AddPathsToCensor(NWildcard::ECensorPathMode censorPathMode); - void AddPreItem(bool include, const UString &path, bool recursive, bool wildcardMatching); - void AddPreItem(const UString &path) + void AddPreItem(bool include, const UString &path, const CCensorPathProps &props); + + void AddPreItem_NoWildcard(const UString &path) { - AddPreItem(true, path, false, false); + CCensorPathProps props; + props.WildcardMatching = false; + AddPreItem( + true, // include + path, props); } void AddPreItem_Wildcard() { - AddPreItem(true, L"*", false, true); + CCensorPathProps props; + // props.WildcardMatching = true; + AddPreItem( + true, // include + UString("*"), props); } }; - } #endif diff --git a/src/plugins/7zip/7za/cpp/Common/Xxh64Reg.cpp b/src/plugins/7zip/7za/cpp/Common/Xxh64Reg.cpp new file mode 100644 index 00000000..8abd15eb --- /dev/null +++ b/src/plugins/7zip/7za/cpp/Common/Xxh64Reg.cpp @@ -0,0 +1,97 @@ +// Xxh64Reg.cpp + +#include "StdAfx.h" + +#include "../../C/Xxh64.h" +#include "../../C/CpuArch.h" + +// #define Z7_USE_HHX64_ORIGINAL +#ifdef Z7_USE_HHX64_ORIGINAL +#ifdef __clang__ +#include "../../C/zstd7z/7z_zstd_cmpl.h" +#pragma GCC diagnostic ignored "-Wlanguage-extension-token" +#endif +#define XXH_STATIC_LINKING_ONLY +#include "../../C/zstd7z/common/xxhash.h" +#endif + +#include "../Common/MyCom.h" + +#include "../7zip/Common/RegisterCodec.h" + +Z7_CLASS_IMP_COM_1( + CXxh64Hasher + , IHasher +) + CXxh64 _state; +public: + Byte _mtDummy[1 << 7]; // it's public to eliminate clang warning: unused private field + CXxh64Hasher() { Init(); } +}; + +Z7_COM7F_IMF2(void, CXxh64Hasher::Init()) +{ + Xxh64_Init(&_state); +} + +Z7_COM7F_IMF2(void, CXxh64Hasher::Update(const void *data, UInt32 size)) +{ +#if 1 + Xxh64_Update(&_state, data, size); +#else // for debug: + for (;;) + { + if (size == 0) + return; + UInt32 size2 = (size * 0x85EBCA87) % size / 8; + if (size2 == 0) + size2 = 1; + Xxh64_Update(&_state, data, size2); + data = (const void *)((const Byte *)data + size2); + size -= size2; + } +#endif +} + +Z7_COM7F_IMF2(void, CXxh64Hasher::Final(Byte *digest)) +{ + const UInt64 val = Xxh64_Digest(&_state); + SetUi64(digest, val) +} + +REGISTER_HASHER(CXxh64Hasher, 0x211, "XXH64", 8) + + + +#ifdef Z7_USE_HHX64_ORIGINAL +namespace NOriginal +{ +Z7_CLASS_IMP_COM_1( + CXxh64Hasher + , IHasher +) + XXH64_state_t _state; +public: + Byte _mtDummy[1 << 7]; // it's public to eliminate clang warning: unused private field + CXxh64Hasher() { Init(); } +}; + +Z7_COM7F_IMF2(void, CXxh64Hasher::Init()) +{ + XXH64_reset(&_state, 0); +} + +Z7_COM7F_IMF2(void, CXxh64Hasher::Update(const void *data, UInt32 size)) +{ + XXH64_update(&_state, data, size); +} + +Z7_COM7F_IMF2(void, CXxh64Hasher::Final(Byte *digest)) +{ + const UInt64 val = XXH64_digest(&_state); + SetUi64(digest, val) +} + +REGISTER_HASHER(CXxh64Hasher, 0x212, "XXH64a", 8) +} +#endif diff --git a/src/plugins/7zip/7za/cpp/Common/XzCrc64Init.cpp b/src/plugins/7zip/7za/cpp/Common/XzCrc64Init.cpp new file mode 100644 index 00000000..5cb8e674 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/Common/XzCrc64Init.cpp @@ -0,0 +1,7 @@ +// XzCrc64Init.cpp + +#include "StdAfx.h" + +#include "../../C/XzCrc64.h" + +static struct CCrc64Gen { CCrc64Gen() { Crc64GenerateTable(); } } g_Crc64TableInit; diff --git a/src/plugins/7zip/7za/cpp/Common/XzCrc64Reg.cpp b/src/plugins/7zip/7za/cpp/Common/XzCrc64Reg.cpp index 33b52493..e9e67efc 100644 --- a/src/plugins/7zip/7za/cpp/Common/XzCrc64Reg.cpp +++ b/src/plugins/7zip/7za/cpp/Common/XzCrc64Reg.cpp @@ -9,34 +9,31 @@ #include "../7zip/Common/RegisterCodec.h" -class CXzCrc64Hasher: - public IHasher, - public CMyUnknownImp -{ +Z7_CLASS_IMP_COM_1( + CXzCrc64Hasher + , IHasher +) UInt64 _crc; - Byte mtDummy[1 << 7]; - public: - CXzCrc64Hasher(): _crc(CRC64_INIT_VAL) {} + Byte _mtDummy[1 << 7]; // it's public to eliminate clang warning: unused private field - MY_UNKNOWN_IMP1(IHasher) - INTERFACE_IHasher(;) + CXzCrc64Hasher(): _crc(CRC64_INIT_VAL) {} }; -STDMETHODIMP_(void) CXzCrc64Hasher::Init() throw() +Z7_COM7F_IMF2(void, CXzCrc64Hasher::Init()) { _crc = CRC64_INIT_VAL; } -STDMETHODIMP_(void) CXzCrc64Hasher::Update(const void *data, UInt32 size) throw() +Z7_COM7F_IMF2(void, CXzCrc64Hasher::Update(const void *data, UInt32 size)) { _crc = Crc64Update(_crc, data, size); } -STDMETHODIMP_(void) CXzCrc64Hasher::Final(Byte *digest) throw() +Z7_COM7F_IMF2(void, CXzCrc64Hasher::Final(Byte *digest)) { - UInt64 val = CRC64_GET_DIGEST(_crc); - SetUi64(digest, val); + const UInt64 val = CRC64_GET_DIGEST(_crc); + SetUi64(digest, val) } REGISTER_HASHER(CXzCrc64Hasher, 0x4, "CRC64", 8) diff --git a/src/plugins/7zip/7za/cpp/Windows/COM.h b/src/plugins/7zip/7za/cpp/Windows/COM.h index cee7f702..a8780cac 100644 --- a/src/plugins/7zip/7za/cpp/Windows/COM.h +++ b/src/plugins/7zip/7za/cpp/Windows/COM.h @@ -1,9 +1,9 @@ // Windows/COM.h -#ifndef __WINDOWS_COM_H -#define __WINDOWS_COM_H +#ifndef ZIP7_INC_WINDOWS_COM_H +#define ZIP7_INC_WINDOWS_COM_H -#include "../Common/MyString.h" +// #include "../Common/MyString.h" namespace NWindows { namespace NCOM { @@ -21,17 +21,18 @@ class CComInitializer // it's single thread. Do we need multithread? CoInitialize(NULL); #endif - }; + } ~CComInitializer() { CoUninitialize(); } }; -class CStgMedium +/* +class CStgMedium2 { STGMEDIUM _object; -public: bool _mustBeReleased; - CStgMedium(): _mustBeReleased(false) {} - ~CStgMedium() { Free(); } +public: + CStgMedium2(): _mustBeReleased(false) {} + ~CStgMedium2() { Free(); } void Free() { if (_mustBeReleased) @@ -42,6 +43,21 @@ class CStgMedium STGMEDIUM* operator->() { return &_object;} STGMEDIUM* operator&() { return &_object; } }; +*/ + +struct CStgMedium: public STGMEDIUM +{ + CStgMedium() + { + tymed = TYMED_NULL; // 0 + hGlobal = NULL; + pUnkForRelease = NULL; + } + ~CStgMedium() + { + ReleaseStgMedium(this); + } +}; #endif diff --git a/src/plugins/7zip/7za/cpp/Windows/Clipboard.cpp b/src/plugins/7zip/7za/cpp/Windows/Clipboard.cpp index e0996930..d39c2646 100644 --- a/src/plugins/7zip/7za/cpp/Windows/Clipboard.cpp +++ b/src/plugins/7zip/7za/cpp/Windows/Clipboard.cpp @@ -9,7 +9,7 @@ #include "../Common/StringConvert.h" #include "Clipboard.h" -#include "Defs.h" +#include "WinDefs.h" #include "MemoryGlobal.h" #include "Shell.h" @@ -117,7 +117,7 @@ bool ClipboardSetText(HWND owner, const UString &s) bool res; res = ClipboardSetData(CF_UNICODETEXT, (const wchar_t *)s, (s.Len() + 1) * sizeof(wchar_t)); #ifndef _UNICODE - AString a = UnicodeStringToMultiByte(s, CP_ACP); + AString a (UnicodeStringToMultiByte(s, CP_ACP)); if (ClipboardSetData(CF_TEXT, (const char *)a, (a.Len() + 1) * sizeof(char))) res = true; a = UnicodeStringToMultiByte(s, CP_OEMCP); diff --git a/src/plugins/7zip/7za/cpp/Windows/Clipboard.h b/src/plugins/7zip/7za/cpp/Windows/Clipboard.h index 60fedc13..3b4f2fe5 100644 --- a/src/plugins/7zip/7za/cpp/Windows/Clipboard.h +++ b/src/plugins/7zip/7za/cpp/Windows/Clipboard.h @@ -1,7 +1,7 @@ // Windows/Clipboard.h -#ifndef __CLIPBOARD_H -#define __CLIPBOARD_H +#ifndef ZIP7_INC_CLIPBOARD_H +#define ZIP7_INC_CLIPBOARD_H #include "../Common/MyString.h" @@ -11,7 +11,7 @@ class CClipboard { bool m_Open; public: - CClipboard(): m_Open(false) {}; + CClipboard(): m_Open(false) {} ~CClipboard() { Close(); } bool Open(HWND wndNewOwner) throw(); bool Close() throw(); diff --git a/src/plugins/7zip/7za/cpp/Windows/CommonDialog.cpp b/src/plugins/7zip/7za/cpp/Windows/CommonDialog.cpp index 8f4f56d3..2e3f297b 100644 --- a/src/plugins/7zip/7za/cpp/Windows/CommonDialog.cpp +++ b/src/plugins/7zip/7za/cpp/Windows/CommonDialog.cpp @@ -2,7 +2,7 @@ #include "StdAfx.h" -#include "../Common/MyWindows.h" +#include "../Common/MyBuffer.h" #ifdef UNDER_CE #include @@ -13,7 +13,8 @@ #endif #include "CommonDialog.h" -#include "Defs.h" +#include "WinDefs.h" +// #include "FileDir.h" #ifndef _UNICODE extern bool g_IsNT; @@ -21,58 +22,46 @@ extern bool g_IsNT; namespace NWindows { -#ifndef _UNICODE +/* + GetSaveFileName() + GetOpenFileName() + OPENFILENAME -class CDoubleZeroStringListA +(lpstrInitialDir) : the initial directory. +DOCs: the algorithm for selecting the initial directory varies on different platforms: { - LPTSTR Buf; - unsigned Size; -public: - CDoubleZeroStringListA(LPSTR buf, unsigned size): Buf(buf), Size(size) {} - bool Add(LPCSTR s) throw(); - void Finish() { *Buf = 0; } -}; - -bool CDoubleZeroStringListA::Add(LPCSTR s) throw() -{ - unsigned len = MyStringLen(s) + 1; - if (len >= Size) - return false; - MyStringCopy(Buf, s); - Buf += len; - Size -= len; - return true; + Win2000/XP/Vista: + 1. If lpstrFile contains a path, that path is the initial directory. + 2. Otherwise, lpstrInitialDir specifies the initial directory. + + Win7: + If lpstrInitialDir has the same value as was passed the first time + the application used an Open or Save As dialog box, the path + most recently selected by the user is used as the initial directory. } -#endif +Win10: + in: + function supports (lpstrInitialDir) path with super prefix "\\\\?\\" + function supports (lpstrInitialDir) path with long path + function doesn't support absolute (lpstrFile) path with super prefix "\\\\?\\" + function doesn't support absolute (lpstrFile) path with long path + out: the path with super prefix "\\\\?\\" will be returned, if selected path is long -class CDoubleZeroStringListW -{ - LPWSTR Buf; - unsigned Size; -public: - CDoubleZeroStringListW(LPWSTR buf, unsigned size): Buf(buf), Size(size) {} - bool Add(LPCWSTR s) throw(); - void Finish() { *Buf = 0; } -}; - -bool CDoubleZeroStringListW::Add(LPCWSTR s) throw() -{ - unsigned len = MyStringLen(s) + 1; - if (len >= Size) - return false; - MyStringCopy(Buf, s); - Buf += len; - Size -= len; - return true; -} +WinXP-64 and Win10: if no filters, the system shows all files. + but DOCs say: If all three members are zero or NULL, + the system does not use any filters and does not + show any files in the file list control of the dialog box. + +in Win7+: GetOpenFileName() and GetSaveFileName() + do not support pstrCustomFilter feature anymore +*/ -#define MY__OFN_PROJECT 0x00400000 -#define MY__OFN_SHOW_ALL 0x01000000 +#ifdef UNDER_CE +#define MY_OFN_PROJECT 0x00400000 +#define MY_OFN_SHOW_ALL 0x01000000 +#endif -/* if (lpstrFilter == NULL && nFilterIndex == 0) - MSDN : "the system doesn't show any files", - but WinXP-64 shows all files. Why ??? */ /* structures @@ -86,99 +75,194 @@ contain additional members: #endif If we compile the source code with (_WIN32_WINNT >= 0x0500), some functions -will not work at NT 4.0, if we use sizeof(OPENFILENAME*). -So we use size of old version of structure. */ +will not work at NT 4.0, if we use sizeof(OPENFILENAME). +We try to use reduced structure OPENFILENAME_NT4. +*/ + +// #if defined(_WIN64) || (defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x0500) +#if defined(__GNUC__) && (__GNUC__ <= 9) || defined(Z7_OLD_WIN_SDK) + #ifndef _UNICODE + #define my_compatib_OPENFILENAMEA OPENFILENAMEA + #endif + #define my_compatib_OPENFILENAMEW OPENFILENAMEW -#if defined(UNDER_CE) || defined(_WIN64) || (_WIN32_WINNT < 0x0500) + // MinGW doesn't support some required macros. So we define them here: + #ifndef CDSIZEOF_STRUCT + #define CDSIZEOF_STRUCT(structname, member) (((int)((LPBYTE)(&((structname*)0)->member) - ((LPBYTE)((structname*)0)))) + sizeof(((structname*)0)->member)) + #endif + #ifndef _UNICODE + #ifndef OPENFILENAME_SIZE_VERSION_400A + #define OPENFILENAME_SIZE_VERSION_400A CDSIZEOF_STRUCT(OPENFILENAMEA,lpTemplateName) + #endif + #endif + #ifndef OPENFILENAME_SIZE_VERSION_400W + #define OPENFILENAME_SIZE_VERSION_400W CDSIZEOF_STRUCT(OPENFILENAMEW,lpTemplateName) + #endif + + #ifndef _UNICODE + #define my_compatib_OPENFILENAMEA_size OPENFILENAME_SIZE_VERSION_400A + #endif + #define my_compatib_OPENFILENAMEW_size OPENFILENAME_SIZE_VERSION_400W +#else + #ifndef _UNICODE + #define my_compatib_OPENFILENAMEA OPENFILENAME_NT4A + #define my_compatib_OPENFILENAMEA_size sizeof(my_compatib_OPENFILENAMEA) + #endif + #define my_compatib_OPENFILENAMEW OPENFILENAME_NT4W + #define my_compatib_OPENFILENAMEW_size sizeof(my_compatib_OPENFILENAMEW) +#endif +/* +#elif defined(UNDER_CE) || defined(_WIN64) || (_WIN32_WINNT < 0x0500) // || !defined(WINVER) + #ifndef _UNICODE + #define my_compatib_OPENFILENAMEA OPENFILENAMEA #define my_compatib_OPENFILENAMEA_size sizeof(OPENFILENAMEA) + #endif + #define my_compatib_OPENFILENAMEW OPENFILENAMEW #define my_compatib_OPENFILENAMEW_size sizeof(OPENFILENAMEW) #else - #define my_compatib_OPENFILENAMEA_size OPENFILENAME_SIZE_VERSION_400A - #define my_compatib_OPENFILENAMEW_size OPENFILENAME_SIZE_VERSION_400W + #endif +*/ +#ifndef _UNICODE #define CONV_U_To_A(dest, src, temp) AString temp; if (src) { temp = GetSystemString(src); dest = temp; } +#endif -bool MyGetOpenFileName(HWND hwnd, LPCWSTR title, - LPCWSTR initialDir, - LPCWSTR filePath, - LPCWSTR filterDescription, - LPCWSTR filter, - UString &resPath - #ifdef UNDER_CE - , bool openFolder - #endif - ) +bool CCommonDialogInfo::CommonDlg_BrowseForFile(LPCWSTR lpstrInitialDir, const UStringVector &filters) { - const unsigned kBufSize = MAX_PATH * 2; - const unsigned kFilterBufSize = MAX_PATH; - if (!filter) - filter = L"*.*"; - #ifndef _UNICODE + /* GetSaveFileName() and GetOpenFileName() could change current dir, + if OFN_NOCHANGEDIR is not used. + We can restore current dir manually, if it's required. + 22.02: we use OFN_NOCHANGEDIR. So we don't need to restore current dir manually. */ + // NFile::NDir::CCurrentDirRestorer curDirRestorer; + +#ifndef _UNICODE if (!g_IsNT) { - CHAR buf[kBufSize]; - MyStringCopy(buf, (const char *)GetSystemString(filePath)); - // OPENFILENAME_NT4A - OPENFILENAMEA p; + AString tempPath; + AStringVector f; + unsigned i; + for (i = 0; i < filters.Size(); i++) + f.Add(GetSystemString(filters[i])); + unsigned size = f.Size() + 1; + for (i = 0; i < f.Size(); i++) + size += f[i].Len(); + CObjArray filterBuf(size); + // memset(filterBuf, 0, size * sizeof(char)); + { + char *dest = filterBuf; + for (i = 0; i < f.Size(); i++) + { + const AString &s = f[i]; + MyStringCopy(dest, s); + dest += s.Len() + 1; + } + *dest = 0; + } + my_compatib_OPENFILENAMEA p; memset(&p, 0, sizeof(p)); p.lStructSize = my_compatib_OPENFILENAMEA_size; - p.hwndOwner = hwnd; - CHAR filterBuf[kFilterBufSize]; + p.hwndOwner = hwndOwner; + if (size > 1) { - CDoubleZeroStringListA dz(filterBuf, kFilterBufSize); - dz.Add(GetSystemString(filterDescription ? filterDescription : filter)); - dz.Add(GetSystemString(filter)); - dz.Finish(); p.lpstrFilter = filterBuf; - p.nFilterIndex = 1; + p.nFilterIndex = (DWORD)(FilterIndex + 1); + } + + CONV_U_To_A(p.lpstrInitialDir, lpstrInitialDir, initialDir_a) + CONV_U_To_A(p.lpstrTitle, lpstrTitle, title_a) + + const AString filePath_a = GetSystemString(FilePath); + const unsigned bufSize = MAX_PATH * 8 + + filePath_a.Len() + + initialDir_a.Len(); + p.nMaxFile = bufSize; + p.lpstrFile = tempPath.GetBuf(bufSize); + MyStringCopy(p.lpstrFile, filePath_a); + p.Flags = + OFN_EXPLORER + | OFN_HIDEREADONLY + | OFN_NOCHANGEDIR; + const BOOL b = SaveMode ? + ::GetSaveFileNameA((LPOPENFILENAMEA)(void *)&p) : + ::GetOpenFileNameA((LPOPENFILENAMEA)(void *)&p); + if (!b) + return false; + { + tempPath.ReleaseBuf_CalcLen(bufSize); + FilePath = GetUnicodeString(tempPath); + FilterIndex = (int)p.nFilterIndex - 1; + return true; } - - p.lpstrFile = buf; - p.nMaxFile = kBufSize; - CONV_U_To_A(p.lpstrInitialDir, initialDir, initialDirA); - CONV_U_To_A(p.lpstrTitle, title, titleA); - p.Flags = OFN_EXPLORER | OFN_HIDEREADONLY; - - bool res = BOOLToBool(::GetOpenFileNameA(&p)); - resPath = GetUnicodeString(buf); - return res; } else - #endif +#endif { - WCHAR buf[kBufSize]; - MyStringCopy(buf, filePath); - // OPENFILENAME_NT4W - OPENFILENAMEW p; + UString tempPath; + unsigned size = filters.Size() + 1; + unsigned i; + for (i = 0; i < filters.Size(); i++) + size += filters[i].Len(); + CObjArray filterBuf(size); + // memset(filterBuf, 0, size * sizeof(wchar_t)); + { + wchar_t *dest = filterBuf; + for (i = 0; i < filters.Size(); i++) + { + const UString &s = filters[i]; + MyStringCopy(dest, s); + dest += s.Len() + 1; + } + *dest = 0; + // if ((unsigned)(dest + 1 - filterBuf) != size) return false; + } + my_compatib_OPENFILENAMEW p; memset(&p, 0, sizeof(p)); p.lStructSize = my_compatib_OPENFILENAMEW_size; - p.hwndOwner = hwnd; - - WCHAR filterBuf[kFilterBufSize]; + p.hwndOwner = hwndOwner; + if (size > 1) { - CDoubleZeroStringListW dz(filterBuf, kFilterBufSize); - dz.Add(filterDescription ? filterDescription : filter); - dz.Add(filter); - dz.Finish(); p.lpstrFilter = filterBuf; - p.nFilterIndex = 1; + p.nFilterIndex = (DWORD)(FilterIndex + 1); + } + unsigned bufSize = MAX_PATH * 8 + FilePath.Len(); + if (lpstrInitialDir) + { + p.lpstrInitialDir = lpstrInitialDir; + bufSize += MyStringLen(lpstrInitialDir); } - - p.lpstrFile = buf; - p.nMaxFile = kBufSize; - p.lpstrInitialDir = initialDir; - p.lpstrTitle = title; - p.Flags = OFN_EXPLORER | OFN_HIDEREADONLY - #ifdef UNDER_CE - | (openFolder ? (MY__OFN_PROJECT | MY__OFN_SHOW_ALL) : 0) - #endif + p.nMaxFile = bufSize; + p.lpstrFile = tempPath.GetBuf(bufSize); + MyStringCopy(p.lpstrFile, FilePath); + p.lpstrTitle = lpstrTitle; + p.Flags = + OFN_EXPLORER + | OFN_HIDEREADONLY + | OFN_NOCHANGEDIR + // | OFN_FORCESHOWHIDDEN // Win10 shows hidden items even without this flag + // | OFN_PATHMUSTEXIST + #ifdef UNDER_CE + | (OpenFolderMode ? (MY_OFN_PROJECT | MY_OFN_SHOW_ALL) : 0) + #endif ; - - bool res = BOOLToBool(::GetOpenFileNameW(&p)); - resPath = buf; - return res; + const BOOL b = SaveMode ? + ::GetSaveFileNameW((LPOPENFILENAMEW)(void *)&p) : + ::GetOpenFileNameW((LPOPENFILENAMEW)(void *)&p); + /* DOCs: lpstrFile : + if the buffer is too small, then: + - the function returns FALSE + - the CommDlgExtendedError() returns FNERR_BUFFERTOOSMALL + - the first two bytes of the lpstrFile buffer contain the + required size, in bytes or characters. */ + if (!b) + return false; + { + tempPath.ReleaseBuf_CalcLen(bufSize); + FilePath = tempPath; + FilterIndex = (int)p.nFilterIndex - 1; + return true; + } } } diff --git a/src/plugins/7zip/7za/cpp/Windows/CommonDialog.h b/src/plugins/7zip/7za/cpp/Windows/CommonDialog.h index aaf17ac5..85b071f0 100644 --- a/src/plugins/7zip/7za/cpp/Windows/CommonDialog.h +++ b/src/plugins/7zip/7za/cpp/Windows/CommonDialog.h @@ -1,22 +1,42 @@ // Windows/CommonDialog.h -#ifndef __WINDOWS_COMMON_DIALOG_H -#define __WINDOWS_COMMON_DIALOG_H +#ifndef ZIP7_INC_WINDOWS_COMMON_DIALOG_H +#define ZIP7_INC_WINDOWS_COMMON_DIALOG_H #include "../Common/MyString.h" namespace NWindows { -bool MyGetOpenFileName(HWND hwnd, LPCWSTR title, - LPCWSTR initialDir, // can be NULL, so dir prefix in filePath will be used - LPCWSTR filePath, // full path - LPCWSTR filterDescription, // like "All files (*.*)" - LPCWSTR filter, // like "*.exe" - UString &resPath - #ifdef UNDER_CE - , bool openFolder = false - #endif -); +struct CCommonDialogInfo +{ + /* (FilterIndex == -1) means no selected filter. + and (-1) also is reserved for unsupported custom filter. + if (FilterIndex >= 0), then FilterIndex is index of filter */ + int FilterIndex; // [in / out] + bool SaveMode; + #ifdef UNDER_CE + bool OpenFolderMode; + #endif + HWND hwndOwner; + // LPCWSTR lpstrInitialDir; + LPCWSTR lpstrTitle; + UString FilePath; // [in / out] + + CCommonDialogInfo() + { + FilterIndex = -1; + SaveMode = false; + #ifdef UNDER_CE + OpenFolderMode = false; + #endif + hwndOwner = NULL; + // lpstrInitialDir = NULL; + lpstrTitle = NULL; + } + + /* (filters) : 2 sequential vector strings (Description, Masks) represent each filter */ + bool CommonDlg_BrowseForFile(LPCWSTR lpstrInitialDir, const UStringVector &filters); +}; } diff --git a/src/plugins/7zip/7za/cpp/Windows/Console.h b/src/plugins/7zip/7za/cpp/Windows/Console.h index 43e02fa6..6b494c84 100644 --- a/src/plugins/7zip/7za/cpp/Windows/Console.h +++ b/src/plugins/7zip/7za/cpp/Windows/Console.h @@ -1,9 +1,9 @@ // Windows/Console.h -#ifndef __WINDOWS_CONSOLE_H -#define __WINDOWS_CONSOLE_H +#ifndef ZIP7_INC_WINDOWS_CONSOLE_H +#define ZIP7_INC_WINDOWS_CONSOLE_H -#include "Defs.h" +#include "WinDefs.h" namespace NWindows { namespace NConsole { diff --git a/src/plugins/7zip/7za/cpp/Windows/Control/ComboBox.cpp b/src/plugins/7zip/7za/cpp/Windows/Control/ComboBox.cpp index febc61ef..2e9c8cb0 100644 --- a/src/plugins/7zip/7za/cpp/Windows/Control/ComboBox.cpp +++ b/src/plugins/7zip/7za/cpp/Windows/Control/ComboBox.cpp @@ -43,10 +43,10 @@ LRESULT CComboBox::GetLBText(int index, UString &s) s.Empty(); if (g_IsNT) { - LRESULT len = SendMsgW(CB_GETLBTEXTLEN, index, 0); + LRESULT len = SendMsgW(CB_GETLBTEXTLEN, MY_int_TO_WPARAM(index), 0); if (len == CB_ERR) return len; - LRESULT len2 = SendMsgW(CB_GETLBTEXT, index, (LPARAM)s.GetBuf((unsigned)len)); + LRESULT len2 = SendMsgW(CB_GETLBTEXT, MY_int_TO_WPARAM(index), (LPARAM)s.GetBuf((unsigned)len)); if (len2 == CB_ERR) return len; if (len > len2) @@ -55,12 +55,21 @@ LRESULT CComboBox::GetLBText(int index, UString &s) return len; } AString sa; - LRESULT len = GetLBText(index, sa); + const LRESULT len = GetLBText(index, sa); if (len == CB_ERR) return len; s = GetUnicodeString(sa); - return s.Len(); + return (LRESULT)s.Len(); } #endif +LRESULT CComboBox::AddString_SetItemData(LPCWSTR s, LPARAM lParam) +{ + const LRESULT index = AddString(s); + // NOTE: SetItemData((int)-1, lParam) works as unexpected. + if (index >= 0) // optional check, because (index < 0) is not expected for normal inputs + SetItemData((int)index, lParam); + return index; +} + }} diff --git a/src/plugins/7zip/7za/cpp/Windows/Control/ComboBox.h b/src/plugins/7zip/7za/cpp/Windows/Control/ComboBox.h index 1d5a4821..224efcaa 100644 --- a/src/plugins/7zip/7za/cpp/Windows/Control/ComboBox.h +++ b/src/plugins/7zip/7za/cpp/Windows/Control/ComboBox.h @@ -1,11 +1,11 @@ // Windows/Control/ComboBox.h -#ifndef __WINDOWS_CONTROL_COMBOBOX_H -#define __WINDOWS_CONTROL_COMBOBOX_H +#ifndef ZIP7_INC_WINDOWS_CONTROL_COMBOBOX_H +#define ZIP7_INC_WINDOWS_CONTROL_COMBOBOX_H #include "../../Common/MyWindows.h" -#include +#include #include "../Window.h" @@ -20,19 +20,29 @@ class CComboBox: public CWindow #ifndef _UNICODE LRESULT AddString(LPCWSTR s); #endif - LRESULT SetCurSel(int index) { return SendMsg(CB_SETCURSEL, index, 0); } + + LRESULT AddString_SetItemData(LPCWSTR s, LPARAM lParam); + + /* If this parameter is -1, any current selection in the list is removed and the edit control is cleared.*/ + LRESULT SetCurSel(int index) { return SendMsg(CB_SETCURSEL, MY_int_TO_WPARAM(index), 0); } + LRESULT SetCurSel(unsigned index) { return SendMsg(CB_SETCURSEL, index, 0); } + + /* If no item is selected, it returns CB_ERR (-1) */ int GetCurSel() { return (int)SendMsg(CB_GETCURSEL, 0, 0); } + + /* If an error occurs, it is CB_ERR (-1) */ int GetCount() { return (int)SendMsg(CB_GETCOUNT, 0, 0); } - LRESULT GetLBTextLen(int index) { return SendMsg(CB_GETLBTEXTLEN, index, 0); } - LRESULT GetLBText(int index, LPTSTR s) { return SendMsg(CB_GETLBTEXT, index, (LPARAM)s); } + LRESULT GetLBTextLen(int index) { return SendMsg(CB_GETLBTEXTLEN, MY_int_TO_WPARAM(index), 0); } + LRESULT GetLBText(int index, LPTSTR s) { return SendMsg(CB_GETLBTEXT, MY_int_TO_WPARAM(index), (LPARAM)s); } LRESULT GetLBText(int index, CSysString &s); #ifndef _UNICODE LRESULT GetLBText(int index, UString &s); #endif - LRESULT SetItemData(int index, LPARAM lParam) { return SendMsg(CB_SETITEMDATA, index, lParam); } - LRESULT GetItemData(int index) { return SendMsg(CB_GETITEMDATA, index, 0); } + LRESULT SetItemData(int index, LPARAM lParam) { return SendMsg(CB_SETITEMDATA, MY_int_TO_WPARAM(index), lParam); } + LRESULT GetItemData(int index) { return SendMsg(CB_GETITEMDATA, MY_int_TO_WPARAM(index), 0); } + LRESULT GetItemData(unsigned index) { return SendMsg(CB_GETITEMDATA, index, 0); } LRESULT GetItemData_of_CurSel() { return GetItemData(GetCurSel()); } @@ -46,14 +56,18 @@ class CComboBoxEx: public CComboBox public: bool SetUnicodeFormat(bool fUnicode) { return LRESULTToBool(SendMsg(CBEM_SETUNICODEFORMAT, BOOLToBool(fUnicode), 0)); } - LRESULT DeleteItem(int index) { return SendMsg(CBEM_DELETEITEM, index, 0); } + /* Returns: + an INT value that represents the number of items remaining in the control. + If (index) is invalid, the message returns CB_ERR. */ + LRESULT DeleteItem(int index) { return SendMsg(CBEM_DELETEITEM, MY_int_TO_WPARAM(index), 0); } + LRESULT InsertItem(COMBOBOXEXITEM *item) { return SendMsg(CBEM_INSERTITEM, 0, (LPARAM)item); } #ifndef _UNICODE LRESULT InsertItem(COMBOBOXEXITEMW *item) { return SendMsg(CBEM_INSERTITEMW, 0, (LPARAM)item); } #endif LRESULT SetItem(COMBOBOXEXITEM *item) { return SendMsg(CBEM_SETITEM, 0, (LPARAM)item); } - DWORD SetExtendedStyle(DWORD exMask, DWORD exStyle) { return (DWORD)SendMsg(CBEM_SETEXTENDEDSTYLE, exMask, exStyle); } + DWORD SetExtendedStyle(DWORD exMask, DWORD exStyle) { return (DWORD)SendMsg(CBEM_SETEXTENDEDSTYLE, exMask, (LPARAM)exStyle); } HWND GetEditControl() { return (HWND)SendMsg(CBEM_GETEDITCONTROL, 0, 0); } HIMAGELIST SetImageList(HIMAGELIST imageList) { return (HIMAGELIST)SendMsg(CBEM_SETIMAGELIST, 0, (LPARAM)imageList); } }; diff --git a/src/plugins/7zip/7za/cpp/Windows/Control/CommandBar.h b/src/plugins/7zip/7za/cpp/Windows/Control/CommandBar.h index a6197447..d1b2f178 100644 --- a/src/plugins/7zip/7za/cpp/Windows/Control/CommandBar.h +++ b/src/plugins/7zip/7za/cpp/Windows/Control/CommandBar.h @@ -1,7 +1,7 @@ // Windows/Control/CommandBar.h -#ifndef __WINDOWS_CONTROL_COMMANDBAR_H -#define __WINDOWS_CONTROL_COMMANDBAR_H +#ifndef ZIP7_INC_WINDOWS_CONTROL_COMMANDBAR_H +#define ZIP7_INC_WINDOWS_CONTROL_COMMANDBAR_H #ifdef UNDER_CE @@ -26,12 +26,12 @@ class CCommandBar: public NWindows::CWindow // Macros // void Destroy() { CommandBar_Destroy(_window); } // bool AddButtons(UINT numButtons, LPTBBUTTON buttons) { return BOOLToBool(SendMsg(TB_ADDBUTTONS, (WPARAM)numButtons, (LPARAM)buttons)); } - bool InsertButton(int iButton, LPTBBUTTON button) { return BOOLToBool(SendMsg(TB_INSERTBUTTON, (WPARAM)iButton, (LPARAM)button)); } - BOOL AddToolTips(UINT numToolTips, LPTSTR toolTips) { return BOOLToBool(SendMsg(TB_SETTOOLTIPS, (WPARAM)numToolTips, (LPARAM)toolTips)); } + // bool InsertButton(unsigned iButton, LPTBBUTTON button) { return BOOLToBool(SendMsg(TB_INSERTBUTTON, (WPARAM)iButton, (LPARAM)button)); } + // BOOL AddToolTips(UINT numToolTips, LPTSTR toolTips) { return BOOLToBool(SendMsg(TB_SETTOOLTIPS, (WPARAM)numToolTips, (LPARAM)toolTips)); } void AutoSize() { SendMsg(TB_AUTOSIZE, 0, 0); } - bool AddAdornments(DWORD dwFlags) { return BOOLToBool(::CommandBar_AddAdornments(_window, dwFlags, 0)); } - int AddBitmap(HINSTANCE hInst, int idBitmap, int iNumImages, int iImageWidth, int iImageHeight) { return ::CommandBar_AddBitmap(_window, hInst, idBitmap, iNumImages, iImageWidth, iImageHeight); } + // bool AddAdornments(DWORD dwFlags) { return BOOLToBool(::CommandBar_AddAdornments(_window, dwFlags, 0)); } + // int AddBitmap(HINSTANCE hInst, int idBitmap, int iNumImages, int iImageWidth, int iImageHeight) { return ::CommandBar_AddBitmap(_window, hInst, idBitmap, iNumImages, iImageWidth, iImageHeight); } bool DrawMenuBar(WORD iButton) { return BOOLToBool(::CommandBar_DrawMenuBar(_window, iButton)); } HMENU GetMenu(WORD iButton) { return ::CommandBar_GetMenu(_window, iButton); } int Height() { return CommandBar_Height(_window); } diff --git a/src/plugins/7zip/7za/cpp/Windows/Control/Dialog.cpp b/src/plugins/7zip/7za/cpp/Windows/Control/Dialog.cpp index 9df3ef5e..cbb000bb 100644 --- a/src/plugins/7zip/7za/cpp/Windows/Control/Dialog.cpp +++ b/src/plugins/7zip/7za/cpp/Windows/Control/Dialog.cpp @@ -2,6 +2,8 @@ #include "StdAfx.h" +// #include "../../Windows/DLL.h" + #ifndef _UNICODE #include "../../Common/StringConvert.h" #endif @@ -16,7 +18,14 @@ extern bool g_IsNT; namespace NWindows { namespace NControl { -static INT_PTR APIENTRY DialogProcedure(HWND dialogHWND, UINT message, WPARAM wParam, LPARAM lParam) +static +#ifdef Z7_OLD_WIN_SDK + BOOL +#else + INT_PTR +#endif +APIENTRY +DialogProcedure(HWND dialogHWND, UINT message, WPARAM wParam, LPARAM lParam) { CWindow tempDialog(dialogHWND); if (message == WM_INITDIALOG) @@ -26,6 +35,14 @@ static INT_PTR APIENTRY DialogProcedure(HWND dialogHWND, UINT message, WPARAM wP return FALSE; if (message == WM_INITDIALOG) dialog->Attach(dialogHWND); + + /* MSDN: The dialog box procedure should return + TRUE - if it processed the message + FALSE - if it did not process the message + If the dialog box procedure returns FALSE, + the dialog manager performs the default dialog operation in response to the message. + */ + try { return BoolToBOOL(dialog->OnMessage(message, wParam, lParam)); } catch(...) { return TRUE; } } @@ -35,10 +52,11 @@ bool CDialog::OnMessage(UINT message, WPARAM wParam, LPARAM lParam) switch (message) { case WM_INITDIALOG: return OnInit(); - case WM_COMMAND: return OnCommand(wParam, lParam); + case WM_COMMAND: return OnCommand(HIWORD(wParam), LOWORD(wParam), lParam); case WM_NOTIFY: return OnNotify((UINT)wParam, (LPNMHDR) lParam); case WM_TIMER: return OnTimer(wParam, lParam); case WM_SIZE: return OnSize(wParam, LOWORD(lParam), HIWORD(lParam)); + case WM_DESTROY: return OnDestroy(); case WM_HELP: OnHelp(); return true; /* OnHelp( @@ -54,47 +72,105 @@ bool CDialog::OnMessage(UINT message, WPARAM wParam, LPARAM lParam) } } -bool CDialog::OnCommand(WPARAM wParam, LPARAM lParam) +/* +bool CDialog::OnCommand2(WPARAM wParam, LPARAM lParam) { return OnCommand(HIWORD(wParam), LOWORD(wParam), lParam); } +*/ -bool CDialog::OnCommand(int code, int itemID, LPARAM lParam) +bool CDialog::OnCommand(unsigned code, unsigned itemID, LPARAM lParam) { if (code == BN_CLICKED) return OnButtonClicked(itemID, (HWND)lParam); return false; } -bool CDialog::OnButtonClicked(int buttonID, HWND /* buttonHWND */) +bool CDialog::OnButtonClicked(unsigned buttonID, HWND /* buttonHWND */) { switch (buttonID) { case IDOK: OnOK(); break; case IDCANCEL: OnCancel(); break; + case IDCLOSE: OnClose(); break; + case IDCONTINUE: OnContinue(); break; case IDHELP: OnHelp(); break; default: return false; } return true; } -static bool GetWorkAreaRect(RECT *rect) +#ifndef UNDER_CE +/* in win2000/win98 : monitor functions are supported. + We need dynamic linking, if we want nt4/win95 support in program. + Even if we compile the code with low (WINVER) value, we still + want to use monitor functions. So we declare missing functions here */ +// #if (WINVER < 0x0500) +#ifndef MONITOR_DEFAULTTOPRIMARY +extern "C" { +DECLARE_HANDLE(HMONITOR); +#define MONITOR_DEFAULTTOPRIMARY 0x00000001 +typedef struct tagMONITORINFO +{ + DWORD cbSize; + RECT rcMonitor; + RECT rcWork; + DWORD dwFlags; +} MONITORINFO, *LPMONITORINFO; +WINUSERAPI HMONITOR WINAPI MonitorFromWindow(HWND hwnd, DWORD dwFlags); +WINUSERAPI BOOL WINAPI GetMonitorInfoA(HMONITOR hMonitor, LPMONITORINFO lpmi); +} +#endif +#endif + +static bool GetWorkAreaRect(RECT *rect, HWND hwnd) { - // use another function for multi-monitor. + if (hwnd) + { + #ifndef UNDER_CE + /* MonitorFromWindow() is supported in Win2000+ + MonitorFromWindow() : retrieves a handle to the display monitor that has the + largest area of intersection with the bounding rectangle of a specified window. + dwFlags: Determines the function's return value if the window does not intersect any display monitor. + MONITOR_DEFAULTTONEAREST : Returns display that is nearest to the window. + MONITOR_DEFAULTTONULL : Returns NULL. + MONITOR_DEFAULTTOPRIMARY : Returns the primary display monitor. + */ + const HMONITOR hmon = MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY); + if (hmon) + { + MONITORINFO mi; + memset(&mi, 0, sizeof(mi)); + mi.cbSize = sizeof(mi); + if (GetMonitorInfoA(hmon, &mi)) + { + *rect = mi.rcWork; + return true; + } + } + #endif + } + + /* Retrieves the size of the work area on the primary display monitor. + The work area is the portion of the screen not obscured + by the system taskbar or by application desktop toolbars. + Any DPI virtualization mode of the caller has no effect on this output. */ + return BOOLToBool(::SystemParametersInfo(SPI_GETWORKAREA, 0, rect, 0)); } -bool IsDialogSizeOK(int xSize, int ySize) + +bool IsDialogSizeOK(int xSize, int ySize, HWND hwnd) { // it returns for system font. Real font uses another values - LONG v = GetDialogBaseUnits(); - int x = LOWORD(v); - int y = HIWORD(v); + const LONG v = GetDialogBaseUnits(); + const int x = LOWORD(v); + const int y = HIWORD(v); RECT rect; - GetWorkAreaRect(&rect); - int wx = RECT_SIZE_X(rect); - int wy = RECT_SIZE_Y(rect); + GetWorkAreaRect(&rect, hwnd); + const int wx = RECT_SIZE_X(rect); + const int wy = RECT_SIZE_Y(rect); return xSize / 4 * x <= wx && ySize / 8 * y <= wy; @@ -128,7 +204,7 @@ int CDialog::Units_To_Pixels_X(int units) return rect.right - rect.left; } -bool CDialog::GetItemSizes(int id, int &x, int &y) +bool CDialog::GetItemSizes(unsigned id, int &x, int &y) { RECT rect; if (!::GetWindowRect(GetItem(id), &rect)) @@ -138,62 +214,182 @@ bool CDialog::GetItemSizes(int id, int &x, int &y) return true; } -void CDialog::GetClientRectOfItem(int id, RECT &rect) +void CDialog::GetClientRectOfItem(unsigned id, RECT &rect) { ::GetWindowRect(GetItem(id), &rect); ScreenToClient(&rect); } -bool CDialog::MoveItem(int id, int x, int y, int width, int height, bool repaint) +bool CDialog::MoveItem(unsigned id, int x, int y, int width, int height, bool repaint) { return BOOLToBool(::MoveWindow(GetItem(id), x, y, width, height, BoolToBOOL(repaint))); } + +/* +typedef BOOL (WINAPI * Func_DwmGetWindowAttribute)( + HWND hwnd, DWORD dwAttribute, PVOID pvAttribute, DWORD cbAttribute); + +static bool GetWindowsRect_DWM(HWND hwnd, RECT *rect) +{ + // dll load and free is too slow : 300 calls in second. + NDLL::CLibrary dll; + if (!dll.Load(FTEXT("dwmapi.dll"))) + return false; + Func_DwmGetWindowAttribute f = (Func_DwmGetWindowAttribute)dll.GetProc("DwmGetWindowAttribute" ); + if (f) + { + #define MY__DWMWA_EXTENDED_FRAME_BOUNDS 9 + // 30000 per second + RECT r; + if (f(hwnd, MY__DWMWA_EXTENDED_FRAME_BOUNDS, &r, sizeof(RECT)) == S_OK) + { + *rect = r; + return true; + } + } + return false; +} +*/ + + +static bool IsRect_Small_Inside_Big(const RECT &sm, const RECT &big) +{ + return sm.left >= big.left + && sm.right <= big.right + && sm.top >= big.top + && sm.bottom <= big.bottom; +} + + +static bool AreRectsOverlapped(const RECT &r1, const RECT &r2) +{ + return r1.left < r2.right + && r1.right > r2.left + && r1.top < r2.bottom + && r1.bottom > r2.top; +} + + +static bool AreRectsEqual(const RECT &r1, const RECT &r2) +{ + return r1.left == r2.left + && r1.right == r2.right + && r1.top == r2.top + && r1.bottom == r2.bottom; +} + + void CDialog::NormalizeSize(bool fullNormalize) { RECT workRect; - GetWorkAreaRect(&workRect); - int xSize = RECT_SIZE_X(workRect); - int ySize = RECT_SIZE_Y(workRect); + if (!GetWorkAreaRect(&workRect, *this)) + return; RECT rect; - GetWindowRect(&rect); - int xSize2 = RECT_SIZE_X(rect); - int ySize2 = RECT_SIZE_Y(rect); - bool needMove = (xSize2 > xSize || ySize2 > ySize); - if (xSize2 > xSize || (needMove && fullNormalize)) + if (!GetWindowRect(&rect)) + return; + int xs = RECT_SIZE_X(rect); + int ys = RECT_SIZE_Y(rect); + + // we don't want to change size using workRect, if window is outside of WorkArea + if (!AreRectsOverlapped(rect, workRect)) + return; + + /* here rect and workRect are overlapped, but it can be false + overlapping of small shadow when window in another display. */ + + const int xsW = RECT_SIZE_X(workRect); + const int ysW = RECT_SIZE_Y(workRect); + if (xs <= xsW && ys <= ysW) + return; // size of window is OK + if (fullNormalize) + { + Show(SW_SHOWMAXIMIZED); + return; + } + int x = workRect.left; + int y = workRect.top; + if (xs < xsW) x += (xsW - xs) / 2; else xs = xsW; + if (ys < ysW) y += (ysW - ys) / 2; else ys = ysW; + Move(x, y, xs, ys, true); +} + + +void CDialog::NormalizePosition() +{ + RECT workRect; + if (!GetWorkAreaRect(&workRect, *this)) + return; + + RECT rect2 = workRect; + bool useWorkArea = true; + const HWND parentHWND = GetParent(); + + if (parentHWND) { - rect.left = workRect.left; - rect.right = workRect.right; - xSize2 = xSize; + RECT workRectParent; + if (!GetWorkAreaRect(&workRectParent, parentHWND)) + return; + + // if windows are in different monitors, we use only workArea of current window + + if (AreRectsEqual(workRectParent, workRect)) + { + // RECT rect3; if (GetWindowsRect_DWM(parentHWND, &rect3)) {} + CWindow wnd(parentHWND); + if (wnd.GetWindowRect(&rect2)) + { + // it's same monitor. So we try to use parentHWND rect. + /* we don't want to change position, if parent window is not inside work area. + In Win10 : parent window rect is 8 pixels larger for each corner than window size for shadow. + In maximize mode : window is outside of workRect. + if parent window is inside workRect, we will use parent window instead of workRect */ + if (IsRect_Small_Inside_Big(rect2, workRect)) + useWorkArea = false; + } + } } - if (ySize2 > ySize || (needMove && fullNormalize)) + + RECT rect; + if (!GetWindowRect(&rect)) + return; + + if (useWorkArea) { - rect.top = workRect.top; - rect.bottom = workRect.bottom; - ySize2 = ySize; + // we don't want to move window, if it's already inside. + if (IsRect_Small_Inside_Big(rect, workRect)) + return; + // we don't want to move window, if it's outside of workArea + if (!AreRectsOverlapped(rect, workRect)) + return; + rect2 = workRect; } - if (needMove) + { - if (fullNormalize) - Show(SW_SHOWMAXIMIZED); - else - Move(rect.left, rect.top, xSize2, ySize2, true); + const int xs = RECT_SIZE_X(rect); + const int ys = RECT_SIZE_Y(rect); + const int xs2 = RECT_SIZE_X(rect2); + const int ys2 = RECT_SIZE_Y(rect2); + // we don't want to change position if parent is smaller. + if (xs <= xs2 && ys <= ys2) + { + const int x = rect2.left + (xs2 - xs) / 2; + const int y = rect2.top + (ys2 - ys) / 2; + + if (x != rect.left || y != rect.top) + Move(x, y, xs, ys, true); + // SetWindowPos(*this, HWND_TOP, x, y, 0, 0, SWP_NOSIZE); + return; + } } } -void CDialog::NormalizePosition() -{ - RECT workRect, rect; - GetWorkAreaRect(&workRect); - GetWindowRect(&rect); - if (rect.bottom > workRect.bottom && rect.top > workRect.top) - Move(rect.left, workRect.top, RECT_SIZE_X(rect), RECT_SIZE_Y(rect), true); -} + bool CModelessDialog::Create(LPCTSTR templateName, HWND parentWindow) { - HWND aHWND = CreateDialogParam(g_hInstance, templateName, parentWindow, DialogProcedure, (LPARAM)this); - if (aHWND == 0) + const HWND aHWND = CreateDialogParam(g_hInstance, templateName, parentWindow, DialogProcedure, (LPARAM)this); + if (!aHWND) return false; Attach(aHWND); return true; diff --git a/src/plugins/7zip/7za/cpp/Windows/Control/Dialog.h b/src/plugins/7zip/7za/cpp/Windows/Control/Dialog.h index 59b9f419..2510c8f1 100644 --- a/src/plugins/7zip/7za/cpp/Windows/Control/Dialog.h +++ b/src/plugins/7zip/7za/cpp/Windows/Control/Dialog.h @@ -1,62 +1,91 @@ // Windows/Control/Dialog.h -#ifndef __WINDOWS_CONTROL_DIALOG_H -#define __WINDOWS_CONTROL_DIALOG_H +#ifndef ZIP7_INC_WINDOWS_CONTROL_DIALOG_H +#define ZIP7_INC_WINDOWS_CONTROL_DIALOG_H #include "../Window.h" namespace NWindows { namespace NControl { +#ifndef IDCONTINUE +#define IDCONTINUE 11 +#endif + class CDialog: public CWindow { + // Z7_CLASS_NO_COPY(CDialog) public: - CDialog(HWND wnd = NULL): CWindow(wnd){}; - virtual ~CDialog() {}; + CDialog(HWND wnd = NULL): CWindow(wnd) {} + virtual ~CDialog() {} - HWND GetItem(int itemID) const - { return GetDlgItem(_window, itemID); } + HWND GetItem(unsigned itemID) const + { return GetDlgItem(_window, (int)itemID); } - bool EnableItem(int itemID, bool enable) const + bool EnableItem(unsigned itemID, bool enable) const { return BOOLToBool(::EnableWindow(GetItem(itemID), BoolToBOOL(enable))); } - bool ShowItem(int itemID, int cmdShow) const + bool ShowItem(unsigned itemID, int cmdShow) const { return BOOLToBool(::ShowWindow(GetItem(itemID), cmdShow)); } - bool ShowItem_Bool(int itemID, bool show) const + bool ShowItem_Bool(unsigned itemID, bool show) const { return ShowItem(itemID, show ? SW_SHOW: SW_HIDE); } - bool HideItem(int itemID) const { return ShowItem(itemID, SW_HIDE); } + bool HideItem(unsigned itemID) const { return ShowItem(itemID, SW_HIDE); } + + bool SetItemText(unsigned itemID, LPCTSTR s) + { return BOOLToBool(SetDlgItemText(_window, (int)itemID, s)); } + + bool SetItemTextA(unsigned itemID, LPCSTR s) + { return BOOLToBool(SetDlgItemTextA(_window, (int)itemID, s)); } - bool SetItemText(int itemID, LPCTSTR s) - { return BOOLToBool(SetDlgItemText(_window, itemID, s)); } + bool SetItemText_Empty(unsigned itemID) + { return SetItemText(itemID, TEXT("")); } #ifndef _UNICODE - bool SetItemText(int itemID, LPCWSTR s) + bool SetItemText(unsigned itemID, LPCWSTR s) { CWindow window(GetItem(itemID)); return window.SetText(s); } #endif - UINT GetItemText(int itemID, LPTSTR string, int maxCount) - { return GetDlgItemText(_window, itemID, string, maxCount); } + UINT GetItemText(unsigned itemID, LPTSTR string, unsigned maxCount) + { return GetDlgItemText(_window, (int)itemID, string, (int)maxCount); } #ifndef _UNICODE /* - bool GetItemText(int itemID, LPWSTR string, int maxCount) + bool GetItemText(unsigned itemID, LPWSTR string, int maxCount) { - CWindow window(GetItem(itemID)); + CWindow window(GetItem(unsigned)); return window.GetText(string, maxCount); } */ #endif - bool SetItemInt(int itemID, UINT value, bool isSigned) - { return BOOLToBool(SetDlgItemInt(_window, itemID, value, BoolToBOOL(isSigned))); } - bool GetItemInt(int itemID, bool isSigned, UINT &value) + bool GetItemText(unsigned itemID, UString &s) + { + CWindow window(GetItem(itemID)); + return window.GetText(s); + } + +/* + bool SetItemInt(unsigned itemID, UINT value, bool isSigned) + { return BOOLToBool(SetDlgItemInt(_window, (int)itemID, value, BoolToBOOL(isSigned))); } +*/ + bool SetItemUInt(unsigned itemID, UINT value) + { return BOOLToBool(SetDlgItemInt(_window, (int)itemID, value, FALSE)); } +/* + bool GetItemInt(unsigned itemID, bool isSigned, UINT &value) { BOOL result; - value = GetDlgItemInt(_window, itemID, &result, BoolToBOOL(isSigned)); + value = GetDlgItemInt(_window, (int)itemID, &result, BoolToBOOL(isSigned)); + return BOOLToBool(result); + } +*/ + bool GetItemUInt(unsigned itemID, UINT &value) + { + BOOL result; + value = GetDlgItemInt(_window, (int)itemID, &result, FALSE); return BOOLToBool(result); } @@ -65,33 +94,42 @@ class CDialog: public CWindow HWND GetNextTabItem(HWND control, bool previous) { return GetNextDlgTabItem(_window, control, BoolToBOOL(previous)); } + LRESULT SendMsg_NextDlgCtl(WPARAM wParam, LPARAM lParam) + { return SendMsg(WM_NEXTDLGCTL, wParam, lParam); } + LRESULT SendMsg_NextDlgCtl_HWND(HWND hwnd) { return SendMsg_NextDlgCtl((WPARAM)hwnd, TRUE); } + LRESULT SendMsg_NextDlgCtl_CtlId(unsigned id) { return SendMsg_NextDlgCtl_HWND(GetItem(id)); } + LRESULT SendMsg_NextDlgCtl_Next() { return SendMsg_NextDlgCtl(0, FALSE); } + LRESULT SendMsg_NextDlgCtl_Prev() { return SendMsg_NextDlgCtl(1, FALSE); } + bool MapRect(LPRECT rect) { return BOOLToBool(MapDialogRect(_window, rect)); } bool IsMessage(LPMSG message) { return BOOLToBool(IsDialogMessage(_window, message)); } - LRESULT SendItemMessage(int itemID, UINT message, WPARAM wParam, LPARAM lParam) - { return SendDlgItemMessage(_window, itemID, message, wParam, lParam); } + LRESULT SendItemMessage(unsigned itemID, UINT message, WPARAM wParam, LPARAM lParam) + { return SendDlgItemMessage(_window, (int)itemID, message, wParam, lParam); } - bool CheckButton(int buttonID, UINT checkState) - { return BOOLToBool(CheckDlgButton(_window, buttonID, checkState)); } - bool CheckButton(int buttonID, bool checkState) + bool CheckButton(unsigned buttonID, UINT checkState) + { return BOOLToBool(CheckDlgButton(_window, (int)buttonID, checkState)); } + bool CheckButton(unsigned buttonID, bool checkState) { return CheckButton(buttonID, UINT(checkState ? BST_CHECKED : BST_UNCHECKED)); } - UINT IsButtonChecked(int buttonID) const - { return IsDlgButtonChecked(_window, buttonID); } - bool IsButtonCheckedBool(int buttonID) const - { return (IsButtonChecked(buttonID) == BST_CHECKED); } + UINT IsButtonChecked_BST(unsigned buttonID) const + { return IsDlgButtonChecked(_window, (int)buttonID); } + bool IsButtonCheckedBool(unsigned buttonID) const + { return (IsButtonChecked_BST(buttonID) == BST_CHECKED); } - bool CheckRadioButton(int firstButtonID, int lastButtonID, int checkButtonID) - { return BOOLToBool(::CheckRadioButton(_window, firstButtonID, lastButtonID, checkButtonID)); } + bool CheckRadioButton(unsigned firstButtonID, unsigned lastButtonID, unsigned checkButtonID) + { return BOOLToBool(::CheckRadioButton(_window, + (int)firstButtonID, (int)lastButtonID, (int)checkButtonID)); } virtual bool OnMessage(UINT message, WPARAM wParam, LPARAM lParam); virtual bool OnInit() { return true; } - virtual bool OnCommand(WPARAM wParam, LPARAM lParam); - virtual bool OnCommand(int code, int itemID, LPARAM lParam); + // virtual bool OnCommand2(WPARAM wParam, LPARAM lParam); + virtual bool OnCommand(unsigned code, unsigned itemID, LPARAM lParam); virtual bool OnSize(WPARAM /* wParam */, int /* xSize */, int /* ySize */) { return false; } + virtual bool OnDestroy() { return false; } /* #ifdef UNDER_CE @@ -100,11 +138,13 @@ class CDialog: public CWindow virtual void OnHelp(LPHELPINFO) { OnHelp(); } #endif */ - virtual void OnHelp() {}; + virtual void OnHelp() {} - virtual bool OnButtonClicked(int buttonID, HWND buttonHWND); - virtual void OnOK() {}; - virtual void OnCancel() {}; + virtual bool OnButtonClicked(unsigned buttonID, HWND buttonHWND); + virtual void OnOK() {} + virtual void OnContinue() {} + virtual void OnCancel() {} + virtual void OnClose() {} virtual bool OnNotify(UINT /* controlID */, LPNMHDR /* lParam */) { return false; } virtual bool OnTimer(WPARAM /* timerID */, LPARAM /* callback */) { return false; } @@ -115,9 +155,11 @@ class CDialog: public CWindow bool GetMargins(int margin, int &x, int &y); int Units_To_Pixels_X(int units); - bool GetItemSizes(int id, int &x, int &y); - void GetClientRectOfItem(int id, RECT &rect); - bool MoveItem(int id, int x, int y, int width, int height, bool repaint = true); + bool GetItemSizes(unsigned id, int &x, int &y); + void GetClientRectOfItem(unsigned id, RECT &rect); + bool MoveItem(unsigned id, int x, int y, int width, int height, bool repaint = true); + bool MoveItem_RECT(unsigned id, const RECT &r, bool repaint = true) + { return MoveItem(id, r.left, r.top, RECT_SIZE_X(r), RECT_SIZE_Y(r), repaint); } void NormalizeSize(bool fullNormalize = false); void NormalizePosition(); @@ -131,8 +173,10 @@ class CModelessDialog: public CDialog #ifndef _UNICODE bool Create(LPCWSTR templateName, HWND parentWindow); #endif - virtual void OnOK() { Destroy(); } - virtual void OnCancel() { Destroy(); } + virtual void OnOK() Z7_override { Destroy(); } + virtual void OnContinue() Z7_override { Destroy(); } + virtual void OnCancel() Z7_override { Destroy(); } + virtual void OnClose() Z7_override { Destroy(); } }; class CModalDialog: public CDialog @@ -145,22 +189,24 @@ class CModalDialog: public CDialog #endif bool End(INT_PTR result) { return BOOLToBool(::EndDialog(_window, result)); } - virtual void OnOK() { End(IDOK); } - virtual void OnCancel() { End(IDCANCEL); } + virtual void OnOK() Z7_override { End(IDOK); } + virtual void OnContinue() Z7_override { End(IDCONTINUE); } + virtual void OnCancel() Z7_override { End(IDCANCEL); } + virtual void OnClose() Z7_override { End(IDCLOSE); } }; class CDialogChildControl: public NWindows::CWindow { - int m_ID; + // unsigned m_ID; public: - void Init(const NWindows::NControl::CDialog &parentDialog, int id) + void Init(const NWindows::NControl::CDialog &parentDialog, unsigned id) { - m_ID = id; + // m_ID = id; Attach(parentDialog.GetItem(id)); } }; -bool IsDialogSizeOK(int xSize, int ySize); +bool IsDialogSizeOK(int xSize, int ySize, HWND hwnd = NULL); }} diff --git a/src/plugins/7zip/7za/cpp/Windows/Control/Edit.h b/src/plugins/7zip/7za/cpp/Windows/Control/Edit.h index 51a22c53..963470d7 100644 --- a/src/plugins/7zip/7za/cpp/Windows/Control/Edit.h +++ b/src/plugins/7zip/7za/cpp/Windows/Control/Edit.h @@ -1,7 +1,7 @@ // Windows/Control/Edit.h -#ifndef __WINDOWS_CONTROL_EDIT_H -#define __WINDOWS_CONTROL_EDIT_H +#ifndef ZIP7_INC_WINDOWS_CONTROL_EDIT_H +#define ZIP7_INC_WINDOWS_CONTROL_EDIT_H #include "../Window.h" diff --git a/src/plugins/7zip/7za/cpp/Windows/Control/ImageList.h b/src/plugins/7zip/7za/cpp/Windows/Control/ImageList.h index 0d9c9313..c10eea7b 100644 --- a/src/plugins/7zip/7za/cpp/Windows/Control/ImageList.h +++ b/src/plugins/7zip/7za/cpp/Windows/Control/ImageList.h @@ -1,11 +1,11 @@ // Windows/Control/ImageList.h -#ifndef __WINDOWS_CONTROL_IMAGE_LIST_H -#define __WINDOWS_CONTROL_IMAGE_LIST_H +#ifndef ZIP7_INC_WINDOWS_CONTROL_IMAGE_LIST_H +#define ZIP7_INC_WINDOWS_CONTROL_IMAGE_LIST_H -#include +#include -#include "../Defs.h" +#include "../WinDefs.h" namespace NWindows { namespace NControl { @@ -56,7 +56,7 @@ class CImageList bool GetImageInfo(int index, IMAGEINFO* imageInfo) const { return BOOLToBool(ImageList_GetImageInfo(m_Object, index, imageInfo)); } - int Add(HBITMAP hbmImage, HBITMAP hbmMask = 0) + int Add(HBITMAP hbmImage, HBITMAP hbmMask = NULL) { return ImageList_Add(m_Object, hbmImage, hbmMask); } int AddMasked(HBITMAP hbmImage, COLORREF mask) { return ImageList_AddMasked(m_Object, hbmImage, mask); } diff --git a/src/plugins/7zip/7za/cpp/Windows/Control/ListView.cpp b/src/plugins/7zip/7za/cpp/Windows/Control/ListView.cpp index 6d916591..3e8786a1 100644 --- a/src/plugins/7zip/7za/cpp/Windows/Control/ListView.cpp +++ b/src/plugins/7zip/7za/cpp/Windows/Control/ListView.cpp @@ -20,78 +20,85 @@ bool CListView::CreateEx(DWORD exStyle, DWORD style, height, parentWindow, idOrHMenu, instance, createParam); } -bool CListView::GetItemParam(int index, LPARAM ¶m) const +/* note: LVITEM and LVCOLUMN structures contain optional fields + depending from preprocessor macros: + #if (_WIN32_IE >= 0x0300) + #if (_WIN32_WINNT >= 0x0501) + #if (_WIN32_WINNT >= 0x0600) +*/ + +bool CListView::GetItemParam(unsigned index, LPARAM ¶m) const { LVITEM item; - item.iItem = index; + item.iItem = (int)index; item.iSubItem = 0; item.mask = LVIF_PARAM; - bool aResult = GetItem(&item); + const bool res = GetItem(&item); param = item.lParam; - return aResult; + return res; } -int CListView::InsertColumn(int columnIndex, LPCTSTR text, int width) +int CListView::InsertColumn(unsigned columnIndex, LPCTSTR text, int width) { LVCOLUMN ci; ci.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_SUBITEM; - ci.pszText = (LPTSTR)text; - ci.iSubItem = columnIndex; + ci.pszText = (LPTSTR)(void *)text; + ci.iSubItem = (int)columnIndex; ci.cx = width; return InsertColumn(columnIndex, &ci); } -int CListView::InsertItem(int index, LPCTSTR text) +int CListView::InsertItem(unsigned index, LPCTSTR text) { LVITEM item; item.mask = LVIF_TEXT | LVIF_PARAM; - item.iItem = index; - item.lParam = index; - item.pszText = (LPTSTR)text; + item.iItem = (int)index; + item.lParam = (LPARAM)index; + item.pszText = (LPTSTR)(void *)text; item.iSubItem = 0; return InsertItem(&item); } -int CListView::SetSubItem(int index, int subIndex, LPCTSTR text) +int CListView::SetSubItem(unsigned index, unsigned subIndex, LPCTSTR text) { LVITEM item; item.mask = LVIF_TEXT; - item.iItem = index; - item.pszText = (LPTSTR)text; - item.iSubItem = subIndex; + item.iItem = (int)index; + item.pszText = (LPTSTR)(void *)text; + item.iSubItem = (int)subIndex; return SetItem(&item); } #ifndef _UNICODE -int CListView::InsertColumn(int columnIndex, LPCWSTR text, int width) +int CListView::InsertColumn(unsigned columnIndex, LPCWSTR text, int width) { LVCOLUMNW ci; ci.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_SUBITEM; - ci.pszText = (LPWSTR)text; - ci.iSubItem = columnIndex; + ci.pszText = (LPWSTR)(void *)text; + ci.iSubItem = (int)columnIndex; ci.cx = width; return InsertColumn(columnIndex, &ci); } -int CListView::InsertItem(int index, LPCWSTR text) +int CListView::InsertItem(unsigned index, LPCWSTR text) { LVITEMW item; item.mask = LVIF_TEXT | LVIF_PARAM; - item.iItem = index; - item.lParam = index; - item.pszText = (LPWSTR)text; + item.iItem = (int)index; + item.lParam = (LPARAM)index; + item.pszText = (LPWSTR)(void *)text; item.iSubItem = 0; return InsertItem(&item); } -int CListView::SetSubItem(int index, int subIndex, LPCWSTR text) +int CListView::SetSubItem(unsigned index, unsigned subIndex, LPCWSTR text) { LVITEMW item; item.mask = LVIF_TEXT; - item.iItem = index; - item.pszText = (LPWSTR)text; - item.iSubItem = subIndex; + item.iItem = (int)index; + item.pszText = (LPWSTR)(void *)text; + item.iSubItem = (int)subIndex; return SetItem(&item); } diff --git a/src/plugins/7zip/7za/cpp/Windows/Control/ListView.h b/src/plugins/7zip/7za/cpp/Windows/Control/ListView.h index 9a3abe70..11a33a07 100644 --- a/src/plugins/7zip/7za/cpp/Windows/Control/ListView.h +++ b/src/plugins/7zip/7za/cpp/Windows/Control/ListView.h @@ -1,11 +1,11 @@ // Windows/Control/ListView.h -#ifndef __WINDOWS_CONTROL_LISTVIEW_H -#define __WINDOWS_CONTROL_LISTVIEW_H +#ifndef ZIP7_INC_WINDOWS_CONTROL_LISTVIEW_H +#define ZIP7_INC_WINDOWS_CONTROL_LISTVIEW_H #include "../../Common/MyWindows.h" -#include +#include #include "../Window.h" @@ -28,11 +28,12 @@ class CListView: public NWindows::CWindow } bool DeleteAllItems() { return BOOLToBool(ListView_DeleteAllItems(_window)); } - bool DeleteColumn(int columnIndex) { return BOOLToBool(ListView_DeleteColumn(_window, columnIndex)); } + bool DeleteColumn(unsigned columnIndex) { return BOOLToBool(ListView_DeleteColumn(_window, columnIndex)); } - int InsertColumn(int columnIndex, const LVCOLUMN *columnInfo) { return ListView_InsertColumn(_window, columnIndex, columnInfo); } - int InsertColumn(int columnIndex, LPCTSTR text, int width); - bool SetColumnOrderArray(int count, const int *columns) { return BOOLToBool(ListView_SetColumnOrderArray(_window, count, columns)); } + int InsertColumn(unsigned columnIndex, const LVCOLUMN *columnInfo) { return ListView_InsertColumn(_window, columnIndex, columnInfo); } + int InsertColumn(unsigned columnIndex, LPCTSTR text, int width); + bool SetColumnOrderArray(unsigned count, const int *columns) + { return BOOLToBool(ListView_SetColumnOrderArray(_window, count, (int *)(void *)columns)); } /* int GetNumColumns() @@ -45,43 +46,49 @@ class CListView: public NWindows::CWindow */ int InsertItem(const LVITEM* item) { return ListView_InsertItem(_window, item); } - int InsertItem(int index, LPCTSTR text); + int InsertItem(unsigned index, LPCTSTR text); bool SetItem(const LVITEM* item) { return BOOLToBool(ListView_SetItem(_window, item)); } - int SetSubItem(int index, int subIndex, LPCTSTR text); + int SetSubItem(unsigned index, unsigned subIndex, LPCTSTR text); #ifndef _UNICODE - int InsertColumn(int columnIndex, const LVCOLUMNW *columnInfo) { return (int)SendMsg(LVM_INSERTCOLUMNW, (WPARAM)columnIndex, (LPARAM)columnInfo); } - int InsertColumn(int columnIndex, LPCWSTR text, int width); + int InsertColumn(unsigned columnIndex, const LVCOLUMNW *columnInfo) { return (int)SendMsg(LVM_INSERTCOLUMNW, (WPARAM)columnIndex, (LPARAM)columnInfo); } + int InsertColumn(unsigned columnIndex, LPCWSTR text, int width); int InsertItem(const LV_ITEMW* item) { return (int)SendMsg(LVM_INSERTITEMW, 0, (LPARAM)item); } - int InsertItem(int index, LPCWSTR text); + int InsertItem(unsigned index, LPCWSTR text); bool SetItem(const LV_ITEMW* item) { return BOOLToBool((BOOL)SendMsg(LVM_SETITEMW, 0, (LPARAM)item)); } - int SetSubItem(int index, int subIndex, LPCWSTR text); + int SetSubItem(unsigned index, unsigned subIndex, LPCWSTR text); #endif - bool DeleteItem(int itemIndex) { return BOOLToBool(ListView_DeleteItem(_window, itemIndex)); } + bool DeleteItem(unsigned itemIndex) { return BOOLToBool(ListView_DeleteItem(_window, itemIndex)); } UINT GetSelectedCount() const { return ListView_GetSelectedCount(_window); } int GetItemCount() const { return ListView_GetItemCount(_window); } INT GetSelectionMark() const { return ListView_GetSelectionMark(_window); } - void SetItemCount(int numItems) { ListView_SetItemCount(_window, numItems); } - void SetItemCountEx(int numItems, DWORD flags) { ListView_SetItemCountEx(_window, numItems, flags); } + void SetItemCount(unsigned numItems) { ListView_SetItemCount(_window, numItems); } + void SetItemCountEx(unsigned numItems, DWORD flags) { ListView_SetItemCountEx(_window, numItems, flags); } + /* startIndex : The index of the item with which to begin the search, + or -1 to find the first item that matches the specified flags. + The specified item itself is excluded from the search. */ int GetNextItem(int startIndex, UINT flags) const { return ListView_GetNextItem(_window, startIndex, flags); } int GetNextSelectedItem(int startIndex) const { return GetNextItem(startIndex, LVNI_SELECTED); } int GetFocusedItem() const { return GetNextItem(-1, LVNI_FOCUSED); } bool GetItem(LVITEM* item) const { return BOOLToBool(ListView_GetItem(_window, item)); } - bool GetItemParam(int itemIndex, LPARAM ¶m) const; - void GetItemText(int itemIndex, int subItemIndex, LPTSTR text, int textSizeMax) const - { ListView_GetItemText(_window, itemIndex, subItemIndex, text, textSizeMax); } + bool GetItemParam(unsigned itemIndex, LPARAM ¶m) const; + /* + void GetItemText(unsigned itemIndex, unsigned subItemIndex, LPTSTR text, unsigned textSizeMax) const + { ListView_GetItemText(_window, itemIndex, subItemIndex, text, textSizeMax) } + */ bool SortItems(PFNLVCOMPARE compareFunction, LPARAM dataParam) { return BOOLToBool(ListView_SortItems(_window, compareFunction, dataParam)); } - void SetItemState(int index, UINT state, UINT mask) { ListView_SetItemState(_window, index, state, mask); } + // If (index == -1), then the state change is applied to all items. + void SetItemState(int index, UINT state, UINT mask) { ListView_SetItemState(_window, index, state, mask) } void SetItemState_Selected(int index, bool select) { SetItemState(index, select ? LVIS_SELECTED : 0, LVIS_SELECTED); } void SetItemState_Selected(int index) { SetItemState(index, LVIS_SELECTED, LVIS_SELECTED); } void SelectAll() { SetItemState_Selected(-1); } @@ -89,7 +96,7 @@ class CListView: public NWindows::CWindow UINT GetItemState(int index, UINT mask) const { return ListView_GetItemState(_window, index, mask); } bool IsItemSelected(int index) const { return GetItemState(index, LVIS_SELECTED) == LVIS_SELECTED; } - bool GetColumn(int columnIndex, LVCOLUMN* columnInfo) const + bool GetColumn(unsigned columnIndex, LVCOLUMN* columnInfo) const { return BOOLToBool(ListView_GetColumn(_window, columnIndex, columnInfo)); } HIMAGELIST SetImageList(HIMAGELIST imageList, int imageListType) @@ -100,7 +107,7 @@ class CListView: public NWindows::CWindow void SetExtendedListViewStyle(DWORD exStyle) { ListView_SetExtendedListViewStyle(_window, exStyle); } void SetExtendedListViewStyle(DWORD exMask, DWORD exStyle) { ListView_SetExtendedListViewStyleEx(_window, exMask, exStyle); } - void SetCheckState(UINT index, bool checkState) { ListView_SetCheckState(_window, index, BoolToBOOL(checkState)); } + void SetCheckState(UINT index, bool checkState) { ListView_SetCheckState(_window, index, BoolToBOOL(checkState)) } bool GetCheckState(UINT index) { return BOOLToBool(ListView_GetCheckState(_window, index)); } bool EnsureVisible(int index, bool partialOK) { return BOOLToBool(ListView_EnsureVisible(_window, index, BoolToBOOL(partialOK))); } @@ -128,7 +135,10 @@ class CListView: public NWindows::CWindow class CListView2: public CListView { WNDPROC _origWindowProc; + // ~CListView2() ZIP7_eq_delete; public: + virtual ~CListView2() {} + CListView2() {} void SetWindowProc(); virtual LRESULT OnMessage(UINT message, WPARAM wParam, LPARAM lParam); }; diff --git a/src/plugins/7zip/7za/cpp/Windows/Control/ProgressBar.h b/src/plugins/7zip/7za/cpp/Windows/Control/ProgressBar.h index 38ebcb61..2256aa9e 100644 --- a/src/plugins/7zip/7za/cpp/Windows/Control/ProgressBar.h +++ b/src/plugins/7zip/7za/cpp/Windows/Control/ProgressBar.h @@ -1,11 +1,11 @@ // Windows/Control/ProgressBar.h -#ifndef __WINDOWS_CONTROL_PROGRESSBAR_H -#define __WINDOWS_CONTROL_PROGRESSBAR_H +#ifndef ZIP7_INC_WINDOWS_CONTROL_PROGRESSBAR_H +#define ZIP7_INC_WINDOWS_CONTROL_PROGRESSBAR_H #include "../../Common/MyWindows.h" -#include +#include #include "../Window.h" @@ -15,18 +15,18 @@ namespace NControl { class CProgressBar: public CWindow { public: - LRESULT SetPos(int pos) { return SendMsg(PBM_SETPOS, pos, 0); } - LRESULT DeltaPos(int increment) { return SendMsg(PBM_DELTAPOS, increment, 0); } - UINT GetPos() { return (UINT)SendMsg(PBM_GETPOS, 0, 0); } - LRESULT SetRange(unsigned short minValue, unsigned short maxValue) { return SendMsg(PBM_SETRANGE, 0, MAKELPARAM(minValue, maxValue)); } - DWORD SetRange32(int minValue, int maxValue) { return (DWORD)SendMsg(PBM_SETRANGE32, minValue, maxValue); } - int SetStep(int step) { return (int)SendMsg(PBM_SETSTEP, step, 0); } - LRESULT StepIt() { return SendMsg(PBM_STEPIT, 0, 0); } - INT GetRange(bool minValue, PPBRANGE range) { return (INT)SendMsg(PBM_GETRANGE, BoolToBOOL(minValue), (LPARAM)range); } + LRESULT SetPos(int pos) { return SendMsg(PBM_SETPOS, (unsigned)pos, 0); } + // LRESULT DeltaPos(int increment) { return SendMsg(PBM_DELTAPOS, increment, 0); } + // UINT GetPos() { return (UINT)SendMsg(PBM_GETPOS, 0, 0); } + // LRESULT SetRange(unsigned short minValue, unsigned short maxValue) { return SendMsg(PBM_SETRANGE, 0, MAKELPARAM(minValue, maxValue)); } + DWORD SetRange32(int minValue, int maxValue) { return (DWORD)SendMsg(PBM_SETRANGE32, (unsigned)minValue, (LPARAM)(unsigned)maxValue); } + // int SetStep(int step) { return (int)SendMsg(PBM_SETSTEP, step, 0); } + // LRESULT StepIt() { return SendMsg(PBM_STEPIT, 0, 0); } + // INT GetRange(bool minValue, PPBRANGE range) { return (INT)SendMsg(PBM_GETRANGE, BoolToBOOL(minValue), (LPARAM)range); } #ifndef UNDER_CE - COLORREF SetBarColor(COLORREF color) { return (COLORREF)SendMsg(PBM_SETBARCOLOR, 0, color); } - COLORREF SetBackgroundColor(COLORREF color) { return (COLORREF)SendMsg(PBM_SETBKCOLOR, 0, color); } + COLORREF SetBarColor(COLORREF color) { return (COLORREF)SendMsg(PBM_SETBARCOLOR, 0, (LPARAM)color); } + COLORREF SetBackgroundColor(COLORREF color) { return (COLORREF)SendMsg(PBM_SETBKCOLOR, 0, (LPARAM)color); } #endif }; diff --git a/src/plugins/7zip/7za/cpp/Windows/Control/PropertyPage.cpp b/src/plugins/7zip/7za/cpp/Windows/Control/PropertyPage.cpp index bbac74ad..9b36cbec 100644 --- a/src/plugins/7zip/7za/cpp/Windows/Control/PropertyPage.cpp +++ b/src/plugins/7zip/7za/cpp/Windows/Control/PropertyPage.cpp @@ -16,7 +16,13 @@ extern bool g_IsNT; namespace NWindows { namespace NControl { -static INT_PTR APIENTRY MyProperyPageProcedure(HWND dialogHWND, UINT message, WPARAM wParam, LPARAM lParam) +static +#ifdef Z7_OLD_WIN_SDK + BOOL +#else + INT_PTR +#endif +APIENTRY MyProperyPageProcedure(HWND dialogHWND, UINT message, WPARAM wParam, LPARAM lParam) { CWindow tempDialog(dialogHWND); if (message == WM_INITDIALOG) @@ -34,75 +40,91 @@ bool CPropertyPage::OnNotify(UINT /* controlID */, LPNMHDR lParam) { switch (lParam->code) { - case PSN_APPLY: SetMsgResult(OnApply(LPPSHNOTIFY(lParam))); break; - case PSN_KILLACTIVE: SetMsgResult(BoolToBOOL(OnKillActive(LPPSHNOTIFY(lParam)))); break; - case PSN_SETACTIVE: SetMsgResult(OnSetActive(LPPSHNOTIFY(lParam))); break; - case PSN_RESET: OnReset(LPPSHNOTIFY(lParam)); break; - case PSN_HELP: OnNotifyHelp(LPPSHNOTIFY(lParam)); break; + case PSN_APPLY: SetMsgResult(OnApply2(LPPSHNOTIFY(lParam))); break; + case PSN_KILLACTIVE: SetMsgResult(BoolToBOOL(OnKillActive2(LPPSHNOTIFY(lParam)))); break; + case PSN_SETACTIVE: SetMsgResult(OnSetActive2(LPPSHNOTIFY(lParam))); break; + case PSN_RESET: OnReset2(LPPSHNOTIFY(lParam)); break; + case PSN_HELP: OnNotifyHelp2(LPPSHNOTIFY(lParam)); break; default: return false; } return true; } +/* +PROPSHEETPAGE fields depend from +#if (_WIN32_WINNT >= 0x0600) +#elif (_WIN32_WINNT >= 0x0501) +#elif (_WIN32_IE >= 0x0400) +PROPSHEETHEADER fields depend from +#if (_WIN32_IE >= 0x0400) +*/ +#if defined(PROPSHEETPAGEA_V1_SIZE) && !defined(Z7_OLD_WIN_SDK) +#ifndef _UNICODE +#define my_compatib_PROPSHEETPAGEA PROPSHEETPAGEA_V1 +#endif +#define my_compatib_PROPSHEETPAGEW PROPSHEETPAGEW_V1 +#else +// for old mingw: +#ifndef _UNICODE +#define my_compatib_PROPSHEETPAGEA PROPSHEETPAGEA +#endif +#define my_compatib_PROPSHEETPAGEW PROPSHEETPAGEW +#endif + INT_PTR MyPropertySheet(const CObjectVector &pagesInfo, HWND hwndParent, const UString &title) { - #ifndef _UNICODE - AStringVector titles; - #endif - #ifndef _UNICODE - CRecordVector pagesA; - #endif - CRecordVector pagesW; - unsigned i; #ifndef _UNICODE + AStringVector titles; for (i = 0; i < pagesInfo.Size(); i++) titles.Add(GetSystemString(pagesInfo[i].Title)); + CRecordVector pagesA; #endif + CRecordVector pagesW; for (i = 0; i < pagesInfo.Size(); i++) { const CPageInfo &pageInfo = pagesInfo[i]; #ifndef _UNICODE { - PROPSHEETPAGE page; + my_compatib_PROPSHEETPAGEA page; + memset(&page, 0, sizeof(page)); page.dwSize = sizeof(page); page.dwFlags = PSP_HASHELP; page.hInstance = g_hInstance; - page.pszTemplate = MAKEINTRESOURCE(pageInfo.ID); - page.pszIcon = NULL; + page.pszTemplate = MAKEINTRESOURCEA(pageInfo.ID); + // page.pszIcon = NULL; page.pfnDlgProc = NWindows::NControl::MyProperyPageProcedure; - if (titles[i].IsEmpty()) - page.pszTitle = NULL; - else + if (!titles[i].IsEmpty()) { - page.dwFlags |= PSP_USETITLE; page.pszTitle = titles[i]; + page.dwFlags |= PSP_USETITLE; } + // else page.pszTitle = NULL; page.lParam = (LPARAM)pageInfo.Page; - page.pfnCallback = NULL; + // page.pfnCallback = NULL; pagesA.Add(page); } #endif { - PROPSHEETPAGEW page; + my_compatib_PROPSHEETPAGEW page; + memset(&page, 0, sizeof(page)); page.dwSize = sizeof(page); page.dwFlags = PSP_HASHELP; page.hInstance = g_hInstance; page.pszTemplate = MAKEINTRESOURCEW(pageInfo.ID); - page.pszIcon = NULL; + // page.pszIcon = NULL; page.pfnDlgProc = NWindows::NControl::MyProperyPageProcedure; - if (pageInfo.Title.IsEmpty()) - page.pszTitle = NULL; - else + if (!pageInfo.Title.IsEmpty()) { - page.dwFlags |= PSP_USETITLE; page.pszTitle = pageInfo.Title; + page.dwFlags |= PSP_USETITLE; } + // else page.pszTitle = NULL; page.lParam = (LPARAM)pageInfo.Page; - page.pfnCallback = NULL; + // page.pfnCallback = NULL; pagesW.Add(page); } } @@ -110,16 +132,16 @@ INT_PTR MyPropertySheet(const CObjectVector &pagesInfo, HWND hwndPare #ifndef _UNICODE if (!g_IsNT) { - PROPSHEETHEADER sheet; + PROPSHEETHEADERA sheet; sheet.dwSize = sizeof(sheet); sheet.dwFlags = PSH_PROPSHEETPAGE; sheet.hwndParent = hwndParent; sheet.hInstance = g_hInstance; - AString titleA = GetSystemString(title); + AString titleA (GetSystemString(title)); sheet.pszCaption = titleA; - sheet.nPages = pagesInfo.Size(); + sheet.nPages = pagesA.Size(); sheet.nStartPage = 0; - sheet.ppsp = &pagesA.Front(); + sheet.ppsp = (LPCPROPSHEETPAGEA)(const void *)pagesA.ConstData(); sheet.pfnCallback = NULL; return ::PropertySheetA(&sheet); } @@ -132,9 +154,9 @@ INT_PTR MyPropertySheet(const CObjectVector &pagesInfo, HWND hwndPare sheet.hwndParent = hwndParent; sheet.hInstance = g_hInstance; sheet.pszCaption = title; - sheet.nPages = pagesInfo.Size(); + sheet.nPages = pagesW.Size(); sheet.nStartPage = 0; - sheet.ppsp = &pagesW.Front(); + sheet.ppsp = (LPCPROPSHEETPAGEW)(const void *)pagesW.ConstData(); sheet.pfnCallback = NULL; return ::PropertySheetW(&sheet); } diff --git a/src/plugins/7zip/7za/cpp/Windows/Control/PropertyPage.h b/src/plugins/7zip/7za/cpp/Windows/Control/PropertyPage.h index 4c4ddad9..264a5d29 100644 --- a/src/plugins/7zip/7za/cpp/Windows/Control/PropertyPage.h +++ b/src/plugins/7zip/7za/cpp/Windows/Control/PropertyPage.h @@ -1,7 +1,7 @@ // Windows/Control/PropertyPage.h -#ifndef __WINDOWS_CONTROL_PROPERTYPAGE_H -#define __WINDOWS_CONTROL_PROPERTYPAGE_H +#ifndef ZIP7_INC_WINDOWS_CONTROL_PROPERTYPAGE_H +#define ZIP7_INC_WINDOWS_CONTROL_PROPERTYPAGE_H #include "../../Common/MyWindows.h" @@ -17,23 +17,23 @@ INT_PTR APIENTRY ProperyPageProcedure(HWND dialogHWND, UINT message, WPARAM wPar class CPropertyPage: public CDialog { public: - CPropertyPage(HWND window = NULL): CDialog(window){}; + CPropertyPage(HWND window = NULL): CDialog(window) {} void Changed() { PropSheet_Changed(GetParent(), (HWND)*this); } void UnChanged() { PropSheet_UnChanged(GetParent(), (HWND)*this); } - virtual bool OnNotify(UINT controlID, LPNMHDR lParam); + virtual bool OnNotify(UINT controlID, LPNMHDR lParam) Z7_override; virtual bool OnKillActive() { return false; } // false = OK - virtual bool OnKillActive(const PSHNOTIFY *) { return OnKillActive(); } + virtual bool OnKillActive2(const PSHNOTIFY *) { return OnKillActive(); } virtual LONG OnSetActive() { return false; } // false = OK - virtual LONG OnSetActive(const PSHNOTIFY *) { return OnSetActive(); } + virtual LONG OnSetActive2(const PSHNOTIFY *) { return OnSetActive(); } virtual LONG OnApply() { return PSNRET_NOERROR; } - virtual LONG OnApply(const PSHNOTIFY *) { return OnApply(); } + virtual LONG OnApply2(const PSHNOTIFY *) { return OnApply(); } virtual void OnNotifyHelp() {} - virtual void OnNotifyHelp(const PSHNOTIFY *) { OnNotifyHelp(); } + virtual void OnNotifyHelp2(const PSHNOTIFY *) { OnNotifyHelp(); } virtual void OnReset() {} - virtual void OnReset(const PSHNOTIFY *) { OnReset(); } + virtual void OnReset2(const PSHNOTIFY *) { OnReset(); } }; struct CPageInfo diff --git a/src/plugins/7zip/7za/cpp/Windows/Control/ReBar.h b/src/plugins/7zip/7za/cpp/Windows/Control/ReBar.h index c2d58dbe..b56f018c 100644 --- a/src/plugins/7zip/7za/cpp/Windows/Control/ReBar.h +++ b/src/plugins/7zip/7za/cpp/Windows/Control/ReBar.h @@ -1,7 +1,7 @@ // Windows/Control/ReBar.h -#ifndef __WINDOWS_CONTROL_REBAR_H -#define __WINDOWS_CONTROL_REBAR_H +#ifndef ZIP7_INC_WINDOWS_CONTROL_REBAR_H +#define ZIP7_INC_WINDOWS_CONTROL_REBAR_H #include "../Window.h" @@ -14,7 +14,7 @@ class CReBar: public NWindows::CWindow bool SetBarInfo(LPREBARINFO barInfo) { return LRESULTToBool(SendMsg(RB_SETBARINFO, 0, (LPARAM)barInfo)); } bool InsertBand(int index, LPREBARBANDINFO bandInfo) - { return LRESULTToBool(SendMsg(RB_INSERTBAND, index, (LPARAM)bandInfo)); } + { return LRESULTToBool(SendMsg(RB_INSERTBAND, MY_int_TO_WPARAM(index), (LPARAM)bandInfo)); } bool SetBandInfo(unsigned index, LPREBARBANDINFO bandInfo) { return LRESULTToBool(SendMsg(RB_SETBANDINFO, index, (LPARAM)bandInfo)); } void MaximizeBand(unsigned index, bool ideal) diff --git a/src/plugins/7zip/7za/cpp/Windows/Control/Static.h b/src/plugins/7zip/7za/cpp/Windows/Control/Static.h index 5523b2e6..ceeedf96 100644 --- a/src/plugins/7zip/7za/cpp/Windows/Control/Static.h +++ b/src/plugins/7zip/7za/cpp/Windows/Control/Static.h @@ -1,7 +1,7 @@ // Windows/Control/Static.h -#ifndef __WINDOWS_CONTROL_STATIC_H -#define __WINDOWS_CONTROL_STATIC_H +#ifndef ZIP7_INC_WINDOWS_CONTROL_STATIC_H +#define ZIP7_INC_WINDOWS_CONTROL_STATIC_H #include "../Window.h" diff --git a/src/plugins/7zip/7za/cpp/Windows/Control/StatusBar.h b/src/plugins/7zip/7za/cpp/Windows/Control/StatusBar.h index 988b8470..38aca478 100644 --- a/src/plugins/7zip/7za/cpp/Windows/Control/StatusBar.h +++ b/src/plugins/7zip/7za/cpp/Windows/Control/StatusBar.h @@ -1,7 +1,7 @@ // Windows/Control/StatusBar.h -#ifndef __WINDOWS_CONTROL_STATUSBAR_H -#define __WINDOWS_CONTROL_STATUSBAR_H +#ifndef ZIP7_INC_WINDOWS_CONTROL_STATUSBAR_H +#define ZIP7_INC_WINDOWS_CONTROL_STATUSBAR_H #include "../Window.h" @@ -12,7 +12,7 @@ class CStatusBar: public NWindows::CWindow { public: bool Create(LONG style, LPCTSTR text, HWND hwndParent, UINT id) - { return (_window = ::CreateStatusWindow(style, text, hwndParent, id)) != 0; } + { return (_window = ::CreateStatusWindow(style, text, hwndParent, id)) != NULL; } bool SetText(LPCTSTR text) { return CWindow::SetText(text); } bool SetText(unsigned index, LPCTSTR text, UINT type) @@ -22,7 +22,7 @@ class CStatusBar: public NWindows::CWindow #ifndef _UNICODE bool Create(LONG style, LPCWSTR text, HWND hwndParent, UINT id) - { return (_window = ::CreateStatusWindowW(style, text, hwndParent, id)) != 0; } + { return (_window = ::CreateStatusWindowW(style, text, hwndParent, id)) != NULL; } bool SetText(LPCWSTR text) { return CWindow::SetText(text); } bool SetText(unsigned index, LPCWSTR text, UINT type) @@ -34,7 +34,7 @@ class CStatusBar: public NWindows::CWindow bool SetParts(unsigned numParts, const int *edgePostions) { return LRESULTToBool(SendMsg(SB_SETPARTS, numParts, (LPARAM)edgePostions)); } void Simple(bool simple) - { SendMsg(SB_SIMPLE, BoolToBOOL(simple), 0); } + { SendMsg(SB_SIMPLE, (WPARAM)BoolToBOOL(simple), 0); } }; }} diff --git a/src/plugins/7zip/7za/cpp/Windows/Control/StdAfx.h b/src/plugins/7zip/7za/cpp/Windows/Control/StdAfx.h index 1cbd7fea..80866550 100644 --- a/src/plugins/7zip/7za/cpp/Windows/Control/StdAfx.h +++ b/src/plugins/7zip/7za/cpp/Windows/Control/StdAfx.h @@ -1,8 +1,11 @@ // StdAfx.h -#ifndef __STDAFX_H -#define __STDAFX_H +#ifndef ZIP7_INC_STDAFX_H +#define ZIP7_INC_STDAFX_H +#if defined(_MSC_VER) && _MSC_VER >= 1800 +#pragma warning(disable : 4464) // relative include path contains '..' +#endif #include "../../Common/Common.h" #endif diff --git a/src/plugins/7zip/7za/cpp/Windows/Control/ToolBar.h b/src/plugins/7zip/7za/cpp/Windows/Control/ToolBar.h index 7bc93a24..2bf20a59 100644 --- a/src/plugins/7zip/7za/cpp/Windows/Control/ToolBar.h +++ b/src/plugins/7zip/7za/cpp/Windows/Control/ToolBar.h @@ -1,7 +1,7 @@ // Windows/Control/ToolBar.h -#ifndef __WINDOWS_CONTROL_TOOLBAR_H -#define __WINDOWS_CONTROL_TOOLBAR_H +#ifndef ZIP7_INC_WINDOWS_CONTROL_TOOLBAR_H +#define ZIP7_INC_WINDOWS_CONTROL_TOOLBAR_H #include "../Window.h" @@ -18,7 +18,7 @@ class CToolBar: public NWindows::CWindow #ifdef UNDER_CE { // maybe it must be fixed for more than 1 buttons - DWORD val = GetButtonSize(); + const DWORD val = GetButtonSize(); size->cx = LOWORD(val); size->cy = HIWORD(val); return true; diff --git a/src/plugins/7zip/7za/cpp/Windows/Control/Trackbar.h b/src/plugins/7zip/7za/cpp/Windows/Control/Trackbar.h index 313e0c85..18d1b291 100644 --- a/src/plugins/7zip/7za/cpp/Windows/Control/Trackbar.h +++ b/src/plugins/7zip/7za/cpp/Windows/Control/Trackbar.h @@ -1,7 +1,7 @@ // Windows/Control/Trackbar.h -#ifndef __WINDOWS_CONTROL_TRACKBAR_H -#define __WINDOWS_CONTROL_TRACKBAR_H +#ifndef ZIP7_INC_WINDOWS_CONTROL_TRACKBAR_H +#define ZIP7_INC_WINDOWS_CONTROL_TRACKBAR_H #include "../Window.h" diff --git a/src/plugins/7zip/7za/cpp/Windows/Control/Window2.cpp b/src/plugins/7zip/7za/cpp/Windows/Control/Window2.cpp index 994d96e0..8fe908e0 100644 --- a/src/plugins/7zip/7za/cpp/Windows/Control/Window2.cpp +++ b/src/plugins/7zip/7za/cpp/Windows/Control/Window2.cpp @@ -32,9 +32,9 @@ static LRESULT CALLBACK WindowProcedure(HWND aHWND, UINT message, WPARAM wParam, if (message == MY_START_WM_CREATE) tempWindow.SetUserDataLongPtr((LONG_PTR)(((LPCREATESTRUCT)lParam)->lpCreateParams)); CWindow2 *window = (CWindow2 *)(tempWindow.GetUserDataLongPtr()); - if (window != NULL && message == MY_START_WM_CREATE) + if (window && message == MY_START_WM_CREATE) window->Attach(aHWND); - if (window == 0) + if (!window) { #ifndef _UNICODE if (g_IsNT) @@ -140,7 +140,7 @@ LRESULT CWindow2::OnMessage(UINT message, WPARAM wParam, LPARAM lParam) return -1; break; case WM_COMMAND: - if (OnCommand(wParam, lParam, result)) + if (OnCommand(HIWORD(wParam), LOWORD(wParam), lParam, result)) return result; break; case WM_NOTIFY: @@ -160,12 +160,14 @@ LRESULT CWindow2::OnMessage(UINT message, WPARAM wParam, LPARAM lParam) return DefProc(message, wParam, lParam); } -bool CWindow2::OnCommand(WPARAM wParam, LPARAM lParam, LRESULT &result) +/* +bool CWindow2::OnCommand2(WPARAM wParam, LPARAM lParam, LRESULT &result) { return OnCommand(HIWORD(wParam), LOWORD(wParam), lParam, result); } +*/ -bool CWindow2::OnCommand(int /* code */, int /* itemID */, LPARAM /* lParam */, LRESULT & /* result */) +bool CWindow2::OnCommand(unsigned /* code */, unsigned /* itemID */, LPARAM /* lParam */, LRESULT & /* result */) { return false; // return DefProc(message, wParam, lParam); @@ -176,7 +178,7 @@ bool CWindow2::OnCommand(int /* code */, int /* itemID */, LPARAM /* lParam */, } /* -bool CDialog::OnButtonClicked(int buttonID, HWND buttonHWND) +bool CDialog::OnButtonClicked(unsigned buttonID, HWND buttonHWND) { switch (buttonID) { diff --git a/src/plugins/7zip/7za/cpp/Windows/Control/Window2.h b/src/plugins/7zip/7za/cpp/Windows/Control/Window2.h index 7ac580cb..ebb59793 100644 --- a/src/plugins/7zip/7za/cpp/Windows/Control/Window2.h +++ b/src/plugins/7zip/7za/cpp/Windows/Control/Window2.h @@ -1,7 +1,7 @@ // Windows/Control/Window2.h -#ifndef __WINDOWS_CONTROL_WINDOW2_H -#define __WINDOWS_CONTROL_WINDOW2_H +#ifndef ZIP7_INC_WINDOWS_CONTROL_WINDOW2_H +#define ZIP7_INC_WINDOWS_CONTROL_WINDOW2_H #include "../Window.h" @@ -10,10 +10,12 @@ namespace NControl { class CWindow2: public CWindow { + // Z7_CLASS_NO_COPY(CWindow2) + LRESULT DefProc(UINT message, WPARAM wParam, LPARAM lParam); public: - CWindow2(HWND newWindow = NULL): CWindow(newWindow){}; - virtual ~CWindow2() {}; + CWindow2(HWND newWindow = NULL): CWindow(newWindow) {} + virtual ~CWindow2() {} bool CreateEx(DWORD exStyle, LPCTSTR className, LPCTSTR windowName, DWORD style, int x, int y, int width, int height, @@ -28,8 +30,8 @@ class CWindow2: public CWindow virtual LRESULT OnMessage(UINT message, WPARAM wParam, LPARAM lParam); virtual bool OnCreate(CREATESTRUCT * /* createStruct */) { return true; } // virtual LRESULT OnCommand(WPARAM wParam, LPARAM lParam); - virtual bool OnCommand(WPARAM wParam, LPARAM lParam, LRESULT &result); - virtual bool OnCommand(int code, int itemID, LPARAM lParam, LRESULT &result); + // bool OnCommand2(WPARAM wParam, LPARAM lParam, LRESULT &result); + virtual bool OnCommand(unsigned code, unsigned itemID, LPARAM lParam, LRESULT &result); virtual bool OnSize(WPARAM /* wParam */, int /* xSize */, int /* ySize */) { return false; } virtual bool OnNotify(UINT /* controlID */, LPNMHDR /* lParam */, LRESULT & /* result */) { return false; } virtual void OnDestroy() { PostQuitMessage(0); } @@ -37,7 +39,7 @@ class CWindow2: public CWindow /* virtual LRESULT OnHelp(LPHELPINFO helpInfo) { OnHelp(); } virtual LRESULT OnHelp() {}; - virtual bool OnButtonClicked(int buttonID, HWND buttonHWND); + virtual bool OnButtonClicked(unsigned buttonID, HWND buttonHWND); virtual void OnOK() {}; virtual void OnCancel() {}; */ diff --git a/src/plugins/7zip/7za/cpp/Windows/DLL.cpp b/src/plugins/7zip/7za/cpp/Windows/DLL.cpp index a71a8017..025df17b 100644 --- a/src/plugins/7zip/7za/cpp/Windows/DLL.cpp +++ b/src/plugins/7zip/7za/cpp/Windows/DLL.cpp @@ -4,6 +4,8 @@ #include "DLL.h" +#ifdef _WIN32 + #ifndef _UNICODE extern bool g_IsNT; #endif @@ -15,11 +17,11 @@ namespace NDLL { bool CLibrary::Free() throw() { - if (_module == 0) + if (_module == NULL) return true; if (!::FreeLibrary(_module)) return false; - _module = 0; + _module = NULL; return true; } @@ -59,14 +61,14 @@ bool CLibrary::Load(CFSTR path) throw() bool MyGetModuleFileName(FString &path) { - HMODULE hModule = g_hInstance; + const HMODULE hModule = g_hInstance; path.Empty(); #ifndef _UNICODE if (!g_IsNT) { TCHAR s[MAX_PATH + 2]; s[0] = 0; - DWORD size = ::GetModuleFileName(hModule, s, MAX_PATH + 1); + const DWORD size = ::GetModuleFileName(hModule, s, MAX_PATH + 1); if (size <= MAX_PATH && size != 0) { path = fas2fs(s); @@ -78,7 +80,7 @@ bool MyGetModuleFileName(FString &path) { WCHAR s[MAX_PATH + 2]; s[0] = 0; - DWORD size = ::GetModuleFileNameW(hModule, s, MAX_PATH + 1); + const DWORD size = ::GetModuleFileNameW(hModule, s, MAX_PATH + 1); if (size <= MAX_PATH && size != 0) { path = us2fs(s); @@ -88,23 +90,89 @@ bool MyGetModuleFileName(FString &path) return false; } -#ifndef _SFX +#ifndef Z7_SFX FString GetModuleDirPrefix() { FString s; if (MyGetModuleFileName(s)) { - int pos = s.ReverseFind_PathSepar(); + const int pos = s.ReverseFind_PathSepar(); if (pos >= 0) - { - s.DeleteFrom(pos + 1); - return s; - } + s.DeleteFrom((unsigned)(pos + 1)); } - return FTEXT(".") FSTRING_PATH_SEPARATOR; + if (s.IsEmpty()) + s = "." STRING_PATH_SEPARATOR; + return s; } #endif }} + +#else // _WIN32 + +#include +#include + +// FARPROC +void *GetProcAddress(HMODULE module, LPCSTR procName) +{ + void *ptr = NULL; + if (module) + ptr = dlsym(module, procName); + return ptr; +} + +namespace NWindows { +namespace NDLL { + +bool CLibrary::Free() throw() +{ + if (!_module) + return true; + const int ret = dlclose(_module); + if (ret != 0) + return false; + _module = NULL; + return true; +} + +bool CLibrary::Load(CFSTR path) throw() +{ + if (!Free()) + return false; + + int options = 0; + + #ifdef RTLD_LOCAL + options |= RTLD_LOCAL; + #endif + + #ifdef RTLD_NOW + options |= RTLD_NOW; + #endif + + #ifdef RTLD_GROUP + #if ! (defined(hpux) || defined(__hpux)) + options |= RTLD_GROUP; // mainly for solaris but not for HPUX + #endif + #endif + + _module = dlopen(path, options); + return (_module != NULL); +} + +/* +// FARPROC +void * CLibrary::GetProc(LPCSTR procName) const +{ + // return My_GetProcAddress(_module, procName); + return local_GetProcAddress(_module, procName); + // return NULL; +} +*/ + +}} + +#endif diff --git a/src/plugins/7zip/7za/cpp/Windows/DLL.h b/src/plugins/7zip/7za/cpp/Windows/DLL.h index 984a1d33..19a82b33 100644 --- a/src/plugins/7zip/7za/cpp/Windows/DLL.h +++ b/src/plugins/7zip/7za/cpp/Windows/DLL.h @@ -1,18 +1,29 @@ // Windows/DLL.h -#ifndef __WINDOWS_DLL_H -#define __WINDOWS_DLL_H +#ifndef ZIP7_INC_WINDOWS_DLL_H +#define ZIP7_INC_WINDOWS_DLL_H #include "../Common/MyString.h" +#ifndef _WIN32 +typedef void * HMODULE; +// typedef int (*FARPROC)(); +// typedef void *FARPROC; +void *GetProcAddress(HMODULE module, LPCSTR procName); +#endif + namespace NWindows { namespace NDLL { +#ifdef _WIN32 + +/* #ifdef UNDER_CE -#define My_GetProcAddress(module, procName) ::GetProcAddressA(module, procName) +#define My_GetProcAddress(module, procName) (void *)::GetProcAddressA(module, procName) #else -#define My_GetProcAddress(module, procName) ::GetProcAddress(module, procName) +#define My_GetProcAddress(module, procName) (void *)::GetProcAddress(module, procName) #endif +*/ /* Win32: Don't call CLibrary::Free() and FreeLibrary() from another FreeLibrary() code: detaching code in DLL entry-point or in @@ -22,13 +33,25 @@ class CLibrary { HMODULE _module; - // CLASS_NO_COPY(CLibrary); + // Z7_CLASS_NO_COPY(CLibrary); + // copy constructor is required here public: - CLibrary(): _module(NULL) {}; + CLibrary(): _module(NULL) {} ~CLibrary() { Free(); } - operator HMODULE() const { return _module; } - HMODULE* operator&() { return &_module; } + CLibrary(const CLibrary &c): _module(NULL) + { + if (c._module) + { + // we need non const to reference from original item + // c._module = NULL; + throw 20230102; + } + } + + HMODULE Get_HMODULE() const { return _module; } + // operator HMODULE() const { return _module; } + // HMODULE* operator&() { return &_module; } bool IsLoaded() const { return (_module != NULL); } void Attach(HMODULE m) @@ -38,7 +61,7 @@ class CLibrary } HMODULE Detach() { - HMODULE m = _module; + const HMODULE m = _module; _module = NULL; return m; } @@ -46,9 +69,31 @@ class CLibrary bool Free() throw(); bool LoadEx(CFSTR path, DWORD flags = LOAD_LIBRARY_AS_DATAFILE) throw(); bool Load(CFSTR path) throw(); - FARPROC GetProc(LPCSTR procName) const { return My_GetProcAddress(_module, procName); } + // FARPROC + // void *GetProc(LPCSTR procName) const { return My_GetProcAddress(_module, procName); } +}; + +#else + +class CLibrary +{ + HMODULE _module; + + // Z7_CLASS_NO_COPY(CLibrary); +public: + CLibrary(): _module(NULL) {} + ~CLibrary() { Free(); } + + HMODULE Get_HMODULE() const { return _module; } + + bool Free() throw(); + bool Load(CFSTR path) throw(); + // FARPROC + // void *GetProc(LPCSTR procName) const; // { return My_GetProcAddress(_module, procName); } }; +#endif + bool MyGetModuleFileName(FString &path); FString GetModuleDirPrefix(); diff --git a/src/plugins/7zip/7za/cpp/Windows/Defs.h b/src/plugins/7zip/7za/cpp/Windows/Defs.h index 281c40c3..0d3ded9a 100644 --- a/src/plugins/7zip/7za/cpp/Windows/Defs.h +++ b/src/plugins/7zip/7za/cpp/Windows/Defs.h @@ -3,15 +3,11 @@ #ifndef __WINDOWS_DEFS_H #define __WINDOWS_DEFS_H -#include "../Common/MyWindows.h" +#include "WinDefs.h" +// Keep the host-only result helper without duplicating conversions now provided by 26.02 WinDefs.h. #ifdef _WIN32 inline bool LRESULTToBool(LRESULT v) { return (v != FALSE); } -inline bool BOOLToBool(BOOL v) { return (v != FALSE); } -inline BOOL BoolToBOOL(bool v) { return (v ? TRUE: FALSE); } #endif -inline VARIANT_BOOL BoolToVARIANT_BOOL(bool v) { return (v ? VARIANT_TRUE: VARIANT_FALSE); } -inline bool VARIANT_BOOLToBool(VARIANT_BOOL v) { return (v != VARIANT_FALSE); } - #endif diff --git a/src/plugins/7zip/7za/cpp/Windows/ErrorMsg.cpp b/src/plugins/7zip/7za/cpp/Windows/ErrorMsg.cpp index c9de4a6c..5acf3adb 100644 --- a/src/plugins/7zip/7za/cpp/Windows/ErrorMsg.cpp +++ b/src/plugins/7zip/7za/cpp/Windows/ErrorMsg.cpp @@ -2,21 +2,33 @@ #include "StdAfx.h" -#ifndef _UNICODE +#if !defined(_UNICODE) || !defined(_WIN32) #include "../Common/StringConvert.h" #endif #include "ErrorMsg.h" -#ifndef _UNICODE +#ifdef _WIN32 +#if !defined(_UNICODE) extern bool g_IsNT; #endif +#endif namespace NWindows { namespace NError { static bool MyFormatMessage(DWORD errorCode, UString &message) { + #ifndef Z7_SFX + if ((HRESULT)errorCode == MY_HRES_ERROR_INTERNAL_ERROR) + { + message = "Internal Error: The failure in hardware (RAM or CPU), OS or program"; + return true; + } + #endif + + #ifdef _WIN32 + LPVOID msgBuf; #ifndef _UNICODE if (!g_IsNT) @@ -38,8 +50,63 @@ static bool MyFormatMessage(DWORD errorCode, UString &message) } ::LocalFree(msgBuf); return true; + + #else // _WIN32 + + AString m; + + const char *s = NULL; + + switch ((Int32)errorCode) + { + // case ERROR_NO_MORE_FILES : s = "No more files"; break; + // case ERROR_DIRECTORY : s = "Error Directory"; break; + case E_NOTIMPL : s = "E_NOTIMPL : Not implemented"; break; + case E_NOINTERFACE : s = "E_NOINTERFACE : No such interface supported"; break; + case E_ABORT : s = "E_ABORT : Operation aborted"; break; + case E_FAIL : s = "E_FAIL : Unspecified error"; break; + + case STG_E_INVALIDFUNCTION : s = "STG_E_INVALIDFUNCTION"; break; + case CLASS_E_CLASSNOTAVAILABLE : s = "CLASS_E_CLASSNOTAVAILABLE"; break; + + case E_OUTOFMEMORY : s = "E_OUTOFMEMORY : Can't allocate required memory"; break; + case E_INVALIDARG : s = "E_INVALIDARG : One or more arguments are invalid"; break; + + // case MY_E_ERROR_NEGATIVE_SEEK : s = "MY_E_ERROR_NEGATIVE_SEEK"; break; + default: + break; + } + + /* strerror() for unknown errors still shows message "Unknown error -12345678") + So we must transfer error codes before strerror() */ + if (!s) + { + if ((errorCode & 0xFFFF0000) == (UInt32)((MY_FACILITY_WRes << 16) | 0x80000000)) + errorCode &= 0xFFFF; + else if ((errorCode & ((UInt32)1 << 31))) + return false; // we will show hex error later for that case + + s = strerror((int)errorCode); + + // if (!s) + { + m += "errno="; + m.Add_UInt32(errorCode); + if (s) + m += " : "; + } + } + + if (s) + m += s; + + MultiByteToUnicodeString2(message, m); + return true; + + #endif } + UString MyFormatMessage(DWORD errorCode) { UString m; @@ -53,8 +120,8 @@ UString MyFormatMessage(DWORD errorCode) s[7 - i] = (char)((t < 10) ? ('0' + t) : ('A' + (t - 10))); } s[8] = 0; - m.AddAscii("Error #"); - m.AddAscii(s); + m += "Error #"; + m += s; } else if (m.Len() >= 2 && m[m.Len() - 1] == 0x0A diff --git a/src/plugins/7zip/7za/cpp/Windows/ErrorMsg.h b/src/plugins/7zip/7za/cpp/Windows/ErrorMsg.h index 0957c696..6142b4eb 100644 --- a/src/plugins/7zip/7za/cpp/Windows/ErrorMsg.h +++ b/src/plugins/7zip/7za/cpp/Windows/ErrorMsg.h @@ -1,7 +1,7 @@ // Windows/ErrorMsg.h -#ifndef __WINDOWS_ERROR_MSG_H -#define __WINDOWS_ERROR_MSG_H +#ifndef ZIP7_INC_WINDOWS_ERROR_MSG_H +#define ZIP7_INC_WINDOWS_ERROR_MSG_H #include "../Common/MyString.h" @@ -9,6 +9,7 @@ namespace NWindows { namespace NError { UString MyFormatMessage(DWORD errorCode); +inline UString MyFormatMessage(HRESULT errorCode) { return MyFormatMessage((DWORD)errorCode); } }} diff --git a/src/plugins/7zip/7za/cpp/Windows/FileDir.cpp b/src/plugins/7zip/7za/cpp/Windows/FileDir.cpp index da71b710..ad0d8c9d 100644 --- a/src/plugins/7zip/7za/cpp/Windows/FileDir.cpp +++ b/src/plugins/7zip/7za/cpp/Windows/FileDir.cpp @@ -2,7 +2,21 @@ #include "StdAfx.h" -#ifndef _UNICODE + +#ifndef _WIN32 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../Common/C_FileIO.h" +#include "../Common/MyBuffer2.h" #include "../Common/StringConvert.h" #endif @@ -18,58 +32,99 @@ using namespace NWindows; using namespace NFile; using namespace NName; +#ifndef _WIN32 + +static bool FiTime_To_timespec(const CFiTime *ft, timespec &ts) +{ + if (ft) + { +#if defined(_AIX) + ts.tv_sec = ft->tv_sec; + ts.tv_nsec = ft->tv_nsec; +#else + ts = *ft; +#endif + return true; + } + // else + { + ts.tv_sec = 0; + ts.tv_nsec = + #ifdef UTIME_OMIT + UTIME_OMIT; // -2 keep old timesptamp + #else + // UTIME_NOW; -1 // set to the current time + 0; + #endif + return false; + } +} +#endif + namespace NWindows { namespace NFile { namespace NDir { +#ifdef _WIN32 + #ifndef UNDER_CE bool GetWindowsDir(FString &path) { - UINT needLength; + const unsigned kBufSize = MAX_PATH + 16; + UINT len; #ifndef _UNICODE if (!g_IsNT) { - TCHAR s[MAX_PATH + 2]; + TCHAR s[kBufSize + 1]; s[0] = 0; - needLength = ::GetWindowsDirectory(s, MAX_PATH + 1); + len = ::GetWindowsDirectory(s, kBufSize); path = fas2fs(s); } else #endif { - WCHAR s[MAX_PATH + 2]; + WCHAR s[kBufSize + 1]; s[0] = 0; - needLength = ::GetWindowsDirectoryW(s, MAX_PATH + 1); + len = ::GetWindowsDirectoryW(s, kBufSize); path = us2fs(s); } - return (needLength > 0 && needLength <= MAX_PATH); + return (len != 0 && len < kBufSize); } + +/* +new DOCs for GetSystemDirectory: + returned path does not end with a backslash unless the + system directory is the root directory. +*/ + bool GetSystemDir(FString &path) { - UINT needLength; + const unsigned kBufSize = MAX_PATH + 16; + UINT len; #ifndef _UNICODE if (!g_IsNT) { - TCHAR s[MAX_PATH + 2]; + TCHAR s[kBufSize + 1]; s[0] = 0; - needLength = ::GetSystemDirectory(s, MAX_PATH + 1); + len = ::GetSystemDirectory(s, kBufSize); path = fas2fs(s); } else #endif { - WCHAR s[MAX_PATH + 2]; + WCHAR s[kBufSize + 1]; s[0] = 0; - needLength = ::GetSystemDirectoryW(s, MAX_PATH + 1); + len = ::GetSystemDirectoryW(s, kBufSize); path = us2fs(s); } - return (needLength > 0 && needLength <= MAX_PATH); + return (len != 0 && len < kBufSize); } -#endif +#endif // UNDER_CE -bool SetDirTime(CFSTR path, const FILETIME *cTime, const FILETIME *aTime, const FILETIME *mTime) + +static bool SetFileTime_Base(CFSTR path, const CFiTime *cTime, const CFiTime *aTime, const CFiTime *mTime, DWORD dwFlagsAndAttributes) { #ifndef _UNICODE if (!g_IsNT) @@ -82,14 +137,14 @@ bool SetDirTime(CFSTR path, const FILETIME *cTime, const FILETIME *aTime, const HANDLE hDir = INVALID_HANDLE_VALUE; IF_USE_MAIN_PATH hDir = ::CreateFileW(fs2us(path), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, - NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); - #ifdef WIN_LONG_PATH + NULL, OPEN_EXISTING, dwFlagsAndAttributes, NULL); + #ifdef Z7_LONG_PATH if (hDir == INVALID_HANDLE_VALUE && USE_SUPER_PATH) { UString superPath; if (GetSuperPath(path, superPath, USE_MAIN_PATH)) hDir = ::CreateFileW(superPath, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, - NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + NULL, OPEN_EXISTING, dwFlagsAndAttributes, NULL); } #endif @@ -102,6 +157,17 @@ bool SetDirTime(CFSTR path, const FILETIME *cTime, const FILETIME *aTime, const return res; } +bool SetDirTime(CFSTR path, const CFiTime *cTime, const CFiTime *aTime, const CFiTime *mTime) +{ + return SetFileTime_Base(path, cTime, aTime, mTime, FILE_FLAG_BACKUP_SEMANTICS); +} + +bool SetLinkFileTime(CFSTR path, const CFiTime *cTime, const CFiTime *aTime, const CFiTime *mTime) +{ + return SetFileTime_Base(path, cTime, aTime, mTime, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT); +} + + bool SetFileAttrib(CFSTR path, DWORD attrib) { #ifndef _UNICODE @@ -116,7 +182,7 @@ bool SetFileAttrib(CFSTR path, DWORD attrib) IF_USE_MAIN_PATH if (::SetFileAttributesW(fs2us(path), attrib)) return true; - #ifdef WIN_LONG_PATH + #ifdef Z7_LONG_PATH if (USE_SUPER_PATH) { UString superPath; @@ -128,6 +194,17 @@ bool SetFileAttrib(CFSTR path, DWORD attrib) return false; } + +bool SetFileAttrib_PosixHighDetect(CFSTR path, DWORD attrib) +{ + #ifdef _WIN32 + if ((attrib & 0xF0000000) != 0) + attrib &= 0x3FFF; + #endif + return SetFileAttrib(path, attrib); +} + + bool RemoveDir(CFSTR path) { #ifndef _UNICODE @@ -142,7 +219,7 @@ bool RemoveDir(CFSTR path) IF_USE_MAIN_PATH if (::RemoveDirectoryW(fs2us(path))) return true; - #ifdef WIN_LONG_PATH + #ifdef Z7_LONG_PATH if (USE_SUPER_PATH) { UString superPath; @@ -154,6 +231,9 @@ bool RemoveDir(CFSTR path) return false; } + +// When moving a directory, oldFile and newFile must be on the same drive. + bool MyMoveFile(CFSTR oldFile, CFSTR newFile) { #ifndef _UNICODE @@ -166,9 +246,11 @@ bool MyMoveFile(CFSTR oldFile, CFSTR newFile) #endif { IF_USE_MAIN_PATH_2(oldFile, newFile) + { if (::MoveFileW(fs2us(oldFile), fs2us(newFile))) return true; - #ifdef WIN_LONG_PATH + } + #ifdef Z7_LONG_PATH if (USE_SUPER_PATH_2) { UString d1, d2; @@ -180,8 +262,65 @@ bool MyMoveFile(CFSTR oldFile, CFSTR newFile) return false; } +#if defined(Z7_WIN32_WINNT_MIN) && Z7_WIN32_WINNT_MIN >= 0x0500 +static DWORD WINAPI CopyProgressRoutine_to_ICopyFileProgress( + LARGE_INTEGER TotalFileSize, // file size + LARGE_INTEGER TotalBytesTransferred, // bytes transferred + LARGE_INTEGER /* StreamSize */, // bytes in stream + LARGE_INTEGER /* StreamBytesTransferred */, // bytes transferred for stream + DWORD /* dwStreamNumber */, // current stream + DWORD /* dwCallbackReason */, // callback reason + HANDLE /* hSourceFile */, // handle to source file + HANDLE /* hDestinationFile */, // handle to destination file + LPVOID lpData // from CopyFileEx +) +{ + return ((ICopyFileProgress *)lpData)->CopyFileProgress( + (UInt64)TotalFileSize.QuadPart, + (UInt64)TotalBytesTransferred.QuadPart); +} +#endif + +bool MyMoveFile_with_Progress(CFSTR oldFile, CFSTR newFile, + ICopyFileProgress *progress) +{ +#if defined(Z7_WIN32_WINNT_MIN) && Z7_WIN32_WINNT_MIN >= 0x0500 +#ifndef _UNICODE + if (g_IsNT) +#endif + if (progress) + { + IF_USE_MAIN_PATH_2(oldFile, newFile) + { + if (::MoveFileWithProgressW(fs2us(oldFile), fs2us(newFile), + CopyProgressRoutine_to_ICopyFileProgress, progress, MOVEFILE_COPY_ALLOWED)) + return true; + if (::GetLastError() == ERROR_REQUEST_ABORTED) + return false; + } + #ifdef Z7_LONG_PATH + if (USE_SUPER_PATH_2) + { + UString d1, d2; + if (GetSuperPaths(oldFile, newFile, d1, d2, USE_MAIN_PATH_2)) + return BOOLToBool(::MoveFileWithProgressW(d1, d2, + CopyProgressRoutine_to_ICopyFileProgress, progress, MOVEFILE_COPY_ALLOWED)); + } + #endif + return false; + } +#else + UNUSED_VAR(progress) +#endif + return MyMoveFile(oldFile, newFile); +} + #ifndef UNDER_CE +#if !defined(Z7_WIN32_WINNT_MIN) || Z7_WIN32_WINNT_MIN < 0x0500 // Win2000 +#define Z7_USE_DYN_CreateHardLink +#endif +#ifdef Z7_USE_DYN_CreateHardLink EXTERN_C_BEGIN typedef BOOL (WINAPI *Func_CreateHardLinkW)( LPCWSTR lpFileName, @@ -189,6 +328,8 @@ typedef BOOL (WINAPI *Func_CreateHardLinkW)( LPSECURITY_ATTRIBUTES lpSecurityAttributes ); EXTERN_C_END +#endif +#endif // UNDER_CE bool MyCreateHardLink(CFSTR newFileName, CFSTR existFileName) { @@ -205,26 +346,35 @@ bool MyCreateHardLink(CFSTR newFileName, CFSTR existFileName) else #endif { - Func_CreateHardLinkW my_CreateHardLinkW = (Func_CreateHardLinkW) - ::GetProcAddress(::GetModuleHandleW(L"kernel32.dll"), "CreateHardLinkW"); +#ifdef Z7_USE_DYN_CreateHardLink + const + Func_CreateHardLinkW + my_CreateHardLinkW = Z7_GET_PROC_ADDRESS( + Func_CreateHardLinkW, ::GetModuleHandleW(L"kernel32.dll"), + "CreateHardLinkW"); if (!my_CreateHardLinkW) return false; + #define MY_CreateHardLinkW my_CreateHardLinkW +#else + #define MY_CreateHardLinkW CreateHardLinkW +#endif IF_USE_MAIN_PATH_2(newFileName, existFileName) - if (my_CreateHardLinkW(fs2us(newFileName), fs2us(existFileName), NULL)) + { + if (MY_CreateHardLinkW(fs2us(newFileName), fs2us(existFileName), NULL)) return true; - #ifdef WIN_LONG_PATH + } + #ifdef Z7_LONG_PATH if (USE_SUPER_PATH_2) { UString d1, d2; if (GetSuperPaths(newFileName, existFileName, d1, d2, USE_MAIN_PATH_2)) - return BOOLToBool(my_CreateHardLinkW(d1, d2, NULL)); + return BOOLToBool(MY_CreateHardLinkW(d1, d2, NULL)); } #endif } return false; } -#endif /* WinXP-64 CreateDir(): @@ -263,7 +413,7 @@ bool CreateDir(CFSTR path) IF_USE_MAIN_PATH if (::CreateDirectoryW(fs2us(path), NULL)) return true; - #ifdef WIN_LONG_PATH + #ifdef Z7_LONG_PATH if ((!USE_MAIN_PATH || ::GetLastError() != ERROR_ALREADY_EXISTS) && USE_SUPER_PATH) { UString superPath; @@ -298,7 +448,7 @@ static bool CreateDir2(CFSTR path) IF_USE_MAIN_PATH if (::CreateDirectoryW(fs2us(path), NULL)) return true; - #ifdef WIN_LONG_PATH + #ifdef Z7_LONG_PATH if ((!USE_MAIN_PATH || ::GetLastError() != ERROR_ALREADY_EXISTS) && USE_SUPER_PATH) { UString superPath; @@ -324,12 +474,16 @@ static bool CreateDir2(CFSTR path) return fi.IsDir(); } +#endif // _WIN32 + +static bool CreateDir2(CFSTR path); + bool CreateComplexDir(CFSTR _path) { #ifdef _WIN32 { - DWORD attrib = NFind::GetFileAttrib(_path); + const DWORD attrib = NFind::GetFileAttrib(_path); if (attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY) != 0) return true; } @@ -339,13 +493,23 @@ bool CreateComplexDir(CFSTR _path) if (IsDriveRootPath_SuperAllowed(_path)) return false; - unsigned prefixSize = GetRootPrefixSize(_path); + const unsigned prefixSize = GetRootPrefixSize(_path); - #endif + #endif // UNDER_CE + + #else // _WIN32 + + // Posix + NFind::CFileInfo fi; + if (fi.Find(_path)) + { + if (fi.IsDir()) + return true; + } - #endif + #endif // _WIN32 - FString path = _path; + FString path (_path); int pos = path.ReverseFind_PathSepar(); if (pos >= 0 && (unsigned)pos == path.Len() - 1) @@ -355,8 +519,8 @@ bool CreateComplexDir(CFSTR _path) path.DeleteBack(); } - const FString path2 = path; - pos = path.Len(); + const FString path2 (path); + pos = (int)path.Len(); for (;;) { @@ -375,17 +539,17 @@ bool CreateComplexDir(CFSTR _path) return false; #endif - path.DeleteFrom(pos); + path.DeleteFrom((unsigned)pos); } while (pos < (int)path2.Len()) { - int pos2 = NName::FindSepar(path2.Ptr(pos + 1)); + int pos2 = NName::FindSepar(path2.Ptr((unsigned)pos + 1)); if (pos2 < 0) - pos = path2.Len(); + pos = (int)path2.Len(); else pos += 1 + pos2; - path.SetFrom(path2, pos); + path.SetFrom(path2, (unsigned)pos); if (!CreateDir(path)) return false; } @@ -393,6 +557,9 @@ bool CreateComplexDir(CFSTR _path) return true; } + +#ifdef _WIN32 + bool DeleteFileAlways(CFSTR path) { /* If alt stream, we also need to clear READ-ONLY attribute of main file before delete. @@ -403,7 +570,7 @@ bool DeleteFileAlways(CFSTR path) && (attrib & FILE_ATTRIBUTE_DIRECTORY) == 0 && (attrib & FILE_ATTRIBUTE_READONLY) != 0) { - if (!SetFileAttrib(path, attrib & ~FILE_ATTRIBUTE_READONLY)) + if (!SetFileAttrib(path, attrib & ~(DWORD)FILE_ATTRIBUTE_READONLY)) return false; } } @@ -422,7 +589,7 @@ bool DeleteFileAlways(CFSTR path) IF_USE_MAIN_PATH if (::DeleteFileW(fs2us(path))) return true; - #ifdef WIN_LONG_PATH + #ifdef Z7_LONG_PATH if (USE_SUPER_PATH) { UString superPath; @@ -434,6 +601,8 @@ bool DeleteFileAlways(CFSTR path) return false; } + + bool RemoveDirWithSubItems(const FString &path) { bool needRemoveSubItems = true; @@ -452,12 +621,14 @@ bool RemoveDirWithSubItems(const FString &path) if (needRemoveSubItems) { - FString s = path; + FString s (path); s.Add_PathSepar(); - unsigned prefixSize = s.Len(); - s += FCHAR_ANY_MASK; - NFind::CEnumerator enumerator(s); - NFind::CFileInfo fi; + const unsigned prefixSize = s.Len(); + NFind::CEnumerator enumerator; + enumerator.SetDirPrefix(s); + NFind::CDirEntry fi; + bool isError = false; + DWORD lastError = 0; while (enumerator.Next(fi)) { s.DeleteFrom(prefixSize); @@ -465,18 +636,61 @@ bool RemoveDirWithSubItems(const FString &path) if (fi.IsDir()) { if (!RemoveDirWithSubItems(s)) - return false; + { + lastError = GetLastError(); + isError = true; + } } else if (!DeleteFileAlways(s)) - return false; + { + lastError = GetLastError(); + isError = false; + } + } + if (isError) + { + SetLastError(lastError); + return false; } } + // we clear read-only attrib to remove read-only dir if (!SetFileAttrib(path, 0)) return false; return RemoveDir(path); } +bool RemoveDirAlways_if_Empty(const FString &path) +{ + const DWORD attrib = NFind::GetFileAttrib(path); + if (attrib != INVALID_FILE_ATTRIBUTES + && (attrib & FILE_ATTRIBUTE_READONLY)) + { + bool need_ClearAttrib = true; + if ((attrib & FILE_ATTRIBUTE_REPARSE_POINT) == 0) + { + FString s (path); + s.Add_PathSepar(); + NFind::CEnumerator enumerator; + enumerator.SetDirPrefix(s); + NFind::CDirEntry fi; + if (enumerator.Next(fi)) + { + // we don't want to change attributes, if there are files + // in directory, because RemoveDir(path) will fail. + need_ClearAttrib = false; + // SetLastError(ERROR_DIR_NOT_EMPTY); + // return false; + } + } + if (need_ClearAttrib) + SetFileAttrib(path, 0); // we clear read-only attrib to remove read-only dir + } + return RemoveDir(path); +} + +#endif // _WIN32 + #ifdef UNDER_CE bool MyGetFullPathName(CFSTR path, FString &resFullPath) @@ -492,9 +706,14 @@ bool MyGetFullPathName(CFSTR path, FString &resFullPath) return GetFullPath(path, resFullPath); } +#ifdef _WIN32 + +/* Win10: SetCurrentDirectory() doesn't support long paths and + doesn't support super prefix "\\?\", if long path behavior is not + enabled in registry (LongPathsEnabled) and in manifest (longPathAware). */ + bool SetCurrentDir(CFSTR path) { - // SetCurrentDirectory doesn't support \\?\ prefix #ifndef _UNICODE if (!g_IsNT) { @@ -507,30 +726,80 @@ bool SetCurrentDir(CFSTR path) } } + +/* +we use system function GetCurrentDirectory() +new GetCurrentDirectory() DOCs: + - If the function fails, the return value is zero. + - If the function succeeds, the return value specifies + the number of characters that are written to the buffer, + not including the terminating null character. + - If the buffer is not large enough, the return value specifies + the required size of the buffer, in characters, + including the null-terminating character. + +GetCurrentDir() calls GetCurrentDirectory(). +GetCurrentDirectory() in win10 in tests: + the returned (path) does not end with a backslash, if + current directory is not root directory of drive. + But that behavior is not guarantied in specification docs. +*/ + bool GetCurrentDir(FString &path) { + const unsigned kBufSize = MAX_PATH + 16; path.Empty(); - DWORD needLength; + #ifndef _UNICODE if (!g_IsNT) { - TCHAR s[MAX_PATH + 2]; + TCHAR s[kBufSize + 1]; s[0] = 0; - needLength = ::GetCurrentDirectory(MAX_PATH + 1, s); + const DWORD len = ::GetCurrentDirectory(kBufSize, s); + if (len == 0 || len >= kBufSize) + return false; + s[kBufSize] = 0; // optional guard path = fas2fs(s); + return true; } else #endif { - WCHAR s[MAX_PATH + 2]; - s[0] = 0; - needLength = ::GetCurrentDirectoryW(MAX_PATH + 1, s); - path = us2fs(s); + DWORD len; + { + WCHAR s[kBufSize + 1]; + s[0] = 0; + len = ::GetCurrentDirectoryW(kBufSize, s); + if (len == 0) + return false; + if (len < kBufSize) + { + s[kBufSize] = 0; // optional guard + path = us2fs(s); + return true; + } + } + UString temp; + const DWORD len2 = ::GetCurrentDirectoryW(len, temp.GetBuf(len)); + if (len2 == 0) + return false; + temp.ReleaseBuf_CalcLen(len); + if (temp.Len() != len2 || len - 1 != len2) + { + /* it's unexpected case, if current dir of process + was changed between two function calls, + or some unexpected function implementation */ + // SetLastError((DWORD)E_FAIL); // we can set some error code + return false; + } + path = us2fs(temp); + return true; } - return (needLength > 0 && needLength <= MAX_PATH); } -#endif +#endif // _WIN32 +#endif // UNDER_CE + bool GetFullPathAndSplit(CFSTR path, FString &resDirPrefix, FString &resFileName) { @@ -538,8 +807,9 @@ bool GetFullPathAndSplit(CFSTR path, FString &resDirPrefix, FString &resFileName if (!res) resDirPrefix = path; int pos = resDirPrefix.ReverseFind_PathSepar(); - resFileName = resDirPrefix.Ptr(pos + 1); - resDirPrefix.DeleteFrom(pos + 1); + pos++; + resFileName = resDirPrefix.Ptr((unsigned)pos); + resDirPrefix.DeleteFrom((unsigned)pos); return res; } @@ -549,50 +819,85 @@ bool GetOnlyDirPrefix(CFSTR path, FString &resDirPrefix) return GetFullPathAndSplit(path, resDirPrefix, resFileName); } + + bool MyGetTempPath(FString &path) { - path.Empty(); - DWORD needLength; + #ifdef _WIN32 + + /* + new DOCs for GetTempPathW(): + - The returned string ends with a backslash. + - The maximum possible return value is MAX_PATH+1 (261). + */ + + const unsigned kBufSize = MAX_PATH + 16; + DWORD len; #ifndef _UNICODE if (!g_IsNT) { - TCHAR s[MAX_PATH + 2]; + TCHAR s[kBufSize + 1]; s[0] = 0; - needLength = ::GetTempPath(MAX_PATH + 1, s); + len = ::GetTempPath(kBufSize, s); path = fas2fs(s); } else #endif { - WCHAR s[MAX_PATH + 2]; + WCHAR s[kBufSize + 1]; s[0] = 0; - needLength = ::GetTempPathW(MAX_PATH + 1, s);; + len = ::GetTempPathW(kBufSize, s); path = us2fs(s); } - return (needLength > 0 && needLength <= MAX_PATH); + /* win10: GetTempPathW() doesn't set backslash at the end of path, + if (buffer_size == len_of(path_with_backslash)). + So we normalize path here: */ + NormalizeDirPathPrefix(path); + return (len != 0 && len < kBufSize); + + #else // !_WIN32 + + // FIXME: improve that code + path = STRING_PATH_SEPARATOR "tmp"; + const char *s; + if (NFind::DoesDirExist_FollowLink(path)) + s = STRING_PATH_SEPARATOR "tmp" STRING_PATH_SEPARATOR; + else + s = "." STRING_PATH_SEPARATOR; + path = s; + return true; + + #endif } -static bool CreateTempFile(CFSTR prefix, bool addRandom, FString &path, NIO::COutFile *outFile) + +bool CreateTempFile2(CFSTR prefix, bool addRandom, AString &postfix, NIO::COutFile *outFile) { - UInt32 d = (GetTickCount() << 12) ^ (GetCurrentThreadId() << 14) ^ GetCurrentProcessId(); + UInt32 d = + #ifdef _WIN32 + (GetTickCount() << 12) ^ (GetCurrentThreadId() << 14) ^ GetCurrentProcessId(); + #else + (UInt32)(time(NULL) << 12) ^ ((UInt32)getppid() << 14) ^ (UInt32)(getpid()); + #endif + for (unsigned i = 0; i < 100; i++) { - path = prefix; + postfix.Empty(); if (addRandom) { - FChar s[16]; - UInt32 value = d; + char s[16]; + UInt32 val = d; unsigned k; for (k = 0; k < 8; k++) { - unsigned t = value & 0xF; - value >>= 4; - s[k] = (FChar)((t < 10) ? ('0' + t) : ('A' + (t - 10))); + const unsigned t = (unsigned)val & 0xF; + val >>= 4; + s[k] = (char)((t < 10) ? ('0' + t) : ('A' + (t - 10))); } s[k] = '\0'; if (outFile) - path += FChar('.'); - path += s; + postfix.Add_Dot(); + postfix += s; UInt32 step = GetTickCount() + 2; if (step == 0) step = 1; @@ -600,7 +905,9 @@ static bool CreateTempFile(CFSTR prefix, bool addRandom, FString &path, NIO::COu } addRandom = true; if (outFile) - path += FTEXT(".tmp"); + postfix += ".tmp"; + FString path (prefix); + path += postfix; if (NFind::DoesFileOrDirExist(path)) { SetLastError(ERROR_ALREADY_EXISTS); @@ -608,7 +915,7 @@ static bool CreateTempFile(CFSTR prefix, bool addRandom, FString &path, NIO::COu } if (outFile) { - if (outFile->Create(path, false)) + if (outFile->Create_NEW(path)) return true; } else @@ -616,12 +923,12 @@ static bool CreateTempFile(CFSTR prefix, bool addRandom, FString &path, NIO::COu if (CreateDir(path)) return true; } - DWORD error = GetLastError(); + const DWORD error = GetLastError(); if (error != ERROR_FILE_EXISTS && error != ERROR_ALREADY_EXISTS) break; } - path.Empty(); + postfix.Empty(); return false; } @@ -629,8 +936,12 @@ bool CTempFile::Create(CFSTR prefix, NIO::COutFile *outFile) { if (!Remove()) return false; - if (!CreateTempFile(prefix, false, _path, outFile)) + _path.Empty(); + AString postfix; + if (!CreateTempFile2(prefix, false, postfix, outFile)) return false; + _path = prefix; + _path += postfix; _mustBeDeleted = true; return true; } @@ -639,11 +950,16 @@ bool CTempFile::CreateRandomInTempFolder(CFSTR namePrefix, NIO::COutFile *outFil { if (!Remove()) return false; + _path.Empty(); FString tempPath; if (!MyGetTempPath(tempPath)) return false; - if (!CreateTempFile(tempPath + namePrefix, true, _path, outFile)) + AString postfix; + tempPath += namePrefix; + if (!CreateTempFile2(tempPath, true, postfix, outFile)) return false; + _path = tempPath; + _path += postfix; _mustBeDeleted = true; return true; } @@ -656,25 +972,46 @@ bool CTempFile::Remove() return !_mustBeDeleted; } -bool CTempFile::MoveTo(CFSTR name, bool deleteDestBefore) +bool CTempFile::MoveTo(CFSTR name, bool deleteDestBefore, + ICopyFileProgress *progress) { if (deleteDestBefore) - if (NFind::DoesFileExist(name)) + { + if (NFind::DoesFileExist_Raw(name)) + { + // attrib = NFind::GetFileAttrib(name); if (!DeleteFileAlways(name)) return false; + } + } DisableDeleting(); - return MyMoveFile(_path, name); + // if (!progress) return MyMoveFile(_path, name); + return MyMoveFile_with_Progress(_path, name, progress); + /* + if (attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_READONLY)) + { + DWORD attrib2 = NFind::GetFileAttrib(name); + if (attrib2 != INVALID_FILE_ATTRIBUTES) + SetFileAttrib(name, attrib2 | FILE_ATTRIBUTE_READONLY); + } + */ } +#ifdef _WIN32 bool CTempDir::Create(CFSTR prefix) { if (!Remove()) return false; + _path.Empty(); FString tempPath; if (!MyGetTempPath(tempPath)) return false; - if (!CreateTempFile(tempPath + prefix, true, _path, NULL)) + tempPath += prefix; + AString postfix; + if (!CreateTempFile2(tempPath, true, postfix, NULL)) return false; + _path = tempPath; + _path += postfix; _mustBeDeleted = true; return true; } @@ -686,5 +1023,340 @@ bool CTempDir::Remove() _mustBeDeleted = !RemoveDirWithSubItems(_path); return !_mustBeDeleted; } +#endif + + + +#ifndef _WIN32 + +bool RemoveDir(CFSTR path) +{ + return (rmdir(path) == 0); +} + + +static BOOL My_CopyFile(CFSTR oldFile, CFSTR newFile, ICopyFileProgress *progress) +{ + { + NIO::COutFile outFile; + if (!outFile.Create_NEW(newFile)) + return FALSE; + NIO::CInFile inFile; + if (!inFile.Open(oldFile)) + return FALSE; + + const size_t k_BufSize = 1 << 16; + CAlignedBuffer1 buf(k_BufSize); + + UInt64 length = 0; + if (progress && !inFile.GetLength(length)) + length = 0; + UInt64 prev = 0; + UInt64 cur = 0; + for (;;) + { + const ssize_t num = inFile.read_part(buf, k_BufSize); + if (num == 0) + return TRUE; + if (num < 0) + break; + size_t processed; + const ssize_t num2 = outFile.write_full(buf, (size_t)num, processed); + if (num2 != num || processed != (size_t)num) + break; + cur += (size_t)num2; + if (progress && cur - prev >= (1u << 20)) + { + prev = cur; + if (progress->CopyFileProgress(length, cur) != PROGRESS_CONTINUE) + { + errno = EINTR; // instead of WIN32::ERROR_REQUEST_ABORTED + break; + } + } + } + } + // There is file IO error or process was interrupted by user. + // We close output file and delete it. + // DeleteFileAlways doesn't change errno (if successed), but we restore errno. + const int errno_save = errno; + DeleteFileAlways(newFile); + errno = errno_save; + return FALSE; +} + + +bool MyMoveFile_with_Progress(CFSTR oldFile, CFSTR newFile, + ICopyFileProgress *progress) +{ + int res = rename(oldFile, newFile); + if (res == 0) + return true; + if (errno != EXDEV) // (oldFile and newFile are not on the same mounted filesystem) + return false; + + if (My_CopyFile(oldFile, newFile, progress) == FALSE) + return false; + + struct stat info_file; + res = stat(oldFile, &info_file); + if (res != 0) + return false; + + /* + ret = chmod(dst,info_file.st_mode & g_umask.mask); + */ + return (unlink(oldFile) == 0); +} + +bool MyMoveFile(CFSTR oldFile, CFSTR newFile) +{ + return MyMoveFile_with_Progress(oldFile, newFile, NULL); +} + + +bool CreateDir(CFSTR path) +{ + return (mkdir(path, 0777) == 0); // change it +} + +static bool CreateDir2(CFSTR path) +{ + return (mkdir(path, 0777) == 0); // change it +} + + +bool DeleteFileAlways(CFSTR path) +{ + return (remove(path) == 0); +} + +bool SetCurrentDir(CFSTR path) +{ + return (chdir(path) == 0); +} + + +bool GetCurrentDir(FString &path) +{ + path.Empty(); + + #define MY_PATH_MAX PATH_MAX + // #define MY_PATH_MAX 1024 + + char s[MY_PATH_MAX + 1]; + char *res = getcwd(s, MY_PATH_MAX); + if (res) + { + path = fas2fs(s); + return true; + } + { + // if (errno != ERANGE) return false; + #if defined(__GLIBC__) || defined(__APPLE__) + /* As an extension to the POSIX.1-2001 standard, glibc's getcwd() + allocates the buffer dynamically using malloc(3) if buf is NULL. */ + res = getcwd(NULL, 0); + if (res) + { + path = fas2fs(res); + ::free(res); + return true; + } + #endif + return false; + } +} + + + +// #undef UTIME_OMIT // to debug + +#ifndef UTIME_OMIT + /* we can define UTIME_OMIT for debian and another systems. + Is it OK to define UTIME_OMIT to -2 here, if UTIME_OMIT is not defined? */ + // #define UTIME_OMIT -2 +#endif + + + + + +static bool SetFileTime_Base(CFSTR path, const CFiTime *cTime, const CFiTime *aTime, const CFiTime *mTime, const int flags) +{ + // need testing + /* + struct utimbuf buf; + struct stat st; + UNUSED_VAR(cTime) + printf("\nstat = %s\n", path); + int ret = stat(path, &st); + if (ret == 0) + { + buf.actime = st.st_atime; + buf.modtime = st.st_mtime; + } + else + { + time_t cur_time = time(0); + buf.actime = cur_time; + buf.modtime = cur_time; + } + if (aTime) + { + UInt32 ut; + if (NTime::FileTimeToUnixTime(*aTime, ut)) + buf.actime = ut; + } + if (mTime) + { + UInt32 ut; + if (NTime::FileTimeToUnixTime(*mTime, ut)) + buf.modtime = ut; + } + return utime(path, &buf) == 0; + */ + + // if (!aTime && !mTime) return true; + struct timespec times[2]; + UNUSED_VAR(cTime) + bool needChange; + needChange = FiTime_To_timespec(aTime, times[0]); + needChange |= FiTime_To_timespec(mTime, times[1]); + // if (mTime) { printf("\n time = %ld.%9ld\n", mTime->tv_sec, mTime->tv_nsec); } + if (!needChange) + return true; + return utimensat(AT_FDCWD, path, times, flags) == 0; +} + +bool SetDirTime(CFSTR path, const CFiTime *cTime, const CFiTime *aTime, const CFiTime *mTime) +{ + return SetFileTime_Base(path, cTime, aTime, mTime, 0); // (flags = 0) means follow_link +} + +bool SetLinkFileTime(CFSTR path, const CFiTime *cTime, const CFiTime *aTime, const CFiTime *mTime) +{ + return SetFileTime_Base(path, cTime, aTime, mTime, AT_SYMLINK_NOFOLLOW); +} + + +struct C_umask +{ + mode_t mask; + + C_umask() + { + /* by security reasons we restrict attributes according + with process's file mode creation mask (umask) */ + const mode_t um = umask(0); // octal :0022 is expected + mask = 0777 & (~um); // octal: 0755 is expected + umask(um); // restore the umask + // printf("\n umask = 0%03o mask = 0%03o\n", um, mask); + + // mask = 0777; // debug we can disable the restriction: + } +}; + +static C_umask g_umask; + +// #define PRF(x) x; +#define PRF(x) + +#define TRACE_SetFileAttrib(msg) \ + PRF(printf("\nSetFileAttrib(%s, %x) : %s\n", (const char *)path, attrib, msg);) + +#define TRACE_chmod(s, mode) \ + PRF(printf("\n chmod(%s, %o)\n", (const char *)path, (unsigned)(mode));) + +int my_chown(CFSTR path, uid_t owner, gid_t group) +{ + return chown(path, owner, group); +} + +bool SetFileAttrib_PosixHighDetect(CFSTR path, DWORD attrib) +{ + TRACE_SetFileAttrib("") + + struct stat st; + + bool use_lstat = true; + if (use_lstat) + { + if (lstat(path, &st) != 0) + { + TRACE_SetFileAttrib("bad lstat()") + return false; + } + // TRACE_chmod("lstat", st.st_mode); + } + else + { + if (stat(path, &st) != 0) + { + TRACE_SetFileAttrib("bad stat()") + return false; + } + } + + if (attrib & FILE_ATTRIBUTE_UNIX_EXTENSION) + { + TRACE_SetFileAttrib("attrib & FILE_ATTRIBUTE_UNIX_EXTENSION") + st.st_mode = attrib >> 16; + if (S_ISDIR(st.st_mode)) + { + // user/7z must be able to create files in this directory + st.st_mode |= (S_IRUSR | S_IWUSR | S_IXUSR); + } + else if (!S_ISREG(st.st_mode)) + return true; + } + else if (S_ISLNK(st.st_mode)) + { + /* for most systems: permissions for symlinks are fixed to rwxrwxrwx. + so we don't need chmod() for symlinks. */ + return true; + // SetLastError(ENOSYS); + // return false; + } + else + { + TRACE_SetFileAttrib("Only Windows Attributes") + // Only Windows Attributes + if (S_ISDIR(st.st_mode) + || (attrib & FILE_ATTRIBUTE_READONLY) == 0) + return true; + st.st_mode &= ~(mode_t)(S_IWUSR | S_IWGRP | S_IWOTH); // octal: ~0222; // disable write permissions + } + + int res; + /* + if (S_ISLNK(st.st_mode)) + { + printf("\nfchmodat()\n"); + TRACE_chmod(path, (st.st_mode) & g_umask.mask) + // AT_SYMLINK_NOFOLLOW is not implemted still in Linux. + res = fchmodat(AT_FDCWD, path, (st.st_mode) & g_umask.mask, + S_ISLNK(st.st_mode) ? AT_SYMLINK_NOFOLLOW : 0); + } + else + */ + { + TRACE_chmod(path, (st.st_mode) & g_umask.mask) + res = chmod(path, (st.st_mode) & g_umask.mask); + } + // TRACE_SetFileAttrib("End") + return (res == 0); +} + + +bool MyCreateHardLink(CFSTR newFileName, CFSTR existFileName) +{ + PRF(printf("\nhard link() %s -> %s\n", newFileName, existFileName);) + return (link(existFileName, newFileName) == 0); +} + +#endif // !_WIN32 + +// #endif }}} diff --git a/src/plugins/7zip/7za/cpp/Windows/FileDir.h b/src/plugins/7zip/7za/cpp/Windows/FileDir.h index b13d1cca..9ba98fca 100644 --- a/src/plugins/7zip/7za/cpp/Windows/FileDir.h +++ b/src/plugins/7zip/7za/cpp/Windows/FileDir.h @@ -1,7 +1,7 @@ // Windows/FileDir.h -#ifndef __WINDOWS_FILE_DIR_H -#define __WINDOWS_FILE_DIR_H +#ifndef ZIP7_INC_WINDOWS_FILE_DIR_H +#define ZIP7_INC_WINDOWS_FILE_DIR_H #include "../Common/MyString.h" @@ -14,9 +14,64 @@ namespace NDir { bool GetWindowsDir(FString &path); bool GetSystemDir(FString &path); -bool SetDirTime(CFSTR path, const FILETIME *cTime, const FILETIME *aTime, const FILETIME *mTime); +/* +WIN32 API : SetFileTime() doesn't allow to set zero timestamps in file +but linux : allows unix time = 0 in filesystem +*/ +/* +SetDirTime() can be used to set time for file or for dir. +If path is symbolic link, SetDirTime() will follow symbolic link, +and it will set timestamps of symbolic link's target file or dir. +*/ +bool SetDirTime(CFSTR path, const CFiTime *cTime, const CFiTime *aTime, const CFiTime *mTime); + +/* +SetLinkFileTime() doesn't follow symbolic link, +and it sets timestamps for symbolic link file itself. +If (path) is not symbolic link, it still can work (at least in some new OS versions). +*/ +bool SetLinkFileTime(CFSTR path, const CFiTime *cTime, const CFiTime *aTime, const CFiTime *mTime); + + +#ifdef _WIN32 + bool SetFileAttrib(CFSTR path, DWORD attrib); + +/* + Some programs store posix attributes in high 16 bits of windows attributes field. + Also some programs use additional flag markers: 0x8000 or 0x4000. + SetFileAttrib_PosixHighDetect() tries to detect posix field, and it extracts only attribute + bits that are related to current system only. +*/ +#else + +int my_chown(CFSTR path, uid_t owner, gid_t group); + +#endif + +bool SetFileAttrib_PosixHighDetect(CFSTR path, DWORD attrib); + + +#ifndef _WIN32 +#define PROGRESS_CONTINUE 0 +#define PROGRESS_CANCEL 1 +// #define PROGRESS_STOP 2 +// #define PROGRESS_QUIET 3 +#endif +Z7_PURE_INTERFACES_BEGIN +DECLARE_INTERFACE(ICopyFileProgress) +{ + // in: total, current: include all/processed alt streams. + // it returns PROGRESS_CONTINUE or PROGRESS_CANCEL. + virtual DWORD CopyFileProgress(UInt64 total, UInt64 current) = 0; +}; +Z7_PURE_INTERFACES_END + bool MyMoveFile(CFSTR existFileName, CFSTR newFileName); +// (progress == NULL) is allowed +bool MyMoveFile_with_Progress(CFSTR oldFile, CFSTR newFile, + ICopyFileProgress *progress); + #ifndef UNDER_CE bool MyCreateHardLink(CFSTR newFileName, CFSTR existFileName); @@ -34,6 +89,11 @@ bool CreateComplexDir(CFSTR path); bool DeleteFileAlways(CFSTR name); bool RemoveDirWithSubItems(const FString &path); +#ifdef _WIN32 +bool RemoveDirAlways_if_Empty(const FString &path); +#else +#define RemoveDirAlways_if_Empty RemoveDir +#endif bool MyGetFullPathName(CFSTR path, FString &resFullPath); bool GetFullPathAndSplit(CFSTR path, FString &resDirPrefix, FString &resFileName); @@ -48,7 +108,9 @@ bool GetCurrentDir(FString &resultPath); bool MyGetTempPath(FString &resultPath); -class CTempFile +bool CreateTempFile2(CFSTR prefix, bool addRandom, AString &postfix, NIO::COutFile *outFile); + +class CTempFile MY_UNCOPYABLE { bool _mustBeDeleted; FString _path; @@ -60,10 +122,14 @@ class CTempFile bool Create(CFSTR pathPrefix, NIO::COutFile *outFile); // pathPrefix is not folder prefix bool CreateRandomInTempFolder(CFSTR namePrefix, NIO::COutFile *outFile); bool Remove(); - bool MoveTo(CFSTR name, bool deleteDestBefore); + // bool MoveTo(CFSTR name, bool deleteDestBefore); + bool MoveTo(CFSTR name, bool deleteDestBefore, + ICopyFileProgress *progress); }; -class CTempDir + +#ifdef _WIN32 +class CTempDir MY_UNCOPYABLE { bool _mustBeDeleted; FString _path; @@ -75,9 +141,11 @@ class CTempDir bool Create(CFSTR namePrefix) ; bool Remove(); }; +#endif + #if !defined(UNDER_CE) -class CCurrentDirRestorer +class CCurrentDirRestorer MY_UNCOPYABLE { FString _path; public: diff --git a/src/plugins/7zip/7za/cpp/Windows/FileFind.cpp b/src/plugins/7zip/7za/cpp/Windows/FileFind.cpp index 05196f92..669541e7 100644 --- a/src/plugins/7zip/7za/cpp/Windows/FileFind.cpp +++ b/src/plugins/7zip/7za/cpp/Windows/FileFind.cpp @@ -2,8 +2,13 @@ #include "StdAfx.h" -#ifndef _UNICODE -#include "../Common/StringConvert.h" +// #include + +#ifndef _WIN32 +#include /* Definition of AT_* constants */ +#include "TimeUtils.h" +// for major +// #include #endif #include "FileFind.h" @@ -20,6 +25,14 @@ using namespace NName; #if defined(_WIN32) && !defined(UNDER_CE) +#if !defined(Z7_WIN32_WINNT_MIN) || Z7_WIN32_WINNT_MIN < 0x0502 // Win2003 +#define Z7_USE_DYN_FindFirstStream +#endif + +#ifdef Z7_USE_DYN_FindFirstStream + +Z7_DIAGNOSTIC_IGNORE_CAST_FUNCTION + EXTERN_C_BEGIN typedef enum @@ -34,27 +47,148 @@ typedef struct WCHAR cStreamName[MAX_PATH + 36]; } MY_WIN32_FIND_STREAM_DATA, *MY_PWIN32_FIND_STREAM_DATA; -typedef WINBASEAPI HANDLE (WINAPI *FindFirstStreamW_Ptr)(LPCWSTR fileName, MY_STREAM_INFO_LEVELS infoLevel, +typedef HANDLE (WINAPI *Func_FindFirstStreamW)(LPCWSTR fileName, MY_STREAM_INFO_LEVELS infoLevel, LPVOID findStreamData, DWORD flags); -typedef WINBASEAPI BOOL (APIENTRY *FindNextStreamW_Ptr)(HANDLE findStream, LPVOID findStreamData); +typedef BOOL (APIENTRY *Func_FindNextStreamW)(HANDLE findStream, LPVOID findStreamData); EXTERN_C_END +#else + +#define MY_WIN32_FIND_STREAM_DATA WIN32_FIND_STREAM_DATA +#define My_FindStreamInfoStandard FindStreamInfoStandard + #endif +#endif // defined(_WIN32) && !defined(UNDER_CE) + namespace NWindows { namespace NFile { -#ifdef SUPPORT_DEVICE_FILE + +#ifdef _WIN32 +#ifdef Z7_DEVICE_FILE namespace NSystem { bool MyGetDiskFreeSpace(CFSTR rootPath, UInt64 &clusterSize, UInt64 &totalSize, UInt64 &freeSize); } #endif +#endif namespace NFind { +/* +#ifdef _WIN32 +#define MY_CLEAR_FILETIME(ft) ft.dwLowDateTime = ft.dwHighDateTime = 0; +#else +#define MY_CLEAR_FILETIME(ft) ft.tv_sec = 0; ft.tv_nsec = 0; +#endif +*/ + +void CFileInfoBase::ClearBase() throw() +{ + Size = 0; + FiTime_Clear(CTime); + FiTime_Clear(ATime); + FiTime_Clear(MTime); + + #ifdef _WIN32 + Attrib = 0; + // ReparseTag = 0; + IsAltStream = false; + IsDevice = false; + #else + dev = 0; + ino = 0; + mode = 0; + nlink = 0; + uid = 0; + gid = 0; + rdev = 0; + #endif +} + + +bool CFileInfoBase::SetAs_StdInFile() +{ + ClearBase(); + Size = (UInt64)(Int64)-1; + NTime::GetCurUtc_FiTime(MTime); + CTime = ATime = MTime; + +#ifdef _WIN32 + + /* in GUI mode : GetStdHandle(STD_INPUT_HANDLE) returns NULL, + and it doesn't set LastError. */ +#if 1 + SetLastError(0); + const HANDLE h = GetStdHandle(STD_INPUT_HANDLE); + if (!h || h == INVALID_HANDLE_VALUE) + { + if (GetLastError() == 0) + SetLastError(ERROR_INVALID_HANDLE); + return false; + } + BY_HANDLE_FILE_INFORMATION info; + if (GetFileInformationByHandle(h, &info) + && info.dwVolumeSerialNumber) + { + Size = (((UInt64)info.nFileSizeHigh) << 32) + info.nFileSizeLow; + // FileID_Low = (((UInt64)info.nFileIndexHigh) << 32) + info.nFileIndexLow; + // NumLinks = SupportHardLinks ? info.nNumberOfLinks : 1; + Attrib = info.dwFileAttributes; + CTime = info.ftCreationTime; + ATime = info.ftLastAccessTime; + MTime = info.ftLastWriteTime; + } +#if 0 + printf( + "\ndwFileAttributes = %8x" + "\nftCreationTime = %8x" + "\nftLastAccessTime = %8x" + "\nftLastWriteTime = %8x" + "\ndwVolumeSerialNumber = %8x" + "\nnFileSizeHigh = %8x" + "\nnFileSizeLow = %8x" + "\nnNumberOfLinks = %8x" + "\nnFileIndexHigh = %8x" + "\nnFileIndexLow = %8x \n", + (unsigned)info.dwFileAttributes, + (unsigned)info.ftCreationTime.dwHighDateTime, + (unsigned)info.ftLastAccessTime.dwHighDateTime, + (unsigned)info.ftLastWriteTime.dwHighDateTime, + (unsigned)info.dwVolumeSerialNumber, + (unsigned)info.nFileSizeHigh, + (unsigned)info.nFileSizeLow, + (unsigned)info.nNumberOfLinks, + (unsigned)info.nFileIndexHigh, + (unsigned)info.nFileIndexLow); +#endif +#endif + +#else // non-Wiondow + + mode = S_IFIFO | 0777; // 0755 : 0775 : 0664 : 0644 : +#if 1 + struct stat st; + if (fstat(0, &st) == 0) + { + SetFrom_stat(st); + if (!S_ISREG(st.st_mode) + // S_ISFIFO(st->st_mode) + || st.st_size == 0) + { + Size = (UInt64)(Int64)-1; + // mode = S_IFIFO | 0777; + } + } +#endif +#endif + + return true; +} + bool CFileInfo::IsDots() const throw() { if (!IsDir() || Name.IsEmpty()) @@ -64,12 +198,17 @@ bool CFileInfo::IsDots() const throw() return Name.Len() == 1 || (Name.Len() == 2 && Name[1] == '.'); } + +#ifdef _WIN32 + + #define WIN_FD_TO_MY_FI(fi, fd) \ fi.Attrib = fd.dwFileAttributes; \ fi.CTime = fd.ftCreationTime; \ fi.ATime = fd.ftLastAccessTime; \ fi.MTime = fd.ftLastWriteTime; \ fi.Size = (((UInt64)fd.nFileSizeHigh) << 32) + fd.nFileSizeLow; \ + /* fi.ReparseTag = fd.dwReserved0; */ \ fi.IsAltStream = false; \ fi.IsDevice = false; @@ -83,7 +222,7 @@ bool CFileInfo::IsDots() const throw() static void Convert_WIN32_FIND_DATA_to_FileInfo(const WIN32_FIND_DATAW &fd, CFileInfo &fi) { - WIN_FD_TO_MY_FI(fi, fd); + WIN_FD_TO_MY_FI(fi, fd) fi.Name = us2fs(fd.cFileName); #if defined(_WIN32) && !defined(UNDER_CE) // fi.ShortName = us2fs(fd.cAlternateFileName); @@ -91,10 +230,9 @@ static void Convert_WIN32_FIND_DATA_to_FileInfo(const WIN32_FIND_DATAW &fd, CFil } #ifndef _UNICODE - static void Convert_WIN32_FIND_DATA_to_FileInfo(const WIN32_FIND_DATA &fd, CFileInfo &fi) { - WIN_FD_TO_MY_FI(fi, fd); + WIN_FD_TO_MY_FI(fi, fd) fi.Name = fas2fs(fd.cFileName); #if defined(_WIN32) && !defined(UNDER_CE) // fi.ShortName = fas2fs(fd.cAlternateFileName); @@ -143,7 +281,8 @@ WinXP-64 FindFirstFile(): \\Server\Share_RootDrive - ERROR_INVALID_NAME \\Server\Share_RootDrive\ - ERROR_INVALID_NAME - c:\* - ERROR_FILE_NOT_FOUND, if thare are no item in that folder + e:\* - ERROR_FILE_NOT_FOUND, if there are no items in that root folder + w:\* - ERROR_PATH_NOT_FOUND, if there is no such drive w: */ bool CFindFile::FindFirst(CFSTR path, CFileInfo &fi) @@ -166,7 +305,7 @@ bool CFindFile::FindFirst(CFSTR path, CFileInfo &fi) IF_USE_MAIN_PATH _handle = ::FindFirstFileW(fs2us(path), &fd); - #ifdef WIN_LONG_PATH + #ifdef Z7_LONG_PATH if (_handle == INVALID_HANDLE_VALUE && USE_SUPER_PATH) { UString superPath; @@ -207,27 +346,39 @@ bool CFindFile::FindNext(CFileInfo &fi) //////////////////////////////// // AltStreams -static FindFirstStreamW_Ptr g_FindFirstStreamW; -static FindNextStreamW_Ptr g_FindNextStreamW; - -struct CFindStreamLoader +#ifdef Z7_USE_DYN_FindFirstStream +static Func_FindFirstStreamW g_FindFirstStreamW; +static Func_FindNextStreamW g_FindNextStreamW; +#define MY_FindFirstStreamW g_FindFirstStreamW +#define MY_FindNextStreamW g_FindNextStreamW +static struct CFindStreamLoader { CFindStreamLoader() { - g_FindFirstStreamW = (FindFirstStreamW_Ptr)::GetProcAddress(::GetModuleHandleA("kernel32.dll"), "FindFirstStreamW"); - g_FindNextStreamW = (FindNextStreamW_Ptr)::GetProcAddress(::GetModuleHandleA("kernel32.dll"), "FindNextStreamW"); + const HMODULE hm = ::GetModuleHandleA("kernel32.dll"); + g_FindFirstStreamW = Z7_GET_PROC_ADDRESS( + Func_FindFirstStreamW, hm, + "FindFirstStreamW"); + g_FindNextStreamW = Z7_GET_PROC_ADDRESS( + Func_FindNextStreamW, hm, + "FindNextStreamW"); } } g_FindStreamLoader; +#else +#define MY_FindFirstStreamW FindFirstStreamW +#define MY_FindNextStreamW FindNextStreamW +#endif + bool CStreamInfo::IsMainStream() const throw() { return StringsAreEqualNoCase_Ascii(Name, "::$DATA"); -}; +} UString CStreamInfo::GetReducedName() const { // remove ":$DATA" postfix, but keep postfix, if Name is "::$DATA" - UString s = Name; + UString s (Name); if (s.Len() > 6 + 1 && StringsAreEqualNoCase_Ascii(s.RightPtr(6), ":$DATA")) s.DeleteFrom(s.Len() - 6); return s; @@ -245,7 +396,7 @@ UString CStreamInfo::GetReducedName2() const static void Convert_WIN32_FIND_STREAM_DATA_to_StreamInfo(const MY_WIN32_FIND_STREAM_DATA &sd, CStreamInfo &si) { - si.Size = sd.StreamSize.QuadPart; + si.Size = (UInt64)sd.StreamSize.QuadPart; si.Name = sd.cStreamName; } @@ -270,27 +421,29 @@ bool CFindStream::FindFirst(CFSTR path, CStreamInfo &si) { if (!Close()) return false; +#ifdef Z7_USE_DYN_FindFirstStream if (!g_FindFirstStreamW) { ::SetLastError(ERROR_CALL_NOT_IMPLEMENTED); return false; } +#endif { MY_WIN32_FIND_STREAM_DATA sd; SetLastError(0); IF_USE_MAIN_PATH - _handle = g_FindFirstStreamW(fs2us(path), My_FindStreamInfoStandard, &sd, 0); + _handle = MY_FindFirstStreamW(fs2us(path), My_FindStreamInfoStandard, &sd, 0); if (_handle == INVALID_HANDLE_VALUE) { if (::GetLastError() == ERROR_HANDLE_EOF) return false; // long name can be tricky for path like ".\dirName". - #ifdef WIN_LONG_PATH + #ifdef Z7_LONG_PATH if (USE_SUPER_PATH) { UString superPath; if (GetSuperPath(path, superPath, USE_MAIN_PATH)) - _handle = g_FindFirstStreamW(superPath, My_FindStreamInfoStandard, &sd, 0); + _handle = MY_FindFirstStreamW(superPath, My_FindStreamInfoStandard, &sd, 0); } #endif } @@ -303,14 +456,16 @@ bool CFindStream::FindFirst(CFSTR path, CStreamInfo &si) bool CFindStream::FindNext(CStreamInfo &si) { +#ifdef Z7_USE_DYN_FindFirstStream if (!g_FindNextStreamW) { ::SetLastError(ERROR_CALL_NOT_IMPLEMENTED); return false; } +#endif { MY_WIN32_FIND_STREAM_DATA sd; - if (!g_FindNextStreamW(_handle, &sd)) + if (!MY_FindNextStreamW(_handle, &sd)) return false; Convert_WIN32_FIND_STREAM_DATA_to_StreamInfo(sd, si); } @@ -336,19 +491,6 @@ bool CStreamEnumerator::Next(CStreamInfo &si, bool &found) #endif -#define MY_CLEAR_FILETIME(ft) ft.dwLowDateTime = ft.dwHighDateTime = 0; - -void CFileInfoBase::ClearBase() throw() -{ - Size = 0; - MY_CLEAR_FILETIME(CTime); - MY_CLEAR_FILETIME(ATime); - MY_CLEAR_FILETIME(MTime); - Attrib = 0; - IsAltStream = false; - IsDevice = false; -} - /* WinXP-64 GetFileAttributes(): If the function fails, it returns INVALID_FILE_ATTRIBUTES and use GetLastError() to get error code @@ -381,7 +523,7 @@ DWORD GetFileAttrib(CFSTR path) if (dw != INVALID_FILE_ATTRIBUTES) return dw; } - #ifdef WIN_LONG_PATH + #ifdef Z7_LONG_PATH if (USE_SUPER_PATH) { UString superPath; @@ -416,9 +558,23 @@ also we support paths that are not supported by FindFirstFile: c::stream - Name = c::stream */ -bool CFileInfo::Find(CFSTR path) +bool CFileInfo::Find(CFSTR path, bool followLink) { - #ifdef SUPPORT_DEVICE_FILE + #ifdef Z7_DEVICE_FILE + + if (IS_PATH_SEPAR(path[0]) && + IS_PATH_SEPAR(path[1]) && + path[2] == '.' && + path[3] == 0) + { + // 22.00 : it's virtual directory for devices + // IsDevice = true; + ClearBase(); + Name = path + 2; + Attrib = FILE_ATTRIBUTE_DIRECTORY; + return true; + } + if (IsDevicePath(path)) { ClearBase(); @@ -449,12 +605,12 @@ bool CFileInfo::Find(CFSTR path) #if defined(_WIN32) && !defined(UNDER_CE) - int colonPos = FindAltStreamColon(path); + const int colonPos = FindAltStreamColon(path); if (colonPos >= 0 && path[(unsigned)colonPos + 1] != 0) { UString streamName = fs2us(path + (unsigned)colonPos); - FString filePath = path; - filePath.DeleteFrom(colonPos); + FString filePath (path); + filePath.DeleteFrom((unsigned)colonPos); /* we allow both cases: name:stream name:stream:$DATA @@ -462,12 +618,12 @@ bool CFileInfo::Find(CFSTR path) const unsigned kPostfixSize = 6; if (streamName.Len() <= kPostfixSize || !StringsAreEqualNoCase_Ascii(streamName.RightPtr(kPostfixSize), ":$DATA")) - streamName += L":$DATA"; + streamName += ":$DATA"; bool isOk = true; if (IsDrivePath2(filePath) && - (colonPos == 2 || colonPos == 3 && filePath[2] == '\\')) + (colonPos == 2 || (colonPos == 3 && filePath[2] == '\\'))) { // FindFirstFile doesn't work for "c:\" and for "c:" (if current dir is ROOT) ClearBase(); @@ -476,11 +632,11 @@ bool CFileInfo::Find(CFSTR path) Name = filePath; } else - isOk = Find(filePath); + isOk = Find(filePath, followLink); // check it (followLink) if (isOk) { - Attrib &= ~(FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT); + Attrib &= ~(DWORD)(FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT); Size = 0; CStreamEnumerator enumerator(filePath); for (;;) @@ -536,11 +692,15 @@ bool CFileInfo::Find(CFSTR path) ClearBase(); Attrib = attrib; Name = path + rootSize; - Name.DeleteFrom(2); // we don't need backslash (C:) + Name.DeleteFrom(2); + if (!Fill_From_ByHandleFileInfo(path)) + { + } return true; } } else if (IS_PATH_SEPAR(path[0])) + { if (path[1] == 0) { DWORD attrib = GetFileAttrib(path); @@ -559,14 +719,19 @@ bool CFileInfo::Find(CFSTR path) { if (NName::FindSepar(path + prefixSize) < 0) { - FString s = path; + if (Fill_From_ByHandleFileInfo(path)) + { + Name = path + prefixSize; + return true; + } + + FString s (path); s.Add_PathSepar(); - s += FCHAR_ANY_MASK; - + s.Add_Char('*'); // CHAR_ANY_MASK bool isOK = false; if (finder.FindFirst(s, *this)) { - if (Name == FTEXT(".")) + if (Name.IsEqualTo(".")) { Name = path + prefixSize; return true; @@ -576,8 +741,8 @@ bool CFileInfo::Find(CFSTR path) But it's possible that there are another items */ } { - DWORD attrib = GetFileAttrib(path); - if (isOK || attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY) != 0) + const DWORD attrib = GetFileAttrib(path); + if (isOK || (attrib != INVALID_FILE_ATTRIBUTES && (attrib & FILE_ATTRIBUTE_DIRECTORY) != 0)) { ClearBase(); if (attrib != INVALID_FILE_ATTRIBUTES) @@ -592,23 +757,112 @@ bool CFileInfo::Find(CFSTR path) } } } + } } #endif - return finder.FindFirst(path, *this); + bool res = finder.FindFirst(path, *this); + if (!followLink + || !res + || !HasReparsePoint()) + return res; + + // return FollowReparse(path, IsDir()); + return Fill_From_ByHandleFileInfo(path); +/* + // Fill_From_ByHandleFileInfo returns false (with Access Denied error), + // if there is reparse link file (not directory reparse item). + if (Fill_From_ByHandleFileInfo(path)) + return true; + return HasReparsePoint(); +*/ +} + +bool CFileInfoBase::Fill_From_ByHandleFileInfo(CFSTR path) +{ + BY_HANDLE_FILE_INFORMATION info; + if (!NIO::CFileBase::GetFileInformation(path, &info)) + return false; + { + Size = (((UInt64)info.nFileSizeHigh) << 32) + info.nFileSizeLow; + CTime = info.ftCreationTime; + ATime = info.ftLastAccessTime; + MTime = info.ftLastWriteTime; + Attrib = info.dwFileAttributes; + return true; + } } +/* +bool CFileInfo::FollowReparse(CFSTR path, bool isDir) +{ + if (isDir) + { + FString prefix = path; + prefix.Add_PathSepar(); + + // "folder/." refers to folder itself. So we can't use that path + // we must use enumerator and search "." item + CEnumerator enumerator; + enumerator.SetDirPrefix(prefix); + for (;;) + { + CFileInfo fi; + if (!enumerator.NextAny(fi)) + break; + if (fi.Name.IsEqualTo_Ascii_NoCase(".")) + { + // we can copy preperies; + CTime = fi.CTime; + ATime = fi.ATime; + MTime = fi.MTime; + Attrib = fi.Attrib; + Size = fi.Size; + return true; + } + break; + } + // LastError(lastError); + return false; + } + + { + NIO::CInFile inFile; + if (inFile.Open(path)) + { + BY_HANDLE_FILE_INFORMATION info; + if (inFile.GetFileInformation(&info)) + { + ClearBase(); + Size = (((UInt64)info.nFileSizeHigh) << 32) + info.nFileSizeLow; + CTime = info.ftCreationTime; + ATime = info.ftLastAccessTime; + MTime = info.ftLastWriteTime; + Attrib = info.dwFileAttributes; + return true; + } + } + return false; + } +} +*/ -bool DoesFileExist(CFSTR name) +bool DoesFileExist_Raw(CFSTR name) { CFileInfo fi; return fi.Find(name) && !fi.IsDir(); } -bool DoesDirExist(CFSTR name) +bool DoesFileExist_FollowLink(CFSTR name) +{ + CFileInfo fi; + return fi.Find_FollowLink(name) && !fi.IsDir(); +} + +bool DoesDirExist(CFSTR name, bool followLink) { CFileInfo fi; - return fi.Find(name) && fi.IsDir(); + return fi.Find(name, followLink) && fi.IsDir(); } bool DoesFileOrDirExist(CFSTR name) @@ -618,6 +872,12 @@ bool DoesFileOrDirExist(CFSTR name) } +void CEnumerator::SetDirPrefix(const FString &dirPrefix) +{ + _wildcard = dirPrefix; + _wildcard.Add_Char('*'); +} + bool CEnumerator::NextAny(CFileInfo &fi) { if (_findFile.IsHandleAllocated()) @@ -639,15 +899,46 @@ bool CEnumerator::Next(CFileInfo &fi) bool CEnumerator::Next(CFileInfo &fi, bool &found) { + /* + for (;;) + { + if (!NextAny(fi)) + break; + if (!fi.IsDots()) + { + found = true; + return true; + } + } + */ + if (Next(fi)) { found = true; return true; } + found = false; - return (::GetLastError() == ERROR_NO_MORE_FILES); + DWORD lastError = ::GetLastError(); + if (_findFile.IsHandleAllocated()) + return (lastError == ERROR_NO_MORE_FILES); + // we support the case for empty root folder: FindFirstFile("c:\\*") returns ERROR_FILE_NOT_FOUND + if (lastError == ERROR_FILE_NOT_FOUND) + return true; + if (lastError == ERROR_ACCESS_DENIED) + { + // here we show inaccessible root system folder as empty folder to eliminate redundant user warnings + const char *s = "System Volume Information" STRING_PATH_SEPARATOR "*"; + const int len = (int)strlen(s); + const int delta = (int)_wildcard.Len() - len; + if (delta == 0 || (delta > 0 && IS_PATH_SEPAR(_wildcard[(unsigned)delta - 1]))) + if (StringsAreEqual_Ascii(_wildcard.Ptr((unsigned)delta), s)) + return true; + } + return false; } + //////////////////////////////// // CFindChangeNotification // FindFirstChangeNotification can return 0. MSDN doesn't tell about it. @@ -672,7 +963,7 @@ HANDLE CFindChangeNotification::FindFirst(CFSTR path, bool watchSubtree, DWORD n { IF_USE_MAIN_PATH _handle = ::FindFirstChangeNotificationW(fs2us(path), BoolToBOOL(watchSubtree), notifyFilter); - #ifdef WIN_LONG_PATH + #ifdef Z7_LONG_PATH if (!IsHandleAllocated()) { UString superPath; @@ -738,6 +1029,430 @@ bool MyGetLogicalDriveStrings(CObjectVector &driveStrings) } } +#endif // UNDER_CE + + + +#else // _WIN32 + +// ---------- POSIX ---------- + +static int MY_lstat(CFSTR path, struct stat *st, bool followLink) +{ + memset(st, 0, sizeof(*st)); + int res; + // #ifdef ENV_HAVE_LSTAT + if (/* global_use_lstat && */ !followLink) + { + // printf("\nlstat\n"); + res = lstat(path, st); + } + else + // #endif + { + // printf("\nstat\n"); + res = stat(path, st); + } +#if 0 +#if defined(__clang__) && __clang_major__ >= 14 + #pragma GCC diagnostic ignored "-Wc++98-compat-pedantic" +#endif + + printf("\n st_dev = %lld", (long long)(st->st_dev)); + printf("\n st_ino = %lld", (long long)(st->st_ino)); + printf("\n st_mode = %llx", (long long)(st->st_mode)); + printf("\n st_nlink = %lld", (long long)(st->st_nlink)); + printf("\n st_uid = %lld", (long long)(st->st_uid)); + printf("\n st_gid = %lld", (long long)(st->st_gid)); + printf("\n st_size = %lld", (long long)(st->st_size)); + printf("\n st_blksize = %lld", (long long)(st->st_blksize)); + printf("\n st_blocks = %lld", (long long)(st->st_blocks)); + printf("\n st_ctim = %lld", (long long)(ST_CTIME((*st)).tv_sec)); + printf("\n st_mtim = %lld", (long long)(ST_MTIME((*st)).tv_sec)); + printf("\n st_atim = %lld", (long long)(ST_ATIME((*st)).tv_sec)); + printf(S_ISFIFO(st->st_mode) ? "\n FIFO" : "\n NO FIFO"); + printf("\n"); +#endif + + return res; +} + + +static const char *Get_Name_from_Path(CFSTR path) throw() +{ + size_t len = strlen(path); + if (len == 0) + return path; + const char *p = path + len - 1; + { + if (p == path) + return path; + p--; + } + for (;;) + { + char c = *p; + if (IS_PATH_SEPAR(c)) + return p + 1; + if (p == path) + return path; + p--; + } +} + + +UInt32 Get_WinAttribPosix_From_PosixMode(UInt32 mode) +{ + UInt32 attrib = S_ISDIR(mode) ? + FILE_ATTRIBUTE_DIRECTORY : + FILE_ATTRIBUTE_ARCHIVE; + if ((mode & 0222) == 0) // S_IWUSR in p7zip + attrib |= FILE_ATTRIBUTE_READONLY; + return attrib | FILE_ATTRIBUTE_UNIX_EXTENSION | ((mode & 0xFFFF) << 16); +} + +/* +UInt32 Get_WinAttrib_From_stat(const struct stat &st) +{ + UInt32 attrib = S_ISDIR(st.st_mode) ? + FILE_ATTRIBUTE_DIRECTORY : + FILE_ATTRIBUTE_ARCHIVE; + + if ((st.st_mode & 0222) == 0) // check it !!! + attrib |= FILE_ATTRIBUTE_READONLY; + + attrib |= FILE_ATTRIBUTE_UNIX_EXTENSION + ((st.st_mode & 0xFFFF) << 16); + return attrib; +} +*/ + +void CFileInfoBase::SetFrom_stat(const struct stat &st) +{ + // IsDevice = false; + + if (S_ISDIR(st.st_mode)) + { + Size = 0; + } + else + { + Size = (UInt64)st.st_size; // for a symbolic link, size = size of filename + } + + // Attrib = Get_WinAttribPosix_From_PosixMode(st.st_mode); + + // NTime::UnixTimeToFileTime(st.st_ctime, CTime); + // NTime::UnixTimeToFileTime(st.st_mtime, MTime); + // NTime::UnixTimeToFileTime(st.st_atime, ATime); + #ifdef __APPLE__ + // #ifdef _DARWIN_FEATURE_64_BIT_INODE + /* + here we can use birthtime instead of st_ctimespec. + but we use st_ctimespec for compatibility with previous versions and p7zip. + st_birthtimespec in OSX + st_birthtim : at FreeBSD, NetBSD + */ + // timespec_To_FILETIME(st.st_birthtimespec, CTime); + // #else + // timespec_To_FILETIME(st.st_ctimespec, CTime); + // #endif + // timespec_To_FILETIME(st.st_mtimespec, MTime); + // timespec_To_FILETIME(st.st_atimespec, ATime); + CTime = st.st_ctimespec; + MTime = st.st_mtimespec; + ATime = st.st_atimespec; + + #elif defined(__QNXNTO__) && defined(__ARM__) && !defined(__aarch64__) + + // CTime = ST_CTIME(st); + // MTime = ST_MTIME(st); + // ATime = ST_ATIME(st); + CTime.tv_sec = st.st_ctime; CTime.tv_nsec = 0; + MTime.tv_sec = st.st_mtime; MTime.tv_nsec = 0; + ATime.tv_sec = st.st_atime; ATime.tv_nsec = 0; + + #else + // timespec_To_FILETIME(st.st_ctim, CTime, &CTime_ns100); + // timespec_To_FILETIME(st.st_mtim, MTime, &MTime_ns100); + // timespec_To_FILETIME(st.st_atim, ATime, &ATime_ns100); + CTime = st.st_ctim; + MTime = st.st_mtim; + ATime = st.st_atim; + + #endif + + dev = st.st_dev; + ino = st.st_ino; + mode = st.st_mode; + nlink = st.st_nlink; + uid = st.st_uid; + gid = st.st_gid; + rdev = st.st_rdev; + + /* + printf("\n sizeof timespec = %d", (int)sizeof(timespec)); + printf("\n sizeof st_rdev = %d", (int)sizeof(rdev)); + printf("\n sizeof st_ino = %d", (int)sizeof(ino)); + printf("\n sizeof mode_t = %d", (int)sizeof(mode_t)); + printf("\n sizeof nlink_t = %d", (int)sizeof(nlink_t)); + printf("\n sizeof uid_t = %d", (int)sizeof(uid_t)); + printf("\n"); + */ + /* + printf("\n st_rdev = %llx", (long long)rdev); + printf("\n st_dev = %llx", (long long)dev); + printf("\n dev : major = %5x minor = %5x", (unsigned)major(dev), (unsigned)minor(dev)); + printf("\n st_ino = %lld", (long long)(ino)); + printf("\n rdev : major = %5x minor = %5x", (unsigned)major(rdev), (unsigned)minor(rdev)); + printf("\n size = %lld \n", (long long)(Size)); + printf("\n"); + */ +} + +/* +int Uid_To_Uname(uid_t uid, AString &name) +{ + name.Empty(); + struct passwd *passwd; + + if (uid != 0 && uid == cached_no_such_uid) + { + *uname = xstrdup (""); + return; + } + + if (!cached_uname || uid != cached_uid) + { + passwd = getpwuid (uid); + if (passwd) + { + cached_uid = uid; + assign_string (&cached_uname, passwd->pw_name); + } + else + { + cached_no_such_uid = uid; + *uname = xstrdup (""); + return; + } + } + *uname = xstrdup (cached_uname); +} +*/ + +bool CFileInfo::Find_DontFill_Name(CFSTR path, bool followLink) +{ + struct stat st; + if (MY_lstat(path, &st, followLink) != 0) + return false; + // printf("\nFind_DontFill_Name : name=%s\n", path); + SetFrom_stat(st); + return true; +} + + +bool CFileInfo::Find(CFSTR path, bool followLink) +{ + // printf("\nCEnumerator::Find() name = %s\n", path); + if (!Find_DontFill_Name(path, followLink)) + return false; + + // printf("\nOK\n"); + + Name = Get_Name_from_Path(path); + if (!Name.IsEmpty()) + { + char c = Name.Back(); + if (IS_PATH_SEPAR(c)) + Name.DeleteBack(); + } + return true; +} + + +bool DoesFileExist_Raw(CFSTR name) +{ + // FIXME for symbolic links. + struct stat st; + if (MY_lstat(name, &st, false) != 0) + return false; + return !S_ISDIR(st.st_mode); +} + +bool DoesFileExist_FollowLink(CFSTR name) +{ + // FIXME for symbolic links. + struct stat st; + if (MY_lstat(name, &st, true) != 0) + return false; + return !S_ISDIR(st.st_mode); +} + +bool DoesDirExist(CFSTR name, bool followLink) +{ + struct stat st; + if (MY_lstat(name, &st, followLink) != 0) + return false; + return S_ISDIR(st.st_mode); +} + +bool DoesFileOrDirExist(CFSTR name) +{ + struct stat st; + if (MY_lstat(name, &st, false) != 0) + return false; + return true; +} + + +CEnumerator::~CEnumerator() +{ + if (_dir) + closedir(_dir); +} + +void CEnumerator::SetDirPrefix(const FString &dirPrefix) +{ + _wildcard = dirPrefix; +} + +bool CDirEntry::IsDots() const throw() +{ + /* some systems (like CentOS 7.x on XFS) have (Type == DT_UNKNOWN) + we can call fstatat() for that case, but we use only (Name) check here */ + +#if !defined(_AIX) && !defined(__sun) && !defined(__QNXNTO__) + if (Type != DT_DIR && Type != DT_UNKNOWN) + return false; +#endif + + return Name.Len() != 0 + && Name.Len() <= 2 + && Name[0] == '.' + && (Name.Len() == 1 || Name[1] == '.'); +} + + +bool CEnumerator::NextAny(CDirEntry &fi, bool &found) +{ + found = false; + + if (!_dir) + { + const char *w = "./"; + if (!_wildcard.IsEmpty()) + w = _wildcard.Ptr(); + _dir = ::opendir((const char *)w); + if (_dir == NULL) + return false; + } + + // To distinguish end of stream from an error, we must set errno to zero before readdir() + errno = 0; + + struct dirent *de = readdir(_dir); + if (!de) + { + if (errno == 0) + return true; // it's end of stream, and we report it with (found = false) + // it's real error + return false; + } + + fi.iNode = de->d_ino; + +#if !defined(_AIX) && !defined(__sun) && !defined(__QNXNTO__) + fi.Type = de->d_type; + /* some systems (like CentOS 7.x on XFS) have (Type == DT_UNKNOWN) + we can set (Type) from fstatat() in that case. + But (Type) is not too important. So we don't set it here with slow fstatat() */ + /* + // fi.Type = DT_UNKNOWN; // for debug + if (fi.Type == DT_UNKNOWN) + { + struct stat st; + if (fstatat(dirfd(_dir), de->d_name, &st, AT_SYMLINK_NOFOLLOW) == 0) + if (S_ISDIR(st.st_mode)) + fi.Type = DT_DIR; + } + */ #endif + + /* + if (de->d_type == DT_DIR) + fi.Attrib = FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_UNIX_EXTENSION | ((UInt32)(S_IFDIR) << 16); + else if (de->d_type < 16) + fi.Attrib = FILE_ATTRIBUTE_UNIX_EXTENSION | ((UInt32)(de->d_type) << (16 + 12)); + */ + fi.Name = de->d_name; + + /* + printf("\nCEnumerator::NextAny; len = %d %s \n", (int)fi.Name.Len(), fi.Name.Ptr()); + for (unsigned i = 0; i < fi.Name.Len(); i++) + printf (" %02x", (unsigned)(Byte)de->d_name[i]); + printf("\n"); + */ + + found = true; + return true; +} + + +bool CEnumerator::Next(CDirEntry &fi, bool &found) +{ + // printf("\nCEnumerator::Next()\n"); + // PrintName("Next", ""); + for (;;) + { + if (!NextAny(fi, found)) + return false; + if (!found) + return true; + if (!fi.IsDots()) + { + /* + if (!NeedFullStat) + return true; + // we silently skip error file here - it can be wrong link item + if (fi.Find_DontFill_Name(path)) + return true; + */ + return true; + } + } +} + +/* +bool CEnumerator::Next(CDirEntry &fileInfo, bool &found) +{ + bool found; + if (!Next(fi, found)) + return false; + return found; +} +*/ + +bool CEnumerator::Fill_FileInfo(const CDirEntry &de, CFileInfo &fileInfo, bool followLink) const +{ + // printf("\nCEnumerator::Fill_FileInfo()\n"); + struct stat st; + // probably it's OK to use fstatat() even if it changes file position dirfd(_dir) + int res = fstatat(dirfd(_dir), de.Name, &st, followLink ? 0 : AT_SYMLINK_NOFOLLOW); + // if fstatat() is not supported, we can use stat() / lstat() + + /* + const FString path = _wildcard + s; + int res = MY_lstat(path, &st, followLink); + */ + + if (res != 0) + return false; + // printf("\nname=%s\n", de.Name.Ptr()); + fileInfo.SetFrom_stat(st); + fileInfo.Name = de.Name; + return true; +} + +#endif // _WIN32 }}} diff --git a/src/plugins/7zip/7za/cpp/Windows/FileFind.h b/src/plugins/7zip/7za/cpp/Windows/FileFind.h index 615c2dfe..9ae11159 100644 --- a/src/plugins/7zip/7za/cpp/Windows/FileFind.h +++ b/src/plugins/7zip/7za/cpp/Windows/FileFind.h @@ -1,15 +1,41 @@ // Windows/FileFind.h -#ifndef __WINDOWS_FILE_FIND_H -#define __WINDOWS_FILE_FIND_H +#ifndef ZIP7_INC_WINDOWS_FILE_FIND_H +#define ZIP7_INC_WINDOWS_FILE_FIND_H +#ifndef _WIN32 +#include +#include +#include +#endif + +#include "../Common/MyLinux.h" #include "../Common/MyString.h" -#include "Defs.h" +#include "../Common/MyWindows.h" + +#include "FileIO.h" namespace NWindows { namespace NFile { namespace NFind { +// bool DoesFileExist(CFSTR name, bool followLink); +bool DoesFileExist_Raw(CFSTR name); +bool DoesFileExist_FollowLink(CFSTR name); +bool DoesDirExist(CFSTR name, bool followLink); + +inline bool DoesDirExist(CFSTR name) + { return DoesDirExist(name, false); } +inline bool DoesDirExist_FollowLink(CFSTR name) + { return DoesDirExist(name, true); } + +// it's always _Raw +bool DoesFileOrDirExist(CFSTR name); + +DWORD GetFileAttrib(CFSTR path); + +#ifdef _WIN32 + namespace NAttributes { inline bool IsReadOnly(DWORD attrib) { return (attrib & FILE_ATTRIBUTE_READONLY) != 0; } @@ -19,16 +45,34 @@ namespace NAttributes inline bool IsArchived(DWORD attrib) { return (attrib & FILE_ATTRIBUTE_ARCHIVE) != 0; } inline bool IsCompressed(DWORD attrib) { return (attrib & FILE_ATTRIBUTE_COMPRESSED) != 0; } inline bool IsEncrypted(DWORD attrib) { return (attrib & FILE_ATTRIBUTE_ENCRYPTED) != 0; } + + inline UInt32 Get_PosixMode_From_WinAttrib(DWORD attrib) + { + UInt32 v = IsDir(attrib) ? MY_LIN_S_IFDIR : MY_LIN_S_IFREG; + /* 21.06: as WSL we allow write permissions (0222) for directories even for (FILE_ATTRIBUTE_READONLY). + So extracting at Linux will be allowed to write files inside (0777) directories. */ + v |= ((IsReadOnly(attrib) && !IsDir(attrib)) ? 0555 : 0777); + return v; + } } +#else + +UInt32 Get_WinAttribPosix_From_PosixMode(UInt32 mode); + +#endif + class CFileInfoBase { + #ifdef _WIN32 bool MatchesMask(UINT32 mask) const { return ((Attrib & mask) != 0); } + #endif public: UInt64 Size; - FILETIME CTime; - FILETIME ATime; - FILETIME MTime; + CFiTime CTime; + CFiTime ATime; + CFiTime MTime; + #ifdef _WIN32 DWORD Attrib; bool IsAltStream; bool IsDevice; @@ -40,11 +84,25 @@ class CFileInfoBase UINT32 ReparseTag; #endif */ + #else + dev_t dev; /* ID of device containing file */ + ino_t ino; + mode_t mode; + nlink_t nlink; + uid_t uid; /* user ID of owner */ + gid_t gid; /* group ID of owner */ + dev_t rdev; /* device ID (defined, if S_ISCHR(mode) || S_ISBLK(mode)) */ + // bool Use_lstat; + #endif CFileInfoBase() { ClearBase(); } void ClearBase() throw(); + bool SetAs_StdInFile(); - void SetAsDir() { Attrib = FILE_ATTRIBUTE_DIRECTORY; } + #ifdef _WIN32 + + bool Fill_From_ByHandleFileInfo(CFSTR path); + void SetAsDir() { Attrib = FILE_ATTRIBUTE_DIRECTORY; } // |= (FILE_ATTRIBUTE_UNIX_EXTENSION + (S_IFDIR << 16)); bool IsArchived() const { return MatchesMask(FILE_ATTRIBUTE_ARCHIVE); } bool IsCompressed() const { return MatchesMask(FILE_ATTRIBUTE_COMPRESSED); } @@ -58,6 +116,43 @@ class CFileInfoBase bool IsSparse() const { return MatchesMask(FILE_ATTRIBUTE_SPARSE_FILE); } bool IsSystem() const { return MatchesMask(FILE_ATTRIBUTE_SYSTEM); } bool IsTemporary() const { return MatchesMask(FILE_ATTRIBUTE_TEMPORARY); } + + UInt32 GetWinAttrib() const { return Attrib; } + UInt32 GetPosixAttrib() const + { + return NAttributes::Get_PosixMode_From_WinAttrib(Attrib); + } + bool Has_Attrib_ReparsePoint() const { return (Attrib & FILE_ATTRIBUTE_REPARSE_POINT) != 0; } + + #else + + UInt32 GetPosixAttrib() const { return mode; } + UInt32 GetWinAttrib() const { return Get_WinAttribPosix_From_PosixMode(mode); } + + bool IsDir() const { return S_ISDIR(mode); } + void SetAsDir() { mode = S_IFDIR | 0777; } + void SetFrom_stat(const struct stat &st); + + bool IsReadOnly() const + { + // does linux support writing to ReadOnly files? + if ((mode & 0222) == 0) // S_IWUSR in p7zip + return true; + return false; + } + + bool IsPosixLink() const { return S_ISLNK(mode); } + + #endif + + bool IsOsSymLink() const + { + #ifdef _WIN32 + return HasReparsePoint(); + #else + return IsPosixLink(); + #endif + } }; struct CFileInfo: public CFileInfoBase @@ -68,10 +163,21 @@ struct CFileInfo: public CFileInfoBase #endif bool IsDots() const throw(); - bool Find(CFSTR path); + bool Find(CFSTR path, bool followLink = false); + bool Find_FollowLink(CFSTR path) { return Find(path, true); } + + #ifdef _WIN32 + // bool Fill_From_ByHandleFileInfo(CFSTR path); + // bool FollowReparse(CFSTR path, bool isDir); + #else + bool Find_DontFill_Name(CFSTR path, bool followLink = false); + #endif }; -class CFindFileBase + +#ifdef _WIN32 + +class CFindFileBase MY_UNCOPYABLE { protected: HANDLE _handle; @@ -108,43 +214,47 @@ class CFindStream: public CFindFileBase bool FindNext(CStreamInfo &streamInfo); }; -class CStreamEnumerator +class CStreamEnumerator MY_UNCOPYABLE { CFindStream _find; FString _filePath; - bool NextAny(CFileInfo &fileInfo); + bool NextAny(CFileInfo &fileInfo, bool &found); public: CStreamEnumerator(const FString &filePath): _filePath(filePath) {} bool Next(CStreamInfo &streamInfo, bool &found); }; -#endif +#endif // defined(_WIN32) && !defined(UNDER_CE) -bool DoesFileExist(CFSTR name); -bool DoesDirExist(CFSTR name); -bool DoesFileOrDirExist(CFSTR name); - -DWORD GetFileAttrib(CFSTR path); -class CEnumerator +class CEnumerator MY_UNCOPYABLE { CFindFile _findFile; FString _wildcard; bool NextAny(CFileInfo &fileInfo); public: - CEnumerator(const FString &wildcard): _wildcard(wildcard) {} + void SetDirPrefix(const FString &dirPrefix); bool Next(CFileInfo &fileInfo); bool Next(CFileInfo &fileInfo, bool &found); }; -class CFindChangeNotification + +class CFindChangeNotification MY_UNCOPYABLE { HANDLE _handle; public: operator HANDLE () { return _handle; } - bool IsHandleAllocated() const { return _handle != INVALID_HANDLE_VALUE && _handle != 0; } + bool IsHandleAllocated() const + { + /* at least on win2000/XP (undocumented): + if pathName is "" or NULL, + FindFirstChangeNotification() could return NULL. + So we check for INVALID_HANDLE_VALUE and NULL. + */ + return _handle != INVALID_HANDLE_VALUE && _handle != NULL; + } CFindChangeNotification(): _handle(INVALID_HANDLE_VALUE) {} ~CFindChangeNotification() { Close(); } bool Close() throw(); @@ -156,6 +266,80 @@ class CFindChangeNotification bool MyGetLogicalDriveStrings(CObjectVector &driveStrings); #endif +typedef CFileInfo CDirEntry; + + +#else // WIN32 + + +struct CDirEntry +{ + ino_t iNode; +#if !defined(_AIX) && !defined(__sun) && !defined(__QNXNTO__) + Byte Type; +#endif + FString Name; + + /* +#if !defined(_AIX) && !defined(__sun) && !defined(__QNXNTO__) + bool IsDir() const + { + // (Type == DT_UNKNOWN) on some systems + return Type == DT_DIR; + } +#endif + */ + + bool IsDots() const throw(); +}; + +class CEnumerator MY_UNCOPYABLE +{ + DIR *_dir; + FString _wildcard; + + bool NextAny(CDirEntry &fileInfo, bool &found); +public: + CEnumerator(): _dir(NULL) {} + ~CEnumerator(); + void SetDirPrefix(const FString &dirPrefix); + + bool Next(CDirEntry &fileInfo, bool &found); + bool Fill_FileInfo(const CDirEntry &de, CFileInfo &fileInfo, bool followLink) const; + bool DirEntry_IsDir(const CDirEntry &de, bool followLink) const + { +#if !defined(_AIX) && !defined(__sun) && !defined(__QNXNTO__) + if (de.Type == DT_DIR) + return true; + if (de.Type != DT_UNKNOWN) + return false; +#endif + CFileInfo fileInfo; + if (Fill_FileInfo(de, fileInfo, followLink)) + { + return fileInfo.IsDir(); + } + return false; // change it + } +}; + +/* +inline UInt32 Get_WinAttrib_From_PosixMode(UInt32 mode) +{ + UInt32 attrib = S_ISDIR(mode) ? + FILE_ATTRIBUTE_DIRECTORY : + FILE_ATTRIBUTE_ARCHIVE; + if ((st.st_mode & 0222) == 0) // check it !!! + attrib |= FILE_ATTRIBUTE_READONLY; + return attrib; +} +*/ + +// UInt32 Get_WinAttrib_From_stat(const struct stat &st); + + +#endif // WIN32 + }}} #endif diff --git a/src/plugins/7zip/7za/cpp/Windows/FileIO.cpp b/src/plugins/7zip/7za/cpp/Windows/FileIO.cpp index 56e6ca45..dc4de14d 100644 --- a/src/plugins/7zip/7za/cpp/Windows/FileIO.cpp +++ b/src/plugins/7zip/7za/cpp/Windows/FileIO.cpp @@ -2,13 +2,33 @@ #include "StdAfx.h" -#ifdef SUPPORT_DEVICE_FILE +#ifdef Z7_DEVICE_FILE #include "../../C/Alloc.h" #endif +// #include + +/* +#ifndef _WIN32 +// for ioctl BLKGETSIZE64 +#include +#include +#endif +*/ + #include "FileIO.h" #include "FileName.h" +HRESULT GetLastError_noZero_HRESULT() +{ + const DWORD res = ::GetLastError(); + if (res == 0) + return E_FAIL; + return HRESULT_FROM_WIN32(res); +} + +#ifdef _WIN32 + #ifndef _UNICODE extern bool g_IsNT; #endif @@ -20,7 +40,7 @@ using namespace NName; namespace NWindows { namespace NFile { -#ifdef SUPPORT_DEVICE_FILE +#ifdef Z7_DEVICE_FILE namespace NSystem { @@ -52,7 +72,7 @@ bool CFileBase::Create(CFSTR path, DWORD desiredAccess, if (!Close()) return false; - #ifdef SUPPORT_DEVICE_FILE + #ifdef Z7_DEVICE_FILE IsDeviceFile = false; #endif @@ -68,7 +88,7 @@ bool CFileBase::Create(CFSTR path, DWORD desiredAccess, IF_USE_MAIN_PATH _handle = ::CreateFileW(fs2us(path), desiredAccess, shareMode, (LPSECURITY_ATTRIBUTES)NULL, creationDisposition, flagsAndAttributes, (HANDLE)NULL); - #ifdef WIN_LONG_PATH + #ifdef Z7_LONG_PATH if (_handle == INVALID_HANDLE_VALUE && USE_SUPER_PATH) { UString superPath; @@ -78,6 +98,42 @@ bool CFileBase::Create(CFSTR path, DWORD desiredAccess, } #endif } + + /* + #ifndef UNDER_CE + #ifndef Z7_SFX + if (_handle == INVALID_HANDLE_VALUE) + { + // it's debug hack to open symbolic links in Windows XP and WSL links in Windows 10 + DWORD lastError = GetLastError(); + if (lastError == ERROR_CANT_ACCESS_FILE) + { + CByteBuffer buf; + if (NIO::GetReparseData(path, buf, NULL)) + { + CReparseAttr attr; + if (attr.Parse(buf, buf.Size())) + { + FString dirPrefix, fileName; + if (NDir::GetFullPathAndSplit(path, dirPrefix, fileName)) + { + FString fullPath; + if (GetFullPath(dirPrefix, us2fs(attr.GetPath()), fullPath)) + { + // FIX IT: recursion levels must be restricted + return Create(fullPath, desiredAccess, + shareMode, creationDisposition, flagsAndAttributes); + } + } + } + } + SetLastError(lastError); + } + } + #endif + #endif + */ + return (_handle != INVALID_HANDLE_VALUE); } @@ -85,20 +141,24 @@ bool CFileBase::Close() throw() { if (_handle == INVALID_HANDLE_VALUE) return true; - if (!::CloseHandle(_handle)) - return false; +#if 0 + if (!IsStdStream) +#endif + { + if (!::CloseHandle(_handle)) + return false; + } +#if 0 + IsStdStream = false; + IsStdPipeStream = false; +#endif _handle = INVALID_HANDLE_VALUE; return true; } -bool CFileBase::GetPosition(UInt64 &position) const throw() -{ - return Seek(0, FILE_CURRENT, position); -} - bool CFileBase::GetLength(UInt64 &length) const throw() { - #ifdef SUPPORT_DEVICE_FILE + #ifdef Z7_DEVICE_FILE if (IsDeviceFile && SizeDefined) { length = Size; @@ -106,18 +166,69 @@ bool CFileBase::GetLength(UInt64 &length) const throw() } #endif - DWORD sizeHigh; - DWORD sizeLow = ::GetFileSize(_handle, &sizeHigh); - if (sizeLow == 0xFFFFFFFF) + DWORD high = 0; + const DWORD low = ::GetFileSize(_handle, &high); + if (low == INVALID_FILE_SIZE) if (::GetLastError() != NO_ERROR) return false; - length = (((UInt64)sizeHigh) << 32) + sizeLow; + length = (((UInt64)high) << 32) + low; + return true; + + /* + LARGE_INTEGER fileSize; + // GetFileSizeEx() is unsupported in 98/ME/NT, and supported in Win2000+ + if (!GetFileSizeEx(_handle, &fileSize)) + return false; + length = (UInt64)fileSize.QuadPart; return true; + */ +} + + +/* Specification for SetFilePointer(): + + If a new file pointer is a negative value, + { + the function fails, + the file pointer is not moved, + the code returned by GetLastError() is ERROR_NEGATIVE_SEEK. + } + + If the hFile handle is opened with the FILE_FLAG_NO_BUFFERING flag set + { + an application can move the file pointer only to sector-aligned positions. + A sector-aligned position is a position that is a whole number multiple of + the volume sector size. + An application can obtain a volume sector size by calling the GetDiskFreeSpace. + } + + It is not an error to set a file pointer to a position beyond the end of the file. + The size of the file does not increase until you call the SetEndOfFile, WriteFile, or WriteFileEx function. + + If the return value is INVALID_SET_FILE_POINTER and if lpDistanceToMoveHigh is non-NULL, + an application must call GetLastError to determine whether or not the function has succeeded or failed. +*/ + +bool CFileBase::GetPosition(UInt64 &position) const throw() +{ + LONG high = 0; + const DWORD low = ::SetFilePointer(_handle, 0, &high, FILE_CURRENT); + if (low == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR) + { + // for error case we can set (position) to (-1) or (0) or leave (position) unchanged. + // position = (UInt64)(Int64)-1; // for debug + position = 0; + return false; + } + position = (((UInt64)(UInt32)high) << 32) + low; + return true; + // we don't want recursed GetPosition() + // return Seek(0, FILE_CURRENT, position); } bool CFileBase::Seek(Int64 distanceToMove, DWORD moveMethod, UInt64 &newPosition) const throw() { - #ifdef SUPPORT_DEVICE_FILE + #ifdef Z7_DEVICE_FILE if (IsDeviceFile && SizeDefined && moveMethod == FILE_END) { distanceToMove += Size; @@ -126,23 +237,31 @@ bool CFileBase::Seek(Int64 distanceToMove, DWORD moveMethod, UInt64 &newPosition #endif LONG high = (LONG)(distanceToMove >> 32); - DWORD low = ::SetFilePointer(_handle, (LONG)(distanceToMove & 0xFFFFFFFF), &high, moveMethod); - if (low == 0xFFFFFFFF) - if (::GetLastError() != NO_ERROR) + const DWORD low = ::SetFilePointer(_handle, (LONG)(distanceToMove & 0xFFFFFFFF), &high, moveMethod); + if (low == INVALID_SET_FILE_POINTER) + { + const DWORD lastError = ::GetLastError(); + if (lastError != NO_ERROR) + { + // 21.07: we set (newPosition) to real position even after error. + GetPosition(newPosition); + SetLastError(lastError); // restore LastError return false; + } + } newPosition = (((UInt64)(UInt32)high) << 32) + low; return true; } bool CFileBase::Seek(UInt64 position, UInt64 &newPosition) const throw() { - return Seek(position, FILE_BEGIN, newPosition); + return Seek((Int64)position, FILE_BEGIN, newPosition); } bool CFileBase::SeekToBegin() const throw() { - UInt64 newPosition; - return Seek(0, newPosition); + UInt64 newPosition = 0; + return Seek(0, newPosition) && (newPosition == 0); } bool CFileBase::SeekToEnd(UInt64 &newPosition) const throw() @@ -152,12 +271,12 @@ bool CFileBase::SeekToEnd(UInt64 &newPosition) const throw() // ---------- CInFile --------- -#ifdef SUPPORT_DEVICE_FILE +#ifdef Z7_DEVICE_FILE void CInFile::CorrectDeviceSize() { // maybe we must decrease kClusterSize to 1 << 12, if we want correct size at tail - static const UInt32 kClusterSize = 1 << 14; + const UInt32 kClusterSize = 1 << 14; UInt64 pos = Size & ~(UInt64)(kClusterSize - 1); UInt64 realNewPosition; if (!Seek(pos, realNewPosition)) @@ -258,7 +377,7 @@ void CInFile::CalcDeviceSize(CFSTR s) if (GetPartitionInfo(&partInfo)) { - Size = partInfo.PartitionLength.QuadPart; + Size = (UInt64)partInfo.PartitionLength.QuadPart; SizeDefined = true; needCorrectSize = false; if ((s)[0] == '\\' && (s)[1] == '\\' && (s)[2] == '.' && (s)[3] == '\\' && (s)[5] == ':' && (s)[6] == 0) @@ -277,7 +396,7 @@ void CInFile::CalcDeviceSize(CFSTR s) my_DISK_GEOMETRY_EX geomEx; SizeDefined = GetGeometryEx(&geomEx); if (SizeDefined) - Size = geomEx.DiskSize.QuadPart; + Size = (UInt64)geomEx.DiskSize.QuadPart; else { DISK_GEOMETRY geom; @@ -285,7 +404,7 @@ void CInFile::CalcDeviceSize(CFSTR s) if (!SizeDefined) SizeDefined = GetCdRomGeometry(&geom); if (SizeDefined) - Size = geom.Cylinders.QuadPart * geom.TracksPerCylinder * geom.SectorsPerTrack * geom.BytesPerSector; + Size = (UInt64)geom.Cylinders.QuadPart * geom.TracksPerCylinder * geom.SectorsPerTrack * geom.BytesPerSector; } } @@ -310,7 +429,24 @@ void CInFile::CalcDeviceSize(CFSTR s) bool CInFile::Open(CFSTR fileName, DWORD shareMode, DWORD creationDisposition, DWORD flagsAndAttributes) { - bool res = Create(fileName, GENERIC_READ, shareMode, creationDisposition, flagsAndAttributes); + DWORD desiredAccess = GENERIC_READ; + + #ifdef _WIN32 + if (PreserveATime) + desiredAccess |= FILE_WRITE_ATTRIBUTES; + #endif + + bool res = Create(fileName, desiredAccess, shareMode, creationDisposition, flagsAndAttributes); + + #ifdef _WIN32 + if (res && PreserveATime) + { + FILETIME ft; + ft.dwHighDateTime = ft.dwLowDateTime = 0xFFFFFFFF; + ::SetFileTime(_handle, NULL, &ft, NULL); + } + #endif + MY_DEVICE_EXTRA_CODE return res; } @@ -330,20 +466,26 @@ bool CInFile::Open(CFSTR fileName) // for 32 MB (maybe also for 16 MB). // And message can be "Network connection was lost" -static UInt32 kChunkSizeMax = (1 << 22); +static const UInt32 kChunkSizeMax = 1 << 22; bool CInFile::Read1(void *data, UInt32 size, UInt32 &processedSize) throw() { DWORD processedLoc = 0; - bool res = BOOLToBool(::ReadFile(_handle, data, size, &processedLoc, NULL)); + const bool res = BOOLToBool(::ReadFile(_handle, data, size, &processedLoc, NULL)); processedSize = (UInt32)processedLoc; return res; } bool CInFile::ReadPart(void *data, UInt32 size, UInt32 &processedSize) throw() { +#if 0 + const UInt32 chunkSizeMax = (0 || IsStdStream) ? (1 << 20) : kChunkSizeMax; + if (size > chunkSizeMax) + size = chunkSizeMax; +#else if (size > kChunkSizeMax) - size = kChunkSizeMax; + size = kChunkSizeMax; +#endif return Read1(data, size, processedSize); } @@ -353,35 +495,49 @@ bool CInFile::Read(void *data, UInt32 size, UInt32 &processedSize) throw() do { UInt32 processedLoc = 0; - bool res = ReadPart(data, size, processedLoc); + const bool res = ReadPart(data, size, processedLoc); processedSize += processedLoc; if (!res) return false; if (processedLoc == 0) return true; - data = (void *)((unsigned char *)data + processedLoc); + data = (void *)((Byte *)data + processedLoc); size -= processedLoc; } - while (size > 0); + while (size); return true; } -// ---------- COutFile --------- +bool CInFile::ReadFull(void *data, size_t size, size_t &processedSize) throw() +{ + processedSize = 0; + do + { + UInt32 processedLoc = 0; + const UInt32 sizeLoc = (size > kChunkSizeMax ? (UInt32)kChunkSizeMax : (UInt32)size); + const bool res = Read1(data, sizeLoc, processedLoc); + processedSize += processedLoc; + if (!res) + return false; + if (processedLoc == 0) + return true; + data = (void *)((Byte *)data + processedLoc); + size -= processedLoc; + } + while (size); + return true; +} -static inline DWORD GetCreationDisposition(bool createAlways) - { return createAlways? CREATE_ALWAYS: CREATE_NEW; } +// ---------- COutFile --------- bool COutFile::Open(CFSTR fileName, DWORD shareMode, DWORD creationDisposition, DWORD flagsAndAttributes) { return CFileBase::Create(fileName, GENERIC_WRITE, shareMode, creationDisposition, flagsAndAttributes); } -bool COutFile::Open(CFSTR fileName, DWORD creationDisposition) +bool COutFile::Open_Disposition(CFSTR fileName, DWORD creationDisposition) { return Open(fileName, FILE_SHARE_READ, creationDisposition, FILE_ATTRIBUTE_NORMAL); } -bool COutFile::Create(CFSTR fileName, bool createAlways) - { return Open(fileName, GetCreationDisposition(createAlways)); } - -bool COutFile::CreateAlways(CFSTR fileName, DWORD flagsAndAttributes) - { return Open(fileName, FILE_SHARE_READ, GetCreationDisposition(true), flagsAndAttributes); } +bool COutFile::Create_ALWAYS_with_Attribs(CFSTR fileName, DWORD flagsAndAttributes) + { return Open(fileName, FILE_SHARE_READ, CREATE_ALWAYS, flagsAndAttributes); } bool COutFile::SetTime(const FILETIME *cTime, const FILETIME *aTime, const FILETIME *mTime) throw() { return BOOLToBool(::SetFileTime(_handle, cTime, aTime, mTime)); } @@ -404,16 +560,33 @@ bool COutFile::Write(const void *data, UInt32 size, UInt32 &processedSize) throw do { UInt32 processedLoc = 0; - bool res = WritePart(data, size, processedLoc); + const bool res = WritePart(data, size, processedLoc); processedSize += processedLoc; if (!res) return false; if (processedLoc == 0) return true; - data = (const void *)((const unsigned char *)data + processedLoc); + data = (const void *)((const Byte *)data + processedLoc); size -= processedLoc; } - while (size > 0); + while (size); + return true; +} + +bool COutFile::WriteFull(const void *data, size_t size) throw() +{ + do + { + UInt32 processedLoc = 0; + const UInt32 sizeCur = (size > kChunkSizeMax ? kChunkSizeMax : (UInt32)size); + if (!WritePart(data, sizeCur, processedLoc)) + return false; + if (processedLoc == 0) + return (size == 0); + data = (const void *)((const Byte *)data + processedLoc); + size -= processedLoc; + } + while (size); return true; } @@ -429,4 +602,353 @@ bool COutFile::SetLength(UInt64 length) throw() return SetEndOfFile(); } +bool COutFile::SetLength_KeepPosition(UInt64 length) throw() +{ + UInt64 currentPos = 0; + if (!GetPosition(currentPos)) + return false; + DWORD lastError = 0; + const bool result = SetLength(length); + if (!result) + lastError = GetLastError(); + UInt64 currentPos2; + const bool result2 = Seek(currentPos, currentPos2); + if (lastError != 0) + SetLastError(lastError); + return (result && result2); +} + +}}} + +#else // _WIN32 + + +// POSIX + +#include +#include + +namespace NWindows { +namespace NFile { + +namespace NDir { +bool SetDirTime(CFSTR path, const CFiTime *cTime, const CFiTime *aTime, const CFiTime *mTime); +} + +namespace NIO { + +bool CFileBase::OpenBinary(const char *name, int flags, mode_t mode) +{ + #ifdef O_BINARY + flags |= O_BINARY; + #endif + + Close(); + _handle = ::open(name, flags, mode); + return _handle != -1; + + /* + if (_handle == -1) + return false; + if (IsString1PrefixedByString2(name, "/dev/")) + { + // /dev/sda + // IsDeviceFile = true; // for debug + // SizeDefined = false; + // SizeDefined = (GetDeviceSize_InBytes(Size) == 0); + } + return true; + */ +} + +bool CFileBase::Close() +{ + if (_handle == -1) + return true; + if (close(_handle) != 0) + return false; + _handle = -1; + /* + IsDeviceFile = false; + SizeDefined = false; + */ + return true; +} + +bool CFileBase::GetLength(UInt64 &length) const +{ + length = 0; + // length = (UInt64)(Int64)-1; // for debug + const off_t curPos = seekToCur(); + if (curPos == -1) + return false; + const off_t lengthTemp = seek(0, SEEK_END); + seek(curPos, SEEK_SET); + length = (UInt64)lengthTemp; + + /* + // 22.00: + if (lengthTemp == 1) + if (IsDeviceFile && SizeDefined) + { + length = Size; + return true; + } + */ + + return (lengthTemp != -1); +} + +off_t CFileBase::seek(off_t distanceToMove, int moveMethod) const +{ + /* + if (IsDeviceFile && SizeDefined && moveMethod == SEEK_END) + { + printf("\n seek : IsDeviceFile moveMethod = %d distanceToMove = %ld\n", moveMethod, distanceToMove); + distanceToMove += Size; + moveMethod = SEEK_SET; + } + */ + + // printf("\nCFileBase::seek() moveMethod = %d, distanceToMove = %lld", moveMethod, (long long)distanceToMove); + // off_t res = ::lseek(_handle, distanceToMove, moveMethod); + // printf("\n lseek : moveMethod = %d distanceToMove = %ld\n", moveMethod, distanceToMove); + return ::lseek(_handle, distanceToMove, moveMethod); + // return res; +} + +off_t CFileBase::seekToBegin() const throw() +{ + return seek(0, SEEK_SET); +} + +off_t CFileBase::seekToCur() const throw() +{ + return seek(0, SEEK_CUR); +} + +/* +bool CFileBase::SeekToBegin() const throw() +{ + return (::seek(0, SEEK_SET) != -1); +} +*/ + + +///////////////////////// +// CInFile + +bool CInFile::Open(const char *name) +{ + return CFileBase::OpenBinary(name, O_RDONLY); +} + +bool CInFile::OpenShared(const char *name, bool) +{ + return Open(name); +} + + +/* +int CFileBase::my_ioctl_BLKGETSIZE64(unsigned long long *numBlocks) +{ + // we can read "/sys/block/sda/size" "/sys/block/sda/sda1/size" - partition + // #include + return ioctl(_handle, BLKGETSIZE64, numBlocks); + // in block size +} + +int CFileBase::GetDeviceSize_InBytes(UInt64 &size) +{ + size = 0; + unsigned long long numBlocks; + int res = my_ioctl_BLKGETSIZE64(&numBlocks); + if (res == 0) + size = numBlocks; // another blockSize s possible? + printf("\nGetDeviceSize_InBytes res = %d, size = %lld\n", res, (long long)size); + return res; +} +*/ + +/* +On Linux (32-bit and 64-bit): +read(), write() (and similar system calls) will transfer at most +0x7ffff000 = (2GiB - 4 KiB) bytes, returning the number of bytes actually transferred. +*/ + +static const size_t kChunkSizeMax = ((size_t)1 << 22); + +ssize_t CInFile::read_part(void *data, size_t size) throw() +{ + if (size > kChunkSizeMax) + size = kChunkSizeMax; + return ::read(_handle, data, size); +} + +bool CInFile::ReadFull(void *data, size_t size, size_t &processed) throw() +{ + processed = 0; + do + { + const ssize_t res = read_part(data, size); + if (res < 0) + return false; + if (res == 0) + break; + data = (void *)((Byte *)data + (size_t)res); + processed += (size_t)res; + size -= (size_t)res; + } + while (size); + return true; +} + + +///////////////////////// +// COutFile + +bool COutFile::OpenBinary_forWrite_oflag(const char *name, int oflag) +{ + Path = name; // change it : set it only if open is success. + return OpenBinary(name, oflag, mode_for_Create); +} + + +/* + windows exist non-exist posix + CREATE_NEW Fail Create O_CREAT | O_EXCL + CREATE_ALWAYS Trunc Create O_CREAT | O_TRUNC + OPEN_ALWAYS Open Create O_CREAT + OPEN_EXISTING Open Fail 0 + TRUNCATE_EXISTING Trunc Fail O_TRUNC ??? + + // O_CREAT = If the file exists, this flag has no effect except as noted under O_EXCL below. + // If O_CREAT and O_EXCL are set, open() shall fail if the file exists. + // O_TRUNC : If the file exists and the file is successfully opened, its length shall be truncated to 0. +*/ +bool COutFile::Open_EXISTING(const char *name) + { return OpenBinary_forWrite_oflag(name, O_WRONLY); } +bool COutFile::Create_ALWAYS(const char *name) + { return OpenBinary_forWrite_oflag(name, O_WRONLY | O_CREAT | O_TRUNC); } +bool COutFile::Create_NEW(const char *name) + { return OpenBinary_forWrite_oflag(name, O_WRONLY | O_CREAT | O_EXCL); } +bool COutFile::Create_ALWAYS_or_Open_ALWAYS(const char *name, bool createAlways) +{ + return OpenBinary_forWrite_oflag(name, + createAlways ? + O_WRONLY | O_CREAT | O_TRUNC : + O_WRONLY | O_CREAT); +} +/* +bool COutFile::Create_ALWAYS_or_NEW(const char *name, bool createAlways) +{ + return OpenBinary_forWrite_oflag(name, + createAlways ? + O_WRONLY | O_CREAT | O_TRUNC : + O_WRONLY | O_CREAT | O_EXCL); +} +bool COutFile::Open_Disposition(const char *name, DWORD creationDisposition) +{ + int flag; + switch (creationDisposition) + { + case CREATE_NEW: flag = O_WRONLY | O_CREAT | O_EXCL; break; + case CREATE_ALWAYS: flag = O_WRONLY | O_CREAT | O_TRUNC; break; + case OPEN_ALWAYS: flag = O_WRONLY | O_CREAT; break; + case OPEN_EXISTING: flag = O_WRONLY; break; + case TRUNCATE_EXISTING: flag = O_WRONLY | O_TRUNC; break; + default: + SetLastError(EINVAL); + return false; + } + return OpenBinary_forWrite_oflag(name, flag); +} +*/ + +ssize_t COutFile::write_part(const void *data, size_t size) throw() +{ + if (size > kChunkSizeMax) + size = kChunkSizeMax; + return ::write(_handle, data, size); +} + +ssize_t COutFile::write_full(const void *data, size_t size, size_t &processed) throw() +{ + processed = 0; + do + { + const ssize_t res = write_part(data, size); + if (res < 0) + return res; + if (res == 0) + break; + data = (const void *)((const Byte *)data + (size_t)res); + processed += (size_t)res; + size -= (size_t)res; + } + while (size); + return (ssize_t)processed; +} + +bool COutFile::SetLength(UInt64 length) throw() +{ + const off_t len2 = (off_t)length; + if ((Int64)length != len2) + { + SetLastError(EFBIG); + return false; + } + // The value of the seek pointer shall not be modified by a call to ftruncate(). + const int iret = ftruncate(_handle, len2); + return (iret == 0); +} + +bool COutFile::Close() +{ + const bool res = CFileBase::Close(); + if (!res) + return res; + if (CTime_defined || ATime_defined || MTime_defined) + { + /* bool res2 = */ NWindows::NFile::NDir::SetDirTime(Path, + CTime_defined ? &CTime : NULL, + ATime_defined ? &ATime : NULL, + MTime_defined ? &MTime : NULL); + } + return res; +} + +bool COutFile::SetTime(const CFiTime *cTime, const CFiTime *aTime, const CFiTime *mTime) throw() +{ + // On some OS (cygwin, MacOSX ...), you must close the file before updating times + // return true; + + if (cTime) { CTime = *cTime; CTime_defined = true; } else CTime_defined = false; + if (aTime) { ATime = *aTime; ATime_defined = true; } else ATime_defined = false; + if (mTime) { MTime = *mTime; MTime_defined = true; } else MTime_defined = false; + return true; + + /* + struct timespec times[2]; + UNUSED_VAR(cTime) + if (!aTime && !mTime) + return true; + bool needChange; + needChange = FiTime_To_timespec(aTime, times[0]); + needChange |= FiTime_To_timespec(mTime, times[1]); + if (!needChange) + return true; + return futimens(_handle, times) == 0; + */ +} + +bool COutFile::SetMTime(const CFiTime *mTime) throw() +{ + if (mTime) { MTime = *mTime; MTime_defined = true; } else MTime_defined = false; + return true; +} + }}} + + +#endif diff --git a/src/plugins/7zip/7za/cpp/Windows/FileIO.h b/src/plugins/7zip/7za/cpp/Windows/FileIO.h index 32c285fb..a202df5d 100644 --- a/src/plugins/7zip/7za/cpp/Windows/FileIO.h +++ b/src/plugins/7zip/7za/cpp/Windows/FileIO.h @@ -1,32 +1,75 @@ // Windows/FileIO.h -#ifndef __WINDOWS_FILE_IO_H -#define __WINDOWS_FILE_IO_H +#ifndef ZIP7_INC_WINDOWS_FILE_IO_H +#define ZIP7_INC_WINDOWS_FILE_IO_H #include "../Common/MyWindows.h" +#define Z7_WIN_IO_REPARSE_TAG_MOUNT_POINT (0xA0000003L) +#define Z7_WIN_IO_REPARSE_TAG_SYMLINK (0xA000000CL) +#define Z7_WIN_IO_REPARSE_TAG_LX_SYMLINK (0xA000001DL) + +#define Z7_WIN_SYMLINK_FLAG_RELATIVE 1 + +#define Z7_WIN_LX_SYMLINK_VERSION_2 2 + +#ifdef _WIN32 + #if defined(_WIN32) && !defined(UNDER_CE) #include #endif +#else + +#include +#include + +#endif + #include "../Common/MyString.h" #include "../Common/MyBuffer.h" -#include "Defs.h" +#include "../Windows/TimeUtils.h" -#define _my_IO_REPARSE_TAG_MOUNT_POINT (0xA0000003L) -#define _my_IO_REPARSE_TAG_SYMLINK (0xA000000CL) +#include "WinDefs.h" -#define _my_SYMLINK_FLAG_RELATIVE 1 +HRESULT GetLastError_noZero_HRESULT(); -#define my_FSCTL_SET_REPARSE_POINT CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 41, METHOD_BUFFERED, FILE_SPECIAL_ACCESS) // REPARSE_DATA_BUFFER -#define my_FSCTL_GET_REPARSE_POINT CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 42, METHOD_BUFFERED, FILE_ANY_ACCESS) // REPARSE_DATA_BUFFER +#define my_FSCTL_SET_REPARSE_POINT CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 41, METHOD_BUFFERED, FILE_SPECIAL_ACCESS) // REPARSE_DATA_BUFFER +#define my_FSCTL_GET_REPARSE_POINT CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 42, METHOD_BUFFERED, FILE_ANY_ACCESS) // REPARSE_DATA_BUFFER +#define my_FSCTL_DELETE_REPARSE_POINT CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 43, METHOD_BUFFERED, FILE_SPECIAL_ACCESS) // REPARSE_DATA_BUFFER namespace NWindows { namespace NFile { #if defined(_WIN32) && !defined(UNDER_CE) -bool FillLinkData(CByteBuffer &dest, const wchar_t *path, bool isSymLink); +/* + in: (CByteBuffer &dest) is empty + in: (path) uses Windows path separator (\). + out: (path) uses Linux path separator (/). + if (isAbsPath == true), then "c:\\" prefix is replaced to "/mnt/c/" prefix +*/ +void Convert_WinPath_to_WslLinuxPath(FString &path, bool convertDrivePath); +// (path) must use Linux path separator (/). +void FillLinkData_WslLink(CByteBuffer &dest, const wchar_t *path); + +/* + in: (CByteBuffer &dest) is empty + if (isSymLink == false) : MOUNT_POINT : (path) must be absolute. + if (isSymLink == true) : SYMLINK : Windows + (path) must use Windows path separator (\). + (path) must be without link "\\??\\" prefix. + link "\\??\\" prefix will be added inside FillLinkData(), if path is absolute. +*/ +void FillLinkData_WinLink(CByteBuffer &dest, const wchar_t *path, bool isSymLink); +// in: (CByteBuffer &dest) is empty +inline void FillLinkData(CByteBuffer &dest, const wchar_t *path, bool isSymLink, bool isWSL) +{ + if (isWSL) + FillLinkData_WslLink(dest, path); + else + FillLinkData_WinLink(dest, path, isSymLink); +} #endif struct CReparseShortInfo @@ -43,25 +86,51 @@ struct CReparseAttr UInt32 Flags; UString SubsName; UString PrintName; + AString WslName; + + bool HeaderError; + bool TagIsUnknown; + bool MinorError; + DWORD ErrorCode; CReparseAttr(): Tag(0), Flags(0) {} + + // returns (true) and (ErrorCode = 0), if (it's correct known link) + // returns (false) and (ErrorCode = ERROR_REPARSE_TAG_INVALID), if unknown tag bool Parse(const Byte *p, size_t size); - bool IsMountPoint() const { return Tag == _my_IO_REPARSE_TAG_MOUNT_POINT; } // it's Junction - bool IsSymLink() const { return Tag == _my_IO_REPARSE_TAG_SYMLINK; } - bool IsRelative() const { return Flags == _my_SYMLINK_FLAG_RELATIVE; } - // bool IsVolume() const; + bool IsMountPoint() const { return Tag == Z7_WIN_IO_REPARSE_TAG_MOUNT_POINT; } // it's Junction + bool IsSymLink_Win() const { return Tag == Z7_WIN_IO_REPARSE_TAG_SYMLINK; } + bool IsSymLink_WSL() const { return Tag == Z7_WIN_IO_REPARSE_TAG_LX_SYMLINK; } + + // note: "/dir1/path" is marked as relative. + bool IsRelative_Win() const { return Flags == Z7_WIN_SYMLINK_FLAG_RELATIVE; } + + bool IsRelative_WSL() const + { + return WslName[0] != '/'; // WSL uses unix path separator + } bool IsOkNamePair() const; UString GetPath() const; }; +#ifdef _WIN32 +#define CFiInfo BY_HANDLE_FILE_INFORMATION +#define ST_MTIME(st) (st).ftLastWriteTime +#else +#define CFiInfo stat +#endif + +#ifdef _WIN32 + namespace NIO { bool GetReparseData(CFSTR path, CByteBuffer &reparseData, BY_HANDLE_FILE_INFORMATION *fileInfo = NULL); bool SetReparseData(CFSTR path, bool isDir, const void *data, DWORD size); +bool DeleteReparseData(CFSTR path); -class CFileBase +class CFileBase MY_UNCOPYABLE { protected: HANDLE _handle; @@ -90,15 +159,31 @@ class CFileBase } public: - #ifdef SUPPORT_DEVICE_FILE + bool PreserveATime; +#if 0 + bool IsStdStream; + bool IsStdPipeStream; +#endif +#ifdef Z7_DEVICE_FILE bool IsDeviceFile; bool SizeDefined; UInt64 Size; // it can be larger than real available size - #endif +#endif - CFileBase(): _handle(INVALID_HANDLE_VALUE) {}; + CFileBase(): + _handle(INVALID_HANDLE_VALUE), + PreserveATime(false) +#if 0 + , IsStdStream(false), + , IsStdPipeStream(false) +#endif + {} ~CFileBase() { Close(); } + HANDLE GetHandle() const { return _handle; } + + // void Detach() { _handle = INVALID_HANDLE_VALUE; } + bool Close() throw(); bool GetPosition(UInt64 &position) const throw(); @@ -114,6 +199,7 @@ class CFileBase static bool GetFileInformation(CFSTR path, BY_HANDLE_FILE_INFORMATION *info) { + // probably it can work for complex paths: unsupported by another things NIO::CFileBase file; if (!file.Create(path, 0, FILE_SHARE_READ, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS)) return false; @@ -139,7 +225,7 @@ struct my_DISK_GEOMETRY_EX class CInFile: public CFileBase { - #ifdef SUPPORT_DEVICE_FILE + #ifdef Z7_DEVICE_FILE #ifndef UNDER_CE @@ -167,11 +253,47 @@ class CInFile: public CFileBase bool OpenShared(CFSTR fileName, bool shareForWrite); bool Open(CFSTR fileName); +#if 0 + bool AttachStdIn() + { + IsDeviceFile = false; + const HANDLE h = GetStdHandle(STD_INPUT_HANDLE); + if (h == INVALID_HANDLE_VALUE || !h) + return false; + IsStdStream = true; + IsStdPipeStream = true; + _handle = h; + return true; + } +#endif + #ifndef UNDER_CE + bool Open_for_ReadAttributes(CFSTR fileName) + { + return Create(fileName, FILE_READ_ATTRIBUTES, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS); + // we must use (FILE_FLAG_BACKUP_SEMANTICS) to open handle of directory. + } + + bool Open_for_FileRenameInformation(CFSTR fileName) + { + return Create(fileName, DELETE | SYNCHRONIZE | GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL); + // we must use (FILE_FLAG_BACKUP_SEMANTICS) to open handle of directory. + } + bool OpenReparse(CFSTR fileName) { - return Open(fileName, FILE_SHARE_READ, OPEN_EXISTING, + // 17.02 fix: to support Windows XP compatibility junctions: + // we use Create() with (desiredAccess = 0) instead of Open() with GENERIC_READ + return + Create(fileName, 0, + // Open(fileName, + FILE_SHARE_READ, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS); } @@ -180,24 +302,166 @@ class CInFile: public CFileBase bool Read1(void *data, UInt32 size, UInt32 &processedSize) throw(); bool ReadPart(void *data, UInt32 size, UInt32 &processedSize) throw(); bool Read(void *data, UInt32 size, UInt32 &processedSize) throw(); + bool ReadFull(void *data, size_t size, size_t &processedSize) throw(); }; class COutFile: public CFileBase { + bool Open_Disposition(CFSTR fileName, DWORD creationDisposition); public: bool Open(CFSTR fileName, DWORD shareMode, DWORD creationDisposition, DWORD flagsAndAttributes); - bool Open(CFSTR fileName, DWORD creationDisposition); - bool Create(CFSTR fileName, bool createAlways); - bool CreateAlways(CFSTR fileName, DWORD flagsAndAttributes); + bool Open_EXISTING(CFSTR fileName) + { return Open_Disposition(fileName, OPEN_EXISTING); } + bool Create_ALWAYS_or_Open_ALWAYS(CFSTR fileName, bool createAlways) + { return Open_Disposition(fileName, createAlways ? CREATE_ALWAYS : OPEN_ALWAYS); } + bool Create_ALWAYS_or_NEW(CFSTR fileName, bool createAlways) + { return Open_Disposition(fileName, createAlways ? CREATE_ALWAYS : CREATE_NEW); } + bool Create_ALWAYS(CFSTR fileName) + { return Open_Disposition(fileName, CREATE_ALWAYS); } + bool Create_NEW(CFSTR fileName) + { return Open_Disposition(fileName, CREATE_NEW); } + + bool Create_ALWAYS_with_Attribs(CFSTR fileName, DWORD flagsAndAttributes); - bool SetTime(const FILETIME *cTime, const FILETIME *aTime, const FILETIME *mTime) throw(); - bool SetMTime(const FILETIME *mTime) throw(); + bool SetTime(const CFiTime *cTime, const CFiTime *aTime, const CFiTime *mTime) throw(); + bool SetMTime(const CFiTime *mTime) throw(); bool WritePart(const void *data, UInt32 size, UInt32 &processedSize) throw(); bool Write(const void *data, UInt32 size, UInt32 &processedSize) throw(); + bool WriteFull(const void *data, size_t size) throw(); bool SetEndOfFile() throw(); bool SetLength(UInt64 length) throw(); + bool SetLength_KeepPosition(UInt64 length) throw(); }; -}}} +} + + +#else // _WIN32 + +namespace NIO { + +bool GetReparseData(CFSTR path, CByteBuffer &reparseData); +// bool SetReparseData(CFSTR path, bool isDir, const void *data, DWORD size); + +// parameters are in reverse order of symlink() function !!! +bool SetSymLink(CFSTR from, CFSTR to); +bool SetSymLink_UString(CFSTR from, const UString &to); + + +class CFileBase +{ +protected: + int _handle; + + /* + bool IsDeviceFile; + bool SizeDefined; + UInt64 Size; // it can be larger than real available size + */ + + bool OpenBinary(const char *name, int flags, mode_t mode = 0666); +public: + bool PreserveATime; +#if 0 + bool IsStdStream; +#endif + + CFileBase(): _handle(-1), PreserveATime(false) +#if 0 + , IsStdStream(false) +#endif + {} + ~CFileBase() { Close(); } + // void Detach() { _handle = -1; } + bool Close(); + bool GetLength(UInt64 &length) const; + off_t seek(off_t distanceToMove, int moveMethod) const; + off_t seekToBegin() const throw(); + off_t seekToCur() const throw(); + // bool SeekToBegin() throw(); + int my_fstat(struct stat *st) const { return fstat(_handle, st); } + /* + int my_ioctl_BLKGETSIZE64(unsigned long long *val); + int GetDeviceSize_InBytes(UInt64 &size); + void CalcDeviceSize(CFSTR s); + */ +}; + +class CInFile: public CFileBase +{ +public: + bool Open(const char *name); + bool OpenShared(const char *name, bool shareForWrite); +#if 0 + bool AttachStdIn() + { + _handle = GetStdHandle(STD_INPUT_HANDLE); + if (_handle == INVALID_HANDLE_VALUE || !_handle) + return false; + IsStdStream = true; + } +#endif + ssize_t read_part(void *data, size_t size) throw(); + // ssize_t read_full(void *data, size_t size, size_t &processed); + bool ReadFull(void *data, size_t size, size_t &processedSize) throw(); +}; + +class COutFile: public CFileBase +{ + bool CTime_defined; + bool ATime_defined; + bool MTime_defined; + CFiTime CTime; + CFiTime ATime; + CFiTime MTime; + + AString Path; + ssize_t write_part(const void *data, size_t size) throw(); + bool OpenBinary_forWrite_oflag(const char *name, int oflag); +public: + mode_t mode_for_Create; + + COutFile(): + CTime_defined(false), + ATime_defined(false), + MTime_defined(false), + mode_for_Create(0666) + {} + + bool Close(); + + bool Open_EXISTING(CFSTR fileName); + bool Create_ALWAYS_or_Open_ALWAYS(CFSTR fileName, bool createAlways); + bool Create_ALWAYS(CFSTR fileName); + bool Create_NEW(CFSTR fileName); + // bool Create_ALWAYS_or_NEW(CFSTR fileName, bool createAlways); + // bool Open_Disposition(const char *name, DWORD creationDisposition); + + ssize_t write_full(const void *data, size_t size, size_t &processed) throw(); + + bool WriteFull(const void *data, size_t size) throw() + { + size_t processed; + ssize_t res = write_full(data, size, processed); + if (res == -1) + return false; + return processed == size; + } + + bool SetLength(UInt64 length) throw(); + bool SetLength_KeepPosition(UInt64 length) throw() + { + return SetLength(length); + } + bool SetTime(const CFiTime *cTime, const CFiTime *aTime, const CFiTime *mTime) throw(); + bool SetMTime(const CFiTime *mTime) throw(); +}; + +} + +#endif // _WIN32 + +}} + #endif diff --git a/src/plugins/7zip/7za/cpp/Windows/FileLink.cpp b/src/plugins/7zip/7za/cpp/Windows/FileLink.cpp index 25d49ac3..2883c823 100644 --- a/src/plugins/7zip/7za/cpp/Windows/FileLink.cpp +++ b/src/plugins/7zip/7za/cpp/Windows/FileLink.cpp @@ -4,15 +4,31 @@ #include "../../C/CpuArch.h" -#ifdef SUPPORT_DEVICE_FILE +#ifndef _WIN32 +#include +#endif + +#ifdef Z7_DEVICE_FILE #include "../../C/Alloc.h" #endif +#include "../Common/UTFConvert.h" +#include "../Common/StringConvert.h" + #include "FileDir.h" #include "FileFind.h" #include "FileIO.h" #include "FileName.h" +#ifdef Z7_OLD_WIN_SDK +#ifndef ERROR_INVALID_REPARSE_DATA +#define ERROR_INVALID_REPARSE_DATA 4392L +#endif +#ifndef ERROR_REPARSE_TAG_INVALID +#define ERROR_REPARSE_TAG_INVALID 4393L +#endif +#endif + #ifndef _UNICODE extern bool g_IsNT; #endif @@ -22,13 +38,25 @@ namespace NFile { using namespace NName; +/* +Win10 Junctions/SymLinks: + - (/) slash doesn't work as path separator + - Win10 preinstalled junctions don't use tail backslash, but tail backslashes also work. + - double backslash works only after drive prefix "c:\\dir1\dir2\", + and doesn't work in another places. + - absolute path without \??\ prefix doesn't work + - absolute path "c:" doesn't work +*/ + /* Reparse Points (Junctions and Symbolic Links): struct { UInt32 Tag; UInt16 Size; // not including starting 8 bytes - UInt16 Reserved; // = 0 + UInt16 Reserved; // = 0, DOCs: // Length, in bytes, of the unparsed portion of + // the file name pointed to by the FileName member of the associated file object. + // This member is only valid for create operations when the I/O fails with STATUS_REPARSE. UInt16 SubstituteOffset; // offset in bytes from start of namesChars UInt16 SubstituteLen; // size in bytes, it doesn't include tailed NUL @@ -52,6 +80,22 @@ using namespace NName; 2) Default Order in table: Print Path Substitute Path + +DOCS: + The print name SHOULD be an informative pathname, suitable for display + to a user, that also identifies the target of the mount point. + Neither of these pathnames can contain dot directory names. + +reparse tags, with the exception of IO_REPARSE_TAG_SYMLINK, +are processed on the server and are not processed by a client +after transmission over the wire. +Clients SHOULD treat associated reparse data as opaque data. +*/ + +/* +Win10 WSL2: +admin rights + sudo: it creates normal windows symbolic link. +in another cases : it creates IO_REPARSE_TAG_LX_SYMLINK repare point. */ /* @@ -59,125 +103,186 @@ static const UInt32 kReparseFlags_Alias = (1 << 29); static const UInt32 kReparseFlags_HighLatency = (1 << 30); static const UInt32 kReparseFlags_Microsoft = ((UInt32)1 << 31); -#define _my_IO_REPARSE_TAG_HSM (0xC0000004L) -#define _my_IO_REPARSE_TAG_HSM2 (0x80000006L) -#define _my_IO_REPARSE_TAG_SIS (0x80000007L) -#define _my_IO_REPARSE_TAG_WIM (0x80000008L) -#define _my_IO_REPARSE_TAG_CSV (0x80000009L) -#define _my_IO_REPARSE_TAG_DFS (0x8000000AL) -#define _my_IO_REPARSE_TAG_DFSR (0x80000012L) +#define Z7_WIN_IO_REPARSE_TAG_HSM (0xC0000004L) +#define Z7_WIN_IO_REPARSE_TAG_HSM2 (0x80000006L) +#define Z7_WIN_IO_REPARSE_TAG_SIS (0x80000007L) +#define Z7_WIN_IO_REPARSE_TAG_WIM (0x80000008L) +#define Z7_WIN_IO_REPARSE_TAG_CSV (0x80000009L) +#define Z7_WIN_IO_REPARSE_TAG_DFS (0x8000000AL) +#define Z7_WIN_IO_REPARSE_TAG_DFSR (0x80000012L) */ #define Get16(p) GetUi16(p) #define Get32(p) GetUi32(p) -#define Set16(p, v) SetUi16(p, v) -#define Set32(p, v) SetUi32(p, v) - -static const wchar_t *k_LinkPrefix = L"\\??\\"; +static const char * const k_LinkPrefix = "\\??\\"; +static const char * const k_LinkPrefix_UNC = "\\??\\UNC\\"; static const unsigned k_LinkPrefix_Size = 4; -static const bool IsLinkPrefix(const wchar_t *s) +static bool IsLinkPrefix(const wchar_t *s) { return IsString1PrefixedByString2(s, k_LinkPrefix); } /* -static const wchar_t *k_VolumePrefix = L"Volume{"; +static const char * const k_VolumePrefix = "Volume{"; static const bool IsVolumeName(const wchar_t *s) { return IsString1PrefixedByString2(s, k_VolumePrefix); } */ -void WriteString(Byte *dest, const wchar_t *path) +#if defined(_WIN32) && !defined(UNDER_CE) + +#define Set16(p, v) SetUi16(p, v) +#define Set32(p, v) SetUi32(p, v) + +static void WriteString(Byte *dest, const wchar_t *path) { for (;;) { - wchar_t c = *path++; + const wchar_t c = *path++; if (c == 0) return; - Set16(dest, (UInt16)c); + Set16(dest, (UInt16)c) dest += 2; } } -#if defined(_WIN32) && !defined(UNDER_CE) +#ifdef _WIN32 +void Convert_WinPath_to_WslLinuxPath(FString &s, bool convertDrivePath) +{ + if (convertDrivePath && IsDrivePath(s)) + { + FChar c = s[0]; + c = MyCharLower_Ascii(c); + s.DeleteFrontal(2); + s.InsertAtFront(c); + s.Insert(0, FTEXT("/mnt/")); + } + s.Replace(FCHAR_PATH_SEPARATOR, FTEXT('/')); +} +#endif + + +static const unsigned k_Link_Size_Limit = 1u << 16; // 16-bit field is used for size. -bool FillLinkData(CByteBuffer &dest, const wchar_t *path, bool isSymLink) +void FillLinkData_WslLink(CByteBuffer &dest, const wchar_t *path) { - bool isAbs = IsAbsolutePath(path); - if (!isAbs && !isSymLink) - return false; + // dest.Free(); // it's empty already + // WSL probably uses Replacement Character UTF-16 0xFFFD for unsupported characters? + AString utf; + ConvertUnicodeToUTF8(path, utf); + const unsigned size = 4 + utf.Len(); + if (size >= k_Link_Size_Limit) + return; + dest.Alloc(8 + size); + Byte *p = dest; + Set32(p, Z7_WIN_IO_REPARSE_TAG_LX_SYMLINK) + // Set32(p + 4, (UInt32)size) + Set16(p + 4, (UInt16)size) + Set16(p + 6, 0) + Set32(p + 8, Z7_WIN_LX_SYMLINK_VERSION_2) + memcpy(p + 12, utf.Ptr(), utf.Len()); +} - bool needPrintName = true; - if (IsSuperPath(path)) +void FillLinkData_WinLink(CByteBuffer &dest, const wchar_t *path, bool isSymLink) +{ + // dest.Free(); // it's empty already + bool isAbs = false; + if (IS_PATH_SEPAR(path[0])) { - path += kSuperPathPrefixSize; - if (!IsDrivePath(path)) - needPrintName = false; + // root paths "\dir1\path" are marked as relative + if (IS_PATH_SEPAR(path[1])) + isAbs = true; + } + else + isAbs = IsAbsolutePath(path); + if (!isAbs && !isSymLink) + { + // Win10 allows us to create relative MOUNT_POINT. + // But relative MOUNT_POINT will not work when accessing it. + // So we prevent useless creation of a relative MOUNT_POINT. + return; } - const unsigned add_Prefix_Len = isAbs ? k_LinkPrefix_Size : 0; - - unsigned len2 = MyStringLen(path) * 2; - const unsigned len1 = len2 + add_Prefix_Len * 2; + bool needPrintName = true; + UString subs (path); + if (isAbs) + { + const bool isSuperPath = IsSuperPath(path); + if (!isSuperPath && NName::IsNetworkPath(us2fs(path))) + { + subs = k_LinkPrefix_UNC; + subs += (path + 2); + } + else + { + if (isSuperPath) + { + // we remove super prefix: + path += kSuperPathPrefixSize; + // we want to get correct abolute path in PrintName still. + if (!IsDrivePath(path)) + needPrintName = false; // we need "\\server\path" for print name. + } + subs = k_LinkPrefix; + subs += path; + } + } + const size_t len1 = subs.Len() * 2; + size_t len2 = (size_t)MyStringLen(path) * 2; if (!needPrintName) len2 = 0; - - unsigned totalNamesSize = (len1 + len2); - + size_t totalNamesSize = len1 + len2; /* some WIM imagex software uses old scheme for symbolic links. - so we can old scheme for byte to byte compatibility */ - - bool newOrderScheme = isSymLink; + so we can use old scheme for byte to byte compatibility */ + const bool newOrderScheme = isSymLink; // newOrderScheme = false; - if (!newOrderScheme) - totalNamesSize += 2 * 2; + totalNamesSize += 2 * 2; // we use NULL terminators in old scheme. const size_t size = 8 + 8 + (isSymLink ? 4 : 0) + totalNamesSize; + if (size >= k_Link_Size_Limit) + return; dest.Alloc(size); memset(dest, 0, size); const UInt32 tag = isSymLink ? - _my_IO_REPARSE_TAG_SYMLINK : - _my_IO_REPARSE_TAG_MOUNT_POINT; + Z7_WIN_IO_REPARSE_TAG_SYMLINK : + Z7_WIN_IO_REPARSE_TAG_MOUNT_POINT; Byte *p = dest; - Set32(p, tag); - Set16(p + 4, (UInt16)(size - 8)); - Set16(p + 6, 0); + Set32(p, tag) + // Set32(p + 4, (UInt32)(size - 8)) + Set16(p + 4, (UInt16)(size - 8)) + Set16(p + 6, 0) p += 8; unsigned subOffs = 0; unsigned printOffs = 0; if (newOrderScheme) - subOffs = len2; + subOffs = (unsigned)len2; else - printOffs = len1 + 2; - - Set16(p + 0, (UInt16)subOffs); - Set16(p + 2, (UInt16)len1); - Set16(p + 4, (UInt16)printOffs); - Set16(p + 6, (UInt16)len2); + printOffs = (unsigned)len1 + 2; + Set16(p + 0, (UInt16)subOffs) + Set16(p + 2, (UInt16)len1) + Set16(p + 4, (UInt16)printOffs) + Set16(p + 6, (UInt16)len2) p += 8; if (isSymLink) { - UInt32 flags = isAbs ? 0 : _my_SYMLINK_FLAG_RELATIVE; - Set32(p, flags); + const UInt32 flags = isAbs ? 0 : Z7_WIN_SYMLINK_FLAG_RELATIVE; + Set32(p, flags) p += 4; } - - if (add_Prefix_Len != 0) - WriteString(p + subOffs, k_LinkPrefix); - WriteString(p + subOffs + add_Prefix_Len * 2, path); + WriteString(p + subOffs, subs); if (needPrintName) WriteString(p + printOffs, path); - return true; } -#endif +#endif // defined(_WIN32) && !defined(UNDER_CE) + static void GetString(const Byte *p, unsigned len, UString &res) { @@ -185,7 +290,7 @@ static void GetString(const Byte *p, unsigned len, UString &res) unsigned i; for (i = 0; i < len; i++) { - wchar_t c = Get16(p + i * 2); + const wchar_t c = Get16(p + (size_t)i * 2); if (c == 0) break; s[i] = c; @@ -194,44 +299,84 @@ static void GetString(const Byte *p, unsigned len, UString &res) res.ReleaseBuf_SetLen(i); } + bool CReparseAttr::Parse(const Byte *p, size_t size) { + ErrorCode = (DWORD)ERROR_INVALID_REPARSE_DATA; + HeaderError = true; + TagIsUnknown = true; + MinorError = false; + if (size < 8) return false; Tag = Get32(p); - UInt32 len = Get16(p + 4); - if (len + 8 > size) + if (Get16(p + 6) != 0) // padding + { + // DOCs: Reserved : the field SHOULD be set to 0 + // and MUST be ignored (by parser). + // Win10 ignores it. + MinorError = true; // optional + } + unsigned len = Get16(p + 4); + p += 8; + size -= 8; + if (len != size) + // if (len > size) return false; /* if ((type & kReparseFlags_Alias) == 0 || (type & kReparseFlags_Microsoft) == 0 || (type & 0xFFFF) != 3) */ - if (Tag != _my_IO_REPARSE_TAG_MOUNT_POINT && - Tag != _my_IO_REPARSE_TAG_SYMLINK) - // return true; - return false; + HeaderError = false; - if (Get16(p + 6) != 0) // padding - return false; - - p += 8; - size -= 8; - - if (len != size) // do we need that check? + if ( Tag != Z7_WIN_IO_REPARSE_TAG_MOUNT_POINT + && Tag != Z7_WIN_IO_REPARSE_TAG_SYMLINK + && Tag != Z7_WIN_IO_REPARSE_TAG_LX_SYMLINK) + { + // for unsupported reparse points + ErrorCode = (DWORD)ERROR_REPARSE_TAG_INVALID; // ERROR_REPARSE_TAG_MISMATCH + // errorCode = ERROR_REPARSE_TAG_MISMATCH; // ERROR_REPARSE_TAG_INVALID return false; + } + + TagIsUnknown = false; + + if (Tag == Z7_WIN_IO_REPARSE_TAG_LX_SYMLINK) + { + if (len < 4) + return false; + if (Get32(p) != Z7_WIN_LX_SYMLINK_VERSION_2) + return false; + len -= 4; + p += 4; + char *s = WslName.GetBuf(len); + unsigned i; + for (i = 0; i < len; i++) + { + const char c = (char)p[i]; + s[i] = c; + if (c == 0) + break; + } + s[i] = 0; + WslName.ReleaseBuf_SetLen(i); + MinorError = (i != len); + ErrorCode = 0; + return true; + } if (len < 8) return false; - unsigned subOffs = Get16(p); - unsigned subLen = Get16(p + 2); - unsigned printOffs = Get16(p + 4); - unsigned printLen = Get16(p + 6); + const unsigned subOffs = Get16(p); + const unsigned subLen = Get16(p + 2); + const unsigned printOffs = Get16(p + 4); + const unsigned printLen = Get16(p + 6); len -= 8; p += 8; Flags = 0; - if (Tag == _my_IO_REPARSE_TAG_SYMLINK) + if (Tag == Z7_WIN_IO_REPARSE_TAG_SYMLINK) { if (len < 4) return false; @@ -247,39 +392,41 @@ bool CReparseAttr::Parse(const Byte *p, size_t size) GetString(p + subOffs, subLen >> 1, SubsName); GetString(p + printOffs, printLen >> 1, PrintName); + ErrorCode = 0; return true; } + bool CReparseShortInfo::Parse(const Byte *p, size_t size) { - const Byte *start = p; - Offset= 0; + const Byte * const start = p; + Offset = 0; Size = 0; if (size < 8) return false; - UInt32 Tag = Get32(p); + const UInt32 Tag = Get32(p); UInt32 len = Get16(p + 4); + /* if (len + 8 > size) return false; + */ /* if ((type & kReparseFlags_Alias) == 0 || (type & kReparseFlags_Microsoft) == 0 || (type & 0xFFFF) != 3) */ - if (Tag != _my_IO_REPARSE_TAG_MOUNT_POINT && - Tag != _my_IO_REPARSE_TAG_SYMLINK) + if (Tag != Z7_WIN_IO_REPARSE_TAG_MOUNT_POINT && + Tag != Z7_WIN_IO_REPARSE_TAG_SYMLINK) // return true; return false; - + /* if (Get16(p + 6) != 0) // padding return false; - + */ p += 8; size -= 8; - if (len != size) // do we need that check? return false; - if (len < 8) return false; unsigned subOffs = Get16(p); @@ -290,7 +437,7 @@ bool CReparseShortInfo::Parse(const Byte *p, size_t size) p += 8; // UInt32 Flags = 0; - if (Tag == _my_IO_REPARSE_TAG_SYMLINK) + if (Tag == Z7_WIN_IO_REPARSE_TAG_SYMLINK) { if (len < 4) return false; @@ -313,10 +460,14 @@ bool CReparseAttr::IsOkNamePair() const { if (IsLinkPrefix(SubsName)) { + if (PrintName == GetPath()) + return true; +/* if (!IsDrivePath(SubsName.Ptr(k_LinkPrefix_Size))) return PrintName.IsEmpty(); if (wcscmp(SubsName.Ptr(k_LinkPrefix_Size), PrintName) == 0) return true; +*/ } return wcscmp(SubsName, PrintName) == 0; } @@ -332,26 +483,39 @@ bool CReparseAttr::IsVolume() const UString CReparseAttr::GetPath() const { - UString s = SubsName; - if (IsLinkPrefix(s)) + UString s (SubsName); + if (IsSymLink_WSL()) { - s.ReplaceOneCharAtPos(1, '\\'); - if (IsDrivePath(s.Ptr(k_LinkPrefix_Size))) - s.DeleteFrontal(k_LinkPrefix_Size); + // if (CheckUTF8(attr.WslName) + if (!ConvertUTF8ToUnicode(WslName, s)) + MultiByteToUnicodeString2(s, WslName); + } + else if (IsLinkPrefix(s)) + { + if (IsString1PrefixedByString2_NoCase_Ascii(s.Ptr(), k_LinkPrefix_UNC)) + { + s.DeleteFrontal(6); + s.ReplaceOneCharAtPos(0, '\\'); + } + else + { + s.ReplaceOneCharAtPos(1, '\\'); // we normalize prefix from "\??\" to "\\?\" + if (IsDrivePath(s.Ptr(k_LinkPrefix_Size))) + s.DeleteFrontal(k_LinkPrefix_Size); + } } return s; } - -#ifdef SUPPORT_DEVICE_FILE +#ifdef Z7_DEVICE_FILE namespace NSystem { bool MyGetDiskFreeSpace(CFSTR rootPath, UInt64 &clusterSize, UInt64 &totalSize, UInt64 &freeSize); } -#endif +#endif // Z7_DEVICE_FILE -#ifndef UNDER_CE +#if defined(_WIN32) && !defined(UNDER_CE) namespace NIO { @@ -376,19 +540,36 @@ bool GetReparseData(CFSTR path, CByteBuffer &reparseData, BY_HANDLE_FILE_INFORMA static bool CreatePrefixDirOfFile(CFSTR path) { - FString path2 = path; - int pos = path2.ReverseFind_PathSepar(); + FString path2 (path); + const int pos = path2.ReverseFind_PathSepar(); if (pos < 0) return true; #ifdef _WIN32 if (pos == 2 && path2[1] == L':') return true; // we don't create Disk folder; #endif - path2.DeleteFrom(pos); + path2.DeleteFrom((unsigned)pos); return NDir::CreateComplexDir(path2); } -// If there is Reprase data already, it still writes new Reparse data + +static bool OutIoReparseData(DWORD controlCode, CFSTR path, void *data, DWORD size) +{ + COutFile file; + if (!file.Open(path, + FILE_SHARE_WRITE, + OPEN_EXISTING, + FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS)) + return false; + + DWORD returnedSize; + return file.DeviceIoControl(controlCode, data, size, NULL, 0, &returnedSize); +} + + +// MOUNT_POINT (Junction Point) and LX_SYMLINK (WSL) can be written without administrator rights. +// SYMLINK requires administrator rights. +// If there is Reparse data already, it still writes new Reparse data bool SetReparseData(CFSTR path, bool isDir, const void *data, DWORD size) { NFile::NFind::CFileInfo fi; @@ -411,26 +592,106 @@ bool SetReparseData(CFSTR path, bool isDir, const void *data, DWORD size) { CreatePrefixDirOfFile(path); COutFile file; - if (!file.Create(path, CREATE_NEW)) + if (!file.Create_NEW(path)) return false; } } - COutFile file; - if (!file.Open(path, - FILE_SHARE_WRITE, - OPEN_EXISTING, - FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS)) + return OutIoReparseData(my_FSCTL_SET_REPARSE_POINT, path, (void *)(const Byte *)(data), size); +} + + +bool DeleteReparseData(CFSTR path) +{ + CByteBuffer reparseData; + if (!GetReparseData(path, reparseData, NULL)) return false; + /* MSDN: The tag specified in the ReparseTag member of this structure + must match the tag of the reparse point to be deleted, + and the ReparseDataLength member must be zero */ + #define my_REPARSE_DATA_BUFFER_HEADER_SIZE 8 + if (reparseData.Size() < my_REPARSE_DATA_BUFFER_HEADER_SIZE) + { + SetLastError(ERROR_INVALID_REPARSE_DATA); + return false; + } + // BYTE buf[my_REPARSE_DATA_BUFFER_HEADER_SIZE]; + // memset(buf, 0, sizeof(buf)); + // memcpy(buf, reparseData, 4); // tag + memset(reparseData + 4, 0, my_REPARSE_DATA_BUFFER_HEADER_SIZE - 4); + return OutIoReparseData(my_FSCTL_DELETE_REPARSE_POINT, path, reparseData, my_REPARSE_DATA_BUFFER_HEADER_SIZE); +} - DWORD returnedSize; - if (!file.DeviceIoControl(my_FSCTL_SET_REPARSE_POINT, (void *)data, size, NULL, 0, &returnedSize)) +} + +#endif // defined(_WIN32) && !defined(UNDER_CE) + + +#ifndef _WIN32 + +namespace NIO { + +bool GetReparseData(CFSTR path, CByteBuffer &reparseData) +{ + reparseData.Free(); + + #define MAX_PATHNAME_LEN 1024 + char buf[MAX_PATHNAME_LEN + 2]; + const size_t request = sizeof(buf) - 1; + + // printf("\nreadlink() path = %s \n", path); + const ssize_t size = readlink(path, buf, request); + // there is no tail zero + + if (size < 0) return false; + if ((size_t)size >= request) + { + SetLastError(EINVAL); // check it: ENAMETOOLONG + return false; + } + + // printf("\nreadlink() res = %s size = %d \n", buf, (int)size); + reparseData.CopyFrom((const Byte *)buf, (size_t)size); return true; } + +/* +// If there is Reparse data already, it still writes new Reparse data +bool SetReparseData(CFSTR path, bool isDir, const void *data, DWORD size) +{ + // AString s; + // s.SetFrom_CalcLen(data, size); + // return (symlink(s, path) == 0); + UNUSED_VAR(path) + UNUSED_VAR(isDir) + UNUSED_VAR(data) + UNUSED_VAR(size) + SetLastError(ENOSYS); + return false; } +*/ -#endif +bool SetSymLink(CFSTR from, CFSTR to) +{ + // printf("\nsymlink() %s -> %s\n", from, to); + int ir; + // ir = unlink(path); + // if (ir == 0) + ir = symlink(to, from); + return (ir == 0); +} + +bool SetSymLink_UString(CFSTR from, const UString &to) +{ + AString utf; + ConvertUnicodeToUTF8(to, utf); + return SetSymLink(from, utf); +} + +} + +#endif // !_WIN32 }} diff --git a/src/plugins/7zip/7za/cpp/Windows/FileMapping.h b/src/plugins/7zip/7za/cpp/Windows/FileMapping.h index f90c429f..caa7ea3e 100644 --- a/src/plugins/7zip/7za/cpp/Windows/FileMapping.h +++ b/src/plugins/7zip/7za/cpp/Windows/FileMapping.h @@ -1,7 +1,7 @@ // Windows/FileMapping.h -#ifndef __WINDOWS_FILEMAPPING_H -#define __WINDOWS_FILEMAPPING_H +#ifndef ZIP7_INC_WINDOWS_FILE_MAPPING_H +#define ZIP7_INC_WINDOWS_FILE_MAPPING_H #include "../Common/MyTypes.h" @@ -34,7 +34,7 @@ class CFileMapping: public CHandle return res; #else _handle = ::OpenFileMapping(desiredAccess, FALSE, name); - if (_handle != 0) + if (_handle != NULL) return 0; return ::GetLastError(); #endif diff --git a/src/plugins/7zip/7za/cpp/Windows/FileName.cpp b/src/plugins/7zip/7za/cpp/Windows/FileName.cpp index 908ed53f..eb62567c 100644 --- a/src/plugins/7zip/7za/cpp/Windows/FileName.cpp +++ b/src/plugins/7zip/7za/cpp/Windows/FileName.cpp @@ -2,6 +2,13 @@ #include "StdAfx.h" +#ifndef _WIN32 +#include +#include +#include "../Common/StringConvert.h" +#endif + +#include "FileDir.h" #include "FileName.h" #ifndef _UNICODE @@ -58,9 +65,34 @@ void NormalizeDirPathPrefix(UString &dirPath) dirPath.Add_PathSepar(); } -#define IS_LETTER_CHAR(c) ((c) >= 'a' && (c) <= 'z' || (c) >= 'A' && (c) <= 'Z') -bool IsDrivePath(const wchar_t *s) throw() { return IS_LETTER_CHAR(s[0]) && s[1] == ':' && IS_SEPAR(s[2]); } +#define IS_LETTER_CHAR(c) ((((unsigned)(int)(c) | 0x20) - (unsigned)'a' <= (unsigned)('z' - 'a'))) +bool IsDrivePath (const wchar_t *s) throw() { return IS_LETTER_CHAR(s[0]) && s[1] == ':' && IS_SEPAR(s[2]); } +// bool IsDriveName2(const wchar_t *s) throw() { return IS_LETTER_CHAR(s[0]) && s[1] == ':' && s[2] == 0; } + +#ifdef _WIN32 + +bool IsDrivePath2(const wchar_t *s) throw() { return IS_LETTER_CHAR(s[0]) && s[1] == ':'; } + +#ifndef USE_UNICODE_FSTRING +#ifdef Z7_LONG_PATH +static void NormalizeDirSeparators(UString &s) +{ + const unsigned len = s.Len(); + for (unsigned i = 0; i < len; i++) + if (s[i] == '/') + s.ReplaceOneCharAtPos(i, WCHAR_PATH_SEPARATOR); +} +#endif +#endif + +void NormalizeDirSeparators(FString &s) +{ + const unsigned len = s.Len(); + for (unsigned i = 0; i < len; i++) + if (s[i] == '/') + s.ReplaceOneCharAtPos(i, FCHAR_PATH_SEPARATOR); +} bool IsAltPathPrefix(CFSTR s) throw() { @@ -85,14 +117,23 @@ bool IsAltPathPrefix(CFSTR s) throw() return true; } -#if defined(_WIN32) && !defined(UNDER_CE) +#endif // _WIN32 -const wchar_t *kSuperPathPrefix = L"\\\\?\\"; -static const wchar_t *kSuperUncPrefix = L"\\\\?\\UNC\\"; + +const char * const kSuperPathPrefix = + STRING_PATH_SEPARATOR + STRING_PATH_SEPARATOR "?" + STRING_PATH_SEPARATOR; +#ifdef Z7_LONG_PATH +static const char * const kSuperUncPrefix = + STRING_PATH_SEPARATOR + STRING_PATH_SEPARATOR "?" + STRING_PATH_SEPARATOR "UNC" + STRING_PATH_SEPARATOR; +#endif #define IS_DEVICE_PATH(s) (IS_SEPAR((s)[0]) && IS_SEPAR((s)[1]) && (s)[2] == '.' && IS_SEPAR((s)[3])) #define IS_SUPER_PREFIX(s) (IS_SEPAR((s)[0]) && IS_SEPAR((s)[1]) && (s)[2] == '?' && IS_SEPAR((s)[3])) -#define IS_SUPER_OR_DEVICE_PATH(s) (IS_SEPAR((s)[0]) && IS_SEPAR((s)[1]) && ((s)[2] == '?' || (s)[2] == '.') && IS_SEPAR((s)[3])) #define IS_UNC_WITH_SLASH(s) ( \ ((s)[0] == 'U' || (s)[0] == 'u') \ @@ -100,6 +141,16 @@ static const wchar_t *kSuperUncPrefix = L"\\\\?\\UNC\\"; && ((s)[2] == 'C' || (s)[2] == 'c') \ && IS_SEPAR((s)[3])) +static const unsigned kDrivePrefixSize = 3; /* c:\ */ + +bool IsSuperPath(const wchar_t *s) throw(); +bool IsSuperPath(const wchar_t *s) throw() { return IS_SUPER_PREFIX(s); } +// bool IsSuperUncPath(const wchar_t *s) throw() { return (IS_SUPER_PREFIX(s) && IS_UNC_WITH_SLASH(s + kSuperPathPrefixSize)); } + +#if defined(_WIN32) && !defined(UNDER_CE) + +#define IS_SUPER_OR_DEVICE_PATH(s) (IS_SEPAR((s)[0]) && IS_SEPAR((s)[1]) && ((s)[2] == '?' || (s)[2] == '.') && IS_SEPAR((s)[3])) +bool IsSuperOrDevicePath(const wchar_t *s) throw() { return IS_SUPER_OR_DEVICE_PATH(s); } bool IsDevicePath(CFSTR s) throw() { #ifdef UNDER_CE @@ -109,7 +160,7 @@ bool IsDevicePath(CFSTR s) throw() /* // actually we don't know the way to open device file in WinCE. unsigned len = MyStringLen(s); - if (len < 5 || len > 5 || memcmp(s, FTEXT("DSK"), 3 * sizeof(FChar)) != 0) + if (len < 5 || len > 5 || !IsString1PrefixedByString2(s, "DSK")) return false; if (s[4] != ':') return false; @@ -120,10 +171,10 @@ bool IsDevicePath(CFSTR s) throw() if (!IS_DEVICE_PATH(s)) return false; - unsigned len = MyStringLen(s); + const unsigned len = MyStringLen(s); if (len == 6 && s[5] == ':') return true; - if (len < 18 || len > 22 || memcmp(s + kDevicePathPrefixSize, FTEXT("PhysicalDrive"), 13 * sizeof(FChar)) != 0) + if (len < 18 || len > 22 || !IsString1PrefixedByString2(s + kDevicePathPrefixSize, "PhysicalDrive")) return false; for (unsigned i = 17; i < len; i++) if (s[i] < '0' || s[i] > '9') @@ -140,7 +191,7 @@ bool IsNetworkPath(CFSTR s) throw() return false; if (IsSuperUncPath(s)) return true; - FChar c = s[2]; + const FChar c = s[2]; return (c != '.' && c != '?'); } @@ -153,35 +204,58 @@ unsigned GetNetworkServerPrefixSize(CFSTR s) throw() prefixSize = kSuperUncPathPrefixSize; else { - FChar c = s[2]; + const FChar c = s[2]; if (c == '.' || c == '?') return 0; } - int pos = FindSepar(s + prefixSize); + const int pos = FindSepar(s + prefixSize); if (pos < 0) return 0; - return prefixSize + pos + 1; + return prefixSize + (unsigned)(pos + 1); } bool IsNetworkShareRootPath(CFSTR s) throw() { - unsigned prefixSize = GetNetworkServerPrefixSize(s); + const unsigned prefixSize = GetNetworkServerPrefixSize(s); if (prefixSize == 0) return false; s += prefixSize; - int pos = FindSepar(s); + const int pos = FindSepar(s); if (pos < 0) return true; return s[(unsigned)pos + 1] == 0; } -static const unsigned kDrivePrefixSize = 3; /* c:\ */ +bool IsAltStreamPrefixWithColon(const UString &s) throw() +{ + if (s.IsEmpty()) + return false; + if (s.Back() != ':') + return false; + unsigned pos = 0; + if (IsSuperPath(s)) + pos = kSuperPathPrefixSize; + if (s.Len() - pos == 2 && IsDrivePath2(s.Ptr(pos))) + return false; + return true; +} + +bool If_IsSuperPath_RemoveSuperPrefix(UString &s) +{ + if (!IsSuperPath(s)) + return false; + unsigned start = 0; + unsigned count = kSuperPathPrefixSize; + const wchar_t *s2 = s.Ptr(kSuperPathPrefixSize); + if (IS_UNC_WITH_SLASH(s2)) + { + start = 2; + count = kSuperUncPathPrefixSize - 2; + } + s.Delete(start, count); + return true; +} -bool IsDrivePath2(const wchar_t *s) throw() { return IS_LETTER_CHAR(s[0]) && s[1] == ':'; } -// bool IsDriveName2(const wchar_t *s) throw() { return IS_LETTER_CHAR(s[0]) && s[1] == ':' && s[2] == 0; } -bool IsSuperPath(const wchar_t *s) throw() { return IS_SUPER_PREFIX(s); } -bool IsSuperOrDevicePath(const wchar_t *s) throw() { return IS_SUPER_OR_DEVICE_PATH(s); } -// bool IsSuperUncPath(const wchar_t *s) throw() { return (IS_SUPER_PREFIX(s) && IS_UNC_WITH_SLASH(s + kSuperPathPrefixSize)); } #ifndef USE_UNICODE_FSTRING bool IsDrivePath2(CFSTR s) throw() { return IS_LETTER_CHAR(s[0]) && s[1] == ':'; } @@ -191,14 +265,12 @@ bool IsSuperPath(CFSTR s) throw() { return IS_SUPER_PREFIX(s); } bool IsSuperOrDevicePath(CFSTR s) throw() { return IS_SUPER_OR_DEVICE_PATH(s); } #endif // USE_UNICODE_FSTRING -/* -bool IsDrivePath_SuperAllowed(CFSTR s) +bool IsDrivePath_SuperAllowed(CFSTR s) throw() { if (IsSuperPath(s)) s += kSuperPathPrefixSize; return IsDrivePath(s); } -*/ bool IsDriveRootPath_SuperAllowed(CFSTR s) throw() { @@ -212,21 +284,23 @@ bool IsAbsolutePath(const wchar_t *s) throw() return IS_SEPAR(s[0]) || IsDrivePath2(s); } -int FindAltStreamColon(CFSTR path) +int FindAltStreamColon(CFSTR path) throw() { unsigned i = 0; - if (IsDrivePath2(path)) - i = 2; + if (IsSuperPath(path)) + i = kSuperPathPrefixSize; + if (IsDrivePath2(path + i)) + i += 2; int colonPos = -1; for (;; i++) { - FChar c = path[i]; + const FChar c = path[i]; if (c == 0) return colonPos; if (c == ':') { if (colonPos < 0) - colonPos = i; + colonPos = (int)i; continue; } if (IS_SEPAR(c)) @@ -239,13 +313,13 @@ int FindAltStreamColon(CFSTR path) static unsigned GetRootPrefixSize_Of_NetworkPath(CFSTR s) { // Network path: we look "server\path\" as root prefix - int pos = FindSepar(s); + const int pos = FindSepar(s); if (pos < 0) return 0; - int pos2 = FindSepar(s + (unsigned)pos + 1); + const int pos2 = FindSepar(s + (unsigned)pos + 1); if (pos2 < 0) return 0; - return pos + pos2 + 2; + return (unsigned)pos + (unsigned)pos2 + 2; } static unsigned GetRootPrefixSize_Of_SimplePath(CFSTR s) @@ -256,7 +330,7 @@ static unsigned GetRootPrefixSize_Of_SimplePath(CFSTR s) return 0; if (s[1] == 0 || !IS_SEPAR(s[1])) return 1; - unsigned size = GetRootPrefixSize_Of_NetworkPath(s + 2); + const unsigned size = GetRootPrefixSize_Of_NetworkPath(s + 2); return (size == 0) ? 0 : 2 + size; } @@ -264,17 +338,17 @@ static unsigned GetRootPrefixSize_Of_SuperPath(CFSTR s) { if (IS_UNC_WITH_SLASH(s + kSuperPathPrefixSize)) { - unsigned size = GetRootPrefixSize_Of_NetworkPath(s + kSuperUncPathPrefixSize); + const unsigned size = GetRootPrefixSize_Of_NetworkPath(s + kSuperUncPathPrefixSize); return (size == 0) ? 0 : kSuperUncPathPrefixSize + size; } // we support \\?\c:\ paths and volume GUID paths \\?\Volume{GUID}\" - int pos = FindSepar(s + kSuperPathPrefixSize); + const int pos = FindSepar(s + kSuperPathPrefixSize); if (pos < 0) return 0; - return kSuperPathPrefixSize + pos + 1; + return kSuperPathPrefixSize + (unsigned)pos + 1; } -unsigned GetRootPrefixSize(CFSTR s) +unsigned GetRootPrefixSize(CFSTR s) throw() { if (IS_DEVICE_PATH(s)) return kDevicePathPrefixSize; @@ -284,20 +358,22 @@ unsigned GetRootPrefixSize(CFSTR s) } #endif // USE_UNICODE_FSTRING +#endif // _WIN32 + -static unsigned GetRootPrefixSize_Of_NetworkPath(const wchar_t *s) +static unsigned GetRootPrefixSize_Of_NetworkPath(const wchar_t *s) throw() { // Network path: we look "server\path\" as root prefix - int pos = FindSepar(s); + const int pos = FindSepar(s); if (pos < 0) return 0; - int pos2 = FindSepar(s + (unsigned)pos + 1); + const int pos2 = FindSepar(s + (unsigned)pos + 1); if (pos2 < 0) return 0; - return pos + pos2 + 2; + return (unsigned)(pos + pos2 + 2); } -static unsigned GetRootPrefixSize_Of_SimplePath(const wchar_t *s) +static unsigned GetRootPrefixSize_Of_SimplePath(const wchar_t *s) throw() { if (IsDrivePath(s)) return kDrivePrefixSize; @@ -305,25 +381,29 @@ static unsigned GetRootPrefixSize_Of_SimplePath(const wchar_t *s) return 0; if (s[1] == 0 || !IS_SEPAR(s[1])) return 1; - unsigned size = GetRootPrefixSize_Of_NetworkPath(s + 2); + const unsigned size = GetRootPrefixSize_Of_NetworkPath(s + 2); return (size == 0) ? 0 : 2 + size; } -static unsigned GetRootPrefixSize_Of_SuperPath(const wchar_t *s) +static unsigned GetRootPrefixSize_Of_SuperPath(const wchar_t *s) throw() { if (IS_UNC_WITH_SLASH(s + kSuperPathPrefixSize)) { - unsigned size = GetRootPrefixSize_Of_NetworkPath(s + kSuperUncPathPrefixSize); + const unsigned size = GetRootPrefixSize_Of_NetworkPath(s + kSuperUncPathPrefixSize); return (size == 0) ? 0 : kSuperUncPathPrefixSize + size; } // we support \\?\c:\ paths and volume GUID paths \\?\Volume{GUID}\" - int pos = FindSepar(s + kSuperPathPrefixSize); + const int pos = FindSepar(s + kSuperPathPrefixSize); if (pos < 0) return 0; - return kSuperPathPrefixSize + pos + 1; + return kSuperPathPrefixSize + (unsigned)(pos + 1); } +#ifdef _WIN32 unsigned GetRootPrefixSize(const wchar_t *s) throw() +#else +unsigned GetRootPrefixSize_WINDOWS(const wchar_t *s) throw() +#endif { if (IS_DEVICE_PATH(s)) return kDevicePathPrefixSize; @@ -332,43 +412,41 @@ unsigned GetRootPrefixSize(const wchar_t *s) throw() return GetRootPrefixSize_Of_SimplePath(s); } -#else // _WIN32 +#ifndef _WIN32 -bool IsAbsolutePath(const wchar_t *s) { return IS_SEPAR(s[0]); } +bool IsAbsolutePath(const wchar_t *s) throw() { return IS_SEPAR(s[0]); } #ifndef USE_UNICODE_FSTRING -unsigned GetRootPrefixSize(CFSTR s) { return IS_SEPAR(s[0]) ? 1 : 0; } +unsigned GetRootPrefixSize(CFSTR s) throw(); +unsigned GetRootPrefixSize(CFSTR s) throw() { return IS_SEPAR(s[0]) ? 1 : 0; } #endif -unsigned GetRootPrefixSize(const wchar_t *s) { return IS_SEPAR(s[0]) ? 1 : 0; } +unsigned GetRootPrefixSize(const wchar_t *s) throw() { return IS_SEPAR(s[0]) ? 1 : 0; } #endif // _WIN32 #ifndef UNDER_CE + +#ifdef USE_UNICODE_FSTRING + +#define GetCurDir NDir::GetCurrentDir + +#else + static bool GetCurDir(UString &path) { path.Empty(); - DWORD needLength; - #ifndef _UNICODE - if (!g_IsNT) - { - TCHAR s[MAX_PATH + 2]; - s[0] = 0; - needLength = ::GetCurrentDirectory(MAX_PATH + 1, s); - path = fs2us(fas2fs(s)); - } - else - #endif - { - WCHAR s[MAX_PATH + 2]; - s[0] = 0; - needLength = ::GetCurrentDirectoryW(MAX_PATH + 1, s); - path = s; - } - return (needLength > 0 && needLength <= MAX_PATH); + FString s; + if (!NDir::GetCurrentDir(s)) + return false; + path = fs2us(s); + return true; } +#endif + + static bool ResolveDotsFolders(UString &s) { #ifdef _WIN32 @@ -390,7 +468,7 @@ static bool ResolveDotsFolders(UString &s) { if (i == 0) return false; - int k = i - 2; + int k = (int)i - 2; i += 2; for (;; k--) @@ -409,8 +487,8 @@ static bool ResolveDotsFolders(UString &s) if (k >= 0) { - num = i - k; - i = k; + num = i - (unsigned)k; + i = (unsigned)k; } else { @@ -470,7 +548,7 @@ static bool AreThereDotsFolders(CFSTR s) #endif #endif // LONG_PATH_DOTS_FOLDERS_PARSING -#ifdef WIN_LONG_PATH +#ifdef Z7_LONG_PATH /* Most of Windows versions have problems, if some file or dir name @@ -530,6 +608,7 @@ int GetUseSuperPathType(CFSTR s) throw() } + /* returns false in two cases: - if GetCurDir was used, and GetCurDir returned error. @@ -540,7 +619,6 @@ int GetUseSuperPathType(CFSTR s) throw() for absolute paths, returns true, res is Super path. */ - static bool GetSuperPathBase(CFSTR s, UString &res) { res.Empty(); @@ -564,11 +642,11 @@ static bool GetSuperPathBase(CFSTR s, UString &res) return true; UString temp = fs2us(s); - unsigned fixedSize = GetRootPrefixSize_Of_SuperPath(temp); + const unsigned fixedSize = GetRootPrefixSize_Of_SuperPath(temp); if (fixedSize == 0) return true; - UString rem = &temp[fixedSize]; + UString rem = temp.Ptr(fixedSize); if (!ResolveDotsFolders(rem)) return true; @@ -586,13 +664,13 @@ static bool GetSuperPathBase(CFSTR s, UString &res) if (IS_SEPAR(s[1])) { UString temp = fs2us(s + 2); - unsigned fixedSize = GetRootPrefixSize_Of_NetworkPath(temp); + const unsigned fixedSize = GetRootPrefixSize_Of_NetworkPath(temp); // we ignore that error to allow short network paths server\share? /* if (fixedSize == 0) return false; */ - UString rem = &temp[fixedSize]; + UString rem = temp.Ptr(fixedSize); if (!ResolveDotsFolders(rem)) return false; res += kSuperUncPrefix; @@ -628,7 +706,7 @@ static bool GetSuperPathBase(CFSTR s, UString &res) unsigned fixedSizeStart = 0; unsigned fixedSize = 0; - const wchar_t *superMarker = NULL; + const char *superMarker = NULL; if (IsSuperPath(curDir)) { fixedSize = GetRootPrefixSize_Of_SuperPath(curDir); @@ -683,7 +761,7 @@ static bool GetSuperPathBase(CFSTR s, UString &res) true false * use Super path true true true don't use any path, we already used mainPath true true false use main path as Super Path, we don't try mainMath - That case is possible now if GetCurDir returns unknow + That case is possible now if GetCurDir returns unknown type of path (not drive and not network) We can change that code if we want to try mainPath, if GetSuperPathBase returns error, @@ -704,6 +782,8 @@ bool GetSuperPath(CFSTR path, UString &superPath, bool onlyIfNew) return false; superPath = fs2us(path); } + + NormalizeDirSeparators(superPath); return true; } return false; @@ -714,6 +794,10 @@ bool GetSuperPaths(CFSTR s1, CFSTR s2, UString &d1, UString &d2, bool onlyIfNew) if (!GetSuperPathBase(s1, d1) || !GetSuperPathBase(s2, d2)) return false; + + NormalizeDirSeparators(d1); + NormalizeDirSeparators(d2); + if (d1.IsEmpty() && d2.IsEmpty() && onlyIfNew) return false; if (d1.IsEmpty()) d1 = fs2us(s1); @@ -731,7 +815,7 @@ bool GetSuperPath(CFSTR path, UString &superPath) return false; } */ -#endif // WIN_LONG_PATH +#endif // Z7_LONG_PATH bool GetFullPath(CFSTR dirPrefix, CFSTR s, FString &res) { @@ -749,8 +833,11 @@ bool GetFullPath(CFSTR dirPrefix, CFSTR s, FString &res) #else - unsigned prefixSize = GetRootPrefixSize(s); + const unsigned prefixSize = GetRootPrefixSize(s); if (prefixSize != 0) +#ifdef _WIN32 + if (prefixSize != 1) +#endif { if (!AreThereDotsFolders(s + prefixSize)) return true; @@ -763,21 +850,9 @@ bool GetFullPath(CFSTR dirPrefix, CFSTR s, FString &res) return true; } - /* - FChar c = s[0]; - if (c == 0) - return true; - if (c == '.' && (s[1] == 0 || (s[1] == '.' && s[2] == 0))) - return true; - if (IS_SEPAR(c) && IS_SEPAR(s[1])) - return true; - if (IsDrivePath(s)) - return true; - */ - UString curDir; - if (dirPrefix) - curDir = fs2us(dirPrefix); + if (dirPrefix && prefixSize == 0) + curDir = fs2us(dirPrefix); // we use (dirPrefix), only if (s) path is relative else { if (!GetCurDir(curDir)) @@ -785,46 +860,40 @@ bool GetFullPath(CFSTR dirPrefix, CFSTR s, FString &res) } NormalizeDirPathPrefix(curDir); - unsigned fixedSize = 0; - - #ifdef _WIN32 + unsigned fixedSize = GetRootPrefixSize(curDir); - if (IsSuperPath(curDir)) + UString temp; +#ifdef _WIN32 + if (prefixSize != 0) { - fixedSize = GetRootPrefixSize_Of_SuperPath(curDir); + /* (s) is absolute path, but only (prefixSize == 1) is possible here. + So for full resolving we need root of current folder and + relative part of (s). */ + s += prefixSize; + // (s) is relative part now if (fixedSize == 0) - return false; - } - else - { - if (IsDrivePath(curDir)) - fixedSize = kDrivePrefixSize; - else { - if (!IsPathSepar(curDir[0]) || !IsPathSepar(curDir[1])) - return false; - fixedSize = GetRootPrefixSize_Of_NetworkPath(curDir.Ptr(2)); - if (fixedSize == 0) - return false; - fixedSize += 2; + // (curDir) is not absolute. + // That case is unexpected, but we support it too. + curDir.Empty(); + curDir.Add_PathSepar(); + fixedSize = 1; + // (curDir) now is just Separ character. + // So final (res) path later also will have Separ prefix. } } - - #endif // _WIN32 - - UString temp; - if (IS_SEPAR(s[0])) - { - temp = fs2us(s + 1); - } else +#endif // _WIN32 { - temp += curDir.Ptr(fixedSize); - temp += fs2us(s); + // (s) is relative path + temp = curDir.Ptr(fixedSize); + // (temp) is relative_part_of(curDir) } + temp += fs2us(s); if (!ResolveDotsFolders(temp)) return false; curDir.DeleteFrom(fixedSize); + // (curDir) now contains only absolute prefix part res = us2fs(curDir); res += us2fs(temp); @@ -833,6 +902,7 @@ bool GetFullPath(CFSTR dirPrefix, CFSTR s, FString &res) return true; } + bool GetFullPath(CFSTR path, FString &fullPath) { return GetFullPath(NULL, path, fullPath); diff --git a/src/plugins/7zip/7za/cpp/Windows/FileName.h b/src/plugins/7zip/7za/cpp/Windows/FileName.h index 6f9b6e03..ce26e781 100644 --- a/src/plugins/7zip/7za/cpp/Windows/FileName.h +++ b/src/plugins/7zip/7za/cpp/Windows/FileName.h @@ -1,7 +1,7 @@ // Windows/FileName.h -#ifndef __WINDOWS_FILE_NAME_H -#define __WINDOWS_FILE_NAME_H +#ifndef ZIP7_INC_WINDOWS_FILE_NAME_H +#define ZIP7_INC_WINDOWS_FILE_NAME_H #include "../Common/MyString.h" @@ -17,17 +17,21 @@ int FindSepar(const FChar *s) throw(); void NormalizeDirPathPrefix(FString &dirPath); // ensures that it ended with '\\', if dirPath is not epmty void NormalizeDirPathPrefix(UString &dirPath); +#ifdef _WIN32 +void NormalizeDirSeparators(FString &s); +#endif + bool IsDrivePath(const wchar_t *s) throw(); // first 3 chars are drive chars like "a:\\" bool IsAltPathPrefix(CFSTR s) throw(); /* name: */ -#if defined(_WIN32) && !defined(UNDER_CE) - -extern const wchar_t *kSuperPathPrefix; /* \\?\ */ +extern const char * const kSuperPathPrefix; /* \\?\ */ const unsigned kDevicePathPrefixSize = 4; const unsigned kSuperPathPrefixSize = 4; const unsigned kSuperUncPathPrefixSize = kSuperPathPrefixSize + 4; +#if defined(_WIN32) && !defined(UNDER_CE) + bool IsDevicePath(CFSTR s) throw(); /* \\.\ */ bool IsSuperUncPath(CFSTR s) throw(); /* \\?\UNC\ */ bool IsNetworkPath(CFSTR s) throw(); /* \\?\UNC\ or \\SERVER */ @@ -42,7 +46,7 @@ unsigned GetNetworkServerPrefixSize(CFSTR s) throw(); bool IsNetworkShareRootPath(CFSTR s) throw(); /* \\?\UNC\SERVER\share or \\SERVER\share or with slash */ -// bool IsDrivePath_SuperAllowed(CFSTR s) throw(); // first chars are drive chars like "a:\" or "\\?\a:\" +bool IsDrivePath_SuperAllowed(CFSTR s) throw(); // first chars are drive chars like "a:\" or "\\?\a:\" bool IsDriveRootPath_SuperAllowed(CFSTR s) throw(); // exact drive root path "a:\" or "\\?\a:\" bool IsDrivePath2(const wchar_t *s) throw(); // first 2 chars are drive chars like "a:" @@ -50,6 +54,10 @@ bool IsDrivePath2(const wchar_t *s) throw(); // first 2 chars are drive chars li bool IsSuperPath(const wchar_t *s) throw(); bool IsSuperOrDevicePath(const wchar_t *s) throw(); +bool IsAltStreamPrefixWithColon(const UString &s) throw(); +// returns true, if super prefix was removed +bool If_IsSuperPath_RemoveSuperPrefix(UString &s); + #ifndef USE_UNICODE_FSTRING bool IsDrivePath2(CFSTR s) throw(); // first 2 chars are drive chars like "a:" // bool IsDriveName2(CFSTR s) throw(); // is drive name like "a:" @@ -71,14 +79,23 @@ unsigned GetRootPrefixSize(CFSTR s) throw(); #endif -int FindAltStreamColon(CFSTR path); +int FindAltStreamColon(CFSTR path) throw(); #endif // _WIN32 bool IsAbsolutePath(const wchar_t *s) throw(); unsigned GetRootPrefixSize(const wchar_t *s) throw(); -#ifdef WIN_LONG_PATH +#ifndef _WIN32 +/* GetRootPrefixSize_WINDOWS() is called in linux, but it parses path by windows rules. + It supports only paths system (linux) slash separators (STRING_PATH_SEPARATOR), + It doesn't parses paths with backslash (windows) separators. + "c:/dir/file" is supported. +*/ +unsigned GetRootPrefixSize_WINDOWS(const wchar_t *s) throw(); +#endif + +#ifdef Z7_LONG_PATH const int kSuperPathType_UseOnlyMain = 0; const int kSuperPathType_UseOnlySuper = 1; @@ -88,16 +105,16 @@ int GetUseSuperPathType(CFSTR s) throw(); bool GetSuperPath(CFSTR path, UString &superPath, bool onlyIfNew); bool GetSuperPaths(CFSTR s1, CFSTR s2, UString &d1, UString &d2, bool onlyIfNew); -#define USE_MAIN_PATH (__useSuperPathType != kSuperPathType_UseOnlySuper) -#define USE_MAIN_PATH_2 (__useSuperPathType1 != kSuperPathType_UseOnlySuper && __useSuperPathType2 != kSuperPathType_UseOnlySuper) +#define USE_MAIN_PATH (_useSuperPathType != kSuperPathType_UseOnlySuper) +#define USE_MAIN_PATH_2 (_useSuperPathType1 != kSuperPathType_UseOnlySuper && _useSuperPathType2 != kSuperPathType_UseOnlySuper) -#define USE_SUPER_PATH (__useSuperPathType != kSuperPathType_UseOnlyMain) -#define USE_SUPER_PATH_2 (__useSuperPathType1 != kSuperPathType_UseOnlyMain || __useSuperPathType2 != kSuperPathType_UseOnlyMain) +#define USE_SUPER_PATH (_useSuperPathType != kSuperPathType_UseOnlyMain) +#define USE_SUPER_PATH_2 (_useSuperPathType1 != kSuperPathType_UseOnlyMain || _useSuperPathType2 != kSuperPathType_UseOnlyMain) -#define IF_USE_MAIN_PATH int __useSuperPathType = GetUseSuperPathType(path); if (USE_MAIN_PATH) +#define IF_USE_MAIN_PATH int _useSuperPathType = GetUseSuperPathType(path); if (USE_MAIN_PATH) #define IF_USE_MAIN_PATH_2(x1, x2) \ - int __useSuperPathType1 = GetUseSuperPathType(x1); \ - int __useSuperPathType2 = GetUseSuperPathType(x2); \ + int _useSuperPathType1 = GetUseSuperPathType(x1); \ + int _useSuperPathType2 = GetUseSuperPathType(x2); \ if (USE_MAIN_PATH_2) #else @@ -105,8 +122,18 @@ bool GetSuperPaths(CFSTR s1, CFSTR s2, UString &d1, UString &d2, bool onlyIfNew) #define IF_USE_MAIN_PATH #define IF_USE_MAIN_PATH_2(x1, x2) -#endif // WIN_LONG_PATH - +#endif // Z7_LONG_PATH + +/* + if (dirPrefix != NULL && (path) is relative) + { + (dirPrefix) will be used + result (fullPath) will contain prefix part of (dirPrefix). + } + Current_Dir path can be used in 2 cases: + 1) if (path) is relative && dirPrefix == NULL + 2) for _WIN32: if (path) is absolute starting wuth "\" +*/ bool GetFullPath(CFSTR dirPrefix, CFSTR path, FString &fullPath); bool GetFullPath(CFSTR path, FString &fullPath); diff --git a/src/plugins/7zip/7za/cpp/Windows/FileSystem.cpp b/src/plugins/7zip/7za/cpp/Windows/FileSystem.cpp index 6c1f48a2..6cb98086 100644 --- a/src/plugins/7zip/7za/cpp/Windows/FileSystem.cpp +++ b/src/plugins/7zip/7za/cpp/Windows/FileSystem.cpp @@ -9,7 +9,7 @@ #endif #include "FileSystem.h" -#include "Defs.h" +#include "WinDefs.h" #ifndef _UNICODE extern bool g_IsNT; @@ -19,6 +19,8 @@ namespace NWindows { namespace NFile { namespace NSystem { +#ifdef _WIN32 + bool MyGetVolumeInformation( CFSTR rootPath, UString &volumeName, @@ -69,19 +71,26 @@ UINT MyGetDriveType(CFSTR pathName) } } -typedef BOOL (WINAPI * GetDiskFreeSpaceExA_Pointer)( +#if !defined(Z7_WIN32_WINNT_MIN) || Z7_WIN32_WINNT_MIN < 0x0400 +// GetDiskFreeSpaceEx requires Windows95-OSR2, NT4 +#define Z7_USE_DYN_GetDiskFreeSpaceEx +#endif + +#ifdef Z7_USE_DYN_GetDiskFreeSpaceEx +typedef BOOL (WINAPI * Func_GetDiskFreeSpaceExA)( LPCSTR lpDirectoryName, // directory name PULARGE_INTEGER lpFreeBytesAvailable, // bytes available to caller PULARGE_INTEGER lpTotalNumberOfBytes, // bytes on disk PULARGE_INTEGER lpTotalNumberOfFreeBytes // free bytes on disk ); -typedef BOOL (WINAPI * GetDiskFreeSpaceExW_Pointer)( +typedef BOOL (WINAPI * Func_GetDiskFreeSpaceExW)( LPCWSTR lpDirectoryName, // directory name PULARGE_INTEGER lpFreeBytesAvailable, // bytes available to caller PULARGE_INTEGER lpTotalNumberOfBytes, // bytes on disk PULARGE_INTEGER lpTotalNumberOfFreeBytes // free bytes on disk ); +#endif bool MyGetDiskFreeSpace(CFSTR rootPath, UInt64 &clusterSize, UInt64 &totalSize, UInt64 &freeSize) { @@ -90,12 +99,22 @@ bool MyGetDiskFreeSpace(CFSTR rootPath, UInt64 &clusterSize, UInt64 &totalSize, #ifndef _UNICODE if (!g_IsNT) { - GetDiskFreeSpaceExA_Pointer pGetDiskFreeSpaceEx = (GetDiskFreeSpaceExA_Pointer)GetProcAddress( - GetModuleHandle(TEXT("kernel32.dll")), "GetDiskFreeSpaceExA"); - if (pGetDiskFreeSpaceEx) +#ifdef Z7_USE_DYN_GetDiskFreeSpaceEx + const + Func_GetDiskFreeSpaceExA f = Z7_GET_PROC_ADDRESS( + Func_GetDiskFreeSpaceExA, GetModuleHandle(TEXT("kernel32.dll")), + "GetDiskFreeSpaceExA"); + if (f) +#endif { ULARGE_INTEGER freeBytesToCaller2, totalSize2, freeSize2; - sizeIsDetected = BOOLToBool(pGetDiskFreeSpaceEx(fs2fas(rootPath), &freeBytesToCaller2, &totalSize2, &freeSize2)); + sizeIsDetected = BOOLToBool( +#ifdef Z7_USE_DYN_GetDiskFreeSpaceEx + f +#else + GetDiskFreeSpaceExA +#endif + (fs2fas(rootPath), &freeBytesToCaller2, &totalSize2, &freeSize2)); totalSize = totalSize2.QuadPart; freeSize = freeSize2.QuadPart; } @@ -105,12 +124,22 @@ bool MyGetDiskFreeSpace(CFSTR rootPath, UInt64 &clusterSize, UInt64 &totalSize, else #endif { - GetDiskFreeSpaceExW_Pointer pGetDiskFreeSpaceEx = (GetDiskFreeSpaceExW_Pointer)GetProcAddress( - GetModuleHandle(TEXT("kernel32.dll")), "GetDiskFreeSpaceExW"); - if (pGetDiskFreeSpaceEx) +#ifdef Z7_USE_DYN_GetDiskFreeSpaceEx + const + Func_GetDiskFreeSpaceExW f = Z7_GET_PROC_ADDRESS( + Func_GetDiskFreeSpaceExW, GetModuleHandle(TEXT("kernel32.dll")), + "GetDiskFreeSpaceExW"); + if (f) +#endif { ULARGE_INTEGER freeBytesToCaller2, totalSize2, freeSize2; - sizeIsDetected = BOOLToBool(pGetDiskFreeSpaceEx(fs2us(rootPath), &freeBytesToCaller2, &totalSize2, &freeSize2)); + sizeIsDetected = BOOLToBool( +#ifdef Z7_USE_DYN_GetDiskFreeSpaceEx + f +#else + GetDiskFreeSpaceExW +#endif + (fs2us(rootPath), &freeBytesToCaller2, &totalSize2, &freeSize2)); totalSize = totalSize2.QuadPart; freeSize = freeSize2.QuadPart; } @@ -126,6 +155,33 @@ bool MyGetDiskFreeSpace(CFSTR rootPath, UInt64 &clusterSize, UInt64 &totalSize, return true; } +#endif + +/* +bool Is_File_LimitedBy_4GB(CFSTR _path, bool &isFsDetected) +{ + isFsDetected = false; + FString path (_path); + path.DeleteFrom(NName::GetRootPrefixSize(path)); + // GetVolumeInformation supports super paths. + // NName::If_IsSuperPath_RemoveSuperPrefix(path); + if (!path.IsEmpty()) + { + DWORD volumeSerialNumber, maximumComponentLength, fileSystemFlags; + UString volName, fileSystemName; + if (MyGetVolumeInformation(path, volName, + &volumeSerialNumber, &maximumComponentLength, &fileSystemFlags, + fileSystemName)) + { + isFsDetected = true; + if (fileSystemName.IsPrefixedBy_Ascii_NoCase("fat")) + return true; + } + } + return false; +} +*/ + }}} #endif diff --git a/src/plugins/7zip/7za/cpp/Windows/FileSystem.h b/src/plugins/7zip/7za/cpp/Windows/FileSystem.h index 9076ea13..9f9e399c 100644 --- a/src/plugins/7zip/7za/cpp/Windows/FileSystem.h +++ b/src/plugins/7zip/7za/cpp/Windows/FileSystem.h @@ -1,7 +1,7 @@ // Windows/FileSystem.h -#ifndef __WINDOWS_FILE_SYSTEM_H -#define __WINDOWS_FILE_SYSTEM_H +#ifndef ZIP7_INC_WINDOWS_FILE_SYSTEM_H +#define ZIP7_INC_WINDOWS_FILE_SYSTEM_H #include "../Common/MyString.h" #include "../Common/MyTypes.h" @@ -10,6 +10,8 @@ namespace NWindows { namespace NFile { namespace NSystem { +#ifdef _WIN32 + bool MyGetVolumeInformation( CFSTR rootPath , UString &volumeName, @@ -22,6 +24,8 @@ UINT MyGetDriveType(CFSTR pathName); bool MyGetDiskFreeSpace(CFSTR rootPath, UInt64 &clusterSize, UInt64 &totalSize, UInt64 &freeSize); +#endif + }}} #endif diff --git a/src/plugins/7zip/7za/cpp/Windows/Handle.h b/src/plugins/7zip/7za/cpp/Windows/Handle.h index bb7cb705..6ae09ecf 100644 --- a/src/plugins/7zip/7za/cpp/Windows/Handle.h +++ b/src/plugins/7zip/7za/cpp/Windows/Handle.h @@ -1,11 +1,13 @@ // Windows/Handle.h -#ifndef __WINDOWS_HANDLE_H -#define __WINDOWS_HANDLE_H +#ifndef ZIP7_INC_WINDOWS_HANDLE_H +#define ZIP7_INC_WINDOWS_HANDLE_H + +#include "../Common/MyWindows.h" namespace NWindows { -class CHandle +class CHandle MY_UNCOPYABLE { protected: HANDLE _handle; @@ -26,7 +28,7 @@ class CHandle void Attach(HANDLE handle) { _handle = handle; } HANDLE Detach() { - HANDLE handle = _handle; + const HANDLE handle = _handle; _handle = NULL; return handle; } diff --git a/src/plugins/7zip/7za/cpp/Windows/MemoryGlobal.h b/src/plugins/7zip/7za/cpp/Windows/MemoryGlobal.h index c217510e..68525915 100644 --- a/src/plugins/7zip/7za/cpp/Windows/MemoryGlobal.h +++ b/src/plugins/7zip/7za/cpp/Windows/MemoryGlobal.h @@ -1,7 +1,7 @@ // Windows/MemoryGlobal.h -#ifndef __WINDOWS_MEMORY_GLOBAL_H -#define __WINDOWS_MEMORY_GLOBAL_H +#ifndef ZIP7_INC_WINDOWS_MEMORY_GLOBAL_H +#define ZIP7_INC_WINDOWS_MEMORY_GLOBAL_H #include "../Common/MyWindows.h" @@ -12,7 +12,7 @@ class CGlobal { HGLOBAL _global; public: - CGlobal(): _global(NULL){}; + CGlobal(): _global(NULL) {} ~CGlobal() { Free(); } operator HGLOBAL() const { return _global; } void Attach(HGLOBAL hGlobal) @@ -22,7 +22,7 @@ class CGlobal } HGLOBAL Detach() { - HGLOBAL h = _global; + const HGLOBAL h = _global; _global = NULL; return h; } @@ -42,10 +42,10 @@ class CGlobalLock CGlobalLock(HGLOBAL hGlobal): _global(hGlobal) { _ptr = GlobalLock(hGlobal); - }; + } ~CGlobalLock() { - if (_ptr != NULL) + if (_ptr) GlobalUnlock(_global); } }; diff --git a/src/plugins/7zip/7za/cpp/Windows/MemoryLock.cpp b/src/plugins/7zip/7za/cpp/Windows/MemoryLock.cpp index b628a03e..bc2d7c71 100644 --- a/src/plugins/7zip/7za/cpp/Windows/MemoryLock.cpp +++ b/src/plugins/7zip/7za/cpp/Windows/MemoryLock.cpp @@ -2,6 +2,8 @@ #include "StdAfx.h" +#include "../../C/CpuArch.h" + #include "MemoryLock.h" namespace NWindows { @@ -19,7 +21,10 @@ typedef BOOL (WINAPI * Func_LookupPrivilegeValue)(LPCTSTR lpSystemName, LPCTSTR typedef BOOL (WINAPI * Func_AdjustTokenPrivileges)(HANDLE TokenHandle, BOOL DisableAllPrivileges, PTOKEN_PRIVILEGES NewState, DWORD BufferLength, PTOKEN_PRIVILEGES PreviousState, PDWORD ReturnLength); } -#define GET_PROC_ADDR(fff, name) Func_ ## fff my_ ## fff = (Func_ ## fff)GetProcAddress(hModule, name) + +#define GET_PROC_ADDR(fff, name) \ + const Func_ ## fff my_ ## fff = Z7_GET_PROC_ADDRESS( \ + Func_ ## fff, hModule, name); #endif bool EnablePrivilege(LPCTSTR privilegeName, bool enable) @@ -28,13 +33,21 @@ bool EnablePrivilege(LPCTSTR privilegeName, bool enable) #ifndef _UNICODE - HMODULE hModule = ::LoadLibrary(TEXT("Advapi32.dll")); - if (hModule == NULL) + const HMODULE hModule = ::LoadLibrary(TEXT("advapi32.dll")); + if (!hModule) return false; - GET_PROC_ADDR(OpenProcessToken, "OpenProcessToken"); - GET_PROC_ADDR(LookupPrivilegeValue, "LookupPrivilegeValueA"); - GET_PROC_ADDR(AdjustTokenPrivileges, "AdjustTokenPrivileges"); +Z7_DIAGNOSTIC_IGNORE_CAST_FUNCTION + + GET_PROC_ADDR( + OpenProcessToken, + "OpenProcessToken") + GET_PROC_ADDR( + LookupPrivilegeValue, + "LookupPrivilegeValueA") + GET_PROC_ADDR( + AdjustTokenPrivileges, + "AdjustTokenPrivileges") if (my_OpenProcessToken && my_AdjustTokenPrivileges && @@ -67,6 +80,48 @@ bool EnablePrivilege(LPCTSTR privilegeName, bool enable) return res; } + +Z7_DIAGNOSTIC_IGNORE_CAST_FUNCTION + +typedef void (WINAPI * Func_RtlGetVersion) (OSVERSIONINFOEXW *); + +/* + We suppose that Window 10 works incorrectly with "Large Pages" at: + - Windows 10 1703 (15063) : incorrect allocating after VirtualFree() + - Windows 10 1709 (16299) : incorrect allocating after VirtualFree() + - Windows 10 1809 (17763) : the failures for blocks of 1 GiB and larger, + if CPU doesn't support 1 GB pages. + Windows 10 1903 (18362) probably works correctly. +*/ + +unsigned Get_LargePages_RiskLevel() +{ + OSVERSIONINFOEXW vi; + const HMODULE ntdll = ::GetModuleHandleW(L"ntdll.dll"); + if (!ntdll) + return 0; + const + Func_RtlGetVersion func = Z7_GET_PROC_ADDRESS( + Func_RtlGetVersion, ntdll, + "RtlGetVersion"); + if (!func) + return 0; + func(&vi); + if (vi.dwPlatformId != VER_PLATFORM_WIN32_NT) + return 0; + if (vi.dwMajorVersion + vi.dwMinorVersion != 10) + return 0; + if (vi.dwBuildNumber <= 16299) + return 1; + + #ifdef MY_CPU_X86_OR_AMD64 + if (vi.dwBuildNumber < 18362 && !CPU_IsSupported_PageGB()) + return 1; + #endif + + return 0; +} + #endif }} diff --git a/src/plugins/7zip/7za/cpp/Windows/MemoryLock.h b/src/plugins/7zip/7za/cpp/Windows/MemoryLock.h index 4a5dafcf..2b850029 100644 --- a/src/plugins/7zip/7za/cpp/Windows/MemoryLock.h +++ b/src/plugins/7zip/7za/cpp/Windows/MemoryLock.h @@ -1,7 +1,7 @@ // Windows/MemoryLock.h -#ifndef __WINDOWS_MEMORY_LOCK_H -#define __WINDOWS_MEMORY_LOCK_H +#ifndef ZIP7_INC_WINDOWS_MEMORY_LOCK_H +#define ZIP7_INC_WINDOWS_MEMORY_LOCK_H #include "../Common/MyWindows.h" @@ -31,6 +31,8 @@ inline void EnablePrivilege_SymLink() // Do we need to set SE_BACKUP_NAME ? } +unsigned Get_LargePages_RiskLevel(); + #endif }} diff --git a/src/plugins/7zip/7za/cpp/Windows/Menu.cpp b/src/plugins/7zip/7za/cpp/Windows/Menu.cpp index 3834881a..af54c668 100644 --- a/src/plugins/7zip/7za/cpp/Windows/Menu.cpp +++ b/src/plugins/7zip/7za/cpp/Windows/Menu.cpp @@ -22,30 +22,60 @@ contain additional member: HBITMAP hbmpItem; #endif If we compile the source code with (WINVER >= 0x0500), some functions -will not work at NT 4.0, if cbSize is set as sizeof(MENUITEMINFO*). -So we use size of old version of structure. */ +will not work at NT4, if cbSize is set as sizeof(MENUITEMINFO). +So we use size of old version of structure in some conditions. +Win98 probably supports full structure including hbmpItem. + +We have 2 ways to get/set string in menu item: +win95/NT4: we must use MIIM_TYPE only. + MIIM_TYPE : Retrieves or sets the fType and dwTypeData members. +win98/win2000: there are new flags that can be used instead of MIIM_TYPE: + MIIM_FTYPE : Retrieves or sets the fType member. + MIIM_STRING : Retrieves or sets the dwTypeData member. + +Windows versions probably support MIIM_TYPE flag, if we set MENUITEMINFO::cbSize +as sizeof of old (small) MENUITEMINFO that doesn't include (hbmpItem) field. +But do all Windows versions support old MIIM_TYPE flag, if we use +MENUITEMINFO::cbSize as sizeof of new (big) MENUITEMINFO including (hbmpItem) field ? +win10 probably supports any combination of small/big (cbSize) and old/new MIIM_TYPE/MIIM_STRING. +*/ #if defined(UNDER_CE) || defined(_WIN64) || (WINVER < 0x0500) + #ifndef _UNICODE #define my_compatib_MENUITEMINFOA_size sizeof(MENUITEMINFOA) + #endif #define my_compatib_MENUITEMINFOW_size sizeof(MENUITEMINFOW) #else #define MY_STRUCT_SIZE_BEFORE(structname, member) ((UINT)(UINT_PTR)((LPBYTE)(&((structname*)0)->member) - (LPBYTE)(structname*)0)) + #ifndef _UNICODE #define my_compatib_MENUITEMINFOA_size MY_STRUCT_SIZE_BEFORE(MENUITEMINFOA, hbmpItem) + #endif #define my_compatib_MENUITEMINFOW_size MY_STRUCT_SIZE_BEFORE(MENUITEMINFOW, hbmpItem) +#if defined(__clang__) && __clang_major__ >= 13 +// error : performing pointer subtraction with a null pointer may have undefined behavior +#pragma GCC diagnostic ignored "-Wnull-pointer-subtraction" +#endif #endif + +#define COPY_MENUITEM_field(d, s, name) \ + d.name = s.name; + +#define COPY_MENUITEM_fields(d, s) \ + COPY_MENUITEM_field(d, s, fMask) \ + COPY_MENUITEM_field(d, s, fType) \ + COPY_MENUITEM_field(d, s, fState) \ + COPY_MENUITEM_field(d, s, wID) \ + COPY_MENUITEM_field(d, s, hSubMenu) \ + COPY_MENUITEM_field(d, s, hbmpChecked) \ + COPY_MENUITEM_field(d, s, hbmpUnchecked) \ + COPY_MENUITEM_field(d, s, dwItemData) \ + static void ConvertItemToSysForm(const CMenuItem &item, MENUITEMINFOW &si) { ZeroMemory(&si, sizeof(si)); si.cbSize = my_compatib_MENUITEMINFOW_size; // sizeof(si); - si.fMask = item.fMask; - si.fType = item.fType; - si.fState = item.fState; - si.wID = item.wID; - si.hSubMenu = item.hSubMenu; - si.hbmpChecked = item.hbmpChecked; - si.hbmpUnchecked = item.hbmpUnchecked; - si.dwItemData = item.dwItemData; + COPY_MENUITEM_fields(si, item) } #ifndef _UNICODE @@ -53,62 +83,63 @@ static void ConvertItemToSysForm(const CMenuItem &item, MENUITEMINFOA &si) { ZeroMemory(&si, sizeof(si)); si.cbSize = my_compatib_MENUITEMINFOA_size; // sizeof(si); - si.fMask = item.fMask; - si.fType = item.fType; - si.fState = item.fState; - si.wID = item.wID; - si.hSubMenu = item.hSubMenu; - si.hbmpChecked = item.hbmpChecked; - si.hbmpUnchecked = item.hbmpUnchecked; - si.dwItemData = item.dwItemData; + COPY_MENUITEM_fields(si, item) } #endif static void ConvertItemToMyForm(const MENUITEMINFOW &si, CMenuItem &item) { - item.fMask = si.fMask; - item.fType = si.fType; - item.fState = si.fState; - item.wID = si.wID; - item.hSubMenu = si.hSubMenu; - item.hbmpChecked = si.hbmpChecked; - item.hbmpUnchecked = si.hbmpUnchecked; - item.dwItemData = si.dwItemData; + COPY_MENUITEM_fields(item, si) } #ifndef _UNICODE static void ConvertItemToMyForm(const MENUITEMINFOA &si, CMenuItem &item) { - item.fMask = si.fMask; - item.fType = si.fType; - item.fState = si.fState; - item.wID = si.wID; - item.hSubMenu = si.hSubMenu; - item.hbmpChecked = si.hbmpChecked; - item.hbmpUnchecked = si.hbmpUnchecked; - item.dwItemData = si.dwItemData; + COPY_MENUITEM_fields(item, si) } #endif -bool CMenu::GetItem(UINT itemIndex, bool byPosition, CMenuItem &item) + +bool CMenu::GetItem(UINT itemIndex, bool byPosition, CMenuItem &item) const { - const UINT kMaxSize = 512; + item.StringValue.Empty(); + const unsigned kMaxSize = 512; #ifndef _UNICODE if (!g_IsNT) { - CHAR s[kMaxSize + 1]; MENUITEMINFOA si; ConvertItemToSysForm(item, si); - if (item.IsString()) + const bool isString = item.IsString(); + unsigned bufSize = kMaxSize; + AString a; + if (isString) { - si.cch = kMaxSize; - si.dwTypeData = s; + si.cch = bufSize; + si.dwTypeData = a.GetBuf(bufSize); } - if (GetItemInfo(itemIndex, byPosition, &si)) + bool res = GetItemInfo(itemIndex, byPosition, &si); + if (isString) + a.ReleaseBuf_CalcLen(bufSize); + if (!res) + return false; { + if (isString && si.cch >= bufSize - 1) + { + si.dwTypeData = NULL; + res = GetItemInfo(itemIndex, byPosition, &si); + if (!res) + return false; + si.cch++; + bufSize = si.cch; + si.dwTypeData = a.GetBuf(bufSize); + res = GetItemInfo(itemIndex, byPosition, &si); + a.ReleaseBuf_CalcLen(bufSize); + if (!res) + return false; + } ConvertItemToMyForm(si, item); - if (item.IsString()) - item.StringValue = GetUnicodeString(s); + if (isString) + item.StringValue = GetUnicodeString(a); return true; } } @@ -116,24 +147,45 @@ bool CMenu::GetItem(UINT itemIndex, bool byPosition, CMenuItem &item) #endif { wchar_t s[kMaxSize + 1]; + s[0] = 0; MENUITEMINFOW si; ConvertItemToSysForm(item, si); - if (item.IsString()) + const bool isString = item.IsString(); + unsigned bufSize = kMaxSize; + if (isString) { - si.cch = kMaxSize; + si.cch = bufSize; si.dwTypeData = s; } - if (GetItemInfo(itemIndex, byPosition, &si)) + bool res = GetItemInfo(itemIndex, byPosition, &si); + if (!res) + return false; + if (isString) { - ConvertItemToMyForm(si, item); - if (item.IsString()) - item.StringValue = s; - return true; + s[Z7_ARRAY_SIZE(s) - 1] = 0; + item.StringValue = s; + if (si.cch >= bufSize - 1) + { + si.dwTypeData = NULL; + res = GetItemInfo(itemIndex, byPosition, &si); + if (!res) + return false; + si.cch++; + bufSize = si.cch; + si.dwTypeData = item.StringValue.GetBuf(bufSize); + res = GetItemInfo(itemIndex, byPosition, &si); + item.StringValue.ReleaseBuf_CalcLen(bufSize); + if (!res) + return false; + } + // if (item.StringValue.Len() != si.cch) throw 123; // for debug } + ConvertItemToMyForm(si, item); + return true; } - return false; } + bool CMenu::SetItem(UINT itemIndex, bool byPosition, const CMenuItem &item) { #ifndef _UNICODE @@ -145,7 +197,7 @@ bool CMenu::SetItem(UINT itemIndex, bool byPosition, const CMenuItem &item) if (item.IsString()) { s = GetSystemString(item.StringValue); - si.dwTypeData = (LPTSTR)(LPCTSTR)s; + si.dwTypeData = s.Ptr_non_const(); } return SetItemInfo(itemIndex, byPosition, &si); } @@ -155,11 +207,12 @@ bool CMenu::SetItem(UINT itemIndex, bool byPosition, const CMenuItem &item) MENUITEMINFOW si; ConvertItemToSysForm(item, si); if (item.IsString()) - si.dwTypeData = (LPWSTR)(LPCWSTR)item.StringValue; + si.dwTypeData = item.StringValue.Ptr_non_const(); return SetItemInfo(itemIndex, byPosition, &si); } } + bool CMenu::InsertItem(UINT itemIndex, bool byPosition, const CMenuItem &item) { #ifndef _UNICODE @@ -171,7 +224,7 @@ bool CMenu::InsertItem(UINT itemIndex, bool byPosition, const CMenuItem &item) if (item.IsString()) { s = GetSystemString(item.StringValue); - si.dwTypeData = (LPTSTR)(LPCTSTR)s; + si.dwTypeData = s.Ptr_non_const(); } return InsertItem(itemIndex, byPosition, &si); } @@ -181,14 +234,14 @@ bool CMenu::InsertItem(UINT itemIndex, bool byPosition, const CMenuItem &item) MENUITEMINFOW si; ConvertItemToSysForm(item, si); if (item.IsString()) - si.dwTypeData = (LPWSTR)(LPCWSTR)item.StringValue; + si.dwTypeData = item.StringValue.Ptr_non_const(); #ifdef UNDER_CE UINT flags = (item.fType & MFT_SEPARATOR) ? MF_SEPARATOR : MF_STRING; - UINT id = item.wID; + UINT_PTR id = item.wID; if ((item.fMask & MIIM_SUBMENU) != 0) { flags |= MF_POPUP; - id = (UINT)item.hSubMenu; + id = (UINT_PTR)item.hSubMenu; } if (!Insert(itemIndex, flags | (byPosition ? MF_BYPOSITION : MF_BYCOMMAND), id, item.StringValue)) return false; diff --git a/src/plugins/7zip/7za/cpp/Windows/Menu.h b/src/plugins/7zip/7za/cpp/Windows/Menu.h index aacdc7c3..00aa692f 100644 --- a/src/plugins/7zip/7za/cpp/Windows/Menu.h +++ b/src/plugins/7zip/7za/cpp/Windows/Menu.h @@ -1,14 +1,27 @@ // Windows/Menu.h -#ifndef __WINDOWS_MENU_H -#define __WINDOWS_MENU_H +#ifndef ZIP7_INC_WINDOWS_MENU_H +#define ZIP7_INC_WINDOWS_MENU_H +#include "../Common/MyWindows.h" #include "../Common/MyString.h" -#include "Defs.h" +#include "WinDefs.h" namespace NWindows { +#ifndef MIIM_STRING +#define MIIM_STRING 0x00000040 +#endif +/* +#ifndef MIIM_BITMAP +#define MIIM_BITMAP 0x00000080 +#endif +*/ +#ifndef MIIM_FTYPE +#define MIIM_FTYPE 0x00000100 +#endif + struct CMenuItem { UString StringValue; @@ -23,24 +36,23 @@ struct CMenuItem // LPTSTR dwTypeData; // UINT cch; // HBITMAP hbmpItem; - bool IsString() const // change it MIIM_STRING - { return ((fMask & MIIM_TYPE) != 0 && (fType == MFT_STRING)); } + bool IsString() const { return (fMask & (MIIM_TYPE | MIIM_STRING)) != 0; } bool IsSeparator() const { return (fType == MFT_SEPARATOR); } - CMenuItem(): fMask(0), fType(0), fState(0), wID(0), hSubMenu(0), hbmpChecked(0), - hbmpUnchecked(0), dwItemData(0) {} + CMenuItem(): fMask(0), fType(0), fState(0), wID(0), + hSubMenu(NULL), hbmpChecked(NULL), hbmpUnchecked(NULL), dwItemData(0) {} }; class CMenu { HMENU _menu; public: - CMenu(): _menu(NULL) {}; + CMenu(): _menu(NULL) {} operator HMENU() const { return _menu; } void Attach(HMENU menu) { _menu = menu; } HMENU Detach() { - HMENU menu = _menu; + const HMENU menu = _menu; _menu = NULL; return menu; } @@ -59,27 +71,27 @@ class CMenu bool Destroy() { - if (_menu == NULL) + if (!_menu) return false; return BOOLToBool(::DestroyMenu(Detach())); } - int GetItemCount() + int GetItemCount() const { #ifdef UNDER_CE - for (int i = 0;; i++) + for (unsigned i = 0;; i++) { CMenuItem item; item.fMask = MIIM_STATE; if (!GetItem(i, true, item)) - return i; + return (int)i; } #else return GetMenuItemCount(_menu); #endif } - HMENU GetSubMenu(int pos) { return ::GetSubMenu(_menu, pos); } + HMENU GetSubMenu(int pos) const { return ::GetSubMenu(_menu, pos); } #ifndef UNDER_CE /* bool GetItemString(UINT idItem, UINT flag, CSysString &result) @@ -93,11 +105,11 @@ class CMenu return (len != 0); } */ - UINT GetItemID(int pos) { return ::GetMenuItemID(_menu, pos); } - UINT GetItemState(UINT id, UINT flags) { return ::GetMenuState(_menu, id, flags); } + UINT GetItemID(int pos) const { return ::GetMenuItemID(_menu, pos); } + UINT GetItemState(UINT id, UINT flags) const { return ::GetMenuState(_menu, id, flags); } #endif - bool GetItemInfo(UINT itemIndex, bool byPosition, LPMENUITEMINFO itemInfo) + bool GetItemInfo(UINT itemIndex, bool byPosition, LPMENUITEMINFO itemInfo) const { return BOOLToBool(::GetMenuItemInfo(_menu, itemIndex, BoolToBOOL(byPosition), itemInfo)); } bool SetItemInfo(UINT itemIndex, bool byPosition, LPMENUITEMINFO itemInfo) { return BOOLToBool(::SetMenuItemInfo(_menu, itemIndex, BoolToBOOL(byPosition), itemInfo)); } @@ -118,7 +130,7 @@ class CMenu void RemoveAllItems() { RemoveAllItemsFrom(0); } #ifndef _UNICODE - bool GetItemInfo(UINT itemIndex, bool byPosition, LPMENUITEMINFOW itemInfo) + bool GetItemInfo(UINT itemIndex, bool byPosition, LPMENUITEMINFOW itemInfo) const { return BOOLToBool(::GetMenuItemInfoW(_menu, itemIndex, BoolToBOOL(byPosition), itemInfo)); } bool InsertItem(UINT itemIndex, bool byPosition, LPMENUITEMINFOW itemInfo) { return BOOLToBool(::InsertMenuItemW(_menu, itemIndex, BoolToBOOL(byPosition), itemInfo)); } @@ -127,7 +139,7 @@ class CMenu bool AppendItem(UINT flags, UINT_PTR newItemID, LPCWSTR newItem); #endif - bool GetItem(UINT itemIndex, bool byPosition, CMenuItem &item); + bool GetItem(UINT itemIndex, bool byPosition, CMenuItem &item) const; bool SetItem(UINT itemIndex, bool byPosition, const CMenuItem &item); bool InsertItem(UINT itemIndex, bool byPosition, const CMenuItem &item); @@ -147,10 +159,10 @@ class CMenuDestroyer CMenu *_menu; public: CMenuDestroyer(CMenu &menu): _menu(&menu) {} - CMenuDestroyer(): _menu(0) {} + CMenuDestroyer(): _menu(NULL) {} ~CMenuDestroyer() { if (_menu) _menu->Destroy(); } void Attach(CMenu &menu) { _menu = &menu; } - void Disable() { _menu = 0; } + void Disable() { _menu = NULL; } }; } diff --git a/src/plugins/7zip/7za/cpp/Windows/NationalTime.cpp b/src/plugins/7zip/7za/cpp/Windows/NationalTime.cpp index 0dcd31e0..d3c38de5 100644 --- a/src/plugins/7zip/7za/cpp/Windows/NationalTime.cpp +++ b/src/plugins/7zip/7za/cpp/Windows/NationalTime.cpp @@ -16,8 +16,8 @@ bool MyGetTimeFormat(LCID locale, DWORD flags, CONST SYSTEMTIME *time, if (numChars == 0) return false; numChars = ::GetTimeFormat(locale, flags, time, format, - resultString.GetBuf(numChars), numChars + 1); - resultString.ReleaseBuf_CalcLen(numChars); + resultString.GetBuf((unsigned)numChars), numChars + 1); + resultString.ReleaseBuf_CalcLen((unsigned)numChars); return (numChars != 0); } @@ -29,8 +29,8 @@ bool MyGetDateFormat(LCID locale, DWORD flags, CONST SYSTEMTIME *time, if (numChars == 0) return false; numChars = ::GetDateFormat(locale, flags, time, format, - resultString.GetBuf(numChars), numChars + 1); - resultString.ReleaseBuf_CalcLen(numChars); + resultString.GetBuf((unsigned)numChars), numChars + 1); + resultString.ReleaseBuf_CalcLen((unsigned)numChars); return (numChars != 0); } diff --git a/src/plugins/7zip/7za/cpp/Windows/NationalTime.h b/src/plugins/7zip/7za/cpp/Windows/NationalTime.h index 49b0e5e8..32ba479f 100644 --- a/src/plugins/7zip/7za/cpp/Windows/NationalTime.h +++ b/src/plugins/7zip/7za/cpp/Windows/NationalTime.h @@ -1,7 +1,7 @@ // Windows/NationalTime.h -#ifndef __WINDOWS_NATIONAL_TIME_H -#define __WINDOWS_NATIONAL_TIME_H +#ifndef ZIP7_INC_WINDOWS_NATIONAL_TIME_H +#define ZIP7_INC_WINDOWS_NATIONAL_TIME_H #include "../Common/MyString.h" diff --git a/src/plugins/7zip/7za/cpp/Windows/Net.cpp b/src/plugins/7zip/7za/cpp/Windows/Net.cpp index 47079f37..0ac82d4e 100644 --- a/src/plugins/7zip/7za/cpp/Windows/Net.cpp +++ b/src/plugins/7zip/7za/cpp/Windows/Net.cpp @@ -14,13 +14,41 @@ extern bool g_IsNT; #endif +extern "C" +{ +#if !defined(WNetGetResourceParent) +// #if defined(Z7_OLD_WIN_SDK) +// #if (WINVER >= 0x0400) +DWORD APIENTRY WNetGetResourceParentA(IN LPNETRESOURCEA lpNetResource, + OUT LPVOID lpBuffer, IN OUT LPDWORD lpcbBuffer); +DWORD APIENTRY WNetGetResourceParentW(IN LPNETRESOURCEW lpNetResource, + OUT LPVOID lpBuffer, IN OUT LPDWORD lpcbBuffer); +#ifdef UNICODE +#define WNetGetResourceParent WNetGetResourceParentW +#else +#define WNetGetResourceParent WNetGetResourceParentA +#endif + +DWORD APIENTRY WNetGetResourceInformationA(IN LPNETRESOURCEA lpNetResource, + OUT LPVOID lpBuffer, IN OUT LPDWORD lpcbBuffer, OUT LPSTR *lplpSystem); +DWORD APIENTRY WNetGetResourceInformationW(IN LPNETRESOURCEW lpNetResource, + OUT LPVOID lpBuffer, IN OUT LPDWORD lpcbBuffer, OUT LPWSTR *lplpSystem); +#ifdef UNICODE +#define WNetGetResourceInformation WNetGetResourceInformationW +#else +#define WNetGetResourceInformation WNetGetResourceInformationA +#endif +// #endif // (WINVER >= 0x0400) +#endif +} + namespace NWindows { namespace NNet { DWORD CEnum::Open(DWORD scope, DWORD type, DWORD usage, LPNETRESOURCE netResource) { Close(); - DWORD result = ::WNetOpenEnum(scope, type, usage, netResource, &_handle); + const DWORD result = ::WNetOpenEnum(scope, type, usage, netResource, &_handle); _handleAllocated = (result == NO_ERROR); return result; } @@ -29,17 +57,17 @@ DWORD CEnum::Open(DWORD scope, DWORD type, DWORD usage, LPNETRESOURCE netResourc DWORD CEnum::Open(DWORD scope, DWORD type, DWORD usage, LPNETRESOURCEW netResource) { Close(); - DWORD result = ::WNetOpenEnumW(scope, type, usage, netResource, &_handle); + const DWORD result = ::WNetOpenEnumW(scope, type, usage, netResource, &_handle); _handleAllocated = (result == NO_ERROR); return result; } #endif -static void SetComplexString(bool &defined, CSysString &destString, LPCTSTR srsString) +static void SetComplexString(bool &defined, CSysString &destString, LPCTSTR srcString) { - defined = (srsString != 0); + defined = (srcString != NULL); if (defined) - destString = srsString; + destString = srcString; else destString.Empty(); } @@ -59,9 +87,9 @@ static void ConvertNETRESOURCEToCResource(const NETRESOURCE &netResource, CResou static void SetComplexString2(LPTSTR *destString, bool defined, const CSysString &srcString) { if (defined) - *destString = (TCHAR *)(const TCHAR *)srcString; + *destString = srcString.Ptr_non_const(); else - *destString = 0; + *destString = NULL; } static void ConvertCResourceToNETRESOURCE(const CResource &resource, NETRESOURCE &netResource) @@ -78,11 +106,11 @@ static void ConvertCResourceToNETRESOURCE(const CResource &resource, NETRESOURCE #ifndef _UNICODE -static void SetComplexString(bool &defined, UString &destString, LPCWSTR srsString) +static void SetComplexString(bool &defined, UString &destString, LPCWSTR src) { - defined = (srsString != 0); + defined = (src != NULL); if (defined) - destString = srsString; + destString = src; else destString.Empty(); } @@ -102,9 +130,9 @@ static void ConvertNETRESOURCEToCResource(const NETRESOURCEW &netResource, CReso static void SetComplexString2(LPWSTR *destString, bool defined, const UString &srcString) { if (defined) - *destString = (WCHAR *)(const WCHAR *)srcString; + *destString = srcString.Ptr_non_const(); else - *destString = 0; + *destString = NULL; } static void ConvertCResourceToNETRESOURCE(const CResourceW &resource, NETRESOURCEW &netResource) @@ -141,10 +169,8 @@ static void ConvertResourceToResourceW(const CResource &resource, CResourceW &re DWORD CEnum::Open(DWORD scope, DWORD type, DWORD usage, const CResource *resource) { NETRESOURCE netResource; - LPNETRESOURCE pointer; - if (resource == 0) - pointer = 0; - else + LPNETRESOURCE pointer = NULL; + if (resource) { ConvertCResourceToNETRESOURCE(*resource, netResource); pointer = &netResource; @@ -158,21 +184,17 @@ DWORD CEnum::Open(DWORD scope, DWORD type, DWORD usage, const CResourceW *resour if (g_IsNT) { NETRESOURCEW netResource; - LPNETRESOURCEW pointer; - if (resource == 0) - pointer = 0; - else + LPNETRESOURCEW pointer = NULL; + if (resource) { ConvertCResourceToNETRESOURCE(*resource, netResource); pointer = &netResource; } return Open(scope, type, usage, pointer); } - CResource *pointer; CResource resourceA; - if (resource == 0) - pointer = 0; - else + CResource *pointer = NULL; + if (resource) { ConvertResourceWToResource(*resource, resourceA); pointer = &resourceA; @@ -185,7 +207,7 @@ DWORD CEnum::Close() { if (!_handleAllocated) return NO_ERROR; - DWORD result = ::WNetCloseEnum(_handle); + const DWORD result = ::WNetCloseEnum(_handle); _handleAllocated = (result != NO_ERROR); return result; } @@ -206,11 +228,11 @@ DWORD CEnum::Next(CResource &resource) { const DWORD kBufferSize = 16384; CByteArr byteBuffer(kBufferSize); - LPNETRESOURCE lpnrLocal = (LPNETRESOURCE) (BYTE *)(byteBuffer); + LPNETRESOURCE lpnrLocal = (LPNETRESOURCE) (void *) (BYTE *)(byteBuffer); ZeroMemory(lpnrLocal, kBufferSize); DWORD bufferSize = kBufferSize; DWORD numEntries = 1; - DWORD result = Next(&numEntries, lpnrLocal, &bufferSize); + const DWORD result = Next(&numEntries, lpnrLocal, &bufferSize); if (result != NO_ERROR) return result; if (numEntries != 1) @@ -226,11 +248,11 @@ DWORD CEnum::Next(CResourceW &resource) { const DWORD kBufferSize = 16384; CByteArr byteBuffer(kBufferSize); - LPNETRESOURCEW lpnrLocal = (LPNETRESOURCEW) (BYTE *)(byteBuffer); + LPNETRESOURCEW lpnrLocal = (LPNETRESOURCEW) (void *) (BYTE *)(byteBuffer); ZeroMemory(lpnrLocal, kBufferSize); DWORD bufferSize = kBufferSize; DWORD numEntries = 1; - DWORD result = NextW(&numEntries, lpnrLocal, &bufferSize); + const DWORD result = NextW(&numEntries, lpnrLocal, &bufferSize); if (result != NO_ERROR) return result; if (numEntries != 1) @@ -239,7 +261,7 @@ DWORD CEnum::Next(CResourceW &resource) return result; } CResource resourceA; - DWORD result = Next(resourceA); + const DWORD result = Next(resourceA); ConvertResourceToResourceW(resourceA, resource); return result; } @@ -250,12 +272,12 @@ DWORD GetResourceParent(const CResource &resource, CResource &parentResource) { const DWORD kBufferSize = 16384; CByteArr byteBuffer(kBufferSize); - LPNETRESOURCE lpnrLocal = (LPNETRESOURCE) (BYTE *)(byteBuffer); + LPNETRESOURCE lpnrLocal = (LPNETRESOURCE) (void *) (BYTE *)(byteBuffer); ZeroMemory(lpnrLocal, kBufferSize); DWORD bufferSize = kBufferSize; NETRESOURCE netResource; ConvertCResourceToNETRESOURCE(resource, netResource); - DWORD result = ::WNetGetResourceParent(&netResource, lpnrLocal, &bufferSize); + const DWORD result = ::WNetGetResourceParent(&netResource, lpnrLocal, &bufferSize); if (result != NO_ERROR) return result; ConvertNETRESOURCEToCResource(lpnrLocal[0], parentResource); @@ -269,12 +291,12 @@ DWORD GetResourceParent(const CResourceW &resource, CResourceW &parentResource) { const DWORD kBufferSize = 16384; CByteArr byteBuffer(kBufferSize); - LPNETRESOURCEW lpnrLocal = (LPNETRESOURCEW) (BYTE *)(byteBuffer); + LPNETRESOURCEW lpnrLocal = (LPNETRESOURCEW) (void *) (BYTE *)(byteBuffer); ZeroMemory(lpnrLocal, kBufferSize); DWORD bufferSize = kBufferSize; NETRESOURCEW netResource; ConvertCResourceToNETRESOURCE(resource, netResource); - DWORD result = ::WNetGetResourceParentW(&netResource, lpnrLocal, &bufferSize); + const DWORD result = ::WNetGetResourceParentW(&netResource, lpnrLocal, &bufferSize); if (result != NO_ERROR) return result; ConvertNETRESOURCEToCResource(lpnrLocal[0], parentResource); @@ -282,7 +304,7 @@ DWORD GetResourceParent(const CResourceW &resource, CResourceW &parentResource) } CResource resourceA, parentResourceA; ConvertResourceWToResource(resource, resourceA); - DWORD result = GetResourceParent(resourceA, parentResourceA); + const DWORD result = GetResourceParent(resourceA, parentResourceA); ConvertResourceToResourceW(parentResourceA, parentResource); return result; } @@ -293,17 +315,17 @@ DWORD GetResourceInformation(const CResource &resource, { const DWORD kBufferSize = 16384; CByteArr byteBuffer(kBufferSize); - LPNETRESOURCE lpnrLocal = (LPNETRESOURCE) (BYTE *)(byteBuffer); + LPNETRESOURCE lpnrLocal = (LPNETRESOURCE) (void *) (BYTE *)(byteBuffer); ZeroMemory(lpnrLocal, kBufferSize); DWORD bufferSize = kBufferSize; NETRESOURCE netResource; ConvertCResourceToNETRESOURCE(resource, netResource); LPTSTR lplpSystem; - DWORD result = ::WNetGetResourceInformation(&netResource, + const DWORD result = ::WNetGetResourceInformation(&netResource, lpnrLocal, &bufferSize, &lplpSystem); if (result != NO_ERROR) return result; - if (lplpSystem != 0) + if (lplpSystem != NULL) systemPathPart = lplpSystem; ConvertNETRESOURCEToCResource(lpnrLocal[0], destResource); return result; @@ -317,13 +339,13 @@ DWORD GetResourceInformation(const CResourceW &resource, { const DWORD kBufferSize = 16384; CByteArr byteBuffer(kBufferSize); - LPNETRESOURCEW lpnrLocal = (LPNETRESOURCEW) (BYTE *)(byteBuffer); + LPNETRESOURCEW lpnrLocal = (LPNETRESOURCEW) (void *) (BYTE *)(byteBuffer); ZeroMemory(lpnrLocal, kBufferSize); DWORD bufferSize = kBufferSize; NETRESOURCEW netResource; ConvertCResourceToNETRESOURCE(resource, netResource); LPWSTR lplpSystem; - DWORD result = ::WNetGetResourceInformationW(&netResource, + const DWORD result = ::WNetGetResourceInformationW(&netResource, lpnrLocal, &bufferSize, &lplpSystem); if (result != NO_ERROR) return result; @@ -335,7 +357,7 @@ DWORD GetResourceInformation(const CResourceW &resource, CResource resourceA, destResourceA; ConvertResourceWToResource(resource, resourceA); AString systemPathPartA; - DWORD result = GetResourceInformation(resourceA, destResourceA, systemPathPartA); + const DWORD result = GetResourceInformation(resourceA, destResourceA, systemPathPartA); ConvertResourceToResourceW(destResourceA, destResource); systemPathPart = GetUnicodeString(systemPathPartA); return result; @@ -364,8 +386,8 @@ DWORD AddConnection2(const CResourceW &resource, LPCWSTR password, LPCWSTR userN } CResource resourceA; ConvertResourceWToResource(resource, resourceA); - CSysString passwordA = GetSystemString(password); - CSysString userNameA = GetSystemString(userName); + const CSysString passwordA (GetSystemString(password)); + const CSysString userNameA (GetSystemString(userName)); return AddConnection2(resourceA, password ? (LPCTSTR)passwordA: 0, userName ? (LPCTSTR)userNameA: 0, diff --git a/src/plugins/7zip/7za/cpp/Windows/Net.h b/src/plugins/7zip/7za/cpp/Windows/Net.h index 7b60b1b4..a954bdb2 100644 --- a/src/plugins/7zip/7za/cpp/Windows/Net.h +++ b/src/plugins/7zip/7za/cpp/Windows/Net.h @@ -1,9 +1,10 @@ // Windows/Net.h -#ifndef __WINDOWS_NET_H -#define __WINDOWS_NET_H +#ifndef ZIP7_INC_WINDOWS_NET_H +#define ZIP7_INC_WINDOWS_NET_H #include "../Common/MyString.h" +#include "../Common/MyWindows.h" namespace NWindows { namespace NNet { diff --git a/src/plugins/7zip/7za/cpp/Windows/NtCheck.h b/src/plugins/7zip/7za/cpp/Windows/NtCheck.h index c6090700..362a05ad 100644 --- a/src/plugins/7zip/7za/cpp/Windows/NtCheck.h +++ b/src/plugins/7zip/7za/cpp/Windows/NtCheck.h @@ -1,7 +1,7 @@ // Windows/NtCheck.h -#ifndef __WINDOWS_NT_CHECK_H -#define __WINDOWS_NT_CHECK_H +#ifndef ZIP7_INC_WINDOWS_NT_CHECK_H +#define ZIP7_INC_WINDOWS_NT_CHECK_H #ifdef _WIN32 @@ -9,44 +9,26 @@ #if !defined(_WIN64) && !defined(UNDER_CE) -// OPENSAL_7ZIP_PATCH BEGIN -// **************************************************************************** -// SalIsWindowsVersionOrGreater -// -// Based on SDK 8.1 VersionHelpers.h -// Indicates if the current OS version matches, or is greater than, the provided -// version information. This function is useful in confirming a version of Windows -// Server that doesn't share a version number with a client release. -// http://msdn.microsoft.com/en-us/library/windows/desktop/dn424964%28v=vs.85%29.aspx -// - -static inline bool SalIsWindowsVersionOrGreater(WORD wMajorVersion, WORD wMinorVersion, WORD wServicePackMajor) -{ - OSVERSIONINFOEXW osvi; - DWORDLONG const dwlConditionMask = VerSetConditionMask(VerSetConditionMask(VerSetConditionMask(0, - VER_MAJORVERSION, VER_GREATER_EQUAL), - VER_MINORVERSION, VER_GREATER_EQUAL), - VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); - - SecureZeroMemory(&osvi, sizeof(osvi)); // replacement for memset (does not require the RTL) - osvi.dwOSVersionInfoSize = sizeof(osvi); - osvi.dwMajorVersion = wMajorVersion; - osvi.dwMinorVersion = wMinorVersion; - osvi.wServicePackMajor = wServicePackMajor; - return VerifyVersionInfoW(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, dwlConditionMask) != FALSE; -} - +#if defined(_MSC_VER) && _MSC_VER >= 1900 +#pragma warning(push) +// GetVersionExW was declared deprecated +#pragma warning(disable : 4996) +#endif static inline bool IsItWindowsNT() { -// OSVERSIONINFO vi; -// vi.dwOSVersionInfoSize = sizeof(vi); -// return (::GetVersionEx(&vi) && vi.dwPlatformId == VER_PLATFORM_WIN32_NT); - return SalIsWindowsVersionOrGreater(5, 0, 0); // Petr: is this at least Windows 2000? + OSVERSIONINFO vi; + vi.dwOSVersionInfoSize = sizeof(vi); + return (::GetVersionEx(&vi) && vi.dwPlatformId == VER_PLATFORM_WIN32_NT); } -// OPENSAL_7ZIP_PATCH END +#if defined(_MSC_VER) && _MSC_VER >= 1900 +#pragma warning(pop) +#endif + #endif #ifndef _UNICODE + extern + bool g_IsNT; #if defined(_WIN64) || defined(UNDER_CE) bool g_IsNT = true; #define SET_IS_NT diff --git a/src/plugins/7zip/7za/cpp/Windows/ProcessMessages.h b/src/plugins/7zip/7za/cpp/Windows/ProcessMessages.h index b2558a01..aa98bbb0 100644 --- a/src/plugins/7zip/7za/cpp/Windows/ProcessMessages.h +++ b/src/plugins/7zip/7za/cpp/Windows/ProcessMessages.h @@ -1,7 +1,7 @@ // Windows/ProcessMessages.h -#ifndef __WINDOWS_PROCESSMESSAGES_H -#define __WINDOWS_PROCESSMESSAGES_H +#ifndef ZIP7_INC_WINDOWS_PROCESS_MESSAGES_H +#define ZIP7_INC_WINDOWS_PROCESS_MESSAGES_H namespace NWindows { diff --git a/src/plugins/7zip/7za/cpp/Windows/ProcessUtils.cpp b/src/plugins/7zip/7za/cpp/Windows/ProcessUtils.cpp index 9b833e54..f5e156c2 100644 --- a/src/plugins/7zip/7za/cpp/Windows/ProcessUtils.cpp +++ b/src/plugins/7zip/7za/cpp/Windows/ProcessUtils.cpp @@ -15,15 +15,30 @@ namespace NWindows { #ifndef UNDER_CE static UString GetQuotedString(const UString &s) { - UString s2 = L'\"'; + UString s2 ('\"'); s2 += s; - s2 += L'\"'; + s2.Add_Char('\"'); return s2; } #endif WRes CProcess::Create(LPCWSTR imageName, const UString ¶ms, LPCWSTR curDir) { + /* + OutputDebugStringW(L"CProcess::Create"); + OutputDebugStringW(imageName); + if (params) + { + OutputDebugStringW(L"params:"); + OutputDebugStringW(params); + } + if (curDir) + { + OutputDebugStringW(L"cur dir:"); + OutputDebugStringW(curDir); + } + */ + Close(); const UString params2 = #ifndef UNDER_CE @@ -31,9 +46,9 @@ WRes CProcess::Create(LPCWSTR imageName, const UString ¶ms, LPCWSTR curDir) #endif params; #ifdef UNDER_CE - curDir = 0; + curDir = NULL; #else - imageName = 0; + imageName = NULL; #endif PROCESS_INFORMATION pi; BOOL result; @@ -42,17 +57,18 @@ WRes CProcess::Create(LPCWSTR imageName, const UString ¶ms, LPCWSTR curDir) { STARTUPINFOA si; si.cb = sizeof(si); - si.lpReserved = 0; - si.lpDesktop = 0; - si.lpTitle = 0; + si.lpReserved = NULL; + si.lpDesktop = NULL; + si.lpTitle = NULL; si.dwFlags = 0; si.cbReserved2 = 0; - si.lpReserved2 = 0; + si.lpReserved2 = NULL; CSysString curDirA; if (curDir != 0) curDirA = GetSystemString(curDir); - result = ::CreateProcessA(NULL, (LPSTR)(LPCSTR)GetSystemString(params2), + const AString s = GetSystemString(params2); + result = ::CreateProcessA(NULL, s.Ptr_non_const(), NULL, NULL, FALSE, 0, NULL, ((curDir != 0) ? (LPCSTR)curDirA: 0), &si, &pi); } else @@ -60,15 +76,15 @@ WRes CProcess::Create(LPCWSTR imageName, const UString ¶ms, LPCWSTR curDir) { STARTUPINFOW si; si.cb = sizeof(si); - si.lpReserved = 0; - si.lpDesktop = 0; - si.lpTitle = 0; + si.lpReserved = NULL; + si.lpDesktop = NULL; + si.lpTitle = NULL; si.dwFlags = 0; si.cbReserved2 = 0; - si.lpReserved2 = 0; + si.lpReserved2 = NULL; - result = CreateProcessW(imageName, (LPWSTR)(LPCWSTR)params2, - NULL, NULL, FALSE, 0, NULL, (LPWSTR)curDir, &si, &pi); + result = CreateProcessW(imageName, params2.Ptr_non_const(), + NULL, NULL, FALSE, 0, NULL, curDir, &si, &pi); } if (result == 0) return ::GetLastError(); @@ -80,7 +96,7 @@ WRes CProcess::Create(LPCWSTR imageName, const UString ¶ms, LPCWSTR curDir) WRes MyCreateProcess(LPCWSTR imageName, const UString ¶ms) { CProcess process; - return process.Create(imageName, params, 0); + return process.Create(imageName, params, NULL); } } diff --git a/src/plugins/7zip/7za/cpp/Windows/ProcessUtils.h b/src/plugins/7zip/7za/cpp/Windows/ProcessUtils.h index a50bb5fc..34fef8da 100644 --- a/src/plugins/7zip/7za/cpp/Windows/ProcessUtils.h +++ b/src/plugins/7zip/7za/cpp/Windows/ProcessUtils.h @@ -1,13 +1,45 @@ // Windows/ProcessUtils.h -#ifndef __WINDOWS_PROCESS_UTILS_H -#define __WINDOWS_PROCESS_UTILS_H +#ifndef ZIP7_INC_WINDOWS_PROCESS_UTILS_H +#define ZIP7_INC_WINDOWS_PROCESS_UTILS_H +#include "../Common/MyWindows.h" + +#ifndef Z7_OLD_WIN_SDK + +#if defined(__MINGW32__) || defined(__MINGW64__) #include +#else +#include +#endif + +#else // Z7_OLD_WIN_SDK + +typedef struct _MODULEINFO { + LPVOID lpBaseOfDll; + DWORD SizeOfImage; + LPVOID EntryPoint; +} MODULEINFO, *LPMODULEINFO; + +typedef struct _PROCESS_MEMORY_COUNTERS { + DWORD cb; + DWORD PageFaultCount; + SIZE_T PeakWorkingSetSize; + SIZE_T WorkingSetSize; + SIZE_T QuotaPeakPagedPoolUsage; + SIZE_T QuotaPagedPoolUsage; + SIZE_T QuotaPeakNonPagedPoolUsage; + SIZE_T QuotaNonPagedPoolUsage; + SIZE_T PagefileUsage; + SIZE_T PeakPagefileUsage; +} PROCESS_MEMORY_COUNTERS; +typedef PROCESS_MEMORY_COUNTERS *PPROCESS_MEMORY_COUNTERS; + +#endif // Z7_OLD_WIN_SDK #include "../Common/MyString.h" -#include "Defs.h" +#include "WinDefs.h" #include "Handle.h" namespace NWindows { @@ -18,7 +50,7 @@ class CProcess: public CHandle bool Open(DWORD desiredAccess, bool inheritHandle, DWORD processId) { _handle = ::OpenProcess(desiredAccess, inheritHandle, processId); - return (_handle != 0); + return (_handle != NULL); } #ifndef UNDER_CE @@ -43,9 +75,14 @@ class CProcess: public CHandle { return BOOLToBool(::ReadProcessMemory(_handle, baseAddress, buffer, size, numberOfBytesRead)); } bool WriteMemory(LPVOID baseAddress, LPCVOID buffer, SIZE_T size, SIZE_T* numberOfBytesWritten) - { return BOOLToBool(::WriteProcessMemory(_handle, baseAddress, buffer, size, numberOfBytesWritten)); } - - bool FlushInstructionCache(LPCVOID baseAddress = 0, SIZE_T size = 0) + { return BOOLToBool(::WriteProcessMemory(_handle, baseAddress, + #ifdef Z7_OLD_WIN_SDK + (LPVOID) + #endif + buffer, + size, numberOfBytesWritten)); } + + bool FlushInstructionCache(LPCVOID baseAddress = NULL, SIZE_T size = 0) { return BOOLToBool(::FlushInstructionCache(_handle, baseAddress, size)); } LPVOID VirtualAlloc(LPVOID address, SIZE_T size, DWORD allocationType, DWORD protect) @@ -56,17 +93,17 @@ class CProcess: public CHandle // Process Status API (PSAPI) + /* bool EmptyWorkingSet() { return BOOLToBool(::EmptyWorkingSet(_handle)); } bool EnumModules(HMODULE *hModules, DWORD arraySizeInBytes, LPDWORD receivedBytes) { return BOOLToBool(::EnumProcessModules(_handle, hModules, arraySizeInBytes, receivedBytes)); } - DWORD MyGetModuleBaseName(HMODULE hModule, LPTSTR baseName, DWORD size) { return ::GetModuleBaseName(_handle, hModule, baseName, size); } bool MyGetModuleBaseName(HMODULE hModule, CSysString &name) { const unsigned len = MAX_PATH + 100; - DWORD resultLen = MyGetModuleBaseName(hModule, name.GetBuf(len), len); + const DWORD resultLen = MyGetModuleBaseName(hModule, name.GetBuf(len), len); name.ReleaseBuf_CalcLen(len); return (resultLen != 0); } @@ -76,7 +113,7 @@ class CProcess: public CHandle bool MyGetModuleFileNameEx(HMODULE hModule, CSysString &name) { const unsigned len = MAX_PATH + 100; - DWORD resultLen = MyGetModuleFileNameEx(hModule, name.GetBuf(len), len); + const DWORD resultLen = MyGetModuleFileNameEx(hModule, name.GetBuf(len), len); name.ReleaseBuf_CalcLen(len); return (resultLen != 0); } @@ -85,6 +122,7 @@ class CProcess: public CHandle { return BOOLToBool(::GetModuleInformation(_handle, hModule, moduleInfo, sizeof(MODULEINFO))); } bool GetMemoryInfo(PPROCESS_MEMORY_COUNTERS memCounters) { return BOOLToBool(::GetProcessMemoryInfo(_handle, memCounters, sizeof(PROCESS_MEMORY_COUNTERS))); } + */ #endif diff --git a/src/plugins/7zip/7za/cpp/Windows/PropVariant.cpp b/src/plugins/7zip/7za/cpp/Windows/PropVariant.cpp index e95cbd2b..678f970e 100644 --- a/src/plugins/7zip/7za/cpp/Windows/PropVariant.cpp +++ b/src/plugins/7zip/7za/cpp/Windows/PropVariant.cpp @@ -91,7 +91,7 @@ CPropVariant& CPropVariant::operator=(BSTR bstrSrc) return *this; } -static const char *kMemException = "out of memory"; +static const char * const kMemException = "out of memory"; CPropVariant& CPropVariant::operator=(LPCOLESTR lpszSrc) { @@ -192,82 +192,112 @@ BSTR CPropVariant::AllocBstr(unsigned numChars) return bstrVal; } +#define SET_PROP_id_dest(id, dest) \ + if (vt != id) { InternalClear(); vt = id; } dest = value; wReserved1 = 0; + +void CPropVariant::Set_Int32(Int32 value) throw() +{ + SET_PROP_id_dest(VT_I4, lVal) +} + +void CPropVariant::Set_Int64(Int64 value) throw() +{ + SET_PROP_id_dest(VT_I8, hVal.QuadPart) +} + #define SET_PROP_FUNC(type, id, dest) \ CPropVariant& CPropVariant::operator=(type value) throw() \ - { if (vt != id) { InternalClear(); vt = id; } \ - dest = value; return *this; } + { SET_PROP_id_dest(id, dest) return *this; } SET_PROP_FUNC(Byte, VT_UI1, bVal) // SET_PROP_FUNC(Int16, VT_I2, iVal) -SET_PROP_FUNC(Int32, VT_I4, lVal) +// SET_PROP_FUNC(Int32, VT_I4, lVal) SET_PROP_FUNC(UInt32, VT_UI4, ulVal) SET_PROP_FUNC(UInt64, VT_UI8, uhVal.QuadPart) -SET_PROP_FUNC(Int64, VT_I8, hVal.QuadPart) +// SET_PROP_FUNC(Int64, VT_I8, hVal.QuadPart) SET_PROP_FUNC(const FILETIME &, VT_FILETIME, filetime) +#define CASE_SIMPLE_VT_VALUES \ + case VT_EMPTY: \ + case VT_BOOL: \ + case VT_FILETIME: \ + case VT_UI8: \ + case VT_UI4: \ + case VT_UI2: \ + case VT_UI1: \ + case VT_I8: \ + case VT_I4: \ + case VT_I2: \ + case VT_I1: \ + case VT_UINT: \ + case VT_INT: \ + case VT_NULL: \ + case VT_ERROR: \ + case VT_R4: \ + case VT_R8: \ + case VT_CY: \ + case VT_DATE: \ + + +/* + ::VariantClear() and ::VariantCopy() don't work, if (vt == VT_FILETIME) + So we handle VT_FILETIME and another simple types directly + we call system functions for VT_BSTR and for unknown typed +*/ + +CPropVariant::~CPropVariant() throw() +{ + switch ((unsigned)vt) + { + CASE_SIMPLE_VT_VALUES + // vt = VT_EMPTY; // it's optional + return; + default: break; + } + ::VariantClear((tagVARIANT *)this); +} + HRESULT PropVariant_Clear(PROPVARIANT *prop) throw() { - switch (prop->vt) + switch ((unsigned)prop->vt) { - case VT_EMPTY: - case VT_UI1: - case VT_I1: - case VT_I2: - case VT_UI2: - case VT_BOOL: - case VT_I4: - case VT_UI4: - case VT_R4: - case VT_INT: - case VT_UINT: - case VT_ERROR: - case VT_FILETIME: - case VT_UI8: - case VT_R8: - case VT_CY: - case VT_DATE: + CASE_SIMPLE_VT_VALUES prop->vt = VT_EMPTY; - prop->wReserved1 = 0; - prop->wReserved2 = 0; - prop->wReserved3 = 0; - prop->uhVal.QuadPart = 0; - return S_OK; + break; + default: + { + const HRESULT res = ::VariantClear((VARIANTARG *)prop); + if (res != S_OK || prop->vt != VT_EMPTY) + return res; + break; + } } - return ::VariantClear((VARIANTARG *)prop); - // return ::PropVariantClear(prop); - // PropVariantClear can clear VT_BLOB. + prop->wReserved1 = 0; + prop->wReserved2 = 0; + prop->wReserved3 = 0; + prop->uhVal.QuadPart = 0; + return S_OK; } HRESULT CPropVariant::Clear() throw() { if (vt == VT_EMPTY) + { + wReserved1 = 0; return S_OK; + } return PropVariant_Clear(this); } HRESULT CPropVariant::Copy(const PROPVARIANT* pSrc) throw() { - ::VariantClear((tagVARIANT *)this); - switch (pSrc->vt) + Clear(); + switch ((unsigned)pSrc->vt) { - case VT_UI1: - case VT_I1: - case VT_I2: - case VT_UI2: - case VT_BOOL: - case VT_I4: - case VT_UI4: - case VT_R4: - case VT_INT: - case VT_UINT: - case VT_ERROR: - case VT_FILETIME: - case VT_UI8: - case VT_R8: - case VT_CY: - case VT_DATE: + CASE_SIMPLE_VT_VALUES memmove((PROPVARIANT*)this, pSrc, sizeof(PROPVARIANT)); return S_OK; + default: break; } return ::VariantCopy((tagVARIANT *)this, (tagVARIANT *)const_cast(pSrc)); } @@ -275,11 +305,13 @@ HRESULT CPropVariant::Copy(const PROPVARIANT* pSrc) throw() HRESULT CPropVariant::Attach(PROPVARIANT *pSrc) throw() { - HRESULT hr = Clear(); + const HRESULT hr = Clear(); if (FAILED(hr)) return hr; - memcpy(this, pSrc, sizeof(PROPVARIANT)); + // memcpy((PROPVARIANT *)this, pSrc, sizeof(PROPVARIANT)); + *(PROPVARIANT *)this = *pSrc; pSrc->vt = VT_EMPTY; + pSrc->wReserved1 = 0; return S_OK; } @@ -287,20 +319,25 @@ HRESULT CPropVariant::Detach(PROPVARIANT *pDest) throw() { if (pDest->vt != VT_EMPTY) { - HRESULT hr = PropVariant_Clear(pDest); + const HRESULT hr = PropVariant_Clear(pDest); if (FAILED(hr)) return hr; } - memcpy(pDest, this, sizeof(PROPVARIANT)); + // memcpy(pDest, this, sizeof(PROPVARIANT)); + *pDest = *(PROPVARIANT *)this; vt = VT_EMPTY; + wReserved1 = 0; return S_OK; } HRESULT CPropVariant::InternalClear() throw() { if (vt == VT_EMPTY) + { + wReserved1 = 0; return S_OK; - HRESULT hr = Clear(); + } + const HRESULT hr = Clear(); if (FAILED(hr)) { vt = VT_ERROR; @@ -311,7 +348,7 @@ HRESULT CPropVariant::InternalClear() throw() void CPropVariant::InternalCopy(const PROPVARIANT *pSrc) { - HRESULT hr = Copy(pSrc); + const HRESULT hr = Copy(pSrc); if (FAILED(hr)) { if (hr == E_OUTOFMEMORY) @@ -321,11 +358,12 @@ void CPropVariant::InternalCopy(const PROPVARIANT *pSrc) } } + int CPropVariant::Compare(const CPropVariant &a) throw() { if (vt != a.vt) return MyCompare(vt, a.vt); - switch (vt) + switch ((unsigned)vt) { case VT_EMPTY: return 0; // case VT_I1: return MyCompare(cVal, a.cVal); @@ -338,7 +376,15 @@ int CPropVariant::Compare(const CPropVariant &a) throw() case VT_I8: return MyCompare(hVal.QuadPart, a.hVal.QuadPart); case VT_UI8: return MyCompare(uhVal.QuadPart, a.uhVal.QuadPart); case VT_BOOL: return -MyCompare(boolVal, a.boolVal); - case VT_FILETIME: return ::CompareFileTime(&filetime, &a.filetime); + case VT_FILETIME: + { + const int res = CompareFileTime(&filetime, &a.filetime); + if (res != 0) + return res; + const unsigned v1 = Get_Ns100(); + const unsigned v2 = a.Get_Ns100(); + return MyCompare(v1, v2); + } case VT_BSTR: return 0; // Not implemented default: return 0; } diff --git a/src/plugins/7zip/7za/cpp/Windows/PropVariant.h b/src/plugins/7zip/7za/cpp/Windows/PropVariant.h index 3610d6a8..f358fdea 100644 --- a/src/plugins/7zip/7za/cpp/Windows/PropVariant.h +++ b/src/plugins/7zip/7za/cpp/Windows/PropVariant.h @@ -1,7 +1,7 @@ // Windows/PropVariant.h -#ifndef __WINDOWS_PROP_VARIANT_H -#define __WINDOWS_PROP_VARIANT_H +#ifndef ZIP7_INC_WINDOWS_PROP_VARIANT_H +#define ZIP7_INC_WINDOWS_PROP_VARIANT_H #include "../Common/MyTypes.h" #include "../Common/MyWindows.h" @@ -29,11 +29,14 @@ inline void PropVarEm_Set_UInt64(PROPVARIANT *p, UInt64 v) throw() p->uhVal.QuadPart = v; } -inline void PropVarEm_Set_FileTime64(PROPVARIANT *p, UInt64 v) throw() +inline void PropVarEm_Set_FileTime64_Prec(PROPVARIANT *p, UInt64 v, unsigned prec) throw() { p->vt = VT_FILETIME; p->filetime.dwLowDateTime = (DWORD)v; p->filetime.dwHighDateTime = (DWORD)(v >> 32); + p->wReserved1 = (WORD)prec; + p->wReserved2 = 0; + p->wReserved3 = 0; } inline void PropVarEm_Set_Bool(PROPVARIANT *p, bool b) throw() @@ -45,6 +48,14 @@ inline void PropVarEm_Set_Bool(PROPVARIANT *p, bool b) throw() class CPropVariant : public tagPROPVARIANT { + // ---------- forbidden functions ---------- + CPropVariant(const char *s); + // CPropVariant(const UString &s); + #ifdef DEBUG_FSTRING_INHERITS_ASTRING + CPropVariant(const FString &s); + CPropVariant& operator=(const FString &s); + #endif + public: CPropVariant() { @@ -53,9 +64,53 @@ class CPropVariant : public tagPROPVARIANT // wReserved2 = 0; // wReserved3 = 0; // uhVal.QuadPart = 0; - bstrVal = 0; + bstrVal = NULL; + } + + + void Set_FtPrec(unsigned prec) + { + wReserved1 = (WORD)prec; + wReserved2 = 0; + wReserved3 = 0; + } + + void SetAsTimeFrom_FT_Prec(const FILETIME &ft, unsigned prec) + { + operator=(ft); + Set_FtPrec(prec); + } + + void SetAsTimeFrom_Ft64_Prec(UInt64 v, unsigned prec) + { + FILETIME ft; + ft.dwLowDateTime = (DWORD)(UInt32)v; + ft.dwHighDateTime = (DWORD)(UInt32)(v >> 32); + operator=(ft); + Set_FtPrec(prec); + } + + void SetAsTimeFrom_FT_Prec_Ns100(const FILETIME &ft, unsigned prec, unsigned ns100) + { + operator=(ft); + wReserved1 = (WORD)prec; + wReserved2 = (WORD)ns100; + wReserved3 = 0; + } + + unsigned Get_Ns100() const + { + const unsigned prec = wReserved1; + const unsigned ns100 = wReserved2; + if (prec == 0 + && prec <= k_PropVar_TimePrec_1ns + && ns100 < 100 + && wReserved3 == 0) + return ns100; + return 0; } - ~CPropVariant() throw() { Clear(); } + + ~CPropVariant() throw(); CPropVariant(const PROPVARIANT &varSrc); CPropVariant(const CPropVariant &varSrc); CPropVariant(BSTR bstrSrc); @@ -64,13 +119,14 @@ class CPropVariant : public tagPROPVARIANT CPropVariant(Byte value) { vt = VT_UI1; wReserved1 = 0; bVal = value; } private: + CPropVariant(UInt16 value); // { vt = VT_UI2; wReserved1 = 0; uiVal = value; } CPropVariant(Int16 value); // { vt = VT_I2; wReserved1 = 0; iVal = value; } CPropVariant(Int32 value); // { vt = VT_I4; wReserved1 = 0; lVal = value; } + CPropVariant(Int64 value); // { vt = VT_I8; wReserved1 = 0; hVal.QuadPart = value; } public: CPropVariant(UInt32 value) { vt = VT_UI4; wReserved1 = 0; ulVal = value; } CPropVariant(UInt64 value) { vt = VT_UI8; wReserved1 = 0; uhVal.QuadPart = value; } - CPropVariant(Int64 value) { vt = VT_I8; wReserved1 = 0; hVal.QuadPart = value; } CPropVariant(const FILETIME &value) { vt = VT_FILETIME; wReserved1 = 0; filetime = value; } CPropVariant& operator=(const CPropVariant &varSrc); @@ -80,20 +136,26 @@ class CPropVariant : public tagPROPVARIANT CPropVariant& operator=(const UString &s); CPropVariant& operator=(const UString2 &s); CPropVariant& operator=(const char *s); + CPropVariant& operator=(const AString &s) + { return (*this)=(const char *)s; } CPropVariant& operator=(bool bSrc) throw(); CPropVariant& operator=(Byte value) throw(); private: CPropVariant& operator=(Int16 value) throw(); + CPropVariant& operator=(UInt16 value) throw(); + CPropVariant& operator=(Int32 value) throw(); + CPropVariant& operator=(Int64 value) throw(); public: - CPropVariant& operator=(Int32 value) throw(); CPropVariant& operator=(UInt32 value) throw(); CPropVariant& operator=(UInt64 value) throw(); - CPropVariant& operator=(Int64 value) throw(); CPropVariant& operator=(const FILETIME &value) throw(); + void Set_Int32(Int32 value) throw(); + void Set_Int64(Int64 value) throw(); + BSTR AllocBstr(unsigned numChars); HRESULT Clear() throw(); @@ -103,7 +165,6 @@ class CPropVariant : public tagPROPVARIANT HRESULT InternalClear() throw(); void InternalCopy(const PROPVARIANT *pSrc); - int Compare(const CPropVariant &a) throw(); }; diff --git a/src/plugins/7zip/7za/cpp/Windows/PropVariantConv.cpp b/src/plugins/7zip/7za/cpp/Windows/PropVariantConv.cpp index fe86f936..467c0f8f 100644 --- a/src/plugins/7zip/7za/cpp/Windows/PropVariantConv.cpp +++ b/src/plugins/7zip/7za/cpp/Windows/PropVariantConv.cpp @@ -1,71 +1,212 @@ -// PropVariantConvert.cpp +// PropVariantConv.cpp #include "StdAfx.h" #include "../Common/IntToString.h" -#include "Defs.h" +#include "WinDefs.h" #include "PropVariantConv.h" #define UINT_TO_STR_2(c, val) { s[0] = (c); s[1] = (char)('0' + (val) / 10); s[2] = (char)('0' + (val) % 10); s += 3; } -bool ConvertFileTimeToString(const FILETIME &ft, char *s, bool includeTime, bool includeSeconds) throw() +static const unsigned k_TimeStringBufferSize = 64; + +bool g_Timestamp_Show_UTC; +#if 0 +bool g_Timestamp_Show_DisableZ; +bool g_Timestamp_Show_TDelimeter; +bool g_Timestamp_Show_ZoneOffset; +#endif + +Z7_NO_INLINE +bool ConvertUtcFileTimeToString2(const FILETIME &utc, unsigned ns100, char *s, int level, unsigned flags) throw() { + *s = 0; + FILETIME ft; + +#if 0 + Int64 bias64 = 0; +#endif + + const bool show_utc = + (flags & kTimestampPrintFlags_Force_UTC) ? true : + (flags & kTimestampPrintFlags_Force_LOCAL) ? false : + g_Timestamp_Show_UTC; + + if (show_utc) + ft = utc; + else + { + if (!FileTimeToLocalFileTime(&utc, &ft)) + return false; +#if 0 + if (g_Timestamp_Show_ZoneOffset) + { + const UInt64 utc64 = (((UInt64)utc.dwHighDateTime) << 32) + utc.dwLowDateTime; + const UInt64 loc64 = (((UInt64) ft.dwHighDateTime) << 32) + ft.dwLowDateTime; + bias64 = (Int64)utc64 - (Int64)loc64; + } +#endif + } + SYSTEMTIME st; if (!BOOLToBool(FileTimeToSystemTime(&ft, &st))) { - *s = 0; + // win10 : that function doesn't work, if bit 63 of 64-bit FILETIME is set. return false; } - unsigned val = st.wYear; - if (val >= 10000) - { - *s++ = (char)('0' + val / 10000); - val %= 10000; - } + { + unsigned val = st.wYear; + if (val >= 10000) + { + *s++ = (char)('0' + val / 10000); + val %= 10000; + } s[3] = (char)('0' + val % 10); val /= 10; s[2] = (char)('0' + val % 10); val /= 10; s[1] = (char)('0' + val % 10); s[0] = (char)('0' + val / 10); s += 4; } - UINT_TO_STR_2('-', st.wMonth); - UINT_TO_STR_2('-', st.wDay); - if (includeTime) + UINT_TO_STR_2('-', st.wMonth) + UINT_TO_STR_2('-', st.wDay) + + if (level > kTimestampPrintLevel_DAY) { - UINT_TO_STR_2(' ', st.wHour); - UINT_TO_STR_2(':', st.wMinute); - if (includeSeconds) + const char setChar = +#if 0 + g_Timestamp_Show_TDelimeter ? 'T' : // ISO 8601 +#endif + ' '; + UINT_TO_STR_2(setChar, st.wHour) + UINT_TO_STR_2(':', st.wMinute) + + if (level >= kTimestampPrintLevel_SEC) { - UINT_TO_STR_2(':', st.wSecond); - /* - *s++ = '.'; - unsigned val = st.wMilliseconds; - s[2] = (char)('0' + val % 10); val /= 10; - s[1] = (char)('0' + val % 10); - s[0] = (char)('0' + val / 10); - s += 3; - */ + UINT_TO_STR_2(':', st.wSecond) + + if (level > kTimestampPrintLevel_SEC) + { + *s++ = '.'; + /* + { + unsigned val = st.wMilliseconds; + s[2] = (char)('0' + val % 10); val /= 10; + s[1] = (char)('0' + val % 10); + s[0] = (char)('0' + val / 10); + s += 3; + } + *s++ = ' '; + */ + + { + unsigned numDigits = 7; + UInt32 val = (UInt32)((((UInt64)ft.dwHighDateTime << 32) + ft.dwLowDateTime) % 10000000); + for (unsigned i = numDigits; i != 0;) + { + i--; + s[i] = (char)('0' + val % 10); val /= 10; + } + if (numDigits > (unsigned)level) + numDigits = (unsigned)level; + s += numDigits; + } + if (level >= kTimestampPrintLevel_NTFS + 1) + { + *s++ = (char)('0' + (ns100 / 10)); + if (level >= kTimestampPrintLevel_NTFS + 2) + *s++ = (char)('0' + (ns100 % 10)); + } + } + } + } + + if (show_utc) + { + if ((flags & kTimestampPrintFlags_DisableZ) == 0 +#if 0 + && !g_Timestamp_Show_DisableZ +#endif + ) + *s++ = 'Z'; + } +#if 0 + else if (g_Timestamp_Show_ZoneOffset) + { +#if 1 + { + char c; + if (bias64 < 0) + { + bias64 = -bias64; + c = '+'; + } + else + c = '-'; + UInt32 bias = (UInt32)((UInt64)bias64 / (10000000 * 60)); +#else + TIME_ZONE_INFORMATION zi; + const DWORD dw = GetTimeZoneInformation(&zi); + if (dw <= TIME_ZONE_ID_DAYLIGHT) // == 2 + { + // UTC = LOCAL + Bias + Int32 bias = zi.Bias; + char c; + if (bias < 0) + { + bias = -bias; + c = '+'; + } + else + c = '-'; +#endif + const UInt32 hours = (UInt32)bias / 60; + const UInt32 mins = (UInt32)bias % 60; + UINT_TO_STR_2(c, hours) + UINT_TO_STR_2(':', mins) } } +#endif *s = 0; return true; } -void ConvertFileTimeToString(const FILETIME &ft, wchar_t *dest, bool includeTime, bool includeSeconds) throw() + +bool ConvertUtcFileTimeToString(const FILETIME &utc, char *s, int level) throw() { - char s[32]; - ConvertFileTimeToString(ft, s, includeTime, includeSeconds); + return ConvertUtcFileTimeToString2(utc, 0, s, level); +} + +bool ConvertUtcFileTimeToString2(const FILETIME &ft, unsigned ns100, wchar_t *dest, int level) throw() +{ + char s[k_TimeStringBufferSize]; + const bool res = ConvertUtcFileTimeToString2(ft, ns100, s, level); for (unsigned i = 0;; i++) { - unsigned char c = s[i]; + const Byte c = (Byte)s[i]; dest[i] = c; if (c == 0) - return; + break; } + return res; } +bool ConvertUtcFileTimeToString(const FILETIME &ft, wchar_t *dest, int level) throw() +{ + char s[k_TimeStringBufferSize]; + const bool res = ConvertUtcFileTimeToString(ft, s, level); + for (unsigned i = 0;; i++) + { + const Byte c = (Byte)s[i]; + dest[i] = c; + if (c == 0) + break; + } + return res; +} + + void ConvertPropVariantToShortString(const PROPVARIANT &prop, char *dest) throw() { *dest = 0; @@ -77,7 +218,19 @@ void ConvertPropVariantToShortString(const PROPVARIANT &prop, char *dest) throw( case VT_UI2: ConvertUInt32ToString(prop.uiVal, dest); return; case VT_UI4: ConvertUInt32ToString(prop.ulVal, dest); return; case VT_UI8: ConvertUInt64ToString(prop.uhVal.QuadPart, dest); return; - case VT_FILETIME: ConvertFileTimeToString(prop.filetime, dest, true, true); return; + case VT_FILETIME: + { + // const unsigned prec = prop.wReserved1; + int level = 0; + /* + if (prec == 0) + level = 7; + else if (prec > 16 && prec <= 16 + 9) + level = prec - 16; + */ + ConvertUtcFileTimeToString(prop.filetime, dest, level); + return; + } // case VT_I1: return ConvertInt64ToString(prop.cVal, dest); return; case VT_I2: ConvertInt64ToString(prop.iVal, dest); return; case VT_I4: ConvertInt64ToString(prop.lVal, dest); return; @@ -98,7 +251,19 @@ void ConvertPropVariantToShortString(const PROPVARIANT &prop, wchar_t *dest) thr case VT_UI2: ConvertUInt32ToString(prop.uiVal, dest); return; case VT_UI4: ConvertUInt32ToString(prop.ulVal, dest); return; case VT_UI8: ConvertUInt64ToString(prop.uhVal.QuadPart, dest); return; - case VT_FILETIME: ConvertFileTimeToString(prop.filetime, dest, true, true); return; + case VT_FILETIME: + { + // const unsigned prec = prop.wReserved1; + int level = 0; + /* + if (prec == 0) + level = 7; + else if (prec > 16 && prec <= 16 + 9) + level = prec - 16; + */ + ConvertUtcFileTimeToString(prop.filetime, dest, level); + return; + } // case VT_I1: return ConvertInt64ToString(prop.cVal, dest); return; case VT_I2: ConvertInt64ToString(prop.iVal, dest); return; case VT_I4: ConvertInt64ToString(prop.lVal, dest); return; diff --git a/src/plugins/7zip/7za/cpp/Windows/PropVariantConv.h b/src/plugins/7zip/7za/cpp/Windows/PropVariantConv.h index 5d26357f..87825073 100644 --- a/src/plugins/7zip/7za/cpp/Windows/PropVariantConv.h +++ b/src/plugins/7zip/7za/cpp/Windows/PropVariantConv.h @@ -1,13 +1,30 @@ // Windows/PropVariantConv.h -#ifndef __PROP_VARIANT_CONV_H -#define __PROP_VARIANT_CONV_H +#ifndef ZIP7_INC_PROP_VARIANT_CONV_H +#define ZIP7_INC_PROP_VARIANT_CONV_H #include "../Common/MyTypes.h" // provide at least 32 bytes for buffer including zero-end -bool ConvertFileTimeToString(const FILETIME &ft, char *s, bool includeTime = true, bool includeSeconds = true) throw(); -void ConvertFileTimeToString(const FILETIME &ft, wchar_t *s, bool includeTime = true, bool includeSeconds = true) throw(); + +extern bool g_Timestamp_Show_UTC; + +#define kTimestampPrintLevel_DAY -3 +// #define kTimestampPrintLevel_HOUR -2 +#define kTimestampPrintLevel_MIN -1 +#define kTimestampPrintLevel_SEC 0 +#define kTimestampPrintLevel_NTFS 7 +#define kTimestampPrintLevel_NS 9 + + +#define kTimestampPrintFlags_Force_UTC (1 << 0) +#define kTimestampPrintFlags_Force_LOCAL (1 << 1) +#define kTimestampPrintFlags_DisableZ (1 << 4) + +bool ConvertUtcFileTimeToString(const FILETIME &ft, char *s, int level = kTimestampPrintLevel_SEC) throw(); +bool ConvertUtcFileTimeToString(const FILETIME &ft, wchar_t *s, int level = kTimestampPrintLevel_SEC) throw(); +bool ConvertUtcFileTimeToString2(const FILETIME &ft, unsigned ns100, char *s, int level = kTimestampPrintLevel_SEC, unsigned flags = 0) throw(); +bool ConvertUtcFileTimeToString2(const FILETIME &ft, unsigned ns100, wchar_t *s, int level = kTimestampPrintLevel_SEC) throw(); // provide at least 32 bytes for buffer including zero-end // don't send VT_BSTR to these functions diff --git a/src/plugins/7zip/7za/cpp/Windows/PropVariantUtils.cpp b/src/plugins/7zip/7za/cpp/Windows/PropVariantUtils.cpp index 7149616b..6daee839 100644 --- a/src/plugins/7zip/7za/cpp/Windows/PropVariantUtils.cpp +++ b/src/plugins/7zip/7za/cpp/Windows/PropVariantUtils.cpp @@ -8,27 +8,32 @@ using namespace NWindows; -static AString GetHex(UInt32 v) +static void AddHex(AString &s, UInt32 v) { char sz[16]; sz[0] = '0'; sz[1] = 'x'; ConvertUInt32ToHex(v, sz + 2); - return sz; + s += sz; } + AString TypePairToString(const CUInt32PCharPair *pairs, unsigned num, UInt32 value) { - AString s; + char sz[16]; + const char *p = NULL; for (unsigned i = 0; i < num; i++) { - const CUInt32PCharPair &p = pairs[i]; - if (p.Value == value) - s = p.Name; + const CUInt32PCharPair &pair = pairs[i]; + if (pair.Value == value) + p = pair.Name; } - if (s.IsEmpty()) - s = GetHex(value); - return s; + if (!p) + { + ConvertUInt32ToString(value, sz); + p = sz; + } + return (AString)p; } void PairToProp(const CUInt32PCharPair *pairs, unsigned num, UInt32 value, NCOM::CPropVariant &prop) @@ -39,14 +44,30 @@ void PairToProp(const CUInt32PCharPair *pairs, unsigned num, UInt32 value, NCOM: AString TypeToString(const char * const table[], unsigned num, UInt32 value) { + char sz[16]; + const char *p = NULL; if (value < num) - return table[value]; - return GetHex(value); + p = table[value]; + if (!p) + { + ConvertUInt32ToString(value, sz); + p = sz; + } + return (AString)p; } -void TypeToProp(const char * const table[], unsigned num, UInt32 value, NCOM::CPropVariant &prop) +void TypeToProp(const char * const table[], unsigned num, UInt32 value, NWindows::NCOM::CPropVariant &prop) { - prop = TypeToString(table, num, value); + char sz[16]; + const char *p = NULL; + if (value < num) + p = table[value]; + if (!p) + { + ConvertUInt32ToString(value, sz); + p = sz; + } + prop = p; } @@ -59,20 +80,17 @@ AString FlagsToString(const char * const *names, unsigned num, UInt32 flags) if ((flags & flag) != 0) { const char *name = names[i]; - if (name != 0 && name[0] != 0) + if (name && name[0] != 0) { - if (!s.IsEmpty()) - s += ' '; - s += name; + s.Add_OptSpaced(name); flags &= ~flag; } } } if (flags != 0) { - if (!s.IsEmpty()) - s += ' '; - s += GetHex(flags); + s.Add_Space_if_NotEmpty(); + AddHex(s, flags); } return s; } @@ -87,30 +105,30 @@ AString FlagsToString(const CUInt32PCharPair *pairs, unsigned num, UInt32 flags) if ((flags & flag) != 0) { if (p.Name[0] != 0) - { - if (!s.IsEmpty()) - s += ' '; - s += p.Name; - } + s.Add_OptSpaced(p.Name); } flags &= ~flag; } if (flags != 0) { - if (!s.IsEmpty()) - s += ' '; - s += GetHex(flags); + s.Add_Space_if_NotEmpty(); + AddHex(s, flags); } return s; } +void FlagsToProp(const char * const *names, unsigned num, UInt32 flags, NCOM::CPropVariant &prop) +{ + prop = FlagsToString(names, num, flags); +} + void FlagsToProp(const CUInt32PCharPair *pairs, unsigned num, UInt32 flags, NCOM::CPropVariant &prop) { prop = FlagsToString(pairs, num, flags); } -AString Flags64ToString(const CUInt32PCharPair *pairs, unsigned num, UInt64 flags) +static AString Flags64ToString(const CUInt32PCharPair *pairs, unsigned num, UInt64 flags) { AString s; for (unsigned i = 0; i < num; i++) @@ -120,24 +138,18 @@ AString Flags64ToString(const CUInt32PCharPair *pairs, unsigned num, UInt64 flag if ((flags & flag) != 0) { if (p.Name[0] != 0) - { - if (!s.IsEmpty()) - s += ' '; - s += p.Name; - } + s.Add_OptSpaced(p.Name); } flags &= ~flag; } if (flags != 0) { - if (!s.IsEmpty()) - s += ' '; { char sz[32]; sz[0] = '0'; sz[1] = 'x'; ConvertUInt64ToHex(flags, sz + 2); - s += sz; + s.Add_OptSpaced(sz); } } return s; diff --git a/src/plugins/7zip/7za/cpp/Windows/PropVariantUtils.h b/src/plugins/7zip/7za/cpp/Windows/PropVariantUtils.h index 64dfd5a1..78adb50d 100644 --- a/src/plugins/7zip/7za/cpp/Windows/PropVariantUtils.h +++ b/src/plugins/7zip/7za/cpp/Windows/PropVariantUtils.h @@ -1,7 +1,7 @@ // Windows/PropVariantUtils.h -#ifndef __PROP_VARIANT_UTILS_H -#define __PROP_VARIANT_UTILS_H +#ifndef ZIP7_INC_PROP_VARIANT_UTILS_H +#define ZIP7_INC_PROP_VARIANT_UTILS_H #include "../Common/MyString.h" @@ -18,16 +18,17 @@ void PairToProp(const CUInt32PCharPair *pairs, unsigned num, UInt32 value, NWind AString FlagsToString(const char * const *names, unsigned num, UInt32 flags); AString FlagsToString(const CUInt32PCharPair *pairs, unsigned num, UInt32 flags); +void FlagsToProp(const char * const *names, unsigned num, UInt32 flags, NWindows::NCOM::CPropVariant &prop); void FlagsToProp(const CUInt32PCharPair *pairs, unsigned num, UInt32 flags, NWindows::NCOM::CPropVariant &prop); AString TypeToString(const char * const table[], unsigned num, UInt32 value); void TypeToProp(const char * const table[], unsigned num, UInt32 value, NWindows::NCOM::CPropVariant &prop); -#define PAIR_TO_PROP(pairs, value, prop) PairToProp(pairs, ARRAY_SIZE(pairs), value, prop) -#define FLAGS_TO_PROP(pairs, value, prop) FlagsToProp(pairs, ARRAY_SIZE(pairs), value, prop) -#define TYPE_TO_PROP(table, value, prop) TypeToProp(table, ARRAY_SIZE(table), value, prop) +#define PAIR_TO_PROP(pairs, value, prop) PairToProp(pairs, Z7_ARRAY_SIZE(pairs), value, prop) +#define FLAGS_TO_PROP(pairs, value, prop) FlagsToProp(pairs, Z7_ARRAY_SIZE(pairs), value, prop) +#define TYPE_TO_PROP(table, value, prop) TypeToProp(table, Z7_ARRAY_SIZE(table), value, prop) void Flags64ToProp(const CUInt32PCharPair *pairs, unsigned num, UInt64 flags, NWindows::NCOM::CPropVariant &prop); -#define FLAGS64_TO_PROP(pairs, value, prop) Flags64ToProp(pairs, ARRAY_SIZE(pairs), value, prop) +#define FLAGS64_TO_PROP(pairs, value, prop) Flags64ToProp(pairs, Z7_ARRAY_SIZE(pairs), value, prop) #endif diff --git a/src/plugins/7zip/7za/cpp/Windows/Registry.cpp b/src/plugins/7zip/7za/cpp/Windows/Registry.cpp index c000e628..a94a50f5 100644 --- a/src/plugins/7zip/7za/cpp/Windows/Registry.cpp +++ b/src/plugins/7zip/7za/cpp/Windows/Registry.cpp @@ -3,6 +3,7 @@ #include "StdAfx.h" #include +// #include #ifndef _UNICODE #include "../Common/StringConvert.h" @@ -17,12 +18,27 @@ namespace NWindows { namespace NRegistry { #define MYASSERT(expr) // _ASSERTE(expr) +#define MY_ASSUME(expr) + +/* +static void Error() +{ + #ifdef _CONSOLE + printf("\nregistry error\n"); + #else + MessageBoxW(0, L"registry error", L"", 0); + // exit(1); + #endif +} + +#define MY_ASSUME(expr) { if (!(expr)) Error(); } +*/ LONG CKey::Create(HKEY parentKey, LPCTSTR keyName, LPTSTR keyClass, DWORD options, REGSAM accessMask, LPSECURITY_ATTRIBUTES securityAttributes, LPDWORD disposition) throw() { - MYASSERT(parentKey != NULL); + MY_ASSUME(parentKey != NULL); DWORD dispositionReal; HKEY key = NULL; LONG res = RegCreateKeyEx(parentKey, keyName, 0, keyClass, @@ -39,7 +55,7 @@ LONG CKey::Create(HKEY parentKey, LPCTSTR keyName, LONG CKey::Open(HKEY parentKey, LPCTSTR keyName, REGSAM accessMask) throw() { - MYASSERT(parentKey != NULL); + MY_ASSUME(parentKey != NULL); HKEY key = NULL; LONG res = RegOpenKeyEx(parentKey, keyName, 0, accessMask, &key); if (res == ERROR_SUCCESS) @@ -62,32 +78,46 @@ LONG CKey::Close() throw() return res; } -// win95, win98: deletes sunkey and all its subkeys +// win95, win98: deletes subkey and all its subkeys // winNT to be deleted must not have subkeys LONG CKey::DeleteSubKey(LPCTSTR subKeyName) throw() { - MYASSERT(_object != NULL); + MY_ASSUME(_object != NULL); return RegDeleteKey(_object, subKeyName); } LONG CKey::RecurseDeleteKey(LPCTSTR subKeyName) throw() { - CKey key; - LONG res = key.Open(_object, subKeyName, KEY_READ | KEY_WRITE); - if (res != ERROR_SUCCESS) - return res; - FILETIME fileTime; - const UInt32 kBufSize = MAX_PATH + 1; // 256 in ATL - DWORD size = kBufSize; - TCHAR buffer[kBufSize]; - while (RegEnumKeyEx(key._object, 0, buffer, &size, NULL, NULL, NULL, &fileTime) == ERROR_SUCCESS) { - res = key.RecurseDeleteKey(buffer); + CKey key; + LONG res = key.Open(_object, subKeyName, KEY_READ | KEY_WRITE); if (res != ERROR_SUCCESS) return res; - size = kBufSize; + FILETIME fileTime; + const UInt32 kBufSize = MAX_PATH + 1; // 256 in ATL + TCHAR buffer[kBufSize]; + // we use loop limit here for some unexpected code failure + for (unsigned loop_cnt = 0; loop_cnt < (1u << 26); loop_cnt++) + { + DWORD size = kBufSize; + // we always request starting item (index==0) in each iteration, + // because we remove starting item (index==0) in each loop iteration. + res = RegEnumKeyEx(key._object, 0, buffer, &size, NULL, NULL, NULL, &fileTime); + if (res != ERROR_SUCCESS) + { + // possible return codes: + // ERROR_NO_MORE_ITEMS : are no more subkeys available + // ERROR_MORE_DATA : name buffer is too small + // we can try to remove (subKeyName), even if there is non ERROR_NO_MORE_ITEMS error. + // if (res != ERROR_NO_MORE_ITEMS) return res; + break; + } + res = key.RecurseDeleteKey(buffer); + if (res != ERROR_SUCCESS) + return res; + } + // key.Close(); } - key.Close(); return DeleteSubKey(subKeyName); } @@ -101,25 +131,25 @@ static inline bool UINT32ToBool(UInt32 value) { return (value != 0); } LONG CKey::DeleteValue(LPCTSTR name) throw() { - MYASSERT(_object != NULL); + MY_ASSUME(_object != NULL); return ::RegDeleteValue(_object, name); } #ifndef _UNICODE LONG CKey::DeleteValue(LPCWSTR name) { - MYASSERT(_object != NULL); + MY_ASSUME(_object != NULL); if (g_IsNT) return ::RegDeleteValueW(_object, name); - return DeleteValue(name == 0 ? 0 : (LPCSTR)GetSystemString(name)); + return DeleteValue(name == NULL ? NULL : (LPCSTR)GetSystemString(name)); } #endif LONG CKey::SetValue(LPCTSTR name, UInt32 value) throw() { - MYASSERT(_object != NULL); + MY_ASSUME(_object != NULL); return RegSetValueEx(_object, name, 0, REG_DWORD, - (BYTE * const)&value, sizeof(UInt32)); + (const BYTE *)&value, sizeof(UInt32)); } LONG CKey::SetValue(LPCTSTR name, bool value) throw() @@ -127,20 +157,23 @@ LONG CKey::SetValue(LPCTSTR name, bool value) throw() return SetValue(name, BoolToUINT32(value)); } + +// value must be string that is NULL terminated LONG CKey::SetValue(LPCTSTR name, LPCTSTR value) throw() { MYASSERT(value != NULL); - MYASSERT(_object != NULL); + MY_ASSUME(_object != NULL); + // note: RegSetValueEx supports (value == NULL), if (cbData == 0) return RegSetValueEx(_object, name, 0, REG_SZ, - (const BYTE * )value, (lstrlen(value) + 1) * sizeof(TCHAR)); + (const BYTE *)value, (DWORD)(((DWORD)lstrlen(value) + 1) * sizeof(TCHAR))); } /* LONG CKey::SetValue(LPCTSTR name, const CSysString &value) { MYASSERT(value != NULL); - MYASSERT(_object != NULL); - return RegSetValueEx(_object, name, NULL, REG_SZ, + MY_ASSUME(_object != NULL); + return RegSetValueEx(_object, name, 0, REG_SZ, (const BYTE *)(const TCHAR *)value, (value.Len() + 1) * sizeof(TCHAR)); } */ @@ -150,12 +183,13 @@ LONG CKey::SetValue(LPCTSTR name, const CSysString &value) LONG CKey::SetValue(LPCWSTR name, LPCWSTR value) { MYASSERT(value != NULL); - MYASSERT(_object != NULL); + MY_ASSUME(_object != NULL); if (g_IsNT) - return RegSetValueExW(_object, name, NULL, REG_SZ, - (const BYTE * )value, (DWORD)((wcslen(value) + 1) * sizeof(wchar_t))); - return SetValue(name == 0 ? 0 : (LPCSTR)GetSystemString(name), - value == 0 ? 0 : (LPCSTR)GetSystemString(value)); + return RegSetValueExW(_object, name, 0, REG_SZ, + (const BYTE *)value, (DWORD)(((DWORD)wcslen(value) + 1) * sizeof(wchar_t))); + return SetValue(name == NULL ? NULL : + (LPCSTR)GetSystemString(name), + (LPCSTR)GetSystemString(value)); } #endif @@ -164,7 +198,7 @@ LONG CKey::SetValue(LPCWSTR name, LPCWSTR value) LONG CKey::SetValue(LPCTSTR name, const void *value, UInt32 size) throw() { MYASSERT(value != NULL); - MYASSERT(_object != NULL); + MY_ASSUME(_object != NULL); return RegSetValueEx(_object, name, 0, REG_BINARY, (const BYTE *)value, size); } @@ -189,130 +223,182 @@ LONG CKey::SetKeyValue(LPCTSTR keyName, LPCTSTR valueName, LPCTSTR value) throw( return res; } -LONG CKey::QueryValue(LPCTSTR name, UInt32 &value) throw() -{ - DWORD type = 0; - DWORD count = sizeof(DWORD); - LONG res = RegQueryValueEx(_object, (LPTSTR)name, NULL, &type, - (LPBYTE)&value, &count); - MYASSERT((res != ERROR_SUCCESS) || (type == REG_DWORD)); - MYASSERT((res != ERROR_SUCCESS) || (count == sizeof(UInt32))); - return res; -} -LONG CKey::QueryValue(LPCTSTR name, bool &value) throw() +LONG CKey::GetValue_UInt32_IfOk(LPCTSTR name, UInt32 &value) throw() { - UInt32 uintValue = BoolToUINT32(value); - LONG res = QueryValue(name, uintValue); - value = UINT32ToBool(uintValue); + DWORD type = 0; + DWORD count = sizeof(value); + UInt32 value2; // = value; + const LONG res = QueryValueEx(name, &type, (LPBYTE)&value2, &count); + if (res == ERROR_SUCCESS) + { + // ERROR_UNSUPPORTED_TYPE + if (count != sizeof(value) || type != REG_DWORD) + return ERROR_UNSUPPORTED_TYPE; // ERROR_INVALID_DATA; + value = value2; + } return res; } -LONG CKey::GetValue_IfOk(LPCTSTR name, UInt32 &value) throw() +LONG CKey::GetValue_UInt64_IfOk(LPCTSTR name, UInt64 &value) throw() { - UInt32 newVal; - LONG res = QueryValue(name, newVal); + DWORD type = 0; + DWORD count = sizeof(value); + UInt64 value2; // = value; + const LONG res = QueryValueEx(name, &type, (LPBYTE)&value2, &count); if (res == ERROR_SUCCESS) - value = newVal; + { + if (count != sizeof(value) || type != REG_QWORD) + return ERROR_UNSUPPORTED_TYPE; + value = value2; + } return res; } -LONG CKey::GetValue_IfOk(LPCTSTR name, bool &value) throw() +LONG CKey::GetValue_bool_IfOk(LPCTSTR name, bool &value) throw() { - bool newVal; - LONG res = QueryValue(name, newVal); + UInt32 uintValue; + const LONG res = GetValue_UInt32_IfOk(name, uintValue); if (res == ERROR_SUCCESS) - value = newVal; + value = UINT32ToBool(uintValue); return res; } -LONG CKey::QueryValue(LPCTSTR name, LPTSTR value, UInt32 &count) throw() -{ - DWORD type = 0; - LONG res = RegQueryValueEx(_object, (LPTSTR)name, NULL, &type, (LPBYTE)value, (DWORD *)&count); - MYASSERT((res != ERROR_SUCCESS) || (type == REG_SZ) || (type == REG_MULTI_SZ) || (type == REG_EXPAND_SZ)); - return res; -} + LONG CKey::QueryValue(LPCTSTR name, CSysString &value) { value.Empty(); - DWORD type = 0; - UInt32 curSize = 0; - LONG res = RegQueryValueEx(_object, (LPTSTR)name, NULL, &type, NULL, (DWORD *)&curSize); - if (res != ERROR_SUCCESS && res != ERROR_MORE_DATA) - return res; - UInt32 curSize2 = curSize; - res = QueryValue(name, value.GetBuf(curSize), curSize2); - if (curSize > curSize2) - curSize = curSize2; - value.ReleaseBuf_CalcLen(curSize / sizeof(TCHAR)); + LONG res = ERROR_SUCCESS; + { + // if we don't want multiple calls here, + // we can use big value (264) here. + // 3 is default available length in new string. + DWORD size_prev = 3 * sizeof(TCHAR); + // at least 2 attempts are required. But we use more attempts for cases, + // where string can be changed by anothner process + for (unsigned i = 0; i < 2 + 2; i++) + { + DWORD type = 0; + DWORD size = size_prev; + { + LPBYTE buf = (LPBYTE)value.GetBuf(size / sizeof(TCHAR)); + res = QueryValueEx(name, &type, size == 0 ? NULL : buf, &size); + // if (size_prev == 0), then (res == ERROR_SUCCESS) is expected here, because we requested only size. + } + if (res == ERROR_SUCCESS || res == ERROR_MORE_DATA) + { + if (type != REG_SZ && type != REG_EXPAND_SZ) + { + res = ERROR_UNSUPPORTED_TYPE; + size = 0; + } + } + else + size = 0; + if (size > size_prev) + { + size_prev = size; + size = 0; + res = ERROR_MORE_DATA; + } + value.ReleaseBuf_CalcLen(size / sizeof(TCHAR)); + if (res != ERROR_MORE_DATA) + return res; + } + } return res; } #ifndef _UNICODE -LONG CKey::QueryValue(LPCWSTR name, LPWSTR value, UInt32 &count) -{ - DWORD type = 0; - LONG res = RegQueryValueExW(_object, name, NULL, &type, (LPBYTE)value, (DWORD *)&count); - MYASSERT((res != ERROR_SUCCESS) || (type == REG_SZ) || (type == REG_MULTI_SZ) || (type == REG_EXPAND_SZ)); - return res; -} - LONG CKey::QueryValue(LPCWSTR name, UString &value) { value.Empty(); - DWORD type = 0; - UInt32 curSize = 0; - - LONG res; - + LONG res = ERROR_SUCCESS; if (g_IsNT) { - res = RegQueryValueExW(_object, name, NULL, &type, NULL, (DWORD *)&curSize); - if (res != ERROR_SUCCESS && res != ERROR_MORE_DATA) - return res; - UInt32 curSize2 = curSize; - res = QueryValue(name, value.GetBuf(curSize), curSize2); - if (curSize > curSize2) - curSize = curSize2; - value.ReleaseBuf_CalcLen(curSize / sizeof(wchar_t)); + DWORD size_prev = 3 * sizeof(wchar_t); + for (unsigned i = 0; i < 2 + 2; i++) + { + DWORD type = 0; + DWORD size = size_prev; + { + LPBYTE buf = (LPBYTE)value.GetBuf(size / sizeof(wchar_t)); + res = RegQueryValueExW(_object, name, NULL, &type, + size == 0 ? NULL : buf, &size); + } + if (res == ERROR_SUCCESS || res == ERROR_MORE_DATA) + { + if (type != REG_SZ && type != REG_EXPAND_SZ) + { + res = ERROR_UNSUPPORTED_TYPE; + size = 0; + } + } + else + size = 0; + if (size > size_prev) + { + size_prev = size; + size = 0; + res = ERROR_MORE_DATA; + } + value.ReleaseBuf_CalcLen(size / sizeof(wchar_t)); + if (res != ERROR_MORE_DATA) + return res; + } } else { AString vTemp; - res = QueryValue(name == 0 ? 0 : (LPCSTR)GetSystemString(name), vTemp); + res = QueryValue(name == NULL ? NULL : (LPCSTR)GetSystemString(name), vTemp); value = GetUnicodeString(vTemp); } - return res; } #endif -LONG CKey::QueryValue(LPCTSTR name, void *value, UInt32 &count) throw() +LONG CKey::QueryValue_Binary(LPCTSTR name, CByteBuffer &value) { - DWORD type = 0; - LONG res = RegQueryValueEx(_object, (LPTSTR)name, NULL, &type, (LPBYTE)value, (DWORD *)&count); - MYASSERT((res != ERROR_SUCCESS) || (type == REG_BINARY)); + // value.Free(); + DWORD size_prev = 0; + LONG res = ERROR_SUCCESS; + for (unsigned i = 0; i < 2 + 2; i++) + { + DWORD type = 0; + DWORD size = size_prev; + value.Alloc(size_prev); + res = QueryValueEx(name, &type, value.NonConstData(), &size); + // if (size_prev == 0), then (res == ERROR_SUCCESS) is expected here, because we requested only size. + if (res == ERROR_SUCCESS || res == ERROR_MORE_DATA) + { + if (type != REG_BINARY) + { + res = ERROR_UNSUPPORTED_TYPE; + size = 0; + } + } + else + size = 0; + if (size > size_prev) + { + size_prev = size; + size = 0; + res = ERROR_MORE_DATA; + } + if (size < value.Size()) + value.ChangeSize_KeepData(size, size); + if (res != ERROR_MORE_DATA) + return res; + } return res; } -LONG CKey::QueryValue(LPCTSTR name, CByteBuffer &value, UInt32 &dataSize) -{ - DWORD type = 0; - dataSize = 0; - LONG res = RegQueryValueEx(_object, (LPTSTR)name, NULL, &type, NULL, (DWORD *)&dataSize); - if (res != ERROR_SUCCESS && res != ERROR_MORE_DATA) - return res; - value.Alloc(dataSize); - return QueryValue(name, (BYTE *)value, dataSize); -} - LONG CKey::EnumKeys(CSysStringVector &keyNames) { keyNames.Clear(); @@ -321,23 +407,23 @@ LONG CKey::EnumKeys(CSysStringVector &keyNames) { const unsigned kBufSize = MAX_PATH + 1; // 256 in ATL FILETIME lastWriteTime; - UInt32 nameSize = kBufSize; - LONG result = ::RegEnumKeyEx(_object, index, keyName.GetBuf(kBufSize), - (DWORD *)&nameSize, NULL, NULL, NULL, &lastWriteTime); + DWORD nameSize = kBufSize; + const LONG res = ::RegEnumKeyEx(_object, index, + keyName.GetBuf(kBufSize), &nameSize, + NULL, NULL, NULL, &lastWriteTime); keyName.ReleaseBuf_CalcLen(kBufSize); - if (result == ERROR_NO_MORE_ITEMS) - break; - if (result != ERROR_SUCCESS) - return result; + if (res == ERROR_NO_MORE_ITEMS) + return ERROR_SUCCESS; + if (res != ERROR_SUCCESS) + return res; keyNames.Add(keyName); } - return ERROR_SUCCESS; } + LONG CKey::SetValue_Strings(LPCTSTR valueName, const UStringVector &strings) { size_t numChars = 0; - unsigned i; for (i = 0; i < strings.Size(); i++) @@ -349,10 +435,11 @@ LONG CKey::SetValue_Strings(LPCTSTR valueName, const UStringVector &strings) for (i = 0; i < strings.Size(); i++) { const UString &s = strings[i]; - size_t size = s.Len() + 1; + const size_t size = s.Len() + 1; wmemcpy(buffer + pos, s, size); pos += size; } + // if (pos != numChars) return E_FAIL; return SetValue(valueName, buffer, (UInt32)numChars * sizeof(wchar_t)); } @@ -360,20 +447,18 @@ LONG CKey::GetValue_Strings(LPCTSTR valueName, UStringVector &strings) { strings.Clear(); CByteBuffer buffer; - UInt32 dataSize = 0; - LONG res = QueryValue(valueName, buffer, dataSize); + const LONG res = QueryValue_Binary(valueName, buffer); if (res != ERROR_SUCCESS) return res; - if (dataSize > buffer.Size()) - return E_FAIL; - if (dataSize % sizeof(wchar_t) != 0) - return E_FAIL; - - const wchar_t *data = (const wchar_t *)(const Byte *)buffer; - size_t numChars = dataSize / sizeof(wchar_t); + const size_t dataSize = buffer.Size(); + if (dataSize % sizeof(wchar_t)) + return ERROR_INVALID_DATA; + const wchar_t *data = (const wchar_t *)(const void *)(const Byte *)buffer; + const size_t numChars = dataSize / sizeof(wchar_t); + // we can check that all names are finished + // if (numChars != 0 && data[numChars - 1] != 0) return ERROR_INVALID_DATA; size_t prev = 0; UString s; - for (size_t i = 0; i < numChars; i++) { if (data[i] == 0) @@ -383,7 +468,6 @@ LONG CKey::GetValue_Strings(LPCTSTR valueName, UStringVector &strings) prev = i + 1; } } - return res; } diff --git a/src/plugins/7zip/7za/cpp/Windows/Registry.h b/src/plugins/7zip/7za/cpp/Windows/Registry.h index ca79dfe3..74ee9196 100644 --- a/src/plugins/7zip/7za/cpp/Windows/Registry.h +++ b/src/plugins/7zip/7za/cpp/Windows/Registry.h @@ -1,7 +1,7 @@ // Windows/Registry.h -#ifndef __WINDOWS_REGISTRY_H -#define __WINDOWS_REGISTRY_H +#ifndef ZIP7_INC_WINDOWS_REGISTRY_H +#define ZIP7_INC_WINDOWS_REGISTRY_H #include "../Common/MyBuffer.h" #include "../Common/MyString.h" @@ -14,6 +14,13 @@ LONG SetValue(HKEY parentKey, LPCTSTR keyName, LPCTSTR valueName, LPCTSTR value) class CKey { HKEY _object; + + LONG QueryValueEx(LPCTSTR lpValueName, LPDWORD lpType, + LPBYTE lpData, LPDWORD lpcbData) + { + return RegQueryValueEx(_object, lpValueName, NULL, lpType, lpData, lpcbData); + } + public: CKey(): _object(NULL) {} ~CKey() { Close(); } @@ -22,13 +29,14 @@ class CKey void Attach(HKEY key) { _object = key; } HKEY Detach() { - HKEY key = _object; + const HKEY key = _object; _object = NULL; return key; } LONG Create(HKEY parentKey, LPCTSTR keyName, - LPTSTR keyClass = REG_NONE, DWORD options = REG_OPTION_NON_VOLATILE, + LPTSTR keyClass = REG_NONE, + DWORD options = REG_OPTION_NON_VOLATILE, REGSAM accessMask = KEY_ALL_ACCESS, LPSECURITY_ATTRIBUTES securityAttributes = NULL, LPDWORD disposition = NULL) throw(); @@ -40,18 +48,18 @@ class CKey LONG RecurseDeleteKey(LPCTSTR subKeyName) throw(); LONG DeleteValue(LPCTSTR name) throw(); - #ifndef _UNICODE +#ifndef _UNICODE LONG DeleteValue(LPCWSTR name); - #endif +#endif LONG SetValue(LPCTSTR valueName, UInt32 value) throw(); LONG SetValue(LPCTSTR valueName, bool value) throw(); LONG SetValue(LPCTSTR valueName, LPCTSTR value) throw(); // LONG SetValue(LPCTSTR valueName, const CSysString &value); - #ifndef _UNICODE +#ifndef _UNICODE LONG SetValue(LPCWSTR name, LPCWSTR value); // LONG SetValue(LPCWSTR name, const UString &value); - #endif +#endif LONG SetValue(LPCTSTR name, const void *value, UInt32 size) throw(); @@ -60,21 +68,25 @@ class CKey LONG SetKeyValue(LPCTSTR keyName, LPCTSTR valueName, LPCTSTR value) throw(); - LONG QueryValue(LPCTSTR name, UInt32 &value) throw(); - LONG QueryValue(LPCTSTR name, bool &value) throw(); - LONG QueryValue(LPCTSTR name, LPTSTR value, UInt32 &dataSize) throw(); - LONG QueryValue(LPCTSTR name, CSysString &value); - - LONG GetValue_IfOk(LPCTSTR name, UInt32 &value) throw(); - LONG GetValue_IfOk(LPCTSTR name, bool &value) throw(); + // GetValue_[type]_IfOk(): + // if (return_result == ERROR_SUCCESS), (value) variable was read from registry + // if (return_result != ERROR_SUCCESS), (value) variable was not changed + LONG GetValue_UInt32_IfOk(LPCTSTR name, UInt32 &value) throw(); + LONG GetValue_UInt64_IfOk(LPCTSTR name, UInt64 &value) throw(); + LONG GetValue_bool_IfOk(LPCTSTR name, bool &value) throw(); - #ifndef _UNICODE - LONG QueryValue(LPCWSTR name, LPWSTR value, UInt32 &dataSize); + // QueryValue(): + // if (return_result == ERROR_SUCCESS), (value) string was read from registry + // if (return_result != ERROR_SUCCESS), (value) string was cleared + LONG QueryValue(LPCTSTR name, CSysString &value); +#ifndef _UNICODE LONG QueryValue(LPCWSTR name, UString &value); - #endif +#endif - LONG QueryValue(LPCTSTR name, void *value, UInt32 &dataSize) throw(); - LONG QueryValue(LPCTSTR name, CByteBuffer &value, UInt32 &dataSize); + // QueryValue_Binary(): + // if (return_result == ERROR_SUCCESS), (value) buffer was read from registry (BINARY data) + // if (return_result != ERROR_SUCCESS), (value) buffer was cleared + LONG QueryValue_Binary(LPCTSTR name, CByteBuffer &value); LONG EnumKeys(CSysStringVector &keyNames); }; diff --git a/src/plugins/7zip/7za/cpp/Windows/ResourceString.cpp b/src/plugins/7zip/7za/cpp/Windows/ResourceString.cpp index cc8b964a..ae8182ed 100644 --- a/src/plugins/7zip/7za/cpp/Windows/ResourceString.cpp +++ b/src/plugins/7zip/7za/cpp/Windows/ResourceString.cpp @@ -25,10 +25,10 @@ static CSysString MyLoadStringA(HINSTANCE hInstance, UINT resourceID) do { size <<= 1; - len = ::LoadString(hInstance, resourceID, s.GetBuf(size - 1), size); + len = ::LoadString(hInstance, resourceID, s.GetBuf((unsigned)size - 1), size); } while (size - len <= 1); - s.ReleaseBuf_CalcLen(len); + s.ReleaseBuf_CalcLen((unsigned)len); return s; } @@ -43,10 +43,10 @@ static void MyLoadString2(HINSTANCE hInstance, UINT resourceID, UString &s) do { size <<= 1; - len = ::LoadStringW(hInstance, resourceID, s.GetBuf(size - 1), size); + len = ::LoadStringW(hInstance, resourceID, s.GetBuf((unsigned)size - 1), size); } while (size - len <= 1); - s.ReleaseBuf_CalcLen(len); + s.ReleaseBuf_CalcLen((unsigned)len); } // NT4 doesn't support LoadStringW(,,, 0) to get pointer to resource string. So we don't use it. diff --git a/src/plugins/7zip/7za/cpp/Windows/ResourceString.h b/src/plugins/7zip/7za/cpp/Windows/ResourceString.h index f0bdabf4..773307b9 100644 --- a/src/plugins/7zip/7za/cpp/Windows/ResourceString.h +++ b/src/plugins/7zip/7za/cpp/Windows/ResourceString.h @@ -1,9 +1,10 @@ // Windows/ResourceString.h -#ifndef __WINDOWS_RESOURCE_STRING_H -#define __WINDOWS_RESOURCE_STRING_H +#ifndef ZIP7_INC_WINDOWS_RESOURCE_STRING_H +#define ZIP7_INC_WINDOWS_RESOURCE_STRING_H #include "../Common/MyString.h" +#include "../Common/MyWindows.h" namespace NWindows { diff --git a/src/plugins/7zip/7za/cpp/Windows/SecurityUtils.cpp b/src/plugins/7zip/7za/cpp/Windows/SecurityUtils.cpp index 67a9d7fd..d4282d07 100644 --- a/src/plugins/7zip/7za/cpp/Windows/SecurityUtils.cpp +++ b/src/plugins/7zip/7za/cpp/Windows/SecurityUtils.cpp @@ -2,8 +2,6 @@ #include "StdAfx.h" -#include "../Common/MyString.h" - #include "SecurityUtils.h" namespace NWindows { @@ -34,7 +32,7 @@ bool MyLookupAccountSid(LPCTSTR systemName, PSID sid, static void SetLsaString(LPWSTR src, PLSA_UNICODE_STRING dest) { - int len = (int)wcslen(src); + const size_t len = (size_t)wcslen(src); dest->Length = (USHORT)(len * sizeof(WCHAR)); dest->MaximumLength = (USHORT)((len + 1) * sizeof(WCHAR)); dest->Buffer = src; @@ -52,8 +50,10 @@ static void MyLookupSids(CPolicy &policy, PSID ps) } */ +extern "C" { + #ifndef _UNICODE -typedef BOOL (WINAPI * LookupAccountNameWP)( +typedef BOOL (WINAPI * Func_LookupAccountNameW)( LPCWSTR lpSystemName, LPCWSTR lpAccountName, PSID Sid, @@ -64,14 +64,19 @@ typedef BOOL (WINAPI * LookupAccountNameWP)( ); #endif +} + static PSID GetSid(LPWSTR accountName) { #ifndef _UNICODE - HMODULE hModule = GetModuleHandle(TEXT("Advapi32.dll")); - if (hModule == NULL) + const HMODULE hModule = GetModuleHandle(TEXT("advapi32.dll")); + if (!hModule) return NULL; - LookupAccountNameWP lookupAccountNameW = (LookupAccountNameWP)GetProcAddress(hModule, "LookupAccountNameW"); - if (lookupAccountNameW == NULL) + const + Func_LookupAccountNameW lookupAccountNameW = Z7_GET_PROC_ADDRESS( + Func_LookupAccountNameW, hModule, + "LookupAccountNameW"); + if (!lookupAccountNameW) return NULL; #endif @@ -81,21 +86,21 @@ static PSID GetSid(LPWSTR accountName) #ifdef _UNICODE ::LookupAccountNameW #else - lookupAccountNameW + lookupAccountNameW #endif - (NULL, accountName, NULL, &sidLen, NULL, &domainLen, &sidNameUse)) + (NULL, accountName, NULL, &sidLen, NULL, &domainLen, &sidNameUse)) { if (::GetLastError() == ERROR_INSUFFICIENT_BUFFER) { - PSID pSid = ::HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sidLen); + const PSID pSid = ::HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sidLen); LPWSTR domainName = (LPWSTR)::HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (domainLen + 1) * sizeof(WCHAR)); - BOOL res = + const BOOL res = #ifdef _UNICODE ::LookupAccountNameW #else - lookupAccountNameW + lookupAccountNameW #endif - (NULL, accountName, pSid, &sidLen, domainName, &domainLen, &sidNameUse); + (NULL, accountName, pSid, &sidLen, domainName, &domainLen, &sidNameUse); ::HeapFree(GetProcessHeap(), 0, domainName); if (res) return pSid; @@ -104,7 +109,7 @@ static PSID GetSid(LPWSTR accountName) return NULL; } -#define MY__SE_LOCK_MEMORY_NAME L"SeLockMemoryPrivilege" +#define Z7_WIN_SE_LOCK_MEMORY_NAME L"SeLockMemoryPrivilege" bool AddLockMemoryPrivilege() { @@ -124,13 +129,13 @@ bool AddLockMemoryPrivilege() != 0) return false; LSA_UNICODE_STRING userRights; - wchar_t s[128] = MY__SE_LOCK_MEMORY_NAME; + wchar_t s[128] = Z7_WIN_SE_LOCK_MEMORY_NAME; SetLsaString(s, &userRights); WCHAR userName[256 + 2]; DWORD size = 256; if (!GetUserNameW(userName, &size)) return false; - PSID psid = GetSid(userName); + const PSID psid = GetSid(userName); if (psid == NULL) return false; bool res = false; @@ -169,7 +174,7 @@ bool AddLockMemoryPrivilege() res = true; } */ - NTSTATUS status = policy.AddAccountRights(psid, &userRights); + const NTSTATUS status = policy.AddAccountRights(psid, &userRights); if (status == 0) res = true; // ULONG res = LsaNtStatusToWinError(status); diff --git a/src/plugins/7zip/7za/cpp/Windows/SecurityUtils.h b/src/plugins/7zip/7za/cpp/Windows/SecurityUtils.h index 8966dfd3..9775d095 100644 --- a/src/plugins/7zip/7za/cpp/Windows/SecurityUtils.h +++ b/src/plugins/7zip/7za/cpp/Windows/SecurityUtils.h @@ -1,11 +1,42 @@ // Windows/SecurityUtils.h -#ifndef __WINDOWS_SECURITY_UTILS_H -#define __WINDOWS_SECURITY_UTILS_H +#ifndef ZIP7_INC_WINDOWS_SECURITY_UTILS_H +#define ZIP7_INC_WINDOWS_SECURITY_UTILS_H +#if defined(__MINGW32__) || defined(__MINGW64__) +#include +#else #include +#endif + +#include "WinDefs.h" + +#ifndef _UNICODE + +extern "C" { +typedef NTSTATUS (NTAPI *Func_LsaOpenPolicy)(PLSA_UNICODE_STRING SystemName, + PLSA_OBJECT_ATTRIBUTES ObjectAttributes, ACCESS_MASK DesiredAccess, PLSA_HANDLE PolicyHandle); +typedef NTSTATUS (NTAPI *Func_LsaClose)(LSA_HANDLE ObjectHandle); +typedef NTSTATUS (NTAPI *Func_LsaAddAccountRights)(LSA_HANDLE PolicyHandle, + PSID AccountSid, PLSA_UNICODE_STRING UserRights, ULONG CountOfRights ); +#define MY_STATUS_NOT_IMPLEMENTED ((NTSTATUS)0xC0000002L) +} + +Z7_DIAGNOSTIC_IGNORE_CAST_FUNCTION + +#define POLICY_FUNC_CALL(fff, str) \ + if (hModule == NULL) return MY_STATUS_NOT_IMPLEMENTED; \ + const Func_ ## fff v = Z7_GET_PROC_ADDRESS(Func_ ## fff, hModule, str); \ + if (!v) return MY_STATUS_NOT_IMPLEMENTED; \ + const NTSTATUS res = v + +#else + +#define POLICY_FUNC_CALL(fff, str) \ + const NTSTATUS res = ::fff + +#endif -#include "Defs.h" namespace NWindows { namespace NSecurity { @@ -14,7 +45,7 @@ class CAccessToken { HANDLE _handle; public: - CAccessToken(): _handle(NULL) {}; + CAccessToken(): _handle(NULL) {} ~CAccessToken() { Close(); } bool Close() { @@ -53,15 +84,9 @@ class CAccessToken }; -#ifndef _UNICODE -typedef NTSTATUS (NTAPI *LsaOpenPolicyP)(PLSA_UNICODE_STRING SystemName, - PLSA_OBJECT_ATTRIBUTES ObjectAttributes, ACCESS_MASK DesiredAccess, PLSA_HANDLE PolicyHandle); -typedef NTSTATUS (NTAPI *LsaCloseP)(LSA_HANDLE ObjectHandle); -typedef NTSTATUS (NTAPI *LsaAddAccountRightsP)(LSA_HANDLE PolicyHandle, - PSID AccountSid, PLSA_UNICODE_STRING UserRights, ULONG CountOfRights ); -#define MY_STATUS_NOT_IMPLEMENTED ((NTSTATUS)0xC0000002L) -#endif + + struct CPolicy { protected: @@ -74,51 +99,25 @@ struct CPolicy CPolicy(): _handle(NULL) { #ifndef _UNICODE - hModule = GetModuleHandle(TEXT("Advapi32.dll")); + hModule = GetModuleHandle(TEXT("advapi32.dll")); #endif - }; + } ~CPolicy() { Close(); } NTSTATUS Open(PLSA_UNICODE_STRING systemName, PLSA_OBJECT_ATTRIBUTES objectAttributes, ACCESS_MASK desiredAccess) { - #ifndef _UNICODE - if (hModule == NULL) - return MY_STATUS_NOT_IMPLEMENTED; - LsaOpenPolicyP lsaOpenPolicy = (LsaOpenPolicyP)GetProcAddress(hModule, "LsaOpenPolicy"); - if (lsaOpenPolicy == NULL) - return MY_STATUS_NOT_IMPLEMENTED; - #endif - Close(); - return - #ifdef _UNICODE - ::LsaOpenPolicy - #else - lsaOpenPolicy - #endif + POLICY_FUNC_CALL (LsaOpenPolicy, "LsaOpenPolicy") (systemName, objectAttributes, desiredAccess, &_handle); + return res; } NTSTATUS Close() { if (_handle == NULL) return 0; - - #ifndef _UNICODE - if (hModule == NULL) - return MY_STATUS_NOT_IMPLEMENTED; - LsaCloseP lsaClose = (LsaCloseP)GetProcAddress(hModule, "LsaClose"); - if (lsaClose == NULL) - return MY_STATUS_NOT_IMPLEMENTED; - #endif - - NTSTATUS res = - #ifdef _UNICODE - ::LsaClose - #else - lsaClose - #endif + POLICY_FUNC_CALL (LsaClose, "LsaClose") (_handle); _handle = NULL; return res; @@ -137,21 +136,9 @@ struct CPolicy NTSTATUS AddAccountRights(PSID accountSid, PLSA_UNICODE_STRING userRights, ULONG countOfRights) { - #ifndef _UNICODE - if (hModule == NULL) - return MY_STATUS_NOT_IMPLEMENTED; - LsaAddAccountRightsP lsaAddAccountRights = (LsaAddAccountRightsP)GetProcAddress(hModule, "LsaAddAccountRights"); - if (lsaAddAccountRights == NULL) - return MY_STATUS_NOT_IMPLEMENTED; - #endif - - return - #ifdef _UNICODE - ::LsaAddAccountRights - #else - lsaAddAccountRights - #endif + POLICY_FUNC_CALL (LsaAddAccountRights, "LsaAddAccountRights") (_handle, accountSid, userRights, countOfRights); + return res; } NTSTATUS AddAccountRights(PSID accountSid, PLSA_UNICODE_STRING userRights) { return AddAccountRights(accountSid, userRights, 1); } diff --git a/src/plugins/7zip/7za/cpp/Windows/Shell.cpp b/src/plugins/7zip/7za/cpp/Windows/Shell.cpp index 8e82c14a..01ceb228 100644 --- a/src/plugins/7zip/7za/cpp/Windows/Shell.cpp +++ b/src/plugins/7zip/7za/cpp/Windows/Shell.cpp @@ -3,17 +3,49 @@ #include "StdAfx.h" #include "../Common/MyCom.h" -#ifndef _UNICODE #include "../Common/StringConvert.h" -#endif #include "COM.h" +#include "FileName.h" +#include "MemoryGlobal.h" #include "Shell.h" #ifndef _UNICODE extern bool g_IsNT; #endif +// MSVC6 and old SDK don't support this function: +// #define LWSTDAPI EXTERN_C DECLSPEC_IMPORT HRESULT STDAPICALLTYPE +// LWSTDAPI StrRetToStrW(STRRET *pstr, LPCITEMIDLIST pidl, LPWSTR *ppsz); + +// #define SHOW_DEBUG_SHELL + +#ifdef SHOW_DEBUG_SHELL + +#include "../Common/IntToString.h" + +static void Print_Number(UInt32 number, const char *s) +{ + AString s2; + s2.Add_UInt32(number); + s2.Add_Space(); + s2 += s; + OutputDebugStringA(s2); +} + +#define ODS(sz) { OutputDebugStringA(sz); } +#define ODS_U(s) { OutputDebugStringW(s); } +#define ODS_(op) { op; } + +#else + +#define ODS(sz) +#define ODS_U(s) +#define ODS_(op) + +#endif + + namespace NWindows { namespace NShell { @@ -23,12 +55,24 @@ namespace NShell { void CItemIDList::Free() { - if (m_Object == NULL) + if (!m_Object) return; + /* DOCs: + SHGetMalloc was introduced in Windows 95 and Microsoft Windows NT 4.0, + but as of Windows 2000 it is no longer necessary. + In its place, programs can call the equivalent (and easier to use) CoTaskMemAlloc and CoTaskMemFree. + Description from oldnewthings: + shell functions could work without COM (if OLE32.DLL is not loaded), + but now if OLE32.DLL is loaded, then shell functions and com functions do same things. + 22.02: so we use OLE32.DLL function to free memory: + */ + /* CMyComPtr shellMalloc; if (::SHGetMalloc(&shellMalloc) != NOERROR) throw 41099; shellMalloc->Free(m_Object); + */ + CoTaskMemFree(m_Object); m_Object = NULL; } @@ -65,9 +109,354 @@ CItemIDList& CItemIDList::operator=(const CItemIDList &object) } */ + +static HRESULT ReadUnicodeStrings(const wchar_t *p, size_t size, UStringVector &names) +{ + names.Clear(); + const wchar_t *lim = p + size; + UString s; + /* + if (size == 0 || p[size - 1] != 0) + return E_INVALIDARG; + if (size == 1) + return S_OK; + if (p[size - 2] != 0) + return E_INVALIDARG; + */ + for (;;) + { + const wchar_t *start = p; + for (;;) + { + if (p == lim) return E_INVALIDARG; // S_FALSE + if (*p++ == 0) + break; + } + const size_t num = (size_t)(p - start); + if (num == 1) + { + if (p != lim) return E_INVALIDARG; // S_FALSE + return S_OK; + } + s.SetFrom(start, (unsigned)(num - 1)); + ODS_U(s) + names.Add(s); + // names.ReserveOnePosition(); + // names.AddInReserved_Ptr_of_new(new UString((unsigned)num - 1, start)); + } +} + + +static HRESULT ReadAnsiStrings(const char *p, size_t size, UStringVector &names) +{ + names.Clear(); + AString name; + for (; size != 0; size--) + { + const char c = *p++; + if (c == 0) + { + if (name.IsEmpty()) + return S_OK; + names.Add(GetUnicodeString(name)); + name.Empty(); + } + else + name.Add_Char(c); + } + return E_INVALIDARG; +} + + +#define INIT_FORMATETC_HGLOBAL(type) { (type), NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL } + +static HRESULT DataObject_GetData_HGLOBAL(IDataObject *dataObject, CLIPFORMAT cf, NCOM::CStgMedium &medium) +{ + FORMATETC etc = INIT_FORMATETC_HGLOBAL(cf); + RINOK(dataObject->GetData(&etc, &medium)) + if (medium.tymed != TYMED_HGLOBAL) + return E_INVALIDARG; + return S_OK; +} + +static HRESULT DataObject_GetData_HDROP_Names(IDataObject *dataObject, UStringVector &names) +{ + names.Clear(); + NCOM::CStgMedium medium; + + /* Win10 : if (dataObject) is from IContextMenu::Initialize() and + if (len_of_path >= MAX_PATH (260) for some file in data object) + { + GetData() returns HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER) + "The data area passed to a system call is too small", + Is there a way to fix this code for long paths? + } */ + + RINOK(DataObject_GetData_HGLOBAL(dataObject, CF_HDROP, medium)) + const size_t blockSize = GlobalSize(medium.hGlobal); + if (blockSize < sizeof(DROPFILES)) + return E_INVALIDARG; + NMemory::CGlobalLock dropLock(medium.hGlobal); + const DROPFILES *dropFiles = (const DROPFILES *)dropLock.GetPointer(); + if (!dropFiles) + return E_INVALIDARG; + if (blockSize < dropFiles->pFiles + || dropFiles->pFiles < sizeof(DROPFILES) + // || dropFiles->pFiles != sizeof(DROPFILES) + ) + return E_INVALIDARG; + const size_t size = blockSize - dropFiles->pFiles; + const void *namesData = (const Byte *)(const void *)dropFiles + dropFiles->pFiles; + HRESULT hres; + if (dropFiles->fWide) + { + if (size % sizeof(wchar_t) != 0) + return E_INVALIDARG; + hres = ReadUnicodeStrings((const wchar_t *)namesData, size / sizeof(wchar_t), names); + } + else + hres = ReadAnsiStrings((const char *)namesData, size, names); + + ODS_(Print_Number(names.Size(), "DataObject_GetData_HDROP_Names")) + return hres; +} + + + +// CF_IDLIST: +#define MYWIN_CFSTR_SHELLIDLIST TEXT("Shell IDList Array") + +typedef struct +{ + UINT cidl; + UINT aoffset[1]; +} MYWIN_CIDA; +/* + cidl : number of PIDLs that are being transferred, not including the parent folder. + aoffset : An array of offsets, relative to the beginning of this structure. + aoffset[0] - fully qualified PIDL of a parent folder. + If this PIDL is empty, the parent folder is the desktop. + aoffset[1] ... aoffset[cidl] : offset to one of the PIDLs to be transferred. + All of these PIDLs are relative to the PIDL of the parent folder. +*/ + +static HRESULT DataObject_GetData_IDLIST(IDataObject *dataObject, UStringVector &names) +{ + names.Clear(); + NCOM::CStgMedium medium; + RINOK(DataObject_GetData_HGLOBAL(dataObject, (CLIPFORMAT) + RegisterClipboardFormat(MYWIN_CFSTR_SHELLIDLIST), medium)) + const size_t blockSize = GlobalSize(medium.hGlobal); + if (blockSize < sizeof(MYWIN_CIDA) || blockSize >= (UInt32)((UInt32)0 - 1)) + return E_INVALIDARG; + NMemory::CGlobalLock dropLock(medium.hGlobal); + const MYWIN_CIDA *cida = (const MYWIN_CIDA *)dropLock.GetPointer(); + if (!cida) + return E_INVALIDARG; + if (cida->cidl == 0) + { + // is it posssible to have no selected items? + // it's unexpected case. + return E_INVALIDARG; + } + if (cida->cidl >= (blockSize - (UInt32)sizeof(MYWIN_CIDA)) / sizeof(UINT)) + return E_INVALIDARG; + const UInt32 start = cida->cidl * (UInt32)sizeof(UINT) + (UInt32)sizeof(MYWIN_CIDA); + + STRRET strret; + CMyComPtr parentFolder; + { + const UINT offset = cida->aoffset[0]; + if (offset < start || offset >= blockSize + // || offset != start + ) + return E_INVALIDARG; + + CMyComPtr desktopFolder; + RINOK(::SHGetDesktopFolder(&desktopFolder)) + if (!desktopFolder) + return E_FAIL; + + LPCITEMIDLIST const lpcItem = (LPCITEMIDLIST)(const void *)((const Byte *)cida + offset); + + #ifdef SHOW_DEBUG_SHELL + { + const HRESULT res = desktopFolder->GetDisplayNameOf( + lpcItem, SHGDN_FORPARSING, &strret); + if (res == S_OK && strret.uType == STRRET_WSTR) + { + ODS_U(strret.pOleStr) + /* if lpcItem is empty, the path will be + "C:\Users\user_name\Desktop" + if lpcItem is "My Computer" folder, the path will be + "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}" */ + CoTaskMemFree(strret.pOleStr); + } + } + #endif + + RINOK(desktopFolder->BindToObject(lpcItem, + NULL, IID_IShellFolder, (void **)&parentFolder)) + if (!parentFolder) + return E_FAIL; + } + + names.ClearAndReserve(cida->cidl); + UString path; + + // for (int y = 0; y < 1; y++) // for debug + for (unsigned i = 1; i <= cida->cidl; i++) + { + const UINT offset = cida->aoffset[i]; + if (offset < start || offset >= blockSize) + return E_INVALIDARG; + const void *p = (const Byte *)(const void *)cida + offset; + /* ITEMIDLIST of file can contain more than one SHITEMID item. + In win10 only SHGDN_FORPARSING returns path that contains + all path parts related to parts of ITEMIDLIST. + So we can use only SHGDN_FORPARSING here. + Don't use (SHGDN_INFOLDER) + Don't use (SHGDN_INFOLDER | SHGDN_FORPARSING) + */ + RINOK(parentFolder->GetDisplayNameOf((LPCITEMIDLIST)p, SHGDN_FORPARSING, &strret)) + + /* + // MSVC6 and old SDK do not support StrRetToStrW(). + LPWSTR lpstr; + RINOK (StrRetToStrW(&strret, NULL, &lpstr)) + ODS_U(lpstr) + path = lpstr; + CoTaskMemFree(lpstr); + */ + if (strret.uType != STRRET_WSTR) + return E_INVALIDARG; + ODS_U(strret.pOleStr) + path = strret.pOleStr; + // the path could have super path prefix "\\\\?\\" + // we can remove super path prefix here, if we don't need that prefix + #ifdef Z7_LONG_PATH + // we remove super prefix, if we can work without that prefix + NFile::NName::If_IsSuperPath_RemoveSuperPrefix(path); + #endif + names.AddInReserved(path); + CoTaskMemFree(strret.pOleStr); + } + + ODS_(Print_Number(cida->cidl, "CFSTR_SHELLIDLIST END")) + return S_OK; +} + + +HRESULT DataObject_GetData_HDROP_or_IDLIST_Names(IDataObject *dataObject, UStringVector &paths) +{ + ODS("-- DataObject_GetData_HDROP_or_IDLIST_Names START") + HRESULT hres = NShell::DataObject_GetData_HDROP_Names(dataObject, paths); + // if (hres == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)) + if (hres != S_OK) + { + ODS("-- DataObject_GetData_IDLIST START") + // for (int y = 0; y < 10000; y++) // for debug + hres = NShell::DataObject_GetData_IDLIST(dataObject, paths); + } + ODS("-- DataObject_GetData_HDROP_or_IDLIST_Names END") + return hres; +} + + + +// #if (NTDDI_VERSION >= NTDDI_VISTA) +typedef struct +{ + UINT cItems; // number of items in rgdwFileAttributes array + DWORD dwSumFileAttributes; // all of the attributes ORed together + DWORD dwProductFileAttributes; // all of the attributes ANDed together + DWORD rgdwFileAttributes[1]; // array +} MYWIN_FILE_ATTRIBUTES_ARRAY; + +#define MYWIN_CFSTR_FILE_ATTRIBUTES_ARRAY TEXT("File Attributes Array") + +HRESULT DataObject_GetData_FILE_ATTRS(IDataObject *dataObject, CFileAttribs &attribs) +{ + attribs.Clear(); + NCOM::CStgMedium medium; + RINOK(DataObject_GetData_HGLOBAL(dataObject, (CLIPFORMAT) + RegisterClipboardFormat(MYWIN_CFSTR_FILE_ATTRIBUTES_ARRAY), medium)) + const size_t blockSize = GlobalSize(medium.hGlobal); + if (blockSize < sizeof(MYWIN_FILE_ATTRIBUTES_ARRAY)) + return E_INVALIDARG; + NMemory::CGlobalLock dropLock(medium.hGlobal); + const MYWIN_FILE_ATTRIBUTES_ARRAY *faa = (const MYWIN_FILE_ATTRIBUTES_ARRAY *)dropLock.GetPointer(); + if (!faa) + return E_INVALIDARG; + const unsigned numFiles = faa->cItems; + if (numFiles == 0) + { + // is it posssible to have empty array here? + return E_INVALIDARG; + } + if ((blockSize - (sizeof(MYWIN_FILE_ATTRIBUTES_ARRAY) - sizeof(DWORD))) + / sizeof(DWORD) != numFiles) + return E_INVALIDARG; + // attribs.Sum = faa->dwSumFileAttributes; + // attribs.Product = faa->dwProductFileAttributes; + // attribs.Vals.SetFromArray(faa->rgdwFileAttributes, numFiles); + // attribs.IsDirVector.ClearAndSetSize(numFiles); + + if ((faa->dwSumFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) + { + /* in win10: if selected items are volumes (c:\, d:\ ..) in My Compter, + all items have FILE_ATTRIBUTE_DIRECTORY attribute + ntfs volume also have FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM + udf volume: FILE_ATTRIBUTE_READONLY + dvd-rom device: (-1) : all bits are set + */ + const DWORD *attr = faa->rgdwFileAttributes; + // DWORD product = (UInt32)0 - 1, sum = 0; + for (unsigned i = 0; i < numFiles; i++) + { + if (attr[i] & FILE_ATTRIBUTE_DIRECTORY) + { + // attribs.ThereAreDirs = true; + attribs.FirstDirIndex = (int)i; + break; + } + // attribs.IsDirVector[i] = (attr[i] & FILE_ATTRIBUTE_DIRECTORY) != 0; + // product &= v; + // sum |= v; + } + // ODS_(Print_Number(product, "Product calc FILE_ATTRIBUTES_ARRAY ==== DataObject_GetData_HDROP_Names")) + // ODS_(Print_Number(sum, "Sum calc FILE_ATTRIBUTES_ARRAY ==== DataObject_GetData_HDROP_Names")) + } + // ODS_(Print_Number(attribs.Product, "Product FILE_ATTRIBUTES_ARRAY ==== DataObject_GetData_HDROP_Names")) + // ODS_(Print_Number(attribs.Sum, "Sum FILE_ATTRIBUTES_ARRAY ==== DataObject_GetData_HDROP_Names")) + ODS_(Print_Number(numFiles, "FILE_ATTRIBUTES_ARRAY ==== DataObject_GetData_HDROP_Names")) + return S_OK; +} + + ///////////////////////////// // CDrop +/* + win10: + DragQueryFile() implementation code is not effective because + there is no pointer inside DROP internal file list, so + DragQueryFile(fileIndex) runs all names in range [0, fileIndex]. + DragQueryFile(,, buf, bufSize) + if (buf == NULL) by spec + { + returns value is the required size + in characters, of the buffer, not including the terminating null character + tests show that if (bufSize == 0), then it also returns required size. + } + if (bufSize != NULL) + { + returns: the count of the characters copied, not including null character. + win10: null character is also copied at position buf[ret_count]; + } +*/ + +/* void CDrop::Attach(HDROP object) { Free(); @@ -87,43 +476,151 @@ UINT CDrop::QueryCountOfFiles() return QueryFile(0xFFFFFFFF, (LPTSTR)NULL, 0); } -UString CDrop::QueryFileName(UINT fileIndex) +void CDrop::QueryFileName(UINT fileIndex, UString &fileName) { - UString fileName; #ifndef _UNICODE if (!g_IsNT) { AString fileNameA; - UINT bufferSize = QueryFile(fileIndex, (LPTSTR)NULL, 0); - const unsigned len = bufferSize + 2; - QueryFile(fileIndex, fileNameA.GetBuf(len), bufferSize + 1); + const UINT len = QueryFile(fileIndex, (LPTSTR)NULL, 0); + const UINT numCopied = QueryFile(fileIndex, fileNameA.GetBuf(len + 2), len + 2); fileNameA.ReleaseBuf_CalcLen(len); + if (numCopied != len) + throw 20221223; fileName = GetUnicodeString(fileNameA); } else #endif { - UINT bufferSize = QueryFile(fileIndex, (LPWSTR)NULL, 0); - const unsigned len = bufferSize + 2; - QueryFile(fileIndex, fileName.GetBuf(len), bufferSize + 1); + // kReserve must be >= 3 for additional buffer size + // safety and for optimal performance + const unsigned kReserve = 3; + { + unsigned len = 0; + wchar_t *buf = fileName.GetBuf_GetMaxAvail(len); + if (len >= kReserve) + { + const UINT numCopied = QueryFile(fileIndex, buf, len); + if (numCopied < len - 1) + { + // (numCopied < len - 1) case means that it have copied full string. + fileName.ReleaseBuf_CalcLen(numCopied); + return; + } + } + } + const UINT len = QueryFile(fileIndex, (LPWSTR)NULL, 0); + const UINT numCopied = QueryFile(fileIndex, + fileName.GetBuf(len + kReserve), len + kReserve); fileName.ReleaseBuf_CalcLen(len); + if (numCopied != len) + throw 20221223; } - return fileName; } + void CDrop::QueryFileNames(UStringVector &fileNames) { UINT numFiles = QueryCountOfFiles(); + + Print_Number(numFiles, "\n====== CDrop::QueryFileNames START ===== \n"); + fileNames.ClearAndReserve(numFiles); + UString s; for (UINT i = 0; i < numFiles; i++) - fileNames.AddInReserved(QueryFileName(i)); + { + QueryFileName(i, s); + if (!s.IsEmpty()) + fileNames.AddInReserved(s); + } + Print_Number(numFiles, "\n====== CDrop::QueryFileNames END ===== \n"); } +*/ + + +// #if (NTDDI_VERSION >= NTDDI_VISTA) +// SHGetPathFromIDListEx returns a win32 file system path for the item in the name space. +typedef int Z7_WIN_GPFIDL_FLAGS; + +extern "C" { +#ifndef _UNICODE +typedef BOOL (WINAPI * Func_SHGetPathFromIDListW)(LPCITEMIDLIST pidl, LPWSTR pszPath); // nt4 +#endif + +#if !defined(Z7_WIN32_WINNT_MIN) || Z7_WIN32_WINNT_MIN < 0x0600 // Vista +#define Z7_USE_DYN_SHGetPathFromIDListEx +#endif +#ifdef Z7_USE_DYN_SHGetPathFromIDListEx +Z7_DIAGNOSTIC_IGNORE_CAST_FUNCTION +typedef BOOL (WINAPI * Func_SHGetPathFromIDListEx)(LPCITEMIDLIST pidl, PWSTR pszPath, DWORD cchPath, Z7_WIN_GPFIDL_FLAGS uOpts); // vista +#endif +} -bool GetPathFromIDList(LPCITEMIDLIST itemIDList, CSysString &path) +#ifndef _UNICODE + +bool GetPathFromIDList(LPCITEMIDLIST itemIDList, AString &path) +{ + path.Empty(); + const unsigned len = MAX_PATH + 16; + const bool result = BOOLToBool(::SHGetPathFromIDList(itemIDList, path.GetBuf(len))); + path.ReleaseBuf_CalcLen(len); + return result; +} + +#endif + +bool GetPathFromIDList(LPCITEMIDLIST itemIDList, UString &path) { - const unsigned len = MAX_PATH * 2; + path.Empty(); + unsigned len = MAX_PATH + 16; + +#ifdef _UNICODE bool result = BOOLToBool(::SHGetPathFromIDList(itemIDList, path.GetBuf(len))); +#else + const + Func_SHGetPathFromIDListW + shGetPathFromIDListW = Z7_GET_PROC_ADDRESS( + Func_SHGetPathFromIDListW, ::GetModuleHandleW(L"shell32.dll"), + "SHGetPathFromIDListW"); + if (!shGetPathFromIDListW) + return false; + bool result = BOOLToBool(shGetPathFromIDListW(itemIDList, path.GetBuf(len))); +#endif + + if (!result) + { + ODS("==== GetPathFromIDList() SHGetPathFromIDList() returned false") + /* for long path we need SHGetPathFromIDListEx(). + win10: SHGetPathFromIDListEx() for long path returns path with + with super path prefix "\\\\?\\". */ +#ifdef Z7_USE_DYN_SHGetPathFromIDListEx + const + Func_SHGetPathFromIDListEx + func_SHGetPathFromIDListEx = Z7_GET_PROC_ADDRESS( + Func_SHGetPathFromIDListEx, ::GetModuleHandleW(L"shell32.dll"), + "SHGetPathFromIDListEx"); + if (func_SHGetPathFromIDListEx) +#endif + { + ODS("==== GetPathFromIDList() (SHGetPathFromIDListEx)") + do + { + len *= 4; + result = BOOLToBool( +#ifdef Z7_USE_DYN_SHGetPathFromIDListEx + func_SHGetPathFromIDListEx +#else + SHGetPathFromIDListEx +#endif + (itemIDList, path.GetBuf(len), len, 0)); + if (result) + break; + } + while (len <= (1 << 16)); + } + } + path.ReleaseBuf_CalcLen(len); return result; } @@ -153,8 +650,8 @@ bool BrowseForFolder(HWND /* owner */, LPCTSTR /* title */, MessageBoxW(0, L"yes", L"", 0); */ /* - UString s = L"all files"; - s += L" (*.*)"; + UString s = "all files"; + s += " (*.*)"; return MyGetOpenFileName(owner, title, initialFolder, s, resultPath, true); */ return false; @@ -162,11 +659,16 @@ bool BrowseForFolder(HWND /* owner */, LPCTSTR /* title */, #else +/* win10: SHBrowseForFolder() doesn't support long paths, + even if long path suppport is enabled in registry and in manifest. + and SHBrowseForFolder() doesn't support super path prefix "\\\\?\\". */ + bool BrowseForFolder(LPBROWSEINFO browseInfo, CSysString &resultPath) { + resultPath.Empty(); NWindows::NCOM::CComInitializer comInitializer; LPITEMIDLIST itemIDList = ::SHBrowseForFolder(browseInfo); - if (itemIDList == NULL) + if (!itemIDList) return false; CItemIDList itemIDListHolder; itemIDListHolder.Attach(itemIDList); @@ -174,7 +676,7 @@ bool BrowseForFolder(LPBROWSEINFO browseInfo, CSysString &resultPath) } -int CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM /* lp */, LPARAM data) +static int CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM /* lp */, LPARAM data) { #ifndef UNDER_CE switch (uMsg) @@ -203,7 +705,7 @@ int CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM /* lp */, LPARAM da } -bool BrowseForFolder(HWND owner, LPCTSTR title, UINT ulFlags, +static bool BrowseForFolder(HWND owner, LPCTSTR title, UINT ulFlags, LPCTSTR initialFolder, CSysString &resultPath) { CSysString displayName; @@ -222,11 +724,18 @@ bool BrowseForFolder(HWND owner, LPCTSTR title, UINT ulFlags, browseInfo.lpszTitle = title; // #endif browseInfo.ulFlags = ulFlags; - browseInfo.lpfn = (initialFolder != NULL) ? BrowseCallbackProc : NULL; + browseInfo.lpfn = initialFolder ? BrowseCallbackProc : NULL; browseInfo.lParam = (LPARAM)initialFolder; return BrowseForFolder(&browseInfo, resultPath); } +#ifdef Z7_OLD_WIN_SDK +// ShlObj.h: +#ifndef BIF_NEWDIALOGSTYLE +#define BIF_NEWDIALOGSTYLE 0x0040 +#endif +#endif + bool BrowseForFolder(HWND owner, LPCTSTR title, LPCTSTR initialFolder, CSysString &resultPath) { @@ -240,39 +749,29 @@ bool BrowseForFolder(HWND owner, LPCTSTR title, #ifndef _UNICODE -typedef BOOL (WINAPI * SHGetPathFromIDListWP)(LPCITEMIDLIST pidl, LPWSTR pszPath); - -bool GetPathFromIDList(LPCITEMIDLIST itemIDList, UString &path) -{ - path.Empty(); - SHGetPathFromIDListWP shGetPathFromIDListW = (SHGetPathFromIDListWP) - ::GetProcAddress(::GetModuleHandleW(L"shell32.dll"), "SHGetPathFromIDListW"); - if (shGetPathFromIDListW == 0) - return false; - const unsigned len = MAX_PATH * 2; - bool result = BOOLToBool(shGetPathFromIDListW(itemIDList, path.GetBuf(len))); - path.ReleaseBuf_CalcLen(len); - return result; +extern "C" { +typedef LPITEMIDLIST (WINAPI * Func_SHBrowseForFolderW)(LPBROWSEINFOW lpbi); } -typedef LPITEMIDLIST (WINAPI * SHBrowseForFolderWP)(LPBROWSEINFOW lpbi); - -bool BrowseForFolder(LPBROWSEINFOW browseInfo, UString &resultPath) +static bool BrowseForFolder(LPBROWSEINFOW browseInfo, UString &resultPath) { NWindows::NCOM::CComInitializer comInitializer; - SHBrowseForFolderWP shBrowseForFolderW = (SHBrowseForFolderWP) - ::GetProcAddress(::GetModuleHandleW(L"shell32.dll"), "SHBrowseForFolderW"); - if (shBrowseForFolderW == 0) + const + Func_SHBrowseForFolderW + f_SHBrowseForFolderW = Z7_GET_PROC_ADDRESS( + Func_SHBrowseForFolderW, ::GetModuleHandleW(L"shell32.dll"), + "SHBrowseForFolderW"); + if (!f_SHBrowseForFolderW) return false; - LPITEMIDLIST itemIDList = shBrowseForFolderW(browseInfo); - if (itemIDList == NULL) + LPITEMIDLIST itemIDList = f_SHBrowseForFolderW(browseInfo); + if (!itemIDList) return false; CItemIDList itemIDListHolder; itemIDListHolder.Attach(itemIDList); return GetPathFromIDList(itemIDList, resultPath); } - +static int CALLBACK BrowseCallbackProc2(HWND hwnd, UINT uMsg, LPARAM /* lp */, LPARAM data) { switch (uMsg) @@ -311,7 +810,7 @@ static bool BrowseForFolder(HWND owner, LPCWSTR title, UINT ulFlags, browseInfo.pszDisplayName = displayName.GetBuf(MAX_PATH); browseInfo.lpszTitle = title; browseInfo.ulFlags = ulFlags; - browseInfo.lpfn = (initialFolder != NULL) ? BrowseCallbackProc2 : NULL; + browseInfo.lpfn = initialFolder ? BrowseCallbackProc2 : NULL; browseInfo.lParam = (LPARAM)initialFolder; return BrowseForFolder(&browseInfo, resultPath); } diff --git a/src/plugins/7zip/7za/cpp/Windows/Shell.h b/src/plugins/7zip/7za/cpp/Windows/Shell.h index 4bff18cf..163c7d9a 100644 --- a/src/plugins/7zip/7za/cpp/Windows/Shell.h +++ b/src/plugins/7zip/7za/cpp/Windows/Shell.h @@ -1,17 +1,21 @@ // Windows/Shell.h -#ifndef __WINDOWS_SHELL_H -#define __WINDOWS_SHELL_H +#ifndef ZIP7_WINDOWS_SHELL_H +#define ZIP7_WINDOWS_SHELL_H -#include +#include "../Common/MyWindows.h" +#if defined(__MINGW32__) || defined(__MINGW64__) #include +#else +#include +#endif #include "../Common/MyString.h" -#include "Defs.h" +#include "WinDefs.h" -namespace NWindows{ -namespace NShell{ +namespace NWindows { +namespace NShell { ///////////////////////// // CItemIDList @@ -20,6 +24,7 @@ namespace NShell{ class CItemIDList { LPITEMIDLIST m_Object; + Z7_CLASS_NO_COPY(CItemIDList) public: CItemIDList(): m_Object(NULL) {} // CItemIDList(LPCITEMIDLIST itemIDList); @@ -49,6 +54,7 @@ class CItemIDList ///////////////////////////// // CDrop +/* class CDrop { HDROP m_Object; @@ -63,22 +69,51 @@ class CDrop operator HDROP() { return m_Object;} bool QueryPoint(LPPOINT point) { return BOOLToBool(::DragQueryPoint(m_Object, point)); } - void Finish() { ::DragFinish(m_Object); } - UINT QueryFile(UINT fileIndex, LPTSTR fileName, UINT fileNameSize) - { return ::DragQueryFile(m_Object, fileIndex, fileName, fileNameSize); } + void Finish() + { + ::DragFinish(m_Object); + } + UINT QueryFile(UINT fileIndex, LPTSTR fileName, UINT bufSize) + { return ::DragQueryFile(m_Object, fileIndex, fileName, bufSize); } #ifndef _UNICODE - UINT QueryFile(UINT fileIndex, LPWSTR fileName, UINT fileNameSize) - { return ::DragQueryFileW(m_Object, fileIndex, fileName, fileNameSize); } + UINT QueryFile(UINT fileIndex, LPWSTR fileName, UINT bufSize) + { return ::DragQueryFileW(m_Object, fileIndex, fileName, bufSize); } #endif UINT QueryCountOfFiles(); - UString QueryFileName(UINT fileIndex); + void QueryFileName(UINT fileIndex, UString &fileName); void QueryFileNames(UStringVector &fileNames); }; - +*/ #endif -///////////////////////////// -// Functions +struct CFileAttribs +{ + int FirstDirIndex; + // DWORD Sum; + // DWORD Product; + // CRecordVector Vals; + // CRecordVector IsDirVector; + + CFileAttribs() + { + Clear(); + } + + void Clear() + { + FirstDirIndex = -1; + // Sum = 0; + // Product = 0; + // IsDirVector.Clear(); + } +}; + + +/* read pathnames from HDROP or SHELLIDLIST. + The parser can return E_INVALIDARG, if there is some unexpected data in dataObject */ +HRESULT DataObject_GetData_HDROP_or_IDLIST_Names(IDataObject *dataObject, UStringVector &names); + +HRESULT DataObject_GetData_FILE_ATTRS(IDataObject *dataObject, CFileAttribs &attribs); bool GetPathFromIDList(LPCITEMIDLIST itemIDList, CSysString &path); bool BrowseForFolder(LPBROWSEINFO lpbi, CSysString &resultPath); diff --git a/src/plugins/7zip/7za/cpp/Windows/StdAfx.h b/src/plugins/7zip/7za/cpp/Windows/StdAfx.h index 1766dfa8..4441a5f0 100644 --- a/src/plugins/7zip/7za/cpp/Windows/StdAfx.h +++ b/src/plugins/7zip/7za/cpp/Windows/StdAfx.h @@ -1,8 +1,11 @@ // StdAfx.h -#ifndef __STDAFX_H -#define __STDAFX_H +#ifndef ZIP7_INC_STDAFX_H +#define ZIP7_INC_STDAFX_H +#if defined(_MSC_VER) && _MSC_VER >= 1800 +#pragma warning(disable : 4464) // relative include path contains '..' +#endif #include "../Common/Common.h" #endif diff --git a/src/plugins/7zip/7za/cpp/Windows/Synchronization.cpp b/src/plugins/7zip/7za/cpp/Windows/Synchronization.cpp index 5f86d1eb..d5542af4 100644 --- a/src/plugins/7zip/7za/cpp/Windows/Synchronization.cpp +++ b/src/plugins/7zip/7za/cpp/Windows/Synchronization.cpp @@ -2,9 +2,86 @@ #include "StdAfx.h" +#ifndef _WIN32 + #include "Synchronization.h" namespace NWindows { namespace NSynchronization { +/* +#define INFINITE 0xFFFFFFFF +#define MAXIMUM_WAIT_OBJECTS 64 +#define STATUS_ABANDONED_WAIT_0 ((NTSTATUS)0x00000080L) +#define WAIT_ABANDONED ((STATUS_ABANDONED_WAIT_0 ) + 0 ) +#define WAIT_ABANDONED_0 ((STATUS_ABANDONED_WAIT_0 ) + 0 ) +// WINAPI +DWORD WaitForMultipleObjects(DWORD count, const HANDLE *handles, BOOL wait_all, DWORD timeout); +*/ + +/* clang: we need to place some virtual functions in cpp file to rid off the warning: + 'CBaseHandle_WFMO' has no out-of-line virtual method definitions; + its vtable will be emitted in every translation unit */ +CBaseHandle_WFMO::~CBaseHandle_WFMO() +{ +} + +bool CBaseEvent_WFMO::IsSignaledAndUpdate() +{ + if (this->_state == false) + return false; + if (this->_manual_reset == false) + this->_state = false; + return true; +} + +bool CSemaphore_WFMO::IsSignaledAndUpdate() +{ + if (this->_count == 0) + return false; + this->_count--; + return true; +} + +DWORD WINAPI WaitForMultiObj_Any_Infinite(DWORD count, const CHandle_WFMO *handles) +{ + if (count < 1) + { + // abort(); + SetLastError(EINVAL); + return WAIT_FAILED; + } + + CSynchro *synchro = handles[0]->_sync; + synchro->Enter(); + + // #ifdef DEBUG_SYNCHRO + for (DWORD i = 1; i < count; i++) + { + if (synchro != handles[i]->_sync) + { + // abort(); + synchro->Leave(); + SetLastError(EINVAL); + return WAIT_FAILED; + } + } + // #endif + + for (;;) + { + for (DWORD i = 0; i < count; i++) + { + if (handles[i]->IsSignaledAndUpdate()) + { + synchro->Leave(); + return WAIT_OBJECT_0 + i; + } + } + synchro->WaitCond(); + } +} + }} + +#endif diff --git a/src/plugins/7zip/7za/cpp/Windows/Synchronization.h b/src/plugins/7zip/7za/cpp/Windows/Synchronization.h index dc695f6f..25764b8e 100644 --- a/src/plugins/7zip/7za/cpp/Windows/Synchronization.h +++ b/src/plugins/7zip/7za/cpp/Windows/Synchronization.h @@ -1,11 +1,13 @@ // Windows/Synchronization.h -#ifndef __WINDOWS_SYNCHRONIZATION_H -#define __WINDOWS_SYNCHRONIZATION_H +#ifndef ZIP7_INC_WINDOWS_SYNCHRONIZATION_H +#define ZIP7_INC_WINDOWS_SYNCHRONIZATION_H #include "../../C/Threads.h" -#include "Defs.h" +#include "../Common/MyTypes.h" + +#include "WinDefs.h" #ifdef _WIN32 #include "Handle.h" @@ -14,28 +16,30 @@ namespace NWindows { namespace NSynchronization { -class CBaseEvent +class CBaseEvent MY_UNCOPYABLE { protected: ::CEvent _object; public: bool IsCreated() { return Event_IsCreated(&_object) != 0; } - operator HANDLE() { return _object; } + CBaseEvent() { Event_Construct(&_object); } ~CBaseEvent() { Close(); } WRes Close() { return Event_Close(&_object); } + #ifdef _WIN32 + operator HANDLE() { return _object; } WRes Create(bool manualReset, bool initiallyOwn, LPCTSTR name = NULL, LPSECURITY_ATTRIBUTES sa = NULL) { _object = ::CreateEvent(sa, BoolToBOOL(manualReset), BoolToBOOL(initiallyOwn), name); - if (name == NULL && _object != 0) + if (name == NULL && _object != NULL) return 0; return ::GetLastError(); } WRes Open(DWORD desiredAccess, bool inheritHandle, LPCTSTR name) { _object = ::OpenEvent(desiredAccess, BoolToBOOL(inheritHandle), name); - if (_object != 0) + if (_object != NULL) return 0; return ::GetLastError(); } @@ -54,10 +58,10 @@ class CManualResetEvent: public CBaseEvent { return ManualResetEvent_Create(&_object, initiallyOwn ? 1: 0); } - WRes CreateIfNotCreated() + WRes CreateIfNotCreated_Reset() { if (IsCreated()) - return 0; + return Reset(); return ManualResetEvent_CreateNotSignaled(&_object); } #ifdef _WIN32 @@ -75,21 +79,25 @@ class CAutoResetEvent: public CBaseEvent { return AutoResetEvent_CreateNotSignaled(&_object); } - WRes CreateIfNotCreated() + WRes CreateIfNotCreated_Reset() { if (IsCreated()) - return 0; + return Reset(); return AutoResetEvent_CreateNotSignaled(&_object); } }; + +/* #ifdef _WIN32 + class CObject: public CHandle { public: WRes Lock(DWORD timeoutInterval = INFINITE) { return (::WaitForSingleObject(_handle, timeoutInterval) == WAIT_OBJECT_0 ? 0 : ::GetLastError()); } }; + class CMutex: public CObject { public: @@ -114,33 +122,47 @@ class CMutex: public CObject return ::ReleaseMutex(_handle) ? 0 : ::GetLastError(); } }; -class CMutexLock + +class CMutexLock MY_UNCOPYABLE { CMutex *_object; public: CMutexLock(CMutex &object): _object(&object) { _object->Lock(); } ~CMutexLock() { _object->Release(); } }; -#endif -class CSemaphore +#endif // _WIN32 +*/ + + +class CSemaphore MY_UNCOPYABLE { ::CSemaphore _object; public: CSemaphore() { Semaphore_Construct(&_object); } ~CSemaphore() { Close(); } - WRes Close() { return Semaphore_Close(&_object); } + WRes Close() { return Semaphore_Close(&_object); } + + #ifdef _WIN32 operator HANDLE() { return _object; } - WRes Create(UInt32 initiallyCount, UInt32 maxCount) + #endif + + // bool IsCreated() const { return Semaphore_IsCreated(&_object) != 0; } + + WRes Create(UInt32 initCount, UInt32 maxCount) { - return Semaphore_Create(&_object, initiallyCount, maxCount); + return Semaphore_Create(&_object, initCount, maxCount); + } + WRes OptCreateInit(UInt32 initCount, UInt32 maxCount) + { + return Semaphore_OptCreateInit(&_object, initCount, maxCount); } WRes Release() { return Semaphore_Release1(&_object); } WRes Release(UInt32 releaseCount) { return Semaphore_ReleaseN(&_object, releaseCount); } WRes Lock() { return Semaphore_Wait(&_object); } }; -class CCriticalSection +class CCriticalSection MY_UNCOPYABLE { ::CCriticalSection _object; public: @@ -150,7 +172,7 @@ class CCriticalSection void Leave() { CriticalSection_Leave(&_object); } }; -class CCriticalSectionLock +class CCriticalSectionLock MY_UNCOPYABLE { CCriticalSection *_object; void Unlock() { _object->Leave(); } @@ -159,6 +181,206 @@ class CCriticalSectionLock ~CCriticalSectionLock() { Unlock(); } }; + +#ifdef _WIN32 + +typedef HANDLE CHandle_WFMO; +typedef CSemaphore CSemaphore_WFMO; +typedef CAutoResetEvent CAutoResetEvent_WFMO; +typedef CManualResetEvent CManualResetEvent_WFMO; + +inline DWORD WINAPI WaitForMultiObj_Any_Infinite(DWORD count, const CHandle_WFMO *handles) +{ + return ::WaitForMultipleObjects(count, handles, FALSE, INFINITE); +} + +#define SYNC_OBJ_DECL(obj) +#define SYNC_WFMO(x) +#define SYNC_PARAM(x) +#define SYNC_PARAM_DECL(x) + +#else // _WIN32 + +// POSIX sync objects for WaitForMultipleObjects + +#define SYNC_WFMO(x) x +#define SYNC_PARAM(x) x, +#define SYNC_PARAM_DECL(x) NWindows::NSynchronization::CSynchro *x +#define SYNC_OBJ_DECL(x) NWindows::NSynchronization::CSynchro x; + +class CSynchro MY_UNCOPYABLE +{ + pthread_mutex_t _mutex; + pthread_cond_t _cond; + bool _isValid; + +public: + CSynchro() { _isValid = false; } + ~CSynchro() + { + if (_isValid) + { + ::pthread_mutex_destroy(&_mutex); + ::pthread_cond_destroy(&_cond); + } + _isValid = false; + } + WRes Create() + { + RINOK(::pthread_mutex_init(&_mutex, NULL)) + const WRes ret = ::pthread_cond_init(&_cond, NULL); + _isValid = 1; + return ret; + } + WRes Enter() + { +#if defined(Z7_LLVM_CLANG_VERSION) && (__clang_major__ == 13) \ + && defined(__FreeBSD__) + #pragma GCC diagnostic ignored "-Wthread-safety-negative" + #pragma GCC diagnostic ignored "-Wthread-safety-analysis" +#endif + return ::pthread_mutex_lock(&_mutex); + } + WRes Leave() + { + return ::pthread_mutex_unlock(&_mutex); + } + WRes WaitCond() + { + return ::pthread_cond_wait(&_cond, &_mutex); + } + WRes LeaveAndSignal() + { + const WRes res1 = ::pthread_cond_broadcast(&_cond); + const WRes res2 = ::pthread_mutex_unlock(&_mutex); + return (res2 ? res2 : res1); + } +}; + + +struct CBaseHandle_WFMO; +typedef NWindows::NSynchronization::CBaseHandle_WFMO *CHandle_WFMO; + +// these constants are from Windows +#define WAIT_OBJECT_0 0 +#define WAIT_FAILED ((DWORD)0xFFFFFFFF) + +DWORD WINAPI WaitForMultiObj_Any_Infinite(DWORD count, const CHandle_WFMO *handles); + + +struct CBaseHandle_WFMO MY_UNCOPYABLE +{ + CSynchro *_sync; + + CBaseHandle_WFMO(): _sync(NULL) {} + virtual ~CBaseHandle_WFMO(); + + operator CHandle_WFMO() { return this; } + virtual bool IsSignaledAndUpdate() = 0; +}; + + +class CBaseEvent_WFMO : public CBaseHandle_WFMO +{ + bool _manual_reset; + bool _state; + +public: + + // bool IsCreated() { return (this->_sync != NULL); } + // CBaseEvent_WFMO() { ; } + // ~CBaseEvent_WFMO() Z7_override { Close(); } + + WRes Close() { this->_sync = NULL; return 0; } + + WRes Create( + CSynchro *sync, + bool manualReset, bool initiallyOwn) + { + this->_sync = sync; + this->_manual_reset = manualReset; + this->_state = initiallyOwn; + return 0; + } + + WRes Set() + { + RINOK(this->_sync->Enter()) + this->_state = true; + return this->_sync->LeaveAndSignal(); + } + + WRes Reset() + { + RINOK(this->_sync->Enter()) + this->_state = false; + return this->_sync->Leave(); + } + + virtual bool IsSignaledAndUpdate() Z7_override; +}; + + +class CManualResetEvent_WFMO Z7_final: public CBaseEvent_WFMO +{ +public: + WRes Create(CSynchro *sync, bool initiallyOwn = false) { return CBaseEvent_WFMO::Create(sync, true, initiallyOwn); } +}; + + +class CAutoResetEvent_WFMO Z7_final: public CBaseEvent_WFMO +{ +public: + WRes Create(CSynchro *sync) { return CBaseEvent_WFMO::Create(sync, false, false); } + WRes CreateIfNotCreated_Reset(CSynchro *sync) + { + return Create(sync); + } +}; + + +class CSemaphore_WFMO Z7_final: public CBaseHandle_WFMO +{ + UInt32 _count; + UInt32 _maxCount; + +public: + CSemaphore_WFMO() : _count(0), _maxCount(0) {} + + WRes Close() { this->_sync = NULL; return 0; } + + WRes Create(CSynchro *sync, UInt32 initCount, UInt32 maxCount) + { + if (initCount > maxCount || maxCount < 1) + return EINVAL; + this->_sync = sync; + this->_count = initCount; + this->_maxCount = maxCount; + return 0; + } + + WRes Release(UInt32 releaseCount = 1) + { + if (releaseCount < 1) + return EINVAL; + + RINOK(this->_sync->Enter()) + UInt32 newCount = this->_count + releaseCount; + if (newCount > this->_maxCount) + { + RINOK(this->_sync->Leave()) + return ERROR_TOO_MANY_POSTS; // EINVAL + } + this->_count = newCount; + + return this->_sync->LeaveAndSignal(); + } + + virtual bool IsSignaledAndUpdate() Z7_override; +}; + +#endif // _WIN32 + }} #endif diff --git a/src/plugins/7zip/7za/cpp/Windows/System.cpp b/src/plugins/7zip/7za/cpp/Windows/System.cpp index 59cb7c0b..c6e065a5 100644 --- a/src/plugins/7zip/7za/cpp/Windows/System.cpp +++ b/src/plugins/7zip/7za/cpp/Windows/System.cpp @@ -2,9 +2,22 @@ #include "StdAfx.h" -#include "../Common/MyWindows.h" +#ifndef _WIN32 +#include +#include +#if defined(__APPLE__) || defined(__DragonFly__) \ + || defined(BSD) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) \ + || defined(__QNXNTO__) +#include +#else +#include +#endif +#endif #include "../Common/Defs.h" +// #include "../Common/MyWindows.h" + +// #include "../../C/CpuArch.h" #include "System.h" @@ -13,18 +26,181 @@ namespace NSystem { #ifdef _WIN32 -UInt32 GetNumberOfProcessors() +/* +note: returned value in 32-bit version can be limited by value 32. + while 64-bit version returns full value. +GetMaximumProcessorCount(groupNumber) can return higher value than +GetActiveProcessorCount(groupNumber) in some cases, because CPUs can be added. +*/ +// typedef DWORD (WINAPI *Func_GetMaximumProcessorCount)(WORD GroupNumber); +typedef DWORD (WINAPI *Func_GetActiveProcessorCount)(WORD GroupNumber); +typedef WORD (WINAPI *Func_GetActiveProcessorGroupCount)(VOID); +/* +#if 0 && defined(ALL_PROCESSOR_GROUPS) +#define MY_ALL_PROCESSOR_GROUPS ALL_PROCESSOR_GROUPS +#else +#define MY_ALL_PROCESSOR_GROUPS 0xffff +#endif +*/ + +Z7_DIAGNOSTIC_IGNORE_CAST_FUNCTION + +bool CCpuGroups::Load() +{ + NumThreadsTotal = 0; + GroupSizes.Clear(); + const HMODULE hmodule = ::GetModuleHandleA("kernel32.dll"); + // Is_Win11_Groups = GetProcAddress(hmodule, "SetThreadSelectedCpuSetMasks") != NULL; + const + Func_GetActiveProcessorGroupCount + fn_GetActiveProcessorGroupCount = Z7_GET_PROC_ADDRESS( + Func_GetActiveProcessorGroupCount, hmodule, + "GetActiveProcessorGroupCount"); + const + Func_GetActiveProcessorCount + fn_GetActiveProcessorCount = Z7_GET_PROC_ADDRESS( + Func_GetActiveProcessorCount, hmodule, + "GetActiveProcessorCount"); + if (!fn_GetActiveProcessorGroupCount || + !fn_GetActiveProcessorCount) + return false; + + const unsigned numGroups = fn_GetActiveProcessorGroupCount(); + if (numGroups == 0) + return false; + UInt32 sum = 0; + for (unsigned i = 0; i < numGroups; i++) + { + const UInt32 num = fn_GetActiveProcessorCount((WORD)i); + /* + if (num == 0) + { + // it means error + // but is it possible that some group is empty by some reason? + // GroupSizes.Clear(); + // return false; + } + */ + sum += num; + GroupSizes.Add(num); + } + NumThreadsTotal = sum; + // NumThreadsTotal = fn_GetActiveProcessorCount(MY_ALL_PROCESSOR_GROUPS); + return true; +} + +UInt32 CountAffinity(DWORD_PTR mask) { + UInt32 num = 0; + for (unsigned i = 0; i < sizeof(mask) * 8; i++) + { + num += (UInt32)(mask & 1); + mask >>= 1; + } + return num; +} + +BOOL CProcessAffinity::Get() +{ + IsGroupMode = false; + Groups.Load(); + // SetThreadAffinityMask(GetCurrentThread(), 1); + // SetProcessAffinityMask(GetCurrentProcess(), 1); + BOOL res = GetProcessAffinityMask(GetCurrentProcess(), + &processAffinityMask, &systemAffinityMask); + /* DOCs: On a system with more than 64 processors, if the threads + of the calling process are in a single processor group, the + function sets the variables pointed to by lpProcessAffinityMask + and lpSystemAffinityMask to the process affinity mask and the + processor mask of active logical processors for that group. + If the calling process contains threads in multiple groups, + the function returns zero for both affinity masks + + note: tested in Win10: GetProcessAffinityMask() doesn't return 0 + in (processAffinityMask) and (systemAffinityMask) masks. + We need to test it in Win11: how to get mask==0 from GetProcessAffinityMask()? + */ + if (!res) + { + processAffinityMask = 0; + systemAffinityMask = 0; + } + if (Groups.GroupSizes.Size() > 1 && Groups.NumThreadsTotal) + if (// !res || + processAffinityMask == 0 || // to support case described in DOCs and for (!res) case + processAffinityMask == systemAffinityMask) // for default nonchanged affinity + { + // we set IsGroupMode only if processAffinity is default (not changed). + res = TRUE; + IsGroupMode = true; + } + return res; +} + + +UInt32 CProcessAffinity::Load_and_GetNumberOfThreads() +{ + if (Get()) + { + const UInt32 numProcessors = GetNumProcessThreads(); + if (numProcessors) + return numProcessors; + } SYSTEM_INFO systemInfo; GetSystemInfo(&systemInfo); - return (UInt32)systemInfo.dwNumberOfProcessors; + // the number of logical processors in the current group + return systemInfo.dwNumberOfProcessors; +} + +UInt32 GetNumberOfProcessors() +{ + // We need to know how many threads we can use. + // By default the process is assigned to one group. + CProcessAffinity pa; + return pa.Load_and_GetNumberOfThreads(); } #else + +BOOL CProcessAffinity::Get() +{ + numSysThreads = GetNumberOfProcessors(); + + /* + numSysThreads = 8; + for (unsigned i = 0; i < numSysThreads; i++) + CpuSet_Set(&cpu_set, i); + return TRUE; + */ + + #ifdef Z7_AFFINITY_SUPPORTED + + // numSysThreads = sysconf(_SC_NPROCESSORS_ONLN); // The number of processors currently online + if (sched_getaffinity(0, sizeof(cpu_set), &cpu_set) != 0) + return FALSE; + return TRUE; + + #else + + // cpu_set = ((CCpuSet)1 << (numSysThreads)) - 1; + return TRUE; + // errno = ENOSYS; + // return FALSE; + + #endif +} + UInt32 GetNumberOfProcessors() { + #ifndef Z7_ST + long n = sysconf(_SC_NPROCESSORS_CONF); // The number of processors configured + if (n < 1) + n = 1; + return (UInt32)n; + #else return 1; + #endif } #endif @@ -34,9 +210,10 @@ UInt32 GetNumberOfProcessors() #ifndef UNDER_CE -#if !defined(_WIN64) && defined(__GNUC__) +#if !defined(_WIN64) && \ + (defined(__MINGW32_VERSION) || defined(Z7_OLD_WIN_SDK)) -typedef struct _MY_MEMORYSTATUSEX { +typedef struct { DWORD dwLength; DWORD dwMemoryLoad; DWORDLONG ullTotalPhys; @@ -55,19 +232,15 @@ typedef struct _MY_MEMORYSTATUSEX { #endif -typedef BOOL (WINAPI *GlobalMemoryStatusExP)(MY_LPMEMORYSTATUSEX lpBuffer); - -#endif - -#endif +typedef BOOL (WINAPI *Func_GlobalMemoryStatusEx)(MY_LPMEMORYSTATUSEX lpBuffer); +#endif // !UNDER_CE -bool GetRamSize(UInt64 &size) + +bool GetRamSize(size_t &size) { - size = (UInt64)(sizeof(size_t)) << 29; + size = (size_t)sizeof(size_t) << 29; - #ifdef _WIN32 - #ifndef UNDER_CE MY_MEMORYSTATUSEX stat; stat.dwLength = sizeof(stat); @@ -83,15 +256,29 @@ bool GetRamSize(UInt64 &size) #else #ifndef UNDER_CE - GlobalMemoryStatusExP globalMemoryStatusEx = (GlobalMemoryStatusExP) - ::GetProcAddress(::GetModuleHandle(TEXT("kernel32.dll")), "GlobalMemoryStatusEx"); - if (globalMemoryStatusEx && globalMemoryStatusEx(&stat)) + const + Func_GlobalMemoryStatusEx fn = Z7_GET_PROC_ADDRESS( + Func_GlobalMemoryStatusEx, ::GetModuleHandleA("kernel32.dll"), + "GlobalMemoryStatusEx"); + if (fn && fn(&stat)) { - size = MyMin(stat.ullTotalVirtual, stat.ullTotalPhys); + // (MY_MEMORYSTATUSEX::ullTotalVirtual) < 4 GiB in 32-bit mode + size_t size2 = (size_t)0 - 1; + if (size2 > stat.ullTotalPhys) + size2 = (size_t)stat.ullTotalPhys; + if (size2 > stat.ullTotalVirtual) + size2 = (size_t)stat.ullTotalVirtual; + size = size2; return true; } #endif + // On computers with more than 4 GB of memory: + // new docs : GlobalMemoryStatus can report (-1) value to indicate an overflow. + // some old docs : GlobalMemoryStatus can report (modulo 4 GiB) value. + // (for example, if 5 GB total memory, it could report 1 GB). + // We don't want to get (modulo 4 GiB) value. + // So we use GlobalMemoryStatusEx() instead. { MEMORYSTATUS stat2; stat2.dwLength = sizeof(stat2); @@ -99,14 +286,138 @@ bool GetRamSize(UInt64 &size) size = MyMin(stat2.dwTotalVirtual, stat2.dwTotalPhys); return true; } - #endif +} + +#else + +// POSIX +// #include + +bool GetRamSize(size_t &size) +{ + UInt64 size64; + size = (size_t)sizeof(size_t) << 29; + size64 = size; + +#if defined(__APPLE__) || defined(__DragonFly__) \ + || defined(BSD) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) \ + || defined(__QNXNTO__) + + uint64_t val = 0; + int mib[2]; + mib[0] = CTL_HW; + #ifdef HW_MEMSIZE + mib[1] = HW_MEMSIZE; + // printf("\n sysctl HW_MEMSIZE"); + #elif defined(HW_PHYSMEM64) + mib[1] = HW_PHYSMEM64; + // printf("\n sysctl HW_PHYSMEM64"); + #else + mib[1] = HW_PHYSMEM; + // printf("\n sysctl HW_PHYSMEM"); + #endif + + size_t size_sys = sizeof(val); + int res = sysctl(mib, 2, &val, &size_sys, NULL, 0); + // printf("\n sysctl res=%d val=%llx size_sys = %d, %d\n", res, (long long int)val, (int)size_sys, errno); + // we use strict check (size_sys == sizeof(val)) for returned value + // because big-endian encoding is possible: + if (res == 0 && size_sys == sizeof(val) && val) + size64 = val; + else + { + uint32_t val32 = 0; + size_sys = sizeof(val32); + res = sysctl(mib, 2, &val32, &size_sys, NULL, 0); + // printf("\n sysctl res=%d val=%llx size_sys = %d, %d\n", res, (long long int)val32, (int)size_sys, errno); + if (res == 0 && size_sys == sizeof(val32) && val32) + size64 = val32; + } + + #elif defined(_AIX) + #if defined(_SC_AIX_REALMEM) // AIX + size64 = (UInt64)sysconf(_SC_AIX_REALMEM) * 1024; + #endif + #elif 0 || defined(__sun) + #if defined(_SC_PHYS_PAGES) && defined(_SC_PAGESIZE) + // FreeBSD, Linux, OpenBSD, and Solaris. + { + const long phys_pages = sysconf(_SC_PHYS_PAGES); + const long page_size = sysconf(_SC_PAGESIZE); + // #pragma message("GetRamSize : sysconf(_SC_PHYS_PAGES) * sysconf(_SC_PAGESIZE)") + // printf("\n_SC_PHYS_PAGES (hex) = %lx", (unsigned long)phys_pages); + // printf("\n_SC_PAGESIZE = %lu\n", (unsigned long)page_size); + if (phys_pages != -1 && page_size != -1) + size64 = (UInt64)(Int64)phys_pages * (UInt64)(Int64)page_size; + } + #endif + #elif defined(__gnu_hurd__) + // fixme + #elif defined(__FreeBSD_kernel__) && defined(__GLIBC__) + // GNU/kFreeBSD Debian + // fixme #else - return false; + struct sysinfo info; + if (::sysinfo(&info) != 0) + return false; + size64 = (UInt64)info.mem_unit * info.totalram; + /* + printf("\n mem_unit = %lld", (UInt64)info.mem_unit); + printf("\n totalram = %lld", (UInt64)info.totalram); + printf("\n freeram = %lld", (UInt64)info.freeram); + */ #endif + + size = (size_t)1 << (sizeof(size_t) * 8 - 1); + if (size > size64) + size = (size_t)size64; + return true; +} + +#endif + + +unsigned long Get_File_OPEN_MAX() +{ + #ifdef _WIN32 + return (1 << 24) - (1 << 16); // ~16M handles + #else + // some linux versions have default open file limit for user process of 1024 files. + long n = sysconf(_SC_OPEN_MAX); + // n = -1; // for debug + // n = 9; // for debug + if (n < 1) + { + // n = OPEN_MAX; // ??? + // n = FOPEN_MAX; // = 16 : + #ifdef _POSIX_OPEN_MAX + n = _POSIX_OPEN_MAX; // = 20 : + #else + n = 30; // our limit + #endif + } + return (unsigned long)n; + #endif +} + +unsigned Get_File_OPEN_MAX_Reduced_for_3_tasks() +{ + unsigned long numFiles_OPEN_MAX = NSystem::Get_File_OPEN_MAX(); + const unsigned delta = 10; // the reserve for another internal needs of process + if (numFiles_OPEN_MAX > delta) + numFiles_OPEN_MAX -= delta; + else + numFiles_OPEN_MAX = 1; + numFiles_OPEN_MAX /= 3; // we suppose that we have up to 3 tasks in total for multiple file processing + numFiles_OPEN_MAX = MyMax(numFiles_OPEN_MAX, (unsigned long)1); + unsigned n = (unsigned)(int)-1; + if (n > numFiles_OPEN_MAX) + n = (unsigned)numFiles_OPEN_MAX; + return n; } }} diff --git a/src/plugins/7zip/7za/cpp/Windows/System.h b/src/plugins/7zip/7za/cpp/Windows/System.h index 910bc7fd..2bd2e125 100644 --- a/src/plugins/7zip/7za/cpp/Windows/System.h +++ b/src/plugins/7zip/7za/cpp/Windows/System.h @@ -1,16 +1,208 @@ // Windows/System.h -#ifndef __WINDOWS_SYSTEM_H -#define __WINDOWS_SYSTEM_H +#ifndef ZIP7_INC_WINDOWS_SYSTEM_H +#define ZIP7_INC_WINDOWS_SYSTEM_H + +#ifndef _WIN32 +// #include +#include "../../C/Threads.h" +#endif #include "../Common/MyTypes.h" +#include "../Common/MyVector.h" +#include "../Common/MyWindows.h" namespace NWindows { namespace NSystem { UInt32 GetNumberOfProcessors(); -bool GetRamSize(UInt64 &size); // returns false, if unknown ram size +#ifdef _WIN32 + +struct CCpuGroups +{ + CRecordVector GroupSizes; + UInt32 NumThreadsTotal; // sum of threads in all groups + // bool Is_Win11_Groups; // useless + + void Get_GroupSize_Min_Max(UInt32 &minSize, UInt32 &maxSize) const + { + unsigned num = GroupSizes.Size(); + UInt32 minSize2 = 0, maxSize2 = 0; + if (num) + { + minSize2 = (UInt32)0 - 1; + do + { + const UInt32 v = GroupSizes[--num]; + if (minSize2 > v) minSize2 = v; + if (maxSize2 < v) maxSize2 = v; + } + while (num); + } + minSize = minSize2; + maxSize = maxSize2; + } + bool Load(); + CCpuGroups(): NumThreadsTotal(0) {} +}; + +UInt32 CountAffinity(DWORD_PTR mask); + +struct CProcessAffinity +{ + // UInt32 numProcessThreads; + // UInt32 numSysThreads; + DWORD_PTR processAffinityMask; + DWORD_PTR systemAffinityMask; + + CCpuGroups Groups; + bool IsGroupMode; + /* + IsGroupMode == true, if + Groups.GroupSizes.Size() > 1) && { dafalt affinity was not changed } + IsGroupMode == false, if single group or affinity was changed + */ + + UInt32 Load_and_GetNumberOfThreads(); + + void InitST() + { + // numProcessThreads = 1; + // numSysThreads = 1; + processAffinityMask = 1; + systemAffinityMask = 1; + IsGroupMode = false; + // Groups.NumThreadsTotal = 0; + // Groups.Is_Win11_Groups = false; + } + +/* + void CpuZero() + { + processAffinityMask = 0; + } + + void CpuSet(unsigned cpuIndex) + { + processAffinityMask |= ((DWORD_PTR)1 << cpuIndex); + } +*/ + + UInt32 GetNumProcessThreads() const + { + if (IsGroupMode) + return Groups.NumThreadsTotal; + // IsGroupMode == false + // so we don't want to use groups + // we return number of threads in default primary group: + return CountAffinity(processAffinityMask); + } + UInt32 GetNumSystemThreads() const + { + if (Groups.GroupSizes.Size() > 1 && Groups.NumThreadsTotal) + return Groups.NumThreadsTotal; + return CountAffinity(systemAffinityMask); + } + + // it returns normilized number of threads + void Get_and_return_NumProcessThreads_and_SysThreads(UInt32 &numProcessThreads, UInt32 &numSysThreads) + { + UInt32 num1 = 0, num2 = 0; + if (Get()) + { + num1 = GetNumProcessThreads(); + num2 = GetNumSystemThreads(); + } + if (num1 == 0) + num1 = NSystem::GetNumberOfProcessors(); + if (num1 == 0) + num1 = 1; + if (num2 < num1) + num2 = num1; + numProcessThreads = num1; + numSysThreads = num2; + } + + BOOL Get(); + + BOOL SetProcAffinity() const + { + return SetProcessAffinityMask(GetCurrentProcess(), processAffinityMask); + } +}; + + +#else // WIN32 + +struct CProcessAffinity +{ + UInt32 numSysThreads; + + UInt32 GetNumSystemThreads() const { return (UInt32)numSysThreads; } + BOOL Get(); + + #ifdef Z7_AFFINITY_SUPPORTED + + CCpuSet cpu_set; + + void InitST() + { + numSysThreads = 1; + CpuSet_Zero(&cpu_set); + CpuSet_Set(&cpu_set, 0); + } + + UInt32 GetNumProcessThreads() const { return (UInt32)CPU_COUNT(&cpu_set); } + void CpuZero() { CpuSet_Zero(&cpu_set); } + void CpuSet(unsigned cpuIndex) { CpuSet_Set(&cpu_set, cpuIndex); } + // CpuSet_IsSet (CPU_ISSET) can return (unsigned long) in some implementations + int IsCpuSet(unsigned cpuIndex) const { return CpuSet_IsSet(&cpu_set, cpuIndex) != 0; } + // void CpuClr(int cpuIndex) { CPU_CLR(cpuIndex, &cpu_set); } + + BOOL SetProcAffinity() const + { + return sched_setaffinity(0, sizeof(cpu_set), &cpu_set) == 0; + } + + #else // Z7_AFFINITY_SUPPORTED + + void InitST() + { + numSysThreads = 1; + } + + UInt32 GetNumProcessThreads() const + { + return numSysThreads; + /* + UInt32 num = 0; + for (unsigned i = 0; i < sizeof(cpu_set) * 8; i++) + num += (UInt32)((cpu_set >> i) & 1); + return num; + */ + } + + void CpuZero() { } + void CpuSet(unsigned /* cpuIndex */) { /* UNUSED_VAR(cpuIndex) */ } + int IsCpuSet(unsigned cpuIndex) const { return (cpuIndex < numSysThreads) ? 1 : 0; } + + BOOL SetProcAffinity() const + { + errno = ENOSYS; + return FALSE; + } + + #endif // Z7_AFFINITY_SUPPORTED +}; + +#endif // _WIN32 + + +bool GetRamSize(size_t &size); // returns false, if unknown ram size + +unsigned long Get_File_OPEN_MAX(); +unsigned Get_File_OPEN_MAX_Reduced_for_3_tasks(); }} diff --git a/src/plugins/7zip/7za/cpp/Windows/SystemInfo.cpp b/src/plugins/7zip/7za/cpp/Windows/SystemInfo.cpp new file mode 100644 index 00000000..37708056 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/Windows/SystemInfo.cpp @@ -0,0 +1,1308 @@ +// Windows/SystemInfo.cpp + +#include "StdAfx.h" + +#include "../../C/CpuArch.h" + +#include "../Common/IntToString.h" +#include "../Common/StringConvert.h" +#include "../Common/StringToInt.h" + +#ifdef _WIN32 + +#include "Registry.h" + +#else + +#include +#include +#ifdef __APPLE__ +#include + +#elif !defined(_AIX) + +#if defined(__GLIBC__) && (__GLIBC__ * 100 + __GLIBC_MINOR__ >= 216) + #define Z7_GETAUXV_AVAILABLE +#elif !defined(__QNXNTO__) +// #pragma message("=== is not NEW GLIBC === ") + #if defined __has_include + #if __has_include () +// #pragma message("=== sys/auxv.h is avail=== ") + #define Z7_GETAUXV_AVAILABLE + #endif + #endif +#endif + +#ifdef Z7_GETAUXV_AVAILABLE +// #if defined __has_include +// #if __has_include () +#include +#define USE_HWCAP +// #endif +// #endif + +// #undef AT_HWCAP // to debug +// #undef AT_HWCAP2 // to debug + +/* the following patch for some debian systems. + Is it OK to define AT_HWCAP and AT_HWCAP2 here with these constant numbers? */ +/* +#if defined(__FreeBSD_kernel__) && defined(__GLIBC__) + #ifndef AT_HWCAP + #define AT_HWCAP 16 + #endif + #ifndef AT_HWCAP2 + #define AT_HWCAP2 26 + #endif +#endif +*/ + +#ifdef USE_HWCAP + +#if defined(__FreeBSD__) || defined(__OpenBSD__) + +// #if (__FreeBSD__ >= 13) // (FreeBSD 12.01 is required for elf_aux_info() ???) +static unsigned long MY_getauxval(int aux) +{ + unsigned long val; + if (elf_aux_info(aux, &val, sizeof(val))) + return 0; + return val; +} + +#else // ! __FreeBSD__ + +#ifdef MY_CPU_ARM_OR_ARM64 + #if defined __has_include + #if __has_include () +#include + #endif + #endif +#endif + +#if defined(AT_HWCAP) || defined(AT_HWCAP2) +#define MY_getauxval getauxval +#endif + +#endif // ! __FreeBSD__ +#endif // USE_HWCAP +#endif // Z7_GETAUXV_AVAILABLE + +#endif // !defined(_AIX) + +#ifdef __linux__ +#include "../Windows/FileIO.h" +#endif + +#endif // WIN32 + +#include "SystemInfo.h" +#include "System.h" + +using namespace NWindows; + +#ifdef __linux__ + +static bool ReadFile_to_Buffer(CFSTR fileName, CByteBuffer &buf) +{ + buf.Free(); + NWindows::NFile::NIO::CInFile file; + if (!file.Open(fileName)) + return false; + /* + UInt64 size; + if (!file.GetLength(size)) + { + // GetLength() doesn't work "/proc/cpuinfo" + return false; + } + if (size >= ((UInt32)1 << 29)) + return false; + */ + size_t size = 0; + size_t addSize = (size_t)1 << 12; + for (;;) + { + // printf("\nsize = %d\n", (unsigned)size); + buf.ChangeSize_KeepData(size + addSize, size); + size_t processed; + if (!file.ReadFull(buf.NonConstData() + size, addSize, processed)) + return false; + if (processed == 0) + { + buf.ChangeSize_KeepData(size, size); + return true; + } + size += processed; + addSize *= 2; + } +} + +static bool ReadFile_to_String(CFSTR fileName, AString &s) +{ + CByteBuffer buf; + if (!ReadFile_to_Buffer(fileName, buf)) + return false; + s.SetFrom_CalcLen((const char *)(const void *)(const Byte *)buf, (unsigned)buf.Size()); + return true; +} + +#endif + + +#if defined(_WIN32) || defined(AT_HWCAP) || defined(AT_HWCAP2) +static void PrintHex(AString &s, UInt64 v) +{ + char temp[32]; + ConvertUInt64ToHex(v, temp); + s += temp; +} +#endif + +#ifdef MY_CPU_X86_OR_AMD64 + +Z7_NO_INLINE +static void PrintCpuChars(AString &s, UInt32 v) +{ + for (unsigned j = 0; j < 4; j++) + { + const Byte b = (Byte)(v & 0xFF); + v >>= 8; + if (b == 0) + break; + if (b >= 0x20 && b <= 0x7f) + s.Add_Char((char)b); + else + { + s.Add_Char('['); + char temp[16]; + ConvertUInt32ToHex(b, temp); + s += temp; + s.Add_Char(']'); + } + } +} + + +static void x86cpuid_to_String(AString &s) +{ + s.Empty(); + + UInt32 a[4]; + // cpuid was called already. So we don't check for cpuid availability here + z7_x86_cpuid(a, 0x80000000); + + if (a[0] >= 0x80000004) // if (maxFunc2 >= hi+4) the full name is available + { + for (unsigned i = 0; i < 3; i++) + { + z7_x86_cpuid(a, (UInt32)(0x80000002 + i)); + for (unsigned j = 0; j < 4; j++) + PrintCpuChars(s, a[j]); + } + } + + s.Trim(); + + if (s.IsEmpty()) + { + z7_x86_cpuid(a, 0); + for (unsigned i = 1; i < 4; i++) + { + const unsigned j = (i ^ (i >> 1)); + PrintCpuChars(s, a[j]); + } + s.Trim(); + } +} + +/* +static void x86cpuid_all_to_String(AString &s) +{ + Cx86cpuid p; + if (!x86cpuid_CheckAndRead(&p)) + return; + s += "x86cpuid maxFunc = "; + s.Add_UInt32(p.maxFunc); + for (unsigned j = 0; j <= p.maxFunc; j++) + { + s.Add_LF(); + // s.Add_UInt32(j); // align + { + char temp[32]; + ConvertUInt32ToString(j, temp); + unsigned len = (unsigned)strlen(temp); + while (len < 8) + { + len++; + s.Add_Space(); + } + s += temp; + } + + s += ":"; + UInt32 d[4] = { 0 }; + MyCPUID(j, &d[0], &d[1], &d[2], &d[3]); + for (unsigned i = 0; i < 4; i++) + { + char temp[32]; + ConvertUInt32ToHex8Digits(d[i], temp); + s.Add_Space(); + s += temp; + } + } +} +*/ + +#endif + + + +#ifdef _WIN32 + +static const char * const k_PROCESSOR_ARCHITECTURE[] = +{ + "x86" // "INTEL" + , "MIPS" + , "ALPHA" + , "PPC" + , "SHX" + , "ARM" + , "IA64" + , "ALPHA64" + , "MSIL" + , "x64" // "AMD64" + , "IA32_ON_WIN64" + , "NEUTRAL" + , "ARM64" + , "ARM32_ON_WIN64" +}; + +#define Z7_WIN_PROCESSOR_ARCHITECTURE_INTEL 0 +#define Z7_WIN_PROCESSOR_ARCHITECTURE_AMD64 9 + + +#define Z7_WIN_PROCESSOR_INTEL_PENTIUM 586 +#define Z7_WIN_PROCESSOR_AMD_X8664 8664 + +/* +static const CUInt32PCharPair k_PROCESSOR[] = +{ + { 2200, "IA64" }, + { 8664, "x64" } +}; + +#define PROCESSOR_INTEL_386 386 +#define PROCESSOR_INTEL_486 486 +#define PROCESSOR_INTEL_PENTIUM 586 +#define PROCESSOR_INTEL_860 860 +#define PROCESSOR_INTEL_IA64 2200 +#define PROCESSOR_AMD_X8664 8664 +#define PROCESSOR_MIPS_R2000 2000 +#define PROCESSOR_MIPS_R3000 3000 +#define PROCESSOR_MIPS_R4000 4000 +#define PROCESSOR_ALPHA_21064 21064 +#define PROCESSOR_PPC_601 601 +#define PROCESSOR_PPC_603 603 +#define PROCESSOR_PPC_604 604 +#define PROCESSOR_PPC_620 620 +#define PROCESSOR_HITACHI_SH3 10003 +#define PROCESSOR_HITACHI_SH3E 10004 +#define PROCESSOR_HITACHI_SH4 10005 +#define PROCESSOR_MOTOROLA_821 821 +#define PROCESSOR_SHx_SH3 103 +#define PROCESSOR_SHx_SH4 104 +#define PROCESSOR_STRONGARM 2577 // 0xA11 +#define PROCESSOR_ARM720 1824 // 0x720 +#define PROCESSOR_ARM820 2080 // 0x820 +#define PROCESSOR_ARM920 2336 // 0x920 +#define PROCESSOR_ARM_7TDMI 70001 +#define PROCESSOR_OPTIL 18767 // 0x494f +*/ + + +/* +static const char * const k_PF[] = +{ + "FP_ERRATA" + , "FP_EMU" + , "CMPXCHG" + , "MMX" + , "PPC_MOVEMEM_64BIT" + , "ALPHA_BYTE" + , "SSE" + , "3DNOW" + , "RDTSC" + , "PAE" + , "SSE2" + , "SSE_DAZ" + , "NX" + , "SSE3" + , "CMPXCHG16B" + , "CMP8XCHG16" + , "CHANNELS" + , "XSAVE" + , "ARM_VFP_32" + , "ARM_NEON" + , "L2AT" + , "VIRT_FIRMWARE" + , "RDWRFSGSBASE" + , "FASTFAIL" + , "ARM_DIVIDE" + , "ARM_64BIT_LOADSTORE_ATOMIC" + , "ARM_EXTERNAL_CACHE" + , "ARM_FMAC" + , "RDRAND" + , "ARM_V8" + , "ARM_V8_CRYPTO" + , "ARM_V8_CRC32" + , "RDTSCP" + , "RDPID" + , "ARM_V81_ATOMIC" + , "MONITORX" +}; +*/ + +#endif + + +static void PrintPage(AString &s, UInt64 v) +{ + const char *t = "B"; + if ((v & ((1 << 20) - 1)) == 0) { v >>= 20; t = "MB"; } + else if ((v & ((1 << 10) - 1)) == 0) { v >>= 10; t = "KB"; } + s.Add_UInt64(v); + s += t; +} + +#ifdef _WIN32 + +static AString TypeToString2(const char * const table[], unsigned num, UInt32 value) +{ + char sz[16]; + const char *p = NULL; + if (value < num) + p = table[value]; + if (!p) + { + ConvertUInt32ToString(value, sz); + p = sz; + } + return (AString)p; +} + +// #if defined(Z7_LARGE_PAGES) || defined(_WIN32) +// #ifdef _WIN32 +void PrintSize_KMGT_Or_Hex(AString &s, UInt64 v) +{ + char c = 0; + if ((v & 0x3FF) == 0) { v >>= 10; c = 'K'; + if ((v & 0x3FF) == 0) { v >>= 10; c = 'M'; + if ((v & 0x3FF) == 0) { v >>= 10; c = 'G'; + if ((v & 0x3FF) == 0) { v >>= 10; c = 'T'; + }}}} + else + { + // s += "0x"; + PrintHex(s, v); + return; + } + s.Add_UInt64(v); + if (c) + s.Add_Char(c); + s.Add_Char('B'); +} +// #endif +// #endif + +static void SysInfo_To_String(AString &s, const SYSTEM_INFO &si) +{ + s += TypeToString2(k_PROCESSOR_ARCHITECTURE, Z7_ARRAY_SIZE(k_PROCESSOR_ARCHITECTURE), si.wProcessorArchitecture); + + if (!( (si.wProcessorArchitecture == Z7_WIN_PROCESSOR_ARCHITECTURE_INTEL && si.dwProcessorType == Z7_WIN_PROCESSOR_INTEL_PENTIUM) + || (si.wProcessorArchitecture == Z7_WIN_PROCESSOR_ARCHITECTURE_AMD64 && si.dwProcessorType == Z7_WIN_PROCESSOR_AMD_X8664))) + { + s.Add_Space(); + // s += TypePairToString(k_PROCESSOR, Z7_ARRAY_SIZE(k_PROCESSOR), si.dwProcessorType); + s.Add_UInt32(si.dwProcessorType); + } + s.Add_Space(); + PrintHex(s, si.wProcessorLevel); + s.Add_Dot(); + PrintHex(s, si.wProcessorRevision); + if ((UInt64)si.dwActiveProcessorMask + 1 != ((UInt64)1 << si.dwNumberOfProcessors)) + if ((UInt64)si.dwActiveProcessorMask + 1 != 0 || si.dwNumberOfProcessors != sizeof(UInt64) * 8) + { + s += " act:"; + PrintHex(s, si.dwActiveProcessorMask); + } + s += " threads:"; + s.Add_UInt32(si.dwNumberOfProcessors); + if (si.dwPageSize != 1 << 12) + { + s += " page:"; + PrintPage(s, si.dwPageSize); + } + if (si.dwAllocationGranularity != 1 << 16) + { + s += " gran:"; + PrintPage(s, si.dwAllocationGranularity); + } + s.Add_Space(); + + const DWORD_PTR minAdd = (DWORD_PTR)si.lpMinimumApplicationAddress; + UInt64 maxSize = (UInt64)(DWORD_PTR)si.lpMaximumApplicationAddress + 1; + const UInt32 kReserveSize = ((UInt32)1 << 16); + if (minAdd != kReserveSize) + { + PrintSize_KMGT_Or_Hex(s, minAdd); + s.Add_Minus(); + } + else + { + if ((maxSize & (kReserveSize - 1)) == 0) + maxSize += kReserveSize; + } + PrintSize_KMGT_Or_Hex(s, maxSize); +} + +#ifndef _WIN64 +EXTERN_C_BEGIN +typedef VOID (WINAPI *Func_GetNativeSystemInfo)(LPSYSTEM_INFO lpSystemInfo); +EXTERN_C_END +#endif + +#endif + +#ifdef __APPLE__ +#ifndef MY_CPU_X86_OR_AMD64 +static void Add_sysctlbyname_to_String(const char *name, AString &s) +{ + size_t bufSize = 256; + char buf[256]; + if (z7_sysctlbyname_Get(name, &buf, &bufSize) == 0) + s += buf; +} +#endif +#endif + +void GetSysInfo(AString &s1, AString &s2); +void GetSysInfo(AString &s1, AString &s2) +{ + s1.Empty(); + s2.Empty(); + + #ifdef _WIN32 + SYSTEM_INFO si; + GetSystemInfo(&si); + { + SysInfo_To_String(s1, si); + // s += " : "; + } + + #if !defined(_WIN64) && !defined(UNDER_CE) + const + Func_GetNativeSystemInfo fn = Z7_GET_PROC_ADDRESS( + Func_GetNativeSystemInfo, GetModuleHandleA("kernel32.dll"), + "GetNativeSystemInfo"); + if (fn) + { + SYSTEM_INFO si2; + fn(&si2); + // if (memcmp(&si, &si2, sizeof(si)) != 0) + { + // s += " - "; + SysInfo_To_String(s2, si2); + } + } + #endif + #endif +} + + +static void AddBracedString(AString &dest, AString &src) +{ + if (!src.IsEmpty()) + { + dest.Add_Space_if_NotEmpty(); + dest.Add_Char('('); + dest += src; + dest.Add_Char(')'); + } +} + +struct CCpuName +{ + AString CpuName; + AString Revision; + AString Microcode; + AString LargePages; + +#ifdef _WIN32 + UInt32 MHz; + +#ifdef MY_CPU_ARM64 +#define Z7_SYS_INFO_SHOW_ARM64_REGS +#endif +#ifdef Z7_SYS_INFO_SHOW_ARM64_REGS + bool Arm64_ISAR0_EL1_Defined; + UInt64 Arm64_ISAR0_EL1; +#endif +#endif + +#ifdef _WIN32 + CCpuName(): + MHz(0) +#ifdef Z7_SYS_INFO_SHOW_ARM64_REGS + , Arm64_ISAR0_EL1_Defined(false) + , Arm64_ISAR0_EL1(0) +#endif + {} +#endif + + void Fill(); + + void Get_Revision_Microcode_LargePages(AString &s) + { + s.Empty(); + AddBracedString(s, Revision); + AddBracedString(s, Microcode); +#ifdef _WIN32 + if (MHz != 0) + { + s.Add_Space_if_NotEmpty(); + s.Add_UInt32(MHz); + s += " MHz"; + } +#endif + if (!LargePages.IsEmpty()) + s.Add_OptSpaced(LargePages); + } + +#ifdef Z7_SYS_INFO_SHOW_ARM64_REGS + void Get_Registers(AString &s) + { + if (Arm64_ISAR0_EL1_Defined) + { + // ID_AA64ISAR0_EL1 + s.Add_OptSpaced("cp4030:"); + PrintHex(s, Arm64_ISAR0_EL1); + { + const unsigned sha2 = ((unsigned)(Arm64_ISAR0_EL1 >> 12) & 0xf) - 1; + if (sha2 < 2) + { + s += ":SHA256"; + if (sha2) + s += ":SHA512"; + } + } + } + } +#endif +}; + +void CCpuName::Fill() +{ + // CpuName.Empty(); + // Revision.Empty(); + // Microcode.Empty(); + // LargePages.Empty(); + + AString &s = CpuName; + + #ifdef MY_CPU_X86_OR_AMD64 + { + #if !defined(MY_CPU_AMD64) + if (z7_x86_cpuid_GetMaxFunc()) + #endif + { + x86cpuid_to_String(s); + { + UInt32 a[4]; + z7_x86_cpuid(a, 1); + char temp[16]; + ConvertUInt32ToHex(a[0], temp); + Revision += temp; + } + } + } + #elif defined(__APPLE__) + { + Add_sysctlbyname_to_String("machdep.cpu.brand_string", s); + } + #elif defined(MY_CPU_E2K) && defined(Z7_MCST_LCC_VERSION) && (Z7_MCST_LCC_VERSION >= 12323) + { + s += "mcst "; + s += __builtin_cpu_name(); + s.Add_Space(); + s += __builtin_cpu_arch(); + } + #endif + + +#ifdef _WIN32 + { + NRegistry::CKey key; + if (key.Open(HKEY_LOCAL_MACHINE, TEXT("HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0"), KEY_READ) == ERROR_SUCCESS) + { + // s.Empty(); // for debug + { + CSysString name; + if (s.IsEmpty()) + if (key.QueryValue(TEXT("ProcessorNameString"), name) == ERROR_SUCCESS) + { + s += GetAnsiString(name); + } + if (key.QueryValue(TEXT("Identifier"), name) == ERROR_SUCCESS) + { + if (!Revision.IsEmpty()) + Revision += " : "; + Revision += GetAnsiString(name); + } + } +#ifdef _WIN32 + key.GetValue_UInt32_IfOk(TEXT("~MHz"), MHz); +#ifdef Z7_SYS_INFO_SHOW_ARM64_REGS +/* +mapping arm64 registers to Windows registry: +CP 4000: MIDR_EL1 +CP 4020: ID_AA64PFR0_EL1 +CP 4021: ID_AA64PFR1_EL1 +CP 4028: ID_AA64DFR0_EL1 +CP 4029: ID_AA64DFR1_EL1 +CP 402C: ID_AA64AFR0_EL1 +CP 402D: ID_AA64AFR1_EL1 +CP 4030: ID_AA64ISAR0_EL1 +CP 4031: ID_AA64ISAR1_EL1 +CP 4038: ID_AA64MMFR0_EL1 +CP 4039: ID_AA64MMFR1_EL1 +CP 403A: ID_AA64MMFR2_EL1 +*/ + if (key.GetValue_UInt64_IfOk(TEXT("CP 4030"), Arm64_ISAR0_EL1) == ERROR_SUCCESS) + Arm64_ISAR0_EL1_Defined = true; +#endif +#endif + LONG res[2]; + CByteBuffer bufs[2]; + res[0] = key.QueryValue_Binary(TEXT("Previous Update Revision"), bufs[0]); + res[1] = key.QueryValue_Binary(TEXT("Update Revision"), bufs[1]); + if (res[0] == ERROR_SUCCESS || res[1] == ERROR_SUCCESS) + { + for (unsigned i = 0; i < 2; i++) + { + if (i == 1) + Microcode += "->"; + if (res[i] != ERROR_SUCCESS) + continue; + const CByteBuffer &buf = bufs[i]; + if (buf.Size() == 8) + { + const UInt32 high = GetUi32(buf); + if (high != 0) + { + PrintHex(Microcode, high); + Microcode.Add_Dot(); + } + PrintHex(Microcode, GetUi32(buf + 4)); + } + } + } + } + } +#endif + + if (s.IsEmpty()) + { + #ifdef MY_CPU_NAME + s += MY_CPU_NAME; + #endif + } + + #ifdef __APPLE__ + { + AString s2; + UInt32 v = 0; + if (z7_sysctlbyname_Get_UInt32("machdep.cpu.core_count", &v) == 0) + { + s2.Add_UInt32(v); + s2.Add_Char('C'); + } + if (z7_sysctlbyname_Get_UInt32("machdep.cpu.thread_count", &v) == 0) + { + s2.Add_UInt32(v); + s2.Add_Char('T'); + } + if (!s2.IsEmpty()) + { + s.Add_Space_if_NotEmpty(); + s += s2; + } + } + #endif + + #ifdef Z7_LARGE_PAGES + Add_LargePages_String(LargePages); + #endif +} + + +#if 0 && defined(Z7_LARGE_PAGES) && defined(__linux__) +bool Get_HugePageSize(UInt64 &pageSize); +bool Get_HugePageSize(UInt64 &pageSize) +{ + AString s2; + if (ReadFile_to_String("/sys/kernel/mm/transparent_hugepage/hpage_pmd_size", s2)) + { + pageSize = ConvertStringToUInt64(s2.Ptr(), NULL); + if (pageSize) + return true; + } + return false; +} +#endif + + +void AddCpuFeatures(AString &s); +void AddCpuFeatures(AString &s) +{ + #ifdef _WIN32 + // const unsigned kNumFeatures_Extra = 32; // we check also for unknown features + // const unsigned kNumFeatures = Z7_ARRAY_SIZE(k_PF) + kNumFeatures_Extra; + const unsigned kNumFeatures = 64; + UInt64 flags = 0; + for (unsigned i = 0; i < kNumFeatures; i++) + { + if (IsProcessorFeaturePresent((DWORD)i)) + { + flags += (UInt64)1 << i; + // s.Add_Space_if_NotEmpty(); + // s += TypeToString2(k_PF, Z7_ARRAY_SIZE(k_PF), i); + } + } + s.Add_OptSpaced("f:"); + PrintHex(s, flags); + + #elif defined(__APPLE__) + { + UInt32 v = 0; + if (z7_sysctlbyname_Get_UInt32("hw.pagesize", &v) == 0) + { + s.Add_OptSpaced("PageSize:"); + PrintPage(s, v); + } + } + + #else + + const long v = sysconf(_SC_PAGESIZE); + if (v != -1) + { + s.Add_OptSpaced("PageSize:"); + PrintPage(s, (unsigned long)v); + } + + #if !defined(_AIX) + + #ifdef __linux__ + + { + AString s2; + if (ReadFile_to_String("/proc/meminfo", s2)) + { + const int pos = s2.Find("Hugepagesize:"); + if (pos >= 0) + { + s.Add_OptSpaced("HPS:"); + s2.DeleteFrontal((unsigned)pos + 13); // 13 == strlen("Hugepagesize:") + s2.TrimLeft(); + // const int pos2 = s2.Find("kB"); + const UInt64 size = ConvertStringToUInt64(s2.Ptr(), NULL); + if (size) + PrintPage(s, size << 10); + } + } + + if (ReadFile_to_String("/sys/kernel/mm/transparent_hugepage/hpage_pmd_size", s2)) + { + s.Add_OptSpaced("THPS:"); + const UInt64 size = ConvertStringToUInt64(s2.Ptr(), NULL); + if (size) + PrintPage(s, size); + } + /* + { + UInt64 pagesSize; + if (Get_HugePageSize(pagesSize) && pagesSize) + { + s.Add_OptSpaced("THPS:"); + PrintPage(s, pagesSize); + } + } + */ + + if (ReadFile_to_String("/sys/kernel/mm/transparent_hugepage/enabled", s2)) + { + s.Add_OptSpaced("THP:"); + const int pos = s2.Find('['); + if (pos >= 0) + { + const int pos2 = s2.Find(']', (unsigned)pos + 1); + if (pos2 >= 0) + { + s2.DeleteFrom((unsigned)pos2); + s2.DeleteFrontal((unsigned)pos + 1); + } + } + s += s2; + } + } + // else throw CSystemException(MY_SRes_HRESULT_FROM_WRes(errno)); + + #endif + + + #ifdef AT_HWCAP + s.Add_OptSpaced("hwcap:"); + { + unsigned long h = MY_getauxval(AT_HWCAP); + PrintHex(s, h); + #ifdef MY_CPU_ARM64 +#ifndef HWCAP_SHA3 +#define HWCAP_SHA3 (1 << 17) +#endif +#ifndef HWCAP_SHA512 +#define HWCAP_SHA512 (1 << 21) +// #pragma message("=== HWCAP_SHA512 define === ") +#endif + if (h & HWCAP_CRC32) s += ":CRC32"; + if (h & HWCAP_SHA1) s += ":SHA1"; + if (h & HWCAP_SHA2) s += ":SHA2"; + if (h & HWCAP_SHA3) s += ":SHA3"; + if (h & HWCAP_SHA512) s += ":SHA512"; + if (h & HWCAP_AES) s += ":AES"; + if (h & HWCAP_ASIMD) s += ":ASIMD"; + #elif defined(MY_CPU_ARM) + if (h & HWCAP_NEON) s += ":NEON"; + #endif + } + #endif // AT_HWCAP + + #ifdef AT_HWCAP2 + { + unsigned long h = MY_getauxval(AT_HWCAP2); + #ifndef MY_CPU_ARM + if (h != 0) + #endif + { + s += " hwcap2:"; + PrintHex(s, h); + #ifdef MY_CPU_ARM + if (h & HWCAP2_CRC32) s += ":CRC32"; + if (h & HWCAP2_SHA1) s += ":SHA1"; + if (h & HWCAP2_SHA2) s += ":SHA2"; + if (h & HWCAP2_AES) s += ":AES"; + #endif + } + } + #endif // AT_HWCAP2 + #endif // _AIX + #endif // _WIN32 +} + + +#ifdef _WIN32 +#ifndef UNDER_CE + +Z7_DIAGNOSTIC_IGNORE_CAST_FUNCTION + +EXTERN_C_BEGIN +typedef void (WINAPI * Func_RtlGetVersion) (OSVERSIONINFOEXW *); +EXTERN_C_END + +static BOOL My_RtlGetVersion(OSVERSIONINFOEXW *vi) +{ + const HMODULE ntdll = ::GetModuleHandleW(L"ntdll.dll"); + if (!ntdll) + return FALSE; + const + Func_RtlGetVersion func = Z7_GET_PROC_ADDRESS( + Func_RtlGetVersion, ntdll, + "RtlGetVersion"); + if (!func) + return FALSE; + func(vi); + return TRUE; +} + +#endif +#endif + + +void GetOsInfoText(AString &sRes) +{ + sRes.Empty(); + AString s; + + #ifdef _WIN32 + #ifndef UNDER_CE + // OSVERSIONINFO vi; + OSVERSIONINFOEXW vi; + vi.dwOSVersionInfoSize = sizeof(vi); + // if (::GetVersionEx(&vi)) + if (My_RtlGetVersion(&vi)) + { + s += "Windows"; + if (vi.dwPlatformId != VER_PLATFORM_WIN32_NT) + s.Add_UInt32(vi.dwPlatformId); + s.Add_Space(); s.Add_UInt32(vi.dwMajorVersion); + s.Add_Dot(); s.Add_UInt32(vi.dwMinorVersion); + s.Add_Space(); s.Add_UInt32(vi.dwBuildNumber); + + if (vi.wServicePackMajor != 0 || vi.wServicePackMinor != 0) + { + s += " SP:"; s.Add_UInt32(vi.wServicePackMajor); + s.Add_Dot(); s.Add_UInt32(vi.wServicePackMinor); + } + // s += " Suite:"; PrintHex(s, vi.wSuiteMask); + // s += " Type:"; s.Add_UInt32(vi.wProductType); + // s.Add_Space(); s += GetOemString(vi.szCSDVersion); + } + /* + { + s += " OEMCP:"; s.Add_UInt32(GetOEMCP()); + s += " ACP:"; s.Add_UInt32(GetACP()); + } + */ + #endif + #else // _WIN32 + + if (!s.IsEmpty()) + s.Add_LF(); + struct utsname un; + if (uname(&un) == 0) + { + s += un.sysname; + // s += " : "; s += un.nodename; // we don't want to show name of computer + s += " : "; s += un.release; + s += " : "; s += un.version; + s += " : "; s += un.machine; + + #ifdef __APPLE__ + // Add_sysctlbyname_to_String("kern.version", s); + // it's same as "utsname.version" + #endif + } + #endif // _WIN32 + + sRes += s; + #ifdef MY_CPU_X86_OR_AMD64 + { + AString s2; + GetVirtCpuid(s2); + if (!s2.IsEmpty()) + { + sRes += " : "; + sRes += s2; + } + } + #endif +} + + + +void GetSystemInfoText(AString &sRes) +{ + GetOsInfoText(sRes); + sRes.Add_LF(); + + { + AString s, s1, s2; + GetSysInfo(s1, s2); + if (!s1.IsEmpty() || !s2.IsEmpty()) + { + s = s1; + if (s1 != s2 && !s2.IsEmpty()) + { + s += " - "; + s += s2; + } + } + { + AddCpuFeatures(s); + if (!s.IsEmpty()) + { + sRes += s; + sRes.Add_LF(); + } + } + } + { + AString s, registers; + GetCpuName_MultiLine(s, registers); + if (!s.IsEmpty()) + { + sRes += s; + sRes.Add_LF(); + } + if (!registers.IsEmpty()) + { + sRes += registers; + sRes.Add_LF(); + } + } + /* + #ifdef MY_CPU_X86_OR_AMD64 + { + AString s; + x86cpuid_all_to_String(s); + if (!s.IsEmpty()) + { + printCallback->Print(s); + printCallback->NewLine(); + } + } + #endif + */ +} + + +void GetCpuName_MultiLine(AString &s, AString ®isters); +void GetCpuName_MultiLine(AString &s, AString ®isters) +{ + CCpuName cpuName; + cpuName.Fill(); + s = cpuName.CpuName; + AString s2; + cpuName.Get_Revision_Microcode_LargePages(s2); + if (!s2.IsEmpty()) + { + s.Add_LF(); + s += s2; + } + registers.Empty(); +#ifdef Z7_SYS_INFO_SHOW_ARM64_REGS + cpuName.Get_Registers(registers); +#endif +} + + +#ifdef MY_CPU_X86_OR_AMD64 + +void GetVirtCpuid(AString &s) +{ + const UInt32 kHv = 0x40000000; + + Z7_IF_X86_CPUID_SUPPORTED + { + UInt32 a[4]; + z7_x86_cpuid(a, kHv); + + if (a[0] < kHv || a[0] >= kHv + (1 << 16)) + return; + { + { + for (unsigned j = 1; j < 4; j++) + PrintCpuChars(s, a[j]); + } + } + if (a[0] >= kHv + 1) + { + UInt32 d[4]; + z7_x86_cpuid(d, kHv + 1); + s += " : "; + PrintCpuChars(s, d[0]); + if (a[0] >= kHv + 2) + { + z7_x86_cpuid(d, kHv + 2); + s += " : "; + s.Add_UInt32(d[1] >> 16); + s.Add_Dot(); s.Add_UInt32(d[1] & 0xffff); + s.Add_Dot(); s.Add_UInt32(d[0]); + s.Add_Dot(); s.Add_UInt32(d[2]); + s.Add_Dot(); s.Add_UInt32(d[3] >> 24); + s.Add_Dot(); s.Add_UInt32(d[3] & 0xffffff); + } + /* + if (a[0] >= kHv + 5) + { + z7_x86_cpuid(d, kHv + 5); + s += " : "; + s.Add_UInt32(d[0]); + s += "p"; + s.Add_UInt32(d[1]); + s += "t"; + } + */ + } + } +} + +#endif + + +void GetCompiler(AString &s) +{ + #ifdef __clang__ + s += " CLANG "; + s.Add_UInt32(__clang_major__); + s.Add_Dot(); + s.Add_UInt32(__clang_minor__); + s.Add_Dot(); + s.Add_UInt32(__clang_patchlevel__); + #endif + + #ifdef __xlC__ + s += " XLC "; + s.Add_UInt32(__xlC__ >> 8); + s.Add_Dot(); + s.Add_UInt32(__xlC__ & 0xFF); + #ifdef __xlC_ver__ + s.Add_Dot(); + s.Add_UInt32(__xlC_ver__ >> 8); + s.Add_Dot(); + s.Add_UInt32(__xlC_ver__ & 0xFF); + #endif + #endif + + // #define __LCC__ 126 + // #define __LCC_MINOR__ 20 + // #define __MCST__ 1 + #ifdef __MCST__ + s += " MCST"; + #endif + #ifdef __LCC__ + s += " LCC "; + s.Add_UInt32(__LCC__ / 100); + s.Add_Dot(); + s.Add_UInt32(__LCC__ % 100 / 10); + s.Add_UInt32(__LCC__ % 10); + #ifdef __LCC_MINOR__ + s.Add_Dot(); + s.Add_UInt32(__LCC_MINOR__ / 10); + s.Add_UInt32(__LCC_MINOR__ % 10); + #endif + #endif + + // #define __EDG_VERSION__ 602 + #ifdef __EDG_VERSION__ + s += " EDG "; + s.Add_UInt32(__EDG_VERSION__ / 100); + s.Add_Dot(); + s.Add_UInt32(__EDG_VERSION__ % 100 / 10); + s.Add_UInt32(__EDG_VERSION__ % 10); + #endif + + #ifdef __VERSION__ + s.Add_Space(); + s += "ver:"; + s += __VERSION__; + #endif + + #ifdef __GNUC__ + s += " GCC "; + s.Add_UInt32(__GNUC__); + s.Add_Dot(); + s.Add_UInt32(__GNUC_MINOR__); + s.Add_Dot(); + s.Add_UInt32(__GNUC_PATCHLEVEL__); + #endif + + + #ifdef _MSC_VER + s += " MSC "; + s.Add_UInt32(_MSC_VER); + #ifdef _MSC_FULL_VER + s.Add_Dot(); + s.Add_UInt32(_MSC_FULL_VER); + #endif + + #endif + + #if defined(__AVX512F__) + #if defined(__AVX512VL__) + #define MY_CPU_COMPILE_ISA "AVX512VL" + #else + #define MY_CPU_COMPILE_ISA "AVX512F" + #endif + #elif defined(__AVX2__) + #define MY_CPU_COMPILE_ISA "AVX2" + #elif defined(__AVX__) + #define MY_CPU_COMPILE_ISA "AVX" + #elif defined(__SSE2__) + #define MY_CPU_COMPILE_ISA "SSE2" + #elif defined(_M_IX86_FP) && (_M_IX86_FP >= 2) + #define MY_CPU_COMPILE_ISA "SSE2" + #elif defined(__SSE__) + #define MY_CPU_COMPILE_ISA "SSE" + #elif defined(_M_IX86_FP) && (_M_IX86_FP >= 1) + #define MY_CPU_COMPILE_ISA "SSE" + #elif defined(__i686__) + #define MY_CPU_COMPILE_ISA "i686" + #elif defined(__i586__) + #define MY_CPU_COMPILE_ISA "i586" + #elif defined(__i486__) + #define MY_CPU_COMPILE_ISA "i486" + #elif defined(__i386__) + #define MY_CPU_COMPILE_ISA "i386" + #elif defined(_M_IX86_FP) + #define MY_CPU_COMPILE_ISA "IA32" + #endif + + AString s2; + + #ifdef MY_CPU_COMPILE_ISA + s2.Add_OptSpaced(MY_CPU_COMPILE_ISA); + #endif + +#ifndef MY_CPU_ARM64 + #ifdef __ARM_FP + s2.Add_OptSpaced("FP"); + #endif + #ifdef __ARM_NEON + s2.Add_OptSpaced("NEON"); + #endif + #ifdef __NEON__ + s2.Add_OptSpaced("__NEON__"); + #endif + #ifdef __ARM_FEATURE_SIMD32 + s2.Add_OptSpaced("SIMD32"); + #endif +#endif + + #ifdef __ARM_FEATURE_CRYPTO + s2.Add_OptSpaced("CRYPTO"); + #endif + + #ifdef __ARM_FEATURE_SHA2 + s2.Add_OptSpaced("SHA2"); + #endif + + #ifdef __ARM_FEATURE_AES + s2.Add_OptSpaced("AES"); + #endif + + #ifdef __ARM_FEATURE_CRC32 + s2.Add_OptSpaced("CRC32"); + #endif + + #ifdef __ARM_FEATURE_UNALIGNED + s2.Add_OptSpaced("UNALIGNED"); + #endif + + + #ifdef MY_CPU_BE + s2.Add_OptSpaced("BE"); + #endif + + #if defined(MY_CPU_LE_UNALIGN) \ + && !defined(MY_CPU_X86_OR_AMD64) \ + && !defined(MY_CPU_ARM64) + s2.Add_OptSpaced("LE-unaligned"); + #endif + + if (!s2.IsEmpty()) + { + s.Add_OptSpaced(": "); + s += s2; + } +} diff --git a/src/plugins/7zip/7za/cpp/Windows/SystemInfo.h b/src/plugins/7zip/7za/cpp/Windows/SystemInfo.h new file mode 100644 index 00000000..4601685e --- /dev/null +++ b/src/plugins/7zip/7za/cpp/Windows/SystemInfo.h @@ -0,0 +1,19 @@ +// Windows/SystemInfo.h + +#ifndef ZIP7_INC_WINDOWS_SYSTEM_INFO_H +#define ZIP7_INC_WINDOWS_SYSTEM_INFO_H + +#include "../Common/MyString.h" + + +void GetCpuName_MultiLine(AString &s, AString ®isters); + +void GetOsInfoText(AString &sRes); +void GetSystemInfoText(AString &s); +void PrintSize_KMGT_Or_Hex(AString &s, UInt64 v); +void Add_LargePages_String(AString &s); + +void GetCompiler(AString &s); +void GetVirtCpuid(AString &s); + +#endif diff --git a/src/plugins/7zip/7za/cpp/Windows/Thread.h b/src/plugins/7zip/7za/cpp/Windows/Thread.h index 16a509d4..028267bc 100644 --- a/src/plugins/7zip/7za/cpp/Windows/Thread.h +++ b/src/plugins/7zip/7za/cpp/Windows/Thread.h @@ -1,27 +1,35 @@ // Windows/Thread.h -#ifndef __WINDOWS_THREAD_H -#define __WINDOWS_THREAD_H +#ifndef ZIP7_INC_WINDOWS_THREAD_H +#define ZIP7_INC_WINDOWS_THREAD_H #include "../../C/Threads.h" -#include "Defs.h" +#include "WinDefs.h" namespace NWindows { -class CThread +class CThread MY_UNCOPYABLE { ::CThread thread; public: - CThread() { Thread_Construct(&thread); } + CThread() { Thread_CONSTRUCT(&thread) } ~CThread() { Close(); } bool IsCreated() { return Thread_WasCreated(&thread) != 0; } WRes Close() { return Thread_Close(&thread); } - WRes Create(THREAD_FUNC_RET_TYPE (THREAD_FUNC_CALL_TYPE *startAddress)(void *), LPVOID parameter) - { return Thread_Create(&thread, startAddress, parameter); } - WRes Wait() { return Thread_Wait(&thread); } - - #ifdef _WIN32 + // WRes Wait() { return Thread_Wait(&thread); } + WRes Wait_Close() { return Thread_Wait_Close(&thread); } + + WRes Create(THREAD_FUNC_TYPE startAddress, LPVOID param) + { return Thread_Create(&thread, startAddress, param); } + WRes Create_With_Affinity(THREAD_FUNC_TYPE startAddress, LPVOID param, CAffinityMask affinity) + { return Thread_Create_With_Affinity(&thread, startAddress, param, affinity); } + WRes Create_With_CpuSet(THREAD_FUNC_TYPE startAddress, LPVOID param, const CCpuSet *cpuSet) + { return Thread_Create_With_CpuSet(&thread, startAddress, param, cpuSet); } + +#ifdef _WIN32 + WRes Create_With_Group(THREAD_FUNC_TYPE startAddress, LPVOID param, unsigned group, CAffinityMask affinity = 0) + { return Thread_Create_With_Group(&thread, startAddress, param, group, affinity); } operator HANDLE() { return thread; } void Attach(HANDLE handle) { thread = handle; } HANDLE Detach() { HANDLE h = thread; thread = NULL; return h; } @@ -30,7 +38,7 @@ class CThread bool Terminate(DWORD exitCode) { return BOOLToBool(::TerminateThread(thread, exitCode)); } int GetPriority() { return ::GetThreadPriority(thread); } bool SetPriority(int priority) { return BOOLToBool(::SetThreadPriority(thread, priority)); } - #endif +#endif }; } diff --git a/src/plugins/7zip/7za/cpp/Windows/TimeUtils.cpp b/src/plugins/7zip/7za/cpp/Windows/TimeUtils.cpp index 7ef44d9c..c83f75d6 100644 --- a/src/plugins/7zip/7za/cpp/Windows/TimeUtils.cpp +++ b/src/plugins/7zip/7za/cpp/Windows/TimeUtils.cpp @@ -2,21 +2,28 @@ #include "StdAfx.h" -#include "Defs.h" +#ifndef _WIN32 +#include +#include +#endif + #include "TimeUtils.h" +#include "WinDefs.h" namespace NWindows { namespace NTime { static const UInt32 kNumTimeQuantumsInSecond = 10000000; -static const UInt32 kFileTimeStartYear = 1601; -static const UInt32 kDosTimeStartYear = 1980; -static const UInt32 kUnixTimeStartYear = 1970; +static const unsigned kFileTimeStartYear = 1601; +#if !defined(_WIN32) || defined(UNDER_CE) +static const unsigned kDosTimeStartYear = 1980; +#endif +static const unsigned kUnixTimeStartYear = 1970; static const UInt64 kUnixTimeOffset = - (UInt64)60 * 60 * 24 * (89 + 365 * (kUnixTimeStartYear - kFileTimeStartYear)); + (UInt64)60 * 60 * 24 * (89 + 365 * (UInt32)(kUnixTimeStartYear - kFileTimeStartYear)); static const UInt64 kNumSecondsInFileTime = (UInt64)(Int64)-1 / kNumTimeQuantumsInSecond; -bool DosTimeToFileTime(UInt32 dosTime, FILETIME &ft) throw() +bool DosTime_To_FileTime(UInt32 dosTime, FILETIME &ft) throw() { #if defined(_WIN32) && !defined(UNDER_CE) return BOOLToBool(::DosDateTimeToFileTime((UInt16)(dosTime >> 16), (UInt16)(dosTime & 0xFFFF), &ft)); @@ -24,8 +31,14 @@ bool DosTimeToFileTime(UInt32 dosTime, FILETIME &ft) throw() ft.dwLowDateTime = 0; ft.dwHighDateTime = 0; UInt64 res; - if (!GetSecondsSince1601(kDosTimeStartYear + (dosTime >> 25), (dosTime >> 21) & 0xF, (dosTime >> 16) & 0x1F, - (dosTime >> 11) & 0x1F, (dosTime >> 5) & 0x3F, (dosTime & 0x1F) * 2, res)) + if (!GetSecondsSince1601( + kDosTimeStartYear + (unsigned)(dosTime >> 25), + (unsigned)((dosTime >> 21) & 0xF), + (unsigned)((dosTime >> 16) & 0x1F), + (unsigned)((dosTime >> 11) & 0x1F), + (unsigned)((dosTime >> 5) & 0x3F), + (unsigned)((dosTime & 0x1F)) * 2, + res)) return false; res *= kNumTimeQuantumsInSecond; ft.dwLowDateTime = (UInt32)res; @@ -37,11 +50,7 @@ bool DosTimeToFileTime(UInt32 dosTime, FILETIME &ft) throw() static const UInt32 kHighDosTime = 0xFF9FBF7D; static const UInt32 kLowDosTime = 0x210000; -#define PERIOD_4 (4 * 365 + 1) -#define PERIOD_100 (PERIOD_4 * 25 - 1) -#define PERIOD_400 (PERIOD_100 * 4 + 1) - -bool FileTimeToDosTime(const FILETIME &ft, UInt32 &dosTime) throw() +bool FileTime_To_DosTime(const FILETIME &ft, UInt32 &dosTime) throw() { #if defined(_WIN32) && !defined(UNDER_CE) @@ -55,6 +64,10 @@ bool FileTimeToDosTime(const FILETIME &ft, UInt32 &dosTime) throw() #else +#define PERIOD_4 (4 * 365 + 1) +#define PERIOD_100 (PERIOD_4 * 25 - 1) +#define PERIOD_400 (PERIOD_100 * 4 + 1) + unsigned year, mon, day, hour, min, sec; UInt64 v64 = ft.dwLowDateTime | ((UInt64)ft.dwHighDateTime << 32); Byte ms[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; @@ -71,7 +84,7 @@ bool FileTimeToDosTime(const FILETIME &ft, UInt32 &dosTime) throw() v = (UInt32)v64; - year = (unsigned)(kFileTimeStartYear + v / PERIOD_400 * 400); + year = kFileTimeStartYear + (unsigned)(v / PERIOD_400 * 400); v %= PERIOD_400; temp = (unsigned)(v / PERIOD_100); @@ -110,44 +123,97 @@ bool FileTimeToDosTime(const FILETIME &ft, UInt32 &dosTime) throw() dosTime = kHighDosTime; if (year >= 128) return false; - dosTime = (year << 25) | (mon << 21) | (day << 16) | (hour << 11) | (min << 5) | (sec >> 1); + dosTime = + ((UInt32)year << 25) + | ((UInt32)mon << 21) + | ((UInt32)day << 16) + | ((UInt32)hour << 11) + | ((UInt32)min << 5) + | ((UInt32)sec >> 1); #endif return true; } -void UnixTimeToFileTime(UInt32 unixTime, FILETIME &ft) throw() + +bool UtcFileTime_To_LocalDosTime(const FILETIME &utc, UInt32 &dosTime) throw() { - UInt64 v = (kUnixTimeOffset + (UInt64)unixTime) * kNumTimeQuantumsInSecond; + FILETIME loc = { 0, 0 }; + const UInt64 u1 = FILETIME_To_UInt64(utc); + const UInt64 kDelta = ((UInt64)1 << 41); // it's larger than quantums in 1 sec. + if (u1 >= kDelta) + { + if (!FileTimeToLocalFileTime(&utc, &loc)) + loc = utc; + else + { + const UInt64 u2 = FILETIME_To_UInt64(loc); + const UInt64 delta = u1 < u2 ? (u2 - u1) : (u1 - u2); + if (delta > kDelta) // if FileTimeToLocalFileTime() overflow, we use UTC time + loc = utc; + } + } + return FileTime_To_DosTime(loc, dosTime); +} + +UInt64 UnixTime_To_FileTime64(UInt32 unixTime) throw() +{ + return (kUnixTimeOffset + (UInt64)unixTime) * kNumTimeQuantumsInSecond; +} + +void UnixTime_To_FileTime(UInt32 unixTime, FILETIME &ft) throw() +{ + const UInt64 v = UnixTime_To_FileTime64(unixTime); ft.dwLowDateTime = (DWORD)v; ft.dwHighDateTime = (DWORD)(v >> 32); } -bool UnixTime64ToFileTime(Int64 unixTime, FILETIME &ft) throw() +UInt64 UnixTime64_To_FileTime64(Int64 unixTime) throw() +{ + return (UInt64)((Int64)kUnixTimeOffset + unixTime) * kNumTimeQuantumsInSecond; +} + + +bool UnixTime64_To_FileTime64(Int64 unixTime, UInt64 &fileTime) throw() { - if (unixTime > kNumSecondsInFileTime - kUnixTimeOffset) + if (unixTime > (Int64)(kNumSecondsInFileTime - kUnixTimeOffset)) { - ft.dwLowDateTime = ft.dwHighDateTime = (UInt32)(Int32)-1; + fileTime = (UInt64)(Int64)-1; return false; } - Int64 v = (Int64)kUnixTimeOffset + unixTime; - if (v < 0) + if (unixTime < -(Int64)kUnixTimeOffset) { - ft.dwLowDateTime = ft.dwHighDateTime = 0; + fileTime = 0; return false; } - UInt64 v2 = (UInt64)v * kNumTimeQuantumsInSecond; - ft.dwLowDateTime = (DWORD)v2; - ft.dwHighDateTime = (DWORD)(v2 >> 32); + fileTime = UnixTime64_To_FileTime64(unixTime); return true; } -Int64 FileTimeToUnixTime64(const FILETIME &ft) throw() + +bool UnixTime64_To_FileTime(Int64 unixTime, FILETIME &ft) throw() { - UInt64 winTime = (((UInt64)ft.dwHighDateTime) << 32) + ft.dwLowDateTime; - return (Int64)(winTime / kNumTimeQuantumsInSecond) - kUnixTimeOffset; + UInt64 v; + const bool res = UnixTime64_To_FileTime64(unixTime, v); + ft.dwLowDateTime = (DWORD)v; + ft.dwHighDateTime = (DWORD)(v >> 32); + return res; +} + + +Int64 FileTime_To_UnixTime64(const FILETIME &ft) throw() +{ + const UInt64 winTime = (((UInt64)ft.dwHighDateTime) << 32) + ft.dwLowDateTime; + return (Int64)(winTime / kNumTimeQuantumsInSecond) - (Int64)kUnixTimeOffset; +} + +Int64 FileTime_To_UnixTime64_and_Quantums(const FILETIME &ft, UInt32 &quantums) throw() +{ + const UInt64 winTime = (((UInt64)ft.dwHighDateTime) << 32) + ft.dwLowDateTime; + quantums = (UInt32)(winTime % kNumTimeQuantumsInSecond); + return (Int64)(winTime / kNumTimeQuantumsInSecond) - (Int64)kUnixTimeOffset; } -bool FileTimeToUnixTime(const FILETIME &ft, UInt32 &unixTime) throw() +bool FileTime_To_UnixTime(const FILETIME &ft, UInt32 &unixTime) throw() { UInt64 winTime = (((UInt64)ft.dwHighDateTime) << 32) + ft.dwLowDateTime; winTime /= kNumTimeQuantumsInSecond; @@ -157,9 +223,9 @@ bool FileTimeToUnixTime(const FILETIME &ft, UInt32 &unixTime) throw() return false; } winTime -= kUnixTimeOffset; - if (winTime > 0xFFFFFFFF) + if (winTime > (UInt32)0xFFFFFFFF) { - unixTime = 0xFFFFFFFF; + unixTime = (UInt32)0xFFFFFFFF; return false; } unixTime = (UInt32)winTime; @@ -173,24 +239,41 @@ bool GetSecondsSince1601(unsigned year, unsigned month, unsigned day, if (year < kFileTimeStartYear || year >= 10000 || month < 1 || month > 12 || day < 1 || day > 31 || hour > 23 || min > 59 || sec > 59) return false; - UInt32 numYears = year - kFileTimeStartYear; - UInt32 numDays = numYears * 365 + numYears / 4 - numYears / 100 + numYears / 400; + const unsigned numYears = year - kFileTimeStartYear; + UInt32 numDays = (UInt32)((UInt32)numYears * 365 + numYears / 4 - numYears / 100 + numYears / 400); Byte ms[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; if (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) ms[1] = 29; month--; for (unsigned i = 0; i < month; i++) numDays += ms[i]; - numDays += day - 1; + numDays += (UInt32)(day - 1); resSeconds = ((UInt64)(numDays * 24 + hour) * 60 + min) * 60 + sec; return true; } -void GetCurUtcFileTime(FILETIME &ft) throw() + +/* docs: TIME_UTC is not defined on many platforms: + glibc 2.15, macOS 10.13 + FreeBSD 11.0, NetBSD 7.1, OpenBSD 6.0, + Minix 3.1.8, AIX 7.1, HP-UX 11.31, IRIX 6.5, Solaris 11.3, + Cygwin 2.9, mingw, MSVC 14, Android 9.0. + Android NDK defines TIME_UTC but doesn't have the timespec_get(). +*/ +#if defined(TIME_UTC) && !defined(__ANDROID__) +#define ZIP7_USE_timespec_get +// #pragma message("ZIP7_USE_timespec_get") +#elif defined(CLOCK_REALTIME) +#define ZIP7_USE_clock_gettime +// #pragma message("ZIP7_USE_clock_gettime") +#endif + +void GetCurUtc_FiTime(CFiTime &ft) throw() { + #ifdef _WIN32 + // Both variants provide same low resolution on WinXP: about 15 ms. // But GetSystemTimeAsFileTime is much faster. - #ifdef UNDER_CE SYSTEMTIME st; GetSystemTime(&st); @@ -198,6 +281,187 @@ void GetCurUtcFileTime(FILETIME &ft) throw() #else GetSystemTimeAsFileTime(&ft); #endif + + #else + + FiTime_Clear(ft); +#ifdef ZIP7_USE_timespec_get + timespec_get(&ft, TIME_UTC); +#elif defined ZIP7_USE_clock_gettime + +#if defined(_AIX) + { + timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + ft.tv_sec = ts.tv_sec; + ft.tv_nsec = ts.tv_nsec; + } +#else + clock_gettime(CLOCK_REALTIME, &ft); +#endif + +#else + struct timeval now; + if (gettimeofday(&now, NULL) == 0) + { + ft.tv_sec = now.tv_sec; + // timeval::tv_usec can be 64-bit signed in some cases + // timespec::tv_nsec can be 32-bit signed in some cases + ft.tv_nsec = + (Int32) // to eliminate compiler conversion error + (now.tv_usec * 1000); + } +#endif + + #endif +} + +#ifndef _WIN32 +void GetCurUtcFileTime(FILETIME &ft) throw() +{ + UInt64 v = 0; +#if defined(ZIP7_USE_timespec_get) || \ + defined(ZIP7_USE_clock_gettime) + timespec ts; +#if defined(ZIP7_USE_timespec_get) + if (timespec_get(&ts, TIME_UTC)) +#else + if (clock_gettime(CLOCK_REALTIME, &ts) == 0) +#endif + { + v = ((UInt64)ts.tv_sec + kUnixTimeOffset) * + kNumTimeQuantumsInSecond + (UInt64)ts.tv_nsec / 100; + } +#else + struct timeval now; + if (gettimeofday(&now, NULL) == 0) + { + v = ((UInt64)now.tv_sec + kUnixTimeOffset) * + kNumTimeQuantumsInSecond + (UInt64)now.tv_usec * 10; + } +#endif + ft.dwLowDateTime = (DWORD)v; + ft.dwHighDateTime = (DWORD)(v >> 32); } +#endif + }} + + +#ifdef _WIN32 + +/* +void FiTime_Normalize_With_Prec(CFiTime &ft, unsigned prec) +{ + if (prec == k_PropVar_TimePrec_0 + || prec == k_PropVar_TimePrec_HighPrec + || prec >= k_PropVar_TimePrec_100ns) + return; + UInt64 v = (((UInt64)ft.dwHighDateTime) << 32) + ft.dwLowDateTime; + + int numDigits = (int)prec - (int)k_PropVar_TimePrec_Base; + UInt32 d; + if (prec == k_PropVar_TimePrec_DOS) + { + // we round up as windows DosDateTimeToFileTime() + v += NWindows::NTime::kNumTimeQuantumsInSecond * 2 - 1; + d = NWindows::NTime::kNumTimeQuantumsInSecond * 2; + } + else + { + if (prec == k_PropVar_TimePrec_Unix) + numDigits = 0; + else if (numDigits < 0) + return; + d = 1; + for (unsigned k = numDigits; k < 7; k++) + d *= 10; + } + v /= d; + v *= d; + ft.dwLowDateTime = (DWORD)v; + ft.dwHighDateTime = (DWORD)(v >> 32); +} +*/ + +#else + +/* +void FiTime_Normalize_With_Prec(CFiTime &ft, unsigned prec) +{ + if (prec >= k_PropVar_TimePrec_1ns + || prec == k_PropVar_TimePrec_HighPrec) + return; + + int numDigits = (int)prec - (int)k_PropVar_TimePrec_Base; + UInt32 d; + if (prec == k_PropVar_TimePrec_Unix || + prec == (int)k_PropVar_TimePrec_Base) + { + ft.tv_nsec = 0; + return; + } + if (prec == k_PropVar_TimePrec_DOS) + { + // we round up as windows DosDateTimeToFileTime() + const unsigned sec1 = (ft.tv_sec & 1); + if (ft.tv_nsec == 0 && sec1 == 0) + return; + ft.tv_nsec = 0; + ft.tv_sec += 2 - sec1; + return; + } + { + if (prec == k_PropVar_TimePrec_0 + || numDigits < 0) + numDigits = 7; + d = 1; + for (unsigned k = numDigits; k < 9; k++) + d *= 10; + ft.tv_nsec /= d; + ft.tv_nsec *= d; + } +} +*/ + +int Compare_FiTime(const CFiTime *a1, const CFiTime *a2) +{ + if (a1->tv_sec < a2->tv_sec) return -1; + if (a1->tv_sec > a2->tv_sec) return 1; + if (a1->tv_nsec < a2->tv_nsec) return -1; + if (a1->tv_nsec > a2->tv_nsec) return 1; + return 0; +} + +bool FILETIME_To_timespec(const FILETIME &ft, CFiTime &ts) +{ + UInt32 quantums; + const Int64 sec = NWindows::NTime::FileTime_To_UnixTime64_and_Quantums(ft, quantums); + // time_t is long + const time_t sec2 = (time_t)sec; + if (sec2 == sec) + { + ts.tv_sec = sec2; + ts.tv_nsec = (Int32)(quantums * 100); + return true; + } + return false; +} + +void FiTime_To_FILETIME_ns100(const CFiTime &ts, FILETIME &ft, unsigned &ns100) +{ + const UInt64 v = NWindows::NTime::UnixTime64_To_FileTime64(ts.tv_sec) + ((UInt64)ts.tv_nsec / 100); + ns100 = (unsigned)((UInt64)ts.tv_nsec % 100); + ft.dwLowDateTime = (DWORD)v; + ft.dwHighDateTime = (DWORD)(v >> 32); +} + +void FiTime_To_FILETIME(const CFiTime &ts, FILETIME &ft) +{ + const UInt64 v = NWindows::NTime::UnixTime64_To_FileTime64(ts.tv_sec) + ((UInt64)ts.tv_nsec / 100); + ft.dwLowDateTime = (DWORD)v; + ft.dwHighDateTime = (DWORD)(v >> 32); +} + +#endif diff --git a/src/plugins/7zip/7za/cpp/Windows/TimeUtils.h b/src/plugins/7zip/7za/cpp/Windows/TimeUtils.h index d3033ede..8e1e4789 100644 --- a/src/plugins/7zip/7za/cpp/Windows/TimeUtils.h +++ b/src/plugins/7zip/7za/cpp/Windows/TimeUtils.h @@ -1,24 +1,154 @@ // Windows/TimeUtils.h -#ifndef __WINDOWS_TIME_UTILS_H -#define __WINDOWS_TIME_UTILS_H +#ifndef ZIP7_INC_WINDOWS_TIME_UTILS_H +#define ZIP7_INC_WINDOWS_TIME_UTILS_H #include "../Common/MyTypes.h" #include "../Common/MyWindows.h" +#include "PropVariant.h" + +inline UInt64 FILETIME_To_UInt64(const FILETIME &ft) +{ + return (((UInt64)ft.dwHighDateTime) << 32) + ft.dwLowDateTime; +} + +inline void FILETIME_Clear(FILETIME &ft) +{ + ft.dwLowDateTime = 0; + ft.dwHighDateTime = 0; +} + +inline bool FILETIME_IsZero(const FILETIME &ft) +{ + return (ft.dwHighDateTime == 0 && ft.dwLowDateTime == 0); +} + + +#ifdef _WIN32 + #define CFiTime FILETIME + #define Compare_FiTime ::CompareFileTime + inline void FiTime_To_FILETIME(const CFiTime &ts, FILETIME &ft) + { + ft = ts; + } + /* + inline void FILETIME_To_FiTime(const FILETIME &ft, CFiTime &ts) + { + ts = ft; + } + */ + inline void FiTime_Clear(CFiTime &ft) + { + ft.dwLowDateTime = 0; + ft.dwHighDateTime = 0; + } +#else + + #include + + #if defined(_AIX) + #define CFiTime st_timespec + #else + #define CFiTime timespec + #endif + int Compare_FiTime(const CFiTime *a1, const CFiTime *a2); + bool FILETIME_To_timespec(const FILETIME &ft, CFiTime &ts); + void FiTime_To_FILETIME(const CFiTime &ts, FILETIME &ft); + void FiTime_To_FILETIME_ns100(const CFiTime &ts, FILETIME &ft, unsigned &ns100); + inline void FiTime_Clear(CFiTime &ft) + { + ft.tv_sec = 0; + ft.tv_nsec = 0; + } + + #ifdef __APPLE__ + #define ST_MTIME(st) st.st_mtimespec + #define ST_ATIME(st) st.st_atimespec + #define ST_CTIME(st) st.st_ctimespec + #elif defined(__QNXNTO__) && defined(__ARM__) && !defined(__aarch64__) + // QNX armv7le (32-bit) for "struct stat" timestamps uses time_t instead of timespec + inline CFiTime ST_MTIME(const struct stat &st) + { timespec ts; ts.tv_sec = st.st_mtime; ts.tv_nsec = 0; return ts; } + inline CFiTime ST_ATIME(const struct stat &st) + { timespec ts; ts.tv_sec = st.st_atime; ts.tv_nsec = 0; return ts; } + inline CFiTime ST_CTIME(const struct stat &st) + { timespec ts; ts.tv_sec = st.st_ctime; ts.tv_nsec = 0; return ts; } + #else + #define ST_MTIME(st) st.st_mtim + #define ST_ATIME(st) st.st_atim + #define ST_CTIME(st) st.st_ctim + #endif + +#endif + +// void FiTime_Normalize_With_Prec(CFiTime &ft, unsigned prec); namespace NWindows { namespace NTime { -bool DosTimeToFileTime(UInt32 dosTime, FILETIME &fileTime) throw(); -bool FileTimeToDosTime(const FILETIME &fileTime, UInt32 &dosTime) throw(); -void UnixTimeToFileTime(UInt32 unixTime, FILETIME &fileTime) throw(); -bool UnixTime64ToFileTime(Int64 unixTime, FILETIME &fileTime) throw(); -bool FileTimeToUnixTime(const FILETIME &fileTime, UInt32 &unixTime) throw(); -Int64 FileTimeToUnixTime64(const FILETIME &ft) throw(); +bool DosTime_To_FileTime(UInt32 dosTime, FILETIME &fileTime) throw(); +bool UtcFileTime_To_LocalDosTime(const FILETIME &utc, UInt32 &dosTime) throw(); +bool FileTime_To_DosTime(const FILETIME &fileTime, UInt32 &dosTime) throw(); + +// UInt32 Unix Time : for dates 1970-2106 +UInt64 UnixTime_To_FileTime64(UInt32 unixTime) throw(); +void UnixTime_To_FileTime(UInt32 unixTime, FILETIME &fileTime) throw(); + +// Int64 Unix Time : negative values for dates before 1970 +UInt64 UnixTime64_To_FileTime64(Int64 unixTime) throw(); // no check +bool UnixTime64_To_FileTime64(Int64 unixTime, UInt64 &fileTime) throw(); +bool UnixTime64_To_FileTime(Int64 unixTime, FILETIME &fileTime) throw(); + +Int64 FileTime64_To_UnixTime64(UInt64 ft64) throw(); +bool FileTime_To_UnixTime(const FILETIME &fileTime, UInt32 &unixTime) throw(); +Int64 FileTime_To_UnixTime64(const FILETIME &ft) throw(); +Int64 FileTime_To_UnixTime64_and_Quantums(const FILETIME &ft, UInt32 &quantums) throw(); + bool GetSecondsSince1601(unsigned year, unsigned month, unsigned day, unsigned hour, unsigned min, unsigned sec, UInt64 &resSeconds) throw(); + +void GetCurUtc_FiTime(CFiTime &ft) throw(); +#ifdef _WIN32 +#define GetCurUtcFileTime GetCurUtc_FiTime +#else void GetCurUtcFileTime(FILETIME &ft) throw(); +#endif }} +inline void PropVariant_SetFrom_UnixTime(NWindows::NCOM::CPropVariant &prop, UInt32 unixTime) +{ + FILETIME ft; + NWindows::NTime::UnixTime_To_FileTime(unixTime, ft); + prop.SetAsTimeFrom_FT_Prec(ft, k_PropVar_TimePrec_Unix); +} + +inline void PropVariant_SetFrom_NtfsTime(NWindows::NCOM::CPropVariant &prop, const FILETIME &ft) +{ + prop.SetAsTimeFrom_FT_Prec(ft, k_PropVar_TimePrec_100ns); +} + +inline void PropVariant_SetFrom_FiTime(NWindows::NCOM::CPropVariant &prop, const CFiTime &fts) +{ + #ifdef _WIN32 + PropVariant_SetFrom_NtfsTime(prop, fts); + #else + unsigned ns100; + FILETIME ft; + FiTime_To_FILETIME_ns100(fts, ft, ns100); + prop.SetAsTimeFrom_FT_Prec_Ns100(ft, k_PropVar_TimePrec_1ns, ns100); + #endif +} + +inline bool PropVariant_SetFrom_DosTime(NWindows::NCOM::CPropVariant &prop, UInt32 dosTime) +{ + FILETIME localFileTime, utc; + if (!NWindows::NTime::DosTime_To_FileTime(dosTime, localFileTime)) + return false; + if (!LocalFileTimeToFileTime(&localFileTime, &utc)) + return false; + prop.SetAsTimeFrom_FT_Prec(utc, k_PropVar_TimePrec_DOS); + return true; +} + #endif diff --git a/src/plugins/7zip/7za/cpp/Windows/WinDefs.h b/src/plugins/7zip/7za/cpp/Windows/WinDefs.h new file mode 100644 index 00000000..86512d28 --- /dev/null +++ b/src/plugins/7zip/7za/cpp/Windows/WinDefs.h @@ -0,0 +1,17 @@ +// Windows/Defs.h + +#ifndef ZIP7_INC_WINDOWS_WIN_DEFS_H +#define ZIP7_INC_WINDOWS_WIN_DEFS_H + +#include "../Common/MyWindows.h" + +#ifdef _WIN32 +inline BOOL BoolToBOOL(bool v) { return (v ? TRUE: FALSE); } +#endif + +inline bool BOOLToBool(BOOL v) { return (v != FALSE); } + +inline VARIANT_BOOL BoolToVARIANT_BOOL(bool v) { return (v ? VARIANT_TRUE: VARIANT_FALSE); } +inline bool VARIANT_BOOLToBool(VARIANT_BOOL v) { return (v != VARIANT_FALSE); } + +#endif diff --git a/src/plugins/7zip/7za/cpp/Windows/Window.cpp b/src/plugins/7zip/7za/cpp/Windows/Window.cpp index 36585022..102c503a 100644 --- a/src/plugins/7zip/7za/cpp/Windows/Window.cpp +++ b/src/plugins/7zip/7za/cpp/Windows/Window.cpp @@ -111,15 +111,15 @@ bool MySetWindowText(HWND wnd, LPCWSTR s) } #endif -bool CWindow::GetText(CSysString &s) +bool CWindow::GetText(CSysString &s) const { s.Empty(); - int len = GetTextLength(); + unsigned len = (unsigned)GetTextLength(); if (len == 0) return (::GetLastError() == ERROR_SUCCESS); TCHAR *p = s.GetBuf(len); { - int len2 = GetText(p, len + 1); + const unsigned len2 = (unsigned)GetText(p, (int)(len + 1)); if (len > len2) len = len2; } @@ -130,17 +130,17 @@ bool CWindow::GetText(CSysString &s) } #ifndef _UNICODE -bool CWindow::GetText(UString &s) +bool CWindow::GetText(UString &s) const { if (g_IsNT) { s.Empty(); - int len = GetWindowTextLengthW(_window); + unsigned len = (unsigned)GetWindowTextLengthW(_window); if (len == 0) return (::GetLastError() == ERROR_SUCCESS); wchar_t *p = s.GetBuf(len); { - int len2 = GetWindowTextW(_window, p, len + 1); + const unsigned len2 = (unsigned)GetWindowTextW(_window, p, (int)(len + 1)); if (len > len2) len = len2; } @@ -150,7 +150,7 @@ bool CWindow::GetText(UString &s) return true; } CSysString sysString; - bool result = GetText(sysString); + const bool result = GetText(sysString); MultiByteToUnicodeString2(s, sysString); return result; } diff --git a/src/plugins/7zip/7za/cpp/Windows/Window.h b/src/plugins/7zip/7za/cpp/Windows/Window.h index 3bda6795..b0ddef9f 100644 --- a/src/plugins/7zip/7za/cpp/Windows/Window.h +++ b/src/plugins/7zip/7za/cpp/Windows/Window.h @@ -1,31 +1,108 @@ // Windows/Window.h -#ifndef __WINDOWS_WINDOW_H -#define __WINDOWS_WINDOW_H +#ifndef ZIP7_INC_WINDOWS_WINDOW_H +#define ZIP7_INC_WINDOWS_WINDOW_H #include "../Common/MyWindows.h" #include "../Common/MyString.h" -#include "Defs.h" +#include "WinDefs.h" #ifndef UNDER_CE +#ifdef WM_CHANGEUISTATE +#define Z7_WIN_WM_CHANGEUISTATE WM_CHANGEUISTATE +#define Z7_WIN_WM_UPDATEUISTATE WM_UPDATEUISTATE +#define Z7_WIN_WM_QUERYUISTATE WM_QUERYUISTATE +#else +// these are defined for (_WIN32_WINNT >= 0x0500): +#define Z7_WIN_WM_CHANGEUISTATE 0x0127 +#define Z7_WIN_WM_UPDATEUISTATE 0x0128 +#define Z7_WIN_WM_QUERYUISTATE 0x0129 +#endif + +#ifdef UIS_SET -#define MY__WM_CHANGEUISTATE 0x0127 -#define MY__WM_UPDATEUISTATE 0x0128 -#define MY__WM_QUERYUISTATE 0x0129 +#define Z7_WIN_UIS_SET UIS_SET +#define Z7_WIN_UIS_CLEAR UIS_CLEAR +#define Z7_WIN_UIS_INITIALIZE UIS_INITIALIZE +#define Z7_WIN_UISF_HIDEFOCUS UISF_HIDEFOCUS +#define Z7_WIN_UISF_HIDEACCEL UISF_HIDEACCEL + +#else +// these are defined for (_WIN32_WINNT >= 0x0500): // LOWORD(wParam) values in WM_*UISTATE -#define MY__UIS_SET 1 -#define MY__UIS_CLEAR 2 -#define MY__UIS_INITIALIZE 3 +#define Z7_WIN_UIS_SET 1 +#define Z7_WIN_UIS_CLEAR 2 +#define Z7_WIN_UIS_INITIALIZE 3 // HIWORD(wParam) values in WM_*UISTATE -#define MY__UISF_HIDEFOCUS 0x1 -#define MY__UISF_HIDEACCEL 0x2 -#define MY__UISF_ACTIVE 0x4 +#define Z7_WIN_UISF_HIDEFOCUS 0x1 +#define Z7_WIN_UISF_HIDEACCEL 0x2 +// defined for for (_WIN32_WINNT >= 0x0501): +// #define Z7_WIN_UISF_ACTIVE 0x4 #endif +#endif // UNDER_CE + + +#ifdef Z7_OLD_WIN_SDK + +// #define VK_OEM_1 0xBA // ';:' for US +#define VK_OEM_PLUS 0xBB // '+' any country +// #define VK_OEM_COMMA 0xBC // ',' any country +#define VK_OEM_MINUS 0xBD // '-' any country +// #define VK_OEM_PERIOD 0xBE // '.' any country +// #define VK_OEM_2 0xBF // '/?' for US +// #define VK_OEM_3 0xC0 // '`~' for US + +// #ifndef GWLP_USERDATA +#define GWLP_WNDPROC (-4) +#define GWLP_USERDATA (-21) +// #endif +#define DWLP_MSGRESULT 0 +// #define DWLP_DLGPROC DWLP_MSGRESULT + sizeof(LRESULT) +// #define DWLP_USER DWLP_DLGPROC + sizeof(DLGPROC) + +#define BTNS_BUTTON TBSTYLE_BUTTON // 0x0000 + +/* +vc6 defines INT_PTR via long: + typedef long INT_PTR, *PINT_PTR; + typedef unsigned long UINT_PTR, *PUINT_PTR; +but newer sdk (sdk2003+) defines INT_PTR via int: + typedef _W64 int INT_PTR, *PINT_PTR; + typedef _W64 unsigned int UINT_PTR, *PUINT_PTR; +*/ + +#define IS_INTRESOURCE(_r) (((ULONG_PTR)(_r) >> 16) == 0) + +#define GetWindowLongPtrA GetWindowLongA +#define GetWindowLongPtrW GetWindowLongW +#ifdef UNICODE +#define GetWindowLongPtr GetWindowLongPtrW +#else +#define GetWindowLongPtr GetWindowLongPtrA +#endif // !UNICODE + +#define SetWindowLongPtrA SetWindowLongA +#define SetWindowLongPtrW SetWindowLongW +#ifdef UNICODE +#define SetWindowLongPtr SetWindowLongPtrW +#else +#define SetWindowLongPtr SetWindowLongPtrA +#endif // !UNICODE + +#define ListView_SetCheckState(hwndLV, i, fCheck) \ + ListView_SetItemState(hwndLV, i, INDEXTOSTATEIMAGEMASK((fCheck)?2:1), LVIS_STATEIMAGEMASK) + +#endif // Z7_OLD_WIN_SDK + +inline bool LRESULTToBool(LRESULT v) { return (v != FALSE); } + +#define MY_int_TO_WPARAM(i) ((WPARAM)(INT_PTR)(i)) + namespace NWindows { inline ATOM MyRegisterClass(CONST WNDCLASS *wndClass) @@ -52,12 +129,13 @@ bool MySetWindowText(HWND wnd, LPCWSTR s); class CWindow { + Z7_CLASS_NO_COPY(CWindow) private: - // bool ModifyStyleBase(int styleOffset, DWORD remove, DWORD add, UINT flags); + // bool ModifyStyleBase(int styleOffset, DWORD remove, DWORD add, UINT flags); protected: HWND _window; public: - CWindow(HWND newWindow = NULL): _window(newWindow){}; + CWindow(HWND newWindow = NULL): _window(newWindow) {} CWindow& operator=(HWND newWindow) { _window = newWindow; @@ -171,9 +249,10 @@ class CWindow bool Update() { return BOOLToBool(::UpdateWindow(_window)); } bool InvalidateRect(LPCRECT rect, bool backgroundErase = true) { return BOOLToBool(::InvalidateRect(_window, rect, BoolToBOOL(backgroundErase))); } - void SetRedraw(bool redraw = true) { SendMsg(WM_SETREDRAW, BoolToBOOL(redraw), 0); } + void SetRedraw(bool redraw = true) { SendMsg(WM_SETREDRAW, (WPARAM)BoolToBOOL(redraw), 0); } LONG_PTR SetStyle(LONG_PTR style) { return SetLongPtr(GWL_STYLE, style); } + // LONG_PTR SetStyle(DWORD style) { return SetLongPtr(GWL_STYLE, (LONG_PTR)style); } LONG_PTR GetStyle() const { return GetLongPtr(GWL_STYLE); } // bool MyIsMaximized() const { return ((GetStyle() & WS_MAXIMIZE) != 0); } @@ -244,21 +323,21 @@ class CWindow int GetTextLength() const { return GetWindowTextLength(_window); } - UINT GetText(LPTSTR string, int maxCount) const + int GetText(LPTSTR string, int maxCount) const { return GetWindowText(_window, string, maxCount); } - bool GetText(CSysString &s); + bool GetText(CSysString &s) const; #ifndef _UNICODE /* UINT GetText(LPWSTR string, int maxCount) const { return GetWindowTextW(_window, string, maxCount); } */ - bool GetText(UString &s); + bool GetText(UString &s) const; #endif bool Enable(bool enable) { return BOOLToBool(::EnableWindow(_window, BoolToBOOL(enable))); } - bool IsEnabled() + bool IsEnabled() const { return BOOLToBool(::IsWindowEnabled(_window)); } #ifndef UNDER_CE @@ -266,7 +345,7 @@ class CWindow { return ::GetSystemMenu(_window, BoolToBOOL(revert)); } #endif - UINT_PTR SetTimer(UINT_PTR idEvent, UINT elapse, TIMERPROC timerFunc = 0) + UINT_PTR SetTimer(UINT_PTR idEvent, UINT elapse, TIMERPROC timerFunc = NULL) { return ::SetTimer(_window, idEvent, elapse, timerFunc); } bool KillTimer(UINT_PTR idEvent) {return BOOLToBool(::KillTimer(_window, idEvent)); } diff --git a/src/plugins/7zip/7zclient.cpp b/src/plugins/7zip/7zclient.cpp index 4a9fa694..d95b455e 100644 --- a/src/plugins/7zip/7zclient.cpp +++ b/src/plugins/7zip/7zclient.cpp @@ -5,7 +5,7 @@ #include #include "dbg.h" -#include "Common/MyInitGuid.h" +// 7zip.cpp is the single owner of 26.02 interface GUID definitions in this plug-in binary. #include "7zip.h" #include "7zclient.h" @@ -95,7 +95,8 @@ BOOL C7zClient::CreateObject(const GUID* interfaceID, void** object) if (!Load(dllPath)) return Error(IDS_CANT_LOAD_LIBRARY); - TCreateObjectFunc createObjectFunc = (TCreateObjectFunc)GetProc("CreateObject"); + // 26.02 exposes the native module handle instead of the retired CLibrary::GetProc helper. + TCreateObjectFunc createObjectFunc = (TCreateObjectFunc)::GetProcAddress(Get_HMODULE(), "CreateObject"); if (createObjectFunc == 0) return Error(IDS_CANT_GET_CRATEOBJECT); @@ -1015,6 +1016,7 @@ C7zClient::SetCompressionParams(IOutArchive* outArchive, CCompressParams* compre if (compressParams->CompressLevel != COMPRESS_LEVEL_STORE) { char dictSizeStr[32]; + // 26.02 reserves signed scalar variants; plug-in settings are non-negative by contract. NWindows::NCOM::CPropVariant prop; switch (compressParams->Method) { @@ -1030,7 +1032,7 @@ C7zClient::SetCompressionParams(IOutArchive* outArchive, CCompressParams* compre // set word size names.Add(L"0fb"); - prop = compressParams->WordSize; + prop = (UInt32)compressParams->WordSize; values.push_back(prop); break; @@ -1046,7 +1048,7 @@ C7zClient::SetCompressionParams(IOutArchive* outArchive, CCompressParams* compre // set word size names.Add(L"0fb"); - prop = compressParams->WordSize; + prop = (UInt32)compressParams->WordSize; values.push_back(prop); break; @@ -1062,13 +1064,13 @@ C7zClient::SetCompressionParams(IOutArchive* outArchive, CCompressParams* compre // set word size names.Add(L"0o"); - prop = compressParams->WordSize; + prop = (UInt32)compressParams->WordSize; values.push_back(prop); break; } // switch } - RINOK(setProperties->SetProperties(&names.Front(), &values.front(), names.Size())); + RINOK(setProperties->SetProperties(&names[0], &values.front(), names.Size())); } return S_OK; diff --git a/src/plugins/7zip/7zclient.h b/src/plugins/7zip/7zclient.h index 7ea77ead..00030bb1 100644 --- a/src/plugins/7zip/7zclient.h +++ b/src/plugins/7zip/7zclient.h @@ -54,9 +54,10 @@ struct CArchiveItemInfo const CFileData* FileData; bool IsDir; - CArchiveItemInfo(CSysString name, const CFileData* fd, bool isDir) + // 26.02 makes AString construction explicit, so archive paths enter through this ANSI boundary deliberately. + CArchiveItemInfo(const char* name, const CFileData* fd, bool isDir) { - NameInArchive = name; + NameInArchive = CSysString(name); FileData = fd; IsDir = isDir; } diff --git a/src/plugins/7zip/7zthreads.cpp b/src/plugins/7zip/7zthreads.cpp index 893c8f1b..d63f9f96 100644 --- a/src/plugins/7zip/7zthreads.cpp +++ b/src/plugins/7zip/7zthreads.cpp @@ -11,26 +11,186 @@ #include "7zip.rh" #include "7zip.rh2" #include "lang\lang.rh" +#include "..\shared\plugin_thread_owner.h" WNDPROC OldProgressDlgProc; CSalamanderForOperationsAbstract* Salamander; +// The completion notification is private to the temporary progress-dialog +// subclass, so it cannot collide with the host's progress-dialog protocol. +const WPARAM WM_7ZIP_TASKCOMPLETE = WM_7ZIP_PASSWORD + 1; +const DWORD SEVEN_ZIP_TASK_PUMP_WAIT = 250; +const DWORD SEVEN_ZIP_TASK_CANCEL_DEADLINE = 5000; + +class C7ZipTaskOperation; +static C7ZipTaskOperation* Active7ZipTaskOperation = NULL; + +struct C7ZipTaskCompletion +{ + C7ZipTaskOperation* Operation; + HRESULT Result; +}; + +// This object owns the worker and retains the caller's arguments until the +// UI has consumed the posted result, preventing a late archive callback from +// observing state released by LaunchAndDo7ZipTask. +class C7ZipTaskOperation +{ +public: + C7ZipTaskOperation(HWND progressWindow, LPTHREAD_START_ROUTINE threadProc, LPVOID arguments) + : ProgressWindow(progressWindow), ThreadProc(threadProc), Arguments(arguments), + CancellationRequested(FALSE), CompletionDelivered(FALSE), CompletionPostFailed(FALSE), + CancellationDeadlineReported(FALSE), CancellationStartedAt(0), Result(E_FAIL) + { + } + + BOOL Start() + { + return Worker.Start(WorkerProc, this, "7-Zip archive task", NULL, 0); + } + + void RequestCancellation() + { + if (InterlockedExchange(&CancellationRequested, TRUE) == FALSE) + { + CancellationStartedAt = GetTickCount64(); + Worker.RequestStop(); + } + } + + BOOL IsCancellationRequested() const + { + return InterlockedCompareExchange((volatile LONG*)&CancellationRequested, FALSE, FALSE) != FALSE; + } + + void DeliverCompletion(HRESULT result) + { + Result = result; + InterlockedExchange(&CompletionDelivered, TRUE); + } + + BOOL HasCompletion() const + { + return InterlockedCompareExchange((volatile LONG*)&CompletionDelivered, FALSE, FALSE) != FALSE; + } + + BOOL WorkerFinished() const + { + return Worker.WaitForCompletion(0) != WAIT_TIMEOUT; + } + + HRESULT GetResult() const + { + return Result; + } + + BOOL CompletionCouldNotBePosted() const + { + return InterlockedCompareExchange((volatile LONG*)&CompletionPostFailed, FALSE, FALSE) != FALSE; + } + + void ReportCancellationDeadline() + { + if (!IsCancellationRequested() || + GetTickCount64() - CancellationStartedAt < SEVEN_ZIP_TASK_CANCEL_DEADLINE || + InterlockedExchange(&CancellationDeadlineReported, TRUE) != FALSE) + { + return; + } + + TRACE_E("7-Zip archive task did not stop within " << SEVEN_ZIP_TASK_CANCEL_DEADLINE + << " ms; continuing to pump until its owned completion arrives."); + } + +private: + static DWORD WINAPI WorkerProc(void* parameter, HANDLE stopEvent) + { + C7ZipTaskOperation* operation = (C7ZipTaskOperation*)parameter; + HRESULT result = E_ABORT; + + if (WaitForSingleObject(stopEvent, 0) != WAIT_OBJECT_0) + { + try + { + result = (HRESULT)operation->ThreadProc(operation->Arguments); + } + catch (...) + { + result = E_UNEXPECTED; + } + } + if (WaitForSingleObject(stopEvent, 0) == WAIT_OBJECT_0 && SUCCEEDED(result)) + result = E_ABORT; + + operation->Result = result; + C7ZipTaskCompletion* completion = new C7ZipTaskCompletion; + if (completion == NULL) + { + InterlockedExchange(&operation->CompletionPostFailed, TRUE); + return result; + } + + completion->Operation = operation; + completion->Result = result; + if (!PostMessage(operation->ProgressWindow, WM_7ZIP, WM_7ZIP_TASKCOMPLETE, (LPARAM)completion)) + { + delete completion; + InterlockedExchange(&operation->CompletionPostFailed, TRUE); + } + return result; + } + +private: + HWND ProgressWindow; + LPTHREAD_START_ROUTINE ThreadProc; + LPVOID Arguments; + CPluginThreadOwner Worker; + volatile LONG CancellationRequested; + volatile LONG CompletionDelivered; + volatile LONG CompletionPostFailed; + volatile LONG CancellationDeadlineReported; + ULONGLONG CancellationStartedAt; + HRESULT Result; +}; + BOOL CALLBACK SubClassedProgressDlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + if (uMsg == WM_COMMAND && LOWORD(wParam) == IDCANCEL && Active7ZipTaskOperation != NULL) + { + // Preserve the dialog's cancel behavior while also waking the owned worker. + Active7ZipTaskOperation->RequestCancellation(); + } + if (WM_7ZIP == uMsg) { switch (wParam) { case WM_7ZIP_PROGRESS: + if (Active7ZipTaskOperation != NULL && Active7ZipTaskOperation->IsCancellationRequested()) + return E_ABORT; if (!Salamander->ProgressSetSize(*(CQuadWord*)lParam, CQuadWord(-1, -1), TRUE)) { // Canceled by the user + if (Active7ZipTaskOperation != NULL) + Active7ZipTaskOperation->RequestCancellation(); Salamander->ProgressDialogAddText(LoadStr(IDS_CANCELING_OPERATION), FALSE); Salamander->ProgressEnableCancel(FALSE); return E_ABORT; } return S_OK; + case WM_7ZIP_TASKCOMPLETE: + { + C7ZipTaskCompletion* completion = (C7ZipTaskCompletion*)lParam; + if (completion != NULL) + { + if (completion->Operation == Active7ZipTaskOperation) + Active7ZipTaskOperation->DeliverCompletion(completion->Result); + delete completion; + } + return S_OK; + } + case WM_7ZIP_SETTOTAL: Salamander->ProgressSetTotalSize(*(CQuadWord*)lParam, CQuadWord(-1, -1)); return S_OK; @@ -84,6 +244,59 @@ BOOL CALLBACK SubClassedProgressDlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPA return (BOOL)CallWindowProc(OldProgressDlgProc, hWnd, uMsg, wParam, lParam); } +// Restoring the original procedure is mandatory even when worker startup or a +// later wait fails; otherwise subsequent progress dialogs call a stale hook. +class CProgressDialogSubclassScope +{ +public: + CProgressDialogSubclassScope(HWND window, C7ZipTaskOperation* operation) + : Window(window), Operation(operation), PreviousProcedure(NULL), SavedOldProcedure(NULL), + PreviousOperation(NULL), Installed(FALSE) + { + } + + BOOL Install() + { + SetLastError(ERROR_SUCCESS); + PreviousProcedure = (WNDPROC)GetWindowLongPtr(Window, GWLP_WNDPROC); + if (PreviousProcedure == NULL && GetLastError() != ERROR_SUCCESS) + return FALSE; + + SavedOldProcedure = OldProgressDlgProc; + PreviousOperation = Active7ZipTaskOperation; + OldProgressDlgProc = PreviousProcedure; + Active7ZipTaskOperation = Operation; + + SetLastError(ERROR_SUCCESS); + SetWindowLongPtr(Window, GWLP_WNDPROC, (LONG_PTR)SubClassedProgressDlgProc); + if (GetLastError() != ERROR_SUCCESS) + { + OldProgressDlgProc = SavedOldProcedure; + Active7ZipTaskOperation = PreviousOperation; + return FALSE; + } + + Installed = TRUE; + return TRUE; + } + + ~CProgressDialogSubclassScope() + { + if (Installed && IsWindow(Window)) + SetWindowLongPtr(Window, GWLP_WNDPROC, (LONG_PTR)PreviousProcedure); + Active7ZipTaskOperation = PreviousOperation; + OldProgressDlgProc = SavedOldProcedure; + } + +private: + HWND Window; + C7ZipTaskOperation* Operation; + WNDPROC PreviousProcedure; + WNDPROC SavedOldProcedure; + C7ZipTaskOperation* PreviousOperation; + BOOL Installed; +}; + // // worker threads // @@ -104,40 +317,43 @@ DWORD WINAPI DecompressThreadProc(LPVOID lpParameter) HRESULT LaunchAndDo7ZipTask(LPTHREAD_START_ROUTINE threadProc, LPVOID args) { - DWORD threadId; - HANDLE hThread; + HWND progressWindow = Salamander->ProgressGetHWND(); + C7ZipTaskOperation operation(progressWindow, threadProc, args); + CProgressDialogSubclassScope subclassScope(progressWindow, &operation); - OldProgressDlgProc = (WNDPROC)GetWindowLongPtr(Salamander->ProgressGetHWND(), GWLP_WNDPROC); - SetWindowLongPtr(Salamander->ProgressGetHWND(), GWLP_WNDPROC, (LONG_PTR)SubClassedProgressDlgProc); - - hThread = ::CreateThread(NULL, 0, threadProc, args, 0, &threadId); - - if (!hThread) - { + if (!subclassScope.Install()) + return GetLastError(); + if (!operation.Start()) return GetLastError(); - } - for (;;) + // Do not release the caller's archive arguments until both the UI payload + // and the owner's completion signal confirm that no worker can use them. + while (!operation.HasCompletion() || !operation.WorkerFinished()) { - if (WAIT_OBJECT_0 == MsgWaitForMultipleObjects(1, &hThread, FALSE, INFINITE, QS_ALLEVENTS | QS_SENDMESSAGE)) - { - // Thread exited - DWORD exitCode; - - GetExitCodeThread(hThread, &exitCode); - CloseHandle(hThread); - return exitCode; // Our thread body func returns HRESULT - } + DWORD waitResult = MsgWaitForMultipleObjects(0, NULL, FALSE, SEVEN_ZIP_TASK_PUMP_WAIT, + QS_ALLEVENTS | QS_SENDMESSAGE); + if (waitResult == WAIT_FAILED) + return HRESULT_FROM_WIN32(GetLastError()); MSG msg; - while (PeekMessage(&msg, SalamanderGeneral->GetMainWindowHWND(), 0, 0, PM_REMOVE)) + while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } + + if (!operation.HasCompletion() && operation.WorkerFinished() && operation.CompletionCouldNotBePosted()) + { + // A destroyed dialog cannot accept the payload, but the owner has + // already retained the final result and safe worker lifetime. + TRACE_E("7-Zip archive task could not post its completion to the progress dialog."); + return operation.GetResult(); + } + + operation.ReportCancellationDeadline(); } - return S_OK; + return operation.GetResult(); } HRESULT DoDecompress(CSalamanderForOperationsAbstract* salamander, CDecompressParamObject* dpo) diff --git a/src/plugins/7zip/FStreams.cpp b/src/plugins/7zip/FStreams.cpp index eec35501..21f836fe 100644 --- a/src/plugins/7zip/FStreams.cpp +++ b/src/plugins/7zip/FStreams.cpp @@ -64,18 +64,59 @@ MENU_TEMPLATE_ITEM MsgBoxButtons[] = } } -CRetryableOutFileStream::CRetryableOutFileStream(HWND _hParentWnd) : hParentWnd(_hParentWnd) +CRetryableOutFileStream::CRetryableOutFileStream(HWND _hParentWnd) : hParentWnd(_hParentWnd), FileStream(NULL) { } +// Keep retry handling at the host boundary because 26.02's concrete stream is intentionally final. +bool CRetryableOutFileStream::Open(CFSTR fileName, DWORD creationDisposition) +{ + COutFileStream* fileStream = new COutFileStream; + if (fileStream == NULL) + return false; + + CMyComPtr stream(fileStream); + bool opened = false; + switch (creationDisposition) + { + case OPEN_EXISTING: + opened = fileStream->Open_EXISTING(fileName); + break; + case OPEN_ALWAYS: + opened = fileStream->Create_ALWAYS_or_Open_ALWAYS(fileName, false); + break; + case CREATE_ALWAYS: + opened = fileStream->Create_ALWAYS(fileName); + break; + case CREATE_NEW: + opened = fileStream->Create_NEW(fileName); + break; + default: + return false; + } + + if (!opened) + return false; + + FileStream = fileStream; + Stream = stream; + return true; +} + +bool CRetryableOutFileStream::SetMTime(const CFiTime* mTime) +{ + return FileStream != NULL && FileStream->SetMTime(mTime); +} + STDMETHODIMP CRetryableOutFileStream::Write(const void* data, UInt32 size, UInt32* processedSize) { - UInt32 written; + // A failed low-level write can report no progress, so keep the retry offset deterministic. + UInt32 written = 0; HRESULT ret; if (processedSize) *processedSize = 0; - while ((S_OK != (ret = COutFileStream::Write(data, size, &written))) /*|| (size != written)*/) + while ((S_OK != (ret = Stream->Write(data, size, &written))) /*|| (size != written)*/) { // NOTE: COutFileStream::Write writes at most kChunkSizeMax bytes (4MB) at once data = (char*)data + written; @@ -94,18 +135,44 @@ STDMETHODIMP CRetryableOutFileStream::Write(const void* data, UInt32 size, UInt3 return ret; } +STDMETHODIMP CRetryableOutFileStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64* newPosition) +{ + return Stream->Seek(offset, seekOrigin, newPosition); +} + +STDMETHODIMP CRetryableOutFileStream::SetSize(UInt64 newSize) +{ + return Stream->SetSize(newSize); +} + CRetryableInFileStream::CRetryableInFileStream(HWND _hParentWnd) : hParentWnd(_hParentWnd) { } +// Own the final 26.02 reader through its COM interface while preserving the host retry prompt. +bool CRetryableInFileStream::Open(CFSTR fileName) +{ + CInFileStream* fileStream = new CInFileStream; + if (fileStream == NULL) + return false; + + CMyComPtr stream(fileStream); + if (!fileStream->Open(fileName)) + return false; + + Stream = stream; + return true; +} + STDMETHODIMP CRetryableInFileStream::Read(void* data, UInt32 size, UInt32* processedSize) { - UInt32 read; + // A failed low-level read can report no progress, so keep the retry offset deterministic. + UInt32 read = 0; HRESULT ret; if (processedSize) *processedSize = 0; - while (S_OK != (ret = CInFileStream::Read(data, size, &read))) + while (S_OK != (ret = Stream->Read(data, size, &read))) { data = (char*)data + read; size -= read; @@ -122,3 +189,8 @@ STDMETHODIMP CRetryableInFileStream::Read(void* data, UInt32 size, UInt32* proce return ret; } + +STDMETHODIMP CRetryableInFileStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64* newPosition) +{ + return Stream->Seek(offset, seekOrigin, newPosition); +} diff --git a/src/plugins/7zip/FStreams.h b/src/plugins/7zip/FStreams.h index fbf23a15..8c2a4dc9 100644 --- a/src/plugins/7zip/FStreams.h +++ b/src/plugins/7zip/FStreams.h @@ -5,30 +5,38 @@ #include "7za/CPP/7zip/Common/FileStreams.h" -class CRetryableOutFileStream : public COutFileStream +class CRetryableOutFileStream : public IOutStream, + public CMyUnknownImp { public: - CRetryableOutFileStream(HWND hParentWnd); + // Composition retains retry prompts while 26.02 keeps its concrete file streams final. + Z7_IFACES_IMP_UNK_2(ISequentialOutStream, IOutStream) - virtual ~CRetryableOutFileStream() {} +public: + CRetryableOutFileStream(HWND hParentWnd); - STDMETHOD(Write) - (const void* data, UInt32 size, UInt32* processedSize); + bool Open(CFSTR fileName, DWORD creationDisposition); + bool SetMTime(const CFiTime* mTime); private: HWND hParentWnd; + COutFileStream* FileStream; + CMyComPtr Stream; }; -class CRetryableInFileStream : public CInFileStream +class CRetryableInFileStream : public IInStream, + public CMyUnknownImp { public: - CRetryableInFileStream(HWND hParentWnd); + // The input adapter follows the same composition rule so retry behavior remains available to archive parsing. + Z7_IFACES_IMP_UNK_2(ISequentialInStream, IInStream) - virtual ~CRetryableInFileStream() {} +public: + CRetryableInFileStream(HWND hParentWnd); - STDMETHOD(Read) - (void* data, UInt32 size, UInt32* processedSize); + bool Open(CFSTR fileName); private: HWND hParentWnd; + CMyComPtr Stream; }; diff --git a/src/plugins/7zip/doc/upgrade-26.02.md b/src/plugins/7zip/doc/upgrade-26.02.md new file mode 100644 index 00000000..df6f2a79 --- /dev/null +++ b/src/plugins/7zip/doc/upgrade-26.02.md @@ -0,0 +1,20 @@ +# 7-Zip 26.02 vendor upgrade + +The `7za` source tree was replaced from upstream 7-Zip 26.02, released 2026-06-25. +The imported source archive is `7z2602-src.7z` from +`https://github.com/ip7z/7zip/releases/download/26.02/7z2602-src.7z` with SHA-256 +`C7502DD4557481F52CCF1B3E680329F1FDD207E79A25544AFEB3106325474944`. + +Open Salamander keeps three compatibility seams: the ANSI file-name boundary, the +two worker-thread crash-stack hooks, and the `7zwrapper` `CompressFiles` export used +by crash-report packaging. The old corrupt-output ordering patch was deliberately +not reapplied because upstream's modern extraction path already closes/flushed +corrupted output before reporting the operation result. + +Before a future 7-Zip source update, run the focused `NativeSafetyRegressionTests` +case and build the `7za.dll` and `7zwrapper` projects. Extend the checked corpus with +valid 7z, encrypted 7z, SFX, and truncated-header samples; compare item names, sizes, +methods, and extraction hashes against the prior release. Preserve minimized malformed +header, invalid-folder, and corrupt-stream fuzz regressions, asserting a clean failure +without output outside the selected destination. Record extraction snapshots for each +sample before making a new engine default. diff --git a/src/plugins/7zip/extract.h b/src/plugins/7zip/extract.h index 23211f02..a861c490 100644 --- a/src/plugins/7zip/extract.h +++ b/src/plugins/7zip/extract.h @@ -34,25 +34,12 @@ class CExtractCallbackImp : public IArchiveExtractCallback, public CMyUnknownImp { public: - MY_UNKNOWN_IMP1(ICryptoGetTextPassword) - - // IProgress - STDMETHOD(SetTotal) - (UINT64 size); - STDMETHOD(SetCompleted) - (const UINT64* completeValue); - - // IExtractCallback200 - STDMETHOD(GetStream) - (UINT32 index, ISequentialOutStream** outStream, INT32 askExtractMode); - STDMETHOD(PrepareOperation) - (INT32 askExtractMode); - STDMETHOD(SetOperationResult) - (INT32 resultEOperationResult); - - // ICryptoGetTextPassword - STDMETHOD(CryptoGetTextPassword) - (BSTR* password); + // Keep callback ABI declarations generated from the 26.02 interfaces, including inherited IProgress. + Z7_IFACES_IMP_UNK_2(IArchiveExtractCallback, + ICryptoGetTextPassword) + Z7_IFACE_COM7_IMP(IProgress) + +public: private: CQuadWord Total; diff --git a/src/plugins/7zip/open.h b/src/plugins/7zip/open.h index 6804d627..a271eb96 100644 --- a/src/plugins/7zip/open.h +++ b/src/plugins/7zip/open.h @@ -15,23 +15,12 @@ class CArchiveOpenCallbackImp : public IArchiveOpenCallback, public CMyUnknownImp { public: - MY_UNKNOWN_IMP1(ICryptoGetTextPassword) + // Use 26.02's COM declarations so the plug-in advertises each legacy callback interface correctly. + Z7_IFACES_IMP_UNK_3(IArchiveOpenCallback, + IArchiveOpenVolumeCallback, + ICryptoGetTextPassword) - // IArchiveOpenCallback - STDMETHOD(SetTotal) - (const UInt64* files, const UInt64* bytes); - STDMETHOD(SetCompleted) - (const UInt64* files, const UInt64* bytes); - - // ICryptoGetTextPassword - STDMETHOD(CryptoGetTextPassword) - (BSTR* password); - - // IArchiveOpenVolumeCallback - STDMETHOD(GetProperty) - (PROPID propID, PROPVARIANT* value); - STDMETHOD(GetStream) - (const wchar_t* name, IInStream** inStream); +public: private: UString& Password; diff --git a/src/plugins/7zip/update.cpp b/src/plugins/7zip/update.cpp index 8f4d3fef..acc08fe3 100644 --- a/src/plugins/7zip/update.cpp +++ b/src/plugins/7zip/update.cpp @@ -54,11 +54,6 @@ STDMETHODIMP CArchiveUpdateCallback::SetCompleted(const UInt64* completeValue) return S_OK; } -STDMETHODIMP CArchiveUpdateCallback::EnumProperties(IEnumSTATPROPSTG** enumerator) -{ - return E_NOTIMPL; -} - STDMETHODIMP CArchiveUpdateCallback::GetUpdateItemInfo(UInt32 index, Int32* newData, Int32* newProperties, UInt32* indexInArchive) { diff --git a/src/plugins/7zip/update.h b/src/plugins/7zip/update.h index ad7bd483..07f9575f 100644 --- a/src/plugins/7zip/update.h +++ b/src/plugins/7zip/update.h @@ -106,36 +106,13 @@ class CArchiveUpdateCallback : public IArchiveUpdateCallback2, public CMyUnknownImp { public: - MY_UNKNOWN_IMP2(IArchiveUpdateCallback2, - ICryptoGetTextPassword2) - - // IProgress - STDMETHOD(SetTotal) - (UInt64 size); - STDMETHOD(SetCompleted) - (const UInt64* completeValue); - - // IUpdateCallback - STDMETHOD(EnumProperties) - (IEnumSTATPROPSTG** enumerator); - STDMETHOD(GetUpdateItemInfo) - (UInt32 index, - Int32* newData, Int32* newProperties, UInt32* indexInArchive); - STDMETHOD(GetProperty) - (UInt32 index, PROPID propID, PROPVARIANT* value); - STDMETHOD(GetStream) - (UInt32 index, ISequentialInStream** inStream); - STDMETHOD(SetOperationResult) - (Int32 operationResult); - - STDMETHOD(GetVolumeSize) - (UInt32 index, UInt64* size); - STDMETHOD(GetVolumeStream) - (UInt32 index, ISequentialOutStream** volumeStream); - - // ICryptoGetTextPassword2 - STDMETHOD(CryptoGetTextPassword2) - (Int32* passwordIsDefined, BSTR* password); + // Match 26.02's callback hierarchy; EnumProperties was removed from this supported interface. + Z7_IFACES_IMP_UNK_2(IArchiveUpdateCallback2, + ICryptoGetTextPassword2) + Z7_IFACE_COM7_IMP(IProgress) + Z7_IFACE_COM7_IMP(IArchiveUpdateCallback) + +public: AString GetProcessedFile() const { return ProcessedFileName; } CQuadWord& GetTotalSize() { return Total; } diff --git a/src/plugins/7zip/vcxproj/7ZA/7za.dll.vcxproj b/src/plugins/7zip/vcxproj/7ZA/7za.dll.vcxproj index a777892f..04ef5a7b 100644 --- a/src/plugins/7zip/vcxproj/7ZA/7za.dll.vcxproj +++ b/src/plugins/7zip/vcxproj/7ZA/7za.dll.vcxproj @@ -154,6 +154,9 @@ + + + @@ -436,6 +439,9 @@ + + + @@ -464,12 +470,18 @@ + + + + + + @@ -670,8 +682,13 @@ + + + + + @@ -686,8 +703,12 @@ + + + + @@ -700,8 +721,18 @@ + + + + + + + + + + @@ -718,6 +749,10 @@ + + + + @@ -1277,4 +1312,4 @@ - \ No newline at end of file + diff --git a/src/plugins/7zip/vcxproj/7zip.vcxproj b/src/plugins/7zip/vcxproj/7zip.vcxproj index d94ab23c..874917b2 100644 --- a/src/plugins/7zip/vcxproj/7zip.vcxproj +++ b/src/plugins/7zip/vcxproj/7zip.vcxproj @@ -199,6 +199,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -342,4 +386,4 @@ - \ No newline at end of file + diff --git a/src/plugins/7zip/vcxproj/7zwrapper/7zwrapper.vcxproj b/src/plugins/7zip/vcxproj/7zwrapper/7zwrapper.vcxproj index c746a213..baab4863 100644 --- a/src/plugins/7zip/vcxproj/7zwrapper/7zwrapper.vcxproj +++ b/src/plugins/7zip/vcxproj/7zwrapper/7zwrapper.vcxproj @@ -149,6 +149,9 @@ + + + @@ -186,4 +189,4 @@ - \ No newline at end of file + diff --git a/src/plugins/automation/scriptlist.cpp b/src/plugins/automation/scriptlist.cpp index 4f35ab0f..c866843e 100644 --- a/src/plugins/automation/scriptlist.cpp +++ b/src/plugins/automation/scriptlist.cpp @@ -12,6 +12,7 @@ */ #include "precomp.h" +#include "../../common/scoped_native_resources.h" #include "scriptlist.h" #include "scriptsite.h" #include "aututils.h" @@ -289,7 +290,10 @@ HRESULT CScriptInfo::LoadOleStringFromFile(PCTSTR pszFileName, __out LPOLESTR& s return hr; } - pszCodeA = (char*)MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0); + // The mapped source is local conversion input, so no script-load return + // path may retain its view after the caller receives its owned text. + CScopedMappingView codeView(MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0)); + pszCodeA = (char*)codeView.Get(); hr = HRESULT_FROM_WIN32(GetLastError()); CloseHandle(hMapping); if (pszCodeA == NULL) @@ -301,20 +305,17 @@ HRESULT CScriptInfo::LoadOleStringFromFile(PCTSTR pszFileName, __out LPOLESTR& s if (cchRequired <= 0) { hr = HRESULT_FROM_WIN32(GetLastError()); - UnmapViewOfFile(pszCodeA); return hr; } s = (LPOLESTR)malloc((cchRequired + 1) * sizeof(WCHAR)); if (s == NULL) { - UnmapViewOfFile(pszCodeA); return E_OUTOFMEMORY; } cchConverted = MultiByteToWideChar(CP_ACP, 0, pszCodeA, cbSize, s, cchRequired); hr = HRESULT_FROM_WIN32(GetLastError()); - UnmapViewOfFile(pszCodeA); if (cchConverted <= 0) { free(s); diff --git a/src/plugins/automation/vcxproj/automation.vcxproj b/src/plugins/automation/vcxproj/automation.vcxproj index 80afddef..af58a6a0 100644 --- a/src/plugins/automation/vcxproj/automation.vcxproj +++ b/src/plugins/automation/vcxproj/automation.vcxproj @@ -286,6 +286,8 @@ + + @@ -333,4 +335,4 @@ - \ No newline at end of file + diff --git a/src/plugins/checkver/checkver.cpp b/src/plugins/checkver/checkver.cpp index 837bfe3d..88ee1052 100644 --- a/src/plugins/checkver/checkver.cpp +++ b/src/plugins/checkver/checkver.cpp @@ -222,6 +222,7 @@ BOOL CPluginInterface::Release(HWND parent, BOOL force) { IncMainDialogID(); // detach the running session - it will not send anything else and // will finish as soon as possible + CancelDownloadThread(); // release WinINet waits before the plug-in can unload CloseHandle(HDownloadThread); HDownloadThread = NULL; ModulesCleanup(); diff --git a/src/plugins/checkver/checkver.h b/src/plugins/checkver/checkver.h index f584e7d8..ea2e79df 100644 --- a/src/plugins/checkver/checkver.h +++ b/src/plugins/checkver/checkver.h @@ -133,6 +133,7 @@ void GetFutureTime(SYSTEMTIME* tgtTime, DWORD days); // script loading BOOL LoadScripDataFromFile(const char* fileName); HANDLE StartDownloadThread(BOOL firstLoadAfterInstall); // returns the thread handle or NULL +void CancelDownloadThread(); // signals the download token and closes the active WinINet handle // log output void ModulesCreateLog(BOOL* moduleWasFound, BOOL rereadModules); diff --git a/src/plugins/checkver/data.cpp b/src/plugins/checkver/data.cpp index de8343fb..b5ed4e83 100644 --- a/src/plugins/checkver/data.cpp +++ b/src/plugins/checkver/data.cpp @@ -987,8 +987,9 @@ BOOL LoadScripDataFromFile(const char* fileName) return FALSE; } - DWORD size = GetFileSize(hFile, NULL); - if (size == 0xFFFFFFFF || size == 0 || size > LOADED_SCRIPT_MAX) + CFileOffsetResult sizeResult = SalGetPluginFileSizeEx(SalGeneral, hFile); + // The script buffer is deliberately fixed, so reject the full 64-bit size before narrowing it for ReadFile. + if (!sizeResult.Succeeded || sizeResult.Value.Value == 0 || sizeResult.Value.Value > LOADED_SCRIPT_MAX) { sprintf(buff, LoadStr(IDS_FILE_OPENERROR), fileName); AddLogLine(buff, TRUE); @@ -996,6 +997,8 @@ BOOL LoadScripDataFromFile(const char* fileName) return FALSE; } + DWORD size = (DWORD)sizeResult.Value.Value; + DWORD read; if (!ReadFile(hFile, LoadedScript, size, &read, NULL) || read != size) { diff --git a/src/plugins/checkver/dialogs.cpp b/src/plugins/checkver/dialogs.cpp index ba22cdae..431d17a4 100644 --- a/src/plugins/checkver/dialogs.cpp +++ b/src/plugins/checkver/dialogs.cpp @@ -718,6 +718,7 @@ MENU_TEMPLATE_ITEM AppendToSystemMenu[] = IncMainDialogID(); // detach the running session - it will not send anything else and // will finish as soon as possible + CancelDownloadThread(); // close the active network handle before detaching the worker CloseHandle(HDownloadThread); HDownloadThread = NULL; ModulesCleanup(); @@ -758,6 +759,7 @@ MENU_TEMPLATE_ITEM AppendToSystemMenu[] = { IncMainDialogID(); // detach the running session - it will not send anything else and // will finish as soon as possible + CancelDownloadThread(); // close the active network handle before detaching the worker CloseHandle(HDownloadThread); HDownloadThread = NULL; ModulesCleanup(); diff --git a/src/plugins/checkver/internet.cpp b/src/plugins/checkver/internet.cpp index 5dd8bf10..266c2d5a 100644 --- a/src/plugins/checkver/internet.cpp +++ b/src/plugins/checkver/internet.cpp @@ -15,6 +15,127 @@ const char* SCRIPT_URL_HTTP = "https://www.taskscape.com/salupdate40/"; const char* SCRIPT_URL_HTTP_AFTERINSTALL = "https://www.taskscape.com/salupdatenew40/"; const char* AGENT_NAME = "Open Salamander CheckVer Plugin"; +const char* GetInetErrorText(DWORD dError); + +namespace +{ +// WinINet applies the connect deadline while resolving the host as well, so +// these values keep each remote phase bounded without changing user settings. +const DWORD kResolveAndConnectTimeoutMs = 15000; +const DWORD kSendTimeoutMs = 15000; +const DWORD kReceiveTimeoutMs = 30000; + +CRITICAL_SECTION DownloadHandleLock; +BOOL DownloadHandleLockInitialized = FALSE; +HINTERNET ActiveDownloadSession = NULL; +HINTERNET ActiveDownloadRequest = NULL; +volatile LONG DownloadCancelled = FALSE; +volatile LONG DownloadThreadActive = FALSE; + +BOOL IsDownloadCancelled() +{ + return InterlockedCompareExchange(&DownloadCancelled, FALSE, FALSE) != FALSE; +} + +BOOL RegisterActiveDownloadSession(HINTERNET session) +{ + BOOL registered = FALSE; + EnterCriticalSection(&DownloadHandleLock); + if (!IsDownloadCancelled()) + { + ActiveDownloadSession = session; + registered = TRUE; + } + LeaveCriticalSection(&DownloadHandleLock); + + if (!registered) + InternetCloseHandle(session); + return registered; +} + +BOOL RegisterActiveDownloadRequest(HINTERNET request) +{ + BOOL registered = FALSE; + EnterCriticalSection(&DownloadHandleLock); + if (!IsDownloadCancelled()) + { + ActiveDownloadRequest = request; + registered = TRUE; + } + LeaveCriticalSection(&DownloadHandleLock); + + if (!registered) + InternetCloseHandle(request); + return registered; +} + +void CloseOwnedDownloadRequest(HINTERNET request) +{ + BOOL closeHandle = TRUE; + EnterCriticalSection(&DownloadHandleLock); + if (ActiveDownloadRequest == request) + ActiveDownloadRequest = NULL; + else if (IsDownloadCancelled()) + closeHandle = FALSE; // cancellation already closed the sole active handle + LeaveCriticalSection(&DownloadHandleLock); + + if (closeHandle) + InternetCloseHandle(request); +} + +void CloseOwnedDownloadSession(HINTERNET session) +{ + BOOL closeHandle = TRUE; + EnterCriticalSection(&DownloadHandleLock); + if (ActiveDownloadSession == session) + ActiveDownloadSession = NULL; + else if (IsDownloadCancelled()) + closeHandle = FALSE; // cancellation already closed the active session + LeaveCriticalSection(&DownloadHandleLock); + + if (closeHandle) + InternetCloseHandle(session); +} + +BOOL SetDownloadTimeouts(HINTERNET session, DWORD* error) +{ + // Keep DNS/connect, request write, and response read failures independently bounded. + DWORD resolveAndConnectTimeout = kResolveAndConnectTimeoutMs; + DWORD sendTimeout = kSendTimeoutMs; + DWORD receiveTimeout = kReceiveTimeoutMs; + if (!InternetSetOption(session, INTERNET_OPTION_CONNECT_TIMEOUT, &resolveAndConnectTimeout, sizeof(resolveAndConnectTimeout)) || + !InternetSetOption(session, INTERNET_OPTION_SEND_TIMEOUT, &sendTimeout, sizeof(sendTimeout)) || + !InternetSetOption(session, INTERNET_OPTION_RECEIVE_TIMEOUT, &receiveTimeout, sizeof(receiveTimeout)) || + !InternetSetOption(session, INTERNET_OPTION_DATA_RECEIVE_TIMEOUT, &receiveTimeout, sizeof(receiveTimeout))) + { + *error = GetLastError(); + return FALSE; + } + return TRUE; +} + +const char* GetInetFailureKind(DWORD error) +{ + if (error == ERROR_INTERNET_TIMEOUT) + return "Network timeout"; + if (error == ERROR_INTERNET_LOGIN_FAILURE || error == ERROR_INTERNET_INCORRECT_PASSWORD) + return "Authentication failure"; + if (error == ERROR_INTERNET_INVALID_URL || error == ERROR_INTERNET_PROTOCOL_NOT_FOUND || + error == ERROR_INTERNET_SEC_CERT_CN_INVALID || error == ERROR_INTERNET_SEC_CERT_DATE_INVALID || + error == ERROR_INTERNET_INVALID_CA) + return "Protocol or TLS failure"; + return NULL; +} + +void GetInetFailureText(DWORD error, char* buffer, int bufferSize) +{ + const char* kind = GetInetFailureKind(error); + if (kind != NULL) + _snprintf_s(buffer, bufferSize, _TRUNCATE, "%s (%lu)", kind, error); + else + lstrcpyn(buffer, GetInetErrorText(error), bufferSize); +} +} // namespace // limitation - may be called from only one thread; otherwise buffer overwrites are not handled const char* GetInetErrorText(DWORD dError) @@ -145,32 +266,35 @@ DWORD WINAPI ThreadDownload(void* param) { AddLogLine(LoadStr(IDS_INET_PROTOCOL), FALSE); AddLogLine(LoadStr(IDS_INET_INIT), FALSE); - errorCode = InternetAttemptConnect(0); - if (errorCode != ERROR_SUCCESS) + } + + if (dialogID == GetMainDialogID() && !exit) + { + hSession = InternetOpen(AGENT_NAME, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0); + if (hSession == NULL || !RegisterActiveDownloadSession(hSession)) { EnterCriticalSection(&MainDialogIDSection); - if (dialogID == MainDialogID) + if (dialogID == MainDialogID && !IsDownloadCancelled()) { + DWORD err = GetLastError(); + char errorText[1024]; char buff2[1024]; - sprintf(buff2, LoadStr(IDS_INET_INIT_FAILED), GetInetErrorText(errorCode)); + GetInetFailureText(err, errorText, sizeof(errorText)); + sprintf(buff2, LoadStr(IDS_INET_INIT_FAILED), errorText); AddLogLine(buff2, TRUE); } LeaveCriticalSection(&MainDialogIDSection); exit = TRUE; } - } - - if (dialogID == GetMainDialogID() && !exit) - { - hSession = InternetOpen(AGENT_NAME, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0); - if (hSession == NULL) + else if (!SetDownloadTimeouts(hSession, &errorCode)) { EnterCriticalSection(&MainDialogIDSection); - if (dialogID == MainDialogID) + if (dialogID == MainDialogID && !IsDownloadCancelled()) { - DWORD err = GetLastError(); + char errorText[1024]; char buff2[1024]; - sprintf(buff2, LoadStr(IDS_INET_INIT_FAILED), GetInetErrorText(err)); + GetInetFailureText(errorCode, errorText, sizeof(errorText)); + sprintf(buff2, LoadStr(IDS_INET_INIT_FAILED), errorText); AddLogLine(buff2, TRUE); } LeaveCriticalSection(&MainDialogIDSection); @@ -217,14 +341,16 @@ DWORD WINAPI ThreadDownload(void* param) } } - if (hUrl == NULL) + if (hUrl == NULL || !RegisterActiveDownloadRequest(hUrl)) { EnterCriticalSection(&MainDialogIDSection); - if (dialogID == MainDialogID) + if (dialogID == MainDialogID && !IsDownloadCancelled()) { DWORD err = GetLastError(); + char errorText[1024]; char buff2[1024]; - sprintf(buff2, LoadStr(IDS_INET_CONNECT_FAILED), GetInetErrorText(err)); + GetInetFailureText(err, errorText, sizeof(errorText)); + sprintf(buff2, LoadStr(IDS_INET_CONNECT_FAILED), errorText); AddLogLine(buff2, TRUE); } LeaveCriticalSection(&MainDialogIDSection); @@ -239,11 +365,13 @@ DWORD WINAPI ThreadDownload(void* param) if (!bResult) { EnterCriticalSection(&MainDialogIDSection); - if (dialogID == MainDialogID) + if (dialogID == MainDialogID && !IsDownloadCancelled()) { DWORD err = GetLastError(); + char errorText[1024]; char buff2[1024]; - sprintf(buff2, LoadStr(IDS_INET_READ_FAILED), GetInetErrorText(err)); + GetInetFailureText(err, errorText, sizeof(errorText)); + sprintf(buff2, LoadStr(IDS_INET_READ_FAILED), errorText); AddLogLine(buff2, TRUE); } LeaveCriticalSection(&MainDialogIDSection); @@ -252,9 +380,9 @@ DWORD WINAPI ThreadDownload(void* param) } if (hUrl != NULL) - InternetCloseHandle(hUrl); + CloseOwnedDownloadRequest(hUrl); if (hSession != NULL) - InternetCloseHandle(hSession); + CloseOwnedDownloadSession(hSession); EnterCriticalSection(&MainDialogIDSection); DWORD id = MainDialogID; @@ -268,6 +396,7 @@ DWORD WINAPI ThreadDownload(void* param) else LoadedScriptSize = 0; PostMessage(HMainDialog, WM_USER_DOWNLOADTHREAD_EXIT, !exit, 0); // thread ends; data are loaded + InterlockedExchange(&DownloadThreadActive, FALSE); FreeLibrary(hLock); // release the lock LeaveCriticalSection(&MainDialogIDSection); return 0; // let the thread die naturally @@ -278,6 +407,7 @@ DWORD WINAPI ThreadDownload(void* param) // we were killed from the outside - after FreeLibrary the last lock on the SPL may be removed // (Salamander may no longer be running) and there would be nowhere to return to, // therefore call this function: + InterlockedExchange(&DownloadThreadActive, FALSE); FreeLibraryAndExitThread(hLock, 3666); return 0; // we never return here, but the compiler cannot know that :-) } @@ -286,6 +416,11 @@ DWORD WINAPI ThreadDownload(void* param) HANDLE StartDownloadThread(BOOL firstLoadAfterInstall) { + // Do not recycle the shared cancellation token until a detached worker has + // observed it and released its WinINet handles. + if (InterlockedCompareExchange(&DownloadThreadActive, TRUE, FALSE) != FALSE) + return NULL; + CTDData data; data.MainDialogID = GetMainDialogID(); data.FirstLoadAfterInstall = firstLoadAfterInstall; @@ -294,13 +429,25 @@ StartDownloadThread(BOOL firstLoadAfterInstall) if (data.Continue == NULL) { TRACE_E("Unable to create Continue event."); + InterlockedExchange(&DownloadThreadActive, FALSE); return NULL; } + if (!DownloadHandleLockInitialized) + { + // A single token owns the active WinINet handle for this one-at-a-time download. + InitializeCriticalSection(&DownloadHandleLock); + DownloadHandleLockInitialized = TRUE; + } + InterlockedExchange(&DownloadCancelled, FALSE); + DWORD threadID; HANDLE hThread = CreateThread(NULL, 0, ThreadDownload, &data, 0, &threadID); if (hThread == NULL) + { TRACE_E("Unable to create Check Version Download thread."); + InterlockedExchange(&DownloadThreadActive, FALSE); + } else // wait until the thread takes the data WaitForSingleObject(data.Continue, INFINITE); @@ -308,3 +455,24 @@ StartDownloadThread(BOOL firstLoadAfterInstall) return hThread; } + +void CancelDownloadThread() +{ + InterlockedExchange(&DownloadCancelled, TRUE); + + HINTERNET session = NULL; + HINTERNET request = NULL; + EnterCriticalSection(&DownloadHandleLock); + session = ActiveDownloadSession; + request = ActiveDownloadRequest; + ActiveDownloadSession = NULL; + ActiveDownloadRequest = NULL; + LeaveCriticalSection(&DownloadHandleLock); + + // Closing both WinINet levels interrupts a DNS/connect/read wait; the + // worker sees the same token before it can begin a later phase. + if (request != NULL) + InternetCloseHandle(request); + if (session != NULL) + InternetCloseHandle(session); +} diff --git a/src/plugins/ftp/ctrlcon.h b/src/plugins/ftp/ctrlcon.h index 46173b6d..1b348d1f 100644 --- a/src/plugins/ftp/ctrlcon.h +++ b/src/plugins/ftp/ctrlcon.h @@ -80,6 +80,7 @@ const char* GetOperationFatalErrorTxt(int opFatalError, char* errBuf); #define CRTLCON_BYTESTOWRITEONSOCKETPREALLOC 512 // how many bytes to preallocate for writing (so the next write does not allocate unnecessarily, for example due to a 1-byte overflow) #define CRTLCON_BYTESTOREADONSOCKET 1024 // the minimum number of bytes to read from the socket at once (also allocate the buffer for the read data) #define CRTLCON_BYTESTOREADONSOCKETPREALLOC 512 // how many bytes to preallocate for reading (so the next read does not immediately allocate again) +#define CRTLCON_MAXIMUM_REPLY_SIZE (64 * 1024) // reject an unterminated server reply before it can grow the dynamic buffer indefinitely // response codes for the 1st digit of the FTP reply code #define FTP_D1_MAYBESUCCESS 1 // possible success (the client must wait for the next reply) @@ -1025,13 +1026,15 @@ class CControlConnectionSocket : public CSocket // whether ESC was pressed in this window and not, for example, in another application; in the main // thread this is SalamanderGeneral->GetMsgBoxParent() or a dialog opened by the plugin); 'parent' // is also the parent of any error message boxes; 'workPath' is the working directory; 'tgtFileName' - // is the target file name (where the download is stored); 'newFileCreated' returns TRUE if at least + // is the target file name (where the download is stored); remote date/time identify the resumable + // staged copy and must come from the same listing as 'fileSizeInBytes'; 'newFileCreated' returns TRUE if at least // part of the file was downloaded (something exists on disk); 'newFileIncomplete' returns TRUE if // the file was not downloaded completely; if 'newFileCreated' is TRUE, 'newFileSize' returns the // file size on disk; 'totalAttemptNum', 'panel', 'notInPanel', 'userBuf', and 'userBufSize' are // parameters for ReconnectIfNeeded() void DownloadOneFile(HWND parent, const char* fileName, CQuadWord const& fileSizeInBytes, BOOL asciiMode, const char* workPath, const char* tgtFileName, + BOOL remoteDateAndTimeValid, const CFTPDate* remoteDate, const CFTPTime* remoteTime, BOOL* newFileCreated, BOOL* newFileIncomplete, CQuadWord* newFileSize, int* totalAttemptNum, int panel, BOOL notInPanel, char* userBuf, int userBufSize); diff --git a/src/plugins/ftp/ctrlcon1.cpp b/src/plugins/ftp/ctrlcon1.cpp index 96198c60..7a64789d 100644 --- a/src/plugins/ftp/ctrlcon1.cpp +++ b/src/plugins/ftp/ctrlcon1.cpp @@ -558,9 +558,17 @@ BOOL CControlConnectionSocket::StartControlConnection(HWND parent, char* user, i char retryBuf[700]; const DWORD showWaitWndTime = WAITWND_STARTCON; // show time of the wait window - int serverTimeout = Config.GetServerRepliesTimeout() * 1000; - if (serverTimeout < 1000) - serverTimeout = 1000; // at least one second + // DNS, TCP connect, and FTP/TLS reply waits have distinct failure modes, + // even though legacy configuration intentionally supplies their common value. + int resolveTimeout = Config.GetServerRepliesTimeout() * 1000; + int connectTimeout = Config.GetServerRepliesTimeout() * 1000; + int protocolTimeout = Config.GetServerRepliesTimeout() * 1000; + if (resolveTimeout < 1000) + resolveTimeout = 1000; // at least one second + if (connectTimeout < 1000) + connectTimeout = 1000; // at least one second + if (protocolTimeout < 1000) + protocolTimeout = 1000; // at least one second // store the focus from 'parent' (if the focus is not from 'parent', store NULL) HWND focusedWnd = GetFocus(); @@ -693,9 +701,9 @@ BOOL CControlConnectionSocket::StartControlConnection(HWND parent, char* user, i CControlConnectionSocketEvent event; DWORD data1, data2; DWORD now = GetTickCount(); - if (now - start > (DWORD)serverTimeout) - now = start + (DWORD)serverTimeout; - WaitForEventOrESC(parent, &event, &data1, &data2, serverTimeout - (now - start), + if (now - start > (DWORD)resolveTimeout) + now = start + (DWORD)resolveTimeout; + WaitForEventOrESC(parent, &event, &data1, &data2, resolveTimeout - (now - start), &waitWnd, NULL, FALSE); switch (event) { @@ -856,9 +864,9 @@ BOOL CControlConnectionSocket::StartControlConnection(HWND parent, char* user, i CControlConnectionSocketEvent event; DWORD data1, data2; DWORD now = GetTickCount(); - if (now - start > (DWORD)serverTimeout) - now = start + (DWORD)serverTimeout; - WaitForEventOrESC(parent, &event, &data1, &data2, serverTimeout - (now - start), + if (now - start > (DWORD)connectTimeout) + now = start + (DWORD)connectTimeout; + WaitForEventOrESC(parent, &event, &data1, &data2, connectTimeout - (now - start), &waitWnd, NULL, FALSE); switch (event) { @@ -1180,9 +1188,9 @@ MENU_TEMPLATE_ITEM MsgBoxButtons[] = CControlConnectionSocketEvent event; DWORD data1, data2; DWORD now = GetTickCount(); - if (now - start > (DWORD)serverTimeout) - now = start + (DWORD)serverTimeout; - WaitForEventOrESC(parent, &event, &data1, &data2, serverTimeout - (now - start), + if (now - start > (DWORD)protocolTimeout) + now = start + (DWORD)protocolTimeout; + WaitForEventOrESC(parent, &event, &data1, &data2, protocolTimeout - (now - start), &waitWnd, NULL, FALSE); switch (event) { @@ -1532,9 +1540,9 @@ MENU_TEMPLATE_ITEM MsgBoxButtons[] = CControlConnectionSocketEvent event; DWORD data1, data2; DWORD now = GetTickCount(); - if (now - start > (DWORD)serverTimeout) - now = start + (DWORD)serverTimeout; - WaitForEventOrESC(parent, &event, &data1, &data2, serverTimeout - (now - start), + if (now - start > (DWORD)protocolTimeout) + now = start + (DWORD)protocolTimeout; + WaitForEventOrESC(parent, &event, &data1, &data2, protocolTimeout - (now - start), &waitWnd, NULL, FALSE); switch (event) { @@ -1639,7 +1647,7 @@ MENU_TEMPLATE_ITEM MsgBoxButtons[] = LoadStr(IDS_FTPPLUGINTITLE), MB_OK | MB_ICONSTOP); state = sccsDone; allBytesWritten = TRUE; // no longer important, the socket is going to be closed - SSLInitSequence = sslisNone; // do not attempt to load OpenSSL libs + SSLInitSequence = sslisNone; // do not attempt to negotiate TLS } else { @@ -1777,16 +1785,21 @@ MENU_TEMPLATE_ITEM MsgBoxButtons[] = fastRetry = TRUE; // user-induced delay, some servers do not like it (ftp.novell.com - kills after 20s) waitWnd.Show(FALSE); + CCertificateErrDialog certificateDialog(parent, errBuf); INT_PTR dlgRes; do { - dlgRes = CCertificateErrDialog(parent, errBuf).Execute(); + dlgRes = certificateDialog.Execute(); switch (dlgRes) { case IDOK: // accept once { Logs.LogMessage(logUID, LoadStr(IDS_SSL_LOG_CERTACCEPTED), -1, TRUE); SetCertificate(unverifiedCert); + // Remembered exceptions are opt-in and remain pinned to this endpoint and certificate. + if (unverifiedCert->RememberException(certificateDialog.RememberException() ? cesPersistent : cesSession) && + certificateDialog.RememberException()) + PersistFTPConfigurationAfterUserCommit(parent); break; } @@ -1816,7 +1829,7 @@ MENU_TEMPLATE_ITEM MsgBoxButtons[] = } } } - else // Error! OpenSSL libs not found? Or not W2K+? + else // Windows TLS services are unavailable. { allBytesWritten = TRUE; // no longer important, the socket will be closed state = sccsFatalError; diff --git a/src/plugins/ftp/ctrlcon2.cpp b/src/plugins/ftp/ctrlcon2.cpp index 2a6e22ed..69730902 100644 --- a/src/plugins/ftp/ctrlcon2.cpp +++ b/src/plugins/ftp/ctrlcon2.cpp @@ -668,7 +668,7 @@ BOOL CControlConnectionSocket::Write(const char* buffer, int bytesToWrite, DWORD // WARNING: if the TELNET protocol is ever introduced again, sending IAC+IP before aborting // a command in the SendFTPCommand() method must be adjusted - int sentLen = SSLLib.SSL_write(SSLConn, buffer + len, bytesToWrite - len); + int sentLen = SSLWrite(SSLConn, buffer + len, bytesToWrite - len); if (sentLen >= 0) // at least something was sent successfully (or rather taken over by Windows; delivery is uncertain) { len += sentLen; @@ -680,7 +680,7 @@ BOOL CControlConnectionSocket::Write(const char* buffer, int bytesToWrite, DWORD } else { - DWORD err = SSLtoWS2Error(SSLLib.SSL_get_error(SSLConn, sentLen)); + DWORD err = SSLtoWS2Error(SSLGetError(SSLConn, sentLen)); if (err == WSAEWOULDBLOCK) // nothing else can be sent (Windows no longer have buffer space) { ret = TRUE; @@ -841,16 +841,29 @@ void CControlConnectionSocket::ReceiveNetEvent(LPARAM lParam, int index) { int newSize = ReadBytesCount + CRTLCON_BYTESTOREADONSOCKET + CRTLCON_BYTESTOREADONSOCKETPREALLOC; - char* newBuf = (char*)realloc(ReadBytes, newSize); - if (newBuf != NULL) + if (newSize > CRTLCON_MAXIMUM_REPLY_SIZE) { - ReadBytes = newBuf; - ReadBytesAllocatedSize = newSize; + // A server reply is external input. The parser has not + // found a complete reply before the accepted limit, so + // surface an explicit socket error instead of retaining + // an unbounded dynamic buffer. + err = WSAEMSGSIZE; + genEvent = TRUE; + lowMem = TRUE; } - else // not enough memory to store data in our buffer (only TRACE reports the error) + else { - TRACE_E(LOW_MEMORY); - lowMem = TRUE; + char* newBuf = (char*)realloc(ReadBytes, newSize); + if (newBuf != NULL) + { + ReadBytes = newBuf; + ReadBytesAllocatedSize = newSize; + } + else // not enough memory to store data in our buffer (only TRACE reports the error) + { + TRACE_E(LOW_MEMORY); + lowMem = TRUE; + } } } } @@ -881,9 +894,9 @@ void CControlConnectionSocket::ReceiveNetEvent(LPARAM lParam, int index) } else { - if (SSLLib.SSL_pending(SSLConn) > 0) // if the internal SSL buffer is not empty, recv() is not called at all and no FD_READ arrives, so post it ourselves to avoid stalling the transfer + if (SSLPending(SSLConn) > 0) // if the internal TLS buffer is not empty, recv() is not called at all and no FD_READ arrives, so post it ourselves to avoid stalling the transfer PostMessage(SocketsThread->GetHiddenWindow(), Msg, (WPARAM)Socket, FD_READ); - int len = SSLLib.SSL_read(SSLConn, ReadBytes + ReadBytesCount, ReadBytesAllocatedSize - ReadBytesCount); + int len = SSLRead(SSLConn, ReadBytes + ReadBytesCount, ReadBytesAllocatedSize - ReadBytesCount); if (len >= 0) // we may have read something (0 = the connection is already closed) { if (len > 0) @@ -897,7 +910,7 @@ void CControlConnectionSocket::ReceiveNetEvent(LPARAM lParam, int index) } else { - err = SSLtoWS2Error(SSLLib.SSL_get_error(SSLConn, len)); + err = SSLtoWS2Error(SSLGetError(SSLConn, len)); ; if (err != WSAEWOULDBLOCK) genEvent = TRUE; // we will generate an event with the error @@ -1126,7 +1139,7 @@ void CControlConnectionSocket::ReceiveNetEvent(LPARAM lParam, int index) { while (1) // loop needed because 'send' does not post FD_WRITE when 'sentLen' < 'bytesToWrite' { - int sentLen = SSLLib.SSL_write(SSLConn, BytesToWrite + BytesToWriteOffset + len, + int sentLen = SSLWrite(SSLConn, BytesToWrite + BytesToWriteOffset + len, BytesToWriteCount - BytesToWriteOffset - len); if (sentLen >= 0) // at least something was sent successfully (or rather taken over by Windows; delivery is uncertain) { @@ -1139,7 +1152,7 @@ void CControlConnectionSocket::ReceiveNetEvent(LPARAM lParam, int index) } else { - err = SSLtoWS2Error(SSLLib.SSL_get_error(SSLConn, sentLen)); + err = SSLtoWS2Error(SSLGetError(SSLConn, sentLen)); if (err == WSAEWOULDBLOCK) // nothing else can be sent (Windows no longer have buffer space) { ret = TRUE; diff --git a/src/plugins/ftp/ctrlcon5.cpp b/src/plugins/ftp/ctrlcon5.cpp index 4306d085..717e1c3f 100644 --- a/src/plugins/ftp/ctrlcon5.cpp +++ b/src/plugins/ftp/ctrlcon5.cpp @@ -4,6 +4,151 @@ #include "precomp.h" +namespace +{ +const DWORD FtpTransactionalDownloadMagic = 0x44505446; // "FTPD" +const DWORD FtpTransactionalDownloadVersion = 1; +const char FtpIncompleteSuffix[] = ".ftp-incomplete"; +const char FtpIncompleteMetadataSuffix[] = ".meta"; + +struct CFTPTransactionalDownloadMetadata +{ + DWORD Magic; + DWORD Version; + unsigned __int64 RemoteSize; + BOOL RemoteDateAndTimeValid; + CFTPDate RemoteDate; + CFTPTime RemoteTime; + unsigned short Port; + char User[USER_MAX_SIZE]; + char Host[HOST_MAX_SIZE]; + char RemotePath[FTP_MAX_PATH]; + char RemoteName[FTP_MAX_PATH]; +}; + +BOOL BuildTransactionalDownloadPaths(const char* target, char* stagedTarget, int stagedTargetSize, + char* metadata, int metadataSize) +{ + const size_t targetLength = strlen(target); + const size_t stagedLength = targetLength + sizeof(FtpIncompleteSuffix); + const size_t metadataLength = stagedLength + sizeof(FtpIncompleteMetadataSuffix) - 1; + if (stagedLength > (size_t)stagedTargetSize || metadataLength > (size_t)metadataSize) + { + SetLastError(ERROR_FILENAME_EXCED_RANGE); + return FALSE; + } + + memcpy(stagedTarget, target, targetLength); + memcpy(stagedTarget + targetLength, FtpIncompleteSuffix, sizeof(FtpIncompleteSuffix)); + memcpy(metadata, stagedTarget, stagedLength - 1); + memcpy(metadata + stagedLength - 1, FtpIncompleteMetadataSuffix, sizeof(FtpIncompleteMetadataSuffix)); + return TRUE; +} + +void BuildTransactionalDownloadMetadata(CFTPTransactionalDownloadMetadata* metadata, + const char* user, const char* host, unsigned short port, + const char* remotePath, const char* remoteName, + const CQuadWord& remoteSize, BOOL remoteDateAndTimeValid, + const CFTPDate* remoteDate, const CFTPTime* remoteTime) +{ + memset(metadata, 0, sizeof(*metadata)); + metadata->Magic = FtpTransactionalDownloadMagic; + metadata->Version = FtpTransactionalDownloadVersion; + metadata->RemoteSize = remoteSize.Value; + metadata->RemoteDateAndTimeValid = remoteDateAndTimeValid; + if (remoteDateAndTimeValid) + { + metadata->RemoteDate = *remoteDate; + metadata->RemoteTime = *remoteTime; + } + metadata->Port = port; + lstrcpyn(metadata->User, user, USER_MAX_SIZE); + lstrcpyn(metadata->Host, host, HOST_MAX_SIZE); + lstrcpyn(metadata->RemotePath, remotePath, FTP_MAX_PATH); + lstrcpyn(metadata->RemoteName, remoteName, FTP_MAX_PATH); +} + +BOOL WriteTransactionalDownloadMetadata(const char* metadataPath, const CFTPTransactionalDownloadMetadata& metadata) +{ + HANDLE file = HANDLES_Q(CreateFileUtf8Local(metadataPath, GENERIC_WRITE, FILE_SHARE_READ, NULL, + CREATE_ALWAYS, FILE_ATTRIBUTE_HIDDEN | FILE_FLAG_WRITE_THROUGH, NULL)); + if (file == INVALID_HANDLE_VALUE) + return FALSE; + + DWORD written = 0; + // The sidecar is flushed before REST so an interruption never resumes an unidentified prefix. + BOOL success = WriteFile(file, &metadata, sizeof(metadata), &written, NULL) && written == sizeof(metadata) && + FlushFileBuffers(file); + DWORD error = success ? NO_ERROR : GetLastError(); + HANDLES(CloseHandle(file)); + if (!success) + SetLastError(error); + return success; +} + +BOOL GetTransactionalDownloadResumeOffset(const char* stagedTarget, const char* metadataPath, + const CFTPTransactionalDownloadMetadata& expected, + CQuadWord* resumeOffset) +{ + resumeOffset->Set(0, 0); + HANDLE metadataFile = HANDLES_Q(CreateFileUtf8Local(metadataPath, GENERIC_READ, FILE_SHARE_READ, NULL, + OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL)); + if (metadataFile == INVALID_HANDLE_VALUE) + return FALSE; + + CFTPTransactionalDownloadMetadata actual; + DWORD read = 0; + BOOL metadataMatches = ReadFile(metadataFile, &actual, sizeof(actual), &read, NULL) && + read == sizeof(actual) && memcmp(&actual, &expected, sizeof(actual)) == 0; + HANDLES(CloseHandle(metadataFile)); + if (!metadataMatches) + return FALSE; + + HANDLE stagedFile = HANDLES_Q(CreateFileUtf8Local(stagedTarget, GENERIC_READ, FILE_SHARE_READ, NULL, + OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL)); + if (stagedFile == INVALID_HANDLE_VALUE) + return FALSE; + + LARGE_INTEGER size; + BOOL haveSize = GetFileSizeEx(stagedFile, &size); + HANDLES(CloseHandle(stagedFile)); + if (!haveSize || size.QuadPart < 0 || (unsigned __int64)size.QuadPart > expected.RemoteSize) + return FALSE; + + resumeOffset->Value = (unsigned __int64)size.QuadPart; + return *resumeOffset != CQuadWord(0, 0); +} + +BOOL CommitTransactionalDownload(const char* stagedTarget, const char* target, const char* metadataPath, + const CQuadWord& expectedSize, BOOL expectedSizeKnown) +{ + HANDLE stagedFile = HANDLES_Q(CreateFileUtf8Local(stagedTarget, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, + OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL)); + if (stagedFile == INVALID_HANDLE_VALUE) + return FALSE; + + LARGE_INTEGER actualSize; + // A successful FTP reply is insufficient: verify the durable staged length before publishing it. + BOOL success = FlushFileBuffers(stagedFile) && GetFileSizeEx(stagedFile, &actualSize) && actualSize.QuadPart >= 0 && + (!expectedSizeKnown || (unsigned __int64)actualSize.QuadPart == expectedSize.Value); + DWORD error = success ? NO_ERROR : GetLastError(); + HANDLES(CloseHandle(stagedFile)); + if (!success) + { + if (error == NO_ERROR) + error = ERROR_INVALID_DATA; + SetLastError(error); + return FALSE; + } + + // Staging shares the destination directory, making this replacement atomic on the target volume. + if (!MoveFileExUtf8Local(stagedTarget, target, MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)) + return FALSE; + DeleteFileUtf8Local(metadataPath); // a committed file has no resumable state + return TRUE; +} +} + // // **************************************************************************** // CControlConnectionSocket @@ -12,7 +157,9 @@ void CControlConnectionSocket::DownloadOneFile(HWND parent, const char* fileName, CQuadWord const& fileSizeInBytes, BOOL asciiMode, const char* workPath, - const char* tgtFileName, BOOL* newFileCreated, + const char* tgtFileName, BOOL remoteDateAndTimeValid, + const CFTPDate* remoteDate, const CFTPTime* remoteTime, + BOOL* newFileCreated, BOOL* newFileIncomplete, CQuadWord* newFileSize, int* totalAttemptNum, int panel, BOOL notInPanel, char* userBuf, int userBufSize) @@ -61,6 +208,46 @@ void CControlConnectionSocket::DownloadOneFile(HWND parent, const char* fileName } else { + char stagedTargetName[MAX_PATH]; + char metadataName[MAX_PATH]; + if (!BuildTransactionalDownloadPaths(tgtFileName, stagedTargetName, MAX_PATH, metadataName, MAX_PATH)) + { + _snprintf_s(errBuf, _TRUNCATE, LoadStr(IDS_DOWNLOADFILEERROR), fileName, workPath); + SalamanderGeneral->SalMessageBox(parent, errBuf, LoadStr(IDS_FTPERRORTITLE), MB_OK | MB_ICONEXCLAMATION); + DeleteSocket(dataConnection); + FTPOpenedFiles.CloseFile(lockedFileUID); + return; + } + + CFTPTransactionalDownloadMetadata transactionalMetadata; + BuildTransactionalDownloadMetadata(&transactionalMetadata, userBuffer, hostBuf, portBuf, workPath, fileName, + fileSizeInBytes, remoteDateAndTimeValid, remoteDate, remoteTime); + CQuadWord stagedResumeOffset(0, 0); + BOOL canResumeStagedDownload = !asciiMode && fileSizeInBytes != CQuadWord(-1, -1) && + GetTransactionalDownloadResumeOffset(stagedTargetName, metadataName, + transactionalMetadata, &stagedResumeOffset); + if (canResumeStagedDownload && stagedResumeOffset > CQuadWord(Config.GetResumeOverlap(), 0)) + { + // Re-request the tail so a complete-looking side file still proves the resumed server boundary. + stagedResumeOffset -= CQuadWord(Config.GetResumeOverlap(), 0); + } + if (!canResumeStagedDownload) + { + // A mismatched or oversized prefix is never allowed to contaminate the next RETR. + SetFileAttributesUtf8Local(stagedTargetName, FILE_ATTRIBUTE_NORMAL); + DeleteFileUtf8Local(stagedTargetName); + DeleteFileUtf8Local(metadataName); + stagedResumeOffset.Set(0, 0); + } + if (!WriteTransactionalDownloadMetadata(metadataName, transactionalMetadata)) + { + _snprintf_s(errBuf, _TRUNCATE, LoadStr(IDS_DOWNLOADFILEERROR), fileName, workPath); + SalamanderGeneral->SalMessageBox(parent, errBuf, LoadStr(IDS_FTPERRORTITLE), MB_OK | MB_ICONEXCLAMATION); + DeleteSocket(dataConnection); + FTPOpenedFiles.CloseFile(lockedFileUID); + return; + } + char cmdBuf[50 + FTP_MAX_PATH]; char logBuf[50 + FTP_MAX_PATH]; const char* retryMsgAux = NULL; @@ -208,9 +395,9 @@ void CControlConnectionSocket::DownloadOneFile(HWND parent, const char* fileName if (ok) { - // set the target file name in the data connection; data flushes will be performed - // into this file (it is always overwritten, we will not do any resume here) - dataConnection->SetDirectFlushParams(tgtFileName, asciiMode ? ctrmASCII : ctrmBinary); + // The final cache name stays untouched until the staged file has passed all completion checks. + dataConnection->SetDirectFlushParams(stagedTargetName, asciiMode ? ctrmASCII : ctrmBinary, + stagedResumeOffset); if (fileSizeInBytes != CQuadWord(-1, -1)) // if the file size in bytes is known, set it dataConnection->SetDataTotalSize(fileSizeInBytes); @@ -223,14 +410,44 @@ void CControlConnectionSocket::DownloadOneFile(HWND parent, const char* fileName CFTPServerPathType pathType = ::GetFTPServerPathType(ServerFirstReply, ServerSystem, workPath); HANDLES(LeaveCriticalSection(&SocketCritSect)); + if (stagedResumeOffset != CQuadWord(0, 0)) + { + char resumeOffset[50]; + _ui64toa(stagedResumeOffset.Value, resumeOffset, 10); + PrepareFTPCommand(cmdBuf, 50 + FTP_MAX_PATH, logBuf, 50 + FTP_MAX_PATH, + ftpcmdRestartTransfer, NULL, resumeOffset); + if (!SendFTPCommand(parent, cmdBuf, logBuf, NULL, GetWaitTime(WAITWND_COMOPER), NULL, + &ftpReplyCode, replyBuf, 700, FALSE, FALSE, FALSE, &canRetry, + retryMsgBuf, 300, NULL)) + { + ok = FALSE; + if (canRetry) + { + run = TRUE; + retryMsgAux = retryMsgBuf; + } + } + else if (FTP_DIGIT_1(ftpReplyCode) != FTP_D1_PARTIALSUCCESS && + FTP_DIGIT_1(ftpReplyCode) != FTP_D1_SUCCESS) + { + // Never append if REST was refused; restart the staged file from a known boundary. + SetFileAttributesUtf8Local(stagedTargetName, FILE_ATTRIBUTE_NORMAL); + DeleteFileUtf8Local(stagedTargetName); + stagedResumeOffset.Set(0, 0); + dataConnection->SetDirectFlushParams(stagedTargetName, asciiMode ? ctrmASCII : ctrmBinary, + stagedResumeOffset); + WriteTransactionalDownloadMetadata(metadataName, transactionalMetadata); + } + } + PrepareFTPCommand(cmdBuf, 50 + FTP_MAX_PATH, logBuf, 50 + FTP_MAX_PATH, ftpcmdRetrieveFile, NULL, fileName); // cannot report an error BOOL fileIncomplete = TRUE; BOOL tgtFileError = FALSE; userIface.InitWnd(fileName, hostBuf, workPath, pathType); - BOOL sendCmdRes = SendFTPCommand(parent, cmdBuf, logBuf, NULL, GetWaitTime(WAITWND_COMOPER), NULL, - &ftpReplyCode, replyBuf, 700, FALSE, FALSE, FALSE, &canRetry, - retryMsgBuf, 300, &userIface); + BOOL sendCmdRes = ok && SendFTPCommand(parent, cmdBuf, logBuf, NULL, GetWaitTime(WAITWND_COMOPER), NULL, + &ftpReplyCode, replyBuf, 700, FALSE, FALSE, FALSE, &canRetry, + retryMsgBuf, 300, &userIface); int asciiTrForBinFileHowToSolve = 0; if (dataConnection->IsAsciiTrForBinFileProblem(&asciiTrForBinFileHowToSolve)) { // the "ascii transfer mode for binary file" problem was detected @@ -255,8 +472,8 @@ void CControlConnectionSocket::DownloadOneFile(HWND parent, const char* fileName // wait for the file to close in the disk cache; otherwise the file cannot be deleted dataConnection->WaitForFileClose(5000); // max. 5 seconds - SetFileAttributesUtf8Local(tgtFileName, FILE_ATTRIBUTE_NORMAL); - DeleteFileUtf8Local(tgtFileName); + SetFileAttributesUtf8Local(stagedTargetName, FILE_ATTRIBUTE_NORMAL); + DeleteFileUtf8Local(stagedTargetName); asciiMode = FALSE; // download again in binary mode ok = FALSE; // repeat the download @@ -271,8 +488,8 @@ void CControlConnectionSocket::DownloadOneFile(HWND parent, const char* fileName // wait for the file to close in the disk cache; otherwise the file cannot be deleted dataConnection->WaitForFileClose(5000); // max. 5 seconds - SetFileAttributesUtf8Local(tgtFileName, FILE_ATTRIBUTE_NORMAL); - DeleteFileUtf8Local(tgtFileName); + SetFileAttributesUtf8Local(stagedTargetName, FILE_ATTRIBUTE_NORMAL); + DeleteFileUtf8Local(stagedTargetName); ok = FALSE; // do not show any message; the user already confirmed the cancel } } @@ -404,15 +621,14 @@ void CControlConnectionSocket::DownloadOneFile(HWND parent, const char* fileName // viewers might not open it either (without SHARE_WRITE it cannot be opened) dataConnection->WaitForFileClose(5000); // max. 5 seconds - if (run) // go for another attempt; clean the target file just in case (it may have been created before the error/interruption) + if (run) // discard only this attempt's staged bytes; the final cache entry remains intact { // we are not in any critical section, so even if the disk operation stalls for a while, nothing happens - SetFileAttributesUtf8Local(tgtFileName, FILE_ATTRIBUTE_NORMAL); - DeleteFileUtf8Local(tgtFileName); + SetFileAttributesUtf8Local(stagedTargetName, FILE_ATTRIBUTE_NORMAL); + DeleteFileUtf8Local(stagedTargetName); } else // finish the download { dataConnection->GetTgtFileState(newFileCreated, newFileSize); // find out whether the file exists + its size - *newFileIncomplete = fileIncomplete; // also store whether the file is complete if (!*newFileCreated && // the data connection cannot create empty files (it creates the file only just before writing), so we must do it here !fileIncomplete && // this is not merely an error when obtaining the file !tgtFileError) // just in case no target file error occurred (to avoid it happening again here) @@ -421,15 +637,16 @@ void CControlConnectionSocket::DownloadOneFile(HWND parent, const char* fileName TRACE_E("CControlConnectionSocket::DownloadOneFile(): unexpected situation: file was not created, but its size is not null!"); // we are not in any critical section, so even if the disk operation stalls for a while, nothing happens - SetFileAttributesUtf8Local(tgtFileName, FILE_ATTRIBUTE_NORMAL); // so a read-only file can be overwritten - HANDLE file = HANDLES_Q(CreateFileUtf8Local(tgtFileName, GENERIC_WRITE, + SetFileAttributesUtf8Local(stagedTargetName, FILE_ATTRIBUTE_NORMAL); // the visible target is never opened for writing + HANDLE file = HANDLES_Q(CreateFileUtf8Local(stagedTargetName, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, - FILE_FLAG_SEQUENTIAL_SCAN, + FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_WRITE_THROUGH, NULL)); if (file != INVALID_HANDLE_VALUE) { *newFileCreated = TRUE; + FlushFileBuffers(file); HANDLES(CloseHandle(file)); } else @@ -449,6 +666,18 @@ void CControlConnectionSocket::DownloadOneFile(HWND parent, const char* fileName MB_OK | MB_ICONEXCLAMATION); } } + if (*newFileCreated && !fileIncomplete && + !CommitTransactionalDownload(stagedTargetName, tgtFileName, metadataName, + fileSizeInBytes, fileSizeInBytes != CQuadWord(-1, -1))) + { + // Keep the sidecar and staged bytes visible for a later verified resume. + *newFileCreated = FALSE; + fileIncomplete = TRUE; + _snprintf_s(errBuf, _TRUNCATE, LoadStr(IDS_DOWNLOADFILEERROR), fileName, workPath); + SalamanderGeneral->SalMessageBox(parent, errBuf, + LoadStr(IDS_FTPERRORTITLE), MB_OK | MB_ICONEXCLAMATION); + } + *newFileIncomplete = fileIncomplete; // a staged failure is not exposed as a completed cache file } } } diff --git a/src/plugins/ftp/datacon.h b/src/plugins/ftp/datacon.h index 5c2f69a7..a768e614 100644 --- a/src/plugins/ftp/datacon.h +++ b/src/plugins/ftp/datacon.h @@ -208,11 +208,13 @@ class CDataConnectionSocket : public CDataConnectionBaseSocket CQuadWord DataTotalSize; // total size of the transferred data in bytes: -1 = unknown - char TgtDiskFileName[MAX_PATH]; // if not "", this is the full name of the disk file to which data should be flushed (the file is overwritten; no resumes are performed here) + char TgtDiskFileName[MAX_PATH]; // if not "", this is the full name of the disk file to which data should be flushed HANDLE TgtDiskFile; // target disk file for flushing data (NULL = we have not opened it yet) BOOL TgtDiskFileCreated; // TRUE if the target disk file for flushing data was created DWORD TgtFileLastError; // code of the last error reported by the disk thread when writing to the TgtDiskFileName file CQuadWord TgtDiskFileSize; // current size of the disk file used for flushing data + CQuadWord TgtDiskFileResumeOffset; // verified staged bytes retained before a binary REST download + BOOL TgtDiskFileAppend; // TRUE only while the first flush must open at TgtDiskFileResumeOffset CCurrentTransferMode CurrentTransferMode; // current transfer mode (ASCII/binary) BOOL AsciiTrModeForBinFileProblemOccured; // TRUE = the "ASCII mode for binary file" error was detected int AsciiTrModeForBinFileHowToSolve; // how to solve the "ASCII mode for binary file" problem: 0 = ask the user, 1 = download again in binary mode, 2 = interrupt the file download (cancel), 3 = ignore (finish the download in ASCII mode) @@ -284,7 +286,8 @@ class CDataConnectionSocket : public CDataConnectionBaseSocket // sets TgtDiskFileName and CurrentTransferMode, while also enabling flushing data // directly into the TgtDiskFileName file (uses FTPDiskThread) - void SetDirectFlushParams(const char* tgtFileName, CCurrentTransferMode currentTransferMode); + void SetDirectFlushParams(const char* tgtFileName, CCurrentTransferMode currentTransferMode, + const CQuadWord& resumeOffset); // returns the state of the target file when flushing data directly to the TgtDiskFileName file; // in 'fileCreated' it returns TRUE if the file was created; in 'fileSize' it returns the size diff --git a/src/plugins/ftp/datacon1.cpp b/src/plugins/ftp/datacon1.cpp index 4f09a974..8db7132a 100644 --- a/src/plugins/ftp/datacon1.cpp +++ b/src/plugins/ftp/datacon1.cpp @@ -425,6 +425,8 @@ CDataConnectionSocket::CDataConnectionSocket(BOOL flushData, CFTPProxyForDataCon TgtDiskFileCreated = FALSE; TgtFileLastError = NO_ERROR; TgtDiskFileSize.Set(0, 0); + TgtDiskFileResumeOffset.Set(0, 0); + TgtDiskFileAppend = FALSE; CurrentTransferMode = ctrmUnknown; AsciiTrModeForBinFileProblemOccured = FALSE; AsciiTrModeForBinFileHowToSolve = 0 /* ask the user */; @@ -706,6 +708,8 @@ void CDataConnectionSocket::DirectFlushData() DiskWork.FlushDataBuffer = flushBuffer; DiskWork.ValidBytesInFlushDataBuffer = validBytesInFlushBuffer; DiskWork.WorkFile = TgtDiskFile; + DiskWork.AppendToFile = TgtDiskFile == NULL && TgtDiskFileAppend; + DiskWork.WriteOrReadFromOffset = TgtDiskFileResumeOffset; if (FTPDiskThread->AddWork(&DiskWork)) DiskWorkIsUsed = TRUE; else // unable to flush the data, cannot continue with the download @@ -978,9 +982,9 @@ void CDataConnectionSocket::ReceiveNetEvent(LPARAM lParam, int index) ReadBytesAllocatedSize - ValidBytesInReadBytesBuf, 0); else { - if (SSLLib.SSL_pending(SSLConn) > 0) // if the internal SSL buffer is not empty, recv() is not called and no further FD_READ arrives, so we must post it ourselves, otherwise the data transfer stops + if (SSLPending(SSLConn) > 0) // if the internal TLS buffer is not empty, recv() is not called and no further FD_READ arrives, so we must post it ourselves, otherwise the data transfer stops PostMessage(SocketsThread->GetHiddenWindow(), Msg, (WPARAM)Socket, FD_READ); - len = SSLLib.SSL_read(SSLConn, ReadBytes + ValidBytesInReadBytesBuf, + len = SSLRead(SSLConn, ReadBytes + ValidBytesInReadBytesBuf, ReadBytesAllocatedSize - ValidBytesInReadBytesBuf); } if (len >= 0) // we may have read something (0 = the connection is already closed) @@ -1057,7 +1061,7 @@ void CDataConnectionSocket::ReceiveNetEvent(LPARAM lParam, int index) } else { - if (SSLConn && (WSAGETSELECTEVENT(lParam) == FD_READ) && (6 /*SSL_ERROR_ZERO_RETURN*/ == SSLLib.SSL_get_error(SSLConn, 0))) + if (SSLConn && (WSAGETSELECTEVENT(lParam) == FD_READ) && (SSL_ERROR_ZERO_RETURN == SSLGetError(SSLConn, 0))) { // seen at ftps://ftp.smartftp.com // SSL_ERROR_ZERO_RETURN: The TLS/SSL connection has been closed. @@ -1072,7 +1076,7 @@ void CDataConnectionSocket::ReceiveNetEvent(LPARAM lParam, int index) } else { - DWORD err = !SSLConn ? WSAGetLastError() : SSLtoWS2Error(SSLLib.SSL_get_error(SSLConn, len)); + DWORD err = !SSLConn ? WSAGetLastError() : SSLtoWS2Error(SSLGetError(SSLConn, len)); if (err != WSAEWOULDBLOCK) { NetEventLastError = err; // an error occurred @@ -1137,9 +1141,9 @@ void CDataConnectionSocket::ReceiveNetEvent(LPARAM lParam, int index) ReadBytesAllocatedSize - ValidBytesInReadBytesBuf, 0); else { - if (SSLLib.SSL_pending(SSLConn) > 0) // if the internal SSL buffer is not empty, recv() is not called and no further FD_READ arrives, so we must post it ourselves, otherwise the data transfer stops + if (SSLPending(SSLConn) > 0) // if the internal TLS buffer is not empty, recv() is not called and no further FD_READ arrives, so we must post it ourselves, otherwise the data transfer stops PostMessage(SocketsThread->GetHiddenWindow(), Msg, (WPARAM)Socket, FD_READ); - len = SSLLib.SSL_read(SSLConn, ReadBytes + ValidBytesInReadBytesBuf, + len = SSLRead(SSLConn, ReadBytes + ValidBytesInReadBytesBuf, ReadBytesAllocatedSize - ValidBytesInReadBytesBuf); } if (len >= 0) // we may have read something (0 = the connection is already closed) @@ -1160,7 +1164,7 @@ void CDataConnectionSocket::ReceiveNetEvent(LPARAM lParam, int index) } else { - if (SSLConn && (WSAGETSELECTEVENT(lParam) == FD_READ) && (6 /*SSL_ERROR_ZERO_RETURN*/ == SSLLib.SSL_get_error(SSLConn, 0))) + if (SSLConn && (WSAGETSELECTEVENT(lParam) == FD_READ) && (SSL_ERROR_ZERO_RETURN == SSLGetError(SSLConn, 0))) { // seen at ftps://ftp.smartftp.com // SSL_ERROR_ZERO_RETURN: The TLS/SSL connection has been closed. @@ -1175,7 +1179,7 @@ void CDataConnectionSocket::ReceiveNetEvent(LPARAM lParam, int index) } else { - DWORD err = !SSLConn ? WSAGetLastError() : SSLtoWS2Error(SSLLib.SSL_get_error(SSLConn, len)); + DWORD err = !SSLConn ? WSAGetLastError() : SSLtoWS2Error(SSLGetError(SSLConn, len)); if (err != WSAEWOULDBLOCK) { NetEventLastError = err; // an error occurred @@ -1349,13 +1353,18 @@ void CDataConnectionSocket::SetPostMessagesToWorker(BOOL post, int msg, int uid, HANDLES(LeaveCriticalSection(&SocketCritSect)); } -void CDataConnectionSocket::SetDirectFlushParams(const char* tgtFileName, CCurrentTransferMode currentTransferMode) +void CDataConnectionSocket::SetDirectFlushParams(const char* tgtFileName, CCurrentTransferMode currentTransferMode, + const CQuadWord& resumeOffset) { CALL_STACK_MESSAGE1("CDataConnectionSocket::SetDirectFlushParams()"); HANDLES(EnterCriticalSection(&SocketCritSect)); lstrcpyn(TgtDiskFileName, tgtFileName, MAX_PATH); CurrentTransferMode = currentTransferMode; + // Only the identity-validated binary prefix may survive an interrupted direct download. + TgtDiskFileResumeOffset = resumeOffset; + TgtDiskFileAppend = resumeOffset != CQuadWord(0, 0); + TgtDiskFileSize = resumeOffset; HANDLES(LeaveCriticalSection(&SocketCritSect)); } diff --git a/src/plugins/ftp/datacon2.cpp b/src/plugins/ftp/datacon2.cpp index 1964466a..595f93ae 100644 --- a/src/plugins/ftp/datacon2.cpp +++ b/src/plugins/ftp/datacon2.cpp @@ -497,9 +497,9 @@ void CKeepAliveDataConSocket::ReceiveNetEvent(LPARAM lParam, int index) len = recv(Socket, buf, KEEPALIVEDATACON_READBUFSIZE, 0); else { - if (SSLLib.SSL_pending(SSLConn) > 0) // if the internal SSL buffer is not empty recv() is not called and no further FD_READ arrives, so we must post it ourselves or the data transfer stops + if (SSLPending(SSLConn) > 0) // if the internal TLS buffer is not empty recv() is not called and no further FD_READ arrives, so we must post it ourselves or the data transfer stops PostMessage(SocketsThread->GetHiddenWindow(), Msg, (WPARAM)Socket, FD_READ); - len = SSLLib.SSL_read(SSLConn, buf, KEEPALIVEDATACON_READBUFSIZE); + len = SSLRead(SSLConn, buf, KEEPALIVEDATACON_READBUFSIZE); } if (len >= 0 /*!= SOCKET_ERROR*/) // we may have read something (0 = the connection is already closed) { @@ -511,7 +511,7 @@ void CKeepAliveDataConSocket::ReceiveNetEvent(LPARAM lParam, int index) } else if (SSLConn) { - if ((WSAGETSELECTEVENT(lParam) == FD_READ) && (6 /*SSL_ERROR_ZERO_RETURN*/ == SSLLib.SSL_get_error(SSLConn, 0))) + if ((WSAGETSELECTEVENT(lParam) == FD_READ) && (SSL_ERROR_ZERO_RETURN == SSLGetError(SSLConn, 0))) { // seen at ftps://ftp.smartftp.com // SSL_ERROR_ZERO_RETURN: The TLS/SSL connection has been closed. @@ -526,7 +526,7 @@ void CKeepAliveDataConSocket::ReceiveNetEvent(LPARAM lParam, int index) } else { - DWORD err = !SSLConn ? WSAGetLastError() : SSLtoWS2Error(SSLLib.SSL_get_error(SSLConn, len)); + DWORD err = !SSLConn ? WSAGetLastError() : SSLtoWS2Error(SSLGetError(SSLConn, len)); ; if (err != WSAEWOULDBLOCK) { @@ -1182,7 +1182,7 @@ void CUploadDataConnectionSocket::ReceiveNetEvent(LPARAM lParam, int index) if (!SSLConn) sentLen = send(Socket, BytesToWrite + BytesToWriteOffset, paketSize, 0); else - sentLen = SSLLib.SSL_write(SSLConn, BytesToWrite + BytesToWriteOffset, paketSize); + sentLen = SSLWrite(SSLConn, BytesToWrite + BytesToWriteOffset, paketSize); if (sentLen >= 0 /*!= SOCKET_ERROR*/) // at least something was successfully sent (or rather accepted by Windows; actual delivery is anyone's guess) { @@ -1281,7 +1281,7 @@ void CUploadDataConnectionSocket::ReceiveNetEvent(LPARAM lParam, int index) } else { - DWORD err = !SSLConn ? WSAGetLastError() : SSLtoWS2Error(SSLLib.SSL_get_error(SSLConn, sentLen)); + DWORD err = !SSLConn ? WSAGetLastError() : SSLtoWS2Error(SSLGetError(SSLConn, sentLen)); if (err == WSAEWOULDBLOCK) // nothing else can be sent (Windows has no buffer space left) WaitingForWriteEvent = TRUE; // stop sending and wait for another FD_WRITE else // a different error occurred - report it diff --git a/src/plugins/ftp/dialogs5.cpp b/src/plugins/ftp/dialogs5.cpp index 79f9df1a..24d0d1bb 100644 --- a/src/plugins/ftp/dialogs5.cpp +++ b/src/plugins/ftp/dialogs5.cpp @@ -1262,12 +1262,13 @@ void COperationDlg::SolveErrorOnConnection(int index) char errBuf2[300]; if (!unverifiedCertificate->CheckCertificate(errBuf2, 300)) // revalidate the certificate and obtain the corresponding error message { + CCertificateErrDialog certificateDialog(HWindow, errBuf2); INT_PTR dlgRes; do { CurrentFlashWnd = SalamanderGeneral->GetWndToFlash(HWindow); DlgWillCloseIfOpFinWithSkips = FALSE; - dlgRes = CCertificateErrDialog(HWindow, errBuf2).Execute(); + dlgRes = certificateDialog.Execute(); DlgWillCloseIfOpFinWithSkips = (IsDlgButtonChecked(HWindow, IDC_OPCLOSEWINWHENDONE) == BST_CHECKED); if (CurrentFlashWnd != NULL) { @@ -1280,6 +1281,10 @@ void COperationDlg::SolveErrorOnConnection(int index) { LastActivityTime = GetTickCount() - OPERDLG_SHOWERRMINIDLETIME; // simulate "idle" so the next error can appear immediately Oper->SetCertificate(unverifiedCertificate); + // A worker may reuse the decision only through the certificate's host, port, pin, and expiry scope. + if (unverifiedCertificate->RememberException(certificateDialog.RememberException() ? cesPersistent : cesSession) && + certificateDialog.RememberException()) + PersistFTPConfigurationAfterUserCommit(HWindow); // notify all workers with a connection error WorkersList->PostLoginChanged(-1); break; diff --git a/src/plugins/ftp/fs5.cpp b/src/plugins/ftp/fs5.cpp index d9f09381..b9d60289 100644 --- a/src/plugins/ftp/fs5.cpp +++ b/src/plugins/ftp/fs5.cpp @@ -447,8 +447,19 @@ void CPluginFSInterface::ViewFile(const char* fsName, HWND parent, if (dataIface == NULL || !dataIface->GetSize(file, fileSizeInBytes, sizeInBytes) || !sizeInBytes) fileSizeInBytes.Set(-1, -1); // the file size is unknown + // Keep the listing timestamp with the cached transfer so a changed remote file cannot resume an old prefix. + BOOL remoteDateAndTimeValid = FALSE; + CFTPDate remoteDate; + CFTPTime remoteTime; + memset(&remoteDate, 0, sizeof(remoteDate)); + memset(&remoteTime, 0, sizeof(remoteTime)); + remoteTime.Hour = 24; + if (dataIface != NULL) + dataIface->GetLastWriteDateAndTime(file, &remoteDateAndTimeValid, &remoteDate, &remoteTime); + ControlConnection->DownloadOneFile(parent, file.Name, fileSizeInBytes, asciiMode, Path, - tmpFileName, &newFileCreated, &newFileIncomplete, &newFileSize, + tmpFileName, remoteDateAndTimeValid, &remoteDate, &remoteTime, + &newFileCreated, &newFileIncomplete, &newFileSize, &TotalConnectAttemptNum, panel, notInPanel, User, USER_MAX_SIZE); } diff --git a/src/plugins/ftp/ftp.cpp b/src/plugins/ftp/ftp.cpp index 14194ce4..0120bccc 100644 --- a/src/plugins/ftp/ftp.cpp +++ b/src/plugins/ftp/ftp.cpp @@ -597,6 +597,9 @@ void CPluginInterface::LoadConfiguration(HWND parent, HKEY regKey, CSalamanderRe registry->GetValue(regKey, CONFIG_LASTBOOKMARK, REG_DWORD, &Config.LastBookmark, sizeof(DWORD)); + // Certificate exceptions are independent of bookmark identity but remain in the plug-in's transactional profile. + LoadCertificateExceptions(regKey, registry); + if (ConfigVersion != 1) // when moving from version 1 to 2 the server-types and ftp-servers lists in the registry are ignored { if (ConfigVersion < 2 || ConfigVersion >= RELOAD_PARSERS_BEFORE_CONFIG_VERSION) @@ -788,6 +791,9 @@ void CPluginInterface::SaveConfiguration(HWND parent, HKEY regKey, CSalamanderRe registry->SetValue(regKey, CONFIG_LASTBOOKMARK, REG_DWORD, &Config.LastBookmark, sizeof(DWORD)); + // Persist only explicitly remembered pins; session decisions never survive a process restart. + SaveCertificateExceptions(regKey, registry); + Config.LockServerTypeList()->Save(parent, regKey, registry); Config.UnlockServerTypeList(); diff --git a/src/plugins/ftp/ftp.h b/src/plugins/ftp/ftp.h index 9970a1a4..83e320e2 100644 --- a/src/plugins/ftp/ftp.h +++ b/src/plugins/ftp/ftp.h @@ -1157,8 +1157,8 @@ class CConfiguration extern CConfiguration Config; // global configuration of the FTP client -// Persists the FTP plug-in configuration after a completed user action so bookmarks are -// not dependent on the application's exit-time configuration save. +// Persists the FTP plug-in configuration after a completed user action so bookmarks and +// explicitly remembered certificate exceptions are not dependent on exit-time saving. void PersistFTPConfigurationAfterUserCommit(HWND parent); // diff --git a/src/plugins/ftp/help/hh/ftp/introduction_intro.htm b/src/plugins/ftp/help/hh/ftp/introduction_intro.htm index 0cd57806..ab7492b8 100644 --- a/src/plugins/ftp/help/hh/ftp/introduction_intro.htm +++ b/src/plugins/ftp/help/hh/ftp/introduction_intro.htm @@ -25,7 +25,7 @@

                      Getting Started with FTP Client Plugin

                      called FTPES, i.e. FTP with Explicit SSL. The plugin uses the same certificate stores and validity verification algorithms as Internet Explorer. All trusted certificates can be viewed in the Internet Options Control Panel. -FTPS uses OpenSSL libraries.

                      +FTPS uses the Windows SChannel TLS implementation and requires TLS 1.2 or later.

                      See File System and Menu Extension sections in Using Plugins for a description of basic work with file system plugin and menu extension.

                      diff --git a/src/plugins/ftp/lang/lang.rc b/src/plugins/ftp/lang/lang.rc index a5ca7489..2e880198 100644 --- a/src/plugins/ftp/lang/lang.rc +++ b/src/plugins/ftp/lang/lang.rc @@ -1068,7 +1068,7 @@ BEGIN PUSHBUTTON "Help",IDHELP,173,210,50,14 END -IDD_CERTIFICATE DIALOGEX 10, 24, 270, 120 +IDD_CERTIFICATE DIALOGEX 10, 24, 270, 138 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU CAPTION "Server Identity Problem" FONT 8, "MS Shell Dlg", 400, 0, 0x1 @@ -1078,10 +1078,12 @@ BEGIN GROUPBOX " Certificate verification summary ",IDC_STATIC_2,9,21,252,54 LTEXT "",IDT_CERTIFICATE_ERROR,16,32,238,40 LTEXT "Accepting the certificate is potentially dangerous and not recommended.",IDC_STATIC_3,7,80,258,8,NOT WS_GROUP - LTEXT "",IDC_STATIC_4,5,95,261,1,SS_ETCHEDHORZ | WS_GROUP - PUSHBUTTON "&View Certificate",IDB_CERTIFICATE_VIEW,10,100,80,14,WS_GROUP - PUSHBUTTON "&Accept Certificate",IDOK,95,100,80,14 - DEFPUSHBUTTON "Cancel",IDCANCEL,180,100,80,14 + CONTROL "Remember this exception for 30 days (this host, port, and certificate only)",IDB_CERTIFICATE_REMEMBER, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,10,93,250,10 + LTEXT "",IDC_STATIC_4,5,112,261,1,SS_ETCHEDHORZ | WS_GROUP + PUSHBUTTON "&View Certificate",IDB_CERTIFICATE_VIEW,10,118,80,14,WS_GROUP + PUSHBUTTON "&Accept Certificate",IDOK,95,118,80,14 + DEFPUSHBUTTON "Cancel",IDCANCEL,180,118,80,14 END diff --git a/src/plugins/ftp/lang/lang.rc2 b/src/plugins/ftp/lang/lang.rc2 index a4ed0f60..2dd0e42b 100644 --- a/src/plugins/ftp/lang/lang.rc2 +++ b/src/plugins/ftp/lang/lang.rc2 @@ -884,12 +884,12 @@ STRINGTABLE IDS_LOGMSGUPLRESUMEERR, "Unable to resume (append) the file (unexpected error).\r\n" IDS_LOGMSGUPLNOTCONFIRMED, "Entire file was sent but server did not confirm its receiving.\r\nGoing to get file size (in bytes) to test if file was successfully copied/moved.\r\n" - IDS_SSL_ERR_OPENSSLNOTFOUND "Unable to find or load OpenSSL libraries needed for encryption.\r\nThe files needed are ssleay32.dll and libeay32.dll.\r\nThey are part of Open Salamander installation." - IDS_SSL_ERR_NEW "Unable to allocate OpenSSL control structure. Out of memory." - IDS_SSL_ERR_NEW_LOG "SSL ERROR: Unable to allocate OpenSSL control structure. Out of memory.\r\n" + IDS_SSL_ERR_OPENSSLNOTFOUND "Windows TLS services are unavailable or do not support TLS 1.2 or later." + IDS_SSL_ERR_NEW "Unable to allocate the Windows TLS connection. Out of memory." + IDS_SSL_ERR_NEW_LOG "TLS ERROR: Unable to allocate the Windows TLS connection. Out of memory.\r\n" IDS_SSL_ERR_CONNECT "Unable to establish encrypted connection (SSL).\n\nError: %s" - IDS_SSL_ERR_CONNECT_ERR "SSL_connect returned %d: %s" - IDS_SSL_ERR_CONNECT_LOG "SSL ERROR: Unable to establish encrypted connection, SSL_connect returned %d: %s\r\n" + IDS_SSL_ERR_CONNECT_ERR "Windows TLS handshake returned %d: %s" + IDS_SSL_ERR_CONNECT_LOG "TLS ERROR: Unable to establish encrypted connection, handshake returned %d: %s\r\n" IDS_SSL_ERR_CONTRENCUNSUP "The server does not support encryption of the control connection.\nThe AUTH TLS command was not understood by the server." IDS_SSL_ERR_DATAENCUNSUP "The server does not support encryption of data connections." IDS_SSL_ERR_NOTYETVALID "The server certificate is not yet valid." diff --git a/src/plugins/ftp/lang/lang.rh b/src/plugins/ftp/lang/lang.rh index 0765f1da..6a66a84f 100644 --- a/src/plugins/ftp/lang/lang.rh +++ b/src/plugins/ftp/lang/lang.rh @@ -281,6 +281,7 @@ #define IDD_CERTIFICATE 940 #define IDT_CERTIFICATE_ERROR 941 #define IDB_CERTIFICATE_VIEW 942 +#define IDB_CERTIFICATE_REMEMBER 943 // Next default values for new objects // diff --git a/src/plugins/ftp/openssl/asn1.h b/src/plugins/ftp/openssl/asn1.h deleted file mode 100644 index 220a0c8c..00000000 --- a/src/plugins/ftp/openssl/asn1.h +++ /dev/null @@ -1,1404 +0,0 @@ -/* crypto/asn1/asn1.h */ -/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) - * All rights reserved. - * - * This package is an SSL implementation written - * by Eric Young (eay@cryptsoft.com). - * The implementation was written so as to conform with Netscapes SSL. - * - * This library is free for commercial and non-commercial use as long as - * the following conditions are aheared to. The following conditions - * apply to all code found in this distribution, be it the RC4, RSA, - * lhash, DES, etc., code; not just the SSL code. The SSL documentation - * included with this distribution is covered by the same copyright terms - * except that the holder is Tim Hudson (tjh@cryptsoft.com). - * - * Copyright remains Eric Young's, and as such any Copyright notices in - * the code are not to be removed. - * If this package is used in a product, Eric Young should be given attribution - * as the author of the parts of the library used. - * This can be in the form of a textual message at program startup or - * in documentation (online or textual) provided with the package. - * - * 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 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. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * "This product includes cryptographic software written by - * Eric Young (eay@cryptsoft.com)" - * The word 'cryptographic' can be left out if the rouines from the library - * being used are not cryptographic related :-). - * 4. If you include any Windows specific code (or a derivative thereof) from - * the apps directory (application code) you must include an acknowledgement: - * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" - * - * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``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 AUTHOR 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. - * - * The licence and distribution terms for any publically available version or - * derivative of this code cannot be changed. i.e. this code cannot simply be - * copied and put under another distribution licence - * [including the GNU Public Licence.] - */ - -#ifndef HEADER_ASN1_H -#define HEADER_ASN1_H - -#include -#include -#ifndef OPENSSL_NO_BIO -#include -#endif -#include -#include - -#include - -#include -#ifndef OPENSSL_NO_DEPRECATED -#include -#endif - -#ifdef OPENSSL_BUILD_SHLIBCRYPTO -# undef OPENSSL_EXTERN -# define OPENSSL_EXTERN OPENSSL_EXPORT -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -#define V_ASN1_UNIVERSAL 0x00 -#define V_ASN1_APPLICATION 0x40 -#define V_ASN1_CONTEXT_SPECIFIC 0x80 -#define V_ASN1_PRIVATE 0xc0 - -#define V_ASN1_CONSTRUCTED 0x20 -#define V_ASN1_PRIMITIVE_TAG 0x1f -#define V_ASN1_PRIMATIVE_TAG 0x1f - -#define V_ASN1_APP_CHOOSE -2 /* let the recipient choose */ -#define V_ASN1_OTHER -3 /* used in ASN1_TYPE */ -#define V_ASN1_ANY -4 /* used in ASN1 template code */ - -#define V_ASN1_NEG 0x100 /* negative flag */ - -#define V_ASN1_UNDEF -1 -#define V_ASN1_EOC 0 -#define V_ASN1_BOOLEAN 1 /**/ -#define V_ASN1_INTEGER 2 -#define V_ASN1_NEG_INTEGER (2 | V_ASN1_NEG) -#define V_ASN1_BIT_STRING 3 -#define V_ASN1_OCTET_STRING 4 -#define V_ASN1_NULL 5 -#define V_ASN1_OBJECT 6 -#define V_ASN1_OBJECT_DESCRIPTOR 7 -#define V_ASN1_EXTERNAL 8 -#define V_ASN1_REAL 9 -#define V_ASN1_ENUMERATED 10 -#define V_ASN1_NEG_ENUMERATED (10 | V_ASN1_NEG) -#define V_ASN1_UTF8STRING 12 -#define V_ASN1_SEQUENCE 16 -#define V_ASN1_SET 17 -#define V_ASN1_NUMERICSTRING 18 /**/ -#define V_ASN1_PRINTABLESTRING 19 -#define V_ASN1_T61STRING 20 -#define V_ASN1_TELETEXSTRING 20 /* alias */ -#define V_ASN1_VIDEOTEXSTRING 21 /**/ -#define V_ASN1_IA5STRING 22 -#define V_ASN1_UTCTIME 23 -#define V_ASN1_GENERALIZEDTIME 24 /**/ -#define V_ASN1_GRAPHICSTRING 25 /**/ -#define V_ASN1_ISO64STRING 26 /**/ -#define V_ASN1_VISIBLESTRING 26 /* alias */ -#define V_ASN1_GENERALSTRING 27 /**/ -#define V_ASN1_UNIVERSALSTRING 28 /**/ -#define V_ASN1_BMPSTRING 30 - -/* For use with d2i_ASN1_type_bytes() */ -#define B_ASN1_NUMERICSTRING 0x0001 -#define B_ASN1_PRINTABLESTRING 0x0002 -#define B_ASN1_T61STRING 0x0004 -#define B_ASN1_TELETEXSTRING 0x0004 -#define B_ASN1_VIDEOTEXSTRING 0x0008 -#define B_ASN1_IA5STRING 0x0010 -#define B_ASN1_GRAPHICSTRING 0x0020 -#define B_ASN1_ISO64STRING 0x0040 -#define B_ASN1_VISIBLESTRING 0x0040 -#define B_ASN1_GENERALSTRING 0x0080 -#define B_ASN1_UNIVERSALSTRING 0x0100 -#define B_ASN1_OCTET_STRING 0x0200 -#define B_ASN1_BIT_STRING 0x0400 -#define B_ASN1_BMPSTRING 0x0800 -#define B_ASN1_UNKNOWN 0x1000 -#define B_ASN1_UTF8STRING 0x2000 -#define B_ASN1_UTCTIME 0x4000 -#define B_ASN1_GENERALIZEDTIME 0x8000 -#define B_ASN1_SEQUENCE 0x10000 - -/* For use with ASN1_mbstring_copy() */ -#define MBSTRING_FLAG 0x1000 -#define MBSTRING_UTF8 (MBSTRING_FLAG) -#define MBSTRING_ASC (MBSTRING_FLAG|1) -#define MBSTRING_BMP (MBSTRING_FLAG|2) -#define MBSTRING_UNIV (MBSTRING_FLAG|4) - -#define SMIME_OLDMIME 0x400 -#define SMIME_CRLFEOL 0x800 -#define SMIME_STREAM 0x1000 - -struct X509_algor_st; -DECLARE_STACK_OF(X509_ALGOR) - -#define DECLARE_ASN1_SET_OF(type) /* filled in by mkstack.pl */ -#define IMPLEMENT_ASN1_SET_OF(type) /* nothing, no longer needed */ - -/* We MUST make sure that, except for constness, asn1_ctx_st and - asn1_const_ctx are exactly the same. Fortunately, as soon as - the old ASN1 parsing macros are gone, we can throw this away - as well... */ -typedef struct asn1_ctx_st - { - unsigned char *p;/* work char pointer */ - int eos; /* end of sequence read for indefinite encoding */ - int error; /* error code to use when returning an error */ - int inf; /* constructed if 0x20, indefinite is 0x21 */ - int tag; /* tag from last 'get object' */ - int xclass; /* class from last 'get object' */ - long slen; /* length of last 'get object' */ - unsigned char *max; /* largest value of p allowed */ - unsigned char *q;/* temporary variable */ - unsigned char **pp;/* variable */ - int line; /* used in error processing */ - } ASN1_CTX; - -typedef struct asn1_const_ctx_st - { - const unsigned char *p;/* work char pointer */ - int eos; /* end of sequence read for indefinite encoding */ - int error; /* error code to use when returning an error */ - int inf; /* constructed if 0x20, indefinite is 0x21 */ - int tag; /* tag from last 'get object' */ - int xclass; /* class from last 'get object' */ - long slen; /* length of last 'get object' */ - const unsigned char *max; /* largest value of p allowed */ - const unsigned char *q;/* temporary variable */ - const unsigned char **pp;/* variable */ - int line; /* used in error processing */ - } ASN1_const_CTX; - -/* These are used internally in the ASN1_OBJECT to keep track of - * whether the names and data need to be free()ed */ -#define ASN1_OBJECT_FLAG_DYNAMIC 0x01 /* internal use */ -#define ASN1_OBJECT_FLAG_CRITICAL 0x02 /* critical x509v3 object id */ -#define ASN1_OBJECT_FLAG_DYNAMIC_STRINGS 0x04 /* internal use */ -#define ASN1_OBJECT_FLAG_DYNAMIC_DATA 0x08 /* internal use */ -typedef struct asn1_object_st - { - const char *sn,*ln; - int nid; - int length; - const unsigned char *data; /* data remains const after init */ - int flags; /* Should we free this one */ - } ASN1_OBJECT; - -#define ASN1_STRING_FLAG_BITS_LEFT 0x08 /* Set if 0x07 has bits left value */ -/* This indicates that the ASN1_STRING is not a real value but just a place - * holder for the location where indefinite length constructed data should - * be inserted in the memory buffer - */ -#define ASN1_STRING_FLAG_NDEF 0x010 - -/* This flag is used by the CMS code to indicate that a string is not - * complete and is a place holder for content when it had all been - * accessed. The flag will be reset when content has been written to it. - */ - -#define ASN1_STRING_FLAG_CONT 0x020 -/* This flag is used by ASN1 code to indicate an ASN1_STRING is an MSTRING - * type. - */ -#define ASN1_STRING_FLAG_MSTRING 0x040 -/* This is the base type that holds just about everything :-) */ -struct asn1_string_st - { - int length; - int type; - unsigned char *data; - /* The value of the following field depends on the type being - * held. It is mostly being used for BIT_STRING so if the - * input data has a non-zero 'unused bits' value, it will be - * handled correctly */ - long flags; - }; - -/* ASN1_ENCODING structure: this is used to save the received - * encoding of an ASN1 type. This is useful to get round - * problems with invalid encodings which can break signatures. - */ - -typedef struct ASN1_ENCODING_st - { - unsigned char *enc; /* DER encoding */ - long len; /* Length of encoding */ - int modified; /* set to 1 if 'enc' is invalid */ - } ASN1_ENCODING; - -/* Used with ASN1 LONG type: if a long is set to this it is omitted */ -#define ASN1_LONG_UNDEF 0x7fffffffL - -#define STABLE_FLAGS_MALLOC 0x01 -#define STABLE_NO_MASK 0x02 -#define DIRSTRING_TYPE \ - (B_ASN1_PRINTABLESTRING|B_ASN1_T61STRING|B_ASN1_BMPSTRING|B_ASN1_UTF8STRING) -#define PKCS9STRING_TYPE (DIRSTRING_TYPE|B_ASN1_IA5STRING) - -typedef struct asn1_string_table_st { - int nid; - long minsize; - long maxsize; - unsigned long mask; - unsigned long flags; -} ASN1_STRING_TABLE; - -DECLARE_STACK_OF(ASN1_STRING_TABLE) - -/* size limits: this stuff is taken straight from RFC2459 */ - -#define ub_name 32768 -#define ub_common_name 64 -#define ub_locality_name 128 -#define ub_state_name 128 -#define ub_organization_name 64 -#define ub_organization_unit_name 64 -#define ub_title 64 -#define ub_email_address 128 - -/* Declarations for template structures: for full definitions - * see asn1t.h - */ -typedef struct ASN1_TEMPLATE_st ASN1_TEMPLATE; -typedef struct ASN1_TLC_st ASN1_TLC; -/* This is just an opaque pointer */ -typedef struct ASN1_VALUE_st ASN1_VALUE; - -/* Declare ASN1 functions: the implement macro in in asn1t.h */ - -#define DECLARE_ASN1_FUNCTIONS(type) DECLARE_ASN1_FUNCTIONS_name(type, type) - -#define DECLARE_ASN1_ALLOC_FUNCTIONS(type) \ - DECLARE_ASN1_ALLOC_FUNCTIONS_name(type, type) - -#define DECLARE_ASN1_FUNCTIONS_name(type, name) \ - DECLARE_ASN1_ALLOC_FUNCTIONS_name(type, name) \ - DECLARE_ASN1_ENCODE_FUNCTIONS(type, name, name) - -#define DECLARE_ASN1_FUNCTIONS_fname(type, itname, name) \ - DECLARE_ASN1_ALLOC_FUNCTIONS_name(type, name) \ - DECLARE_ASN1_ENCODE_FUNCTIONS(type, itname, name) - -#define DECLARE_ASN1_ENCODE_FUNCTIONS(type, itname, name) \ - type *d2i_##name(type **a, const unsigned char **in, long len); \ - int i2d_##name(type *a, unsigned char **out); \ - DECLARE_ASN1_ITEM(itname) - -#define DECLARE_ASN1_ENCODE_FUNCTIONS_const(type, name) \ - type *d2i_##name(type **a, const unsigned char **in, long len); \ - int i2d_##name(const type *a, unsigned char **out); \ - DECLARE_ASN1_ITEM(name) - -#define DECLARE_ASN1_NDEF_FUNCTION(name) \ - int i2d_##name##_NDEF(name *a, unsigned char **out); - -#define DECLARE_ASN1_FUNCTIONS_const(name) \ - DECLARE_ASN1_ALLOC_FUNCTIONS(name) \ - DECLARE_ASN1_ENCODE_FUNCTIONS_const(name, name) - -#define DECLARE_ASN1_ALLOC_FUNCTIONS_name(type, name) \ - type *name##_new(void); \ - void name##_free(type *a); - -#define DECLARE_ASN1_PRINT_FUNCTION(stname) \ - DECLARE_ASN1_PRINT_FUNCTION_fname(stname, stname) - -#define DECLARE_ASN1_PRINT_FUNCTION_fname(stname, fname) \ - int fname##_print_ctx(BIO *out, stname *x, int indent, \ - const ASN1_PCTX *pctx); - -#define D2I_OF(type) type *(*)(type **,const unsigned char **,long) -#define I2D_OF(type) int (*)(type *,unsigned char **) -#define I2D_OF_const(type) int (*)(const type *,unsigned char **) - -#define CHECKED_D2I_OF(type, d2i) \ - ((d2i_of_void*) (1 ? d2i : ((D2I_OF(type))0))) -#define CHECKED_I2D_OF(type, i2d) \ - ((i2d_of_void*) (1 ? i2d : ((I2D_OF(type))0))) -#define CHECKED_NEW_OF(type, xnew) \ - ((void *(*)(void)) (1 ? xnew : ((type *(*)(void))0))) -#define CHECKED_PTR_OF(type, p) \ - ((void*) (1 ? p : (type*)0)) -#define CHECKED_PPTR_OF(type, p) \ - ((void**) (1 ? p : (type**)0)) - -#define TYPEDEF_D2I_OF(type) typedef type *d2i_of_##type(type **,const unsigned char **,long) -#define TYPEDEF_I2D_OF(type) typedef int i2d_of_##type(type *,unsigned char **) -#define TYPEDEF_D2I2D_OF(type) TYPEDEF_D2I_OF(type); TYPEDEF_I2D_OF(type) - -TYPEDEF_D2I2D_OF(void); - -/* The following macros and typedefs allow an ASN1_ITEM - * to be embedded in a structure and referenced. Since - * the ASN1_ITEM pointers need to be globally accessible - * (possibly from shared libraries) they may exist in - * different forms. On platforms that support it the - * ASN1_ITEM structure itself will be globally exported. - * Other platforms will export a function that returns - * an ASN1_ITEM pointer. - * - * To handle both cases transparently the macros below - * should be used instead of hard coding an ASN1_ITEM - * pointer in a structure. - * - * The structure will look like this: - * - * typedef struct SOMETHING_st { - * ... - * ASN1_ITEM_EXP *iptr; - * ... - * } SOMETHING; - * - * It would be initialised as e.g.: - * - * SOMETHING somevar = {...,ASN1_ITEM_ref(X509),...}; - * - * and the actual pointer extracted with: - * - * const ASN1_ITEM *it = ASN1_ITEM_ptr(somevar.iptr); - * - * Finally an ASN1_ITEM pointer can be extracted from an - * appropriate reference with: ASN1_ITEM_rptr(X509). This - * would be used when a function takes an ASN1_ITEM * argument. - * - */ - -#ifndef OPENSSL_EXPORT_VAR_AS_FUNCTION - -/* ASN1_ITEM pointer exported type */ -typedef const ASN1_ITEM ASN1_ITEM_EXP; - -/* Macro to obtain ASN1_ITEM pointer from exported type */ -#define ASN1_ITEM_ptr(iptr) (iptr) - -/* Macro to include ASN1_ITEM pointer from base type */ -#define ASN1_ITEM_ref(iptr) (&(iptr##_it)) - -#define ASN1_ITEM_rptr(ref) (&(ref##_it)) - -#define DECLARE_ASN1_ITEM(name) \ - OPENSSL_EXTERN const ASN1_ITEM name##_it; - -#else - -/* Platforms that can't easily handle shared global variables are declared - * as functions returning ASN1_ITEM pointers. - */ - -/* ASN1_ITEM pointer exported type */ -typedef const ASN1_ITEM * ASN1_ITEM_EXP(void); - -/* Macro to obtain ASN1_ITEM pointer from exported type */ -#define ASN1_ITEM_ptr(iptr) (iptr()) - -/* Macro to include ASN1_ITEM pointer from base type */ -#define ASN1_ITEM_ref(iptr) (iptr##_it) - -#define ASN1_ITEM_rptr(ref) (ref##_it()) - -#define DECLARE_ASN1_ITEM(name) \ - const ASN1_ITEM * name##_it(void); - -#endif - -/* Parameters used by ASN1_STRING_print_ex() */ - -/* These determine which characters to escape: - * RFC2253 special characters, control characters and - * MSB set characters - */ - -#define ASN1_STRFLGS_ESC_2253 1 -#define ASN1_STRFLGS_ESC_CTRL 2 -#define ASN1_STRFLGS_ESC_MSB 4 - - -/* This flag determines how we do escaping: normally - * RC2253 backslash only, set this to use backslash and - * quote. - */ - -#define ASN1_STRFLGS_ESC_QUOTE 8 - - -/* These three flags are internal use only. */ - -/* Character is a valid PrintableString character */ -#define CHARTYPE_PRINTABLESTRING 0x10 -/* Character needs escaping if it is the first character */ -#define CHARTYPE_FIRST_ESC_2253 0x20 -/* Character needs escaping if it is the last character */ -#define CHARTYPE_LAST_ESC_2253 0x40 - -/* NB the internal flags are safely reused below by flags - * handled at the top level. - */ - -/* If this is set we convert all character strings - * to UTF8 first - */ - -#define ASN1_STRFLGS_UTF8_CONVERT 0x10 - -/* If this is set we don't attempt to interpret content: - * just assume all strings are 1 byte per character. This - * will produce some pretty odd looking output! - */ - -#define ASN1_STRFLGS_IGNORE_TYPE 0x20 - -/* If this is set we include the string type in the output */ -#define ASN1_STRFLGS_SHOW_TYPE 0x40 - -/* This determines which strings to display and which to - * 'dump' (hex dump of content octets or DER encoding). We can - * only dump non character strings or everything. If we - * don't dump 'unknown' they are interpreted as character - * strings with 1 octet per character and are subject to - * the usual escaping options. - */ - -#define ASN1_STRFLGS_DUMP_ALL 0x80 -#define ASN1_STRFLGS_DUMP_UNKNOWN 0x100 - -/* These determine what 'dumping' does, we can dump the - * content octets or the DER encoding: both use the - * RFC2253 #XXXXX notation. - */ - -#define ASN1_STRFLGS_DUMP_DER 0x200 - -/* All the string flags consistent with RFC2253, - * escaping control characters isn't essential in - * RFC2253 but it is advisable anyway. - */ - -#define ASN1_STRFLGS_RFC2253 (ASN1_STRFLGS_ESC_2253 | \ - ASN1_STRFLGS_ESC_CTRL | \ - ASN1_STRFLGS_ESC_MSB | \ - ASN1_STRFLGS_UTF8_CONVERT | \ - ASN1_STRFLGS_DUMP_UNKNOWN | \ - ASN1_STRFLGS_DUMP_DER) - -DECLARE_STACK_OF(ASN1_INTEGER) -DECLARE_ASN1_SET_OF(ASN1_INTEGER) - -DECLARE_STACK_OF(ASN1_GENERALSTRING) - -typedef struct asn1_type_st - { - int type; - union { - char *ptr; - ASN1_BOOLEAN boolean; - ASN1_STRING * asn1_string; - ASN1_OBJECT * object; - ASN1_INTEGER * integer; - ASN1_ENUMERATED * enumerated; - ASN1_BIT_STRING * bit_string; - ASN1_OCTET_STRING * octet_string; - ASN1_PRINTABLESTRING * printablestring; - ASN1_T61STRING * t61string; - ASN1_IA5STRING * ia5string; - ASN1_GENERALSTRING * generalstring; - ASN1_BMPSTRING * bmpstring; - ASN1_UNIVERSALSTRING * universalstring; - ASN1_UTCTIME * utctime; - ASN1_GENERALIZEDTIME * generalizedtime; - ASN1_VISIBLESTRING * visiblestring; - ASN1_UTF8STRING * utf8string; - /* set and sequence are left complete and still - * contain the set or sequence bytes */ - ASN1_STRING * set; - ASN1_STRING * sequence; - ASN1_VALUE * asn1_value; - } value; - } ASN1_TYPE; - -DECLARE_STACK_OF(ASN1_TYPE) -DECLARE_ASN1_SET_OF(ASN1_TYPE) - -typedef STACK_OF(ASN1_TYPE) ASN1_SEQUENCE_ANY; - -DECLARE_ASN1_ENCODE_FUNCTIONS_const(ASN1_SEQUENCE_ANY, ASN1_SEQUENCE_ANY) -DECLARE_ASN1_ENCODE_FUNCTIONS_const(ASN1_SEQUENCE_ANY, ASN1_SET_ANY) - -typedef struct NETSCAPE_X509_st - { - ASN1_OCTET_STRING *header; - X509 *cert; - } NETSCAPE_X509; - -/* This is used to contain a list of bit names */ -typedef struct BIT_STRING_BITNAME_st { - int bitnum; - const char *lname; - const char *sname; -} BIT_STRING_BITNAME; - - -#define M_ASN1_STRING_length(x) ((x)->length) -#define M_ASN1_STRING_length_set(x, n) ((x)->length = (n)) -#define M_ASN1_STRING_type(x) ((x)->type) -#define M_ASN1_STRING_data(x) ((x)->data) - -/* Macros for string operations */ -#define M_ASN1_BIT_STRING_new() (ASN1_BIT_STRING *)\ - ASN1_STRING_type_new(V_ASN1_BIT_STRING) -#define M_ASN1_BIT_STRING_free(a) ASN1_STRING_free((ASN1_STRING *)a) -#define M_ASN1_BIT_STRING_dup(a) (ASN1_BIT_STRING *)\ - ASN1_STRING_dup((const ASN1_STRING *)a) -#define M_ASN1_BIT_STRING_cmp(a,b) ASN1_STRING_cmp(\ - (const ASN1_STRING *)a,(const ASN1_STRING *)b) -#define M_ASN1_BIT_STRING_set(a,b,c) ASN1_STRING_set((ASN1_STRING *)a,b,c) - -#define M_ASN1_INTEGER_new() (ASN1_INTEGER *)\ - ASN1_STRING_type_new(V_ASN1_INTEGER) -#define M_ASN1_INTEGER_free(a) ASN1_STRING_free((ASN1_STRING *)a) -#define M_ASN1_INTEGER_dup(a) (ASN1_INTEGER *)\ - ASN1_STRING_dup((const ASN1_STRING *)a) -#define M_ASN1_INTEGER_cmp(a,b) ASN1_STRING_cmp(\ - (const ASN1_STRING *)a,(const ASN1_STRING *)b) - -#define M_ASN1_ENUMERATED_new() (ASN1_ENUMERATED *)\ - ASN1_STRING_type_new(V_ASN1_ENUMERATED) -#define M_ASN1_ENUMERATED_free(a) ASN1_STRING_free((ASN1_STRING *)a) -#define M_ASN1_ENUMERATED_dup(a) (ASN1_ENUMERATED *)\ - ASN1_STRING_dup((const ASN1_STRING *)a) -#define M_ASN1_ENUMERATED_cmp(a,b) ASN1_STRING_cmp(\ - (const ASN1_STRING *)a,(const ASN1_STRING *)b) - -#define M_ASN1_OCTET_STRING_new() (ASN1_OCTET_STRING *)\ - ASN1_STRING_type_new(V_ASN1_OCTET_STRING) -#define M_ASN1_OCTET_STRING_free(a) ASN1_STRING_free((ASN1_STRING *)a) -#define M_ASN1_OCTET_STRING_dup(a) (ASN1_OCTET_STRING *)\ - ASN1_STRING_dup((const ASN1_STRING *)a) -#define M_ASN1_OCTET_STRING_cmp(a,b) ASN1_STRING_cmp(\ - (const ASN1_STRING *)a,(const ASN1_STRING *)b) -#define M_ASN1_OCTET_STRING_set(a,b,c) ASN1_STRING_set((ASN1_STRING *)a,b,c) -#define M_ASN1_OCTET_STRING_print(a,b) ASN1_STRING_print(a,(ASN1_STRING *)b) -#define M_i2d_ASN1_OCTET_STRING(a,pp) \ - i2d_ASN1_bytes((ASN1_STRING *)a,pp,V_ASN1_OCTET_STRING,\ - V_ASN1_UNIVERSAL) - -#define B_ASN1_TIME \ - B_ASN1_UTCTIME | \ - B_ASN1_GENERALIZEDTIME - -#define B_ASN1_PRINTABLE \ - B_ASN1_NUMERICSTRING| \ - B_ASN1_PRINTABLESTRING| \ - B_ASN1_T61STRING| \ - B_ASN1_IA5STRING| \ - B_ASN1_BIT_STRING| \ - B_ASN1_UNIVERSALSTRING|\ - B_ASN1_BMPSTRING|\ - B_ASN1_UTF8STRING|\ - B_ASN1_SEQUENCE|\ - B_ASN1_UNKNOWN - -#define B_ASN1_DIRECTORYSTRING \ - B_ASN1_PRINTABLESTRING| \ - B_ASN1_TELETEXSTRING|\ - B_ASN1_BMPSTRING|\ - B_ASN1_UNIVERSALSTRING|\ - B_ASN1_UTF8STRING - -#define B_ASN1_DISPLAYTEXT \ - B_ASN1_IA5STRING| \ - B_ASN1_VISIBLESTRING| \ - B_ASN1_BMPSTRING|\ - B_ASN1_UTF8STRING - -#define M_ASN1_PRINTABLE_new() ASN1_STRING_type_new(V_ASN1_T61STRING) -#define M_ASN1_PRINTABLE_free(a) ASN1_STRING_free((ASN1_STRING *)a) -#define M_i2d_ASN1_PRINTABLE(a,pp) i2d_ASN1_bytes((ASN1_STRING *)a,\ - pp,a->type,V_ASN1_UNIVERSAL) -#define M_d2i_ASN1_PRINTABLE(a,pp,l) \ - d2i_ASN1_type_bytes((ASN1_STRING **)a,pp,l, \ - B_ASN1_PRINTABLE) - -#define M_DIRECTORYSTRING_new() ASN1_STRING_type_new(V_ASN1_PRINTABLESTRING) -#define M_DIRECTORYSTRING_free(a) ASN1_STRING_free((ASN1_STRING *)a) -#define M_i2d_DIRECTORYSTRING(a,pp) i2d_ASN1_bytes((ASN1_STRING *)a,\ - pp,a->type,V_ASN1_UNIVERSAL) -#define M_d2i_DIRECTORYSTRING(a,pp,l) \ - d2i_ASN1_type_bytes((ASN1_STRING **)a,pp,l, \ - B_ASN1_DIRECTORYSTRING) - -#define M_DISPLAYTEXT_new() ASN1_STRING_type_new(V_ASN1_VISIBLESTRING) -#define M_DISPLAYTEXT_free(a) ASN1_STRING_free((ASN1_STRING *)a) -#define M_i2d_DISPLAYTEXT(a,pp) i2d_ASN1_bytes((ASN1_STRING *)a,\ - pp,a->type,V_ASN1_UNIVERSAL) -#define M_d2i_DISPLAYTEXT(a,pp,l) \ - d2i_ASN1_type_bytes((ASN1_STRING **)a,pp,l, \ - B_ASN1_DISPLAYTEXT) - -#define M_ASN1_PRINTABLESTRING_new() (ASN1_PRINTABLESTRING *)\ - ASN1_STRING_type_new(V_ASN1_PRINTABLESTRING) -#define M_ASN1_PRINTABLESTRING_free(a) ASN1_STRING_free((ASN1_STRING *)a) -#define M_i2d_ASN1_PRINTABLESTRING(a,pp) \ - i2d_ASN1_bytes((ASN1_STRING *)a,pp,V_ASN1_PRINTABLESTRING,\ - V_ASN1_UNIVERSAL) -#define M_d2i_ASN1_PRINTABLESTRING(a,pp,l) \ - (ASN1_PRINTABLESTRING *)d2i_ASN1_type_bytes\ - ((ASN1_STRING **)a,pp,l,B_ASN1_PRINTABLESTRING) - -#define M_ASN1_T61STRING_new() (ASN1_T61STRING *)\ - ASN1_STRING_type_new(V_ASN1_T61STRING) -#define M_ASN1_T61STRING_free(a) ASN1_STRING_free((ASN1_STRING *)a) -#define M_i2d_ASN1_T61STRING(a,pp) \ - i2d_ASN1_bytes((ASN1_STRING *)a,pp,V_ASN1_T61STRING,\ - V_ASN1_UNIVERSAL) -#define M_d2i_ASN1_T61STRING(a,pp,l) \ - (ASN1_T61STRING *)d2i_ASN1_type_bytes\ - ((ASN1_STRING **)a,pp,l,B_ASN1_T61STRING) - -#define M_ASN1_IA5STRING_new() (ASN1_IA5STRING *)\ - ASN1_STRING_type_new(V_ASN1_IA5STRING) -#define M_ASN1_IA5STRING_free(a) ASN1_STRING_free((ASN1_STRING *)a) -#define M_ASN1_IA5STRING_dup(a) \ - (ASN1_IA5STRING *)ASN1_STRING_dup((const ASN1_STRING *)a) -#define M_i2d_ASN1_IA5STRING(a,pp) \ - i2d_ASN1_bytes((ASN1_STRING *)a,pp,V_ASN1_IA5STRING,\ - V_ASN1_UNIVERSAL) -#define M_d2i_ASN1_IA5STRING(a,pp,l) \ - (ASN1_IA5STRING *)d2i_ASN1_type_bytes((ASN1_STRING **)a,pp,l,\ - B_ASN1_IA5STRING) - -#define M_ASN1_UTCTIME_new() (ASN1_UTCTIME *)\ - ASN1_STRING_type_new(V_ASN1_UTCTIME) -#define M_ASN1_UTCTIME_free(a) ASN1_STRING_free((ASN1_STRING *)a) -#define M_ASN1_UTCTIME_dup(a) (ASN1_UTCTIME *)\ - ASN1_STRING_dup((const ASN1_STRING *)a) - -#define M_ASN1_GENERALIZEDTIME_new() (ASN1_GENERALIZEDTIME *)\ - ASN1_STRING_type_new(V_ASN1_GENERALIZEDTIME) -#define M_ASN1_GENERALIZEDTIME_free(a) ASN1_STRING_free((ASN1_STRING *)a) -#define M_ASN1_GENERALIZEDTIME_dup(a) (ASN1_GENERALIZEDTIME *)ASN1_STRING_dup(\ - (const ASN1_STRING *)a) - -#define M_ASN1_TIME_new() (ASN1_TIME *)\ - ASN1_STRING_type_new(V_ASN1_UTCTIME) -#define M_ASN1_TIME_free(a) ASN1_STRING_free((ASN1_STRING *)a) -#define M_ASN1_TIME_dup(a) (ASN1_TIME *)\ - ASN1_STRING_dup((const ASN1_STRING *)a) - -#define M_ASN1_GENERALSTRING_new() (ASN1_GENERALSTRING *)\ - ASN1_STRING_type_new(V_ASN1_GENERALSTRING) -#define M_ASN1_GENERALSTRING_free(a) ASN1_STRING_free((ASN1_STRING *)a) -#define M_i2d_ASN1_GENERALSTRING(a,pp) \ - i2d_ASN1_bytes((ASN1_STRING *)a,pp,V_ASN1_GENERALSTRING,\ - V_ASN1_UNIVERSAL) -#define M_d2i_ASN1_GENERALSTRING(a,pp,l) \ - (ASN1_GENERALSTRING *)d2i_ASN1_type_bytes\ - ((ASN1_STRING **)a,pp,l,B_ASN1_GENERALSTRING) - -#define M_ASN1_UNIVERSALSTRING_new() (ASN1_UNIVERSALSTRING *)\ - ASN1_STRING_type_new(V_ASN1_UNIVERSALSTRING) -#define M_ASN1_UNIVERSALSTRING_free(a) ASN1_STRING_free((ASN1_STRING *)a) -#define M_i2d_ASN1_UNIVERSALSTRING(a,pp) \ - i2d_ASN1_bytes((ASN1_STRING *)a,pp,V_ASN1_UNIVERSALSTRING,\ - V_ASN1_UNIVERSAL) -#define M_d2i_ASN1_UNIVERSALSTRING(a,pp,l) \ - (ASN1_UNIVERSALSTRING *)d2i_ASN1_type_bytes\ - ((ASN1_STRING **)a,pp,l,B_ASN1_UNIVERSALSTRING) - -#define M_ASN1_BMPSTRING_new() (ASN1_BMPSTRING *)\ - ASN1_STRING_type_new(V_ASN1_BMPSTRING) -#define M_ASN1_BMPSTRING_free(a) ASN1_STRING_free((ASN1_STRING *)a) -#define M_i2d_ASN1_BMPSTRING(a,pp) \ - i2d_ASN1_bytes((ASN1_STRING *)a,pp,V_ASN1_BMPSTRING,\ - V_ASN1_UNIVERSAL) -#define M_d2i_ASN1_BMPSTRING(a,pp,l) \ - (ASN1_BMPSTRING *)d2i_ASN1_type_bytes\ - ((ASN1_STRING **)a,pp,l,B_ASN1_BMPSTRING) - -#define M_ASN1_VISIBLESTRING_new() (ASN1_VISIBLESTRING *)\ - ASN1_STRING_type_new(V_ASN1_VISIBLESTRING) -#define M_ASN1_VISIBLESTRING_free(a) ASN1_STRING_free((ASN1_STRING *)a) -#define M_i2d_ASN1_VISIBLESTRING(a,pp) \ - i2d_ASN1_bytes((ASN1_STRING *)a,pp,V_ASN1_VISIBLESTRING,\ - V_ASN1_UNIVERSAL) -#define M_d2i_ASN1_VISIBLESTRING(a,pp,l) \ - (ASN1_VISIBLESTRING *)d2i_ASN1_type_bytes\ - ((ASN1_STRING **)a,pp,l,B_ASN1_VISIBLESTRING) - -#define M_ASN1_UTF8STRING_new() (ASN1_UTF8STRING *)\ - ASN1_STRING_type_new(V_ASN1_UTF8STRING) -#define M_ASN1_UTF8STRING_free(a) ASN1_STRING_free((ASN1_STRING *)a) -#define M_i2d_ASN1_UTF8STRING(a,pp) \ - i2d_ASN1_bytes((ASN1_STRING *)a,pp,V_ASN1_UTF8STRING,\ - V_ASN1_UNIVERSAL) -#define M_d2i_ASN1_UTF8STRING(a,pp,l) \ - (ASN1_UTF8STRING *)d2i_ASN1_type_bytes\ - ((ASN1_STRING **)a,pp,l,B_ASN1_UTF8STRING) - - /* for the is_set parameter to i2d_ASN1_SET */ -#define IS_SEQUENCE 0 -#define IS_SET 1 - -DECLARE_ASN1_FUNCTIONS_fname(ASN1_TYPE, ASN1_ANY, ASN1_TYPE) - -int ASN1_TYPE_get(ASN1_TYPE *a); -void ASN1_TYPE_set(ASN1_TYPE *a, int type, void *value); -int ASN1_TYPE_set1(ASN1_TYPE *a, int type, const void *value); -int ASN1_TYPE_cmp(ASN1_TYPE *a, ASN1_TYPE *b); - -ASN1_OBJECT * ASN1_OBJECT_new(void ); -void ASN1_OBJECT_free(ASN1_OBJECT *a); -int i2d_ASN1_OBJECT(ASN1_OBJECT *a,unsigned char **pp); -ASN1_OBJECT * c2i_ASN1_OBJECT(ASN1_OBJECT **a,const unsigned char **pp, - long length); -ASN1_OBJECT * d2i_ASN1_OBJECT(ASN1_OBJECT **a,const unsigned char **pp, - long length); - -DECLARE_ASN1_ITEM(ASN1_OBJECT) - -DECLARE_STACK_OF(ASN1_OBJECT) -DECLARE_ASN1_SET_OF(ASN1_OBJECT) - -ASN1_STRING * ASN1_STRING_new(void); -void ASN1_STRING_free(ASN1_STRING *a); -int ASN1_STRING_copy(ASN1_STRING *dst, const ASN1_STRING *str); -ASN1_STRING * ASN1_STRING_dup(const ASN1_STRING *a); -ASN1_STRING * ASN1_STRING_type_new(int type ); -int ASN1_STRING_cmp(const ASN1_STRING *a, const ASN1_STRING *b); - /* Since this is used to store all sorts of things, via macros, for now, make - its data void * */ -int ASN1_STRING_set(ASN1_STRING *str, const void *data, int len); -void ASN1_STRING_set0(ASN1_STRING *str, void *data, int len); -int ASN1_STRING_length(const ASN1_STRING *x); -void ASN1_STRING_length_set(ASN1_STRING *x, int n); -int ASN1_STRING_type(ASN1_STRING *x); -unsigned char * ASN1_STRING_data(ASN1_STRING *x); - -DECLARE_ASN1_FUNCTIONS(ASN1_BIT_STRING) -int i2c_ASN1_BIT_STRING(ASN1_BIT_STRING *a,unsigned char **pp); -ASN1_BIT_STRING *c2i_ASN1_BIT_STRING(ASN1_BIT_STRING **a,const unsigned char **pp, - long length); -int ASN1_BIT_STRING_set(ASN1_BIT_STRING *a, unsigned char *d, - int length ); -int ASN1_BIT_STRING_set_bit(ASN1_BIT_STRING *a, int n, int value); -int ASN1_BIT_STRING_get_bit(ASN1_BIT_STRING *a, int n); -int ASN1_BIT_STRING_check(ASN1_BIT_STRING *a, - unsigned char *flags, int flags_len); - -#ifndef OPENSSL_NO_BIO -int ASN1_BIT_STRING_name_print(BIO *out, ASN1_BIT_STRING *bs, - BIT_STRING_BITNAME *tbl, int indent); -#endif -int ASN1_BIT_STRING_num_asc(char *name, BIT_STRING_BITNAME *tbl); -int ASN1_BIT_STRING_set_asc(ASN1_BIT_STRING *bs, char *name, int value, - BIT_STRING_BITNAME *tbl); - -int i2d_ASN1_BOOLEAN(int a,unsigned char **pp); -int d2i_ASN1_BOOLEAN(int *a,const unsigned char **pp,long length); - -DECLARE_ASN1_FUNCTIONS(ASN1_INTEGER) -int i2c_ASN1_INTEGER(ASN1_INTEGER *a,unsigned char **pp); -ASN1_INTEGER *c2i_ASN1_INTEGER(ASN1_INTEGER **a,const unsigned char **pp, - long length); -ASN1_INTEGER *d2i_ASN1_UINTEGER(ASN1_INTEGER **a,const unsigned char **pp, - long length); -ASN1_INTEGER * ASN1_INTEGER_dup(const ASN1_INTEGER *x); -int ASN1_INTEGER_cmp(const ASN1_INTEGER *x, const ASN1_INTEGER *y); - -DECLARE_ASN1_FUNCTIONS(ASN1_ENUMERATED) - -int ASN1_UTCTIME_check(ASN1_UTCTIME *a); -ASN1_UTCTIME *ASN1_UTCTIME_set(ASN1_UTCTIME *s,time_t t); -ASN1_UTCTIME *ASN1_UTCTIME_adj(ASN1_UTCTIME *s, time_t t, - int offset_day, long offset_sec); -int ASN1_UTCTIME_set_string(ASN1_UTCTIME *s, const char *str); -int ASN1_UTCTIME_cmp_time_t(const ASN1_UTCTIME *s, time_t t); -#if 0 -time_t ASN1_UTCTIME_get(const ASN1_UTCTIME *s); -#endif - -int ASN1_GENERALIZEDTIME_check(ASN1_GENERALIZEDTIME *a); -ASN1_GENERALIZEDTIME *ASN1_GENERALIZEDTIME_set(ASN1_GENERALIZEDTIME *s,time_t t); -ASN1_GENERALIZEDTIME *ASN1_GENERALIZEDTIME_adj(ASN1_GENERALIZEDTIME *s, - time_t t, int offset_day, long offset_sec); -int ASN1_GENERALIZEDTIME_set_string(ASN1_GENERALIZEDTIME *s, const char *str); - -DECLARE_ASN1_FUNCTIONS(ASN1_OCTET_STRING) -ASN1_OCTET_STRING * ASN1_OCTET_STRING_dup(const ASN1_OCTET_STRING *a); -int ASN1_OCTET_STRING_cmp(const ASN1_OCTET_STRING *a, const ASN1_OCTET_STRING *b); -int ASN1_OCTET_STRING_set(ASN1_OCTET_STRING *str, const unsigned char *data, int len); - -DECLARE_ASN1_FUNCTIONS(ASN1_VISIBLESTRING) -DECLARE_ASN1_FUNCTIONS(ASN1_UNIVERSALSTRING) -DECLARE_ASN1_FUNCTIONS(ASN1_UTF8STRING) -DECLARE_ASN1_FUNCTIONS(ASN1_NULL) -DECLARE_ASN1_FUNCTIONS(ASN1_BMPSTRING) - -int UTF8_getc(const unsigned char *str, int len, unsigned long *val); -int UTF8_putc(unsigned char *str, int len, unsigned long value); - -DECLARE_ASN1_FUNCTIONS_name(ASN1_STRING, ASN1_PRINTABLE) - -DECLARE_ASN1_FUNCTIONS_name(ASN1_STRING, DIRECTORYSTRING) -DECLARE_ASN1_FUNCTIONS_name(ASN1_STRING, DISPLAYTEXT) -DECLARE_ASN1_FUNCTIONS(ASN1_PRINTABLESTRING) -DECLARE_ASN1_FUNCTIONS(ASN1_T61STRING) -DECLARE_ASN1_FUNCTIONS(ASN1_IA5STRING) -DECLARE_ASN1_FUNCTIONS(ASN1_GENERALSTRING) -DECLARE_ASN1_FUNCTIONS(ASN1_UTCTIME) -DECLARE_ASN1_FUNCTIONS(ASN1_GENERALIZEDTIME) -DECLARE_ASN1_FUNCTIONS(ASN1_TIME) - -DECLARE_ASN1_ITEM(ASN1_OCTET_STRING_NDEF) - -ASN1_TIME *ASN1_TIME_set(ASN1_TIME *s,time_t t); -ASN1_TIME *ASN1_TIME_adj(ASN1_TIME *s,time_t t, - int offset_day, long offset_sec); -int ASN1_TIME_check(ASN1_TIME *t); -ASN1_GENERALIZEDTIME *ASN1_TIME_to_generalizedtime(ASN1_TIME *t, ASN1_GENERALIZEDTIME **out); -int ASN1_TIME_set_string(ASN1_TIME *s, const char *str); - -int i2d_ASN1_SET(STACK_OF(OPENSSL_BLOCK) *a, unsigned char **pp, - i2d_of_void *i2d, int ex_tag, int ex_class, - int is_set); -STACK_OF(OPENSSL_BLOCK) *d2i_ASN1_SET(STACK_OF(OPENSSL_BLOCK) **a, - const unsigned char **pp, - long length, d2i_of_void *d2i, - void (*free_func)(OPENSSL_BLOCK), int ex_tag, - int ex_class); - -#ifndef OPENSSL_NO_BIO -int i2a_ASN1_INTEGER(BIO *bp, ASN1_INTEGER *a); -int a2i_ASN1_INTEGER(BIO *bp,ASN1_INTEGER *bs,char *buf,int size); -int i2a_ASN1_ENUMERATED(BIO *bp, ASN1_ENUMERATED *a); -int a2i_ASN1_ENUMERATED(BIO *bp,ASN1_ENUMERATED *bs,char *buf,int size); -int i2a_ASN1_OBJECT(BIO *bp,ASN1_OBJECT *a); -int a2i_ASN1_STRING(BIO *bp,ASN1_STRING *bs,char *buf,int size); -int i2a_ASN1_STRING(BIO *bp, ASN1_STRING *a, int type); -#endif -int i2t_ASN1_OBJECT(char *buf,int buf_len,ASN1_OBJECT *a); - -int a2d_ASN1_OBJECT(unsigned char *out,int olen, const char *buf, int num); -ASN1_OBJECT *ASN1_OBJECT_create(int nid, unsigned char *data,int len, - const char *sn, const char *ln); - -int ASN1_INTEGER_set(ASN1_INTEGER *a, long v); -long ASN1_INTEGER_get(const ASN1_INTEGER *a); -ASN1_INTEGER *BN_to_ASN1_INTEGER(const BIGNUM *bn, ASN1_INTEGER *ai); -BIGNUM *ASN1_INTEGER_to_BN(const ASN1_INTEGER *ai,BIGNUM *bn); - -int ASN1_ENUMERATED_set(ASN1_ENUMERATED *a, long v); -long ASN1_ENUMERATED_get(ASN1_ENUMERATED *a); -ASN1_ENUMERATED *BN_to_ASN1_ENUMERATED(BIGNUM *bn, ASN1_ENUMERATED *ai); -BIGNUM *ASN1_ENUMERATED_to_BN(ASN1_ENUMERATED *ai,BIGNUM *bn); - -/* General */ -/* given a string, return the correct type, max is the maximum length */ -int ASN1_PRINTABLE_type(const unsigned char *s, int max); - -int i2d_ASN1_bytes(ASN1_STRING *a, unsigned char **pp, int tag, int xclass); -ASN1_STRING *d2i_ASN1_bytes(ASN1_STRING **a, const unsigned char **pp, - long length, int Ptag, int Pclass); -unsigned long ASN1_tag2bit(int tag); -/* type is one or more of the B_ASN1_ values. */ -ASN1_STRING *d2i_ASN1_type_bytes(ASN1_STRING **a,const unsigned char **pp, - long length,int type); - -/* PARSING */ -int asn1_Finish(ASN1_CTX *c); -int asn1_const_Finish(ASN1_const_CTX *c); - -/* SPECIALS */ -int ASN1_get_object(const unsigned char **pp, long *plength, int *ptag, - int *pclass, long omax); -int ASN1_check_infinite_end(unsigned char **p,long len); -int ASN1_const_check_infinite_end(const unsigned char **p,long len); -void ASN1_put_object(unsigned char **pp, int constructed, int length, - int tag, int xclass); -int ASN1_put_eoc(unsigned char **pp); -int ASN1_object_size(int constructed, int length, int tag); - -/* Used to implement other functions */ -void *ASN1_dup(i2d_of_void *i2d, d2i_of_void *d2i, void *x); - -#define ASN1_dup_of(type,i2d,d2i,x) \ - ((type*)ASN1_dup(CHECKED_I2D_OF(type, i2d), \ - CHECKED_D2I_OF(type, d2i), \ - CHECKED_PTR_OF(type, x))) - -#define ASN1_dup_of_const(type,i2d,d2i,x) \ - ((type*)ASN1_dup(CHECKED_I2D_OF(const type, i2d), \ - CHECKED_D2I_OF(type, d2i), \ - CHECKED_PTR_OF(const type, x))) - -void *ASN1_item_dup(const ASN1_ITEM *it, void *x); - -/* ASN1 alloc/free macros for when a type is only used internally */ - -#define M_ASN1_new_of(type) (type *)ASN1_item_new(ASN1_ITEM_rptr(type)) -#define M_ASN1_free_of(x, type) \ - ASN1_item_free(CHECKED_PTR_OF(type, x), ASN1_ITEM_rptr(type)) - -#ifndef OPENSSL_NO_FP_API -void *ASN1_d2i_fp(void *(*xnew)(void), d2i_of_void *d2i, FILE *in, void **x); - -#define ASN1_d2i_fp_of(type,xnew,d2i,in,x) \ - ((type*)ASN1_d2i_fp(CHECKED_NEW_OF(type, xnew), \ - CHECKED_D2I_OF(type, d2i), \ - in, \ - CHECKED_PPTR_OF(type, x))) - -void *ASN1_item_d2i_fp(const ASN1_ITEM *it, FILE *in, void *x); -int ASN1_i2d_fp(i2d_of_void *i2d,FILE *out,void *x); - -#define ASN1_i2d_fp_of(type,i2d,out,x) \ - (ASN1_i2d_fp(CHECKED_I2D_OF(type, i2d), \ - out, \ - CHECKED_PTR_OF(type, x))) - -#define ASN1_i2d_fp_of_const(type,i2d,out,x) \ - (ASN1_i2d_fp(CHECKED_I2D_OF(const type, i2d), \ - out, \ - CHECKED_PTR_OF(const type, x))) - -int ASN1_item_i2d_fp(const ASN1_ITEM *it, FILE *out, void *x); -int ASN1_STRING_print_ex_fp(FILE *fp, ASN1_STRING *str, unsigned long flags); -#endif - -int ASN1_STRING_to_UTF8(unsigned char **out, ASN1_STRING *in); - -#ifndef OPENSSL_NO_BIO -void *ASN1_d2i_bio(void *(*xnew)(void), d2i_of_void *d2i, BIO *in, void **x); - -#define ASN1_d2i_bio_of(type,xnew,d2i,in,x) \ - ((type*)ASN1_d2i_bio( CHECKED_NEW_OF(type, xnew), \ - CHECKED_D2I_OF(type, d2i), \ - in, \ - CHECKED_PPTR_OF(type, x))) - -void *ASN1_item_d2i_bio(const ASN1_ITEM *it, BIO *in, void *x); -int ASN1_i2d_bio(i2d_of_void *i2d,BIO *out, unsigned char *x); - -#define ASN1_i2d_bio_of(type,i2d,out,x) \ - (ASN1_i2d_bio(CHECKED_I2D_OF(type, i2d), \ - out, \ - CHECKED_PTR_OF(type, x))) - -#define ASN1_i2d_bio_of_const(type,i2d,out,x) \ - (ASN1_i2d_bio(CHECKED_I2D_OF(const type, i2d), \ - out, \ - CHECKED_PTR_OF(const type, x))) - -int ASN1_item_i2d_bio(const ASN1_ITEM *it, BIO *out, void *x); -int ASN1_UTCTIME_print(BIO *fp, const ASN1_UTCTIME *a); -int ASN1_GENERALIZEDTIME_print(BIO *fp, const ASN1_GENERALIZEDTIME *a); -int ASN1_TIME_print(BIO *fp, const ASN1_TIME *a); -int ASN1_STRING_print(BIO *bp, const ASN1_STRING *v); -int ASN1_STRING_print_ex(BIO *out, ASN1_STRING *str, unsigned long flags); -int ASN1_bn_print(BIO *bp, const char *number, const BIGNUM *num, - unsigned char *buf, int off); -int ASN1_parse(BIO *bp,const unsigned char *pp,long len,int indent); -int ASN1_parse_dump(BIO *bp,const unsigned char *pp,long len,int indent,int dump); -#endif -const char *ASN1_tag2str(int tag); - -/* Used to load and write netscape format cert */ - -DECLARE_ASN1_FUNCTIONS(NETSCAPE_X509) - -int ASN1_UNIVERSALSTRING_to_string(ASN1_UNIVERSALSTRING *s); - -int ASN1_TYPE_set_octetstring(ASN1_TYPE *a, - unsigned char *data, int len); -int ASN1_TYPE_get_octetstring(ASN1_TYPE *a, - unsigned char *data, int max_len); -int ASN1_TYPE_set_int_octetstring(ASN1_TYPE *a, long num, - unsigned char *data, int len); -int ASN1_TYPE_get_int_octetstring(ASN1_TYPE *a,long *num, - unsigned char *data, int max_len); - -STACK_OF(OPENSSL_BLOCK) *ASN1_seq_unpack(const unsigned char *buf, int len, - d2i_of_void *d2i, void (*free_func)(OPENSSL_BLOCK)); -unsigned char *ASN1_seq_pack(STACK_OF(OPENSSL_BLOCK) *safes, i2d_of_void *i2d, - unsigned char **buf, int *len ); -void *ASN1_unpack_string(ASN1_STRING *oct, d2i_of_void *d2i); -void *ASN1_item_unpack(ASN1_STRING *oct, const ASN1_ITEM *it); -ASN1_STRING *ASN1_pack_string(void *obj, i2d_of_void *i2d, - ASN1_OCTET_STRING **oct); - -#define ASN1_pack_string_of(type,obj,i2d,oct) \ - (ASN1_pack_string(CHECKED_PTR_OF(type, obj), \ - CHECKED_I2D_OF(type, i2d), \ - oct)) - -ASN1_STRING *ASN1_item_pack(void *obj, const ASN1_ITEM *it, ASN1_OCTET_STRING **oct); - -void ASN1_STRING_set_default_mask(unsigned long mask); -int ASN1_STRING_set_default_mask_asc(const char *p); -unsigned long ASN1_STRING_get_default_mask(void); -int ASN1_mbstring_copy(ASN1_STRING **out, const unsigned char *in, int len, - int inform, unsigned long mask); -int ASN1_mbstring_ncopy(ASN1_STRING **out, const unsigned char *in, int len, - int inform, unsigned long mask, - long minsize, long maxsize); - -ASN1_STRING *ASN1_STRING_set_by_NID(ASN1_STRING **out, - const unsigned char *in, int inlen, int inform, int nid); -ASN1_STRING_TABLE *ASN1_STRING_TABLE_get(int nid); -int ASN1_STRING_TABLE_add(int, long, long, unsigned long, unsigned long); -void ASN1_STRING_TABLE_cleanup(void); - -/* ASN1 template functions */ - -/* Old API compatible functions */ -ASN1_VALUE *ASN1_item_new(const ASN1_ITEM *it); -void ASN1_item_free(ASN1_VALUE *val, const ASN1_ITEM *it); -ASN1_VALUE * ASN1_item_d2i(ASN1_VALUE **val, const unsigned char **in, long len, const ASN1_ITEM *it); -int ASN1_item_i2d(ASN1_VALUE *val, unsigned char **out, const ASN1_ITEM *it); -int ASN1_item_ndef_i2d(ASN1_VALUE *val, unsigned char **out, const ASN1_ITEM *it); - -void ASN1_add_oid_module(void); - -ASN1_TYPE *ASN1_generate_nconf(char *str, CONF *nconf); -ASN1_TYPE *ASN1_generate_v3(char *str, X509V3_CTX *cnf); - -/* ASN1 Print flags */ - -/* Indicate missing OPTIONAL fields */ -#define ASN1_PCTX_FLAGS_SHOW_ABSENT 0x001 -/* Mark start and end of SEQUENCE */ -#define ASN1_PCTX_FLAGS_SHOW_SEQUENCE 0x002 -/* Mark start and end of SEQUENCE/SET OF */ -#define ASN1_PCTX_FLAGS_SHOW_SSOF 0x004 -/* Show the ASN1 type of primitives */ -#define ASN1_PCTX_FLAGS_SHOW_TYPE 0x008 -/* Don't show ASN1 type of ANY */ -#define ASN1_PCTX_FLAGS_NO_ANY_TYPE 0x010 -/* Don't show ASN1 type of MSTRINGs */ -#define ASN1_PCTX_FLAGS_NO_MSTRING_TYPE 0x020 -/* Don't show field names in SEQUENCE */ -#define ASN1_PCTX_FLAGS_NO_FIELD_NAME 0x040 -/* Show structure names of each SEQUENCE field */ -#define ASN1_PCTX_FLAGS_SHOW_FIELD_STRUCT_NAME 0x080 -/* Don't show structure name even at top level */ -#define ASN1_PCTX_FLAGS_NO_STRUCT_NAME 0x100 - -int ASN1_item_print(BIO *out, ASN1_VALUE *ifld, int indent, - const ASN1_ITEM *it, const ASN1_PCTX *pctx); -ASN1_PCTX *ASN1_PCTX_new(void); -void ASN1_PCTX_free(ASN1_PCTX *p); -unsigned long ASN1_PCTX_get_flags(ASN1_PCTX *p); -void ASN1_PCTX_set_flags(ASN1_PCTX *p, unsigned long flags); -unsigned long ASN1_PCTX_get_nm_flags(ASN1_PCTX *p); -void ASN1_PCTX_set_nm_flags(ASN1_PCTX *p, unsigned long flags); -unsigned long ASN1_PCTX_get_cert_flags(ASN1_PCTX *p); -void ASN1_PCTX_set_cert_flags(ASN1_PCTX *p, unsigned long flags); -unsigned long ASN1_PCTX_get_oid_flags(ASN1_PCTX *p); -void ASN1_PCTX_set_oid_flags(ASN1_PCTX *p, unsigned long flags); -unsigned long ASN1_PCTX_get_str_flags(ASN1_PCTX *p); -void ASN1_PCTX_set_str_flags(ASN1_PCTX *p, unsigned long flags); - -BIO_METHOD *BIO_f_asn1(void); - -BIO *BIO_new_NDEF(BIO *out, ASN1_VALUE *val, const ASN1_ITEM *it); - -int i2d_ASN1_bio_stream(BIO *out, ASN1_VALUE *val, BIO *in, int flags, - const ASN1_ITEM *it); -int PEM_write_bio_ASN1_stream(BIO *out, ASN1_VALUE *val, BIO *in, int flags, - const char *hdr, - const ASN1_ITEM *it); -int SMIME_write_ASN1(BIO *bio, ASN1_VALUE *val, BIO *data, int flags, - int ctype_nid, int econt_nid, - STACK_OF(X509_ALGOR) *mdalgs, - const ASN1_ITEM *it); -ASN1_VALUE *SMIME_read_ASN1(BIO *bio, BIO **bcont, const ASN1_ITEM *it); -int SMIME_crlf_copy(BIO *in, BIO *out, int flags); -int SMIME_text(BIO *in, BIO *out); - -/* BEGIN ERROR CODES */ -/* The following lines are auto generated by the script mkerr.pl. Any changes - * made after this point may be overwritten when the script is next run. - */ -void ERR_load_ASN1_strings(void); - -/* Error codes for the ASN1 functions. */ - -/* Function codes. */ -#define ASN1_F_A2D_ASN1_OBJECT 100 -#define ASN1_F_A2I_ASN1_ENUMERATED 101 -#define ASN1_F_A2I_ASN1_INTEGER 102 -#define ASN1_F_A2I_ASN1_STRING 103 -#define ASN1_F_APPEND_EXP 176 -#define ASN1_F_ASN1_BIT_STRING_SET_BIT 183 -#define ASN1_F_ASN1_CB 177 -#define ASN1_F_ASN1_CHECK_TLEN 104 -#define ASN1_F_ASN1_COLLATE_PRIMITIVE 105 -#define ASN1_F_ASN1_COLLECT 106 -#define ASN1_F_ASN1_D2I_EX_PRIMITIVE 108 -#define ASN1_F_ASN1_D2I_FP 109 -#define ASN1_F_ASN1_D2I_READ_BIO 107 -#define ASN1_F_ASN1_DIGEST 184 -#define ASN1_F_ASN1_DO_ADB 110 -#define ASN1_F_ASN1_DUP 111 -#define ASN1_F_ASN1_ENUMERATED_SET 112 -#define ASN1_F_ASN1_ENUMERATED_TO_BN 113 -#define ASN1_F_ASN1_EX_C2I 204 -#define ASN1_F_ASN1_FIND_END 190 -#define ASN1_F_ASN1_GENERALIZEDTIME_ADJ 216 -#define ASN1_F_ASN1_GENERALIZEDTIME_SET 185 -#define ASN1_F_ASN1_GENERATE_V3 178 -#define ASN1_F_ASN1_GET_OBJECT 114 -#define ASN1_F_ASN1_HEADER_NEW 115 -#define ASN1_F_ASN1_I2D_BIO 116 -#define ASN1_F_ASN1_I2D_FP 117 -#define ASN1_F_ASN1_INTEGER_SET 118 -#define ASN1_F_ASN1_INTEGER_TO_BN 119 -#define ASN1_F_ASN1_ITEM_D2I_FP 206 -#define ASN1_F_ASN1_ITEM_DUP 191 -#define ASN1_F_ASN1_ITEM_EX_COMBINE_NEW 121 -#define ASN1_F_ASN1_ITEM_EX_D2I 120 -#define ASN1_F_ASN1_ITEM_I2D_BIO 192 -#define ASN1_F_ASN1_ITEM_I2D_FP 193 -#define ASN1_F_ASN1_ITEM_PACK 198 -#define ASN1_F_ASN1_ITEM_SIGN 195 -#define ASN1_F_ASN1_ITEM_SIGN_CTX 220 -#define ASN1_F_ASN1_ITEM_UNPACK 199 -#define ASN1_F_ASN1_ITEM_VERIFY 197 -#define ASN1_F_ASN1_MBSTRING_NCOPY 122 -#define ASN1_F_ASN1_OBJECT_NEW 123 -#define ASN1_F_ASN1_OUTPUT_DATA 214 -#define ASN1_F_ASN1_PACK_STRING 124 -#define ASN1_F_ASN1_PCTX_NEW 205 -#define ASN1_F_ASN1_PKCS5_PBE_SET 125 -#define ASN1_F_ASN1_SEQ_PACK 126 -#define ASN1_F_ASN1_SEQ_UNPACK 127 -#define ASN1_F_ASN1_SIGN 128 -#define ASN1_F_ASN1_STR2TYPE 179 -#define ASN1_F_ASN1_STRING_SET 186 -#define ASN1_F_ASN1_STRING_TABLE_ADD 129 -#define ASN1_F_ASN1_STRING_TYPE_NEW 130 -#define ASN1_F_ASN1_TEMPLATE_EX_D2I 132 -#define ASN1_F_ASN1_TEMPLATE_NEW 133 -#define ASN1_F_ASN1_TEMPLATE_NOEXP_D2I 131 -#define ASN1_F_ASN1_TIME_ADJ 217 -#define ASN1_F_ASN1_TIME_SET 175 -#define ASN1_F_ASN1_TYPE_GET_INT_OCTETSTRING 134 -#define ASN1_F_ASN1_TYPE_GET_OCTETSTRING 135 -#define ASN1_F_ASN1_UNPACK_STRING 136 -#define ASN1_F_ASN1_UTCTIME_ADJ 218 -#define ASN1_F_ASN1_UTCTIME_SET 187 -#define ASN1_F_ASN1_VERIFY 137 -#define ASN1_F_B64_READ_ASN1 209 -#define ASN1_F_B64_WRITE_ASN1 210 -#define ASN1_F_BIO_NEW_NDEF 208 -#define ASN1_F_BITSTR_CB 180 -#define ASN1_F_BN_TO_ASN1_ENUMERATED 138 -#define ASN1_F_BN_TO_ASN1_INTEGER 139 -#define ASN1_F_C2I_ASN1_BIT_STRING 189 -#define ASN1_F_C2I_ASN1_INTEGER 194 -#define ASN1_F_C2I_ASN1_OBJECT 196 -#define ASN1_F_COLLECT_DATA 140 -#define ASN1_F_D2I_ASN1_BIT_STRING 141 -#define ASN1_F_D2I_ASN1_BOOLEAN 142 -#define ASN1_F_D2I_ASN1_BYTES 143 -#define ASN1_F_D2I_ASN1_GENERALIZEDTIME 144 -#define ASN1_F_D2I_ASN1_HEADER 145 -#define ASN1_F_D2I_ASN1_INTEGER 146 -#define ASN1_F_D2I_ASN1_OBJECT 147 -#define ASN1_F_D2I_ASN1_SET 148 -#define ASN1_F_D2I_ASN1_TYPE_BYTES 149 -#define ASN1_F_D2I_ASN1_UINTEGER 150 -#define ASN1_F_D2I_ASN1_UTCTIME 151 -#define ASN1_F_D2I_AUTOPRIVATEKEY 207 -#define ASN1_F_D2I_NETSCAPE_RSA 152 -#define ASN1_F_D2I_NETSCAPE_RSA_2 153 -#define ASN1_F_D2I_PRIVATEKEY 154 -#define ASN1_F_D2I_PUBLICKEY 155 -#define ASN1_F_D2I_RSA_NET 200 -#define ASN1_F_D2I_RSA_NET_2 201 -#define ASN1_F_D2I_X509 156 -#define ASN1_F_D2I_X509_CINF 157 -#define ASN1_F_D2I_X509_PKEY 159 -#define ASN1_F_I2D_ASN1_BIO_STREAM 211 -#define ASN1_F_I2D_ASN1_SET 188 -#define ASN1_F_I2D_ASN1_TIME 160 -#define ASN1_F_I2D_DSA_PUBKEY 161 -#define ASN1_F_I2D_EC_PUBKEY 181 -#define ASN1_F_I2D_PRIVATEKEY 163 -#define ASN1_F_I2D_PUBLICKEY 164 -#define ASN1_F_I2D_RSA_NET 162 -#define ASN1_F_I2D_RSA_PUBKEY 165 -#define ASN1_F_LONG_C2I 166 -#define ASN1_F_OID_MODULE_INIT 174 -#define ASN1_F_PARSE_TAGGING 182 -#define ASN1_F_PKCS5_PBE2_SET_IV 167 -#define ASN1_F_PKCS5_PBE_SET 202 -#define ASN1_F_PKCS5_PBE_SET0_ALGOR 215 -#define ASN1_F_PKCS5_PBKDF2_SET 219 -#define ASN1_F_SMIME_READ_ASN1 212 -#define ASN1_F_SMIME_TEXT 213 -#define ASN1_F_X509_CINF_NEW 168 -#define ASN1_F_X509_CRL_ADD0_REVOKED 169 -#define ASN1_F_X509_INFO_NEW 170 -#define ASN1_F_X509_NAME_ENCODE 203 -#define ASN1_F_X509_NAME_EX_D2I 158 -#define ASN1_F_X509_NAME_EX_NEW 171 -#define ASN1_F_X509_NEW 172 -#define ASN1_F_X509_PKEY_NEW 173 - -/* Reason codes. */ -#define ASN1_R_ADDING_OBJECT 171 -#define ASN1_R_ASN1_PARSE_ERROR 203 -#define ASN1_R_ASN1_SIG_PARSE_ERROR 204 -#define ASN1_R_AUX_ERROR 100 -#define ASN1_R_BAD_CLASS 101 -#define ASN1_R_BAD_OBJECT_HEADER 102 -#define ASN1_R_BAD_PASSWORD_READ 103 -#define ASN1_R_BAD_TAG 104 -#define ASN1_R_BMPSTRING_IS_WRONG_LENGTH 214 -#define ASN1_R_BN_LIB 105 -#define ASN1_R_BOOLEAN_IS_WRONG_LENGTH 106 -#define ASN1_R_BUFFER_TOO_SMALL 107 -#define ASN1_R_CIPHER_HAS_NO_OBJECT_IDENTIFIER 108 -#define ASN1_R_CONTEXT_NOT_INITIALISED 217 -#define ASN1_R_DATA_IS_WRONG 109 -#define ASN1_R_DECODE_ERROR 110 -#define ASN1_R_DECODING_ERROR 111 -#define ASN1_R_DEPTH_EXCEEDED 174 -#define ASN1_R_DIGEST_AND_KEY_TYPE_NOT_SUPPORTED 198 -#define ASN1_R_ENCODE_ERROR 112 -#define ASN1_R_ERROR_GETTING_TIME 173 -#define ASN1_R_ERROR_LOADING_SECTION 172 -#define ASN1_R_ERROR_PARSING_SET_ELEMENT 113 -#define ASN1_R_ERROR_SETTING_CIPHER_PARAMS 114 -#define ASN1_R_EXPECTING_AN_INTEGER 115 -#define ASN1_R_EXPECTING_AN_OBJECT 116 -#define ASN1_R_EXPECTING_A_BOOLEAN 117 -#define ASN1_R_EXPECTING_A_TIME 118 -#define ASN1_R_EXPLICIT_LENGTH_MISMATCH 119 -#define ASN1_R_EXPLICIT_TAG_NOT_CONSTRUCTED 120 -#define ASN1_R_FIELD_MISSING 121 -#define ASN1_R_FIRST_NUM_TOO_LARGE 122 -#define ASN1_R_HEADER_TOO_LONG 123 -#define ASN1_R_ILLEGAL_BITSTRING_FORMAT 175 -#define ASN1_R_ILLEGAL_BOOLEAN 176 -#define ASN1_R_ILLEGAL_CHARACTERS 124 -#define ASN1_R_ILLEGAL_FORMAT 177 -#define ASN1_R_ILLEGAL_HEX 178 -#define ASN1_R_ILLEGAL_IMPLICIT_TAG 179 -#define ASN1_R_ILLEGAL_INTEGER 180 -#define ASN1_R_ILLEGAL_NESTED_TAGGING 181 -#define ASN1_R_ILLEGAL_NULL 125 -#define ASN1_R_ILLEGAL_NULL_VALUE 182 -#define ASN1_R_ILLEGAL_OBJECT 183 -#define ASN1_R_ILLEGAL_OPTIONAL_ANY 126 -#define ASN1_R_ILLEGAL_OPTIONS_ON_ITEM_TEMPLATE 170 -#define ASN1_R_ILLEGAL_TAGGED_ANY 127 -#define ASN1_R_ILLEGAL_TIME_VALUE 184 -#define ASN1_R_INTEGER_NOT_ASCII_FORMAT 185 -#define ASN1_R_INTEGER_TOO_LARGE_FOR_LONG 128 -#define ASN1_R_INVALID_BMPSTRING_LENGTH 129 -#define ASN1_R_INVALID_DIGIT 130 -#define ASN1_R_INVALID_MIME_TYPE 205 -#define ASN1_R_INVALID_MODIFIER 186 -#define ASN1_R_INVALID_NUMBER 187 -#define ASN1_R_INVALID_OBJECT_ENCODING 216 -#define ASN1_R_INVALID_SEPARATOR 131 -#define ASN1_R_INVALID_TIME_FORMAT 132 -#define ASN1_R_INVALID_UNIVERSALSTRING_LENGTH 133 -#define ASN1_R_INVALID_UTF8STRING 134 -#define ASN1_R_IV_TOO_LARGE 135 -#define ASN1_R_LENGTH_ERROR 136 -#define ASN1_R_LIST_ERROR 188 -#define ASN1_R_MIME_NO_CONTENT_TYPE 206 -#define ASN1_R_MIME_PARSE_ERROR 207 -#define ASN1_R_MIME_SIG_PARSE_ERROR 208 -#define ASN1_R_MISSING_EOC 137 -#define ASN1_R_MISSING_SECOND_NUMBER 138 -#define ASN1_R_MISSING_VALUE 189 -#define ASN1_R_MSTRING_NOT_UNIVERSAL 139 -#define ASN1_R_MSTRING_WRONG_TAG 140 -#define ASN1_R_NESTED_ASN1_STRING 197 -#define ASN1_R_NON_HEX_CHARACTERS 141 -#define ASN1_R_NOT_ASCII_FORMAT 190 -#define ASN1_R_NOT_ENOUGH_DATA 142 -#define ASN1_R_NO_CONTENT_TYPE 209 -#define ASN1_R_NO_DEFAULT_DIGEST 201 -#define ASN1_R_NO_MATCHING_CHOICE_TYPE 143 -#define ASN1_R_NO_MULTIPART_BODY_FAILURE 210 -#define ASN1_R_NO_MULTIPART_BOUNDARY 211 -#define ASN1_R_NO_SIG_CONTENT_TYPE 212 -#define ASN1_R_NULL_IS_WRONG_LENGTH 144 -#define ASN1_R_OBJECT_NOT_ASCII_FORMAT 191 -#define ASN1_R_ODD_NUMBER_OF_CHARS 145 -#define ASN1_R_PRIVATE_KEY_HEADER_MISSING 146 -#define ASN1_R_SECOND_NUMBER_TOO_LARGE 147 -#define ASN1_R_SEQUENCE_LENGTH_MISMATCH 148 -#define ASN1_R_SEQUENCE_NOT_CONSTRUCTED 149 -#define ASN1_R_SEQUENCE_OR_SET_NEEDS_CONFIG 192 -#define ASN1_R_SHORT_LINE 150 -#define ASN1_R_SIG_INVALID_MIME_TYPE 213 -#define ASN1_R_STREAMING_NOT_SUPPORTED 202 -#define ASN1_R_STRING_TOO_LONG 151 -#define ASN1_R_STRING_TOO_SHORT 152 -#define ASN1_R_TAG_VALUE_TOO_HIGH 153 -#define ASN1_R_THE_ASN1_OBJECT_IDENTIFIER_IS_NOT_KNOWN_FOR_THIS_MD 154 -#define ASN1_R_TIME_NOT_ASCII_FORMAT 193 -#define ASN1_R_TOO_LONG 155 -#define ASN1_R_TYPE_NOT_CONSTRUCTED 156 -#define ASN1_R_UNABLE_TO_DECODE_RSA_KEY 157 -#define ASN1_R_UNABLE_TO_DECODE_RSA_PRIVATE_KEY 158 -#define ASN1_R_UNEXPECTED_EOC 159 -#define ASN1_R_UNIVERSALSTRING_IS_WRONG_LENGTH 215 -#define ASN1_R_UNKNOWN_FORMAT 160 -#define ASN1_R_UNKNOWN_MESSAGE_DIGEST_ALGORITHM 161 -#define ASN1_R_UNKNOWN_OBJECT_TYPE 162 -#define ASN1_R_UNKNOWN_PUBLIC_KEY_TYPE 163 -#define ASN1_R_UNKNOWN_SIGNATURE_ALGORITHM 199 -#define ASN1_R_UNKNOWN_TAG 194 -#define ASN1_R_UNKOWN_FORMAT 195 -#define ASN1_R_UNSUPPORTED_ANY_DEFINED_BY_TYPE 164 -#define ASN1_R_UNSUPPORTED_CIPHER 165 -#define ASN1_R_UNSUPPORTED_ENCRYPTION_ALGORITHM 166 -#define ASN1_R_UNSUPPORTED_PUBLIC_KEY_TYPE 167 -#define ASN1_R_UNSUPPORTED_TYPE 196 -#define ASN1_R_WRONG_PUBLIC_KEY_TYPE 200 -#define ASN1_R_WRONG_TAG 168 -#define ASN1_R_WRONG_TYPE 169 - -#ifdef __cplusplus -} -#endif -#endif diff --git a/src/plugins/ftp/openssl/bio.h b/src/plugins/ftp/openssl/bio.h deleted file mode 100644 index 05699ab2..00000000 --- a/src/plugins/ftp/openssl/bio.h +++ /dev/null @@ -1,847 +0,0 @@ -/* crypto/bio/bio.h */ -/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) - * All rights reserved. - * - * This package is an SSL implementation written - * by Eric Young (eay@cryptsoft.com). - * The implementation was written so as to conform with Netscapes SSL. - * - * This library is free for commercial and non-commercial use as long as - * the following conditions are aheared to. The following conditions - * apply to all code found in this distribution, be it the RC4, RSA, - * lhash, DES, etc., code; not just the SSL code. The SSL documentation - * included with this distribution is covered by the same copyright terms - * except that the holder is Tim Hudson (tjh@cryptsoft.com). - * - * Copyright remains Eric Young's, and as such any Copyright notices in - * the code are not to be removed. - * If this package is used in a product, Eric Young should be given attribution - * as the author of the parts of the library used. - * This can be in the form of a textual message at program startup or - * in documentation (online or textual) provided with the package. - * - * 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 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. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * "This product includes cryptographic software written by - * Eric Young (eay@cryptsoft.com)" - * The word 'cryptographic' can be left out if the rouines from the library - * being used are not cryptographic related :-). - * 4. If you include any Windows specific code (or a derivative thereof) from - * the apps directory (application code) you must include an acknowledgement: - * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" - * - * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``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 AUTHOR 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. - * - * The licence and distribution terms for any publically available version or - * derivative of this code cannot be changed. i.e. this code cannot simply be - * copied and put under another distribution licence - * [including the GNU Public Licence.] - */ - -#ifndef HEADER_BIO_H -#define HEADER_BIO_H - -#include - -#ifndef OPENSSL_NO_FP_API -# include -#endif -#include - -#include - -#ifndef OPENSSL_NO_SCTP -# ifndef OPENSSL_SYS_VMS -# include -# else -# include -# endif -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -/* These are the 'types' of BIOs */ -#define BIO_TYPE_NONE 0 -#define BIO_TYPE_MEM (1|0x0400) -#define BIO_TYPE_FILE (2|0x0400) - -#define BIO_TYPE_FD (4|0x0400|0x0100) -#define BIO_TYPE_SOCKET (5|0x0400|0x0100) -#define BIO_TYPE_NULL (6|0x0400) -#define BIO_TYPE_SSL (7|0x0200) -#define BIO_TYPE_MD (8|0x0200) /* passive filter */ -#define BIO_TYPE_BUFFER (9|0x0200) /* filter */ -#define BIO_TYPE_CIPHER (10|0x0200) /* filter */ -#define BIO_TYPE_BASE64 (11|0x0200) /* filter */ -#define BIO_TYPE_CONNECT (12|0x0400|0x0100) /* socket - connect */ -#define BIO_TYPE_ACCEPT (13|0x0400|0x0100) /* socket for accept */ -#define BIO_TYPE_PROXY_CLIENT (14|0x0200) /* client proxy BIO */ -#define BIO_TYPE_PROXY_SERVER (15|0x0200) /* server proxy BIO */ -#define BIO_TYPE_NBIO_TEST (16|0x0200) /* server proxy BIO */ -#define BIO_TYPE_NULL_FILTER (17|0x0200) -#define BIO_TYPE_BER (18|0x0200) /* BER -> bin filter */ -#define BIO_TYPE_BIO (19|0x0400) /* (half a) BIO pair */ -#define BIO_TYPE_LINEBUFFER (20|0x0200) /* filter */ -#define BIO_TYPE_DGRAM (21|0x0400|0x0100) -#ifndef OPENSSL_NO_SCTP -#define BIO_TYPE_DGRAM_SCTP (24|0x0400|0x0100) -#endif -#define BIO_TYPE_ASN1 (22|0x0200) /* filter */ -#define BIO_TYPE_COMP (23|0x0200) /* filter */ - -#define BIO_TYPE_DESCRIPTOR 0x0100 /* socket, fd, connect or accept */ -#define BIO_TYPE_FILTER 0x0200 -#define BIO_TYPE_SOURCE_SINK 0x0400 - -/* BIO_FILENAME_READ|BIO_CLOSE to open or close on free. - * BIO_set_fp(in,stdin,BIO_NOCLOSE); */ -#define BIO_NOCLOSE 0x00 -#define BIO_CLOSE 0x01 - -/* These are used in the following macros and are passed to - * BIO_ctrl() */ -#define BIO_CTRL_RESET 1 /* opt - rewind/zero etc */ -#define BIO_CTRL_EOF 2 /* opt - are we at the eof */ -#define BIO_CTRL_INFO 3 /* opt - extra tit-bits */ -#define BIO_CTRL_SET 4 /* man - set the 'IO' type */ -#define BIO_CTRL_GET 5 /* man - get the 'IO' type */ -#define BIO_CTRL_PUSH 6 /* opt - internal, used to signify change */ -#define BIO_CTRL_POP 7 /* opt - internal, used to signify change */ -#define BIO_CTRL_GET_CLOSE 8 /* man - set the 'close' on free */ -#define BIO_CTRL_SET_CLOSE 9 /* man - set the 'close' on free */ -#define BIO_CTRL_PENDING 10 /* opt - is their more data buffered */ -#define BIO_CTRL_FLUSH 11 /* opt - 'flush' buffered output */ -#define BIO_CTRL_DUP 12 /* man - extra stuff for 'duped' BIO */ -#define BIO_CTRL_WPENDING 13 /* opt - number of bytes still to write */ -/* callback is int cb(BIO *bio,state,ret); */ -#define BIO_CTRL_SET_CALLBACK 14 /* opt - set callback function */ -#define BIO_CTRL_GET_CALLBACK 15 /* opt - set callback function */ - -#define BIO_CTRL_SET_FILENAME 30 /* BIO_s_file special */ - -/* dgram BIO stuff */ -#define BIO_CTRL_DGRAM_CONNECT 31 /* BIO dgram special */ -#define BIO_CTRL_DGRAM_SET_CONNECTED 32 /* allow for an externally - * connected socket to be - * passed in */ -#define BIO_CTRL_DGRAM_SET_RECV_TIMEOUT 33 /* setsockopt, essentially */ -#define BIO_CTRL_DGRAM_GET_RECV_TIMEOUT 34 /* getsockopt, essentially */ -#define BIO_CTRL_DGRAM_SET_SEND_TIMEOUT 35 /* setsockopt, essentially */ -#define BIO_CTRL_DGRAM_GET_SEND_TIMEOUT 36 /* getsockopt, essentially */ - -#define BIO_CTRL_DGRAM_GET_RECV_TIMER_EXP 37 /* flag whether the last */ -#define BIO_CTRL_DGRAM_GET_SEND_TIMER_EXP 38 /* I/O operation tiemd out */ - -/* #ifdef IP_MTU_DISCOVER */ -#define BIO_CTRL_DGRAM_MTU_DISCOVER 39 /* set DF bit on egress packets */ -/* #endif */ - -#define BIO_CTRL_DGRAM_QUERY_MTU 40 /* as kernel for current MTU */ -#define BIO_CTRL_DGRAM_GET_FALLBACK_MTU 47 -#define BIO_CTRL_DGRAM_GET_MTU 41 /* get cached value for MTU */ -#define BIO_CTRL_DGRAM_SET_MTU 42 /* set cached value for - * MTU. want to use this - * if asking the kernel - * fails */ - -#define BIO_CTRL_DGRAM_MTU_EXCEEDED 43 /* check whether the MTU - * was exceed in the - * previous write - * operation */ - -#define BIO_CTRL_DGRAM_GET_PEER 46 -#define BIO_CTRL_DGRAM_SET_PEER 44 /* Destination for the data */ - -#define BIO_CTRL_DGRAM_SET_NEXT_TIMEOUT 45 /* Next DTLS handshake timeout to - * adjust socket timeouts */ - -#ifndef OPENSSL_NO_SCTP -/* SCTP stuff */ -#define BIO_CTRL_DGRAM_SCTP_SET_IN_HANDSHAKE 50 -#define BIO_CTRL_DGRAM_SCTP_ADD_AUTH_KEY 51 -#define BIO_CTRL_DGRAM_SCTP_NEXT_AUTH_KEY 52 -#define BIO_CTRL_DGRAM_SCTP_AUTH_CCS_RCVD 53 -#define BIO_CTRL_DGRAM_SCTP_GET_SNDINFO 60 -#define BIO_CTRL_DGRAM_SCTP_SET_SNDINFO 61 -#define BIO_CTRL_DGRAM_SCTP_GET_RCVINFO 62 -#define BIO_CTRL_DGRAM_SCTP_SET_RCVINFO 63 -#define BIO_CTRL_DGRAM_SCTP_GET_PRINFO 64 -#define BIO_CTRL_DGRAM_SCTP_SET_PRINFO 65 -#define BIO_CTRL_DGRAM_SCTP_SAVE_SHUTDOWN 70 -#endif - -/* modifiers */ -#define BIO_FP_READ 0x02 -#define BIO_FP_WRITE 0x04 -#define BIO_FP_APPEND 0x08 -#define BIO_FP_TEXT 0x10 - -#define BIO_FLAGS_READ 0x01 -#define BIO_FLAGS_WRITE 0x02 -#define BIO_FLAGS_IO_SPECIAL 0x04 -#define BIO_FLAGS_RWS (BIO_FLAGS_READ|BIO_FLAGS_WRITE|BIO_FLAGS_IO_SPECIAL) -#define BIO_FLAGS_SHOULD_RETRY 0x08 -#ifndef BIO_FLAGS_UPLINK -/* "UPLINK" flag denotes file descriptors provided by application. - It defaults to 0, as most platforms don't require UPLINK interface. */ -#define BIO_FLAGS_UPLINK 0 -#endif - -/* Used in BIO_gethostbyname() */ -#define BIO_GHBN_CTRL_HITS 1 -#define BIO_GHBN_CTRL_MISSES 2 -#define BIO_GHBN_CTRL_CACHE_SIZE 3 -#define BIO_GHBN_CTRL_GET_ENTRY 4 -#define BIO_GHBN_CTRL_FLUSH 5 - -/* Mostly used in the SSL BIO */ -/* Not used anymore - * #define BIO_FLAGS_PROTOCOL_DELAYED_READ 0x10 - * #define BIO_FLAGS_PROTOCOL_DELAYED_WRITE 0x20 - * #define BIO_FLAGS_PROTOCOL_STARTUP 0x40 - */ - -#define BIO_FLAGS_BASE64_NO_NL 0x100 - -/* This is used with memory BIOs: it means we shouldn't free up or change the - * data in any way. - */ -#define BIO_FLAGS_MEM_RDONLY 0x200 - -typedef struct bio_st BIO; - -void BIO_set_flags(BIO *b, int flags); -int BIO_test_flags(const BIO *b, int flags); -void BIO_clear_flags(BIO *b, int flags); - -#define BIO_get_flags(b) BIO_test_flags(b, ~(0x0)) -#define BIO_set_retry_special(b) \ - BIO_set_flags(b, (BIO_FLAGS_IO_SPECIAL|BIO_FLAGS_SHOULD_RETRY)) -#define BIO_set_retry_read(b) \ - BIO_set_flags(b, (BIO_FLAGS_READ|BIO_FLAGS_SHOULD_RETRY)) -#define BIO_set_retry_write(b) \ - BIO_set_flags(b, (BIO_FLAGS_WRITE|BIO_FLAGS_SHOULD_RETRY)) - -/* These are normally used internally in BIOs */ -#define BIO_clear_retry_flags(b) \ - BIO_clear_flags(b, (BIO_FLAGS_RWS|BIO_FLAGS_SHOULD_RETRY)) -#define BIO_get_retry_flags(b) \ - BIO_test_flags(b, (BIO_FLAGS_RWS|BIO_FLAGS_SHOULD_RETRY)) - -/* These should be used by the application to tell why we should retry */ -#define BIO_should_read(a) BIO_test_flags(a, BIO_FLAGS_READ) -#define BIO_should_write(a) BIO_test_flags(a, BIO_FLAGS_WRITE) -#define BIO_should_io_special(a) BIO_test_flags(a, BIO_FLAGS_IO_SPECIAL) -#define BIO_retry_type(a) BIO_test_flags(a, BIO_FLAGS_RWS) -#define BIO_should_retry(a) BIO_test_flags(a, BIO_FLAGS_SHOULD_RETRY) - -/* The next three are used in conjunction with the - * BIO_should_io_special() condition. After this returns true, - * BIO *BIO_get_retry_BIO(BIO *bio, int *reason); will walk the BIO - * stack and return the 'reason' for the special and the offending BIO. - * Given a BIO, BIO_get_retry_reason(bio) will return the code. */ -/* Returned from the SSL bio when the certificate retrieval code had an error */ -#define BIO_RR_SSL_X509_LOOKUP 0x01 -/* Returned from the connect BIO when a connect would have blocked */ -#define BIO_RR_CONNECT 0x02 -/* Returned from the accept BIO when an accept would have blocked */ -#define BIO_RR_ACCEPT 0x03 - -/* These are passed by the BIO callback */ -#define BIO_CB_FREE 0x01 -#define BIO_CB_READ 0x02 -#define BIO_CB_WRITE 0x03 -#define BIO_CB_PUTS 0x04 -#define BIO_CB_GETS 0x05 -#define BIO_CB_CTRL 0x06 - -/* The callback is called before and after the underling operation, - * The BIO_CB_RETURN flag indicates if it is after the call */ -#define BIO_CB_RETURN 0x80 -#define BIO_CB_return(a) ((a)|BIO_CB_RETURN)) -#define BIO_cb_pre(a) (!((a)&BIO_CB_RETURN)) -#define BIO_cb_post(a) ((a)&BIO_CB_RETURN) - -long (*BIO_get_callback(const BIO *b)) (struct bio_st *,int,const char *,int, long,long); -void BIO_set_callback(BIO *b, - long (*callback)(struct bio_st *,int,const char *,int, long,long)); -char *BIO_get_callback_arg(const BIO *b); -void BIO_set_callback_arg(BIO *b, char *arg); - -const char * BIO_method_name(const BIO *b); -int BIO_method_type(const BIO *b); - -typedef void bio_info_cb(struct bio_st *, int, const char *, int, long, long); - -typedef struct bio_method_st - { - int type; - const char *name; - int (*bwrite)(BIO *, const char *, int); - int (*bread)(BIO *, char *, int); - int (*bputs)(BIO *, const char *); - int (*bgets)(BIO *, char *, int); - long (*ctrl)(BIO *, int, long, void *); - int (*create)(BIO *); - int (*destroy)(BIO *); - long (*callback_ctrl)(BIO *, int, bio_info_cb *); - } BIO_METHOD; - -struct bio_st - { - BIO_METHOD *method; - /* bio, mode, argp, argi, argl, ret */ - long (*callback)(struct bio_st *,int,const char *,int, long,long); - char *cb_arg; /* first argument for the callback */ - - int init; - int shutdown; - int flags; /* extra storage */ - int retry_reason; - int num; - void *ptr; - struct bio_st *next_bio; /* used by filter BIOs */ - struct bio_st *prev_bio; /* used by filter BIOs */ - int references; - unsigned long num_read; - unsigned long num_write; - - CRYPTO_EX_DATA ex_data; - }; - -DECLARE_STACK_OF(BIO) - -typedef struct bio_f_buffer_ctx_struct - { - /* Buffers are setup like this: - * - * <---------------------- size -----------------------> - * +---------------------------------------------------+ - * | consumed | remaining | free space | - * +---------------------------------------------------+ - * <-- off --><------- len -------> - */ - - /* BIO *bio; */ /* this is now in the BIO struct */ - int ibuf_size; /* how big is the input buffer */ - int obuf_size; /* how big is the output buffer */ - - char *ibuf; /* the char array */ - int ibuf_len; /* how many bytes are in it */ - int ibuf_off; /* write/read offset */ - - char *obuf; /* the char array */ - int obuf_len; /* how many bytes are in it */ - int obuf_off; /* write/read offset */ - } BIO_F_BUFFER_CTX; - -/* Prefix and suffix callback in ASN1 BIO */ -typedef int asn1_ps_func(BIO *b, unsigned char **pbuf, int *plen, void *parg); - -#ifndef OPENSSL_NO_SCTP -/* SCTP parameter structs */ -struct bio_dgram_sctp_sndinfo - { - uint16_t snd_sid; - uint16_t snd_flags; - uint32_t snd_ppid; - uint32_t snd_context; - }; - -struct bio_dgram_sctp_rcvinfo - { - uint16_t rcv_sid; - uint16_t rcv_ssn; - uint16_t rcv_flags; - uint32_t rcv_ppid; - uint32_t rcv_tsn; - uint32_t rcv_cumtsn; - uint32_t rcv_context; - }; - -struct bio_dgram_sctp_prinfo - { - uint16_t pr_policy; - uint32_t pr_value; - }; -#endif - -/* connect BIO stuff */ -#define BIO_CONN_S_BEFORE 1 -#define BIO_CONN_S_GET_IP 2 -#define BIO_CONN_S_GET_PORT 3 -#define BIO_CONN_S_CREATE_SOCKET 4 -#define BIO_CONN_S_CONNECT 5 -#define BIO_CONN_S_OK 6 -#define BIO_CONN_S_BLOCKED_CONNECT 7 -#define BIO_CONN_S_NBIO 8 -/*#define BIO_CONN_get_param_hostname BIO_ctrl */ - -#define BIO_C_SET_CONNECT 100 -#define BIO_C_DO_STATE_MACHINE 101 -#define BIO_C_SET_NBIO 102 -#define BIO_C_SET_PROXY_PARAM 103 -#define BIO_C_SET_FD 104 -#define BIO_C_GET_FD 105 -#define BIO_C_SET_FILE_PTR 106 -#define BIO_C_GET_FILE_PTR 107 -#define BIO_C_SET_FILENAME 108 -#define BIO_C_SET_SSL 109 -#define BIO_C_GET_SSL 110 -#define BIO_C_SET_MD 111 -#define BIO_C_GET_MD 112 -#define BIO_C_GET_CIPHER_STATUS 113 -#define BIO_C_SET_BUF_MEM 114 -#define BIO_C_GET_BUF_MEM_PTR 115 -#define BIO_C_GET_BUFF_NUM_LINES 116 -#define BIO_C_SET_BUFF_SIZE 117 -#define BIO_C_SET_ACCEPT 118 -#define BIO_C_SSL_MODE 119 -#define BIO_C_GET_MD_CTX 120 -#define BIO_C_GET_PROXY_PARAM 121 -#define BIO_C_SET_BUFF_READ_DATA 122 /* data to read first */ -#define BIO_C_GET_CONNECT 123 -#define BIO_C_GET_ACCEPT 124 -#define BIO_C_SET_SSL_RENEGOTIATE_BYTES 125 -#define BIO_C_GET_SSL_NUM_RENEGOTIATES 126 -#define BIO_C_SET_SSL_RENEGOTIATE_TIMEOUT 127 -#define BIO_C_FILE_SEEK 128 -#define BIO_C_GET_CIPHER_CTX 129 -#define BIO_C_SET_BUF_MEM_EOF_RETURN 130/*return end of input value*/ -#define BIO_C_SET_BIND_MODE 131 -#define BIO_C_GET_BIND_MODE 132 -#define BIO_C_FILE_TELL 133 -#define BIO_C_GET_SOCKS 134 -#define BIO_C_SET_SOCKS 135 - -#define BIO_C_SET_WRITE_BUF_SIZE 136/* for BIO_s_bio */ -#define BIO_C_GET_WRITE_BUF_SIZE 137 -#define BIO_C_MAKE_BIO_PAIR 138 -#define BIO_C_DESTROY_BIO_PAIR 139 -#define BIO_C_GET_WRITE_GUARANTEE 140 -#define BIO_C_GET_READ_REQUEST 141 -#define BIO_C_SHUTDOWN_WR 142 -#define BIO_C_NREAD0 143 -#define BIO_C_NREAD 144 -#define BIO_C_NWRITE0 145 -#define BIO_C_NWRITE 146 -#define BIO_C_RESET_READ_REQUEST 147 -#define BIO_C_SET_MD_CTX 148 - -#define BIO_C_SET_PREFIX 149 -#define BIO_C_GET_PREFIX 150 -#define BIO_C_SET_SUFFIX 151 -#define BIO_C_GET_SUFFIX 152 - -#define BIO_C_SET_EX_ARG 153 -#define BIO_C_GET_EX_ARG 154 - -#define BIO_set_app_data(s,arg) BIO_set_ex_data(s,0,arg) -#define BIO_get_app_data(s) BIO_get_ex_data(s,0) - -/* BIO_s_connect() and BIO_s_socks4a_connect() */ -#define BIO_set_conn_hostname(b,name) BIO_ctrl(b,BIO_C_SET_CONNECT,0,(char *)name) -#define BIO_set_conn_port(b,port) BIO_ctrl(b,BIO_C_SET_CONNECT,1,(char *)port) -#define BIO_set_conn_ip(b,ip) BIO_ctrl(b,BIO_C_SET_CONNECT,2,(char *)ip) -#define BIO_set_conn_int_port(b,port) BIO_ctrl(b,BIO_C_SET_CONNECT,3,(char *)port) -#define BIO_get_conn_hostname(b) BIO_ptr_ctrl(b,BIO_C_GET_CONNECT,0) -#define BIO_get_conn_port(b) BIO_ptr_ctrl(b,BIO_C_GET_CONNECT,1) -#define BIO_get_conn_ip(b) BIO_ptr_ctrl(b,BIO_C_GET_CONNECT,2) -#define BIO_get_conn_int_port(b) BIO_int_ctrl(b,BIO_C_GET_CONNECT,3,0) - - -#define BIO_set_nbio(b,n) BIO_ctrl(b,BIO_C_SET_NBIO,(n),NULL) - -/* BIO_s_accept_socket() */ -#define BIO_set_accept_port(b,name) BIO_ctrl(b,BIO_C_SET_ACCEPT,0,(char *)name) -#define BIO_get_accept_port(b) BIO_ptr_ctrl(b,BIO_C_GET_ACCEPT,0) -/* #define BIO_set_nbio(b,n) BIO_ctrl(b,BIO_C_SET_NBIO,(n),NULL) */ -#define BIO_set_nbio_accept(b,n) BIO_ctrl(b,BIO_C_SET_ACCEPT,1,(n)?(void *)"a":NULL) -#define BIO_set_accept_bios(b,bio) BIO_ctrl(b,BIO_C_SET_ACCEPT,2,(char *)bio) - -#define BIO_BIND_NORMAL 0 -#define BIO_BIND_REUSEADDR_IF_UNUSED 1 -#define BIO_BIND_REUSEADDR 2 -#define BIO_set_bind_mode(b,mode) BIO_ctrl(b,BIO_C_SET_BIND_MODE,mode,NULL) -#define BIO_get_bind_mode(b,mode) BIO_ctrl(b,BIO_C_GET_BIND_MODE,0,NULL) - -#define BIO_do_connect(b) BIO_do_handshake(b) -#define BIO_do_accept(b) BIO_do_handshake(b) -#define BIO_do_handshake(b) BIO_ctrl(b,BIO_C_DO_STATE_MACHINE,0,NULL) - -/* BIO_s_proxy_client() */ -#define BIO_set_url(b,url) BIO_ctrl(b,BIO_C_SET_PROXY_PARAM,0,(char *)(url)) -#define BIO_set_proxies(b,p) BIO_ctrl(b,BIO_C_SET_PROXY_PARAM,1,(char *)(p)) -/* BIO_set_nbio(b,n) */ -#define BIO_set_filter_bio(b,s) BIO_ctrl(b,BIO_C_SET_PROXY_PARAM,2,(char *)(s)) -/* BIO *BIO_get_filter_bio(BIO *bio); */ -#define BIO_set_proxy_cb(b,cb) BIO_callback_ctrl(b,BIO_C_SET_PROXY_PARAM,3,(void *(*cb)())) -#define BIO_set_proxy_header(b,sk) BIO_ctrl(b,BIO_C_SET_PROXY_PARAM,4,(char *)sk) -#define BIO_set_no_connect_return(b,bool) BIO_int_ctrl(b,BIO_C_SET_PROXY_PARAM,5,bool) - -#define BIO_get_proxy_header(b,skp) BIO_ctrl(b,BIO_C_GET_PROXY_PARAM,0,(char *)skp) -#define BIO_get_proxies(b,pxy_p) BIO_ctrl(b,BIO_C_GET_PROXY_PARAM,1,(char *)(pxy_p)) -#define BIO_get_url(b,url) BIO_ctrl(b,BIO_C_GET_PROXY_PARAM,2,(char *)(url)) -#define BIO_get_no_connect_return(b) BIO_ctrl(b,BIO_C_GET_PROXY_PARAM,5,NULL) - -#define BIO_set_fd(b,fd,c) BIO_int_ctrl(b,BIO_C_SET_FD,c,fd) -#define BIO_get_fd(b,c) BIO_ctrl(b,BIO_C_GET_FD,0,(char *)c) - -#define BIO_set_fp(b,fp,c) BIO_ctrl(b,BIO_C_SET_FILE_PTR,c,(char *)fp) -#define BIO_get_fp(b,fpp) BIO_ctrl(b,BIO_C_GET_FILE_PTR,0,(char *)fpp) - -#define BIO_seek(b,ofs) (int)BIO_ctrl(b,BIO_C_FILE_SEEK,ofs,NULL) -#define BIO_tell(b) (int)BIO_ctrl(b,BIO_C_FILE_TELL,0,NULL) - -/* name is cast to lose const, but might be better to route through a function - so we can do it safely */ -#ifdef CONST_STRICT -/* If you are wondering why this isn't defined, its because CONST_STRICT is - * purely a compile-time kludge to allow const to be checked. - */ -int BIO_read_filename(BIO *b,const char *name); -#else -#define BIO_read_filename(b,name) BIO_ctrl(b,BIO_C_SET_FILENAME, \ - BIO_CLOSE|BIO_FP_READ,(char *)name) -#endif -#define BIO_write_filename(b,name) BIO_ctrl(b,BIO_C_SET_FILENAME, \ - BIO_CLOSE|BIO_FP_WRITE,name) -#define BIO_append_filename(b,name) BIO_ctrl(b,BIO_C_SET_FILENAME, \ - BIO_CLOSE|BIO_FP_APPEND,name) -#define BIO_rw_filename(b,name) BIO_ctrl(b,BIO_C_SET_FILENAME, \ - BIO_CLOSE|BIO_FP_READ|BIO_FP_WRITE,name) - -/* WARNING WARNING, this ups the reference count on the read bio of the - * SSL structure. This is because the ssl read BIO is now pointed to by - * the next_bio field in the bio. So when you free the BIO, make sure - * you are doing a BIO_free_all() to catch the underlying BIO. */ -#define BIO_set_ssl(b,ssl,c) BIO_ctrl(b,BIO_C_SET_SSL,c,(char *)ssl) -#define BIO_get_ssl(b,sslp) BIO_ctrl(b,BIO_C_GET_SSL,0,(char *)sslp) -#define BIO_set_ssl_mode(b,client) BIO_ctrl(b,BIO_C_SSL_MODE,client,NULL) -#define BIO_set_ssl_renegotiate_bytes(b,num) \ - BIO_ctrl(b,BIO_C_SET_SSL_RENEGOTIATE_BYTES,num,NULL); -#define BIO_get_num_renegotiates(b) \ - BIO_ctrl(b,BIO_C_GET_SSL_NUM_RENEGOTIATES,0,NULL); -#define BIO_set_ssl_renegotiate_timeout(b,seconds) \ - BIO_ctrl(b,BIO_C_SET_SSL_RENEGOTIATE_TIMEOUT,seconds,NULL); - -/* defined in evp.h */ -/* #define BIO_set_md(b,md) BIO_ctrl(b,BIO_C_SET_MD,1,(char *)md) */ - -#define BIO_get_mem_data(b,pp) BIO_ctrl(b,BIO_CTRL_INFO,0,(char *)pp) -#define BIO_set_mem_buf(b,bm,c) BIO_ctrl(b,BIO_C_SET_BUF_MEM,c,(char *)bm) -#define BIO_get_mem_ptr(b,pp) BIO_ctrl(b,BIO_C_GET_BUF_MEM_PTR,0,(char *)pp) -#define BIO_set_mem_eof_return(b,v) \ - BIO_ctrl(b,BIO_C_SET_BUF_MEM_EOF_RETURN,v,NULL) - -/* For the BIO_f_buffer() type */ -#define BIO_get_buffer_num_lines(b) BIO_ctrl(b,BIO_C_GET_BUFF_NUM_LINES,0,NULL) -#define BIO_set_buffer_size(b,size) BIO_ctrl(b,BIO_C_SET_BUFF_SIZE,size,NULL) -#define BIO_set_read_buffer_size(b,size) BIO_int_ctrl(b,BIO_C_SET_BUFF_SIZE,size,0) -#define BIO_set_write_buffer_size(b,size) BIO_int_ctrl(b,BIO_C_SET_BUFF_SIZE,size,1) -#define BIO_set_buffer_read_data(b,buf,num) BIO_ctrl(b,BIO_C_SET_BUFF_READ_DATA,num,buf) - -/* Don't use the next one unless you know what you are doing :-) */ -#define BIO_dup_state(b,ret) BIO_ctrl(b,BIO_CTRL_DUP,0,(char *)(ret)) - -#define BIO_reset(b) (int)BIO_ctrl(b,BIO_CTRL_RESET,0,NULL) -#define BIO_eof(b) (int)BIO_ctrl(b,BIO_CTRL_EOF,0,NULL) -#define BIO_set_close(b,c) (int)BIO_ctrl(b,BIO_CTRL_SET_CLOSE,(c),NULL) -#define BIO_get_close(b) (int)BIO_ctrl(b,BIO_CTRL_GET_CLOSE,0,NULL) -#define BIO_pending(b) (int)BIO_ctrl(b,BIO_CTRL_PENDING,0,NULL) -#define BIO_wpending(b) (int)BIO_ctrl(b,BIO_CTRL_WPENDING,0,NULL) -/* ...pending macros have inappropriate return type */ -size_t BIO_ctrl_pending(BIO *b); -size_t BIO_ctrl_wpending(BIO *b); -#define BIO_flush(b) (int)BIO_ctrl(b,BIO_CTRL_FLUSH,0,NULL) -#define BIO_get_info_callback(b,cbp) (int)BIO_ctrl(b,BIO_CTRL_GET_CALLBACK,0, \ - cbp) -#define BIO_set_info_callback(b,cb) (int)BIO_callback_ctrl(b,BIO_CTRL_SET_CALLBACK,cb) - -/* For the BIO_f_buffer() type */ -#define BIO_buffer_get_num_lines(b) BIO_ctrl(b,BIO_CTRL_GET,0,NULL) - -/* For BIO_s_bio() */ -#define BIO_set_write_buf_size(b,size) (int)BIO_ctrl(b,BIO_C_SET_WRITE_BUF_SIZE,size,NULL) -#define BIO_get_write_buf_size(b,size) (size_t)BIO_ctrl(b,BIO_C_GET_WRITE_BUF_SIZE,size,NULL) -#define BIO_make_bio_pair(b1,b2) (int)BIO_ctrl(b1,BIO_C_MAKE_BIO_PAIR,0,b2) -#define BIO_destroy_bio_pair(b) (int)BIO_ctrl(b,BIO_C_DESTROY_BIO_PAIR,0,NULL) -#define BIO_shutdown_wr(b) (int)BIO_ctrl(b, BIO_C_SHUTDOWN_WR, 0, NULL) -/* macros with inappropriate type -- but ...pending macros use int too: */ -#define BIO_get_write_guarantee(b) (int)BIO_ctrl(b,BIO_C_GET_WRITE_GUARANTEE,0,NULL) -#define BIO_get_read_request(b) (int)BIO_ctrl(b,BIO_C_GET_READ_REQUEST,0,NULL) -size_t BIO_ctrl_get_write_guarantee(BIO *b); -size_t BIO_ctrl_get_read_request(BIO *b); -int BIO_ctrl_reset_read_request(BIO *b); - -/* ctrl macros for dgram */ -#define BIO_ctrl_dgram_connect(b,peer) \ - (int)BIO_ctrl(b,BIO_CTRL_DGRAM_CONNECT,0, (char *)peer) -#define BIO_ctrl_set_connected(b, state, peer) \ - (int)BIO_ctrl(b, BIO_CTRL_DGRAM_SET_CONNECTED, state, (char *)peer) -#define BIO_dgram_recv_timedout(b) \ - (int)BIO_ctrl(b, BIO_CTRL_DGRAM_GET_RECV_TIMER_EXP, 0, NULL) -#define BIO_dgram_send_timedout(b) \ - (int)BIO_ctrl(b, BIO_CTRL_DGRAM_GET_SEND_TIMER_EXP, 0, NULL) -#define BIO_dgram_get_peer(b,peer) \ - (int)BIO_ctrl(b, BIO_CTRL_DGRAM_GET_PEER, 0, (char *)peer) -#define BIO_dgram_set_peer(b,peer) \ - (int)BIO_ctrl(b, BIO_CTRL_DGRAM_SET_PEER, 0, (char *)peer) - -/* These two aren't currently implemented */ -/* int BIO_get_ex_num(BIO *bio); */ -/* void BIO_set_ex_free_func(BIO *bio,int idx,void (*cb)()); */ -int BIO_set_ex_data(BIO *bio,int idx,void *data); -void *BIO_get_ex_data(BIO *bio,int idx); -int BIO_get_ex_new_index(long argl, void *argp, CRYPTO_EX_new *new_func, - CRYPTO_EX_dup *dup_func, CRYPTO_EX_free *free_func); -unsigned long BIO_number_read(BIO *bio); -unsigned long BIO_number_written(BIO *bio); - -/* For BIO_f_asn1() */ -int BIO_asn1_set_prefix(BIO *b, asn1_ps_func *prefix, - asn1_ps_func *prefix_free); -int BIO_asn1_get_prefix(BIO *b, asn1_ps_func **pprefix, - asn1_ps_func **pprefix_free); -int BIO_asn1_set_suffix(BIO *b, asn1_ps_func *suffix, - asn1_ps_func *suffix_free); -int BIO_asn1_get_suffix(BIO *b, asn1_ps_func **psuffix, - asn1_ps_func **psuffix_free); - -# ifndef OPENSSL_NO_FP_API -BIO_METHOD *BIO_s_file(void ); -BIO *BIO_new_file(const char *filename, const char *mode); -BIO *BIO_new_fp(FILE *stream, int close_flag); -# define BIO_s_file_internal BIO_s_file -# endif -BIO * BIO_new(BIO_METHOD *type); -int BIO_set(BIO *a,BIO_METHOD *type); -int BIO_free(BIO *a); -void BIO_vfree(BIO *a); -int BIO_read(BIO *b, void *data, int len); -int BIO_gets(BIO *bp,char *buf, int size); -int BIO_write(BIO *b, const void *data, int len); -int BIO_puts(BIO *bp,const char *buf); -int BIO_indent(BIO *b,int indent,int max); -long BIO_ctrl(BIO *bp,int cmd,long larg,void *parg); -long BIO_callback_ctrl(BIO *b, int cmd, void (*fp)(struct bio_st *, int, const char *, int, long, long)); -char * BIO_ptr_ctrl(BIO *bp,int cmd,long larg); -long BIO_int_ctrl(BIO *bp,int cmd,long larg,int iarg); -BIO * BIO_push(BIO *b,BIO *append); -BIO * BIO_pop(BIO *b); -void BIO_free_all(BIO *a); -BIO * BIO_find_type(BIO *b,int bio_type); -BIO * BIO_next(BIO *b); -BIO * BIO_get_retry_BIO(BIO *bio, int *reason); -int BIO_get_retry_reason(BIO *bio); -BIO * BIO_dup_chain(BIO *in); - -int BIO_nread0(BIO *bio, char **buf); -int BIO_nread(BIO *bio, char **buf, int num); -int BIO_nwrite0(BIO *bio, char **buf); -int BIO_nwrite(BIO *bio, char **buf, int num); - -long BIO_debug_callback(BIO *bio,int cmd,const char *argp,int argi, - long argl,long ret); - -BIO_METHOD *BIO_s_mem(void); -BIO *BIO_new_mem_buf(void *buf, int len); -BIO_METHOD *BIO_s_socket(void); -BIO_METHOD *BIO_s_connect(void); -BIO_METHOD *BIO_s_accept(void); -BIO_METHOD *BIO_s_fd(void); -#ifndef OPENSSL_SYS_OS2 -BIO_METHOD *BIO_s_log(void); -#endif -BIO_METHOD *BIO_s_bio(void); -BIO_METHOD *BIO_s_null(void); -BIO_METHOD *BIO_f_null(void); -BIO_METHOD *BIO_f_buffer(void); -#ifdef OPENSSL_SYS_VMS -BIO_METHOD *BIO_f_linebuffer(void); -#endif -BIO_METHOD *BIO_f_nbio_test(void); -#ifndef OPENSSL_NO_DGRAM -BIO_METHOD *BIO_s_datagram(void); -#ifndef OPENSSL_NO_SCTP -BIO_METHOD *BIO_s_datagram_sctp(void); -#endif -#endif - -/* BIO_METHOD *BIO_f_ber(void); */ - -int BIO_sock_should_retry(int i); -int BIO_sock_non_fatal_error(int error); -int BIO_dgram_non_fatal_error(int error); - -int BIO_fd_should_retry(int i); -int BIO_fd_non_fatal_error(int error); -int BIO_dump_cb(int (*cb)(const void *data, size_t len, void *u), - void *u, const char *s, int len); -int BIO_dump_indent_cb(int (*cb)(const void *data, size_t len, void *u), - void *u, const char *s, int len, int indent); -int BIO_dump(BIO *b,const char *bytes,int len); -int BIO_dump_indent(BIO *b,const char *bytes,int len,int indent); -#ifndef OPENSSL_NO_FP_API -int BIO_dump_fp(FILE *fp, const char *s, int len); -int BIO_dump_indent_fp(FILE *fp, const char *s, int len, int indent); -#endif -struct hostent *BIO_gethostbyname(const char *name); -/* We might want a thread-safe interface too: - * struct hostent *BIO_gethostbyname_r(const char *name, - * struct hostent *result, void *buffer, size_t buflen); - * or something similar (caller allocates a struct hostent, - * pointed to by "result", and additional buffer space for the various - * substructures; if the buffer does not suffice, NULL is returned - * and an appropriate error code is set). - */ -int BIO_sock_error(int sock); -int BIO_socket_ioctl(int fd, long type, void *arg); -int BIO_socket_nbio(int fd,int mode); -int BIO_get_port(const char *str, unsigned short *port_ptr); -int BIO_get_host_ip(const char *str, unsigned char *ip); -int BIO_get_accept_socket(char *host_port,int mode); -int BIO_accept(int sock,char **ip_port); -int BIO_sock_init(void ); -void BIO_sock_cleanup(void); -int BIO_set_tcp_ndelay(int sock,int turn_on); - -BIO *BIO_new_socket(int sock, int close_flag); -BIO *BIO_new_dgram(int fd, int close_flag); -#ifndef OPENSSL_NO_SCTP -BIO *BIO_new_dgram_sctp(int fd, int close_flag); -int BIO_dgram_is_sctp(BIO *bio); -int BIO_dgram_sctp_notification_cb(BIO *b, - void (*handle_notifications)(BIO *bio, void *context, void *buf), - void *context); -int BIO_dgram_sctp_wait_for_dry(BIO *b); -int BIO_dgram_sctp_msg_waiting(BIO *b); -#endif -BIO *BIO_new_fd(int fd, int close_flag); -BIO *BIO_new_connect(char *host_port); -BIO *BIO_new_accept(char *host_port); - -int BIO_new_bio_pair(BIO **bio1, size_t writebuf1, - BIO **bio2, size_t writebuf2); -/* If successful, returns 1 and in *bio1, *bio2 two BIO pair endpoints. - * Otherwise returns 0 and sets *bio1 and *bio2 to NULL. - * Size 0 uses default value. - */ - -void BIO_copy_next_retry(BIO *b); - -/*long BIO_ghbn_ctrl(int cmd,int iarg,char *parg);*/ - -#ifdef __GNUC__ -# define __bio_h__attr__ __attribute__ -#else -# define __bio_h__attr__(x) -#endif -int BIO_printf(BIO *bio, const char *format, ...) - __bio_h__attr__((__format__(__printf__,2,3))); -int BIO_vprintf(BIO *bio, const char *format, va_list args) - __bio_h__attr__((__format__(__printf__,2,0))); -int BIO_snprintf(char *buf, size_t n, const char *format, ...) - __bio_h__attr__((__format__(__printf__,3,4))); -int BIO_vsnprintf(char *buf, size_t n, const char *format, va_list args) - __bio_h__attr__((__format__(__printf__,3,0))); -#undef __bio_h__attr__ - -/* BEGIN ERROR CODES */ -/* The following lines are auto generated by the script mkerr.pl. Any changes - * made after this point may be overwritten when the script is next run. - */ -void ERR_load_BIO_strings(void); - -/* Error codes for the BIO functions. */ - -/* Function codes. */ -#define BIO_F_ACPT_STATE 100 -#define BIO_F_BIO_ACCEPT 101 -#define BIO_F_BIO_BER_GET_HEADER 102 -#define BIO_F_BIO_CALLBACK_CTRL 131 -#define BIO_F_BIO_CTRL 103 -#define BIO_F_BIO_GETHOSTBYNAME 120 -#define BIO_F_BIO_GETS 104 -#define BIO_F_BIO_GET_ACCEPT_SOCKET 105 -#define BIO_F_BIO_GET_HOST_IP 106 -#define BIO_F_BIO_GET_PORT 107 -#define BIO_F_BIO_MAKE_PAIR 121 -#define BIO_F_BIO_NEW 108 -#define BIO_F_BIO_NEW_FILE 109 -#define BIO_F_BIO_NEW_MEM_BUF 126 -#define BIO_F_BIO_NREAD 123 -#define BIO_F_BIO_NREAD0 124 -#define BIO_F_BIO_NWRITE 125 -#define BIO_F_BIO_NWRITE0 122 -#define BIO_F_BIO_PUTS 110 -#define BIO_F_BIO_READ 111 -#define BIO_F_BIO_SOCK_INIT 112 -#define BIO_F_BIO_WRITE 113 -#define BIO_F_BUFFER_CTRL 114 -#define BIO_F_CONN_CTRL 127 -#define BIO_F_CONN_STATE 115 -#define BIO_F_DGRAM_SCTP_READ 132 -#define BIO_F_FILE_CTRL 116 -#define BIO_F_FILE_READ 130 -#define BIO_F_LINEBUFFER_CTRL 129 -#define BIO_F_MEM_READ 128 -#define BIO_F_MEM_WRITE 117 -#define BIO_F_SSL_NEW 118 -#define BIO_F_WSASTARTUP 119 - -/* Reason codes. */ -#define BIO_R_ACCEPT_ERROR 100 -#define BIO_R_BAD_FOPEN_MODE 101 -#define BIO_R_BAD_HOSTNAME_LOOKUP 102 -#define BIO_R_BROKEN_PIPE 124 -#define BIO_R_CONNECT_ERROR 103 -#define BIO_R_EOF_ON_MEMORY_BIO 127 -#define BIO_R_ERROR_SETTING_NBIO 104 -#define BIO_R_ERROR_SETTING_NBIO_ON_ACCEPTED_SOCKET 105 -#define BIO_R_ERROR_SETTING_NBIO_ON_ACCEPT_SOCKET 106 -#define BIO_R_GETHOSTBYNAME_ADDR_IS_NOT_AF_INET 107 -#define BIO_R_INVALID_ARGUMENT 125 -#define BIO_R_INVALID_IP_ADDRESS 108 -#define BIO_R_IN_USE 123 -#define BIO_R_KEEPALIVE 109 -#define BIO_R_NBIO_CONNECT_ERROR 110 -#define BIO_R_NO_ACCEPT_PORT_SPECIFIED 111 -#define BIO_R_NO_HOSTNAME_SPECIFIED 112 -#define BIO_R_NO_PORT_DEFINED 113 -#define BIO_R_NO_PORT_SPECIFIED 114 -#define BIO_R_NO_SUCH_FILE 128 -#define BIO_R_NULL_PARAMETER 115 -#define BIO_R_TAG_MISMATCH 116 -#define BIO_R_UNABLE_TO_BIND_SOCKET 117 -#define BIO_R_UNABLE_TO_CREATE_SOCKET 118 -#define BIO_R_UNABLE_TO_LISTEN_SOCKET 119 -#define BIO_R_UNINITIALIZED 120 -#define BIO_R_UNSUPPORTED_METHOD 121 -#define BIO_R_WRITE_TO_READ_ONLY_BIO 126 -#define BIO_R_WSASTARTUP 122 - -#ifdef __cplusplus -} -#endif -#endif diff --git a/src/plugins/ftp/openssl/bn.h b/src/plugins/ftp/openssl/bn.h deleted file mode 100644 index f34248ec..00000000 --- a/src/plugins/ftp/openssl/bn.h +++ /dev/null @@ -1,891 +0,0 @@ -/* crypto/bn/bn.h */ -/* Copyright (C) 1995-1997 Eric Young (eay@cryptsoft.com) - * All rights reserved. - * - * This package is an SSL implementation written - * by Eric Young (eay@cryptsoft.com). - * The implementation was written so as to conform with Netscapes SSL. - * - * This library is free for commercial and non-commercial use as long as - * the following conditions are aheared to. The following conditions - * apply to all code found in this distribution, be it the RC4, RSA, - * lhash, DES, etc., code; not just the SSL code. The SSL documentation - * included with this distribution is covered by the same copyright terms - * except that the holder is Tim Hudson (tjh@cryptsoft.com). - * - * Copyright remains Eric Young's, and as such any Copyright notices in - * the code are not to be removed. - * If this package is used in a product, Eric Young should be given attribution - * as the author of the parts of the library used. - * This can be in the form of a textual message at program startup or - * in documentation (online or textual) provided with the package. - * - * 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 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. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * "This product includes cryptographic software written by - * Eric Young (eay@cryptsoft.com)" - * The word 'cryptographic' can be left out if the rouines from the library - * being used are not cryptographic related :-). - * 4. If you include any Windows specific code (or a derivative thereof) from - * the apps directory (application code) you must include an acknowledgement: - * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" - * - * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``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 AUTHOR 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. - * - * The licence and distribution terms for any publically available version or - * derivative of this code cannot be changed. i.e. this code cannot simply be - * copied and put under another distribution licence - * [including the GNU Public Licence.] - */ -/* ==================================================================== - * Copyright (c) 1998-2006 The OpenSSL Project. All rights reserved. - * - * 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. All advertising materials mentioning features or use of this - * software must display the following acknowledgment: - * "This product includes software developed by the OpenSSL Project - * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" - * - * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For written permission, please contact - * openssl-core@openssl.org. - * - * 5. Products derived from this software may not be called "OpenSSL" - * nor may "OpenSSL" appear in their names without prior written - * permission of the OpenSSL Project. - * - * 6. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by the OpenSSL Project - * for use in the OpenSSL Toolkit (http://www.openssl.org/)" - * - * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY - * EXPRESSED 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 OpenSSL PROJECT OR - * ITS 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. - * ==================================================================== - * - * This product includes cryptographic software written by Eric Young - * (eay@cryptsoft.com). This product includes software written by Tim - * Hudson (tjh@cryptsoft.com). - * - */ -/* ==================================================================== - * Copyright 2002 Sun Microsystems, Inc. ALL RIGHTS RESERVED. - * - * Portions of the attached software ("Contribution") are developed by - * SUN MICROSYSTEMS, INC., and are contributed to the OpenSSL project. - * - * The Contribution is licensed pursuant to the Eric Young open source - * license provided above. - * - * The binary polynomial arithmetic software is originally written by - * Sheueling Chang Shantz and Douglas Stebila of Sun Microsystems Laboratories. - * - */ - -#ifndef HEADER_BN_H -#define HEADER_BN_H - -#include -#ifndef OPENSSL_NO_FP_API -#include /* FILE */ -#endif -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/* These preprocessor symbols control various aspects of the bignum headers and - * library code. They're not defined by any "normal" configuration, as they are - * intended for development and testing purposes. NB: defining all three can be - * useful for debugging application code as well as openssl itself. - * - * BN_DEBUG - turn on various debugging alterations to the bignum code - * BN_DEBUG_RAND - uses random poisoning of unused words to trip up - * mismanagement of bignum internals. You must also define BN_DEBUG. - */ -/* #define BN_DEBUG */ -/* #define BN_DEBUG_RAND */ - -#ifndef OPENSSL_SMALL_FOOTPRINT -#define BN_MUL_COMBA -#define BN_SQR_COMBA -#define BN_RECURSION -#endif - -/* This next option uses the C libraries (2 word)/(1 word) function. - * If it is not defined, I use my C version (which is slower). - * The reason for this flag is that when the particular C compiler - * library routine is used, and the library is linked with a different - * compiler, the library is missing. This mostly happens when the - * library is built with gcc and then linked using normal cc. This would - * be a common occurrence because gcc normally produces code that is - * 2 times faster than system compilers for the big number stuff. - * For machines with only one compiler (or shared libraries), this should - * be on. Again this in only really a problem on machines - * using "long long's", are 32bit, and are not using my assembler code. */ -#if defined(OPENSSL_SYS_MSDOS) || defined(OPENSSL_SYS_WINDOWS) || \ - defined(OPENSSL_SYS_WIN32) || defined(linux) -# ifndef BN_DIV2W -# define BN_DIV2W -# endif -#endif - -/* assuming long is 64bit - this is the DEC Alpha - * unsigned long long is only 64 bits :-(, don't define - * BN_LLONG for the DEC Alpha */ -#ifdef SIXTY_FOUR_BIT_LONG -#define BN_ULLONG unsigned long long -#define BN_ULONG unsigned long -#define BN_LONG long -#define BN_BITS 128 -#define BN_BYTES 8 -#define BN_BITS2 64 -#define BN_BITS4 32 -#define BN_MASK (0xffffffffffffffffffffffffffffffffLL) -#define BN_MASK2 (0xffffffffffffffffL) -#define BN_MASK2l (0xffffffffL) -#define BN_MASK2h (0xffffffff00000000L) -#define BN_MASK2h1 (0xffffffff80000000L) -#define BN_TBIT (0x8000000000000000L) -#define BN_DEC_CONV (10000000000000000000UL) -#define BN_DEC_FMT1 "%lu" -#define BN_DEC_FMT2 "%019lu" -#define BN_DEC_NUM 19 -#define BN_HEX_FMT1 "%lX" -#define BN_HEX_FMT2 "%016lX" -#endif - -/* This is where the long long data type is 64 bits, but long is 32. - * For machines where there are 64bit registers, this is the mode to use. - * IRIX, on R4000 and above should use this mode, along with the relevant - * assembler code :-). Do NOT define BN_LLONG. - */ -#ifdef SIXTY_FOUR_BIT -#undef BN_LLONG -#undef BN_ULLONG -#define BN_ULONG unsigned long long -#define BN_LONG long long -#define BN_BITS 128 -#define BN_BYTES 8 -#define BN_BITS2 64 -#define BN_BITS4 32 -#define BN_MASK2 (0xffffffffffffffffLL) -#define BN_MASK2l (0xffffffffL) -#define BN_MASK2h (0xffffffff00000000LL) -#define BN_MASK2h1 (0xffffffff80000000LL) -#define BN_TBIT (0x8000000000000000LL) -#define BN_DEC_CONV (10000000000000000000ULL) -#define BN_DEC_FMT1 "%llu" -#define BN_DEC_FMT2 "%019llu" -#define BN_DEC_NUM 19 -#define BN_HEX_FMT1 "%llX" -#define BN_HEX_FMT2 "%016llX" -#endif - -#ifdef THIRTY_TWO_BIT -#ifdef BN_LLONG -# if defined(_WIN32) && !defined(__GNUC__) -# define BN_ULLONG unsigned __int64 -# define BN_MASK (0xffffffffffffffffI64) -# else -# define BN_ULLONG unsigned long long -# define BN_MASK (0xffffffffffffffffLL) -# endif -#endif -#define BN_ULONG unsigned int -#define BN_LONG int -#define BN_BITS 64 -#define BN_BYTES 4 -#define BN_BITS2 32 -#define BN_BITS4 16 -#define BN_MASK2 (0xffffffffL) -#define BN_MASK2l (0xffff) -#define BN_MASK2h1 (0xffff8000L) -#define BN_MASK2h (0xffff0000L) -#define BN_TBIT (0x80000000L) -#define BN_DEC_CONV (1000000000L) -#define BN_DEC_FMT1 "%u" -#define BN_DEC_FMT2 "%09u" -#define BN_DEC_NUM 9 -#define BN_HEX_FMT1 "%X" -#define BN_HEX_FMT2 "%08X" -#endif - -/* 2011-02-22 SMS. - * In various places, a size_t variable or a type cast to size_t was - * used to perform integer-only operations on pointers. This failed on - * VMS with 64-bit pointers (CC /POINTER_SIZE = 64) because size_t is - * still only 32 bits. What's needed in these cases is an integer type - * with the same size as a pointer, which size_t is not certain to be. - * The only fix here is VMS-specific. - */ -#if defined(OPENSSL_SYS_VMS) -# if __INITIAL_POINTER_SIZE == 64 -# define PTR_SIZE_INT long long -# else /* __INITIAL_POINTER_SIZE == 64 */ -# define PTR_SIZE_INT int -# endif /* __INITIAL_POINTER_SIZE == 64 [else] */ -#else /* defined(OPENSSL_SYS_VMS) */ -# define PTR_SIZE_INT size_t -#endif /* defined(OPENSSL_SYS_VMS) [else] */ - -#define BN_DEFAULT_BITS 1280 - -#define BN_FLG_MALLOCED 0x01 -#define BN_FLG_STATIC_DATA 0x02 -#define BN_FLG_CONSTTIME 0x04 /* avoid leaking exponent information through timing, - * BN_mod_exp_mont() will call BN_mod_exp_mont_consttime, - * BN_div() will call BN_div_no_branch, - * BN_mod_inverse() will call BN_mod_inverse_no_branch. - */ - -#ifndef OPENSSL_NO_DEPRECATED -#define BN_FLG_EXP_CONSTTIME BN_FLG_CONSTTIME /* deprecated name for the flag */ - /* avoid leaking exponent information through timings - * (BN_mod_exp_mont() will call BN_mod_exp_mont_consttime) */ -#endif - -#ifndef OPENSSL_NO_DEPRECATED -#define BN_FLG_FREE 0x8000 /* used for debuging */ -#endif -#define BN_set_flags(b,n) ((b)->flags|=(n)) -#define BN_get_flags(b,n) ((b)->flags&(n)) - -/* get a clone of a BIGNUM with changed flags, for *temporary* use only - * (the two BIGNUMs cannot not be used in parallel!) */ -#define BN_with_flags(dest,b,n) ((dest)->d=(b)->d, \ - (dest)->top=(b)->top, \ - (dest)->dmax=(b)->dmax, \ - (dest)->neg=(b)->neg, \ - (dest)->flags=(((dest)->flags & BN_FLG_MALLOCED) \ - | ((b)->flags & ~BN_FLG_MALLOCED) \ - | BN_FLG_STATIC_DATA \ - | (n))) - -/* Already declared in ossl_typ.h */ -#if 0 -typedef struct bignum_st BIGNUM; -/* Used for temp variables (declaration hidden in bn_lcl.h) */ -typedef struct bignum_ctx BN_CTX; -typedef struct bn_blinding_st BN_BLINDING; -typedef struct bn_mont_ctx_st BN_MONT_CTX; -typedef struct bn_recp_ctx_st BN_RECP_CTX; -typedef struct bn_gencb_st BN_GENCB; -#endif - -struct bignum_st - { - BN_ULONG *d; /* Pointer to an array of 'BN_BITS2' bit chunks. */ - int top; /* Index of last used d +1. */ - /* The next are internal book keeping for bn_expand. */ - int dmax; /* Size of the d array. */ - int neg; /* one if the number is negative */ - int flags; - }; - -/* Used for montgomery multiplication */ -struct bn_mont_ctx_st - { - int ri; /* number of bits in R */ - BIGNUM RR; /* used to convert to montgomery form */ - BIGNUM N; /* The modulus */ - BIGNUM Ni; /* R*(1/R mod N) - N*Ni = 1 - * (Ni is only stored for bignum algorithm) */ - BN_ULONG n0[2];/* least significant word(s) of Ni; - (type changed with 0.9.9, was "BN_ULONG n0;" before) */ - int flags; - }; - -/* Used for reciprocal division/mod functions - * It cannot be shared between threads - */ -struct bn_recp_ctx_st - { - BIGNUM N; /* the divisor */ - BIGNUM Nr; /* the reciprocal */ - int num_bits; - int shift; - int flags; - }; - -/* Used for slow "generation" functions. */ -struct bn_gencb_st - { - unsigned int ver; /* To handle binary (in)compatibility */ - void *arg; /* callback-specific data */ - union - { - /* if(ver==1) - handles old style callbacks */ - void (*cb_1)(int, int, void *); - /* if(ver==2) - new callback style */ - int (*cb_2)(int, int, BN_GENCB *); - } cb; - }; -/* Wrapper function to make using BN_GENCB easier, */ -int BN_GENCB_call(BN_GENCB *cb, int a, int b); -/* Macro to populate a BN_GENCB structure with an "old"-style callback */ -#define BN_GENCB_set_old(gencb, callback, cb_arg) { \ - BN_GENCB *tmp_gencb = (gencb); \ - tmp_gencb->ver = 1; \ - tmp_gencb->arg = (cb_arg); \ - tmp_gencb->cb.cb_1 = (callback); } -/* Macro to populate a BN_GENCB structure with a "new"-style callback */ -#define BN_GENCB_set(gencb, callback, cb_arg) { \ - BN_GENCB *tmp_gencb = (gencb); \ - tmp_gencb->ver = 2; \ - tmp_gencb->arg = (cb_arg); \ - tmp_gencb->cb.cb_2 = (callback); } - -#define BN_prime_checks 0 /* default: select number of iterations - based on the size of the number */ - -/* number of Miller-Rabin iterations for an error rate of less than 2^-80 - * for random 'b'-bit input, b >= 100 (taken from table 4.4 in the Handbook - * of Applied Cryptography [Menezes, van Oorschot, Vanstone; CRC Press 1996]; - * original paper: Damgaard, Landrock, Pomerance: Average case error estimates - * for the strong probable prime test. -- Math. Comp. 61 (1993) 177-194) */ -#define BN_prime_checks_for_size(b) ((b) >= 1300 ? 2 : \ - (b) >= 850 ? 3 : \ - (b) >= 650 ? 4 : \ - (b) >= 550 ? 5 : \ - (b) >= 450 ? 6 : \ - (b) >= 400 ? 7 : \ - (b) >= 350 ? 8 : \ - (b) >= 300 ? 9 : \ - (b) >= 250 ? 12 : \ - (b) >= 200 ? 15 : \ - (b) >= 150 ? 18 : \ - /* b >= 100 */ 27) - -#define BN_num_bytes(a) ((BN_num_bits(a)+7)/8) - -/* Note that BN_abs_is_word didn't work reliably for w == 0 until 0.9.8 */ -#define BN_abs_is_word(a,w) ((((a)->top == 1) && ((a)->d[0] == (BN_ULONG)(w))) || \ - (((w) == 0) && ((a)->top == 0))) -#define BN_is_zero(a) ((a)->top == 0) -#define BN_is_one(a) (BN_abs_is_word((a),1) && !(a)->neg) -#define BN_is_word(a,w) (BN_abs_is_word((a),(w)) && (!(w) || !(a)->neg)) -#define BN_is_odd(a) (((a)->top > 0) && ((a)->d[0] & 1)) - -#define BN_one(a) (BN_set_word((a),1)) -#define BN_zero_ex(a) \ - do { \ - BIGNUM *_tmp_bn = (a); \ - _tmp_bn->top = 0; \ - _tmp_bn->neg = 0; \ - } while(0) -#ifdef OPENSSL_NO_DEPRECATED -#define BN_zero(a) BN_zero_ex(a) -#else -#define BN_zero(a) (BN_set_word((a),0)) -#endif - -const BIGNUM *BN_value_one(void); -char * BN_options(void); -BN_CTX *BN_CTX_new(void); -#ifndef OPENSSL_NO_DEPRECATED -void BN_CTX_init(BN_CTX *c); -#endif -void BN_CTX_free(BN_CTX *c); -void BN_CTX_start(BN_CTX *ctx); -BIGNUM *BN_CTX_get(BN_CTX *ctx); -void BN_CTX_end(BN_CTX *ctx); -int BN_rand(BIGNUM *rnd, int bits, int top,int bottom); -int BN_pseudo_rand(BIGNUM *rnd, int bits, int top,int bottom); -int BN_rand_range(BIGNUM *rnd, const BIGNUM *range); -int BN_pseudo_rand_range(BIGNUM *rnd, const BIGNUM *range); -int BN_num_bits(const BIGNUM *a); -int BN_num_bits_word(BN_ULONG); -BIGNUM *BN_new(void); -void BN_init(BIGNUM *); -void BN_clear_free(BIGNUM *a); -BIGNUM *BN_copy(BIGNUM *a, const BIGNUM *b); -void BN_swap(BIGNUM *a, BIGNUM *b); -BIGNUM *BN_bin2bn(const unsigned char *s,int len,BIGNUM *ret); -int BN_bn2bin(const BIGNUM *a, unsigned char *to); -BIGNUM *BN_mpi2bn(const unsigned char *s,int len,BIGNUM *ret); -int BN_bn2mpi(const BIGNUM *a, unsigned char *to); -int BN_sub(BIGNUM *r, const BIGNUM *a, const BIGNUM *b); -int BN_usub(BIGNUM *r, const BIGNUM *a, const BIGNUM *b); -int BN_uadd(BIGNUM *r, const BIGNUM *a, const BIGNUM *b); -int BN_add(BIGNUM *r, const BIGNUM *a, const BIGNUM *b); -int BN_mul(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, BN_CTX *ctx); -int BN_sqr(BIGNUM *r, const BIGNUM *a,BN_CTX *ctx); -/** BN_set_negative sets sign of a BIGNUM - * \param b pointer to the BIGNUM object - * \param n 0 if the BIGNUM b should be positive and a value != 0 otherwise - */ -void BN_set_negative(BIGNUM *b, int n); -/** BN_is_negative returns 1 if the BIGNUM is negative - * \param a pointer to the BIGNUM object - * \return 1 if a < 0 and 0 otherwise - */ -#define BN_is_negative(a) ((a)->neg != 0) - -int BN_div(BIGNUM *dv, BIGNUM *rem, const BIGNUM *m, const BIGNUM *d, - BN_CTX *ctx); -#define BN_mod(rem,m,d,ctx) BN_div(NULL,(rem),(m),(d),(ctx)) -int BN_nnmod(BIGNUM *r, const BIGNUM *m, const BIGNUM *d, BN_CTX *ctx); -int BN_mod_add(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, const BIGNUM *m, BN_CTX *ctx); -int BN_mod_add_quick(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, const BIGNUM *m); -int BN_mod_sub(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, const BIGNUM *m, BN_CTX *ctx); -int BN_mod_sub_quick(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, const BIGNUM *m); -int BN_mod_mul(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, - const BIGNUM *m, BN_CTX *ctx); -int BN_mod_sqr(BIGNUM *r, const BIGNUM *a, const BIGNUM *m, BN_CTX *ctx); -int BN_mod_lshift1(BIGNUM *r, const BIGNUM *a, const BIGNUM *m, BN_CTX *ctx); -int BN_mod_lshift1_quick(BIGNUM *r, const BIGNUM *a, const BIGNUM *m); -int BN_mod_lshift(BIGNUM *r, const BIGNUM *a, int n, const BIGNUM *m, BN_CTX *ctx); -int BN_mod_lshift_quick(BIGNUM *r, const BIGNUM *a, int n, const BIGNUM *m); - -BN_ULONG BN_mod_word(const BIGNUM *a, BN_ULONG w); -BN_ULONG BN_div_word(BIGNUM *a, BN_ULONG w); -int BN_mul_word(BIGNUM *a, BN_ULONG w); -int BN_add_word(BIGNUM *a, BN_ULONG w); -int BN_sub_word(BIGNUM *a, BN_ULONG w); -int BN_set_word(BIGNUM *a, BN_ULONG w); -BN_ULONG BN_get_word(const BIGNUM *a); - -int BN_cmp(const BIGNUM *a, const BIGNUM *b); -void BN_free(BIGNUM *a); -int BN_is_bit_set(const BIGNUM *a, int n); -int BN_lshift(BIGNUM *r, const BIGNUM *a, int n); -int BN_lshift1(BIGNUM *r, const BIGNUM *a); -int BN_exp(BIGNUM *r, const BIGNUM *a, const BIGNUM *p,BN_CTX *ctx); - -int BN_mod_exp(BIGNUM *r, const BIGNUM *a, const BIGNUM *p, - const BIGNUM *m,BN_CTX *ctx); -int BN_mod_exp_mont(BIGNUM *r, const BIGNUM *a, const BIGNUM *p, - const BIGNUM *m, BN_CTX *ctx, BN_MONT_CTX *m_ctx); -int BN_mod_exp_mont_consttime(BIGNUM *rr, const BIGNUM *a, const BIGNUM *p, - const BIGNUM *m, BN_CTX *ctx, BN_MONT_CTX *in_mont); -int BN_mod_exp_mont_word(BIGNUM *r, BN_ULONG a, const BIGNUM *p, - const BIGNUM *m, BN_CTX *ctx, BN_MONT_CTX *m_ctx); -int BN_mod_exp2_mont(BIGNUM *r, const BIGNUM *a1, const BIGNUM *p1, - const BIGNUM *a2, const BIGNUM *p2,const BIGNUM *m, - BN_CTX *ctx,BN_MONT_CTX *m_ctx); -int BN_mod_exp_simple(BIGNUM *r, const BIGNUM *a, const BIGNUM *p, - const BIGNUM *m,BN_CTX *ctx); - -int BN_mask_bits(BIGNUM *a,int n); -#ifndef OPENSSL_NO_FP_API -int BN_print_fp(FILE *fp, const BIGNUM *a); -#endif -#ifdef HEADER_BIO_H -int BN_print(BIO *fp, const BIGNUM *a); -#else -int BN_print(void *fp, const BIGNUM *a); -#endif -int BN_reciprocal(BIGNUM *r, const BIGNUM *m, int len, BN_CTX *ctx); -int BN_rshift(BIGNUM *r, const BIGNUM *a, int n); -int BN_rshift1(BIGNUM *r, const BIGNUM *a); -void BN_clear(BIGNUM *a); -BIGNUM *BN_dup(const BIGNUM *a); -int BN_ucmp(const BIGNUM *a, const BIGNUM *b); -int BN_set_bit(BIGNUM *a, int n); -int BN_clear_bit(BIGNUM *a, int n); -char * BN_bn2hex(const BIGNUM *a); -char * BN_bn2dec(const BIGNUM *a); -int BN_hex2bn(BIGNUM **a, const char *str); -int BN_dec2bn(BIGNUM **a, const char *str); -int BN_asc2bn(BIGNUM **a, const char *str); -int BN_gcd(BIGNUM *r,const BIGNUM *a,const BIGNUM *b,BN_CTX *ctx); -int BN_kronecker(const BIGNUM *a,const BIGNUM *b,BN_CTX *ctx); /* returns -2 for error */ -BIGNUM *BN_mod_inverse(BIGNUM *ret, - const BIGNUM *a, const BIGNUM *n,BN_CTX *ctx); -BIGNUM *BN_mod_sqrt(BIGNUM *ret, - const BIGNUM *a, const BIGNUM *n,BN_CTX *ctx); - -/* Deprecated versions */ -#ifndef OPENSSL_NO_DEPRECATED -BIGNUM *BN_generate_prime(BIGNUM *ret,int bits,int safe, - const BIGNUM *add, const BIGNUM *rem, - void (*callback)(int,int,void *),void *cb_arg); -int BN_is_prime(const BIGNUM *p,int nchecks, - void (*callback)(int,int,void *), - BN_CTX *ctx,void *cb_arg); -int BN_is_prime_fasttest(const BIGNUM *p,int nchecks, - void (*callback)(int,int,void *),BN_CTX *ctx,void *cb_arg, - int do_trial_division); -#endif /* !defined(OPENSSL_NO_DEPRECATED) */ - -/* Newer versions */ -int BN_generate_prime_ex(BIGNUM *ret,int bits,int safe, const BIGNUM *add, - const BIGNUM *rem, BN_GENCB *cb); -int BN_is_prime_ex(const BIGNUM *p,int nchecks, BN_CTX *ctx, BN_GENCB *cb); -int BN_is_prime_fasttest_ex(const BIGNUM *p,int nchecks, BN_CTX *ctx, - int do_trial_division, BN_GENCB *cb); - -int BN_X931_generate_Xpq(BIGNUM *Xp, BIGNUM *Xq, int nbits, BN_CTX *ctx); - -int BN_X931_derive_prime_ex(BIGNUM *p, BIGNUM *p1, BIGNUM *p2, - const BIGNUM *Xp, const BIGNUM *Xp1, const BIGNUM *Xp2, - const BIGNUM *e, BN_CTX *ctx, BN_GENCB *cb); -int BN_X931_generate_prime_ex(BIGNUM *p, BIGNUM *p1, BIGNUM *p2, - BIGNUM *Xp1, BIGNUM *Xp2, - const BIGNUM *Xp, - const BIGNUM *e, BN_CTX *ctx, - BN_GENCB *cb); - -BN_MONT_CTX *BN_MONT_CTX_new(void ); -void BN_MONT_CTX_init(BN_MONT_CTX *ctx); -int BN_mod_mul_montgomery(BIGNUM *r,const BIGNUM *a,const BIGNUM *b, - BN_MONT_CTX *mont, BN_CTX *ctx); -#define BN_to_montgomery(r,a,mont,ctx) BN_mod_mul_montgomery(\ - (r),(a),&((mont)->RR),(mont),(ctx)) -int BN_from_montgomery(BIGNUM *r,const BIGNUM *a, - BN_MONT_CTX *mont, BN_CTX *ctx); -void BN_MONT_CTX_free(BN_MONT_CTX *mont); -int BN_MONT_CTX_set(BN_MONT_CTX *mont,const BIGNUM *mod,BN_CTX *ctx); -BN_MONT_CTX *BN_MONT_CTX_copy(BN_MONT_CTX *to,BN_MONT_CTX *from); -BN_MONT_CTX *BN_MONT_CTX_set_locked(BN_MONT_CTX **pmont, int lock, - const BIGNUM *mod, BN_CTX *ctx); - -/* BN_BLINDING flags */ -#define BN_BLINDING_NO_UPDATE 0x00000001 -#define BN_BLINDING_NO_RECREATE 0x00000002 - -BN_BLINDING *BN_BLINDING_new(const BIGNUM *A, const BIGNUM *Ai, BIGNUM *mod); -void BN_BLINDING_free(BN_BLINDING *b); -int BN_BLINDING_update(BN_BLINDING *b,BN_CTX *ctx); -int BN_BLINDING_convert(BIGNUM *n, BN_BLINDING *b, BN_CTX *ctx); -int BN_BLINDING_invert(BIGNUM *n, BN_BLINDING *b, BN_CTX *ctx); -int BN_BLINDING_convert_ex(BIGNUM *n, BIGNUM *r, BN_BLINDING *b, BN_CTX *); -int BN_BLINDING_invert_ex(BIGNUM *n, const BIGNUM *r, BN_BLINDING *b, BN_CTX *); -#ifndef OPENSSL_NO_DEPRECATED -unsigned long BN_BLINDING_get_thread_id(const BN_BLINDING *); -void BN_BLINDING_set_thread_id(BN_BLINDING *, unsigned long); -#endif -CRYPTO_THREADID *BN_BLINDING_thread_id(BN_BLINDING *); -unsigned long BN_BLINDING_get_flags(const BN_BLINDING *); -void BN_BLINDING_set_flags(BN_BLINDING *, unsigned long); -BN_BLINDING *BN_BLINDING_create_param(BN_BLINDING *b, - const BIGNUM *e, BIGNUM *m, BN_CTX *ctx, - int (*bn_mod_exp)(BIGNUM *r, const BIGNUM *a, const BIGNUM *p, - const BIGNUM *m, BN_CTX *ctx, BN_MONT_CTX *m_ctx), - BN_MONT_CTX *m_ctx); - -#ifndef OPENSSL_NO_DEPRECATED -void BN_set_params(int mul,int high,int low,int mont); -int BN_get_params(int which); /* 0, mul, 1 high, 2 low, 3 mont */ -#endif - -void BN_RECP_CTX_init(BN_RECP_CTX *recp); -BN_RECP_CTX *BN_RECP_CTX_new(void); -void BN_RECP_CTX_free(BN_RECP_CTX *recp); -int BN_RECP_CTX_set(BN_RECP_CTX *recp,const BIGNUM *rdiv,BN_CTX *ctx); -int BN_mod_mul_reciprocal(BIGNUM *r, const BIGNUM *x, const BIGNUM *y, - BN_RECP_CTX *recp,BN_CTX *ctx); -int BN_mod_exp_recp(BIGNUM *r, const BIGNUM *a, const BIGNUM *p, - const BIGNUM *m, BN_CTX *ctx); -int BN_div_recp(BIGNUM *dv, BIGNUM *rem, const BIGNUM *m, - BN_RECP_CTX *recp, BN_CTX *ctx); - -#ifndef OPENSSL_NO_EC2M - -/* Functions for arithmetic over binary polynomials represented by BIGNUMs. - * - * The BIGNUM::neg property of BIGNUMs representing binary polynomials is - * ignored. - * - * Note that input arguments are not const so that their bit arrays can - * be expanded to the appropriate size if needed. - */ - -int BN_GF2m_add(BIGNUM *r, const BIGNUM *a, const BIGNUM *b); /*r = a + b*/ -#define BN_GF2m_sub(r, a, b) BN_GF2m_add(r, a, b) -int BN_GF2m_mod(BIGNUM *r, const BIGNUM *a, const BIGNUM *p); /*r=a mod p*/ -int BN_GF2m_mod_mul(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, - const BIGNUM *p, BN_CTX *ctx); /* r = (a * b) mod p */ -int BN_GF2m_mod_sqr(BIGNUM *r, const BIGNUM *a, const BIGNUM *p, - BN_CTX *ctx); /* r = (a * a) mod p */ -int BN_GF2m_mod_inv(BIGNUM *r, const BIGNUM *b, const BIGNUM *p, - BN_CTX *ctx); /* r = (1 / b) mod p */ -int BN_GF2m_mod_div(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, - const BIGNUM *p, BN_CTX *ctx); /* r = (a / b) mod p */ -int BN_GF2m_mod_exp(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, - const BIGNUM *p, BN_CTX *ctx); /* r = (a ^ b) mod p */ -int BN_GF2m_mod_sqrt(BIGNUM *r, const BIGNUM *a, const BIGNUM *p, - BN_CTX *ctx); /* r = sqrt(a) mod p */ -int BN_GF2m_mod_solve_quad(BIGNUM *r, const BIGNUM *a, const BIGNUM *p, - BN_CTX *ctx); /* r^2 + r = a mod p */ -#define BN_GF2m_cmp(a, b) BN_ucmp((a), (b)) -/* Some functions allow for representation of the irreducible polynomials - * as an unsigned int[], say p. The irreducible f(t) is then of the form: - * t^p[0] + t^p[1] + ... + t^p[k] - * where m = p[0] > p[1] > ... > p[k] = 0. - */ -int BN_GF2m_mod_arr(BIGNUM *r, const BIGNUM *a, const int p[]); - /* r = a mod p */ -int BN_GF2m_mod_mul_arr(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, - const int p[], BN_CTX *ctx); /* r = (a * b) mod p */ -int BN_GF2m_mod_sqr_arr(BIGNUM *r, const BIGNUM *a, const int p[], - BN_CTX *ctx); /* r = (a * a) mod p */ -int BN_GF2m_mod_inv_arr(BIGNUM *r, const BIGNUM *b, const int p[], - BN_CTX *ctx); /* r = (1 / b) mod p */ -int BN_GF2m_mod_div_arr(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, - const int p[], BN_CTX *ctx); /* r = (a / b) mod p */ -int BN_GF2m_mod_exp_arr(BIGNUM *r, const BIGNUM *a, const BIGNUM *b, - const int p[], BN_CTX *ctx); /* r = (a ^ b) mod p */ -int BN_GF2m_mod_sqrt_arr(BIGNUM *r, const BIGNUM *a, - const int p[], BN_CTX *ctx); /* r = sqrt(a) mod p */ -int BN_GF2m_mod_solve_quad_arr(BIGNUM *r, const BIGNUM *a, - const int p[], BN_CTX *ctx); /* r^2 + r = a mod p */ -int BN_GF2m_poly2arr(const BIGNUM *a, int p[], int max); -int BN_GF2m_arr2poly(const int p[], BIGNUM *a); - -#endif - -/* faster mod functions for the 'NIST primes' - * 0 <= a < p^2 */ -int BN_nist_mod_192(BIGNUM *r, const BIGNUM *a, const BIGNUM *p, BN_CTX *ctx); -int BN_nist_mod_224(BIGNUM *r, const BIGNUM *a, const BIGNUM *p, BN_CTX *ctx); -int BN_nist_mod_256(BIGNUM *r, const BIGNUM *a, const BIGNUM *p, BN_CTX *ctx); -int BN_nist_mod_384(BIGNUM *r, const BIGNUM *a, const BIGNUM *p, BN_CTX *ctx); -int BN_nist_mod_521(BIGNUM *r, const BIGNUM *a, const BIGNUM *p, BN_CTX *ctx); - -const BIGNUM *BN_get0_nist_prime_192(void); -const BIGNUM *BN_get0_nist_prime_224(void); -const BIGNUM *BN_get0_nist_prime_256(void); -const BIGNUM *BN_get0_nist_prime_384(void); -const BIGNUM *BN_get0_nist_prime_521(void); - -/* library internal functions */ - -#define bn_expand(a,bits) ((((((bits+BN_BITS2-1))/BN_BITS2)) <= (a)->dmax)?\ - (a):bn_expand2((a),(bits+BN_BITS2-1)/BN_BITS2)) -#define bn_wexpand(a,words) (((words) <= (a)->dmax)?(a):bn_expand2((a),(words))) -BIGNUM *bn_expand2(BIGNUM *a, int words); -#ifndef OPENSSL_NO_DEPRECATED -BIGNUM *bn_dup_expand(const BIGNUM *a, int words); /* unused */ -#endif - -/* Bignum consistency macros - * There is one "API" macro, bn_fix_top(), for stripping leading zeroes from - * bignum data after direct manipulations on the data. There is also an - * "internal" macro, bn_check_top(), for verifying that there are no leading - * zeroes. Unfortunately, some auditing is required due to the fact that - * bn_fix_top() has become an overabused duct-tape because bignum data is - * occasionally passed around in an inconsistent state. So the following - * changes have been made to sort this out; - * - bn_fix_top()s implementation has been moved to bn_correct_top() - * - if BN_DEBUG isn't defined, bn_fix_top() maps to bn_correct_top(), and - * bn_check_top() is as before. - * - if BN_DEBUG *is* defined; - * - bn_check_top() tries to pollute unused words even if the bignum 'top' is - * consistent. (ed: only if BN_DEBUG_RAND is defined) - * - bn_fix_top() maps to bn_check_top() rather than "fixing" anything. - * The idea is to have debug builds flag up inconsistent bignums when they - * occur. If that occurs in a bn_fix_top(), we examine the code in question; if - * the use of bn_fix_top() was appropriate (ie. it follows directly after code - * that manipulates the bignum) it is converted to bn_correct_top(), and if it - * was not appropriate, we convert it permanently to bn_check_top() and track - * down the cause of the bug. Eventually, no internal code should be using the - * bn_fix_top() macro. External applications and libraries should try this with - * their own code too, both in terms of building against the openssl headers - * with BN_DEBUG defined *and* linking with a version of OpenSSL built with it - * defined. This not only improves external code, it provides more test - * coverage for openssl's own code. - */ - -#ifdef BN_DEBUG - -/* We only need assert() when debugging */ -#include - -#ifdef BN_DEBUG_RAND -/* To avoid "make update" cvs wars due to BN_DEBUG, use some tricks */ -#ifndef RAND_pseudo_bytes -int RAND_pseudo_bytes(unsigned char *buf,int num); -#define BN_DEBUG_TRIX -#endif -#define bn_pollute(a) \ - do { \ - const BIGNUM *_bnum1 = (a); \ - if(_bnum1->top < _bnum1->dmax) { \ - unsigned char _tmp_char; \ - /* We cast away const without the compiler knowing, any \ - * *genuinely* constant variables that aren't mutable \ - * wouldn't be constructed with top!=dmax. */ \ - BN_ULONG *_not_const; \ - memcpy(&_not_const, &_bnum1->d, sizeof(BN_ULONG*)); \ - RAND_pseudo_bytes(&_tmp_char, 1); \ - memset((unsigned char *)(_not_const + _bnum1->top), _tmp_char, \ - (_bnum1->dmax - _bnum1->top) * sizeof(BN_ULONG)); \ - } \ - } while(0) -#ifdef BN_DEBUG_TRIX -#undef RAND_pseudo_bytes -#endif -#else -#define bn_pollute(a) -#endif -#define bn_check_top(a) \ - do { \ - const BIGNUM *_bnum2 = (a); \ - if (_bnum2 != NULL) { \ - assert((_bnum2->top == 0) || \ - (_bnum2->d[_bnum2->top - 1] != 0)); \ - bn_pollute(_bnum2); \ - } \ - } while(0) - -#define bn_fix_top(a) bn_check_top(a) - -#else /* !BN_DEBUG */ - -#define bn_pollute(a) -#define bn_check_top(a) -#define bn_fix_top(a) bn_correct_top(a) - -#endif - -#define bn_correct_top(a) \ - { \ - BN_ULONG *ftl; \ - int tmp_top = (a)->top; \ - if (tmp_top > 0) \ - { \ - for (ftl= &((a)->d[tmp_top-1]); tmp_top > 0; tmp_top--) \ - if (*(ftl--)) break; \ - (a)->top = tmp_top; \ - } \ - bn_pollute(a); \ - } - -BN_ULONG bn_mul_add_words(BN_ULONG *rp, const BN_ULONG *ap, int num, BN_ULONG w); -BN_ULONG bn_mul_words(BN_ULONG *rp, const BN_ULONG *ap, int num, BN_ULONG w); -void bn_sqr_words(BN_ULONG *rp, const BN_ULONG *ap, int num); -BN_ULONG bn_div_words(BN_ULONG h, BN_ULONG l, BN_ULONG d); -BN_ULONG bn_add_words(BN_ULONG *rp, const BN_ULONG *ap, const BN_ULONG *bp,int num); -BN_ULONG bn_sub_words(BN_ULONG *rp, const BN_ULONG *ap, const BN_ULONG *bp,int num); - -/* Primes from RFC 2409 */ -BIGNUM *get_rfc2409_prime_768(BIGNUM *bn); -BIGNUM *get_rfc2409_prime_1024(BIGNUM *bn); - -/* Primes from RFC 3526 */ -BIGNUM *get_rfc3526_prime_1536(BIGNUM *bn); -BIGNUM *get_rfc3526_prime_2048(BIGNUM *bn); -BIGNUM *get_rfc3526_prime_3072(BIGNUM *bn); -BIGNUM *get_rfc3526_prime_4096(BIGNUM *bn); -BIGNUM *get_rfc3526_prime_6144(BIGNUM *bn); -BIGNUM *get_rfc3526_prime_8192(BIGNUM *bn); - -int BN_bntest_rand(BIGNUM *rnd, int bits, int top,int bottom); - -/* BEGIN ERROR CODES */ -/* The following lines are auto generated by the script mkerr.pl. Any changes - * made after this point may be overwritten when the script is next run. - */ -void ERR_load_BN_strings(void); - -/* Error codes for the BN functions. */ - -/* Function codes. */ -#define BN_F_BNRAND 127 -#define BN_F_BN_BLINDING_CONVERT_EX 100 -#define BN_F_BN_BLINDING_CREATE_PARAM 128 -#define BN_F_BN_BLINDING_INVERT_EX 101 -#define BN_F_BN_BLINDING_NEW 102 -#define BN_F_BN_BLINDING_UPDATE 103 -#define BN_F_BN_BN2DEC 104 -#define BN_F_BN_BN2HEX 105 -#define BN_F_BN_CTX_GET 116 -#define BN_F_BN_CTX_NEW 106 -#define BN_F_BN_CTX_START 129 -#define BN_F_BN_DIV 107 -#define BN_F_BN_DIV_NO_BRANCH 138 -#define BN_F_BN_DIV_RECP 130 -#define BN_F_BN_EXP 123 -#define BN_F_BN_EXPAND2 108 -#define BN_F_BN_EXPAND_INTERNAL 120 -#define BN_F_BN_GF2M_MOD 131 -#define BN_F_BN_GF2M_MOD_EXP 132 -#define BN_F_BN_GF2M_MOD_MUL 133 -#define BN_F_BN_GF2M_MOD_SOLVE_QUAD 134 -#define BN_F_BN_GF2M_MOD_SOLVE_QUAD_ARR 135 -#define BN_F_BN_GF2M_MOD_SQR 136 -#define BN_F_BN_GF2M_MOD_SQRT 137 -#define BN_F_BN_MOD_EXP2_MONT 118 -#define BN_F_BN_MOD_EXP_MONT 109 -#define BN_F_BN_MOD_EXP_MONT_CONSTTIME 124 -#define BN_F_BN_MOD_EXP_MONT_WORD 117 -#define BN_F_BN_MOD_EXP_RECP 125 -#define BN_F_BN_MOD_EXP_SIMPLE 126 -#define BN_F_BN_MOD_INVERSE 110 -#define BN_F_BN_MOD_INVERSE_NO_BRANCH 139 -#define BN_F_BN_MOD_LSHIFT_QUICK 119 -#define BN_F_BN_MOD_MUL_RECIPROCAL 111 -#define BN_F_BN_MOD_SQRT 121 -#define BN_F_BN_MPI2BN 112 -#define BN_F_BN_NEW 113 -#define BN_F_BN_RAND 114 -#define BN_F_BN_RAND_RANGE 122 -#define BN_F_BN_USUB 115 - -/* Reason codes. */ -#define BN_R_ARG2_LT_ARG3 100 -#define BN_R_BAD_RECIPROCAL 101 -#define BN_R_BIGNUM_TOO_LONG 114 -#define BN_R_CALLED_WITH_EVEN_MODULUS 102 -#define BN_R_DIV_BY_ZERO 103 -#define BN_R_ENCODING_ERROR 104 -#define BN_R_EXPAND_ON_STATIC_BIGNUM_DATA 105 -#define BN_R_INPUT_NOT_REDUCED 110 -#define BN_R_INVALID_LENGTH 106 -#define BN_R_INVALID_RANGE 115 -#define BN_R_NOT_A_SQUARE 111 -#define BN_R_NOT_INITIALIZED 107 -#define BN_R_NO_INVERSE 108 -#define BN_R_NO_SOLUTION 116 -#define BN_R_P_IS_NOT_PRIME 112 -#define BN_R_TOO_MANY_ITERATIONS 113 -#define BN_R_TOO_MANY_TEMPORARY_VARIABLES 109 - -#ifdef __cplusplus -} -#endif -#endif diff --git a/src/plugins/ftp/openssl/buffer.h b/src/plugins/ftp/openssl/buffer.h deleted file mode 100644 index 178e4182..00000000 --- a/src/plugins/ftp/openssl/buffer.h +++ /dev/null @@ -1,119 +0,0 @@ -/* crypto/buffer/buffer.h */ -/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) - * All rights reserved. - * - * This package is an SSL implementation written - * by Eric Young (eay@cryptsoft.com). - * The implementation was written so as to conform with Netscapes SSL. - * - * This library is free for commercial and non-commercial use as long as - * the following conditions are aheared to. The following conditions - * apply to all code found in this distribution, be it the RC4, RSA, - * lhash, DES, etc., code; not just the SSL code. The SSL documentation - * included with this distribution is covered by the same copyright terms - * except that the holder is Tim Hudson (tjh@cryptsoft.com). - * - * Copyright remains Eric Young's, and as such any Copyright notices in - * the code are not to be removed. - * If this package is used in a product, Eric Young should be given attribution - * as the author of the parts of the library used. - * This can be in the form of a textual message at program startup or - * in documentation (online or textual) provided with the package. - * - * 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 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. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * "This product includes cryptographic software written by - * Eric Young (eay@cryptsoft.com)" - * The word 'cryptographic' can be left out if the rouines from the library - * being used are not cryptographic related :-). - * 4. If you include any Windows specific code (or a derivative thereof) from - * the apps directory (application code) you must include an acknowledgement: - * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" - * - * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``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 AUTHOR 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. - * - * The licence and distribution terms for any publically available version or - * derivative of this code cannot be changed. i.e. this code cannot simply be - * copied and put under another distribution licence - * [including the GNU Public Licence.] - */ - -#ifndef HEADER_BUFFER_H -#define HEADER_BUFFER_H - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -#if !defined(NO_SYS_TYPES_H) -#include -#endif - -/* Already declared in ossl_typ.h */ -/* typedef struct buf_mem_st BUF_MEM; */ - -struct buf_mem_st - { - size_t length; /* current number of bytes */ - char *data; - size_t max; /* size of buffer */ - }; - -BUF_MEM *BUF_MEM_new(void); -void BUF_MEM_free(BUF_MEM *a); -int BUF_MEM_grow(BUF_MEM *str, size_t len); -int BUF_MEM_grow_clean(BUF_MEM *str, size_t len); -char * BUF_strdup(const char *str); -char * BUF_strndup(const char *str, size_t siz); -void * BUF_memdup(const void *data, size_t siz); -void BUF_reverse(unsigned char *out, unsigned char *in, size_t siz); - -/* safe string functions */ -size_t BUF_strlcpy(char *dst,const char *src,size_t siz); -size_t BUF_strlcat(char *dst,const char *src,size_t siz); - - -/* BEGIN ERROR CODES */ -/* The following lines are auto generated by the script mkerr.pl. Any changes - * made after this point may be overwritten when the script is next run. - */ -void ERR_load_BUF_strings(void); - -/* Error codes for the BUF functions. */ - -/* Function codes. */ -#define BUF_F_BUF_MEMDUP 103 -#define BUF_F_BUF_MEM_GROW 100 -#define BUF_F_BUF_MEM_GROW_CLEAN 105 -#define BUF_F_BUF_MEM_NEW 101 -#define BUF_F_BUF_STRDUP 102 -#define BUF_F_BUF_STRNDUP 104 - -/* Reason codes. */ - -#ifdef __cplusplus -} -#endif -#endif diff --git a/src/plugins/ftp/openssl/crypto.h b/src/plugins/ftp/openssl/crypto.h deleted file mode 100644 index 6aeda0a9..00000000 --- a/src/plugins/ftp/openssl/crypto.h +++ /dev/null @@ -1,604 +0,0 @@ -/* crypto/crypto.h */ -/* ==================================================================== - * Copyright (c) 1998-2006 The OpenSSL Project. All rights reserved. - * - * 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. All advertising materials mentioning features or use of this - * software must display the following acknowledgment: - * "This product includes software developed by the OpenSSL Project - * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" - * - * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For written permission, please contact - * openssl-core@openssl.org. - * - * 5. Products derived from this software may not be called "OpenSSL" - * nor may "OpenSSL" appear in their names without prior written - * permission of the OpenSSL Project. - * - * 6. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by the OpenSSL Project - * for use in the OpenSSL Toolkit (http://www.openssl.org/)" - * - * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY - * EXPRESSED 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 OpenSSL PROJECT OR - * ITS 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. - * ==================================================================== - * - * This product includes cryptographic software written by Eric Young - * (eay@cryptsoft.com). This product includes software written by Tim - * Hudson (tjh@cryptsoft.com). - * - */ -/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) - * All rights reserved. - * - * This package is an SSL implementation written - * by Eric Young (eay@cryptsoft.com). - * The implementation was written so as to conform with Netscapes SSL. - * - * This library is free for commercial and non-commercial use as long as - * the following conditions are aheared to. The following conditions - * apply to all code found in this distribution, be it the RC4, RSA, - * lhash, DES, etc., code; not just the SSL code. The SSL documentation - * included with this distribution is covered by the same copyright terms - * except that the holder is Tim Hudson (tjh@cryptsoft.com). - * - * Copyright remains Eric Young's, and as such any Copyright notices in - * the code are not to be removed. - * If this package is used in a product, Eric Young should be given attribution - * as the author of the parts of the library used. - * This can be in the form of a textual message at program startup or - * in documentation (online or textual) provided with the package. - * - * 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 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. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * "This product includes cryptographic software written by - * Eric Young (eay@cryptsoft.com)" - * The word 'cryptographic' can be left out if the rouines from the library - * being used are not cryptographic related :-). - * 4. If you include any Windows specific code (or a derivative thereof) from - * the apps directory (application code) you must include an acknowledgement: - * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" - * - * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``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 AUTHOR 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. - * - * The licence and distribution terms for any publically available version or - * derivative of this code cannot be changed. i.e. this code cannot simply be - * copied and put under another distribution licence - * [including the GNU Public Licence.] - */ -/* ==================================================================== - * Copyright 2002 Sun Microsystems, Inc. ALL RIGHTS RESERVED. - * ECDH support in OpenSSL originally developed by - * SUN MICROSYSTEMS, INC., and contributed to the OpenSSL project. - */ - -#ifndef HEADER_CRYPTO_H -#define HEADER_CRYPTO_H - -#include - -#include - -#ifndef OPENSSL_NO_FP_API -#include -#endif - -#include -#include -#include -#include - -#ifdef CHARSET_EBCDIC -#include -#endif - -/* Resolve problems on some operating systems with symbol names that clash - one way or another */ -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/* Backward compatibility to SSLeay */ -/* This is more to be used to check the correct DLL is being used - * in the MS world. */ -#define SSLEAY_VERSION_NUMBER OPENSSL_VERSION_NUMBER -#define SSLEAY_VERSION 0 -/* #define SSLEAY_OPTIONS 1 no longer supported */ -#define SSLEAY_CFLAGS 2 -#define SSLEAY_BUILT_ON 3 -#define SSLEAY_PLATFORM 4 -#define SSLEAY_DIR 5 - -/* Already declared in ossl_typ.h */ -#if 0 -typedef struct crypto_ex_data_st CRYPTO_EX_DATA; -/* Called when a new object is created */ -typedef int CRYPTO_EX_new(void *parent, void *ptr, CRYPTO_EX_DATA *ad, - int idx, long argl, void *argp); -/* Called when an object is free()ed */ -typedef void CRYPTO_EX_free(void *parent, void *ptr, CRYPTO_EX_DATA *ad, - int idx, long argl, void *argp); -/* Called when we need to dup an object */ -typedef int CRYPTO_EX_dup(CRYPTO_EX_DATA *to, CRYPTO_EX_DATA *from, void *from_d, - int idx, long argl, void *argp); -#endif - -/* A generic structure to pass assorted data in a expandable way */ -typedef struct openssl_item_st - { - int code; - void *value; /* Not used for flag attributes */ - size_t value_size; /* Max size of value for output, length for input */ - size_t *value_length; /* Returned length of value for output */ - } OPENSSL_ITEM; - - -/* When changing the CRYPTO_LOCK_* list, be sure to maintin the text lock - * names in cryptlib.c - */ - -#define CRYPTO_LOCK_ERR 1 -#define CRYPTO_LOCK_EX_DATA 2 -#define CRYPTO_LOCK_X509 3 -#define CRYPTO_LOCK_X509_INFO 4 -#define CRYPTO_LOCK_X509_PKEY 5 -#define CRYPTO_LOCK_X509_CRL 6 -#define CRYPTO_LOCK_X509_REQ 7 -#define CRYPTO_LOCK_DSA 8 -#define CRYPTO_LOCK_RSA 9 -#define CRYPTO_LOCK_EVP_PKEY 10 -#define CRYPTO_LOCK_X509_STORE 11 -#define CRYPTO_LOCK_SSL_CTX 12 -#define CRYPTO_LOCK_SSL_CERT 13 -#define CRYPTO_LOCK_SSL_SESSION 14 -#define CRYPTO_LOCK_SSL_SESS_CERT 15 -#define CRYPTO_LOCK_SSL 16 -#define CRYPTO_LOCK_SSL_METHOD 17 -#define CRYPTO_LOCK_RAND 18 -#define CRYPTO_LOCK_RAND2 19 -#define CRYPTO_LOCK_MALLOC 20 -#define CRYPTO_LOCK_BIO 21 -#define CRYPTO_LOCK_GETHOSTBYNAME 22 -#define CRYPTO_LOCK_GETSERVBYNAME 23 -#define CRYPTO_LOCK_READDIR 24 -#define CRYPTO_LOCK_RSA_BLINDING 25 -#define CRYPTO_LOCK_DH 26 -#define CRYPTO_LOCK_MALLOC2 27 -#define CRYPTO_LOCK_DSO 28 -#define CRYPTO_LOCK_DYNLOCK 29 -#define CRYPTO_LOCK_ENGINE 30 -#define CRYPTO_LOCK_UI 31 -#define CRYPTO_LOCK_ECDSA 32 -#define CRYPTO_LOCK_EC 33 -#define CRYPTO_LOCK_ECDH 34 -#define CRYPTO_LOCK_BN 35 -#define CRYPTO_LOCK_EC_PRE_COMP 36 -#define CRYPTO_LOCK_STORE 37 -#define CRYPTO_LOCK_COMP 38 -#define CRYPTO_LOCK_FIPS 39 -#define CRYPTO_LOCK_FIPS2 40 -#define CRYPTO_NUM_LOCKS 41 - -#define CRYPTO_LOCK 1 -#define CRYPTO_UNLOCK 2 -#define CRYPTO_READ 4 -#define CRYPTO_WRITE 8 - -#ifndef OPENSSL_NO_LOCKING -#ifndef CRYPTO_w_lock -#define CRYPTO_w_lock(type) \ - CRYPTO_lock(CRYPTO_LOCK|CRYPTO_WRITE,type,__FILE__,__LINE__) -#define CRYPTO_w_unlock(type) \ - CRYPTO_lock(CRYPTO_UNLOCK|CRYPTO_WRITE,type,__FILE__,__LINE__) -#define CRYPTO_r_lock(type) \ - CRYPTO_lock(CRYPTO_LOCK|CRYPTO_READ,type,__FILE__,__LINE__) -#define CRYPTO_r_unlock(type) \ - CRYPTO_lock(CRYPTO_UNLOCK|CRYPTO_READ,type,__FILE__,__LINE__) -#define CRYPTO_add(addr,amount,type) \ - CRYPTO_add_lock(addr,amount,type,__FILE__,__LINE__) -#endif -#else -#define CRYPTO_w_lock(a) -#define CRYPTO_w_unlock(a) -#define CRYPTO_r_lock(a) -#define CRYPTO_r_unlock(a) -#define CRYPTO_add(a,b,c) ((*(a))+=(b)) -#endif - -/* Some applications as well as some parts of OpenSSL need to allocate - and deallocate locks in a dynamic fashion. The following typedef - makes this possible in a type-safe manner. */ -/* struct CRYPTO_dynlock_value has to be defined by the application. */ -typedef struct - { - int references; - struct CRYPTO_dynlock_value *data; - } CRYPTO_dynlock; - - -/* The following can be used to detect memory leaks in the SSLeay library. - * It used, it turns on malloc checking */ - -#define CRYPTO_MEM_CHECK_OFF 0x0 /* an enume */ -#define CRYPTO_MEM_CHECK_ON 0x1 /* a bit */ -#define CRYPTO_MEM_CHECK_ENABLE 0x2 /* a bit */ -#define CRYPTO_MEM_CHECK_DISABLE 0x3 /* an enume */ - -/* The following are bit values to turn on or off options connected to the - * malloc checking functionality */ - -/* Adds time to the memory checking information */ -#define V_CRYPTO_MDEBUG_TIME 0x1 /* a bit */ -/* Adds thread number to the memory checking information */ -#define V_CRYPTO_MDEBUG_THREAD 0x2 /* a bit */ - -#define V_CRYPTO_MDEBUG_ALL (V_CRYPTO_MDEBUG_TIME | V_CRYPTO_MDEBUG_THREAD) - - -/* predec of the BIO type */ -typedef struct bio_st BIO_dummy; - -struct crypto_ex_data_st - { - STACK_OF(void) *sk; - int dummy; /* gcc is screwing up this data structure :-( */ - }; -DECLARE_STACK_OF(void) - -/* This stuff is basically class callback functions - * The current classes are SSL_CTX, SSL, SSL_SESSION, and a few more */ - -typedef struct crypto_ex_data_func_st - { - long argl; /* Arbitary long */ - void *argp; /* Arbitary void * */ - CRYPTO_EX_new *new_func; - CRYPTO_EX_free *free_func; - CRYPTO_EX_dup *dup_func; - } CRYPTO_EX_DATA_FUNCS; - -DECLARE_STACK_OF(CRYPTO_EX_DATA_FUNCS) - -/* Per class, we have a STACK of CRYPTO_EX_DATA_FUNCS for each CRYPTO_EX_DATA - * entry. - */ - -#define CRYPTO_EX_INDEX_BIO 0 -#define CRYPTO_EX_INDEX_SSL 1 -#define CRYPTO_EX_INDEX_SSL_CTX 2 -#define CRYPTO_EX_INDEX_SSL_SESSION 3 -#define CRYPTO_EX_INDEX_X509_STORE 4 -#define CRYPTO_EX_INDEX_X509_STORE_CTX 5 -#define CRYPTO_EX_INDEX_RSA 6 -#define CRYPTO_EX_INDEX_DSA 7 -#define CRYPTO_EX_INDEX_DH 8 -#define CRYPTO_EX_INDEX_ENGINE 9 -#define CRYPTO_EX_INDEX_X509 10 -#define CRYPTO_EX_INDEX_UI 11 -#define CRYPTO_EX_INDEX_ECDSA 12 -#define CRYPTO_EX_INDEX_ECDH 13 -#define CRYPTO_EX_INDEX_COMP 14 -#define CRYPTO_EX_INDEX_STORE 15 - -/* Dynamically assigned indexes start from this value (don't use directly, use - * via CRYPTO_ex_data_new_class). */ -#define CRYPTO_EX_INDEX_USER 100 - - -/* This is the default callbacks, but we can have others as well: - * this is needed in Win32 where the application malloc and the - * library malloc may not be the same. - */ -#define CRYPTO_malloc_init() CRYPTO_set_mem_functions(\ - malloc, realloc, free) - -#if defined CRYPTO_MDEBUG_ALL || defined CRYPTO_MDEBUG_TIME || defined CRYPTO_MDEBUG_THREAD -# ifndef CRYPTO_MDEBUG /* avoid duplicate #define */ -# define CRYPTO_MDEBUG -# endif -#endif - -/* Set standard debugging functions (not done by default - * unless CRYPTO_MDEBUG is defined) */ -#define CRYPTO_malloc_debug_init() do {\ - CRYPTO_set_mem_debug_functions(\ - CRYPTO_dbg_malloc,\ - CRYPTO_dbg_realloc,\ - CRYPTO_dbg_free,\ - CRYPTO_dbg_set_options,\ - CRYPTO_dbg_get_options);\ - } while(0) - -int CRYPTO_mem_ctrl(int mode); -int CRYPTO_is_mem_check_on(void); - -/* for applications */ -#define MemCheck_start() CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ON) -#define MemCheck_stop() CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_OFF) - -/* for library-internal use */ -#define MemCheck_on() CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ENABLE) -#define MemCheck_off() CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_DISABLE) -#define is_MemCheck_on() CRYPTO_is_mem_check_on() - -#define OPENSSL_malloc(num) CRYPTO_malloc((int)num,__FILE__,__LINE__) -#define OPENSSL_strdup(str) CRYPTO_strdup((str),__FILE__,__LINE__) -#define OPENSSL_realloc(addr,num) \ - CRYPTO_realloc((char *)addr,(int)num,__FILE__,__LINE__) -#define OPENSSL_realloc_clean(addr,old_num,num) \ - CRYPTO_realloc_clean(addr,old_num,num,__FILE__,__LINE__) -#define OPENSSL_remalloc(addr,num) \ - CRYPTO_remalloc((char **)addr,(int)num,__FILE__,__LINE__) -#define OPENSSL_freeFunc CRYPTO_free -#define OPENSSL_free(addr) CRYPTO_free(addr) - -#define OPENSSL_malloc_locked(num) \ - CRYPTO_malloc_locked((int)num,__FILE__,__LINE__) -#define OPENSSL_free_locked(addr) CRYPTO_free_locked(addr) - - -const char *SSLeay_version(int type); -unsigned long SSLeay(void); - -int OPENSSL_issetugid(void); - -/* An opaque type representing an implementation of "ex_data" support */ -typedef struct st_CRYPTO_EX_DATA_IMPL CRYPTO_EX_DATA_IMPL; -/* Return an opaque pointer to the current "ex_data" implementation */ -const CRYPTO_EX_DATA_IMPL *CRYPTO_get_ex_data_implementation(void); -/* Sets the "ex_data" implementation to be used (if it's not too late) */ -int CRYPTO_set_ex_data_implementation(const CRYPTO_EX_DATA_IMPL *i); -/* Get a new "ex_data" class, and return the corresponding "class_index" */ -int CRYPTO_ex_data_new_class(void); -/* Within a given class, get/register a new index */ -int CRYPTO_get_ex_new_index(int class_index, long argl, void *argp, - CRYPTO_EX_new *new_func, CRYPTO_EX_dup *dup_func, - CRYPTO_EX_free *free_func); -/* Initialise/duplicate/free CRYPTO_EX_DATA variables corresponding to a given - * class (invokes whatever per-class callbacks are applicable) */ -int CRYPTO_new_ex_data(int class_index, void *obj, CRYPTO_EX_DATA *ad); -int CRYPTO_dup_ex_data(int class_index, CRYPTO_EX_DATA *to, - CRYPTO_EX_DATA *from); -void CRYPTO_free_ex_data(int class_index, void *obj, CRYPTO_EX_DATA *ad); -/* Get/set data in a CRYPTO_EX_DATA variable corresponding to a particular index - * (relative to the class type involved) */ -int CRYPTO_set_ex_data(CRYPTO_EX_DATA *ad, int idx, void *val); -void *CRYPTO_get_ex_data(const CRYPTO_EX_DATA *ad,int idx); -/* This function cleans up all "ex_data" state. It mustn't be called under - * potential race-conditions. */ -void CRYPTO_cleanup_all_ex_data(void); - -int CRYPTO_get_new_lockid(char *name); - -int CRYPTO_num_locks(void); /* return CRYPTO_NUM_LOCKS (shared libs!) */ -void CRYPTO_lock(int mode, int type,const char *file,int line); -void CRYPTO_set_locking_callback(void (*func)(int mode,int type, - const char *file,int line)); -void (*CRYPTO_get_locking_callback(void))(int mode,int type,const char *file, - int line); -void CRYPTO_set_add_lock_callback(int (*func)(int *num,int mount,int type, - const char *file, int line)); -int (*CRYPTO_get_add_lock_callback(void))(int *num,int mount,int type, - const char *file,int line); - -/* Don't use this structure directly. */ -typedef struct crypto_threadid_st - { - void *ptr; - unsigned long val; - } CRYPTO_THREADID; -/* Only use CRYPTO_THREADID_set_[numeric|pointer]() within callbacks */ -void CRYPTO_THREADID_set_numeric(CRYPTO_THREADID *id, unsigned long val); -void CRYPTO_THREADID_set_pointer(CRYPTO_THREADID *id, void *ptr); -int CRYPTO_THREADID_set_callback(void (*threadid_func)(CRYPTO_THREADID *)); -void (*CRYPTO_THREADID_get_callback(void))(CRYPTO_THREADID *); -void CRYPTO_THREADID_current(CRYPTO_THREADID *id); -int CRYPTO_THREADID_cmp(const CRYPTO_THREADID *a, const CRYPTO_THREADID *b); -void CRYPTO_THREADID_cpy(CRYPTO_THREADID *dest, const CRYPTO_THREADID *src); -unsigned long CRYPTO_THREADID_hash(const CRYPTO_THREADID *id); -#ifndef OPENSSL_NO_DEPRECATED -void CRYPTO_set_id_callback(unsigned long (*func)(void)); -unsigned long (*CRYPTO_get_id_callback(void))(void); -unsigned long CRYPTO_thread_id(void); -#endif - -const char *CRYPTO_get_lock_name(int type); -int CRYPTO_add_lock(int *pointer,int amount,int type, const char *file, - int line); - -int CRYPTO_get_new_dynlockid(void); -void CRYPTO_destroy_dynlockid(int i); -struct CRYPTO_dynlock_value *CRYPTO_get_dynlock_value(int i); -void CRYPTO_set_dynlock_create_callback(struct CRYPTO_dynlock_value *(*dyn_create_function)(const char *file, int line)); -void CRYPTO_set_dynlock_lock_callback(void (*dyn_lock_function)(int mode, struct CRYPTO_dynlock_value *l, const char *file, int line)); -void CRYPTO_set_dynlock_destroy_callback(void (*dyn_destroy_function)(struct CRYPTO_dynlock_value *l, const char *file, int line)); -struct CRYPTO_dynlock_value *(*CRYPTO_get_dynlock_create_callback(void))(const char *file,int line); -void (*CRYPTO_get_dynlock_lock_callback(void))(int mode, struct CRYPTO_dynlock_value *l, const char *file,int line); -void (*CRYPTO_get_dynlock_destroy_callback(void))(struct CRYPTO_dynlock_value *l, const char *file,int line); - -/* CRYPTO_set_mem_functions includes CRYPTO_set_locked_mem_functions -- - * call the latter last if you need different functions */ -int CRYPTO_set_mem_functions(void *(*m)(size_t),void *(*r)(void *,size_t), void (*f)(void *)); -int CRYPTO_set_locked_mem_functions(void *(*m)(size_t), void (*free_func)(void *)); -int CRYPTO_set_mem_ex_functions(void *(*m)(size_t,const char *,int), - void *(*r)(void *,size_t,const char *,int), - void (*f)(void *)); -int CRYPTO_set_locked_mem_ex_functions(void *(*m)(size_t,const char *,int), - void (*free_func)(void *)); -int CRYPTO_set_mem_debug_functions(void (*m)(void *,int,const char *,int,int), - void (*r)(void *,void *,int,const char *,int,int), - void (*f)(void *,int), - void (*so)(long), - long (*go)(void)); -void CRYPTO_get_mem_functions(void *(**m)(size_t),void *(**r)(void *, size_t), void (**f)(void *)); -void CRYPTO_get_locked_mem_functions(void *(**m)(size_t), void (**f)(void *)); -void CRYPTO_get_mem_ex_functions(void *(**m)(size_t,const char *,int), - void *(**r)(void *, size_t,const char *,int), - void (**f)(void *)); -void CRYPTO_get_locked_mem_ex_functions(void *(**m)(size_t,const char *,int), - void (**f)(void *)); -void CRYPTO_get_mem_debug_functions(void (**m)(void *,int,const char *,int,int), - void (**r)(void *,void *,int,const char *,int,int), - void (**f)(void *,int), - void (**so)(long), - long (**go)(void)); - -void *CRYPTO_malloc_locked(int num, const char *file, int line); -void CRYPTO_free_locked(void *); -void *CRYPTO_malloc(int num, const char *file, int line); -char *CRYPTO_strdup(const char *str, const char *file, int line); -void CRYPTO_free(void *); -void *CRYPTO_realloc(void *addr,int num, const char *file, int line); -void *CRYPTO_realloc_clean(void *addr,int old_num,int num,const char *file, - int line); -void *CRYPTO_remalloc(void *addr,int num, const char *file, int line); - -void OPENSSL_cleanse(void *ptr, size_t len); - -void CRYPTO_set_mem_debug_options(long bits); -long CRYPTO_get_mem_debug_options(void); - -#define CRYPTO_push_info(info) \ - CRYPTO_push_info_(info, __FILE__, __LINE__); -int CRYPTO_push_info_(const char *info, const char *file, int line); -int CRYPTO_pop_info(void); -int CRYPTO_remove_all_info(void); - - -/* Default debugging functions (enabled by CRYPTO_malloc_debug_init() macro; - * used as default in CRYPTO_MDEBUG compilations): */ -/* The last argument has the following significance: - * - * 0: called before the actual memory allocation has taken place - * 1: called after the actual memory allocation has taken place - */ -void CRYPTO_dbg_malloc(void *addr,int num,const char *file,int line,int before_p); -void CRYPTO_dbg_realloc(void *addr1,void *addr2,int num,const char *file,int line,int before_p); -void CRYPTO_dbg_free(void *addr,int before_p); -/* Tell the debugging code about options. By default, the following values - * apply: - * - * 0: Clear all options. - * V_CRYPTO_MDEBUG_TIME (1): Set the "Show Time" option. - * V_CRYPTO_MDEBUG_THREAD (2): Set the "Show Thread Number" option. - * V_CRYPTO_MDEBUG_ALL (3): 1 + 2 - */ -void CRYPTO_dbg_set_options(long bits); -long CRYPTO_dbg_get_options(void); - - -#ifndef OPENSSL_NO_FP_API -void CRYPTO_mem_leaks_fp(FILE *); -#endif -void CRYPTO_mem_leaks(struct bio_st *bio); -/* unsigned long order, char *file, int line, int num_bytes, char *addr */ -typedef void *CRYPTO_MEM_LEAK_CB(unsigned long, const char *, int, int, void *); -void CRYPTO_mem_leaks_cb(CRYPTO_MEM_LEAK_CB *cb); - -/* die if we have to */ -void OpenSSLDie(const char *file,int line,const char *assertion); -#define OPENSSL_assert(e) (void)((e) ? 0 : (OpenSSLDie(__FILE__, __LINE__, #e),1)) - -unsigned long *OPENSSL_ia32cap_loc(void); -#define OPENSSL_ia32cap (*(OPENSSL_ia32cap_loc())) -int OPENSSL_isservice(void); - -int FIPS_mode(void); -int FIPS_mode_set(int r); - -void OPENSSL_init(void); - -#define fips_md_init(alg) fips_md_init_ctx(alg, alg) - -#ifdef OPENSSL_FIPS -#define fips_md_init_ctx(alg, cx) \ - int alg##_Init(cx##_CTX *c) \ - { \ - if (FIPS_mode()) OpenSSLDie(__FILE__, __LINE__, \ - "Low level API call to digest " #alg " forbidden in FIPS mode!"); \ - return private_##alg##_Init(c); \ - } \ - int private_##alg##_Init(cx##_CTX *c) - -#define fips_cipher_abort(alg) \ - if (FIPS_mode()) OpenSSLDie(__FILE__, __LINE__, \ - "Low level API call to cipher " #alg " forbidden in FIPS mode!") - -#else -#define fips_md_init_ctx(alg, cx) \ - int alg##_Init(cx##_CTX *c) -#define fips_cipher_abort(alg) while(0) -#endif - -/* BEGIN ERROR CODES */ -/* The following lines are auto generated by the script mkerr.pl. Any changes - * made after this point may be overwritten when the script is next run. - */ -void ERR_load_CRYPTO_strings(void); - -/* Error codes for the CRYPTO functions. */ - -/* Function codes. */ -#define CRYPTO_F_CRYPTO_GET_EX_NEW_INDEX 100 -#define CRYPTO_F_CRYPTO_GET_NEW_DYNLOCKID 103 -#define CRYPTO_F_CRYPTO_GET_NEW_LOCKID 101 -#define CRYPTO_F_CRYPTO_SET_EX_DATA 102 -#define CRYPTO_F_DEF_ADD_INDEX 104 -#define CRYPTO_F_DEF_GET_CLASS 105 -#define CRYPTO_F_FIPS_MODE_SET 109 -#define CRYPTO_F_INT_DUP_EX_DATA 106 -#define CRYPTO_F_INT_FREE_EX_DATA 107 -#define CRYPTO_F_INT_NEW_EX_DATA 108 - -/* Reason codes. */ -#define CRYPTO_R_FIPS_MODE_NOT_SUPPORTED 101 -#define CRYPTO_R_NO_DYNLOCK_CREATE_CALLBACK 100 - -#ifdef __cplusplus -} -#endif -#endif diff --git a/src/plugins/ftp/openssl/dh.h b/src/plugins/ftp/openssl/dh.h deleted file mode 100644 index ea59e610..00000000 --- a/src/plugins/ftp/openssl/dh.h +++ /dev/null @@ -1,280 +0,0 @@ -/* crypto/dh/dh.h */ -/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) - * All rights reserved. - * - * This package is an SSL implementation written - * by Eric Young (eay@cryptsoft.com). - * The implementation was written so as to conform with Netscapes SSL. - * - * This library is free for commercial and non-commercial use as long as - * the following conditions are aheared to. The following conditions - * apply to all code found in this distribution, be it the RC4, RSA, - * lhash, DES, etc., code; not just the SSL code. The SSL documentation - * included with this distribution is covered by the same copyright terms - * except that the holder is Tim Hudson (tjh@cryptsoft.com). - * - * Copyright remains Eric Young's, and as such any Copyright notices in - * the code are not to be removed. - * If this package is used in a product, Eric Young should be given attribution - * as the author of the parts of the library used. - * This can be in the form of a textual message at program startup or - * in documentation (online or textual) provided with the package. - * - * 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 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. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * "This product includes cryptographic software written by - * Eric Young (eay@cryptsoft.com)" - * The word 'cryptographic' can be left out if the rouines from the library - * being used are not cryptographic related :-). - * 4. If you include any Windows specific code (or a derivative thereof) from - * the apps directory (application code) you must include an acknowledgement: - * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" - * - * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``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 AUTHOR 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. - * - * The licence and distribution terms for any publically available version or - * derivative of this code cannot be changed. i.e. this code cannot simply be - * copied and put under another distribution licence - * [including the GNU Public Licence.] - */ - -#ifndef HEADER_DH_H -#define HEADER_DH_H - -#include - -#ifdef OPENSSL_NO_DH -#error DH is disabled. -#endif - -#ifndef OPENSSL_NO_BIO -#include -#endif -#include -#ifndef OPENSSL_NO_DEPRECATED -#include -#endif - -#ifndef OPENSSL_DH_MAX_MODULUS_BITS -# define OPENSSL_DH_MAX_MODULUS_BITS 10000 -#endif - -#define DH_FLAG_CACHE_MONT_P 0x01 -#define DH_FLAG_NO_EXP_CONSTTIME 0x02 /* new with 0.9.7h; the built-in DH - * implementation now uses constant time - * modular exponentiation for secret exponents - * by default. This flag causes the - * faster variable sliding window method to - * be used for all exponents. - */ - -/* If this flag is set the DH method is FIPS compliant and can be used - * in FIPS mode. This is set in the validated module method. If an - * application sets this flag in its own methods it is its reposibility - * to ensure the result is compliant. - */ - -#define DH_FLAG_FIPS_METHOD 0x0400 - -/* If this flag is set the operations normally disabled in FIPS mode are - * permitted it is then the applications responsibility to ensure that the - * usage is compliant. - */ - -#define DH_FLAG_NON_FIPS_ALLOW 0x0400 - -#ifdef __cplusplus -extern "C" { -#endif - -/* Already defined in ossl_typ.h */ -/* typedef struct dh_st DH; */ -/* typedef struct dh_method DH_METHOD; */ - -struct dh_method - { - const char *name; - /* Methods here */ - int (*generate_key)(DH *dh); - int (*compute_key)(unsigned char *key,const BIGNUM *pub_key,DH *dh); - int (*bn_mod_exp)(const DH *dh, BIGNUM *r, const BIGNUM *a, - const BIGNUM *p, const BIGNUM *m, BN_CTX *ctx, - BN_MONT_CTX *m_ctx); /* Can be null */ - - int (*init)(DH *dh); - int (*finish)(DH *dh); - int flags; - char *app_data; - /* If this is non-NULL, it will be used to generate parameters */ - int (*generate_params)(DH *dh, int prime_len, int generator, BN_GENCB *cb); - }; - -struct dh_st - { - /* This first argument is used to pick up errors when - * a DH is passed instead of a EVP_PKEY */ - int pad; - int version; - BIGNUM *p; - BIGNUM *g; - long length; /* optional */ - BIGNUM *pub_key; /* g^x */ - BIGNUM *priv_key; /* x */ - - int flags; - BN_MONT_CTX *method_mont_p; - /* Place holders if we want to do X9.42 DH */ - BIGNUM *q; - BIGNUM *j; - unsigned char *seed; - int seedlen; - BIGNUM *counter; - - int references; - CRYPTO_EX_DATA ex_data; - const DH_METHOD *meth; - ENGINE *engine; - }; - -#define DH_GENERATOR_2 2 -/* #define DH_GENERATOR_3 3 */ -#define DH_GENERATOR_5 5 - -/* DH_check error codes */ -#define DH_CHECK_P_NOT_PRIME 0x01 -#define DH_CHECK_P_NOT_SAFE_PRIME 0x02 -#define DH_UNABLE_TO_CHECK_GENERATOR 0x04 -#define DH_NOT_SUITABLE_GENERATOR 0x08 - -/* DH_check_pub_key error codes */ -#define DH_CHECK_PUBKEY_TOO_SMALL 0x01 -#define DH_CHECK_PUBKEY_TOO_LARGE 0x02 - -/* primes p where (p-1)/2 is prime too are called "safe"; we define - this for backward compatibility: */ -#define DH_CHECK_P_NOT_STRONG_PRIME DH_CHECK_P_NOT_SAFE_PRIME - -#define d2i_DHparams_fp(fp,x) (DH *)ASN1_d2i_fp((char *(*)())DH_new, \ - (char *(*)())d2i_DHparams,(fp),(unsigned char **)(x)) -#define i2d_DHparams_fp(fp,x) ASN1_i2d_fp(i2d_DHparams,(fp), \ - (unsigned char *)(x)) -#define d2i_DHparams_bio(bp,x) ASN1_d2i_bio_of(DH,DH_new,d2i_DHparams,bp,x) -#define i2d_DHparams_bio(bp,x) ASN1_i2d_bio_of_const(DH,i2d_DHparams,bp,x) - -DH *DHparams_dup(DH *); - -const DH_METHOD *DH_OpenSSL(void); - -void DH_set_default_method(const DH_METHOD *meth); -const DH_METHOD *DH_get_default_method(void); -int DH_set_method(DH *dh, const DH_METHOD *meth); -DH *DH_new_method(ENGINE *engine); - -DH * DH_new(void); -void DH_free(DH *dh); -int DH_up_ref(DH *dh); -int DH_size(const DH *dh); -int DH_get_ex_new_index(long argl, void *argp, CRYPTO_EX_new *new_func, - CRYPTO_EX_dup *dup_func, CRYPTO_EX_free *free_func); -int DH_set_ex_data(DH *d, int idx, void *arg); -void *DH_get_ex_data(DH *d, int idx); - -/* Deprecated version */ -#ifndef OPENSSL_NO_DEPRECATED -DH * DH_generate_parameters(int prime_len,int generator, - void (*callback)(int,int,void *),void *cb_arg); -#endif /* !defined(OPENSSL_NO_DEPRECATED) */ - -/* New version */ -int DH_generate_parameters_ex(DH *dh, int prime_len,int generator, BN_GENCB *cb); - -int DH_check(const DH *dh,int *codes); -int DH_check_pub_key(const DH *dh,const BIGNUM *pub_key, int *codes); -int DH_generate_key(DH *dh); -int DH_compute_key(unsigned char *key,const BIGNUM *pub_key,DH *dh); -DH * d2i_DHparams(DH **a,const unsigned char **pp, long length); -int i2d_DHparams(const DH *a,unsigned char **pp); -#ifndef OPENSSL_NO_FP_API -int DHparams_print_fp(FILE *fp, const DH *x); -#endif -#ifndef OPENSSL_NO_BIO -int DHparams_print(BIO *bp, const DH *x); -#else -int DHparams_print(char *bp, const DH *x); -#endif - -#define EVP_PKEY_CTX_set_dh_paramgen_prime_len(ctx, len) \ - EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_DH, EVP_PKEY_OP_PARAMGEN, \ - EVP_PKEY_CTRL_DH_PARAMGEN_PRIME_LEN, len, NULL) - -#define EVP_PKEY_CTX_set_dh_paramgen_generator(ctx, gen) \ - EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_DH, EVP_PKEY_OP_PARAMGEN, \ - EVP_PKEY_CTRL_DH_PARAMGEN_GENERATOR, gen, NULL) - -#define EVP_PKEY_CTRL_DH_PARAMGEN_PRIME_LEN (EVP_PKEY_ALG_CTRL + 1) -#define EVP_PKEY_CTRL_DH_PARAMGEN_GENERATOR (EVP_PKEY_ALG_CTRL + 2) - - -/* BEGIN ERROR CODES */ -/* The following lines are auto generated by the script mkerr.pl. Any changes - * made after this point may be overwritten when the script is next run. - */ -void ERR_load_DH_strings(void); - -/* Error codes for the DH functions. */ - -/* Function codes. */ -#define DH_F_COMPUTE_KEY 102 -#define DH_F_DHPARAMS_PRINT_FP 101 -#define DH_F_DH_BUILTIN_GENPARAMS 106 -#define DH_F_DH_COMPUTE_KEY 114 -#define DH_F_DH_GENERATE_KEY 115 -#define DH_F_DH_GENERATE_PARAMETERS_EX 116 -#define DH_F_DH_NEW_METHOD 105 -#define DH_F_DH_PARAM_DECODE 107 -#define DH_F_DH_PRIV_DECODE 110 -#define DH_F_DH_PRIV_ENCODE 111 -#define DH_F_DH_PUB_DECODE 108 -#define DH_F_DH_PUB_ENCODE 109 -#define DH_F_DO_DH_PRINT 100 -#define DH_F_GENERATE_KEY 103 -#define DH_F_GENERATE_PARAMETERS 104 -#define DH_F_PKEY_DH_DERIVE 112 -#define DH_F_PKEY_DH_KEYGEN 113 - -/* Reason codes. */ -#define DH_R_BAD_GENERATOR 101 -#define DH_R_BN_DECODE_ERROR 109 -#define DH_R_BN_ERROR 106 -#define DH_R_DECODE_ERROR 104 -#define DH_R_INVALID_PUBKEY 102 -#define DH_R_KEYS_NOT_SET 108 -#define DH_R_KEY_SIZE_TOO_SMALL 110 -#define DH_R_MODULUS_TOO_LARGE 103 -#define DH_R_NON_FIPS_METHOD 111 -#define DH_R_NO_PARAMETERS_SET 107 -#define DH_R_NO_PRIVATE_VALUE 100 -#define DH_R_PARAMETER_ENCODING_ERROR 105 - -#ifdef __cplusplus -} -#endif -#endif diff --git a/src/plugins/ftp/openssl/dsa.h b/src/plugins/ftp/openssl/dsa.h deleted file mode 100644 index a6f6d0b0..00000000 --- a/src/plugins/ftp/openssl/dsa.h +++ /dev/null @@ -1,327 +0,0 @@ -/* crypto/dsa/dsa.h */ -/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) - * All rights reserved. - * - * This package is an SSL implementation written - * by Eric Young (eay@cryptsoft.com). - * The implementation was written so as to conform with Netscapes SSL. - * - * This library is free for commercial and non-commercial use as long as - * the following conditions are aheared to. The following conditions - * apply to all code found in this distribution, be it the RC4, RSA, - * lhash, DES, etc., code; not just the SSL code. The SSL documentation - * included with this distribution is covered by the same copyright terms - * except that the holder is Tim Hudson (tjh@cryptsoft.com). - * - * Copyright remains Eric Young's, and as such any Copyright notices in - * the code are not to be removed. - * If this package is used in a product, Eric Young should be given attribution - * as the author of the parts of the library used. - * This can be in the form of a textual message at program startup or - * in documentation (online or textual) provided with the package. - * - * 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 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. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * "This product includes cryptographic software written by - * Eric Young (eay@cryptsoft.com)" - * The word 'cryptographic' can be left out if the rouines from the library - * being used are not cryptographic related :-). - * 4. If you include any Windows specific code (or a derivative thereof) from - * the apps directory (application code) you must include an acknowledgement: - * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" - * - * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``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 AUTHOR 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. - * - * The licence and distribution terms for any publically available version or - * derivative of this code cannot be changed. i.e. this code cannot simply be - * copied and put under another distribution licence - * [including the GNU Public Licence.] - */ - -/* - * The DSS routines are based on patches supplied by - * Steven Schoch . He basically did the - * work and I have just tweaked them a little to fit into my - * stylistic vision for SSLeay :-) */ - -#ifndef HEADER_DSA_H -#define HEADER_DSA_H - -#include - -#ifdef OPENSSL_NO_DSA -#error DSA is disabled. -#endif - -#ifndef OPENSSL_NO_BIO -#include -#endif -#include -#include - -#ifndef OPENSSL_NO_DEPRECATED -#include -#ifndef OPENSSL_NO_DH -# include -#endif -#endif - -#ifndef OPENSSL_DSA_MAX_MODULUS_BITS -# define OPENSSL_DSA_MAX_MODULUS_BITS 10000 -#endif - -#define DSA_FLAG_CACHE_MONT_P 0x01 -#define DSA_FLAG_NO_EXP_CONSTTIME 0x02 /* new with 0.9.7h; the built-in DSA - * implementation now uses constant time - * modular exponentiation for secret exponents - * by default. This flag causes the - * faster variable sliding window method to - * be used for all exponents. - */ - -/* If this flag is set the DSA method is FIPS compliant and can be used - * in FIPS mode. This is set in the validated module method. If an - * application sets this flag in its own methods it is its reposibility - * to ensure the result is compliant. - */ - -#define DSA_FLAG_FIPS_METHOD 0x0400 - -/* If this flag is set the operations normally disabled in FIPS mode are - * permitted it is then the applications responsibility to ensure that the - * usage is compliant. - */ - -#define DSA_FLAG_NON_FIPS_ALLOW 0x0400 - -#ifdef __cplusplus -extern "C" { -#endif - -/* Already defined in ossl_typ.h */ -/* typedef struct dsa_st DSA; */ -/* typedef struct dsa_method DSA_METHOD; */ - -typedef struct DSA_SIG_st - { - BIGNUM *r; - BIGNUM *s; - } DSA_SIG; - -struct dsa_method - { - const char *name; - DSA_SIG * (*dsa_do_sign)(const unsigned char *dgst, int dlen, DSA *dsa); - int (*dsa_sign_setup)(DSA *dsa, BN_CTX *ctx_in, BIGNUM **kinvp, - BIGNUM **rp); - int (*dsa_do_verify)(const unsigned char *dgst, int dgst_len, - DSA_SIG *sig, DSA *dsa); - int (*dsa_mod_exp)(DSA *dsa, BIGNUM *rr, BIGNUM *a1, BIGNUM *p1, - BIGNUM *a2, BIGNUM *p2, BIGNUM *m, BN_CTX *ctx, - BN_MONT_CTX *in_mont); - int (*bn_mod_exp)(DSA *dsa, BIGNUM *r, BIGNUM *a, const BIGNUM *p, - const BIGNUM *m, BN_CTX *ctx, - BN_MONT_CTX *m_ctx); /* Can be null */ - int (*init)(DSA *dsa); - int (*finish)(DSA *dsa); - int flags; - char *app_data; - /* If this is non-NULL, it is used to generate DSA parameters */ - int (*dsa_paramgen)(DSA *dsa, int bits, - const unsigned char *seed, int seed_len, - int *counter_ret, unsigned long *h_ret, - BN_GENCB *cb); - /* If this is non-NULL, it is used to generate DSA keys */ - int (*dsa_keygen)(DSA *dsa); - }; - -struct dsa_st - { - /* This first variable is used to pick up errors where - * a DSA is passed instead of of a EVP_PKEY */ - int pad; - long version; - int write_params; - BIGNUM *p; - BIGNUM *q; /* == 20 */ - BIGNUM *g; - - BIGNUM *pub_key; /* y public key */ - BIGNUM *priv_key; /* x private key */ - - BIGNUM *kinv; /* Signing pre-calc */ - BIGNUM *r; /* Signing pre-calc */ - - int flags; - /* Normally used to cache montgomery values */ - BN_MONT_CTX *method_mont_p; - int references; - CRYPTO_EX_DATA ex_data; - const DSA_METHOD *meth; - /* functional reference if 'meth' is ENGINE-provided */ - ENGINE *engine; - }; - -#define d2i_DSAparams_fp(fp,x) (DSA *)ASN1_d2i_fp((char *(*)())DSA_new, \ - (char *(*)())d2i_DSAparams,(fp),(unsigned char **)(x)) -#define i2d_DSAparams_fp(fp,x) ASN1_i2d_fp(i2d_DSAparams,(fp), \ - (unsigned char *)(x)) -#define d2i_DSAparams_bio(bp,x) ASN1_d2i_bio_of(DSA,DSA_new,d2i_DSAparams,bp,x) -#define i2d_DSAparams_bio(bp,x) ASN1_i2d_bio_of_const(DSA,i2d_DSAparams,bp,x) - - -DSA *DSAparams_dup(DSA *x); -DSA_SIG * DSA_SIG_new(void); -void DSA_SIG_free(DSA_SIG *a); -int i2d_DSA_SIG(const DSA_SIG *a, unsigned char **pp); -DSA_SIG * d2i_DSA_SIG(DSA_SIG **v, const unsigned char **pp, long length); - -DSA_SIG * DSA_do_sign(const unsigned char *dgst,int dlen,DSA *dsa); -int DSA_do_verify(const unsigned char *dgst,int dgst_len, - DSA_SIG *sig,DSA *dsa); - -const DSA_METHOD *DSA_OpenSSL(void); - -void DSA_set_default_method(const DSA_METHOD *); -const DSA_METHOD *DSA_get_default_method(void); -int DSA_set_method(DSA *dsa, const DSA_METHOD *); - -DSA * DSA_new(void); -DSA * DSA_new_method(ENGINE *engine); -void DSA_free (DSA *r); -/* "up" the DSA object's reference count */ -int DSA_up_ref(DSA *r); -int DSA_size(const DSA *); - /* next 4 return -1 on error */ -int DSA_sign_setup( DSA *dsa,BN_CTX *ctx_in,BIGNUM **kinvp,BIGNUM **rp); -int DSA_sign(int type,const unsigned char *dgst,int dlen, - unsigned char *sig, unsigned int *siglen, DSA *dsa); -int DSA_verify(int type,const unsigned char *dgst,int dgst_len, - const unsigned char *sigbuf, int siglen, DSA *dsa); -int DSA_get_ex_new_index(long argl, void *argp, CRYPTO_EX_new *new_func, - CRYPTO_EX_dup *dup_func, CRYPTO_EX_free *free_func); -int DSA_set_ex_data(DSA *d, int idx, void *arg); -void *DSA_get_ex_data(DSA *d, int idx); - -DSA * d2i_DSAPublicKey(DSA **a, const unsigned char **pp, long length); -DSA * d2i_DSAPrivateKey(DSA **a, const unsigned char **pp, long length); -DSA * d2i_DSAparams(DSA **a, const unsigned char **pp, long length); - -/* Deprecated version */ -#ifndef OPENSSL_NO_DEPRECATED -DSA * DSA_generate_parameters(int bits, - unsigned char *seed,int seed_len, - int *counter_ret, unsigned long *h_ret,void - (*callback)(int, int, void *),void *cb_arg); -#endif /* !defined(OPENSSL_NO_DEPRECATED) */ - -/* New version */ -int DSA_generate_parameters_ex(DSA *dsa, int bits, - const unsigned char *seed,int seed_len, - int *counter_ret, unsigned long *h_ret, BN_GENCB *cb); - -int DSA_generate_key(DSA *a); -int i2d_DSAPublicKey(const DSA *a, unsigned char **pp); -int i2d_DSAPrivateKey(const DSA *a, unsigned char **pp); -int i2d_DSAparams(const DSA *a,unsigned char **pp); - -#ifndef OPENSSL_NO_BIO -int DSAparams_print(BIO *bp, const DSA *x); -int DSA_print(BIO *bp, const DSA *x, int off); -#endif -#ifndef OPENSSL_NO_FP_API -int DSAparams_print_fp(FILE *fp, const DSA *x); -int DSA_print_fp(FILE *bp, const DSA *x, int off); -#endif - -#define DSS_prime_checks 50 -/* Primality test according to FIPS PUB 186[-1], Appendix 2.1: - * 50 rounds of Rabin-Miller */ -#define DSA_is_prime(n, callback, cb_arg) \ - BN_is_prime(n, DSS_prime_checks, callback, NULL, cb_arg) - -#ifndef OPENSSL_NO_DH -/* Convert DSA structure (key or just parameters) into DH structure - * (be careful to avoid small subgroup attacks when using this!) */ -DH *DSA_dup_DH(const DSA *r); -#endif - -#define EVP_PKEY_CTX_set_dsa_paramgen_bits(ctx, nbits) \ - EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_DSA, EVP_PKEY_OP_PARAMGEN, \ - EVP_PKEY_CTRL_DSA_PARAMGEN_BITS, nbits, NULL) - -#define EVP_PKEY_CTRL_DSA_PARAMGEN_BITS (EVP_PKEY_ALG_CTRL + 1) -#define EVP_PKEY_CTRL_DSA_PARAMGEN_Q_BITS (EVP_PKEY_ALG_CTRL + 2) -#define EVP_PKEY_CTRL_DSA_PARAMGEN_MD (EVP_PKEY_ALG_CTRL + 3) - -/* BEGIN ERROR CODES */ -/* The following lines are auto generated by the script mkerr.pl. Any changes - * made after this point may be overwritten when the script is next run. - */ -void ERR_load_DSA_strings(void); - -/* Error codes for the DSA functions. */ - -/* Function codes. */ -#define DSA_F_D2I_DSA_SIG 110 -#define DSA_F_DO_DSA_PRINT 104 -#define DSA_F_DSAPARAMS_PRINT 100 -#define DSA_F_DSAPARAMS_PRINT_FP 101 -#define DSA_F_DSA_DO_SIGN 112 -#define DSA_F_DSA_DO_VERIFY 113 -#define DSA_F_DSA_GENERATE_KEY 124 -#define DSA_F_DSA_GENERATE_PARAMETERS_EX 123 -#define DSA_F_DSA_NEW_METHOD 103 -#define DSA_F_DSA_PARAM_DECODE 119 -#define DSA_F_DSA_PRINT_FP 105 -#define DSA_F_DSA_PRIV_DECODE 115 -#define DSA_F_DSA_PRIV_ENCODE 116 -#define DSA_F_DSA_PUB_DECODE 117 -#define DSA_F_DSA_PUB_ENCODE 118 -#define DSA_F_DSA_SIGN 106 -#define DSA_F_DSA_SIGN_SETUP 107 -#define DSA_F_DSA_SIG_NEW 109 -#define DSA_F_DSA_SIG_PRINT 125 -#define DSA_F_DSA_VERIFY 108 -#define DSA_F_I2D_DSA_SIG 111 -#define DSA_F_OLD_DSA_PRIV_DECODE 122 -#define DSA_F_PKEY_DSA_CTRL 120 -#define DSA_F_PKEY_DSA_KEYGEN 121 -#define DSA_F_SIG_CB 114 - -/* Reason codes. */ -#define DSA_R_BAD_Q_VALUE 102 -#define DSA_R_BN_DECODE_ERROR 108 -#define DSA_R_BN_ERROR 109 -#define DSA_R_DATA_TOO_LARGE_FOR_KEY_SIZE 100 -#define DSA_R_DECODE_ERROR 104 -#define DSA_R_INVALID_DIGEST_TYPE 106 -#define DSA_R_MISSING_PARAMETERS 101 -#define DSA_R_MODULUS_TOO_LARGE 103 -#define DSA_R_NEED_NEW_SETUP_VALUES 110 -#define DSA_R_NON_FIPS_DSA_METHOD 111 -#define DSA_R_NO_PARAMETERS_SET 107 -#define DSA_R_PARAMETER_ENCODING_ERROR 105 - -#ifdef __cplusplus -} -#endif -#endif diff --git a/src/plugins/ftp/openssl/e_os2.h b/src/plugins/ftp/openssl/e_os2.h deleted file mode 100644 index d22c0368..00000000 --- a/src/plugins/ftp/openssl/e_os2.h +++ /dev/null @@ -1,315 +0,0 @@ -/* e_os2.h */ -/* ==================================================================== - * Copyright (c) 1998-2000 The OpenSSL Project. All rights reserved. - * - * 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. All advertising materials mentioning features or use of this - * software must display the following acknowledgment: - * "This product includes software developed by the OpenSSL Project - * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" - * - * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For written permission, please contact - * openssl-core@openssl.org. - * - * 5. Products derived from this software may not be called "OpenSSL" - * nor may "OpenSSL" appear in their names without prior written - * permission of the OpenSSL Project. - * - * 6. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by the OpenSSL Project - * for use in the OpenSSL Toolkit (http://www.openssl.org/)" - * - * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY - * EXPRESSED 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 OpenSSL PROJECT OR - * ITS 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. - * ==================================================================== - * - * This product includes cryptographic software written by Eric Young - * (eay@cryptsoft.com). This product includes software written by Tim - * Hudson (tjh@cryptsoft.com). - * - */ - -#include - -#ifndef HEADER_E_OS2_H -#define HEADER_E_OS2_H - -#ifdef __cplusplus -extern "C" { -#endif - -/****************************************************************************** - * Detect operating systems. This probably needs completing. - * The result is that at least one OPENSSL_SYS_os macro should be defined. - * However, if none is defined, Unix is assumed. - **/ - -#define OPENSSL_SYS_UNIX - -/* ----------------------- Macintosh, before MacOS X ----------------------- */ -#if defined(__MWERKS__) && defined(macintosh) || defined(OPENSSL_SYSNAME_MAC) -# undef OPENSSL_SYS_UNIX -# define OPENSSL_SYS_MACINTOSH_CLASSIC -#endif - -/* ----------------------- NetWare ----------------------------------------- */ -#if defined(NETWARE) || defined(OPENSSL_SYSNAME_NETWARE) -# undef OPENSSL_SYS_UNIX -# define OPENSSL_SYS_NETWARE -#endif - -/* ---------------------- Microsoft operating systems ---------------------- */ - -/* Note that MSDOS actually denotes 32-bit environments running on top of - MS-DOS, such as DJGPP one. */ -#if defined(OPENSSL_SYSNAME_MSDOS) -# undef OPENSSL_SYS_UNIX -# define OPENSSL_SYS_MSDOS -#endif - -/* For 32 bit environment, there seems to be the CygWin environment and then - all the others that try to do the same thing Microsoft does... */ -#if defined(OPENSSL_SYSNAME_UWIN) -# undef OPENSSL_SYS_UNIX -# define OPENSSL_SYS_WIN32_UWIN -#else -# if defined(__CYGWIN32__) || defined(OPENSSL_SYSNAME_CYGWIN32) -# undef OPENSSL_SYS_UNIX -# define OPENSSL_SYS_WIN32_CYGWIN -# else -# if defined(_WIN32) || defined(OPENSSL_SYSNAME_WIN32) -# undef OPENSSL_SYS_UNIX -# define OPENSSL_SYS_WIN32 -# endif -# if defined(OPENSSL_SYSNAME_WINNT) -# undef OPENSSL_SYS_UNIX -# define OPENSSL_SYS_WINNT -# endif -# if defined(OPENSSL_SYSNAME_WINCE) -# undef OPENSSL_SYS_UNIX -# define OPENSSL_SYS_WINCE -# endif -# endif -#endif - -/* Anything that tries to look like Microsoft is "Windows" */ -#if defined(OPENSSL_SYS_WIN32) || defined(OPENSSL_SYS_WINNT) || defined(OPENSSL_SYS_WINCE) -# undef OPENSSL_SYS_UNIX -# define OPENSSL_SYS_WINDOWS -# ifndef OPENSSL_SYS_MSDOS -# define OPENSSL_SYS_MSDOS -# endif -#endif - -/* DLL settings. This part is a bit tough, because it's up to the application - implementor how he or she will link the application, so it requires some - macro to be used. */ -#ifdef OPENSSL_SYS_WINDOWS -# ifndef OPENSSL_OPT_WINDLL -# if defined(_WINDLL) /* This is used when building OpenSSL to indicate that - DLL linkage should be used */ -# define OPENSSL_OPT_WINDLL -# endif -# endif -#endif - -/* -------------------------------- OpenVMS -------------------------------- */ -#if defined(__VMS) || defined(VMS) || defined(OPENSSL_SYSNAME_VMS) -# undef OPENSSL_SYS_UNIX -# define OPENSSL_SYS_VMS -# if defined(__DECC) -# define OPENSSL_SYS_VMS_DECC -# elif defined(__DECCXX) -# define OPENSSL_SYS_VMS_DECC -# define OPENSSL_SYS_VMS_DECCXX -# else -# define OPENSSL_SYS_VMS_NODECC -# endif -#endif - -/* --------------------------------- OS/2 ---------------------------------- */ -#if defined(__EMX__) || defined(__OS2__) -# undef OPENSSL_SYS_UNIX -# define OPENSSL_SYS_OS2 -#endif - -/* --------------------------------- Unix ---------------------------------- */ -#ifdef OPENSSL_SYS_UNIX -# if defined(linux) || defined(__linux__) || defined(OPENSSL_SYSNAME_LINUX) -# define OPENSSL_SYS_LINUX -# endif -# ifdef OPENSSL_SYSNAME_MPE -# define OPENSSL_SYS_MPE -# endif -# ifdef OPENSSL_SYSNAME_SNI -# define OPENSSL_SYS_SNI -# endif -# ifdef OPENSSL_SYSNAME_ULTRASPARC -# define OPENSSL_SYS_ULTRASPARC -# endif -# ifdef OPENSSL_SYSNAME_NEWS4 -# define OPENSSL_SYS_NEWS4 -# endif -# ifdef OPENSSL_SYSNAME_MACOSX -# define OPENSSL_SYS_MACOSX -# endif -# ifdef OPENSSL_SYSNAME_MACOSX_RHAPSODY -# define OPENSSL_SYS_MACOSX_RHAPSODY -# define OPENSSL_SYS_MACOSX -# endif -# ifdef OPENSSL_SYSNAME_SUNOS -# define OPENSSL_SYS_SUNOS -#endif -# if defined(_CRAY) || defined(OPENSSL_SYSNAME_CRAY) -# define OPENSSL_SYS_CRAY -# endif -# if defined(_AIX) || defined(OPENSSL_SYSNAME_AIX) -# define OPENSSL_SYS_AIX -# endif -#endif - -/* --------------------------------- VOS ----------------------------------- */ -#if defined(__VOS__) || defined(OPENSSL_SYSNAME_VOS) -# define OPENSSL_SYS_VOS -#ifdef __HPPA__ -# define OPENSSL_SYS_VOS_HPPA -#endif -#ifdef __IA32__ -# define OPENSSL_SYS_VOS_IA32 -#endif -#endif - -/* ------------------------------- VxWorks --------------------------------- */ -#ifdef OPENSSL_SYSNAME_VXWORKS -# define OPENSSL_SYS_VXWORKS -#endif - -/* --------------------------------- BeOS ---------------------------------- */ -#if defined(__BEOS__) -# define OPENSSL_SYS_BEOS -# include -# if defined(BONE_VERSION) -# define OPENSSL_SYS_BEOS_BONE -# else -# define OPENSSL_SYS_BEOS_R5 -# endif -#endif - -/** - * That's it for OS-specific stuff - *****************************************************************************/ - - -/* Specials for I/O an exit */ -#ifdef OPENSSL_SYS_MSDOS -# define OPENSSL_UNISTD_IO -# define OPENSSL_DECLARE_EXIT extern void exit(int); -#else -# define OPENSSL_UNISTD_IO OPENSSL_UNISTD -# define OPENSSL_DECLARE_EXIT /* declared in unistd.h */ -#endif - -/* Definitions of OPENSSL_GLOBAL and OPENSSL_EXTERN, to define and declare - certain global symbols that, with some compilers under VMS, have to be - defined and declared explicitely with globaldef and globalref. - Definitions of OPENSSL_EXPORT and OPENSSL_IMPORT, to define and declare - DLL exports and imports for compilers under Win32. These are a little - more complicated to use. Basically, for any library that exports some - global variables, the following code must be present in the header file - that declares them, before OPENSSL_EXTERN is used: - - #ifdef SOME_BUILD_FLAG_MACRO - # undef OPENSSL_EXTERN - # define OPENSSL_EXTERN OPENSSL_EXPORT - #endif - - The default is to have OPENSSL_EXPORT, OPENSSL_IMPORT and OPENSSL_GLOBAL - have some generally sensible values, and for OPENSSL_EXTERN to have the - value OPENSSL_IMPORT. -*/ - -#if defined(OPENSSL_SYS_VMS_NODECC) -# define OPENSSL_EXPORT globalref -# define OPENSSL_IMPORT globalref -# define OPENSSL_GLOBAL globaldef -#elif defined(OPENSSL_SYS_WINDOWS) && defined(OPENSSL_OPT_WINDLL) -# define OPENSSL_EXPORT extern __declspec(dllexport) -# define OPENSSL_IMPORT extern __declspec(dllimport) -# define OPENSSL_GLOBAL -#else -# define OPENSSL_EXPORT extern -# define OPENSSL_IMPORT extern -# define OPENSSL_GLOBAL -#endif -#define OPENSSL_EXTERN OPENSSL_IMPORT - -/* Macros to allow global variables to be reached through function calls when - required (if a shared library version requires it, for example. - The way it's done allows definitions like this: - - // in foobar.c - OPENSSL_IMPLEMENT_GLOBAL(int,foobar,0) - // in foobar.h - OPENSSL_DECLARE_GLOBAL(int,foobar); - #define foobar OPENSSL_GLOBAL_REF(foobar) -*/ -#ifdef OPENSSL_EXPORT_VAR_AS_FUNCTION -# define OPENSSL_IMPLEMENT_GLOBAL(type,name,value) \ - type *_shadow_##name(void) \ - { static type _hide_##name=value; return &_hide_##name; } -# define OPENSSL_DECLARE_GLOBAL(type,name) type *_shadow_##name(void) -# define OPENSSL_GLOBAL_REF(name) (*(_shadow_##name())) -#else -# define OPENSSL_IMPLEMENT_GLOBAL(type,name,value) OPENSSL_GLOBAL type _shadow_##name=value; -# define OPENSSL_DECLARE_GLOBAL(type,name) OPENSSL_EXPORT type _shadow_##name -# define OPENSSL_GLOBAL_REF(name) _shadow_##name -#endif - -#if defined(OPENSSL_SYS_MACINTOSH_CLASSIC) && macintosh==1 && !defined(MAC_OS_GUSI_SOURCE) -# define ossl_ssize_t long -#endif - -#ifdef OPENSSL_SYS_MSDOS -# define ossl_ssize_t long -#endif - -#if defined(NeXT) || defined(OPENSSL_SYS_NEWS4) || defined(OPENSSL_SYS_SUNOS) -# define ssize_t int -#endif - -#if defined(__ultrix) && !defined(ssize_t) -# define ossl_ssize_t int -#endif - -#ifndef ossl_ssize_t -# define ossl_ssize_t ssize_t -#endif - -#ifdef __cplusplus -} -#endif -#endif diff --git a/src/plugins/ftp/openssl/ec.h b/src/plugins/ftp/openssl/ec.h deleted file mode 100644 index 9d01325a..00000000 --- a/src/plugins/ftp/openssl/ec.h +++ /dev/null @@ -1,1159 +0,0 @@ -/* crypto/ec/ec.h */ -/* - * Originally written by Bodo Moeller for the OpenSSL project. - */ -/** - * \file crypto/ec/ec.h Include file for the OpenSSL EC functions - * \author Originally written by Bodo Moeller for the OpenSSL project - */ -/* ==================================================================== - * Copyright (c) 1998-2005 The OpenSSL Project. All rights reserved. - * - * 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. All advertising materials mentioning features or use of this - * software must display the following acknowledgment: - * "This product includes software developed by the OpenSSL Project - * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" - * - * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For written permission, please contact - * openssl-core@openssl.org. - * - * 5. Products derived from this software may not be called "OpenSSL" - * nor may "OpenSSL" appear in their names without prior written - * permission of the OpenSSL Project. - * - * 6. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by the OpenSSL Project - * for use in the OpenSSL Toolkit (http://www.openssl.org/)" - * - * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY - * EXPRESSED 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 OpenSSL PROJECT OR - * ITS 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. - * ==================================================================== - * - * This product includes cryptographic software written by Eric Young - * (eay@cryptsoft.com). This product includes software written by Tim - * Hudson (tjh@cryptsoft.com). - * - */ -/* ==================================================================== - * Copyright 2002 Sun Microsystems, Inc. ALL RIGHTS RESERVED. - * - * Portions of the attached software ("Contribution") are developed by - * SUN MICROSYSTEMS, INC., and are contributed to the OpenSSL project. - * - * The Contribution is licensed pursuant to the OpenSSL open source - * license provided above. - * - * The elliptic curve binary polynomial software is originally written by - * Sheueling Chang Shantz and Douglas Stebila of Sun Microsystems Laboratories. - * - */ - -#ifndef HEADER_EC_H -#define HEADER_EC_H - -#include - -#ifdef OPENSSL_NO_EC -#error EC is disabled. -#endif - -#include -#include -#ifndef OPENSSL_NO_DEPRECATED -#include -#endif - -#ifdef __cplusplus -extern "C" { -#elif defined(__SUNPRO_C) -# if __SUNPRO_C >= 0x520 -# pragma error_messages (off,E_ARRAY_OF_INCOMPLETE_NONAME,E_ARRAY_OF_INCOMPLETE) -# endif -#endif - - -#ifndef OPENSSL_ECC_MAX_FIELD_BITS -# define OPENSSL_ECC_MAX_FIELD_BITS 661 -#endif - -/** Enum for the point conversion form as defined in X9.62 (ECDSA) - * for the encoding of a elliptic curve point (x,y) */ -typedef enum { - /** the point is encoded as z||x, where the octet z specifies - * which solution of the quadratic equation y is */ - POINT_CONVERSION_COMPRESSED = 2, - /** the point is encoded as z||x||y, where z is the octet 0x02 */ - POINT_CONVERSION_UNCOMPRESSED = 4, - /** the point is encoded as z||x||y, where the octet z specifies - * which solution of the quadratic equation y is */ - POINT_CONVERSION_HYBRID = 6 -} point_conversion_form_t; - - -typedef struct ec_method_st EC_METHOD; - -typedef struct ec_group_st - /* - EC_METHOD *meth; - -- field definition - -- curve coefficients - -- optional generator with associated information (order, cofactor) - -- optional extra data (precomputed table for fast computation of multiples of generator) - -- ASN1 stuff - */ - EC_GROUP; - -typedef struct ec_point_st EC_POINT; - - -/********************************************************************/ -/* EC_METHODs for curves over GF(p) */ -/********************************************************************/ - -/** Returns the basic GFp ec methods which provides the basis for the - * optimized methods. - * \return EC_METHOD object - */ -const EC_METHOD *EC_GFp_simple_method(void); - -/** Returns GFp methods using montgomery multiplication. - * \return EC_METHOD object - */ -const EC_METHOD *EC_GFp_mont_method(void); - -/** Returns GFp methods using optimized methods for NIST recommended curves - * \return EC_METHOD object - */ -const EC_METHOD *EC_GFp_nist_method(void); - -#ifndef OPENSSL_NO_EC_NISTP_64_GCC_128 -/** Returns 64-bit optimized methods for nistp224 - * \return EC_METHOD object - */ -const EC_METHOD *EC_GFp_nistp224_method(void); - -/** Returns 64-bit optimized methods for nistp256 - * \return EC_METHOD object - */ -const EC_METHOD *EC_GFp_nistp256_method(void); - -/** Returns 64-bit optimized methods for nistp521 - * \return EC_METHOD object - */ -const EC_METHOD *EC_GFp_nistp521_method(void); -#endif - -#ifndef OPENSSL_NO_EC2M -/********************************************************************/ -/* EC_METHOD for curves over GF(2^m) */ -/********************************************************************/ - -/** Returns the basic GF2m ec method - * \return EC_METHOD object - */ -const EC_METHOD *EC_GF2m_simple_method(void); - -#endif - - -/********************************************************************/ -/* EC_GROUP functions */ -/********************************************************************/ - -/** Creates a new EC_GROUP object - * \param meth EC_METHOD to use - * \return newly created EC_GROUP object or NULL in case of an error. - */ -EC_GROUP *EC_GROUP_new(const EC_METHOD *meth); - -/** Frees a EC_GROUP object - * \param group EC_GROUP object to be freed. - */ -void EC_GROUP_free(EC_GROUP *group); - -/** Clears and frees a EC_GROUP object - * \param group EC_GROUP object to be cleared and freed. - */ -void EC_GROUP_clear_free(EC_GROUP *group); - -/** Copies EC_GROUP objects. Note: both EC_GROUPs must use the same EC_METHOD. - * \param dst destination EC_GROUP object - * \param src source EC_GROUP object - * \return 1 on success and 0 if an error occurred. - */ -int EC_GROUP_copy(EC_GROUP *dst, const EC_GROUP *src); - -/** Creates a new EC_GROUP object and copies the copies the content - * form src to the newly created EC_KEY object - * \param src source EC_GROUP object - * \return newly created EC_GROUP object or NULL in case of an error. - */ -EC_GROUP *EC_GROUP_dup(const EC_GROUP *src); - -/** Returns the EC_METHOD of the EC_GROUP object. - * \param group EC_GROUP object - * \return EC_METHOD used in this EC_GROUP object. - */ -const EC_METHOD *EC_GROUP_method_of(const EC_GROUP *group); - -/** Returns the field type of the EC_METHOD. - * \param meth EC_METHOD object - * \return NID of the underlying field type OID. - */ -int EC_METHOD_get_field_type(const EC_METHOD *meth); - -/** Sets the generator and it's order/cofactor of a EC_GROUP object. - * \param group EC_GROUP object - * \param generator EC_POINT object with the generator. - * \param order the order of the group generated by the generator. - * \param cofactor the index of the sub-group generated by the generator - * in the group of all points on the elliptic curve. - * \return 1 on success and 0 if an error occured - */ -int EC_GROUP_set_generator(EC_GROUP *group, const EC_POINT *generator, const BIGNUM *order, const BIGNUM *cofactor); - -/** Returns the generator of a EC_GROUP object. - * \param group EC_GROUP object - * \return the currently used generator (possibly NULL). - */ -const EC_POINT *EC_GROUP_get0_generator(const EC_GROUP *group); - -/** Gets the order of a EC_GROUP - * \param group EC_GROUP object - * \param order BIGNUM to which the order is copied - * \param ctx BN_CTX object (optional) - * \return 1 on success and 0 if an error occured - */ -int EC_GROUP_get_order(const EC_GROUP *group, BIGNUM *order, BN_CTX *ctx); - -/** Gets the cofactor of a EC_GROUP - * \param group EC_GROUP object - * \param cofactor BIGNUM to which the cofactor is copied - * \param ctx BN_CTX object (optional) - * \return 1 on success and 0 if an error occured - */ -int EC_GROUP_get_cofactor(const EC_GROUP *group, BIGNUM *cofactor, BN_CTX *ctx); - -/** Sets the name of a EC_GROUP object - * \param group EC_GROUP object - * \param nid NID of the curve name OID - */ -void EC_GROUP_set_curve_name(EC_GROUP *group, int nid); - -/** Returns the curve name of a EC_GROUP object - * \param group EC_GROUP object - * \return NID of the curve name OID or 0 if not set. - */ -int EC_GROUP_get_curve_name(const EC_GROUP *group); - -void EC_GROUP_set_asn1_flag(EC_GROUP *group, int flag); -int EC_GROUP_get_asn1_flag(const EC_GROUP *group); - -void EC_GROUP_set_point_conversion_form(EC_GROUP *, point_conversion_form_t); -point_conversion_form_t EC_GROUP_get_point_conversion_form(const EC_GROUP *); - -unsigned char *EC_GROUP_get0_seed(const EC_GROUP *); -size_t EC_GROUP_get_seed_len(const EC_GROUP *); -size_t EC_GROUP_set_seed(EC_GROUP *, const unsigned char *, size_t len); - -/** Sets the parameter of a ec over GFp defined by y^2 = x^3 + a*x + b - * \param group EC_GROUP object - * \param p BIGNUM with the prime number - * \param a BIGNUM with parameter a of the equation - * \param b BIGNUM with parameter b of the equation - * \param ctx BN_CTX object (optional) - * \return 1 on success and 0 if an error occured - */ -int EC_GROUP_set_curve_GFp(EC_GROUP *group, const BIGNUM *p, const BIGNUM *a, const BIGNUM *b, BN_CTX *ctx); - -/** Gets the parameter of the ec over GFp defined by y^2 = x^3 + a*x + b - * \param group EC_GROUP object - * \param p BIGNUM for the prime number - * \param a BIGNUM for parameter a of the equation - * \param b BIGNUM for parameter b of the equation - * \param ctx BN_CTX object (optional) - * \return 1 on success and 0 if an error occured - */ -int EC_GROUP_get_curve_GFp(const EC_GROUP *group, BIGNUM *p, BIGNUM *a, BIGNUM *b, BN_CTX *ctx); - -#ifndef OPENSSL_NO_EC2M -/** Sets the parameter of a ec over GF2m defined by y^2 + x*y = x^3 + a*x^2 + b - * \param group EC_GROUP object - * \param p BIGNUM with the polynomial defining the underlying field - * \param a BIGNUM with parameter a of the equation - * \param b BIGNUM with parameter b of the equation - * \param ctx BN_CTX object (optional) - * \return 1 on success and 0 if an error occured - */ -int EC_GROUP_set_curve_GF2m(EC_GROUP *group, const BIGNUM *p, const BIGNUM *a, const BIGNUM *b, BN_CTX *ctx); - -/** Gets the parameter of the ec over GF2m defined by y^2 + x*y = x^3 + a*x^2 + b - * \param group EC_GROUP object - * \param p BIGNUM for the polynomial defining the underlying field - * \param a BIGNUM for parameter a of the equation - * \param b BIGNUM for parameter b of the equation - * \param ctx BN_CTX object (optional) - * \return 1 on success and 0 if an error occured - */ -int EC_GROUP_get_curve_GF2m(const EC_GROUP *group, BIGNUM *p, BIGNUM *a, BIGNUM *b, BN_CTX *ctx); -#endif -/** Returns the number of bits needed to represent a field element - * \param group EC_GROUP object - * \return number of bits needed to represent a field element - */ -int EC_GROUP_get_degree(const EC_GROUP *group); - -/** Checks whether the parameter in the EC_GROUP define a valid ec group - * \param group EC_GROUP object - * \param ctx BN_CTX object (optional) - * \return 1 if group is a valid ec group and 0 otherwise - */ -int EC_GROUP_check(const EC_GROUP *group, BN_CTX *ctx); - -/** Checks whether the discriminant of the elliptic curve is zero or not - * \param group EC_GROUP object - * \param ctx BN_CTX object (optional) - * \return 1 if the discriminant is not zero and 0 otherwise - */ -int EC_GROUP_check_discriminant(const EC_GROUP *group, BN_CTX *ctx); - -/** Compares two EC_GROUP objects - * \param a first EC_GROUP object - * \param b second EC_GROUP object - * \param ctx BN_CTX object (optional) - * \return 0 if both groups are equal and 1 otherwise - */ -int EC_GROUP_cmp(const EC_GROUP *a, const EC_GROUP *b, BN_CTX *ctx); - -/* EC_GROUP_new_GF*() calls EC_GROUP_new() and EC_GROUP_set_GF*() - * after choosing an appropriate EC_METHOD */ - -/** Creates a new EC_GROUP object with the specified parameters defined - * over GFp (defined by the equation y^2 = x^3 + a*x + b) - * \param p BIGNUM with the prime number - * \param a BIGNUM with the parameter a of the equation - * \param b BIGNUM with the parameter b of the equation - * \param ctx BN_CTX object (optional) - * \return newly created EC_GROUP object with the specified parameters - */ -EC_GROUP *EC_GROUP_new_curve_GFp(const BIGNUM *p, const BIGNUM *a, const BIGNUM *b, BN_CTX *ctx); -#ifndef OPENSSL_NO_EC2M -/** Creates a new EC_GROUP object with the specified parameters defined - * over GF2m (defined by the equation y^2 + x*y = x^3 + a*x^2 + b) - * \param p BIGNUM with the polynomial defining the underlying field - * \param a BIGNUM with the parameter a of the equation - * \param b BIGNUM with the parameter b of the equation - * \param ctx BN_CTX object (optional) - * \return newly created EC_GROUP object with the specified parameters - */ -EC_GROUP *EC_GROUP_new_curve_GF2m(const BIGNUM *p, const BIGNUM *a, const BIGNUM *b, BN_CTX *ctx); -#endif -/** Creates a EC_GROUP object with a curve specified by a NID - * \param nid NID of the OID of the curve name - * \return newly created EC_GROUP object with specified curve or NULL - * if an error occurred - */ -EC_GROUP *EC_GROUP_new_by_curve_name(int nid); - - -/********************************************************************/ -/* handling of internal curves */ -/********************************************************************/ - -typedef struct { - int nid; - const char *comment; - } EC_builtin_curve; - -/* EC_builtin_curves(EC_builtin_curve *r, size_t size) returns number - * of all available curves or zero if a error occurred. - * In case r ist not zero nitems EC_builtin_curve structures - * are filled with the data of the first nitems internal groups */ -size_t EC_get_builtin_curves(EC_builtin_curve *r, size_t nitems); - - -/********************************************************************/ -/* EC_POINT functions */ -/********************************************************************/ - -/** Creates a new EC_POINT object for the specified EC_GROUP - * \param group EC_GROUP the underlying EC_GROUP object - * \return newly created EC_POINT object or NULL if an error occurred - */ -EC_POINT *EC_POINT_new(const EC_GROUP *group); - -/** Frees a EC_POINT object - * \param point EC_POINT object to be freed - */ -void EC_POINT_free(EC_POINT *point); - -/** Clears and frees a EC_POINT object - * \param point EC_POINT object to be cleared and freed - */ -void EC_POINT_clear_free(EC_POINT *point); - -/** Copies EC_POINT object - * \param dst destination EC_POINT object - * \param src source EC_POINT object - * \return 1 on success and 0 if an error occured - */ -int EC_POINT_copy(EC_POINT *dst, const EC_POINT *src); - -/** Creates a new EC_POINT object and copies the content of the supplied - * EC_POINT - * \param src source EC_POINT object - * \param group underlying the EC_GROUP object - * \return newly created EC_POINT object or NULL if an error occurred - */ -EC_POINT *EC_POINT_dup(const EC_POINT *src, const EC_GROUP *group); - -/** Returns the EC_METHOD used in EC_POINT object - * \param point EC_POINT object - * \return the EC_METHOD used - */ -const EC_METHOD *EC_POINT_method_of(const EC_POINT *point); - -/** Sets a point to infinity (neutral element) - * \param group underlying EC_GROUP object - * \param point EC_POINT to set to infinity - * \return 1 on success and 0 if an error occured - */ -int EC_POINT_set_to_infinity(const EC_GROUP *group, EC_POINT *point); - -/** Sets the jacobian projective coordinates of a EC_POINT over GFp - * \param group underlying EC_GROUP object - * \param p EC_POINT object - * \param x BIGNUM with the x-coordinate - * \param y BIGNUM with the y-coordinate - * \param z BIGNUM with the z-coordinate - * \param ctx BN_CTX object (optional) - * \return 1 on success and 0 if an error occured - */ -int EC_POINT_set_Jprojective_coordinates_GFp(const EC_GROUP *group, EC_POINT *p, - const BIGNUM *x, const BIGNUM *y, const BIGNUM *z, BN_CTX *ctx); - -/** Gets the jacobian projective coordinates of a EC_POINT over GFp - * \param group underlying EC_GROUP object - * \param p EC_POINT object - * \param x BIGNUM for the x-coordinate - * \param y BIGNUM for the y-coordinate - * \param z BIGNUM for the z-coordinate - * \param ctx BN_CTX object (optional) - * \return 1 on success and 0 if an error occured - */ -int EC_POINT_get_Jprojective_coordinates_GFp(const EC_GROUP *group, - const EC_POINT *p, BIGNUM *x, BIGNUM *y, BIGNUM *z, BN_CTX *ctx); - -/** Sets the affine coordinates of a EC_POINT over GFp - * \param group underlying EC_GROUP object - * \param p EC_POINT object - * \param x BIGNUM with the x-coordinate - * \param y BIGNUM with the y-coordinate - * \param ctx BN_CTX object (optional) - * \return 1 on success and 0 if an error occured - */ -int EC_POINT_set_affine_coordinates_GFp(const EC_GROUP *group, EC_POINT *p, - const BIGNUM *x, const BIGNUM *y, BN_CTX *ctx); - -/** Gets the affine coordinates of a EC_POINT over GFp - * \param group underlying EC_GROUP object - * \param p EC_POINT object - * \param x BIGNUM for the x-coordinate - * \param y BIGNUM for the y-coordinate - * \param ctx BN_CTX object (optional) - * \return 1 on success and 0 if an error occured - */ -int EC_POINT_get_affine_coordinates_GFp(const EC_GROUP *group, - const EC_POINT *p, BIGNUM *x, BIGNUM *y, BN_CTX *ctx); - -/** Sets the x9.62 compressed coordinates of a EC_POINT over GFp - * \param group underlying EC_GROUP object - * \param p EC_POINT object - * \param x BIGNUM with x-coordinate - * \param y_bit integer with the y-Bit (either 0 or 1) - * \param ctx BN_CTX object (optional) - * \return 1 on success and 0 if an error occured - */ -int EC_POINT_set_compressed_coordinates_GFp(const EC_GROUP *group, EC_POINT *p, - const BIGNUM *x, int y_bit, BN_CTX *ctx); -#ifndef OPENSSL_NO_EC2M -/** Sets the affine coordinates of a EC_POINT over GF2m - * \param group underlying EC_GROUP object - * \param p EC_POINT object - * \param x BIGNUM with the x-coordinate - * \param y BIGNUM with the y-coordinate - * \param ctx BN_CTX object (optional) - * \return 1 on success and 0 if an error occured - */ -int EC_POINT_set_affine_coordinates_GF2m(const EC_GROUP *group, EC_POINT *p, - const BIGNUM *x, const BIGNUM *y, BN_CTX *ctx); - -/** Gets the affine coordinates of a EC_POINT over GF2m - * \param group underlying EC_GROUP object - * \param p EC_POINT object - * \param x BIGNUM for the x-coordinate - * \param y BIGNUM for the y-coordinate - * \param ctx BN_CTX object (optional) - * \return 1 on success and 0 if an error occured - */ -int EC_POINT_get_affine_coordinates_GF2m(const EC_GROUP *group, - const EC_POINT *p, BIGNUM *x, BIGNUM *y, BN_CTX *ctx); - -/** Sets the x9.62 compressed coordinates of a EC_POINT over GF2m - * \param group underlying EC_GROUP object - * \param p EC_POINT object - * \param x BIGNUM with x-coordinate - * \param y_bit integer with the y-Bit (either 0 or 1) - * \param ctx BN_CTX object (optional) - * \return 1 on success and 0 if an error occured - */ -int EC_POINT_set_compressed_coordinates_GF2m(const EC_GROUP *group, EC_POINT *p, - const BIGNUM *x, int y_bit, BN_CTX *ctx); -#endif -/** Encodes a EC_POINT object to a octet string - * \param group underlying EC_GROUP object - * \param p EC_POINT object - * \param form point conversion form - * \param buf memory buffer for the result. If NULL the function returns - * required buffer size. - * \param len length of the memory buffer - * \param ctx BN_CTX object (optional) - * \return the length of the encoded octet string or 0 if an error occurred - */ -size_t EC_POINT_point2oct(const EC_GROUP *group, const EC_POINT *p, - point_conversion_form_t form, - unsigned char *buf, size_t len, BN_CTX *ctx); - -/** Decodes a EC_POINT from a octet string - * \param group underlying EC_GROUP object - * \param p EC_POINT object - * \param buf memory buffer with the encoded ec point - * \param len length of the encoded ec point - * \param ctx BN_CTX object (optional) - * \return 1 on success and 0 if an error occured - */ -int EC_POINT_oct2point(const EC_GROUP *group, EC_POINT *p, - const unsigned char *buf, size_t len, BN_CTX *ctx); - -/* other interfaces to point2oct/oct2point: */ -BIGNUM *EC_POINT_point2bn(const EC_GROUP *, const EC_POINT *, - point_conversion_form_t form, BIGNUM *, BN_CTX *); -EC_POINT *EC_POINT_bn2point(const EC_GROUP *, const BIGNUM *, - EC_POINT *, BN_CTX *); -char *EC_POINT_point2hex(const EC_GROUP *, const EC_POINT *, - point_conversion_form_t form, BN_CTX *); -EC_POINT *EC_POINT_hex2point(const EC_GROUP *, const char *, - EC_POINT *, BN_CTX *); - - -/********************************************************************/ -/* functions for doing EC_POINT arithmetic */ -/********************************************************************/ - -/** Computes the sum of two EC_POINT - * \param group underlying EC_GROUP object - * \param r EC_POINT object for the result (r = a + b) - * \param a EC_POINT object with the first summand - * \param b EC_POINT object with the second summand - * \param ctx BN_CTX object (optional) - * \return 1 on success and 0 if an error occured - */ -int EC_POINT_add(const EC_GROUP *group, EC_POINT *r, const EC_POINT *a, const EC_POINT *b, BN_CTX *ctx); - -/** Computes the double of a EC_POINT - * \param group underlying EC_GROUP object - * \param r EC_POINT object for the result (r = 2 * a) - * \param a EC_POINT object - * \param ctx BN_CTX object (optional) - * \return 1 on success and 0 if an error occured - */ -int EC_POINT_dbl(const EC_GROUP *group, EC_POINT *r, const EC_POINT *a, BN_CTX *ctx); - -/** Computes the inverse of a EC_POINT - * \param group underlying EC_GROUP object - * \param a EC_POINT object to be inverted (it's used for the result as well) - * \param ctx BN_CTX object (optional) - * \return 1 on success and 0 if an error occured - */ -int EC_POINT_invert(const EC_GROUP *group, EC_POINT *a, BN_CTX *ctx); - -/** Checks whether the point is the neutral element of the group - * \param group the underlying EC_GROUP object - * \param p EC_POINT object - * \return 1 if the point is the neutral element and 0 otherwise - */ -int EC_POINT_is_at_infinity(const EC_GROUP *group, const EC_POINT *p); - -/** Checks whether the point is on the curve - * \param group underlying EC_GROUP object - * \param point EC_POINT object to check - * \param ctx BN_CTX object (optional) - * \return 1 if point if on the curve and 0 otherwise - */ -int EC_POINT_is_on_curve(const EC_GROUP *group, const EC_POINT *point, BN_CTX *ctx); - -/** Compares two EC_POINTs - * \param group underlying EC_GROUP object - * \param a first EC_POINT object - * \param b second EC_POINT object - * \param ctx BN_CTX object (optional) - * \return 0 if both points are equal and a value != 0 otherwise - */ -int EC_POINT_cmp(const EC_GROUP *group, const EC_POINT *a, const EC_POINT *b, BN_CTX *ctx); - -int EC_POINT_make_affine(const EC_GROUP *, EC_POINT *, BN_CTX *); -int EC_POINTs_make_affine(const EC_GROUP *, size_t num, EC_POINT *[], BN_CTX *); - -/** Computes r = generator * n sum_{i=0}^num p[i] * m[i] - * \param group underlying EC_GROUP object - * \param r EC_POINT object for the result - * \param n BIGNUM with the multiplier for the group generator (optional) - * \param num number futher summands - * \param p array of size num of EC_POINT objects - * \param m array of size num of BIGNUM objects - * \param ctx BN_CTX object (optional) - * \return 1 on success and 0 if an error occured - */ -int EC_POINTs_mul(const EC_GROUP *group, EC_POINT *r, const BIGNUM *n, size_t num, const EC_POINT *p[], const BIGNUM *m[], BN_CTX *ctx); - -/** Computes r = generator * n + q * m - * \param group underlying EC_GROUP object - * \param r EC_POINT object for the result - * \param n BIGNUM with the multiplier for the group generator (optional) - * \param q EC_POINT object with the first factor of the second summand - * \param m BIGNUM with the second factor of the second summand - * \param ctx BN_CTX object (optional) - * \return 1 on success and 0 if an error occured - */ -int EC_POINT_mul(const EC_GROUP *group, EC_POINT *r, const BIGNUM *n, const EC_POINT *q, const BIGNUM *m, BN_CTX *ctx); - -/** Stores multiples of generator for faster point multiplication - * \param group EC_GROUP object - * \param ctx BN_CTX object (optional) - * \return 1 on success and 0 if an error occured - */ -int EC_GROUP_precompute_mult(EC_GROUP *group, BN_CTX *ctx); - -/** Reports whether a precomputation has been done - * \param group EC_GROUP object - * \return 1 if a pre-computation has been done and 0 otherwise - */ -int EC_GROUP_have_precompute_mult(const EC_GROUP *group); - - -/********************************************************************/ -/* ASN1 stuff */ -/********************************************************************/ - -/* EC_GROUP_get_basis_type() returns the NID of the basis type - * used to represent the field elements */ -int EC_GROUP_get_basis_type(const EC_GROUP *); -#ifndef OPENSSL_NO_EC2M -int EC_GROUP_get_trinomial_basis(const EC_GROUP *, unsigned int *k); -int EC_GROUP_get_pentanomial_basis(const EC_GROUP *, unsigned int *k1, - unsigned int *k2, unsigned int *k3); -#endif - -#define OPENSSL_EC_NAMED_CURVE 0x001 - -typedef struct ecpk_parameters_st ECPKPARAMETERS; - -EC_GROUP *d2i_ECPKParameters(EC_GROUP **, const unsigned char **in, long len); -int i2d_ECPKParameters(const EC_GROUP *, unsigned char **out); - -#define d2i_ECPKParameters_bio(bp,x) ASN1_d2i_bio_of(EC_GROUP,NULL,d2i_ECPKParameters,bp,x) -#define i2d_ECPKParameters_bio(bp,x) ASN1_i2d_bio_of_const(EC_GROUP,i2d_ECPKParameters,bp,x) -#define d2i_ECPKParameters_fp(fp,x) (EC_GROUP *)ASN1_d2i_fp(NULL, \ - (char *(*)())d2i_ECPKParameters,(fp),(unsigned char **)(x)) -#define i2d_ECPKParameters_fp(fp,x) ASN1_i2d_fp(i2d_ECPKParameters,(fp), \ - (unsigned char *)(x)) - -#ifndef OPENSSL_NO_BIO -int ECPKParameters_print(BIO *bp, const EC_GROUP *x, int off); -#endif -#ifndef OPENSSL_NO_FP_API -int ECPKParameters_print_fp(FILE *fp, const EC_GROUP *x, int off); -#endif - - -/********************************************************************/ -/* EC_KEY functions */ -/********************************************************************/ - -typedef struct ec_key_st EC_KEY; - -/* some values for the encoding_flag */ -#define EC_PKEY_NO_PARAMETERS 0x001 -#define EC_PKEY_NO_PUBKEY 0x002 - -/* some values for the flags field */ -#define EC_FLAG_NON_FIPS_ALLOW 0x1 -#define EC_FLAG_FIPS_CHECKED 0x2 - -/** Creates a new EC_KEY object. - * \return EC_KEY object or NULL if an error occurred. - */ -EC_KEY *EC_KEY_new(void); - -int EC_KEY_get_flags(const EC_KEY *key); - -void EC_KEY_set_flags(EC_KEY *key, int flags); - -void EC_KEY_clear_flags(EC_KEY *key, int flags); - -/** Creates a new EC_KEY object using a named curve as underlying - * EC_GROUP object. - * \param nid NID of the named curve. - * \return EC_KEY object or NULL if an error occurred. - */ -EC_KEY *EC_KEY_new_by_curve_name(int nid); - -/** Frees a EC_KEY object. - * \param key EC_KEY object to be freed. - */ -void EC_KEY_free(EC_KEY *key); - -/** Copies a EC_KEY object. - * \param dst destination EC_KEY object - * \param src src EC_KEY object - * \return dst or NULL if an error occurred. - */ -EC_KEY *EC_KEY_copy(EC_KEY *dst, const EC_KEY *src); - -/** Creates a new EC_KEY object and copies the content from src to it. - * \param src the source EC_KEY object - * \return newly created EC_KEY object or NULL if an error occurred. - */ -EC_KEY *EC_KEY_dup(const EC_KEY *src); - -/** Increases the internal reference count of a EC_KEY object. - * \param key EC_KEY object - * \return 1 on success and 0 if an error occurred. - */ -int EC_KEY_up_ref(EC_KEY *key); - -/** Returns the EC_GROUP object of a EC_KEY object - * \param key EC_KEY object - * \return the EC_GROUP object (possibly NULL). - */ -const EC_GROUP *EC_KEY_get0_group(const EC_KEY *key); - -/** Sets the EC_GROUP of a EC_KEY object. - * \param key EC_KEY object - * \param group EC_GROUP to use in the EC_KEY object (note: the EC_KEY - * object will use an own copy of the EC_GROUP). - * \return 1 on success and 0 if an error occurred. - */ -int EC_KEY_set_group(EC_KEY *key, const EC_GROUP *group); - -/** Returns the private key of a EC_KEY object. - * \param key EC_KEY object - * \return a BIGNUM with the private key (possibly NULL). - */ -const BIGNUM *EC_KEY_get0_private_key(const EC_KEY *key); - -/** Sets the private key of a EC_KEY object. - * \param key EC_KEY object - * \param prv BIGNUM with the private key (note: the EC_KEY object - * will use an own copy of the BIGNUM). - * \return 1 on success and 0 if an error occurred. - */ -int EC_KEY_set_private_key(EC_KEY *key, const BIGNUM *prv); - -/** Returns the public key of a EC_KEY object. - * \param key the EC_KEY object - * \return a EC_POINT object with the public key (possibly NULL) - */ -const EC_POINT *EC_KEY_get0_public_key(const EC_KEY *key); - -/** Sets the public key of a EC_KEY object. - * \param key EC_KEY object - * \param pub EC_POINT object with the public key (note: the EC_KEY object - * will use an own copy of the EC_POINT object). - * \return 1 on success and 0 if an error occurred. - */ -int EC_KEY_set_public_key(EC_KEY *key, const EC_POINT *pub); - -unsigned EC_KEY_get_enc_flags(const EC_KEY *key); -void EC_KEY_set_enc_flags(EC_KEY *, unsigned int); -point_conversion_form_t EC_KEY_get_conv_form(const EC_KEY *); -void EC_KEY_set_conv_form(EC_KEY *, point_conversion_form_t); -/* functions to set/get method specific data */ -void *EC_KEY_get_key_method_data(EC_KEY *, - void *(*dup_func)(void *), void (*free_func)(void *), void (*clear_free_func)(void *)); -void EC_KEY_insert_key_method_data(EC_KEY *, void *data, - void *(*dup_func)(void *), void (*free_func)(void *), void (*clear_free_func)(void *)); -/* wrapper functions for the underlying EC_GROUP object */ -void EC_KEY_set_asn1_flag(EC_KEY *, int); - -/** Creates a table of pre-computed multiples of the generator to - * accelerate further EC_KEY operations. - * \param key EC_KEY object - * \param ctx BN_CTX object (optional) - * \return 1 on success and 0 if an error occurred. - */ -int EC_KEY_precompute_mult(EC_KEY *key, BN_CTX *ctx); - -/** Creates a new ec private (and optional a new public) key. - * \param key EC_KEY object - * \return 1 on success and 0 if an error occurred. - */ -int EC_KEY_generate_key(EC_KEY *key); - -/** Verifies that a private and/or public key is valid. - * \param key the EC_KEY object - * \return 1 on success and 0 otherwise. - */ -int EC_KEY_check_key(const EC_KEY *key); - -/** Sets a public key from affine coordindates performing - * neccessary NIST PKV tests. - * \param key the EC_KEY object - * \param x public key x coordinate - * \param y public key y coordinate - * \return 1 on success and 0 otherwise. - */ -int EC_KEY_set_public_key_affine_coordinates(EC_KEY *key, BIGNUM *x, BIGNUM *y); - - -/********************************************************************/ -/* de- and encoding functions for SEC1 ECPrivateKey */ -/********************************************************************/ - -/** Decodes a private key from a memory buffer. - * \param key a pointer to a EC_KEY object which should be used (or NULL) - * \param in pointer to memory with the DER encoded private key - * \param len length of the DER encoded private key - * \return the decoded private key or NULL if an error occurred. - */ -EC_KEY *d2i_ECPrivateKey(EC_KEY **key, const unsigned char **in, long len); - -/** Encodes a private key object and stores the result in a buffer. - * \param key the EC_KEY object to encode - * \param out the buffer for the result (if NULL the function returns number - * of bytes needed). - * \return 1 on success and 0 if an error occurred. - */ -int i2d_ECPrivateKey(EC_KEY *key, unsigned char **out); - - -/********************************************************************/ -/* de- and encoding functions for EC parameters */ -/********************************************************************/ - -/** Decodes ec parameter from a memory buffer. - * \param key a pointer to a EC_KEY object which should be used (or NULL) - * \param in pointer to memory with the DER encoded ec parameters - * \param len length of the DER encoded ec parameters - * \return a EC_KEY object with the decoded parameters or NULL if an error - * occurred. - */ -EC_KEY *d2i_ECParameters(EC_KEY **key, const unsigned char **in, long len); - -/** Encodes ec parameter and stores the result in a buffer. - * \param key the EC_KEY object with ec paramters to encode - * \param out the buffer for the result (if NULL the function returns number - * of bytes needed). - * \return 1 on success and 0 if an error occurred. - */ -int i2d_ECParameters(EC_KEY *key, unsigned char **out); - - -/********************************************************************/ -/* de- and encoding functions for EC public key */ -/* (octet string, not DER -- hence 'o2i' and 'i2o') */ -/********************************************************************/ - -/** Decodes a ec public key from a octet string. - * \param key a pointer to a EC_KEY object which should be used - * \param in memory buffer with the encoded public key - * \param len length of the encoded public key - * \return EC_KEY object with decoded public key or NULL if an error - * occurred. - */ -EC_KEY *o2i_ECPublicKey(EC_KEY **key, const unsigned char **in, long len); - -/** Encodes a ec public key in an octet string. - * \param key the EC_KEY object with the public key - * \param out the buffer for the result (if NULL the function returns number - * of bytes needed). - * \return 1 on success and 0 if an error occurred - */ -int i2o_ECPublicKey(EC_KEY *key, unsigned char **out); - -#ifndef OPENSSL_NO_BIO -/** Prints out the ec parameters on human readable form. - * \param bp BIO object to which the information is printed - * \param key EC_KEY object - * \return 1 on success and 0 if an error occurred - */ -int ECParameters_print(BIO *bp, const EC_KEY *key); - -/** Prints out the contents of a EC_KEY object - * \param bp BIO object to which the information is printed - * \param key EC_KEY object - * \param off line offset - * \return 1 on success and 0 if an error occurred - */ -int EC_KEY_print(BIO *bp, const EC_KEY *key, int off); - -#endif -#ifndef OPENSSL_NO_FP_API -/** Prints out the ec parameters on human readable form. - * \param fp file descriptor to which the information is printed - * \param key EC_KEY object - * \return 1 on success and 0 if an error occurred - */ -int ECParameters_print_fp(FILE *fp, const EC_KEY *key); - -/** Prints out the contents of a EC_KEY object - * \param fp file descriptor to which the information is printed - * \param key EC_KEY object - * \param off line offset - * \return 1 on success and 0 if an error occurred - */ -int EC_KEY_print_fp(FILE *fp, const EC_KEY *key, int off); - -#endif - -#define ECParameters_dup(x) ASN1_dup_of(EC_KEY,i2d_ECParameters,d2i_ECParameters,x) - -#ifndef __cplusplus -#if defined(__SUNPRO_C) -# if __SUNPRO_C >= 0x520 -# pragma error_messages (default,E_ARRAY_OF_INCOMPLETE_NONAME,E_ARRAY_OF_INCOMPLETE) -# endif -# endif -#endif - -#define EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, nid) \ - EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_EC, EVP_PKEY_OP_PARAMGEN, \ - EVP_PKEY_CTRL_EC_PARAMGEN_CURVE_NID, nid, NULL) - - -#define EVP_PKEY_CTRL_EC_PARAMGEN_CURVE_NID (EVP_PKEY_ALG_CTRL + 1) - -/* BEGIN ERROR CODES */ -/* The following lines are auto generated by the script mkerr.pl. Any changes - * made after this point may be overwritten when the script is next run. - */ -void ERR_load_EC_strings(void); - -/* Error codes for the EC functions. */ - -/* Function codes. */ -#define EC_F_BN_TO_FELEM 224 -#define EC_F_COMPUTE_WNAF 143 -#define EC_F_D2I_ECPARAMETERS 144 -#define EC_F_D2I_ECPKPARAMETERS 145 -#define EC_F_D2I_ECPRIVATEKEY 146 -#define EC_F_DO_EC_KEY_PRINT 221 -#define EC_F_ECKEY_PARAM2TYPE 223 -#define EC_F_ECKEY_PARAM_DECODE 212 -#define EC_F_ECKEY_PRIV_DECODE 213 -#define EC_F_ECKEY_PRIV_ENCODE 214 -#define EC_F_ECKEY_PUB_DECODE 215 -#define EC_F_ECKEY_PUB_ENCODE 216 -#define EC_F_ECKEY_TYPE2PARAM 220 -#define EC_F_ECPARAMETERS_PRINT 147 -#define EC_F_ECPARAMETERS_PRINT_FP 148 -#define EC_F_ECPKPARAMETERS_PRINT 149 -#define EC_F_ECPKPARAMETERS_PRINT_FP 150 -#define EC_F_ECP_NIST_MOD_192 203 -#define EC_F_ECP_NIST_MOD_224 204 -#define EC_F_ECP_NIST_MOD_256 205 -#define EC_F_ECP_NIST_MOD_521 206 -#define EC_F_EC_ASN1_GROUP2CURVE 153 -#define EC_F_EC_ASN1_GROUP2FIELDID 154 -#define EC_F_EC_ASN1_GROUP2PARAMETERS 155 -#define EC_F_EC_ASN1_GROUP2PKPARAMETERS 156 -#define EC_F_EC_ASN1_PARAMETERS2GROUP 157 -#define EC_F_EC_ASN1_PKPARAMETERS2GROUP 158 -#define EC_F_EC_EX_DATA_SET_DATA 211 -#define EC_F_EC_GF2M_MONTGOMERY_POINT_MULTIPLY 208 -#define EC_F_EC_GF2M_SIMPLE_GROUP_CHECK_DISCRIMINANT 159 -#define EC_F_EC_GF2M_SIMPLE_GROUP_SET_CURVE 195 -#define EC_F_EC_GF2M_SIMPLE_OCT2POINT 160 -#define EC_F_EC_GF2M_SIMPLE_POINT2OCT 161 -#define EC_F_EC_GF2M_SIMPLE_POINT_GET_AFFINE_COORDINATES 162 -#define EC_F_EC_GF2M_SIMPLE_POINT_SET_AFFINE_COORDINATES 163 -#define EC_F_EC_GF2M_SIMPLE_SET_COMPRESSED_COORDINATES 164 -#define EC_F_EC_GFP_MONT_FIELD_DECODE 133 -#define EC_F_EC_GFP_MONT_FIELD_ENCODE 134 -#define EC_F_EC_GFP_MONT_FIELD_MUL 131 -#define EC_F_EC_GFP_MONT_FIELD_SET_TO_ONE 209 -#define EC_F_EC_GFP_MONT_FIELD_SQR 132 -#define EC_F_EC_GFP_MONT_GROUP_SET_CURVE 189 -#define EC_F_EC_GFP_MONT_GROUP_SET_CURVE_GFP 135 -#define EC_F_EC_GFP_NISTP224_GROUP_SET_CURVE 225 -#define EC_F_EC_GFP_NISTP224_POINTS_MUL 228 -#define EC_F_EC_GFP_NISTP224_POINT_GET_AFFINE_COORDINATES 226 -#define EC_F_EC_GFP_NISTP256_GROUP_SET_CURVE 230 -#define EC_F_EC_GFP_NISTP256_POINTS_MUL 231 -#define EC_F_EC_GFP_NISTP256_POINT_GET_AFFINE_COORDINATES 232 -#define EC_F_EC_GFP_NISTP521_GROUP_SET_CURVE 233 -#define EC_F_EC_GFP_NISTP521_POINTS_MUL 234 -#define EC_F_EC_GFP_NISTP521_POINT_GET_AFFINE_COORDINATES 235 -#define EC_F_EC_GFP_NIST_FIELD_MUL 200 -#define EC_F_EC_GFP_NIST_FIELD_SQR 201 -#define EC_F_EC_GFP_NIST_GROUP_SET_CURVE 202 -#define EC_F_EC_GFP_SIMPLE_GROUP_CHECK_DISCRIMINANT 165 -#define EC_F_EC_GFP_SIMPLE_GROUP_SET_CURVE 166 -#define EC_F_EC_GFP_SIMPLE_GROUP_SET_CURVE_GFP 100 -#define EC_F_EC_GFP_SIMPLE_GROUP_SET_GENERATOR 101 -#define EC_F_EC_GFP_SIMPLE_MAKE_AFFINE 102 -#define EC_F_EC_GFP_SIMPLE_OCT2POINT 103 -#define EC_F_EC_GFP_SIMPLE_POINT2OCT 104 -#define EC_F_EC_GFP_SIMPLE_POINTS_MAKE_AFFINE 137 -#define EC_F_EC_GFP_SIMPLE_POINT_GET_AFFINE_COORDINATES 167 -#define EC_F_EC_GFP_SIMPLE_POINT_GET_AFFINE_COORDINATES_GFP 105 -#define EC_F_EC_GFP_SIMPLE_POINT_SET_AFFINE_COORDINATES 168 -#define EC_F_EC_GFP_SIMPLE_POINT_SET_AFFINE_COORDINATES_GFP 128 -#define EC_F_EC_GFP_SIMPLE_SET_COMPRESSED_COORDINATES 169 -#define EC_F_EC_GFP_SIMPLE_SET_COMPRESSED_COORDINATES_GFP 129 -#define EC_F_EC_GROUP_CHECK 170 -#define EC_F_EC_GROUP_CHECK_DISCRIMINANT 171 -#define EC_F_EC_GROUP_COPY 106 -#define EC_F_EC_GROUP_GET0_GENERATOR 139 -#define EC_F_EC_GROUP_GET_COFACTOR 140 -#define EC_F_EC_GROUP_GET_CURVE_GF2M 172 -#define EC_F_EC_GROUP_GET_CURVE_GFP 130 -#define EC_F_EC_GROUP_GET_DEGREE 173 -#define EC_F_EC_GROUP_GET_ORDER 141 -#define EC_F_EC_GROUP_GET_PENTANOMIAL_BASIS 193 -#define EC_F_EC_GROUP_GET_TRINOMIAL_BASIS 194 -#define EC_F_EC_GROUP_NEW 108 -#define EC_F_EC_GROUP_NEW_BY_CURVE_NAME 174 -#define EC_F_EC_GROUP_NEW_FROM_DATA 175 -#define EC_F_EC_GROUP_PRECOMPUTE_MULT 142 -#define EC_F_EC_GROUP_SET_CURVE_GF2M 176 -#define EC_F_EC_GROUP_SET_CURVE_GFP 109 -#define EC_F_EC_GROUP_SET_EXTRA_DATA 110 -#define EC_F_EC_GROUP_SET_GENERATOR 111 -#define EC_F_EC_KEY_CHECK_KEY 177 -#define EC_F_EC_KEY_COPY 178 -#define EC_F_EC_KEY_GENERATE_KEY 179 -#define EC_F_EC_KEY_NEW 182 -#define EC_F_EC_KEY_PRINT 180 -#define EC_F_EC_KEY_PRINT_FP 181 -#define EC_F_EC_KEY_SET_PUBLIC_KEY_AFFINE_COORDINATES 229 -#define EC_F_EC_POINTS_MAKE_AFFINE 136 -#define EC_F_EC_POINT_ADD 112 -#define EC_F_EC_POINT_CMP 113 -#define EC_F_EC_POINT_COPY 114 -#define EC_F_EC_POINT_DBL 115 -#define EC_F_EC_POINT_GET_AFFINE_COORDINATES_GF2M 183 -#define EC_F_EC_POINT_GET_AFFINE_COORDINATES_GFP 116 -#define EC_F_EC_POINT_GET_JPROJECTIVE_COORDINATES_GFP 117 -#define EC_F_EC_POINT_INVERT 210 -#define EC_F_EC_POINT_IS_AT_INFINITY 118 -#define EC_F_EC_POINT_IS_ON_CURVE 119 -#define EC_F_EC_POINT_MAKE_AFFINE 120 -#define EC_F_EC_POINT_MUL 184 -#define EC_F_EC_POINT_NEW 121 -#define EC_F_EC_POINT_OCT2POINT 122 -#define EC_F_EC_POINT_POINT2OCT 123 -#define EC_F_EC_POINT_SET_AFFINE_COORDINATES_GF2M 185 -#define EC_F_EC_POINT_SET_AFFINE_COORDINATES_GFP 124 -#define EC_F_EC_POINT_SET_COMPRESSED_COORDINATES_GF2M 186 -#define EC_F_EC_POINT_SET_COMPRESSED_COORDINATES_GFP 125 -#define EC_F_EC_POINT_SET_JPROJECTIVE_COORDINATES_GFP 126 -#define EC_F_EC_POINT_SET_TO_INFINITY 127 -#define EC_F_EC_PRE_COMP_DUP 207 -#define EC_F_EC_PRE_COMP_NEW 196 -#define EC_F_EC_WNAF_MUL 187 -#define EC_F_EC_WNAF_PRECOMPUTE_MULT 188 -#define EC_F_I2D_ECPARAMETERS 190 -#define EC_F_I2D_ECPKPARAMETERS 191 -#define EC_F_I2D_ECPRIVATEKEY 192 -#define EC_F_I2O_ECPUBLICKEY 151 -#define EC_F_NISTP224_PRE_COMP_NEW 227 -#define EC_F_NISTP256_PRE_COMP_NEW 236 -#define EC_F_NISTP521_PRE_COMP_NEW 237 -#define EC_F_O2I_ECPUBLICKEY 152 -#define EC_F_OLD_EC_PRIV_DECODE 222 -#define EC_F_PKEY_EC_CTRL 197 -#define EC_F_PKEY_EC_CTRL_STR 198 -#define EC_F_PKEY_EC_DERIVE 217 -#define EC_F_PKEY_EC_KEYGEN 199 -#define EC_F_PKEY_EC_PARAMGEN 219 -#define EC_F_PKEY_EC_SIGN 218 - -/* Reason codes. */ -#define EC_R_ASN1_ERROR 115 -#define EC_R_ASN1_UNKNOWN_FIELD 116 -#define EC_R_BIGNUM_OUT_OF_RANGE 144 -#define EC_R_BUFFER_TOO_SMALL 100 -#define EC_R_COORDINATES_OUT_OF_RANGE 146 -#define EC_R_D2I_ECPKPARAMETERS_FAILURE 117 -#define EC_R_DECODE_ERROR 142 -#define EC_R_DISCRIMINANT_IS_ZERO 118 -#define EC_R_EC_GROUP_NEW_BY_NAME_FAILURE 119 -#define EC_R_FIELD_TOO_LARGE 143 -#define EC_R_GF2M_NOT_SUPPORTED 147 -#define EC_R_GROUP2PKPARAMETERS_FAILURE 120 -#define EC_R_I2D_ECPKPARAMETERS_FAILURE 121 -#define EC_R_INCOMPATIBLE_OBJECTS 101 -#define EC_R_INVALID_ARGUMENT 112 -#define EC_R_INVALID_COMPRESSED_POINT 110 -#define EC_R_INVALID_COMPRESSION_BIT 109 -#define EC_R_INVALID_CURVE 141 -#define EC_R_INVALID_DIGEST_TYPE 138 -#define EC_R_INVALID_ENCODING 102 -#define EC_R_INVALID_FIELD 103 -#define EC_R_INVALID_FORM 104 -#define EC_R_INVALID_GROUP_ORDER 122 -#define EC_R_INVALID_PENTANOMIAL_BASIS 132 -#define EC_R_INVALID_PRIVATE_KEY 123 -#define EC_R_INVALID_TRINOMIAL_BASIS 137 -#define EC_R_KEYS_NOT_SET 140 -#define EC_R_MISSING_PARAMETERS 124 -#define EC_R_MISSING_PRIVATE_KEY 125 -#define EC_R_NOT_A_NIST_PRIME 135 -#define EC_R_NOT_A_SUPPORTED_NIST_PRIME 136 -#define EC_R_NOT_IMPLEMENTED 126 -#define EC_R_NOT_INITIALIZED 111 -#define EC_R_NO_FIELD_MOD 133 -#define EC_R_NO_PARAMETERS_SET 139 -#define EC_R_PASSED_NULL_PARAMETER 134 -#define EC_R_PKPARAMETERS2GROUP_FAILURE 127 -#define EC_R_POINT_AT_INFINITY 106 -#define EC_R_POINT_IS_NOT_ON_CURVE 107 -#define EC_R_SLOT_FULL 108 -#define EC_R_UNDEFINED_GENERATOR 113 -#define EC_R_UNDEFINED_ORDER 128 -#define EC_R_UNKNOWN_GROUP 129 -#define EC_R_UNKNOWN_ORDER 114 -#define EC_R_UNSUPPORTED_FIELD 131 -#define EC_R_WRONG_CURVE_PARAMETERS 145 -#define EC_R_WRONG_ORDER 130 - -#ifdef __cplusplus -} -#endif -#endif diff --git a/src/plugins/ftp/openssl/ecdh.h b/src/plugins/ftp/openssl/ecdh.h deleted file mode 100644 index 8887102c..00000000 --- a/src/plugins/ftp/openssl/ecdh.h +++ /dev/null @@ -1,125 +0,0 @@ -/* crypto/ecdh/ecdh.h */ -/* ==================================================================== - * Copyright 2002 Sun Microsystems, Inc. ALL RIGHTS RESERVED. - * - * The Elliptic Curve Public-Key Crypto Library (ECC Code) included - * herein is developed by SUN MICROSYSTEMS, INC., and is contributed - * to the OpenSSL project. - * - * The ECC Code is licensed pursuant to the OpenSSL open source - * license provided below. - * - * The ECDH software is originally written by Douglas Stebila of - * Sun Microsystems Laboratories. - * - */ -/* ==================================================================== - * Copyright (c) 2000-2002 The OpenSSL Project. All rights reserved. - * - * 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. All advertising materials mentioning features or use of this - * software must display the following acknowledgment: - * "This product includes software developed by the OpenSSL Project - * for use in the OpenSSL Toolkit. (http://www.OpenSSL.org/)" - * - * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For written permission, please contact - * licensing@OpenSSL.org. - * - * 5. Products derived from this software may not be called "OpenSSL" - * nor may "OpenSSL" appear in their names without prior written - * permission of the OpenSSL Project. - * - * 6. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by the OpenSSL Project - * for use in the OpenSSL Toolkit (http://www.OpenSSL.org/)" - * - * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY - * EXPRESSED 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 OpenSSL PROJECT OR - * ITS 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. - * ==================================================================== - * - * This product includes cryptographic software written by Eric Young - * (eay@cryptsoft.com). This product includes software written by Tim - * Hudson (tjh@cryptsoft.com). - * - */ -#ifndef HEADER_ECDH_H -#define HEADER_ECDH_H - -#include - -#ifdef OPENSSL_NO_ECDH -#error ECDH is disabled. -#endif - -#include -#include -#ifndef OPENSSL_NO_DEPRECATED -#include -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -const ECDH_METHOD *ECDH_OpenSSL(void); - -void ECDH_set_default_method(const ECDH_METHOD *); -const ECDH_METHOD *ECDH_get_default_method(void); -int ECDH_set_method(EC_KEY *, const ECDH_METHOD *); - -int ECDH_compute_key(void *out, size_t outlen, const EC_POINT *pub_key, EC_KEY *ecdh, - void *(*KDF)(const void *in, size_t inlen, void *out, size_t *outlen)); - -int ECDH_get_ex_new_index(long argl, void *argp, CRYPTO_EX_new - *new_func, CRYPTO_EX_dup *dup_func, CRYPTO_EX_free *free_func); -int ECDH_set_ex_data(EC_KEY *d, int idx, void *arg); -void *ECDH_get_ex_data(EC_KEY *d, int idx); - - -/* BEGIN ERROR CODES */ -/* The following lines are auto generated by the script mkerr.pl. Any changes - * made after this point may be overwritten when the script is next run. - */ -void ERR_load_ECDH_strings(void); - -/* Error codes for the ECDH functions. */ - -/* Function codes. */ -#define ECDH_F_ECDH_CHECK 102 -#define ECDH_F_ECDH_COMPUTE_KEY 100 -#define ECDH_F_ECDH_DATA_NEW_METHOD 101 - -/* Reason codes. */ -#define ECDH_R_KDF_FAILED 102 -#define ECDH_R_NON_FIPS_METHOD 103 -#define ECDH_R_NO_PRIVATE_VALUE 100 -#define ECDH_R_POINT_ARITHMETIC_FAILURE 101 - -#ifdef __cplusplus -} -#endif -#endif diff --git a/src/plugins/ftp/openssl/ecdsa.h b/src/plugins/ftp/openssl/ecdsa.h deleted file mode 100644 index 7fb5254b..00000000 --- a/src/plugins/ftp/openssl/ecdsa.h +++ /dev/null @@ -1,260 +0,0 @@ -/* crypto/ecdsa/ecdsa.h */ -/** - * \file crypto/ecdsa/ecdsa.h Include file for the OpenSSL ECDSA functions - * \author Written by Nils Larsch for the OpenSSL project - */ -/* ==================================================================== - * Copyright (c) 2000-2005 The OpenSSL Project. All rights reserved. - * - * 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. All advertising materials mentioning features or use of this - * software must display the following acknowledgment: - * "This product includes software developed by the OpenSSL Project - * for use in the OpenSSL Toolkit. (http://www.OpenSSL.org/)" - * - * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For written permission, please contact - * licensing@OpenSSL.org. - * - * 5. Products derived from this software may not be called "OpenSSL" - * nor may "OpenSSL" appear in their names without prior written - * permission of the OpenSSL Project. - * - * 6. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by the OpenSSL Project - * for use in the OpenSSL Toolkit (http://www.OpenSSL.org/)" - * - * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY - * EXPRESSED 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 OpenSSL PROJECT OR - * ITS 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. - * ==================================================================== - * - * This product includes cryptographic software written by Eric Young - * (eay@cryptsoft.com). This product includes software written by Tim - * Hudson (tjh@cryptsoft.com). - * - */ -#ifndef HEADER_ECDSA_H -#define HEADER_ECDSA_H - -#include - -#ifdef OPENSSL_NO_ECDSA -#error ECDSA is disabled. -#endif - -#include -#include -#ifndef OPENSSL_NO_DEPRECATED -#include -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct ECDSA_SIG_st - { - BIGNUM *r; - BIGNUM *s; - } ECDSA_SIG; - -/** Allocates and initialize a ECDSA_SIG structure - * \return pointer to a ECDSA_SIG structure or NULL if an error occurred - */ -ECDSA_SIG *ECDSA_SIG_new(void); - -/** frees a ECDSA_SIG structure - * \param sig pointer to the ECDSA_SIG structure - */ -void ECDSA_SIG_free(ECDSA_SIG *sig); - -/** DER encode content of ECDSA_SIG object (note: this function modifies *pp - * (*pp += length of the DER encoded signature)). - * \param sig pointer to the ECDSA_SIG object - * \param pp pointer to a unsigned char pointer for the output or NULL - * \return the length of the DER encoded ECDSA_SIG object or 0 - */ -int i2d_ECDSA_SIG(const ECDSA_SIG *sig, unsigned char **pp); - -/** Decodes a DER encoded ECDSA signature (note: this function changes *pp - * (*pp += len)). - * \param sig pointer to ECDSA_SIG pointer (may be NULL) - * \param pp memory buffer with the DER encoded signature - * \param len length of the buffer - * \return pointer to the decoded ECDSA_SIG structure (or NULL) - */ -ECDSA_SIG *d2i_ECDSA_SIG(ECDSA_SIG **sig, const unsigned char **pp, long len); - -/** Computes the ECDSA signature of the given hash value using - * the supplied private key and returns the created signature. - * \param dgst pointer to the hash value - * \param dgst_len length of the hash value - * \param eckey EC_KEY object containing a private EC key - * \return pointer to a ECDSA_SIG structure or NULL if an error occurred - */ -ECDSA_SIG *ECDSA_do_sign(const unsigned char *dgst,int dgst_len,EC_KEY *eckey); - -/** Computes ECDSA signature of a given hash value using the supplied - * private key (note: sig must point to ECDSA_size(eckey) bytes of memory). - * \param dgst pointer to the hash value to sign - * \param dgstlen length of the hash value - * \param kinv BIGNUM with a pre-computed inverse k (optional) - * \param rp BIGNUM with a pre-computed rp value (optioanl), - * see ECDSA_sign_setup - * \param eckey EC_KEY object containing a private EC key - * \return pointer to a ECDSA_SIG structure or NULL if an error occurred - */ -ECDSA_SIG *ECDSA_do_sign_ex(const unsigned char *dgst, int dgstlen, - const BIGNUM *kinv, const BIGNUM *rp, EC_KEY *eckey); - -/** Verifies that the supplied signature is a valid ECDSA - * signature of the supplied hash value using the supplied public key. - * \param dgst pointer to the hash value - * \param dgst_len length of the hash value - * \param sig ECDSA_SIG structure - * \param eckey EC_KEY object containing a public EC key - * \return 1 if the signature is valid, 0 if the signature is invalid - * and -1 on error - */ -int ECDSA_do_verify(const unsigned char *dgst, int dgst_len, - const ECDSA_SIG *sig, EC_KEY* eckey); - -const ECDSA_METHOD *ECDSA_OpenSSL(void); - -/** Sets the default ECDSA method - * \param meth new default ECDSA_METHOD - */ -void ECDSA_set_default_method(const ECDSA_METHOD *meth); - -/** Returns the default ECDSA method - * \return pointer to ECDSA_METHOD structure containing the default method - */ -const ECDSA_METHOD *ECDSA_get_default_method(void); - -/** Sets method to be used for the ECDSA operations - * \param eckey EC_KEY object - * \param meth new method - * \return 1 on success and 0 otherwise - */ -int ECDSA_set_method(EC_KEY *eckey, const ECDSA_METHOD *meth); - -/** Returns the maximum length of the DER encoded signature - * \param eckey EC_KEY object - * \return numbers of bytes required for the DER encoded signature - */ -int ECDSA_size(const EC_KEY *eckey); - -/** Precompute parts of the signing operation - * \param eckey EC_KEY object containing a private EC key - * \param ctx BN_CTX object (optional) - * \param kinv BIGNUM pointer for the inverse of k - * \param rp BIGNUM pointer for x coordinate of k * generator - * \return 1 on success and 0 otherwise - */ -int ECDSA_sign_setup(EC_KEY *eckey, BN_CTX *ctx, BIGNUM **kinv, - BIGNUM **rp); - -/** Computes ECDSA signature of a given hash value using the supplied - * private key (note: sig must point to ECDSA_size(eckey) bytes of memory). - * \param type this parameter is ignored - * \param dgst pointer to the hash value to sign - * \param dgstlen length of the hash value - * \param sig memory for the DER encoded created signature - * \param siglen pointer to the length of the returned signature - * \param eckey EC_KEY object containing a private EC key - * \return 1 on success and 0 otherwise - */ -int ECDSA_sign(int type, const unsigned char *dgst, int dgstlen, - unsigned char *sig, unsigned int *siglen, EC_KEY *eckey); - - -/** Computes ECDSA signature of a given hash value using the supplied - * private key (note: sig must point to ECDSA_size(eckey) bytes of memory). - * \param type this parameter is ignored - * \param dgst pointer to the hash value to sign - * \param dgstlen length of the hash value - * \param sig buffer to hold the DER encoded signature - * \param siglen pointer to the length of the returned signature - * \param kinv BIGNUM with a pre-computed inverse k (optional) - * \param rp BIGNUM with a pre-computed rp value (optioanl), - * see ECDSA_sign_setup - * \param eckey EC_KEY object containing a private EC key - * \return 1 on success and 0 otherwise - */ -int ECDSA_sign_ex(int type, const unsigned char *dgst, int dgstlen, - unsigned char *sig, unsigned int *siglen, const BIGNUM *kinv, - const BIGNUM *rp, EC_KEY *eckey); - -/** Verifies that the given signature is valid ECDSA signature - * of the supplied hash value using the specified public key. - * \param type this parameter is ignored - * \param dgst pointer to the hash value - * \param dgstlen length of the hash value - * \param sig pointer to the DER encoded signature - * \param siglen length of the DER encoded signature - * \param eckey EC_KEY object containing a public EC key - * \return 1 if the signature is valid, 0 if the signature is invalid - * and -1 on error - */ -int ECDSA_verify(int type, const unsigned char *dgst, int dgstlen, - const unsigned char *sig, int siglen, EC_KEY *eckey); - -/* the standard ex_data functions */ -int ECDSA_get_ex_new_index(long argl, void *argp, CRYPTO_EX_new - *new_func, CRYPTO_EX_dup *dup_func, CRYPTO_EX_free *free_func); -int ECDSA_set_ex_data(EC_KEY *d, int idx, void *arg); -void *ECDSA_get_ex_data(EC_KEY *d, int idx); - - -/* BEGIN ERROR CODES */ -/* The following lines are auto generated by the script mkerr.pl. Any changes - * made after this point may be overwritten when the script is next run. - */ -void ERR_load_ECDSA_strings(void); - -/* Error codes for the ECDSA functions. */ - -/* Function codes. */ -#define ECDSA_F_ECDSA_CHECK 104 -#define ECDSA_F_ECDSA_DATA_NEW_METHOD 100 -#define ECDSA_F_ECDSA_DO_SIGN 101 -#define ECDSA_F_ECDSA_DO_VERIFY 102 -#define ECDSA_F_ECDSA_SIGN_SETUP 103 - -/* Reason codes. */ -#define ECDSA_R_BAD_SIGNATURE 100 -#define ECDSA_R_DATA_TOO_LARGE_FOR_KEY_SIZE 101 -#define ECDSA_R_ERR_EC_LIB 102 -#define ECDSA_R_MISSING_PARAMETERS 103 -#define ECDSA_R_NEED_NEW_SETUP_VALUES 106 -#define ECDSA_R_NON_FIPS_METHOD 107 -#define ECDSA_R_RANDOM_NUMBER_GENERATION_FAILED 104 -#define ECDSA_R_SIGNATURE_MALLOC_FAILED 105 - -#ifdef __cplusplus -} -#endif -#endif diff --git a/src/plugins/ftp/openssl/evp.h b/src/plugins/ftp/openssl/evp.h deleted file mode 100644 index 0d1b20a7..00000000 --- a/src/plugins/ftp/openssl/evp.h +++ /dev/null @@ -1,1402 +0,0 @@ -/* crypto/evp/evp.h */ -/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) - * All rights reserved. - * - * This package is an SSL implementation written - * by Eric Young (eay@cryptsoft.com). - * The implementation was written so as to conform with Netscapes SSL. - * - * This library is free for commercial and non-commercial use as long as - * the following conditions are aheared to. The following conditions - * apply to all code found in this distribution, be it the RC4, RSA, - * lhash, DES, etc., code; not just the SSL code. The SSL documentation - * included with this distribution is covered by the same copyright terms - * except that the holder is Tim Hudson (tjh@cryptsoft.com). - * - * Copyright remains Eric Young's, and as such any Copyright notices in - * the code are not to be removed. - * If this package is used in a product, Eric Young should be given attribution - * as the author of the parts of the library used. - * This can be in the form of a textual message at program startup or - * in documentation (online or textual) provided with the package. - * - * 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 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. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * "This product includes cryptographic software written by - * Eric Young (eay@cryptsoft.com)" - * The word 'cryptographic' can be left out if the rouines from the library - * being used are not cryptographic related :-). - * 4. If you include any Windows specific code (or a derivative thereof) from - * the apps directory (application code) you must include an acknowledgement: - * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" - * - * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``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 AUTHOR 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. - * - * The licence and distribution terms for any publically available version or - * derivative of this code cannot be changed. i.e. this code cannot simply be - * copied and put under another distribution licence - * [including the GNU Public Licence.] - */ - -#ifndef HEADER_ENVELOPE_H -#define HEADER_ENVELOPE_H - -#ifdef OPENSSL_ALGORITHM_DEFINES -# include -#else -# define OPENSSL_ALGORITHM_DEFINES -# include -# undef OPENSSL_ALGORITHM_DEFINES -#endif - -#include - -#include - -#ifndef OPENSSL_NO_BIO -#include -#endif - -/* -#define EVP_RC2_KEY_SIZE 16 -#define EVP_RC4_KEY_SIZE 16 -#define EVP_BLOWFISH_KEY_SIZE 16 -#define EVP_CAST5_KEY_SIZE 16 -#define EVP_RC5_32_12_16_KEY_SIZE 16 -*/ -#define EVP_MAX_MD_SIZE 64 /* longest known is SHA512 */ -#define EVP_MAX_KEY_LENGTH 64 -#define EVP_MAX_IV_LENGTH 16 -#define EVP_MAX_BLOCK_LENGTH 32 - -#define PKCS5_SALT_LEN 8 -/* Default PKCS#5 iteration count */ -#define PKCS5_DEFAULT_ITER 2048 - -#include - -#define EVP_PK_RSA 0x0001 -#define EVP_PK_DSA 0x0002 -#define EVP_PK_DH 0x0004 -#define EVP_PK_EC 0x0008 -#define EVP_PKT_SIGN 0x0010 -#define EVP_PKT_ENC 0x0020 -#define EVP_PKT_EXCH 0x0040 -#define EVP_PKS_RSA 0x0100 -#define EVP_PKS_DSA 0x0200 -#define EVP_PKS_EC 0x0400 -#define EVP_PKT_EXP 0x1000 /* <= 512 bit key */ - -#define EVP_PKEY_NONE NID_undef -#define EVP_PKEY_RSA NID_rsaEncryption -#define EVP_PKEY_RSA2 NID_rsa -#define EVP_PKEY_DSA NID_dsa -#define EVP_PKEY_DSA1 NID_dsa_2 -#define EVP_PKEY_DSA2 NID_dsaWithSHA -#define EVP_PKEY_DSA3 NID_dsaWithSHA1 -#define EVP_PKEY_DSA4 NID_dsaWithSHA1_2 -#define EVP_PKEY_DH NID_dhKeyAgreement -#define EVP_PKEY_EC NID_X9_62_id_ecPublicKey -#define EVP_PKEY_HMAC NID_hmac -#define EVP_PKEY_CMAC NID_cmac - -#ifdef __cplusplus -extern "C" { -#endif - -/* Type needs to be a bit field - * Sub-type needs to be for variations on the method, as in, can it do - * arbitrary encryption.... */ -struct evp_pkey_st - { - int type; - int save_type; - int references; - const EVP_PKEY_ASN1_METHOD *ameth; - ENGINE *engine; - union { - char *ptr; -#ifndef OPENSSL_NO_RSA - struct rsa_st *rsa; /* RSA */ -#endif -#ifndef OPENSSL_NO_DSA - struct dsa_st *dsa; /* DSA */ -#endif -#ifndef OPENSSL_NO_DH - struct dh_st *dh; /* DH */ -#endif -#ifndef OPENSSL_NO_EC - struct ec_key_st *ec; /* ECC */ -#endif - } pkey; - int save_parameters; - STACK_OF(X509_ATTRIBUTE) *attributes; /* [ 0 ] */ - } /* EVP_PKEY */; - -#define EVP_PKEY_MO_SIGN 0x0001 -#define EVP_PKEY_MO_VERIFY 0x0002 -#define EVP_PKEY_MO_ENCRYPT 0x0004 -#define EVP_PKEY_MO_DECRYPT 0x0008 - -#ifndef EVP_MD -struct env_md_st - { - int type; - int pkey_type; - int md_size; - unsigned long flags; - int (*init)(EVP_MD_CTX *ctx); - int (*update)(EVP_MD_CTX *ctx,const void *data,size_t count); - int (*final)(EVP_MD_CTX *ctx,unsigned char *md); - int (*copy)(EVP_MD_CTX *to,const EVP_MD_CTX *from); - int (*cleanup)(EVP_MD_CTX *ctx); - - /* FIXME: prototype these some day */ - int (*sign)(int type, const unsigned char *m, unsigned int m_length, - unsigned char *sigret, unsigned int *siglen, void *key); - int (*verify)(int type, const unsigned char *m, unsigned int m_length, - const unsigned char *sigbuf, unsigned int siglen, - void *key); - int required_pkey_type[5]; /*EVP_PKEY_xxx */ - int block_size; - int ctx_size; /* how big does the ctx->md_data need to be */ - /* control function */ - int (*md_ctrl)(EVP_MD_CTX *ctx, int cmd, int p1, void *p2); - } /* EVP_MD */; - -typedef int evp_sign_method(int type,const unsigned char *m, - unsigned int m_length,unsigned char *sigret, - unsigned int *siglen, void *key); -typedef int evp_verify_method(int type,const unsigned char *m, - unsigned int m_length,const unsigned char *sigbuf, - unsigned int siglen, void *key); - -#define EVP_MD_FLAG_ONESHOT 0x0001 /* digest can only handle a single - * block */ - -#define EVP_MD_FLAG_PKEY_DIGEST 0x0002 /* digest is a "clone" digest used - * which is a copy of an existing - * one for a specific public key type. - * EVP_dss1() etc */ - -/* Digest uses EVP_PKEY_METHOD for signing instead of MD specific signing */ - -#define EVP_MD_FLAG_PKEY_METHOD_SIGNATURE 0x0004 - -/* DigestAlgorithmIdentifier flags... */ - -#define EVP_MD_FLAG_DIGALGID_MASK 0x0018 - -/* NULL or absent parameter accepted. Use NULL */ - -#define EVP_MD_FLAG_DIGALGID_NULL 0x0000 - -/* NULL or absent parameter accepted. Use NULL for PKCS#1 otherwise absent */ - -#define EVP_MD_FLAG_DIGALGID_ABSENT 0x0008 - -/* Custom handling via ctrl */ - -#define EVP_MD_FLAG_DIGALGID_CUSTOM 0x0018 - -#define EVP_MD_FLAG_FIPS 0x0400 /* Note if suitable for use in FIPS mode */ - -/* Digest ctrls */ - -#define EVP_MD_CTRL_DIGALGID 0x1 -#define EVP_MD_CTRL_MICALG 0x2 - -/* Minimum Algorithm specific ctrl value */ - -#define EVP_MD_CTRL_ALG_CTRL 0x1000 - -#define EVP_PKEY_NULL_method NULL,NULL,{0,0,0,0} - -#ifndef OPENSSL_NO_DSA -#define EVP_PKEY_DSA_method (evp_sign_method *)DSA_sign, \ - (evp_verify_method *)DSA_verify, \ - {EVP_PKEY_DSA,EVP_PKEY_DSA2,EVP_PKEY_DSA3, \ - EVP_PKEY_DSA4,0} -#else -#define EVP_PKEY_DSA_method EVP_PKEY_NULL_method -#endif - -#ifndef OPENSSL_NO_ECDSA -#define EVP_PKEY_ECDSA_method (evp_sign_method *)ECDSA_sign, \ - (evp_verify_method *)ECDSA_verify, \ - {EVP_PKEY_EC,0,0,0} -#else -#define EVP_PKEY_ECDSA_method EVP_PKEY_NULL_method -#endif - -#ifndef OPENSSL_NO_RSA -#define EVP_PKEY_RSA_method (evp_sign_method *)RSA_sign, \ - (evp_verify_method *)RSA_verify, \ - {EVP_PKEY_RSA,EVP_PKEY_RSA2,0,0} -#define EVP_PKEY_RSA_ASN1_OCTET_STRING_method \ - (evp_sign_method *)RSA_sign_ASN1_OCTET_STRING, \ - (evp_verify_method *)RSA_verify_ASN1_OCTET_STRING, \ - {EVP_PKEY_RSA,EVP_PKEY_RSA2,0,0} -#else -#define EVP_PKEY_RSA_method EVP_PKEY_NULL_method -#define EVP_PKEY_RSA_ASN1_OCTET_STRING_method EVP_PKEY_NULL_method -#endif - -#endif /* !EVP_MD */ - -struct env_md_ctx_st - { - const EVP_MD *digest; - ENGINE *engine; /* functional reference if 'digest' is ENGINE-provided */ - unsigned long flags; - void *md_data; - /* Public key context for sign/verify */ - EVP_PKEY_CTX *pctx; - /* Update function: usually copied from EVP_MD */ - int (*update)(EVP_MD_CTX *ctx,const void *data,size_t count); - } /* EVP_MD_CTX */; - -/* values for EVP_MD_CTX flags */ - -#define EVP_MD_CTX_FLAG_ONESHOT 0x0001 /* digest update will be called - * once only */ -#define EVP_MD_CTX_FLAG_CLEANED 0x0002 /* context has already been - * cleaned */ -#define EVP_MD_CTX_FLAG_REUSE 0x0004 /* Don't free up ctx->md_data - * in EVP_MD_CTX_cleanup */ -/* FIPS and pad options are ignored in 1.0.0, definitions are here - * so we don't accidentally reuse the values for other purposes. - */ - -#define EVP_MD_CTX_FLAG_NON_FIPS_ALLOW 0x0008 /* Allow use of non FIPS digest - * in FIPS mode */ - -/* The following PAD options are also currently ignored in 1.0.0, digest - * parameters are handled through EVP_DigestSign*() and EVP_DigestVerify*() - * instead. - */ -#define EVP_MD_CTX_FLAG_PAD_MASK 0xF0 /* RSA mode to use */ -#define EVP_MD_CTX_FLAG_PAD_PKCS1 0x00 /* PKCS#1 v1.5 mode */ -#define EVP_MD_CTX_FLAG_PAD_X931 0x10 /* X9.31 mode */ -#define EVP_MD_CTX_FLAG_PAD_PSS 0x20 /* PSS mode */ - -#define EVP_MD_CTX_FLAG_NO_INIT 0x0100 /* Don't initialize md_data */ - -struct evp_cipher_st - { - int nid; - int block_size; - int key_len; /* Default value for variable length ciphers */ - int iv_len; - unsigned long flags; /* Various flags */ - int (*init)(EVP_CIPHER_CTX *ctx, const unsigned char *key, - const unsigned char *iv, int enc); /* init key */ - int (*do_cipher)(EVP_CIPHER_CTX *ctx, unsigned char *out, - const unsigned char *in, size_t inl);/* encrypt/decrypt data */ - int (*cleanup)(EVP_CIPHER_CTX *); /* cleanup ctx */ - int ctx_size; /* how big ctx->cipher_data needs to be */ - int (*set_asn1_parameters)(EVP_CIPHER_CTX *, ASN1_TYPE *); /* Populate a ASN1_TYPE with parameters */ - int (*get_asn1_parameters)(EVP_CIPHER_CTX *, ASN1_TYPE *); /* Get parameters from a ASN1_TYPE */ - int (*ctrl)(EVP_CIPHER_CTX *, int type, int arg, void *ptr); /* Miscellaneous operations */ - void *app_data; /* Application data */ - } /* EVP_CIPHER */; - -/* Values for cipher flags */ - -/* Modes for ciphers */ - -#define EVP_CIPH_STREAM_CIPHER 0x0 -#define EVP_CIPH_ECB_MODE 0x1 -#define EVP_CIPH_CBC_MODE 0x2 -#define EVP_CIPH_CFB_MODE 0x3 -#define EVP_CIPH_OFB_MODE 0x4 -#define EVP_CIPH_CTR_MODE 0x5 -#define EVP_CIPH_GCM_MODE 0x6 -#define EVP_CIPH_CCM_MODE 0x7 -#define EVP_CIPH_XTS_MODE 0x10001 -#define EVP_CIPH_MODE 0xF0007 -/* Set if variable length cipher */ -#define EVP_CIPH_VARIABLE_LENGTH 0x8 -/* Set if the iv handling should be done by the cipher itself */ -#define EVP_CIPH_CUSTOM_IV 0x10 -/* Set if the cipher's init() function should be called if key is NULL */ -#define EVP_CIPH_ALWAYS_CALL_INIT 0x20 -/* Call ctrl() to init cipher parameters */ -#define EVP_CIPH_CTRL_INIT 0x40 -/* Don't use standard key length function */ -#define EVP_CIPH_CUSTOM_KEY_LENGTH 0x80 -/* Don't use standard block padding */ -#define EVP_CIPH_NO_PADDING 0x100 -/* cipher handles random key generation */ -#define EVP_CIPH_RAND_KEY 0x200 -/* cipher has its own additional copying logic */ -#define EVP_CIPH_CUSTOM_COPY 0x400 -/* Allow use default ASN1 get/set iv */ -#define EVP_CIPH_FLAG_DEFAULT_ASN1 0x1000 -/* Buffer length in bits not bytes: CFB1 mode only */ -#define EVP_CIPH_FLAG_LENGTH_BITS 0x2000 -/* Note if suitable for use in FIPS mode */ -#define EVP_CIPH_FLAG_FIPS 0x4000 -/* Allow non FIPS cipher in FIPS mode */ -#define EVP_CIPH_FLAG_NON_FIPS_ALLOW 0x8000 -/* Cipher handles any and all padding logic as well - * as finalisation. - */ -#define EVP_CIPH_FLAG_CUSTOM_CIPHER 0x100000 -#define EVP_CIPH_FLAG_AEAD_CIPHER 0x200000 - -/* ctrl() values */ - -#define EVP_CTRL_INIT 0x0 -#define EVP_CTRL_SET_KEY_LENGTH 0x1 -#define EVP_CTRL_GET_RC2_KEY_BITS 0x2 -#define EVP_CTRL_SET_RC2_KEY_BITS 0x3 -#define EVP_CTRL_GET_RC5_ROUNDS 0x4 -#define EVP_CTRL_SET_RC5_ROUNDS 0x5 -#define EVP_CTRL_RAND_KEY 0x6 -#define EVP_CTRL_PBE_PRF_NID 0x7 -#define EVP_CTRL_COPY 0x8 -#define EVP_CTRL_GCM_SET_IVLEN 0x9 -#define EVP_CTRL_GCM_GET_TAG 0x10 -#define EVP_CTRL_GCM_SET_TAG 0x11 -#define EVP_CTRL_GCM_SET_IV_FIXED 0x12 -#define EVP_CTRL_GCM_IV_GEN 0x13 -#define EVP_CTRL_CCM_SET_IVLEN EVP_CTRL_GCM_SET_IVLEN -#define EVP_CTRL_CCM_GET_TAG EVP_CTRL_GCM_GET_TAG -#define EVP_CTRL_CCM_SET_TAG EVP_CTRL_GCM_SET_TAG -#define EVP_CTRL_CCM_SET_L 0x14 -#define EVP_CTRL_CCM_SET_MSGLEN 0x15 -/* AEAD cipher deduces payload length and returns number of bytes - * required to store MAC and eventual padding. Subsequent call to - * EVP_Cipher even appends/verifies MAC. - */ -#define EVP_CTRL_AEAD_TLS1_AAD 0x16 -/* Used by composite AEAD ciphers, no-op in GCM, CCM... */ -#define EVP_CTRL_AEAD_SET_MAC_KEY 0x17 -/* Set the GCM invocation field, decrypt only */ -#define EVP_CTRL_GCM_SET_IV_INV 0x18 - -/* GCM TLS constants */ -/* Length of fixed part of IV derived from PRF */ -#define EVP_GCM_TLS_FIXED_IV_LEN 4 -/* Length of explicit part of IV part of TLS records */ -#define EVP_GCM_TLS_EXPLICIT_IV_LEN 8 -/* Length of tag for TLS */ -#define EVP_GCM_TLS_TAG_LEN 16 - - -typedef struct evp_cipher_info_st - { - const EVP_CIPHER *cipher; - unsigned char iv[EVP_MAX_IV_LENGTH]; - } EVP_CIPHER_INFO; - -struct evp_cipher_ctx_st - { - const EVP_CIPHER *cipher; - ENGINE *engine; /* functional reference if 'cipher' is ENGINE-provided */ - int encrypt; /* encrypt or decrypt */ - int buf_len; /* number we have left */ - - unsigned char oiv[EVP_MAX_IV_LENGTH]; /* original iv */ - unsigned char iv[EVP_MAX_IV_LENGTH]; /* working iv */ - unsigned char buf[EVP_MAX_BLOCK_LENGTH];/* saved partial block */ - int num; /* used by cfb/ofb/ctr mode */ - - void *app_data; /* application stuff */ - int key_len; /* May change for variable length cipher */ - unsigned long flags; /* Various flags */ - void *cipher_data; /* per EVP data */ - int final_used; - int block_mask; - unsigned char final[EVP_MAX_BLOCK_LENGTH];/* possible final block */ - } /* EVP_CIPHER_CTX */; - -typedef struct evp_Encode_Ctx_st - { - int num; /* number saved in a partial encode/decode */ - int length; /* The length is either the output line length - * (in input bytes) or the shortest input line - * length that is ok. Once decoding begins, - * the length is adjusted up each time a longer - * line is decoded */ - unsigned char enc_data[80]; /* data to encode */ - int line_num; /* number read on current line */ - int expect_nl; - } EVP_ENCODE_CTX; - -/* Password based encryption function */ -typedef int (EVP_PBE_KEYGEN)(EVP_CIPHER_CTX *ctx, const char *pass, int passlen, - ASN1_TYPE *param, const EVP_CIPHER *cipher, - const EVP_MD *md, int en_de); - -#ifndef OPENSSL_NO_RSA -#define EVP_PKEY_assign_RSA(pkey,rsa) EVP_PKEY_assign((pkey),EVP_PKEY_RSA,\ - (char *)(rsa)) -#endif - -#ifndef OPENSSL_NO_DSA -#define EVP_PKEY_assign_DSA(pkey,dsa) EVP_PKEY_assign((pkey),EVP_PKEY_DSA,\ - (char *)(dsa)) -#endif - -#ifndef OPENSSL_NO_DH -#define EVP_PKEY_assign_DH(pkey,dh) EVP_PKEY_assign((pkey),EVP_PKEY_DH,\ - (char *)(dh)) -#endif - -#ifndef OPENSSL_NO_EC -#define EVP_PKEY_assign_EC_KEY(pkey,eckey) EVP_PKEY_assign((pkey),EVP_PKEY_EC,\ - (char *)(eckey)) -#endif - -/* Add some extra combinations */ -#define EVP_get_digestbynid(a) EVP_get_digestbyname(OBJ_nid2sn(a)) -#define EVP_get_digestbyobj(a) EVP_get_digestbynid(OBJ_obj2nid(a)) -#define EVP_get_cipherbynid(a) EVP_get_cipherbyname(OBJ_nid2sn(a)) -#define EVP_get_cipherbyobj(a) EVP_get_cipherbynid(OBJ_obj2nid(a)) - -int EVP_MD_type(const EVP_MD *md); -#define EVP_MD_nid(e) EVP_MD_type(e) -#define EVP_MD_name(e) OBJ_nid2sn(EVP_MD_nid(e)) -int EVP_MD_pkey_type(const EVP_MD *md); -int EVP_MD_size(const EVP_MD *md); -int EVP_MD_block_size(const EVP_MD *md); -unsigned long EVP_MD_flags(const EVP_MD *md); - -const EVP_MD *EVP_MD_CTX_md(const EVP_MD_CTX *ctx); -#define EVP_MD_CTX_size(e) EVP_MD_size(EVP_MD_CTX_md(e)) -#define EVP_MD_CTX_block_size(e) EVP_MD_block_size(EVP_MD_CTX_md(e)) -#define EVP_MD_CTX_type(e) EVP_MD_type(EVP_MD_CTX_md(e)) - -int EVP_CIPHER_nid(const EVP_CIPHER *cipher); -#define EVP_CIPHER_name(e) OBJ_nid2sn(EVP_CIPHER_nid(e)) -int EVP_CIPHER_block_size(const EVP_CIPHER *cipher); -int EVP_CIPHER_key_length(const EVP_CIPHER *cipher); -int EVP_CIPHER_iv_length(const EVP_CIPHER *cipher); -unsigned long EVP_CIPHER_flags(const EVP_CIPHER *cipher); -#define EVP_CIPHER_mode(e) (EVP_CIPHER_flags(e) & EVP_CIPH_MODE) - -const EVP_CIPHER * EVP_CIPHER_CTX_cipher(const EVP_CIPHER_CTX *ctx); -int EVP_CIPHER_CTX_nid(const EVP_CIPHER_CTX *ctx); -int EVP_CIPHER_CTX_block_size(const EVP_CIPHER_CTX *ctx); -int EVP_CIPHER_CTX_key_length(const EVP_CIPHER_CTX *ctx); -int EVP_CIPHER_CTX_iv_length(const EVP_CIPHER_CTX *ctx); -int EVP_CIPHER_CTX_copy(EVP_CIPHER_CTX *out, const EVP_CIPHER_CTX *in); -void * EVP_CIPHER_CTX_get_app_data(const EVP_CIPHER_CTX *ctx); -void EVP_CIPHER_CTX_set_app_data(EVP_CIPHER_CTX *ctx, void *data); -#define EVP_CIPHER_CTX_type(c) EVP_CIPHER_type(EVP_CIPHER_CTX_cipher(c)) -unsigned long EVP_CIPHER_CTX_flags(const EVP_CIPHER_CTX *ctx); -#define EVP_CIPHER_CTX_mode(e) (EVP_CIPHER_CTX_flags(e) & EVP_CIPH_MODE) - -#define EVP_ENCODE_LENGTH(l) (((l+2)/3*4)+(l/48+1)*2+80) -#define EVP_DECODE_LENGTH(l) ((l+3)/4*3+80) - -#define EVP_SignInit_ex(a,b,c) EVP_DigestInit_ex(a,b,c) -#define EVP_SignInit(a,b) EVP_DigestInit(a,b) -#define EVP_SignUpdate(a,b,c) EVP_DigestUpdate(a,b,c) -#define EVP_VerifyInit_ex(a,b,c) EVP_DigestInit_ex(a,b,c) -#define EVP_VerifyInit(a,b) EVP_DigestInit(a,b) -#define EVP_VerifyUpdate(a,b,c) EVP_DigestUpdate(a,b,c) -#define EVP_OpenUpdate(a,b,c,d,e) EVP_DecryptUpdate(a,b,c,d,e) -#define EVP_SealUpdate(a,b,c,d,e) EVP_EncryptUpdate(a,b,c,d,e) -#define EVP_DigestSignUpdate(a,b,c) EVP_DigestUpdate(a,b,c) -#define EVP_DigestVerifyUpdate(a,b,c) EVP_DigestUpdate(a,b,c) - -#ifdef CONST_STRICT -void BIO_set_md(BIO *,const EVP_MD *md); -#else -# define BIO_set_md(b,md) BIO_ctrl(b,BIO_C_SET_MD,0,(char *)md) -#endif -#define BIO_get_md(b,mdp) BIO_ctrl(b,BIO_C_GET_MD,0,(char *)mdp) -#define BIO_get_md_ctx(b,mdcp) BIO_ctrl(b,BIO_C_GET_MD_CTX,0,(char *)mdcp) -#define BIO_set_md_ctx(b,mdcp) BIO_ctrl(b,BIO_C_SET_MD_CTX,0,(char *)mdcp) -#define BIO_get_cipher_status(b) BIO_ctrl(b,BIO_C_GET_CIPHER_STATUS,0,NULL) -#define BIO_get_cipher_ctx(b,c_pp) BIO_ctrl(b,BIO_C_GET_CIPHER_CTX,0,(char *)c_pp) - -int EVP_Cipher(EVP_CIPHER_CTX *c, - unsigned char *out, - const unsigned char *in, - unsigned int inl); - -#define EVP_add_cipher_alias(n,alias) \ - OBJ_NAME_add((alias),OBJ_NAME_TYPE_CIPHER_METH|OBJ_NAME_ALIAS,(n)) -#define EVP_add_digest_alias(n,alias) \ - OBJ_NAME_add((alias),OBJ_NAME_TYPE_MD_METH|OBJ_NAME_ALIAS,(n)) -#define EVP_delete_cipher_alias(alias) \ - OBJ_NAME_remove(alias,OBJ_NAME_TYPE_CIPHER_METH|OBJ_NAME_ALIAS); -#define EVP_delete_digest_alias(alias) \ - OBJ_NAME_remove(alias,OBJ_NAME_TYPE_MD_METH|OBJ_NAME_ALIAS); - -void EVP_MD_CTX_init(EVP_MD_CTX *ctx); -int EVP_MD_CTX_cleanup(EVP_MD_CTX *ctx); -EVP_MD_CTX *EVP_MD_CTX_create(void); -void EVP_MD_CTX_destroy(EVP_MD_CTX *ctx); -int EVP_MD_CTX_copy_ex(EVP_MD_CTX *out,const EVP_MD_CTX *in); -void EVP_MD_CTX_set_flags(EVP_MD_CTX *ctx, int flags); -void EVP_MD_CTX_clear_flags(EVP_MD_CTX *ctx, int flags); -int EVP_MD_CTX_test_flags(const EVP_MD_CTX *ctx,int flags); -int EVP_DigestInit_ex(EVP_MD_CTX *ctx, const EVP_MD *type, ENGINE *impl); -int EVP_DigestUpdate(EVP_MD_CTX *ctx,const void *d, - size_t cnt); -int EVP_DigestFinal_ex(EVP_MD_CTX *ctx,unsigned char *md,unsigned int *s); -int EVP_Digest(const void *data, size_t count, - unsigned char *md, unsigned int *size, const EVP_MD *type, ENGINE *impl); - -int EVP_MD_CTX_copy(EVP_MD_CTX *out,const EVP_MD_CTX *in); -int EVP_DigestInit(EVP_MD_CTX *ctx, const EVP_MD *type); -int EVP_DigestFinal(EVP_MD_CTX *ctx,unsigned char *md,unsigned int *s); - -int EVP_read_pw_string(char *buf,int length,const char *prompt,int verify); -int EVP_read_pw_string_min(char *buf,int minlen,int maxlen,const char *prompt,int verify); -void EVP_set_pw_prompt(const char *prompt); -char * EVP_get_pw_prompt(void); - -int EVP_BytesToKey(const EVP_CIPHER *type,const EVP_MD *md, - const unsigned char *salt, const unsigned char *data, - int datal, int count, unsigned char *key,unsigned char *iv); - -void EVP_CIPHER_CTX_set_flags(EVP_CIPHER_CTX *ctx, int flags); -void EVP_CIPHER_CTX_clear_flags(EVP_CIPHER_CTX *ctx, int flags); -int EVP_CIPHER_CTX_test_flags(const EVP_CIPHER_CTX *ctx,int flags); - -int EVP_EncryptInit(EVP_CIPHER_CTX *ctx,const EVP_CIPHER *cipher, - const unsigned char *key, const unsigned char *iv); -int EVP_EncryptInit_ex(EVP_CIPHER_CTX *ctx,const EVP_CIPHER *cipher, ENGINE *impl, - const unsigned char *key, const unsigned char *iv); -int EVP_EncryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, - int *outl, const unsigned char *in, int inl); -int EVP_EncryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl); -int EVP_EncryptFinal(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl); - -int EVP_DecryptInit(EVP_CIPHER_CTX *ctx,const EVP_CIPHER *cipher, - const unsigned char *key, const unsigned char *iv); -int EVP_DecryptInit_ex(EVP_CIPHER_CTX *ctx,const EVP_CIPHER *cipher, ENGINE *impl, - const unsigned char *key, const unsigned char *iv); -int EVP_DecryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, - int *outl, const unsigned char *in, int inl); -int EVP_DecryptFinal(EVP_CIPHER_CTX *ctx, unsigned char *outm, int *outl); -int EVP_DecryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *outm, int *outl); - -int EVP_CipherInit(EVP_CIPHER_CTX *ctx,const EVP_CIPHER *cipher, - const unsigned char *key,const unsigned char *iv, - int enc); -int EVP_CipherInit_ex(EVP_CIPHER_CTX *ctx,const EVP_CIPHER *cipher, ENGINE *impl, - const unsigned char *key,const unsigned char *iv, - int enc); -int EVP_CipherUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, - int *outl, const unsigned char *in, int inl); -int EVP_CipherFinal(EVP_CIPHER_CTX *ctx, unsigned char *outm, int *outl); -int EVP_CipherFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *outm, int *outl); - -int EVP_SignFinal(EVP_MD_CTX *ctx,unsigned char *md,unsigned int *s, - EVP_PKEY *pkey); - -int EVP_VerifyFinal(EVP_MD_CTX *ctx,const unsigned char *sigbuf, - unsigned int siglen,EVP_PKEY *pkey); - -int EVP_DigestSignInit(EVP_MD_CTX *ctx, EVP_PKEY_CTX **pctx, - const EVP_MD *type, ENGINE *e, EVP_PKEY *pkey); -int EVP_DigestSignFinal(EVP_MD_CTX *ctx, - unsigned char *sigret, size_t *siglen); - -int EVP_DigestVerifyInit(EVP_MD_CTX *ctx, EVP_PKEY_CTX **pctx, - const EVP_MD *type, ENGINE *e, EVP_PKEY *pkey); -int EVP_DigestVerifyFinal(EVP_MD_CTX *ctx, - unsigned char *sig, size_t siglen); - -int EVP_OpenInit(EVP_CIPHER_CTX *ctx,const EVP_CIPHER *type, - const unsigned char *ek, int ekl, const unsigned char *iv, - EVP_PKEY *priv); -int EVP_OpenFinal(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl); - -int EVP_SealInit(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *type, - unsigned char **ek, int *ekl, unsigned char *iv, - EVP_PKEY **pubk, int npubk); -int EVP_SealFinal(EVP_CIPHER_CTX *ctx,unsigned char *out,int *outl); - -void EVP_EncodeInit(EVP_ENCODE_CTX *ctx); -void EVP_EncodeUpdate(EVP_ENCODE_CTX *ctx,unsigned char *out,int *outl, - const unsigned char *in,int inl); -void EVP_EncodeFinal(EVP_ENCODE_CTX *ctx,unsigned char *out,int *outl); -int EVP_EncodeBlock(unsigned char *t, const unsigned char *f, int n); - -void EVP_DecodeInit(EVP_ENCODE_CTX *ctx); -int EVP_DecodeUpdate(EVP_ENCODE_CTX *ctx,unsigned char *out,int *outl, - const unsigned char *in, int inl); -int EVP_DecodeFinal(EVP_ENCODE_CTX *ctx, unsigned - char *out, int *outl); -int EVP_DecodeBlock(unsigned char *t, const unsigned char *f, int n); - -void EVP_CIPHER_CTX_init(EVP_CIPHER_CTX *a); -int EVP_CIPHER_CTX_cleanup(EVP_CIPHER_CTX *a); -EVP_CIPHER_CTX *EVP_CIPHER_CTX_new(void); -void EVP_CIPHER_CTX_free(EVP_CIPHER_CTX *a); -int EVP_CIPHER_CTX_set_key_length(EVP_CIPHER_CTX *x, int keylen); -int EVP_CIPHER_CTX_set_padding(EVP_CIPHER_CTX *c, int pad); -int EVP_CIPHER_CTX_ctrl(EVP_CIPHER_CTX *ctx, int type, int arg, void *ptr); -int EVP_CIPHER_CTX_rand_key(EVP_CIPHER_CTX *ctx, unsigned char *key); - -#ifndef OPENSSL_NO_BIO -BIO_METHOD *BIO_f_md(void); -BIO_METHOD *BIO_f_base64(void); -BIO_METHOD *BIO_f_cipher(void); -BIO_METHOD *BIO_f_reliable(void); -void BIO_set_cipher(BIO *b,const EVP_CIPHER *c,const unsigned char *k, - const unsigned char *i, int enc); -#endif - -const EVP_MD *EVP_md_null(void); -#ifndef OPENSSL_NO_MD2 -const EVP_MD *EVP_md2(void); -#endif -#ifndef OPENSSL_NO_MD4 -const EVP_MD *EVP_md4(void); -#endif -#ifndef OPENSSL_NO_MD5 -const EVP_MD *EVP_md5(void); -#endif -#ifndef OPENSSL_NO_SHA -const EVP_MD *EVP_sha(void); -const EVP_MD *EVP_sha1(void); -const EVP_MD *EVP_dss(void); -const EVP_MD *EVP_dss1(void); -const EVP_MD *EVP_ecdsa(void); -#endif -#ifndef OPENSSL_NO_SHA256 -const EVP_MD *EVP_sha224(void); -const EVP_MD *EVP_sha256(void); -#endif -#ifndef OPENSSL_NO_SHA512 -const EVP_MD *EVP_sha384(void); -const EVP_MD *EVP_sha512(void); -#endif -#ifndef OPENSSL_NO_MDC2 -const EVP_MD *EVP_mdc2(void); -#endif -#ifndef OPENSSL_NO_RIPEMD -const EVP_MD *EVP_ripemd160(void); -#endif -#ifndef OPENSSL_NO_WHIRLPOOL -const EVP_MD *EVP_whirlpool(void); -#endif -const EVP_CIPHER *EVP_enc_null(void); /* does nothing :-) */ -#ifndef OPENSSL_NO_DES -const EVP_CIPHER *EVP_des_ecb(void); -const EVP_CIPHER *EVP_des_ede(void); -const EVP_CIPHER *EVP_des_ede3(void); -const EVP_CIPHER *EVP_des_ede_ecb(void); -const EVP_CIPHER *EVP_des_ede3_ecb(void); -const EVP_CIPHER *EVP_des_cfb64(void); -# define EVP_des_cfb EVP_des_cfb64 -const EVP_CIPHER *EVP_des_cfb1(void); -const EVP_CIPHER *EVP_des_cfb8(void); -const EVP_CIPHER *EVP_des_ede_cfb64(void); -# define EVP_des_ede_cfb EVP_des_ede_cfb64 -#if 0 -const EVP_CIPHER *EVP_des_ede_cfb1(void); -const EVP_CIPHER *EVP_des_ede_cfb8(void); -#endif -const EVP_CIPHER *EVP_des_ede3_cfb64(void); -# define EVP_des_ede3_cfb EVP_des_ede3_cfb64 -const EVP_CIPHER *EVP_des_ede3_cfb1(void); -const EVP_CIPHER *EVP_des_ede3_cfb8(void); -const EVP_CIPHER *EVP_des_ofb(void); -const EVP_CIPHER *EVP_des_ede_ofb(void); -const EVP_CIPHER *EVP_des_ede3_ofb(void); -const EVP_CIPHER *EVP_des_cbc(void); -const EVP_CIPHER *EVP_des_ede_cbc(void); -const EVP_CIPHER *EVP_des_ede3_cbc(void); -const EVP_CIPHER *EVP_desx_cbc(void); -/* This should now be supported through the dev_crypto ENGINE. But also, why are - * rc4 and md5 declarations made here inside a "NO_DES" precompiler branch? */ -#if 0 -# ifdef OPENSSL_OPENBSD_DEV_CRYPTO -const EVP_CIPHER *EVP_dev_crypto_des_ede3_cbc(void); -const EVP_CIPHER *EVP_dev_crypto_rc4(void); -const EVP_MD *EVP_dev_crypto_md5(void); -# endif -#endif -#endif -#ifndef OPENSSL_NO_RC4 -const EVP_CIPHER *EVP_rc4(void); -const EVP_CIPHER *EVP_rc4_40(void); -#ifndef OPENSSL_NO_MD5 -const EVP_CIPHER *EVP_rc4_hmac_md5(void); -#endif -#endif -#ifndef OPENSSL_NO_IDEA -const EVP_CIPHER *EVP_idea_ecb(void); -const EVP_CIPHER *EVP_idea_cfb64(void); -# define EVP_idea_cfb EVP_idea_cfb64 -const EVP_CIPHER *EVP_idea_ofb(void); -const EVP_CIPHER *EVP_idea_cbc(void); -#endif -#ifndef OPENSSL_NO_RC2 -const EVP_CIPHER *EVP_rc2_ecb(void); -const EVP_CIPHER *EVP_rc2_cbc(void); -const EVP_CIPHER *EVP_rc2_40_cbc(void); -const EVP_CIPHER *EVP_rc2_64_cbc(void); -const EVP_CIPHER *EVP_rc2_cfb64(void); -# define EVP_rc2_cfb EVP_rc2_cfb64 -const EVP_CIPHER *EVP_rc2_ofb(void); -#endif -#ifndef OPENSSL_NO_BF -const EVP_CIPHER *EVP_bf_ecb(void); -const EVP_CIPHER *EVP_bf_cbc(void); -const EVP_CIPHER *EVP_bf_cfb64(void); -# define EVP_bf_cfb EVP_bf_cfb64 -const EVP_CIPHER *EVP_bf_ofb(void); -#endif -#ifndef OPENSSL_NO_CAST -const EVP_CIPHER *EVP_cast5_ecb(void); -const EVP_CIPHER *EVP_cast5_cbc(void); -const EVP_CIPHER *EVP_cast5_cfb64(void); -# define EVP_cast5_cfb EVP_cast5_cfb64 -const EVP_CIPHER *EVP_cast5_ofb(void); -#endif -#ifndef OPENSSL_NO_RC5 -const EVP_CIPHER *EVP_rc5_32_12_16_cbc(void); -const EVP_CIPHER *EVP_rc5_32_12_16_ecb(void); -const EVP_CIPHER *EVP_rc5_32_12_16_cfb64(void); -# define EVP_rc5_32_12_16_cfb EVP_rc5_32_12_16_cfb64 -const EVP_CIPHER *EVP_rc5_32_12_16_ofb(void); -#endif -#ifndef OPENSSL_NO_AES -const EVP_CIPHER *EVP_aes_128_ecb(void); -const EVP_CIPHER *EVP_aes_128_cbc(void); -const EVP_CIPHER *EVP_aes_128_cfb1(void); -const EVP_CIPHER *EVP_aes_128_cfb8(void); -const EVP_CIPHER *EVP_aes_128_cfb128(void); -# define EVP_aes_128_cfb EVP_aes_128_cfb128 -const EVP_CIPHER *EVP_aes_128_ofb(void); -const EVP_CIPHER *EVP_aes_128_ctr(void); -const EVP_CIPHER *EVP_aes_128_gcm(void); -const EVP_CIPHER *EVP_aes_128_ccm(void); -const EVP_CIPHER *EVP_aes_128_xts(void); -const EVP_CIPHER *EVP_aes_192_ecb(void); -const EVP_CIPHER *EVP_aes_192_cbc(void); -const EVP_CIPHER *EVP_aes_192_cfb1(void); -const EVP_CIPHER *EVP_aes_192_cfb8(void); -const EVP_CIPHER *EVP_aes_192_cfb128(void); -# define EVP_aes_192_cfb EVP_aes_192_cfb128 -const EVP_CIPHER *EVP_aes_192_ofb(void); -const EVP_CIPHER *EVP_aes_192_ctr(void); -const EVP_CIPHER *EVP_aes_192_gcm(void); -const EVP_CIPHER *EVP_aes_192_ccm(void); -const EVP_CIPHER *EVP_aes_256_ecb(void); -const EVP_CIPHER *EVP_aes_256_cbc(void); -const EVP_CIPHER *EVP_aes_256_cfb1(void); -const EVP_CIPHER *EVP_aes_256_cfb8(void); -const EVP_CIPHER *EVP_aes_256_cfb128(void); -# define EVP_aes_256_cfb EVP_aes_256_cfb128 -const EVP_CIPHER *EVP_aes_256_ofb(void); -const EVP_CIPHER *EVP_aes_256_ctr(void); -const EVP_CIPHER *EVP_aes_256_gcm(void); -const EVP_CIPHER *EVP_aes_256_ccm(void); -const EVP_CIPHER *EVP_aes_256_xts(void); -#if !defined(OPENSSL_NO_SHA) && !defined(OPENSSL_NO_SHA1) -const EVP_CIPHER *EVP_aes_128_cbc_hmac_sha1(void); -const EVP_CIPHER *EVP_aes_256_cbc_hmac_sha1(void); -#endif -#endif -#ifndef OPENSSL_NO_CAMELLIA -const EVP_CIPHER *EVP_camellia_128_ecb(void); -const EVP_CIPHER *EVP_camellia_128_cbc(void); -const EVP_CIPHER *EVP_camellia_128_cfb1(void); -const EVP_CIPHER *EVP_camellia_128_cfb8(void); -const EVP_CIPHER *EVP_camellia_128_cfb128(void); -# define EVP_camellia_128_cfb EVP_camellia_128_cfb128 -const EVP_CIPHER *EVP_camellia_128_ofb(void); -const EVP_CIPHER *EVP_camellia_192_ecb(void); -const EVP_CIPHER *EVP_camellia_192_cbc(void); -const EVP_CIPHER *EVP_camellia_192_cfb1(void); -const EVP_CIPHER *EVP_camellia_192_cfb8(void); -const EVP_CIPHER *EVP_camellia_192_cfb128(void); -# define EVP_camellia_192_cfb EVP_camellia_192_cfb128 -const EVP_CIPHER *EVP_camellia_192_ofb(void); -const EVP_CIPHER *EVP_camellia_256_ecb(void); -const EVP_CIPHER *EVP_camellia_256_cbc(void); -const EVP_CIPHER *EVP_camellia_256_cfb1(void); -const EVP_CIPHER *EVP_camellia_256_cfb8(void); -const EVP_CIPHER *EVP_camellia_256_cfb128(void); -# define EVP_camellia_256_cfb EVP_camellia_256_cfb128 -const EVP_CIPHER *EVP_camellia_256_ofb(void); -#endif - -#ifndef OPENSSL_NO_SEED -const EVP_CIPHER *EVP_seed_ecb(void); -const EVP_CIPHER *EVP_seed_cbc(void); -const EVP_CIPHER *EVP_seed_cfb128(void); -# define EVP_seed_cfb EVP_seed_cfb128 -const EVP_CIPHER *EVP_seed_ofb(void); -#endif - -void OPENSSL_add_all_algorithms_noconf(void); -void OPENSSL_add_all_algorithms_conf(void); - -#ifdef OPENSSL_LOAD_CONF -#define OpenSSL_add_all_algorithms() \ - OPENSSL_add_all_algorithms_conf() -#else -#define OpenSSL_add_all_algorithms() \ - OPENSSL_add_all_algorithms_noconf() -#endif - -void OpenSSL_add_all_ciphers(void); -void OpenSSL_add_all_digests(void); -#define SSLeay_add_all_algorithms() OpenSSL_add_all_algorithms() -#define SSLeay_add_all_ciphers() OpenSSL_add_all_ciphers() -#define SSLeay_add_all_digests() OpenSSL_add_all_digests() - -int EVP_add_cipher(const EVP_CIPHER *cipher); -int EVP_add_digest(const EVP_MD *digest); - -const EVP_CIPHER *EVP_get_cipherbyname(const char *name); -const EVP_MD *EVP_get_digestbyname(const char *name); -void EVP_cleanup(void); - -void EVP_CIPHER_do_all(void (*fn)(const EVP_CIPHER *ciph, - const char *from, const char *to, void *x), void *arg); -void EVP_CIPHER_do_all_sorted(void (*fn)(const EVP_CIPHER *ciph, - const char *from, const char *to, void *x), void *arg); - -void EVP_MD_do_all(void (*fn)(const EVP_MD *ciph, - const char *from, const char *to, void *x), void *arg); -void EVP_MD_do_all_sorted(void (*fn)(const EVP_MD *ciph, - const char *from, const char *to, void *x), void *arg); - -int EVP_PKEY_decrypt_old(unsigned char *dec_key, - const unsigned char *enc_key,int enc_key_len, - EVP_PKEY *private_key); -int EVP_PKEY_encrypt_old(unsigned char *enc_key, - const unsigned char *key,int key_len, - EVP_PKEY *pub_key); -int EVP_PKEY_type(int type); -int EVP_PKEY_id(const EVP_PKEY *pkey); -int EVP_PKEY_base_id(const EVP_PKEY *pkey); -int EVP_PKEY_bits(EVP_PKEY *pkey); -int EVP_PKEY_size(EVP_PKEY *pkey); -int EVP_PKEY_set_type(EVP_PKEY *pkey,int type); -int EVP_PKEY_set_type_str(EVP_PKEY *pkey, const char *str, int len); -int EVP_PKEY_assign(EVP_PKEY *pkey,int type,void *key); -void * EVP_PKEY_get0(EVP_PKEY *pkey); - -#ifndef OPENSSL_NO_RSA -struct rsa_st; -int EVP_PKEY_set1_RSA(EVP_PKEY *pkey,struct rsa_st *key); -struct rsa_st *EVP_PKEY_get1_RSA(EVP_PKEY *pkey); -#endif -#ifndef OPENSSL_NO_DSA -struct dsa_st; -int EVP_PKEY_set1_DSA(EVP_PKEY *pkey,struct dsa_st *key); -struct dsa_st *EVP_PKEY_get1_DSA(EVP_PKEY *pkey); -#endif -#ifndef OPENSSL_NO_DH -struct dh_st; -int EVP_PKEY_set1_DH(EVP_PKEY *pkey,struct dh_st *key); -struct dh_st *EVP_PKEY_get1_DH(EVP_PKEY *pkey); -#endif -#ifndef OPENSSL_NO_EC -struct ec_key_st; -int EVP_PKEY_set1_EC_KEY(EVP_PKEY *pkey,struct ec_key_st *key); -struct ec_key_st *EVP_PKEY_get1_EC_KEY(EVP_PKEY *pkey); -#endif - -EVP_PKEY * EVP_PKEY_new(void); -void EVP_PKEY_free(EVP_PKEY *pkey); - -EVP_PKEY * d2i_PublicKey(int type,EVP_PKEY **a, const unsigned char **pp, - long length); -int i2d_PublicKey(EVP_PKEY *a, unsigned char **pp); - -EVP_PKEY * d2i_PrivateKey(int type,EVP_PKEY **a, const unsigned char **pp, - long length); -EVP_PKEY * d2i_AutoPrivateKey(EVP_PKEY **a, const unsigned char **pp, - long length); -int i2d_PrivateKey(EVP_PKEY *a, unsigned char **pp); - -int EVP_PKEY_copy_parameters(EVP_PKEY *to, const EVP_PKEY *from); -int EVP_PKEY_missing_parameters(const EVP_PKEY *pkey); -int EVP_PKEY_save_parameters(EVP_PKEY *pkey,int mode); -int EVP_PKEY_cmp_parameters(const EVP_PKEY *a, const EVP_PKEY *b); - -int EVP_PKEY_cmp(const EVP_PKEY *a, const EVP_PKEY *b); - -int EVP_PKEY_print_public(BIO *out, const EVP_PKEY *pkey, - int indent, ASN1_PCTX *pctx); -int EVP_PKEY_print_private(BIO *out, const EVP_PKEY *pkey, - int indent, ASN1_PCTX *pctx); -int EVP_PKEY_print_params(BIO *out, const EVP_PKEY *pkey, - int indent, ASN1_PCTX *pctx); - -int EVP_PKEY_get_default_digest_nid(EVP_PKEY *pkey, int *pnid); - -int EVP_CIPHER_type(const EVP_CIPHER *ctx); - -/* calls methods */ -int EVP_CIPHER_param_to_asn1(EVP_CIPHER_CTX *c, ASN1_TYPE *type); -int EVP_CIPHER_asn1_to_param(EVP_CIPHER_CTX *c, ASN1_TYPE *type); - -/* These are used by EVP_CIPHER methods */ -int EVP_CIPHER_set_asn1_iv(EVP_CIPHER_CTX *c,ASN1_TYPE *type); -int EVP_CIPHER_get_asn1_iv(EVP_CIPHER_CTX *c,ASN1_TYPE *type); - -/* PKCS5 password based encryption */ -int PKCS5_PBE_keyivgen(EVP_CIPHER_CTX *ctx, const char *pass, int passlen, - ASN1_TYPE *param, const EVP_CIPHER *cipher, const EVP_MD *md, - int en_de); -int PKCS5_PBKDF2_HMAC_SHA1(const char *pass, int passlen, - const unsigned char *salt, int saltlen, int iter, - int keylen, unsigned char *out); -int PKCS5_PBKDF2_HMAC(const char *pass, int passlen, - const unsigned char *salt, int saltlen, int iter, - const EVP_MD *digest, - int keylen, unsigned char *out); -int PKCS5_v2_PBE_keyivgen(EVP_CIPHER_CTX *ctx, const char *pass, int passlen, - ASN1_TYPE *param, const EVP_CIPHER *cipher, const EVP_MD *md, - int en_de); - -void PKCS5_PBE_add(void); - -int EVP_PBE_CipherInit (ASN1_OBJECT *pbe_obj, const char *pass, int passlen, - ASN1_TYPE *param, EVP_CIPHER_CTX *ctx, int en_de); - -/* PBE type */ - -/* Can appear as the outermost AlgorithmIdentifier */ -#define EVP_PBE_TYPE_OUTER 0x0 -/* Is an PRF type OID */ -#define EVP_PBE_TYPE_PRF 0x1 - -int EVP_PBE_alg_add_type(int pbe_type, int pbe_nid, int cipher_nid, int md_nid, - EVP_PBE_KEYGEN *keygen); -int EVP_PBE_alg_add(int nid, const EVP_CIPHER *cipher, const EVP_MD *md, - EVP_PBE_KEYGEN *keygen); -int EVP_PBE_find(int type, int pbe_nid, - int *pcnid, int *pmnid, EVP_PBE_KEYGEN **pkeygen); -void EVP_PBE_cleanup(void); - -#define ASN1_PKEY_ALIAS 0x1 -#define ASN1_PKEY_DYNAMIC 0x2 -#define ASN1_PKEY_SIGPARAM_NULL 0x4 - -#define ASN1_PKEY_CTRL_PKCS7_SIGN 0x1 -#define ASN1_PKEY_CTRL_PKCS7_ENCRYPT 0x2 -#define ASN1_PKEY_CTRL_DEFAULT_MD_NID 0x3 -#define ASN1_PKEY_CTRL_CMS_SIGN 0x5 -#define ASN1_PKEY_CTRL_CMS_ENVELOPE 0x7 - -int EVP_PKEY_asn1_get_count(void); -const EVP_PKEY_ASN1_METHOD *EVP_PKEY_asn1_get0(int idx); -const EVP_PKEY_ASN1_METHOD *EVP_PKEY_asn1_find(ENGINE **pe, int type); -const EVP_PKEY_ASN1_METHOD *EVP_PKEY_asn1_find_str(ENGINE **pe, - const char *str, int len); -int EVP_PKEY_asn1_add0(const EVP_PKEY_ASN1_METHOD *ameth); -int EVP_PKEY_asn1_add_alias(int to, int from); -int EVP_PKEY_asn1_get0_info(int *ppkey_id, int *pkey_base_id, int *ppkey_flags, - const char **pinfo, const char **ppem_str, - const EVP_PKEY_ASN1_METHOD *ameth); - -const EVP_PKEY_ASN1_METHOD* EVP_PKEY_get0_asn1(EVP_PKEY *pkey); -EVP_PKEY_ASN1_METHOD* EVP_PKEY_asn1_new(int id, int flags, - const char *pem_str, const char *info); -void EVP_PKEY_asn1_copy(EVP_PKEY_ASN1_METHOD *dst, - const EVP_PKEY_ASN1_METHOD *src); -void EVP_PKEY_asn1_free(EVP_PKEY_ASN1_METHOD *ameth); -void EVP_PKEY_asn1_set_public(EVP_PKEY_ASN1_METHOD *ameth, - int (*pub_decode)(EVP_PKEY *pk, X509_PUBKEY *pub), - int (*pub_encode)(X509_PUBKEY *pub, const EVP_PKEY *pk), - int (*pub_cmp)(const EVP_PKEY *a, const EVP_PKEY *b), - int (*pub_print)(BIO *out, const EVP_PKEY *pkey, int indent, - ASN1_PCTX *pctx), - int (*pkey_size)(const EVP_PKEY *pk), - int (*pkey_bits)(const EVP_PKEY *pk)); -void EVP_PKEY_asn1_set_private(EVP_PKEY_ASN1_METHOD *ameth, - int (*priv_decode)(EVP_PKEY *pk, PKCS8_PRIV_KEY_INFO *p8inf), - int (*priv_encode)(PKCS8_PRIV_KEY_INFO *p8, const EVP_PKEY *pk), - int (*priv_print)(BIO *out, const EVP_PKEY *pkey, int indent, - ASN1_PCTX *pctx)); -void EVP_PKEY_asn1_set_param(EVP_PKEY_ASN1_METHOD *ameth, - int (*param_decode)(EVP_PKEY *pkey, - const unsigned char **pder, int derlen), - int (*param_encode)(const EVP_PKEY *pkey, unsigned char **pder), - int (*param_missing)(const EVP_PKEY *pk), - int (*param_copy)(EVP_PKEY *to, const EVP_PKEY *from), - int (*param_cmp)(const EVP_PKEY *a, const EVP_PKEY *b), - int (*param_print)(BIO *out, const EVP_PKEY *pkey, int indent, - ASN1_PCTX *pctx)); - -void EVP_PKEY_asn1_set_free(EVP_PKEY_ASN1_METHOD *ameth, - void (*pkey_free)(EVP_PKEY *pkey)); -void EVP_PKEY_asn1_set_ctrl(EVP_PKEY_ASN1_METHOD *ameth, - int (*pkey_ctrl)(EVP_PKEY *pkey, int op, - long arg1, void *arg2)); - - -#define EVP_PKEY_OP_UNDEFINED 0 -#define EVP_PKEY_OP_PARAMGEN (1<<1) -#define EVP_PKEY_OP_KEYGEN (1<<2) -#define EVP_PKEY_OP_SIGN (1<<3) -#define EVP_PKEY_OP_VERIFY (1<<4) -#define EVP_PKEY_OP_VERIFYRECOVER (1<<5) -#define EVP_PKEY_OP_SIGNCTX (1<<6) -#define EVP_PKEY_OP_VERIFYCTX (1<<7) -#define EVP_PKEY_OP_ENCRYPT (1<<8) -#define EVP_PKEY_OP_DECRYPT (1<<9) -#define EVP_PKEY_OP_DERIVE (1<<10) - -#define EVP_PKEY_OP_TYPE_SIG \ - (EVP_PKEY_OP_SIGN | EVP_PKEY_OP_VERIFY | EVP_PKEY_OP_VERIFYRECOVER \ - | EVP_PKEY_OP_SIGNCTX | EVP_PKEY_OP_VERIFYCTX) - -#define EVP_PKEY_OP_TYPE_CRYPT \ - (EVP_PKEY_OP_ENCRYPT | EVP_PKEY_OP_DECRYPT) - -#define EVP_PKEY_OP_TYPE_NOGEN \ - (EVP_PKEY_OP_SIG | EVP_PKEY_OP_CRYPT | EVP_PKEY_OP_DERIVE) - -#define EVP_PKEY_OP_TYPE_GEN \ - (EVP_PKEY_OP_PARAMGEN | EVP_PKEY_OP_KEYGEN) - -#define EVP_PKEY_CTX_set_signature_md(ctx, md) \ - EVP_PKEY_CTX_ctrl(ctx, -1, EVP_PKEY_OP_TYPE_SIG, \ - EVP_PKEY_CTRL_MD, 0, (void *)md) - -#define EVP_PKEY_CTRL_MD 1 -#define EVP_PKEY_CTRL_PEER_KEY 2 - -#define EVP_PKEY_CTRL_PKCS7_ENCRYPT 3 -#define EVP_PKEY_CTRL_PKCS7_DECRYPT 4 - -#define EVP_PKEY_CTRL_PKCS7_SIGN 5 - -#define EVP_PKEY_CTRL_SET_MAC_KEY 6 - -#define EVP_PKEY_CTRL_DIGESTINIT 7 - -/* Used by GOST key encryption in TLS */ -#define EVP_PKEY_CTRL_SET_IV 8 - -#define EVP_PKEY_CTRL_CMS_ENCRYPT 9 -#define EVP_PKEY_CTRL_CMS_DECRYPT 10 -#define EVP_PKEY_CTRL_CMS_SIGN 11 - -#define EVP_PKEY_CTRL_CIPHER 12 - -#define EVP_PKEY_ALG_CTRL 0x1000 - - -#define EVP_PKEY_FLAG_AUTOARGLEN 2 -/* Method handles all operations: don't assume any digest related - * defaults. - */ -#define EVP_PKEY_FLAG_SIGCTX_CUSTOM 4 - -const EVP_PKEY_METHOD *EVP_PKEY_meth_find(int type); -EVP_PKEY_METHOD* EVP_PKEY_meth_new(int id, int flags); -void EVP_PKEY_meth_get0_info(int *ppkey_id, int *pflags, - const EVP_PKEY_METHOD *meth); -void EVP_PKEY_meth_copy(EVP_PKEY_METHOD *dst, const EVP_PKEY_METHOD *src); -void EVP_PKEY_meth_free(EVP_PKEY_METHOD *pmeth); -int EVP_PKEY_meth_add0(const EVP_PKEY_METHOD *pmeth); - -EVP_PKEY_CTX *EVP_PKEY_CTX_new(EVP_PKEY *pkey, ENGINE *e); -EVP_PKEY_CTX *EVP_PKEY_CTX_new_id(int id, ENGINE *e); -EVP_PKEY_CTX *EVP_PKEY_CTX_dup(EVP_PKEY_CTX *ctx); -void EVP_PKEY_CTX_free(EVP_PKEY_CTX *ctx); - -int EVP_PKEY_CTX_ctrl(EVP_PKEY_CTX *ctx, int keytype, int optype, - int cmd, int p1, void *p2); -int EVP_PKEY_CTX_ctrl_str(EVP_PKEY_CTX *ctx, const char *type, - const char *value); - -int EVP_PKEY_CTX_get_operation(EVP_PKEY_CTX *ctx); -void EVP_PKEY_CTX_set0_keygen_info(EVP_PKEY_CTX *ctx, int *dat, int datlen); - -EVP_PKEY *EVP_PKEY_new_mac_key(int type, ENGINE *e, - const unsigned char *key, int keylen); - -void EVP_PKEY_CTX_set_data(EVP_PKEY_CTX *ctx, void *data); -void *EVP_PKEY_CTX_get_data(EVP_PKEY_CTX *ctx); -EVP_PKEY *EVP_PKEY_CTX_get0_pkey(EVP_PKEY_CTX *ctx); - -EVP_PKEY *EVP_PKEY_CTX_get0_peerkey(EVP_PKEY_CTX *ctx); - -void EVP_PKEY_CTX_set_app_data(EVP_PKEY_CTX *ctx, void *data); -void *EVP_PKEY_CTX_get_app_data(EVP_PKEY_CTX *ctx); - -int EVP_PKEY_sign_init(EVP_PKEY_CTX *ctx); -int EVP_PKEY_sign(EVP_PKEY_CTX *ctx, - unsigned char *sig, size_t *siglen, - const unsigned char *tbs, size_t tbslen); -int EVP_PKEY_verify_init(EVP_PKEY_CTX *ctx); -int EVP_PKEY_verify(EVP_PKEY_CTX *ctx, - const unsigned char *sig, size_t siglen, - const unsigned char *tbs, size_t tbslen); -int EVP_PKEY_verify_recover_init(EVP_PKEY_CTX *ctx); -int EVP_PKEY_verify_recover(EVP_PKEY_CTX *ctx, - unsigned char *rout, size_t *routlen, - const unsigned char *sig, size_t siglen); -int EVP_PKEY_encrypt_init(EVP_PKEY_CTX *ctx); -int EVP_PKEY_encrypt(EVP_PKEY_CTX *ctx, - unsigned char *out, size_t *outlen, - const unsigned char *in, size_t inlen); -int EVP_PKEY_decrypt_init(EVP_PKEY_CTX *ctx); -int EVP_PKEY_decrypt(EVP_PKEY_CTX *ctx, - unsigned char *out, size_t *outlen, - const unsigned char *in, size_t inlen); - -int EVP_PKEY_derive_init(EVP_PKEY_CTX *ctx); -int EVP_PKEY_derive_set_peer(EVP_PKEY_CTX *ctx, EVP_PKEY *peer); -int EVP_PKEY_derive(EVP_PKEY_CTX *ctx, unsigned char *key, size_t *keylen); - -typedef int EVP_PKEY_gen_cb(EVP_PKEY_CTX *ctx); - -int EVP_PKEY_paramgen_init(EVP_PKEY_CTX *ctx); -int EVP_PKEY_paramgen(EVP_PKEY_CTX *ctx, EVP_PKEY **ppkey); -int EVP_PKEY_keygen_init(EVP_PKEY_CTX *ctx); -int EVP_PKEY_keygen(EVP_PKEY_CTX *ctx, EVP_PKEY **ppkey); - -void EVP_PKEY_CTX_set_cb(EVP_PKEY_CTX *ctx, EVP_PKEY_gen_cb *cb); -EVP_PKEY_gen_cb *EVP_PKEY_CTX_get_cb(EVP_PKEY_CTX *ctx); - -int EVP_PKEY_CTX_get_keygen_info(EVP_PKEY_CTX *ctx, int idx); - -void EVP_PKEY_meth_set_init(EVP_PKEY_METHOD *pmeth, - int (*init)(EVP_PKEY_CTX *ctx)); - -void EVP_PKEY_meth_set_copy(EVP_PKEY_METHOD *pmeth, - int (*copy)(EVP_PKEY_CTX *dst, EVP_PKEY_CTX *src)); - -void EVP_PKEY_meth_set_cleanup(EVP_PKEY_METHOD *pmeth, - void (*cleanup)(EVP_PKEY_CTX *ctx)); - -void EVP_PKEY_meth_set_paramgen(EVP_PKEY_METHOD *pmeth, - int (*paramgen_init)(EVP_PKEY_CTX *ctx), - int (*paramgen)(EVP_PKEY_CTX *ctx, EVP_PKEY *pkey)); - -void EVP_PKEY_meth_set_keygen(EVP_PKEY_METHOD *pmeth, - int (*keygen_init)(EVP_PKEY_CTX *ctx), - int (*keygen)(EVP_PKEY_CTX *ctx, EVP_PKEY *pkey)); - -void EVP_PKEY_meth_set_sign(EVP_PKEY_METHOD *pmeth, - int (*sign_init)(EVP_PKEY_CTX *ctx), - int (*sign)(EVP_PKEY_CTX *ctx, unsigned char *sig, size_t *siglen, - const unsigned char *tbs, size_t tbslen)); - -void EVP_PKEY_meth_set_verify(EVP_PKEY_METHOD *pmeth, - int (*verify_init)(EVP_PKEY_CTX *ctx), - int (*verify)(EVP_PKEY_CTX *ctx, const unsigned char *sig, size_t siglen, - const unsigned char *tbs, size_t tbslen)); - -void EVP_PKEY_meth_set_verify_recover(EVP_PKEY_METHOD *pmeth, - int (*verify_recover_init)(EVP_PKEY_CTX *ctx), - int (*verify_recover)(EVP_PKEY_CTX *ctx, - unsigned char *sig, size_t *siglen, - const unsigned char *tbs, size_t tbslen)); - -void EVP_PKEY_meth_set_signctx(EVP_PKEY_METHOD *pmeth, - int (*signctx_init)(EVP_PKEY_CTX *ctx, EVP_MD_CTX *mctx), - int (*signctx)(EVP_PKEY_CTX *ctx, unsigned char *sig, size_t *siglen, - EVP_MD_CTX *mctx)); - -void EVP_PKEY_meth_set_verifyctx(EVP_PKEY_METHOD *pmeth, - int (*verifyctx_init)(EVP_PKEY_CTX *ctx, EVP_MD_CTX *mctx), - int (*verifyctx)(EVP_PKEY_CTX *ctx, const unsigned char *sig,int siglen, - EVP_MD_CTX *mctx)); - -void EVP_PKEY_meth_set_encrypt(EVP_PKEY_METHOD *pmeth, - int (*encrypt_init)(EVP_PKEY_CTX *ctx), - int (*encryptfn)(EVP_PKEY_CTX *ctx, unsigned char *out, size_t *outlen, - const unsigned char *in, size_t inlen)); - -void EVP_PKEY_meth_set_decrypt(EVP_PKEY_METHOD *pmeth, - int (*decrypt_init)(EVP_PKEY_CTX *ctx), - int (*decrypt)(EVP_PKEY_CTX *ctx, unsigned char *out, size_t *outlen, - const unsigned char *in, size_t inlen)); - -void EVP_PKEY_meth_set_derive(EVP_PKEY_METHOD *pmeth, - int (*derive_init)(EVP_PKEY_CTX *ctx), - int (*derive)(EVP_PKEY_CTX *ctx, unsigned char *key, size_t *keylen)); - -void EVP_PKEY_meth_set_ctrl(EVP_PKEY_METHOD *pmeth, - int (*ctrl)(EVP_PKEY_CTX *ctx, int type, int p1, void *p2), - int (*ctrl_str)(EVP_PKEY_CTX *ctx, - const char *type, const char *value)); - -/* BEGIN ERROR CODES */ -/* The following lines are auto generated by the script mkerr.pl. Any changes - * made after this point may be overwritten when the script is next run. - */ -void ERR_load_EVP_strings(void); - -/* Error codes for the EVP functions. */ - -/* Function codes. */ -#define EVP_F_AESNI_INIT_KEY 165 -#define EVP_F_AESNI_XTS_CIPHER 176 -#define EVP_F_AES_INIT_KEY 133 -#define EVP_F_AES_XTS 172 -#define EVP_F_AES_XTS_CIPHER 175 -#define EVP_F_CAMELLIA_INIT_KEY 159 -#define EVP_F_CMAC_INIT 173 -#define EVP_F_D2I_PKEY 100 -#define EVP_F_DO_SIGVER_INIT 161 -#define EVP_F_DSAPKEY2PKCS8 134 -#define EVP_F_DSA_PKEY2PKCS8 135 -#define EVP_F_ECDSA_PKEY2PKCS8 129 -#define EVP_F_ECKEY_PKEY2PKCS8 132 -#define EVP_F_EVP_CIPHERINIT_EX 123 -#define EVP_F_EVP_CIPHER_CTX_COPY 163 -#define EVP_F_EVP_CIPHER_CTX_CTRL 124 -#define EVP_F_EVP_CIPHER_CTX_SET_KEY_LENGTH 122 -#define EVP_F_EVP_DECRYPTFINAL_EX 101 -#define EVP_F_EVP_DIGESTINIT_EX 128 -#define EVP_F_EVP_ENCRYPTFINAL_EX 127 -#define EVP_F_EVP_MD_CTX_COPY_EX 110 -#define EVP_F_EVP_MD_SIZE 162 -#define EVP_F_EVP_OPENINIT 102 -#define EVP_F_EVP_PBE_ALG_ADD 115 -#define EVP_F_EVP_PBE_ALG_ADD_TYPE 160 -#define EVP_F_EVP_PBE_CIPHERINIT 116 -#define EVP_F_EVP_PKCS82PKEY 111 -#define EVP_F_EVP_PKCS82PKEY_BROKEN 136 -#define EVP_F_EVP_PKEY2PKCS8_BROKEN 113 -#define EVP_F_EVP_PKEY_COPY_PARAMETERS 103 -#define EVP_F_EVP_PKEY_CTX_CTRL 137 -#define EVP_F_EVP_PKEY_CTX_CTRL_STR 150 -#define EVP_F_EVP_PKEY_CTX_DUP 156 -#define EVP_F_EVP_PKEY_DECRYPT 104 -#define EVP_F_EVP_PKEY_DECRYPT_INIT 138 -#define EVP_F_EVP_PKEY_DECRYPT_OLD 151 -#define EVP_F_EVP_PKEY_DERIVE 153 -#define EVP_F_EVP_PKEY_DERIVE_INIT 154 -#define EVP_F_EVP_PKEY_DERIVE_SET_PEER 155 -#define EVP_F_EVP_PKEY_ENCRYPT 105 -#define EVP_F_EVP_PKEY_ENCRYPT_INIT 139 -#define EVP_F_EVP_PKEY_ENCRYPT_OLD 152 -#define EVP_F_EVP_PKEY_GET1_DH 119 -#define EVP_F_EVP_PKEY_GET1_DSA 120 -#define EVP_F_EVP_PKEY_GET1_ECDSA 130 -#define EVP_F_EVP_PKEY_GET1_EC_KEY 131 -#define EVP_F_EVP_PKEY_GET1_RSA 121 -#define EVP_F_EVP_PKEY_KEYGEN 146 -#define EVP_F_EVP_PKEY_KEYGEN_INIT 147 -#define EVP_F_EVP_PKEY_NEW 106 -#define EVP_F_EVP_PKEY_PARAMGEN 148 -#define EVP_F_EVP_PKEY_PARAMGEN_INIT 149 -#define EVP_F_EVP_PKEY_SIGN 140 -#define EVP_F_EVP_PKEY_SIGN_INIT 141 -#define EVP_F_EVP_PKEY_VERIFY 142 -#define EVP_F_EVP_PKEY_VERIFY_INIT 143 -#define EVP_F_EVP_PKEY_VERIFY_RECOVER 144 -#define EVP_F_EVP_PKEY_VERIFY_RECOVER_INIT 145 -#define EVP_F_EVP_RIJNDAEL 126 -#define EVP_F_EVP_SIGNFINAL 107 -#define EVP_F_EVP_VERIFYFINAL 108 -#define EVP_F_FIPS_CIPHERINIT 166 -#define EVP_F_FIPS_CIPHER_CTX_COPY 170 -#define EVP_F_FIPS_CIPHER_CTX_CTRL 167 -#define EVP_F_FIPS_CIPHER_CTX_SET_KEY_LENGTH 171 -#define EVP_F_FIPS_DIGESTINIT 168 -#define EVP_F_FIPS_MD_CTX_COPY 169 -#define EVP_F_HMAC_INIT_EX 174 -#define EVP_F_INT_CTX_NEW 157 -#define EVP_F_PKCS5_PBE_KEYIVGEN 117 -#define EVP_F_PKCS5_V2_PBE_KEYIVGEN 118 -#define EVP_F_PKCS5_V2_PBKDF2_KEYIVGEN 164 -#define EVP_F_PKCS8_SET_BROKEN 112 -#define EVP_F_PKEY_SET_TYPE 158 -#define EVP_F_RC2_MAGIC_TO_METH 109 -#define EVP_F_RC5_CTRL 125 - -/* Reason codes. */ -#define EVP_R_AES_IV_SETUP_FAILED 162 -#define EVP_R_AES_KEY_SETUP_FAILED 143 -#define EVP_R_ASN1_LIB 140 -#define EVP_R_BAD_BLOCK_LENGTH 136 -#define EVP_R_BAD_DECRYPT 100 -#define EVP_R_BAD_KEY_LENGTH 137 -#define EVP_R_BN_DECODE_ERROR 112 -#define EVP_R_BN_PUBKEY_ERROR 113 -#define EVP_R_BUFFER_TOO_SMALL 155 -#define EVP_R_CAMELLIA_KEY_SETUP_FAILED 157 -#define EVP_R_CIPHER_PARAMETER_ERROR 122 -#define EVP_R_COMMAND_NOT_SUPPORTED 147 -#define EVP_R_CTRL_NOT_IMPLEMENTED 132 -#define EVP_R_CTRL_OPERATION_NOT_IMPLEMENTED 133 -#define EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH 138 -#define EVP_R_DECODE_ERROR 114 -#define EVP_R_DIFFERENT_KEY_TYPES 101 -#define EVP_R_DIFFERENT_PARAMETERS 153 -#define EVP_R_DISABLED_FOR_FIPS 163 -#define EVP_R_ENCODE_ERROR 115 -#define EVP_R_EVP_PBE_CIPHERINIT_ERROR 119 -#define EVP_R_EXPECTING_AN_RSA_KEY 127 -#define EVP_R_EXPECTING_A_DH_KEY 128 -#define EVP_R_EXPECTING_A_DSA_KEY 129 -#define EVP_R_EXPECTING_A_ECDSA_KEY 141 -#define EVP_R_EXPECTING_A_EC_KEY 142 -#define EVP_R_INITIALIZATION_ERROR 134 -#define EVP_R_INPUT_NOT_INITIALIZED 111 -#define EVP_R_INVALID_DIGEST 152 -#define EVP_R_INVALID_KEY_LENGTH 130 -#define EVP_R_INVALID_OPERATION 148 -#define EVP_R_IV_TOO_LARGE 102 -#define EVP_R_KEYGEN_FAILURE 120 -#define EVP_R_MESSAGE_DIGEST_IS_NULL 159 -#define EVP_R_METHOD_NOT_SUPPORTED 144 -#define EVP_R_MISSING_PARAMETERS 103 -#define EVP_R_NO_CIPHER_SET 131 -#define EVP_R_NO_DEFAULT_DIGEST 158 -#define EVP_R_NO_DIGEST_SET 139 -#define EVP_R_NO_DSA_PARAMETERS 116 -#define EVP_R_NO_KEY_SET 154 -#define EVP_R_NO_OPERATION_SET 149 -#define EVP_R_NO_SIGN_FUNCTION_CONFIGURED 104 -#define EVP_R_NO_VERIFY_FUNCTION_CONFIGURED 105 -#define EVP_R_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE 150 -#define EVP_R_OPERATON_NOT_INITIALIZED 151 -#define EVP_R_PKCS8_UNKNOWN_BROKEN_TYPE 117 -#define EVP_R_PRIVATE_KEY_DECODE_ERROR 145 -#define EVP_R_PRIVATE_KEY_ENCODE_ERROR 146 -#define EVP_R_PUBLIC_KEY_NOT_RSA 106 -#define EVP_R_TOO_LARGE 164 -#define EVP_R_UNKNOWN_CIPHER 160 -#define EVP_R_UNKNOWN_DIGEST 161 -#define EVP_R_UNKNOWN_PBE_ALGORITHM 121 -#define EVP_R_UNSUPORTED_NUMBER_OF_ROUNDS 135 -#define EVP_R_UNSUPPORTED_ALGORITHM 156 -#define EVP_R_UNSUPPORTED_CIPHER 107 -#define EVP_R_UNSUPPORTED_KEYLENGTH 123 -#define EVP_R_UNSUPPORTED_KEY_DERIVATION_FUNCTION 124 -#define EVP_R_UNSUPPORTED_KEY_SIZE 108 -#define EVP_R_UNSUPPORTED_PRF 125 -#define EVP_R_UNSUPPORTED_PRIVATE_KEY_ALGORITHM 118 -#define EVP_R_UNSUPPORTED_SALT_TYPE 126 -#define EVP_R_WRONG_FINAL_BLOCK_LENGTH 109 -#define EVP_R_WRONG_PUBLIC_KEY_TYPE 110 - -#ifdef __cplusplus -} -#endif -#endif diff --git a/src/plugins/ftp/openssl/lhash.h b/src/plugins/ftp/openssl/lhash.h deleted file mode 100644 index e7d87635..00000000 --- a/src/plugins/ftp/openssl/lhash.h +++ /dev/null @@ -1,241 +0,0 @@ -/* crypto/lhash/lhash.h */ -/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) - * All rights reserved. - * - * This package is an SSL implementation written - * by Eric Young (eay@cryptsoft.com). - * The implementation was written so as to conform with Netscapes SSL. - * - * This library is free for commercial and non-commercial use as long as - * the following conditions are aheared to. The following conditions - * apply to all code found in this distribution, be it the RC4, RSA, - * lhash, DES, etc., code; not just the SSL code. The SSL documentation - * included with this distribution is covered by the same copyright terms - * except that the holder is Tim Hudson (tjh@cryptsoft.com). - * - * Copyright remains Eric Young's, and as such any Copyright notices in - * the code are not to be removed. - * If this package is used in a product, Eric Young should be given attribution - * as the author of the parts of the library used. - * This can be in the form of a textual message at program startup or - * in documentation (online or textual) provided with the package. - * - * 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 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. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * "This product includes cryptographic software written by - * Eric Young (eay@cryptsoft.com)" - * The word 'cryptographic' can be left out if the rouines from the library - * being used are not cryptographic related :-). - * 4. If you include any Windows specific code (or a derivative thereof) from - * the apps directory (application code) you must include an acknowledgement: - * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" - * - * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``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 AUTHOR 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. - * - * The licence and distribution terms for any publically available version or - * derivative of this code cannot be changed. i.e. this code cannot simply be - * copied and put under another distribution licence - * [including the GNU Public Licence.] - */ - -/* Header for dynamic hash table routines - * Author - Eric Young - */ - -#ifndef HEADER_LHASH_H -#define HEADER_LHASH_H - -#include -#ifndef OPENSSL_NO_FP_API -#include -#endif - -#ifndef OPENSSL_NO_BIO -#include -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct lhash_node_st - { - void *data; - struct lhash_node_st *next; -#ifndef OPENSSL_NO_HASH_COMP - unsigned long hash; -#endif - } LHASH_NODE; - -typedef int (*LHASH_COMP_FN_TYPE)(const void *, const void *); -typedef unsigned long (*LHASH_HASH_FN_TYPE)(const void *); -typedef void (*LHASH_DOALL_FN_TYPE)(void *); -typedef void (*LHASH_DOALL_ARG_FN_TYPE)(void *, void *); - -/* Macros for declaring and implementing type-safe wrappers for LHASH callbacks. - * This way, callbacks can be provided to LHASH structures without function - * pointer casting and the macro-defined callbacks provide per-variable casting - * before deferring to the underlying type-specific callbacks. NB: It is - * possible to place a "static" in front of both the DECLARE and IMPLEMENT - * macros if the functions are strictly internal. */ - -/* First: "hash" functions */ -#define DECLARE_LHASH_HASH_FN(name, o_type) \ - unsigned long name##_LHASH_HASH(const void *); -#define IMPLEMENT_LHASH_HASH_FN(name, o_type) \ - unsigned long name##_LHASH_HASH(const void *arg) { \ - const o_type *a = arg; \ - return name##_hash(a); } -#define LHASH_HASH_FN(name) name##_LHASH_HASH - -/* Second: "compare" functions */ -#define DECLARE_LHASH_COMP_FN(name, o_type) \ - int name##_LHASH_COMP(const void *, const void *); -#define IMPLEMENT_LHASH_COMP_FN(name, o_type) \ - int name##_LHASH_COMP(const void *arg1, const void *arg2) { \ - const o_type *a = arg1; \ - const o_type *b = arg2; \ - return name##_cmp(a,b); } -#define LHASH_COMP_FN(name) name##_LHASH_COMP - -/* Third: "doall" functions */ -#define DECLARE_LHASH_DOALL_FN(name, o_type) \ - void name##_LHASH_DOALL(void *); -#define IMPLEMENT_LHASH_DOALL_FN(name, o_type) \ - void name##_LHASH_DOALL(void *arg) { \ - o_type *a = arg; \ - name##_doall(a); } -#define LHASH_DOALL_FN(name) name##_LHASH_DOALL - -/* Fourth: "doall_arg" functions */ -#define DECLARE_LHASH_DOALL_ARG_FN(name, o_type, a_type) \ - void name##_LHASH_DOALL_ARG(void *, void *); -#define IMPLEMENT_LHASH_DOALL_ARG_FN(name, o_type, a_type) \ - void name##_LHASH_DOALL_ARG(void *arg1, void *arg2) { \ - o_type *a = arg1; \ - a_type *b = arg2; \ - name##_doall_arg(a, b); } -#define LHASH_DOALL_ARG_FN(name) name##_LHASH_DOALL_ARG - -typedef struct lhash_st - { - LHASH_NODE **b; - LHASH_COMP_FN_TYPE comp; - LHASH_HASH_FN_TYPE hash; - unsigned int num_nodes; - unsigned int num_alloc_nodes; - unsigned int p; - unsigned int pmax; - unsigned long up_load; /* load times 256 */ - unsigned long down_load; /* load times 256 */ - unsigned long num_items; - - unsigned long num_expands; - unsigned long num_expand_reallocs; - unsigned long num_contracts; - unsigned long num_contract_reallocs; - unsigned long num_hash_calls; - unsigned long num_comp_calls; - unsigned long num_insert; - unsigned long num_replace; - unsigned long num_delete; - unsigned long num_no_delete; - unsigned long num_retrieve; - unsigned long num_retrieve_miss; - unsigned long num_hash_comps; - - int error; - } _LHASH; /* Do not use _LHASH directly, use LHASH_OF - * and friends */ - -#define LH_LOAD_MULT 256 - -/* Indicates a malloc() error in the last call, this is only bad - * in lh_insert(). */ -#define lh_error(lh) ((lh)->error) - -_LHASH *lh_new(LHASH_HASH_FN_TYPE h, LHASH_COMP_FN_TYPE c); -void lh_free(_LHASH *lh); -void *lh_insert(_LHASH *lh, void *data); -void *lh_delete(_LHASH *lh, const void *data); -void *lh_retrieve(_LHASH *lh, const void *data); -void lh_doall(_LHASH *lh, LHASH_DOALL_FN_TYPE func); -void lh_doall_arg(_LHASH *lh, LHASH_DOALL_ARG_FN_TYPE func, void *arg); -unsigned long lh_strhash(const char *c); -unsigned long lh_num_items(const _LHASH *lh); - -#ifndef OPENSSL_NO_FP_API -void lh_stats(const _LHASH *lh, FILE *out); -void lh_node_stats(const _LHASH *lh, FILE *out); -void lh_node_usage_stats(const _LHASH *lh, FILE *out); -#endif - -#ifndef OPENSSL_NO_BIO -void lh_stats_bio(const _LHASH *lh, BIO *out); -void lh_node_stats_bio(const _LHASH *lh, BIO *out); -void lh_node_usage_stats_bio(const _LHASH *lh, BIO *out); -#endif - -/* Type checking... */ - -#define LHASH_OF(type) struct lhash_st_##type - -#define DECLARE_LHASH_OF(type) LHASH_OF(type) { int dummy; } - -#define CHECKED_LHASH_OF(type,lh) \ - ((_LHASH *)CHECKED_PTR_OF(LHASH_OF(type),lh)) - -/* Define wrapper functions. */ -#define LHM_lh_new(type, name) \ - ((LHASH_OF(type) *)lh_new(LHASH_HASH_FN(name), LHASH_COMP_FN(name))) -#define LHM_lh_error(type, lh) \ - lh_error(CHECKED_LHASH_OF(type,lh)) -#define LHM_lh_insert(type, lh, inst) \ - ((type *)lh_insert(CHECKED_LHASH_OF(type, lh), \ - CHECKED_PTR_OF(type, inst))) -#define LHM_lh_retrieve(type, lh, inst) \ - ((type *)lh_retrieve(CHECKED_LHASH_OF(type, lh), \ - CHECKED_PTR_OF(type, inst))) -#define LHM_lh_delete(type, lh, inst) \ - ((type *)lh_delete(CHECKED_LHASH_OF(type, lh), \ - CHECKED_PTR_OF(type, inst))) -#define LHM_lh_doall(type, lh,fn) lh_doall(CHECKED_LHASH_OF(type, lh), fn) -#define LHM_lh_doall_arg(type, lh, fn, arg_type, arg) \ - lh_doall_arg(CHECKED_LHASH_OF(type, lh), fn, CHECKED_PTR_OF(arg_type, arg)) -#define LHM_lh_num_items(type, lh) lh_num_items(CHECKED_LHASH_OF(type, lh)) -#define LHM_lh_down_load(type, lh) (CHECKED_LHASH_OF(type, lh)->down_load) -#define LHM_lh_node_stats_bio(type, lh, out) \ - lh_node_stats_bio(CHECKED_LHASH_OF(type, lh), out) -#define LHM_lh_node_usage_stats_bio(type, lh, out) \ - lh_node_usage_stats_bio(CHECKED_LHASH_OF(type, lh), out) -#define LHM_lh_stats_bio(type, lh, out) \ - lh_stats_bio(CHECKED_LHASH_OF(type, lh), out) -#define LHM_lh_free(type, lh) lh_free(CHECKED_LHASH_OF(type, lh)) - -DECLARE_LHASH_OF(OPENSSL_STRING); -DECLARE_LHASH_OF(OPENSSL_CSTRING); - -#ifdef __cplusplus -} -#endif - -#endif - diff --git a/src/plugins/ftp/openssl/obj_mac.h b/src/plugins/ftp/openssl/obj_mac.h deleted file mode 100644 index b5ea7cda..00000000 --- a/src/plugins/ftp/openssl/obj_mac.h +++ /dev/null @@ -1,4032 +0,0 @@ -/* crypto/objects/obj_mac.h */ - -/* THIS FILE IS GENERATED FROM objects.txt by objects.pl via the - * following command: - * perl objects.pl objects.txt obj_mac.num obj_mac.h - */ - -/* Copyright (C) 1995-1997 Eric Young (eay@cryptsoft.com) - * All rights reserved. - * - * This package is an SSL implementation written - * by Eric Young (eay@cryptsoft.com). - * The implementation was written so as to conform with Netscapes SSL. - * - * This library is free for commercial and non-commercial use as long as - * the following conditions are aheared to. The following conditions - * apply to all code found in this distribution, be it the RC4, RSA, - * lhash, DES, etc., code; not just the SSL code. The SSL documentation - * included with this distribution is covered by the same copyright terms - * except that the holder is Tim Hudson (tjh@cryptsoft.com). - * - * Copyright remains Eric Young's, and as such any Copyright notices in - * the code are not to be removed. - * If this package is used in a product, Eric Young should be given attribution - * as the author of the parts of the library used. - * This can be in the form of a textual message at program startup or - * in documentation (online or textual) provided with the package. - * - * 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 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. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * "This product includes cryptographic software written by - * Eric Young (eay@cryptsoft.com)" - * The word 'cryptographic' can be left out if the rouines from the library - * being used are not cryptographic related :-). - * 4. If you include any Windows specific code (or a derivative thereof) from - * the apps directory (application code) you must include an acknowledgement: - * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" - * - * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``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 AUTHOR 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. - * - * The licence and distribution terms for any publically available version or - * derivative of this code cannot be changed. i.e. this code cannot simply be - * copied and put under another distribution licence - * [including the GNU Public Licence.] - */ - -#define SN_undef "UNDEF" -#define LN_undef "undefined" -#define NID_undef 0 -#define OBJ_undef 0L - -#define SN_itu_t "ITU-T" -#define LN_itu_t "itu-t" -#define NID_itu_t 645 -#define OBJ_itu_t 0L - -#define NID_ccitt 404 -#define OBJ_ccitt OBJ_itu_t - -#define SN_iso "ISO" -#define LN_iso "iso" -#define NID_iso 181 -#define OBJ_iso 1L - -#define SN_joint_iso_itu_t "JOINT-ISO-ITU-T" -#define LN_joint_iso_itu_t "joint-iso-itu-t" -#define NID_joint_iso_itu_t 646 -#define OBJ_joint_iso_itu_t 2L - -#define NID_joint_iso_ccitt 393 -#define OBJ_joint_iso_ccitt OBJ_joint_iso_itu_t - -#define SN_member_body "member-body" -#define LN_member_body "ISO Member Body" -#define NID_member_body 182 -#define OBJ_member_body OBJ_iso,2L - -#define SN_identified_organization "identified-organization" -#define NID_identified_organization 676 -#define OBJ_identified_organization OBJ_iso,3L - -#define SN_hmac_md5 "HMAC-MD5" -#define LN_hmac_md5 "hmac-md5" -#define NID_hmac_md5 780 -#define OBJ_hmac_md5 OBJ_identified_organization,6L,1L,5L,5L,8L,1L,1L - -#define SN_hmac_sha1 "HMAC-SHA1" -#define LN_hmac_sha1 "hmac-sha1" -#define NID_hmac_sha1 781 -#define OBJ_hmac_sha1 OBJ_identified_organization,6L,1L,5L,5L,8L,1L,2L - -#define SN_certicom_arc "certicom-arc" -#define NID_certicom_arc 677 -#define OBJ_certicom_arc OBJ_identified_organization,132L - -#define SN_international_organizations "international-organizations" -#define LN_international_organizations "International Organizations" -#define NID_international_organizations 647 -#define OBJ_international_organizations OBJ_joint_iso_itu_t,23L - -#define SN_wap "wap" -#define NID_wap 678 -#define OBJ_wap OBJ_international_organizations,43L - -#define SN_wap_wsg "wap-wsg" -#define NID_wap_wsg 679 -#define OBJ_wap_wsg OBJ_wap,1L - -#define SN_selected_attribute_types "selected-attribute-types" -#define LN_selected_attribute_types "Selected Attribute Types" -#define NID_selected_attribute_types 394 -#define OBJ_selected_attribute_types OBJ_joint_iso_itu_t,5L,1L,5L - -#define SN_clearance "clearance" -#define NID_clearance 395 -#define OBJ_clearance OBJ_selected_attribute_types,55L - -#define SN_ISO_US "ISO-US" -#define LN_ISO_US "ISO US Member Body" -#define NID_ISO_US 183 -#define OBJ_ISO_US OBJ_member_body,840L - -#define SN_X9_57 "X9-57" -#define LN_X9_57 "X9.57" -#define NID_X9_57 184 -#define OBJ_X9_57 OBJ_ISO_US,10040L - -#define SN_X9cm "X9cm" -#define LN_X9cm "X9.57 CM ?" -#define NID_X9cm 185 -#define OBJ_X9cm OBJ_X9_57,4L - -#define SN_dsa "DSA" -#define LN_dsa "dsaEncryption" -#define NID_dsa 116 -#define OBJ_dsa OBJ_X9cm,1L - -#define SN_dsaWithSHA1 "DSA-SHA1" -#define LN_dsaWithSHA1 "dsaWithSHA1" -#define NID_dsaWithSHA1 113 -#define OBJ_dsaWithSHA1 OBJ_X9cm,3L - -#define SN_ansi_X9_62 "ansi-X9-62" -#define LN_ansi_X9_62 "ANSI X9.62" -#define NID_ansi_X9_62 405 -#define OBJ_ansi_X9_62 OBJ_ISO_US,10045L - -#define OBJ_X9_62_id_fieldType OBJ_ansi_X9_62,1L - -#define SN_X9_62_prime_field "prime-field" -#define NID_X9_62_prime_field 406 -#define OBJ_X9_62_prime_field OBJ_X9_62_id_fieldType,1L - -#define SN_X9_62_characteristic_two_field "characteristic-two-field" -#define NID_X9_62_characteristic_two_field 407 -#define OBJ_X9_62_characteristic_two_field OBJ_X9_62_id_fieldType,2L - -#define SN_X9_62_id_characteristic_two_basis "id-characteristic-two-basis" -#define NID_X9_62_id_characteristic_two_basis 680 -#define OBJ_X9_62_id_characteristic_two_basis OBJ_X9_62_characteristic_two_field,3L - -#define SN_X9_62_onBasis "onBasis" -#define NID_X9_62_onBasis 681 -#define OBJ_X9_62_onBasis OBJ_X9_62_id_characteristic_two_basis,1L - -#define SN_X9_62_tpBasis "tpBasis" -#define NID_X9_62_tpBasis 682 -#define OBJ_X9_62_tpBasis OBJ_X9_62_id_characteristic_two_basis,2L - -#define SN_X9_62_ppBasis "ppBasis" -#define NID_X9_62_ppBasis 683 -#define OBJ_X9_62_ppBasis OBJ_X9_62_id_characteristic_two_basis,3L - -#define OBJ_X9_62_id_publicKeyType OBJ_ansi_X9_62,2L - -#define SN_X9_62_id_ecPublicKey "id-ecPublicKey" -#define NID_X9_62_id_ecPublicKey 408 -#define OBJ_X9_62_id_ecPublicKey OBJ_X9_62_id_publicKeyType,1L - -#define OBJ_X9_62_ellipticCurve OBJ_ansi_X9_62,3L - -#define OBJ_X9_62_c_TwoCurve OBJ_X9_62_ellipticCurve,0L - -#define SN_X9_62_c2pnb163v1 "c2pnb163v1" -#define NID_X9_62_c2pnb163v1 684 -#define OBJ_X9_62_c2pnb163v1 OBJ_X9_62_c_TwoCurve,1L - -#define SN_X9_62_c2pnb163v2 "c2pnb163v2" -#define NID_X9_62_c2pnb163v2 685 -#define OBJ_X9_62_c2pnb163v2 OBJ_X9_62_c_TwoCurve,2L - -#define SN_X9_62_c2pnb163v3 "c2pnb163v3" -#define NID_X9_62_c2pnb163v3 686 -#define OBJ_X9_62_c2pnb163v3 OBJ_X9_62_c_TwoCurve,3L - -#define SN_X9_62_c2pnb176v1 "c2pnb176v1" -#define NID_X9_62_c2pnb176v1 687 -#define OBJ_X9_62_c2pnb176v1 OBJ_X9_62_c_TwoCurve,4L - -#define SN_X9_62_c2tnb191v1 "c2tnb191v1" -#define NID_X9_62_c2tnb191v1 688 -#define OBJ_X9_62_c2tnb191v1 OBJ_X9_62_c_TwoCurve,5L - -#define SN_X9_62_c2tnb191v2 "c2tnb191v2" -#define NID_X9_62_c2tnb191v2 689 -#define OBJ_X9_62_c2tnb191v2 OBJ_X9_62_c_TwoCurve,6L - -#define SN_X9_62_c2tnb191v3 "c2tnb191v3" -#define NID_X9_62_c2tnb191v3 690 -#define OBJ_X9_62_c2tnb191v3 OBJ_X9_62_c_TwoCurve,7L - -#define SN_X9_62_c2onb191v4 "c2onb191v4" -#define NID_X9_62_c2onb191v4 691 -#define OBJ_X9_62_c2onb191v4 OBJ_X9_62_c_TwoCurve,8L - -#define SN_X9_62_c2onb191v5 "c2onb191v5" -#define NID_X9_62_c2onb191v5 692 -#define OBJ_X9_62_c2onb191v5 OBJ_X9_62_c_TwoCurve,9L - -#define SN_X9_62_c2pnb208w1 "c2pnb208w1" -#define NID_X9_62_c2pnb208w1 693 -#define OBJ_X9_62_c2pnb208w1 OBJ_X9_62_c_TwoCurve,10L - -#define SN_X9_62_c2tnb239v1 "c2tnb239v1" -#define NID_X9_62_c2tnb239v1 694 -#define OBJ_X9_62_c2tnb239v1 OBJ_X9_62_c_TwoCurve,11L - -#define SN_X9_62_c2tnb239v2 "c2tnb239v2" -#define NID_X9_62_c2tnb239v2 695 -#define OBJ_X9_62_c2tnb239v2 OBJ_X9_62_c_TwoCurve,12L - -#define SN_X9_62_c2tnb239v3 "c2tnb239v3" -#define NID_X9_62_c2tnb239v3 696 -#define OBJ_X9_62_c2tnb239v3 OBJ_X9_62_c_TwoCurve,13L - -#define SN_X9_62_c2onb239v4 "c2onb239v4" -#define NID_X9_62_c2onb239v4 697 -#define OBJ_X9_62_c2onb239v4 OBJ_X9_62_c_TwoCurve,14L - -#define SN_X9_62_c2onb239v5 "c2onb239v5" -#define NID_X9_62_c2onb239v5 698 -#define OBJ_X9_62_c2onb239v5 OBJ_X9_62_c_TwoCurve,15L - -#define SN_X9_62_c2pnb272w1 "c2pnb272w1" -#define NID_X9_62_c2pnb272w1 699 -#define OBJ_X9_62_c2pnb272w1 OBJ_X9_62_c_TwoCurve,16L - -#define SN_X9_62_c2pnb304w1 "c2pnb304w1" -#define NID_X9_62_c2pnb304w1 700 -#define OBJ_X9_62_c2pnb304w1 OBJ_X9_62_c_TwoCurve,17L - -#define SN_X9_62_c2tnb359v1 "c2tnb359v1" -#define NID_X9_62_c2tnb359v1 701 -#define OBJ_X9_62_c2tnb359v1 OBJ_X9_62_c_TwoCurve,18L - -#define SN_X9_62_c2pnb368w1 "c2pnb368w1" -#define NID_X9_62_c2pnb368w1 702 -#define OBJ_X9_62_c2pnb368w1 OBJ_X9_62_c_TwoCurve,19L - -#define SN_X9_62_c2tnb431r1 "c2tnb431r1" -#define NID_X9_62_c2tnb431r1 703 -#define OBJ_X9_62_c2tnb431r1 OBJ_X9_62_c_TwoCurve,20L - -#define OBJ_X9_62_primeCurve OBJ_X9_62_ellipticCurve,1L - -#define SN_X9_62_prime192v1 "prime192v1" -#define NID_X9_62_prime192v1 409 -#define OBJ_X9_62_prime192v1 OBJ_X9_62_primeCurve,1L - -#define SN_X9_62_prime192v2 "prime192v2" -#define NID_X9_62_prime192v2 410 -#define OBJ_X9_62_prime192v2 OBJ_X9_62_primeCurve,2L - -#define SN_X9_62_prime192v3 "prime192v3" -#define NID_X9_62_prime192v3 411 -#define OBJ_X9_62_prime192v3 OBJ_X9_62_primeCurve,3L - -#define SN_X9_62_prime239v1 "prime239v1" -#define NID_X9_62_prime239v1 412 -#define OBJ_X9_62_prime239v1 OBJ_X9_62_primeCurve,4L - -#define SN_X9_62_prime239v2 "prime239v2" -#define NID_X9_62_prime239v2 413 -#define OBJ_X9_62_prime239v2 OBJ_X9_62_primeCurve,5L - -#define SN_X9_62_prime239v3 "prime239v3" -#define NID_X9_62_prime239v3 414 -#define OBJ_X9_62_prime239v3 OBJ_X9_62_primeCurve,6L - -#define SN_X9_62_prime256v1 "prime256v1" -#define NID_X9_62_prime256v1 415 -#define OBJ_X9_62_prime256v1 OBJ_X9_62_primeCurve,7L - -#define OBJ_X9_62_id_ecSigType OBJ_ansi_X9_62,4L - -#define SN_ecdsa_with_SHA1 "ecdsa-with-SHA1" -#define NID_ecdsa_with_SHA1 416 -#define OBJ_ecdsa_with_SHA1 OBJ_X9_62_id_ecSigType,1L - -#define SN_ecdsa_with_Recommended "ecdsa-with-Recommended" -#define NID_ecdsa_with_Recommended 791 -#define OBJ_ecdsa_with_Recommended OBJ_X9_62_id_ecSigType,2L - -#define SN_ecdsa_with_Specified "ecdsa-with-Specified" -#define NID_ecdsa_with_Specified 792 -#define OBJ_ecdsa_with_Specified OBJ_X9_62_id_ecSigType,3L - -#define SN_ecdsa_with_SHA224 "ecdsa-with-SHA224" -#define NID_ecdsa_with_SHA224 793 -#define OBJ_ecdsa_with_SHA224 OBJ_ecdsa_with_Specified,1L - -#define SN_ecdsa_with_SHA256 "ecdsa-with-SHA256" -#define NID_ecdsa_with_SHA256 794 -#define OBJ_ecdsa_with_SHA256 OBJ_ecdsa_with_Specified,2L - -#define SN_ecdsa_with_SHA384 "ecdsa-with-SHA384" -#define NID_ecdsa_with_SHA384 795 -#define OBJ_ecdsa_with_SHA384 OBJ_ecdsa_with_Specified,3L - -#define SN_ecdsa_with_SHA512 "ecdsa-with-SHA512" -#define NID_ecdsa_with_SHA512 796 -#define OBJ_ecdsa_with_SHA512 OBJ_ecdsa_with_Specified,4L - -#define OBJ_secg_ellipticCurve OBJ_certicom_arc,0L - -#define SN_secp112r1 "secp112r1" -#define NID_secp112r1 704 -#define OBJ_secp112r1 OBJ_secg_ellipticCurve,6L - -#define SN_secp112r2 "secp112r2" -#define NID_secp112r2 705 -#define OBJ_secp112r2 OBJ_secg_ellipticCurve,7L - -#define SN_secp128r1 "secp128r1" -#define NID_secp128r1 706 -#define OBJ_secp128r1 OBJ_secg_ellipticCurve,28L - -#define SN_secp128r2 "secp128r2" -#define NID_secp128r2 707 -#define OBJ_secp128r2 OBJ_secg_ellipticCurve,29L - -#define SN_secp160k1 "secp160k1" -#define NID_secp160k1 708 -#define OBJ_secp160k1 OBJ_secg_ellipticCurve,9L - -#define SN_secp160r1 "secp160r1" -#define NID_secp160r1 709 -#define OBJ_secp160r1 OBJ_secg_ellipticCurve,8L - -#define SN_secp160r2 "secp160r2" -#define NID_secp160r2 710 -#define OBJ_secp160r2 OBJ_secg_ellipticCurve,30L - -#define SN_secp192k1 "secp192k1" -#define NID_secp192k1 711 -#define OBJ_secp192k1 OBJ_secg_ellipticCurve,31L - -#define SN_secp224k1 "secp224k1" -#define NID_secp224k1 712 -#define OBJ_secp224k1 OBJ_secg_ellipticCurve,32L - -#define SN_secp224r1 "secp224r1" -#define NID_secp224r1 713 -#define OBJ_secp224r1 OBJ_secg_ellipticCurve,33L - -#define SN_secp256k1 "secp256k1" -#define NID_secp256k1 714 -#define OBJ_secp256k1 OBJ_secg_ellipticCurve,10L - -#define SN_secp384r1 "secp384r1" -#define NID_secp384r1 715 -#define OBJ_secp384r1 OBJ_secg_ellipticCurve,34L - -#define SN_secp521r1 "secp521r1" -#define NID_secp521r1 716 -#define OBJ_secp521r1 OBJ_secg_ellipticCurve,35L - -#define SN_sect113r1 "sect113r1" -#define NID_sect113r1 717 -#define OBJ_sect113r1 OBJ_secg_ellipticCurve,4L - -#define SN_sect113r2 "sect113r2" -#define NID_sect113r2 718 -#define OBJ_sect113r2 OBJ_secg_ellipticCurve,5L - -#define SN_sect131r1 "sect131r1" -#define NID_sect131r1 719 -#define OBJ_sect131r1 OBJ_secg_ellipticCurve,22L - -#define SN_sect131r2 "sect131r2" -#define NID_sect131r2 720 -#define OBJ_sect131r2 OBJ_secg_ellipticCurve,23L - -#define SN_sect163k1 "sect163k1" -#define NID_sect163k1 721 -#define OBJ_sect163k1 OBJ_secg_ellipticCurve,1L - -#define SN_sect163r1 "sect163r1" -#define NID_sect163r1 722 -#define OBJ_sect163r1 OBJ_secg_ellipticCurve,2L - -#define SN_sect163r2 "sect163r2" -#define NID_sect163r2 723 -#define OBJ_sect163r2 OBJ_secg_ellipticCurve,15L - -#define SN_sect193r1 "sect193r1" -#define NID_sect193r1 724 -#define OBJ_sect193r1 OBJ_secg_ellipticCurve,24L - -#define SN_sect193r2 "sect193r2" -#define NID_sect193r2 725 -#define OBJ_sect193r2 OBJ_secg_ellipticCurve,25L - -#define SN_sect233k1 "sect233k1" -#define NID_sect233k1 726 -#define OBJ_sect233k1 OBJ_secg_ellipticCurve,26L - -#define SN_sect233r1 "sect233r1" -#define NID_sect233r1 727 -#define OBJ_sect233r1 OBJ_secg_ellipticCurve,27L - -#define SN_sect239k1 "sect239k1" -#define NID_sect239k1 728 -#define OBJ_sect239k1 OBJ_secg_ellipticCurve,3L - -#define SN_sect283k1 "sect283k1" -#define NID_sect283k1 729 -#define OBJ_sect283k1 OBJ_secg_ellipticCurve,16L - -#define SN_sect283r1 "sect283r1" -#define NID_sect283r1 730 -#define OBJ_sect283r1 OBJ_secg_ellipticCurve,17L - -#define SN_sect409k1 "sect409k1" -#define NID_sect409k1 731 -#define OBJ_sect409k1 OBJ_secg_ellipticCurve,36L - -#define SN_sect409r1 "sect409r1" -#define NID_sect409r1 732 -#define OBJ_sect409r1 OBJ_secg_ellipticCurve,37L - -#define SN_sect571k1 "sect571k1" -#define NID_sect571k1 733 -#define OBJ_sect571k1 OBJ_secg_ellipticCurve,38L - -#define SN_sect571r1 "sect571r1" -#define NID_sect571r1 734 -#define OBJ_sect571r1 OBJ_secg_ellipticCurve,39L - -#define OBJ_wap_wsg_idm_ecid OBJ_wap_wsg,4L - -#define SN_wap_wsg_idm_ecid_wtls1 "wap-wsg-idm-ecid-wtls1" -#define NID_wap_wsg_idm_ecid_wtls1 735 -#define OBJ_wap_wsg_idm_ecid_wtls1 OBJ_wap_wsg_idm_ecid,1L - -#define SN_wap_wsg_idm_ecid_wtls3 "wap-wsg-idm-ecid-wtls3" -#define NID_wap_wsg_idm_ecid_wtls3 736 -#define OBJ_wap_wsg_idm_ecid_wtls3 OBJ_wap_wsg_idm_ecid,3L - -#define SN_wap_wsg_idm_ecid_wtls4 "wap-wsg-idm-ecid-wtls4" -#define NID_wap_wsg_idm_ecid_wtls4 737 -#define OBJ_wap_wsg_idm_ecid_wtls4 OBJ_wap_wsg_idm_ecid,4L - -#define SN_wap_wsg_idm_ecid_wtls5 "wap-wsg-idm-ecid-wtls5" -#define NID_wap_wsg_idm_ecid_wtls5 738 -#define OBJ_wap_wsg_idm_ecid_wtls5 OBJ_wap_wsg_idm_ecid,5L - -#define SN_wap_wsg_idm_ecid_wtls6 "wap-wsg-idm-ecid-wtls6" -#define NID_wap_wsg_idm_ecid_wtls6 739 -#define OBJ_wap_wsg_idm_ecid_wtls6 OBJ_wap_wsg_idm_ecid,6L - -#define SN_wap_wsg_idm_ecid_wtls7 "wap-wsg-idm-ecid-wtls7" -#define NID_wap_wsg_idm_ecid_wtls7 740 -#define OBJ_wap_wsg_idm_ecid_wtls7 OBJ_wap_wsg_idm_ecid,7L - -#define SN_wap_wsg_idm_ecid_wtls8 "wap-wsg-idm-ecid-wtls8" -#define NID_wap_wsg_idm_ecid_wtls8 741 -#define OBJ_wap_wsg_idm_ecid_wtls8 OBJ_wap_wsg_idm_ecid,8L - -#define SN_wap_wsg_idm_ecid_wtls9 "wap-wsg-idm-ecid-wtls9" -#define NID_wap_wsg_idm_ecid_wtls9 742 -#define OBJ_wap_wsg_idm_ecid_wtls9 OBJ_wap_wsg_idm_ecid,9L - -#define SN_wap_wsg_idm_ecid_wtls10 "wap-wsg-idm-ecid-wtls10" -#define NID_wap_wsg_idm_ecid_wtls10 743 -#define OBJ_wap_wsg_idm_ecid_wtls10 OBJ_wap_wsg_idm_ecid,10L - -#define SN_wap_wsg_idm_ecid_wtls11 "wap-wsg-idm-ecid-wtls11" -#define NID_wap_wsg_idm_ecid_wtls11 744 -#define OBJ_wap_wsg_idm_ecid_wtls11 OBJ_wap_wsg_idm_ecid,11L - -#define SN_wap_wsg_idm_ecid_wtls12 "wap-wsg-idm-ecid-wtls12" -#define NID_wap_wsg_idm_ecid_wtls12 745 -#define OBJ_wap_wsg_idm_ecid_wtls12 OBJ_wap_wsg_idm_ecid,12L - -#define SN_cast5_cbc "CAST5-CBC" -#define LN_cast5_cbc "cast5-cbc" -#define NID_cast5_cbc 108 -#define OBJ_cast5_cbc OBJ_ISO_US,113533L,7L,66L,10L - -#define SN_cast5_ecb "CAST5-ECB" -#define LN_cast5_ecb "cast5-ecb" -#define NID_cast5_ecb 109 - -#define SN_cast5_cfb64 "CAST5-CFB" -#define LN_cast5_cfb64 "cast5-cfb" -#define NID_cast5_cfb64 110 - -#define SN_cast5_ofb64 "CAST5-OFB" -#define LN_cast5_ofb64 "cast5-ofb" -#define NID_cast5_ofb64 111 - -#define LN_pbeWithMD5AndCast5_CBC "pbeWithMD5AndCast5CBC" -#define NID_pbeWithMD5AndCast5_CBC 112 -#define OBJ_pbeWithMD5AndCast5_CBC OBJ_ISO_US,113533L,7L,66L,12L - -#define SN_id_PasswordBasedMAC "id-PasswordBasedMAC" -#define LN_id_PasswordBasedMAC "password based MAC" -#define NID_id_PasswordBasedMAC 782 -#define OBJ_id_PasswordBasedMAC OBJ_ISO_US,113533L,7L,66L,13L - -#define SN_id_DHBasedMac "id-DHBasedMac" -#define LN_id_DHBasedMac "Diffie-Hellman based MAC" -#define NID_id_DHBasedMac 783 -#define OBJ_id_DHBasedMac OBJ_ISO_US,113533L,7L,66L,30L - -#define SN_rsadsi "rsadsi" -#define LN_rsadsi "RSA Data Security, Inc." -#define NID_rsadsi 1 -#define OBJ_rsadsi OBJ_ISO_US,113549L - -#define SN_pkcs "pkcs" -#define LN_pkcs "RSA Data Security, Inc. PKCS" -#define NID_pkcs 2 -#define OBJ_pkcs OBJ_rsadsi,1L - -#define SN_pkcs1 "pkcs1" -#define NID_pkcs1 186 -#define OBJ_pkcs1 OBJ_pkcs,1L - -#define LN_rsaEncryption "rsaEncryption" -#define NID_rsaEncryption 6 -#define OBJ_rsaEncryption OBJ_pkcs1,1L - -#define SN_md2WithRSAEncryption "RSA-MD2" -#define LN_md2WithRSAEncryption "md2WithRSAEncryption" -#define NID_md2WithRSAEncryption 7 -#define OBJ_md2WithRSAEncryption OBJ_pkcs1,2L - -#define SN_md4WithRSAEncryption "RSA-MD4" -#define LN_md4WithRSAEncryption "md4WithRSAEncryption" -#define NID_md4WithRSAEncryption 396 -#define OBJ_md4WithRSAEncryption OBJ_pkcs1,3L - -#define SN_md5WithRSAEncryption "RSA-MD5" -#define LN_md5WithRSAEncryption "md5WithRSAEncryption" -#define NID_md5WithRSAEncryption 8 -#define OBJ_md5WithRSAEncryption OBJ_pkcs1,4L - -#define SN_sha1WithRSAEncryption "RSA-SHA1" -#define LN_sha1WithRSAEncryption "sha1WithRSAEncryption" -#define NID_sha1WithRSAEncryption 65 -#define OBJ_sha1WithRSAEncryption OBJ_pkcs1,5L - -#define SN_rsaesOaep "RSAES-OAEP" -#define LN_rsaesOaep "rsaesOaep" -#define NID_rsaesOaep 919 -#define OBJ_rsaesOaep OBJ_pkcs1,7L - -#define SN_mgf1 "MGF1" -#define LN_mgf1 "mgf1" -#define NID_mgf1 911 -#define OBJ_mgf1 OBJ_pkcs1,8L - -#define SN_rsassaPss "RSASSA-PSS" -#define LN_rsassaPss "rsassaPss" -#define NID_rsassaPss 912 -#define OBJ_rsassaPss OBJ_pkcs1,10L - -#define SN_sha256WithRSAEncryption "RSA-SHA256" -#define LN_sha256WithRSAEncryption "sha256WithRSAEncryption" -#define NID_sha256WithRSAEncryption 668 -#define OBJ_sha256WithRSAEncryption OBJ_pkcs1,11L - -#define SN_sha384WithRSAEncryption "RSA-SHA384" -#define LN_sha384WithRSAEncryption "sha384WithRSAEncryption" -#define NID_sha384WithRSAEncryption 669 -#define OBJ_sha384WithRSAEncryption OBJ_pkcs1,12L - -#define SN_sha512WithRSAEncryption "RSA-SHA512" -#define LN_sha512WithRSAEncryption "sha512WithRSAEncryption" -#define NID_sha512WithRSAEncryption 670 -#define OBJ_sha512WithRSAEncryption OBJ_pkcs1,13L - -#define SN_sha224WithRSAEncryption "RSA-SHA224" -#define LN_sha224WithRSAEncryption "sha224WithRSAEncryption" -#define NID_sha224WithRSAEncryption 671 -#define OBJ_sha224WithRSAEncryption OBJ_pkcs1,14L - -#define SN_pkcs3 "pkcs3" -#define NID_pkcs3 27 -#define OBJ_pkcs3 OBJ_pkcs,3L - -#define LN_dhKeyAgreement "dhKeyAgreement" -#define NID_dhKeyAgreement 28 -#define OBJ_dhKeyAgreement OBJ_pkcs3,1L - -#define SN_pkcs5 "pkcs5" -#define NID_pkcs5 187 -#define OBJ_pkcs5 OBJ_pkcs,5L - -#define SN_pbeWithMD2AndDES_CBC "PBE-MD2-DES" -#define LN_pbeWithMD2AndDES_CBC "pbeWithMD2AndDES-CBC" -#define NID_pbeWithMD2AndDES_CBC 9 -#define OBJ_pbeWithMD2AndDES_CBC OBJ_pkcs5,1L - -#define SN_pbeWithMD5AndDES_CBC "PBE-MD5-DES" -#define LN_pbeWithMD5AndDES_CBC "pbeWithMD5AndDES-CBC" -#define NID_pbeWithMD5AndDES_CBC 10 -#define OBJ_pbeWithMD5AndDES_CBC OBJ_pkcs5,3L - -#define SN_pbeWithMD2AndRC2_CBC "PBE-MD2-RC2-64" -#define LN_pbeWithMD2AndRC2_CBC "pbeWithMD2AndRC2-CBC" -#define NID_pbeWithMD2AndRC2_CBC 168 -#define OBJ_pbeWithMD2AndRC2_CBC OBJ_pkcs5,4L - -#define SN_pbeWithMD5AndRC2_CBC "PBE-MD5-RC2-64" -#define LN_pbeWithMD5AndRC2_CBC "pbeWithMD5AndRC2-CBC" -#define NID_pbeWithMD5AndRC2_CBC 169 -#define OBJ_pbeWithMD5AndRC2_CBC OBJ_pkcs5,6L - -#define SN_pbeWithSHA1AndDES_CBC "PBE-SHA1-DES" -#define LN_pbeWithSHA1AndDES_CBC "pbeWithSHA1AndDES-CBC" -#define NID_pbeWithSHA1AndDES_CBC 170 -#define OBJ_pbeWithSHA1AndDES_CBC OBJ_pkcs5,10L - -#define SN_pbeWithSHA1AndRC2_CBC "PBE-SHA1-RC2-64" -#define LN_pbeWithSHA1AndRC2_CBC "pbeWithSHA1AndRC2-CBC" -#define NID_pbeWithSHA1AndRC2_CBC 68 -#define OBJ_pbeWithSHA1AndRC2_CBC OBJ_pkcs5,11L - -#define LN_id_pbkdf2 "PBKDF2" -#define NID_id_pbkdf2 69 -#define OBJ_id_pbkdf2 OBJ_pkcs5,12L - -#define LN_pbes2 "PBES2" -#define NID_pbes2 161 -#define OBJ_pbes2 OBJ_pkcs5,13L - -#define LN_pbmac1 "PBMAC1" -#define NID_pbmac1 162 -#define OBJ_pbmac1 OBJ_pkcs5,14L - -#define SN_pkcs7 "pkcs7" -#define NID_pkcs7 20 -#define OBJ_pkcs7 OBJ_pkcs,7L - -#define LN_pkcs7_data "pkcs7-data" -#define NID_pkcs7_data 21 -#define OBJ_pkcs7_data OBJ_pkcs7,1L - -#define LN_pkcs7_signed "pkcs7-signedData" -#define NID_pkcs7_signed 22 -#define OBJ_pkcs7_signed OBJ_pkcs7,2L - -#define LN_pkcs7_enveloped "pkcs7-envelopedData" -#define NID_pkcs7_enveloped 23 -#define OBJ_pkcs7_enveloped OBJ_pkcs7,3L - -#define LN_pkcs7_signedAndEnveloped "pkcs7-signedAndEnvelopedData" -#define NID_pkcs7_signedAndEnveloped 24 -#define OBJ_pkcs7_signedAndEnveloped OBJ_pkcs7,4L - -#define LN_pkcs7_digest "pkcs7-digestData" -#define NID_pkcs7_digest 25 -#define OBJ_pkcs7_digest OBJ_pkcs7,5L - -#define LN_pkcs7_encrypted "pkcs7-encryptedData" -#define NID_pkcs7_encrypted 26 -#define OBJ_pkcs7_encrypted OBJ_pkcs7,6L - -#define SN_pkcs9 "pkcs9" -#define NID_pkcs9 47 -#define OBJ_pkcs9 OBJ_pkcs,9L - -#define LN_pkcs9_emailAddress "emailAddress" -#define NID_pkcs9_emailAddress 48 -#define OBJ_pkcs9_emailAddress OBJ_pkcs9,1L - -#define LN_pkcs9_unstructuredName "unstructuredName" -#define NID_pkcs9_unstructuredName 49 -#define OBJ_pkcs9_unstructuredName OBJ_pkcs9,2L - -#define LN_pkcs9_contentType "contentType" -#define NID_pkcs9_contentType 50 -#define OBJ_pkcs9_contentType OBJ_pkcs9,3L - -#define LN_pkcs9_messageDigest "messageDigest" -#define NID_pkcs9_messageDigest 51 -#define OBJ_pkcs9_messageDigest OBJ_pkcs9,4L - -#define LN_pkcs9_signingTime "signingTime" -#define NID_pkcs9_signingTime 52 -#define OBJ_pkcs9_signingTime OBJ_pkcs9,5L - -#define LN_pkcs9_countersignature "countersignature" -#define NID_pkcs9_countersignature 53 -#define OBJ_pkcs9_countersignature OBJ_pkcs9,6L - -#define LN_pkcs9_challengePassword "challengePassword" -#define NID_pkcs9_challengePassword 54 -#define OBJ_pkcs9_challengePassword OBJ_pkcs9,7L - -#define LN_pkcs9_unstructuredAddress "unstructuredAddress" -#define NID_pkcs9_unstructuredAddress 55 -#define OBJ_pkcs9_unstructuredAddress OBJ_pkcs9,8L - -#define LN_pkcs9_extCertAttributes "extendedCertificateAttributes" -#define NID_pkcs9_extCertAttributes 56 -#define OBJ_pkcs9_extCertAttributes OBJ_pkcs9,9L - -#define SN_ext_req "extReq" -#define LN_ext_req "Extension Request" -#define NID_ext_req 172 -#define OBJ_ext_req OBJ_pkcs9,14L - -#define SN_SMIMECapabilities "SMIME-CAPS" -#define LN_SMIMECapabilities "S/MIME Capabilities" -#define NID_SMIMECapabilities 167 -#define OBJ_SMIMECapabilities OBJ_pkcs9,15L - -#define SN_SMIME "SMIME" -#define LN_SMIME "S/MIME" -#define NID_SMIME 188 -#define OBJ_SMIME OBJ_pkcs9,16L - -#define SN_id_smime_mod "id-smime-mod" -#define NID_id_smime_mod 189 -#define OBJ_id_smime_mod OBJ_SMIME,0L - -#define SN_id_smime_ct "id-smime-ct" -#define NID_id_smime_ct 190 -#define OBJ_id_smime_ct OBJ_SMIME,1L - -#define SN_id_smime_aa "id-smime-aa" -#define NID_id_smime_aa 191 -#define OBJ_id_smime_aa OBJ_SMIME,2L - -#define SN_id_smime_alg "id-smime-alg" -#define NID_id_smime_alg 192 -#define OBJ_id_smime_alg OBJ_SMIME,3L - -#define SN_id_smime_cd "id-smime-cd" -#define NID_id_smime_cd 193 -#define OBJ_id_smime_cd OBJ_SMIME,4L - -#define SN_id_smime_spq "id-smime-spq" -#define NID_id_smime_spq 194 -#define OBJ_id_smime_spq OBJ_SMIME,5L - -#define SN_id_smime_cti "id-smime-cti" -#define NID_id_smime_cti 195 -#define OBJ_id_smime_cti OBJ_SMIME,6L - -#define SN_id_smime_mod_cms "id-smime-mod-cms" -#define NID_id_smime_mod_cms 196 -#define OBJ_id_smime_mod_cms OBJ_id_smime_mod,1L - -#define SN_id_smime_mod_ess "id-smime-mod-ess" -#define NID_id_smime_mod_ess 197 -#define OBJ_id_smime_mod_ess OBJ_id_smime_mod,2L - -#define SN_id_smime_mod_oid "id-smime-mod-oid" -#define NID_id_smime_mod_oid 198 -#define OBJ_id_smime_mod_oid OBJ_id_smime_mod,3L - -#define SN_id_smime_mod_msg_v3 "id-smime-mod-msg-v3" -#define NID_id_smime_mod_msg_v3 199 -#define OBJ_id_smime_mod_msg_v3 OBJ_id_smime_mod,4L - -#define SN_id_smime_mod_ets_eSignature_88 "id-smime-mod-ets-eSignature-88" -#define NID_id_smime_mod_ets_eSignature_88 200 -#define OBJ_id_smime_mod_ets_eSignature_88 OBJ_id_smime_mod,5L - -#define SN_id_smime_mod_ets_eSignature_97 "id-smime-mod-ets-eSignature-97" -#define NID_id_smime_mod_ets_eSignature_97 201 -#define OBJ_id_smime_mod_ets_eSignature_97 OBJ_id_smime_mod,6L - -#define SN_id_smime_mod_ets_eSigPolicy_88 "id-smime-mod-ets-eSigPolicy-88" -#define NID_id_smime_mod_ets_eSigPolicy_88 202 -#define OBJ_id_smime_mod_ets_eSigPolicy_88 OBJ_id_smime_mod,7L - -#define SN_id_smime_mod_ets_eSigPolicy_97 "id-smime-mod-ets-eSigPolicy-97" -#define NID_id_smime_mod_ets_eSigPolicy_97 203 -#define OBJ_id_smime_mod_ets_eSigPolicy_97 OBJ_id_smime_mod,8L - -#define SN_id_smime_ct_receipt "id-smime-ct-receipt" -#define NID_id_smime_ct_receipt 204 -#define OBJ_id_smime_ct_receipt OBJ_id_smime_ct,1L - -#define SN_id_smime_ct_authData "id-smime-ct-authData" -#define NID_id_smime_ct_authData 205 -#define OBJ_id_smime_ct_authData OBJ_id_smime_ct,2L - -#define SN_id_smime_ct_publishCert "id-smime-ct-publishCert" -#define NID_id_smime_ct_publishCert 206 -#define OBJ_id_smime_ct_publishCert OBJ_id_smime_ct,3L - -#define SN_id_smime_ct_TSTInfo "id-smime-ct-TSTInfo" -#define NID_id_smime_ct_TSTInfo 207 -#define OBJ_id_smime_ct_TSTInfo OBJ_id_smime_ct,4L - -#define SN_id_smime_ct_TDTInfo "id-smime-ct-TDTInfo" -#define NID_id_smime_ct_TDTInfo 208 -#define OBJ_id_smime_ct_TDTInfo OBJ_id_smime_ct,5L - -#define SN_id_smime_ct_contentInfo "id-smime-ct-contentInfo" -#define NID_id_smime_ct_contentInfo 209 -#define OBJ_id_smime_ct_contentInfo OBJ_id_smime_ct,6L - -#define SN_id_smime_ct_DVCSRequestData "id-smime-ct-DVCSRequestData" -#define NID_id_smime_ct_DVCSRequestData 210 -#define OBJ_id_smime_ct_DVCSRequestData OBJ_id_smime_ct,7L - -#define SN_id_smime_ct_DVCSResponseData "id-smime-ct-DVCSResponseData" -#define NID_id_smime_ct_DVCSResponseData 211 -#define OBJ_id_smime_ct_DVCSResponseData OBJ_id_smime_ct,8L - -#define SN_id_smime_ct_compressedData "id-smime-ct-compressedData" -#define NID_id_smime_ct_compressedData 786 -#define OBJ_id_smime_ct_compressedData OBJ_id_smime_ct,9L - -#define SN_id_ct_asciiTextWithCRLF "id-ct-asciiTextWithCRLF" -#define NID_id_ct_asciiTextWithCRLF 787 -#define OBJ_id_ct_asciiTextWithCRLF OBJ_id_smime_ct,27L - -#define SN_id_smime_aa_receiptRequest "id-smime-aa-receiptRequest" -#define NID_id_smime_aa_receiptRequest 212 -#define OBJ_id_smime_aa_receiptRequest OBJ_id_smime_aa,1L - -#define SN_id_smime_aa_securityLabel "id-smime-aa-securityLabel" -#define NID_id_smime_aa_securityLabel 213 -#define OBJ_id_smime_aa_securityLabel OBJ_id_smime_aa,2L - -#define SN_id_smime_aa_mlExpandHistory "id-smime-aa-mlExpandHistory" -#define NID_id_smime_aa_mlExpandHistory 214 -#define OBJ_id_smime_aa_mlExpandHistory OBJ_id_smime_aa,3L - -#define SN_id_smime_aa_contentHint "id-smime-aa-contentHint" -#define NID_id_smime_aa_contentHint 215 -#define OBJ_id_smime_aa_contentHint OBJ_id_smime_aa,4L - -#define SN_id_smime_aa_msgSigDigest "id-smime-aa-msgSigDigest" -#define NID_id_smime_aa_msgSigDigest 216 -#define OBJ_id_smime_aa_msgSigDigest OBJ_id_smime_aa,5L - -#define SN_id_smime_aa_encapContentType "id-smime-aa-encapContentType" -#define NID_id_smime_aa_encapContentType 217 -#define OBJ_id_smime_aa_encapContentType OBJ_id_smime_aa,6L - -#define SN_id_smime_aa_contentIdentifier "id-smime-aa-contentIdentifier" -#define NID_id_smime_aa_contentIdentifier 218 -#define OBJ_id_smime_aa_contentIdentifier OBJ_id_smime_aa,7L - -#define SN_id_smime_aa_macValue "id-smime-aa-macValue" -#define NID_id_smime_aa_macValue 219 -#define OBJ_id_smime_aa_macValue OBJ_id_smime_aa,8L - -#define SN_id_smime_aa_equivalentLabels "id-smime-aa-equivalentLabels" -#define NID_id_smime_aa_equivalentLabels 220 -#define OBJ_id_smime_aa_equivalentLabels OBJ_id_smime_aa,9L - -#define SN_id_smime_aa_contentReference "id-smime-aa-contentReference" -#define NID_id_smime_aa_contentReference 221 -#define OBJ_id_smime_aa_contentReference OBJ_id_smime_aa,10L - -#define SN_id_smime_aa_encrypKeyPref "id-smime-aa-encrypKeyPref" -#define NID_id_smime_aa_encrypKeyPref 222 -#define OBJ_id_smime_aa_encrypKeyPref OBJ_id_smime_aa,11L - -#define SN_id_smime_aa_signingCertificate "id-smime-aa-signingCertificate" -#define NID_id_smime_aa_signingCertificate 223 -#define OBJ_id_smime_aa_signingCertificate OBJ_id_smime_aa,12L - -#define SN_id_smime_aa_smimeEncryptCerts "id-smime-aa-smimeEncryptCerts" -#define NID_id_smime_aa_smimeEncryptCerts 224 -#define OBJ_id_smime_aa_smimeEncryptCerts OBJ_id_smime_aa,13L - -#define SN_id_smime_aa_timeStampToken "id-smime-aa-timeStampToken" -#define NID_id_smime_aa_timeStampToken 225 -#define OBJ_id_smime_aa_timeStampToken OBJ_id_smime_aa,14L - -#define SN_id_smime_aa_ets_sigPolicyId "id-smime-aa-ets-sigPolicyId" -#define NID_id_smime_aa_ets_sigPolicyId 226 -#define OBJ_id_smime_aa_ets_sigPolicyId OBJ_id_smime_aa,15L - -#define SN_id_smime_aa_ets_commitmentType "id-smime-aa-ets-commitmentType" -#define NID_id_smime_aa_ets_commitmentType 227 -#define OBJ_id_smime_aa_ets_commitmentType OBJ_id_smime_aa,16L - -#define SN_id_smime_aa_ets_signerLocation "id-smime-aa-ets-signerLocation" -#define NID_id_smime_aa_ets_signerLocation 228 -#define OBJ_id_smime_aa_ets_signerLocation OBJ_id_smime_aa,17L - -#define SN_id_smime_aa_ets_signerAttr "id-smime-aa-ets-signerAttr" -#define NID_id_smime_aa_ets_signerAttr 229 -#define OBJ_id_smime_aa_ets_signerAttr OBJ_id_smime_aa,18L - -#define SN_id_smime_aa_ets_otherSigCert "id-smime-aa-ets-otherSigCert" -#define NID_id_smime_aa_ets_otherSigCert 230 -#define OBJ_id_smime_aa_ets_otherSigCert OBJ_id_smime_aa,19L - -#define SN_id_smime_aa_ets_contentTimestamp "id-smime-aa-ets-contentTimestamp" -#define NID_id_smime_aa_ets_contentTimestamp 231 -#define OBJ_id_smime_aa_ets_contentTimestamp OBJ_id_smime_aa,20L - -#define SN_id_smime_aa_ets_CertificateRefs "id-smime-aa-ets-CertificateRefs" -#define NID_id_smime_aa_ets_CertificateRefs 232 -#define OBJ_id_smime_aa_ets_CertificateRefs OBJ_id_smime_aa,21L - -#define SN_id_smime_aa_ets_RevocationRefs "id-smime-aa-ets-RevocationRefs" -#define NID_id_smime_aa_ets_RevocationRefs 233 -#define OBJ_id_smime_aa_ets_RevocationRefs OBJ_id_smime_aa,22L - -#define SN_id_smime_aa_ets_certValues "id-smime-aa-ets-certValues" -#define NID_id_smime_aa_ets_certValues 234 -#define OBJ_id_smime_aa_ets_certValues OBJ_id_smime_aa,23L - -#define SN_id_smime_aa_ets_revocationValues "id-smime-aa-ets-revocationValues" -#define NID_id_smime_aa_ets_revocationValues 235 -#define OBJ_id_smime_aa_ets_revocationValues OBJ_id_smime_aa,24L - -#define SN_id_smime_aa_ets_escTimeStamp "id-smime-aa-ets-escTimeStamp" -#define NID_id_smime_aa_ets_escTimeStamp 236 -#define OBJ_id_smime_aa_ets_escTimeStamp OBJ_id_smime_aa,25L - -#define SN_id_smime_aa_ets_certCRLTimestamp "id-smime-aa-ets-certCRLTimestamp" -#define NID_id_smime_aa_ets_certCRLTimestamp 237 -#define OBJ_id_smime_aa_ets_certCRLTimestamp OBJ_id_smime_aa,26L - -#define SN_id_smime_aa_ets_archiveTimeStamp "id-smime-aa-ets-archiveTimeStamp" -#define NID_id_smime_aa_ets_archiveTimeStamp 238 -#define OBJ_id_smime_aa_ets_archiveTimeStamp OBJ_id_smime_aa,27L - -#define SN_id_smime_aa_signatureType "id-smime-aa-signatureType" -#define NID_id_smime_aa_signatureType 239 -#define OBJ_id_smime_aa_signatureType OBJ_id_smime_aa,28L - -#define SN_id_smime_aa_dvcs_dvc "id-smime-aa-dvcs-dvc" -#define NID_id_smime_aa_dvcs_dvc 240 -#define OBJ_id_smime_aa_dvcs_dvc OBJ_id_smime_aa,29L - -#define SN_id_smime_alg_ESDHwith3DES "id-smime-alg-ESDHwith3DES" -#define NID_id_smime_alg_ESDHwith3DES 241 -#define OBJ_id_smime_alg_ESDHwith3DES OBJ_id_smime_alg,1L - -#define SN_id_smime_alg_ESDHwithRC2 "id-smime-alg-ESDHwithRC2" -#define NID_id_smime_alg_ESDHwithRC2 242 -#define OBJ_id_smime_alg_ESDHwithRC2 OBJ_id_smime_alg,2L - -#define SN_id_smime_alg_3DESwrap "id-smime-alg-3DESwrap" -#define NID_id_smime_alg_3DESwrap 243 -#define OBJ_id_smime_alg_3DESwrap OBJ_id_smime_alg,3L - -#define SN_id_smime_alg_RC2wrap "id-smime-alg-RC2wrap" -#define NID_id_smime_alg_RC2wrap 244 -#define OBJ_id_smime_alg_RC2wrap OBJ_id_smime_alg,4L - -#define SN_id_smime_alg_ESDH "id-smime-alg-ESDH" -#define NID_id_smime_alg_ESDH 245 -#define OBJ_id_smime_alg_ESDH OBJ_id_smime_alg,5L - -#define SN_id_smime_alg_CMS3DESwrap "id-smime-alg-CMS3DESwrap" -#define NID_id_smime_alg_CMS3DESwrap 246 -#define OBJ_id_smime_alg_CMS3DESwrap OBJ_id_smime_alg,6L - -#define SN_id_smime_alg_CMSRC2wrap "id-smime-alg-CMSRC2wrap" -#define NID_id_smime_alg_CMSRC2wrap 247 -#define OBJ_id_smime_alg_CMSRC2wrap OBJ_id_smime_alg,7L - -#define SN_id_alg_PWRI_KEK "id-alg-PWRI-KEK" -#define NID_id_alg_PWRI_KEK 893 -#define OBJ_id_alg_PWRI_KEK OBJ_id_smime_alg,9L - -#define SN_id_smime_cd_ldap "id-smime-cd-ldap" -#define NID_id_smime_cd_ldap 248 -#define OBJ_id_smime_cd_ldap OBJ_id_smime_cd,1L - -#define SN_id_smime_spq_ets_sqt_uri "id-smime-spq-ets-sqt-uri" -#define NID_id_smime_spq_ets_sqt_uri 249 -#define OBJ_id_smime_spq_ets_sqt_uri OBJ_id_smime_spq,1L - -#define SN_id_smime_spq_ets_sqt_unotice "id-smime-spq-ets-sqt-unotice" -#define NID_id_smime_spq_ets_sqt_unotice 250 -#define OBJ_id_smime_spq_ets_sqt_unotice OBJ_id_smime_spq,2L - -#define SN_id_smime_cti_ets_proofOfOrigin "id-smime-cti-ets-proofOfOrigin" -#define NID_id_smime_cti_ets_proofOfOrigin 251 -#define OBJ_id_smime_cti_ets_proofOfOrigin OBJ_id_smime_cti,1L - -#define SN_id_smime_cti_ets_proofOfReceipt "id-smime-cti-ets-proofOfReceipt" -#define NID_id_smime_cti_ets_proofOfReceipt 252 -#define OBJ_id_smime_cti_ets_proofOfReceipt OBJ_id_smime_cti,2L - -#define SN_id_smime_cti_ets_proofOfDelivery "id-smime-cti-ets-proofOfDelivery" -#define NID_id_smime_cti_ets_proofOfDelivery 253 -#define OBJ_id_smime_cti_ets_proofOfDelivery OBJ_id_smime_cti,3L - -#define SN_id_smime_cti_ets_proofOfSender "id-smime-cti-ets-proofOfSender" -#define NID_id_smime_cti_ets_proofOfSender 254 -#define OBJ_id_smime_cti_ets_proofOfSender OBJ_id_smime_cti,4L - -#define SN_id_smime_cti_ets_proofOfApproval "id-smime-cti-ets-proofOfApproval" -#define NID_id_smime_cti_ets_proofOfApproval 255 -#define OBJ_id_smime_cti_ets_proofOfApproval OBJ_id_smime_cti,5L - -#define SN_id_smime_cti_ets_proofOfCreation "id-smime-cti-ets-proofOfCreation" -#define NID_id_smime_cti_ets_proofOfCreation 256 -#define OBJ_id_smime_cti_ets_proofOfCreation OBJ_id_smime_cti,6L - -#define LN_friendlyName "friendlyName" -#define NID_friendlyName 156 -#define OBJ_friendlyName OBJ_pkcs9,20L - -#define LN_localKeyID "localKeyID" -#define NID_localKeyID 157 -#define OBJ_localKeyID OBJ_pkcs9,21L - -#define SN_ms_csp_name "CSPName" -#define LN_ms_csp_name "Microsoft CSP Name" -#define NID_ms_csp_name 417 -#define OBJ_ms_csp_name 1L,3L,6L,1L,4L,1L,311L,17L,1L - -#define SN_LocalKeySet "LocalKeySet" -#define LN_LocalKeySet "Microsoft Local Key set" -#define NID_LocalKeySet 856 -#define OBJ_LocalKeySet 1L,3L,6L,1L,4L,1L,311L,17L,2L - -#define OBJ_certTypes OBJ_pkcs9,22L - -#define LN_x509Certificate "x509Certificate" -#define NID_x509Certificate 158 -#define OBJ_x509Certificate OBJ_certTypes,1L - -#define LN_sdsiCertificate "sdsiCertificate" -#define NID_sdsiCertificate 159 -#define OBJ_sdsiCertificate OBJ_certTypes,2L - -#define OBJ_crlTypes OBJ_pkcs9,23L - -#define LN_x509Crl "x509Crl" -#define NID_x509Crl 160 -#define OBJ_x509Crl OBJ_crlTypes,1L - -#define OBJ_pkcs12 OBJ_pkcs,12L - -#define OBJ_pkcs12_pbeids OBJ_pkcs12,1L - -#define SN_pbe_WithSHA1And128BitRC4 "PBE-SHA1-RC4-128" -#define LN_pbe_WithSHA1And128BitRC4 "pbeWithSHA1And128BitRC4" -#define NID_pbe_WithSHA1And128BitRC4 144 -#define OBJ_pbe_WithSHA1And128BitRC4 OBJ_pkcs12_pbeids,1L - -#define SN_pbe_WithSHA1And40BitRC4 "PBE-SHA1-RC4-40" -#define LN_pbe_WithSHA1And40BitRC4 "pbeWithSHA1And40BitRC4" -#define NID_pbe_WithSHA1And40BitRC4 145 -#define OBJ_pbe_WithSHA1And40BitRC4 OBJ_pkcs12_pbeids,2L - -#define SN_pbe_WithSHA1And3_Key_TripleDES_CBC "PBE-SHA1-3DES" -#define LN_pbe_WithSHA1And3_Key_TripleDES_CBC "pbeWithSHA1And3-KeyTripleDES-CBC" -#define NID_pbe_WithSHA1And3_Key_TripleDES_CBC 146 -#define OBJ_pbe_WithSHA1And3_Key_TripleDES_CBC OBJ_pkcs12_pbeids,3L - -#define SN_pbe_WithSHA1And2_Key_TripleDES_CBC "PBE-SHA1-2DES" -#define LN_pbe_WithSHA1And2_Key_TripleDES_CBC "pbeWithSHA1And2-KeyTripleDES-CBC" -#define NID_pbe_WithSHA1And2_Key_TripleDES_CBC 147 -#define OBJ_pbe_WithSHA1And2_Key_TripleDES_CBC OBJ_pkcs12_pbeids,4L - -#define SN_pbe_WithSHA1And128BitRC2_CBC "PBE-SHA1-RC2-128" -#define LN_pbe_WithSHA1And128BitRC2_CBC "pbeWithSHA1And128BitRC2-CBC" -#define NID_pbe_WithSHA1And128BitRC2_CBC 148 -#define OBJ_pbe_WithSHA1And128BitRC2_CBC OBJ_pkcs12_pbeids,5L - -#define SN_pbe_WithSHA1And40BitRC2_CBC "PBE-SHA1-RC2-40" -#define LN_pbe_WithSHA1And40BitRC2_CBC "pbeWithSHA1And40BitRC2-CBC" -#define NID_pbe_WithSHA1And40BitRC2_CBC 149 -#define OBJ_pbe_WithSHA1And40BitRC2_CBC OBJ_pkcs12_pbeids,6L - -#define OBJ_pkcs12_Version1 OBJ_pkcs12,10L - -#define OBJ_pkcs12_BagIds OBJ_pkcs12_Version1,1L - -#define LN_keyBag "keyBag" -#define NID_keyBag 150 -#define OBJ_keyBag OBJ_pkcs12_BagIds,1L - -#define LN_pkcs8ShroudedKeyBag "pkcs8ShroudedKeyBag" -#define NID_pkcs8ShroudedKeyBag 151 -#define OBJ_pkcs8ShroudedKeyBag OBJ_pkcs12_BagIds,2L - -#define LN_certBag "certBag" -#define NID_certBag 152 -#define OBJ_certBag OBJ_pkcs12_BagIds,3L - -#define LN_crlBag "crlBag" -#define NID_crlBag 153 -#define OBJ_crlBag OBJ_pkcs12_BagIds,4L - -#define LN_secretBag "secretBag" -#define NID_secretBag 154 -#define OBJ_secretBag OBJ_pkcs12_BagIds,5L - -#define LN_safeContentsBag "safeContentsBag" -#define NID_safeContentsBag 155 -#define OBJ_safeContentsBag OBJ_pkcs12_BagIds,6L - -#define SN_md2 "MD2" -#define LN_md2 "md2" -#define NID_md2 3 -#define OBJ_md2 OBJ_rsadsi,2L,2L - -#define SN_md4 "MD4" -#define LN_md4 "md4" -#define NID_md4 257 -#define OBJ_md4 OBJ_rsadsi,2L,4L - -#define SN_md5 "MD5" -#define LN_md5 "md5" -#define NID_md5 4 -#define OBJ_md5 OBJ_rsadsi,2L,5L - -#define SN_md5_sha1 "MD5-SHA1" -#define LN_md5_sha1 "md5-sha1" -#define NID_md5_sha1 114 - -#define LN_hmacWithMD5 "hmacWithMD5" -#define NID_hmacWithMD5 797 -#define OBJ_hmacWithMD5 OBJ_rsadsi,2L,6L - -#define LN_hmacWithSHA1 "hmacWithSHA1" -#define NID_hmacWithSHA1 163 -#define OBJ_hmacWithSHA1 OBJ_rsadsi,2L,7L - -#define LN_hmacWithSHA224 "hmacWithSHA224" -#define NID_hmacWithSHA224 798 -#define OBJ_hmacWithSHA224 OBJ_rsadsi,2L,8L - -#define LN_hmacWithSHA256 "hmacWithSHA256" -#define NID_hmacWithSHA256 799 -#define OBJ_hmacWithSHA256 OBJ_rsadsi,2L,9L - -#define LN_hmacWithSHA384 "hmacWithSHA384" -#define NID_hmacWithSHA384 800 -#define OBJ_hmacWithSHA384 OBJ_rsadsi,2L,10L - -#define LN_hmacWithSHA512 "hmacWithSHA512" -#define NID_hmacWithSHA512 801 -#define OBJ_hmacWithSHA512 OBJ_rsadsi,2L,11L - -#define SN_rc2_cbc "RC2-CBC" -#define LN_rc2_cbc "rc2-cbc" -#define NID_rc2_cbc 37 -#define OBJ_rc2_cbc OBJ_rsadsi,3L,2L - -#define SN_rc2_ecb "RC2-ECB" -#define LN_rc2_ecb "rc2-ecb" -#define NID_rc2_ecb 38 - -#define SN_rc2_cfb64 "RC2-CFB" -#define LN_rc2_cfb64 "rc2-cfb" -#define NID_rc2_cfb64 39 - -#define SN_rc2_ofb64 "RC2-OFB" -#define LN_rc2_ofb64 "rc2-ofb" -#define NID_rc2_ofb64 40 - -#define SN_rc2_40_cbc "RC2-40-CBC" -#define LN_rc2_40_cbc "rc2-40-cbc" -#define NID_rc2_40_cbc 98 - -#define SN_rc2_64_cbc "RC2-64-CBC" -#define LN_rc2_64_cbc "rc2-64-cbc" -#define NID_rc2_64_cbc 166 - -#define SN_rc4 "RC4" -#define LN_rc4 "rc4" -#define NID_rc4 5 -#define OBJ_rc4 OBJ_rsadsi,3L,4L - -#define SN_rc4_40 "RC4-40" -#define LN_rc4_40 "rc4-40" -#define NID_rc4_40 97 - -#define SN_des_ede3_cbc "DES-EDE3-CBC" -#define LN_des_ede3_cbc "des-ede3-cbc" -#define NID_des_ede3_cbc 44 -#define OBJ_des_ede3_cbc OBJ_rsadsi,3L,7L - -#define SN_rc5_cbc "RC5-CBC" -#define LN_rc5_cbc "rc5-cbc" -#define NID_rc5_cbc 120 -#define OBJ_rc5_cbc OBJ_rsadsi,3L,8L - -#define SN_rc5_ecb "RC5-ECB" -#define LN_rc5_ecb "rc5-ecb" -#define NID_rc5_ecb 121 - -#define SN_rc5_cfb64 "RC5-CFB" -#define LN_rc5_cfb64 "rc5-cfb" -#define NID_rc5_cfb64 122 - -#define SN_rc5_ofb64 "RC5-OFB" -#define LN_rc5_ofb64 "rc5-ofb" -#define NID_rc5_ofb64 123 - -#define SN_ms_ext_req "msExtReq" -#define LN_ms_ext_req "Microsoft Extension Request" -#define NID_ms_ext_req 171 -#define OBJ_ms_ext_req 1L,3L,6L,1L,4L,1L,311L,2L,1L,14L - -#define SN_ms_code_ind "msCodeInd" -#define LN_ms_code_ind "Microsoft Individual Code Signing" -#define NID_ms_code_ind 134 -#define OBJ_ms_code_ind 1L,3L,6L,1L,4L,1L,311L,2L,1L,21L - -#define SN_ms_code_com "msCodeCom" -#define LN_ms_code_com "Microsoft Commercial Code Signing" -#define NID_ms_code_com 135 -#define OBJ_ms_code_com 1L,3L,6L,1L,4L,1L,311L,2L,1L,22L - -#define SN_ms_ctl_sign "msCTLSign" -#define LN_ms_ctl_sign "Microsoft Trust List Signing" -#define NID_ms_ctl_sign 136 -#define OBJ_ms_ctl_sign 1L,3L,6L,1L,4L,1L,311L,10L,3L,1L - -#define SN_ms_sgc "msSGC" -#define LN_ms_sgc "Microsoft Server Gated Crypto" -#define NID_ms_sgc 137 -#define OBJ_ms_sgc 1L,3L,6L,1L,4L,1L,311L,10L,3L,3L - -#define SN_ms_efs "msEFS" -#define LN_ms_efs "Microsoft Encrypted File System" -#define NID_ms_efs 138 -#define OBJ_ms_efs 1L,3L,6L,1L,4L,1L,311L,10L,3L,4L - -#define SN_ms_smartcard_login "msSmartcardLogin" -#define LN_ms_smartcard_login "Microsoft Smartcardlogin" -#define NID_ms_smartcard_login 648 -#define OBJ_ms_smartcard_login 1L,3L,6L,1L,4L,1L,311L,20L,2L,2L - -#define SN_ms_upn "msUPN" -#define LN_ms_upn "Microsoft Universal Principal Name" -#define NID_ms_upn 649 -#define OBJ_ms_upn 1L,3L,6L,1L,4L,1L,311L,20L,2L,3L - -#define SN_idea_cbc "IDEA-CBC" -#define LN_idea_cbc "idea-cbc" -#define NID_idea_cbc 34 -#define OBJ_idea_cbc 1L,3L,6L,1L,4L,1L,188L,7L,1L,1L,2L - -#define SN_idea_ecb "IDEA-ECB" -#define LN_idea_ecb "idea-ecb" -#define NID_idea_ecb 36 - -#define SN_idea_cfb64 "IDEA-CFB" -#define LN_idea_cfb64 "idea-cfb" -#define NID_idea_cfb64 35 - -#define SN_idea_ofb64 "IDEA-OFB" -#define LN_idea_ofb64 "idea-ofb" -#define NID_idea_ofb64 46 - -#define SN_bf_cbc "BF-CBC" -#define LN_bf_cbc "bf-cbc" -#define NID_bf_cbc 91 -#define OBJ_bf_cbc 1L,3L,6L,1L,4L,1L,3029L,1L,2L - -#define SN_bf_ecb "BF-ECB" -#define LN_bf_ecb "bf-ecb" -#define NID_bf_ecb 92 - -#define SN_bf_cfb64 "BF-CFB" -#define LN_bf_cfb64 "bf-cfb" -#define NID_bf_cfb64 93 - -#define SN_bf_ofb64 "BF-OFB" -#define LN_bf_ofb64 "bf-ofb" -#define NID_bf_ofb64 94 - -#define SN_id_pkix "PKIX" -#define NID_id_pkix 127 -#define OBJ_id_pkix 1L,3L,6L,1L,5L,5L,7L - -#define SN_id_pkix_mod "id-pkix-mod" -#define NID_id_pkix_mod 258 -#define OBJ_id_pkix_mod OBJ_id_pkix,0L - -#define SN_id_pe "id-pe" -#define NID_id_pe 175 -#define OBJ_id_pe OBJ_id_pkix,1L - -#define SN_id_qt "id-qt" -#define NID_id_qt 259 -#define OBJ_id_qt OBJ_id_pkix,2L - -#define SN_id_kp "id-kp" -#define NID_id_kp 128 -#define OBJ_id_kp OBJ_id_pkix,3L - -#define SN_id_it "id-it" -#define NID_id_it 260 -#define OBJ_id_it OBJ_id_pkix,4L - -#define SN_id_pkip "id-pkip" -#define NID_id_pkip 261 -#define OBJ_id_pkip OBJ_id_pkix,5L - -#define SN_id_alg "id-alg" -#define NID_id_alg 262 -#define OBJ_id_alg OBJ_id_pkix,6L - -#define SN_id_cmc "id-cmc" -#define NID_id_cmc 263 -#define OBJ_id_cmc OBJ_id_pkix,7L - -#define SN_id_on "id-on" -#define NID_id_on 264 -#define OBJ_id_on OBJ_id_pkix,8L - -#define SN_id_pda "id-pda" -#define NID_id_pda 265 -#define OBJ_id_pda OBJ_id_pkix,9L - -#define SN_id_aca "id-aca" -#define NID_id_aca 266 -#define OBJ_id_aca OBJ_id_pkix,10L - -#define SN_id_qcs "id-qcs" -#define NID_id_qcs 267 -#define OBJ_id_qcs OBJ_id_pkix,11L - -#define SN_id_cct "id-cct" -#define NID_id_cct 268 -#define OBJ_id_cct OBJ_id_pkix,12L - -#define SN_id_ppl "id-ppl" -#define NID_id_ppl 662 -#define OBJ_id_ppl OBJ_id_pkix,21L - -#define SN_id_ad "id-ad" -#define NID_id_ad 176 -#define OBJ_id_ad OBJ_id_pkix,48L - -#define SN_id_pkix1_explicit_88 "id-pkix1-explicit-88" -#define NID_id_pkix1_explicit_88 269 -#define OBJ_id_pkix1_explicit_88 OBJ_id_pkix_mod,1L - -#define SN_id_pkix1_implicit_88 "id-pkix1-implicit-88" -#define NID_id_pkix1_implicit_88 270 -#define OBJ_id_pkix1_implicit_88 OBJ_id_pkix_mod,2L - -#define SN_id_pkix1_explicit_93 "id-pkix1-explicit-93" -#define NID_id_pkix1_explicit_93 271 -#define OBJ_id_pkix1_explicit_93 OBJ_id_pkix_mod,3L - -#define SN_id_pkix1_implicit_93 "id-pkix1-implicit-93" -#define NID_id_pkix1_implicit_93 272 -#define OBJ_id_pkix1_implicit_93 OBJ_id_pkix_mod,4L - -#define SN_id_mod_crmf "id-mod-crmf" -#define NID_id_mod_crmf 273 -#define OBJ_id_mod_crmf OBJ_id_pkix_mod,5L - -#define SN_id_mod_cmc "id-mod-cmc" -#define NID_id_mod_cmc 274 -#define OBJ_id_mod_cmc OBJ_id_pkix_mod,6L - -#define SN_id_mod_kea_profile_88 "id-mod-kea-profile-88" -#define NID_id_mod_kea_profile_88 275 -#define OBJ_id_mod_kea_profile_88 OBJ_id_pkix_mod,7L - -#define SN_id_mod_kea_profile_93 "id-mod-kea-profile-93" -#define NID_id_mod_kea_profile_93 276 -#define OBJ_id_mod_kea_profile_93 OBJ_id_pkix_mod,8L - -#define SN_id_mod_cmp "id-mod-cmp" -#define NID_id_mod_cmp 277 -#define OBJ_id_mod_cmp OBJ_id_pkix_mod,9L - -#define SN_id_mod_qualified_cert_88 "id-mod-qualified-cert-88" -#define NID_id_mod_qualified_cert_88 278 -#define OBJ_id_mod_qualified_cert_88 OBJ_id_pkix_mod,10L - -#define SN_id_mod_qualified_cert_93 "id-mod-qualified-cert-93" -#define NID_id_mod_qualified_cert_93 279 -#define OBJ_id_mod_qualified_cert_93 OBJ_id_pkix_mod,11L - -#define SN_id_mod_attribute_cert "id-mod-attribute-cert" -#define NID_id_mod_attribute_cert 280 -#define OBJ_id_mod_attribute_cert OBJ_id_pkix_mod,12L - -#define SN_id_mod_timestamp_protocol "id-mod-timestamp-protocol" -#define NID_id_mod_timestamp_protocol 281 -#define OBJ_id_mod_timestamp_protocol OBJ_id_pkix_mod,13L - -#define SN_id_mod_ocsp "id-mod-ocsp" -#define NID_id_mod_ocsp 282 -#define OBJ_id_mod_ocsp OBJ_id_pkix_mod,14L - -#define SN_id_mod_dvcs "id-mod-dvcs" -#define NID_id_mod_dvcs 283 -#define OBJ_id_mod_dvcs OBJ_id_pkix_mod,15L - -#define SN_id_mod_cmp2000 "id-mod-cmp2000" -#define NID_id_mod_cmp2000 284 -#define OBJ_id_mod_cmp2000 OBJ_id_pkix_mod,16L - -#define SN_info_access "authorityInfoAccess" -#define LN_info_access "Authority Information Access" -#define NID_info_access 177 -#define OBJ_info_access OBJ_id_pe,1L - -#define SN_biometricInfo "biometricInfo" -#define LN_biometricInfo "Biometric Info" -#define NID_biometricInfo 285 -#define OBJ_biometricInfo OBJ_id_pe,2L - -#define SN_qcStatements "qcStatements" -#define NID_qcStatements 286 -#define OBJ_qcStatements OBJ_id_pe,3L - -#define SN_ac_auditEntity "ac-auditEntity" -#define NID_ac_auditEntity 287 -#define OBJ_ac_auditEntity OBJ_id_pe,4L - -#define SN_ac_targeting "ac-targeting" -#define NID_ac_targeting 288 -#define OBJ_ac_targeting OBJ_id_pe,5L - -#define SN_aaControls "aaControls" -#define NID_aaControls 289 -#define OBJ_aaControls OBJ_id_pe,6L - -#define SN_sbgp_ipAddrBlock "sbgp-ipAddrBlock" -#define NID_sbgp_ipAddrBlock 290 -#define OBJ_sbgp_ipAddrBlock OBJ_id_pe,7L - -#define SN_sbgp_autonomousSysNum "sbgp-autonomousSysNum" -#define NID_sbgp_autonomousSysNum 291 -#define OBJ_sbgp_autonomousSysNum OBJ_id_pe,8L - -#define SN_sbgp_routerIdentifier "sbgp-routerIdentifier" -#define NID_sbgp_routerIdentifier 292 -#define OBJ_sbgp_routerIdentifier OBJ_id_pe,9L - -#define SN_ac_proxying "ac-proxying" -#define NID_ac_proxying 397 -#define OBJ_ac_proxying OBJ_id_pe,10L - -#define SN_sinfo_access "subjectInfoAccess" -#define LN_sinfo_access "Subject Information Access" -#define NID_sinfo_access 398 -#define OBJ_sinfo_access OBJ_id_pe,11L - -#define SN_proxyCertInfo "proxyCertInfo" -#define LN_proxyCertInfo "Proxy Certificate Information" -#define NID_proxyCertInfo 663 -#define OBJ_proxyCertInfo OBJ_id_pe,14L - -#define SN_id_qt_cps "id-qt-cps" -#define LN_id_qt_cps "Policy Qualifier CPS" -#define NID_id_qt_cps 164 -#define OBJ_id_qt_cps OBJ_id_qt,1L - -#define SN_id_qt_unotice "id-qt-unotice" -#define LN_id_qt_unotice "Policy Qualifier User Notice" -#define NID_id_qt_unotice 165 -#define OBJ_id_qt_unotice OBJ_id_qt,2L - -#define SN_textNotice "textNotice" -#define NID_textNotice 293 -#define OBJ_textNotice OBJ_id_qt,3L - -#define SN_server_auth "serverAuth" -#define LN_server_auth "TLS Web Server Authentication" -#define NID_server_auth 129 -#define OBJ_server_auth OBJ_id_kp,1L - -#define SN_client_auth "clientAuth" -#define LN_client_auth "TLS Web Client Authentication" -#define NID_client_auth 130 -#define OBJ_client_auth OBJ_id_kp,2L - -#define SN_code_sign "codeSigning" -#define LN_code_sign "Code Signing" -#define NID_code_sign 131 -#define OBJ_code_sign OBJ_id_kp,3L - -#define SN_email_protect "emailProtection" -#define LN_email_protect "E-mail Protection" -#define NID_email_protect 132 -#define OBJ_email_protect OBJ_id_kp,4L - -#define SN_ipsecEndSystem "ipsecEndSystem" -#define LN_ipsecEndSystem "IPSec End System" -#define NID_ipsecEndSystem 294 -#define OBJ_ipsecEndSystem OBJ_id_kp,5L - -#define SN_ipsecTunnel "ipsecTunnel" -#define LN_ipsecTunnel "IPSec Tunnel" -#define NID_ipsecTunnel 295 -#define OBJ_ipsecTunnel OBJ_id_kp,6L - -#define SN_ipsecUser "ipsecUser" -#define LN_ipsecUser "IPSec User" -#define NID_ipsecUser 296 -#define OBJ_ipsecUser OBJ_id_kp,7L - -#define SN_time_stamp "timeStamping" -#define LN_time_stamp "Time Stamping" -#define NID_time_stamp 133 -#define OBJ_time_stamp OBJ_id_kp,8L - -#define SN_OCSP_sign "OCSPSigning" -#define LN_OCSP_sign "OCSP Signing" -#define NID_OCSP_sign 180 -#define OBJ_OCSP_sign OBJ_id_kp,9L - -#define SN_dvcs "DVCS" -#define LN_dvcs "dvcs" -#define NID_dvcs 297 -#define OBJ_dvcs OBJ_id_kp,10L - -#define SN_id_it_caProtEncCert "id-it-caProtEncCert" -#define NID_id_it_caProtEncCert 298 -#define OBJ_id_it_caProtEncCert OBJ_id_it,1L - -#define SN_id_it_signKeyPairTypes "id-it-signKeyPairTypes" -#define NID_id_it_signKeyPairTypes 299 -#define OBJ_id_it_signKeyPairTypes OBJ_id_it,2L - -#define SN_id_it_encKeyPairTypes "id-it-encKeyPairTypes" -#define NID_id_it_encKeyPairTypes 300 -#define OBJ_id_it_encKeyPairTypes OBJ_id_it,3L - -#define SN_id_it_preferredSymmAlg "id-it-preferredSymmAlg" -#define NID_id_it_preferredSymmAlg 301 -#define OBJ_id_it_preferredSymmAlg OBJ_id_it,4L - -#define SN_id_it_caKeyUpdateInfo "id-it-caKeyUpdateInfo" -#define NID_id_it_caKeyUpdateInfo 302 -#define OBJ_id_it_caKeyUpdateInfo OBJ_id_it,5L - -#define SN_id_it_currentCRL "id-it-currentCRL" -#define NID_id_it_currentCRL 303 -#define OBJ_id_it_currentCRL OBJ_id_it,6L - -#define SN_id_it_unsupportedOIDs "id-it-unsupportedOIDs" -#define NID_id_it_unsupportedOIDs 304 -#define OBJ_id_it_unsupportedOIDs OBJ_id_it,7L - -#define SN_id_it_subscriptionRequest "id-it-subscriptionRequest" -#define NID_id_it_subscriptionRequest 305 -#define OBJ_id_it_subscriptionRequest OBJ_id_it,8L - -#define SN_id_it_subscriptionResponse "id-it-subscriptionResponse" -#define NID_id_it_subscriptionResponse 306 -#define OBJ_id_it_subscriptionResponse OBJ_id_it,9L - -#define SN_id_it_keyPairParamReq "id-it-keyPairParamReq" -#define NID_id_it_keyPairParamReq 307 -#define OBJ_id_it_keyPairParamReq OBJ_id_it,10L - -#define SN_id_it_keyPairParamRep "id-it-keyPairParamRep" -#define NID_id_it_keyPairParamRep 308 -#define OBJ_id_it_keyPairParamRep OBJ_id_it,11L - -#define SN_id_it_revPassphrase "id-it-revPassphrase" -#define NID_id_it_revPassphrase 309 -#define OBJ_id_it_revPassphrase OBJ_id_it,12L - -#define SN_id_it_implicitConfirm "id-it-implicitConfirm" -#define NID_id_it_implicitConfirm 310 -#define OBJ_id_it_implicitConfirm OBJ_id_it,13L - -#define SN_id_it_confirmWaitTime "id-it-confirmWaitTime" -#define NID_id_it_confirmWaitTime 311 -#define OBJ_id_it_confirmWaitTime OBJ_id_it,14L - -#define SN_id_it_origPKIMessage "id-it-origPKIMessage" -#define NID_id_it_origPKIMessage 312 -#define OBJ_id_it_origPKIMessage OBJ_id_it,15L - -#define SN_id_it_suppLangTags "id-it-suppLangTags" -#define NID_id_it_suppLangTags 784 -#define OBJ_id_it_suppLangTags OBJ_id_it,16L - -#define SN_id_regCtrl "id-regCtrl" -#define NID_id_regCtrl 313 -#define OBJ_id_regCtrl OBJ_id_pkip,1L - -#define SN_id_regInfo "id-regInfo" -#define NID_id_regInfo 314 -#define OBJ_id_regInfo OBJ_id_pkip,2L - -#define SN_id_regCtrl_regToken "id-regCtrl-regToken" -#define NID_id_regCtrl_regToken 315 -#define OBJ_id_regCtrl_regToken OBJ_id_regCtrl,1L - -#define SN_id_regCtrl_authenticator "id-regCtrl-authenticator" -#define NID_id_regCtrl_authenticator 316 -#define OBJ_id_regCtrl_authenticator OBJ_id_regCtrl,2L - -#define SN_id_regCtrl_pkiPublicationInfo "id-regCtrl-pkiPublicationInfo" -#define NID_id_regCtrl_pkiPublicationInfo 317 -#define OBJ_id_regCtrl_pkiPublicationInfo OBJ_id_regCtrl,3L - -#define SN_id_regCtrl_pkiArchiveOptions "id-regCtrl-pkiArchiveOptions" -#define NID_id_regCtrl_pkiArchiveOptions 318 -#define OBJ_id_regCtrl_pkiArchiveOptions OBJ_id_regCtrl,4L - -#define SN_id_regCtrl_oldCertID "id-regCtrl-oldCertID" -#define NID_id_regCtrl_oldCertID 319 -#define OBJ_id_regCtrl_oldCertID OBJ_id_regCtrl,5L - -#define SN_id_regCtrl_protocolEncrKey "id-regCtrl-protocolEncrKey" -#define NID_id_regCtrl_protocolEncrKey 320 -#define OBJ_id_regCtrl_protocolEncrKey OBJ_id_regCtrl,6L - -#define SN_id_regInfo_utf8Pairs "id-regInfo-utf8Pairs" -#define NID_id_regInfo_utf8Pairs 321 -#define OBJ_id_regInfo_utf8Pairs OBJ_id_regInfo,1L - -#define SN_id_regInfo_certReq "id-regInfo-certReq" -#define NID_id_regInfo_certReq 322 -#define OBJ_id_regInfo_certReq OBJ_id_regInfo,2L - -#define SN_id_alg_des40 "id-alg-des40" -#define NID_id_alg_des40 323 -#define OBJ_id_alg_des40 OBJ_id_alg,1L - -#define SN_id_alg_noSignature "id-alg-noSignature" -#define NID_id_alg_noSignature 324 -#define OBJ_id_alg_noSignature OBJ_id_alg,2L - -#define SN_id_alg_dh_sig_hmac_sha1 "id-alg-dh-sig-hmac-sha1" -#define NID_id_alg_dh_sig_hmac_sha1 325 -#define OBJ_id_alg_dh_sig_hmac_sha1 OBJ_id_alg,3L - -#define SN_id_alg_dh_pop "id-alg-dh-pop" -#define NID_id_alg_dh_pop 326 -#define OBJ_id_alg_dh_pop OBJ_id_alg,4L - -#define SN_id_cmc_statusInfo "id-cmc-statusInfo" -#define NID_id_cmc_statusInfo 327 -#define OBJ_id_cmc_statusInfo OBJ_id_cmc,1L - -#define SN_id_cmc_identification "id-cmc-identification" -#define NID_id_cmc_identification 328 -#define OBJ_id_cmc_identification OBJ_id_cmc,2L - -#define SN_id_cmc_identityProof "id-cmc-identityProof" -#define NID_id_cmc_identityProof 329 -#define OBJ_id_cmc_identityProof OBJ_id_cmc,3L - -#define SN_id_cmc_dataReturn "id-cmc-dataReturn" -#define NID_id_cmc_dataReturn 330 -#define OBJ_id_cmc_dataReturn OBJ_id_cmc,4L - -#define SN_id_cmc_transactionId "id-cmc-transactionId" -#define NID_id_cmc_transactionId 331 -#define OBJ_id_cmc_transactionId OBJ_id_cmc,5L - -#define SN_id_cmc_senderNonce "id-cmc-senderNonce" -#define NID_id_cmc_senderNonce 332 -#define OBJ_id_cmc_senderNonce OBJ_id_cmc,6L - -#define SN_id_cmc_recipientNonce "id-cmc-recipientNonce" -#define NID_id_cmc_recipientNonce 333 -#define OBJ_id_cmc_recipientNonce OBJ_id_cmc,7L - -#define SN_id_cmc_addExtensions "id-cmc-addExtensions" -#define NID_id_cmc_addExtensions 334 -#define OBJ_id_cmc_addExtensions OBJ_id_cmc,8L - -#define SN_id_cmc_encryptedPOP "id-cmc-encryptedPOP" -#define NID_id_cmc_encryptedPOP 335 -#define OBJ_id_cmc_encryptedPOP OBJ_id_cmc,9L - -#define SN_id_cmc_decryptedPOP "id-cmc-decryptedPOP" -#define NID_id_cmc_decryptedPOP 336 -#define OBJ_id_cmc_decryptedPOP OBJ_id_cmc,10L - -#define SN_id_cmc_lraPOPWitness "id-cmc-lraPOPWitness" -#define NID_id_cmc_lraPOPWitness 337 -#define OBJ_id_cmc_lraPOPWitness OBJ_id_cmc,11L - -#define SN_id_cmc_getCert "id-cmc-getCert" -#define NID_id_cmc_getCert 338 -#define OBJ_id_cmc_getCert OBJ_id_cmc,15L - -#define SN_id_cmc_getCRL "id-cmc-getCRL" -#define NID_id_cmc_getCRL 339 -#define OBJ_id_cmc_getCRL OBJ_id_cmc,16L - -#define SN_id_cmc_revokeRequest "id-cmc-revokeRequest" -#define NID_id_cmc_revokeRequest 340 -#define OBJ_id_cmc_revokeRequest OBJ_id_cmc,17L - -#define SN_id_cmc_regInfo "id-cmc-regInfo" -#define NID_id_cmc_regInfo 341 -#define OBJ_id_cmc_regInfo OBJ_id_cmc,18L - -#define SN_id_cmc_responseInfo "id-cmc-responseInfo" -#define NID_id_cmc_responseInfo 342 -#define OBJ_id_cmc_responseInfo OBJ_id_cmc,19L - -#define SN_id_cmc_queryPending "id-cmc-queryPending" -#define NID_id_cmc_queryPending 343 -#define OBJ_id_cmc_queryPending OBJ_id_cmc,21L - -#define SN_id_cmc_popLinkRandom "id-cmc-popLinkRandom" -#define NID_id_cmc_popLinkRandom 344 -#define OBJ_id_cmc_popLinkRandom OBJ_id_cmc,22L - -#define SN_id_cmc_popLinkWitness "id-cmc-popLinkWitness" -#define NID_id_cmc_popLinkWitness 345 -#define OBJ_id_cmc_popLinkWitness OBJ_id_cmc,23L - -#define SN_id_cmc_confirmCertAcceptance "id-cmc-confirmCertAcceptance" -#define NID_id_cmc_confirmCertAcceptance 346 -#define OBJ_id_cmc_confirmCertAcceptance OBJ_id_cmc,24L - -#define SN_id_on_personalData "id-on-personalData" -#define NID_id_on_personalData 347 -#define OBJ_id_on_personalData OBJ_id_on,1L - -#define SN_id_on_permanentIdentifier "id-on-permanentIdentifier" -#define LN_id_on_permanentIdentifier "Permanent Identifier" -#define NID_id_on_permanentIdentifier 858 -#define OBJ_id_on_permanentIdentifier OBJ_id_on,3L - -#define SN_id_pda_dateOfBirth "id-pda-dateOfBirth" -#define NID_id_pda_dateOfBirth 348 -#define OBJ_id_pda_dateOfBirth OBJ_id_pda,1L - -#define SN_id_pda_placeOfBirth "id-pda-placeOfBirth" -#define NID_id_pda_placeOfBirth 349 -#define OBJ_id_pda_placeOfBirth OBJ_id_pda,2L - -#define SN_id_pda_gender "id-pda-gender" -#define NID_id_pda_gender 351 -#define OBJ_id_pda_gender OBJ_id_pda,3L - -#define SN_id_pda_countryOfCitizenship "id-pda-countryOfCitizenship" -#define NID_id_pda_countryOfCitizenship 352 -#define OBJ_id_pda_countryOfCitizenship OBJ_id_pda,4L - -#define SN_id_pda_countryOfResidence "id-pda-countryOfResidence" -#define NID_id_pda_countryOfResidence 353 -#define OBJ_id_pda_countryOfResidence OBJ_id_pda,5L - -#define SN_id_aca_authenticationInfo "id-aca-authenticationInfo" -#define NID_id_aca_authenticationInfo 354 -#define OBJ_id_aca_authenticationInfo OBJ_id_aca,1L - -#define SN_id_aca_accessIdentity "id-aca-accessIdentity" -#define NID_id_aca_accessIdentity 355 -#define OBJ_id_aca_accessIdentity OBJ_id_aca,2L - -#define SN_id_aca_chargingIdentity "id-aca-chargingIdentity" -#define NID_id_aca_chargingIdentity 356 -#define OBJ_id_aca_chargingIdentity OBJ_id_aca,3L - -#define SN_id_aca_group "id-aca-group" -#define NID_id_aca_group 357 -#define OBJ_id_aca_group OBJ_id_aca,4L - -#define SN_id_aca_role "id-aca-role" -#define NID_id_aca_role 358 -#define OBJ_id_aca_role OBJ_id_aca,5L - -#define SN_id_aca_encAttrs "id-aca-encAttrs" -#define NID_id_aca_encAttrs 399 -#define OBJ_id_aca_encAttrs OBJ_id_aca,6L - -#define SN_id_qcs_pkixQCSyntax_v1 "id-qcs-pkixQCSyntax-v1" -#define NID_id_qcs_pkixQCSyntax_v1 359 -#define OBJ_id_qcs_pkixQCSyntax_v1 OBJ_id_qcs,1L - -#define SN_id_cct_crs "id-cct-crs" -#define NID_id_cct_crs 360 -#define OBJ_id_cct_crs OBJ_id_cct,1L - -#define SN_id_cct_PKIData "id-cct-PKIData" -#define NID_id_cct_PKIData 361 -#define OBJ_id_cct_PKIData OBJ_id_cct,2L - -#define SN_id_cct_PKIResponse "id-cct-PKIResponse" -#define NID_id_cct_PKIResponse 362 -#define OBJ_id_cct_PKIResponse OBJ_id_cct,3L - -#define SN_id_ppl_anyLanguage "id-ppl-anyLanguage" -#define LN_id_ppl_anyLanguage "Any language" -#define NID_id_ppl_anyLanguage 664 -#define OBJ_id_ppl_anyLanguage OBJ_id_ppl,0L - -#define SN_id_ppl_inheritAll "id-ppl-inheritAll" -#define LN_id_ppl_inheritAll "Inherit all" -#define NID_id_ppl_inheritAll 665 -#define OBJ_id_ppl_inheritAll OBJ_id_ppl,1L - -#define SN_Independent "id-ppl-independent" -#define LN_Independent "Independent" -#define NID_Independent 667 -#define OBJ_Independent OBJ_id_ppl,2L - -#define SN_ad_OCSP "OCSP" -#define LN_ad_OCSP "OCSP" -#define NID_ad_OCSP 178 -#define OBJ_ad_OCSP OBJ_id_ad,1L - -#define SN_ad_ca_issuers "caIssuers" -#define LN_ad_ca_issuers "CA Issuers" -#define NID_ad_ca_issuers 179 -#define OBJ_ad_ca_issuers OBJ_id_ad,2L - -#define SN_ad_timeStamping "ad_timestamping" -#define LN_ad_timeStamping "AD Time Stamping" -#define NID_ad_timeStamping 363 -#define OBJ_ad_timeStamping OBJ_id_ad,3L - -#define SN_ad_dvcs "AD_DVCS" -#define LN_ad_dvcs "ad dvcs" -#define NID_ad_dvcs 364 -#define OBJ_ad_dvcs OBJ_id_ad,4L - -#define SN_caRepository "caRepository" -#define LN_caRepository "CA Repository" -#define NID_caRepository 785 -#define OBJ_caRepository OBJ_id_ad,5L - -#define OBJ_id_pkix_OCSP OBJ_ad_OCSP - -#define SN_id_pkix_OCSP_basic "basicOCSPResponse" -#define LN_id_pkix_OCSP_basic "Basic OCSP Response" -#define NID_id_pkix_OCSP_basic 365 -#define OBJ_id_pkix_OCSP_basic OBJ_id_pkix_OCSP,1L - -#define SN_id_pkix_OCSP_Nonce "Nonce" -#define LN_id_pkix_OCSP_Nonce "OCSP Nonce" -#define NID_id_pkix_OCSP_Nonce 366 -#define OBJ_id_pkix_OCSP_Nonce OBJ_id_pkix_OCSP,2L - -#define SN_id_pkix_OCSP_CrlID "CrlID" -#define LN_id_pkix_OCSP_CrlID "OCSP CRL ID" -#define NID_id_pkix_OCSP_CrlID 367 -#define OBJ_id_pkix_OCSP_CrlID OBJ_id_pkix_OCSP,3L - -#define SN_id_pkix_OCSP_acceptableResponses "acceptableResponses" -#define LN_id_pkix_OCSP_acceptableResponses "Acceptable OCSP Responses" -#define NID_id_pkix_OCSP_acceptableResponses 368 -#define OBJ_id_pkix_OCSP_acceptableResponses OBJ_id_pkix_OCSP,4L - -#define SN_id_pkix_OCSP_noCheck "noCheck" -#define LN_id_pkix_OCSP_noCheck "OCSP No Check" -#define NID_id_pkix_OCSP_noCheck 369 -#define OBJ_id_pkix_OCSP_noCheck OBJ_id_pkix_OCSP,5L - -#define SN_id_pkix_OCSP_archiveCutoff "archiveCutoff" -#define LN_id_pkix_OCSP_archiveCutoff "OCSP Archive Cutoff" -#define NID_id_pkix_OCSP_archiveCutoff 370 -#define OBJ_id_pkix_OCSP_archiveCutoff OBJ_id_pkix_OCSP,6L - -#define SN_id_pkix_OCSP_serviceLocator "serviceLocator" -#define LN_id_pkix_OCSP_serviceLocator "OCSP Service Locator" -#define NID_id_pkix_OCSP_serviceLocator 371 -#define OBJ_id_pkix_OCSP_serviceLocator OBJ_id_pkix_OCSP,7L - -#define SN_id_pkix_OCSP_extendedStatus "extendedStatus" -#define LN_id_pkix_OCSP_extendedStatus "Extended OCSP Status" -#define NID_id_pkix_OCSP_extendedStatus 372 -#define OBJ_id_pkix_OCSP_extendedStatus OBJ_id_pkix_OCSP,8L - -#define SN_id_pkix_OCSP_valid "valid" -#define NID_id_pkix_OCSP_valid 373 -#define OBJ_id_pkix_OCSP_valid OBJ_id_pkix_OCSP,9L - -#define SN_id_pkix_OCSP_path "path" -#define NID_id_pkix_OCSP_path 374 -#define OBJ_id_pkix_OCSP_path OBJ_id_pkix_OCSP,10L - -#define SN_id_pkix_OCSP_trustRoot "trustRoot" -#define LN_id_pkix_OCSP_trustRoot "Trust Root" -#define NID_id_pkix_OCSP_trustRoot 375 -#define OBJ_id_pkix_OCSP_trustRoot OBJ_id_pkix_OCSP,11L - -#define SN_algorithm "algorithm" -#define LN_algorithm "algorithm" -#define NID_algorithm 376 -#define OBJ_algorithm 1L,3L,14L,3L,2L - -#define SN_md5WithRSA "RSA-NP-MD5" -#define LN_md5WithRSA "md5WithRSA" -#define NID_md5WithRSA 104 -#define OBJ_md5WithRSA OBJ_algorithm,3L - -#define SN_des_ecb "DES-ECB" -#define LN_des_ecb "des-ecb" -#define NID_des_ecb 29 -#define OBJ_des_ecb OBJ_algorithm,6L - -#define SN_des_cbc "DES-CBC" -#define LN_des_cbc "des-cbc" -#define NID_des_cbc 31 -#define OBJ_des_cbc OBJ_algorithm,7L - -#define SN_des_ofb64 "DES-OFB" -#define LN_des_ofb64 "des-ofb" -#define NID_des_ofb64 45 -#define OBJ_des_ofb64 OBJ_algorithm,8L - -#define SN_des_cfb64 "DES-CFB" -#define LN_des_cfb64 "des-cfb" -#define NID_des_cfb64 30 -#define OBJ_des_cfb64 OBJ_algorithm,9L - -#define SN_rsaSignature "rsaSignature" -#define NID_rsaSignature 377 -#define OBJ_rsaSignature OBJ_algorithm,11L - -#define SN_dsa_2 "DSA-old" -#define LN_dsa_2 "dsaEncryption-old" -#define NID_dsa_2 67 -#define OBJ_dsa_2 OBJ_algorithm,12L - -#define SN_dsaWithSHA "DSA-SHA" -#define LN_dsaWithSHA "dsaWithSHA" -#define NID_dsaWithSHA 66 -#define OBJ_dsaWithSHA OBJ_algorithm,13L - -#define SN_shaWithRSAEncryption "RSA-SHA" -#define LN_shaWithRSAEncryption "shaWithRSAEncryption" -#define NID_shaWithRSAEncryption 42 -#define OBJ_shaWithRSAEncryption OBJ_algorithm,15L - -#define SN_des_ede_ecb "DES-EDE" -#define LN_des_ede_ecb "des-ede" -#define NID_des_ede_ecb 32 -#define OBJ_des_ede_ecb OBJ_algorithm,17L - -#define SN_des_ede3_ecb "DES-EDE3" -#define LN_des_ede3_ecb "des-ede3" -#define NID_des_ede3_ecb 33 - -#define SN_des_ede_cbc "DES-EDE-CBC" -#define LN_des_ede_cbc "des-ede-cbc" -#define NID_des_ede_cbc 43 - -#define SN_des_ede_cfb64 "DES-EDE-CFB" -#define LN_des_ede_cfb64 "des-ede-cfb" -#define NID_des_ede_cfb64 60 - -#define SN_des_ede3_cfb64 "DES-EDE3-CFB" -#define LN_des_ede3_cfb64 "des-ede3-cfb" -#define NID_des_ede3_cfb64 61 - -#define SN_des_ede_ofb64 "DES-EDE-OFB" -#define LN_des_ede_ofb64 "des-ede-ofb" -#define NID_des_ede_ofb64 62 - -#define SN_des_ede3_ofb64 "DES-EDE3-OFB" -#define LN_des_ede3_ofb64 "des-ede3-ofb" -#define NID_des_ede3_ofb64 63 - -#define SN_desx_cbc "DESX-CBC" -#define LN_desx_cbc "desx-cbc" -#define NID_desx_cbc 80 - -#define SN_sha "SHA" -#define LN_sha "sha" -#define NID_sha 41 -#define OBJ_sha OBJ_algorithm,18L - -#define SN_sha1 "SHA1" -#define LN_sha1 "sha1" -#define NID_sha1 64 -#define OBJ_sha1 OBJ_algorithm,26L - -#define SN_dsaWithSHA1_2 "DSA-SHA1-old" -#define LN_dsaWithSHA1_2 "dsaWithSHA1-old" -#define NID_dsaWithSHA1_2 70 -#define OBJ_dsaWithSHA1_2 OBJ_algorithm,27L - -#define SN_sha1WithRSA "RSA-SHA1-2" -#define LN_sha1WithRSA "sha1WithRSA" -#define NID_sha1WithRSA 115 -#define OBJ_sha1WithRSA OBJ_algorithm,29L - -#define SN_ripemd160 "RIPEMD160" -#define LN_ripemd160 "ripemd160" -#define NID_ripemd160 117 -#define OBJ_ripemd160 1L,3L,36L,3L,2L,1L - -#define SN_ripemd160WithRSA "RSA-RIPEMD160" -#define LN_ripemd160WithRSA "ripemd160WithRSA" -#define NID_ripemd160WithRSA 119 -#define OBJ_ripemd160WithRSA 1L,3L,36L,3L,3L,1L,2L - -#define SN_sxnet "SXNetID" -#define LN_sxnet "Strong Extranet ID" -#define NID_sxnet 143 -#define OBJ_sxnet 1L,3L,101L,1L,4L,1L - -#define SN_X500 "X500" -#define LN_X500 "directory services (X.500)" -#define NID_X500 11 -#define OBJ_X500 2L,5L - -#define SN_X509 "X509" -#define NID_X509 12 -#define OBJ_X509 OBJ_X500,4L - -#define SN_commonName "CN" -#define LN_commonName "commonName" -#define NID_commonName 13 -#define OBJ_commonName OBJ_X509,3L - -#define SN_surname "SN" -#define LN_surname "surname" -#define NID_surname 100 -#define OBJ_surname OBJ_X509,4L - -#define LN_serialNumber "serialNumber" -#define NID_serialNumber 105 -#define OBJ_serialNumber OBJ_X509,5L - -#define SN_countryName "C" -#define LN_countryName "countryName" -#define NID_countryName 14 -#define OBJ_countryName OBJ_X509,6L - -#define SN_localityName "L" -#define LN_localityName "localityName" -#define NID_localityName 15 -#define OBJ_localityName OBJ_X509,7L - -#define SN_stateOrProvinceName "ST" -#define LN_stateOrProvinceName "stateOrProvinceName" -#define NID_stateOrProvinceName 16 -#define OBJ_stateOrProvinceName OBJ_X509,8L - -#define SN_streetAddress "street" -#define LN_streetAddress "streetAddress" -#define NID_streetAddress 660 -#define OBJ_streetAddress OBJ_X509,9L - -#define SN_organizationName "O" -#define LN_organizationName "organizationName" -#define NID_organizationName 17 -#define OBJ_organizationName OBJ_X509,10L - -#define SN_organizationalUnitName "OU" -#define LN_organizationalUnitName "organizationalUnitName" -#define NID_organizationalUnitName 18 -#define OBJ_organizationalUnitName OBJ_X509,11L - -#define SN_title "title" -#define LN_title "title" -#define NID_title 106 -#define OBJ_title OBJ_X509,12L - -#define LN_description "description" -#define NID_description 107 -#define OBJ_description OBJ_X509,13L - -#define LN_searchGuide "searchGuide" -#define NID_searchGuide 859 -#define OBJ_searchGuide OBJ_X509,14L - -#define LN_businessCategory "businessCategory" -#define NID_businessCategory 860 -#define OBJ_businessCategory OBJ_X509,15L - -#define LN_postalAddress "postalAddress" -#define NID_postalAddress 861 -#define OBJ_postalAddress OBJ_X509,16L - -#define LN_postalCode "postalCode" -#define NID_postalCode 661 -#define OBJ_postalCode OBJ_X509,17L - -#define LN_postOfficeBox "postOfficeBox" -#define NID_postOfficeBox 862 -#define OBJ_postOfficeBox OBJ_X509,18L - -#define LN_physicalDeliveryOfficeName "physicalDeliveryOfficeName" -#define NID_physicalDeliveryOfficeName 863 -#define OBJ_physicalDeliveryOfficeName OBJ_X509,19L - -#define LN_telephoneNumber "telephoneNumber" -#define NID_telephoneNumber 864 -#define OBJ_telephoneNumber OBJ_X509,20L - -#define LN_telexNumber "telexNumber" -#define NID_telexNumber 865 -#define OBJ_telexNumber OBJ_X509,21L - -#define LN_teletexTerminalIdentifier "teletexTerminalIdentifier" -#define NID_teletexTerminalIdentifier 866 -#define OBJ_teletexTerminalIdentifier OBJ_X509,22L - -#define LN_facsimileTelephoneNumber "facsimileTelephoneNumber" -#define NID_facsimileTelephoneNumber 867 -#define OBJ_facsimileTelephoneNumber OBJ_X509,23L - -#define LN_x121Address "x121Address" -#define NID_x121Address 868 -#define OBJ_x121Address OBJ_X509,24L - -#define LN_internationaliSDNNumber "internationaliSDNNumber" -#define NID_internationaliSDNNumber 869 -#define OBJ_internationaliSDNNumber OBJ_X509,25L - -#define LN_registeredAddress "registeredAddress" -#define NID_registeredAddress 870 -#define OBJ_registeredAddress OBJ_X509,26L - -#define LN_destinationIndicator "destinationIndicator" -#define NID_destinationIndicator 871 -#define OBJ_destinationIndicator OBJ_X509,27L - -#define LN_preferredDeliveryMethod "preferredDeliveryMethod" -#define NID_preferredDeliveryMethod 872 -#define OBJ_preferredDeliveryMethod OBJ_X509,28L - -#define LN_presentationAddress "presentationAddress" -#define NID_presentationAddress 873 -#define OBJ_presentationAddress OBJ_X509,29L - -#define LN_supportedApplicationContext "supportedApplicationContext" -#define NID_supportedApplicationContext 874 -#define OBJ_supportedApplicationContext OBJ_X509,30L - -#define SN_member "member" -#define NID_member 875 -#define OBJ_member OBJ_X509,31L - -#define SN_owner "owner" -#define NID_owner 876 -#define OBJ_owner OBJ_X509,32L - -#define LN_roleOccupant "roleOccupant" -#define NID_roleOccupant 877 -#define OBJ_roleOccupant OBJ_X509,33L - -#define SN_seeAlso "seeAlso" -#define NID_seeAlso 878 -#define OBJ_seeAlso OBJ_X509,34L - -#define LN_userPassword "userPassword" -#define NID_userPassword 879 -#define OBJ_userPassword OBJ_X509,35L - -#define LN_userCertificate "userCertificate" -#define NID_userCertificate 880 -#define OBJ_userCertificate OBJ_X509,36L - -#define LN_cACertificate "cACertificate" -#define NID_cACertificate 881 -#define OBJ_cACertificate OBJ_X509,37L - -#define LN_authorityRevocationList "authorityRevocationList" -#define NID_authorityRevocationList 882 -#define OBJ_authorityRevocationList OBJ_X509,38L - -#define LN_certificateRevocationList "certificateRevocationList" -#define NID_certificateRevocationList 883 -#define OBJ_certificateRevocationList OBJ_X509,39L - -#define LN_crossCertificatePair "crossCertificatePair" -#define NID_crossCertificatePair 884 -#define OBJ_crossCertificatePair OBJ_X509,40L - -#define SN_name "name" -#define LN_name "name" -#define NID_name 173 -#define OBJ_name OBJ_X509,41L - -#define SN_givenName "GN" -#define LN_givenName "givenName" -#define NID_givenName 99 -#define OBJ_givenName OBJ_X509,42L - -#define SN_initials "initials" -#define LN_initials "initials" -#define NID_initials 101 -#define OBJ_initials OBJ_X509,43L - -#define LN_generationQualifier "generationQualifier" -#define NID_generationQualifier 509 -#define OBJ_generationQualifier OBJ_X509,44L - -#define LN_x500UniqueIdentifier "x500UniqueIdentifier" -#define NID_x500UniqueIdentifier 503 -#define OBJ_x500UniqueIdentifier OBJ_X509,45L - -#define SN_dnQualifier "dnQualifier" -#define LN_dnQualifier "dnQualifier" -#define NID_dnQualifier 174 -#define OBJ_dnQualifier OBJ_X509,46L - -#define LN_enhancedSearchGuide "enhancedSearchGuide" -#define NID_enhancedSearchGuide 885 -#define OBJ_enhancedSearchGuide OBJ_X509,47L - -#define LN_protocolInformation "protocolInformation" -#define NID_protocolInformation 886 -#define OBJ_protocolInformation OBJ_X509,48L - -#define LN_distinguishedName "distinguishedName" -#define NID_distinguishedName 887 -#define OBJ_distinguishedName OBJ_X509,49L - -#define LN_uniqueMember "uniqueMember" -#define NID_uniqueMember 888 -#define OBJ_uniqueMember OBJ_X509,50L - -#define LN_houseIdentifier "houseIdentifier" -#define NID_houseIdentifier 889 -#define OBJ_houseIdentifier OBJ_X509,51L - -#define LN_supportedAlgorithms "supportedAlgorithms" -#define NID_supportedAlgorithms 890 -#define OBJ_supportedAlgorithms OBJ_X509,52L - -#define LN_deltaRevocationList "deltaRevocationList" -#define NID_deltaRevocationList 891 -#define OBJ_deltaRevocationList OBJ_X509,53L - -#define SN_dmdName "dmdName" -#define NID_dmdName 892 -#define OBJ_dmdName OBJ_X509,54L - -#define LN_pseudonym "pseudonym" -#define NID_pseudonym 510 -#define OBJ_pseudonym OBJ_X509,65L - -#define SN_role "role" -#define LN_role "role" -#define NID_role 400 -#define OBJ_role OBJ_X509,72L - -#define SN_X500algorithms "X500algorithms" -#define LN_X500algorithms "directory services - algorithms" -#define NID_X500algorithms 378 -#define OBJ_X500algorithms OBJ_X500,8L - -#define SN_rsa "RSA" -#define LN_rsa "rsa" -#define NID_rsa 19 -#define OBJ_rsa OBJ_X500algorithms,1L,1L - -#define SN_mdc2WithRSA "RSA-MDC2" -#define LN_mdc2WithRSA "mdc2WithRSA" -#define NID_mdc2WithRSA 96 -#define OBJ_mdc2WithRSA OBJ_X500algorithms,3L,100L - -#define SN_mdc2 "MDC2" -#define LN_mdc2 "mdc2" -#define NID_mdc2 95 -#define OBJ_mdc2 OBJ_X500algorithms,3L,101L - -#define SN_id_ce "id-ce" -#define NID_id_ce 81 -#define OBJ_id_ce OBJ_X500,29L - -#define SN_subject_directory_attributes "subjectDirectoryAttributes" -#define LN_subject_directory_attributes "X509v3 Subject Directory Attributes" -#define NID_subject_directory_attributes 769 -#define OBJ_subject_directory_attributes OBJ_id_ce,9L - -#define SN_subject_key_identifier "subjectKeyIdentifier" -#define LN_subject_key_identifier "X509v3 Subject Key Identifier" -#define NID_subject_key_identifier 82 -#define OBJ_subject_key_identifier OBJ_id_ce,14L - -#define SN_key_usage "keyUsage" -#define LN_key_usage "X509v3 Key Usage" -#define NID_key_usage 83 -#define OBJ_key_usage OBJ_id_ce,15L - -#define SN_private_key_usage_period "privateKeyUsagePeriod" -#define LN_private_key_usage_period "X509v3 Private Key Usage Period" -#define NID_private_key_usage_period 84 -#define OBJ_private_key_usage_period OBJ_id_ce,16L - -#define SN_subject_alt_name "subjectAltName" -#define LN_subject_alt_name "X509v3 Subject Alternative Name" -#define NID_subject_alt_name 85 -#define OBJ_subject_alt_name OBJ_id_ce,17L - -#define SN_issuer_alt_name "issuerAltName" -#define LN_issuer_alt_name "X509v3 Issuer Alternative Name" -#define NID_issuer_alt_name 86 -#define OBJ_issuer_alt_name OBJ_id_ce,18L - -#define SN_basic_constraints "basicConstraints" -#define LN_basic_constraints "X509v3 Basic Constraints" -#define NID_basic_constraints 87 -#define OBJ_basic_constraints OBJ_id_ce,19L - -#define SN_crl_number "crlNumber" -#define LN_crl_number "X509v3 CRL Number" -#define NID_crl_number 88 -#define OBJ_crl_number OBJ_id_ce,20L - -#define SN_crl_reason "CRLReason" -#define LN_crl_reason "X509v3 CRL Reason Code" -#define NID_crl_reason 141 -#define OBJ_crl_reason OBJ_id_ce,21L - -#define SN_invalidity_date "invalidityDate" -#define LN_invalidity_date "Invalidity Date" -#define NID_invalidity_date 142 -#define OBJ_invalidity_date OBJ_id_ce,24L - -#define SN_delta_crl "deltaCRL" -#define LN_delta_crl "X509v3 Delta CRL Indicator" -#define NID_delta_crl 140 -#define OBJ_delta_crl OBJ_id_ce,27L - -#define SN_issuing_distribution_point "issuingDistributionPoint" -#define LN_issuing_distribution_point "X509v3 Issuing Distrubution Point" -#define NID_issuing_distribution_point 770 -#define OBJ_issuing_distribution_point OBJ_id_ce,28L - -#define SN_certificate_issuer "certificateIssuer" -#define LN_certificate_issuer "X509v3 Certificate Issuer" -#define NID_certificate_issuer 771 -#define OBJ_certificate_issuer OBJ_id_ce,29L - -#define SN_name_constraints "nameConstraints" -#define LN_name_constraints "X509v3 Name Constraints" -#define NID_name_constraints 666 -#define OBJ_name_constraints OBJ_id_ce,30L - -#define SN_crl_distribution_points "crlDistributionPoints" -#define LN_crl_distribution_points "X509v3 CRL Distribution Points" -#define NID_crl_distribution_points 103 -#define OBJ_crl_distribution_points OBJ_id_ce,31L - -#define SN_certificate_policies "certificatePolicies" -#define LN_certificate_policies "X509v3 Certificate Policies" -#define NID_certificate_policies 89 -#define OBJ_certificate_policies OBJ_id_ce,32L - -#define SN_any_policy "anyPolicy" -#define LN_any_policy "X509v3 Any Policy" -#define NID_any_policy 746 -#define OBJ_any_policy OBJ_certificate_policies,0L - -#define SN_policy_mappings "policyMappings" -#define LN_policy_mappings "X509v3 Policy Mappings" -#define NID_policy_mappings 747 -#define OBJ_policy_mappings OBJ_id_ce,33L - -#define SN_authority_key_identifier "authorityKeyIdentifier" -#define LN_authority_key_identifier "X509v3 Authority Key Identifier" -#define NID_authority_key_identifier 90 -#define OBJ_authority_key_identifier OBJ_id_ce,35L - -#define SN_policy_constraints "policyConstraints" -#define LN_policy_constraints "X509v3 Policy Constraints" -#define NID_policy_constraints 401 -#define OBJ_policy_constraints OBJ_id_ce,36L - -#define SN_ext_key_usage "extendedKeyUsage" -#define LN_ext_key_usage "X509v3 Extended Key Usage" -#define NID_ext_key_usage 126 -#define OBJ_ext_key_usage OBJ_id_ce,37L - -#define SN_freshest_crl "freshestCRL" -#define LN_freshest_crl "X509v3 Freshest CRL" -#define NID_freshest_crl 857 -#define OBJ_freshest_crl OBJ_id_ce,46L - -#define SN_inhibit_any_policy "inhibitAnyPolicy" -#define LN_inhibit_any_policy "X509v3 Inhibit Any Policy" -#define NID_inhibit_any_policy 748 -#define OBJ_inhibit_any_policy OBJ_id_ce,54L - -#define SN_target_information "targetInformation" -#define LN_target_information "X509v3 AC Targeting" -#define NID_target_information 402 -#define OBJ_target_information OBJ_id_ce,55L - -#define SN_no_rev_avail "noRevAvail" -#define LN_no_rev_avail "X509v3 No Revocation Available" -#define NID_no_rev_avail 403 -#define OBJ_no_rev_avail OBJ_id_ce,56L - -#define SN_anyExtendedKeyUsage "anyExtendedKeyUsage" -#define LN_anyExtendedKeyUsage "Any Extended Key Usage" -#define NID_anyExtendedKeyUsage 910 -#define OBJ_anyExtendedKeyUsage OBJ_ext_key_usage,0L - -#define SN_netscape "Netscape" -#define LN_netscape "Netscape Communications Corp." -#define NID_netscape 57 -#define OBJ_netscape 2L,16L,840L,1L,113730L - -#define SN_netscape_cert_extension "nsCertExt" -#define LN_netscape_cert_extension "Netscape Certificate Extension" -#define NID_netscape_cert_extension 58 -#define OBJ_netscape_cert_extension OBJ_netscape,1L - -#define SN_netscape_data_type "nsDataType" -#define LN_netscape_data_type "Netscape Data Type" -#define NID_netscape_data_type 59 -#define OBJ_netscape_data_type OBJ_netscape,2L - -#define SN_netscape_cert_type "nsCertType" -#define LN_netscape_cert_type "Netscape Cert Type" -#define NID_netscape_cert_type 71 -#define OBJ_netscape_cert_type OBJ_netscape_cert_extension,1L - -#define SN_netscape_base_url "nsBaseUrl" -#define LN_netscape_base_url "Netscape Base Url" -#define NID_netscape_base_url 72 -#define OBJ_netscape_base_url OBJ_netscape_cert_extension,2L - -#define SN_netscape_revocation_url "nsRevocationUrl" -#define LN_netscape_revocation_url "Netscape Revocation Url" -#define NID_netscape_revocation_url 73 -#define OBJ_netscape_revocation_url OBJ_netscape_cert_extension,3L - -#define SN_netscape_ca_revocation_url "nsCaRevocationUrl" -#define LN_netscape_ca_revocation_url "Netscape CA Revocation Url" -#define NID_netscape_ca_revocation_url 74 -#define OBJ_netscape_ca_revocation_url OBJ_netscape_cert_extension,4L - -#define SN_netscape_renewal_url "nsRenewalUrl" -#define LN_netscape_renewal_url "Netscape Renewal Url" -#define NID_netscape_renewal_url 75 -#define OBJ_netscape_renewal_url OBJ_netscape_cert_extension,7L - -#define SN_netscape_ca_policy_url "nsCaPolicyUrl" -#define LN_netscape_ca_policy_url "Netscape CA Policy Url" -#define NID_netscape_ca_policy_url 76 -#define OBJ_netscape_ca_policy_url OBJ_netscape_cert_extension,8L - -#define SN_netscape_ssl_server_name "nsSslServerName" -#define LN_netscape_ssl_server_name "Netscape SSL Server Name" -#define NID_netscape_ssl_server_name 77 -#define OBJ_netscape_ssl_server_name OBJ_netscape_cert_extension,12L - -#define SN_netscape_comment "nsComment" -#define LN_netscape_comment "Netscape Comment" -#define NID_netscape_comment 78 -#define OBJ_netscape_comment OBJ_netscape_cert_extension,13L - -#define SN_netscape_cert_sequence "nsCertSequence" -#define LN_netscape_cert_sequence "Netscape Certificate Sequence" -#define NID_netscape_cert_sequence 79 -#define OBJ_netscape_cert_sequence OBJ_netscape_data_type,5L - -#define SN_ns_sgc "nsSGC" -#define LN_ns_sgc "Netscape Server Gated Crypto" -#define NID_ns_sgc 139 -#define OBJ_ns_sgc OBJ_netscape,4L,1L - -#define SN_org "ORG" -#define LN_org "org" -#define NID_org 379 -#define OBJ_org OBJ_iso,3L - -#define SN_dod "DOD" -#define LN_dod "dod" -#define NID_dod 380 -#define OBJ_dod OBJ_org,6L - -#define SN_iana "IANA" -#define LN_iana "iana" -#define NID_iana 381 -#define OBJ_iana OBJ_dod,1L - -#define OBJ_internet OBJ_iana - -#define SN_Directory "directory" -#define LN_Directory "Directory" -#define NID_Directory 382 -#define OBJ_Directory OBJ_internet,1L - -#define SN_Management "mgmt" -#define LN_Management "Management" -#define NID_Management 383 -#define OBJ_Management OBJ_internet,2L - -#define SN_Experimental "experimental" -#define LN_Experimental "Experimental" -#define NID_Experimental 384 -#define OBJ_Experimental OBJ_internet,3L - -#define SN_Private "private" -#define LN_Private "Private" -#define NID_Private 385 -#define OBJ_Private OBJ_internet,4L - -#define SN_Security "security" -#define LN_Security "Security" -#define NID_Security 386 -#define OBJ_Security OBJ_internet,5L - -#define SN_SNMPv2 "snmpv2" -#define LN_SNMPv2 "SNMPv2" -#define NID_SNMPv2 387 -#define OBJ_SNMPv2 OBJ_internet,6L - -#define LN_Mail "Mail" -#define NID_Mail 388 -#define OBJ_Mail OBJ_internet,7L - -#define SN_Enterprises "enterprises" -#define LN_Enterprises "Enterprises" -#define NID_Enterprises 389 -#define OBJ_Enterprises OBJ_Private,1L - -#define SN_dcObject "dcobject" -#define LN_dcObject "dcObject" -#define NID_dcObject 390 -#define OBJ_dcObject OBJ_Enterprises,1466L,344L - -#define SN_mime_mhs "mime-mhs" -#define LN_mime_mhs "MIME MHS" -#define NID_mime_mhs 504 -#define OBJ_mime_mhs OBJ_Mail,1L - -#define SN_mime_mhs_headings "mime-mhs-headings" -#define LN_mime_mhs_headings "mime-mhs-headings" -#define NID_mime_mhs_headings 505 -#define OBJ_mime_mhs_headings OBJ_mime_mhs,1L - -#define SN_mime_mhs_bodies "mime-mhs-bodies" -#define LN_mime_mhs_bodies "mime-mhs-bodies" -#define NID_mime_mhs_bodies 506 -#define OBJ_mime_mhs_bodies OBJ_mime_mhs,2L - -#define SN_id_hex_partial_message "id-hex-partial-message" -#define LN_id_hex_partial_message "id-hex-partial-message" -#define NID_id_hex_partial_message 507 -#define OBJ_id_hex_partial_message OBJ_mime_mhs_headings,1L - -#define SN_id_hex_multipart_message "id-hex-multipart-message" -#define LN_id_hex_multipart_message "id-hex-multipart-message" -#define NID_id_hex_multipart_message 508 -#define OBJ_id_hex_multipart_message OBJ_mime_mhs_headings,2L - -#define SN_rle_compression "RLE" -#define LN_rle_compression "run length compression" -#define NID_rle_compression 124 -#define OBJ_rle_compression 1L,1L,1L,1L,666L,1L - -#define SN_zlib_compression "ZLIB" -#define LN_zlib_compression "zlib compression" -#define NID_zlib_compression 125 -#define OBJ_zlib_compression OBJ_id_smime_alg,8L - -#define OBJ_csor 2L,16L,840L,1L,101L,3L - -#define OBJ_nistAlgorithms OBJ_csor,4L - -#define OBJ_aes OBJ_nistAlgorithms,1L - -#define SN_aes_128_ecb "AES-128-ECB" -#define LN_aes_128_ecb "aes-128-ecb" -#define NID_aes_128_ecb 418 -#define OBJ_aes_128_ecb OBJ_aes,1L - -#define SN_aes_128_cbc "AES-128-CBC" -#define LN_aes_128_cbc "aes-128-cbc" -#define NID_aes_128_cbc 419 -#define OBJ_aes_128_cbc OBJ_aes,2L - -#define SN_aes_128_ofb128 "AES-128-OFB" -#define LN_aes_128_ofb128 "aes-128-ofb" -#define NID_aes_128_ofb128 420 -#define OBJ_aes_128_ofb128 OBJ_aes,3L - -#define SN_aes_128_cfb128 "AES-128-CFB" -#define LN_aes_128_cfb128 "aes-128-cfb" -#define NID_aes_128_cfb128 421 -#define OBJ_aes_128_cfb128 OBJ_aes,4L - -#define SN_id_aes128_wrap "id-aes128-wrap" -#define NID_id_aes128_wrap 788 -#define OBJ_id_aes128_wrap OBJ_aes,5L - -#define SN_aes_128_gcm "id-aes128-GCM" -#define LN_aes_128_gcm "aes-128-gcm" -#define NID_aes_128_gcm 895 -#define OBJ_aes_128_gcm OBJ_aes,6L - -#define SN_aes_128_ccm "id-aes128-CCM" -#define LN_aes_128_ccm "aes-128-ccm" -#define NID_aes_128_ccm 896 -#define OBJ_aes_128_ccm OBJ_aes,7L - -#define SN_id_aes128_wrap_pad "id-aes128-wrap-pad" -#define NID_id_aes128_wrap_pad 897 -#define OBJ_id_aes128_wrap_pad OBJ_aes,8L - -#define SN_aes_192_ecb "AES-192-ECB" -#define LN_aes_192_ecb "aes-192-ecb" -#define NID_aes_192_ecb 422 -#define OBJ_aes_192_ecb OBJ_aes,21L - -#define SN_aes_192_cbc "AES-192-CBC" -#define LN_aes_192_cbc "aes-192-cbc" -#define NID_aes_192_cbc 423 -#define OBJ_aes_192_cbc OBJ_aes,22L - -#define SN_aes_192_ofb128 "AES-192-OFB" -#define LN_aes_192_ofb128 "aes-192-ofb" -#define NID_aes_192_ofb128 424 -#define OBJ_aes_192_ofb128 OBJ_aes,23L - -#define SN_aes_192_cfb128 "AES-192-CFB" -#define LN_aes_192_cfb128 "aes-192-cfb" -#define NID_aes_192_cfb128 425 -#define OBJ_aes_192_cfb128 OBJ_aes,24L - -#define SN_id_aes192_wrap "id-aes192-wrap" -#define NID_id_aes192_wrap 789 -#define OBJ_id_aes192_wrap OBJ_aes,25L - -#define SN_aes_192_gcm "id-aes192-GCM" -#define LN_aes_192_gcm "aes-192-gcm" -#define NID_aes_192_gcm 898 -#define OBJ_aes_192_gcm OBJ_aes,26L - -#define SN_aes_192_ccm "id-aes192-CCM" -#define LN_aes_192_ccm "aes-192-ccm" -#define NID_aes_192_ccm 899 -#define OBJ_aes_192_ccm OBJ_aes,27L - -#define SN_id_aes192_wrap_pad "id-aes192-wrap-pad" -#define NID_id_aes192_wrap_pad 900 -#define OBJ_id_aes192_wrap_pad OBJ_aes,28L - -#define SN_aes_256_ecb "AES-256-ECB" -#define LN_aes_256_ecb "aes-256-ecb" -#define NID_aes_256_ecb 426 -#define OBJ_aes_256_ecb OBJ_aes,41L - -#define SN_aes_256_cbc "AES-256-CBC" -#define LN_aes_256_cbc "aes-256-cbc" -#define NID_aes_256_cbc 427 -#define OBJ_aes_256_cbc OBJ_aes,42L - -#define SN_aes_256_ofb128 "AES-256-OFB" -#define LN_aes_256_ofb128 "aes-256-ofb" -#define NID_aes_256_ofb128 428 -#define OBJ_aes_256_ofb128 OBJ_aes,43L - -#define SN_aes_256_cfb128 "AES-256-CFB" -#define LN_aes_256_cfb128 "aes-256-cfb" -#define NID_aes_256_cfb128 429 -#define OBJ_aes_256_cfb128 OBJ_aes,44L - -#define SN_id_aes256_wrap "id-aes256-wrap" -#define NID_id_aes256_wrap 790 -#define OBJ_id_aes256_wrap OBJ_aes,45L - -#define SN_aes_256_gcm "id-aes256-GCM" -#define LN_aes_256_gcm "aes-256-gcm" -#define NID_aes_256_gcm 901 -#define OBJ_aes_256_gcm OBJ_aes,46L - -#define SN_aes_256_ccm "id-aes256-CCM" -#define LN_aes_256_ccm "aes-256-ccm" -#define NID_aes_256_ccm 902 -#define OBJ_aes_256_ccm OBJ_aes,47L - -#define SN_id_aes256_wrap_pad "id-aes256-wrap-pad" -#define NID_id_aes256_wrap_pad 903 -#define OBJ_id_aes256_wrap_pad OBJ_aes,48L - -#define SN_aes_128_cfb1 "AES-128-CFB1" -#define LN_aes_128_cfb1 "aes-128-cfb1" -#define NID_aes_128_cfb1 650 - -#define SN_aes_192_cfb1 "AES-192-CFB1" -#define LN_aes_192_cfb1 "aes-192-cfb1" -#define NID_aes_192_cfb1 651 - -#define SN_aes_256_cfb1 "AES-256-CFB1" -#define LN_aes_256_cfb1 "aes-256-cfb1" -#define NID_aes_256_cfb1 652 - -#define SN_aes_128_cfb8 "AES-128-CFB8" -#define LN_aes_128_cfb8 "aes-128-cfb8" -#define NID_aes_128_cfb8 653 - -#define SN_aes_192_cfb8 "AES-192-CFB8" -#define LN_aes_192_cfb8 "aes-192-cfb8" -#define NID_aes_192_cfb8 654 - -#define SN_aes_256_cfb8 "AES-256-CFB8" -#define LN_aes_256_cfb8 "aes-256-cfb8" -#define NID_aes_256_cfb8 655 - -#define SN_aes_128_ctr "AES-128-CTR" -#define LN_aes_128_ctr "aes-128-ctr" -#define NID_aes_128_ctr 904 - -#define SN_aes_192_ctr "AES-192-CTR" -#define LN_aes_192_ctr "aes-192-ctr" -#define NID_aes_192_ctr 905 - -#define SN_aes_256_ctr "AES-256-CTR" -#define LN_aes_256_ctr "aes-256-ctr" -#define NID_aes_256_ctr 906 - -#define SN_aes_128_xts "AES-128-XTS" -#define LN_aes_128_xts "aes-128-xts" -#define NID_aes_128_xts 913 - -#define SN_aes_256_xts "AES-256-XTS" -#define LN_aes_256_xts "aes-256-xts" -#define NID_aes_256_xts 914 - -#define SN_des_cfb1 "DES-CFB1" -#define LN_des_cfb1 "des-cfb1" -#define NID_des_cfb1 656 - -#define SN_des_cfb8 "DES-CFB8" -#define LN_des_cfb8 "des-cfb8" -#define NID_des_cfb8 657 - -#define SN_des_ede3_cfb1 "DES-EDE3-CFB1" -#define LN_des_ede3_cfb1 "des-ede3-cfb1" -#define NID_des_ede3_cfb1 658 - -#define SN_des_ede3_cfb8 "DES-EDE3-CFB8" -#define LN_des_ede3_cfb8 "des-ede3-cfb8" -#define NID_des_ede3_cfb8 659 - -#define OBJ_nist_hashalgs OBJ_nistAlgorithms,2L - -#define SN_sha256 "SHA256" -#define LN_sha256 "sha256" -#define NID_sha256 672 -#define OBJ_sha256 OBJ_nist_hashalgs,1L - -#define SN_sha384 "SHA384" -#define LN_sha384 "sha384" -#define NID_sha384 673 -#define OBJ_sha384 OBJ_nist_hashalgs,2L - -#define SN_sha512 "SHA512" -#define LN_sha512 "sha512" -#define NID_sha512 674 -#define OBJ_sha512 OBJ_nist_hashalgs,3L - -#define SN_sha224 "SHA224" -#define LN_sha224 "sha224" -#define NID_sha224 675 -#define OBJ_sha224 OBJ_nist_hashalgs,4L - -#define OBJ_dsa_with_sha2 OBJ_nistAlgorithms,3L - -#define SN_dsa_with_SHA224 "dsa_with_SHA224" -#define NID_dsa_with_SHA224 802 -#define OBJ_dsa_with_SHA224 OBJ_dsa_with_sha2,1L - -#define SN_dsa_with_SHA256 "dsa_with_SHA256" -#define NID_dsa_with_SHA256 803 -#define OBJ_dsa_with_SHA256 OBJ_dsa_with_sha2,2L - -#define SN_hold_instruction_code "holdInstructionCode" -#define LN_hold_instruction_code "Hold Instruction Code" -#define NID_hold_instruction_code 430 -#define OBJ_hold_instruction_code OBJ_id_ce,23L - -#define OBJ_holdInstruction OBJ_X9_57,2L - -#define SN_hold_instruction_none "holdInstructionNone" -#define LN_hold_instruction_none "Hold Instruction None" -#define NID_hold_instruction_none 431 -#define OBJ_hold_instruction_none OBJ_holdInstruction,1L - -#define SN_hold_instruction_call_issuer "holdInstructionCallIssuer" -#define LN_hold_instruction_call_issuer "Hold Instruction Call Issuer" -#define NID_hold_instruction_call_issuer 432 -#define OBJ_hold_instruction_call_issuer OBJ_holdInstruction,2L - -#define SN_hold_instruction_reject "holdInstructionReject" -#define LN_hold_instruction_reject "Hold Instruction Reject" -#define NID_hold_instruction_reject 433 -#define OBJ_hold_instruction_reject OBJ_holdInstruction,3L - -#define SN_data "data" -#define NID_data 434 -#define OBJ_data OBJ_itu_t,9L - -#define SN_pss "pss" -#define NID_pss 435 -#define OBJ_pss OBJ_data,2342L - -#define SN_ucl "ucl" -#define NID_ucl 436 -#define OBJ_ucl OBJ_pss,19200300L - -#define SN_pilot "pilot" -#define NID_pilot 437 -#define OBJ_pilot OBJ_ucl,100L - -#define LN_pilotAttributeType "pilotAttributeType" -#define NID_pilotAttributeType 438 -#define OBJ_pilotAttributeType OBJ_pilot,1L - -#define LN_pilotAttributeSyntax "pilotAttributeSyntax" -#define NID_pilotAttributeSyntax 439 -#define OBJ_pilotAttributeSyntax OBJ_pilot,3L - -#define LN_pilotObjectClass "pilotObjectClass" -#define NID_pilotObjectClass 440 -#define OBJ_pilotObjectClass OBJ_pilot,4L - -#define LN_pilotGroups "pilotGroups" -#define NID_pilotGroups 441 -#define OBJ_pilotGroups OBJ_pilot,10L - -#define LN_iA5StringSyntax "iA5StringSyntax" -#define NID_iA5StringSyntax 442 -#define OBJ_iA5StringSyntax OBJ_pilotAttributeSyntax,4L - -#define LN_caseIgnoreIA5StringSyntax "caseIgnoreIA5StringSyntax" -#define NID_caseIgnoreIA5StringSyntax 443 -#define OBJ_caseIgnoreIA5StringSyntax OBJ_pilotAttributeSyntax,5L - -#define LN_pilotObject "pilotObject" -#define NID_pilotObject 444 -#define OBJ_pilotObject OBJ_pilotObjectClass,3L - -#define LN_pilotPerson "pilotPerson" -#define NID_pilotPerson 445 -#define OBJ_pilotPerson OBJ_pilotObjectClass,4L - -#define SN_account "account" -#define NID_account 446 -#define OBJ_account OBJ_pilotObjectClass,5L - -#define SN_document "document" -#define NID_document 447 -#define OBJ_document OBJ_pilotObjectClass,6L - -#define SN_room "room" -#define NID_room 448 -#define OBJ_room OBJ_pilotObjectClass,7L - -#define LN_documentSeries "documentSeries" -#define NID_documentSeries 449 -#define OBJ_documentSeries OBJ_pilotObjectClass,9L - -#define SN_Domain "domain" -#define LN_Domain "Domain" -#define NID_Domain 392 -#define OBJ_Domain OBJ_pilotObjectClass,13L - -#define LN_rFC822localPart "rFC822localPart" -#define NID_rFC822localPart 450 -#define OBJ_rFC822localPart OBJ_pilotObjectClass,14L - -#define LN_dNSDomain "dNSDomain" -#define NID_dNSDomain 451 -#define OBJ_dNSDomain OBJ_pilotObjectClass,15L - -#define LN_domainRelatedObject "domainRelatedObject" -#define NID_domainRelatedObject 452 -#define OBJ_domainRelatedObject OBJ_pilotObjectClass,17L - -#define LN_friendlyCountry "friendlyCountry" -#define NID_friendlyCountry 453 -#define OBJ_friendlyCountry OBJ_pilotObjectClass,18L - -#define LN_simpleSecurityObject "simpleSecurityObject" -#define NID_simpleSecurityObject 454 -#define OBJ_simpleSecurityObject OBJ_pilotObjectClass,19L - -#define LN_pilotOrganization "pilotOrganization" -#define NID_pilotOrganization 455 -#define OBJ_pilotOrganization OBJ_pilotObjectClass,20L - -#define LN_pilotDSA "pilotDSA" -#define NID_pilotDSA 456 -#define OBJ_pilotDSA OBJ_pilotObjectClass,21L - -#define LN_qualityLabelledData "qualityLabelledData" -#define NID_qualityLabelledData 457 -#define OBJ_qualityLabelledData OBJ_pilotObjectClass,22L - -#define SN_userId "UID" -#define LN_userId "userId" -#define NID_userId 458 -#define OBJ_userId OBJ_pilotAttributeType,1L - -#define LN_textEncodedORAddress "textEncodedORAddress" -#define NID_textEncodedORAddress 459 -#define OBJ_textEncodedORAddress OBJ_pilotAttributeType,2L - -#define SN_rfc822Mailbox "mail" -#define LN_rfc822Mailbox "rfc822Mailbox" -#define NID_rfc822Mailbox 460 -#define OBJ_rfc822Mailbox OBJ_pilotAttributeType,3L - -#define SN_info "info" -#define NID_info 461 -#define OBJ_info OBJ_pilotAttributeType,4L - -#define LN_favouriteDrink "favouriteDrink" -#define NID_favouriteDrink 462 -#define OBJ_favouriteDrink OBJ_pilotAttributeType,5L - -#define LN_roomNumber "roomNumber" -#define NID_roomNumber 463 -#define OBJ_roomNumber OBJ_pilotAttributeType,6L - -#define SN_photo "photo" -#define NID_photo 464 -#define OBJ_photo OBJ_pilotAttributeType,7L - -#define LN_userClass "userClass" -#define NID_userClass 465 -#define OBJ_userClass OBJ_pilotAttributeType,8L - -#define SN_host "host" -#define NID_host 466 -#define OBJ_host OBJ_pilotAttributeType,9L - -#define SN_manager "manager" -#define NID_manager 467 -#define OBJ_manager OBJ_pilotAttributeType,10L - -#define LN_documentIdentifier "documentIdentifier" -#define NID_documentIdentifier 468 -#define OBJ_documentIdentifier OBJ_pilotAttributeType,11L - -#define LN_documentTitle "documentTitle" -#define NID_documentTitle 469 -#define OBJ_documentTitle OBJ_pilotAttributeType,12L - -#define LN_documentVersion "documentVersion" -#define NID_documentVersion 470 -#define OBJ_documentVersion OBJ_pilotAttributeType,13L - -#define LN_documentAuthor "documentAuthor" -#define NID_documentAuthor 471 -#define OBJ_documentAuthor OBJ_pilotAttributeType,14L - -#define LN_documentLocation "documentLocation" -#define NID_documentLocation 472 -#define OBJ_documentLocation OBJ_pilotAttributeType,15L - -#define LN_homeTelephoneNumber "homeTelephoneNumber" -#define NID_homeTelephoneNumber 473 -#define OBJ_homeTelephoneNumber OBJ_pilotAttributeType,20L - -#define SN_secretary "secretary" -#define NID_secretary 474 -#define OBJ_secretary OBJ_pilotAttributeType,21L - -#define LN_otherMailbox "otherMailbox" -#define NID_otherMailbox 475 -#define OBJ_otherMailbox OBJ_pilotAttributeType,22L - -#define LN_lastModifiedTime "lastModifiedTime" -#define NID_lastModifiedTime 476 -#define OBJ_lastModifiedTime OBJ_pilotAttributeType,23L - -#define LN_lastModifiedBy "lastModifiedBy" -#define NID_lastModifiedBy 477 -#define OBJ_lastModifiedBy OBJ_pilotAttributeType,24L - -#define SN_domainComponent "DC" -#define LN_domainComponent "domainComponent" -#define NID_domainComponent 391 -#define OBJ_domainComponent OBJ_pilotAttributeType,25L - -#define LN_aRecord "aRecord" -#define NID_aRecord 478 -#define OBJ_aRecord OBJ_pilotAttributeType,26L - -#define LN_pilotAttributeType27 "pilotAttributeType27" -#define NID_pilotAttributeType27 479 -#define OBJ_pilotAttributeType27 OBJ_pilotAttributeType,27L - -#define LN_mXRecord "mXRecord" -#define NID_mXRecord 480 -#define OBJ_mXRecord OBJ_pilotAttributeType,28L - -#define LN_nSRecord "nSRecord" -#define NID_nSRecord 481 -#define OBJ_nSRecord OBJ_pilotAttributeType,29L - -#define LN_sOARecord "sOARecord" -#define NID_sOARecord 482 -#define OBJ_sOARecord OBJ_pilotAttributeType,30L - -#define LN_cNAMERecord "cNAMERecord" -#define NID_cNAMERecord 483 -#define OBJ_cNAMERecord OBJ_pilotAttributeType,31L - -#define LN_associatedDomain "associatedDomain" -#define NID_associatedDomain 484 -#define OBJ_associatedDomain OBJ_pilotAttributeType,37L - -#define LN_associatedName "associatedName" -#define NID_associatedName 485 -#define OBJ_associatedName OBJ_pilotAttributeType,38L - -#define LN_homePostalAddress "homePostalAddress" -#define NID_homePostalAddress 486 -#define OBJ_homePostalAddress OBJ_pilotAttributeType,39L - -#define LN_personalTitle "personalTitle" -#define NID_personalTitle 487 -#define OBJ_personalTitle OBJ_pilotAttributeType,40L - -#define LN_mobileTelephoneNumber "mobileTelephoneNumber" -#define NID_mobileTelephoneNumber 488 -#define OBJ_mobileTelephoneNumber OBJ_pilotAttributeType,41L - -#define LN_pagerTelephoneNumber "pagerTelephoneNumber" -#define NID_pagerTelephoneNumber 489 -#define OBJ_pagerTelephoneNumber OBJ_pilotAttributeType,42L - -#define LN_friendlyCountryName "friendlyCountryName" -#define NID_friendlyCountryName 490 -#define OBJ_friendlyCountryName OBJ_pilotAttributeType,43L - -#define LN_organizationalStatus "organizationalStatus" -#define NID_organizationalStatus 491 -#define OBJ_organizationalStatus OBJ_pilotAttributeType,45L - -#define LN_janetMailbox "janetMailbox" -#define NID_janetMailbox 492 -#define OBJ_janetMailbox OBJ_pilotAttributeType,46L - -#define LN_mailPreferenceOption "mailPreferenceOption" -#define NID_mailPreferenceOption 493 -#define OBJ_mailPreferenceOption OBJ_pilotAttributeType,47L - -#define LN_buildingName "buildingName" -#define NID_buildingName 494 -#define OBJ_buildingName OBJ_pilotAttributeType,48L - -#define LN_dSAQuality "dSAQuality" -#define NID_dSAQuality 495 -#define OBJ_dSAQuality OBJ_pilotAttributeType,49L - -#define LN_singleLevelQuality "singleLevelQuality" -#define NID_singleLevelQuality 496 -#define OBJ_singleLevelQuality OBJ_pilotAttributeType,50L - -#define LN_subtreeMinimumQuality "subtreeMinimumQuality" -#define NID_subtreeMinimumQuality 497 -#define OBJ_subtreeMinimumQuality OBJ_pilotAttributeType,51L - -#define LN_subtreeMaximumQuality "subtreeMaximumQuality" -#define NID_subtreeMaximumQuality 498 -#define OBJ_subtreeMaximumQuality OBJ_pilotAttributeType,52L - -#define LN_personalSignature "personalSignature" -#define NID_personalSignature 499 -#define OBJ_personalSignature OBJ_pilotAttributeType,53L - -#define LN_dITRedirect "dITRedirect" -#define NID_dITRedirect 500 -#define OBJ_dITRedirect OBJ_pilotAttributeType,54L - -#define SN_audio "audio" -#define NID_audio 501 -#define OBJ_audio OBJ_pilotAttributeType,55L - -#define LN_documentPublisher "documentPublisher" -#define NID_documentPublisher 502 -#define OBJ_documentPublisher OBJ_pilotAttributeType,56L - -#define SN_id_set "id-set" -#define LN_id_set "Secure Electronic Transactions" -#define NID_id_set 512 -#define OBJ_id_set OBJ_international_organizations,42L - -#define SN_set_ctype "set-ctype" -#define LN_set_ctype "content types" -#define NID_set_ctype 513 -#define OBJ_set_ctype OBJ_id_set,0L - -#define SN_set_msgExt "set-msgExt" -#define LN_set_msgExt "message extensions" -#define NID_set_msgExt 514 -#define OBJ_set_msgExt OBJ_id_set,1L - -#define SN_set_attr "set-attr" -#define NID_set_attr 515 -#define OBJ_set_attr OBJ_id_set,3L - -#define SN_set_policy "set-policy" -#define NID_set_policy 516 -#define OBJ_set_policy OBJ_id_set,5L - -#define SN_set_certExt "set-certExt" -#define LN_set_certExt "certificate extensions" -#define NID_set_certExt 517 -#define OBJ_set_certExt OBJ_id_set,7L - -#define SN_set_brand "set-brand" -#define NID_set_brand 518 -#define OBJ_set_brand OBJ_id_set,8L - -#define SN_setct_PANData "setct-PANData" -#define NID_setct_PANData 519 -#define OBJ_setct_PANData OBJ_set_ctype,0L - -#define SN_setct_PANToken "setct-PANToken" -#define NID_setct_PANToken 520 -#define OBJ_setct_PANToken OBJ_set_ctype,1L - -#define SN_setct_PANOnly "setct-PANOnly" -#define NID_setct_PANOnly 521 -#define OBJ_setct_PANOnly OBJ_set_ctype,2L - -#define SN_setct_OIData "setct-OIData" -#define NID_setct_OIData 522 -#define OBJ_setct_OIData OBJ_set_ctype,3L - -#define SN_setct_PI "setct-PI" -#define NID_setct_PI 523 -#define OBJ_setct_PI OBJ_set_ctype,4L - -#define SN_setct_PIData "setct-PIData" -#define NID_setct_PIData 524 -#define OBJ_setct_PIData OBJ_set_ctype,5L - -#define SN_setct_PIDataUnsigned "setct-PIDataUnsigned" -#define NID_setct_PIDataUnsigned 525 -#define OBJ_setct_PIDataUnsigned OBJ_set_ctype,6L - -#define SN_setct_HODInput "setct-HODInput" -#define NID_setct_HODInput 526 -#define OBJ_setct_HODInput OBJ_set_ctype,7L - -#define SN_setct_AuthResBaggage "setct-AuthResBaggage" -#define NID_setct_AuthResBaggage 527 -#define OBJ_setct_AuthResBaggage OBJ_set_ctype,8L - -#define SN_setct_AuthRevReqBaggage "setct-AuthRevReqBaggage" -#define NID_setct_AuthRevReqBaggage 528 -#define OBJ_setct_AuthRevReqBaggage OBJ_set_ctype,9L - -#define SN_setct_AuthRevResBaggage "setct-AuthRevResBaggage" -#define NID_setct_AuthRevResBaggage 529 -#define OBJ_setct_AuthRevResBaggage OBJ_set_ctype,10L - -#define SN_setct_CapTokenSeq "setct-CapTokenSeq" -#define NID_setct_CapTokenSeq 530 -#define OBJ_setct_CapTokenSeq OBJ_set_ctype,11L - -#define SN_setct_PInitResData "setct-PInitResData" -#define NID_setct_PInitResData 531 -#define OBJ_setct_PInitResData OBJ_set_ctype,12L - -#define SN_setct_PI_TBS "setct-PI-TBS" -#define NID_setct_PI_TBS 532 -#define OBJ_setct_PI_TBS OBJ_set_ctype,13L - -#define SN_setct_PResData "setct-PResData" -#define NID_setct_PResData 533 -#define OBJ_setct_PResData OBJ_set_ctype,14L - -#define SN_setct_AuthReqTBS "setct-AuthReqTBS" -#define NID_setct_AuthReqTBS 534 -#define OBJ_setct_AuthReqTBS OBJ_set_ctype,16L - -#define SN_setct_AuthResTBS "setct-AuthResTBS" -#define NID_setct_AuthResTBS 535 -#define OBJ_setct_AuthResTBS OBJ_set_ctype,17L - -#define SN_setct_AuthResTBSX "setct-AuthResTBSX" -#define NID_setct_AuthResTBSX 536 -#define OBJ_setct_AuthResTBSX OBJ_set_ctype,18L - -#define SN_setct_AuthTokenTBS "setct-AuthTokenTBS" -#define NID_setct_AuthTokenTBS 537 -#define OBJ_setct_AuthTokenTBS OBJ_set_ctype,19L - -#define SN_setct_CapTokenData "setct-CapTokenData" -#define NID_setct_CapTokenData 538 -#define OBJ_setct_CapTokenData OBJ_set_ctype,20L - -#define SN_setct_CapTokenTBS "setct-CapTokenTBS" -#define NID_setct_CapTokenTBS 539 -#define OBJ_setct_CapTokenTBS OBJ_set_ctype,21L - -#define SN_setct_AcqCardCodeMsg "setct-AcqCardCodeMsg" -#define NID_setct_AcqCardCodeMsg 540 -#define OBJ_setct_AcqCardCodeMsg OBJ_set_ctype,22L - -#define SN_setct_AuthRevReqTBS "setct-AuthRevReqTBS" -#define NID_setct_AuthRevReqTBS 541 -#define OBJ_setct_AuthRevReqTBS OBJ_set_ctype,23L - -#define SN_setct_AuthRevResData "setct-AuthRevResData" -#define NID_setct_AuthRevResData 542 -#define OBJ_setct_AuthRevResData OBJ_set_ctype,24L - -#define SN_setct_AuthRevResTBS "setct-AuthRevResTBS" -#define NID_setct_AuthRevResTBS 543 -#define OBJ_setct_AuthRevResTBS OBJ_set_ctype,25L - -#define SN_setct_CapReqTBS "setct-CapReqTBS" -#define NID_setct_CapReqTBS 544 -#define OBJ_setct_CapReqTBS OBJ_set_ctype,26L - -#define SN_setct_CapReqTBSX "setct-CapReqTBSX" -#define NID_setct_CapReqTBSX 545 -#define OBJ_setct_CapReqTBSX OBJ_set_ctype,27L - -#define SN_setct_CapResData "setct-CapResData" -#define NID_setct_CapResData 546 -#define OBJ_setct_CapResData OBJ_set_ctype,28L - -#define SN_setct_CapRevReqTBS "setct-CapRevReqTBS" -#define NID_setct_CapRevReqTBS 547 -#define OBJ_setct_CapRevReqTBS OBJ_set_ctype,29L - -#define SN_setct_CapRevReqTBSX "setct-CapRevReqTBSX" -#define NID_setct_CapRevReqTBSX 548 -#define OBJ_setct_CapRevReqTBSX OBJ_set_ctype,30L - -#define SN_setct_CapRevResData "setct-CapRevResData" -#define NID_setct_CapRevResData 549 -#define OBJ_setct_CapRevResData OBJ_set_ctype,31L - -#define SN_setct_CredReqTBS "setct-CredReqTBS" -#define NID_setct_CredReqTBS 550 -#define OBJ_setct_CredReqTBS OBJ_set_ctype,32L - -#define SN_setct_CredReqTBSX "setct-CredReqTBSX" -#define NID_setct_CredReqTBSX 551 -#define OBJ_setct_CredReqTBSX OBJ_set_ctype,33L - -#define SN_setct_CredResData "setct-CredResData" -#define NID_setct_CredResData 552 -#define OBJ_setct_CredResData OBJ_set_ctype,34L - -#define SN_setct_CredRevReqTBS "setct-CredRevReqTBS" -#define NID_setct_CredRevReqTBS 553 -#define OBJ_setct_CredRevReqTBS OBJ_set_ctype,35L - -#define SN_setct_CredRevReqTBSX "setct-CredRevReqTBSX" -#define NID_setct_CredRevReqTBSX 554 -#define OBJ_setct_CredRevReqTBSX OBJ_set_ctype,36L - -#define SN_setct_CredRevResData "setct-CredRevResData" -#define NID_setct_CredRevResData 555 -#define OBJ_setct_CredRevResData OBJ_set_ctype,37L - -#define SN_setct_PCertReqData "setct-PCertReqData" -#define NID_setct_PCertReqData 556 -#define OBJ_setct_PCertReqData OBJ_set_ctype,38L - -#define SN_setct_PCertResTBS "setct-PCertResTBS" -#define NID_setct_PCertResTBS 557 -#define OBJ_setct_PCertResTBS OBJ_set_ctype,39L - -#define SN_setct_BatchAdminReqData "setct-BatchAdminReqData" -#define NID_setct_BatchAdminReqData 558 -#define OBJ_setct_BatchAdminReqData OBJ_set_ctype,40L - -#define SN_setct_BatchAdminResData "setct-BatchAdminResData" -#define NID_setct_BatchAdminResData 559 -#define OBJ_setct_BatchAdminResData OBJ_set_ctype,41L - -#define SN_setct_CardCInitResTBS "setct-CardCInitResTBS" -#define NID_setct_CardCInitResTBS 560 -#define OBJ_setct_CardCInitResTBS OBJ_set_ctype,42L - -#define SN_setct_MeAqCInitResTBS "setct-MeAqCInitResTBS" -#define NID_setct_MeAqCInitResTBS 561 -#define OBJ_setct_MeAqCInitResTBS OBJ_set_ctype,43L - -#define SN_setct_RegFormResTBS "setct-RegFormResTBS" -#define NID_setct_RegFormResTBS 562 -#define OBJ_setct_RegFormResTBS OBJ_set_ctype,44L - -#define SN_setct_CertReqData "setct-CertReqData" -#define NID_setct_CertReqData 563 -#define OBJ_setct_CertReqData OBJ_set_ctype,45L - -#define SN_setct_CertReqTBS "setct-CertReqTBS" -#define NID_setct_CertReqTBS 564 -#define OBJ_setct_CertReqTBS OBJ_set_ctype,46L - -#define SN_setct_CertResData "setct-CertResData" -#define NID_setct_CertResData 565 -#define OBJ_setct_CertResData OBJ_set_ctype,47L - -#define SN_setct_CertInqReqTBS "setct-CertInqReqTBS" -#define NID_setct_CertInqReqTBS 566 -#define OBJ_setct_CertInqReqTBS OBJ_set_ctype,48L - -#define SN_setct_ErrorTBS "setct-ErrorTBS" -#define NID_setct_ErrorTBS 567 -#define OBJ_setct_ErrorTBS OBJ_set_ctype,49L - -#define SN_setct_PIDualSignedTBE "setct-PIDualSignedTBE" -#define NID_setct_PIDualSignedTBE 568 -#define OBJ_setct_PIDualSignedTBE OBJ_set_ctype,50L - -#define SN_setct_PIUnsignedTBE "setct-PIUnsignedTBE" -#define NID_setct_PIUnsignedTBE 569 -#define OBJ_setct_PIUnsignedTBE OBJ_set_ctype,51L - -#define SN_setct_AuthReqTBE "setct-AuthReqTBE" -#define NID_setct_AuthReqTBE 570 -#define OBJ_setct_AuthReqTBE OBJ_set_ctype,52L - -#define SN_setct_AuthResTBE "setct-AuthResTBE" -#define NID_setct_AuthResTBE 571 -#define OBJ_setct_AuthResTBE OBJ_set_ctype,53L - -#define SN_setct_AuthResTBEX "setct-AuthResTBEX" -#define NID_setct_AuthResTBEX 572 -#define OBJ_setct_AuthResTBEX OBJ_set_ctype,54L - -#define SN_setct_AuthTokenTBE "setct-AuthTokenTBE" -#define NID_setct_AuthTokenTBE 573 -#define OBJ_setct_AuthTokenTBE OBJ_set_ctype,55L - -#define SN_setct_CapTokenTBE "setct-CapTokenTBE" -#define NID_setct_CapTokenTBE 574 -#define OBJ_setct_CapTokenTBE OBJ_set_ctype,56L - -#define SN_setct_CapTokenTBEX "setct-CapTokenTBEX" -#define NID_setct_CapTokenTBEX 575 -#define OBJ_setct_CapTokenTBEX OBJ_set_ctype,57L - -#define SN_setct_AcqCardCodeMsgTBE "setct-AcqCardCodeMsgTBE" -#define NID_setct_AcqCardCodeMsgTBE 576 -#define OBJ_setct_AcqCardCodeMsgTBE OBJ_set_ctype,58L - -#define SN_setct_AuthRevReqTBE "setct-AuthRevReqTBE" -#define NID_setct_AuthRevReqTBE 577 -#define OBJ_setct_AuthRevReqTBE OBJ_set_ctype,59L - -#define SN_setct_AuthRevResTBE "setct-AuthRevResTBE" -#define NID_setct_AuthRevResTBE 578 -#define OBJ_setct_AuthRevResTBE OBJ_set_ctype,60L - -#define SN_setct_AuthRevResTBEB "setct-AuthRevResTBEB" -#define NID_setct_AuthRevResTBEB 579 -#define OBJ_setct_AuthRevResTBEB OBJ_set_ctype,61L - -#define SN_setct_CapReqTBE "setct-CapReqTBE" -#define NID_setct_CapReqTBE 580 -#define OBJ_setct_CapReqTBE OBJ_set_ctype,62L - -#define SN_setct_CapReqTBEX "setct-CapReqTBEX" -#define NID_setct_CapReqTBEX 581 -#define OBJ_setct_CapReqTBEX OBJ_set_ctype,63L - -#define SN_setct_CapResTBE "setct-CapResTBE" -#define NID_setct_CapResTBE 582 -#define OBJ_setct_CapResTBE OBJ_set_ctype,64L - -#define SN_setct_CapRevReqTBE "setct-CapRevReqTBE" -#define NID_setct_CapRevReqTBE 583 -#define OBJ_setct_CapRevReqTBE OBJ_set_ctype,65L - -#define SN_setct_CapRevReqTBEX "setct-CapRevReqTBEX" -#define NID_setct_CapRevReqTBEX 584 -#define OBJ_setct_CapRevReqTBEX OBJ_set_ctype,66L - -#define SN_setct_CapRevResTBE "setct-CapRevResTBE" -#define NID_setct_CapRevResTBE 585 -#define OBJ_setct_CapRevResTBE OBJ_set_ctype,67L - -#define SN_setct_CredReqTBE "setct-CredReqTBE" -#define NID_setct_CredReqTBE 586 -#define OBJ_setct_CredReqTBE OBJ_set_ctype,68L - -#define SN_setct_CredReqTBEX "setct-CredReqTBEX" -#define NID_setct_CredReqTBEX 587 -#define OBJ_setct_CredReqTBEX OBJ_set_ctype,69L - -#define SN_setct_CredResTBE "setct-CredResTBE" -#define NID_setct_CredResTBE 588 -#define OBJ_setct_CredResTBE OBJ_set_ctype,70L - -#define SN_setct_CredRevReqTBE "setct-CredRevReqTBE" -#define NID_setct_CredRevReqTBE 589 -#define OBJ_setct_CredRevReqTBE OBJ_set_ctype,71L - -#define SN_setct_CredRevReqTBEX "setct-CredRevReqTBEX" -#define NID_setct_CredRevReqTBEX 590 -#define OBJ_setct_CredRevReqTBEX OBJ_set_ctype,72L - -#define SN_setct_CredRevResTBE "setct-CredRevResTBE" -#define NID_setct_CredRevResTBE 591 -#define OBJ_setct_CredRevResTBE OBJ_set_ctype,73L - -#define SN_setct_BatchAdminReqTBE "setct-BatchAdminReqTBE" -#define NID_setct_BatchAdminReqTBE 592 -#define OBJ_setct_BatchAdminReqTBE OBJ_set_ctype,74L - -#define SN_setct_BatchAdminResTBE "setct-BatchAdminResTBE" -#define NID_setct_BatchAdminResTBE 593 -#define OBJ_setct_BatchAdminResTBE OBJ_set_ctype,75L - -#define SN_setct_RegFormReqTBE "setct-RegFormReqTBE" -#define NID_setct_RegFormReqTBE 594 -#define OBJ_setct_RegFormReqTBE OBJ_set_ctype,76L - -#define SN_setct_CertReqTBE "setct-CertReqTBE" -#define NID_setct_CertReqTBE 595 -#define OBJ_setct_CertReqTBE OBJ_set_ctype,77L - -#define SN_setct_CertReqTBEX "setct-CertReqTBEX" -#define NID_setct_CertReqTBEX 596 -#define OBJ_setct_CertReqTBEX OBJ_set_ctype,78L - -#define SN_setct_CertResTBE "setct-CertResTBE" -#define NID_setct_CertResTBE 597 -#define OBJ_setct_CertResTBE OBJ_set_ctype,79L - -#define SN_setct_CRLNotificationTBS "setct-CRLNotificationTBS" -#define NID_setct_CRLNotificationTBS 598 -#define OBJ_setct_CRLNotificationTBS OBJ_set_ctype,80L - -#define SN_setct_CRLNotificationResTBS "setct-CRLNotificationResTBS" -#define NID_setct_CRLNotificationResTBS 599 -#define OBJ_setct_CRLNotificationResTBS OBJ_set_ctype,81L - -#define SN_setct_BCIDistributionTBS "setct-BCIDistributionTBS" -#define NID_setct_BCIDistributionTBS 600 -#define OBJ_setct_BCIDistributionTBS OBJ_set_ctype,82L - -#define SN_setext_genCrypt "setext-genCrypt" -#define LN_setext_genCrypt "generic cryptogram" -#define NID_setext_genCrypt 601 -#define OBJ_setext_genCrypt OBJ_set_msgExt,1L - -#define SN_setext_miAuth "setext-miAuth" -#define LN_setext_miAuth "merchant initiated auth" -#define NID_setext_miAuth 602 -#define OBJ_setext_miAuth OBJ_set_msgExt,3L - -#define SN_setext_pinSecure "setext-pinSecure" -#define NID_setext_pinSecure 603 -#define OBJ_setext_pinSecure OBJ_set_msgExt,4L - -#define SN_setext_pinAny "setext-pinAny" -#define NID_setext_pinAny 604 -#define OBJ_setext_pinAny OBJ_set_msgExt,5L - -#define SN_setext_track2 "setext-track2" -#define NID_setext_track2 605 -#define OBJ_setext_track2 OBJ_set_msgExt,7L - -#define SN_setext_cv "setext-cv" -#define LN_setext_cv "additional verification" -#define NID_setext_cv 606 -#define OBJ_setext_cv OBJ_set_msgExt,8L - -#define SN_set_policy_root "set-policy-root" -#define NID_set_policy_root 607 -#define OBJ_set_policy_root OBJ_set_policy,0L - -#define SN_setCext_hashedRoot "setCext-hashedRoot" -#define NID_setCext_hashedRoot 608 -#define OBJ_setCext_hashedRoot OBJ_set_certExt,0L - -#define SN_setCext_certType "setCext-certType" -#define NID_setCext_certType 609 -#define OBJ_setCext_certType OBJ_set_certExt,1L - -#define SN_setCext_merchData "setCext-merchData" -#define NID_setCext_merchData 610 -#define OBJ_setCext_merchData OBJ_set_certExt,2L - -#define SN_setCext_cCertRequired "setCext-cCertRequired" -#define NID_setCext_cCertRequired 611 -#define OBJ_setCext_cCertRequired OBJ_set_certExt,3L - -#define SN_setCext_tunneling "setCext-tunneling" -#define NID_setCext_tunneling 612 -#define OBJ_setCext_tunneling OBJ_set_certExt,4L - -#define SN_setCext_setExt "setCext-setExt" -#define NID_setCext_setExt 613 -#define OBJ_setCext_setExt OBJ_set_certExt,5L - -#define SN_setCext_setQualf "setCext-setQualf" -#define NID_setCext_setQualf 614 -#define OBJ_setCext_setQualf OBJ_set_certExt,6L - -#define SN_setCext_PGWYcapabilities "setCext-PGWYcapabilities" -#define NID_setCext_PGWYcapabilities 615 -#define OBJ_setCext_PGWYcapabilities OBJ_set_certExt,7L - -#define SN_setCext_TokenIdentifier "setCext-TokenIdentifier" -#define NID_setCext_TokenIdentifier 616 -#define OBJ_setCext_TokenIdentifier OBJ_set_certExt,8L - -#define SN_setCext_Track2Data "setCext-Track2Data" -#define NID_setCext_Track2Data 617 -#define OBJ_setCext_Track2Data OBJ_set_certExt,9L - -#define SN_setCext_TokenType "setCext-TokenType" -#define NID_setCext_TokenType 618 -#define OBJ_setCext_TokenType OBJ_set_certExt,10L - -#define SN_setCext_IssuerCapabilities "setCext-IssuerCapabilities" -#define NID_setCext_IssuerCapabilities 619 -#define OBJ_setCext_IssuerCapabilities OBJ_set_certExt,11L - -#define SN_setAttr_Cert "setAttr-Cert" -#define NID_setAttr_Cert 620 -#define OBJ_setAttr_Cert OBJ_set_attr,0L - -#define SN_setAttr_PGWYcap "setAttr-PGWYcap" -#define LN_setAttr_PGWYcap "payment gateway capabilities" -#define NID_setAttr_PGWYcap 621 -#define OBJ_setAttr_PGWYcap OBJ_set_attr,1L - -#define SN_setAttr_TokenType "setAttr-TokenType" -#define NID_setAttr_TokenType 622 -#define OBJ_setAttr_TokenType OBJ_set_attr,2L - -#define SN_setAttr_IssCap "setAttr-IssCap" -#define LN_setAttr_IssCap "issuer capabilities" -#define NID_setAttr_IssCap 623 -#define OBJ_setAttr_IssCap OBJ_set_attr,3L - -#define SN_set_rootKeyThumb "set-rootKeyThumb" -#define NID_set_rootKeyThumb 624 -#define OBJ_set_rootKeyThumb OBJ_setAttr_Cert,0L - -#define SN_set_addPolicy "set-addPolicy" -#define NID_set_addPolicy 625 -#define OBJ_set_addPolicy OBJ_setAttr_Cert,1L - -#define SN_setAttr_Token_EMV "setAttr-Token-EMV" -#define NID_setAttr_Token_EMV 626 -#define OBJ_setAttr_Token_EMV OBJ_setAttr_TokenType,1L - -#define SN_setAttr_Token_B0Prime "setAttr-Token-B0Prime" -#define NID_setAttr_Token_B0Prime 627 -#define OBJ_setAttr_Token_B0Prime OBJ_setAttr_TokenType,2L - -#define SN_setAttr_IssCap_CVM "setAttr-IssCap-CVM" -#define NID_setAttr_IssCap_CVM 628 -#define OBJ_setAttr_IssCap_CVM OBJ_setAttr_IssCap,3L - -#define SN_setAttr_IssCap_T2 "setAttr-IssCap-T2" -#define NID_setAttr_IssCap_T2 629 -#define OBJ_setAttr_IssCap_T2 OBJ_setAttr_IssCap,4L - -#define SN_setAttr_IssCap_Sig "setAttr-IssCap-Sig" -#define NID_setAttr_IssCap_Sig 630 -#define OBJ_setAttr_IssCap_Sig OBJ_setAttr_IssCap,5L - -#define SN_setAttr_GenCryptgrm "setAttr-GenCryptgrm" -#define LN_setAttr_GenCryptgrm "generate cryptogram" -#define NID_setAttr_GenCryptgrm 631 -#define OBJ_setAttr_GenCryptgrm OBJ_setAttr_IssCap_CVM,1L - -#define SN_setAttr_T2Enc "setAttr-T2Enc" -#define LN_setAttr_T2Enc "encrypted track 2" -#define NID_setAttr_T2Enc 632 -#define OBJ_setAttr_T2Enc OBJ_setAttr_IssCap_T2,1L - -#define SN_setAttr_T2cleartxt "setAttr-T2cleartxt" -#define LN_setAttr_T2cleartxt "cleartext track 2" -#define NID_setAttr_T2cleartxt 633 -#define OBJ_setAttr_T2cleartxt OBJ_setAttr_IssCap_T2,2L - -#define SN_setAttr_TokICCsig "setAttr-TokICCsig" -#define LN_setAttr_TokICCsig "ICC or token signature" -#define NID_setAttr_TokICCsig 634 -#define OBJ_setAttr_TokICCsig OBJ_setAttr_IssCap_Sig,1L - -#define SN_setAttr_SecDevSig "setAttr-SecDevSig" -#define LN_setAttr_SecDevSig "secure device signature" -#define NID_setAttr_SecDevSig 635 -#define OBJ_setAttr_SecDevSig OBJ_setAttr_IssCap_Sig,2L - -#define SN_set_brand_IATA_ATA "set-brand-IATA-ATA" -#define NID_set_brand_IATA_ATA 636 -#define OBJ_set_brand_IATA_ATA OBJ_set_brand,1L - -#define SN_set_brand_Diners "set-brand-Diners" -#define NID_set_brand_Diners 637 -#define OBJ_set_brand_Diners OBJ_set_brand,30L - -#define SN_set_brand_AmericanExpress "set-brand-AmericanExpress" -#define NID_set_brand_AmericanExpress 638 -#define OBJ_set_brand_AmericanExpress OBJ_set_brand,34L - -#define SN_set_brand_JCB "set-brand-JCB" -#define NID_set_brand_JCB 639 -#define OBJ_set_brand_JCB OBJ_set_brand,35L - -#define SN_set_brand_Visa "set-brand-Visa" -#define NID_set_brand_Visa 640 -#define OBJ_set_brand_Visa OBJ_set_brand,4L - -#define SN_set_brand_MasterCard "set-brand-MasterCard" -#define NID_set_brand_MasterCard 641 -#define OBJ_set_brand_MasterCard OBJ_set_brand,5L - -#define SN_set_brand_Novus "set-brand-Novus" -#define NID_set_brand_Novus 642 -#define OBJ_set_brand_Novus OBJ_set_brand,6011L - -#define SN_des_cdmf "DES-CDMF" -#define LN_des_cdmf "des-cdmf" -#define NID_des_cdmf 643 -#define OBJ_des_cdmf OBJ_rsadsi,3L,10L - -#define SN_rsaOAEPEncryptionSET "rsaOAEPEncryptionSET" -#define NID_rsaOAEPEncryptionSET 644 -#define OBJ_rsaOAEPEncryptionSET OBJ_rsadsi,1L,1L,6L - -#define SN_ipsec3 "Oakley-EC2N-3" -#define LN_ipsec3 "ipsec3" -#define NID_ipsec3 749 - -#define SN_ipsec4 "Oakley-EC2N-4" -#define LN_ipsec4 "ipsec4" -#define NID_ipsec4 750 - -#define SN_whirlpool "whirlpool" -#define NID_whirlpool 804 -#define OBJ_whirlpool OBJ_iso,0L,10118L,3L,0L,55L - -#define SN_cryptopro "cryptopro" -#define NID_cryptopro 805 -#define OBJ_cryptopro OBJ_member_body,643L,2L,2L - -#define SN_cryptocom "cryptocom" -#define NID_cryptocom 806 -#define OBJ_cryptocom OBJ_member_body,643L,2L,9L - -#define SN_id_GostR3411_94_with_GostR3410_2001 "id-GostR3411-94-with-GostR3410-2001" -#define LN_id_GostR3411_94_with_GostR3410_2001 "GOST R 34.11-94 with GOST R 34.10-2001" -#define NID_id_GostR3411_94_with_GostR3410_2001 807 -#define OBJ_id_GostR3411_94_with_GostR3410_2001 OBJ_cryptopro,3L - -#define SN_id_GostR3411_94_with_GostR3410_94 "id-GostR3411-94-with-GostR3410-94" -#define LN_id_GostR3411_94_with_GostR3410_94 "GOST R 34.11-94 with GOST R 34.10-94" -#define NID_id_GostR3411_94_with_GostR3410_94 808 -#define OBJ_id_GostR3411_94_with_GostR3410_94 OBJ_cryptopro,4L - -#define SN_id_GostR3411_94 "md_gost94" -#define LN_id_GostR3411_94 "GOST R 34.11-94" -#define NID_id_GostR3411_94 809 -#define OBJ_id_GostR3411_94 OBJ_cryptopro,9L - -#define SN_id_HMACGostR3411_94 "id-HMACGostR3411-94" -#define LN_id_HMACGostR3411_94 "HMAC GOST 34.11-94" -#define NID_id_HMACGostR3411_94 810 -#define OBJ_id_HMACGostR3411_94 OBJ_cryptopro,10L - -#define SN_id_GostR3410_2001 "gost2001" -#define LN_id_GostR3410_2001 "GOST R 34.10-2001" -#define NID_id_GostR3410_2001 811 -#define OBJ_id_GostR3410_2001 OBJ_cryptopro,19L - -#define SN_id_GostR3410_94 "gost94" -#define LN_id_GostR3410_94 "GOST R 34.10-94" -#define NID_id_GostR3410_94 812 -#define OBJ_id_GostR3410_94 OBJ_cryptopro,20L - -#define SN_id_Gost28147_89 "gost89" -#define LN_id_Gost28147_89 "GOST 28147-89" -#define NID_id_Gost28147_89 813 -#define OBJ_id_Gost28147_89 OBJ_cryptopro,21L - -#define SN_gost89_cnt "gost89-cnt" -#define NID_gost89_cnt 814 - -#define SN_id_Gost28147_89_MAC "gost-mac" -#define LN_id_Gost28147_89_MAC "GOST 28147-89 MAC" -#define NID_id_Gost28147_89_MAC 815 -#define OBJ_id_Gost28147_89_MAC OBJ_cryptopro,22L - -#define SN_id_GostR3411_94_prf "prf-gostr3411-94" -#define LN_id_GostR3411_94_prf "GOST R 34.11-94 PRF" -#define NID_id_GostR3411_94_prf 816 -#define OBJ_id_GostR3411_94_prf OBJ_cryptopro,23L - -#define SN_id_GostR3410_2001DH "id-GostR3410-2001DH" -#define LN_id_GostR3410_2001DH "GOST R 34.10-2001 DH" -#define NID_id_GostR3410_2001DH 817 -#define OBJ_id_GostR3410_2001DH OBJ_cryptopro,98L - -#define SN_id_GostR3410_94DH "id-GostR3410-94DH" -#define LN_id_GostR3410_94DH "GOST R 34.10-94 DH" -#define NID_id_GostR3410_94DH 818 -#define OBJ_id_GostR3410_94DH OBJ_cryptopro,99L - -#define SN_id_Gost28147_89_CryptoPro_KeyMeshing "id-Gost28147-89-CryptoPro-KeyMeshing" -#define NID_id_Gost28147_89_CryptoPro_KeyMeshing 819 -#define OBJ_id_Gost28147_89_CryptoPro_KeyMeshing OBJ_cryptopro,14L,1L - -#define SN_id_Gost28147_89_None_KeyMeshing "id-Gost28147-89-None-KeyMeshing" -#define NID_id_Gost28147_89_None_KeyMeshing 820 -#define OBJ_id_Gost28147_89_None_KeyMeshing OBJ_cryptopro,14L,0L - -#define SN_id_GostR3411_94_TestParamSet "id-GostR3411-94-TestParamSet" -#define NID_id_GostR3411_94_TestParamSet 821 -#define OBJ_id_GostR3411_94_TestParamSet OBJ_cryptopro,30L,0L - -#define SN_id_GostR3411_94_CryptoProParamSet "id-GostR3411-94-CryptoProParamSet" -#define NID_id_GostR3411_94_CryptoProParamSet 822 -#define OBJ_id_GostR3411_94_CryptoProParamSet OBJ_cryptopro,30L,1L - -#define SN_id_Gost28147_89_TestParamSet "id-Gost28147-89-TestParamSet" -#define NID_id_Gost28147_89_TestParamSet 823 -#define OBJ_id_Gost28147_89_TestParamSet OBJ_cryptopro,31L,0L - -#define SN_id_Gost28147_89_CryptoPro_A_ParamSet "id-Gost28147-89-CryptoPro-A-ParamSet" -#define NID_id_Gost28147_89_CryptoPro_A_ParamSet 824 -#define OBJ_id_Gost28147_89_CryptoPro_A_ParamSet OBJ_cryptopro,31L,1L - -#define SN_id_Gost28147_89_CryptoPro_B_ParamSet "id-Gost28147-89-CryptoPro-B-ParamSet" -#define NID_id_Gost28147_89_CryptoPro_B_ParamSet 825 -#define OBJ_id_Gost28147_89_CryptoPro_B_ParamSet OBJ_cryptopro,31L,2L - -#define SN_id_Gost28147_89_CryptoPro_C_ParamSet "id-Gost28147-89-CryptoPro-C-ParamSet" -#define NID_id_Gost28147_89_CryptoPro_C_ParamSet 826 -#define OBJ_id_Gost28147_89_CryptoPro_C_ParamSet OBJ_cryptopro,31L,3L - -#define SN_id_Gost28147_89_CryptoPro_D_ParamSet "id-Gost28147-89-CryptoPro-D-ParamSet" -#define NID_id_Gost28147_89_CryptoPro_D_ParamSet 827 -#define OBJ_id_Gost28147_89_CryptoPro_D_ParamSet OBJ_cryptopro,31L,4L - -#define SN_id_Gost28147_89_CryptoPro_Oscar_1_1_ParamSet "id-Gost28147-89-CryptoPro-Oscar-1-1-ParamSet" -#define NID_id_Gost28147_89_CryptoPro_Oscar_1_1_ParamSet 828 -#define OBJ_id_Gost28147_89_CryptoPro_Oscar_1_1_ParamSet OBJ_cryptopro,31L,5L - -#define SN_id_Gost28147_89_CryptoPro_Oscar_1_0_ParamSet "id-Gost28147-89-CryptoPro-Oscar-1-0-ParamSet" -#define NID_id_Gost28147_89_CryptoPro_Oscar_1_0_ParamSet 829 -#define OBJ_id_Gost28147_89_CryptoPro_Oscar_1_0_ParamSet OBJ_cryptopro,31L,6L - -#define SN_id_Gost28147_89_CryptoPro_RIC_1_ParamSet "id-Gost28147-89-CryptoPro-RIC-1-ParamSet" -#define NID_id_Gost28147_89_CryptoPro_RIC_1_ParamSet 830 -#define OBJ_id_Gost28147_89_CryptoPro_RIC_1_ParamSet OBJ_cryptopro,31L,7L - -#define SN_id_GostR3410_94_TestParamSet "id-GostR3410-94-TestParamSet" -#define NID_id_GostR3410_94_TestParamSet 831 -#define OBJ_id_GostR3410_94_TestParamSet OBJ_cryptopro,32L,0L - -#define SN_id_GostR3410_94_CryptoPro_A_ParamSet "id-GostR3410-94-CryptoPro-A-ParamSet" -#define NID_id_GostR3410_94_CryptoPro_A_ParamSet 832 -#define OBJ_id_GostR3410_94_CryptoPro_A_ParamSet OBJ_cryptopro,32L,2L - -#define SN_id_GostR3410_94_CryptoPro_B_ParamSet "id-GostR3410-94-CryptoPro-B-ParamSet" -#define NID_id_GostR3410_94_CryptoPro_B_ParamSet 833 -#define OBJ_id_GostR3410_94_CryptoPro_B_ParamSet OBJ_cryptopro,32L,3L - -#define SN_id_GostR3410_94_CryptoPro_C_ParamSet "id-GostR3410-94-CryptoPro-C-ParamSet" -#define NID_id_GostR3410_94_CryptoPro_C_ParamSet 834 -#define OBJ_id_GostR3410_94_CryptoPro_C_ParamSet OBJ_cryptopro,32L,4L - -#define SN_id_GostR3410_94_CryptoPro_D_ParamSet "id-GostR3410-94-CryptoPro-D-ParamSet" -#define NID_id_GostR3410_94_CryptoPro_D_ParamSet 835 -#define OBJ_id_GostR3410_94_CryptoPro_D_ParamSet OBJ_cryptopro,32L,5L - -#define SN_id_GostR3410_94_CryptoPro_XchA_ParamSet "id-GostR3410-94-CryptoPro-XchA-ParamSet" -#define NID_id_GostR3410_94_CryptoPro_XchA_ParamSet 836 -#define OBJ_id_GostR3410_94_CryptoPro_XchA_ParamSet OBJ_cryptopro,33L,1L - -#define SN_id_GostR3410_94_CryptoPro_XchB_ParamSet "id-GostR3410-94-CryptoPro-XchB-ParamSet" -#define NID_id_GostR3410_94_CryptoPro_XchB_ParamSet 837 -#define OBJ_id_GostR3410_94_CryptoPro_XchB_ParamSet OBJ_cryptopro,33L,2L - -#define SN_id_GostR3410_94_CryptoPro_XchC_ParamSet "id-GostR3410-94-CryptoPro-XchC-ParamSet" -#define NID_id_GostR3410_94_CryptoPro_XchC_ParamSet 838 -#define OBJ_id_GostR3410_94_CryptoPro_XchC_ParamSet OBJ_cryptopro,33L,3L - -#define SN_id_GostR3410_2001_TestParamSet "id-GostR3410-2001-TestParamSet" -#define NID_id_GostR3410_2001_TestParamSet 839 -#define OBJ_id_GostR3410_2001_TestParamSet OBJ_cryptopro,35L,0L - -#define SN_id_GostR3410_2001_CryptoPro_A_ParamSet "id-GostR3410-2001-CryptoPro-A-ParamSet" -#define NID_id_GostR3410_2001_CryptoPro_A_ParamSet 840 -#define OBJ_id_GostR3410_2001_CryptoPro_A_ParamSet OBJ_cryptopro,35L,1L - -#define SN_id_GostR3410_2001_CryptoPro_B_ParamSet "id-GostR3410-2001-CryptoPro-B-ParamSet" -#define NID_id_GostR3410_2001_CryptoPro_B_ParamSet 841 -#define OBJ_id_GostR3410_2001_CryptoPro_B_ParamSet OBJ_cryptopro,35L,2L - -#define SN_id_GostR3410_2001_CryptoPro_C_ParamSet "id-GostR3410-2001-CryptoPro-C-ParamSet" -#define NID_id_GostR3410_2001_CryptoPro_C_ParamSet 842 -#define OBJ_id_GostR3410_2001_CryptoPro_C_ParamSet OBJ_cryptopro,35L,3L - -#define SN_id_GostR3410_2001_CryptoPro_XchA_ParamSet "id-GostR3410-2001-CryptoPro-XchA-ParamSet" -#define NID_id_GostR3410_2001_CryptoPro_XchA_ParamSet 843 -#define OBJ_id_GostR3410_2001_CryptoPro_XchA_ParamSet OBJ_cryptopro,36L,0L - -#define SN_id_GostR3410_2001_CryptoPro_XchB_ParamSet "id-GostR3410-2001-CryptoPro-XchB-ParamSet" -#define NID_id_GostR3410_2001_CryptoPro_XchB_ParamSet 844 -#define OBJ_id_GostR3410_2001_CryptoPro_XchB_ParamSet OBJ_cryptopro,36L,1L - -#define SN_id_GostR3410_94_a "id-GostR3410-94-a" -#define NID_id_GostR3410_94_a 845 -#define OBJ_id_GostR3410_94_a OBJ_id_GostR3410_94,1L - -#define SN_id_GostR3410_94_aBis "id-GostR3410-94-aBis" -#define NID_id_GostR3410_94_aBis 846 -#define OBJ_id_GostR3410_94_aBis OBJ_id_GostR3410_94,2L - -#define SN_id_GostR3410_94_b "id-GostR3410-94-b" -#define NID_id_GostR3410_94_b 847 -#define OBJ_id_GostR3410_94_b OBJ_id_GostR3410_94,3L - -#define SN_id_GostR3410_94_bBis "id-GostR3410-94-bBis" -#define NID_id_GostR3410_94_bBis 848 -#define OBJ_id_GostR3410_94_bBis OBJ_id_GostR3410_94,4L - -#define SN_id_Gost28147_89_cc "id-Gost28147-89-cc" -#define LN_id_Gost28147_89_cc "GOST 28147-89 Cryptocom ParamSet" -#define NID_id_Gost28147_89_cc 849 -#define OBJ_id_Gost28147_89_cc OBJ_cryptocom,1L,6L,1L - -#define SN_id_GostR3410_94_cc "gost94cc" -#define LN_id_GostR3410_94_cc "GOST 34.10-94 Cryptocom" -#define NID_id_GostR3410_94_cc 850 -#define OBJ_id_GostR3410_94_cc OBJ_cryptocom,1L,5L,3L - -#define SN_id_GostR3410_2001_cc "gost2001cc" -#define LN_id_GostR3410_2001_cc "GOST 34.10-2001 Cryptocom" -#define NID_id_GostR3410_2001_cc 851 -#define OBJ_id_GostR3410_2001_cc OBJ_cryptocom,1L,5L,4L - -#define SN_id_GostR3411_94_with_GostR3410_94_cc "id-GostR3411-94-with-GostR3410-94-cc" -#define LN_id_GostR3411_94_with_GostR3410_94_cc "GOST R 34.11-94 with GOST R 34.10-94 Cryptocom" -#define NID_id_GostR3411_94_with_GostR3410_94_cc 852 -#define OBJ_id_GostR3411_94_with_GostR3410_94_cc OBJ_cryptocom,1L,3L,3L - -#define SN_id_GostR3411_94_with_GostR3410_2001_cc "id-GostR3411-94-with-GostR3410-2001-cc" -#define LN_id_GostR3411_94_with_GostR3410_2001_cc "GOST R 34.11-94 with GOST R 34.10-2001 Cryptocom" -#define NID_id_GostR3411_94_with_GostR3410_2001_cc 853 -#define OBJ_id_GostR3411_94_with_GostR3410_2001_cc OBJ_cryptocom,1L,3L,4L - -#define SN_id_GostR3410_2001_ParamSet_cc "id-GostR3410-2001-ParamSet-cc" -#define LN_id_GostR3410_2001_ParamSet_cc "GOST R 3410-2001 Parameter Set Cryptocom" -#define NID_id_GostR3410_2001_ParamSet_cc 854 -#define OBJ_id_GostR3410_2001_ParamSet_cc OBJ_cryptocom,1L,8L,1L - -#define SN_camellia_128_cbc "CAMELLIA-128-CBC" -#define LN_camellia_128_cbc "camellia-128-cbc" -#define NID_camellia_128_cbc 751 -#define OBJ_camellia_128_cbc 1L,2L,392L,200011L,61L,1L,1L,1L,2L - -#define SN_camellia_192_cbc "CAMELLIA-192-CBC" -#define LN_camellia_192_cbc "camellia-192-cbc" -#define NID_camellia_192_cbc 752 -#define OBJ_camellia_192_cbc 1L,2L,392L,200011L,61L,1L,1L,1L,3L - -#define SN_camellia_256_cbc "CAMELLIA-256-CBC" -#define LN_camellia_256_cbc "camellia-256-cbc" -#define NID_camellia_256_cbc 753 -#define OBJ_camellia_256_cbc 1L,2L,392L,200011L,61L,1L,1L,1L,4L - -#define SN_id_camellia128_wrap "id-camellia128-wrap" -#define NID_id_camellia128_wrap 907 -#define OBJ_id_camellia128_wrap 1L,2L,392L,200011L,61L,1L,1L,3L,2L - -#define SN_id_camellia192_wrap "id-camellia192-wrap" -#define NID_id_camellia192_wrap 908 -#define OBJ_id_camellia192_wrap 1L,2L,392L,200011L,61L,1L,1L,3L,3L - -#define SN_id_camellia256_wrap "id-camellia256-wrap" -#define NID_id_camellia256_wrap 909 -#define OBJ_id_camellia256_wrap 1L,2L,392L,200011L,61L,1L,1L,3L,4L - -#define OBJ_ntt_ds 0L,3L,4401L,5L - -#define OBJ_camellia OBJ_ntt_ds,3L,1L,9L - -#define SN_camellia_128_ecb "CAMELLIA-128-ECB" -#define LN_camellia_128_ecb "camellia-128-ecb" -#define NID_camellia_128_ecb 754 -#define OBJ_camellia_128_ecb OBJ_camellia,1L - -#define SN_camellia_128_ofb128 "CAMELLIA-128-OFB" -#define LN_camellia_128_ofb128 "camellia-128-ofb" -#define NID_camellia_128_ofb128 766 -#define OBJ_camellia_128_ofb128 OBJ_camellia,3L - -#define SN_camellia_128_cfb128 "CAMELLIA-128-CFB" -#define LN_camellia_128_cfb128 "camellia-128-cfb" -#define NID_camellia_128_cfb128 757 -#define OBJ_camellia_128_cfb128 OBJ_camellia,4L - -#define SN_camellia_192_ecb "CAMELLIA-192-ECB" -#define LN_camellia_192_ecb "camellia-192-ecb" -#define NID_camellia_192_ecb 755 -#define OBJ_camellia_192_ecb OBJ_camellia,21L - -#define SN_camellia_192_ofb128 "CAMELLIA-192-OFB" -#define LN_camellia_192_ofb128 "camellia-192-ofb" -#define NID_camellia_192_ofb128 767 -#define OBJ_camellia_192_ofb128 OBJ_camellia,23L - -#define SN_camellia_192_cfb128 "CAMELLIA-192-CFB" -#define LN_camellia_192_cfb128 "camellia-192-cfb" -#define NID_camellia_192_cfb128 758 -#define OBJ_camellia_192_cfb128 OBJ_camellia,24L - -#define SN_camellia_256_ecb "CAMELLIA-256-ECB" -#define LN_camellia_256_ecb "camellia-256-ecb" -#define NID_camellia_256_ecb 756 -#define OBJ_camellia_256_ecb OBJ_camellia,41L - -#define SN_camellia_256_ofb128 "CAMELLIA-256-OFB" -#define LN_camellia_256_ofb128 "camellia-256-ofb" -#define NID_camellia_256_ofb128 768 -#define OBJ_camellia_256_ofb128 OBJ_camellia,43L - -#define SN_camellia_256_cfb128 "CAMELLIA-256-CFB" -#define LN_camellia_256_cfb128 "camellia-256-cfb" -#define NID_camellia_256_cfb128 759 -#define OBJ_camellia_256_cfb128 OBJ_camellia,44L - -#define SN_camellia_128_cfb1 "CAMELLIA-128-CFB1" -#define LN_camellia_128_cfb1 "camellia-128-cfb1" -#define NID_camellia_128_cfb1 760 - -#define SN_camellia_192_cfb1 "CAMELLIA-192-CFB1" -#define LN_camellia_192_cfb1 "camellia-192-cfb1" -#define NID_camellia_192_cfb1 761 - -#define SN_camellia_256_cfb1 "CAMELLIA-256-CFB1" -#define LN_camellia_256_cfb1 "camellia-256-cfb1" -#define NID_camellia_256_cfb1 762 - -#define SN_camellia_128_cfb8 "CAMELLIA-128-CFB8" -#define LN_camellia_128_cfb8 "camellia-128-cfb8" -#define NID_camellia_128_cfb8 763 - -#define SN_camellia_192_cfb8 "CAMELLIA-192-CFB8" -#define LN_camellia_192_cfb8 "camellia-192-cfb8" -#define NID_camellia_192_cfb8 764 - -#define SN_camellia_256_cfb8 "CAMELLIA-256-CFB8" -#define LN_camellia_256_cfb8 "camellia-256-cfb8" -#define NID_camellia_256_cfb8 765 - -#define SN_kisa "KISA" -#define LN_kisa "kisa" -#define NID_kisa 773 -#define OBJ_kisa OBJ_member_body,410L,200004L - -#define SN_seed_ecb "SEED-ECB" -#define LN_seed_ecb "seed-ecb" -#define NID_seed_ecb 776 -#define OBJ_seed_ecb OBJ_kisa,1L,3L - -#define SN_seed_cbc "SEED-CBC" -#define LN_seed_cbc "seed-cbc" -#define NID_seed_cbc 777 -#define OBJ_seed_cbc OBJ_kisa,1L,4L - -#define SN_seed_cfb128 "SEED-CFB" -#define LN_seed_cfb128 "seed-cfb" -#define NID_seed_cfb128 779 -#define OBJ_seed_cfb128 OBJ_kisa,1L,5L - -#define SN_seed_ofb128 "SEED-OFB" -#define LN_seed_ofb128 "seed-ofb" -#define NID_seed_ofb128 778 -#define OBJ_seed_ofb128 OBJ_kisa,1L,6L - -#define SN_hmac "HMAC" -#define LN_hmac "hmac" -#define NID_hmac 855 - -#define SN_cmac "CMAC" -#define LN_cmac "cmac" -#define NID_cmac 894 - -#define SN_rc4_hmac_md5 "RC4-HMAC-MD5" -#define LN_rc4_hmac_md5 "rc4-hmac-md5" -#define NID_rc4_hmac_md5 915 - -#define SN_aes_128_cbc_hmac_sha1 "AES-128-CBC-HMAC-SHA1" -#define LN_aes_128_cbc_hmac_sha1 "aes-128-cbc-hmac-sha1" -#define NID_aes_128_cbc_hmac_sha1 916 - -#define SN_aes_192_cbc_hmac_sha1 "AES-192-CBC-HMAC-SHA1" -#define LN_aes_192_cbc_hmac_sha1 "aes-192-cbc-hmac-sha1" -#define NID_aes_192_cbc_hmac_sha1 917 - -#define SN_aes_256_cbc_hmac_sha1 "AES-256-CBC-HMAC-SHA1" -#define LN_aes_256_cbc_hmac_sha1 "aes-256-cbc-hmac-sha1" -#define NID_aes_256_cbc_hmac_sha1 918 - diff --git a/src/plugins/ftp/openssl/objects.h b/src/plugins/ftp/openssl/objects.h deleted file mode 100644 index bd0ee52f..00000000 --- a/src/plugins/ftp/openssl/objects.h +++ /dev/null @@ -1,1138 +0,0 @@ -/* crypto/objects/objects.h */ -/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) - * All rights reserved. - * - * This package is an SSL implementation written - * by Eric Young (eay@cryptsoft.com). - * The implementation was written so as to conform with Netscapes SSL. - * - * This library is free for commercial and non-commercial use as long as - * the following conditions are aheared to. The following conditions - * apply to all code found in this distribution, be it the RC4, RSA, - * lhash, DES, etc., code; not just the SSL code. The SSL documentation - * included with this distribution is covered by the same copyright terms - * except that the holder is Tim Hudson (tjh@cryptsoft.com). - * - * Copyright remains Eric Young's, and as such any Copyright notices in - * the code are not to be removed. - * If this package is used in a product, Eric Young should be given attribution - * as the author of the parts of the library used. - * This can be in the form of a textual message at program startup or - * in documentation (online or textual) provided with the package. - * - * 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 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. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * "This product includes cryptographic software written by - * Eric Young (eay@cryptsoft.com)" - * The word 'cryptographic' can be left out if the rouines from the library - * being used are not cryptographic related :-). - * 4. If you include any Windows specific code (or a derivative thereof) from - * the apps directory (application code) you must include an acknowledgement: - * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" - * - * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``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 AUTHOR 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. - * - * The licence and distribution terms for any publically available version or - * derivative of this code cannot be changed. i.e. this code cannot simply be - * copied and put under another distribution licence - * [including the GNU Public Licence.] - */ - -#ifndef HEADER_OBJECTS_H -#define HEADER_OBJECTS_H - -#define USE_OBJ_MAC - -#ifdef USE_OBJ_MAC -#include -#else -#define SN_undef "UNDEF" -#define LN_undef "undefined" -#define NID_undef 0 -#define OBJ_undef 0L - -#define SN_Algorithm "Algorithm" -#define LN_algorithm "algorithm" -#define NID_algorithm 38 -#define OBJ_algorithm 1L,3L,14L,3L,2L - -#define LN_rsadsi "rsadsi" -#define NID_rsadsi 1 -#define OBJ_rsadsi 1L,2L,840L,113549L - -#define LN_pkcs "pkcs" -#define NID_pkcs 2 -#define OBJ_pkcs OBJ_rsadsi,1L - -#define SN_md2 "MD2" -#define LN_md2 "md2" -#define NID_md2 3 -#define OBJ_md2 OBJ_rsadsi,2L,2L - -#define SN_md5 "MD5" -#define LN_md5 "md5" -#define NID_md5 4 -#define OBJ_md5 OBJ_rsadsi,2L,5L - -#define SN_rc4 "RC4" -#define LN_rc4 "rc4" -#define NID_rc4 5 -#define OBJ_rc4 OBJ_rsadsi,3L,4L - -#define LN_rsaEncryption "rsaEncryption" -#define NID_rsaEncryption 6 -#define OBJ_rsaEncryption OBJ_pkcs,1L,1L - -#define SN_md2WithRSAEncryption "RSA-MD2" -#define LN_md2WithRSAEncryption "md2WithRSAEncryption" -#define NID_md2WithRSAEncryption 7 -#define OBJ_md2WithRSAEncryption OBJ_pkcs,1L,2L - -#define SN_md5WithRSAEncryption "RSA-MD5" -#define LN_md5WithRSAEncryption "md5WithRSAEncryption" -#define NID_md5WithRSAEncryption 8 -#define OBJ_md5WithRSAEncryption OBJ_pkcs,1L,4L - -#define SN_pbeWithMD2AndDES_CBC "PBE-MD2-DES" -#define LN_pbeWithMD2AndDES_CBC "pbeWithMD2AndDES-CBC" -#define NID_pbeWithMD2AndDES_CBC 9 -#define OBJ_pbeWithMD2AndDES_CBC OBJ_pkcs,5L,1L - -#define SN_pbeWithMD5AndDES_CBC "PBE-MD5-DES" -#define LN_pbeWithMD5AndDES_CBC "pbeWithMD5AndDES-CBC" -#define NID_pbeWithMD5AndDES_CBC 10 -#define OBJ_pbeWithMD5AndDES_CBC OBJ_pkcs,5L,3L - -#define LN_X500 "X500" -#define NID_X500 11 -#define OBJ_X500 2L,5L - -#define LN_X509 "X509" -#define NID_X509 12 -#define OBJ_X509 OBJ_X500,4L - -#define SN_commonName "CN" -#define LN_commonName "commonName" -#define NID_commonName 13 -#define OBJ_commonName OBJ_X509,3L - -#define SN_countryName "C" -#define LN_countryName "countryName" -#define NID_countryName 14 -#define OBJ_countryName OBJ_X509,6L - -#define SN_localityName "L" -#define LN_localityName "localityName" -#define NID_localityName 15 -#define OBJ_localityName OBJ_X509,7L - -/* Postal Address? PA */ - -/* should be "ST" (rfc1327) but MS uses 'S' */ -#define SN_stateOrProvinceName "ST" -#define LN_stateOrProvinceName "stateOrProvinceName" -#define NID_stateOrProvinceName 16 -#define OBJ_stateOrProvinceName OBJ_X509,8L - -#define SN_organizationName "O" -#define LN_organizationName "organizationName" -#define NID_organizationName 17 -#define OBJ_organizationName OBJ_X509,10L - -#define SN_organizationalUnitName "OU" -#define LN_organizationalUnitName "organizationalUnitName" -#define NID_organizationalUnitName 18 -#define OBJ_organizationalUnitName OBJ_X509,11L - -#define SN_rsa "RSA" -#define LN_rsa "rsa" -#define NID_rsa 19 -#define OBJ_rsa OBJ_X500,8L,1L,1L - -#define LN_pkcs7 "pkcs7" -#define NID_pkcs7 20 -#define OBJ_pkcs7 OBJ_pkcs,7L - -#define LN_pkcs7_data "pkcs7-data" -#define NID_pkcs7_data 21 -#define OBJ_pkcs7_data OBJ_pkcs7,1L - -#define LN_pkcs7_signed "pkcs7-signedData" -#define NID_pkcs7_signed 22 -#define OBJ_pkcs7_signed OBJ_pkcs7,2L - -#define LN_pkcs7_enveloped "pkcs7-envelopedData" -#define NID_pkcs7_enveloped 23 -#define OBJ_pkcs7_enveloped OBJ_pkcs7,3L - -#define LN_pkcs7_signedAndEnveloped "pkcs7-signedAndEnvelopedData" -#define NID_pkcs7_signedAndEnveloped 24 -#define OBJ_pkcs7_signedAndEnveloped OBJ_pkcs7,4L - -#define LN_pkcs7_digest "pkcs7-digestData" -#define NID_pkcs7_digest 25 -#define OBJ_pkcs7_digest OBJ_pkcs7,5L - -#define LN_pkcs7_encrypted "pkcs7-encryptedData" -#define NID_pkcs7_encrypted 26 -#define OBJ_pkcs7_encrypted OBJ_pkcs7,6L - -#define LN_pkcs3 "pkcs3" -#define NID_pkcs3 27 -#define OBJ_pkcs3 OBJ_pkcs,3L - -#define LN_dhKeyAgreement "dhKeyAgreement" -#define NID_dhKeyAgreement 28 -#define OBJ_dhKeyAgreement OBJ_pkcs3,1L - -#define SN_des_ecb "DES-ECB" -#define LN_des_ecb "des-ecb" -#define NID_des_ecb 29 -#define OBJ_des_ecb OBJ_algorithm,6L - -#define SN_des_cfb64 "DES-CFB" -#define LN_des_cfb64 "des-cfb" -#define NID_des_cfb64 30 -/* IV + num */ -#define OBJ_des_cfb64 OBJ_algorithm,9L - -#define SN_des_cbc "DES-CBC" -#define LN_des_cbc "des-cbc" -#define NID_des_cbc 31 -/* IV */ -#define OBJ_des_cbc OBJ_algorithm,7L - -#define SN_des_ede "DES-EDE" -#define LN_des_ede "des-ede" -#define NID_des_ede 32 -/* ?? */ -#define OBJ_des_ede OBJ_algorithm,17L - -#define SN_des_ede3 "DES-EDE3" -#define LN_des_ede3 "des-ede3" -#define NID_des_ede3 33 - -#define SN_idea_cbc "IDEA-CBC" -#define LN_idea_cbc "idea-cbc" -#define NID_idea_cbc 34 -#define OBJ_idea_cbc 1L,3L,6L,1L,4L,1L,188L,7L,1L,1L,2L - -#define SN_idea_cfb64 "IDEA-CFB" -#define LN_idea_cfb64 "idea-cfb" -#define NID_idea_cfb64 35 - -#define SN_idea_ecb "IDEA-ECB" -#define LN_idea_ecb "idea-ecb" -#define NID_idea_ecb 36 - -#define SN_rc2_cbc "RC2-CBC" -#define LN_rc2_cbc "rc2-cbc" -#define NID_rc2_cbc 37 -#define OBJ_rc2_cbc OBJ_rsadsi,3L,2L - -#define SN_rc2_ecb "RC2-ECB" -#define LN_rc2_ecb "rc2-ecb" -#define NID_rc2_ecb 38 - -#define SN_rc2_cfb64 "RC2-CFB" -#define LN_rc2_cfb64 "rc2-cfb" -#define NID_rc2_cfb64 39 - -#define SN_rc2_ofb64 "RC2-OFB" -#define LN_rc2_ofb64 "rc2-ofb" -#define NID_rc2_ofb64 40 - -#define SN_sha "SHA" -#define LN_sha "sha" -#define NID_sha 41 -#define OBJ_sha OBJ_algorithm,18L - -#define SN_shaWithRSAEncryption "RSA-SHA" -#define LN_shaWithRSAEncryption "shaWithRSAEncryption" -#define NID_shaWithRSAEncryption 42 -#define OBJ_shaWithRSAEncryption OBJ_algorithm,15L - -#define SN_des_ede_cbc "DES-EDE-CBC" -#define LN_des_ede_cbc "des-ede-cbc" -#define NID_des_ede_cbc 43 - -#define SN_des_ede3_cbc "DES-EDE3-CBC" -#define LN_des_ede3_cbc "des-ede3-cbc" -#define NID_des_ede3_cbc 44 -#define OBJ_des_ede3_cbc OBJ_rsadsi,3L,7L - -#define SN_des_ofb64 "DES-OFB" -#define LN_des_ofb64 "des-ofb" -#define NID_des_ofb64 45 -#define OBJ_des_ofb64 OBJ_algorithm,8L - -#define SN_idea_ofb64 "IDEA-OFB" -#define LN_idea_ofb64 "idea-ofb" -#define NID_idea_ofb64 46 - -#define LN_pkcs9 "pkcs9" -#define NID_pkcs9 47 -#define OBJ_pkcs9 OBJ_pkcs,9L - -#define SN_pkcs9_emailAddress "Email" -#define LN_pkcs9_emailAddress "emailAddress" -#define NID_pkcs9_emailAddress 48 -#define OBJ_pkcs9_emailAddress OBJ_pkcs9,1L - -#define LN_pkcs9_unstructuredName "unstructuredName" -#define NID_pkcs9_unstructuredName 49 -#define OBJ_pkcs9_unstructuredName OBJ_pkcs9,2L - -#define LN_pkcs9_contentType "contentType" -#define NID_pkcs9_contentType 50 -#define OBJ_pkcs9_contentType OBJ_pkcs9,3L - -#define LN_pkcs9_messageDigest "messageDigest" -#define NID_pkcs9_messageDigest 51 -#define OBJ_pkcs9_messageDigest OBJ_pkcs9,4L - -#define LN_pkcs9_signingTime "signingTime" -#define NID_pkcs9_signingTime 52 -#define OBJ_pkcs9_signingTime OBJ_pkcs9,5L - -#define LN_pkcs9_countersignature "countersignature" -#define NID_pkcs9_countersignature 53 -#define OBJ_pkcs9_countersignature OBJ_pkcs9,6L - -#define LN_pkcs9_challengePassword "challengePassword" -#define NID_pkcs9_challengePassword 54 -#define OBJ_pkcs9_challengePassword OBJ_pkcs9,7L - -#define LN_pkcs9_unstructuredAddress "unstructuredAddress" -#define NID_pkcs9_unstructuredAddress 55 -#define OBJ_pkcs9_unstructuredAddress OBJ_pkcs9,8L - -#define LN_pkcs9_extCertAttributes "extendedCertificateAttributes" -#define NID_pkcs9_extCertAttributes 56 -#define OBJ_pkcs9_extCertAttributes OBJ_pkcs9,9L - -#define SN_netscape "Netscape" -#define LN_netscape "Netscape Communications Corp." -#define NID_netscape 57 -#define OBJ_netscape 2L,16L,840L,1L,113730L - -#define SN_netscape_cert_extension "nsCertExt" -#define LN_netscape_cert_extension "Netscape Certificate Extension" -#define NID_netscape_cert_extension 58 -#define OBJ_netscape_cert_extension OBJ_netscape,1L - -#define SN_netscape_data_type "nsDataType" -#define LN_netscape_data_type "Netscape Data Type" -#define NID_netscape_data_type 59 -#define OBJ_netscape_data_type OBJ_netscape,2L - -#define SN_des_ede_cfb64 "DES-EDE-CFB" -#define LN_des_ede_cfb64 "des-ede-cfb" -#define NID_des_ede_cfb64 60 - -#define SN_des_ede3_cfb64 "DES-EDE3-CFB" -#define LN_des_ede3_cfb64 "des-ede3-cfb" -#define NID_des_ede3_cfb64 61 - -#define SN_des_ede_ofb64 "DES-EDE-OFB" -#define LN_des_ede_ofb64 "des-ede-ofb" -#define NID_des_ede_ofb64 62 - -#define SN_des_ede3_ofb64 "DES-EDE3-OFB" -#define LN_des_ede3_ofb64 "des-ede3-ofb" -#define NID_des_ede3_ofb64 63 - -/* I'm not sure about the object ID */ -#define SN_sha1 "SHA1" -#define LN_sha1 "sha1" -#define NID_sha1 64 -#define OBJ_sha1 OBJ_algorithm,26L -/* 28 Jun 1996 - eay */ -/* #define OBJ_sha1 1L,3L,14L,2L,26L,05L <- wrong */ - -#define SN_sha1WithRSAEncryption "RSA-SHA1" -#define LN_sha1WithRSAEncryption "sha1WithRSAEncryption" -#define NID_sha1WithRSAEncryption 65 -#define OBJ_sha1WithRSAEncryption OBJ_pkcs,1L,5L - -#define SN_dsaWithSHA "DSA-SHA" -#define LN_dsaWithSHA "dsaWithSHA" -#define NID_dsaWithSHA 66 -#define OBJ_dsaWithSHA OBJ_algorithm,13L - -#define SN_dsa_2 "DSA-old" -#define LN_dsa_2 "dsaEncryption-old" -#define NID_dsa_2 67 -#define OBJ_dsa_2 OBJ_algorithm,12L - -/* proposed by microsoft to RSA */ -#define SN_pbeWithSHA1AndRC2_CBC "PBE-SHA1-RC2-64" -#define LN_pbeWithSHA1AndRC2_CBC "pbeWithSHA1AndRC2-CBC" -#define NID_pbeWithSHA1AndRC2_CBC 68 -#define OBJ_pbeWithSHA1AndRC2_CBC OBJ_pkcs,5L,11L - -/* proposed by microsoft to RSA as pbeWithSHA1AndRC4: it is now - * defined explicitly in PKCS#5 v2.0 as id-PBKDF2 which is something - * completely different. - */ -#define LN_id_pbkdf2 "PBKDF2" -#define NID_id_pbkdf2 69 -#define OBJ_id_pbkdf2 OBJ_pkcs,5L,12L - -#define SN_dsaWithSHA1_2 "DSA-SHA1-old" -#define LN_dsaWithSHA1_2 "dsaWithSHA1-old" -#define NID_dsaWithSHA1_2 70 -/* Got this one from 'sdn706r20.pdf' which is actually an NSA document :-) */ -#define OBJ_dsaWithSHA1_2 OBJ_algorithm,27L - -#define SN_netscape_cert_type "nsCertType" -#define LN_netscape_cert_type "Netscape Cert Type" -#define NID_netscape_cert_type 71 -#define OBJ_netscape_cert_type OBJ_netscape_cert_extension,1L - -#define SN_netscape_base_url "nsBaseUrl" -#define LN_netscape_base_url "Netscape Base Url" -#define NID_netscape_base_url 72 -#define OBJ_netscape_base_url OBJ_netscape_cert_extension,2L - -#define SN_netscape_revocation_url "nsRevocationUrl" -#define LN_netscape_revocation_url "Netscape Revocation Url" -#define NID_netscape_revocation_url 73 -#define OBJ_netscape_revocation_url OBJ_netscape_cert_extension,3L - -#define SN_netscape_ca_revocation_url "nsCaRevocationUrl" -#define LN_netscape_ca_revocation_url "Netscape CA Revocation Url" -#define NID_netscape_ca_revocation_url 74 -#define OBJ_netscape_ca_revocation_url OBJ_netscape_cert_extension,4L - -#define SN_netscape_renewal_url "nsRenewalUrl" -#define LN_netscape_renewal_url "Netscape Renewal Url" -#define NID_netscape_renewal_url 75 -#define OBJ_netscape_renewal_url OBJ_netscape_cert_extension,7L - -#define SN_netscape_ca_policy_url "nsCaPolicyUrl" -#define LN_netscape_ca_policy_url "Netscape CA Policy Url" -#define NID_netscape_ca_policy_url 76 -#define OBJ_netscape_ca_policy_url OBJ_netscape_cert_extension,8L - -#define SN_netscape_ssl_server_name "nsSslServerName" -#define LN_netscape_ssl_server_name "Netscape SSL Server Name" -#define NID_netscape_ssl_server_name 77 -#define OBJ_netscape_ssl_server_name OBJ_netscape_cert_extension,12L - -#define SN_netscape_comment "nsComment" -#define LN_netscape_comment "Netscape Comment" -#define NID_netscape_comment 78 -#define OBJ_netscape_comment OBJ_netscape_cert_extension,13L - -#define SN_netscape_cert_sequence "nsCertSequence" -#define LN_netscape_cert_sequence "Netscape Certificate Sequence" -#define NID_netscape_cert_sequence 79 -#define OBJ_netscape_cert_sequence OBJ_netscape_data_type,5L - -#define SN_desx_cbc "DESX-CBC" -#define LN_desx_cbc "desx-cbc" -#define NID_desx_cbc 80 - -#define SN_id_ce "id-ce" -#define NID_id_ce 81 -#define OBJ_id_ce 2L,5L,29L - -#define SN_subject_key_identifier "subjectKeyIdentifier" -#define LN_subject_key_identifier "X509v3 Subject Key Identifier" -#define NID_subject_key_identifier 82 -#define OBJ_subject_key_identifier OBJ_id_ce,14L - -#define SN_key_usage "keyUsage" -#define LN_key_usage "X509v3 Key Usage" -#define NID_key_usage 83 -#define OBJ_key_usage OBJ_id_ce,15L - -#define SN_private_key_usage_period "privateKeyUsagePeriod" -#define LN_private_key_usage_period "X509v3 Private Key Usage Period" -#define NID_private_key_usage_period 84 -#define OBJ_private_key_usage_period OBJ_id_ce,16L - -#define SN_subject_alt_name "subjectAltName" -#define LN_subject_alt_name "X509v3 Subject Alternative Name" -#define NID_subject_alt_name 85 -#define OBJ_subject_alt_name OBJ_id_ce,17L - -#define SN_issuer_alt_name "issuerAltName" -#define LN_issuer_alt_name "X509v3 Issuer Alternative Name" -#define NID_issuer_alt_name 86 -#define OBJ_issuer_alt_name OBJ_id_ce,18L - -#define SN_basic_constraints "basicConstraints" -#define LN_basic_constraints "X509v3 Basic Constraints" -#define NID_basic_constraints 87 -#define OBJ_basic_constraints OBJ_id_ce,19L - -#define SN_crl_number "crlNumber" -#define LN_crl_number "X509v3 CRL Number" -#define NID_crl_number 88 -#define OBJ_crl_number OBJ_id_ce,20L - -#define SN_certificate_policies "certificatePolicies" -#define LN_certificate_policies "X509v3 Certificate Policies" -#define NID_certificate_policies 89 -#define OBJ_certificate_policies OBJ_id_ce,32L - -#define SN_authority_key_identifier "authorityKeyIdentifier" -#define LN_authority_key_identifier "X509v3 Authority Key Identifier" -#define NID_authority_key_identifier 90 -#define OBJ_authority_key_identifier OBJ_id_ce,35L - -#define SN_bf_cbc "BF-CBC" -#define LN_bf_cbc "bf-cbc" -#define NID_bf_cbc 91 -#define OBJ_bf_cbc 1L,3L,6L,1L,4L,1L,3029L,1L,2L - -#define SN_bf_ecb "BF-ECB" -#define LN_bf_ecb "bf-ecb" -#define NID_bf_ecb 92 - -#define SN_bf_cfb64 "BF-CFB" -#define LN_bf_cfb64 "bf-cfb" -#define NID_bf_cfb64 93 - -#define SN_bf_ofb64 "BF-OFB" -#define LN_bf_ofb64 "bf-ofb" -#define NID_bf_ofb64 94 - -#define SN_mdc2 "MDC2" -#define LN_mdc2 "mdc2" -#define NID_mdc2 95 -#define OBJ_mdc2 2L,5L,8L,3L,101L -/* An alternative? 1L,3L,14L,3L,2L,19L */ - -#define SN_mdc2WithRSA "RSA-MDC2" -#define LN_mdc2WithRSA "mdc2withRSA" -#define NID_mdc2WithRSA 96 -#define OBJ_mdc2WithRSA 2L,5L,8L,3L,100L - -#define SN_rc4_40 "RC4-40" -#define LN_rc4_40 "rc4-40" -#define NID_rc4_40 97 - -#define SN_rc2_40_cbc "RC2-40-CBC" -#define LN_rc2_40_cbc "rc2-40-cbc" -#define NID_rc2_40_cbc 98 - -#define SN_givenName "G" -#define LN_givenName "givenName" -#define NID_givenName 99 -#define OBJ_givenName OBJ_X509,42L - -#define SN_surname "S" -#define LN_surname "surname" -#define NID_surname 100 -#define OBJ_surname OBJ_X509,4L - -#define SN_initials "I" -#define LN_initials "initials" -#define NID_initials 101 -#define OBJ_initials OBJ_X509,43L - -#define SN_uniqueIdentifier "UID" -#define LN_uniqueIdentifier "uniqueIdentifier" -#define NID_uniqueIdentifier 102 -#define OBJ_uniqueIdentifier OBJ_X509,45L - -#define SN_crl_distribution_points "crlDistributionPoints" -#define LN_crl_distribution_points "X509v3 CRL Distribution Points" -#define NID_crl_distribution_points 103 -#define OBJ_crl_distribution_points OBJ_id_ce,31L - -#define SN_md5WithRSA "RSA-NP-MD5" -#define LN_md5WithRSA "md5WithRSA" -#define NID_md5WithRSA 104 -#define OBJ_md5WithRSA OBJ_algorithm,3L - -#define SN_serialNumber "SN" -#define LN_serialNumber "serialNumber" -#define NID_serialNumber 105 -#define OBJ_serialNumber OBJ_X509,5L - -#define SN_title "T" -#define LN_title "title" -#define NID_title 106 -#define OBJ_title OBJ_X509,12L - -#define SN_description "D" -#define LN_description "description" -#define NID_description 107 -#define OBJ_description OBJ_X509,13L - -/* CAST5 is CAST-128, I'm just sticking with the documentation */ -#define SN_cast5_cbc "CAST5-CBC" -#define LN_cast5_cbc "cast5-cbc" -#define NID_cast5_cbc 108 -#define OBJ_cast5_cbc 1L,2L,840L,113533L,7L,66L,10L - -#define SN_cast5_ecb "CAST5-ECB" -#define LN_cast5_ecb "cast5-ecb" -#define NID_cast5_ecb 109 - -#define SN_cast5_cfb64 "CAST5-CFB" -#define LN_cast5_cfb64 "cast5-cfb" -#define NID_cast5_cfb64 110 - -#define SN_cast5_ofb64 "CAST5-OFB" -#define LN_cast5_ofb64 "cast5-ofb" -#define NID_cast5_ofb64 111 - -#define LN_pbeWithMD5AndCast5_CBC "pbeWithMD5AndCast5CBC" -#define NID_pbeWithMD5AndCast5_CBC 112 -#define OBJ_pbeWithMD5AndCast5_CBC 1L,2L,840L,113533L,7L,66L,12L - -/* This is one sun will soon be using :-( - * id-dsa-with-sha1 ID ::= { - * iso(1) member-body(2) us(840) x9-57 (10040) x9cm(4) 3 } - */ -#define SN_dsaWithSHA1 "DSA-SHA1" -#define LN_dsaWithSHA1 "dsaWithSHA1" -#define NID_dsaWithSHA1 113 -#define OBJ_dsaWithSHA1 1L,2L,840L,10040L,4L,3L - -#define NID_md5_sha1 114 -#define SN_md5_sha1 "MD5-SHA1" -#define LN_md5_sha1 "md5-sha1" - -#define SN_sha1WithRSA "RSA-SHA1-2" -#define LN_sha1WithRSA "sha1WithRSA" -#define NID_sha1WithRSA 115 -#define OBJ_sha1WithRSA OBJ_algorithm,29L - -#define SN_dsa "DSA" -#define LN_dsa "dsaEncryption" -#define NID_dsa 116 -#define OBJ_dsa 1L,2L,840L,10040L,4L,1L - -#define SN_ripemd160 "RIPEMD160" -#define LN_ripemd160 "ripemd160" -#define NID_ripemd160 117 -#define OBJ_ripemd160 1L,3L,36L,3L,2L,1L - -/* The name should actually be rsaSignatureWithripemd160, but I'm going - * to continue using the convention I'm using with the other ciphers */ -#define SN_ripemd160WithRSA "RSA-RIPEMD160" -#define LN_ripemd160WithRSA "ripemd160WithRSA" -#define NID_ripemd160WithRSA 119 -#define OBJ_ripemd160WithRSA 1L,3L,36L,3L,3L,1L,2L - -/* Taken from rfc2040 - * RC5_CBC_Parameters ::= SEQUENCE { - * version INTEGER (v1_0(16)), - * rounds INTEGER (8..127), - * blockSizeInBits INTEGER (64, 128), - * iv OCTET STRING OPTIONAL - * } - */ -#define SN_rc5_cbc "RC5-CBC" -#define LN_rc5_cbc "rc5-cbc" -#define NID_rc5_cbc 120 -#define OBJ_rc5_cbc OBJ_rsadsi,3L,8L - -#define SN_rc5_ecb "RC5-ECB" -#define LN_rc5_ecb "rc5-ecb" -#define NID_rc5_ecb 121 - -#define SN_rc5_cfb64 "RC5-CFB" -#define LN_rc5_cfb64 "rc5-cfb" -#define NID_rc5_cfb64 122 - -#define SN_rc5_ofb64 "RC5-OFB" -#define LN_rc5_ofb64 "rc5-ofb" -#define NID_rc5_ofb64 123 - -#define SN_rle_compression "RLE" -#define LN_rle_compression "run length compression" -#define NID_rle_compression 124 -#define OBJ_rle_compression 1L,1L,1L,1L,666L,1L - -#define SN_zlib_compression "ZLIB" -#define LN_zlib_compression "zlib compression" -#define NID_zlib_compression 125 -#define OBJ_zlib_compression 1L,1L,1L,1L,666L,2L - -#define SN_ext_key_usage "extendedKeyUsage" -#define LN_ext_key_usage "X509v3 Extended Key Usage" -#define NID_ext_key_usage 126 -#define OBJ_ext_key_usage OBJ_id_ce,37 - -#define SN_id_pkix "PKIX" -#define NID_id_pkix 127 -#define OBJ_id_pkix 1L,3L,6L,1L,5L,5L,7L - -#define SN_id_kp "id-kp" -#define NID_id_kp 128 -#define OBJ_id_kp OBJ_id_pkix,3L - -/* PKIX extended key usage OIDs */ - -#define SN_server_auth "serverAuth" -#define LN_server_auth "TLS Web Server Authentication" -#define NID_server_auth 129 -#define OBJ_server_auth OBJ_id_kp,1L - -#define SN_client_auth "clientAuth" -#define LN_client_auth "TLS Web Client Authentication" -#define NID_client_auth 130 -#define OBJ_client_auth OBJ_id_kp,2L - -#define SN_code_sign "codeSigning" -#define LN_code_sign "Code Signing" -#define NID_code_sign 131 -#define OBJ_code_sign OBJ_id_kp,3L - -#define SN_email_protect "emailProtection" -#define LN_email_protect "E-mail Protection" -#define NID_email_protect 132 -#define OBJ_email_protect OBJ_id_kp,4L - -#define SN_time_stamp "timeStamping" -#define LN_time_stamp "Time Stamping" -#define NID_time_stamp 133 -#define OBJ_time_stamp OBJ_id_kp,8L - -/* Additional extended key usage OIDs: Microsoft */ - -#define SN_ms_code_ind "msCodeInd" -#define LN_ms_code_ind "Microsoft Individual Code Signing" -#define NID_ms_code_ind 134 -#define OBJ_ms_code_ind 1L,3L,6L,1L,4L,1L,311L,2L,1L,21L - -#define SN_ms_code_com "msCodeCom" -#define LN_ms_code_com "Microsoft Commercial Code Signing" -#define NID_ms_code_com 135 -#define OBJ_ms_code_com 1L,3L,6L,1L,4L,1L,311L,2L,1L,22L - -#define SN_ms_ctl_sign "msCTLSign" -#define LN_ms_ctl_sign "Microsoft Trust List Signing" -#define NID_ms_ctl_sign 136 -#define OBJ_ms_ctl_sign 1L,3L,6L,1L,4L,1L,311L,10L,3L,1L - -#define SN_ms_sgc "msSGC" -#define LN_ms_sgc "Microsoft Server Gated Crypto" -#define NID_ms_sgc 137 -#define OBJ_ms_sgc 1L,3L,6L,1L,4L,1L,311L,10L,3L,3L - -#define SN_ms_efs "msEFS" -#define LN_ms_efs "Microsoft Encrypted File System" -#define NID_ms_efs 138 -#define OBJ_ms_efs 1L,3L,6L,1L,4L,1L,311L,10L,3L,4L - -/* Additional usage: Netscape */ - -#define SN_ns_sgc "nsSGC" -#define LN_ns_sgc "Netscape Server Gated Crypto" -#define NID_ns_sgc 139 -#define OBJ_ns_sgc OBJ_netscape,4L,1L - -#define SN_delta_crl "deltaCRL" -#define LN_delta_crl "X509v3 Delta CRL Indicator" -#define NID_delta_crl 140 -#define OBJ_delta_crl OBJ_id_ce,27L - -#define SN_crl_reason "CRLReason" -#define LN_crl_reason "CRL Reason Code" -#define NID_crl_reason 141 -#define OBJ_crl_reason OBJ_id_ce,21L - -#define SN_invalidity_date "invalidityDate" -#define LN_invalidity_date "Invalidity Date" -#define NID_invalidity_date 142 -#define OBJ_invalidity_date OBJ_id_ce,24L - -#define SN_sxnet "SXNetID" -#define LN_sxnet "Strong Extranet ID" -#define NID_sxnet 143 -#define OBJ_sxnet 1L,3L,101L,1L,4L,1L - -/* PKCS12 and related OBJECT IDENTIFIERS */ - -#define OBJ_pkcs12 OBJ_pkcs,12L -#define OBJ_pkcs12_pbeids OBJ_pkcs12, 1 - -#define SN_pbe_WithSHA1And128BitRC4 "PBE-SHA1-RC4-128" -#define LN_pbe_WithSHA1And128BitRC4 "pbeWithSHA1And128BitRC4" -#define NID_pbe_WithSHA1And128BitRC4 144 -#define OBJ_pbe_WithSHA1And128BitRC4 OBJ_pkcs12_pbeids, 1L - -#define SN_pbe_WithSHA1And40BitRC4 "PBE-SHA1-RC4-40" -#define LN_pbe_WithSHA1And40BitRC4 "pbeWithSHA1And40BitRC4" -#define NID_pbe_WithSHA1And40BitRC4 145 -#define OBJ_pbe_WithSHA1And40BitRC4 OBJ_pkcs12_pbeids, 2L - -#define SN_pbe_WithSHA1And3_Key_TripleDES_CBC "PBE-SHA1-3DES" -#define LN_pbe_WithSHA1And3_Key_TripleDES_CBC "pbeWithSHA1And3-KeyTripleDES-CBC" -#define NID_pbe_WithSHA1And3_Key_TripleDES_CBC 146 -#define OBJ_pbe_WithSHA1And3_Key_TripleDES_CBC OBJ_pkcs12_pbeids, 3L - -#define SN_pbe_WithSHA1And2_Key_TripleDES_CBC "PBE-SHA1-2DES" -#define LN_pbe_WithSHA1And2_Key_TripleDES_CBC "pbeWithSHA1And2-KeyTripleDES-CBC" -#define NID_pbe_WithSHA1And2_Key_TripleDES_CBC 147 -#define OBJ_pbe_WithSHA1And2_Key_TripleDES_CBC OBJ_pkcs12_pbeids, 4L - -#define SN_pbe_WithSHA1And128BitRC2_CBC "PBE-SHA1-RC2-128" -#define LN_pbe_WithSHA1And128BitRC2_CBC "pbeWithSHA1And128BitRC2-CBC" -#define NID_pbe_WithSHA1And128BitRC2_CBC 148 -#define OBJ_pbe_WithSHA1And128BitRC2_CBC OBJ_pkcs12_pbeids, 5L - -#define SN_pbe_WithSHA1And40BitRC2_CBC "PBE-SHA1-RC2-40" -#define LN_pbe_WithSHA1And40BitRC2_CBC "pbeWithSHA1And40BitRC2-CBC" -#define NID_pbe_WithSHA1And40BitRC2_CBC 149 -#define OBJ_pbe_WithSHA1And40BitRC2_CBC OBJ_pkcs12_pbeids, 6L - -#define OBJ_pkcs12_Version1 OBJ_pkcs12, 10L - -#define OBJ_pkcs12_BagIds OBJ_pkcs12_Version1, 1L - -#define LN_keyBag "keyBag" -#define NID_keyBag 150 -#define OBJ_keyBag OBJ_pkcs12_BagIds, 1L - -#define LN_pkcs8ShroudedKeyBag "pkcs8ShroudedKeyBag" -#define NID_pkcs8ShroudedKeyBag 151 -#define OBJ_pkcs8ShroudedKeyBag OBJ_pkcs12_BagIds, 2L - -#define LN_certBag "certBag" -#define NID_certBag 152 -#define OBJ_certBag OBJ_pkcs12_BagIds, 3L - -#define LN_crlBag "crlBag" -#define NID_crlBag 153 -#define OBJ_crlBag OBJ_pkcs12_BagIds, 4L - -#define LN_secretBag "secretBag" -#define NID_secretBag 154 -#define OBJ_secretBag OBJ_pkcs12_BagIds, 5L - -#define LN_safeContentsBag "safeContentsBag" -#define NID_safeContentsBag 155 -#define OBJ_safeContentsBag OBJ_pkcs12_BagIds, 6L - -#define LN_friendlyName "friendlyName" -#define NID_friendlyName 156 -#define OBJ_friendlyName OBJ_pkcs9, 20L - -#define LN_localKeyID "localKeyID" -#define NID_localKeyID 157 -#define OBJ_localKeyID OBJ_pkcs9, 21L - -#define OBJ_certTypes OBJ_pkcs9, 22L - -#define LN_x509Certificate "x509Certificate" -#define NID_x509Certificate 158 -#define OBJ_x509Certificate OBJ_certTypes, 1L - -#define LN_sdsiCertificate "sdsiCertificate" -#define NID_sdsiCertificate 159 -#define OBJ_sdsiCertificate OBJ_certTypes, 2L - -#define OBJ_crlTypes OBJ_pkcs9, 23L - -#define LN_x509Crl "x509Crl" -#define NID_x509Crl 160 -#define OBJ_x509Crl OBJ_crlTypes, 1L - -/* PKCS#5 v2 OIDs */ - -#define LN_pbes2 "PBES2" -#define NID_pbes2 161 -#define OBJ_pbes2 OBJ_pkcs,5L,13L - -#define LN_pbmac1 "PBMAC1" -#define NID_pbmac1 162 -#define OBJ_pbmac1 OBJ_pkcs,5L,14L - -#define LN_hmacWithSHA1 "hmacWithSHA1" -#define NID_hmacWithSHA1 163 -#define OBJ_hmacWithSHA1 OBJ_rsadsi,2L,7L - -/* Policy Qualifier Ids */ - -#define LN_id_qt_cps "Policy Qualifier CPS" -#define SN_id_qt_cps "id-qt-cps" -#define NID_id_qt_cps 164 -#define OBJ_id_qt_cps OBJ_id_pkix,2L,1L - -#define LN_id_qt_unotice "Policy Qualifier User Notice" -#define SN_id_qt_unotice "id-qt-unotice" -#define NID_id_qt_unotice 165 -#define OBJ_id_qt_unotice OBJ_id_pkix,2L,2L - -#define SN_rc2_64_cbc "RC2-64-CBC" -#define LN_rc2_64_cbc "rc2-64-cbc" -#define NID_rc2_64_cbc 166 - -#define SN_SMIMECapabilities "SMIME-CAPS" -#define LN_SMIMECapabilities "S/MIME Capabilities" -#define NID_SMIMECapabilities 167 -#define OBJ_SMIMECapabilities OBJ_pkcs9,15L - -#define SN_pbeWithMD2AndRC2_CBC "PBE-MD2-RC2-64" -#define LN_pbeWithMD2AndRC2_CBC "pbeWithMD2AndRC2-CBC" -#define NID_pbeWithMD2AndRC2_CBC 168 -#define OBJ_pbeWithMD2AndRC2_CBC OBJ_pkcs,5L,4L - -#define SN_pbeWithMD5AndRC2_CBC "PBE-MD5-RC2-64" -#define LN_pbeWithMD5AndRC2_CBC "pbeWithMD5AndRC2-CBC" -#define NID_pbeWithMD5AndRC2_CBC 169 -#define OBJ_pbeWithMD5AndRC2_CBC OBJ_pkcs,5L,6L - -#define SN_pbeWithSHA1AndDES_CBC "PBE-SHA1-DES" -#define LN_pbeWithSHA1AndDES_CBC "pbeWithSHA1AndDES-CBC" -#define NID_pbeWithSHA1AndDES_CBC 170 -#define OBJ_pbeWithSHA1AndDES_CBC OBJ_pkcs,5L,10L - -/* Extension request OIDs */ - -#define LN_ms_ext_req "Microsoft Extension Request" -#define SN_ms_ext_req "msExtReq" -#define NID_ms_ext_req 171 -#define OBJ_ms_ext_req 1L,3L,6L,1L,4L,1L,311L,2L,1L,14L - -#define LN_ext_req "Extension Request" -#define SN_ext_req "extReq" -#define NID_ext_req 172 -#define OBJ_ext_req OBJ_pkcs9,14L - -#define SN_name "name" -#define LN_name "name" -#define NID_name 173 -#define OBJ_name OBJ_X509,41L - -#define SN_dnQualifier "dnQualifier" -#define LN_dnQualifier "dnQualifier" -#define NID_dnQualifier 174 -#define OBJ_dnQualifier OBJ_X509,46L - -#define SN_id_pe "id-pe" -#define NID_id_pe 175 -#define OBJ_id_pe OBJ_id_pkix,1L - -#define SN_id_ad "id-ad" -#define NID_id_ad 176 -#define OBJ_id_ad OBJ_id_pkix,48L - -#define SN_info_access "authorityInfoAccess" -#define LN_info_access "Authority Information Access" -#define NID_info_access 177 -#define OBJ_info_access OBJ_id_pe,1L - -#define SN_ad_OCSP "OCSP" -#define LN_ad_OCSP "OCSP" -#define NID_ad_OCSP 178 -#define OBJ_ad_OCSP OBJ_id_ad,1L - -#define SN_ad_ca_issuers "caIssuers" -#define LN_ad_ca_issuers "CA Issuers" -#define NID_ad_ca_issuers 179 -#define OBJ_ad_ca_issuers OBJ_id_ad,2L - -#define SN_OCSP_sign "OCSPSigning" -#define LN_OCSP_sign "OCSP Signing" -#define NID_OCSP_sign 180 -#define OBJ_OCSP_sign OBJ_id_kp,9L -#endif /* USE_OBJ_MAC */ - -#include -#include - -#define OBJ_NAME_TYPE_UNDEF 0x00 -#define OBJ_NAME_TYPE_MD_METH 0x01 -#define OBJ_NAME_TYPE_CIPHER_METH 0x02 -#define OBJ_NAME_TYPE_PKEY_METH 0x03 -#define OBJ_NAME_TYPE_COMP_METH 0x04 -#define OBJ_NAME_TYPE_NUM 0x05 - -#define OBJ_NAME_ALIAS 0x8000 - -#define OBJ_BSEARCH_VALUE_ON_NOMATCH 0x01 -#define OBJ_BSEARCH_FIRST_VALUE_ON_MATCH 0x02 - - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct obj_name_st - { - int type; - int alias; - const char *name; - const char *data; - } OBJ_NAME; - -#define OBJ_create_and_add_object(a,b,c) OBJ_create(a,b,c) - - -int OBJ_NAME_init(void); -int OBJ_NAME_new_index(unsigned long (*hash_func)(const char *), - int (*cmp_func)(const char *, const char *), - void (*free_func)(const char *, int, const char *)); -const char *OBJ_NAME_get(const char *name,int type); -int OBJ_NAME_add(const char *name,int type,const char *data); -int OBJ_NAME_remove(const char *name,int type); -void OBJ_NAME_cleanup(int type); /* -1 for everything */ -void OBJ_NAME_do_all(int type,void (*fn)(const OBJ_NAME *,void *arg), - void *arg); -void OBJ_NAME_do_all_sorted(int type,void (*fn)(const OBJ_NAME *,void *arg), - void *arg); - -ASN1_OBJECT * OBJ_dup(const ASN1_OBJECT *o); -ASN1_OBJECT * OBJ_nid2obj(int n); -const char * OBJ_nid2ln(int n); -const char * OBJ_nid2sn(int n); -int OBJ_obj2nid(const ASN1_OBJECT *o); -ASN1_OBJECT * OBJ_txt2obj(const char *s, int no_name); -int OBJ_obj2txt(char *buf, int buf_len, const ASN1_OBJECT *a, int no_name); -int OBJ_txt2nid(const char *s); -int OBJ_ln2nid(const char *s); -int OBJ_sn2nid(const char *s); -int OBJ_cmp(const ASN1_OBJECT *a,const ASN1_OBJECT *b); -const void * OBJ_bsearch_(const void *key,const void *base,int num,int size, - int (*cmp)(const void *, const void *)); -const void * OBJ_bsearch_ex_(const void *key,const void *base,int num, - int size, - int (*cmp)(const void *, const void *), - int flags); - -#define _DECLARE_OBJ_BSEARCH_CMP_FN(scope, type1, type2, nm) \ - static int nm##_cmp_BSEARCH_CMP_FN(const void *, const void *); \ - static int nm##_cmp(type1 const *, type2 const *); \ - scope type2 * OBJ_bsearch_##nm(type1 *key, type2 const *base, int num) - -#define DECLARE_OBJ_BSEARCH_CMP_FN(type1, type2, cmp) \ - _DECLARE_OBJ_BSEARCH_CMP_FN(static, type1, type2, cmp) -#define DECLARE_OBJ_BSEARCH_GLOBAL_CMP_FN(type1, type2, nm) \ - type2 * OBJ_bsearch_##nm(type1 *key, type2 const *base, int num) - -/* - * Unsolved problem: if a type is actually a pointer type, like - * nid_triple is, then its impossible to get a const where you need - * it. Consider: - * - * typedef int nid_triple[3]; - * const void *a_; - * const nid_triple const *a = a_; - * - * The assignement discards a const because what you really want is: - * - * const int const * const *a = a_; - * - * But if you do that, you lose the fact that a is an array of 3 ints, - * which breaks comparison functions. - * - * Thus we end up having to cast, sadly, or unpack the - * declarations. Or, as I finally did in this case, delcare nid_triple - * to be a struct, which it should have been in the first place. - * - * Ben, August 2008. - * - * Also, strictly speaking not all types need be const, but handling - * the non-constness means a lot of complication, and in practice - * comparison routines do always not touch their arguments. - */ - -#define IMPLEMENT_OBJ_BSEARCH_CMP_FN(type1, type2, nm) \ - static int nm##_cmp_BSEARCH_CMP_FN(const void *a_, const void *b_) \ - { \ - type1 const *a = a_; \ - type2 const *b = b_; \ - return nm##_cmp(a,b); \ - } \ - static type2 *OBJ_bsearch_##nm(type1 *key, type2 const *base, int num) \ - { \ - return (type2 *)OBJ_bsearch_(key, base, num, sizeof(type2), \ - nm##_cmp_BSEARCH_CMP_FN); \ - } \ - extern void dummy_prototype(void) - -#define IMPLEMENT_OBJ_BSEARCH_GLOBAL_CMP_FN(type1, type2, nm) \ - static int nm##_cmp_BSEARCH_CMP_FN(const void *a_, const void *b_) \ - { \ - type1 const *a = a_; \ - type2 const *b = b_; \ - return nm##_cmp(a,b); \ - } \ - type2 *OBJ_bsearch_##nm(type1 *key, type2 const *base, int num) \ - { \ - return (type2 *)OBJ_bsearch_(key, base, num, sizeof(type2), \ - nm##_cmp_BSEARCH_CMP_FN); \ - } \ - extern void dummy_prototype(void) - -#define OBJ_bsearch(type1,key,type2,base,num,cmp) \ - ((type2 *)OBJ_bsearch_(CHECKED_PTR_OF(type1,key),CHECKED_PTR_OF(type2,base), \ - num,sizeof(type2), \ - ((void)CHECKED_PTR_OF(type1,cmp##_type_1), \ - (void)CHECKED_PTR_OF(type2,cmp##_type_2), \ - cmp##_BSEARCH_CMP_FN))) - -#define OBJ_bsearch_ex(type1,key,type2,base,num,cmp,flags) \ - ((type2 *)OBJ_bsearch_ex_(CHECKED_PTR_OF(type1,key),CHECKED_PTR_OF(type2,base), \ - num,sizeof(type2), \ - ((void)CHECKED_PTR_OF(type1,cmp##_type_1), \ - (void)type_2=CHECKED_PTR_OF(type2,cmp##_type_2), \ - cmp##_BSEARCH_CMP_FN)),flags) - -int OBJ_new_nid(int num); -int OBJ_add_object(const ASN1_OBJECT *obj); -int OBJ_create(const char *oid,const char *sn,const char *ln); -void OBJ_cleanup(void ); -int OBJ_create_objects(BIO *in); - -int OBJ_find_sigid_algs(int signid, int *pdig_nid, int *ppkey_nid); -int OBJ_find_sigid_by_algs(int *psignid, int dig_nid, int pkey_nid); -int OBJ_add_sigid(int signid, int dig_id, int pkey_id); -void OBJ_sigid_free(void); - -extern int obj_cleanup_defer; -void check_defer(int nid); - -/* BEGIN ERROR CODES */ -/* The following lines are auto generated by the script mkerr.pl. Any changes - * made after this point may be overwritten when the script is next run. - */ -void ERR_load_OBJ_strings(void); - -/* Error codes for the OBJ functions. */ - -/* Function codes. */ -#define OBJ_F_OBJ_ADD_OBJECT 105 -#define OBJ_F_OBJ_CREATE 100 -#define OBJ_F_OBJ_DUP 101 -#define OBJ_F_OBJ_NAME_NEW_INDEX 106 -#define OBJ_F_OBJ_NID2LN 102 -#define OBJ_F_OBJ_NID2OBJ 103 -#define OBJ_F_OBJ_NID2SN 104 - -/* Reason codes. */ -#define OBJ_R_MALLOC_FAILURE 100 -#define OBJ_R_UNKNOWN_NID 101 - -#ifdef __cplusplus -} -#endif -#endif diff --git a/src/plugins/ftp/openssl/opensslconf.h b/src/plugins/ftp/openssl/opensslconf.h deleted file mode 100644 index f07a711c..00000000 --- a/src/plugins/ftp/openssl/opensslconf.h +++ /dev/null @@ -1,235 +0,0 @@ -/* opensslconf.h */ -/* WARNING: Generated automatically from opensslconf.h.in by Configure. */ - -/* OpenSSL was configured with the following options: */ -#ifndef OPENSSL_SYSNAME_WIN64A -# define OPENSSL_SYSNAME_WIN64A -#endif -#ifndef OPENSSL_DOING_MAKEDEPEND - - -#ifndef OPENSSL_NO_EC_NISTP_64_GCC_128 -# define OPENSSL_NO_EC_NISTP_64_GCC_128 -#endif -#ifndef OPENSSL_NO_GMP -# define OPENSSL_NO_GMP -#endif -#ifndef OPENSSL_NO_JPAKE -# define OPENSSL_NO_JPAKE -#endif -#ifndef OPENSSL_NO_KRB5 -# define OPENSSL_NO_KRB5 -#endif -#ifndef OPENSSL_NO_MD2 -# define OPENSSL_NO_MD2 -#endif -#ifndef OPENSSL_NO_RC5 -# define OPENSSL_NO_RC5 -#endif -#ifndef OPENSSL_NO_RFC3779 -# define OPENSSL_NO_RFC3779 -#endif -#ifndef OPENSSL_NO_SCTP -# define OPENSSL_NO_SCTP -#endif -#ifndef OPENSSL_NO_STORE -# define OPENSSL_NO_STORE -#endif - -#endif /* OPENSSL_DOING_MAKEDEPEND */ - -#ifndef OPENSSL_THREADS -# define OPENSSL_THREADS -#endif - -/* The OPENSSL_NO_* macros are also defined as NO_* if the application - asks for it. This is a transient feature that is provided for those - who haven't had the time to do the appropriate changes in their - applications. */ -#ifdef OPENSSL_ALGORITHM_DEFINES -# if defined(OPENSSL_NO_EC_NISTP_64_GCC_128) && !defined(NO_EC_NISTP_64_GCC_128) -# define NO_EC_NISTP_64_GCC_128 -# endif -# if defined(OPENSSL_NO_GMP) && !defined(NO_GMP) -# define NO_GMP -# endif -# if defined(OPENSSL_NO_JPAKE) && !defined(NO_JPAKE) -# define NO_JPAKE -# endif -# if defined(OPENSSL_NO_KRB5) && !defined(NO_KRB5) -# define NO_KRB5 -# endif -# if defined(OPENSSL_NO_MD2) && !defined(NO_MD2) -# define NO_MD2 -# endif -# if defined(OPENSSL_NO_RC5) && !defined(NO_RC5) -# define NO_RC5 -# endif -# if defined(OPENSSL_NO_RFC3779) && !defined(NO_RFC3779) -# define NO_RFC3779 -# endif -# if defined(OPENSSL_NO_SCTP) && !defined(NO_SCTP) -# define NO_SCTP -# endif -# if defined(OPENSSL_NO_STORE) && !defined(NO_STORE) -# define NO_STORE -# endif -#endif - -#define OPENSSL_CPUID_OBJ - -/* crypto/opensslconf.h.in */ - -/* Generate 80386 code? */ -#undef I386_ONLY - -#if !(defined(VMS) || defined(__VMS)) /* VMS uses logical names instead */ -#if defined(HEADER_CRYPTLIB_H) && !defined(OPENSSLDIR) -#define ENGINESDIR "/usr/local/ssl/lib/engines" -#define OPENSSLDIR "/usr/local/ssl" -#endif -#endif - -#undef OPENSSL_UNISTD -#define OPENSSL_UNISTD - -#undef OPENSSL_EXPORT_VAR_AS_FUNCTION -#define OPENSSL_EXPORT_VAR_AS_FUNCTION - -#if defined(HEADER_IDEA_H) && !defined(IDEA_INT) -#define IDEA_INT unsigned int -#endif - -#if defined(HEADER_MD2_H) && !defined(MD2_INT) -#define MD2_INT unsigned int -#endif - -#if defined(HEADER_RC2_H) && !defined(RC2_INT) -/* I need to put in a mod for the alpha - eay */ -#define RC2_INT unsigned int -#endif - -#if defined(HEADER_RC4_H) -#if !defined(RC4_INT) -/* using int types make the structure larger but make the code faster - * on most boxes I have tested - up to %20 faster. */ -/* - * I don't know what does "most" mean, but declaring "int" is a must on: - * - Intel P6 because partial register stalls are very expensive; - * - elder Alpha because it lacks byte load/store instructions; - */ -#define RC4_INT unsigned int -#endif -#if !defined(RC4_CHUNK) -/* - * This enables code handling data aligned at natural CPU word - * boundary. See crypto/rc4/rc4_enc.c for further details. - */ -#define RC4_CHUNK unsigned long long -#endif -#endif - -#if (defined(HEADER_NEW_DES_H) || defined(HEADER_DES_H)) && !defined(DES_LONG) -/* If this is set to 'unsigned int' on a DEC Alpha, this gives about a - * %20 speed up (longs are 8 bytes, int's are 4). */ -#ifndef DES_LONG -#define DES_LONG unsigned int -#endif -#endif - -#if defined(HEADER_BN_H) && !defined(CONFIG_HEADER_BN_H) -#define CONFIG_HEADER_BN_H -#undef BN_LLONG - -/* Should we define BN_DIV2W here? */ - -/* Only one for the following should be defined */ -#undef SIXTY_FOUR_BIT_LONG -#define SIXTY_FOUR_BIT -#undef THIRTY_TWO_BIT -#endif - -#if defined(HEADER_RC4_LOCL_H) && !defined(CONFIG_HEADER_RC4_LOCL_H) -#define CONFIG_HEADER_RC4_LOCL_H -/* if this is defined data[i] is used instead of *data, this is a %20 - * speedup on x86 */ -#undef RC4_INDEX -#endif - -#if defined(HEADER_BF_LOCL_H) && !defined(CONFIG_HEADER_BF_LOCL_H) -#define CONFIG_HEADER_BF_LOCL_H -#undef BF_PTR -#endif /* HEADER_BF_LOCL_H */ - -#if defined(HEADER_DES_LOCL_H) && !defined(CONFIG_HEADER_DES_LOCL_H) -#define CONFIG_HEADER_DES_LOCL_H -#ifndef DES_DEFAULT_OPTIONS -/* the following is tweaked from a config script, that is why it is a - * protected undef/define */ -#ifndef DES_PTR -#undef DES_PTR -#endif - -/* This helps C compiler generate the correct code for multiple functional - * units. It reduces register dependancies at the expense of 2 more - * registers */ -#ifndef DES_RISC1 -#undef DES_RISC1 -#endif - -#ifndef DES_RISC2 -#undef DES_RISC2 -#endif - -#if defined(DES_RISC1) && defined(DES_RISC2) -YOU SHOULD NOT HAVE BOTH DES_RISC1 AND DES_RISC2 DEFINED!!!!! -#endif - -/* Unroll the inner loop, this sometimes helps, sometimes hinders. - * Very mucy CPU dependant */ -#ifndef DES_UNROLL -#undef DES_UNROLL -#endif - -/* These default values were supplied by - * Peter Gutman - * They are only used if nothing else has been defined */ -#if !defined(DES_PTR) && !defined(DES_RISC1) && !defined(DES_RISC2) && !defined(DES_UNROLL) -/* Special defines which change the way the code is built depending on the - CPU and OS. For SGI machines you can use _MIPS_SZLONG (32 or 64) to find - even newer MIPS CPU's, but at the moment one size fits all for - optimization options. Older Sparc's work better with only UNROLL, but - there's no way to tell at compile time what it is you're running on */ - -#if defined( sun ) /* Newer Sparc's */ -# define DES_PTR -# define DES_RISC1 -# define DES_UNROLL -#elif defined( __ultrix ) /* Older MIPS */ -# define DES_PTR -# define DES_RISC2 -# define DES_UNROLL -#elif defined( __osf1__ ) /* Alpha */ -# define DES_PTR -# define DES_RISC2 -#elif defined ( _AIX ) /* RS6000 */ - /* Unknown */ -#elif defined( __hpux ) /* HP-PA */ - /* Unknown */ -#elif defined( __aux ) /* 68K */ - /* Unknown */ -#elif defined( __dgux ) /* 88K (but P6 in latest boxes) */ -# define DES_UNROLL -#elif defined( __sgi ) /* Newer MIPS */ -# define DES_PTR -# define DES_RISC2 -# define DES_UNROLL -#elif defined(i386) || defined(__i386__) /* x86 boxes, should be gcc */ -# define DES_PTR -# define DES_RISC1 -# define DES_UNROLL -#endif /* Systems-specific speed defines */ -#endif - -#endif /* DES_DEFAULT_OPTIONS */ -#endif /* HEADER_DES_LOCL_H */ diff --git a/src/plugins/ftp/openssl/opensslv.h b/src/plugins/ftp/openssl/opensslv.h deleted file mode 100644 index 71be3590..00000000 --- a/src/plugins/ftp/openssl/opensslv.h +++ /dev/null @@ -1,89 +0,0 @@ -#ifndef HEADER_OPENSSLV_H -#define HEADER_OPENSSLV_H - -/* Numeric release version identifier: - * MNNFFPPS: major minor fix patch status - * The status nibble has one of the values 0 for development, 1 to e for betas - * 1 to 14, and f for release. The patch level is exactly that. - * For example: - * 0.9.3-dev 0x00903000 - * 0.9.3-beta1 0x00903001 - * 0.9.3-beta2-dev 0x00903002 - * 0.9.3-beta2 0x00903002 (same as ...beta2-dev) - * 0.9.3 0x0090300f - * 0.9.3a 0x0090301f - * 0.9.4 0x0090400f - * 1.2.3z 0x102031af - * - * For continuity reasons (because 0.9.5 is already out, and is coded - * 0x00905100), between 0.9.5 and 0.9.6 the coding of the patch level - * part is slightly different, by setting the highest bit. This means - * that 0.9.5a looks like this: 0x0090581f. At 0.9.6, we can start - * with 0x0090600S... - * - * (Prior to 0.9.3-dev a different scheme was used: 0.9.2b is 0x0922.) - * (Prior to 0.9.5a beta1, a different scheme was used: MMNNFFRBB for - * major minor fix final patch/beta) - */ -#define OPENSSL_VERSION_NUMBER 0x1000103fL -#ifdef OPENSSL_FIPS -#define OPENSSL_VERSION_TEXT "OpenSSL 1.0.1c-fips 10 May 2012" -#else -#define OPENSSL_VERSION_TEXT "OpenSSL 1.0.1c 10 May 2012" -#endif -#define OPENSSL_VERSION_PTEXT " part of " OPENSSL_VERSION_TEXT - - -/* The macros below are to be used for shared library (.so, .dll, ...) - * versioning. That kind of versioning works a bit differently between - * operating systems. The most usual scheme is to set a major and a minor - * number, and have the runtime loader check that the major number is equal - * to what it was at application link time, while the minor number has to - * be greater or equal to what it was at application link time. With this - * scheme, the version number is usually part of the file name, like this: - * - * libcrypto.so.0.9 - * - * Some unixen also make a softlink with the major verson number only: - * - * libcrypto.so.0 - * - * On Tru64 and IRIX 6.x it works a little bit differently. There, the - * shared library version is stored in the file, and is actually a series - * of versions, separated by colons. The rightmost version present in the - * library when linking an application is stored in the application to be - * matched at run time. When the application is run, a check is done to - * see if the library version stored in the application matches any of the - * versions in the version string of the library itself. - * This version string can be constructed in any way, depending on what - * kind of matching is desired. However, to implement the same scheme as - * the one used in the other unixen, all compatible versions, from lowest - * to highest, should be part of the string. Consecutive builds would - * give the following versions strings: - * - * 3.0 - * 3.0:3.1 - * 3.0:3.1:3.2 - * 4.0 - * 4.0:4.1 - * - * Notice how version 4 is completely incompatible with version, and - * therefore give the breach you can see. - * - * There may be other schemes as well that I haven't yet discovered. - * - * So, here's the way it works here: first of all, the library version - * number doesn't need at all to match the overall OpenSSL version. - * However, it's nice and more understandable if it actually does. - * The current library version is stored in the macro SHLIB_VERSION_NUMBER, - * which is just a piece of text in the format "M.m.e" (Major, minor, edit). - * For the sake of Tru64, IRIX, and any other OS that behaves in similar ways, - * we need to keep a history of version numbers, which is done in the - * macro SHLIB_VERSION_HISTORY. The numbers are separated by colons and - * should only keep the versions that are binary compatible with the current. - */ -#define SHLIB_VERSION_HISTORY "" -#define SHLIB_VERSION_NUMBER "1.0.0" - - -#endif /* HEADER_OPENSSLV_H */ diff --git a/src/plugins/ftp/openssl/ossl_typ.h b/src/plugins/ftp/openssl/ossl_typ.h deleted file mode 100644 index ea9227f6..00000000 --- a/src/plugins/ftp/openssl/ossl_typ.h +++ /dev/null @@ -1,202 +0,0 @@ -/* ==================================================================== - * Copyright (c) 1998-2001 The OpenSSL Project. All rights reserved. - * - * 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. All advertising materials mentioning features or use of this - * software must display the following acknowledgment: - * "This product includes software developed by the OpenSSL Project - * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" - * - * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For written permission, please contact - * openssl-core@openssl.org. - * - * 5. Products derived from this software may not be called "OpenSSL" - * nor may "OpenSSL" appear in their names without prior written - * permission of the OpenSSL Project. - * - * 6. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by the OpenSSL Project - * for use in the OpenSSL Toolkit (http://www.openssl.org/)" - * - * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY - * EXPRESSED 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 OpenSSL PROJECT OR - * ITS 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. - * ==================================================================== - * - * This product includes cryptographic software written by Eric Young - * (eay@cryptsoft.com). This product includes software written by Tim - * Hudson (tjh@cryptsoft.com). - * - */ - -#ifndef HEADER_OPENSSL_TYPES_H -#define HEADER_OPENSSL_TYPES_H - -#include - -#ifdef NO_ASN1_TYPEDEFS -#define ASN1_INTEGER ASN1_STRING -#define ASN1_ENUMERATED ASN1_STRING -#define ASN1_BIT_STRING ASN1_STRING -#define ASN1_OCTET_STRING ASN1_STRING -#define ASN1_PRINTABLESTRING ASN1_STRING -#define ASN1_T61STRING ASN1_STRING -#define ASN1_IA5STRING ASN1_STRING -#define ASN1_UTCTIME ASN1_STRING -#define ASN1_GENERALIZEDTIME ASN1_STRING -#define ASN1_TIME ASN1_STRING -#define ASN1_GENERALSTRING ASN1_STRING -#define ASN1_UNIVERSALSTRING ASN1_STRING -#define ASN1_BMPSTRING ASN1_STRING -#define ASN1_VISIBLESTRING ASN1_STRING -#define ASN1_UTF8STRING ASN1_STRING -#define ASN1_BOOLEAN int -#define ASN1_NULL int -#else -typedef struct asn1_string_st ASN1_INTEGER; -typedef struct asn1_string_st ASN1_ENUMERATED; -typedef struct asn1_string_st ASN1_BIT_STRING; -typedef struct asn1_string_st ASN1_OCTET_STRING; -typedef struct asn1_string_st ASN1_PRINTABLESTRING; -typedef struct asn1_string_st ASN1_T61STRING; -typedef struct asn1_string_st ASN1_IA5STRING; -typedef struct asn1_string_st ASN1_GENERALSTRING; -typedef struct asn1_string_st ASN1_UNIVERSALSTRING; -typedef struct asn1_string_st ASN1_BMPSTRING; -typedef struct asn1_string_st ASN1_UTCTIME; -typedef struct asn1_string_st ASN1_TIME; -typedef struct asn1_string_st ASN1_GENERALIZEDTIME; -typedef struct asn1_string_st ASN1_VISIBLESTRING; -typedef struct asn1_string_st ASN1_UTF8STRING; -typedef struct asn1_string_st ASN1_STRING; -typedef int ASN1_BOOLEAN; -typedef int ASN1_NULL; -#endif - -typedef struct ASN1_ITEM_st ASN1_ITEM; -typedef struct asn1_pctx_st ASN1_PCTX; - -#ifdef OPENSSL_SYS_WIN32 -#undef X509_NAME -#undef X509_EXTENSIONS -#undef X509_CERT_PAIR -#undef PKCS7_ISSUER_AND_SERIAL -#undef OCSP_REQUEST -#undef OCSP_RESPONSE -#endif - -#ifdef BIGNUM -#undef BIGNUM -#endif -typedef struct bignum_st BIGNUM; -typedef struct bignum_ctx BN_CTX; -typedef struct bn_blinding_st BN_BLINDING; -typedef struct bn_mont_ctx_st BN_MONT_CTX; -typedef struct bn_recp_ctx_st BN_RECP_CTX; -typedef struct bn_gencb_st BN_GENCB; - -typedef struct buf_mem_st BUF_MEM; - -typedef struct evp_cipher_st EVP_CIPHER; -typedef struct evp_cipher_ctx_st EVP_CIPHER_CTX; -typedef struct env_md_st EVP_MD; -typedef struct env_md_ctx_st EVP_MD_CTX; -typedef struct evp_pkey_st EVP_PKEY; - -typedef struct evp_pkey_asn1_method_st EVP_PKEY_ASN1_METHOD; - -typedef struct evp_pkey_method_st EVP_PKEY_METHOD; -typedef struct evp_pkey_ctx_st EVP_PKEY_CTX; - -typedef struct dh_st DH; -typedef struct dh_method DH_METHOD; - -typedef struct dsa_st DSA; -typedef struct dsa_method DSA_METHOD; - -typedef struct rsa_st RSA; -typedef struct rsa_meth_st RSA_METHOD; - -typedef struct rand_meth_st RAND_METHOD; - -typedef struct ecdh_method ECDH_METHOD; -typedef struct ecdsa_method ECDSA_METHOD; - -typedef struct x509_st X509; -typedef struct X509_algor_st X509_ALGOR; -typedef struct X509_crl_st X509_CRL; -typedef struct x509_crl_method_st X509_CRL_METHOD; -typedef struct x509_revoked_st X509_REVOKED; -typedef struct X509_name_st X509_NAME; -typedef struct X509_pubkey_st X509_PUBKEY; -typedef struct x509_store_st X509_STORE; -typedef struct x509_store_ctx_st X509_STORE_CTX; - -typedef struct pkcs8_priv_key_info_st PKCS8_PRIV_KEY_INFO; - -typedef struct v3_ext_ctx X509V3_CTX; -typedef struct conf_st CONF; - -typedef struct store_st STORE; -typedef struct store_method_st STORE_METHOD; - -typedef struct ui_st UI; -typedef struct ui_method_st UI_METHOD; - -typedef struct st_ERR_FNS ERR_FNS; - -typedef struct engine_st ENGINE; -typedef struct ssl_st SSL; -typedef struct ssl_ctx_st SSL_CTX; - -typedef struct X509_POLICY_NODE_st X509_POLICY_NODE; -typedef struct X509_POLICY_LEVEL_st X509_POLICY_LEVEL; -typedef struct X509_POLICY_TREE_st X509_POLICY_TREE; -typedef struct X509_POLICY_CACHE_st X509_POLICY_CACHE; - -typedef struct AUTHORITY_KEYID_st AUTHORITY_KEYID; -typedef struct DIST_POINT_st DIST_POINT; -typedef struct ISSUING_DIST_POINT_st ISSUING_DIST_POINT; -typedef struct NAME_CONSTRAINTS_st NAME_CONSTRAINTS; - - /* If placed in pkcs12.h, we end up with a circular depency with pkcs7.h */ -#define DECLARE_PKCS12_STACK_OF(type) /* Nothing */ -#define IMPLEMENT_PKCS12_STACK_OF(type) /* Nothing */ - -typedef struct crypto_ex_data_st CRYPTO_EX_DATA; -/* Callback types for crypto.h */ -typedef int CRYPTO_EX_new(void *parent, void *ptr, CRYPTO_EX_DATA *ad, - int idx, long argl, void *argp); -typedef void CRYPTO_EX_free(void *parent, void *ptr, CRYPTO_EX_DATA *ad, - int idx, long argl, void *argp); -typedef int CRYPTO_EX_dup(CRYPTO_EX_DATA *to, CRYPTO_EX_DATA *from, void *from_d, - int idx, long argl, void *argp); - -typedef struct ocsp_req_ctx_st OCSP_REQ_CTX; -typedef struct ocsp_response_st OCSP_RESPONSE; -typedef struct ocsp_responder_id_st OCSP_RESPID; - -#endif /* def HEADER_OPENSSL_TYPES_H */ diff --git a/src/plugins/ftp/openssl/pkcs7.h b/src/plugins/ftp/openssl/pkcs7.h deleted file mode 100644 index e4d44319..00000000 --- a/src/plugins/ftp/openssl/pkcs7.h +++ /dev/null @@ -1,499 +0,0 @@ -/* crypto/pkcs7/pkcs7.h */ -/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) - * All rights reserved. - * - * This package is an SSL implementation written - * by Eric Young (eay@cryptsoft.com). - * The implementation was written so as to conform with Netscapes SSL. - * - * This library is free for commercial and non-commercial use as long as - * the following conditions are aheared to. The following conditions - * apply to all code found in this distribution, be it the RC4, RSA, - * lhash, DES, etc., code; not just the SSL code. The SSL documentation - * included with this distribution is covered by the same copyright terms - * except that the holder is Tim Hudson (tjh@cryptsoft.com). - * - * Copyright remains Eric Young's, and as such any Copyright notices in - * the code are not to be removed. - * If this package is used in a product, Eric Young should be given attribution - * as the author of the parts of the library used. - * This can be in the form of a textual message at program startup or - * in documentation (online or textual) provided with the package. - * - * 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 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. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * "This product includes cryptographic software written by - * Eric Young (eay@cryptsoft.com)" - * The word 'cryptographic' can be left out if the rouines from the library - * being used are not cryptographic related :-). - * 4. If you include any Windows specific code (or a derivative thereof) from - * the apps directory (application code) you must include an acknowledgement: - * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" - * - * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``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 AUTHOR 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. - * - * The licence and distribution terms for any publically available version or - * derivative of this code cannot be changed. i.e. this code cannot simply be - * copied and put under another distribution licence - * [including the GNU Public Licence.] - */ - -#ifndef HEADER_PKCS7_H -#define HEADER_PKCS7_H - -#include -#include -#include - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef OPENSSL_SYS_WIN32 -/* Under Win32 thes are defined in wincrypt.h */ -#undef PKCS7_ISSUER_AND_SERIAL -#undef PKCS7_SIGNER_INFO -#endif - -/* -Encryption_ID DES-CBC -Digest_ID MD5 -Digest_Encryption_ID rsaEncryption -Key_Encryption_ID rsaEncryption -*/ - -typedef struct pkcs7_issuer_and_serial_st - { - X509_NAME *issuer; - ASN1_INTEGER *serial; - } PKCS7_ISSUER_AND_SERIAL; - -typedef struct pkcs7_signer_info_st - { - ASN1_INTEGER *version; /* version 1 */ - PKCS7_ISSUER_AND_SERIAL *issuer_and_serial; - X509_ALGOR *digest_alg; - STACK_OF(X509_ATTRIBUTE) *auth_attr; /* [ 0 ] */ - X509_ALGOR *digest_enc_alg; - ASN1_OCTET_STRING *enc_digest; - STACK_OF(X509_ATTRIBUTE) *unauth_attr; /* [ 1 ] */ - - /* The private key to sign with */ - EVP_PKEY *pkey; - } PKCS7_SIGNER_INFO; - -DECLARE_STACK_OF(PKCS7_SIGNER_INFO) -DECLARE_ASN1_SET_OF(PKCS7_SIGNER_INFO) - -typedef struct pkcs7_recip_info_st - { - ASN1_INTEGER *version; /* version 0 */ - PKCS7_ISSUER_AND_SERIAL *issuer_and_serial; - X509_ALGOR *key_enc_algor; - ASN1_OCTET_STRING *enc_key; - X509 *cert; /* get the pub-key from this */ - } PKCS7_RECIP_INFO; - -DECLARE_STACK_OF(PKCS7_RECIP_INFO) -DECLARE_ASN1_SET_OF(PKCS7_RECIP_INFO) - -typedef struct pkcs7_signed_st - { - ASN1_INTEGER *version; /* version 1 */ - STACK_OF(X509_ALGOR) *md_algs; /* md used */ - STACK_OF(X509) *cert; /* [ 0 ] */ - STACK_OF(X509_CRL) *crl; /* [ 1 ] */ - STACK_OF(PKCS7_SIGNER_INFO) *signer_info; - - struct pkcs7_st *contents; - } PKCS7_SIGNED; -/* The above structure is very very similar to PKCS7_SIGN_ENVELOPE. - * How about merging the two */ - -typedef struct pkcs7_enc_content_st - { - ASN1_OBJECT *content_type; - X509_ALGOR *algorithm; - ASN1_OCTET_STRING *enc_data; /* [ 0 ] */ - const EVP_CIPHER *cipher; - } PKCS7_ENC_CONTENT; - -typedef struct pkcs7_enveloped_st - { - ASN1_INTEGER *version; /* version 0 */ - STACK_OF(PKCS7_RECIP_INFO) *recipientinfo; - PKCS7_ENC_CONTENT *enc_data; - } PKCS7_ENVELOPE; - -typedef struct pkcs7_signedandenveloped_st - { - ASN1_INTEGER *version; /* version 1 */ - STACK_OF(X509_ALGOR) *md_algs; /* md used */ - STACK_OF(X509) *cert; /* [ 0 ] */ - STACK_OF(X509_CRL) *crl; /* [ 1 ] */ - STACK_OF(PKCS7_SIGNER_INFO) *signer_info; - - PKCS7_ENC_CONTENT *enc_data; - STACK_OF(PKCS7_RECIP_INFO) *recipientinfo; - } PKCS7_SIGN_ENVELOPE; - -typedef struct pkcs7_digest_st - { - ASN1_INTEGER *version; /* version 0 */ - X509_ALGOR *md; /* md used */ - struct pkcs7_st *contents; - ASN1_OCTET_STRING *digest; - } PKCS7_DIGEST; - -typedef struct pkcs7_encrypted_st - { - ASN1_INTEGER *version; /* version 0 */ - PKCS7_ENC_CONTENT *enc_data; - } PKCS7_ENCRYPT; - -typedef struct pkcs7_st - { - /* The following is non NULL if it contains ASN1 encoding of - * this structure */ - unsigned char *asn1; - long length; - -#define PKCS7_S_HEADER 0 -#define PKCS7_S_BODY 1 -#define PKCS7_S_TAIL 2 - int state; /* used during processing */ - - int detached; - - ASN1_OBJECT *type; - /* content as defined by the type */ - /* all encryption/message digests are applied to the 'contents', - * leaving out the 'type' field. */ - union { - char *ptr; - - /* NID_pkcs7_data */ - ASN1_OCTET_STRING *data; - - /* NID_pkcs7_signed */ - PKCS7_SIGNED *sign; - - /* NID_pkcs7_enveloped */ - PKCS7_ENVELOPE *enveloped; - - /* NID_pkcs7_signedAndEnveloped */ - PKCS7_SIGN_ENVELOPE *signed_and_enveloped; - - /* NID_pkcs7_digest */ - PKCS7_DIGEST *digest; - - /* NID_pkcs7_encrypted */ - PKCS7_ENCRYPT *encrypted; - - /* Anything else */ - ASN1_TYPE *other; - } d; - } PKCS7; - -DECLARE_STACK_OF(PKCS7) -DECLARE_ASN1_SET_OF(PKCS7) -DECLARE_PKCS12_STACK_OF(PKCS7) - -#define PKCS7_OP_SET_DETACHED_SIGNATURE 1 -#define PKCS7_OP_GET_DETACHED_SIGNATURE 2 - -#define PKCS7_get_signed_attributes(si) ((si)->auth_attr) -#define PKCS7_get_attributes(si) ((si)->unauth_attr) - -#define PKCS7_type_is_signed(a) (OBJ_obj2nid((a)->type) == NID_pkcs7_signed) -#define PKCS7_type_is_encrypted(a) (OBJ_obj2nid((a)->type) == NID_pkcs7_encrypted) -#define PKCS7_type_is_enveloped(a) (OBJ_obj2nid((a)->type) == NID_pkcs7_enveloped) -#define PKCS7_type_is_signedAndEnveloped(a) \ - (OBJ_obj2nid((a)->type) == NID_pkcs7_signedAndEnveloped) -#define PKCS7_type_is_data(a) (OBJ_obj2nid((a)->type) == NID_pkcs7_data) -#define PKCS7_type_is_digest(a) (OBJ_obj2nid((a)->type) == NID_pkcs7_digest) -#define PKCS7_type_is_encrypted(a) \ - (OBJ_obj2nid((a)->type) == NID_pkcs7_encrypted) - -#define PKCS7_type_is_digest(a) (OBJ_obj2nid((a)->type) == NID_pkcs7_digest) - -#define PKCS7_set_detached(p,v) \ - PKCS7_ctrl(p,PKCS7_OP_SET_DETACHED_SIGNATURE,v,NULL) -#define PKCS7_get_detached(p) \ - PKCS7_ctrl(p,PKCS7_OP_GET_DETACHED_SIGNATURE,0,NULL) - -#define PKCS7_is_detached(p7) (PKCS7_type_is_signed(p7) && PKCS7_get_detached(p7)) - -/* S/MIME related flags */ - -#define PKCS7_TEXT 0x1 -#define PKCS7_NOCERTS 0x2 -#define PKCS7_NOSIGS 0x4 -#define PKCS7_NOCHAIN 0x8 -#define PKCS7_NOINTERN 0x10 -#define PKCS7_NOVERIFY 0x20 -#define PKCS7_DETACHED 0x40 -#define PKCS7_BINARY 0x80 -#define PKCS7_NOATTR 0x100 -#define PKCS7_NOSMIMECAP 0x200 -#define PKCS7_NOOLDMIMETYPE 0x400 -#define PKCS7_CRLFEOL 0x800 -#define PKCS7_STREAM 0x1000 -#define PKCS7_NOCRL 0x2000 -#define PKCS7_PARTIAL 0x4000 -#define PKCS7_REUSE_DIGEST 0x8000 - -/* Flags: for compatibility with older code */ - -#define SMIME_TEXT PKCS7_TEXT -#define SMIME_NOCERTS PKCS7_NOCERTS -#define SMIME_NOSIGS PKCS7_NOSIGS -#define SMIME_NOCHAIN PKCS7_NOCHAIN -#define SMIME_NOINTERN PKCS7_NOINTERN -#define SMIME_NOVERIFY PKCS7_NOVERIFY -#define SMIME_DETACHED PKCS7_DETACHED -#define SMIME_BINARY PKCS7_BINARY -#define SMIME_NOATTR PKCS7_NOATTR - -DECLARE_ASN1_FUNCTIONS(PKCS7_ISSUER_AND_SERIAL) - -int PKCS7_ISSUER_AND_SERIAL_digest(PKCS7_ISSUER_AND_SERIAL *data,const EVP_MD *type, - unsigned char *md,unsigned int *len); -#ifndef OPENSSL_NO_FP_API -PKCS7 *d2i_PKCS7_fp(FILE *fp,PKCS7 **p7); -int i2d_PKCS7_fp(FILE *fp,PKCS7 *p7); -#endif -PKCS7 *PKCS7_dup(PKCS7 *p7); -PKCS7 *d2i_PKCS7_bio(BIO *bp,PKCS7 **p7); -int i2d_PKCS7_bio(BIO *bp,PKCS7 *p7); -int i2d_PKCS7_bio_stream(BIO *out, PKCS7 *p7, BIO *in, int flags); -int PEM_write_bio_PKCS7_stream(BIO *out, PKCS7 *p7, BIO *in, int flags); - -DECLARE_ASN1_FUNCTIONS(PKCS7_SIGNER_INFO) -DECLARE_ASN1_FUNCTIONS(PKCS7_RECIP_INFO) -DECLARE_ASN1_FUNCTIONS(PKCS7_SIGNED) -DECLARE_ASN1_FUNCTIONS(PKCS7_ENC_CONTENT) -DECLARE_ASN1_FUNCTIONS(PKCS7_ENVELOPE) -DECLARE_ASN1_FUNCTIONS(PKCS7_SIGN_ENVELOPE) -DECLARE_ASN1_FUNCTIONS(PKCS7_DIGEST) -DECLARE_ASN1_FUNCTIONS(PKCS7_ENCRYPT) -DECLARE_ASN1_FUNCTIONS(PKCS7) - -DECLARE_ASN1_ITEM(PKCS7_ATTR_SIGN) -DECLARE_ASN1_ITEM(PKCS7_ATTR_VERIFY) - -DECLARE_ASN1_NDEF_FUNCTION(PKCS7) -DECLARE_ASN1_PRINT_FUNCTION(PKCS7) - -long PKCS7_ctrl(PKCS7 *p7, int cmd, long larg, char *parg); - -int PKCS7_set_type(PKCS7 *p7, int type); -int PKCS7_set0_type_other(PKCS7 *p7, int type, ASN1_TYPE *other); -int PKCS7_set_content(PKCS7 *p7, PKCS7 *p7_data); -int PKCS7_SIGNER_INFO_set(PKCS7_SIGNER_INFO *p7i, X509 *x509, EVP_PKEY *pkey, - const EVP_MD *dgst); -int PKCS7_SIGNER_INFO_sign(PKCS7_SIGNER_INFO *si); -int PKCS7_add_signer(PKCS7 *p7, PKCS7_SIGNER_INFO *p7i); -int PKCS7_add_certificate(PKCS7 *p7, X509 *x509); -int PKCS7_add_crl(PKCS7 *p7, X509_CRL *x509); -int PKCS7_content_new(PKCS7 *p7, int nid); -int PKCS7_dataVerify(X509_STORE *cert_store, X509_STORE_CTX *ctx, - BIO *bio, PKCS7 *p7, PKCS7_SIGNER_INFO *si); -int PKCS7_signatureVerify(BIO *bio, PKCS7 *p7, PKCS7_SIGNER_INFO *si, - X509 *x509); - -BIO *PKCS7_dataInit(PKCS7 *p7, BIO *bio); -int PKCS7_dataFinal(PKCS7 *p7, BIO *bio); -BIO *PKCS7_dataDecode(PKCS7 *p7, EVP_PKEY *pkey, BIO *in_bio, X509 *pcert); - - -PKCS7_SIGNER_INFO *PKCS7_add_signature(PKCS7 *p7, X509 *x509, - EVP_PKEY *pkey, const EVP_MD *dgst); -X509 *PKCS7_cert_from_signer_info(PKCS7 *p7, PKCS7_SIGNER_INFO *si); -int PKCS7_set_digest(PKCS7 *p7, const EVP_MD *md); -STACK_OF(PKCS7_SIGNER_INFO) *PKCS7_get_signer_info(PKCS7 *p7); - -PKCS7_RECIP_INFO *PKCS7_add_recipient(PKCS7 *p7, X509 *x509); -void PKCS7_SIGNER_INFO_get0_algs(PKCS7_SIGNER_INFO *si, EVP_PKEY **pk, - X509_ALGOR **pdig, X509_ALGOR **psig); -void PKCS7_RECIP_INFO_get0_alg(PKCS7_RECIP_INFO *ri, X509_ALGOR **penc); -int PKCS7_add_recipient_info(PKCS7 *p7, PKCS7_RECIP_INFO *ri); -int PKCS7_RECIP_INFO_set(PKCS7_RECIP_INFO *p7i, X509 *x509); -int PKCS7_set_cipher(PKCS7 *p7, const EVP_CIPHER *cipher); -int PKCS7_stream(unsigned char ***boundary, PKCS7 *p7); - -PKCS7_ISSUER_AND_SERIAL *PKCS7_get_issuer_and_serial(PKCS7 *p7, int idx); -ASN1_OCTET_STRING *PKCS7_digest_from_attributes(STACK_OF(X509_ATTRIBUTE) *sk); -int PKCS7_add_signed_attribute(PKCS7_SIGNER_INFO *p7si,int nid,int type, - void *data); -int PKCS7_add_attribute (PKCS7_SIGNER_INFO *p7si, int nid, int atrtype, - void *value); -ASN1_TYPE *PKCS7_get_attribute(PKCS7_SIGNER_INFO *si, int nid); -ASN1_TYPE *PKCS7_get_signed_attribute(PKCS7_SIGNER_INFO *si, int nid); -int PKCS7_set_signed_attributes(PKCS7_SIGNER_INFO *p7si, - STACK_OF(X509_ATTRIBUTE) *sk); -int PKCS7_set_attributes(PKCS7_SIGNER_INFO *p7si,STACK_OF(X509_ATTRIBUTE) *sk); - - -PKCS7 *PKCS7_sign(X509 *signcert, EVP_PKEY *pkey, STACK_OF(X509) *certs, - BIO *data, int flags); - -PKCS7_SIGNER_INFO *PKCS7_sign_add_signer(PKCS7 *p7, - X509 *signcert, EVP_PKEY *pkey, const EVP_MD *md, - int flags); - -int PKCS7_final(PKCS7 *p7, BIO *data, int flags); -int PKCS7_verify(PKCS7 *p7, STACK_OF(X509) *certs, X509_STORE *store, - BIO *indata, BIO *out, int flags); -STACK_OF(X509) *PKCS7_get0_signers(PKCS7 *p7, STACK_OF(X509) *certs, int flags); -PKCS7 *PKCS7_encrypt(STACK_OF(X509) *certs, BIO *in, const EVP_CIPHER *cipher, - int flags); -int PKCS7_decrypt(PKCS7 *p7, EVP_PKEY *pkey, X509 *cert, BIO *data, int flags); - -int PKCS7_add_attrib_smimecap(PKCS7_SIGNER_INFO *si, - STACK_OF(X509_ALGOR) *cap); -STACK_OF(X509_ALGOR) *PKCS7_get_smimecap(PKCS7_SIGNER_INFO *si); -int PKCS7_simple_smimecap(STACK_OF(X509_ALGOR) *sk, int nid, int arg); - -int PKCS7_add_attrib_content_type(PKCS7_SIGNER_INFO *si, ASN1_OBJECT *coid); -int PKCS7_add0_attrib_signing_time(PKCS7_SIGNER_INFO *si, ASN1_TIME *t); -int PKCS7_add1_attrib_digest(PKCS7_SIGNER_INFO *si, - const unsigned char *md, int mdlen); - -int SMIME_write_PKCS7(BIO *bio, PKCS7 *p7, BIO *data, int flags); -PKCS7 *SMIME_read_PKCS7(BIO *bio, BIO **bcont); - -BIO *BIO_new_PKCS7(BIO *out, PKCS7 *p7); - - -/* BEGIN ERROR CODES */ -/* The following lines are auto generated by the script mkerr.pl. Any changes - * made after this point may be overwritten when the script is next run. - */ -void ERR_load_PKCS7_strings(void); - -/* Error codes for the PKCS7 functions. */ - -/* Function codes. */ -#define PKCS7_F_B64_READ_PKCS7 120 -#define PKCS7_F_B64_WRITE_PKCS7 121 -#define PKCS7_F_DO_PKCS7_SIGNED_ATTRIB 136 -#define PKCS7_F_I2D_PKCS7_BIO_STREAM 140 -#define PKCS7_F_PKCS7_ADD0_ATTRIB_SIGNING_TIME 135 -#define PKCS7_F_PKCS7_ADD_ATTRIB_SMIMECAP 118 -#define PKCS7_F_PKCS7_ADD_CERTIFICATE 100 -#define PKCS7_F_PKCS7_ADD_CRL 101 -#define PKCS7_F_PKCS7_ADD_RECIPIENT_INFO 102 -#define PKCS7_F_PKCS7_ADD_SIGNATURE 131 -#define PKCS7_F_PKCS7_ADD_SIGNER 103 -#define PKCS7_F_PKCS7_BIO_ADD_DIGEST 125 -#define PKCS7_F_PKCS7_COPY_EXISTING_DIGEST 138 -#define PKCS7_F_PKCS7_CTRL 104 -#define PKCS7_F_PKCS7_DATADECODE 112 -#define PKCS7_F_PKCS7_DATAFINAL 128 -#define PKCS7_F_PKCS7_DATAINIT 105 -#define PKCS7_F_PKCS7_DATASIGN 106 -#define PKCS7_F_PKCS7_DATAVERIFY 107 -#define PKCS7_F_PKCS7_DECRYPT 114 -#define PKCS7_F_PKCS7_DECRYPT_RINFO 133 -#define PKCS7_F_PKCS7_ENCODE_RINFO 132 -#define PKCS7_F_PKCS7_ENCRYPT 115 -#define PKCS7_F_PKCS7_FINAL 134 -#define PKCS7_F_PKCS7_FIND_DIGEST 127 -#define PKCS7_F_PKCS7_GET0_SIGNERS 124 -#define PKCS7_F_PKCS7_RECIP_INFO_SET 130 -#define PKCS7_F_PKCS7_SET_CIPHER 108 -#define PKCS7_F_PKCS7_SET_CONTENT 109 -#define PKCS7_F_PKCS7_SET_DIGEST 126 -#define PKCS7_F_PKCS7_SET_TYPE 110 -#define PKCS7_F_PKCS7_SIGN 116 -#define PKCS7_F_PKCS7_SIGNATUREVERIFY 113 -#define PKCS7_F_PKCS7_SIGNER_INFO_SET 129 -#define PKCS7_F_PKCS7_SIGNER_INFO_SIGN 139 -#define PKCS7_F_PKCS7_SIGN_ADD_SIGNER 137 -#define PKCS7_F_PKCS7_SIMPLE_SMIMECAP 119 -#define PKCS7_F_PKCS7_VERIFY 117 -#define PKCS7_F_SMIME_READ_PKCS7 122 -#define PKCS7_F_SMIME_TEXT 123 - -/* Reason codes. */ -#define PKCS7_R_CERTIFICATE_VERIFY_ERROR 117 -#define PKCS7_R_CIPHER_HAS_NO_OBJECT_IDENTIFIER 144 -#define PKCS7_R_CIPHER_NOT_INITIALIZED 116 -#define PKCS7_R_CONTENT_AND_DATA_PRESENT 118 -#define PKCS7_R_CTRL_ERROR 152 -#define PKCS7_R_DECODE_ERROR 130 -#define PKCS7_R_DECRYPTED_KEY_IS_WRONG_LENGTH 100 -#define PKCS7_R_DECRYPT_ERROR 119 -#define PKCS7_R_DIGEST_FAILURE 101 -#define PKCS7_R_ENCRYPTION_CTRL_FAILURE 149 -#define PKCS7_R_ENCRYPTION_NOT_SUPPORTED_FOR_THIS_KEY_TYPE 150 -#define PKCS7_R_ERROR_ADDING_RECIPIENT 120 -#define PKCS7_R_ERROR_SETTING_CIPHER 121 -#define PKCS7_R_INVALID_MIME_TYPE 131 -#define PKCS7_R_INVALID_NULL_POINTER 143 -#define PKCS7_R_MIME_NO_CONTENT_TYPE 132 -#define PKCS7_R_MIME_PARSE_ERROR 133 -#define PKCS7_R_MIME_SIG_PARSE_ERROR 134 -#define PKCS7_R_MISSING_CERIPEND_INFO 103 -#define PKCS7_R_NO_CONTENT 122 -#define PKCS7_R_NO_CONTENT_TYPE 135 -#define PKCS7_R_NO_DEFAULT_DIGEST 151 -#define PKCS7_R_NO_MATCHING_DIGEST_TYPE_FOUND 154 -#define PKCS7_R_NO_MULTIPART_BODY_FAILURE 136 -#define PKCS7_R_NO_MULTIPART_BOUNDARY 137 -#define PKCS7_R_NO_RECIPIENT_MATCHES_CERTIFICATE 115 -#define PKCS7_R_NO_RECIPIENT_MATCHES_KEY 146 -#define PKCS7_R_NO_SIGNATURES_ON_DATA 123 -#define PKCS7_R_NO_SIGNERS 142 -#define PKCS7_R_NO_SIG_CONTENT_TYPE 138 -#define PKCS7_R_OPERATION_NOT_SUPPORTED_ON_THIS_TYPE 104 -#define PKCS7_R_PKCS7_ADD_SIGNATURE_ERROR 124 -#define PKCS7_R_PKCS7_ADD_SIGNER_ERROR 153 -#define PKCS7_R_PKCS7_DATAFINAL 126 -#define PKCS7_R_PKCS7_DATAFINAL_ERROR 125 -#define PKCS7_R_PKCS7_DATASIGN 145 -#define PKCS7_R_PKCS7_PARSE_ERROR 139 -#define PKCS7_R_PKCS7_SIG_PARSE_ERROR 140 -#define PKCS7_R_PRIVATE_KEY_DOES_NOT_MATCH_CERTIFICATE 127 -#define PKCS7_R_SIGNATURE_FAILURE 105 -#define PKCS7_R_SIGNER_CERTIFICATE_NOT_FOUND 128 -#define PKCS7_R_SIGNING_CTRL_FAILURE 147 -#define PKCS7_R_SIGNING_NOT_SUPPORTED_FOR_THIS_KEY_TYPE 148 -#define PKCS7_R_SIG_INVALID_MIME_TYPE 141 -#define PKCS7_R_SMIME_TEXT_ERROR 129 -#define PKCS7_R_UNABLE_TO_FIND_CERTIFICATE 106 -#define PKCS7_R_UNABLE_TO_FIND_MEM_BIO 107 -#define PKCS7_R_UNABLE_TO_FIND_MESSAGE_DIGEST 108 -#define PKCS7_R_UNKNOWN_DIGEST_TYPE 109 -#define PKCS7_R_UNKNOWN_OPERATION 110 -#define PKCS7_R_UNSUPPORTED_CIPHER_TYPE 111 -#define PKCS7_R_UNSUPPORTED_CONTENT_TYPE 112 -#define PKCS7_R_WRONG_CONTENT_TYPE 113 -#define PKCS7_R_WRONG_PKCS7_TYPE 114 - -#ifdef __cplusplus -} -#endif -#endif diff --git a/src/plugins/ftp/openssl/rsa.h b/src/plugins/ftp/openssl/rsa.h deleted file mode 100644 index 4814a2fc..00000000 --- a/src/plugins/ftp/openssl/rsa.h +++ /dev/null @@ -1,582 +0,0 @@ -/* crypto/rsa/rsa.h */ -/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) - * All rights reserved. - * - * This package is an SSL implementation written - * by Eric Young (eay@cryptsoft.com). - * The implementation was written so as to conform with Netscapes SSL. - * - * This library is free for commercial and non-commercial use as long as - * the following conditions are aheared to. The following conditions - * apply to all code found in this distribution, be it the RC4, RSA, - * lhash, DES, etc., code; not just the SSL code. The SSL documentation - * included with this distribution is covered by the same copyright terms - * except that the holder is Tim Hudson (tjh@cryptsoft.com). - * - * Copyright remains Eric Young's, and as such any Copyright notices in - * the code are not to be removed. - * If this package is used in a product, Eric Young should be given attribution - * as the author of the parts of the library used. - * This can be in the form of a textual message at program startup or - * in documentation (online or textual) provided with the package. - * - * 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 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. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * "This product includes cryptographic software written by - * Eric Young (eay@cryptsoft.com)" - * The word 'cryptographic' can be left out if the rouines from the library - * being used are not cryptographic related :-). - * 4. If you include any Windows specific code (or a derivative thereof) from - * the apps directory (application code) you must include an acknowledgement: - * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" - * - * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``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 AUTHOR 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. - * - * The licence and distribution terms for any publically available version or - * derivative of this code cannot be changed. i.e. this code cannot simply be - * copied and put under another distribution licence - * [including the GNU Public Licence.] - */ - -#ifndef HEADER_RSA_H -#define HEADER_RSA_H - -#include - -#ifndef OPENSSL_NO_BIO -#include -#endif -#include -#include -#ifndef OPENSSL_NO_DEPRECATED -#include -#endif - -#ifdef OPENSSL_NO_RSA -#error RSA is disabled. -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -/* Declared already in ossl_typ.h */ -/* typedef struct rsa_st RSA; */ -/* typedef struct rsa_meth_st RSA_METHOD; */ - -struct rsa_meth_st - { - const char *name; - int (*rsa_pub_enc)(int flen,const unsigned char *from, - unsigned char *to, - RSA *rsa,int padding); - int (*rsa_pub_dec)(int flen,const unsigned char *from, - unsigned char *to, - RSA *rsa,int padding); - int (*rsa_priv_enc)(int flen,const unsigned char *from, - unsigned char *to, - RSA *rsa,int padding); - int (*rsa_priv_dec)(int flen,const unsigned char *from, - unsigned char *to, - RSA *rsa,int padding); - int (*rsa_mod_exp)(BIGNUM *r0,const BIGNUM *I,RSA *rsa,BN_CTX *ctx); /* Can be null */ - int (*bn_mod_exp)(BIGNUM *r, const BIGNUM *a, const BIGNUM *p, - const BIGNUM *m, BN_CTX *ctx, - BN_MONT_CTX *m_ctx); /* Can be null */ - int (*init)(RSA *rsa); /* called at new */ - int (*finish)(RSA *rsa); /* called at free */ - int flags; /* RSA_METHOD_FLAG_* things */ - char *app_data; /* may be needed! */ -/* New sign and verify functions: some libraries don't allow arbitrary data - * to be signed/verified: this allows them to be used. Note: for this to work - * the RSA_public_decrypt() and RSA_private_encrypt() should *NOT* be used - * RSA_sign(), RSA_verify() should be used instead. Note: for backwards - * compatibility this functionality is only enabled if the RSA_FLAG_SIGN_VER - * option is set in 'flags'. - */ - int (*rsa_sign)(int type, - const unsigned char *m, unsigned int m_length, - unsigned char *sigret, unsigned int *siglen, const RSA *rsa); - int (*rsa_verify)(int dtype, - const unsigned char *m, unsigned int m_length, - const unsigned char *sigbuf, unsigned int siglen, - const RSA *rsa); -/* If this callback is NULL, the builtin software RSA key-gen will be used. This - * is for behavioural compatibility whilst the code gets rewired, but one day - * it would be nice to assume there are no such things as "builtin software" - * implementations. */ - int (*rsa_keygen)(RSA *rsa, int bits, BIGNUM *e, BN_GENCB *cb); - }; - -struct rsa_st - { - /* The first parameter is used to pickup errors where - * this is passed instead of aEVP_PKEY, it is set to 0 */ - int pad; - long version; - const RSA_METHOD *meth; - /* functional reference if 'meth' is ENGINE-provided */ - ENGINE *engine; - BIGNUM *n; - BIGNUM *e; - BIGNUM *d; - BIGNUM *p; - BIGNUM *q; - BIGNUM *dmp1; - BIGNUM *dmq1; - BIGNUM *iqmp; - /* be careful using this if the RSA structure is shared */ - CRYPTO_EX_DATA ex_data; - int references; - int flags; - - /* Used to cache montgomery values */ - BN_MONT_CTX *_method_mod_n; - BN_MONT_CTX *_method_mod_p; - BN_MONT_CTX *_method_mod_q; - - /* all BIGNUM values are actually in the following data, if it is not - * NULL */ - char *bignum_data; - BN_BLINDING *blinding; - BN_BLINDING *mt_blinding; - }; - -#ifndef OPENSSL_RSA_MAX_MODULUS_BITS -# define OPENSSL_RSA_MAX_MODULUS_BITS 16384 -#endif - -#ifndef OPENSSL_RSA_SMALL_MODULUS_BITS -# define OPENSSL_RSA_SMALL_MODULUS_BITS 3072 -#endif -#ifndef OPENSSL_RSA_MAX_PUBEXP_BITS -# define OPENSSL_RSA_MAX_PUBEXP_BITS 64 /* exponent limit enforced for "large" modulus only */ -#endif - -#define RSA_3 0x3L -#define RSA_F4 0x10001L - -#define RSA_METHOD_FLAG_NO_CHECK 0x0001 /* don't check pub/private match */ - -#define RSA_FLAG_CACHE_PUBLIC 0x0002 -#define RSA_FLAG_CACHE_PRIVATE 0x0004 -#define RSA_FLAG_BLINDING 0x0008 -#define RSA_FLAG_THREAD_SAFE 0x0010 -/* This flag means the private key operations will be handled by rsa_mod_exp - * and that they do not depend on the private key components being present: - * for example a key stored in external hardware. Without this flag bn_mod_exp - * gets called when private key components are absent. - */ -#define RSA_FLAG_EXT_PKEY 0x0020 - -/* This flag in the RSA_METHOD enables the new rsa_sign, rsa_verify functions. - */ -#define RSA_FLAG_SIGN_VER 0x0040 - -#define RSA_FLAG_NO_BLINDING 0x0080 /* new with 0.9.6j and 0.9.7b; the built-in - * RSA implementation now uses blinding by - * default (ignoring RSA_FLAG_BLINDING), - * but other engines might not need it - */ -#define RSA_FLAG_NO_CONSTTIME 0x0100 /* new with 0.9.8f; the built-in RSA - * implementation now uses constant time - * operations by default in private key operations, - * e.g., constant time modular exponentiation, - * modular inverse without leaking branches, - * division without leaking branches. This - * flag disables these constant time - * operations and results in faster RSA - * private key operations. - */ -#ifndef OPENSSL_NO_DEPRECATED -#define RSA_FLAG_NO_EXP_CONSTTIME RSA_FLAG_NO_CONSTTIME /* deprecated name for the flag*/ - /* new with 0.9.7h; the built-in RSA - * implementation now uses constant time - * modular exponentiation for secret exponents - * by default. This flag causes the - * faster variable sliding window method to - * be used for all exponents. - */ -#endif - - -#define EVP_PKEY_CTX_set_rsa_padding(ctx, pad) \ - EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_RSA, -1, EVP_PKEY_CTRL_RSA_PADDING, \ - pad, NULL) - -#define EVP_PKEY_CTX_get_rsa_padding(ctx, ppad) \ - EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_RSA, -1, \ - EVP_PKEY_CTRL_GET_RSA_PADDING, 0, ppad) - -#define EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx, len) \ - EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_RSA, \ - (EVP_PKEY_OP_SIGN|EVP_PKEY_OP_VERIFY), \ - EVP_PKEY_CTRL_RSA_PSS_SALTLEN, \ - len, NULL) - -#define EVP_PKEY_CTX_get_rsa_pss_saltlen(ctx, plen) \ - EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_RSA, \ - (EVP_PKEY_OP_SIGN|EVP_PKEY_OP_VERIFY), \ - EVP_PKEY_CTRL_GET_RSA_PSS_SALTLEN, \ - 0, plen) - -#define EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, bits) \ - EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_RSA, EVP_PKEY_OP_KEYGEN, \ - EVP_PKEY_CTRL_RSA_KEYGEN_BITS, bits, NULL) - -#define EVP_PKEY_CTX_set_rsa_keygen_pubexp(ctx, pubexp) \ - EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_RSA, EVP_PKEY_OP_KEYGEN, \ - EVP_PKEY_CTRL_RSA_KEYGEN_PUBEXP, 0, pubexp) - -#define EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, md) \ - EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_RSA, EVP_PKEY_OP_TYPE_SIG, \ - EVP_PKEY_CTRL_RSA_MGF1_MD, 0, (void *)md) - -#define EVP_PKEY_CTX_get_rsa_mgf1_md(ctx, pmd) \ - EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_RSA, EVP_PKEY_OP_TYPE_SIG, \ - EVP_PKEY_CTRL_GET_RSA_MGF1_MD, 0, (void *)pmd) - -#define EVP_PKEY_CTRL_RSA_PADDING (EVP_PKEY_ALG_CTRL + 1) -#define EVP_PKEY_CTRL_RSA_PSS_SALTLEN (EVP_PKEY_ALG_CTRL + 2) - -#define EVP_PKEY_CTRL_RSA_KEYGEN_BITS (EVP_PKEY_ALG_CTRL + 3) -#define EVP_PKEY_CTRL_RSA_KEYGEN_PUBEXP (EVP_PKEY_ALG_CTRL + 4) -#define EVP_PKEY_CTRL_RSA_MGF1_MD (EVP_PKEY_ALG_CTRL + 5) - -#define EVP_PKEY_CTRL_GET_RSA_PADDING (EVP_PKEY_ALG_CTRL + 6) -#define EVP_PKEY_CTRL_GET_RSA_PSS_SALTLEN (EVP_PKEY_ALG_CTRL + 7) -#define EVP_PKEY_CTRL_GET_RSA_MGF1_MD (EVP_PKEY_ALG_CTRL + 8) - -#define RSA_PKCS1_PADDING 1 -#define RSA_SSLV23_PADDING 2 -#define RSA_NO_PADDING 3 -#define RSA_PKCS1_OAEP_PADDING 4 -#define RSA_X931_PADDING 5 -/* EVP_PKEY_ only */ -#define RSA_PKCS1_PSS_PADDING 6 - -#define RSA_PKCS1_PADDING_SIZE 11 - -#define RSA_set_app_data(s,arg) RSA_set_ex_data(s,0,arg) -#define RSA_get_app_data(s) RSA_get_ex_data(s,0) - -RSA * RSA_new(void); -RSA * RSA_new_method(ENGINE *engine); -int RSA_size(const RSA *); - -/* Deprecated version */ -#ifndef OPENSSL_NO_DEPRECATED -RSA * RSA_generate_key(int bits, unsigned long e,void - (*callback)(int,int,void *),void *cb_arg); -#endif /* !defined(OPENSSL_NO_DEPRECATED) */ - -/* New version */ -int RSA_generate_key_ex(RSA *rsa, int bits, BIGNUM *e, BN_GENCB *cb); - -int RSA_check_key(const RSA *); - /* next 4 return -1 on error */ -int RSA_public_encrypt(int flen, const unsigned char *from, - unsigned char *to, RSA *rsa,int padding); -int RSA_private_encrypt(int flen, const unsigned char *from, - unsigned char *to, RSA *rsa,int padding); -int RSA_public_decrypt(int flen, const unsigned char *from, - unsigned char *to, RSA *rsa,int padding); -int RSA_private_decrypt(int flen, const unsigned char *from, - unsigned char *to, RSA *rsa,int padding); -void RSA_free (RSA *r); -/* "up" the RSA object's reference count */ -int RSA_up_ref(RSA *r); - -int RSA_flags(const RSA *r); - -void RSA_set_default_method(const RSA_METHOD *meth); -const RSA_METHOD *RSA_get_default_method(void); -const RSA_METHOD *RSA_get_method(const RSA *rsa); -int RSA_set_method(RSA *rsa, const RSA_METHOD *meth); - -/* This function needs the memory locking malloc callbacks to be installed */ -int RSA_memory_lock(RSA *r); - -/* these are the actual SSLeay RSA functions */ -const RSA_METHOD *RSA_PKCS1_SSLeay(void); - -const RSA_METHOD *RSA_null_method(void); - -DECLARE_ASN1_ENCODE_FUNCTIONS_const(RSA, RSAPublicKey) -DECLARE_ASN1_ENCODE_FUNCTIONS_const(RSA, RSAPrivateKey) - -typedef struct rsa_pss_params_st - { - X509_ALGOR *hashAlgorithm; - X509_ALGOR *maskGenAlgorithm; - ASN1_INTEGER *saltLength; - ASN1_INTEGER *trailerField; - } RSA_PSS_PARAMS; - -DECLARE_ASN1_FUNCTIONS(RSA_PSS_PARAMS) - -#ifndef OPENSSL_NO_FP_API -int RSA_print_fp(FILE *fp, const RSA *r,int offset); -#endif - -#ifndef OPENSSL_NO_BIO -int RSA_print(BIO *bp, const RSA *r,int offset); -#endif - -#ifndef OPENSSL_NO_RC4 -int i2d_RSA_NET(const RSA *a, unsigned char **pp, - int (*cb)(char *buf, int len, const char *prompt, int verify), - int sgckey); -RSA *d2i_RSA_NET(RSA **a, const unsigned char **pp, long length, - int (*cb)(char *buf, int len, const char *prompt, int verify), - int sgckey); - -int i2d_Netscape_RSA(const RSA *a, unsigned char **pp, - int (*cb)(char *buf, int len, const char *prompt, - int verify)); -RSA *d2i_Netscape_RSA(RSA **a, const unsigned char **pp, long length, - int (*cb)(char *buf, int len, const char *prompt, - int verify)); -#endif - -/* The following 2 functions sign and verify a X509_SIG ASN1 object - * inside PKCS#1 padded RSA encryption */ -int RSA_sign(int type, const unsigned char *m, unsigned int m_length, - unsigned char *sigret, unsigned int *siglen, RSA *rsa); -int RSA_verify(int type, const unsigned char *m, unsigned int m_length, - const unsigned char *sigbuf, unsigned int siglen, RSA *rsa); - -/* The following 2 function sign and verify a ASN1_OCTET_STRING - * object inside PKCS#1 padded RSA encryption */ -int RSA_sign_ASN1_OCTET_STRING(int type, - const unsigned char *m, unsigned int m_length, - unsigned char *sigret, unsigned int *siglen, RSA *rsa); -int RSA_verify_ASN1_OCTET_STRING(int type, - const unsigned char *m, unsigned int m_length, - unsigned char *sigbuf, unsigned int siglen, RSA *rsa); - -int RSA_blinding_on(RSA *rsa, BN_CTX *ctx); -void RSA_blinding_off(RSA *rsa); -BN_BLINDING *RSA_setup_blinding(RSA *rsa, BN_CTX *ctx); - -int RSA_padding_add_PKCS1_type_1(unsigned char *to,int tlen, - const unsigned char *f,int fl); -int RSA_padding_check_PKCS1_type_1(unsigned char *to,int tlen, - const unsigned char *f,int fl,int rsa_len); -int RSA_padding_add_PKCS1_type_2(unsigned char *to,int tlen, - const unsigned char *f,int fl); -int RSA_padding_check_PKCS1_type_2(unsigned char *to,int tlen, - const unsigned char *f,int fl,int rsa_len); -int PKCS1_MGF1(unsigned char *mask, long len, - const unsigned char *seed, long seedlen, const EVP_MD *dgst); -int RSA_padding_add_PKCS1_OAEP(unsigned char *to,int tlen, - const unsigned char *f,int fl, - const unsigned char *p,int pl); -int RSA_padding_check_PKCS1_OAEP(unsigned char *to,int tlen, - const unsigned char *f,int fl,int rsa_len, - const unsigned char *p,int pl); -int RSA_padding_add_SSLv23(unsigned char *to,int tlen, - const unsigned char *f,int fl); -int RSA_padding_check_SSLv23(unsigned char *to,int tlen, - const unsigned char *f,int fl,int rsa_len); -int RSA_padding_add_none(unsigned char *to,int tlen, - const unsigned char *f,int fl); -int RSA_padding_check_none(unsigned char *to,int tlen, - const unsigned char *f,int fl,int rsa_len); -int RSA_padding_add_X931(unsigned char *to,int tlen, - const unsigned char *f,int fl); -int RSA_padding_check_X931(unsigned char *to,int tlen, - const unsigned char *f,int fl,int rsa_len); -int RSA_X931_hash_id(int nid); - -int RSA_verify_PKCS1_PSS(RSA *rsa, const unsigned char *mHash, - const EVP_MD *Hash, const unsigned char *EM, int sLen); -int RSA_padding_add_PKCS1_PSS(RSA *rsa, unsigned char *EM, - const unsigned char *mHash, - const EVP_MD *Hash, int sLen); - -int RSA_verify_PKCS1_PSS_mgf1(RSA *rsa, const unsigned char *mHash, - const EVP_MD *Hash, const EVP_MD *mgf1Hash, - const unsigned char *EM, int sLen); - -int RSA_padding_add_PKCS1_PSS_mgf1(RSA *rsa, unsigned char *EM, - const unsigned char *mHash, - const EVP_MD *Hash, const EVP_MD *mgf1Hash, int sLen); - -int RSA_get_ex_new_index(long argl, void *argp, CRYPTO_EX_new *new_func, - CRYPTO_EX_dup *dup_func, CRYPTO_EX_free *free_func); -int RSA_set_ex_data(RSA *r,int idx,void *arg); -void *RSA_get_ex_data(const RSA *r, int idx); - -RSA *RSAPublicKey_dup(RSA *rsa); -RSA *RSAPrivateKey_dup(RSA *rsa); - -/* If this flag is set the RSA method is FIPS compliant and can be used - * in FIPS mode. This is set in the validated module method. If an - * application sets this flag in its own methods it is its responsibility - * to ensure the result is compliant. - */ - -#define RSA_FLAG_FIPS_METHOD 0x0400 - -/* If this flag is set the operations normally disabled in FIPS mode are - * permitted it is then the applications responsibility to ensure that the - * usage is compliant. - */ - -#define RSA_FLAG_NON_FIPS_ALLOW 0x0400 -/* Application has decided PRNG is good enough to generate a key: don't - * check. - */ -#define RSA_FLAG_CHECKED 0x0800 - -/* BEGIN ERROR CODES */ -/* The following lines are auto generated by the script mkerr.pl. Any changes - * made after this point may be overwritten when the script is next run. - */ -void ERR_load_RSA_strings(void); - -/* Error codes for the RSA functions. */ - -/* Function codes. */ -#define RSA_F_CHECK_PADDING_MD 140 -#define RSA_F_DO_RSA_PRINT 146 -#define RSA_F_INT_RSA_VERIFY 145 -#define RSA_F_MEMORY_LOCK 100 -#define RSA_F_OLD_RSA_PRIV_DECODE 147 -#define RSA_F_PKEY_RSA_CTRL 143 -#define RSA_F_PKEY_RSA_CTRL_STR 144 -#define RSA_F_PKEY_RSA_SIGN 142 -#define RSA_F_PKEY_RSA_VERIFY 154 -#define RSA_F_PKEY_RSA_VERIFYRECOVER 141 -#define RSA_F_RSA_BUILTIN_KEYGEN 129 -#define RSA_F_RSA_CHECK_KEY 123 -#define RSA_F_RSA_EAY_PRIVATE_DECRYPT 101 -#define RSA_F_RSA_EAY_PRIVATE_ENCRYPT 102 -#define RSA_F_RSA_EAY_PUBLIC_DECRYPT 103 -#define RSA_F_RSA_EAY_PUBLIC_ENCRYPT 104 -#define RSA_F_RSA_GENERATE_KEY 105 -#define RSA_F_RSA_GENERATE_KEY_EX 155 -#define RSA_F_RSA_ITEM_VERIFY 156 -#define RSA_F_RSA_MEMORY_LOCK 130 -#define RSA_F_RSA_NEW_METHOD 106 -#define RSA_F_RSA_NULL 124 -#define RSA_F_RSA_NULL_MOD_EXP 131 -#define RSA_F_RSA_NULL_PRIVATE_DECRYPT 132 -#define RSA_F_RSA_NULL_PRIVATE_ENCRYPT 133 -#define RSA_F_RSA_NULL_PUBLIC_DECRYPT 134 -#define RSA_F_RSA_NULL_PUBLIC_ENCRYPT 135 -#define RSA_F_RSA_PADDING_ADD_NONE 107 -#define RSA_F_RSA_PADDING_ADD_PKCS1_OAEP 121 -#define RSA_F_RSA_PADDING_ADD_PKCS1_PSS 125 -#define RSA_F_RSA_PADDING_ADD_PKCS1_PSS_MGF1 148 -#define RSA_F_RSA_PADDING_ADD_PKCS1_TYPE_1 108 -#define RSA_F_RSA_PADDING_ADD_PKCS1_TYPE_2 109 -#define RSA_F_RSA_PADDING_ADD_SSLV23 110 -#define RSA_F_RSA_PADDING_ADD_X931 127 -#define RSA_F_RSA_PADDING_CHECK_NONE 111 -#define RSA_F_RSA_PADDING_CHECK_PKCS1_OAEP 122 -#define RSA_F_RSA_PADDING_CHECK_PKCS1_TYPE_1 112 -#define RSA_F_RSA_PADDING_CHECK_PKCS1_TYPE_2 113 -#define RSA_F_RSA_PADDING_CHECK_SSLV23 114 -#define RSA_F_RSA_PADDING_CHECK_X931 128 -#define RSA_F_RSA_PRINT 115 -#define RSA_F_RSA_PRINT_FP 116 -#define RSA_F_RSA_PRIVATE_DECRYPT 150 -#define RSA_F_RSA_PRIVATE_ENCRYPT 151 -#define RSA_F_RSA_PRIV_DECODE 137 -#define RSA_F_RSA_PRIV_ENCODE 138 -#define RSA_F_RSA_PUBLIC_DECRYPT 152 -#define RSA_F_RSA_PUBLIC_ENCRYPT 153 -#define RSA_F_RSA_PUB_DECODE 139 -#define RSA_F_RSA_SETUP_BLINDING 136 -#define RSA_F_RSA_SIGN 117 -#define RSA_F_RSA_SIGN_ASN1_OCTET_STRING 118 -#define RSA_F_RSA_VERIFY 119 -#define RSA_F_RSA_VERIFY_ASN1_OCTET_STRING 120 -#define RSA_F_RSA_VERIFY_PKCS1_PSS 126 -#define RSA_F_RSA_VERIFY_PKCS1_PSS_MGF1 149 - -/* Reason codes. */ -#define RSA_R_ALGORITHM_MISMATCH 100 -#define RSA_R_BAD_E_VALUE 101 -#define RSA_R_BAD_FIXED_HEADER_DECRYPT 102 -#define RSA_R_BAD_PAD_BYTE_COUNT 103 -#define RSA_R_BAD_SIGNATURE 104 -#define RSA_R_BLOCK_TYPE_IS_NOT_01 106 -#define RSA_R_BLOCK_TYPE_IS_NOT_02 107 -#define RSA_R_DATA_GREATER_THAN_MOD_LEN 108 -#define RSA_R_DATA_TOO_LARGE 109 -#define RSA_R_DATA_TOO_LARGE_FOR_KEY_SIZE 110 -#define RSA_R_DATA_TOO_LARGE_FOR_MODULUS 132 -#define RSA_R_DATA_TOO_SMALL 111 -#define RSA_R_DATA_TOO_SMALL_FOR_KEY_SIZE 122 -#define RSA_R_DIGEST_TOO_BIG_FOR_RSA_KEY 112 -#define RSA_R_DMP1_NOT_CONGRUENT_TO_D 124 -#define RSA_R_DMQ1_NOT_CONGRUENT_TO_D 125 -#define RSA_R_D_E_NOT_CONGRUENT_TO_1 123 -#define RSA_R_FIRST_OCTET_INVALID 133 -#define RSA_R_ILLEGAL_OR_UNSUPPORTED_PADDING_MODE 144 -#define RSA_R_INVALID_DIGEST_LENGTH 143 -#define RSA_R_INVALID_HEADER 137 -#define RSA_R_INVALID_KEYBITS 145 -#define RSA_R_INVALID_MESSAGE_LENGTH 131 -#define RSA_R_INVALID_MGF1_MD 156 -#define RSA_R_INVALID_PADDING 138 -#define RSA_R_INVALID_PADDING_MODE 141 -#define RSA_R_INVALID_PSS_PARAMETERS 149 -#define RSA_R_INVALID_PSS_SALTLEN 146 -#define RSA_R_INVALID_SALT_LENGTH 150 -#define RSA_R_INVALID_TRAILER 139 -#define RSA_R_INVALID_X931_DIGEST 142 -#define RSA_R_IQMP_NOT_INVERSE_OF_Q 126 -#define RSA_R_KEY_SIZE_TOO_SMALL 120 -#define RSA_R_LAST_OCTET_INVALID 134 -#define RSA_R_MODULUS_TOO_LARGE 105 -#define RSA_R_NON_FIPS_RSA_METHOD 157 -#define RSA_R_NO_PUBLIC_EXPONENT 140 -#define RSA_R_NULL_BEFORE_BLOCK_MISSING 113 -#define RSA_R_N_DOES_NOT_EQUAL_P_Q 127 -#define RSA_R_OAEP_DECODING_ERROR 121 -#define RSA_R_OPERATION_NOT_ALLOWED_IN_FIPS_MODE 158 -#define RSA_R_OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE 148 -#define RSA_R_PADDING_CHECK_FAILED 114 -#define RSA_R_P_NOT_PRIME 128 -#define RSA_R_Q_NOT_PRIME 129 -#define RSA_R_RSA_OPERATIONS_NOT_SUPPORTED 130 -#define RSA_R_SLEN_CHECK_FAILED 136 -#define RSA_R_SLEN_RECOVERY_FAILED 135 -#define RSA_R_SSLV3_ROLLBACK_ATTACK 115 -#define RSA_R_THE_ASN1_OBJECT_IDENTIFIER_IS_NOT_KNOWN_FOR_THIS_MD 116 -#define RSA_R_UNKNOWN_ALGORITHM_TYPE 117 -#define RSA_R_UNKNOWN_MASK_DIGEST 151 -#define RSA_R_UNKNOWN_PADDING_TYPE 118 -#define RSA_R_UNKNOWN_PSS_DIGEST 152 -#define RSA_R_UNSUPPORTED_MASK_ALGORITHM 153 -#define RSA_R_UNSUPPORTED_MASK_PARAMETER 154 -#define RSA_R_UNSUPPORTED_SIGNATURE_TYPE 155 -#define RSA_R_VALUE_MISSING 147 -#define RSA_R_WRONG_SIGNATURE_LENGTH 119 - -#ifdef __cplusplus -} -#endif -#endif diff --git a/src/plugins/ftp/openssl/safestack.h b/src/plugins/ftp/openssl/safestack.h deleted file mode 100644 index ea3aa0d8..00000000 --- a/src/plugins/ftp/openssl/safestack.h +++ /dev/null @@ -1,2663 +0,0 @@ -/* ==================================================================== - * Copyright (c) 1999 The OpenSSL Project. All rights reserved. - * - * 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. All advertising materials mentioning features or use of this - * software must display the following acknowledgment: - * "This product includes software developed by the OpenSSL Project - * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" - * - * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For written permission, please contact - * openssl-core@openssl.org. - * - * 5. Products derived from this software may not be called "OpenSSL" - * nor may "OpenSSL" appear in their names without prior written - * permission of the OpenSSL Project. - * - * 6. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by the OpenSSL Project - * for use in the OpenSSL Toolkit (http://www.openssl.org/)" - * - * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY - * EXPRESSED 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 OpenSSL PROJECT OR - * ITS 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. - * ==================================================================== - * - * This product includes cryptographic software written by Eric Young - * (eay@cryptsoft.com). This product includes software written by Tim - * Hudson (tjh@cryptsoft.com). - * - */ - -#ifndef HEADER_SAFESTACK_H -#define HEADER_SAFESTACK_H - -#include - -#ifndef CHECKED_PTR_OF -#define CHECKED_PTR_OF(type, p) \ - ((void*) (1 ? p : (type*)0)) -#endif - -/* In C++ we get problems because an explicit cast is needed from (void *) - * we use CHECKED_STACK_OF to ensure the correct type is passed in the macros - * below. - */ - -#define CHECKED_STACK_OF(type, p) \ - ((_STACK*) (1 ? p : (STACK_OF(type)*)0)) - -#define CHECKED_SK_FREE_FUNC(type, p) \ - ((void (*)(void *)) ((1 ? p : (void (*)(type *))0))) - -#define CHECKED_SK_FREE_FUNC2(type, p) \ - ((void (*)(void *)) ((1 ? p : (void (*)(type))0))) - -#define CHECKED_SK_CMP_FUNC(type, p) \ - ((int (*)(const void *, const void *)) \ - ((1 ? p : (int (*)(const type * const *, const type * const *))0))) - -#define STACK_OF(type) struct stack_st_##type -#define PREDECLARE_STACK_OF(type) STACK_OF(type); - -#define DECLARE_STACK_OF(type) \ -STACK_OF(type) \ - { \ - _STACK stack; \ - }; -#define DECLARE_SPECIAL_STACK_OF(type, type2) \ -STACK_OF(type) \ - { \ - _STACK stack; \ - }; - -#define IMPLEMENT_STACK_OF(type) /* nada (obsolete in new safestack approach)*/ - - -/* Strings are special: normally an lhash entry will point to a single - * (somewhat) mutable object. In the case of strings: - * - * a) Instead of a single char, there is an array of chars, NUL-terminated. - * b) The string may have be immutable. - * - * So, they need their own declarations. Especially important for - * type-checking tools, such as Deputy. - * -o * In practice, however, it appears to be hard to have a const - * string. For now, I'm settling for dealing with the fact it is a - * string at all. - */ -typedef char *OPENSSL_STRING; - -typedef const char *OPENSSL_CSTRING; - -/* Confusingly, LHASH_OF(STRING) deals with char ** throughout, but - * STACK_OF(STRING) is really more like STACK_OF(char), only, as - * mentioned above, instead of a single char each entry is a - * NUL-terminated array of chars. So, we have to implement STRING - * specially for STACK_OF. This is dealt with in the autogenerated - * macros below. - */ - -DECLARE_SPECIAL_STACK_OF(OPENSSL_STRING, char) - -/* Similarly, we sometimes use a block of characters, NOT - * nul-terminated. These should also be distinguished from "normal" - * stacks. */ - -typedef void *OPENSSL_BLOCK; -DECLARE_SPECIAL_STACK_OF(OPENSSL_BLOCK, void) - -/* SKM_sk_... stack macros are internal to safestack.h: - * never use them directly, use sk__... instead */ -#define SKM_sk_new(type, cmp) \ - ((STACK_OF(type) *)sk_new(CHECKED_SK_CMP_FUNC(type, cmp))) -#define SKM_sk_new_null(type) \ - ((STACK_OF(type) *)sk_new_null()) -#define SKM_sk_free(type, st) \ - sk_free(CHECKED_STACK_OF(type, st)) -#define SKM_sk_num(type, st) \ - sk_num(CHECKED_STACK_OF(type, st)) -#define SKM_sk_value(type, st,i) \ - ((type *)sk_value(CHECKED_STACK_OF(type, st), i)) -#define SKM_sk_set(type, st,i,val) \ - sk_set(CHECKED_STACK_OF(type, st), i, CHECKED_PTR_OF(type, val)) -#define SKM_sk_zero(type, st) \ - sk_zero(CHECKED_STACK_OF(type, st)) -#define SKM_sk_push(type, st, val) \ - sk_push(CHECKED_STACK_OF(type, st), CHECKED_PTR_OF(type, val)) -#define SKM_sk_unshift(type, st, val) \ - sk_unshift(CHECKED_STACK_OF(type, st), CHECKED_PTR_OF(type, val)) -#define SKM_sk_find(type, st, val) \ - sk_find(CHECKED_STACK_OF(type, st), CHECKED_PTR_OF(type, val)) -#define SKM_sk_find_ex(type, st, val) \ - sk_find_ex(CHECKED_STACK_OF(type, st), \ - CHECKED_PTR_OF(type, val)) -#define SKM_sk_delete(type, st, i) \ - (type *)sk_delete(CHECKED_STACK_OF(type, st), i) -#define SKM_sk_delete_ptr(type, st, ptr) \ - (type *)sk_delete_ptr(CHECKED_STACK_OF(type, st), CHECKED_PTR_OF(type, ptr)) -#define SKM_sk_insert(type, st,val, i) \ - sk_insert(CHECKED_STACK_OF(type, st), CHECKED_PTR_OF(type, val), i) -#define SKM_sk_set_cmp_func(type, st, cmp) \ - ((int (*)(const type * const *,const type * const *)) \ - sk_set_cmp_func(CHECKED_STACK_OF(type, st), CHECKED_SK_CMP_FUNC(type, cmp))) -#define SKM_sk_dup(type, st) \ - (STACK_OF(type) *)sk_dup(CHECKED_STACK_OF(type, st)) -#define SKM_sk_pop_free(type, st, free_func) \ - sk_pop_free(CHECKED_STACK_OF(type, st), CHECKED_SK_FREE_FUNC(type, free_func)) -#define SKM_sk_shift(type, st) \ - (type *)sk_shift(CHECKED_STACK_OF(type, st)) -#define SKM_sk_pop(type, st) \ - (type *)sk_pop(CHECKED_STACK_OF(type, st)) -#define SKM_sk_sort(type, st) \ - sk_sort(CHECKED_STACK_OF(type, st)) -#define SKM_sk_is_sorted(type, st) \ - sk_is_sorted(CHECKED_STACK_OF(type, st)) - -#define SKM_ASN1_SET_OF_d2i(type, st, pp, length, d2i_func, free_func, ex_tag, ex_class) \ - (STACK_OF(type) *)d2i_ASN1_SET( \ - (STACK_OF(OPENSSL_BLOCK) **)CHECKED_PTR_OF(STACK_OF(type)*, st), \ - pp, length, \ - CHECKED_D2I_OF(type, d2i_func), \ - CHECKED_SK_FREE_FUNC(type, free_func), \ - ex_tag, ex_class) - -#define SKM_ASN1_SET_OF_i2d(type, st, pp, i2d_func, ex_tag, ex_class, is_set) \ - i2d_ASN1_SET((STACK_OF(OPENSSL_BLOCK) *)CHECKED_STACK_OF(type, st), pp, \ - CHECKED_I2D_OF(type, i2d_func), \ - ex_tag, ex_class, is_set) - -#define SKM_ASN1_seq_pack(type, st, i2d_func, buf, len) \ - ASN1_seq_pack(CHECKED_PTR_OF(STACK_OF(type), st), \ - CHECKED_I2D_OF(type, i2d_func), buf, len) - -#define SKM_ASN1_seq_unpack(type, buf, len, d2i_func, free_func) \ - (STACK_OF(type) *)ASN1_seq_unpack(buf, len, CHECKED_D2I_OF(type, d2i_func), CHECKED_SK_FREE_FUNC(type, free_func)) - -#define SKM_PKCS12_decrypt_d2i(type, algor, d2i_func, free_func, pass, passlen, oct, seq) \ - (STACK_OF(type) *)PKCS12_decrypt_d2i(algor, \ - CHECKED_D2I_OF(type, d2i_func), \ - CHECKED_SK_FREE_FUNC(type, free_func), \ - pass, passlen, oct, seq) - -/* This block of defines is updated by util/mkstack.pl, please do not touch! */ -#define sk_ACCESS_DESCRIPTION_new(cmp) SKM_sk_new(ACCESS_DESCRIPTION, (cmp)) -#define sk_ACCESS_DESCRIPTION_new_null() SKM_sk_new_null(ACCESS_DESCRIPTION) -#define sk_ACCESS_DESCRIPTION_free(st) SKM_sk_free(ACCESS_DESCRIPTION, (st)) -#define sk_ACCESS_DESCRIPTION_num(st) SKM_sk_num(ACCESS_DESCRIPTION, (st)) -#define sk_ACCESS_DESCRIPTION_value(st, i) SKM_sk_value(ACCESS_DESCRIPTION, (st), (i)) -#define sk_ACCESS_DESCRIPTION_set(st, i, val) SKM_sk_set(ACCESS_DESCRIPTION, (st), (i), (val)) -#define sk_ACCESS_DESCRIPTION_zero(st) SKM_sk_zero(ACCESS_DESCRIPTION, (st)) -#define sk_ACCESS_DESCRIPTION_push(st, val) SKM_sk_push(ACCESS_DESCRIPTION, (st), (val)) -#define sk_ACCESS_DESCRIPTION_unshift(st, val) SKM_sk_unshift(ACCESS_DESCRIPTION, (st), (val)) -#define sk_ACCESS_DESCRIPTION_find(st, val) SKM_sk_find(ACCESS_DESCRIPTION, (st), (val)) -#define sk_ACCESS_DESCRIPTION_find_ex(st, val) SKM_sk_find_ex(ACCESS_DESCRIPTION, (st), (val)) -#define sk_ACCESS_DESCRIPTION_delete(st, i) SKM_sk_delete(ACCESS_DESCRIPTION, (st), (i)) -#define sk_ACCESS_DESCRIPTION_delete_ptr(st, ptr) SKM_sk_delete_ptr(ACCESS_DESCRIPTION, (st), (ptr)) -#define sk_ACCESS_DESCRIPTION_insert(st, val, i) SKM_sk_insert(ACCESS_DESCRIPTION, (st), (val), (i)) -#define sk_ACCESS_DESCRIPTION_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(ACCESS_DESCRIPTION, (st), (cmp)) -#define sk_ACCESS_DESCRIPTION_dup(st) SKM_sk_dup(ACCESS_DESCRIPTION, st) -#define sk_ACCESS_DESCRIPTION_pop_free(st, free_func) SKM_sk_pop_free(ACCESS_DESCRIPTION, (st), (free_func)) -#define sk_ACCESS_DESCRIPTION_shift(st) SKM_sk_shift(ACCESS_DESCRIPTION, (st)) -#define sk_ACCESS_DESCRIPTION_pop(st) SKM_sk_pop(ACCESS_DESCRIPTION, (st)) -#define sk_ACCESS_DESCRIPTION_sort(st) SKM_sk_sort(ACCESS_DESCRIPTION, (st)) -#define sk_ACCESS_DESCRIPTION_is_sorted(st) SKM_sk_is_sorted(ACCESS_DESCRIPTION, (st)) - -#define sk_ASIdOrRange_new(cmp) SKM_sk_new(ASIdOrRange, (cmp)) -#define sk_ASIdOrRange_new_null() SKM_sk_new_null(ASIdOrRange) -#define sk_ASIdOrRange_free(st) SKM_sk_free(ASIdOrRange, (st)) -#define sk_ASIdOrRange_num(st) SKM_sk_num(ASIdOrRange, (st)) -#define sk_ASIdOrRange_value(st, i) SKM_sk_value(ASIdOrRange, (st), (i)) -#define sk_ASIdOrRange_set(st, i, val) SKM_sk_set(ASIdOrRange, (st), (i), (val)) -#define sk_ASIdOrRange_zero(st) SKM_sk_zero(ASIdOrRange, (st)) -#define sk_ASIdOrRange_push(st, val) SKM_sk_push(ASIdOrRange, (st), (val)) -#define sk_ASIdOrRange_unshift(st, val) SKM_sk_unshift(ASIdOrRange, (st), (val)) -#define sk_ASIdOrRange_find(st, val) SKM_sk_find(ASIdOrRange, (st), (val)) -#define sk_ASIdOrRange_find_ex(st, val) SKM_sk_find_ex(ASIdOrRange, (st), (val)) -#define sk_ASIdOrRange_delete(st, i) SKM_sk_delete(ASIdOrRange, (st), (i)) -#define sk_ASIdOrRange_delete_ptr(st, ptr) SKM_sk_delete_ptr(ASIdOrRange, (st), (ptr)) -#define sk_ASIdOrRange_insert(st, val, i) SKM_sk_insert(ASIdOrRange, (st), (val), (i)) -#define sk_ASIdOrRange_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(ASIdOrRange, (st), (cmp)) -#define sk_ASIdOrRange_dup(st) SKM_sk_dup(ASIdOrRange, st) -#define sk_ASIdOrRange_pop_free(st, free_func) SKM_sk_pop_free(ASIdOrRange, (st), (free_func)) -#define sk_ASIdOrRange_shift(st) SKM_sk_shift(ASIdOrRange, (st)) -#define sk_ASIdOrRange_pop(st) SKM_sk_pop(ASIdOrRange, (st)) -#define sk_ASIdOrRange_sort(st) SKM_sk_sort(ASIdOrRange, (st)) -#define sk_ASIdOrRange_is_sorted(st) SKM_sk_is_sorted(ASIdOrRange, (st)) - -#define sk_ASN1_GENERALSTRING_new(cmp) SKM_sk_new(ASN1_GENERALSTRING, (cmp)) -#define sk_ASN1_GENERALSTRING_new_null() SKM_sk_new_null(ASN1_GENERALSTRING) -#define sk_ASN1_GENERALSTRING_free(st) SKM_sk_free(ASN1_GENERALSTRING, (st)) -#define sk_ASN1_GENERALSTRING_num(st) SKM_sk_num(ASN1_GENERALSTRING, (st)) -#define sk_ASN1_GENERALSTRING_value(st, i) SKM_sk_value(ASN1_GENERALSTRING, (st), (i)) -#define sk_ASN1_GENERALSTRING_set(st, i, val) SKM_sk_set(ASN1_GENERALSTRING, (st), (i), (val)) -#define sk_ASN1_GENERALSTRING_zero(st) SKM_sk_zero(ASN1_GENERALSTRING, (st)) -#define sk_ASN1_GENERALSTRING_push(st, val) SKM_sk_push(ASN1_GENERALSTRING, (st), (val)) -#define sk_ASN1_GENERALSTRING_unshift(st, val) SKM_sk_unshift(ASN1_GENERALSTRING, (st), (val)) -#define sk_ASN1_GENERALSTRING_find(st, val) SKM_sk_find(ASN1_GENERALSTRING, (st), (val)) -#define sk_ASN1_GENERALSTRING_find_ex(st, val) SKM_sk_find_ex(ASN1_GENERALSTRING, (st), (val)) -#define sk_ASN1_GENERALSTRING_delete(st, i) SKM_sk_delete(ASN1_GENERALSTRING, (st), (i)) -#define sk_ASN1_GENERALSTRING_delete_ptr(st, ptr) SKM_sk_delete_ptr(ASN1_GENERALSTRING, (st), (ptr)) -#define sk_ASN1_GENERALSTRING_insert(st, val, i) SKM_sk_insert(ASN1_GENERALSTRING, (st), (val), (i)) -#define sk_ASN1_GENERALSTRING_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(ASN1_GENERALSTRING, (st), (cmp)) -#define sk_ASN1_GENERALSTRING_dup(st) SKM_sk_dup(ASN1_GENERALSTRING, st) -#define sk_ASN1_GENERALSTRING_pop_free(st, free_func) SKM_sk_pop_free(ASN1_GENERALSTRING, (st), (free_func)) -#define sk_ASN1_GENERALSTRING_shift(st) SKM_sk_shift(ASN1_GENERALSTRING, (st)) -#define sk_ASN1_GENERALSTRING_pop(st) SKM_sk_pop(ASN1_GENERALSTRING, (st)) -#define sk_ASN1_GENERALSTRING_sort(st) SKM_sk_sort(ASN1_GENERALSTRING, (st)) -#define sk_ASN1_GENERALSTRING_is_sorted(st) SKM_sk_is_sorted(ASN1_GENERALSTRING, (st)) - -#define sk_ASN1_INTEGER_new(cmp) SKM_sk_new(ASN1_INTEGER, (cmp)) -#define sk_ASN1_INTEGER_new_null() SKM_sk_new_null(ASN1_INTEGER) -#define sk_ASN1_INTEGER_free(st) SKM_sk_free(ASN1_INTEGER, (st)) -#define sk_ASN1_INTEGER_num(st) SKM_sk_num(ASN1_INTEGER, (st)) -#define sk_ASN1_INTEGER_value(st, i) SKM_sk_value(ASN1_INTEGER, (st), (i)) -#define sk_ASN1_INTEGER_set(st, i, val) SKM_sk_set(ASN1_INTEGER, (st), (i), (val)) -#define sk_ASN1_INTEGER_zero(st) SKM_sk_zero(ASN1_INTEGER, (st)) -#define sk_ASN1_INTEGER_push(st, val) SKM_sk_push(ASN1_INTEGER, (st), (val)) -#define sk_ASN1_INTEGER_unshift(st, val) SKM_sk_unshift(ASN1_INTEGER, (st), (val)) -#define sk_ASN1_INTEGER_find(st, val) SKM_sk_find(ASN1_INTEGER, (st), (val)) -#define sk_ASN1_INTEGER_find_ex(st, val) SKM_sk_find_ex(ASN1_INTEGER, (st), (val)) -#define sk_ASN1_INTEGER_delete(st, i) SKM_sk_delete(ASN1_INTEGER, (st), (i)) -#define sk_ASN1_INTEGER_delete_ptr(st, ptr) SKM_sk_delete_ptr(ASN1_INTEGER, (st), (ptr)) -#define sk_ASN1_INTEGER_insert(st, val, i) SKM_sk_insert(ASN1_INTEGER, (st), (val), (i)) -#define sk_ASN1_INTEGER_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(ASN1_INTEGER, (st), (cmp)) -#define sk_ASN1_INTEGER_dup(st) SKM_sk_dup(ASN1_INTEGER, st) -#define sk_ASN1_INTEGER_pop_free(st, free_func) SKM_sk_pop_free(ASN1_INTEGER, (st), (free_func)) -#define sk_ASN1_INTEGER_shift(st) SKM_sk_shift(ASN1_INTEGER, (st)) -#define sk_ASN1_INTEGER_pop(st) SKM_sk_pop(ASN1_INTEGER, (st)) -#define sk_ASN1_INTEGER_sort(st) SKM_sk_sort(ASN1_INTEGER, (st)) -#define sk_ASN1_INTEGER_is_sorted(st) SKM_sk_is_sorted(ASN1_INTEGER, (st)) - -#define sk_ASN1_OBJECT_new(cmp) SKM_sk_new(ASN1_OBJECT, (cmp)) -#define sk_ASN1_OBJECT_new_null() SKM_sk_new_null(ASN1_OBJECT) -#define sk_ASN1_OBJECT_free(st) SKM_sk_free(ASN1_OBJECT, (st)) -#define sk_ASN1_OBJECT_num(st) SKM_sk_num(ASN1_OBJECT, (st)) -#define sk_ASN1_OBJECT_value(st, i) SKM_sk_value(ASN1_OBJECT, (st), (i)) -#define sk_ASN1_OBJECT_set(st, i, val) SKM_sk_set(ASN1_OBJECT, (st), (i), (val)) -#define sk_ASN1_OBJECT_zero(st) SKM_sk_zero(ASN1_OBJECT, (st)) -#define sk_ASN1_OBJECT_push(st, val) SKM_sk_push(ASN1_OBJECT, (st), (val)) -#define sk_ASN1_OBJECT_unshift(st, val) SKM_sk_unshift(ASN1_OBJECT, (st), (val)) -#define sk_ASN1_OBJECT_find(st, val) SKM_sk_find(ASN1_OBJECT, (st), (val)) -#define sk_ASN1_OBJECT_find_ex(st, val) SKM_sk_find_ex(ASN1_OBJECT, (st), (val)) -#define sk_ASN1_OBJECT_delete(st, i) SKM_sk_delete(ASN1_OBJECT, (st), (i)) -#define sk_ASN1_OBJECT_delete_ptr(st, ptr) SKM_sk_delete_ptr(ASN1_OBJECT, (st), (ptr)) -#define sk_ASN1_OBJECT_insert(st, val, i) SKM_sk_insert(ASN1_OBJECT, (st), (val), (i)) -#define sk_ASN1_OBJECT_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(ASN1_OBJECT, (st), (cmp)) -#define sk_ASN1_OBJECT_dup(st) SKM_sk_dup(ASN1_OBJECT, st) -#define sk_ASN1_OBJECT_pop_free(st, free_func) SKM_sk_pop_free(ASN1_OBJECT, (st), (free_func)) -#define sk_ASN1_OBJECT_shift(st) SKM_sk_shift(ASN1_OBJECT, (st)) -#define sk_ASN1_OBJECT_pop(st) SKM_sk_pop(ASN1_OBJECT, (st)) -#define sk_ASN1_OBJECT_sort(st) SKM_sk_sort(ASN1_OBJECT, (st)) -#define sk_ASN1_OBJECT_is_sorted(st) SKM_sk_is_sorted(ASN1_OBJECT, (st)) - -#define sk_ASN1_STRING_TABLE_new(cmp) SKM_sk_new(ASN1_STRING_TABLE, (cmp)) -#define sk_ASN1_STRING_TABLE_new_null() SKM_sk_new_null(ASN1_STRING_TABLE) -#define sk_ASN1_STRING_TABLE_free(st) SKM_sk_free(ASN1_STRING_TABLE, (st)) -#define sk_ASN1_STRING_TABLE_num(st) SKM_sk_num(ASN1_STRING_TABLE, (st)) -#define sk_ASN1_STRING_TABLE_value(st, i) SKM_sk_value(ASN1_STRING_TABLE, (st), (i)) -#define sk_ASN1_STRING_TABLE_set(st, i, val) SKM_sk_set(ASN1_STRING_TABLE, (st), (i), (val)) -#define sk_ASN1_STRING_TABLE_zero(st) SKM_sk_zero(ASN1_STRING_TABLE, (st)) -#define sk_ASN1_STRING_TABLE_push(st, val) SKM_sk_push(ASN1_STRING_TABLE, (st), (val)) -#define sk_ASN1_STRING_TABLE_unshift(st, val) SKM_sk_unshift(ASN1_STRING_TABLE, (st), (val)) -#define sk_ASN1_STRING_TABLE_find(st, val) SKM_sk_find(ASN1_STRING_TABLE, (st), (val)) -#define sk_ASN1_STRING_TABLE_find_ex(st, val) SKM_sk_find_ex(ASN1_STRING_TABLE, (st), (val)) -#define sk_ASN1_STRING_TABLE_delete(st, i) SKM_sk_delete(ASN1_STRING_TABLE, (st), (i)) -#define sk_ASN1_STRING_TABLE_delete_ptr(st, ptr) SKM_sk_delete_ptr(ASN1_STRING_TABLE, (st), (ptr)) -#define sk_ASN1_STRING_TABLE_insert(st, val, i) SKM_sk_insert(ASN1_STRING_TABLE, (st), (val), (i)) -#define sk_ASN1_STRING_TABLE_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(ASN1_STRING_TABLE, (st), (cmp)) -#define sk_ASN1_STRING_TABLE_dup(st) SKM_sk_dup(ASN1_STRING_TABLE, st) -#define sk_ASN1_STRING_TABLE_pop_free(st, free_func) SKM_sk_pop_free(ASN1_STRING_TABLE, (st), (free_func)) -#define sk_ASN1_STRING_TABLE_shift(st) SKM_sk_shift(ASN1_STRING_TABLE, (st)) -#define sk_ASN1_STRING_TABLE_pop(st) SKM_sk_pop(ASN1_STRING_TABLE, (st)) -#define sk_ASN1_STRING_TABLE_sort(st) SKM_sk_sort(ASN1_STRING_TABLE, (st)) -#define sk_ASN1_STRING_TABLE_is_sorted(st) SKM_sk_is_sorted(ASN1_STRING_TABLE, (st)) - -#define sk_ASN1_TYPE_new(cmp) SKM_sk_new(ASN1_TYPE, (cmp)) -#define sk_ASN1_TYPE_new_null() SKM_sk_new_null(ASN1_TYPE) -#define sk_ASN1_TYPE_free(st) SKM_sk_free(ASN1_TYPE, (st)) -#define sk_ASN1_TYPE_num(st) SKM_sk_num(ASN1_TYPE, (st)) -#define sk_ASN1_TYPE_value(st, i) SKM_sk_value(ASN1_TYPE, (st), (i)) -#define sk_ASN1_TYPE_set(st, i, val) SKM_sk_set(ASN1_TYPE, (st), (i), (val)) -#define sk_ASN1_TYPE_zero(st) SKM_sk_zero(ASN1_TYPE, (st)) -#define sk_ASN1_TYPE_push(st, val) SKM_sk_push(ASN1_TYPE, (st), (val)) -#define sk_ASN1_TYPE_unshift(st, val) SKM_sk_unshift(ASN1_TYPE, (st), (val)) -#define sk_ASN1_TYPE_find(st, val) SKM_sk_find(ASN1_TYPE, (st), (val)) -#define sk_ASN1_TYPE_find_ex(st, val) SKM_sk_find_ex(ASN1_TYPE, (st), (val)) -#define sk_ASN1_TYPE_delete(st, i) SKM_sk_delete(ASN1_TYPE, (st), (i)) -#define sk_ASN1_TYPE_delete_ptr(st, ptr) SKM_sk_delete_ptr(ASN1_TYPE, (st), (ptr)) -#define sk_ASN1_TYPE_insert(st, val, i) SKM_sk_insert(ASN1_TYPE, (st), (val), (i)) -#define sk_ASN1_TYPE_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(ASN1_TYPE, (st), (cmp)) -#define sk_ASN1_TYPE_dup(st) SKM_sk_dup(ASN1_TYPE, st) -#define sk_ASN1_TYPE_pop_free(st, free_func) SKM_sk_pop_free(ASN1_TYPE, (st), (free_func)) -#define sk_ASN1_TYPE_shift(st) SKM_sk_shift(ASN1_TYPE, (st)) -#define sk_ASN1_TYPE_pop(st) SKM_sk_pop(ASN1_TYPE, (st)) -#define sk_ASN1_TYPE_sort(st) SKM_sk_sort(ASN1_TYPE, (st)) -#define sk_ASN1_TYPE_is_sorted(st) SKM_sk_is_sorted(ASN1_TYPE, (st)) - -#define sk_ASN1_UTF8STRING_new(cmp) SKM_sk_new(ASN1_UTF8STRING, (cmp)) -#define sk_ASN1_UTF8STRING_new_null() SKM_sk_new_null(ASN1_UTF8STRING) -#define sk_ASN1_UTF8STRING_free(st) SKM_sk_free(ASN1_UTF8STRING, (st)) -#define sk_ASN1_UTF8STRING_num(st) SKM_sk_num(ASN1_UTF8STRING, (st)) -#define sk_ASN1_UTF8STRING_value(st, i) SKM_sk_value(ASN1_UTF8STRING, (st), (i)) -#define sk_ASN1_UTF8STRING_set(st, i, val) SKM_sk_set(ASN1_UTF8STRING, (st), (i), (val)) -#define sk_ASN1_UTF8STRING_zero(st) SKM_sk_zero(ASN1_UTF8STRING, (st)) -#define sk_ASN1_UTF8STRING_push(st, val) SKM_sk_push(ASN1_UTF8STRING, (st), (val)) -#define sk_ASN1_UTF8STRING_unshift(st, val) SKM_sk_unshift(ASN1_UTF8STRING, (st), (val)) -#define sk_ASN1_UTF8STRING_find(st, val) SKM_sk_find(ASN1_UTF8STRING, (st), (val)) -#define sk_ASN1_UTF8STRING_find_ex(st, val) SKM_sk_find_ex(ASN1_UTF8STRING, (st), (val)) -#define sk_ASN1_UTF8STRING_delete(st, i) SKM_sk_delete(ASN1_UTF8STRING, (st), (i)) -#define sk_ASN1_UTF8STRING_delete_ptr(st, ptr) SKM_sk_delete_ptr(ASN1_UTF8STRING, (st), (ptr)) -#define sk_ASN1_UTF8STRING_insert(st, val, i) SKM_sk_insert(ASN1_UTF8STRING, (st), (val), (i)) -#define sk_ASN1_UTF8STRING_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(ASN1_UTF8STRING, (st), (cmp)) -#define sk_ASN1_UTF8STRING_dup(st) SKM_sk_dup(ASN1_UTF8STRING, st) -#define sk_ASN1_UTF8STRING_pop_free(st, free_func) SKM_sk_pop_free(ASN1_UTF8STRING, (st), (free_func)) -#define sk_ASN1_UTF8STRING_shift(st) SKM_sk_shift(ASN1_UTF8STRING, (st)) -#define sk_ASN1_UTF8STRING_pop(st) SKM_sk_pop(ASN1_UTF8STRING, (st)) -#define sk_ASN1_UTF8STRING_sort(st) SKM_sk_sort(ASN1_UTF8STRING, (st)) -#define sk_ASN1_UTF8STRING_is_sorted(st) SKM_sk_is_sorted(ASN1_UTF8STRING, (st)) - -#define sk_ASN1_VALUE_new(cmp) SKM_sk_new(ASN1_VALUE, (cmp)) -#define sk_ASN1_VALUE_new_null() SKM_sk_new_null(ASN1_VALUE) -#define sk_ASN1_VALUE_free(st) SKM_sk_free(ASN1_VALUE, (st)) -#define sk_ASN1_VALUE_num(st) SKM_sk_num(ASN1_VALUE, (st)) -#define sk_ASN1_VALUE_value(st, i) SKM_sk_value(ASN1_VALUE, (st), (i)) -#define sk_ASN1_VALUE_set(st, i, val) SKM_sk_set(ASN1_VALUE, (st), (i), (val)) -#define sk_ASN1_VALUE_zero(st) SKM_sk_zero(ASN1_VALUE, (st)) -#define sk_ASN1_VALUE_push(st, val) SKM_sk_push(ASN1_VALUE, (st), (val)) -#define sk_ASN1_VALUE_unshift(st, val) SKM_sk_unshift(ASN1_VALUE, (st), (val)) -#define sk_ASN1_VALUE_find(st, val) SKM_sk_find(ASN1_VALUE, (st), (val)) -#define sk_ASN1_VALUE_find_ex(st, val) SKM_sk_find_ex(ASN1_VALUE, (st), (val)) -#define sk_ASN1_VALUE_delete(st, i) SKM_sk_delete(ASN1_VALUE, (st), (i)) -#define sk_ASN1_VALUE_delete_ptr(st, ptr) SKM_sk_delete_ptr(ASN1_VALUE, (st), (ptr)) -#define sk_ASN1_VALUE_insert(st, val, i) SKM_sk_insert(ASN1_VALUE, (st), (val), (i)) -#define sk_ASN1_VALUE_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(ASN1_VALUE, (st), (cmp)) -#define sk_ASN1_VALUE_dup(st) SKM_sk_dup(ASN1_VALUE, st) -#define sk_ASN1_VALUE_pop_free(st, free_func) SKM_sk_pop_free(ASN1_VALUE, (st), (free_func)) -#define sk_ASN1_VALUE_shift(st) SKM_sk_shift(ASN1_VALUE, (st)) -#define sk_ASN1_VALUE_pop(st) SKM_sk_pop(ASN1_VALUE, (st)) -#define sk_ASN1_VALUE_sort(st) SKM_sk_sort(ASN1_VALUE, (st)) -#define sk_ASN1_VALUE_is_sorted(st) SKM_sk_is_sorted(ASN1_VALUE, (st)) - -#define sk_BIO_new(cmp) SKM_sk_new(BIO, (cmp)) -#define sk_BIO_new_null() SKM_sk_new_null(BIO) -#define sk_BIO_free(st) SKM_sk_free(BIO, (st)) -#define sk_BIO_num(st) SKM_sk_num(BIO, (st)) -#define sk_BIO_value(st, i) SKM_sk_value(BIO, (st), (i)) -#define sk_BIO_set(st, i, val) SKM_sk_set(BIO, (st), (i), (val)) -#define sk_BIO_zero(st) SKM_sk_zero(BIO, (st)) -#define sk_BIO_push(st, val) SKM_sk_push(BIO, (st), (val)) -#define sk_BIO_unshift(st, val) SKM_sk_unshift(BIO, (st), (val)) -#define sk_BIO_find(st, val) SKM_sk_find(BIO, (st), (val)) -#define sk_BIO_find_ex(st, val) SKM_sk_find_ex(BIO, (st), (val)) -#define sk_BIO_delete(st, i) SKM_sk_delete(BIO, (st), (i)) -#define sk_BIO_delete_ptr(st, ptr) SKM_sk_delete_ptr(BIO, (st), (ptr)) -#define sk_BIO_insert(st, val, i) SKM_sk_insert(BIO, (st), (val), (i)) -#define sk_BIO_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(BIO, (st), (cmp)) -#define sk_BIO_dup(st) SKM_sk_dup(BIO, st) -#define sk_BIO_pop_free(st, free_func) SKM_sk_pop_free(BIO, (st), (free_func)) -#define sk_BIO_shift(st) SKM_sk_shift(BIO, (st)) -#define sk_BIO_pop(st) SKM_sk_pop(BIO, (st)) -#define sk_BIO_sort(st) SKM_sk_sort(BIO, (st)) -#define sk_BIO_is_sorted(st) SKM_sk_is_sorted(BIO, (st)) - -#define sk_BY_DIR_ENTRY_new(cmp) SKM_sk_new(BY_DIR_ENTRY, (cmp)) -#define sk_BY_DIR_ENTRY_new_null() SKM_sk_new_null(BY_DIR_ENTRY) -#define sk_BY_DIR_ENTRY_free(st) SKM_sk_free(BY_DIR_ENTRY, (st)) -#define sk_BY_DIR_ENTRY_num(st) SKM_sk_num(BY_DIR_ENTRY, (st)) -#define sk_BY_DIR_ENTRY_value(st, i) SKM_sk_value(BY_DIR_ENTRY, (st), (i)) -#define sk_BY_DIR_ENTRY_set(st, i, val) SKM_sk_set(BY_DIR_ENTRY, (st), (i), (val)) -#define sk_BY_DIR_ENTRY_zero(st) SKM_sk_zero(BY_DIR_ENTRY, (st)) -#define sk_BY_DIR_ENTRY_push(st, val) SKM_sk_push(BY_DIR_ENTRY, (st), (val)) -#define sk_BY_DIR_ENTRY_unshift(st, val) SKM_sk_unshift(BY_DIR_ENTRY, (st), (val)) -#define sk_BY_DIR_ENTRY_find(st, val) SKM_sk_find(BY_DIR_ENTRY, (st), (val)) -#define sk_BY_DIR_ENTRY_find_ex(st, val) SKM_sk_find_ex(BY_DIR_ENTRY, (st), (val)) -#define sk_BY_DIR_ENTRY_delete(st, i) SKM_sk_delete(BY_DIR_ENTRY, (st), (i)) -#define sk_BY_DIR_ENTRY_delete_ptr(st, ptr) SKM_sk_delete_ptr(BY_DIR_ENTRY, (st), (ptr)) -#define sk_BY_DIR_ENTRY_insert(st, val, i) SKM_sk_insert(BY_DIR_ENTRY, (st), (val), (i)) -#define sk_BY_DIR_ENTRY_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(BY_DIR_ENTRY, (st), (cmp)) -#define sk_BY_DIR_ENTRY_dup(st) SKM_sk_dup(BY_DIR_ENTRY, st) -#define sk_BY_DIR_ENTRY_pop_free(st, free_func) SKM_sk_pop_free(BY_DIR_ENTRY, (st), (free_func)) -#define sk_BY_DIR_ENTRY_shift(st) SKM_sk_shift(BY_DIR_ENTRY, (st)) -#define sk_BY_DIR_ENTRY_pop(st) SKM_sk_pop(BY_DIR_ENTRY, (st)) -#define sk_BY_DIR_ENTRY_sort(st) SKM_sk_sort(BY_DIR_ENTRY, (st)) -#define sk_BY_DIR_ENTRY_is_sorted(st) SKM_sk_is_sorted(BY_DIR_ENTRY, (st)) - -#define sk_BY_DIR_HASH_new(cmp) SKM_sk_new(BY_DIR_HASH, (cmp)) -#define sk_BY_DIR_HASH_new_null() SKM_sk_new_null(BY_DIR_HASH) -#define sk_BY_DIR_HASH_free(st) SKM_sk_free(BY_DIR_HASH, (st)) -#define sk_BY_DIR_HASH_num(st) SKM_sk_num(BY_DIR_HASH, (st)) -#define sk_BY_DIR_HASH_value(st, i) SKM_sk_value(BY_DIR_HASH, (st), (i)) -#define sk_BY_DIR_HASH_set(st, i, val) SKM_sk_set(BY_DIR_HASH, (st), (i), (val)) -#define sk_BY_DIR_HASH_zero(st) SKM_sk_zero(BY_DIR_HASH, (st)) -#define sk_BY_DIR_HASH_push(st, val) SKM_sk_push(BY_DIR_HASH, (st), (val)) -#define sk_BY_DIR_HASH_unshift(st, val) SKM_sk_unshift(BY_DIR_HASH, (st), (val)) -#define sk_BY_DIR_HASH_find(st, val) SKM_sk_find(BY_DIR_HASH, (st), (val)) -#define sk_BY_DIR_HASH_find_ex(st, val) SKM_sk_find_ex(BY_DIR_HASH, (st), (val)) -#define sk_BY_DIR_HASH_delete(st, i) SKM_sk_delete(BY_DIR_HASH, (st), (i)) -#define sk_BY_DIR_HASH_delete_ptr(st, ptr) SKM_sk_delete_ptr(BY_DIR_HASH, (st), (ptr)) -#define sk_BY_DIR_HASH_insert(st, val, i) SKM_sk_insert(BY_DIR_HASH, (st), (val), (i)) -#define sk_BY_DIR_HASH_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(BY_DIR_HASH, (st), (cmp)) -#define sk_BY_DIR_HASH_dup(st) SKM_sk_dup(BY_DIR_HASH, st) -#define sk_BY_DIR_HASH_pop_free(st, free_func) SKM_sk_pop_free(BY_DIR_HASH, (st), (free_func)) -#define sk_BY_DIR_HASH_shift(st) SKM_sk_shift(BY_DIR_HASH, (st)) -#define sk_BY_DIR_HASH_pop(st) SKM_sk_pop(BY_DIR_HASH, (st)) -#define sk_BY_DIR_HASH_sort(st) SKM_sk_sort(BY_DIR_HASH, (st)) -#define sk_BY_DIR_HASH_is_sorted(st) SKM_sk_is_sorted(BY_DIR_HASH, (st)) - -#define sk_CMS_CertificateChoices_new(cmp) SKM_sk_new(CMS_CertificateChoices, (cmp)) -#define sk_CMS_CertificateChoices_new_null() SKM_sk_new_null(CMS_CertificateChoices) -#define sk_CMS_CertificateChoices_free(st) SKM_sk_free(CMS_CertificateChoices, (st)) -#define sk_CMS_CertificateChoices_num(st) SKM_sk_num(CMS_CertificateChoices, (st)) -#define sk_CMS_CertificateChoices_value(st, i) SKM_sk_value(CMS_CertificateChoices, (st), (i)) -#define sk_CMS_CertificateChoices_set(st, i, val) SKM_sk_set(CMS_CertificateChoices, (st), (i), (val)) -#define sk_CMS_CertificateChoices_zero(st) SKM_sk_zero(CMS_CertificateChoices, (st)) -#define sk_CMS_CertificateChoices_push(st, val) SKM_sk_push(CMS_CertificateChoices, (st), (val)) -#define sk_CMS_CertificateChoices_unshift(st, val) SKM_sk_unshift(CMS_CertificateChoices, (st), (val)) -#define sk_CMS_CertificateChoices_find(st, val) SKM_sk_find(CMS_CertificateChoices, (st), (val)) -#define sk_CMS_CertificateChoices_find_ex(st, val) SKM_sk_find_ex(CMS_CertificateChoices, (st), (val)) -#define sk_CMS_CertificateChoices_delete(st, i) SKM_sk_delete(CMS_CertificateChoices, (st), (i)) -#define sk_CMS_CertificateChoices_delete_ptr(st, ptr) SKM_sk_delete_ptr(CMS_CertificateChoices, (st), (ptr)) -#define sk_CMS_CertificateChoices_insert(st, val, i) SKM_sk_insert(CMS_CertificateChoices, (st), (val), (i)) -#define sk_CMS_CertificateChoices_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(CMS_CertificateChoices, (st), (cmp)) -#define sk_CMS_CertificateChoices_dup(st) SKM_sk_dup(CMS_CertificateChoices, st) -#define sk_CMS_CertificateChoices_pop_free(st, free_func) SKM_sk_pop_free(CMS_CertificateChoices, (st), (free_func)) -#define sk_CMS_CertificateChoices_shift(st) SKM_sk_shift(CMS_CertificateChoices, (st)) -#define sk_CMS_CertificateChoices_pop(st) SKM_sk_pop(CMS_CertificateChoices, (st)) -#define sk_CMS_CertificateChoices_sort(st) SKM_sk_sort(CMS_CertificateChoices, (st)) -#define sk_CMS_CertificateChoices_is_sorted(st) SKM_sk_is_sorted(CMS_CertificateChoices, (st)) - -#define sk_CMS_RecipientInfo_new(cmp) SKM_sk_new(CMS_RecipientInfo, (cmp)) -#define sk_CMS_RecipientInfo_new_null() SKM_sk_new_null(CMS_RecipientInfo) -#define sk_CMS_RecipientInfo_free(st) SKM_sk_free(CMS_RecipientInfo, (st)) -#define sk_CMS_RecipientInfo_num(st) SKM_sk_num(CMS_RecipientInfo, (st)) -#define sk_CMS_RecipientInfo_value(st, i) SKM_sk_value(CMS_RecipientInfo, (st), (i)) -#define sk_CMS_RecipientInfo_set(st, i, val) SKM_sk_set(CMS_RecipientInfo, (st), (i), (val)) -#define sk_CMS_RecipientInfo_zero(st) SKM_sk_zero(CMS_RecipientInfo, (st)) -#define sk_CMS_RecipientInfo_push(st, val) SKM_sk_push(CMS_RecipientInfo, (st), (val)) -#define sk_CMS_RecipientInfo_unshift(st, val) SKM_sk_unshift(CMS_RecipientInfo, (st), (val)) -#define sk_CMS_RecipientInfo_find(st, val) SKM_sk_find(CMS_RecipientInfo, (st), (val)) -#define sk_CMS_RecipientInfo_find_ex(st, val) SKM_sk_find_ex(CMS_RecipientInfo, (st), (val)) -#define sk_CMS_RecipientInfo_delete(st, i) SKM_sk_delete(CMS_RecipientInfo, (st), (i)) -#define sk_CMS_RecipientInfo_delete_ptr(st, ptr) SKM_sk_delete_ptr(CMS_RecipientInfo, (st), (ptr)) -#define sk_CMS_RecipientInfo_insert(st, val, i) SKM_sk_insert(CMS_RecipientInfo, (st), (val), (i)) -#define sk_CMS_RecipientInfo_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(CMS_RecipientInfo, (st), (cmp)) -#define sk_CMS_RecipientInfo_dup(st) SKM_sk_dup(CMS_RecipientInfo, st) -#define sk_CMS_RecipientInfo_pop_free(st, free_func) SKM_sk_pop_free(CMS_RecipientInfo, (st), (free_func)) -#define sk_CMS_RecipientInfo_shift(st) SKM_sk_shift(CMS_RecipientInfo, (st)) -#define sk_CMS_RecipientInfo_pop(st) SKM_sk_pop(CMS_RecipientInfo, (st)) -#define sk_CMS_RecipientInfo_sort(st) SKM_sk_sort(CMS_RecipientInfo, (st)) -#define sk_CMS_RecipientInfo_is_sorted(st) SKM_sk_is_sorted(CMS_RecipientInfo, (st)) - -#define sk_CMS_RevocationInfoChoice_new(cmp) SKM_sk_new(CMS_RevocationInfoChoice, (cmp)) -#define sk_CMS_RevocationInfoChoice_new_null() SKM_sk_new_null(CMS_RevocationInfoChoice) -#define sk_CMS_RevocationInfoChoice_free(st) SKM_sk_free(CMS_RevocationInfoChoice, (st)) -#define sk_CMS_RevocationInfoChoice_num(st) SKM_sk_num(CMS_RevocationInfoChoice, (st)) -#define sk_CMS_RevocationInfoChoice_value(st, i) SKM_sk_value(CMS_RevocationInfoChoice, (st), (i)) -#define sk_CMS_RevocationInfoChoice_set(st, i, val) SKM_sk_set(CMS_RevocationInfoChoice, (st), (i), (val)) -#define sk_CMS_RevocationInfoChoice_zero(st) SKM_sk_zero(CMS_RevocationInfoChoice, (st)) -#define sk_CMS_RevocationInfoChoice_push(st, val) SKM_sk_push(CMS_RevocationInfoChoice, (st), (val)) -#define sk_CMS_RevocationInfoChoice_unshift(st, val) SKM_sk_unshift(CMS_RevocationInfoChoice, (st), (val)) -#define sk_CMS_RevocationInfoChoice_find(st, val) SKM_sk_find(CMS_RevocationInfoChoice, (st), (val)) -#define sk_CMS_RevocationInfoChoice_find_ex(st, val) SKM_sk_find_ex(CMS_RevocationInfoChoice, (st), (val)) -#define sk_CMS_RevocationInfoChoice_delete(st, i) SKM_sk_delete(CMS_RevocationInfoChoice, (st), (i)) -#define sk_CMS_RevocationInfoChoice_delete_ptr(st, ptr) SKM_sk_delete_ptr(CMS_RevocationInfoChoice, (st), (ptr)) -#define sk_CMS_RevocationInfoChoice_insert(st, val, i) SKM_sk_insert(CMS_RevocationInfoChoice, (st), (val), (i)) -#define sk_CMS_RevocationInfoChoice_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(CMS_RevocationInfoChoice, (st), (cmp)) -#define sk_CMS_RevocationInfoChoice_dup(st) SKM_sk_dup(CMS_RevocationInfoChoice, st) -#define sk_CMS_RevocationInfoChoice_pop_free(st, free_func) SKM_sk_pop_free(CMS_RevocationInfoChoice, (st), (free_func)) -#define sk_CMS_RevocationInfoChoice_shift(st) SKM_sk_shift(CMS_RevocationInfoChoice, (st)) -#define sk_CMS_RevocationInfoChoice_pop(st) SKM_sk_pop(CMS_RevocationInfoChoice, (st)) -#define sk_CMS_RevocationInfoChoice_sort(st) SKM_sk_sort(CMS_RevocationInfoChoice, (st)) -#define sk_CMS_RevocationInfoChoice_is_sorted(st) SKM_sk_is_sorted(CMS_RevocationInfoChoice, (st)) - -#define sk_CMS_SignerInfo_new(cmp) SKM_sk_new(CMS_SignerInfo, (cmp)) -#define sk_CMS_SignerInfo_new_null() SKM_sk_new_null(CMS_SignerInfo) -#define sk_CMS_SignerInfo_free(st) SKM_sk_free(CMS_SignerInfo, (st)) -#define sk_CMS_SignerInfo_num(st) SKM_sk_num(CMS_SignerInfo, (st)) -#define sk_CMS_SignerInfo_value(st, i) SKM_sk_value(CMS_SignerInfo, (st), (i)) -#define sk_CMS_SignerInfo_set(st, i, val) SKM_sk_set(CMS_SignerInfo, (st), (i), (val)) -#define sk_CMS_SignerInfo_zero(st) SKM_sk_zero(CMS_SignerInfo, (st)) -#define sk_CMS_SignerInfo_push(st, val) SKM_sk_push(CMS_SignerInfo, (st), (val)) -#define sk_CMS_SignerInfo_unshift(st, val) SKM_sk_unshift(CMS_SignerInfo, (st), (val)) -#define sk_CMS_SignerInfo_find(st, val) SKM_sk_find(CMS_SignerInfo, (st), (val)) -#define sk_CMS_SignerInfo_find_ex(st, val) SKM_sk_find_ex(CMS_SignerInfo, (st), (val)) -#define sk_CMS_SignerInfo_delete(st, i) SKM_sk_delete(CMS_SignerInfo, (st), (i)) -#define sk_CMS_SignerInfo_delete_ptr(st, ptr) SKM_sk_delete_ptr(CMS_SignerInfo, (st), (ptr)) -#define sk_CMS_SignerInfo_insert(st, val, i) SKM_sk_insert(CMS_SignerInfo, (st), (val), (i)) -#define sk_CMS_SignerInfo_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(CMS_SignerInfo, (st), (cmp)) -#define sk_CMS_SignerInfo_dup(st) SKM_sk_dup(CMS_SignerInfo, st) -#define sk_CMS_SignerInfo_pop_free(st, free_func) SKM_sk_pop_free(CMS_SignerInfo, (st), (free_func)) -#define sk_CMS_SignerInfo_shift(st) SKM_sk_shift(CMS_SignerInfo, (st)) -#define sk_CMS_SignerInfo_pop(st) SKM_sk_pop(CMS_SignerInfo, (st)) -#define sk_CMS_SignerInfo_sort(st) SKM_sk_sort(CMS_SignerInfo, (st)) -#define sk_CMS_SignerInfo_is_sorted(st) SKM_sk_is_sorted(CMS_SignerInfo, (st)) - -#define sk_CONF_IMODULE_new(cmp) SKM_sk_new(CONF_IMODULE, (cmp)) -#define sk_CONF_IMODULE_new_null() SKM_sk_new_null(CONF_IMODULE) -#define sk_CONF_IMODULE_free(st) SKM_sk_free(CONF_IMODULE, (st)) -#define sk_CONF_IMODULE_num(st) SKM_sk_num(CONF_IMODULE, (st)) -#define sk_CONF_IMODULE_value(st, i) SKM_sk_value(CONF_IMODULE, (st), (i)) -#define sk_CONF_IMODULE_set(st, i, val) SKM_sk_set(CONF_IMODULE, (st), (i), (val)) -#define sk_CONF_IMODULE_zero(st) SKM_sk_zero(CONF_IMODULE, (st)) -#define sk_CONF_IMODULE_push(st, val) SKM_sk_push(CONF_IMODULE, (st), (val)) -#define sk_CONF_IMODULE_unshift(st, val) SKM_sk_unshift(CONF_IMODULE, (st), (val)) -#define sk_CONF_IMODULE_find(st, val) SKM_sk_find(CONF_IMODULE, (st), (val)) -#define sk_CONF_IMODULE_find_ex(st, val) SKM_sk_find_ex(CONF_IMODULE, (st), (val)) -#define sk_CONF_IMODULE_delete(st, i) SKM_sk_delete(CONF_IMODULE, (st), (i)) -#define sk_CONF_IMODULE_delete_ptr(st, ptr) SKM_sk_delete_ptr(CONF_IMODULE, (st), (ptr)) -#define sk_CONF_IMODULE_insert(st, val, i) SKM_sk_insert(CONF_IMODULE, (st), (val), (i)) -#define sk_CONF_IMODULE_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(CONF_IMODULE, (st), (cmp)) -#define sk_CONF_IMODULE_dup(st) SKM_sk_dup(CONF_IMODULE, st) -#define sk_CONF_IMODULE_pop_free(st, free_func) SKM_sk_pop_free(CONF_IMODULE, (st), (free_func)) -#define sk_CONF_IMODULE_shift(st) SKM_sk_shift(CONF_IMODULE, (st)) -#define sk_CONF_IMODULE_pop(st) SKM_sk_pop(CONF_IMODULE, (st)) -#define sk_CONF_IMODULE_sort(st) SKM_sk_sort(CONF_IMODULE, (st)) -#define sk_CONF_IMODULE_is_sorted(st) SKM_sk_is_sorted(CONF_IMODULE, (st)) - -#define sk_CONF_MODULE_new(cmp) SKM_sk_new(CONF_MODULE, (cmp)) -#define sk_CONF_MODULE_new_null() SKM_sk_new_null(CONF_MODULE) -#define sk_CONF_MODULE_free(st) SKM_sk_free(CONF_MODULE, (st)) -#define sk_CONF_MODULE_num(st) SKM_sk_num(CONF_MODULE, (st)) -#define sk_CONF_MODULE_value(st, i) SKM_sk_value(CONF_MODULE, (st), (i)) -#define sk_CONF_MODULE_set(st, i, val) SKM_sk_set(CONF_MODULE, (st), (i), (val)) -#define sk_CONF_MODULE_zero(st) SKM_sk_zero(CONF_MODULE, (st)) -#define sk_CONF_MODULE_push(st, val) SKM_sk_push(CONF_MODULE, (st), (val)) -#define sk_CONF_MODULE_unshift(st, val) SKM_sk_unshift(CONF_MODULE, (st), (val)) -#define sk_CONF_MODULE_find(st, val) SKM_sk_find(CONF_MODULE, (st), (val)) -#define sk_CONF_MODULE_find_ex(st, val) SKM_sk_find_ex(CONF_MODULE, (st), (val)) -#define sk_CONF_MODULE_delete(st, i) SKM_sk_delete(CONF_MODULE, (st), (i)) -#define sk_CONF_MODULE_delete_ptr(st, ptr) SKM_sk_delete_ptr(CONF_MODULE, (st), (ptr)) -#define sk_CONF_MODULE_insert(st, val, i) SKM_sk_insert(CONF_MODULE, (st), (val), (i)) -#define sk_CONF_MODULE_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(CONF_MODULE, (st), (cmp)) -#define sk_CONF_MODULE_dup(st) SKM_sk_dup(CONF_MODULE, st) -#define sk_CONF_MODULE_pop_free(st, free_func) SKM_sk_pop_free(CONF_MODULE, (st), (free_func)) -#define sk_CONF_MODULE_shift(st) SKM_sk_shift(CONF_MODULE, (st)) -#define sk_CONF_MODULE_pop(st) SKM_sk_pop(CONF_MODULE, (st)) -#define sk_CONF_MODULE_sort(st) SKM_sk_sort(CONF_MODULE, (st)) -#define sk_CONF_MODULE_is_sorted(st) SKM_sk_is_sorted(CONF_MODULE, (st)) - -#define sk_CONF_VALUE_new(cmp) SKM_sk_new(CONF_VALUE, (cmp)) -#define sk_CONF_VALUE_new_null() SKM_sk_new_null(CONF_VALUE) -#define sk_CONF_VALUE_free(st) SKM_sk_free(CONF_VALUE, (st)) -#define sk_CONF_VALUE_num(st) SKM_sk_num(CONF_VALUE, (st)) -#define sk_CONF_VALUE_value(st, i) SKM_sk_value(CONF_VALUE, (st), (i)) -#define sk_CONF_VALUE_set(st, i, val) SKM_sk_set(CONF_VALUE, (st), (i), (val)) -#define sk_CONF_VALUE_zero(st) SKM_sk_zero(CONF_VALUE, (st)) -#define sk_CONF_VALUE_push(st, val) SKM_sk_push(CONF_VALUE, (st), (val)) -#define sk_CONF_VALUE_unshift(st, val) SKM_sk_unshift(CONF_VALUE, (st), (val)) -#define sk_CONF_VALUE_find(st, val) SKM_sk_find(CONF_VALUE, (st), (val)) -#define sk_CONF_VALUE_find_ex(st, val) SKM_sk_find_ex(CONF_VALUE, (st), (val)) -#define sk_CONF_VALUE_delete(st, i) SKM_sk_delete(CONF_VALUE, (st), (i)) -#define sk_CONF_VALUE_delete_ptr(st, ptr) SKM_sk_delete_ptr(CONF_VALUE, (st), (ptr)) -#define sk_CONF_VALUE_insert(st, val, i) SKM_sk_insert(CONF_VALUE, (st), (val), (i)) -#define sk_CONF_VALUE_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(CONF_VALUE, (st), (cmp)) -#define sk_CONF_VALUE_dup(st) SKM_sk_dup(CONF_VALUE, st) -#define sk_CONF_VALUE_pop_free(st, free_func) SKM_sk_pop_free(CONF_VALUE, (st), (free_func)) -#define sk_CONF_VALUE_shift(st) SKM_sk_shift(CONF_VALUE, (st)) -#define sk_CONF_VALUE_pop(st) SKM_sk_pop(CONF_VALUE, (st)) -#define sk_CONF_VALUE_sort(st) SKM_sk_sort(CONF_VALUE, (st)) -#define sk_CONF_VALUE_is_sorted(st) SKM_sk_is_sorted(CONF_VALUE, (st)) - -#define sk_CRYPTO_EX_DATA_FUNCS_new(cmp) SKM_sk_new(CRYPTO_EX_DATA_FUNCS, (cmp)) -#define sk_CRYPTO_EX_DATA_FUNCS_new_null() SKM_sk_new_null(CRYPTO_EX_DATA_FUNCS) -#define sk_CRYPTO_EX_DATA_FUNCS_free(st) SKM_sk_free(CRYPTO_EX_DATA_FUNCS, (st)) -#define sk_CRYPTO_EX_DATA_FUNCS_num(st) SKM_sk_num(CRYPTO_EX_DATA_FUNCS, (st)) -#define sk_CRYPTO_EX_DATA_FUNCS_value(st, i) SKM_sk_value(CRYPTO_EX_DATA_FUNCS, (st), (i)) -#define sk_CRYPTO_EX_DATA_FUNCS_set(st, i, val) SKM_sk_set(CRYPTO_EX_DATA_FUNCS, (st), (i), (val)) -#define sk_CRYPTO_EX_DATA_FUNCS_zero(st) SKM_sk_zero(CRYPTO_EX_DATA_FUNCS, (st)) -#define sk_CRYPTO_EX_DATA_FUNCS_push(st, val) SKM_sk_push(CRYPTO_EX_DATA_FUNCS, (st), (val)) -#define sk_CRYPTO_EX_DATA_FUNCS_unshift(st, val) SKM_sk_unshift(CRYPTO_EX_DATA_FUNCS, (st), (val)) -#define sk_CRYPTO_EX_DATA_FUNCS_find(st, val) SKM_sk_find(CRYPTO_EX_DATA_FUNCS, (st), (val)) -#define sk_CRYPTO_EX_DATA_FUNCS_find_ex(st, val) SKM_sk_find_ex(CRYPTO_EX_DATA_FUNCS, (st), (val)) -#define sk_CRYPTO_EX_DATA_FUNCS_delete(st, i) SKM_sk_delete(CRYPTO_EX_DATA_FUNCS, (st), (i)) -#define sk_CRYPTO_EX_DATA_FUNCS_delete_ptr(st, ptr) SKM_sk_delete_ptr(CRYPTO_EX_DATA_FUNCS, (st), (ptr)) -#define sk_CRYPTO_EX_DATA_FUNCS_insert(st, val, i) SKM_sk_insert(CRYPTO_EX_DATA_FUNCS, (st), (val), (i)) -#define sk_CRYPTO_EX_DATA_FUNCS_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(CRYPTO_EX_DATA_FUNCS, (st), (cmp)) -#define sk_CRYPTO_EX_DATA_FUNCS_dup(st) SKM_sk_dup(CRYPTO_EX_DATA_FUNCS, st) -#define sk_CRYPTO_EX_DATA_FUNCS_pop_free(st, free_func) SKM_sk_pop_free(CRYPTO_EX_DATA_FUNCS, (st), (free_func)) -#define sk_CRYPTO_EX_DATA_FUNCS_shift(st) SKM_sk_shift(CRYPTO_EX_DATA_FUNCS, (st)) -#define sk_CRYPTO_EX_DATA_FUNCS_pop(st) SKM_sk_pop(CRYPTO_EX_DATA_FUNCS, (st)) -#define sk_CRYPTO_EX_DATA_FUNCS_sort(st) SKM_sk_sort(CRYPTO_EX_DATA_FUNCS, (st)) -#define sk_CRYPTO_EX_DATA_FUNCS_is_sorted(st) SKM_sk_is_sorted(CRYPTO_EX_DATA_FUNCS, (st)) - -#define sk_CRYPTO_dynlock_new(cmp) SKM_sk_new(CRYPTO_dynlock, (cmp)) -#define sk_CRYPTO_dynlock_new_null() SKM_sk_new_null(CRYPTO_dynlock) -#define sk_CRYPTO_dynlock_free(st) SKM_sk_free(CRYPTO_dynlock, (st)) -#define sk_CRYPTO_dynlock_num(st) SKM_sk_num(CRYPTO_dynlock, (st)) -#define sk_CRYPTO_dynlock_value(st, i) SKM_sk_value(CRYPTO_dynlock, (st), (i)) -#define sk_CRYPTO_dynlock_set(st, i, val) SKM_sk_set(CRYPTO_dynlock, (st), (i), (val)) -#define sk_CRYPTO_dynlock_zero(st) SKM_sk_zero(CRYPTO_dynlock, (st)) -#define sk_CRYPTO_dynlock_push(st, val) SKM_sk_push(CRYPTO_dynlock, (st), (val)) -#define sk_CRYPTO_dynlock_unshift(st, val) SKM_sk_unshift(CRYPTO_dynlock, (st), (val)) -#define sk_CRYPTO_dynlock_find(st, val) SKM_sk_find(CRYPTO_dynlock, (st), (val)) -#define sk_CRYPTO_dynlock_find_ex(st, val) SKM_sk_find_ex(CRYPTO_dynlock, (st), (val)) -#define sk_CRYPTO_dynlock_delete(st, i) SKM_sk_delete(CRYPTO_dynlock, (st), (i)) -#define sk_CRYPTO_dynlock_delete_ptr(st, ptr) SKM_sk_delete_ptr(CRYPTO_dynlock, (st), (ptr)) -#define sk_CRYPTO_dynlock_insert(st, val, i) SKM_sk_insert(CRYPTO_dynlock, (st), (val), (i)) -#define sk_CRYPTO_dynlock_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(CRYPTO_dynlock, (st), (cmp)) -#define sk_CRYPTO_dynlock_dup(st) SKM_sk_dup(CRYPTO_dynlock, st) -#define sk_CRYPTO_dynlock_pop_free(st, free_func) SKM_sk_pop_free(CRYPTO_dynlock, (st), (free_func)) -#define sk_CRYPTO_dynlock_shift(st) SKM_sk_shift(CRYPTO_dynlock, (st)) -#define sk_CRYPTO_dynlock_pop(st) SKM_sk_pop(CRYPTO_dynlock, (st)) -#define sk_CRYPTO_dynlock_sort(st) SKM_sk_sort(CRYPTO_dynlock, (st)) -#define sk_CRYPTO_dynlock_is_sorted(st) SKM_sk_is_sorted(CRYPTO_dynlock, (st)) - -#define sk_DIST_POINT_new(cmp) SKM_sk_new(DIST_POINT, (cmp)) -#define sk_DIST_POINT_new_null() SKM_sk_new_null(DIST_POINT) -#define sk_DIST_POINT_free(st) SKM_sk_free(DIST_POINT, (st)) -#define sk_DIST_POINT_num(st) SKM_sk_num(DIST_POINT, (st)) -#define sk_DIST_POINT_value(st, i) SKM_sk_value(DIST_POINT, (st), (i)) -#define sk_DIST_POINT_set(st, i, val) SKM_sk_set(DIST_POINT, (st), (i), (val)) -#define sk_DIST_POINT_zero(st) SKM_sk_zero(DIST_POINT, (st)) -#define sk_DIST_POINT_push(st, val) SKM_sk_push(DIST_POINT, (st), (val)) -#define sk_DIST_POINT_unshift(st, val) SKM_sk_unshift(DIST_POINT, (st), (val)) -#define sk_DIST_POINT_find(st, val) SKM_sk_find(DIST_POINT, (st), (val)) -#define sk_DIST_POINT_find_ex(st, val) SKM_sk_find_ex(DIST_POINT, (st), (val)) -#define sk_DIST_POINT_delete(st, i) SKM_sk_delete(DIST_POINT, (st), (i)) -#define sk_DIST_POINT_delete_ptr(st, ptr) SKM_sk_delete_ptr(DIST_POINT, (st), (ptr)) -#define sk_DIST_POINT_insert(st, val, i) SKM_sk_insert(DIST_POINT, (st), (val), (i)) -#define sk_DIST_POINT_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(DIST_POINT, (st), (cmp)) -#define sk_DIST_POINT_dup(st) SKM_sk_dup(DIST_POINT, st) -#define sk_DIST_POINT_pop_free(st, free_func) SKM_sk_pop_free(DIST_POINT, (st), (free_func)) -#define sk_DIST_POINT_shift(st) SKM_sk_shift(DIST_POINT, (st)) -#define sk_DIST_POINT_pop(st) SKM_sk_pop(DIST_POINT, (st)) -#define sk_DIST_POINT_sort(st) SKM_sk_sort(DIST_POINT, (st)) -#define sk_DIST_POINT_is_sorted(st) SKM_sk_is_sorted(DIST_POINT, (st)) - -#define sk_ENGINE_new(cmp) SKM_sk_new(ENGINE, (cmp)) -#define sk_ENGINE_new_null() SKM_sk_new_null(ENGINE) -#define sk_ENGINE_free(st) SKM_sk_free(ENGINE, (st)) -#define sk_ENGINE_num(st) SKM_sk_num(ENGINE, (st)) -#define sk_ENGINE_value(st, i) SKM_sk_value(ENGINE, (st), (i)) -#define sk_ENGINE_set(st, i, val) SKM_sk_set(ENGINE, (st), (i), (val)) -#define sk_ENGINE_zero(st) SKM_sk_zero(ENGINE, (st)) -#define sk_ENGINE_push(st, val) SKM_sk_push(ENGINE, (st), (val)) -#define sk_ENGINE_unshift(st, val) SKM_sk_unshift(ENGINE, (st), (val)) -#define sk_ENGINE_find(st, val) SKM_sk_find(ENGINE, (st), (val)) -#define sk_ENGINE_find_ex(st, val) SKM_sk_find_ex(ENGINE, (st), (val)) -#define sk_ENGINE_delete(st, i) SKM_sk_delete(ENGINE, (st), (i)) -#define sk_ENGINE_delete_ptr(st, ptr) SKM_sk_delete_ptr(ENGINE, (st), (ptr)) -#define sk_ENGINE_insert(st, val, i) SKM_sk_insert(ENGINE, (st), (val), (i)) -#define sk_ENGINE_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(ENGINE, (st), (cmp)) -#define sk_ENGINE_dup(st) SKM_sk_dup(ENGINE, st) -#define sk_ENGINE_pop_free(st, free_func) SKM_sk_pop_free(ENGINE, (st), (free_func)) -#define sk_ENGINE_shift(st) SKM_sk_shift(ENGINE, (st)) -#define sk_ENGINE_pop(st) SKM_sk_pop(ENGINE, (st)) -#define sk_ENGINE_sort(st) SKM_sk_sort(ENGINE, (st)) -#define sk_ENGINE_is_sorted(st) SKM_sk_is_sorted(ENGINE, (st)) - -#define sk_ENGINE_CLEANUP_ITEM_new(cmp) SKM_sk_new(ENGINE_CLEANUP_ITEM, (cmp)) -#define sk_ENGINE_CLEANUP_ITEM_new_null() SKM_sk_new_null(ENGINE_CLEANUP_ITEM) -#define sk_ENGINE_CLEANUP_ITEM_free(st) SKM_sk_free(ENGINE_CLEANUP_ITEM, (st)) -#define sk_ENGINE_CLEANUP_ITEM_num(st) SKM_sk_num(ENGINE_CLEANUP_ITEM, (st)) -#define sk_ENGINE_CLEANUP_ITEM_value(st, i) SKM_sk_value(ENGINE_CLEANUP_ITEM, (st), (i)) -#define sk_ENGINE_CLEANUP_ITEM_set(st, i, val) SKM_sk_set(ENGINE_CLEANUP_ITEM, (st), (i), (val)) -#define sk_ENGINE_CLEANUP_ITEM_zero(st) SKM_sk_zero(ENGINE_CLEANUP_ITEM, (st)) -#define sk_ENGINE_CLEANUP_ITEM_push(st, val) SKM_sk_push(ENGINE_CLEANUP_ITEM, (st), (val)) -#define sk_ENGINE_CLEANUP_ITEM_unshift(st, val) SKM_sk_unshift(ENGINE_CLEANUP_ITEM, (st), (val)) -#define sk_ENGINE_CLEANUP_ITEM_find(st, val) SKM_sk_find(ENGINE_CLEANUP_ITEM, (st), (val)) -#define sk_ENGINE_CLEANUP_ITEM_find_ex(st, val) SKM_sk_find_ex(ENGINE_CLEANUP_ITEM, (st), (val)) -#define sk_ENGINE_CLEANUP_ITEM_delete(st, i) SKM_sk_delete(ENGINE_CLEANUP_ITEM, (st), (i)) -#define sk_ENGINE_CLEANUP_ITEM_delete_ptr(st, ptr) SKM_sk_delete_ptr(ENGINE_CLEANUP_ITEM, (st), (ptr)) -#define sk_ENGINE_CLEANUP_ITEM_insert(st, val, i) SKM_sk_insert(ENGINE_CLEANUP_ITEM, (st), (val), (i)) -#define sk_ENGINE_CLEANUP_ITEM_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(ENGINE_CLEANUP_ITEM, (st), (cmp)) -#define sk_ENGINE_CLEANUP_ITEM_dup(st) SKM_sk_dup(ENGINE_CLEANUP_ITEM, st) -#define sk_ENGINE_CLEANUP_ITEM_pop_free(st, free_func) SKM_sk_pop_free(ENGINE_CLEANUP_ITEM, (st), (free_func)) -#define sk_ENGINE_CLEANUP_ITEM_shift(st) SKM_sk_shift(ENGINE_CLEANUP_ITEM, (st)) -#define sk_ENGINE_CLEANUP_ITEM_pop(st) SKM_sk_pop(ENGINE_CLEANUP_ITEM, (st)) -#define sk_ENGINE_CLEANUP_ITEM_sort(st) SKM_sk_sort(ENGINE_CLEANUP_ITEM, (st)) -#define sk_ENGINE_CLEANUP_ITEM_is_sorted(st) SKM_sk_is_sorted(ENGINE_CLEANUP_ITEM, (st)) - -#define sk_ESS_CERT_ID_new(cmp) SKM_sk_new(ESS_CERT_ID, (cmp)) -#define sk_ESS_CERT_ID_new_null() SKM_sk_new_null(ESS_CERT_ID) -#define sk_ESS_CERT_ID_free(st) SKM_sk_free(ESS_CERT_ID, (st)) -#define sk_ESS_CERT_ID_num(st) SKM_sk_num(ESS_CERT_ID, (st)) -#define sk_ESS_CERT_ID_value(st, i) SKM_sk_value(ESS_CERT_ID, (st), (i)) -#define sk_ESS_CERT_ID_set(st, i, val) SKM_sk_set(ESS_CERT_ID, (st), (i), (val)) -#define sk_ESS_CERT_ID_zero(st) SKM_sk_zero(ESS_CERT_ID, (st)) -#define sk_ESS_CERT_ID_push(st, val) SKM_sk_push(ESS_CERT_ID, (st), (val)) -#define sk_ESS_CERT_ID_unshift(st, val) SKM_sk_unshift(ESS_CERT_ID, (st), (val)) -#define sk_ESS_CERT_ID_find(st, val) SKM_sk_find(ESS_CERT_ID, (st), (val)) -#define sk_ESS_CERT_ID_find_ex(st, val) SKM_sk_find_ex(ESS_CERT_ID, (st), (val)) -#define sk_ESS_CERT_ID_delete(st, i) SKM_sk_delete(ESS_CERT_ID, (st), (i)) -#define sk_ESS_CERT_ID_delete_ptr(st, ptr) SKM_sk_delete_ptr(ESS_CERT_ID, (st), (ptr)) -#define sk_ESS_CERT_ID_insert(st, val, i) SKM_sk_insert(ESS_CERT_ID, (st), (val), (i)) -#define sk_ESS_CERT_ID_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(ESS_CERT_ID, (st), (cmp)) -#define sk_ESS_CERT_ID_dup(st) SKM_sk_dup(ESS_CERT_ID, st) -#define sk_ESS_CERT_ID_pop_free(st, free_func) SKM_sk_pop_free(ESS_CERT_ID, (st), (free_func)) -#define sk_ESS_CERT_ID_shift(st) SKM_sk_shift(ESS_CERT_ID, (st)) -#define sk_ESS_CERT_ID_pop(st) SKM_sk_pop(ESS_CERT_ID, (st)) -#define sk_ESS_CERT_ID_sort(st) SKM_sk_sort(ESS_CERT_ID, (st)) -#define sk_ESS_CERT_ID_is_sorted(st) SKM_sk_is_sorted(ESS_CERT_ID, (st)) - -#define sk_EVP_MD_new(cmp) SKM_sk_new(EVP_MD, (cmp)) -#define sk_EVP_MD_new_null() SKM_sk_new_null(EVP_MD) -#define sk_EVP_MD_free(st) SKM_sk_free(EVP_MD, (st)) -#define sk_EVP_MD_num(st) SKM_sk_num(EVP_MD, (st)) -#define sk_EVP_MD_value(st, i) SKM_sk_value(EVP_MD, (st), (i)) -#define sk_EVP_MD_set(st, i, val) SKM_sk_set(EVP_MD, (st), (i), (val)) -#define sk_EVP_MD_zero(st) SKM_sk_zero(EVP_MD, (st)) -#define sk_EVP_MD_push(st, val) SKM_sk_push(EVP_MD, (st), (val)) -#define sk_EVP_MD_unshift(st, val) SKM_sk_unshift(EVP_MD, (st), (val)) -#define sk_EVP_MD_find(st, val) SKM_sk_find(EVP_MD, (st), (val)) -#define sk_EVP_MD_find_ex(st, val) SKM_sk_find_ex(EVP_MD, (st), (val)) -#define sk_EVP_MD_delete(st, i) SKM_sk_delete(EVP_MD, (st), (i)) -#define sk_EVP_MD_delete_ptr(st, ptr) SKM_sk_delete_ptr(EVP_MD, (st), (ptr)) -#define sk_EVP_MD_insert(st, val, i) SKM_sk_insert(EVP_MD, (st), (val), (i)) -#define sk_EVP_MD_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(EVP_MD, (st), (cmp)) -#define sk_EVP_MD_dup(st) SKM_sk_dup(EVP_MD, st) -#define sk_EVP_MD_pop_free(st, free_func) SKM_sk_pop_free(EVP_MD, (st), (free_func)) -#define sk_EVP_MD_shift(st) SKM_sk_shift(EVP_MD, (st)) -#define sk_EVP_MD_pop(st) SKM_sk_pop(EVP_MD, (st)) -#define sk_EVP_MD_sort(st) SKM_sk_sort(EVP_MD, (st)) -#define sk_EVP_MD_is_sorted(st) SKM_sk_is_sorted(EVP_MD, (st)) - -#define sk_EVP_PBE_CTL_new(cmp) SKM_sk_new(EVP_PBE_CTL, (cmp)) -#define sk_EVP_PBE_CTL_new_null() SKM_sk_new_null(EVP_PBE_CTL) -#define sk_EVP_PBE_CTL_free(st) SKM_sk_free(EVP_PBE_CTL, (st)) -#define sk_EVP_PBE_CTL_num(st) SKM_sk_num(EVP_PBE_CTL, (st)) -#define sk_EVP_PBE_CTL_value(st, i) SKM_sk_value(EVP_PBE_CTL, (st), (i)) -#define sk_EVP_PBE_CTL_set(st, i, val) SKM_sk_set(EVP_PBE_CTL, (st), (i), (val)) -#define sk_EVP_PBE_CTL_zero(st) SKM_sk_zero(EVP_PBE_CTL, (st)) -#define sk_EVP_PBE_CTL_push(st, val) SKM_sk_push(EVP_PBE_CTL, (st), (val)) -#define sk_EVP_PBE_CTL_unshift(st, val) SKM_sk_unshift(EVP_PBE_CTL, (st), (val)) -#define sk_EVP_PBE_CTL_find(st, val) SKM_sk_find(EVP_PBE_CTL, (st), (val)) -#define sk_EVP_PBE_CTL_find_ex(st, val) SKM_sk_find_ex(EVP_PBE_CTL, (st), (val)) -#define sk_EVP_PBE_CTL_delete(st, i) SKM_sk_delete(EVP_PBE_CTL, (st), (i)) -#define sk_EVP_PBE_CTL_delete_ptr(st, ptr) SKM_sk_delete_ptr(EVP_PBE_CTL, (st), (ptr)) -#define sk_EVP_PBE_CTL_insert(st, val, i) SKM_sk_insert(EVP_PBE_CTL, (st), (val), (i)) -#define sk_EVP_PBE_CTL_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(EVP_PBE_CTL, (st), (cmp)) -#define sk_EVP_PBE_CTL_dup(st) SKM_sk_dup(EVP_PBE_CTL, st) -#define sk_EVP_PBE_CTL_pop_free(st, free_func) SKM_sk_pop_free(EVP_PBE_CTL, (st), (free_func)) -#define sk_EVP_PBE_CTL_shift(st) SKM_sk_shift(EVP_PBE_CTL, (st)) -#define sk_EVP_PBE_CTL_pop(st) SKM_sk_pop(EVP_PBE_CTL, (st)) -#define sk_EVP_PBE_CTL_sort(st) SKM_sk_sort(EVP_PBE_CTL, (st)) -#define sk_EVP_PBE_CTL_is_sorted(st) SKM_sk_is_sorted(EVP_PBE_CTL, (st)) - -#define sk_EVP_PKEY_ASN1_METHOD_new(cmp) SKM_sk_new(EVP_PKEY_ASN1_METHOD, (cmp)) -#define sk_EVP_PKEY_ASN1_METHOD_new_null() SKM_sk_new_null(EVP_PKEY_ASN1_METHOD) -#define sk_EVP_PKEY_ASN1_METHOD_free(st) SKM_sk_free(EVP_PKEY_ASN1_METHOD, (st)) -#define sk_EVP_PKEY_ASN1_METHOD_num(st) SKM_sk_num(EVP_PKEY_ASN1_METHOD, (st)) -#define sk_EVP_PKEY_ASN1_METHOD_value(st, i) SKM_sk_value(EVP_PKEY_ASN1_METHOD, (st), (i)) -#define sk_EVP_PKEY_ASN1_METHOD_set(st, i, val) SKM_sk_set(EVP_PKEY_ASN1_METHOD, (st), (i), (val)) -#define sk_EVP_PKEY_ASN1_METHOD_zero(st) SKM_sk_zero(EVP_PKEY_ASN1_METHOD, (st)) -#define sk_EVP_PKEY_ASN1_METHOD_push(st, val) SKM_sk_push(EVP_PKEY_ASN1_METHOD, (st), (val)) -#define sk_EVP_PKEY_ASN1_METHOD_unshift(st, val) SKM_sk_unshift(EVP_PKEY_ASN1_METHOD, (st), (val)) -#define sk_EVP_PKEY_ASN1_METHOD_find(st, val) SKM_sk_find(EVP_PKEY_ASN1_METHOD, (st), (val)) -#define sk_EVP_PKEY_ASN1_METHOD_find_ex(st, val) SKM_sk_find_ex(EVP_PKEY_ASN1_METHOD, (st), (val)) -#define sk_EVP_PKEY_ASN1_METHOD_delete(st, i) SKM_sk_delete(EVP_PKEY_ASN1_METHOD, (st), (i)) -#define sk_EVP_PKEY_ASN1_METHOD_delete_ptr(st, ptr) SKM_sk_delete_ptr(EVP_PKEY_ASN1_METHOD, (st), (ptr)) -#define sk_EVP_PKEY_ASN1_METHOD_insert(st, val, i) SKM_sk_insert(EVP_PKEY_ASN1_METHOD, (st), (val), (i)) -#define sk_EVP_PKEY_ASN1_METHOD_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(EVP_PKEY_ASN1_METHOD, (st), (cmp)) -#define sk_EVP_PKEY_ASN1_METHOD_dup(st) SKM_sk_dup(EVP_PKEY_ASN1_METHOD, st) -#define sk_EVP_PKEY_ASN1_METHOD_pop_free(st, free_func) SKM_sk_pop_free(EVP_PKEY_ASN1_METHOD, (st), (free_func)) -#define sk_EVP_PKEY_ASN1_METHOD_shift(st) SKM_sk_shift(EVP_PKEY_ASN1_METHOD, (st)) -#define sk_EVP_PKEY_ASN1_METHOD_pop(st) SKM_sk_pop(EVP_PKEY_ASN1_METHOD, (st)) -#define sk_EVP_PKEY_ASN1_METHOD_sort(st) SKM_sk_sort(EVP_PKEY_ASN1_METHOD, (st)) -#define sk_EVP_PKEY_ASN1_METHOD_is_sorted(st) SKM_sk_is_sorted(EVP_PKEY_ASN1_METHOD, (st)) - -#define sk_EVP_PKEY_METHOD_new(cmp) SKM_sk_new(EVP_PKEY_METHOD, (cmp)) -#define sk_EVP_PKEY_METHOD_new_null() SKM_sk_new_null(EVP_PKEY_METHOD) -#define sk_EVP_PKEY_METHOD_free(st) SKM_sk_free(EVP_PKEY_METHOD, (st)) -#define sk_EVP_PKEY_METHOD_num(st) SKM_sk_num(EVP_PKEY_METHOD, (st)) -#define sk_EVP_PKEY_METHOD_value(st, i) SKM_sk_value(EVP_PKEY_METHOD, (st), (i)) -#define sk_EVP_PKEY_METHOD_set(st, i, val) SKM_sk_set(EVP_PKEY_METHOD, (st), (i), (val)) -#define sk_EVP_PKEY_METHOD_zero(st) SKM_sk_zero(EVP_PKEY_METHOD, (st)) -#define sk_EVP_PKEY_METHOD_push(st, val) SKM_sk_push(EVP_PKEY_METHOD, (st), (val)) -#define sk_EVP_PKEY_METHOD_unshift(st, val) SKM_sk_unshift(EVP_PKEY_METHOD, (st), (val)) -#define sk_EVP_PKEY_METHOD_find(st, val) SKM_sk_find(EVP_PKEY_METHOD, (st), (val)) -#define sk_EVP_PKEY_METHOD_find_ex(st, val) SKM_sk_find_ex(EVP_PKEY_METHOD, (st), (val)) -#define sk_EVP_PKEY_METHOD_delete(st, i) SKM_sk_delete(EVP_PKEY_METHOD, (st), (i)) -#define sk_EVP_PKEY_METHOD_delete_ptr(st, ptr) SKM_sk_delete_ptr(EVP_PKEY_METHOD, (st), (ptr)) -#define sk_EVP_PKEY_METHOD_insert(st, val, i) SKM_sk_insert(EVP_PKEY_METHOD, (st), (val), (i)) -#define sk_EVP_PKEY_METHOD_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(EVP_PKEY_METHOD, (st), (cmp)) -#define sk_EVP_PKEY_METHOD_dup(st) SKM_sk_dup(EVP_PKEY_METHOD, st) -#define sk_EVP_PKEY_METHOD_pop_free(st, free_func) SKM_sk_pop_free(EVP_PKEY_METHOD, (st), (free_func)) -#define sk_EVP_PKEY_METHOD_shift(st) SKM_sk_shift(EVP_PKEY_METHOD, (st)) -#define sk_EVP_PKEY_METHOD_pop(st) SKM_sk_pop(EVP_PKEY_METHOD, (st)) -#define sk_EVP_PKEY_METHOD_sort(st) SKM_sk_sort(EVP_PKEY_METHOD, (st)) -#define sk_EVP_PKEY_METHOD_is_sorted(st) SKM_sk_is_sorted(EVP_PKEY_METHOD, (st)) - -#define sk_GENERAL_NAME_new(cmp) SKM_sk_new(GENERAL_NAME, (cmp)) -#define sk_GENERAL_NAME_new_null() SKM_sk_new_null(GENERAL_NAME) -#define sk_GENERAL_NAME_free(st) SKM_sk_free(GENERAL_NAME, (st)) -#define sk_GENERAL_NAME_num(st) SKM_sk_num(GENERAL_NAME, (st)) -#define sk_GENERAL_NAME_value(st, i) SKM_sk_value(GENERAL_NAME, (st), (i)) -#define sk_GENERAL_NAME_set(st, i, val) SKM_sk_set(GENERAL_NAME, (st), (i), (val)) -#define sk_GENERAL_NAME_zero(st) SKM_sk_zero(GENERAL_NAME, (st)) -#define sk_GENERAL_NAME_push(st, val) SKM_sk_push(GENERAL_NAME, (st), (val)) -#define sk_GENERAL_NAME_unshift(st, val) SKM_sk_unshift(GENERAL_NAME, (st), (val)) -#define sk_GENERAL_NAME_find(st, val) SKM_sk_find(GENERAL_NAME, (st), (val)) -#define sk_GENERAL_NAME_find_ex(st, val) SKM_sk_find_ex(GENERAL_NAME, (st), (val)) -#define sk_GENERAL_NAME_delete(st, i) SKM_sk_delete(GENERAL_NAME, (st), (i)) -#define sk_GENERAL_NAME_delete_ptr(st, ptr) SKM_sk_delete_ptr(GENERAL_NAME, (st), (ptr)) -#define sk_GENERAL_NAME_insert(st, val, i) SKM_sk_insert(GENERAL_NAME, (st), (val), (i)) -#define sk_GENERAL_NAME_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(GENERAL_NAME, (st), (cmp)) -#define sk_GENERAL_NAME_dup(st) SKM_sk_dup(GENERAL_NAME, st) -#define sk_GENERAL_NAME_pop_free(st, free_func) SKM_sk_pop_free(GENERAL_NAME, (st), (free_func)) -#define sk_GENERAL_NAME_shift(st) SKM_sk_shift(GENERAL_NAME, (st)) -#define sk_GENERAL_NAME_pop(st) SKM_sk_pop(GENERAL_NAME, (st)) -#define sk_GENERAL_NAME_sort(st) SKM_sk_sort(GENERAL_NAME, (st)) -#define sk_GENERAL_NAME_is_sorted(st) SKM_sk_is_sorted(GENERAL_NAME, (st)) - -#define sk_GENERAL_NAMES_new(cmp) SKM_sk_new(GENERAL_NAMES, (cmp)) -#define sk_GENERAL_NAMES_new_null() SKM_sk_new_null(GENERAL_NAMES) -#define sk_GENERAL_NAMES_free(st) SKM_sk_free(GENERAL_NAMES, (st)) -#define sk_GENERAL_NAMES_num(st) SKM_sk_num(GENERAL_NAMES, (st)) -#define sk_GENERAL_NAMES_value(st, i) SKM_sk_value(GENERAL_NAMES, (st), (i)) -#define sk_GENERAL_NAMES_set(st, i, val) SKM_sk_set(GENERAL_NAMES, (st), (i), (val)) -#define sk_GENERAL_NAMES_zero(st) SKM_sk_zero(GENERAL_NAMES, (st)) -#define sk_GENERAL_NAMES_push(st, val) SKM_sk_push(GENERAL_NAMES, (st), (val)) -#define sk_GENERAL_NAMES_unshift(st, val) SKM_sk_unshift(GENERAL_NAMES, (st), (val)) -#define sk_GENERAL_NAMES_find(st, val) SKM_sk_find(GENERAL_NAMES, (st), (val)) -#define sk_GENERAL_NAMES_find_ex(st, val) SKM_sk_find_ex(GENERAL_NAMES, (st), (val)) -#define sk_GENERAL_NAMES_delete(st, i) SKM_sk_delete(GENERAL_NAMES, (st), (i)) -#define sk_GENERAL_NAMES_delete_ptr(st, ptr) SKM_sk_delete_ptr(GENERAL_NAMES, (st), (ptr)) -#define sk_GENERAL_NAMES_insert(st, val, i) SKM_sk_insert(GENERAL_NAMES, (st), (val), (i)) -#define sk_GENERAL_NAMES_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(GENERAL_NAMES, (st), (cmp)) -#define sk_GENERAL_NAMES_dup(st) SKM_sk_dup(GENERAL_NAMES, st) -#define sk_GENERAL_NAMES_pop_free(st, free_func) SKM_sk_pop_free(GENERAL_NAMES, (st), (free_func)) -#define sk_GENERAL_NAMES_shift(st) SKM_sk_shift(GENERAL_NAMES, (st)) -#define sk_GENERAL_NAMES_pop(st) SKM_sk_pop(GENERAL_NAMES, (st)) -#define sk_GENERAL_NAMES_sort(st) SKM_sk_sort(GENERAL_NAMES, (st)) -#define sk_GENERAL_NAMES_is_sorted(st) SKM_sk_is_sorted(GENERAL_NAMES, (st)) - -#define sk_GENERAL_SUBTREE_new(cmp) SKM_sk_new(GENERAL_SUBTREE, (cmp)) -#define sk_GENERAL_SUBTREE_new_null() SKM_sk_new_null(GENERAL_SUBTREE) -#define sk_GENERAL_SUBTREE_free(st) SKM_sk_free(GENERAL_SUBTREE, (st)) -#define sk_GENERAL_SUBTREE_num(st) SKM_sk_num(GENERAL_SUBTREE, (st)) -#define sk_GENERAL_SUBTREE_value(st, i) SKM_sk_value(GENERAL_SUBTREE, (st), (i)) -#define sk_GENERAL_SUBTREE_set(st, i, val) SKM_sk_set(GENERAL_SUBTREE, (st), (i), (val)) -#define sk_GENERAL_SUBTREE_zero(st) SKM_sk_zero(GENERAL_SUBTREE, (st)) -#define sk_GENERAL_SUBTREE_push(st, val) SKM_sk_push(GENERAL_SUBTREE, (st), (val)) -#define sk_GENERAL_SUBTREE_unshift(st, val) SKM_sk_unshift(GENERAL_SUBTREE, (st), (val)) -#define sk_GENERAL_SUBTREE_find(st, val) SKM_sk_find(GENERAL_SUBTREE, (st), (val)) -#define sk_GENERAL_SUBTREE_find_ex(st, val) SKM_sk_find_ex(GENERAL_SUBTREE, (st), (val)) -#define sk_GENERAL_SUBTREE_delete(st, i) SKM_sk_delete(GENERAL_SUBTREE, (st), (i)) -#define sk_GENERAL_SUBTREE_delete_ptr(st, ptr) SKM_sk_delete_ptr(GENERAL_SUBTREE, (st), (ptr)) -#define sk_GENERAL_SUBTREE_insert(st, val, i) SKM_sk_insert(GENERAL_SUBTREE, (st), (val), (i)) -#define sk_GENERAL_SUBTREE_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(GENERAL_SUBTREE, (st), (cmp)) -#define sk_GENERAL_SUBTREE_dup(st) SKM_sk_dup(GENERAL_SUBTREE, st) -#define sk_GENERAL_SUBTREE_pop_free(st, free_func) SKM_sk_pop_free(GENERAL_SUBTREE, (st), (free_func)) -#define sk_GENERAL_SUBTREE_shift(st) SKM_sk_shift(GENERAL_SUBTREE, (st)) -#define sk_GENERAL_SUBTREE_pop(st) SKM_sk_pop(GENERAL_SUBTREE, (st)) -#define sk_GENERAL_SUBTREE_sort(st) SKM_sk_sort(GENERAL_SUBTREE, (st)) -#define sk_GENERAL_SUBTREE_is_sorted(st) SKM_sk_is_sorted(GENERAL_SUBTREE, (st)) - -#define sk_IPAddressFamily_new(cmp) SKM_sk_new(IPAddressFamily, (cmp)) -#define sk_IPAddressFamily_new_null() SKM_sk_new_null(IPAddressFamily) -#define sk_IPAddressFamily_free(st) SKM_sk_free(IPAddressFamily, (st)) -#define sk_IPAddressFamily_num(st) SKM_sk_num(IPAddressFamily, (st)) -#define sk_IPAddressFamily_value(st, i) SKM_sk_value(IPAddressFamily, (st), (i)) -#define sk_IPAddressFamily_set(st, i, val) SKM_sk_set(IPAddressFamily, (st), (i), (val)) -#define sk_IPAddressFamily_zero(st) SKM_sk_zero(IPAddressFamily, (st)) -#define sk_IPAddressFamily_push(st, val) SKM_sk_push(IPAddressFamily, (st), (val)) -#define sk_IPAddressFamily_unshift(st, val) SKM_sk_unshift(IPAddressFamily, (st), (val)) -#define sk_IPAddressFamily_find(st, val) SKM_sk_find(IPAddressFamily, (st), (val)) -#define sk_IPAddressFamily_find_ex(st, val) SKM_sk_find_ex(IPAddressFamily, (st), (val)) -#define sk_IPAddressFamily_delete(st, i) SKM_sk_delete(IPAddressFamily, (st), (i)) -#define sk_IPAddressFamily_delete_ptr(st, ptr) SKM_sk_delete_ptr(IPAddressFamily, (st), (ptr)) -#define sk_IPAddressFamily_insert(st, val, i) SKM_sk_insert(IPAddressFamily, (st), (val), (i)) -#define sk_IPAddressFamily_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(IPAddressFamily, (st), (cmp)) -#define sk_IPAddressFamily_dup(st) SKM_sk_dup(IPAddressFamily, st) -#define sk_IPAddressFamily_pop_free(st, free_func) SKM_sk_pop_free(IPAddressFamily, (st), (free_func)) -#define sk_IPAddressFamily_shift(st) SKM_sk_shift(IPAddressFamily, (st)) -#define sk_IPAddressFamily_pop(st) SKM_sk_pop(IPAddressFamily, (st)) -#define sk_IPAddressFamily_sort(st) SKM_sk_sort(IPAddressFamily, (st)) -#define sk_IPAddressFamily_is_sorted(st) SKM_sk_is_sorted(IPAddressFamily, (st)) - -#define sk_IPAddressOrRange_new(cmp) SKM_sk_new(IPAddressOrRange, (cmp)) -#define sk_IPAddressOrRange_new_null() SKM_sk_new_null(IPAddressOrRange) -#define sk_IPAddressOrRange_free(st) SKM_sk_free(IPAddressOrRange, (st)) -#define sk_IPAddressOrRange_num(st) SKM_sk_num(IPAddressOrRange, (st)) -#define sk_IPAddressOrRange_value(st, i) SKM_sk_value(IPAddressOrRange, (st), (i)) -#define sk_IPAddressOrRange_set(st, i, val) SKM_sk_set(IPAddressOrRange, (st), (i), (val)) -#define sk_IPAddressOrRange_zero(st) SKM_sk_zero(IPAddressOrRange, (st)) -#define sk_IPAddressOrRange_push(st, val) SKM_sk_push(IPAddressOrRange, (st), (val)) -#define sk_IPAddressOrRange_unshift(st, val) SKM_sk_unshift(IPAddressOrRange, (st), (val)) -#define sk_IPAddressOrRange_find(st, val) SKM_sk_find(IPAddressOrRange, (st), (val)) -#define sk_IPAddressOrRange_find_ex(st, val) SKM_sk_find_ex(IPAddressOrRange, (st), (val)) -#define sk_IPAddressOrRange_delete(st, i) SKM_sk_delete(IPAddressOrRange, (st), (i)) -#define sk_IPAddressOrRange_delete_ptr(st, ptr) SKM_sk_delete_ptr(IPAddressOrRange, (st), (ptr)) -#define sk_IPAddressOrRange_insert(st, val, i) SKM_sk_insert(IPAddressOrRange, (st), (val), (i)) -#define sk_IPAddressOrRange_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(IPAddressOrRange, (st), (cmp)) -#define sk_IPAddressOrRange_dup(st) SKM_sk_dup(IPAddressOrRange, st) -#define sk_IPAddressOrRange_pop_free(st, free_func) SKM_sk_pop_free(IPAddressOrRange, (st), (free_func)) -#define sk_IPAddressOrRange_shift(st) SKM_sk_shift(IPAddressOrRange, (st)) -#define sk_IPAddressOrRange_pop(st) SKM_sk_pop(IPAddressOrRange, (st)) -#define sk_IPAddressOrRange_sort(st) SKM_sk_sort(IPAddressOrRange, (st)) -#define sk_IPAddressOrRange_is_sorted(st) SKM_sk_is_sorted(IPAddressOrRange, (st)) - -#define sk_KRB5_APREQBODY_new(cmp) SKM_sk_new(KRB5_APREQBODY, (cmp)) -#define sk_KRB5_APREQBODY_new_null() SKM_sk_new_null(KRB5_APREQBODY) -#define sk_KRB5_APREQBODY_free(st) SKM_sk_free(KRB5_APREQBODY, (st)) -#define sk_KRB5_APREQBODY_num(st) SKM_sk_num(KRB5_APREQBODY, (st)) -#define sk_KRB5_APREQBODY_value(st, i) SKM_sk_value(KRB5_APREQBODY, (st), (i)) -#define sk_KRB5_APREQBODY_set(st, i, val) SKM_sk_set(KRB5_APREQBODY, (st), (i), (val)) -#define sk_KRB5_APREQBODY_zero(st) SKM_sk_zero(KRB5_APREQBODY, (st)) -#define sk_KRB5_APREQBODY_push(st, val) SKM_sk_push(KRB5_APREQBODY, (st), (val)) -#define sk_KRB5_APREQBODY_unshift(st, val) SKM_sk_unshift(KRB5_APREQBODY, (st), (val)) -#define sk_KRB5_APREQBODY_find(st, val) SKM_sk_find(KRB5_APREQBODY, (st), (val)) -#define sk_KRB5_APREQBODY_find_ex(st, val) SKM_sk_find_ex(KRB5_APREQBODY, (st), (val)) -#define sk_KRB5_APREQBODY_delete(st, i) SKM_sk_delete(KRB5_APREQBODY, (st), (i)) -#define sk_KRB5_APREQBODY_delete_ptr(st, ptr) SKM_sk_delete_ptr(KRB5_APREQBODY, (st), (ptr)) -#define sk_KRB5_APREQBODY_insert(st, val, i) SKM_sk_insert(KRB5_APREQBODY, (st), (val), (i)) -#define sk_KRB5_APREQBODY_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(KRB5_APREQBODY, (st), (cmp)) -#define sk_KRB5_APREQBODY_dup(st) SKM_sk_dup(KRB5_APREQBODY, st) -#define sk_KRB5_APREQBODY_pop_free(st, free_func) SKM_sk_pop_free(KRB5_APREQBODY, (st), (free_func)) -#define sk_KRB5_APREQBODY_shift(st) SKM_sk_shift(KRB5_APREQBODY, (st)) -#define sk_KRB5_APREQBODY_pop(st) SKM_sk_pop(KRB5_APREQBODY, (st)) -#define sk_KRB5_APREQBODY_sort(st) SKM_sk_sort(KRB5_APREQBODY, (st)) -#define sk_KRB5_APREQBODY_is_sorted(st) SKM_sk_is_sorted(KRB5_APREQBODY, (st)) - -#define sk_KRB5_AUTHDATA_new(cmp) SKM_sk_new(KRB5_AUTHDATA, (cmp)) -#define sk_KRB5_AUTHDATA_new_null() SKM_sk_new_null(KRB5_AUTHDATA) -#define sk_KRB5_AUTHDATA_free(st) SKM_sk_free(KRB5_AUTHDATA, (st)) -#define sk_KRB5_AUTHDATA_num(st) SKM_sk_num(KRB5_AUTHDATA, (st)) -#define sk_KRB5_AUTHDATA_value(st, i) SKM_sk_value(KRB5_AUTHDATA, (st), (i)) -#define sk_KRB5_AUTHDATA_set(st, i, val) SKM_sk_set(KRB5_AUTHDATA, (st), (i), (val)) -#define sk_KRB5_AUTHDATA_zero(st) SKM_sk_zero(KRB5_AUTHDATA, (st)) -#define sk_KRB5_AUTHDATA_push(st, val) SKM_sk_push(KRB5_AUTHDATA, (st), (val)) -#define sk_KRB5_AUTHDATA_unshift(st, val) SKM_sk_unshift(KRB5_AUTHDATA, (st), (val)) -#define sk_KRB5_AUTHDATA_find(st, val) SKM_sk_find(KRB5_AUTHDATA, (st), (val)) -#define sk_KRB5_AUTHDATA_find_ex(st, val) SKM_sk_find_ex(KRB5_AUTHDATA, (st), (val)) -#define sk_KRB5_AUTHDATA_delete(st, i) SKM_sk_delete(KRB5_AUTHDATA, (st), (i)) -#define sk_KRB5_AUTHDATA_delete_ptr(st, ptr) SKM_sk_delete_ptr(KRB5_AUTHDATA, (st), (ptr)) -#define sk_KRB5_AUTHDATA_insert(st, val, i) SKM_sk_insert(KRB5_AUTHDATA, (st), (val), (i)) -#define sk_KRB5_AUTHDATA_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(KRB5_AUTHDATA, (st), (cmp)) -#define sk_KRB5_AUTHDATA_dup(st) SKM_sk_dup(KRB5_AUTHDATA, st) -#define sk_KRB5_AUTHDATA_pop_free(st, free_func) SKM_sk_pop_free(KRB5_AUTHDATA, (st), (free_func)) -#define sk_KRB5_AUTHDATA_shift(st) SKM_sk_shift(KRB5_AUTHDATA, (st)) -#define sk_KRB5_AUTHDATA_pop(st) SKM_sk_pop(KRB5_AUTHDATA, (st)) -#define sk_KRB5_AUTHDATA_sort(st) SKM_sk_sort(KRB5_AUTHDATA, (st)) -#define sk_KRB5_AUTHDATA_is_sorted(st) SKM_sk_is_sorted(KRB5_AUTHDATA, (st)) - -#define sk_KRB5_AUTHENTBODY_new(cmp) SKM_sk_new(KRB5_AUTHENTBODY, (cmp)) -#define sk_KRB5_AUTHENTBODY_new_null() SKM_sk_new_null(KRB5_AUTHENTBODY) -#define sk_KRB5_AUTHENTBODY_free(st) SKM_sk_free(KRB5_AUTHENTBODY, (st)) -#define sk_KRB5_AUTHENTBODY_num(st) SKM_sk_num(KRB5_AUTHENTBODY, (st)) -#define sk_KRB5_AUTHENTBODY_value(st, i) SKM_sk_value(KRB5_AUTHENTBODY, (st), (i)) -#define sk_KRB5_AUTHENTBODY_set(st, i, val) SKM_sk_set(KRB5_AUTHENTBODY, (st), (i), (val)) -#define sk_KRB5_AUTHENTBODY_zero(st) SKM_sk_zero(KRB5_AUTHENTBODY, (st)) -#define sk_KRB5_AUTHENTBODY_push(st, val) SKM_sk_push(KRB5_AUTHENTBODY, (st), (val)) -#define sk_KRB5_AUTHENTBODY_unshift(st, val) SKM_sk_unshift(KRB5_AUTHENTBODY, (st), (val)) -#define sk_KRB5_AUTHENTBODY_find(st, val) SKM_sk_find(KRB5_AUTHENTBODY, (st), (val)) -#define sk_KRB5_AUTHENTBODY_find_ex(st, val) SKM_sk_find_ex(KRB5_AUTHENTBODY, (st), (val)) -#define sk_KRB5_AUTHENTBODY_delete(st, i) SKM_sk_delete(KRB5_AUTHENTBODY, (st), (i)) -#define sk_KRB5_AUTHENTBODY_delete_ptr(st, ptr) SKM_sk_delete_ptr(KRB5_AUTHENTBODY, (st), (ptr)) -#define sk_KRB5_AUTHENTBODY_insert(st, val, i) SKM_sk_insert(KRB5_AUTHENTBODY, (st), (val), (i)) -#define sk_KRB5_AUTHENTBODY_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(KRB5_AUTHENTBODY, (st), (cmp)) -#define sk_KRB5_AUTHENTBODY_dup(st) SKM_sk_dup(KRB5_AUTHENTBODY, st) -#define sk_KRB5_AUTHENTBODY_pop_free(st, free_func) SKM_sk_pop_free(KRB5_AUTHENTBODY, (st), (free_func)) -#define sk_KRB5_AUTHENTBODY_shift(st) SKM_sk_shift(KRB5_AUTHENTBODY, (st)) -#define sk_KRB5_AUTHENTBODY_pop(st) SKM_sk_pop(KRB5_AUTHENTBODY, (st)) -#define sk_KRB5_AUTHENTBODY_sort(st) SKM_sk_sort(KRB5_AUTHENTBODY, (st)) -#define sk_KRB5_AUTHENTBODY_is_sorted(st) SKM_sk_is_sorted(KRB5_AUTHENTBODY, (st)) - -#define sk_KRB5_CHECKSUM_new(cmp) SKM_sk_new(KRB5_CHECKSUM, (cmp)) -#define sk_KRB5_CHECKSUM_new_null() SKM_sk_new_null(KRB5_CHECKSUM) -#define sk_KRB5_CHECKSUM_free(st) SKM_sk_free(KRB5_CHECKSUM, (st)) -#define sk_KRB5_CHECKSUM_num(st) SKM_sk_num(KRB5_CHECKSUM, (st)) -#define sk_KRB5_CHECKSUM_value(st, i) SKM_sk_value(KRB5_CHECKSUM, (st), (i)) -#define sk_KRB5_CHECKSUM_set(st, i, val) SKM_sk_set(KRB5_CHECKSUM, (st), (i), (val)) -#define sk_KRB5_CHECKSUM_zero(st) SKM_sk_zero(KRB5_CHECKSUM, (st)) -#define sk_KRB5_CHECKSUM_push(st, val) SKM_sk_push(KRB5_CHECKSUM, (st), (val)) -#define sk_KRB5_CHECKSUM_unshift(st, val) SKM_sk_unshift(KRB5_CHECKSUM, (st), (val)) -#define sk_KRB5_CHECKSUM_find(st, val) SKM_sk_find(KRB5_CHECKSUM, (st), (val)) -#define sk_KRB5_CHECKSUM_find_ex(st, val) SKM_sk_find_ex(KRB5_CHECKSUM, (st), (val)) -#define sk_KRB5_CHECKSUM_delete(st, i) SKM_sk_delete(KRB5_CHECKSUM, (st), (i)) -#define sk_KRB5_CHECKSUM_delete_ptr(st, ptr) SKM_sk_delete_ptr(KRB5_CHECKSUM, (st), (ptr)) -#define sk_KRB5_CHECKSUM_insert(st, val, i) SKM_sk_insert(KRB5_CHECKSUM, (st), (val), (i)) -#define sk_KRB5_CHECKSUM_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(KRB5_CHECKSUM, (st), (cmp)) -#define sk_KRB5_CHECKSUM_dup(st) SKM_sk_dup(KRB5_CHECKSUM, st) -#define sk_KRB5_CHECKSUM_pop_free(st, free_func) SKM_sk_pop_free(KRB5_CHECKSUM, (st), (free_func)) -#define sk_KRB5_CHECKSUM_shift(st) SKM_sk_shift(KRB5_CHECKSUM, (st)) -#define sk_KRB5_CHECKSUM_pop(st) SKM_sk_pop(KRB5_CHECKSUM, (st)) -#define sk_KRB5_CHECKSUM_sort(st) SKM_sk_sort(KRB5_CHECKSUM, (st)) -#define sk_KRB5_CHECKSUM_is_sorted(st) SKM_sk_is_sorted(KRB5_CHECKSUM, (st)) - -#define sk_KRB5_ENCDATA_new(cmp) SKM_sk_new(KRB5_ENCDATA, (cmp)) -#define sk_KRB5_ENCDATA_new_null() SKM_sk_new_null(KRB5_ENCDATA) -#define sk_KRB5_ENCDATA_free(st) SKM_sk_free(KRB5_ENCDATA, (st)) -#define sk_KRB5_ENCDATA_num(st) SKM_sk_num(KRB5_ENCDATA, (st)) -#define sk_KRB5_ENCDATA_value(st, i) SKM_sk_value(KRB5_ENCDATA, (st), (i)) -#define sk_KRB5_ENCDATA_set(st, i, val) SKM_sk_set(KRB5_ENCDATA, (st), (i), (val)) -#define sk_KRB5_ENCDATA_zero(st) SKM_sk_zero(KRB5_ENCDATA, (st)) -#define sk_KRB5_ENCDATA_push(st, val) SKM_sk_push(KRB5_ENCDATA, (st), (val)) -#define sk_KRB5_ENCDATA_unshift(st, val) SKM_sk_unshift(KRB5_ENCDATA, (st), (val)) -#define sk_KRB5_ENCDATA_find(st, val) SKM_sk_find(KRB5_ENCDATA, (st), (val)) -#define sk_KRB5_ENCDATA_find_ex(st, val) SKM_sk_find_ex(KRB5_ENCDATA, (st), (val)) -#define sk_KRB5_ENCDATA_delete(st, i) SKM_sk_delete(KRB5_ENCDATA, (st), (i)) -#define sk_KRB5_ENCDATA_delete_ptr(st, ptr) SKM_sk_delete_ptr(KRB5_ENCDATA, (st), (ptr)) -#define sk_KRB5_ENCDATA_insert(st, val, i) SKM_sk_insert(KRB5_ENCDATA, (st), (val), (i)) -#define sk_KRB5_ENCDATA_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(KRB5_ENCDATA, (st), (cmp)) -#define sk_KRB5_ENCDATA_dup(st) SKM_sk_dup(KRB5_ENCDATA, st) -#define sk_KRB5_ENCDATA_pop_free(st, free_func) SKM_sk_pop_free(KRB5_ENCDATA, (st), (free_func)) -#define sk_KRB5_ENCDATA_shift(st) SKM_sk_shift(KRB5_ENCDATA, (st)) -#define sk_KRB5_ENCDATA_pop(st) SKM_sk_pop(KRB5_ENCDATA, (st)) -#define sk_KRB5_ENCDATA_sort(st) SKM_sk_sort(KRB5_ENCDATA, (st)) -#define sk_KRB5_ENCDATA_is_sorted(st) SKM_sk_is_sorted(KRB5_ENCDATA, (st)) - -#define sk_KRB5_ENCKEY_new(cmp) SKM_sk_new(KRB5_ENCKEY, (cmp)) -#define sk_KRB5_ENCKEY_new_null() SKM_sk_new_null(KRB5_ENCKEY) -#define sk_KRB5_ENCKEY_free(st) SKM_sk_free(KRB5_ENCKEY, (st)) -#define sk_KRB5_ENCKEY_num(st) SKM_sk_num(KRB5_ENCKEY, (st)) -#define sk_KRB5_ENCKEY_value(st, i) SKM_sk_value(KRB5_ENCKEY, (st), (i)) -#define sk_KRB5_ENCKEY_set(st, i, val) SKM_sk_set(KRB5_ENCKEY, (st), (i), (val)) -#define sk_KRB5_ENCKEY_zero(st) SKM_sk_zero(KRB5_ENCKEY, (st)) -#define sk_KRB5_ENCKEY_push(st, val) SKM_sk_push(KRB5_ENCKEY, (st), (val)) -#define sk_KRB5_ENCKEY_unshift(st, val) SKM_sk_unshift(KRB5_ENCKEY, (st), (val)) -#define sk_KRB5_ENCKEY_find(st, val) SKM_sk_find(KRB5_ENCKEY, (st), (val)) -#define sk_KRB5_ENCKEY_find_ex(st, val) SKM_sk_find_ex(KRB5_ENCKEY, (st), (val)) -#define sk_KRB5_ENCKEY_delete(st, i) SKM_sk_delete(KRB5_ENCKEY, (st), (i)) -#define sk_KRB5_ENCKEY_delete_ptr(st, ptr) SKM_sk_delete_ptr(KRB5_ENCKEY, (st), (ptr)) -#define sk_KRB5_ENCKEY_insert(st, val, i) SKM_sk_insert(KRB5_ENCKEY, (st), (val), (i)) -#define sk_KRB5_ENCKEY_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(KRB5_ENCKEY, (st), (cmp)) -#define sk_KRB5_ENCKEY_dup(st) SKM_sk_dup(KRB5_ENCKEY, st) -#define sk_KRB5_ENCKEY_pop_free(st, free_func) SKM_sk_pop_free(KRB5_ENCKEY, (st), (free_func)) -#define sk_KRB5_ENCKEY_shift(st) SKM_sk_shift(KRB5_ENCKEY, (st)) -#define sk_KRB5_ENCKEY_pop(st) SKM_sk_pop(KRB5_ENCKEY, (st)) -#define sk_KRB5_ENCKEY_sort(st) SKM_sk_sort(KRB5_ENCKEY, (st)) -#define sk_KRB5_ENCKEY_is_sorted(st) SKM_sk_is_sorted(KRB5_ENCKEY, (st)) - -#define sk_KRB5_PRINCNAME_new(cmp) SKM_sk_new(KRB5_PRINCNAME, (cmp)) -#define sk_KRB5_PRINCNAME_new_null() SKM_sk_new_null(KRB5_PRINCNAME) -#define sk_KRB5_PRINCNAME_free(st) SKM_sk_free(KRB5_PRINCNAME, (st)) -#define sk_KRB5_PRINCNAME_num(st) SKM_sk_num(KRB5_PRINCNAME, (st)) -#define sk_KRB5_PRINCNAME_value(st, i) SKM_sk_value(KRB5_PRINCNAME, (st), (i)) -#define sk_KRB5_PRINCNAME_set(st, i, val) SKM_sk_set(KRB5_PRINCNAME, (st), (i), (val)) -#define sk_KRB5_PRINCNAME_zero(st) SKM_sk_zero(KRB5_PRINCNAME, (st)) -#define sk_KRB5_PRINCNAME_push(st, val) SKM_sk_push(KRB5_PRINCNAME, (st), (val)) -#define sk_KRB5_PRINCNAME_unshift(st, val) SKM_sk_unshift(KRB5_PRINCNAME, (st), (val)) -#define sk_KRB5_PRINCNAME_find(st, val) SKM_sk_find(KRB5_PRINCNAME, (st), (val)) -#define sk_KRB5_PRINCNAME_find_ex(st, val) SKM_sk_find_ex(KRB5_PRINCNAME, (st), (val)) -#define sk_KRB5_PRINCNAME_delete(st, i) SKM_sk_delete(KRB5_PRINCNAME, (st), (i)) -#define sk_KRB5_PRINCNAME_delete_ptr(st, ptr) SKM_sk_delete_ptr(KRB5_PRINCNAME, (st), (ptr)) -#define sk_KRB5_PRINCNAME_insert(st, val, i) SKM_sk_insert(KRB5_PRINCNAME, (st), (val), (i)) -#define sk_KRB5_PRINCNAME_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(KRB5_PRINCNAME, (st), (cmp)) -#define sk_KRB5_PRINCNAME_dup(st) SKM_sk_dup(KRB5_PRINCNAME, st) -#define sk_KRB5_PRINCNAME_pop_free(st, free_func) SKM_sk_pop_free(KRB5_PRINCNAME, (st), (free_func)) -#define sk_KRB5_PRINCNAME_shift(st) SKM_sk_shift(KRB5_PRINCNAME, (st)) -#define sk_KRB5_PRINCNAME_pop(st) SKM_sk_pop(KRB5_PRINCNAME, (st)) -#define sk_KRB5_PRINCNAME_sort(st) SKM_sk_sort(KRB5_PRINCNAME, (st)) -#define sk_KRB5_PRINCNAME_is_sorted(st) SKM_sk_is_sorted(KRB5_PRINCNAME, (st)) - -#define sk_KRB5_TKTBODY_new(cmp) SKM_sk_new(KRB5_TKTBODY, (cmp)) -#define sk_KRB5_TKTBODY_new_null() SKM_sk_new_null(KRB5_TKTBODY) -#define sk_KRB5_TKTBODY_free(st) SKM_sk_free(KRB5_TKTBODY, (st)) -#define sk_KRB5_TKTBODY_num(st) SKM_sk_num(KRB5_TKTBODY, (st)) -#define sk_KRB5_TKTBODY_value(st, i) SKM_sk_value(KRB5_TKTBODY, (st), (i)) -#define sk_KRB5_TKTBODY_set(st, i, val) SKM_sk_set(KRB5_TKTBODY, (st), (i), (val)) -#define sk_KRB5_TKTBODY_zero(st) SKM_sk_zero(KRB5_TKTBODY, (st)) -#define sk_KRB5_TKTBODY_push(st, val) SKM_sk_push(KRB5_TKTBODY, (st), (val)) -#define sk_KRB5_TKTBODY_unshift(st, val) SKM_sk_unshift(KRB5_TKTBODY, (st), (val)) -#define sk_KRB5_TKTBODY_find(st, val) SKM_sk_find(KRB5_TKTBODY, (st), (val)) -#define sk_KRB5_TKTBODY_find_ex(st, val) SKM_sk_find_ex(KRB5_TKTBODY, (st), (val)) -#define sk_KRB5_TKTBODY_delete(st, i) SKM_sk_delete(KRB5_TKTBODY, (st), (i)) -#define sk_KRB5_TKTBODY_delete_ptr(st, ptr) SKM_sk_delete_ptr(KRB5_TKTBODY, (st), (ptr)) -#define sk_KRB5_TKTBODY_insert(st, val, i) SKM_sk_insert(KRB5_TKTBODY, (st), (val), (i)) -#define sk_KRB5_TKTBODY_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(KRB5_TKTBODY, (st), (cmp)) -#define sk_KRB5_TKTBODY_dup(st) SKM_sk_dup(KRB5_TKTBODY, st) -#define sk_KRB5_TKTBODY_pop_free(st, free_func) SKM_sk_pop_free(KRB5_TKTBODY, (st), (free_func)) -#define sk_KRB5_TKTBODY_shift(st) SKM_sk_shift(KRB5_TKTBODY, (st)) -#define sk_KRB5_TKTBODY_pop(st) SKM_sk_pop(KRB5_TKTBODY, (st)) -#define sk_KRB5_TKTBODY_sort(st) SKM_sk_sort(KRB5_TKTBODY, (st)) -#define sk_KRB5_TKTBODY_is_sorted(st) SKM_sk_is_sorted(KRB5_TKTBODY, (st)) - -#define sk_MEM_OBJECT_DATA_new(cmp) SKM_sk_new(MEM_OBJECT_DATA, (cmp)) -#define sk_MEM_OBJECT_DATA_new_null() SKM_sk_new_null(MEM_OBJECT_DATA) -#define sk_MEM_OBJECT_DATA_free(st) SKM_sk_free(MEM_OBJECT_DATA, (st)) -#define sk_MEM_OBJECT_DATA_num(st) SKM_sk_num(MEM_OBJECT_DATA, (st)) -#define sk_MEM_OBJECT_DATA_value(st, i) SKM_sk_value(MEM_OBJECT_DATA, (st), (i)) -#define sk_MEM_OBJECT_DATA_set(st, i, val) SKM_sk_set(MEM_OBJECT_DATA, (st), (i), (val)) -#define sk_MEM_OBJECT_DATA_zero(st) SKM_sk_zero(MEM_OBJECT_DATA, (st)) -#define sk_MEM_OBJECT_DATA_push(st, val) SKM_sk_push(MEM_OBJECT_DATA, (st), (val)) -#define sk_MEM_OBJECT_DATA_unshift(st, val) SKM_sk_unshift(MEM_OBJECT_DATA, (st), (val)) -#define sk_MEM_OBJECT_DATA_find(st, val) SKM_sk_find(MEM_OBJECT_DATA, (st), (val)) -#define sk_MEM_OBJECT_DATA_find_ex(st, val) SKM_sk_find_ex(MEM_OBJECT_DATA, (st), (val)) -#define sk_MEM_OBJECT_DATA_delete(st, i) SKM_sk_delete(MEM_OBJECT_DATA, (st), (i)) -#define sk_MEM_OBJECT_DATA_delete_ptr(st, ptr) SKM_sk_delete_ptr(MEM_OBJECT_DATA, (st), (ptr)) -#define sk_MEM_OBJECT_DATA_insert(st, val, i) SKM_sk_insert(MEM_OBJECT_DATA, (st), (val), (i)) -#define sk_MEM_OBJECT_DATA_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(MEM_OBJECT_DATA, (st), (cmp)) -#define sk_MEM_OBJECT_DATA_dup(st) SKM_sk_dup(MEM_OBJECT_DATA, st) -#define sk_MEM_OBJECT_DATA_pop_free(st, free_func) SKM_sk_pop_free(MEM_OBJECT_DATA, (st), (free_func)) -#define sk_MEM_OBJECT_DATA_shift(st) SKM_sk_shift(MEM_OBJECT_DATA, (st)) -#define sk_MEM_OBJECT_DATA_pop(st) SKM_sk_pop(MEM_OBJECT_DATA, (st)) -#define sk_MEM_OBJECT_DATA_sort(st) SKM_sk_sort(MEM_OBJECT_DATA, (st)) -#define sk_MEM_OBJECT_DATA_is_sorted(st) SKM_sk_is_sorted(MEM_OBJECT_DATA, (st)) - -#define sk_MIME_HEADER_new(cmp) SKM_sk_new(MIME_HEADER, (cmp)) -#define sk_MIME_HEADER_new_null() SKM_sk_new_null(MIME_HEADER) -#define sk_MIME_HEADER_free(st) SKM_sk_free(MIME_HEADER, (st)) -#define sk_MIME_HEADER_num(st) SKM_sk_num(MIME_HEADER, (st)) -#define sk_MIME_HEADER_value(st, i) SKM_sk_value(MIME_HEADER, (st), (i)) -#define sk_MIME_HEADER_set(st, i, val) SKM_sk_set(MIME_HEADER, (st), (i), (val)) -#define sk_MIME_HEADER_zero(st) SKM_sk_zero(MIME_HEADER, (st)) -#define sk_MIME_HEADER_push(st, val) SKM_sk_push(MIME_HEADER, (st), (val)) -#define sk_MIME_HEADER_unshift(st, val) SKM_sk_unshift(MIME_HEADER, (st), (val)) -#define sk_MIME_HEADER_find(st, val) SKM_sk_find(MIME_HEADER, (st), (val)) -#define sk_MIME_HEADER_find_ex(st, val) SKM_sk_find_ex(MIME_HEADER, (st), (val)) -#define sk_MIME_HEADER_delete(st, i) SKM_sk_delete(MIME_HEADER, (st), (i)) -#define sk_MIME_HEADER_delete_ptr(st, ptr) SKM_sk_delete_ptr(MIME_HEADER, (st), (ptr)) -#define sk_MIME_HEADER_insert(st, val, i) SKM_sk_insert(MIME_HEADER, (st), (val), (i)) -#define sk_MIME_HEADER_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(MIME_HEADER, (st), (cmp)) -#define sk_MIME_HEADER_dup(st) SKM_sk_dup(MIME_HEADER, st) -#define sk_MIME_HEADER_pop_free(st, free_func) SKM_sk_pop_free(MIME_HEADER, (st), (free_func)) -#define sk_MIME_HEADER_shift(st) SKM_sk_shift(MIME_HEADER, (st)) -#define sk_MIME_HEADER_pop(st) SKM_sk_pop(MIME_HEADER, (st)) -#define sk_MIME_HEADER_sort(st) SKM_sk_sort(MIME_HEADER, (st)) -#define sk_MIME_HEADER_is_sorted(st) SKM_sk_is_sorted(MIME_HEADER, (st)) - -#define sk_MIME_PARAM_new(cmp) SKM_sk_new(MIME_PARAM, (cmp)) -#define sk_MIME_PARAM_new_null() SKM_sk_new_null(MIME_PARAM) -#define sk_MIME_PARAM_free(st) SKM_sk_free(MIME_PARAM, (st)) -#define sk_MIME_PARAM_num(st) SKM_sk_num(MIME_PARAM, (st)) -#define sk_MIME_PARAM_value(st, i) SKM_sk_value(MIME_PARAM, (st), (i)) -#define sk_MIME_PARAM_set(st, i, val) SKM_sk_set(MIME_PARAM, (st), (i), (val)) -#define sk_MIME_PARAM_zero(st) SKM_sk_zero(MIME_PARAM, (st)) -#define sk_MIME_PARAM_push(st, val) SKM_sk_push(MIME_PARAM, (st), (val)) -#define sk_MIME_PARAM_unshift(st, val) SKM_sk_unshift(MIME_PARAM, (st), (val)) -#define sk_MIME_PARAM_find(st, val) SKM_sk_find(MIME_PARAM, (st), (val)) -#define sk_MIME_PARAM_find_ex(st, val) SKM_sk_find_ex(MIME_PARAM, (st), (val)) -#define sk_MIME_PARAM_delete(st, i) SKM_sk_delete(MIME_PARAM, (st), (i)) -#define sk_MIME_PARAM_delete_ptr(st, ptr) SKM_sk_delete_ptr(MIME_PARAM, (st), (ptr)) -#define sk_MIME_PARAM_insert(st, val, i) SKM_sk_insert(MIME_PARAM, (st), (val), (i)) -#define sk_MIME_PARAM_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(MIME_PARAM, (st), (cmp)) -#define sk_MIME_PARAM_dup(st) SKM_sk_dup(MIME_PARAM, st) -#define sk_MIME_PARAM_pop_free(st, free_func) SKM_sk_pop_free(MIME_PARAM, (st), (free_func)) -#define sk_MIME_PARAM_shift(st) SKM_sk_shift(MIME_PARAM, (st)) -#define sk_MIME_PARAM_pop(st) SKM_sk_pop(MIME_PARAM, (st)) -#define sk_MIME_PARAM_sort(st) SKM_sk_sort(MIME_PARAM, (st)) -#define sk_MIME_PARAM_is_sorted(st) SKM_sk_is_sorted(MIME_PARAM, (st)) - -#define sk_NAME_FUNCS_new(cmp) SKM_sk_new(NAME_FUNCS, (cmp)) -#define sk_NAME_FUNCS_new_null() SKM_sk_new_null(NAME_FUNCS) -#define sk_NAME_FUNCS_free(st) SKM_sk_free(NAME_FUNCS, (st)) -#define sk_NAME_FUNCS_num(st) SKM_sk_num(NAME_FUNCS, (st)) -#define sk_NAME_FUNCS_value(st, i) SKM_sk_value(NAME_FUNCS, (st), (i)) -#define sk_NAME_FUNCS_set(st, i, val) SKM_sk_set(NAME_FUNCS, (st), (i), (val)) -#define sk_NAME_FUNCS_zero(st) SKM_sk_zero(NAME_FUNCS, (st)) -#define sk_NAME_FUNCS_push(st, val) SKM_sk_push(NAME_FUNCS, (st), (val)) -#define sk_NAME_FUNCS_unshift(st, val) SKM_sk_unshift(NAME_FUNCS, (st), (val)) -#define sk_NAME_FUNCS_find(st, val) SKM_sk_find(NAME_FUNCS, (st), (val)) -#define sk_NAME_FUNCS_find_ex(st, val) SKM_sk_find_ex(NAME_FUNCS, (st), (val)) -#define sk_NAME_FUNCS_delete(st, i) SKM_sk_delete(NAME_FUNCS, (st), (i)) -#define sk_NAME_FUNCS_delete_ptr(st, ptr) SKM_sk_delete_ptr(NAME_FUNCS, (st), (ptr)) -#define sk_NAME_FUNCS_insert(st, val, i) SKM_sk_insert(NAME_FUNCS, (st), (val), (i)) -#define sk_NAME_FUNCS_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(NAME_FUNCS, (st), (cmp)) -#define sk_NAME_FUNCS_dup(st) SKM_sk_dup(NAME_FUNCS, st) -#define sk_NAME_FUNCS_pop_free(st, free_func) SKM_sk_pop_free(NAME_FUNCS, (st), (free_func)) -#define sk_NAME_FUNCS_shift(st) SKM_sk_shift(NAME_FUNCS, (st)) -#define sk_NAME_FUNCS_pop(st) SKM_sk_pop(NAME_FUNCS, (st)) -#define sk_NAME_FUNCS_sort(st) SKM_sk_sort(NAME_FUNCS, (st)) -#define sk_NAME_FUNCS_is_sorted(st) SKM_sk_is_sorted(NAME_FUNCS, (st)) - -#define sk_OCSP_CERTID_new(cmp) SKM_sk_new(OCSP_CERTID, (cmp)) -#define sk_OCSP_CERTID_new_null() SKM_sk_new_null(OCSP_CERTID) -#define sk_OCSP_CERTID_free(st) SKM_sk_free(OCSP_CERTID, (st)) -#define sk_OCSP_CERTID_num(st) SKM_sk_num(OCSP_CERTID, (st)) -#define sk_OCSP_CERTID_value(st, i) SKM_sk_value(OCSP_CERTID, (st), (i)) -#define sk_OCSP_CERTID_set(st, i, val) SKM_sk_set(OCSP_CERTID, (st), (i), (val)) -#define sk_OCSP_CERTID_zero(st) SKM_sk_zero(OCSP_CERTID, (st)) -#define sk_OCSP_CERTID_push(st, val) SKM_sk_push(OCSP_CERTID, (st), (val)) -#define sk_OCSP_CERTID_unshift(st, val) SKM_sk_unshift(OCSP_CERTID, (st), (val)) -#define sk_OCSP_CERTID_find(st, val) SKM_sk_find(OCSP_CERTID, (st), (val)) -#define sk_OCSP_CERTID_find_ex(st, val) SKM_sk_find_ex(OCSP_CERTID, (st), (val)) -#define sk_OCSP_CERTID_delete(st, i) SKM_sk_delete(OCSP_CERTID, (st), (i)) -#define sk_OCSP_CERTID_delete_ptr(st, ptr) SKM_sk_delete_ptr(OCSP_CERTID, (st), (ptr)) -#define sk_OCSP_CERTID_insert(st, val, i) SKM_sk_insert(OCSP_CERTID, (st), (val), (i)) -#define sk_OCSP_CERTID_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(OCSP_CERTID, (st), (cmp)) -#define sk_OCSP_CERTID_dup(st) SKM_sk_dup(OCSP_CERTID, st) -#define sk_OCSP_CERTID_pop_free(st, free_func) SKM_sk_pop_free(OCSP_CERTID, (st), (free_func)) -#define sk_OCSP_CERTID_shift(st) SKM_sk_shift(OCSP_CERTID, (st)) -#define sk_OCSP_CERTID_pop(st) SKM_sk_pop(OCSP_CERTID, (st)) -#define sk_OCSP_CERTID_sort(st) SKM_sk_sort(OCSP_CERTID, (st)) -#define sk_OCSP_CERTID_is_sorted(st) SKM_sk_is_sorted(OCSP_CERTID, (st)) - -#define sk_OCSP_ONEREQ_new(cmp) SKM_sk_new(OCSP_ONEREQ, (cmp)) -#define sk_OCSP_ONEREQ_new_null() SKM_sk_new_null(OCSP_ONEREQ) -#define sk_OCSP_ONEREQ_free(st) SKM_sk_free(OCSP_ONEREQ, (st)) -#define sk_OCSP_ONEREQ_num(st) SKM_sk_num(OCSP_ONEREQ, (st)) -#define sk_OCSP_ONEREQ_value(st, i) SKM_sk_value(OCSP_ONEREQ, (st), (i)) -#define sk_OCSP_ONEREQ_set(st, i, val) SKM_sk_set(OCSP_ONEREQ, (st), (i), (val)) -#define sk_OCSP_ONEREQ_zero(st) SKM_sk_zero(OCSP_ONEREQ, (st)) -#define sk_OCSP_ONEREQ_push(st, val) SKM_sk_push(OCSP_ONEREQ, (st), (val)) -#define sk_OCSP_ONEREQ_unshift(st, val) SKM_sk_unshift(OCSP_ONEREQ, (st), (val)) -#define sk_OCSP_ONEREQ_find(st, val) SKM_sk_find(OCSP_ONEREQ, (st), (val)) -#define sk_OCSP_ONEREQ_find_ex(st, val) SKM_sk_find_ex(OCSP_ONEREQ, (st), (val)) -#define sk_OCSP_ONEREQ_delete(st, i) SKM_sk_delete(OCSP_ONEREQ, (st), (i)) -#define sk_OCSP_ONEREQ_delete_ptr(st, ptr) SKM_sk_delete_ptr(OCSP_ONEREQ, (st), (ptr)) -#define sk_OCSP_ONEREQ_insert(st, val, i) SKM_sk_insert(OCSP_ONEREQ, (st), (val), (i)) -#define sk_OCSP_ONEREQ_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(OCSP_ONEREQ, (st), (cmp)) -#define sk_OCSP_ONEREQ_dup(st) SKM_sk_dup(OCSP_ONEREQ, st) -#define sk_OCSP_ONEREQ_pop_free(st, free_func) SKM_sk_pop_free(OCSP_ONEREQ, (st), (free_func)) -#define sk_OCSP_ONEREQ_shift(st) SKM_sk_shift(OCSP_ONEREQ, (st)) -#define sk_OCSP_ONEREQ_pop(st) SKM_sk_pop(OCSP_ONEREQ, (st)) -#define sk_OCSP_ONEREQ_sort(st) SKM_sk_sort(OCSP_ONEREQ, (st)) -#define sk_OCSP_ONEREQ_is_sorted(st) SKM_sk_is_sorted(OCSP_ONEREQ, (st)) - -#define sk_OCSP_RESPID_new(cmp) SKM_sk_new(OCSP_RESPID, (cmp)) -#define sk_OCSP_RESPID_new_null() SKM_sk_new_null(OCSP_RESPID) -#define sk_OCSP_RESPID_free(st) SKM_sk_free(OCSP_RESPID, (st)) -#define sk_OCSP_RESPID_num(st) SKM_sk_num(OCSP_RESPID, (st)) -#define sk_OCSP_RESPID_value(st, i) SKM_sk_value(OCSP_RESPID, (st), (i)) -#define sk_OCSP_RESPID_set(st, i, val) SKM_sk_set(OCSP_RESPID, (st), (i), (val)) -#define sk_OCSP_RESPID_zero(st) SKM_sk_zero(OCSP_RESPID, (st)) -#define sk_OCSP_RESPID_push(st, val) SKM_sk_push(OCSP_RESPID, (st), (val)) -#define sk_OCSP_RESPID_unshift(st, val) SKM_sk_unshift(OCSP_RESPID, (st), (val)) -#define sk_OCSP_RESPID_find(st, val) SKM_sk_find(OCSP_RESPID, (st), (val)) -#define sk_OCSP_RESPID_find_ex(st, val) SKM_sk_find_ex(OCSP_RESPID, (st), (val)) -#define sk_OCSP_RESPID_delete(st, i) SKM_sk_delete(OCSP_RESPID, (st), (i)) -#define sk_OCSP_RESPID_delete_ptr(st, ptr) SKM_sk_delete_ptr(OCSP_RESPID, (st), (ptr)) -#define sk_OCSP_RESPID_insert(st, val, i) SKM_sk_insert(OCSP_RESPID, (st), (val), (i)) -#define sk_OCSP_RESPID_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(OCSP_RESPID, (st), (cmp)) -#define sk_OCSP_RESPID_dup(st) SKM_sk_dup(OCSP_RESPID, st) -#define sk_OCSP_RESPID_pop_free(st, free_func) SKM_sk_pop_free(OCSP_RESPID, (st), (free_func)) -#define sk_OCSP_RESPID_shift(st) SKM_sk_shift(OCSP_RESPID, (st)) -#define sk_OCSP_RESPID_pop(st) SKM_sk_pop(OCSP_RESPID, (st)) -#define sk_OCSP_RESPID_sort(st) SKM_sk_sort(OCSP_RESPID, (st)) -#define sk_OCSP_RESPID_is_sorted(st) SKM_sk_is_sorted(OCSP_RESPID, (st)) - -#define sk_OCSP_SINGLERESP_new(cmp) SKM_sk_new(OCSP_SINGLERESP, (cmp)) -#define sk_OCSP_SINGLERESP_new_null() SKM_sk_new_null(OCSP_SINGLERESP) -#define sk_OCSP_SINGLERESP_free(st) SKM_sk_free(OCSP_SINGLERESP, (st)) -#define sk_OCSP_SINGLERESP_num(st) SKM_sk_num(OCSP_SINGLERESP, (st)) -#define sk_OCSP_SINGLERESP_value(st, i) SKM_sk_value(OCSP_SINGLERESP, (st), (i)) -#define sk_OCSP_SINGLERESP_set(st, i, val) SKM_sk_set(OCSP_SINGLERESP, (st), (i), (val)) -#define sk_OCSP_SINGLERESP_zero(st) SKM_sk_zero(OCSP_SINGLERESP, (st)) -#define sk_OCSP_SINGLERESP_push(st, val) SKM_sk_push(OCSP_SINGLERESP, (st), (val)) -#define sk_OCSP_SINGLERESP_unshift(st, val) SKM_sk_unshift(OCSP_SINGLERESP, (st), (val)) -#define sk_OCSP_SINGLERESP_find(st, val) SKM_sk_find(OCSP_SINGLERESP, (st), (val)) -#define sk_OCSP_SINGLERESP_find_ex(st, val) SKM_sk_find_ex(OCSP_SINGLERESP, (st), (val)) -#define sk_OCSP_SINGLERESP_delete(st, i) SKM_sk_delete(OCSP_SINGLERESP, (st), (i)) -#define sk_OCSP_SINGLERESP_delete_ptr(st, ptr) SKM_sk_delete_ptr(OCSP_SINGLERESP, (st), (ptr)) -#define sk_OCSP_SINGLERESP_insert(st, val, i) SKM_sk_insert(OCSP_SINGLERESP, (st), (val), (i)) -#define sk_OCSP_SINGLERESP_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(OCSP_SINGLERESP, (st), (cmp)) -#define sk_OCSP_SINGLERESP_dup(st) SKM_sk_dup(OCSP_SINGLERESP, st) -#define sk_OCSP_SINGLERESP_pop_free(st, free_func) SKM_sk_pop_free(OCSP_SINGLERESP, (st), (free_func)) -#define sk_OCSP_SINGLERESP_shift(st) SKM_sk_shift(OCSP_SINGLERESP, (st)) -#define sk_OCSP_SINGLERESP_pop(st) SKM_sk_pop(OCSP_SINGLERESP, (st)) -#define sk_OCSP_SINGLERESP_sort(st) SKM_sk_sort(OCSP_SINGLERESP, (st)) -#define sk_OCSP_SINGLERESP_is_sorted(st) SKM_sk_is_sorted(OCSP_SINGLERESP, (st)) - -#define sk_PKCS12_SAFEBAG_new(cmp) SKM_sk_new(PKCS12_SAFEBAG, (cmp)) -#define sk_PKCS12_SAFEBAG_new_null() SKM_sk_new_null(PKCS12_SAFEBAG) -#define sk_PKCS12_SAFEBAG_free(st) SKM_sk_free(PKCS12_SAFEBAG, (st)) -#define sk_PKCS12_SAFEBAG_num(st) SKM_sk_num(PKCS12_SAFEBAG, (st)) -#define sk_PKCS12_SAFEBAG_value(st, i) SKM_sk_value(PKCS12_SAFEBAG, (st), (i)) -#define sk_PKCS12_SAFEBAG_set(st, i, val) SKM_sk_set(PKCS12_SAFEBAG, (st), (i), (val)) -#define sk_PKCS12_SAFEBAG_zero(st) SKM_sk_zero(PKCS12_SAFEBAG, (st)) -#define sk_PKCS12_SAFEBAG_push(st, val) SKM_sk_push(PKCS12_SAFEBAG, (st), (val)) -#define sk_PKCS12_SAFEBAG_unshift(st, val) SKM_sk_unshift(PKCS12_SAFEBAG, (st), (val)) -#define sk_PKCS12_SAFEBAG_find(st, val) SKM_sk_find(PKCS12_SAFEBAG, (st), (val)) -#define sk_PKCS12_SAFEBAG_find_ex(st, val) SKM_sk_find_ex(PKCS12_SAFEBAG, (st), (val)) -#define sk_PKCS12_SAFEBAG_delete(st, i) SKM_sk_delete(PKCS12_SAFEBAG, (st), (i)) -#define sk_PKCS12_SAFEBAG_delete_ptr(st, ptr) SKM_sk_delete_ptr(PKCS12_SAFEBAG, (st), (ptr)) -#define sk_PKCS12_SAFEBAG_insert(st, val, i) SKM_sk_insert(PKCS12_SAFEBAG, (st), (val), (i)) -#define sk_PKCS12_SAFEBAG_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(PKCS12_SAFEBAG, (st), (cmp)) -#define sk_PKCS12_SAFEBAG_dup(st) SKM_sk_dup(PKCS12_SAFEBAG, st) -#define sk_PKCS12_SAFEBAG_pop_free(st, free_func) SKM_sk_pop_free(PKCS12_SAFEBAG, (st), (free_func)) -#define sk_PKCS12_SAFEBAG_shift(st) SKM_sk_shift(PKCS12_SAFEBAG, (st)) -#define sk_PKCS12_SAFEBAG_pop(st) SKM_sk_pop(PKCS12_SAFEBAG, (st)) -#define sk_PKCS12_SAFEBAG_sort(st) SKM_sk_sort(PKCS12_SAFEBAG, (st)) -#define sk_PKCS12_SAFEBAG_is_sorted(st) SKM_sk_is_sorted(PKCS12_SAFEBAG, (st)) - -#define sk_PKCS7_new(cmp) SKM_sk_new(PKCS7, (cmp)) -#define sk_PKCS7_new_null() SKM_sk_new_null(PKCS7) -#define sk_PKCS7_free(st) SKM_sk_free(PKCS7, (st)) -#define sk_PKCS7_num(st) SKM_sk_num(PKCS7, (st)) -#define sk_PKCS7_value(st, i) SKM_sk_value(PKCS7, (st), (i)) -#define sk_PKCS7_set(st, i, val) SKM_sk_set(PKCS7, (st), (i), (val)) -#define sk_PKCS7_zero(st) SKM_sk_zero(PKCS7, (st)) -#define sk_PKCS7_push(st, val) SKM_sk_push(PKCS7, (st), (val)) -#define sk_PKCS7_unshift(st, val) SKM_sk_unshift(PKCS7, (st), (val)) -#define sk_PKCS7_find(st, val) SKM_sk_find(PKCS7, (st), (val)) -#define sk_PKCS7_find_ex(st, val) SKM_sk_find_ex(PKCS7, (st), (val)) -#define sk_PKCS7_delete(st, i) SKM_sk_delete(PKCS7, (st), (i)) -#define sk_PKCS7_delete_ptr(st, ptr) SKM_sk_delete_ptr(PKCS7, (st), (ptr)) -#define sk_PKCS7_insert(st, val, i) SKM_sk_insert(PKCS7, (st), (val), (i)) -#define sk_PKCS7_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(PKCS7, (st), (cmp)) -#define sk_PKCS7_dup(st) SKM_sk_dup(PKCS7, st) -#define sk_PKCS7_pop_free(st, free_func) SKM_sk_pop_free(PKCS7, (st), (free_func)) -#define sk_PKCS7_shift(st) SKM_sk_shift(PKCS7, (st)) -#define sk_PKCS7_pop(st) SKM_sk_pop(PKCS7, (st)) -#define sk_PKCS7_sort(st) SKM_sk_sort(PKCS7, (st)) -#define sk_PKCS7_is_sorted(st) SKM_sk_is_sorted(PKCS7, (st)) - -#define sk_PKCS7_RECIP_INFO_new(cmp) SKM_sk_new(PKCS7_RECIP_INFO, (cmp)) -#define sk_PKCS7_RECIP_INFO_new_null() SKM_sk_new_null(PKCS7_RECIP_INFO) -#define sk_PKCS7_RECIP_INFO_free(st) SKM_sk_free(PKCS7_RECIP_INFO, (st)) -#define sk_PKCS7_RECIP_INFO_num(st) SKM_sk_num(PKCS7_RECIP_INFO, (st)) -#define sk_PKCS7_RECIP_INFO_value(st, i) SKM_sk_value(PKCS7_RECIP_INFO, (st), (i)) -#define sk_PKCS7_RECIP_INFO_set(st, i, val) SKM_sk_set(PKCS7_RECIP_INFO, (st), (i), (val)) -#define sk_PKCS7_RECIP_INFO_zero(st) SKM_sk_zero(PKCS7_RECIP_INFO, (st)) -#define sk_PKCS7_RECIP_INFO_push(st, val) SKM_sk_push(PKCS7_RECIP_INFO, (st), (val)) -#define sk_PKCS7_RECIP_INFO_unshift(st, val) SKM_sk_unshift(PKCS7_RECIP_INFO, (st), (val)) -#define sk_PKCS7_RECIP_INFO_find(st, val) SKM_sk_find(PKCS7_RECIP_INFO, (st), (val)) -#define sk_PKCS7_RECIP_INFO_find_ex(st, val) SKM_sk_find_ex(PKCS7_RECIP_INFO, (st), (val)) -#define sk_PKCS7_RECIP_INFO_delete(st, i) SKM_sk_delete(PKCS7_RECIP_INFO, (st), (i)) -#define sk_PKCS7_RECIP_INFO_delete_ptr(st, ptr) SKM_sk_delete_ptr(PKCS7_RECIP_INFO, (st), (ptr)) -#define sk_PKCS7_RECIP_INFO_insert(st, val, i) SKM_sk_insert(PKCS7_RECIP_INFO, (st), (val), (i)) -#define sk_PKCS7_RECIP_INFO_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(PKCS7_RECIP_INFO, (st), (cmp)) -#define sk_PKCS7_RECIP_INFO_dup(st) SKM_sk_dup(PKCS7_RECIP_INFO, st) -#define sk_PKCS7_RECIP_INFO_pop_free(st, free_func) SKM_sk_pop_free(PKCS7_RECIP_INFO, (st), (free_func)) -#define sk_PKCS7_RECIP_INFO_shift(st) SKM_sk_shift(PKCS7_RECIP_INFO, (st)) -#define sk_PKCS7_RECIP_INFO_pop(st) SKM_sk_pop(PKCS7_RECIP_INFO, (st)) -#define sk_PKCS7_RECIP_INFO_sort(st) SKM_sk_sort(PKCS7_RECIP_INFO, (st)) -#define sk_PKCS7_RECIP_INFO_is_sorted(st) SKM_sk_is_sorted(PKCS7_RECIP_INFO, (st)) - -#define sk_PKCS7_SIGNER_INFO_new(cmp) SKM_sk_new(PKCS7_SIGNER_INFO, (cmp)) -#define sk_PKCS7_SIGNER_INFO_new_null() SKM_sk_new_null(PKCS7_SIGNER_INFO) -#define sk_PKCS7_SIGNER_INFO_free(st) SKM_sk_free(PKCS7_SIGNER_INFO, (st)) -#define sk_PKCS7_SIGNER_INFO_num(st) SKM_sk_num(PKCS7_SIGNER_INFO, (st)) -#define sk_PKCS7_SIGNER_INFO_value(st, i) SKM_sk_value(PKCS7_SIGNER_INFO, (st), (i)) -#define sk_PKCS7_SIGNER_INFO_set(st, i, val) SKM_sk_set(PKCS7_SIGNER_INFO, (st), (i), (val)) -#define sk_PKCS7_SIGNER_INFO_zero(st) SKM_sk_zero(PKCS7_SIGNER_INFO, (st)) -#define sk_PKCS7_SIGNER_INFO_push(st, val) SKM_sk_push(PKCS7_SIGNER_INFO, (st), (val)) -#define sk_PKCS7_SIGNER_INFO_unshift(st, val) SKM_sk_unshift(PKCS7_SIGNER_INFO, (st), (val)) -#define sk_PKCS7_SIGNER_INFO_find(st, val) SKM_sk_find(PKCS7_SIGNER_INFO, (st), (val)) -#define sk_PKCS7_SIGNER_INFO_find_ex(st, val) SKM_sk_find_ex(PKCS7_SIGNER_INFO, (st), (val)) -#define sk_PKCS7_SIGNER_INFO_delete(st, i) SKM_sk_delete(PKCS7_SIGNER_INFO, (st), (i)) -#define sk_PKCS7_SIGNER_INFO_delete_ptr(st, ptr) SKM_sk_delete_ptr(PKCS7_SIGNER_INFO, (st), (ptr)) -#define sk_PKCS7_SIGNER_INFO_insert(st, val, i) SKM_sk_insert(PKCS7_SIGNER_INFO, (st), (val), (i)) -#define sk_PKCS7_SIGNER_INFO_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(PKCS7_SIGNER_INFO, (st), (cmp)) -#define sk_PKCS7_SIGNER_INFO_dup(st) SKM_sk_dup(PKCS7_SIGNER_INFO, st) -#define sk_PKCS7_SIGNER_INFO_pop_free(st, free_func) SKM_sk_pop_free(PKCS7_SIGNER_INFO, (st), (free_func)) -#define sk_PKCS7_SIGNER_INFO_shift(st) SKM_sk_shift(PKCS7_SIGNER_INFO, (st)) -#define sk_PKCS7_SIGNER_INFO_pop(st) SKM_sk_pop(PKCS7_SIGNER_INFO, (st)) -#define sk_PKCS7_SIGNER_INFO_sort(st) SKM_sk_sort(PKCS7_SIGNER_INFO, (st)) -#define sk_PKCS7_SIGNER_INFO_is_sorted(st) SKM_sk_is_sorted(PKCS7_SIGNER_INFO, (st)) - -#define sk_POLICYINFO_new(cmp) SKM_sk_new(POLICYINFO, (cmp)) -#define sk_POLICYINFO_new_null() SKM_sk_new_null(POLICYINFO) -#define sk_POLICYINFO_free(st) SKM_sk_free(POLICYINFO, (st)) -#define sk_POLICYINFO_num(st) SKM_sk_num(POLICYINFO, (st)) -#define sk_POLICYINFO_value(st, i) SKM_sk_value(POLICYINFO, (st), (i)) -#define sk_POLICYINFO_set(st, i, val) SKM_sk_set(POLICYINFO, (st), (i), (val)) -#define sk_POLICYINFO_zero(st) SKM_sk_zero(POLICYINFO, (st)) -#define sk_POLICYINFO_push(st, val) SKM_sk_push(POLICYINFO, (st), (val)) -#define sk_POLICYINFO_unshift(st, val) SKM_sk_unshift(POLICYINFO, (st), (val)) -#define sk_POLICYINFO_find(st, val) SKM_sk_find(POLICYINFO, (st), (val)) -#define sk_POLICYINFO_find_ex(st, val) SKM_sk_find_ex(POLICYINFO, (st), (val)) -#define sk_POLICYINFO_delete(st, i) SKM_sk_delete(POLICYINFO, (st), (i)) -#define sk_POLICYINFO_delete_ptr(st, ptr) SKM_sk_delete_ptr(POLICYINFO, (st), (ptr)) -#define sk_POLICYINFO_insert(st, val, i) SKM_sk_insert(POLICYINFO, (st), (val), (i)) -#define sk_POLICYINFO_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(POLICYINFO, (st), (cmp)) -#define sk_POLICYINFO_dup(st) SKM_sk_dup(POLICYINFO, st) -#define sk_POLICYINFO_pop_free(st, free_func) SKM_sk_pop_free(POLICYINFO, (st), (free_func)) -#define sk_POLICYINFO_shift(st) SKM_sk_shift(POLICYINFO, (st)) -#define sk_POLICYINFO_pop(st) SKM_sk_pop(POLICYINFO, (st)) -#define sk_POLICYINFO_sort(st) SKM_sk_sort(POLICYINFO, (st)) -#define sk_POLICYINFO_is_sorted(st) SKM_sk_is_sorted(POLICYINFO, (st)) - -#define sk_POLICYQUALINFO_new(cmp) SKM_sk_new(POLICYQUALINFO, (cmp)) -#define sk_POLICYQUALINFO_new_null() SKM_sk_new_null(POLICYQUALINFO) -#define sk_POLICYQUALINFO_free(st) SKM_sk_free(POLICYQUALINFO, (st)) -#define sk_POLICYQUALINFO_num(st) SKM_sk_num(POLICYQUALINFO, (st)) -#define sk_POLICYQUALINFO_value(st, i) SKM_sk_value(POLICYQUALINFO, (st), (i)) -#define sk_POLICYQUALINFO_set(st, i, val) SKM_sk_set(POLICYQUALINFO, (st), (i), (val)) -#define sk_POLICYQUALINFO_zero(st) SKM_sk_zero(POLICYQUALINFO, (st)) -#define sk_POLICYQUALINFO_push(st, val) SKM_sk_push(POLICYQUALINFO, (st), (val)) -#define sk_POLICYQUALINFO_unshift(st, val) SKM_sk_unshift(POLICYQUALINFO, (st), (val)) -#define sk_POLICYQUALINFO_find(st, val) SKM_sk_find(POLICYQUALINFO, (st), (val)) -#define sk_POLICYQUALINFO_find_ex(st, val) SKM_sk_find_ex(POLICYQUALINFO, (st), (val)) -#define sk_POLICYQUALINFO_delete(st, i) SKM_sk_delete(POLICYQUALINFO, (st), (i)) -#define sk_POLICYQUALINFO_delete_ptr(st, ptr) SKM_sk_delete_ptr(POLICYQUALINFO, (st), (ptr)) -#define sk_POLICYQUALINFO_insert(st, val, i) SKM_sk_insert(POLICYQUALINFO, (st), (val), (i)) -#define sk_POLICYQUALINFO_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(POLICYQUALINFO, (st), (cmp)) -#define sk_POLICYQUALINFO_dup(st) SKM_sk_dup(POLICYQUALINFO, st) -#define sk_POLICYQUALINFO_pop_free(st, free_func) SKM_sk_pop_free(POLICYQUALINFO, (st), (free_func)) -#define sk_POLICYQUALINFO_shift(st) SKM_sk_shift(POLICYQUALINFO, (st)) -#define sk_POLICYQUALINFO_pop(st) SKM_sk_pop(POLICYQUALINFO, (st)) -#define sk_POLICYQUALINFO_sort(st) SKM_sk_sort(POLICYQUALINFO, (st)) -#define sk_POLICYQUALINFO_is_sorted(st) SKM_sk_is_sorted(POLICYQUALINFO, (st)) - -#define sk_POLICY_MAPPING_new(cmp) SKM_sk_new(POLICY_MAPPING, (cmp)) -#define sk_POLICY_MAPPING_new_null() SKM_sk_new_null(POLICY_MAPPING) -#define sk_POLICY_MAPPING_free(st) SKM_sk_free(POLICY_MAPPING, (st)) -#define sk_POLICY_MAPPING_num(st) SKM_sk_num(POLICY_MAPPING, (st)) -#define sk_POLICY_MAPPING_value(st, i) SKM_sk_value(POLICY_MAPPING, (st), (i)) -#define sk_POLICY_MAPPING_set(st, i, val) SKM_sk_set(POLICY_MAPPING, (st), (i), (val)) -#define sk_POLICY_MAPPING_zero(st) SKM_sk_zero(POLICY_MAPPING, (st)) -#define sk_POLICY_MAPPING_push(st, val) SKM_sk_push(POLICY_MAPPING, (st), (val)) -#define sk_POLICY_MAPPING_unshift(st, val) SKM_sk_unshift(POLICY_MAPPING, (st), (val)) -#define sk_POLICY_MAPPING_find(st, val) SKM_sk_find(POLICY_MAPPING, (st), (val)) -#define sk_POLICY_MAPPING_find_ex(st, val) SKM_sk_find_ex(POLICY_MAPPING, (st), (val)) -#define sk_POLICY_MAPPING_delete(st, i) SKM_sk_delete(POLICY_MAPPING, (st), (i)) -#define sk_POLICY_MAPPING_delete_ptr(st, ptr) SKM_sk_delete_ptr(POLICY_MAPPING, (st), (ptr)) -#define sk_POLICY_MAPPING_insert(st, val, i) SKM_sk_insert(POLICY_MAPPING, (st), (val), (i)) -#define sk_POLICY_MAPPING_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(POLICY_MAPPING, (st), (cmp)) -#define sk_POLICY_MAPPING_dup(st) SKM_sk_dup(POLICY_MAPPING, st) -#define sk_POLICY_MAPPING_pop_free(st, free_func) SKM_sk_pop_free(POLICY_MAPPING, (st), (free_func)) -#define sk_POLICY_MAPPING_shift(st) SKM_sk_shift(POLICY_MAPPING, (st)) -#define sk_POLICY_MAPPING_pop(st) SKM_sk_pop(POLICY_MAPPING, (st)) -#define sk_POLICY_MAPPING_sort(st) SKM_sk_sort(POLICY_MAPPING, (st)) -#define sk_POLICY_MAPPING_is_sorted(st) SKM_sk_is_sorted(POLICY_MAPPING, (st)) - -#define sk_SRP_gN_new(cmp) SKM_sk_new(SRP_gN, (cmp)) -#define sk_SRP_gN_new_null() SKM_sk_new_null(SRP_gN) -#define sk_SRP_gN_free(st) SKM_sk_free(SRP_gN, (st)) -#define sk_SRP_gN_num(st) SKM_sk_num(SRP_gN, (st)) -#define sk_SRP_gN_value(st, i) SKM_sk_value(SRP_gN, (st), (i)) -#define sk_SRP_gN_set(st, i, val) SKM_sk_set(SRP_gN, (st), (i), (val)) -#define sk_SRP_gN_zero(st) SKM_sk_zero(SRP_gN, (st)) -#define sk_SRP_gN_push(st, val) SKM_sk_push(SRP_gN, (st), (val)) -#define sk_SRP_gN_unshift(st, val) SKM_sk_unshift(SRP_gN, (st), (val)) -#define sk_SRP_gN_find(st, val) SKM_sk_find(SRP_gN, (st), (val)) -#define sk_SRP_gN_find_ex(st, val) SKM_sk_find_ex(SRP_gN, (st), (val)) -#define sk_SRP_gN_delete(st, i) SKM_sk_delete(SRP_gN, (st), (i)) -#define sk_SRP_gN_delete_ptr(st, ptr) SKM_sk_delete_ptr(SRP_gN, (st), (ptr)) -#define sk_SRP_gN_insert(st, val, i) SKM_sk_insert(SRP_gN, (st), (val), (i)) -#define sk_SRP_gN_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(SRP_gN, (st), (cmp)) -#define sk_SRP_gN_dup(st) SKM_sk_dup(SRP_gN, st) -#define sk_SRP_gN_pop_free(st, free_func) SKM_sk_pop_free(SRP_gN, (st), (free_func)) -#define sk_SRP_gN_shift(st) SKM_sk_shift(SRP_gN, (st)) -#define sk_SRP_gN_pop(st) SKM_sk_pop(SRP_gN, (st)) -#define sk_SRP_gN_sort(st) SKM_sk_sort(SRP_gN, (st)) -#define sk_SRP_gN_is_sorted(st) SKM_sk_is_sorted(SRP_gN, (st)) - -#define sk_SRP_gN_cache_new(cmp) SKM_sk_new(SRP_gN_cache, (cmp)) -#define sk_SRP_gN_cache_new_null() SKM_sk_new_null(SRP_gN_cache) -#define sk_SRP_gN_cache_free(st) SKM_sk_free(SRP_gN_cache, (st)) -#define sk_SRP_gN_cache_num(st) SKM_sk_num(SRP_gN_cache, (st)) -#define sk_SRP_gN_cache_value(st, i) SKM_sk_value(SRP_gN_cache, (st), (i)) -#define sk_SRP_gN_cache_set(st, i, val) SKM_sk_set(SRP_gN_cache, (st), (i), (val)) -#define sk_SRP_gN_cache_zero(st) SKM_sk_zero(SRP_gN_cache, (st)) -#define sk_SRP_gN_cache_push(st, val) SKM_sk_push(SRP_gN_cache, (st), (val)) -#define sk_SRP_gN_cache_unshift(st, val) SKM_sk_unshift(SRP_gN_cache, (st), (val)) -#define sk_SRP_gN_cache_find(st, val) SKM_sk_find(SRP_gN_cache, (st), (val)) -#define sk_SRP_gN_cache_find_ex(st, val) SKM_sk_find_ex(SRP_gN_cache, (st), (val)) -#define sk_SRP_gN_cache_delete(st, i) SKM_sk_delete(SRP_gN_cache, (st), (i)) -#define sk_SRP_gN_cache_delete_ptr(st, ptr) SKM_sk_delete_ptr(SRP_gN_cache, (st), (ptr)) -#define sk_SRP_gN_cache_insert(st, val, i) SKM_sk_insert(SRP_gN_cache, (st), (val), (i)) -#define sk_SRP_gN_cache_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(SRP_gN_cache, (st), (cmp)) -#define sk_SRP_gN_cache_dup(st) SKM_sk_dup(SRP_gN_cache, st) -#define sk_SRP_gN_cache_pop_free(st, free_func) SKM_sk_pop_free(SRP_gN_cache, (st), (free_func)) -#define sk_SRP_gN_cache_shift(st) SKM_sk_shift(SRP_gN_cache, (st)) -#define sk_SRP_gN_cache_pop(st) SKM_sk_pop(SRP_gN_cache, (st)) -#define sk_SRP_gN_cache_sort(st) SKM_sk_sort(SRP_gN_cache, (st)) -#define sk_SRP_gN_cache_is_sorted(st) SKM_sk_is_sorted(SRP_gN_cache, (st)) - -#define sk_SRP_user_pwd_new(cmp) SKM_sk_new(SRP_user_pwd, (cmp)) -#define sk_SRP_user_pwd_new_null() SKM_sk_new_null(SRP_user_pwd) -#define sk_SRP_user_pwd_free(st) SKM_sk_free(SRP_user_pwd, (st)) -#define sk_SRP_user_pwd_num(st) SKM_sk_num(SRP_user_pwd, (st)) -#define sk_SRP_user_pwd_value(st, i) SKM_sk_value(SRP_user_pwd, (st), (i)) -#define sk_SRP_user_pwd_set(st, i, val) SKM_sk_set(SRP_user_pwd, (st), (i), (val)) -#define sk_SRP_user_pwd_zero(st) SKM_sk_zero(SRP_user_pwd, (st)) -#define sk_SRP_user_pwd_push(st, val) SKM_sk_push(SRP_user_pwd, (st), (val)) -#define sk_SRP_user_pwd_unshift(st, val) SKM_sk_unshift(SRP_user_pwd, (st), (val)) -#define sk_SRP_user_pwd_find(st, val) SKM_sk_find(SRP_user_pwd, (st), (val)) -#define sk_SRP_user_pwd_find_ex(st, val) SKM_sk_find_ex(SRP_user_pwd, (st), (val)) -#define sk_SRP_user_pwd_delete(st, i) SKM_sk_delete(SRP_user_pwd, (st), (i)) -#define sk_SRP_user_pwd_delete_ptr(st, ptr) SKM_sk_delete_ptr(SRP_user_pwd, (st), (ptr)) -#define sk_SRP_user_pwd_insert(st, val, i) SKM_sk_insert(SRP_user_pwd, (st), (val), (i)) -#define sk_SRP_user_pwd_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(SRP_user_pwd, (st), (cmp)) -#define sk_SRP_user_pwd_dup(st) SKM_sk_dup(SRP_user_pwd, st) -#define sk_SRP_user_pwd_pop_free(st, free_func) SKM_sk_pop_free(SRP_user_pwd, (st), (free_func)) -#define sk_SRP_user_pwd_shift(st) SKM_sk_shift(SRP_user_pwd, (st)) -#define sk_SRP_user_pwd_pop(st) SKM_sk_pop(SRP_user_pwd, (st)) -#define sk_SRP_user_pwd_sort(st) SKM_sk_sort(SRP_user_pwd, (st)) -#define sk_SRP_user_pwd_is_sorted(st) SKM_sk_is_sorted(SRP_user_pwd, (st)) - -#define sk_SRTP_PROTECTION_PROFILE_new(cmp) SKM_sk_new(SRTP_PROTECTION_PROFILE, (cmp)) -#define sk_SRTP_PROTECTION_PROFILE_new_null() SKM_sk_new_null(SRTP_PROTECTION_PROFILE) -#define sk_SRTP_PROTECTION_PROFILE_free(st) SKM_sk_free(SRTP_PROTECTION_PROFILE, (st)) -#define sk_SRTP_PROTECTION_PROFILE_num(st) SKM_sk_num(SRTP_PROTECTION_PROFILE, (st)) -#define sk_SRTP_PROTECTION_PROFILE_value(st, i) SKM_sk_value(SRTP_PROTECTION_PROFILE, (st), (i)) -#define sk_SRTP_PROTECTION_PROFILE_set(st, i, val) SKM_sk_set(SRTP_PROTECTION_PROFILE, (st), (i), (val)) -#define sk_SRTP_PROTECTION_PROFILE_zero(st) SKM_sk_zero(SRTP_PROTECTION_PROFILE, (st)) -#define sk_SRTP_PROTECTION_PROFILE_push(st, val) SKM_sk_push(SRTP_PROTECTION_PROFILE, (st), (val)) -#define sk_SRTP_PROTECTION_PROFILE_unshift(st, val) SKM_sk_unshift(SRTP_PROTECTION_PROFILE, (st), (val)) -#define sk_SRTP_PROTECTION_PROFILE_find(st, val) SKM_sk_find(SRTP_PROTECTION_PROFILE, (st), (val)) -#define sk_SRTP_PROTECTION_PROFILE_find_ex(st, val) SKM_sk_find_ex(SRTP_PROTECTION_PROFILE, (st), (val)) -#define sk_SRTP_PROTECTION_PROFILE_delete(st, i) SKM_sk_delete(SRTP_PROTECTION_PROFILE, (st), (i)) -#define sk_SRTP_PROTECTION_PROFILE_delete_ptr(st, ptr) SKM_sk_delete_ptr(SRTP_PROTECTION_PROFILE, (st), (ptr)) -#define sk_SRTP_PROTECTION_PROFILE_insert(st, val, i) SKM_sk_insert(SRTP_PROTECTION_PROFILE, (st), (val), (i)) -#define sk_SRTP_PROTECTION_PROFILE_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(SRTP_PROTECTION_PROFILE, (st), (cmp)) -#define sk_SRTP_PROTECTION_PROFILE_dup(st) SKM_sk_dup(SRTP_PROTECTION_PROFILE, st) -#define sk_SRTP_PROTECTION_PROFILE_pop_free(st, free_func) SKM_sk_pop_free(SRTP_PROTECTION_PROFILE, (st), (free_func)) -#define sk_SRTP_PROTECTION_PROFILE_shift(st) SKM_sk_shift(SRTP_PROTECTION_PROFILE, (st)) -#define sk_SRTP_PROTECTION_PROFILE_pop(st) SKM_sk_pop(SRTP_PROTECTION_PROFILE, (st)) -#define sk_SRTP_PROTECTION_PROFILE_sort(st) SKM_sk_sort(SRTP_PROTECTION_PROFILE, (st)) -#define sk_SRTP_PROTECTION_PROFILE_is_sorted(st) SKM_sk_is_sorted(SRTP_PROTECTION_PROFILE, (st)) - -#define sk_SSL_CIPHER_new(cmp) SKM_sk_new(SSL_CIPHER, (cmp)) -#define sk_SSL_CIPHER_new_null() SKM_sk_new_null(SSL_CIPHER) -#define sk_SSL_CIPHER_free(st) SKM_sk_free(SSL_CIPHER, (st)) -#define sk_SSL_CIPHER_num(st) SKM_sk_num(SSL_CIPHER, (st)) -#define sk_SSL_CIPHER_value(st, i) SKM_sk_value(SSL_CIPHER, (st), (i)) -#define sk_SSL_CIPHER_set(st, i, val) SKM_sk_set(SSL_CIPHER, (st), (i), (val)) -#define sk_SSL_CIPHER_zero(st) SKM_sk_zero(SSL_CIPHER, (st)) -#define sk_SSL_CIPHER_push(st, val) SKM_sk_push(SSL_CIPHER, (st), (val)) -#define sk_SSL_CIPHER_unshift(st, val) SKM_sk_unshift(SSL_CIPHER, (st), (val)) -#define sk_SSL_CIPHER_find(st, val) SKM_sk_find(SSL_CIPHER, (st), (val)) -#define sk_SSL_CIPHER_find_ex(st, val) SKM_sk_find_ex(SSL_CIPHER, (st), (val)) -#define sk_SSL_CIPHER_delete(st, i) SKM_sk_delete(SSL_CIPHER, (st), (i)) -#define sk_SSL_CIPHER_delete_ptr(st, ptr) SKM_sk_delete_ptr(SSL_CIPHER, (st), (ptr)) -#define sk_SSL_CIPHER_insert(st, val, i) SKM_sk_insert(SSL_CIPHER, (st), (val), (i)) -#define sk_SSL_CIPHER_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(SSL_CIPHER, (st), (cmp)) -#define sk_SSL_CIPHER_dup(st) SKM_sk_dup(SSL_CIPHER, st) -#define sk_SSL_CIPHER_pop_free(st, free_func) SKM_sk_pop_free(SSL_CIPHER, (st), (free_func)) -#define sk_SSL_CIPHER_shift(st) SKM_sk_shift(SSL_CIPHER, (st)) -#define sk_SSL_CIPHER_pop(st) SKM_sk_pop(SSL_CIPHER, (st)) -#define sk_SSL_CIPHER_sort(st) SKM_sk_sort(SSL_CIPHER, (st)) -#define sk_SSL_CIPHER_is_sorted(st) SKM_sk_is_sorted(SSL_CIPHER, (st)) - -#define sk_SSL_COMP_new(cmp) SKM_sk_new(SSL_COMP, (cmp)) -#define sk_SSL_COMP_new_null() SKM_sk_new_null(SSL_COMP) -#define sk_SSL_COMP_free(st) SKM_sk_free(SSL_COMP, (st)) -#define sk_SSL_COMP_num(st) SKM_sk_num(SSL_COMP, (st)) -#define sk_SSL_COMP_value(st, i) SKM_sk_value(SSL_COMP, (st), (i)) -#define sk_SSL_COMP_set(st, i, val) SKM_sk_set(SSL_COMP, (st), (i), (val)) -#define sk_SSL_COMP_zero(st) SKM_sk_zero(SSL_COMP, (st)) -#define sk_SSL_COMP_push(st, val) SKM_sk_push(SSL_COMP, (st), (val)) -#define sk_SSL_COMP_unshift(st, val) SKM_sk_unshift(SSL_COMP, (st), (val)) -#define sk_SSL_COMP_find(st, val) SKM_sk_find(SSL_COMP, (st), (val)) -#define sk_SSL_COMP_find_ex(st, val) SKM_sk_find_ex(SSL_COMP, (st), (val)) -#define sk_SSL_COMP_delete(st, i) SKM_sk_delete(SSL_COMP, (st), (i)) -#define sk_SSL_COMP_delete_ptr(st, ptr) SKM_sk_delete_ptr(SSL_COMP, (st), (ptr)) -#define sk_SSL_COMP_insert(st, val, i) SKM_sk_insert(SSL_COMP, (st), (val), (i)) -#define sk_SSL_COMP_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(SSL_COMP, (st), (cmp)) -#define sk_SSL_COMP_dup(st) SKM_sk_dup(SSL_COMP, st) -#define sk_SSL_COMP_pop_free(st, free_func) SKM_sk_pop_free(SSL_COMP, (st), (free_func)) -#define sk_SSL_COMP_shift(st) SKM_sk_shift(SSL_COMP, (st)) -#define sk_SSL_COMP_pop(st) SKM_sk_pop(SSL_COMP, (st)) -#define sk_SSL_COMP_sort(st) SKM_sk_sort(SSL_COMP, (st)) -#define sk_SSL_COMP_is_sorted(st) SKM_sk_is_sorted(SSL_COMP, (st)) - -#define sk_STACK_OF_X509_NAME_ENTRY_new(cmp) SKM_sk_new(STACK_OF_X509_NAME_ENTRY, (cmp)) -#define sk_STACK_OF_X509_NAME_ENTRY_new_null() SKM_sk_new_null(STACK_OF_X509_NAME_ENTRY) -#define sk_STACK_OF_X509_NAME_ENTRY_free(st) SKM_sk_free(STACK_OF_X509_NAME_ENTRY, (st)) -#define sk_STACK_OF_X509_NAME_ENTRY_num(st) SKM_sk_num(STACK_OF_X509_NAME_ENTRY, (st)) -#define sk_STACK_OF_X509_NAME_ENTRY_value(st, i) SKM_sk_value(STACK_OF_X509_NAME_ENTRY, (st), (i)) -#define sk_STACK_OF_X509_NAME_ENTRY_set(st, i, val) SKM_sk_set(STACK_OF_X509_NAME_ENTRY, (st), (i), (val)) -#define sk_STACK_OF_X509_NAME_ENTRY_zero(st) SKM_sk_zero(STACK_OF_X509_NAME_ENTRY, (st)) -#define sk_STACK_OF_X509_NAME_ENTRY_push(st, val) SKM_sk_push(STACK_OF_X509_NAME_ENTRY, (st), (val)) -#define sk_STACK_OF_X509_NAME_ENTRY_unshift(st, val) SKM_sk_unshift(STACK_OF_X509_NAME_ENTRY, (st), (val)) -#define sk_STACK_OF_X509_NAME_ENTRY_find(st, val) SKM_sk_find(STACK_OF_X509_NAME_ENTRY, (st), (val)) -#define sk_STACK_OF_X509_NAME_ENTRY_find_ex(st, val) SKM_sk_find_ex(STACK_OF_X509_NAME_ENTRY, (st), (val)) -#define sk_STACK_OF_X509_NAME_ENTRY_delete(st, i) SKM_sk_delete(STACK_OF_X509_NAME_ENTRY, (st), (i)) -#define sk_STACK_OF_X509_NAME_ENTRY_delete_ptr(st, ptr) SKM_sk_delete_ptr(STACK_OF_X509_NAME_ENTRY, (st), (ptr)) -#define sk_STACK_OF_X509_NAME_ENTRY_insert(st, val, i) SKM_sk_insert(STACK_OF_X509_NAME_ENTRY, (st), (val), (i)) -#define sk_STACK_OF_X509_NAME_ENTRY_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(STACK_OF_X509_NAME_ENTRY, (st), (cmp)) -#define sk_STACK_OF_X509_NAME_ENTRY_dup(st) SKM_sk_dup(STACK_OF_X509_NAME_ENTRY, st) -#define sk_STACK_OF_X509_NAME_ENTRY_pop_free(st, free_func) SKM_sk_pop_free(STACK_OF_X509_NAME_ENTRY, (st), (free_func)) -#define sk_STACK_OF_X509_NAME_ENTRY_shift(st) SKM_sk_shift(STACK_OF_X509_NAME_ENTRY, (st)) -#define sk_STACK_OF_X509_NAME_ENTRY_pop(st) SKM_sk_pop(STACK_OF_X509_NAME_ENTRY, (st)) -#define sk_STACK_OF_X509_NAME_ENTRY_sort(st) SKM_sk_sort(STACK_OF_X509_NAME_ENTRY, (st)) -#define sk_STACK_OF_X509_NAME_ENTRY_is_sorted(st) SKM_sk_is_sorted(STACK_OF_X509_NAME_ENTRY, (st)) - -#define sk_STORE_ATTR_INFO_new(cmp) SKM_sk_new(STORE_ATTR_INFO, (cmp)) -#define sk_STORE_ATTR_INFO_new_null() SKM_sk_new_null(STORE_ATTR_INFO) -#define sk_STORE_ATTR_INFO_free(st) SKM_sk_free(STORE_ATTR_INFO, (st)) -#define sk_STORE_ATTR_INFO_num(st) SKM_sk_num(STORE_ATTR_INFO, (st)) -#define sk_STORE_ATTR_INFO_value(st, i) SKM_sk_value(STORE_ATTR_INFO, (st), (i)) -#define sk_STORE_ATTR_INFO_set(st, i, val) SKM_sk_set(STORE_ATTR_INFO, (st), (i), (val)) -#define sk_STORE_ATTR_INFO_zero(st) SKM_sk_zero(STORE_ATTR_INFO, (st)) -#define sk_STORE_ATTR_INFO_push(st, val) SKM_sk_push(STORE_ATTR_INFO, (st), (val)) -#define sk_STORE_ATTR_INFO_unshift(st, val) SKM_sk_unshift(STORE_ATTR_INFO, (st), (val)) -#define sk_STORE_ATTR_INFO_find(st, val) SKM_sk_find(STORE_ATTR_INFO, (st), (val)) -#define sk_STORE_ATTR_INFO_find_ex(st, val) SKM_sk_find_ex(STORE_ATTR_INFO, (st), (val)) -#define sk_STORE_ATTR_INFO_delete(st, i) SKM_sk_delete(STORE_ATTR_INFO, (st), (i)) -#define sk_STORE_ATTR_INFO_delete_ptr(st, ptr) SKM_sk_delete_ptr(STORE_ATTR_INFO, (st), (ptr)) -#define sk_STORE_ATTR_INFO_insert(st, val, i) SKM_sk_insert(STORE_ATTR_INFO, (st), (val), (i)) -#define sk_STORE_ATTR_INFO_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(STORE_ATTR_INFO, (st), (cmp)) -#define sk_STORE_ATTR_INFO_dup(st) SKM_sk_dup(STORE_ATTR_INFO, st) -#define sk_STORE_ATTR_INFO_pop_free(st, free_func) SKM_sk_pop_free(STORE_ATTR_INFO, (st), (free_func)) -#define sk_STORE_ATTR_INFO_shift(st) SKM_sk_shift(STORE_ATTR_INFO, (st)) -#define sk_STORE_ATTR_INFO_pop(st) SKM_sk_pop(STORE_ATTR_INFO, (st)) -#define sk_STORE_ATTR_INFO_sort(st) SKM_sk_sort(STORE_ATTR_INFO, (st)) -#define sk_STORE_ATTR_INFO_is_sorted(st) SKM_sk_is_sorted(STORE_ATTR_INFO, (st)) - -#define sk_STORE_OBJECT_new(cmp) SKM_sk_new(STORE_OBJECT, (cmp)) -#define sk_STORE_OBJECT_new_null() SKM_sk_new_null(STORE_OBJECT) -#define sk_STORE_OBJECT_free(st) SKM_sk_free(STORE_OBJECT, (st)) -#define sk_STORE_OBJECT_num(st) SKM_sk_num(STORE_OBJECT, (st)) -#define sk_STORE_OBJECT_value(st, i) SKM_sk_value(STORE_OBJECT, (st), (i)) -#define sk_STORE_OBJECT_set(st, i, val) SKM_sk_set(STORE_OBJECT, (st), (i), (val)) -#define sk_STORE_OBJECT_zero(st) SKM_sk_zero(STORE_OBJECT, (st)) -#define sk_STORE_OBJECT_push(st, val) SKM_sk_push(STORE_OBJECT, (st), (val)) -#define sk_STORE_OBJECT_unshift(st, val) SKM_sk_unshift(STORE_OBJECT, (st), (val)) -#define sk_STORE_OBJECT_find(st, val) SKM_sk_find(STORE_OBJECT, (st), (val)) -#define sk_STORE_OBJECT_find_ex(st, val) SKM_sk_find_ex(STORE_OBJECT, (st), (val)) -#define sk_STORE_OBJECT_delete(st, i) SKM_sk_delete(STORE_OBJECT, (st), (i)) -#define sk_STORE_OBJECT_delete_ptr(st, ptr) SKM_sk_delete_ptr(STORE_OBJECT, (st), (ptr)) -#define sk_STORE_OBJECT_insert(st, val, i) SKM_sk_insert(STORE_OBJECT, (st), (val), (i)) -#define sk_STORE_OBJECT_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(STORE_OBJECT, (st), (cmp)) -#define sk_STORE_OBJECT_dup(st) SKM_sk_dup(STORE_OBJECT, st) -#define sk_STORE_OBJECT_pop_free(st, free_func) SKM_sk_pop_free(STORE_OBJECT, (st), (free_func)) -#define sk_STORE_OBJECT_shift(st) SKM_sk_shift(STORE_OBJECT, (st)) -#define sk_STORE_OBJECT_pop(st) SKM_sk_pop(STORE_OBJECT, (st)) -#define sk_STORE_OBJECT_sort(st) SKM_sk_sort(STORE_OBJECT, (st)) -#define sk_STORE_OBJECT_is_sorted(st) SKM_sk_is_sorted(STORE_OBJECT, (st)) - -#define sk_SXNETID_new(cmp) SKM_sk_new(SXNETID, (cmp)) -#define sk_SXNETID_new_null() SKM_sk_new_null(SXNETID) -#define sk_SXNETID_free(st) SKM_sk_free(SXNETID, (st)) -#define sk_SXNETID_num(st) SKM_sk_num(SXNETID, (st)) -#define sk_SXNETID_value(st, i) SKM_sk_value(SXNETID, (st), (i)) -#define sk_SXNETID_set(st, i, val) SKM_sk_set(SXNETID, (st), (i), (val)) -#define sk_SXNETID_zero(st) SKM_sk_zero(SXNETID, (st)) -#define sk_SXNETID_push(st, val) SKM_sk_push(SXNETID, (st), (val)) -#define sk_SXNETID_unshift(st, val) SKM_sk_unshift(SXNETID, (st), (val)) -#define sk_SXNETID_find(st, val) SKM_sk_find(SXNETID, (st), (val)) -#define sk_SXNETID_find_ex(st, val) SKM_sk_find_ex(SXNETID, (st), (val)) -#define sk_SXNETID_delete(st, i) SKM_sk_delete(SXNETID, (st), (i)) -#define sk_SXNETID_delete_ptr(st, ptr) SKM_sk_delete_ptr(SXNETID, (st), (ptr)) -#define sk_SXNETID_insert(st, val, i) SKM_sk_insert(SXNETID, (st), (val), (i)) -#define sk_SXNETID_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(SXNETID, (st), (cmp)) -#define sk_SXNETID_dup(st) SKM_sk_dup(SXNETID, st) -#define sk_SXNETID_pop_free(st, free_func) SKM_sk_pop_free(SXNETID, (st), (free_func)) -#define sk_SXNETID_shift(st) SKM_sk_shift(SXNETID, (st)) -#define sk_SXNETID_pop(st) SKM_sk_pop(SXNETID, (st)) -#define sk_SXNETID_sort(st) SKM_sk_sort(SXNETID, (st)) -#define sk_SXNETID_is_sorted(st) SKM_sk_is_sorted(SXNETID, (st)) - -#define sk_UI_STRING_new(cmp) SKM_sk_new(UI_STRING, (cmp)) -#define sk_UI_STRING_new_null() SKM_sk_new_null(UI_STRING) -#define sk_UI_STRING_free(st) SKM_sk_free(UI_STRING, (st)) -#define sk_UI_STRING_num(st) SKM_sk_num(UI_STRING, (st)) -#define sk_UI_STRING_value(st, i) SKM_sk_value(UI_STRING, (st), (i)) -#define sk_UI_STRING_set(st, i, val) SKM_sk_set(UI_STRING, (st), (i), (val)) -#define sk_UI_STRING_zero(st) SKM_sk_zero(UI_STRING, (st)) -#define sk_UI_STRING_push(st, val) SKM_sk_push(UI_STRING, (st), (val)) -#define sk_UI_STRING_unshift(st, val) SKM_sk_unshift(UI_STRING, (st), (val)) -#define sk_UI_STRING_find(st, val) SKM_sk_find(UI_STRING, (st), (val)) -#define sk_UI_STRING_find_ex(st, val) SKM_sk_find_ex(UI_STRING, (st), (val)) -#define sk_UI_STRING_delete(st, i) SKM_sk_delete(UI_STRING, (st), (i)) -#define sk_UI_STRING_delete_ptr(st, ptr) SKM_sk_delete_ptr(UI_STRING, (st), (ptr)) -#define sk_UI_STRING_insert(st, val, i) SKM_sk_insert(UI_STRING, (st), (val), (i)) -#define sk_UI_STRING_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(UI_STRING, (st), (cmp)) -#define sk_UI_STRING_dup(st) SKM_sk_dup(UI_STRING, st) -#define sk_UI_STRING_pop_free(st, free_func) SKM_sk_pop_free(UI_STRING, (st), (free_func)) -#define sk_UI_STRING_shift(st) SKM_sk_shift(UI_STRING, (st)) -#define sk_UI_STRING_pop(st) SKM_sk_pop(UI_STRING, (st)) -#define sk_UI_STRING_sort(st) SKM_sk_sort(UI_STRING, (st)) -#define sk_UI_STRING_is_sorted(st) SKM_sk_is_sorted(UI_STRING, (st)) - -#define sk_X509_new(cmp) SKM_sk_new(X509, (cmp)) -#define sk_X509_new_null() SKM_sk_new_null(X509) -#define sk_X509_free(st) SKM_sk_free(X509, (st)) -#define sk_X509_num(st) SKM_sk_num(X509, (st)) -#define sk_X509_value(st, i) SKM_sk_value(X509, (st), (i)) -#define sk_X509_set(st, i, val) SKM_sk_set(X509, (st), (i), (val)) -#define sk_X509_zero(st) SKM_sk_zero(X509, (st)) -#define sk_X509_push(st, val) SKM_sk_push(X509, (st), (val)) -#define sk_X509_unshift(st, val) SKM_sk_unshift(X509, (st), (val)) -#define sk_X509_find(st, val) SKM_sk_find(X509, (st), (val)) -#define sk_X509_find_ex(st, val) SKM_sk_find_ex(X509, (st), (val)) -#define sk_X509_delete(st, i) SKM_sk_delete(X509, (st), (i)) -#define sk_X509_delete_ptr(st, ptr) SKM_sk_delete_ptr(X509, (st), (ptr)) -#define sk_X509_insert(st, val, i) SKM_sk_insert(X509, (st), (val), (i)) -#define sk_X509_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(X509, (st), (cmp)) -#define sk_X509_dup(st) SKM_sk_dup(X509, st) -#define sk_X509_pop_free(st, free_func) SKM_sk_pop_free(X509, (st), (free_func)) -#define sk_X509_shift(st) SKM_sk_shift(X509, (st)) -#define sk_X509_pop(st) SKM_sk_pop(X509, (st)) -#define sk_X509_sort(st) SKM_sk_sort(X509, (st)) -#define sk_X509_is_sorted(st) SKM_sk_is_sorted(X509, (st)) - -#define sk_X509V3_EXT_METHOD_new(cmp) SKM_sk_new(X509V3_EXT_METHOD, (cmp)) -#define sk_X509V3_EXT_METHOD_new_null() SKM_sk_new_null(X509V3_EXT_METHOD) -#define sk_X509V3_EXT_METHOD_free(st) SKM_sk_free(X509V3_EXT_METHOD, (st)) -#define sk_X509V3_EXT_METHOD_num(st) SKM_sk_num(X509V3_EXT_METHOD, (st)) -#define sk_X509V3_EXT_METHOD_value(st, i) SKM_sk_value(X509V3_EXT_METHOD, (st), (i)) -#define sk_X509V3_EXT_METHOD_set(st, i, val) SKM_sk_set(X509V3_EXT_METHOD, (st), (i), (val)) -#define sk_X509V3_EXT_METHOD_zero(st) SKM_sk_zero(X509V3_EXT_METHOD, (st)) -#define sk_X509V3_EXT_METHOD_push(st, val) SKM_sk_push(X509V3_EXT_METHOD, (st), (val)) -#define sk_X509V3_EXT_METHOD_unshift(st, val) SKM_sk_unshift(X509V3_EXT_METHOD, (st), (val)) -#define sk_X509V3_EXT_METHOD_find(st, val) SKM_sk_find(X509V3_EXT_METHOD, (st), (val)) -#define sk_X509V3_EXT_METHOD_find_ex(st, val) SKM_sk_find_ex(X509V3_EXT_METHOD, (st), (val)) -#define sk_X509V3_EXT_METHOD_delete(st, i) SKM_sk_delete(X509V3_EXT_METHOD, (st), (i)) -#define sk_X509V3_EXT_METHOD_delete_ptr(st, ptr) SKM_sk_delete_ptr(X509V3_EXT_METHOD, (st), (ptr)) -#define sk_X509V3_EXT_METHOD_insert(st, val, i) SKM_sk_insert(X509V3_EXT_METHOD, (st), (val), (i)) -#define sk_X509V3_EXT_METHOD_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(X509V3_EXT_METHOD, (st), (cmp)) -#define sk_X509V3_EXT_METHOD_dup(st) SKM_sk_dup(X509V3_EXT_METHOD, st) -#define sk_X509V3_EXT_METHOD_pop_free(st, free_func) SKM_sk_pop_free(X509V3_EXT_METHOD, (st), (free_func)) -#define sk_X509V3_EXT_METHOD_shift(st) SKM_sk_shift(X509V3_EXT_METHOD, (st)) -#define sk_X509V3_EXT_METHOD_pop(st) SKM_sk_pop(X509V3_EXT_METHOD, (st)) -#define sk_X509V3_EXT_METHOD_sort(st) SKM_sk_sort(X509V3_EXT_METHOD, (st)) -#define sk_X509V3_EXT_METHOD_is_sorted(st) SKM_sk_is_sorted(X509V3_EXT_METHOD, (st)) - -#define sk_X509_ALGOR_new(cmp) SKM_sk_new(X509_ALGOR, (cmp)) -#define sk_X509_ALGOR_new_null() SKM_sk_new_null(X509_ALGOR) -#define sk_X509_ALGOR_free(st) SKM_sk_free(X509_ALGOR, (st)) -#define sk_X509_ALGOR_num(st) SKM_sk_num(X509_ALGOR, (st)) -#define sk_X509_ALGOR_value(st, i) SKM_sk_value(X509_ALGOR, (st), (i)) -#define sk_X509_ALGOR_set(st, i, val) SKM_sk_set(X509_ALGOR, (st), (i), (val)) -#define sk_X509_ALGOR_zero(st) SKM_sk_zero(X509_ALGOR, (st)) -#define sk_X509_ALGOR_push(st, val) SKM_sk_push(X509_ALGOR, (st), (val)) -#define sk_X509_ALGOR_unshift(st, val) SKM_sk_unshift(X509_ALGOR, (st), (val)) -#define sk_X509_ALGOR_find(st, val) SKM_sk_find(X509_ALGOR, (st), (val)) -#define sk_X509_ALGOR_find_ex(st, val) SKM_sk_find_ex(X509_ALGOR, (st), (val)) -#define sk_X509_ALGOR_delete(st, i) SKM_sk_delete(X509_ALGOR, (st), (i)) -#define sk_X509_ALGOR_delete_ptr(st, ptr) SKM_sk_delete_ptr(X509_ALGOR, (st), (ptr)) -#define sk_X509_ALGOR_insert(st, val, i) SKM_sk_insert(X509_ALGOR, (st), (val), (i)) -#define sk_X509_ALGOR_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(X509_ALGOR, (st), (cmp)) -#define sk_X509_ALGOR_dup(st) SKM_sk_dup(X509_ALGOR, st) -#define sk_X509_ALGOR_pop_free(st, free_func) SKM_sk_pop_free(X509_ALGOR, (st), (free_func)) -#define sk_X509_ALGOR_shift(st) SKM_sk_shift(X509_ALGOR, (st)) -#define sk_X509_ALGOR_pop(st) SKM_sk_pop(X509_ALGOR, (st)) -#define sk_X509_ALGOR_sort(st) SKM_sk_sort(X509_ALGOR, (st)) -#define sk_X509_ALGOR_is_sorted(st) SKM_sk_is_sorted(X509_ALGOR, (st)) - -#define sk_X509_ATTRIBUTE_new(cmp) SKM_sk_new(X509_ATTRIBUTE, (cmp)) -#define sk_X509_ATTRIBUTE_new_null() SKM_sk_new_null(X509_ATTRIBUTE) -#define sk_X509_ATTRIBUTE_free(st) SKM_sk_free(X509_ATTRIBUTE, (st)) -#define sk_X509_ATTRIBUTE_num(st) SKM_sk_num(X509_ATTRIBUTE, (st)) -#define sk_X509_ATTRIBUTE_value(st, i) SKM_sk_value(X509_ATTRIBUTE, (st), (i)) -#define sk_X509_ATTRIBUTE_set(st, i, val) SKM_sk_set(X509_ATTRIBUTE, (st), (i), (val)) -#define sk_X509_ATTRIBUTE_zero(st) SKM_sk_zero(X509_ATTRIBUTE, (st)) -#define sk_X509_ATTRIBUTE_push(st, val) SKM_sk_push(X509_ATTRIBUTE, (st), (val)) -#define sk_X509_ATTRIBUTE_unshift(st, val) SKM_sk_unshift(X509_ATTRIBUTE, (st), (val)) -#define sk_X509_ATTRIBUTE_find(st, val) SKM_sk_find(X509_ATTRIBUTE, (st), (val)) -#define sk_X509_ATTRIBUTE_find_ex(st, val) SKM_sk_find_ex(X509_ATTRIBUTE, (st), (val)) -#define sk_X509_ATTRIBUTE_delete(st, i) SKM_sk_delete(X509_ATTRIBUTE, (st), (i)) -#define sk_X509_ATTRIBUTE_delete_ptr(st, ptr) SKM_sk_delete_ptr(X509_ATTRIBUTE, (st), (ptr)) -#define sk_X509_ATTRIBUTE_insert(st, val, i) SKM_sk_insert(X509_ATTRIBUTE, (st), (val), (i)) -#define sk_X509_ATTRIBUTE_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(X509_ATTRIBUTE, (st), (cmp)) -#define sk_X509_ATTRIBUTE_dup(st) SKM_sk_dup(X509_ATTRIBUTE, st) -#define sk_X509_ATTRIBUTE_pop_free(st, free_func) SKM_sk_pop_free(X509_ATTRIBUTE, (st), (free_func)) -#define sk_X509_ATTRIBUTE_shift(st) SKM_sk_shift(X509_ATTRIBUTE, (st)) -#define sk_X509_ATTRIBUTE_pop(st) SKM_sk_pop(X509_ATTRIBUTE, (st)) -#define sk_X509_ATTRIBUTE_sort(st) SKM_sk_sort(X509_ATTRIBUTE, (st)) -#define sk_X509_ATTRIBUTE_is_sorted(st) SKM_sk_is_sorted(X509_ATTRIBUTE, (st)) - -#define sk_X509_CRL_new(cmp) SKM_sk_new(X509_CRL, (cmp)) -#define sk_X509_CRL_new_null() SKM_sk_new_null(X509_CRL) -#define sk_X509_CRL_free(st) SKM_sk_free(X509_CRL, (st)) -#define sk_X509_CRL_num(st) SKM_sk_num(X509_CRL, (st)) -#define sk_X509_CRL_value(st, i) SKM_sk_value(X509_CRL, (st), (i)) -#define sk_X509_CRL_set(st, i, val) SKM_sk_set(X509_CRL, (st), (i), (val)) -#define sk_X509_CRL_zero(st) SKM_sk_zero(X509_CRL, (st)) -#define sk_X509_CRL_push(st, val) SKM_sk_push(X509_CRL, (st), (val)) -#define sk_X509_CRL_unshift(st, val) SKM_sk_unshift(X509_CRL, (st), (val)) -#define sk_X509_CRL_find(st, val) SKM_sk_find(X509_CRL, (st), (val)) -#define sk_X509_CRL_find_ex(st, val) SKM_sk_find_ex(X509_CRL, (st), (val)) -#define sk_X509_CRL_delete(st, i) SKM_sk_delete(X509_CRL, (st), (i)) -#define sk_X509_CRL_delete_ptr(st, ptr) SKM_sk_delete_ptr(X509_CRL, (st), (ptr)) -#define sk_X509_CRL_insert(st, val, i) SKM_sk_insert(X509_CRL, (st), (val), (i)) -#define sk_X509_CRL_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(X509_CRL, (st), (cmp)) -#define sk_X509_CRL_dup(st) SKM_sk_dup(X509_CRL, st) -#define sk_X509_CRL_pop_free(st, free_func) SKM_sk_pop_free(X509_CRL, (st), (free_func)) -#define sk_X509_CRL_shift(st) SKM_sk_shift(X509_CRL, (st)) -#define sk_X509_CRL_pop(st) SKM_sk_pop(X509_CRL, (st)) -#define sk_X509_CRL_sort(st) SKM_sk_sort(X509_CRL, (st)) -#define sk_X509_CRL_is_sorted(st) SKM_sk_is_sorted(X509_CRL, (st)) - -#define sk_X509_EXTENSION_new(cmp) SKM_sk_new(X509_EXTENSION, (cmp)) -#define sk_X509_EXTENSION_new_null() SKM_sk_new_null(X509_EXTENSION) -#define sk_X509_EXTENSION_free(st) SKM_sk_free(X509_EXTENSION, (st)) -#define sk_X509_EXTENSION_num(st) SKM_sk_num(X509_EXTENSION, (st)) -#define sk_X509_EXTENSION_value(st, i) SKM_sk_value(X509_EXTENSION, (st), (i)) -#define sk_X509_EXTENSION_set(st, i, val) SKM_sk_set(X509_EXTENSION, (st), (i), (val)) -#define sk_X509_EXTENSION_zero(st) SKM_sk_zero(X509_EXTENSION, (st)) -#define sk_X509_EXTENSION_push(st, val) SKM_sk_push(X509_EXTENSION, (st), (val)) -#define sk_X509_EXTENSION_unshift(st, val) SKM_sk_unshift(X509_EXTENSION, (st), (val)) -#define sk_X509_EXTENSION_find(st, val) SKM_sk_find(X509_EXTENSION, (st), (val)) -#define sk_X509_EXTENSION_find_ex(st, val) SKM_sk_find_ex(X509_EXTENSION, (st), (val)) -#define sk_X509_EXTENSION_delete(st, i) SKM_sk_delete(X509_EXTENSION, (st), (i)) -#define sk_X509_EXTENSION_delete_ptr(st, ptr) SKM_sk_delete_ptr(X509_EXTENSION, (st), (ptr)) -#define sk_X509_EXTENSION_insert(st, val, i) SKM_sk_insert(X509_EXTENSION, (st), (val), (i)) -#define sk_X509_EXTENSION_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(X509_EXTENSION, (st), (cmp)) -#define sk_X509_EXTENSION_dup(st) SKM_sk_dup(X509_EXTENSION, st) -#define sk_X509_EXTENSION_pop_free(st, free_func) SKM_sk_pop_free(X509_EXTENSION, (st), (free_func)) -#define sk_X509_EXTENSION_shift(st) SKM_sk_shift(X509_EXTENSION, (st)) -#define sk_X509_EXTENSION_pop(st) SKM_sk_pop(X509_EXTENSION, (st)) -#define sk_X509_EXTENSION_sort(st) SKM_sk_sort(X509_EXTENSION, (st)) -#define sk_X509_EXTENSION_is_sorted(st) SKM_sk_is_sorted(X509_EXTENSION, (st)) - -#define sk_X509_INFO_new(cmp) SKM_sk_new(X509_INFO, (cmp)) -#define sk_X509_INFO_new_null() SKM_sk_new_null(X509_INFO) -#define sk_X509_INFO_free(st) SKM_sk_free(X509_INFO, (st)) -#define sk_X509_INFO_num(st) SKM_sk_num(X509_INFO, (st)) -#define sk_X509_INFO_value(st, i) SKM_sk_value(X509_INFO, (st), (i)) -#define sk_X509_INFO_set(st, i, val) SKM_sk_set(X509_INFO, (st), (i), (val)) -#define sk_X509_INFO_zero(st) SKM_sk_zero(X509_INFO, (st)) -#define sk_X509_INFO_push(st, val) SKM_sk_push(X509_INFO, (st), (val)) -#define sk_X509_INFO_unshift(st, val) SKM_sk_unshift(X509_INFO, (st), (val)) -#define sk_X509_INFO_find(st, val) SKM_sk_find(X509_INFO, (st), (val)) -#define sk_X509_INFO_find_ex(st, val) SKM_sk_find_ex(X509_INFO, (st), (val)) -#define sk_X509_INFO_delete(st, i) SKM_sk_delete(X509_INFO, (st), (i)) -#define sk_X509_INFO_delete_ptr(st, ptr) SKM_sk_delete_ptr(X509_INFO, (st), (ptr)) -#define sk_X509_INFO_insert(st, val, i) SKM_sk_insert(X509_INFO, (st), (val), (i)) -#define sk_X509_INFO_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(X509_INFO, (st), (cmp)) -#define sk_X509_INFO_dup(st) SKM_sk_dup(X509_INFO, st) -#define sk_X509_INFO_pop_free(st, free_func) SKM_sk_pop_free(X509_INFO, (st), (free_func)) -#define sk_X509_INFO_shift(st) SKM_sk_shift(X509_INFO, (st)) -#define sk_X509_INFO_pop(st) SKM_sk_pop(X509_INFO, (st)) -#define sk_X509_INFO_sort(st) SKM_sk_sort(X509_INFO, (st)) -#define sk_X509_INFO_is_sorted(st) SKM_sk_is_sorted(X509_INFO, (st)) - -#define sk_X509_LOOKUP_new(cmp) SKM_sk_new(X509_LOOKUP, (cmp)) -#define sk_X509_LOOKUP_new_null() SKM_sk_new_null(X509_LOOKUP) -#define sk_X509_LOOKUP_free(st) SKM_sk_free(X509_LOOKUP, (st)) -#define sk_X509_LOOKUP_num(st) SKM_sk_num(X509_LOOKUP, (st)) -#define sk_X509_LOOKUP_value(st, i) SKM_sk_value(X509_LOOKUP, (st), (i)) -#define sk_X509_LOOKUP_set(st, i, val) SKM_sk_set(X509_LOOKUP, (st), (i), (val)) -#define sk_X509_LOOKUP_zero(st) SKM_sk_zero(X509_LOOKUP, (st)) -#define sk_X509_LOOKUP_push(st, val) SKM_sk_push(X509_LOOKUP, (st), (val)) -#define sk_X509_LOOKUP_unshift(st, val) SKM_sk_unshift(X509_LOOKUP, (st), (val)) -#define sk_X509_LOOKUP_find(st, val) SKM_sk_find(X509_LOOKUP, (st), (val)) -#define sk_X509_LOOKUP_find_ex(st, val) SKM_sk_find_ex(X509_LOOKUP, (st), (val)) -#define sk_X509_LOOKUP_delete(st, i) SKM_sk_delete(X509_LOOKUP, (st), (i)) -#define sk_X509_LOOKUP_delete_ptr(st, ptr) SKM_sk_delete_ptr(X509_LOOKUP, (st), (ptr)) -#define sk_X509_LOOKUP_insert(st, val, i) SKM_sk_insert(X509_LOOKUP, (st), (val), (i)) -#define sk_X509_LOOKUP_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(X509_LOOKUP, (st), (cmp)) -#define sk_X509_LOOKUP_dup(st) SKM_sk_dup(X509_LOOKUP, st) -#define sk_X509_LOOKUP_pop_free(st, free_func) SKM_sk_pop_free(X509_LOOKUP, (st), (free_func)) -#define sk_X509_LOOKUP_shift(st) SKM_sk_shift(X509_LOOKUP, (st)) -#define sk_X509_LOOKUP_pop(st) SKM_sk_pop(X509_LOOKUP, (st)) -#define sk_X509_LOOKUP_sort(st) SKM_sk_sort(X509_LOOKUP, (st)) -#define sk_X509_LOOKUP_is_sorted(st) SKM_sk_is_sorted(X509_LOOKUP, (st)) - -#define sk_X509_NAME_new(cmp) SKM_sk_new(X509_NAME, (cmp)) -#define sk_X509_NAME_new_null() SKM_sk_new_null(X509_NAME) -#define sk_X509_NAME_free(st) SKM_sk_free(X509_NAME, (st)) -#define sk_X509_NAME_num(st) SKM_sk_num(X509_NAME, (st)) -#define sk_X509_NAME_value(st, i) SKM_sk_value(X509_NAME, (st), (i)) -#define sk_X509_NAME_set(st, i, val) SKM_sk_set(X509_NAME, (st), (i), (val)) -#define sk_X509_NAME_zero(st) SKM_sk_zero(X509_NAME, (st)) -#define sk_X509_NAME_push(st, val) SKM_sk_push(X509_NAME, (st), (val)) -#define sk_X509_NAME_unshift(st, val) SKM_sk_unshift(X509_NAME, (st), (val)) -#define sk_X509_NAME_find(st, val) SKM_sk_find(X509_NAME, (st), (val)) -#define sk_X509_NAME_find_ex(st, val) SKM_sk_find_ex(X509_NAME, (st), (val)) -#define sk_X509_NAME_delete(st, i) SKM_sk_delete(X509_NAME, (st), (i)) -#define sk_X509_NAME_delete_ptr(st, ptr) SKM_sk_delete_ptr(X509_NAME, (st), (ptr)) -#define sk_X509_NAME_insert(st, val, i) SKM_sk_insert(X509_NAME, (st), (val), (i)) -#define sk_X509_NAME_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(X509_NAME, (st), (cmp)) -#define sk_X509_NAME_dup(st) SKM_sk_dup(X509_NAME, st) -#define sk_X509_NAME_pop_free(st, free_func) SKM_sk_pop_free(X509_NAME, (st), (free_func)) -#define sk_X509_NAME_shift(st) SKM_sk_shift(X509_NAME, (st)) -#define sk_X509_NAME_pop(st) SKM_sk_pop(X509_NAME, (st)) -#define sk_X509_NAME_sort(st) SKM_sk_sort(X509_NAME, (st)) -#define sk_X509_NAME_is_sorted(st) SKM_sk_is_sorted(X509_NAME, (st)) - -#define sk_X509_NAME_ENTRY_new(cmp) SKM_sk_new(X509_NAME_ENTRY, (cmp)) -#define sk_X509_NAME_ENTRY_new_null() SKM_sk_new_null(X509_NAME_ENTRY) -#define sk_X509_NAME_ENTRY_free(st) SKM_sk_free(X509_NAME_ENTRY, (st)) -#define sk_X509_NAME_ENTRY_num(st) SKM_sk_num(X509_NAME_ENTRY, (st)) -#define sk_X509_NAME_ENTRY_value(st, i) SKM_sk_value(X509_NAME_ENTRY, (st), (i)) -#define sk_X509_NAME_ENTRY_set(st, i, val) SKM_sk_set(X509_NAME_ENTRY, (st), (i), (val)) -#define sk_X509_NAME_ENTRY_zero(st) SKM_sk_zero(X509_NAME_ENTRY, (st)) -#define sk_X509_NAME_ENTRY_push(st, val) SKM_sk_push(X509_NAME_ENTRY, (st), (val)) -#define sk_X509_NAME_ENTRY_unshift(st, val) SKM_sk_unshift(X509_NAME_ENTRY, (st), (val)) -#define sk_X509_NAME_ENTRY_find(st, val) SKM_sk_find(X509_NAME_ENTRY, (st), (val)) -#define sk_X509_NAME_ENTRY_find_ex(st, val) SKM_sk_find_ex(X509_NAME_ENTRY, (st), (val)) -#define sk_X509_NAME_ENTRY_delete(st, i) SKM_sk_delete(X509_NAME_ENTRY, (st), (i)) -#define sk_X509_NAME_ENTRY_delete_ptr(st, ptr) SKM_sk_delete_ptr(X509_NAME_ENTRY, (st), (ptr)) -#define sk_X509_NAME_ENTRY_insert(st, val, i) SKM_sk_insert(X509_NAME_ENTRY, (st), (val), (i)) -#define sk_X509_NAME_ENTRY_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(X509_NAME_ENTRY, (st), (cmp)) -#define sk_X509_NAME_ENTRY_dup(st) SKM_sk_dup(X509_NAME_ENTRY, st) -#define sk_X509_NAME_ENTRY_pop_free(st, free_func) SKM_sk_pop_free(X509_NAME_ENTRY, (st), (free_func)) -#define sk_X509_NAME_ENTRY_shift(st) SKM_sk_shift(X509_NAME_ENTRY, (st)) -#define sk_X509_NAME_ENTRY_pop(st) SKM_sk_pop(X509_NAME_ENTRY, (st)) -#define sk_X509_NAME_ENTRY_sort(st) SKM_sk_sort(X509_NAME_ENTRY, (st)) -#define sk_X509_NAME_ENTRY_is_sorted(st) SKM_sk_is_sorted(X509_NAME_ENTRY, (st)) - -#define sk_X509_OBJECT_new(cmp) SKM_sk_new(X509_OBJECT, (cmp)) -#define sk_X509_OBJECT_new_null() SKM_sk_new_null(X509_OBJECT) -#define sk_X509_OBJECT_free(st) SKM_sk_free(X509_OBJECT, (st)) -#define sk_X509_OBJECT_num(st) SKM_sk_num(X509_OBJECT, (st)) -#define sk_X509_OBJECT_value(st, i) SKM_sk_value(X509_OBJECT, (st), (i)) -#define sk_X509_OBJECT_set(st, i, val) SKM_sk_set(X509_OBJECT, (st), (i), (val)) -#define sk_X509_OBJECT_zero(st) SKM_sk_zero(X509_OBJECT, (st)) -#define sk_X509_OBJECT_push(st, val) SKM_sk_push(X509_OBJECT, (st), (val)) -#define sk_X509_OBJECT_unshift(st, val) SKM_sk_unshift(X509_OBJECT, (st), (val)) -#define sk_X509_OBJECT_find(st, val) SKM_sk_find(X509_OBJECT, (st), (val)) -#define sk_X509_OBJECT_find_ex(st, val) SKM_sk_find_ex(X509_OBJECT, (st), (val)) -#define sk_X509_OBJECT_delete(st, i) SKM_sk_delete(X509_OBJECT, (st), (i)) -#define sk_X509_OBJECT_delete_ptr(st, ptr) SKM_sk_delete_ptr(X509_OBJECT, (st), (ptr)) -#define sk_X509_OBJECT_insert(st, val, i) SKM_sk_insert(X509_OBJECT, (st), (val), (i)) -#define sk_X509_OBJECT_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(X509_OBJECT, (st), (cmp)) -#define sk_X509_OBJECT_dup(st) SKM_sk_dup(X509_OBJECT, st) -#define sk_X509_OBJECT_pop_free(st, free_func) SKM_sk_pop_free(X509_OBJECT, (st), (free_func)) -#define sk_X509_OBJECT_shift(st) SKM_sk_shift(X509_OBJECT, (st)) -#define sk_X509_OBJECT_pop(st) SKM_sk_pop(X509_OBJECT, (st)) -#define sk_X509_OBJECT_sort(st) SKM_sk_sort(X509_OBJECT, (st)) -#define sk_X509_OBJECT_is_sorted(st) SKM_sk_is_sorted(X509_OBJECT, (st)) - -#define sk_X509_POLICY_DATA_new(cmp) SKM_sk_new(X509_POLICY_DATA, (cmp)) -#define sk_X509_POLICY_DATA_new_null() SKM_sk_new_null(X509_POLICY_DATA) -#define sk_X509_POLICY_DATA_free(st) SKM_sk_free(X509_POLICY_DATA, (st)) -#define sk_X509_POLICY_DATA_num(st) SKM_sk_num(X509_POLICY_DATA, (st)) -#define sk_X509_POLICY_DATA_value(st, i) SKM_sk_value(X509_POLICY_DATA, (st), (i)) -#define sk_X509_POLICY_DATA_set(st, i, val) SKM_sk_set(X509_POLICY_DATA, (st), (i), (val)) -#define sk_X509_POLICY_DATA_zero(st) SKM_sk_zero(X509_POLICY_DATA, (st)) -#define sk_X509_POLICY_DATA_push(st, val) SKM_sk_push(X509_POLICY_DATA, (st), (val)) -#define sk_X509_POLICY_DATA_unshift(st, val) SKM_sk_unshift(X509_POLICY_DATA, (st), (val)) -#define sk_X509_POLICY_DATA_find(st, val) SKM_sk_find(X509_POLICY_DATA, (st), (val)) -#define sk_X509_POLICY_DATA_find_ex(st, val) SKM_sk_find_ex(X509_POLICY_DATA, (st), (val)) -#define sk_X509_POLICY_DATA_delete(st, i) SKM_sk_delete(X509_POLICY_DATA, (st), (i)) -#define sk_X509_POLICY_DATA_delete_ptr(st, ptr) SKM_sk_delete_ptr(X509_POLICY_DATA, (st), (ptr)) -#define sk_X509_POLICY_DATA_insert(st, val, i) SKM_sk_insert(X509_POLICY_DATA, (st), (val), (i)) -#define sk_X509_POLICY_DATA_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(X509_POLICY_DATA, (st), (cmp)) -#define sk_X509_POLICY_DATA_dup(st) SKM_sk_dup(X509_POLICY_DATA, st) -#define sk_X509_POLICY_DATA_pop_free(st, free_func) SKM_sk_pop_free(X509_POLICY_DATA, (st), (free_func)) -#define sk_X509_POLICY_DATA_shift(st) SKM_sk_shift(X509_POLICY_DATA, (st)) -#define sk_X509_POLICY_DATA_pop(st) SKM_sk_pop(X509_POLICY_DATA, (st)) -#define sk_X509_POLICY_DATA_sort(st) SKM_sk_sort(X509_POLICY_DATA, (st)) -#define sk_X509_POLICY_DATA_is_sorted(st) SKM_sk_is_sorted(X509_POLICY_DATA, (st)) - -#define sk_X509_POLICY_NODE_new(cmp) SKM_sk_new(X509_POLICY_NODE, (cmp)) -#define sk_X509_POLICY_NODE_new_null() SKM_sk_new_null(X509_POLICY_NODE) -#define sk_X509_POLICY_NODE_free(st) SKM_sk_free(X509_POLICY_NODE, (st)) -#define sk_X509_POLICY_NODE_num(st) SKM_sk_num(X509_POLICY_NODE, (st)) -#define sk_X509_POLICY_NODE_value(st, i) SKM_sk_value(X509_POLICY_NODE, (st), (i)) -#define sk_X509_POLICY_NODE_set(st, i, val) SKM_sk_set(X509_POLICY_NODE, (st), (i), (val)) -#define sk_X509_POLICY_NODE_zero(st) SKM_sk_zero(X509_POLICY_NODE, (st)) -#define sk_X509_POLICY_NODE_push(st, val) SKM_sk_push(X509_POLICY_NODE, (st), (val)) -#define sk_X509_POLICY_NODE_unshift(st, val) SKM_sk_unshift(X509_POLICY_NODE, (st), (val)) -#define sk_X509_POLICY_NODE_find(st, val) SKM_sk_find(X509_POLICY_NODE, (st), (val)) -#define sk_X509_POLICY_NODE_find_ex(st, val) SKM_sk_find_ex(X509_POLICY_NODE, (st), (val)) -#define sk_X509_POLICY_NODE_delete(st, i) SKM_sk_delete(X509_POLICY_NODE, (st), (i)) -#define sk_X509_POLICY_NODE_delete_ptr(st, ptr) SKM_sk_delete_ptr(X509_POLICY_NODE, (st), (ptr)) -#define sk_X509_POLICY_NODE_insert(st, val, i) SKM_sk_insert(X509_POLICY_NODE, (st), (val), (i)) -#define sk_X509_POLICY_NODE_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(X509_POLICY_NODE, (st), (cmp)) -#define sk_X509_POLICY_NODE_dup(st) SKM_sk_dup(X509_POLICY_NODE, st) -#define sk_X509_POLICY_NODE_pop_free(st, free_func) SKM_sk_pop_free(X509_POLICY_NODE, (st), (free_func)) -#define sk_X509_POLICY_NODE_shift(st) SKM_sk_shift(X509_POLICY_NODE, (st)) -#define sk_X509_POLICY_NODE_pop(st) SKM_sk_pop(X509_POLICY_NODE, (st)) -#define sk_X509_POLICY_NODE_sort(st) SKM_sk_sort(X509_POLICY_NODE, (st)) -#define sk_X509_POLICY_NODE_is_sorted(st) SKM_sk_is_sorted(X509_POLICY_NODE, (st)) - -#define sk_X509_PURPOSE_new(cmp) SKM_sk_new(X509_PURPOSE, (cmp)) -#define sk_X509_PURPOSE_new_null() SKM_sk_new_null(X509_PURPOSE) -#define sk_X509_PURPOSE_free(st) SKM_sk_free(X509_PURPOSE, (st)) -#define sk_X509_PURPOSE_num(st) SKM_sk_num(X509_PURPOSE, (st)) -#define sk_X509_PURPOSE_value(st, i) SKM_sk_value(X509_PURPOSE, (st), (i)) -#define sk_X509_PURPOSE_set(st, i, val) SKM_sk_set(X509_PURPOSE, (st), (i), (val)) -#define sk_X509_PURPOSE_zero(st) SKM_sk_zero(X509_PURPOSE, (st)) -#define sk_X509_PURPOSE_push(st, val) SKM_sk_push(X509_PURPOSE, (st), (val)) -#define sk_X509_PURPOSE_unshift(st, val) SKM_sk_unshift(X509_PURPOSE, (st), (val)) -#define sk_X509_PURPOSE_find(st, val) SKM_sk_find(X509_PURPOSE, (st), (val)) -#define sk_X509_PURPOSE_find_ex(st, val) SKM_sk_find_ex(X509_PURPOSE, (st), (val)) -#define sk_X509_PURPOSE_delete(st, i) SKM_sk_delete(X509_PURPOSE, (st), (i)) -#define sk_X509_PURPOSE_delete_ptr(st, ptr) SKM_sk_delete_ptr(X509_PURPOSE, (st), (ptr)) -#define sk_X509_PURPOSE_insert(st, val, i) SKM_sk_insert(X509_PURPOSE, (st), (val), (i)) -#define sk_X509_PURPOSE_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(X509_PURPOSE, (st), (cmp)) -#define sk_X509_PURPOSE_dup(st) SKM_sk_dup(X509_PURPOSE, st) -#define sk_X509_PURPOSE_pop_free(st, free_func) SKM_sk_pop_free(X509_PURPOSE, (st), (free_func)) -#define sk_X509_PURPOSE_shift(st) SKM_sk_shift(X509_PURPOSE, (st)) -#define sk_X509_PURPOSE_pop(st) SKM_sk_pop(X509_PURPOSE, (st)) -#define sk_X509_PURPOSE_sort(st) SKM_sk_sort(X509_PURPOSE, (st)) -#define sk_X509_PURPOSE_is_sorted(st) SKM_sk_is_sorted(X509_PURPOSE, (st)) - -#define sk_X509_REVOKED_new(cmp) SKM_sk_new(X509_REVOKED, (cmp)) -#define sk_X509_REVOKED_new_null() SKM_sk_new_null(X509_REVOKED) -#define sk_X509_REVOKED_free(st) SKM_sk_free(X509_REVOKED, (st)) -#define sk_X509_REVOKED_num(st) SKM_sk_num(X509_REVOKED, (st)) -#define sk_X509_REVOKED_value(st, i) SKM_sk_value(X509_REVOKED, (st), (i)) -#define sk_X509_REVOKED_set(st, i, val) SKM_sk_set(X509_REVOKED, (st), (i), (val)) -#define sk_X509_REVOKED_zero(st) SKM_sk_zero(X509_REVOKED, (st)) -#define sk_X509_REVOKED_push(st, val) SKM_sk_push(X509_REVOKED, (st), (val)) -#define sk_X509_REVOKED_unshift(st, val) SKM_sk_unshift(X509_REVOKED, (st), (val)) -#define sk_X509_REVOKED_find(st, val) SKM_sk_find(X509_REVOKED, (st), (val)) -#define sk_X509_REVOKED_find_ex(st, val) SKM_sk_find_ex(X509_REVOKED, (st), (val)) -#define sk_X509_REVOKED_delete(st, i) SKM_sk_delete(X509_REVOKED, (st), (i)) -#define sk_X509_REVOKED_delete_ptr(st, ptr) SKM_sk_delete_ptr(X509_REVOKED, (st), (ptr)) -#define sk_X509_REVOKED_insert(st, val, i) SKM_sk_insert(X509_REVOKED, (st), (val), (i)) -#define sk_X509_REVOKED_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(X509_REVOKED, (st), (cmp)) -#define sk_X509_REVOKED_dup(st) SKM_sk_dup(X509_REVOKED, st) -#define sk_X509_REVOKED_pop_free(st, free_func) SKM_sk_pop_free(X509_REVOKED, (st), (free_func)) -#define sk_X509_REVOKED_shift(st) SKM_sk_shift(X509_REVOKED, (st)) -#define sk_X509_REVOKED_pop(st) SKM_sk_pop(X509_REVOKED, (st)) -#define sk_X509_REVOKED_sort(st) SKM_sk_sort(X509_REVOKED, (st)) -#define sk_X509_REVOKED_is_sorted(st) SKM_sk_is_sorted(X509_REVOKED, (st)) - -#define sk_X509_TRUST_new(cmp) SKM_sk_new(X509_TRUST, (cmp)) -#define sk_X509_TRUST_new_null() SKM_sk_new_null(X509_TRUST) -#define sk_X509_TRUST_free(st) SKM_sk_free(X509_TRUST, (st)) -#define sk_X509_TRUST_num(st) SKM_sk_num(X509_TRUST, (st)) -#define sk_X509_TRUST_value(st, i) SKM_sk_value(X509_TRUST, (st), (i)) -#define sk_X509_TRUST_set(st, i, val) SKM_sk_set(X509_TRUST, (st), (i), (val)) -#define sk_X509_TRUST_zero(st) SKM_sk_zero(X509_TRUST, (st)) -#define sk_X509_TRUST_push(st, val) SKM_sk_push(X509_TRUST, (st), (val)) -#define sk_X509_TRUST_unshift(st, val) SKM_sk_unshift(X509_TRUST, (st), (val)) -#define sk_X509_TRUST_find(st, val) SKM_sk_find(X509_TRUST, (st), (val)) -#define sk_X509_TRUST_find_ex(st, val) SKM_sk_find_ex(X509_TRUST, (st), (val)) -#define sk_X509_TRUST_delete(st, i) SKM_sk_delete(X509_TRUST, (st), (i)) -#define sk_X509_TRUST_delete_ptr(st, ptr) SKM_sk_delete_ptr(X509_TRUST, (st), (ptr)) -#define sk_X509_TRUST_insert(st, val, i) SKM_sk_insert(X509_TRUST, (st), (val), (i)) -#define sk_X509_TRUST_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(X509_TRUST, (st), (cmp)) -#define sk_X509_TRUST_dup(st) SKM_sk_dup(X509_TRUST, st) -#define sk_X509_TRUST_pop_free(st, free_func) SKM_sk_pop_free(X509_TRUST, (st), (free_func)) -#define sk_X509_TRUST_shift(st) SKM_sk_shift(X509_TRUST, (st)) -#define sk_X509_TRUST_pop(st) SKM_sk_pop(X509_TRUST, (st)) -#define sk_X509_TRUST_sort(st) SKM_sk_sort(X509_TRUST, (st)) -#define sk_X509_TRUST_is_sorted(st) SKM_sk_is_sorted(X509_TRUST, (st)) - -#define sk_X509_VERIFY_PARAM_new(cmp) SKM_sk_new(X509_VERIFY_PARAM, (cmp)) -#define sk_X509_VERIFY_PARAM_new_null() SKM_sk_new_null(X509_VERIFY_PARAM) -#define sk_X509_VERIFY_PARAM_free(st) SKM_sk_free(X509_VERIFY_PARAM, (st)) -#define sk_X509_VERIFY_PARAM_num(st) SKM_sk_num(X509_VERIFY_PARAM, (st)) -#define sk_X509_VERIFY_PARAM_value(st, i) SKM_sk_value(X509_VERIFY_PARAM, (st), (i)) -#define sk_X509_VERIFY_PARAM_set(st, i, val) SKM_sk_set(X509_VERIFY_PARAM, (st), (i), (val)) -#define sk_X509_VERIFY_PARAM_zero(st) SKM_sk_zero(X509_VERIFY_PARAM, (st)) -#define sk_X509_VERIFY_PARAM_push(st, val) SKM_sk_push(X509_VERIFY_PARAM, (st), (val)) -#define sk_X509_VERIFY_PARAM_unshift(st, val) SKM_sk_unshift(X509_VERIFY_PARAM, (st), (val)) -#define sk_X509_VERIFY_PARAM_find(st, val) SKM_sk_find(X509_VERIFY_PARAM, (st), (val)) -#define sk_X509_VERIFY_PARAM_find_ex(st, val) SKM_sk_find_ex(X509_VERIFY_PARAM, (st), (val)) -#define sk_X509_VERIFY_PARAM_delete(st, i) SKM_sk_delete(X509_VERIFY_PARAM, (st), (i)) -#define sk_X509_VERIFY_PARAM_delete_ptr(st, ptr) SKM_sk_delete_ptr(X509_VERIFY_PARAM, (st), (ptr)) -#define sk_X509_VERIFY_PARAM_insert(st, val, i) SKM_sk_insert(X509_VERIFY_PARAM, (st), (val), (i)) -#define sk_X509_VERIFY_PARAM_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(X509_VERIFY_PARAM, (st), (cmp)) -#define sk_X509_VERIFY_PARAM_dup(st) SKM_sk_dup(X509_VERIFY_PARAM, st) -#define sk_X509_VERIFY_PARAM_pop_free(st, free_func) SKM_sk_pop_free(X509_VERIFY_PARAM, (st), (free_func)) -#define sk_X509_VERIFY_PARAM_shift(st) SKM_sk_shift(X509_VERIFY_PARAM, (st)) -#define sk_X509_VERIFY_PARAM_pop(st) SKM_sk_pop(X509_VERIFY_PARAM, (st)) -#define sk_X509_VERIFY_PARAM_sort(st) SKM_sk_sort(X509_VERIFY_PARAM, (st)) -#define sk_X509_VERIFY_PARAM_is_sorted(st) SKM_sk_is_sorted(X509_VERIFY_PARAM, (st)) - -#define sk_nid_triple_new(cmp) SKM_sk_new(nid_triple, (cmp)) -#define sk_nid_triple_new_null() SKM_sk_new_null(nid_triple) -#define sk_nid_triple_free(st) SKM_sk_free(nid_triple, (st)) -#define sk_nid_triple_num(st) SKM_sk_num(nid_triple, (st)) -#define sk_nid_triple_value(st, i) SKM_sk_value(nid_triple, (st), (i)) -#define sk_nid_triple_set(st, i, val) SKM_sk_set(nid_triple, (st), (i), (val)) -#define sk_nid_triple_zero(st) SKM_sk_zero(nid_triple, (st)) -#define sk_nid_triple_push(st, val) SKM_sk_push(nid_triple, (st), (val)) -#define sk_nid_triple_unshift(st, val) SKM_sk_unshift(nid_triple, (st), (val)) -#define sk_nid_triple_find(st, val) SKM_sk_find(nid_triple, (st), (val)) -#define sk_nid_triple_find_ex(st, val) SKM_sk_find_ex(nid_triple, (st), (val)) -#define sk_nid_triple_delete(st, i) SKM_sk_delete(nid_triple, (st), (i)) -#define sk_nid_triple_delete_ptr(st, ptr) SKM_sk_delete_ptr(nid_triple, (st), (ptr)) -#define sk_nid_triple_insert(st, val, i) SKM_sk_insert(nid_triple, (st), (val), (i)) -#define sk_nid_triple_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(nid_triple, (st), (cmp)) -#define sk_nid_triple_dup(st) SKM_sk_dup(nid_triple, st) -#define sk_nid_triple_pop_free(st, free_func) SKM_sk_pop_free(nid_triple, (st), (free_func)) -#define sk_nid_triple_shift(st) SKM_sk_shift(nid_triple, (st)) -#define sk_nid_triple_pop(st) SKM_sk_pop(nid_triple, (st)) -#define sk_nid_triple_sort(st) SKM_sk_sort(nid_triple, (st)) -#define sk_nid_triple_is_sorted(st) SKM_sk_is_sorted(nid_triple, (st)) - -#define sk_void_new(cmp) SKM_sk_new(void, (cmp)) -#define sk_void_new_null() SKM_sk_new_null(void) -#define sk_void_free(st) SKM_sk_free(void, (st)) -#define sk_void_num(st) SKM_sk_num(void, (st)) -#define sk_void_value(st, i) SKM_sk_value(void, (st), (i)) -#define sk_void_set(st, i, val) SKM_sk_set(void, (st), (i), (val)) -#define sk_void_zero(st) SKM_sk_zero(void, (st)) -#define sk_void_push(st, val) SKM_sk_push(void, (st), (val)) -#define sk_void_unshift(st, val) SKM_sk_unshift(void, (st), (val)) -#define sk_void_find(st, val) SKM_sk_find(void, (st), (val)) -#define sk_void_find_ex(st, val) SKM_sk_find_ex(void, (st), (val)) -#define sk_void_delete(st, i) SKM_sk_delete(void, (st), (i)) -#define sk_void_delete_ptr(st, ptr) SKM_sk_delete_ptr(void, (st), (ptr)) -#define sk_void_insert(st, val, i) SKM_sk_insert(void, (st), (val), (i)) -#define sk_void_set_cmp_func(st, cmp) SKM_sk_set_cmp_func(void, (st), (cmp)) -#define sk_void_dup(st) SKM_sk_dup(void, st) -#define sk_void_pop_free(st, free_func) SKM_sk_pop_free(void, (st), (free_func)) -#define sk_void_shift(st) SKM_sk_shift(void, (st)) -#define sk_void_pop(st) SKM_sk_pop(void, (st)) -#define sk_void_sort(st) SKM_sk_sort(void, (st)) -#define sk_void_is_sorted(st) SKM_sk_is_sorted(void, (st)) - -#define sk_OPENSSL_STRING_new(cmp) ((STACK_OF(OPENSSL_STRING) *)sk_new(CHECKED_SK_CMP_FUNC(char, cmp))) -#define sk_OPENSSL_STRING_new_null() ((STACK_OF(OPENSSL_STRING) *)sk_new_null()) -#define sk_OPENSSL_STRING_push(st, val) sk_push(CHECKED_STACK_OF(OPENSSL_STRING, st), CHECKED_PTR_OF(char, val)) -#define sk_OPENSSL_STRING_find(st, val) sk_find(CHECKED_STACK_OF(OPENSSL_STRING, st), CHECKED_PTR_OF(char, val)) -#define sk_OPENSSL_STRING_value(st, i) ((OPENSSL_STRING)sk_value(CHECKED_STACK_OF(OPENSSL_STRING, st), i)) -#define sk_OPENSSL_STRING_num(st) SKM_sk_num(OPENSSL_STRING, st) -#define sk_OPENSSL_STRING_pop_free(st, free_func) sk_pop_free(CHECKED_STACK_OF(OPENSSL_STRING, st), CHECKED_SK_FREE_FUNC2(OPENSSL_STRING, free_func)) -#define sk_OPENSSL_STRING_insert(st, val, i) sk_insert(CHECKED_STACK_OF(OPENSSL_STRING, st), CHECKED_PTR_OF(char, val), i) -#define sk_OPENSSL_STRING_free(st) SKM_sk_free(OPENSSL_STRING, st) -#define sk_OPENSSL_STRING_set(st, i, val) sk_set(CHECKED_STACK_OF(OPENSSL_STRING, st), i, CHECKED_PTR_OF(char, val)) -#define sk_OPENSSL_STRING_zero(st) SKM_sk_zero(OPENSSL_STRING, (st)) -#define sk_OPENSSL_STRING_unshift(st, val) sk_unshift(CHECKED_STACK_OF(OPENSSL_STRING, st), CHECKED_PTR_OF(char, val)) -#define sk_OPENSSL_STRING_find_ex(st, val) sk_find_ex((_STACK *)CHECKED_CONST_PTR_OF(STACK_OF(OPENSSL_STRING), st), CHECKED_CONST_PTR_OF(char, val)) -#define sk_OPENSSL_STRING_delete(st, i) SKM_sk_delete(OPENSSL_STRING, (st), (i)) -#define sk_OPENSSL_STRING_delete_ptr(st, ptr) (OPENSSL_STRING *)sk_delete_ptr(CHECKED_STACK_OF(OPENSSL_STRING, st), CHECKED_PTR_OF(char, ptr)) -#define sk_OPENSSL_STRING_set_cmp_func(st, cmp) \ - ((int (*)(const char * const *,const char * const *)) \ - sk_set_cmp_func(CHECKED_STACK_OF(OPENSSL_STRING, st), CHECKED_SK_CMP_FUNC(char, cmp))) -#define sk_OPENSSL_STRING_dup(st) SKM_sk_dup(OPENSSL_STRING, st) -#define sk_OPENSSL_STRING_shift(st) SKM_sk_shift(OPENSSL_STRING, (st)) -#define sk_OPENSSL_STRING_pop(st) (char *)sk_pop(CHECKED_STACK_OF(OPENSSL_STRING, st)) -#define sk_OPENSSL_STRING_sort(st) SKM_sk_sort(OPENSSL_STRING, (st)) -#define sk_OPENSSL_STRING_is_sorted(st) SKM_sk_is_sorted(OPENSSL_STRING, (st)) - - -#define sk_OPENSSL_BLOCK_new(cmp) ((STACK_OF(OPENSSL_BLOCK) *)sk_new(CHECKED_SK_CMP_FUNC(void, cmp))) -#define sk_OPENSSL_BLOCK_new_null() ((STACK_OF(OPENSSL_BLOCK) *)sk_new_null()) -#define sk_OPENSSL_BLOCK_push(st, val) sk_push(CHECKED_STACK_OF(OPENSSL_BLOCK, st), CHECKED_PTR_OF(void, val)) -#define sk_OPENSSL_BLOCK_find(st, val) sk_find(CHECKED_STACK_OF(OPENSSL_BLOCK, st), CHECKED_PTR_OF(void, val)) -#define sk_OPENSSL_BLOCK_value(st, i) ((OPENSSL_BLOCK)sk_value(CHECKED_STACK_OF(OPENSSL_BLOCK, st), i)) -#define sk_OPENSSL_BLOCK_num(st) SKM_sk_num(OPENSSL_BLOCK, st) -#define sk_OPENSSL_BLOCK_pop_free(st, free_func) sk_pop_free(CHECKED_STACK_OF(OPENSSL_BLOCK, st), CHECKED_SK_FREE_FUNC2(OPENSSL_BLOCK, free_func)) -#define sk_OPENSSL_BLOCK_insert(st, val, i) sk_insert(CHECKED_STACK_OF(OPENSSL_BLOCK, st), CHECKED_PTR_OF(void, val), i) -#define sk_OPENSSL_BLOCK_free(st) SKM_sk_free(OPENSSL_BLOCK, st) -#define sk_OPENSSL_BLOCK_set(st, i, val) sk_set(CHECKED_STACK_OF(OPENSSL_BLOCK, st), i, CHECKED_PTR_OF(void, val)) -#define sk_OPENSSL_BLOCK_zero(st) SKM_sk_zero(OPENSSL_BLOCK, (st)) -#define sk_OPENSSL_BLOCK_unshift(st, val) sk_unshift(CHECKED_STACK_OF(OPENSSL_BLOCK, st), CHECKED_PTR_OF(void, val)) -#define sk_OPENSSL_BLOCK_find_ex(st, val) sk_find_ex((_STACK *)CHECKED_CONST_PTR_OF(STACK_OF(OPENSSL_BLOCK), st), CHECKED_CONST_PTR_OF(void, val)) -#define sk_OPENSSL_BLOCK_delete(st, i) SKM_sk_delete(OPENSSL_BLOCK, (st), (i)) -#define sk_OPENSSL_BLOCK_delete_ptr(st, ptr) (OPENSSL_BLOCK *)sk_delete_ptr(CHECKED_STACK_OF(OPENSSL_BLOCK, st), CHECKED_PTR_OF(void, ptr)) -#define sk_OPENSSL_BLOCK_set_cmp_func(st, cmp) \ - ((int (*)(const void * const *,const void * const *)) \ - sk_set_cmp_func(CHECKED_STACK_OF(OPENSSL_BLOCK, st), CHECKED_SK_CMP_FUNC(void, cmp))) -#define sk_OPENSSL_BLOCK_dup(st) SKM_sk_dup(OPENSSL_BLOCK, st) -#define sk_OPENSSL_BLOCK_shift(st) SKM_sk_shift(OPENSSL_BLOCK, (st)) -#define sk_OPENSSL_BLOCK_pop(st) (void *)sk_pop(CHECKED_STACK_OF(OPENSSL_BLOCK, st)) -#define sk_OPENSSL_BLOCK_sort(st) SKM_sk_sort(OPENSSL_BLOCK, (st)) -#define sk_OPENSSL_BLOCK_is_sorted(st) SKM_sk_is_sorted(OPENSSL_BLOCK, (st)) - - -#define sk_OPENSSL_PSTRING_new(cmp) ((STACK_OF(OPENSSL_PSTRING) *)sk_new(CHECKED_SK_CMP_FUNC(OPENSSL_STRING, cmp))) -#define sk_OPENSSL_PSTRING_new_null() ((STACK_OF(OPENSSL_PSTRING) *)sk_new_null()) -#define sk_OPENSSL_PSTRING_push(st, val) sk_push(CHECKED_STACK_OF(OPENSSL_PSTRING, st), CHECKED_PTR_OF(OPENSSL_STRING, val)) -#define sk_OPENSSL_PSTRING_find(st, val) sk_find(CHECKED_STACK_OF(OPENSSL_PSTRING, st), CHECKED_PTR_OF(OPENSSL_STRING, val)) -#define sk_OPENSSL_PSTRING_value(st, i) ((OPENSSL_PSTRING)sk_value(CHECKED_STACK_OF(OPENSSL_PSTRING, st), i)) -#define sk_OPENSSL_PSTRING_num(st) SKM_sk_num(OPENSSL_PSTRING, st) -#define sk_OPENSSL_PSTRING_pop_free(st, free_func) sk_pop_free(CHECKED_STACK_OF(OPENSSL_PSTRING, st), CHECKED_SK_FREE_FUNC2(OPENSSL_PSTRING, free_func)) -#define sk_OPENSSL_PSTRING_insert(st, val, i) sk_insert(CHECKED_STACK_OF(OPENSSL_PSTRING, st), CHECKED_PTR_OF(OPENSSL_STRING, val), i) -#define sk_OPENSSL_PSTRING_free(st) SKM_sk_free(OPENSSL_PSTRING, st) -#define sk_OPENSSL_PSTRING_set(st, i, val) sk_set(CHECKED_STACK_OF(OPENSSL_PSTRING, st), i, CHECKED_PTR_OF(OPENSSL_STRING, val)) -#define sk_OPENSSL_PSTRING_zero(st) SKM_sk_zero(OPENSSL_PSTRING, (st)) -#define sk_OPENSSL_PSTRING_unshift(st, val) sk_unshift(CHECKED_STACK_OF(OPENSSL_PSTRING, st), CHECKED_PTR_OF(OPENSSL_STRING, val)) -#define sk_OPENSSL_PSTRING_find_ex(st, val) sk_find_ex((_STACK *)CHECKED_CONST_PTR_OF(STACK_OF(OPENSSL_PSTRING), st), CHECKED_CONST_PTR_OF(OPENSSL_STRING, val)) -#define sk_OPENSSL_PSTRING_delete(st, i) SKM_sk_delete(OPENSSL_PSTRING, (st), (i)) -#define sk_OPENSSL_PSTRING_delete_ptr(st, ptr) (OPENSSL_PSTRING *)sk_delete_ptr(CHECKED_STACK_OF(OPENSSL_PSTRING, st), CHECKED_PTR_OF(OPENSSL_STRING, ptr)) -#define sk_OPENSSL_PSTRING_set_cmp_func(st, cmp) \ - ((int (*)(const OPENSSL_STRING * const *,const OPENSSL_STRING * const *)) \ - sk_set_cmp_func(CHECKED_STACK_OF(OPENSSL_PSTRING, st), CHECKED_SK_CMP_FUNC(OPENSSL_STRING, cmp))) -#define sk_OPENSSL_PSTRING_dup(st) SKM_sk_dup(OPENSSL_PSTRING, st) -#define sk_OPENSSL_PSTRING_shift(st) SKM_sk_shift(OPENSSL_PSTRING, (st)) -#define sk_OPENSSL_PSTRING_pop(st) (OPENSSL_STRING *)sk_pop(CHECKED_STACK_OF(OPENSSL_PSTRING, st)) -#define sk_OPENSSL_PSTRING_sort(st) SKM_sk_sort(OPENSSL_PSTRING, (st)) -#define sk_OPENSSL_PSTRING_is_sorted(st) SKM_sk_is_sorted(OPENSSL_PSTRING, (st)) - - -#define d2i_ASN1_SET_OF_ACCESS_DESCRIPTION(st, pp, length, d2i_func, free_func, ex_tag, ex_class) \ - SKM_ASN1_SET_OF_d2i(ACCESS_DESCRIPTION, (st), (pp), (length), (d2i_func), (free_func), (ex_tag), (ex_class)) -#define i2d_ASN1_SET_OF_ACCESS_DESCRIPTION(st, pp, i2d_func, ex_tag, ex_class, is_set) \ - SKM_ASN1_SET_OF_i2d(ACCESS_DESCRIPTION, (st), (pp), (i2d_func), (ex_tag), (ex_class), (is_set)) -#define ASN1_seq_pack_ACCESS_DESCRIPTION(st, i2d_func, buf, len) \ - SKM_ASN1_seq_pack(ACCESS_DESCRIPTION, (st), (i2d_func), (buf), (len)) -#define ASN1_seq_unpack_ACCESS_DESCRIPTION(buf, len, d2i_func, free_func) \ - SKM_ASN1_seq_unpack(ACCESS_DESCRIPTION, (buf), (len), (d2i_func), (free_func)) - -#define d2i_ASN1_SET_OF_ASN1_INTEGER(st, pp, length, d2i_func, free_func, ex_tag, ex_class) \ - SKM_ASN1_SET_OF_d2i(ASN1_INTEGER, (st), (pp), (length), (d2i_func), (free_func), (ex_tag), (ex_class)) -#define i2d_ASN1_SET_OF_ASN1_INTEGER(st, pp, i2d_func, ex_tag, ex_class, is_set) \ - SKM_ASN1_SET_OF_i2d(ASN1_INTEGER, (st), (pp), (i2d_func), (ex_tag), (ex_class), (is_set)) -#define ASN1_seq_pack_ASN1_INTEGER(st, i2d_func, buf, len) \ - SKM_ASN1_seq_pack(ASN1_INTEGER, (st), (i2d_func), (buf), (len)) -#define ASN1_seq_unpack_ASN1_INTEGER(buf, len, d2i_func, free_func) \ - SKM_ASN1_seq_unpack(ASN1_INTEGER, (buf), (len), (d2i_func), (free_func)) - -#define d2i_ASN1_SET_OF_ASN1_OBJECT(st, pp, length, d2i_func, free_func, ex_tag, ex_class) \ - SKM_ASN1_SET_OF_d2i(ASN1_OBJECT, (st), (pp), (length), (d2i_func), (free_func), (ex_tag), (ex_class)) -#define i2d_ASN1_SET_OF_ASN1_OBJECT(st, pp, i2d_func, ex_tag, ex_class, is_set) \ - SKM_ASN1_SET_OF_i2d(ASN1_OBJECT, (st), (pp), (i2d_func), (ex_tag), (ex_class), (is_set)) -#define ASN1_seq_pack_ASN1_OBJECT(st, i2d_func, buf, len) \ - SKM_ASN1_seq_pack(ASN1_OBJECT, (st), (i2d_func), (buf), (len)) -#define ASN1_seq_unpack_ASN1_OBJECT(buf, len, d2i_func, free_func) \ - SKM_ASN1_seq_unpack(ASN1_OBJECT, (buf), (len), (d2i_func), (free_func)) - -#define d2i_ASN1_SET_OF_ASN1_TYPE(st, pp, length, d2i_func, free_func, ex_tag, ex_class) \ - SKM_ASN1_SET_OF_d2i(ASN1_TYPE, (st), (pp), (length), (d2i_func), (free_func), (ex_tag), (ex_class)) -#define i2d_ASN1_SET_OF_ASN1_TYPE(st, pp, i2d_func, ex_tag, ex_class, is_set) \ - SKM_ASN1_SET_OF_i2d(ASN1_TYPE, (st), (pp), (i2d_func), (ex_tag), (ex_class), (is_set)) -#define ASN1_seq_pack_ASN1_TYPE(st, i2d_func, buf, len) \ - SKM_ASN1_seq_pack(ASN1_TYPE, (st), (i2d_func), (buf), (len)) -#define ASN1_seq_unpack_ASN1_TYPE(buf, len, d2i_func, free_func) \ - SKM_ASN1_seq_unpack(ASN1_TYPE, (buf), (len), (d2i_func), (free_func)) - -#define d2i_ASN1_SET_OF_ASN1_UTF8STRING(st, pp, length, d2i_func, free_func, ex_tag, ex_class) \ - SKM_ASN1_SET_OF_d2i(ASN1_UTF8STRING, (st), (pp), (length), (d2i_func), (free_func), (ex_tag), (ex_class)) -#define i2d_ASN1_SET_OF_ASN1_UTF8STRING(st, pp, i2d_func, ex_tag, ex_class, is_set) \ - SKM_ASN1_SET_OF_i2d(ASN1_UTF8STRING, (st), (pp), (i2d_func), (ex_tag), (ex_class), (is_set)) -#define ASN1_seq_pack_ASN1_UTF8STRING(st, i2d_func, buf, len) \ - SKM_ASN1_seq_pack(ASN1_UTF8STRING, (st), (i2d_func), (buf), (len)) -#define ASN1_seq_unpack_ASN1_UTF8STRING(buf, len, d2i_func, free_func) \ - SKM_ASN1_seq_unpack(ASN1_UTF8STRING, (buf), (len), (d2i_func), (free_func)) - -#define d2i_ASN1_SET_OF_DIST_POINT(st, pp, length, d2i_func, free_func, ex_tag, ex_class) \ - SKM_ASN1_SET_OF_d2i(DIST_POINT, (st), (pp), (length), (d2i_func), (free_func), (ex_tag), (ex_class)) -#define i2d_ASN1_SET_OF_DIST_POINT(st, pp, i2d_func, ex_tag, ex_class, is_set) \ - SKM_ASN1_SET_OF_i2d(DIST_POINT, (st), (pp), (i2d_func), (ex_tag), (ex_class), (is_set)) -#define ASN1_seq_pack_DIST_POINT(st, i2d_func, buf, len) \ - SKM_ASN1_seq_pack(DIST_POINT, (st), (i2d_func), (buf), (len)) -#define ASN1_seq_unpack_DIST_POINT(buf, len, d2i_func, free_func) \ - SKM_ASN1_seq_unpack(DIST_POINT, (buf), (len), (d2i_func), (free_func)) - -#define d2i_ASN1_SET_OF_ESS_CERT_ID(st, pp, length, d2i_func, free_func, ex_tag, ex_class) \ - SKM_ASN1_SET_OF_d2i(ESS_CERT_ID, (st), (pp), (length), (d2i_func), (free_func), (ex_tag), (ex_class)) -#define i2d_ASN1_SET_OF_ESS_CERT_ID(st, pp, i2d_func, ex_tag, ex_class, is_set) \ - SKM_ASN1_SET_OF_i2d(ESS_CERT_ID, (st), (pp), (i2d_func), (ex_tag), (ex_class), (is_set)) -#define ASN1_seq_pack_ESS_CERT_ID(st, i2d_func, buf, len) \ - SKM_ASN1_seq_pack(ESS_CERT_ID, (st), (i2d_func), (buf), (len)) -#define ASN1_seq_unpack_ESS_CERT_ID(buf, len, d2i_func, free_func) \ - SKM_ASN1_seq_unpack(ESS_CERT_ID, (buf), (len), (d2i_func), (free_func)) - -#define d2i_ASN1_SET_OF_EVP_MD(st, pp, length, d2i_func, free_func, ex_tag, ex_class) \ - SKM_ASN1_SET_OF_d2i(EVP_MD, (st), (pp), (length), (d2i_func), (free_func), (ex_tag), (ex_class)) -#define i2d_ASN1_SET_OF_EVP_MD(st, pp, i2d_func, ex_tag, ex_class, is_set) \ - SKM_ASN1_SET_OF_i2d(EVP_MD, (st), (pp), (i2d_func), (ex_tag), (ex_class), (is_set)) -#define ASN1_seq_pack_EVP_MD(st, i2d_func, buf, len) \ - SKM_ASN1_seq_pack(EVP_MD, (st), (i2d_func), (buf), (len)) -#define ASN1_seq_unpack_EVP_MD(buf, len, d2i_func, free_func) \ - SKM_ASN1_seq_unpack(EVP_MD, (buf), (len), (d2i_func), (free_func)) - -#define d2i_ASN1_SET_OF_GENERAL_NAME(st, pp, length, d2i_func, free_func, ex_tag, ex_class) \ - SKM_ASN1_SET_OF_d2i(GENERAL_NAME, (st), (pp), (length), (d2i_func), (free_func), (ex_tag), (ex_class)) -#define i2d_ASN1_SET_OF_GENERAL_NAME(st, pp, i2d_func, ex_tag, ex_class, is_set) \ - SKM_ASN1_SET_OF_i2d(GENERAL_NAME, (st), (pp), (i2d_func), (ex_tag), (ex_class), (is_set)) -#define ASN1_seq_pack_GENERAL_NAME(st, i2d_func, buf, len) \ - SKM_ASN1_seq_pack(GENERAL_NAME, (st), (i2d_func), (buf), (len)) -#define ASN1_seq_unpack_GENERAL_NAME(buf, len, d2i_func, free_func) \ - SKM_ASN1_seq_unpack(GENERAL_NAME, (buf), (len), (d2i_func), (free_func)) - -#define d2i_ASN1_SET_OF_OCSP_ONEREQ(st, pp, length, d2i_func, free_func, ex_tag, ex_class) \ - SKM_ASN1_SET_OF_d2i(OCSP_ONEREQ, (st), (pp), (length), (d2i_func), (free_func), (ex_tag), (ex_class)) -#define i2d_ASN1_SET_OF_OCSP_ONEREQ(st, pp, i2d_func, ex_tag, ex_class, is_set) \ - SKM_ASN1_SET_OF_i2d(OCSP_ONEREQ, (st), (pp), (i2d_func), (ex_tag), (ex_class), (is_set)) -#define ASN1_seq_pack_OCSP_ONEREQ(st, i2d_func, buf, len) \ - SKM_ASN1_seq_pack(OCSP_ONEREQ, (st), (i2d_func), (buf), (len)) -#define ASN1_seq_unpack_OCSP_ONEREQ(buf, len, d2i_func, free_func) \ - SKM_ASN1_seq_unpack(OCSP_ONEREQ, (buf), (len), (d2i_func), (free_func)) - -#define d2i_ASN1_SET_OF_OCSP_SINGLERESP(st, pp, length, d2i_func, free_func, ex_tag, ex_class) \ - SKM_ASN1_SET_OF_d2i(OCSP_SINGLERESP, (st), (pp), (length), (d2i_func), (free_func), (ex_tag), (ex_class)) -#define i2d_ASN1_SET_OF_OCSP_SINGLERESP(st, pp, i2d_func, ex_tag, ex_class, is_set) \ - SKM_ASN1_SET_OF_i2d(OCSP_SINGLERESP, (st), (pp), (i2d_func), (ex_tag), (ex_class), (is_set)) -#define ASN1_seq_pack_OCSP_SINGLERESP(st, i2d_func, buf, len) \ - SKM_ASN1_seq_pack(OCSP_SINGLERESP, (st), (i2d_func), (buf), (len)) -#define ASN1_seq_unpack_OCSP_SINGLERESP(buf, len, d2i_func, free_func) \ - SKM_ASN1_seq_unpack(OCSP_SINGLERESP, (buf), (len), (d2i_func), (free_func)) - -#define d2i_ASN1_SET_OF_PKCS12_SAFEBAG(st, pp, length, d2i_func, free_func, ex_tag, ex_class) \ - SKM_ASN1_SET_OF_d2i(PKCS12_SAFEBAG, (st), (pp), (length), (d2i_func), (free_func), (ex_tag), (ex_class)) -#define i2d_ASN1_SET_OF_PKCS12_SAFEBAG(st, pp, i2d_func, ex_tag, ex_class, is_set) \ - SKM_ASN1_SET_OF_i2d(PKCS12_SAFEBAG, (st), (pp), (i2d_func), (ex_tag), (ex_class), (is_set)) -#define ASN1_seq_pack_PKCS12_SAFEBAG(st, i2d_func, buf, len) \ - SKM_ASN1_seq_pack(PKCS12_SAFEBAG, (st), (i2d_func), (buf), (len)) -#define ASN1_seq_unpack_PKCS12_SAFEBAG(buf, len, d2i_func, free_func) \ - SKM_ASN1_seq_unpack(PKCS12_SAFEBAG, (buf), (len), (d2i_func), (free_func)) - -#define d2i_ASN1_SET_OF_PKCS7(st, pp, length, d2i_func, free_func, ex_tag, ex_class) \ - SKM_ASN1_SET_OF_d2i(PKCS7, (st), (pp), (length), (d2i_func), (free_func), (ex_tag), (ex_class)) -#define i2d_ASN1_SET_OF_PKCS7(st, pp, i2d_func, ex_tag, ex_class, is_set) \ - SKM_ASN1_SET_OF_i2d(PKCS7, (st), (pp), (i2d_func), (ex_tag), (ex_class), (is_set)) -#define ASN1_seq_pack_PKCS7(st, i2d_func, buf, len) \ - SKM_ASN1_seq_pack(PKCS7, (st), (i2d_func), (buf), (len)) -#define ASN1_seq_unpack_PKCS7(buf, len, d2i_func, free_func) \ - SKM_ASN1_seq_unpack(PKCS7, (buf), (len), (d2i_func), (free_func)) - -#define d2i_ASN1_SET_OF_PKCS7_RECIP_INFO(st, pp, length, d2i_func, free_func, ex_tag, ex_class) \ - SKM_ASN1_SET_OF_d2i(PKCS7_RECIP_INFO, (st), (pp), (length), (d2i_func), (free_func), (ex_tag), (ex_class)) -#define i2d_ASN1_SET_OF_PKCS7_RECIP_INFO(st, pp, i2d_func, ex_tag, ex_class, is_set) \ - SKM_ASN1_SET_OF_i2d(PKCS7_RECIP_INFO, (st), (pp), (i2d_func), (ex_tag), (ex_class), (is_set)) -#define ASN1_seq_pack_PKCS7_RECIP_INFO(st, i2d_func, buf, len) \ - SKM_ASN1_seq_pack(PKCS7_RECIP_INFO, (st), (i2d_func), (buf), (len)) -#define ASN1_seq_unpack_PKCS7_RECIP_INFO(buf, len, d2i_func, free_func) \ - SKM_ASN1_seq_unpack(PKCS7_RECIP_INFO, (buf), (len), (d2i_func), (free_func)) - -#define d2i_ASN1_SET_OF_PKCS7_SIGNER_INFO(st, pp, length, d2i_func, free_func, ex_tag, ex_class) \ - SKM_ASN1_SET_OF_d2i(PKCS7_SIGNER_INFO, (st), (pp), (length), (d2i_func), (free_func), (ex_tag), (ex_class)) -#define i2d_ASN1_SET_OF_PKCS7_SIGNER_INFO(st, pp, i2d_func, ex_tag, ex_class, is_set) \ - SKM_ASN1_SET_OF_i2d(PKCS7_SIGNER_INFO, (st), (pp), (i2d_func), (ex_tag), (ex_class), (is_set)) -#define ASN1_seq_pack_PKCS7_SIGNER_INFO(st, i2d_func, buf, len) \ - SKM_ASN1_seq_pack(PKCS7_SIGNER_INFO, (st), (i2d_func), (buf), (len)) -#define ASN1_seq_unpack_PKCS7_SIGNER_INFO(buf, len, d2i_func, free_func) \ - SKM_ASN1_seq_unpack(PKCS7_SIGNER_INFO, (buf), (len), (d2i_func), (free_func)) - -#define d2i_ASN1_SET_OF_POLICYINFO(st, pp, length, d2i_func, free_func, ex_tag, ex_class) \ - SKM_ASN1_SET_OF_d2i(POLICYINFO, (st), (pp), (length), (d2i_func), (free_func), (ex_tag), (ex_class)) -#define i2d_ASN1_SET_OF_POLICYINFO(st, pp, i2d_func, ex_tag, ex_class, is_set) \ - SKM_ASN1_SET_OF_i2d(POLICYINFO, (st), (pp), (i2d_func), (ex_tag), (ex_class), (is_set)) -#define ASN1_seq_pack_POLICYINFO(st, i2d_func, buf, len) \ - SKM_ASN1_seq_pack(POLICYINFO, (st), (i2d_func), (buf), (len)) -#define ASN1_seq_unpack_POLICYINFO(buf, len, d2i_func, free_func) \ - SKM_ASN1_seq_unpack(POLICYINFO, (buf), (len), (d2i_func), (free_func)) - -#define d2i_ASN1_SET_OF_POLICYQUALINFO(st, pp, length, d2i_func, free_func, ex_tag, ex_class) \ - SKM_ASN1_SET_OF_d2i(POLICYQUALINFO, (st), (pp), (length), (d2i_func), (free_func), (ex_tag), (ex_class)) -#define i2d_ASN1_SET_OF_POLICYQUALINFO(st, pp, i2d_func, ex_tag, ex_class, is_set) \ - SKM_ASN1_SET_OF_i2d(POLICYQUALINFO, (st), (pp), (i2d_func), (ex_tag), (ex_class), (is_set)) -#define ASN1_seq_pack_POLICYQUALINFO(st, i2d_func, buf, len) \ - SKM_ASN1_seq_pack(POLICYQUALINFO, (st), (i2d_func), (buf), (len)) -#define ASN1_seq_unpack_POLICYQUALINFO(buf, len, d2i_func, free_func) \ - SKM_ASN1_seq_unpack(POLICYQUALINFO, (buf), (len), (d2i_func), (free_func)) - -#define d2i_ASN1_SET_OF_SXNETID(st, pp, length, d2i_func, free_func, ex_tag, ex_class) \ - SKM_ASN1_SET_OF_d2i(SXNETID, (st), (pp), (length), (d2i_func), (free_func), (ex_tag), (ex_class)) -#define i2d_ASN1_SET_OF_SXNETID(st, pp, i2d_func, ex_tag, ex_class, is_set) \ - SKM_ASN1_SET_OF_i2d(SXNETID, (st), (pp), (i2d_func), (ex_tag), (ex_class), (is_set)) -#define ASN1_seq_pack_SXNETID(st, i2d_func, buf, len) \ - SKM_ASN1_seq_pack(SXNETID, (st), (i2d_func), (buf), (len)) -#define ASN1_seq_unpack_SXNETID(buf, len, d2i_func, free_func) \ - SKM_ASN1_seq_unpack(SXNETID, (buf), (len), (d2i_func), (free_func)) - -#define d2i_ASN1_SET_OF_X509(st, pp, length, d2i_func, free_func, ex_tag, ex_class) \ - SKM_ASN1_SET_OF_d2i(X509, (st), (pp), (length), (d2i_func), (free_func), (ex_tag), (ex_class)) -#define i2d_ASN1_SET_OF_X509(st, pp, i2d_func, ex_tag, ex_class, is_set) \ - SKM_ASN1_SET_OF_i2d(X509, (st), (pp), (i2d_func), (ex_tag), (ex_class), (is_set)) -#define ASN1_seq_pack_X509(st, i2d_func, buf, len) \ - SKM_ASN1_seq_pack(X509, (st), (i2d_func), (buf), (len)) -#define ASN1_seq_unpack_X509(buf, len, d2i_func, free_func) \ - SKM_ASN1_seq_unpack(X509, (buf), (len), (d2i_func), (free_func)) - -#define d2i_ASN1_SET_OF_X509_ALGOR(st, pp, length, d2i_func, free_func, ex_tag, ex_class) \ - SKM_ASN1_SET_OF_d2i(X509_ALGOR, (st), (pp), (length), (d2i_func), (free_func), (ex_tag), (ex_class)) -#define i2d_ASN1_SET_OF_X509_ALGOR(st, pp, i2d_func, ex_tag, ex_class, is_set) \ - SKM_ASN1_SET_OF_i2d(X509_ALGOR, (st), (pp), (i2d_func), (ex_tag), (ex_class), (is_set)) -#define ASN1_seq_pack_X509_ALGOR(st, i2d_func, buf, len) \ - SKM_ASN1_seq_pack(X509_ALGOR, (st), (i2d_func), (buf), (len)) -#define ASN1_seq_unpack_X509_ALGOR(buf, len, d2i_func, free_func) \ - SKM_ASN1_seq_unpack(X509_ALGOR, (buf), (len), (d2i_func), (free_func)) - -#define d2i_ASN1_SET_OF_X509_ATTRIBUTE(st, pp, length, d2i_func, free_func, ex_tag, ex_class) \ - SKM_ASN1_SET_OF_d2i(X509_ATTRIBUTE, (st), (pp), (length), (d2i_func), (free_func), (ex_tag), (ex_class)) -#define i2d_ASN1_SET_OF_X509_ATTRIBUTE(st, pp, i2d_func, ex_tag, ex_class, is_set) \ - SKM_ASN1_SET_OF_i2d(X509_ATTRIBUTE, (st), (pp), (i2d_func), (ex_tag), (ex_class), (is_set)) -#define ASN1_seq_pack_X509_ATTRIBUTE(st, i2d_func, buf, len) \ - SKM_ASN1_seq_pack(X509_ATTRIBUTE, (st), (i2d_func), (buf), (len)) -#define ASN1_seq_unpack_X509_ATTRIBUTE(buf, len, d2i_func, free_func) \ - SKM_ASN1_seq_unpack(X509_ATTRIBUTE, (buf), (len), (d2i_func), (free_func)) - -#define d2i_ASN1_SET_OF_X509_CRL(st, pp, length, d2i_func, free_func, ex_tag, ex_class) \ - SKM_ASN1_SET_OF_d2i(X509_CRL, (st), (pp), (length), (d2i_func), (free_func), (ex_tag), (ex_class)) -#define i2d_ASN1_SET_OF_X509_CRL(st, pp, i2d_func, ex_tag, ex_class, is_set) \ - SKM_ASN1_SET_OF_i2d(X509_CRL, (st), (pp), (i2d_func), (ex_tag), (ex_class), (is_set)) -#define ASN1_seq_pack_X509_CRL(st, i2d_func, buf, len) \ - SKM_ASN1_seq_pack(X509_CRL, (st), (i2d_func), (buf), (len)) -#define ASN1_seq_unpack_X509_CRL(buf, len, d2i_func, free_func) \ - SKM_ASN1_seq_unpack(X509_CRL, (buf), (len), (d2i_func), (free_func)) - -#define d2i_ASN1_SET_OF_X509_EXTENSION(st, pp, length, d2i_func, free_func, ex_tag, ex_class) \ - SKM_ASN1_SET_OF_d2i(X509_EXTENSION, (st), (pp), (length), (d2i_func), (free_func), (ex_tag), (ex_class)) -#define i2d_ASN1_SET_OF_X509_EXTENSION(st, pp, i2d_func, ex_tag, ex_class, is_set) \ - SKM_ASN1_SET_OF_i2d(X509_EXTENSION, (st), (pp), (i2d_func), (ex_tag), (ex_class), (is_set)) -#define ASN1_seq_pack_X509_EXTENSION(st, i2d_func, buf, len) \ - SKM_ASN1_seq_pack(X509_EXTENSION, (st), (i2d_func), (buf), (len)) -#define ASN1_seq_unpack_X509_EXTENSION(buf, len, d2i_func, free_func) \ - SKM_ASN1_seq_unpack(X509_EXTENSION, (buf), (len), (d2i_func), (free_func)) - -#define d2i_ASN1_SET_OF_X509_NAME_ENTRY(st, pp, length, d2i_func, free_func, ex_tag, ex_class) \ - SKM_ASN1_SET_OF_d2i(X509_NAME_ENTRY, (st), (pp), (length), (d2i_func), (free_func), (ex_tag), (ex_class)) -#define i2d_ASN1_SET_OF_X509_NAME_ENTRY(st, pp, i2d_func, ex_tag, ex_class, is_set) \ - SKM_ASN1_SET_OF_i2d(X509_NAME_ENTRY, (st), (pp), (i2d_func), (ex_tag), (ex_class), (is_set)) -#define ASN1_seq_pack_X509_NAME_ENTRY(st, i2d_func, buf, len) \ - SKM_ASN1_seq_pack(X509_NAME_ENTRY, (st), (i2d_func), (buf), (len)) -#define ASN1_seq_unpack_X509_NAME_ENTRY(buf, len, d2i_func, free_func) \ - SKM_ASN1_seq_unpack(X509_NAME_ENTRY, (buf), (len), (d2i_func), (free_func)) - -#define d2i_ASN1_SET_OF_X509_REVOKED(st, pp, length, d2i_func, free_func, ex_tag, ex_class) \ - SKM_ASN1_SET_OF_d2i(X509_REVOKED, (st), (pp), (length), (d2i_func), (free_func), (ex_tag), (ex_class)) -#define i2d_ASN1_SET_OF_X509_REVOKED(st, pp, i2d_func, ex_tag, ex_class, is_set) \ - SKM_ASN1_SET_OF_i2d(X509_REVOKED, (st), (pp), (i2d_func), (ex_tag), (ex_class), (is_set)) -#define ASN1_seq_pack_X509_REVOKED(st, i2d_func, buf, len) \ - SKM_ASN1_seq_pack(X509_REVOKED, (st), (i2d_func), (buf), (len)) -#define ASN1_seq_unpack_X509_REVOKED(buf, len, d2i_func, free_func) \ - SKM_ASN1_seq_unpack(X509_REVOKED, (buf), (len), (d2i_func), (free_func)) - -#define PKCS12_decrypt_d2i_PKCS12_SAFEBAG(algor, d2i_func, free_func, pass, passlen, oct, seq) \ - SKM_PKCS12_decrypt_d2i(PKCS12_SAFEBAG, (algor), (d2i_func), (free_func), (pass), (passlen), (oct), (seq)) - -#define PKCS12_decrypt_d2i_PKCS7(algor, d2i_func, free_func, pass, passlen, oct, seq) \ - SKM_PKCS12_decrypt_d2i(PKCS7, (algor), (d2i_func), (free_func), (pass), (passlen), (oct), (seq)) - -#define lh_ADDED_OBJ_new() LHM_lh_new(ADDED_OBJ,added_obj) -#define lh_ADDED_OBJ_insert(lh,inst) LHM_lh_insert(ADDED_OBJ,lh,inst) -#define lh_ADDED_OBJ_retrieve(lh,inst) LHM_lh_retrieve(ADDED_OBJ,lh,inst) -#define lh_ADDED_OBJ_delete(lh,inst) LHM_lh_delete(ADDED_OBJ,lh,inst) -#define lh_ADDED_OBJ_doall(lh,fn) LHM_lh_doall(ADDED_OBJ,lh,fn) -#define lh_ADDED_OBJ_doall_arg(lh,fn,arg_type,arg) \ - LHM_lh_doall_arg(ADDED_OBJ,lh,fn,arg_type,arg) -#define lh_ADDED_OBJ_error(lh) LHM_lh_error(ADDED_OBJ,lh) -#define lh_ADDED_OBJ_num_items(lh) LHM_lh_num_items(ADDED_OBJ,lh) -#define lh_ADDED_OBJ_down_load(lh) LHM_lh_down_load(ADDED_OBJ,lh) -#define lh_ADDED_OBJ_node_stats_bio(lh,out) \ - LHM_lh_node_stats_bio(ADDED_OBJ,lh,out) -#define lh_ADDED_OBJ_node_usage_stats_bio(lh,out) \ - LHM_lh_node_usage_stats_bio(ADDED_OBJ,lh,out) -#define lh_ADDED_OBJ_stats_bio(lh,out) \ - LHM_lh_stats_bio(ADDED_OBJ,lh,out) -#define lh_ADDED_OBJ_free(lh) LHM_lh_free(ADDED_OBJ,lh) - -#define lh_APP_INFO_new() LHM_lh_new(APP_INFO,app_info) -#define lh_APP_INFO_insert(lh,inst) LHM_lh_insert(APP_INFO,lh,inst) -#define lh_APP_INFO_retrieve(lh,inst) LHM_lh_retrieve(APP_INFO,lh,inst) -#define lh_APP_INFO_delete(lh,inst) LHM_lh_delete(APP_INFO,lh,inst) -#define lh_APP_INFO_doall(lh,fn) LHM_lh_doall(APP_INFO,lh,fn) -#define lh_APP_INFO_doall_arg(lh,fn,arg_type,arg) \ - LHM_lh_doall_arg(APP_INFO,lh,fn,arg_type,arg) -#define lh_APP_INFO_error(lh) LHM_lh_error(APP_INFO,lh) -#define lh_APP_INFO_num_items(lh) LHM_lh_num_items(APP_INFO,lh) -#define lh_APP_INFO_down_load(lh) LHM_lh_down_load(APP_INFO,lh) -#define lh_APP_INFO_node_stats_bio(lh,out) \ - LHM_lh_node_stats_bio(APP_INFO,lh,out) -#define lh_APP_INFO_node_usage_stats_bio(lh,out) \ - LHM_lh_node_usage_stats_bio(APP_INFO,lh,out) -#define lh_APP_INFO_stats_bio(lh,out) \ - LHM_lh_stats_bio(APP_INFO,lh,out) -#define lh_APP_INFO_free(lh) LHM_lh_free(APP_INFO,lh) - -#define lh_CONF_VALUE_new() LHM_lh_new(CONF_VALUE,conf_value) -#define lh_CONF_VALUE_insert(lh,inst) LHM_lh_insert(CONF_VALUE,lh,inst) -#define lh_CONF_VALUE_retrieve(lh,inst) LHM_lh_retrieve(CONF_VALUE,lh,inst) -#define lh_CONF_VALUE_delete(lh,inst) LHM_lh_delete(CONF_VALUE,lh,inst) -#define lh_CONF_VALUE_doall(lh,fn) LHM_lh_doall(CONF_VALUE,lh,fn) -#define lh_CONF_VALUE_doall_arg(lh,fn,arg_type,arg) \ - LHM_lh_doall_arg(CONF_VALUE,lh,fn,arg_type,arg) -#define lh_CONF_VALUE_error(lh) LHM_lh_error(CONF_VALUE,lh) -#define lh_CONF_VALUE_num_items(lh) LHM_lh_num_items(CONF_VALUE,lh) -#define lh_CONF_VALUE_down_load(lh) LHM_lh_down_load(CONF_VALUE,lh) -#define lh_CONF_VALUE_node_stats_bio(lh,out) \ - LHM_lh_node_stats_bio(CONF_VALUE,lh,out) -#define lh_CONF_VALUE_node_usage_stats_bio(lh,out) \ - LHM_lh_node_usage_stats_bio(CONF_VALUE,lh,out) -#define lh_CONF_VALUE_stats_bio(lh,out) \ - LHM_lh_stats_bio(CONF_VALUE,lh,out) -#define lh_CONF_VALUE_free(lh) LHM_lh_free(CONF_VALUE,lh) - -#define lh_ENGINE_PILE_new() LHM_lh_new(ENGINE_PILE,engine_pile) -#define lh_ENGINE_PILE_insert(lh,inst) LHM_lh_insert(ENGINE_PILE,lh,inst) -#define lh_ENGINE_PILE_retrieve(lh,inst) LHM_lh_retrieve(ENGINE_PILE,lh,inst) -#define lh_ENGINE_PILE_delete(lh,inst) LHM_lh_delete(ENGINE_PILE,lh,inst) -#define lh_ENGINE_PILE_doall(lh,fn) LHM_lh_doall(ENGINE_PILE,lh,fn) -#define lh_ENGINE_PILE_doall_arg(lh,fn,arg_type,arg) \ - LHM_lh_doall_arg(ENGINE_PILE,lh,fn,arg_type,arg) -#define lh_ENGINE_PILE_error(lh) LHM_lh_error(ENGINE_PILE,lh) -#define lh_ENGINE_PILE_num_items(lh) LHM_lh_num_items(ENGINE_PILE,lh) -#define lh_ENGINE_PILE_down_load(lh) LHM_lh_down_load(ENGINE_PILE,lh) -#define lh_ENGINE_PILE_node_stats_bio(lh,out) \ - LHM_lh_node_stats_bio(ENGINE_PILE,lh,out) -#define lh_ENGINE_PILE_node_usage_stats_bio(lh,out) \ - LHM_lh_node_usage_stats_bio(ENGINE_PILE,lh,out) -#define lh_ENGINE_PILE_stats_bio(lh,out) \ - LHM_lh_stats_bio(ENGINE_PILE,lh,out) -#define lh_ENGINE_PILE_free(lh) LHM_lh_free(ENGINE_PILE,lh) - -#define lh_ERR_STATE_new() LHM_lh_new(ERR_STATE,err_state) -#define lh_ERR_STATE_insert(lh,inst) LHM_lh_insert(ERR_STATE,lh,inst) -#define lh_ERR_STATE_retrieve(lh,inst) LHM_lh_retrieve(ERR_STATE,lh,inst) -#define lh_ERR_STATE_delete(lh,inst) LHM_lh_delete(ERR_STATE,lh,inst) -#define lh_ERR_STATE_doall(lh,fn) LHM_lh_doall(ERR_STATE,lh,fn) -#define lh_ERR_STATE_doall_arg(lh,fn,arg_type,arg) \ - LHM_lh_doall_arg(ERR_STATE,lh,fn,arg_type,arg) -#define lh_ERR_STATE_error(lh) LHM_lh_error(ERR_STATE,lh) -#define lh_ERR_STATE_num_items(lh) LHM_lh_num_items(ERR_STATE,lh) -#define lh_ERR_STATE_down_load(lh) LHM_lh_down_load(ERR_STATE,lh) -#define lh_ERR_STATE_node_stats_bio(lh,out) \ - LHM_lh_node_stats_bio(ERR_STATE,lh,out) -#define lh_ERR_STATE_node_usage_stats_bio(lh,out) \ - LHM_lh_node_usage_stats_bio(ERR_STATE,lh,out) -#define lh_ERR_STATE_stats_bio(lh,out) \ - LHM_lh_stats_bio(ERR_STATE,lh,out) -#define lh_ERR_STATE_free(lh) LHM_lh_free(ERR_STATE,lh) - -#define lh_ERR_STRING_DATA_new() LHM_lh_new(ERR_STRING_DATA,err_string_data) -#define lh_ERR_STRING_DATA_insert(lh,inst) LHM_lh_insert(ERR_STRING_DATA,lh,inst) -#define lh_ERR_STRING_DATA_retrieve(lh,inst) LHM_lh_retrieve(ERR_STRING_DATA,lh,inst) -#define lh_ERR_STRING_DATA_delete(lh,inst) LHM_lh_delete(ERR_STRING_DATA,lh,inst) -#define lh_ERR_STRING_DATA_doall(lh,fn) LHM_lh_doall(ERR_STRING_DATA,lh,fn) -#define lh_ERR_STRING_DATA_doall_arg(lh,fn,arg_type,arg) \ - LHM_lh_doall_arg(ERR_STRING_DATA,lh,fn,arg_type,arg) -#define lh_ERR_STRING_DATA_error(lh) LHM_lh_error(ERR_STRING_DATA,lh) -#define lh_ERR_STRING_DATA_num_items(lh) LHM_lh_num_items(ERR_STRING_DATA,lh) -#define lh_ERR_STRING_DATA_down_load(lh) LHM_lh_down_load(ERR_STRING_DATA,lh) -#define lh_ERR_STRING_DATA_node_stats_bio(lh,out) \ - LHM_lh_node_stats_bio(ERR_STRING_DATA,lh,out) -#define lh_ERR_STRING_DATA_node_usage_stats_bio(lh,out) \ - LHM_lh_node_usage_stats_bio(ERR_STRING_DATA,lh,out) -#define lh_ERR_STRING_DATA_stats_bio(lh,out) \ - LHM_lh_stats_bio(ERR_STRING_DATA,lh,out) -#define lh_ERR_STRING_DATA_free(lh) LHM_lh_free(ERR_STRING_DATA,lh) - -#define lh_EX_CLASS_ITEM_new() LHM_lh_new(EX_CLASS_ITEM,ex_class_item) -#define lh_EX_CLASS_ITEM_insert(lh,inst) LHM_lh_insert(EX_CLASS_ITEM,lh,inst) -#define lh_EX_CLASS_ITEM_retrieve(lh,inst) LHM_lh_retrieve(EX_CLASS_ITEM,lh,inst) -#define lh_EX_CLASS_ITEM_delete(lh,inst) LHM_lh_delete(EX_CLASS_ITEM,lh,inst) -#define lh_EX_CLASS_ITEM_doall(lh,fn) LHM_lh_doall(EX_CLASS_ITEM,lh,fn) -#define lh_EX_CLASS_ITEM_doall_arg(lh,fn,arg_type,arg) \ - LHM_lh_doall_arg(EX_CLASS_ITEM,lh,fn,arg_type,arg) -#define lh_EX_CLASS_ITEM_error(lh) LHM_lh_error(EX_CLASS_ITEM,lh) -#define lh_EX_CLASS_ITEM_num_items(lh) LHM_lh_num_items(EX_CLASS_ITEM,lh) -#define lh_EX_CLASS_ITEM_down_load(lh) LHM_lh_down_load(EX_CLASS_ITEM,lh) -#define lh_EX_CLASS_ITEM_node_stats_bio(lh,out) \ - LHM_lh_node_stats_bio(EX_CLASS_ITEM,lh,out) -#define lh_EX_CLASS_ITEM_node_usage_stats_bio(lh,out) \ - LHM_lh_node_usage_stats_bio(EX_CLASS_ITEM,lh,out) -#define lh_EX_CLASS_ITEM_stats_bio(lh,out) \ - LHM_lh_stats_bio(EX_CLASS_ITEM,lh,out) -#define lh_EX_CLASS_ITEM_free(lh) LHM_lh_free(EX_CLASS_ITEM,lh) - -#define lh_FUNCTION_new() LHM_lh_new(FUNCTION,function) -#define lh_FUNCTION_insert(lh,inst) LHM_lh_insert(FUNCTION,lh,inst) -#define lh_FUNCTION_retrieve(lh,inst) LHM_lh_retrieve(FUNCTION,lh,inst) -#define lh_FUNCTION_delete(lh,inst) LHM_lh_delete(FUNCTION,lh,inst) -#define lh_FUNCTION_doall(lh,fn) LHM_lh_doall(FUNCTION,lh,fn) -#define lh_FUNCTION_doall_arg(lh,fn,arg_type,arg) \ - LHM_lh_doall_arg(FUNCTION,lh,fn,arg_type,arg) -#define lh_FUNCTION_error(lh) LHM_lh_error(FUNCTION,lh) -#define lh_FUNCTION_num_items(lh) LHM_lh_num_items(FUNCTION,lh) -#define lh_FUNCTION_down_load(lh) LHM_lh_down_load(FUNCTION,lh) -#define lh_FUNCTION_node_stats_bio(lh,out) \ - LHM_lh_node_stats_bio(FUNCTION,lh,out) -#define lh_FUNCTION_node_usage_stats_bio(lh,out) \ - LHM_lh_node_usage_stats_bio(FUNCTION,lh,out) -#define lh_FUNCTION_stats_bio(lh,out) \ - LHM_lh_stats_bio(FUNCTION,lh,out) -#define lh_FUNCTION_free(lh) LHM_lh_free(FUNCTION,lh) - -#define lh_MEM_new() LHM_lh_new(MEM,mem) -#define lh_MEM_insert(lh,inst) LHM_lh_insert(MEM,lh,inst) -#define lh_MEM_retrieve(lh,inst) LHM_lh_retrieve(MEM,lh,inst) -#define lh_MEM_delete(lh,inst) LHM_lh_delete(MEM,lh,inst) -#define lh_MEM_doall(lh,fn) LHM_lh_doall(MEM,lh,fn) -#define lh_MEM_doall_arg(lh,fn,arg_type,arg) \ - LHM_lh_doall_arg(MEM,lh,fn,arg_type,arg) -#define lh_MEM_error(lh) LHM_lh_error(MEM,lh) -#define lh_MEM_num_items(lh) LHM_lh_num_items(MEM,lh) -#define lh_MEM_down_load(lh) LHM_lh_down_load(MEM,lh) -#define lh_MEM_node_stats_bio(lh,out) \ - LHM_lh_node_stats_bio(MEM,lh,out) -#define lh_MEM_node_usage_stats_bio(lh,out) \ - LHM_lh_node_usage_stats_bio(MEM,lh,out) -#define lh_MEM_stats_bio(lh,out) \ - LHM_lh_stats_bio(MEM,lh,out) -#define lh_MEM_free(lh) LHM_lh_free(MEM,lh) - -#define lh_OBJ_NAME_new() LHM_lh_new(OBJ_NAME,obj_name) -#define lh_OBJ_NAME_insert(lh,inst) LHM_lh_insert(OBJ_NAME,lh,inst) -#define lh_OBJ_NAME_retrieve(lh,inst) LHM_lh_retrieve(OBJ_NAME,lh,inst) -#define lh_OBJ_NAME_delete(lh,inst) LHM_lh_delete(OBJ_NAME,lh,inst) -#define lh_OBJ_NAME_doall(lh,fn) LHM_lh_doall(OBJ_NAME,lh,fn) -#define lh_OBJ_NAME_doall_arg(lh,fn,arg_type,arg) \ - LHM_lh_doall_arg(OBJ_NAME,lh,fn,arg_type,arg) -#define lh_OBJ_NAME_error(lh) LHM_lh_error(OBJ_NAME,lh) -#define lh_OBJ_NAME_num_items(lh) LHM_lh_num_items(OBJ_NAME,lh) -#define lh_OBJ_NAME_down_load(lh) LHM_lh_down_load(OBJ_NAME,lh) -#define lh_OBJ_NAME_node_stats_bio(lh,out) \ - LHM_lh_node_stats_bio(OBJ_NAME,lh,out) -#define lh_OBJ_NAME_node_usage_stats_bio(lh,out) \ - LHM_lh_node_usage_stats_bio(OBJ_NAME,lh,out) -#define lh_OBJ_NAME_stats_bio(lh,out) \ - LHM_lh_stats_bio(OBJ_NAME,lh,out) -#define lh_OBJ_NAME_free(lh) LHM_lh_free(OBJ_NAME,lh) - -#define lh_OPENSSL_CSTRING_new() LHM_lh_new(OPENSSL_CSTRING,openssl_cstring) -#define lh_OPENSSL_CSTRING_insert(lh,inst) LHM_lh_insert(OPENSSL_CSTRING,lh,inst) -#define lh_OPENSSL_CSTRING_retrieve(lh,inst) LHM_lh_retrieve(OPENSSL_CSTRING,lh,inst) -#define lh_OPENSSL_CSTRING_delete(lh,inst) LHM_lh_delete(OPENSSL_CSTRING,lh,inst) -#define lh_OPENSSL_CSTRING_doall(lh,fn) LHM_lh_doall(OPENSSL_CSTRING,lh,fn) -#define lh_OPENSSL_CSTRING_doall_arg(lh,fn,arg_type,arg) \ - LHM_lh_doall_arg(OPENSSL_CSTRING,lh,fn,arg_type,arg) -#define lh_OPENSSL_CSTRING_error(lh) LHM_lh_error(OPENSSL_CSTRING,lh) -#define lh_OPENSSL_CSTRING_num_items(lh) LHM_lh_num_items(OPENSSL_CSTRING,lh) -#define lh_OPENSSL_CSTRING_down_load(lh) LHM_lh_down_load(OPENSSL_CSTRING,lh) -#define lh_OPENSSL_CSTRING_node_stats_bio(lh,out) \ - LHM_lh_node_stats_bio(OPENSSL_CSTRING,lh,out) -#define lh_OPENSSL_CSTRING_node_usage_stats_bio(lh,out) \ - LHM_lh_node_usage_stats_bio(OPENSSL_CSTRING,lh,out) -#define lh_OPENSSL_CSTRING_stats_bio(lh,out) \ - LHM_lh_stats_bio(OPENSSL_CSTRING,lh,out) -#define lh_OPENSSL_CSTRING_free(lh) LHM_lh_free(OPENSSL_CSTRING,lh) - -#define lh_OPENSSL_STRING_new() LHM_lh_new(OPENSSL_STRING,openssl_string) -#define lh_OPENSSL_STRING_insert(lh,inst) LHM_lh_insert(OPENSSL_STRING,lh,inst) -#define lh_OPENSSL_STRING_retrieve(lh,inst) LHM_lh_retrieve(OPENSSL_STRING,lh,inst) -#define lh_OPENSSL_STRING_delete(lh,inst) LHM_lh_delete(OPENSSL_STRING,lh,inst) -#define lh_OPENSSL_STRING_doall(lh,fn) LHM_lh_doall(OPENSSL_STRING,lh,fn) -#define lh_OPENSSL_STRING_doall_arg(lh,fn,arg_type,arg) \ - LHM_lh_doall_arg(OPENSSL_STRING,lh,fn,arg_type,arg) -#define lh_OPENSSL_STRING_error(lh) LHM_lh_error(OPENSSL_STRING,lh) -#define lh_OPENSSL_STRING_num_items(lh) LHM_lh_num_items(OPENSSL_STRING,lh) -#define lh_OPENSSL_STRING_down_load(lh) LHM_lh_down_load(OPENSSL_STRING,lh) -#define lh_OPENSSL_STRING_node_stats_bio(lh,out) \ - LHM_lh_node_stats_bio(OPENSSL_STRING,lh,out) -#define lh_OPENSSL_STRING_node_usage_stats_bio(lh,out) \ - LHM_lh_node_usage_stats_bio(OPENSSL_STRING,lh,out) -#define lh_OPENSSL_STRING_stats_bio(lh,out) \ - LHM_lh_stats_bio(OPENSSL_STRING,lh,out) -#define lh_OPENSSL_STRING_free(lh) LHM_lh_free(OPENSSL_STRING,lh) - -#define lh_SSL_SESSION_new() LHM_lh_new(SSL_SESSION,ssl_session) -#define lh_SSL_SESSION_insert(lh,inst) LHM_lh_insert(SSL_SESSION,lh,inst) -#define lh_SSL_SESSION_retrieve(lh,inst) LHM_lh_retrieve(SSL_SESSION,lh,inst) -#define lh_SSL_SESSION_delete(lh,inst) LHM_lh_delete(SSL_SESSION,lh,inst) -#define lh_SSL_SESSION_doall(lh,fn) LHM_lh_doall(SSL_SESSION,lh,fn) -#define lh_SSL_SESSION_doall_arg(lh,fn,arg_type,arg) \ - LHM_lh_doall_arg(SSL_SESSION,lh,fn,arg_type,arg) -#define lh_SSL_SESSION_error(lh) LHM_lh_error(SSL_SESSION,lh) -#define lh_SSL_SESSION_num_items(lh) LHM_lh_num_items(SSL_SESSION,lh) -#define lh_SSL_SESSION_down_load(lh) LHM_lh_down_load(SSL_SESSION,lh) -#define lh_SSL_SESSION_node_stats_bio(lh,out) \ - LHM_lh_node_stats_bio(SSL_SESSION,lh,out) -#define lh_SSL_SESSION_node_usage_stats_bio(lh,out) \ - LHM_lh_node_usage_stats_bio(SSL_SESSION,lh,out) -#define lh_SSL_SESSION_stats_bio(lh,out) \ - LHM_lh_stats_bio(SSL_SESSION,lh,out) -#define lh_SSL_SESSION_free(lh) LHM_lh_free(SSL_SESSION,lh) -/* End of util/mkstack.pl block, you may now edit :-) */ - -#endif /* !defined HEADER_SAFESTACK_H */ diff --git a/src/plugins/ftp/openssl/sha.h b/src/plugins/ftp/openssl/sha.h deleted file mode 100644 index 8a6bf4bb..00000000 --- a/src/plugins/ftp/openssl/sha.h +++ /dev/null @@ -1,214 +0,0 @@ -/* crypto/sha/sha.h */ -/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) - * All rights reserved. - * - * This package is an SSL implementation written - * by Eric Young (eay@cryptsoft.com). - * The implementation was written so as to conform with Netscapes SSL. - * - * This library is free for commercial and non-commercial use as long as - * the following conditions are aheared to. The following conditions - * apply to all code found in this distribution, be it the RC4, RSA, - * lhash, DES, etc., code; not just the SSL code. The SSL documentation - * included with this distribution is covered by the same copyright terms - * except that the holder is Tim Hudson (tjh@cryptsoft.com). - * - * Copyright remains Eric Young's, and as such any Copyright notices in - * the code are not to be removed. - * If this package is used in a product, Eric Young should be given attribution - * as the author of the parts of the library used. - * This can be in the form of a textual message at program startup or - * in documentation (online or textual) provided with the package. - * - * 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 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. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * "This product includes cryptographic software written by - * Eric Young (eay@cryptsoft.com)" - * The word 'cryptographic' can be left out if the rouines from the library - * being used are not cryptographic related :-). - * 4. If you include any Windows specific code (or a derivative thereof) from - * the apps directory (application code) you must include an acknowledgement: - * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" - * - * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``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 AUTHOR 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. - * - * The licence and distribution terms for any publically available version or - * derivative of this code cannot be changed. i.e. this code cannot simply be - * copied and put under another distribution licence - * [including the GNU Public Licence.] - */ - -#ifndef HEADER_SHA_H -#define HEADER_SHA_H - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#if defined(OPENSSL_NO_SHA) || (defined(OPENSSL_NO_SHA0) && defined(OPENSSL_NO_SHA1)) -#error SHA is disabled. -#endif - -#if defined(OPENSSL_FIPS) -#define FIPS_SHA_SIZE_T size_t -#endif - -/* - * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - * ! SHA_LONG has to be at least 32 bits wide. If it's wider, then ! - * ! SHA_LONG_LOG2 has to be defined along. ! - * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - */ - -#if defined(__LP32__) -#define SHA_LONG unsigned long -#elif defined(OPENSSL_SYS_CRAY) || defined(__ILP64__) -#define SHA_LONG unsigned long -#define SHA_LONG_LOG2 3 -#else -#define SHA_LONG unsigned int -#endif - -#define SHA_LBLOCK 16 -#define SHA_CBLOCK (SHA_LBLOCK*4) /* SHA treats input data as a - * contiguous array of 32 bit - * wide big-endian values. */ -#define SHA_LAST_BLOCK (SHA_CBLOCK-8) -#define SHA_DIGEST_LENGTH 20 - -typedef struct SHAstate_st - { - SHA_LONG h0,h1,h2,h3,h4; - SHA_LONG Nl,Nh; - SHA_LONG data[SHA_LBLOCK]; - unsigned int num; - } SHA_CTX; - -#ifndef OPENSSL_NO_SHA0 -#ifdef OPENSSL_FIPS -int private_SHA_Init(SHA_CTX *c); -#endif -int SHA_Init(SHA_CTX *c); -int SHA_Update(SHA_CTX *c, const void *data, size_t len); -int SHA_Final(unsigned char *md, SHA_CTX *c); -unsigned char *SHA(const unsigned char *d, size_t n, unsigned char *md); -void SHA_Transform(SHA_CTX *c, const unsigned char *data); -#endif -#ifndef OPENSSL_NO_SHA1 -#ifdef OPENSSL_FIPS -int private_SHA1_Init(SHA_CTX *c); -#endif -int SHA1_Init(SHA_CTX *c); -int SHA1_Update(SHA_CTX *c, const void *data, size_t len); -int SHA1_Final(unsigned char *md, SHA_CTX *c); -unsigned char *SHA1(const unsigned char *d, size_t n, unsigned char *md); -void SHA1_Transform(SHA_CTX *c, const unsigned char *data); -#endif - -#define SHA256_CBLOCK (SHA_LBLOCK*4) /* SHA-256 treats input data as a - * contiguous array of 32 bit - * wide big-endian values. */ -#define SHA224_DIGEST_LENGTH 28 -#define SHA256_DIGEST_LENGTH 32 - -typedef struct SHA256state_st - { - SHA_LONG h[8]; - SHA_LONG Nl,Nh; - SHA_LONG data[SHA_LBLOCK]; - unsigned int num,md_len; - } SHA256_CTX; - -#ifndef OPENSSL_NO_SHA256 -#ifdef OPENSSL_FIPS -int private_SHA224_Init(SHA256_CTX *c); -int private_SHA256_Init(SHA256_CTX *c); -#endif -int SHA224_Init(SHA256_CTX *c); -int SHA224_Update(SHA256_CTX *c, const void *data, size_t len); -int SHA224_Final(unsigned char *md, SHA256_CTX *c); -unsigned char *SHA224(const unsigned char *d, size_t n,unsigned char *md); -int SHA256_Init(SHA256_CTX *c); -int SHA256_Update(SHA256_CTX *c, const void *data, size_t len); -int SHA256_Final(unsigned char *md, SHA256_CTX *c); -unsigned char *SHA256(const unsigned char *d, size_t n,unsigned char *md); -void SHA256_Transform(SHA256_CTX *c, const unsigned char *data); -#endif - -#define SHA384_DIGEST_LENGTH 48 -#define SHA512_DIGEST_LENGTH 64 - -#ifndef OPENSSL_NO_SHA512 -/* - * Unlike 32-bit digest algorithms, SHA-512 *relies* on SHA_LONG64 - * being exactly 64-bit wide. See Implementation Notes in sha512.c - * for further details. - */ -#define SHA512_CBLOCK (SHA_LBLOCK*8) /* SHA-512 treats input data as a - * contiguous array of 64 bit - * wide big-endian values. */ -#if (defined(_WIN32) || defined(_WIN64)) && !defined(__MINGW32__) -#define SHA_LONG64 unsigned __int64 -#define U64(C) C##UI64 -#elif defined(__arch64__) -#define SHA_LONG64 unsigned long -#define U64(C) C##UL -#else -#define SHA_LONG64 unsigned long long -#define U64(C) C##ULL -#endif - -typedef struct SHA512state_st - { - SHA_LONG64 h[8]; - SHA_LONG64 Nl,Nh; - union { - SHA_LONG64 d[SHA_LBLOCK]; - unsigned char p[SHA512_CBLOCK]; - } u; - unsigned int num,md_len; - } SHA512_CTX; -#endif - -#ifndef OPENSSL_NO_SHA512 -#ifdef OPENSSL_FIPS -int private_SHA384_Init(SHA512_CTX *c); -int private_SHA512_Init(SHA512_CTX *c); -#endif -int SHA384_Init(SHA512_CTX *c); -int SHA384_Update(SHA512_CTX *c, const void *data, size_t len); -int SHA384_Final(unsigned char *md, SHA512_CTX *c); -unsigned char *SHA384(const unsigned char *d, size_t n,unsigned char *md); -int SHA512_Init(SHA512_CTX *c); -int SHA512_Update(SHA512_CTX *c, const void *data, size_t len); -int SHA512_Final(unsigned char *md, SHA512_CTX *c); -unsigned char *SHA512(const unsigned char *d, size_t n,unsigned char *md); -void SHA512_Transform(SHA512_CTX *c, const unsigned char *data); -#endif - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/src/plugins/ftp/openssl/stack.h b/src/plugins/ftp/openssl/stack.h deleted file mode 100644 index ce35e554..00000000 --- a/src/plugins/ftp/openssl/stack.h +++ /dev/null @@ -1,108 +0,0 @@ -/* crypto/stack/stack.h */ -/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) - * All rights reserved. - * - * This package is an SSL implementation written - * by Eric Young (eay@cryptsoft.com). - * The implementation was written so as to conform with Netscapes SSL. - * - * This library is free for commercial and non-commercial use as long as - * the following conditions are aheared to. The following conditions - * apply to all code found in this distribution, be it the RC4, RSA, - * lhash, DES, etc., code; not just the SSL code. The SSL documentation - * included with this distribution is covered by the same copyright terms - * except that the holder is Tim Hudson (tjh@cryptsoft.com). - * - * Copyright remains Eric Young's, and as such any Copyright notices in - * the code are not to be removed. - * If this package is used in a product, Eric Young should be given attribution - * as the author of the parts of the library used. - * This can be in the form of a textual message at program startup or - * in documentation (online or textual) provided with the package. - * - * 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 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. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * "This product includes cryptographic software written by - * Eric Young (eay@cryptsoft.com)" - * The word 'cryptographic' can be left out if the rouines from the library - * being used are not cryptographic related :-). - * 4. If you include any Windows specific code (or a derivative thereof) from - * the apps directory (application code) you must include an acknowledgement: - * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" - * - * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``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 AUTHOR 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. - * - * The licence and distribution terms for any publically available version or - * derivative of this code cannot be changed. i.e. this code cannot simply be - * copied and put under another distribution licence - * [including the GNU Public Licence.] - */ - -#ifndef HEADER_STACK_H -#define HEADER_STACK_H - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct stack_st - { - int num; - char **data; - int sorted; - - int num_alloc; - int (*comp)(const void *, const void *); - } _STACK; /* Use STACK_OF(...) instead */ - -#define M_sk_num(sk) ((sk) ? (sk)->num:-1) -#define M_sk_value(sk,n) ((sk) ? (sk)->data[n] : NULL) - -int sk_num(const _STACK *); -void *sk_value(const _STACK *, int); - -void *sk_set(_STACK *, int, void *); - -_STACK *sk_new(int (*cmp)(const void *, const void *)); -_STACK *sk_new_null(void); -void sk_free(_STACK *); -void sk_pop_free(_STACK *st, void (*func)(void *)); -int sk_insert(_STACK *sk, void *data, int where); -void *sk_delete(_STACK *st, int loc); -void *sk_delete_ptr(_STACK *st, void *p); -int sk_find(_STACK *st, void *data); -int sk_find_ex(_STACK *st, void *data); -int sk_push(_STACK *st, void *data); -int sk_unshift(_STACK *st, void *data); -void *sk_shift(_STACK *st); -void *sk_pop(_STACK *st); -void sk_zero(_STACK *st); -int (*sk_set_cmp_func(_STACK *sk, int (*c)(const void *, const void *))) - (const void *, const void *); -_STACK *sk_dup(_STACK *st); -void sk_sort(_STACK *st); -int sk_is_sorted(const _STACK *st); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/src/plugins/ftp/openssl/symhacks.h b/src/plugins/ftp/openssl/symhacks.h deleted file mode 100644 index 403f592d..00000000 --- a/src/plugins/ftp/openssl/symhacks.h +++ /dev/null @@ -1,477 +0,0 @@ -/* ==================================================================== - * Copyright (c) 1999 The OpenSSL Project. All rights reserved. - * - * 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. All advertising materials mentioning features or use of this - * software must display the following acknowledgment: - * "This product includes software developed by the OpenSSL Project - * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" - * - * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to - * endorse or promote products derived from this software without - * prior written permission. For written permission, please contact - * openssl-core@openssl.org. - * - * 5. Products derived from this software may not be called "OpenSSL" - * nor may "OpenSSL" appear in their names without prior written - * permission of the OpenSSL Project. - * - * 6. Redistributions of any form whatsoever must retain the following - * acknowledgment: - * "This product includes software developed by the OpenSSL Project - * for use in the OpenSSL Toolkit (http://www.openssl.org/)" - * - * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY - * EXPRESSED 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 OpenSSL PROJECT OR - * ITS 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. - * ==================================================================== - * - * This product includes cryptographic software written by Eric Young - * (eay@cryptsoft.com). This product includes software written by Tim - * Hudson (tjh@cryptsoft.com). - * - */ - -#ifndef HEADER_SYMHACKS_H -#define HEADER_SYMHACKS_H - -#include - -/* Hacks to solve the problem with linkers incapable of handling very long - symbol names. In the case of VMS, the limit is 31 characters on VMS for - VAX. */ -/* Note that this affects util/libeay.num and util/ssleay.num... you may - change those manually, but that's not recommended, as those files are - controlled centrally and updated on Unix, and the central definition - may disagree with yours, which in turn may come with shareable library - incompatibilities. */ -#ifdef OPENSSL_SYS_VMS - -/* Hack a long name in crypto/ex_data.c */ -#undef CRYPTO_get_ex_data_implementation -#define CRYPTO_get_ex_data_implementation CRYPTO_get_ex_data_impl -#undef CRYPTO_set_ex_data_implementation -#define CRYPTO_set_ex_data_implementation CRYPTO_set_ex_data_impl - -/* Hack a long name in crypto/asn1/a_mbstr.c */ -#undef ASN1_STRING_set_default_mask_asc -#define ASN1_STRING_set_default_mask_asc ASN1_STRING_set_def_mask_asc - -#if 0 /* No longer needed, since safestack macro magic does the job */ -/* Hack the names created with DECLARE_ASN1_SET_OF(PKCS7_SIGNER_INFO) */ -#undef i2d_ASN1_SET_OF_PKCS7_SIGNER_INFO -#define i2d_ASN1_SET_OF_PKCS7_SIGNER_INFO i2d_ASN1_SET_OF_PKCS7_SIGINF -#undef d2i_ASN1_SET_OF_PKCS7_SIGNER_INFO -#define d2i_ASN1_SET_OF_PKCS7_SIGNER_INFO d2i_ASN1_SET_OF_PKCS7_SIGINF -#endif - -#if 0 /* No longer needed, since safestack macro magic does the job */ -/* Hack the names created with DECLARE_ASN1_SET_OF(PKCS7_RECIP_INFO) */ -#undef i2d_ASN1_SET_OF_PKCS7_RECIP_INFO -#define i2d_ASN1_SET_OF_PKCS7_RECIP_INFO i2d_ASN1_SET_OF_PKCS7_RECINF -#undef d2i_ASN1_SET_OF_PKCS7_RECIP_INFO -#define d2i_ASN1_SET_OF_PKCS7_RECIP_INFO d2i_ASN1_SET_OF_PKCS7_RECINF -#endif - -#if 0 /* No longer needed, since safestack macro magic does the job */ -/* Hack the names created with DECLARE_ASN1_SET_OF(ACCESS_DESCRIPTION) */ -#undef i2d_ASN1_SET_OF_ACCESS_DESCRIPTION -#define i2d_ASN1_SET_OF_ACCESS_DESCRIPTION i2d_ASN1_SET_OF_ACC_DESC -#undef d2i_ASN1_SET_OF_ACCESS_DESCRIPTION -#define d2i_ASN1_SET_OF_ACCESS_DESCRIPTION d2i_ASN1_SET_OF_ACC_DESC -#endif - -/* Hack the names created with DECLARE_PEM_rw(NETSCAPE_CERT_SEQUENCE) */ -#undef PEM_read_NETSCAPE_CERT_SEQUENCE -#define PEM_read_NETSCAPE_CERT_SEQUENCE PEM_read_NS_CERT_SEQ -#undef PEM_write_NETSCAPE_CERT_SEQUENCE -#define PEM_write_NETSCAPE_CERT_SEQUENCE PEM_write_NS_CERT_SEQ -#undef PEM_read_bio_NETSCAPE_CERT_SEQUENCE -#define PEM_read_bio_NETSCAPE_CERT_SEQUENCE PEM_read_bio_NS_CERT_SEQ -#undef PEM_write_bio_NETSCAPE_CERT_SEQUENCE -#define PEM_write_bio_NETSCAPE_CERT_SEQUENCE PEM_write_bio_NS_CERT_SEQ -#undef PEM_write_cb_bio_NETSCAPE_CERT_SEQUENCE -#define PEM_write_cb_bio_NETSCAPE_CERT_SEQUENCE PEM_write_cb_bio_NS_CERT_SEQ - -/* Hack the names created with DECLARE_PEM_rw(PKCS8_PRIV_KEY_INFO) */ -#undef PEM_read_PKCS8_PRIV_KEY_INFO -#define PEM_read_PKCS8_PRIV_KEY_INFO PEM_read_P8_PRIV_KEY_INFO -#undef PEM_write_PKCS8_PRIV_KEY_INFO -#define PEM_write_PKCS8_PRIV_KEY_INFO PEM_write_P8_PRIV_KEY_INFO -#undef PEM_read_bio_PKCS8_PRIV_KEY_INFO -#define PEM_read_bio_PKCS8_PRIV_KEY_INFO PEM_read_bio_P8_PRIV_KEY_INFO -#undef PEM_write_bio_PKCS8_PRIV_KEY_INFO -#define PEM_write_bio_PKCS8_PRIV_KEY_INFO PEM_write_bio_P8_PRIV_KEY_INFO -#undef PEM_write_cb_bio_PKCS8_PRIV_KEY_INFO -#define PEM_write_cb_bio_PKCS8_PRIV_KEY_INFO PEM_wrt_cb_bio_P8_PRIV_KEY_INFO - -/* Hack other PEM names */ -#undef PEM_write_bio_PKCS8PrivateKey_nid -#define PEM_write_bio_PKCS8PrivateKey_nid PEM_write_bio_PKCS8PrivKey_nid - -/* Hack some long X509 names */ -#undef X509_REVOKED_get_ext_by_critical -#define X509_REVOKED_get_ext_by_critical X509_REVOKED_get_ext_by_critic -#undef X509_policy_tree_get0_user_policies -#define X509_policy_tree_get0_user_policies X509_pcy_tree_get0_usr_policies -#undef X509_policy_node_get0_qualifiers -#define X509_policy_node_get0_qualifiers X509_pcy_node_get0_qualifiers -#undef X509_STORE_CTX_get_explicit_policy -#define X509_STORE_CTX_get_explicit_policy X509_STORE_CTX_get_expl_policy -#undef X509_STORE_CTX_get0_current_issuer -#define X509_STORE_CTX_get0_current_issuer X509_STORE_CTX_get0_cur_issuer - -/* Hack some long CRYPTO names */ -#undef CRYPTO_set_dynlock_destroy_callback -#define CRYPTO_set_dynlock_destroy_callback CRYPTO_set_dynlock_destroy_cb -#undef CRYPTO_set_dynlock_create_callback -#define CRYPTO_set_dynlock_create_callback CRYPTO_set_dynlock_create_cb -#undef CRYPTO_set_dynlock_lock_callback -#define CRYPTO_set_dynlock_lock_callback CRYPTO_set_dynlock_lock_cb -#undef CRYPTO_get_dynlock_lock_callback -#define CRYPTO_get_dynlock_lock_callback CRYPTO_get_dynlock_lock_cb -#undef CRYPTO_get_dynlock_destroy_callback -#define CRYPTO_get_dynlock_destroy_callback CRYPTO_get_dynlock_destroy_cb -#undef CRYPTO_get_dynlock_create_callback -#define CRYPTO_get_dynlock_create_callback CRYPTO_get_dynlock_create_cb -#undef CRYPTO_set_locked_mem_ex_functions -#define CRYPTO_set_locked_mem_ex_functions CRYPTO_set_locked_mem_ex_funcs -#undef CRYPTO_get_locked_mem_ex_functions -#define CRYPTO_get_locked_mem_ex_functions CRYPTO_get_locked_mem_ex_funcs - -/* Hack some long SSL names */ -#undef SSL_CTX_set_default_verify_paths -#define SSL_CTX_set_default_verify_paths SSL_CTX_set_def_verify_paths -#undef SSL_get_ex_data_X509_STORE_CTX_idx -#define SSL_get_ex_data_X509_STORE_CTX_idx SSL_get_ex_d_X509_STORE_CTX_idx -#undef SSL_add_file_cert_subjects_to_stack -#define SSL_add_file_cert_subjects_to_stack SSL_add_file_cert_subjs_to_stk -#undef SSL_add_dir_cert_subjects_to_stack -#define SSL_add_dir_cert_subjects_to_stack SSL_add_dir_cert_subjs_to_stk -#undef SSL_CTX_use_certificate_chain_file -#define SSL_CTX_use_certificate_chain_file SSL_CTX_use_cert_chain_file -#undef SSL_CTX_set_cert_verify_callback -#define SSL_CTX_set_cert_verify_callback SSL_CTX_set_cert_verify_cb -#undef SSL_CTX_set_default_passwd_cb_userdata -#define SSL_CTX_set_default_passwd_cb_userdata SSL_CTX_set_def_passwd_cb_ud -#undef SSL_COMP_get_compression_methods -#define SSL_COMP_get_compression_methods SSL_COMP_get_compress_methods -#undef ssl_add_clienthello_renegotiate_ext -#define ssl_add_clienthello_renegotiate_ext ssl_add_clienthello_reneg_ext -#undef ssl_add_serverhello_renegotiate_ext -#define ssl_add_serverhello_renegotiate_ext ssl_add_serverhello_reneg_ext -#undef ssl_parse_clienthello_renegotiate_ext -#define ssl_parse_clienthello_renegotiate_ext ssl_parse_clienthello_reneg_ext -#undef ssl_parse_serverhello_renegotiate_ext -#define ssl_parse_serverhello_renegotiate_ext ssl_parse_serverhello_reneg_ext -#undef SSL_srp_server_param_with_username -#define SSL_srp_server_param_with_username SSL_srp_server_param_with_un -#undef SSL_CTX_set_srp_client_pwd_callback -#define SSL_CTX_set_srp_client_pwd_callback SSL_CTX_set_srp_client_pwd_cb -#undef SSL_CTX_set_srp_verify_param_callback -#define SSL_CTX_set_srp_verify_param_callback SSL_CTX_set_srp_vfy_param_cb -#undef SSL_CTX_set_srp_username_callback -#define SSL_CTX_set_srp_username_callback SSL_CTX_set_srp_un_cb -#undef ssl_add_clienthello_use_srtp_ext -#define ssl_add_clienthello_use_srtp_ext ssl_add_clihello_use_srtp_ext -#undef ssl_add_serverhello_use_srtp_ext -#define ssl_add_serverhello_use_srtp_ext ssl_add_serhello_use_srtp_ext -#undef ssl_parse_clienthello_use_srtp_ext -#define ssl_parse_clienthello_use_srtp_ext ssl_parse_clihello_use_srtp_ext -#undef ssl_parse_serverhello_use_srtp_ext -#define ssl_parse_serverhello_use_srtp_ext ssl_parse_serhello_use_srtp_ext -#undef SSL_CTX_set_next_protos_advertised_cb -#define SSL_CTX_set_next_protos_advertised_cb SSL_CTX_set_next_protos_adv_cb -#undef SSL_CTX_set_next_proto_select_cb -#define SSL_CTX_set_next_proto_select_cb SSL_CTX_set_next_proto_sel_cb - -/* Hack some long ENGINE names */ -#undef ENGINE_get_default_BN_mod_exp_crt -#define ENGINE_get_default_BN_mod_exp_crt ENGINE_get_def_BN_mod_exp_crt -#undef ENGINE_set_default_BN_mod_exp_crt -#define ENGINE_set_default_BN_mod_exp_crt ENGINE_set_def_BN_mod_exp_crt -#undef ENGINE_set_load_privkey_function -#define ENGINE_set_load_privkey_function ENGINE_set_load_privkey_fn -#undef ENGINE_get_load_privkey_function -#define ENGINE_get_load_privkey_function ENGINE_get_load_privkey_fn -#undef ENGINE_unregister_pkey_asn1_meths -#define ENGINE_unregister_pkey_asn1_meths ENGINE_unreg_pkey_asn1_meths -#undef ENGINE_register_all_pkey_asn1_meths -#define ENGINE_register_all_pkey_asn1_meths ENGINE_reg_all_pkey_asn1_meths -#undef ENGINE_set_default_pkey_asn1_meths -#define ENGINE_set_default_pkey_asn1_meths ENGINE_set_def_pkey_asn1_meths -#undef ENGINE_get_pkey_asn1_meth_engine -#define ENGINE_get_pkey_asn1_meth_engine ENGINE_get_pkey_asn1_meth_eng -#undef ENGINE_set_load_ssl_client_cert_function -#define ENGINE_set_load_ssl_client_cert_function \ - ENGINE_set_ld_ssl_clnt_cert_fn -#undef ENGINE_get_ssl_client_cert_function -#define ENGINE_get_ssl_client_cert_function ENGINE_get_ssl_client_cert_fn - -/* Hack some long OCSP names */ -#undef OCSP_REQUEST_get_ext_by_critical -#define OCSP_REQUEST_get_ext_by_critical OCSP_REQUEST_get_ext_by_crit -#undef OCSP_BASICRESP_get_ext_by_critical -#define OCSP_BASICRESP_get_ext_by_critical OCSP_BASICRESP_get_ext_by_crit -#undef OCSP_SINGLERESP_get_ext_by_critical -#define OCSP_SINGLERESP_get_ext_by_critical OCSP_SINGLERESP_get_ext_by_crit - -/* Hack some long DES names */ -#undef _ossl_old_des_ede3_cfb64_encrypt -#define _ossl_old_des_ede3_cfb64_encrypt _ossl_odes_ede3_cfb64_encrypt -#undef _ossl_old_des_ede3_ofb64_encrypt -#define _ossl_old_des_ede3_ofb64_encrypt _ossl_odes_ede3_ofb64_encrypt - -/* Hack some long EVP names */ -#undef OPENSSL_add_all_algorithms_noconf -#define OPENSSL_add_all_algorithms_noconf OPENSSL_add_all_algo_noconf -#undef OPENSSL_add_all_algorithms_conf -#define OPENSSL_add_all_algorithms_conf OPENSSL_add_all_algo_conf -#undef EVP_PKEY_meth_set_verify_recover -#define EVP_PKEY_meth_set_verify_recover EVP_PKEY_meth_set_vrfy_recover - -/* Hack some long EC names */ -#undef EC_GROUP_set_point_conversion_form -#define EC_GROUP_set_point_conversion_form EC_GROUP_set_point_conv_form -#undef EC_GROUP_get_point_conversion_form -#define EC_GROUP_get_point_conversion_form EC_GROUP_get_point_conv_form -#undef EC_GROUP_clear_free_all_extra_data -#define EC_GROUP_clear_free_all_extra_data EC_GROUP_clr_free_all_xtra_data -#undef EC_KEY_set_public_key_affine_coordinates -#define EC_KEY_set_public_key_affine_coordinates \ - EC_KEY_set_pub_key_aff_coords -#undef EC_POINT_set_Jprojective_coordinates_GFp -#define EC_POINT_set_Jprojective_coordinates_GFp \ - EC_POINT_set_Jproj_coords_GFp -#undef EC_POINT_get_Jprojective_coordinates_GFp -#define EC_POINT_get_Jprojective_coordinates_GFp \ - EC_POINT_get_Jproj_coords_GFp -#undef EC_POINT_set_affine_coordinates_GFp -#define EC_POINT_set_affine_coordinates_GFp EC_POINT_set_affine_coords_GFp -#undef EC_POINT_get_affine_coordinates_GFp -#define EC_POINT_get_affine_coordinates_GFp EC_POINT_get_affine_coords_GFp -#undef EC_POINT_set_compressed_coordinates_GFp -#define EC_POINT_set_compressed_coordinates_GFp EC_POINT_set_compr_coords_GFp -#undef EC_POINT_set_affine_coordinates_GF2m -#define EC_POINT_set_affine_coordinates_GF2m EC_POINT_set_affine_coords_GF2m -#undef EC_POINT_get_affine_coordinates_GF2m -#define EC_POINT_get_affine_coordinates_GF2m EC_POINT_get_affine_coords_GF2m -#undef EC_POINT_set_compressed_coordinates_GF2m -#define EC_POINT_set_compressed_coordinates_GF2m \ - EC_POINT_set_compr_coords_GF2m -#undef ec_GF2m_simple_group_clear_finish -#define ec_GF2m_simple_group_clear_finish ec_GF2m_simple_grp_clr_finish -#undef ec_GF2m_simple_group_check_discriminant -#define ec_GF2m_simple_group_check_discriminant ec_GF2m_simple_grp_chk_discrim -#undef ec_GF2m_simple_point_clear_finish -#define ec_GF2m_simple_point_clear_finish ec_GF2m_simple_pt_clr_finish -#undef ec_GF2m_simple_point_set_to_infinity -#define ec_GF2m_simple_point_set_to_infinity ec_GF2m_simple_pt_set_to_inf -#undef ec_GF2m_simple_points_make_affine -#define ec_GF2m_simple_points_make_affine ec_GF2m_simple_pts_make_affine -#undef ec_GF2m_simple_point_set_affine_coordinates -#define ec_GF2m_simple_point_set_affine_coordinates \ - ec_GF2m_smp_pt_set_af_coords -#undef ec_GF2m_simple_point_get_affine_coordinates -#define ec_GF2m_simple_point_get_affine_coordinates \ - ec_GF2m_smp_pt_get_af_coords -#undef ec_GF2m_simple_set_compressed_coordinates -#define ec_GF2m_simple_set_compressed_coordinates \ - ec_GF2m_smp_set_compr_coords -#undef ec_GFp_simple_group_set_curve_GFp -#define ec_GFp_simple_group_set_curve_GFp ec_GFp_simple_grp_set_curve_GFp -#undef ec_GFp_simple_group_get_curve_GFp -#define ec_GFp_simple_group_get_curve_GFp ec_GFp_simple_grp_get_curve_GFp -#undef ec_GFp_simple_group_clear_finish -#define ec_GFp_simple_group_clear_finish ec_GFp_simple_grp_clear_finish -#undef ec_GFp_simple_group_set_generator -#define ec_GFp_simple_group_set_generator ec_GFp_simple_grp_set_generator -#undef ec_GFp_simple_group_get0_generator -#define ec_GFp_simple_group_get0_generator ec_GFp_simple_grp_gt0_generator -#undef ec_GFp_simple_group_get_cofactor -#define ec_GFp_simple_group_get_cofactor ec_GFp_simple_grp_get_cofactor -#undef ec_GFp_simple_point_clear_finish -#define ec_GFp_simple_point_clear_finish ec_GFp_simple_pt_clear_finish -#undef ec_GFp_simple_point_set_to_infinity -#define ec_GFp_simple_point_set_to_infinity ec_GFp_simple_pt_set_to_inf -#undef ec_GFp_simple_points_make_affine -#define ec_GFp_simple_points_make_affine ec_GFp_simple_pts_make_affine -#undef ec_GFp_simple_group_get_curve_GFp -#define ec_GFp_simple_group_get_curve_GFp ec_GFp_simple_grp_get_curve_GFp -#undef ec_GFp_simple_set_Jprojective_coordinates_GFp -#define ec_GFp_simple_set_Jprojective_coordinates_GFp \ - ec_GFp_smp_set_Jproj_coords_GFp -#undef ec_GFp_simple_get_Jprojective_coordinates_GFp -#define ec_GFp_simple_get_Jprojective_coordinates_GFp \ - ec_GFp_smp_get_Jproj_coords_GFp -#undef ec_GFp_simple_point_set_affine_coordinates_GFp -#define ec_GFp_simple_point_set_affine_coordinates_GFp \ - ec_GFp_smp_pt_set_af_coords_GFp -#undef ec_GFp_simple_point_get_affine_coordinates_GFp -#define ec_GFp_simple_point_get_affine_coordinates_GFp \ - ec_GFp_smp_pt_get_af_coords_GFp -#undef ec_GFp_simple_set_compressed_coordinates_GFp -#define ec_GFp_simple_set_compressed_coordinates_GFp \ - ec_GFp_smp_set_compr_coords_GFp -#undef ec_GFp_simple_point_set_affine_coordinates -#define ec_GFp_simple_point_set_affine_coordinates \ - ec_GFp_smp_pt_set_af_coords -#undef ec_GFp_simple_point_get_affine_coordinates -#define ec_GFp_simple_point_get_affine_coordinates \ - ec_GFp_smp_pt_get_af_coords -#undef ec_GFp_simple_set_compressed_coordinates -#define ec_GFp_simple_set_compressed_coordinates \ - ec_GFp_smp_set_compr_coords -#undef ec_GFp_simple_group_check_discriminant -#define ec_GFp_simple_group_check_discriminant ec_GFp_simple_grp_chk_discrim - -/* Hack som long STORE names */ -#undef STORE_method_set_initialise_function -#define STORE_method_set_initialise_function STORE_meth_set_initialise_fn -#undef STORE_method_set_cleanup_function -#define STORE_method_set_cleanup_function STORE_meth_set_cleanup_fn -#undef STORE_method_set_generate_function -#define STORE_method_set_generate_function STORE_meth_set_generate_fn -#undef STORE_method_set_modify_function -#define STORE_method_set_modify_function STORE_meth_set_modify_fn -#undef STORE_method_set_revoke_function -#define STORE_method_set_revoke_function STORE_meth_set_revoke_fn -#undef STORE_method_set_delete_function -#define STORE_method_set_delete_function STORE_meth_set_delete_fn -#undef STORE_method_set_list_start_function -#define STORE_method_set_list_start_function STORE_meth_set_list_start_fn -#undef STORE_method_set_list_next_function -#define STORE_method_set_list_next_function STORE_meth_set_list_next_fn -#undef STORE_method_set_list_end_function -#define STORE_method_set_list_end_function STORE_meth_set_list_end_fn -#undef STORE_method_set_update_store_function -#define STORE_method_set_update_store_function STORE_meth_set_update_store_fn -#undef STORE_method_set_lock_store_function -#define STORE_method_set_lock_store_function STORE_meth_set_lock_store_fn -#undef STORE_method_set_unlock_store_function -#define STORE_method_set_unlock_store_function STORE_meth_set_unlock_store_fn -#undef STORE_method_get_initialise_function -#define STORE_method_get_initialise_function STORE_meth_get_initialise_fn -#undef STORE_method_get_cleanup_function -#define STORE_method_get_cleanup_function STORE_meth_get_cleanup_fn -#undef STORE_method_get_generate_function -#define STORE_method_get_generate_function STORE_meth_get_generate_fn -#undef STORE_method_get_modify_function -#define STORE_method_get_modify_function STORE_meth_get_modify_fn -#undef STORE_method_get_revoke_function -#define STORE_method_get_revoke_function STORE_meth_get_revoke_fn -#undef STORE_method_get_delete_function -#define STORE_method_get_delete_function STORE_meth_get_delete_fn -#undef STORE_method_get_list_start_function -#define STORE_method_get_list_start_function STORE_meth_get_list_start_fn -#undef STORE_method_get_list_next_function -#define STORE_method_get_list_next_function STORE_meth_get_list_next_fn -#undef STORE_method_get_list_end_function -#define STORE_method_get_list_end_function STORE_meth_get_list_end_fn -#undef STORE_method_get_update_store_function -#define STORE_method_get_update_store_function STORE_meth_get_update_store_fn -#undef STORE_method_get_lock_store_function -#define STORE_method_get_lock_store_function STORE_meth_get_lock_store_fn -#undef STORE_method_get_unlock_store_function -#define STORE_method_get_unlock_store_function STORE_meth_get_unlock_store_fn - -/* Hack some long TS names */ -#undef TS_RESP_CTX_set_status_info_cond -#define TS_RESP_CTX_set_status_info_cond TS_RESP_CTX_set_stat_info_cond -#undef TS_RESP_CTX_set_clock_precision_digits -#define TS_RESP_CTX_set_clock_precision_digits TS_RESP_CTX_set_clk_prec_digits -#undef TS_CONF_set_clock_precision_digits -#define TS_CONF_set_clock_precision_digits TS_CONF_set_clk_prec_digits - -/* Hack some long CMS names */ -#undef CMS_RecipientInfo_ktri_get0_algs -#define CMS_RecipientInfo_ktri_get0_algs CMS_RecipInfo_ktri_get0_algs -#undef CMS_RecipientInfo_ktri_get0_signer_id -#define CMS_RecipientInfo_ktri_get0_signer_id CMS_RecipInfo_ktri_get0_sigr_id -#undef CMS_OtherRevocationInfoFormat_it -#define CMS_OtherRevocationInfoFormat_it CMS_OtherRevocInfoFormat_it -#undef CMS_KeyAgreeRecipientIdentifier_it -#define CMS_KeyAgreeRecipientIdentifier_it CMS_KeyAgreeRecipIdentifier_it -#undef CMS_OriginatorIdentifierOrKey_it -#define CMS_OriginatorIdentifierOrKey_it CMS_OriginatorIdOrKey_it -#undef cms_SignerIdentifier_get0_signer_id -#define cms_SignerIdentifier_get0_signer_id cms_SignerId_get0_signer_id - -/* Hack some long DTLS1 names */ -#undef dtls1_retransmit_buffered_messages -#define dtls1_retransmit_buffered_messages dtls1_retransmit_buffered_msgs - -/* Hack some long SRP names */ -#undef SRP_generate_server_master_secret -#define SRP_generate_server_master_secret SRP_gen_server_master_secret -#undef SRP_generate_client_master_secret -#define SRP_generate_client_master_secret SRP_gen_client_master_secret - -/* Hack some long UI names */ -#undef UI_method_get_prompt_constructor -#define UI_method_get_prompt_constructor UI_method_get_prompt_constructr -#undef UI_method_set_prompt_constructor -#define UI_method_set_prompt_constructor UI_method_set_prompt_constructr - -#endif /* defined OPENSSL_SYS_VMS */ - - -/* Case insensitive linking causes problems.... */ -#if defined(OPENSSL_SYS_VMS) || defined(OPENSSL_SYS_OS2) -#undef ERR_load_CRYPTO_strings -#define ERR_load_CRYPTO_strings ERR_load_CRYPTOlib_strings -#undef OCSP_crlID_new -#define OCSP_crlID_new OCSP_crlID2_new - -#undef d2i_ECPARAMETERS -#define d2i_ECPARAMETERS d2i_UC_ECPARAMETERS -#undef i2d_ECPARAMETERS -#define i2d_ECPARAMETERS i2d_UC_ECPARAMETERS -#undef d2i_ECPKPARAMETERS -#define d2i_ECPKPARAMETERS d2i_UC_ECPKPARAMETERS -#undef i2d_ECPKPARAMETERS -#define i2d_ECPKPARAMETERS i2d_UC_ECPKPARAMETERS - -/* These functions do not seem to exist! However, I'm paranoid... - Original command in x509v3.h: - These functions are being redefined in another directory, - and clash when the linker is case-insensitive, so let's - hide them a little, by giving them an extra 'o' at the - beginning of the name... */ -#undef X509v3_cleanup_extensions -#define X509v3_cleanup_extensions oX509v3_cleanup_extensions -#undef X509v3_add_extension -#define X509v3_add_extension oX509v3_add_extension -#undef X509v3_add_netscape_extensions -#define X509v3_add_netscape_extensions oX509v3_add_netscape_extensions -#undef X509v3_add_standard_extensions -#define X509v3_add_standard_extensions oX509v3_add_standard_extensions - -/* This one clashes with CMS_data_create */ -#undef cms_Data_create -#define cms_Data_create priv_cms_Data_create - -#endif - - -#endif /* ! defined HEADER_VMS_IDHACKS_H */ diff --git a/src/plugins/ftp/openssl/x509.h b/src/plugins/ftp/openssl/x509.h deleted file mode 100644 index 092dd745..00000000 --- a/src/plugins/ftp/openssl/x509.h +++ /dev/null @@ -1,1297 +0,0 @@ -/* crypto/x509/x509.h */ -/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) - * All rights reserved. - * - * This package is an SSL implementation written - * by Eric Young (eay@cryptsoft.com). - * The implementation was written so as to conform with Netscapes SSL. - * - * This library is free for commercial and non-commercial use as long as - * the following conditions are aheared to. The following conditions - * apply to all code found in this distribution, be it the RC4, RSA, - * lhash, DES, etc., code; not just the SSL code. The SSL documentation - * included with this distribution is covered by the same copyright terms - * except that the holder is Tim Hudson (tjh@cryptsoft.com). - * - * Copyright remains Eric Young's, and as such any Copyright notices in - * the code are not to be removed. - * If this package is used in a product, Eric Young should be given attribution - * as the author of the parts of the library used. - * This can be in the form of a textual message at program startup or - * in documentation (online or textual) provided with the package. - * - * 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 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. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * "This product includes cryptographic software written by - * Eric Young (eay@cryptsoft.com)" - * The word 'cryptographic' can be left out if the rouines from the library - * being used are not cryptographic related :-). - * 4. If you include any Windows specific code (or a derivative thereof) from - * the apps directory (application code) you must include an acknowledgement: - * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" - * - * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``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 AUTHOR 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. - * - * The licence and distribution terms for any publically available version or - * derivative of this code cannot be changed. i.e. this code cannot simply be - * copied and put under another distribution licence - * [including the GNU Public Licence.] - */ -/* ==================================================================== - * Copyright 2002 Sun Microsystems, Inc. ALL RIGHTS RESERVED. - * ECDH support in OpenSSL originally developed by - * SUN MICROSYSTEMS, INC., and contributed to the OpenSSL project. - */ - -#ifndef HEADER_X509_H -#define HEADER_X509_H - -#include -#include -#ifndef OPENSSL_NO_BUFFER -#include -#endif -#ifndef OPENSSL_NO_EVP -#include -#endif -#ifndef OPENSSL_NO_BIO -#include -#endif -#include -#include -#include - -#ifndef OPENSSL_NO_EC -#include -#endif - -#ifndef OPENSSL_NO_ECDSA -#include -#endif - -#ifndef OPENSSL_NO_ECDH -#include -#endif - -#ifndef OPENSSL_NO_DEPRECATED -#ifndef OPENSSL_NO_RSA -#include -#endif -#ifndef OPENSSL_NO_DSA -#include -#endif -#ifndef OPENSSL_NO_DH -#include -#endif -#endif - -#ifndef OPENSSL_NO_SHA -#include -#endif -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef OPENSSL_SYS_WIN32 -/* Under Win32 these are defined in wincrypt.h */ -#undef X509_NAME -#undef X509_CERT_PAIR -#undef X509_EXTENSIONS -#endif - -#define X509_FILETYPE_PEM 1 -#define X509_FILETYPE_ASN1 2 -#define X509_FILETYPE_DEFAULT 3 - -#define X509v3_KU_DIGITAL_SIGNATURE 0x0080 -#define X509v3_KU_NON_REPUDIATION 0x0040 -#define X509v3_KU_KEY_ENCIPHERMENT 0x0020 -#define X509v3_KU_DATA_ENCIPHERMENT 0x0010 -#define X509v3_KU_KEY_AGREEMENT 0x0008 -#define X509v3_KU_KEY_CERT_SIGN 0x0004 -#define X509v3_KU_CRL_SIGN 0x0002 -#define X509v3_KU_ENCIPHER_ONLY 0x0001 -#define X509v3_KU_DECIPHER_ONLY 0x8000 -#define X509v3_KU_UNDEF 0xffff - -typedef struct X509_objects_st - { - int nid; - int (*a2i)(void); - int (*i2a)(void); - } X509_OBJECTS; - -struct X509_algor_st - { - ASN1_OBJECT *algorithm; - ASN1_TYPE *parameter; - } /* X509_ALGOR */; - -DECLARE_ASN1_SET_OF(X509_ALGOR) - -typedef STACK_OF(X509_ALGOR) X509_ALGORS; - -typedef struct X509_val_st - { - ASN1_TIME *notBefore; - ASN1_TIME *notAfter; - } X509_VAL; - -struct X509_pubkey_st - { - X509_ALGOR *algor; - ASN1_BIT_STRING *public_key; - EVP_PKEY *pkey; - }; - -typedef struct X509_sig_st - { - X509_ALGOR *algor; - ASN1_OCTET_STRING *digest; - } X509_SIG; - -typedef struct X509_name_entry_st - { - ASN1_OBJECT *object; - ASN1_STRING *value; - int set; - int size; /* temp variable */ - } X509_NAME_ENTRY; - -DECLARE_STACK_OF(X509_NAME_ENTRY) -DECLARE_ASN1_SET_OF(X509_NAME_ENTRY) - -/* we always keep X509_NAMEs in 2 forms. */ -struct X509_name_st - { - STACK_OF(X509_NAME_ENTRY) *entries; - int modified; /* true if 'bytes' needs to be built */ -#ifndef OPENSSL_NO_BUFFER - BUF_MEM *bytes; -#else - char *bytes; -#endif -/* unsigned long hash; Keep the hash around for lookups */ - unsigned char *canon_enc; - int canon_enclen; - } /* X509_NAME */; - -DECLARE_STACK_OF(X509_NAME) - -#define X509_EX_V_NETSCAPE_HACK 0x8000 -#define X509_EX_V_INIT 0x0001 -typedef struct X509_extension_st - { - ASN1_OBJECT *object; - ASN1_BOOLEAN critical; - ASN1_OCTET_STRING *value; - } X509_EXTENSION; - -typedef STACK_OF(X509_EXTENSION) X509_EXTENSIONS; - -DECLARE_STACK_OF(X509_EXTENSION) -DECLARE_ASN1_SET_OF(X509_EXTENSION) - -/* a sequence of these are used */ -typedef struct x509_attributes_st - { - ASN1_OBJECT *object; - int single; /* 0 for a set, 1 for a single item (which is wrong) */ - union { - char *ptr; -/* 0 */ STACK_OF(ASN1_TYPE) *set; -/* 1 */ ASN1_TYPE *single; - } value; - } X509_ATTRIBUTE; - -DECLARE_STACK_OF(X509_ATTRIBUTE) -DECLARE_ASN1_SET_OF(X509_ATTRIBUTE) - - -typedef struct X509_req_info_st - { - ASN1_ENCODING enc; - ASN1_INTEGER *version; - X509_NAME *subject; - X509_PUBKEY *pubkey; - /* d=2 hl=2 l= 0 cons: cont: 00 */ - STACK_OF(X509_ATTRIBUTE) *attributes; /* [ 0 ] */ - } X509_REQ_INFO; - -typedef struct X509_req_st - { - X509_REQ_INFO *req_info; - X509_ALGOR *sig_alg; - ASN1_BIT_STRING *signature; - int references; - } X509_REQ; - -typedef struct x509_cinf_st - { - ASN1_INTEGER *version; /* [ 0 ] default of v1 */ - ASN1_INTEGER *serialNumber; - X509_ALGOR *signature; - X509_NAME *issuer; - X509_VAL *validity; - X509_NAME *subject; - X509_PUBKEY *key; - ASN1_BIT_STRING *issuerUID; /* [ 1 ] optional in v2 */ - ASN1_BIT_STRING *subjectUID; /* [ 2 ] optional in v2 */ - STACK_OF(X509_EXTENSION) *extensions; /* [ 3 ] optional in v3 */ - ASN1_ENCODING enc; - } X509_CINF; - -/* This stuff is certificate "auxiliary info" - * it contains details which are useful in certificate - * stores and databases. When used this is tagged onto - * the end of the certificate itself - */ - -typedef struct x509_cert_aux_st - { - STACK_OF(ASN1_OBJECT) *trust; /* trusted uses */ - STACK_OF(ASN1_OBJECT) *reject; /* rejected uses */ - ASN1_UTF8STRING *alias; /* "friendly name" */ - ASN1_OCTET_STRING *keyid; /* key id of private key */ - STACK_OF(X509_ALGOR) *other; /* other unspecified info */ - } X509_CERT_AUX; - -struct x509_st - { - X509_CINF *cert_info; - X509_ALGOR *sig_alg; - ASN1_BIT_STRING *signature; - int valid; - int references; - char *name; - CRYPTO_EX_DATA ex_data; - /* These contain copies of various extension values */ - long ex_pathlen; - long ex_pcpathlen; - unsigned long ex_flags; - unsigned long ex_kusage; - unsigned long ex_xkusage; - unsigned long ex_nscert; - ASN1_OCTET_STRING *skid; - AUTHORITY_KEYID *akid; - X509_POLICY_CACHE *policy_cache; - STACK_OF(DIST_POINT) *crldp; - STACK_OF(GENERAL_NAME) *altname; - NAME_CONSTRAINTS *nc; -#ifndef OPENSSL_NO_RFC3779 - STACK_OF(IPAddressFamily) *rfc3779_addr; - struct ASIdentifiers_st *rfc3779_asid; -#endif -#ifndef OPENSSL_NO_SHA - unsigned char sha1_hash[SHA_DIGEST_LENGTH]; -#endif - X509_CERT_AUX *aux; - } /* X509 */; - -DECLARE_STACK_OF(X509) -DECLARE_ASN1_SET_OF(X509) - -/* This is used for a table of trust checking functions */ - -typedef struct x509_trust_st { - int trust; - int flags; - int (*check_trust)(struct x509_trust_st *, X509 *, int); - char *name; - int arg1; - void *arg2; -} X509_TRUST; - -DECLARE_STACK_OF(X509_TRUST) - -typedef struct x509_cert_pair_st { - X509 *forward; - X509 *reverse; -} X509_CERT_PAIR; - -/* standard trust ids */ - -#define X509_TRUST_DEFAULT -1 /* Only valid in purpose settings */ - -#define X509_TRUST_COMPAT 1 -#define X509_TRUST_SSL_CLIENT 2 -#define X509_TRUST_SSL_SERVER 3 -#define X509_TRUST_EMAIL 4 -#define X509_TRUST_OBJECT_SIGN 5 -#define X509_TRUST_OCSP_SIGN 6 -#define X509_TRUST_OCSP_REQUEST 7 -#define X509_TRUST_TSA 8 - -/* Keep these up to date! */ -#define X509_TRUST_MIN 1 -#define X509_TRUST_MAX 8 - - -/* trust_flags values */ -#define X509_TRUST_DYNAMIC 1 -#define X509_TRUST_DYNAMIC_NAME 2 - -/* check_trust return codes */ - -#define X509_TRUST_TRUSTED 1 -#define X509_TRUST_REJECTED 2 -#define X509_TRUST_UNTRUSTED 3 - -/* Flags for X509_print_ex() */ - -#define X509_FLAG_COMPAT 0 -#define X509_FLAG_NO_HEADER 1L -#define X509_FLAG_NO_VERSION (1L << 1) -#define X509_FLAG_NO_SERIAL (1L << 2) -#define X509_FLAG_NO_SIGNAME (1L << 3) -#define X509_FLAG_NO_ISSUER (1L << 4) -#define X509_FLAG_NO_VALIDITY (1L << 5) -#define X509_FLAG_NO_SUBJECT (1L << 6) -#define X509_FLAG_NO_PUBKEY (1L << 7) -#define X509_FLAG_NO_EXTENSIONS (1L << 8) -#define X509_FLAG_NO_SIGDUMP (1L << 9) -#define X509_FLAG_NO_AUX (1L << 10) -#define X509_FLAG_NO_ATTRIBUTES (1L << 11) - -/* Flags specific to X509_NAME_print_ex() */ - -/* The field separator information */ - -#define XN_FLAG_SEP_MASK (0xf << 16) - -#define XN_FLAG_COMPAT 0 /* Traditional SSLeay: use old X509_NAME_print */ -#define XN_FLAG_SEP_COMMA_PLUS (1 << 16) /* RFC2253 ,+ */ -#define XN_FLAG_SEP_CPLUS_SPC (2 << 16) /* ,+ spaced: more readable */ -#define XN_FLAG_SEP_SPLUS_SPC (3 << 16) /* ;+ spaced */ -#define XN_FLAG_SEP_MULTILINE (4 << 16) /* One line per field */ - -#define XN_FLAG_DN_REV (1 << 20) /* Reverse DN order */ - -/* How the field name is shown */ - -#define XN_FLAG_FN_MASK (0x3 << 21) - -#define XN_FLAG_FN_SN 0 /* Object short name */ -#define XN_FLAG_FN_LN (1 << 21) /* Object long name */ -#define XN_FLAG_FN_OID (2 << 21) /* Always use OIDs */ -#define XN_FLAG_FN_NONE (3 << 21) /* No field names */ - -#define XN_FLAG_SPC_EQ (1 << 23) /* Put spaces round '=' */ - -/* This determines if we dump fields we don't recognise: - * RFC2253 requires this. - */ - -#define XN_FLAG_DUMP_UNKNOWN_FIELDS (1 << 24) - -#define XN_FLAG_FN_ALIGN (1 << 25) /* Align field names to 20 characters */ - -/* Complete set of RFC2253 flags */ - -#define XN_FLAG_RFC2253 (ASN1_STRFLGS_RFC2253 | \ - XN_FLAG_SEP_COMMA_PLUS | \ - XN_FLAG_DN_REV | \ - XN_FLAG_FN_SN | \ - XN_FLAG_DUMP_UNKNOWN_FIELDS) - -/* readable oneline form */ - -#define XN_FLAG_ONELINE (ASN1_STRFLGS_RFC2253 | \ - ASN1_STRFLGS_ESC_QUOTE | \ - XN_FLAG_SEP_CPLUS_SPC | \ - XN_FLAG_SPC_EQ | \ - XN_FLAG_FN_SN) - -/* readable multiline form */ - -#define XN_FLAG_MULTILINE (ASN1_STRFLGS_ESC_CTRL | \ - ASN1_STRFLGS_ESC_MSB | \ - XN_FLAG_SEP_MULTILINE | \ - XN_FLAG_SPC_EQ | \ - XN_FLAG_FN_LN | \ - XN_FLAG_FN_ALIGN) - -struct x509_revoked_st - { - ASN1_INTEGER *serialNumber; - ASN1_TIME *revocationDate; - STACK_OF(X509_EXTENSION) /* optional */ *extensions; - /* Set up if indirect CRL */ - STACK_OF(GENERAL_NAME) *issuer; - /* Revocation reason */ - int reason; - int sequence; /* load sequence */ - }; - -DECLARE_STACK_OF(X509_REVOKED) -DECLARE_ASN1_SET_OF(X509_REVOKED) - -typedef struct X509_crl_info_st - { - ASN1_INTEGER *version; - X509_ALGOR *sig_alg; - X509_NAME *issuer; - ASN1_TIME *lastUpdate; - ASN1_TIME *nextUpdate; - STACK_OF(X509_REVOKED) *revoked; - STACK_OF(X509_EXTENSION) /* [0] */ *extensions; - ASN1_ENCODING enc; - } X509_CRL_INFO; - -struct X509_crl_st - { - /* actual signature */ - X509_CRL_INFO *crl; - X509_ALGOR *sig_alg; - ASN1_BIT_STRING *signature; - int references; - int flags; - /* Copies of various extensions */ - AUTHORITY_KEYID *akid; - ISSUING_DIST_POINT *idp; - /* Convenient breakdown of IDP */ - int idp_flags; - int idp_reasons; - /* CRL and base CRL numbers for delta processing */ - ASN1_INTEGER *crl_number; - ASN1_INTEGER *base_crl_number; -#ifndef OPENSSL_NO_SHA - unsigned char sha1_hash[SHA_DIGEST_LENGTH]; -#endif - STACK_OF(GENERAL_NAMES) *issuers; - const X509_CRL_METHOD *meth; - void *meth_data; - } /* X509_CRL */; - -DECLARE_STACK_OF(X509_CRL) -DECLARE_ASN1_SET_OF(X509_CRL) - -typedef struct private_key_st - { - int version; - /* The PKCS#8 data types */ - X509_ALGOR *enc_algor; - ASN1_OCTET_STRING *enc_pkey; /* encrypted pub key */ - - /* When decrypted, the following will not be NULL */ - EVP_PKEY *dec_pkey; - - /* used to encrypt and decrypt */ - int key_length; - char *key_data; - int key_free; /* true if we should auto free key_data */ - - /* expanded version of 'enc_algor' */ - EVP_CIPHER_INFO cipher; - - int references; - } X509_PKEY; - -#ifndef OPENSSL_NO_EVP -typedef struct X509_info_st - { - X509 *x509; - X509_CRL *crl; - X509_PKEY *x_pkey; - - EVP_CIPHER_INFO enc_cipher; - int enc_len; - char *enc_data; - - int references; - } X509_INFO; - -DECLARE_STACK_OF(X509_INFO) -#endif - -/* The next 2 structures and their 8 routines were sent to me by - * Pat Richard and are used to manipulate - * Netscapes spki structures - useful if you are writing a CA web page - */ -typedef struct Netscape_spkac_st - { - X509_PUBKEY *pubkey; - ASN1_IA5STRING *challenge; /* challenge sent in atlas >= PR2 */ - } NETSCAPE_SPKAC; - -typedef struct Netscape_spki_st - { - NETSCAPE_SPKAC *spkac; /* signed public key and challenge */ - X509_ALGOR *sig_algor; - ASN1_BIT_STRING *signature; - } NETSCAPE_SPKI; - -/* Netscape certificate sequence structure */ -typedef struct Netscape_certificate_sequence - { - ASN1_OBJECT *type; - STACK_OF(X509) *certs; - } NETSCAPE_CERT_SEQUENCE; - -/* Unused (and iv length is wrong) -typedef struct CBCParameter_st - { - unsigned char iv[8]; - } CBC_PARAM; -*/ - -/* Password based encryption structure */ - -typedef struct PBEPARAM_st { -ASN1_OCTET_STRING *salt; -ASN1_INTEGER *iter; -} PBEPARAM; - -/* Password based encryption V2 structures */ - -typedef struct PBE2PARAM_st { -X509_ALGOR *keyfunc; -X509_ALGOR *encryption; -} PBE2PARAM; - -typedef struct PBKDF2PARAM_st { -ASN1_TYPE *salt; /* Usually OCTET STRING but could be anything */ -ASN1_INTEGER *iter; -ASN1_INTEGER *keylength; -X509_ALGOR *prf; -} PBKDF2PARAM; - - -/* PKCS#8 private key info structure */ - -struct pkcs8_priv_key_info_st - { - int broken; /* Flag for various broken formats */ -#define PKCS8_OK 0 -#define PKCS8_NO_OCTET 1 -#define PKCS8_EMBEDDED_PARAM 2 -#define PKCS8_NS_DB 3 -#define PKCS8_NEG_PRIVKEY 4 - ASN1_INTEGER *version; - X509_ALGOR *pkeyalg; - ASN1_TYPE *pkey; /* Should be OCTET STRING but some are broken */ - STACK_OF(X509_ATTRIBUTE) *attributes; - }; - -#ifdef __cplusplus -} -#endif - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#define X509_EXT_PACK_UNKNOWN 1 -#define X509_EXT_PACK_STRING 2 - -#define X509_get_version(x) ASN1_INTEGER_get((x)->cert_info->version) -/* #define X509_get_serialNumber(x) ((x)->cert_info->serialNumber) */ -#define X509_get_notBefore(x) ((x)->cert_info->validity->notBefore) -#define X509_get_notAfter(x) ((x)->cert_info->validity->notAfter) -#define X509_extract_key(x) X509_get_pubkey(x) /*****/ -#define X509_REQ_get_version(x) ASN1_INTEGER_get((x)->req_info->version) -#define X509_REQ_get_subject_name(x) ((x)->req_info->subject) -#define X509_REQ_extract_key(a) X509_REQ_get_pubkey(a) -#define X509_name_cmp(a,b) X509_NAME_cmp((a),(b)) -#define X509_get_signature_type(x) EVP_PKEY_type(OBJ_obj2nid((x)->sig_alg->algorithm)) - -#define X509_CRL_get_version(x) ASN1_INTEGER_get((x)->crl->version) -#define X509_CRL_get_lastUpdate(x) ((x)->crl->lastUpdate) -#define X509_CRL_get_nextUpdate(x) ((x)->crl->nextUpdate) -#define X509_CRL_get_issuer(x) ((x)->crl->issuer) -#define X509_CRL_get_REVOKED(x) ((x)->crl->revoked) - -void X509_CRL_set_default_method(const X509_CRL_METHOD *meth); -X509_CRL_METHOD *X509_CRL_METHOD_new( - int (*crl_init)(X509_CRL *crl), - int (*crl_free)(X509_CRL *crl), - int (*crl_lookup)(X509_CRL *crl, X509_REVOKED **ret, - ASN1_INTEGER *ser, X509_NAME *issuer), - int (*crl_verify)(X509_CRL *crl, EVP_PKEY *pk)); -void X509_CRL_METHOD_free(X509_CRL_METHOD *m); - -void X509_CRL_set_meth_data(X509_CRL *crl, void *dat); -void *X509_CRL_get_meth_data(X509_CRL *crl); - -/* This one is only used so that a binary form can output, as in - * i2d_X509_NAME(X509_get_X509_PUBKEY(x),&buf) */ -#define X509_get_X509_PUBKEY(x) ((x)->cert_info->key) - - -const char *X509_verify_cert_error_string(long n); - -#ifndef OPENSSL_NO_EVP -int X509_verify(X509 *a, EVP_PKEY *r); - -int X509_REQ_verify(X509_REQ *a, EVP_PKEY *r); -int X509_CRL_verify(X509_CRL *a, EVP_PKEY *r); -int NETSCAPE_SPKI_verify(NETSCAPE_SPKI *a, EVP_PKEY *r); - -NETSCAPE_SPKI * NETSCAPE_SPKI_b64_decode(const char *str, int len); -char * NETSCAPE_SPKI_b64_encode(NETSCAPE_SPKI *x); -EVP_PKEY *NETSCAPE_SPKI_get_pubkey(NETSCAPE_SPKI *x); -int NETSCAPE_SPKI_set_pubkey(NETSCAPE_SPKI *x, EVP_PKEY *pkey); - -int NETSCAPE_SPKI_print(BIO *out, NETSCAPE_SPKI *spki); - -int X509_signature_dump(BIO *bp,const ASN1_STRING *sig, int indent); -int X509_signature_print(BIO *bp,X509_ALGOR *alg, ASN1_STRING *sig); - -int X509_sign(X509 *x, EVP_PKEY *pkey, const EVP_MD *md); -int X509_sign_ctx(X509 *x, EVP_MD_CTX *ctx); -int X509_REQ_sign(X509_REQ *x, EVP_PKEY *pkey, const EVP_MD *md); -int X509_REQ_sign_ctx(X509_REQ *x, EVP_MD_CTX *ctx); -int X509_CRL_sign(X509_CRL *x, EVP_PKEY *pkey, const EVP_MD *md); -int X509_CRL_sign_ctx(X509_CRL *x, EVP_MD_CTX *ctx); -int NETSCAPE_SPKI_sign(NETSCAPE_SPKI *x, EVP_PKEY *pkey, const EVP_MD *md); - -int X509_pubkey_digest(const X509 *data,const EVP_MD *type, - unsigned char *md, unsigned int *len); -int X509_digest(const X509 *data,const EVP_MD *type, - unsigned char *md, unsigned int *len); -int X509_CRL_digest(const X509_CRL *data,const EVP_MD *type, - unsigned char *md, unsigned int *len); -int X509_REQ_digest(const X509_REQ *data,const EVP_MD *type, - unsigned char *md, unsigned int *len); -int X509_NAME_digest(const X509_NAME *data,const EVP_MD *type, - unsigned char *md, unsigned int *len); -#endif - -#ifndef OPENSSL_NO_FP_API -X509 *d2i_X509_fp(FILE *fp, X509 **x509); -int i2d_X509_fp(FILE *fp,X509 *x509); -X509_CRL *d2i_X509_CRL_fp(FILE *fp,X509_CRL **crl); -int i2d_X509_CRL_fp(FILE *fp,X509_CRL *crl); -X509_REQ *d2i_X509_REQ_fp(FILE *fp,X509_REQ **req); -int i2d_X509_REQ_fp(FILE *fp,X509_REQ *req); -#ifndef OPENSSL_NO_RSA -RSA *d2i_RSAPrivateKey_fp(FILE *fp,RSA **rsa); -int i2d_RSAPrivateKey_fp(FILE *fp,RSA *rsa); -RSA *d2i_RSAPublicKey_fp(FILE *fp,RSA **rsa); -int i2d_RSAPublicKey_fp(FILE *fp,RSA *rsa); -RSA *d2i_RSA_PUBKEY_fp(FILE *fp,RSA **rsa); -int i2d_RSA_PUBKEY_fp(FILE *fp,RSA *rsa); -#endif -#ifndef OPENSSL_NO_DSA -DSA *d2i_DSA_PUBKEY_fp(FILE *fp, DSA **dsa); -int i2d_DSA_PUBKEY_fp(FILE *fp, DSA *dsa); -DSA *d2i_DSAPrivateKey_fp(FILE *fp, DSA **dsa); -int i2d_DSAPrivateKey_fp(FILE *fp, DSA *dsa); -#endif -#ifndef OPENSSL_NO_EC -EC_KEY *d2i_EC_PUBKEY_fp(FILE *fp, EC_KEY **eckey); -int i2d_EC_PUBKEY_fp(FILE *fp, EC_KEY *eckey); -EC_KEY *d2i_ECPrivateKey_fp(FILE *fp, EC_KEY **eckey); -int i2d_ECPrivateKey_fp(FILE *fp, EC_KEY *eckey); -#endif -X509_SIG *d2i_PKCS8_fp(FILE *fp,X509_SIG **p8); -int i2d_PKCS8_fp(FILE *fp,X509_SIG *p8); -PKCS8_PRIV_KEY_INFO *d2i_PKCS8_PRIV_KEY_INFO_fp(FILE *fp, - PKCS8_PRIV_KEY_INFO **p8inf); -int i2d_PKCS8_PRIV_KEY_INFO_fp(FILE *fp,PKCS8_PRIV_KEY_INFO *p8inf); -int i2d_PKCS8PrivateKeyInfo_fp(FILE *fp, EVP_PKEY *key); -int i2d_PrivateKey_fp(FILE *fp, EVP_PKEY *pkey); -EVP_PKEY *d2i_PrivateKey_fp(FILE *fp, EVP_PKEY **a); -int i2d_PUBKEY_fp(FILE *fp, EVP_PKEY *pkey); -EVP_PKEY *d2i_PUBKEY_fp(FILE *fp, EVP_PKEY **a); -#endif - -#ifndef OPENSSL_NO_BIO -X509 *d2i_X509_bio(BIO *bp,X509 **x509); -int i2d_X509_bio(BIO *bp,X509 *x509); -X509_CRL *d2i_X509_CRL_bio(BIO *bp,X509_CRL **crl); -int i2d_X509_CRL_bio(BIO *bp,X509_CRL *crl); -X509_REQ *d2i_X509_REQ_bio(BIO *bp,X509_REQ **req); -int i2d_X509_REQ_bio(BIO *bp,X509_REQ *req); -#ifndef OPENSSL_NO_RSA -RSA *d2i_RSAPrivateKey_bio(BIO *bp,RSA **rsa); -int i2d_RSAPrivateKey_bio(BIO *bp,RSA *rsa); -RSA *d2i_RSAPublicKey_bio(BIO *bp,RSA **rsa); -int i2d_RSAPublicKey_bio(BIO *bp,RSA *rsa); -RSA *d2i_RSA_PUBKEY_bio(BIO *bp,RSA **rsa); -int i2d_RSA_PUBKEY_bio(BIO *bp,RSA *rsa); -#endif -#ifndef OPENSSL_NO_DSA -DSA *d2i_DSA_PUBKEY_bio(BIO *bp, DSA **dsa); -int i2d_DSA_PUBKEY_bio(BIO *bp, DSA *dsa); -DSA *d2i_DSAPrivateKey_bio(BIO *bp, DSA **dsa); -int i2d_DSAPrivateKey_bio(BIO *bp, DSA *dsa); -#endif -#ifndef OPENSSL_NO_EC -EC_KEY *d2i_EC_PUBKEY_bio(BIO *bp, EC_KEY **eckey); -int i2d_EC_PUBKEY_bio(BIO *bp, EC_KEY *eckey); -EC_KEY *d2i_ECPrivateKey_bio(BIO *bp, EC_KEY **eckey); -int i2d_ECPrivateKey_bio(BIO *bp, EC_KEY *eckey); -#endif -X509_SIG *d2i_PKCS8_bio(BIO *bp,X509_SIG **p8); -int i2d_PKCS8_bio(BIO *bp,X509_SIG *p8); -PKCS8_PRIV_KEY_INFO *d2i_PKCS8_PRIV_KEY_INFO_bio(BIO *bp, - PKCS8_PRIV_KEY_INFO **p8inf); -int i2d_PKCS8_PRIV_KEY_INFO_bio(BIO *bp,PKCS8_PRIV_KEY_INFO *p8inf); -int i2d_PKCS8PrivateKeyInfo_bio(BIO *bp, EVP_PKEY *key); -int i2d_PrivateKey_bio(BIO *bp, EVP_PKEY *pkey); -EVP_PKEY *d2i_PrivateKey_bio(BIO *bp, EVP_PKEY **a); -int i2d_PUBKEY_bio(BIO *bp, EVP_PKEY *pkey); -EVP_PKEY *d2i_PUBKEY_bio(BIO *bp, EVP_PKEY **a); -#endif - -X509 *X509_dup(X509 *x509); -X509_ATTRIBUTE *X509_ATTRIBUTE_dup(X509_ATTRIBUTE *xa); -X509_EXTENSION *X509_EXTENSION_dup(X509_EXTENSION *ex); -X509_CRL *X509_CRL_dup(X509_CRL *crl); -X509_REQ *X509_REQ_dup(X509_REQ *req); -X509_ALGOR *X509_ALGOR_dup(X509_ALGOR *xn); -int X509_ALGOR_set0(X509_ALGOR *alg, ASN1_OBJECT *aobj, int ptype, void *pval); -void X509_ALGOR_get0(ASN1_OBJECT **paobj, int *pptype, void **ppval, - X509_ALGOR *algor); -void X509_ALGOR_set_md(X509_ALGOR *alg, const EVP_MD *md); - -X509_NAME *X509_NAME_dup(X509_NAME *xn); -X509_NAME_ENTRY *X509_NAME_ENTRY_dup(X509_NAME_ENTRY *ne); - -int X509_cmp_time(const ASN1_TIME *s, time_t *t); -int X509_cmp_current_time(const ASN1_TIME *s); -ASN1_TIME * X509_time_adj(ASN1_TIME *s, long adj, time_t *t); -ASN1_TIME * X509_time_adj_ex(ASN1_TIME *s, - int offset_day, long offset_sec, time_t *t); -ASN1_TIME * X509_gmtime_adj(ASN1_TIME *s, long adj); - -const char * X509_get_default_cert_area(void ); -const char * X509_get_default_cert_dir(void ); -const char * X509_get_default_cert_file(void ); -const char * X509_get_default_cert_dir_env(void ); -const char * X509_get_default_cert_file_env(void ); -const char * X509_get_default_private_dir(void ); - -X509_REQ * X509_to_X509_REQ(X509 *x, EVP_PKEY *pkey, const EVP_MD *md); -X509 * X509_REQ_to_X509(X509_REQ *r, int days,EVP_PKEY *pkey); - -DECLARE_ASN1_FUNCTIONS(X509_ALGOR) -DECLARE_ASN1_ENCODE_FUNCTIONS(X509_ALGORS, X509_ALGORS, X509_ALGORS) -DECLARE_ASN1_FUNCTIONS(X509_VAL) - -DECLARE_ASN1_FUNCTIONS(X509_PUBKEY) - -int X509_PUBKEY_set(X509_PUBKEY **x, EVP_PKEY *pkey); -EVP_PKEY * X509_PUBKEY_get(X509_PUBKEY *key); -int X509_get_pubkey_parameters(EVP_PKEY *pkey, - STACK_OF(X509) *chain); -int i2d_PUBKEY(EVP_PKEY *a,unsigned char **pp); -EVP_PKEY * d2i_PUBKEY(EVP_PKEY **a,const unsigned char **pp, - long length); -#ifndef OPENSSL_NO_RSA -int i2d_RSA_PUBKEY(RSA *a,unsigned char **pp); -RSA * d2i_RSA_PUBKEY(RSA **a,const unsigned char **pp, - long length); -#endif -#ifndef OPENSSL_NO_DSA -int i2d_DSA_PUBKEY(DSA *a,unsigned char **pp); -DSA * d2i_DSA_PUBKEY(DSA **a,const unsigned char **pp, - long length); -#endif -#ifndef OPENSSL_NO_EC -int i2d_EC_PUBKEY(EC_KEY *a, unsigned char **pp); -EC_KEY *d2i_EC_PUBKEY(EC_KEY **a, const unsigned char **pp, - long length); -#endif - -DECLARE_ASN1_FUNCTIONS(X509_SIG) -DECLARE_ASN1_FUNCTIONS(X509_REQ_INFO) -DECLARE_ASN1_FUNCTIONS(X509_REQ) - -DECLARE_ASN1_FUNCTIONS(X509_ATTRIBUTE) -X509_ATTRIBUTE *X509_ATTRIBUTE_create(int nid, int atrtype, void *value); - -DECLARE_ASN1_FUNCTIONS(X509_EXTENSION) -DECLARE_ASN1_ENCODE_FUNCTIONS(X509_EXTENSIONS, X509_EXTENSIONS, X509_EXTENSIONS) - -DECLARE_ASN1_FUNCTIONS(X509_NAME_ENTRY) - -DECLARE_ASN1_FUNCTIONS(X509_NAME) - -int X509_NAME_set(X509_NAME **xn, X509_NAME *name); - -DECLARE_ASN1_FUNCTIONS(X509_CINF) - -DECLARE_ASN1_FUNCTIONS(X509) -DECLARE_ASN1_FUNCTIONS(X509_CERT_AUX) - -DECLARE_ASN1_FUNCTIONS(X509_CERT_PAIR) - -int X509_get_ex_new_index(long argl, void *argp, CRYPTO_EX_new *new_func, - CRYPTO_EX_dup *dup_func, CRYPTO_EX_free *free_func); -int X509_set_ex_data(X509 *r, int idx, void *arg); -void *X509_get_ex_data(X509 *r, int idx); -int i2d_X509_AUX(X509 *a,unsigned char **pp); -X509 * d2i_X509_AUX(X509 **a,const unsigned char **pp,long length); - -int X509_alias_set1(X509 *x, unsigned char *name, int len); -int X509_keyid_set1(X509 *x, unsigned char *id, int len); -unsigned char * X509_alias_get0(X509 *x, int *len); -unsigned char * X509_keyid_get0(X509 *x, int *len); -int (*X509_TRUST_set_default(int (*trust)(int , X509 *, int)))(int, X509 *, int); -int X509_TRUST_set(int *t, int trust); -int X509_add1_trust_object(X509 *x, ASN1_OBJECT *obj); -int X509_add1_reject_object(X509 *x, ASN1_OBJECT *obj); -void X509_trust_clear(X509 *x); -void X509_reject_clear(X509 *x); - -DECLARE_ASN1_FUNCTIONS(X509_REVOKED) -DECLARE_ASN1_FUNCTIONS(X509_CRL_INFO) -DECLARE_ASN1_FUNCTIONS(X509_CRL) - -int X509_CRL_add0_revoked(X509_CRL *crl, X509_REVOKED *rev); -int X509_CRL_get0_by_serial(X509_CRL *crl, - X509_REVOKED **ret, ASN1_INTEGER *serial); -int X509_CRL_get0_by_cert(X509_CRL *crl, X509_REVOKED **ret, X509 *x); - -X509_PKEY * X509_PKEY_new(void ); -void X509_PKEY_free(X509_PKEY *a); -int i2d_X509_PKEY(X509_PKEY *a,unsigned char **pp); -X509_PKEY * d2i_X509_PKEY(X509_PKEY **a,const unsigned char **pp,long length); - -DECLARE_ASN1_FUNCTIONS(NETSCAPE_SPKI) -DECLARE_ASN1_FUNCTIONS(NETSCAPE_SPKAC) -DECLARE_ASN1_FUNCTIONS(NETSCAPE_CERT_SEQUENCE) - -#ifndef OPENSSL_NO_EVP -X509_INFO * X509_INFO_new(void); -void X509_INFO_free(X509_INFO *a); -char * X509_NAME_oneline(X509_NAME *a,char *buf,int size); - -int ASN1_verify(i2d_of_void *i2d, X509_ALGOR *algor1, - ASN1_BIT_STRING *signature,char *data,EVP_PKEY *pkey); - -int ASN1_digest(i2d_of_void *i2d,const EVP_MD *type,char *data, - unsigned char *md,unsigned int *len); - -int ASN1_sign(i2d_of_void *i2d, X509_ALGOR *algor1, - X509_ALGOR *algor2, ASN1_BIT_STRING *signature, - char *data,EVP_PKEY *pkey, const EVP_MD *type); - -int ASN1_item_digest(const ASN1_ITEM *it,const EVP_MD *type,void *data, - unsigned char *md,unsigned int *len); - -int ASN1_item_verify(const ASN1_ITEM *it, X509_ALGOR *algor1, - ASN1_BIT_STRING *signature,void *data,EVP_PKEY *pkey); - -int ASN1_item_sign(const ASN1_ITEM *it, X509_ALGOR *algor1, X509_ALGOR *algor2, - ASN1_BIT_STRING *signature, - void *data, EVP_PKEY *pkey, const EVP_MD *type); -int ASN1_item_sign_ctx(const ASN1_ITEM *it, - X509_ALGOR *algor1, X509_ALGOR *algor2, - ASN1_BIT_STRING *signature, void *asn, EVP_MD_CTX *ctx); -#endif - -int X509_set_version(X509 *x,long version); -int X509_set_serialNumber(X509 *x, ASN1_INTEGER *serial); -ASN1_INTEGER * X509_get_serialNumber(X509 *x); -int X509_set_issuer_name(X509 *x, X509_NAME *name); -X509_NAME * X509_get_issuer_name(X509 *a); -int X509_set_subject_name(X509 *x, X509_NAME *name); -X509_NAME * X509_get_subject_name(X509 *a); -int X509_set_notBefore(X509 *x, const ASN1_TIME *tm); -int X509_set_notAfter(X509 *x, const ASN1_TIME *tm); -int X509_set_pubkey(X509 *x, EVP_PKEY *pkey); -EVP_PKEY * X509_get_pubkey(X509 *x); -ASN1_BIT_STRING * X509_get0_pubkey_bitstr(const X509 *x); -int X509_certificate_type(X509 *x,EVP_PKEY *pubkey /* optional */); - -int X509_REQ_set_version(X509_REQ *x,long version); -int X509_REQ_set_subject_name(X509_REQ *req,X509_NAME *name); -int X509_REQ_set_pubkey(X509_REQ *x, EVP_PKEY *pkey); -EVP_PKEY * X509_REQ_get_pubkey(X509_REQ *req); -int X509_REQ_extension_nid(int nid); -int * X509_REQ_get_extension_nids(void); -void X509_REQ_set_extension_nids(int *nids); -STACK_OF(X509_EXTENSION) *X509_REQ_get_extensions(X509_REQ *req); -int X509_REQ_add_extensions_nid(X509_REQ *req, STACK_OF(X509_EXTENSION) *exts, - int nid); -int X509_REQ_add_extensions(X509_REQ *req, STACK_OF(X509_EXTENSION) *exts); -int X509_REQ_get_attr_count(const X509_REQ *req); -int X509_REQ_get_attr_by_NID(const X509_REQ *req, int nid, - int lastpos); -int X509_REQ_get_attr_by_OBJ(const X509_REQ *req, ASN1_OBJECT *obj, - int lastpos); -X509_ATTRIBUTE *X509_REQ_get_attr(const X509_REQ *req, int loc); -X509_ATTRIBUTE *X509_REQ_delete_attr(X509_REQ *req, int loc); -int X509_REQ_add1_attr(X509_REQ *req, X509_ATTRIBUTE *attr); -int X509_REQ_add1_attr_by_OBJ(X509_REQ *req, - const ASN1_OBJECT *obj, int type, - const unsigned char *bytes, int len); -int X509_REQ_add1_attr_by_NID(X509_REQ *req, - int nid, int type, - const unsigned char *bytes, int len); -int X509_REQ_add1_attr_by_txt(X509_REQ *req, - const char *attrname, int type, - const unsigned char *bytes, int len); - -int X509_CRL_set_version(X509_CRL *x, long version); -int X509_CRL_set_issuer_name(X509_CRL *x, X509_NAME *name); -int X509_CRL_set_lastUpdate(X509_CRL *x, const ASN1_TIME *tm); -int X509_CRL_set_nextUpdate(X509_CRL *x, const ASN1_TIME *tm); -int X509_CRL_sort(X509_CRL *crl); - -int X509_REVOKED_set_serialNumber(X509_REVOKED *x, ASN1_INTEGER *serial); -int X509_REVOKED_set_revocationDate(X509_REVOKED *r, ASN1_TIME *tm); - -int X509_REQ_check_private_key(X509_REQ *x509,EVP_PKEY *pkey); - -int X509_check_private_key(X509 *x509,EVP_PKEY *pkey); - -int X509_issuer_and_serial_cmp(const X509 *a, const X509 *b); -unsigned long X509_issuer_and_serial_hash(X509 *a); - -int X509_issuer_name_cmp(const X509 *a, const X509 *b); -unsigned long X509_issuer_name_hash(X509 *a); - -int X509_subject_name_cmp(const X509 *a, const X509 *b); -unsigned long X509_subject_name_hash(X509 *x); - -#ifndef OPENSSL_NO_MD5 -unsigned long X509_issuer_name_hash_old(X509 *a); -unsigned long X509_subject_name_hash_old(X509 *x); -#endif - -int X509_cmp(const X509 *a, const X509 *b); -int X509_NAME_cmp(const X509_NAME *a, const X509_NAME *b); -unsigned long X509_NAME_hash(X509_NAME *x); -unsigned long X509_NAME_hash_old(X509_NAME *x); - -int X509_CRL_cmp(const X509_CRL *a, const X509_CRL *b); -int X509_CRL_match(const X509_CRL *a, const X509_CRL *b); -#ifndef OPENSSL_NO_FP_API -int X509_print_ex_fp(FILE *bp,X509 *x, unsigned long nmflag, unsigned long cflag); -int X509_print_fp(FILE *bp,X509 *x); -int X509_CRL_print_fp(FILE *bp,X509_CRL *x); -int X509_REQ_print_fp(FILE *bp,X509_REQ *req); -int X509_NAME_print_ex_fp(FILE *fp, X509_NAME *nm, int indent, unsigned long flags); -#endif - -#ifndef OPENSSL_NO_BIO -int X509_NAME_print(BIO *bp, X509_NAME *name, int obase); -int X509_NAME_print_ex(BIO *out, X509_NAME *nm, int indent, unsigned long flags); -int X509_print_ex(BIO *bp,X509 *x, unsigned long nmflag, unsigned long cflag); -int X509_print(BIO *bp,X509 *x); -int X509_ocspid_print(BIO *bp,X509 *x); -int X509_CERT_AUX_print(BIO *bp,X509_CERT_AUX *x, int indent); -int X509_CRL_print(BIO *bp,X509_CRL *x); -int X509_REQ_print_ex(BIO *bp, X509_REQ *x, unsigned long nmflag, unsigned long cflag); -int X509_REQ_print(BIO *bp,X509_REQ *req); -#endif - -int X509_NAME_entry_count(X509_NAME *name); -int X509_NAME_get_text_by_NID(X509_NAME *name, int nid, - char *buf,int len); -int X509_NAME_get_text_by_OBJ(X509_NAME *name, ASN1_OBJECT *obj, - char *buf,int len); - -/* NOTE: you should be passsing -1, not 0 as lastpos. The functions that use - * lastpos, search after that position on. */ -int X509_NAME_get_index_by_NID(X509_NAME *name,int nid,int lastpos); -int X509_NAME_get_index_by_OBJ(X509_NAME *name,ASN1_OBJECT *obj, - int lastpos); -X509_NAME_ENTRY *X509_NAME_get_entry(X509_NAME *name, int loc); -X509_NAME_ENTRY *X509_NAME_delete_entry(X509_NAME *name, int loc); -int X509_NAME_add_entry(X509_NAME *name,X509_NAME_ENTRY *ne, - int loc, int set); -int X509_NAME_add_entry_by_OBJ(X509_NAME *name, ASN1_OBJECT *obj, int type, - unsigned char *bytes, int len, int loc, int set); -int X509_NAME_add_entry_by_NID(X509_NAME *name, int nid, int type, - unsigned char *bytes, int len, int loc, int set); -X509_NAME_ENTRY *X509_NAME_ENTRY_create_by_txt(X509_NAME_ENTRY **ne, - const char *field, int type, const unsigned char *bytes, int len); -X509_NAME_ENTRY *X509_NAME_ENTRY_create_by_NID(X509_NAME_ENTRY **ne, int nid, - int type,unsigned char *bytes, int len); -int X509_NAME_add_entry_by_txt(X509_NAME *name, const char *field, int type, - const unsigned char *bytes, int len, int loc, int set); -X509_NAME_ENTRY *X509_NAME_ENTRY_create_by_OBJ(X509_NAME_ENTRY **ne, - ASN1_OBJECT *obj, int type,const unsigned char *bytes, - int len); -int X509_NAME_ENTRY_set_object(X509_NAME_ENTRY *ne, - ASN1_OBJECT *obj); -int X509_NAME_ENTRY_set_data(X509_NAME_ENTRY *ne, int type, - const unsigned char *bytes, int len); -ASN1_OBJECT * X509_NAME_ENTRY_get_object(X509_NAME_ENTRY *ne); -ASN1_STRING * X509_NAME_ENTRY_get_data(X509_NAME_ENTRY *ne); - -int X509v3_get_ext_count(const STACK_OF(X509_EXTENSION) *x); -int X509v3_get_ext_by_NID(const STACK_OF(X509_EXTENSION) *x, - int nid, int lastpos); -int X509v3_get_ext_by_OBJ(const STACK_OF(X509_EXTENSION) *x, - ASN1_OBJECT *obj,int lastpos); -int X509v3_get_ext_by_critical(const STACK_OF(X509_EXTENSION) *x, - int crit, int lastpos); -X509_EXTENSION *X509v3_get_ext(const STACK_OF(X509_EXTENSION) *x, int loc); -X509_EXTENSION *X509v3_delete_ext(STACK_OF(X509_EXTENSION) *x, int loc); -STACK_OF(X509_EXTENSION) *X509v3_add_ext(STACK_OF(X509_EXTENSION) **x, - X509_EXTENSION *ex, int loc); - -int X509_get_ext_count(X509 *x); -int X509_get_ext_by_NID(X509 *x, int nid, int lastpos); -int X509_get_ext_by_OBJ(X509 *x,ASN1_OBJECT *obj,int lastpos); -int X509_get_ext_by_critical(X509 *x, int crit, int lastpos); -X509_EXTENSION *X509_get_ext(X509 *x, int loc); -X509_EXTENSION *X509_delete_ext(X509 *x, int loc); -int X509_add_ext(X509 *x, X509_EXTENSION *ex, int loc); -void * X509_get_ext_d2i(X509 *x, int nid, int *crit, int *idx); -int X509_add1_ext_i2d(X509 *x, int nid, void *value, int crit, - unsigned long flags); - -int X509_CRL_get_ext_count(X509_CRL *x); -int X509_CRL_get_ext_by_NID(X509_CRL *x, int nid, int lastpos); -int X509_CRL_get_ext_by_OBJ(X509_CRL *x,ASN1_OBJECT *obj,int lastpos); -int X509_CRL_get_ext_by_critical(X509_CRL *x, int crit, int lastpos); -X509_EXTENSION *X509_CRL_get_ext(X509_CRL *x, int loc); -X509_EXTENSION *X509_CRL_delete_ext(X509_CRL *x, int loc); -int X509_CRL_add_ext(X509_CRL *x, X509_EXTENSION *ex, int loc); -void * X509_CRL_get_ext_d2i(X509_CRL *x, int nid, int *crit, int *idx); -int X509_CRL_add1_ext_i2d(X509_CRL *x, int nid, void *value, int crit, - unsigned long flags); - -int X509_REVOKED_get_ext_count(X509_REVOKED *x); -int X509_REVOKED_get_ext_by_NID(X509_REVOKED *x, int nid, int lastpos); -int X509_REVOKED_get_ext_by_OBJ(X509_REVOKED *x,ASN1_OBJECT *obj,int lastpos); -int X509_REVOKED_get_ext_by_critical(X509_REVOKED *x, int crit, int lastpos); -X509_EXTENSION *X509_REVOKED_get_ext(X509_REVOKED *x, int loc); -X509_EXTENSION *X509_REVOKED_delete_ext(X509_REVOKED *x, int loc); -int X509_REVOKED_add_ext(X509_REVOKED *x, X509_EXTENSION *ex, int loc); -void * X509_REVOKED_get_ext_d2i(X509_REVOKED *x, int nid, int *crit, int *idx); -int X509_REVOKED_add1_ext_i2d(X509_REVOKED *x, int nid, void *value, int crit, - unsigned long flags); - -X509_EXTENSION *X509_EXTENSION_create_by_NID(X509_EXTENSION **ex, - int nid, int crit, ASN1_OCTET_STRING *data); -X509_EXTENSION *X509_EXTENSION_create_by_OBJ(X509_EXTENSION **ex, - ASN1_OBJECT *obj,int crit,ASN1_OCTET_STRING *data); -int X509_EXTENSION_set_object(X509_EXTENSION *ex,ASN1_OBJECT *obj); -int X509_EXTENSION_set_critical(X509_EXTENSION *ex, int crit); -int X509_EXTENSION_set_data(X509_EXTENSION *ex, - ASN1_OCTET_STRING *data); -ASN1_OBJECT * X509_EXTENSION_get_object(X509_EXTENSION *ex); -ASN1_OCTET_STRING *X509_EXTENSION_get_data(X509_EXTENSION *ne); -int X509_EXTENSION_get_critical(X509_EXTENSION *ex); - -int X509at_get_attr_count(const STACK_OF(X509_ATTRIBUTE) *x); -int X509at_get_attr_by_NID(const STACK_OF(X509_ATTRIBUTE) *x, int nid, - int lastpos); -int X509at_get_attr_by_OBJ(const STACK_OF(X509_ATTRIBUTE) *sk, ASN1_OBJECT *obj, - int lastpos); -X509_ATTRIBUTE *X509at_get_attr(const STACK_OF(X509_ATTRIBUTE) *x, int loc); -X509_ATTRIBUTE *X509at_delete_attr(STACK_OF(X509_ATTRIBUTE) *x, int loc); -STACK_OF(X509_ATTRIBUTE) *X509at_add1_attr(STACK_OF(X509_ATTRIBUTE) **x, - X509_ATTRIBUTE *attr); -STACK_OF(X509_ATTRIBUTE) *X509at_add1_attr_by_OBJ(STACK_OF(X509_ATTRIBUTE) **x, - const ASN1_OBJECT *obj, int type, - const unsigned char *bytes, int len); -STACK_OF(X509_ATTRIBUTE) *X509at_add1_attr_by_NID(STACK_OF(X509_ATTRIBUTE) **x, - int nid, int type, - const unsigned char *bytes, int len); -STACK_OF(X509_ATTRIBUTE) *X509at_add1_attr_by_txt(STACK_OF(X509_ATTRIBUTE) **x, - const char *attrname, int type, - const unsigned char *bytes, int len); -void *X509at_get0_data_by_OBJ(STACK_OF(X509_ATTRIBUTE) *x, - ASN1_OBJECT *obj, int lastpos, int type); -X509_ATTRIBUTE *X509_ATTRIBUTE_create_by_NID(X509_ATTRIBUTE **attr, int nid, - int atrtype, const void *data, int len); -X509_ATTRIBUTE *X509_ATTRIBUTE_create_by_OBJ(X509_ATTRIBUTE **attr, - const ASN1_OBJECT *obj, int atrtype, const void *data, int len); -X509_ATTRIBUTE *X509_ATTRIBUTE_create_by_txt(X509_ATTRIBUTE **attr, - const char *atrname, int type, const unsigned char *bytes, int len); -int X509_ATTRIBUTE_set1_object(X509_ATTRIBUTE *attr, const ASN1_OBJECT *obj); -int X509_ATTRIBUTE_set1_data(X509_ATTRIBUTE *attr, int attrtype, const void *data, int len); -void *X509_ATTRIBUTE_get0_data(X509_ATTRIBUTE *attr, int idx, - int atrtype, void *data); -int X509_ATTRIBUTE_count(X509_ATTRIBUTE *attr); -ASN1_OBJECT *X509_ATTRIBUTE_get0_object(X509_ATTRIBUTE *attr); -ASN1_TYPE *X509_ATTRIBUTE_get0_type(X509_ATTRIBUTE *attr, int idx); - -int EVP_PKEY_get_attr_count(const EVP_PKEY *key); -int EVP_PKEY_get_attr_by_NID(const EVP_PKEY *key, int nid, - int lastpos); -int EVP_PKEY_get_attr_by_OBJ(const EVP_PKEY *key, ASN1_OBJECT *obj, - int lastpos); -X509_ATTRIBUTE *EVP_PKEY_get_attr(const EVP_PKEY *key, int loc); -X509_ATTRIBUTE *EVP_PKEY_delete_attr(EVP_PKEY *key, int loc); -int EVP_PKEY_add1_attr(EVP_PKEY *key, X509_ATTRIBUTE *attr); -int EVP_PKEY_add1_attr_by_OBJ(EVP_PKEY *key, - const ASN1_OBJECT *obj, int type, - const unsigned char *bytes, int len); -int EVP_PKEY_add1_attr_by_NID(EVP_PKEY *key, - int nid, int type, - const unsigned char *bytes, int len); -int EVP_PKEY_add1_attr_by_txt(EVP_PKEY *key, - const char *attrname, int type, - const unsigned char *bytes, int len); - -int X509_verify_cert(X509_STORE_CTX *ctx); - -/* lookup a cert from a X509 STACK */ -X509 *X509_find_by_issuer_and_serial(STACK_OF(X509) *sk,X509_NAME *name, - ASN1_INTEGER *serial); -X509 *X509_find_by_subject(STACK_OF(X509) *sk,X509_NAME *name); - -DECLARE_ASN1_FUNCTIONS(PBEPARAM) -DECLARE_ASN1_FUNCTIONS(PBE2PARAM) -DECLARE_ASN1_FUNCTIONS(PBKDF2PARAM) - -int PKCS5_pbe_set0_algor(X509_ALGOR *algor, int alg, int iter, - const unsigned char *salt, int saltlen); - -X509_ALGOR *PKCS5_pbe_set(int alg, int iter, - const unsigned char *salt, int saltlen); -X509_ALGOR *PKCS5_pbe2_set(const EVP_CIPHER *cipher, int iter, - unsigned char *salt, int saltlen); -X509_ALGOR *PKCS5_pbe2_set_iv(const EVP_CIPHER *cipher, int iter, - unsigned char *salt, int saltlen, - unsigned char *aiv, int prf_nid); - -X509_ALGOR *PKCS5_pbkdf2_set(int iter, unsigned char *salt, int saltlen, - int prf_nid, int keylen); - -/* PKCS#8 utilities */ - -DECLARE_ASN1_FUNCTIONS(PKCS8_PRIV_KEY_INFO) - -EVP_PKEY *EVP_PKCS82PKEY(PKCS8_PRIV_KEY_INFO *p8); -PKCS8_PRIV_KEY_INFO *EVP_PKEY2PKCS8(EVP_PKEY *pkey); -PKCS8_PRIV_KEY_INFO *EVP_PKEY2PKCS8_broken(EVP_PKEY *pkey, int broken); -PKCS8_PRIV_KEY_INFO *PKCS8_set_broken(PKCS8_PRIV_KEY_INFO *p8, int broken); - -int PKCS8_pkey_set0(PKCS8_PRIV_KEY_INFO *priv, ASN1_OBJECT *aobj, - int version, int ptype, void *pval, - unsigned char *penc, int penclen); -int PKCS8_pkey_get0(ASN1_OBJECT **ppkalg, - const unsigned char **pk, int *ppklen, - X509_ALGOR **pa, - PKCS8_PRIV_KEY_INFO *p8); - -int X509_PUBKEY_set0_param(X509_PUBKEY *pub, ASN1_OBJECT *aobj, - int ptype, void *pval, - unsigned char *penc, int penclen); -int X509_PUBKEY_get0_param(ASN1_OBJECT **ppkalg, - const unsigned char **pk, int *ppklen, - X509_ALGOR **pa, - X509_PUBKEY *pub); - -int X509_check_trust(X509 *x, int id, int flags); -int X509_TRUST_get_count(void); -X509_TRUST * X509_TRUST_get0(int idx); -int X509_TRUST_get_by_id(int id); -int X509_TRUST_add(int id, int flags, int (*ck)(X509_TRUST *, X509 *, int), - char *name, int arg1, void *arg2); -void X509_TRUST_cleanup(void); -int X509_TRUST_get_flags(X509_TRUST *xp); -char *X509_TRUST_get0_name(X509_TRUST *xp); -int X509_TRUST_get_trust(X509_TRUST *xp); - -/* BEGIN ERROR CODES */ -/* The following lines are auto generated by the script mkerr.pl. Any changes - * made after this point may be overwritten when the script is next run. - */ -void ERR_load_X509_strings(void); - -/* Error codes for the X509 functions. */ - -/* Function codes. */ -#define X509_F_ADD_CERT_DIR 100 -#define X509_F_BY_FILE_CTRL 101 -#define X509_F_CHECK_POLICY 145 -#define X509_F_DIR_CTRL 102 -#define X509_F_GET_CERT_BY_SUBJECT 103 -#define X509_F_NETSCAPE_SPKI_B64_DECODE 129 -#define X509_F_NETSCAPE_SPKI_B64_ENCODE 130 -#define X509_F_X509AT_ADD1_ATTR 135 -#define X509_F_X509V3_ADD_EXT 104 -#define X509_F_X509_ATTRIBUTE_CREATE_BY_NID 136 -#define X509_F_X509_ATTRIBUTE_CREATE_BY_OBJ 137 -#define X509_F_X509_ATTRIBUTE_CREATE_BY_TXT 140 -#define X509_F_X509_ATTRIBUTE_GET0_DATA 139 -#define X509_F_X509_ATTRIBUTE_SET1_DATA 138 -#define X509_F_X509_CHECK_PRIVATE_KEY 128 -#define X509_F_X509_CRL_PRINT_FP 147 -#define X509_F_X509_EXTENSION_CREATE_BY_NID 108 -#define X509_F_X509_EXTENSION_CREATE_BY_OBJ 109 -#define X509_F_X509_GET_PUBKEY_PARAMETERS 110 -#define X509_F_X509_LOAD_CERT_CRL_FILE 132 -#define X509_F_X509_LOAD_CERT_FILE 111 -#define X509_F_X509_LOAD_CRL_FILE 112 -#define X509_F_X509_NAME_ADD_ENTRY 113 -#define X509_F_X509_NAME_ENTRY_CREATE_BY_NID 114 -#define X509_F_X509_NAME_ENTRY_CREATE_BY_TXT 131 -#define X509_F_X509_NAME_ENTRY_SET_OBJECT 115 -#define X509_F_X509_NAME_ONELINE 116 -#define X509_F_X509_NAME_PRINT 117 -#define X509_F_X509_PRINT_EX_FP 118 -#define X509_F_X509_PUBKEY_GET 119 -#define X509_F_X509_PUBKEY_SET 120 -#define X509_F_X509_REQ_CHECK_PRIVATE_KEY 144 -#define X509_F_X509_REQ_PRINT_EX 121 -#define X509_F_X509_REQ_PRINT_FP 122 -#define X509_F_X509_REQ_TO_X509 123 -#define X509_F_X509_STORE_ADD_CERT 124 -#define X509_F_X509_STORE_ADD_CRL 125 -#define X509_F_X509_STORE_CTX_GET1_ISSUER 146 -#define X509_F_X509_STORE_CTX_INIT 143 -#define X509_F_X509_STORE_CTX_NEW 142 -#define X509_F_X509_STORE_CTX_PURPOSE_INHERIT 134 -#define X509_F_X509_TO_X509_REQ 126 -#define X509_F_X509_TRUST_ADD 133 -#define X509_F_X509_TRUST_SET 141 -#define X509_F_X509_VERIFY_CERT 127 - -/* Reason codes. */ -#define X509_R_BAD_X509_FILETYPE 100 -#define X509_R_BASE64_DECODE_ERROR 118 -#define X509_R_CANT_CHECK_DH_KEY 114 -#define X509_R_CERT_ALREADY_IN_HASH_TABLE 101 -#define X509_R_ERR_ASN1_LIB 102 -#define X509_R_INVALID_DIRECTORY 113 -#define X509_R_INVALID_FIELD_NAME 119 -#define X509_R_INVALID_TRUST 123 -#define X509_R_KEY_TYPE_MISMATCH 115 -#define X509_R_KEY_VALUES_MISMATCH 116 -#define X509_R_LOADING_CERT_DIR 103 -#define X509_R_LOADING_DEFAULTS 104 -#define X509_R_METHOD_NOT_SUPPORTED 124 -#define X509_R_NO_CERT_SET_FOR_US_TO_VERIFY 105 -#define X509_R_PUBLIC_KEY_DECODE_ERROR 125 -#define X509_R_PUBLIC_KEY_ENCODE_ERROR 126 -#define X509_R_SHOULD_RETRY 106 -#define X509_R_UNABLE_TO_FIND_PARAMETERS_IN_CHAIN 107 -#define X509_R_UNABLE_TO_GET_CERTS_PUBLIC_KEY 108 -#define X509_R_UNKNOWN_KEY_TYPE 117 -#define X509_R_UNKNOWN_NID 109 -#define X509_R_UNKNOWN_PURPOSE_ID 121 -#define X509_R_UNKNOWN_TRUST_ID 120 -#define X509_R_UNSUPPORTED_ALGORITHM 111 -#define X509_R_WRONG_LOOKUP_TYPE 112 -#define X509_R_WRONG_TYPE 122 - -#ifdef __cplusplus -} -#endif -#endif diff --git a/src/plugins/ftp/openssl/x509_vfy.h b/src/plugins/ftp/openssl/x509_vfy.h deleted file mode 100644 index fe09b30a..00000000 --- a/src/plugins/ftp/openssl/x509_vfy.h +++ /dev/null @@ -1,567 +0,0 @@ -/* crypto/x509/x509_vfy.h */ -/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) - * All rights reserved. - * - * This package is an SSL implementation written - * by Eric Young (eay@cryptsoft.com). - * The implementation was written so as to conform with Netscapes SSL. - * - * This library is free for commercial and non-commercial use as long as - * the following conditions are aheared to. The following conditions - * apply to all code found in this distribution, be it the RC4, RSA, - * lhash, DES, etc., code; not just the SSL code. The SSL documentation - * included with this distribution is covered by the same copyright terms - * except that the holder is Tim Hudson (tjh@cryptsoft.com). - * - * Copyright remains Eric Young's, and as such any Copyright notices in - * the code are not to be removed. - * If this package is used in a product, Eric Young should be given attribution - * as the author of the parts of the library used. - * This can be in the form of a textual message at program startup or - * in documentation (online or textual) provided with the package. - * - * 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 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. All advertising materials mentioning features or use of this software - * must display the following acknowledgement: - * "This product includes cryptographic software written by - * Eric Young (eay@cryptsoft.com)" - * The word 'cryptographic' can be left out if the rouines from the library - * being used are not cryptographic related :-). - * 4. If you include any Windows specific code (or a derivative thereof) from - * the apps directory (application code) you must include an acknowledgement: - * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" - * - * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``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 AUTHOR 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. - * - * The licence and distribution terms for any publically available version or - * derivative of this code cannot be changed. i.e. this code cannot simply be - * copied and put under another distribution licence - * [including the GNU Public Licence.] - */ - -#ifndef HEADER_X509_H -#include -/* openssl/x509.h ends up #include-ing this file at about the only - * appropriate moment. */ -#endif - -#ifndef HEADER_X509_VFY_H -#define HEADER_X509_VFY_H - -#include -#ifndef OPENSSL_NO_LHASH -#include -#endif -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#if 0 -/* Outer object */ -typedef struct x509_hash_dir_st - { - int num_dirs; - char **dirs; - int *dirs_type; - int num_dirs_alloced; - } X509_HASH_DIR_CTX; -#endif - -typedef struct x509_file_st - { - int num_paths; /* number of paths to files or directories */ - int num_alloced; - char **paths; /* the list of paths or directories */ - int *path_type; - } X509_CERT_FILE_CTX; - -/*******************************/ -/* -SSL_CTX -> X509_STORE - -> X509_LOOKUP - ->X509_LOOKUP_METHOD - -> X509_LOOKUP - ->X509_LOOKUP_METHOD - -SSL -> X509_STORE_CTX - ->X509_STORE - -The X509_STORE holds the tables etc for verification stuff. -A X509_STORE_CTX is used while validating a single certificate. -The X509_STORE has X509_LOOKUPs for looking up certs. -The X509_STORE then calls a function to actually verify the -certificate chain. -*/ - -#define X509_LU_RETRY -1 -#define X509_LU_FAIL 0 -#define X509_LU_X509 1 -#define X509_LU_CRL 2 -#define X509_LU_PKEY 3 - -typedef struct x509_object_st - { - /* one of the above types */ - int type; - union { - char *ptr; - X509 *x509; - X509_CRL *crl; - EVP_PKEY *pkey; - } data; - } X509_OBJECT; - -typedef struct x509_lookup_st X509_LOOKUP; - -DECLARE_STACK_OF(X509_LOOKUP) -DECLARE_STACK_OF(X509_OBJECT) - -/* This is a static that defines the function interface */ -typedef struct x509_lookup_method_st - { - const char *name; - int (*new_item)(X509_LOOKUP *ctx); - void (*free)(X509_LOOKUP *ctx); - int (*init)(X509_LOOKUP *ctx); - int (*shutdown)(X509_LOOKUP *ctx); - int (*ctrl)(X509_LOOKUP *ctx,int cmd,const char *argc,long argl, - char **ret); - int (*get_by_subject)(X509_LOOKUP *ctx,int type,X509_NAME *name, - X509_OBJECT *ret); - int (*get_by_issuer_serial)(X509_LOOKUP *ctx,int type,X509_NAME *name, - ASN1_INTEGER *serial,X509_OBJECT *ret); - int (*get_by_fingerprint)(X509_LOOKUP *ctx,int type, - unsigned char *bytes,int len, - X509_OBJECT *ret); - int (*get_by_alias)(X509_LOOKUP *ctx,int type,char *str,int len, - X509_OBJECT *ret); - } X509_LOOKUP_METHOD; - -/* This structure hold all parameters associated with a verify operation - * by including an X509_VERIFY_PARAM structure in related structures the - * parameters used can be customized - */ - -typedef struct X509_VERIFY_PARAM_st - { - char *name; - time_t check_time; /* Time to use */ - unsigned long inh_flags; /* Inheritance flags */ - unsigned long flags; /* Various verify flags */ - int purpose; /* purpose to check untrusted certificates */ - int trust; /* trust setting to check */ - int depth; /* Verify depth */ - STACK_OF(ASN1_OBJECT) *policies; /* Permissible policies */ - } X509_VERIFY_PARAM; - -DECLARE_STACK_OF(X509_VERIFY_PARAM) - -/* This is used to hold everything. It is used for all certificate - * validation. Once we have a certificate chain, the 'verify' - * function is then called to actually check the cert chain. */ -struct x509_store_st - { - /* The following is a cache of trusted certs */ - int cache; /* if true, stash any hits */ - STACK_OF(X509_OBJECT) *objs; /* Cache of all objects */ - - /* These are external lookup methods */ - STACK_OF(X509_LOOKUP) *get_cert_methods; - - X509_VERIFY_PARAM *param; - - /* Callbacks for various operations */ - int (*verify)(X509_STORE_CTX *ctx); /* called to verify a certificate */ - int (*verify_cb)(int ok,X509_STORE_CTX *ctx); /* error callback */ - int (*get_issuer)(X509 **issuer, X509_STORE_CTX *ctx, X509 *x); /* get issuers cert from ctx */ - int (*check_issued)(X509_STORE_CTX *ctx, X509 *x, X509 *issuer); /* check issued */ - int (*check_revocation)(X509_STORE_CTX *ctx); /* Check revocation status of chain */ - int (*get_crl)(X509_STORE_CTX *ctx, X509_CRL **crl, X509 *x); /* retrieve CRL */ - int (*check_crl)(X509_STORE_CTX *ctx, X509_CRL *crl); /* Check CRL validity */ - int (*cert_crl)(X509_STORE_CTX *ctx, X509_CRL *crl, X509 *x); /* Check certificate against CRL */ - STACK_OF(X509) * (*lookup_certs)(X509_STORE_CTX *ctx, X509_NAME *nm); - STACK_OF(X509_CRL) * (*lookup_crls)(X509_STORE_CTX *ctx, X509_NAME *nm); - int (*cleanup)(X509_STORE_CTX *ctx); - - CRYPTO_EX_DATA ex_data; - int references; - } /* X509_STORE */; - -int X509_STORE_set_depth(X509_STORE *store, int depth); - -#define X509_STORE_set_verify_cb_func(ctx,func) ((ctx)->verify_cb=(func)) -#define X509_STORE_set_verify_func(ctx,func) ((ctx)->verify=(func)) - -/* This is the functions plus an instance of the local variables. */ -struct x509_lookup_st - { - int init; /* have we been started */ - int skip; /* don't use us. */ - X509_LOOKUP_METHOD *method; /* the functions */ - char *method_data; /* method data */ - - X509_STORE *store_ctx; /* who owns us */ - } /* X509_LOOKUP */; - -/* This is a used when verifying cert chains. Since the - * gathering of the cert chain can take some time (and have to be - * 'retried', this needs to be kept and passed around. */ -struct x509_store_ctx_st /* X509_STORE_CTX */ - { - X509_STORE *ctx; - int current_method; /* used when looking up certs */ - - /* The following are set by the caller */ - X509 *cert; /* The cert to check */ - STACK_OF(X509) *untrusted; /* chain of X509s - untrusted - passed in */ - STACK_OF(X509_CRL) *crls; /* set of CRLs passed in */ - - X509_VERIFY_PARAM *param; - void *other_ctx; /* Other info for use with get_issuer() */ - - /* Callbacks for various operations */ - int (*verify)(X509_STORE_CTX *ctx); /* called to verify a certificate */ - int (*verify_cb)(int ok,X509_STORE_CTX *ctx); /* error callback */ - int (*get_issuer)(X509 **issuer, X509_STORE_CTX *ctx, X509 *x); /* get issuers cert from ctx */ - int (*check_issued)(X509_STORE_CTX *ctx, X509 *x, X509 *issuer); /* check issued */ - int (*check_revocation)(X509_STORE_CTX *ctx); /* Check revocation status of chain */ - int (*get_crl)(X509_STORE_CTX *ctx, X509_CRL **crl, X509 *x); /* retrieve CRL */ - int (*check_crl)(X509_STORE_CTX *ctx, X509_CRL *crl); /* Check CRL validity */ - int (*cert_crl)(X509_STORE_CTX *ctx, X509_CRL *crl, X509 *x); /* Check certificate against CRL */ - int (*check_policy)(X509_STORE_CTX *ctx); - STACK_OF(X509) * (*lookup_certs)(X509_STORE_CTX *ctx, X509_NAME *nm); - STACK_OF(X509_CRL) * (*lookup_crls)(X509_STORE_CTX *ctx, X509_NAME *nm); - int (*cleanup)(X509_STORE_CTX *ctx); - - /* The following is built up */ - int valid; /* if 0, rebuild chain */ - int last_untrusted; /* index of last untrusted cert */ - STACK_OF(X509) *chain; /* chain of X509s - built up and trusted */ - X509_POLICY_TREE *tree; /* Valid policy tree */ - - int explicit_policy; /* Require explicit policy value */ - - /* When something goes wrong, this is why */ - int error_depth; - int error; - X509 *current_cert; - X509 *current_issuer; /* cert currently being tested as valid issuer */ - X509_CRL *current_crl; /* current CRL */ - - int current_crl_score; /* score of current CRL */ - unsigned int current_reasons; /* Reason mask */ - - X509_STORE_CTX *parent; /* For CRL path validation: parent context */ - - CRYPTO_EX_DATA ex_data; - } /* X509_STORE_CTX */; - -void X509_STORE_CTX_set_depth(X509_STORE_CTX *ctx, int depth); - -#define X509_STORE_CTX_set_app_data(ctx,data) \ - X509_STORE_CTX_set_ex_data(ctx,0,data) -#define X509_STORE_CTX_get_app_data(ctx) \ - X509_STORE_CTX_get_ex_data(ctx,0) - -#define X509_L_FILE_LOAD 1 -#define X509_L_ADD_DIR 2 - -#define X509_LOOKUP_load_file(x,name,type) \ - X509_LOOKUP_ctrl((x),X509_L_FILE_LOAD,(name),(long)(type),NULL) - -#define X509_LOOKUP_add_dir(x,name,type) \ - X509_LOOKUP_ctrl((x),X509_L_ADD_DIR,(name),(long)(type),NULL) - -#define X509_V_OK 0 -/* illegal error (for uninitialized values, to avoid X509_V_OK): 1 */ - -#define X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT 2 -#define X509_V_ERR_UNABLE_TO_GET_CRL 3 -#define X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE 4 -#define X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE 5 -#define X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY 6 -#define X509_V_ERR_CERT_SIGNATURE_FAILURE 7 -#define X509_V_ERR_CRL_SIGNATURE_FAILURE 8 -#define X509_V_ERR_CERT_NOT_YET_VALID 9 -#define X509_V_ERR_CERT_HAS_EXPIRED 10 -#define X509_V_ERR_CRL_NOT_YET_VALID 11 -#define X509_V_ERR_CRL_HAS_EXPIRED 12 -#define X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD 13 -#define X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD 14 -#define X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD 15 -#define X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD 16 -#define X509_V_ERR_OUT_OF_MEM 17 -#define X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT 18 -#define X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN 19 -#define X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY 20 -#define X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE 21 -#define X509_V_ERR_CERT_CHAIN_TOO_LONG 22 -#define X509_V_ERR_CERT_REVOKED 23 -#define X509_V_ERR_INVALID_CA 24 -#define X509_V_ERR_PATH_LENGTH_EXCEEDED 25 -#define X509_V_ERR_INVALID_PURPOSE 26 -#define X509_V_ERR_CERT_UNTRUSTED 27 -#define X509_V_ERR_CERT_REJECTED 28 -/* These are 'informational' when looking for issuer cert */ -#define X509_V_ERR_SUBJECT_ISSUER_MISMATCH 29 -#define X509_V_ERR_AKID_SKID_MISMATCH 30 -#define X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH 31 -#define X509_V_ERR_KEYUSAGE_NO_CERTSIGN 32 - -#define X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER 33 -#define X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION 34 -#define X509_V_ERR_KEYUSAGE_NO_CRL_SIGN 35 -#define X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION 36 -#define X509_V_ERR_INVALID_NON_CA 37 -#define X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED 38 -#define X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE 39 -#define X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED 40 - -#define X509_V_ERR_INVALID_EXTENSION 41 -#define X509_V_ERR_INVALID_POLICY_EXTENSION 42 -#define X509_V_ERR_NO_EXPLICIT_POLICY 43 -#define X509_V_ERR_DIFFERENT_CRL_SCOPE 44 -#define X509_V_ERR_UNSUPPORTED_EXTENSION_FEATURE 45 - -#define X509_V_ERR_UNNESTED_RESOURCE 46 - -#define X509_V_ERR_PERMITTED_VIOLATION 47 -#define X509_V_ERR_EXCLUDED_VIOLATION 48 -#define X509_V_ERR_SUBTREE_MINMAX 49 -#define X509_V_ERR_UNSUPPORTED_CONSTRAINT_TYPE 51 -#define X509_V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX 52 -#define X509_V_ERR_UNSUPPORTED_NAME_SYNTAX 53 -#define X509_V_ERR_CRL_PATH_VALIDATION_ERROR 54 - -/* The application is not happy */ -#define X509_V_ERR_APPLICATION_VERIFICATION 50 - -/* Certificate verify flags */ - -/* Send issuer+subject checks to verify_cb */ -#define X509_V_FLAG_CB_ISSUER_CHECK 0x1 -/* Use check time instead of current time */ -#define X509_V_FLAG_USE_CHECK_TIME 0x2 -/* Lookup CRLs */ -#define X509_V_FLAG_CRL_CHECK 0x4 -/* Lookup CRLs for whole chain */ -#define X509_V_FLAG_CRL_CHECK_ALL 0x8 -/* Ignore unhandled critical extensions */ -#define X509_V_FLAG_IGNORE_CRITICAL 0x10 -/* Disable workarounds for broken certificates */ -#define X509_V_FLAG_X509_STRICT 0x20 -/* Enable proxy certificate validation */ -#define X509_V_FLAG_ALLOW_PROXY_CERTS 0x40 -/* Enable policy checking */ -#define X509_V_FLAG_POLICY_CHECK 0x80 -/* Policy variable require-explicit-policy */ -#define X509_V_FLAG_EXPLICIT_POLICY 0x100 -/* Policy variable inhibit-any-policy */ -#define X509_V_FLAG_INHIBIT_ANY 0x200 -/* Policy variable inhibit-policy-mapping */ -#define X509_V_FLAG_INHIBIT_MAP 0x400 -/* Notify callback that policy is OK */ -#define X509_V_FLAG_NOTIFY_POLICY 0x800 -/* Extended CRL features such as indirect CRLs, alternate CRL signing keys */ -#define X509_V_FLAG_EXTENDED_CRL_SUPPORT 0x1000 -/* Delta CRL support */ -#define X509_V_FLAG_USE_DELTAS 0x2000 -/* Check selfsigned CA signature */ -#define X509_V_FLAG_CHECK_SS_SIGNATURE 0x4000 - - -#define X509_VP_FLAG_DEFAULT 0x1 -#define X509_VP_FLAG_OVERWRITE 0x2 -#define X509_VP_FLAG_RESET_FLAGS 0x4 -#define X509_VP_FLAG_LOCKED 0x8 -#define X509_VP_FLAG_ONCE 0x10 - -/* Internal use: mask of policy related options */ -#define X509_V_FLAG_POLICY_MASK (X509_V_FLAG_POLICY_CHECK \ - | X509_V_FLAG_EXPLICIT_POLICY \ - | X509_V_FLAG_INHIBIT_ANY \ - | X509_V_FLAG_INHIBIT_MAP) - -int X509_OBJECT_idx_by_subject(STACK_OF(X509_OBJECT) *h, int type, - X509_NAME *name); -X509_OBJECT *X509_OBJECT_retrieve_by_subject(STACK_OF(X509_OBJECT) *h,int type,X509_NAME *name); -X509_OBJECT *X509_OBJECT_retrieve_match(STACK_OF(X509_OBJECT) *h, X509_OBJECT *x); -void X509_OBJECT_up_ref_count(X509_OBJECT *a); -void X509_OBJECT_free_contents(X509_OBJECT *a); -X509_STORE *X509_STORE_new(void ); -void X509_STORE_free(X509_STORE *v); - -STACK_OF(X509)* X509_STORE_get1_certs(X509_STORE_CTX *st, X509_NAME *nm); -STACK_OF(X509_CRL)* X509_STORE_get1_crls(X509_STORE_CTX *st, X509_NAME *nm); -int X509_STORE_set_flags(X509_STORE *ctx, unsigned long flags); -int X509_STORE_set_purpose(X509_STORE *ctx, int purpose); -int X509_STORE_set_trust(X509_STORE *ctx, int trust); -int X509_STORE_set1_param(X509_STORE *ctx, X509_VERIFY_PARAM *pm); - -void X509_STORE_set_verify_cb(X509_STORE *ctx, - int (*verify_cb)(int, X509_STORE_CTX *)); - -X509_STORE_CTX *X509_STORE_CTX_new(void); - -int X509_STORE_CTX_get1_issuer(X509 **issuer, X509_STORE_CTX *ctx, X509 *x); - -void X509_STORE_CTX_free(X509_STORE_CTX *ctx); -int X509_STORE_CTX_init(X509_STORE_CTX *ctx, X509_STORE *store, - X509 *x509, STACK_OF(X509) *chain); -void X509_STORE_CTX_trusted_stack(X509_STORE_CTX *ctx, STACK_OF(X509) *sk); -void X509_STORE_CTX_cleanup(X509_STORE_CTX *ctx); - -X509_LOOKUP *X509_STORE_add_lookup(X509_STORE *v, X509_LOOKUP_METHOD *m); - -X509_LOOKUP_METHOD *X509_LOOKUP_hash_dir(void); -X509_LOOKUP_METHOD *X509_LOOKUP_file(void); - -int X509_STORE_add_cert(X509_STORE *ctx, X509 *x); -int X509_STORE_add_crl(X509_STORE *ctx, X509_CRL *x); - -int X509_STORE_get_by_subject(X509_STORE_CTX *vs,int type,X509_NAME *name, - X509_OBJECT *ret); - -int X509_LOOKUP_ctrl(X509_LOOKUP *ctx, int cmd, const char *argc, - long argl, char **ret); - -#ifndef OPENSSL_NO_STDIO -int X509_load_cert_file(X509_LOOKUP *ctx, const char *file, int type); -int X509_load_crl_file(X509_LOOKUP *ctx, const char *file, int type); -int X509_load_cert_crl_file(X509_LOOKUP *ctx, const char *file, int type); -#endif - - -X509_LOOKUP *X509_LOOKUP_new(X509_LOOKUP_METHOD *method); -void X509_LOOKUP_free(X509_LOOKUP *ctx); -int X509_LOOKUP_init(X509_LOOKUP *ctx); -int X509_LOOKUP_by_subject(X509_LOOKUP *ctx, int type, X509_NAME *name, - X509_OBJECT *ret); -int X509_LOOKUP_by_issuer_serial(X509_LOOKUP *ctx, int type, X509_NAME *name, - ASN1_INTEGER *serial, X509_OBJECT *ret); -int X509_LOOKUP_by_fingerprint(X509_LOOKUP *ctx, int type, - unsigned char *bytes, int len, X509_OBJECT *ret); -int X509_LOOKUP_by_alias(X509_LOOKUP *ctx, int type, char *str, - int len, X509_OBJECT *ret); -int X509_LOOKUP_shutdown(X509_LOOKUP *ctx); - -#ifndef OPENSSL_NO_STDIO -int X509_STORE_load_locations (X509_STORE *ctx, - const char *file, const char *dir); -int X509_STORE_set_default_paths(X509_STORE *ctx); -#endif - -int X509_STORE_CTX_get_ex_new_index(long argl, void *argp, CRYPTO_EX_new *new_func, - CRYPTO_EX_dup *dup_func, CRYPTO_EX_free *free_func); -int X509_STORE_CTX_set_ex_data(X509_STORE_CTX *ctx,int idx,void *data); -void * X509_STORE_CTX_get_ex_data(X509_STORE_CTX *ctx,int idx); -int X509_STORE_CTX_get_error(X509_STORE_CTX *ctx); -void X509_STORE_CTX_set_error(X509_STORE_CTX *ctx,int s); -int X509_STORE_CTX_get_error_depth(X509_STORE_CTX *ctx); -X509 * X509_STORE_CTX_get_current_cert(X509_STORE_CTX *ctx); -X509 *X509_STORE_CTX_get0_current_issuer(X509_STORE_CTX *ctx); -X509_CRL *X509_STORE_CTX_get0_current_crl(X509_STORE_CTX *ctx); -X509_STORE_CTX *X509_STORE_CTX_get0_parent_ctx(X509_STORE_CTX *ctx); -STACK_OF(X509) *X509_STORE_CTX_get_chain(X509_STORE_CTX *ctx); -STACK_OF(X509) *X509_STORE_CTX_get1_chain(X509_STORE_CTX *ctx); -void X509_STORE_CTX_set_cert(X509_STORE_CTX *c,X509 *x); -void X509_STORE_CTX_set_chain(X509_STORE_CTX *c,STACK_OF(X509) *sk); -void X509_STORE_CTX_set0_crls(X509_STORE_CTX *c,STACK_OF(X509_CRL) *sk); -int X509_STORE_CTX_set_purpose(X509_STORE_CTX *ctx, int purpose); -int X509_STORE_CTX_set_trust(X509_STORE_CTX *ctx, int trust); -int X509_STORE_CTX_purpose_inherit(X509_STORE_CTX *ctx, int def_purpose, - int purpose, int trust); -void X509_STORE_CTX_set_flags(X509_STORE_CTX *ctx, unsigned long flags); -void X509_STORE_CTX_set_time(X509_STORE_CTX *ctx, unsigned long flags, - time_t t); -void X509_STORE_CTX_set_verify_cb(X509_STORE_CTX *ctx, - int (*verify_cb)(int, X509_STORE_CTX *)); - -X509_POLICY_TREE *X509_STORE_CTX_get0_policy_tree(X509_STORE_CTX *ctx); -int X509_STORE_CTX_get_explicit_policy(X509_STORE_CTX *ctx); - -X509_VERIFY_PARAM *X509_STORE_CTX_get0_param(X509_STORE_CTX *ctx); -void X509_STORE_CTX_set0_param(X509_STORE_CTX *ctx, X509_VERIFY_PARAM *param); -int X509_STORE_CTX_set_default(X509_STORE_CTX *ctx, const char *name); - -/* X509_VERIFY_PARAM functions */ - -X509_VERIFY_PARAM *X509_VERIFY_PARAM_new(void); -void X509_VERIFY_PARAM_free(X509_VERIFY_PARAM *param); -int X509_VERIFY_PARAM_inherit(X509_VERIFY_PARAM *to, - const X509_VERIFY_PARAM *from); -int X509_VERIFY_PARAM_set1(X509_VERIFY_PARAM *to, - const X509_VERIFY_PARAM *from); -int X509_VERIFY_PARAM_set1_name(X509_VERIFY_PARAM *param, const char *name); -int X509_VERIFY_PARAM_set_flags(X509_VERIFY_PARAM *param, unsigned long flags); -int X509_VERIFY_PARAM_clear_flags(X509_VERIFY_PARAM *param, - unsigned long flags); -unsigned long X509_VERIFY_PARAM_get_flags(X509_VERIFY_PARAM *param); -int X509_VERIFY_PARAM_set_purpose(X509_VERIFY_PARAM *param, int purpose); -int X509_VERIFY_PARAM_set_trust(X509_VERIFY_PARAM *param, int trust); -void X509_VERIFY_PARAM_set_depth(X509_VERIFY_PARAM *param, int depth); -void X509_VERIFY_PARAM_set_time(X509_VERIFY_PARAM *param, time_t t); -int X509_VERIFY_PARAM_add0_policy(X509_VERIFY_PARAM *param, - ASN1_OBJECT *policy); -int X509_VERIFY_PARAM_set1_policies(X509_VERIFY_PARAM *param, - STACK_OF(ASN1_OBJECT) *policies); -int X509_VERIFY_PARAM_get_depth(const X509_VERIFY_PARAM *param); - -int X509_VERIFY_PARAM_add0_table(X509_VERIFY_PARAM *param); -const X509_VERIFY_PARAM *X509_VERIFY_PARAM_lookup(const char *name); -void X509_VERIFY_PARAM_table_cleanup(void); - -int X509_policy_check(X509_POLICY_TREE **ptree, int *pexplicit_policy, - STACK_OF(X509) *certs, - STACK_OF(ASN1_OBJECT) *policy_oids, - unsigned int flags); - -void X509_policy_tree_free(X509_POLICY_TREE *tree); - -int X509_policy_tree_level_count(const X509_POLICY_TREE *tree); -X509_POLICY_LEVEL * - X509_policy_tree_get0_level(const X509_POLICY_TREE *tree, int i); - -STACK_OF(X509_POLICY_NODE) * - X509_policy_tree_get0_policies(const X509_POLICY_TREE *tree); - -STACK_OF(X509_POLICY_NODE) * - X509_policy_tree_get0_user_policies(const X509_POLICY_TREE *tree); - -int X509_policy_level_node_count(X509_POLICY_LEVEL *level); - -X509_POLICY_NODE *X509_policy_level_get0_node(X509_POLICY_LEVEL *level, int i); - -const ASN1_OBJECT *X509_policy_node_get0_policy(const X509_POLICY_NODE *node); - -STACK_OF(POLICYQUALINFO) * - X509_policy_node_get0_qualifiers(const X509_POLICY_NODE *node); -const X509_POLICY_NODE * - X509_policy_node_get0_parent(const X509_POLICY_NODE *node); - -#ifdef __cplusplus -} -#endif -#endif - diff --git a/src/plugins/ftp/operats.h b/src/plugins/ftp/operats.h index fdd975e4..7adce28f 100644 --- a/src/plugins/ftp/operats.h +++ b/src/plugins/ftp/operats.h @@ -501,6 +501,10 @@ class CFTPQueueItemChAttrExplore : public CFTPQueueItem // CFTPQueue // +// These retain durable transfer intent within a predictable per-operation budget. +#define FTP_OPERATION_QUEUE_LIMIT 100000 +#define FTP_DISK_WORK_QUEUE_LIMIT 512 + class CFTPQueue { protected: @@ -533,16 +537,24 @@ class CFTPQueue DWORD LastErrorOccurenceTime; // "time" when the last error occurred (initialized to -1) DWORD LastFoundErrorOccurenceTime; // "time" of the last found item with an error or the "time" before which no item with an error exists anymore + // Transfer planning is durable user intent, so admission rejects expansion + // explicitly instead of silently dropping pending copy or delete items. + int RejectedItemCount; + int HighWaterMark; + public: CFTPQueue(); ~CFTPQueue(); - // adds a new item to the queue; returns TRUE on success + // adds a new item to the queue; returns FALSE for allocation or explicit capacity backpressure BOOL AddItem(CFTPQueueItem* newItem); // returns the number of items in the queue int GetCount(); + // Reports explicit expansion backpressure for the operation UI and diagnostics. + void GetQueueMetrics(int* count, int* rejected, int* highWaterMark); + // helper method: adjusts the counters ExploreAndResolveItemsCount, DoneOrSkippedItemsCount // and WaitingOrProcessingOrDelayedItemsCount according to the item 'item'; 'add' is // TRUE/FALSE when the item 'item' is being added to/removed from the queue @@ -829,6 +841,7 @@ struct CFTPDiskWork int ValidBytesInFlushDataBuffer; int EOLsInFlushDataBuffer; HANDLE WorkFile; + BOOL AppendToFile; // direct staged download: preserve only a verified prefix before the first write // the result of the disk operation is returned in the following variables: DWORD ProblemID; // if not ITEMPR_OK, this is the error that occurred @@ -873,6 +886,8 @@ class CFTPDiskThread : public CThread TIndirectArray FilesToClose; BOOL ShouldTerminate; // TRUE = the thread should terminate BOOL WorkIsInProgress; // TRUE = processing of item Work[0] is in progress + int WorkRejectedCount; // bounded admission protects a slow local disk from remote producers + int WorkHighWaterMark; int NextFileCloseIndex; // sequence number of the next file close operation int DoneFileCloseIndex; // sequence number of the last completed file close (-1 = none closed yet) @@ -889,9 +904,12 @@ class CFTPDiskThread : public CThread void Terminate(); // adds work to the thread, the pointer 'work' must remain valid until the result message is received - // or until CancelWork() is called; returns success + // or until CancelWork() is called; returns FALSE for explicit capacity backpressure BOOL AddWork(CFTPDiskWork* work); + // Reports queue pressure without exposing the work array across its lock. + void GetWorkQueueMetrics(int* count, int* rejected, int* highWaterMark); + // cancels the work 'work' added to the thread; returns TRUE if the work has not started yet // or if it can still be interrupted (it is in progress and after finishing the opposite // action will be performed); if the work is in progress, returns TRUE in 'workIsInProgress' (if not NULL); returns diff --git a/src/plugins/ftp/operats1.cpp b/src/plugins/ftp/operats1.cpp index ee790271..45782d6f 100644 --- a/src/plugins/ftp/operats1.cpp +++ b/src/plugins/ftp/operats1.cpp @@ -732,6 +732,8 @@ CFTPQueue::CFTPQueue() : Items(100, 500) CopyUnknownSizeCountIfUnknownBlockSize = 0; LastErrorOccurenceTime = -1; LastFoundErrorOccurenceTime = -1; + RejectedItemCount = 0; + HighWaterMark = 0; } CFTPQueue::~CFTPQueue() @@ -894,22 +896,46 @@ BOOL CFTPQueue::AddItem(CFTPQueueItem* newItem) HANDLES(EnterCriticalSection(&QueueCritSect)); BOOL ret = TRUE; - Items.Add(newItem); - if (Items.IsGood()) + // A recursive remote listing may otherwise retain one operation object per entry indefinitely. + if (Items.Count >= FTP_OPERATION_QUEUE_LIMIT) { - UpdateCounters(newItem, TRUE); - if (newItem->IsItemInSimpleErrorState()) - newItem->ErrorOccurenceTime = GiveLastErrorOccurenceTime(); + RejectedItemCount++; + TRACE_I("CFTPQueue::AddItem(): bounded operation queue rejected a new item"); + ret = FALSE; } else { - Items.ResetState(); - ret = FALSE; + Items.Add(newItem); + if (Items.IsGood()) + { + UpdateCounters(newItem, TRUE); + if (Items.Count > HighWaterMark) + HighWaterMark = Items.Count; + if (newItem->IsItemInSimpleErrorState()) + newItem->ErrorOccurenceTime = GiveLastErrorOccurenceTime(); + } + else + { + Items.ResetState(); + ret = FALSE; + } } HANDLES(LeaveCriticalSection(&QueueCritSect)); return ret; } +void CFTPQueue::GetQueueMetrics(int* count, int* rejected, int* highWaterMark) +{ + HANDLES(EnterCriticalSection(&QueueCritSect)); + if (count != NULL) + *count = Items.Count; + if (rejected != NULL) + *rejected = RejectedItemCount; + if (highWaterMark != NULL) + *highWaterMark = HighWaterMark; + HANDLES(LeaveCriticalSection(&QueueCritSect)); +} + BOOL CFTPQueue::ReplaceItemWithListOfItems(int itemUID, CFTPQueueItem** items, int itemsCount) { CALL_STACK_MESSAGE3("CFTPQueue::ReplaceItemWithListOfItems(%d, , %d)", itemUID, itemsCount); @@ -920,27 +946,39 @@ BOOL CFTPQueue::ReplaceItemWithListOfItems(int itemUID, CFTPQueueItem** items, i { if (itemsCount > 1) { - Items.Insert(LastFoundIndex + 1, items + 1, itemsCount - 1); - if (Items.IsGood()) + if (itemsCount > FTP_OPERATION_QUEUE_LIMIT || + Items.Count > FTP_OPERATION_QUEUE_LIMIT - (itemsCount - 1)) { - UpdateCounters(Items[LastFoundIndex], FALSE); - delete Items[LastFoundIndex]; - Items[LastFoundIndex] = *items; - LastFoundUID = (*items)->UID; - int i; - for (i = LastFoundIndex; i < LastFoundIndex + itemsCount; i++) + // Expansion must fail atomically so callers retain the original durable item to report it. + RejectedItemCount += itemsCount - 1; + TRACE_I("CFTPQueue::ReplaceItemWithListOfItems(): bounded operation queue rejected expansion"); + } + else + { + Items.Insert(LastFoundIndex + 1, items + 1, itemsCount - 1); + if (Items.IsGood()) { - CFTPQueueItem* item = Items[i]; - UpdateCounters(item, TRUE); - if (item->IsItemInSimpleErrorState()) - item->ErrorOccurenceTime = GiveLastErrorOccurenceTime(); + UpdateCounters(Items[LastFoundIndex], FALSE); + delete Items[LastFoundIndex]; + Items[LastFoundIndex] = *items; + LastFoundUID = (*items)->UID; + int i; + for (i = LastFoundIndex; i < LastFoundIndex + itemsCount; i++) + { + CFTPQueueItem* item = Items[i]; + UpdateCounters(item, TRUE); + if (item->IsItemInSimpleErrorState()) + item->ErrorOccurenceTime = GiveLastErrorOccurenceTime(); + } + ret = TRUE; + if (Items.Count > HighWaterMark) + HighWaterMark = Items.Count; + HandleFirstWaitingItemIndex(TRUE, // instead of searching the array we expect the worst case - an explore item at the first position in the array + LastFoundIndex); } - ret = TRUE; - HandleFirstWaitingItemIndex(TRUE, // instead of searching the array we expect the worst case - an explore item at the first position in the array - LastFoundIndex); + else + Items.ResetState(); } - else - Items.ResetState(); } else { diff --git a/src/plugins/ftp/operats3.cpp b/src/plugins/ftp/operats3.cpp index 42718897..c7005cc3 100644 --- a/src/plugins/ftp/operats3.cpp +++ b/src/plugins/ftp/operats3.cpp @@ -347,6 +347,8 @@ void CFTPWorker::InitDiskWork(DWORD msgID, CFTPDiskWorkType type, const char* pa DiskWork.WorkFile = NULL; } DiskWork.EOLsInFlushDataBuffer = 0; + // Non-direct work must never inherit the direct-download append marker from a reused work record. + DiskWork.AppendToFile = FALSE; } void CFTPWorker::GetListViewData(LVITEM* itemData, char* buf, int bufSize) @@ -1485,7 +1487,7 @@ BOOL CFTPWorker::Write(const char* buffer, int bytesToWrite, DWORD* error, BOOL* } else { - int sentLen = SSLLib.SSL_write(SSLConn, buffer + len, bytesToWrite - len); + int sentLen = SSLWrite(SSLConn, buffer + len, bytesToWrite - len); if (sentLen >= 0) // at least something was successfully sent (or rather accepted by Windows; delivery is uncertain) { len += sentLen; @@ -1497,7 +1499,7 @@ BOOL CFTPWorker::Write(const char* buffer, int bytesToWrite, DWORD* error, BOOL* } else { - DWORD err = SSLtoWS2Error(SSLLib.SSL_get_error(SSLConn, sentLen)); + DWORD err = SSLtoWS2Error(SSLGetError(SSLConn, sentLen)); if (err == WSAEWOULDBLOCK) // nothing else can be sent (Windows no longer has buffer space) { ret = TRUE; @@ -1520,7 +1522,7 @@ BOOL CFTPWorker::Write(const char* buffer, int bytesToWrite, DWORD* error, BOOL* // WARNING: if the TELNET protocol is introduced again, sending IAC+IP must be reworked // before aborting the command in SendFTPCommand() - int sentLen = SSLLib.SSL_write(SSLConn, buffer + len, bytesToWrite - len); + int sentLen = SSLWrite(SSLConn, buffer + len, bytesToWrite - len); if (sentLen > 0) // at least something was successfully sent (or rather accepted by Windows; delivery is uncertain) { len += sentLen; @@ -1532,7 +1534,7 @@ BOOL CFTPWorker::Write(const char* buffer, int bytesToWrite, DWORD* error, BOOL* } else { - DWORD err = SSLtoWS2Error(SSLLib.SSL_get_error(SSLConn, sentLen)); + DWORD err = SSLtoWS2Error(SSLGetError(SSLConn, sentLen)); if (err == WSAEWOULDBLOCK) // nothing else can be sent (Windows no longer has buffer space) { ret = TRUE; @@ -1718,9 +1720,9 @@ void CFTPWorker::ReceiveNetEvent(LPARAM lParam, int index) } else { - if (SSLLib.SSL_pending(SSLConn) > 0) // if the internal SSL buffer is not empty recv() is never called, so no additional FD_READ arrives; post it ourselves, otherwise the transfer stops + if (SSLPending(SSLConn) > 0) // if the internal TLS buffer is not empty recv() is never called, so no additional FD_READ arrives; post it ourselves, otherwise the transfer stops PostMessage(SocketsThread->GetHiddenWindow(), Msg, (WPARAM)Socket, FD_READ); - int len = SSLLib.SSL_read(SSLConn, ReadBytes + ReadBytesCount, ReadBytesAllocatedSize - ReadBytesCount); + int len = SSLRead(SSLConn, ReadBytes + ReadBytesCount, ReadBytesAllocatedSize - ReadBytesCount); if (len >= 0) // we may have read something (0 = connection already closed) { if (len > 0) @@ -1734,7 +1736,7 @@ void CFTPWorker::ReceiveNetEvent(LPARAM lParam, int index) } else { - err = SSLtoWS2Error(SSLLib.SSL_get_error(SSLConn, len)); + err = SSLtoWS2Error(SSLGetError(SSLConn, len)); if (err != WSAEWOULDBLOCK) genEvent = TRUE; // generate an event with the error } @@ -1829,7 +1831,7 @@ void CFTPWorker::ReceiveNetEvent(LPARAM lParam, int index) { while (1) // loop needed because 'send' does not emit FD_WRITE when 'sentLen' < 'bytesToWrite' { - int sentLen = SSLLib.SSL_write(SSLConn, BytesToWrite + BytesToWriteOffset + len, + int sentLen = SSLWrite(SSLConn, BytesToWrite + BytesToWriteOffset + len, BytesToWriteCount - BytesToWriteOffset - len); if (sentLen >= 0) // at least something was successfully sent (or rather accepted by Windows; delivery is uncertain) { @@ -1842,7 +1844,7 @@ void CFTPWorker::ReceiveNetEvent(LPARAM lParam, int index) } else { - err = SSLtoWS2Error(SSLLib.SSL_get_error(SSLConn, sentLen)); + err = SSLtoWS2Error(SSLGetError(SSLConn, sentLen)); if (err == WSAEWOULDBLOCK) // nothing else can be sent (Windows no longer has buffer space) { ret = TRUE; diff --git a/src/plugins/ftp/operats5.cpp b/src/plugins/ftp/operats5.cpp index e26ea690..40adc044 100644 --- a/src/plugins/ftp/operats5.cpp +++ b/src/plugins/ftp/operats5.cpp @@ -858,6 +858,7 @@ void CFTPDiskWork::CopyFrom(CFTPDiskWork* work) ValidBytesInFlushDataBuffer = work->ValidBytesInFlushDataBuffer; EOLsInFlushDataBuffer = work->EOLsInFlushDataBuffer; WorkFile = work->WorkFile; + AppendToFile = work->AppendToFile; ProblemID = work->ProblemID; WinError = work->WinError; @@ -878,6 +879,8 @@ CFTPDiskThread::CFTPDiskThread() : CThread("FTP Disk Thread"), Work(20, 50, dtNo TRACE_E("CFTPDiskThread::CFTPDiskThread(): Unable to create synchronization event object."); ShouldTerminate = FALSE; WorkIsInProgress = FALSE; + WorkRejectedCount = 0; + WorkHighWaterMark = 0; NextFileCloseIndex = 0; DoneFileCloseIndex = -1; FileClosedEvent = HANDLES(CreateEvent(NULL, TRUE, FALSE, NULL)); // manual, nonsignaled @@ -918,18 +921,42 @@ BOOL CFTPDiskThread::AddWork(CFTPDiskWork* work) CALL_STACK_MESSAGE1("CFTPDiskThread::AddWork()"); HANDLES(EnterCriticalSection(&DiskCritSect)); BOOL ret = TRUE; - Work.Add(work); - if (!Work.IsGood()) + // Remote producers must receive backpressure before a slow local disk retains an unbounded backlog. + if (Work.Count >= FTP_DISK_WORK_QUEUE_LIMIT) { - Work.ResetState(); + WorkRejectedCount++; + TRACE_I("CFTPDiskThread::AddWork(): bounded disk queue rejected work"); ret = FALSE; } + else + { + Work.Add(work); + if (!Work.IsGood()) + { + Work.ResetState(); + ret = FALSE; + } + else if (Work.Count > WorkHighWaterMark) + WorkHighWaterMark = Work.Count; + } if (Work.Count == 1) SetEvent(ContEvent); HANDLES(LeaveCriticalSection(&DiskCritSect)); return ret; } +void CFTPDiskThread::GetWorkQueueMetrics(int* count, int* rejected, int* highWaterMark) +{ + HANDLES(EnterCriticalSection(&DiskCritSect)); + if (count != NULL) + *count = Work.Count; + if (rejected != NULL) + *rejected = WorkRejectedCount; + if (highWaterMark != NULL) + *highWaterMark = WorkHighWaterMark; + HANDLES(LeaveCriticalSection(&DiskCritSect)); +} + BOOL CFTPDiskThread::CancelWork(const CFTPDiskWork* work, BOOL* workIsInProgress) { CALL_STACK_MESSAGE1("CFTPDiskThread::CancelWork()"); @@ -1982,11 +2009,29 @@ void DoCreateAndWriteFile(CFTPDiskWork& localWork, BOOL& needCopyBack, BOOL& wor SetFileAttributesUtf8Local(localWork.Name, FILE_ATTRIBUTE_NORMAL); // to allow overwriting a read-only file as well HANDLE f = HANDLES_Q(CreateFileUtf8Local(localWork.Name, GENERIC_WRITE, FILE_SHARE_READ, NULL, - CREATE_ALWAYS, + localWork.AppendToFile ? OPEN_ALWAYS : CREATE_ALWAYS, FILE_FLAG_SEQUENTIAL_SCAN, NULL)); if (f != INVALID_HANDLE_VALUE) { + if (localWork.AppendToFile) + { + LARGE_INTEGER offset; + offset.QuadPart = localWork.WriteOrReadFromOffset.Value; + // The REST offset was derived from durable metadata and must remain the write offset. + LARGE_INTEGER existingSize; + BOOL haveExistingSize = GetFileSizeEx(f, &existingSize); + if (!haveExistingSize || existingSize.QuadPart < offset.QuadPart || + !SetFilePointerEx(f, offset, NULL, FILE_BEGIN)) + { + localWork.State = sqisFailed; + localWork.WinError = !haveExistingSize ? GetLastError() : + (existingSize.QuadPart < offset.QuadPart ? ERROR_INVALID_DATA : GetLastError()); + HANDLES(CloseHandle(f)); + needCopyBack = TRUE; + return; + } + } file = f; localWork.OpenedFile = f; workDone = TRUE; // if cancelled, close the file handle and delete the file diff --git a/src/plugins/ftp/precomp.h b/src/plugins/ftp/precomp.h index 252dbab9..04c26ef0 100644 --- a/src/plugins/ftp/precomp.h +++ b/src/plugins/ftp/precomp.h @@ -155,6 +155,25 @@ static BOOL MoveFileUtf8Local(const char* srcName, const char* destName) return ok; } +static BOOL MoveFileExUtf8Local(const char* srcName, const char* destName, DWORD flags) +{ + BOOL ok = FALSE; + WCHAR* srcNameW = Utf8AllocWide(srcName); + WCHAR* destNameW = Utf8AllocWide(destName); + if (srcNameW == NULL || destNameW == NULL) + { + free(srcNameW); + free(destNameW); + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + // A staged download must become visible as one write-through namespace change. + ok = MoveFileExW(srcNameW, destNameW, flags); + free(srcNameW); + free(destNameW); + return ok; +} + static BOOL CopyFileUtf8Local(const char* existingFileName, const char* newFileName, BOOL failIfExists) { BOOL ok = FALSE; diff --git a/src/plugins/ftp/sockets.cpp b/src/plugins/ftp/sockets.cpp index 3f5d669d..fc090102 100644 --- a/src/plugins/ftp/sockets.cpp +++ b/src/plugins/ftp/sockets.cpp @@ -188,8 +188,8 @@ BOOL CSocket::Shutdown(DWORD* error) { if (SSLConn) { - int err = SSLLib.SSL_shutdown(SSLConn); - SSLLib.SSL_free(SSLConn); + int err = SSLShutdown(SSLConn); + SSLFree(SSLConn); SSLConn = NULL; } if (shutdown(Socket, SD_SEND) != SOCKET_ERROR) @@ -224,8 +224,8 @@ BOOL CSocket::CloseSocket(DWORD* error) { if (SSLConn) { - int err = SSLLib.SSL_shutdown(SSLConn); - SSLLib.SSL_free(SSLConn); + int err = SSLShutdown(SSLConn); + SSLFree(SSLConn); SSLConn = NULL; } if (closesocket(Socket) != SOCKET_ERROR) @@ -1089,7 +1089,7 @@ void CSocket::ProxySendBytes(const char* buf, int bufLen, int index, BOOL* csLef { while (1) // loop necessary because of the 'send' function (if a "would block" error occurs, it reports it only in the next iteration) { - int sentLen = SSLLib.SSL_write(SSLConn, buf + len, bufLen - len); + int sentLen = SSLWrite(SSLConn, buf + len, bufLen - len); if (sentLen > 0) // at least something was sent successfully (or rather accepted by Windows, delivery is uncertain) { len += sentLen; @@ -1100,7 +1100,7 @@ void CSocket::ProxySendBytes(const char* buf, int bufLen, int index, BOOL* csLef { ProxyErrorCode = pecSendingBytes; // if WSAEWOULDBLOCK -> nothing else can be sent (Windows no longer has buffer space), send the rest after FD_WRITE (not implemented yet, hopefully unnecessary) - ProxyWinError = SSLtoWS2Error(SSLLib.SSL_get_error(SSLConn, sentLen)); + ProxyWinError = SSLtoWS2Error(SSLGetError(SSLConn, sentLen)); SocketState = isConnect ? ssConnectFailed : ssListenFailed; HANDLES(LeaveCriticalSection(&SocketCritSect)); if (isConnect) @@ -2228,7 +2228,7 @@ void CSocket::ReceiveNetEvent(LPARAM lParam, int index) { while (1) { - int r = SSLLib.SSL_read(SSLConn, buf, 500); + int r = SSLRead(SSLConn, buf, 500); if (r <= 0) break; // loop until an error or zero (0 = gracefully closed) else @@ -3147,7 +3147,7 @@ CSocketsThread::Body() WaitForSingleObject(CanEndThread, INFINITE); } - // Petr: if this thread used OpenSSL, we must release memory with the following call + // Kept for the historic socket-thread shutdown protocol; SChannel has no thread-local cleanup. SSLThreadLocalCleanup(); TRACE_I("End"); diff --git a/src/plugins/ftp/ssl.cpp b/src/plugins/ftp/ssl.cpp index 37a363c8..e3eabccf 100644 --- a/src/plugins/ftp/ssl.cpp +++ b/src/plugins/ftp/ssl.cpp @@ -4,194 +4,322 @@ #include "precomp.h" -// Error codes returned by SSL_get_error() -#define SSL_ERROR_NONE 0 -#define SSL_ERROR_SSL 1 -#define SSL_ERROR_WANT_READ 2 -#define SSL_ERROR_WANT_WRITE 3 -#define SSL_ERROR_WANT_X509_LOOKUP 4 -#define SSL_ERROR_SYSCALL 5 // look at error stack/return value/errno -#define SSL_ERROR_ZERO_RETURN 6 -#define SSL_ERROR_WANT_CONNECT 7 -#define SSL_ERROR_WANT_ACCEPT 8 +#define MAX_DER_CERT_SIZE 5120 +#define SizeOf(x) (sizeof(x) / sizeof(x[0])) -// Return codes of SSL_read() -#define SSL_NOTHING 1 -#define SSL_READING 2 -#define SSL_WRITING 3 -#define SSL_X509_LOOKUP 4 +#define FTP_CERTIFICATE_EXCEPTION_LIMIT 64 +#define FTP_CERTIFICATE_FINGERPRINT_SIZE 32 +#define FTP_CERTIFICATE_SESSION_EXCEPTION_HOURS 8 +#define FTP_CERTIFICATE_PERSISTENT_EXCEPTION_DAYS 30 -// Transferred from WinSocket -// #define WSAECONNCLOSED (WSABASEERR + 10000) +static const char* FTP_CERTIFICATE_EXCEPTIONS_KEY = "Certificate Exceptions"; +static const char* FTP_CERTIFICATE_EXCEPTION_HOST = "Host"; +static const char* FTP_CERTIFICATE_EXCEPTION_PORT = "Port"; +static const char* FTP_CERTIFICATE_EXCEPTION_SPKI = "SPKI SHA-256"; +static const char* FTP_CERTIFICATE_EXCEPTION_CERTIFICATE = "Certificate SHA-256"; +static const char* FTP_CERTIFICATE_EXCEPTION_SCOPE = "Scope"; +static const char* FTP_CERTIFICATE_EXCEPTION_EXPIRY = "Expiry"; -#define SSL_CTRL_OPTIONS 32 +struct CCertificateExceptionRecord +{ + char Host[HOST_MAX_SIZE]; + unsigned short Port; + BYTE SPKIFingerprint[FTP_CERTIFICATE_FINGERPRINT_SIZE]; + BYTE CertificateFingerprint[FTP_CERTIFICATE_FINGERPRINT_SIZE]; + FILETIME ExpiresAt; + DWORD Scope; +}; + +static bool IsExpired(const FILETIME& expiresAt) +{ + FILETIME now; + GetSystemTimeAsFileTime(&now); + ULARGE_INTEGER nowValue = {}; + ULARGE_INTEGER expiryValue = {}; + nowValue.LowPart = now.dwLowDateTime; + nowValue.HighPart = now.dwHighDateTime; + expiryValue.LowPart = expiresAt.dwLowDateTime; + expiryValue.HighPart = expiresAt.dwHighDateTime; + return expiryValue.QuadPart <= nowValue.QuadPart; +} -/* SSL_OP_ALL: various bug workarounds that should be rather harmless. - * This used to be 0x000FFFFFL before 0.9.7. */ -#define SSL_OP_ALL 0x80000BFFL +static FILETIME CertificateExceptionExpiry(CCertificateExceptionScope scope) +{ + FILETIME now; + GetSystemTimeAsFileTime(&now); + ULARGE_INTEGER expiry = {}; + expiry.LowPart = now.dwLowDateTime; + expiry.HighPart = now.dwHighDateTime; + const ULONGLONG hours = scope == cesPersistent ? FTP_CERTIFICATE_PERSISTENT_EXCEPTION_DAYS * 24ULL + : FTP_CERTIFICATE_SESSION_EXCEPTION_HOURS; + expiry.QuadPart += hours * 60ULL * 60ULL * 10000000ULL; + FILETIME result = {expiry.LowPart, expiry.HighPart}; + return result; +} -#define SSL_OP_NO_SSLv2 0x01000000L +static bool GetCertificateFingerprints(const BYTE* der, int derLength, BYTE* spkiFingerprint, + BYTE* certificateFingerprint) +{ + if (der == NULL || derLength <= 0) + return false; -#define MAX_DER_CERT_SIZE 5120 // Is 5KB enough? + PCCERT_CONTEXT certificate = CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, der, derLength); + if (certificate == NULL) + return false; -#define SizeOf(x) (sizeof(x) / sizeof(x[0])) + DWORD fingerprintLength = FTP_CERTIFICATE_FINGERPRINT_SIZE; + bool ok = CryptHashCertificate(0, CALG_SHA_256, 0, der, derLength, certificateFingerprint, &fingerprintLength) && + fingerprintLength == FTP_CERTIFICATE_FINGERPRINT_SIZE; + fingerprintLength = FTP_CERTIFICATE_FINGERPRINT_SIZE; + ok = ok && CryptHashPublicKeyInfo(0, CALG_SHA_256, 0, X509_ASN_ENCODING, + &certificate->pCertInfo->SubjectPublicKeyInfo, spkiFingerprint, + &fingerprintLength) && + fingerprintLength == FTP_CERTIFICATE_FINGERPRINT_SIZE; + CertFreeCertificateContext(certificate); + return ok; +} -#ifndef CERT_VERIFY_REV_SERVER_OCSP_FLAG -#define CERT_VERIFY_REV_SERVER_OCSP_FLAG 8 // defined in SDK's newer than 2003 -#endif +class CCertificateExceptionStore +{ +public: + CCertificateExceptionStore() : Count(0) + { + // Certificate decisions can be consulted by connection and operation workers concurrently. + HANDLES(InitializeCriticalSection(&CriticalSection)); + } -// Brought from ssl.h -#define SSL_ST_CONNECT 0x1000 -#define SSL_ST_ACCEPT 0x2000 -#define SSL_ST_MASK 0x0FFF -#define SSL_ST_INIT (SSL_ST_CONNECT | SSL_ST_ACCEPT) -#define SSL_ST_BEFORE 0x4000 -#define SSL_ST_OK 0x03 -#define SSL_ST_RENEGOTIATE (0x04 | SSL_ST_INIT) - -#define SSL_CB_LOOP 0x01 -#define SSL_CB_EXIT 0x02 -#define SSL_CB_READ 0x04 -#define SSL_CB_WRITE 0x08 -#define SSL_CB_ALERT 0x4000 /* used in callback */ -#define SSL_CB_READ_ALERT (SSL_CB_ALERT | SSL_CB_READ) -#define SSL_CB_WRITE_ALERT (SSL_CB_ALERT | SSL_CB_WRITE) -#define SSL_CB_ACCEPT_LOOP (SSL_ST_ACCEPT | SSL_CB_LOOP) -#define SSL_CB_ACCEPT_EXIT (SSL_ST_ACCEPT | SSL_CB_EXIT) -#define SSL_CB_CONNECT_LOOP (SSL_ST_CONNECT | SSL_CB_LOOP) -#define SSL_CB_CONNECT_EXIT (SSL_ST_CONNECT | SSL_CB_EXIT) -#define SSL_CB_HANDSHAKE_START 0x10 -#define SSL_CB_HANDSHAKE_DONE 0x20 - -sSSLLib SSLLib; + ~CCertificateExceptionStore() { HANDLES(DeleteCriticalSection(&CriticalSection)); } -static bool bSSLInited = false; + bool Remember(const char* host, unsigned short port, const BYTE* der, int derLength, + CCertificateExceptionScope scope) + { + CCertificateExceptionRecord record = {}; + if (host == NULL || host[0] == 0 || !GetCertificateFingerprints(der, derLength, record.SPKIFingerprint, + record.CertificateFingerprint)) + return false; + + lstrcpynA(record.Host, host, SizeOf(record.Host)); + record.Port = port; + record.Scope = scope; + record.ExpiresAt = CertificateExceptionExpiry(scope); + + HANDLES(EnterCriticalSection(&CriticalSection)); + int replace = -1; + for (int i = 0; i < Count; ++i) + { + if (_stricmp(Records[i].Host, record.Host) == 0 && Records[i].Port == record.Port && + memcmp(Records[i].CertificateFingerprint, record.CertificateFingerprint, + FTP_CERTIFICATE_FINGERPRINT_SIZE) == 0) + { + replace = i; + break; + } + } + if (replace == -1) + { + // A fixed store prevents repeated hostile certificates from growing configuration without bound. + replace = Count < FTP_CERTIFICATE_EXCEPTION_LIMIT ? Count++ : 0; + } + Records[replace] = record; + HANDLES(LeaveCriticalSection(&CriticalSection)); + return true; + } + + bool IsAccepted(const char* host, unsigned short port, const BYTE* der, int derLength, bool* endpointKnown) + { + BYTE spkiFingerprint[FTP_CERTIFICATE_FINGERPRINT_SIZE]; + BYTE certificateFingerprint[FTP_CERTIFICATE_FINGERPRINT_SIZE]; + if (endpointKnown) + *endpointKnown = false; + if (host == NULL || host[0] == 0 || + !GetCertificateFingerprints(der, derLength, spkiFingerprint, certificateFingerprint)) + return false; + + bool accepted = false; + HANDLES(EnterCriticalSection(&CriticalSection)); + for (int i = Count - 1; i >= 0;) + { + if (IsExpired(Records[i].ExpiresAt)) + { + Records[i] = Records[--Count]; + continue; + } + if (_stricmp(Records[i].Host, host) == 0 && Records[i].Port == port) + { + if (endpointKnown) + *endpointKnown = true; + // Both pins must match: preserving an SPKI across renewal does not silently approve a new certificate. + if (memcmp(Records[i].SPKIFingerprint, spkiFingerprint, FTP_CERTIFICATE_FINGERPRINT_SIZE) == 0 && + memcmp(Records[i].CertificateFingerprint, certificateFingerprint, + FTP_CERTIFICATE_FINGERPRINT_SIZE) == 0) + { + accepted = true; + break; + } + } + --i; + } + HANDLES(LeaveCriticalSection(&CriticalSection)); + return accepted; + } -BYTE hex(char c) + void Load(HKEY regKey, CSalamanderRegistryAbstract* registry) + { + ClearPersistent(); + HKEY exceptionsKey; + if (!registry->OpenKey(regKey, FTP_CERTIFICATE_EXCEPTIONS_KEY, exceptionsKey)) + return; + + for (int i = 1; i <= FTP_CERTIFICATE_EXCEPTION_LIMIT; ++i) + { + char keyName[12]; + HKEY entryKey; + _itoa(i, keyName, 10); + if (!registry->OpenKey(exceptionsKey, keyName, entryKey)) + continue; + + CCertificateExceptionRecord record = {}; + DWORD port = 0; + bool valid = registry->GetValue(entryKey, FTP_CERTIFICATE_EXCEPTION_HOST, REG_SZ, record.Host, + SizeOf(record.Host)) && + registry->GetValue(entryKey, FTP_CERTIFICATE_EXCEPTION_PORT, REG_DWORD, &port, sizeof(port)) && + registry->GetValue(entryKey, FTP_CERTIFICATE_EXCEPTION_SPKI, REG_BINARY, record.SPKIFingerprint, + sizeof(record.SPKIFingerprint)) && + registry->GetValue(entryKey, FTP_CERTIFICATE_EXCEPTION_CERTIFICATE, REG_BINARY, + record.CertificateFingerprint, sizeof(record.CertificateFingerprint)) && + registry->GetValue(entryKey, FTP_CERTIFICATE_EXCEPTION_SCOPE, REG_DWORD, &record.Scope, + sizeof(record.Scope)) && + registry->GetValue(entryKey, FTP_CERTIFICATE_EXCEPTION_EXPIRY, REG_BINARY, &record.ExpiresAt, + sizeof(record.ExpiresAt)); + registry->CloseKey(entryKey); + record.Port = (unsigned short)port; + if (valid && record.Host[0] != 0 && port <= USHRT_MAX && record.Scope == cesPersistent && !IsExpired(record.ExpiresAt)) + RememberLoaded(record); + } + registry->CloseKey(exceptionsKey); + } + + void Save(HKEY regKey, CSalamanderRegistryAbstract* registry) + { + HKEY exceptionsKey; + if (!registry->CreateKey(regKey, FTP_CERTIFICATE_EXCEPTIONS_KEY, exceptionsKey)) + return; + + registry->ClearKey(exceptionsKey); + HANDLES(EnterCriticalSection(&CriticalSection)); + int saved = 0; + for (int i = 0; i < Count; ++i) + { + const CCertificateExceptionRecord& record = Records[i]; + if (record.Scope != cesPersistent || IsExpired(record.ExpiresAt)) + continue; + char keyName[12]; + HKEY entryKey; + _itoa(++saved, keyName, 10); + if (registry->CreateKey(exceptionsKey, keyName, entryKey)) + { + DWORD port = record.Port; + registry->SetValue(entryKey, FTP_CERTIFICATE_EXCEPTION_HOST, REG_SZ, record.Host, -1); + registry->SetValue(entryKey, FTP_CERTIFICATE_EXCEPTION_PORT, REG_DWORD, &port, sizeof(port)); + registry->SetValue(entryKey, FTP_CERTIFICATE_EXCEPTION_SPKI, REG_BINARY, record.SPKIFingerprint, + sizeof(record.SPKIFingerprint)); + registry->SetValue(entryKey, FTP_CERTIFICATE_EXCEPTION_CERTIFICATE, REG_BINARY, + record.CertificateFingerprint, sizeof(record.CertificateFingerprint)); + registry->SetValue(entryKey, FTP_CERTIFICATE_EXCEPTION_SCOPE, REG_DWORD, &record.Scope, + sizeof(record.Scope)); + registry->SetValue(entryKey, FTP_CERTIFICATE_EXCEPTION_EXPIRY, REG_BINARY, &record.ExpiresAt, + sizeof(record.ExpiresAt)); + registry->CloseKey(entryKey); + } + } + HANDLES(LeaveCriticalSection(&CriticalSection)); + registry->CloseKey(exceptionsKey); + } + +private: + void ClearPersistent() + { + // A configuration reload must not retain a remembered exception deleted from the profile. + HANDLES(EnterCriticalSection(&CriticalSection)); + for (int i = Count - 1; i >= 0; --i) + { + if (Records[i].Scope == cesPersistent) + Records[i] = Records[--Count]; + } + HANDLES(LeaveCriticalSection(&CriticalSection)); + } + + void RememberLoaded(const CCertificateExceptionRecord& record) + { + HANDLES(EnterCriticalSection(&CriticalSection)); + if (Count < FTP_CERTIFICATE_EXCEPTION_LIMIT) + Records[Count++] = record; + HANDLES(LeaveCriticalSection(&CriticalSection)); + } + + CRITICAL_SECTION CriticalSection; + CCertificateExceptionRecord Records[FTP_CERTIFICATE_EXCEPTION_LIMIT]; + int Count; +}; + +static CCertificateExceptionStore CertificateExceptions; + +void LoadCertificateExceptions(HKEY regKey, CSalamanderRegistryAbstract* registry) { - if (c >= '0' && c <= '9') - return c - '0'; - if (c >= 'A' && c <= 'F') - return c - 'A' + 10; - if (c >= 'a' && c <= 'f') - return c - 'a' + 10; - return 0; + CertificateExceptions.Load(regKey, registry); } -#define IMP_SYMBOL(name) {(void**)&SSLLib.name, #name}, - -typedef struct _SymbolInfo -{ - void** Addr; - const char* Name; -} TSymbolInfo, *PSymbolInfo; - -static TSymbolInfo SSLSymbols[] = { - IMP_SYMBOL(SSL_get_error) - IMP_SYMBOL(SSL_write) - IMP_SYMBOL(SSL_read) - IMP_SYMBOL(SSL_library_init) - IMP_SYMBOL(SSL_load_error_strings) - // IMP_SYMBOL(SSLv3_client_method) - IMP_SYMBOL(SSLv23_client_method) - IMP_SYMBOL(SSL_CTX_new) - IMP_SYMBOL(SSL_CTX_free) - IMP_SYMBOL(SSL_CTX_ctrl) - IMP_SYMBOL(SSL_new) - IMP_SYMBOL(SSL_free) - IMP_SYMBOL(SSL_set_fd) - IMP_SYMBOL(SSL_connect) - IMP_SYMBOL(SSL_shutdown) - IMP_SYMBOL(SSL_get_current_cipher) - IMP_SYMBOL(SSL_CIPHER_get_version) - IMP_SYMBOL(SSL_CIPHER_get_bits) - IMP_SYMBOL(SSL_CIPHER_get_name) - IMP_SYMBOL(SSL_get_verify_result) - IMP_SYMBOL(SSL_set_verify) - IMP_SYMBOL(SSL_pending) - IMP_SYMBOL(SSL_get_peer_certificate) - IMP_SYMBOL(SSL_get_peer_cert_chain) - IMP_SYMBOL(SSL_COMP_get_compression_methods) - IMP_SYMBOL(SSL_get1_session) - IMP_SYMBOL(SSL_set_session) - IMP_SYMBOL(SSL_SESSION_free) - IMP_SYMBOL(SSL_ctrl) -#ifdef _DEBUG - IMP_SYMBOL(SSL_state_string_long) - IMP_SYMBOL(SSL_alert_type_string_long) - IMP_SYMBOL(SSL_alert_desc_string_long) - IMP_SYMBOL(SSL_set_info_callback) -#endif - {NULL, NULL}}; +void SaveCertificateExceptions(HKEY regKey, CSalamanderRegistryAbstract* registry) +{ + CertificateExceptions.Save(regKey, registry); +} -static TSymbolInfo SSLUtilSymbols[] = { - IMP_SYMBOL(SSLeay_version) -#ifdef _DEBUG - IMP_SYMBOL(X509_verify_cert_error_string) +#ifndef SP_PROT_TLS1_2_CLIENT +#define SP_PROT_TLS1_2_CLIENT 0x00000800 #endif - IMP_SYMBOL(X509_NAME_oneline) - IMP_SYMBOL(i2d_X509) - IMP_SYMBOL(X509_free) - IMP_SYMBOL(PKCS7_new) - IMP_SYMBOL(PKCS7_SIGNED_new) - IMP_SYMBOL(OBJ_nid2obj) - IMP_SYMBOL(ASN1_INTEGER_set) - IMP_SYMBOL(PKCS7_free) - IMP_SYMBOL(i2d_PKCS7) - IMP_SYMBOL(sk_new_null) - // IMP_SYMBOL(BIO_s_mem) - // IMP_SYMBOL(BIO_new) - // IMP_SYMBOL(BIO_free) - // IMP_SYMBOL(BIO_read) - // IMP_SYMBOL(ERR_clear_error) - IMP_SYMBOL(ERR_error_string) - // IMP_SYMBOL(ERR_print_errors) - IMP_SYMBOL(ERR_get_error) - // IMP_SYMBOL(ERR_get_state) - IMP_SYMBOL(ERR_remove_state) - IMP_SYMBOL(ENGINE_cleanup) - IMP_SYMBOL(CONF_modules_finish) - IMP_SYMBOL(CONF_modules_free) - IMP_SYMBOL(CONF_modules_unload) - IMP_SYMBOL(ERR_free_strings) - IMP_SYMBOL(sk_free) - IMP_SYMBOL(EVP_cleanup) - IMP_SYMBOL(CRYPTO_cleanup_all_ex_data) - IMP_SYMBOL(CRYPTO_num_locks) - IMP_SYMBOL(CRYPTO_set_locking_callback) - IMP_SYMBOL(RAND_seed){NULL, NULL}}; - -static bool LoadSymbols(LPCTSTR libName, LPCTSTR altLibName, HINSTANCE& hLib, PSymbolInfo pSymbols, int logUID) -{ - hLib = LoadLibrary(libName); - if (!hLib && altLibName) - { - hLib = LoadLibrary(altLibName); - if (hLib) - libName = altLibName; - } - if (hLib) - SalamanderDebug->AddModuleWithPossibleMemoryLeaks(libName); - char buf[MAX_PATH + 400]; - if (!hLib) - { - sprintf(buf, "Err %u: Unable to load %s\r\n", GetLastError(), libName); - Logs.LogMessage(logUID, buf, -1); - return false; - } - while (pSymbols->Addr) +#ifndef SP_PROT_TLS1_3_CLIENT +#define SP_PROT_TLS1_3_CLIENT 0x00002000 +#endif + +struct CSchannelConnection +{ + CtxtHandle Context; + bool ContextValid; + SOCKET Socket; + SecPkgContext_StreamSizes StreamSizes; + BYTE* Encrypted; + DWORD EncryptedLength; + DWORD EncryptedCapacity; + BYTE* Decrypted; + DWORD DecryptedLength; + BYTE* PendingWrite; + DWORD PendingWriteLength; + DWORD PendingWriteOffset; + int PendingPlaintextLength; + int LastError; +}; + +static CredHandle SChannelCredentials; +static bool bSSLInited = false; + +static bool EnsureBuffer(BYTE*& buffer, DWORD& capacity, DWORD required) +{ + if (required <= capacity) + return true; + DWORD newCapacity = capacity == 0 ? 32768 : capacity; + while (newCapacity < required) { - *pSymbols->Addr = GetProcAddress(hLib, pSymbols->Name); - if (!*pSymbols->Addr) - { - sprintf(buf, "Err %u: Unable to find %s in %s\r\n", GetLastError(), pSymbols->Name, libName); - Logs.LogMessage(logUID, buf, -1); + if (newCapacity > 1024 * 1024) return false; - } - pSymbols++; + newCapacity *= 2; } + BYTE* newBuffer = (BYTE*)realloc(buffer, newCapacity); + if (newBuffer == NULL) + return false; + buffer = newBuffer; + capacity = newCapacity; return true; -} /* LoadSymbols */ +} static void AddNewLine(char*& buf, int& maxlen) { @@ -207,779 +335,792 @@ static void AddNewLine(char*& buf, int& maxlen) buf++; } } -} /* AddNewLine */ +} static bool CheckCertificate(BYTE* pCert, int certLen, LPTSTR buf, int maxlen, const char* host, LPCWSTR hostW) { - CALL_STACK_MESSAGE5("CheckCertificate(0x%p, %d, %s, %ls)", pCert, certLen, host, hostW); - CERT_CHAIN_PARA ChainPara; - PCCERT_CHAIN_CONTEXT pChainContext = NULL; - CERT_CHAIN_POLICY_PARA PolicyPara; - CERT_CHAIN_POLICY_STATUS PolicyStatus; - HTTPSPolicyCallbackData polHttps; - PCCERT_CONTEXT pCertContext = CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, pCert, certLen); - WCHAR PeerName[128]; + CERT_CHAIN_PARA chainPara; + PCCERT_CHAIN_CONTEXT chainContext = NULL; + CERT_CHAIN_POLICY_PARA policyPara; + CERT_CHAIN_POLICY_STATUS policyStatus; + HTTPSPolicyCallbackData httpsPolicy; + PCCERT_CONTEXT certContext = CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, pCert, certLen); + WCHAR peerName[256] = {}; if (maxlen > 0) buf[0] = 0; - if (!pCertContext) + if (!certContext) { lstrcpyn(buf, SalamanderGeneral->GetErrorText(GetLastError()), maxlen); return false; } bool checkRevocation = true; - CHECK_CERT_AGAIN: - - memset(&ChainPara, 0, sizeof(ChainPara)); - ChainPara.cbSize = sizeof(ChainPara); - if (!CertGetCertificateChain(NULL, - pCertContext, - NULL, - NULL, - &ChainPara, - (checkRevocation ? CERT_CHAIN_REVOCATION_CHECK_CHAIN : 0) | // Revocation checking is done on all of the certificates in every chain. - CERT_CHAIN_CACHE_END_CERT | // When this flag is set, the end certificate is cached, which might speed up the chain-building process. By default, the end certificate is not cached, and it would need to be verified each time a chain is built for it. - CERT_CHAIN_DISABLE_AUTH_ROOT_AUTO_UPDATE, // Inhibits the auto update of third-party roots from the Windows Update Web Server - NULL, - &pChainContext)) + memset(&chainPara, 0, sizeof(chainPara)); + chainPara.cbSize = sizeof(chainPara); + if (!CertGetCertificateChain(NULL, certContext, NULL, NULL, &chainPara, + (checkRevocation ? CERT_CHAIN_REVOCATION_CHECK_CHAIN : 0) | + CERT_CHAIN_CACHE_END_CERT | + CERT_CHAIN_DISABLE_AUTH_ROOT_AUTO_UPDATE, + NULL, &chainContext)) { lstrcpyn(buf, SalamanderGeneral->GetErrorText(GetLastError()), maxlen); - CertFreeCertificateContext(pCertContext); + CertFreeCertificateContext(certContext); return false; } - memset(&polHttps, 0, sizeof(HTTPSPolicyCallbackData)); - polHttps.cbStruct = sizeof(HTTPSPolicyCallbackData); - polHttps.dwAuthType = AUTHTYPE_SERVER; - polHttps.fdwChecks = 0; - if (host != NULL) - MultiByteToWideChar(CP_ACP, 0, host, -1, PeerName, SizeOf(PeerName)); - else - { - if (hostW != NULL) - lstrcpynW(PeerName, hostW, SizeOf(PeerName)); - } - polHttps.pwszServerName = PeerName; - - memset(&PolicyPara, 0, sizeof(PolicyPara)); - PolicyPara.cbSize = sizeof(PolicyPara); - PolicyPara.pvExtraPolicyPara = &polHttps; - - memset(&PolicyStatus, 0, sizeof(PolicyStatus)); - PolicyStatus.cbSize = sizeof(PolicyStatus); - if (!CertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_SSL, - pChainContext, &PolicyPara, &PolicyStatus)) + memset(&httpsPolicy, 0, sizeof(httpsPolicy)); + httpsPolicy.cbStruct = sizeof(httpsPolicy); + httpsPolicy.dwAuthType = AUTHTYPE_SERVER; + if (host != NULL) + MultiByteToWideChar(CP_ACP, 0, host, -1, peerName, SizeOf(peerName)); + else if (hostW != NULL) + lstrcpynW(peerName, hostW, SizeOf(peerName)); + httpsPolicy.pwszServerName = peerName; + + memset(&policyPara, 0, sizeof(policyPara)); + policyPara.cbSize = sizeof(policyPara); + policyPara.pvExtraPolicyPara = &httpsPolicy; + memset(&policyStatus, 0, sizeof(policyStatus)); + policyStatus.cbSize = sizeof(policyStatus); + + if (!CertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_SSL, chainContext, &policyPara, &policyStatus)) { lstrcpyn(buf, SalamanderGeneral->GetErrorText(GetLastError()), maxlen); - CertFreeCertificateChain(pChainContext); - CertFreeCertificateContext(pCertContext); + CertFreeCertificateChain(chainContext); + CertFreeCertificateContext(certContext); return false; } bool ok = true; - - if (PolicyStatus.dwError) + if (policyStatus.dwError) { - if (checkRevocation && PolicyStatus.dwError == CRYPT_E_NO_REVOCATION_CHECK) - { // The revocation function was unable to check revocation for the certificate. OK, so try it without checking revocation (MS probably also skips it because they accept the same certificate at the same time on the same machine). - CertFreeCertificateChain(pChainContext); - pChainContext = NULL; + if (checkRevocation && policyStatus.dwError == CRYPT_E_NO_REVOCATION_CHECK) + { + CertFreeCertificateChain(chainContext); + chainContext = NULL; checkRevocation = false; goto CHECK_CERT_AGAIN; } - else - { - ok = false; - lstrcpyn(buf, SalamanderGeneral->GetErrorText(PolicyStatus.dwError), maxlen); - } + ok = false; + lstrcpyn(buf, SalamanderGeneral->GetErrorText(policyStatus.dwError), maxlen); } - int res = CertVerifyTimeValidity(NULL, pCertContext->pCertInfo); - if (res != 0) + int timeResult = CertVerifyTimeValidity(NULL, certContext->pCertInfo); + if (timeResult != 0) { ok = false; AddNewLine(buf, maxlen); - lstrcpyn(buf, LoadStr((res < 0) ? IDS_SSL_ERR_NOTYETVALID : IDS_SSL_ERR_EXPIRED), maxlen); - } - // Whatever flag is used, revokation check fails on most servers :-/ - /*int i; - for (i = 0; i < pChainContext->cChain; i++) - { - CERT_REVOCATION_STATUS revStat; - - revStat.cbSize = sizeof(CERT_REVOCATION_STATUS); - if (!CertVerifyRevocation(X509_ASN_ENCODING, - CERT_CONTEXT_REVOCATION_TYPE, - 1, - (void**)&pChainContext->rgpChain[i]->rgpElement[0]->pCertContext, - //CERT_VERIFY_CACHE_ONLY_BASED_REVOCATION/ - CERT_VERIFY_REV_SERVER_OCSP_FLAG - //CERT_VERIFY_REV_CHAIN_FLAG, - NULL, - &revStat)) - { - ok = false; - AddNewLine(buf, maxlen); - lstrcpyn(buf, SalamanderGeneral->GetErrorText(revStat.dwError), maxlen); - break; - } - }*/ - - CertFreeCertificateChain(pChainContext); - CertFreeCertificateContext(pCertContext); + lstrcpyn(buf, LoadStr(timeResult < 0 ? IDS_SSL_ERR_NOTYETVALID : IDS_SSL_ERR_EXPIRED), maxlen); + } + CertFreeCertificateChain(chainContext); + CertFreeCertificateContext(certContext); return ok; -} /* CheckCertificate */ +} -static bool ViewCertificate(HWND hParent, BYTE* pCertData, int CertDataLen, BYTE* pPKCS7Cert, int PKCS7CertLen, LPCWSTR pTitle) +static bool ViewCertificate(HWND hParent, BYTE* certData, int certDataLen, BYTE* pkcs7Data, int pkcs7DataLen, LPCWSTR title) { - CALL_STACK_MESSAGE5("ViewCertificate(0x%p, 0x%p, %d, %ls)", hParent, pCertData, CertDataLen, pTitle); - CRYPTUI_VIEWCERTIFICATE_STRUCTW cvi; - PCCERT_CONTEXT pCertContext = CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, pCertData, CertDataLen); - if (!pCertContext) + CRYPTUI_VIEWCERTIFICATE_STRUCTW view = {}; + PCCERT_CONTEXT cert = CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, certData, certDataLen); + if (!cert) { - const char* errStr = SalamanderGeneral->GetErrorText(GetLastError()); - SalamanderGeneral->SalMessageBox(hParent, errStr, LoadStr(IDS_FTPPLUGINTITLE), MB_OK | MB_ICONSTOP); + SalamanderGeneral->SalMessageBox(hParent, SalamanderGeneral->GetErrorText(GetLastError()), LoadStr(IDS_FTPPLUGINTITLE), MB_OK | MB_ICONSTOP); return false; } - - // Put all other certificates provided by the server, possibly untrusted, to hCertStore - CRYPT_INTEGER_BLOB blob = {(DWORD)PKCS7CertLen, pPKCS7Cert}; - HCERTSTORE hCertStore = CertOpenStore(CERT_STORE_PROV_PKCS7, PKCS_7_ASN_ENCODING, NULL, 0, &blob); - - memset(&cvi, 0, sizeof(cvi)); - cvi.dwSize = sizeof(cvi); - cvi.hwndParent = hParent; - cvi.dwFlags = CRYPTUI_DISABLE_EDITPROPERTIES; - cvi.szTitle = pTitle; - cvi.pCertContext = pCertContext; - if (hCertStore) + HCERTSTORE store = NULL; + if (pkcs7Data != NULL && pkcs7DataLen > 0) + { + CRYPT_INTEGER_BLOB blob = {(DWORD)pkcs7DataLen, pkcs7Data}; + store = CertOpenStore(CERT_STORE_PROV_PKCS7, PKCS_7_ASN_ENCODING, NULL, 0, &blob); + } + view.dwSize = sizeof(view); + view.hwndParent = hParent; + view.dwFlags = CRYPTUI_DISABLE_EDITPROPERTIES; + view.szTitle = title; + view.pCertContext = cert; + if (store) { - cvi.cStores = 1; - cvi.rghStores = &hCertStore; + view.cStores = 1; + view.rghStores = &store; } + if (!CryptUIDlgViewCertificateW(&view, NULL) && GetLastError() != ERROR_CANCELLED) + SalamanderGeneral->SalMessageBox(hParent, SalamanderGeneral->GetErrorText(GetLastError()), LoadStr(IDS_FTPPLUGINTITLE), MB_OK | MB_ICONSTOP); + if (store) + CertCloseStore(store, CERT_CLOSE_STORE_FORCE_FLAG); + CertFreeCertificateContext(cert); + return true; +} - if (!CryptUIDlgViewCertificateW(&cvi, NULL)) +static void SecurityStatusText(SECURITY_STATUS status, char* buffer, int bufferSize) +{ + if (bufferSize <= 0) + return; + DWORD chars = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, status, 0, buffer, bufferSize, NULL); + if (chars == 0) + _snprintf_s(buffer, bufferSize, _TRUNCATE, "SChannel status 0x%08lX", (DWORD)status); +} + +static bool SendBlocking(SOCKET socket, const BYTE* buffer, DWORD length, DWORD* error) +{ + DWORD sentTotal = 0; + while (sentTotal < length) { - DWORD err = GetLastError(); - if (ERROR_CANCELLED != err) + int sent = send(socket, (const char*)buffer + sentTotal, (int)(length - sentTotal), 0); + if (sent == SOCKET_ERROR) { - const char* errStr = SalamanderGeneral->GetErrorText(err); - CertFreeCertificateContext(pCertContext); - if (hCertStore) - CertCloseStore(hCertStore, CERT_CLOSE_STORE_FORCE_FLAG); - SalamanderGeneral->SalMessageBox(hParent, errStr, LoadStr(IDS_FTPPLUGINTITLE), MB_OK | MB_ICONSTOP); + if (error) + *error = WSAGetLastError(); return false; } + if (sent == 0) + { + if (error) + *error = WSAECONNRESET; + return false; + } + sentTotal += sent; } - if (hCertStore) - CertCloseStore(hCertStore, CERT_CLOSE_STORE_FORCE_FLAG); - CertFreeCertificateContext(pCertContext); return true; -} /* ViewCertificate */ +} -#ifdef _DEBUG -static void InfoCallback(const SSL* s, int where, int ret) +static bool SendSecurityToken(SOCKET socket, SecBuffer* token, DWORD* error) { - const char* str; - int w; - char buf[512]; - - w = where & ~SSL_ST_MASK; - buf[0] = 0; + bool ok = true; + if (token->pvBuffer != NULL && token->cbBuffer != 0) + ok = SendBlocking(socket, (const BYTE*)token->pvBuffer, token->cbBuffer, error); + if (token->pvBuffer != NULL) + FreeContextBuffer(token->pvBuffer); + token->pvBuffer = NULL; + token->cbBuffer = 0; + return ok; +} - if (w & SSL_ST_CONNECT) - str = "SSL_connect"; - else if (w & SSL_ST_ACCEPT) - str = "SSL_accept"; - else - str = "undefined"; +static bool CompleteHandshake(CSchannelConnection* connection, LPCSTR target, DWORD* socketError, SECURITY_STATUS* securityError) +{ + DWORD attributes = 0; + TimeStamp expiry; + SecBuffer output = {0, SECBUFFER_TOKEN, NULL}; + SecBufferDesc outputDesc = {SECBUFFER_VERSION, 1, &output}; + DWORD requestFlags = ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONFIDENTIALITY | + ISC_REQ_EXTENDED_ERROR | ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM | ISC_REQ_MANUAL_CRED_VALIDATION; + SECURITY_STATUS status = InitializeSecurityContextA(&SChannelCredentials, NULL, (SEC_CHAR*)target, + requestFlags, 0, SECURITY_NATIVE_DREP, NULL, 0, + &connection->Context, &outputDesc, &attributes, &expiry); + connection->ContextValid = status == SEC_I_CONTINUE_NEEDED || status == SEC_E_OK; + if (status != SEC_I_CONTINUE_NEEDED && status != SEC_E_OK) + { + *securityError = status; + if (output.pvBuffer) + FreeContextBuffer(output.pvBuffer); + return false; + } + if (!SendSecurityToken(connection->Socket, &output, socketError)) + return false; - if (where & SSL_CB_LOOP) + while (status == SEC_I_CONTINUE_NEEDED || status == SEC_E_INCOMPLETE_MESSAGE) { - sprintf(buf, "%s:%s\n", str, SSLLib.SSL_state_string_long(s)); + if (status == SEC_E_INCOMPLETE_MESSAGE || connection->EncryptedLength == 0) + { + if (!EnsureBuffer(connection->Encrypted, connection->EncryptedCapacity, connection->EncryptedLength + 16384)) + { + *securityError = SEC_E_INSUFFICIENT_MEMORY; + return false; + } + int received = recv(connection->Socket, (char*)connection->Encrypted + connection->EncryptedLength, + (int)(connection->EncryptedCapacity - connection->EncryptedLength), 0); + if (received == SOCKET_ERROR || received == 0) + { + *socketError = received == 0 ? WSAECONNRESET : WSAGetLastError(); + return false; + } + connection->EncryptedLength += received; + } + + SecBuffer inputBuffers[2] = { + {connection->EncryptedLength, SECBUFFER_TOKEN, connection->Encrypted}, + {0, SECBUFFER_EMPTY, NULL}}; + SecBufferDesc inputDesc = {SECBUFFER_VERSION, 2, inputBuffers}; + output.cbBuffer = 0; + output.BufferType = SECBUFFER_TOKEN; + output.pvBuffer = NULL; + status = InitializeSecurityContextA(&SChannelCredentials, &connection->Context, (SEC_CHAR*)target, + requestFlags, 0, SECURITY_NATIVE_DREP, &inputDesc, 0, + NULL, &outputDesc, &attributes, &expiry); + if (status == SEC_E_INCOMPLETE_MESSAGE) + continue; + if (status != SEC_I_CONTINUE_NEEDED && status != SEC_E_OK) + { + *securityError = status; + if (output.pvBuffer) + FreeContextBuffer(output.pvBuffer); + return false; + } + if (!SendSecurityToken(connection->Socket, &output, socketError)) + return false; + if (inputBuffers[1].BufferType == SECBUFFER_EXTRA) + { + memmove(connection->Encrypted, inputBuffers[1].pvBuffer, inputBuffers[1].cbBuffer); + connection->EncryptedLength = inputBuffers[1].cbBuffer; + } + else + connection->EncryptedLength = 0; } - else if (where & SSL_CB_ALERT) + if (QueryContextAttributes(&connection->Context, SECPKG_ATTR_STREAM_SIZES, &connection->StreamSizes) != SEC_E_OK) { - str = (where & SSL_CB_READ) ? "read" : "write"; - sprintf(buf, "SSL3 alert %s:%s:%s\n", str, - SSLLib.SSL_alert_type_string_long(ret), - SSLLib.SSL_alert_desc_string_long(ret)); + *securityError = SEC_E_INTERNAL_ERROR; + return false; } - else if (where & SSL_CB_EXIT) + return true; +} + +static int FlushPendingWrite(CSchannelConnection* connection) +{ + while (connection->PendingWriteOffset < connection->PendingWriteLength) { - if (ret == 0) + int sent = send(connection->Socket, (const char*)connection->PendingWrite + connection->PendingWriteOffset, + (int)(connection->PendingWriteLength - connection->PendingWriteOffset), 0); + if (sent == SOCKET_ERROR) { - sprintf(buf, "%s:failed in %s\n", str, SSLLib.SSL_state_string_long(s)); + DWORD error = WSAGetLastError(); + connection->LastError = error == WSAEWOULDBLOCK ? SSL_ERROR_WANT_WRITE : SSL_ERROR_SYSCALL; + return -1; } - else if (ret < 0) + if (sent == 0) { - sprintf(buf, "%s:error %d in %s\n", str, ret, SSLLib.SSL_state_string_long(s)); + connection->LastError = SSL_ERROR_ZERO_RETURN; + return -1; } + connection->PendingWriteOffset += sent; + } + return 0; +} + +int SSLWrite(SSL* ssl, const void* data, int size) +{ + if (ssl == NULL || size <= 0) + return size; + if (ssl->PendingWriteLength != 0) + { + if (FlushPendingWrite(ssl) != 0) + return -1; + int completed = ssl->PendingPlaintextLength; + ssl->PendingWriteLength = ssl->PendingWriteOffset = 0; + ssl->PendingPlaintextLength = 0; + ssl->LastError = SSL_ERROR_NONE; + return completed; } - if (buf[0]) + int plainLength = min(size, (int)ssl->StreamSizes.cbMaximumMessage); + DWORD total = ssl->StreamSizes.cbHeader + plainLength + ssl->StreamSizes.cbTrailer; + BYTE* pendingWrite = (BYTE*)realloc(ssl->PendingWrite, total); + if (pendingWrite == NULL) { - OutputDebugString(buf); - TRACE_I(buf); + ssl->LastError = SSL_ERROR_SSL; + return -1; } + ssl->PendingWrite = pendingWrite; + memcpy(ssl->PendingWrite + ssl->StreamSizes.cbHeader, data, plainLength); + SecBuffer buffers[4] = { + {ssl->StreamSizes.cbHeader, SECBUFFER_STREAM_HEADER, ssl->PendingWrite}, + {(DWORD)plainLength, SECBUFFER_DATA, ssl->PendingWrite + ssl->StreamSizes.cbHeader}, + {ssl->StreamSizes.cbTrailer, SECBUFFER_STREAM_TRAILER, ssl->PendingWrite + ssl->StreamSizes.cbHeader + plainLength}, + {0, SECBUFFER_EMPTY, NULL}}; + SecBufferDesc descriptor = {SECBUFFER_VERSION, 4, buffers}; + SECURITY_STATUS status = EncryptMessage(&ssl->Context, 0, &descriptor, 0); + if (status != SEC_E_OK) + { + ssl->LastError = SSL_ERROR_SSL; + return -1; + } + ssl->PendingWriteLength = buffers[0].cbBuffer + buffers[1].cbBuffer + buffers[2].cbBuffer; + ssl->PendingWriteOffset = 0; + ssl->PendingPlaintextLength = plainLength; + if (FlushPendingWrite(ssl) != 0) + return -1; + ssl->PendingWriteLength = ssl->PendingWriteOffset = 0; + ssl->PendingPlaintextLength = 0; + ssl->LastError = SSL_ERROR_NONE; + return plainLength; } -#endif -void WriteSSLErrorStackToLog(int logUID, const char* errSrc) +static int CopyDecrypted(CSchannelConnection* ssl, void* data, int size) { - // log OpenSSL error stack - int err2; - char buffer[256]; // they say: at least 120 bytes - while ((err2 = SSLLib.ERR_get_error()) != 0) + int count = min(size, (int)ssl->DecryptedLength); + if (count > 0) { - SSLLib.ERR_error_string(err2, buffer); - Logs.LogMessage(logUID, "SSL ERROR: ", -1); - Logs.LogMessage(logUID, errSrc, -1); - Logs.LogMessage(logUID, ": ", -1); - Logs.LogMessage(logUID, buffer, -1); - Logs.LogMessage(logUID, "\r\n", -1); + memcpy(data, ssl->Decrypted, count); + ssl->DecryptedLength -= count; + if (ssl->DecryptedLength) + memmove(ssl->Decrypted, ssl->Decrypted + count, ssl->DecryptedLength); } + return count; } -BOOL CSocket::EncryptSocket(int logUID, int* sslErrorOccured, CCertificate** unverifiedCert, - int* errorID, char* errorBuf, int errorBufLen, CSocket* conForReuse) +int SSLRead(SSL* ssl, void* data, int size) { - int err; - SSL* Conn; - char buffer[256], name[200]; - - CALL_STACK_MESSAGE3("CSocket::EncryptSocket(%d, , , , , , 0x%p)", logUID, conForReuse); - if (errorID != NULL) - *errorID = -1; - if (errorBufLen > 0) - errorBuf[0] = 0; - if (unverifiedCert != NULL) - *unverifiedCert = NULL; - if (sslErrorOccured != NULL) - *sslErrorOccured = SSLConn != NULL ? SSLCONERR_NOERROR : SSLCONERR_DONOTRETRY; - if (SSLConn != NULL) - return TRUE; // socket has already been encrypted - if (!bSSLInited) - return FALSE; - WriteSSLErrorStackToLog(logUID, "unknown source"); - Conn = SSLLib.SSL_new(SSLLib.Ctx); - if (Conn) - { - HWND hWnd = SocketsThread->GetHiddenWindow(); - u_long argp = 0; - - WSAAsyncSelect(Socket, hWnd, 0, 0); - err = ioctlsocket(Socket, FIONBIO, &argp); - // On x64, SOCKET is a 64-bit value, but the OpenSSL folks assume it never exceeds 2^32 - // see http://comments.gmane.org/gmane.comp.encryption.openssl.devel/13621 - // http://msdn.microsoft.com/en-us/library/ms724485%28VS.85%29.aspx - // if that happens and this condition starts failing, maybe an x64 version of SSL will already exist - if (Socket > 0x00000000ffffffff) - { - DWORD* crash = NULL; - *crash = 0; - } - if (!SSLLib.SSL_set_fd(Conn, (int)Socket)) - WriteSSLErrorStackToLog(logUID, "SSL_set_fd"); - SSLLib.SSL_set_verify(Conn, 0, NULL); - -#ifdef _DEBUG - SSLLib.SSL_set_info_callback(Conn, InfoCallback); - Logs.LogMessage(logUID, "SSL DEBUG INFO: See Trace Server for messages from information callback for this SSL connection.\r\n", -1); -#endif - - BOOL testReuseSSLSession = FALSE; - if (conForReuse != NULL && conForReuse->SSLConn != NULL && conForReuse->ReuseSSLSession != 2 /* no */) + if (ssl == NULL || size <= 0) + return size; + if (ssl->DecryptedLength) + return CopyDecrypted(ssl, data, size); + for (;;) + { + if (ssl->EncryptedLength == 0) { - SSL_SESSION* ssl_sessionid = SSLLib.SSL_get1_session(conForReuse->SSLConn); // sessionid.addref() - if (ssl_sessionid == NULL) - Logs.LogMessage(logUID, "SSL ERROR: SSL_get1_session returns NULL!\r\n", -1); - else + if (!EnsureBuffer(ssl->Encrypted, ssl->EncryptedCapacity, 16384)) { - if (!SSLLib.SSL_set_session(Conn, ssl_sessionid)) - WriteSSLErrorStackToLog(logUID, "SSL_set_session"); - else - testReuseSSLSession = TRUE; - SSLLib.SSL_SESSION_free(ssl_sessionid); // sessionid.release() + ssl->LastError = SSL_ERROR_SSL; + return -1; } - } - - TRACE_I("SSL_connect: begin"); - { - CALL_STACK_MESSAGE1("CSocket::EncryptSocket::SSL_connect()"); - err = SSLLib.SSL_connect(Conn); - } - TRACE_I("SSL_connect: end"); - - if (err > 0) - { - if (testReuseSSLSession) + int received = recv(ssl->Socket, (char*)ssl->Encrypted, (int)ssl->EncryptedCapacity, 0); + if (received == SOCKET_ERROR) { - if (SSLLib.SSL_session_reused(Conn)) - { - Logs.LogMessage(logUID, "SSL INFO: SSL session reused for data-connection\r\n", -1); - if (conForReuse->ReuseSSLSession == 0 /* try */) - conForReuse->ReuseSSLSession = 1 /* yes */; - } - else // SSL session not reused - { - if (conForReuse->ReuseSSLSession == 0 /* try */) // do not try again for future data-connections (or it will fail) - { - Logs.LogMessage(logUID, "SSL INFO: SSL session was NOT reused, will not try for future data-connections...\r\n", -1); - conForReuse->ReuseSSLSession = 2 /* no */; - } - else // try for all future data-cons to set ReuseSSLSessionFailed to TRUE and so reconnect ctrl-con (except if this is keep-alive data-con) - { - Logs.LogMessage(logUID, "SSL INFO: SSL session was NOT reused, it has expired in server session cache, reconnect of control connection is needed...\r\n", -1); - conForReuse->ReuseSSLSessionFailed = TRUE; // To open the data connection, reuse is probably necessary, but it reports an error; the only solution is to reconnect the control connection. - } - } + DWORD error = WSAGetLastError(); + ssl->LastError = error == WSAEWOULDBLOCK ? SSL_ERROR_WANT_READ : SSL_ERROR_SYSCALL; + return -1; } - - void* ssl_cipher; - char* ssl_version; - char* cipher_name; - int ssl_bits; - X509* peerCert; - STACK_OF(X509) * certStack; - BYTE *DERCert, *PKCS7Cert = NULL, *tmp; - int DERCertLen, PKCS7CertLen; - int verRes = SSLLib.SSL_get_verify_result(Conn); - - wsprintf(buffer, LoadStr(IDS_SSL_LOG_OSSL_CERT_VERIFY), verRes); - Logs.LogMessage(logUID, buffer, -1); -#ifdef _DEBUG - const char* str = SSLLib.X509_verify_cert_error_string(verRes); -#endif - - peerCert = SSLLib.SSL_get_peer_certificate(Conn); - SSLLib.X509_NAME_oneline(peerCert->cert_info->subject, name, sizeof(name)); - wsprintf(buffer, LoadStr(IDS_SSL_LOG_SUBJECT), name); - Logs.LogMessage(logUID, buffer, -1); - SSLLib.X509_NAME_oneline(peerCert->cert_info->issuer, name, sizeof(name)); - wsprintf(buffer, LoadStr(IDS_SSL_LOG_ISSUER), name); - Logs.LogMessage(logUID, buffer, -1); - - // Obtain entire certificate chain upto root certificate - certStack = SSLLib.SSL_get_peer_cert_chain(Conn); - if (certStack) + if (received == 0) { - PKCS7* p7 = SSLLib.PKCS7_new(); - if (p7) - { - PKCS7_SIGNED* p7s = SSLLib.PKCS7_SIGNED_new(); - if (p7s) - { - p7->type = SSLLib.OBJ_nid2obj(NID_pkcs7_signed); - p7->d.sign = p7s; - p7s->contents->type = SSLLib.OBJ_nid2obj(NID_pkcs7_data); - SSLLib.ASN1_INTEGER_set(p7s->version, 1); - p7s->cert = certStack; - p7s->crl = SSLLib.sk_new_null(); - PKCS7CertLen = SSLLib.i2d_PKCS7(p7, NULL); - tmp = PKCS7Cert = (BYTE*)malloc(PKCS7CertLen); - SSLLib.i2d_PKCS7(p7, &tmp); - } - } - p7->d.sign->cert = NULL; // Avoid freeing it - SSLLib.PKCS7_free(p7); + ssl->LastError = SSL_ERROR_ZERO_RETURN; + return 0; } - - DERCertLen = SSLLib.i2d_X509(peerCert, NULL); - tmp = DERCert = (BYTE*)malloc(DERCertLen); - SSLLib.i2d_X509(peerCert, &tmp); - SSLLib.X509_free(peerCert); - - ssl_version = SSLLib.SSL_CIPHER_get_version(SSLLib.SSL_get_current_cipher(Conn)); - ssl_cipher = SSLLib.SSL_get_current_cipher(Conn); - SSLLib.SSL_CIPHER_get_bits(ssl_cipher, &ssl_bits); - cipher_name = SSLLib.SSL_CIPHER_get_name(ssl_cipher); - wsprintf(buffer, LoadStr(IDS_SSL_LOG_ALGO), ssl_version, cipher_name, ssl_bits); - Logs.LogMessage(logUID, buffer, -1); - - BOOL certAcceptedOrVerified = FALSE; - if (pCertificate) + ssl->EncryptedLength = received; + } + SecBuffer buffers[4] = { + {ssl->EncryptedLength, SECBUFFER_DATA, ssl->Encrypted}, + {0, SECBUFFER_EMPTY, NULL}, {0, SECBUFFER_EMPTY, NULL}, {0, SECBUFFER_EMPTY, NULL}}; + SecBufferDesc descriptor = {SECBUFFER_VERSION, 4, buffers}; + SECURITY_STATUS status = DecryptMessage(&ssl->Context, &descriptor, 0, NULL); + if (status == SEC_E_INCOMPLETE_MESSAGE) + { + if (!EnsureBuffer(ssl->Encrypted, ssl->EncryptedCapacity, ssl->EncryptedLength + 16384)) { - if (pCertificate->IsSame(DERCert, DERCertLen, PKCS7Cert, PKCS7CertLen)) - { - Logs.LogMessage(logUID, LoadStr(pCertificate->IsVerified() ? IDS_SSL_LOG_CERTVERIFIED : IDS_SSL_LOG_CERTACCEPTED), -1, TRUE); - certAcceptedOrVerified = TRUE; - } - else // Huh! The certificate has changed????? - { - Logs.LogMessage(logUID, LoadStr(IDS_SSL_LOG_CERTCHANGED), -1, TRUE); - pCertificate->Release(); - pCertificate = NULL; - } + ssl->LastError = SSL_ERROR_SSL; + return -1; } - if (!certAcceptedOrVerified) + int received = recv(ssl->Socket, (char*)ssl->Encrypted + ssl->EncryptedLength, + (int)(ssl->EncryptedCapacity - ssl->EncryptedLength), 0); + if (received == SOCKET_ERROR) { - if (CheckCertificate(DERCert, DERCertLen, errorBuf, errorBufLen, HostAddress, NULL)) - { - Logs.LogMessage(logUID, LoadStr(IDS_SSL_LOG_CERTVERIFIED), -1, TRUE); - pCertificate = new CCertificate(DERCert, DERCertLen, PKCS7Cert, PKCS7CertLen, true, HostAddress); - certAcceptedOrVerified = TRUE; // Passed - } - else - Logs.LogMessage(logUID, LoadStr(IDS_SSL_LOG_CERTNOTVERIFIED), -1, TRUE); + DWORD error = WSAGetLastError(); + ssl->LastError = error == WSAEWOULDBLOCK ? SSL_ERROR_WANT_READ : SSL_ERROR_SYSCALL; + return -1; } - if (!certAcceptedOrVerified) + if (received == 0) { - // The certificate was not verified nor previously accepted by user, so user should accept - // it before further using of this socket. - if (unverifiedCert != NULL) - *unverifiedCert = new CCertificate(DERCert, DERCertLen, PKCS7Cert, PKCS7CertLen, false, HostAddress); - else - { - SSLLib.SSL_shutdown(Conn); - SSLLib.SSL_free(Conn); - if (PKCS7Cert) - free(PKCS7Cert); - free(DERCert); - if (sslErrorOccured != NULL) - *sslErrorOccured = SSLCONERR_UNVERIFIEDCERT; // The certificate was not verified nor previously accepted by user. - return FALSE; - } + ssl->LastError = SSL_ERROR_ZERO_RETURN; + return 0; } - if (PKCS7Cert) - free(PKCS7Cert); - free(DERCert); - WSAAsyncSelect(Socket, hWnd, Msg, FD_READ | FD_CLOSE | FD_WRITE); - SSLConn = Conn; - if (sslErrorOccured != NULL) - *sslErrorOccured = SSLCONERR_NOERROR; // But the certificate must not be verified nor previously accepted by user. - return TRUE; + ssl->EncryptedLength += received; + continue; + } + if (status == SEC_I_CONTEXT_EXPIRED) + { + ssl->LastError = SSL_ERROR_ZERO_RETURN; + return 0; + } + if (status != SEC_E_OK) + { + ssl->LastError = SSL_ERROR_SSL; + return -1; + } + SecBuffer* dataBuffer = NULL; + SecBuffer* extraBuffer = NULL; + for (int i = 0; i < 4; i++) + { + if (buffers[i].BufferType == SECBUFFER_DATA) + dataBuffer = &buffers[i]; + else if (buffers[i].BufferType == SECBUFFER_EXTRA) + extraBuffer = &buffers[i]; + } + if (dataBuffer && dataBuffer->cbBuffer) + { + BYTE* decrypted = (BYTE*)realloc(ssl->Decrypted, dataBuffer->cbBuffer); + if (decrypted == NULL) + { + ssl->LastError = SSL_ERROR_SSL; + return -1; + } + ssl->Decrypted = decrypted; + // DecryptMessage points into Encrypted. Copy before moving the + // SECBUFFER_EXTRA tail to the beginning of that input buffer. + memcpy(ssl->Decrypted, dataBuffer->pvBuffer, dataBuffer->cbBuffer); + ssl->DecryptedLength = dataBuffer->cbBuffer; + } + if (extraBuffer) + { + memmove(ssl->Encrypted, extraBuffer->pvBuffer, extraBuffer->cbBuffer); + ssl->EncryptedLength = extraBuffer->cbBuffer; } else + ssl->EncryptedLength = 0; + if (dataBuffer && dataBuffer->cbBuffer) { - /* ERR_STATE *es = SSLLib.ERR_get_state(); - fd_set fs; - timeval tv = {0,10}; - FD_ZERO(&fs); - FD_SET(Socket, &fs); - select(1, NULL, NULL, &fs, &tv);*/ - err = SSLLib.SSL_get_error(Conn, err); - // err = SSLLib.ERR_get_error(); - // err = GetLastError(); - - sprintf(buffer, LoadStr(IDS_SSL_ERR_CONNECT_LOG), err, SSLLib.ERR_error_string(err, name)); - Logs.LogMessage(logUID, buffer, -1, TRUE); - WriteSSLErrorStackToLog(logUID, "SSL_connect"); - - if (errorID != NULL) - *errorID = IDS_SSL_ERR_CONNECT; - if (errorBufLen > 0) - _snprintf_s(errorBuf, errorBufLen, _TRUNCATE, LoadStr(IDS_SSL_ERR_CONNECT_ERR), err, name); - SSLLib.SSL_free(Conn); - if (sslErrorOccured != NULL) - *sslErrorOccured = SSLCONERR_CANRETRY; + ssl->LastError = SSL_ERROR_NONE; + return CopyDecrypted(ssl, data, size); } } - else - { - Logs.LogMessage(logUID, LoadStr(IDS_SSL_ERR_NEW_LOG), -1, TRUE); - if (errorID != NULL) - *errorID = IDS_SSL_ERR_NEW; - WriteSSLErrorStackToLog(logUID, "SSL_new"); - } - return FALSE; } -void FreeSSL(int loadStatus) +int SSLPending(SSL* ssl) { - if (bSSLInited || loadStatus != 0) - { - if (SSLLib.Locks) - { - SSLLib.CRYPTO_set_locking_callback(NULL); - for (int i = 0; i < SSLLib.CRYPTO_num_locks(); i++) - if (SSLLib.Locks[i]) - CloseHandle(SSLLib.Locks[i]); - free(SSLLib.Locks); - } - if (SSLLib.Ctx) - SSLLib.SSL_CTX_free(SSLLib.Ctx); - - if (loadStatus == 0 || loadStatus == 2) - { - // Petr: OpenSSL left a bunch of memory leaks, so I added this block - - // thread-local cleanup - SSLLib.ERR_remove_state(0); - - // thread-safe cleanup - SSLLib.ENGINE_cleanup(); - SSLLib.CONF_modules_finish(); - SSLLib.CONF_modules_free(); - SSLLib.CONF_modules_unload(1); - - // global application exit cleanup (after all SSL activity is shutdown) - SSLLib.ERR_free_strings(); - SSLLib.EVP_cleanup(); - SSLLib.CRYPTO_cleanup_all_ex_data(); - - // The stack with compression methods probably cannot be released "legally", so it is handled manually - STACK_OF(SSL_COMP)* comp_sk = SSLLib.SSL_COMP_get_compression_methods(); - SSLLib.sk_free(CHECKED_STACK_OF(SSL_COMP, comp_sk)); + return ssl ? (int)ssl->DecryptedLength : 0; +} - // Petr: end of block - } +int SSLGetError(SSL* ssl, int result) +{ + if (result > 0) + return SSL_ERROR_NONE; + return ssl ? ssl->LastError : SSL_ERROR_SSL; +} - if (SSLLib.hSSLLib) - FreeLibrary(SSLLib.hSSLLib); - if (SSLLib.hSSLUtilLib) - FreeLibrary(SSLLib.hSSLUtilLib); - bSSLInited = false; - } +int SSLShutdown(SSL* ssl) +{ + if (ssl == NULL || !ssl->ContextValid) + return 1; + DWORD shutdown = SCHANNEL_SHUTDOWN; + SecBuffer input = {sizeof(shutdown), SECBUFFER_TOKEN, &shutdown}; + SecBufferDesc inputDesc = {SECBUFFER_VERSION, 1, &input}; + ApplyControlToken(&ssl->Context, &inputDesc); + SecBuffer output = {0, SECBUFFER_TOKEN, NULL}; + SecBufferDesc outputDesc = {SECBUFFER_VERSION, 1, &output}; + DWORD attributes = 0; + TimeStamp expiry; + SECURITY_STATUS status = InitializeSecurityContext(NULL, &ssl->Context, NULL, 0, 0, SECURITY_NATIVE_DREP, + NULL, 0, NULL, &outputDesc, &attributes, &expiry); + DWORD ignoredError = NO_ERROR; + if (status == SEC_E_OK) + SendSecurityToken(ssl->Socket, &output, &ignoredError); + else if (output.pvBuffer) + FreeContextBuffer(output.pvBuffer); + return status == SEC_E_OK ? 1 : 0; } -void SSLThreadLocalCleanup() +void SSLFree(SSL* ssl) { - if (bSSLInited) - SSLLib.ERR_remove_state(0); + if (ssl == NULL) + return; + if (ssl->ContextValid) + DeleteSecurityContext(&ssl->Context); + free(ssl->Encrypted); + free(ssl->Decrypted); + free(ssl->PendingWrite); + free(ssl); } -static void LockingCallback(int mode, int type, const char* file, int line) +int SSLtoWS2Error(int err) { - if (mode & CRYPTO_LOCK) - { - WaitForSingleObject(SSLLib.Locks[type], INFINITE); - } - else + switch (err) { - ReleaseMutex(SSLLib.Locks[type]); + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + return WSAEWOULDBLOCK; + case SSL_ERROR_ZERO_RETURN: + return WSAECONNRESET; + default: + return err; } } bool InitSSL(int logUID, int* errorID) { - if (errorID != NULL) + if (errorID) *errorID = -1; if (bSSLInited) return true; + SCHANNEL_CRED credentials = {}; + credentials.dwVersion = SCHANNEL_CRED_VERSION; + credentials.grbitEnabledProtocols = SP_PROT_TLS1_2_CLIENT | SP_PROT_TLS1_3_CLIENT; + credentials.dwFlags = SCH_CRED_MANUAL_CRED_VALIDATION | SCH_USE_STRONG_CRYPTO; + TimeStamp expiry; + SECURITY_STATUS status = AcquireCredentialsHandleA(NULL, (LPSTR)UNISP_NAME_A, SECPKG_CRED_OUTBOUND, NULL, + &credentials, NULL, NULL, &SChannelCredentials, &expiry); + if (status == SEC_E_OK) + { + bSSLInited = true; + Logs.LogMessage(logUID, "TLS INFO: Using Windows SChannel (TLS 1.2/1.3 only).\r\n", -1); + return true; + } + char message[256]; + SecurityStatusText(status, message, SizeOf(message)); + Logs.LogMessage(logUID, "TLS ERROR: ", -1); + Logs.LogMessage(logUID, message, -1); + Logs.LogMessage(logUID, "\r\n", -1); + if (errorID) + *errorID = IDS_SSL_ERR_OPENSSLNOTFOUND; + return false; +} - CALL_STACK_MESSAGE2("InitSSL(%d,)", logUID); +void FreeSSL(int) +{ + if (bSSLInited) + FreeCredentialsHandle(&SChannelCredentials); + bSSLInited = false; +} - memset(&SSLLib, 0, sizeof(SSLLib)); +void SSLThreadLocalCleanup() +{ + // SChannel does not allocate per-thread state that callers must release. +} - bool ret = false; - char dir[MAX_PATH]; - int loadStatus = 1; - if (GetModuleFileName(NULL, dir, _countof(dir)) && - SalamanderGeneral->CutDirectory(dir) && - SalamanderGeneral->SalPathAppend(dir, "utils", _countof(dir))) +BOOL CSocket::EncryptSocket(int logUID, int* sslErrorOccured, CCertificate** unverifiedCert, + int* errorID, char* errorBuf, int errorBufLen, CSocket* conForReuse) +{ + if (errorID) + *errorID = -1; + if (errorBufLen > 0) + errorBuf[0] = 0; + if (unverifiedCert) + *unverifiedCert = NULL; + if (sslErrorOccured) + *sslErrorOccured = SSLConn ? SSLCONERR_NOERROR : SSLCONERR_DONOTRETRY; + if (SSLConn) + return TRUE; + if (!bSSLInited) + return FALSE; + + SSL* connection = (SSL*)calloc(1, sizeof(SSL)); + if (!connection) { - ret = true; - char* s = dir + strlen(dir); - if (!SalamanderGeneral->SalPathAppend(dir, "libeay32.dll", _countof(dir)) || - !LoadSymbols(dir, NULL, SSLLib.hSSLUtilLib, SSLUtilSymbols, logUID)) - { - ret = false; - } - *s = 0; - if (!ret || - !SalamanderGeneral->SalPathAppend(dir, "ssleay32.dll", _countof(dir)) || - !LoadSymbols(dir, NULL /*_T("libssl32.dll")*/, SSLLib.hSSLLib, SSLSymbols, logUID)) - { - ret = false; - } + if (errorID) + *errorID = IDS_SSL_ERR_NEW; + return FALSE; } + connection->Socket = Socket; + connection->LastError = SSL_ERROR_SSL; - if (ret) - { - loadStatus = 2; - sprintf(dir, "SSL INFO: Version: %s \r\nSSL INFO: Compile flags: ", SSLLib.SSLeay_version(SSLEAY_VERSION)); - Logs.LogMessage(logUID, dir, -1); - Logs.LogMessage(logUID, SSLLib.SSLeay_version(SSLEAY_CFLAGS), -1); - Logs.LogMessage(logUID, "\r\n", -1); - // NOTE: There are no unload counterparts for SSL_library_init & SSL_load_error_strings - SSLLib.SSL_library_init(); - SSLLib.SSL_load_error_strings(); - DWORD seed = GetTickCount(); - SSLLib.RAND_seed(&seed, sizeof(seed)); - int locksCount = SSLLib.CRYPTO_num_locks(); - SSLLib.Locks = (HANDLE*)malloc(locksCount * sizeof(HANDLE)); - if (SSLLib.Locks) - { - for (int i = 0; i < locksCount; i++) - { - SSLLib.Locks[i] = !ret ? NULL : CreateMutex(NULL, FALSE, NULL); - if (ret && SSLLib.Locks[i] == NULL) - ret = false; - } - } + if (HostAddress == NULL || HostAddress[0] == 0) + { + SSLFree(connection); + if (errorID) + *errorID = IDS_SSL_ERR_CONNECT; + return FALSE; + } + + HWND window = SocketsThread->GetHiddenWindow(); + WSAAsyncSelect(Socket, window, 0, 0); + u_long blocking = 0; + ioctlsocket(Socket, FIONBIO, &blocking); + // SChannel drives this legacy handshake through blocking send/recv calls; + // bound that narrow phase so a silent TLS peer cannot prevent shutdown. + DWORD tlsHandshakeTimeout = Config.GetServerRepliesTimeout() * 1000; + if (tlsHandshakeTimeout < 1000) + tlsHandshakeTimeout = 1000; + setsockopt(Socket, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tlsHandshakeTimeout, sizeof(tlsHandshakeTimeout)); + setsockopt(Socket, SOL_SOCKET, SO_SNDTIMEO, (const char*)&tlsHandshakeTimeout, sizeof(tlsHandshakeTimeout)); + DWORD socketError = NO_ERROR; + SECURITY_STATUS securityError = SEC_E_OK; + bool connected = CompleteHandshake(connection, HostAddress, &socketError, &securityError); + + DWORD noSocketDeadline = 0; + setsockopt(Socket, SOL_SOCKET, SO_RCVTIMEO, (const char*)&noSocketDeadline, sizeof(noSocketDeadline)); + setsockopt(Socket, SOL_SOCKET, SO_SNDTIMEO, (const char*)&noSocketDeadline, sizeof(noSocketDeadline)); + u_long nonBlocking = 1; + ioctlsocket(Socket, FIONBIO, &nonBlocking); + WSAAsyncSelect(Socket, window, Msg, FD_READ | FD_CLOSE | FD_WRITE); + if (!connected) + { + char message[256]; + if (socketError == WSAETIMEDOUT) + lstrcpyn(message, "TLS handshake deadline expired.", SizeOf(message)); + else if (socketError != NO_ERROR) + lstrcpyn(message, SalamanderGeneral->GetErrorText(socketError), SizeOf(message)); else - ret = false; - if (!ret) + SecurityStatusText(securityError, message, SizeOf(message)); + if (errorID) + *errorID = IDS_SSL_ERR_CONNECT; + if (errorBufLen > 0) + _snprintf_s(errorBuf, errorBufLen, _TRUNCATE, LoadStr(IDS_SSL_ERR_CONNECT_ERR), (int)(socketError ? socketError : securityError), message); + Logs.LogMessage(logUID, "TLS ERROR: ", -1, TRUE); + Logs.LogMessage(logUID, message, -1, TRUE); + Logs.LogMessage(logUID, "\r\n", -1, TRUE); + // A timed-out or failed handshake cannot become usable later; closing it + // also releases any pending socket operation before retry/error handling. + CloseSocket(NULL); + SSLFree(connection); + if (sslErrorOccured) + *sslErrorOccured = SSLCONERR_CANRETRY; + return FALSE; + } + + PCCERT_CONTEXT peerCert = NULL; + if (QueryContextAttributes(&connection->Context, SECPKG_ATTR_REMOTE_CERT_CONTEXT, &peerCert) != SEC_E_OK || peerCert == NULL) + { + SSLFree(connection); + if (errorID) + *errorID = IDS_SSL_ERR_CONNECT; + if (sslErrorOccured) + *sslErrorOccured = SSLCONERR_CANRETRY; + return FALSE; + } + BYTE* der = (BYTE*)malloc(peerCert->cbCertEncoded); + if (!der) + { + CertFreeCertificateContext(peerCert); + SSLFree(connection); + if (errorID) + *errorID = IDS_SSL_ERR_NEW; + return FALSE; + } + memcpy(der, peerCert->pbCertEncoded, peerCert->cbCertEncoded); + int derLength = peerCert->cbCertEncoded; + CertFreeCertificateContext(peerCert); + + SecPkgContext_ConnectionInfo info = {}; + if (QueryContextAttributes(&connection->Context, SECPKG_ATTR_CONNECTION_INFO, &info) == SEC_E_OK) + { + char details[160]; + _snprintf_s(details, SizeOf(details), _TRUNCATE, "TLS INFO: protocol 0x%lX, cipher 0x%lX (%lu bits)\r\n", + info.dwProtocol, info.aiCipher, info.dwCipherStrength); + Logs.LogMessage(logUID, details, -1); + } + + bool accepted = false; + // Reconnects must recheck exception expiry; data sockets intentionally inherit the already pinned control identity. + if (pCertificate && pCertificate->IsSame(der, derLength, NULL, 0) && + (pCertificate->IsVerified() || pCertificate->IsCurrentException()) && + (IsDataConnection || pCertificate->MatchesEndpoint(HostAddress, HostPort))) + { + Logs.LogMessage(logUID, LoadStr(pCertificate->IsVerified() ? IDS_SSL_LOG_CERTVERIFIED : IDS_SSL_LOG_CERTACCEPTED), -1, TRUE); + accepted = true; + } + else if (pCertificate) + { + Logs.LogMessage(logUID, LoadStr(IDS_SSL_LOG_CERTCHANGED), -1, TRUE); + pCertificate->Release(); + pCertificate = NULL; + } + if (!accepted && CheckCertificate(der, derLength, errorBuf, errorBufLen, HostAddress, NULL)) + { + Logs.LogMessage(logUID, LoadStr(IDS_SSL_LOG_CERTVERIFIED), -1, TRUE); + pCertificate = new CCertificate(der, derLength, NULL, 0, true, HostAddress, HostPort); + accepted = true; + } + if (!accepted) + { + bool endpointKnown = false; + if (CertificateExceptions.IsAccepted(HostAddress, HostPort, der, derLength, &endpointKnown)) { - sprintf(dir, "SSL Err: Unable to alloc %d locks\r\n", locksCount); - Logs.LogMessage(logUID, dir, -1); + Logs.LogMessage(logUID, LoadStr(IDS_SSL_LOG_CERTACCEPTED), -1, TRUE); + pCertificate = new CCertificate(der, derLength, NULL, 0, false, HostAddress, HostPort); + accepted = true; } - - if (ret) + else if (endpointKnown) { - SSLLib.CRYPTO_set_locking_callback(/*(void (*)(int,int,const char *,int))*/ LockingCallback); - - // NOTE: the pointer returned SSLv23_client_method is not to be freed - // - // SSLv23_client_method() is default method used in OpenSLL.exe and CURL. - // Unsafe SSL2 protocol is disabled using OPENSSL_NO_SSL2 define. - // SSLv3_client_method() didn't work with wedos server: /viewtopic.php?f=2&t=6667 - // SSLLib.Meth = SSLLib.SSLv3_client_method(); - SSLLib.Meth = SSLLib.SSLv23_client_method(); - if (SSLLib.Meth) - { - SSLLib.Ctx = SSLLib.SSL_CTX_new(SSLLib.Meth); - if (SSLLib.Ctx) - { - /* also switch on all the interoperability and bug - * workarounds so that we will communicate with people - * that cannot read poorly written specs :-) - */ - SSLLib.SSL_CTX_ctrl(SSLLib.Ctx, SSL_CTRL_OPTIONS, SSL_OP_ALL, NULL); - bSSLInited = true; - return true; - } - } // if Meth <> NULL then + // A stored exception exists for this endpoint but its pin or lifetime no longer matches; prompt again. + Logs.LogMessage(logUID, LoadStr(IDS_SSL_LOG_CERTCHANGED), -1, TRUE); } } - FreeSSL(loadStatus); - memset(&SSLLib, 0, sizeof(SSLLib)); // clean up, everything is freed - if (errorID != NULL) - *errorID = IDS_SSL_ERR_OPENSSLNOTFOUND; - Logs.LogMessage(logUID, LoadStr(IDS_SSL_ERR_OPENSSLNOTFOUND), -1); - Logs.LogMessage(logUID, "\r\n", -1); - return false; -} /* InitSSL */ - -int SSLtoWS2Error(int err) -{ - switch (err) + if (!accepted) { - case SSL_ERROR_WANT_READ: - return WSAEWOULDBLOCK; - case SSL_ERROR_WANT_WRITE: - return WSAEWOULDBLOCK; - // case SSL_ERROR_SYSCALL: return WSAECONNCLOSED; - // case SSL_ERROR_ZERO_RETURN: return WSAECONNCLOSED; - default: - return err; + Logs.LogMessage(logUID, LoadStr(IDS_SSL_LOG_CERTNOTVERIFIED), -1, TRUE); + if (unverifiedCert) + *unverifiedCert = new CCertificate(der, derLength, NULL, 0, false, HostAddress, HostPort); + else + { + SSLShutdown(connection); + SSLFree(connection); + free(der); + if (sslErrorOccured) + *sslErrorOccured = SSLCONERR_UNVERIFIEDCERT; + return FALSE; + } } + free(der); + SSLConn = connection; + if (conForReuse) + conForReuse->ReuseSSLSession = 2; // SChannel maintains its own target-name session cache. + if (sslErrorOccured) + *sslErrorOccured = SSLCONERR_NOERROR; + return TRUE; } -//////////////////////// CCertificateErrDialog //////////////////////// CCertificateErrDialog::CCertificateErrDialog(HWND hParent, const char* errorStr) - : CCenteredDialog(HLanguage, IDD_CERTIFICATE, hParent), ErrorStr(errorStr) + : CCenteredDialog(HLanguage, IDD_CERTIFICATE, hParent), ErrorStr(errorStr), RememberedException(false) { } INT_PTR CCertificateErrDialog::DialogProc(UINT uMsg, WPARAM wParam, LPARAM lParam) { - CALL_STACK_MESSAGE4("CCertificateErrDialog::DialogProc(0x%X, 0x%IX, 0x%IX)", uMsg, wParam, lParam); switch (uMsg) { case WM_INITDIALOG: SetDlgItemText(HWindow, IDT_CERTIFICATE_ERROR, ErrorStr); break; case WM_COMMAND: - switch (LOWORD(wParam)) - { - case IDB_CERTIFICATE_VIEW: - if (HIWORD(wParam) == BN_CLICKED) - { - EndDialog(HWindow, IDB_CERTIFICATE_VIEW); - } - break; - } + if (LOWORD(wParam) == IDOK) + RememberedException = IsDlgButtonChecked(HWindow, IDB_CERTIFICATE_REMEMBER) == BST_CHECKED; + if (LOWORD(wParam) == IDB_CERTIFICATE_VIEW && HIWORD(wParam) == BN_CLICKED) + EndDialog(HWindow, IDB_CERTIFICATE_VIEW); + break; } return CCenteredDialog::DialogProc(uMsg, wParam, lParam); } -//////////////////////// CCertificate //////////////////////// -CCertificate::CCertificate(BYTE* pDERCert, int DERCertLen, BYTE* pPKCS7Cert, int PKCS7CertLen, bool bValid, LPCSTR host) +CCertificate::CCertificate(BYTE* derCert, int derCertLen, BYTE* pkcs7Cert, int pkcs7CertLen, bool valid, LPCSTR host, + unsigned short port) + : nRefCount(1), pDERData(NULL), pPKCS7Data(NULL), nDERDataLen(0), nPKCS7DataLen(0), bVerified(valid), Host(NULL), + HostAddress(NULL), Port(port) { - CALL_STACK_MESSAGE7("CCertificate::ctor(0x%p, %d, 0x%p, %d, %d, %s)", pDERCert, DERCertLen, pPKCS7Cert, PKCS7CertLen, bValid, host); - bVerified = bValid; - pDERData = (BYTE*)malloc(DERCertLen); - if (pDERData) - { - nDERDataLen = DERCertLen; - memcpy(pDERData, pDERCert, nDERDataLen); - } - else - { - nDERDataLen = 0; - } - pPKCS7Data = (BYTE*)malloc(PKCS7CertLen); - if (pPKCS7Data) + if (derCertLen > 0 && (pDERData = (BYTE*)malloc(derCertLen)) != NULL) { - nPKCS7DataLen = PKCS7CertLen; - memcpy(pPKCS7Data, pPKCS7Cert, nPKCS7DataLen); + nDERDataLen = derCertLen; + memcpy(pDERData, derCert, derCertLen); } - else + if (pkcs7CertLen > 0 && (pPKCS7Data = (BYTE*)malloc(pkcs7CertLen)) != NULL) { - nPKCS7DataLen = 0; + nPKCS7DataLen = pkcs7CertLen; + memcpy(pPKCS7Data, pkcs7Cert, pkcs7CertLen); } if (host) { - Host = (LPWSTR)malloc(sizeof(WCHAR) * (strlen(host) + 1)); - if (Host) - { - LPWSTR out = Host; - while (*host) - *out++ = *host++; - *out = 0; - } - } - else - { - Host = NULL; + HostAddress = _strdup(host); + int length = MultiByteToWideChar(CP_ACP, 0, host, -1, NULL, 0); + if (length > 0 && (Host = (LPWSTR)malloc(length * sizeof(WCHAR))) != NULL) + MultiByteToWideChar(CP_ACP, 0, host, -1, Host, length); } - nRefCount = 1; } CCertificate::~CCertificate() { - if (Host) - free(Host); - if (pDERData) - free(pDERData); - if (pPKCS7Data) - free(pPKCS7Data); + free(Host); + free(HostAddress); + free(pDERData); + free(pPKCS7Data); } -LONG CCertificate::AddRef() -{ - return InterlockedIncrement(&nRefCount); -} +LONG CCertificate::AddRef() { return InterlockedIncrement(&nRefCount); } LONG CCertificate::Release() { - LONG ret = InterlockedDecrement(&nRefCount); - - if (!ret) + LONG result = InterlockedDecrement(&nRefCount); + if (!result) delete this; - return ret; + return result; } -void CCertificate::ShowCertificate(HWND hParent) +void CCertificate::ShowCertificate(HWND hParent) { ViewCertificate(hParent, pDERData, nDERDataLen, pPKCS7Data, nPKCS7DataLen, Host); } +bool CCertificate::CheckCertificate(LPTSTR buf, int maxlen) { return ::CheckCertificate(pDERData, nDERDataLen, buf, maxlen, NULL, Host); } + +bool CCertificate::IsSame(BYTE* derCert, int derCertLen, BYTE* pkcs7Cert, int pkcs7CertLen) { - ViewCertificate(hParent, pDERData, nDERDataLen, pPKCS7Data, nPKCS7DataLen, Host); + return derCertLen == nDERDataLen && pkcs7CertLen == nPKCS7DataLen && + (derCertLen == 0 || memcmp(pDERData, derCert, derCertLen) == 0) && + (pkcs7CertLen == 0 || memcmp(pPKCS7Data, pkcs7Cert, pkcs7CertLen) == 0); } -bool CCertificate::CheckCertificate(LPTSTR buf, int maxlen) +bool CCertificate::MatchesEndpoint(LPCSTR host, unsigned short port) const { - return ::CheckCertificate(pDERData, nDERDataLen, buf, maxlen, NULL, Host); + return HostAddress != NULL && host != NULL && _stricmp(HostAddress, host) == 0 && Port == port; } -bool CCertificate::IsSame(BYTE* pDERCert, int DERCertLen, BYTE* pPKCS7Cert, int PKCS7CertLen) +bool CCertificate::RememberException(CCertificateExceptionScope scope) const { - if (DERCertLen != nDERDataLen) - return false; - if (PKCS7CertLen != nPKCS7DataLen) - return false; - if (memcmp(pDERData, pDERCert, nDERDataLen)) - return false; - return memcmp(pPKCS7Data, pPKCS7Cert, nPKCS7DataLen) ? false : true; + return CertificateExceptions.Remember(HostAddress, Port, pDERData, nDERDataLen, scope); +} + +bool CCertificate::IsCurrentException() const +{ + // Data sockets inherit the control certificate, so validate the stored control endpoint rather than their ephemeral port. + return CertificateExceptions.IsAccepted(HostAddress, Port, pDERData, nDERDataLen, NULL); } diff --git a/src/plugins/ftp/ssl.h b/src/plugins/ftp/ssl.h index 6b7d3238..f510a12f 100644 --- a/src/plugins/ftp/ssl.h +++ b/src/plugins/ftp/ssl.h @@ -4,37 +4,50 @@ #pragma once -// SSL 2.0 has security flaws and is disabled in current Firefox, Windows, etc. -#define OPENSSL_NO_SSL2 - -#include -#ifndef __in -#define __in -#define __out_ecount(x) +#ifndef SECURITY_WIN32 +#define SECURITY_WIN32 #endif - -//#include +#include +#include #include -#define SSLCONERR_NOERROR 0 // no error (also when SSL is not used) -#define SSLCONERR_CANRETRY 1 // unable to encrypt connection + retrying can help -#define SSLCONERR_DONOTRETRY 2 // unable to encrypt connection + it has no sense to retry -#define SSLCONERR_UNVERIFIEDCERT 3 // server's certificate was not verified nor previously accepted by user +#define SSLCONERR_NOERROR 0 +#define SSLCONERR_CANRETRY 1 +#define SSLCONERR_DONOTRETRY 2 +#define SSLCONERR_UNVERIFIEDCERT 3 + +// These values are private to this adapter; callers translate them with SSLtoWS2Error. +#define SSL_ERROR_NONE 0 +#define SSL_ERROR_SSL 1 +#define SSL_ERROR_WANT_READ 2 +#define SSL_ERROR_WANT_WRITE 3 +#define SSL_ERROR_SYSCALL 5 +#define SSL_ERROR_ZERO_RETURN 6 + +// Scope is explicit so accepting an invalid chain never becomes durable without a second user choice. +enum CCertificateExceptionScope +{ + cesSession = 1, + cesPersistent = 2, +}; class CCertificate { public: - CCertificate(BYTE* pDERCert, int DERCertLen, BYTE* pPKCS7Cert, int PKCS7CertLen, bool bValid, LPCSTR host); + CCertificate(BYTE* pDERCert, int DERCertLen, BYTE* pPKCS7Cert, int PKCS7CertLen, bool bValid, LPCSTR host, + unsigned short port); LONG AddRef(); LONG Release(); void ShowCertificate(HWND hParent); bool CheckCertificate(LPTSTR buf, int maxlen); - // NOTE: the method modifies certificate data, the caller must ensure the data are not used - // simultaneously in another thread (ideally call it while this is the only reference to the object) + // The caller must ensure the certificate is not in use on another thread. void SetVerified(bool verified) { bVerified = verified; }; bool IsSame(BYTE* pDERCert, int DERCertLen, BYTE* pPKCS7Cert, int PKCS7CertLen); + bool MatchesEndpoint(LPCSTR host, unsigned short port) const; + bool RememberException(CCertificateExceptionScope scope) const; + bool IsCurrentException() const; bool IsVerified() { return bVerified; }; LPCWSTR GetHostName() { return Host; }; @@ -44,8 +57,10 @@ class CCertificate LONG nRefCount; BYTE *pDERData, *pPKCS7Data; int nDERDataLen, nPKCS7DataLen; - bool bVerified; // false when accepted once, true if verified and valid + bool bVerified; LPWSTR Host; + LPSTR HostAddress; + unsigned short Port; }; class CCertificateErrDialog : public CCenteredDialog @@ -55,184 +70,31 @@ class CCertificateErrDialog : public CCenteredDialog public: CCertificateErrDialog(HWND hParent, const char* errorStr); + bool RememberException() const { return RememberedException; } protected: virtual INT_PTR DialogProc(UINT uMsg, WPARAM wParam, LPARAM lParam); -}; -#define SSL_CTRL_GET_SESSION_REUSED 8 + // Retain the explicit choice so the caller can distinguish session trust from persisted trust. + bool RememberedException; +}; -#define SSL_session_reused(ssl) \ - SSL_ctrl((ssl), SSL_CTRL_GET_SESSION_REUSED, 0, NULL) +void LoadCertificateExceptions(HKEY regKey, CSalamanderRegistryAbstract* registry); +void SaveCertificateExceptions(HKEY regKey, CSalamanderRegistryAbstract* registry); -struct SSL_SESSION -{ -}; // Petr: we do not need to know the contents of the structure; it is handled exclusively inside OpenSSL - -typedef int (*TSSL_get_error)(SSL* SSL, int ret); -typedef int (*TSSL_write)(SSL* SSL, const void* data, int Size); -typedef int (*TSSL_read)(SSL* SSL, void* data, int Size); -typedef int (*TSSL_library_init)(); -typedef void (*TSSL_load_error_strings)(); -//typedef void *(*TSSLv3_client_method)(); -typedef void* (*TSSLv23_client_method)(); -typedef SSL_CTX* (*TSSL_CTX_new)(void* SSL_METHOD); -typedef void (*TSSL_CTX_free)(void* SSL_CTX); -typedef int (*TSSL_CTX_ctrl)(void* SSL_CTX, int Cmd, int larg, void* parg); -typedef SSL* (*TSSL_new)(void* SSL_CTX); -typedef void (*TSSL_free)(void* SSL_CTX); -typedef int (*TSSL_set_fd)(SSL* SSL, int fd); -typedef int (*TSSL_connect)(SSL* SSL); -typedef int (*TSSL_shutdown)(SSL* SSL); -typedef void* (*TSSL_get_current_cipher)(SSL* SSL); -typedef char* (*TSSL_CIPHER_get_version)(void* SSL_CIPHER); -typedef int (*TSSL_CIPHER_get_bits)(void* SSL_CIPHER, int* alg_bits); -typedef char* (*TSSL_CIPHER_get_name)(void* SSL_CIPHER); -typedef long (*TSSL_get_verify_result)(void* SSL); -typedef void (*TSSL_set_verify)(SSL* SSL, int mode, void* Callback); -typedef char* (*TSSLeay_version)(int infoType); -typedef int (*TSSL_pending)(SSL* SSL); -typedef const char* (*TX509_verify_cert_error_string)(long n); -typedef X509* (*TSSL_get_peer_certificate)(const SSL* SSL); -typedef STACK_OF(X509) * (*TSSL_get_peer_cert_chain)(const SSL* s); -typedef STACK_OF(SSL_COMP) * (*TSSL_COMP_get_compression_methods)(void); -typedef SSL_SESSION* (*TSSL_get1_session)(SSL* ssl); /* obtain a reference count */ -typedef void (*TSSL_SESSION_free)(SSL_SESSION* ses); -typedef long (*TSSL_ctrl)(SSL* ssl, int cmd, long larg, void* parg); -typedef int (*TSSL_set_session)(SSL* to, SSL_SESSION* session); -typedef const char* (*TSSL_state_string_long)(const SSL* s); -typedef const char* (*TSSL_alert_type_string_long)(int value); -typedef const char* (*TSSL_alert_desc_string_long)(int value); -typedef void (*TSSL_set_info_callback)(SSL* ctx, void (*cb)(const SSL* ssl, int type, int val)); - -//typedef X509_NAME *(*TX509_get_subject_name)(X509 *a); // rets a->cert_info->subject -//typedef X509_NAME *(*TX509_get_issuer_name)(X509 *a); // rets a->cert_info->issuer -typedef int (*Ti2d_X509)(X509* a, BYTE** buf); -typedef char* (*TX509_NAME_oneline)(X509_NAME* a, char* buf, int size); -//typedef BIO *(*TBIO_s_mem)(); -//typedef BIO *(*TBIO_new)(void *type); -//typedef int (*TBIO_free)(BIO *a); -//typedef int (*TBIO_read)(BIO *b, void *data, int len); -typedef void (*TX509_free)(X509* cert); -typedef char* (*TERR_error_string)(unsigned long e, char* buf); -//typedef void (*TERR_print_errors)(void *bio); -typedef unsigned long (*TERR_get_error)(void); -//typedef ERR_STATE *(*TERR_get_state)(void); - -typedef void (*TERR_remove_state)(unsigned long pid); -typedef void (*TENGINE_cleanup)(void); -typedef void (*TCONF_modules_finish)(void); -typedef void (*TCONF_modules_free)(void); -typedef void (*TCONF_modules_unload)(int all); -typedef void (*TERR_free_strings)(void); -typedef void (*Tsk_free)(_STACK*); -typedef void (*TEVP_cleanup)(void); -typedef void (*TCRYPTO_cleanup_all_ex_data)(void); - -typedef int (*TCRYPTO_num_locks)(void); -typedef void (*TCRYPTO_set_locking_callback)(void (*func)(int mode, int type, const char* file, int line)); -typedef void (*TRAND_seed)(const void* buf, int num); - -//typedef void (*TERR_clear_error)(); - -typedef PKCS7* (*TPKCS7_new)(void); -typedef PKCS7_SIGNED* (*TPKCS7_SIGNED_new)(); -typedef ASN1_OBJECT* (*TOBJ_nid2obj)(int n); -typedef int (*TASN1_INTEGER_set)(ASN1_INTEGER* a, long v); -typedef void (*TPKCS7_free)(PKCS7* p7); -typedef int (*Ti2d_PKCS7)(PKCS7* p7, BYTE** buf); -typedef stack_st_X509_CRL* (*Tsk_new_null)(); - -typedef struct sSSLLib -{ - HINSTANCE hSSLLib; - HINSTANCE hSSLUtilLib; - - TSSL_get_error SSL_get_error; - TSSL_write SSL_write; - TSSL_read SSL_read; - TSSL_library_init SSL_library_init; - TSSL_load_error_strings SSL_load_error_strings; - // TSSLv3_client_method SSLv3_client_method; - TSSLv23_client_method SSLv23_client_method; - TSSL_CTX_new SSL_CTX_new; - TSSL_CTX_free SSL_CTX_free; - TSSL_CTX_ctrl SSL_CTX_ctrl; - TSSL_new SSL_new; - TSSL_free SSL_free; - TSSL_set_fd SSL_set_fd; - TSSL_connect SSL_connect; - TSSL_shutdown SSL_shutdown; - TSSL_get_current_cipher SSL_get_current_cipher; - TSSL_CIPHER_get_version SSL_CIPHER_get_version; - TSSL_CIPHER_get_bits SSL_CIPHER_get_bits; - TSSL_CIPHER_get_name SSL_CIPHER_get_name; - TSSL_get_verify_result SSL_get_verify_result; - TSSL_set_verify SSL_set_verify; - TSSLeay_version SSLeay_version; - TSSL_pending SSL_pending; - TSSL_get_peer_certificate SSL_get_peer_certificate; - TSSL_get_peer_cert_chain SSL_get_peer_cert_chain; - TSSL_COMP_get_compression_methods SSL_COMP_get_compression_methods; - TSSL_get1_session SSL_get1_session; - TSSL_set_session SSL_set_session; - TSSL_SESSION_free SSL_SESSION_free; - TSSL_ctrl SSL_ctrl; -#ifdef _DEBUG - TSSL_state_string_long SSL_state_string_long; - TSSL_alert_type_string_long SSL_alert_type_string_long; - TSSL_alert_desc_string_long SSL_alert_desc_string_long; - TSSL_set_info_callback SSL_set_info_callback; - TX509_verify_cert_error_string X509_verify_cert_error_string; -#endif - TX509_NAME_oneline X509_NAME_oneline; - TX509_free X509_free; - Ti2d_X509 i2d_X509; - - TPKCS7_new PKCS7_new; - TPKCS7_SIGNED_new PKCS7_SIGNED_new; - TOBJ_nid2obj OBJ_nid2obj; - TASN1_INTEGER_set ASN1_INTEGER_set; - TPKCS7_free PKCS7_free; - Ti2d_PKCS7 i2d_PKCS7; - Tsk_new_null sk_new_null; - - // TBIO_s_mem BIO_s_mem; - // TBIO_new BIO_new; - // TBIO_free BIO_free; - //TBIO_read BIO_read; - // TERR_clear_error ERR_clear_error; - TERR_error_string ERR_error_string; - // TERR_print_errors ERR_print_errors; - TERR_get_error ERR_get_error; - // TERR_get_state ERR_get_state; - - TERR_remove_state ERR_remove_state; - TENGINE_cleanup ENGINE_cleanup; - TCONF_modules_finish CONF_modules_finish; - TCONF_modules_free CONF_modules_free; - TCONF_modules_unload CONF_modules_unload; - TERR_free_strings ERR_free_strings; - Tsk_free sk_free; - TEVP_cleanup EVP_cleanup; - TCRYPTO_cleanup_all_ex_data CRYPTO_cleanup_all_ex_data; - - TCRYPTO_num_locks CRYPTO_num_locks; - TCRYPTO_set_locking_callback CRYPTO_set_locking_callback; - TRAND_seed RAND_seed; - - void* Meth; - SSL_CTX* Ctx; - HANDLE* Locks; -} sSSLLib; - -extern sSSLLib SSLLib; +// SChannel is a stream security package. Keeping this opaque type behind the +// historic SSL name limits the migration to the TLS adapter and its call sites. +struct CSchannelConnection; +typedef CSchannelConnection SSL; bool InitSSL(int logUID, int* errorID); -// loadStatus: 0 = lib was fully initialized, -// 1 = only DLLs may be loaded (probably not both DLLs are loaded), -// 2 = DLLs were loaded + lib init called (SSL_library_init() and others) void FreeSSL(int loadStatus = 0); void SSLThreadLocalCleanup(); +int SSLRead(SSL* ssl, void* data, int size); +int SSLWrite(SSL* ssl, const void* data, int size); +int SSLPending(SSL* ssl); +int SSLGetError(SSL* ssl, int result); +int SSLShutdown(SSL* ssl); +void SSLFree(SSL* ssl); int SSLtoWS2Error(int err); diff --git a/src/plugins/ftp/vcxproj/ftp.props b/src/plugins/ftp/vcxproj/ftp.props index 5dbf8f62..3cd4a0f5 100644 --- a/src/plugins/ftp/vcxproj/ftp.props +++ b/src/plugins/ftp/vcxproj/ftp.props @@ -9,7 +9,7 @@ WINVER=0x0601;_WIN32_WINNT=0x0601;_WIN32_IE=0x0800;_CRT_SECURE_NO_WARNINGS;_SCL_SECURE_NO_WARNINGS;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES;_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES_COUNT;ENABLE_PROPERTYDIALOG;%(PreprocessorDefinitions) - WSOCK32.LIB;Crypt32.lib;Cryptui.lib;%(AdditionalDependencies) + WSOCK32.LIB;Crypt32.lib;Cryptui.lib;Secur32.lib;%(AdditionalDependencies) WINVER=0x0601;%(PreprocessorDefinitions) diff --git a/src/plugins/ieviewer/cmark-gfm/VENDOR.md b/src/plugins/ieviewer/cmark-gfm/VENDOR.md new file mode 100644 index 00000000..69a67d1e --- /dev/null +++ b/src/plugins/ieviewer/cmark-gfm/VENDOR.md @@ -0,0 +1,19 @@ +# cmark-gfm vendor record + +- Version: `0.29.0.gfm.13` +- Upstream: https://github.com/github/cmark-gfm/releases/tag/0.29.0.gfm.13 +- Archive: `https://github.com/github/cmark-gfm/archive/refs/tags/0.29.0.gfm.13.tar.gz` +- SHA-256: `5abc61798ebd9de5660bc076443c07abad2b8d15dbc11094a3a79644b8ad243a` +- License: BSD-2-Clause (see `doc/COPYING`) +- Local patches: none; the only integration code is the separately maintained + bounded renderer in `../markdown_rendering.cpp`. + +The IE Viewer enables only `autolink`, `strikethrough`, `table`, `tagfilter`, +and `tasklist`. Rendering must retain cmark's safe default (never +`CMARK_OPT_UNSAFE`), validate UTF-8, and reject input, tree node/depth, and +HTML output that exceed the documented limits in `../markdown_rendering.h`. + +Before a future update, run `tools/test-cmark-gfm-hardening.ps1`, review every +snapshot change in `tests/cmark-gfm/snapshots`, and replay the deterministic +extension-combination fuzz loop. Review this component quarterly and whenever +an upstream security advisory or release is published. diff --git a/src/plugins/ieviewer/cmark-gfm/build/src/cmark-gfm_version.h b/src/plugins/ieviewer/cmark-gfm/build/src/cmark-gfm_version.h index 7e7bd823..cf4dfee0 100644 --- a/src/plugins/ieviewer/cmark-gfm/build/src/cmark-gfm_version.h +++ b/src/plugins/ieviewer/cmark-gfm/build/src/cmark-gfm_version.h @@ -1,7 +1,7 @@ #ifndef CMARK_GFM_VERSION_H #define CMARK_GFM_VERSION_H -#define CMARK_GFM_VERSION ((0 << 24) | (29 << 16) | (0 << 8) | 0) -#define CMARK_GFM_VERSION_STRING "0.29.0.gfm.0" +#define CMARK_GFM_VERSION ((0 << 24) | (29 << 16) | (0 << 8) | 13) +#define CMARK_GFM_VERSION_STRING "0.29.0.gfm.13" #endif diff --git a/src/plugins/ieviewer/cmark-gfm/doc/README.md b/src/plugins/ieviewer/cmark-gfm/doc/README.md index 7d6549d2..0b9f719f 100644 --- a/src/plugins/ieviewer/cmark-gfm/doc/README.md +++ b/src/plugins/ieviewer/cmark-gfm/doc/README.md @@ -1,8 +1,7 @@ cmark-gfm ========= -[![Build Status]](https://travis-ci.org/github/cmark-gfm) -[![Windows Build Status]](https://ci.appveyor.com/project/github/cmark) +![Actions CI](https://github.com/github/cmark-gfm/actions/workflows/ci.yml/badge.svg) `cmark-gfm` is an extended version of the C reference implementation of [CommonMark], a rationalized version of Markdown syntax with a spec. This @@ -66,6 +65,7 @@ There are also libraries that wrap `libcmark` for [Perl](https://metacpan.org/release/CommonMark), [Python](https://pypi.python.org/pypi/paka.cmark), [R](https://cran.r-project.org/package=commonmark), +[Tcl](https://github.com/apnadkarni/tcl-cmark), [Scala](https://github.com/sparsetech/cmark-scala) and [Node.js](https://github.com/killa123/node-cmark). @@ -204,4 +204,3 @@ most of the C library's API and its test harness. [Windows Build Status]: https://ci.appveyor.com/api/projects/status/wv7ifhqhv5itm3d5?svg=true [american fuzzy lop]: http://lcamtuf.coredump.cx/afl/ [libFuzzer]: http://llvm.org/docs/LibFuzzer.html - diff --git a/src/plugins/ieviewer/cmark-gfm/doc/changelog.txt b/src/plugins/ieviewer/cmark-gfm/doc/changelog.txt index b86a41a2..94e8a19f 100644 --- a/src/plugins/ieviewer/cmark-gfm/doc/changelog.txt +++ b/src/plugins/ieviewer/cmark-gfm/doc/changelog.txt @@ -1,3 +1,105 @@ +[0.29.0.gfm.13] + * Normalized marker row vs. delimiter row nomenclature (#273) + * Exposed CMARK_NODE_FOOTNOTE_DEFINITION literal value (#336) + * Fix format specifier for printing a size_t (#340) + +[0.29.0.gfm.12] + + * Fixed polynomial time complexity issues per + https://github.com/github/cmark-gfm/security/advisories/GHSA-w4qg-3vf7-m9x5 + * Added CodeQL project integration (#337) + * Addressed const qualifier discard compiler warnings (#330, #331) + +[0.29.0.gfm.11] + + * Improved fixes for polynomial time complexity issues per + https://github.com/github/cmark-gfm/security/advisories/GHSA-66g8-4hjf-77xh + (#323, #324) + * Added fuzzing target for bracketed patterns (#318) + * Fixed bug in list numbering introduced in + 763587e8775350b8cb4a2aa0f4cec3685aa96e8b (#322) which caused list numbers + to increment by 2 + * Fixed strict prototype clang warning (#310) + * Fixed regression test (#312) + * Added additional output formats to quadratic fuzzer (#327) + * Fixed buffer overflow in fuzzing harness (#326) + + Note: these changes may lead to minor changes in expected output on plaintext + rendering of list items. Notably, blank lines may no longer delineate the start + of a list when rendering to plaintext due to changes in how the tight list status + is calculated. + +[0.29.0.gfm.10] + + * Fixed polynomial time complexity issue per + https://github.com/github/cmark-gfm/security/advisories/GHSA-r8vr-c48j-fcc5 + * Fixed polynomial time complexity issues per + https://github.com/github/cmark-gfm/security/advisories/GHSA-66g8-4hjf-77xh + + Note: these changes remove redundant bold tag nesting which may result + in existing rendering tests failing, e.g. rendering "____bold____" to html + will no longer yield "

                      bold

                      ". + +[0.29.0.gfm.9] + + * Cleanup: Use of a private header was cleaned up (#248) + * Cleanup: Man page was updated (#255) + * Cleanup: Warnings for -Wstrict-prototypes were cleaned up (#285) + * Cleanup: We avoid header duplication (#289) + + * We now store positioning info for url_match (#201) + * We now expose cmark_parent_footnote_def for non-C renderers (#254) + * Footnote aria-label text now reference the specific footnote backref, and we include a data-footnote-backref-idx attribute so the label can be internationalized in a downstream filter (#307) + +[0.29.0.gfm.8] + + * We restored backwards compatibility by deprecating the `cmark_init_standard_node_flags()` requirement, which is now a noop (#305) + * We added a quadratic complexity fuzzing target (#304) + +[0.29.0.gfm.7] + + * Fixed a polynomial time complexity issue per + https://github.com/github/cmark-gfm/security/advisories/GHSA-r572-jvj2-3m8p + * Fixed an issue in which crafted markdown document could trigger an + out-of-bounds read in the validate_protocol function per + https://github.com/github/cmark-gfm/security/advisories/GHSA-c944-cv5f-hpvr + * Fixed a polynomial time complexity issue + https://github.com/github/cmark-gfm/security/advisories/GHSA-24f7-9frr-5h2r + * Fixed several polynomial time complexity issues per + https://github.com/github/cmark-gfm/security/advisories/GHSA-29g3-96g3-jg6c + * We removed an unneeded .DS_Store file (#291) + * We added a test for domains with underscores and fix roundtrip behavior (#292) + * We now use an up-to-date clang-format (#294) + * We made a variety of implicit integer trunctions explicit by moving to + size_t as our standard size integer type (#302) + +[0.29.0.gfm.6] + * Fixed polynomial time complexity DoS vulnerability in autolink extension + +[0.29.0.gfm.5] + * Added xmpp: and mailto: support to the autolink extension + +[0.29.0.gfm.4] + * Remove `source` from list of HTML blocks + +[0.29.0.gfm.3] + * Fixed heap memory corruption vulnerabiliy via integer overflow per https://github.com/github/cmark-gfm/security/advisories/GHSA-mc3g-88wq-6f4x + +[0.29.0.gfm.2] + * Fixed issues with footnote rendering when used with the autolinker (#121), + and when footnotes are adjacent (#139). + * We now allow footnotes to be referenced from inside a footnote definition, + we use the footnote label for the fnref href text when rendering html, and + we insert multiple backrefs when a footnote has been referenced multiple + times (#229, #230) + * We added new data- attributes to footnote html rendering to make them + easier to style (#234) + +[0.29.0.gfm.1] + + * Fixed denial of service bug in GFM's table extension + per https://github.com/github/cmark-gfm/security/advisories/GHSA-7gc6-9qr5-hc85 + [0.29.0] * Update spec to 0.29. diff --git a/src/plugins/ieviewer/cmark-gfm/extensions/CMakeLists.txt b/src/plugins/ieviewer/cmark-gfm/extensions/CMakeLists.txt index 0c007c70..e545b6d6 100644 --- a/src/plugins/ieviewer/cmark-gfm/extensions/CMakeLists.txt +++ b/src/plugins/ieviewer/cmark-gfm/extensions/CMakeLists.txt @@ -1,4 +1,3 @@ -cmake_minimum_required(VERSION 2.8) set(LIBRARY "libcmark-gfm-extensions") set(STATICLIBRARY "libcmark-gfm-extensions_static") set(LIBRARY_SOURCES @@ -18,19 +17,17 @@ include_directories( ${PROJECT_BINARY_DIR}/src ) -include (GenerateExportHeader) - include_directories(. ${CMAKE_CURRENT_BINARY_DIR}) set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE} -pg") set(CMAKE_LINKER_PROFILE "${CMAKE_LINKER_FLAGS_RELEASE} -pg") -add_compiler_export_flags() if (CMARK_SHARED) add_library(${LIBRARY} SHARED ${LIBRARY_SOURCES}) set_target_properties(${LIBRARY} PROPERTIES OUTPUT_NAME "cmark-gfm-extensions" + DEFINE_SYMBOL "cmark-gfm" SOVERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}.gfm.${PROJECT_VERSION_GFM} VERSION ${PROJECT_VERSION}) @@ -40,9 +37,6 @@ if (CMARK_SHARED) # Avoid name clash between PROGRAM and LIBRARY pdb files. set_target_properties(${LIBRARY} PROPERTIES PDB_NAME cmark-gfm-extensions_dll) - generate_export_header(${LIBRARY} - BASE_NAME cmark-gfm-extensions) - list(APPEND CMARK_INSTALL ${LIBRARY}) target_link_libraries(${LIBRARY} libcmark-gfm) @@ -53,6 +47,7 @@ if (CMARK_STATIC) set_target_properties(${STATICLIBRARY} PROPERTIES COMPILE_FLAGS "-DCMARK_GFM_STATIC_DEFINE -DCMARK_GFM_EXTENSIONS_STATIC_DEFINE" + DEFINE_SYMBOL "cmark-gfm" POSITION_INDEPENDENT_CODE ON) if (MSVC) @@ -65,11 +60,6 @@ if (CMARK_STATIC) VERSION ${PROJECT_VERSION}) endif(MSVC) - if (NOT CMARK_SHARED) - generate_export_header(${STATICLIBRARY} - BASE_NAME cmark-gfm-extensions) - endif() - list(APPEND CMARK_INSTALL ${STATICLIBRARY}) endif() @@ -86,7 +76,6 @@ install(TARGETS ${CMARK_INSTALL} if (CMARK_SHARED OR CMARK_STATIC) install(FILES cmark-gfm-core-extensions.h - ${CMAKE_CURRENT_BINARY_DIR}/cmark-gfm-extensions_export.h DESTINATION include ) diff --git a/src/plugins/ieviewer/cmark-gfm/extensions/autolink.c b/src/plugins/ieviewer/cmark-gfm/extensions/autolink.c index 41564ee4..d898cd2d 100644 --- a/src/plugins/ieviewer/cmark-gfm/extensions/autolink.c +++ b/src/plugins/ieviewer/cmark-gfm/extensions/autolink.c @@ -2,6 +2,7 @@ #include #include #include +#include #if defined(_WIN32) #define strncasecmp _strnicmp @@ -35,44 +36,25 @@ static int sd_autolink_issafe(const uint8_t *link, size_t link_len) { } static size_t autolink_delim(uint8_t *data, size_t link_end) { - uint8_t cclose, copen; size_t i; + size_t closing = 0; + size_t opening = 0; - for (i = 0; i < link_end; ++i) - if (data[i] == '<') { + for (i = 0; i < link_end; ++i) { + const uint8_t c = data[i]; + if (c == '<') { link_end = i; break; + } else if (c == '(') { + opening++; + } else if (c == ')') { + closing++; } + } while (link_end > 0) { - cclose = data[link_end - 1]; - - switch (cclose) { + switch (data[link_end - 1]) { case ')': - copen = '('; - break; - default: - copen = 0; - } - - if (strchr("?!.,:*_~'\"", data[link_end - 1]) != NULL) - link_end--; - - else if (data[link_end - 1] == ';') { - size_t new_end = link_end - 2; - - while (new_end > 0 && cmark_isalpha(data[new_end])) - new_end--; - - if (new_end < link_end - 2 && data[new_end] == '&') - link_end = new_end; - else - link_end--; - } else if (copen != 0) { - size_t closing = 0; - size_t opening = 0; - i = 0; - /* Allow any number of matching brackets (as recognised in copen/cclose) * at the end of the URL. If there is a greater number of closing * brackets than opening ones, we remove one character from the end of @@ -80,34 +62,52 @@ static size_t autolink_delim(uint8_t *data, size_t link_end) { * * Examples (input text => output linked portion): * - * http://www.pokemon.com/Pikachu_(Electric) - * => http://www.pokemon.com/Pikachu_(Electric) + * http://www.pokemon.com/Pikachu_(Electric) + * => http://www.pokemon.com/Pikachu_(Electric) * - * http://www.pokemon.com/Pikachu_((Electric) - * => http://www.pokemon.com/Pikachu_((Electric) + * http://www.pokemon.com/Pikachu_((Electric) + * => http://www.pokemon.com/Pikachu_((Electric) * - * http://www.pokemon.com/Pikachu_(Electric)) - * => http://www.pokemon.com/Pikachu_(Electric) + * http://www.pokemon.com/Pikachu_(Electric)) + * => http://www.pokemon.com/Pikachu_(Electric) * - * http://www.pokemon.com/Pikachu_((Electric)) - * => http://www.pokemon.com/Pikachu_((Electric)) + * http://www.pokemon.com/Pikachu_((Electric)) + * => http://www.pokemon.com/Pikachu_((Electric)) */ - - while (i < link_end) { - if (data[i] == copen) - opening++; - else if (data[i] == cclose) - closing++; - - i++; + if (closing <= opening) { + return link_end; } + closing--; + link_end--; + break; + case '?': + case '!': + case '.': + case ',': + case ':': + case '*': + case '_': + case '~': + case '\'': + case '"': + link_end--; + break; + case ';': { + size_t new_end = link_end - 2; - if (closing <= opening) - break; + while (new_end > 0 && cmark_isalpha(data[new_end])) + new_end--; - link_end--; - } else + if (new_end < link_end - 2 && data[new_end] == '&') + link_end = new_end; + else + link_end--; break; + } + + default: + return link_end; + } } return link_end; @@ -116,7 +116,20 @@ static size_t autolink_delim(uint8_t *data, size_t link_end) { static size_t check_domain(uint8_t *data, size_t size, int allow_short) { size_t i, np = 0, uscore1 = 0, uscore2 = 0; + /* The purpose of this code is to reject urls that contain an underscore + * in one of the last two segments. Examples: + * + * www.xxx.yyy.zzz autolinked + * www.xxx.yyy._zzz not autolinked + * www.xxx._yyy.zzz not autolinked + * www._xxx.yyy.zzz autolinked + * + * The reason is that domain names are allowed to include underscores, + * but host names are not. See: https://stackoverflow.com/a/2183140 + */ for (i = 1; i < size - 1; i++) { + if (data[i] == '\\' && i < size - 2) + i++; if (data[i] == '_') uscore2++; else if (data[i] == '.') { @@ -127,8 +140,17 @@ static size_t check_domain(uint8_t *data, size_t size, int allow_short) { break; } - if (uscore1 > 0 || uscore2 > 0) - return 0; + if (uscore1 > 0 || uscore2 > 0) { + /* If the url is very long then accept it despite the underscores, + * to avoid quadratic behavior causing a denial of service. See: + * https://github.com/github/cmark-gfm/security/advisories/GHSA-29g3-96g3-jg6c + * Reasonable urls are unlikely to have more than 10 segments, so + * this extra condition shouldn't have any impact on normal usage. + */ + if (np <= 10) { + return 0; + } + } if (allow_short) { /* We don't need a valid domain in the strict sense (with @@ -165,7 +187,7 @@ static cmark_node *www_match(cmark_parser *parser, cmark_node *parent, if (link_end == 0) return NULL; - while (link_end < size && !cmark_isspace(data[link_end])) + while (link_end < size && !cmark_isspace(data[link_end]) && data[link_end] != '<') link_end++; link_end = autolink_delim(data, link_end); @@ -225,7 +247,7 @@ static cmark_node *url_match(cmark_parser *parser, cmark_node *parent, return 0; link_end += domain_len; - while (link_end < size && !cmark_isspace(data[link_end])) + while (link_end < size && !cmark_isspace(data[link_end]) && data[link_end] != '<') link_end++; link_end = autolink_delim(data, link_end); @@ -245,6 +267,11 @@ static cmark_node *url_match(cmark_parser *parser, cmark_node *parent, cmark_node *text = cmark_node_new_with_mem(CMARK_NODE_TEXT, parser->mem); text->as.literal = url; cmark_node_append_child(node, text); + + node->start_line = text->start_line = node->end_line = text->end_line = cmark_inline_parser_get_line(inline_parser); + + node->start_column = text->start_column = max_rewind - rewind; + node->end_column = text->end_column = cmark_inline_parser_get_column(inline_parser) - 1; return node; } @@ -269,111 +296,167 @@ static cmark_node *match(cmark_syntax_extension *ext, cmark_parser *parser, // inline was finished in inlines.c. } -static void postprocess_text(cmark_parser *parser, cmark_node *text, int offset, int depth) { - // postprocess_text can recurse very deeply if there is a very long line of - // '@' only. Stop at a reasonable depth to ensure it cannot crash. - if (depth > 1000) return; +static bool validate_protocol(const char protocol[], uint8_t *data, size_t rewind, size_t max_rewind) { + size_t len = strlen(protocol); - size_t link_end; - uint8_t *data = text->as.literal.data, - *at; - size_t size = text->as.literal.len; - int rewind, max_rewind, - nb = 0, np = 0, ns = 0; + if (len > (max_rewind - rewind)) { + return false; + } - if (offset < 0 || (size_t)offset >= size) - return; + // Check that the protocol matches + if (memcmp(data - rewind - len, protocol, len) != 0) { + return false; + } - data += offset; - size -= offset; + if (len == (max_rewind - rewind)) { + return true; + } - at = (uint8_t *)memchr(data, '@', size); - if (!at) - return; + char prev_char = data[-((ptrdiff_t)rewind) - len - 1]; - max_rewind = (int)(at - data); - data += max_rewind; - size -= max_rewind; + // Make sure the character before the protocol is non-alphanumeric + return !cmark_isalnum(prev_char); +} - for (rewind = 0; rewind < max_rewind; ++rewind) { - uint8_t c = data[-rewind - 1]; +static void postprocess_text(cmark_parser *parser, cmark_node *text) { + size_t start = 0; + size_t offset = 0; + // `text` is going to be split into a list of nodes containing shorter segments + // of text, so we detach the memory buffer from text and use `cmark_chunk_dup` to + // create references to it. Later, `cmark_chunk_to_cstr` is used to convert + // the references into allocated buffers. The detached buffer is freed before we + // return. + cmark_chunk detached_chunk = text->as.literal; + text->as.literal = cmark_chunk_dup(&detached_chunk, 0, detached_chunk.len); + + uint8_t *data = text->as.literal.data; + size_t remaining = text->as.literal.len; + + while (true) { + size_t link_end; + uint8_t *at; + bool auto_mailto = true; + bool is_xmpp = false; + size_t rewind; + size_t max_rewind; + size_t np = 0; + + if (offset >= remaining) + break; - if (cmark_isalnum(c)) - continue; + at = (uint8_t *)memchr(data + start + offset, '@', remaining - offset); + if (!at) + break; - if (strchr(".+-_", c) != NULL) - continue; + max_rewind = at - (data + start + offset); - if (c == '/') - ns++; +found_at: + for (rewind = 0; rewind < max_rewind; ++rewind) { + uint8_t c = data[start + offset + max_rewind - rewind - 1]; - break; - } + if (cmark_isalnum(c)) + continue; - if (rewind == 0 || ns > 0) { - postprocess_text(parser, text, max_rewind + 1 + offset, depth + 1); - return; - } + if (strchr(".+-_", c) != NULL) + continue; - for (link_end = 0; link_end < size; ++link_end) { - uint8_t c = data[link_end]; + if (strchr(":", c) != NULL) { + if (validate_protocol("mailto:", data + start + offset + max_rewind, rewind, max_rewind)) { + auto_mailto = false; + continue; + } - if (cmark_isalnum(c)) - continue; + if (validate_protocol("xmpp:", data + start + offset + max_rewind, rewind, max_rewind)) { + auto_mailto = false; + is_xmpp = true; + continue; + } + } - if (c == '@') - nb++; - else if (c == '.' && link_end < size - 1 && cmark_isalnum(data[link_end + 1])) - np++; - else if (c != '-' && c != '_') break; - } + } - if (link_end < 2 || nb != 1 || np == 0 || - (!cmark_isalpha(data[link_end - 1]) && data[link_end - 1] != '.')) { - postprocess_text(parser, text, max_rewind + 1 + offset, depth + 1); - return; - } + if (rewind == 0) { + offset += max_rewind + 1; + continue; + } - link_end = autolink_delim(data, link_end); + assert(data[start + offset + max_rewind] == '@'); + for (link_end = 1; link_end < remaining - offset - max_rewind; ++link_end) { + uint8_t c = data[start + offset + max_rewind + link_end]; + + if (cmark_isalnum(c)) + continue; + + if (c == '@') { + // Found another '@', so go back and try again with an updated offset and max_rewind. + offset += max_rewind + 1; + max_rewind = link_end - 1; + goto found_at; + } else if (c == '.' && link_end < remaining - offset - max_rewind - 1 && + cmark_isalnum(data[start + offset + max_rewind + link_end + 1])) + np++; + else if (c == '/' && is_xmpp) + continue; + else if (c != '-' && c != '_') + break; + } - if (link_end == 0) { - postprocess_text(parser, text, max_rewind + 1 + offset, depth + 1); - return; - } + if (link_end < 2 || np == 0 || + (!cmark_isalpha(data[start + offset + max_rewind + link_end - 1]) && + data[start + offset + max_rewind + link_end - 1] != '.')) { + offset += max_rewind + link_end; + continue; + } - cmark_chunk_to_cstr(parser->mem, &text->as.literal); + link_end = autolink_delim(data + start + offset + max_rewind, link_end); - cmark_node *link_node = cmark_node_new_with_mem(CMARK_NODE_LINK, parser->mem); - cmark_strbuf buf; - cmark_strbuf_init(parser->mem, &buf, 10); - cmark_strbuf_puts(&buf, "mailto:"); - cmark_strbuf_put(&buf, data - rewind, (bufsize_t)(link_end + rewind)); - link_node->as.link.url = cmark_chunk_buf_detach(&buf); - - cmark_node *link_text = cmark_node_new_with_mem(CMARK_NODE_TEXT, parser->mem); - cmark_chunk email = cmark_chunk_dup( - &text->as.literal, - offset + max_rewind - rewind, + if (link_end == 0) { + offset += max_rewind + 1; + continue; + } + + cmark_node *link_node = cmark_node_new_with_mem(CMARK_NODE_LINK, parser->mem); + cmark_strbuf buf; + cmark_strbuf_init(parser->mem, &buf, 10); + if (auto_mailto) + cmark_strbuf_puts(&buf, "mailto:"); + cmark_strbuf_put(&buf, data + start + offset + max_rewind - rewind, (bufsize_t)(link_end + rewind)); + link_node->as.link.url = cmark_chunk_buf_detach(&buf); + + cmark_node *link_text = cmark_node_new_with_mem(CMARK_NODE_TEXT, parser->mem); + cmark_chunk email = cmark_chunk_dup( + &detached_chunk, + (bufsize_t)(start + offset + max_rewind - rewind), (bufsize_t)(link_end + rewind)); - cmark_chunk_to_cstr(parser->mem, &email); - link_text->as.literal = email; - cmark_node_append_child(link_node, link_text); + cmark_chunk_to_cstr(parser->mem, &email); + link_text->as.literal = email; + cmark_node_append_child(link_node, link_text); - cmark_node_insert_after(text, link_node); + cmark_node_insert_after(text, link_node); - cmark_node *post = cmark_node_new_with_mem(CMARK_NODE_TEXT, parser->mem); - post->as.literal = cmark_chunk_dup(&text->as.literal, - (bufsize_t)(offset + max_rewind + link_end), - (bufsize_t)(size - link_end)); - cmark_chunk_to_cstr(parser->mem, &post->as.literal); + cmark_node *post = cmark_node_new_with_mem(CMARK_NODE_TEXT, parser->mem); + post->as.literal = cmark_chunk_dup(&detached_chunk, + (bufsize_t)(start + offset + max_rewind + link_end), + (bufsize_t)(remaining - offset - max_rewind - link_end)); - cmark_node_insert_after(link_node, post); + cmark_node_insert_after(link_node, post); - text->as.literal.len = offset + max_rewind - rewind; - text->as.literal.data[text->as.literal.len] = 0; + text->as.literal = cmark_chunk_dup(&detached_chunk, (bufsize_t)start, (bufsize_t)(offset + max_rewind - rewind)); + cmark_chunk_to_cstr(parser->mem, &text->as.literal); + + text = post; + start += offset + max_rewind + link_end; + remaining -= offset + max_rewind + link_end; + offset = 0; + } + + // Convert the reference to allocated memory. + assert(!text->as.literal.alloc); + cmark_chunk_to_cstr(parser->mem, &text->as.literal); - postprocess_text(parser, post, 0, depth + 1); + // Free the detached buffer. + cmark_chunk_free(parser->mem, &detached_chunk); } static cmark_node *postprocess(cmark_syntax_extension *ext, cmark_parser *parser, cmark_node *root) { @@ -400,7 +483,7 @@ static cmark_node *postprocess(cmark_syntax_extension *ext, cmark_parser *parser } if (ev == CMARK_EVENT_ENTER && node->type == CMARK_NODE_TEXT) { - postprocess_text(parser, node, 0, /*depth*/0); + postprocess_text(parser, node); } } diff --git a/src/plugins/ieviewer/cmark-gfm/extensions/cmark-gfm-core-extensions.h b/src/plugins/ieviewer/cmark-gfm/extensions/cmark-gfm-core-extensions.h index 0645915f..bc29ffd4 100644 --- a/src/plugins/ieviewer/cmark-gfm/extensions/cmark-gfm-core-extensions.h +++ b/src/plugins/ieviewer/cmark-gfm/extensions/cmark-gfm-core-extensions.h @@ -6,45 +6,45 @@ extern "C" { #endif #include "cmark-gfm-extension_api.h" -#include "cmark-gfm-extensions_export.h" -#include "config.h" // for bool +#include "cmark-gfm_export.h" +#include #include -CMARK_GFM_EXTENSIONS_EXPORT +CMARK_GFM_EXPORT void cmark_gfm_core_extensions_ensure_registered(void); -CMARK_GFM_EXTENSIONS_EXPORT +CMARK_GFM_EXPORT uint16_t cmark_gfm_extensions_get_table_columns(cmark_node *node); /** Sets the number of columns for the table, returning 1 on success and 0 on error. */ -CMARK_GFM_EXTENSIONS_EXPORT +CMARK_GFM_EXPORT int cmark_gfm_extensions_set_table_columns(cmark_node *node, uint16_t n_columns); -CMARK_GFM_EXTENSIONS_EXPORT +CMARK_GFM_EXPORT uint8_t *cmark_gfm_extensions_get_table_alignments(cmark_node *node); /** Sets the alignments for the table, returning 1 on success and 0 on error. */ -CMARK_GFM_EXTENSIONS_EXPORT +CMARK_GFM_EXPORT int cmark_gfm_extensions_set_table_alignments(cmark_node *node, uint16_t ncols, uint8_t *alignments); -CMARK_GFM_EXTENSIONS_EXPORT +CMARK_GFM_EXPORT int cmark_gfm_extensions_get_table_row_is_header(cmark_node *node); /** Sets whether the node is a table header row, returning 1 on success and 0 on error. */ -CMARK_GFM_EXTENSIONS_EXPORT +CMARK_GFM_EXPORT int cmark_gfm_extensions_set_table_row_is_header(cmark_node *node, int is_header); -CMARK_GFM_EXTENSIONS_EXPORT +CMARK_GFM_EXPORT bool cmark_gfm_extensions_get_tasklist_item_checked(cmark_node *node); /* For backwards compatibility */ #define cmark_gfm_extensions_tasklist_is_checked cmark_gfm_extensions_get_tasklist_item_checked /** Sets whether a tasklist item is "checked" (completed), returning 1 on success and 0 on error. */ -CMARK_GFM_EXTENSIONS_EXPORT +CMARK_GFM_EXPORT int cmark_gfm_extensions_set_tasklist_item_checked(cmark_node *node, bool is_checked); #ifdef __cplusplus diff --git a/src/plugins/ieviewer/cmark-gfm/extensions/core-extensions.c b/src/plugins/ieviewer/cmark-gfm/extensions/core-extensions.c index 4b90aa4d..846e2bc2 100644 --- a/src/plugins/ieviewer/cmark-gfm/extensions/core-extensions.c +++ b/src/plugins/ieviewer/cmark-gfm/extensions/core-extensions.c @@ -18,13 +18,10 @@ static int core_extensions_registration(cmark_plugin *plugin) { } void cmark_gfm_core_extensions_ensure_registered(void) { - // JRYFIXME: how are we supposed to release plugins initialized like this? - // Temporary hack, we always initialize / release them. - // Probably a bug, file an issue on GitHub. - //static int registered = 0; + static int registered = 0; - //if (!registered) { + if (!registered) { cmark_register_plugin(core_extensions_registration); - // registered = 1; - //} + registered = 1; + } } diff --git a/src/plugins/ieviewer/cmark-gfm/extensions/ext_scanners.c b/src/plugins/ieviewer/cmark-gfm/extensions/ext_scanners.c index c3de227a..0d3ba288 100644 --- a/src/plugins/ieviewer/cmark-gfm/extensions/ext_scanners.c +++ b/src/plugins/ieviewer/cmark-gfm/extensions/ext_scanners.c @@ -1,4 +1,5 @@ -/* Generated by re2c 1.1.1 */ +/* Generated by re2c 1.3 */ + #include "ext_scanners.h" #include @@ -39,265 +40,180 @@ bufsize_t _scan_table_start(const unsigned char *p) { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; - yych = *(marker = p); - if (yych <= '{') { - if (yych <= 0x1F) { - if (yych <= '\t') { - if (yych <= 0x08) - goto yy3; + yych = *p; + if (yych <= ' ') { + if (yych <= '\n') { + if (yych == '\t') goto yy4; - } else { - if (yych <= '\n') - goto yy2; - if (yych <= '\f') - goto yy4; - goto yy3; - } } else { - if (yych <= '-') { - if (yych <= ' ') - goto yy4; - if (yych <= ',') - goto yy3; - goto yy5; - } else { - if (yych == ':') - goto yy6; - goto yy3; - } + if (yych <= '\f') + goto yy4; + if (yych >= ' ') + goto yy4; } } else { - if (yych <= 0xEC) { - if (yych <= 0xC1) { - if (yych <= '|') - goto yy4; - if (yych <= 0x7F) - goto yy3; - } else { - if (yych <= 0xDF) - goto yy7; - if (yych <= 0xE0) - goto yy9; - goto yy10; - } + if (yych <= '9') { + if (yych == '-') + goto yy5; } else { - if (yych <= 0xF0) { - if (yych <= 0xED) - goto yy11; - if (yych <= 0xEF) - goto yy10; - goto yy12; - } else { - if (yych <= 0xF3) - goto yy13; - if (yych <= 0xF4) - goto yy14; - } + if (yych <= ':') + goto yy6; + if (yych == '|') + goto yy4; } } - yy2 : { return 0; } - yy3: ++p; - goto yy2; + yy3 : { return 0; } yy4: yych = *(marker = ++p); if (yybm[0 + yych] & 64) { - goto yy15; + goto yy7; } if (yych == '-') - goto yy17; + goto yy10; if (yych == ':') - goto yy19; - goto yy2; + goto yy12; + goto yy3; yy5: yych = *(marker = ++p); if (yybm[0 + yych] & 128) { - goto yy17; + goto yy10; } if (yych <= ' ') { if (yych <= 0x08) - goto yy2; + goto yy3; if (yych <= '\r') - goto yy21; + goto yy14; if (yych <= 0x1F) - goto yy2; - goto yy21; + goto yy3; + goto yy14; } else { if (yych <= ':') { if (yych <= '9') - goto yy2; - goto yy20; + goto yy3; + goto yy13; } else { if (yych == '|') - goto yy21; - goto yy2; + goto yy14; + goto yy3; } } yy6: yych = *(marker = ++p); if (yybm[0 + yych] & 128) { - goto yy17; + goto yy10; } - goto yy2; + goto yy3; yy7: - yych = *++p; - if (yych <= 0x7F) - goto yy8; - if (yych <= 0xBF) - goto yy3; - yy8: - p = marker; - goto yy2; - yy9: - yych = *++p; - if (yych <= 0x9F) - goto yy8; - if (yych <= 0xBF) - goto yy7; - goto yy8; - yy10: - yych = *++p; - if (yych <= 0x7F) - goto yy8; - if (yych <= 0xBF) - goto yy7; - goto yy8; - yy11: - yych = *++p; - if (yych <= 0x7F) - goto yy8; - if (yych <= 0x9F) - goto yy7; - goto yy8; - yy12: - yych = *++p; - if (yych <= 0x8F) - goto yy8; - if (yych <= 0xBF) - goto yy10; - goto yy8; - yy13: - yych = *++p; - if (yych <= 0x7F) - goto yy8; - if (yych <= 0xBF) - goto yy10; - goto yy8; - yy14: - yych = *++p; - if (yych <= 0x7F) - goto yy8; - if (yych <= 0x8F) - goto yy10; - goto yy8; - yy15: yych = *++p; if (yybm[0 + yych] & 64) { - goto yy15; + goto yy7; } if (yych == '-') - goto yy17; + goto yy10; if (yych == ':') - goto yy19; - goto yy8; - yy17: + goto yy12; + yy9: + p = marker; + goto yy3; + yy10: yych = *++p; if (yybm[0 + yych] & 128) { - goto yy17; + goto yy10; } if (yych <= 0x1F) { if (yych <= '\n') { if (yych <= 0x08) - goto yy8; + goto yy9; if (yych <= '\t') - goto yy20; - goto yy22; + goto yy13; + goto yy15; } else { if (yych <= '\f') - goto yy20; + goto yy13; if (yych <= '\r') - goto yy24; - goto yy8; + goto yy17; + goto yy9; } } else { if (yych <= ':') { if (yych <= ' ') - goto yy20; + goto yy13; if (yych <= '9') - goto yy8; - goto yy20; + goto yy9; + goto yy13; } else { if (yych == '|') - goto yy25; - goto yy8; + goto yy18; + goto yy9; } } - yy19: + yy12: yych = *++p; if (yybm[0 + yych] & 128) { - goto yy17; + goto yy10; } - goto yy8; - yy20: + goto yy9; + yy13: yych = *++p; - yy21: + yy14: if (yych <= '\r') { if (yych <= '\t') { if (yych <= 0x08) - goto yy8; - goto yy20; + goto yy9; + goto yy13; } else { if (yych <= '\n') - goto yy22; + goto yy15; if (yych <= '\f') - goto yy20; - goto yy24; + goto yy13; + goto yy17; } } else { if (yych <= ' ') { if (yych <= 0x1F) - goto yy8; - goto yy20; + goto yy9; + goto yy13; } else { if (yych == '|') - goto yy25; - goto yy8; + goto yy18; + goto yy9; } } - yy22: + yy15: ++p; { return (bufsize_t)(p - start); } - yy24: + yy17: yych = *++p; if (yych == '\n') - goto yy22; - goto yy8; - yy25: + goto yy15; + goto yy9; + yy18: yych = *++p; if (yybm[0 + yych] & 128) { - goto yy17; + goto yy10; } if (yych <= '\r') { if (yych <= '\t') { if (yych <= 0x08) - goto yy8; - goto yy25; + goto yy9; + goto yy18; } else { if (yych <= '\n') - goto yy22; + goto yy15; if (yych <= '\f') - goto yy25; - goto yy24; + goto yy18; + goto yy17; } } else { if (yych <= ' ') { if (yych <= 0x1F) - goto yy8; - goto yy25; + goto yy9; + goto yy18; } else { if (yych == ':') - goto yy19; - goto yy8; + goto yy12; + goto yy9; } } } @@ -309,6 +225,7 @@ bufsize_t _scan_table_cell(const unsigned char *p) { { unsigned char yych; + unsigned int yyaccept = 0; static const unsigned char yybm[] = { 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 0, 64, 64, 0, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, @@ -326,53 +243,51 @@ bufsize_t _scan_table_cell(const unsigned char *p) { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; - yych = *(marker = p); + yych = *p; if (yybm[0 + yych] & 64) { - goto yy30; + goto yy22; } - if (yych <= 0xE0) { - if (yych <= '\\') { - if (yych <= '\n') - goto yy29; + if (yych <= 0xEC) { + if (yych <= 0xC1) { if (yych <= '\r') - goto yy32; - goto yy34; + goto yy25; + if (yych <= '\\') + goto yy27; + goto yy25; } else { - if (yych <= '|') - goto yy32; - if (yych <= 0xC1) - goto yy29; if (yych <= 0xDF) - goto yy36; - goto yy38; + goto yy29; + if (yych <= 0xE0) + goto yy30; + goto yy31; } } else { - if (yych <= 0xEF) { - if (yych == 0xED) - goto yy40; - goto yy39; + if (yych <= 0xF0) { + if (yych <= 0xED) + goto yy32; + if (yych <= 0xEF) + goto yy31; + goto yy33; } else { - if (yych <= 0xF0) - goto yy41; if (yych <= 0xF3) - goto yy42; + goto yy34; if (yych <= 0xF4) - goto yy43; + goto yy35; + goto yy25; } } - yy29 : { return (bufsize_t)(p - start); } - yy30: + yy22: + yyaccept = 0; yych = *(marker = ++p); if (yybm[0 + yych] & 64) { - goto yy30; + goto yy22; } if (yych <= 0xEC) { if (yych <= 0xC1) { if (yych <= '\r') - goto yy29; + goto yy24; if (yych <= '\\') - goto yy34; - goto yy29; + goto yy27; } else { if (yych <= 0xDF) goto yy36; @@ -392,29 +307,31 @@ bufsize_t _scan_table_cell(const unsigned char *p) { goto yy42; if (yych <= 0xF4) goto yy43; - goto yy29; } } - yy32: + yy24 : { return (bufsize_t)(p - start); } + yy25: ++p; - { return 0; } - yy34: + yy26 : { return 0; } + yy27: + yyaccept = 0; yych = *(marker = ++p); if (yybm[0 + yych] & 128) { - goto yy34; + goto yy27; } if (yych <= 0xDF) { if (yych <= '\f') { if (yych == '\n') - goto yy29; - goto yy30; + goto yy24; + goto yy22; } else { if (yych <= '\r') - goto yy29; + goto yy24; if (yych <= 0x7F) - goto yy30; + goto yy22; if (yych <= 0xC1) - goto yy29; + goto yy24; + goto yy36; } } else { if (yych <= 0xEF) { @@ -430,18 +347,77 @@ bufsize_t _scan_table_cell(const unsigned char *p) { goto yy42; if (yych <= 0xF4) goto yy43; - goto yy29; + goto yy24; } } + yy29: + yych = *++p; + if (yych <= 0x7F) + goto yy26; + if (yych <= 0xBF) + goto yy22; + goto yy26; + yy30: + yyaccept = 1; + yych = *(marker = ++p); + if (yych <= 0x9F) + goto yy26; + if (yych <= 0xBF) + goto yy36; + goto yy26; + yy31: + yyaccept = 1; + yych = *(marker = ++p); + if (yych <= 0x7F) + goto yy26; + if (yych <= 0xBF) + goto yy36; + goto yy26; + yy32: + yyaccept = 1; + yych = *(marker = ++p); + if (yych <= 0x7F) + goto yy26; + if (yych <= 0x9F) + goto yy36; + goto yy26; + yy33: + yyaccept = 1; + yych = *(marker = ++p); + if (yych <= 0x8F) + goto yy26; + if (yych <= 0xBF) + goto yy39; + goto yy26; + yy34: + yyaccept = 1; + yych = *(marker = ++p); + if (yych <= 0x7F) + goto yy26; + if (yych <= 0xBF) + goto yy39; + goto yy26; + yy35: + yyaccept = 1; + yych = *(marker = ++p); + if (yych <= 0x7F) + goto yy26; + if (yych <= 0x8F) + goto yy39; + goto yy26; yy36: yych = *++p; if (yych <= 0x7F) goto yy37; if (yych <= 0xBF) - goto yy30; + goto yy22; yy37: p = marker; - goto yy29; + if (yyaccept == 0) { + goto yy24; + } else { + goto yy26; + } yy38: yych = *++p; if (yych <= 0x9F) @@ -488,12 +464,10 @@ bufsize_t _scan_table_cell(const unsigned char *p) { } bufsize_t _scan_table_cell_end(const unsigned char *p) { - const unsigned char *marker = NULL; const unsigned char *start = p; { unsigned char yych; - unsigned int yyaccept = 0; static const unsigned char yybm[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 128, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, @@ -509,115 +483,17 @@ bufsize_t _scan_table_cell_end(const unsigned char *p) { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; - yych = *(marker = p); - if (yych <= 0xDF) { - if (yych <= '{') { - if (yych != '\n') - goto yy47; - } else { - if (yych <= '|') - goto yy48; - if (yych <= 0x7F) - goto yy47; - if (yych >= 0xC2) - goto yy51; - } - } else { - if (yych <= 0xEF) { - if (yych <= 0xE0) - goto yy53; - if (yych == 0xED) - goto yy55; - goto yy54; - } else { - if (yych <= 0xF0) - goto yy56; - if (yych <= 0xF3) - goto yy57; - if (yych <= 0xF4) - goto yy58; - } - } - yy46 : { return 0; } - yy47: + yych = *p; + if (yych == '|') + goto yy48; ++p; - goto yy46; + { return 0; } yy48: - yyaccept = 1; - yych = *(marker = ++p); + yych = *++p; if (yybm[0 + yych] & 128) { goto yy48; } - if (yych <= 0x08) - goto yy50; - if (yych <= '\n') - goto yy59; - if (yych <= '\r') - goto yy60; - yy50 : { return (bufsize_t)(p - start); } - yy51: - yych = *++p; - if (yych <= 0x7F) - goto yy52; - if (yych <= 0xBF) - goto yy47; - yy52: - p = marker; - if (yyaccept == 0) { - goto yy46; - } else { - goto yy50; - } - yy53: - yych = *++p; - if (yych <= 0x9F) - goto yy52; - if (yych <= 0xBF) - goto yy51; - goto yy52; - yy54: - yych = *++p; - if (yych <= 0x7F) - goto yy52; - if (yych <= 0xBF) - goto yy51; - goto yy52; - yy55: - yych = *++p; - if (yych <= 0x7F) - goto yy52; - if (yych <= 0x9F) - goto yy51; - goto yy52; - yy56: - yych = *++p; - if (yych <= 0x8F) - goto yy52; - if (yych <= 0xBF) - goto yy54; - goto yy52; - yy57: - yych = *++p; - if (yych <= 0x7F) - goto yy52; - if (yych <= 0xBF) - goto yy54; - goto yy52; - yy58: - yych = *++p; - if (yych <= 0x7F) - goto yy52; - if (yych <= 0x8F) - goto yy54; - goto yy52; - yy59: - ++p; - goto yy50; - yy60: - yych = *++p; - if (yych == '\n') - goto yy59; - goto yy52; + { return (bufsize_t)(p - start); } } } @@ -642,138 +518,62 @@ bufsize_t _scan_table_row_end(const unsigned char *p) { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; - yych = *(marker = p); - if (yych <= 0xC1) { - if (yych <= '\f') { - if (yych <= 0x08) - goto yy64; - if (yych == '\n') - goto yy66; - goto yy65; - } else { - if (yych <= 0x1F) { - if (yych <= '\r') - goto yy68; - goto yy64; - } else { - if (yych <= ' ') - goto yy65; - if (yych <= 0x7F) - goto yy64; - } - } + yych = *p; + if (yych <= '\f') { + if (yych <= 0x08) + goto yy53; + if (yych == '\n') + goto yy56; + goto yy55; } else { - if (yych <= 0xED) { - if (yych <= 0xDF) - goto yy69; - if (yych <= 0xE0) - goto yy71; - if (yych <= 0xEC) - goto yy72; - goto yy73; - } else { - if (yych <= 0xF0) { - if (yych <= 0xEF) - goto yy72; - goto yy74; - } else { - if (yych <= 0xF3) - goto yy75; - if (yych <= 0xF4) - goto yy76; - } - } + if (yych <= '\r') + goto yy58; + if (yych == ' ') + goto yy55; } - yy63 : { return 0; } - yy64: + yy53: ++p; - goto yy63; - yy65: + yy54 : { return 0; } + yy55: yych = *(marker = ++p); if (yych <= 0x08) - goto yy63; + goto yy54; if (yych <= '\r') - goto yy78; + goto yy60; if (yych == ' ') - goto yy78; - goto yy63; - yy66: + goto yy60; + goto yy54; + yy56: ++p; { return (bufsize_t)(p - start); } - yy68: + yy58: yych = *++p; if (yych == '\n') - goto yy66; - goto yy63; - yy69: - yych = *++p; - if (yych <= 0x7F) - goto yy70; - if (yych <= 0xBF) - goto yy64; - yy70: - p = marker; - goto yy63; - yy71: - yych = *++p; - if (yych <= 0x9F) - goto yy70; - if (yych <= 0xBF) - goto yy69; - goto yy70; - yy72: - yych = *++p; - if (yych <= 0x7F) - goto yy70; - if (yych <= 0xBF) - goto yy69; - goto yy70; - yy73: - yych = *++p; - if (yych <= 0x7F) - goto yy70; - if (yych <= 0x9F) - goto yy69; - goto yy70; - yy74: - yych = *++p; - if (yych <= 0x8F) - goto yy70; - if (yych <= 0xBF) - goto yy72; - goto yy70; - yy75: - yych = *++p; - if (yych <= 0x7F) - goto yy70; - if (yych <= 0xBF) - goto yy72; - goto yy70; - yy76: - yych = *++p; - if (yych <= 0x7F) - goto yy70; - if (yych <= 0x8F) - goto yy72; - goto yy70; - yy77: + goto yy56; + goto yy54; + yy59: yych = *++p; - yy78: + yy60: if (yybm[0 + yych] & 128) { - goto yy77; + goto yy59; } if (yych <= 0x08) - goto yy70; + goto yy61; if (yych <= '\n') - goto yy66; - if (yych >= 0x0E) - goto yy70; + goto yy56; + if (yych <= '\r') + goto yy62; + yy61: + p = marker; + goto yy54; + yy62: yych = *++p; if (yych == '\n') - goto yy66; - goto yy70; + goto yy56; + goto yy61; } } + bufsize_t _scan_tasklist(const unsigned char *p) { const unsigned char *marker = NULL; const unsigned char *start = p; @@ -798,361 +598,281 @@ bufsize_t _scan_tasklist(const unsigned char *p) { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; - yych = *(marker = p); - if (yych <= '/') { - if (yych <= 0x1F) { - if (yych <= '\t') { - if (yych <= 0x08) - goto yy83; - goto yy84; - } else { - if (yych <= '\n') - goto yy82; - if (yych <= '\f') - goto yy84; - goto yy83; - } + yych = *p; + if (yych <= ' ') { + if (yych <= '\n') { + if (yych == '\t') + goto yy67; } else { - if (yych <= '+') { - if (yych <= ' ') - goto yy84; - if (yych <= ')') - goto yy83; - goto yy85; - } else { - if (yych == '-') - goto yy85; - goto yy83; - } + if (yych <= '\f') + goto yy67; + if (yych >= ' ') + goto yy67; } } else { - if (yych <= 0xEC) { - if (yych <= 0xC1) { - if (yych <= '9') - goto yy86; - if (yych <= 0x7F) - goto yy83; - } else { - if (yych <= 0xDF) - goto yy87; - if (yych <= 0xE0) - goto yy89; - goto yy90; - } + if (yych <= ',') { + if (yych <= ')') + goto yy65; + if (yych <= '+') + goto yy68; } else { - if (yych <= 0xF0) { - if (yych <= 0xED) - goto yy91; - if (yych <= 0xEF) - goto yy90; - goto yy92; - } else { - if (yych <= 0xF3) - goto yy93; - if (yych <= 0xF4) - goto yy94; - } + if (yych <= '-') + goto yy68; + if (yych <= '/') + goto yy65; + if (yych <= '9') + goto yy69; } } - yy82 : { return 0; } - yy83: + yy65: ++p; - goto yy82; - yy84: + yy66 : { return 0; } + yy67: yych = *(marker = ++p); if (yybm[0 + yych] & 64) { - goto yy95; + goto yy70; } if (yych <= ',') { if (yych <= ')') - goto yy82; + goto yy66; if (yych <= '+') - goto yy97; - goto yy82; + goto yy73; + goto yy66; } else { if (yych <= '-') - goto yy97; + goto yy73; if (yych <= '/') - goto yy82; + goto yy66; if (yych <= '9') - goto yy98; - goto yy82; + goto yy74; + goto yy66; } - yy85: + yy68: yych = *(marker = ++p); if (yych <= '\n') { if (yych == '\t') - goto yy99; - goto yy82; + goto yy75; + goto yy66; } else { if (yych <= '\f') - goto yy99; + goto yy75; if (yych == ' ') - goto yy99; - goto yy82; + goto yy75; + goto yy66; } - yy86: + yy69: yych = *(marker = ++p); if (yych <= 0x1F) { if (yych <= '\t') { if (yych <= 0x08) - goto yy102; - goto yy97; + goto yy78; + goto yy73; } else { if (yych <= '\n') - goto yy82; + goto yy66; if (yych <= '\f') - goto yy97; - goto yy102; + goto yy73; + goto yy78; } } else { if (yych <= 0x7F) { if (yych <= ' ') - goto yy97; - goto yy102; + goto yy73; + goto yy78; } else { if (yych <= 0xC1) - goto yy82; + goto yy66; if (yych <= 0xF4) - goto yy102; - goto yy82; + goto yy78; + goto yy66; } } - yy87: - yych = *++p; - if (yych <= 0x7F) - goto yy88; - if (yych <= 0xBF) - goto yy83; - yy88: - p = marker; - goto yy82; - yy89: - yych = *++p; - if (yych <= 0x9F) - goto yy88; - if (yych <= 0xBF) - goto yy87; - goto yy88; - yy90: - yych = *++p; - if (yych <= 0x7F) - goto yy88; - if (yych <= 0xBF) - goto yy87; - goto yy88; - yy91: - yych = *++p; - if (yych <= 0x7F) - goto yy88; - if (yych <= 0x9F) - goto yy87; - goto yy88; - yy92: - yych = *++p; - if (yych <= 0x8F) - goto yy88; - if (yych <= 0xBF) - goto yy90; - goto yy88; - yy93: - yych = *++p; - if (yych <= 0x7F) - goto yy88; - if (yych <= 0xBF) - goto yy90; - goto yy88; - yy94: - yych = *++p; - if (yych <= 0x7F) - goto yy88; - if (yych <= 0x8F) - goto yy90; - goto yy88; - yy95: + yy70: yych = *++p; if (yybm[0 + yych] & 64) { - goto yy95; + goto yy70; } if (yych <= ',') { if (yych <= ')') - goto yy88; - if (yych >= ',') - goto yy88; + goto yy72; + if (yych <= '+') + goto yy73; } else { if (yych <= '-') - goto yy97; + goto yy73; if (yych <= '/') - goto yy88; + goto yy72; if (yych <= '9') - goto yy98; - goto yy88; + goto yy74; } - yy97: + yy72: + p = marker; + goto yy66; + yy73: yych = *++p; if (yych == '[') - goto yy88; - goto yy100; - yy98: + goto yy72; + goto yy76; + yy74: yych = *++p; if (yych <= '\n') { if (yych == '\t') - goto yy97; - goto yy102; + goto yy73; + goto yy78; } else { if (yych <= '\f') - goto yy97; + goto yy73; if (yych == ' ') - goto yy97; - goto yy102; + goto yy73; + goto yy78; } - yy99: + yy75: yych = *++p; - yy100: + yy76: if (yych <= '\f') { if (yych == '\t') - goto yy99; + goto yy75; if (yych <= '\n') - goto yy88; - goto yy99; + goto yy72; + goto yy75; } else { if (yych <= ' ') { if (yych <= 0x1F) - goto yy88; - goto yy99; + goto yy72; + goto yy75; } else { if (yych == '[') - goto yy110; - goto yy88; + goto yy86; + goto yy72; } } - yy101: + yy77: yych = *++p; - yy102: + yy78: if (yybm[0 + yych] & 128) { - goto yy101; + goto yy77; } if (yych <= 0xC1) { if (yych <= '\f') { if (yych <= 0x08) - goto yy97; + goto yy73; if (yych == '\n') - goto yy88; - goto yy99; + goto yy72; + goto yy75; } else { if (yych == ' ') - goto yy99; + goto yy75; if (yych <= 0x7F) - goto yy97; - goto yy88; + goto yy73; + goto yy72; } } else { if (yych <= 0xED) { if (yych <= 0xDF) - goto yy103; + goto yy79; if (yych <= 0xE0) - goto yy104; + goto yy80; if (yych <= 0xEC) - goto yy105; - goto yy106; + goto yy81; + goto yy82; } else { if (yych <= 0xF0) { if (yych <= 0xEF) - goto yy105; - goto yy107; + goto yy81; + goto yy83; } else { if (yych <= 0xF3) - goto yy108; + goto yy84; if (yych <= 0xF4) - goto yy109; - goto yy88; + goto yy85; + goto yy72; } } } - yy103: + yy79: yych = *++p; if (yych <= 0x7F) - goto yy88; + goto yy72; if (yych <= 0xBF) - goto yy97; - goto yy88; - yy104: + goto yy73; + goto yy72; + yy80: yych = *++p; if (yych <= 0x9F) - goto yy88; + goto yy72; if (yych <= 0xBF) - goto yy103; - goto yy88; - yy105: + goto yy79; + goto yy72; + yy81: yych = *++p; if (yych <= 0x7F) - goto yy88; + goto yy72; if (yych <= 0xBF) - goto yy103; - goto yy88; - yy106: + goto yy79; + goto yy72; + yy82: yych = *++p; if (yych <= 0x7F) - goto yy88; + goto yy72; if (yych <= 0x9F) - goto yy103; - goto yy88; - yy107: + goto yy79; + goto yy72; + yy83: yych = *++p; if (yych <= 0x8F) - goto yy88; + goto yy72; if (yych <= 0xBF) - goto yy105; - goto yy88; - yy108: + goto yy81; + goto yy72; + yy84: yych = *++p; if (yych <= 0x7F) - goto yy88; + goto yy72; if (yych <= 0xBF) - goto yy105; - goto yy88; - yy109: + goto yy81; + goto yy72; + yy85: yych = *++p; if (yych <= 0x7F) - goto yy88; + goto yy72; if (yych <= 0x8F) - goto yy105; - goto yy88; - yy110: + goto yy81; + goto yy72; + yy86: yych = *++p; if (yych <= 'W') { if (yych != ' ') - goto yy88; + goto yy72; } else { if (yych <= 'X') - goto yy111; + goto yy87; if (yych != 'x') - goto yy88; + goto yy72; } - yy111: + yy87: yych = *++p; if (yych != ']') - goto yy88; + goto yy72; yych = *++p; if (yych <= '\n') { if (yych != '\t') - goto yy88; + goto yy72; } else { if (yych <= '\f') - goto yy113; + goto yy89; if (yych != ' ') - goto yy88; + goto yy72; } - yy113: + yy89: yych = *++p; if (yych <= '\n') { if (yych == '\t') - goto yy113; + goto yy89; } else { if (yych <= '\f') - goto yy113; + goto yy89; if (yych == ' ') - goto yy113; + goto yy89; } { return (bufsize_t)(p - start); } } diff --git a/src/plugins/ieviewer/cmark-gfm/extensions/ext_scanners.re b/src/plugins/ieviewer/cmark-gfm/extensions/ext_scanners.re index 94a4c673..9144e5b4 100644 --- a/src/plugins/ieviewer/cmark-gfm/extensions/ext_scanners.re +++ b/src/plugins/ieviewer/cmark-gfm/extensions/ext_scanners.re @@ -1,3 +1,6 @@ +/*!re2c re2c:flags:no-debug-info = 1; */ +/*!re2c re2c:indent:string = ' '; */ + #include #include "ext_scanners.h" @@ -22,7 +25,6 @@ bufsize_t _ext_scan_at(bufsize_t (*scanner)(const unsigned char *), unsigned cha re2c:define:YYCTYPE = "unsigned char"; re2c:define:YYCURSOR = p; re2c:define:YYMARKER = marker; - re2c:define:YYCTXMARKER = marker; re2c:yyfill:enable = 0; spacechar = [ \t\v\f]; @@ -30,7 +32,7 @@ bufsize_t _ext_scan_at(bufsize_t (*scanner)(const unsigned char *), unsigned cha escaped_char = [\\][|!"#$%&'()*+,./:;<=>?@[\\\]^_`{}~-]; table_marker = (spacechar*[:]?[-]+[:]?spacechar*); - table_cell = (escaped_char|[^|\r\n])*; + table_cell = (escaped_char|[^|\r\n])+; tasklist = spacechar*("-"|"+"|"*"|[0-9]+.)spacechar+("[ ]"|"[x]")spacechar+; */ @@ -39,47 +41,52 @@ bufsize_t _scan_table_start(const unsigned char *p) { const unsigned char *marker = NULL; const unsigned char *start = p; -/*!re2c - [|]? table_marker ([|] table_marker)* [|]? spacechar* newline { return (bufsize_t)(p - start); } - .? { return 0; } -*/ + /*!re2c + [|]? table_marker ([|] table_marker)* [|]? spacechar* newline { + return (bufsize_t)(p - start); + } + * { return 0; } + */ } bufsize_t _scan_table_cell(const unsigned char *p) { const unsigned char *marker = NULL; const unsigned char *start = p; -/*!re2c - table_cell { return (bufsize_t)(p - start); } - .? { return 0; } -*/ + /*!re2c + // In fact, `table_cell` matches non-empty table cells only. The empty + // string is also a valid table cell, but is handled by the default rule. + // This approach prevents re2c's match-empty-string warning. + table_cell { return (bufsize_t)(p - start); } + * { return 0; } + */ } bufsize_t _scan_table_cell_end(const unsigned char *p) { - const unsigned char *marker = NULL; const unsigned char *start = p; -/*!re2c - [|] spacechar* newline? { return (bufsize_t)(p - start); } - .? { return 0; } -*/ + /*!re2c + [|] spacechar* { return (bufsize_t)(p - start); } + * { return 0; } + */ } bufsize_t _scan_table_row_end(const unsigned char *p) { const unsigned char *marker = NULL; const unsigned char *start = p; -/*!re2c - spacechar* newline { return (bufsize_t)(p - start); } - .? { return 0; } -*/ + /*!re2c + spacechar* newline { return (bufsize_t)(p - start); } + * { return 0; } + */ } + bufsize_t _scan_tasklist(const unsigned char *p) { const unsigned char *marker = NULL; const unsigned char *start = p; -/*!re2c - tasklist { return (bufsize_t)(p - start); } - .? { return 0; } -*/ + /*!re2c + tasklist { return (bufsize_t)(p - start); } + * { return 0; } + */ } diff --git a/src/plugins/ieviewer/cmark-gfm/extensions/strikethrough.c b/src/plugins/ieviewer/cmark-gfm/extensions/strikethrough.c index 8145d23b..e0884224 100644 --- a/src/plugins/ieviewer/cmark-gfm/extensions/strikethrough.c +++ b/src/plugins/ieviewer/cmark-gfm/extensions/strikethrough.c @@ -67,6 +67,7 @@ static delimiter *insert(cmark_syntax_extension *self, cmark_parser *parser, strikethrough->end_column = closer->inl_text->start_column + closer->inl_text->as.literal.len - 1; cmark_node_free(closer->inl_text); +done: delim = closer; while (delim != NULL && delim != opener) { tmp_delim = delim->previous; @@ -76,7 +77,6 @@ static delimiter *insert(cmark_syntax_extension *self, cmark_parser *parser, cmark_inline_parser_remove_delimiter(inline_parser, opener); -done: return res; } diff --git a/src/plugins/ieviewer/cmark-gfm/extensions/table.c b/src/plugins/ieviewer/cmark-gfm/extensions/table.c index 0ea31cbc..e8359f25 100644 --- a/src/plugins/ieviewer/cmark-gfm/extensions/table.c +++ b/src/plugins/ieviewer/cmark-gfm/extensions/table.c @@ -11,42 +11,55 @@ #include "table.h" #include "cmark-gfm-core-extensions.h" +// Limit to prevent a malicious input from causing a denial of service. +#define MAX_AUTOCOMPLETED_CELLS 0x80000 + +// Custom node flag, initialized in `create_table_extension`. +static cmark_node_internal_flags CMARK_NODE__TABLE_VISITED; + cmark_node_type CMARK_NODE_TABLE, CMARK_NODE_TABLE_ROW, CMARK_NODE_TABLE_CELL; +typedef struct { + cmark_strbuf *buf; + int start_offset, end_offset, internal_offset; +} node_cell; + typedef struct { uint16_t n_columns; int paragraph_offset; - cmark_llist *cells; + node_cell *cells; } table_row; typedef struct { uint16_t n_columns; uint8_t *alignments; + int n_rows; + int n_nonempty_cells; } node_table; typedef struct { bool is_header; } node_table_row; -typedef struct { - cmark_strbuf *buf; - int start_offset, end_offset, internal_offset; -} node_cell; - -static void free_table_cell(cmark_mem *mem, void *data) { - node_cell *cell = (node_cell *)data; +static void free_table_cell(cmark_mem *mem, node_cell *cell) { cmark_strbuf_free((cmark_strbuf *)cell->buf); mem->free(cell->buf); - mem->free(cell); +} + +static void free_row_cells(cmark_mem *mem, table_row *row) { + while (row->n_columns > 0) { + free_table_cell(mem, &row->cells[--row->n_columns]); + } + mem->free(row->cells); + row->cells = NULL; } static void free_table_row(cmark_mem *mem, table_row *row) { if (!row) return; - cmark_llist_free_full(mem, row->cells, (cmark_free_func)free_table_cell); - + free_row_cells(mem, row); mem->free(row); } @@ -75,6 +88,33 @@ static int set_n_table_columns(cmark_node *node, uint16_t n_columns) { return 1; } +// Increment the number of rows in the table. Also update n_nonempty_cells, +// which keeps track of the number of cells which were parsed from the +// input file. (If one of the rows is too short, then the trailing cells +// are autocompleted. Autocompleted cells are not counted in n_nonempty_cells.) +// The purpose of this is to prevent a malicious input from generating a very +// large number of autocompleted cells, which could cause a denial of service +// vulnerability. +static int incr_table_row_count(cmark_node *node, int i) { + if (!node || node->type != CMARK_NODE_TABLE) { + return 0; + } + + ((node_table *)node->as.opaque)->n_rows++; + ((node_table *)node->as.opaque)->n_nonempty_cells += i; + return 1; +} + +// Calculate the number of autocompleted cells. +static int get_n_autocompleted_cells(cmark_node *node) { + if (!node || node->type != CMARK_NODE_TABLE) { + return 0; + } + + const node_table *nt = (node_table *)node->as.opaque; + return (nt->n_columns * nt->n_rows) - nt->n_nonempty_cells; +} + static uint8_t *get_table_alignments(cmark_node *node) { if (!node || node->type != CMARK_NODE_TABLE) return 0; @@ -90,6 +130,23 @@ static int set_table_alignments(cmark_node *node, uint8_t *alignments) { return 1; } +static uint8_t get_cell_alignment(cmark_node *node) { + if (!node || node->type != CMARK_NODE_TABLE_CELL) + return 0; + + const uint8_t *alignments = get_table_alignments(node->parent->parent); + int i = node->as.cell_index; + return alignments[i]; +} + +static int set_cell_index(cmark_node *node, int i) { + if (!node || node->type != CMARK_NODE_TABLE_CELL) + return 0; + + node->as.cell_index = i; + return 1; +} + static cmark_strbuf *unescape_pipes(cmark_mem *mem, unsigned char *string, bufsize_t len) { cmark_strbuf *res = (cmark_strbuf *)mem->calloc(1, sizeof(cmark_strbuf)); @@ -111,63 +168,111 @@ static cmark_strbuf *unescape_pipes(cmark_mem *mem, unsigned char *string, bufsi return res; } +// Adds a new cell to the end of the row. A pointer to the new cell is returned +// for the caller to initialize. +static node_cell* append_row_cell(cmark_mem *mem, table_row *row) { + const uint32_t n_columns = row->n_columns + 1; + // realloc when n_columns is a power of 2 + if ((n_columns & (n_columns-1)) == 0) { + // make sure we never wrap row->n_columns + // offset will != len and our exit will clean up as intended + if (n_columns > UINT16_MAX) { + return NULL; + } + // Use realloc to double the size of the buffer. + row->cells = (node_cell *)mem->realloc(row->cells, (2 * n_columns - 1) * sizeof(node_cell)); + } + row->n_columns = (uint16_t)n_columns; + return &row->cells[n_columns-1]; +} + static table_row *row_from_string(cmark_syntax_extension *self, cmark_parser *parser, unsigned char *string, int len) { + // Parses a single table row. It has the following form: + // `delim? table_cell (delim table_cell)* delim? newline` + // Note that cells are allowed to be empty. + // + // From the GitHub-flavored Markdown specification: + // + // > Each row consists of cells containing arbitrary text, in which inlines + // > are parsed, separated by pipes (|). A leading and trailing pipe is also + // > recommended for clarity of reading, and if there’s otherwise parsing + // > ambiguity. + table_row *row = NULL; bufsize_t cell_matched = 1, pipe_matched = 1, offset; - int cell_end_offset; + int expect_more_cells = 1; + int row_end_offset = 0; + int int_overflow_abort = 0; row = (table_row *)parser->mem->calloc(1, sizeof(table_row)); row->n_columns = 0; row->cells = NULL; + // Scan past the (optional) leading pipe. offset = scan_table_cell_end(string, len, 0); // Parse the cells of the row. Stop if we reach the end of the input, or if we // cannot detect any more cells. - while (offset < len && (cell_matched || pipe_matched)) { + while (offset < len && expect_more_cells) { cell_matched = scan_table_cell(string, len, offset); pipe_matched = scan_table_cell_end(string, len, offset + cell_matched); if (cell_matched || pipe_matched) { - cell_end_offset = offset + cell_matched - 1; - - if (string[cell_end_offset] == '\n' || string[cell_end_offset] == '\r') { - row->paragraph_offset = cell_end_offset; - - cmark_llist_free_full(parser->mem, row->cells, (cmark_free_func)free_table_cell); - row->cells = NULL; - row->n_columns = 0; - } else { - cmark_strbuf *cell_buf = unescape_pipes(parser->mem, string + offset, - cell_matched); - cmark_strbuf_trim(cell_buf); - - node_cell *cell = (node_cell *)parser->mem->calloc(1, sizeof(*cell)); - cell->buf = cell_buf; - cell->start_offset = offset; - cell->end_offset = cell_end_offset; - - while (cell->start_offset > 0 && string[cell->start_offset - 1] != '|') { - --cell->start_offset; - ++cell->internal_offset; - } - - row->n_columns += 1; - row->cells = cmark_llist_append(parser->mem, row->cells, cell); + // We are guaranteed to have a cell, since (1) either we found some + // content and cell_matched, or (2) we found an empty cell followed by a + // pipe. + cmark_strbuf *cell_buf = unescape_pipes(parser->mem, string + offset, + cell_matched); + cmark_strbuf_trim(cell_buf); + + node_cell *cell = append_row_cell(parser->mem, row); + if (!cell) { + int_overflow_abort = 1; + cmark_strbuf_free(cell_buf); + parser->mem->free(cell_buf); + break; + } + cell->buf = cell_buf; + cell->start_offset = offset; + cell->end_offset = offset + cell_matched - 1; + cell->internal_offset = 0; + + while (cell->start_offset > row->paragraph_offset && string[cell->start_offset - 1] != '|') { + --cell->start_offset; + ++cell->internal_offset; } } offset += cell_matched + pipe_matched; - if (!pipe_matched) { - pipe_matched = scan_table_row_end(string, len, offset); - offset += pipe_matched; + if (pipe_matched) { + expect_more_cells = 1; + } else { + // We've scanned the last cell. Check if we have reached the end of the row + row_end_offset = scan_table_row_end(string, len, offset); + offset += row_end_offset; + + // If the end of the row is not the end of the input, + // the row is not a real row but potentially part of the paragraph + // preceding the table. + if (row_end_offset && offset != len) { + row->paragraph_offset = offset; + + free_row_cells(parser->mem, row); + + // Scan past the (optional) leading pipe. + offset += scan_table_cell_end(string, len, offset); + + expect_more_cells = 1; + } else { + expect_more_cells = 0; + } } } - if (offset != len || !row->n_columns) { + if (offset != len || row->n_columns == 0 || int_overflow_abort) { free_table_row(parser->mem, row); row = NULL; } @@ -199,55 +304,65 @@ static cmark_node *try_opening_table_header(cmark_syntax_extension *self, cmark_parser *parser, cmark_node *parent_container, unsigned char *input, int len) { - bufsize_t matched = - scan_table_start(input, len, cmark_parser_get_first_nonspace(parser)); cmark_node *table_header; table_row *header_row = NULL; - table_row *marker_row = NULL; + table_row *delimiter_row = NULL; node_table_row *ntr; const char *parent_string; uint16_t i; - if (!matched) + if (parent_container->flags & CMARK_NODE__TABLE_VISITED) { return parent_container; + } - parent_string = cmark_node_get_string_content(parent_container); - - cmark_arena_push(); - - header_row = row_from_string(self, parser, (unsigned char *)parent_string, - (int)strlen(parent_string)); - - if (!header_row) { - free_table_row(parser->mem, header_row); - cmark_arena_pop(); + if (!scan_table_start(input, len, cmark_parser_get_first_nonspace(parser))) { return parent_container; } - marker_row = row_from_string(self, parser, - input + cmark_parser_get_first_nonspace(parser), - len - cmark_parser_get_first_nonspace(parser)); + // Since scan_table_start was successful, we must have a delimiter row. + delimiter_row = row_from_string( + self, parser, input + cmark_parser_get_first_nonspace(parser), + len - cmark_parser_get_first_nonspace(parser)); + // assert may be optimized out, don't rely on it for security boundaries + if (!delimiter_row) { + return parent_container; + } - assert(marker_row); + assert(delimiter_row); + + cmark_arena_push(); - if (header_row->n_columns != marker_row->n_columns) { + // Check for a matching header row. We call `row_from_string` with the entire + // (potentially long) parent container as input, but this should be safe since + // `row_from_string` bails out early if it does not find a row. + parent_string = cmark_node_get_string_content(parent_container); + header_row = row_from_string(self, parser, (unsigned char *)parent_string, + (int)strlen(parent_string)); + if (!header_row || header_row->n_columns != delimiter_row->n_columns) { + free_table_row(parser->mem, delimiter_row); free_table_row(parser->mem, header_row); - free_table_row(parser->mem, marker_row); cmark_arena_pop(); + parent_container->flags |= CMARK_NODE__TABLE_VISITED; return parent_container; } if (cmark_arena_pop()) { + delimiter_row = row_from_string( + self, parser, input + cmark_parser_get_first_nonspace(parser), + len - cmark_parser_get_first_nonspace(parser)); header_row = row_from_string(self, parser, (unsigned char *)parent_string, (int)strlen(parent_string)); - marker_row = row_from_string(self, parser, - input + cmark_parser_get_first_nonspace(parser), - len - cmark_parser_get_first_nonspace(parser)); + // row_from_string can return NULL, add additional check to ensure n_columns match + if (!delimiter_row || !header_row || header_row->n_columns != delimiter_row->n_columns) { + free_table_row(parser->mem, delimiter_row); + free_table_row(parser->mem, header_row); + return parent_container; + } } if (!cmark_node_set_type(parent_container, CMARK_NODE_TABLE)) { free_table_row(parser->mem, header_row); - free_table_row(parser->mem, marker_row); + free_table_row(parser->mem, delimiter_row); return parent_container; } @@ -257,16 +372,15 @@ static cmark_node *try_opening_table_header(cmark_syntax_extension *self, } cmark_node_set_syntax_extension(parent_container, self); - parent_container->as.opaque = parser->mem->calloc(1, sizeof(node_table)); - set_n_table_columns(parent_container, header_row->n_columns); + // allocate alignments based on delimiter_row->n_columns + // since we populate the alignments array based on delimiter_row->cells uint8_t *alignments = - (uint8_t *)parser->mem->calloc(header_row->n_columns, sizeof(uint8_t)); - cmark_llist *it = marker_row->cells; - for (i = 0; it; it = it->next, ++i) { - node_cell *node = (node_cell *)it->data; + (uint8_t *)parser->mem->calloc(delimiter_row->n_columns, sizeof(uint8_t)); + for (i = 0; i < delimiter_row->n_columns; ++i) { + node_cell *node = &delimiter_row->cells[i]; bool left = node->buf->ptr[0] == ':', right = node->buf->ptr[node->buf->size - 1] == ':'; if (left && right) @@ -288,27 +402,26 @@ static cmark_node *try_opening_table_header(cmark_syntax_extension *self, table_header->as.opaque = ntr = (node_table_row *)parser->mem->calloc(1, sizeof(node_table_row)); ntr->is_header = true; - { - cmark_llist *tmp; - - for (tmp = header_row->cells; tmp; tmp = tmp->next) { - node_cell *cell = (node_cell *) tmp->data; - cmark_node *header_cell = cmark_parser_add_child(parser, table_header, - CMARK_NODE_TABLE_CELL, parent_container->start_column + cell->start_offset); - header_cell->start_line = header_cell->end_line = parent_container->start_line; - header_cell->internal_offset = cell->internal_offset; - header_cell->end_column = parent_container->start_column + cell->end_offset; - cmark_node_set_string_content(header_cell, (char *) cell->buf->ptr); - cmark_node_set_syntax_extension(header_cell, self); - } + for (i = 0; i < header_row->n_columns; ++i) { + node_cell *cell = &header_row->cells[i]; + cmark_node *header_cell = cmark_parser_add_child(parser, table_header, + CMARK_NODE_TABLE_CELL, parent_container->start_column + cell->start_offset); + header_cell->start_line = header_cell->end_line = parent_container->start_line; + header_cell->internal_offset = cell->internal_offset; + header_cell->end_column = parent_container->start_column + cell->end_offset; + cmark_node_set_string_content(header_cell, (char *) cell->buf->ptr); + cmark_node_set_syntax_extension(header_cell, self); + set_cell_index(header_cell, i); } + incr_table_row_count(parent_container, i); + cmark_parser_advance_offset( parser, (char *)input, (int)strlen((char *)input) - 1 - cmark_parser_get_offset(parser), false); free_table_row(parser->mem, header_row); - free_table_row(parser->mem, marker_row); + free_table_row(parser->mem, delimiter_row); return parent_container; } @@ -322,6 +435,10 @@ static cmark_node *try_opening_table_row(cmark_syntax_extension *self, if (cmark_parser_is_blank(parser)) return NULL; + if (get_n_autocompleted_cells(parent_container) > MAX_AUTOCOMPLETED_CELLS) { + return NULL; + } + table_row_block = cmark_parser_add_child(parser, parent_container, CMARK_NODE_TABLE_ROW, parent_container->start_column); @@ -332,24 +449,33 @@ static cmark_node *try_opening_table_row(cmark_syntax_extension *self, row = row_from_string(self, parser, input + cmark_parser_get_first_nonspace(parser), len - cmark_parser_get_first_nonspace(parser)); + if (!row) { + // clean up the dangling node + cmark_node_free(table_row_block); + return NULL; + } + { - cmark_llist *tmp; int i, table_columns = get_n_table_columns(parent_container); - for (tmp = row->cells, i = 0; tmp && i < table_columns; tmp = tmp->next, ++i) { - node_cell *cell = (node_cell *) tmp->data; + for (i = 0; i < row->n_columns && i < table_columns; ++i) { + node_cell *cell = &row->cells[i]; cmark_node *node = cmark_parser_add_child(parser, table_row_block, CMARK_NODE_TABLE_CELL, parent_container->start_column + cell->start_offset); node->internal_offset = cell->internal_offset; node->end_column = parent_container->start_column + cell->end_offset; cmark_node_set_string_content(node, (char *) cell->buf->ptr); cmark_node_set_syntax_extension(node, self); + set_cell_index(node, i); } + incr_table_row_count(parent_container, i); + for (; i < table_columns; ++i) { cmark_node *node = cmark_parser_add_child( parser, table_row_block, CMARK_NODE_TABLE_CELL, 0); cmark_node_set_syntax_extension(node, self); + set_cell_index(node, i); } } @@ -534,13 +660,7 @@ static const char *xml_attr(cmark_syntax_extension *extension, cmark_node *node) { if (node->type == CMARK_NODE_TABLE_CELL) { if (cmark_gfm_extensions_get_table_row_is_header(node->parent)) { - uint8_t *alignments = get_table_alignments(node->parent->parent); - int i = 0; - cmark_node *n; - for (n = node->parent->first_child; n; n = n->next, ++i) - if (n == node) - break; - switch (alignments[i]) { + switch (get_cell_alignment(node)) { case 'l': return " align=\"left\""; case 'c': return " align=\"center\""; case 'r': return " align=\"right\""; @@ -628,7 +748,6 @@ static void html_render(cmark_syntax_extension *extension, cmark_event_type ev_type, int options) { bool entering = (ev_type == CMARK_EVENT_ENTER); cmark_strbuf *html = renderer->html; - cmark_node *n; // XXX: we just monopolise renderer->opaque. struct html_table_state *table_state = @@ -677,7 +796,6 @@ static void html_render(cmark_syntax_extension *extension, } } } else if (node->type == CMARK_NODE_TABLE_CELL) { - uint8_t *alignments = get_table_alignments(node->parent->parent); if (entering) { cmark_html_render_cr(html); if (table_state->in_table_header) { @@ -686,12 +804,7 @@ static void html_render(cmark_syntax_extension *extension, cmark_strbuf_puts(html, "parent->first_child; n; n = n->next, ++i) - if (n == node) - break; - - switch (alignments[i]) { + switch (get_cell_alignment(node)) { case 'l': html_table_add_align(html, "left", options); break; case 'c': html_table_add_align(html, "center", options); break; case 'r': html_table_add_align(html, "right", options); break; @@ -740,6 +853,7 @@ static int escape(cmark_syntax_extension *self, cmark_node *node, int c) { cmark_syntax_extension *create_table_extension(void) { cmark_syntax_extension *self = cmark_syntax_extension_new("table"); + cmark_register_node_flag(&CMARK_NODE__TABLE_VISITED); cmark_syntax_extension_set_match_block_func(self, matches); cmark_syntax_extension_set_open_block_func(self, try_opening_table_block); cmark_syntax_extension_set_get_type_string_func(self, get_type_string); diff --git a/src/plugins/ieviewer/cmark-gfm/extensions/tasklist.c b/src/plugins/ieviewer/cmark-gfm/extensions/tasklist.c index df11c413..7bef4549 100644 --- a/src/plugins/ieviewer/cmark-gfm/extensions/tasklist.c +++ b/src/plugins/ieviewer/cmark-gfm/extensions/tasklist.c @@ -23,21 +23,15 @@ int cmark_gfm_extensions_set_tasklist_item_checked(cmark_node *node, bool is_che if (!node || !node->extension || strcmp(cmark_node_get_type_string(node), TYPE_STRING)) return 0; - if (is_checked) { - node->as.opaque = (void *)CMARK_TASKLIST_CHECKED; - return 1; - } - else { - node->as.opaque = (void *)CMARK_TASKLIST_NOCHECKED; - return 1; - } + node->as.list.checked = is_checked; + return 1; } bool cmark_gfm_extensions_get_tasklist_item_checked(cmark_node *node) { if (!node || !node->extension || strcmp(cmark_node_get_type_string(node), TYPE_STRING)) return false; - if (node->as.opaque == (void *)CMARK_TASKLIST_CHECKED) { + if (node->as.list.checked) { return true; } else { @@ -95,11 +89,7 @@ static cmark_node *open_tasklist_item(cmark_syntax_extension *self, cmark_parser_advance_offset(parser, (char *)input, 3, false); // Either an upper or lower case X means the task is completed. - if (strstr((char*)input, "[x]") || strstr((char*)input, "[X]")) { - parent_container->as.opaque = (void *)CMARK_TASKLIST_CHECKED; - } else { - parent_container->as.opaque = (void *)CMARK_TASKLIST_NOCHECKED; - } + parent_container->as.list.checked = (strstr((char*)input, "[x]") || strstr((char*)input, "[X]")); return NULL; } @@ -110,7 +100,7 @@ static void commonmark_render(cmark_syntax_extension *extension, bool entering = (ev_type == CMARK_EVENT_ENTER); if (entering) { renderer->cr(renderer); - if (node->as.opaque == (void *)CMARK_TASKLIST_CHECKED) { + if (node->as.list.checked) { renderer->out(renderer, node, "- [x] ", false, LITERAL); } else { renderer->out(renderer, node, "- [ ] ", false, LITERAL); @@ -131,7 +121,7 @@ static void html_render(cmark_syntax_extension *extension, cmark_strbuf_puts(renderer->html, "html, options); cmark_strbuf_putc(renderer->html, '>'); - if (node->as.opaque == (void *)CMARK_TASKLIST_CHECKED) { + if (node->as.list.checked) { cmark_strbuf_puts(renderer->html, " "); } else { cmark_strbuf_puts(renderer->html, " "); @@ -143,7 +133,7 @@ static void html_render(cmark_syntax_extension *extension, static const char *xml_attr(cmark_syntax_extension *extension, cmark_node *node) { - if (node->as.opaque == (void *)CMARK_TASKLIST_CHECKED) { + if (node->as.list.checked) { return " completed=\"true\""; } else { return " completed=\"false\""; diff --git a/src/plugins/ieviewer/cmark-gfm/src/arena.c b/src/plugins/ieviewer/cmark-gfm/src/arena.c index 83a15255..da1a70e9 100644 --- a/src/plugins/ieviewer/cmark-gfm/src/arena.c +++ b/src/plugins/ieviewer/cmark-gfm/src/arena.c @@ -68,15 +68,16 @@ static void *arena_calloc(size_t nmem, size_t size) { const size_t align = sizeof(size_t) - 1; sz = (sz + align) & ~align; + struct arena_chunk *chunk; if (sz > A->sz) { - A->prev = alloc_arena_chunk(sz, A->prev); - return (uint8_t *) A->prev->ptr + sizeof(size_t); + A->prev = chunk = alloc_arena_chunk(sz, A->prev); + } else if (sz > A->sz - A->used) { + A = chunk = alloc_arena_chunk(A->sz + A->sz / 2, A); + } else { + chunk = A; } - if (sz > A->sz - A->used) { - A = alloc_arena_chunk(A->sz + A->sz / 2, A); - } - void *ptr = (uint8_t *) A->ptr + A->used; - A->used += sz; + void *ptr = (uint8_t *) chunk->ptr + chunk->used; + chunk->used += sz; *((size_t *) ptr) = sz - sizeof(size_t); return (uint8_t *) ptr + sizeof(size_t); } @@ -98,6 +99,6 @@ static void arena_free(void *ptr) { cmark_mem CMARK_ARENA_MEM_ALLOCATOR = {arena_calloc, arena_realloc, arena_free}; -cmark_mem *cmark_get_arena_mem_allocator() { +cmark_mem *cmark_get_arena_mem_allocator(void) { return &CMARK_ARENA_MEM_ALLOCATOR; } diff --git a/src/plugins/ieviewer/cmark-gfm/src/blocks.c b/src/plugins/ieviewer/cmark-gfm/src/blocks.c index 53e882f1..3b5da56b 100644 --- a/src/plugins/ieviewer/cmark-gfm/src/blocks.c +++ b/src/plugins/ieviewer/cmark-gfm/src/blocks.c @@ -8,6 +8,7 @@ #include #include #include +#include #include "cmark_ctype.h" #include "syntax_extension.h" @@ -26,6 +27,14 @@ #define CODE_INDENT 4 #define TAB_STOP 4 +/** + * Very deeply nested lists can cause quadratic performance issues. + * This constant is used in open_new_blocks() to limit the nesting + * depth. It is unlikely that a non-contrived markdown document will + * be nested this deeply. + */ +#define MAX_LIST_DEPTH 100 + #ifndef MIN #define MIN(x, y) ((x < y) ? x : y) #endif @@ -468,7 +477,6 @@ static void process_footnotes(cmark_parser *parser) { while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) { cur = cmark_iter_get_node(iter); if (ev_type == CMARK_EVENT_EXIT && cur->type == CMARK_NODE_FOOTNOTE_DEFINITION) { - cmark_node_unlink(cur); cmark_footnote_create(map, cur); } } @@ -485,6 +493,15 @@ static void process_footnotes(cmark_parser *parser) { if (!footnote->ix) footnote->ix = ++ix; + // store a reference to this footnote reference's footnote definition + // this is used by renderers when generating label ids + cur->parent_footnote_def = footnote->node; + + // keep track of a) count of how many times this footnote def has been + // referenced, and b) which reference index this footnote ref is at. + // this is used by renderers when generating links and backreferences. + cur->footnote.ref_ix = ++footnote->node->footnote.def_count; + char n[32]; snprintf(n, sizeof(n), "%d", footnote->ix); cmark_chunk_free(parser->mem, &cur->as.literal); @@ -515,13 +532,16 @@ static void process_footnotes(cmark_parser *parser) { qsort(map->sorted, map->size, sizeof(cmark_map_entry *), sort_footnote_by_ix); for (unsigned int i = 0; i < map->size; ++i) { cmark_footnote *footnote = (cmark_footnote *)map->sorted[i]; - if (!footnote->ix) + if (!footnote->ix) { + cmark_node_unlink(footnote->node); continue; + } cmark_node_append_child(parser->root, footnote->node); footnote->node = NULL; } } + cmark_unlink_footnotes_map(map); cmark_map_free(map); } @@ -628,6 +648,14 @@ static cmark_node *finalize_document(cmark_parser *parser) { } finalize(parser, parser->root); + + // Limit total size of extra content created from reference links to + // document size to avoid superlinear growth. Always allow 100KB. + if (parser->total_size > 100000) + parser->refmap->max_ref_size = parser->total_size; + else + parser->refmap->max_ref_size = 100000; + process_inlines(parser, parser->refmap, parser->options); if (parser->options & CMARK_OPT_FOOTNOTES) process_footnotes(parser); @@ -687,6 +715,11 @@ static void S_parser_feed(cmark_parser *parser, const unsigned char *buffer, const unsigned char *end = buffer + len; static const uint8_t repl[] = {239, 191, 189}; + if (len > UINT_MAX - parser->total_size) + parser->total_size = UINT_MAX; + else + parser->total_size += len; + if (parser->last_buffer_ended_with_cr && *buffer == '\n') { // skip NL if last buffer ended with CR ; see #117 buffer++; @@ -1094,10 +1127,11 @@ static void open_new_blocks(cmark_parser *parser, cmark_node **container, bool has_content; int save_offset; int save_column; + size_t depth = 0; while (cont_type != CMARK_NODE_CODE_BLOCK && cont_type != CMARK_NODE_HTML_BLOCK) { - + depth++; S_find_first_nonspace(parser, input); indented = parser->indent >= CODE_INDENT; @@ -1183,15 +1217,17 @@ static void open_new_blocks(cmark_parser *parser, cmark_node **container, parser->first_nonspace + 1); S_advance_offset(parser, input, input->len - 1 - parser->offset, false); } else if (!indented && - parser->options & CMARK_OPT_FOOTNOTES && + (parser->options & CMARK_OPT_FOOTNOTES) && + depth < MAX_LIST_DEPTH && (matched = scan_footnote_definition(input, parser->first_nonspace))) { cmark_chunk c = cmark_chunk_dup(input, parser->first_nonspace + 2, matched - 2); - cmark_chunk_to_cstr(parser->mem, &c); while (c.data[c.len - 1] != ']') --c.len; --c.len; + cmark_chunk_to_cstr(parser->mem, &c); + S_advance_offset(parser, input, parser->first_nonspace + matched - parser->offset, false); *container = add_child(parser, *container, CMARK_NODE_FOOTNOTE_DEFINITION, parser->first_nonspace + matched + 1); (*container)->as.literal = c; @@ -1199,6 +1235,7 @@ static void open_new_blocks(cmark_parser *parser, cmark_node **container, (*container)->internal_offset = matched; } else if ((!indented || cont_type == CMARK_NODE_LIST) && parser->indent < 4 && + depth < MAX_LIST_DEPTH && (matched = parse_list_marker( parser->mem, input, parser->first_nonspace, (*container)->type == CMARK_NODE_PARAGRAPH, &data))) { diff --git a/src/plugins/ieviewer/cmark-gfm/src/cmark-gfm-extension_api.h b/src/plugins/ieviewer/cmark-gfm/src/cmark-gfm-extension_api.h index 9403c4f0..bb92a59b 100644 --- a/src/plugins/ieviewer/cmark-gfm/src/cmark-gfm-extension_api.h +++ b/src/plugins/ieviewer/cmark-gfm/src/cmark-gfm-extension_api.h @@ -114,6 +114,7 @@ typedef struct delimiter { struct delimiter *previous; struct delimiter *next; cmark_node *inl_text; + bufsize_t position; bufsize_t length; unsigned char delim_char; int can_open; diff --git a/src/plugins/ieviewer/cmark-gfm/src/cmark-gfm.h b/src/plugins/ieviewer/cmark-gfm/src/cmark-gfm.h index 6fb28693..0544057a 100644 --- a/src/plugins/ieviewer/cmark-gfm/src/cmark-gfm.h +++ b/src/plugins/ieviewer/cmark-gfm/src/cmark-gfm.h @@ -111,13 +111,13 @@ typedef struct cmark_mem { * realloc and free. */ CMARK_GFM_EXPORT -cmark_mem *cmark_get_default_mem_allocator(); +cmark_mem *cmark_get_default_mem_allocator(void); /** An arena allocator; uses system calloc to allocate large * slabs of memory. Memory in these slabs is not reused at all. */ CMARK_GFM_EXPORT -cmark_mem *cmark_get_arena_mem_allocator(); +cmark_mem *cmark_get_arena_mem_allocator(void); /** Resets the arena allocator, quickly returning all used memory * to the operating system. @@ -225,6 +225,11 @@ CMARK_GFM_EXPORT cmark_node *cmark_node_first_child(cmark_node *node); */ CMARK_GFM_EXPORT cmark_node *cmark_node_last_child(cmark_node *node); +/** Returns the footnote reference of 'node', or NULL if 'node' doesn't have a + * footnote reference. + */ +CMARK_GFM_EXPORT cmark_node *cmark_node_parent_footnote_def(cmark_node *node); + /** * ## Iterator * @@ -408,6 +413,17 @@ CMARK_GFM_EXPORT int cmark_node_get_list_tight(cmark_node *node); */ CMARK_GFM_EXPORT int cmark_node_set_list_tight(cmark_node *node, int tight); +/** + * Returns item index of 'node'. This is only used when rendering output + * formats such as commonmark, which need to output the index. It is not + * required for formats such as html or latex. + */ +CMARK_GFM_EXPORT int cmark_node_get_item_index(cmark_node *node); + +/** Sets item index of 'node'. Returns 1 on success, 0 on failure. + */ +CMARK_GFM_EXPORT int cmark_node_set_item_index(cmark_node *node, int idx); + /** Returns the info string from a fenced code block. */ CMARK_GFM_EXPORT const char *cmark_node_get_fence_info(cmark_node *node); diff --git a/src/plugins/ieviewer/cmark-gfm/src/cmark.c b/src/plugins/ieviewer/cmark-gfm/src/cmark.c index b3fad4b0..68c40c47 100644 --- a/src/plugins/ieviewer/cmark-gfm/src/cmark.c +++ b/src/plugins/ieviewer/cmark-gfm/src/cmark.c @@ -10,9 +10,9 @@ cmark_node_type CMARK_NODE_LAST_BLOCK = CMARK_NODE_FOOTNOTE_DEFINITION; cmark_node_type CMARK_NODE_LAST_INLINE = CMARK_NODE_FOOTNOTE_REFERENCE; -int cmark_version() { return CMARK_GFM_VERSION; } +int cmark_version(void) { return CMARK_GFM_VERSION; } -const char *cmark_version_string() { return CMARK_GFM_VERSION_STRING; } +const char *cmark_version_string(void) { return CMARK_GFM_VERSION_STRING; } static void *xcalloc(size_t nmem, size_t size) { void *ptr = calloc(nmem, size); @@ -38,7 +38,7 @@ static void xfree(void *ptr) { cmark_mem CMARK_DEFAULT_MEM_ALLOCATOR = {xcalloc, xrealloc, xfree}; -cmark_mem *cmark_get_default_mem_allocator() { +cmark_mem *cmark_get_default_mem_allocator(void) { return &CMARK_DEFAULT_MEM_ALLOCATOR; } diff --git a/src/plugins/ieviewer/cmark-gfm/src/commonmark.c b/src/plugins/ieviewer/cmark-gfm/src/commonmark.c index f272d4d2..987b4731 100644 --- a/src/plugins/ieviewer/cmark-gfm/src/commonmark.c +++ b/src/plugins/ieviewer/cmark-gfm/src/commonmark.c @@ -153,23 +153,8 @@ static bool is_autolink(cmark_node *node) { link_text->as.literal.len) == 0); } -// if node is a block node, returns node. -// otherwise returns first block-level node that is an ancestor of node. -// if there is no block-level ancestor, returns NULL. -static cmark_node *get_containing_block(cmark_node *node) { - while (node) { - if (CMARK_NODE_BLOCK_P(node)) { - return node; - } else { - node = node->parent; - } - } - return NULL; -} - static int S_render_node(cmark_renderer *renderer, cmark_node *node, cmark_event_type ev_type, int options) { - cmark_node *tmp; int list_number; cmark_delim_type list_delim; int numticks; @@ -180,7 +165,7 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node, char fencechar[2] = {'\0', '\0'}; size_t info_len, code_len; char listmarker[LISTMARKER_SIZE]; - char *emph_delim; + const char *emph_delim; bool first_in_list_item; bufsize_t marker_width; bool allow_wrap = renderer->width > 0 && !(CMARK_OPT_NOBREAKS & options) && @@ -189,14 +174,17 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node, // Don't adjust tight list status til we've started the list. // Otherwise we loose the blank line between a paragraph and // a following list. - if (!(node->type == CMARK_NODE_ITEM && node->prev == NULL && entering)) { - tmp = get_containing_block(node); - renderer->in_tight_list_item = - tmp && // tmp might be NULL if there is no containing block - ((tmp->type == CMARK_NODE_ITEM && - cmark_node_get_list_tight(tmp->parent)) || - (tmp && tmp->parent && tmp->parent->type == CMARK_NODE_ITEM && - cmark_node_get_list_tight(tmp->parent->parent))); + if (entering) { + if (node->parent && node->parent->type == CMARK_NODE_ITEM) { + renderer->in_tight_list_item = node->parent->parent->as.list.tight; + } + } else { + if (node->type == CMARK_NODE_LIST) { + renderer->in_tight_list_item = + node->parent && + node->parent->type == CMARK_NODE_ITEM && + node->parent->parent->as.list.tight; + } } if (node->extension && node->extension->commonmark_render_func) { @@ -234,13 +222,8 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node, if (cmark_node_get_list_type(node->parent) == CMARK_BULLET_LIST) { marker_width = 4; } else { - list_number = cmark_node_get_list_start(node->parent); + list_number = cmark_node_get_item_index(node); list_delim = cmark_node_get_list_delim(node->parent); - tmp = node; - while (tmp->prev) { - tmp = tmp->prev; - list_number += 1; - } // we ensure a width of at least 4 so // we get nice transition from single digits // to double @@ -405,10 +388,12 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node, break; case CMARK_NODE_STRONG: - if (entering) { - LIT("**"); - } else { - LIT("**"); + if (node->parent == NULL || node->parent->type != CMARK_NODE_STRONG) { + if (entering) { + LIT("**"); + } else { + LIT("**"); + } } break; @@ -477,7 +462,13 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node, case CMARK_NODE_FOOTNOTE_REFERENCE: if (entering) { LIT("[^"); - OUT(cmark_chunk_to_cstr(renderer->mem, &node->as.literal), false, LITERAL); + + char *footnote_label = renderer->mem->calloc(node->parent_footnote_def->as.literal.len + 1, sizeof(char)); + memmove(footnote_label, node->parent_footnote_def->as.literal.data, node->parent_footnote_def->as.literal.len); + + OUT(footnote_label, false, LITERAL); + renderer->mem->free(footnote_label); + LIT("]"); } break; @@ -486,9 +477,13 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node, if (entering) { renderer->footnote_ix += 1; LIT("[^"); - char n[32]; - snprintf(n, sizeof(n), "%d", renderer->footnote_ix); - OUT(n, false, LITERAL); + + char *footnote_label = renderer->mem->calloc(node->as.literal.len + 1, sizeof(char)); + memmove(footnote_label, node->as.literal.data, node->as.literal.len); + + OUT(footnote_label, false, LITERAL); + renderer->mem->free(footnote_label); + LIT("]:\n"); cmark_strbuf_puts(renderer->prefix, " "); diff --git a/src/plugins/ieviewer/cmark-gfm/src/footnotes.c b/src/plugins/ieviewer/cmark-gfm/src/footnotes.c index f2d2765f..c2b745f7 100644 --- a/src/plugins/ieviewer/cmark-gfm/src/footnotes.c +++ b/src/plugins/ieviewer/cmark-gfm/src/footnotes.c @@ -38,3 +38,26 @@ void cmark_footnote_create(cmark_map *map, cmark_node *node) { cmark_map *cmark_footnote_map_new(cmark_mem *mem) { return cmark_map_new(mem, footnote_free); } + +// Before calling `cmark_map_free` on a map with `cmark_footnotes`, first +// unlink all of the footnote nodes before freeing their memory. +// +// Sometimes, two (unused) footnote nodes can end up referencing each other, +// which as they get freed up by calling `cmark_map_free` -> `footnote_free` -> +// etc, can lead to a use-after-free error. +// +// Better to `unlink` every footnote node first, setting their next, prev, and +// parent pointers to NULL, and only then walk thru & free them up. +void cmark_unlink_footnotes_map(cmark_map *map) { + cmark_map_entry *ref; + cmark_map_entry *next; + + ref = map->refs; + while(ref) { + next = ref->next; + if (((cmark_footnote *)ref)->node) { + cmark_node_unlink(((cmark_footnote *)ref)->node); + } + ref = next; + } +} diff --git a/src/plugins/ieviewer/cmark-gfm/src/footnotes.h b/src/plugins/ieviewer/cmark-gfm/src/footnotes.h index 43dd64ff..64e2901e 100644 --- a/src/plugins/ieviewer/cmark-gfm/src/footnotes.h +++ b/src/plugins/ieviewer/cmark-gfm/src/footnotes.h @@ -18,6 +18,8 @@ typedef struct cmark_footnote cmark_footnote; void cmark_footnote_create(cmark_map *map, cmark_node *node); cmark_map *cmark_footnote_map_new(cmark_mem *mem); +void cmark_unlink_footnotes_map(cmark_map *map); + #ifdef __cplusplus } #endif diff --git a/src/plugins/ieviewer/cmark-gfm/src/html.c b/src/plugins/ieviewer/cmark-gfm/src/html.c index ea1f6e18..22513c93 100644 --- a/src/plugins/ieviewer/cmark-gfm/src/html.c +++ b/src/plugins/ieviewer/cmark-gfm/src/html.c @@ -59,16 +59,44 @@ static void filter_html_block(cmark_html_renderer *renderer, uint8_t *data, size cmark_strbuf_put(html, data, (bufsize_t)len); } -static bool S_put_footnote_backref(cmark_html_renderer *renderer, cmark_strbuf *html) { +static bool S_put_footnote_backref(cmark_html_renderer *renderer, cmark_strbuf *html, cmark_node *node) { if (renderer->written_footnote_ix >= renderer->footnote_ix) return false; renderer->written_footnote_ix = renderer->footnote_ix; + char m[32]; + snprintf(m, sizeof(m), "%d", renderer->written_footnote_ix); + + cmark_strbuf_puts(html, "as.literal.data, node->as.literal.len); + cmark_strbuf_puts(html, "\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\""); + cmark_strbuf_puts(html, m); + cmark_strbuf_puts(html, "\" aria-label=\"Back to reference "); + cmark_strbuf_puts(html, m); + cmark_strbuf_puts(html, "\">↩"); + + if (node->footnote.def_count > 1) + { + for(int i = 2; i <= node->footnote.def_count; i++) { + char n[32]; + snprintf(n, sizeof(n), "%d", i); - cmark_strbuf_puts(html, "footnote_ix); - cmark_strbuf_puts(html, n); - cmark_strbuf_puts(html, "\" class=\"footnote-backref\">↩"); + cmark_strbuf_puts(html, " as.literal.data, node->as.literal.len); + cmark_strbuf_puts(html, "-"); + cmark_strbuf_puts(html, n); + cmark_strbuf_puts(html, "\" class=\"footnote-backref\" data-footnote-backref data-footnote-backref-idx=\""); + cmark_strbuf_puts(html, m); + cmark_strbuf_puts(html, "-"); + cmark_strbuf_puts(html, n); + cmark_strbuf_puts(html, "\" aria-label=\"Back to reference "); + cmark_strbuf_puts(html, m); + cmark_strbuf_puts(html, "-"); + cmark_strbuf_puts(html, n); + cmark_strbuf_puts(html, "\">↩"); + cmark_strbuf_puts(html, n); + cmark_strbuf_puts(html, ""); + } + } return true; } @@ -273,7 +301,7 @@ static int S_render_node(cmark_html_renderer *renderer, cmark_node *node, } else { if (parent->type == CMARK_NODE_FOOTNOTE_DEFINITION && node->next == NULL) { cmark_strbuf_putc(html, ' '); - S_put_footnote_backref(renderer, html); + S_put_footnote_backref(renderer, html, parent); } cmark_strbuf_puts(html, "

                      \n"); } @@ -336,10 +364,12 @@ static int S_render_node(cmark_html_renderer *renderer, cmark_node *node, break; case CMARK_NODE_STRONG: - if (entering) { - cmark_strbuf_puts(html, ""); - } else { - cmark_strbuf_puts(html, ""); + if (node->parent == NULL || node->parent->type != CMARK_NODE_STRONG) { + if (entering) { + cmark_strbuf_puts(html, ""); + } else { + cmark_strbuf_puts(html, ""); + } } break; @@ -392,16 +422,15 @@ static int S_render_node(cmark_html_renderer *renderer, cmark_node *node, case CMARK_NODE_FOOTNOTE_DEFINITION: if (entering) { if (renderer->footnote_ix == 0) { - cmark_strbuf_puts(html, "
                      \n
                        \n"); + cmark_strbuf_puts(html, "
                        \n
                          \n"); } ++renderer->footnote_ix; - cmark_strbuf_puts(html, "
                        1. footnote_ix); - cmark_strbuf_puts(html, n); + + cmark_strbuf_puts(html, "
                        2. as.literal.data, node->as.literal.len); cmark_strbuf_puts(html, "\">\n"); } else { - if (S_put_footnote_backref(renderer, html)) { + if (S_put_footnote_backref(renderer, html, node)) { cmark_strbuf_putc(html, '\n'); } cmark_strbuf_puts(html, "
                        3. \n"); @@ -410,12 +439,20 @@ static int S_render_node(cmark_html_renderer *renderer, cmark_node *node, case CMARK_NODE_FOOTNOTE_REFERENCE: if (entering) { - cmark_strbuf_puts(html, "as.literal.data, node->as.literal.len); - cmark_strbuf_puts(html, "\" id=\"fnref"); - cmark_strbuf_put(html, node->as.literal.data, node->as.literal.len); - cmark_strbuf_puts(html, "\">"); - cmark_strbuf_put(html, node->as.literal.data, node->as.literal.len); + cmark_strbuf_puts(html, "parent_footnote_def->as.literal.data, node->parent_footnote_def->as.literal.len); + cmark_strbuf_puts(html, "\" id=\"fnref-"); + houdini_escape_href(html, node->parent_footnote_def->as.literal.data, node->parent_footnote_def->as.literal.len); + + if (node->footnote.ref_ix > 1) { + char n[32]; + snprintf(n, sizeof(n), "%d", node->footnote.ref_ix); + cmark_strbuf_puts(html, "-"); + cmark_strbuf_puts(html, n); + } + + cmark_strbuf_puts(html, "\" data-footnote-ref>"); + houdini_escape_href(html, node->as.literal.data, node->as.literal.len); cmark_strbuf_puts(html, ""); } break; diff --git a/src/plugins/ieviewer/cmark-gfm/src/inlines.c b/src/plugins/ieviewer/cmark-gfm/src/inlines.c index c21430bd..30f2c170 100644 --- a/src/plugins/ieviewer/cmark-gfm/src/inlines.c +++ b/src/plugins/ieviewer/cmark-gfm/src/inlines.c @@ -35,17 +35,24 @@ static const char *RIGHTSINGLEQUOTE = "\xE2\x80\x99"; typedef struct bracket { struct bracket *previous; - struct delimiter *previous_delimiter; cmark_node *inl_text; bufsize_t position; bool image; bool active; bool bracket_after; + bool in_bracket_image0; + bool in_bracket_image1; } bracket; +#define FLAG_SKIP_HTML_CDATA (1u << 0) +#define FLAG_SKIP_HTML_DECLARATION (1u << 1) +#define FLAG_SKIP_HTML_PI (1u << 2) +#define FLAG_SKIP_HTML_COMMENT (1u << 3) + typedef struct subject{ cmark_mem *mem; cmark_chunk input; + unsigned flags; int line; bufsize_t pos; int block_offset; @@ -55,6 +62,7 @@ typedef struct subject{ bracket *last_bracket; bufsize_t backticks[MAXBACKTICKS + 1]; bool scanned_for_backticks; + bool no_link_openers; } subject; // Extensions may populate this. @@ -109,6 +117,24 @@ static cmark_node *make_str_with_entities(subject *subj, } } +// Like cmark_node_append_child but without costly sanity checks. +// Assumes that child was newly created. +static void append_child(cmark_node *node, cmark_node *child) { + cmark_node *old_last_child = node->last_child; + + child->next = NULL; + child->prev = old_last_child; + child->parent = node; + node->last_child = child; + + if (old_last_child) { + old_last_child->next = child; + } else { + // Also set first_child if node previously had no children. + node->first_child = child; + } +} + // Duplicate a chunk by creating a copy of the buffer not by reusing the // buffer like cmark_chunk_dup does. static cmark_chunk chunk_clone(cmark_mem *mem, cmark_chunk *src) { @@ -152,7 +178,7 @@ static CMARK_INLINE cmark_node *make_autolink(subject *subj, link->start_line = link->end_line = subj->line; link->start_column = start_column + 1; link->end_column = end_column + 1; - cmark_node_append_child(link, make_str_with_entities(subj, start_column + 1, end_column - 1, &url)); + append_child(link, make_str_with_entities(subj, start_column + 1, end_column - 1, &url)); return link; } @@ -161,6 +187,7 @@ static void subject_from_buf(cmark_mem *mem, int line_number, int block_offset, int i; e->mem = mem; e->input = *chunk; + e->flags = 0; e->line = line_number; e->pos = 0; e->block_offset = block_offset; @@ -172,6 +199,7 @@ static void subject_from_buf(cmark_mem *mem, int line_number, int block_offset, e->backticks[i] = 0; } e->scanned_for_backticks = false; + e->no_link_openers = true; } static CMARK_INLINE int isbacktick(int c) { return (c == '`'); } @@ -503,6 +531,7 @@ static void push_delimiter(subject *subj, unsigned char c, bool can_open, delim->can_open = can_open; delim->can_close = can_close; delim->inl_text = inl_text; + delim->position = subj->pos; delim->length = inl_text->as.literal.len; delim->previous = subj->last_delim; delim->next = NULL; @@ -516,15 +545,24 @@ static void push_bracket(subject *subj, bool image, cmark_node *inl_text) { bracket *b = (bracket *)subj->mem->calloc(1, sizeof(bracket)); if (subj->last_bracket != NULL) { subj->last_bracket->bracket_after = true; + b->in_bracket_image0 = subj->last_bracket->in_bracket_image0; + b->in_bracket_image1 = subj->last_bracket->in_bracket_image1; } b->image = image; b->active = true; b->inl_text = inl_text; b->previous = subj->last_bracket; - b->previous_delimiter = subj->last_delim; b->position = subj->pos; b->bracket_after = false; + if (image) { + b->in_bracket_image1 = true; + } else { + b->in_bracket_image0 = true; + } subj->last_bracket = b; + if (!image) { + subj->no_link_openers = false; + } } // Assumes the subject has a c at the current position. @@ -631,12 +669,13 @@ static cmark_syntax_extension *get_extension_for_special_char(cmark_parser *pars return NULL; } -static void process_emphasis(cmark_parser *parser, subject *subj, delimiter *stack_bottom) { - delimiter *closer = subj->last_delim; +static void process_emphasis(cmark_parser *parser, subject *subj, bufsize_t stack_bottom) { + delimiter *candidate; + delimiter *closer = NULL; delimiter *opener; delimiter *old_closer; bool opener_found; - delimiter *openers_bottom[3][128]; + bufsize_t openers_bottom[3][128]; int i; // initialize openers_bottom: @@ -649,8 +688,10 @@ static void process_emphasis(cmark_parser *parser, subject *subj, delimiter *sta } // move back to first relevant delim. - while (closer != NULL && closer->previous != stack_bottom) { - closer = closer->previous; + candidate = subj->last_delim; + while (candidate != NULL && candidate->position >= stack_bottom) { + closer = candidate; + candidate = candidate->previous; } // now move forward, looking for closers, and handling each @@ -660,8 +701,8 @@ static void process_emphasis(cmark_parser *parser, subject *subj, delimiter *sta // Now look backwards for first matching opener: opener = closer->previous; opener_found = false; - while (opener != NULL && opener != stack_bottom && - opener != openers_bottom[closer->length % 3][closer->delim_char]) { + while (opener != NULL && opener->position >= stack_bottom && + opener->position >= openers_bottom[closer->length % 3][closer->delim_char]) { if (opener->can_open && opener->delim_char == closer->delim_char) { // interior closer of size 2 can't match opener of size 1 // or of size 1 can't match 2 @@ -687,27 +728,29 @@ static void process_emphasis(cmark_parser *parser, subject *subj, delimiter *sta } else { closer = closer->next; } - } else if (closer->delim_char == '\'') { + } else if (closer->delim_char == '\'' || closer->delim_char == '"') { cmark_chunk_free(subj->mem, &closer->inl_text->as.literal); - closer->inl_text->as.literal = cmark_chunk_literal(RIGHTSINGLEQUOTE); - if (opener_found) { - cmark_chunk_free(subj->mem, &opener->inl_text->as.literal); - opener->inl_text->as.literal = cmark_chunk_literal(LEFTSINGLEQUOTE); + if (closer->delim_char == '\'') { + closer->inl_text->as.literal = cmark_chunk_literal(RIGHTSINGLEQUOTE); + } else { + closer->inl_text->as.literal = cmark_chunk_literal(RIGHTDOUBLEQUOTE); } closer = closer->next; - } else if (closer->delim_char == '"') { - cmark_chunk_free(subj->mem, &closer->inl_text->as.literal); - closer->inl_text->as.literal = cmark_chunk_literal(RIGHTDOUBLEQUOTE); if (opener_found) { cmark_chunk_free(subj->mem, &opener->inl_text->as.literal); - opener->inl_text->as.literal = cmark_chunk_literal(LEFTDOUBLEQUOTE); + if (old_closer->delim_char == '\'') { + opener->inl_text->as.literal = cmark_chunk_literal(LEFTSINGLEQUOTE); + } else { + opener->inl_text->as.literal = cmark_chunk_literal(LEFTDOUBLEQUOTE); + } + remove_delimiter(subj, opener); + remove_delimiter(subj, old_closer); } - closer = closer->next; } if (!opener_found) { // set lower bound for future searches for openers openers_bottom[old_closer->length % 3][old_closer->delim_char] = - old_closer->previous; + old_closer->position; if (!old_closer->can_open) { // we can remove a closer that can't be an // opener, once we've seen there's no @@ -720,7 +763,8 @@ static void process_emphasis(cmark_parser *parser, subject *subj, delimiter *sta } } // free all delimiters in list until stack_bottom: - while (subj->last_delim != NULL && subj->last_delim != stack_bottom) { + while (subj->last_delim != NULL && + subj->last_delim->position >= stack_bottom) { remove_delimiter(subj, subj->last_delim); } } @@ -759,7 +803,8 @@ static delimiter *S_insert_emph(subject *subj, delimiter *opener, tmp = opener_inl->next; while (tmp && tmp != closer_inl) { tmpnext = tmp->next; - cmark_node_append_child(emph, tmp); + cmark_node_unlink(tmp); + append_child(emph, tmp); tmp = tmpnext; } cmark_node_insert_after(opener_inl, emph); @@ -890,7 +935,63 @@ static cmark_node *handle_pointy_brace(subject *subj, int options) { } // finally, try to match an html tag - matchlen = scan_html_tag(&subj->input, subj->pos); + if (subj->pos + 2 <= subj->input.len) { + int c = subj->input.data[subj->pos]; + if (c == '!' && (subj->flags & FLAG_SKIP_HTML_COMMENT) == 0) { + c = subj->input.data[subj->pos+1]; + if (c == '-' && subj->input.data[subj->pos+2] == '-') { + if (subj->input.data[subj->pos+3] == '>') { + matchlen = 4; + } else if (subj->input.data[subj->pos+3] == '-' && + subj->input.data[subj->pos+4] == '>') { + matchlen = 5; + } else { + matchlen = scan_html_comment(&subj->input, subj->pos + 1); + if (matchlen > 0) { + matchlen += 1; // prefix "<" + } else { // no match through end of input: set a flag so + // we don't reparse looking for -->: + subj->flags |= FLAG_SKIP_HTML_COMMENT; + } + } + } else if (c == '[') { + if ((subj->flags & FLAG_SKIP_HTML_CDATA) == 0) { + matchlen = scan_html_cdata(&subj->input, subj->pos + 2); + if (matchlen > 0) { + // The regex doesn't require the final "]]>". But if we're not at + // the end of input, it must come after the match. Otherwise, + // disable subsequent scans to avoid quadratic behavior. + matchlen += 5; // prefix "![", suffix "]]>" + if (subj->pos + matchlen > subj->input.len) { + subj->flags |= FLAG_SKIP_HTML_CDATA; + matchlen = 0; + } + } + } + } else if ((subj->flags & FLAG_SKIP_HTML_DECLARATION) == 0) { + matchlen = scan_html_declaration(&subj->input, subj->pos + 1); + if (matchlen > 0) { + matchlen += 2; // prefix "!", suffix ">" + if (subj->pos + matchlen > subj->input.len) { + subj->flags |= FLAG_SKIP_HTML_DECLARATION; + matchlen = 0; + } + } + } + } else if (c == '?') { + if ((subj->flags & FLAG_SKIP_HTML_PI) == 0) { + // Note that we allow an empty match. + matchlen = scan_html_pi(&subj->input, subj->pos + 1); + matchlen += 3; // prefix "?", suffix "?>" + if (subj->pos + matchlen > subj->input.len) { + subj->flags |= FLAG_SKIP_HTML_PI; + matchlen = 0; + } + } + } else { + matchlen = scan_html_tag(&subj->input, subj->pos); + } + } if (matchlen > 0) { contents = cmark_chunk_dup(&subj->input, subj->pos - 1, matchlen + 1); subj->pos += matchlen; @@ -1056,16 +1157,16 @@ static cmark_node *handle_close_bracket(cmark_parser *parser, subject *subj) { return make_str(subj, subj->pos - 1, subj->pos - 1, cmark_chunk_literal("]")); } - if (!opener->active) { + // If we got here, we matched a potential link/image text. + // Now we check to see if it's a link/image. + is_image = opener->image; + + if (!is_image && subj->no_link_openers) { // take delimiter off stack pop_bracket(subj); return make_str(subj, subj->pos - 1, subj->pos - 1, cmark_chunk_literal("]")); } - // If we got here, we matched a potential link/image text. - // Now we check to see if it's a link/image. - is_image = opener->image; - after_link_text_pos = subj->pos; // First, look for an inline link. @@ -1137,19 +1238,77 @@ static cmark_node *handle_close_bracket(cmark_parser *parser, subject *subj) { // What if we're a footnote link? if (parser->options & CMARK_OPT_FOOTNOTES && opener->inl_text->next && - opener->inl_text->next->type == CMARK_NODE_TEXT && - !opener->inl_text->next->next) { + opener->inl_text->next->type == CMARK_NODE_TEXT) { + cmark_chunk *literal = &opener->inl_text->next->as.literal; - if (literal->len > 1 && literal->data[0] == '^') { - inl = make_simple(subj->mem, CMARK_NODE_FOOTNOTE_REFERENCE); - inl->as.literal = cmark_chunk_dup(literal, 1, literal->len - 1); - inl->start_line = inl->end_line = subj->line; - inl->start_column = opener->inl_text->start_column; - inl->end_column = subj->pos + subj->column_offset + subj->block_offset; - cmark_node_insert_before(opener->inl_text, inl); - cmark_node_free(opener->inl_text->next); + + // look back to the opening '[', and skip ahead to the next character + // if we're looking at a '[^' sequence, and there is other text or nodes + // after the ^, let's call it a footnote reference. + if ((literal->len > 0 && literal->data[0] == '^') && (literal->len > 1 || opener->inl_text->next->next)) { + + // Before we got this far, the `handle_close_bracket` function may have + // advanced the current state beyond our footnote's actual closing + // bracket, ie if it went looking for a `link_label`. + // Let's just rewind the subject's position: + subj->pos = initial_pos; + + cmark_node *fnref = make_simple(subj->mem, CMARK_NODE_FOOTNOTE_REFERENCE); + + // the start and end of the footnote ref is the opening and closing brace + // i.e. the subject's current position, and the opener's start_column + int fnref_end_column = subj->pos + subj->column_offset + subj->block_offset; + int fnref_start_column = opener->inl_text->start_column; + + // any given node delineates a substring of the line being processed, + // with the remainder of the line being pointed to thru its 'literal' + // struct member. + // here, we copy the literal's pointer, moving it past the '^' character + // for a length equal to the size of footnote reference text. + // i.e. end_col minus start_col, minus the [ and the ^ characters + // + // this copies the footnote reference string, even if between the + // `opener` and the subject's current position there are other nodes + // + // (first, check for underflows) + if ((fnref_start_column + 2) <= fnref_end_column) { + fnref->as.literal = cmark_chunk_dup(literal, 1, (fnref_end_column - fnref_start_column) - 2); + } else { + fnref->as.literal = cmark_chunk_dup(literal, 1, 0); + } + + fnref->start_line = fnref->end_line = subj->line; + fnref->start_column = fnref_start_column; + fnref->end_column = fnref_end_column; + + // we then replace the opener with this new fnref node, the net effect + // being replacing the opening '[' text node with a `^footnote-ref]` node. + cmark_node_insert_before(opener->inl_text, fnref); + + process_emphasis(parser, subj, opener->position); + // sometimes, the footnote reference text gets parsed into multiple nodes + // i.e. '[^example]' parsed into '[', '^exam', 'ple]'. + // this happens for ex with the autolink extension. when the autolinker + // finds the 'w' character, it will split the text into multiple nodes + // in hopes of being able to match a 'www.' substring. + // + // because this function is called one character at a time via the + // `parse_inlines` function, and the current subj->pos is pointing at the + // closing ] brace, and because we copy all the text between the [ ] + // braces, we should be able to safely ignore and delete any nodes after + // the opener->inl_text->next. + // + // therefore, here we walk thru the list and free them all up + cmark_node *next_node; + cmark_node *current_node = opener->inl_text->next; + while(current_node) { + next_node = current_node->next; + cmark_node_free(current_node); + current_node = next_node; + } + cmark_node_free(opener->inl_text); - process_emphasis(parser, subj, opener->previous_delimiter); + pop_bracket(subj); return NULL; } @@ -1171,31 +1330,22 @@ static cmark_node *handle_close_bracket(cmark_parser *parser, subject *subj) { tmp = opener->inl_text->next; while (tmp) { tmpnext = tmp->next; - cmark_node_append_child(inl, tmp); + cmark_node_unlink(tmp); + append_child(inl, tmp); tmp = tmpnext; } // Free the bracket [: cmark_node_free(opener->inl_text); - process_emphasis(parser, subj, opener->previous_delimiter); + process_emphasis(parser, subj, opener->position); pop_bracket(subj); - // Now, if we have a link, we also want to deactivate earlier link - // delimiters. (This code can be removed if we decide to allow links + // Now, if we have a link, we also want to deactivate links until + // we get a new opener. (This code can be removed if we decide to allow links // inside links.) if (!is_image) { - opener = subj->last_bracket; - while (opener != NULL) { - if (!opener->image) { - if (!opener->active) { - break; - } else { - opener->active = false; - } - } - opener = opener->previous; - } + subj->no_link_openers = true; } return NULL; @@ -1373,7 +1523,7 @@ static int parse_inline(cmark_parser *parser, subject *subj, cmark_node *parent, new_inl = make_str(subj, startpos, endpos - 1, contents); } if (new_inl != NULL) { - cmark_node_append_child(parent, new_inl); + append_child(parent, new_inl); } return 1; @@ -1392,7 +1542,7 @@ void cmark_parse_inlines(cmark_parser *parser, while (!is_eof(&subj) && parse_inline(parser, &subj, parent, options)) ; - process_emphasis(parser, &subj, NULL); + process_emphasis(parser, &subj, 0); // free bracket and delim stack while (subj.last_delim) { remove_delimiter(&subj, subj.last_delim); @@ -1604,10 +1754,15 @@ cmark_chunk *cmark_inline_parser_get_chunk(cmark_inline_parser *parser) { } int cmark_inline_parser_in_bracket(cmark_inline_parser *parser, int image) { - for (bracket *b = parser->last_bracket; b; b = b->previous) - if (b->active && b->image == (image != 0)) - return 1; - return 0; + bracket *b = parser->last_bracket; + if (!b) { + return 0; + } + if (image != 0) { + return b->in_bracket_image1; + } else { + return b->in_bracket_image0; + } } void cmark_node_unput(cmark_node *node, int n) { diff --git a/src/plugins/ieviewer/cmark-gfm/src/latex.c b/src/plugins/ieviewer/cmark-gfm/src/latex.c index 8be15b0d..1a6367a4 100644 --- a/src/plugins/ieviewer/cmark-gfm/src/latex.c +++ b/src/plugins/ieviewer/cmark-gfm/src/latex.c @@ -385,10 +385,12 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node, break; case CMARK_NODE_STRONG: - if (entering) { - LIT("\\textbf{"); - } else { - LIT("}"); + if (node->parent == NULL || node->parent->type != CMARK_NODE_STRONG) { + if (entering) { + LIT("\\textbf{"); + } else { + LIT("}"); + } } break; diff --git a/src/plugins/ieviewer/cmark-gfm/src/man.c b/src/plugins/ieviewer/cmark-gfm/src/man.c index 441a96e4..634fd9d0 100644 --- a/src/plugins/ieviewer/cmark-gfm/src/man.c +++ b/src/plugins/ieviewer/cmark-gfm/src/man.c @@ -74,7 +74,6 @@ static void S_outc(cmark_renderer *renderer, cmark_node *node, static int S_render_node(cmark_renderer *renderer, cmark_node *node, cmark_event_type ev_type, int options) { - cmark_node *tmp; int list_number; bool entering = (ev_type == CMARK_EVENT_ENTER); bool allow_wrap = renderer->width > 0 && !(CMARK_OPT_NOBREAKS & options); @@ -123,12 +122,7 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node, if (cmark_node_get_list_type(node->parent) == CMARK_BULLET_LIST) { LIT("\\[bu] 2"); } else { - list_number = cmark_node_get_list_start(node->parent); - tmp = node; - while (tmp->prev) { - tmp = tmp->prev; - list_number += 1; - } + list_number = cmark_node_get_item_index(node); char list_number_s[LIST_NUMBER_SIZE]; snprintf(list_number_s, LIST_NUMBER_SIZE, "\"%d.\" 4", list_number); LIT(list_number_s); @@ -225,10 +219,12 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node, break; case CMARK_NODE_STRONG: - if (entering) { - LIT("\\f[B]"); - } else { - LIT("\\f[]"); + if (node->parent == NULL || node->parent->type != CMARK_NODE_STRONG) { + if (entering) { + LIT("\\f[B]"); + } else { + LIT("\\f[]"); + } } break; diff --git a/src/plugins/ieviewer/cmark-gfm/src/map.c b/src/plugins/ieviewer/cmark-gfm/src/map.c index 9a418dfd..f60f8fc6 100644 --- a/src/plugins/ieviewer/cmark-gfm/src/map.c +++ b/src/plugins/ieviewer/cmark-gfm/src/map.c @@ -51,7 +51,7 @@ refsearch(const void *label, const void *p2) { } static void sort_map(cmark_map *map) { - unsigned int i = 0, last = 0, size = map->size; + size_t i = 0, last = 0, size = map->size; cmark_map_entry *r = map->refs, **sorted = NULL; sorted = (cmark_map_entry **)map->mem->calloc(size, sizeof(cmark_map_entry *)); @@ -73,6 +73,7 @@ static void sort_map(cmark_map *map) { cmark_map_entry *cmark_map_lookup(cmark_map *map, cmark_chunk *label) { cmark_map_entry **ref = NULL; + cmark_map_entry *r = NULL; unsigned char *norm; if (label->len < 1 || label->len > MAX_LINK_LABEL_LENGTH) @@ -91,10 +92,15 @@ cmark_map_entry *cmark_map_lookup(cmark_map *map, cmark_chunk *label) { ref = (cmark_map_entry **)bsearch(norm, map->sorted, map->size, sizeof(cmark_map_entry *), refsearch); map->mem->free(norm); - if (!ref) - return NULL; + if (ref != NULL) { + r = ref[0]; + /* Check for expansion limit */ + if (r->size > map->max_ref_size - map->ref_size) + return NULL; + map->ref_size += r->size; + } - return ref[0]; + return r; } void cmark_map_free(cmark_map *map) { @@ -118,5 +124,6 @@ cmark_map *cmark_map_new(cmark_mem *mem, cmark_map_free_f free) { cmark_map *map = (cmark_map *)mem->calloc(1, sizeof(cmark_map)); map->mem = mem; map->free = free; + map->max_ref_size = UINT_MAX; return map; } diff --git a/src/plugins/ieviewer/cmark-gfm/src/map.h b/src/plugins/ieviewer/cmark-gfm/src/map.h index aa4c06e5..cc9b1f30 100644 --- a/src/plugins/ieviewer/cmark-gfm/src/map.h +++ b/src/plugins/ieviewer/cmark-gfm/src/map.h @@ -10,7 +10,8 @@ extern "C" { struct cmark_map_entry { struct cmark_map_entry *next; unsigned char *label; - unsigned int age; + size_t age; + size_t size; }; typedef struct cmark_map_entry cmark_map_entry; @@ -23,7 +24,9 @@ struct cmark_map { cmark_mem *mem; cmark_map_entry *refs; cmark_map_entry **sorted; - unsigned int size; + size_t size; + size_t ref_size; + size_t max_ref_size; cmark_map_free_f free; }; diff --git a/src/plugins/ieviewer/cmark-gfm/src/node.c b/src/plugins/ieviewer/cmark-gfm/src/node.c index 0118d651..e7a9606d 100644 --- a/src/plugins/ieviewer/cmark-gfm/src/node.c +++ b/src/plugins/ieviewer/cmark-gfm/src/node.c @@ -5,10 +5,42 @@ #include "node.h" #include "syntax_extension.h" +/** + * Expensive safety checks are off by default, but can be enabled + * by calling cmark_enable_safety_checks(). + */ +static bool enable_safety_checks = false; + +void cmark_enable_safety_checks(bool enable) { + enable_safety_checks = enable; +} + static void S_node_unlink(cmark_node *node); #define NODE_MEM(node) cmark_node_mem(node) +void cmark_register_node_flag(cmark_node_internal_flags *flags) { + static cmark_node_internal_flags nextflag = CMARK_NODE__REGISTER_FIRST; + + // flags should be a pointer to a global variable and this function + // should only be called once to initialize its value. + if (*flags) { + fprintf(stderr, "flag initialization error in cmark_register_node_flag\n"); + abort(); + } + + // Check that we haven't run out of bits. + if (nextflag == 0) { + fprintf(stderr, "too many flags in cmark_register_node_flag\n"); + abort(); + } + + *flags = nextflag; + nextflag <<= 1; +} + +void cmark_init_standard_node_flags(void) {} + bool cmark_node_can_contain_type(cmark_node *node, cmark_node_type child_type) { if (child_type == CMARK_NODE_DOCUMENT) { return false; @@ -48,8 +80,6 @@ bool cmark_node_can_contain_type(cmark_node *node, cmark_node_type child_type) { } static bool S_can_contain(cmark_node *node, cmark_node *child) { - cmark_node *cur; - if (node == NULL || child == NULL) { return false; } @@ -57,14 +87,16 @@ static bool S_can_contain(cmark_node *node, cmark_node *child) { return 0; } - // Verify that child is not an ancestor of node or equal to node. - cur = node; - do { - if (cur == child) { - return false; - } - cur = cur->parent; - } while (cur != NULL); + if (enable_safety_checks) { + // Verify that child is not an ancestor of node or equal to node. + cmark_node *cur = node; + do { + if (cur == child) { + return false; + } + cur = cur->parent; + } while (cur != NULL); + } return cmark_node_can_contain_type(node, (cmark_node_type) child->type); } @@ -301,6 +333,14 @@ cmark_node *cmark_node_last_child(cmark_node *node) { } } +cmark_node *cmark_node_parent_footnote_def(cmark_node *node) { + if (node == NULL) { + return NULL; + } else { + return node->parent_footnote_def; + } +} + void *cmark_node_get_user_data(cmark_node *node) { if (node == NULL) { return NULL; @@ -337,6 +377,7 @@ const char *cmark_node_get_literal(cmark_node *node) { case CMARK_NODE_HTML_INLINE: case CMARK_NODE_CODE: case CMARK_NODE_FOOTNOTE_REFERENCE: + case CMARK_NODE_FOOTNOTE_DEFINITION: return cmark_chunk_to_cstr(NODE_MEM(node), &node->as.literal); case CMARK_NODE_CODE_BLOCK: @@ -524,6 +565,31 @@ int cmark_node_set_list_tight(cmark_node *node, int tight) { } } +int cmark_node_get_item_index(cmark_node *node) { + if (node == NULL) { + return 0; + } + + if (node->type == CMARK_NODE_ITEM) { + return node->as.list.start; + } else { + return 0; + } +} + +int cmark_node_set_item_index(cmark_node *node, int idx) { + if (node == NULL || idx < 0) { + return 0; + } + + if (node->type == CMARK_NODE_ITEM) { + node->as.list.start = idx; + return 1; + } else { + return 0; + } +} + const char *cmark_node_get_fence_info(cmark_node *node) { if (node == NULL) { return NULL; diff --git a/src/plugins/ieviewer/cmark-gfm/src/node.h b/src/plugins/ieviewer/cmark-gfm/src/node.h index 6b05df17..73ca7605 100644 --- a/src/plugins/ieviewer/cmark-gfm/src/node.h +++ b/src/plugins/ieviewer/cmark-gfm/src/node.h @@ -21,6 +21,7 @@ typedef struct { cmark_delim_type delimiter; unsigned char bullet_char; bool tight; + bool checked; // For task list extension } cmark_list; typedef struct { @@ -51,8 +52,14 @@ enum cmark_node__internal_flags { CMARK_NODE__OPEN = (1 << 0), CMARK_NODE__LAST_LINE_BLANK = (1 << 1), CMARK_NODE__LAST_LINE_CHECKED = (1 << 2), + + // Extensions can register custom flags by calling `cmark_register_node_flag`. + // This is the starting value for the custom flags. + CMARK_NODE__REGISTER_FIRST = (1 << 3), }; +typedef uint16_t cmark_node_internal_flags; + struct cmark_node { cmark_strbuf content; @@ -71,10 +78,25 @@ struct cmark_node { int end_column; int internal_offset; uint16_t type; - uint16_t flags; + cmark_node_internal_flags flags; cmark_syntax_extension *extension; + /** + * Used during cmark_render() to cache the most recent non-NULL + * extension, if you go up the parent chain like this: + * + * node->parent->...parent->extension + */ + cmark_syntax_extension *ancestor_extension; + + union { + int ref_ix; + int def_count; + } footnote; + + cmark_node *parent_footnote_def; + union { cmark_chunk literal; cmark_list list; @@ -83,10 +105,31 @@ struct cmark_node { cmark_link link; cmark_custom custom; int html_block_type; + int cell_index; // For keeping track of TABLE_CELL table alignments void *opaque; } as; }; +/** + * Syntax extensions can use this function to register a custom node + * flag. The flags are stored in the `flags` field of the `cmark_node` + * struct. The `flags` parameter should be the address of a global variable + * which will store the flag value. + */ +CMARK_GFM_EXPORT +void cmark_register_node_flag(cmark_node_internal_flags *flags); + +/** + * DEPRECATED. + * + * This function was added in cmark-gfm version 0.29.0.gfm.7, and was + * required to be called at program start time, which caused + * backwards-compatibility issues in applications that use cmark-gfm as a + * library. It is now a no-op. + */ +CMARK_GFM_EXPORT +void cmark_init_standard_node_flags(void); + static CMARK_INLINE cmark_mem *cmark_node_mem(cmark_node *node) { return node->content.mem; } @@ -110,6 +153,13 @@ static CMARK_INLINE bool CMARK_NODE_INLINE_P(cmark_node *node) { CMARK_GFM_EXPORT bool cmark_node_can_contain_type(cmark_node *node, cmark_node_type child_type); +/** + * Enable (or disable) extra safety checks. These extra checks cause + * extra performance overhead (in some cases quadratic), so they are only + * intended to be used during testing. + */ +CMARK_GFM_EXPORT void cmark_enable_safety_checks(bool enable); + #ifdef __cplusplus } #endif diff --git a/src/plugins/ieviewer/cmark-gfm/src/parser.h b/src/plugins/ieviewer/cmark-gfm/src/parser.h index 245580b8..436c53f5 100644 --- a/src/plugins/ieviewer/cmark-gfm/src/parser.h +++ b/src/plugins/ieviewer/cmark-gfm/src/parser.h @@ -46,6 +46,7 @@ struct cmark_parser { /* Options set by the user, see the Options section in cmark.h */ int options; bool last_buffer_ended_with_cr; + size_t total_size; cmark_llist *syntax_extensions; cmark_llist *inline_syntax_extensions; cmark_ispunct_func backslash_ispunct; diff --git a/src/plugins/ieviewer/cmark-gfm/src/plaintext.c b/src/plugins/ieviewer/cmark-gfm/src/plaintext.c index b25e4a39..0c7d257b 100644 --- a/src/plugins/ieviewer/cmark-gfm/src/plaintext.c +++ b/src/plugins/ieviewer/cmark-gfm/src/plaintext.c @@ -16,23 +16,8 @@ static CMARK_INLINE void outc(cmark_renderer *renderer, cmark_node *node, cmark_render_code_point(renderer, c); } -// if node is a block node, returns node. -// otherwise returns first block-level node that is an ancestor of node. -// if there is no block-level ancestor, returns NULL. -static cmark_node *get_containing_block(cmark_node *node) { - while (node) { - if (CMARK_NODE_BLOCK_P(node)) { - return node; - } else { - node = node->parent; - } - } - return NULL; -} - static int S_render_node(cmark_renderer *renderer, cmark_node *node, cmark_event_type ev_type, int options) { - cmark_node *tmp; int list_number; cmark_delim_type list_delim; int i; @@ -46,14 +31,17 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node, // Don't adjust tight list status til we've started the list. // Otherwise we loose the blank line between a paragraph and // a following list. - if (!(node->type == CMARK_NODE_ITEM && node->prev == NULL && entering)) { - tmp = get_containing_block(node); - renderer->in_tight_list_item = - tmp && // tmp might be NULL if there is no containing block - ((tmp->type == CMARK_NODE_ITEM && - cmark_node_get_list_tight(tmp->parent)) || - (tmp && tmp->parent && tmp->parent->type == CMARK_NODE_ITEM && - cmark_node_get_list_tight(tmp->parent->parent))); + if (entering) { + if (node->parent && node->parent->type == CMARK_NODE_ITEM) { + renderer->in_tight_list_item = node->parent->parent->as.list.tight; + } + } else { + if (node->type == CMARK_NODE_LIST) { + renderer->in_tight_list_item = + node->parent && + node->parent->type == CMARK_NODE_ITEM && + node->parent->parent->as.list.tight; + } } if (node->extension && node->extension->plaintext_render_func) { @@ -79,13 +67,8 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node, if (cmark_node_get_list_type(node->parent) == CMARK_BULLET_LIST) { marker_width = 4; } else { - list_number = cmark_node_get_list_start(node->parent); + list_number = cmark_node_get_item_index(node); list_delim = cmark_node_get_list_delim(node->parent); - tmp = node; - while (tmp->prev) { - tmp = tmp->prev; - list_number += 1; - } // we ensure a width of at least 4 so // we get nice transition from single digits // to double diff --git a/src/plugins/ieviewer/cmark-gfm/src/references.c b/src/plugins/ieviewer/cmark-gfm/src/references.c index 7e7f34b3..0c9d3995 100644 --- a/src/plugins/ieviewer/cmark-gfm/src/references.c +++ b/src/plugins/ieviewer/cmark-gfm/src/references.c @@ -32,6 +32,7 @@ void cmark_reference_create(cmark_map *map, cmark_chunk *label, ref->title = cmark_clean_title(map->mem, title); ref->entry.age = map->size; ref->entry.next = map->refs; + ref->entry.size = ref->url.len + ref->title.len; map->refs = (cmark_map_entry *)ref; map->size++; diff --git a/src/plugins/ieviewer/cmark-gfm/src/render.c b/src/plugins/ieviewer/cmark-gfm/src/render.c index 02e9e838..1a0d2ae8 100644 --- a/src/plugins/ieviewer/cmark-gfm/src/render.c +++ b/src/plugins/ieviewer/cmark-gfm/src/render.c @@ -31,13 +31,7 @@ static void S_out(cmark_renderer *renderer, cmark_node *node, cmark_chunk remainder = cmark_chunk_literal(""); int k = renderer->buffer->size - 1; - cmark_syntax_extension *ext = NULL; - cmark_node *n = node; - while (n && !ext) { - ext = n->extension; - if (!ext) - n = n->parent; - } + cmark_syntax_extension *ext = node->ancestor_extension; if (ext && !ext->commonmark_escape_func) ext = NULL; @@ -182,6 +176,20 @@ char *cmark_render(cmark_mem *mem, cmark_node *root, int options, int width, while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) { cur = cmark_iter_get_node(iter); + if (cur->extension) { + cur->ancestor_extension = cur->extension; + } else if (cur->parent) { + cur->ancestor_extension = cur->parent->ancestor_extension; + } + if (cur->type == CMARK_NODE_ITEM) { + // Calculate the list item's index, for the benefit of output formats + // like commonmark and plaintext. + if (cur->prev) { + cmark_node_set_item_index(cur, 1 + cmark_node_get_item_index(cur->prev)); + } else { + cmark_node_set_item_index(cur, cmark_node_get_list_start(cur->parent)); + } + } if (!render_node(&renderer, cur, ev_type, options)) { // a false value causes us to skip processing // the node's contents. this is used for diff --git a/src/plugins/ieviewer/cmark-gfm/src/scanners.c b/src/plugins/ieviewer/cmark-gfm/src/scanners.c index dfa6e5c1..19720601 100644 --- a/src/plugins/ieviewer/cmark-gfm/src/scanners.c +++ b/src/plugins/ieviewer/cmark-gfm/src/scanners.c @@ -1,10520 +1,14056 @@ -/* Generated by re2c 1.1.1 */ -#include -#include "chunk.h" +/* Generated by re2c 3.0 */ #include "scanners.h" +#include "chunk.h" +#include -bufsize_t _scan_at(bufsize_t (*scanner)(const unsigned char *), cmark_chunk *c, bufsize_t offset) -{ - bufsize_t res; - unsigned char *ptr = (unsigned char *)c->data; +bufsize_t _scan_at(bufsize_t (*scanner)(const unsigned char *), cmark_chunk *c, + bufsize_t offset) { + bufsize_t res; + unsigned char *ptr = (unsigned char *)c->data; - if (ptr == NULL || offset > c->len) { - return 0; - } else { - unsigned char lim = ptr[c->len]; + if (ptr == NULL || offset > c->len) { + return 0; + } else { + unsigned char lim = ptr[c->len]; - ptr[c->len] = '\0'; - res = scanner(ptr + offset); - ptr[c->len] = lim; - } + ptr[c->len] = '\0'; + res = scanner(ptr + offset); + ptr[c->len] = lim; + } - return res; + return res; } - - // Try to match a scheme including colon. -bufsize_t _scan_scheme(const unsigned char *p) -{ +bufsize_t _scan_scheme(const unsigned char *p) { const unsigned char *marker = NULL; const unsigned char *start = p; -{ - unsigned char yych; - yych = *p; - if (yych <= '@') goto yy2; - if (yych <= 'Z') goto yy4; - if (yych <= '`') goto yy2; - if (yych <= 'z') goto yy4; -yy2: - ++p; -yy3: - { return 0; } -yy4: - yych = *(marker = ++p); - if (yych <= '/') { - if (yych <= '+') { - if (yych <= '*') goto yy3; - } else { - if (yych <= ',') goto yy3; - if (yych >= '/') goto yy3; - } - } else { - if (yych <= 'Z') { - if (yych <= '9') goto yy5; - if (yych <= '@') goto yy3; - } else { - if (yych <= '`') goto yy3; - if (yych >= '{') goto yy3; - } - } -yy5: - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych == '+') goto yy7; - } else { - if (yych != '/') goto yy7; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy8; - if (yych >= 'A') goto yy7; - } else { - if (yych <= '`') goto yy6; - if (yych <= 'z') goto yy7; - } - } -yy6: - p = marker; - goto yy3; -yy7: - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych == '+') goto yy10; - goto yy6; - } else { - if (yych == '/') goto yy6; - goto yy10; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy8; - if (yych <= '@') goto yy6; - goto yy10; - } else { - if (yych <= '`') goto yy6; - if (yych <= 'z') goto yy10; - goto yy6; - } - } -yy8: - ++p; - { return (bufsize_t)(p - start); } -yy10: - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy6; - } else { - if (yych == '/') goto yy6; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy8; - if (yych <= '@') goto yy6; - } else { - if (yych <= '`') goto yy6; - if (yych >= '{') goto yy6; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy6; - } else { - if (yych == '/') goto yy6; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy8; - if (yych <= '@') goto yy6; - } else { - if (yych <= '`') goto yy6; - if (yych >= '{') goto yy6; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy6; - } else { - if (yych == '/') goto yy6; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy8; - if (yych <= '@') goto yy6; - } else { - if (yych <= '`') goto yy6; - if (yych >= '{') goto yy6; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy6; - } else { - if (yych == '/') goto yy6; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy8; - if (yych <= '@') goto yy6; - } else { - if (yych <= '`') goto yy6; - if (yych >= '{') goto yy6; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy6; - } else { - if (yych == '/') goto yy6; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy8; - if (yych <= '@') goto yy6; - } else { - if (yych <= '`') goto yy6; - if (yych >= '{') goto yy6; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy6; - } else { - if (yych == '/') goto yy6; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy8; - if (yych <= '@') goto yy6; - } else { - if (yych <= '`') goto yy6; - if (yych >= '{') goto yy6; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy6; - } else { - if (yych == '/') goto yy6; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy8; - if (yych <= '@') goto yy6; - } else { - if (yych <= '`') goto yy6; - if (yych >= '{') goto yy6; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy6; - } else { - if (yych == '/') goto yy6; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy8; - if (yych <= '@') goto yy6; - } else { - if (yych <= '`') goto yy6; - if (yych >= '{') goto yy6; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy6; - } else { - if (yych == '/') goto yy6; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy8; - if (yych <= '@') goto yy6; - } else { - if (yych <= '`') goto yy6; - if (yych >= '{') goto yy6; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy6; - } else { - if (yych == '/') goto yy6; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy8; - if (yych <= '@') goto yy6; - } else { - if (yych <= '`') goto yy6; - if (yych >= '{') goto yy6; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy6; - } else { - if (yych == '/') goto yy6; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy8; - if (yych <= '@') goto yy6; - } else { - if (yych <= '`') goto yy6; - if (yych >= '{') goto yy6; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy6; - } else { - if (yych == '/') goto yy6; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy8; - if (yych <= '@') goto yy6; - } else { - if (yych <= '`') goto yy6; - if (yych >= '{') goto yy6; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy6; - } else { - if (yych == '/') goto yy6; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy8; - if (yych <= '@') goto yy6; - } else { - if (yych <= '`') goto yy6; - if (yych >= '{') goto yy6; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy6; - } else { - if (yych == '/') goto yy6; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy8; - if (yych <= '@') goto yy6; - } else { - if (yych <= '`') goto yy6; - if (yych >= '{') goto yy6; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy6; - } else { - if (yych == '/') goto yy6; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy8; - if (yych <= '@') goto yy6; - } else { - if (yych <= '`') goto yy6; - if (yych >= '{') goto yy6; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy6; - } else { - if (yych == '/') goto yy6; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy8; - if (yych <= '@') goto yy6; - } else { - if (yych <= '`') goto yy6; - if (yych >= '{') goto yy6; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy6; - } else { - if (yych == '/') goto yy6; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy8; - if (yych <= '@') goto yy6; - } else { - if (yych <= '`') goto yy6; - if (yych >= '{') goto yy6; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy6; - } else { - if (yych == '/') goto yy6; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy8; - if (yych <= '@') goto yy6; - } else { - if (yych <= '`') goto yy6; - if (yych >= '{') goto yy6; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy6; - } else { - if (yych == '/') goto yy6; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy8; - if (yych <= '@') goto yy6; - } else { - if (yych <= '`') goto yy6; - if (yych >= '{') goto yy6; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy6; - } else { - if (yych == '/') goto yy6; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy8; - if (yych <= '@') goto yy6; - } else { - if (yych <= '`') goto yy6; - if (yych >= '{') goto yy6; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy6; - } else { - if (yych == '/') goto yy6; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy8; - if (yych <= '@') goto yy6; - } else { - if (yych <= '`') goto yy6; - if (yych >= '{') goto yy6; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy6; - } else { - if (yych == '/') goto yy6; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy8; - if (yych <= '@') goto yy6; - } else { - if (yych <= '`') goto yy6; - if (yych >= '{') goto yy6; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy6; - } else { - if (yych == '/') goto yy6; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy8; - if (yych <= '@') goto yy6; - } else { - if (yych <= '`') goto yy6; - if (yych >= '{') goto yy6; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy6; - } else { - if (yych == '/') goto yy6; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy8; - if (yych <= '@') goto yy6; - } else { - if (yych <= '`') goto yy6; - if (yych >= '{') goto yy6; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy6; - } else { - if (yych == '/') goto yy6; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy8; - if (yych <= '@') goto yy6; - } else { - if (yych <= '`') goto yy6; - if (yych >= '{') goto yy6; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy6; - } else { - if (yych == '/') goto yy6; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy8; - if (yych <= '@') goto yy6; - } else { - if (yych <= '`') goto yy6; - if (yych >= '{') goto yy6; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy6; - } else { - if (yych == '/') goto yy6; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy8; - if (yych <= '@') goto yy6; - } else { - if (yych <= '`') goto yy6; - if (yych >= '{') goto yy6; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy6; - } else { - if (yych == '/') goto yy6; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy8; - if (yych <= '@') goto yy6; - } else { - if (yych <= '`') goto yy6; - if (yych >= '{') goto yy6; - } - } - yych = *++p; - if (yych == ':') goto yy8; - goto yy6; -} - + { + unsigned char yych; + yych = *p; + if (yych <= '@') + goto yy1; + if (yych <= 'Z') + goto yy3; + if (yych <= '`') + goto yy1; + if (yych <= 'z') + goto yy3; + yy1: + ++p; + yy2 : { return 0; } + yy3: + yych = *(marker = ++p); + if (yych <= '/') { + if (yych <= '+') { + if (yych <= '*') + goto yy2; + } else { + if (yych <= ',') + goto yy2; + if (yych >= '/') + goto yy2; + } + } else { + if (yych <= 'Z') { + if (yych <= '9') + goto yy4; + if (yych <= '@') + goto yy2; + } else { + if (yych <= '`') + goto yy2; + if (yych >= '{') + goto yy2; + } + } + yy4: + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych == '+') + goto yy6; + } else { + if (yych != '/') + goto yy6; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy7; + if (yych >= 'A') + goto yy6; + } else { + if (yych <= '`') + goto yy5; + if (yych <= 'z') + goto yy6; + } + } + yy5: + p = marker; + goto yy2; + yy6: + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych == '+') + goto yy8; + goto yy5; + } else { + if (yych == '/') + goto yy5; + goto yy8; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy7; + if (yych <= '@') + goto yy5; + goto yy8; + } else { + if (yych <= '`') + goto yy5; + if (yych <= 'z') + goto yy8; + goto yy5; + } + } + yy7: + ++p; + { return (bufsize_t)(p - start); } + yy8: + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy5; + } else { + if (yych == '/') + goto yy5; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy7; + if (yych <= '@') + goto yy5; + } else { + if (yych <= '`') + goto yy5; + if (yych >= '{') + goto yy5; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy5; + } else { + if (yych == '/') + goto yy5; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy7; + if (yych <= '@') + goto yy5; + } else { + if (yych <= '`') + goto yy5; + if (yych >= '{') + goto yy5; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy5; + } else { + if (yych == '/') + goto yy5; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy7; + if (yych <= '@') + goto yy5; + } else { + if (yych <= '`') + goto yy5; + if (yych >= '{') + goto yy5; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy5; + } else { + if (yych == '/') + goto yy5; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy7; + if (yych <= '@') + goto yy5; + } else { + if (yych <= '`') + goto yy5; + if (yych >= '{') + goto yy5; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy5; + } else { + if (yych == '/') + goto yy5; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy7; + if (yych <= '@') + goto yy5; + } else { + if (yych <= '`') + goto yy5; + if (yych >= '{') + goto yy5; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy5; + } else { + if (yych == '/') + goto yy5; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy7; + if (yych <= '@') + goto yy5; + } else { + if (yych <= '`') + goto yy5; + if (yych >= '{') + goto yy5; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy5; + } else { + if (yych == '/') + goto yy5; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy7; + if (yych <= '@') + goto yy5; + } else { + if (yych <= '`') + goto yy5; + if (yych >= '{') + goto yy5; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy5; + } else { + if (yych == '/') + goto yy5; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy7; + if (yych <= '@') + goto yy5; + } else { + if (yych <= '`') + goto yy5; + if (yych >= '{') + goto yy5; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy5; + } else { + if (yych == '/') + goto yy5; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy7; + if (yych <= '@') + goto yy5; + } else { + if (yych <= '`') + goto yy5; + if (yych >= '{') + goto yy5; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy5; + } else { + if (yych == '/') + goto yy5; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy7; + if (yych <= '@') + goto yy5; + } else { + if (yych <= '`') + goto yy5; + if (yych >= '{') + goto yy5; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy5; + } else { + if (yych == '/') + goto yy5; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy7; + if (yych <= '@') + goto yy5; + } else { + if (yych <= '`') + goto yy5; + if (yych >= '{') + goto yy5; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy5; + } else { + if (yych == '/') + goto yy5; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy7; + if (yych <= '@') + goto yy5; + } else { + if (yych <= '`') + goto yy5; + if (yych >= '{') + goto yy5; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy5; + } else { + if (yych == '/') + goto yy5; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy7; + if (yych <= '@') + goto yy5; + } else { + if (yych <= '`') + goto yy5; + if (yych >= '{') + goto yy5; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy5; + } else { + if (yych == '/') + goto yy5; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy7; + if (yych <= '@') + goto yy5; + } else { + if (yych <= '`') + goto yy5; + if (yych >= '{') + goto yy5; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy5; + } else { + if (yych == '/') + goto yy5; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy7; + if (yych <= '@') + goto yy5; + } else { + if (yych <= '`') + goto yy5; + if (yych >= '{') + goto yy5; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy5; + } else { + if (yych == '/') + goto yy5; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy7; + if (yych <= '@') + goto yy5; + } else { + if (yych <= '`') + goto yy5; + if (yych >= '{') + goto yy5; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy5; + } else { + if (yych == '/') + goto yy5; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy7; + if (yych <= '@') + goto yy5; + } else { + if (yych <= '`') + goto yy5; + if (yych >= '{') + goto yy5; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy5; + } else { + if (yych == '/') + goto yy5; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy7; + if (yych <= '@') + goto yy5; + } else { + if (yych <= '`') + goto yy5; + if (yych >= '{') + goto yy5; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy5; + } else { + if (yych == '/') + goto yy5; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy7; + if (yych <= '@') + goto yy5; + } else { + if (yych <= '`') + goto yy5; + if (yych >= '{') + goto yy5; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy5; + } else { + if (yych == '/') + goto yy5; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy7; + if (yych <= '@') + goto yy5; + } else { + if (yych <= '`') + goto yy5; + if (yych >= '{') + goto yy5; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy5; + } else { + if (yych == '/') + goto yy5; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy7; + if (yych <= '@') + goto yy5; + } else { + if (yych <= '`') + goto yy5; + if (yych >= '{') + goto yy5; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy5; + } else { + if (yych == '/') + goto yy5; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy7; + if (yych <= '@') + goto yy5; + } else { + if (yych <= '`') + goto yy5; + if (yych >= '{') + goto yy5; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy5; + } else { + if (yych == '/') + goto yy5; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy7; + if (yych <= '@') + goto yy5; + } else { + if (yych <= '`') + goto yy5; + if (yych >= '{') + goto yy5; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy5; + } else { + if (yych == '/') + goto yy5; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy7; + if (yych <= '@') + goto yy5; + } else { + if (yych <= '`') + goto yy5; + if (yych >= '{') + goto yy5; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy5; + } else { + if (yych == '/') + goto yy5; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy7; + if (yych <= '@') + goto yy5; + } else { + if (yych <= '`') + goto yy5; + if (yych >= '{') + goto yy5; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy5; + } else { + if (yych == '/') + goto yy5; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy7; + if (yych <= '@') + goto yy5; + } else { + if (yych <= '`') + goto yy5; + if (yych >= '{') + goto yy5; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy5; + } else { + if (yych == '/') + goto yy5; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy7; + if (yych <= '@') + goto yy5; + } else { + if (yych <= '`') + goto yy5; + if (yych >= '{') + goto yy5; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy5; + } else { + if (yych == '/') + goto yy5; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy7; + if (yych <= '@') + goto yy5; + } else { + if (yych <= '`') + goto yy5; + if (yych >= '{') + goto yy5; + } + } + yych = *++p; + if (yych == ':') + goto yy7; + goto yy5; + } } // Try to match URI autolink after first <, returning number of chars matched. -bufsize_t _scan_autolink_uri(const unsigned char *p) -{ +bufsize_t _scan_autolink_uri(const unsigned char *p) { const unsigned char *marker = NULL; const unsigned char *start = p; -{ - unsigned char yych; - static const unsigned char yybm[] = { - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 0, 128, 0, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - }; - yych = *p; - if (yych <= '@') goto yy41; - if (yych <= 'Z') goto yy43; - if (yych <= '`') goto yy41; - if (yych <= 'z') goto yy43; -yy41: - ++p; -yy42: - { return 0; } -yy43: - yych = *(marker = ++p); - if (yych <= '/') { - if (yych <= '+') { - if (yych <= '*') goto yy42; - } else { - if (yych <= ',') goto yy42; - if (yych >= '/') goto yy42; - } - } else { - if (yych <= 'Z') { - if (yych <= '9') goto yy44; - if (yych <= '@') goto yy42; - } else { - if (yych <= '`') goto yy42; - if (yych >= '{') goto yy42; - } - } -yy44: - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych == '+') goto yy46; - } else { - if (yych != '/') goto yy46; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy47; - if (yych >= 'A') goto yy46; - } else { - if (yych <= '`') goto yy45; - if (yych <= 'z') goto yy46; - } - } -yy45: - p = marker; - goto yy42; -yy46: - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych == '+') goto yy49; - goto yy45; - } else { - if (yych == '/') goto yy45; - goto yy49; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy47; - if (yych <= '@') goto yy45; - goto yy49; - } else { - if (yych <= '`') goto yy45; - if (yych <= 'z') goto yy49; - goto yy45; - } - } -yy47: - yych = *++p; - if (yybm[0+yych] & 128) { - goto yy47; - } - if (yych <= 0xEC) { - if (yych <= 0xC1) { - if (yych <= '<') goto yy45; - if (yych <= '>') goto yy50; - goto yy45; - } else { - if (yych <= 0xDF) goto yy52; - if (yych <= 0xE0) goto yy53; - goto yy54; - } - } else { - if (yych <= 0xF0) { - if (yych <= 0xED) goto yy55; - if (yych <= 0xEF) goto yy54; - goto yy56; - } else { - if (yych <= 0xF3) goto yy57; - if (yych <= 0xF4) goto yy58; - goto yy45; - } - } -yy49: - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych == '+') goto yy59; - goto yy45; - } else { - if (yych == '/') goto yy45; - goto yy59; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy47; - if (yych <= '@') goto yy45; - goto yy59; - } else { - if (yych <= '`') goto yy45; - if (yych <= 'z') goto yy59; - goto yy45; - } - } -yy50: - ++p; - { return (bufsize_t)(p - start); } -yy52: - yych = *++p; - if (yych <= 0x7F) goto yy45; - if (yych <= 0xBF) goto yy47; - goto yy45; -yy53: - yych = *++p; - if (yych <= 0x9F) goto yy45; - if (yych <= 0xBF) goto yy52; - goto yy45; -yy54: - yych = *++p; - if (yych <= 0x7F) goto yy45; - if (yych <= 0xBF) goto yy52; - goto yy45; -yy55: - yych = *++p; - if (yych <= 0x7F) goto yy45; - if (yych <= 0x9F) goto yy52; - goto yy45; -yy56: - yych = *++p; - if (yych <= 0x8F) goto yy45; - if (yych <= 0xBF) goto yy54; - goto yy45; -yy57: - yych = *++p; - if (yych <= 0x7F) goto yy45; - if (yych <= 0xBF) goto yy54; - goto yy45; -yy58: - yych = *++p; - if (yych <= 0x7F) goto yy45; - if (yych <= 0x8F) goto yy54; - goto yy45; -yy59: - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy45; - } else { - if (yych == '/') goto yy45; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy47; - if (yych <= '@') goto yy45; - } else { - if (yych <= '`') goto yy45; - if (yych >= '{') goto yy45; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy45; - } else { - if (yych == '/') goto yy45; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy47; - if (yych <= '@') goto yy45; - } else { - if (yych <= '`') goto yy45; - if (yych >= '{') goto yy45; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy45; - } else { - if (yych == '/') goto yy45; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy47; - if (yych <= '@') goto yy45; - } else { - if (yych <= '`') goto yy45; - if (yych >= '{') goto yy45; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy45; - } else { - if (yych == '/') goto yy45; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy47; - if (yych <= '@') goto yy45; - } else { - if (yych <= '`') goto yy45; - if (yych >= '{') goto yy45; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy45; - } else { - if (yych == '/') goto yy45; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy47; - if (yych <= '@') goto yy45; - } else { - if (yych <= '`') goto yy45; - if (yych >= '{') goto yy45; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy45; - } else { - if (yych == '/') goto yy45; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy47; - if (yych <= '@') goto yy45; - } else { - if (yych <= '`') goto yy45; - if (yych >= '{') goto yy45; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy45; - } else { - if (yych == '/') goto yy45; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy47; - if (yych <= '@') goto yy45; - } else { - if (yych <= '`') goto yy45; - if (yych >= '{') goto yy45; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy45; - } else { - if (yych == '/') goto yy45; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy47; - if (yych <= '@') goto yy45; - } else { - if (yych <= '`') goto yy45; - if (yych >= '{') goto yy45; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy45; - } else { - if (yych == '/') goto yy45; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy47; - if (yych <= '@') goto yy45; - } else { - if (yych <= '`') goto yy45; - if (yych >= '{') goto yy45; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy45; - } else { - if (yych == '/') goto yy45; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy47; - if (yych <= '@') goto yy45; - } else { - if (yych <= '`') goto yy45; - if (yych >= '{') goto yy45; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy45; - } else { - if (yych == '/') goto yy45; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy47; - if (yych <= '@') goto yy45; - } else { - if (yych <= '`') goto yy45; - if (yych >= '{') goto yy45; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy45; - } else { - if (yych == '/') goto yy45; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy47; - if (yych <= '@') goto yy45; - } else { - if (yych <= '`') goto yy45; - if (yych >= '{') goto yy45; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy45; - } else { - if (yych == '/') goto yy45; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy47; - if (yych <= '@') goto yy45; - } else { - if (yych <= '`') goto yy45; - if (yych >= '{') goto yy45; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy45; - } else { - if (yych == '/') goto yy45; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy47; - if (yych <= '@') goto yy45; - } else { - if (yych <= '`') goto yy45; - if (yych >= '{') goto yy45; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy45; - } else { - if (yych == '/') goto yy45; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy47; - if (yych <= '@') goto yy45; - } else { - if (yych <= '`') goto yy45; - if (yych >= '{') goto yy45; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy45; - } else { - if (yych == '/') goto yy45; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy47; - if (yych <= '@') goto yy45; - } else { - if (yych <= '`') goto yy45; - if (yych >= '{') goto yy45; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy45; - } else { - if (yych == '/') goto yy45; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy47; - if (yych <= '@') goto yy45; - } else { - if (yych <= '`') goto yy45; - if (yych >= '{') goto yy45; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy45; - } else { - if (yych == '/') goto yy45; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy47; - if (yych <= '@') goto yy45; - } else { - if (yych <= '`') goto yy45; - if (yych >= '{') goto yy45; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy45; - } else { - if (yych == '/') goto yy45; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy47; - if (yych <= '@') goto yy45; - } else { - if (yych <= '`') goto yy45; - if (yych >= '{') goto yy45; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy45; - } else { - if (yych == '/') goto yy45; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy47; - if (yych <= '@') goto yy45; - } else { - if (yych <= '`') goto yy45; - if (yych >= '{') goto yy45; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy45; - } else { - if (yych == '/') goto yy45; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy47; - if (yych <= '@') goto yy45; - } else { - if (yych <= '`') goto yy45; - if (yych >= '{') goto yy45; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy45; - } else { - if (yych == '/') goto yy45; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy47; - if (yych <= '@') goto yy45; - } else { - if (yych <= '`') goto yy45; - if (yych >= '{') goto yy45; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy45; - } else { - if (yych == '/') goto yy45; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy47; - if (yych <= '@') goto yy45; - } else { - if (yych <= '`') goto yy45; - if (yych >= '{') goto yy45; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy45; - } else { - if (yych == '/') goto yy45; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy47; - if (yych <= '@') goto yy45; - } else { - if (yych <= '`') goto yy45; - if (yych >= '{') goto yy45; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy45; - } else { - if (yych == '/') goto yy45; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy47; - if (yych <= '@') goto yy45; - } else { - if (yych <= '`') goto yy45; - if (yych >= '{') goto yy45; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy45; - } else { - if (yych == '/') goto yy45; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy47; - if (yych <= '@') goto yy45; - } else { - if (yych <= '`') goto yy45; - if (yych >= '{') goto yy45; - } - } - yych = *++p; - if (yych <= '9') { - if (yych <= ',') { - if (yych != '+') goto yy45; - } else { - if (yych == '/') goto yy45; - } - } else { - if (yych <= 'Z') { - if (yych <= ':') goto yy47; - if (yych <= '@') goto yy45; - } else { - if (yych <= '`') goto yy45; - if (yych >= '{') goto yy45; - } - } - yych = *++p; - if (yych == ':') goto yy47; - goto yy45; + { + unsigned char yych; + static const unsigned char yybm[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 0, 128, 0, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + }; + yych = *p; + if (yych <= '@') + goto yy10; + if (yych <= 'Z') + goto yy12; + if (yych <= '`') + goto yy10; + if (yych <= 'z') + goto yy12; + yy10: + ++p; + yy11 : { return 0; } + yy12: + yych = *(marker = ++p); + if (yych <= '/') { + if (yych <= '+') { + if (yych <= '*') + goto yy11; + } else { + if (yych <= ',') + goto yy11; + if (yych >= '/') + goto yy11; + } + } else { + if (yych <= 'Z') { + if (yych <= '9') + goto yy13; + if (yych <= '@') + goto yy11; + } else { + if (yych <= '`') + goto yy11; + if (yych >= '{') + goto yy11; + } + } + yy13: + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych == '+') + goto yy15; + } else { + if (yych != '/') + goto yy15; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy16; + if (yych >= 'A') + goto yy15; + } else { + if (yych <= '`') + goto yy14; + if (yych <= 'z') + goto yy15; + } + } + yy14: + p = marker; + goto yy11; + yy15: + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych == '+') + goto yy17; + goto yy14; + } else { + if (yych == '/') + goto yy14; + goto yy17; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy16; + if (yych <= '@') + goto yy14; + goto yy17; + } else { + if (yych <= '`') + goto yy14; + if (yych <= 'z') + goto yy17; + goto yy14; + } + } + yy16: + yych = *++p; + if (yybm[0 + yych] & 128) { + goto yy16; + } + if (yych <= 0xEC) { + if (yych <= 0xC1) { + if (yych <= '<') + goto yy14; + if (yych <= '>') + goto yy18; + goto yy14; + } else { + if (yych <= 0xDF) + goto yy19; + if (yych <= 0xE0) + goto yy20; + goto yy21; + } + } else { + if (yych <= 0xF0) { + if (yych <= 0xED) + goto yy22; + if (yych <= 0xEF) + goto yy21; + goto yy23; + } else { + if (yych <= 0xF3) + goto yy24; + if (yych <= 0xF4) + goto yy25; + goto yy14; + } + } + yy17: + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych == '+') + goto yy26; + goto yy14; + } else { + if (yych == '/') + goto yy14; + goto yy26; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy16; + if (yych <= '@') + goto yy14; + goto yy26; + } else { + if (yych <= '`') + goto yy14; + if (yych <= 'z') + goto yy26; + goto yy14; + } + } + yy18: + ++p; + { return (bufsize_t)(p - start); } + yy19: + yych = *++p; + if (yych <= 0x7F) + goto yy14; + if (yych <= 0xBF) + goto yy16; + goto yy14; + yy20: + yych = *++p; + if (yych <= 0x9F) + goto yy14; + if (yych <= 0xBF) + goto yy19; + goto yy14; + yy21: + yych = *++p; + if (yych <= 0x7F) + goto yy14; + if (yych <= 0xBF) + goto yy19; + goto yy14; + yy22: + yych = *++p; + if (yych <= 0x7F) + goto yy14; + if (yych <= 0x9F) + goto yy19; + goto yy14; + yy23: + yych = *++p; + if (yych <= 0x8F) + goto yy14; + if (yych <= 0xBF) + goto yy21; + goto yy14; + yy24: + yych = *++p; + if (yych <= 0x7F) + goto yy14; + if (yych <= 0xBF) + goto yy21; + goto yy14; + yy25: + yych = *++p; + if (yych <= 0x7F) + goto yy14; + if (yych <= 0x8F) + goto yy21; + goto yy14; + yy26: + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy14; + } else { + if (yych == '/') + goto yy14; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy16; + if (yych <= '@') + goto yy14; + } else { + if (yych <= '`') + goto yy14; + if (yych >= '{') + goto yy14; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy14; + } else { + if (yych == '/') + goto yy14; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy16; + if (yych <= '@') + goto yy14; + } else { + if (yych <= '`') + goto yy14; + if (yych >= '{') + goto yy14; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy14; + } else { + if (yych == '/') + goto yy14; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy16; + if (yych <= '@') + goto yy14; + } else { + if (yych <= '`') + goto yy14; + if (yych >= '{') + goto yy14; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy14; + } else { + if (yych == '/') + goto yy14; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy16; + if (yych <= '@') + goto yy14; + } else { + if (yych <= '`') + goto yy14; + if (yych >= '{') + goto yy14; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy14; + } else { + if (yych == '/') + goto yy14; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy16; + if (yych <= '@') + goto yy14; + } else { + if (yych <= '`') + goto yy14; + if (yych >= '{') + goto yy14; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy14; + } else { + if (yych == '/') + goto yy14; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy16; + if (yych <= '@') + goto yy14; + } else { + if (yych <= '`') + goto yy14; + if (yych >= '{') + goto yy14; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy14; + } else { + if (yych == '/') + goto yy14; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy16; + if (yych <= '@') + goto yy14; + } else { + if (yych <= '`') + goto yy14; + if (yych >= '{') + goto yy14; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy14; + } else { + if (yych == '/') + goto yy14; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy16; + if (yych <= '@') + goto yy14; + } else { + if (yych <= '`') + goto yy14; + if (yych >= '{') + goto yy14; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy14; + } else { + if (yych == '/') + goto yy14; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy16; + if (yych <= '@') + goto yy14; + } else { + if (yych <= '`') + goto yy14; + if (yych >= '{') + goto yy14; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy14; + } else { + if (yych == '/') + goto yy14; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy16; + if (yych <= '@') + goto yy14; + } else { + if (yych <= '`') + goto yy14; + if (yych >= '{') + goto yy14; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy14; + } else { + if (yych == '/') + goto yy14; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy16; + if (yych <= '@') + goto yy14; + } else { + if (yych <= '`') + goto yy14; + if (yych >= '{') + goto yy14; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy14; + } else { + if (yych == '/') + goto yy14; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy16; + if (yych <= '@') + goto yy14; + } else { + if (yych <= '`') + goto yy14; + if (yych >= '{') + goto yy14; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy14; + } else { + if (yych == '/') + goto yy14; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy16; + if (yych <= '@') + goto yy14; + } else { + if (yych <= '`') + goto yy14; + if (yych >= '{') + goto yy14; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy14; + } else { + if (yych == '/') + goto yy14; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy16; + if (yych <= '@') + goto yy14; + } else { + if (yych <= '`') + goto yy14; + if (yych >= '{') + goto yy14; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy14; + } else { + if (yych == '/') + goto yy14; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy16; + if (yych <= '@') + goto yy14; + } else { + if (yych <= '`') + goto yy14; + if (yych >= '{') + goto yy14; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy14; + } else { + if (yych == '/') + goto yy14; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy16; + if (yych <= '@') + goto yy14; + } else { + if (yych <= '`') + goto yy14; + if (yych >= '{') + goto yy14; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy14; + } else { + if (yych == '/') + goto yy14; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy16; + if (yych <= '@') + goto yy14; + } else { + if (yych <= '`') + goto yy14; + if (yych >= '{') + goto yy14; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy14; + } else { + if (yych == '/') + goto yy14; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy16; + if (yych <= '@') + goto yy14; + } else { + if (yych <= '`') + goto yy14; + if (yych >= '{') + goto yy14; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy14; + } else { + if (yych == '/') + goto yy14; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy16; + if (yych <= '@') + goto yy14; + } else { + if (yych <= '`') + goto yy14; + if (yych >= '{') + goto yy14; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy14; + } else { + if (yych == '/') + goto yy14; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy16; + if (yych <= '@') + goto yy14; + } else { + if (yych <= '`') + goto yy14; + if (yych >= '{') + goto yy14; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy14; + } else { + if (yych == '/') + goto yy14; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy16; + if (yych <= '@') + goto yy14; + } else { + if (yych <= '`') + goto yy14; + if (yych >= '{') + goto yy14; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy14; + } else { + if (yych == '/') + goto yy14; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy16; + if (yych <= '@') + goto yy14; + } else { + if (yych <= '`') + goto yy14; + if (yych >= '{') + goto yy14; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy14; + } else { + if (yych == '/') + goto yy14; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy16; + if (yych <= '@') + goto yy14; + } else { + if (yych <= '`') + goto yy14; + if (yych >= '{') + goto yy14; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy14; + } else { + if (yych == '/') + goto yy14; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy16; + if (yych <= '@') + goto yy14; + } else { + if (yych <= '`') + goto yy14; + if (yych >= '{') + goto yy14; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy14; + } else { + if (yych == '/') + goto yy14; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy16; + if (yych <= '@') + goto yy14; + } else { + if (yych <= '`') + goto yy14; + if (yych >= '{') + goto yy14; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy14; + } else { + if (yych == '/') + goto yy14; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy16; + if (yych <= '@') + goto yy14; + } else { + if (yych <= '`') + goto yy14; + if (yych >= '{') + goto yy14; + } + } + yych = *++p; + if (yych <= '9') { + if (yych <= ',') { + if (yych != '+') + goto yy14; + } else { + if (yych == '/') + goto yy14; + } + } else { + if (yych <= 'Z') { + if (yych <= ':') + goto yy16; + if (yych <= '@') + goto yy14; + } else { + if (yych <= '`') + goto yy14; + if (yych >= '{') + goto yy14; + } + } + yych = *++p; + if (yych == ':') + goto yy16; + goto yy14; + } } +// Try to match email autolink after first <, returning num of chars matched. +bufsize_t _scan_autolink_email(const unsigned char *p) { + const unsigned char *marker = NULL; + const unsigned char *start = p; + + { + unsigned char yych; + static const unsigned char yybm[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 128, 0, 128, 128, 128, 128, 128, 0, 0, + 128, 128, 0, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 0, 0, 0, 128, 0, 128, 0, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 0, 0, 0, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + }; + yych = *p; + if (yych <= '9') { + if (yych <= '\'') { + if (yych == '!') + goto yy30; + if (yych >= '#') + goto yy30; + } else { + if (yych <= ')') + goto yy28; + if (yych != ',') + goto yy30; + } + } else { + if (yych <= '?') { + if (yych == '=') + goto yy30; + if (yych >= '?') + goto yy30; + } else { + if (yych <= 'Z') { + if (yych >= 'A') + goto yy30; + } else { + if (yych <= ']') + goto yy28; + if (yych <= '~') + goto yy30; + } + } + } + yy28: + ++p; + yy29 : { return 0; } + yy30: + yych = *(marker = ++p); + if (yych <= ',') { + if (yych <= '"') { + if (yych == '!') + goto yy32; + goto yy29; + } else { + if (yych <= '\'') + goto yy32; + if (yych <= ')') + goto yy29; + if (yych <= '+') + goto yy32; + goto yy29; + } + } else { + if (yych <= '>') { + if (yych <= '9') + goto yy32; + if (yych == '=') + goto yy32; + goto yy29; + } else { + if (yych <= 'Z') + goto yy32; + if (yych <= ']') + goto yy29; + if (yych <= '~') + goto yy32; + goto yy29; + } + } + yy31: + yych = *++p; + yy32: + if (yybm[0 + yych] & 128) { + goto yy31; + } + if (yych <= '>') + goto yy33; + if (yych <= '@') + goto yy34; + yy33: + p = marker; + goto yy29; + yy34: + yych = *++p; + if (yych <= '@') { + if (yych <= '/') + goto yy33; + if (yych >= ':') + goto yy33; + } else { + if (yych <= 'Z') + goto yy35; + if (yych <= '`') + goto yy33; + if (yych >= '{') + goto yy33; + } + yy35: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy36; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy36; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy36; + goto yy33; + } + } + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy38; + if (yych <= '/') + goto yy33; + goto yy39; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy39; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy39; + goto yy33; + } + } + yy36: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych <= '-') + goto yy38; + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy39; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy39; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy39; + goto yy33; + } + } + yy37: + ++p; + { return (bufsize_t)(p - start); } + yy38: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy40; + if (yych <= '/') + goto yy33; + goto yy41; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy41; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy41; + goto yy33; + } + } + yy39: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy41; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy41; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy41; + goto yy33; + } + } + yy40: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy42; + if (yych <= '/') + goto yy33; + goto yy43; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy43; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy43; + goto yy33; + } + } + yy41: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy43; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy43; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy43; + goto yy33; + } + } + yy42: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy44; + if (yych <= '/') + goto yy33; + goto yy45; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy45; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy45; + goto yy33; + } + } + yy43: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy45; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy45; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy45; + goto yy33; + } + } + yy44: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy46; + if (yych <= '/') + goto yy33; + goto yy47; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy47; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy47; + goto yy33; + } + } + yy45: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy47; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy47; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy47; + goto yy33; + } + } + yy46: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy48; + if (yych <= '/') + goto yy33; + goto yy49; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy49; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy49; + goto yy33; + } + } + yy47: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy49; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy49; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy49; + goto yy33; + } + } + yy48: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy50; + if (yych <= '/') + goto yy33; + goto yy51; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy51; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy51; + goto yy33; + } + } + yy49: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy51; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy51; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy51; + goto yy33; + } + } + yy50: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy52; + if (yych <= '/') + goto yy33; + goto yy53; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy53; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy53; + goto yy33; + } + } + yy51: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy53; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy53; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy53; + goto yy33; + } + } + yy52: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy54; + if (yych <= '/') + goto yy33; + goto yy55; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy55; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy55; + goto yy33; + } + } + yy53: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy55; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy55; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy55; + goto yy33; + } + } + yy54: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy56; + if (yych <= '/') + goto yy33; + goto yy57; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy57; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy57; + goto yy33; + } + } + yy55: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy57; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy57; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy57; + goto yy33; + } + } + yy56: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy58; + if (yych <= '/') + goto yy33; + goto yy59; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy59; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy59; + goto yy33; + } + } + yy57: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy59; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy59; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy59; + goto yy33; + } + } + yy58: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy60; + if (yych <= '/') + goto yy33; + goto yy61; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy61; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy61; + goto yy33; + } + } + yy59: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy61; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy61; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy61; + goto yy33; + } + } + yy60: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy62; + if (yych <= '/') + goto yy33; + goto yy63; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy63; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy63; + goto yy33; + } + } + yy61: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy63; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy63; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy63; + goto yy33; + } + } + yy62: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy64; + if (yych <= '/') + goto yy33; + goto yy65; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy65; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy65; + goto yy33; + } + } + yy63: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy65; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy65; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy65; + goto yy33; + } + } + yy64: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy66; + if (yych <= '/') + goto yy33; + goto yy67; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy67; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy67; + goto yy33; + } + } + yy65: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy67; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy67; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy67; + goto yy33; + } + } + yy66: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy68; + if (yych <= '/') + goto yy33; + goto yy69; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy69; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy69; + goto yy33; + } + } + yy67: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy69; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy69; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy69; + goto yy33; + } + } + yy68: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy70; + if (yych <= '/') + goto yy33; + goto yy71; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy71; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy71; + goto yy33; + } + } + yy69: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy71; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy71; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy71; + goto yy33; + } + } + yy70: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy72; + if (yych <= '/') + goto yy33; + goto yy73; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy73; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy73; + goto yy33; + } + } + yy71: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy73; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy73; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy73; + goto yy33; + } + } + yy72: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy74; + if (yych <= '/') + goto yy33; + goto yy75; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy75; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy75; + goto yy33; + } + } + yy73: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy75; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy75; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy75; + goto yy33; + } + } + yy74: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy76; + if (yych <= '/') + goto yy33; + goto yy77; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy77; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy77; + goto yy33; + } + } + yy75: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy77; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy77; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy77; + goto yy33; + } + } + yy76: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy78; + if (yych <= '/') + goto yy33; + goto yy79; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy79; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy79; + goto yy33; + } + } + yy77: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy79; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy79; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy79; + goto yy33; + } + } + yy78: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy80; + if (yych <= '/') + goto yy33; + goto yy81; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy81; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy81; + goto yy33; + } + } + yy79: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy81; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy81; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy81; + goto yy33; + } + } + yy80: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy82; + if (yych <= '/') + goto yy33; + goto yy83; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy83; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy83; + goto yy33; + } + } + yy81: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy83; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy83; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy83; + goto yy33; + } + } + yy82: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy84; + if (yych <= '/') + goto yy33; + goto yy85; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy85; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy85; + goto yy33; + } + } + yy83: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy85; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy85; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy85; + goto yy33; + } + } + yy84: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy86; + if (yych <= '/') + goto yy33; + goto yy87; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy87; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy87; + goto yy33; + } + } + yy85: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy87; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy87; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy87; + goto yy33; + } + } + yy86: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy88; + if (yych <= '/') + goto yy33; + goto yy89; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy89; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy89; + goto yy33; + } + } + yy87: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy89; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy89; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy89; + goto yy33; + } + } + yy88: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy90; + if (yych <= '/') + goto yy33; + goto yy91; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy91; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy91; + goto yy33; + } + } + yy89: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy91; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy91; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy91; + goto yy33; + } + } + yy90: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy92; + if (yych <= '/') + goto yy33; + goto yy93; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy93; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy93; + goto yy33; + } + } + yy91: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy93; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy93; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy93; + goto yy33; + } + } + yy92: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy94; + if (yych <= '/') + goto yy33; + goto yy95; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy95; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy95; + goto yy33; + } + } + yy93: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy95; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy95; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy95; + goto yy33; + } + } + yy94: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy96; + if (yych <= '/') + goto yy33; + goto yy97; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy97; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy97; + goto yy33; + } + } + yy95: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy97; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy97; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy97; + goto yy33; + } + } + yy96: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy98; + if (yych <= '/') + goto yy33; + goto yy99; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy99; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy99; + goto yy33; + } + } + yy97: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy99; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy99; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy99; + goto yy33; + } + } + yy98: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy100; + if (yych <= '/') + goto yy33; + goto yy101; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy101; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy101; + goto yy33; + } + } + yy99: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy101; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy101; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy101; + goto yy33; + } + } + yy100: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy102; + if (yych <= '/') + goto yy33; + goto yy103; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy103; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy103; + goto yy33; + } + } + yy101: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy103; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy103; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy103; + goto yy33; + } + } + yy102: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy104; + if (yych <= '/') + goto yy33; + goto yy105; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy105; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy105; + goto yy33; + } + } + yy103: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy105; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy105; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy105; + goto yy33; + } + } + yy104: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy106; + if (yych <= '/') + goto yy33; + goto yy107; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy107; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy107; + goto yy33; + } + } + yy105: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy107; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy107; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy107; + goto yy33; + } + } + yy106: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy108; + if (yych <= '/') + goto yy33; + goto yy109; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy109; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy109; + goto yy33; + } + } + yy107: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy109; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy109; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy109; + goto yy33; + } + } + yy108: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy110; + if (yych <= '/') + goto yy33; + goto yy111; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy111; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy111; + goto yy33; + } + } + yy109: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy111; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy111; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy111; + goto yy33; + } + } + yy110: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy112; + if (yych <= '/') + goto yy33; + goto yy113; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy113; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy113; + goto yy33; + } + } + yy111: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy113; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy113; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy113; + goto yy33; + } + } + yy112: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy114; + if (yych <= '/') + goto yy33; + goto yy115; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy115; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy115; + goto yy33; + } + } + yy113: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy115; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy115; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy115; + goto yy33; + } + } + yy114: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy116; + if (yych <= '/') + goto yy33; + goto yy117; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy117; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy117; + goto yy33; + } + } + yy115: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy117; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy117; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy117; + goto yy33; + } + } + yy116: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy118; + if (yych <= '/') + goto yy33; + goto yy119; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy119; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy119; + goto yy33; + } + } + yy117: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy119; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy119; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy119; + goto yy33; + } + } + yy118: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy120; + if (yych <= '/') + goto yy33; + goto yy121; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy121; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy121; + goto yy33; + } + } + yy119: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy121; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy121; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy121; + goto yy33; + } + } + yy120: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy122; + if (yych <= '/') + goto yy33; + goto yy123; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy123; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy123; + goto yy33; + } + } + yy121: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy123; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy123; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy123; + goto yy33; + } + } + yy122: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy124; + if (yych <= '/') + goto yy33; + goto yy125; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy125; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy125; + goto yy33; + } + } + yy123: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy125; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy125; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy125; + goto yy33; + } + } + yy124: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy126; + if (yych <= '/') + goto yy33; + goto yy127; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy127; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy127; + goto yy33; + } + } + yy125: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy127; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy127; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy127; + goto yy33; + } + } + yy126: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy128; + if (yych <= '/') + goto yy33; + goto yy129; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy129; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy129; + goto yy33; + } + } + yy127: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy129; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy129; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy129; + goto yy33; + } + } + yy128: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy130; + if (yych <= '/') + goto yy33; + goto yy131; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy131; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy131; + goto yy33; + } + } + yy129: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy131; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy131; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy131; + goto yy33; + } + } + yy130: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy132; + if (yych <= '/') + goto yy33; + goto yy133; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy133; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy133; + goto yy33; + } + } + yy131: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy133; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy133; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy133; + goto yy33; + } + } + yy132: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy134; + if (yych <= '/') + goto yy33; + goto yy135; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy135; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy135; + goto yy33; + } + } + yy133: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy135; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy135; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy135; + goto yy33; + } + } + yy134: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy136; + if (yych <= '/') + goto yy33; + goto yy137; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy137; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy137; + goto yy33; + } + } + yy135: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy137; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy137; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy137; + goto yy33; + } + } + yy136: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy138; + if (yych <= '/') + goto yy33; + goto yy139; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy139; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy139; + goto yy33; + } + } + yy137: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy139; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy139; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy139; + goto yy33; + } + } + yy138: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy140; + if (yych <= '/') + goto yy33; + goto yy141; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy141; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy141; + goto yy33; + } + } + yy139: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy141; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy141; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy141; + goto yy33; + } + } + yy140: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy142; + if (yych <= '/') + goto yy33; + goto yy143; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy143; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy143; + goto yy33; + } + } + yy141: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy143; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy143; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy143; + goto yy33; + } + } + yy142: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy144; + if (yych <= '/') + goto yy33; + goto yy145; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy145; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy145; + goto yy33; + } + } + yy143: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy145; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy145; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy145; + goto yy33; + } + } + yy144: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy146; + if (yych <= '/') + goto yy33; + goto yy147; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy147; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy147; + goto yy33; + } + } + yy145: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy147; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy147; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy147; + goto yy33; + } + } + yy146: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy148; + if (yych <= '/') + goto yy33; + goto yy149; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy149; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy149; + goto yy33; + } + } + yy147: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy149; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy149; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy149; + goto yy33; + } + } + yy148: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy150; + if (yych <= '/') + goto yy33; + goto yy151; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy151; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy151; + goto yy33; + } + } + yy149: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy151; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy151; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy151; + goto yy33; + } + } + yy150: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy152; + if (yych <= '/') + goto yy33; + goto yy153; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy153; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy153; + goto yy33; + } + } + yy151: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy153; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy153; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy153; + goto yy33; + } + } + yy152: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy154; + if (yych <= '/') + goto yy33; + goto yy155; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy155; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy155; + goto yy33; + } + } + yy153: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy155; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy155; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy155; + goto yy33; + } + } + yy154: + yych = *++p; + if (yych <= '9') { + if (yych == '-') + goto yy156; + if (yych <= '/') + goto yy33; + goto yy157; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy33; + goto yy157; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy157; + goto yy33; + } + } + yy155: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= ',') + goto yy33; + if (yych >= '.') + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy157; + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + goto yy157; + } else { + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy157; + goto yy33; + } + } + yy156: + yych = *++p; + if (yych <= '@') { + if (yych <= '/') + goto yy33; + if (yych <= '9') + goto yy158; + goto yy33; + } else { + if (yych <= 'Z') + goto yy158; + if (yych <= '`') + goto yy33; + if (yych <= 'z') + goto yy158; + goto yy33; + } + yy157: + yych = *++p; + if (yych <= '=') { + if (yych <= '.') { + if (yych <= '-') + goto yy33; + goto yy34; + } else { + if (yych <= '/') + goto yy33; + if (yych >= ':') + goto yy33; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy37; + if (yych <= '@') + goto yy33; + } else { + if (yych <= '`') + goto yy33; + if (yych >= '{') + goto yy33; + } + } + yy158: + yych = *++p; + if (yych == '.') + goto yy34; + if (yych == '>') + goto yy37; + goto yy33; + } } -// Try to match email autolink after first <, returning num of chars matched. -bufsize_t _scan_autolink_email(const unsigned char *p) -{ +// Try to match an HTML tag after first <, returning num of chars matched. +bufsize_t _scan_html_tag(const unsigned char *p) { const unsigned char *marker = NULL; const unsigned char *start = p; -{ - unsigned char yych; - static const unsigned char yybm[] = { - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 128, 0, 128, 128, 128, 128, 128, - 0, 0, 128, 128, 0, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 0, 0, 0, 128, 0, 128, - 0, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 0, 0, 0, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - }; - yych = *p; - if (yych <= '9') { - if (yych <= '\'') { - if (yych == '!') goto yy91; - if (yych >= '#') goto yy91; - } else { - if (yych <= ')') goto yy89; - if (yych != ',') goto yy91; - } - } else { - if (yych <= '?') { - if (yych == '=') goto yy91; - if (yych >= '?') goto yy91; - } else { - if (yych <= 'Z') { - if (yych >= 'A') goto yy91; - } else { - if (yych <= ']') goto yy89; - if (yych <= '~') goto yy91; - } - } - } -yy89: - ++p; -yy90: - { return 0; } -yy91: - yych = *(marker = ++p); - if (yych <= ',') { - if (yych <= '"') { - if (yych == '!') goto yy93; - goto yy90; - } else { - if (yych <= '\'') goto yy93; - if (yych <= ')') goto yy90; - if (yych <= '+') goto yy93; - goto yy90; - } - } else { - if (yych <= '>') { - if (yych <= '9') goto yy93; - if (yych == '=') goto yy93; - goto yy90; - } else { - if (yych <= 'Z') goto yy93; - if (yych <= ']') goto yy90; - if (yych <= '~') goto yy93; - goto yy90; - } - } -yy92: - yych = *++p; -yy93: - if (yybm[0+yych] & 128) { - goto yy92; - } - if (yych <= '>') goto yy94; - if (yych <= '@') goto yy95; -yy94: - p = marker; - goto yy90; -yy95: - yych = *++p; - if (yych <= '@') { - if (yych <= '/') goto yy94; - if (yych >= ':') goto yy94; - } else { - if (yych <= 'Z') goto yy96; - if (yych <= '`') goto yy94; - if (yych >= '{') goto yy94; - } -yy96: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy98; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy98; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy98; - goto yy94; - } - } - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy101; - if (yych <= '/') goto yy94; - goto yy102; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy102; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy102; - goto yy94; - } - } -yy98: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych <= '-') goto yy101; - goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy102; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy102; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy102; - goto yy94; - } - } -yy99: - ++p; - { return (bufsize_t)(p - start); } -yy101: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy103; - if (yych <= '/') goto yy94; - goto yy104; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy104; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy104; - goto yy94; - } - } -yy102: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy104; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy104; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy104; - goto yy94; - } - } -yy103: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy105; - if (yych <= '/') goto yy94; - goto yy106; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy106; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy106; - goto yy94; - } - } -yy104: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy106; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy106; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy106; - goto yy94; - } - } -yy105: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy107; - if (yych <= '/') goto yy94; - goto yy108; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy108; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy108; - goto yy94; - } - } -yy106: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy108; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy108; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy108; - goto yy94; - } - } -yy107: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy109; - if (yych <= '/') goto yy94; - goto yy110; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy110; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy110; - goto yy94; - } - } -yy108: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy110; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy110; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy110; - goto yy94; - } - } -yy109: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy111; - if (yych <= '/') goto yy94; - goto yy112; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy112; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy112; - goto yy94; - } - } -yy110: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy112; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy112; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy112; - goto yy94; - } - } -yy111: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy113; - if (yych <= '/') goto yy94; - goto yy114; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy114; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy114; - goto yy94; - } - } -yy112: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy114; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy114; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy114; - goto yy94; - } - } -yy113: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy115; - if (yych <= '/') goto yy94; - goto yy116; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy116; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy116; - goto yy94; - } - } -yy114: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy116; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy116; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy116; - goto yy94; - } - } -yy115: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy117; - if (yych <= '/') goto yy94; - goto yy118; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy118; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy118; - goto yy94; - } - } -yy116: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy118; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy118; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy118; - goto yy94; - } - } -yy117: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy119; - if (yych <= '/') goto yy94; - goto yy120; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy120; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy120; - goto yy94; - } - } -yy118: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy120; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy120; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy120; - goto yy94; - } - } -yy119: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy121; - if (yych <= '/') goto yy94; - goto yy122; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy122; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy122; - goto yy94; - } - } -yy120: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy122; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy122; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy122; - goto yy94; - } - } -yy121: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy123; - if (yych <= '/') goto yy94; - goto yy124; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy124; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy124; - goto yy94; - } - } -yy122: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy124; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy124; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy124; - goto yy94; - } - } -yy123: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy125; - if (yych <= '/') goto yy94; - goto yy126; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy126; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy126; - goto yy94; - } - } -yy124: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy126; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy126; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy126; - goto yy94; - } - } -yy125: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy127; - if (yych <= '/') goto yy94; - goto yy128; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy128; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy128; - goto yy94; - } - } -yy126: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy128; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy128; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy128; - goto yy94; - } - } -yy127: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy129; - if (yych <= '/') goto yy94; - goto yy130; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy130; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy130; - goto yy94; - } - } -yy128: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy130; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy130; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy130; - goto yy94; - } - } -yy129: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy131; - if (yych <= '/') goto yy94; - goto yy132; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy132; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy132; - goto yy94; - } - } -yy130: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy132; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy132; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy132; - goto yy94; - } - } -yy131: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy133; - if (yych <= '/') goto yy94; - goto yy134; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy134; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy134; - goto yy94; - } - } -yy132: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy134; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy134; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy134; - goto yy94; - } - } -yy133: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy135; - if (yych <= '/') goto yy94; - goto yy136; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy136; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy136; - goto yy94; - } - } -yy134: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy136; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy136; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy136; - goto yy94; - } - } -yy135: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy137; - if (yych <= '/') goto yy94; - goto yy138; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy138; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy138; - goto yy94; - } - } -yy136: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy138; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy138; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy138; - goto yy94; - } - } -yy137: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy139; - if (yych <= '/') goto yy94; - goto yy140; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy140; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy140; - goto yy94; - } - } -yy138: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy140; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy140; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy140; - goto yy94; - } - } -yy139: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy141; - if (yych <= '/') goto yy94; - goto yy142; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy142; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy142; - goto yy94; - } - } -yy140: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy142; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy142; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy142; - goto yy94; - } - } -yy141: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy143; - if (yych <= '/') goto yy94; - goto yy144; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy144; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy144; - goto yy94; - } - } -yy142: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy144; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy144; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy144; - goto yy94; - } - } -yy143: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy145; - if (yych <= '/') goto yy94; - goto yy146; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy146; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy146; - goto yy94; - } - } -yy144: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy146; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy146; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy146; - goto yy94; - } - } -yy145: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy147; - if (yych <= '/') goto yy94; - goto yy148; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy148; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy148; - goto yy94; - } - } -yy146: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy148; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy148; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy148; - goto yy94; - } - } -yy147: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy149; - if (yych <= '/') goto yy94; - goto yy150; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy150; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy150; - goto yy94; - } - } -yy148: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy150; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy150; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy150; - goto yy94; - } - } -yy149: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy151; - if (yych <= '/') goto yy94; - goto yy152; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy152; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy152; - goto yy94; - } - } -yy150: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy152; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy152; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy152; - goto yy94; - } - } -yy151: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy153; - if (yych <= '/') goto yy94; - goto yy154; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy154; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy154; - goto yy94; - } - } -yy152: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy154; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy154; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy154; - goto yy94; - } - } -yy153: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy155; - if (yych <= '/') goto yy94; - goto yy156; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy156; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy156; - goto yy94; - } - } -yy154: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy156; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy156; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy156; - goto yy94; - } - } -yy155: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy157; - if (yych <= '/') goto yy94; - goto yy158; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy158; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy158; - goto yy94; - } - } -yy156: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy158; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy158; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy158; - goto yy94; - } - } -yy157: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy159; - if (yych <= '/') goto yy94; - goto yy160; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy160; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy160; - goto yy94; - } - } -yy158: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy160; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy160; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy160; - goto yy94; - } - } -yy159: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy161; - if (yych <= '/') goto yy94; - goto yy162; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy162; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy162; - goto yy94; - } - } -yy160: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy162; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy162; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy162; - goto yy94; - } - } -yy161: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy163; - if (yych <= '/') goto yy94; - goto yy164; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy164; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy164; - goto yy94; - } - } -yy162: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy164; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy164; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy164; - goto yy94; - } - } -yy163: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy165; - if (yych <= '/') goto yy94; - goto yy166; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy166; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy166; - goto yy94; - } - } -yy164: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy166; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy166; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy166; - goto yy94; - } - } -yy165: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy167; - if (yych <= '/') goto yy94; - goto yy168; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy168; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy168; - goto yy94; - } - } -yy166: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy168; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy168; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy168; - goto yy94; - } - } -yy167: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy169; - if (yych <= '/') goto yy94; - goto yy170; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy170; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy170; - goto yy94; - } - } -yy168: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy170; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy170; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy170; - goto yy94; - } - } -yy169: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy171; - if (yych <= '/') goto yy94; - goto yy172; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy172; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy172; - goto yy94; - } - } -yy170: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy172; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy172; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy172; - goto yy94; - } - } -yy171: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy173; - if (yych <= '/') goto yy94; - goto yy174; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy174; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy174; - goto yy94; - } - } -yy172: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy174; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy174; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy174; - goto yy94; - } - } -yy173: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy175; - if (yych <= '/') goto yy94; - goto yy176; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy176; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy176; - goto yy94; - } - } -yy174: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy176; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy176; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy176; - goto yy94; - } - } -yy175: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy177; - if (yych <= '/') goto yy94; - goto yy178; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy178; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy178; - goto yy94; - } - } -yy176: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy178; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy178; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy178; - goto yy94; - } - } -yy177: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy179; - if (yych <= '/') goto yy94; - goto yy180; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy180; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy180; - goto yy94; - } - } -yy178: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy180; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy180; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy180; - goto yy94; - } - } -yy179: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy181; - if (yych <= '/') goto yy94; - goto yy182; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy182; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy182; - goto yy94; - } - } -yy180: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy182; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy182; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy182; - goto yy94; - } - } -yy181: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy183; - if (yych <= '/') goto yy94; - goto yy184; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy184; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy184; - goto yy94; - } - } -yy182: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy184; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy184; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy184; - goto yy94; - } - } -yy183: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy185; - if (yych <= '/') goto yy94; - goto yy186; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy186; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy186; - goto yy94; - } - } -yy184: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy186; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy186; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy186; - goto yy94; - } - } -yy185: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy187; - if (yych <= '/') goto yy94; - goto yy188; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy188; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy188; - goto yy94; - } - } -yy186: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy188; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy188; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy188; - goto yy94; - } - } -yy187: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy189; - if (yych <= '/') goto yy94; - goto yy190; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy190; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy190; - goto yy94; - } - } -yy188: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy190; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy190; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy190; - goto yy94; - } - } -yy189: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy191; - if (yych <= '/') goto yy94; - goto yy192; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy192; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy192; - goto yy94; - } - } -yy190: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy192; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy192; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy192; - goto yy94; - } - } -yy191: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy193; - if (yych <= '/') goto yy94; - goto yy194; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy194; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy194; - goto yy94; - } - } -yy192: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy194; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy194; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy194; - goto yy94; - } - } -yy193: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy195; - if (yych <= '/') goto yy94; - goto yy196; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy196; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy196; - goto yy94; - } - } -yy194: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy196; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy196; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy196; - goto yy94; - } - } -yy195: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy197; - if (yych <= '/') goto yy94; - goto yy198; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy198; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy198; - goto yy94; - } - } -yy196: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy198; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy198; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy198; - goto yy94; - } - } -yy197: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy199; - if (yych <= '/') goto yy94; - goto yy200; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy200; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy200; - goto yy94; - } - } -yy198: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy200; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy200; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy200; - goto yy94; - } - } -yy199: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy201; - if (yych <= '/') goto yy94; - goto yy202; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy202; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy202; - goto yy94; - } - } -yy200: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy202; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy202; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy202; - goto yy94; - } - } -yy201: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy203; - if (yych <= '/') goto yy94; - goto yy204; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy204; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy204; - goto yy94; - } - } -yy202: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy204; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy204; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy204; - goto yy94; - } - } -yy203: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy205; - if (yych <= '/') goto yy94; - goto yy206; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy206; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy206; - goto yy94; - } - } -yy204: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy206; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy206; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy206; - goto yy94; - } - } -yy205: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy207; - if (yych <= '/') goto yy94; - goto yy208; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy208; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy208; - goto yy94; - } - } -yy206: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy208; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy208; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy208; - goto yy94; - } - } -yy207: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy209; - if (yych <= '/') goto yy94; - goto yy210; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy210; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy210; - goto yy94; - } - } -yy208: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy210; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy210; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy210; - goto yy94; - } - } -yy209: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy211; - if (yych <= '/') goto yy94; - goto yy212; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy212; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy212; - goto yy94; - } - } -yy210: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy212; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy212; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy212; - goto yy94; - } - } -yy211: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy213; - if (yych <= '/') goto yy94; - goto yy214; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy214; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy214; - goto yy94; - } - } -yy212: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy214; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy214; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy214; - goto yy94; - } - } -yy213: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy215; - if (yych <= '/') goto yy94; - goto yy216; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy216; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy216; - goto yy94; - } - } -yy214: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy216; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy216; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy216; - goto yy94; - } - } -yy215: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy217; - if (yych <= '/') goto yy94; - goto yy218; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy218; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy218; - goto yy94; - } - } -yy216: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy218; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy218; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy218; - goto yy94; - } - } -yy217: - yych = *++p; - if (yych <= '9') { - if (yych == '-') goto yy219; - if (yych <= '/') goto yy94; - goto yy220; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy94; - goto yy220; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy220; - goto yy94; - } - } -yy218: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= ',') goto yy94; - if (yych >= '.') goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy220; - goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - goto yy220; - } else { - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy220; - goto yy94; - } - } -yy219: - yych = *++p; - if (yych <= '@') { - if (yych <= '/') goto yy94; - if (yych <= '9') goto yy221; - goto yy94; - } else { - if (yych <= 'Z') goto yy221; - if (yych <= '`') goto yy94; - if (yych <= 'z') goto yy221; - goto yy94; - } -yy220: - yych = *++p; - if (yych <= '=') { - if (yych <= '.') { - if (yych <= '-') goto yy94; - goto yy95; - } else { - if (yych <= '/') goto yy94; - if (yych >= ':') goto yy94; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy99; - if (yych <= '@') goto yy94; - } else { - if (yych <= '`') goto yy94; - if (yych >= '{') goto yy94; - } - } -yy221: - yych = *++p; - if (yych == '.') goto yy95; - if (yych == '>') goto yy99; - goto yy94; + { + unsigned char yych; + static const unsigned char yybm[] = { + 0, 224, 224, 224, 224, 224, 224, 224, 224, 200, 200, 200, 200, 200, + 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, + 224, 224, 224, 224, 200, 224, 128, 224, 224, 224, 224, 64, 224, 224, + 224, 224, 224, 244, 240, 224, 244, 244, 244, 244, 244, 244, 244, 244, + 244, 244, 240, 224, 192, 192, 192, 224, 224, 244, 244, 244, 244, 244, + 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, + 244, 244, 244, 244, 244, 244, 244, 224, 224, 224, 224, 240, 192, 244, + 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, + 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, 224, 224, 224, + 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + }; + yych = *p; + if (yych <= '@') { + if (yych == '/') + goto yy162; + } else { + if (yych <= 'Z') + goto yy163; + if (yych <= '`') + goto yy160; + if (yych <= 'z') + goto yy163; + } + yy160: + ++p; + yy161 : { return 0; } + yy162: + yych = *(marker = ++p); + if (yych <= '@') + goto yy161; + if (yych <= 'Z') + goto yy164; + if (yych <= '`') + goto yy161; + if (yych <= 'z') + goto yy164; + goto yy161; + yy163: + yych = *(marker = ++p); + if (yych <= '.') { + if (yych <= 0x1F) { + if (yych <= 0x08) + goto yy161; + if (yych <= '\r') + goto yy168; + goto yy161; + } else { + if (yych <= ' ') + goto yy168; + if (yych == '-') + goto yy168; + goto yy161; + } + } else { + if (yych <= '@') { + if (yych <= '9') + goto yy168; + if (yych == '>') + goto yy168; + goto yy161; + } else { + if (yych <= 'Z') + goto yy168; + if (yych <= '`') + goto yy161; + if (yych <= 'z') + goto yy168; + goto yy161; + } + } + yy164: + yych = *++p; + if (yybm[0 + yych] & 4) { + goto yy164; + } + if (yych <= 0x1F) { + if (yych <= 0x08) + goto yy165; + if (yych <= '\r') + goto yy171; + } else { + if (yych <= ' ') + goto yy171; + if (yych == '>') + goto yy170; + } + yy165: + p = marker; + goto yy161; + yy166: + yych = *++p; + if (yybm[0 + yych] & 8) { + goto yy166; + } + if (yych <= '>') { + if (yych <= '9') { + if (yych == '/') + goto yy169; + goto yy165; + } else { + if (yych <= ':') + goto yy172; + if (yych <= '=') + goto yy165; + goto yy170; + } + } else { + if (yych <= '^') { + if (yych <= '@') + goto yy165; + if (yych <= 'Z') + goto yy172; + goto yy165; + } else { + if (yych == '`') + goto yy165; + if (yych <= 'z') + goto yy172; + goto yy165; + } + } + yy167: + yych = *++p; + yy168: + if (yybm[0 + yych] & 8) { + goto yy166; + } + if (yych <= '=') { + if (yych <= '.') { + if (yych == '-') + goto yy167; + goto yy165; + } else { + if (yych <= '/') + goto yy169; + if (yych <= '9') + goto yy167; + goto yy165; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy170; + if (yych <= '@') + goto yy165; + goto yy167; + } else { + if (yych <= '`') + goto yy165; + if (yych <= 'z') + goto yy167; + goto yy165; + } + } + yy169: + yych = *++p; + if (yych != '>') + goto yy165; + yy170: + ++p; + { return (bufsize_t)(p - start); } + yy171: + yych = *++p; + if (yych <= 0x1F) { + if (yych <= 0x08) + goto yy165; + if (yych <= '\r') + goto yy171; + goto yy165; + } else { + if (yych <= ' ') + goto yy171; + if (yych == '>') + goto yy170; + goto yy165; + } + yy172: + yych = *++p; + if (yybm[0 + yych] & 16) { + goto yy172; + } + if (yych <= ',') { + if (yych <= '\r') { + if (yych <= 0x08) + goto yy165; + } else { + if (yych != ' ') + goto yy165; + } + } else { + if (yych <= '<') { + if (yych <= '/') + goto yy169; + goto yy165; + } else { + if (yych <= '=') + goto yy174; + if (yych <= '>') + goto yy170; + goto yy165; + } + } + yy173: + yych = *++p; + if (yych <= '<') { + if (yych <= ' ') { + if (yych <= 0x08) + goto yy165; + if (yych <= '\r') + goto yy173; + if (yych <= 0x1F) + goto yy165; + goto yy173; + } else { + if (yych <= '/') { + if (yych <= '.') + goto yy165; + goto yy169; + } else { + if (yych == ':') + goto yy172; + goto yy165; + } + } + } else { + if (yych <= 'Z') { + if (yych <= '=') + goto yy174; + if (yych <= '>') + goto yy170; + if (yych <= '@') + goto yy165; + goto yy172; + } else { + if (yych <= '_') { + if (yych <= '^') + goto yy165; + goto yy172; + } else { + if (yych <= '`') + goto yy165; + if (yych <= 'z') + goto yy172; + goto yy165; + } + } + } + yy174: + yych = *++p; + if (yybm[0 + yych] & 32) { + goto yy175; + } + if (yych <= 0xE0) { + if (yych <= '"') { + if (yych <= 0x00) + goto yy165; + if (yych <= ' ') + goto yy174; + goto yy176; + } else { + if (yych <= '\'') + goto yy177; + if (yych <= 0xC1) + goto yy165; + if (yych <= 0xDF) + goto yy178; + goto yy179; + } + } else { + if (yych <= 0xEF) { + if (yych == 0xED) + goto yy181; + goto yy180; + } else { + if (yych <= 0xF0) + goto yy182; + if (yych <= 0xF3) + goto yy183; + if (yych <= 0xF4) + goto yy184; + goto yy165; + } + } + yy175: + yych = *++p; + if (yybm[0 + yych] & 32) { + goto yy175; + } + if (yych <= 0xE0) { + if (yych <= '=') { + if (yych <= 0x00) + goto yy165; + if (yych <= ' ') + goto yy166; + goto yy165; + } else { + if (yych <= '>') + goto yy170; + if (yych <= 0xC1) + goto yy165; + if (yych <= 0xDF) + goto yy178; + goto yy179; + } + } else { + if (yych <= 0xEF) { + if (yych == 0xED) + goto yy181; + goto yy180; + } else { + if (yych <= 0xF0) + goto yy182; + if (yych <= 0xF3) + goto yy183; + if (yych <= 0xF4) + goto yy184; + goto yy165; + } + } + yy176: + yych = *++p; + if (yybm[0 + yych] & 64) { + goto yy176; + } + if (yych <= 0xEC) { + if (yych <= 0xC1) { + if (yych <= 0x00) + goto yy165; + if (yych <= '"') + goto yy185; + goto yy165; + } else { + if (yych <= 0xDF) + goto yy186; + if (yych <= 0xE0) + goto yy187; + goto yy188; + } + } else { + if (yych <= 0xF0) { + if (yych <= 0xED) + goto yy189; + if (yych <= 0xEF) + goto yy188; + goto yy190; + } else { + if (yych <= 0xF3) + goto yy191; + if (yych <= 0xF4) + goto yy192; + goto yy165; + } + } + yy177: + yych = *++p; + if (yybm[0 + yych] & 128) { + goto yy177; + } + if (yych <= 0xEC) { + if (yych <= 0xC1) { + if (yych <= 0x00) + goto yy165; + if (yych <= '\'') + goto yy185; + goto yy165; + } else { + if (yych <= 0xDF) + goto yy193; + if (yych <= 0xE0) + goto yy194; + goto yy195; + } + } else { + if (yych <= 0xF0) { + if (yych <= 0xED) + goto yy196; + if (yych <= 0xEF) + goto yy195; + goto yy197; + } else { + if (yych <= 0xF3) + goto yy198; + if (yych <= 0xF4) + goto yy199; + goto yy165; + } + } + yy178: + yych = *++p; + if (yych <= 0x7F) + goto yy165; + if (yych <= 0xBF) + goto yy175; + goto yy165; + yy179: + yych = *++p; + if (yych <= 0x9F) + goto yy165; + if (yych <= 0xBF) + goto yy178; + goto yy165; + yy180: + yych = *++p; + if (yych <= 0x7F) + goto yy165; + if (yych <= 0xBF) + goto yy178; + goto yy165; + yy181: + yych = *++p; + if (yych <= 0x7F) + goto yy165; + if (yych <= 0x9F) + goto yy178; + goto yy165; + yy182: + yych = *++p; + if (yych <= 0x8F) + goto yy165; + if (yych <= 0xBF) + goto yy180; + goto yy165; + yy183: + yych = *++p; + if (yych <= 0x7F) + goto yy165; + if (yych <= 0xBF) + goto yy180; + goto yy165; + yy184: + yych = *++p; + if (yych <= 0x7F) + goto yy165; + if (yych <= 0x8F) + goto yy180; + goto yy165; + yy185: + yych = *++p; + if (yybm[0 + yych] & 8) { + goto yy166; + } + if (yych == '/') + goto yy169; + if (yych == '>') + goto yy170; + goto yy165; + yy186: + yych = *++p; + if (yych <= 0x7F) + goto yy165; + if (yych <= 0xBF) + goto yy176; + goto yy165; + yy187: + yych = *++p; + if (yych <= 0x9F) + goto yy165; + if (yych <= 0xBF) + goto yy186; + goto yy165; + yy188: + yych = *++p; + if (yych <= 0x7F) + goto yy165; + if (yych <= 0xBF) + goto yy186; + goto yy165; + yy189: + yych = *++p; + if (yych <= 0x7F) + goto yy165; + if (yych <= 0x9F) + goto yy186; + goto yy165; + yy190: + yych = *++p; + if (yych <= 0x8F) + goto yy165; + if (yych <= 0xBF) + goto yy188; + goto yy165; + yy191: + yych = *++p; + if (yych <= 0x7F) + goto yy165; + if (yych <= 0xBF) + goto yy188; + goto yy165; + yy192: + yych = *++p; + if (yych <= 0x7F) + goto yy165; + if (yych <= 0x8F) + goto yy188; + goto yy165; + yy193: + yych = *++p; + if (yych <= 0x7F) + goto yy165; + if (yych <= 0xBF) + goto yy177; + goto yy165; + yy194: + yych = *++p; + if (yych <= 0x9F) + goto yy165; + if (yych <= 0xBF) + goto yy193; + goto yy165; + yy195: + yych = *++p; + if (yych <= 0x7F) + goto yy165; + if (yych <= 0xBF) + goto yy193; + goto yy165; + yy196: + yych = *++p; + if (yych <= 0x7F) + goto yy165; + if (yych <= 0x9F) + goto yy193; + goto yy165; + yy197: + yych = *++p; + if (yych <= 0x8F) + goto yy165; + if (yych <= 0xBF) + goto yy195; + goto yy165; + yy198: + yych = *++p; + if (yych <= 0x7F) + goto yy165; + if (yych <= 0xBF) + goto yy195; + goto yy165; + yy199: + yych = *++p; + if (yych <= 0x7F) + goto yy165; + if (yych <= 0x8F) + goto yy195; + goto yy165; + } } +// Try to (liberally) match an HTML tag after first <, returning num of chars +// matched. +bufsize_t _scan_liberal_html_tag(const unsigned char *p) { + const unsigned char *marker = NULL; + const unsigned char *start = p; + + { + unsigned char yych; + unsigned int yyaccept = 0; + static const unsigned char yybm[] = { + 0, 64, 64, 64, 64, 64, 64, 64, 64, 64, 0, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 128, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + }; + yych = *p; + if (yych <= 0xE0) { + if (yych <= '\n') { + if (yych <= 0x00) + goto yy201; + if (yych <= '\t') + goto yy203; + } else { + if (yych <= 0x7F) + goto yy203; + if (yych <= 0xC1) + goto yy201; + if (yych <= 0xDF) + goto yy204; + goto yy205; + } + } else { + if (yych <= 0xEF) { + if (yych == 0xED) + goto yy207; + goto yy206; + } else { + if (yych <= 0xF0) + goto yy208; + if (yych <= 0xF3) + goto yy209; + if (yych <= 0xF4) + goto yy210; + } + } + yy201: + ++p; + yy202 : { return 0; } + yy203: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= '\n') { + if (yych <= 0x00) + goto yy202; + if (yych <= '\t') + goto yy212; + goto yy202; + } else { + if (yych <= 0x7F) + goto yy212; + if (yych <= 0xC1) + goto yy202; + if (yych <= 0xF4) + goto yy212; + goto yy202; + } + yy204: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= 0x7F) + goto yy202; + if (yych <= 0xBF) + goto yy211; + goto yy202; + yy205: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= 0x9F) + goto yy202; + if (yych <= 0xBF) + goto yy216; + goto yy202; + yy206: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= 0x7F) + goto yy202; + if (yych <= 0xBF) + goto yy216; + goto yy202; + yy207: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= 0x7F) + goto yy202; + if (yych <= 0x9F) + goto yy216; + goto yy202; + yy208: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= 0x8F) + goto yy202; + if (yych <= 0xBF) + goto yy218; + goto yy202; + yy209: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= 0x7F) + goto yy202; + if (yych <= 0xBF) + goto yy218; + goto yy202; + yy210: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= 0x7F) + goto yy202; + if (yych <= 0x8F) + goto yy218; + goto yy202; + yy211: + yych = *++p; + yy212: + if (yybm[0 + yych] & 64) { + goto yy211; + } + if (yych <= 0xEC) { + if (yych <= 0xC1) { + if (yych <= '\n') + goto yy213; + if (yych <= '>') + goto yy214; + } else { + if (yych <= 0xDF) + goto yy216; + if (yych <= 0xE0) + goto yy217; + goto yy218; + } + } else { + if (yych <= 0xF0) { + if (yych <= 0xED) + goto yy219; + if (yych <= 0xEF) + goto yy218; + goto yy220; + } else { + if (yych <= 0xF3) + goto yy221; + if (yych <= 0xF4) + goto yy222; + } + } + yy213: + p = marker; + if (yyaccept == 0) { + goto yy202; + } else { + goto yy215; + } + yy214: + yyaccept = 1; + yych = *(marker = ++p); + if (yybm[0 + yych] & 64) { + goto yy211; + } + if (yych <= 0xEC) { + if (yych <= 0xC1) { + if (yych <= '\n') + goto yy215; + if (yych <= '>') + goto yy214; + } else { + if (yych <= 0xDF) + goto yy216; + if (yych <= 0xE0) + goto yy217; + goto yy218; + } + } else { + if (yych <= 0xF0) { + if (yych <= 0xED) + goto yy219; + if (yych <= 0xEF) + goto yy218; + goto yy220; + } else { + if (yych <= 0xF3) + goto yy221; + if (yych <= 0xF4) + goto yy222; + } + } + yy215 : { return (bufsize_t)(p - start); } + yy216: + yych = *++p; + if (yych <= 0x7F) + goto yy213; + if (yych <= 0xBF) + goto yy211; + goto yy213; + yy217: + yych = *++p; + if (yych <= 0x9F) + goto yy213; + if (yych <= 0xBF) + goto yy216; + goto yy213; + yy218: + yych = *++p; + if (yych <= 0x7F) + goto yy213; + if (yych <= 0xBF) + goto yy216; + goto yy213; + yy219: + yych = *++p; + if (yych <= 0x7F) + goto yy213; + if (yych <= 0x9F) + goto yy216; + goto yy213; + yy220: + yych = *++p; + if (yych <= 0x8F) + goto yy213; + if (yych <= 0xBF) + goto yy218; + goto yy213; + yy221: + yych = *++p; + if (yych <= 0x7F) + goto yy213; + if (yych <= 0xBF) + goto yy218; + goto yy213; + yy222: + yych = *++p; + if (yych <= 0x7F) + goto yy213; + if (yych <= 0x8F) + goto yy218; + goto yy213; + } } -// Try to match an HTML tag after first <, returning num of chars matched. -bufsize_t _scan_html_tag(const unsigned char *p) -{ +bufsize_t _scan_html_comment(const unsigned char *p) { const unsigned char *marker = NULL; const unsigned char *start = p; -{ - unsigned char yych; - static const unsigned char yybm[] = { - /* table 1 .. 8: 0 */ - 0, 250, 250, 250, 250, 250, 250, 250, - 250, 235, 235, 235, 235, 235, 250, 250, - 250, 250, 250, 250, 250, 250, 250, 250, - 250, 250, 250, 250, 250, 250, 250, 250, - 235, 250, 202, 250, 250, 250, 250, 170, - 250, 250, 250, 250, 250, 246, 254, 250, - 254, 254, 254, 254, 254, 254, 254, 254, - 254, 254, 254, 250, 234, 234, 232, 250, - 250, 254, 254, 254, 254, 254, 254, 254, - 254, 254, 254, 254, 254, 254, 254, 254, - 254, 254, 254, 254, 254, 254, 254, 254, - 254, 254, 254, 250, 250, 122, 250, 254, - 234, 254, 254, 254, 254, 254, 254, 254, - 254, 254, 254, 254, 254, 254, 254, 254, - 254, 254, 254, 254, 254, 254, 254, 254, - 254, 254, 254, 250, 250, 250, 250, 250, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - /* table 9 .. 11: 256 */ - 0, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 192, 128, 128, - 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 128, 128, 128, 128, 128, 0, - 128, 224, 224, 224, 224, 224, 224, 224, - 224, 224, 224, 224, 224, 224, 224, 224, - 224, 224, 224, 224, 224, 224, 224, 224, - 224, 224, 224, 128, 128, 128, 128, 128, - 128, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 192, 128, 128, 128, 128, 128, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - }; - yych = *p; - if (yych <= '>') { - if (yych <= '!') { - if (yych >= '!') goto yy226; - } else { - if (yych == '/') goto yy227; - } - } else { - if (yych <= 'Z') { - if (yych <= '?') goto yy228; - if (yych >= 'A') goto yy229; - } else { - if (yych <= '`') goto yy224; - if (yych <= 'z') goto yy229; - } - } -yy224: - ++p; -yy225: - { return 0; } -yy226: - yych = *(marker = ++p); - if (yybm[256+yych] & 32) { - goto yy232; - } - if (yych == '-') goto yy230; - if (yych <= '@') goto yy225; - if (yych <= '[') goto yy234; - goto yy225; -yy227: - yych = *(marker = ++p); - if (yych <= '@') goto yy225; - if (yych <= 'Z') goto yy235; - if (yych <= '`') goto yy225; - if (yych <= 'z') goto yy235; - goto yy225; -yy228: - yych = *(marker = ++p); - if (yych <= 0x00) goto yy225; - if (yych <= 0x7F) goto yy238; - if (yych <= 0xC1) goto yy225; - if (yych <= 0xF4) goto yy238; - goto yy225; -yy229: - yych = *(marker = ++p); - if (yych <= '.') { - if (yych <= 0x1F) { - if (yych <= 0x08) goto yy225; - if (yych <= '\r') goto yy250; - goto yy225; - } else { - if (yych <= ' ') goto yy250; - if (yych == '-') goto yy250; - goto yy225; - } - } else { - if (yych <= '@') { - if (yych <= '9') goto yy250; - if (yych == '>') goto yy250; - goto yy225; - } else { - if (yych <= 'Z') goto yy250; - if (yych <= '`') goto yy225; - if (yych <= 'z') goto yy250; - goto yy225; - } - } -yy230: - yych = *++p; - if (yych == '-') goto yy254; -yy231: - p = marker; - goto yy225; -yy232: - yych = *++p; - if (yybm[256+yych] & 32) { - goto yy232; - } - if (yych <= 0x08) goto yy231; - if (yych <= '\r') goto yy255; - if (yych == ' ') goto yy255; - goto yy231; -yy234: - yych = *++p; - if (yych == 'C') goto yy257; - if (yych == 'c') goto yy257; - goto yy231; -yy235: - yych = *++p; - if (yybm[256+yych] & 64) { - goto yy235; - } - if (yych <= 0x1F) { - if (yych <= 0x08) goto yy231; - if (yych <= '\r') goto yy258; - goto yy231; - } else { - if (yych <= ' ') goto yy258; - if (yych == '>') goto yy252; - goto yy231; - } -yy237: - yych = *++p; -yy238: - if (yybm[256+yych] & 128) { - goto yy237; - } - if (yych <= 0xEC) { - if (yych <= 0xC1) { - if (yych <= 0x00) goto yy231; - if (yych >= '@') goto yy231; - } else { - if (yych <= 0xDF) goto yy240; - if (yych <= 0xE0) goto yy241; - goto yy242; - } - } else { - if (yych <= 0xF0) { - if (yych <= 0xED) goto yy243; - if (yych <= 0xEF) goto yy242; - goto yy244; - } else { - if (yych <= 0xF3) goto yy245; - if (yych <= 0xF4) goto yy246; - goto yy231; - } - } - yych = *++p; - if (yych <= 0xE0) { - if (yych <= '>') { - if (yych <= 0x00) goto yy231; - if (yych <= '=') goto yy237; - goto yy252; - } else { - if (yych <= 0x7F) goto yy237; - if (yych <= 0xC1) goto yy231; - if (yych >= 0xE0) goto yy241; - } - } else { - if (yych <= 0xEF) { - if (yych == 0xED) goto yy243; - goto yy242; - } else { - if (yych <= 0xF0) goto yy244; - if (yych <= 0xF3) goto yy245; - if (yych <= 0xF4) goto yy246; - goto yy231; - } - } -yy240: - yych = *++p; - if (yych <= 0x7F) goto yy231; - if (yych <= 0xBF) goto yy237; - goto yy231; -yy241: - yych = *++p; - if (yych <= 0x9F) goto yy231; - if (yych <= 0xBF) goto yy240; - goto yy231; -yy242: - yych = *++p; - if (yych <= 0x7F) goto yy231; - if (yych <= 0xBF) goto yy240; - goto yy231; -yy243: - yych = *++p; - if (yych <= 0x7F) goto yy231; - if (yych <= 0x9F) goto yy240; - goto yy231; -yy244: - yych = *++p; - if (yych <= 0x8F) goto yy231; - if (yych <= 0xBF) goto yy242; - goto yy231; -yy245: - yych = *++p; - if (yych <= 0x7F) goto yy231; - if (yych <= 0xBF) goto yy242; - goto yy231; -yy246: - yych = *++p; - if (yych <= 0x7F) goto yy231; - if (yych <= 0x8F) goto yy242; - goto yy231; -yy247: - yych = *++p; - if (yybm[0+yych] & 1) { - goto yy247; - } - if (yych <= '>') { - if (yych <= '9') { - if (yych == '/') goto yy251; - goto yy231; - } else { - if (yych <= ':') goto yy260; - if (yych <= '=') goto yy231; - goto yy252; - } - } else { - if (yych <= '^') { - if (yych <= '@') goto yy231; - if (yych <= 'Z') goto yy260; - goto yy231; - } else { - if (yych == '`') goto yy231; - if (yych <= 'z') goto yy260; - goto yy231; - } - } -yy249: - yych = *++p; -yy250: - if (yybm[0+yych] & 1) { - goto yy247; - } - if (yych <= '=') { - if (yych <= '.') { - if (yych == '-') goto yy249; - goto yy231; - } else { - if (yych <= '/') goto yy251; - if (yych <= '9') goto yy249; - goto yy231; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy252; - if (yych <= '@') goto yy231; - goto yy249; - } else { - if (yych <= '`') goto yy231; - if (yych <= 'z') goto yy249; - goto yy231; - } - } -yy251: - yych = *++p; - if (yych != '>') goto yy231; -yy252: - ++p; - { return (bufsize_t)(p - start); } -yy254: - yych = *++p; - if (yych == '-') goto yy264; - if (yych == '>') goto yy231; - goto yy263; -yy255: - yych = *++p; - if (yybm[0+yych] & 2) { - goto yy255; - } - if (yych <= 0xEC) { - if (yych <= 0xC1) { - if (yych <= 0x00) goto yy231; - if (yych <= '>') goto yy252; - goto yy231; - } else { - if (yych <= 0xDF) goto yy272; - if (yych <= 0xE0) goto yy273; - goto yy274; - } - } else { - if (yych <= 0xF0) { - if (yych <= 0xED) goto yy275; - if (yych <= 0xEF) goto yy274; - goto yy276; - } else { - if (yych <= 0xF3) goto yy277; - if (yych <= 0xF4) goto yy278; - goto yy231; - } - } -yy257: - yych = *++p; - if (yych == 'D') goto yy279; - if (yych == 'd') goto yy279; - goto yy231; -yy258: - yych = *++p; - if (yych <= 0x1F) { - if (yych <= 0x08) goto yy231; - if (yych <= '\r') goto yy258; - goto yy231; - } else { - if (yych <= ' ') goto yy258; - if (yych == '>') goto yy252; - goto yy231; - } -yy260: - yych = *++p; - if (yybm[0+yych] & 4) { - goto yy260; - } - if (yych <= ',') { - if (yych <= '\r') { - if (yych <= 0x08) goto yy231; - goto yy280; - } else { - if (yych == ' ') goto yy280; - goto yy231; - } - } else { - if (yych <= '<') { - if (yych <= '/') goto yy251; - goto yy231; - } else { - if (yych <= '=') goto yy282; - if (yych <= '>') goto yy252; - goto yy231; - } - } -yy262: - yych = *++p; -yy263: - if (yybm[0+yych] & 8) { - goto yy262; - } - if (yych <= 0xEC) { - if (yych <= 0xC1) { - if (yych <= 0x00) goto yy231; - if (yych <= '-') goto yy284; - goto yy231; - } else { - if (yych <= 0xDF) goto yy265; - if (yych <= 0xE0) goto yy266; - goto yy267; - } - } else { - if (yych <= 0xF0) { - if (yych <= 0xED) goto yy268; - if (yych <= 0xEF) goto yy267; - goto yy269; - } else { - if (yych <= 0xF3) goto yy270; - if (yych <= 0xF4) goto yy271; - goto yy231; - } - } -yy264: - yych = *++p; - if (yych == '-') goto yy251; - if (yych == '>') goto yy231; - goto yy263; -yy265: - yych = *++p; - if (yych <= 0x7F) goto yy231; - if (yych <= 0xBF) goto yy262; - goto yy231; -yy266: - yych = *++p; - if (yych <= 0x9F) goto yy231; - if (yych <= 0xBF) goto yy265; - goto yy231; -yy267: - yych = *++p; - if (yych <= 0x7F) goto yy231; - if (yych <= 0xBF) goto yy265; - goto yy231; -yy268: - yych = *++p; - if (yych <= 0x7F) goto yy231; - if (yych <= 0x9F) goto yy265; - goto yy231; -yy269: - yych = *++p; - if (yych <= 0x8F) goto yy231; - if (yych <= 0xBF) goto yy267; - goto yy231; -yy270: - yych = *++p; - if (yych <= 0x7F) goto yy231; - if (yych <= 0xBF) goto yy267; - goto yy231; -yy271: - yych = *++p; - if (yych <= 0x7F) goto yy231; - if (yych <= 0x8F) goto yy267; - goto yy231; -yy272: - yych = *++p; - if (yych <= 0x7F) goto yy231; - if (yych <= 0xBF) goto yy255; - goto yy231; -yy273: - yych = *++p; - if (yych <= 0x9F) goto yy231; - if (yych <= 0xBF) goto yy272; - goto yy231; -yy274: - yych = *++p; - if (yych <= 0x7F) goto yy231; - if (yych <= 0xBF) goto yy272; - goto yy231; -yy275: - yych = *++p; - if (yych <= 0x7F) goto yy231; - if (yych <= 0x9F) goto yy272; - goto yy231; -yy276: - yych = *++p; - if (yych <= 0x8F) goto yy231; - if (yych <= 0xBF) goto yy274; - goto yy231; -yy277: - yych = *++p; - if (yych <= 0x7F) goto yy231; - if (yych <= 0xBF) goto yy274; - goto yy231; -yy278: - yych = *++p; - if (yych <= 0x7F) goto yy231; - if (yych <= 0x8F) goto yy274; - goto yy231; -yy279: - yych = *++p; - if (yych == 'A') goto yy285; - if (yych == 'a') goto yy285; - goto yy231; -yy280: - yych = *++p; - if (yych <= '<') { - if (yych <= ' ') { - if (yych <= 0x08) goto yy231; - if (yych <= '\r') goto yy280; - if (yych <= 0x1F) goto yy231; - goto yy280; - } else { - if (yych <= '/') { - if (yych <= '.') goto yy231; - goto yy251; - } else { - if (yych == ':') goto yy260; - goto yy231; - } - } - } else { - if (yych <= 'Z') { - if (yych <= '=') goto yy282; - if (yych <= '>') goto yy252; - if (yych <= '@') goto yy231; - goto yy260; - } else { - if (yych <= '_') { - if (yych <= '^') goto yy231; - goto yy260; - } else { - if (yych <= '`') goto yy231; - if (yych <= 'z') goto yy260; - goto yy231; - } - } - } -yy282: - yych = *++p; - if (yybm[0+yych] & 16) { - goto yy286; - } - if (yych <= 0xE0) { - if (yych <= '"') { - if (yych <= 0x00) goto yy231; - if (yych <= ' ') goto yy282; - goto yy288; - } else { - if (yych <= '\'') goto yy290; - if (yych <= 0xC1) goto yy231; - if (yych <= 0xDF) goto yy292; - goto yy293; - } - } else { - if (yych <= 0xEF) { - if (yych == 0xED) goto yy295; - goto yy294; - } else { - if (yych <= 0xF0) goto yy296; - if (yych <= 0xF3) goto yy297; - if (yych <= 0xF4) goto yy298; - goto yy231; - } - } -yy284: - yych = *++p; - if (yybm[0+yych] & 8) { - goto yy262; - } - if (yych <= 0xEC) { - if (yych <= 0xC1) { - if (yych <= 0x00) goto yy231; - if (yych <= '-') goto yy251; - goto yy231; - } else { - if (yych <= 0xDF) goto yy265; - if (yych <= 0xE0) goto yy266; - goto yy267; - } - } else { - if (yych <= 0xF0) { - if (yych <= 0xED) goto yy268; - if (yych <= 0xEF) goto yy267; - goto yy269; - } else { - if (yych <= 0xF3) goto yy270; - if (yych <= 0xF4) goto yy271; - goto yy231; - } - } -yy285: - yych = *++p; - if (yych == 'T') goto yy299; - if (yych == 't') goto yy299; - goto yy231; -yy286: - yych = *++p; - if (yybm[0+yych] & 16) { - goto yy286; - } - if (yych <= 0xE0) { - if (yych <= '=') { - if (yych <= 0x00) goto yy231; - if (yych <= ' ') goto yy247; - goto yy231; - } else { - if (yych <= '>') goto yy252; - if (yych <= 0xC1) goto yy231; - if (yych <= 0xDF) goto yy292; - goto yy293; - } - } else { - if (yych <= 0xEF) { - if (yych == 0xED) goto yy295; - goto yy294; - } else { - if (yych <= 0xF0) goto yy296; - if (yych <= 0xF3) goto yy297; - if (yych <= 0xF4) goto yy298; - goto yy231; - } - } -yy288: - yych = *++p; - if (yybm[0+yych] & 32) { - goto yy288; - } - if (yych <= 0xEC) { - if (yych <= 0xC1) { - if (yych <= 0x00) goto yy231; - if (yych <= '"') goto yy300; - goto yy231; - } else { - if (yych <= 0xDF) goto yy301; - if (yych <= 0xE0) goto yy302; - goto yy303; - } - } else { - if (yych <= 0xF0) { - if (yych <= 0xED) goto yy304; - if (yych <= 0xEF) goto yy303; - goto yy305; - } else { - if (yych <= 0xF3) goto yy306; - if (yych <= 0xF4) goto yy307; - goto yy231; - } - } -yy290: - yych = *++p; - if (yybm[0+yych] & 64) { - goto yy290; - } - if (yych <= 0xEC) { - if (yych <= 0xC1) { - if (yych <= 0x00) goto yy231; - if (yych <= '\'') goto yy300; - goto yy231; - } else { - if (yych <= 0xDF) goto yy308; - if (yych <= 0xE0) goto yy309; - goto yy310; - } - } else { - if (yych <= 0xF0) { - if (yych <= 0xED) goto yy311; - if (yych <= 0xEF) goto yy310; - goto yy312; - } else { - if (yych <= 0xF3) goto yy313; - if (yych <= 0xF4) goto yy314; - goto yy231; - } - } -yy292: - yych = *++p; - if (yych <= 0x7F) goto yy231; - if (yych <= 0xBF) goto yy286; - goto yy231; -yy293: - yych = *++p; - if (yych <= 0x9F) goto yy231; - if (yych <= 0xBF) goto yy292; - goto yy231; -yy294: - yych = *++p; - if (yych <= 0x7F) goto yy231; - if (yych <= 0xBF) goto yy292; - goto yy231; -yy295: - yych = *++p; - if (yych <= 0x7F) goto yy231; - if (yych <= 0x9F) goto yy292; - goto yy231; -yy296: - yych = *++p; - if (yych <= 0x8F) goto yy231; - if (yych <= 0xBF) goto yy294; - goto yy231; -yy297: - yych = *++p; - if (yych <= 0x7F) goto yy231; - if (yych <= 0xBF) goto yy294; - goto yy231; -yy298: - yych = *++p; - if (yych <= 0x7F) goto yy231; - if (yych <= 0x8F) goto yy294; - goto yy231; -yy299: - yych = *++p; - if (yych == 'A') goto yy315; - if (yych == 'a') goto yy315; - goto yy231; -yy300: - yych = *++p; - if (yybm[0+yych] & 1) { - goto yy247; - } - if (yych == '/') goto yy251; - if (yych == '>') goto yy252; - goto yy231; -yy301: - yych = *++p; - if (yych <= 0x7F) goto yy231; - if (yych <= 0xBF) goto yy288; - goto yy231; -yy302: - yych = *++p; - if (yych <= 0x9F) goto yy231; - if (yych <= 0xBF) goto yy301; - goto yy231; -yy303: - yych = *++p; - if (yych <= 0x7F) goto yy231; - if (yych <= 0xBF) goto yy301; - goto yy231; -yy304: - yych = *++p; - if (yych <= 0x7F) goto yy231; - if (yych <= 0x9F) goto yy301; - goto yy231; -yy305: - yych = *++p; - if (yych <= 0x8F) goto yy231; - if (yych <= 0xBF) goto yy303; - goto yy231; -yy306: - yych = *++p; - if (yych <= 0x7F) goto yy231; - if (yych <= 0xBF) goto yy303; - goto yy231; -yy307: - yych = *++p; - if (yych <= 0x7F) goto yy231; - if (yych <= 0x8F) goto yy303; - goto yy231; -yy308: - yych = *++p; - if (yych <= 0x7F) goto yy231; - if (yych <= 0xBF) goto yy290; - goto yy231; -yy309: - yych = *++p; - if (yych <= 0x9F) goto yy231; - if (yych <= 0xBF) goto yy308; - goto yy231; -yy310: - yych = *++p; - if (yych <= 0x7F) goto yy231; - if (yych <= 0xBF) goto yy308; - goto yy231; -yy311: - yych = *++p; - if (yych <= 0x7F) goto yy231; - if (yych <= 0x9F) goto yy308; - goto yy231; -yy312: - yych = *++p; - if (yych <= 0x8F) goto yy231; - if (yych <= 0xBF) goto yy310; - goto yy231; -yy313: - yych = *++p; - if (yych <= 0x7F) goto yy231; - if (yych <= 0xBF) goto yy310; - goto yy231; -yy314: - yych = *++p; - if (yych <= 0x7F) goto yy231; - if (yych <= 0x8F) goto yy310; - goto yy231; -yy315: - yych = *++p; - if (yych != '[') goto yy231; -yy316: - yych = *++p; - if (yybm[0+yych] & 128) { - goto yy316; - } - if (yych <= 0xEC) { - if (yych <= 0xC1) { - if (yych <= 0x00) goto yy231; - if (yych >= '^') goto yy231; - } else { - if (yych <= 0xDF) goto yy319; - if (yych <= 0xE0) goto yy320; - goto yy321; - } - } else { - if (yych <= 0xF0) { - if (yych <= 0xED) goto yy322; - if (yych <= 0xEF) goto yy321; - goto yy323; - } else { - if (yych <= 0xF3) goto yy324; - if (yych <= 0xF4) goto yy325; - goto yy231; - } - } - yych = *++p; - if (yybm[0+yych] & 128) { - goto yy316; - } - if (yych <= 0xEC) { - if (yych <= 0xC1) { - if (yych <= 0x00) goto yy231; - if (yych <= ']') goto yy326; - goto yy231; - } else { - if (yych <= 0xDF) goto yy319; - if (yych <= 0xE0) goto yy320; - goto yy321; - } - } else { - if (yych <= 0xF0) { - if (yych <= 0xED) goto yy322; - if (yych <= 0xEF) goto yy321; - goto yy323; - } else { - if (yych <= 0xF3) goto yy324; - if (yych <= 0xF4) goto yy325; - goto yy231; - } - } -yy319: - yych = *++p; - if (yych <= 0x7F) goto yy231; - if (yych <= 0xBF) goto yy316; - goto yy231; -yy320: - yych = *++p; - if (yych <= 0x9F) goto yy231; - if (yych <= 0xBF) goto yy319; - goto yy231; -yy321: - yych = *++p; - if (yych <= 0x7F) goto yy231; - if (yych <= 0xBF) goto yy319; - goto yy231; -yy322: - yych = *++p; - if (yych <= 0x7F) goto yy231; - if (yych <= 0x9F) goto yy319; - goto yy231; -yy323: - yych = *++p; - if (yych <= 0x8F) goto yy231; - if (yych <= 0xBF) goto yy321; - goto yy231; -yy324: - yych = *++p; - if (yych <= 0x7F) goto yy231; - if (yych <= 0xBF) goto yy321; - goto yy231; -yy325: - yych = *++p; - if (yych <= 0x7F) goto yy231; - if (yych <= 0x8F) goto yy321; - goto yy231; -yy326: - yych = *++p; - if (yych <= 0xE0) { - if (yych <= '>') { - if (yych <= 0x00) goto yy231; - if (yych <= '=') goto yy316; - goto yy252; - } else { - if (yych <= 0x7F) goto yy316; - if (yych <= 0xC1) goto yy231; - if (yych <= 0xDF) goto yy319; - goto yy320; - } - } else { - if (yych <= 0xEF) { - if (yych == 0xED) goto yy322; - goto yy321; - } else { - if (yych <= 0xF0) goto yy323; - if (yych <= 0xF3) goto yy324; - if (yych <= 0xF4) goto yy325; - goto yy231; - } - } + { + unsigned char yych; + static const unsigned char yybm[] = { + 0, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 0, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + }; + yych = *p; + if (yych == '-') + goto yy225; + ++p; + yy224 : { return 0; } + yy225: + yych = *(marker = ++p); + if (yych != '-') + goto yy224; + yy226: + yych = *++p; + if (yybm[0 + yych] & 128) { + goto yy226; + } + if (yych <= 0xEC) { + if (yych <= 0xC1) { + if (yych <= 0x00) + goto yy227; + if (yych <= '-') + goto yy228; + } else { + if (yych <= 0xDF) + goto yy229; + if (yych <= 0xE0) + goto yy230; + goto yy231; + } + } else { + if (yych <= 0xF0) { + if (yych <= 0xED) + goto yy232; + if (yych <= 0xEF) + goto yy231; + goto yy233; + } else { + if (yych <= 0xF3) + goto yy234; + if (yych <= 0xF4) + goto yy235; + } + } + yy227: + p = marker; + goto yy224; + yy228: + yych = *++p; + if (yybm[0 + yych] & 128) { + goto yy226; + } + if (yych <= 0xEC) { + if (yych <= 0xC1) { + if (yych <= 0x00) + goto yy227; + if (yych <= '-') + goto yy236; + goto yy227; + } else { + if (yych <= 0xDF) + goto yy229; + if (yych <= 0xE0) + goto yy230; + goto yy231; + } + } else { + if (yych <= 0xF0) { + if (yych <= 0xED) + goto yy232; + if (yych <= 0xEF) + goto yy231; + goto yy233; + } else { + if (yych <= 0xF3) + goto yy234; + if (yych <= 0xF4) + goto yy235; + goto yy227; + } + } + yy229: + yych = *++p; + if (yych <= 0x7F) + goto yy227; + if (yych <= 0xBF) + goto yy226; + goto yy227; + yy230: + yych = *++p; + if (yych <= 0x9F) + goto yy227; + if (yych <= 0xBF) + goto yy229; + goto yy227; + yy231: + yych = *++p; + if (yych <= 0x7F) + goto yy227; + if (yych <= 0xBF) + goto yy229; + goto yy227; + yy232: + yych = *++p; + if (yych <= 0x7F) + goto yy227; + if (yych <= 0x9F) + goto yy229; + goto yy227; + yy233: + yych = *++p; + if (yych <= 0x8F) + goto yy227; + if (yych <= 0xBF) + goto yy231; + goto yy227; + yy234: + yych = *++p; + if (yych <= 0x7F) + goto yy227; + if (yych <= 0xBF) + goto yy231; + goto yy227; + yy235: + yych = *++p; + if (yych <= 0x7F) + goto yy227; + if (yych <= 0x8F) + goto yy231; + goto yy227; + yy236: + yych = *++p; + if (yych <= 0xE0) { + if (yych <= '>') { + if (yych <= 0x00) + goto yy227; + if (yych <= '=') + goto yy226; + } else { + if (yych <= 0x7F) + goto yy226; + if (yych <= 0xC1) + goto yy227; + if (yych <= 0xDF) + goto yy229; + goto yy230; + } + } else { + if (yych <= 0xEF) { + if (yych == 0xED) + goto yy232; + goto yy231; + } else { + if (yych <= 0xF0) + goto yy233; + if (yych <= 0xF3) + goto yy234; + if (yych <= 0xF4) + goto yy235; + goto yy227; + } + } + ++p; + { return (bufsize_t)(p - start); } + } } +bufsize_t _scan_html_pi(const unsigned char *p) { + const unsigned char *marker = NULL; + const unsigned char *start = p; + + { + unsigned char yych; + unsigned int yyaccept = 0; + static const unsigned char yybm[] = { + 0, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 0, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + }; + yych = *p; + if (yybm[0 + yych] & 128) { + goto yy240; + } + if (yych <= 0xEC) { + if (yych <= 0xC1) { + if (yych <= 0x00) + goto yy238; + if (yych <= '?') + goto yy243; + } else { + if (yych <= 0xDF) + goto yy244; + if (yych <= 0xE0) + goto yy245; + goto yy246; + } + } else { + if (yych <= 0xF0) { + if (yych <= 0xED) + goto yy247; + if (yych <= 0xEF) + goto yy246; + goto yy248; + } else { + if (yych <= 0xF3) + goto yy249; + if (yych <= 0xF4) + goto yy250; + } + } + yy238: + ++p; + yy239 : { return 0; } + yy240: + yyaccept = 0; + yych = *(marker = ++p); + yy241: + if (yybm[0 + yych] & 128) { + goto yy240; + } + if (yych <= 0xEC) { + if (yych <= 0xC1) { + if (yych <= 0x00) + goto yy242; + if (yych <= '?') + goto yy251; + } else { + if (yych <= 0xDF) + goto yy253; + if (yych <= 0xE0) + goto yy254; + goto yy255; + } + } else { + if (yych <= 0xF0) { + if (yych <= 0xED) + goto yy256; + if (yych <= 0xEF) + goto yy255; + goto yy257; + } else { + if (yych <= 0xF3) + goto yy258; + if (yych <= 0xF4) + goto yy259; + } + } + yy242 : { return (bufsize_t)(p - start); } + yy243: + yyaccept = 1; + yych = *(marker = ++p); + if (yych <= '?') { + if (yych <= 0x00) + goto yy239; + if (yych <= '=') + goto yy241; + if (yych <= '>') + goto yy239; + goto yy240; + } else { + if (yych <= 0x7F) + goto yy241; + if (yych <= 0xC1) + goto yy239; + if (yych <= 0xF4) + goto yy241; + goto yy239; + } + yy244: + yych = *++p; + if (yych <= 0x7F) + goto yy239; + if (yych <= 0xBF) + goto yy240; + goto yy239; + yy245: + yyaccept = 1; + yych = *(marker = ++p); + if (yych <= 0x9F) + goto yy239; + if (yych <= 0xBF) + goto yy253; + goto yy239; + yy246: + yyaccept = 1; + yych = *(marker = ++p); + if (yych <= 0x7F) + goto yy239; + if (yych <= 0xBF) + goto yy253; + goto yy239; + yy247: + yyaccept = 1; + yych = *(marker = ++p); + if (yych <= 0x7F) + goto yy239; + if (yych <= 0x9F) + goto yy253; + goto yy239; + yy248: + yyaccept = 1; + yych = *(marker = ++p); + if (yych <= 0x8F) + goto yy239; + if (yych <= 0xBF) + goto yy255; + goto yy239; + yy249: + yyaccept = 1; + yych = *(marker = ++p); + if (yych <= 0x7F) + goto yy239; + if (yych <= 0xBF) + goto yy255; + goto yy239; + yy250: + yyaccept = 1; + yych = *(marker = ++p); + if (yych <= 0x7F) + goto yy239; + if (yych <= 0x8F) + goto yy255; + goto yy239; + yy251: + yych = *++p; + if (yych <= 0xE0) { + if (yych <= '>') { + if (yych <= 0x00) + goto yy252; + if (yych <= '=') + goto yy240; + } else { + if (yych <= 0x7F) + goto yy240; + if (yych <= 0xC1) + goto yy252; + if (yych <= 0xDF) + goto yy253; + goto yy254; + } + } else { + if (yych <= 0xEF) { + if (yych == 0xED) + goto yy256; + goto yy255; + } else { + if (yych <= 0xF0) + goto yy257; + if (yych <= 0xF3) + goto yy258; + if (yych <= 0xF4) + goto yy259; + } + } + yy252: + p = marker; + if (yyaccept == 0) { + goto yy242; + } else { + goto yy239; + } + yy253: + yych = *++p; + if (yych <= 0x7F) + goto yy252; + if (yych <= 0xBF) + goto yy240; + goto yy252; + yy254: + yych = *++p; + if (yych <= 0x9F) + goto yy252; + if (yych <= 0xBF) + goto yy253; + goto yy252; + yy255: + yych = *++p; + if (yych <= 0x7F) + goto yy252; + if (yych <= 0xBF) + goto yy253; + goto yy252; + yy256: + yych = *++p; + if (yych <= 0x7F) + goto yy252; + if (yych <= 0x9F) + goto yy253; + goto yy252; + yy257: + yych = *++p; + if (yych <= 0x8F) + goto yy252; + if (yych <= 0xBF) + goto yy255; + goto yy252; + yy258: + yych = *++p; + if (yych <= 0x7F) + goto yy252; + if (yych <= 0xBF) + goto yy255; + goto yy252; + yy259: + yych = *++p; + if (yych <= 0x7F) + goto yy252; + if (yych <= 0x8F) + goto yy255; + goto yy252; + } } -// Try to (liberally) match an HTML tag after first <, returning num of chars matched. -bufsize_t _scan_liberal_html_tag(const unsigned char *p) -{ +bufsize_t _scan_html_declaration(const unsigned char *p) { const unsigned char *marker = NULL; const unsigned char *start = p; -{ - unsigned char yych; - unsigned int yyaccept = 0; - static const unsigned char yybm[] = { - 0, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 0, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 128, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - }; - yych = *p; - if (yych <= 0xE0) { - if (yych <= '\n') { - if (yych <= 0x00) goto yy329; - if (yych <= '\t') goto yy331; - } else { - if (yych <= 0x7F) goto yy331; - if (yych <= 0xC1) goto yy329; - if (yych <= 0xDF) goto yy332; - goto yy333; - } - } else { - if (yych <= 0xEF) { - if (yych == 0xED) goto yy335; - goto yy334; - } else { - if (yych <= 0xF0) goto yy336; - if (yych <= 0xF3) goto yy337; - if (yych <= 0xF4) goto yy338; - } - } -yy329: - ++p; -yy330: - { return 0; } -yy331: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= '\n') { - if (yych <= 0x00) goto yy330; - if (yych <= '\t') goto yy340; - goto yy330; - } else { - if (yych <= 0x7F) goto yy340; - if (yych <= 0xC1) goto yy330; - if (yych <= 0xF4) goto yy340; - goto yy330; - } -yy332: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= 0x7F) goto yy330; - if (yych <= 0xBF) goto yy339; - goto yy330; -yy333: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= 0x9F) goto yy330; - if (yych <= 0xBF) goto yy345; - goto yy330; -yy334: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= 0x7F) goto yy330; - if (yych <= 0xBF) goto yy345; - goto yy330; -yy335: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= 0x7F) goto yy330; - if (yych <= 0x9F) goto yy345; - goto yy330; -yy336: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= 0x8F) goto yy330; - if (yych <= 0xBF) goto yy347; - goto yy330; -yy337: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= 0x7F) goto yy330; - if (yych <= 0xBF) goto yy347; - goto yy330; -yy338: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= 0x7F) goto yy330; - if (yych <= 0x8F) goto yy347; - goto yy330; -yy339: - yych = *++p; -yy340: - if (yybm[0+yych] & 64) { - goto yy339; - } - if (yych <= 0xEC) { - if (yych <= 0xC1) { - if (yych <= '\n') goto yy341; - if (yych <= '>') goto yy342; - } else { - if (yych <= 0xDF) goto yy345; - if (yych <= 0xE0) goto yy346; - goto yy347; - } - } else { - if (yych <= 0xF0) { - if (yych <= 0xED) goto yy348; - if (yych <= 0xEF) goto yy347; - goto yy349; - } else { - if (yych <= 0xF3) goto yy350; - if (yych <= 0xF4) goto yy351; - } - } -yy341: - p = marker; - if (yyaccept == 0) { - goto yy330; - } else { - goto yy344; - } -yy342: - yyaccept = 1; - yych = *(marker = ++p); - if (yybm[0+yych] & 64) { - goto yy339; - } - if (yych <= 0xEC) { - if (yych <= 0xC1) { - if (yych <= '\n') goto yy344; - if (yych <= '>') goto yy342; - } else { - if (yych <= 0xDF) goto yy345; - if (yych <= 0xE0) goto yy346; - goto yy347; - } - } else { - if (yych <= 0xF0) { - if (yych <= 0xED) goto yy348; - if (yych <= 0xEF) goto yy347; - goto yy349; - } else { - if (yych <= 0xF3) goto yy350; - if (yych <= 0xF4) goto yy351; - } - } -yy344: - { return (bufsize_t)(p - start); } -yy345: - yych = *++p; - if (yych <= 0x7F) goto yy341; - if (yych <= 0xBF) goto yy339; - goto yy341; -yy346: - yych = *++p; - if (yych <= 0x9F) goto yy341; - if (yych <= 0xBF) goto yy345; - goto yy341; -yy347: - yych = *++p; - if (yych <= 0x7F) goto yy341; - if (yych <= 0xBF) goto yy345; - goto yy341; -yy348: - yych = *++p; - if (yych <= 0x7F) goto yy341; - if (yych <= 0x9F) goto yy345; - goto yy341; -yy349: - yych = *++p; - if (yych <= 0x8F) goto yy341; - if (yych <= 0xBF) goto yy347; - goto yy341; -yy350: - yych = *++p; - if (yych <= 0x7F) goto yy341; - if (yych <= 0xBF) goto yy347; - goto yy341; -yy351: - yych = *++p; - if (yych <= 0x7F) goto yy341; - if (yych <= 0x8F) goto yy347; - goto yy341; + { + unsigned char yych; + unsigned int yyaccept = 0; + static const unsigned char yybm[] = { + 0, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 0, 64, 64, 192, 192, 192, 192, 192, + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, + 192, 192, 192, 192, 192, 192, 192, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + }; + yych = *p; + if (yych <= '@') + goto yy261; + if (yych <= 'Z') + goto yy263; + yy261: + ++p; + yy262 : { return 0; } + yy263: + yyaccept = 0; + yych = *(marker = ++p); + if (yybm[0 + yych] & 128) { + goto yy266; + } + if (yych <= 0x08) + goto yy262; + if (yych <= '\r') + goto yy264; + if (yych != ' ') + goto yy262; + yy264: + yyaccept = 1; + yych = *(marker = ++p); + if (yybm[0 + yych] & 64) { + goto yy264; + } + if (yych <= 0xED) { + if (yych <= 0xDF) { + if (yych >= 0xC2) + goto yy268; + } else { + if (yych <= 0xE0) + goto yy269; + if (yych <= 0xEC) + goto yy270; + goto yy271; + } + } else { + if (yych <= 0xF0) { + if (yych <= 0xEF) + goto yy270; + goto yy272; + } else { + if (yych <= 0xF3) + goto yy273; + if (yych <= 0xF4) + goto yy274; + } + } + yy265 : { return (bufsize_t)(p - start); } + yy266: + yych = *++p; + if (yybm[0 + yych] & 128) { + goto yy266; + } + if (yych <= 0x08) + goto yy267; + if (yych <= '\r') + goto yy264; + if (yych == ' ') + goto yy264; + yy267: + p = marker; + if (yyaccept == 0) { + goto yy262; + } else { + goto yy265; + } + yy268: + yych = *++p; + if (yych <= 0x7F) + goto yy267; + if (yych <= 0xBF) + goto yy264; + goto yy267; + yy269: + yych = *++p; + if (yych <= 0x9F) + goto yy267; + if (yych <= 0xBF) + goto yy268; + goto yy267; + yy270: + yych = *++p; + if (yych <= 0x7F) + goto yy267; + if (yych <= 0xBF) + goto yy268; + goto yy267; + yy271: + yych = *++p; + if (yych <= 0x7F) + goto yy267; + if (yych <= 0x9F) + goto yy268; + goto yy267; + yy272: + yych = *++p; + if (yych <= 0x8F) + goto yy267; + if (yych <= 0xBF) + goto yy270; + goto yy267; + yy273: + yych = *++p; + if (yych <= 0x7F) + goto yy267; + if (yych <= 0xBF) + goto yy270; + goto yy267; + yy274: + yych = *++p; + if (yych <= 0x7F) + goto yy267; + if (yych <= 0x8F) + goto yy270; + goto yy267; + } } +bufsize_t _scan_html_cdata(const unsigned char *p) { + const unsigned char *marker = NULL; + const unsigned char *start = p; + + { + unsigned char yych; + unsigned int yyaccept = 0; + static const unsigned char yybm[] = { + 0, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 0, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + }; + yych = *p; + if (yych == 'C') + goto yy277; + if (yych == 'c') + goto yy277; + ++p; + yy276 : { return 0; } + yy277: + yyaccept = 0; + yych = *(marker = ++p); + if (yych == 'D') + goto yy278; + if (yych != 'd') + goto yy276; + yy278: + yych = *++p; + if (yych == 'A') + goto yy280; + if (yych == 'a') + goto yy280; + yy279: + p = marker; + if (yyaccept == 0) { + goto yy276; + } else { + goto yy284; + } + yy280: + yych = *++p; + if (yych == 'T') + goto yy281; + if (yych != 't') + goto yy279; + yy281: + yych = *++p; + if (yych == 'A') + goto yy282; + if (yych != 'a') + goto yy279; + yy282: + yych = *++p; + if (yych != '[') + goto yy279; + yy283: + yyaccept = 1; + yych = *(marker = ++p); + if (yybm[0 + yych] & 128) { + goto yy283; + } + if (yych <= 0xEC) { + if (yych <= 0xC1) { + if (yych <= 0x00) + goto yy284; + if (yych <= ']') + goto yy285; + } else { + if (yych <= 0xDF) + goto yy286; + if (yych <= 0xE0) + goto yy287; + goto yy288; + } + } else { + if (yych <= 0xF0) { + if (yych <= 0xED) + goto yy289; + if (yych <= 0xEF) + goto yy288; + goto yy290; + } else { + if (yych <= 0xF3) + goto yy291; + if (yych <= 0xF4) + goto yy292; + } + } + yy284 : { return (bufsize_t)(p - start); } + yy285: + yych = *++p; + if (yybm[0 + yych] & 128) { + goto yy283; + } + if (yych <= 0xEC) { + if (yych <= 0xC1) { + if (yych <= 0x00) + goto yy279; + if (yych <= ']') + goto yy293; + goto yy279; + } else { + if (yych <= 0xDF) + goto yy286; + if (yych <= 0xE0) + goto yy287; + goto yy288; + } + } else { + if (yych <= 0xF0) { + if (yych <= 0xED) + goto yy289; + if (yych <= 0xEF) + goto yy288; + goto yy290; + } else { + if (yych <= 0xF3) + goto yy291; + if (yych <= 0xF4) + goto yy292; + goto yy279; + } + } + yy286: + yych = *++p; + if (yych <= 0x7F) + goto yy279; + if (yych <= 0xBF) + goto yy283; + goto yy279; + yy287: + yych = *++p; + if (yych <= 0x9F) + goto yy279; + if (yych <= 0xBF) + goto yy286; + goto yy279; + yy288: + yych = *++p; + if (yych <= 0x7F) + goto yy279; + if (yych <= 0xBF) + goto yy286; + goto yy279; + yy289: + yych = *++p; + if (yych <= 0x7F) + goto yy279; + if (yych <= 0x9F) + goto yy286; + goto yy279; + yy290: + yych = *++p; + if (yych <= 0x8F) + goto yy279; + if (yych <= 0xBF) + goto yy288; + goto yy279; + yy291: + yych = *++p; + if (yych <= 0x7F) + goto yy279; + if (yych <= 0xBF) + goto yy288; + goto yy279; + yy292: + yych = *++p; + if (yych <= 0x7F) + goto yy279; + if (yych <= 0x8F) + goto yy288; + goto yy279; + yy293: + yych = *++p; + if (yych <= 0xE0) { + if (yych <= '>') { + if (yych <= 0x00) + goto yy279; + if (yych <= '=') + goto yy283; + goto yy279; + } else { + if (yych <= 0x7F) + goto yy283; + if (yych <= 0xC1) + goto yy279; + if (yych <= 0xDF) + goto yy286; + goto yy287; + } + } else { + if (yych <= 0xEF) { + if (yych == 0xED) + goto yy289; + goto yy288; + } else { + if (yych <= 0xF0) + goto yy290; + if (yych <= 0xF3) + goto yy291; + if (yych <= 0xF4) + goto yy292; + goto yy279; + } + } + } } // Try to match an HTML block tag start line, returning // an integer code for the type of block (1-6, matching the spec). // #7 is handled by a separate function, below. -bufsize_t _scan_html_block_start(const unsigned char *p) -{ +bufsize_t _scan_html_block_start(const unsigned char *p) { const unsigned char *marker = NULL; -{ - unsigned char yych; - yych = *p; - if (yych == '<') goto yy356; - ++p; -yy355: - { return 0; } -yy356: - yych = *(marker = ++p); - switch (yych) { - case '!': goto yy357; - case '/': goto yy359; - case '?': goto yy360; - case 'A': - case 'a': goto yy362; - case 'B': - case 'b': goto yy363; - case 'C': - case 'c': goto yy364; - case 'D': - case 'd': goto yy365; - case 'F': - case 'f': goto yy366; - case 'H': - case 'h': goto yy367; - case 'I': - case 'i': goto yy368; - case 'L': - case 'l': goto yy369; - case 'M': - case 'm': goto yy370; - case 'N': - case 'n': goto yy371; - case 'O': - case 'o': goto yy372; - case 'P': - case 'p': goto yy373; - case 'S': - case 's': goto yy374; - case 'T': - case 't': goto yy375; - case 'U': - case 'u': goto yy376; - default: goto yy355; - } -yy357: - yych = *++p; - if (yych <= '@') { - if (yych == '-') goto yy377; - } else { - if (yych <= 'Z') goto yy378; - if (yych <= '[') goto yy380; - } -yy358: - p = marker; - goto yy355; -yy359: - yych = *++p; - switch (yych) { - case 'A': - case 'a': goto yy362; - case 'B': - case 'b': goto yy363; - case 'C': - case 'c': goto yy364; - case 'D': - case 'd': goto yy365; - case 'F': - case 'f': goto yy366; - case 'H': - case 'h': goto yy367; - case 'I': - case 'i': goto yy368; - case 'L': - case 'l': goto yy369; - case 'M': - case 'm': goto yy370; - case 'N': - case 'n': goto yy371; - case 'O': - case 'o': goto yy372; - case 'P': - case 'p': goto yy381; - case 'S': - case 's': goto yy382; - case 'T': - case 't': goto yy375; - case 'U': - case 'u': goto yy376; - default: goto yy358; - } -yy360: - ++p; - { return 3; } -yy362: - yych = *++p; - if (yych <= 'S') { - if (yych <= 'D') { - if (yych <= 'C') goto yy358; - goto yy383; - } else { - if (yych <= 'Q') goto yy358; - if (yych <= 'R') goto yy384; - goto yy385; - } - } else { - if (yych <= 'q') { - if (yych == 'd') goto yy383; - goto yy358; - } else { - if (yych <= 'r') goto yy384; - if (yych <= 's') goto yy385; - goto yy358; - } - } -yy363: - yych = *++p; - if (yych <= 'O') { - if (yych <= 'K') { - if (yych == 'A') goto yy386; - goto yy358; - } else { - if (yych <= 'L') goto yy387; - if (yych <= 'N') goto yy358; - goto yy388; - } - } else { - if (yych <= 'k') { - if (yych == 'a') goto yy386; - goto yy358; - } else { - if (yych <= 'l') goto yy387; - if (yych == 'o') goto yy388; - goto yy358; - } - } -yy364: - yych = *++p; - if (yych <= 'O') { - if (yych <= 'D') { - if (yych == 'A') goto yy389; - goto yy358; - } else { - if (yych <= 'E') goto yy390; - if (yych <= 'N') goto yy358; - goto yy391; - } - } else { - if (yych <= 'd') { - if (yych == 'a') goto yy389; - goto yy358; - } else { - if (yych <= 'e') goto yy390; - if (yych == 'o') goto yy391; - goto yy358; - } - } -yy365: - yych = *++p; - switch (yych) { - case 'D': - case 'L': - case 'T': - case 'd': - case 'l': - case 't': goto yy392; - case 'E': - case 'e': goto yy393; - case 'I': - case 'i': goto yy394; - default: goto yy358; - } -yy366: - yych = *++p; - if (yych <= 'R') { - if (yych <= 'N') { - if (yych == 'I') goto yy395; - goto yy358; - } else { - if (yych <= 'O') goto yy396; - if (yych <= 'Q') goto yy358; - goto yy397; - } - } else { - if (yych <= 'n') { - if (yych == 'i') goto yy395; - goto yy358; - } else { - if (yych <= 'o') goto yy396; - if (yych == 'r') goto yy397; - goto yy358; - } - } -yy367: - yych = *++p; - if (yych <= 'S') { - if (yych <= 'D') { - if (yych <= '0') goto yy358; - if (yych <= '6') goto yy392; - goto yy358; - } else { - if (yych <= 'E') goto yy398; - if (yych == 'R') goto yy392; - goto yy358; - } - } else { - if (yych <= 'q') { - if (yych <= 'T') goto yy399; - if (yych == 'e') goto yy398; - goto yy358; - } else { - if (yych <= 'r') goto yy392; - if (yych == 't') goto yy399; - goto yy358; - } - } -yy368: - yych = *++p; - if (yych == 'F') goto yy400; - if (yych == 'f') goto yy400; - goto yy358; -yy369: - yych = *++p; - if (yych <= 'I') { - if (yych == 'E') goto yy401; - if (yych <= 'H') goto yy358; - goto yy402; - } else { - if (yych <= 'e') { - if (yych <= 'd') goto yy358; - goto yy401; - } else { - if (yych == 'i') goto yy402; - goto yy358; - } - } -yy370: - yych = *++p; - if (yych <= 'E') { - if (yych == 'A') goto yy403; - if (yych <= 'D') goto yy358; - goto yy404; - } else { - if (yych <= 'a') { - if (yych <= '`') goto yy358; - goto yy403; - } else { - if (yych == 'e') goto yy404; - goto yy358; - } - } -yy371: - yych = *++p; - if (yych <= 'O') { - if (yych == 'A') goto yy405; - if (yych <= 'N') goto yy358; - goto yy406; - } else { - if (yych <= 'a') { - if (yych <= '`') goto yy358; - goto yy405; - } else { - if (yych == 'o') goto yy406; - goto yy358; - } - } -yy372: - yych = *++p; - if (yych <= 'P') { - if (yych == 'L') goto yy392; - if (yych <= 'O') goto yy358; - goto yy407; - } else { - if (yych <= 'l') { - if (yych <= 'k') goto yy358; - goto yy392; - } else { - if (yych == 'p') goto yy407; - goto yy358; - } - } -yy373: - yych = *++p; - if (yych <= '>') { - if (yych <= ' ') { - if (yych <= 0x08) goto yy358; - if (yych <= '\r') goto yy408; - if (yych <= 0x1F) goto yy358; - goto yy408; - } else { - if (yych == '/') goto yy410; - if (yych <= '=') goto yy358; - goto yy408; - } - } else { - if (yych <= 'R') { - if (yych == 'A') goto yy411; - if (yych <= 'Q') goto yy358; - goto yy412; - } else { - if (yych <= 'a') { - if (yych <= '`') goto yy358; - goto yy411; - } else { - if (yych == 'r') goto yy412; - goto yy358; - } - } - } -yy374: - yych = *++p; - switch (yych) { - case 'C': - case 'c': goto yy413; - case 'E': - case 'e': goto yy414; - case 'O': - case 'o': goto yy415; - case 'T': - case 't': goto yy416; - case 'U': - case 'u': goto yy417; - default: goto yy358; - } -yy375: - yych = *++p; - switch (yych) { - case 'A': - case 'a': goto yy418; - case 'B': - case 'b': goto yy419; - case 'D': - case 'd': goto yy392; - case 'F': - case 'f': goto yy420; - case 'H': - case 'h': goto yy421; - case 'I': - case 'i': goto yy422; - case 'R': - case 'r': goto yy423; - default: goto yy358; - } -yy376: - yych = *++p; - if (yych == 'L') goto yy392; - if (yych == 'l') goto yy392; - goto yy358; -yy377: - yych = *++p; - if (yych == '-') goto yy424; - goto yy358; -yy378: - ++p; - { return 4; } -yy380: - yych = *++p; - if (yych == 'C') goto yy426; - if (yych == 'c') goto yy426; - goto yy358; -yy381: - yych = *++p; - if (yych <= '/') { - if (yych <= 0x1F) { - if (yych <= 0x08) goto yy358; - if (yych <= '\r') goto yy408; - goto yy358; - } else { - if (yych <= ' ') goto yy408; - if (yych <= '.') goto yy358; - goto yy410; - } - } else { - if (yych <= '@') { - if (yych == '>') goto yy408; - goto yy358; - } else { - if (yych <= 'A') goto yy411; - if (yych == 'a') goto yy411; - goto yy358; - } - } -yy382: - yych = *++p; - if (yych <= 'U') { - if (yych <= 'N') { - if (yych == 'E') goto yy414; - goto yy358; - } else { - if (yych <= 'O') goto yy415; - if (yych <= 'T') goto yy358; - goto yy417; - } - } else { - if (yych <= 'n') { - if (yych == 'e') goto yy414; - goto yy358; - } else { - if (yych <= 'o') goto yy415; - if (yych == 'u') goto yy417; - goto yy358; - } - } -yy383: - yych = *++p; - if (yych == 'D') goto yy427; - if (yych == 'd') goto yy427; - goto yy358; -yy384: - yych = *++p; - if (yych == 'T') goto yy428; - if (yych == 't') goto yy428; - goto yy358; -yy385: - yych = *++p; - if (yych == 'I') goto yy429; - if (yych == 'i') goto yy429; - goto yy358; -yy386: - yych = *++p; - if (yych == 'S') goto yy430; - if (yych == 's') goto yy430; - goto yy358; -yy387: - yych = *++p; - if (yych == 'O') goto yy431; - if (yych == 'o') goto yy431; - goto yy358; -yy388: - yych = *++p; - if (yych == 'D') goto yy432; - if (yych == 'd') goto yy432; - goto yy358; -yy389: - yych = *++p; - if (yych == 'P') goto yy433; - if (yych == 'p') goto yy433; - goto yy358; -yy390: - yych = *++p; - if (yych == 'N') goto yy434; - if (yych == 'n') goto yy434; - goto yy358; -yy391: - yych = *++p; - if (yych == 'L') goto yy435; - if (yych == 'l') goto yy435; - goto yy358; -yy392: - yych = *++p; - if (yych <= ' ') { - if (yych <= 0x08) goto yy358; - if (yych <= '\r') goto yy408; - if (yych <= 0x1F) goto yy358; - goto yy408; - } else { - if (yych <= '/') { - if (yych <= '.') goto yy358; - goto yy410; - } else { - if (yych == '>') goto yy408; - goto yy358; - } - } -yy393: - yych = *++p; - if (yych == 'T') goto yy436; - if (yych == 't') goto yy436; - goto yy358; -yy394: - yych = *++p; - if (yych <= 'V') { - if (yych <= 'Q') { - if (yych == 'A') goto yy437; - goto yy358; - } else { - if (yych <= 'R') goto yy392; - if (yych <= 'U') goto yy358; - goto yy392; - } - } else { - if (yych <= 'q') { - if (yych == 'a') goto yy437; - goto yy358; - } else { - if (yych <= 'r') goto yy392; - if (yych == 'v') goto yy392; - goto yy358; - } - } -yy395: - yych = *++p; - if (yych <= 'G') { - if (yych == 'E') goto yy438; - if (yych <= 'F') goto yy358; - goto yy439; - } else { - if (yych <= 'e') { - if (yych <= 'd') goto yy358; - goto yy438; - } else { - if (yych == 'g') goto yy439; - goto yy358; - } - } -yy396: - yych = *++p; - if (yych <= 'R') { - if (yych == 'O') goto yy434; - if (yych <= 'Q') goto yy358; - goto yy440; - } else { - if (yych <= 'o') { - if (yych <= 'n') goto yy358; - goto yy434; - } else { - if (yych == 'r') goto yy440; - goto yy358; - } - } -yy397: - yych = *++p; - if (yych == 'A') goto yy441; - if (yych == 'a') goto yy441; - goto yy358; -yy398: - yych = *++p; - if (yych == 'A') goto yy442; - if (yych == 'a') goto yy442; - goto yy358; -yy399: - yych = *++p; - if (yych == 'M') goto yy376; - if (yych == 'm') goto yy376; - goto yy358; -yy400: - yych = *++p; - if (yych == 'R') goto yy443; - if (yych == 'r') goto yy443; - goto yy358; -yy401: - yych = *++p; - if (yych == 'G') goto yy444; - if (yych == 'g') goto yy444; - goto yy358; -yy402: - yych = *++p; - if (yych <= '/') { - if (yych <= 0x1F) { - if (yych <= 0x08) goto yy358; - if (yych <= '\r') goto yy408; - goto yy358; - } else { - if (yych <= ' ') goto yy408; - if (yych <= '.') goto yy358; - goto yy410; - } - } else { - if (yych <= 'M') { - if (yych == '>') goto yy408; - goto yy358; - } else { - if (yych <= 'N') goto yy445; - if (yych == 'n') goto yy445; - goto yy358; - } - } -yy403: - yych = *++p; - if (yych == 'I') goto yy446; - if (yych == 'i') goto yy446; - goto yy358; -yy404: - yych = *++p; - if (yych == 'N') goto yy447; - if (yych == 'n') goto yy447; - goto yy358; -yy405: - yych = *++p; - if (yych == 'V') goto yy392; - if (yych == 'v') goto yy392; - goto yy358; -yy406: - yych = *++p; - if (yych == 'F') goto yy448; - if (yych == 'f') goto yy448; - goto yy358; -yy407: - yych = *++p; - if (yych == 'T') goto yy449; - if (yych == 't') goto yy449; - goto yy358; -yy408: - ++p; - { return 6; } -yy410: - yych = *++p; - if (yych == '>') goto yy408; - goto yy358; -yy411: - yych = *++p; - if (yych == 'R') goto yy450; - if (yych == 'r') goto yy450; - goto yy358; -yy412: - yych = *++p; - if (yych == 'E') goto yy451; - if (yych == 'e') goto yy451; - goto yy358; -yy413: - yych = *++p; - if (yych == 'R') goto yy452; - if (yych == 'r') goto yy452; - goto yy358; -yy414: - yych = *++p; - if (yych == 'C') goto yy433; - if (yych == 'c') goto yy433; - goto yy358; -yy415: - yych = *++p; - if (yych == 'U') goto yy453; - if (yych == 'u') goto yy453; - goto yy358; -yy416: - yych = *++p; - if (yych == 'Y') goto yy454; - if (yych == 'y') goto yy454; - goto yy358; -yy417: - yych = *++p; - if (yych == 'M') goto yy455; - if (yych == 'm') goto yy455; - goto yy358; -yy418: - yych = *++p; - if (yych == 'B') goto yy456; - if (yych == 'b') goto yy456; - goto yy358; -yy419: - yych = *++p; - if (yych == 'O') goto yy388; - if (yych == 'o') goto yy388; - goto yy358; -yy420: - yych = *++p; - if (yych == 'O') goto yy457; - if (yych == 'o') goto yy457; - goto yy358; -yy421: - yych = *++p; - if (yych <= '/') { - if (yych <= 0x1F) { - if (yych <= 0x08) goto yy358; - if (yych <= '\r') goto yy408; - goto yy358; - } else { - if (yych <= ' ') goto yy408; - if (yych <= '.') goto yy358; - goto yy410; - } - } else { - if (yych <= 'D') { - if (yych == '>') goto yy408; - goto yy358; - } else { - if (yych <= 'E') goto yy458; - if (yych == 'e') goto yy458; - goto yy358; - } - } -yy422: - yych = *++p; - if (yych == 'T') goto yy456; - if (yych == 't') goto yy456; - goto yy358; -yy423: - yych = *++p; - if (yych <= '/') { - if (yych <= 0x1F) { - if (yych <= 0x08) goto yy358; - if (yych <= '\r') goto yy408; - goto yy358; - } else { - if (yych <= ' ') goto yy408; - if (yych <= '.') goto yy358; - goto yy410; - } - } else { - if (yych <= '@') { - if (yych == '>') goto yy408; - goto yy358; - } else { - if (yych <= 'A') goto yy459; - if (yych == 'a') goto yy459; - goto yy358; - } - } -yy424: - ++p; - { return 2; } -yy426: - yych = *++p; - if (yych == 'D') goto yy460; - if (yych == 'd') goto yy460; - goto yy358; -yy427: - yych = *++p; - if (yych == 'R') goto yy461; - if (yych == 'r') goto yy461; - goto yy358; -yy428: - yych = *++p; - if (yych == 'I') goto yy462; - if (yych == 'i') goto yy462; - goto yy358; -yy429: - yych = *++p; - if (yych == 'D') goto yy463; - if (yych == 'd') goto yy463; - goto yy358; -yy430: - yych = *++p; - if (yych == 'E') goto yy464; - if (yych == 'e') goto yy464; - goto yy358; -yy431: - yych = *++p; - if (yych == 'C') goto yy465; - if (yych == 'c') goto yy465; - goto yy358; -yy432: - yych = *++p; - if (yych == 'Y') goto yy392; - if (yych == 'y') goto yy392; - goto yy358; -yy433: - yych = *++p; - if (yych == 'T') goto yy466; - if (yych == 't') goto yy466; - goto yy358; -yy434: - yych = *++p; - if (yych == 'T') goto yy467; - if (yych == 't') goto yy467; - goto yy358; -yy435: - yych = *++p; - if (yych <= '/') { - if (yych <= 0x1F) { - if (yych <= 0x08) goto yy358; - if (yych <= '\r') goto yy408; - goto yy358; - } else { - if (yych <= ' ') goto yy408; - if (yych <= '.') goto yy358; - goto yy410; - } - } else { - if (yych <= 'F') { - if (yych == '>') goto yy408; - goto yy358; - } else { - if (yych <= 'G') goto yy468; - if (yych == 'g') goto yy468; - goto yy358; - } - } -yy436: - yych = *++p; - if (yych == 'A') goto yy469; - if (yych == 'a') goto yy469; - goto yy358; -yy437: - yych = *++p; - if (yych == 'L') goto yy470; - if (yych == 'l') goto yy470; - goto yy358; -yy438: - yych = *++p; - if (yych == 'L') goto yy471; - if (yych == 'l') goto yy471; - goto yy358; -yy439: - yych = *++p; - if (yych <= 'U') { - if (yych == 'C') goto yy472; - if (yych <= 'T') goto yy358; - goto yy473; - } else { - if (yych <= 'c') { - if (yych <= 'b') goto yy358; - goto yy472; - } else { - if (yych == 'u') goto yy473; - goto yy358; - } - } -yy440: - yych = *++p; - if (yych == 'M') goto yy392; - if (yych == 'm') goto yy392; - goto yy358; -yy441: - yych = *++p; - if (yych == 'M') goto yy474; - if (yych == 'm') goto yy474; - goto yy358; -yy442: - yych = *++p; - if (yych == 'D') goto yy475; - if (yych == 'd') goto yy475; - goto yy358; -yy443: - yych = *++p; - if (yych == 'A') goto yy476; - if (yych == 'a') goto yy476; - goto yy358; -yy444: - yych = *++p; - if (yych == 'E') goto yy477; - if (yych == 'e') goto yy477; - goto yy358; -yy445: - yych = *++p; - if (yych == 'K') goto yy392; - if (yych == 'k') goto yy392; - goto yy358; -yy446: - yych = *++p; - if (yych == 'N') goto yy392; - if (yych == 'n') goto yy392; - goto yy358; -yy447: - yych = *++p; - if (yych == 'U') goto yy478; - if (yych == 'u') goto yy478; - goto yy358; -yy448: - yych = *++p; - if (yych == 'R') goto yy479; - if (yych == 'r') goto yy479; - goto yy358; -yy449: - yych = *++p; - if (yych <= 'I') { - if (yych == 'G') goto yy468; - if (yych <= 'H') goto yy358; - goto yy480; - } else { - if (yych <= 'g') { - if (yych <= 'f') goto yy358; - goto yy468; - } else { - if (yych == 'i') goto yy480; - goto yy358; - } - } -yy450: - yych = *++p; - if (yych == 'A') goto yy440; - if (yych == 'a') goto yy440; - goto yy358; -yy451: - yych = *++p; - if (yych <= 0x1F) { - if (yych <= 0x08) goto yy358; - if (yych <= '\r') goto yy481; - goto yy358; - } else { - if (yych <= ' ') goto yy481; - if (yych == '>') goto yy481; - goto yy358; - } -yy452: - yych = *++p; - if (yych == 'I') goto yy483; - if (yych == 'i') goto yy483; - goto yy358; -yy453: - yych = *++p; - if (yych == 'R') goto yy484; - if (yych == 'r') goto yy484; - goto yy358; -yy454: - yych = *++p; - if (yych == 'L') goto yy412; - if (yych == 'l') goto yy412; - goto yy358; -yy455: - yych = *++p; - if (yych == 'M') goto yy485; - if (yych == 'm') goto yy485; - goto yy358; -yy456: - yych = *++p; - if (yych == 'L') goto yy463; - if (yych == 'l') goto yy463; - goto yy358; -yy457: - yych = *++p; - if (yych == 'O') goto yy486; - if (yych == 'o') goto yy486; - goto yy358; -yy458: - yych = *++p; - if (yych == 'A') goto yy487; - if (yych == 'a') goto yy487; - goto yy358; -yy459: - yych = *++p; - if (yych == 'C') goto yy445; - if (yych == 'c') goto yy445; - goto yy358; -yy460: - yych = *++p; - if (yych == 'A') goto yy488; - if (yych == 'a') goto yy488; - goto yy358; -yy461: - yych = *++p; - if (yych == 'E') goto yy489; - if (yych == 'e') goto yy489; - goto yy358; -yy462: - yych = *++p; - if (yych == 'C') goto yy456; - if (yych == 'c') goto yy456; - goto yy358; -yy463: - yych = *++p; - if (yych == 'E') goto yy392; - if (yych == 'e') goto yy392; - goto yy358; -yy464: - yych = *++p; - if (yych <= '/') { - if (yych <= 0x1F) { - if (yych <= 0x08) goto yy358; - if (yych <= '\r') goto yy408; - goto yy358; - } else { - if (yych <= ' ') goto yy408; - if (yych <= '.') goto yy358; - goto yy410; - } - } else { - if (yych <= 'E') { - if (yych == '>') goto yy408; - goto yy358; - } else { - if (yych <= 'F') goto yy490; - if (yych == 'f') goto yy490; - goto yy358; - } - } -yy465: - yych = *++p; - if (yych == 'K') goto yy491; - if (yych == 'k') goto yy491; - goto yy358; -yy466: - yych = *++p; - if (yych == 'I') goto yy480; - if (yych == 'i') goto yy480; - goto yy358; -yy467: - yych = *++p; - if (yych == 'E') goto yy492; - if (yych == 'e') goto yy492; - goto yy358; -yy468: - yych = *++p; - if (yych == 'R') goto yy493; - if (yych == 'r') goto yy493; - goto yy358; -yy469: - yych = *++p; - if (yych == 'I') goto yy494; - if (yych == 'i') goto yy494; - goto yy358; -yy470: - yych = *++p; - if (yych == 'O') goto yy495; - if (yych == 'o') goto yy495; - goto yy358; -yy471: - yych = *++p; - if (yych == 'D') goto yy496; - if (yych == 'd') goto yy496; - goto yy358; -yy472: - yych = *++p; - if (yych == 'A') goto yy389; - if (yych == 'a') goto yy389; - goto yy358; -yy473: - yych = *++p; - if (yych == 'R') goto yy463; - if (yych == 'r') goto yy463; - goto yy358; -yy474: - yych = *++p; - if (yych == 'E') goto yy497; - if (yych == 'e') goto yy497; - goto yy358; -yy475: - yych = *++p; - if (yych <= '/') { - if (yych <= 0x1F) { - if (yych <= 0x08) goto yy358; - if (yych <= '\r') goto yy408; - goto yy358; - } else { - if (yych <= ' ') goto yy408; - if (yych <= '.') goto yy358; - goto yy410; - } - } else { - if (yych <= 'D') { - if (yych == '>') goto yy408; - goto yy358; - } else { - if (yych <= 'E') goto yy492; - if (yych == 'e') goto yy492; - goto yy358; - } - } -yy476: - yych = *++p; - if (yych == 'M') goto yy463; - if (yych == 'm') goto yy463; - goto yy358; -yy477: - yych = *++p; - if (yych == 'N') goto yy487; - if (yych == 'n') goto yy487; - goto yy358; -yy478: - yych = *++p; - if (yych <= '/') { - if (yych <= 0x1F) { - if (yych <= 0x08) goto yy358; - if (yych <= '\r') goto yy408; - goto yy358; - } else { - if (yych <= ' ') goto yy408; - if (yych <= '.') goto yy358; - goto yy410; - } - } else { - if (yych <= 'H') { - if (yych == '>') goto yy408; - goto yy358; - } else { - if (yych <= 'I') goto yy498; - if (yych == 'i') goto yy498; - goto yy358; - } - } -yy479: - yych = *++p; - if (yych == 'A') goto yy499; - if (yych == 'a') goto yy499; - goto yy358; -yy480: - yych = *++p; - if (yych == 'O') goto yy446; - if (yych == 'o') goto yy446; - goto yy358; -yy481: - ++p; - { return 1; } -yy483: - yych = *++p; - if (yych == 'P') goto yy500; - if (yych == 'p') goto yy500; - goto yy358; -yy484: - yych = *++p; - if (yych == 'C') goto yy463; - if (yych == 'c') goto yy463; - goto yy358; -yy485: - yych = *++p; - if (yych == 'A') goto yy501; - if (yych == 'a') goto yy501; - goto yy358; -yy486: - yych = *++p; - if (yych == 'T') goto yy392; - if (yych == 't') goto yy392; - goto yy358; -yy487: - yych = *++p; - if (yych == 'D') goto yy392; - if (yych == 'd') goto yy392; - goto yy358; -yy488: - yych = *++p; - if (yych == 'T') goto yy502; - if (yych == 't') goto yy502; - goto yy358; -yy489: - yych = *++p; - if (yych == 'S') goto yy503; - if (yych == 's') goto yy503; - goto yy358; -yy490: - yych = *++p; - if (yych == 'O') goto yy504; - if (yych == 'o') goto yy504; - goto yy358; -yy491: - yych = *++p; - if (yych == 'Q') goto yy505; - if (yych == 'q') goto yy505; - goto yy358; -yy492: - yych = *++p; - if (yych == 'R') goto yy392; - if (yych == 'r') goto yy392; - goto yy358; -yy493: - yych = *++p; - if (yych == 'O') goto yy506; - if (yych == 'o') goto yy506; - goto yy358; -yy494: - yych = *++p; - if (yych == 'L') goto yy503; - if (yych == 'l') goto yy503; - goto yy358; -yy495: - yych = *++p; - if (yych == 'G') goto yy392; - if (yych == 'g') goto yy392; - goto yy358; -yy496: - yych = *++p; - if (yych == 'S') goto yy507; - if (yych == 's') goto yy507; - goto yy358; -yy497: - yych = *++p; - if (yych <= '/') { - if (yych <= 0x1F) { - if (yych <= 0x08) goto yy358; - if (yych <= '\r') goto yy408; - goto yy358; - } else { - if (yych <= ' ') goto yy408; - if (yych <= '.') goto yy358; - goto yy410; - } - } else { - if (yych <= 'R') { - if (yych == '>') goto yy408; - goto yy358; - } else { - if (yych <= 'S') goto yy507; - if (yych == 's') goto yy507; - goto yy358; - } - } -yy498: - yych = *++p; - if (yych == 'T') goto yy508; - if (yych == 't') goto yy508; - goto yy358; -yy499: - yych = *++p; - if (yych == 'M') goto yy509; - if (yych == 'm') goto yy509; - goto yy358; -yy500: - yych = *++p; - if (yych == 'T') goto yy451; - if (yych == 't') goto yy451; - goto yy358; -yy501: - yych = *++p; - if (yych == 'R') goto yy432; - if (yych == 'r') goto yy432; - goto yy358; -yy502: - yych = *++p; - if (yych == 'A') goto yy510; - if (yych == 'a') goto yy510; - goto yy358; -yy503: - yych = *++p; - if (yych == 'S') goto yy392; - if (yych == 's') goto yy392; - goto yy358; -yy504: - yych = *++p; - if (yych == 'N') goto yy486; - if (yych == 'n') goto yy486; - goto yy358; -yy505: - yych = *++p; - if (yych == 'U') goto yy511; - if (yych == 'u') goto yy511; - goto yy358; -yy506: - yych = *++p; - if (yych == 'U') goto yy512; - if (yych == 'u') goto yy512; - goto yy358; -yy507: - yych = *++p; - if (yych == 'E') goto yy486; - if (yych == 'e') goto yy486; - goto yy358; -yy508: - yych = *++p; - if (yych == 'E') goto yy440; - if (yych == 'e') goto yy440; - goto yy358; -yy509: - yych = *++p; - if (yych == 'E') goto yy503; - if (yych == 'e') goto yy503; - goto yy358; -yy510: - yych = *++p; - if (yych == '[') goto yy513; - goto yy358; -yy511: - yych = *++p; - if (yych == 'O') goto yy515; - if (yych == 'o') goto yy515; - goto yy358; -yy512: - yych = *++p; - if (yych == 'P') goto yy392; - if (yych == 'p') goto yy392; - goto yy358; -yy513: - ++p; - { return 5; } -yy515: - yych = *++p; - if (yych == 'T') goto yy463; - if (yych == 't') goto yy463; - goto yy358; -} - + { + unsigned char yych; + yych = *p; + if (yych == '<') + goto yy296; + ++p; + yy295 : { return 0; } + yy296: + yych = *(marker = ++p); + switch (yych) { + case '!': + goto yy297; + case '/': + goto yy299; + case '?': + goto yy300; + case 'A': + case 'a': + goto yy301; + case 'B': + case 'b': + goto yy302; + case 'C': + case 'c': + goto yy303; + case 'D': + case 'd': + goto yy304; + case 'F': + case 'f': + goto yy305; + case 'H': + case 'h': + goto yy306; + case 'I': + case 'i': + goto yy307; + case 'L': + case 'l': + goto yy308; + case 'M': + case 'm': + goto yy309; + case 'N': + case 'n': + goto yy310; + case 'O': + case 'o': + goto yy311; + case 'P': + case 'p': + goto yy312; + case 'S': + case 's': + goto yy313; + case 'T': + case 't': + goto yy314; + case 'U': + case 'u': + goto yy315; + default: + goto yy295; + } + yy297: + yych = *++p; + if (yych <= '@') { + if (yych == '-') + goto yy316; + } else { + if (yych <= 'Z') + goto yy317; + if (yych <= '[') + goto yy318; + } + yy298: + p = marker; + goto yy295; + yy299: + yych = *++p; + switch (yych) { + case 'A': + case 'a': + goto yy301; + case 'B': + case 'b': + goto yy302; + case 'C': + case 'c': + goto yy303; + case 'D': + case 'd': + goto yy304; + case 'F': + case 'f': + goto yy305; + case 'H': + case 'h': + goto yy306; + case 'I': + case 'i': + goto yy307; + case 'L': + case 'l': + goto yy308; + case 'M': + case 'm': + goto yy309; + case 'N': + case 'n': + goto yy310; + case 'O': + case 'o': + goto yy311; + case 'P': + case 'p': + goto yy319; + case 'S': + case 's': + goto yy320; + case 'T': + case 't': + goto yy321; + case 'U': + case 'u': + goto yy315; + default: + goto yy298; + } + yy300: + ++p; + { return 3; } + yy301: + yych = *++p; + if (yych <= 'S') { + if (yych <= 'D') { + if (yych <= 'C') + goto yy298; + goto yy322; + } else { + if (yych <= 'Q') + goto yy298; + if (yych <= 'R') + goto yy323; + goto yy324; + } + } else { + if (yych <= 'q') { + if (yych == 'd') + goto yy322; + goto yy298; + } else { + if (yych <= 'r') + goto yy323; + if (yych <= 's') + goto yy324; + goto yy298; + } + } + yy302: + yych = *++p; + if (yych <= 'O') { + if (yych <= 'K') { + if (yych == 'A') + goto yy325; + goto yy298; + } else { + if (yych <= 'L') + goto yy326; + if (yych <= 'N') + goto yy298; + goto yy327; + } + } else { + if (yych <= 'k') { + if (yych == 'a') + goto yy325; + goto yy298; + } else { + if (yych <= 'l') + goto yy326; + if (yych == 'o') + goto yy327; + goto yy298; + } + } + yy303: + yych = *++p; + if (yych <= 'O') { + if (yych <= 'D') { + if (yych == 'A') + goto yy328; + goto yy298; + } else { + if (yych <= 'E') + goto yy329; + if (yych <= 'N') + goto yy298; + goto yy330; + } + } else { + if (yych <= 'd') { + if (yych == 'a') + goto yy328; + goto yy298; + } else { + if (yych <= 'e') + goto yy329; + if (yych == 'o') + goto yy330; + goto yy298; + } + } + yy304: + yych = *++p; + switch (yych) { + case 'D': + case 'L': + case 'T': + case 'd': + case 'l': + case 't': + goto yy331; + case 'E': + case 'e': + goto yy332; + case 'I': + case 'i': + goto yy333; + default: + goto yy298; + } + yy305: + yych = *++p; + if (yych <= 'R') { + if (yych <= 'N') { + if (yych == 'I') + goto yy334; + goto yy298; + } else { + if (yych <= 'O') + goto yy335; + if (yych <= 'Q') + goto yy298; + goto yy336; + } + } else { + if (yych <= 'n') { + if (yych == 'i') + goto yy334; + goto yy298; + } else { + if (yych <= 'o') + goto yy335; + if (yych == 'r') + goto yy336; + goto yy298; + } + } + yy306: + yych = *++p; + if (yych <= 'S') { + if (yych <= 'D') { + if (yych <= '0') + goto yy298; + if (yych <= '6') + goto yy331; + goto yy298; + } else { + if (yych <= 'E') + goto yy337; + if (yych == 'R') + goto yy331; + goto yy298; + } + } else { + if (yych <= 'q') { + if (yych <= 'T') + goto yy338; + if (yych == 'e') + goto yy337; + goto yy298; + } else { + if (yych <= 'r') + goto yy331; + if (yych == 't') + goto yy338; + goto yy298; + } + } + yy307: + yych = *++p; + if (yych == 'F') + goto yy339; + if (yych == 'f') + goto yy339; + goto yy298; + yy308: + yych = *++p; + if (yych <= 'I') { + if (yych == 'E') + goto yy340; + if (yych <= 'H') + goto yy298; + goto yy341; + } else { + if (yych <= 'e') { + if (yych <= 'd') + goto yy298; + goto yy340; + } else { + if (yych == 'i') + goto yy341; + goto yy298; + } + } + yy309: + yych = *++p; + if (yych <= 'E') { + if (yych == 'A') + goto yy342; + if (yych <= 'D') + goto yy298; + goto yy343; + } else { + if (yych <= 'a') { + if (yych <= '`') + goto yy298; + goto yy342; + } else { + if (yych == 'e') + goto yy343; + goto yy298; + } + } + yy310: + yych = *++p; + if (yych <= 'O') { + if (yych == 'A') + goto yy344; + if (yych <= 'N') + goto yy298; + goto yy345; + } else { + if (yych <= 'a') { + if (yych <= '`') + goto yy298; + goto yy344; + } else { + if (yych == 'o') + goto yy345; + goto yy298; + } + } + yy311: + yych = *++p; + if (yych <= 'P') { + if (yych == 'L') + goto yy331; + if (yych <= 'O') + goto yy298; + goto yy346; + } else { + if (yych <= 'l') { + if (yych <= 'k') + goto yy298; + goto yy331; + } else { + if (yych == 'p') + goto yy346; + goto yy298; + } + } + yy312: + yych = *++p; + if (yych <= '>') { + if (yych <= ' ') { + if (yych <= 0x08) + goto yy298; + if (yych <= '\r') + goto yy347; + if (yych <= 0x1F) + goto yy298; + goto yy347; + } else { + if (yych == '/') + goto yy348; + if (yych <= '=') + goto yy298; + goto yy347; + } + } else { + if (yych <= 'R') { + if (yych == 'A') + goto yy349; + if (yych <= 'Q') + goto yy298; + goto yy350; + } else { + if (yych <= 'a') { + if (yych <= '`') + goto yy298; + goto yy349; + } else { + if (yych == 'r') + goto yy350; + goto yy298; + } + } + } + yy313: + yych = *++p; + switch (yych) { + case 'C': + case 'c': + goto yy351; + case 'E': + case 'e': + goto yy352; + case 'O': + case 'o': + goto yy353; + case 'T': + case 't': + goto yy354; + case 'U': + case 'u': + goto yy355; + default: + goto yy298; + } + yy314: + yych = *++p; + switch (yych) { + case 'A': + case 'a': + goto yy356; + case 'B': + case 'b': + goto yy357; + case 'D': + case 'd': + goto yy331; + case 'E': + case 'e': + goto yy358; + case 'F': + case 'f': + goto yy359; + case 'H': + case 'h': + goto yy360; + case 'I': + case 'i': + goto yy361; + case 'R': + case 'r': + goto yy362; + default: + goto yy298; + } + yy315: + yych = *++p; + if (yych == 'L') + goto yy331; + if (yych == 'l') + goto yy331; + goto yy298; + yy316: + yych = *++p; + if (yych == '-') + goto yy363; + goto yy298; + yy317: + ++p; + { return 4; } + yy318: + yych = *++p; + if (yych == 'C') + goto yy364; + if (yych == 'c') + goto yy364; + goto yy298; + yy319: + yych = *++p; + if (yych <= '/') { + if (yych <= 0x1F) { + if (yych <= 0x08) + goto yy298; + if (yych <= '\r') + goto yy347; + goto yy298; + } else { + if (yych <= ' ') + goto yy347; + if (yych <= '.') + goto yy298; + goto yy348; + } + } else { + if (yych <= '@') { + if (yych == '>') + goto yy347; + goto yy298; + } else { + if (yych <= 'A') + goto yy349; + if (yych == 'a') + goto yy349; + goto yy298; + } + } + yy320: + yych = *++p; + if (yych <= 'U') { + if (yych <= 'N') { + if (yych == 'E') + goto yy352; + goto yy298; + } else { + if (yych <= 'O') + goto yy353; + if (yych <= 'T') + goto yy298; + goto yy355; + } + } else { + if (yych <= 'n') { + if (yych == 'e') + goto yy352; + goto yy298; + } else { + if (yych <= 'o') + goto yy353; + if (yych == 'u') + goto yy355; + goto yy298; + } + } + yy321: + yych = *++p; + switch (yych) { + case 'A': + case 'a': + goto yy356; + case 'B': + case 'b': + goto yy357; + case 'D': + case 'd': + goto yy331; + case 'F': + case 'f': + goto yy359; + case 'H': + case 'h': + goto yy360; + case 'I': + case 'i': + goto yy361; + case 'R': + case 'r': + goto yy362; + default: + goto yy298; + } + yy322: + yych = *++p; + if (yych == 'D') + goto yy365; + if (yych == 'd') + goto yy365; + goto yy298; + yy323: + yych = *++p; + if (yych == 'T') + goto yy366; + if (yych == 't') + goto yy366; + goto yy298; + yy324: + yych = *++p; + if (yych == 'I') + goto yy367; + if (yych == 'i') + goto yy367; + goto yy298; + yy325: + yych = *++p; + if (yych == 'S') + goto yy368; + if (yych == 's') + goto yy368; + goto yy298; + yy326: + yych = *++p; + if (yych == 'O') + goto yy369; + if (yych == 'o') + goto yy369; + goto yy298; + yy327: + yych = *++p; + if (yych == 'D') + goto yy370; + if (yych == 'd') + goto yy370; + goto yy298; + yy328: + yych = *++p; + if (yych == 'P') + goto yy371; + if (yych == 'p') + goto yy371; + goto yy298; + yy329: + yych = *++p; + if (yych == 'N') + goto yy372; + if (yych == 'n') + goto yy372; + goto yy298; + yy330: + yych = *++p; + if (yych == 'L') + goto yy373; + if (yych == 'l') + goto yy373; + goto yy298; + yy331: + yych = *++p; + if (yych <= ' ') { + if (yych <= 0x08) + goto yy298; + if (yych <= '\r') + goto yy347; + if (yych <= 0x1F) + goto yy298; + goto yy347; + } else { + if (yych <= '/') { + if (yych <= '.') + goto yy298; + goto yy348; + } else { + if (yych == '>') + goto yy347; + goto yy298; + } + } + yy332: + yych = *++p; + if (yych == 'T') + goto yy374; + if (yych == 't') + goto yy374; + goto yy298; + yy333: + yych = *++p; + if (yych <= 'V') { + if (yych <= 'Q') { + if (yych == 'A') + goto yy375; + goto yy298; + } else { + if (yych <= 'R') + goto yy331; + if (yych <= 'U') + goto yy298; + goto yy331; + } + } else { + if (yych <= 'q') { + if (yych == 'a') + goto yy375; + goto yy298; + } else { + if (yych <= 'r') + goto yy331; + if (yych == 'v') + goto yy331; + goto yy298; + } + } + yy334: + yych = *++p; + if (yych <= 'G') { + if (yych == 'E') + goto yy376; + if (yych <= 'F') + goto yy298; + goto yy377; + } else { + if (yych <= 'e') { + if (yych <= 'd') + goto yy298; + goto yy376; + } else { + if (yych == 'g') + goto yy377; + goto yy298; + } + } + yy335: + yych = *++p; + if (yych <= 'R') { + if (yych == 'O') + goto yy372; + if (yych <= 'Q') + goto yy298; + goto yy378; + } else { + if (yych <= 'o') { + if (yych <= 'n') + goto yy298; + goto yy372; + } else { + if (yych == 'r') + goto yy378; + goto yy298; + } + } + yy336: + yych = *++p; + if (yych == 'A') + goto yy379; + if (yych == 'a') + goto yy379; + goto yy298; + yy337: + yych = *++p; + if (yych == 'A') + goto yy380; + if (yych == 'a') + goto yy380; + goto yy298; + yy338: + yych = *++p; + if (yych == 'M') + goto yy315; + if (yych == 'm') + goto yy315; + goto yy298; + yy339: + yych = *++p; + if (yych == 'R') + goto yy381; + if (yych == 'r') + goto yy381; + goto yy298; + yy340: + yych = *++p; + if (yych == 'G') + goto yy382; + if (yych == 'g') + goto yy382; + goto yy298; + yy341: + yych = *++p; + if (yych <= '/') { + if (yych <= 0x1F) { + if (yych <= 0x08) + goto yy298; + if (yych <= '\r') + goto yy347; + goto yy298; + } else { + if (yych <= ' ') + goto yy347; + if (yych <= '.') + goto yy298; + goto yy348; + } + } else { + if (yych <= 'M') { + if (yych == '>') + goto yy347; + goto yy298; + } else { + if (yych <= 'N') + goto yy383; + if (yych == 'n') + goto yy383; + goto yy298; + } + } + yy342: + yych = *++p; + if (yych == 'I') + goto yy384; + if (yych == 'i') + goto yy384; + goto yy298; + yy343: + yych = *++p; + if (yych == 'N') + goto yy385; + if (yych == 'n') + goto yy385; + goto yy298; + yy344: + yych = *++p; + if (yych == 'V') + goto yy331; + if (yych == 'v') + goto yy331; + goto yy298; + yy345: + yych = *++p; + if (yych == 'F') + goto yy386; + if (yych == 'f') + goto yy386; + goto yy298; + yy346: + yych = *++p; + if (yych == 'T') + goto yy387; + if (yych == 't') + goto yy387; + goto yy298; + yy347: + ++p; + { return 6; } + yy348: + yych = *++p; + if (yych == '>') + goto yy347; + goto yy298; + yy349: + yych = *++p; + if (yych == 'R') + goto yy388; + if (yych == 'r') + goto yy388; + goto yy298; + yy350: + yych = *++p; + if (yych == 'E') + goto yy389; + if (yych == 'e') + goto yy389; + goto yy298; + yy351: + yych = *++p; + if (yych == 'R') + goto yy390; + if (yych == 'r') + goto yy390; + goto yy298; + yy352: + yych = *++p; + if (yych == 'C') + goto yy371; + if (yych == 'c') + goto yy371; + goto yy298; + yy353: + yych = *++p; + if (yych == 'U') + goto yy391; + if (yych == 'u') + goto yy391; + goto yy298; + yy354: + yych = *++p; + if (yych == 'Y') + goto yy392; + if (yych == 'y') + goto yy392; + goto yy298; + yy355: + yych = *++p; + if (yych == 'M') + goto yy393; + if (yych == 'm') + goto yy393; + goto yy298; + yy356: + yych = *++p; + if (yych == 'B') + goto yy394; + if (yych == 'b') + goto yy394; + goto yy298; + yy357: + yych = *++p; + if (yych == 'O') + goto yy327; + if (yych == 'o') + goto yy327; + goto yy298; + yy358: + yych = *++p; + if (yych == 'X') + goto yy395; + if (yych == 'x') + goto yy395; + goto yy298; + yy359: + yych = *++p; + if (yych == 'O') + goto yy396; + if (yych == 'o') + goto yy396; + goto yy298; + yy360: + yych = *++p; + if (yych <= '/') { + if (yych <= 0x1F) { + if (yych <= 0x08) + goto yy298; + if (yych <= '\r') + goto yy347; + goto yy298; + } else { + if (yych <= ' ') + goto yy347; + if (yych <= '.') + goto yy298; + goto yy348; + } + } else { + if (yych <= 'D') { + if (yych == '>') + goto yy347; + goto yy298; + } else { + if (yych <= 'E') + goto yy397; + if (yych == 'e') + goto yy397; + goto yy298; + } + } + yy361: + yych = *++p; + if (yych == 'T') + goto yy394; + if (yych == 't') + goto yy394; + goto yy298; + yy362: + yych = *++p; + if (yych <= '/') { + if (yych <= 0x1F) { + if (yych <= 0x08) + goto yy298; + if (yych <= '\r') + goto yy347; + goto yy298; + } else { + if (yych <= ' ') + goto yy347; + if (yych <= '.') + goto yy298; + goto yy348; + } + } else { + if (yych <= '@') { + if (yych == '>') + goto yy347; + goto yy298; + } else { + if (yych <= 'A') + goto yy398; + if (yych == 'a') + goto yy398; + goto yy298; + } + } + yy363: + ++p; + { return 2; } + yy364: + yych = *++p; + if (yych == 'D') + goto yy399; + if (yych == 'd') + goto yy399; + goto yy298; + yy365: + yych = *++p; + if (yych == 'R') + goto yy400; + if (yych == 'r') + goto yy400; + goto yy298; + yy366: + yych = *++p; + if (yych == 'I') + goto yy401; + if (yych == 'i') + goto yy401; + goto yy298; + yy367: + yych = *++p; + if (yych == 'D') + goto yy402; + if (yych == 'd') + goto yy402; + goto yy298; + yy368: + yych = *++p; + if (yych == 'E') + goto yy403; + if (yych == 'e') + goto yy403; + goto yy298; + yy369: + yych = *++p; + if (yych == 'C') + goto yy404; + if (yych == 'c') + goto yy404; + goto yy298; + yy370: + yych = *++p; + if (yych == 'Y') + goto yy331; + if (yych == 'y') + goto yy331; + goto yy298; + yy371: + yych = *++p; + if (yych == 'T') + goto yy405; + if (yych == 't') + goto yy405; + goto yy298; + yy372: + yych = *++p; + if (yych == 'T') + goto yy406; + if (yych == 't') + goto yy406; + goto yy298; + yy373: + yych = *++p; + if (yych <= '/') { + if (yych <= 0x1F) { + if (yych <= 0x08) + goto yy298; + if (yych <= '\r') + goto yy347; + goto yy298; + } else { + if (yych <= ' ') + goto yy347; + if (yych <= '.') + goto yy298; + goto yy348; + } + } else { + if (yych <= 'F') { + if (yych == '>') + goto yy347; + goto yy298; + } else { + if (yych <= 'G') + goto yy407; + if (yych == 'g') + goto yy407; + goto yy298; + } + } + yy374: + yych = *++p; + if (yych == 'A') + goto yy408; + if (yych == 'a') + goto yy408; + goto yy298; + yy375: + yych = *++p; + if (yych == 'L') + goto yy409; + if (yych == 'l') + goto yy409; + goto yy298; + yy376: + yych = *++p; + if (yych == 'L') + goto yy410; + if (yych == 'l') + goto yy410; + goto yy298; + yy377: + yych = *++p; + if (yych <= 'U') { + if (yych == 'C') + goto yy411; + if (yych <= 'T') + goto yy298; + goto yy412; + } else { + if (yych <= 'c') { + if (yych <= 'b') + goto yy298; + goto yy411; + } else { + if (yych == 'u') + goto yy412; + goto yy298; + } + } + yy378: + yych = *++p; + if (yych == 'M') + goto yy331; + if (yych == 'm') + goto yy331; + goto yy298; + yy379: + yych = *++p; + if (yych == 'M') + goto yy413; + if (yych == 'm') + goto yy413; + goto yy298; + yy380: + yych = *++p; + if (yych == 'D') + goto yy414; + if (yych == 'd') + goto yy414; + goto yy298; + yy381: + yych = *++p; + if (yych == 'A') + goto yy415; + if (yych == 'a') + goto yy415; + goto yy298; + yy382: + yych = *++p; + if (yych == 'E') + goto yy416; + if (yych == 'e') + goto yy416; + goto yy298; + yy383: + yych = *++p; + if (yych == 'K') + goto yy331; + if (yych == 'k') + goto yy331; + goto yy298; + yy384: + yych = *++p; + if (yych == 'N') + goto yy331; + if (yych == 'n') + goto yy331; + goto yy298; + yy385: + yych = *++p; + if (yych == 'U') + goto yy417; + if (yych == 'u') + goto yy417; + goto yy298; + yy386: + yych = *++p; + if (yych == 'R') + goto yy418; + if (yych == 'r') + goto yy418; + goto yy298; + yy387: + yych = *++p; + if (yych <= 'I') { + if (yych == 'G') + goto yy407; + if (yych <= 'H') + goto yy298; + goto yy419; + } else { + if (yych <= 'g') { + if (yych <= 'f') + goto yy298; + goto yy407; + } else { + if (yych == 'i') + goto yy419; + goto yy298; + } + } + yy388: + yych = *++p; + if (yych == 'A') + goto yy378; + if (yych == 'a') + goto yy378; + goto yy298; + yy389: + yych = *++p; + if (yych <= 0x1F) { + if (yych <= 0x08) + goto yy298; + if (yych <= '\r') + goto yy420; + goto yy298; + } else { + if (yych <= ' ') + goto yy420; + if (yych == '>') + goto yy420; + goto yy298; + } + yy390: + yych = *++p; + if (yych == 'I') + goto yy421; + if (yych == 'i') + goto yy421; + goto yy298; + yy391: + yych = *++p; + if (yych == 'R') + goto yy422; + if (yych == 'r') + goto yy422; + goto yy298; + yy392: + yych = *++p; + if (yych == 'L') + goto yy350; + if (yych == 'l') + goto yy350; + goto yy298; + yy393: + yych = *++p; + if (yych == 'M') + goto yy423; + if (yych == 'm') + goto yy423; + goto yy298; + yy394: + yych = *++p; + if (yych == 'L') + goto yy402; + if (yych == 'l') + goto yy402; + goto yy298; + yy395: + yych = *++p; + if (yych == 'T') + goto yy424; + if (yych == 't') + goto yy424; + goto yy298; + yy396: + yych = *++p; + if (yych == 'O') + goto yy425; + if (yych == 'o') + goto yy425; + goto yy298; + yy397: + yych = *++p; + if (yych == 'A') + goto yy426; + if (yych == 'a') + goto yy426; + goto yy298; + yy398: + yych = *++p; + if (yych == 'C') + goto yy383; + if (yych == 'c') + goto yy383; + goto yy298; + yy399: + yych = *++p; + if (yych == 'A') + goto yy427; + if (yych == 'a') + goto yy427; + goto yy298; + yy400: + yych = *++p; + if (yych == 'E') + goto yy428; + if (yych == 'e') + goto yy428; + goto yy298; + yy401: + yych = *++p; + if (yych == 'C') + goto yy394; + if (yych == 'c') + goto yy394; + goto yy298; + yy402: + yych = *++p; + if (yych == 'E') + goto yy331; + if (yych == 'e') + goto yy331; + goto yy298; + yy403: + yych = *++p; + if (yych <= '/') { + if (yych <= 0x1F) { + if (yych <= 0x08) + goto yy298; + if (yych <= '\r') + goto yy347; + goto yy298; + } else { + if (yych <= ' ') + goto yy347; + if (yych <= '.') + goto yy298; + goto yy348; + } + } else { + if (yych <= 'E') { + if (yych == '>') + goto yy347; + goto yy298; + } else { + if (yych <= 'F') + goto yy429; + if (yych == 'f') + goto yy429; + goto yy298; + } + } + yy404: + yych = *++p; + if (yych == 'K') + goto yy430; + if (yych == 'k') + goto yy430; + goto yy298; + yy405: + yych = *++p; + if (yych == 'I') + goto yy419; + if (yych == 'i') + goto yy419; + goto yy298; + yy406: + yych = *++p; + if (yych == 'E') + goto yy431; + if (yych == 'e') + goto yy431; + goto yy298; + yy407: + yych = *++p; + if (yych == 'R') + goto yy432; + if (yych == 'r') + goto yy432; + goto yy298; + yy408: + yych = *++p; + if (yych == 'I') + goto yy433; + if (yych == 'i') + goto yy433; + goto yy298; + yy409: + yych = *++p; + if (yych == 'O') + goto yy434; + if (yych == 'o') + goto yy434; + goto yy298; + yy410: + yych = *++p; + if (yych == 'D') + goto yy435; + if (yych == 'd') + goto yy435; + goto yy298; + yy411: + yych = *++p; + if (yych == 'A') + goto yy328; + if (yych == 'a') + goto yy328; + goto yy298; + yy412: + yych = *++p; + if (yych == 'R') + goto yy402; + if (yych == 'r') + goto yy402; + goto yy298; + yy413: + yych = *++p; + if (yych == 'E') + goto yy436; + if (yych == 'e') + goto yy436; + goto yy298; + yy414: + yych = *++p; + if (yych <= '/') { + if (yych <= 0x1F) { + if (yych <= 0x08) + goto yy298; + if (yych <= '\r') + goto yy347; + goto yy298; + } else { + if (yych <= ' ') + goto yy347; + if (yych <= '.') + goto yy298; + goto yy348; + } + } else { + if (yych <= 'D') { + if (yych == '>') + goto yy347; + goto yy298; + } else { + if (yych <= 'E') + goto yy431; + if (yych == 'e') + goto yy431; + goto yy298; + } + } + yy415: + yych = *++p; + if (yych == 'M') + goto yy402; + if (yych == 'm') + goto yy402; + goto yy298; + yy416: + yych = *++p; + if (yych == 'N') + goto yy426; + if (yych == 'n') + goto yy426; + goto yy298; + yy417: + yych = *++p; + if (yych <= '/') { + if (yych <= 0x1F) { + if (yych <= 0x08) + goto yy298; + if (yych <= '\r') + goto yy347; + goto yy298; + } else { + if (yych <= ' ') + goto yy347; + if (yych <= '.') + goto yy298; + goto yy348; + } + } else { + if (yych <= 'H') { + if (yych == '>') + goto yy347; + goto yy298; + } else { + if (yych <= 'I') + goto yy437; + if (yych == 'i') + goto yy437; + goto yy298; + } + } + yy418: + yych = *++p; + if (yych == 'A') + goto yy438; + if (yych == 'a') + goto yy438; + goto yy298; + yy419: + yych = *++p; + if (yych == 'O') + goto yy384; + if (yych == 'o') + goto yy384; + goto yy298; + yy420: + ++p; + { return 1; } + yy421: + yych = *++p; + if (yych == 'P') + goto yy439; + if (yych == 'p') + goto yy439; + goto yy298; + yy422: + yych = *++p; + if (yych == 'C') + goto yy402; + if (yych == 'c') + goto yy402; + goto yy298; + yy423: + yych = *++p; + if (yych == 'A') + goto yy440; + if (yych == 'a') + goto yy440; + goto yy298; + yy424: + yych = *++p; + if (yych == 'A') + goto yy441; + if (yych == 'a') + goto yy441; + goto yy298; + yy425: + yych = *++p; + if (yych == 'T') + goto yy331; + if (yych == 't') + goto yy331; + goto yy298; + yy426: + yych = *++p; + if (yych == 'D') + goto yy331; + if (yych == 'd') + goto yy331; + goto yy298; + yy427: + yych = *++p; + if (yych == 'T') + goto yy442; + if (yych == 't') + goto yy442; + goto yy298; + yy428: + yych = *++p; + if (yych == 'S') + goto yy443; + if (yych == 's') + goto yy443; + goto yy298; + yy429: + yych = *++p; + if (yych == 'O') + goto yy444; + if (yych == 'o') + goto yy444; + goto yy298; + yy430: + yych = *++p; + if (yych == 'Q') + goto yy445; + if (yych == 'q') + goto yy445; + goto yy298; + yy431: + yych = *++p; + if (yych == 'R') + goto yy331; + if (yych == 'r') + goto yy331; + goto yy298; + yy432: + yych = *++p; + if (yych == 'O') + goto yy446; + if (yych == 'o') + goto yy446; + goto yy298; + yy433: + yych = *++p; + if (yych == 'L') + goto yy443; + if (yych == 'l') + goto yy443; + goto yy298; + yy434: + yych = *++p; + if (yych == 'G') + goto yy331; + if (yych == 'g') + goto yy331; + goto yy298; + yy435: + yych = *++p; + if (yych == 'S') + goto yy447; + if (yych == 's') + goto yy447; + goto yy298; + yy436: + yych = *++p; + if (yych <= '/') { + if (yych <= 0x1F) { + if (yych <= 0x08) + goto yy298; + if (yych <= '\r') + goto yy347; + goto yy298; + } else { + if (yych <= ' ') + goto yy347; + if (yych <= '.') + goto yy298; + goto yy348; + } + } else { + if (yych <= 'R') { + if (yych == '>') + goto yy347; + goto yy298; + } else { + if (yych <= 'S') + goto yy447; + if (yych == 's') + goto yy447; + goto yy298; + } + } + yy437: + yych = *++p; + if (yych == 'T') + goto yy448; + if (yych == 't') + goto yy448; + goto yy298; + yy438: + yych = *++p; + if (yych == 'M') + goto yy449; + if (yych == 'm') + goto yy449; + goto yy298; + yy439: + yych = *++p; + if (yych == 'T') + goto yy389; + if (yych == 't') + goto yy389; + goto yy298; + yy440: + yych = *++p; + if (yych == 'R') + goto yy370; + if (yych == 'r') + goto yy370; + goto yy298; + yy441: + yych = *++p; + if (yych == 'R') + goto yy450; + if (yych == 'r') + goto yy450; + goto yy298; + yy442: + yych = *++p; + if (yych == 'A') + goto yy451; + if (yych == 'a') + goto yy451; + goto yy298; + yy443: + yych = *++p; + if (yych == 'S') + goto yy331; + if (yych == 's') + goto yy331; + goto yy298; + yy444: + yych = *++p; + if (yych == 'N') + goto yy425; + if (yych == 'n') + goto yy425; + goto yy298; + yy445: + yych = *++p; + if (yych == 'U') + goto yy452; + if (yych == 'u') + goto yy452; + goto yy298; + yy446: + yych = *++p; + if (yych == 'U') + goto yy453; + if (yych == 'u') + goto yy453; + goto yy298; + yy447: + yych = *++p; + if (yych == 'E') + goto yy425; + if (yych == 'e') + goto yy425; + goto yy298; + yy448: + yych = *++p; + if (yych == 'E') + goto yy378; + if (yych == 'e') + goto yy378; + goto yy298; + yy449: + yych = *++p; + if (yych == 'E') + goto yy443; + if (yych == 'e') + goto yy443; + goto yy298; + yy450: + yych = *++p; + if (yych == 'E') + goto yy454; + if (yych == 'e') + goto yy454; + goto yy298; + yy451: + yych = *++p; + if (yych == '[') + goto yy455; + goto yy298; + yy452: + yych = *++p; + if (yych == 'O') + goto yy456; + if (yych == 'o') + goto yy456; + goto yy298; + yy453: + yych = *++p; + if (yych == 'P') + goto yy331; + if (yych == 'p') + goto yy331; + goto yy298; + yy454: + yych = *++p; + if (yych == 'A') + goto yy389; + if (yych == 'a') + goto yy389; + goto yy298; + yy455: + ++p; + { return 5; } + yy456: + yych = *++p; + if (yych == 'T') + goto yy402; + if (yych == 't') + goto yy402; + goto yy298; + } } // Try to match an HTML block tag start line of type 7, returning // 7 if successful, 0 if not. -bufsize_t _scan_html_block_start_7(const unsigned char *p) -{ +bufsize_t _scan_html_block_start_7(const unsigned char *p) { const unsigned char *marker = NULL; -{ - unsigned char yych; - unsigned int yyaccept = 0; - static const unsigned char yybm[] = { - 0, 224, 224, 224, 224, 224, 224, 224, - 224, 198, 210, 194, 198, 194, 224, 224, - 224, 224, 224, 224, 224, 224, 224, 224, - 224, 224, 224, 224, 224, 224, 224, 224, - 198, 224, 128, 224, 224, 224, 224, 64, - 224, 224, 224, 224, 224, 233, 232, 224, - 233, 233, 233, 233, 233, 233, 233, 233, - 233, 233, 232, 224, 192, 192, 192, 224, - 224, 233, 233, 233, 233, 233, 233, 233, - 233, 233, 233, 233, 233, 233, 233, 233, - 233, 233, 233, 233, 233, 233, 233, 233, - 233, 233, 233, 224, 224, 224, 224, 232, - 192, 233, 233, 233, 233, 233, 233, 233, - 233, 233, 233, 233, 233, 233, 233, 233, - 233, 233, 233, 233, 233, 233, 233, 233, - 233, 233, 233, 224, 224, 224, 224, 224, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - }; - yych = *p; - if (yych == '<') goto yy520; - ++p; -yy519: - { return 0; } -yy520: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= '@') { - if (yych != '/') goto yy519; - } else { - if (yych <= 'Z') goto yy523; - if (yych <= '`') goto yy519; - if (yych <= 'z') goto yy523; - goto yy519; - } - yych = *++p; - if (yych <= '@') goto yy522; - if (yych <= 'Z') goto yy525; - if (yych <= '`') goto yy522; - if (yych <= 'z') goto yy525; -yy522: - p = marker; - if (yyaccept == 0) { - goto yy519; - } else { - goto yy538; - } -yy523: - yych = *++p; - if (yybm[0+yych] & 2) { - goto yy527; - } - if (yych <= '=') { - if (yych <= '.') { - if (yych == '-') goto yy523; - goto yy522; - } else { - if (yych <= '/') goto yy529; - if (yych <= '9') goto yy523; - goto yy522; - } - } else { - if (yych <= 'Z') { - if (yych <= '>') goto yy530; - if (yych <= '@') goto yy522; - goto yy523; - } else { - if (yych <= '`') goto yy522; - if (yych <= 'z') goto yy523; - goto yy522; - } - } -yy525: - yych = *++p; - if (yych <= '/') { - if (yych <= 0x1F) { - if (yych <= 0x08) goto yy522; - if (yych <= '\r') goto yy532; - goto yy522; - } else { - if (yych <= ' ') goto yy532; - if (yych == '-') goto yy525; - goto yy522; - } - } else { - if (yych <= '@') { - if (yych <= '9') goto yy525; - if (yych == '>') goto yy530; - goto yy522; - } else { - if (yych <= 'Z') goto yy525; - if (yych <= '`') goto yy522; - if (yych <= 'z') goto yy525; - goto yy522; - } - } -yy527: - yych = *++p; - if (yybm[0+yych] & 2) { - goto yy527; - } - if (yych <= '>') { - if (yych <= '9') { - if (yych != '/') goto yy522; - } else { - if (yych <= ':') goto yy534; - if (yych <= '=') goto yy522; - goto yy530; - } - } else { - if (yych <= '^') { - if (yych <= '@') goto yy522; - if (yych <= 'Z') goto yy534; - goto yy522; - } else { - if (yych == '`') goto yy522; - if (yych <= 'z') goto yy534; - goto yy522; - } - } -yy529: - yych = *++p; - if (yych != '>') goto yy522; -yy530: - yych = *++p; - if (yybm[0+yych] & 4) { - goto yy530; - } - if (yych <= 0x08) goto yy522; - if (yych <= '\n') goto yy536; - if (yych <= '\v') goto yy522; - if (yych <= '\r') goto yy539; - goto yy522; -yy532: - yych = *++p; - if (yych <= 0x1F) { - if (yych <= 0x08) goto yy522; - if (yych <= '\r') goto yy532; - goto yy522; - } else { - if (yych <= ' ') goto yy532; - if (yych == '>') goto yy530; - goto yy522; - } -yy534: - yych = *++p; - if (yybm[0+yych] & 8) { - goto yy534; - } - if (yych <= ',') { - if (yych <= '\r') { - if (yych <= 0x08) goto yy522; - goto yy540; - } else { - if (yych == ' ') goto yy540; - goto yy522; - } - } else { - if (yych <= '<') { - if (yych <= '/') goto yy529; - goto yy522; - } else { - if (yych <= '=') goto yy542; - if (yych <= '>') goto yy530; - goto yy522; - } - } -yy536: - yyaccept = 1; - yych = *(marker = ++p); - if (yybm[0+yych] & 4) { - goto yy530; - } - if (yych <= 0x08) goto yy538; - if (yych <= '\n') goto yy536; - if (yych <= '\v') goto yy538; - if (yych <= '\r') goto yy539; -yy538: - { return 7; } -yy539: - ++p; - goto yy538; -yy540: - yych = *++p; - if (yych <= '<') { - if (yych <= ' ') { - if (yych <= 0x08) goto yy522; - if (yych <= '\r') goto yy540; - if (yych <= 0x1F) goto yy522; - goto yy540; - } else { - if (yych <= '/') { - if (yych <= '.') goto yy522; - goto yy529; - } else { - if (yych == ':') goto yy534; - goto yy522; - } - } - } else { - if (yych <= 'Z') { - if (yych <= '=') goto yy542; - if (yych <= '>') goto yy530; - if (yych <= '@') goto yy522; - goto yy534; - } else { - if (yych <= '_') { - if (yych <= '^') goto yy522; - goto yy534; - } else { - if (yych <= '`') goto yy522; - if (yych <= 'z') goto yy534; - goto yy522; - } - } - } -yy542: - yych = *++p; - if (yybm[0+yych] & 32) { - goto yy544; - } - if (yych <= 0xE0) { - if (yych <= '"') { - if (yych <= 0x00) goto yy522; - if (yych <= ' ') goto yy542; - goto yy546; - } else { - if (yych <= '\'') goto yy548; - if (yych <= 0xC1) goto yy522; - if (yych <= 0xDF) goto yy550; - goto yy551; - } - } else { - if (yych <= 0xEF) { - if (yych == 0xED) goto yy553; - goto yy552; - } else { - if (yych <= 0xF0) goto yy554; - if (yych <= 0xF3) goto yy555; - if (yych <= 0xF4) goto yy556; - goto yy522; - } - } -yy544: - yych = *++p; - if (yybm[0+yych] & 32) { - goto yy544; - } - if (yych <= 0xE0) { - if (yych <= '=') { - if (yych <= 0x00) goto yy522; - if (yych <= ' ') goto yy527; - goto yy522; - } else { - if (yych <= '>') goto yy530; - if (yych <= 0xC1) goto yy522; - if (yych <= 0xDF) goto yy550; - goto yy551; - } - } else { - if (yych <= 0xEF) { - if (yych == 0xED) goto yy553; - goto yy552; - } else { - if (yych <= 0xF0) goto yy554; - if (yych <= 0xF3) goto yy555; - if (yych <= 0xF4) goto yy556; - goto yy522; - } - } -yy546: - yych = *++p; - if (yybm[0+yych] & 64) { - goto yy546; - } - if (yych <= 0xEC) { - if (yych <= 0xC1) { - if (yych <= 0x00) goto yy522; - if (yych <= '"') goto yy557; - goto yy522; - } else { - if (yych <= 0xDF) goto yy558; - if (yych <= 0xE0) goto yy559; - goto yy560; - } - } else { - if (yych <= 0xF0) { - if (yych <= 0xED) goto yy561; - if (yych <= 0xEF) goto yy560; - goto yy562; - } else { - if (yych <= 0xF3) goto yy563; - if (yych <= 0xF4) goto yy564; - goto yy522; - } - } -yy548: - yych = *++p; - if (yybm[0+yych] & 128) { - goto yy548; - } - if (yych <= 0xEC) { - if (yych <= 0xC1) { - if (yych <= 0x00) goto yy522; - if (yych <= '\'') goto yy557; - goto yy522; - } else { - if (yych <= 0xDF) goto yy565; - if (yych <= 0xE0) goto yy566; - goto yy567; - } - } else { - if (yych <= 0xF0) { - if (yych <= 0xED) goto yy568; - if (yych <= 0xEF) goto yy567; - goto yy569; - } else { - if (yych <= 0xF3) goto yy570; - if (yych <= 0xF4) goto yy571; - goto yy522; - } - } -yy550: - yych = *++p; - if (yych <= 0x7F) goto yy522; - if (yych <= 0xBF) goto yy544; - goto yy522; -yy551: - yych = *++p; - if (yych <= 0x9F) goto yy522; - if (yych <= 0xBF) goto yy550; - goto yy522; -yy552: - yych = *++p; - if (yych <= 0x7F) goto yy522; - if (yych <= 0xBF) goto yy550; - goto yy522; -yy553: - yych = *++p; - if (yych <= 0x7F) goto yy522; - if (yych <= 0x9F) goto yy550; - goto yy522; -yy554: - yych = *++p; - if (yych <= 0x8F) goto yy522; - if (yych <= 0xBF) goto yy552; - goto yy522; -yy555: - yych = *++p; - if (yych <= 0x7F) goto yy522; - if (yych <= 0xBF) goto yy552; - goto yy522; -yy556: - yych = *++p; - if (yych <= 0x7F) goto yy522; - if (yych <= 0x8F) goto yy552; - goto yy522; -yy557: - yych = *++p; - if (yybm[0+yych] & 2) { - goto yy527; - } - if (yych == '/') goto yy529; - if (yych == '>') goto yy530; - goto yy522; -yy558: - yych = *++p; - if (yych <= 0x7F) goto yy522; - if (yych <= 0xBF) goto yy546; - goto yy522; -yy559: - yych = *++p; - if (yych <= 0x9F) goto yy522; - if (yych <= 0xBF) goto yy558; - goto yy522; -yy560: - yych = *++p; - if (yych <= 0x7F) goto yy522; - if (yych <= 0xBF) goto yy558; - goto yy522; -yy561: - yych = *++p; - if (yych <= 0x7F) goto yy522; - if (yych <= 0x9F) goto yy558; - goto yy522; -yy562: - yych = *++p; - if (yych <= 0x8F) goto yy522; - if (yych <= 0xBF) goto yy560; - goto yy522; -yy563: - yych = *++p; - if (yych <= 0x7F) goto yy522; - if (yych <= 0xBF) goto yy560; - goto yy522; -yy564: - yych = *++p; - if (yych <= 0x7F) goto yy522; - if (yych <= 0x8F) goto yy560; - goto yy522; -yy565: - yych = *++p; - if (yych <= 0x7F) goto yy522; - if (yych <= 0xBF) goto yy548; - goto yy522; -yy566: - yych = *++p; - if (yych <= 0x9F) goto yy522; - if (yych <= 0xBF) goto yy565; - goto yy522; -yy567: - yych = *++p; - if (yych <= 0x7F) goto yy522; - if (yych <= 0xBF) goto yy565; - goto yy522; -yy568: - yych = *++p; - if (yych <= 0x7F) goto yy522; - if (yych <= 0x9F) goto yy565; - goto yy522; -yy569: - yych = *++p; - if (yych <= 0x8F) goto yy522; - if (yych <= 0xBF) goto yy567; - goto yy522; -yy570: - yych = *++p; - if (yych <= 0x7F) goto yy522; - if (yych <= 0xBF) goto yy567; - goto yy522; -yy571: - yych = *++p; - if (yych <= 0x7F) goto yy522; - if (yych <= 0x8F) goto yy567; - goto yy522; -} - + { + unsigned char yych; + unsigned int yyaccept = 0; + static const unsigned char yybm[] = { + 0, 224, 224, 224, 224, 224, 224, 224, 224, 198, 210, 194, 198, 194, + 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, + 224, 224, 224, 224, 198, 224, 128, 224, 224, 224, 224, 64, 224, 224, + 224, 224, 224, 233, 232, 224, 233, 233, 233, 233, 233, 233, 233, 233, + 233, 233, 232, 224, 192, 192, 192, 224, 224, 233, 233, 233, 233, 233, + 233, 233, 233, 233, 233, 233, 233, 233, 233, 233, 233, 233, 233, 233, + 233, 233, 233, 233, 233, 233, 233, 224, 224, 224, 224, 232, 192, 233, + 233, 233, 233, 233, 233, 233, 233, 233, 233, 233, 233, 233, 233, 233, + 233, 233, 233, 233, 233, 233, 233, 233, 233, 233, 233, 224, 224, 224, + 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + }; + yych = *p; + if (yych == '<') + goto yy459; + ++p; + yy458 : { return 0; } + yy459: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= '@') { + if (yych != '/') + goto yy458; + } else { + if (yych <= 'Z') + goto yy461; + if (yych <= '`') + goto yy458; + if (yych <= 'z') + goto yy461; + goto yy458; + } + yych = *++p; + if (yych <= '@') + goto yy460; + if (yych <= 'Z') + goto yy462; + if (yych <= '`') + goto yy460; + if (yych <= 'z') + goto yy462; + yy460: + p = marker; + if (yyaccept == 0) { + goto yy458; + } else { + goto yy469; + } + yy461: + yych = *++p; + if (yybm[0 + yych] & 2) { + goto yy463; + } + if (yych <= '=') { + if (yych <= '.') { + if (yych == '-') + goto yy461; + goto yy460; + } else { + if (yych <= '/') + goto yy464; + if (yych <= '9') + goto yy461; + goto yy460; + } + } else { + if (yych <= 'Z') { + if (yych <= '>') + goto yy465; + if (yych <= '@') + goto yy460; + goto yy461; + } else { + if (yych <= '`') + goto yy460; + if (yych <= 'z') + goto yy461; + goto yy460; + } + } + yy462: + yych = *++p; + if (yych <= '/') { + if (yych <= 0x1F) { + if (yych <= 0x08) + goto yy460; + if (yych <= '\r') + goto yy466; + goto yy460; + } else { + if (yych <= ' ') + goto yy466; + if (yych == '-') + goto yy462; + goto yy460; + } + } else { + if (yych <= '@') { + if (yych <= '9') + goto yy462; + if (yych == '>') + goto yy465; + goto yy460; + } else { + if (yych <= 'Z') + goto yy462; + if (yych <= '`') + goto yy460; + if (yych <= 'z') + goto yy462; + goto yy460; + } + } + yy463: + yych = *++p; + if (yybm[0 + yych] & 2) { + goto yy463; + } + if (yych <= '>') { + if (yych <= '9') { + if (yych != '/') + goto yy460; + } else { + if (yych <= ':') + goto yy467; + if (yych <= '=') + goto yy460; + goto yy465; + } + } else { + if (yych <= '^') { + if (yych <= '@') + goto yy460; + if (yych <= 'Z') + goto yy467; + goto yy460; + } else { + if (yych == '`') + goto yy460; + if (yych <= 'z') + goto yy467; + goto yy460; + } + } + yy464: + yych = *++p; + if (yych != '>') + goto yy460; + yy465: + yych = *++p; + if (yybm[0 + yych] & 4) { + goto yy465; + } + if (yych <= 0x08) + goto yy460; + if (yych <= '\n') + goto yy468; + if (yych <= '\v') + goto yy460; + if (yych <= '\r') + goto yy470; + goto yy460; + yy466: + yych = *++p; + if (yych <= 0x1F) { + if (yych <= 0x08) + goto yy460; + if (yych <= '\r') + goto yy466; + goto yy460; + } else { + if (yych <= ' ') + goto yy466; + if (yych == '>') + goto yy465; + goto yy460; + } + yy467: + yych = *++p; + if (yybm[0 + yych] & 8) { + goto yy467; + } + if (yych <= ',') { + if (yych <= '\r') { + if (yych <= 0x08) + goto yy460; + goto yy471; + } else { + if (yych == ' ') + goto yy471; + goto yy460; + } + } else { + if (yych <= '<') { + if (yych <= '/') + goto yy464; + goto yy460; + } else { + if (yych <= '=') + goto yy472; + if (yych <= '>') + goto yy465; + goto yy460; + } + } + yy468: + yyaccept = 1; + yych = *(marker = ++p); + if (yybm[0 + yych] & 4) { + goto yy465; + } + if (yych <= 0x08) + goto yy469; + if (yych <= '\n') + goto yy468; + if (yych <= '\v') + goto yy469; + if (yych <= '\r') + goto yy470; + yy469 : { return 7; } + yy470: + ++p; + goto yy469; + yy471: + yych = *++p; + if (yych <= '<') { + if (yych <= ' ') { + if (yych <= 0x08) + goto yy460; + if (yych <= '\r') + goto yy471; + if (yych <= 0x1F) + goto yy460; + goto yy471; + } else { + if (yych <= '/') { + if (yych <= '.') + goto yy460; + goto yy464; + } else { + if (yych == ':') + goto yy467; + goto yy460; + } + } + } else { + if (yych <= 'Z') { + if (yych <= '=') + goto yy472; + if (yych <= '>') + goto yy465; + if (yych <= '@') + goto yy460; + goto yy467; + } else { + if (yych <= '_') { + if (yych <= '^') + goto yy460; + goto yy467; + } else { + if (yych <= '`') + goto yy460; + if (yych <= 'z') + goto yy467; + goto yy460; + } + } + } + yy472: + yych = *++p; + if (yybm[0 + yych] & 32) { + goto yy473; + } + if (yych <= 0xE0) { + if (yych <= '"') { + if (yych <= 0x00) + goto yy460; + if (yych <= ' ') + goto yy472; + goto yy474; + } else { + if (yych <= '\'') + goto yy475; + if (yych <= 0xC1) + goto yy460; + if (yych <= 0xDF) + goto yy476; + goto yy477; + } + } else { + if (yych <= 0xEF) { + if (yych == 0xED) + goto yy479; + goto yy478; + } else { + if (yych <= 0xF0) + goto yy480; + if (yych <= 0xF3) + goto yy481; + if (yych <= 0xF4) + goto yy482; + goto yy460; + } + } + yy473: + yych = *++p; + if (yybm[0 + yych] & 32) { + goto yy473; + } + if (yych <= 0xE0) { + if (yych <= '=') { + if (yych <= 0x00) + goto yy460; + if (yych <= ' ') + goto yy463; + goto yy460; + } else { + if (yych <= '>') + goto yy465; + if (yych <= 0xC1) + goto yy460; + if (yych <= 0xDF) + goto yy476; + goto yy477; + } + } else { + if (yych <= 0xEF) { + if (yych == 0xED) + goto yy479; + goto yy478; + } else { + if (yych <= 0xF0) + goto yy480; + if (yych <= 0xF3) + goto yy481; + if (yych <= 0xF4) + goto yy482; + goto yy460; + } + } + yy474: + yych = *++p; + if (yybm[0 + yych] & 64) { + goto yy474; + } + if (yych <= 0xEC) { + if (yych <= 0xC1) { + if (yych <= 0x00) + goto yy460; + if (yych <= '"') + goto yy483; + goto yy460; + } else { + if (yych <= 0xDF) + goto yy484; + if (yych <= 0xE0) + goto yy485; + goto yy486; + } + } else { + if (yych <= 0xF0) { + if (yych <= 0xED) + goto yy487; + if (yych <= 0xEF) + goto yy486; + goto yy488; + } else { + if (yych <= 0xF3) + goto yy489; + if (yych <= 0xF4) + goto yy490; + goto yy460; + } + } + yy475: + yych = *++p; + if (yybm[0 + yych] & 128) { + goto yy475; + } + if (yych <= 0xEC) { + if (yych <= 0xC1) { + if (yych <= 0x00) + goto yy460; + if (yych <= '\'') + goto yy483; + goto yy460; + } else { + if (yych <= 0xDF) + goto yy491; + if (yych <= 0xE0) + goto yy492; + goto yy493; + } + } else { + if (yych <= 0xF0) { + if (yych <= 0xED) + goto yy494; + if (yych <= 0xEF) + goto yy493; + goto yy495; + } else { + if (yych <= 0xF3) + goto yy496; + if (yych <= 0xF4) + goto yy497; + goto yy460; + } + } + yy476: + yych = *++p; + if (yych <= 0x7F) + goto yy460; + if (yych <= 0xBF) + goto yy473; + goto yy460; + yy477: + yych = *++p; + if (yych <= 0x9F) + goto yy460; + if (yych <= 0xBF) + goto yy476; + goto yy460; + yy478: + yych = *++p; + if (yych <= 0x7F) + goto yy460; + if (yych <= 0xBF) + goto yy476; + goto yy460; + yy479: + yych = *++p; + if (yych <= 0x7F) + goto yy460; + if (yych <= 0x9F) + goto yy476; + goto yy460; + yy480: + yych = *++p; + if (yych <= 0x8F) + goto yy460; + if (yych <= 0xBF) + goto yy478; + goto yy460; + yy481: + yych = *++p; + if (yych <= 0x7F) + goto yy460; + if (yych <= 0xBF) + goto yy478; + goto yy460; + yy482: + yych = *++p; + if (yych <= 0x7F) + goto yy460; + if (yych <= 0x8F) + goto yy478; + goto yy460; + yy483: + yych = *++p; + if (yybm[0 + yych] & 2) { + goto yy463; + } + if (yych == '/') + goto yy464; + if (yych == '>') + goto yy465; + goto yy460; + yy484: + yych = *++p; + if (yych <= 0x7F) + goto yy460; + if (yych <= 0xBF) + goto yy474; + goto yy460; + yy485: + yych = *++p; + if (yych <= 0x9F) + goto yy460; + if (yych <= 0xBF) + goto yy484; + goto yy460; + yy486: + yych = *++p; + if (yych <= 0x7F) + goto yy460; + if (yych <= 0xBF) + goto yy484; + goto yy460; + yy487: + yych = *++p; + if (yych <= 0x7F) + goto yy460; + if (yych <= 0x9F) + goto yy484; + goto yy460; + yy488: + yych = *++p; + if (yych <= 0x8F) + goto yy460; + if (yych <= 0xBF) + goto yy486; + goto yy460; + yy489: + yych = *++p; + if (yych <= 0x7F) + goto yy460; + if (yych <= 0xBF) + goto yy486; + goto yy460; + yy490: + yych = *++p; + if (yych <= 0x7F) + goto yy460; + if (yych <= 0x8F) + goto yy486; + goto yy460; + yy491: + yych = *++p; + if (yych <= 0x7F) + goto yy460; + if (yych <= 0xBF) + goto yy475; + goto yy460; + yy492: + yych = *++p; + if (yych <= 0x9F) + goto yy460; + if (yych <= 0xBF) + goto yy491; + goto yy460; + yy493: + yych = *++p; + if (yych <= 0x7F) + goto yy460; + if (yych <= 0xBF) + goto yy491; + goto yy460; + yy494: + yych = *++p; + if (yych <= 0x7F) + goto yy460; + if (yych <= 0x9F) + goto yy491; + goto yy460; + yy495: + yych = *++p; + if (yych <= 0x8F) + goto yy460; + if (yych <= 0xBF) + goto yy493; + goto yy460; + yy496: + yych = *++p; + if (yych <= 0x7F) + goto yy460; + if (yych <= 0xBF) + goto yy493; + goto yy460; + yy497: + yych = *++p; + if (yych <= 0x7F) + goto yy460; + if (yych <= 0x8F) + goto yy493; + goto yy460; + } } // Try to match an HTML block end line of type 1 -bufsize_t _scan_html_block_end_1(const unsigned char *p) -{ +bufsize_t _scan_html_block_end_1(const unsigned char *p) { const unsigned char *marker = NULL; const unsigned char *start = p; -{ - unsigned char yych; - unsigned int yyaccept = 0; - static const unsigned char yybm[] = { - 0, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 0, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 128, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - }; - yych = *p; - if (yych <= 0xDF) { - if (yych <= ';') { - if (yych <= 0x00) goto yy574; - if (yych != '\n') goto yy576; - } else { - if (yych <= '<') goto yy577; - if (yych <= 0x7F) goto yy576; - if (yych >= 0xC2) goto yy578; - } - } else { - if (yych <= 0xEF) { - if (yych <= 0xE0) goto yy579; - if (yych == 0xED) goto yy581; - goto yy580; - } else { - if (yych <= 0xF0) goto yy582; - if (yych <= 0xF3) goto yy583; - if (yych <= 0xF4) goto yy584; - } - } -yy574: - ++p; -yy575: - { return 0; } -yy576: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= '\n') { - if (yych <= 0x00) goto yy575; - if (yych <= '\t') goto yy586; - goto yy575; - } else { - if (yych <= 0x7F) goto yy586; - if (yych <= 0xC1) goto yy575; - if (yych <= 0xF4) goto yy586; - goto yy575; - } -yy577: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= '.') { - if (yych <= 0x00) goto yy575; - if (yych == '\n') goto yy575; - goto yy586; - } else { - if (yych <= 0x7F) { - if (yych <= '/') goto yy597; - goto yy586; - } else { - if (yych <= 0xC1) goto yy575; - if (yych <= 0xF4) goto yy586; - goto yy575; - } - } -yy578: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= 0x7F) goto yy575; - if (yych <= 0xBF) goto yy585; - goto yy575; -yy579: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= 0x9F) goto yy575; - if (yych <= 0xBF) goto yy590; - goto yy575; -yy580: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= 0x7F) goto yy575; - if (yych <= 0xBF) goto yy590; - goto yy575; -yy581: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= 0x7F) goto yy575; - if (yych <= 0x9F) goto yy590; - goto yy575; -yy582: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= 0x8F) goto yy575; - if (yych <= 0xBF) goto yy592; - goto yy575; -yy583: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= 0x7F) goto yy575; - if (yych <= 0xBF) goto yy592; - goto yy575; -yy584: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= 0x7F) goto yy575; - if (yych <= 0x8F) goto yy592; - goto yy575; -yy585: - yych = *++p; -yy586: - if (yybm[0+yych] & 64) { - goto yy585; - } - if (yych <= 0xEC) { - if (yych <= 0xC1) { - if (yych <= '\n') goto yy587; - if (yych <= '<') goto yy588; - } else { - if (yych <= 0xDF) goto yy590; - if (yych <= 0xE0) goto yy591; - goto yy592; - } - } else { - if (yych <= 0xF0) { - if (yych <= 0xED) goto yy593; - if (yych <= 0xEF) goto yy592; - goto yy594; - } else { - if (yych <= 0xF3) goto yy595; - if (yych <= 0xF4) goto yy596; - } - } -yy587: - p = marker; - if (yyaccept == 0) { - goto yy575; - } else { - goto yy607; - } -yy588: - yych = *++p; - if (yybm[0+yych] & 128) { - goto yy588; - } - if (yych <= 0xDF) { - if (yych <= '.') { - if (yych <= 0x00) goto yy587; - if (yych == '\n') goto yy587; - goto yy585; - } else { - if (yych <= '/') goto yy597; - if (yych <= 0x7F) goto yy585; - if (yych <= 0xC1) goto yy587; - } - } else { - if (yych <= 0xEF) { - if (yych <= 0xE0) goto yy591; - if (yych == 0xED) goto yy593; - goto yy592; - } else { - if (yych <= 0xF0) goto yy594; - if (yych <= 0xF3) goto yy595; - if (yych <= 0xF4) goto yy596; - goto yy587; - } - } -yy590: - yych = *++p; - if (yych <= 0x7F) goto yy587; - if (yych <= 0xBF) goto yy585; - goto yy587; -yy591: - yych = *++p; - if (yych <= 0x9F) goto yy587; - if (yych <= 0xBF) goto yy590; - goto yy587; -yy592: - yych = *++p; - if (yych <= 0x7F) goto yy587; - if (yych <= 0xBF) goto yy590; - goto yy587; -yy593: - yych = *++p; - if (yych <= 0x7F) goto yy587; - if (yych <= 0x9F) goto yy590; - goto yy587; -yy594: - yych = *++p; - if (yych <= 0x8F) goto yy587; - if (yych <= 0xBF) goto yy592; - goto yy587; -yy595: - yych = *++p; - if (yych <= 0x7F) goto yy587; - if (yych <= 0xBF) goto yy592; - goto yy587; -yy596: - yych = *++p; - if (yych <= 0x7F) goto yy587; - if (yych <= 0x8F) goto yy592; - goto yy587; -yy597: - yych = *++p; - if (yybm[0+yych] & 128) { - goto yy588; - } - if (yych <= 's') { - if (yych <= 'P') { - if (yych <= '\t') { - if (yych <= 0x00) goto yy587; - goto yy585; - } else { - if (yych <= '\n') goto yy587; - if (yych <= 'O') goto yy585; - } - } else { - if (yych <= 'o') { - if (yych == 'S') goto yy599; - goto yy585; - } else { - if (yych <= 'p') goto yy598; - if (yych <= 'r') goto yy585; - goto yy599; - } - } - } else { - if (yych <= 0xEC) { - if (yych <= 0xC1) { - if (yych <= 0x7F) goto yy585; - goto yy587; - } else { - if (yych <= 0xDF) goto yy590; - if (yych <= 0xE0) goto yy591; - goto yy592; - } - } else { - if (yych <= 0xF0) { - if (yych <= 0xED) goto yy593; - if (yych <= 0xEF) goto yy592; - goto yy594; - } else { - if (yych <= 0xF3) goto yy595; - if (yych <= 0xF4) goto yy596; - goto yy587; - } - } - } -yy598: - yych = *++p; - if (yybm[0+yych] & 128) { - goto yy588; - } - if (yych <= 0xC1) { - if (yych <= 'Q') { - if (yych <= 0x00) goto yy587; - if (yych == '\n') goto yy587; - goto yy585; - } else { - if (yych <= 'q') { - if (yych <= 'R') goto yy600; - goto yy585; - } else { - if (yych <= 'r') goto yy600; - if (yych <= 0x7F) goto yy585; - goto yy587; - } - } - } else { - if (yych <= 0xED) { - if (yych <= 0xDF) goto yy590; - if (yych <= 0xE0) goto yy591; - if (yych <= 0xEC) goto yy592; - goto yy593; - } else { - if (yych <= 0xF0) { - if (yych <= 0xEF) goto yy592; - goto yy594; - } else { - if (yych <= 0xF3) goto yy595; - if (yych <= 0xF4) goto yy596; - goto yy587; - } - } - } -yy599: - yych = *++p; - if (yybm[0+yych] & 128) { - goto yy588; - } - if (yych <= 't') { - if (yych <= 'C') { - if (yych <= '\t') { - if (yych <= 0x00) goto yy587; - goto yy585; - } else { - if (yych <= '\n') goto yy587; - if (yych <= 'B') goto yy585; - goto yy601; - } - } else { - if (yych <= 'b') { - if (yych == 'T') goto yy602; - goto yy585; - } else { - if (yych <= 'c') goto yy601; - if (yych <= 's') goto yy585; - goto yy602; - } - } - } else { - if (yych <= 0xEC) { - if (yych <= 0xC1) { - if (yych <= 0x7F) goto yy585; - goto yy587; - } else { - if (yych <= 0xDF) goto yy590; - if (yych <= 0xE0) goto yy591; - goto yy592; - } - } else { - if (yych <= 0xF0) { - if (yych <= 0xED) goto yy593; - if (yych <= 0xEF) goto yy592; - goto yy594; - } else { - if (yych <= 0xF3) goto yy595; - if (yych <= 0xF4) goto yy596; - goto yy587; - } - } - } -yy600: - yych = *++p; - if (yybm[0+yych] & 128) { - goto yy588; - } - if (yych <= 0xC1) { - if (yych <= 'D') { - if (yych <= 0x00) goto yy587; - if (yych == '\n') goto yy587; - goto yy585; - } else { - if (yych <= 'd') { - if (yych <= 'E') goto yy603; - goto yy585; - } else { - if (yych <= 'e') goto yy603; - if (yych <= 0x7F) goto yy585; - goto yy587; - } - } - } else { - if (yych <= 0xED) { - if (yych <= 0xDF) goto yy590; - if (yych <= 0xE0) goto yy591; - if (yych <= 0xEC) goto yy592; - goto yy593; - } else { - if (yych <= 0xF0) { - if (yych <= 0xEF) goto yy592; - goto yy594; - } else { - if (yych <= 0xF3) goto yy595; - if (yych <= 0xF4) goto yy596; - goto yy587; - } - } - } -yy601: - yych = *++p; - if (yybm[0+yych] & 128) { - goto yy588; - } - if (yych <= 0xC1) { - if (yych <= 'Q') { - if (yych <= 0x00) goto yy587; - if (yych == '\n') goto yy587; - goto yy585; - } else { - if (yych <= 'q') { - if (yych <= 'R') goto yy604; - goto yy585; - } else { - if (yych <= 'r') goto yy604; - if (yych <= 0x7F) goto yy585; - goto yy587; - } - } - } else { - if (yych <= 0xED) { - if (yych <= 0xDF) goto yy590; - if (yych <= 0xE0) goto yy591; - if (yych <= 0xEC) goto yy592; - goto yy593; - } else { - if (yych <= 0xF0) { - if (yych <= 0xEF) goto yy592; - goto yy594; - } else { - if (yych <= 0xF3) goto yy595; - if (yych <= 0xF4) goto yy596; - goto yy587; - } - } - } -yy602: - yych = *++p; - if (yybm[0+yych] & 128) { - goto yy588; - } - if (yych <= 0xC1) { - if (yych <= 'X') { - if (yych <= 0x00) goto yy587; - if (yych == '\n') goto yy587; - goto yy585; - } else { - if (yych <= 'x') { - if (yych <= 'Y') goto yy605; - goto yy585; - } else { - if (yych <= 'y') goto yy605; - if (yych <= 0x7F) goto yy585; - goto yy587; - } - } - } else { - if (yych <= 0xED) { - if (yych <= 0xDF) goto yy590; - if (yych <= 0xE0) goto yy591; - if (yych <= 0xEC) goto yy592; - goto yy593; - } else { - if (yych <= 0xF0) { - if (yych <= 0xEF) goto yy592; - goto yy594; - } else { - if (yych <= 0xF3) goto yy595; - if (yych <= 0xF4) goto yy596; - goto yy587; - } - } - } -yy603: - yych = *++p; - if (yybm[0+yych] & 128) { - goto yy588; - } - if (yych <= 0xDF) { - if (yych <= '=') { - if (yych <= 0x00) goto yy587; - if (yych == '\n') goto yy587; - goto yy585; - } else { - if (yych <= '>') goto yy606; - if (yych <= 0x7F) goto yy585; - if (yych <= 0xC1) goto yy587; - goto yy590; - } - } else { - if (yych <= 0xEF) { - if (yych <= 0xE0) goto yy591; - if (yych == 0xED) goto yy593; - goto yy592; - } else { - if (yych <= 0xF0) goto yy594; - if (yych <= 0xF3) goto yy595; - if (yych <= 0xF4) goto yy596; - goto yy587; - } - } -yy604: - yych = *++p; - if (yybm[0+yych] & 128) { - goto yy588; - } - if (yych <= 0xC1) { - if (yych <= 'H') { - if (yych <= 0x00) goto yy587; - if (yych == '\n') goto yy587; - goto yy585; - } else { - if (yych <= 'h') { - if (yych <= 'I') goto yy608; - goto yy585; - } else { - if (yych <= 'i') goto yy608; - if (yych <= 0x7F) goto yy585; - goto yy587; - } - } - } else { - if (yych <= 0xED) { - if (yych <= 0xDF) goto yy590; - if (yych <= 0xE0) goto yy591; - if (yych <= 0xEC) goto yy592; - goto yy593; - } else { - if (yych <= 0xF0) { - if (yych <= 0xEF) goto yy592; - goto yy594; - } else { - if (yych <= 0xF3) goto yy595; - if (yych <= 0xF4) goto yy596; - goto yy587; - } - } - } -yy605: - yych = *++p; - if (yybm[0+yych] & 128) { - goto yy588; - } - if (yych <= 0xC1) { - if (yych <= 'K') { - if (yych <= 0x00) goto yy587; - if (yych == '\n') goto yy587; - goto yy585; - } else { - if (yych <= 'k') { - if (yych <= 'L') goto yy600; - goto yy585; - } else { - if (yych <= 'l') goto yy600; - if (yych <= 0x7F) goto yy585; - goto yy587; - } - } - } else { - if (yych <= 0xED) { - if (yych <= 0xDF) goto yy590; - if (yych <= 0xE0) goto yy591; - if (yych <= 0xEC) goto yy592; - goto yy593; - } else { - if (yych <= 0xF0) { - if (yych <= 0xEF) goto yy592; - goto yy594; - } else { - if (yych <= 0xF3) goto yy595; - if (yych <= 0xF4) goto yy596; - goto yy587; - } - } - } -yy606: - yyaccept = 1; - yych = *(marker = ++p); - if (yybm[0+yych] & 64) { - goto yy585; - } - if (yych <= 0xEC) { - if (yych <= 0xC1) { - if (yych <= '\n') goto yy607; - if (yych <= '<') goto yy588; - } else { - if (yych <= 0xDF) goto yy590; - if (yych <= 0xE0) goto yy591; - goto yy592; - } - } else { - if (yych <= 0xF0) { - if (yych <= 0xED) goto yy593; - if (yych <= 0xEF) goto yy592; - goto yy594; - } else { - if (yych <= 0xF3) goto yy595; - if (yych <= 0xF4) goto yy596; - } - } -yy607: - { return (bufsize_t)(p - start); } -yy608: - yych = *++p; - if (yybm[0+yych] & 128) { - goto yy588; - } - if (yych <= 0xC1) { - if (yych <= 'O') { - if (yych <= 0x00) goto yy587; - if (yych == '\n') goto yy587; - goto yy585; - } else { - if (yych <= 'o') { - if (yych >= 'Q') goto yy585; - } else { - if (yych <= 'p') goto yy609; - if (yych <= 0x7F) goto yy585; - goto yy587; - } - } - } else { - if (yych <= 0xED) { - if (yych <= 0xDF) goto yy590; - if (yych <= 0xE0) goto yy591; - if (yych <= 0xEC) goto yy592; - goto yy593; - } else { - if (yych <= 0xF0) { - if (yych <= 0xEF) goto yy592; - goto yy594; - } else { - if (yych <= 0xF3) goto yy595; - if (yych <= 0xF4) goto yy596; - goto yy587; - } - } - } -yy609: - yych = *++p; - if (yybm[0+yych] & 128) { - goto yy588; - } - if (yych <= 0xC1) { - if (yych <= 'S') { - if (yych <= 0x00) goto yy587; - if (yych == '\n') goto yy587; - goto yy585; - } else { - if (yych <= 's') { - if (yych <= 'T') goto yy603; - goto yy585; - } else { - if (yych <= 't') goto yy603; - if (yych <= 0x7F) goto yy585; - goto yy587; - } - } - } else { - if (yych <= 0xED) { - if (yych <= 0xDF) goto yy590; - if (yych <= 0xE0) goto yy591; - if (yych <= 0xEC) goto yy592; - goto yy593; - } else { - if (yych <= 0xF0) { - if (yych <= 0xEF) goto yy592; - goto yy594; - } else { - if (yych <= 0xF3) goto yy595; - if (yych <= 0xF4) goto yy596; - goto yy587; - } - } - } -} - + { + unsigned char yych; + unsigned int yyaccept = 0; + static const unsigned char yybm[] = { + 0, 64, 64, 64, 64, 64, 64, 64, 64, 64, 0, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 128, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + }; + yych = *p; + if (yych <= 0xDF) { + if (yych <= ';') { + if (yych <= 0x00) + goto yy499; + if (yych != '\n') + goto yy501; + } else { + if (yych <= '<') + goto yy502; + if (yych <= 0x7F) + goto yy501; + if (yych >= 0xC2) + goto yy503; + } + } else { + if (yych <= 0xEF) { + if (yych <= 0xE0) + goto yy504; + if (yych == 0xED) + goto yy506; + goto yy505; + } else { + if (yych <= 0xF0) + goto yy507; + if (yych <= 0xF3) + goto yy508; + if (yych <= 0xF4) + goto yy509; + } + } + yy499: + ++p; + yy500 : { return 0; } + yy501: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= '\n') { + if (yych <= 0x00) + goto yy500; + if (yych <= '\t') + goto yy511; + goto yy500; + } else { + if (yych <= 0x7F) + goto yy511; + if (yych <= 0xC1) + goto yy500; + if (yych <= 0xF4) + goto yy511; + goto yy500; + } + yy502: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= '.') { + if (yych <= 0x00) + goto yy500; + if (yych == '\n') + goto yy500; + goto yy511; + } else { + if (yych <= 0x7F) { + if (yych <= '/') + goto yy521; + goto yy511; + } else { + if (yych <= 0xC1) + goto yy500; + if (yych <= 0xF4) + goto yy511; + goto yy500; + } + } + yy503: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= 0x7F) + goto yy500; + if (yych <= 0xBF) + goto yy510; + goto yy500; + yy504: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= 0x9F) + goto yy500; + if (yych <= 0xBF) + goto yy514; + goto yy500; + yy505: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= 0x7F) + goto yy500; + if (yych <= 0xBF) + goto yy514; + goto yy500; + yy506: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= 0x7F) + goto yy500; + if (yych <= 0x9F) + goto yy514; + goto yy500; + yy507: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= 0x8F) + goto yy500; + if (yych <= 0xBF) + goto yy516; + goto yy500; + yy508: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= 0x7F) + goto yy500; + if (yych <= 0xBF) + goto yy516; + goto yy500; + yy509: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= 0x7F) + goto yy500; + if (yych <= 0x8F) + goto yy516; + goto yy500; + yy510: + yych = *++p; + yy511: + if (yybm[0 + yych] & 64) { + goto yy510; + } + if (yych <= 0xEC) { + if (yych <= 0xC1) { + if (yych <= '\n') + goto yy512; + if (yych <= '<') + goto yy513; + } else { + if (yych <= 0xDF) + goto yy514; + if (yych <= 0xE0) + goto yy515; + goto yy516; + } + } else { + if (yych <= 0xF0) { + if (yych <= 0xED) + goto yy517; + if (yych <= 0xEF) + goto yy516; + goto yy518; + } else { + if (yych <= 0xF3) + goto yy519; + if (yych <= 0xF4) + goto yy520; + } + } + yy512: + p = marker; + if (yyaccept == 0) { + goto yy500; + } else { + goto yy534; + } + yy513: + yych = *++p; + if (yybm[0 + yych] & 128) { + goto yy513; + } + if (yych <= 0xDF) { + if (yych <= '.') { + if (yych <= 0x00) + goto yy512; + if (yych == '\n') + goto yy512; + goto yy510; + } else { + if (yych <= '/') + goto yy521; + if (yych <= 0x7F) + goto yy510; + if (yych <= 0xC1) + goto yy512; + } + } else { + if (yych <= 0xEF) { + if (yych <= 0xE0) + goto yy515; + if (yych == 0xED) + goto yy517; + goto yy516; + } else { + if (yych <= 0xF0) + goto yy518; + if (yych <= 0xF3) + goto yy519; + if (yych <= 0xF4) + goto yy520; + goto yy512; + } + } + yy514: + yych = *++p; + if (yych <= 0x7F) + goto yy512; + if (yych <= 0xBF) + goto yy510; + goto yy512; + yy515: + yych = *++p; + if (yych <= 0x9F) + goto yy512; + if (yych <= 0xBF) + goto yy514; + goto yy512; + yy516: + yych = *++p; + if (yych <= 0x7F) + goto yy512; + if (yych <= 0xBF) + goto yy514; + goto yy512; + yy517: + yych = *++p; + if (yych <= 0x7F) + goto yy512; + if (yych <= 0x9F) + goto yy514; + goto yy512; + yy518: + yych = *++p; + if (yych <= 0x8F) + goto yy512; + if (yych <= 0xBF) + goto yy516; + goto yy512; + yy519: + yych = *++p; + if (yych <= 0x7F) + goto yy512; + if (yych <= 0xBF) + goto yy516; + goto yy512; + yy520: + yych = *++p; + if (yych <= 0x7F) + goto yy512; + if (yych <= 0x8F) + goto yy516; + goto yy512; + yy521: + yych = *++p; + if (yybm[0 + yych] & 128) { + goto yy513; + } + if (yych <= 's') { + if (yych <= 'R') { + if (yych <= '\n') { + if (yych <= 0x00) + goto yy512; + if (yych <= '\t') + goto yy510; + goto yy512; + } else { + if (yych != 'P') + goto yy510; + } + } else { + if (yych <= 'o') { + if (yych <= 'S') + goto yy523; + if (yych <= 'T') + goto yy524; + goto yy510; + } else { + if (yych <= 'p') + goto yy522; + if (yych <= 'r') + goto yy510; + goto yy523; + } + } + } else { + if (yych <= 0xEC) { + if (yych <= 0xC1) { + if (yych <= 't') + goto yy524; + if (yych <= 0x7F) + goto yy510; + goto yy512; + } else { + if (yych <= 0xDF) + goto yy514; + if (yych <= 0xE0) + goto yy515; + goto yy516; + } + } else { + if (yych <= 0xF0) { + if (yych <= 0xED) + goto yy517; + if (yych <= 0xEF) + goto yy516; + goto yy518; + } else { + if (yych <= 0xF3) + goto yy519; + if (yych <= 0xF4) + goto yy520; + goto yy512; + } + } + } + yy522: + yych = *++p; + if (yybm[0 + yych] & 128) { + goto yy513; + } + if (yych <= 0xC1) { + if (yych <= 'Q') { + if (yych <= 0x00) + goto yy512; + if (yych == '\n') + goto yy512; + goto yy510; + } else { + if (yych <= 'q') { + if (yych <= 'R') + goto yy525; + goto yy510; + } else { + if (yych <= 'r') + goto yy525; + if (yych <= 0x7F) + goto yy510; + goto yy512; + } + } + } else { + if (yych <= 0xED) { + if (yych <= 0xDF) + goto yy514; + if (yych <= 0xE0) + goto yy515; + if (yych <= 0xEC) + goto yy516; + goto yy517; + } else { + if (yych <= 0xF0) { + if (yych <= 0xEF) + goto yy516; + goto yy518; + } else { + if (yych <= 0xF3) + goto yy519; + if (yych <= 0xF4) + goto yy520; + goto yy512; + } + } + } + yy523: + yych = *++p; + if (yybm[0 + yych] & 128) { + goto yy513; + } + if (yych <= 't') { + if (yych <= 'C') { + if (yych <= '\t') { + if (yych <= 0x00) + goto yy512; + goto yy510; + } else { + if (yych <= '\n') + goto yy512; + if (yych <= 'B') + goto yy510; + goto yy526; + } + } else { + if (yych <= 'b') { + if (yych == 'T') + goto yy527; + goto yy510; + } else { + if (yych <= 'c') + goto yy526; + if (yych <= 's') + goto yy510; + goto yy527; + } + } + } else { + if (yych <= 0xEC) { + if (yych <= 0xC1) { + if (yych <= 0x7F) + goto yy510; + goto yy512; + } else { + if (yych <= 0xDF) + goto yy514; + if (yych <= 0xE0) + goto yy515; + goto yy516; + } + } else { + if (yych <= 0xF0) { + if (yych <= 0xED) + goto yy517; + if (yych <= 0xEF) + goto yy516; + goto yy518; + } else { + if (yych <= 0xF3) + goto yy519; + if (yych <= 0xF4) + goto yy520; + goto yy512; + } + } + } + yy524: + yych = *++p; + if (yybm[0 + yych] & 128) { + goto yy513; + } + if (yych <= 0xC1) { + if (yych <= 'D') { + if (yych <= 0x00) + goto yy512; + if (yych == '\n') + goto yy512; + goto yy510; + } else { + if (yych <= 'd') { + if (yych <= 'E') + goto yy528; + goto yy510; + } else { + if (yych <= 'e') + goto yy528; + if (yych <= 0x7F) + goto yy510; + goto yy512; + } + } + } else { + if (yych <= 0xED) { + if (yych <= 0xDF) + goto yy514; + if (yych <= 0xE0) + goto yy515; + if (yych <= 0xEC) + goto yy516; + goto yy517; + } else { + if (yych <= 0xF0) { + if (yych <= 0xEF) + goto yy516; + goto yy518; + } else { + if (yych <= 0xF3) + goto yy519; + if (yych <= 0xF4) + goto yy520; + goto yy512; + } + } + } + yy525: + yych = *++p; + if (yybm[0 + yych] & 128) { + goto yy513; + } + if (yych <= 0xC1) { + if (yych <= 'D') { + if (yych <= 0x00) + goto yy512; + if (yych == '\n') + goto yy512; + goto yy510; + } else { + if (yych <= 'd') { + if (yych <= 'E') + goto yy529; + goto yy510; + } else { + if (yych <= 'e') + goto yy529; + if (yych <= 0x7F) + goto yy510; + goto yy512; + } + } + } else { + if (yych <= 0xED) { + if (yych <= 0xDF) + goto yy514; + if (yych <= 0xE0) + goto yy515; + if (yych <= 0xEC) + goto yy516; + goto yy517; + } else { + if (yych <= 0xF0) { + if (yych <= 0xEF) + goto yy516; + goto yy518; + } else { + if (yych <= 0xF3) + goto yy519; + if (yych <= 0xF4) + goto yy520; + goto yy512; + } + } + } + yy526: + yych = *++p; + if (yybm[0 + yych] & 128) { + goto yy513; + } + if (yych <= 0xC1) { + if (yych <= 'Q') { + if (yych <= 0x00) + goto yy512; + if (yych == '\n') + goto yy512; + goto yy510; + } else { + if (yych <= 'q') { + if (yych <= 'R') + goto yy530; + goto yy510; + } else { + if (yych <= 'r') + goto yy530; + if (yych <= 0x7F) + goto yy510; + goto yy512; + } + } + } else { + if (yych <= 0xED) { + if (yych <= 0xDF) + goto yy514; + if (yych <= 0xE0) + goto yy515; + if (yych <= 0xEC) + goto yy516; + goto yy517; + } else { + if (yych <= 0xF0) { + if (yych <= 0xEF) + goto yy516; + goto yy518; + } else { + if (yych <= 0xF3) + goto yy519; + if (yych <= 0xF4) + goto yy520; + goto yy512; + } + } + } + yy527: + yych = *++p; + if (yybm[0 + yych] & 128) { + goto yy513; + } + if (yych <= 0xC1) { + if (yych <= 'X') { + if (yych <= 0x00) + goto yy512; + if (yych == '\n') + goto yy512; + goto yy510; + } else { + if (yych <= 'x') { + if (yych <= 'Y') + goto yy531; + goto yy510; + } else { + if (yych <= 'y') + goto yy531; + if (yych <= 0x7F) + goto yy510; + goto yy512; + } + } + } else { + if (yych <= 0xED) { + if (yych <= 0xDF) + goto yy514; + if (yych <= 0xE0) + goto yy515; + if (yych <= 0xEC) + goto yy516; + goto yy517; + } else { + if (yych <= 0xF0) { + if (yych <= 0xEF) + goto yy516; + goto yy518; + } else { + if (yych <= 0xF3) + goto yy519; + if (yych <= 0xF4) + goto yy520; + goto yy512; + } + } + } + yy528: + yych = *++p; + if (yybm[0 + yych] & 128) { + goto yy513; + } + if (yych <= 0xC1) { + if (yych <= 'W') { + if (yych <= 0x00) + goto yy512; + if (yych == '\n') + goto yy512; + goto yy510; + } else { + if (yych <= 'w') { + if (yych <= 'X') + goto yy532; + goto yy510; + } else { + if (yych <= 'x') + goto yy532; + if (yych <= 0x7F) + goto yy510; + goto yy512; + } + } + } else { + if (yych <= 0xED) { + if (yych <= 0xDF) + goto yy514; + if (yych <= 0xE0) + goto yy515; + if (yych <= 0xEC) + goto yy516; + goto yy517; + } else { + if (yych <= 0xF0) { + if (yych <= 0xEF) + goto yy516; + goto yy518; + } else { + if (yych <= 0xF3) + goto yy519; + if (yych <= 0xF4) + goto yy520; + goto yy512; + } + } + } + yy529: + yych = *++p; + if (yybm[0 + yych] & 128) { + goto yy513; + } + if (yych <= 0xDF) { + if (yych <= '=') { + if (yych <= 0x00) + goto yy512; + if (yych == '\n') + goto yy512; + goto yy510; + } else { + if (yych <= '>') + goto yy533; + if (yych <= 0x7F) + goto yy510; + if (yych <= 0xC1) + goto yy512; + goto yy514; + } + } else { + if (yych <= 0xEF) { + if (yych <= 0xE0) + goto yy515; + if (yych == 0xED) + goto yy517; + goto yy516; + } else { + if (yych <= 0xF0) + goto yy518; + if (yych <= 0xF3) + goto yy519; + if (yych <= 0xF4) + goto yy520; + goto yy512; + } + } + yy530: + yych = *++p; + if (yybm[0 + yych] & 128) { + goto yy513; + } + if (yych <= 0xC1) { + if (yych <= 'H') { + if (yych <= 0x00) + goto yy512; + if (yych == '\n') + goto yy512; + goto yy510; + } else { + if (yych <= 'h') { + if (yych <= 'I') + goto yy535; + goto yy510; + } else { + if (yych <= 'i') + goto yy535; + if (yych <= 0x7F) + goto yy510; + goto yy512; + } + } + } else { + if (yych <= 0xED) { + if (yych <= 0xDF) + goto yy514; + if (yych <= 0xE0) + goto yy515; + if (yych <= 0xEC) + goto yy516; + goto yy517; + } else { + if (yych <= 0xF0) { + if (yych <= 0xEF) + goto yy516; + goto yy518; + } else { + if (yych <= 0xF3) + goto yy519; + if (yych <= 0xF4) + goto yy520; + goto yy512; + } + } + } + yy531: + yych = *++p; + if (yybm[0 + yych] & 128) { + goto yy513; + } + if (yych <= 0xC1) { + if (yych <= 'K') { + if (yych <= 0x00) + goto yy512; + if (yych == '\n') + goto yy512; + goto yy510; + } else { + if (yych <= 'k') { + if (yych <= 'L') + goto yy525; + goto yy510; + } else { + if (yych <= 'l') + goto yy525; + if (yych <= 0x7F) + goto yy510; + goto yy512; + } + } + } else { + if (yych <= 0xED) { + if (yych <= 0xDF) + goto yy514; + if (yych <= 0xE0) + goto yy515; + if (yych <= 0xEC) + goto yy516; + goto yy517; + } else { + if (yych <= 0xF0) { + if (yych <= 0xEF) + goto yy516; + goto yy518; + } else { + if (yych <= 0xF3) + goto yy519; + if (yych <= 0xF4) + goto yy520; + goto yy512; + } + } + } + yy532: + yych = *++p; + if (yybm[0 + yych] & 128) { + goto yy513; + } + if (yych <= 0xC1) { + if (yych <= 'S') { + if (yych <= 0x00) + goto yy512; + if (yych == '\n') + goto yy512; + goto yy510; + } else { + if (yych <= 's') { + if (yych <= 'T') + goto yy536; + goto yy510; + } else { + if (yych <= 't') + goto yy536; + if (yych <= 0x7F) + goto yy510; + goto yy512; + } + } + } else { + if (yych <= 0xED) { + if (yych <= 0xDF) + goto yy514; + if (yych <= 0xE0) + goto yy515; + if (yych <= 0xEC) + goto yy516; + goto yy517; + } else { + if (yych <= 0xF0) { + if (yych <= 0xEF) + goto yy516; + goto yy518; + } else { + if (yych <= 0xF3) + goto yy519; + if (yych <= 0xF4) + goto yy520; + goto yy512; + } + } + } + yy533: + yyaccept = 1; + yych = *(marker = ++p); + if (yybm[0 + yych] & 64) { + goto yy510; + } + if (yych <= 0xEC) { + if (yych <= 0xC1) { + if (yych <= '\n') + goto yy534; + if (yych <= '<') + goto yy513; + } else { + if (yych <= 0xDF) + goto yy514; + if (yych <= 0xE0) + goto yy515; + goto yy516; + } + } else { + if (yych <= 0xF0) { + if (yych <= 0xED) + goto yy517; + if (yych <= 0xEF) + goto yy516; + goto yy518; + } else { + if (yych <= 0xF3) + goto yy519; + if (yych <= 0xF4) + goto yy520; + } + } + yy534 : { return (bufsize_t)(p - start); } + yy535: + yych = *++p; + if (yybm[0 + yych] & 128) { + goto yy513; + } + if (yych <= 0xC1) { + if (yych <= 'O') { + if (yych <= 0x00) + goto yy512; + if (yych == '\n') + goto yy512; + goto yy510; + } else { + if (yych <= 'o') { + if (yych <= 'P') + goto yy537; + goto yy510; + } else { + if (yych <= 'p') + goto yy537; + if (yych <= 0x7F) + goto yy510; + goto yy512; + } + } + } else { + if (yych <= 0xED) { + if (yych <= 0xDF) + goto yy514; + if (yych <= 0xE0) + goto yy515; + if (yych <= 0xEC) + goto yy516; + goto yy517; + } else { + if (yych <= 0xF0) { + if (yych <= 0xEF) + goto yy516; + goto yy518; + } else { + if (yych <= 0xF3) + goto yy519; + if (yych <= 0xF4) + goto yy520; + goto yy512; + } + } + } + yy536: + yych = *++p; + if (yybm[0 + yych] & 128) { + goto yy513; + } + if (yych <= 0xC1) { + if (yych <= '@') { + if (yych <= 0x00) + goto yy512; + if (yych == '\n') + goto yy512; + goto yy510; + } else { + if (yych <= '`') { + if (yych <= 'A') + goto yy538; + goto yy510; + } else { + if (yych <= 'a') + goto yy538; + if (yych <= 0x7F) + goto yy510; + goto yy512; + } + } + } else { + if (yych <= 0xED) { + if (yych <= 0xDF) + goto yy514; + if (yych <= 0xE0) + goto yy515; + if (yych <= 0xEC) + goto yy516; + goto yy517; + } else { + if (yych <= 0xF0) { + if (yych <= 0xEF) + goto yy516; + goto yy518; + } else { + if (yych <= 0xF3) + goto yy519; + if (yych <= 0xF4) + goto yy520; + goto yy512; + } + } + } + yy537: + yych = *++p; + if (yybm[0 + yych] & 128) { + goto yy513; + } + if (yych <= 0xC1) { + if (yych <= 'S') { + if (yych <= 0x00) + goto yy512; + if (yych == '\n') + goto yy512; + goto yy510; + } else { + if (yych <= 's') { + if (yych <= 'T') + goto yy529; + goto yy510; + } else { + if (yych <= 't') + goto yy529; + if (yych <= 0x7F) + goto yy510; + goto yy512; + } + } + } else { + if (yych <= 0xED) { + if (yych <= 0xDF) + goto yy514; + if (yych <= 0xE0) + goto yy515; + if (yych <= 0xEC) + goto yy516; + goto yy517; + } else { + if (yych <= 0xF0) { + if (yych <= 0xEF) + goto yy516; + goto yy518; + } else { + if (yych <= 0xF3) + goto yy519; + if (yych <= 0xF4) + goto yy520; + goto yy512; + } + } + } + yy538: + yych = *++p; + if (yybm[0 + yych] & 128) { + goto yy513; + } + if (yych <= 0xC1) { + if (yych <= 'Q') { + if (yych <= 0x00) + goto yy512; + if (yych == '\n') + goto yy512; + goto yy510; + } else { + if (yych <= 'q') { + if (yych >= 'S') + goto yy510; + } else { + if (yych <= 'r') + goto yy539; + if (yych <= 0x7F) + goto yy510; + goto yy512; + } + } + } else { + if (yych <= 0xED) { + if (yych <= 0xDF) + goto yy514; + if (yych <= 0xE0) + goto yy515; + if (yych <= 0xEC) + goto yy516; + goto yy517; + } else { + if (yych <= 0xF0) { + if (yych <= 0xEF) + goto yy516; + goto yy518; + } else { + if (yych <= 0xF3) + goto yy519; + if (yych <= 0xF4) + goto yy520; + goto yy512; + } + } + } + yy539: + yych = *++p; + if (yybm[0 + yych] & 128) { + goto yy513; + } + if (yych <= 0xC1) { + if (yych <= 'D') { + if (yych <= 0x00) + goto yy512; + if (yych == '\n') + goto yy512; + goto yy510; + } else { + if (yych <= 'd') { + if (yych >= 'F') + goto yy510; + } else { + if (yych <= 'e') + goto yy540; + if (yych <= 0x7F) + goto yy510; + goto yy512; + } + } + } else { + if (yych <= 0xED) { + if (yych <= 0xDF) + goto yy514; + if (yych <= 0xE0) + goto yy515; + if (yych <= 0xEC) + goto yy516; + goto yy517; + } else { + if (yych <= 0xF0) { + if (yych <= 0xEF) + goto yy516; + goto yy518; + } else { + if (yych <= 0xF3) + goto yy519; + if (yych <= 0xF4) + goto yy520; + goto yy512; + } + } + } + yy540: + yych = *++p; + if (yybm[0 + yych] & 128) { + goto yy513; + } + if (yych <= 0xC1) { + if (yych <= '@') { + if (yych <= 0x00) + goto yy512; + if (yych == '\n') + goto yy512; + goto yy510; + } else { + if (yych <= '`') { + if (yych <= 'A') + goto yy529; + goto yy510; + } else { + if (yych <= 'a') + goto yy529; + if (yych <= 0x7F) + goto yy510; + goto yy512; + } + } + } else { + if (yych <= 0xED) { + if (yych <= 0xDF) + goto yy514; + if (yych <= 0xE0) + goto yy515; + if (yych <= 0xEC) + goto yy516; + goto yy517; + } else { + if (yych <= 0xF0) { + if (yych <= 0xEF) + goto yy516; + goto yy518; + } else { + if (yych <= 0xF3) + goto yy519; + if (yych <= 0xF4) + goto yy520; + goto yy512; + } + } + } + } } // Try to match an HTML block end line of type 2 -bufsize_t _scan_html_block_end_2(const unsigned char *p) -{ +bufsize_t _scan_html_block_end_2(const unsigned char *p) { const unsigned char *marker = NULL; const unsigned char *start = p; -{ - unsigned char yych; - unsigned int yyaccept = 0; - static const unsigned char yybm[] = { - 0, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 0, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 128, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - }; - yych = *p; - if (yych <= 0xDF) { - if (yych <= ',') { - if (yych <= 0x00) goto yy612; - if (yych != '\n') goto yy614; - } else { - if (yych <= '-') goto yy615; - if (yych <= 0x7F) goto yy614; - if (yych >= 0xC2) goto yy616; - } - } else { - if (yych <= 0xEF) { - if (yych <= 0xE0) goto yy617; - if (yych == 0xED) goto yy619; - goto yy618; - } else { - if (yych <= 0xF0) goto yy620; - if (yych <= 0xF3) goto yy621; - if (yych <= 0xF4) goto yy622; - } - } -yy612: - ++p; -yy613: - { return 0; } -yy614: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= '\n') { - if (yych <= 0x00) goto yy613; - if (yych <= '\t') goto yy624; - goto yy613; - } else { - if (yych <= 0x7F) goto yy624; - if (yych <= 0xC1) goto yy613; - if (yych <= 0xF4) goto yy624; - goto yy613; - } -yy615: - yyaccept = 0; - yych = *(marker = ++p); - if (yybm[0+yych] & 128) { - goto yy634; - } - if (yych <= '\n') { - if (yych <= 0x00) goto yy613; - if (yych <= '\t') goto yy624; - goto yy613; - } else { - if (yych <= 0x7F) goto yy624; - if (yych <= 0xC1) goto yy613; - if (yych <= 0xF4) goto yy624; - goto yy613; - } -yy616: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= 0x7F) goto yy613; - if (yych <= 0xBF) goto yy623; - goto yy613; -yy617: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= 0x9F) goto yy613; - if (yych <= 0xBF) goto yy627; - goto yy613; -yy618: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= 0x7F) goto yy613; - if (yych <= 0xBF) goto yy627; - goto yy613; -yy619: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= 0x7F) goto yy613; - if (yych <= 0x9F) goto yy627; - goto yy613; -yy620: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= 0x8F) goto yy613; - if (yych <= 0xBF) goto yy629; - goto yy613; -yy621: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= 0x7F) goto yy613; - if (yych <= 0xBF) goto yy629; - goto yy613; -yy622: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= 0x7F) goto yy613; - if (yych <= 0x8F) goto yy629; - goto yy613; -yy623: - yych = *++p; -yy624: - if (yybm[0+yych] & 64) { - goto yy623; - } - if (yych <= 0xEC) { - if (yych <= 0xC1) { - if (yych <= '\n') goto yy625; - if (yych <= '-') goto yy626; - } else { - if (yych <= 0xDF) goto yy627; - if (yych <= 0xE0) goto yy628; - goto yy629; - } - } else { - if (yych <= 0xF0) { - if (yych <= 0xED) goto yy630; - if (yych <= 0xEF) goto yy629; - goto yy631; - } else { - if (yych <= 0xF3) goto yy632; - if (yych <= 0xF4) goto yy633; - } - } -yy625: - p = marker; - if (yyaccept == 0) { - goto yy613; - } else { - goto yy637; - } -yy626: - yych = *++p; - if (yybm[0+yych] & 64) { - goto yy623; - } - if (yych <= 0xEC) { - if (yych <= 0xC1) { - if (yych <= '\n') goto yy625; - if (yych <= '-') goto yy634; - goto yy625; - } else { - if (yych <= 0xDF) goto yy627; - if (yych <= 0xE0) goto yy628; - goto yy629; - } - } else { - if (yych <= 0xF0) { - if (yych <= 0xED) goto yy630; - if (yych <= 0xEF) goto yy629; - goto yy631; - } else { - if (yych <= 0xF3) goto yy632; - if (yych <= 0xF4) goto yy633; - goto yy625; - } - } -yy627: - yych = *++p; - if (yych <= 0x7F) goto yy625; - if (yych <= 0xBF) goto yy623; - goto yy625; -yy628: - yych = *++p; - if (yych <= 0x9F) goto yy625; - if (yych <= 0xBF) goto yy627; - goto yy625; -yy629: - yych = *++p; - if (yych <= 0x7F) goto yy625; - if (yych <= 0xBF) goto yy627; - goto yy625; -yy630: - yych = *++p; - if (yych <= 0x7F) goto yy625; - if (yych <= 0x9F) goto yy627; - goto yy625; -yy631: - yych = *++p; - if (yych <= 0x8F) goto yy625; - if (yych <= 0xBF) goto yy629; - goto yy625; -yy632: - yych = *++p; - if (yych <= 0x7F) goto yy625; - if (yych <= 0xBF) goto yy629; - goto yy625; -yy633: - yych = *++p; - if (yych <= 0x7F) goto yy625; - if (yych <= 0x8F) goto yy629; - goto yy625; -yy634: - yych = *++p; - if (yybm[0+yych] & 128) { - goto yy634; - } - if (yych <= 0xDF) { - if (yych <= '=') { - if (yych <= 0x00) goto yy625; - if (yych == '\n') goto yy625; - goto yy623; - } else { - if (yych <= '>') goto yy636; - if (yych <= 0x7F) goto yy623; - if (yych <= 0xC1) goto yy625; - goto yy627; - } - } else { - if (yych <= 0xEF) { - if (yych <= 0xE0) goto yy628; - if (yych == 0xED) goto yy630; - goto yy629; - } else { - if (yych <= 0xF0) goto yy631; - if (yych <= 0xF3) goto yy632; - if (yych <= 0xF4) goto yy633; - goto yy625; - } - } -yy636: - yyaccept = 1; - yych = *(marker = ++p); - if (yybm[0+yych] & 64) { - goto yy623; - } - if (yych <= 0xEC) { - if (yych <= 0xC1) { - if (yych <= '\n') goto yy637; - if (yych <= '-') goto yy626; - } else { - if (yych <= 0xDF) goto yy627; - if (yych <= 0xE0) goto yy628; - goto yy629; - } - } else { - if (yych <= 0xF0) { - if (yych <= 0xED) goto yy630; - if (yych <= 0xEF) goto yy629; - goto yy631; - } else { - if (yych <= 0xF3) goto yy632; - if (yych <= 0xF4) goto yy633; - } - } -yy637: - { return (bufsize_t)(p - start); } -} - + { + unsigned char yych; + unsigned int yyaccept = 0; + static const unsigned char yybm[] = { + 0, 64, 64, 64, 64, 64, 64, 64, 64, 64, 0, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 128, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + }; + yych = *p; + if (yych <= 0xDF) { + if (yych <= ',') { + if (yych <= 0x00) + goto yy542; + if (yych != '\n') + goto yy544; + } else { + if (yych <= '-') + goto yy545; + if (yych <= 0x7F) + goto yy544; + if (yych >= 0xC2) + goto yy546; + } + } else { + if (yych <= 0xEF) { + if (yych <= 0xE0) + goto yy547; + if (yych == 0xED) + goto yy549; + goto yy548; + } else { + if (yych <= 0xF0) + goto yy550; + if (yych <= 0xF3) + goto yy551; + if (yych <= 0xF4) + goto yy552; + } + } + yy542: + ++p; + yy543 : { return 0; } + yy544: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= '\n') { + if (yych <= 0x00) + goto yy543; + if (yych <= '\t') + goto yy554; + goto yy543; + } else { + if (yych <= 0x7F) + goto yy554; + if (yych <= 0xC1) + goto yy543; + if (yych <= 0xF4) + goto yy554; + goto yy543; + } + yy545: + yyaccept = 0; + yych = *(marker = ++p); + if (yybm[0 + yych] & 128) { + goto yy564; + } + if (yych <= '\n') { + if (yych <= 0x00) + goto yy543; + if (yych <= '\t') + goto yy554; + goto yy543; + } else { + if (yych <= 0x7F) + goto yy554; + if (yych <= 0xC1) + goto yy543; + if (yych <= 0xF4) + goto yy554; + goto yy543; + } + yy546: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= 0x7F) + goto yy543; + if (yych <= 0xBF) + goto yy553; + goto yy543; + yy547: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= 0x9F) + goto yy543; + if (yych <= 0xBF) + goto yy557; + goto yy543; + yy548: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= 0x7F) + goto yy543; + if (yych <= 0xBF) + goto yy557; + goto yy543; + yy549: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= 0x7F) + goto yy543; + if (yych <= 0x9F) + goto yy557; + goto yy543; + yy550: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= 0x8F) + goto yy543; + if (yych <= 0xBF) + goto yy559; + goto yy543; + yy551: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= 0x7F) + goto yy543; + if (yych <= 0xBF) + goto yy559; + goto yy543; + yy552: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= 0x7F) + goto yy543; + if (yych <= 0x8F) + goto yy559; + goto yy543; + yy553: + yych = *++p; + yy554: + if (yybm[0 + yych] & 64) { + goto yy553; + } + if (yych <= 0xEC) { + if (yych <= 0xC1) { + if (yych <= '\n') + goto yy555; + if (yych <= '-') + goto yy556; + } else { + if (yych <= 0xDF) + goto yy557; + if (yych <= 0xE0) + goto yy558; + goto yy559; + } + } else { + if (yych <= 0xF0) { + if (yych <= 0xED) + goto yy560; + if (yych <= 0xEF) + goto yy559; + goto yy561; + } else { + if (yych <= 0xF3) + goto yy562; + if (yych <= 0xF4) + goto yy563; + } + } + yy555: + p = marker; + if (yyaccept == 0) { + goto yy543; + } else { + goto yy566; + } + yy556: + yych = *++p; + if (yybm[0 + yych] & 64) { + goto yy553; + } + if (yych <= 0xEC) { + if (yych <= 0xC1) { + if (yych <= '\n') + goto yy555; + if (yych <= '-') + goto yy564; + goto yy555; + } else { + if (yych <= 0xDF) + goto yy557; + if (yych <= 0xE0) + goto yy558; + goto yy559; + } + } else { + if (yych <= 0xF0) { + if (yych <= 0xED) + goto yy560; + if (yych <= 0xEF) + goto yy559; + goto yy561; + } else { + if (yych <= 0xF3) + goto yy562; + if (yych <= 0xF4) + goto yy563; + goto yy555; + } + } + yy557: + yych = *++p; + if (yych <= 0x7F) + goto yy555; + if (yych <= 0xBF) + goto yy553; + goto yy555; + yy558: + yych = *++p; + if (yych <= 0x9F) + goto yy555; + if (yych <= 0xBF) + goto yy557; + goto yy555; + yy559: + yych = *++p; + if (yych <= 0x7F) + goto yy555; + if (yych <= 0xBF) + goto yy557; + goto yy555; + yy560: + yych = *++p; + if (yych <= 0x7F) + goto yy555; + if (yych <= 0x9F) + goto yy557; + goto yy555; + yy561: + yych = *++p; + if (yych <= 0x8F) + goto yy555; + if (yych <= 0xBF) + goto yy559; + goto yy555; + yy562: + yych = *++p; + if (yych <= 0x7F) + goto yy555; + if (yych <= 0xBF) + goto yy559; + goto yy555; + yy563: + yych = *++p; + if (yych <= 0x7F) + goto yy555; + if (yych <= 0x8F) + goto yy559; + goto yy555; + yy564: + yych = *++p; + if (yybm[0 + yych] & 128) { + goto yy564; + } + if (yych <= 0xDF) { + if (yych <= '=') { + if (yych <= 0x00) + goto yy555; + if (yych == '\n') + goto yy555; + goto yy553; + } else { + if (yych <= '>') + goto yy565; + if (yych <= 0x7F) + goto yy553; + if (yych <= 0xC1) + goto yy555; + goto yy557; + } + } else { + if (yych <= 0xEF) { + if (yych <= 0xE0) + goto yy558; + if (yych == 0xED) + goto yy560; + goto yy559; + } else { + if (yych <= 0xF0) + goto yy561; + if (yych <= 0xF3) + goto yy562; + if (yych <= 0xF4) + goto yy563; + goto yy555; + } + } + yy565: + yyaccept = 1; + yych = *(marker = ++p); + if (yybm[0 + yych] & 64) { + goto yy553; + } + if (yych <= 0xEC) { + if (yych <= 0xC1) { + if (yych <= '\n') + goto yy566; + if (yych <= '-') + goto yy556; + } else { + if (yych <= 0xDF) + goto yy557; + if (yych <= 0xE0) + goto yy558; + goto yy559; + } + } else { + if (yych <= 0xF0) { + if (yych <= 0xED) + goto yy560; + if (yych <= 0xEF) + goto yy559; + goto yy561; + } else { + if (yych <= 0xF3) + goto yy562; + if (yych <= 0xF4) + goto yy563; + } + } + yy566 : { return (bufsize_t)(p - start); } + } } // Try to match an HTML block end line of type 3 -bufsize_t _scan_html_block_end_3(const unsigned char *p) -{ +bufsize_t _scan_html_block_end_3(const unsigned char *p) { const unsigned char *marker = NULL; const unsigned char *start = p; -{ - unsigned char yych; - unsigned int yyaccept = 0; - static const unsigned char yybm[] = { - 0, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 0, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 128, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - }; - yych = *p; - if (yych <= 0xDF) { - if (yych <= '>') { - if (yych <= 0x00) goto yy640; - if (yych != '\n') goto yy642; - } else { - if (yych <= '?') goto yy643; - if (yych <= 0x7F) goto yy642; - if (yych >= 0xC2) goto yy644; - } - } else { - if (yych <= 0xEF) { - if (yych <= 0xE0) goto yy645; - if (yych == 0xED) goto yy647; - goto yy646; - } else { - if (yych <= 0xF0) goto yy648; - if (yych <= 0xF3) goto yy649; - if (yych <= 0xF4) goto yy650; - } - } -yy640: - ++p; -yy641: - { return 0; } -yy642: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= '\n') { - if (yych <= 0x00) goto yy641; - if (yych <= '\t') goto yy652; - goto yy641; - } else { - if (yych <= 0x7F) goto yy652; - if (yych <= 0xC1) goto yy641; - if (yych <= 0xF4) goto yy652; - goto yy641; - } -yy643: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= '=') { - if (yych <= 0x00) goto yy641; - if (yych == '\n') goto yy641; - goto yy652; - } else { - if (yych <= 0x7F) { - if (yych <= '>') goto yy663; - goto yy652; - } else { - if (yych <= 0xC1) goto yy641; - if (yych <= 0xF4) goto yy652; - goto yy641; - } - } -yy644: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= 0x7F) goto yy641; - if (yych <= 0xBF) goto yy651; - goto yy641; -yy645: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= 0x9F) goto yy641; - if (yych <= 0xBF) goto yy656; - goto yy641; -yy646: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= 0x7F) goto yy641; - if (yych <= 0xBF) goto yy656; - goto yy641; -yy647: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= 0x7F) goto yy641; - if (yych <= 0x9F) goto yy656; - goto yy641; -yy648: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= 0x8F) goto yy641; - if (yych <= 0xBF) goto yy658; - goto yy641; -yy649: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= 0x7F) goto yy641; - if (yych <= 0xBF) goto yy658; - goto yy641; -yy650: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= 0x7F) goto yy641; - if (yych <= 0x8F) goto yy658; - goto yy641; -yy651: - yych = *++p; -yy652: - if (yybm[0+yych] & 64) { - goto yy651; - } - if (yych <= 0xEC) { - if (yych <= 0xC1) { - if (yych <= '\n') goto yy653; - if (yych <= '?') goto yy654; - } else { - if (yych <= 0xDF) goto yy656; - if (yych <= 0xE0) goto yy657; - goto yy658; - } - } else { - if (yych <= 0xF0) { - if (yych <= 0xED) goto yy659; - if (yych <= 0xEF) goto yy658; - goto yy660; - } else { - if (yych <= 0xF3) goto yy661; - if (yych <= 0xF4) goto yy662; - } - } -yy653: - p = marker; - if (yyaccept == 0) { - goto yy641; - } else { - goto yy664; - } -yy654: - yych = *++p; - if (yybm[0+yych] & 128) { - goto yy654; - } - if (yych <= 0xDF) { - if (yych <= '=') { - if (yych <= 0x00) goto yy653; - if (yych == '\n') goto yy653; - goto yy651; - } else { - if (yych <= '>') goto yy663; - if (yych <= 0x7F) goto yy651; - if (yych <= 0xC1) goto yy653; - } - } else { - if (yych <= 0xEF) { - if (yych <= 0xE0) goto yy657; - if (yych == 0xED) goto yy659; - goto yy658; - } else { - if (yych <= 0xF0) goto yy660; - if (yych <= 0xF3) goto yy661; - if (yych <= 0xF4) goto yy662; - goto yy653; - } - } -yy656: - yych = *++p; - if (yych <= 0x7F) goto yy653; - if (yych <= 0xBF) goto yy651; - goto yy653; -yy657: - yych = *++p; - if (yych <= 0x9F) goto yy653; - if (yych <= 0xBF) goto yy656; - goto yy653; -yy658: - yych = *++p; - if (yych <= 0x7F) goto yy653; - if (yych <= 0xBF) goto yy656; - goto yy653; -yy659: - yych = *++p; - if (yych <= 0x7F) goto yy653; - if (yych <= 0x9F) goto yy656; - goto yy653; -yy660: - yych = *++p; - if (yych <= 0x8F) goto yy653; - if (yych <= 0xBF) goto yy658; - goto yy653; -yy661: - yych = *++p; - if (yych <= 0x7F) goto yy653; - if (yych <= 0xBF) goto yy658; - goto yy653; -yy662: - yych = *++p; - if (yych <= 0x7F) goto yy653; - if (yych <= 0x8F) goto yy658; - goto yy653; -yy663: - yyaccept = 1; - yych = *(marker = ++p); - if (yybm[0+yych] & 64) { - goto yy651; - } - if (yych <= 0xEC) { - if (yych <= 0xC1) { - if (yych <= '\n') goto yy664; - if (yych <= '?') goto yy654; - } else { - if (yych <= 0xDF) goto yy656; - if (yych <= 0xE0) goto yy657; - goto yy658; - } - } else { - if (yych <= 0xF0) { - if (yych <= 0xED) goto yy659; - if (yych <= 0xEF) goto yy658; - goto yy660; - } else { - if (yych <= 0xF3) goto yy661; - if (yych <= 0xF4) goto yy662; - } - } -yy664: - { return (bufsize_t)(p - start); } -} - + { + unsigned char yych; + unsigned int yyaccept = 0; + static const unsigned char yybm[] = { + 0, 64, 64, 64, 64, 64, 64, 64, 64, 64, 0, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 128, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + }; + yych = *p; + if (yych <= 0xDF) { + if (yych <= '>') { + if (yych <= 0x00) + goto yy568; + if (yych != '\n') + goto yy570; + } else { + if (yych <= '?') + goto yy571; + if (yych <= 0x7F) + goto yy570; + if (yych >= 0xC2) + goto yy572; + } + } else { + if (yych <= 0xEF) { + if (yych <= 0xE0) + goto yy573; + if (yych == 0xED) + goto yy575; + goto yy574; + } else { + if (yych <= 0xF0) + goto yy576; + if (yych <= 0xF3) + goto yy577; + if (yych <= 0xF4) + goto yy578; + } + } + yy568: + ++p; + yy569 : { return 0; } + yy570: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= '\n') { + if (yych <= 0x00) + goto yy569; + if (yych <= '\t') + goto yy580; + goto yy569; + } else { + if (yych <= 0x7F) + goto yy580; + if (yych <= 0xC1) + goto yy569; + if (yych <= 0xF4) + goto yy580; + goto yy569; + } + yy571: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= '=') { + if (yych <= 0x00) + goto yy569; + if (yych == '\n') + goto yy569; + goto yy580; + } else { + if (yych <= 0x7F) { + if (yych <= '>') + goto yy590; + goto yy580; + } else { + if (yych <= 0xC1) + goto yy569; + if (yych <= 0xF4) + goto yy580; + goto yy569; + } + } + yy572: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= 0x7F) + goto yy569; + if (yych <= 0xBF) + goto yy579; + goto yy569; + yy573: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= 0x9F) + goto yy569; + if (yych <= 0xBF) + goto yy583; + goto yy569; + yy574: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= 0x7F) + goto yy569; + if (yych <= 0xBF) + goto yy583; + goto yy569; + yy575: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= 0x7F) + goto yy569; + if (yych <= 0x9F) + goto yy583; + goto yy569; + yy576: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= 0x8F) + goto yy569; + if (yych <= 0xBF) + goto yy585; + goto yy569; + yy577: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= 0x7F) + goto yy569; + if (yych <= 0xBF) + goto yy585; + goto yy569; + yy578: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= 0x7F) + goto yy569; + if (yych <= 0x8F) + goto yy585; + goto yy569; + yy579: + yych = *++p; + yy580: + if (yybm[0 + yych] & 64) { + goto yy579; + } + if (yych <= 0xEC) { + if (yych <= 0xC1) { + if (yych <= '\n') + goto yy581; + if (yych <= '?') + goto yy582; + } else { + if (yych <= 0xDF) + goto yy583; + if (yych <= 0xE0) + goto yy584; + goto yy585; + } + } else { + if (yych <= 0xF0) { + if (yych <= 0xED) + goto yy586; + if (yych <= 0xEF) + goto yy585; + goto yy587; + } else { + if (yych <= 0xF3) + goto yy588; + if (yych <= 0xF4) + goto yy589; + } + } + yy581: + p = marker; + if (yyaccept == 0) { + goto yy569; + } else { + goto yy591; + } + yy582: + yych = *++p; + if (yybm[0 + yych] & 128) { + goto yy582; + } + if (yych <= 0xDF) { + if (yych <= '=') { + if (yych <= 0x00) + goto yy581; + if (yych == '\n') + goto yy581; + goto yy579; + } else { + if (yych <= '>') + goto yy590; + if (yych <= 0x7F) + goto yy579; + if (yych <= 0xC1) + goto yy581; + } + } else { + if (yych <= 0xEF) { + if (yych <= 0xE0) + goto yy584; + if (yych == 0xED) + goto yy586; + goto yy585; + } else { + if (yych <= 0xF0) + goto yy587; + if (yych <= 0xF3) + goto yy588; + if (yych <= 0xF4) + goto yy589; + goto yy581; + } + } + yy583: + yych = *++p; + if (yych <= 0x7F) + goto yy581; + if (yych <= 0xBF) + goto yy579; + goto yy581; + yy584: + yych = *++p; + if (yych <= 0x9F) + goto yy581; + if (yych <= 0xBF) + goto yy583; + goto yy581; + yy585: + yych = *++p; + if (yych <= 0x7F) + goto yy581; + if (yych <= 0xBF) + goto yy583; + goto yy581; + yy586: + yych = *++p; + if (yych <= 0x7F) + goto yy581; + if (yych <= 0x9F) + goto yy583; + goto yy581; + yy587: + yych = *++p; + if (yych <= 0x8F) + goto yy581; + if (yych <= 0xBF) + goto yy585; + goto yy581; + yy588: + yych = *++p; + if (yych <= 0x7F) + goto yy581; + if (yych <= 0xBF) + goto yy585; + goto yy581; + yy589: + yych = *++p; + if (yych <= 0x7F) + goto yy581; + if (yych <= 0x8F) + goto yy585; + goto yy581; + yy590: + yyaccept = 1; + yych = *(marker = ++p); + if (yybm[0 + yych] & 64) { + goto yy579; + } + if (yych <= 0xEC) { + if (yych <= 0xC1) { + if (yych <= '\n') + goto yy591; + if (yych <= '?') + goto yy582; + } else { + if (yych <= 0xDF) + goto yy583; + if (yych <= 0xE0) + goto yy584; + goto yy585; + } + } else { + if (yych <= 0xF0) { + if (yych <= 0xED) + goto yy586; + if (yych <= 0xEF) + goto yy585; + goto yy587; + } else { + if (yych <= 0xF3) + goto yy588; + if (yych <= 0xF4) + goto yy589; + } + } + yy591 : { return (bufsize_t)(p - start); } + } } // Try to match an HTML block end line of type 4 -bufsize_t _scan_html_block_end_4(const unsigned char *p) -{ +bufsize_t _scan_html_block_end_4(const unsigned char *p) { const unsigned char *marker = NULL; const unsigned char *start = p; -{ - unsigned char yych; - unsigned int yyaccept = 0; - static const unsigned char yybm[] = { - 0, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 0, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 64, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 128, 128, 128, 128, 128, 128, 128, 128, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - }; - yych = *p; - if (yybm[0+yych] & 64) { - goto yy670; - } - if (yych <= 0xE0) { - if (yych <= '\n') { - if (yych <= 0x00) goto yy667; - if (yych <= '\t') goto yy669; - } else { - if (yych <= 0x7F) goto yy669; - if (yych <= 0xC1) goto yy667; - if (yych <= 0xDF) goto yy673; - goto yy674; - } - } else { - if (yych <= 0xEF) { - if (yych == 0xED) goto yy676; - goto yy675; - } else { - if (yych <= 0xF0) goto yy677; - if (yych <= 0xF3) goto yy678; - if (yych <= 0xF4) goto yy679; - } - } -yy667: - ++p; -yy668: - { return 0; } -yy669: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= '\n') { - if (yych <= 0x00) goto yy668; - if (yych <= '\t') goto yy681; - goto yy668; - } else { - if (yych <= 0x7F) goto yy681; - if (yych <= 0xC1) goto yy668; - if (yych <= 0xF4) goto yy681; - goto yy668; - } -yy670: - yyaccept = 1; - yych = *(marker = ++p); - if (yybm[0+yych] & 128) { - goto yy680; - } - if (yych <= 0xEC) { - if (yych <= 0xC1) { - if (yych <= '\n') goto yy672; - if (yych <= '>') goto yy670; - } else { - if (yych <= 0xDF) goto yy683; - if (yych <= 0xE0) goto yy684; - goto yy685; - } - } else { - if (yych <= 0xF0) { - if (yych <= 0xED) goto yy686; - if (yych <= 0xEF) goto yy685; - goto yy687; - } else { - if (yych <= 0xF3) goto yy688; - if (yych <= 0xF4) goto yy689; - } - } -yy672: - { return (bufsize_t)(p - start); } -yy673: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= 0x7F) goto yy668; - if (yych <= 0xBF) goto yy680; - goto yy668; -yy674: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= 0x9F) goto yy668; - if (yych <= 0xBF) goto yy683; - goto yy668; -yy675: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= 0x7F) goto yy668; - if (yych <= 0xBF) goto yy683; - goto yy668; -yy676: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= 0x7F) goto yy668; - if (yych <= 0x9F) goto yy683; - goto yy668; -yy677: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= 0x8F) goto yy668; - if (yych <= 0xBF) goto yy685; - goto yy668; -yy678: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= 0x7F) goto yy668; - if (yych <= 0xBF) goto yy685; - goto yy668; -yy679: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= 0x7F) goto yy668; - if (yych <= 0x8F) goto yy685; - goto yy668; -yy680: - yych = *++p; -yy681: - if (yybm[0+yych] & 128) { - goto yy680; - } - if (yych <= 0xEC) { - if (yych <= 0xC1) { - if (yych <= '\n') goto yy682; - if (yych <= '>') goto yy670; - } else { - if (yych <= 0xDF) goto yy683; - if (yych <= 0xE0) goto yy684; - goto yy685; - } - } else { - if (yych <= 0xF0) { - if (yych <= 0xED) goto yy686; - if (yych <= 0xEF) goto yy685; - goto yy687; - } else { - if (yych <= 0xF3) goto yy688; - if (yych <= 0xF4) goto yy689; - } - } -yy682: - p = marker; - if (yyaccept == 0) { - goto yy668; - } else { - goto yy672; - } -yy683: - yych = *++p; - if (yych <= 0x7F) goto yy682; - if (yych <= 0xBF) goto yy680; - goto yy682; -yy684: - yych = *++p; - if (yych <= 0x9F) goto yy682; - if (yych <= 0xBF) goto yy683; - goto yy682; -yy685: - yych = *++p; - if (yych <= 0x7F) goto yy682; - if (yych <= 0xBF) goto yy683; - goto yy682; -yy686: - yych = *++p; - if (yych <= 0x7F) goto yy682; - if (yych <= 0x9F) goto yy683; - goto yy682; -yy687: - yych = *++p; - if (yych <= 0x8F) goto yy682; - if (yych <= 0xBF) goto yy685; - goto yy682; -yy688: - yych = *++p; - if (yych <= 0x7F) goto yy682; - if (yych <= 0xBF) goto yy685; - goto yy682; -yy689: - yych = *++p; - if (yych <= 0x7F) goto yy682; - if (yych <= 0x8F) goto yy685; - goto yy682; -} - + { + unsigned char yych; + unsigned int yyaccept = 0; + static const unsigned char yybm[] = { + 0, 128, 128, 128, 128, 128, 128, 128, 128, 128, 0, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 64, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + }; + yych = *p; + if (yybm[0 + yych] & 64) { + goto yy596; + } + if (yych <= 0xE0) { + if (yych <= '\n') { + if (yych <= 0x00) + goto yy593; + if (yych <= '\t') + goto yy595; + } else { + if (yych <= 0x7F) + goto yy595; + if (yych <= 0xC1) + goto yy593; + if (yych <= 0xDF) + goto yy598; + goto yy599; + } + } else { + if (yych <= 0xEF) { + if (yych == 0xED) + goto yy601; + goto yy600; + } else { + if (yych <= 0xF0) + goto yy602; + if (yych <= 0xF3) + goto yy603; + if (yych <= 0xF4) + goto yy604; + } + } + yy593: + ++p; + yy594 : { return 0; } + yy595: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= '\n') { + if (yych <= 0x00) + goto yy594; + if (yych <= '\t') + goto yy606; + goto yy594; + } else { + if (yych <= 0x7F) + goto yy606; + if (yych <= 0xC1) + goto yy594; + if (yych <= 0xF4) + goto yy606; + goto yy594; + } + yy596: + yyaccept = 1; + yych = *(marker = ++p); + if (yybm[0 + yych] & 128) { + goto yy605; + } + if (yych <= 0xEC) { + if (yych <= 0xC1) { + if (yych <= '\n') + goto yy597; + if (yych <= '>') + goto yy596; + } else { + if (yych <= 0xDF) + goto yy608; + if (yych <= 0xE0) + goto yy609; + goto yy610; + } + } else { + if (yych <= 0xF0) { + if (yych <= 0xED) + goto yy611; + if (yych <= 0xEF) + goto yy610; + goto yy612; + } else { + if (yych <= 0xF3) + goto yy613; + if (yych <= 0xF4) + goto yy614; + } + } + yy597 : { return (bufsize_t)(p - start); } + yy598: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= 0x7F) + goto yy594; + if (yych <= 0xBF) + goto yy605; + goto yy594; + yy599: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= 0x9F) + goto yy594; + if (yych <= 0xBF) + goto yy608; + goto yy594; + yy600: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= 0x7F) + goto yy594; + if (yych <= 0xBF) + goto yy608; + goto yy594; + yy601: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= 0x7F) + goto yy594; + if (yych <= 0x9F) + goto yy608; + goto yy594; + yy602: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= 0x8F) + goto yy594; + if (yych <= 0xBF) + goto yy610; + goto yy594; + yy603: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= 0x7F) + goto yy594; + if (yych <= 0xBF) + goto yy610; + goto yy594; + yy604: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= 0x7F) + goto yy594; + if (yych <= 0x8F) + goto yy610; + goto yy594; + yy605: + yych = *++p; + yy606: + if (yybm[0 + yych] & 128) { + goto yy605; + } + if (yych <= 0xEC) { + if (yych <= 0xC1) { + if (yych <= '\n') + goto yy607; + if (yych <= '>') + goto yy596; + } else { + if (yych <= 0xDF) + goto yy608; + if (yych <= 0xE0) + goto yy609; + goto yy610; + } + } else { + if (yych <= 0xF0) { + if (yych <= 0xED) + goto yy611; + if (yych <= 0xEF) + goto yy610; + goto yy612; + } else { + if (yych <= 0xF3) + goto yy613; + if (yych <= 0xF4) + goto yy614; + } + } + yy607: + p = marker; + if (yyaccept == 0) { + goto yy594; + } else { + goto yy597; + } + yy608: + yych = *++p; + if (yych <= 0x7F) + goto yy607; + if (yych <= 0xBF) + goto yy605; + goto yy607; + yy609: + yych = *++p; + if (yych <= 0x9F) + goto yy607; + if (yych <= 0xBF) + goto yy608; + goto yy607; + yy610: + yych = *++p; + if (yych <= 0x7F) + goto yy607; + if (yych <= 0xBF) + goto yy608; + goto yy607; + yy611: + yych = *++p; + if (yych <= 0x7F) + goto yy607; + if (yych <= 0x9F) + goto yy608; + goto yy607; + yy612: + yych = *++p; + if (yych <= 0x8F) + goto yy607; + if (yych <= 0xBF) + goto yy610; + goto yy607; + yy613: + yych = *++p; + if (yych <= 0x7F) + goto yy607; + if (yych <= 0xBF) + goto yy610; + goto yy607; + yy614: + yych = *++p; + if (yych <= 0x7F) + goto yy607; + if (yych <= 0x8F) + goto yy610; + goto yy607; + } } // Try to match an HTML block end line of type 5 -bufsize_t _scan_html_block_end_5(const unsigned char *p) -{ +bufsize_t _scan_html_block_end_5(const unsigned char *p) { const unsigned char *marker = NULL; const unsigned char *start = p; -{ - unsigned char yych; - unsigned int yyaccept = 0; - static const unsigned char yybm[] = { - 0, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 0, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 128, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - }; - yych = *p; - if (yych <= 0xDF) { - if (yych <= '\\') { - if (yych <= 0x00) goto yy692; - if (yych != '\n') goto yy694; - } else { - if (yych <= ']') goto yy695; - if (yych <= 0x7F) goto yy694; - if (yych >= 0xC2) goto yy696; - } - } else { - if (yych <= 0xEF) { - if (yych <= 0xE0) goto yy697; - if (yych == 0xED) goto yy699; - goto yy698; - } else { - if (yych <= 0xF0) goto yy700; - if (yych <= 0xF3) goto yy701; - if (yych <= 0xF4) goto yy702; - } - } -yy692: - ++p; -yy693: - { return 0; } -yy694: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= '\n') { - if (yych <= 0x00) goto yy693; - if (yych <= '\t') goto yy704; - goto yy693; - } else { - if (yych <= 0x7F) goto yy704; - if (yych <= 0xC1) goto yy693; - if (yych <= 0xF4) goto yy704; - goto yy693; - } -yy695: - yyaccept = 0; - yych = *(marker = ++p); - if (yybm[0+yych] & 128) { - goto yy714; - } - if (yych <= '\n') { - if (yych <= 0x00) goto yy693; - if (yych <= '\t') goto yy704; - goto yy693; - } else { - if (yych <= 0x7F) goto yy704; - if (yych <= 0xC1) goto yy693; - if (yych <= 0xF4) goto yy704; - goto yy693; - } -yy696: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= 0x7F) goto yy693; - if (yych <= 0xBF) goto yy703; - goto yy693; -yy697: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= 0x9F) goto yy693; - if (yych <= 0xBF) goto yy707; - goto yy693; -yy698: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= 0x7F) goto yy693; - if (yych <= 0xBF) goto yy707; - goto yy693; -yy699: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= 0x7F) goto yy693; - if (yych <= 0x9F) goto yy707; - goto yy693; -yy700: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= 0x8F) goto yy693; - if (yych <= 0xBF) goto yy709; - goto yy693; -yy701: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= 0x7F) goto yy693; - if (yych <= 0xBF) goto yy709; - goto yy693; -yy702: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= 0x7F) goto yy693; - if (yych <= 0x8F) goto yy709; - goto yy693; -yy703: - yych = *++p; -yy704: - if (yybm[0+yych] & 64) { - goto yy703; - } - if (yych <= 0xEC) { - if (yych <= 0xC1) { - if (yych <= '\n') goto yy705; - if (yych <= ']') goto yy706; - } else { - if (yych <= 0xDF) goto yy707; - if (yych <= 0xE0) goto yy708; - goto yy709; - } - } else { - if (yych <= 0xF0) { - if (yych <= 0xED) goto yy710; - if (yych <= 0xEF) goto yy709; - goto yy711; - } else { - if (yych <= 0xF3) goto yy712; - if (yych <= 0xF4) goto yy713; - } - } -yy705: - p = marker; - if (yyaccept == 0) { - goto yy693; - } else { - goto yy717; - } -yy706: - yych = *++p; - if (yybm[0+yych] & 64) { - goto yy703; - } - if (yych <= 0xEC) { - if (yych <= 0xC1) { - if (yych <= '\n') goto yy705; - if (yych <= ']') goto yy714; - goto yy705; - } else { - if (yych <= 0xDF) goto yy707; - if (yych <= 0xE0) goto yy708; - goto yy709; - } - } else { - if (yych <= 0xF0) { - if (yych <= 0xED) goto yy710; - if (yych <= 0xEF) goto yy709; - goto yy711; - } else { - if (yych <= 0xF3) goto yy712; - if (yych <= 0xF4) goto yy713; - goto yy705; - } - } -yy707: - yych = *++p; - if (yych <= 0x7F) goto yy705; - if (yych <= 0xBF) goto yy703; - goto yy705; -yy708: - yych = *++p; - if (yych <= 0x9F) goto yy705; - if (yych <= 0xBF) goto yy707; - goto yy705; -yy709: - yych = *++p; - if (yych <= 0x7F) goto yy705; - if (yych <= 0xBF) goto yy707; - goto yy705; -yy710: - yych = *++p; - if (yych <= 0x7F) goto yy705; - if (yych <= 0x9F) goto yy707; - goto yy705; -yy711: - yych = *++p; - if (yych <= 0x8F) goto yy705; - if (yych <= 0xBF) goto yy709; - goto yy705; -yy712: - yych = *++p; - if (yych <= 0x7F) goto yy705; - if (yych <= 0xBF) goto yy709; - goto yy705; -yy713: - yych = *++p; - if (yych <= 0x7F) goto yy705; - if (yych <= 0x8F) goto yy709; - goto yy705; -yy714: - yych = *++p; - if (yybm[0+yych] & 128) { - goto yy714; - } - if (yych <= 0xDF) { - if (yych <= '=') { - if (yych <= 0x00) goto yy705; - if (yych == '\n') goto yy705; - goto yy703; - } else { - if (yych <= '>') goto yy716; - if (yych <= 0x7F) goto yy703; - if (yych <= 0xC1) goto yy705; - goto yy707; - } - } else { - if (yych <= 0xEF) { - if (yych <= 0xE0) goto yy708; - if (yych == 0xED) goto yy710; - goto yy709; - } else { - if (yych <= 0xF0) goto yy711; - if (yych <= 0xF3) goto yy712; - if (yych <= 0xF4) goto yy713; - goto yy705; - } - } -yy716: - yyaccept = 1; - yych = *(marker = ++p); - if (yybm[0+yych] & 64) { - goto yy703; - } - if (yych <= 0xEC) { - if (yych <= 0xC1) { - if (yych <= '\n') goto yy717; - if (yych <= ']') goto yy706; - } else { - if (yych <= 0xDF) goto yy707; - if (yych <= 0xE0) goto yy708; - goto yy709; - } - } else { - if (yych <= 0xF0) { - if (yych <= 0xED) goto yy710; - if (yych <= 0xEF) goto yy709; - goto yy711; - } else { - if (yych <= 0xF3) goto yy712; - if (yych <= 0xF4) goto yy713; - } - } -yy717: - { return (bufsize_t)(p - start); } -} - + { + unsigned char yych; + unsigned int yyaccept = 0; + static const unsigned char yybm[] = { + 0, 64, 64, 64, 64, 64, 64, 64, 64, 64, 0, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 128, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + }; + yych = *p; + if (yych <= 0xDF) { + if (yych <= '\\') { + if (yych <= 0x00) + goto yy616; + if (yych != '\n') + goto yy618; + } else { + if (yych <= ']') + goto yy619; + if (yych <= 0x7F) + goto yy618; + if (yych >= 0xC2) + goto yy620; + } + } else { + if (yych <= 0xEF) { + if (yych <= 0xE0) + goto yy621; + if (yych == 0xED) + goto yy623; + goto yy622; + } else { + if (yych <= 0xF0) + goto yy624; + if (yych <= 0xF3) + goto yy625; + if (yych <= 0xF4) + goto yy626; + } + } + yy616: + ++p; + yy617 : { return 0; } + yy618: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= '\n') { + if (yych <= 0x00) + goto yy617; + if (yych <= '\t') + goto yy628; + goto yy617; + } else { + if (yych <= 0x7F) + goto yy628; + if (yych <= 0xC1) + goto yy617; + if (yych <= 0xF4) + goto yy628; + goto yy617; + } + yy619: + yyaccept = 0; + yych = *(marker = ++p); + if (yybm[0 + yych] & 128) { + goto yy638; + } + if (yych <= '\n') { + if (yych <= 0x00) + goto yy617; + if (yych <= '\t') + goto yy628; + goto yy617; + } else { + if (yych <= 0x7F) + goto yy628; + if (yych <= 0xC1) + goto yy617; + if (yych <= 0xF4) + goto yy628; + goto yy617; + } + yy620: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= 0x7F) + goto yy617; + if (yych <= 0xBF) + goto yy627; + goto yy617; + yy621: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= 0x9F) + goto yy617; + if (yych <= 0xBF) + goto yy631; + goto yy617; + yy622: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= 0x7F) + goto yy617; + if (yych <= 0xBF) + goto yy631; + goto yy617; + yy623: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= 0x7F) + goto yy617; + if (yych <= 0x9F) + goto yy631; + goto yy617; + yy624: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= 0x8F) + goto yy617; + if (yych <= 0xBF) + goto yy633; + goto yy617; + yy625: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= 0x7F) + goto yy617; + if (yych <= 0xBF) + goto yy633; + goto yy617; + yy626: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= 0x7F) + goto yy617; + if (yych <= 0x8F) + goto yy633; + goto yy617; + yy627: + yych = *++p; + yy628: + if (yybm[0 + yych] & 64) { + goto yy627; + } + if (yych <= 0xEC) { + if (yych <= 0xC1) { + if (yych <= '\n') + goto yy629; + if (yych <= ']') + goto yy630; + } else { + if (yych <= 0xDF) + goto yy631; + if (yych <= 0xE0) + goto yy632; + goto yy633; + } + } else { + if (yych <= 0xF0) { + if (yych <= 0xED) + goto yy634; + if (yych <= 0xEF) + goto yy633; + goto yy635; + } else { + if (yych <= 0xF3) + goto yy636; + if (yych <= 0xF4) + goto yy637; + } + } + yy629: + p = marker; + if (yyaccept == 0) { + goto yy617; + } else { + goto yy640; + } + yy630: + yych = *++p; + if (yybm[0 + yych] & 64) { + goto yy627; + } + if (yych <= 0xEC) { + if (yych <= 0xC1) { + if (yych <= '\n') + goto yy629; + if (yych <= ']') + goto yy638; + goto yy629; + } else { + if (yych <= 0xDF) + goto yy631; + if (yych <= 0xE0) + goto yy632; + goto yy633; + } + } else { + if (yych <= 0xF0) { + if (yych <= 0xED) + goto yy634; + if (yych <= 0xEF) + goto yy633; + goto yy635; + } else { + if (yych <= 0xF3) + goto yy636; + if (yych <= 0xF4) + goto yy637; + goto yy629; + } + } + yy631: + yych = *++p; + if (yych <= 0x7F) + goto yy629; + if (yych <= 0xBF) + goto yy627; + goto yy629; + yy632: + yych = *++p; + if (yych <= 0x9F) + goto yy629; + if (yych <= 0xBF) + goto yy631; + goto yy629; + yy633: + yych = *++p; + if (yych <= 0x7F) + goto yy629; + if (yych <= 0xBF) + goto yy631; + goto yy629; + yy634: + yych = *++p; + if (yych <= 0x7F) + goto yy629; + if (yych <= 0x9F) + goto yy631; + goto yy629; + yy635: + yych = *++p; + if (yych <= 0x8F) + goto yy629; + if (yych <= 0xBF) + goto yy633; + goto yy629; + yy636: + yych = *++p; + if (yych <= 0x7F) + goto yy629; + if (yych <= 0xBF) + goto yy633; + goto yy629; + yy637: + yych = *++p; + if (yych <= 0x7F) + goto yy629; + if (yych <= 0x8F) + goto yy633; + goto yy629; + yy638: + yych = *++p; + if (yybm[0 + yych] & 128) { + goto yy638; + } + if (yych <= 0xDF) { + if (yych <= '=') { + if (yych <= 0x00) + goto yy629; + if (yych == '\n') + goto yy629; + goto yy627; + } else { + if (yych <= '>') + goto yy639; + if (yych <= 0x7F) + goto yy627; + if (yych <= 0xC1) + goto yy629; + goto yy631; + } + } else { + if (yych <= 0xEF) { + if (yych <= 0xE0) + goto yy632; + if (yych == 0xED) + goto yy634; + goto yy633; + } else { + if (yych <= 0xF0) + goto yy635; + if (yych <= 0xF3) + goto yy636; + if (yych <= 0xF4) + goto yy637; + goto yy629; + } + } + yy639: + yyaccept = 1; + yych = *(marker = ++p); + if (yybm[0 + yych] & 64) { + goto yy627; + } + if (yych <= 0xEC) { + if (yych <= 0xC1) { + if (yych <= '\n') + goto yy640; + if (yych <= ']') + goto yy630; + } else { + if (yych <= 0xDF) + goto yy631; + if (yych <= 0xE0) + goto yy632; + goto yy633; + } + } else { + if (yych <= 0xF0) { + if (yych <= 0xED) + goto yy634; + if (yych <= 0xEF) + goto yy633; + goto yy635; + } else { + if (yych <= 0xF3) + goto yy636; + if (yych <= 0xF4) + goto yy637; + } + } + yy640 : { return (bufsize_t)(p - start); } + } } // Try to match a link title (in single quotes, in double quotes, or // in parentheses), returning number of chars matched. Allow one // level of internal nesting (quotes within quotes). -bufsize_t _scan_link_title(const unsigned char *p) -{ +bufsize_t _scan_link_title(const unsigned char *p) { const unsigned char *marker = NULL; const unsigned char *start = p; -{ - unsigned char yych; - unsigned int yyaccept = 0; - static const unsigned char yybm[] = { - 0, 208, 208, 208, 208, 208, 208, 208, - 208, 208, 208, 208, 208, 208, 208, 208, - 208, 208, 208, 208, 208, 208, 208, 208, - 208, 208, 208, 208, 208, 208, 208, 208, - 208, 208, 192, 208, 208, 208, 208, 144, - 80, 80, 208, 208, 208, 208, 208, 208, - 208, 208, 208, 208, 208, 208, 208, 208, - 208, 208, 208, 208, 208, 208, 208, 208, - 208, 208, 208, 208, 208, 208, 208, 208, - 208, 208, 208, 208, 208, 208, 208, 208, - 208, 208, 208, 208, 208, 208, 208, 208, - 208, 208, 208, 208, 32, 208, 208, 208, - 208, 208, 208, 208, 208, 208, 208, 208, - 208, 208, 208, 208, 208, 208, 208, 208, - 208, 208, 208, 208, 208, 208, 208, 208, - 208, 208, 208, 208, 208, 208, 208, 208, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - }; - yych = *p; - if (yych <= '&') { - if (yych == '"') goto yy722; - } else { - if (yych <= '\'') goto yy723; - if (yych <= '(') goto yy724; - } - ++p; -yy721: - { return 0; } -yy722: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= 0x00) goto yy721; - if (yych <= 0x7F) goto yy726; - if (yych <= 0xC1) goto yy721; - if (yych <= 0xF4) goto yy726; - goto yy721; -yy723: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= 0x00) goto yy721; - if (yych <= 0x7F) goto yy740; - if (yych <= 0xC1) goto yy721; - if (yych <= 0xF4) goto yy740; - goto yy721; -yy724: - yyaccept = 0; - yych = *(marker = ++p); - if (yych <= '(') { - if (yych <= 0x00) goto yy721; - if (yych <= '\'') goto yy753; - goto yy721; - } else { - if (yych <= 0x7F) goto yy753; - if (yych <= 0xC1) goto yy721; - if (yych <= 0xF4) goto yy753; - goto yy721; - } -yy725: - yych = *++p; -yy726: - if (yybm[0+yych] & 16) { - goto yy725; - } - if (yych <= 0xE0) { - if (yych <= '\\') { - if (yych <= 0x00) goto yy727; - if (yych <= '"') goto yy728; - goto yy730; - } else { - if (yych <= 0xC1) goto yy727; - if (yych <= 0xDF) goto yy732; - goto yy733; - } - } else { - if (yych <= 0xEF) { - if (yych == 0xED) goto yy735; - goto yy734; - } else { - if (yych <= 0xF0) goto yy736; - if (yych <= 0xF3) goto yy737; - if (yych <= 0xF4) goto yy738; - } - } -yy727: - p = marker; - if (yyaccept <= 1) { - if (yyaccept == 0) { - goto yy721; - } else { - goto yy729; - } - } else { - if (yyaccept == 2) { - goto yy742; - } else { - goto yy755; - } - } -yy728: - ++p; -yy729: - { return (bufsize_t)(p - start); } -yy730: - yych = *++p; - if (yybm[0+yych] & 16) { - goto yy725; - } - if (yych <= 0xE0) { - if (yych <= '\\') { - if (yych <= 0x00) goto yy727; - if (yych <= '"') goto yy765; - goto yy730; - } else { - if (yych <= 0xC1) goto yy727; - if (yych >= 0xE0) goto yy733; - } - } else { - if (yych <= 0xEF) { - if (yych == 0xED) goto yy735; - goto yy734; - } else { - if (yych <= 0xF0) goto yy736; - if (yych <= 0xF3) goto yy737; - if (yych <= 0xF4) goto yy738; - goto yy727; - } - } -yy732: - yych = *++p; - if (yych <= 0x7F) goto yy727; - if (yych <= 0xBF) goto yy725; - goto yy727; -yy733: - yych = *++p; - if (yych <= 0x9F) goto yy727; - if (yych <= 0xBF) goto yy732; - goto yy727; -yy734: - yych = *++p; - if (yych <= 0x7F) goto yy727; - if (yych <= 0xBF) goto yy732; - goto yy727; -yy735: - yych = *++p; - if (yych <= 0x7F) goto yy727; - if (yych <= 0x9F) goto yy732; - goto yy727; -yy736: - yych = *++p; - if (yych <= 0x8F) goto yy727; - if (yych <= 0xBF) goto yy734; - goto yy727; -yy737: - yych = *++p; - if (yych <= 0x7F) goto yy727; - if (yych <= 0xBF) goto yy734; - goto yy727; -yy738: - yych = *++p; - if (yych <= 0x7F) goto yy727; - if (yych <= 0x8F) goto yy734; - goto yy727; -yy739: - yych = *++p; -yy740: - if (yybm[0+yych] & 64) { - goto yy739; - } - if (yych <= 0xE0) { - if (yych <= '\\') { - if (yych <= 0x00) goto yy727; - if (yych >= '(') goto yy743; - } else { - if (yych <= 0xC1) goto yy727; - if (yych <= 0xDF) goto yy745; - goto yy746; - } - } else { - if (yych <= 0xEF) { - if (yych == 0xED) goto yy748; - goto yy747; - } else { - if (yych <= 0xF0) goto yy749; - if (yych <= 0xF3) goto yy750; - if (yych <= 0xF4) goto yy751; - goto yy727; - } - } -yy741: - ++p; -yy742: - { return (bufsize_t)(p - start); } -yy743: - yych = *++p; - if (yybm[0+yych] & 64) { - goto yy739; - } - if (yych <= 0xE0) { - if (yych <= '\\') { - if (yych <= 0x00) goto yy727; - if (yych <= '\'') goto yy766; - goto yy743; - } else { - if (yych <= 0xC1) goto yy727; - if (yych >= 0xE0) goto yy746; - } - } else { - if (yych <= 0xEF) { - if (yych == 0xED) goto yy748; - goto yy747; - } else { - if (yych <= 0xF0) goto yy749; - if (yych <= 0xF3) goto yy750; - if (yych <= 0xF4) goto yy751; - goto yy727; - } - } -yy745: - yych = *++p; - if (yych <= 0x7F) goto yy727; - if (yych <= 0xBF) goto yy739; - goto yy727; -yy746: - yych = *++p; - if (yych <= 0x9F) goto yy727; - if (yych <= 0xBF) goto yy745; - goto yy727; -yy747: - yych = *++p; - if (yych <= 0x7F) goto yy727; - if (yych <= 0xBF) goto yy745; - goto yy727; -yy748: - yych = *++p; - if (yych <= 0x7F) goto yy727; - if (yych <= 0x9F) goto yy745; - goto yy727; -yy749: - yych = *++p; - if (yych <= 0x8F) goto yy727; - if (yych <= 0xBF) goto yy747; - goto yy727; -yy750: - yych = *++p; - if (yych <= 0x7F) goto yy727; - if (yych <= 0xBF) goto yy747; - goto yy727; -yy751: - yych = *++p; - if (yych <= 0x7F) goto yy727; - if (yych <= 0x8F) goto yy747; - goto yy727; -yy752: - yych = *++p; -yy753: - if (yybm[0+yych] & 128) { - goto yy752; - } - if (yych <= 0xE0) { - if (yych <= '\\') { - if (yych <= '(') goto yy727; - if (yych >= '*') goto yy756; - } else { - if (yych <= 0xC1) goto yy727; - if (yych <= 0xDF) goto yy758; - goto yy759; - } - } else { - if (yych <= 0xEF) { - if (yych == 0xED) goto yy761; - goto yy760; - } else { - if (yych <= 0xF0) goto yy762; - if (yych <= 0xF3) goto yy763; - if (yych <= 0xF4) goto yy764; - goto yy727; - } - } -yy754: - ++p; -yy755: - { return (bufsize_t)(p - start); } -yy756: - yych = *++p; - if (yych <= 0xDF) { - if (yych <= '[') { - if (yych <= 0x00) goto yy727; - if (yych == ')') goto yy767; - goto yy752; - } else { - if (yych <= '\\') goto yy756; - if (yych <= 0x7F) goto yy752; - if (yych <= 0xC1) goto yy727; - } - } else { - if (yych <= 0xEF) { - if (yych <= 0xE0) goto yy759; - if (yych == 0xED) goto yy761; - goto yy760; - } else { - if (yych <= 0xF0) goto yy762; - if (yych <= 0xF3) goto yy763; - if (yych <= 0xF4) goto yy764; - goto yy727; - } - } -yy758: - yych = *++p; - if (yych <= 0x7F) goto yy727; - if (yych <= 0xBF) goto yy752; - goto yy727; -yy759: - yych = *++p; - if (yych <= 0x9F) goto yy727; - if (yych <= 0xBF) goto yy758; - goto yy727; -yy760: - yych = *++p; - if (yych <= 0x7F) goto yy727; - if (yych <= 0xBF) goto yy758; - goto yy727; -yy761: - yych = *++p; - if (yych <= 0x7F) goto yy727; - if (yych <= 0x9F) goto yy758; - goto yy727; -yy762: - yych = *++p; - if (yych <= 0x8F) goto yy727; - if (yych <= 0xBF) goto yy760; - goto yy727; -yy763: - yych = *++p; - if (yych <= 0x7F) goto yy727; - if (yych <= 0xBF) goto yy760; - goto yy727; -yy764: - yych = *++p; - if (yych <= 0x7F) goto yy727; - if (yych <= 0x8F) goto yy760; - goto yy727; -yy765: - yyaccept = 1; - yych = *(marker = ++p); - if (yybm[0+yych] & 16) { - goto yy725; - } - if (yych <= 0xE0) { - if (yych <= '\\') { - if (yych <= 0x00) goto yy729; - if (yych <= '"') goto yy728; - goto yy730; - } else { - if (yych <= 0xC1) goto yy729; - if (yych <= 0xDF) goto yy732; - goto yy733; - } - } else { - if (yych <= 0xEF) { - if (yych == 0xED) goto yy735; - goto yy734; - } else { - if (yych <= 0xF0) goto yy736; - if (yych <= 0xF3) goto yy737; - if (yych <= 0xF4) goto yy738; - goto yy729; - } - } -yy766: - yyaccept = 2; - yych = *(marker = ++p); - if (yybm[0+yych] & 64) { - goto yy739; - } - if (yych <= 0xE0) { - if (yych <= '\\') { - if (yych <= 0x00) goto yy742; - if (yych <= '\'') goto yy741; - goto yy743; - } else { - if (yych <= 0xC1) goto yy742; - if (yych <= 0xDF) goto yy745; - goto yy746; - } - } else { - if (yych <= 0xEF) { - if (yych == 0xED) goto yy748; - goto yy747; - } else { - if (yych <= 0xF0) goto yy749; - if (yych <= 0xF3) goto yy750; - if (yych <= 0xF4) goto yy751; - goto yy742; - } - } -yy767: - yyaccept = 3; - yych = *(marker = ++p); - if (yybm[0+yych] & 128) { - goto yy752; - } - if (yych <= 0xE0) { - if (yych <= '\\') { - if (yych <= '(') goto yy755; - if (yych <= ')') goto yy754; - goto yy756; - } else { - if (yych <= 0xC1) goto yy755; - if (yych <= 0xDF) goto yy758; - goto yy759; - } - } else { - if (yych <= 0xEF) { - if (yych == 0xED) goto yy761; - goto yy760; - } else { - if (yych <= 0xF0) goto yy762; - if (yych <= 0xF3) goto yy763; - if (yych <= 0xF4) goto yy764; - goto yy755; - } - } -} - + { + unsigned char yych; + unsigned int yyaccept = 0; + static const unsigned char yybm[] = { + 0, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, + 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, + 208, 208, 208, 208, 208, 208, 192, 208, 208, 208, 208, 144, 80, 80, + 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, + 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, + 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, + 208, 208, 208, 208, 208, 208, 208, 208, 32, 208, 208, 208, 208, 208, + 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, + 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, 208, + 208, 208, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + }; + yych = *p; + if (yych <= '&') { + if (yych == '"') + goto yy643; + } else { + if (yych <= '\'') + goto yy644; + if (yych <= '(') + goto yy645; + } + ++p; + yy642 : { return 0; } + yy643: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= 0x00) + goto yy642; + if (yych <= 0x7F) + goto yy647; + if (yych <= 0xC1) + goto yy642; + if (yych <= 0xF4) + goto yy647; + goto yy642; + yy644: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= 0x00) + goto yy642; + if (yych <= 0x7F) + goto yy660; + if (yych <= 0xC1) + goto yy642; + if (yych <= 0xF4) + goto yy660; + goto yy642; + yy645: + yyaccept = 0; + yych = *(marker = ++p); + if (yych <= '(') { + if (yych <= 0x00) + goto yy642; + if (yych <= '\'') + goto yy672; + goto yy642; + } else { + if (yych <= 0x7F) + goto yy672; + if (yych <= 0xC1) + goto yy642; + if (yych <= 0xF4) + goto yy672; + goto yy642; + } + yy646: + yych = *++p; + yy647: + if (yybm[0 + yych] & 16) { + goto yy646; + } + if (yych <= 0xE0) { + if (yych <= '\\') { + if (yych <= 0x00) + goto yy648; + if (yych <= '"') + goto yy649; + goto yy651; + } else { + if (yych <= 0xC1) + goto yy648; + if (yych <= 0xDF) + goto yy652; + goto yy653; + } + } else { + if (yych <= 0xEF) { + if (yych == 0xED) + goto yy655; + goto yy654; + } else { + if (yych <= 0xF0) + goto yy656; + if (yych <= 0xF3) + goto yy657; + if (yych <= 0xF4) + goto yy658; + } + } + yy648: + p = marker; + if (yyaccept <= 1) { + if (yyaccept == 0) { + goto yy642; + } else { + goto yy650; + } + } else { + if (yyaccept == 2) { + goto yy662; + } else { + goto yy674; + } + } + yy649: + ++p; + yy650 : { return (bufsize_t)(p - start); } + yy651: + yych = *++p; + if (yybm[0 + yych] & 16) { + goto yy646; + } + if (yych <= 0xE0) { + if (yych <= '\\') { + if (yych <= 0x00) + goto yy648; + if (yych <= '"') + goto yy683; + goto yy651; + } else { + if (yych <= 0xC1) + goto yy648; + if (yych >= 0xE0) + goto yy653; + } + } else { + if (yych <= 0xEF) { + if (yych == 0xED) + goto yy655; + goto yy654; + } else { + if (yych <= 0xF0) + goto yy656; + if (yych <= 0xF3) + goto yy657; + if (yych <= 0xF4) + goto yy658; + goto yy648; + } + } + yy652: + yych = *++p; + if (yych <= 0x7F) + goto yy648; + if (yych <= 0xBF) + goto yy646; + goto yy648; + yy653: + yych = *++p; + if (yych <= 0x9F) + goto yy648; + if (yych <= 0xBF) + goto yy652; + goto yy648; + yy654: + yych = *++p; + if (yych <= 0x7F) + goto yy648; + if (yych <= 0xBF) + goto yy652; + goto yy648; + yy655: + yych = *++p; + if (yych <= 0x7F) + goto yy648; + if (yych <= 0x9F) + goto yy652; + goto yy648; + yy656: + yych = *++p; + if (yych <= 0x8F) + goto yy648; + if (yych <= 0xBF) + goto yy654; + goto yy648; + yy657: + yych = *++p; + if (yych <= 0x7F) + goto yy648; + if (yych <= 0xBF) + goto yy654; + goto yy648; + yy658: + yych = *++p; + if (yych <= 0x7F) + goto yy648; + if (yych <= 0x8F) + goto yy654; + goto yy648; + yy659: + yych = *++p; + yy660: + if (yybm[0 + yych] & 64) { + goto yy659; + } + if (yych <= 0xE0) { + if (yych <= '\\') { + if (yych <= 0x00) + goto yy648; + if (yych >= '(') + goto yy663; + } else { + if (yych <= 0xC1) + goto yy648; + if (yych <= 0xDF) + goto yy664; + goto yy665; + } + } else { + if (yych <= 0xEF) { + if (yych == 0xED) + goto yy667; + goto yy666; + } else { + if (yych <= 0xF0) + goto yy668; + if (yych <= 0xF3) + goto yy669; + if (yych <= 0xF4) + goto yy670; + goto yy648; + } + } + yy661: + ++p; + yy662 : { return (bufsize_t)(p - start); } + yy663: + yych = *++p; + if (yybm[0 + yych] & 64) { + goto yy659; + } + if (yych <= 0xE0) { + if (yych <= '\\') { + if (yych <= 0x00) + goto yy648; + if (yych <= '\'') + goto yy684; + goto yy663; + } else { + if (yych <= 0xC1) + goto yy648; + if (yych >= 0xE0) + goto yy665; + } + } else { + if (yych <= 0xEF) { + if (yych == 0xED) + goto yy667; + goto yy666; + } else { + if (yych <= 0xF0) + goto yy668; + if (yych <= 0xF3) + goto yy669; + if (yych <= 0xF4) + goto yy670; + goto yy648; + } + } + yy664: + yych = *++p; + if (yych <= 0x7F) + goto yy648; + if (yych <= 0xBF) + goto yy659; + goto yy648; + yy665: + yych = *++p; + if (yych <= 0x9F) + goto yy648; + if (yych <= 0xBF) + goto yy664; + goto yy648; + yy666: + yych = *++p; + if (yych <= 0x7F) + goto yy648; + if (yych <= 0xBF) + goto yy664; + goto yy648; + yy667: + yych = *++p; + if (yych <= 0x7F) + goto yy648; + if (yych <= 0x9F) + goto yy664; + goto yy648; + yy668: + yych = *++p; + if (yych <= 0x8F) + goto yy648; + if (yych <= 0xBF) + goto yy666; + goto yy648; + yy669: + yych = *++p; + if (yych <= 0x7F) + goto yy648; + if (yych <= 0xBF) + goto yy666; + goto yy648; + yy670: + yych = *++p; + if (yych <= 0x7F) + goto yy648; + if (yych <= 0x8F) + goto yy666; + goto yy648; + yy671: + yych = *++p; + yy672: + if (yybm[0 + yych] & 128) { + goto yy671; + } + if (yych <= 0xE0) { + if (yych <= '\\') { + if (yych <= '(') + goto yy648; + if (yych >= '*') + goto yy675; + } else { + if (yych <= 0xC1) + goto yy648; + if (yych <= 0xDF) + goto yy676; + goto yy677; + } + } else { + if (yych <= 0xEF) { + if (yych == 0xED) + goto yy679; + goto yy678; + } else { + if (yych <= 0xF0) + goto yy680; + if (yych <= 0xF3) + goto yy681; + if (yych <= 0xF4) + goto yy682; + goto yy648; + } + } + yy673: + ++p; + yy674 : { return (bufsize_t)(p - start); } + yy675: + yych = *++p; + if (yych <= 0xDF) { + if (yych <= '[') { + if (yych <= 0x00) + goto yy648; + if (yych == ')') + goto yy685; + goto yy671; + } else { + if (yych <= '\\') + goto yy675; + if (yych <= 0x7F) + goto yy671; + if (yych <= 0xC1) + goto yy648; + } + } else { + if (yych <= 0xEF) { + if (yych <= 0xE0) + goto yy677; + if (yych == 0xED) + goto yy679; + goto yy678; + } else { + if (yych <= 0xF0) + goto yy680; + if (yych <= 0xF3) + goto yy681; + if (yych <= 0xF4) + goto yy682; + goto yy648; + } + } + yy676: + yych = *++p; + if (yych <= 0x7F) + goto yy648; + if (yych <= 0xBF) + goto yy671; + goto yy648; + yy677: + yych = *++p; + if (yych <= 0x9F) + goto yy648; + if (yych <= 0xBF) + goto yy676; + goto yy648; + yy678: + yych = *++p; + if (yych <= 0x7F) + goto yy648; + if (yych <= 0xBF) + goto yy676; + goto yy648; + yy679: + yych = *++p; + if (yych <= 0x7F) + goto yy648; + if (yych <= 0x9F) + goto yy676; + goto yy648; + yy680: + yych = *++p; + if (yych <= 0x8F) + goto yy648; + if (yych <= 0xBF) + goto yy678; + goto yy648; + yy681: + yych = *++p; + if (yych <= 0x7F) + goto yy648; + if (yych <= 0xBF) + goto yy678; + goto yy648; + yy682: + yych = *++p; + if (yych <= 0x7F) + goto yy648; + if (yych <= 0x8F) + goto yy678; + goto yy648; + yy683: + yyaccept = 1; + yych = *(marker = ++p); + if (yybm[0 + yych] & 16) { + goto yy646; + } + if (yych <= 0xE0) { + if (yych <= '\\') { + if (yych <= 0x00) + goto yy650; + if (yych <= '"') + goto yy649; + goto yy651; + } else { + if (yych <= 0xC1) + goto yy650; + if (yych <= 0xDF) + goto yy652; + goto yy653; + } + } else { + if (yych <= 0xEF) { + if (yych == 0xED) + goto yy655; + goto yy654; + } else { + if (yych <= 0xF0) + goto yy656; + if (yych <= 0xF3) + goto yy657; + if (yych <= 0xF4) + goto yy658; + goto yy650; + } + } + yy684: + yyaccept = 2; + yych = *(marker = ++p); + if (yybm[0 + yych] & 64) { + goto yy659; + } + if (yych <= 0xE0) { + if (yych <= '\\') { + if (yych <= 0x00) + goto yy662; + if (yych <= '\'') + goto yy661; + goto yy663; + } else { + if (yych <= 0xC1) + goto yy662; + if (yych <= 0xDF) + goto yy664; + goto yy665; + } + } else { + if (yych <= 0xEF) { + if (yych == 0xED) + goto yy667; + goto yy666; + } else { + if (yych <= 0xF0) + goto yy668; + if (yych <= 0xF3) + goto yy669; + if (yych <= 0xF4) + goto yy670; + goto yy662; + } + } + yy685: + yyaccept = 3; + yych = *(marker = ++p); + if (yybm[0 + yych] & 128) { + goto yy671; + } + if (yych <= 0xE0) { + if (yych <= '\\') { + if (yych <= '(') + goto yy674; + if (yych <= ')') + goto yy673; + goto yy675; + } else { + if (yych <= 0xC1) + goto yy674; + if (yych <= 0xDF) + goto yy676; + goto yy677; + } + } else { + if (yych <= 0xEF) { + if (yych == 0xED) + goto yy679; + goto yy678; + } else { + if (yych <= 0xF0) + goto yy680; + if (yych <= 0xF3) + goto yy681; + if (yych <= 0xF4) + goto yy682; + goto yy674; + } + } + } } // Match space characters, including newlines. -bufsize_t _scan_spacechars(const unsigned char *p) -{ - const unsigned char *start = p; \ - -{ - unsigned char yych; - static const unsigned char yybm[] = { - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 128, 128, 128, 128, 128, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 128, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - }; - yych = *p; - if (yybm[0+yych] & 128) { - goto yy772; - } - ++p; - { return 0; } -yy772: - yych = *++p; - if (yybm[0+yych] & 128) { - goto yy772; - } - { return (bufsize_t)(p - start); } -} +bufsize_t _scan_spacechars(const unsigned char *p) { + const unsigned char *start = p; + { + unsigned char yych; + static const unsigned char yybm[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 128, 128, 128, 128, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }; + yych = *p; + if (yybm[0 + yych] & 128) { + goto yy687; + } + ++p; + { return 0; } + yy687: + yych = *++p; + if (yybm[0 + yych] & 128) { + goto yy687; + } + { return (bufsize_t)(p - start); } + } } // Match ATX heading start. -bufsize_t _scan_atx_heading_start(const unsigned char *p) -{ +bufsize_t _scan_atx_heading_start(const unsigned char *p) { const unsigned char *marker = NULL; const unsigned char *start = p; -{ - unsigned char yych; - static const unsigned char yybm[] = { - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 128, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 128, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - }; - yych = *p; - if (yych == '#') goto yy779; - ++p; -yy778: - { return 0; } -yy779: - yych = *(marker = ++p); - if (yybm[0+yych] & 128) { - goto yy780; - } - if (yych <= '\f') { - if (yych <= 0x08) goto yy778; - if (yych <= '\n') goto yy783; - goto yy778; - } else { - if (yych <= '\r') goto yy783; - if (yych == '#') goto yy784; - goto yy778; - } -yy780: - yych = *++p; - if (yybm[0+yych] & 128) { - goto yy780; - } -yy782: - { return (bufsize_t)(p - start); } -yy783: - ++p; - goto yy782; -yy784: - yych = *++p; - if (yybm[0+yych] & 128) { - goto yy780; - } - if (yych <= '\f') { - if (yych <= 0x08) goto yy785; - if (yych <= '\n') goto yy783; - } else { - if (yych <= '\r') goto yy783; - if (yych == '#') goto yy786; - } -yy785: - p = marker; - goto yy778; -yy786: - yych = *++p; - if (yybm[0+yych] & 128) { - goto yy780; - } - if (yych <= '\f') { - if (yych <= 0x08) goto yy785; - if (yych <= '\n') goto yy783; - goto yy785; - } else { - if (yych <= '\r') goto yy783; - if (yych != '#') goto yy785; - } - yych = *++p; - if (yybm[0+yych] & 128) { - goto yy780; - } - if (yych <= '\f') { - if (yych <= 0x08) goto yy785; - if (yych <= '\n') goto yy783; - goto yy785; - } else { - if (yych <= '\r') goto yy783; - if (yych != '#') goto yy785; - } - yych = *++p; - if (yybm[0+yych] & 128) { - goto yy780; - } - if (yych <= '\f') { - if (yych <= 0x08) goto yy785; - if (yych <= '\n') goto yy783; - goto yy785; - } else { - if (yych <= '\r') goto yy783; - if (yych != '#') goto yy785; - } - yych = *++p; - if (yybm[0+yych] & 128) { - goto yy780; - } - if (yych <= 0x08) goto yy785; - if (yych <= '\n') goto yy783; - if (yych == '\r') goto yy783; - goto yy785; -} - + { + unsigned char yych; + static const unsigned char yybm[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }; + yych = *p; + if (yych == '#') + goto yy690; + ++p; + yy689 : { return 0; } + yy690: + yych = *(marker = ++p); + if (yybm[0 + yych] & 128) { + goto yy691; + } + if (yych <= '\f') { + if (yych <= 0x08) + goto yy689; + if (yych <= '\n') + goto yy693; + goto yy689; + } else { + if (yych <= '\r') + goto yy693; + if (yych == '#') + goto yy694; + goto yy689; + } + yy691: + yych = *++p; + if (yybm[0 + yych] & 128) { + goto yy691; + } + yy692 : { return (bufsize_t)(p - start); } + yy693: + ++p; + goto yy692; + yy694: + yych = *++p; + if (yybm[0 + yych] & 128) { + goto yy691; + } + if (yych <= '\f') { + if (yych <= 0x08) + goto yy695; + if (yych <= '\n') + goto yy693; + } else { + if (yych <= '\r') + goto yy693; + if (yych == '#') + goto yy696; + } + yy695: + p = marker; + goto yy689; + yy696: + yych = *++p; + if (yybm[0 + yych] & 128) { + goto yy691; + } + if (yych <= '\f') { + if (yych <= 0x08) + goto yy695; + if (yych <= '\n') + goto yy693; + goto yy695; + } else { + if (yych <= '\r') + goto yy693; + if (yych != '#') + goto yy695; + } + yych = *++p; + if (yybm[0 + yych] & 128) { + goto yy691; + } + if (yych <= '\f') { + if (yych <= 0x08) + goto yy695; + if (yych <= '\n') + goto yy693; + goto yy695; + } else { + if (yych <= '\r') + goto yy693; + if (yych != '#') + goto yy695; + } + yych = *++p; + if (yybm[0 + yych] & 128) { + goto yy691; + } + if (yych <= '\f') { + if (yych <= 0x08) + goto yy695; + if (yych <= '\n') + goto yy693; + goto yy695; + } else { + if (yych <= '\r') + goto yy693; + if (yych != '#') + goto yy695; + } + yych = *++p; + if (yybm[0 + yych] & 128) { + goto yy691; + } + if (yych <= 0x08) + goto yy695; + if (yych <= '\n') + goto yy693; + if (yych == '\r') + goto yy693; + goto yy695; + } } // Match setext heading line. Return 1 for level-1 heading, // 2 for level-2, 0 for no match. -bufsize_t _scan_setext_heading_line(const unsigned char *p) -{ +bufsize_t _scan_setext_heading_line(const unsigned char *p) { const unsigned char *marker = NULL; -{ - unsigned char yych; - static const unsigned char yybm[] = { - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 32, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 32, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 64, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 128, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - }; - yych = *p; - if (yych == '-') goto yy794; - if (yych == '=') goto yy795; - ++p; -yy793: - { return 0; } -yy794: - yych = *(marker = ++p); - if (yybm[0+yych] & 64) { - goto yy801; - } - if (yych <= '\f') { - if (yych <= 0x08) goto yy793; - if (yych <= '\n') goto yy797; - goto yy793; - } else { - if (yych <= '\r') goto yy797; - if (yych == ' ') goto yy797; - goto yy793; - } -yy795: - yych = *(marker = ++p); - if (yybm[0+yych] & 128) { - goto yy807; - } - if (yych <= '\f') { - if (yych <= 0x08) goto yy793; - if (yych <= '\n') goto yy804; - goto yy793; - } else { - if (yych <= '\r') goto yy804; - if (yych == ' ') goto yy804; - goto yy793; - } -yy796: - yych = *++p; -yy797: - if (yybm[0+yych] & 32) { - goto yy796; - } - if (yych <= 0x08) goto yy798; - if (yych <= '\n') goto yy799; - if (yych == '\r') goto yy799; -yy798: - p = marker; - goto yy793; -yy799: - ++p; - { return 2; } -yy801: - yych = *++p; - if (yybm[0+yych] & 32) { - goto yy796; - } - if (yych <= '\f') { - if (yych <= 0x08) goto yy798; - if (yych <= '\n') goto yy799; - goto yy798; - } else { - if (yych <= '\r') goto yy799; - if (yych == '-') goto yy801; - goto yy798; - } -yy803: - yych = *++p; -yy804: - if (yych <= '\f') { - if (yych <= 0x08) goto yy798; - if (yych <= '\t') goto yy803; - if (yych >= '\v') goto yy798; - } else { - if (yych <= '\r') goto yy805; - if (yych == ' ') goto yy803; - goto yy798; - } -yy805: - ++p; - { return 1; } -yy807: - yych = *++p; - if (yybm[0+yych] & 128) { - goto yy807; - } - if (yych <= '\f') { - if (yych <= 0x08) goto yy798; - if (yych <= '\t') goto yy803; - if (yych <= '\n') goto yy805; - goto yy798; - } else { - if (yych <= '\r') goto yy805; - if (yych == ' ') goto yy803; - goto yy798; - } -} - + { + unsigned char yych; + static const unsigned char yybm[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }; + yych = *p; + if (yych == '-') + goto yy699; + if (yych == '=') + goto yy700; + ++p; + yy698 : { return 0; } + yy699: + yych = *(marker = ++p); + if (yybm[0 + yych] & 64) { + goto yy705; + } + if (yych <= '\f') { + if (yych <= 0x08) + goto yy698; + if (yych <= '\n') + goto yy702; + goto yy698; + } else { + if (yych <= '\r') + goto yy702; + if (yych == ' ') + goto yy702; + goto yy698; + } + yy700: + yych = *(marker = ++p); + if (yybm[0 + yych] & 128) { + goto yy709; + } + if (yych <= '\f') { + if (yych <= 0x08) + goto yy698; + if (yych <= '\n') + goto yy707; + goto yy698; + } else { + if (yych <= '\r') + goto yy707; + if (yych == ' ') + goto yy707; + goto yy698; + } + yy701: + yych = *++p; + yy702: + if (yybm[0 + yych] & 32) { + goto yy701; + } + if (yych <= 0x08) + goto yy703; + if (yych <= '\n') + goto yy704; + if (yych == '\r') + goto yy704; + yy703: + p = marker; + goto yy698; + yy704: + ++p; + { return 2; } + yy705: + yych = *++p; + if (yybm[0 + yych] & 32) { + goto yy701; + } + if (yych <= '\f') { + if (yych <= 0x08) + goto yy703; + if (yych <= '\n') + goto yy704; + goto yy703; + } else { + if (yych <= '\r') + goto yy704; + if (yych == '-') + goto yy705; + goto yy703; + } + yy706: + yych = *++p; + yy707: + if (yych <= '\f') { + if (yych <= 0x08) + goto yy703; + if (yych <= '\t') + goto yy706; + if (yych >= '\v') + goto yy703; + } else { + if (yych <= '\r') + goto yy708; + if (yych == ' ') + goto yy706; + goto yy703; + } + yy708: + ++p; + { return 1; } + yy709: + yych = *++p; + if (yybm[0 + yych] & 128) { + goto yy709; + } + if (yych <= '\f') { + if (yych <= 0x08) + goto yy703; + if (yych <= '\t') + goto yy706; + if (yych <= '\n') + goto yy708; + goto yy703; + } else { + if (yych <= '\r') + goto yy708; + if (yych == ' ') + goto yy706; + goto yy703; + } + } } // Scan an opening code fence. -bufsize_t _scan_open_code_fence(const unsigned char *p) -{ +bufsize_t _scan_open_code_fence(const unsigned char *p) { const unsigned char *marker = NULL; const unsigned char *start = p; -{ - unsigned char yych; - static const unsigned char yybm[] = { - 0, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 0, 192, 192, 0, 192, 192, - 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 192, 192, 192, 192, 192, 192, - 144, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 192, 192, 192, 192, 192, 192, - 192, 192, 192, 192, 192, 192, 224, 192, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - }; - yych = *p; - if (yych == '`') goto yy813; - if (yych == '~') goto yy814; - ++p; -yy812: - { return 0; } -yy813: - yych = *(marker = ++p); - if (yych == '`') goto yy815; - goto yy812; -yy814: - yych = *(marker = ++p); - if (yych == '~') goto yy817; - goto yy812; -yy815: - yych = *++p; - if (yybm[0+yych] & 16) { - goto yy818; - } -yy816: - p = marker; - goto yy812; -yy817: - yych = *++p; - if (yybm[0+yych] & 32) { - goto yy820; - } - goto yy816; -yy818: - yych = *++p; - if (yybm[0+yych] & 16) { - goto yy818; - } - if (yych <= 0xDF) { - if (yych <= '\f') { - if (yych <= 0x00) goto yy816; - if (yych == '\n') { - marker = p; - goto yy824; - } - marker = p; - goto yy822; - } else { - if (yych <= '\r') { - marker = p; - goto yy824; - } - if (yych <= 0x7F) { - marker = p; - goto yy822; - } - if (yych <= 0xC1) goto yy816; - marker = p; - goto yy826; - } - } else { - if (yych <= 0xEF) { - if (yych <= 0xE0) { - marker = p; - goto yy827; - } - if (yych == 0xED) { - marker = p; - goto yy829; - } - marker = p; - goto yy828; - } else { - if (yych <= 0xF0) { - marker = p; - goto yy830; - } - if (yych <= 0xF3) { - marker = p; - goto yy831; - } - if (yych <= 0xF4) { - marker = p; - goto yy832; - } - goto yy816; - } - } -yy820: - yych = *++p; - if (yybm[0+yych] & 32) { - goto yy820; - } - if (yych <= 0xDF) { - if (yych <= '\f') { - if (yych <= 0x00) goto yy816; - if (yych == '\n') { - marker = p; - goto yy835; - } - marker = p; - goto yy833; - } else { - if (yych <= '\r') { - marker = p; - goto yy835; - } - if (yych <= 0x7F) { - marker = p; - goto yy833; - } - if (yych <= 0xC1) goto yy816; - marker = p; - goto yy837; - } - } else { - if (yych <= 0xEF) { - if (yych <= 0xE0) { - marker = p; - goto yy838; - } - if (yych == 0xED) { - marker = p; - goto yy840; - } - marker = p; - goto yy839; - } else { - if (yych <= 0xF0) { - marker = p; - goto yy841; - } - if (yych <= 0xF3) { - marker = p; - goto yy842; - } - if (yych <= 0xF4) { - marker = p; - goto yy843; - } - goto yy816; - } - } -yy822: - yych = *++p; - if (yybm[0+yych] & 64) { - goto yy822; - } - if (yych <= 0xEC) { - if (yych <= 0xC1) { - if (yych <= 0x00) goto yy816; - if (yych >= 0x0E) goto yy816; - } else { - if (yych <= 0xDF) goto yy826; - if (yych <= 0xE0) goto yy827; - goto yy828; - } - } else { - if (yych <= 0xF0) { - if (yych <= 0xED) goto yy829; - if (yych <= 0xEF) goto yy828; - goto yy830; - } else { - if (yych <= 0xF3) goto yy831; - if (yych <= 0xF4) goto yy832; - goto yy816; - } - } -yy824: - ++p; - p = marker; - { return (bufsize_t)(p - start); } -yy826: - yych = *++p; - if (yych <= 0x7F) goto yy816; - if (yych <= 0xBF) goto yy822; - goto yy816; -yy827: - yych = *++p; - if (yych <= 0x9F) goto yy816; - if (yych <= 0xBF) goto yy826; - goto yy816; -yy828: - yych = *++p; - if (yych <= 0x7F) goto yy816; - if (yych <= 0xBF) goto yy826; - goto yy816; -yy829: - yych = *++p; - if (yych <= 0x7F) goto yy816; - if (yych <= 0x9F) goto yy826; - goto yy816; -yy830: - yych = *++p; - if (yych <= 0x8F) goto yy816; - if (yych <= 0xBF) goto yy828; - goto yy816; -yy831: - yych = *++p; - if (yych <= 0x7F) goto yy816; - if (yych <= 0xBF) goto yy828; - goto yy816; -yy832: - yych = *++p; - if (yych <= 0x7F) goto yy816; - if (yych <= 0x8F) goto yy828; - goto yy816; -yy833: - yych = *++p; - if (yybm[0+yych] & 128) { - goto yy833; - } - if (yych <= 0xEC) { - if (yych <= 0xC1) { - if (yych <= 0x00) goto yy816; - if (yych >= 0x0E) goto yy816; - } else { - if (yych <= 0xDF) goto yy837; - if (yych <= 0xE0) goto yy838; - goto yy839; - } - } else { - if (yych <= 0xF0) { - if (yych <= 0xED) goto yy840; - if (yych <= 0xEF) goto yy839; - goto yy841; - } else { - if (yych <= 0xF3) goto yy842; - if (yych <= 0xF4) goto yy843; - goto yy816; - } - } -yy835: - ++p; - p = marker; - { return (bufsize_t)(p - start); } -yy837: - yych = *++p; - if (yych <= 0x7F) goto yy816; - if (yych <= 0xBF) goto yy833; - goto yy816; -yy838: - yych = *++p; - if (yych <= 0x9F) goto yy816; - if (yych <= 0xBF) goto yy837; - goto yy816; -yy839: - yych = *++p; - if (yych <= 0x7F) goto yy816; - if (yych <= 0xBF) goto yy837; - goto yy816; -yy840: - yych = *++p; - if (yych <= 0x7F) goto yy816; - if (yych <= 0x9F) goto yy837; - goto yy816; -yy841: - yych = *++p; - if (yych <= 0x8F) goto yy816; - if (yych <= 0xBF) goto yy839; - goto yy816; -yy842: - yych = *++p; - if (yych <= 0x7F) goto yy816; - if (yych <= 0xBF) goto yy839; - goto yy816; -yy843: - yych = *++p; - if (yych <= 0x7F) goto yy816; - if (yych <= 0x8F) goto yy839; - goto yy816; -} - + { + unsigned char yych; + static const unsigned char yybm[] = { + 0, 192, 192, 192, 192, 192, 192, 192, 192, 192, 0, 192, 192, 0, + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 144, 192, + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, + 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, + 224, 192, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + }; + yych = *p; + if (yych == '`') + goto yy712; + if (yych == '~') + goto yy713; + ++p; + yy711 : { return 0; } + yy712: + yych = *(marker = ++p); + if (yych == '`') + goto yy714; + goto yy711; + yy713: + yych = *(marker = ++p); + if (yych == '~') + goto yy716; + goto yy711; + yy714: + yych = *++p; + if (yybm[0 + yych] & 16) { + goto yy717; + } + yy715: + p = marker; + goto yy711; + yy716: + yych = *++p; + if (yybm[0 + yych] & 32) { + goto yy718; + } + goto yy715; + yy717: + yych = *++p; + if (yybm[0 + yych] & 16) { + goto yy717; + } + if (yych <= 0xDF) { + if (yych <= '\f') { + if (yych <= 0x00) + goto yy715; + if (yych == '\n') { + marker = p; + goto yy720; + } + marker = p; + goto yy719; + } else { + if (yych <= '\r') { + marker = p; + goto yy720; + } + if (yych <= 0x7F) { + marker = p; + goto yy719; + } + if (yych <= 0xC1) + goto yy715; + marker = p; + goto yy721; + } + } else { + if (yych <= 0xEF) { + if (yych <= 0xE0) { + marker = p; + goto yy722; + } + if (yych == 0xED) { + marker = p; + goto yy724; + } + marker = p; + goto yy723; + } else { + if (yych <= 0xF0) { + marker = p; + goto yy725; + } + if (yych <= 0xF3) { + marker = p; + goto yy726; + } + if (yych <= 0xF4) { + marker = p; + goto yy727; + } + goto yy715; + } + } + yy718: + yych = *++p; + if (yybm[0 + yych] & 32) { + goto yy718; + } + if (yych <= 0xDF) { + if (yych <= '\f') { + if (yych <= 0x00) + goto yy715; + if (yych == '\n') { + marker = p; + goto yy729; + } + marker = p; + goto yy728; + } else { + if (yych <= '\r') { + marker = p; + goto yy729; + } + if (yych <= 0x7F) { + marker = p; + goto yy728; + } + if (yych <= 0xC1) + goto yy715; + marker = p; + goto yy730; + } + } else { + if (yych <= 0xEF) { + if (yych <= 0xE0) { + marker = p; + goto yy731; + } + if (yych == 0xED) { + marker = p; + goto yy733; + } + marker = p; + goto yy732; + } else { + if (yych <= 0xF0) { + marker = p; + goto yy734; + } + if (yych <= 0xF3) { + marker = p; + goto yy735; + } + if (yych <= 0xF4) { + marker = p; + goto yy736; + } + goto yy715; + } + } + yy719: + yych = *++p; + if (yybm[0 + yych] & 64) { + goto yy719; + } + if (yych <= 0xEC) { + if (yych <= 0xC1) { + if (yych <= 0x00) + goto yy715; + if (yych >= 0x0E) + goto yy715; + } else { + if (yych <= 0xDF) + goto yy721; + if (yych <= 0xE0) + goto yy722; + goto yy723; + } + } else { + if (yych <= 0xF0) { + if (yych <= 0xED) + goto yy724; + if (yych <= 0xEF) + goto yy723; + goto yy725; + } else { + if (yych <= 0xF3) + goto yy726; + if (yych <= 0xF4) + goto yy727; + goto yy715; + } + } + yy720: + ++p; + p = marker; + { return (bufsize_t)(p - start); } + yy721: + yych = *++p; + if (yych <= 0x7F) + goto yy715; + if (yych <= 0xBF) + goto yy719; + goto yy715; + yy722: + yych = *++p; + if (yych <= 0x9F) + goto yy715; + if (yych <= 0xBF) + goto yy721; + goto yy715; + yy723: + yych = *++p; + if (yych <= 0x7F) + goto yy715; + if (yych <= 0xBF) + goto yy721; + goto yy715; + yy724: + yych = *++p; + if (yych <= 0x7F) + goto yy715; + if (yych <= 0x9F) + goto yy721; + goto yy715; + yy725: + yych = *++p; + if (yych <= 0x8F) + goto yy715; + if (yych <= 0xBF) + goto yy723; + goto yy715; + yy726: + yych = *++p; + if (yych <= 0x7F) + goto yy715; + if (yych <= 0xBF) + goto yy723; + goto yy715; + yy727: + yych = *++p; + if (yych <= 0x7F) + goto yy715; + if (yych <= 0x8F) + goto yy723; + goto yy715; + yy728: + yych = *++p; + if (yybm[0 + yych] & 128) { + goto yy728; + } + if (yych <= 0xEC) { + if (yych <= 0xC1) { + if (yych <= 0x00) + goto yy715; + if (yych >= 0x0E) + goto yy715; + } else { + if (yych <= 0xDF) + goto yy730; + if (yych <= 0xE0) + goto yy731; + goto yy732; + } + } else { + if (yych <= 0xF0) { + if (yych <= 0xED) + goto yy733; + if (yych <= 0xEF) + goto yy732; + goto yy734; + } else { + if (yych <= 0xF3) + goto yy735; + if (yych <= 0xF4) + goto yy736; + goto yy715; + } + } + yy729: + ++p; + p = marker; + { return (bufsize_t)(p - start); } + yy730: + yych = *++p; + if (yych <= 0x7F) + goto yy715; + if (yych <= 0xBF) + goto yy728; + goto yy715; + yy731: + yych = *++p; + if (yych <= 0x9F) + goto yy715; + if (yych <= 0xBF) + goto yy730; + goto yy715; + yy732: + yych = *++p; + if (yych <= 0x7F) + goto yy715; + if (yych <= 0xBF) + goto yy730; + goto yy715; + yy733: + yych = *++p; + if (yych <= 0x7F) + goto yy715; + if (yych <= 0x9F) + goto yy730; + goto yy715; + yy734: + yych = *++p; + if (yych <= 0x8F) + goto yy715; + if (yych <= 0xBF) + goto yy732; + goto yy715; + yy735: + yych = *++p; + if (yych <= 0x7F) + goto yy715; + if (yych <= 0xBF) + goto yy732; + goto yy715; + yy736: + yych = *++p; + if (yych <= 0x7F) + goto yy715; + if (yych <= 0x8F) + goto yy732; + goto yy715; + } } // Scan a closing code fence with length at least len. -bufsize_t _scan_close_code_fence(const unsigned char *p) -{ +bufsize_t _scan_close_code_fence(const unsigned char *p) { const unsigned char *marker = NULL; const unsigned char *start = p; -{ - unsigned char yych; - static const unsigned char yybm[] = { - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 128, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 128, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 32, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 64, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - }; - yych = *p; - if (yych == '`') goto yy848; - if (yych == '~') goto yy849; - ++p; -yy847: - { return 0; } -yy848: - yych = *(marker = ++p); - if (yych == '`') goto yy850; - goto yy847; -yy849: - yych = *(marker = ++p); - if (yych == '~') goto yy852; - goto yy847; -yy850: - yych = *++p; - if (yybm[0+yych] & 32) { - goto yy853; - } -yy851: - p = marker; - goto yy847; -yy852: - yych = *++p; - if (yybm[0+yych] & 64) { - goto yy855; - } - goto yy851; -yy853: - yych = *++p; - if (yybm[0+yych] & 32) { - goto yy853; - } - if (yych <= '\f') { - if (yych <= 0x08) goto yy851; - if (yych <= '\t') { - marker = p; - goto yy857; - } - if (yych <= '\n') { - marker = p; - goto yy859; - } - goto yy851; - } else { - if (yych <= '\r') { - marker = p; - goto yy859; - } - if (yych == ' ') { - marker = p; - goto yy857; - } - goto yy851; - } -yy855: - yych = *++p; - if (yybm[0+yych] & 64) { - goto yy855; - } - if (yych <= '\f') { - if (yych <= 0x08) goto yy851; - if (yych <= '\t') { - marker = p; - goto yy861; - } - if (yych <= '\n') { - marker = p; - goto yy863; - } - goto yy851; - } else { - if (yych <= '\r') { - marker = p; - goto yy863; - } - if (yych == ' ') { - marker = p; - goto yy861; - } - goto yy851; - } -yy857: - yych = *++p; - if (yybm[0+yych] & 128) { - goto yy857; - } - if (yych <= 0x08) goto yy851; - if (yych <= '\n') goto yy859; - if (yych != '\r') goto yy851; -yy859: - ++p; - p = marker; - { return (bufsize_t)(p - start); } -yy861: - yych = *++p; - if (yych <= '\f') { - if (yych <= 0x08) goto yy851; - if (yych <= '\t') goto yy861; - if (yych >= '\v') goto yy851; - } else { - if (yych <= '\r') goto yy863; - if (yych == ' ') goto yy861; - goto yy851; - } -yy863: - ++p; - p = marker; - { return (bufsize_t)(p - start); } -} - + { + unsigned char yych; + static const unsigned char yybm[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }; + yych = *p; + if (yych == '`') + goto yy739; + if (yych == '~') + goto yy740; + ++p; + yy738 : { return 0; } + yy739: + yych = *(marker = ++p); + if (yych == '`') + goto yy741; + goto yy738; + yy740: + yych = *(marker = ++p); + if (yych == '~') + goto yy743; + goto yy738; + yy741: + yych = *++p; + if (yybm[0 + yych] & 32) { + goto yy744; + } + yy742: + p = marker; + goto yy738; + yy743: + yych = *++p; + if (yybm[0 + yych] & 64) { + goto yy745; + } + goto yy742; + yy744: + yych = *++p; + if (yybm[0 + yych] & 32) { + goto yy744; + } + if (yych <= '\f') { + if (yych <= 0x08) + goto yy742; + if (yych <= '\t') { + marker = p; + goto yy746; + } + if (yych <= '\n') { + marker = p; + goto yy747; + } + goto yy742; + } else { + if (yych <= '\r') { + marker = p; + goto yy747; + } + if (yych == ' ') { + marker = p; + goto yy746; + } + goto yy742; + } + yy745: + yych = *++p; + if (yybm[0 + yych] & 64) { + goto yy745; + } + if (yych <= '\f') { + if (yych <= 0x08) + goto yy742; + if (yych <= '\t') { + marker = p; + goto yy748; + } + if (yych <= '\n') { + marker = p; + goto yy749; + } + goto yy742; + } else { + if (yych <= '\r') { + marker = p; + goto yy749; + } + if (yych == ' ') { + marker = p; + goto yy748; + } + goto yy742; + } + yy746: + yych = *++p; + if (yybm[0 + yych] & 128) { + goto yy746; + } + if (yych <= 0x08) + goto yy742; + if (yych <= '\n') + goto yy747; + if (yych != '\r') + goto yy742; + yy747: + ++p; + p = marker; + { return (bufsize_t)(p - start); } + yy748: + yych = *++p; + if (yych <= '\f') { + if (yych <= 0x08) + goto yy742; + if (yych <= '\t') + goto yy748; + if (yych >= '\v') + goto yy742; + } else { + if (yych <= '\r') + goto yy749; + if (yych == ' ') + goto yy748; + goto yy742; + } + yy749: + ++p; + p = marker; + { return (bufsize_t)(p - start); } + } } // Scans an entity. // Returns number of chars matched. -bufsize_t _scan_entity(const unsigned char *p) -{ +bufsize_t _scan_entity(const unsigned char *p) { const unsigned char *marker = NULL; const unsigned char *start = p; -{ - unsigned char yych; - yych = *p; - if (yych == '&') goto yy869; - ++p; -yy868: - { return 0; } -yy869: - yych = *(marker = ++p); - if (yych <= '@') { - if (yych != '#') goto yy868; - } else { - if (yych <= 'Z') goto yy872; - if (yych <= '`') goto yy868; - if (yych <= 'z') goto yy872; - goto yy868; - } - yych = *++p; - if (yych <= 'W') { - if (yych <= '/') goto yy871; - if (yych <= '9') goto yy873; - } else { - if (yych <= 'X') goto yy874; - if (yych == 'x') goto yy874; - } -yy871: - p = marker; - goto yy868; -yy872: - yych = *++p; - if (yych <= '@') { - if (yych <= '/') goto yy871; - if (yych <= '9') goto yy875; - goto yy871; - } else { - if (yych <= 'Z') goto yy875; - if (yych <= '`') goto yy871; - if (yych <= 'z') goto yy875; - goto yy871; - } -yy873: - yych = *++p; - if (yych <= '/') goto yy871; - if (yych <= '9') goto yy876; - if (yych == ';') goto yy877; - goto yy871; -yy874: - yych = *++p; - if (yych <= '@') { - if (yych <= '/') goto yy871; - if (yych <= '9') goto yy879; - goto yy871; - } else { - if (yych <= 'F') goto yy879; - if (yych <= '`') goto yy871; - if (yych <= 'f') goto yy879; - goto yy871; - } -yy875: - yych = *++p; - if (yych <= ';') { - if (yych <= '/') goto yy871; - if (yych <= '9') goto yy880; - if (yych <= ':') goto yy871; - goto yy877; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy871; - goto yy880; - } else { - if (yych <= '`') goto yy871; - if (yych <= 'z') goto yy880; - goto yy871; - } - } -yy876: - yych = *++p; - if (yych <= '/') goto yy871; - if (yych <= '9') goto yy881; - if (yych != ';') goto yy871; -yy877: - ++p; - { return (bufsize_t)(p - start); } -yy879: - yych = *++p; - if (yych <= ';') { - if (yych <= '/') goto yy871; - if (yych <= '9') goto yy882; - if (yych <= ':') goto yy871; - goto yy877; - } else { - if (yych <= 'F') { - if (yych <= '@') goto yy871; - goto yy882; - } else { - if (yych <= '`') goto yy871; - if (yych <= 'f') goto yy882; - goto yy871; - } - } -yy880: - yych = *++p; - if (yych <= ';') { - if (yych <= '/') goto yy871; - if (yych <= '9') goto yy883; - if (yych <= ':') goto yy871; - goto yy877; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy871; - goto yy883; - } else { - if (yych <= '`') goto yy871; - if (yych <= 'z') goto yy883; - goto yy871; - } - } -yy881: - yych = *++p; - if (yych <= '/') goto yy871; - if (yych <= '9') goto yy884; - if (yych == ';') goto yy877; - goto yy871; -yy882: - yych = *++p; - if (yych <= ';') { - if (yych <= '/') goto yy871; - if (yych <= '9') goto yy885; - if (yych <= ':') goto yy871; - goto yy877; - } else { - if (yych <= 'F') { - if (yych <= '@') goto yy871; - goto yy885; - } else { - if (yych <= '`') goto yy871; - if (yych <= 'f') goto yy885; - goto yy871; - } - } -yy883: - yych = *++p; - if (yych <= ';') { - if (yych <= '/') goto yy871; - if (yych <= '9') goto yy886; - if (yych <= ':') goto yy871; - goto yy877; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy871; - goto yy886; - } else { - if (yych <= '`') goto yy871; - if (yych <= 'z') goto yy886; - goto yy871; - } - } -yy884: - yych = *++p; - if (yych <= '/') goto yy871; - if (yych <= '9') goto yy887; - if (yych == ';') goto yy877; - goto yy871; -yy885: - yych = *++p; - if (yych <= ';') { - if (yych <= '/') goto yy871; - if (yych <= '9') goto yy888; - if (yych <= ':') goto yy871; - goto yy877; - } else { - if (yych <= 'F') { - if (yych <= '@') goto yy871; - goto yy888; - } else { - if (yych <= '`') goto yy871; - if (yych <= 'f') goto yy888; - goto yy871; - } - } -yy886: - yych = *++p; - if (yych <= ';') { - if (yych <= '/') goto yy871; - if (yych <= '9') goto yy889; - if (yych <= ':') goto yy871; - goto yy877; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy871; - goto yy889; - } else { - if (yych <= '`') goto yy871; - if (yych <= 'z') goto yy889; - goto yy871; - } - } -yy887: - yych = *++p; - if (yych <= '/') goto yy871; - if (yych <= '9') goto yy890; - if (yych == ';') goto yy877; - goto yy871; -yy888: - yych = *++p; - if (yych <= ';') { - if (yych <= '/') goto yy871; - if (yych <= '9') goto yy891; - if (yych <= ':') goto yy871; - goto yy877; - } else { - if (yych <= 'F') { - if (yych <= '@') goto yy871; - goto yy891; - } else { - if (yych <= '`') goto yy871; - if (yych <= 'f') goto yy891; - goto yy871; - } - } -yy889: - yych = *++p; - if (yych <= ';') { - if (yych <= '/') goto yy871; - if (yych <= '9') goto yy892; - if (yych <= ':') goto yy871; - goto yy877; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy871; - goto yy892; - } else { - if (yych <= '`') goto yy871; - if (yych <= 'z') goto yy892; - goto yy871; - } - } -yy890: - yych = *++p; - if (yych <= '/') goto yy871; - if (yych <= '9') goto yy893; - if (yych == ';') goto yy877; - goto yy871; -yy891: - yych = *++p; - if (yych <= ';') { - if (yych <= '/') goto yy871; - if (yych <= '9') goto yy893; - if (yych <= ':') goto yy871; - goto yy877; - } else { - if (yych <= 'F') { - if (yych <= '@') goto yy871; - goto yy893; - } else { - if (yych <= '`') goto yy871; - if (yych <= 'f') goto yy893; - goto yy871; - } - } -yy892: - yych = *++p; - if (yych <= ';') { - if (yych <= '/') goto yy871; - if (yych <= '9') goto yy894; - if (yych <= ':') goto yy871; - goto yy877; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy871; - goto yy894; - } else { - if (yych <= '`') goto yy871; - if (yych <= 'z') goto yy894; - goto yy871; - } - } -yy893: - yych = *++p; - if (yych == ';') goto yy877; - goto yy871; -yy894: - yych = *++p; - if (yych <= ';') { - if (yych <= '/') goto yy871; - if (yych <= '9') goto yy895; - if (yych <= ':') goto yy871; - goto yy877; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy871; - } else { - if (yych <= '`') goto yy871; - if (yych >= '{') goto yy871; - } - } -yy895: - yych = *++p; - if (yych <= ';') { - if (yych <= '/') goto yy871; - if (yych <= '9') goto yy896; - if (yych <= ':') goto yy871; - goto yy877; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy871; - } else { - if (yych <= '`') goto yy871; - if (yych >= '{') goto yy871; - } - } -yy896: - yych = *++p; - if (yych <= ';') { - if (yych <= '/') goto yy871; - if (yych <= '9') goto yy897; - if (yych <= ':') goto yy871; - goto yy877; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy871; - } else { - if (yych <= '`') goto yy871; - if (yych >= '{') goto yy871; - } - } -yy897: - yych = *++p; - if (yych <= ';') { - if (yych <= '/') goto yy871; - if (yych <= '9') goto yy898; - if (yych <= ':') goto yy871; - goto yy877; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy871; - } else { - if (yych <= '`') goto yy871; - if (yych >= '{') goto yy871; - } - } -yy898: - yych = *++p; - if (yych <= ';') { - if (yych <= '/') goto yy871; - if (yych <= '9') goto yy899; - if (yych <= ':') goto yy871; - goto yy877; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy871; - } else { - if (yych <= '`') goto yy871; - if (yych >= '{') goto yy871; - } - } -yy899: - yych = *++p; - if (yych <= ';') { - if (yych <= '/') goto yy871; - if (yych <= '9') goto yy900; - if (yych <= ':') goto yy871; - goto yy877; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy871; - } else { - if (yych <= '`') goto yy871; - if (yych >= '{') goto yy871; - } - } -yy900: - yych = *++p; - if (yych <= ';') { - if (yych <= '/') goto yy871; - if (yych <= '9') goto yy901; - if (yych <= ':') goto yy871; - goto yy877; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy871; - } else { - if (yych <= '`') goto yy871; - if (yych >= '{') goto yy871; - } - } -yy901: - yych = *++p; - if (yych <= ';') { - if (yych <= '/') goto yy871; - if (yych <= '9') goto yy902; - if (yych <= ':') goto yy871; - goto yy877; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy871; - } else { - if (yych <= '`') goto yy871; - if (yych >= '{') goto yy871; - } - } -yy902: - yych = *++p; - if (yych <= ';') { - if (yych <= '/') goto yy871; - if (yych <= '9') goto yy903; - if (yych <= ':') goto yy871; - goto yy877; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy871; - } else { - if (yych <= '`') goto yy871; - if (yych >= '{') goto yy871; - } - } -yy903: - yych = *++p; - if (yych <= ';') { - if (yych <= '/') goto yy871; - if (yych <= '9') goto yy904; - if (yych <= ':') goto yy871; - goto yy877; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy871; - } else { - if (yych <= '`') goto yy871; - if (yych >= '{') goto yy871; - } - } -yy904: - yych = *++p; - if (yych <= ';') { - if (yych <= '/') goto yy871; - if (yych <= '9') goto yy905; - if (yych <= ':') goto yy871; - goto yy877; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy871; - } else { - if (yych <= '`') goto yy871; - if (yych >= '{') goto yy871; - } - } -yy905: - yych = *++p; - if (yych <= ';') { - if (yych <= '/') goto yy871; - if (yych <= '9') goto yy906; - if (yych <= ':') goto yy871; - goto yy877; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy871; - } else { - if (yych <= '`') goto yy871; - if (yych >= '{') goto yy871; - } - } -yy906: - yych = *++p; - if (yych <= ';') { - if (yych <= '/') goto yy871; - if (yych <= '9') goto yy907; - if (yych <= ':') goto yy871; - goto yy877; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy871; - } else { - if (yych <= '`') goto yy871; - if (yych >= '{') goto yy871; - } - } -yy907: - yych = *++p; - if (yych <= ';') { - if (yych <= '/') goto yy871; - if (yych <= '9') goto yy908; - if (yych <= ':') goto yy871; - goto yy877; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy871; - } else { - if (yych <= '`') goto yy871; - if (yych >= '{') goto yy871; - } - } -yy908: - yych = *++p; - if (yych <= ';') { - if (yych <= '/') goto yy871; - if (yych <= '9') goto yy909; - if (yych <= ':') goto yy871; - goto yy877; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy871; - } else { - if (yych <= '`') goto yy871; - if (yych >= '{') goto yy871; - } - } -yy909: - yych = *++p; - if (yych <= ';') { - if (yych <= '/') goto yy871; - if (yych <= '9') goto yy910; - if (yych <= ':') goto yy871; - goto yy877; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy871; - } else { - if (yych <= '`') goto yy871; - if (yych >= '{') goto yy871; - } - } -yy910: - yych = *++p; - if (yych <= ';') { - if (yych <= '/') goto yy871; - if (yych <= '9') goto yy911; - if (yych <= ':') goto yy871; - goto yy877; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy871; - } else { - if (yych <= '`') goto yy871; - if (yych >= '{') goto yy871; - } - } -yy911: - yych = *++p; - if (yych <= ';') { - if (yych <= '/') goto yy871; - if (yych <= '9') goto yy912; - if (yych <= ':') goto yy871; - goto yy877; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy871; - } else { - if (yych <= '`') goto yy871; - if (yych >= '{') goto yy871; - } - } -yy912: - yych = *++p; - if (yych <= ';') { - if (yych <= '/') goto yy871; - if (yych <= '9') goto yy913; - if (yych <= ':') goto yy871; - goto yy877; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy871; - } else { - if (yych <= '`') goto yy871; - if (yych >= '{') goto yy871; - } - } -yy913: - yych = *++p; - if (yych <= ';') { - if (yych <= '/') goto yy871; - if (yych <= '9') goto yy914; - if (yych <= ':') goto yy871; - goto yy877; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy871; - } else { - if (yych <= '`') goto yy871; - if (yych >= '{') goto yy871; - } - } -yy914: - yych = *++p; - if (yych <= ';') { - if (yych <= '/') goto yy871; - if (yych <= '9') goto yy915; - if (yych <= ':') goto yy871; - goto yy877; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy871; - } else { - if (yych <= '`') goto yy871; - if (yych >= '{') goto yy871; - } - } -yy915: - yych = *++p; - if (yych <= ';') { - if (yych <= '/') goto yy871; - if (yych <= '9') goto yy916; - if (yych <= ':') goto yy871; - goto yy877; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy871; - } else { - if (yych <= '`') goto yy871; - if (yych >= '{') goto yy871; - } - } -yy916: - yych = *++p; - if (yych <= ';') { - if (yych <= '/') goto yy871; - if (yych <= '9') goto yy917; - if (yych <= ':') goto yy871; - goto yy877; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy871; - } else { - if (yych <= '`') goto yy871; - if (yych >= '{') goto yy871; - } - } -yy917: - yych = *++p; - if (yych <= ';') { - if (yych <= '/') goto yy871; - if (yych <= '9') goto yy893; - if (yych <= ':') goto yy871; - goto yy877; - } else { - if (yych <= 'Z') { - if (yych <= '@') goto yy871; - goto yy893; - } else { - if (yych <= '`') goto yy871; - if (yych <= 'z') goto yy893; - goto yy871; - } - } -} - + { + unsigned char yych; + yych = *p; + if (yych == '&') + goto yy752; + ++p; + yy751 : { return 0; } + yy752: + yych = *(marker = ++p); + if (yych <= '@') { + if (yych != '#') + goto yy751; + } else { + if (yych <= 'Z') + goto yy754; + if (yych <= '`') + goto yy751; + if (yych <= 'z') + goto yy754; + goto yy751; + } + yych = *++p; + if (yych <= 'W') { + if (yych <= '/') + goto yy753; + if (yych <= '9') + goto yy755; + } else { + if (yych <= 'X') + goto yy756; + if (yych == 'x') + goto yy756; + } + yy753: + p = marker; + goto yy751; + yy754: + yych = *++p; + if (yych <= '@') { + if (yych <= '/') + goto yy753; + if (yych <= '9') + goto yy757; + goto yy753; + } else { + if (yych <= 'Z') + goto yy757; + if (yych <= '`') + goto yy753; + if (yych <= 'z') + goto yy757; + goto yy753; + } + yy755: + yych = *++p; + if (yych <= '/') + goto yy753; + if (yych <= '9') + goto yy758; + if (yych == ';') + goto yy759; + goto yy753; + yy756: + yych = *++p; + if (yych <= '@') { + if (yych <= '/') + goto yy753; + if (yych <= '9') + goto yy760; + goto yy753; + } else { + if (yych <= 'F') + goto yy760; + if (yych <= '`') + goto yy753; + if (yych <= 'f') + goto yy760; + goto yy753; + } + yy757: + yych = *++p; + if (yych <= ';') { + if (yych <= '/') + goto yy753; + if (yych <= '9') + goto yy761; + if (yych <= ':') + goto yy753; + goto yy759; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy753; + goto yy761; + } else { + if (yych <= '`') + goto yy753; + if (yych <= 'z') + goto yy761; + goto yy753; + } + } + yy758: + yych = *++p; + if (yych <= '/') + goto yy753; + if (yych <= '9') + goto yy762; + if (yych != ';') + goto yy753; + yy759: + ++p; + { return (bufsize_t)(p - start); } + yy760: + yych = *++p; + if (yych <= ';') { + if (yych <= '/') + goto yy753; + if (yych <= '9') + goto yy763; + if (yych <= ':') + goto yy753; + goto yy759; + } else { + if (yych <= 'F') { + if (yych <= '@') + goto yy753; + goto yy763; + } else { + if (yych <= '`') + goto yy753; + if (yych <= 'f') + goto yy763; + goto yy753; + } + } + yy761: + yych = *++p; + if (yych <= ';') { + if (yych <= '/') + goto yy753; + if (yych <= '9') + goto yy764; + if (yych <= ':') + goto yy753; + goto yy759; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy753; + goto yy764; + } else { + if (yych <= '`') + goto yy753; + if (yych <= 'z') + goto yy764; + goto yy753; + } + } + yy762: + yych = *++p; + if (yych <= '/') + goto yy753; + if (yych <= '9') + goto yy765; + if (yych == ';') + goto yy759; + goto yy753; + yy763: + yych = *++p; + if (yych <= ';') { + if (yych <= '/') + goto yy753; + if (yych <= '9') + goto yy766; + if (yych <= ':') + goto yy753; + goto yy759; + } else { + if (yych <= 'F') { + if (yych <= '@') + goto yy753; + goto yy766; + } else { + if (yych <= '`') + goto yy753; + if (yych <= 'f') + goto yy766; + goto yy753; + } + } + yy764: + yych = *++p; + if (yych <= ';') { + if (yych <= '/') + goto yy753; + if (yych <= '9') + goto yy767; + if (yych <= ':') + goto yy753; + goto yy759; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy753; + goto yy767; + } else { + if (yych <= '`') + goto yy753; + if (yych <= 'z') + goto yy767; + goto yy753; + } + } + yy765: + yych = *++p; + if (yych <= '/') + goto yy753; + if (yych <= '9') + goto yy768; + if (yych == ';') + goto yy759; + goto yy753; + yy766: + yych = *++p; + if (yych <= ';') { + if (yych <= '/') + goto yy753; + if (yych <= '9') + goto yy769; + if (yych <= ':') + goto yy753; + goto yy759; + } else { + if (yych <= 'F') { + if (yych <= '@') + goto yy753; + goto yy769; + } else { + if (yych <= '`') + goto yy753; + if (yych <= 'f') + goto yy769; + goto yy753; + } + } + yy767: + yych = *++p; + if (yych <= ';') { + if (yych <= '/') + goto yy753; + if (yych <= '9') + goto yy770; + if (yych <= ':') + goto yy753; + goto yy759; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy753; + goto yy770; + } else { + if (yych <= '`') + goto yy753; + if (yych <= 'z') + goto yy770; + goto yy753; + } + } + yy768: + yych = *++p; + if (yych <= '/') + goto yy753; + if (yych <= '9') + goto yy771; + if (yych == ';') + goto yy759; + goto yy753; + yy769: + yych = *++p; + if (yych <= ';') { + if (yych <= '/') + goto yy753; + if (yych <= '9') + goto yy772; + if (yych <= ':') + goto yy753; + goto yy759; + } else { + if (yych <= 'F') { + if (yych <= '@') + goto yy753; + goto yy772; + } else { + if (yych <= '`') + goto yy753; + if (yych <= 'f') + goto yy772; + goto yy753; + } + } + yy770: + yych = *++p; + if (yych <= ';') { + if (yych <= '/') + goto yy753; + if (yych <= '9') + goto yy773; + if (yych <= ':') + goto yy753; + goto yy759; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy753; + goto yy773; + } else { + if (yych <= '`') + goto yy753; + if (yych <= 'z') + goto yy773; + goto yy753; + } + } + yy771: + yych = *++p; + if (yych <= '/') + goto yy753; + if (yych <= '9') + goto yy774; + if (yych == ';') + goto yy759; + goto yy753; + yy772: + yych = *++p; + if (yych <= ';') { + if (yych <= '/') + goto yy753; + if (yych <= '9') + goto yy774; + if (yych <= ':') + goto yy753; + goto yy759; + } else { + if (yych <= 'F') { + if (yych <= '@') + goto yy753; + goto yy774; + } else { + if (yych <= '`') + goto yy753; + if (yych <= 'f') + goto yy774; + goto yy753; + } + } + yy773: + yych = *++p; + if (yych <= ';') { + if (yych <= '/') + goto yy753; + if (yych <= '9') + goto yy775; + if (yych <= ':') + goto yy753; + goto yy759; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy753; + goto yy775; + } else { + if (yych <= '`') + goto yy753; + if (yych <= 'z') + goto yy775; + goto yy753; + } + } + yy774: + yych = *++p; + if (yych == ';') + goto yy759; + goto yy753; + yy775: + yych = *++p; + if (yych <= ';') { + if (yych <= '/') + goto yy753; + if (yych <= '9') + goto yy776; + if (yych <= ':') + goto yy753; + goto yy759; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy753; + } else { + if (yych <= '`') + goto yy753; + if (yych >= '{') + goto yy753; + } + } + yy776: + yych = *++p; + if (yych <= ';') { + if (yych <= '/') + goto yy753; + if (yych <= '9') + goto yy777; + if (yych <= ':') + goto yy753; + goto yy759; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy753; + } else { + if (yych <= '`') + goto yy753; + if (yych >= '{') + goto yy753; + } + } + yy777: + yych = *++p; + if (yych <= ';') { + if (yych <= '/') + goto yy753; + if (yych <= '9') + goto yy778; + if (yych <= ':') + goto yy753; + goto yy759; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy753; + } else { + if (yych <= '`') + goto yy753; + if (yych >= '{') + goto yy753; + } + } + yy778: + yych = *++p; + if (yych <= ';') { + if (yych <= '/') + goto yy753; + if (yych <= '9') + goto yy779; + if (yych <= ':') + goto yy753; + goto yy759; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy753; + } else { + if (yych <= '`') + goto yy753; + if (yych >= '{') + goto yy753; + } + } + yy779: + yych = *++p; + if (yych <= ';') { + if (yych <= '/') + goto yy753; + if (yych <= '9') + goto yy780; + if (yych <= ':') + goto yy753; + goto yy759; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy753; + } else { + if (yych <= '`') + goto yy753; + if (yych >= '{') + goto yy753; + } + } + yy780: + yych = *++p; + if (yych <= ';') { + if (yych <= '/') + goto yy753; + if (yych <= '9') + goto yy781; + if (yych <= ':') + goto yy753; + goto yy759; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy753; + } else { + if (yych <= '`') + goto yy753; + if (yych >= '{') + goto yy753; + } + } + yy781: + yych = *++p; + if (yych <= ';') { + if (yych <= '/') + goto yy753; + if (yych <= '9') + goto yy782; + if (yych <= ':') + goto yy753; + goto yy759; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy753; + } else { + if (yych <= '`') + goto yy753; + if (yych >= '{') + goto yy753; + } + } + yy782: + yych = *++p; + if (yych <= ';') { + if (yych <= '/') + goto yy753; + if (yych <= '9') + goto yy783; + if (yych <= ':') + goto yy753; + goto yy759; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy753; + } else { + if (yych <= '`') + goto yy753; + if (yych >= '{') + goto yy753; + } + } + yy783: + yych = *++p; + if (yych <= ';') { + if (yych <= '/') + goto yy753; + if (yych <= '9') + goto yy784; + if (yych <= ':') + goto yy753; + goto yy759; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy753; + } else { + if (yych <= '`') + goto yy753; + if (yych >= '{') + goto yy753; + } + } + yy784: + yych = *++p; + if (yych <= ';') { + if (yych <= '/') + goto yy753; + if (yych <= '9') + goto yy785; + if (yych <= ':') + goto yy753; + goto yy759; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy753; + } else { + if (yych <= '`') + goto yy753; + if (yych >= '{') + goto yy753; + } + } + yy785: + yych = *++p; + if (yych <= ';') { + if (yych <= '/') + goto yy753; + if (yych <= '9') + goto yy786; + if (yych <= ':') + goto yy753; + goto yy759; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy753; + } else { + if (yych <= '`') + goto yy753; + if (yych >= '{') + goto yy753; + } + } + yy786: + yych = *++p; + if (yych <= ';') { + if (yych <= '/') + goto yy753; + if (yych <= '9') + goto yy787; + if (yych <= ':') + goto yy753; + goto yy759; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy753; + } else { + if (yych <= '`') + goto yy753; + if (yych >= '{') + goto yy753; + } + } + yy787: + yych = *++p; + if (yych <= ';') { + if (yych <= '/') + goto yy753; + if (yych <= '9') + goto yy788; + if (yych <= ':') + goto yy753; + goto yy759; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy753; + } else { + if (yych <= '`') + goto yy753; + if (yych >= '{') + goto yy753; + } + } + yy788: + yych = *++p; + if (yych <= ';') { + if (yych <= '/') + goto yy753; + if (yych <= '9') + goto yy789; + if (yych <= ':') + goto yy753; + goto yy759; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy753; + } else { + if (yych <= '`') + goto yy753; + if (yych >= '{') + goto yy753; + } + } + yy789: + yych = *++p; + if (yych <= ';') { + if (yych <= '/') + goto yy753; + if (yych <= '9') + goto yy790; + if (yych <= ':') + goto yy753; + goto yy759; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy753; + } else { + if (yych <= '`') + goto yy753; + if (yych >= '{') + goto yy753; + } + } + yy790: + yych = *++p; + if (yych <= ';') { + if (yych <= '/') + goto yy753; + if (yych <= '9') + goto yy791; + if (yych <= ':') + goto yy753; + goto yy759; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy753; + } else { + if (yych <= '`') + goto yy753; + if (yych >= '{') + goto yy753; + } + } + yy791: + yych = *++p; + if (yych <= ';') { + if (yych <= '/') + goto yy753; + if (yych <= '9') + goto yy792; + if (yych <= ':') + goto yy753; + goto yy759; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy753; + } else { + if (yych <= '`') + goto yy753; + if (yych >= '{') + goto yy753; + } + } + yy792: + yych = *++p; + if (yych <= ';') { + if (yych <= '/') + goto yy753; + if (yych <= '9') + goto yy793; + if (yych <= ':') + goto yy753; + goto yy759; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy753; + } else { + if (yych <= '`') + goto yy753; + if (yych >= '{') + goto yy753; + } + } + yy793: + yych = *++p; + if (yych <= ';') { + if (yych <= '/') + goto yy753; + if (yych <= '9') + goto yy794; + if (yych <= ':') + goto yy753; + goto yy759; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy753; + } else { + if (yych <= '`') + goto yy753; + if (yych >= '{') + goto yy753; + } + } + yy794: + yych = *++p; + if (yych <= ';') { + if (yych <= '/') + goto yy753; + if (yych <= '9') + goto yy795; + if (yych <= ':') + goto yy753; + goto yy759; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy753; + } else { + if (yych <= '`') + goto yy753; + if (yych >= '{') + goto yy753; + } + } + yy795: + yych = *++p; + if (yych <= ';') { + if (yych <= '/') + goto yy753; + if (yych <= '9') + goto yy796; + if (yych <= ':') + goto yy753; + goto yy759; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy753; + } else { + if (yych <= '`') + goto yy753; + if (yych >= '{') + goto yy753; + } + } + yy796: + yych = *++p; + if (yych <= ';') { + if (yych <= '/') + goto yy753; + if (yych <= '9') + goto yy797; + if (yych <= ':') + goto yy753; + goto yy759; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy753; + } else { + if (yych <= '`') + goto yy753; + if (yych >= '{') + goto yy753; + } + } + yy797: + yych = *++p; + if (yych <= ';') { + if (yych <= '/') + goto yy753; + if (yych <= '9') + goto yy798; + if (yych <= ':') + goto yy753; + goto yy759; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy753; + } else { + if (yych <= '`') + goto yy753; + if (yych >= '{') + goto yy753; + } + } + yy798: + yych = *++p; + if (yych <= ';') { + if (yych <= '/') + goto yy753; + if (yych <= '9') + goto yy774; + if (yych <= ':') + goto yy753; + goto yy759; + } else { + if (yych <= 'Z') { + if (yych <= '@') + goto yy753; + goto yy774; + } else { + if (yych <= '`') + goto yy753; + if (yych <= 'z') + goto yy774; + goto yy753; + } + } + } } // Returns positive value if a URL begins in a way that is potentially // dangerous, with javascript:, vbscript:, file:, or data:, otherwise 0. -bufsize_t _scan_dangerous_url(const unsigned char *p) -{ +bufsize_t _scan_dangerous_url(const unsigned char *p) { const unsigned char *marker = NULL; const unsigned char *start = p; -{ - unsigned char yych; - unsigned int yyaccept = 0; - yych = *p; - if (yych <= 'V') { - if (yych <= 'F') { - if (yych == 'D') goto yy922; - if (yych >= 'F') goto yy923; - } else { - if (yych == 'J') goto yy924; - if (yych >= 'V') goto yy925; - } - } else { - if (yych <= 'f') { - if (yych == 'd') goto yy922; - if (yych >= 'f') goto yy923; - } else { - if (yych <= 'j') { - if (yych >= 'j') goto yy924; - } else { - if (yych == 'v') goto yy925; - } - } - } - ++p; -yy921: - { return 0; } -yy922: - yyaccept = 0; - yych = *(marker = ++p); - if (yych == 'A') goto yy926; - if (yych == 'a') goto yy926; - goto yy921; -yy923: - yyaccept = 0; - yych = *(marker = ++p); - if (yych == 'I') goto yy928; - if (yych == 'i') goto yy928; - goto yy921; -yy924: - yyaccept = 0; - yych = *(marker = ++p); - if (yych == 'A') goto yy929; - if (yych == 'a') goto yy929; - goto yy921; -yy925: - yyaccept = 0; - yych = *(marker = ++p); - if (yych == 'B') goto yy930; - if (yych == 'b') goto yy930; - goto yy921; -yy926: - yych = *++p; - if (yych == 'T') goto yy931; - if (yych == 't') goto yy931; -yy927: - p = marker; - if (yyaccept == 0) { - goto yy921; - } else { - goto yy939; - } -yy928: - yych = *++p; - if (yych == 'L') goto yy932; - if (yych == 'l') goto yy932; - goto yy927; -yy929: - yych = *++p; - if (yych == 'V') goto yy933; - if (yych == 'v') goto yy933; - goto yy927; -yy930: - yych = *++p; - if (yych == 'S') goto yy934; - if (yych == 's') goto yy934; - goto yy927; -yy931: - yych = *++p; - if (yych == 'A') goto yy935; - if (yych == 'a') goto yy935; - goto yy927; -yy932: - yych = *++p; - if (yych == 'E') goto yy936; - if (yych == 'e') goto yy936; - goto yy927; -yy933: - yych = *++p; - if (yych == 'A') goto yy930; - if (yych == 'a') goto yy930; - goto yy927; -yy934: - yych = *++p; - if (yych == 'C') goto yy937; - if (yych == 'c') goto yy937; - goto yy927; -yy935: - yych = *++p; - if (yych == ':') goto yy938; - goto yy927; -yy936: - yych = *++p; - if (yych == ':') goto yy940; - goto yy927; -yy937: - yych = *++p; - if (yych == 'R') goto yy941; - if (yych == 'r') goto yy941; - goto yy927; -yy938: - yyaccept = 1; - yych = *(marker = ++p); - if (yych == 'I') goto yy942; - if (yych == 'i') goto yy942; -yy939: - { return (bufsize_t)(p - start); } -yy940: - ++p; - goto yy939; -yy941: - yych = *++p; - if (yych == 'I') goto yy943; - if (yych == 'i') goto yy943; - goto yy927; -yy942: - yych = *++p; - if (yych == 'M') goto yy944; - if (yych == 'm') goto yy944; - goto yy927; -yy943: - yych = *++p; - if (yych == 'P') goto yy945; - if (yych == 'p') goto yy945; - goto yy927; -yy944: - yych = *++p; - if (yych == 'A') goto yy946; - if (yych == 'a') goto yy946; - goto yy927; -yy945: - yych = *++p; - if (yych == 'T') goto yy936; - if (yych == 't') goto yy936; - goto yy927; -yy946: - yych = *++p; - if (yych == 'G') goto yy947; - if (yych != 'g') goto yy927; -yy947: - yych = *++p; - if (yych == 'E') goto yy948; - if (yych != 'e') goto yy927; -yy948: - yych = *++p; - if (yych != '/') goto yy927; - yych = *++p; - if (yych <= 'W') { - if (yych <= 'J') { - if (yych == 'G') goto yy950; - if (yych <= 'I') goto yy927; - goto yy951; - } else { - if (yych == 'P') goto yy952; - if (yych <= 'V') goto yy927; - goto yy953; - } - } else { - if (yych <= 'j') { - if (yych == 'g') goto yy950; - if (yych <= 'i') goto yy927; - goto yy951; - } else { - if (yych <= 'p') { - if (yych <= 'o') goto yy927; - goto yy952; - } else { - if (yych == 'w') goto yy953; - goto yy927; - } - } - } -yy950: - yych = *++p; - if (yych == 'I') goto yy954; - if (yych == 'i') goto yy954; - goto yy927; -yy951: - yych = *++p; - if (yych == 'P') goto yy955; - if (yych == 'p') goto yy955; - goto yy927; -yy952: - yych = *++p; - if (yych == 'N') goto yy956; - if (yych == 'n') goto yy956; - goto yy927; -yy953: - yych = *++p; - if (yych == 'E') goto yy957; - if (yych == 'e') goto yy957; - goto yy927; -yy954: - yych = *++p; - if (yych == 'F') goto yy958; - if (yych == 'f') goto yy958; - goto yy927; -yy955: - yych = *++p; - if (yych == 'E') goto yy956; - if (yych != 'e') goto yy927; -yy956: - yych = *++p; - if (yych == 'G') goto yy958; - if (yych == 'g') goto yy958; - goto yy927; -yy957: - yych = *++p; - if (yych == 'B') goto yy960; - if (yych == 'b') goto yy960; - goto yy927; -yy958: - ++p; - { return 0; } -yy960: - yych = *++p; - if (yych == 'P') goto yy958; - if (yych == 'p') goto yy958; - goto yy927; -} - + { + unsigned char yych; + unsigned int yyaccept = 0; + yych = *p; + if (yych <= 'V') { + if (yych <= 'F') { + if (yych == 'D') + goto yy801; + if (yych >= 'F') + goto yy802; + } else { + if (yych == 'J') + goto yy803; + if (yych >= 'V') + goto yy804; + } + } else { + if (yych <= 'f') { + if (yych == 'd') + goto yy801; + if (yych >= 'f') + goto yy802; + } else { + if (yych <= 'j') { + if (yych >= 'j') + goto yy803; + } else { + if (yych == 'v') + goto yy804; + } + } + } + ++p; + yy800 : { return 0; } + yy801: + yyaccept = 0; + yych = *(marker = ++p); + if (yych == 'A') + goto yy805; + if (yych == 'a') + goto yy805; + goto yy800; + yy802: + yyaccept = 0; + yych = *(marker = ++p); + if (yych == 'I') + goto yy807; + if (yych == 'i') + goto yy807; + goto yy800; + yy803: + yyaccept = 0; + yych = *(marker = ++p); + if (yych == 'A') + goto yy808; + if (yych == 'a') + goto yy808; + goto yy800; + yy804: + yyaccept = 0; + yych = *(marker = ++p); + if (yych == 'B') + goto yy809; + if (yych == 'b') + goto yy809; + goto yy800; + yy805: + yych = *++p; + if (yych == 'T') + goto yy810; + if (yych == 't') + goto yy810; + yy806: + p = marker; + if (yyaccept == 0) { + goto yy800; + } else { + goto yy818; + } + yy807: + yych = *++p; + if (yych == 'L') + goto yy811; + if (yych == 'l') + goto yy811; + goto yy806; + yy808: + yych = *++p; + if (yych == 'V') + goto yy812; + if (yych == 'v') + goto yy812; + goto yy806; + yy809: + yych = *++p; + if (yych == 'S') + goto yy813; + if (yych == 's') + goto yy813; + goto yy806; + yy810: + yych = *++p; + if (yych == 'A') + goto yy814; + if (yych == 'a') + goto yy814; + goto yy806; + yy811: + yych = *++p; + if (yych == 'E') + goto yy815; + if (yych == 'e') + goto yy815; + goto yy806; + yy812: + yych = *++p; + if (yych == 'A') + goto yy809; + if (yych == 'a') + goto yy809; + goto yy806; + yy813: + yych = *++p; + if (yych == 'C') + goto yy816; + if (yych == 'c') + goto yy816; + goto yy806; + yy814: + yych = *++p; + if (yych == ':') + goto yy817; + goto yy806; + yy815: + yych = *++p; + if (yych == ':') + goto yy819; + goto yy806; + yy816: + yych = *++p; + if (yych == 'R') + goto yy820; + if (yych == 'r') + goto yy820; + goto yy806; + yy817: + yyaccept = 1; + yych = *(marker = ++p); + if (yych == 'I') + goto yy821; + if (yych == 'i') + goto yy821; + yy818 : { return (bufsize_t)(p - start); } + yy819: + ++p; + goto yy818; + yy820: + yych = *++p; + if (yych == 'I') + goto yy822; + if (yych == 'i') + goto yy822; + goto yy806; + yy821: + yych = *++p; + if (yych == 'M') + goto yy823; + if (yych == 'm') + goto yy823; + goto yy806; + yy822: + yych = *++p; + if (yych == 'P') + goto yy824; + if (yych == 'p') + goto yy824; + goto yy806; + yy823: + yych = *++p; + if (yych == 'A') + goto yy825; + if (yych == 'a') + goto yy825; + goto yy806; + yy824: + yych = *++p; + if (yych == 'T') + goto yy815; + if (yych == 't') + goto yy815; + goto yy806; + yy825: + yych = *++p; + if (yych == 'G') + goto yy826; + if (yych != 'g') + goto yy806; + yy826: + yych = *++p; + if (yych == 'E') + goto yy827; + if (yych != 'e') + goto yy806; + yy827: + yych = *++p; + if (yych != '/') + goto yy806; + yych = *++p; + if (yych <= 'W') { + if (yych <= 'J') { + if (yych == 'G') + goto yy828; + if (yych <= 'I') + goto yy806; + goto yy829; + } else { + if (yych == 'P') + goto yy830; + if (yych <= 'V') + goto yy806; + goto yy831; + } + } else { + if (yych <= 'j') { + if (yych == 'g') + goto yy828; + if (yych <= 'i') + goto yy806; + goto yy829; + } else { + if (yych <= 'p') { + if (yych <= 'o') + goto yy806; + goto yy830; + } else { + if (yych == 'w') + goto yy831; + goto yy806; + } + } + } + yy828: + yych = *++p; + if (yych == 'I') + goto yy832; + if (yych == 'i') + goto yy832; + goto yy806; + yy829: + yych = *++p; + if (yych == 'P') + goto yy833; + if (yych == 'p') + goto yy833; + goto yy806; + yy830: + yych = *++p; + if (yych == 'N') + goto yy834; + if (yych == 'n') + goto yy834; + goto yy806; + yy831: + yych = *++p; + if (yych == 'E') + goto yy835; + if (yych == 'e') + goto yy835; + goto yy806; + yy832: + yych = *++p; + if (yych == 'F') + goto yy836; + if (yych == 'f') + goto yy836; + goto yy806; + yy833: + yych = *++p; + if (yych == 'E') + goto yy834; + if (yych != 'e') + goto yy806; + yy834: + yych = *++p; + if (yych == 'G') + goto yy836; + if (yych == 'g') + goto yy836; + goto yy806; + yy835: + yych = *++p; + if (yych == 'B') + goto yy837; + if (yych == 'b') + goto yy837; + goto yy806; + yy836: + ++p; + { return 0; } + yy837: + yych = *++p; + if (yych == 'P') + goto yy836; + if (yych == 'p') + goto yy836; + goto yy806; + } } // Scans a footnote definition opening. -bufsize_t _scan_footnote_definition(const unsigned char *p) -{ +bufsize_t _scan_footnote_definition(const unsigned char *p) { const unsigned char *marker = NULL; const unsigned char *start = p; -{ - unsigned char yych; - static const unsigned char yybm[] = { - 0, 64, 64, 64, 64, 64, 64, 64, - 64, 128, 0, 64, 64, 0, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 128, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 0, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 64, 64, 64, 64, 64, 64, 64, 64, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - }; - yych = *p; - if (yych == '[') goto yy965; - ++p; -yy964: - { return 0; } -yy965: - yych = *(marker = ++p); - if (yych != '^') goto yy964; - yych = *++p; - if (yych != ']') goto yy969; -yy967: - p = marker; - goto yy964; -yy968: - yych = *++p; -yy969: - if (yybm[0+yych] & 64) { - goto yy968; - } - if (yych <= 0xEC) { - if (yych <= 0xC1) { - if (yych <= ' ') goto yy967; - if (yych <= ']') goto yy977; - goto yy967; - } else { - if (yych <= 0xDF) goto yy970; - if (yych <= 0xE0) goto yy971; - goto yy972; - } - } else { - if (yych <= 0xF0) { - if (yych <= 0xED) goto yy973; - if (yych <= 0xEF) goto yy972; - goto yy974; - } else { - if (yych <= 0xF3) goto yy975; - if (yych <= 0xF4) goto yy976; - goto yy967; - } - } -yy970: - yych = *++p; - if (yych <= 0x7F) goto yy967; - if (yych <= 0xBF) goto yy968; - goto yy967; -yy971: - yych = *++p; - if (yych <= 0x9F) goto yy967; - if (yych <= 0xBF) goto yy970; - goto yy967; -yy972: - yych = *++p; - if (yych <= 0x7F) goto yy967; - if (yych <= 0xBF) goto yy970; - goto yy967; -yy973: - yych = *++p; - if (yych <= 0x7F) goto yy967; - if (yych <= 0x9F) goto yy970; - goto yy967; -yy974: - yych = *++p; - if (yych <= 0x8F) goto yy967; - if (yych <= 0xBF) goto yy972; - goto yy967; -yy975: - yych = *++p; - if (yych <= 0x7F) goto yy967; - if (yych <= 0xBF) goto yy972; - goto yy967; -yy976: - yych = *++p; - if (yych <= 0x7F) goto yy967; - if (yych <= 0x8F) goto yy972; - goto yy967; -yy977: - yych = *++p; - if (yych != ':') goto yy967; -yy978: - yych = *++p; - if (yybm[0+yych] & 128) { - goto yy978; - } - { return (bufsize_t)(p - start); } -} - + { + unsigned char yych; + static const unsigned char yybm[] = { + 0, 64, 64, 64, 64, 64, 64, 64, 64, 128, 0, 64, 64, 0, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 128, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 0, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }; + yych = *p; + if (yych == '[') + goto yy840; + ++p; + yy839 : { return 0; } + yy840: + yych = *(marker = ++p); + if (yych != '^') + goto yy839; + yych = *++p; + if (yych != ']') + goto yy843; + yy841: + p = marker; + goto yy839; + yy842: + yych = *++p; + yy843: + if (yybm[0 + yych] & 64) { + goto yy842; + } + if (yych <= 0xEC) { + if (yych <= 0xC1) { + if (yych <= ' ') + goto yy841; + if (yych <= ']') + goto yy851; + goto yy841; + } else { + if (yych <= 0xDF) + goto yy844; + if (yych <= 0xE0) + goto yy845; + goto yy846; + } + } else { + if (yych <= 0xF0) { + if (yych <= 0xED) + goto yy847; + if (yych <= 0xEF) + goto yy846; + goto yy848; + } else { + if (yych <= 0xF3) + goto yy849; + if (yych <= 0xF4) + goto yy850; + goto yy841; + } + } + yy844: + yych = *++p; + if (yych <= 0x7F) + goto yy841; + if (yych <= 0xBF) + goto yy842; + goto yy841; + yy845: + yych = *++p; + if (yych <= 0x9F) + goto yy841; + if (yych <= 0xBF) + goto yy844; + goto yy841; + yy846: + yych = *++p; + if (yych <= 0x7F) + goto yy841; + if (yych <= 0xBF) + goto yy844; + goto yy841; + yy847: + yych = *++p; + if (yych <= 0x7F) + goto yy841; + if (yych <= 0x9F) + goto yy844; + goto yy841; + yy848: + yych = *++p; + if (yych <= 0x8F) + goto yy841; + if (yych <= 0xBF) + goto yy846; + goto yy841; + yy849: + yych = *++p; + if (yych <= 0x7F) + goto yy841; + if (yych <= 0xBF) + goto yy846; + goto yy841; + yy850: + yych = *++p; + if (yych <= 0x7F) + goto yy841; + if (yych <= 0x8F) + goto yy846; + goto yy841; + yy851: + yych = *++p; + if (yych != ':') + goto yy841; + yy852: + yych = *++p; + if (yybm[0 + yych] & 128) { + goto yy852; + } + { return (bufsize_t)(p - start); } + } } diff --git a/src/plugins/ieviewer/cmark-gfm/src/scanners.h b/src/plugins/ieviewer/cmark-gfm/src/scanners.h index 8861f8dd..7e6a10ae 100644 --- a/src/plugins/ieviewer/cmark-gfm/src/scanners.h +++ b/src/plugins/ieviewer/cmark-gfm/src/scanners.h @@ -15,6 +15,10 @@ bufsize_t _scan_autolink_uri(const unsigned char *p); bufsize_t _scan_autolink_email(const unsigned char *p); bufsize_t _scan_html_tag(const unsigned char *p); bufsize_t _scan_liberal_html_tag(const unsigned char *p); +bufsize_t _scan_html_comment(const unsigned char *p); +bufsize_t _scan_html_pi(const unsigned char *p); +bufsize_t _scan_html_declaration(const unsigned char *p); +bufsize_t _scan_html_cdata(const unsigned char *p); bufsize_t _scan_html_block_start(const unsigned char *p); bufsize_t _scan_html_block_start_7(const unsigned char *p); bufsize_t _scan_html_block_end_1(const unsigned char *p); @@ -37,6 +41,10 @@ bufsize_t _scan_footnote_definition(const unsigned char *p); #define scan_autolink_email(c, n) _scan_at(&_scan_autolink_email, c, n) #define scan_html_tag(c, n) _scan_at(&_scan_html_tag, c, n) #define scan_liberal_html_tag(c, n) _scan_at(&_scan_liberal_html_tag, c, n) +#define scan_html_comment(c, n) _scan_at(&_scan_html_comment, c, n) +#define scan_html_pi(c, n) _scan_at(&_scan_html_pi, c, n) +#define scan_html_declaration(c, n) _scan_at(&_scan_html_declaration, c, n) +#define scan_html_cdata(c, n) _scan_at(&_scan_html_cdata, c, n) #define scan_html_block_start(c, n) _scan_at(&_scan_html_block_start, c, n) #define scan_html_block_start_7(c, n) _scan_at(&_scan_html_block_start_7, c, n) #define scan_html_block_end_1(c, n) _scan_at(&_scan_html_block_end_1, c, n) diff --git a/src/plugins/ieviewer/cmark-gfm/src/scanners.re b/src/plugins/ieviewer/cmark-gfm/src/scanners.re index c486f362..ac4b9cf4 100644 --- a/src/plugins/ieviewer/cmark-gfm/src/scanners.re +++ b/src/plugins/ieviewer/cmark-gfm/src/scanners.re @@ -54,16 +54,15 @@ bufsize_t _scan_at(bufsize_t (*scanner)(const unsigned char *), cmark_chunk *c, opentag = tagname attribute* spacechar* [/]? [>]; closetag = [/] tagname spacechar* [>]; - htmlcomment = "!---->" | ("!--" ([-]? [^\x00>-]) ([-]? [^\x00-])* "-->"); + htmlcomment = "--" ([^\x00-]+ | "-" [^\x00-] | "--" [^\x00>])* "-->"; - processinginstruction = "?" ([^?>\x00]+ | [?][^>\x00] | [>])* "?>"; + processinginstruction = ([^?>\x00]+ | [?][^>\x00] | [>])+; - declaration = "!" [A-Z]+ spacechar+ [^>\x00]* ">"; + declaration = [A-Z]+ spacechar+ [^>\x00]*; - cdata = "![CDATA[" ([^\]\x00]+ | "]" [^\]\x00] | "]]" [^>\x00])* "]]>"; + cdata = "CDATA[" ([^\]\x00]+ | "]" [^\]\x00] | "]]" [^>\x00])*; - htmltag = opentag | closetag | htmlcomment | processinginstruction | - declaration | cdata; + htmltag = opentag | closetag; in_parens_nosp = [(] (reg_char|escaped_char|[\\])* [)]; @@ -133,6 +132,46 @@ bufsize_t _scan_liberal_html_tag(const unsigned char *p) */ } +bufsize_t _scan_html_comment(const unsigned char *p) +{ + const unsigned char *marker = NULL; + const unsigned char *start = p; +/*!re2c + htmlcomment { return (bufsize_t)(p - start); } + * { return 0; } +*/ +} + +bufsize_t _scan_html_pi(const unsigned char *p) +{ + const unsigned char *marker = NULL; + const unsigned char *start = p; +/*!re2c + processinginstruction { return (bufsize_t)(p - start); } + * { return 0; } +*/ +} + +bufsize_t _scan_html_declaration(const unsigned char *p) +{ + const unsigned char *marker = NULL; + const unsigned char *start = p; +/*!re2c + declaration { return (bufsize_t)(p - start); } + * { return 0; } +*/ +} + +bufsize_t _scan_html_cdata(const unsigned char *p) +{ + const unsigned char *marker = NULL; + const unsigned char *start = p; +/*!re2c + cdata { return (bufsize_t)(p - start); } + * { return 0; } +*/ +} + // Try to match an HTML block tag start line, returning // an integer code for the type of block (1-6, matching the spec). // #7 is handled by a separate function, below. @@ -140,7 +179,7 @@ bufsize_t _scan_html_block_start(const unsigned char *p) { const unsigned char *marker = NULL; /*!re2c - [<] ('script'|'pre'|'style') (spacechar | [>]) { return 1; } + [<] ('script'|'pre'|'textarea'|'style') (spacechar | [>]) { return 1; } ' + NotUsing + NotUsing + NotUsing + NotUsing + Create Create @@ -433,6 +440,8 @@ + + @@ -464,4 +473,4 @@ - \ No newline at end of file + diff --git a/src/plugins/nethood/cache.cpp b/src/plugins/nethood/cache.cpp index 52add2ea..1ec60f5d 100644 --- a/src/plugins/nethood/cache.cpp +++ b/src/plugins/nethood/cache.cpp @@ -2459,7 +2459,9 @@ BOOL CNethoodCacheEnumerationThread::ResolveNetShortcut( NULL)); if (hFile != INVALID_HANDLE_VALUE) { - if (GetFileSize(hFile, NULL) <= 1000) // so far all had 92 bytes, so 1000 bytes should be more than enough + CFileOffsetResult sizeResult = SalGetPluginFileSizeEx(SalamanderGeneral, hFile); + // desktop.ini is intentionally bounded by the fixed scan buffer; inspect the complete size before reading it. + if (sizeResult.Succeeded && sizeResult.Value.Value <= 1000) // so far all had 92 bytes, so 1000 bytes should be more than enough { char buf[1000]; DWORD read; diff --git a/src/plugins/peviewer/peviewer.cpp b/src/plugins/peviewer/peviewer.cpp index af7194de..7c879862 100644 --- a/src/plugins/peviewer/peviewer.cpp +++ b/src/plugins/peviewer/peviewer.cpp @@ -279,7 +279,7 @@ CPluginInterface::GetInterfaceForViewer() // BOOL MapFileToMemory(LPCTSTR name, HANDLE& hFile, HANDLE& hFileMapping, - LPVOID& lpFileBase, DWORD& fileSize, BOOL quietMode) + LPVOID& lpFileBase, CQuadWord& fileSize, BOOL quietMode) { hFile = CreateFileUtf8Local(name, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); @@ -296,8 +296,8 @@ BOOL MapFileToMemory(LPCTSTR name, HANDLE& hFile, HANDLE& hFileMapping, return FALSE; } - fileSize = GetFileSize(hFile, NULL); - if (fileSize == -1) + CFileOffsetResult sizeResult = SalGetPluginFileSizeEx(SalGeneral, hFile); + if (!sizeResult.Succeeded) { if (!quietMode) { @@ -310,7 +310,23 @@ BOOL MapFileToMemory(LPCTSTR name, HANDLE& hFile, HANDLE& hFileMapping, return FALSE; } - hFileMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL); + fileSize = sizeResult.Value; + // The PE parser stores its validated byte offsets in DWORD fields, so do not silently truncate a larger mapping. + if (fileSize.HiDWord != 0) + { + if (!quietMode) + { + TCHAR buff[2000]; + _stprintf(buff, LoadStr(IDS_ERR_OPEN_FILE), name); + SalGeneral->SalMessageBox(SalGeneral->GetMsgBoxParent(), buff, + LoadStr(IDS_PLUGIN_NAME), MB_OK | MB_ICONEXCLAMATION); + } + CloseHandle(hFile); + return FALSE; + } + + // Keep the native mapping length in its original 64-bit representation even at the DWORD parser boundary. + hFileMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY, fileSize.HiDWord, fileSize.LoDWord, NULL); if (hFileMapping == 0) { if (!quietMode) @@ -365,7 +381,7 @@ BOOL CPluginInterfaceForViewer::ViewFile(LPCTSTR name, int left, int top, int wi HANDLE hFile; HANDLE hFileMapping; LPVOID lpFileBase; - DWORD fileSize; + CQuadWord fileSize; HCURSOR hOldCur = SetCursor(LoadCursor(NULL, IDC_WAIT)); @@ -385,7 +401,7 @@ BOOL CPluginInterfaceForViewer::ViewFile(LPCTSTR name, int left, int top, int wi // Create a temporary file and pour the module dump into it. FILE* outStream = _tfopen(tempFileName, _T("w")); - if (!DumpFileInfo(lpFileBase, fileSize, outStream)) + if (!DumpFileInfo(lpFileBase, fileSize.LoDWord, outStream)) { TCHAR buff[2000]; SetCursor(hOldCur); @@ -428,14 +444,14 @@ BOOL CPluginInterfaceForViewer::CanViewFile(LPCTSTR name) HANDLE hFile; HANDLE hFileMapping; LPVOID lpFileBase; - DWORD fileSize; + CQuadWord fileSize; // Try to map the file into memory (silently -- no error messages). if (!MapFileToMemory(name, hFile, hFileMapping, lpFileBase, fileSize, TRUE)) return FALSE; // Extract the header type. - CPEFile PEFile(lpFileBase, fileSize); + CPEFile PEFile(lpFileBase, fileSize.LoDWord); DWORD imageType = PEFile.ImageFileType(NULL); // Unmap the file from memory. diff --git a/src/plugins/shared/auxtools.cpp b/src/plugins/shared/auxtools.cpp index 28385676..41fddf3c 100644 --- a/src/plugins/shared/auxtools.cpp +++ b/src/plugins/shared/auxtools.cpp @@ -28,6 +28,48 @@ #include "dbg.h" #include "auxtools.h" +// Plug-in workers may still be executing plug-in code during release. A +// deadline makes a slow cooperative shutdown diagnosable without reviving the +// unsafe forced-termination path. +class CPluginThreadShutdownDeadline +{ +public: + CPluginThreadShutdownDeadline(const char* workerName) + : WorkerName(workerName != NULL ? workerName : "unnamed plug-in worker") + { + } + + DWORD WaitForSafeJoin(HANDLE worker) const + { + DWORD wait = WaitForSingleObject(worker, 5000); + if (wait != WAIT_TIMEOUT) + return wait; + + TraceBreach("cancellation", 5000, worker); + wait = WaitForSingleObject(worker, 30000); + if (wait != WAIT_TIMEOUT) + return WAIT_TIMEOUT; + + TraceBreach("operation recovery", 30000, worker); + WaitForSingleObject(worker, INFINITE); + return WAIT_TIMEOUT; + } + +private: + void TraceBreach(const char* phase, DWORD milliseconds, HANDLE worker) const + { + DWORD exitCode = STILL_ACTIVE; + if (!GetExitCodeThread(worker, &exitCode)) + exitCode = GetLastError(); + TRACE_E("Plug-in queue " << WorkerName << " breached " << phase << " deadline after " + << milliseconds << " ms; thread state=" << exitCode + << ". Keeping the process alive for a safe join."); + } + +private: + const char* WorkerName; +}; + // // **************************************************************************** // CThreadQueue @@ -37,16 +79,40 @@ CThreadQueue::CThreadQueue(const char* queueName) { QueueName = queueName; Head = NULL; - Continue = CreateEvent(NULL, FALSE, FALSE, NULL); } CThreadQueue::~CThreadQueue() { - ClearFinishedThreads(); // section not needed, only one thread should be using it now - if (Continue != NULL) - CloseHandle(Continue); + // The queue owns callback state, so workers must be joined before the + // critical section is destroyed by the containing queue object. + KillAll(TRUE, 0, 0); if (Head != NULL) - TRACE_E("Some thread is still in " << QueueName << " queue!"); // after terminating thread that waits for (or is currently terminating) another thread from queue, otherwise should not happen... + TRACE_E("Some thread is still in " << QueueName << " queue!"); +} + +CThreadQueueItem::CThreadQueueItem(CPluginThreadOwner* owner, DWORD tid) +{ + Owner = owner; + Thread = owner != NULL ? owner->GetThreadHandle() : NULL; + ThreadID = tid; + Next = NULL; + Locks = 0; +} + +CThreadQueueItem::CThreadQueueItem(HANDLE thread, DWORD tid) +{ + // DiskMap creates workers directly, but the queue must still become the + // sole handle owner so waits and final cleanup follow the shared invariant. + Owner = new CPluginThreadOwner(thread, tid); + Thread = thread; + ThreadID = tid; + Next = NULL; + Locks = 0; +} + +CThreadQueueItem::~CThreadQueueItem() +{ + delete Owner; } void CThreadQueue::ClearFinishedThreads() @@ -55,14 +121,12 @@ void CThreadQueue::ClearFinishedThreads() CThreadQueueItem* act = Head; while (act != NULL) { - DWORD ec; - if (act->Locks == 0 && (!GetExitCodeThread(act->Thread, &ec) || ec != STILL_ACTIVE)) + if (act->Locks == 0 && act->Owner->WaitForCompletion(0) != WAIT_TIMEOUT) { // tento thread neni zamceny + uz skoncil, vyhodime ho ze seznamu if (last != NULL) last->Next = act->Next; else Head = act->Next; - CloseHandle(act->Thread); delete act; act = (last != NULL ? last->Next : Head); } @@ -91,7 +155,7 @@ BOOL CThreadQueue::Add(CThreadQueueItem* item) BOOL CThreadQueue::FindAndLockItem(HANDLE thread) { - CS.Enter(); + CScopedQueueLock lock(CS); CThreadQueueItem* act = Head; // zkusime najit otevreny handle threadu while (act != NULL) @@ -104,14 +168,12 @@ BOOL CThreadQueue::FindAndLockItem(HANDLE thread) act = act->Next; } - CS.Leave(); - return act != NULL; // NULL = nenalezeno } void CThreadQueue::UnlockItem(HANDLE thread, BOOL deleteIfUnlocked) { - CS.Enter(); + CScopedQueueLock lock(CS); CThreadQueueItem* last = NULL; CThreadQueueItem* act = Head; // zkusime najit otevreny handle threadu @@ -134,7 +196,6 @@ void CThreadQueue::UnlockItem(HANDLE thread, BOOL deleteIfUnlocked) last->Next = act->Next; else Head = act->Next; - CloseHandle(act->Thread); delete act; } } @@ -142,7 +203,6 @@ void CThreadQueue::UnlockItem(HANDLE thread, BOOL deleteIfUnlocked) else TRACE_E("CThreadQueue::UnlockItem(): unable to find thread!"); // to nebyl zamknuty, ze je smazany? - CS.Leave(); } BOOL CThreadQueue::WaitForExit(HANDLE thread, int milliseconds) @@ -153,7 +213,16 @@ BOOL CThreadQueue::WaitForExit(HANDLE thread, int milliseconds) { if (FindAndLockItem(thread)) // thread handle found and locked - we can wait for it, then delete it { - ret = WaitForSingleObject(thread, milliseconds) != WAIT_TIMEOUT; + CPluginThreadOwner* owner = NULL; + { + CScopedQueueLock lock(CS); + CThreadQueueItem* item = Head; + while (item != NULL && item->Thread != thread) + item = item->Next; + if (item != NULL) + owner = item->Owner; + } + ret = owner != NULL && owner->WaitForCompletion(milliseconds) != WAIT_TIMEOUT; UnlockItem(thread, ret); } @@ -166,12 +235,25 @@ BOOL CThreadQueue::WaitForExit(HANDLE thread, int milliseconds) void CThreadQueue::KillThread(HANDLE thread, DWORD exitCode) { CALL_STACK_MESSAGE2("CThreadQueue::KillThread(, %d)", exitCode); + (void)exitCode; // Compatibility parameter: force no longer permits unsafe thread termination. if (thread != NULL) { - if (FindAndLockItem(thread)) // thread handle found and locked - we can terminate it, then delete it + if (FindAndLockItem(thread)) // thread handle found and locked - request stop before deletion { - TerminateThread(thread, exitCode); - WaitForSingleObject(thread, INFINITE); // pockame az thread skutecne skonci, nekdy mu to dost trva + CPluginThreadOwner* owner = NULL; + { + CScopedQueueLock lock(CS); + CThreadQueueItem* item = Head; + while (item != NULL && item->Thread != thread) + item = item->Next; + if (item != NULL) + owner = item->Owner; + } + if (owner != NULL) + { + owner->RequestStop(); + CPluginThreadShutdownDeadline(QueueName).WaitForSafeJoin(thread); + } UnlockItem(thread, TRUE); } @@ -183,179 +265,166 @@ void CThreadQueue::KillThread(HANDLE thread, DWORD exitCode) BOOL CThreadQueue::KillAll(BOOL force, int waitTime, int forceWaitTime, DWORD exitCode) { CALL_STACK_MESSAGE5("CThreadQueue::KillAll(%d, %d, %d, %d)", force, waitTime, forceWaitTime, exitCode); - DWORD ti = GetTickCount(); - DWORD w = force ? forceWaitTime : waitTime; + (void)exitCode; // Compatibility parameter: cooperative joins replace forced termination. + const DWORD initialWait = force ? forceWaitTime : waitTime; + const ULONGLONG deadline = initialWait == INFINITE ? 0 : GetTickCount64() + initialWait; - CS.Enter(); - - // kill all threads that do not intend to finish themselves - CThreadQueueItem* prevItem = NULL; - CThreadQueueItem* item = Head; - while (item != NULL) + for (;;) { - BOOL leaveCS = FALSE; - DWORD ec; - if (GetExitCodeThread(item->Thread, &ec) && ec == STILL_ACTIVE) - { // thread jeste nejspis bezi - DWORD t = GetTickCount() - ti; - if (w == INFINITE || t < w) // mame jeste cekat - { - // release queue for other threads (so they can e.g. wait for thread termination from queue and then terminate themselves) - CS.Leave(); - - if (w == INFINITE || 50 < w - t) - Sleep(50); - else - { - Sleep(w - t); - ti -= w; // next time we exclude the wait test - } - - CS.Enter(); - item = Head; - prevItem = NULL; - continue; // zacneme pekne od zacatku (podminka cyklu se otestuje) - } - if (force) // zabijeme ho - { - TRACE_E("Thread has not ended itself, we must terminate it (" << QueueName << " queue)."); - TerminateThread(item->Thread, exitCode); - WaitForSingleObject(item->Thread, INFINITE); // pockame az thread skutecne skonci, nekdy mu to dost trva - // if any thread is waiting for termination of just killed thread, we release for it for a moment - // the queue, otherwise it will remain stuck in UnlockItem() - leaveCS = item->Locks > 0; - } - else // without 'force', only report that something is still running - { - TRACE_I("KillAll(): At least one thread is still running in " << QueueName << " queue."); - ClearFinishedThreads(); // just for clarity when debugging - CS.Leave(); - return FALSE; - } - } - CThreadQueueItem* delItem = item; - item = item->Next; - if (delItem->Locks == 0) // handle can be closed, item can be deleted + HANDLE thread = NULL; + CPluginThreadOwner* owner = NULL; { - if (Head == delItem) - Head = item; - else - prevItem->Next = item; - CloseHandle(delItem->Thread); - delete delItem; + CScopedQueueLock lock(CS); + ClearFinishedThreads(); + if (Head == NULL) + return TRUE; + + CThreadQueueItem* item = Head; + while (item != NULL && item->Owner->WaitForCompletion(0) != WAIT_TIMEOUT) + item = item->Next; + if (item == NULL) + continue; + + item->Locks++; + thread = item->Thread; + owner = item->Owner; } - else - prevItem = delItem; // handle must be left alone, so the item too - if (leaveCS) + const ULONGLONG now = GetTickCount64(); + const DWORD remaining = initialWait == INFINITE ? INFINITE : + (now >= deadline ? 0 : (DWORD)(deadline - now)); + if (remaining != 0 && owner->WaitForCompletion(remaining) != WAIT_TIMEOUT) { - // release queue for other threads (so they can e.g. wait for thread termination from queue and then terminate themselves) - CS.Leave(); - - Sleep(50); // moment to take over the queue and possibly let the thread finish (before killing it like all the others) + UnlockItem(thread, TRUE); + continue; + } - CS.Enter(); - item = Head; - prevItem = NULL; - continue; // start from the beginning (loop condition will be tested) + if (!force) + { + UnlockItem(thread, FALSE); + TRACE_I("KillAll(): At least one thread is still running in " << QueueName << " queue."); + return FALSE; } - } - CS.Leave(); - return TRUE; + // Force now means request cancellation and log its bounded phases; it + // never permits destruction of a plug-in callback while it is running. + owner->RequestStop(); + CPluginThreadShutdownDeadline(QueueName).WaitForSafeJoin(thread); + UnlockItem(thread, TRUE); + } } -struct CThreadBaseData +struct CThreadQueue::CThreadQueueLaunchData { - unsigned(WINAPI* Body)(void*); - void* Param; - HANDLE Continue; + unsigned(WINAPI* LegacyBody)(void*); + CThreadQueueStopBody StopBody; + void* Parameter; + HANDLE Accepted; }; -DWORD WINAPI -CThreadQueue::ThreadBase(void* param) +DWORD WINAPI CThreadQueue::ThreadBase(void* param, HANDLE stopEvent) { - CThreadBaseData* d = (CThreadBaseData*)param; - - // back up data on the stack ('d' stops being valid after 'Continue') - unsigned(WINAPI * threadBody)(void*) = d->Body; - void* threadParam = d->Param; - - SetEvent(d->Continue); // let the main thread continue - d = NULL; + CThreadQueueLaunchData* data = (CThreadQueueLaunchData*)param; + unsigned(WINAPI * legacyBody)(void*) = data->LegacyBody; + CThreadQueueStopBody stopBody = data->StopBody; + void* threadParam = data->Parameter; + SetEvent(data->Accepted); // The caller may release stack-backed arguments after this copy. + delete data; + + if (stopBody != NULL) + return stopBody(threadParam, stopEvent); + return legacyBody != NULL ? SalamanderDebug->CallWithCallStack(legacyBody, threadParam) : ERROR_INVALID_PARAMETER; +} - // start our thread - return SalamanderDebug->CallWithCallStack(threadBody, threadParam); +void WINAPI CThreadQueue::NameThread(const char* name) +{ + SalamanderDebug->SetThreadNameInVCAndTrace(name); } -HANDLE -CThreadQueue::StartThread(unsigned(WINAPI* body)(void*), void* param, unsigned stack_size, - HANDLE* threadHandle, DWORD* threadID) +HANDLE CThreadQueue::StartThreadInternal(unsigned(WINAPI* legacyBody)(void*), CThreadQueueStopBody stopBody, + void* param, unsigned stackSize, HANDLE* threadHandle, DWORD* threadID) { - CALL_STACK_MESSAGE2("CThreadQueue::StartThread(, , %d, ,)", stack_size); if (threadHandle != NULL) *threadHandle = NULL; if (threadID != NULL) *threadID = 0; - if (Continue == NULL) + + // Hold the queue lock until the worker has copied its launch record. This + // retains the legacy stack-argument guarantee without a shared hand-off event. + CScopedQueueLock lock(CS); + CPluginThreadOwner* owner = new CPluginThreadOwner; + CThreadQueueLaunchData* data = new CThreadQueueLaunchData; + if (owner == NULL || data == NULL) { - TRACE_E("Unable to start thread, because Continue event was not created."); + delete data; + delete owner; + SetLastError(ERROR_NOT_ENOUGH_MEMORY); return NULL; } - CS.Enter(); - - CThreadBaseData data; - data.Body = body; - data.Param = param; - data.Continue = Continue; - - // start the thread; do not use _beginthreadex(), because since VC2015 it has a side effect - // of loading this module (plugin) again, which is released again on normal termination, - // but if TerminateThread() is used, the module stays loaded until the Salamander process - // terminates; only then are global object destructors run, which can lead to unexpected - // crashes because all plugin interfaces have already been released (for example - // SalamanderDebug) - DWORD tid; - HANDLE thread = CreateThread(NULL, stack_size, CThreadQueue::ThreadBase, &data, CREATE_SUSPENDED, &tid); - if (thread == NULL) + data->LegacyBody = legacyBody; + data->StopBody = stopBody; + data->Parameter = param; + data->Accepted = CreateEvent(NULL, TRUE, FALSE, NULL); + HANDLE accepted = data->Accepted; + if (accepted == NULL || !owner->Start(ThreadBase, data, QueueName, NameThread, stackSize)) { - TRACE_E("Unable to start thread."); - - CS.Leave(); + if (accepted != NULL) + CloseHandle(accepted); + delete data; + delete owner; + return NULL; + } + CThreadQueueItem* item = new CThreadQueueItem(owner, owner->GetThreadID()); + if (item == NULL) + { + TRACE_E("Unable to add thread to the " << QueueName << " queue."); + owner->StopAndJoin(INFINITE); // Do not orphan a started callback when queue allocation fails. + CloseHandle(accepted); return NULL; } - else + if (!Add(item)) { - // add thread to this plugin's thread queue - if (!Add(new CThreadQueueItem(thread, tid))) - { - TRACE_E("Unable to add thread to the queue."); - TerminateThread(thread, 666); // it is suspended, so it will not be in any critical section, etc. - WaitForSingleObject(thread, INFINITE); // wait until the thread really ends; sometimes it takes quite a while - CloseHandle(thread); - - CS.Leave(); - - return NULL; - } - - // write while thread is not running (guarantees it has not finished and its object is not deallocated) - if (threadHandle != NULL) - *threadHandle = thread; - if (threadID != NULL) - *threadID = tid; + TRACE_E("Unable to add thread to the " << QueueName << " queue."); + delete item; // Its owner requests stop and safely joins before releasing launch state. + CloseHandle(accepted); + return NULL; + } - SalamanderDebug->TraceAttachThread(thread, tid); - ResumeThread(thread); + HANDLE thread = item->Thread; + if (WaitForSingleObject(accepted, 10000) == WAIT_TIMEOUT) + { + // Never return while a legacy callback could still dereference the caller's stack data. + TRACE_E("Timed out transferring startup data to " << QueueName << " queue worker."); + owner->RequestStop(); + CPluginThreadShutdownDeadline(QueueName).WaitForSafeJoin(thread); + CloseHandle(accepted); + ClearFinishedThreads(); + return NULL; + } + CloseHandle(accepted); - WaitForSingleObject(Continue, INFINITE); // wait for data handoff to CThreadQueue::ThreadBase + SalamanderDebug->TraceAttachThread(thread, item->ThreadID); + if (threadHandle != NULL) + *threadHandle = thread; + if (threadID != NULL) + *threadID = item->ThreadID; + return thread; +} - CS.Leave(); +HANDLE CThreadQueue::StartThread(unsigned(WINAPI* body)(void*), void* param, unsigned stackSize, + HANDLE* threadHandle, DWORD* threadID) +{ + CALL_STACK_MESSAGE2("CThreadQueue::StartThread(, , %d, ,)", stackSize); + return StartThreadInternal(body, NULL, param, stackSize, threadHandle, threadID); +} - return thread; - } +HANDLE CThreadQueue::StartThread(CThreadQueueStopBody body, void* param, unsigned stackSize, + HANDLE* threadHandle, DWORD* threadID) +{ + CALL_STACK_MESSAGE2("CThreadQueue::StartThread(stop-aware, , %d, ,)", stackSize); + return StartThreadInternal(NULL, body, param, stackSize, threadHandle, threadID); } // diff --git a/src/plugins/shared/auxtools.h b/src/plugins/shared/auxtools.h index 2722c22d..f70b3602 100644 --- a/src/plugins/shared/auxtools.h +++ b/src/plugins/shared/auxtools.h @@ -11,6 +11,10 @@ #pragma once +#include "plugin_thread_owner.h" + +typedef DWORD(WINAPI* CThreadQueueStopBody)(void* parameter, HANDLE stopEvent); + // // **************************************************************************** // CThreadQueue @@ -19,17 +23,16 @@ struct CThreadQueueItem { HANDLE Thread; + CPluginThreadOwner* Owner; DWORD ThreadID; // only for debugging purposes (finding thread in thread list in debugger) int Locks; // number of locks; if > 0, 'Thread' must not be closed CThreadQueueItem* Next; - CThreadQueueItem(HANDLE thread, DWORD tid) - { - Thread = thread; - ThreadID = tid; - Next = NULL; - Locks = 0; - } + CThreadQueueItem(CPluginThreadOwner* owner, DWORD tid); + // Compatibility registration transfers a legacy worker handle into the + // same queue ownership model without requiring a second thread wrapper. + CThreadQueueItem(HANDLE thread, DWORD tid); + ~CThreadQueueItem(); }; class CThreadQueue @@ -37,8 +40,6 @@ class CThreadQueue protected: const char* QueueName; // queue name (only for debugging purposes) CThreadQueueItem* Head; - HANDLE Continue; // we must wait for data transfer to started thread - struct CCS // access from multiple threads -> synchronization required { CRITICAL_SECTION cs; @@ -50,6 +51,17 @@ class CThreadQueue void Leave() { LeaveCriticalSection(&cs); } } CS; + // Queue mutations must release the lock on every timeout/error path so a + // cooperative shutdown never strands later waiters behind stale ownership. + class CScopedQueueLock + { + public: + CScopedQueueLock(CCS& section) : Section(section) { Section.Enter(); } + ~CScopedQueueLock() { Section.Leave(); } + private: + CCS& Section; + }; + public: CThreadQueue(const char* queueName /* e.g. "DemoPlug Viewers" */); ~CThreadQueue(); @@ -60,10 +72,8 @@ class CThreadQueue // (if not NULL), use returned thread handle only for NULL tests and for calling // CThreadQueue methods: WaitForExit() and KillThread(); closing the thread handle is handled // by this queue object - // WARNING: -thread may start with delay after return from StartThread() - // (if 'param' is a pointer to a structure stored on the stack, it is necessary to - // synchronize data transfer from 'param' - main thread must wait - // for the new thread to take over the data) + // WARNING: -the legacy callback cannot observe cancellation; migrate it to + // the stop-aware overload before relying on prompt shutdown // -returned thread handle may already be closed if thread finishes before // return from StartThread() and StartThread() is called from another thread or // KillAll() @@ -71,21 +81,24 @@ class CThreadQueue HANDLE StartThread(unsigned(WINAPI* body)(void*), void* param, unsigned stack_size = 0, HANDLE* threadHandle = NULL, DWORD* threadID = NULL); + // New callbacks receive a manual-reset stop event owned until their safe + // join, allowing plug-ins to migrate without changing the queue surface. + HANDLE StartThread(CThreadQueueStopBody body, void* param, unsigned stack_size = 0, + HANDLE* threadHandle = NULL, DWORD* threadID = NULL); + // waits for thread termination from this queue; 'thread' is thread handle, which may already // be closed (this object closes it when calling StartThread and KillAll); if // waits for thread termination, removes the thread from the queue, and closes its handle BOOL WaitForExit(HANDLE thread, int milliseconds = INFINITE); - // kills a thread from this queue (via TerminateThread()); 'thread' is the thread handle, - // which may already be closed (this object closes it when calling StartThread and KillAll); - // if thread is found, kills it, removes from queue and closes its handle (thread object - // is not deallocated, because its state is unknown, possibly inconsistent) + // Requests cooperative stop and safely joins this queue entry. Legacy + // callbacks may take longer because they do not yet consume the stop event. void KillThread(HANDLE thread, DWORD exitCode = 666); // verifies that all threads have finished; if 'force' is TRUE and some thread is still running, - // waits 'forceWaitTime' (in ms) for all threads to finish, then kills running threads - // (their objects are not deallocated, because their state is unknown, possibly inconsistent); - // returns TRUE, if all threads are terminated, with 'force' TRUE always returns TRUE; + // waits 'forceWaitTime' (in ms) for all threads to finish, then requests + // cooperative stop and safely joins running threads; returns TRUE with + // force TRUE only after every callback has exited; // if 'force' is FALSE and some thread is still running, waits 'waitTime' (in ms) for termination // of all threads, if something is still running then, returns FALSE; time INFINITE = unlimited // waiting @@ -97,7 +110,11 @@ class CThreadQueue BOOL FindAndLockItem(HANDLE thread); // finds item for 'thread' in queue and locks it void UnlockItem(HANDLE thread, BOOL deleteIfUnlocked); // unlocks item for 'thread' in queue, optionally deletes it void ClearFinishedThreads(); // removes already finished threads from the queue - static DWORD WINAPI ThreadBase(void* param); // universal thread body + struct CThreadQueueLaunchData; + static DWORD WINAPI ThreadBase(void* param, HANDLE stopEvent); + static void WINAPI NameThread(const char* name); + HANDLE StartThreadInternal(unsigned(WINAPI* legacyBody)(void*), CThreadQueueStopBody stopBody, + void* param, unsigned stackSize, HANDLE* threadHandle, DWORD* threadID); }; // diff --git a/src/plugins/shared/plugin_thread_owner.h b/src/plugins/shared/plugin_thread_owner.h new file mode 100644 index 00000000..5fa10125 --- /dev/null +++ b/src/plugins/shared/plugin_thread_owner.h @@ -0,0 +1,171 @@ +// SPDX-FileCopyrightText: 2026 Taskscape Ltd +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +typedef DWORD(WINAPI* CPluginThreadOwnerEntry)(void* parameter, HANDLE stopEvent); +typedef void(WINAPI* CPluginThreadOwnerNameThread)(const char* name); + +// Plug-ins cannot link the host-only CThreadOwner implementation, so this +// adapter keeps the same ownership invariant at the shared SDK boundary. +class CPluginThreadOwner +{ +public: + CPluginThreadOwner() + : Thread(NULL), StopEvent(NULL), CompletionEvent(NULL), ThreadID(0) + { + } + + // Legacy plug-ins may create their worker before registering it; adopting + // the handle preserves single ownership while their existing abort path remains external. + CPluginThreadOwner(HANDLE thread, DWORD threadID) + : Thread(thread), StopEvent(NULL), CompletionEvent(NULL), ThreadID(threadID) + { + } + + ~CPluginThreadOwner() + { + StopAndJoin(INFINITE); + } + +private: + CPluginThreadOwner(const CPluginThreadOwner&); + CPluginThreadOwner& operator=(const CPluginThreadOwner&); + +public: + BOOL Start(CPluginThreadOwnerEntry entry, void* parameter, const char* name, + CPluginThreadOwnerNameThread nameThread, unsigned stackSize) + { + if (entry == NULL || Thread != NULL) + { + SetLastError(entry == NULL ? ERROR_INVALID_PARAMETER : ERROR_BUSY); + return FALSE; + } + + StopEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + CompletionEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (StopEvent == NULL || CompletionEvent == NULL) + { + CloseOwnedHandles(); + return FALSE; + } + + CLaunch* launch = new CLaunch; + if (launch == NULL) + { + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + CloseOwnedHandles(); + return FALSE; + } + + launch->Entry = entry; + launch->Parameter = parameter; + launch->StopEvent = StopEvent; + launch->CompletionEvent = CompletionEvent; + launch->NameThread = nameThread; + lstrcpynA(launch->Name, name != NULL ? name : "OpenSalamanderPluginWorker", sizeof(launch->Name)); + + unsigned threadID = 0; + Thread = (HANDLE)_beginthreadex(NULL, stackSize, ThreadMain, launch, 0, &threadID); + if (Thread == NULL) + { + delete launch; + CloseOwnedHandles(); + return FALSE; + } + ThreadID = threadID; + return TRUE; + } + + HANDLE GetThreadHandle() const { return Thread; } + DWORD GetThreadID() const { return ThreadID; } + HANDLE GetStopEvent() const { return StopEvent; } + DWORD WaitForCompletion(DWORD timeout) const + { + // Adopted legacy workers have no private completion event, so their + // owned thread handle is the authoritative completion signal. + if (CompletionEvent != NULL) + return WaitForSingleObject(CompletionEvent, timeout); + return Thread != NULL ? WaitForSingleObject(Thread, timeout) : WAIT_OBJECT_0; + } + + void RequestStop() + { + if (StopEvent != NULL) + SetEvent(StopEvent); + } + + // A timeout is diagnostic only. The owner remains alive until its callback + // exits, preventing a plug-in unload from freeing callback state in use. + DWORD StopAndJoin(DWORD timeout) + { + if (Thread == NULL) + return WAIT_OBJECT_0; + + RequestStop(); + DWORD completion = WaitForCompletion(timeout); + WaitForSingleObject(Thread, INFINITE); + CloseOwnedHandles(); + return completion; + } + +private: + struct CLaunch + { + CPluginThreadOwnerEntry Entry; + void* Parameter; + HANDLE StopEvent; + HANDLE CompletionEvent; + CPluginThreadOwnerNameThread NameThread; + char Name[64]; + }; + + static unsigned __stdcall ThreadMain(void* parameter) + { + CLaunch* launch = (CLaunch*)parameter; + DWORD result = ERROR_UNHANDLED_EXCEPTION; + if (launch->NameThread != NULL) + launch->NameThread(launch->Name); + + try + { + result = launch->Entry(launch->Parameter, launch->StopEvent); + } + catch (...) + { + result = ERROR_UNHANDLED_EXCEPTION; + } + + SetEvent(launch->CompletionEvent); + delete launch; + return result; + } + + void CloseOwnedHandles() + { + if (Thread != NULL) + { + CloseHandle(Thread); + Thread = NULL; + ThreadID = 0; + } + if (StopEvent != NULL) + { + CloseHandle(StopEvent); + StopEvent = NULL; + } + if (CompletionEvent != NULL) + { + CloseHandle(CompletionEvent); + CompletionEvent = NULL; + } + } + +private: + HANDLE Thread; + HANDLE StopEvent; + HANDLE CompletionEvent; + DWORD ThreadID; +}; diff --git a/src/plugins/shared/spl_com.h b/src/plugins/shared/spl_com.h index 66ff2f8e..0945a7a8 100644 --- a/src/plugins/shared/spl_com.h +++ b/src/plugins/shared/spl_com.h @@ -195,6 +195,18 @@ struct CQuadWord } }; +// Preserve both the 64-bit value and the captured failure code whenever a plug-in +// obtains an external file size; DWORD sentinels cannot represent that contract. +struct CFileOffsetResult +{ + CQuadWord Value; + DWORD Error; + BOOL Succeeded; + + CFileOffsetResult(const CQuadWord& value) : Value(value), Error(NO_ERROR), Succeeded(TRUE) {} + CFileOffsetResult(DWORD error) : Error(error), Succeeded(FALSE) { Value.Set(0, 0); } +}; + #define QW_MAX CQuadWord(0xFFFFFFFF, 0xFFFFFFFF) #define ICONOVERLAYINDEX_NOTUSED 15 // value for CFileData::IconOverlayIndex in case icon has no overlay diff --git a/src/plugins/shared/spl_gen.h b/src/plugins/shared/spl_gen.h index 9a1e0460..3e1a0cbe 100644 --- a/src/plugins/shared/spl_gen.h +++ b/src/plugins/shared/spl_gen.h @@ -3451,6 +3451,17 @@ class CSalamanderGeneralAbstract virtual void WINAPI CloseAllOwnedEnabledDialogs(HWND parent, DWORD tid = 0) = 0; }; +// Adapt the stable plug-in ABI's success/error out parameters to the shared +// result type so plug-ins do not revive raw GetFileSize sentinel handling. +inline CFileOffsetResult SalGetPluginFileSizeEx(CSalamanderGeneralAbstract* salamander, HANDLE file) +{ + CQuadWord size; + DWORD error; + if (!salamander->SalGetFileSize(file, size, error)) + return CFileOffsetResult(error); + return CFileOffsetResult(size); +} + #ifdef _MSC_VER #pragma pack(pop, enter_include_spl_gen) #endif // _MSC_VER diff --git a/src/plugins/shared/spl_vers.h b/src/plugins/shared/spl_vers.h index 4225cc75..e9dc8310 100644 --- a/src/plugins/shared/spl_vers.h +++ b/src/plugins/shared/spl_vers.h @@ -21,11 +21,12 @@ #define VERSINFO_xstr(s) VERSINFO_str(s) #define VERSINFO_str(s) #s -#define VERSINFO_SALAMANDER_MAJOR 5 +// The release-managed major stays separate from the automatically injected build date. +#define VERSINFO_SALAMANDER_MAJOR 6 #define VERSINFO_SALAMANDER_MINORA 0 #define VERSINFO_SALAMANDER_MINORB 0 -// Version format: MAJOR.BUILDDATE (e.g. "5.20260620") +// Version format: MAJOR.BUILDDATE (e.g. "6.20260620") // VERSINFO_SALAMANDER_BUILDDATE is the build date in YYYYMMDD format. // It is normally injected by the build: src\Directory.Build.props computes the current // date and passes VERSINFO_SALAMANDER_BUILDDATE_DYNAMIC=YYYYMMDD to both the C/C++ and diff --git a/src/plugins/shared/sqlite/sqlite3.h b/src/plugins/shared/sqlite/sqlite3.h index 3afb6aad..c91b81c0 100644 --- a/src/plugins/shared/sqlite/sqlite3.h +++ b/src/plugins/shared/sqlite/sqlite3.h @@ -1,6 +1,3 @@ -// SPDX-FileCopyrightText: 2023 Taskscape Ltd -// SPDX-License-Identifier: GPL-2.0-or-later - /* ** 2001-09-15 ** @@ -35,39 +32,62 @@ */ #ifndef SQLITE3_H #define SQLITE3_H -#include /* Needed for the definition of va_list */ +#include /* Needed for the definition of va_list */ /* ** Make sure we can call this stuff from C++. */ #ifdef __cplusplus -extern "C" -{ +extern "C" { #endif + /* -** Provide the ability to override linkage features of the interface. +** Facilitate override of interface linkage and calling conventions. +** Be aware that these macros may not be used within this particular +** translation of the amalgamation and its associated header file. +** +** The SQLITE_EXTERN and SQLITE_API macros are used to instruct the +** compiler that the target identifier should have external linkage. +** +** The SQLITE_CDECL macro is used to set the calling convention for +** public functions that accept a variable number of arguments. +** +** The SQLITE_APICALL macro is used to set the calling convention for +** public functions that accept a fixed number of arguments. +** +** The SQLITE_STDCALL macro is no longer used and is now deprecated. +** +** The SQLITE_CALLBACK macro is used to set the calling convention for +** function pointers. +** +** The SQLITE_SYSAPI macro is used to set the calling convention for +** functions provided by the operating system. +** +** Currently, the SQLITE_CDECL, SQLITE_APICALL, SQLITE_CALLBACK, and +** SQLITE_SYSAPI macros are used only when building for environments +** that require non-default calling conventions. */ #ifndef SQLITE_EXTERN -#define SQLITE_EXTERN extern +# define SQLITE_EXTERN extern #endif #ifndef SQLITE_API -#define SQLITE_API +# define SQLITE_API #endif #ifndef SQLITE_CDECL -#define SQLITE_CDECL +# define SQLITE_CDECL #endif #ifndef SQLITE_APICALL -#define SQLITE_APICALL +# define SQLITE_APICALL #endif #ifndef SQLITE_STDCALL -#define SQLITE_STDCALL SQLITE_APICALL +# define SQLITE_STDCALL SQLITE_APICALL #endif #ifndef SQLITE_CALLBACK -#define SQLITE_CALLBACK +# define SQLITE_CALLBACK #endif #ifndef SQLITE_SYSAPI -#define SQLITE_SYSAPI +# define SQLITE_SYSAPI #endif /* @@ -90,10 +110,10 @@ extern "C" ** Ensure these symbols were not defined by some previous header file. */ #ifdef SQLITE_VERSION -#undef SQLITE_VERSION +# undef SQLITE_VERSION #endif #ifdef SQLITE_VERSION_NUMBER -#undef SQLITE_VERSION_NUMBER +# undef SQLITE_VERSION_NUMBER #endif /* @@ -111,9 +131,9 @@ extern "C" ** be held constant and Z will be incremented or else Y will be incremented ** and Z will be reset to zero. ** -** Since [version 3.6.18] ([dateof:3.6.18]), +** Since [version 3.6.18] ([dateof:3.6.18]), ** SQLite source code has been stored in the -** Fossil configuration management +** Fossil configuration management ** system. ^The SQLITE_SOURCE_ID macro evaluates to ** a string which identifies a particular check-in of SQLite ** within its configuration management system. ^The SQLITE_SOURCE_ID @@ -126,11 +146,14 @@ extern "C" ** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite_version()] and [sqlite_source_id()]. */ -#define SQLITE_VERSION "3.28.0" -#define SQLITE_VERSION_NUMBER 3028000 -#define SQLITE_SOURCE_ID "2019-04-16 19:49:53 884b4b7e502b4e991677b53971277adfaf0a04a284f8e483e2553d0f83156b50" +#define SQLITE_VERSION "3.53.4" +#define SQLITE_VERSION_NUMBER 3053004 +#define SQLITE_SOURCE_ID "2026-07-24 19:02:57 bf7c7f30031888f4e796e429ab3978879485813aaca6f641c7b33e4e09459bcc" +#define SQLITE_SCM_BRANCH "branch-3.53" +#define SQLITE_SCM_TAGS "release version-3.53.4" +#define SQLITE_SCM_DATETIME "2026-07-24T19:02:57.525Z" - /* +/* ** CAPI3REF: Run-Time Library Version Numbers ** KEYWORDS: sqlite3_version sqlite3_sourceid ** @@ -148,56 +171,56 @@ extern "C" ** assert( strcmp(sqlite3_libversion(),SQLITE_VERSION)==0 ); ** )^ ** -** ^The sqlite3_version[] string constant contains the text of [SQLITE_VERSION] -** macro. ^The sqlite3_libversion() function returns a pointer to the -** to the sqlite3_version[] string constant. The sqlite3_libversion() +** ^The sqlite3_version[] string constant contains the text of the +** [SQLITE_VERSION] macro. ^The sqlite3_libversion() function returns a +** pointer to the sqlite3_version[] string constant. The sqlite3_libversion() ** function is provided for use in DLLs since DLL users usually do not have ** direct access to string constants within the DLL. ^The ** sqlite3_libversion_number() function returns an integer equal to -** [SQLITE_VERSION_NUMBER]. ^(The sqlite3_sourceid() function returns -** a pointer to a string constant whose value is the same as the +** [SQLITE_VERSION_NUMBER]. ^(The sqlite3_sourceid() function returns +** a pointer to a string constant whose value is the same as the ** [SQLITE_SOURCE_ID] C preprocessor macro. Except if SQLite is built ** using an edited copy of [the amalgamation], then the last four characters ** of the hash might be different from [SQLITE_SOURCE_ID].)^ ** ** See also: [sqlite_version()] and [sqlite_source_id()]. */ - SQLITE_API SQLITE_EXTERN const char sqlite3_version[]; - SQLITE_API const char* sqlite3_libversion(void); - SQLITE_API const char* sqlite3_sourceid(void); - SQLITE_API int sqlite3_libversion_number(void); +SQLITE_API SQLITE_EXTERN const char sqlite3_version[]; +SQLITE_API const char *sqlite3_libversion(void); +SQLITE_API const char *sqlite3_sourceid(void); +SQLITE_API int sqlite3_libversion_number(void); /* ** CAPI3REF: Run-Time Library Compilation Options Diagnostics ** -** ^The sqlite3_compileoption_used() function returns 0 or 1 -** indicating whether the specified option was defined at -** compile time. ^The SQLITE_ prefix may be omitted from the -** option name passed to sqlite3_compileoption_used(). +** ^The sqlite3_compileoption_used() function returns 0 or 1 +** indicating whether the specified option was defined at +** compile time. ^The SQLITE_ prefix may be omitted from the +** option name passed to sqlite3_compileoption_used(). ** ** ^The sqlite3_compileoption_get() function allows iterating ** over the list of options that were defined at compile time by ** returning the N-th compile time option string. ^If N is out of range, -** sqlite3_compileoption_get() returns a NULL pointer. ^The SQLITE_ -** prefix is omitted from any strings returned by +** sqlite3_compileoption_get() returns a NULL pointer. ^The SQLITE_ +** prefix is omitted from any strings returned by ** sqlite3_compileoption_get(). ** ** ^Support for the diagnostic functions sqlite3_compileoption_used() -** and sqlite3_compileoption_get() may be omitted by specifying the +** and sqlite3_compileoption_get() may be omitted by specifying the ** [SQLITE_OMIT_COMPILEOPTION_DIAGS] option at compile time. ** ** See also: SQL functions [sqlite_compileoption_used()] and ** [sqlite_compileoption_get()] and the [compile_options pragma]. */ #ifndef SQLITE_OMIT_COMPILEOPTION_DIAGS - SQLITE_API int sqlite3_compileoption_used(const char* zOptName); - SQLITE_API const char* sqlite3_compileoption_get(int N); +SQLITE_API int sqlite3_compileoption_used(const char *zOptName); +SQLITE_API const char *sqlite3_compileoption_get(int N); #else -#define sqlite3_compileoption_used(X) 0 -#define sqlite3_compileoption_get(X) ((void*)0) +# define sqlite3_compileoption_used(X) 0 +# define sqlite3_compileoption_get(X) ((void*)0) #endif - /* +/* ** CAPI3REF: Test To See If The Library Is Threadsafe ** ** ^The sqlite3_threadsafe() function returns zero if and only if @@ -207,7 +230,7 @@ extern "C" ** SQLite can be compiled with or without mutexes. When ** the [SQLITE_THREADSAFE] C preprocessor macro is 1 or 2, mutexes ** are enabled and SQLite is threadsafe. When the -** [SQLITE_THREADSAFE] macro is 0, +** [SQLITE_THREADSAFE] macro is 0, ** the mutexes are omitted. Without the mutexes, it is not safe ** to use SQLite concurrently from more than one thread. ** @@ -233,9 +256,9 @@ extern "C" ** ** See the [threading mode] documentation for additional information. */ - SQLITE_API int sqlite3_threadsafe(void); +SQLITE_API int sqlite3_threadsafe(void); - /* +/* ** CAPI3REF: Database Connection Handle ** KEYWORDS: {database connection} {database connections} ** @@ -249,7 +272,7 @@ extern "C" ** [sqlite3_busy_timeout()] to name but three) that are methods on an ** sqlite3 object. */ - typedef struct sqlite3 sqlite3; +typedef struct sqlite3 sqlite3; /* ** CAPI3REF: 64-Bit Integer Types @@ -264,35 +287,35 @@ extern "C" ** ** ^The sqlite3_int64 and sqlite_int64 types can store integer values ** between -9223372036854775808 and +9223372036854775807 inclusive. ^The -** sqlite3_uint64 and sqlite_uint64 types can store integer values +** sqlite3_uint64 and sqlite_uint64 types can store integer values ** between 0 and +18446744073709551615 inclusive. */ #ifdef SQLITE_INT64_TYPE - typedef SQLITE_INT64_TYPE sqlite_int64; -#ifdef SQLITE_UINT64_TYPE + typedef SQLITE_INT64_TYPE sqlite_int64; +# ifdef SQLITE_UINT64_TYPE typedef SQLITE_UINT64_TYPE sqlite_uint64; -#else +# else typedef unsigned SQLITE_INT64_TYPE sqlite_uint64; -#endif +# endif #elif defined(_MSC_VER) || defined(__BORLANDC__) -typedef __int64 sqlite_int64; -typedef unsigned __int64 sqlite_uint64; + typedef __int64 sqlite_int64; + typedef unsigned __int64 sqlite_uint64; #else -typedef long long int sqlite_int64; -typedef unsigned long long int sqlite_uint64; + typedef long long int sqlite_int64; + typedef unsigned long long int sqlite_uint64; #endif - typedef sqlite_int64 sqlite3_int64; - typedef sqlite_uint64 sqlite3_uint64; +typedef sqlite_int64 sqlite3_int64; +typedef sqlite_uint64 sqlite3_uint64; /* ** If compiling for a processor that lacks floating point support, ** substitute integer for floating-point. */ #ifdef SQLITE_OMIT_FLOATING_POINT -#define double sqlite3_int64 +# define double sqlite3_int64 #endif - /* +/* ** CAPI3REF: Closing A Database Connection ** DESTRUCTOR: sqlite3 ** @@ -302,26 +325,22 @@ typedef unsigned long long int sqlite_uint64; ** the [sqlite3] object is successfully destroyed and all associated ** resources are deallocated. ** -** ^If the database connection is associated with unfinalized prepared -** statements or unfinished sqlite3_backup objects then sqlite3_close() -** will leave the database connection open and return [SQLITE_BUSY]. -** ^If sqlite3_close_v2() is called with unfinalized prepared statements -** and/or unfinished sqlite3_backups, then the database connection becomes -** an unusable "zombie" which will automatically be deallocated when the -** last prepared statement is finalized or the last sqlite3_backup is -** finished. The sqlite3_close_v2() interface is intended for use with -** host languages that are garbage collected, and where the order in which -** destructors are called is arbitrary. -** -** Applications should [sqlite3_finalize | finalize] all [prepared statements], -** [sqlite3_blob_close | close] all [BLOB handles], and +** Ideally, applications should [sqlite3_finalize | finalize] all +** [prepared statements], [sqlite3_blob_close | close] all [BLOB handles], and ** [sqlite3_backup_finish | finish] all [sqlite3_backup] objects associated -** with the [sqlite3] object prior to attempting to close the object. ^If -** sqlite3_close_v2() is called on a [database connection] that still has -** outstanding [prepared statements], [BLOB handles], and/or -** [sqlite3_backup] objects then it returns [SQLITE_OK] and the deallocation -** of resources is deferred until all [prepared statements], [BLOB handles], -** and [sqlite3_backup] objects are also destroyed. +** with the [sqlite3] object prior to attempting to close the object. +** ^If the database connection is associated with unfinalized prepared +** statements, BLOB handlers, and/or unfinished sqlite3_backup objects then +** sqlite3_close() will leave the database connection open and return +** [SQLITE_BUSY]. ^If sqlite3_close_v2() is called with unfinalized prepared +** statements, unclosed BLOB handlers, and/or unfinished sqlite3_backups, +** it returns [SQLITE_OK] regardless, but instead of deallocating the database +** connection immediately, it marks the database connection as an unusable +** "zombie" and makes arrangements to automatically deallocate the database +** connection after all prepared statements are finalized, all BLOB handles +** are closed, and all backups have finished. The sqlite3_close_v2() interface +** is intended for use with host languages that are garbage collected, and +** where the order in which destructors are called is arbitrary. ** ** ^If an [sqlite3] object is destroyed while a transaction is open, ** the transaction is automatically rolled back. @@ -334,27 +353,27 @@ typedef unsigned long long int sqlite_uint64; ** ^Calling sqlite3_close() or sqlite3_close_v2() with a NULL pointer ** argument is a harmless no-op. */ - SQLITE_API int sqlite3_close(sqlite3*); - SQLITE_API int sqlite3_close_v2(sqlite3*); +SQLITE_API int sqlite3_close(sqlite3*); +SQLITE_API int sqlite3_close_v2(sqlite3*); - /* +/* ** The type for a callback function. ** This is legacy and deprecated. It is included for historical ** compatibility and is not documented. */ - typedef int (*sqlite3_callback)(void*, int, char**, char**); +typedef int (*sqlite3_callback)(void*,int,char**, char**); - /* +/* ** CAPI3REF: One-Step Query Execution Interface ** METHOD: sqlite3 ** ** The sqlite3_exec() interface is a convenience wrapper around ** [sqlite3_prepare_v2()], [sqlite3_step()], and [sqlite3_finalize()], ** that allows an application to run multiple statements of SQL -** without having to use a lot of C code. +** without having to use a lot of C code. ** ** ^The sqlite3_exec() interface runs zero or more UTF-8 encoded, -** semicolon-separate SQL statements passed into its 2nd argument, +** semicolon-separated SQL statements passed into its 2nd argument, ** in the context of the [database connection] passed in as its 1st ** argument. ^If the callback function of the 3rd argument to ** sqlite3_exec() is not NULL, then it is invoked for each result row @@ -387,11 +406,11 @@ typedef unsigned long long int sqlite_uint64; ** result row is NULL then the corresponding string pointer for the ** sqlite3_exec() callback is a NULL pointer. ^The 4th argument to the ** sqlite3_exec() callback is an array of pointers to strings where each -** entry represents the name of corresponding result column as obtained +** entry represents the name of a corresponding result column as obtained ** from [sqlite3_column_name()]. ** ** ^If the 2nd parameter to sqlite3_exec() is a NULL pointer, a pointer -** to an empty string, or a pointer that contains only whitespace and/or +** to an empty string, or a pointer that contains only whitespace and/or ** SQL comments, then no SQL statements are evaluated and the database ** is not changed. ** @@ -404,15 +423,17 @@ typedef unsigned long long int sqlite_uint64; ** the 1st parameter to sqlite3_exec() while sqlite3_exec() is running. **
                        4. The application must not modify the SQL statement text passed into ** the 2nd parameter of sqlite3_exec() while sqlite3_exec() is running. +**
                        5. The application must not dereference the arrays or string pointers +** passed as the 3rd and 4th callback parameters after it returns. **
                    */ - SQLITE_API int sqlite3_exec( - sqlite3*, /* An open database */ - const char* sql, /* SQL to be evaluated */ - int (*callback)(void*, int, char**, char**), /* Callback function */ - void*, /* 1st argument to callback */ - char** errmsg /* Error msg written here */ - ); +SQLITE_API int sqlite3_exec( + sqlite3*, /* An open database */ + const char *sql, /* SQL to be evaluated */ + int (*callback)(void*,int,char**,char**), /* Callback function */ + void *, /* 1st argument to callback */ + char **errmsg /* Error msg written here */ +); /* ** CAPI3REF: Result Codes @@ -425,38 +446,38 @@ typedef unsigned long long int sqlite_uint64; ** ** See also: [extended result code definitions] */ -#define SQLITE_OK 0 /* Successful result */ +#define SQLITE_OK 0 /* Successful result */ /* beginning-of-error-codes */ -#define SQLITE_ERROR 1 /* Generic error */ -#define SQLITE_INTERNAL 2 /* Internal logic error in SQLite */ -#define SQLITE_PERM 3 /* Access permission denied */ -#define SQLITE_ABORT 4 /* Callback routine requested an abort */ -#define SQLITE_BUSY 5 /* The database file is locked */ -#define SQLITE_LOCKED 6 /* A table in the database is locked */ -#define SQLITE_NOMEM 7 /* A malloc() failed */ -#define SQLITE_READONLY 8 /* Attempt to write a readonly database */ -#define SQLITE_INTERRUPT 9 /* Operation terminated by sqlite3_interrupt()*/ -#define SQLITE_IOERR 10 /* Some kind of disk I/O error occurred */ -#define SQLITE_CORRUPT 11 /* The database disk image is malformed */ -#define SQLITE_NOTFOUND 12 /* Unknown opcode in sqlite3_file_control() */ -#define SQLITE_FULL 13 /* Insertion failed because database is full */ -#define SQLITE_CANTOPEN 14 /* Unable to open the database file */ -#define SQLITE_PROTOCOL 15 /* Database lock protocol error */ -#define SQLITE_EMPTY 16 /* Internal use only */ -#define SQLITE_SCHEMA 17 /* The database schema changed */ -#define SQLITE_TOOBIG 18 /* String or BLOB exceeds size limit */ -#define SQLITE_CONSTRAINT 19 /* Abort due to constraint violation */ -#define SQLITE_MISMATCH 20 /* Data type mismatch */ -#define SQLITE_MISUSE 21 /* Library used incorrectly */ -#define SQLITE_NOLFS 22 /* Uses OS features not supported on host */ -#define SQLITE_AUTH 23 /* Authorization denied */ -#define SQLITE_FORMAT 24 /* Not used */ -#define SQLITE_RANGE 25 /* 2nd parameter to sqlite3_bind out of range */ -#define SQLITE_NOTADB 26 /* File opened that is not a database file */ -#define SQLITE_NOTICE 27 /* Notifications from sqlite3_log() */ -#define SQLITE_WARNING 28 /* Warnings from sqlite3_log() */ -#define SQLITE_ROW 100 /* sqlite3_step() has another row ready */ -#define SQLITE_DONE 101 /* sqlite3_step() has finished executing */ +#define SQLITE_ERROR 1 /* Generic error */ +#define SQLITE_INTERNAL 2 /* Internal logic error in SQLite */ +#define SQLITE_PERM 3 /* Access permission denied */ +#define SQLITE_ABORT 4 /* Callback routine requested an abort */ +#define SQLITE_BUSY 5 /* The database file is locked */ +#define SQLITE_LOCKED 6 /* A table in the database is locked */ +#define SQLITE_NOMEM 7 /* A malloc() failed */ +#define SQLITE_READONLY 8 /* Attempt to write a readonly database */ +#define SQLITE_INTERRUPT 9 /* Operation terminated by sqlite3_interrupt()*/ +#define SQLITE_IOERR 10 /* Some kind of disk I/O error occurred */ +#define SQLITE_CORRUPT 11 /* The database disk image is malformed */ +#define SQLITE_NOTFOUND 12 /* Unknown opcode in sqlite3_file_control() */ +#define SQLITE_FULL 13 /* Insertion failed because database is full */ +#define SQLITE_CANTOPEN 14 /* Unable to open the database file */ +#define SQLITE_PROTOCOL 15 /* Database lock protocol error */ +#define SQLITE_EMPTY 16 /* Internal use only */ +#define SQLITE_SCHEMA 17 /* The database schema changed */ +#define SQLITE_TOOBIG 18 /* String or BLOB exceeds size limit */ +#define SQLITE_CONSTRAINT 19 /* Abort due to constraint violation */ +#define SQLITE_MISMATCH 20 /* Data type mismatch */ +#define SQLITE_MISUSE 21 /* Library used incorrectly */ +#define SQLITE_NOLFS 22 /* Uses OS features not supported on host */ +#define SQLITE_AUTH 23 /* Authorization denied */ +#define SQLITE_FORMAT 24 /* Not used */ +#define SQLITE_RANGE 25 /* 2nd parameter to sqlite3_bind out of range */ +#define SQLITE_NOTADB 26 /* File opened that is not a database file */ +#define SQLITE_NOTICE 27 /* Notifications from sqlite3_log() */ +#define SQLITE_WARNING 28 /* Warnings from sqlite3_log() */ +#define SQLITE_ROW 100 /* sqlite3_step() has another row ready */ +#define SQLITE_DONE 101 /* sqlite3_step() has finished executing */ /* end-of-error-codes */ /* @@ -476,73 +497,88 @@ typedef unsigned long long int sqlite_uint64; ** the most recent error can be obtained using ** [sqlite3_extended_errcode()]. */ -#define SQLITE_ERROR_MISSING_COLLSEQ (SQLITE_ERROR | (1 << 8)) -#define SQLITE_ERROR_RETRY (SQLITE_ERROR | (2 << 8)) -#define SQLITE_ERROR_SNAPSHOT (SQLITE_ERROR | (3 << 8)) -#define SQLITE_IOERR_READ (SQLITE_IOERR | (1 << 8)) -#define SQLITE_IOERR_SHORT_READ (SQLITE_IOERR | (2 << 8)) -#define SQLITE_IOERR_WRITE (SQLITE_IOERR | (3 << 8)) -#define SQLITE_IOERR_FSYNC (SQLITE_IOERR | (4 << 8)) -#define SQLITE_IOERR_DIR_FSYNC (SQLITE_IOERR | (5 << 8)) -#define SQLITE_IOERR_TRUNCATE (SQLITE_IOERR | (6 << 8)) -#define SQLITE_IOERR_FSTAT (SQLITE_IOERR | (7 << 8)) -#define SQLITE_IOERR_UNLOCK (SQLITE_IOERR | (8 << 8)) -#define SQLITE_IOERR_RDLOCK (SQLITE_IOERR | (9 << 8)) -#define SQLITE_IOERR_DELETE (SQLITE_IOERR | (10 << 8)) -#define SQLITE_IOERR_BLOCKED (SQLITE_IOERR | (11 << 8)) -#define SQLITE_IOERR_NOMEM (SQLITE_IOERR | (12 << 8)) -#define SQLITE_IOERR_ACCESS (SQLITE_IOERR | (13 << 8)) -#define SQLITE_IOERR_CHECKRESERVEDLOCK (SQLITE_IOERR | (14 << 8)) -#define SQLITE_IOERR_LOCK (SQLITE_IOERR | (15 << 8)) -#define SQLITE_IOERR_CLOSE (SQLITE_IOERR | (16 << 8)) -#define SQLITE_IOERR_DIR_CLOSE (SQLITE_IOERR | (17 << 8)) -#define SQLITE_IOERR_SHMOPEN (SQLITE_IOERR | (18 << 8)) -#define SQLITE_IOERR_SHMSIZE (SQLITE_IOERR | (19 << 8)) -#define SQLITE_IOERR_SHMLOCK (SQLITE_IOERR | (20 << 8)) -#define SQLITE_IOERR_SHMMAP (SQLITE_IOERR | (21 << 8)) -#define SQLITE_IOERR_SEEK (SQLITE_IOERR | (22 << 8)) -#define SQLITE_IOERR_DELETE_NOENT (SQLITE_IOERR | (23 << 8)) -#define SQLITE_IOERR_MMAP (SQLITE_IOERR | (24 << 8)) -#define SQLITE_IOERR_GETTEMPPATH (SQLITE_IOERR | (25 << 8)) -#define SQLITE_IOERR_CONVPATH (SQLITE_IOERR | (26 << 8)) -#define SQLITE_IOERR_VNODE (SQLITE_IOERR | (27 << 8)) -#define SQLITE_IOERR_AUTH (SQLITE_IOERR | (28 << 8)) -#define SQLITE_IOERR_BEGIN_ATOMIC (SQLITE_IOERR | (29 << 8)) -#define SQLITE_IOERR_COMMIT_ATOMIC (SQLITE_IOERR | (30 << 8)) -#define SQLITE_IOERR_ROLLBACK_ATOMIC (SQLITE_IOERR | (31 << 8)) -#define SQLITE_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1 << 8)) -#define SQLITE_LOCKED_VTAB (SQLITE_LOCKED | (2 << 8)) -#define SQLITE_BUSY_RECOVERY (SQLITE_BUSY | (1 << 8)) -#define SQLITE_BUSY_SNAPSHOT (SQLITE_BUSY | (2 << 8)) -#define SQLITE_CANTOPEN_NOTEMPDIR (SQLITE_CANTOPEN | (1 << 8)) -#define SQLITE_CANTOPEN_ISDIR (SQLITE_CANTOPEN | (2 << 8)) -#define SQLITE_CANTOPEN_FULLPATH (SQLITE_CANTOPEN | (3 << 8)) -#define SQLITE_CANTOPEN_CONVPATH (SQLITE_CANTOPEN | (4 << 8)) -#define SQLITE_CANTOPEN_DIRTYWAL (SQLITE_CANTOPEN | (5 << 8)) /* Not Used */ -#define SQLITE_CORRUPT_VTAB (SQLITE_CORRUPT | (1 << 8)) -#define SQLITE_CORRUPT_SEQUENCE (SQLITE_CORRUPT | (2 << 8)) -#define SQLITE_READONLY_RECOVERY (SQLITE_READONLY | (1 << 8)) -#define SQLITE_READONLY_CANTLOCK (SQLITE_READONLY | (2 << 8)) -#define SQLITE_READONLY_ROLLBACK (SQLITE_READONLY | (3 << 8)) -#define SQLITE_READONLY_DBMOVED (SQLITE_READONLY | (4 << 8)) -#define SQLITE_READONLY_CANTINIT (SQLITE_READONLY | (5 << 8)) -#define SQLITE_READONLY_DIRECTORY (SQLITE_READONLY | (6 << 8)) -#define SQLITE_ABORT_ROLLBACK (SQLITE_ABORT | (2 << 8)) -#define SQLITE_CONSTRAINT_CHECK (SQLITE_CONSTRAINT | (1 << 8)) -#define SQLITE_CONSTRAINT_COMMITHOOK (SQLITE_CONSTRAINT | (2 << 8)) -#define SQLITE_CONSTRAINT_FOREIGNKEY (SQLITE_CONSTRAINT | (3 << 8)) -#define SQLITE_CONSTRAINT_FUNCTION (SQLITE_CONSTRAINT | (4 << 8)) -#define SQLITE_CONSTRAINT_NOTNULL (SQLITE_CONSTRAINT | (5 << 8)) -#define SQLITE_CONSTRAINT_PRIMARYKEY (SQLITE_CONSTRAINT | (6 << 8)) -#define SQLITE_CONSTRAINT_TRIGGER (SQLITE_CONSTRAINT | (7 << 8)) -#define SQLITE_CONSTRAINT_UNIQUE (SQLITE_CONSTRAINT | (8 << 8)) -#define SQLITE_CONSTRAINT_VTAB (SQLITE_CONSTRAINT | (9 << 8)) -#define SQLITE_CONSTRAINT_ROWID (SQLITE_CONSTRAINT | (10 << 8)) -#define SQLITE_NOTICE_RECOVER_WAL (SQLITE_NOTICE | (1 << 8)) -#define SQLITE_NOTICE_RECOVER_ROLLBACK (SQLITE_NOTICE | (2 << 8)) -#define SQLITE_WARNING_AUTOINDEX (SQLITE_WARNING | (1 << 8)) -#define SQLITE_AUTH_USER (SQLITE_AUTH | (1 << 8)) -#define SQLITE_OK_LOAD_PERMANENTLY (SQLITE_OK | (1 << 8)) +#define SQLITE_ERROR_MISSING_COLLSEQ (SQLITE_ERROR | (1<<8)) +#define SQLITE_ERROR_RETRY (SQLITE_ERROR | (2<<8)) +#define SQLITE_ERROR_SNAPSHOT (SQLITE_ERROR | (3<<8)) +#define SQLITE_ERROR_RESERVESIZE (SQLITE_ERROR | (4<<8)) +#define SQLITE_ERROR_KEY (SQLITE_ERROR | (5<<8)) +#define SQLITE_ERROR_UNABLE (SQLITE_ERROR | (6<<8)) +#define SQLITE_IOERR_READ (SQLITE_IOERR | (1<<8)) +#define SQLITE_IOERR_SHORT_READ (SQLITE_IOERR | (2<<8)) +#define SQLITE_IOERR_WRITE (SQLITE_IOERR | (3<<8)) +#define SQLITE_IOERR_FSYNC (SQLITE_IOERR | (4<<8)) +#define SQLITE_IOERR_DIR_FSYNC (SQLITE_IOERR | (5<<8)) +#define SQLITE_IOERR_TRUNCATE (SQLITE_IOERR | (6<<8)) +#define SQLITE_IOERR_FSTAT (SQLITE_IOERR | (7<<8)) +#define SQLITE_IOERR_UNLOCK (SQLITE_IOERR | (8<<8)) +#define SQLITE_IOERR_RDLOCK (SQLITE_IOERR | (9<<8)) +#define SQLITE_IOERR_DELETE (SQLITE_IOERR | (10<<8)) +#define SQLITE_IOERR_BLOCKED (SQLITE_IOERR | (11<<8)) +#define SQLITE_IOERR_NOMEM (SQLITE_IOERR | (12<<8)) +#define SQLITE_IOERR_ACCESS (SQLITE_IOERR | (13<<8)) +#define SQLITE_IOERR_CHECKRESERVEDLOCK (SQLITE_IOERR | (14<<8)) +#define SQLITE_IOERR_LOCK (SQLITE_IOERR | (15<<8)) +#define SQLITE_IOERR_CLOSE (SQLITE_IOERR | (16<<8)) +#define SQLITE_IOERR_DIR_CLOSE (SQLITE_IOERR | (17<<8)) +#define SQLITE_IOERR_SHMOPEN (SQLITE_IOERR | (18<<8)) +#define SQLITE_IOERR_SHMSIZE (SQLITE_IOERR | (19<<8)) +#define SQLITE_IOERR_SHMLOCK (SQLITE_IOERR | (20<<8)) +#define SQLITE_IOERR_SHMMAP (SQLITE_IOERR | (21<<8)) +#define SQLITE_IOERR_SEEK (SQLITE_IOERR | (22<<8)) +#define SQLITE_IOERR_DELETE_NOENT (SQLITE_IOERR | (23<<8)) +#define SQLITE_IOERR_MMAP (SQLITE_IOERR | (24<<8)) +#define SQLITE_IOERR_GETTEMPPATH (SQLITE_IOERR | (25<<8)) +#define SQLITE_IOERR_CONVPATH (SQLITE_IOERR | (26<<8)) +#define SQLITE_IOERR_VNODE (SQLITE_IOERR | (27<<8)) +#define SQLITE_IOERR_AUTH (SQLITE_IOERR | (28<<8)) +#define SQLITE_IOERR_BEGIN_ATOMIC (SQLITE_IOERR | (29<<8)) +#define SQLITE_IOERR_COMMIT_ATOMIC (SQLITE_IOERR | (30<<8)) +#define SQLITE_IOERR_ROLLBACK_ATOMIC (SQLITE_IOERR | (31<<8)) +#define SQLITE_IOERR_DATA (SQLITE_IOERR | (32<<8)) +#define SQLITE_IOERR_CORRUPTFS (SQLITE_IOERR | (33<<8)) +#define SQLITE_IOERR_IN_PAGE (SQLITE_IOERR | (34<<8)) +#define SQLITE_IOERR_BADKEY (SQLITE_IOERR | (35<<8)) +#define SQLITE_IOERR_CODEC (SQLITE_IOERR | (36<<8)) +#define SQLITE_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1<<8)) +#define SQLITE_LOCKED_VTAB (SQLITE_LOCKED | (2<<8)) +#define SQLITE_BUSY_RECOVERY (SQLITE_BUSY | (1<<8)) +#define SQLITE_BUSY_SNAPSHOT (SQLITE_BUSY | (2<<8)) +#define SQLITE_BUSY_TIMEOUT (SQLITE_BUSY | (3<<8)) +#define SQLITE_CANTOPEN_NOTEMPDIR (SQLITE_CANTOPEN | (1<<8)) +#define SQLITE_CANTOPEN_ISDIR (SQLITE_CANTOPEN | (2<<8)) +#define SQLITE_CANTOPEN_FULLPATH (SQLITE_CANTOPEN | (3<<8)) +#define SQLITE_CANTOPEN_CONVPATH (SQLITE_CANTOPEN | (4<<8)) +#define SQLITE_CANTOPEN_DIRTYWAL (SQLITE_CANTOPEN | (5<<8)) /* Not Used */ +#define SQLITE_CANTOPEN_SYMLINK (SQLITE_CANTOPEN | (6<<8)) +#define SQLITE_CORRUPT_VTAB (SQLITE_CORRUPT | (1<<8)) +#define SQLITE_CORRUPT_SEQUENCE (SQLITE_CORRUPT | (2<<8)) +#define SQLITE_CORRUPT_INDEX (SQLITE_CORRUPT | (3<<8)) +#define SQLITE_READONLY_RECOVERY (SQLITE_READONLY | (1<<8)) +#define SQLITE_READONLY_CANTLOCK (SQLITE_READONLY | (2<<8)) +#define SQLITE_READONLY_ROLLBACK (SQLITE_READONLY | (3<<8)) +#define SQLITE_READONLY_DBMOVED (SQLITE_READONLY | (4<<8)) +#define SQLITE_READONLY_CANTINIT (SQLITE_READONLY | (5<<8)) +#define SQLITE_READONLY_DIRECTORY (SQLITE_READONLY | (6<<8)) +#define SQLITE_ABORT_ROLLBACK (SQLITE_ABORT | (2<<8)) +#define SQLITE_CONSTRAINT_CHECK (SQLITE_CONSTRAINT | (1<<8)) +#define SQLITE_CONSTRAINT_COMMITHOOK (SQLITE_CONSTRAINT | (2<<8)) +#define SQLITE_CONSTRAINT_FOREIGNKEY (SQLITE_CONSTRAINT | (3<<8)) +#define SQLITE_CONSTRAINT_FUNCTION (SQLITE_CONSTRAINT | (4<<8)) +#define SQLITE_CONSTRAINT_NOTNULL (SQLITE_CONSTRAINT | (5<<8)) +#define SQLITE_CONSTRAINT_PRIMARYKEY (SQLITE_CONSTRAINT | (6<<8)) +#define SQLITE_CONSTRAINT_TRIGGER (SQLITE_CONSTRAINT | (7<<8)) +#define SQLITE_CONSTRAINT_UNIQUE (SQLITE_CONSTRAINT | (8<<8)) +#define SQLITE_CONSTRAINT_VTAB (SQLITE_CONSTRAINT | (9<<8)) +#define SQLITE_CONSTRAINT_ROWID (SQLITE_CONSTRAINT |(10<<8)) +#define SQLITE_CONSTRAINT_PINNED (SQLITE_CONSTRAINT |(11<<8)) +#define SQLITE_CONSTRAINT_DATATYPE (SQLITE_CONSTRAINT |(12<<8)) +#define SQLITE_NOTICE_RECOVER_WAL (SQLITE_NOTICE | (1<<8)) +#define SQLITE_NOTICE_RECOVER_ROLLBACK (SQLITE_NOTICE | (2<<8)) +#define SQLITE_NOTICE_RBU (SQLITE_NOTICE | (3<<8)) +#define SQLITE_WARNING_AUTOINDEX (SQLITE_WARNING | (1<<8)) +#define SQLITE_AUTH_USER (SQLITE_AUTH | (1<<8)) +#define SQLITE_OK_LOAD_PERMANENTLY (SQLITE_OK | (1<<8)) +#define SQLITE_OK_SYMLINK (SQLITE_OK | (2<<8)) /* internal only */ /* ** CAPI3REF: Flags For File Open Operations @@ -550,29 +586,47 @@ typedef unsigned long long int sqlite_uint64; ** These bit values are intended for use in the ** 3rd parameter to the [sqlite3_open_v2()] interface and ** in the 4th parameter to the [sqlite3_vfs.xOpen] method. -*/ -#define SQLITE_OPEN_READONLY 0x00000001 /* Ok for sqlite3_open_v2() */ -#define SQLITE_OPEN_READWRITE 0x00000002 /* Ok for sqlite3_open_v2() */ -#define SQLITE_OPEN_CREATE 0x00000004 /* Ok for sqlite3_open_v2() */ -#define SQLITE_OPEN_DELETEONCLOSE 0x00000008 /* VFS only */ -#define SQLITE_OPEN_EXCLUSIVE 0x00000010 /* VFS only */ -#define SQLITE_OPEN_AUTOPROXY 0x00000020 /* VFS only */ -#define SQLITE_OPEN_URI 0x00000040 /* Ok for sqlite3_open_v2() */ -#define SQLITE_OPEN_MEMORY 0x00000080 /* Ok for sqlite3_open_v2() */ -#define SQLITE_OPEN_MAIN_DB 0x00000100 /* VFS only */ -#define SQLITE_OPEN_TEMP_DB 0x00000200 /* VFS only */ -#define SQLITE_OPEN_TRANSIENT_DB 0x00000400 /* VFS only */ -#define SQLITE_OPEN_MAIN_JOURNAL 0x00000800 /* VFS only */ -#define SQLITE_OPEN_TEMP_JOURNAL 0x00001000 /* VFS only */ -#define SQLITE_OPEN_SUBJOURNAL 0x00002000 /* VFS only */ -#define SQLITE_OPEN_MASTER_JOURNAL 0x00004000 /* VFS only */ -#define SQLITE_OPEN_NOMUTEX 0x00008000 /* Ok for sqlite3_open_v2() */ -#define SQLITE_OPEN_FULLMUTEX 0x00010000 /* Ok for sqlite3_open_v2() */ -#define SQLITE_OPEN_SHAREDCACHE 0x00020000 /* Ok for sqlite3_open_v2() */ -#define SQLITE_OPEN_PRIVATECACHE 0x00040000 /* Ok for sqlite3_open_v2() */ -#define SQLITE_OPEN_WAL 0x00080000 /* VFS only */ +** +** Only those flags marked as "Ok for sqlite3_open_v2()" may be +** used as the third argument to the [sqlite3_open_v2()] interface. +** The other flags have historically been ignored by sqlite3_open_v2(), +** though future versions of SQLite might change so that an error is +** raised if any of the disallowed bits are passed into sqlite3_open_v2(). +** Applications should not depend on the historical behavior. +** +** Note in particular that passing the SQLITE_OPEN_EXCLUSIVE flag into +** [sqlite3_open_v2()] does *not* cause the underlying database file +** to be opened using O_EXCL. Passing SQLITE_OPEN_EXCLUSIVE into +** [sqlite3_open_v2()] has historically been a no-op and might become an +** error in future versions of SQLite. +*/ +#define SQLITE_OPEN_READONLY 0x00000001 /* Ok for sqlite3_open_v2() */ +#define SQLITE_OPEN_READWRITE 0x00000002 /* Ok for sqlite3_open_v2() */ +#define SQLITE_OPEN_CREATE 0x00000004 /* Ok for sqlite3_open_v2() */ +#define SQLITE_OPEN_DELETEONCLOSE 0x00000008 /* VFS only */ +#define SQLITE_OPEN_EXCLUSIVE 0x00000010 /* VFS only */ +#define SQLITE_OPEN_AUTOPROXY 0x00000020 /* VFS only */ +#define SQLITE_OPEN_URI 0x00000040 /* Ok for sqlite3_open_v2() */ +#define SQLITE_OPEN_MEMORY 0x00000080 /* Ok for sqlite3_open_v2() */ +#define SQLITE_OPEN_MAIN_DB 0x00000100 /* VFS only */ +#define SQLITE_OPEN_TEMP_DB 0x00000200 /* VFS only */ +#define SQLITE_OPEN_TRANSIENT_DB 0x00000400 /* VFS only */ +#define SQLITE_OPEN_MAIN_JOURNAL 0x00000800 /* VFS only */ +#define SQLITE_OPEN_TEMP_JOURNAL 0x00001000 /* VFS only */ +#define SQLITE_OPEN_SUBJOURNAL 0x00002000 /* VFS only */ +#define SQLITE_OPEN_SUPER_JOURNAL 0x00004000 /* VFS only */ +#define SQLITE_OPEN_NOMUTEX 0x00008000 /* Ok for sqlite3_open_v2() */ +#define SQLITE_OPEN_FULLMUTEX 0x00010000 /* Ok for sqlite3_open_v2() */ +#define SQLITE_OPEN_SHAREDCACHE 0x00020000 /* Ok for sqlite3_open_v2() */ +#define SQLITE_OPEN_PRIVATECACHE 0x00040000 /* Ok for sqlite3_open_v2() */ +#define SQLITE_OPEN_WAL 0x00080000 /* VFS only */ +#define SQLITE_OPEN_NOFOLLOW 0x01000000 /* Ok for sqlite3_open_v2() */ +#define SQLITE_OPEN_EXRESCODE 0x02000000 /* Extended result codes */ /* Reserved: 0x00F00000 */ +/* Legacy compatibility: */ +#define SQLITE_OPEN_MASTER_JOURNAL 0x00004000 /* VFS only */ + /* ** CAPI3REF: Device Characteristics @@ -606,35 +660,47 @@ typedef unsigned long long int sqlite_uint64; ** filesystem supports doing multiple write operations atomically when those ** write operations are bracketed by [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] and ** [SQLITE_FCNTL_COMMIT_ATOMIC_WRITE]. -*/ -#define SQLITE_IOCAP_ATOMIC 0x00000001 -#define SQLITE_IOCAP_ATOMIC512 0x00000002 -#define SQLITE_IOCAP_ATOMIC1K 0x00000004 -#define SQLITE_IOCAP_ATOMIC2K 0x00000008 -#define SQLITE_IOCAP_ATOMIC4K 0x00000010 -#define SQLITE_IOCAP_ATOMIC8K 0x00000020 -#define SQLITE_IOCAP_ATOMIC16K 0x00000040 -#define SQLITE_IOCAP_ATOMIC32K 0x00000080 -#define SQLITE_IOCAP_ATOMIC64K 0x00000100 -#define SQLITE_IOCAP_SAFE_APPEND 0x00000200 -#define SQLITE_IOCAP_SEQUENTIAL 0x00000400 -#define SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN 0x00000800 -#define SQLITE_IOCAP_POWERSAFE_OVERWRITE 0x00001000 -#define SQLITE_IOCAP_IMMUTABLE 0x00002000 -#define SQLITE_IOCAP_BATCH_ATOMIC 0x00004000 +** +** The SQLITE_IOCAP_SUBPAGE_READ property means that it is ok to read +** from the database file in amounts that are not a multiple of the +** page size and that do not begin at a page boundary. Without this +** property, SQLite is careful to only do full-page reads and write +** on aligned pages, with the one exception that it will do a sub-page +** read of the first page to access the database header. +*/ +#define SQLITE_IOCAP_ATOMIC 0x00000001 +#define SQLITE_IOCAP_ATOMIC512 0x00000002 +#define SQLITE_IOCAP_ATOMIC1K 0x00000004 +#define SQLITE_IOCAP_ATOMIC2K 0x00000008 +#define SQLITE_IOCAP_ATOMIC4K 0x00000010 +#define SQLITE_IOCAP_ATOMIC8K 0x00000020 +#define SQLITE_IOCAP_ATOMIC16K 0x00000040 +#define SQLITE_IOCAP_ATOMIC32K 0x00000080 +#define SQLITE_IOCAP_ATOMIC64K 0x00000100 +#define SQLITE_IOCAP_SAFE_APPEND 0x00000200 +#define SQLITE_IOCAP_SEQUENTIAL 0x00000400 +#define SQLITE_IOCAP_UNDELETABLE_WHEN_OPEN 0x00000800 +#define SQLITE_IOCAP_POWERSAFE_OVERWRITE 0x00001000 +#define SQLITE_IOCAP_IMMUTABLE 0x00002000 +#define SQLITE_IOCAP_BATCH_ATOMIC 0x00004000 +#define SQLITE_IOCAP_SUBPAGE_READ 0x00008000 /* ** CAPI3REF: File Locking Levels ** ** SQLite uses one of these integer values as the second ** argument to calls it makes to the xLock() and xUnlock() methods -** of an [sqlite3_io_methods] object. +** of an [sqlite3_io_methods] object. These values are ordered from +** least restrictive to most restrictive. +** +** The argument to xLock() is always SHARED or higher. The argument to +** xUnlock is either SHARED or NONE. */ -#define SQLITE_LOCK_NONE 0 -#define SQLITE_LOCK_SHARED 1 -#define SQLITE_LOCK_RESERVED 2 -#define SQLITE_LOCK_PENDING 3 -#define SQLITE_LOCK_EXCLUSIVE 4 +#define SQLITE_LOCK_NONE 0 /* xUnlock() only */ +#define SQLITE_LOCK_SHARED 1 /* xLock() or xUnlock() */ +#define SQLITE_LOCK_RESERVED 2 /* xLock() only */ +#define SQLITE_LOCK_PENDING 3 /* xLock() only */ +#define SQLITE_LOCK_EXCLUSIVE 4 /* xLock() only */ /* ** CAPI3REF: Synchronization Type Flags @@ -662,14 +728,14 @@ typedef unsigned long long int sqlite_uint64; ** operating systems natively supported by SQLite, only Mac OSX ** cares about the difference.) */ -#define SQLITE_SYNC_NORMAL 0x00002 -#define SQLITE_SYNC_FULL 0x00003 -#define SQLITE_SYNC_DATAONLY 0x00010 +#define SQLITE_SYNC_NORMAL 0x00002 +#define SQLITE_SYNC_FULL 0x00003 +#define SQLITE_SYNC_DATAONLY 0x00010 - /* +/* ** CAPI3REF: OS Interface Open File Handle ** -** An [sqlite3_file] object represents an open file in the +** An [sqlite3_file] object represents an open file in the ** [sqlite3_vfs | OS interface layer]. Individual OS interface ** implementations will ** want to subclass this object by appending additional fields @@ -677,13 +743,12 @@ typedef unsigned long long int sqlite_uint64; ** [sqlite3_io_methods] object that defines methods for performing ** I/O operations on the open file. */ - typedef struct sqlite3_file sqlite3_file; - struct sqlite3_file - { - const struct sqlite3_io_methods* pMethods; /* Methods for an open file */ - }; +typedef struct sqlite3_file sqlite3_file; +struct sqlite3_file { + const struct sqlite3_io_methods *pMethods; /* Methods for an open file */ +}; - /* +/* ** CAPI3REF: OS Interface File Virtual Methods Object ** ** Every file opened by the [sqlite3_vfs.xOpen] method populates an @@ -692,7 +757,7 @@ typedef unsigned long long int sqlite_uint64; ** This object defines the methods used to perform various operations ** against the open file represented by the [sqlite3_file] object. ** -** If the [sqlite3_vfs.xOpen] method sets the sqlite3_file.pMethods element +** If the [sqlite3_vfs.xOpen] method sets the sqlite3_file.pMethods element ** to a non-NULL pointer, then the sqlite3_io_methods.xClose method ** may be invoked even if the [sqlite3_vfs.xOpen] reported that it failed. The ** only way to prevent a call to xClose following a failed [sqlite3_vfs.xOpen] @@ -713,11 +778,18 @@ typedef unsigned long long int sqlite_uint64; **
                  2. [SQLITE_LOCK_PENDING], or **
                  3. [SQLITE_LOCK_EXCLUSIVE]. ** -** xLock() increases the lock. xUnlock() decreases the lock. +** xLock() upgrades the database file lock. In other words, xLock() moves the +** database file lock in the direction NONE toward EXCLUSIVE. The argument to +** xLock() is always one of SHARED, RESERVED, PENDING, or EXCLUSIVE, never +** SQLITE_LOCK_NONE. If the database file lock is already at or above the +** requested lock, then the call to xLock() is a no-op. +** xUnlock() downgrades the database file lock to either SHARED or NONE. +** If the lock is already at or below the requested lock state, then the call +** to xUnlock() is a no-op. ** The xCheckReservedLock() method checks whether any database connection, ** either in this process or in some other process, is holding a RESERVED, -** PENDING, or EXCLUSIVE lock on the file. It returns true -** if such a lock exists and false otherwise. +** PENDING, or EXCLUSIVE lock on the file. It returns, via its output +** pointer parameter, true if such a lock exists and false otherwise. ** ** The xFileControl() method is a generic interface that allows custom ** VFS implementations to directly control an open file using the @@ -758,6 +830,7 @@ typedef unsigned long long int sqlite_uint64; **
                  4. [SQLITE_IOCAP_POWERSAFE_OVERWRITE] **
                  5. [SQLITE_IOCAP_IMMUTABLE] **
                  6. [SQLITE_IOCAP_BATCH_ATOMIC] +**
                  7. [SQLITE_IOCAP_SUBPAGE_READ] ** ** ** The SQLITE_IOCAP_ATOMIC property means that all writes of @@ -777,33 +850,32 @@ typedef unsigned long long int sqlite_uint64; ** failure to zero-fill short reads will eventually lead to ** database corruption. */ - typedef struct sqlite3_io_methods sqlite3_io_methods; - struct sqlite3_io_methods - { - int iVersion; - int (*xClose)(sqlite3_file*); - int (*xRead)(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst); - int (*xWrite)(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst); - int (*xTruncate)(sqlite3_file*, sqlite3_int64 size); - int (*xSync)(sqlite3_file*, int flags); - int (*xFileSize)(sqlite3_file*, sqlite3_int64* pSize); - int (*xLock)(sqlite3_file*, int); - int (*xUnlock)(sqlite3_file*, int); - int (*xCheckReservedLock)(sqlite3_file*, int* pResOut); - int (*xFileControl)(sqlite3_file*, int op, void* pArg); - int (*xSectorSize)(sqlite3_file*); - int (*xDeviceCharacteristics)(sqlite3_file*); - /* Methods above are valid for version 1 */ - int (*xShmMap)(sqlite3_file*, int iPg, int pgsz, int, void volatile**); - int (*xShmLock)(sqlite3_file*, int offset, int n, int flags); - void (*xShmBarrier)(sqlite3_file*); - int (*xShmUnmap)(sqlite3_file*, int deleteFlag); - /* Methods above are valid for version 2 */ - int (*xFetch)(sqlite3_file*, sqlite3_int64 iOfst, int iAmt, void** pp); - int (*xUnfetch)(sqlite3_file*, sqlite3_int64 iOfst, void* p); - /* Methods above are valid for version 3 */ - /* Additional methods may be added in future releases */ - }; +typedef struct sqlite3_io_methods sqlite3_io_methods; +struct sqlite3_io_methods { + int iVersion; + int (*xClose)(sqlite3_file*); + int (*xRead)(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst); + int (*xWrite)(sqlite3_file*, const void*, int iAmt, sqlite3_int64 iOfst); + int (*xTruncate)(sqlite3_file*, sqlite3_int64 size); + int (*xSync)(sqlite3_file*, int flags); + int (*xFileSize)(sqlite3_file*, sqlite3_int64 *pSize); + int (*xLock)(sqlite3_file*, int); + int (*xUnlock)(sqlite3_file*, int); + int (*xCheckReservedLock)(sqlite3_file*, int *pResOut); + int (*xFileControl)(sqlite3_file*, int op, void *pArg); + int (*xSectorSize)(sqlite3_file*); + int (*xDeviceCharacteristics)(sqlite3_file*); + /* Methods above are valid for version 1 */ + int (*xShmMap)(sqlite3_file*, int iPg, int pgsz, int, void volatile**); + int (*xShmLock)(sqlite3_file*, int offset, int n, int flags); + void (*xShmBarrier)(sqlite3_file*); + int (*xShmUnmap)(sqlite3_file*, int deleteFlag); + /* Methods above are valid for version 2 */ + int (*xFetch)(sqlite3_file*, sqlite3_int64 iOfst, int iAmt, void **pp); + int (*xUnfetch)(sqlite3_file*, sqlite3_int64 iOfst, void *p); + /* Methods above are valid for version 3 */ + /* Additional methods may be added in future releases */ +}; /* ** CAPI3REF: Standard File Control Opcodes @@ -819,9 +891,8 @@ typedef unsigned long long int sqlite_uint64; ** opcode causes the xFileControl method to write the current state of ** the lock (one of [SQLITE_LOCK_NONE], [SQLITE_LOCK_SHARED], ** [SQLITE_LOCK_RESERVED], [SQLITE_LOCK_PENDING], or [SQLITE_LOCK_EXCLUSIVE]) -** into an integer that the pArg argument points to. This capability -** is used during testing and is only available when the SQLITE_TEST -** compile-time option is used. +** into an integer that the pArg argument points to. +** This capability is only available if SQLite is compiled with [SQLITE_DEBUG]. ** **
                  8. [[SQLITE_FCNTL_SIZE_HINT]] ** The [SQLITE_FCNTL_SIZE_HINT] opcode is used by SQLite to give the VFS @@ -843,7 +914,7 @@ typedef unsigned long long int sqlite_uint64; **
                  9. [[SQLITE_FCNTL_CHUNK_SIZE]] ** The [SQLITE_FCNTL_CHUNK_SIZE] opcode is used to request that the VFS ** extends and truncates the database file in chunks of a size specified -** by the user. The fourth argument to [sqlite3_file_control()] should +** by the user. The fourth argument to [sqlite3_file_control()] should ** point to an integer (type int) containing the new chunk-size to use ** for the nominated database. Allocating database file space in large ** chunks (say 1MB at a time), may reduce file-system fragmentation and @@ -861,29 +932,29 @@ typedef unsigned long long int sqlite_uint64; ** connection. See also [SQLITE_FCNTL_FILE_POINTER]. ** **
                  10. [[SQLITE_FCNTL_SYNC_OMITTED]] -** No longer in use. +** The SQLITE_FCNTL_SYNC_OMITTED file-control is no longer used. ** **
                  11. [[SQLITE_FCNTL_SYNC]] ** The [SQLITE_FCNTL_SYNC] opcode is generated internally by SQLite and ** sent to the VFS immediately before the xSync method is invoked on a -** database file descriptor. Or, if the xSync method is not invoked -** because the user has configured SQLite with -** [PRAGMA synchronous | PRAGMA synchronous=OFF] it is invoked in place +** database file descriptor. Or, if the xSync method is not invoked +** because the user has configured SQLite with +** [PRAGMA synchronous | PRAGMA synchronous=OFF] it is invoked in place ** of the xSync method. In most cases, the pointer argument passed with ** this file-control is NULL. However, if the database file is being synced ** as part of a multi-database commit, the argument points to a nul-terminated -** string containing the transactions master-journal file name. VFSes that -** do not need this signal should silently ignore this opcode. Applications -** should not call [sqlite3_file_control()] with this opcode as doing so may -** disrupt the operation of the specialized VFSes that do require it. +** string containing the transactions super-journal file name. VFSes that +** do not need this signal should silently ignore this opcode. Applications +** should not call [sqlite3_file_control()] with this opcode as doing so may +** disrupt the operation of the specialized VFSes that do require it. ** **
                  12. [[SQLITE_FCNTL_COMMIT_PHASETWO]] ** The [SQLITE_FCNTL_COMMIT_PHASETWO] opcode is generated internally by SQLite ** and sent to the VFS after a transaction has been committed immediately ** but before the database is unlocked. VFSes that do not need this signal ** should silently ignore this opcode. Applications should not call -** [sqlite3_file_control()] with this opcode as doing so may disrupt the -** operation of the specialized VFSes that do require it. +** [sqlite3_file_control()] with this opcode as doing so may disrupt the +** operation of the specialized VFSes that do require it. ** **
                  13. [[SQLITE_FCNTL_WIN32_AV_RETRY]] ** ^The [SQLITE_FCNTL_WIN32_AV_RETRY] opcode is used to configure automatic @@ -931,13 +1002,13 @@ typedef unsigned long long int sqlite_uint64; **
                  14. [[SQLITE_FCNTL_OVERWRITE]] ** ^The [SQLITE_FCNTL_OVERWRITE] opcode is invoked by SQLite after opening ** a write transaction to indicate that, unless it is rolled back for some -** reason, the entire database file will be overwritten by the current +** reason, the entire database file will be overwritten by the current ** transaction. This is used by VACUUM operations. ** **
                  15. [[SQLITE_FCNTL_VFSNAME]] ** ^The [SQLITE_FCNTL_VFSNAME] opcode can be used to obtain the names of -** all [VFSes] in the VFS stack. The names are of all VFS shims and the -** final bottom-level VFS are written into memory obtained from +** all [VFSes] in the VFS stack. The names of all VFS shims and the +** final bottom-level VFS are written into memory obtained from ** [sqlite3_malloc()] and the result is stored in the char* variable ** that the fourth parameter of [sqlite3_file_control()] points to. ** The caller is responsible for freeing the memory when done. As with @@ -950,13 +1021,13 @@ typedef unsigned long long int sqlite_uint64; ** ^The [SQLITE_FCNTL_VFS_POINTER] opcode finds a pointer to the top-level ** [VFSes] currently in use. ^(The argument X in ** sqlite3_file_control(db,SQLITE_FCNTL_VFS_POINTER,X) must be -** of type "[sqlite3_vfs] **". This opcodes will set *X +** of type "[sqlite3_vfs] **". This opcode will set *X ** to a pointer to the top-level VFS.)^ ** ^When there are multiple VFS shims in the stack, this opcode finds the ** upper-most shim only. ** **
                  16. [[SQLITE_FCNTL_PRAGMA]] -** ^Whenever a [PRAGMA] statement is parsed, an [SQLITE_FCNTL_PRAGMA] +** ^Whenever a [PRAGMA] statement is parsed, an [SQLITE_FCNTL_PRAGMA] ** file control is sent to the open [sqlite3_file] object corresponding ** to the database file to which the pragma statement refers. ^The argument ** to the [SQLITE_FCNTL_PRAGMA] file control is an array of @@ -967,7 +1038,7 @@ typedef unsigned long long int sqlite_uint64; ** of the char** argument point to a string obtained from [sqlite3_mprintf()] ** or the equivalent and that string will become the result of the pragma or ** the error message if the pragma fails. ^If the -** [SQLITE_FCNTL_PRAGMA] file control returns [SQLITE_NOTFOUND], then normal +** [SQLITE_FCNTL_PRAGMA] file control returns [SQLITE_NOTFOUND], then normal ** [PRAGMA] processing continues. ^If the [SQLITE_FCNTL_PRAGMA] ** file control returns [SQLITE_OK], then the parser assumes that the ** VFS has handled the PRAGMA itself and the parser generates a no-op @@ -984,16 +1055,16 @@ typedef unsigned long long int sqlite_uint64; ** ^The [SQLITE_FCNTL_BUSYHANDLER] ** file-control may be invoked by SQLite on the database file handle ** shortly after it is opened in order to provide a custom VFS with access -** to the connections busy-handler callback. The argument is of type (void **) +** to the connection's busy-handler callback. The argument is of type (void**) ** - an array of two (void *) values. The first (void *) actually points -** to a function of type (int (*)(void *)). In order to invoke the connections +** to a function of type (int (*)(void *)). In order to invoke the connection's ** busy-handler, this function should be invoked with the second (void *) in ** the array as the only argument. If it returns non-zero, then the operation ** should be retried. If it returns zero, the custom VFS should abandon the ** current operation. ** **
                  17. [[SQLITE_FCNTL_TEMPFILENAME]] -** ^Application can invoke the [SQLITE_FCNTL_TEMPFILENAME] file-control +** ^Applications can invoke the [SQLITE_FCNTL_TEMPFILENAME] file-control ** to have SQLite generate a ** temporary filename using the same algorithm that is followed to generate ** temporary filenames for TEMP tables and other internal uses. The @@ -1007,7 +1078,7 @@ typedef unsigned long long int sqlite_uint64; ** The argument is a pointer to a value of type sqlite3_int64 that ** is an advisory maximum number of bytes in the file to memory map. The ** pointer is overwritten with the old value. The limit is not changed if -** the value originally pointed to is negative, and so the current limit +** the value originally pointed to is negative, and so the current limit ** can be queried by passing in a pointer to a negative number. This ** file-control is used internally to implement [PRAGMA mmap_size]. ** @@ -1037,6 +1108,11 @@ typedef unsigned long long int sqlite_uint64; ** pointed to by the pArg argument. This capability is used during testing ** and only needs to be supported when SQLITE_TEST is defined. ** +**
                  18. [[SQLITE_FCNTL_NULL_IO]] +** The [SQLITE_FCNTL_NULL_IO] opcode sets the low-level file descriptor +** or file handle for the [sqlite3_file] object such that it will no longer +** read or write to the database file. +** **
                  19. [[SQLITE_FCNTL_WAL_BLOCK]] ** The [SQLITE_FCNTL_WAL_BLOCK] is a signal to the VFS layer that it might ** be advantageous to block on the next WAL lock if the lock is not immediately @@ -1051,7 +1127,7 @@ typedef unsigned long long int sqlite_uint64; **
                  20. [[SQLITE_FCNTL_RBU]] ** The [SQLITE_FCNTL_RBU] opcode is implemented by the special VFS used by ** the RBU extension only. All other VFS should return SQLITE_NOTFOUND for -** this opcode. +** this opcode. ** **
                  21. [[SQLITE_FCNTL_BEGIN_ATOMIC_WRITE]] ** If the [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] opcode returns SQLITE_OK, then @@ -1068,7 +1144,7 @@ typedef unsigned long long int sqlite_uint64; ** **
                  22. [[SQLITE_FCNTL_COMMIT_ATOMIC_WRITE]] ** The [SQLITE_FCNTL_COMMIT_ATOMIC_WRITE] opcode causes all write -** operations since the previous successful call to +** operations since the previous successful call to ** [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] to be performed atomically. ** This file control returns [SQLITE_OK] if and only if the writes were ** all performed successfully and have been committed to persistent storage. @@ -1080,7 +1156,7 @@ typedef unsigned long long int sqlite_uint64; ** **
                  23. [[SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE]] ** The [SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE] opcode causes all write -** operations since the previous successful call to +** operations since the previous successful call to ** [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE] to be rolled back. ** ^This file control takes the file descriptor out of batch write mode ** so that all subsequent write operations are independent. @@ -1088,10 +1164,18 @@ typedef unsigned long long int sqlite_uint64; ** a prior successful call to [SQLITE_FCNTL_BEGIN_ATOMIC_WRITE]. ** **
                  24. [[SQLITE_FCNTL_LOCK_TIMEOUT]] -** The [SQLITE_FCNTL_LOCK_TIMEOUT] opcode causes attempts to obtain -** a file lock using the xLock or xShmLock methods of the VFS to wait -** for up to M milliseconds before failing, where M is the single -** unsigned integer parameter. +** The [SQLITE_FCNTL_LOCK_TIMEOUT] opcode is used to configure a VFS +** to block for up to M milliseconds before failing when attempting to +** obtain a file lock using the xLock or xShmLock methods of the VFS. +** The parameter is a pointer to a 32-bit signed integer that contains +** the value that M is to be set to. Before returning, the 32-bit signed +** integer is overwritten with the previous value of M. +** +**
                  25. [[SQLITE_FCNTL_BLOCK_ON_CONNECT]] +** The [SQLITE_FCNTL_BLOCK_ON_CONNECT] opcode is used to configure the +** VFS to block when taking a SHARED lock to connect to a wal mode database. +** This is used to implement the functionality associated with +** SQLITE_SETLK_BLOCK_ON_CONNECT. ** **
                  26. [[SQLITE_FCNTL_DATA_VERSION]] ** The [SQLITE_FCNTL_DATA_VERSION] opcode is used to detect changes to @@ -1106,56 +1190,114 @@ typedef unsigned long long int sqlite_uint64; ** not provide a mechanism to detect changes to MAIN only. Also, the ** [sqlite3_total_changes()] interface responds to internal changes only and ** omits changes made by other database connections. The -** [PRAGMA data_version] command provide a mechanism to detect changes to +** [PRAGMA data_version] command provides a mechanism to detect changes to ** a single attached database that occur due to other database connections, ** but omits changes implemented by the database connection on which it is ** called. This file control is the only mechanism to detect changes that ** happen either internally or externally and that are associated with ** a particular attached database. +** +**
                  27. [[SQLITE_FCNTL_CKPT_START]] +** The [SQLITE_FCNTL_CKPT_START] opcode is invoked from within a checkpoint +** in wal mode before the client starts to copy pages from the wal +** file to the database file. +** +**
                  28. [[SQLITE_FCNTL_CKPT_DONE]] +** The [SQLITE_FCNTL_CKPT_DONE] opcode is invoked from within a checkpoint +** in wal mode after the client has finished copying pages from the wal +** file to the database file, but before the *-shm file is updated to +** record the fact that the pages have been checkpointed. +** +**
                  29. [[SQLITE_FCNTL_EXTERNAL_READER]] +** The EXPERIMENTAL [SQLITE_FCNTL_EXTERNAL_READER] opcode is used to detect +** whether or not there is a database client in another process with a wal-mode +** transaction open on the database or not. It is only available on unix. The +** (void*) argument passed with this file-control should be a pointer to a +** value of type (int). The integer value is set to 1 if the database is a wal +** mode database and there exists at least one client in another process that +** currently has an SQL transaction open on the database. It is set to 0 if +** the database is not a wal-mode db, or if there is no such connection in any +** other process. This opcode cannot be used to detect transactions opened +** by clients within the current process, only within other processes. +** +**
                  30. [[SQLITE_FCNTL_CKSM_FILE]] +** The [SQLITE_FCNTL_CKSM_FILE] opcode is for use internally by the +** [checksum VFS shim] only. +** +**
                  31. [[SQLITE_FCNTL_RESET_CACHE]] +** If there is currently no transaction open on the database, and the +** database is not a temp db, then the [SQLITE_FCNTL_RESET_CACHE] file-control +** purges the contents of the in-memory page cache. If there is an open +** transaction, or if the db is a temp-db, this opcode is a no-op, not an error. +** +**
                  32. [[SQLITE_FCNTL_FILESTAT]] +** The [SQLITE_FCNTL_FILESTAT] opcode returns low-level diagnostic information +** about the [sqlite3_file] objects used access the database and journal files +** for the given schema. The fourth parameter to [sqlite3_file_control()] +** should be an initialized [sqlite3_str] pointer. JSON text describing +** various aspects of the sqlite3_file object is appended to the sqlite3_str. +** The SQLITE_FCNTL_FILESTAT opcode is usually a no-op, unless compile-time +** options are used to enable it. ** */ -#define SQLITE_FCNTL_LOCKSTATE 1 -#define SQLITE_FCNTL_GET_LOCKPROXYFILE 2 -#define SQLITE_FCNTL_SET_LOCKPROXYFILE 3 -#define SQLITE_FCNTL_LAST_ERRNO 4 -#define SQLITE_FCNTL_SIZE_HINT 5 -#define SQLITE_FCNTL_CHUNK_SIZE 6 -#define SQLITE_FCNTL_FILE_POINTER 7 -#define SQLITE_FCNTL_SYNC_OMITTED 8 -#define SQLITE_FCNTL_WIN32_AV_RETRY 9 -#define SQLITE_FCNTL_PERSIST_WAL 10 -#define SQLITE_FCNTL_OVERWRITE 11 -#define SQLITE_FCNTL_VFSNAME 12 -#define SQLITE_FCNTL_POWERSAFE_OVERWRITE 13 -#define SQLITE_FCNTL_PRAGMA 14 -#define SQLITE_FCNTL_BUSYHANDLER 15 -#define SQLITE_FCNTL_TEMPFILENAME 16 -#define SQLITE_FCNTL_MMAP_SIZE 18 -#define SQLITE_FCNTL_TRACE 19 -#define SQLITE_FCNTL_HAS_MOVED 20 -#define SQLITE_FCNTL_SYNC 21 -#define SQLITE_FCNTL_COMMIT_PHASETWO 22 -#define SQLITE_FCNTL_WIN32_SET_HANDLE 23 -#define SQLITE_FCNTL_WAL_BLOCK 24 -#define SQLITE_FCNTL_ZIPVFS 25 -#define SQLITE_FCNTL_RBU 26 -#define SQLITE_FCNTL_VFS_POINTER 27 -#define SQLITE_FCNTL_JOURNAL_POINTER 28 -#define SQLITE_FCNTL_WIN32_GET_HANDLE 29 -#define SQLITE_FCNTL_PDB 30 -#define SQLITE_FCNTL_BEGIN_ATOMIC_WRITE 31 -#define SQLITE_FCNTL_COMMIT_ATOMIC_WRITE 32 -#define SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE 33 -#define SQLITE_FCNTL_LOCK_TIMEOUT 34 -#define SQLITE_FCNTL_DATA_VERSION 35 -#define SQLITE_FCNTL_SIZE_LIMIT 36 +#define SQLITE_FCNTL_LOCKSTATE 1 +#define SQLITE_FCNTL_GET_LOCKPROXYFILE 2 +#define SQLITE_FCNTL_SET_LOCKPROXYFILE 3 +#define SQLITE_FCNTL_LAST_ERRNO 4 +#define SQLITE_FCNTL_SIZE_HINT 5 +#define SQLITE_FCNTL_CHUNK_SIZE 6 +#define SQLITE_FCNTL_FILE_POINTER 7 +#define SQLITE_FCNTL_SYNC_OMITTED 8 +#define SQLITE_FCNTL_WIN32_AV_RETRY 9 +#define SQLITE_FCNTL_PERSIST_WAL 10 +#define SQLITE_FCNTL_OVERWRITE 11 +#define SQLITE_FCNTL_VFSNAME 12 +#define SQLITE_FCNTL_POWERSAFE_OVERWRITE 13 +#define SQLITE_FCNTL_PRAGMA 14 +#define SQLITE_FCNTL_BUSYHANDLER 15 +#define SQLITE_FCNTL_TEMPFILENAME 16 +#define SQLITE_FCNTL_MMAP_SIZE 18 +#define SQLITE_FCNTL_TRACE 19 +#define SQLITE_FCNTL_HAS_MOVED 20 +#define SQLITE_FCNTL_SYNC 21 +#define SQLITE_FCNTL_COMMIT_PHASETWO 22 +#define SQLITE_FCNTL_WIN32_SET_HANDLE 23 +#define SQLITE_FCNTL_WAL_BLOCK 24 +#define SQLITE_FCNTL_ZIPVFS 25 +#define SQLITE_FCNTL_RBU 26 +#define SQLITE_FCNTL_VFS_POINTER 27 +#define SQLITE_FCNTL_JOURNAL_POINTER 28 +#define SQLITE_FCNTL_WIN32_GET_HANDLE 29 +#define SQLITE_FCNTL_PDB 30 +#define SQLITE_FCNTL_BEGIN_ATOMIC_WRITE 31 +#define SQLITE_FCNTL_COMMIT_ATOMIC_WRITE 32 +#define SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE 33 +#define SQLITE_FCNTL_LOCK_TIMEOUT 34 +#define SQLITE_FCNTL_DATA_VERSION 35 +#define SQLITE_FCNTL_SIZE_LIMIT 36 +#define SQLITE_FCNTL_CKPT_DONE 37 +#define SQLITE_FCNTL_RESERVE_BYTES 38 +#define SQLITE_FCNTL_CKPT_START 39 +#define SQLITE_FCNTL_EXTERNAL_READER 40 +#define SQLITE_FCNTL_CKSM_FILE 41 +#define SQLITE_FCNTL_RESET_CACHE 42 +#define SQLITE_FCNTL_NULL_IO 43 +#define SQLITE_FCNTL_BLOCK_ON_CONNECT 44 +#define SQLITE_FCNTL_FILESTAT 45 /* deprecated names */ -#define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE -#define SQLITE_SET_LOCKPROXYFILE SQLITE_FCNTL_SET_LOCKPROXYFILE -#define SQLITE_LAST_ERRNO SQLITE_FCNTL_LAST_ERRNO +#define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE +#define SQLITE_SET_LOCKPROXYFILE SQLITE_FCNTL_SET_LOCKPROXYFILE +#define SQLITE_LAST_ERRNO SQLITE_FCNTL_LAST_ERRNO + +/* reserved file-control numbers: +** 101 +** 102 +** 103 +*/ + - /* +/* ** CAPI3REF: Mutex Handle ** ** The mutex module within SQLite defines [sqlite3_mutex] to be an @@ -1165,9 +1307,9 @@ typedef unsigned long long int sqlite_uint64; ** ** Mutexes are created using [sqlite3_mutex_alloc()]. */ - typedef struct sqlite3_mutex sqlite3_mutex; +typedef struct sqlite3_mutex sqlite3_mutex; - /* +/* ** CAPI3REF: Loadable Extension Thunk ** ** A pointer to the opaque sqlite3_api_routines structure is passed as @@ -1175,9 +1317,29 @@ typedef unsigned long long int sqlite_uint64; ** structure must be typedefed in order to work around compiler warnings ** on some platforms. */ - typedef struct sqlite3_api_routines sqlite3_api_routines; +typedef struct sqlite3_api_routines sqlite3_api_routines; + +/* +** CAPI3REF: File Name +** +** Type [sqlite3_filename] is used by SQLite to pass filenames to the +** xOpen method of a [VFS]. It may be cast to (const char*) and treated +** as a normal, nul-terminated, UTF-8 buffer containing the filename, but +** may also be passed to special APIs such as: +** +**
                      +**
                    • sqlite3_filename_database() +**
                    • sqlite3_filename_journal() +**
                    • sqlite3_filename_wal() +**
                    • sqlite3_uri_parameter() +**
                    • sqlite3_uri_boolean() +**
                    • sqlite3_uri_int64() +**
                    • sqlite3_uri_key() +**
                    +*/ +typedef const char *sqlite3_filename; - /* +/* ** CAPI3REF: OS Interface Object ** ** An instance of the sqlite3_vfs object defines the interface between @@ -1193,10 +1355,10 @@ typedef unsigned long long int sqlite_uint64; ** to 3 with SQLite [version 3.7.6] on [dateof:3.7.6]. Additional fields ** may be appended to the sqlite3_vfs object and the iVersion value ** may increase again in future versions of SQLite. -** Note that the structure -** of the sqlite3_vfs object changes in the transition from +** Note that due to an oversight, the structure +** of the sqlite3_vfs object changed in the transition from ** SQLite [version 3.5.9] to [version 3.6.0] on [dateof:3.6.0] -** and yet the iVersion field was not modified. +** and yet the iVersion field was not increased. ** ** The szOsFile field is the size of the subclassed [sqlite3_file] ** structure used by this VFS. mxPathname is the maximum length of @@ -1231,14 +1393,14 @@ typedef unsigned long long int sqlite_uint64; ** the [sqlite3_file] can safely store a pointer to the ** filename if it needs to remember the filename for some reason. ** If the zFilename parameter to xOpen is a NULL pointer then xOpen -** must invent its own temporary name for the file. ^Whenever the +** must invent its own temporary name for the file. ^Whenever the ** xFilename parameter is NULL it will also be the case that the ** flags parameter will include [SQLITE_OPEN_DELETEONCLOSE]. ** ** The flags argument to xOpen() includes all bits set in ** the flags argument to [sqlite3_open_v2()]. Or if [sqlite3_open()] ** or [sqlite3_open16()] is used, then flags includes at least -** [SQLITE_OPEN_READWRITE] | [SQLITE_OPEN_CREATE]. +** [SQLITE_OPEN_READWRITE] | [SQLITE_OPEN_CREATE]. ** If xOpen() opens a file read-only then it sets *pOutFlags to ** include [SQLITE_OPEN_READONLY]. Other bits in *pOutFlags may be set. ** @@ -1252,7 +1414,7 @@ typedef unsigned long long int sqlite_uint64; **
                  33. [SQLITE_OPEN_TEMP_JOURNAL] **
                  34. [SQLITE_OPEN_TRANSIENT_DB] **
                  35. [SQLITE_OPEN_SUBJOURNAL] -**
                  36. [SQLITE_OPEN_MASTER_JOURNAL] +**
                  37. [SQLITE_OPEN_SUPER_JOURNAL] **
                  38. [SQLITE_OPEN_WAL] ** )^ ** @@ -1280,14 +1442,14 @@ typedef unsigned long long int sqlite_uint64; ** ^The [SQLITE_OPEN_EXCLUSIVE] flag is always used in conjunction ** with the [SQLITE_OPEN_CREATE] flag, which are both directly ** analogous to the O_EXCL and O_CREAT flags of the POSIX open() -** API. The SQLITE_OPEN_EXCLUSIVE flag, when paired with the +** API. The SQLITE_OPEN_EXCLUSIVE flag, when paired with the ** SQLITE_OPEN_CREATE, is used to indicate that file should always ** be created, and that it is an error if it already exists. -** It is not used to indicate the file should be opened +** It is not used to indicate the file should be opened ** for exclusive access. ** ** ^At least szOsFile bytes of memory are allocated by SQLite -** to hold the [sqlite3_file] structure passed as the third +** to hold the [sqlite3_file] structure passed as the third ** argument to xOpen. The xOpen method does not have to ** allocate the structure; it should just fill it in. Note that ** the xOpen method must set the sqlite3_file.pMethods to either @@ -1300,8 +1462,14 @@ typedef unsigned long long int sqlite_uint64; ** ^The flags argument to xAccess() may be [SQLITE_ACCESS_EXISTS] ** to test for the existence of a file, or [SQLITE_ACCESS_READWRITE] to ** test whether a file is readable and writable, or [SQLITE_ACCESS_READ] -** to test whether a file is at least readable. The file can be a -** directory. +** to test whether a file is at least readable. The SQLITE_ACCESS_READ +** flag is never actually used and is not implemented in the built-in +** VFSes of SQLite. The file is named by the second argument and can be a +** directory. The xAccess method returns [SQLITE_OK] on success or some +** non-zero error code if there is an I/O error or if the name of +** the file given in the second argument is illegal. If SQLITE_OK +** is returned, then non-zero or zero is written into *pResOut to indicate +** whether or not the file is accessible. ** ** ^SQLite will always allocate at least mxPathname+1 bytes for the ** output buffer xFullPathname. The exact size of the output buffer @@ -1321,16 +1489,16 @@ typedef unsigned long long int sqlite_uint64; ** method returns a Julian Day Number for the current date and time as ** a floating point value. ** ^The xCurrentTimeInt64() method returns, as an integer, the Julian -** Day Number multiplied by 86400000 (the number of milliseconds in -** a 24-hour day). +** Day Number multiplied by 86400000 (the number of milliseconds in +** a 24-hour day). ** ^SQLite will use the xCurrentTimeInt64() method to get the current -** date and time if that method is available (if iVersion is 2 or +** date and time if that method is available (if iVersion is 2 or ** greater and the function pointer is not NULL) and will fall back ** to xCurrentTime() if xCurrentTimeInt64() is unavailable. ** -** ^The xSetSystemCall(), xGetSystemCall(), and xNestSystemCall() interfaces +** ^The xSetSystemCall(), xGetSystemCall(), and xNextSystemCall() interfaces ** are not used by the SQLite core. These optional interfaces are provided -** by some VFSes to facilitate testing of the VFS code. By overriding +** by some VFSes to facilitate testing of the VFS code. By overriding ** system calls with functions under its control, a test program can ** simulate faults and error conditions that would otherwise be difficult ** or impossible to induce. The set of system calls that can be overridden @@ -1340,47 +1508,46 @@ typedef unsigned long long int sqlite_uint64; ** from one release to the next. Applications must not attempt to access ** any of these methods if the iVersion of the VFS is less than 3. */ - typedef struct sqlite3_vfs sqlite3_vfs; - typedef void (*sqlite3_syscall_ptr)(void); - struct sqlite3_vfs - { - int iVersion; /* Structure version number (currently 3) */ - int szOsFile; /* Size of subclassed sqlite3_file */ - int mxPathname; /* Maximum file pathname length */ - sqlite3_vfs* pNext; /* Next registered VFS */ - const char* zName; /* Name of this virtual file system */ - void* pAppData; /* Pointer to application-specific data */ - int (*xOpen)(sqlite3_vfs*, const char* zName, sqlite3_file*, - int flags, int* pOutFlags); - int (*xDelete)(sqlite3_vfs*, const char* zName, int syncDir); - int (*xAccess)(sqlite3_vfs*, const char* zName, int flags, int* pResOut); - int (*xFullPathname)(sqlite3_vfs*, const char* zName, int nOut, char* zOut); - void* (*xDlOpen)(sqlite3_vfs*, const char* zFilename); - void (*xDlError)(sqlite3_vfs*, int nByte, char* zErrMsg); - void (*(*xDlSym)(sqlite3_vfs*, void*, const char* zSymbol))(void); - void (*xDlClose)(sqlite3_vfs*, void*); - int (*xRandomness)(sqlite3_vfs*, int nByte, char* zOut); - int (*xSleep)(sqlite3_vfs*, int microseconds); - int (*xCurrentTime)(sqlite3_vfs*, double*); - int (*xGetLastError)(sqlite3_vfs*, int, char*); - /* +typedef struct sqlite3_vfs sqlite3_vfs; +typedef void (*sqlite3_syscall_ptr)(void); +struct sqlite3_vfs { + int iVersion; /* Structure version number (currently 3) */ + int szOsFile; /* Size of subclassed sqlite3_file */ + int mxPathname; /* Maximum file pathname length */ + sqlite3_vfs *pNext; /* Next registered VFS */ + const char *zName; /* Name of this virtual file system */ + void *pAppData; /* Pointer to application-specific data */ + int (*xOpen)(sqlite3_vfs*, sqlite3_filename zName, sqlite3_file*, + int flags, int *pOutFlags); + int (*xDelete)(sqlite3_vfs*, const char *zName, int syncDir); + int (*xAccess)(sqlite3_vfs*, const char *zName, int flags, int *pResOut); + int (*xFullPathname)(sqlite3_vfs*, const char *zName, int nOut, char *zOut); + void *(*xDlOpen)(sqlite3_vfs*, const char *zFilename); + void (*xDlError)(sqlite3_vfs*, int nByte, char *zErrMsg); + void (*(*xDlSym)(sqlite3_vfs*,void*, const char *zSymbol))(void); + void (*xDlClose)(sqlite3_vfs*, void*); + int (*xRandomness)(sqlite3_vfs*, int nByte, char *zOut); + int (*xSleep)(sqlite3_vfs*, int microseconds); + int (*xCurrentTime)(sqlite3_vfs*, double*); + int (*xGetLastError)(sqlite3_vfs*, int, char *); + /* ** The methods above are in version 1 of the sqlite_vfs object ** definition. Those that follow are added in version 2 or later */ - int (*xCurrentTimeInt64)(sqlite3_vfs*, sqlite3_int64*); - /* + int (*xCurrentTimeInt64)(sqlite3_vfs*, sqlite3_int64*); + /* ** The methods above are in versions 1 and 2 of the sqlite_vfs object. ** Those below are for version 3 and greater. */ - int (*xSetSystemCall)(sqlite3_vfs*, const char* zName, sqlite3_syscall_ptr); - sqlite3_syscall_ptr (*xGetSystemCall)(sqlite3_vfs*, const char* zName); - const char* (*xNextSystemCall)(sqlite3_vfs*, const char* zName); - /* + int (*xSetSystemCall)(sqlite3_vfs*, const char *zName, sqlite3_syscall_ptr); + sqlite3_syscall_ptr (*xGetSystemCall)(sqlite3_vfs*, const char *zName); + const char *(*xNextSystemCall)(sqlite3_vfs*, const char *zName); + /* ** The methods above are in versions 1 through 3 of the sqlite_vfs object. ** New fields may be appended in future versions. The iVersion - ** value will increment whenever this happens. + ** value will increment whenever this happens. */ - }; +}; /* ** CAPI3REF: Flags for the xAccess VFS method @@ -1402,9 +1569,9 @@ typedef unsigned long long int sqlite_uint64; ** currently unused, though it might be used in a future release of ** SQLite. */ -#define SQLITE_ACCESS_EXISTS 0 -#define SQLITE_ACCESS_READWRITE 1 /* Used by PRAGMA temp_store_directory */ -#define SQLITE_ACCESS_READ 2 /* Unused */ +#define SQLITE_ACCESS_EXISTS 0 +#define SQLITE_ACCESS_READWRITE 1 /* Used by PRAGMA temp_store_directory */ +#define SQLITE_ACCESS_READ 2 /* Unused */ /* ** CAPI3REF: Flags for the xShmLock VFS method @@ -1422,16 +1589,16 @@ typedef unsigned long long int sqlite_uint64; ** ** ** When unlocking, the same SHARED or EXCLUSIVE flag must be supplied as -** was given on the corresponding lock. +** was given on the corresponding lock. ** ** The xShmLock method can transition between unlocked and SHARED or ** between unlocked and EXCLUSIVE. It cannot transition between SHARED ** and EXCLUSIVE. */ -#define SQLITE_SHM_UNLOCK 1 -#define SQLITE_SHM_LOCK 2 -#define SQLITE_SHM_SHARED 4 -#define SQLITE_SHM_EXCLUSIVE 8 +#define SQLITE_SHM_UNLOCK 1 +#define SQLITE_SHM_LOCK 2 +#define SQLITE_SHM_SHARED 4 +#define SQLITE_SHM_EXCLUSIVE 8 /* ** CAPI3REF: Maximum xShmLock index @@ -1441,9 +1608,10 @@ typedef unsigned long long int sqlite_uint64; ** The SQLite core will never attempt to acquire or release a ** lock outside of this range */ -#define SQLITE_SHM_NLOCK 8 +#define SQLITE_SHM_NLOCK 8 - /* + +/* ** CAPI3REF: Initialize The SQLite Library ** ** ^The sqlite3_initialize() routine initializes the @@ -1484,7 +1652,7 @@ typedef unsigned long long int sqlite_uint64; ** SQLite interfaces so that an application usually does not need to ** invoke sqlite3_initialize() directly. For example, [sqlite3_open()] ** calls sqlite3_initialize() so the SQLite library will be automatically -** initialized when [sqlite3_open()] is called if it has not be initialized +** initialized when [sqlite3_open()] is called if it has not been initialized ** already. ^However, if SQLite is compiled with the [SQLITE_OMIT_AUTOINIT] ** compile-time option, then the automatic calls to sqlite3_initialize() ** are omitted and the application must call sqlite3_initialize() directly @@ -1518,12 +1686,12 @@ typedef unsigned long long int sqlite_uint64; ** must return [SQLITE_OK] on success and some other [error code] upon ** failure. */ - SQLITE_API int sqlite3_initialize(void); - SQLITE_API int sqlite3_shutdown(void); - SQLITE_API int sqlite3_os_init(void); - SQLITE_API int sqlite3_os_end(void); +SQLITE_API int sqlite3_initialize(void); +SQLITE_API int sqlite3_shutdown(void); +SQLITE_API int sqlite3_os_init(void); +SQLITE_API int sqlite3_os_end(void); - /* +/* ** CAPI3REF: Configuring The SQLite Library ** ** The sqlite3_config() interface is used to make global configuration @@ -1536,27 +1704,31 @@ typedef unsigned long long int sqlite_uint64; ** must ensure that no other SQLite interfaces are invoked by other ** threads while sqlite3_config() is running. ** -** The sqlite3_config() interface -** may only be invoked prior to library initialization using -** [sqlite3_initialize()] or after shutdown by [sqlite3_shutdown()]. -** ^If sqlite3_config() is called after [sqlite3_initialize()] and before -** [sqlite3_shutdown()] then it will return SQLITE_MISUSE. -** Note, however, that ^sqlite3_config() can be called as part of the -** implementation of an application-defined [sqlite3_os_init()]. -** ** The first argument to sqlite3_config() is an integer ** [configuration option] that determines ** what property of SQLite is to be configured. Subsequent arguments ** vary depending on the [configuration option] ** in the first argument. ** +** For most configuration options, the sqlite3_config() interface +** may only be invoked prior to library initialization using +** [sqlite3_initialize()] or after shutdown by [sqlite3_shutdown()]. +** The exceptional configuration options that may be invoked at any time +** are called "anytime configuration options". +** ^If sqlite3_config() is called after [sqlite3_initialize()] and before +** [sqlite3_shutdown()] with a first argument that is not an anytime +** configuration option, then the sqlite3_config() call will +** return SQLITE_MISUSE. +** Note, however, that ^sqlite3_config() can be called as part of the +** implementation of an application-defined [sqlite3_os_init()]. +** ** ^When a configuration option is set, sqlite3_config() returns [SQLITE_OK]. ** ^If the option is unknown or SQLite is unable to set the option ** then this routine returns a non-zero [error code]. */ - SQLITE_API int sqlite3_config(int, ...); +SQLITE_API int sqlite3_config(int, ...); - /* +/* ** CAPI3REF: Configure database connections ** METHOD: sqlite3 ** @@ -1566,16 +1738,16 @@ typedef unsigned long long int sqlite_uint64; ** [database connection] (specified in the first argument). ** ** The second argument to sqlite3_db_config(D,V,...) is the -** [SQLITE_DBCONFIG_LOOKASIDE | configuration verb] - an integer code +** [SQLITE_DBCONFIG_LOOKASIDE | configuration verb] - an integer code ** that indicates what aspect of the [database connection] is being configured. ** Subsequent arguments vary depending on the configuration verb. ** ** ^Calls to sqlite3_db_config() return SQLITE_OK if and only if ** the call is considered successful. */ - SQLITE_API int sqlite3_db_config(sqlite3*, int op, ...); +SQLITE_API int sqlite3_db_config(sqlite3*, int op, ...); - /* +/* ** CAPI3REF: Memory Allocation Routines ** ** An instance of this object defines the interface between SQLite @@ -1584,7 +1756,7 @@ typedef unsigned long long int sqlite_uint64; ** This object is used in only one place in the SQLite interface. ** A pointer to an instance of this object is the argument to ** [sqlite3_config()] when the configuration option is -** [SQLITE_CONFIG_MALLOC] or [SQLITE_CONFIG_GETMALLOC]. +** [SQLITE_CONFIG_MALLOC] or [SQLITE_CONFIG_GETMALLOC]. ** By creating an instance of this object ** and passing it to [sqlite3_config]([SQLITE_CONFIG_MALLOC]) ** during configuration, an application can specify an alternative @@ -1614,17 +1786,17 @@ typedef unsigned long long int sqlite_uint64; ** allocators round up memory allocations at least to the next multiple ** of 8. Some allocators round up to a larger multiple or to a power of 2. ** Every memory allocation request coming in through [sqlite3_malloc()] -** or [sqlite3_realloc()] first calls xRoundup. If xRoundup returns 0, +** or [sqlite3_realloc()] first calls xRoundup. If xRoundup returns 0, ** that causes the corresponding memory allocation to fail. ** ** The xInit method initializes the memory allocator. For example, -** it might allocate any require mutexes or initialize internal data +** it might allocate any required mutexes or initialize internal data ** structures. The xShutdown method is invoked (indirectly) by ** [sqlite3_shutdown()] and should deallocate any resources acquired ** by xInit. The pAppData pointer is used as the only parameter to ** xInit and xShutdown. ** -** SQLite holds the [SQLITE_MUTEX_STATIC_MASTER] mutex when it invokes +** SQLite holds the [SQLITE_MUTEX_STATIC_MAIN] mutex when it invokes ** the xInit method, so the xInit method need not be threadsafe. The ** xShutdown method is only called from [sqlite3_shutdown()] so it does ** not need to be threadsafe either. For all other methods, SQLite @@ -1638,18 +1810,17 @@ typedef unsigned long long int sqlite_uint64; ** SQLite will never invoke xInit() more than once without an intervening ** call to xShutdown(). */ - typedef struct sqlite3_mem_methods sqlite3_mem_methods; - struct sqlite3_mem_methods - { - void* (*xMalloc)(int); /* Memory allocation function */ - void (*xFree)(void*); /* Free a prior allocation */ - void* (*xRealloc)(void*, int); /* Resize an allocation */ - int (*xSize)(void*); /* Return the size of an allocation */ - int (*xRoundup)(int); /* Round up request size to allocation size */ - int (*xInit)(void*); /* Initialize the memory allocator */ - void (*xShutdown)(void*); /* Deinitialize the memory allocator */ - void* pAppData; /* Argument to xInit() and xShutdown() */ - }; +typedef struct sqlite3_mem_methods sqlite3_mem_methods; +struct sqlite3_mem_methods { + void *(*xMalloc)(int); /* Memory allocation function */ + void (*xFree)(void*); /* Free a prior allocation */ + void *(*xRealloc)(void*,int); /* Resize an allocation */ + int (*xSize)(void*); /* Return the size of an allocation */ + int (*xRoundup)(int); /* Round up request size to allocation size */ + int (*xInit)(void*); /* Initialize the memory allocator */ + void (*xShutdown)(void*); /* Deinitialize the memory allocator */ + void *pAppData; /* Argument to xInit() and xShutdown() */ +}; /* ** CAPI3REF: Configuration Options @@ -1658,6 +1829,23 @@ typedef unsigned long long int sqlite_uint64; ** These constants are the available integer configuration options that ** can be passed as the first argument to the [sqlite3_config()] interface. ** +** Most of the configuration options for sqlite3_config() +** will only work if invoked prior to [sqlite3_initialize()] or after +** [sqlite3_shutdown()]. The few exceptions to this rule are called +** "anytime configuration options". +** ^Calling [sqlite3_config()] with a first argument that is not an +** anytime configuration option in between calls to [sqlite3_initialize()] and +** [sqlite3_shutdown()] is a no-op that returns SQLITE_MISUSE. +** +** The set of anytime configuration options can change (by insertions +** and/or deletions) from one release of SQLite to the next. +** As of SQLite version 3.42.0, the complete set of anytime configuration +** options is: +**
                      +**
                    • SQLITE_CONFIG_LOG +**
                    • SQLITE_CONFIG_PCACHE_HDRSZ +**
                    +** ** New configuration options may be added in future releases of SQLite. ** Existing configuration options might be discontinued. Applications ** should check the return code from [sqlite3_config()] to make sure that @@ -1673,7 +1861,7 @@ typedef unsigned long long int sqlite_uint64; ** by a single thread. ^If SQLite is compiled with ** the [SQLITE_THREADSAFE | SQLITE_THREADSAFE=0] compile-time option then ** it is not possible to change the [threading mode] from its default -** value of Single-thread and so [sqlite3_config()] will return +** value of Single-thread and so [sqlite3_config()] will return ** [SQLITE_ERROR] if called with the SQLITE_CONFIG_SINGLETHREAD ** configuration option. ** @@ -1708,7 +1896,7 @@ typedef unsigned long long int sqlite_uint64; ** SQLITE_CONFIG_SERIALIZED configuration option. ** ** [[SQLITE_CONFIG_MALLOC]]
                    SQLITE_CONFIG_MALLOC
                    -**
                    ^(The SQLITE_CONFIG_MALLOC option takes a single argument which is +**
                    ^(The SQLITE_CONFIG_MALLOC option takes a single argument which is ** a pointer to an instance of the [sqlite3_mem_methods] structure. ** The argument specifies ** alternative low-level memory allocation routines to be used in place of @@ -1722,25 +1910,26 @@ typedef unsigned long long int sqlite_uint64; ** The [sqlite3_mem_methods] ** structure is filled with the currently defined memory allocation routines.)^ ** This option can be used to overload the default memory allocation -** routines with a wrapper that simulations memory allocation failure or +** routines with a wrapper that simulates memory allocation failure or ** tracks memory usage, for example.
                    ** ** [[SQLITE_CONFIG_SMALL_MALLOC]]
                    SQLITE_CONFIG_SMALL_MALLOC
                    -**
                    ^The SQLITE_CONFIG_SMALL_MALLOC option takes single argument of +**
                    ^The SQLITE_CONFIG_SMALL_MALLOC option takes a single argument of ** type int, interpreted as a boolean, which if true provides a hint to ** SQLite that it should avoid large memory allocations if possible. ** SQLite will run faster if it is free to make large memory allocations, -** but some application might prefer to run slower in exchange for +** but some applications might prefer to run slower in exchange for ** guarantees about memory fragmentation that are possible if large ** allocations are avoided. This hint is normally off. **
                    ** ** [[SQLITE_CONFIG_MEMSTATUS]]
                    SQLITE_CONFIG_MEMSTATUS
                    -**
                    ^The SQLITE_CONFIG_MEMSTATUS option takes single argument of type int, +**
                    ^The SQLITE_CONFIG_MEMSTATUS option takes a single argument of type int, ** interpreted as a boolean, which enables or disables the collection of ** memory allocation statistics. ^(When memory allocation statistics are ** disabled, the following SQLite interfaces become non-operational: **
                      +**
                    • [sqlite3_hard_heap_limit64()] **
                    • [sqlite3_memory_used()] **
                    • [sqlite3_memory_highwater()] **
                    • [sqlite3_soft_heap_limit64()] @@ -1758,8 +1947,8 @@ typedef unsigned long long int sqlite_uint64; ** [[SQLITE_CONFIG_PAGECACHE]]
                      SQLITE_CONFIG_PAGECACHE
                      **
                      ^The SQLITE_CONFIG_PAGECACHE option specifies a memory pool ** that SQLite can use for the database page cache with the default page -** cache implementation. -** This configuration option is a no-op if an application-define page +** cache implementation. +** This configuration option is a no-op if an application-defined page ** cache implementation is loaded using the [SQLITE_CONFIG_PCACHE2]. ** ^There are three arguments to SQLITE_CONFIG_PAGECACHE: A pointer to ** 8-byte aligned memory (pMem), the size of each page cache line (sz), @@ -1780,13 +1969,13 @@ typedef unsigned long long int sqlite_uint64; ** ^If pMem is NULL and N is non-zero, then each database connection ** does an initial bulk allocation for page cache memory ** from [sqlite3_malloc()] sufficient for N cache lines if N is positive or -** of -1024*N bytes if N is negative, . ^If additional +** of -1024*N bytes if N is negative. ^If additional ** page cache memory is needed beyond what is provided by the initial ** allocation, then SQLite goes to [sqlite3_malloc()] separately for each ** additional cache line.
                      ** ** [[SQLITE_CONFIG_HEAP]]
                      SQLITE_CONFIG_HEAP
                      -**
                      ^The SQLITE_CONFIG_HEAP option specifies a static memory buffer +**
                      ^The SQLITE_CONFIG_HEAP option specifies a static memory buffer ** that SQLite will use for all of its dynamic memory allocation needs ** beyond those provided for by [SQLITE_CONFIG_PAGECACHE]. ** ^The SQLITE_CONFIG_HEAP option is only available if SQLite is compiled @@ -1809,7 +1998,7 @@ typedef unsigned long long int sqlite_uint64; **
                      ^(The SQLITE_CONFIG_MUTEX option takes a single argument which is a ** pointer to an instance of the [sqlite3_mutex_methods] structure. ** The argument specifies alternative low-level mutex routines to be used -** in place the mutex routines built into SQLite.)^ ^SQLite makes a copy of +** in place of the mutex routines built into SQLite.)^ ^SQLite makes a copy of ** the content of the [sqlite3_mutex_methods] structure before the call to ** [sqlite3_config()] returns. ^If SQLite is compiled with ** the [SQLITE_THREADSAFE | SQLITE_THREADSAFE=0] compile-time option then @@ -1832,30 +2021,33 @@ typedef unsigned long long int sqlite_uint64; ** ** [[SQLITE_CONFIG_LOOKASIDE]]
                      SQLITE_CONFIG_LOOKASIDE
                      **
                      ^(The SQLITE_CONFIG_LOOKASIDE option takes two arguments that determine -** the default size of lookaside memory on each [database connection]. +** the default size of [lookaside memory] on each [database connection]. ** The first argument is the -** size of each lookaside buffer slot and the second is the number of -** slots allocated to each database connection.)^ ^(SQLITE_CONFIG_LOOKASIDE -** sets the default lookaside size. The [SQLITE_DBCONFIG_LOOKASIDE] -** option to [sqlite3_db_config()] can be used to change the lookaside -** configuration on individual connections.)^
                      +** size of each lookaside buffer slot ("sz") and the second is the number of +** slots allocated to each database connection ("cnt").)^ +** ^(SQLITE_CONFIG_LOOKASIDE sets the default lookaside size. +** The [SQLITE_DBCONFIG_LOOKASIDE] option to [sqlite3_db_config()] can +** be used to change the lookaside configuration on individual connections.)^ +** The [-DSQLITE_DEFAULT_LOOKASIDE] option can be used to change the +** default lookaside configuration at compile-time. +**
                    ** ** [[SQLITE_CONFIG_PCACHE2]]
                    SQLITE_CONFIG_PCACHE2
                    -**
                    ^(The SQLITE_CONFIG_PCACHE2 option takes a single argument which is +**
                    ^(The SQLITE_CONFIG_PCACHE2 option takes a single argument which is ** a pointer to an [sqlite3_pcache_methods2] object. This object specifies ** the interface to a custom page cache implementation.)^ ** ^SQLite makes a copy of the [sqlite3_pcache_methods2] object.
                    ** ** [[SQLITE_CONFIG_GETPCACHE2]]
                    SQLITE_CONFIG_GETPCACHE2
                    **
                    ^(The SQLITE_CONFIG_GETPCACHE2 option takes a single argument which -** is a pointer to an [sqlite3_pcache_methods2] object. SQLite copies of +** is a pointer to an [sqlite3_pcache_methods2] object. SQLite copies off ** the current page cache implementation into that object.)^
                    ** ** [[SQLITE_CONFIG_LOG]]
                    SQLITE_CONFIG_LOG
                    **
                    The SQLITE_CONFIG_LOG option is used to configure the SQLite ** global [error log]. ** (^The SQLITE_CONFIG_LOG option takes two arguments: a pointer to a -** function with a call signature of void(*)(void*,int,const char*), +** function with a call signature of void(*)(void*,int,const char*), ** and a pointer to void. ^If the function pointer is not NULL, it is ** invoked by [sqlite3_log()] to process each logging event. ^If the ** function pointer is NULL, the [sqlite3_log()] interface becomes a no-op. @@ -1865,7 +2057,7 @@ typedef unsigned long long int sqlite_uint64; ** the logger function is a copy of the first parameter to the corresponding ** [sqlite3_log()] call and is intended to be a [result code] or an ** [extended result code]. ^The third parameter passed to the logger is -** log message after formatting via [sqlite3_snprintf()]. +** a log message after formatting via [sqlite3_snprintf()]. ** The SQLite logging interface is not reentrant; the logger function ** supplied by the application must not invoke any SQLite interface. ** In a multi-threaded application, the application-defined logger @@ -1964,7 +2156,7 @@ typedef unsigned long long int sqlite_uint64; ** [[SQLITE_CONFIG_STMTJRNL_SPILL]] **
                    SQLITE_CONFIG_STMTJRNL_SPILL **
                    ^The SQLITE_CONFIG_STMTJRNL_SPILL option takes a single parameter which -** becomes the [statement journal] spill-to-disk threshold. +** becomes the [statement journal] spill-to-disk threshold. ** [Statement journals] are held in memory until their size (in bytes) ** exceeds this threshold, at which point they are written to disk. ** Or if the threshold is -1, statement journals are always held @@ -1986,8 +2178,8 @@ typedef unsigned long long int sqlite_uint64; ** than the configured sorter-reference size threshold - then a reference ** is stored in each sorted record and the required column values loaded ** from the database as records are returned in sorted order. The default -** value for this option is to never use this optimization. Specifying a -** negative value for this option restores the default behaviour. +** value for this option is to never use this optimization. Specifying a +** negative value for this option restores the default behavior. ** This option is only available if SQLite is compiled with the ** [SQLITE_ENABLE_SORTER_REFERENCES] compile-time option. ** @@ -2001,43 +2193,68 @@ typedef unsigned long long int sqlite_uint64; ** configuration setting is never used, then the default maximum is determined ** by the [SQLITE_MEMDB_DEFAULT_MAXSIZE] compile-time option. If that ** compile-time option is not set, then the default maximum is 1073741824. +** +** [[SQLITE_CONFIG_ROWID_IN_VIEW]] +**
                    SQLITE_CONFIG_ROWID_IN_VIEW +**
                    The SQLITE_CONFIG_ROWID_IN_VIEW option enables or disables the ability +** for VIEWs to have a ROWID. The capability can only be enabled if SQLite is +** compiled with -DSQLITE_ALLOW_ROWID_IN_VIEW, in which case the capability +** defaults to on. This configuration option queries the current setting or +** changes the setting to off or on. The argument is a pointer to an integer. +** If that integer initially holds a value of 1, then the ability for VIEWs to +** have ROWIDs is activated. If the integer initially holds zero, then the +** ability is deactivated. Any other initial value for the integer leaves the +** setting unchanged. After changes, if any, the integer is written with +** a 1 or 0, if the ability for VIEWs to have ROWIDs is on or off. If SQLite +** is compiled without -DSQLITE_ALLOW_ROWID_IN_VIEW (which is the usual and +** recommended case) then the integer is always filled with zero, regardless +** if its initial value. ** */ -#define SQLITE_CONFIG_SINGLETHREAD 1 /* nil */ -#define SQLITE_CONFIG_MULTITHREAD 2 /* nil */ -#define SQLITE_CONFIG_SERIALIZED 3 /* nil */ -#define SQLITE_CONFIG_MALLOC 4 /* sqlite3_mem_methods* */ -#define SQLITE_CONFIG_GETMALLOC 5 /* sqlite3_mem_methods* */ -#define SQLITE_CONFIG_SCRATCH 6 /* No longer used */ -#define SQLITE_CONFIG_PAGECACHE 7 /* void*, int sz, int N */ -#define SQLITE_CONFIG_HEAP 8 /* void*, int nByte, int min */ -#define SQLITE_CONFIG_MEMSTATUS 9 /* boolean */ -#define SQLITE_CONFIG_MUTEX 10 /* sqlite3_mutex_methods* */ -#define SQLITE_CONFIG_GETMUTEX 11 /* sqlite3_mutex_methods* */ -/* previously SQLITE_CONFIG_CHUNKALLOC 12 which is now unused. */ -#define SQLITE_CONFIG_LOOKASIDE 13 /* int int */ -#define SQLITE_CONFIG_PCACHE 14 /* no-op */ -#define SQLITE_CONFIG_GETPCACHE 15 /* no-op */ -#define SQLITE_CONFIG_LOG 16 /* xFunc, void* */ -#define SQLITE_CONFIG_URI 17 /* int */ -#define SQLITE_CONFIG_PCACHE2 18 /* sqlite3_pcache_methods2* */ -#define SQLITE_CONFIG_GETPCACHE2 19 /* sqlite3_pcache_methods2* */ -#define SQLITE_CONFIG_COVERING_INDEX_SCAN 20 /* int */ -#define SQLITE_CONFIG_SQLLOG 21 /* xSqllog, void* */ -#define SQLITE_CONFIG_MMAP_SIZE 22 /* sqlite3_int64, sqlite3_int64 */ -#define SQLITE_CONFIG_WIN32_HEAPSIZE 23 /* int nByte */ -#define SQLITE_CONFIG_PCACHE_HDRSZ 24 /* int *psz */ -#define SQLITE_CONFIG_PMASZ 25 /* unsigned int szPma */ -#define SQLITE_CONFIG_STMTJRNL_SPILL 26 /* int nByte */ -#define SQLITE_CONFIG_SMALL_MALLOC 27 /* boolean */ -#define SQLITE_CONFIG_SORTERREF_SIZE 28 /* int nByte */ -#define SQLITE_CONFIG_MEMDB_MAXSIZE 29 /* sqlite3_int64 */ +#define SQLITE_CONFIG_SINGLETHREAD 1 /* nil */ +#define SQLITE_CONFIG_MULTITHREAD 2 /* nil */ +#define SQLITE_CONFIG_SERIALIZED 3 /* nil */ +#define SQLITE_CONFIG_MALLOC 4 /* sqlite3_mem_methods* */ +#define SQLITE_CONFIG_GETMALLOC 5 /* sqlite3_mem_methods* */ +#define SQLITE_CONFIG_SCRATCH 6 /* No longer used */ +#define SQLITE_CONFIG_PAGECACHE 7 /* void*, int sz, int N */ +#define SQLITE_CONFIG_HEAP 8 /* void*, int nByte, int min */ +#define SQLITE_CONFIG_MEMSTATUS 9 /* boolean */ +#define SQLITE_CONFIG_MUTEX 10 /* sqlite3_mutex_methods* */ +#define SQLITE_CONFIG_GETMUTEX 11 /* sqlite3_mutex_methods* */ +/* previously SQLITE_CONFIG_CHUNKALLOC 12 which is now unused. */ +#define SQLITE_CONFIG_LOOKASIDE 13 /* int int */ +#define SQLITE_CONFIG_PCACHE 14 /* no-op */ +#define SQLITE_CONFIG_GETPCACHE 15 /* no-op */ +#define SQLITE_CONFIG_LOG 16 /* xFunc, void* */ +#define SQLITE_CONFIG_URI 17 /* int */ +#define SQLITE_CONFIG_PCACHE2 18 /* sqlite3_pcache_methods2* */ +#define SQLITE_CONFIG_GETPCACHE2 19 /* sqlite3_pcache_methods2* */ +#define SQLITE_CONFIG_COVERING_INDEX_SCAN 20 /* int */ +#define SQLITE_CONFIG_SQLLOG 21 /* xSqllog, void* */ +#define SQLITE_CONFIG_MMAP_SIZE 22 /* sqlite3_int64, sqlite3_int64 */ +#define SQLITE_CONFIG_WIN32_HEAPSIZE 23 /* int nByte */ +#define SQLITE_CONFIG_PCACHE_HDRSZ 24 /* int *psz */ +#define SQLITE_CONFIG_PMASZ 25 /* unsigned int szPma */ +#define SQLITE_CONFIG_STMTJRNL_SPILL 26 /* int nByte */ +#define SQLITE_CONFIG_SMALL_MALLOC 27 /* boolean */ +#define SQLITE_CONFIG_SORTERREF_SIZE 28 /* int nByte */ +#define SQLITE_CONFIG_MEMDB_MAXSIZE 29 /* sqlite3_int64 */ +#define SQLITE_CONFIG_ROWID_IN_VIEW 30 /* int* */ /* ** CAPI3REF: Database Connection Configuration Options ** ** These constants are the available integer configuration options that -** can be passed as the second argument to the [sqlite3_db_config()] interface. +** can be passed as the second parameter to the [sqlite3_db_config()] interface. +** +** The [sqlite3_db_config()] interface is a var-args function. It takes a +** variable number of parameters, though always at least two. The number of +** parameters passed into sqlite3_db_config() depends on which of these +** constants is given as the second parameter. This documentation page +** refers to parameters beyond the second as "arguments". Thus, when this +** page says "the N-th argument" it means "the N-th parameter past the +** configuration option" or "the (N+2)-th parameter to sqlite3_db_config()". ** ** New configuration options may be added in future releases of SQLite. ** Existing configuration options might be discontinued. Applications @@ -2049,31 +2266,58 @@ typedef unsigned long long int sqlite_uint64; **
                    ** [[SQLITE_DBCONFIG_LOOKASIDE]] **
                    SQLITE_DBCONFIG_LOOKASIDE
                    -**
                    ^This option takes three additional arguments that determine the -** [lookaside memory allocator] configuration for the [database connection]. -** ^The first argument (the third parameter to [sqlite3_db_config()] is a +**
                    The SQLITE_DBCONFIG_LOOKASIDE option is used to adjust the +** configuration of the [lookaside memory allocator] within a database +** connection. +** The arguments to the SQLITE_DBCONFIG_LOOKASIDE option are not +** in the [DBCONFIG arguments|usual format]. +** The SQLITE_DBCONFIG_LOOKASIDE option takes three arguments, not two, +** so that a call to [sqlite3_db_config()] that uses SQLITE_DBCONFIG_LOOKASIDE +** should have a total of five parameters. +**
                      +**
                    1. The first argument ("buf") is a ** pointer to a memory buffer to use for lookaside memory. -** ^The first argument after the SQLITE_DBCONFIG_LOOKASIDE verb -** may be NULL in which case SQLite will allocate the -** lookaside buffer itself using [sqlite3_malloc()]. ^The second argument is the -** size of each lookaside buffer slot. ^The third argument is the number of -** slots. The size of the buffer in the first argument must be greater than -** or equal to the product of the second and third arguments. The buffer -** must be aligned to an 8-byte boundary. ^If the second argument to -** SQLITE_DBCONFIG_LOOKASIDE is not a multiple of 8, it is internally -** rounded down to the next smaller multiple of 8. ^(The lookaside memory +** The first argument may be NULL in which case SQLite will allocate the +** lookaside buffer itself using [sqlite3_malloc()]. +**

                    2. The second argument ("sz") is the +** size of each lookaside buffer slot. Lookaside is disabled if "sz" +** is less than 8. The "sz" argument should be a multiple of 8 less than +** 65536. If "sz" does not meet this constraint, it is reduced in size until +** it does. +**

                    3. The third argument ("cnt") is the number of slots. +** Lookaside is disabled if "cnt"is less than 1. +* The "cnt" value will be reduced, if necessary, so +** that the product of "sz" and "cnt" does not exceed 2,147,418,112. The "cnt" +** parameter is usually chosen so that the product of "sz" and "cnt" is less +** than 1,000,000. +**

                    +**

                    If the "buf" argument is not NULL, then it must +** point to a memory buffer with a size that is greater than +** or equal to the product of "sz" and "cnt". +** The buffer must be aligned to an 8-byte boundary. +** The lookaside memory ** configuration for a database connection can only be changed when that ** connection is not currently using lookaside memory, or in other words -** when the "current value" returned by -** [sqlite3_db_status](D,[SQLITE_CONFIG_LOOKASIDE],...) is zero. +** when the value returned by [SQLITE_DBSTATUS_LOOKASIDE_USED] is zero. ** Any attempt to change the lookaside memory configuration when lookaside -** memory is in use leaves the configuration unchanged and returns -** [SQLITE_BUSY].)^

                    +** memory is in use leaves the configuration unchanged and returns +** [SQLITE_BUSY]. +** If the "buf" argument is NULL and an attempt +** to allocate memory based on "sz" and "cnt" fails, then +** lookaside is silently disabled. +**

                    +** The [SQLITE_CONFIG_LOOKASIDE] configuration option can be used to set the +** default lookaside configuration at initialization. The +** [-DSQLITE_DEFAULT_LOOKASIDE] option can be used to set the default lookaside +** configuration at compile-time. Typical values for lookaside are 1200 for +** "sz" and 40 to 100 for "cnt". +**

                    ** ** [[SQLITE_DBCONFIG_ENABLE_FKEY]] **
                    SQLITE_DBCONFIG_ENABLE_FKEY
                    **
                    ^This option is used to enable or disable the enforcement of -** [foreign key constraints]. There should be two additional arguments. +** [foreign key constraints]. This is the same setting that is +** enabled or disabled by the [PRAGMA foreign_keys] statement. ** The first argument is an integer which is 0 to disable FK enforcement, ** positive to enable FK enforcement or negative to leave FK enforcement ** unchanged. The second parameter is a pointer to an integer into which @@ -2090,21 +2334,47 @@ typedef unsigned long long int sqlite_uint64; ** The second parameter is a pointer to an integer into which ** is written 0 or 1 to indicate whether triggers are disabled or enabled ** following this call. The second parameter may be a NULL pointer, in -** which case the trigger setting is not reported back.
                    +** which case the trigger setting is not reported back. +** +**

                    Originally this option disabled all triggers. ^(However, since +** SQLite version 3.35.0, TEMP triggers are still allowed even if +** this option is off. So, in other words, this option now only disables +** triggers in the main database schema or in the schemas of [ATTACH]-ed +** databases.)^ +** +** [[SQLITE_DBCONFIG_ENABLE_VIEW]] +**

                    SQLITE_DBCONFIG_ENABLE_VIEW
                    +**
                    ^This option is used to enable or disable [CREATE VIEW | views]. +** There must be two additional arguments. +** The first argument is an integer which is 0 to disable views, +** positive to enable views or negative to leave the setting unchanged. +** The second parameter is a pointer to an integer into which +** is written 0 or 1 to indicate whether views are disabled or enabled +** following this call. The second parameter may be a NULL pointer, in +** which case the view setting is not reported back. +** +**

                    Originally this option disabled all views. ^(However, since +** SQLite version 3.35.0, TEMP views are still allowed even if +** this option is off. So, in other words, this option now only disables +** views in the main database schema or in the schemas of ATTACH-ed +** databases.)^

                    ** ** [[SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER]] **
                    SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER
                    -**
                    ^This option is used to enable or disable the -** [fts3_tokenizer()] function which is part of the -** [FTS3] full-text search engine extension. -** There should be two additional arguments. -** The first argument is an integer which is 0 to disable fts3_tokenizer() or -** positive to enable fts3_tokenizer() or negative to leave the setting -** unchanged. -** The second parameter is a pointer to an integer into which -** is written 0 or 1 to indicate whether fts3_tokenizer is disabled or enabled -** following this call. The second parameter may be a NULL pointer, in -** which case the new setting is not reported back.
                    +**
                    ^This option is used to enable or disable using the +** [fts3_tokenizer()] function - part of the [FTS3] full-text search engine +** extension - without using bound parameters as the parameters. Doing so +** is disabled by default. There must be two additional arguments. The first +** argument is an integer. If it is passed 0, then using fts3_tokenizer() +** without bound parameters is disabled. If it is passed a positive value, +** then calling fts3_tokenizer without bound parameters is enabled. If it +** is passed a negative value, this setting is not modified - this can be +** used to query for the current setting. The second parameter is a pointer +** to an integer into which is written 0 or 1 to indicate the current value +** of this setting (after it is modified, if applicable). The second +** parameter may be a NULL pointer, in which case the value of the setting +** is not reported back. Refer to [FTS3] documentation for further details. +**
                    ** ** [[SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION]] **
                    SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION
                    @@ -2112,12 +2382,12 @@ typedef unsigned long long int sqlite_uint64; ** interface independently of the [load_extension()] SQL function. ** The [sqlite3_enable_load_extension()] API enables or disables both the ** C-API [sqlite3_load_extension()] and the SQL function [load_extension()]. -** There should be two additional arguments. +** There must be two additional arguments. ** When the first argument to this interface is 1, then only the C-API is ** enabled and the SQL function remains disabled. If the first argument to ** this interface is 0, then both the C-API and the SQL function are disabled. -** If the first argument is -1, then no changes are made to state of either the -** C-API or the SQL function. +** If the first argument is -1, then no changes are made to the state of either +** the C-API or the SQL function. ** The second parameter is a pointer to an integer into which ** is written 0 or 1 to indicate whether [sqlite3_load_extension()] interface ** is disabled or enabled following this call. The second parameter may @@ -2126,23 +2396,30 @@ typedef unsigned long long int sqlite_uint64; ** ** [[SQLITE_DBCONFIG_MAINDBNAME]]
                    SQLITE_DBCONFIG_MAINDBNAME
                    **
                    ^This option is used to change the name of the "main" database -** schema. ^The sole argument is a pointer to a constant UTF8 string -** which will become the new schema name in place of "main". ^SQLite -** does not make a copy of the new main schema name string, so the application -** must ensure that the argument passed into this DBCONFIG option is unchanged -** until after the database connection closes. +** schema. This option does not follow the +** [DBCONFIG arguments|usual SQLITE_DBCONFIG argument format]. +** This option takes exactly one additional argument so that the +** [sqlite3_db_config()] call has a total of three parameters. The +** extra argument must be a pointer to a constant UTF8 string which +** will become the new schema name in place of "main". ^SQLite does +** not make a copy of the new main schema name string, so the application +** must ensure that the argument passed into SQLITE_DBCONFIG MAINDBNAME +** is unchanged until after the database connection closes. **
                    ** -** [[SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE]] +** [[SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE]] **
                    SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE
                    -**
                    Usually, when a database in wal mode is closed or detached from a -** database handle, SQLite checks if this will mean that there are now no -** connections at all to the database. If so, it performs a checkpoint -** operation before closing the connection. This option may be used to -** override this behaviour. The first parameter passed to this operation -** is an integer - positive to disable checkpoints-on-close, or zero (the -** default) to enable them, and negative to leave the setting unchanged. -** The second parameter is a pointer to an integer +**
                    Usually, when a database in [WAL mode] is closed or detached from a +** database handle, SQLite checks if if there are other connections to the +** same database, and if there are no other database connection (if the +** connection being closed is the last open connection to the database), +** then SQLite performs a [checkpoint] before closing the connection and +** deletes the WAL file. The SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE option can +** be used to override that behavior. The first argument passed to this +** operation (the third parameter to [sqlite3_db_config()]) is an integer +** which is positive to disable checkpoints-on-close, or zero (the default) +** to enable them, and negative to leave the setting unchanged. +** The second argument (the fourth parameter) is a pointer to an integer ** into which is written 0 or 1 to indicate whether checkpoints-on-close ** have been disabled - 0 if they are not disabled, 1 if they are. **
                    @@ -2156,7 +2433,7 @@ typedef unsigned long long int sqlite_uint64; ** slower. But the QPSG has the advantage of more predictable behavior. With ** the QPSG active, SQLite will always use the same query plan in the field as ** was used during testing in the lab. -** The first argument to this setting is an integer which is 0 to disable +** The first argument to this setting is an integer which is 0 to disable ** the QPSG, positive to enable QPSG, or negative to leave the setting ** unchanged. The second parameter is a pointer to an integer into which ** is written 0 or 1 to indicate whether the QPSG is disabled or enabled @@ -2164,15 +2441,15 @@ typedef unsigned long long int sqlite_uint64; ** ** ** [[SQLITE_DBCONFIG_TRIGGER_EQP]]
                    SQLITE_DBCONFIG_TRIGGER_EQP
                    -**
                    By default, the output of EXPLAIN QUERY PLAN commands does not +**
                    By default, the output of EXPLAIN QUERY PLAN commands does not ** include output for any operations performed by trigger programs. This ** option is used to set or clear (the default) a flag that governs this ** behavior. The first parameter passed to this operation is an integer - ** positive to enable output for trigger programs, or zero to disable it, ** or negative to leave the setting unchanged. -** The second parameter is a pointer to an integer into which is written -** 0 or 1 to indicate whether output-for-triggers has been disabled - 0 if -** it is not disabled, 1 if it is. +** The second parameter is a pointer to an integer into which is written +** 0 or 1 to indicate whether output-for-triggers has been disabled - 0 if +** it is not disabled, 1 if it is. **
                    ** ** [[SQLITE_DBCONFIG_RESET_DATABASE]]
                    SQLITE_DBCONFIG_RESET_DATABASE
                    @@ -2186,23 +2463,29 @@ typedef unsigned long long int sqlite_uint64; ** database, or calling sqlite3_table_column_metadata(), ignoring any ** errors. This step is only necessary if the application desires to keep ** the database in WAL mode after the reset if it was in WAL mode before -** the reset. +** the reset. **
                  39. sqlite3_db_config(db, SQLITE_DBCONFIG_RESET_DATABASE, 1, 0); **
                  40. [sqlite3_exec](db, "[VACUUM]", 0, 0, 0); **
                  41. sqlite3_db_config(db, SQLITE_DBCONFIG_RESET_DATABASE, 0, 0); **
                  ** Because resetting a database is destructive and irreversible, the -** process requires the use of this obscure API and multiple steps to help -** ensure that it does not happen by accident. +** process requires the use of this obscure API and multiple steps to +** help ensure that it does not happen by accident. Because this +** feature must be capable of resetting corrupt databases, and +** shutting down virtual tables may require access to that corrupt +** storage, the library must abandon any installed virtual tables +** without calling their xDestroy() methods. ** ** [[SQLITE_DBCONFIG_DEFENSIVE]]
                  SQLITE_DBCONFIG_DEFENSIVE
                  **
                  The SQLITE_DBCONFIG_DEFENSIVE option activates or deactivates the ** "defensive" flag for a database connection. When the defensive -** flag is enabled, language features that allow ordinary SQL to +** flag is enabled, language features that allow ordinary SQL to ** deliberately corrupt the database file are disabled. The disabled ** features include but are not limited to the following: **
                    **
                  • The [PRAGMA writable_schema=ON] statement. +**
                  • The [PRAGMA journal_mode=OFF] statement. +**
                  • The [PRAGMA schema_version=N] statement. **
                  • Writes to the [sqlite_dbpage] virtual table. **
                  • Direct writes to [shadow tables]. **
                  @@ -2212,29 +2495,234 @@ typedef unsigned long long int sqlite_uint64; **
                  The SQLITE_DBCONFIG_WRITABLE_SCHEMA option activates or deactivates the ** "writable_schema" flag. This has the same effect and is logically equivalent ** to setting [PRAGMA writable_schema=ON] or [PRAGMA writable_schema=OFF]. -** The first argument to this setting is an integer which is 0 to disable +** The first argument to this setting is an integer which is 0 to disable ** the writable_schema, positive to enable writable_schema, or negative to ** leave the setting unchanged. The second parameter is a pointer to an ** integer into which is written 0 or 1 to indicate whether the writable_schema ** is enabled or disabled following this call. **
                  +** +** [[SQLITE_DBCONFIG_LEGACY_ALTER_TABLE]] +**
                  SQLITE_DBCONFIG_LEGACY_ALTER_TABLE
                  +**
                  The SQLITE_DBCONFIG_LEGACY_ALTER_TABLE option activates or deactivates +** the legacy behavior of the [ALTER TABLE RENAME] command such that it +** behaves as it did prior to [version 3.24.0] (2018-06-04). See the +** "Compatibility Notice" on the [ALTER TABLE RENAME documentation] for +** additional information. This feature can also be turned on and off +** using the [PRAGMA legacy_alter_table] statement. +**
                  +** +** [[SQLITE_DBCONFIG_DQS_DML]] +**
                  SQLITE_DBCONFIG_DQS_DML
                  +**
                  The SQLITE_DBCONFIG_DQS_DML option activates or deactivates +** the legacy [double-quoted string literal] misfeature for DML statements +** only, that is DELETE, INSERT, SELECT, and UPDATE statements. The +** default value of this setting is determined by the [-DSQLITE_DQS] +** compile-time option. +**
                  +** +** [[SQLITE_DBCONFIG_DQS_DDL]] +**
                  SQLITE_DBCONFIG_DQS_DDL
                  +**
                  The SQLITE_DBCONFIG_DQS option activates or deactivates +** the legacy [double-quoted string literal] misfeature for DDL statements, +** such as CREATE TABLE and CREATE INDEX. The +** default value of this setting is determined by the [-DSQLITE_DQS] +** compile-time option. +**
                  +** +** [[SQLITE_DBCONFIG_TRUSTED_SCHEMA]] +**
                  SQLITE_DBCONFIG_TRUSTED_SCHEMA
                  +**
                  The SQLITE_DBCONFIG_TRUSTED_SCHEMA option tells SQLite to +** assume that database schemas are untainted by malicious content. +** When the SQLITE_DBCONFIG_TRUSTED_SCHEMA option is disabled, SQLite +** takes additional defensive steps to protect the application from harm +** including: +**
                    +**
                  • Prohibit the use of SQL functions inside triggers, views, +** CHECK constraints, DEFAULT clauses, expression indexes, +** partial indexes, or generated columns +** unless those functions are tagged with [SQLITE_INNOCUOUS]. +**
                  • Prohibit the use of virtual tables inside of triggers or views +** unless those virtual tables are tagged with [SQLITE_VTAB_INNOCUOUS]. +**
                  +** This setting defaults to "on" for legacy compatibility, however +** all applications are advised to turn it off if possible. This setting +** can also be controlled using the [PRAGMA trusted_schema] statement. +**
                  +** +** [[SQLITE_DBCONFIG_LEGACY_FILE_FORMAT]] +**
                  SQLITE_DBCONFIG_LEGACY_FILE_FORMAT
                  +**
                  The SQLITE_DBCONFIG_LEGACY_FILE_FORMAT option activates or deactivates +** the legacy file format flag. When activated, this flag causes all newly +** created database files to have a schema format version number (the 4-byte +** integer found at offset 44 into the database header) of 1. This in turn +** means that the resulting database file will be readable and writable by +** any SQLite version back to 3.0.0 ([dateof:3.0.0]). Without this setting, +** newly created databases are generally not understandable by SQLite versions +** prior to 3.3.0 ([dateof:3.3.0]). As these words are written, there +** is now scarcely any need to generate database files that are compatible +** all the way back to version 3.0.0, and so this setting is of little +** practical use, but is provided so that SQLite can continue to claim the +** ability to generate new database files that are compatible with version +** 3.0.0. +**

                  Note that when the SQLITE_DBCONFIG_LEGACY_FILE_FORMAT setting is on, +** the [VACUUM] command will fail with an obscure error when attempting to +** process a table with generated columns and a descending index. This is +** not considered a bug since SQLite versions 3.3.0 and earlier do not support +** either generated columns or descending indexes. +**

                  +** +** [[SQLITE_DBCONFIG_STMT_SCANSTATUS]] +**
                  SQLITE_DBCONFIG_STMT_SCANSTATUS
                  +**
                  The SQLITE_DBCONFIG_STMT_SCANSTATUS option is only useful in +** [SQLITE_ENABLE_STMT_SCANSTATUS] builds. In this case, it sets or clears +** a flag that enables collection of run-time performance statistics +** used by [sqlite3_stmt_scanstatus_v2()] and the [nexec and ncycle] +** columns of the [bytecode virtual table]. +** For statistics to be collected, the flag must be set on +** the database handle both when the SQL statement is +** [sqlite3_prepare|prepared] and when it is [sqlite3_step|stepped]. +** The flag is set (collection of statistics is enabled) by default. +**

                  This option takes two arguments: an integer and a pointer to +** an integer. The first argument is 1, 0, or -1 to enable, disable, or +** leave unchanged the statement scanstatus option. If the second argument +** is not NULL, then the value of the statement scanstatus setting after +** processing the first argument is written into the integer that the second +** argument points to. +**

                  +** +** [[SQLITE_DBCONFIG_REVERSE_SCANORDER]] +**
                  SQLITE_DBCONFIG_REVERSE_SCANORDER
                  +**
                  The SQLITE_DBCONFIG_REVERSE_SCANORDER option changes the default order +** in which tables and indexes are scanned so that the scans start at the end +** and work toward the beginning rather than starting at the beginning and +** working toward the end. Setting SQLITE_DBCONFIG_REVERSE_SCANORDER is the +** same as setting [PRAGMA reverse_unordered_selects].

                  This option takes +** two arguments which are an integer and a pointer to an integer. The first +** argument is 1, 0, or -1 to enable, disable, or leave unchanged the +** reverse scan order flag, respectively. If the second argument is not NULL, +** then 0 or 1 is written into the integer that the second argument points to +** depending on if the reverse scan order flag is set after processing the +** first argument. +**

                  +** +** [[SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE]] +**
                  SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE
                  +**
                  The SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE option enables or disables +** the ability of the [ATTACH DATABASE] SQL command to create a new database +** file if the database filed named in the ATTACH command does not already +** exist. This ability of ATTACH to create a new database is enabled by +** default. Applications can disable or reenable the ability for ATTACH to +** create new database files using this DBCONFIG option.

                  +** This option takes two arguments which are an integer and a pointer +** to an integer. The first argument is 1, 0, or -1 to enable, disable, or +** leave unchanged the attach-create flag, respectively. If the second +** argument is not NULL, then 0 or 1 is written into the integer that the +** second argument points to depending on if the attach-create flag is set +** after processing the first argument. +**

                  +** +** [[SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE]] +**
                  SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE
                  +**
                  The SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE option enables or disables the +** ability of the [ATTACH DATABASE] SQL command to open a database for writing. +** This capability is enabled by default. Applications can disable or +** reenable this capability using the current DBCONFIG option. If +** this capability is disabled, the [ATTACH] command will still work, +** but the database will be opened read-only. If this option is disabled, +** then the ability to create a new database using [ATTACH] is also disabled, +** regardless of the value of the [SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE] +** option.

                  +** This option takes two arguments which are an integer and a pointer +** to an integer. The first argument is 1, 0, or -1 to enable, disable, or +** leave unchanged the ability to ATTACH another database for writing, +** respectively. If the second argument is not NULL, then 0 or 1 is written +** into the integer to which the second argument points, depending on whether +** the ability to ATTACH a read/write database is enabled or disabled +** after processing the first argument. +**

                  +** +** [[SQLITE_DBCONFIG_ENABLE_COMMENTS]] +**
                  SQLITE_DBCONFIG_ENABLE_COMMENTS
                  +**
                  The SQLITE_DBCONFIG_ENABLE_COMMENTS option enables or disables the +** ability to include comments in SQL text. Comments are enabled by default. +** An application can disable or reenable comments in SQL text using this +** DBCONFIG option.

                  +** This option takes two arguments which are an integer and a pointer +** to an integer. The first argument is 1, 0, or -1 to enable, disable, or +** leave unchanged the ability to use comments in SQL text, +** respectively. If the second argument is not NULL, then 0 or 1 is written +** into the integer that the second argument points to depending on if +** comments are allowed in SQL text after processing the first argument. +**

                  +** +** [[SQLITE_DBCONFIG_FP_DIGITS]] +**
                  SQLITE_DBCONFIG_FP_DIGITS
                  +**
                  The SQLITE_DBCONFIG_FP_DIGITS setting is a small integer that determines +** the number of significant digits that SQLite will attempt to preserve when +** converting floating point numbers (IEEE 754 "doubles") into text. The +** default value 17, as of SQLite version 3.52.0. The value was 15 in all +** prior versions.

                  +** This option takes two arguments which are an integer and a pointer +** to an integer. The first argument is a small integer, between 3 and 23, or +** zero. The FP_DIGITS setting is changed to that small integer, or left +** unaltered if the first argument is zero or out of range. The second argument +** is a pointer to an integer. If the pointer is not NULL, then the value of +** the FP_DIGITS setting, after possibly being modified by the first +** arguments, is written into the integer to which the second argument points. +**

                  +** ** -*/ -#define SQLITE_DBCONFIG_MAINDBNAME 1000 /* const char* */ -#define SQLITE_DBCONFIG_LOOKASIDE 1001 /* void* int int */ -#define SQLITE_DBCONFIG_ENABLE_FKEY 1002 /* int int* */ -#define SQLITE_DBCONFIG_ENABLE_TRIGGER 1003 /* int int* */ +** +** [[DBCONFIG arguments]]

                  Arguments To SQLITE_DBCONFIG Options

                  +** +**

                  Most of the SQLITE_DBCONFIG options take two arguments, so that the +** overall call to [sqlite3_db_config()] has a total of four parameters. +** The first argument (the third parameter to sqlite3_db_config()) is +** an integer. +** The second argument is a pointer to an integer. If the first argument is 1, +** then the option becomes enabled. If the first integer argument is 0, +** then the option is disabled. +** If the first argument is -1, then the option setting +** is unchanged. The second argument, the pointer to an integer, may be NULL. +** If the second argument is not NULL, then a value of 0 or 1 is written into +** the integer to which the second argument points, depending on whether the +** setting is disabled or enabled after applying any changes specified by +** the first argument. +** +**

                  While most SQLITE_DBCONFIG options use the argument format +** described in the previous paragraph, the [SQLITE_DBCONFIG_MAINDBNAME], +** [SQLITE_DBCONFIG_LOOKASIDE], and [SQLITE_DBCONFIG_FP_DIGITS] options +** are different. See the documentation of those exceptional options for +** details. +*/ +#define SQLITE_DBCONFIG_MAINDBNAME 1000 /* const char* */ +#define SQLITE_DBCONFIG_LOOKASIDE 1001 /* void* int int */ +#define SQLITE_DBCONFIG_ENABLE_FKEY 1002 /* int int* */ +#define SQLITE_DBCONFIG_ENABLE_TRIGGER 1003 /* int int* */ #define SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER 1004 /* int int* */ #define SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION 1005 /* int int* */ -#define SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE 1006 /* int int* */ -#define SQLITE_DBCONFIG_ENABLE_QPSG 1007 /* int int* */ -#define SQLITE_DBCONFIG_TRIGGER_EQP 1008 /* int int* */ -#define SQLITE_DBCONFIG_RESET_DATABASE 1009 /* int int* */ -#define SQLITE_DBCONFIG_DEFENSIVE 1010 /* int int* */ -#define SQLITE_DBCONFIG_WRITABLE_SCHEMA 1011 /* int int* */ -#define SQLITE_DBCONFIG_MAX 1011 /* Largest DBCONFIG */ - - /* +#define SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE 1006 /* int int* */ +#define SQLITE_DBCONFIG_ENABLE_QPSG 1007 /* int int* */ +#define SQLITE_DBCONFIG_TRIGGER_EQP 1008 /* int int* */ +#define SQLITE_DBCONFIG_RESET_DATABASE 1009 /* int int* */ +#define SQLITE_DBCONFIG_DEFENSIVE 1010 /* int int* */ +#define SQLITE_DBCONFIG_WRITABLE_SCHEMA 1011 /* int int* */ +#define SQLITE_DBCONFIG_LEGACY_ALTER_TABLE 1012 /* int int* */ +#define SQLITE_DBCONFIG_DQS_DML 1013 /* int int* */ +#define SQLITE_DBCONFIG_DQS_DDL 1014 /* int int* */ +#define SQLITE_DBCONFIG_ENABLE_VIEW 1015 /* int int* */ +#define SQLITE_DBCONFIG_LEGACY_FILE_FORMAT 1016 /* int int* */ +#define SQLITE_DBCONFIG_TRUSTED_SCHEMA 1017 /* int int* */ +#define SQLITE_DBCONFIG_STMT_SCANSTATUS 1018 /* int int* */ +#define SQLITE_DBCONFIG_REVERSE_SCANORDER 1019 /* int int* */ +#define SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE 1020 /* int int* */ +#define SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE 1021 /* int int* */ +#define SQLITE_DBCONFIG_ENABLE_COMMENTS 1022 /* int int* */ +#define SQLITE_DBCONFIG_FP_DIGITS 1023 /* int int* */ +#define SQLITE_DBCONFIG_MAX 1023 /* Largest DBCONFIG */ + +/* ** CAPI3REF: Enable Or Disable Extended Result Codes ** METHOD: sqlite3 ** @@ -2242,9 +2730,9 @@ typedef unsigned long long int sqlite_uint64; ** [extended result codes] feature of SQLite. ^The extended result ** codes are disabled by default for historical compatibility. */ - SQLITE_API int sqlite3_extended_result_codes(sqlite3*, int onoff); +SQLITE_API int sqlite3_extended_result_codes(sqlite3*, int onoff); - /* +/* ** CAPI3REF: Last Insert Rowid ** METHOD: sqlite3 ** @@ -2259,8 +2747,8 @@ typedef unsigned long long int sqlite_uint64; ** ^The sqlite3_last_insert_rowid(D) interface usually returns the [rowid] of ** the most recent successful [INSERT] into a rowid table or [virtual table] ** on database connection D. ^Inserts into [WITHOUT ROWID] tables are not -** recorded. ^If no successful [INSERT]s into rowid tables have ever occurred -** on the database connection D, then sqlite3_last_insert_rowid(D) returns +** recorded. ^If no successful [INSERT]s into rowid tables have ever occurred +** on the database connection D, then sqlite3_last_insert_rowid(D) returns ** zero. ** ** As well as being set automatically as rows are inserted into database @@ -2270,15 +2758,15 @@ typedef unsigned long long int sqlite_uint64; ** Some virtual table implementations may INSERT rows into rowid tables as ** part of committing a transaction (e.g. to flush data accumulated in memory ** to disk). In this case subsequent calls to this function return the rowid -** associated with these internal INSERT operations, which leads to +** associated with these internal INSERT operations, which leads to ** unintuitive results. Virtual table implementations that do write to rowid -** tables in this way can avoid this problem by restoring the original -** rowid value using [sqlite3_set_last_insert_rowid()] before returning +** tables in this way can avoid this problem by restoring the original +** rowid value using [sqlite3_set_last_insert_rowid()] before returning ** control to the user. ** -** ^(If an [INSERT] occurs within a trigger then this routine will -** return the [rowid] of the inserted row as long as the trigger is -** running. Once the trigger program ends, the value returned +** ^(If an [INSERT] occurs within a trigger then this routine will +** return the [rowid] of the inserted row as long as the trigger is +** running. Once the trigger program ends, the value returned ** by this routine reverts to what it was before the trigger was fired.)^ ** ** ^An [INSERT] that fails due to a constraint violation is not a @@ -2304,60 +2792,67 @@ typedef unsigned long long int sqlite_uint64; ** unpredictable and might not equal either the old or the new ** last insert [rowid]. */ - SQLITE_API sqlite3_int64 sqlite3_last_insert_rowid(sqlite3*); +SQLITE_API sqlite3_int64 sqlite3_last_insert_rowid(sqlite3*); - /* +/* ** CAPI3REF: Set the Last Insert Rowid value. ** METHOD: sqlite3 ** ** The sqlite3_set_last_insert_rowid(D, R) method allows the application to -** set the value returned by calling sqlite3_last_insert_rowid(D) to R +** set the value returned by calling sqlite3_last_insert_rowid(D) to R ** without inserting a row into the database. */ - SQLITE_API void sqlite3_set_last_insert_rowid(sqlite3*, sqlite3_int64); +SQLITE_API void sqlite3_set_last_insert_rowid(sqlite3*,sqlite3_int64); - /* +/* ** CAPI3REF: Count The Number Of Rows Modified ** METHOD: sqlite3 ** -** ^This function returns the number of rows modified, inserted or +** ^These functions return the number of rows modified, inserted or ** deleted by the most recently completed INSERT, UPDATE or DELETE ** statement on the database connection specified by the only parameter. -** ^Executing any other type of SQL statement does not modify the value -** returned by this function. +** The two functions are identical except for the type of the return value +** and that if the number of rows modified by the most recent INSERT, UPDATE, +** or DELETE is greater than the maximum value supported by type "int", then +** the return value of sqlite3_changes() is undefined. ^Executing any other +** type of SQL statement does not modify the value returned by these functions. +** For the purposes of this interface, a CREATE TABLE AS SELECT statement +** does not count as an INSERT, UPDATE or DELETE statement and hence the rows +** added to the new table by the CREATE TABLE AS SELECT statement are not +** counted. ** ** ^Only changes made directly by the INSERT, UPDATE or DELETE statement are -** considered - auxiliary changes caused by [CREATE TRIGGER | triggers], +** considered - auxiliary changes caused by [CREATE TRIGGER | triggers], ** [foreign key actions] or [REPLACE] constraint resolution are not counted. -** -** Changes to a view that are intercepted by -** [INSTEAD OF trigger | INSTEAD OF triggers] are not counted. ^The value -** returned by sqlite3_changes() immediately after an INSERT, UPDATE or -** DELETE statement run on a view is always zero. Only changes made to real +** +** Changes to a view that are intercepted by +** [INSTEAD OF trigger | INSTEAD OF triggers] are not counted. ^The value +** returned by sqlite3_changes() immediately after an INSERT, UPDATE or +** DELETE statement run on a view is always zero. Only changes made to real ** tables are counted. ** ** Things are more complicated if the sqlite3_changes() function is ** executed while a trigger program is running. This may happen if the ** program uses the [changes() SQL function], or if some other callback ** function invokes sqlite3_changes() directly. Essentially: -** +** **

                    **
                  • ^(Before entering a trigger program the value returned by -** sqlite3_changes() function is saved. After the trigger program +** sqlite3_changes() function is saved. After the trigger program ** has finished, the original value is restored.)^ -** -**
                  • ^(Within a trigger program each INSERT, UPDATE and DELETE -** statement sets the value returned by sqlite3_changes() -** upon completion as normal. Of course, this value will not include -** any changes performed by sub-triggers, as the sqlite3_changes() +** +**
                  • ^(Within a trigger program each INSERT, UPDATE and DELETE +** statement sets the value returned by sqlite3_changes() +** upon completion as normal. Of course, this value will not include +** any changes performed by sub-triggers, as the sqlite3_changes() ** value will be saved and restored after each sub-trigger has run.)^ **
                  -** +** ** ^This means that if the changes() SQL function (or similar) is used -** by the first INSERT, UPDATE or DELETE statement within a trigger, it +** by the first INSERT, UPDATE or DELETE statement within a trigger, it ** returns the value as set when the calling statement began executing. -** ^If it is used by the second or subsequent such statement within a trigger -** program, the value returned reflects the number of rows modified by the +** ^If it is used by the second or subsequent such statement within a trigger +** program, the value returned reflects the number of rows modified by the ** previous INSERT, UPDATE or DELETE statement within the same trigger. ** ** If a separate thread makes changes on the same database connection @@ -2372,21 +2867,26 @@ typedef unsigned long long int sqlite_uint64; **
                • the [data_version pragma] ** */ - SQLITE_API int sqlite3_changes(sqlite3*); +SQLITE_API int sqlite3_changes(sqlite3*); +SQLITE_API sqlite3_int64 sqlite3_changes64(sqlite3*); - /* +/* ** CAPI3REF: Total Number Of Rows Modified ** METHOD: sqlite3 ** -** ^This function returns the total number of rows inserted, modified or +** ^These functions return the total number of rows inserted, modified or ** deleted by all [INSERT], [UPDATE] or [DELETE] statements completed ** since the database connection was opened, including those executed as -** part of trigger programs. ^Executing any other type of SQL statement -** does not affect the value returned by sqlite3_total_changes(). -** +** part of trigger programs. The two functions are identical except for the +** type of the return value and that if the number of rows modified by the +** connection exceeds the maximum value supported by type "int", then +** the return value of sqlite3_total_changes() is undefined. ^Executing +** any other type of SQL statement does not affect the value returned by +** sqlite3_total_changes(). +** ** ^Changes made as part of [foreign key actions] are included in the ** count, but those made as part of REPLACE constraint resolution are -** not. ^Changes to a view that are intercepted by INSTEAD OF triggers +** not. ^Changes to a view that are intercepted by INSTEAD OF triggers ** are not counted. ** ** The [sqlite3_total_changes(D)] interface only reports the number @@ -2395,7 +2895,7 @@ typedef unsigned long long int sqlite_uint64; ** To detect changes against a database file from other database ** connections use the [PRAGMA data_version] command or the ** [SQLITE_FCNTL_DATA_VERSION] [file control]. -** +** ** If a separate thread makes changes on the same database connection ** while [sqlite3_total_changes()] is running then the value ** returned is unpredictable and not meaningful. @@ -2409,9 +2909,10 @@ typedef unsigned long long int sqlite_uint64; **
                • the [SQLITE_FCNTL_DATA_VERSION] [file control] ** */ - SQLITE_API int sqlite3_total_changes(sqlite3*); +SQLITE_API int sqlite3_total_changes(sqlite3*); +SQLITE_API sqlite3_int64 sqlite3_total_changes64(sqlite3*); - /* +/* ** CAPI3REF: Interrupt A Long-Running Query ** METHOD: sqlite3 ** @@ -2437,18 +2938,23 @@ typedef unsigned long long int sqlite_uint64; ** ** ^The sqlite3_interrupt(D) call is in effect until all currently running ** SQL statements on [database connection] D complete. ^Any new SQL statements -** that are started after the sqlite3_interrupt() call and before the -** running statements reaches zero are interrupted as if they had been +** that are started after the sqlite3_interrupt() call and before the +** running statement count reaches zero are interrupted as if they had been ** running prior to the sqlite3_interrupt() call. ^New SQL statements ** that are started after the running statement count reaches zero are ** not effected by the sqlite3_interrupt(). ** ^A call to sqlite3_interrupt(D) that occurs when there are no running ** SQL statements is a no-op and has no effect on SQL statements ** that are started after the sqlite3_interrupt() call returns. +** +** ^The [sqlite3_is_interrupted(D)] interface can be used to determine whether +** or not an interrupt is currently in effect for [database connection] D. +** It returns 1 if an interrupt is currently in effect, or 0 otherwise. */ - SQLITE_API void sqlite3_interrupt(sqlite3*); +SQLITE_API void sqlite3_interrupt(sqlite3*); +SQLITE_API int sqlite3_is_interrupted(sqlite3*); - /* +/* ** CAPI3REF: Determine If An SQL Statement Is Complete ** ** These routines are useful during command-line input to determine if the @@ -2466,10 +2972,10 @@ typedef unsigned long long int sqlite_uint64; ** ^These routines return 0 if the statement is incomplete. ^If a ** memory allocation fails, then SQLITE_NOMEM is returned. ** -** ^These routines do not parse the SQL statements thus +** ^These routines do not parse the SQL statements and thus ** will not detect syntactically incorrect SQL. ** -** ^(If SQLite has not been initialized using [sqlite3_initialize()] prior +** ^(If SQLite has not been initialized using [sqlite3_initialize()] prior ** to invoking sqlite3_complete16() then sqlite3_initialize() is invoked ** automatically by sqlite3_complete16(). If that initialization fails, ** then the return value from sqlite3_complete16() will be non-zero @@ -2481,10 +2987,10 @@ typedef unsigned long long int sqlite_uint64; ** The input to [sqlite3_complete16()] must be a zero-terminated ** UTF-16 string in native byte order. */ - SQLITE_API int sqlite3_complete(const char* sql); - SQLITE_API int sqlite3_complete16(const void* sql); +SQLITE_API int sqlite3_complete(const char *sql); +SQLITE_API int sqlite3_complete16(const void *sql); - /* +/* ** CAPI3REF: Register A Callback To Handle SQLITE_BUSY Errors ** KEYWORDS: {busy-handler callback} {busy handler} ** METHOD: sqlite3 @@ -2514,7 +3020,7 @@ typedef unsigned long long int sqlite_uint64; ** The presence of a busy handler does not guarantee that it will be invoked ** when there is lock contention. ^If SQLite determines that invoking the busy ** handler could result in a deadlock, it will go ahead and return [SQLITE_BUSY] -** to the application instead of invoking the +** to the application instead of invoking the ** busy handler. ** Consider a scenario where one process is holding a read lock that ** it is trying to promote to a reserved lock and @@ -2539,13 +3045,13 @@ typedef unsigned long long int sqlite_uint64; ** database connection that invoked the busy handler. In other words, ** the busy handler is not reentrant. Any such actions ** result in undefined behavior. -** +** ** A busy handler must not close the database connection ** or [prepared statement] that invoked the busy handler. */ - SQLITE_API int sqlite3_busy_handler(sqlite3*, int (*)(void*, int), void*); +SQLITE_API int sqlite3_busy_handler(sqlite3*,int(*)(void*,int),void*); - /* +/* ** CAPI3REF: Set A Busy Timeout ** METHOD: sqlite3 ** @@ -2566,16 +3072,54 @@ typedef unsigned long long int sqlite_uint64; ** ** See also: [PRAGMA busy_timeout] */ - SQLITE_API int sqlite3_busy_timeout(sqlite3*, int ms); +SQLITE_API int sqlite3_busy_timeout(sqlite3*, int ms); + +/* +** CAPI3REF: Set the Setlk Timeout +** METHOD: sqlite3 +** +** This routine is only useful in SQLITE_ENABLE_SETLK_TIMEOUT builds. If +** the VFS supports blocking locks, it sets the timeout in ms used by +** eligible locks taken on wal mode databases by the specified database +** handle. In non-SQLITE_ENABLE_SETLK_TIMEOUT builds, or if the VFS does +** not support blocking locks, this function is a no-op. +** +** Passing 0 to this function disables blocking locks altogether. Passing +** -1 to this function requests that the VFS blocks for a long time - +** indefinitely if possible. The results of passing any other negative value +** are undefined. +** +** Internally, each SQLite database handle stores two timeout values - the +** busy-timeout (used for rollback mode databases, or if the VFS does not +** support blocking locks) and the setlk-timeout (used for blocking locks +** on wal-mode databases). The sqlite3_busy_timeout() method sets both +** values, this function sets only the setlk-timeout value. Therefore, +** to configure separate busy-timeout and setlk-timeout values for a single +** database handle, call sqlite3_busy_timeout() followed by this function. +** +** Whenever the number of connections to a wal mode database falls from +** 1 to 0, the last connection takes an exclusive lock on the database, +** then checkpoints and deletes the wal file. While it is doing this, any +** new connection that tries to read from the database fails with an +** SQLITE_BUSY error. Or, if the SQLITE_SETLK_BLOCK_ON_CONNECT flag is +** passed to this API, the new connection blocks until the exclusive lock +** has been released. +*/ +SQLITE_API int sqlite3_setlk_timeout(sqlite3*, int ms, int flags); + +/* +** CAPI3REF: Flags for sqlite3_setlk_timeout() +*/ +#define SQLITE_SETLK_BLOCK_ON_CONNECT 0x01 - /* +/* ** CAPI3REF: Convenience Routines For Running Queries ** METHOD: sqlite3 ** ** This is a legacy interface that is preserved for backwards compatibility. ** Use of this interface is not recommended. ** -** Definition: A result table is memory data structure created by the +** Definition: A result table is a memory data structure created by the ** [sqlite3_get_table()] interface. A result table records the ** complete query results from one or more queries. ** @@ -2606,9 +3150,9 @@ typedef unsigned long long int sqlite_uint64; ** Cindy | 21 ** ** -** There are two column (M==2) and three rows (N==3). Thus the +** There are two columns (M==2) and three rows (N==3). Thus the ** result table has 8 entries. Suppose the result table is stored -** in an array names azResult. Then azResult holds this content: +** in an array named azResult. Then azResult holds this content: ** **
                   **        azResult[0] = "Name";
                  @@ -2641,23 +3185,23 @@ typedef unsigned long long int sqlite_uint64;
                   ** reflected in subsequent calls to [sqlite3_errcode()] or
                   ** [sqlite3_errmsg()].
                   */
                  -    SQLITE_API int sqlite3_get_table(
                  -        sqlite3* db,       /* An open database */
                  -        const char* zSql,  /* SQL to be evaluated */
                  -        char*** pazResult, /* Results of the query */
                  -        int* pnRow,        /* Number of result rows written here */
                  -        int* pnColumn,     /* Number of result columns written here */
                  -        char** pzErrmsg    /* Error msg written here */
                  -    );
                  -    SQLITE_API void sqlite3_free_table(char** result);
                  -
                  -    /*
                  +SQLITE_API int sqlite3_get_table(
                  +  sqlite3 *db,          /* An open database */
                  +  const char *zSql,     /* SQL to be evaluated */
                  +  char ***pazResult,    /* Results of the query */
                  +  int *pnRow,           /* Number of result rows written here */
                  +  int *pnColumn,        /* Number of result columns written here */
                  +  char **pzErrmsg       /* Error msg written here */
                  +);
                  +SQLITE_API void sqlite3_free_table(char **result);
                  +
                  +/*
                   ** CAPI3REF: Formatted String Printing Functions
                   **
                   ** These routines are work-alikes of the "printf()" family of functions
                   ** from the standard C library.
                   ** These routines understand most of the common formatting options from
                  -** the standard library printf() 
                  +** the standard library printf()
                   ** plus some additional non-standard formats ([%q], [%Q], [%w], and [%z]).
                   ** See the [built-in printf()] documentation for details.
                   **
                  @@ -2691,17 +3235,17 @@ typedef unsigned long long int sqlite_uint64;
                   **
                   ** See also:  [built-in printf()], [printf() SQL function]
                   */
                  -    SQLITE_API char* sqlite3_mprintf(const char*, ...);
                  -    SQLITE_API char* sqlite3_vmprintf(const char*, va_list);
                  -    SQLITE_API char* sqlite3_snprintf(int, char*, const char*, ...);
                  -    SQLITE_API char* sqlite3_vsnprintf(int, char*, const char*, va_list);
                  +SQLITE_API char *sqlite3_mprintf(const char*,...);
                  +SQLITE_API char *sqlite3_vmprintf(const char*, va_list);
                  +SQLITE_API char *sqlite3_snprintf(int,char*,const char*, ...);
                  +SQLITE_API char *sqlite3_vsnprintf(int,char*,const char*, va_list);
                   
                  -    /*
                  +/*
                   ** CAPI3REF: Memory Allocation Subsystem
                   **
                   ** The SQLite core uses these three routines for all of its own
                   ** internal memory allocation needs. "Core" in the previous sentence
                  -** does not include operating-system specific VFS implementation.  The
                  +** does not include operating-system specific [VFS] implementation.  The
                   ** Windows VFS uses native malloc() and free() for some operations.
                   **
                   ** ^The sqlite3_malloc() routine returns a pointer to a block
                  @@ -2718,7 +3262,7 @@ typedef unsigned long long int sqlite_uint64;
                   ** ^Calling sqlite3_free() with a pointer previously returned
                   ** by sqlite3_malloc() or sqlite3_realloc() releases that memory so
                   ** that it might be reused.  ^The sqlite3_free() routine is
                  -** a no-op if is called with a NULL pointer.  Passing a NULL pointer
                  +** a no-op if it is called with a NULL pointer.  Passing a NULL pointer
                   ** to sqlite3_free() is harmless.  After being freed, memory
                   ** should neither be read nor written.  Even reading previously freed
                   ** memory might result in a segmentation fault or other severe error.
                  @@ -2736,13 +3280,13 @@ typedef unsigned long long int sqlite_uint64;
                   ** sqlite3_free(X).
                   ** ^sqlite3_realloc(X,N) returns a pointer to a memory allocation
                   ** of at least N bytes in size or NULL if insufficient memory is available.
                  -** ^If M is the size of the prior allocation, then min(N,M) bytes
                  -** of the prior allocation are copied into the beginning of buffer returned
                  +** ^If M is the size of the prior allocation, then min(N,M) bytes of the
                  +** prior allocation are copied into the beginning of the buffer returned
                   ** by sqlite3_realloc(X,N) and the prior allocation is freed.
                   ** ^If sqlite3_realloc(X,N) returns NULL and N is positive, then the
                   ** prior allocation is not freed.
                   **
                  -** ^The sqlite3_realloc64(X,N) interfaces works the same as
                  +** ^The sqlite3_realloc64(X,N) interface works the same as
                   ** sqlite3_realloc(X,N) except that N is a 64-bit unsigned integer instead
                   ** of a 32-bit signed integer.
                   **
                  @@ -2762,19 +3306,6 @@ typedef unsigned long long int sqlite_uint64;
                   ** 4 byte boundary if the [SQLITE_4_BYTE_ALIGNED_MALLOC] compile-time
                   ** option is used.
                   **
                  -** In SQLite version 3.5.0 and 3.5.1, it was possible to define
                  -** the SQLITE_OMIT_MEMORY_ALLOCATION which would cause the built-in
                  -** implementation of these routines to be omitted.  That capability
                  -** is no longer provided.  Only built-in memory allocators can be used.
                  -**
                  -** Prior to SQLite version 3.7.10, the Windows OS interface layer called
                  -** the system malloc() and free() directly when converting
                  -** filenames between the UTF-8 encoding used by SQLite
                  -** and whatever filename encoding is used by the particular Windows
                  -** installation.  Memory allocation errors were detected, but
                  -** they were reported back as [SQLITE_CANTOPEN] or
                  -** [SQLITE_IOERR] rather than [SQLITE_NOMEM].
                  -**
                   ** The pointer arguments to [sqlite3_free()] and [sqlite3_realloc()]
                   ** must be either NULL or else pointers obtained from a prior
                   ** invocation of [sqlite3_malloc()] or [sqlite3_realloc()] that have
                  @@ -2784,14 +3315,14 @@ typedef unsigned long long int sqlite_uint64;
                   ** a block of memory after it has been released using
                   ** [sqlite3_free()] or [sqlite3_realloc()].
                   */
                  -    SQLITE_API void* sqlite3_malloc(int);
                  -    SQLITE_API void* sqlite3_malloc64(sqlite3_uint64);
                  -    SQLITE_API void* sqlite3_realloc(void*, int);
                  -    SQLITE_API void* sqlite3_realloc64(void*, sqlite3_uint64);
                  -    SQLITE_API void sqlite3_free(void*);
                  -    SQLITE_API sqlite3_uint64 sqlite3_msize(void*);
                  +SQLITE_API void *sqlite3_malloc(int);
                  +SQLITE_API void *sqlite3_malloc64(sqlite3_uint64);
                  +SQLITE_API void *sqlite3_realloc(void*, int);
                  +SQLITE_API void *sqlite3_realloc64(void*, sqlite3_uint64);
                  +SQLITE_API void sqlite3_free(void*);
                  +SQLITE_API sqlite3_uint64 sqlite3_msize(void*);
                   
                  -    /*
                  +/*
                   ** CAPI3REF: Memory Allocator Statistics
                   **
                   ** SQLite provides these two interfaces for reporting on the status
                  @@ -2805,7 +3336,7 @@ typedef unsigned long long int sqlite_uint64;
                   ** was last reset.  ^The values returned by [sqlite3_memory_used()] and
                   ** [sqlite3_memory_highwater()] include any overhead
                   ** added by SQLite in its implementation of [sqlite3_malloc()],
                  -** but not overhead added by the any underlying system library
                  +** but not overhead added by any underlying system library
                   ** routines that [sqlite3_malloc()] may call.
                   **
                   ** ^The memory high-water mark is reset to the current value of
                  @@ -2814,16 +3345,16 @@ typedef unsigned long long int sqlite_uint64;
                   ** by [sqlite3_memory_highwater(1)] is the high-water mark
                   ** prior to the reset.
                   */
                  -    SQLITE_API sqlite3_int64 sqlite3_memory_used(void);
                  -    SQLITE_API sqlite3_int64 sqlite3_memory_highwater(int resetFlag);
                  +SQLITE_API sqlite3_int64 sqlite3_memory_used(void);
                  +SQLITE_API sqlite3_int64 sqlite3_memory_highwater(int resetFlag);
                   
                  -    /*
                  +/*
                   ** CAPI3REF: Pseudo-Random Number Generator
                   **
                   ** SQLite contains a high-quality pseudo-random number generator (PRNG) used to
                   ** select random [ROWID | ROWIDs] when inserting new records into a table that
                   ** already uses the largest possible [ROWID].  The PRNG is also used for
                  -** the build-in random() and randomblob() SQL functions.  This interface allows
                  +** the built-in random() and randomblob() SQL functions.  This interface allows
                   ** applications to access the same PRNG for other purposes.
                   **
                   ** ^A call to this routine stores N bytes of randomness into buffer P.
                  @@ -2838,9 +3369,9 @@ typedef unsigned long long int sqlite_uint64;
                   ** internally and without recourse to the [sqlite3_vfs] xRandomness
                   ** method.
                   */
                  -    SQLITE_API void sqlite3_randomness(int N, void* P);
                  +SQLITE_API void sqlite3_randomness(int N, void *P);
                   
                  -    /*
                  +/*
                   ** CAPI3REF: Compile-Time Authorization Callbacks
                   ** METHOD: sqlite3
                   ** KEYWORDS: {authorizer callback}
                  @@ -2866,7 +3397,7 @@ typedef unsigned long long int sqlite_uint64;
                   ** requested is ok.  ^When the callback returns [SQLITE_DENY], the
                   ** [sqlite3_prepare_v2()] or equivalent call that triggered the
                   ** authorizer will fail with an error message explaining that
                  -** access is denied. 
                  +** access is denied.
                   **
                   ** ^The first parameter to the authorizer callback is a copy of the third
                   ** parameter to the sqlite3_set_authorizer() interface. ^The second parameter
                  @@ -2919,7 +3450,7 @@ typedef unsigned long long int sqlite_uint64;
                   ** database connections for the meaning of "modify" in this paragraph.
                   **
                   ** ^When [sqlite3_prepare_v2()] is used to prepare a statement, the
                  -** statement might be re-prepared during [sqlite3_step()] due to a 
                  +** statement might be re-prepared during [sqlite3_step()] due to a
                   ** schema change.  Hence, the application should ensure that the
                   ** correct authorizer callback remains in place during the [sqlite3_step()].
                   **
                  @@ -2929,10 +3460,11 @@ typedef unsigned long long int sqlite_uint64;
                   ** as stated in the previous paragraph, sqlite3_step() invokes
                   ** sqlite3_prepare_v2() to reprepare a statement after a schema change.
                   */
                  -    SQLITE_API int sqlite3_set_authorizer(
                  -        sqlite3*,
                  -        int (*xAuth)(void*, int, const char*, const char*, const char*, const char*),
                  -        void* pUserData);
                  +SQLITE_API int sqlite3_set_authorizer(
                  +  sqlite3*,
                  +  int (*xAuth)(void*,int,const char*,const char*,const char*,const char*),
                  +  void *pUserData
                  +);
                   
                   /*
                   ** CAPI3REF: Authorizer Return Codes
                  @@ -2946,8 +3478,8 @@ typedef unsigned long long int sqlite_uint64;
                   ** Note that SQLITE_IGNORE is also used as a [conflict resolution mode]
                   ** returned from the [sqlite3_vtab_on_conflict()] interface.
                   */
                  -#define SQLITE_DENY 1   /* Abort the SQL statement with an error */
                  -#define SQLITE_IGNORE 2 /* Don't allow access, but don't generate an error */
                  +#define SQLITE_DENY   1   /* Abort the SQL statement with an error */
                  +#define SQLITE_IGNORE 2   /* Don't allow access, but don't generate an error */
                   
                   /*
                   ** CAPI3REF: Authorizer Action Codes
                  @@ -2969,44 +3501,44 @@ typedef unsigned long long int sqlite_uint64;
                   ** top-level SQL code.
                   */
                   /******************************************* 3rd ************ 4th ***********/
                  -#define SQLITE_CREATE_INDEX 1        /* Index Name      Table Name      */
                  -#define SQLITE_CREATE_TABLE 2        /* Table Name      NULL            */
                  -#define SQLITE_CREATE_TEMP_INDEX 3   /* Index Name      Table Name      */
                  -#define SQLITE_CREATE_TEMP_TABLE 4   /* Table Name      NULL            */
                  -#define SQLITE_CREATE_TEMP_TRIGGER 5 /* Trigger Name    Table Name      */
                  -#define SQLITE_CREATE_TEMP_VIEW 6    /* View Name       NULL            */
                  -#define SQLITE_CREATE_TRIGGER 7      /* Trigger Name    Table Name      */
                  -#define SQLITE_CREATE_VIEW 8         /* View Name       NULL            */
                  -#define SQLITE_DELETE 9              /* Table Name      NULL            */
                  -#define SQLITE_DROP_INDEX 10         /* Index Name      Table Name      */
                  -#define SQLITE_DROP_TABLE 11         /* Table Name      NULL            */
                  -#define SQLITE_DROP_TEMP_INDEX 12    /* Index Name      Table Name      */
                  -#define SQLITE_DROP_TEMP_TABLE 13    /* Table Name      NULL            */
                  -#define SQLITE_DROP_TEMP_TRIGGER 14  /* Trigger Name    Table Name      */
                  -#define SQLITE_DROP_TEMP_VIEW 15     /* View Name       NULL            */
                  -#define SQLITE_DROP_TRIGGER 16       /* Trigger Name    Table Name      */
                  -#define SQLITE_DROP_VIEW 17          /* View Name       NULL            */
                  -#define SQLITE_INSERT 18             /* Table Name      NULL            */
                  -#define SQLITE_PRAGMA 19             /* Pragma Name     1st arg or NULL */
                  -#define SQLITE_READ 20               /* Table Name      Column Name     */
                  -#define SQLITE_SELECT 21             /* NULL            NULL            */
                  -#define SQLITE_TRANSACTION 22        /* Operation       NULL            */
                  -#define SQLITE_UPDATE 23             /* Table Name      Column Name     */
                  -#define SQLITE_ATTACH 24             /* Filename        NULL            */
                  -#define SQLITE_DETACH 25             /* Database Name   NULL            */
                  -#define SQLITE_ALTER_TABLE 26        /* Database Name   Table Name      */
                  -#define SQLITE_REINDEX 27            /* Index Name      NULL            */
                  -#define SQLITE_ANALYZE 28            /* Table Name      NULL            */
                  -#define SQLITE_CREATE_VTABLE 29      /* Table Name      Module Name     */
                  -#define SQLITE_DROP_VTABLE 30        /* Table Name      Module Name     */
                  -#define SQLITE_FUNCTION 31           /* NULL            Function Name   */
                  -#define SQLITE_SAVEPOINT 32          /* Operation       Savepoint Name  */
                  -#define SQLITE_COPY 0                /* No longer used */
                  -#define SQLITE_RECURSIVE 33          /* NULL            NULL            */
                  -
                  -    /*
                  -** CAPI3REF: Tracing And Profiling Functions
                  -** METHOD: sqlite3
                  +#define SQLITE_CREATE_INDEX          1   /* Index Name      Table Name      */
                  +#define SQLITE_CREATE_TABLE          2   /* Table Name      NULL            */
                  +#define SQLITE_CREATE_TEMP_INDEX     3   /* Index Name      Table Name      */
                  +#define SQLITE_CREATE_TEMP_TABLE     4   /* Table Name      NULL            */
                  +#define SQLITE_CREATE_TEMP_TRIGGER   5   /* Trigger Name    Table Name      */
                  +#define SQLITE_CREATE_TEMP_VIEW      6   /* View Name       NULL            */
                  +#define SQLITE_CREATE_TRIGGER        7   /* Trigger Name    Table Name      */
                  +#define SQLITE_CREATE_VIEW           8   /* View Name       NULL            */
                  +#define SQLITE_DELETE                9   /* Table Name      NULL            */
                  +#define SQLITE_DROP_INDEX           10   /* Index Name      Table Name      */
                  +#define SQLITE_DROP_TABLE           11   /* Table Name      NULL            */
                  +#define SQLITE_DROP_TEMP_INDEX      12   /* Index Name      Table Name      */
                  +#define SQLITE_DROP_TEMP_TABLE      13   /* Table Name      NULL            */
                  +#define SQLITE_DROP_TEMP_TRIGGER    14   /* Trigger Name    Table Name      */
                  +#define SQLITE_DROP_TEMP_VIEW       15   /* View Name       NULL            */
                  +#define SQLITE_DROP_TRIGGER         16   /* Trigger Name    Table Name      */
                  +#define SQLITE_DROP_VIEW            17   /* View Name       NULL            */
                  +#define SQLITE_INSERT               18   /* Table Name      NULL            */
                  +#define SQLITE_PRAGMA               19   /* Pragma Name     1st arg or NULL */
                  +#define SQLITE_READ                 20   /* Table Name      Column Name     */
                  +#define SQLITE_SELECT               21   /* NULL            NULL            */
                  +#define SQLITE_TRANSACTION          22   /* Operation       NULL            */
                  +#define SQLITE_UPDATE               23   /* Table Name      Column Name     */
                  +#define SQLITE_ATTACH               24   /* Filename        NULL            */
                  +#define SQLITE_DETACH               25   /* Database Name   NULL            */
                  +#define SQLITE_ALTER_TABLE          26   /* Database Name   Table Name      */
                  +#define SQLITE_REINDEX              27   /* Index Name      NULL            */
                  +#define SQLITE_ANALYZE              28   /* Table Name      NULL            */
                  +#define SQLITE_CREATE_VTABLE        29   /* Table Name      Module Name     */
                  +#define SQLITE_DROP_VTABLE          30   /* Table Name      Module Name     */
                  +#define SQLITE_FUNCTION             31   /* NULL            Function Name   */
                  +#define SQLITE_SAVEPOINT            32   /* Operation       Savepoint Name  */
                  +#define SQLITE_COPY                  0   /* No longer used */
                  +#define SQLITE_RECURSIVE            33   /* NULL            NULL            */
                  +
                  +/*
                  +** CAPI3REF: Deprecated Tracing And Profiling Functions
                  +** DEPRECATED
                   **
                   ** These routines are deprecated. Use the [sqlite3_trace_v2()] interface
                   ** instead of the routines described here.
                  @@ -3036,10 +3568,10 @@ typedef unsigned long long int sqlite_uint64;
                   ** either [sqlite3_trace()] or [sqlite3_trace_v2()] will cancel the
                   ** profile callback.
                   */
                  -    SQLITE_API SQLITE_DEPRECATED void* sqlite3_trace(sqlite3*,
                  -                                                     void (*xTrace)(void*, const char*), void*);
                  -    SQLITE_API SQLITE_DEPRECATED void* sqlite3_profile(sqlite3*,
                  -                                                       void (*xProfile)(void*, const char*, sqlite3_uint64), void*);
                  +SQLITE_API SQLITE_DEPRECATED void *sqlite3_trace(sqlite3*,
                  +   void(*xTrace)(void*,const char*), void*);
                  +SQLITE_API SQLITE_DEPRECATED void *sqlite3_profile(sqlite3*,
                  +   void(*xProfile)(void*,const char*,sqlite3_uint64), void*);
                   
                   /*
                   ** CAPI3REF: SQL Trace Event Codes
                  @@ -3066,7 +3598,7 @@ typedef unsigned long long int sqlite_uint64;
                   ** execution of the prepared statement, such as at the start of each
                   ** trigger subprogram. ^The P argument is a pointer to the
                   ** [prepared statement]. ^The X argument is a pointer to a string which
                  -** is the unexpanded SQL text of the prepared statement or an SQL comment 
                  +** is the unexpanded SQL text of the prepared statement or an SQL comment
                   ** that indicates the invocation of a trigger.  ^The callback can compute
                   ** the same text that would have been returned by the legacy [sqlite3_trace()]
                   ** interface by using the X argument when X begins with "--" and invoking
                  @@ -3076,13 +3608,13 @@ typedef unsigned long long int sqlite_uint64;
                   ** 
                  ^An SQLITE_TRACE_PROFILE callback provides approximately the same ** information as is provided by the [sqlite3_profile()] callback. ** ^The P argument is a pointer to the [prepared statement] and the -** X argument points to a 64-bit integer which is the estimated of -** the number of nanosecond that the prepared statement took to run. +** X argument points to a 64-bit integer which is approximately +** the number of nanoseconds that the prepared statement took to run. ** ^The SQLITE_TRACE_PROFILE callback is invoked when the statement finishes. ** ** [[SQLITE_TRACE_ROW]]
                  SQLITE_TRACE_ROW
                  **
                  ^An SQLITE_TRACE_ROW callback is invoked whenever a prepared -** statement generates a single row of result. +** statement generates a single row of result. ** ^The P argument is a pointer to the [prepared statement] and the ** X argument is unused. ** @@ -3093,12 +3625,12 @@ typedef unsigned long long int sqlite_uint64; ** and the X argument is unused. ** */ -#define SQLITE_TRACE_STMT 0x01 -#define SQLITE_TRACE_PROFILE 0x02 -#define SQLITE_TRACE_ROW 0x04 -#define SQLITE_TRACE_CLOSE 0x08 +#define SQLITE_TRACE_STMT 0x01 +#define SQLITE_TRACE_PROFILE 0x02 +#define SQLITE_TRACE_ROW 0x04 +#define SQLITE_TRACE_CLOSE 0x08 - /* +/* ** CAPI3REF: SQL Trace Hook ** METHOD: sqlite3 ** @@ -3109,10 +3641,12 @@ typedef unsigned long long int sqlite_uint64; ** M argument should be the bitwise OR-ed combination of ** zero or more [SQLITE_TRACE] constants. ** -** ^Each call to either sqlite3_trace() or sqlite3_trace_v2() overrides -** (cancels) any prior calls to sqlite3_trace() or sqlite3_trace_v2(). +** ^Each call to either sqlite3_trace(D,X,P) or sqlite3_trace_v2(D,M,X,P) +** overrides (cancels) all prior calls to sqlite3_trace(D,X,P) or +** sqlite3_trace_v2(D,M,X,P) for the [database connection] D. Each +** database connection may have at most one trace callback. ** -** ^The X callback is invoked whenever any of the events identified by +** ^The X callback is invoked whenever any of the events identified by ** mask M occur. ^The integer return value from the callback is currently ** ignored, though this may change in future releases. Callback ** implementations should return zero to ensure future compatibility. @@ -3127,24 +3661,25 @@ typedef unsigned long long int sqlite_uint64; ** interfaces [sqlite3_trace()] and [sqlite3_profile()], both of which ** are deprecated. */ - SQLITE_API int sqlite3_trace_v2( - sqlite3*, - unsigned uMask, - int (*xCallback)(unsigned, void*, void*, void*), - void* pCtx); +SQLITE_API int sqlite3_trace_v2( + sqlite3*, + unsigned uMask, + int(*xCallback)(unsigned,void*,void*,void*), + void *pCtx +); - /* +/* ** CAPI3REF: Query Progress Callbacks ** METHOD: sqlite3 ** ** ^The sqlite3_progress_handler(D,N,X,P) interface causes the callback ** function X to be invoked periodically during long running calls to -** [sqlite3_exec()], [sqlite3_step()] and [sqlite3_get_table()] for +** [sqlite3_step()] and [sqlite3_prepare()] and similar for ** database connection D. An example use for this ** interface is to keep a GUI updated during a large query. ** -** ^The parameter P is passed through as the only parameter to the -** callback function X. ^The parameter N is the approximate number of +** ^The parameter P is passed through as the only parameter to the +** callback function X. ^The parameter N is the approximate number of ** [virtual machine instructions] that are evaluated between successive ** invocations of the callback X. ^If N is less than one then the progress ** handler is disabled. @@ -3164,14 +3699,21 @@ typedef unsigned long long int sqlite_uint64; ** Note that [sqlite3_prepare_v2()] and [sqlite3_step()] both modify their ** database connections for the meaning of "modify" in this paragraph. ** +** The progress handler callback would originally only be invoked from the +** bytecode engine. It still might be invoked during [sqlite3_prepare()] +** and similar because those routines might force a reparse of the schema +** which involves running the bytecode engine. However, beginning with +** SQLite version 3.41.0, the progress handler callback might also be +** invoked directly from [sqlite3_prepare()] while analyzing and generating +** code for complex queries. */ - SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int (*)(void*), void*); +SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*); - /* +/* ** CAPI3REF: Opening A New Database Connection ** CONSTRUCTOR: sqlite3 ** -** ^These routines open an SQLite database file as specified by the +** ^These routines open an SQLite database file as specified by the ** filename argument. ^The filename argument is interpreted as UTF-8 for ** sqlite3_open() and sqlite3_open_v2() and as UTF-16 in the native byte ** order for sqlite3_open16(). ^(A [database connection] handle is usually @@ -3195,20 +3737,23 @@ typedef unsigned long long int sqlite_uint64; ** The sqlite3_open_v2() interface works like sqlite3_open() ** except that it accepts two additional parameters for additional control ** over the new database connection. ^(The flags parameter to -** sqlite3_open_v2() can take one of -** the following three values, optionally combined with the -** [SQLITE_OPEN_NOMUTEX], [SQLITE_OPEN_FULLMUTEX], [SQLITE_OPEN_SHAREDCACHE], -** [SQLITE_OPEN_PRIVATECACHE], and/or [SQLITE_OPEN_URI] flags:)^ +** sqlite3_open_v2() must include, at a minimum, one of the following +** three flag combinations:)^ ** **
                  ** ^(
                  [SQLITE_OPEN_READONLY]
                  -**
                  The database is opened in read-only mode. If the database does not -** already exist, an error is returned.
                  )^ +**
                  The database is opened in read-only mode. If the database does +** not already exist, an error is returned.
                  )^ ** ** ^(
                  [SQLITE_OPEN_READWRITE]
                  -**
                  The database is opened for reading and writing if possible, or reading -** only if the file is write protected by the operating system. In either -** case the database must already exist, otherwise an error is returned.
                  )^ +**
                  The database is opened for reading and writing if possible, or +** reading only if the file is write protected by the operating +** system. In either case the database must already exist, otherwise +** an error is returned. For historical reasons, if opening in +** read-write mode fails due to OS-level permissions, an attempt is +** made to open it in read-only mode. [sqlite3_db_readonly()] can be +** used to determine whether the database is actually +** read-write.
                  )^ ** ** ^(
                  [SQLITE_OPEN_READWRITE] | [SQLITE_OPEN_CREATE]
                  **
                  The database is opened for reading and writing, and is created if @@ -3216,22 +3761,69 @@ typedef unsigned long long int sqlite_uint64; ** sqlite3_open() and sqlite3_open16().
                  )^ **
                  ** +** In addition to the required flags, the following optional flags are +** also supported: +** +**
                  +** ^(
                  [SQLITE_OPEN_URI]
                  +**
                  The filename can be interpreted as a URI if this flag is set.
                  )^ +** +** ^(
                  [SQLITE_OPEN_MEMORY]
                  +**
                  The database will be opened as an in-memory database. The database +** is named by the "filename" argument for the purposes of cache-sharing, +** if shared cache mode is enabled, but the "filename" is otherwise ignored. +**
                  )^ +** +** ^(
                  [SQLITE_OPEN_NOMUTEX]
                  +**
                  The new database connection will use the "multi-thread" +** [threading mode].)^ This means that separate threads are allowed +** to use SQLite at the same time, as long as each thread is using +** a different [database connection]. +** +** ^(
                  [SQLITE_OPEN_FULLMUTEX]
                  +**
                  The new database connection will use the "serialized" +** [threading mode].)^ This means the multiple threads can safely +** attempt to use the same database connection at the same time. +** (Mutexes will block any actual concurrency, but in this mode +** there is no harm in trying.) +** +** ^(
                  [SQLITE_OPEN_SHAREDCACHE]
                  +**
                  The database is opened with [shared cache] enabled, overriding +** the default shared cache setting provided by +** [sqlite3_enable_shared_cache()].)^ +** The [use of shared cache mode is discouraged] and hence shared cache +** capabilities may be omitted from many builds of SQLite. In such cases, +** this option is a no-op. +** +** ^(
                  [SQLITE_OPEN_PRIVATECACHE]
                  +**
                  The database is opened with [shared cache] disabled, overriding +** the default shared cache setting provided by +** [sqlite3_enable_shared_cache()].)^ +** +** [[OPEN_EXRESCODE]] ^(
                  [SQLITE_OPEN_EXRESCODE]
                  +**
                  The database connection comes up in "extended result code mode". +** In other words, the database behaves as if +** [sqlite3_extended_result_codes(db,1)] were called on the database +** connection as soon as the connection is created. In addition to setting +** the extended result code mode, this flag also causes [sqlite3_open_v2()] +** to return an extended result code.
                  +** +** [[OPEN_NOFOLLOW]] ^(
                  [SQLITE_OPEN_NOFOLLOW]
                  +**
                  The database filename is not allowed to contain a symbolic link
                  +**
                  )^ +** ** If the 3rd parameter to sqlite3_open_v2() is not one of the -** combinations shown above optionally combined with other +** required combinations shown above optionally combined with other ** [SQLITE_OPEN_READONLY | SQLITE_OPEN_* bits] -** then the behavior is undefined. -** -** ^If the [SQLITE_OPEN_NOMUTEX] flag is set, then the database connection -** opens in the multi-thread [threading mode] as long as the single-thread -** mode has not been set at compile-time or start-time. ^If the -** [SQLITE_OPEN_FULLMUTEX] flag is set then the database connection opens -** in the serialized [threading mode] unless single-thread was -** previously selected at compile-time or start-time. -** ^The [SQLITE_OPEN_SHAREDCACHE] flag causes the database connection to be -** eligible to use [shared cache mode], regardless of whether or not shared -** cache is enabled using [sqlite3_enable_shared_cache()]. ^The -** [SQLITE_OPEN_PRIVATECACHE] flag causes the database connection to not -** participate in [shared cache mode] even if it is enabled. +** then the behavior is undefined. Historic versions of SQLite +** have silently ignored surplus bits in the flags parameter to +** sqlite3_open_v2(), however that behavior might not be carried through +** into future versions of SQLite and so applications should not rely +** upon it. Note in particular that the SQLITE_OPEN_EXCLUSIVE flag is a no-op +** for sqlite3_open_v2(). The SQLITE_OPEN_EXCLUSIVE does *not* cause +** the open to fail if the database already exists. The SQLITE_OPEN_EXCLUSIVE +** flag is intended for use by the [sqlite3_vfs|VFS interface] only, and not +** by sqlite3_open_v2(). ** ** ^The fourth parameter to sqlite3_open_v2() is the name of the ** [sqlite3_vfs] object that defines the operating system interface that @@ -3264,17 +3856,17 @@ typedef unsigned long long int sqlite_uint64; ** information. ** ** URI filenames are parsed according to RFC 3986. ^If the URI contains an -** authority, then it must be either an empty string or the string -** "localhost". ^If the authority is not an empty string or "localhost", an -** error is returned to the caller. ^The fragment component of a URI, if +** authority, then it must be either an empty string or the string +** "localhost". ^If the authority is not an empty string or "localhost", an +** error is returned to the caller. ^The fragment component of a URI, if ** present, is ignored. ** ** ^SQLite uses the path component of the URI as the name of the disk file -** which contains the database. ^If the path begins with a '/' character, -** then it is interpreted as an absolute path. ^If the path does not begin +** which contains the database. ^If the path begins with a '/' character, +** then it is interpreted as an absolute path. ^If the path does not begin ** with a '/' (meaning that the authority section is omitted from the URI) -** then the path is interpreted as a relative path. -** ^(On windows, the first component of an absolute path +** then the path is interpreted as a relative path. +** ^(On windows, the first component of an absolute path ** is a drive specification (e.g. "C:").)^ ** ** [[core URI query parameters]] @@ -3294,13 +3886,13 @@ typedef unsigned long long int sqlite_uint64; ** **
                • mode: ^(The mode parameter may be set to either "ro", "rw", ** "rwc", or "memory". Attempting to set it to any other value is -** an error)^. -** ^If "ro" is specified, then the database is opened for read-only -** access, just as if the [SQLITE_OPEN_READONLY] flag had been set in the -** third argument to sqlite3_open_v2(). ^If the mode option is set to -** "rw", then the database is opened for read-write (but not create) -** access, as if SQLITE_OPEN_READWRITE (but not SQLITE_OPEN_CREATE) had -** been set. ^Value "rwc" is equivalent to setting both +** an error)^. +** ^If "ro" is specified, then the database is opened for read-only +** access, just as if the [SQLITE_OPEN_READONLY] flag had been set in the +** third argument to sqlite3_open_v2(). ^If the mode option is set to +** "rw", then the database is opened for read-write (but not create) +** access, as if SQLITE_OPEN_READWRITE (but not SQLITE_OPEN_CREATE) had +** been set. ^Value "rwc" is equivalent to setting both ** SQLITE_OPEN_READWRITE and SQLITE_OPEN_CREATE. ^If the mode option is ** set to "memory" then a pure [in-memory database] that never reads ** or writes from disk is used. ^It is an error to specify a value for @@ -3310,7 +3902,7 @@ typedef unsigned long long int sqlite_uint64; **
                • cache: ^The cache parameter may be set to either "shared" or ** "private". ^Setting it to "shared" is equivalent to setting the ** SQLITE_OPEN_SHAREDCACHE bit in the flags argument passed to -** sqlite3_open_v2(). ^Setting the cache parameter to "private" is +** sqlite3_open_v2(). ^Setting the cache parameter to "private" is ** equivalent to setting the SQLITE_OPEN_PRIVATECACHE bit. ** ^If sqlite3_open_v2() is used and the "cache" parameter is present in ** a URI filename, its value overrides any behavior requested by setting @@ -3336,7 +3928,7 @@ typedef unsigned long long int sqlite_uint64; ** property on a database file that does in fact change can result ** in incorrect query results and/or [SQLITE_CORRUPT] errors. ** See also: [SQLITE_IOCAP_IMMUTABLE]. -** +** ** ** ** ^Specifying an unknown parameter in the query component of a URI is not an @@ -3348,36 +3940,37 @@ typedef unsigned long long int sqlite_uint64; ** **
                • **
                  URI filenames Results -**
                  file:data.db +**
                  file:data.db ** Open the file "data.db" in the current directory. **
                  file:/home/fred/data.db
                  -** file:///home/fred/data.db
                  -** file://localhost/home/fred/data.db
                  +** file:///home/fred/data.db
                  +** file://localhost/home/fred/data.db
                  ** Open the database file "/home/fred/data.db". -**
                  file://darkstar/home/fred/data.db +**
                  file://darkstar/home/fred/data.db ** An error. "darkstar" is not a recognized authority. -**
                  +**
                  ** file:///C:/Documents%20and%20Settings/fred/Desktop/data.db ** Windows only: Open the file "data.db" on fred's desktop on drive -** C:. Note that the %20 escaping in this example is not strictly +** C:. Note that the %20 escaping in this example is not strictly ** necessary - space characters can be used literally ** in URI filenames. -**
                  file:data.db?mode=ro&cache=private +**
                  file:data.db?mode=ro&cache=private ** Open file "data.db" in the current directory for read-only access. ** Regardless of whether or not shared-cache mode is enabled by ** default, use a private cache. **
                  file:/home/fred/data.db?vfs=unix-dotfile ** Open file "/home/fred/data.db". Use the special VFS "unix-dotfile" ** that uses dot-files in place of posix advisory locking. -**
                  file:data.db?mode=readonly +**
                  file:data.db?mode=readonly ** An error. "readonly" is not a valid option for the "mode" parameter. +** Use "ro" instead: "file:data.db?mode=ro". **
                  ** ** ^URI hexadecimal escape sequences (%HH) are supported within the path and ** query components of a URI. A hexadecimal escape sequence consists of a -** percent sign - "%" - followed by exactly two hexadecimal digits +** percent sign - "%" - followed by exactly two hexadecimal digits ** specifying an octet value. ^Before the path or query components of a -** URI filename are interpreted, they are encoded using UTF-8 and all +** URI filename are interpreted, they are encoded using UTF-8 and all ** hexadecimal escape sequences replaced by a single byte containing the ** corresponding octet. If this process generates an invalid UTF-8 encoding, ** the results are undefined. @@ -3394,35 +3987,45 @@ typedef unsigned long long int sqlite_uint64; ** ** See also: [sqlite3_temp_directory] */ - SQLITE_API int sqlite3_open( - const char* filename, /* Database filename (UTF-8) */ - sqlite3** ppDb /* OUT: SQLite db handle */ - ); - SQLITE_API int sqlite3_open16( - const void* filename, /* Database filename (UTF-16) */ - sqlite3** ppDb /* OUT: SQLite db handle */ - ); - SQLITE_API int sqlite3_open_v2( - const char* filename, /* Database filename (UTF-8) */ - sqlite3** ppDb, /* OUT: SQLite db handle */ - int flags, /* Flags */ - const char* zVfs /* Name of VFS module to use */ - ); - - /* +SQLITE_API int sqlite3_open( + const char *filename, /* Database filename (UTF-8) */ + sqlite3 **ppDb /* OUT: SQLite db handle */ +); +SQLITE_API int sqlite3_open16( + const void *filename, /* Database filename (UTF-16) */ + sqlite3 **ppDb /* OUT: SQLite db handle */ +); +SQLITE_API int sqlite3_open_v2( + const char *filename, /* Database filename (UTF-8) */ + sqlite3 **ppDb, /* OUT: SQLite db handle */ + int flags, /* Flags */ + const char *zVfs /* Name of VFS module to use */ +); + +/* ** CAPI3REF: Obtain Values For URI Parameters ** -** These are utility routines, useful to VFS implementations, that check -** to see if a database file was a URI that contained a specific query +** These are utility routines, useful to [VFS|custom VFS implementations], +** that check if a database file was a URI that contained a specific query ** parameter, and if so obtains the value of that query parameter. ** -** If F is the database filename pointer passed into the xOpen() method of -** a VFS implementation when the flags parameter to xOpen() has one or -** more of the [SQLITE_OPEN_URI] or [SQLITE_OPEN_MAIN_DB] bits set and -** P is the name of the query parameter, then +** The first parameter to these interfaces (hereafter referred to +** as F) must be one of: +**
                    +**
                  • A database filename pointer created by the SQLite core and +** passed into the xOpen() method of a VFS implementation, or +**
                  • A filename obtained from [sqlite3_db_filename()], or +**
                  • A new filename constructed using [sqlite3_create_filename()]. +**
                  +** If the F parameter is not one of the above, then the behavior is +** undefined and probably undesirable. Older versions of SQLite were +** more tolerant of invalid F parameters than newer versions. +** +** If F is a suitable filename (as described in the previous paragraph) +** and if P is the name of the query parameter, then ** sqlite3_uri_parameter(F,P) returns the value of the P -** parameter if it exists or a NULL pointer if P does not appear as a -** query parameter on F. If P is a query parameter of F +** parameter if it exists or a NULL pointer if P does not appear as a +** query parameter on F. If P is a query parameter of F and it ** has no explicit value, then sqlite3_uri_parameter(F,P) returns ** a pointer to an empty string. ** @@ -3430,40 +4033,160 @@ typedef unsigned long long int sqlite_uint64; ** parameter and returns true (1) or false (0) according to the value ** of P. The sqlite3_uri_boolean(F,P,B) routine returns true (1) if the ** value of query parameter P is one of "yes", "true", or "on" in any -** case or if the value begins with a non-zero number. The +** case or if the value begins with a non-zero number. The ** sqlite3_uri_boolean(F,P,B) routines returns false (0) if the value of ** query parameter P is one of "no", "false", or "off" in any case or ** if the value begins with a numeric zero. If P is not a query -** parameter on F or if the value of P is does not match any of the +** parameter on F or if the value of P does not match any of the ** above, then sqlite3_uri_boolean(F,P,B) returns (B!=0). ** ** The sqlite3_uri_int64(F,P,D) routine converts the value of P into a ** 64-bit signed integer and returns that integer, or D if P does not ** exist. If the value of P is something other than an integer, then ** zero is returned. -** +** +** The sqlite3_uri_key(F,N) returns a pointer to the name (not +** the value) of the N-th query parameter for filename F, or a NULL +** pointer if N is less than zero or greater than the number of query +** parameters minus 1. The N value is zero-based so N should be 0 to obtain +** the name of the first query parameter, 1 for the second parameter, and +** so forth. +** ** If F is a NULL pointer, then sqlite3_uri_parameter(F,P) returns NULL and ** sqlite3_uri_boolean(F,P,B) returns B. If F is not a NULL pointer and -** is not a database file pathname pointer that SQLite passed into the xOpen -** VFS method, then the behavior of this routine is undefined and probably -** undesirable. +** is not a database file pathname pointer that the SQLite core passed +** into the xOpen VFS method, then the behavior of this routine is undefined +** and probably undesirable. +** +** Beginning with SQLite [version 3.31.0] ([dateof:3.31.0]) the input F +** parameter can also be the name of a rollback journal file or WAL file +** in addition to the main database file. Prior to version 3.31.0, these +** routines would only work if F was the name of the main database file. +** When the F parameter is the name of the rollback journal or WAL file, +** it has access to all the same query parameters as were found on the +** main database file. ** ** See the [URI filename] documentation for additional information. */ - SQLITE_API const char* sqlite3_uri_parameter(const char* zFilename, const char* zParam); - SQLITE_API int sqlite3_uri_boolean(const char* zFile, const char* zParam, int bDefault); - SQLITE_API sqlite3_int64 sqlite3_uri_int64(const char*, const char*, sqlite3_int64); +SQLITE_API const char *sqlite3_uri_parameter(sqlite3_filename z, const char *zParam); +SQLITE_API int sqlite3_uri_boolean(sqlite3_filename z, const char *zParam, int bDefault); +SQLITE_API sqlite3_int64 sqlite3_uri_int64(sqlite3_filename, const char*, sqlite3_int64); +SQLITE_API const char *sqlite3_uri_key(sqlite3_filename z, int N); + +/* +** CAPI3REF: Translate filenames +** +** These routines are available to [VFS|custom VFS implementations] for +** translating filenames between the main database file, the journal file, +** and the WAL file. +** +** If F is the name of an sqlite database file, journal file, or WAL file +** passed by the SQLite core into the VFS, then sqlite3_filename_database(F) +** returns the name of the corresponding database file. +** +** If F is the name of an sqlite database file, journal file, or WAL file +** passed by the SQLite core into the VFS, or if F is a database filename +** obtained from [sqlite3_db_filename()], then sqlite3_filename_journal(F) +** returns the name of the corresponding rollback journal file. +** +** If F is the name of an sqlite database file, journal file, or WAL file +** that was passed by the SQLite core into the VFS, or if F is a database +** filename obtained from [sqlite3_db_filename()], then +** sqlite3_filename_wal(F) returns the name of the corresponding +** WAL file. +** +** In all of the above, if F is not the name of a database, journal or WAL +** filename passed into the VFS from the SQLite core and F is not the +** return value from [sqlite3_db_filename()], then the result is +** undefined and is likely a memory access violation. +*/ +SQLITE_API const char *sqlite3_filename_database(sqlite3_filename); +SQLITE_API const char *sqlite3_filename_journal(sqlite3_filename); +SQLITE_API const char *sqlite3_filename_wal(sqlite3_filename); + +/* +** CAPI3REF: Database File Corresponding To A Journal +** +** ^If X is the name of a rollback or WAL-mode journal file that is +** passed into the xOpen method of [sqlite3_vfs], then +** sqlite3_database_file_object(X) returns a pointer to the [sqlite3_file] +** object that represents the main database file. +** +** This routine is intended for use in custom [VFS] implementations +** only. It is not a general-purpose interface. +** The argument sqlite3_file_object(X) must be a filename pointer that +** has been passed into [sqlite3_vfs].xOpen method where the +** flags parameter to xOpen contains one of the bits +** [SQLITE_OPEN_MAIN_JOURNAL] or [SQLITE_OPEN_WAL]. Any other use +** of this routine results in undefined and probably undesirable +** behavior. +*/ +SQLITE_API sqlite3_file *sqlite3_database_file_object(const char*); + +/* +** CAPI3REF: Create and Destroy VFS Filenames +** +** These interfaces are provided for use by [VFS shim] implementations and +** are not useful outside of that context. +** +** The sqlite3_create_filename(D,J,W,N,P) allocates memory to hold a version of +** database filename D with corresponding journal file J and WAL file W and +** an array P of N URI Key/Value pairs. The result from +** sqlite3_create_filename(D,J,W,N,P) is a pointer to a database filename that +** is safe to pass to routines like: +**
                    +**
                  • [sqlite3_uri_parameter()], +**
                  • [sqlite3_uri_boolean()], +**
                  • [sqlite3_uri_int64()], +**
                  • [sqlite3_uri_key()], +**
                  • [sqlite3_filename_database()], +**
                  • [sqlite3_filename_journal()], or +**
                  • [sqlite3_filename_wal()]. +**
                  +** If a memory allocation error occurs, sqlite3_create_filename() might +** return a NULL pointer. The memory obtained from sqlite3_create_filename(X) +** must be released by a corresponding call to sqlite3_free_filename(Y). +** +** The P parameter in sqlite3_create_filename(D,J,W,N,P) should be an array +** of 2*N pointers to strings. Each pair of pointers in this array corresponds +** to a key and value for a query parameter. The P parameter may be a NULL +** pointer if N is zero. None of the 2*N pointers in the P array may be +** NULL pointers and key pointers should not be empty strings. +** None of the D, J, or W parameters to sqlite3_create_filename(D,J,W,N,P) may +** be NULL pointers, though they can be empty strings. +** +** The sqlite3_free_filename(Y) routine releases a memory allocation +** previously obtained from sqlite3_create_filename(). Invoking +** sqlite3_free_filename(Y) where Y is a NULL pointer is a harmless no-op. +** +** If the Y parameter to sqlite3_free_filename(Y) is anything other +** than a NULL pointer or a pointer previously acquired from +** sqlite3_create_filename(), then bad things such as heap +** corruption or segfaults may occur. The value Y should not be +** used again after sqlite3_free_filename(Y) has been called. This means +** that if the [sqlite3_vfs.xOpen()] method of a VFS has been called using Y, +** then the corresponding [sqlite3_module.xClose() method should also be +** invoked prior to calling sqlite3_free_filename(Y). +*/ +SQLITE_API sqlite3_filename sqlite3_create_filename( + const char *zDatabase, + const char *zJournal, + const char *zWal, + int nParam, + const char **azParam +); +SQLITE_API void sqlite3_free_filename(sqlite3_filename); - /* +/* ** CAPI3REF: Error Codes And Messages ** METHOD: sqlite3 ** -** ^If the most recent sqlite3_* API call associated with +** ^If the most recent sqlite3_* API call associated with ** [database connection] D failed, then the sqlite3_errcode(D) interface ** returns the numeric [result code] or [extended result code] for that ** API call. ** ^The sqlite3_extended_errcode() -** interface is the same except that it always returns the +** interface is the same except that it always returns the ** [extended result code] even when extended result codes are ** disabled. ** @@ -3471,27 +4194,39 @@ typedef unsigned long long int sqlite_uint64; ** sqlite3_extended_errcode() might change with each API call. ** Except, there are some interfaces that are guaranteed to never ** change the value of the error code. The error-code preserving -** interfaces are: +** interfaces include the following: ** **
                    **
                  • sqlite3_errcode() **
                  • sqlite3_extended_errcode() **
                  • sqlite3_errmsg() **
                  • sqlite3_errmsg16() +**
                  • sqlite3_error_offset() +**
                  • sqlite3_db_handle() **
                  ** ** ^The sqlite3_errmsg() and sqlite3_errmsg16() return English-language -** text that describes the error, as either UTF-8 or UTF-16 respectively. +** text that describes the error, as either UTF-8 or UTF-16 respectively, +** or NULL if no error message is available. +** (See how SQLite handles [invalid UTF] for exceptions to this rule.) ** ^(Memory to hold the error message string is managed internally. ** The application does not need to worry about freeing the result. ** However, the error string might be overwritten or deallocated by ** subsequent calls to other SQLite interface functions.)^ ** -** ^The sqlite3_errstr() interface returns the English-language text -** that describes the [result code], as UTF-8. +** ^The sqlite3_errstr(E) interface returns the English-language text +** that describes the [result code] E, as UTF-8, or NULL if E is not a +** result code for which a text error message is available. ** ^(Memory to hold the error message string is managed internally ** and must not be freed by the application)^. ** +** ^If the most recent error references a specific token in the input +** SQL, the sqlite3_error_offset() interface returns the byte offset +** of the start of that token. ^The byte offset returned by +** sqlite3_error_offset() assumes that the input SQL is UTF-8. +** ^If the most recent error does not reference a specific token in the input +** SQL, then the sqlite3_error_offset() function returns -1. +** ** When the serialized [threading mode] is in use, it might be the ** case that a second error occurs on a separate thread in between ** the time of the first error and the call to these interfaces. @@ -3506,13 +4241,42 @@ typedef unsigned long long int sqlite_uint64; ** was invoked incorrectly by the application. In that case, the ** error code and message may or may not be set. */ - SQLITE_API int sqlite3_errcode(sqlite3* db); - SQLITE_API int sqlite3_extended_errcode(sqlite3* db); - SQLITE_API const char* sqlite3_errmsg(sqlite3*); - SQLITE_API const void* sqlite3_errmsg16(sqlite3*); - SQLITE_API const char* sqlite3_errstr(int); +SQLITE_API int sqlite3_errcode(sqlite3 *db); +SQLITE_API int sqlite3_extended_errcode(sqlite3 *db); +SQLITE_API const char *sqlite3_errmsg(sqlite3*); +SQLITE_API const void *sqlite3_errmsg16(sqlite3*); +SQLITE_API const char *sqlite3_errstr(int); +SQLITE_API int sqlite3_error_offset(sqlite3 *db); + +/* +** CAPI3REF: Set Error Code And Message +** METHOD: sqlite3 +** +** Set the error code of the database handle passed as the first argument +** to errcode, and the error message to a copy of nul-terminated string +** zErrMsg. If zErrMsg is passed NULL, then the error message is set to +** the default message associated with the supplied error code. Subsequent +** calls to [sqlite3_errcode()] and [sqlite3_errmsg()] and similar will +** return the values set by this routine in place of what was previously +** set by SQLite itself. +** +** This function returns SQLITE_OK if the error code and error message are +** successfully set, SQLITE_NOMEM if an OOM occurs, and SQLITE_MISUSE if +** the database handle is NULL or invalid. +** +** The error code and message set by this routine remains in effect until +** they are changed, either by another call to this routine or until they are +** changed to by SQLite itself to reflect the result of some subsquent +** API call. +** +** This function is intended for use by SQLite extensions or wrappers. The +** idea is that an extension or wrapper can use this routine to set error +** messages and error codes and thus behave more like a core SQLite +** feature from the point of view of an application. +*/ +SQLITE_API int sqlite3_set_errmsg(sqlite3 *db, int errcode, const char *zErrMsg); - /* +/* ** CAPI3REF: Prepared Statement Object ** KEYWORDS: {prepared statement} {prepared statements} ** @@ -3520,7 +4284,7 @@ typedef unsigned long long int sqlite_uint64; ** has been compiled into binary form and is ready to be evaluated. ** ** Think of each SQL statement as a separate computer program. The -** original SQL text is source code. A prepared statement object +** original SQL text is source code. A prepared statement object ** is the compiled object code. All SQL must be converted into a ** prepared statement before it can be run. ** @@ -3536,9 +4300,9 @@ typedef unsigned long long int sqlite_uint64; **
                • Destroy the object using [sqlite3_finalize()]. **
      */ - typedef struct sqlite3_stmt sqlite3_stmt; +typedef struct sqlite3_stmt sqlite3_stmt; - /* +/* ** CAPI3REF: Run-time Limits ** METHOD: sqlite3 ** @@ -3550,7 +4314,7 @@ typedef unsigned long long int sqlite_uint64; ** new limit for that construct.)^ ** ** ^If the new limit is a negative number, the limit is unchanged. -** ^(For each limit category SQLITE_LIMIT_NAME there is a +** ^(For each limit category SQLITE_LIMIT_NAME there is a ** [limits | hard upper bound] ** set at compile-time by a C preprocessor macro called ** [limits | SQLITE_MAX_NAME]. @@ -3558,7 +4322,7 @@ typedef unsigned long long int sqlite_uint64; ** ^Attempts to increase a limit above its hard upper bound are ** silently truncated to the hard upper bound. ** -** ^Regardless of whether or not the limit was changed, the +** ^Regardless of whether or not the limit was changed, the ** [sqlite3_limit()] interface returns the prior value of the limit. ** ^Hence, to find the current value of a limit without changing it, ** simply invoke this interface with the third parameter set to -1. @@ -3578,7 +4342,7 @@ typedef unsigned long long int sqlite_uint64; ** ** New run-time limit categories may be added in future releases. */ - SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); +SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); /* ** CAPI3REF: Run-Time Limit Categories @@ -3586,8 +4350,8 @@ typedef unsigned long long int sqlite_uint64; ** ** These constants define various performance limits ** that can be lowered at run-time using [sqlite3_limit()]. -** The synopsis of the meanings of the various limits is shown below. -** Additional information is available at [limits | Limits in SQLite]. +** A concise description of these limits follows, and additional information +** is available at [limits | Limits in SQLite]. ** **
      ** [[SQLITE_LIMIT_LENGTH]] ^(
      SQLITE_LIMIT_LENGTH
      @@ -3602,7 +4366,12 @@ typedef unsigned long long int sqlite_uint64; ** or in an ORDER BY or GROUP BY clause.
    9. )^ ** ** [[SQLITE_LIMIT_EXPR_DEPTH]] ^(
      SQLITE_LIMIT_EXPR_DEPTH
      -**
      The maximum depth of the parse tree on any expression.
      )^ +**
      The maximum depth of the parse tree on any expression and +** the maximum nesting depth for subqueries and VIEWs
      )^ +** +** [[SQLITE_LIMIT_PARSER_DEPTH]] ^(
      SQLITE_LIMIT_PARSER_DEPTH
      +**
      The maximum depth of the LALR(1) parser stack used to analyze +** input SQL statements.
      )^ ** ** [[SQLITE_LIMIT_COMPOUND_SELECT]] ^(
      SQLITE_LIMIT_COMPOUND_SELECT
      **
      The maximum number of terms in a compound SELECT statement.
      )^ @@ -3629,30 +4398,32 @@ typedef unsigned long long int sqlite_uint64; **
      The maximum index number of any [parameter] in an SQL statement.)^ ** ** [[SQLITE_LIMIT_TRIGGER_DEPTH]] ^(
      SQLITE_LIMIT_TRIGGER_DEPTH
      -**
      The maximum depth of recursion for triggers.
      )^ +**
      The maximum depth of recursion for triggers, and the maximum +** nesting depth for separate triggers.
      )^ ** ** [[SQLITE_LIMIT_WORKER_THREADS]] ^(
      SQLITE_LIMIT_WORKER_THREADS
      **
      The maximum number of auxiliary worker threads that a single ** [prepared statement] may start.
      )^ ** */ -#define SQLITE_LIMIT_LENGTH 0 -#define SQLITE_LIMIT_SQL_LENGTH 1 -#define SQLITE_LIMIT_COLUMN 2 -#define SQLITE_LIMIT_EXPR_DEPTH 3 -#define SQLITE_LIMIT_COMPOUND_SELECT 4 -#define SQLITE_LIMIT_VDBE_OP 5 -#define SQLITE_LIMIT_FUNCTION_ARG 6 -#define SQLITE_LIMIT_ATTACHED 7 -#define SQLITE_LIMIT_LIKE_PATTERN_LENGTH 8 -#define SQLITE_LIMIT_VARIABLE_NUMBER 9 -#define SQLITE_LIMIT_TRIGGER_DEPTH 10 -#define SQLITE_LIMIT_WORKER_THREADS 11 +#define SQLITE_LIMIT_LENGTH 0 +#define SQLITE_LIMIT_SQL_LENGTH 1 +#define SQLITE_LIMIT_COLUMN 2 +#define SQLITE_LIMIT_EXPR_DEPTH 3 +#define SQLITE_LIMIT_COMPOUND_SELECT 4 +#define SQLITE_LIMIT_VDBE_OP 5 +#define SQLITE_LIMIT_FUNCTION_ARG 6 +#define SQLITE_LIMIT_ATTACHED 7 +#define SQLITE_LIMIT_LIKE_PATTERN_LENGTH 8 +#define SQLITE_LIMIT_VARIABLE_NUMBER 9 +#define SQLITE_LIMIT_TRIGGER_DEPTH 10 +#define SQLITE_LIMIT_WORKER_THREADS 11 +#define SQLITE_LIMIT_PARSER_DEPTH 12 /* ** CAPI3REF: Prepare Flags ** -** These constants define various flags that can be passed into +** These constants define various flags that can be passed into the ** "prepFlags" parameter of the [sqlite3_prepare_v3()] and ** [sqlite3_prepare16_v3()] interfaces. ** @@ -3663,7 +4434,7 @@ typedef unsigned long long int sqlite_uint64; **
      The SQLITE_PREPARE_PERSISTENT flag is a hint to the query planner ** that the prepared statement will be retained for a long time and ** probably reused many times.)^ ^Without this flag, [sqlite3_prepare_v3()] -** and [sqlite3_prepare16_v3()] assume that the prepared statement will +** and [sqlite3_prepare16_v3()] assume that the prepared statement will ** be used just once or at most a few times and then destroyed using ** [sqlite3_finalize()] relatively soon. The current implementation acts ** on this hint by avoiding the use of [lookaside memory] so as not to @@ -3682,13 +4453,41 @@ typedef unsigned long long int sqlite_uint64; **
      The SQLITE_PREPARE_NO_VTAB flag causes the SQL compiler ** to return an error (error code SQLITE_ERROR) if the statement uses ** any virtual tables. +** +** [[SQLITE_PREPARE_DONT_LOG]]
      SQLITE_PREPARE_DONT_LOG
      +**
      The SQLITE_PREPARE_DONT_LOG flag prevents SQL compiler +** errors from being sent to the error log defined by +** [SQLITE_CONFIG_LOG]. This can be used, for example, to do test +** compiles to see if some SQL syntax is well-formed, without generating +** messages on the global error log when it is not. If the test compile +** fails, the sqlite3_prepare_v3() call returns the same error indications +** with or without this flag; it just omits the call to [sqlite3_log()] that +** logs the error. +** +** [[SQLITE_PREPARE_FROM_DDL]]
      SQLITE_PREPARE_FROM_DDL
      +**
      The SQLITE_PREPARE_FROM_DDL flag causes the SQL compiler to enforce +** security constraints that would otherwise only be enforced when parsing +** the database schema. In other words, the SQLITE_PREPARE_FROM_DDL flag +** causes the SQL compiler to treat the SQL statement being prepared as if +** it had come from an attacker. When SQLITE_PREPARE_FROM_DDL is used and +** [SQLITE_DBCONFIG_TRUSTED_SCHEMA] is off, SQL functions may only be called +** if they are tagged with [SQLITE_INNOCUOUS] and virtual tables may only +** be used if they are tagged with [SQLITE_VTAB_INNOCUOUS]. Best practice +** is to use the SQLITE_PREPARE_FROM_DDL option when preparing any SQL that +** is derived from parts of the database schema. In particular, virtual +** table implementations that run SQL statements that are derived from +** arguments to their CREATE VIRTUAL TABLE statement should always use +** [sqlite3_prepare_v3()] and set the SQLITE_PREPARE_FROM_DDL flag to +** prevent bypass of the [SQLITE_DBCONFIG_TRUSTED_SCHEMA] security checks. ** */ -#define SQLITE_PREPARE_PERSISTENT 0x01 -#define SQLITE_PREPARE_NORMALIZE 0x02 -#define SQLITE_PREPARE_NO_VTAB 0x04 +#define SQLITE_PREPARE_PERSISTENT 0x01 +#define SQLITE_PREPARE_NORMALIZE 0x02 +#define SQLITE_PREPARE_NO_VTAB 0x04 +#define SQLITE_PREPARE_DONT_LOG 0x10 +#define SQLITE_PREPARE_FROM_DDL 0x20 - /* +/* ** CAPI3REF: Compiling An SQL Statement ** KEYWORDS: {SQL statement compiler} ** METHOD: sqlite3 @@ -3700,8 +4499,9 @@ typedef unsigned long long int sqlite_uint64; ** ** The preferred routine to use is [sqlite3_prepare_v2()]. The ** [sqlite3_prepare()] interface is legacy and should be avoided. -** [sqlite3_prepare_v3()] has an extra "prepFlags" option that is used -** for special purposes. +** [sqlite3_prepare_v3()] has an extra +** [SQLITE_PREPARE_FROM_DDL|"prepFlags" option] that is sometimes +** needed for special purpose or to pass along security restrictions. ** ** The use of the UTF-8 interfaces is preferred, as SQLite currently ** does all parsing using UTF-8. The UTF-16 interfaces are provided @@ -3719,13 +4519,17 @@ typedef unsigned long long int sqlite_uint64; ** and sqlite3_prepare16_v3() use UTF-16. ** ** ^If the nByte argument is negative, then zSql is read up to the -** first zero terminator. ^If nByte is positive, then it is the -** number of bytes read from zSql. ^If nByte is zero, then no prepared +** first zero terminator. ^If nByte is positive, then it is the maximum +** number of bytes read from zSql. When nByte is positive, zSql is read +** up to the first zero terminator or until the nByte bytes have been read, +** whichever comes first. ^If nByte is zero, then no prepared ** statement is generated. ** If the caller knows that the supplied string is nul-terminated, then ** there is a small performance advantage to passing an nByte parameter that ** is the number of bytes in the input string including ** the nul-terminator. +** Note that nByte measures the length of the input in bytes, not +** characters, even for the UTF-16 interfaces. ** ** ^If pzTail is not NULL then *pzTail is made to point to the first byte ** past the end of the first SQL statement in zSql. These routines only @@ -3770,15 +4574,15 @@ typedef unsigned long long int sqlite_uint64; **
    10. ** **
    11. -** ^If the specific value bound to [parameter | host parameter] in the +** ^If the specific value bound to a [parameter | host parameter] in the ** WHERE clause might influence the choice of query plan for a statement, -** then the statement will be automatically recompiled, as if there had been -** a schema change, on the first [sqlite3_step()] call following any change -** to the [sqlite3_bind_text | bindings] of that [parameter]. -** ^The specific value of WHERE-clause [parameter] might influence the +** then the statement will be automatically recompiled, as if there had been +** a schema change, on the first [sqlite3_step()] call following any change +** to the [sqlite3_bind_text | bindings] of that [parameter]. +** ^The specific value of a WHERE-clause [parameter] might influence the ** choice of query plan if the parameter is the left-hand side of a [LIKE] ** or [GLOB] operator or if the parameter is compared to an indexed column -** and the [SQLITE_ENABLE_STAT3] compile-time option is enabled. +** and the [SQLITE_ENABLE_STAT4] compile-time option is enabled. **
    12. **
    ** @@ -3788,52 +4592,52 @@ typedef unsigned long long int sqlite_uint64; ** sqlite3_prepare_v2() interface works exactly the same as ** sqlite3_prepare_v3() with a zero prepFlags parameter. */ - SQLITE_API int sqlite3_prepare( - sqlite3* db, /* Database handle */ - const char* zSql, /* SQL statement, UTF-8 encoded */ - int nByte, /* Maximum length of zSql in bytes. */ - sqlite3_stmt** ppStmt, /* OUT: Statement handle */ - const char** pzTail /* OUT: Pointer to unused portion of zSql */ - ); - SQLITE_API int sqlite3_prepare_v2( - sqlite3* db, /* Database handle */ - const char* zSql, /* SQL statement, UTF-8 encoded */ - int nByte, /* Maximum length of zSql in bytes. */ - sqlite3_stmt** ppStmt, /* OUT: Statement handle */ - const char** pzTail /* OUT: Pointer to unused portion of zSql */ - ); - SQLITE_API int sqlite3_prepare_v3( - sqlite3* db, /* Database handle */ - const char* zSql, /* SQL statement, UTF-8 encoded */ - int nByte, /* Maximum length of zSql in bytes. */ - unsigned int prepFlags, /* Zero or more SQLITE_PREPARE_ flags */ - sqlite3_stmt** ppStmt, /* OUT: Statement handle */ - const char** pzTail /* OUT: Pointer to unused portion of zSql */ - ); - SQLITE_API int sqlite3_prepare16( - sqlite3* db, /* Database handle */ - const void* zSql, /* SQL statement, UTF-16 encoded */ - int nByte, /* Maximum length of zSql in bytes. */ - sqlite3_stmt** ppStmt, /* OUT: Statement handle */ - const void** pzTail /* OUT: Pointer to unused portion of zSql */ - ); - SQLITE_API int sqlite3_prepare16_v2( - sqlite3* db, /* Database handle */ - const void* zSql, /* SQL statement, UTF-16 encoded */ - int nByte, /* Maximum length of zSql in bytes. */ - sqlite3_stmt** ppStmt, /* OUT: Statement handle */ - const void** pzTail /* OUT: Pointer to unused portion of zSql */ - ); - SQLITE_API int sqlite3_prepare16_v3( - sqlite3* db, /* Database handle */ - const void* zSql, /* SQL statement, UTF-16 encoded */ - int nByte, /* Maximum length of zSql in bytes. */ - unsigned int prepFlags, /* Zero or more SQLITE_PREPARE_ flags */ - sqlite3_stmt** ppStmt, /* OUT: Statement handle */ - const void** pzTail /* OUT: Pointer to unused portion of zSql */ - ); - - /* +SQLITE_API int sqlite3_prepare( + sqlite3 *db, /* Database handle */ + const char *zSql, /* SQL statement, UTF-8 encoded */ + int nByte, /* Maximum length of zSql in bytes. */ + sqlite3_stmt **ppStmt, /* OUT: Statement handle */ + const char **pzTail /* OUT: Pointer to unused portion of zSql */ +); +SQLITE_API int sqlite3_prepare_v2( + sqlite3 *db, /* Database handle */ + const char *zSql, /* SQL statement, UTF-8 encoded */ + int nByte, /* Maximum length of zSql in bytes. */ + sqlite3_stmt **ppStmt, /* OUT: Statement handle */ + const char **pzTail /* OUT: Pointer to unused portion of zSql */ +); +SQLITE_API int sqlite3_prepare_v3( + sqlite3 *db, /* Database handle */ + const char *zSql, /* SQL statement, UTF-8 encoded */ + int nByte, /* Maximum length of zSql in bytes. */ + unsigned int prepFlags, /* Zero or more SQLITE_PREPARE_ flags */ + sqlite3_stmt **ppStmt, /* OUT: Statement handle */ + const char **pzTail /* OUT: Pointer to unused portion of zSql */ +); +SQLITE_API int sqlite3_prepare16( + sqlite3 *db, /* Database handle */ + const void *zSql, /* SQL statement, UTF-16 encoded */ + int nByte, /* Maximum length of zSql in bytes. */ + sqlite3_stmt **ppStmt, /* OUT: Statement handle */ + const void **pzTail /* OUT: Pointer to unused portion of zSql */ +); +SQLITE_API int sqlite3_prepare16_v2( + sqlite3 *db, /* Database handle */ + const void *zSql, /* SQL statement, UTF-16 encoded */ + int nByte, /* Maximum length of zSql in bytes. */ + sqlite3_stmt **ppStmt, /* OUT: Statement handle */ + const void **pzTail /* OUT: Pointer to unused portion of zSql */ +); +SQLITE_API int sqlite3_prepare16_v3( + sqlite3 *db, /* Database handle */ + const void *zSql, /* SQL statement, UTF-16 encoded */ + int nByte, /* Maximum length of zSql in bytes. */ + unsigned int prepFlags, /* Zero or more SQLITE_PREPARE_ flags */ + sqlite3_stmt **ppStmt, /* OUT: Statement handle */ + const void **pzTail /* OUT: Pointer to unused portion of zSql */ +); + +/* ** CAPI3REF: Retrieving Statement SQL ** METHOD: sqlite3_stmt ** @@ -3858,7 +4662,7 @@ typedef unsigned long long int sqlite_uint64; ** ** ^The sqlite3_expanded_sql() interface returns NULL if insufficient memory ** is available to hold the result, or if the result would exceed the -** the maximum string length determined by the [SQLITE_LIMIT_LENGTH]. +** maximum string length determined by the [SQLITE_LIMIT_LENGTH]. ** ** ^The [SQLITE_TRACE_SIZE_LIMIT] compile-time option limits the size of ** bound parameter expansions. ^The [SQLITE_OMIT_TRACE] compile-time @@ -3868,14 +4672,19 @@ typedef unsigned long long int sqlite_uint64; ** are managed by SQLite and are automatically freed when the prepared ** statement is finalized. ** ^The string returned by sqlite3_expanded_sql(P), on the other hand, -** is obtained from [sqlite3_malloc()] and must be free by the application +** is obtained from [sqlite3_malloc()] and must be freed by the application ** by passing it to [sqlite3_free()]. +** +** ^The sqlite3_normalized_sql() interface is only available if +** the [SQLITE_ENABLE_NORMALIZE] compile-time option is defined. */ - SQLITE_API const char* sqlite3_sql(sqlite3_stmt* pStmt); - SQLITE_API char* sqlite3_expanded_sql(sqlite3_stmt* pStmt); - SQLITE_API const char* sqlite3_normalized_sql(sqlite3_stmt* pStmt); +SQLITE_API const char *sqlite3_sql(sqlite3_stmt *pStmt); +SQLITE_API char *sqlite3_expanded_sql(sqlite3_stmt *pStmt); +#ifdef SQLITE_ENABLE_NORMALIZE +SQLITE_API const char *sqlite3_normalized_sql(sqlite3_stmt *pStmt); +#endif - /* +/* ** CAPI3REF: Determine If An SQL Statement Writes The Database ** METHOD: sqlite3_stmt ** @@ -3884,8 +4693,8 @@ typedef unsigned long long int sqlite_uint64; ** the content of the database file. ** ** Note that [application-defined SQL functions] or -** [virtual tables] might change the database indirectly as a side effect. -** ^(For example, if an application defines a function "eval()" that +** [virtual tables] might change the database indirectly as a side effect. +** ^(For example, if an application defines a function "eval()" that ** calls [sqlite3_exec()], then the following SQL statement would ** change the database file through side-effects: ** @@ -3899,19 +4708,32 @@ typedef unsigned long long int sqlite_uint64; ** ^Transaction control statements such as [BEGIN], [COMMIT], [ROLLBACK], ** [SAVEPOINT], and [RELEASE] cause sqlite3_stmt_readonly() to return true, ** since the statements themselves do not actually modify the database but -** rather they control the timing of when other statements modify the +** rather they control the timing of when other statements modify the ** database. ^The [ATTACH] and [DETACH] statements also cause ** sqlite3_stmt_readonly() to return true since, while those statements -** change the configuration of a database connection, they do not make +** change the configuration of a database connection, they do not make ** changes to the content of the database files on disk. ** ^The sqlite3_stmt_readonly() interface returns true for [BEGIN] since ** [BEGIN] merely sets internal flags, but the [BEGIN|BEGIN IMMEDIATE] and ** [BEGIN|BEGIN EXCLUSIVE] commands do touch the database and so ** sqlite3_stmt_readonly() returns false for those commands. +** +** ^This routine returns false if there is any possibility that the +** statement might change the database file. ^A false return does +** not guarantee that the statement will change the database file. +** ^For example, an UPDATE statement might have a WHERE clause that +** makes it a no-op, but the sqlite3_stmt_readonly() result would still +** be false. ^Similarly, a CREATE TABLE IF NOT EXISTS statement is a +** read-only no-op if the table already exists, but +** sqlite3_stmt_readonly() still returns false for such a statement. +** +** ^If prepared statement X is an [EXPLAIN] or [EXPLAIN QUERY PLAN] +** statement, then sqlite3_stmt_readonly(X) returns the same value as +** if the EXPLAIN or EXPLAIN QUERY PLAN prefix were omitted. */ - SQLITE_API int sqlite3_stmt_readonly(sqlite3_stmt* pStmt); +SQLITE_API int sqlite3_stmt_readonly(sqlite3_stmt *pStmt); - /* +/* ** CAPI3REF: Query The EXPLAIN Setting For A Prepared Statement ** METHOD: sqlite3_stmt ** @@ -3921,30 +4743,65 @@ typedef unsigned long long int sqlite_uint64; ** ^The sqlite3_stmt_isexplain(S) interface returns 0 if S is ** an ordinary statement or a NULL pointer. */ - SQLITE_API int sqlite3_stmt_isexplain(sqlite3_stmt* pStmt); +SQLITE_API int sqlite3_stmt_isexplain(sqlite3_stmt *pStmt); + +/* +** CAPI3REF: Change The EXPLAIN Setting For A Prepared Statement +** METHOD: sqlite3_stmt +** +** The sqlite3_stmt_explain(S,E) interface changes the EXPLAIN +** setting for [prepared statement] S. If E is zero, then S becomes +** a normal prepared statement. If E is 1, then S behaves as if +** its SQL text began with "[EXPLAIN]". If E is 2, then S behaves as if +** its SQL text began with "[EXPLAIN QUERY PLAN]". +** +** Calling sqlite3_stmt_explain(S,E) might cause S to be reprepared. +** SQLite tries to avoid a reprepare, but a reprepare might be necessary +** on the first transition into EXPLAIN or EXPLAIN QUERY PLAN mode. +** +** Because of the potential need to reprepare, a call to +** sqlite3_stmt_explain(S,E) will fail with SQLITE_ERROR if S cannot be +** reprepared because it was created using [sqlite3_prepare()] instead of +** the newer [sqlite3_prepare_v2()] or [sqlite3_prepare_v3()] interfaces and +** hence has no saved SQL text with which to reprepare. +** +** Changing the explain setting for a prepared statement does not change +** the original SQL text for the statement. Hence, if the SQL text originally +** began with EXPLAIN or EXPLAIN QUERY PLAN, but sqlite3_stmt_explain(S,0) +** is called to convert the statement into an ordinary statement, the EXPLAIN +** or EXPLAIN QUERY PLAN keywords will still appear in the sqlite3_sql(S) +** output, even though the statement now acts like a normal SQL statement. +** +** This routine returns SQLITE_OK if the explain mode is successfully +** changed, or an error code if the explain mode could not be changed. +** The explain mode cannot be changed while a statement is active. +** Hence, it is good practice to call [sqlite3_reset(S)] +** immediately prior to calling sqlite3_stmt_explain(S,E). +*/ +SQLITE_API int sqlite3_stmt_explain(sqlite3_stmt *pStmt, int eMode); - /* +/* ** CAPI3REF: Determine If A Prepared Statement Has Been Reset ** METHOD: sqlite3_stmt ** ** ^The sqlite3_stmt_busy(S) interface returns true (non-zero) if the -** [prepared statement] S has been stepped at least once using +** [prepared statement] S has been stepped at least once using ** [sqlite3_step(S)] but has neither run to completion (returned ** [SQLITE_DONE] from [sqlite3_step(S)]) nor ** been reset using [sqlite3_reset(S)]. ^The sqlite3_stmt_busy(S) -** interface returns false if S is a NULL pointer. If S is not a +** interface returns false if S is a NULL pointer. If S is not a ** NULL pointer and is not a pointer to a valid [prepared statement] ** object, then the behavior is undefined and probably undesirable. ** ** This interface can be used in combination [sqlite3_next_stmt()] -** to locate all prepared statements associated with a database +** to locate all prepared statements associated with a database ** connection that are in need of being reset. This can be used, -** for example, in diagnostic routines to search for prepared +** for example, in diagnostic routines to search for prepared ** statements that are holding a transaction open. */ - SQLITE_API int sqlite3_stmt_busy(sqlite3_stmt*); +SQLITE_API int sqlite3_stmt_busy(sqlite3_stmt*); - /* +/* ** CAPI3REF: Dynamically Typed Value Object ** KEYWORDS: {protected sqlite3_value} {unprotected sqlite3_value} ** @@ -3958,7 +4815,7 @@ typedef unsigned long long int sqlite_uint64; ** will accept either a protected or an unprotected sqlite3_value. ** Every interface that accepts sqlite3_value arguments specifies ** whether or not it requires a protected sqlite3_value. The -** [sqlite3_value_dup()] interface can be used to construct a new +** [sqlite3_value_dup()] interface can be used to construct a new ** protected sqlite3_value from an unprotected sqlite3_value. ** ** The terms "protected" and "unprotected" refer to whether or not @@ -3966,7 +4823,7 @@ typedef unsigned long long int sqlite_uint64; ** sqlite3_value object but no mutex is held for an unprotected ** sqlite3_value object. If SQLite is compiled to be single-threaded ** (with [SQLITE_THREADSAFE=0] and with [sqlite3_threadsafe()] returning 0) -** or if SQLite is run in one of reduced mutex modes +** or if SQLite is run in one of reduced mutex modes ** [SQLITE_CONFIG_SINGLETHREAD] or [SQLITE_CONFIG_MULTITHREAD] ** then there is no distinction between protected and unprotected ** sqlite3_value objects and they can be used interchangeably. However, @@ -3976,6 +4833,8 @@ typedef unsigned long long int sqlite_uint64; ** ** ^The sqlite3_value objects that are passed as parameters into the ** implementation of [application-defined SQL functions] are protected. +** ^The sqlite3_value objects returned by [sqlite3_vtab_rhs_value()] +** are protected. ** ^The sqlite3_value object returned by ** [sqlite3_column_value()] is unprotected. ** Unprotected sqlite3_value objects may only be used as arguments @@ -3984,30 +4843,30 @@ typedef unsigned long long int sqlite_uint64; ** The [sqlite3_value_blob | sqlite3_value_type()] family of ** interfaces require protected sqlite3_value objects. */ - typedef struct sqlite3_value sqlite3_value; +typedef struct sqlite3_value sqlite3_value; - /* +/* ** CAPI3REF: SQL Function Context Object ** ** The context in which an SQL function executes is stored in an ** sqlite3_context object. ^A pointer to an sqlite3_context object -** is always first parameter to [application-defined SQL functions]. +** is always the first parameter to [application-defined SQL functions]. ** The application-defined SQL function implementation will pass this ** pointer through into calls to [sqlite3_result_int | sqlite3_result()], ** [sqlite3_aggregate_context()], [sqlite3_user_data()], ** [sqlite3_context_db_handle()], [sqlite3_get_auxdata()], ** and/or [sqlite3_set_auxdata()]. */ - typedef struct sqlite3_context sqlite3_context; +typedef struct sqlite3_context sqlite3_context; - /* +/* ** CAPI3REF: Binding Values To Prepared Statements ** KEYWORDS: {host parameter} {host parameters} {host parameter name} ** KEYWORDS: {SQL parameter} {SQL parameters} {parameter binding} ** METHOD: sqlite3_stmt ** ** ^(In the SQL statement text input to [sqlite3_prepare_v2()] and its variants, -** literals may be replaced by a [parameter] that matches one of following +** literals may be replaced by a [parameter] that matches one of the following ** templates: ** **
      @@ -4035,12 +4894,30 @@ typedef unsigned long long int sqlite_uint64; ** [sqlite3_bind_parameter_index()] API if desired. ^The index ** for "?NNN" parameters is the value of NNN. ** ^The NNN value must be between 1 and the [sqlite3_limit()] -** parameter [SQLITE_LIMIT_VARIABLE_NUMBER] (default value: 999). +** parameter [SQLITE_LIMIT_VARIABLE_NUMBER] (default value: 32766). ** ** ^The third argument is the value to bind to the parameter. ** ^If the third parameter to sqlite3_bind_text() or sqlite3_bind_text16() ** or sqlite3_bind_blob() is a NULL pointer then the fourth parameter ** is ignored and the end result is the same as sqlite3_bind_null(). +** ^If the third parameter to sqlite3_bind_text() is not NULL, then +** it should be a pointer to well-formed UTF8 text. +** ^If the third parameter to sqlite3_bind_text16() is not NULL, then +** it should be a pointer to well-formed UTF16 text. +** ^If the third parameter to sqlite3_bind_text64() is not NULL, then +** it should be a pointer to a well-formed unicode string that is +** either UTF8 if the sixth parameter is SQLITE_UTF8 or SQLITE_UTF8_ZT, +** or UTF16 otherwise. +** +** [[byte-order determination rules]] ^The byte-order of +** UTF16 input text is determined by the byte-order mark (BOM, U+FEFF) +** found in the first character, which is removed, or in the absence of a BOM +** the byte order is the native byte order of the host +** machine for sqlite3_bind_text16() or the byte order specified in +** the 6th parameter for sqlite3_bind_text64().)^ +** ^If UTF16 input text contains invalid unicode +** characters, then SQLite might change those invalid characters +** into the unicode replacement character: U+FFFD. ** ** ^(In those routines that have a fourth argument, its value is the ** number of bytes in the parameter. To be clear: the value is the @@ -4054,28 +4931,37 @@ typedef unsigned long long int sqlite_uint64; ** or sqlite3_bind_text16() or sqlite3_bind_text64() then ** that parameter must be the byte offset ** where the NUL terminator would occur assuming the string were NUL -** terminated. If any NUL characters occur at byte offsets less than +** terminated. If any NUL characters occur at byte offsets less than ** the value of the fourth parameter then the resulting string value will ** contain embedded NULs. The result of expressions involving strings ** with embedded NULs is undefined. ** -** ^The fifth argument to the BLOB and string binding interfaces -** is a destructor used to dispose of the BLOB or -** string after SQLite has finished with it. ^The destructor is called -** to dispose of the BLOB or string even if the call to the bind API fails, -** except the destructor is not called if the third parameter is a NULL -** pointer or the fourth parameter is negative. -** ^If the fifth argument is -** the special value [SQLITE_STATIC], then SQLite assumes that the -** information is in static, unmanaged space and does not need to be freed. -** ^If the fifth argument has the value [SQLITE_TRANSIENT], then -** SQLite makes its own private copy of the data immediately, before -** the sqlite3_bind_*() routine returns. -** -** ^The sixth argument to sqlite3_bind_text64() must be one of -** [SQLITE_UTF8], [SQLITE_UTF16], [SQLITE_UTF16BE], or [SQLITE_UTF16LE] -** to specify the encoding of the text in the third parameter. If -** the sixth argument to sqlite3_bind_text64() is not one of the +** ^The fifth argument to the BLOB and string binding interfaces controls +** or indicates the lifetime of the object referenced by the third parameter. +** These three options exist: +** ^ (1) A destructor to dispose of the BLOB or string after SQLite has finished +** with it may be passed. ^It is called to dispose of the BLOB or string even +** if the call to the bind API fails, except the destructor is not called if +** the third parameter is a NULL pointer or the fourth parameter is negative. +** ^ (2) The special constant, [SQLITE_STATIC], may be passed to indicate that +** the application remains responsible for disposing of the object. ^In this +** case, the object and the provided pointer to it must remain valid until +** either the prepared statement is finalized or the same SQL parameter is +** bound to something else, whichever occurs sooner. +** ^ (3) The constant, [SQLITE_TRANSIENT], may be passed to indicate that the +** object is to be copied prior to the return from sqlite3_bind_*(). ^The +** object and pointer to it must remain valid until then. ^SQLite will then +** manage the lifetime of its private copy. +** +** ^The sixth argument (the E argument) +** to sqlite3_bind_text64(S,K,Z,N,D,E) must be one of +** [SQLITE_UTF8], [SQLITE_UTF8_ZT], [SQLITE_UTF16], [SQLITE_UTF16BE], +** or [SQLITE_UTF16LE] to specify the encoding of the text in the +** third parameter, Z. The special value [SQLITE_UTF8_ZT] means that the +** string argument is both UTF-8 encoded and is zero-terminated. In other +** words, SQLITE_UTF8_ZT means that the Z array is allocated to hold at +** least N+1 bytes and that the Z[N] byte is zero. If +** the E argument to sqlite3_bind_text64(S,K,Z,N,D,E) is not one of the ** allowed values shown above, or if the text encoding is different ** from the encoding specified by the sixth parameter, then the behavior ** is undefined. @@ -4093,9 +4979,11 @@ typedef unsigned long long int sqlite_uint64; ** associated with the pointer P of type T. ^D is either a NULL pointer or ** a pointer to a destructor function for P. ^SQLite will invoke the ** destructor D with a single argument of P when it is finished using -** P. The T parameter should be a static string, preferably a string -** literal. The sqlite3_bind_pointer() routine is part of the -** [pointer passing interface] added for SQLite 3.20.0. +** P, even if the call to sqlite3_bind_pointer() fails. Due to a +** historical design quirk, results are undefined if D is +** SQLITE_TRANSIENT. The T parameter should be a static string, +** preferably a string literal. The sqlite3_bind_pointer() routine is +** part of the [pointer passing interface] added for SQLite 3.20.0. ** ** ^If any of the sqlite3_bind_*() routines are called with a NULL pointer ** for the [prepared statement] or with a prepared statement for which @@ -4118,23 +5006,23 @@ typedef unsigned long long int sqlite_uint64; ** See also: [sqlite3_bind_parameter_count()], ** [sqlite3_bind_parameter_name()], and [sqlite3_bind_parameter_index()]. */ - SQLITE_API int sqlite3_bind_blob(sqlite3_stmt*, int, const void*, int n, void (*)(void*)); - SQLITE_API int sqlite3_bind_blob64(sqlite3_stmt*, int, const void*, sqlite3_uint64, - void (*)(void*)); - SQLITE_API int sqlite3_bind_double(sqlite3_stmt*, int, double); - SQLITE_API int sqlite3_bind_int(sqlite3_stmt*, int, int); - SQLITE_API int sqlite3_bind_int64(sqlite3_stmt*, int, sqlite3_int64); - SQLITE_API int sqlite3_bind_null(sqlite3_stmt*, int); - SQLITE_API int sqlite3_bind_text(sqlite3_stmt*, int, const char*, int, void (*)(void*)); - SQLITE_API int sqlite3_bind_text16(sqlite3_stmt*, int, const void*, int, void (*)(void*)); - SQLITE_API int sqlite3_bind_text64(sqlite3_stmt*, int, const char*, sqlite3_uint64, - void (*)(void*), unsigned char encoding); - SQLITE_API int sqlite3_bind_value(sqlite3_stmt*, int, const sqlite3_value*); - SQLITE_API int sqlite3_bind_pointer(sqlite3_stmt*, int, void*, const char*, void (*)(void*)); - SQLITE_API int sqlite3_bind_zeroblob(sqlite3_stmt*, int, int n); - SQLITE_API int sqlite3_bind_zeroblob64(sqlite3_stmt*, int, sqlite3_uint64); - - /* +SQLITE_API int sqlite3_bind_blob(sqlite3_stmt*, int, const void*, int n, void(*)(void*)); +SQLITE_API int sqlite3_bind_blob64(sqlite3_stmt*, int, const void*, sqlite3_uint64, + void(*)(void*)); +SQLITE_API int sqlite3_bind_double(sqlite3_stmt*, int, double); +SQLITE_API int sqlite3_bind_int(sqlite3_stmt*, int, int); +SQLITE_API int sqlite3_bind_int64(sqlite3_stmt*, int, sqlite3_int64); +SQLITE_API int sqlite3_bind_null(sqlite3_stmt*, int); +SQLITE_API int sqlite3_bind_text(sqlite3_stmt*,int,const char*,int,void(*)(void*)); +SQLITE_API int sqlite3_bind_text16(sqlite3_stmt*, int, const void*, int, void(*)(void*)); +SQLITE_API int sqlite3_bind_text64(sqlite3_stmt*, int, const char*, sqlite3_uint64, + void(*)(void*), unsigned char encoding); +SQLITE_API int sqlite3_bind_value(sqlite3_stmt*, int, const sqlite3_value*); +SQLITE_API int sqlite3_bind_pointer(sqlite3_stmt*, int, void*, const char*,void(*)(void*)); +SQLITE_API int sqlite3_bind_zeroblob(sqlite3_stmt*, int, int n); +SQLITE_API int sqlite3_bind_zeroblob64(sqlite3_stmt*, int, sqlite3_uint64); + +/* ** CAPI3REF: Number Of SQL Parameters ** METHOD: sqlite3_stmt ** @@ -4153,9 +5041,9 @@ typedef unsigned long long int sqlite_uint64; ** [sqlite3_bind_parameter_name()], and ** [sqlite3_bind_parameter_index()]. */ - SQLITE_API int sqlite3_bind_parameter_count(sqlite3_stmt*); +SQLITE_API int sqlite3_bind_parameter_count(sqlite3_stmt*); - /* +/* ** CAPI3REF: Name Of A Host Parameter ** METHOD: sqlite3_stmt ** @@ -4181,9 +5069,9 @@ typedef unsigned long long int sqlite_uint64; ** [sqlite3_bind_parameter_count()], and ** [sqlite3_bind_parameter_index()]. */ - SQLITE_API const char* sqlite3_bind_parameter_name(sqlite3_stmt*, int); +SQLITE_API const char *sqlite3_bind_parameter_name(sqlite3_stmt*, int); - /* +/* ** CAPI3REF: Index Of A Parameter With A Given Name ** METHOD: sqlite3_stmt ** @@ -4199,9 +5087,9 @@ typedef unsigned long long int sqlite_uint64; ** [sqlite3_bind_parameter_count()], and ** [sqlite3_bind_parameter_name()]. */ - SQLITE_API int sqlite3_bind_parameter_index(sqlite3_stmt*, const char* zName); +SQLITE_API int sqlite3_bind_parameter_index(sqlite3_stmt*, const char *zName); - /* +/* ** CAPI3REF: Reset All Bindings On A Prepared Statement ** METHOD: sqlite3_stmt ** @@ -4209,14 +5097,14 @@ typedef unsigned long long int sqlite_uint64; ** the [sqlite3_bind_blob | bindings] on a [prepared statement]. ** ^Use this routine to reset all host parameters to NULL. */ - SQLITE_API int sqlite3_clear_bindings(sqlite3_stmt*); +SQLITE_API int sqlite3_clear_bindings(sqlite3_stmt*); - /* +/* ** CAPI3REF: Number Of Columns In A Result Set ** METHOD: sqlite3_stmt ** ** ^Return the number of columns in the result set returned by the -** [prepared statement]. ^If this routine returns 0, that means the +** [prepared statement]. ^If this routine returns 0, that means the ** [prepared statement] returns no data (for example an [UPDATE]). ** ^However, just because this routine returns a positive number does not ** mean that one or more rows of data will be returned. ^A SELECT statement @@ -4225,9 +5113,9 @@ typedef unsigned long long int sqlite_uint64; ** ** See also: [sqlite3_data_count()] */ - SQLITE_API int sqlite3_column_count(sqlite3_stmt* pStmt); +SQLITE_API int sqlite3_column_count(sqlite3_stmt *pStmt); - /* +/* ** CAPI3REF: Column Names In A Result Set ** METHOD: sqlite3_stmt ** @@ -4254,15 +5142,15 @@ typedef unsigned long long int sqlite_uint64; ** then the name of the column is unspecified and may change from ** one release of SQLite to the next. */ - SQLITE_API const char* sqlite3_column_name(sqlite3_stmt*, int N); - SQLITE_API const void* sqlite3_column_name16(sqlite3_stmt*, int N); +SQLITE_API const char *sqlite3_column_name(sqlite3_stmt*, int N); +SQLITE_API const void *sqlite3_column_name16(sqlite3_stmt*, int N); - /* +/* ** CAPI3REF: Source Of Data In A Query Result ** METHOD: sqlite3_stmt ** ** ^These routines provide a means to determine the database, table, and -** table column that is the origin of a particular result column in +** table column that is the origin of a particular result column in a ** [SELECT] statement. ** ^The name of the database or table or column can be returned as ** either a UTF-8 or UTF-16 string. ^The _database_ routines return @@ -4284,7 +5172,7 @@ typedef unsigned long long int sqlite_uint64; ** ** ^If the Nth column returned by the statement is an expression or ** subquery and is not a column value, then all of these functions return -** NULL. ^These routine might also return NULL if a memory allocation error +** NULL. ^These routines might also return NULL if a memory allocation error ** occurs. ^Otherwise, they return the name of the attached database, table, ** or column that query result column was extracted from. ** @@ -4294,23 +5182,19 @@ typedef unsigned long long int sqlite_uint64; ** ^These APIs are only available if the library was compiled with the ** [SQLITE_ENABLE_COLUMN_METADATA] C-preprocessor symbol. ** -** If two or more threads call one or more of these routines against the same -** prepared statement and column at the same time then the results are -** undefined. -** ** If two or more threads call one or more ** [sqlite3_column_database_name | column metadata interfaces] ** for the same [prepared statement] and result column ** at the same time then the results are undefined. */ - SQLITE_API const char* sqlite3_column_database_name(sqlite3_stmt*, int); - SQLITE_API const void* sqlite3_column_database_name16(sqlite3_stmt*, int); - SQLITE_API const char* sqlite3_column_table_name(sqlite3_stmt*, int); - SQLITE_API const void* sqlite3_column_table_name16(sqlite3_stmt*, int); - SQLITE_API const char* sqlite3_column_origin_name(sqlite3_stmt*, int); - SQLITE_API const void* sqlite3_column_origin_name16(sqlite3_stmt*, int); +SQLITE_API const char *sqlite3_column_database_name(sqlite3_stmt*,int); +SQLITE_API const void *sqlite3_column_database_name16(sqlite3_stmt*,int); +SQLITE_API const char *sqlite3_column_table_name(sqlite3_stmt*,int); +SQLITE_API const void *sqlite3_column_table_name16(sqlite3_stmt*,int); +SQLITE_API const char *sqlite3_column_origin_name(sqlite3_stmt*,int); +SQLITE_API const void *sqlite3_column_origin_name16(sqlite3_stmt*,int); - /* +/* ** CAPI3REF: Declared Datatype Of A Query Result ** METHOD: sqlite3_stmt ** @@ -4340,10 +5224,10 @@ typedef unsigned long long int sqlite_uint64; ** is associated with individual values, not with the containers ** used to hold those values. */ - SQLITE_API const char* sqlite3_column_decltype(sqlite3_stmt*, int); - SQLITE_API const void* sqlite3_column_decltype16(sqlite3_stmt*, int); +SQLITE_API const char *sqlite3_column_decltype(sqlite3_stmt*,int); +SQLITE_API const void *sqlite3_column_decltype16(sqlite3_stmt*,int); - /* +/* ** CAPI3REF: Evaluate An SQL Statement ** METHOD: sqlite3_stmt ** @@ -4402,9 +5286,9 @@ typedef unsigned long long int sqlite_uint64; ** For all versions of SQLite up to and including 3.6.23.1, a call to ** [sqlite3_reset()] was required after sqlite3_step() returned anything ** other than [SQLITE_ROW] before any subsequent invocation of -** sqlite3_step(). Failure to reset the prepared statement using +** sqlite3_step(). Failure to reset the prepared statement using ** [sqlite3_reset()] would result in an [SQLITE_MISUSE] return from -** sqlite3_step(). But after [version 3.6.23.1] ([dateof:3.6.23.1], +** sqlite3_step(). But after [version 3.6.23.1] ([dateof:3.6.23.1]), ** sqlite3_step() began ** calling [sqlite3_reset()] automatically in this circumstance rather ** than returning [SQLITE_MISUSE]. This is not considered a compatibility @@ -4425,16 +5309,16 @@ typedef unsigned long long int sqlite_uint64; ** then the more specific [error codes] are returned directly ** by sqlite3_step(). The use of the "vX" interfaces is recommended. */ - SQLITE_API int sqlite3_step(sqlite3_stmt*); +SQLITE_API int sqlite3_step(sqlite3_stmt*); - /* +/* ** CAPI3REF: Number of columns in a result set ** METHOD: sqlite3_stmt ** ** ^The sqlite3_data_count(P) interface returns the number of columns in the ** current row of the result set of [prepared statement] P. ** ^If prepared statement P does not have results ready to return -** (via calls to the [sqlite3_column_int | sqlite3_column_*()] of +** (via calls to the [sqlite3_column_int | sqlite3_column()] family of ** interfaces) then sqlite3_data_count(P) returns 0. ** ^The sqlite3_data_count(P) routine also returns 0 if P is a NULL pointer. ** ^The sqlite3_data_count(P) routine returns 0 if the previous call to @@ -4446,7 +5330,7 @@ typedef unsigned long long int sqlite_uint64; ** ** See also: [sqlite3_column_count()] */ - SQLITE_API int sqlite3_data_count(sqlite3_stmt* pStmt); +SQLITE_API int sqlite3_data_count(sqlite3_stmt *pStmt); /* ** CAPI3REF: Fundamental Datatypes @@ -4469,18 +5353,18 @@ typedef unsigned long long int sqlite_uint64; ** SQLite version 2 and SQLite version 3 should use SQLITE3_TEXT, not ** SQLITE_TEXT. */ -#define SQLITE_INTEGER 1 -#define SQLITE_FLOAT 2 -#define SQLITE_BLOB 4 -#define SQLITE_NULL 5 +#define SQLITE_INTEGER 1 +#define SQLITE_FLOAT 2 +#define SQLITE_BLOB 4 +#define SQLITE_NULL 5 #ifdef SQLITE_TEXT -#undef SQLITE_TEXT +# undef SQLITE_TEXT #else -#define SQLITE_TEXT 3 +# define SQLITE_TEXT 3 #endif -#define SQLITE3_TEXT 3 +#define SQLITE3_TEXT 3 - /* +/* ** CAPI3REF: Result Values From A Query ** KEYWORDS: {column access functions} ** METHOD: sqlite3_stmt @@ -4493,7 +5377,7 @@ typedef unsigned long long int sqlite_uint64; **
    sqlite3_column_int6464-bit INTEGER result **
    sqlite3_column_textUTF-8 TEXT result **
    sqlite3_column_text16UTF-16 TEXT result -**
    sqlite3_column_valueThe result as an +**
    sqlite3_column_valueThe result as an ** [sqlite3_value|unprotected sqlite3_value] object. **
        **
    sqlite3_column_bytesSize of a BLOB @@ -4541,7 +5425,7 @@ typedef unsigned long long int sqlite_uint64; ** The return value of sqlite3_column_type() can be used to decide which ** of the first six interface should be used to extract the column value. ** The value returned by sqlite3_column_type() is only meaningful if no -** automatic type conversions have occurred for the value in question. +** automatic type conversions have occurred for the value in question. ** After a type conversion, the result of calling sqlite3_column_type() ** is undefined, though harmless. Future ** versions of SQLite may change the behavior of sqlite3_column_type() @@ -4569,7 +5453,7 @@ typedef unsigned long long int sqlite_uint64; ** the number of bytes in that string. ** ^If the result is NULL, then sqlite3_column_bytes16() returns zero. ** -** ^The values returned by [sqlite3_column_bytes()] and +** ^The values returned by [sqlite3_column_bytes()] and ** [sqlite3_column_bytes16()] do not include the zero terminators at the end ** of the string. ^For clarity: the values returned by ** [sqlite3_column_bytes()] and [sqlite3_column_bytes16()] are the number of @@ -4579,6 +5463,10 @@ typedef unsigned long long int sqlite_uint64; ** even empty strings, are always zero-terminated. ^The return ** value from sqlite3_column_blob() for a zero-length BLOB is a NULL pointer. ** +** ^Strings returned by sqlite3_column_text16() always have the endianness +** which is native to the platform, regardless of the text encoding set +** for the database. +** ** Warning: ^The object returned by [sqlite3_column_value()] is an ** [unprotected sqlite3_value] object. In a multithreaded environment, ** an unprotected sqlite3_value object may only be used safely with @@ -4588,11 +5476,11 @@ typedef unsigned long long int sqlite_uint64; ** to routines like [sqlite3_value_int()], [sqlite3_value_text()], ** or [sqlite3_value_bytes()], the behavior is not threadsafe. ** Hence, the sqlite3_column_value() interface -** is normally only useful within the implementation of +** is normally only useful within the implementation of ** [application-defined SQL functions] or [virtual tables], not within ** top-level application code. ** -** The these routines may attempt to convert the datatype of the result. +** These routines may attempt to convert the datatype of the result. ** ^For example, if the internal representation is FLOAT and a text result ** is requested, [sqlite3_snprintf()] is used internally to perform the ** conversion automatically. ^(The following table details the conversions @@ -4617,7 +5505,7 @@ typedef unsigned long long int sqlite_uint64; **
    TEXT BLOB No change **
    BLOB INTEGER [CAST] to INTEGER **
    BLOB FLOAT [CAST] to REAL -**
    BLOB TEXT Add a zero terminator if needed +**
    BLOB TEXT [CAST] to TEXT, ensure zero terminator **
    ** )^ ** @@ -4689,24 +5577,24 @@ typedef unsigned long long int sqlite_uint64; ** return value is obtained and before any ** other SQLite interface is called on the same [database connection]. */ - SQLITE_API const void* sqlite3_column_blob(sqlite3_stmt*, int iCol); - SQLITE_API double sqlite3_column_double(sqlite3_stmt*, int iCol); - SQLITE_API int sqlite3_column_int(sqlite3_stmt*, int iCol); - SQLITE_API sqlite3_int64 sqlite3_column_int64(sqlite3_stmt*, int iCol); - SQLITE_API const unsigned char* sqlite3_column_text(sqlite3_stmt*, int iCol); - SQLITE_API const void* sqlite3_column_text16(sqlite3_stmt*, int iCol); - SQLITE_API sqlite3_value* sqlite3_column_value(sqlite3_stmt*, int iCol); - SQLITE_API int sqlite3_column_bytes(sqlite3_stmt*, int iCol); - SQLITE_API int sqlite3_column_bytes16(sqlite3_stmt*, int iCol); - SQLITE_API int sqlite3_column_type(sqlite3_stmt*, int iCol); - - /* +SQLITE_API const void *sqlite3_column_blob(sqlite3_stmt*, int iCol); +SQLITE_API double sqlite3_column_double(sqlite3_stmt*, int iCol); +SQLITE_API int sqlite3_column_int(sqlite3_stmt*, int iCol); +SQLITE_API sqlite3_int64 sqlite3_column_int64(sqlite3_stmt*, int iCol); +SQLITE_API const unsigned char *sqlite3_column_text(sqlite3_stmt*, int iCol); +SQLITE_API const void *sqlite3_column_text16(sqlite3_stmt*, int iCol); +SQLITE_API sqlite3_value *sqlite3_column_value(sqlite3_stmt*, int iCol); +SQLITE_API int sqlite3_column_bytes(sqlite3_stmt*, int iCol); +SQLITE_API int sqlite3_column_bytes16(sqlite3_stmt*, int iCol); +SQLITE_API int sqlite3_column_type(sqlite3_stmt*, int iCol); + +/* ** CAPI3REF: Destroy A Prepared Statement Object ** DESTRUCTOR: sqlite3_stmt ** ** ^The sqlite3_finalize() function is called to delete a [prepared statement]. ** ^If the most recent evaluation of the statement encountered no errors -** or if the statement is never been evaluated, then sqlite3_finalize() returns +** or if the statement has never been evaluated, then sqlite3_finalize() returns ** SQLITE_OK. ^If the most recent evaluation of statement S failed, then ** sqlite3_finalize(S) returns the appropriate [error code] or ** [extended error code]. @@ -4726,9 +5614,9 @@ typedef unsigned long long int sqlite_uint64; ** statement after it has been finalized can result in undefined and ** undesirable behavior such as segfaults and heap corruption. */ - SQLITE_API int sqlite3_finalize(sqlite3_stmt* pStmt); +SQLITE_API int sqlite3_finalize(sqlite3_stmt *pStmt); - /* +/* ** CAPI3REF: Reset A Prepared Statement Object ** METHOD: sqlite3_stmt ** @@ -4741,32 +5629,43 @@ typedef unsigned long long int sqlite_uint64; ** ^The [sqlite3_reset(S)] interface resets the [prepared statement] S ** back to the beginning of its program. ** -** ^If the most recent call to [sqlite3_step(S)] for the -** [prepared statement] S returned [SQLITE_ROW] or [SQLITE_DONE], -** or if [sqlite3_step(S)] has never before been called on S, -** then [sqlite3_reset(S)] returns [SQLITE_OK]. +** ^The return code from [sqlite3_reset(S)] indicates whether or not +** the previous evaluation of prepared statement S completed successfully. +** ^If [sqlite3_step(S)] has never before been called on S or if +** [sqlite3_step(S)] has not been called since the previous call +** to [sqlite3_reset(S)], then [sqlite3_reset(S)] will return +** [SQLITE_OK]. ** ** ^If the most recent call to [sqlite3_step(S)] for the ** [prepared statement] S indicated an error, then ** [sqlite3_reset(S)] returns an appropriate [error code]. +** ^The [sqlite3_reset(S)] interface might also return an [error code] +** if there were no prior errors but the process of resetting +** the prepared statement caused a new error. ^For example, if an +** [INSERT] statement with a [RETURNING] clause is only stepped one time, +** that one call to [sqlite3_step(S)] might return SQLITE_ROW but +** the overall statement might still fail and the [sqlite3_reset(S)] call +** might return SQLITE_BUSY if locking constraints prevent the +** database change from committing. Therefore, it is important that +** applications check the return code from [sqlite3_reset(S)] even if +** no prior call to [sqlite3_step(S)] indicated a problem. ** ** ^The [sqlite3_reset(S)] interface does not change the values ** of any [sqlite3_bind_blob|bindings] on the [prepared statement] S. */ - SQLITE_API int sqlite3_reset(sqlite3_stmt* pStmt); +SQLITE_API int sqlite3_reset(sqlite3_stmt *pStmt); + - /* +/* ** CAPI3REF: Create Or Redefine SQL Functions ** KEYWORDS: {function creation routines} -** KEYWORDS: {application-defined SQL function} -** KEYWORDS: {application-defined SQL functions} ** METHOD: sqlite3 ** ** ^These functions (collectively known as "function creation routines") ** are used to add SQL functions or aggregates or to redefine the behavior ** of existing SQL functions or aggregates. The only differences between -** the three "sqlite3_create_function*" routines are the text encoding -** expected for the second parameter (the name of the function being +** the three "sqlite3_create_function*" routines are the text encoding +** expected for the second parameter (the name of the function being ** created) and the presence or absence of a destructor callback for ** the application data pointer. Function sqlite3_create_window_function() ** is similar, but allows the user to supply the extra callback functions @@ -4780,7 +5679,7 @@ typedef unsigned long long int sqlite_uint64; ** ^The second parameter is the name of the SQL function to be created or ** redefined. ^The length of the name is limited to 255 bytes in a UTF-8 ** representation, exclusive of the zero-terminator. ^Note that the name -** length limit is in UTF-8 bytes, not characters nor UTF-16 bytes. +** length limit is in UTF-8 bytes, not characters nor UTF-16 bytes. ** ^Any attempt to create a function with a longer name ** will result in [SQLITE_MISUSE] being returned. ** @@ -4795,7 +5694,7 @@ typedef unsigned long long int sqlite_uint64; ** ^The fourth parameter, eTextRep, specifies what ** [SQLITE_UTF8 | text encoding] this SQL function prefers for ** its parameters. The application should set this parameter to -** [SQLITE_UTF16LE] if the function implementation invokes +** [SQLITE_UTF16LE] if the function implementation invokes ** [sqlite3_value_text16le()] on an input, or [SQLITE_UTF16BE] if the ** implementation invokes [sqlite3_value_text16be()] on an input, or ** [SQLITE_UTF16] if [sqlite3_value_text16()] is used, or [SQLITE_UTF8] @@ -4813,6 +5712,21 @@ typedef unsigned long long int sqlite_uint64; ** perform additional optimizations on deterministic functions, so use ** of the [SQLITE_DETERMINISTIC] flag is recommended where possible. ** +** ^The fourth parameter may also optionally include the [SQLITE_DIRECTONLY] +** flag, which if present prevents the function from being invoked from +** within VIEWs, TRIGGERs, CHECK constraints, generated column expressions, +** index expressions, or the WHERE clause of partial indexes. +** +** For best security, the [SQLITE_DIRECTONLY] flag is recommended for +** all application-defined SQL functions that do not need to be +** used inside of triggers, views, CHECK constraints, or other elements of +** the database schema. This flag is especially recommended for SQL +** functions that have side effects or reveal internal application state. +** Without this flag, an attacker might be able to modify the schema of +** a database file to include invocations of the function with parameters +** chosen by the attacker, which the application will then execute when +** the database file is opened and read. +** ** ^(The fifth parameter is an arbitrary pointer. The implementation of the ** function can gain access to this pointer using [sqlite3_user_data()].)^ ** @@ -4826,21 +5740,21 @@ typedef unsigned long long int sqlite_uint64; ** SQL function or aggregate, pass NULL pointers for all three function ** callbacks. ** -** ^The sixth, seventh, eighth and ninth parameters (xStep, xFinal, xValue +** ^The sixth, seventh, eighth and ninth parameters (xStep, xFinal, xValue ** and xInverse) passed to sqlite3_create_window_function are pointers to ** C-language callbacks that implement the new function. xStep and xFinal ** must both be non-NULL. xValue and xInverse may either both be NULL, in -** which case a regular aggregate function is created, or must both be +** which case a regular aggregate function is created, or must both be ** non-NULL, in which case the new function may be used as either an aggregate ** or aggregate window function. More details regarding the implementation -** of aggregate window functions are +** of aggregate window functions are ** [user-defined window functions|available here]. ** ** ^(If the final parameter to sqlite3_create_function_v2() or -** sqlite3_create_window_function() is not NULL, then it is destructor for -** the application data pointer. The destructor is invoked when the function -** is deleted, either by being overloaded or when the database connection -** closes.)^ ^The destructor is also invoked if the call to +** sqlite3_create_window_function() is not NULL, then it is the destructor for +** the application data pointer. The destructor is invoked when the function +** is deleted, either by being overloaded or when the database connection +** closes.)^ ^The destructor is also invoked if the call to ** sqlite3_create_function_v2() fails. ^When the destructor callback is ** invoked, it is passed a single argument which is a copy of the application ** data pointer which was the fifth parameter to sqlite3_create_function_v2(). @@ -4853,7 +5767,7 @@ typedef unsigned long long int sqlite_uint64; ** nArg parameter is a better match than a function implementation with ** a negative nArg. ^A function where the preferred text encoding ** matches the database encoding is a better -** match than a function where the encoding is different. +** match than a function where the encoding is different. ** ^A function where the encoding difference is between UTF16le and UTF16be ** is a closer match than a function where the encoding difference is ** between UTF8 and UTF16. @@ -4865,90 +5779,237 @@ typedef unsigned long long int sqlite_uint64; ** close the database connection nor finalize or reset the prepared ** statement in which the function is running. */ - SQLITE_API int sqlite3_create_function( - sqlite3* db, - const char* zFunctionName, - int nArg, - int eTextRep, - void* pApp, - void (*xFunc)(sqlite3_context*, int, sqlite3_value**), - void (*xStep)(sqlite3_context*, int, sqlite3_value**), - void (*xFinal)(sqlite3_context*)); - SQLITE_API int sqlite3_create_function16( - sqlite3* db, - const void* zFunctionName, - int nArg, - int eTextRep, - void* pApp, - void (*xFunc)(sqlite3_context*, int, sqlite3_value**), - void (*xStep)(sqlite3_context*, int, sqlite3_value**), - void (*xFinal)(sqlite3_context*)); - SQLITE_API int sqlite3_create_function_v2( - sqlite3* db, - const char* zFunctionName, - int nArg, - int eTextRep, - void* pApp, - void (*xFunc)(sqlite3_context*, int, sqlite3_value**), - void (*xStep)(sqlite3_context*, int, sqlite3_value**), - void (*xFinal)(sqlite3_context*), - void (*xDestroy)(void*)); - SQLITE_API int sqlite3_create_window_function( - sqlite3* db, - const char* zFunctionName, - int nArg, - int eTextRep, - void* pApp, - void (*xStep)(sqlite3_context*, int, sqlite3_value**), - void (*xFinal)(sqlite3_context*), - void (*xValue)(sqlite3_context*), - void (*xInverse)(sqlite3_context*, int, sqlite3_value**), - void (*xDestroy)(void*)); +SQLITE_API int sqlite3_create_function( + sqlite3 *db, + const char *zFunctionName, + int nArg, + int eTextRep, + void *pApp, + void (*xFunc)(sqlite3_context*,int,sqlite3_value**), + void (*xStep)(sqlite3_context*,int,sqlite3_value**), + void (*xFinal)(sqlite3_context*) +); +SQLITE_API int sqlite3_create_function16( + sqlite3 *db, + const void *zFunctionName, + int nArg, + int eTextRep, + void *pApp, + void (*xFunc)(sqlite3_context*,int,sqlite3_value**), + void (*xStep)(sqlite3_context*,int,sqlite3_value**), + void (*xFinal)(sqlite3_context*) +); +SQLITE_API int sqlite3_create_function_v2( + sqlite3 *db, + const char *zFunctionName, + int nArg, + int eTextRep, + void *pApp, + void (*xFunc)(sqlite3_context*,int,sqlite3_value**), + void (*xStep)(sqlite3_context*,int,sqlite3_value**), + void (*xFinal)(sqlite3_context*), + void(*xDestroy)(void*) +); +SQLITE_API int sqlite3_create_window_function( + sqlite3 *db, + const char *zFunctionName, + int nArg, + int eTextRep, + void *pApp, + void (*xStep)(sqlite3_context*,int,sqlite3_value**), + void (*xFinal)(sqlite3_context*), + void (*xValue)(sqlite3_context*), + void (*xInverse)(sqlite3_context*,int,sqlite3_value**), + void(*xDestroy)(void*) +); /* ** CAPI3REF: Text Encodings ** -** These constant define integer codes that represent the various +** These constants define integer codes that represent the various ** text encodings supported by SQLite. +** +**
    +** [[SQLITE_UTF8]]
    SQLITE_UTF8
    Text is encoding as UTF-8
    +** +** [[SQLITE_UTF16LE]]
    SQLITE_UTF16LE
    Text is encoding as UTF-16 +** with each code point being expressed "little endian" - the least significant +** byte first. This is the usual encoding, for example on Windows.
    +** +** [[SQLITE_UTF16BE]]
    SQLITE_UTF16BE
    Text is encoding as UTF-16 +** with each code point being expressed "big endian" - the most significant +** byte first. This encoding is less common, but is still sometimes seen, +** specially on older systems. +** +** [[SQLITE_UTF16]]
    SQLITE_UTF16
    Text is encoding as UTF-16 +** with each code point being expressed either little endian or as big +** endian, according to the native endianness of the host computer. +** +** [[SQLITE_ANY]]
    SQLITE_ANY
    This encoding value may only be used +** to declare the preferred text for [application-defined SQL functions] +** created using [sqlite3_create_function()] and similar. If the preferred +** encoding (the 4th parameter to sqlite3_create_function() - the eTextRep +** parameter) is SQLITE_ANY, that indicates that the function does not have +** a preference regarding the text encoding of its parameters and can take +** any text encoding that the SQLite core find convenient to supply. This +** option is deprecated. Please do not use it in new applications. +** +** [[SQLITE_UTF16_ALIGNED]]
    SQLITE_UTF16_ALIGNED
    This encoding +** value may be used as the 3rd parameter (the eTextRep parameter) to +** [sqlite3_create_collation()] and similar. This encoding value means +** that the application-defined collating sequence created expects its +** input strings to be in UTF16 in native byte order, and that the start +** of the strings must be aligned to a 2-byte boundary. +** +** [[SQLITE_UTF8_ZT]]
    SQLITE_UTF8_ZT
    This option can only be +** used to specify the text encoding to strings input to +** [sqlite3_result_text64()] and [sqlite3_bind_text64()]. +** The SQLITE_UTF8_ZT encoding means that the input string (call it "z") +** is UTF-8 encoded and that it is zero-terminated. If the length parameter +** (call it "n") is non-negative, this encoding option means that the caller +** guarantees that z array contains at least n+1 bytes and that the z[n] +** byte has a value of zero. +** This option gives the same output as SQLITE_UTF8, but can be more efficient +** by avoiding the need to make a copy of the input string, in some cases. +** However, if z is allocated to hold fewer than n+1 bytes or if the +** z[n] byte is not zero, undefined behavior may result. +**
    */ -#define SQLITE_UTF8 1 /* IMP: R-37514-35566 */ -#define SQLITE_UTF16LE 2 /* IMP: R-03371-37637 */ -#define SQLITE_UTF16BE 3 /* IMP: R-51971-34154 */ -#define SQLITE_UTF16 4 /* Use native byte order */ -#define SQLITE_ANY 5 /* Deprecated */ -#define SQLITE_UTF16_ALIGNED 8 /* sqlite3_create_collation only */ +#define SQLITE_UTF8 1 /* IMP: R-37514-35566 */ +#define SQLITE_UTF16LE 2 /* IMP: R-03371-37637 */ +#define SQLITE_UTF16BE 3 /* IMP: R-51971-34154 */ +#define SQLITE_UTF16 4 /* Use native byte order */ +#define SQLITE_ANY 5 /* Deprecated */ +#define SQLITE_UTF16_ALIGNED 8 /* sqlite3_create_collation only */ +#define SQLITE_UTF8_ZT 16 /* Zero-terminated UTF8 */ /* ** CAPI3REF: Function Flags ** -** These constants may be ORed together with the +** These constants may be ORed together with the ** [SQLITE_UTF8 | preferred text encoding] as the fourth argument ** to [sqlite3_create_function()], [sqlite3_create_function16()], or ** [sqlite3_create_function_v2()]. +** +**
    +** [[SQLITE_DETERMINISTIC]]
    SQLITE_DETERMINISTIC
    +** The SQLITE_DETERMINISTIC flag means that the new function always gives +** the same output when the input parameters are the same. +** The [abs|abs() function] is deterministic, for example, but +** [randomblob|randomblob()] is not. Functions must +** be deterministic in order to be used in certain contexts such as +** with the WHERE clause of [partial indexes] or in [generated columns]. +** SQLite might also optimize deterministic functions by factoring them +** out of inner loops. +**
    +** +** [[SQLITE_DIRECTONLY]]
    SQLITE_DIRECTONLY
    +** The SQLITE_DIRECTONLY flag means that the function may only be invoked +** from top-level SQL, and cannot be used in VIEWs or TRIGGERs nor in +** schema structures such as [CHECK constraints], [DEFAULT clauses], +** [expression indexes], [partial indexes], or [generated columns]. +**

    +** The SQLITE_DIRECTONLY flag is recommended for any +** [application-defined SQL function] +** that has side-effects or that could potentially leak sensitive information. +** This will prevent attacks in which an application is tricked +** into using a database file that has had its schema surreptitiously +** modified to invoke the application-defined function in ways that are +** harmful. +**

    +** Some people say it is good practice to set SQLITE_DIRECTONLY on all +** [application-defined SQL functions], regardless of whether or not they +** are security sensitive, as doing so prevents those functions from being used +** inside of the database schema, and thus ensures that the database +** can be inspected and modified using generic tools (such as the [CLI]) +** that do not have access to the application-defined functions. +**

    +** +** [[SQLITE_INNOCUOUS]]
    SQLITE_INNOCUOUS
    +** The SQLITE_INNOCUOUS flag means that the function is unlikely +** to cause problems even if misused. An innocuous function should have +** no side effects and should not depend on any values other than its +** input parameters. The [abs|abs() function] is an example of an +** innocuous function. +** The [load_extension() SQL function] is not innocuous because of its +** side effects. +**

    SQLITE_INNOCUOUS is similar to SQLITE_DETERMINISTIC, but is not +** exactly the same. The [random|random() function] is an example of a +** function that is innocuous but not deterministic. +**

    Some heightened security settings +** ([SQLITE_DBCONFIG_TRUSTED_SCHEMA] and [PRAGMA trusted_schema=OFF]) +** disable the use of SQL functions inside views and triggers and in +** schema structures such as [CHECK constraints], [DEFAULT clauses], +** [expression indexes], [partial indexes], and [generated columns] unless +** the function is tagged with SQLITE_INNOCUOUS. Most built-in functions +** are innocuous. Developers are advised to avoid using the +** SQLITE_INNOCUOUS flag for application-defined functions unless the +** function has been carefully audited and found to be free of potentially +** security-adverse side-effects and information-leaks. +**

    +** +** [[SQLITE_SUBTYPE]]
    SQLITE_SUBTYPE
    +** The SQLITE_SUBTYPE flag indicates to SQLite that a function might call +** [sqlite3_value_subtype()] to inspect the sub-types of its arguments. +** This flag instructs SQLite to omit some corner-case optimizations that +** might disrupt the operation of the [sqlite3_value_subtype()] function, +** causing it to return zero rather than the correct subtype(). +** All SQL functions that invoke [sqlite3_value_subtype()] should have this +** property. If the SQLITE_SUBTYPE property is omitted, then the return +** value from [sqlite3_value_subtype()] might sometimes be zero even though +** a non-zero subtype was specified by the function argument expression. +** +** [[SQLITE_RESULT_SUBTYPE]]
    SQLITE_RESULT_SUBTYPE
    +** The SQLITE_RESULT_SUBTYPE flag indicates to SQLite that a function might call +** [sqlite3_result_subtype()] to cause a sub-type to be associated with its +** result. +** Every function that invokes [sqlite3_result_subtype()] should have this +** property. If it does not, then the call to [sqlite3_result_subtype()] +** might become a no-op if the function is used as a term in an +** [expression index]. On the other hand, SQL functions that never invoke +** [sqlite3_result_subtype()] should avoid setting this property, as the +** purpose of this property is to disable certain optimizations that are +** incompatible with subtypes. +** +** [[SQLITE_SELFORDER1]]
    SQLITE_SELFORDER1
    +** The SQLITE_SELFORDER1 flag indicates that the function is an aggregate +** that internally orders the values provided to the first argument. The +** ordered-set aggregate SQL notation with a single ORDER BY term can be +** used to invoke this function. If the ordered-set aggregate notation is +** used on a function that lacks this flag, then an error is raised. Note +** that the ordered-set aggregate syntax is only available if SQLite is +** built using the -DSQLITE_ENABLE_ORDERED_SET_AGGREGATES compile-time option. +**
    +**
    */ -#define SQLITE_DETERMINISTIC 0x800 +#define SQLITE_DETERMINISTIC 0x000000800 +#define SQLITE_DIRECTONLY 0x000080000 +#define SQLITE_SUBTYPE 0x000100000 +#define SQLITE_INNOCUOUS 0x000200000 +#define SQLITE_RESULT_SUBTYPE 0x001000000 +#define SQLITE_SELFORDER1 0x002000000 /* ** CAPI3REF: Deprecated Functions ** DEPRECATED ** ** These functions are [deprecated]. In order to maintain -** backwards compatibility with older code, these functions continue +** backwards compatibility with older code, these functions continue ** to be supported. However, new applications should avoid ** the use of these functions. To encourage programmers to avoid ** these functions, we will not explain what they do. */ #ifndef SQLITE_OMIT_DEPRECATED - SQLITE_API SQLITE_DEPRECATED int sqlite3_aggregate_count(sqlite3_context*); - SQLITE_API SQLITE_DEPRECATED int sqlite3_expired(sqlite3_stmt*); - SQLITE_API SQLITE_DEPRECATED int sqlite3_transfer_bindings(sqlite3_stmt*, sqlite3_stmt*); - SQLITE_API SQLITE_DEPRECATED int sqlite3_global_recover(void); - SQLITE_API SQLITE_DEPRECATED void sqlite3_thread_cleanup(void); - SQLITE_API SQLITE_DEPRECATED int sqlite3_memory_alarm(void (*)(void*, sqlite3_int64, int), - void*, sqlite3_int64); +SQLITE_API SQLITE_DEPRECATED int sqlite3_aggregate_count(sqlite3_context*); +SQLITE_API SQLITE_DEPRECATED int sqlite3_expired(sqlite3_stmt*); +SQLITE_API SQLITE_DEPRECATED int sqlite3_transfer_bindings(sqlite3_stmt*, sqlite3_stmt*); +SQLITE_API SQLITE_DEPRECATED int sqlite3_global_recover(void); +SQLITE_API SQLITE_DEPRECATED void sqlite3_thread_cleanup(void); +SQLITE_API SQLITE_DEPRECATED int sqlite3_memory_alarm(void(*)(void*,sqlite3_int64,int), + void*,sqlite3_int64); #endif - /* +/* ** CAPI3REF: Obtaining SQL Values ** METHOD: sqlite3_value ** @@ -4985,8 +6046,8 @@ typedef unsigned long long int sqlite_uint64; ** ** These routines extract type, size, and content information from ** [protected sqlite3_value] objects. Protected sqlite3_value objects -** are used to pass parameter information into implementation of -** [application-defined SQL functions] and [virtual tables]. +** are used to pass parameter information into the functions that +** implement [application-defined SQL functions] and [virtual tables]. ** ** These routines work only with [protected sqlite3_value] objects. ** Any attempt to use these routines on an [unprotected sqlite3_value] @@ -5001,11 +6062,11 @@ typedef unsigned long long int sqlite_uint64; ** sqlite3_value_text16be() and sqlite3_value_text16le() interfaces ** extract UTF-16 strings as big-endian and little-endian respectively. ** -** ^If [sqlite3_value] object V was initialized +** ^If [sqlite3_value] object V was initialized ** using [sqlite3_bind_pointer(S,I,P,X,D)] or [sqlite3_result_pointer(C,P,X,D)] ** and if X and Y are strings that compare equal according to strcmp(X,Y), ** then sqlite3_value_pointer(V,Y) will return the pointer P. ^Otherwise, -** sqlite3_value_pointer(V,Y) returns a NULL. The sqlite3_bind_pointer() +** sqlite3_value_pointer(V,Y) returns a NULL. The sqlite3_bind_pointer() ** routine is part of the [pointer passing interface] added for SQLite 3.20.0. ** ** ^(The sqlite3_value_type(V) interface returns the @@ -5031,7 +6092,7 @@ typedef unsigned long long int sqlite_uint64; ** sqlite3_value_nochange(X) interface returns true if and only if ** the column corresponding to X is unchanged by the UPDATE operation ** that the xUpdate method call was invoked to implement and if -** and the prior [xColumn] method call that was invoked to extracted +** the prior [xColumn] method call that was invoked to extract ** the value for that column returned without setting a result (probably ** because it queried [sqlite3_vtab_nochange()] and found that the column ** was unchanging). ^Within an [xUpdate] method, any value for which @@ -5043,7 +6104,7 @@ typedef unsigned long long int sqlite_uint64; ** ^The sqlite3_value_frombind(X) interface returns non-zero if the ** value X originated from one of the [sqlite3_bind_int|sqlite3_bind()] ** interfaces. ^If X comes from an SQL literal value, or a table column, -** and expression, then sqlite3_value_frombind(X) returns zero. +** or an expression, then sqlite3_value_frombind(X) returns zero. ** ** Please pay particular attention to the fact that the pointer returned ** from [sqlite3_value_blob()], [sqlite3_value_text()], or @@ -5055,44 +6116,63 @@ typedef unsigned long long int sqlite_uint64; ** the SQL function that supplied the [sqlite3_value*] parameters. ** ** As long as the input parameter is correct, these routines can only -** fail if an out-of-memory error occurs during a format conversion. -** Only the following subset of interfaces are subject to out-of-memory -** errors: -** -**
      -**
    • sqlite3_value_blob() -**
    • sqlite3_value_text() -**
    • sqlite3_value_text16() -**
    • sqlite3_value_text16le() -**
    • sqlite3_value_text16be() -**
    • sqlite3_value_bytes() -**
    • sqlite3_value_bytes16() -**
    -** +** fail if an out-of-memory error occurs while trying to do a +** UTF8→UTF16 or UTF16→UTF8 conversion. ** If an out-of-memory error occurs, then the return value from these ** routines is the same as if the column had contained an SQL NULL value. -** Valid SQL NULL returns can be distinguished from out-of-memory errors -** by invoking the [sqlite3_errcode()] immediately after the suspect +** If the input sqlite3_value was not obtained from [sqlite3_value_dup()], +** then valid SQL NULL returns can also be distinguished from +** out-of-memory errors after extracting the value +** by invoking the [sqlite3_errcode()] immediately after the suspicious ** return value is obtained and before any ** other SQLite interface is called on the same [database connection]. -*/ - SQLITE_API const void* sqlite3_value_blob(sqlite3_value*); - SQLITE_API double sqlite3_value_double(sqlite3_value*); - SQLITE_API int sqlite3_value_int(sqlite3_value*); - SQLITE_API sqlite3_int64 sqlite3_value_int64(sqlite3_value*); - SQLITE_API void* sqlite3_value_pointer(sqlite3_value*, const char*); - SQLITE_API const unsigned char* sqlite3_value_text(sqlite3_value*); - SQLITE_API const void* sqlite3_value_text16(sqlite3_value*); - SQLITE_API const void* sqlite3_value_text16le(sqlite3_value*); - SQLITE_API const void* sqlite3_value_text16be(sqlite3_value*); - SQLITE_API int sqlite3_value_bytes(sqlite3_value*); - SQLITE_API int sqlite3_value_bytes16(sqlite3_value*); - SQLITE_API int sqlite3_value_type(sqlite3_value*); - SQLITE_API int sqlite3_value_numeric_type(sqlite3_value*); - SQLITE_API int sqlite3_value_nochange(sqlite3_value*); - SQLITE_API int sqlite3_value_frombind(sqlite3_value*); - - /* +** If the input sqlite3_value was obtained from sqlite3_value_dup() then +** it is disconnected from the database connection and so sqlite3_errcode() +** will not work. +** In that case, the only way to distinguish an out-of-memory +** condition from a true SQL NULL is to invoke sqlite3_value_type() on the +** input to see if it is NULL prior to trying to extract the value. +*/ +SQLITE_API const void *sqlite3_value_blob(sqlite3_value*); +SQLITE_API double sqlite3_value_double(sqlite3_value*); +SQLITE_API int sqlite3_value_int(sqlite3_value*); +SQLITE_API sqlite3_int64 sqlite3_value_int64(sqlite3_value*); +SQLITE_API void *sqlite3_value_pointer(sqlite3_value*, const char*); +SQLITE_API const unsigned char *sqlite3_value_text(sqlite3_value*); +SQLITE_API const void *sqlite3_value_text16(sqlite3_value*); +SQLITE_API const void *sqlite3_value_text16le(sqlite3_value*); +SQLITE_API const void *sqlite3_value_text16be(sqlite3_value*); +SQLITE_API int sqlite3_value_bytes(sqlite3_value*); +SQLITE_API int sqlite3_value_bytes16(sqlite3_value*); +SQLITE_API int sqlite3_value_type(sqlite3_value*); +SQLITE_API int sqlite3_value_numeric_type(sqlite3_value*); +SQLITE_API int sqlite3_value_nochange(sqlite3_value*); +SQLITE_API int sqlite3_value_frombind(sqlite3_value*); + +/* +** CAPI3REF: Report the internal text encoding state of an sqlite3_value object +** METHOD: sqlite3_value +** +** ^(The sqlite3_value_encoding(X) interface returns one of [SQLITE_UTF8], +** [SQLITE_UTF16BE], or [SQLITE_UTF16LE] according to the current text encoding +** of the value X, assuming that X has type TEXT.)^ If sqlite3_value_type(X) +** returns something other than SQLITE_TEXT, then the return value from +** sqlite3_value_encoding(X) is meaningless. ^Calls to +** [sqlite3_value_text(X)], [sqlite3_value_text16(X)], +** [sqlite3_value_text16be(X)], +** [sqlite3_value_text16le(X)], [sqlite3_value_bytes(X)], or +** [sqlite3_value_bytes16(X)] might change the encoding of the value X and +** thus change the return from subsequent calls to sqlite3_value_encoding(X). +** +** This routine is intended for used by applications that test and validate +** the SQLite implementation. This routine is inquiring about the opaque +** internal state of an [sqlite3_value] object. Ordinary applications should +** not need to know what the internal state of an sqlite3_value object is and +** hence should not need to use this interface. +*/ +SQLITE_API int sqlite3_value_encoding(sqlite3_value*); + +/* ** CAPI3REF: Finding The Subtype Of SQL Values ** METHOD: sqlite3_value ** @@ -5101,36 +6181,43 @@ typedef unsigned long long int sqlite_uint64; ** information can be used to pass a limited amount of context from ** one SQL function to another. Use the [sqlite3_result_subtype()] ** routine to set the subtype for the return value of an SQL function. +** +** Every [application-defined SQL function] that invokes this interface +** should include the [SQLITE_SUBTYPE] property in the text +** encoding argument when the function is [sqlite3_create_function|registered]. +** If the [SQLITE_SUBTYPE] property is omitted, then sqlite3_value_subtype() +** might return zero instead of the upstream subtype in some corner cases. */ - SQLITE_API unsigned int sqlite3_value_subtype(sqlite3_value*); +SQLITE_API unsigned int sqlite3_value_subtype(sqlite3_value*); - /* +/* ** CAPI3REF: Copy And Free SQL Values ** METHOD: sqlite3_value ** ** ^The sqlite3_value_dup(V) interface makes a copy of the [sqlite3_value] -** object D and returns a pointer to that copy. ^The [sqlite3_value] returned +** object V and returns a pointer to that copy. ^The [sqlite3_value] returned ** is a [protected sqlite3_value] object even if the input is not. ** ^The sqlite3_value_dup(V) interface returns NULL if V is NULL or if a -** memory allocation fails. +** memory allocation fails. ^If V is a [pointer value], then the result +** of sqlite3_value_dup(V) is a NULL value. ** ** ^The sqlite3_value_free(V) interface frees an [sqlite3_value] object ** previously obtained from [sqlite3_value_dup()]. ^If V is a NULL pointer ** then sqlite3_value_free(V) is a harmless no-op. */ - SQLITE_API sqlite3_value* sqlite3_value_dup(const sqlite3_value*); - SQLITE_API void sqlite3_value_free(sqlite3_value*); +SQLITE_API sqlite3_value *sqlite3_value_dup(const sqlite3_value*); +SQLITE_API void sqlite3_value_free(sqlite3_value*); - /* +/* ** CAPI3REF: Obtain Aggregate Function Context ** METHOD: sqlite3_context ** ** Implementations of aggregate SQL functions use this ** routine to allocate memory for storing their state. ** -** ^The first time the sqlite3_aggregate_context(C,N) routine is called -** for a particular aggregate function, SQLite -** allocates N of memory, zeroes out that memory, and returns a pointer +** ^The first time the sqlite3_aggregate_context(C,N) routine is called +** for a particular aggregate function, SQLite allocates +** N bytes of memory, zeroes out that memory, and returns a pointer ** to the new memory. ^On second and subsequent calls to ** sqlite3_aggregate_context() for the same aggregate function instance, ** the same buffer is returned. Sqlite3_aggregate_context() is normally @@ -5141,19 +6228,19 @@ typedef unsigned long long int sqlite_uint64; ** In those cases, sqlite3_aggregate_context() might be called for the ** first time from within xFinal().)^ ** -** ^The sqlite3_aggregate_context(C,N) routine returns a NULL pointer +** ^The sqlite3_aggregate_context(C,N) routine returns a NULL pointer ** when first called if N is less than or equal to zero or if a memory -** allocate error occurs. +** allocation error occurs. ** ** ^(The amount of space allocated by sqlite3_aggregate_context(C,N) is -** determined by the N parameter on first successful call. Changing the -** value of N in subsequent call to sqlite3_aggregate_context() within +** determined by the N parameter on the first successful call. Changing the +** value of N in any subsequent call to sqlite3_aggregate_context() within ** the same aggregate function instance will not resize the memory ** allocation.)^ Within the xFinal callback, it is customary to set -** N=0 in calls to sqlite3_aggregate_context(C,N) so that no +** N=0 in calls to sqlite3_aggregate_context(C,N) so that no ** pointless memory allocations occur. ** -** ^SQLite automatically frees the memory allocated by +** ^SQLite automatically frees the memory allocated by ** sqlite3_aggregate_context() when the aggregate query concludes. ** ** The first parameter must be a copy of the @@ -5164,9 +6251,9 @@ typedef unsigned long long int sqlite_uint64; ** This routine must be called from the same thread in which ** the aggregate SQL function is running. */ - SQLITE_API void* sqlite3_aggregate_context(sqlite3_context*, int nBytes); +SQLITE_API void *sqlite3_aggregate_context(sqlite3_context*, int nBytes); - /* +/* ** CAPI3REF: User Data For Functions ** METHOD: sqlite3_context ** @@ -5179,9 +6266,9 @@ typedef unsigned long long int sqlite_uint64; ** This routine must be called from the same thread in which ** the application-defined function is running. */ - SQLITE_API void* sqlite3_user_data(sqlite3_context*); +SQLITE_API void *sqlite3_user_data(sqlite3_context*); - /* +/* ** CAPI3REF: Database Connection For Functions ** METHOD: sqlite3_context ** @@ -5191,55 +6278,63 @@ typedef unsigned long long int sqlite_uint64; ** and [sqlite3_create_function16()] routines that originally ** registered the application defined function. */ - SQLITE_API sqlite3* sqlite3_context_db_handle(sqlite3_context*); +SQLITE_API sqlite3 *sqlite3_context_db_handle(sqlite3_context*); - /* +/* ** CAPI3REF: Function Auxiliary Data ** METHOD: sqlite3_context ** ** These functions may be used by (non-aggregate) SQL functions to -** associate metadata with argument values. If the same value is passed to -** multiple invocations of the same SQL function during query execution, under -** some circumstances the associated metadata may be preserved. An example -** of where this might be useful is in a regular-expression matching -** function. The compiled version of the regular expression can be stored as -** metadata associated with the pattern string. -** Then as long as the pattern string remains the same, +** associate auxiliary data with argument values. If the same argument +** value is passed to multiple invocations of the same SQL function during +** query execution, under some circumstances the associated auxiliary data +** might be preserved. An example of where this might be useful is in a +** regular-expression matching function. The compiled version of the regular +** expression can be stored as auxiliary data associated with the pattern +** string. Then as long as the pattern string remains the same, ** the compiled regular expression can be reused on multiple ** invocations of the same function. ** -** ^The sqlite3_get_auxdata(C,N) interface returns a pointer to the metadata -** associated by the sqlite3_set_auxdata(C,N,P,X) function with the Nth argument -** value to the application-defined function. ^N is zero for the left-most -** function argument. ^If there is no metadata -** associated with the function argument, the sqlite3_get_auxdata(C,N) interface -** returns a NULL pointer. +** ^The sqlite3_get_auxdata(C,N) interface returns a pointer to the auxiliary +** data associated by the sqlite3_set_auxdata(C,N,P,X) function with the +** Nth argument value to the application-defined function. ^N is zero +** for the left-most function argument. ^If there is no auxiliary data +** associated with the function argument, the sqlite3_get_auxdata(C,N) +** interface returns a NULL pointer. ** -** ^The sqlite3_set_auxdata(C,N,P,X) interface saves P as metadata for the N-th -** argument of the application-defined function. ^Subsequent +** ^The sqlite3_set_auxdata(C,N,P,X) interface saves P as auxiliary data for the +** N-th argument of the application-defined function. ^Subsequent ** calls to sqlite3_get_auxdata(C,N) return P from the most recent -** sqlite3_set_auxdata(C,N,P,X) call if the metadata is still valid or -** NULL if the metadata has been discarded. +** sqlite3_set_auxdata(C,N,P,X) call if the auxiliary data is still valid or +** NULL if the auxiliary data has been discarded. ** ^After each call to sqlite3_set_auxdata(C,N,P,X) where X is not NULL, ** SQLite will invoke the destructor function X with parameter P exactly -** once, when the metadata is discarded. -** SQLite is free to discard the metadata at any time, including:
      +** once, when the auxiliary data is discarded. +** SQLite is free to discard the auxiliary data at any time, including:
        **
      • ^(when the corresponding function parameter changes)^, or **
      • ^(when [sqlite3_reset()] or [sqlite3_finalize()] is called for the ** SQL statement)^, or **
      • ^(when sqlite3_set_auxdata() is invoked again on the same ** parameter)^, or -**
      • ^(during the original sqlite3_set_auxdata() call when a memory -** allocation error occurs.)^
      +**
    • ^(during the original sqlite3_set_auxdata() call when a memory +** allocation error occurs.)^ +**
    • ^(during the original sqlite3_set_auxdata() call if the function +** is evaluated during query planning instead of during query execution, +** as sometimes happens with [SQLITE_ENABLE_STAT4].)^
    ** -** Note the last bullet in particular. The destructor X in +** Note the last two bullets in particular. The destructor X in ** sqlite3_set_auxdata(C,N,P,X) might be called immediately, before the ** sqlite3_set_auxdata() interface even returns. Hence sqlite3_set_auxdata() ** should be called near the end of the function implementation and the ** function implementation should not make any use of P after -** sqlite3_set_auxdata() has been called. -** -** ^(In practice, metadata is preserved between function calls for +** sqlite3_set_auxdata() has been called. Furthermore, a call to +** sqlite3_get_auxdata() that occurs immediately after a corresponding call +** to sqlite3_set_auxdata() might still return NULL if an out-of-memory +** condition occurred during the sqlite3_set_auxdata() call or if the +** function is being evaluated during query planning rather than during +** query execution. +** +** ^(In practice, auxiliary data is preserved between function calls for ** function parameters that are compile-time constants, including literal ** values and [parameters] and expressions composed from the same.)^ ** @@ -5249,11 +6344,74 @@ typedef unsigned long long int sqlite_uint64; ** ** These routines must be called from the same thread in which ** the SQL function is running. +** +** See also: [sqlite3_get_clientdata()] and [sqlite3_set_clientdata()]. +*/ +SQLITE_API void *sqlite3_get_auxdata(sqlite3_context*, int N); +SQLITE_API void sqlite3_set_auxdata(sqlite3_context*, int N, void*, void (*)(void*)); + +/* +** CAPI3REF: Database Connection Client Data +** METHOD: sqlite3 +** +** These functions are used to associate one or more named pointers +** with a [database connection]. +** A call to sqlite3_set_clientdata(D,N,P,X) causes the pointer P +** to be attached to [database connection] D using name N. Subsequent +** calls to sqlite3_get_clientdata(D,N) will return a copy of pointer P +** or a NULL pointer if there were no prior calls to +** sqlite3_set_clientdata() with the same values of D and N. +** Names are compared using strcmp() and are thus case sensitive. +** It returns 0 on success and SQLITE_NOMEM on allocation failure. +** +** If P and X are both non-NULL, then the destructor X is invoked with +** argument P on the first of the following occurrences: +**
      +**
    • An out-of-memory error occurs during the call to +** sqlite3_set_clientdata() which attempts to register pointer P. +**
    • A subsequent call to sqlite3_set_clientdata(D,N,P,X) is made +** with the same D and N parameters. +**
    • The database connection closes. SQLite does not make any guarantees +** about the order in which destructors are called, only that all +** destructors will be called exactly once at some point during the +** database connection closing process. +**
    +** +** SQLite does not do anything with client data other than invoke +** destructors on the client data at the appropriate time. The intended +** use for client data is to provide a mechanism for wrapper libraries +** to store additional information about an SQLite database connection. +** +** There is no limit (other than available memory) on the number of different +** client data pointers (with different names) that can be attached to a +** single database connection. However, the current implementation stores +** the content on a linked list. Insert and retrieval performance will +** be proportional to the number of entries. The design use case, and +** the use case for which the implementation is optimized, is +** that an application will store only small number of client data names, +** typically just one or two. This interface is not intended to be a +** generalized key/value store for thousands or millions of keys. It +** will work for that, but performance might be disappointing. +** +** There is no way to enumerate the client data pointers +** associated with a database connection. The N parameter can be thought +** of as a secret key such that only code that knows the secret key is able +** to access the associated data. +** +** Security Warning: These interfaces should not be exposed in scripting +** languages or in other circumstances where it might be possible for an +** attacker to invoke them. Any agent that can invoke these interfaces +** can probably also take control of the process. +** +** Database connection client data is only available for SQLite +** version 3.44.0 ([dateof:3.44.0]) and later. +** +** See also: [sqlite3_set_auxdata()] and [sqlite3_get_auxdata()]. */ - SQLITE_API void* sqlite3_get_auxdata(sqlite3_context*, int N); - SQLITE_API void sqlite3_set_auxdata(sqlite3_context*, int N, void*, void (*)(void*)); +SQLITE_API void *sqlite3_get_clientdata(sqlite3*,const char*); +SQLITE_API int sqlite3_set_clientdata(sqlite3*, const char*, void*, void(*)(void*)); - /* +/* ** CAPI3REF: Constants Defining Special Destructor Behavior ** ** These are special values for the destructor that is passed in as the @@ -5267,11 +6425,11 @@ typedef unsigned long long int sqlite_uint64; ** The typedef is necessary to work around problems in certain ** C++ compilers. */ - typedef void (*sqlite3_destructor_type)(void*); -#define SQLITE_STATIC ((sqlite3_destructor_type)0) -#define SQLITE_TRANSIENT ((sqlite3_destructor_type) - 1) +typedef void (*sqlite3_destructor_type)(void*); +#define SQLITE_STATIC ((sqlite3_destructor_type)0) +#define SQLITE_TRANSIENT ((sqlite3_destructor_type)-1) - /* +/* ** CAPI3REF: Setting The Result Of An SQL Function ** METHOD: sqlite3_context ** @@ -5303,8 +6461,9 @@ typedef unsigned long long int sqlite_uint64; ** 2nd parameter of sqlite3_result_error() or sqlite3_result_error16() ** as the text of an error message. ^SQLite interprets the error ** message string from sqlite3_result_error() as UTF-8. ^SQLite -** interprets the string from sqlite3_result_error16() as UTF-16 in native -** byte order. ^If the third parameter to sqlite3_result_error() +** interprets the string from sqlite3_result_error16() as UTF-16 using +** the same [byte-order determination rules] as [sqlite3_bind_text16()]. +** ^If the third parameter to sqlite3_result_error() ** or sqlite3_result_error16() is negative then SQLite takes as the error ** message all text up through the first zero character. ** ^If the third parameter to sqlite3_result_error() or @@ -5340,21 +6499,26 @@ typedef unsigned long long int sqlite_uint64; ** set the return value of the application-defined function to be ** a text string which is represented as UTF-8, UTF-16 native byte order, ** UTF-16 little endian, or UTF-16 big endian, respectively. -** ^The sqlite3_result_text64() interface sets the return value of an +** ^The sqlite3_result_text64(C,Z,N,D,E) interface sets the return value of an ** application-defined function to be a text string in an encoding -** specified by the fifth (and last) parameter, which must be one -** of [SQLITE_UTF8], [SQLITE_UTF16], [SQLITE_UTF16BE], or [SQLITE_UTF16LE]. +** specified the E parameter, which must be one +** of [SQLITE_UTF8], [SQLITE_UTF8_ZT], [SQLITE_UTF16], [SQLITE_UTF16BE], +** or [SQLITE_UTF16LE]. ^The special value [SQLITE_UTF8_ZT] means that +** the result text is both UTF-8 and zero-terminated. In other words, +** SQLITE_UTF8_ZT means that the Z array holds at least N+1 bytes and that +** the Z[N] is zero. ** ^SQLite takes the text result from the application from ** the 2nd parameter of the sqlite3_result_text* interfaces. -** ^If the 3rd parameter to the sqlite3_result_text* interfaces -** is negative, then SQLite takes result text from the 2nd parameter -** through the first zero character. +** ^If the 3rd parameter to any of the sqlite3_result_text* interfaces +** other than sqlite3_result_text64() is negative, then SQLite computes +** the string length itself by searching the 2nd parameter for the first +** zero character. ** ^If the 3rd parameter to the sqlite3_result_text* interfaces ** is non-negative, then as many bytes (not characters) of the text ** pointed to by the 2nd parameter are taken as the application-defined ** function result. If the 3rd parameter is non-negative, then it ** must be the byte offset into the string where the NUL terminator would -** appear if the string where NUL terminated. If any NUL characters occur +** appear if the string were NUL terminated. If any NUL characters occur ** in the string at a byte offset that is less than the value of the 3rd ** parameter, then the resulting string will contain embedded NULs and the ** result of expressions operating on strings with embedded NULs is undefined. @@ -5372,6 +6536,25 @@ typedef unsigned long long int sqlite_uint64; ** then SQLite makes a copy of the result into space obtained ** from [sqlite3_malloc()] before it returns. ** +** ^For the sqlite3_result_text16(), sqlite3_result_text16le(), and +** sqlite3_result_text16be() routines, and for sqlite3_result_text64() +** when the encoding is not UTF8, if the input UTF16 begins with a +** byte-order mark (BOM, U+FEFF) then the BOM is removed from the +** string and the rest of the string is interpreted according to the +** byte-order specified by the BOM. ^The byte-order specified by +** the BOM at the beginning of the text overrides the byte-order +** specified by the interface procedure. ^So, for example, if +** sqlite3_result_text16le() is invoked with text that begins +** with bytes 0xfe, 0xff (a big-endian byte-order mark) then the +** first two bytes of input are skipped and the remaining input +** is interpreted as UTF16BE text. +** +** ^For UTF16 input text to the sqlite3_result_text16(), +** sqlite3_result_text16be(), sqlite3_result_text16le(), and +** sqlite3_result_text64() routines, if the text contains invalid +** UTF16 characters, the invalid characters might be converted +** into the unicode replacement character, U+FFFD. +** ** ^The sqlite3_result_value() interface sets the result of ** the application-defined function to be a copy of the ** [unprotected sqlite3_value] object specified by the 2nd parameter. ^The @@ -5384,7 +6567,7 @@ typedef unsigned long long int sqlite_uint64; ** ** ^The sqlite3_result_pointer(C,P,T,D) interface sets the result to an ** SQL NULL value, just like [sqlite3_result_null(C)], except that it -** also associates the host-language pointer P or type T with that +** also associates the host-language pointer P or type T with that ** NULL value such that the pointer can be retrieved within an ** [application-defined SQL function] using [sqlite3_value_pointer()]. ** ^If the D parameter is not NULL, then it is a pointer to a destructor @@ -5393,48 +6576,63 @@ typedef unsigned long long int sqlite_uint64; ** string and preferably a string literal. The sqlite3_result_pointer() ** routine is part of the [pointer passing interface] added for SQLite 3.20.0. ** -** If these routines are called from within the different thread +** If these routines are called from within a different thread ** than the one containing the application-defined function that received ** the [sqlite3_context] pointer, the results are undefined. */ - SQLITE_API void sqlite3_result_blob(sqlite3_context*, const void*, int, void (*)(void*)); - SQLITE_API void sqlite3_result_blob64(sqlite3_context*, const void*, - sqlite3_uint64, void (*)(void*)); - SQLITE_API void sqlite3_result_double(sqlite3_context*, double); - SQLITE_API void sqlite3_result_error(sqlite3_context*, const char*, int); - SQLITE_API void sqlite3_result_error16(sqlite3_context*, const void*, int); - SQLITE_API void sqlite3_result_error_toobig(sqlite3_context*); - SQLITE_API void sqlite3_result_error_nomem(sqlite3_context*); - SQLITE_API void sqlite3_result_error_code(sqlite3_context*, int); - SQLITE_API void sqlite3_result_int(sqlite3_context*, int); - SQLITE_API void sqlite3_result_int64(sqlite3_context*, sqlite3_int64); - SQLITE_API void sqlite3_result_null(sqlite3_context*); - SQLITE_API void sqlite3_result_text(sqlite3_context*, const char*, int, void (*)(void*)); - SQLITE_API void sqlite3_result_text64(sqlite3_context*, const char*, sqlite3_uint64, - void (*)(void*), unsigned char encoding); - SQLITE_API void sqlite3_result_text16(sqlite3_context*, const void*, int, void (*)(void*)); - SQLITE_API void sqlite3_result_text16le(sqlite3_context*, const void*, int, void (*)(void*)); - SQLITE_API void sqlite3_result_text16be(sqlite3_context*, const void*, int, void (*)(void*)); - SQLITE_API void sqlite3_result_value(sqlite3_context*, sqlite3_value*); - SQLITE_API void sqlite3_result_pointer(sqlite3_context*, void*, const char*, void (*)(void*)); - SQLITE_API void sqlite3_result_zeroblob(sqlite3_context*, int n); - SQLITE_API int sqlite3_result_zeroblob64(sqlite3_context*, sqlite3_uint64 n); - - /* +SQLITE_API void sqlite3_result_blob(sqlite3_context*, const void*, int, void(*)(void*)); +SQLITE_API void sqlite3_result_blob64(sqlite3_context*,const void*, + sqlite3_uint64,void(*)(void*)); +SQLITE_API void sqlite3_result_double(sqlite3_context*, double); +SQLITE_API void sqlite3_result_error(sqlite3_context*, const char*, int); +SQLITE_API void sqlite3_result_error16(sqlite3_context*, const void*, int); +SQLITE_API void sqlite3_result_error_toobig(sqlite3_context*); +SQLITE_API void sqlite3_result_error_nomem(sqlite3_context*); +SQLITE_API void sqlite3_result_error_code(sqlite3_context*, int); +SQLITE_API void sqlite3_result_int(sqlite3_context*, int); +SQLITE_API void sqlite3_result_int64(sqlite3_context*, sqlite3_int64); +SQLITE_API void sqlite3_result_null(sqlite3_context*); +SQLITE_API void sqlite3_result_text(sqlite3_context*, const char*, int, void(*)(void*)); +SQLITE_API void sqlite3_result_text64(sqlite3_context*, const char *z, sqlite3_uint64 n, + void(*)(void*), unsigned char encoding); +SQLITE_API void sqlite3_result_text16(sqlite3_context*, const void*, int, void(*)(void*)); +SQLITE_API void sqlite3_result_text16le(sqlite3_context*, const void*, int,void(*)(void*)); +SQLITE_API void sqlite3_result_text16be(sqlite3_context*, const void*, int,void(*)(void*)); +SQLITE_API void sqlite3_result_value(sqlite3_context*, sqlite3_value*); +SQLITE_API void sqlite3_result_pointer(sqlite3_context*, void*,const char*,void(*)(void*)); +SQLITE_API void sqlite3_result_zeroblob(sqlite3_context*, int n); +SQLITE_API int sqlite3_result_zeroblob64(sqlite3_context*, sqlite3_uint64 n); + + +/* ** CAPI3REF: Setting The Subtype Of An SQL Function ** METHOD: sqlite3_context ** ** The sqlite3_result_subtype(C,T) function causes the subtype of -** the result from the [application-defined SQL function] with -** [sqlite3_context] C to be the value T. Only the lower 8 bits +** the result from the [application-defined SQL function] with +** [sqlite3_context] C to be the value T. Only the lower 8 bits ** of the subtype T are preserved in current versions of SQLite; ** higher order bits are discarded. ** The number of subtype bytes preserved by SQLite might increase ** in future releases of SQLite. +** +** Every [application-defined SQL function] that invokes this interface +** should include the [SQLITE_RESULT_SUBTYPE] property in its +** text encoding argument when the SQL function is +** [sqlite3_create_function|registered]. If the [SQLITE_RESULT_SUBTYPE] +** property is omitted from the function that invokes sqlite3_result_subtype(), +** then in some cases the sqlite3_result_subtype() might fail to set +** the result subtype. +** +** If SQLite is compiled with -DSQLITE_STRICT_SUBTYPE=1, then any +** SQL function that invokes the sqlite3_result_subtype() interface +** and that does not have the SQLITE_RESULT_SUBTYPE property will raise +** an error. Future versions of SQLite might enable -DSQLITE_STRICT_SUBTYPE=1 +** by default. */ - SQLITE_API void sqlite3_result_subtype(sqlite3_context*, unsigned int); +SQLITE_API void sqlite3_result_subtype(sqlite3_context*,unsigned int); - /* +/* ** CAPI3REF: Define New Collating Sequences ** METHOD: sqlite3 ** @@ -5456,7 +6654,7 @@ typedef unsigned long long int sqlite_uint64; **
  • [SQLITE_UTF16_ALIGNED]. ** )^ ** ^The eTextRep argument determines the encoding of strings passed -** to the collating function callback, xCallback. +** to the collating function callback, xCompare. ** ^The [SQLITE_UTF16] and [SQLITE_UTF16_ALIGNED] values for eTextRep ** force strings to be UTF16 with native byte order. ** ^The [SQLITE_UTF16_ALIGNED] value for eTextRep forces strings to begin @@ -5465,18 +6663,19 @@ typedef unsigned long long int sqlite_uint64; ** ^The fourth argument, pArg, is an application data pointer that is passed ** through as the first argument to the collating function callback. ** -** ^The fifth argument, xCallback, is a pointer to the collating function. +** ^The fifth argument, xCompare, is a pointer to the collating function. ** ^Multiple collating functions can be registered using the same name but ** with different eTextRep parameters and SQLite will use whichever ** function requires the least amount of data transformation. -** ^If the xCallback argument is NULL then the collating function is +** ^If the xCompare argument is NULL then the collating function is ** deleted. ^When all collating functions having the same name are deleted, ** that collation is no longer usable. ** -** ^The collating function callback is invoked with a copy of the pArg +** ^The collating function callback is invoked with a copy of the pArg ** application data pointer and with two strings in the encoding specified -** by the eTextRep argument. The collating function must return an -** integer that is negative, zero, or positive +** by the eTextRep argument. The two integer parameters to the collating +** function callback are the length of the two strings, in bytes. The collating +** function must return an integer that is negative, zero, or positive ** if the first string is less than, equal to, or greater than the second, ** respectively. A collating function must always return the same answer ** given the same inputs. If two or more collating functions are registered @@ -5493,7 +6692,7 @@ typedef unsigned long long int sqlite_uint64; ** ** ** If a collating function fails any of the above constraints and that -** collating function is registered and used, then the behavior of SQLite +** collating function is registered and used, then the behavior of SQLite ** is undefined. ** ** ^The sqlite3_create_collation_v2() works like sqlite3_create_collation() @@ -5503,38 +6702,41 @@ typedef unsigned long long int sqlite_uint64; ** calls to the collation creation functions or when the ** [database connection] is closed using [sqlite3_close()]. ** -** ^The xDestroy callback is not called if the +** ^The xDestroy callback is not called if the ** sqlite3_create_collation_v2() function fails. Applications that invoke -** sqlite3_create_collation_v2() with a non-NULL xDestroy argument should +** sqlite3_create_collation_v2() with a non-NULL xDestroy argument should ** check the return code and dispose of the application data pointer ** themselves rather than expecting SQLite to deal with it for them. -** This is different from every other SQLite interface. The inconsistency -** is unfortunate but cannot be changed without breaking backwards +** This is different from every other SQLite interface. The inconsistency +** is unfortunate but cannot be changed without breaking backwards ** compatibility. ** ** See also: [sqlite3_collation_needed()] and [sqlite3_collation_needed16()]. */ - SQLITE_API int sqlite3_create_collation( - sqlite3*, - const char* zName, - int eTextRep, - void* pArg, - int (*xCompare)(void*, int, const void*, int, const void*)); - SQLITE_API int sqlite3_create_collation_v2( - sqlite3*, - const char* zName, - int eTextRep, - void* pArg, - int (*xCompare)(void*, int, const void*, int, const void*), - void (*xDestroy)(void*)); - SQLITE_API int sqlite3_create_collation16( - sqlite3*, - const void* zName, - int eTextRep, - void* pArg, - int (*xCompare)(void*, int, const void*, int, const void*)); - - /* +SQLITE_API int sqlite3_create_collation( + sqlite3*, + const char *zName, + int eTextRep, + void *pArg, + int(*xCompare)(void*,int,const void*,int,const void*) +); +SQLITE_API int sqlite3_create_collation_v2( + sqlite3*, + const char *zName, + int eTextRep, + void *pArg, + int(*xCompare)(void*,int,const void*,int,const void*), + void(*xDestroy)(void*) +); +SQLITE_API int sqlite3_create_collation16( + sqlite3*, + const void *zName, + int eTextRep, + void *pArg, + int(*xCompare)(void*,int,const void*,int,const void*) +); + +/* ** CAPI3REF: Collation Needed Callbacks ** METHOD: sqlite3 ** @@ -5561,71 +6763,28 @@ typedef unsigned long long int sqlite_uint64; ** [sqlite3_create_collation()], [sqlite3_create_collation16()], or ** [sqlite3_create_collation_v2()]. */ - SQLITE_API int sqlite3_collation_needed( - sqlite3*, - void*, - void (*)(void*, sqlite3*, int eTextRep, const char*)); - SQLITE_API int sqlite3_collation_needed16( - sqlite3*, - void*, - void (*)(void*, sqlite3*, int eTextRep, const void*)); - -#ifdef SQLITE_HAS_CODEC - /* -** Specify the key for an encrypted database. This routine should be -** called right after sqlite3_open(). -** -** The code to implement this API is not available in the public release -** of SQLite. -*/ - SQLITE_API int sqlite3_key( - sqlite3* db, /* Database to be rekeyed */ - const void* pKey, int nKey /* The key */ - ); - SQLITE_API int sqlite3_key_v2( - sqlite3* db, /* Database to be rekeyed */ - const char* zDbName, /* Name of the database */ - const void* pKey, int nKey /* The key */ - ); - - /* -** Change the key on an open database. If the current database is not -** encrypted, this routine will encrypt it. If pNew==0 or nNew==0, the -** database is decrypted. -** -** The code to implement this API is not available in the public release -** of SQLite. -*/ - SQLITE_API int sqlite3_rekey( - sqlite3* db, /* Database to be rekeyed */ - const void* pKey, int nKey /* The new key */ - ); - SQLITE_API int sqlite3_rekey_v2( - sqlite3* db, /* Database to be rekeyed */ - const char* zDbName, /* Name of the database */ - const void* pKey, int nKey /* The new key */ - ); - - /* -** Specify the activation key for a SEE database. Unless -** activated, none of the SEE routines will work. -*/ - SQLITE_API void sqlite3_activate_see( - const char* zPassPhrase /* Activation phrase */ - ); -#endif +SQLITE_API int sqlite3_collation_needed( + sqlite3*, + void*, + void(*)(void*,sqlite3*,int eTextRep,const char*) +); +SQLITE_API int sqlite3_collation_needed16( + sqlite3*, + void*, + void(*)(void*,sqlite3*,int eTextRep,const void*) +); #ifdef SQLITE_ENABLE_CEROD - /* -** Specify the activation key for a CEROD database. Unless +/* +** Specify the activation key for a CEROD database. Unless ** activated, none of the CEROD routines will work. */ - SQLITE_API void sqlite3_activate_cerod( - const char* zPassPhrase /* Activation phrase */ - ); +SQLITE_API void sqlite3_activate_cerod( + const char *zPassPhrase /* Activation phrase */ +); #endif - /* +/* ** CAPI3REF: Suspend Execution For A Short Time ** ** The sqlite3_sleep() function causes the current thread to suspend execution @@ -5641,10 +6800,17 @@ typedef unsigned long long int sqlite_uint64; ** of the default VFS is not implemented correctly, or not implemented at ** all, then the behavior of sqlite3_sleep() may deviate from the description ** in the previous paragraphs. +** +** If a negative argument is passed to sqlite3_sleep() the results vary by +** VFS and operating system. Some system treat a negative argument as an +** instruction to sleep forever. Others understand it to mean do not sleep +** at all. ^In SQLite version 3.42.0 and later, a negative +** argument passed into sqlite3_sleep() is changed to zero before it is relayed +** down into the xSleep method of the VFS. */ - SQLITE_API int sqlite3_sleep(int); +SQLITE_API int sqlite3_sleep(int); - /* +/* ** CAPI3REF: Name Of The Folder Holding Temporary Files ** ** ^(If this global variable is made to point to a string which is @@ -5673,7 +6839,7 @@ typedef unsigned long long int sqlite_uint64; ** ^The [temp_store_directory pragma] may modify this variable and cause ** it to point to memory obtained from [sqlite3_malloc]. ^Furthermore, ** the [temp_store_directory pragma] always assumes that any string -** that this variable points to is held in memory obtained from +** that this variable points to is held in memory obtained from ** [sqlite3_malloc] and the pragma may attempt to free that memory ** using [sqlite3_free]. ** Hence, if this variable is modified directly, either it should be @@ -5700,9 +6866,9 @@ typedef unsigned long long int sqlite_uint64; ** sqlite3_temp_directory = sqlite3_mprintf("%s", zPathBuf); ** */ - SQLITE_API SQLITE_EXTERN char* sqlite3_temp_directory; +SQLITE_API SQLITE_EXTERN char *sqlite3_temp_directory; - /* +/* ** CAPI3REF: Name Of The Folder Holding Database Files ** ** ^(If this global variable is made to point to a string which is @@ -5730,16 +6896,16 @@ typedef unsigned long long int sqlite_uint64; ** ^The [data_store_directory pragma] may modify this variable and cause ** it to point to memory obtained from [sqlite3_malloc]. ^Furthermore, ** the [data_store_directory pragma] always assumes that any string -** that this variable points to is held in memory obtained from +** that this variable points to is held in memory obtained from ** [sqlite3_malloc] and the pragma may attempt to free that memory ** using [sqlite3_free]. ** Hence, if this variable is modified directly, either it should be ** made NULL or made to point to memory obtained from [sqlite3_malloc] ** or else the use of the [data_store_directory pragma] should be avoided. */ - SQLITE_API SQLITE_EXTERN char* sqlite3_data_directory; +SQLITE_API SQLITE_EXTERN char *sqlite3_data_directory; - /* +/* ** CAPI3REF: Win32 Specific Interface ** ** These interfaces are available only on Windows. The @@ -5758,12 +6924,12 @@ typedef unsigned long long int sqlite_uint64; ** sqlite3_win32_set_directory interface except the string parameter must be ** UTF-8 or UTF-16, respectively. */ - SQLITE_API int sqlite3_win32_set_directory( - unsigned long type, /* Identifier for directory being set or reset */ - void* zValue /* New value for directory being set or reset */ - ); - SQLITE_API int sqlite3_win32_set_directory8(unsigned long type, const char* zValue); - SQLITE_API int sqlite3_win32_set_directory16(unsigned long type, const void* zValue); +SQLITE_API int sqlite3_win32_set_directory( + unsigned long type, /* Identifier for directory being set or reset */ + void *zValue /* New value for directory being set or reset */ +); +SQLITE_API int sqlite3_win32_set_directory8(unsigned long type, const char *zValue); +SQLITE_API int sqlite3_win32_set_directory16(unsigned long type, const void *zValue); /* ** CAPI3REF: Win32 Directory Types @@ -5771,10 +6937,10 @@ typedef unsigned long long int sqlite_uint64; ** These macros are only available on Windows. They define the allowed values ** for the type argument to the [sqlite3_win32_set_directory] interface. */ -#define SQLITE_WIN32_DATA_DIRECTORY_TYPE 1 -#define SQLITE_WIN32_TEMP_DIRECTORY_TYPE 2 +#define SQLITE_WIN32_DATA_DIRECTORY_TYPE 1 +#define SQLITE_WIN32_TEMP_DIRECTORY_TYPE 2 - /* +/* ** CAPI3REF: Test For Auto-Commit Mode ** KEYWORDS: {autocommit mode} ** METHOD: sqlite3 @@ -5796,9 +6962,9 @@ typedef unsigned long long int sqlite_uint64; ** connection while this routine is running, then the return value ** is undefined. */ - SQLITE_API int sqlite3_get_autocommit(sqlite3*); +SQLITE_API int sqlite3_get_autocommit(sqlite3*); - /* +/* ** CAPI3REF: Find The Database Handle Of A Prepared Statement ** METHOD: sqlite3_stmt ** @@ -5809,26 +6975,63 @@ typedef unsigned long long int sqlite_uint64; ** to the [sqlite3_prepare_v2()] call (or its variants) that was used to ** create the statement in the first place. */ - SQLITE_API sqlite3* sqlite3_db_handle(sqlite3_stmt*); +SQLITE_API sqlite3 *sqlite3_db_handle(sqlite3_stmt*); - /* +/* +** CAPI3REF: Return The Schema Name For A Database Connection +** METHOD: sqlite3 +** +** ^The sqlite3_db_name(D,N) interface returns a pointer to the schema name +** for the N-th database on database connection D, or a NULL pointer if N is +** out of range. An N value of 0 means the main database file. An N of 1 is +** the "temp" schema. Larger values of N correspond to various ATTACH-ed +** databases. +** +** Space to hold the string that is returned by sqlite3_db_name() is managed +** by SQLite itself. The string might be deallocated by any operation that +** changes the schema, including [ATTACH] or [DETACH] or calls to +** [sqlite3_serialize()] or [sqlite3_deserialize()], even operations that +** occur on a different thread. Applications that need to +** remember the string long-term should make their own copy. Applications that +** are accessing the same database connection simultaneously on multiple +** threads should mutex-protect calls to this API and should make their own +** private copy of the result prior to releasing the mutex. +*/ +SQLITE_API const char *sqlite3_db_name(sqlite3 *db, int N); + +/* ** CAPI3REF: Return The Filename For A Database Connection ** METHOD: sqlite3 ** -** ^The sqlite3_db_filename(D,N) interface returns a pointer to a filename -** associated with database N of connection D. ^The main database file -** has the name "main". If there is no attached database N on the database +** ^The sqlite3_db_filename(D,N) interface returns a pointer to the filename +** associated with database N of connection D. +** ^If there is no attached database N on the database ** connection D, or if database N is a temporary or in-memory database, then ** this function will return either a NULL pointer or an empty string. ** +** ^The string value returned by this routine is owned and managed by +** the database connection. ^The value will be valid until the database N +** is [DETACH]-ed or until the database connection closes. +** ** ^The filename returned by this function is the output of the ** xFullPathname method of the [VFS]. ^In other words, the filename ** will be an absolute pathname, even if the filename used ** to open the database originally was a URI or relative pathname. +** +** If the filename pointer returned by this routine is not NULL, then it +** can be used as the filename input parameter to these routines: +**
      +**
    • [sqlite3_uri_parameter()] +**
    • [sqlite3_uri_boolean()] +**
    • [sqlite3_uri_int64()] +**
    • [sqlite3_filename_database()] +**
    • [sqlite3_filename_journal()] +**
    • [sqlite3_filename_wal()] +**
    */ - SQLITE_API const char* sqlite3_db_filename(sqlite3* db, const char* zDbName); +SQLITE_API sqlite3_filename sqlite3_db_filename(sqlite3 *db, const char *zDbName); - /* +/* ** CAPI3REF: Determine if a database is read-only ** METHOD: sqlite3 ** @@ -5836,9 +7039,60 @@ typedef unsigned long long int sqlite_uint64; ** of connection D is read-only, 0 if it is read/write, or -1 if N is not ** the name of a database on connection D. */ - SQLITE_API int sqlite3_db_readonly(sqlite3* db, const char* zDbName); +SQLITE_API int sqlite3_db_readonly(sqlite3 *db, const char *zDbName); + +/* +** CAPI3REF: Determine the transaction state of a database +** METHOD: sqlite3 +** +** ^The sqlite3_txn_state(D,S) interface returns the current +** [transaction state] of schema S in database connection D. ^If S is NULL, +** then the highest transaction state of any schema on database connection D +** is returned. Transaction states are (in order of lowest to highest): +**
      +**
    1. SQLITE_TXN_NONE +**
    2. SQLITE_TXN_READ +**
    3. SQLITE_TXN_WRITE +**
    +** ^If the S argument to sqlite3_txn_state(D,S) is not the name of +** a valid schema, then -1 is returned. +*/ +SQLITE_API int sqlite3_txn_state(sqlite3*,const char *zSchema); + +/* +** CAPI3REF: Allowed return values from sqlite3_txn_state() +** KEYWORDS: {transaction state} +** +** These constants define the current transaction state of a database file. +** ^The [sqlite3_txn_state(D,S)] interface returns one of these +** constants in order to describe the transaction state of schema S +** in [database connection] D. +** +**
    +** [[SQLITE_TXN_NONE]]
    SQLITE_TXN_NONE
    +**
    The SQLITE_TXN_NONE state means that no transaction is currently +** pending.
    +** +** [[SQLITE_TXN_READ]]
    SQLITE_TXN_READ
    +**
    The SQLITE_TXN_READ state means that the database is currently +** in a read transaction. Content has been read from the database file +** but nothing in the database file has changed. The transaction state +** will be advanced to SQLITE_TXN_WRITE if any changes occur and there are +** no other conflicting concurrent write transactions. The transaction +** state will revert to SQLITE_TXN_NONE following a [ROLLBACK] or +** [COMMIT].
    +** +** [[SQLITE_TXN_WRITE]]
    SQLITE_TXN_WRITE
    +**
    The SQLITE_TXN_WRITE state means that the database is currently +** in a write transaction. Content has been written to the database file +** but has not yet committed. The transaction state will change to +** SQLITE_TXN_NONE at the next [ROLLBACK] or [COMMIT].
    +*/ +#define SQLITE_TXN_NONE 0 +#define SQLITE_TXN_READ 1 +#define SQLITE_TXN_WRITE 2 - /* +/* ** CAPI3REF: Find the next prepared statement ** METHOD: sqlite3 ** @@ -5852,9 +7106,9 @@ typedef unsigned long long int sqlite_uint64; ** [sqlite3_next_stmt(D,S)] must refer to an open database ** connection and in particular must not be a NULL pointer. */ - SQLITE_API sqlite3_stmt* sqlite3_next_stmt(sqlite3* pDb, sqlite3_stmt* pStmt); +SQLITE_API sqlite3_stmt *sqlite3_next_stmt(sqlite3 *pDb, sqlite3_stmt *pStmt); - /* +/* ** CAPI3REF: Commit And Rollback Notification Callbacks ** METHOD: sqlite3 ** @@ -5901,10 +7155,76 @@ typedef unsigned long long int sqlite_uint64; ** ** See also the [sqlite3_update_hook()] interface. */ - SQLITE_API void* sqlite3_commit_hook(sqlite3*, int (*)(void*), void*); - SQLITE_API void* sqlite3_rollback_hook(sqlite3*, void (*)(void*), void*); +SQLITE_API void *sqlite3_commit_hook(sqlite3*, int(*)(void*), void*); +SQLITE_API void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*); - /* +/* +** CAPI3REF: Autovacuum Compaction Amount Callback +** METHOD: sqlite3 +** +** ^The sqlite3_autovacuum_pages(D,C,P,X) interface registers a callback +** function C that is invoked prior to each autovacuum of the database +** file. ^The callback is passed a copy of the generic data pointer (P), +** the schema-name of the attached database that is being autovacuumed, +** the size of the database file in pages, the number of free pages, +** and the number of bytes per page, respectively. The callback should +** return the number of free pages that should be removed by the +** autovacuum. ^If the callback returns zero, then no autovacuum happens. +** ^If the value returned is greater than or equal to the number of +** free pages, then a complete autovacuum happens. +** +**

    ^If there are multiple ATTACH-ed database files that are being +** modified as part of a transaction commit, then the autovacuum pages +** callback is invoked separately for each file. +** +**

    The callback is not reentrant. The callback function should +** not attempt to invoke any other SQLite interface. If it does, bad +** things may happen, including segmentation faults and corrupt database +** files. The callback function should be a simple function that +** does some arithmetic on its input parameters and returns a result. +** +** ^The X parameter to sqlite3_autovacuum_pages(D,C,P,X) is an optional +** destructor for the P parameter. ^If X is not NULL, then X(P) is +** invoked whenever the database connection closes or when the callback +** is overwritten by another invocation of sqlite3_autovacuum_pages(). +** +**

    ^There is only one autovacuum pages callback per database connection. +** ^Each call to the sqlite3_autovacuum_pages() interface overrides all +** previous invocations for that database connection. ^If the callback +** argument (C) to sqlite3_autovacuum_pages(D,C,P,X) is a NULL pointer, +** then the autovacuum steps callback is canceled. The return value +** from sqlite3_autovacuum_pages() is normally SQLITE_OK, but might +** be some other error code if something goes wrong. The current +** implementation will only return SQLITE_OK or SQLITE_MISUSE, but other +** return codes might be added in future releases. +** +**

    If no autovacuum pages callback is specified (the usual case) or +** a NULL pointer is provided for the callback, +** then the default behavior is to vacuum all free pages. So, in other +** words, the default behavior is the same as if the callback function +** were something like this: +** +**

    +**     unsigned int demonstration_autovac_pages_callback(
    +**       void *pClientData,
    +**       const char *zSchema,
    +**       unsigned int nDbPage,
    +**       unsigned int nFreePage,
    +**       unsigned int nBytePerPage
    +**     ){
    +**       return nFreePage;
    +**     }
    +** 
    +*/ +SQLITE_API int sqlite3_autovacuum_pages( + sqlite3 *db, + unsigned int(*)(void*,const char*,unsigned int,unsigned int,unsigned int), + void*, + void(*)(void*) +); + + +/* ** CAPI3REF: Data Change Notification Callbacks ** METHOD: sqlite3 ** @@ -5917,6 +7237,8 @@ typedef unsigned long long int sqlite_uint64; ** ** ^The second argument is a pointer to the function to invoke when a ** row is updated, inserted or deleted in a rowid table. +** ^The update hook is disabled by invoking sqlite3_update_hook() +** with a NULL pointer as the second parameter. ** ^The first argument to the callback is a copy of the third argument ** to sqlite3_update_hook(). ** ^The second callback argument is one of [SQLITE_INSERT], [SQLITE_DELETE], @@ -5928,7 +7250,7 @@ typedef unsigned long long int sqlite_uint64; ** ^In the case of an update, this is the [rowid] after the update takes place. ** ** ^(The update hook is not invoked when internal system tables are -** modified (i.e. sqlite_master and sqlite_sequence).)^ +** modified (i.e. sqlite_sequence).)^ ** ^The update hook is not invoked when [WITHOUT ROWID] tables are modified. ** ** ^In the current implementation, the update hook @@ -5938,6 +7260,12 @@ typedef unsigned long long int sqlite_uint64; ** The exceptions defined in this paragraph might change in a future ** release of SQLite. ** +** Whether the update hook is invoked before or after the +** corresponding change is currently unspecified and may differ +** depending on the type of change. Do not rely on the order of the +** hook call with regards to the final result of the operation which +** triggers the hook. +** ** The update hook implementation must not do anything that will modify ** the database connection that invoked the update hook. Any actions ** to modify the database connection must be deferred until after the @@ -5953,12 +7281,13 @@ typedef unsigned long long int sqlite_uint64; ** See also the [sqlite3_commit_hook()], [sqlite3_rollback_hook()], ** and [sqlite3_preupdate_hook()] interfaces. */ - SQLITE_API void* sqlite3_update_hook( - sqlite3*, - void (*)(void*, int, char const*, char const*, sqlite3_int64), - void*); +SQLITE_API void *sqlite3_update_hook( + sqlite3*, + void(*)(void *,int ,char const *,char const *,sqlite3_int64), + void* +); - /* +/* ** CAPI3REF: Enable Or Disable Shared Pager Cache ** ** ^(This routine enables or disables the sharing of the database cache @@ -5966,26 +7295,35 @@ typedef unsigned long long int sqlite_uint64; ** to the same database. Sharing is enabled if the argument is true ** and disabled if the argument is false.)^ ** +** This interface is omitted if SQLite is compiled with +** [-DSQLITE_OMIT_SHARED_CACHE]. The [-DSQLITE_OMIT_SHARED_CACHE] +** compile-time option is recommended because the +** [use of shared cache mode is discouraged]. +** ** ^Cache sharing is enabled and disabled for an entire process. -** This is a change as of SQLite [version 3.5.0] ([dateof:3.5.0]). +** This is a change as of SQLite [version 3.5.0] ([dateof:3.5.0]). ** In prior versions of SQLite, ** sharing was enabled or disabled for each thread separately. ** ** ^(The cache sharing mode set by this interface effects all subsequent ** calls to [sqlite3_open()], [sqlite3_open_v2()], and [sqlite3_open16()]. -** Existing database connections continue use the sharing mode +** Existing database connections continue to use the sharing mode ** that was in effect at the time they were opened.)^ ** ** ^(This routine returns [SQLITE_OK] if shared cache was enabled or disabled ** successfully. An [error code] is returned otherwise.)^ ** -** ^Shared cache is disabled by default. But this might change in -** future releases of SQLite. Applications that care about shared -** cache setting should set it explicitly. +** ^Shared cache is disabled by default. It is recommended that it stay +** that way. In other words, do not use this routine. This interface +** continues to be provided for historical compatibility, but its use is +** discouraged. Any use of shared cache is discouraged. If shared cache +** must be used, it is recommended that shared cache only be enabled for +** individual database connections using the [sqlite3_open_v2()] interface +** with the [SQLITE_OPEN_SHAREDCACHE] flag. ** ** Note: This method is disabled on MacOS X 10.7 and iOS version 5.0 -** and will always return SQLITE_MISUSE. On those systems, -** shared cache mode should be enabled per-database connection via +** and will always return SQLITE_MISUSE. On those systems, +** shared cache mode should be enabled per-database connection via ** [sqlite3_open_v2()] with [SQLITE_OPEN_SHAREDCACHE]. ** ** This interface is threadsafe on processors where writing a @@ -5993,9 +7331,9 @@ typedef unsigned long long int sqlite_uint64; ** ** See Also: [SQLite Shared-Cache Mode] */ - SQLITE_API int sqlite3_enable_shared_cache(int); +SQLITE_API int sqlite3_enable_shared_cache(int); - /* +/* ** CAPI3REF: Attempt To Free Heap Memory ** ** ^The sqlite3_release_memory() interface attempts to free N bytes @@ -6009,9 +7347,9 @@ typedef unsigned long long int sqlite_uint64; ** ** See also: [sqlite3_db_release_memory()] */ - SQLITE_API int sqlite3_release_memory(int); +SQLITE_API int sqlite3_release_memory(int); - /* +/* ** CAPI3REF: Free Memory Used By A Database Connection ** METHOD: sqlite3 ** @@ -6023,11 +7361,14 @@ typedef unsigned long long int sqlite_uint64; ** ** See also: [sqlite3_release_memory()] */ - SQLITE_API int sqlite3_db_release_memory(sqlite3*); +SQLITE_API int sqlite3_db_release_memory(sqlite3*); - /* +/* ** CAPI3REF: Impose A Limit On Heap Size ** +** These interfaces impose limits on the amount of heap memory that will be +** used by all database connections within a single process. +** ** ^The sqlite3_soft_heap_limit64() interface sets and/or queries the ** soft limit on the amount of heap memory that may be allocated by SQLite. ** ^SQLite strives to keep heap memory utilization below the soft heap @@ -6035,23 +7376,44 @@ typedef unsigned long long int sqlite_uint64; ** as heap memory usages approaches the limit. ** ^The soft heap limit is "soft" because even though SQLite strives to stay ** below the limit, it will exceed the limit rather than generate -** an [SQLITE_NOMEM] error. In other words, the soft heap limit +** an [SQLITE_NOMEM] error. In other words, the soft heap limit ** is advisory only. ** -** ^The return value from sqlite3_soft_heap_limit64() is the size of -** the soft heap limit prior to the call, or negative in the case of an -** error. ^If the argument N is negative -** then no change is made to the soft heap limit. Hence, the current -** size of the soft heap limit can be determined by invoking -** sqlite3_soft_heap_limit64() with a negative argument. -** -** ^If the argument N is zero then the soft heap limit is disabled. +** ^The sqlite3_hard_heap_limit64(N) interface sets a hard upper bound of +** N bytes on the amount of memory that will be allocated. ^The +** sqlite3_hard_heap_limit64(N) interface is similar to +** sqlite3_soft_heap_limit64(N) except that memory allocations will fail +** when the hard heap limit is reached. ** -** ^(The soft heap limit is not enforced in the current implementation +** ^The return value from both sqlite3_soft_heap_limit64() and +** sqlite3_hard_heap_limit64() is the size of +** the heap limit prior to the call, or negative in the case of an +** error. ^If the argument N is negative +** then no change is made to the heap limit. Hence, the current +** size of heap limits can be determined by invoking +** sqlite3_soft_heap_limit64(-1) or sqlite3_hard_heap_limit(-1). +** +** ^Setting the heap limits to zero disables the heap limiter mechanism. +** +** ^The soft heap limit may not be greater than the hard heap limit. +** ^If the hard heap limit is enabled and if sqlite3_soft_heap_limit(N) +** is invoked with a value of N that is greater than the hard heap limit, +** the soft heap limit is set to the value of the hard heap limit. +** ^The soft heap limit is automatically enabled whenever the hard heap +** limit is enabled. ^When sqlite3_hard_heap_limit64(N) is invoked and +** the soft heap limit is outside the range of 1..N, then the soft heap +** limit is set to N. ^Invoking sqlite3_soft_heap_limit64(0) when the +** hard heap limit is enabled makes the soft heap limit equal to the +** hard heap limit. +** +** The memory allocation limits can also be adjusted using +** [PRAGMA soft_heap_limit] and [PRAGMA hard_heap_limit]. +** +** ^(The heap limits are not enforced in the current implementation ** if one or more of following conditions are true: ** **
      -**
    • The soft heap limit is set to zero. +**
    • The limit value is set to zero. **
    • Memory accounting is disabled using a combination of the ** [sqlite3_config]([SQLITE_CONFIG_MEMSTATUS],...) start-time option and ** the [SQLITE_DEFAULT_MEMSTATUS] compile-time option. @@ -6062,23 +7424,13 @@ typedef unsigned long long int sqlite_uint64; ** from the heap. **
    )^ ** -** Beginning with SQLite [version 3.7.3] ([dateof:3.7.3]), -** the soft heap limit is enforced -** regardless of whether or not the [SQLITE_ENABLE_MEMORY_MANAGEMENT] -** compile-time option is invoked. With [SQLITE_ENABLE_MEMORY_MANAGEMENT], -** the soft heap limit is enforced on every memory allocation. Without -** [SQLITE_ENABLE_MEMORY_MANAGEMENT], the soft heap limit is only enforced -** when memory is allocated by the page cache. Testing suggests that because -** the page cache is the predominate memory user in SQLite, most -** applications will achieve adequate soft heap limit enforcement without -** the use of [SQLITE_ENABLE_MEMORY_MANAGEMENT]. -** -** The circumstances under which SQLite will enforce the soft heap limit may -** changes in future releases of SQLite. +** The circumstances under which SQLite will enforce the heap limits may +** change in future releases of SQLite. */ - SQLITE_API sqlite3_int64 sqlite3_soft_heap_limit64(sqlite3_int64 N); +SQLITE_API sqlite3_int64 sqlite3_soft_heap_limit64(sqlite3_int64 N); +SQLITE_API sqlite3_int64 sqlite3_hard_heap_limit64(sqlite3_int64 N); - /* +/* ** CAPI3REF: Deprecated Soft Heap Limit Interface ** DEPRECATED ** @@ -6087,9 +7439,10 @@ typedef unsigned long long int sqlite_uint64; ** only. All new applications should use the ** [sqlite3_soft_heap_limit64()] interface rather than this one. */ - SQLITE_API SQLITE_DEPRECATED void sqlite3_soft_heap_limit(int N); +SQLITE_API SQLITE_DEPRECATED void sqlite3_soft_heap_limit(int N); - /* + +/* ** CAPI3REF: Extract Metadata About A Column Of A Table ** METHOD: sqlite3 ** @@ -6099,7 +7452,7 @@ typedef unsigned long long int sqlite_uint64; ** interface returns SQLITE_OK and fills in the non-NULL pointers in ** the final five arguments with appropriate values if the specified ** column exists. ^The sqlite3_table_column_metadata() interface returns -** SQLITE_ERROR and if the specified column does not exist. +** SQLITE_ERROR if the specified column does not exist. ** ^If the column-name parameter to sqlite3_table_column_metadata() is a ** NULL pointer, then this routine simply checks for the existence of the ** table and returns SQLITE_OK if the table exists and SQLITE_ERROR if it @@ -6139,7 +7492,7 @@ typedef unsigned long long int sqlite_uint64; ** ** ^If the specified table is actually a view, an [error code] is returned. ** -** ^If the specified column is "rowid", "oid" or "_rowid_" and the table +** ^If the specified column is "rowid", "oid" or "_rowid_" and the table ** is not a [WITHOUT ROWID] table and an ** [INTEGER PRIMARY KEY] column has been explicitly declared, then the output ** parameters are set for the explicitly declared column. ^(If there is no @@ -6158,19 +7511,19 @@ typedef unsigned long long int sqlite_uint64; ** parsed, if that has not already been done, and returns an error if ** any errors are encountered while loading the schema. */ - SQLITE_API int sqlite3_table_column_metadata( - sqlite3* db, /* Connection handle */ - const char* zDbName, /* Database name or NULL */ - const char* zTableName, /* Table name */ - const char* zColumnName, /* Column name */ - char const** pzDataType, /* OUTPUT: Declared data type */ - char const** pzCollSeq, /* OUTPUT: Collation sequence name */ - int* pNotNull, /* OUTPUT: True if NOT NULL constraint exists */ - int* pPrimaryKey, /* OUTPUT: True if column part of PK */ - int* pAutoinc /* OUTPUT: True if column is auto-increment */ - ); - - /* +SQLITE_API int sqlite3_table_column_metadata( + sqlite3 *db, /* Connection handle */ + const char *zDbName, /* Database name or NULL */ + const char *zTableName, /* Table name */ + const char *zColumnName, /* Column name */ + char const **pzDataType, /* OUTPUT: Declared data type */ + char const **pzCollSeq, /* OUTPUT: Collation sequence name */ + int *pNotNull, /* OUTPUT: True if NOT NULL constraint exists */ + int *pPrimaryKey, /* OUTPUT: True if column part of PK */ + int *pAutoinc /* OUTPUT: True if column is auto-increment */ +); + +/* ** CAPI3REF: Load An Extension ** METHOD: sqlite3 ** @@ -6179,7 +7532,7 @@ typedef unsigned long long int sqlite_uint64; ** ^The sqlite3_load_extension() interface attempts to load an ** [SQLite extension] library contained in the file zFile. If ** the file cannot be loaded directly, attempts are made to load -** with various operating-system specific extensions added. +** with various operating-system specific filename extensions added. ** So for example, if "samplelib" cannot be loaded, then names like ** "samplelib.so" or "samplelib.dylib" or "samplelib.dll" might ** be tried also. @@ -6187,10 +7540,10 @@ typedef unsigned long long int sqlite_uint64; ** ^The entry point is zProc. ** ^(zProc may be 0, in which case SQLite will try to come up with an ** entry point name on its own. It first tries "sqlite3_extension_init". -** If that does not work, it constructs a name "sqlite3_X_init" where the -** X is consists of the lower-case equivalent of all ASCII alphabetic -** characters in the filename from the last "/" to the first following -** "." and omitting any initial "lib".)^ +** If that does not work, it tries names of the form "sqlite3_X_init" +** where X consists of the lower-case equivalent of all ASCII alphabetic +** characters or all ASCII alphanumeric characters in the filename from +** the last "/" to the first following "." and omitting any initial "lib".)^ ** ^The sqlite3_load_extension() interface returns ** [SQLITE_OK] on success and [SQLITE_ERROR] if something goes wrong. ** ^If an error occurs and pzErrMsg is not 0, then the @@ -6205,7 +7558,7 @@ typedef unsigned long long int sqlite_uint64; ** prior to calling this API, ** otherwise an error will be returned. ** -** Security warning: It is recommended that the +** Security warning: It is recommended that the ** [SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION] method be used to enable only this ** interface. The use of the [sqlite3_enable_load_extension()] interface ** should be avoided. This will keep the SQL function [load_extension()] @@ -6214,14 +7567,14 @@ typedef unsigned long long int sqlite_uint64; ** ** See also the [load_extension() SQL function]. */ - SQLITE_API int sqlite3_load_extension( - sqlite3* db, /* Load the extension into this database connection */ - const char* zFile, /* Name of the shared library containing extension */ - const char* zProc, /* Entry point. Derived from zFile if 0 */ - char** pzErrMsg /* Put error message here if not 0 */ - ); +SQLITE_API int sqlite3_load_extension( + sqlite3 *db, /* Load the extension into this database connection */ + const char *zFile, /* Name of the shared library containing extension */ + const char *zProc, /* Entry point. Derived from zFile if 0 */ + char **pzErrMsg /* Put error message here if not 0 */ +); - /* +/* ** CAPI3REF: Enable Or Disable Extension Loading ** METHOD: sqlite3 ** @@ -6241,14 +7594,14 @@ typedef unsigned long long int sqlite_uint64; ** to enable or disable only the C-API.)^ ** ** Security warning: It is recommended that extension loading -** be disabled using the [SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION] method +** be enabled using the [SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION] method ** rather than this interface, so the [load_extension()] SQL function ** remains disabled. This will prevent SQL injections from giving attackers ** access to extension loading capabilities. */ - SQLITE_API int sqlite3_enable_load_extension(sqlite3* db, int onoff); +SQLITE_API int sqlite3_enable_load_extension(sqlite3 *db, int onoff); - /* +/* ** CAPI3REF: Automatically Load Statically Linked Extensions ** ** ^This interface causes the xEntryPoint() function to be invoked for @@ -6259,12 +7612,12 @@ typedef unsigned long long int sqlite_uint64; ** ^(Even though the function prototype shows that xEntryPoint() takes ** no arguments and returns void, SQLite invokes xEntryPoint() with three ** arguments and expects an integer result as if the signature of the -** entry point where as follows: +** entry point were as follows: ** **
     **    int xEntryPoint(
     **      sqlite3 *db,
    -**      const char **pzErrMsg,
    +**      char **pzErrMsg,
     **      const struct sqlite3_api_routines *pThunk
     **    );
     ** 
    )^ @@ -6284,51 +7637,42 @@ typedef unsigned long long int sqlite_uint64; ** See also: [sqlite3_reset_auto_extension()] ** and [sqlite3_cancel_auto_extension()] */ - SQLITE_API int sqlite3_auto_extension(void (*xEntryPoint)(void)); +SQLITE_API int sqlite3_auto_extension(void(*xEntryPoint)(void)); - /* +/* ** CAPI3REF: Cancel Automatic Extension Loading ** ** ^The [sqlite3_cancel_auto_extension(X)] interface unregisters the ** initialization routine X that was registered using a prior call to ** [sqlite3_auto_extension(X)]. ^The [sqlite3_cancel_auto_extension(X)] -** routine returns 1 if initialization routine X was successfully +** routine returns 1 if initialization routine X was successfully ** unregistered and it returns 0 if X was not on the list of initialization ** routines. */ - SQLITE_API int sqlite3_cancel_auto_extension(void (*xEntryPoint)(void)); +SQLITE_API int sqlite3_cancel_auto_extension(void(*xEntryPoint)(void)); - /* +/* ** CAPI3REF: Reset Automatic Extension Loading ** ** ^This interface disables all automatic extensions previously ** registered using [sqlite3_auto_extension()]. */ - SQLITE_API void sqlite3_reset_auto_extension(void); +SQLITE_API void sqlite3_reset_auto_extension(void); - /* -** The interface to the virtual-table mechanism is currently considered -** to be experimental. The interface might change in incompatible ways. -** If this is a problem for you, do not use the interface at this time. -** -** When the virtual-table mechanism stabilizes, we will declare the -** interface fixed, support it indefinitely, and remove this comment. -*/ - - /* +/* ** Structures used by the virtual table interface */ - typedef struct sqlite3_vtab sqlite3_vtab; - typedef struct sqlite3_index_info sqlite3_index_info; - typedef struct sqlite3_vtab_cursor sqlite3_vtab_cursor; - typedef struct sqlite3_module sqlite3_module; +typedef struct sqlite3_vtab sqlite3_vtab; +typedef struct sqlite3_index_info sqlite3_index_info; +typedef struct sqlite3_vtab_cursor sqlite3_vtab_cursor; +typedef struct sqlite3_module sqlite3_module; - /* +/* ** CAPI3REF: Virtual Table Object ** KEYWORDS: sqlite3_module {virtual table module} ** -** This structure, sometimes called a "virtual table module", -** defines the implementation of a [virtual tables]. +** This structure, sometimes called a "virtual table module", +** defines the implementation of a [virtual table]. ** This structure consists mostly of methods for the module. ** ** ^A virtual table module is created by filling in a persistent @@ -6339,46 +7683,49 @@ typedef unsigned long long int sqlite_uint64; ** of this structure must not change while it is registered with ** any database connection. */ - struct sqlite3_module - { - int iVersion; - int (*xCreate)(sqlite3*, void* pAux, - int argc, const char* const* argv, - sqlite3_vtab** ppVTab, char**); - int (*xConnect)(sqlite3*, void* pAux, - int argc, const char* const* argv, - sqlite3_vtab** ppVTab, char**); - int (*xBestIndex)(sqlite3_vtab* pVTab, sqlite3_index_info*); - int (*xDisconnect)(sqlite3_vtab* pVTab); - int (*xDestroy)(sqlite3_vtab* pVTab); - int (*xOpen)(sqlite3_vtab* pVTab, sqlite3_vtab_cursor** ppCursor); - int (*xClose)(sqlite3_vtab_cursor*); - int (*xFilter)(sqlite3_vtab_cursor*, int idxNum, const char* idxStr, - int argc, sqlite3_value** argv); - int (*xNext)(sqlite3_vtab_cursor*); - int (*xEof)(sqlite3_vtab_cursor*); - int (*xColumn)(sqlite3_vtab_cursor*, sqlite3_context*, int); - int (*xRowid)(sqlite3_vtab_cursor*, sqlite3_int64* pRowid); - int (*xUpdate)(sqlite3_vtab*, int, sqlite3_value**, sqlite3_int64*); - int (*xBegin)(sqlite3_vtab* pVTab); - int (*xSync)(sqlite3_vtab* pVTab); - int (*xCommit)(sqlite3_vtab* pVTab); - int (*xRollback)(sqlite3_vtab* pVTab); - int (*xFindFunction)(sqlite3_vtab* pVtab, int nArg, const char* zName, - void (**pxFunc)(sqlite3_context*, int, sqlite3_value**), - void** ppArg); - int (*xRename)(sqlite3_vtab* pVtab, const char* zNew); - /* The methods above are in version 1 of the sqlite_module object. Those +struct sqlite3_module { + int iVersion; + int (*xCreate)(sqlite3*, void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVTab, char**); + int (*xConnect)(sqlite3*, void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVTab, char**); + int (*xBestIndex)(sqlite3_vtab *pVTab, sqlite3_index_info*); + int (*xDisconnect)(sqlite3_vtab *pVTab); + int (*xDestroy)(sqlite3_vtab *pVTab); + int (*xOpen)(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor); + int (*xClose)(sqlite3_vtab_cursor*); + int (*xFilter)(sqlite3_vtab_cursor*, int idxNum, const char *idxStr, + int argc, sqlite3_value **argv); + int (*xNext)(sqlite3_vtab_cursor*); + int (*xEof)(sqlite3_vtab_cursor*); + int (*xColumn)(sqlite3_vtab_cursor*, sqlite3_context*, int); + int (*xRowid)(sqlite3_vtab_cursor*, sqlite3_int64 *pRowid); + int (*xUpdate)(sqlite3_vtab *, int, sqlite3_value **, sqlite3_int64 *); + int (*xBegin)(sqlite3_vtab *pVTab); + int (*xSync)(sqlite3_vtab *pVTab); + int (*xCommit)(sqlite3_vtab *pVTab); + int (*xRollback)(sqlite3_vtab *pVTab); + int (*xFindFunction)(sqlite3_vtab *pVtab, int nArg, const char *zName, + void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), + void **ppArg); + int (*xRename)(sqlite3_vtab *pVtab, const char *zNew); + /* The methods above are in version 1 of the sqlite_module object. Those ** below are for version 2 and greater. */ - int (*xSavepoint)(sqlite3_vtab* pVTab, int); - int (*xRelease)(sqlite3_vtab* pVTab, int); - int (*xRollbackTo)(sqlite3_vtab* pVTab, int); - /* The methods above are in versions 1 and 2 of the sqlite_module object. + int (*xSavepoint)(sqlite3_vtab *pVTab, int); + int (*xRelease)(sqlite3_vtab *pVTab, int); + int (*xRollbackTo)(sqlite3_vtab *pVTab, int); + /* The methods above are in versions 1 and 2 of the sqlite_module object. ** Those below are for version 3 and greater. */ - int (*xShadowName)(const char*); - }; + int (*xShadowName)(const char*); + /* The methods above are in versions 1 through 3 of the sqlite_module object. + ** Those below are for version 4 and greater. */ + int (*xIntegrity)(sqlite3_vtab *pVTab, const char *zSchema, + const char *zTabName, int mFlags, char **pzErr); +}; - /* +/* ** CAPI3REF: Virtual Table Indexing Information ** KEYWORDS: sqlite3_index_info ** @@ -6418,7 +7765,7 @@ typedef unsigned long long int sqlite_uint64; ** required by SQLite. If the table has at least 64 columns and any column ** to the right of the first 63 is required, then bit 63 of colUsed is also ** set. In other words, column iCol may be required if the expression -** (colUsed & ((sqlite3_uint64)1 << (iCol>=63 ? 63 : iCol))) evaluates to +** (colUsed & ((sqlite3_uint64)1 << (iCol>=63 ? 63 : iCol))) evaluates to ** non-zero. ** ** The [xBestIndex] method must fill aConstraintUsage[] with information @@ -6426,12 +7773,18 @@ typedef unsigned long long int sqlite_uint64; ** the right-hand side of the corresponding aConstraint[] is evaluated ** and becomes the argvIndex-th entry in argv. ^(If aConstraintUsage[].omit ** is true, then the constraint is assumed to be fully handled by the -** virtual table and is not checked again by SQLite.)^ -** -** ^The idxNum and idxPtr values are recorded and passed into the +** virtual table and might not be checked again by the byte code.)^ ^(The +** aConstraintUsage[].omit flag is an optimization hint. When the omit flag +** is left in its default setting of false, the constraint will always be +** checked separately in byte code. If the omit flag is changed to true, then +** the constraint may or may not be checked in byte code. In other words, +** when the omit flag is true there is no guarantee that the constraint will +** not be checked again using byte code.)^ +** +** ^The idxNum and idxStr values are recorded and passed into the ** [xFilter] method. -** ^[sqlite3_free()] is used to free idxPtr if and only if -** needToFreeIdxPtr is true. +** ^[sqlite3_free()] is used to free idxStr if and only if +** needToFreeIdxStr is true. ** ** ^The orderByConsumed means that output from [xFilter]/[xNext] will occur in ** the correct order to satisfy the ORDER BY clause so that no separate @@ -6439,17 +7792,19 @@ typedef unsigned long long int sqlite_uint64; ** ** ^The estimatedCost value is an estimate of the cost of a particular ** strategy. A cost of N indicates that the cost of the strategy is similar -** to a linear scan of an SQLite table with N rows. A cost of log(N) +** to a linear scan of an SQLite table with N rows. A cost of log(N) ** indicates that the expense of the operation is similar to that of a ** binary search on a unique indexed field of an SQLite table with N rows. ** ** ^The estimatedRows value is an estimate of the number of rows that ** will be returned by the strategy. ** -** The xBestIndex method may optionally populate the idxFlags field with a -** mask of SQLITE_INDEX_SCAN_* flags. Currently there is only one such flag - -** SQLITE_INDEX_SCAN_UNIQUE. If the xBestIndex method sets this flag, SQLite -** assumes that the strategy may visit at most one row. +** The xBestIndex method may optionally populate the idxFlags field with a +** mask of SQLITE_INDEX_SCAN_* flags. One such flag is +** [SQLITE_INDEX_SCAN_HEX], which if set causes the [EXPLAIN QUERY PLAN] +** output to show the idxNum as hex instead of as decimal. Another flag is +** SQLITE_INDEX_SCAN_UNIQUE, which if set indicates that the query plan will +** return at most one row. ** ** Additionally, if xBestIndex sets the SQLITE_INDEX_SCAN_UNIQUE flag, then ** SQLite also assumes that if a call to the xUpdate() method is made as @@ -6462,88 +7817,118 @@ typedef unsigned long long int sqlite_uint64; ** the xUpdate method are automatically rolled back by SQLite. ** ** IMPORTANT: The estimatedRows field was added to the sqlite3_index_info -** structure for SQLite [version 3.8.2] ([dateof:3.8.2]). +** structure for SQLite [version 3.8.2] ([dateof:3.8.2]). ** If a virtual table extension is -** used with an SQLite version earlier than 3.8.2, the results of attempting -** to read or write the estimatedRows field are undefined (but are likely -** to included crashing the application). The estimatedRows field should +** used with an SQLite version earlier than 3.8.2, the results of attempting +** to read or write the estimatedRows field are undefined (but are likely +** to include crashing the application). The estimatedRows field should ** therefore only be used if [sqlite3_libversion_number()] returns a ** value greater than or equal to 3008002. Similarly, the idxFlags field -** was added for [version 3.9.0] ([dateof:3.9.0]). +** was added for [version 3.9.0] ([dateof:3.9.0]). ** It may therefore only be used if ** sqlite3_libversion_number() returns a value greater than or equal to ** 3009000. */ - struct sqlite3_index_info - { - /* Inputs */ - int nConstraint; /* Number of entries in aConstraint */ - struct sqlite3_index_constraint - { - int iColumn; /* Column constrained. -1 for ROWID */ - unsigned char op; /* Constraint operator */ - unsigned char usable; /* True if this constraint is usable */ - int iTermOffset; /* Used internally - xBestIndex should ignore */ - }* aConstraint; /* Table of WHERE clause constraints */ - int nOrderBy; /* Number of terms in the ORDER BY clause */ - struct sqlite3_index_orderby - { - int iColumn; /* Column number */ - unsigned char desc; /* True for DESC. False for ASC. */ - }* aOrderBy; /* The ORDER BY clause */ - /* Outputs */ - struct sqlite3_index_constraint_usage - { - int argvIndex; /* if >0, constraint is part of argv to xFilter */ - unsigned char omit; /* Do not code a test for this constraint */ - }* aConstraintUsage; - int idxNum; /* Number used to identify the index */ - char* idxStr; /* String, possibly obtained from sqlite3_malloc */ - int needToFreeIdxStr; /* Free idxStr using sqlite3_free() if true */ - int orderByConsumed; /* True if output is already ordered */ - double estimatedCost; /* Estimated cost of using this index */ - /* Fields below are only available in SQLite 3.8.2 and later */ - sqlite3_int64 estimatedRows; /* Estimated number of rows returned */ - /* Fields below are only available in SQLite 3.9.0 and later */ - int idxFlags; /* Mask of SQLITE_INDEX_SCAN_* flags */ - /* Fields below are only available in SQLite 3.10.0 and later */ - sqlite3_uint64 colUsed; /* Input: Mask of columns used by statement */ - }; +struct sqlite3_index_info { + /* Inputs */ + int nConstraint; /* Number of entries in aConstraint */ + struct sqlite3_index_constraint { + int iColumn; /* Column constrained. -1 for ROWID */ + unsigned char op; /* Constraint operator */ + unsigned char usable; /* True if this constraint is usable */ + int iTermOffset; /* Used internally - xBestIndex should ignore */ + } *aConstraint; /* Table of WHERE clause constraints */ + int nOrderBy; /* Number of terms in the ORDER BY clause */ + struct sqlite3_index_orderby { + int iColumn; /* Column number */ + unsigned char desc; /* True for DESC. False for ASC. */ + } *aOrderBy; /* The ORDER BY clause */ + /* Outputs */ + struct sqlite3_index_constraint_usage { + int argvIndex; /* if >0, constraint is part of argv to xFilter */ + unsigned char omit; /* Do not code a test for this constraint */ + } *aConstraintUsage; + int idxNum; /* Number used to identify the index */ + char *idxStr; /* String, possibly obtained from sqlite3_malloc */ + int needToFreeIdxStr; /* Free idxStr using sqlite3_free() if true */ + int orderByConsumed; /* True if output is already ordered */ + double estimatedCost; /* Estimated cost of using this index */ + /* Fields below are only available in SQLite 3.8.2 and later */ + sqlite3_int64 estimatedRows; /* Estimated number of rows returned */ + /* Fields below are only available in SQLite 3.9.0 and later */ + int idxFlags; /* Mask of SQLITE_INDEX_SCAN_* flags */ + /* Fields below are only available in SQLite 3.10.0 and later */ + sqlite3_uint64 colUsed; /* Input: Mask of columns used by statement */ +}; /* ** CAPI3REF: Virtual Table Scan Flags ** -** Virtual table implementations are allowed to set the +** Virtual table implementations are allowed to set the ** [sqlite3_index_info].idxFlags field to some combination of ** these bits. */ -#define SQLITE_INDEX_SCAN_UNIQUE 1 /* Scan visits at most 1 row */ +#define SQLITE_INDEX_SCAN_UNIQUE 0x00000001 /* Scan visits at most 1 row */ +#define SQLITE_INDEX_SCAN_HEX 0x00000002 /* Display idxNum as hex */ + /* in EXPLAIN QUERY PLAN */ /* ** CAPI3REF: Virtual Table Constraint Operator Codes ** -** These macros defined the allowed values for the +** These macros define the allowed values for the ** [sqlite3_index_info].aConstraint[].op field. Each value represents -** an operator that is part of a constraint term in the wHERE clause of +** an operator that is part of a constraint term in the WHERE clause of ** a query that uses a [virtual table]. -*/ -#define SQLITE_INDEX_CONSTRAINT_EQ 2 -#define SQLITE_INDEX_CONSTRAINT_GT 4 -#define SQLITE_INDEX_CONSTRAINT_LE 8 -#define SQLITE_INDEX_CONSTRAINT_LT 16 -#define SQLITE_INDEX_CONSTRAINT_GE 32 -#define SQLITE_INDEX_CONSTRAINT_MATCH 64 -#define SQLITE_INDEX_CONSTRAINT_LIKE 65 -#define SQLITE_INDEX_CONSTRAINT_GLOB 66 -#define SQLITE_INDEX_CONSTRAINT_REGEXP 67 -#define SQLITE_INDEX_CONSTRAINT_NE 68 -#define SQLITE_INDEX_CONSTRAINT_ISNOT 69 -#define SQLITE_INDEX_CONSTRAINT_ISNOTNULL 70 -#define SQLITE_INDEX_CONSTRAINT_ISNULL 71 -#define SQLITE_INDEX_CONSTRAINT_IS 72 -#define SQLITE_INDEX_CONSTRAINT_FUNCTION 150 - - /* +** +** ^The left-hand operand of the operator is given by the corresponding +** aConstraint[].iColumn field. ^An iColumn of -1 indicates the left-hand +** operand is the rowid. +** The SQLITE_INDEX_CONSTRAINT_LIMIT and SQLITE_INDEX_CONSTRAINT_OFFSET +** operators have no left-hand operand, and so for those operators the +** corresponding aConstraint[].iColumn is meaningless and should not be +** used. +** +** All operator values from SQLITE_INDEX_CONSTRAINT_FUNCTION through +** value 255 are reserved to represent functions that are overloaded +** by the [xFindFunction|xFindFunction method] of the virtual table +** implementation. +** +** The right-hand operands for each constraint might be accessible using +** the [sqlite3_vtab_rhs_value()] interface. Usually the right-hand +** operand is only available if it appears as a single constant literal +** in the input SQL. If the right-hand operand is another column or an +** expression (even a constant expression) or a parameter, then the +** sqlite3_vtab_rhs_value() probably will not be able to extract it. +** ^The SQLITE_INDEX_CONSTRAINT_ISNULL and +** SQLITE_INDEX_CONSTRAINT_ISNOTNULL operators have no right-hand operand +** and hence calls to sqlite3_vtab_rhs_value() for those operators will +** always return SQLITE_NOTFOUND. +** +** The collating sequence to be used for comparison can be found using +** the [sqlite3_vtab_collation()] interface. For most real-world virtual +** tables, the collating sequence of constraints does not matter (for example +** because the constraints are numeric) and so the sqlite3_vtab_collation() +** interface is not commonly needed. +*/ +#define SQLITE_INDEX_CONSTRAINT_EQ 2 +#define SQLITE_INDEX_CONSTRAINT_GT 4 +#define SQLITE_INDEX_CONSTRAINT_LE 8 +#define SQLITE_INDEX_CONSTRAINT_LT 16 +#define SQLITE_INDEX_CONSTRAINT_GE 32 +#define SQLITE_INDEX_CONSTRAINT_MATCH 64 +#define SQLITE_INDEX_CONSTRAINT_LIKE 65 +#define SQLITE_INDEX_CONSTRAINT_GLOB 66 +#define SQLITE_INDEX_CONSTRAINT_REGEXP 67 +#define SQLITE_INDEX_CONSTRAINT_NE 68 +#define SQLITE_INDEX_CONSTRAINT_ISNOT 69 +#define SQLITE_INDEX_CONSTRAINT_ISNOTNULL 70 +#define SQLITE_INDEX_CONSTRAINT_ISNULL 71 +#define SQLITE_INDEX_CONSTRAINT_IS 72 +#define SQLITE_INDEX_CONSTRAINT_LIMIT 73 +#define SQLITE_INDEX_CONSTRAINT_OFFSET 74 +#define SQLITE_INDEX_CONSTRAINT_FUNCTION 150 + +/* ** CAPI3REF: Register A Virtual Table Implementation ** METHOD: sqlite3 ** @@ -6553,12 +7938,12 @@ typedef unsigned long long int sqlite_uint64; ** preexisting [virtual table] for the module. ** ** ^The module name is registered on the [database connection] specified -** by the first parameter. ^The name of the module is given by the +** by the first parameter. ^The name of the module is given by the ** second parameter. ^The third parameter is a pointer to ** the implementation of the [virtual table module]. ^The fourth ** parameter is an arbitrary client data pointer that is passed through ** into the [xCreate] and [xConnect] methods of the virtual table module -** when a new virtual table is be being created or reinitialized. +** when a new virtual table is being created or reinitialized. ** ** ^The sqlite3_create_module_v2() interface has a fifth parameter which ** is a pointer to a destructor for the pClientData. ^SQLite will @@ -6568,22 +7953,45 @@ typedef unsigned long long int sqlite_uint64; ** ^The sqlite3_create_module() ** interface is equivalent to sqlite3_create_module_v2() with a NULL ** destructor. +** +** ^If the third parameter (the pointer to the sqlite3_module object) is +** NULL then no new module is created and any existing modules with the +** same name are dropped. +** +** See also: [sqlite3_drop_modules()] +*/ +SQLITE_API int sqlite3_create_module( + sqlite3 *db, /* SQLite connection to register module with */ + const char *zName, /* Name of the module */ + const sqlite3_module *p, /* Methods for the module */ + void *pClientData /* Client data for xCreate/xConnect */ +); +SQLITE_API int sqlite3_create_module_v2( + sqlite3 *db, /* SQLite connection to register module with */ + const char *zName, /* Name of the module */ + const sqlite3_module *p, /* Methods for the module */ + void *pClientData, /* Client data for xCreate/xConnect */ + void(*xDestroy)(void*) /* Module destructor function */ +); + +/* +** CAPI3REF: Remove Unnecessary Virtual Table Implementations +** METHOD: sqlite3 +** +** ^The sqlite3_drop_modules(D,L) interface removes all virtual +** table modules from database connection D except those named on list L. +** The L parameter must be either NULL or a pointer to an array of pointers +** to strings where the array is terminated by a single NULL pointer. +** ^If the L parameter is NULL, then all virtual table modules are removed. +** +** See also: [sqlite3_create_module()] */ - SQLITE_API int sqlite3_create_module( - sqlite3* db, /* SQLite connection to register module with */ - const char* zName, /* Name of the module */ - const sqlite3_module* p, /* Methods for the module */ - void* pClientData /* Client data for xCreate/xConnect */ - ); - SQLITE_API int sqlite3_create_module_v2( - sqlite3* db, /* SQLite connection to register module with */ - const char* zName, /* Name of the module */ - const sqlite3_module* p, /* Methods for the module */ - void* pClientData, /* Client data for xCreate/xConnect */ - void (*xDestroy)(void*) /* Module destructor function */ - ); - - /* +SQLITE_API int sqlite3_drop_modules( + sqlite3 *db, /* Remove modules from this connection */ + const char **azKeep /* Except, do not remove the ones named here */ +); + +/* ** CAPI3REF: Virtual Table Instance Object ** KEYWORDS: sqlite3_vtab ** @@ -6601,15 +8009,14 @@ typedef unsigned long long int sqlite_uint64; ** is delivered up to the client application, the string will be automatically ** freed by sqlite3_free() and the zErrMsg field will be zeroed. */ - struct sqlite3_vtab - { - const sqlite3_module* pModule; /* The module for this virtual table */ - int nRef; /* Number of open cursors */ - char* zErrMsg; /* Error message from sqlite3_mprintf() */ - /* Virtual table implementations will typically add additional fields */ - }; +struct sqlite3_vtab { + const sqlite3_module *pModule; /* The module for this virtual table */ + int nRef; /* Number of open cursors */ + char *zErrMsg; /* Error message from sqlite3_mprintf() */ + /* Virtual table implementations will typically add additional fields */ +}; - /* +/* ** CAPI3REF: Virtual Table Cursor Object ** KEYWORDS: sqlite3_vtab_cursor {virtual table cursor} ** @@ -6626,13 +8033,12 @@ typedef unsigned long long int sqlite_uint64; ** This superclass exists in order to define fields of the cursor that ** are common to all implementations. */ - struct sqlite3_vtab_cursor - { - sqlite3_vtab* pVtab; /* Virtual table of this cursor */ - /* Virtual table implementations will typically add additional fields */ - }; +struct sqlite3_vtab_cursor { + sqlite3_vtab *pVtab; /* Virtual table of this cursor */ + /* Virtual table implementations will typically add additional fields */ +}; - /* +/* ** CAPI3REF: Declare The Schema Of A Virtual Table ** ** ^The [xCreate] and [xConnect] methods of a @@ -6640,14 +8046,14 @@ typedef unsigned long long int sqlite_uint64; ** to declare the format (the names and datatypes of the columns) of ** the virtual tables they implement. */ - SQLITE_API int sqlite3_declare_vtab(sqlite3*, const char* zSQL); +SQLITE_API int sqlite3_declare_vtab(sqlite3*, const char *zSQL); - /* +/* ** CAPI3REF: Overload A Function For A Virtual Table ** METHOD: sqlite3 ** ** ^(Virtual tables can provide alternative implementations of functions -** using the [xFindFunction] method of the [virtual table module]. +** using the [xFindFunction] method of the [virtual table module]. ** But global versions of those functions ** must exist in order to be overloaded.)^ ** @@ -6659,19 +8065,9 @@ typedef unsigned long long int sqlite_uint64; ** purpose is to be a placeholder function that can be overloaded ** by a [virtual table]. */ - SQLITE_API int sqlite3_overload_function(sqlite3*, const char* zFuncName, int nArg); - - /* -** The interface to the virtual-table mechanism defined above (back up -** to a comment remarkably similar to this one) is currently considered -** to be experimental. The interface might change in incompatible ways. -** If this is a problem for you, do not use the interface at this time. -** -** When the virtual-table mechanism stabilizes, we will declare the -** interface fixed, support it indefinitely, and remove this comment. -*/ +SQLITE_API int sqlite3_overload_function(sqlite3*, const char *zFuncName, int nArg); - /* +/* ** CAPI3REF: A Handle To An Open BLOB ** KEYWORDS: {BLOB handle} {BLOB handles} ** @@ -6683,9 +8079,9 @@ typedef unsigned long long int sqlite_uint64; ** can be used to read or write small subsections of the BLOB. ** ^The [sqlite3_blob_bytes()] interface returns the size of the BLOB in bytes. */ - typedef struct sqlite3_blob sqlite3_blob; +typedef struct sqlite3_blob sqlite3_blob; - /* +/* ** CAPI3REF: Open A BLOB For Incremental I/O ** METHOD: sqlite3 ** CONSTRUCTOR: sqlite3_blob @@ -6698,7 +8094,7 @@ typedef unsigned long long int sqlite_uint64; ** SELECT zColumn FROM zDb.zTable WHERE [rowid] = iRow; ** )^ ** -** ^(Parameter zDb is not the filename that contains the database, but +** ^(Parameter zDb is not the filename that contains the database, but ** rather the symbolic name of the database. For attached databases, this is ** the name that appears after the AS keyword in the [ATTACH] statement. ** For the main database file, the database name is "main". For TEMP @@ -6711,28 +8107,28 @@ typedef unsigned long long int sqlite_uint64; ** ^(On success, [SQLITE_OK] is returned and the new [BLOB handle] is stored ** in *ppBlob. Otherwise an [error code] is returned and, unless the error ** code is SQLITE_MISUSE, *ppBlob is set to NULL.)^ ^This means that, provided -** the API is not misused, it is always safe to call [sqlite3_blob_close()] -** on *ppBlob after this function it returns. +** the API is not misused, it is always safe to call [sqlite3_blob_close()] +** on *ppBlob after this function returns. ** ** This function fails with SQLITE_ERROR if any of the following are true: **
      -**
    • ^(Database zDb does not exist)^, -**
    • ^(Table zTable does not exist within database zDb)^, -**
    • ^(Table zTable is a WITHOUT ROWID table)^, +**
    • ^(Database zDb does not exist)^, +**
    • ^(Table zTable does not exist within database zDb)^, +**
    • ^(Table zTable is a WITHOUT ROWID table)^, **
    • ^(Column zColumn does not exist)^, **
    • ^(Row iRow is not present in the table)^, **
    • ^(The specified column of row iRow contains a value that is not ** a TEXT or BLOB value)^, -**
    • ^(Column zColumn is part of an index, PRIMARY KEY or UNIQUE +**
    • ^(Column zColumn is part of an index, PRIMARY KEY or UNIQUE ** constraint and the blob is being opened for read/write access)^, -**
    • ^([foreign key constraints | Foreign key constraints] are enabled, +**
    • ^([foreign key constraints | Foreign key constraints] are enabled, ** column zColumn is part of a [child key] definition and the blob is ** being opened for read/write access)^. **
    ** -** ^Unless it returns SQLITE_MISUSE, this function sets the -** [database connection] error code and message accessible via -** [sqlite3_errcode()] and [sqlite3_errmsg()] and related functions. +** ^Unless it returns SQLITE_MISUSE, this function sets the +** [database connection] error code and message accessible via +** [sqlite3_errcode()] and [sqlite3_errmsg()] and related functions. ** ** A BLOB referenced by sqlite3_blob_open() may be read using the ** [sqlite3_blob_read()] interface and modified by using @@ -6758,7 +8154,7 @@ typedef unsigned long long int sqlite_uint64; ** blob. ** ** ^The [sqlite3_bind_zeroblob()] and [sqlite3_result_zeroblob()] interfaces -** and the built-in [zeroblob] SQL function may be used to create a +** and the built-in [zeroblob] SQL function may be used to create a ** zero-filled blob to read or write using the incremental-blob interface. ** ** To avoid a resource leak, every open [BLOB handle] should eventually @@ -6768,16 +8164,17 @@ typedef unsigned long long int sqlite_uint64; ** [sqlite3_blob_reopen()], [sqlite3_blob_read()], ** [sqlite3_blob_bytes()], [sqlite3_blob_write()]. */ - SQLITE_API int sqlite3_blob_open( - sqlite3*, - const char* zDb, - const char* zTable, - const char* zColumn, - sqlite3_int64 iRow, - int flags, - sqlite3_blob** ppBlob); +SQLITE_API int sqlite3_blob_open( + sqlite3*, + const char *zDb, + const char *zTable, + const char *zColumn, + sqlite3_int64 iRow, + int flags, + sqlite3_blob **ppBlob +); - /* +/* ** CAPI3REF: Move a BLOB Handle to a New Row ** METHOD: sqlite3_blob ** @@ -6800,14 +8197,14 @@ typedef unsigned long long int sqlite_uint64; ** ** ^This function sets the database handle error code and message. */ - SQLITE_API int sqlite3_blob_reopen(sqlite3_blob*, sqlite3_int64); +SQLITE_API int sqlite3_blob_reopen(sqlite3_blob *, sqlite3_int64); - /* +/* ** CAPI3REF: Close A BLOB Handle ** DESTRUCTOR: sqlite3_blob ** ** ^This function closes an open [BLOB handle]. ^(The BLOB handle is closed -** unconditionally. Even if this routine returns an error code, the +** unconditionally. Even if this routine returns an error code, the ** handle is still closed.)^ ** ** ^If the blob handle being closed was opened for read-write access, and if @@ -6817,21 +8214,21 @@ typedef unsigned long long int sqlite_uint64; ** code is returned and the transaction rolled back. ** ** Calling this function with an argument that is not a NULL pointer or an -** open blob handle results in undefined behaviour. ^Calling this routine -** with a null pointer (such as would be returned by a failed call to +** open blob handle results in undefined behavior. ^Calling this routine +** with a null pointer (such as would be returned by a failed call to ** [sqlite3_blob_open()]) is a harmless no-op. ^Otherwise, if this function -** is passed a valid open blob handle, the values returned by the +** is passed a valid open blob handle, the values returned by the ** sqlite3_errcode() and sqlite3_errmsg() functions are set before returning. */ - SQLITE_API int sqlite3_blob_close(sqlite3_blob*); +SQLITE_API int sqlite3_blob_close(sqlite3_blob *); - /* +/* ** CAPI3REF: Return The Size Of An Open BLOB ** METHOD: sqlite3_blob ** -** ^Returns the size in bytes of the BLOB accessible via the +** ^Returns the size in bytes of the BLOB accessible via the ** successfully opened [BLOB handle] in its only argument. ^The -** incremental blob I/O routines can only read or overwriting existing +** incremental blob I/O routines can only read or overwrite existing ** blob content; they cannot change the size of a blob. ** ** This routine only works on a [BLOB handle] which has been created @@ -6839,9 +8236,9 @@ typedef unsigned long long int sqlite_uint64; ** been closed by [sqlite3_blob_close()]. Passing any other pointer in ** to this routine results in undefined and probably undesirable behavior. */ - SQLITE_API int sqlite3_blob_bytes(sqlite3_blob*); +SQLITE_API int sqlite3_blob_bytes(sqlite3_blob *); - /* +/* ** CAPI3REF: Read Data From A BLOB Incrementally ** METHOD: sqlite3_blob ** @@ -6868,9 +8265,9 @@ typedef unsigned long long int sqlite_uint64; ** ** See also: [sqlite3_blob_write()]. */ - SQLITE_API int sqlite3_blob_read(sqlite3_blob*, void* Z, int N, int iOffset); +SQLITE_API int sqlite3_blob_read(sqlite3_blob *, void *Z, int N, int iOffset); - /* +/* ** CAPI3REF: Write Data Into A BLOB Incrementally ** METHOD: sqlite3_blob ** @@ -6880,9 +8277,9 @@ typedef unsigned long long int sqlite_uint64; ** ** ^(On success, sqlite3_blob_write() returns SQLITE_OK. ** Otherwise, an [error code] or an [extended error code] is returned.)^ -** ^Unless SQLITE_MISUSE is returned, this function sets the -** [database connection] error code and message accessible via -** [sqlite3_errcode()] and [sqlite3_errmsg()] and related functions. +** ^Unless SQLITE_MISUSE is returned, this function sets the +** [database connection] error code and message accessible via +** [sqlite3_errcode()] and [sqlite3_errmsg()] and related functions. ** ** ^If the [BLOB handle] passed as the first argument was not opened for ** writing (the flags parameter to [sqlite3_blob_open()] was zero), @@ -6891,9 +8288,9 @@ typedef unsigned long long int sqlite_uint64; ** This function may only modify the contents of the BLOB; it is ** not possible to increase the size of a BLOB using this API. ** ^If offset iOffset is less than N bytes from the end of the BLOB, -** [SQLITE_ERROR] is returned and no data is written. The size of the -** BLOB (and hence the maximum value of N+iOffset) can be determined -** using the [sqlite3_blob_bytes()] interface. ^If N or iOffset are less +** [SQLITE_ERROR] is returned and no data is written. The size of the +** BLOB (and hence the maximum value of N+iOffset) can be determined +** using the [sqlite3_blob_bytes()] interface. ^If N or iOffset are less ** than zero [SQLITE_ERROR] is returned and no data is written. ** ** ^An attempt to write to an expired [BLOB handle] fails with an @@ -6910,9 +8307,9 @@ typedef unsigned long long int sqlite_uint64; ** ** See also: [sqlite3_blob_read()]. */ - SQLITE_API int sqlite3_blob_write(sqlite3_blob*, const void* z, int n, int iOffset); +SQLITE_API int sqlite3_blob_write(sqlite3_blob *, const void *z, int n, int iOffset); - /* +/* ** CAPI3REF: Virtual File System Objects ** ** A virtual filesystem (VFS) is an [sqlite3_vfs] object @@ -6941,11 +8338,11 @@ typedef unsigned long long int sqlite_uint64; ** ^(If the default VFS is unregistered, another VFS is chosen as ** the default. The choice for the new VFS is arbitrary.)^ */ - SQLITE_API sqlite3_vfs* sqlite3_vfs_find(const char* zVfsName); - SQLITE_API int sqlite3_vfs_register(sqlite3_vfs*, int makeDflt); - SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*); +SQLITE_API sqlite3_vfs *sqlite3_vfs_find(const char *zVfsName); +SQLITE_API int sqlite3_vfs_register(sqlite3_vfs*, int makeDflt); +SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*); - /* +/* ** CAPI3REF: Mutexes ** ** The SQLite core uses these routines for thread @@ -6970,24 +8367,17 @@ typedef unsigned long long int sqlite_uint64; ** SQLITE_MUTEX_W32 implementations are appropriate for use on Unix ** and Windows. ** -** If SQLite is compiled with the SQLITE_MUTEX_APPDEF preprocessor -** macro defined (with "-DSQLITE_MUTEX_APPDEF=1"), then no mutex -** implementation is included with the library. In this case the -** application must supply a custom mutex implementation using the -** [SQLITE_CONFIG_MUTEX] option of the sqlite3_config() function -** before calling sqlite3_initialize() or any other public sqlite3_ -** function that calls sqlite3_initialize(). ** ** ^The sqlite3_mutex_alloc() routine allocates a new ** mutex and returns a pointer to it. ^The sqlite3_mutex_alloc() ** routine returns NULL if it is unable to allocate the requested -** mutex. The argument to sqlite3_mutex_alloc() must one of these +** mutex. The argument to sqlite3_mutex_alloc() must be one of these ** integer constants: ** **
      **
    • SQLITE_MUTEX_FAST **
    • SQLITE_MUTEX_RECURSIVE -**
    • SQLITE_MUTEX_STATIC_MASTER +**
    • SQLITE_MUTEX_STATIC_MAIN **
    • SQLITE_MUTEX_STATIC_MEM **
    • SQLITE_MUTEX_STATIC_OPEN **
    • SQLITE_MUTEX_STATIC_PRNG @@ -7044,28 +8434,30 @@ typedef unsigned long long int sqlite_uint64; ** ** ^(Some systems (for example, Windows 95) do not support the operation ** implemented by sqlite3_mutex_try(). On those systems, sqlite3_mutex_try() -** will always return SQLITE_BUSY. The SQLite core only ever uses -** sqlite3_mutex_try() as an optimization so this is acceptable -** behavior.)^ +** will always return SQLITE_BUSY. In most cases the SQLite core only uses +** sqlite3_mutex_try() as an optimization, so this is acceptable +** behavior. The exceptions are unix builds that set the +** SQLITE_ENABLE_SETLK_TIMEOUT build option. In that case a working +** sqlite3_mutex_try() is required.)^ ** ** ^The sqlite3_mutex_leave() routine exits a mutex that was ** previously entered by the same thread. The behavior ** is undefined if the mutex is not currently entered by the ** calling thread or is not currently allocated. ** -** ^If the argument to sqlite3_mutex_enter(), sqlite3_mutex_try(), or -** sqlite3_mutex_leave() is a NULL pointer, then all three routines -** behave as no-ops. +** ^If the argument to sqlite3_mutex_enter(), sqlite3_mutex_try(), +** sqlite3_mutex_leave(), or sqlite3_mutex_free() is a NULL pointer, +** then any of the four routines behaves as a no-op. ** ** See also: [sqlite3_mutex_held()] and [sqlite3_mutex_notheld()]. */ - SQLITE_API sqlite3_mutex* sqlite3_mutex_alloc(int); - SQLITE_API void sqlite3_mutex_free(sqlite3_mutex*); - SQLITE_API void sqlite3_mutex_enter(sqlite3_mutex*); - SQLITE_API int sqlite3_mutex_try(sqlite3_mutex*); - SQLITE_API void sqlite3_mutex_leave(sqlite3_mutex*); +SQLITE_API sqlite3_mutex *sqlite3_mutex_alloc(int); +SQLITE_API void sqlite3_mutex_free(sqlite3_mutex*); +SQLITE_API void sqlite3_mutex_enter(sqlite3_mutex*); +SQLITE_API int sqlite3_mutex_try(sqlite3_mutex*); +SQLITE_API void sqlite3_mutex_leave(sqlite3_mutex*); - /* +/* ** CAPI3REF: Mutex Methods Object ** ** An instance of this structure defines the low-level routines @@ -7110,7 +8502,7 @@ typedef unsigned long long int sqlite_uint64; ** The only difference is that the public sqlite3_XXX functions enumerated ** above silently ignore any invocations that pass a NULL pointer instead ** of a valid mutex handle. The implementations of the methods defined -** by this structure are not required to handle this case, the results +** by this structure are not required to handle this case. The results ** of passing a NULL pointer instead of a valid mutex handle are undefined ** (i.e. it is acceptable to provide an implementation that segfaults if ** it is passed a NULL pointer). @@ -7130,19 +8522,18 @@ typedef unsigned long long int sqlite_uint64; ** If xMutexInit fails in any way, it is expected to clean up after itself ** prior to returning. */ - typedef struct sqlite3_mutex_methods sqlite3_mutex_methods; - struct sqlite3_mutex_methods - { - int (*xMutexInit)(void); - int (*xMutexEnd)(void); - sqlite3_mutex* (*xMutexAlloc)(int); - void (*xMutexFree)(sqlite3_mutex*); - void (*xMutexEnter)(sqlite3_mutex*); - int (*xMutexTry)(sqlite3_mutex*); - void (*xMutexLeave)(sqlite3_mutex*); - int (*xMutexHeld)(sqlite3_mutex*); - int (*xMutexNotheld)(sqlite3_mutex*); - }; +typedef struct sqlite3_mutex_methods sqlite3_mutex_methods; +struct sqlite3_mutex_methods { + int (*xMutexInit)(void); + int (*xMutexEnd)(void); + sqlite3_mutex *(*xMutexAlloc)(int); + void (*xMutexFree)(sqlite3_mutex *); + void (*xMutexEnter)(sqlite3_mutex *); + int (*xMutexTry)(sqlite3_mutex *); + void (*xMutexLeave)(sqlite3_mutex *); + int (*xMutexHeld)(sqlite3_mutex *); + int (*xMutexNotheld)(sqlite3_mutex *); +}; /* ** CAPI3REF: Mutex Verification Routines @@ -7174,8 +8565,8 @@ typedef unsigned long long int sqlite_uint64; ** interface should also return 1 when given a NULL pointer. */ #ifndef NDEBUG - SQLITE_API int sqlite3_mutex_held(sqlite3_mutex*); - SQLITE_API int sqlite3_mutex_notheld(sqlite3_mutex*); +SQLITE_API int sqlite3_mutex_held(sqlite3_mutex*); +SQLITE_API int sqlite3_mutex_notheld(sqlite3_mutex*); #endif /* @@ -7188,36 +8579,40 @@ typedef unsigned long long int sqlite_uint64; ** next. Applications that override the built-in mutex logic must be ** prepared to accommodate additional static mutexes. */ -#define SQLITE_MUTEX_FAST 0 -#define SQLITE_MUTEX_RECURSIVE 1 -#define SQLITE_MUTEX_STATIC_MASTER 2 -#define SQLITE_MUTEX_STATIC_MEM 3 /* sqlite3_malloc() */ -#define SQLITE_MUTEX_STATIC_MEM2 4 /* NOT USED */ -#define SQLITE_MUTEX_STATIC_OPEN 4 /* sqlite3BtreeOpen() */ -#define SQLITE_MUTEX_STATIC_PRNG 5 /* sqlite3_randomness() */ -#define SQLITE_MUTEX_STATIC_LRU 6 /* lru page list */ -#define SQLITE_MUTEX_STATIC_LRU2 7 /* NOT USED */ -#define SQLITE_MUTEX_STATIC_PMEM 7 /* sqlite3PageMalloc() */ -#define SQLITE_MUTEX_STATIC_APP1 8 /* For use by application */ -#define SQLITE_MUTEX_STATIC_APP2 9 /* For use by application */ -#define SQLITE_MUTEX_STATIC_APP3 10 /* For use by application */ -#define SQLITE_MUTEX_STATIC_VFS1 11 /* For use by built-in VFS */ -#define SQLITE_MUTEX_STATIC_VFS2 12 /* For use by extension VFS */ -#define SQLITE_MUTEX_STATIC_VFS3 13 /* For use by application VFS */ - - /* +#define SQLITE_MUTEX_FAST 0 +#define SQLITE_MUTEX_RECURSIVE 1 +#define SQLITE_MUTEX_STATIC_MAIN 2 +#define SQLITE_MUTEX_STATIC_MEM 3 /* sqlite3_malloc() */ +#define SQLITE_MUTEX_STATIC_MEM2 4 /* NOT USED */ +#define SQLITE_MUTEX_STATIC_OPEN 4 /* sqlite3BtreeOpen() */ +#define SQLITE_MUTEX_STATIC_PRNG 5 /* sqlite3_randomness() */ +#define SQLITE_MUTEX_STATIC_LRU 6 /* lru page list */ +#define SQLITE_MUTEX_STATIC_LRU2 7 /* NOT USED */ +#define SQLITE_MUTEX_STATIC_PMEM 7 /* sqlite3PageMalloc() */ +#define SQLITE_MUTEX_STATIC_APP1 8 /* For use by application */ +#define SQLITE_MUTEX_STATIC_APP2 9 /* For use by application */ +#define SQLITE_MUTEX_STATIC_APP3 10 /* For use by application */ +#define SQLITE_MUTEX_STATIC_VFS1 11 /* For use by built-in VFS */ +#define SQLITE_MUTEX_STATIC_VFS2 12 /* For use by extension VFS */ +#define SQLITE_MUTEX_STATIC_VFS3 13 /* For use by application VFS */ + +/* Legacy compatibility: */ +#define SQLITE_MUTEX_STATIC_MASTER 2 + + +/* ** CAPI3REF: Retrieve the mutex for a database connection ** METHOD: sqlite3 ** -** ^This interface returns a pointer the [sqlite3_mutex] object that +** ^This interface returns a pointer to the [sqlite3_mutex] object that ** serializes access to the [database connection] given in the argument ** when the [threading mode] is Serialized. ** ^If the [threading mode] is Single-thread or Multi-thread then this ** routine returns a NULL pointer. */ - SQLITE_API sqlite3_mutex* sqlite3_db_mutex(sqlite3*); +SQLITE_API sqlite3_mutex *sqlite3_db_mutex(sqlite3*); - /* +/* ** CAPI3REF: Low-Level Control Of Database Files ** METHOD: sqlite3 ** KEYWORDS: {file control} @@ -7236,7 +8631,7 @@ typedef unsigned long long int sqlite_uint64; ** method becomes the return value of this routine. ** ** A few opcodes for [sqlite3_file_control()] are handled directly -** by the SQLite core and never invoke the +** by the SQLite core and never invoke the ** sqlite3_io_methods.xFileControl method. ** ^The [SQLITE_FCNTL_FILE_POINTER] value for the op parameter causes ** a pointer to the underlying [sqlite3_file] object to be written into @@ -7258,9 +8653,9 @@ typedef unsigned long long int sqlite_uint64; ** ** See also: [file control opcodes] */ - SQLITE_API int sqlite3_file_control(sqlite3*, const char* zDbName, int op, void*); +SQLITE_API int sqlite3_file_control(sqlite3*, const char *zDbName, int op, void*); - /* +/* ** CAPI3REF: Testing Interface ** ** ^The sqlite3_test_control() interface is used to read out internal @@ -7277,7 +8672,7 @@ typedef unsigned long long int sqlite_uint64; ** Unlike most of the SQLite API, this function is not guaranteed to ** operate consistently from one release to the next. */ - SQLITE_API int sqlite3_test_control(int op, ...); +SQLITE_API int sqlite3_test_control(int op, ...); /* ** CAPI3REF: Testing Interface Operation Codes @@ -7290,45 +8685,57 @@ typedef unsigned long long int sqlite_uint64; ** Applications should not use any of these parameters or the ** [sqlite3_test_control()] interface. */ -#define SQLITE_TESTCTRL_FIRST 5 -#define SQLITE_TESTCTRL_PRNG_SAVE 5 -#define SQLITE_TESTCTRL_PRNG_RESTORE 6 -#define SQLITE_TESTCTRL_PRNG_RESET 7 -#define SQLITE_TESTCTRL_BITVEC_TEST 8 -#define SQLITE_TESTCTRL_FAULT_INSTALL 9 -#define SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS 10 -#define SQLITE_TESTCTRL_PENDING_BYTE 11 -#define SQLITE_TESTCTRL_ASSERT 12 -#define SQLITE_TESTCTRL_ALWAYS 13 -#define SQLITE_TESTCTRL_RESERVE 14 -#define SQLITE_TESTCTRL_OPTIMIZATIONS 15 -#define SQLITE_TESTCTRL_ISKEYWORD 16 /* NOT USED */ -#define SQLITE_TESTCTRL_SCRATCHMALLOC 17 /* NOT USED */ -#define SQLITE_TESTCTRL_INTERNAL_FUNCTIONS 17 -#define SQLITE_TESTCTRL_LOCALTIME_FAULT 18 -#define SQLITE_TESTCTRL_EXPLAIN_STMT 19 /* NOT USED */ -#define SQLITE_TESTCTRL_ONCE_RESET_THRESHOLD 19 -#define SQLITE_TESTCTRL_NEVER_CORRUPT 20 -#define SQLITE_TESTCTRL_VDBE_COVERAGE 21 -#define SQLITE_TESTCTRL_BYTEORDER 22 -#define SQLITE_TESTCTRL_ISINIT 23 -#define SQLITE_TESTCTRL_SORTER_MMAP 24 -#define SQLITE_TESTCTRL_IMPOSTER 25 -#define SQLITE_TESTCTRL_PARSER_COVERAGE 26 -#define SQLITE_TESTCTRL_LAST 26 /* Largest TESTCTRL */ - - /* +#define SQLITE_TESTCTRL_FIRST 5 +#define SQLITE_TESTCTRL_PRNG_SAVE 5 +#define SQLITE_TESTCTRL_PRNG_RESTORE 6 +#define SQLITE_TESTCTRL_PRNG_RESET 7 /* NOT USED */ +#define SQLITE_TESTCTRL_FK_NO_ACTION 7 +#define SQLITE_TESTCTRL_BITVEC_TEST 8 +#define SQLITE_TESTCTRL_FAULT_INSTALL 9 +#define SQLITE_TESTCTRL_BENIGN_MALLOC_HOOKS 10 +#define SQLITE_TESTCTRL_PENDING_BYTE 11 +#define SQLITE_TESTCTRL_ASSERT 12 +#define SQLITE_TESTCTRL_ALWAYS 13 +#define SQLITE_TESTCTRL_RESERVE 14 /* NOT USED */ +#define SQLITE_TESTCTRL_JSON_SELFCHECK 14 +#define SQLITE_TESTCTRL_OPTIMIZATIONS 15 +#define SQLITE_TESTCTRL_ISKEYWORD 16 /* NOT USED */ +#define SQLITE_TESTCTRL_GETOPT 16 +#define SQLITE_TESTCTRL_SCRATCHMALLOC 17 /* NOT USED */ +#define SQLITE_TESTCTRL_INTERNAL_FUNCTIONS 17 +#define SQLITE_TESTCTRL_LOCALTIME_FAULT 18 +#define SQLITE_TESTCTRL_EXPLAIN_STMT 19 /* NOT USED */ +#define SQLITE_TESTCTRL_ONCE_RESET_THRESHOLD 19 +#define SQLITE_TESTCTRL_NEVER_CORRUPT 20 +#define SQLITE_TESTCTRL_VDBE_COVERAGE 21 +#define SQLITE_TESTCTRL_BYTEORDER 22 +#define SQLITE_TESTCTRL_ISINIT 23 +#define SQLITE_TESTCTRL_SORTER_MMAP 24 +#define SQLITE_TESTCTRL_IMPOSTER 25 +#define SQLITE_TESTCTRL_PARSER_COVERAGE 26 +#define SQLITE_TESTCTRL_RESULT_INTREAL 27 +#define SQLITE_TESTCTRL_PRNG_SEED 28 +#define SQLITE_TESTCTRL_EXTRA_SCHEMA_CHECKS 29 +#define SQLITE_TESTCTRL_SEEK_COUNT 30 +#define SQLITE_TESTCTRL_TRACEFLAGS 31 +#define SQLITE_TESTCTRL_TUNE 32 +#define SQLITE_TESTCTRL_LOGEST 33 +#define SQLITE_TESTCTRL_USELONGDOUBLE 34 /* NOT USED */ +#define SQLITE_TESTCTRL_ATOF 34 +#define SQLITE_TESTCTRL_LAST 34 /* Largest TESTCTRL */ + +/* ** CAPI3REF: SQL Keyword Checking ** -** These routines provide access to the set of SQL language keywords -** recognized by SQLite. Applications can uses these routines to determine +** These routines provide access to the set of SQL language keywords +** recognized by SQLite. Applications can use these routines to determine ** whether or not a specific identifier needs to be escaped (for example, ** by enclosing in double-quotes) so as not to confuse the parser. ** ** The sqlite3_keyword_count() interface returns the number of distinct ** keywords understood by SQLite. ** -** The sqlite3_keyword_name(N,Z,L) interface finds the N-th keyword and +** The sqlite3_keyword_name(N,Z,L) interface finds the 0-based N-th keyword and ** makes *Z point to that keyword expressed as UTF8 and writes the number ** of bytes in the keyword into *L. The string that *Z points to is not ** zero-terminated. The sqlite3_keyword_name(N,Z,L) routine returns @@ -7364,11 +8771,11 @@ typedef unsigned long long int sqlite_uint64; ** SQLite is compiled with the [-DSQLITE_OMIT_VACUUM] option. Also, ** new keywords may be added to future releases of SQLite. */ - SQLITE_API int sqlite3_keyword_count(void); - SQLITE_API int sqlite3_keyword_name(int, const char**, int*); - SQLITE_API int sqlite3_keyword_check(const char*, int); +SQLITE_API int sqlite3_keyword_count(void); +SQLITE_API int sqlite3_keyword_name(int,const char**,int*); +SQLITE_API int sqlite3_keyword_check(const char*,int); - /* +/* ** CAPI3REF: Dynamic String Object ** KEYWORDS: {dynamic string} ** @@ -7384,22 +8791,22 @@ typedef unsigned long long int sqlite_uint64; ** is returned using the [sqlite3_str_finish()] interface. ** */ - typedef struct sqlite3_str sqlite3_str; +typedef struct sqlite3_str sqlite3_str; - /* +/* ** CAPI3REF: Create A New Dynamic String Object ** CONSTRUCTOR: sqlite3_str ** ** ^The [sqlite3_str_new(D)] interface allocates and initializes ** a new [sqlite3_str] object. To avoid memory leaks, the object returned by -** [sqlite3_str_new()] must be freed by a subsequent call to +** [sqlite3_str_new()] must be freed by a subsequent call to ** [sqlite3_str_finish(X)]. ** ** ^The [sqlite3_str_new(D)] interface always returns a pointer to a ** valid [sqlite3_str] object, though in the event of an out-of-memory ** error the returned object might be a special singleton that will -** silently reject new text, always return SQLITE_NOMEM from -** [sqlite3_str_errcode()], always return 0 for +** silently reject new text, always return SQLITE_NOMEM from +** [sqlite3_str_errcode()], always return 0 for ** [sqlite3_str_length()], and always return NULL from ** [sqlite3_str_finish(X)]. It is always safe to use the value ** returned by [sqlite3_str_new(D)] as the sqlite3_str parameter @@ -7411,9 +8818,9 @@ typedef unsigned long long int sqlite_uint64; ** the value set for [sqlite3_limit](D,[SQLITE_LIMIT_LENGTH]) instead ** of [SQLITE_MAX_LENGTH]. */ - SQLITE_API sqlite3_str* sqlite3_str_new(sqlite3*); +SQLITE_API sqlite3_str *sqlite3_str_new(sqlite3*); - /* +/* ** CAPI3REF: Finalize A Dynamic String ** DESTRUCTOR: sqlite3_str ** @@ -7423,21 +8830,26 @@ typedef unsigned long long int sqlite_uint64; ** pass the returned value to [sqlite3_free()] to avoid a memory leak. ** ^The [sqlite3_str_finish(X)] interface may return a NULL pointer if any ** errors were encountered during construction of the string. ^The -** [sqlite3_str_finish(X)] interface will also return a NULL pointer if the +** [sqlite3_str_finish(X)] interface might also return a NULL pointer if the ** string in [sqlite3_str] object X is zero bytes long. +** +** ^The [sqlite3_str_free(X)] interface destroys both the sqlite3_str object +** X and the string content it contains. Calling sqlite3_str_free(X) is +** the equivalent of calling [sqlite3_free](sqlite3_str_finish(X)). */ - SQLITE_API char* sqlite3_str_finish(sqlite3_str*); +SQLITE_API char *sqlite3_str_finish(sqlite3_str*); +SQLITE_API void sqlite3_str_free(sqlite3_str*); - /* +/* ** CAPI3REF: Add Content To A Dynamic String ** METHOD: sqlite3_str ** -** These interfaces add content to an sqlite3_str object previously obtained -** from [sqlite3_str_new()]. +** These interfaces add or remove content to an sqlite3_str object +** previously obtained from [sqlite3_str_new()]. ** -** ^The [sqlite3_str_appendf(X,F,...)] and +** ^The [sqlite3_str_appendf(X,F,...)] and ** [sqlite3_str_vappendf(X,F,V)] interfaces uses the [built-in printf] -** functionality of SQLite to append formatted text onto the end of +** functionality of SQLite to append formatted text onto the end of ** [sqlite3_str] object X. ** ** ^The [sqlite3_str_append(X,S,N)] method appends exactly N bytes from string S @@ -7454,20 +8866,25 @@ typedef unsigned long long int sqlite_uint64; ** ^This method can be used, for example, to add whitespace indentation. ** ** ^The [sqlite3_str_reset(X)] method resets the string under construction -** inside [sqlite3_str] object X back to zero bytes in length. +** inside [sqlite3_str] object X back to zero bytes in length. +** +** ^The [sqlite3_str_truncate(X,N)] method changes the length of the string +** under construction to be N bytes or less. This routine is a no-op if +** N is negative or if the string is already N bytes or smaller in size. ** ** These methods do not return a result code. ^If an error occurs, that fact ** is recorded in the [sqlite3_str] object and can be recovered by a ** subsequent call to [sqlite3_str_errcode(X)]. */ - SQLITE_API void sqlite3_str_appendf(sqlite3_str*, const char* zFormat, ...); - SQLITE_API void sqlite3_str_vappendf(sqlite3_str*, const char* zFormat, va_list); - SQLITE_API void sqlite3_str_append(sqlite3_str*, const char* zIn, int N); - SQLITE_API void sqlite3_str_appendall(sqlite3_str*, const char* zIn); - SQLITE_API void sqlite3_str_appendchar(sqlite3_str*, int N, char C); - SQLITE_API void sqlite3_str_reset(sqlite3_str*); +SQLITE_API void sqlite3_str_appendf(sqlite3_str*, const char *zFormat, ...); +SQLITE_API void sqlite3_str_vappendf(sqlite3_str*, const char *zFormat, va_list); +SQLITE_API void sqlite3_str_append(sqlite3_str*, const char *zIn, int N); +SQLITE_API void sqlite3_str_appendall(sqlite3_str*, const char *zIn); +SQLITE_API void sqlite3_str_appendchar(sqlite3_str*, int N, char C); +SQLITE_API void sqlite3_str_reset(sqlite3_str*); +SQLITE_API void sqlite3_str_truncate(sqlite3_str*,int N); - /* +/* ** CAPI3REF: Status Of A Dynamic String ** METHOD: sqlite3_str ** @@ -7489,18 +8906,18 @@ typedef unsigned long long int sqlite_uint64; ** content of the dynamic string under construction in X. The value ** returned by [sqlite3_str_value(X)] is managed by the sqlite3_str object X ** and might be freed or altered by any subsequent method on the same -** [sqlite3_str] object. Applications must not used the pointer returned +** [sqlite3_str] object. Applications must not use the pointer returned by ** [sqlite3_str_value(X)] after any subsequent method call on the same ** object. ^Applications may change the content of the string returned ** by [sqlite3_str_value(X)] as long as they do not write into any bytes ** outside the range of 0 to [sqlite3_str_length(X)] and do not read or ** write any byte after any subsequent sqlite3_str method call. */ - SQLITE_API int sqlite3_str_errcode(sqlite3_str*); - SQLITE_API int sqlite3_str_length(sqlite3_str*); - SQLITE_API char* sqlite3_str_value(sqlite3_str*); +SQLITE_API int sqlite3_str_errcode(sqlite3_str*); +SQLITE_API int sqlite3_str_length(sqlite3_str*); +SQLITE_API char *sqlite3_str_value(sqlite3_str*); - /* +/* ** CAPI3REF: SQLite Runtime Status ** ** ^These interfaces are used to retrieve runtime status information @@ -7526,12 +8943,14 @@ typedef unsigned long long int sqlite_uint64; ** ** See also: [sqlite3_db_status()] */ - SQLITE_API int sqlite3_status(int op, int* pCurrent, int* pHighwater, int resetFlag); - SQLITE_API int sqlite3_status64( - int op, - sqlite3_int64* pCurrent, - sqlite3_int64* pHighwater, - int resetFlag); +SQLITE_API int sqlite3_status(int op, int *pCurrent, int *pHighwater, int resetFlag); +SQLITE_API int sqlite3_status64( + int op, + sqlite3_int64 *pCurrent, + sqlite3_int64 *pHighwater, + int resetFlag +); + /* ** CAPI3REF: Status Parameters @@ -7554,7 +8973,7 @@ typedef unsigned long long int sqlite_uint64; **
      This parameter records the largest memory allocation request ** handed to [sqlite3_malloc()] or [sqlite3_realloc()] (or their ** internal equivalents). Only the value returned in the -** *pHighwater parameter to [sqlite3_status()] is of interest. +** *pHighwater parameter to [sqlite3_status()] is of interest. ** The value written into the *pCurrent parameter is undefined.
      )^ ** ** [[SQLITE_STATUS_MALLOC_COUNT]] ^(
      SQLITE_STATUS_MALLOC_COUNT
      @@ -7563,24 +8982,24 @@ typedef unsigned long long int sqlite_uint64; ** ** [[SQLITE_STATUS_PAGECACHE_USED]] ^(
      SQLITE_STATUS_PAGECACHE_USED
      **
      This parameter returns the number of pages used out of the -** [pagecache memory allocator] that was configured using +** [pagecache memory allocator] that was configured using ** [SQLITE_CONFIG_PAGECACHE]. The ** value returned is in pages, not in bytes.
      )^ ** -** [[SQLITE_STATUS_PAGECACHE_OVERFLOW]] +** [[SQLITE_STATUS_PAGECACHE_OVERFLOW]] ** ^(
      SQLITE_STATUS_PAGECACHE_OVERFLOW
      **
      This parameter returns the number of bytes of page cache ** allocation which could not be satisfied by the [SQLITE_CONFIG_PAGECACHE] ** buffer and where forced to overflow to [sqlite3_malloc()]. The ** returned value includes allocations that overflowed because they -** where too large (they were larger than the "sz" parameter to +** were too large (they were larger than the "sz" parameter to ** [SQLITE_CONFIG_PAGECACHE]) and allocations that overflowed because ** no space was left in the page cache.
      )^ ** ** [[SQLITE_STATUS_PAGECACHE_SIZE]] ^(
      SQLITE_STATUS_PAGECACHE_SIZE
      **
      This parameter records the largest memory allocation request -** handed to [pagecache memory allocator]. Only the value returned in the -** *pHighwater parameter to [sqlite3_status()] is of interest. +** handed to the [pagecache memory allocator]. Only the value returned in the +** *pHighwater parameter to [sqlite3_status()] is of interest. ** The value written into the *pCurrent parameter is undefined.
      )^ ** ** [[SQLITE_STATUS_SCRATCH_USED]]
      SQLITE_STATUS_SCRATCH_USED
      @@ -7593,34 +9012,34 @@ typedef unsigned long long int sqlite_uint64; **
      No longer used.
      ** ** [[SQLITE_STATUS_PARSER_STACK]] ^(
      SQLITE_STATUS_PARSER_STACK
      -**
      The *pHighwater parameter records the deepest parser stack. +**
      The *pHighwater parameter records the deepest parser stack. ** The *pCurrent value is undefined. The *pHighwater value is only ** meaningful if SQLite is compiled with [YYTRACKMAXSTACKDEPTH].
      )^ **
    ** ** New status parameters may be added from time to time. */ -#define SQLITE_STATUS_MEMORY_USED 0 -#define SQLITE_STATUS_PAGECACHE_USED 1 -#define SQLITE_STATUS_PAGECACHE_OVERFLOW 2 -#define SQLITE_STATUS_SCRATCH_USED 3 /* NOT USED */ -#define SQLITE_STATUS_SCRATCH_OVERFLOW 4 /* NOT USED */ -#define SQLITE_STATUS_MALLOC_SIZE 5 -#define SQLITE_STATUS_PARSER_STACK 6 -#define SQLITE_STATUS_PAGECACHE_SIZE 7 -#define SQLITE_STATUS_SCRATCH_SIZE 8 /* NOT USED */ -#define SQLITE_STATUS_MALLOC_COUNT 9 - - /* +#define SQLITE_STATUS_MEMORY_USED 0 +#define SQLITE_STATUS_PAGECACHE_USED 1 +#define SQLITE_STATUS_PAGECACHE_OVERFLOW 2 +#define SQLITE_STATUS_SCRATCH_USED 3 /* NOT USED */ +#define SQLITE_STATUS_SCRATCH_OVERFLOW 4 /* NOT USED */ +#define SQLITE_STATUS_MALLOC_SIZE 5 +#define SQLITE_STATUS_PARSER_STACK 6 +#define SQLITE_STATUS_PAGECACHE_SIZE 7 +#define SQLITE_STATUS_SCRATCH_SIZE 8 /* NOT USED */ +#define SQLITE_STATUS_MALLOC_COUNT 9 + +/* ** CAPI3REF: Database Connection Status ** METHOD: sqlite3 ** -** ^This interface is used to retrieve runtime status information +** ^This interface is used to retrieve runtime status information ** about a single [database connection]. ^The first argument is the ** database connection object to be interrogated. ^The second argument ** is an integer constant, taken from the set of ** [SQLITE_DBSTATUS options], that -** determines the parameter to interrogate. The set of +** determines the parameter to interrogate. The set of ** [SQLITE_DBSTATUS options] is likely ** to grow in future releases of SQLite. ** @@ -7632,9 +9051,18 @@ typedef unsigned long long int sqlite_uint64; ** ^The sqlite3_db_status() routine returns SQLITE_OK on success and a ** non-zero [error code] on failure. ** +** ^The sqlite3_db_status64(D,O,C,H,R) routine works exactly the same +** way as sqlite3_db_status(D,O,C,H,R) routine except that the C and H +** parameters are pointer to 64-bit integers (type: sqlite3_int64) instead +** of pointers to 32-bit integers, which allows larger status values +** to be returned. If a status value exceeds 2,147,483,647 then +** sqlite3_db_status() will truncate the value whereas sqlite3_db_status64() +** will return the full value. +** ** See also: [sqlite3_status()] and [sqlite3_stmt_status()]. */ - SQLITE_API int sqlite3_db_status(sqlite3*, int op, int* pCur, int* pHiwtr, int resetFlg); +SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int resetFlg); +SQLITE_API int sqlite3_db_status64(sqlite3*,int,sqlite3_int64*,sqlite3_int64*,int); /* ** CAPI3REF: Status Parameters for database connections @@ -7655,51 +9083,53 @@ typedef unsigned long long int sqlite_uint64; ** checked out.
  • )^ ** ** [[SQLITE_DBSTATUS_LOOKASIDE_HIT]] ^(
    SQLITE_DBSTATUS_LOOKASIDE_HIT
    -**
    This parameter returns the number malloc attempts that were +**
    This parameter returns the number of malloc attempts that were ** satisfied using lookaside memory. Only the high-water value is meaningful; -** the current value is always zero.)^ +** the current value is always zero.
    )^ ** ** [[SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE]] ** ^(
    SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE
    -**
    This parameter returns the number malloc attempts that might have +**
    This parameter returns the number of malloc attempts that might have ** been satisfied using lookaside memory but failed due to the amount of ** memory requested being larger than the lookaside slot size. ** Only the high-water value is meaningful; -** the current value is always zero.)^ +** the current value is always zero.
    )^ ** ** [[SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL]] ** ^(
    SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL
    -**
    This parameter returns the number malloc attempts that might have +**
    This parameter returns the number of malloc attempts that might have ** been satisfied using lookaside memory but failed due to all lookaside ** memory already being in use. ** Only the high-water value is meaningful; -** the current value is always zero.)^ +** the current value is always zero.
    )^ ** ** [[SQLITE_DBSTATUS_CACHE_USED]] ^(
    SQLITE_DBSTATUS_CACHE_USED
    **
    This parameter returns the approximate number of bytes of heap ** memory used by all pager caches associated with the database connection.)^ ** ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_USED is always 0. +**
    ** -** [[SQLITE_DBSTATUS_CACHE_USED_SHARED]] +** [[SQLITE_DBSTATUS_CACHE_USED_SHARED]] ** ^(
    SQLITE_DBSTATUS_CACHE_USED_SHARED
    **
    This parameter is similar to DBSTATUS_CACHE_USED, except that if a ** pager cache is shared between two or more connections the bytes of heap ** memory used by that pager cache is divided evenly between the attached ** connections.)^ In other words, if none of the pager caches associated ** with the database connection are shared, this request returns the same -** value as DBSTATUS_CACHE_USED. Or, if one or more or the pager caches are +** value as DBSTATUS_CACHE_USED. Or, if one or more of the pager caches are ** shared, the value returned by this call will be smaller than that returned ** by DBSTATUS_CACHE_USED. ^The highwater mark associated with -** SQLITE_DBSTATUS_CACHE_USED_SHARED is always 0. +** SQLITE_DBSTATUS_CACHE_USED_SHARED is always 0.
    ** ** [[SQLITE_DBSTATUS_SCHEMA_USED]] ^(
    SQLITE_DBSTATUS_SCHEMA_USED
    **
    This parameter returns the approximate number of bytes of heap ** memory used to store the schema for all databases associated -** with the connection - main, temp, and any [ATTACH]-ed databases.)^ +** with the connection - main, temp, and any [ATTACH]-ed databases.)^ ** ^The full amount of memory used by the schemas is reported, even if the ** schema memory is shared with other database connections due to ** [shared cache mode] being enabled. ** ^The highwater mark associated with SQLITE_DBSTATUS_SCHEMA_USED is always 0. +**
    ** ** [[SQLITE_DBSTATUS_STMT_USED]] ^(
    SQLITE_DBSTATUS_STMT_USED
    **
    This parameter returns the approximate number of bytes of heap @@ -7710,13 +9140,13 @@ typedef unsigned long long int sqlite_uint64; ** ** [[SQLITE_DBSTATUS_CACHE_HIT]] ^(
    SQLITE_DBSTATUS_CACHE_HIT
    **
    This parameter returns the number of pager cache hits that have -** occurred.)^ ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_HIT +** occurred.)^ ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_HIT ** is always 0. **
    ** ** [[SQLITE_DBSTATUS_CACHE_MISS]] ^(
    SQLITE_DBSTATUS_CACHE_MISS
    **
    This parameter returns the number of pager cache misses that have -** occurred.)^ ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_MISS +** occurred.)^ ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_MISS ** is always 0. **
    ** @@ -7729,6 +9159,10 @@ typedef unsigned long long int sqlite_uint64; ** If an IO or other error occurs while writing a page to disk, the effect ** on subsequent SQLITE_DBSTATUS_CACHE_WRITE requests is undefined.)^ ^The ** highwater mark associated with SQLITE_DBSTATUS_CACHE_WRITE is always 0. +**

    +** ^(There is overlap between the quantities measured by this parameter +** (SQLITE_DBSTATUS_CACHE_WRITE) and SQLITE_DBSTATUS_TEMPBUF_SPILL. +** Resetting one will reduce the other.)^ ** ** ** [[SQLITE_DBSTATUS_CACHE_SPILL]] ^(

    SQLITE_DBSTATUS_CACHE_SPILL
    @@ -7736,33 +9170,47 @@ typedef unsigned long long int sqlite_uint64; ** been written to disk in the middle of a transaction due to the page ** cache overflowing. Transactions are more efficient if they are written ** to disk all at once. When pages spill mid-transaction, that introduces -** additional overhead. This parameter can be used help identify -** inefficiencies that can be resolve by increasing the cache size. +** additional overhead. This parameter can be used to help identify +** inefficiencies that can be resolved by increasing the cache size. ** ** ** [[SQLITE_DBSTATUS_DEFERRED_FKS]] ^(
    SQLITE_DBSTATUS_DEFERRED_FKS
    **
    This parameter returns zero for the current value if and only if ** all foreign key constraints (deferred or immediate) have been ** resolved.)^ ^The highwater mark is always 0. +** +** [[SQLITE_DBSTATUS_TEMPBUF_SPILL] ^(
    SQLITE_DBSTATUS_TEMPBUF_SPILL
    +**
    ^(This parameter returns the number of bytes written to temporary +** files on disk that could have been kept in memory had sufficient memory +** been available. This value includes writes to intermediate tables that +** are part of complex queries, external sorts that spill to disk, and +** writes to TEMP tables.)^ +** ^The highwater mark is always 0. +**

    +** ^(There is overlap between the quantities measured by this parameter +** (SQLITE_DBSTATUS_TEMPBUF_SPILL) and SQLITE_DBSTATUS_CACHE_WRITE. +** Resetting one will reduce the other.)^ **

    ** */ -#define SQLITE_DBSTATUS_LOOKASIDE_USED 0 -#define SQLITE_DBSTATUS_CACHE_USED 1 -#define SQLITE_DBSTATUS_SCHEMA_USED 2 -#define SQLITE_DBSTATUS_STMT_USED 3 -#define SQLITE_DBSTATUS_LOOKASIDE_HIT 4 -#define SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE 5 -#define SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL 6 -#define SQLITE_DBSTATUS_CACHE_HIT 7 -#define SQLITE_DBSTATUS_CACHE_MISS 8 -#define SQLITE_DBSTATUS_CACHE_WRITE 9 -#define SQLITE_DBSTATUS_DEFERRED_FKS 10 -#define SQLITE_DBSTATUS_CACHE_USED_SHARED 11 -#define SQLITE_DBSTATUS_CACHE_SPILL 12 -#define SQLITE_DBSTATUS_MAX 12 /* Largest defined DBSTATUS */ - - /* +#define SQLITE_DBSTATUS_LOOKASIDE_USED 0 +#define SQLITE_DBSTATUS_CACHE_USED 1 +#define SQLITE_DBSTATUS_SCHEMA_USED 2 +#define SQLITE_DBSTATUS_STMT_USED 3 +#define SQLITE_DBSTATUS_LOOKASIDE_HIT 4 +#define SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE 5 +#define SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL 6 +#define SQLITE_DBSTATUS_CACHE_HIT 7 +#define SQLITE_DBSTATUS_CACHE_MISS 8 +#define SQLITE_DBSTATUS_CACHE_WRITE 9 +#define SQLITE_DBSTATUS_DEFERRED_FKS 10 +#define SQLITE_DBSTATUS_CACHE_USED_SHARED 11 +#define SQLITE_DBSTATUS_CACHE_SPILL 12 +#define SQLITE_DBSTATUS_TEMPBUF_SPILL 13 +#define SQLITE_DBSTATUS_MAX 13 /* Largest defined DBSTATUS */ + + +/* ** CAPI3REF: Prepared Statement Status ** METHOD: sqlite3_stmt ** @@ -7773,7 +9221,7 @@ typedef unsigned long long int sqlite_uint64; ** statements. For example, if the number of table steps greatly exceeds ** the number of table searches or result rows, that would tend to indicate ** that the prepared statement is using a full table scan rather than -** an index. +** an index. ** ** ^(This interface is used to retrieve and reset counter values from ** a [prepared statement]. The first argument is the prepared statement @@ -7786,7 +9234,7 @@ typedef unsigned long long int sqlite_uint64; ** ** See also: [sqlite3_status()] and [sqlite3_db_status()]. */ - SQLITE_API int sqlite3_stmt_status(sqlite3_stmt*, int op, int resetFlg); +SQLITE_API int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg); /* ** CAPI3REF: Status Parameters for prepared statements @@ -7800,40 +9248,50 @@ typedef unsigned long long int sqlite_uint64; ** [[SQLITE_STMTSTATUS_FULLSCAN_STEP]]
    SQLITE_STMTSTATUS_FULLSCAN_STEP
    **
    ^This is the number of times that SQLite has stepped forward in ** a table as part of a full table scan. Large numbers for this counter -** may indicate opportunities for performance improvement through +** may indicate opportunities for performance improvement through ** careful use of indices.
    ** ** [[SQLITE_STMTSTATUS_SORT]]
    SQLITE_STMTSTATUS_SORT
    **
    ^This is the number of sort operations that have occurred. ** A non-zero value in this counter may indicate an opportunity to -** improvement performance through careful use of indices.
    +** improve performance through careful use of indices. ** ** [[SQLITE_STMTSTATUS_AUTOINDEX]]
    SQLITE_STMTSTATUS_AUTOINDEX
    **
    ^This is the number of rows inserted into transient indices that ** were created automatically in order to help joins run faster. ** A non-zero value in this counter may indicate an opportunity to -** improvement performance by adding permanent indices that do not +** improve performance by adding permanent indices that do not ** need to be reinitialized each time the statement is run.
    ** ** [[SQLITE_STMTSTATUS_VM_STEP]]
    SQLITE_STMTSTATUS_VM_STEP
    **
    ^This is the number of virtual machine operations executed ** by the prepared statement if that number is less than or equal -** to 2147483647. The number of virtual machine operations can be +** to 2147483647. The number of virtual machine operations can be ** used as a proxy for the total work done by the prepared statement. ** If the number of virtual machine operations exceeds 2147483647 -** then the value returned by this statement status code is undefined. +** then the value returned by this statement status code is undefined.
    ** ** [[SQLITE_STMTSTATUS_REPREPARE]]
    SQLITE_STMTSTATUS_REPREPARE
    **
    ^This is the number of times that the prepare statement has been -** automatically regenerated due to schema changes or change to -** [bound parameters] that might affect the query plan. +** automatically regenerated due to schema changes or changes to +** [bound parameters] that might affect the query plan.
    ** ** [[SQLITE_STMTSTATUS_RUN]]
    SQLITE_STMTSTATUS_RUN
    **
    ^This is the number of times that the prepared statement has ** been run. A single "run" for the purposes of this counter is one ** or more calls to [sqlite3_step()] followed by a call to [sqlite3_reset()]. ** The counter is incremented on the first [sqlite3_step()] call of each -** cycle. +** cycle.
    +** +** [[SQLITE_STMTSTATUS_FILTER_MISS]] +** [[SQLITE_STMTSTATUS_FILTER HIT]] +**
    SQLITE_STMTSTATUS_FILTER_HIT
    +** SQLITE_STMTSTATUS_FILTER_MISS
    +**
    ^SQLITE_STMTSTATUS_FILTER_HIT is the number of times that a join +** step was bypassed because a Bloom filter returned not-found. The +** corresponding SQLITE_STMTSTATUS_FILTER_MISS value is the number of +** times that the Bloom filter returned a find, and thus the join step +** had to be processed as normal.
    ** ** [[SQLITE_STMTSTATUS_MEMUSED]]
    SQLITE_STMTSTATUS_MEMUSED
    **
    ^This is the approximate number of bytes of heap memory @@ -7843,15 +9301,17 @@ typedef unsigned long long int sqlite_uint64; **
    ** */ -#define SQLITE_STMTSTATUS_FULLSCAN_STEP 1 -#define SQLITE_STMTSTATUS_SORT 2 -#define SQLITE_STMTSTATUS_AUTOINDEX 3 -#define SQLITE_STMTSTATUS_VM_STEP 4 -#define SQLITE_STMTSTATUS_REPREPARE 5 -#define SQLITE_STMTSTATUS_RUN 6 -#define SQLITE_STMTSTATUS_MEMUSED 99 +#define SQLITE_STMTSTATUS_FULLSCAN_STEP 1 +#define SQLITE_STMTSTATUS_SORT 2 +#define SQLITE_STMTSTATUS_AUTOINDEX 3 +#define SQLITE_STMTSTATUS_VM_STEP 4 +#define SQLITE_STMTSTATUS_REPREPARE 5 +#define SQLITE_STMTSTATUS_RUN 6 +#define SQLITE_STMTSTATUS_FILTER_MISS 7 +#define SQLITE_STMTSTATUS_FILTER_HIT 8 +#define SQLITE_STMTSTATUS_MEMUSED 99 - /* +/* ** CAPI3REF: Custom Page Cache Object ** ** The sqlite3_pcache type is opaque. It is implemented by @@ -7862,9 +9322,9 @@ typedef unsigned long long int sqlite_uint64; ** ** See [sqlite3_pcache_methods2] for additional information. */ - typedef struct sqlite3_pcache sqlite3_pcache; +typedef struct sqlite3_pcache sqlite3_pcache; - /* +/* ** CAPI3REF: Custom Page Cache Object ** ** The sqlite3_pcache_page object represents a single page in the @@ -7874,27 +9334,26 @@ typedef unsigned long long int sqlite_uint64; ** ** See [sqlite3_pcache_methods2] for additional information. */ - typedef struct sqlite3_pcache_page sqlite3_pcache_page; - struct sqlite3_pcache_page - { - void* pBuf; /* The content of the page */ - void* pExtra; /* Extra information associated with the page */ - }; +typedef struct sqlite3_pcache_page sqlite3_pcache_page; +struct sqlite3_pcache_page { + void *pBuf; /* The content of the page */ + void *pExtra; /* Extra information associated with the page */ +}; - /* +/* ** CAPI3REF: Application Defined Page Cache. ** KEYWORDS: {page cache} ** ** ^(The [sqlite3_config]([SQLITE_CONFIG_PCACHE2], ...) interface can -** register an alternative page cache implementation by passing in an +** register an alternative page cache implementation by passing in an ** instance of the sqlite3_pcache_methods2 structure.)^ -** In many applications, most of the heap memory allocated by +** In many applications, most of the heap memory allocated by ** SQLite is used for the page cache. -** By implementing a +** By implementing a ** custom page cache using this API, an application can better control -** the amount of memory consumed by SQLite, the way in which -** that memory is allocated and released, and the policies used to -** determine exactly which parts of a database file are cached and for +** the amount of memory consumed by SQLite, the way in which +** that memory is allocated and released, and the policies used to +** determine exactly which parts of a database file are cached and for ** how long. ** ** The alternative page cache mechanism is an @@ -7907,19 +9366,19 @@ typedef unsigned long long int sqlite_uint64; ** [sqlite3_config()] returns.)^ ** ** [[the xInit() page cache method]] -** ^(The xInit() method is called once for each effective +** ^(The xInit() method is called once for each effective ** call to [sqlite3_initialize()])^ ** (usually only once during the lifetime of the process). ^(The xInit() ** method is passed a copy of the sqlite3_pcache_methods2.pArg value.)^ -** The intent of the xInit() method is to set up global data structures -** required by the custom page cache implementation. -** ^(If the xInit() method is NULL, then the +** The intent of the xInit() method is to set up global data structures +** required by the custom page cache implementation. +** ^(If the xInit() method is NULL, then the ** built-in default page cache is used instead of the application defined ** page cache.)^ ** ** [[the xShutdown() page cache method]] ** ^The xShutdown() method is called by [sqlite3_shutdown()]. -** It can be used to clean up +** It can be used to clean up ** any outstanding resources before process shutdown, if required. ** ^The xShutdown() method may be NULL. ** @@ -7937,9 +9396,9 @@ typedef unsigned long long int sqlite_uint64; ** SQLite will typically create one cache instance for each open database file, ** though this is not guaranteed. ^The ** first parameter, szPage, is the size in bytes of the pages that must -** be allocated by the cache. ^szPage will always a power of two. ^The -** second parameter szExtra is a number of bytes of extra storage -** associated with each page cache entry. ^The szExtra parameter will +** be allocated by the cache. ^szPage will always be a power of two. ^The +** second parameter szExtra is a number of bytes of extra storage +** associated with each page cache entry. ^The szExtra parameter will be ** a number less than 250. SQLite will use the ** extra szExtra bytes on each page to store metadata about the underlying ** database page on disk. The value passed into szExtra depends @@ -7947,17 +9406,17 @@ typedef unsigned long long int sqlite_uint64; ** ^The third argument to xCreate(), bPurgeable, is true if the cache being ** created will be used to cache database pages of a file stored on disk, or ** false if it is used for an in-memory database. The cache implementation -** does not have to do anything special based with the value of bPurgeable; +** does not have to do anything special based upon the value of bPurgeable; ** it is purely advisory. ^On a cache where bPurgeable is false, SQLite will ** never invoke xUnpin() except to deliberately delete a page. ** ^In other words, calls to xUnpin() on a cache with bPurgeable set to -** false will always have the "discard" flag set to true. -** ^Hence, a cache created with bPurgeable false will +** false will always have the "discard" flag set to true. +** ^Hence, a cache created with bPurgeable set to false will ** never contain any unpinned pages. ** ** [[the xCachesize() page cache method]] ** ^(The xCachesize() method may be called at any time by SQLite to set the -** suggested maximum cache-size (number of pages stored by) the cache +** suggested maximum cache-size (number of pages stored) for the cache ** instance passed as the first argument. This is the value configured using ** the SQLite "[PRAGMA cache_size]" command.)^ As with the bPurgeable ** parameter, the implementation is not required to do anything with this @@ -7966,12 +9425,12 @@ typedef unsigned long long int sqlite_uint64; ** [[the xPagecount() page cache methods]] ** The xPagecount() method must return the number of pages currently ** stored in the cache, both pinned and unpinned. -** +** ** [[the xFetch() page cache methods]] -** The xFetch() method locates a page in the cache and returns a pointer to +** The xFetch() method locates a page in the cache and returns a pointer to ** an sqlite3_pcache_page object associated with that page, or a NULL pointer. ** The pBuf element of the returned sqlite3_pcache_page object will be a -** pointer to a buffer of szPage bytes used to store the content of a +** pointer to a buffer of szPage bytes used to store the content of a ** single database page. The pExtra element of sqlite3_pcache_page will be ** a pointer to the szExtra bytes of extra storage that SQLite has requested ** for each entry in the page cache. @@ -7984,12 +9443,12 @@ typedef unsigned long long int sqlite_uint64; ** implementation must return a pointer to the page buffer with its content ** intact. If the requested page is not already in the cache, then the ** cache implementation should use the value of the createFlag -** parameter to help it determined what action to take: +** parameter to help it determine what action to take: ** ** **
    createFlag Behavior when page is not already in cache **
    0 Do not allocate a new page. Return NULL. -**
    1 Allocate a new page if it easy and convenient to do so. +**
    1 Allocate a new page if it is easy and convenient to do so. ** Otherwise return NULL. **
    2 Make every effort to allocate a new page. Only return ** NULL if allocating a new page is effectively impossible. @@ -7997,7 +9456,7 @@ typedef unsigned long long int sqlite_uint64; ** ** ^(SQLite will normally invoke xFetch() with a createFlag of 0 or 1. SQLite ** will only use a createFlag of 2 after a prior call with a createFlag of 1 -** failed.)^ In between the to xFetch() calls, SQLite may +** failed.)^ In between the xFetch() calls, SQLite may ** attempt to unpin one or more cache pages by spilling the content of ** pinned pages to disk and synching the operating system disk cache. ** @@ -8006,12 +9465,12 @@ typedef unsigned long long int sqlite_uint64; ** as its second argument. If the third parameter, discard, is non-zero, ** then the page must be evicted from the cache. ** ^If the discard parameter is -** zero, then the page may be discarded or retained at the discretion of +** zero, then the page may be discarded or retained at the discretion of the ** page cache implementation. ^The page cache implementation ** may choose to evict unpinned pages at any time. ** -** The cache must not perform any reference counting. A single -** call to xUnpin() unpins the page regardless of the number of prior calls +** The cache must not perform any reference counting. A single +** call to xUnpin() unpins the page regardless of the number of prior calls ** to xFetch(). ** ** [[the xRekey() page cache methods]] @@ -8024,7 +9483,7 @@ typedef unsigned long long int sqlite_uint64; ** When SQLite calls the xTruncate() method, the cache must discard all ** existing cache entries with page numbers (keys) greater than or equal ** to the value of the iLimit parameter passed to xTruncate(). If any -** of these pages are pinned, they are implicitly unpinned, meaning that +** of these pages are pinned, they become implicitly unpinned, meaning that ** they can be safely discarded. ** ** [[the xDestroy() page cache method]] @@ -8040,47 +9499,46 @@ typedef unsigned long long int sqlite_uint64; ** is not obligated to free any memory, but well-behaved implementations should ** do their best. */ - typedef struct sqlite3_pcache_methods2 sqlite3_pcache_methods2; - struct sqlite3_pcache_methods2 - { - int iVersion; - void* pArg; - int (*xInit)(void*); - void (*xShutdown)(void*); - sqlite3_pcache* (*xCreate)(int szPage, int szExtra, int bPurgeable); - void (*xCachesize)(sqlite3_pcache*, int nCachesize); - int (*xPagecount)(sqlite3_pcache*); - sqlite3_pcache_page* (*xFetch)(sqlite3_pcache*, unsigned key, int createFlag); - void (*xUnpin)(sqlite3_pcache*, sqlite3_pcache_page*, int discard); - void (*xRekey)(sqlite3_pcache*, sqlite3_pcache_page*, - unsigned oldKey, unsigned newKey); - void (*xTruncate)(sqlite3_pcache*, unsigned iLimit); - void (*xDestroy)(sqlite3_pcache*); - void (*xShrink)(sqlite3_pcache*); - }; - - /* +typedef struct sqlite3_pcache_methods2 sqlite3_pcache_methods2; +struct sqlite3_pcache_methods2 { + int iVersion; + void *pArg; + int (*xInit)(void*); + void (*xShutdown)(void*); + sqlite3_pcache *(*xCreate)(int szPage, int szExtra, int bPurgeable); + void (*xCachesize)(sqlite3_pcache*, int nCachesize); + int (*xPagecount)(sqlite3_pcache*); + sqlite3_pcache_page *(*xFetch)(sqlite3_pcache*, unsigned key, int createFlag); + void (*xUnpin)(sqlite3_pcache*, sqlite3_pcache_page*, int discard); + void (*xRekey)(sqlite3_pcache*, sqlite3_pcache_page*, + unsigned oldKey, unsigned newKey); + void (*xTruncate)(sqlite3_pcache*, unsigned iLimit); + void (*xDestroy)(sqlite3_pcache*); + void (*xShrink)(sqlite3_pcache*); +}; + +/* ** This is the obsolete pcache_methods object that has now been replaced ** by sqlite3_pcache_methods2. This object is not used by SQLite. It is ** retained in the header file for backwards compatibility only. */ - typedef struct sqlite3_pcache_methods sqlite3_pcache_methods; - struct sqlite3_pcache_methods - { - void* pArg; - int (*xInit)(void*); - void (*xShutdown)(void*); - sqlite3_pcache* (*xCreate)(int szPage, int bPurgeable); - void (*xCachesize)(sqlite3_pcache*, int nCachesize); - int (*xPagecount)(sqlite3_pcache*); - void* (*xFetch)(sqlite3_pcache*, unsigned key, int createFlag); - void (*xUnpin)(sqlite3_pcache*, void*, int discard); - void (*xRekey)(sqlite3_pcache*, void*, unsigned oldKey, unsigned newKey); - void (*xTruncate)(sqlite3_pcache*, unsigned iLimit); - void (*xDestroy)(sqlite3_pcache*); - }; - - /* +typedef struct sqlite3_pcache_methods sqlite3_pcache_methods; +struct sqlite3_pcache_methods { + void *pArg; + int (*xInit)(void*); + void (*xShutdown)(void*); + sqlite3_pcache *(*xCreate)(int szPage, int bPurgeable); + void (*xCachesize)(sqlite3_pcache*, int nCachesize); + int (*xPagecount)(sqlite3_pcache*); + void *(*xFetch)(sqlite3_pcache*, unsigned key, int createFlag); + void (*xUnpin)(sqlite3_pcache*, void*, int discard); + void (*xRekey)(sqlite3_pcache*, void*, unsigned oldKey, unsigned newKey); + void (*xTruncate)(sqlite3_pcache*, unsigned iLimit); + void (*xDestroy)(sqlite3_pcache*); +}; + + +/* ** CAPI3REF: Online Backup Object ** ** The sqlite3_backup object records state information about an ongoing @@ -8090,14 +9548,14 @@ typedef unsigned long long int sqlite_uint64; ** ** See Also: [Using the SQLite Online Backup API] */ - typedef struct sqlite3_backup sqlite3_backup; +typedef struct sqlite3_backup sqlite3_backup; - /* +/* ** CAPI3REF: Online Backup API. ** ** The backup API copies the content of one database into another. ** It is useful either for creating backups of databases or -** for copying in-memory databases to or from persistent files. +** for copying in-memory databases to or from persistent files. ** ** See Also: [Using the SQLite Online Backup API] ** @@ -8108,36 +9566,36 @@ typedef unsigned long long int sqlite_uint64; ** ^Thus, the backup may be performed on a live source database without ** preventing other database connections from ** reading or writing to the source database while the backup is underway. -** -** ^(To perform a backup operation: +** +** ^(To perform a backup operation: **
      **
    1. sqlite3_backup_init() is called once to initialize the -** backup, -**
    2. sqlite3_backup_step() is called one or more times to transfer +** backup, +**
    3. sqlite3_backup_step() is called one or more times to transfer ** the data between the two databases, and finally -**
    4. sqlite3_backup_finish() is called to release all resources -** associated with the backup operation. +**
    5. sqlite3_backup_finish() is called to release all resources +** associated with the backup operation. **
    )^ ** There should be exactly one call to sqlite3_backup_finish() for each ** successful call to sqlite3_backup_init(). ** ** [[sqlite3_backup_init()]] sqlite3_backup_init() ** -** ^The D and N arguments to sqlite3_backup_init(D,N,S,M) are the -** [database connection] associated with the destination database +** ^The D and N arguments to sqlite3_backup_init(D,N,S,M) are the +** [database connection] associated with the destination database ** and the database name, respectively. ** ^The database name is "main" for the main database, "temp" for the ** temporary database, or the name specified after the AS keyword in ** an [ATTACH] statement for an attached database. -** ^The S and M arguments passed to +** ^The S and M arguments passed to ** sqlite3_backup_init(D,N,S,M) identify the [database connection] ** and database name of the source database, respectively. ** ^The source and destination [database connections] (parameters S and D) ** must be different or else sqlite3_backup_init(D,N,S,M) will fail with ** an error. ** -** ^A call to sqlite3_backup_init() will fail, returning NULL, if -** there is already a read or read-write transaction open on the +** ^A call to sqlite3_backup_init() will fail, returning NULL, if +** there is already a read or read-write transaction open on the ** destination database. ** ** ^If an error occurs within sqlite3_backup_init(D,N,S,M), then NULL is @@ -8149,14 +9607,14 @@ typedef unsigned long long int sqlite_uint64; ** ^A successful call to sqlite3_backup_init() returns a pointer to an ** [sqlite3_backup] object. ** ^The [sqlite3_backup] object may be used with the sqlite3_backup_step() and -** sqlite3_backup_finish() functions to perform the specified backup +** sqlite3_backup_finish() functions to perform the specified backup ** operation. ** ** [[sqlite3_backup_step()]] sqlite3_backup_step() ** -** ^Function sqlite3_backup_step(B,N) will copy up to N pages between +** ^Function sqlite3_backup_step(B,N) will copy up to N pages between ** the source and destination databases specified by [sqlite3_backup] object B. -** ^If N is negative, all remaining source pages are copied. +** ^If N is negative, all remaining source pages are copied. ** ^If sqlite3_backup_step(B,N) successfully copies N pages and there ** are still more pages to be copied, then the function returns [SQLITE_OK]. ** ^If sqlite3_backup_step(B,N) successfully finishes copying all pages @@ -8178,8 +9636,8 @@ typedef unsigned long long int sqlite_uint64; ** ** ^If sqlite3_backup_step() cannot obtain a required file-system lock, then ** the [sqlite3_busy_handler | busy-handler function] -** is invoked (if one is specified). ^If the -** busy-handler returns non-zero before the lock is available, then +** is invoked (if one is specified). ^If the +** busy-handler returns non-zero before the lock is available, then ** [SQLITE_BUSY] is returned to the caller. ^In this case the call to ** sqlite3_backup_step() can be retried later. ^If the source ** [database connection] @@ -8187,15 +9645,15 @@ typedef unsigned long long int sqlite_uint64; ** is called, then [SQLITE_LOCKED] is returned immediately. ^Again, in this ** case the call to sqlite3_backup_step() can be retried later on. ^(If ** [SQLITE_IOERR_ACCESS | SQLITE_IOERR_XXX], [SQLITE_NOMEM], or -** [SQLITE_READONLY] is returned, then -** there is no point in retrying the call to sqlite3_backup_step(). These -** errors are considered fatal.)^ The application must accept -** that the backup operation has failed and pass the backup operation handle +** [SQLITE_READONLY] is returned, then +** there is no point in retrying the call to sqlite3_backup_step(). These +** errors are considered fatal.)^ The application must accept +** that the backup operation has failed and pass the backup operation handle ** to the sqlite3_backup_finish() to release associated resources. ** ** ^The first call to sqlite3_backup_step() obtains an exclusive lock -** on the destination file. ^The exclusive lock is not released until either -** sqlite3_backup_finish() is called or the backup operation is complete +** on the destination file. ^The exclusive lock is not released until either +** sqlite3_backup_finish() is called or the backup operation is complete ** and sqlite3_backup_step() returns [SQLITE_DONE]. ^Every call to ** sqlite3_backup_step() obtains a [shared lock] on the source database that ** lasts for the duration of the sqlite3_backup_step() call. @@ -8204,25 +9662,25 @@ typedef unsigned long long int sqlite_uint64; ** through the backup process. ^If the source database is modified by an ** external process or via a database connection other than the one being ** used by the backup operation, then the backup will be automatically -** restarted by the next call to sqlite3_backup_step(). ^If the source -** database is modified by the using the same database connection as is used +** restarted by the next call to sqlite3_backup_step(). ^If the source +** database is modified by using the same database connection as is used ** by the backup operation, then the backup database is automatically ** updated at the same time. ** ** [[sqlite3_backup_finish()]] sqlite3_backup_finish() ** -** When sqlite3_backup_step() has returned [SQLITE_DONE], or when the +** When sqlite3_backup_step() has returned [SQLITE_DONE], or when the ** application wishes to abandon the backup operation, the application ** should destroy the [sqlite3_backup] by passing it to sqlite3_backup_finish(). ** ^The sqlite3_backup_finish() interfaces releases all -** resources associated with the [sqlite3_backup] object. +** resources associated with the [sqlite3_backup] object. ** ^If sqlite3_backup_step() has not yet returned [SQLITE_DONE], then any ** active write-transaction on the destination database is rolled back. ** The [sqlite3_backup] object is invalid ** and may not be used following a call to sqlite3_backup_finish(). ** ** ^The value returned by sqlite3_backup_finish is [SQLITE_OK] if no -** sqlite3_backup_step() errors occurred, regardless or whether or not +** sqlite3_backup_step() errors occurred, regardless of whether or not ** sqlite3_backup_step() completed. ** ^If an out-of-memory condition or IO error occurred during any prior ** sqlite3_backup_step() call on the same [sqlite3_backup] object, then @@ -8255,49 +9713,59 @@ typedef unsigned long long int sqlite_uint64; ** connections, then the source database connection may be used concurrently ** from within other threads. ** -** However, the application must guarantee that the destination -** [database connection] is not passed to any other API (by any thread) after +** However, the application must guarantee that the destination +** [database connection] is not passed to any other API (by any thread) after ** sqlite3_backup_init() is called and before the corresponding call to ** sqlite3_backup_finish(). SQLite does not currently check to see ** if the application incorrectly accesses the destination [database connection] ** and so no error code is reported, but the operations may malfunction ** nevertheless. Use of the destination database connection while a -** backup is in progress might also also cause a mutex deadlock. +** backup is in progress might also cause a mutex deadlock. ** ** If running in [shared cache mode], the application must ** guarantee that the shared cache used by the destination database ** is not accessed while the backup is running. In practice this means -** that the application must guarantee that the disk file being +** that the application must guarantee that the disk file being ** backed up to is not accessed by any connection within the process, ** not just the specific connection that was passed to sqlite3_backup_init(). ** -** The [sqlite3_backup] object itself is partially threadsafe. Multiple +** The [sqlite3_backup] object itself is partially threadsafe. Multiple ** threads may safely make multiple concurrent calls to sqlite3_backup_step(). ** However, the sqlite3_backup_remaining() and sqlite3_backup_pagecount() ** APIs are not strictly speaking threadsafe. If they are invoked at the ** same time as another thread is invoking sqlite3_backup_step() it is ** possible that they return invalid values. +** +** Alternatives To Using The Backup API +** +** Other techniques for safely creating a consistent backup of an SQLite +** database include: +** +**
      +**
    • The [VACUUM INTO] command. +**
    • The [sqlite3_rsync] utility program. +**
    */ - SQLITE_API sqlite3_backup* sqlite3_backup_init( - sqlite3* pDest, /* Destination database handle */ - const char* zDestName, /* Destination database name */ - sqlite3* pSource, /* Source database handle */ - const char* zSourceName /* Source database name */ - ); - SQLITE_API int sqlite3_backup_step(sqlite3_backup* p, int nPage); - SQLITE_API int sqlite3_backup_finish(sqlite3_backup* p); - SQLITE_API int sqlite3_backup_remaining(sqlite3_backup* p); - SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup* p); - - /* +SQLITE_API sqlite3_backup *sqlite3_backup_init( + sqlite3 *pDest, /* Destination database handle */ + const char *zDestName, /* Destination database name */ + sqlite3 *pSource, /* Source database handle */ + const char *zSourceName /* Source database name */ +); +SQLITE_API int sqlite3_backup_step(sqlite3_backup *p, int nPage); +SQLITE_API int sqlite3_backup_finish(sqlite3_backup *p); +SQLITE_API int sqlite3_backup_remaining(sqlite3_backup *p); +SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p); + +/* ** CAPI3REF: Unlock Notification ** METHOD: sqlite3 ** ** ^When running in shared-cache mode, a database operation may fail with ** an [SQLITE_LOCKED] error if the required locks on the shared-cache or ** individual tables within the shared-cache cannot be obtained. See -** [SQLite Shared-Cache Mode] for a description of shared-cache locking. -** ^This API may be used to register a callback that SQLite will invoke +** [SQLite Shared-Cache Mode] for a description of shared-cache locking. +** ^This API may be used to register a callback that SQLite will invoke ** when the connection currently holding the required lock relinquishes it. ** ^This API is only available if the library was compiled with the ** [SQLITE_ENABLE_UNLOCK_NOTIFY] C-preprocessor symbol defined. @@ -8305,18 +9773,18 @@ typedef unsigned long long int sqlite_uint64; ** See Also: [Using the SQLite Unlock Notification Feature]. ** ** ^Shared-cache locks are released when a database connection concludes -** its current transaction, either by committing it or rolling it back. +** its current transaction, either by committing it or rolling it back. ** ** ^When a connection (known as the blocked connection) fails to obtain a ** shared-cache lock and SQLITE_LOCKED is returned to the caller, the ** identity of the database connection (the blocking connection) that -** has locked the required resource is stored internally. ^After an +** has locked the required resource is stored internally. ^After an ** application receives an SQLITE_LOCKED error, it may call the -** sqlite3_unlock_notify() method with the blocked connection handle as +** sqlite3_unlock_notify() method with the blocked connection handle as ** the first argument to register for a callback that will be invoked -** when the blocking connections current transaction is concluded. ^The +** when the blocking connection's current transaction is concluded. ^The ** callback is invoked from within the [sqlite3_step] or [sqlite3_close] -** call that concludes the blocking connections transaction. +** call that concludes the blocking connection's transaction. ** ** ^(If sqlite3_unlock_notify() is called in a multi-threaded application, ** there is a chance that the blocking connection will have already @@ -8326,15 +9794,15 @@ typedef unsigned long long int sqlite_uint64; ** ** ^If the blocked connection is attempting to obtain a write-lock on a ** shared-cache table, and more than one other connection currently holds -** a read-lock on the same table, then SQLite arbitrarily selects one of +** a read-lock on the same table, then SQLite arbitrarily selects one of ** the other connections to use as the blocking connection. ** -** ^(There may be at most one unlock-notify callback registered by a +** ^(There may be at most one unlock-notify callback registered by a ** blocked connection. If sqlite3_unlock_notify() is called when the ** blocked connection already has a registered unlock-notify callback, ** then the new callback replaces the old.)^ ^If sqlite3_unlock_notify() is ** called with a NULL pointer as its second argument, then any existing -** unlock-notify callback is canceled. ^The blocked connections +** unlock-notify callback is canceled. ^The blocked connection's ** unlock-notify callback may also be canceled by closing the blocked ** connection using [sqlite3_close()]. ** @@ -8347,25 +9815,25 @@ typedef unsigned long long int sqlite_uint64; ** ** Callback Invocation Details ** -** When an unlock-notify callback is registered, the application provides a +** When an unlock-notify callback is registered, the application provides a ** single void* pointer that is passed to the callback when it is invoked. ** However, the signature of the callback function allows SQLite to pass ** it an array of void* context pointers. The first argument passed to ** an unlock-notify callback is a pointer to an array of void* pointers, ** and the second is the number of entries in the array. ** -** When a blocking connections transaction is concluded, there may be +** When a blocking connection's transaction is concluded, there may be ** more than one blocked connection that has registered for an unlock-notify ** callback. ^If two or more such blocked connections have specified the ** same callback function, then instead of invoking the callback function ** multiple times, it is invoked once with the set of void* context pointers ** specified by the blocked connections bundled together into an array. -** This gives the application an opportunity to prioritize any actions +** This gives the application an opportunity to prioritize any actions ** related to the set of unblocked database connections. ** ** Deadlock Detection ** -** Assuming that after registering for an unlock-notify callback a +** Assuming that after registering for an unlock-notify callback a ** database waits for the callback to be issued before taking any further ** action (a reasonable assumption), then using this API may cause the ** application to deadlock. For example, if connection X is waiting for @@ -8388,7 +9856,7 @@ typedef unsigned long long int sqlite_uint64; ** ** The "DROP TABLE" Exception ** -** When a call to [sqlite3_step()] returns SQLITE_LOCKED, it is almost +** When a call to [sqlite3_step()] returns SQLITE_LOCKED, it is almost ** always appropriate to call sqlite3_unlock_notify(). There is however, ** one exception. When executing a "DROP TABLE" or "DROP INDEX" statement, ** SQLite checks if there are any currently executing SELECT statements @@ -8401,16 +9869,17 @@ typedef unsigned long long int sqlite_uint64; ** One way around this problem is to check the extended error code returned ** by an sqlite3_step() call. ^(If there is a blocking connection, then the ** extended error code is set to SQLITE_LOCKED_SHAREDCACHE. Otherwise, in -** the special "DROP TABLE/INDEX" case, the extended error code is just +** the special "DROP TABLE/INDEX" case, the extended error code is just ** SQLITE_LOCKED.)^ */ - SQLITE_API int sqlite3_unlock_notify( - sqlite3* pBlocked, /* Waiting connection */ - void (*xNotify)(void** apArg, int nArg), /* Callback function to invoke */ - void* pNotifyArg /* Argument to pass to xNotify */ - ); +SQLITE_API int sqlite3_unlock_notify( + sqlite3 *pBlocked, /* Waiting connection */ + void (*xNotify)(void **apArg, int nArg), /* Callback function to invoke */ + void *pNotifyArg /* Argument to pass to xNotify */ +); - /* + +/* ** CAPI3REF: String Comparison ** ** ^The [sqlite3_stricmp()] and [sqlite3_strnicmp()] APIs allow applications @@ -8418,10 +9887,10 @@ typedef unsigned long long int sqlite_uint64; ** strings in a case-independent fashion, using the same definition of "case ** independence" that SQLite uses internally when comparing identifiers. */ - SQLITE_API int sqlite3_stricmp(const char*, const char*); - SQLITE_API int sqlite3_strnicmp(const char*, const char*, int); +SQLITE_API int sqlite3_stricmp(const char *, const char *); +SQLITE_API int sqlite3_strnicmp(const char *, const char *, int); - /* +/* ** CAPI3REF: String Globbing * ** ^The [sqlite3_strglob(P,X)] interface returns zero if and only if @@ -8436,9 +9905,9 @@ typedef unsigned long long int sqlite_uint64; ** ** See also: [sqlite3_strlike()]. */ - SQLITE_API int sqlite3_strglob(const char* zGlob, const char* zStr); +SQLITE_API int sqlite3_strglob(const char *zGlob, const char *zStr); - /* +/* ** CAPI3REF: String LIKE Matching * ** ^The [sqlite3_strlike(P,X,E)] interface returns zero if and only if @@ -8459,9 +9928,9 @@ typedef unsigned long long int sqlite_uint64; ** ** See also: [sqlite3_strglob()]. */ - SQLITE_API int sqlite3_strlike(const char* zGlob, const char* zStr, unsigned int cEsc); +SQLITE_API int sqlite3_strlike(const char *zGlob, const char *zStr, unsigned int cEsc); - /* +/* ** CAPI3REF: Error Logging Interface ** ** ^The [sqlite3_log()] interface writes a message into the [error log] @@ -8482,17 +9951,17 @@ typedef unsigned long long int sqlite_uint64; ** a few hundred characters, it will be truncated to the length of the ** buffer. */ - SQLITE_API void sqlite3_log(int iErrCode, const char* zFormat, ...); +SQLITE_API void sqlite3_log(int iErrCode, const char *zFormat, ...); - /* +/* ** CAPI3REF: Write-Ahead Log Commit Hook ** METHOD: sqlite3 ** ** ^The [sqlite3_wal_hook()] function is used to register a callback that ** is invoked each time data is committed to a database in wal mode. ** -** ^(The callback is invoked by SQLite after the commit has taken place and -** the associated write-lock on the database released)^, so the implementation +** ^(The callback is invoked by SQLite after the commit has taken place and +** the associated write-lock on the database released)^, so the implementation ** may read, write or [checkpoint] the database as required. ** ** ^The first parameter passed to the callback function when it is invoked @@ -8503,7 +9972,7 @@ typedef unsigned long long int sqlite_uint64; ** is the number of pages currently in the write-ahead log file, ** including those that were just committed. ** -** The callback function should normally return [SQLITE_OK]. ^If an error +** ^The callback function should normally return [SQLITE_OK]. ^If an error ** code is returned, that error will propagate back up through the ** SQLite code base to cause the statement that provoked the callback ** to report an error, though the commit will have still occurred. If the @@ -8511,19 +9980,34 @@ typedef unsigned long long int sqlite_uint64; ** that does not correspond to any valid SQLite error code, the results ** are undefined. ** -** A single database handle may have at most a single write-ahead log callback -** registered at one time. ^Calling [sqlite3_wal_hook()] replaces any -** previously registered write-ahead log callback. ^Note that the -** [sqlite3_wal_autocheckpoint()] interface and the -** [wal_autocheckpoint pragma] both invoke [sqlite3_wal_hook()] and will -** overwrite any prior [sqlite3_wal_hook()] settings. +** ^A single database handle may have at most a single write-ahead log +** callback registered at one time. ^Calling [sqlite3_wal_hook()] +** replaces the default behavior or previously registered write-ahead +** log callback. +** +** ^The return value is a copy of the third parameter from the +** previous call, if any, or 0. +** +** ^The [sqlite3_wal_autocheckpoint()] interface and the +** [wal_autocheckpoint pragma] both invoke [sqlite3_wal_hook()] and +** will overwrite any prior [sqlite3_wal_hook()] settings. +** +** ^If a write-ahead log callback is set using this function then +** [sqlite3_wal_checkpoint_v2()] or [PRAGMA wal_checkpoint] +** should be invoked periodically to keep the write-ahead log file +** from growing without bound. +** +** ^Passing a NULL pointer for the callback disables automatic +** checkpointing entirely. To re-enable the default behavior, call +** sqlite3_wal_autocheckpoint(db,1000) or use [PRAGMA wal_checkpoint]. */ - SQLITE_API void* sqlite3_wal_hook( - sqlite3*, - int (*)(void*, sqlite3*, const char*, int), - void*); +SQLITE_API void *sqlite3_wal_hook( + sqlite3*, + int(*)(void *,sqlite3*,const char*,int), + void* +); - /* +/* ** CAPI3REF: Configure an auto-checkpoint ** METHOD: sqlite3 ** @@ -8531,8 +10015,8 @@ typedef unsigned long long int sqlite_uint64; ** [sqlite3_wal_hook()] that causes any database on [database connection] D ** to automatically [checkpoint] ** after committing a transaction if there are N or -** more frames in the [write-ahead log] file. ^Passing zero or -** a negative value as the nFrame parameter disables automatic +** more frames in the [write-ahead log] file. ^Passing zero or +** a negative value as the N parameter disables automatic ** checkpoints entirely. ** ** ^The callback registered by this function replaces any existing callback @@ -8548,20 +10032,21 @@ typedef unsigned long long int sqlite_uint64; ** ** ^Every new [database connection] defaults to having the auto-checkpoint ** enabled with a threshold of 1000 or [SQLITE_DEFAULT_WAL_AUTOCHECKPOINT] -** pages. The use of this interface -** is only necessary if the default setting is found to be suboptimal -** for a particular application. +** pages. +** +** ^The use of this interface is only necessary if the default setting +** is found to be suboptimal for a particular application. */ - SQLITE_API int sqlite3_wal_autocheckpoint(sqlite3* db, int N); +SQLITE_API int sqlite3_wal_autocheckpoint(sqlite3 *db, int N); - /* +/* ** CAPI3REF: Checkpoint a database ** METHOD: sqlite3 ** ** ^(The sqlite3_wal_checkpoint(D,X) is equivalent to ** [sqlite3_wal_checkpoint_v2](D,X,[SQLITE_CHECKPOINT_PASSIVE],0,0).)^ ** -** In brief, sqlite3_wal_checkpoint(D,X) causes the content in the +** In brief, sqlite3_wal_checkpoint(D,X) causes the content in the ** [write-ahead log] for database X on [database connection] D to be ** transferred into the database file and for the write-ahead log to ** be reset. See the [checkpointing] documentation for addition @@ -8574,9 +10059,9 @@ typedef unsigned long long int sqlite_uint64; ** start a callback but which do not need the full power (and corresponding ** complication) of [sqlite3_wal_checkpoint_v2()]. */ - SQLITE_API int sqlite3_wal_checkpoint(sqlite3* db, const char* zDb); +SQLITE_API int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb); - /* +/* ** CAPI3REF: Checkpoint a database ** METHOD: sqlite3 ** @@ -8587,10 +10072,10 @@ typedef unsigned long long int sqlite_uint64; ** **
    **
    SQLITE_CHECKPOINT_PASSIVE
    -** ^Checkpoint as many frames as possible without waiting for any database -** readers or writers to finish, then sync the database file if all frames +** ^Checkpoint as many frames as possible without waiting for any database +** readers or writers to finish, then sync the database file if all frames ** in the log were checkpointed. ^The [busy-handler callback] -** is never invoked in the SQLITE_CHECKPOINT_PASSIVE mode. +** is never invoked in the SQLITE_CHECKPOINT_PASSIVE mode. ** ^On the other hand, passive mode might leave the checkpoint unfinished ** if there are concurrent readers or writers. ** @@ -8604,9 +10089,9 @@ typedef unsigned long long int sqlite_uint64; ** **
    SQLITE_CHECKPOINT_RESTART
    ** ^This mode works the same way as SQLITE_CHECKPOINT_FULL with the addition -** that after checkpointing the log file it blocks (calls the +** that after checkpointing the log file it blocks (calls the ** [busy-handler callback]) -** until all readers are reading from the database file only. ^This ensures +** until all readers are reading from the database file only. ^This ensures ** that the next writer will restart the log file from the beginning. ** ^Like SQLITE_CHECKPOINT_FULL, this mode blocks new ** database writer attempts while it is pending, but does not impede readers. @@ -8615,6 +10100,11 @@ typedef unsigned long long int sqlite_uint64; ** ^This mode works the same way as SQLITE_CHECKPOINT_RESTART with the ** addition that it also truncates the log file to zero bytes just prior ** to a successful return. +** +**
    SQLITE_CHECKPOINT_NOOP
    +** ^This mode always checkpoints zero frames. The only reason to invoke +** a NOOP checkpoint is to access the values returned by +** sqlite3_wal_checkpoint_v2() via output parameters *pnLog and *pnCkpt. **
    ** ** ^If pnLog is not NULL, then *pnLog is set to the total number of frames in @@ -8628,31 +10118,31 @@ typedef unsigned long long int sqlite_uint64; ** truncated to zero bytes and so both *pnLog and *pnCkpt will be set to zero. ** ** ^All calls obtain an exclusive "checkpoint" lock on the database file. ^If -** any other process is running a checkpoint operation at the same time, the -** lock cannot be obtained and SQLITE_BUSY is returned. ^Even if there is a +** any other process is running a checkpoint operation at the same time, the +** lock cannot be obtained and SQLITE_BUSY is returned. ^Even if there is a ** busy-handler configured, it will not be invoked in this case. ** -** ^The SQLITE_CHECKPOINT_FULL, RESTART and TRUNCATE modes also obtain the +** ^The SQLITE_CHECKPOINT_FULL, RESTART and TRUNCATE modes also obtain the ** exclusive "writer" lock on the database file. ^If the writer lock cannot be ** obtained immediately, and a busy-handler is configured, it is invoked and ** the writer lock retried until either the busy-handler returns 0 or the lock ** is successfully obtained. ^The busy-handler is also invoked while waiting for ** database readers as described above. ^If the busy-handler returns 0 before ** the writer lock is obtained or while waiting for database readers, the -** checkpoint operation proceeds from that point in the same way as -** SQLITE_CHECKPOINT_PASSIVE - checkpointing as many frames as possible +** checkpoint operation proceeds from that point in the same way as +** SQLITE_CHECKPOINT_PASSIVE - checkpointing as many frames as possible ** without blocking any further. ^SQLITE_BUSY is returned in this case. ** ** ^If parameter zDb is NULL or points to a zero length string, then the -** specified operation is attempted on all WAL databases [attached] to +** specified operation is attempted on all WAL databases [attached] to ** [database connection] db. In this case the -** values written to output parameters *pnLog and *pnCkpt are undefined. ^If -** an SQLITE_BUSY error is encountered when processing one or more of the -** attached WAL databases, the operation is still attempted on any remaining -** attached databases and SQLITE_BUSY is returned at the end. ^If any other -** error occurs while processing an attached database, processing is abandoned -** and the error code is returned to the caller immediately. ^If no error -** (SQLITE_BUSY or otherwise) is encountered while processing the attached +** values written to output parameters *pnLog and *pnCkpt are undefined. ^If +** an SQLITE_BUSY error is encountered when processing one or more of the +** attached WAL databases, the operation is still attempted on any remaining +** attached databases and SQLITE_BUSY is returned at the end. ^If any other +** error occurs while processing an attached database, processing is abandoned +** and the error code is returned to the caller immediately. ^If no error +** (SQLITE_BUSY or otherwise) is encountered while processing the attached ** databases, SQLITE_OK is returned. ** ** ^If database zDb is the name of an attached database that is not in WAL @@ -8668,13 +10158,13 @@ typedef unsigned long long int sqlite_uint64; ** ^The [PRAGMA wal_checkpoint] command can be used to invoke this interface ** from SQL. */ - SQLITE_API int sqlite3_wal_checkpoint_v2( - sqlite3* db, /* Database handle */ - const char* zDb, /* Name of attached database (or NULL) */ - int eMode, /* SQLITE_CHECKPOINT_* value */ - int* pnLog, /* OUT: Size of WAL log in frames */ - int* pnCkpt /* OUT: Total number of frames checkpointed */ - ); +SQLITE_API int sqlite3_wal_checkpoint_v2( + sqlite3 *db, /* Database handle */ + const char *zDb, /* Name of attached database (or NULL) */ + int eMode, /* SQLITE_CHECKPOINT_* value */ + int *pnLog, /* OUT: Size of WAL log in frames */ + int *pnCkpt /* OUT: Total number of frames checkpointed */ +); /* ** CAPI3REF: Checkpoint Mode Values @@ -8685,12 +10175,13 @@ typedef unsigned long long int sqlite_uint64; ** See the [sqlite3_wal_checkpoint_v2()] documentation for details on the ** meaning of each of these checkpoint modes. */ -#define SQLITE_CHECKPOINT_PASSIVE 0 /* Do as much as possible w/o blocking */ -#define SQLITE_CHECKPOINT_FULL 1 /* Wait for writers, then checkpoint */ -#define SQLITE_CHECKPOINT_RESTART 2 /* Like FULL but wait for for readers */ -#define SQLITE_CHECKPOINT_TRUNCATE 3 /* Like RESTART but also truncate WAL */ +#define SQLITE_CHECKPOINT_NOOP -1 /* Do no work at all */ +#define SQLITE_CHECKPOINT_PASSIVE 0 /* Do as much as possible w/o blocking */ +#define SQLITE_CHECKPOINT_FULL 1 /* Wait for writers, then checkpoint */ +#define SQLITE_CHECKPOINT_RESTART 2 /* Like FULL but wait for readers */ +#define SQLITE_CHECKPOINT_TRUNCATE 3 /* Like RESTART but also truncate WAL */ - /* +/* ** CAPI3REF: Virtual Table Interface Configuration ** ** This function may be called by either the [xConnect] or [xCreate] method @@ -8700,14 +10191,20 @@ typedef unsigned long long int sqlite_uint64; ** If this interface is invoked outside the context of an xConnect or ** xCreate virtual table method then the behavior is undefined. ** -** At present, there is only one option that may be configured using -** this function. (See [SQLITE_VTAB_CONSTRAINT_SUPPORT].) Further options -** may be added in the future. +** In the call sqlite3_vtab_config(D,C,...) the D parameter is the +** [database connection] in which the virtual table is being created and +** which is passed in as the first argument to the [xConnect] or [xCreate] +** method that is invoking sqlite3_vtab_config(). The C parameter is one +** of the [virtual table configuration options]. The presence and meaning +** of parameters after C depend on which [virtual table configuration option] +** is used. */ - SQLITE_API int sqlite3_vtab_config(sqlite3*, int op, ...); +SQLITE_API int sqlite3_vtab_config(sqlite3*, int op, ...); /* ** CAPI3REF: Virtual Table Configuration Options +** KEYWORDS: {virtual table configuration options} +** KEYWORDS: {virtual table configuration option} ** ** These macros define the various options to the ** [sqlite3_vtab_config()] interface that [virtual table] implementations @@ -8715,7 +10212,7 @@ typedef unsigned long long int sqlite_uint64; ** **
    ** [[SQLITE_VTAB_CONSTRAINT_SUPPORT]] -**
    SQLITE_VTAB_CONSTRAINT_SUPPORT +**
    SQLITE_VTAB_CONSTRAINT_SUPPORT
    **
    Calls of the form ** [sqlite3_vtab_config](db,SQLITE_VTAB_CONSTRAINT_SUPPORT,X) are supported, ** where X is an integer. If X is zero, then the [virtual table] whose @@ -8723,32 +10220,64 @@ typedef unsigned long long int sqlite_uint64; ** support constraints. In this configuration (which is the default) if ** a call to the [xUpdate] method returns [SQLITE_CONSTRAINT], then the entire ** statement is rolled back as if [ON CONFLICT | OR ABORT] had been -** specified as part of the users SQL statement, regardless of the actual +** specified as part of the user's SQL statement, regardless of the actual ** ON CONFLICT mode specified. ** ** If X is non-zero, then the virtual table implementation guarantees ** that if [xUpdate] returns [SQLITE_CONSTRAINT], it will do so before ** any modifications to internal or persistent data structures have been made. -** If the [ON CONFLICT] mode is ABORT, FAIL, IGNORE or ROLLBACK, SQLite +** If the [ON CONFLICT] mode is ABORT, FAIL, IGNORE or ROLLBACK, SQLite ** is able to roll back a statement or database transaction, and abandon -** or continue processing the current SQL statement as appropriate. +** or continue processing the current SQL statement as appropriate. ** If the ON CONFLICT mode is REPLACE and the [xUpdate] method returns ** [SQLITE_CONSTRAINT], SQLite handles this as if the ON CONFLICT mode ** had been ABORT. ** ** Virtual table implementations that are required to handle OR REPLACE -** must do so within the [xUpdate] method. If a call to the -** [sqlite3_vtab_on_conflict()] function indicates that the current ON -** CONFLICT policy is REPLACE, the virtual table implementation should +** must do so within the [xUpdate] method. If a call to the +** [sqlite3_vtab_on_conflict()] function indicates that the current ON +** CONFLICT policy is REPLACE, the virtual table implementation should ** silently replace the appropriate rows within the xUpdate callback and ** return SQLITE_OK. Or, if this is not possible, it may return -** SQLITE_CONSTRAINT, in which case SQLite falls back to OR ABORT +** SQLITE_CONSTRAINT, in which case SQLite falls back to OR ABORT ** constraint handling. +**
    +** +** [[SQLITE_VTAB_DIRECTONLY]]
    SQLITE_VTAB_DIRECTONLY
    +**
    Calls of the form +** [sqlite3_vtab_config](db,SQLITE_VTAB_DIRECTONLY) from within the +** the [xConnect] or [xCreate] methods of a [virtual table] implementation +** prohibits that virtual table from being used from within triggers and +** views. +**
    +** +** [[SQLITE_VTAB_INNOCUOUS]]
    SQLITE_VTAB_INNOCUOUS
    +**
    Calls of the form +** [sqlite3_vtab_config](db,SQLITE_VTAB_INNOCUOUS) from within the +** [xConnect] or [xCreate] methods of a [virtual table] implementation +** identify that virtual table as being safe to use from within triggers +** and views. Conceptually, the SQLITE_VTAB_INNOCUOUS tag means that the +** virtual table can do no serious harm even if it is controlled by a +** malicious hacker. Developers should avoid setting the SQLITE_VTAB_INNOCUOUS +** flag unless absolutely necessary. +**
    +** +** [[SQLITE_VTAB_USES_ALL_SCHEMAS]]
    SQLITE_VTAB_USES_ALL_SCHEMAS
    +**
    Calls of the form +** [sqlite3_vtab_config](db,SQLITE_VTAB_USES_ALL_SCHEMA) from within the +** the [xConnect] or [xCreate] methods of a [virtual table] implementation +** instruct the query planner to begin at least a read transaction on +** all schemas ("main", "temp", and any ATTACH-ed databases) whenever the +** virtual table is used. +**
    **
    */ #define SQLITE_VTAB_CONSTRAINT_SUPPORT 1 +#define SQLITE_VTAB_INNOCUOUS 2 +#define SQLITE_VTAB_DIRECTONLY 3 +#define SQLITE_VTAB_USES_ALL_SCHEMAS 4 - /* +/* ** CAPI3REF: Determine The Virtual Table Conflict Policy ** ** This function may only be called from within a call to the [xUpdate] method @@ -8758,16 +10287,17 @@ typedef unsigned long long int sqlite_uint64; ** of the SQL statement that triggered the call to the [xUpdate] method of the ** [virtual table]. */ - SQLITE_API int sqlite3_vtab_on_conflict(sqlite3*); +SQLITE_API int sqlite3_vtab_on_conflict(sqlite3 *); - /* +/* ** CAPI3REF: Determine If Virtual Table Column Access Is For UPDATE ** ** If the sqlite3_vtab_nochange(X) routine is called within the [xColumn] -** method of a [virtual table], then it returns true if and only if the +** method of a [virtual table], then it might return true if the ** column is being fetched as part of an UPDATE operation during which the -** column value will not change. Applications might use this to substitute -** a return value that is less expensive to compute and that the corresponding +** column value will not change. The virtual table implementation can use +** this hint as permission to substitute a return value that is less +** expensive to compute and that the corresponding ** [xUpdate] method understands as a "no-change" value. ** ** If the [xColumn] method calls sqlite3_vtab_nochange() and finds that @@ -8776,31 +10306,315 @@ typedef unsigned long long int sqlite_uint64; ** any of the [sqlite3_result_int|sqlite3_result_xxxxx() interfaces]. ** In that case, [sqlite3_value_nochange(X)] will return true for the ** same column in the [xUpdate] method. +** +** The sqlite3_vtab_nochange() routine is an optimization. Virtual table +** implementations should continue to give a correct answer even if the +** sqlite3_vtab_nochange() interface were to always return false. In the +** current implementation, the sqlite3_vtab_nochange() interface does always +** returns false for the enhanced [UPDATE FROM] statement. */ - SQLITE_API int sqlite3_vtab_nochange(sqlite3_context*); +SQLITE_API int sqlite3_vtab_nochange(sqlite3_context*); - /* +/* ** CAPI3REF: Determine The Collation For a Virtual Table Constraint +** METHOD: sqlite3_index_info ** ** This function may only be called from within a call to the [xBestIndex] -** method of a [virtual table]. +** method of a [virtual table]. This function returns a pointer to a string +** that is the name of the appropriate collation sequence to use for text +** comparisons on the constraint identified by its arguments. +** +** The first argument must be the pointer to the [sqlite3_index_info] object +** that is the first parameter to the xBestIndex() method. The second argument +** must be an index into the aConstraint[] array belonging to the +** sqlite3_index_info structure passed to xBestIndex. +** +** Important: +** The first parameter must be the same pointer that is passed into the +** xBestMethod() method. The first parameter may not be a pointer to a +** different [sqlite3_index_info] object, even an exact copy. +** +** The return value is computed as follows: +** +**
      +**
    1. If the constraint comes from a WHERE clause expression that contains +** a [COLLATE operator], then the name of the collation specified by +** that COLLATE operator is returned. +**

    2. If there is no COLLATE operator, but the column that is the subject +** of the constraint specifies an alternative collating sequence via +** a [COLLATE clause] on the column definition within the CREATE TABLE +** statement that was passed into [sqlite3_declare_vtab()], then the +** name of that alternative collating sequence is returned. +**

    3. Otherwise, "BINARY" is returned. +**

    +*/ +SQLITE_API const char *sqlite3_vtab_collation(sqlite3_index_info*,int); + +/* +** CAPI3REF: Determine if a virtual table query is DISTINCT +** METHOD: sqlite3_index_info +** +** This API may only be used from within an [xBestIndex|xBestIndex method] +** of a [virtual table] implementation. The result of calling this +** interface from outside of xBestIndex() is undefined and probably harmful. +** +** ^The sqlite3_vtab_distinct() interface returns an integer between 0 and +** 3. The integer returned by sqlite3_vtab_distinct() +** gives the virtual table additional information about how the query +** planner wants the output to be ordered. As long as the virtual table +** can meet the ordering requirements of the query planner, it may set +** the "orderByConsumed" flag. +** +**
    1. +** ^If the sqlite3_vtab_distinct() interface returns 0, that means +** that the query planner needs the virtual table to return all rows in the +** sort order defined by the "nOrderBy" and "aOrderBy" fields of the +** [sqlite3_index_info] object. This is the default expectation. If the +** virtual table outputs all rows in sorted order, then it is always safe for +** the xBestIndex method to set the "orderByConsumed" flag, regardless of +** the return value from sqlite3_vtab_distinct(). +**

    2. +** ^(If the sqlite3_vtab_distinct() interface returns 1, that means +** that the query planner does not need the rows to be returned in sorted order +** as long as all rows with the same values in all columns identified by the +** "aOrderBy" field are adjacent.)^ This mode is used when the query planner +** is doing a GROUP BY. +**

    3. +** ^(If the sqlite3_vtab_distinct() interface returns 2, that means +** that the query planner does not need the rows returned in any particular +** order, as long as rows with the same values in all columns identified +** by "aOrderBy" are adjacent.)^ ^(Furthermore, when two or more rows +** contain the same values for all columns identified by "colUsed", all but +** one such row may optionally be omitted from the result.)^ +** The virtual table is not required to omit rows that are duplicates +** over the "colUsed" columns, but if the virtual table can do that without +** too much extra effort, it could potentially help the query to run faster. +** This mode is used for a DISTINCT query. +**

    4. +** ^(If the sqlite3_vtab_distinct() interface returns 3, that means the +** virtual table must return rows in the order defined by "aOrderBy" as +** if the sqlite3_vtab_distinct() interface had returned 0. However if +** two or more rows in the result have the same values for all columns +** identified by "colUsed", then all but one such row may optionally be +** omitted.)^ Like when the return value is 2, the virtual table +** is not required to omit rows that are duplicates over the "colUsed" +** columns, but if the virtual table can do that without +** too much extra effort, it could potentially help the query to run faster. +** This mode is used for queries +** that have both DISTINCT and ORDER BY clauses. +**

    +** +**

    The following table summarizes the conditions under which the +** virtual table is allowed to set the "orderByConsumed" flag based on +** the value returned by sqlite3_vtab_distinct(). This table is a +** restatement of the previous four paragraphs: +** +** +** +**
    sqlite3_vtab_distinct() return value +** Rows are returned in aOrderBy order +** Rows with the same value in all aOrderBy columns are +** adjacent +** Duplicates over all colUsed columns may be omitted +**
    0yesyesno +**
    1noyesno +**
    2noyesyes +**
    3yesyesyes +**
    +** +** ^For the purposes of comparing virtual table output values to see if the +** values are the same value for sorting purposes, two NULL values are +** considered to be the same. In other words, the comparison operator is "IS" +** (or "IS NOT DISTINCT FROM") and not "==". +** +** If a virtual table implementation is unable to meet the requirements +** specified above, then it must not set the "orderByConsumed" flag in the +** [sqlite3_index_info] object or an incorrect answer may result. +** +** ^A virtual table implementation is always free to return rows in any order +** it wants, as long as the "orderByConsumed" flag is not set. ^When the +** "orderByConsumed" flag is unset, the query planner will add extra +** [bytecode] to ensure that the final results returned by the SQL query are +** ordered correctly. The use of the "orderByConsumed" flag and the +** sqlite3_vtab_distinct() interface is merely an optimization. ^Careful +** use of the sqlite3_vtab_distinct() interface and the "orderByConsumed" +** flag might help queries against a virtual table to run faster. Being +** overly aggressive and setting the "orderByConsumed" flag when it is not +** valid to do so, on the other hand, might cause SQLite to return incorrect +** results. +*/ +SQLITE_API int sqlite3_vtab_distinct(sqlite3_index_info*); + +/* +** CAPI3REF: Identify and handle IN constraints in xBestIndex +** +** This interface may only be used from within an +** [xBestIndex|xBestIndex() method] of a [virtual table] implementation. +** The result of invoking this interface from any other context is +** undefined and probably harmful. +** +** ^(A constraint on a virtual table of the form +** "[IN operator|column IN (...)]" is +** communicated to the xBestIndex method as a +** [SQLITE_INDEX_CONSTRAINT_EQ] constraint.)^ If xBestIndex wants to use +** this constraint, it must set the corresponding +** aConstraintUsage[].argvIndex to a positive integer. ^(Then, under +** the usual mode of handling IN operators, SQLite generates [bytecode] +** that invokes the [xFilter|xFilter() method] once for each value +** on the right-hand side of the IN operator.)^ Thus the virtual table +** only sees a single value from the right-hand side of the IN operator +** at a time. +** +** In some cases, however, it would be advantageous for the virtual +** table to see all values on the right-hand of the IN operator all at +** once. The sqlite3_vtab_in() interfaces facilitates this in two ways: +** +**

      +**
    1. +** ^A call to sqlite3_vtab_in(P,N,-1) will return true (non-zero) +** if and only if the [sqlite3_index_info|P->aConstraint][N] constraint +** is an [IN operator] that can be processed all at once. ^In other words, +** sqlite3_vtab_in() with -1 in the third argument is a mechanism +** by which the virtual table can ask SQLite if all-at-once processing +** of the IN operator is even possible. +** +**

    2. +** ^A call to sqlite3_vtab_in(P,N,F) with F==1 or F==0 indicates +** to SQLite that the virtual table does or does not want to process +** the IN operator all-at-once, respectively. ^Thus when the third +** parameter (F) is non-negative, this interface is the mechanism by +** which the virtual table tells SQLite how it wants to process the +** IN operator. +**

    +** +** ^The sqlite3_vtab_in(P,N,F) interface can be invoked multiple times +** within the same xBestIndex method call. ^For any given P,N pair, +** the return value from sqlite3_vtab_in(P,N,F) will always be the same +** within the same xBestIndex call. ^If the interface returns true +** (non-zero), that means that the constraint is an IN operator +** that can be processed all-at-once. ^If the constraint is not an IN +** operator or cannot be processed all-at-once, then the interface returns +** false. +** +** ^(All-at-once processing of the IN operator is selected if both of the +** following conditions are met: +** +**
      +**
    1. The P->aConstraintUsage[N].argvIndex value is set to a positive +** integer. This is how the virtual table tells SQLite that it wants to +** use the N-th constraint. +** +**

    2. The last call to sqlite3_vtab_in(P,N,F) for which F was +** non-negative had F>=1. +**

    )^ +** +** ^If either or both of the conditions above are false, then SQLite uses +** the traditional one-at-a-time processing strategy for the IN constraint. +** ^If both conditions are true, then the argvIndex-th parameter to the +** xFilter method will be an [sqlite3_value] that appears to be NULL, +** but which can be passed to [sqlite3_vtab_in_first()] and +** [sqlite3_vtab_in_next()] to find all values on the right-hand side +** of the IN constraint. +*/ +SQLITE_API int sqlite3_vtab_in(sqlite3_index_info*, int iCons, int bHandle); + +/* +** CAPI3REF: Find all elements on the right-hand side of an IN constraint. +** +** These interfaces are only useful from within the +** [xFilter|xFilter() method] of a [virtual table] implementation. +** The result of invoking these interfaces from any other context +** is undefined and probably harmful. +** +** The X parameter in a call to sqlite3_vtab_in_first(X,P) or +** sqlite3_vtab_in_next(X,P) should be one of the parameters to the +** xFilter method which invokes these routines, and specifically +** a parameter that was previously selected for all-at-once IN constraint +** processing using the [sqlite3_vtab_in()] interface in the +** [xBestIndex|xBestIndex method]. ^(If the X parameter is not +** an xFilter argument that was selected for all-at-once IN constraint +** processing, then these routines return [SQLITE_ERROR].)^ +** +** ^(Use these routines to access all values on the right-hand side +** of the IN constraint using code like the following: +** +**
    +**    for(rc=sqlite3_vtab_in_first(pList, &pVal);
    +**        rc==SQLITE_OK && pVal;
    +**        rc=sqlite3_vtab_in_next(pList, &pVal)
    +**    ){
    +**      // do something with pVal
    +**    }
    +**    if( rc!=SQLITE_DONE ){
    +**      // an error has occurred
    +**    }
    +** 
    )^ ** -** The first argument must be the sqlite3_index_info object that is the -** first parameter to the xBestIndex() method. The second argument must be -** an index into the aConstraint[] array belonging to the sqlite3_index_info -** structure passed to xBestIndex. This function returns a pointer to a buffer -** containing the name of the collation sequence for the corresponding -** constraint. +** ^On success, the sqlite3_vtab_in_first(X,P) and sqlite3_vtab_in_next(X,P) +** routines return SQLITE_OK and set *P to point to the first or next value +** on the RHS of the IN constraint. ^If there are no more values on the +** right hand side of the IN constraint, then *P is set to NULL and these +** routines return [SQLITE_DONE]. ^The return value might be +** some other value, such as SQLITE_NOMEM, in the event of a malfunction. +** +** The *ppOut values returned by these routines are only valid until the +** next call to either of these routines or until the end of the xFilter +** method from which these routines were called. If the virtual table +** implementation needs to retain the *ppOut values for longer, it must make +** copies. The *ppOut values are [protected sqlite3_value|protected]. */ - SQLITE_API SQLITE_EXPERIMENTAL const char* sqlite3_vtab_collation(sqlite3_index_info*, int); +SQLITE_API int sqlite3_vtab_in_first(sqlite3_value *pVal, sqlite3_value **ppOut); +SQLITE_API int sqlite3_vtab_in_next(sqlite3_value *pVal, sqlite3_value **ppOut); + +/* +** CAPI3REF: Constraint values in xBestIndex() +** METHOD: sqlite3_index_info +** +** This API may only be used from within the [xBestIndex|xBestIndex method] +** of a [virtual table] implementation. The result of calling this interface +** from outside of an xBestIndex method are undefined and probably harmful. +** +** ^When the sqlite3_vtab_rhs_value(P,J,V) interface is invoked from within +** the [xBestIndex] method of a [virtual table] implementation, with P being +** a copy of the [sqlite3_index_info] object pointer passed into xBestIndex and +** J being a 0-based index into P->aConstraint[], then this routine +** attempts to set *V to the value of the right-hand operand of +** that constraint if the right-hand operand is known. ^If the +** right-hand operand is not known, then *V is set to a NULL pointer. +** ^The sqlite3_vtab_rhs_value(P,J,V) interface returns SQLITE_OK if +** and only if *V is set to a value. ^The sqlite3_vtab_rhs_value(P,J,V) +** inteface returns SQLITE_NOTFOUND if the right-hand side of the J-th +** constraint is not available. ^The sqlite3_vtab_rhs_value() interface +** can return a result code other than SQLITE_OK or SQLITE_NOTFOUND if +** something goes wrong. +** +** The sqlite3_vtab_rhs_value() interface is usually only successful if +** the right-hand operand of a constraint is a literal value in the original +** SQL statement. If the right-hand operand is an expression or a reference +** to some other column or a [host parameter], then sqlite3_vtab_rhs_value() +** will probably return [SQLITE_NOTFOUND]. +** +** ^(Some constraints, such as [SQLITE_INDEX_CONSTRAINT_ISNULL] and +** [SQLITE_INDEX_CONSTRAINT_ISNOTNULL], have no right-hand operand. For such +** constraints, sqlite3_vtab_rhs_value() always returns SQLITE_NOTFOUND.)^ +** +** ^The [sqlite3_value] object returned in *V is a protected sqlite3_value +** and remains valid for the duration of the xBestIndex method call. +** ^When xBestIndex returns, the sqlite3_value object returned by +** sqlite3_vtab_rhs_value() is automatically deallocated. +** +** The "_rhs_" in the name of this routine is an abbreviation for +** "Right-Hand Side". +*/ +SQLITE_API int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value **ppVal); /* ** CAPI3REF: Conflict resolution modes ** KEYWORDS: {conflict resolution mode} ** ** These constants are returned by [sqlite3_vtab_on_conflict()] to -** inform a [virtual table] implementation what the [ON CONFLICT] mode -** is for the SQL statement being evaluated. +** inform a [virtual table] implementation of the [ON CONFLICT] mode +** for the SQL statement being evaluated. ** ** Note that the [SQLITE_IGNORE] constant is also used as a potential ** return value from the [sqlite3_set_authorizer()] callback and that @@ -8808,9 +10622,9 @@ typedef unsigned long long int sqlite_uint64; */ #define SQLITE_ROLLBACK 1 /* #define SQLITE_IGNORE 2 // Also used by sqlite3_authorizer() callback */ -#define SQLITE_FAIL 3 +#define SQLITE_FAIL 3 /* #define SQLITE_ABORT 4 // Also an error code */ -#define SQLITE_REPLACE 5 +#define SQLITE_REPLACE 5 /* ** CAPI3REF: Prepared Statement Scan Status Opcodes @@ -8824,53 +10638,71 @@ typedef unsigned long long int sqlite_uint64; ** managed by the prepared statement S and will be automatically freed when ** S is finalized. ** +** Not all values are available for all query elements. When a value is +** not available, the output variable is set to -1 if the value is numeric, +** or to NULL if it is a string (SQLITE_SCANSTAT_NAME). +** **
    ** [[SQLITE_SCANSTAT_NLOOP]]
    SQLITE_SCANSTAT_NLOOP
    -**
    ^The [sqlite3_int64] variable pointed to by the T parameter will be +**
    ^The [sqlite3_int64] variable pointed to by the V parameter will be ** set to the total number of times that the X-th loop has run.
    ** ** [[SQLITE_SCANSTAT_NVISIT]]
    SQLITE_SCANSTAT_NVISIT
    -**
    ^The [sqlite3_int64] variable pointed to by the T parameter will be set +**
    ^The [sqlite3_int64] variable pointed to by the V parameter will be set ** to the total number of rows examined by all iterations of the X-th loop.
    ** ** [[SQLITE_SCANSTAT_EST]]
    SQLITE_SCANSTAT_EST
    -**
    ^The "double" variable pointed to by the T parameter will be set to the +**
    ^The "double" variable pointed to by the V parameter will be set to the ** query planner's estimate for the average number of rows output from each -** iteration of the X-th loop. If the query planner's estimates was accurate, +** iteration of the X-th loop. If the query planner's estimate was accurate, ** then this value will approximate the quotient NVISIT/NLOOP and the ** product of this value for all prior loops with the same SELECTID will -** be the NLOOP value for the current loop. +** be the NLOOP value for the current loop.
    ** ** [[SQLITE_SCANSTAT_NAME]]
    SQLITE_SCANSTAT_NAME
    -**
    ^The "const char *" variable pointed to by the T parameter will be set +**
    ^The "const char *" variable pointed to by the V parameter will be set ** to a zero-terminated UTF-8 string containing the name of the index or table -** used for the X-th loop. +** used for the X-th loop.
    ** ** [[SQLITE_SCANSTAT_EXPLAIN]]
    SQLITE_SCANSTAT_EXPLAIN
    -**
    ^The "const char *" variable pointed to by the T parameter will be set +**
    ^The "const char *" variable pointed to by the V parameter will be set ** to a zero-terminated UTF-8 string containing the [EXPLAIN QUERY PLAN] -** description for the X-th loop. -** -** [[SQLITE_SCANSTAT_SELECTID]]
    SQLITE_SCANSTAT_SELECT
    -**
    ^The "int" variable pointed to by the T parameter will be set to the -** "select-id" for the X-th loop. The select-id identifies which query or -** subquery the loop is part of. The main query has a select-id of zero. -** The select-id is the same value as is output in the first column -** of an [EXPLAIN QUERY PLAN] query. +** description for the X-th loop.
    +** +** [[SQLITE_SCANSTAT_SELECTID]]
    SQLITE_SCANSTAT_SELECTID
    +**
    ^The "int" variable pointed to by the V parameter will be set to the +** id for the X-th query plan element. The id value is unique within the +** statement. The select-id is the same value as is output in the first +** column of an [EXPLAIN QUERY PLAN] query.
    +** +** [[SQLITE_SCANSTAT_PARENTID]]
    SQLITE_SCANSTAT_PARENTID
    +**
    The "int" variable pointed to by the V parameter will be set to the +** id of the parent of the current query element, if applicable, or +** to zero if the query element has no parent. This is the same value as +** returned in the second column of an [EXPLAIN QUERY PLAN] query.
    +** +** [[SQLITE_SCANSTAT_NCYCLE]]
    SQLITE_SCANSTAT_NCYCLE
    +**
    The sqlite3_int64 output value is set to the number of cycles, +** according to the processor time-stamp counter, that elapsed while the +** query element was being processed. This value is not available for +** all query elements - if it is unavailable the output variable is +** set to -1.
    **
    */ -#define SQLITE_SCANSTAT_NLOOP 0 -#define SQLITE_SCANSTAT_NVISIT 1 -#define SQLITE_SCANSTAT_EST 2 -#define SQLITE_SCANSTAT_NAME 3 -#define SQLITE_SCANSTAT_EXPLAIN 4 +#define SQLITE_SCANSTAT_NLOOP 0 +#define SQLITE_SCANSTAT_NVISIT 1 +#define SQLITE_SCANSTAT_EST 2 +#define SQLITE_SCANSTAT_NAME 3 +#define SQLITE_SCANSTAT_EXPLAIN 4 #define SQLITE_SCANSTAT_SELECTID 5 +#define SQLITE_SCANSTAT_PARENTID 6 +#define SQLITE_SCANSTAT_NCYCLE 7 - /* +/* ** CAPI3REF: Prepared Statement Scan Status ** METHOD: sqlite3_stmt ** -** This interface returns information about the predicted and measured +** These interfaces return information about the predicted and measured ** performance for pStmt. Advanced applications can use this ** interface to compare the predicted and the measured performance and ** issue warnings and/or rerun [ANALYZE] if discrepancies are found. @@ -8881,30 +10713,50 @@ typedef unsigned long long int sqlite_uint64; ** ** The "iScanStatusOp" parameter determines which status information to return. ** The "iScanStatusOp" must be one of the [scanstatus options] or the behavior -** of this interface is undefined. -** ^The requested measurement is written into a variable pointed to by -** the "pOut" parameter. -** Parameter "idx" identifies the specific loop to retrieve statistics for. -** Loops are numbered starting from zero. ^If idx is out of range - less than -** zero or greater than or equal to the total number of loops used to implement -** the statement - a non-zero value is returned and the variable that pOut -** points to is unchanged. -** -** ^Statistics might not be available for all loops in all statements. ^In cases -** where there exist loops with no available statistics, this function behaves -** as if the loop did not exist - it returns non-zero and leave the variable -** that pOut points to unchanged. -** -** See also: [sqlite3_stmt_scanstatus_reset()] -*/ - SQLITE_API int sqlite3_stmt_scanstatus( - sqlite3_stmt* pStmt, /* Prepared statement for which info desired */ - int idx, /* Index of loop to report on */ - int iScanStatusOp, /* Information desired. SQLITE_SCANSTAT_* */ - void* pOut /* Result written here */ - ); - - /* +** of this interface is undefined. ^The requested measurement is written into +** a variable pointed to by the "pOut" parameter. +** +** The "flags" parameter must be passed a mask of flags. At present only +** one flag is defined - [SQLITE_SCANSTAT_COMPLEX]. If SQLITE_SCANSTAT_COMPLEX +** is specified, then status information is available for all elements +** of a query plan that are reported by "[EXPLAIN QUERY PLAN]" output. If +** SQLITE_SCANSTAT_COMPLEX is not specified, then only query plan elements +** that correspond to query loops (the "SCAN..." and "SEARCH..." elements of +** the EXPLAIN QUERY PLAN output) are available. Invoking API +** sqlite3_stmt_scanstatus() is equivalent to calling +** sqlite3_stmt_scanstatus_v2() with a zeroed flags parameter. +** +** Parameter "idx" identifies the specific query element to retrieve statistics +** for. Query elements are numbered starting from zero. A value of -1 may +** retrieve statistics for the entire query. ^If idx is out of range +** - less than -1 or greater than or equal to the total number of query +** elements used to implement the statement - a non-zero value is returned and +** the variable that pOut points to is unchanged. +** +** See also: [sqlite3_stmt_scanstatus_reset()] and the +** [nexec and ncycle] columns of the [bytecode virtual table]. +*/ +SQLITE_API int sqlite3_stmt_scanstatus( + sqlite3_stmt *pStmt, /* Prepared statement for which info desired */ + int idx, /* Index of loop to report on */ + int iScanStatusOp, /* Information desired. SQLITE_SCANSTAT_* */ + void *pOut /* Result written here */ +); +SQLITE_API int sqlite3_stmt_scanstatus_v2( + sqlite3_stmt *pStmt, /* Prepared statement for which info desired */ + int idx, /* Index of loop to report on */ + int iScanStatusOp, /* Information desired. SQLITE_SCANSTAT_* */ + int flags, /* Mask of flags defined below */ + void *pOut /* Result written here */ +); + +/* +** CAPI3REF: Prepared Statement Scan Status +** KEYWORDS: {scan status flags} +*/ +#define SQLITE_SCANSTAT_COMPLEX 0x0001 + +/* ** CAPI3REF: Zero Scan-Status Counters ** METHOD: sqlite3_stmt ** @@ -8913,22 +10765,23 @@ typedef unsigned long long int sqlite_uint64; ** This API is only available if the library is built with pre-processor ** symbol [SQLITE_ENABLE_STMT_SCANSTATUS] defined. */ - SQLITE_API void sqlite3_stmt_scanstatus_reset(sqlite3_stmt*); +SQLITE_API void sqlite3_stmt_scanstatus_reset(sqlite3_stmt*); - /* +/* ** CAPI3REF: Flush caches to disk mid-transaction +** METHOD: sqlite3 ** ** ^If a write-transaction is open on [database connection] D when the -** [sqlite3_db_cacheflush(D)] interface invoked, any dirty -** pages in the pager-cache that are not currently in use are written out +** [sqlite3_db_cacheflush(D)] interface is invoked, any dirty +** pages in the pager-cache that are not currently in use are written out ** to disk. A dirty page may be in use if a database cursor created by an ** active SQL statement is reading from it, or if it is page 1 of a database ** file (page 1 is always "in use"). ^The [sqlite3_db_cacheflush(D)] ** interface flushes caches for all schemas - "main", "temp", and ** any [attached] databases. ** -** ^If this function needs to obtain extra database locks before dirty pages -** can be flushed to disk, it does so. ^If those locks cannot be obtained +** ^If this function needs to obtain extra database locks before dirty pages +** can be flushed to disk, it does so. ^If those locks cannot be obtained ** immediately and there is a busy-handler callback configured, it is invoked ** in the usual manner. ^If the required lock still cannot be obtained, then ** the database is skipped and an attempt made to flush any dirty pages @@ -8945,10 +10798,11 @@ typedef unsigned long long int sqlite_uint64; ** ^This function does not set the database handle error code or message ** returned by the [sqlite3_errcode()] and [sqlite3_errmsg()] functions. */ - SQLITE_API int sqlite3_db_cacheflush(sqlite3*); +SQLITE_API int sqlite3_db_cacheflush(sqlite3*); /* ** CAPI3REF: The pre-update hook. +** METHOD: sqlite3 ** ** ^These interfaces are only available if SQLite is compiled using the ** [SQLITE_ENABLE_PREUPDATE_HOOK] compile-time option. @@ -8966,7 +10820,7 @@ typedef unsigned long long int sqlite_uint64; ** ** ^The preupdate hook only fires for changes to real database tables; the ** preupdate hook is not invoked for changes to [virtual tables] or to -** system tables like sqlite_master or sqlite_stat1. +** system tables like sqlite_sequence or sqlite_stat1. ** ** ^The second parameter to the preupdate callback is a pointer to ** the [database connection] that registered the preupdate hook. @@ -8975,21 +10829,25 @@ typedef unsigned long long int sqlite_uint64; ** kind of update operation that is about to occur. ** ^(The fourth parameter to the preupdate callback is the name of the ** database within the database connection that is being modified. This -** will be "main" for the main database or "temp" for TEMP tables or +** will be "main" for the main database or "temp" for TEMP tables or ** the name given after the AS keyword in the [ATTACH] statement for attached ** databases.)^ ** ^The fifth parameter to the preupdate callback is the name of the ** table that is being modified. ** ** For an UPDATE or DELETE operation on a [rowid table], the sixth -** parameter passed to the preupdate callback is the initial [rowid] of the +** parameter passed to the preupdate callback is the initial [rowid] of the ** row being modified or deleted. For an INSERT operation on a rowid table, -** or any operation on a WITHOUT ROWID table, the value of the sixth +** or any operation on a WITHOUT ROWID table, the value of the sixth ** parameter is undefined. For an INSERT or UPDATE on a rowid table the ** seventh parameter is the final rowid value of the row being inserted ** or updated. The value of the seventh parameter passed to the callback ** function is not defined for operations on WITHOUT ROWID tables, or for -** INSERT operations on rowid tables. +** DELETE operations on rowid tables. +** +** ^The sqlite3_preupdate_hook(D,C,P) function returns the P argument from +** the previous call on the same [database connection] D, or NULL for +** the first call on D. ** ** The [sqlite3_preupdate_old()], [sqlite3_preupdate_new()], ** [sqlite3_preupdate_count()], and [sqlite3_preupdate_depth()] interfaces @@ -9023,44 +10881,56 @@ typedef unsigned long long int sqlite_uint64; ** ** ^The [sqlite3_preupdate_depth(D)] interface returns 0 if the preupdate ** callback was invoked as a result of a direct insert, update, or delete -** operation; or 1 for inserts, updates, or deletes invoked by top-level +** operation; or 1 for inserts, updates, or deletes invoked by top-level ** triggers; or 2 for changes resulting from triggers called by top-level ** triggers; and so forth. ** +** When the [sqlite3_blob_write()] API is used to update a blob column, +** the pre-update hook is invoked with SQLITE_DELETE, because +** the new values are not yet available. In this case, when a +** callback made with op==SQLITE_DELETE is actually a write using the +** sqlite3_blob_write() API, the [sqlite3_preupdate_blobwrite()] returns +** the index of the column being written. In other cases, where the +** pre-update hook is being invoked for some other reason, including a +** regular DELETE, sqlite3_preupdate_blobwrite() returns -1. +** ** See also: [sqlite3_update_hook()] */ #if defined(SQLITE_ENABLE_PREUPDATE_HOOK) - SQLITE_API void* sqlite3_preupdate_hook( - sqlite3* db, - void (*xPreUpdate)( - void* pCtx, /* Copy of third arg to preupdate_hook() */ - sqlite3* db, /* Database handle */ - int op, /* SQLITE_UPDATE, DELETE or INSERT */ - char const* zDb, /* Database name */ - char const* zName, /* Table name */ - sqlite3_int64 iKey1, /* Rowid of row about to be deleted/updated */ - sqlite3_int64 iKey2 /* New rowid value (for a rowid UPDATE) */ - ), - void*); - SQLITE_API int sqlite3_preupdate_old(sqlite3*, int, sqlite3_value**); - SQLITE_API int sqlite3_preupdate_count(sqlite3*); - SQLITE_API int sqlite3_preupdate_depth(sqlite3*); - SQLITE_API int sqlite3_preupdate_new(sqlite3*, int, sqlite3_value**); +SQLITE_API void *sqlite3_preupdate_hook( + sqlite3 *db, + void(*xPreUpdate)( + void *pCtx, /* Copy of third arg to preupdate_hook() */ + sqlite3 *db, /* Database handle */ + int op, /* SQLITE_UPDATE, DELETE or INSERT */ + char const *zDb, /* Database name */ + char const *zName, /* Table name */ + sqlite3_int64 iKey1, /* Rowid of row about to be deleted/updated */ + sqlite3_int64 iKey2 /* New rowid value (for a rowid UPDATE) */ + ), + void* +); +SQLITE_API int sqlite3_preupdate_old(sqlite3 *, int, sqlite3_value **); +SQLITE_API int sqlite3_preupdate_count(sqlite3 *); +SQLITE_API int sqlite3_preupdate_depth(sqlite3 *); +SQLITE_API int sqlite3_preupdate_new(sqlite3 *, int, sqlite3_value **); +SQLITE_API int sqlite3_preupdate_blobwrite(sqlite3 *); #endif - /* +/* ** CAPI3REF: Low-level system error code +** METHOD: sqlite3 ** ** ^Attempt to return the underlying operating system error code or error ** number that caused the most recent I/O error or failure to open a file. ** The return value is OS-dependent. For example, on unix systems, after ** [sqlite3_open_v2()] returns [SQLITE_CANTOPEN], this interface could be ** called to get back the underlying "errno" that caused the problem, such -** as ENOSPC, EAUTH, EISDIR, and so forth. +** as ENOSPC, EAUTH, EISDIR, and so forth. */ - SQLITE_API int sqlite3_system_errno(sqlite3*); +SQLITE_API int sqlite3_system_errno(sqlite3*); - /* +/* ** CAPI3REF: Database Snapshot ** KEYWORDS: {snapshot} {sqlite3_snapshot} ** @@ -9080,12 +10950,11 @@ typedef unsigned long long int sqlite_uint64; ** transaction that sees that historical version of the database rather than ** the most recent version. */ - typedef struct sqlite3_snapshot - { - unsigned char hidden[48]; - } sqlite3_snapshot; +typedef struct sqlite3_snapshot { + unsigned char hidden[48]; +} sqlite3_snapshot; - /* +/* ** CAPI3REF: Record A Database Snapshot ** CONSTRUCTOR: sqlite3_snapshot ** @@ -9095,12 +10964,20 @@ typedef unsigned long long int sqlite_uint64; ** [sqlite3_snapshot_get(D,S,P)] interface writes a pointer to the newly ** created [sqlite3_snapshot] object into *P and returns SQLITE_OK. ** If there is not already a read-transaction open on schema S when -** this function is called, one is opened automatically. +** this function is called, one is opened automatically. +** +** If a read-transaction is opened by this function, then it is guaranteed +** that the returned snapshot object may not be invalidated by a database +** writer or checkpointer until after the read-transaction is closed. This +** is not guaranteed if a read-transaction is already open when this +** function is called. In that case, any subsequent write or checkpoint +** operation on the database may invalidate the returned snapshot handle, +** even while the read-transaction remains open. ** ** The following must be true for this function to succeed. If any of ** the following statements are false when sqlite3_snapshot_get() is ** called, SQLITE_ERROR is returned. The final value of *P is undefined -** in this case. +** in this case. ** **
      **
    • The database handle must not be in [autocommit mode]. @@ -9112,13 +10989,13 @@ typedef unsigned long long int sqlite_uint64; ** **
    • One or more transactions must have been written to the current wal ** file since it was created on disk (by any connection). This means -** that a snapshot cannot be taken on a wal mode database with no wal +** that a snapshot cannot be taken on a wal mode database with no wal ** file immediately after it is first opened. At least one transaction ** must be written to it first. **
    ** ** This function may also return SQLITE_NOMEM. If it is called with the -** database handle in autocommit mode but fails for some other reason, +** database handle in autocommit mode but fails for some other reason, ** whether or not a read transaction is opened on schema S is undefined. ** ** The [sqlite3_snapshot] object returned from a successful call to @@ -9128,47 +11005,48 @@ typedef unsigned long long int sqlite_uint64; ** The [sqlite3_snapshot_get()] interface is only available when the ** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used. */ - SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_get( - sqlite3* db, - const char* zSchema, - sqlite3_snapshot** ppSnapshot); +SQLITE_API int sqlite3_snapshot_get( + sqlite3 *db, + const char *zSchema, + sqlite3_snapshot **ppSnapshot +); - /* +/* ** CAPI3REF: Start a read transaction on an historical snapshot ** METHOD: sqlite3_snapshot ** -** ^The [sqlite3_snapshot_open(D,S,P)] interface either starts a new read -** transaction or upgrades an existing one for schema S of -** [database connection] D such that the read transaction refers to -** historical [snapshot] P, rather than the most recent change to the -** database. ^The [sqlite3_snapshot_open()] interface returns SQLITE_OK +** ^The [sqlite3_snapshot_open(D,S,P)] interface either starts a new read +** transaction or upgrades an existing one for schema S of +** [database connection] D such that the read transaction refers to +** historical [snapshot] P, rather than the most recent change to the +** database. ^The [sqlite3_snapshot_open()] interface returns SQLITE_OK ** on success or an appropriate [error code] if it fails. ** -** ^In order to succeed, the database connection must not be in +** ^In order to succeed, the database connection must not be in ** [autocommit mode] when [sqlite3_snapshot_open(D,S,P)] is called. If there ** is already a read transaction open on schema S, then the database handle ** must have no active statements (SELECT statements that have been passed -** to sqlite3_step() but not sqlite3_reset() or sqlite3_finalize()). +** to sqlite3_step() but not sqlite3_reset() or sqlite3_finalize()). ** SQLITE_ERROR is returned if either of these conditions is violated, or ** if schema S does not exist, or if the snapshot object is invalid. ** ** ^A call to sqlite3_snapshot_open() will fail to open if the specified -** snapshot has been overwritten by a [checkpoint]. In this case +** snapshot has been overwritten by a [checkpoint]. In this case ** SQLITE_ERROR_SNAPSHOT is returned. ** -** If there is already a read transaction open when this function is +** If there is already a read transaction open when this function is ** invoked, then the same read transaction remains open (on the same ** database snapshot) if SQLITE_ERROR, SQLITE_BUSY or SQLITE_ERROR_SNAPSHOT ** is returned. If another error code - for example SQLITE_PROTOCOL or an ** SQLITE_IOERR error code - is returned, then the final state of the -** read transaction is undefined. If SQLITE_OK is returned, then the +** read transaction is undefined. If SQLITE_OK is returned, then the ** read transaction is now open on database snapshot P. ** ** ^(A call to [sqlite3_snapshot_open(D,S,P)] will fail if the ** database connection D does not know that the database file for ** schema S is in [WAL mode]. A database connection might not know ** that the database file is in [WAL mode] if there has been no prior -** I/O on that database connection, or if the database entered [WAL mode] +** I/O on that database connection, or if the database entered [WAL mode] ** after the most recent I/O on the database connection.)^ ** (Hint: Run "[PRAGMA application_id]" against a newly opened ** database connection in order to make it ready to use snapshots.) @@ -9176,12 +11054,13 @@ typedef unsigned long long int sqlite_uint64; ** The [sqlite3_snapshot_open()] interface is only available when the ** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used. */ - SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_open( - sqlite3* db, - const char* zSchema, - sqlite3_snapshot* pSnapshot); +SQLITE_API int sqlite3_snapshot_open( + sqlite3 *db, + const char *zSchema, + sqlite3_snapshot *pSnapshot +); - /* +/* ** CAPI3REF: Destroy a snapshot ** DESTRUCTOR: sqlite3_snapshot ** @@ -9192,24 +11071,24 @@ typedef unsigned long long int sqlite_uint64; ** The [sqlite3_snapshot_free()] interface is only available when the ** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used. */ - SQLITE_API SQLITE_EXPERIMENTAL void sqlite3_snapshot_free(sqlite3_snapshot*); +SQLITE_API void sqlite3_snapshot_free(sqlite3_snapshot*); - /* +/* ** CAPI3REF: Compare the ages of two snapshot handles. ** METHOD: sqlite3_snapshot ** ** The sqlite3_snapshot_cmp(P1, P2) interface is used to compare the ages -** of two valid snapshot handles. +** of two valid snapshot handles. ** -** If the two snapshot handles are not associated with the same database -** file, the result of the comparison is undefined. +** If the two snapshot handles are not associated with the same database +** file, the result of the comparison is undefined. ** ** Additionally, the result of the comparison is only valid if both of the ** snapshot handles were obtained by calling sqlite3_snapshot_get() since the ** last time the wal file was deleted. The wal file is deleted when the ** database is changed back to rollback mode or when the number of database -** clients drops to zero. If either snapshot handle was obtained before the -** wal file was last deleted, the value returned by this function +** clients drops to zero. If either snapshot handle was obtained before the +** wal file was last deleted, the value returned by this function ** is undefined. ** ** Otherwise, this API returns a negative value if P1 refers to an older @@ -9219,11 +11098,12 @@ typedef unsigned long long int sqlite_uint64; ** This interface is only available if SQLite is compiled with the ** [SQLITE_ENABLE_SNAPSHOT] option. */ - SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_cmp( - sqlite3_snapshot* p1, - sqlite3_snapshot* p2); +SQLITE_API int sqlite3_snapshot_cmp( + sqlite3_snapshot *p1, + sqlite3_snapshot *p2 +); - /* +/* ** CAPI3REF: Recover snapshots from a wal file ** METHOD: sqlite3_snapshot ** @@ -9246,20 +11126,21 @@ typedef unsigned long long int sqlite_uint64; ** This interface is only available if SQLite is compiled with the ** [SQLITE_ENABLE_SNAPSHOT] option. */ - SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3* db, const char* zDb); +SQLITE_API int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb); - /* +/* ** CAPI3REF: Serialize a database ** -** The sqlite3_serialize(D,S,P,F) interface returns a pointer to memory -** that is a serialization of the S database on [database connection] D. +** The sqlite3_serialize(D,S,P,F) interface returns a pointer to +** memory that is a serialization of the S database on +** [database connection] D. If S is a NULL pointer, the main database is used. ** If P is not a NULL pointer, then the size of the database in bytes ** is written into *P. ** ** For an ordinary on-disk database file, the serialization is just a ** copy of the disk file. For an in-memory database or a "TEMP" database, ** the serialization is the same sequence of bytes which would be written -** to disk if that database where backed up to disk. +** to disk if that database were backed up to disk. ** ** The usual case is that sqlite3_serialize() copies the serialization of ** the database into memory obtained from [sqlite3_malloc64()] and returns @@ -9268,28 +11149,35 @@ typedef unsigned long long int sqlite_uint64; ** contains the SQLITE_SERIALIZE_NOCOPY bit, then no memory allocations ** are made, and the sqlite3_serialize() function will return a pointer ** to the contiguous memory representation of the database that SQLite -** is currently using for that database, or NULL if the no such contiguous +** is currently using for that database, or NULL if no such contiguous ** memory representation of the database exists. A contiguous memory ** representation of the database will usually only exist if there has ** been a prior call to [sqlite3_deserialize(D,S,...)] with the same ** values of D and S. -** The size of the database is written into *P even if the +** The size of the database is written into *P even if the ** SQLITE_SERIALIZE_NOCOPY bit is set but no contiguous copy ** of the database exists. ** +** After the call, if the SQLITE_SERIALIZE_NOCOPY bit had been set, +** the returned buffer content will remain accessible and unchanged +** until either the next write operation on the connection or when +** the connection is closed, and applications must not modify the +** buffer. If the bit had been clear, the returned buffer will not +** be accessed by SQLite after the call. +** ** A call to sqlite3_serialize(D,S,P,F) might return NULL even if the ** SQLITE_SERIALIZE_NOCOPY bit is omitted from argument F if a memory ** allocation error occurs. ** -** This interface is only available if SQLite is compiled with the -** [SQLITE_ENABLE_DESERIALIZE] option. +** This interface is omitted if SQLite is compiled with the +** [SQLITE_OMIT_DESERIALIZE] option. */ - SQLITE_API unsigned char* sqlite3_serialize( - sqlite3* db, /* The database connection */ - const char* zSchema, /* Which DB to serialize. ex: "main", "temp", ... */ - sqlite3_int64* piSize, /* Write size of the DB here, if not NULL */ - unsigned int mFlags /* Zero or more SQLITE_SERIALIZE_* flags */ - ); +SQLITE_API unsigned char *sqlite3_serialize( + sqlite3 *db, /* The database connection */ + const char *zSchema, /* Which DB to serialize. ex: "main", "temp", ... */ + sqlite3_int64 *piSize, /* Write size of the DB here, if not NULL */ + unsigned int mFlags /* Zero or more SQLITE_SERIALIZE_* flags */ +); /* ** CAPI3REF: Flags for sqlite3_serialize @@ -9305,19 +11193,20 @@ typedef unsigned long long int sqlite_uint64; ** using a contiguous in-memory database if it has been initialized by a ** prior call to [sqlite3_deserialize()]. */ -#define SQLITE_SERIALIZE_NOCOPY 0x001 /* Do no memory allocations */ +#define SQLITE_SERIALIZE_NOCOPY 0x001 /* Do no memory allocations */ - /* +/* ** CAPI3REF: Deserialize a database ** -** The sqlite3_deserialize(D,S,P,N,M,F) interface causes the +** The sqlite3_deserialize(D,S,P,N,M,F) interface causes the ** [database connection] D to disconnect from database S and then -** reopen S as an in-memory database based on the serialization contained -** in P. The serialized database P is N bytes in size. M is the size of -** the buffer P, which might be larger than N. If M is larger than N, and -** the SQLITE_DESERIALIZE_READONLY bit is not set in F, then SQLite is -** permitted to add content to the in-memory database as long as the total -** size does not exceed M bytes. +** reopen S as an in-memory database based on the serialization +** contained in P. If S is a NULL pointer, the main database is +** used. The serialized database P is N bytes in size. M is the size +** of the buffer P, which might be larger than N. If M is larger than +** N, and the SQLITE_DESERIALIZE_READONLY bit is not set in F, then +** SQLite is permitted to add content to the in-memory database as +** long as the total size does not exceed M bytes. ** ** If the SQLITE_DESERIALIZE_FREEONCLOSE bit is set in F, then SQLite will ** invoke sqlite3_free() on the serialization buffer when the database @@ -9325,30 +11214,44 @@ typedef unsigned long long int sqlite_uint64; ** SQLite will try to increase the buffer size using sqlite3_realloc64() ** if writes on the database cause it to grow larger than M bytes. ** +** Applications must not modify the buffer P or invalidate it before +** the database connection D is closed. +** ** The sqlite3_deserialize() interface will fail with SQLITE_BUSY if the ** database is currently in a read transaction or is involved in a backup ** operation. ** -** If sqlite3_deserialize(D,S,P,N,M,F) fails for any reason and if the +** It is not possible to deserialize into the TEMP database. If the +** S argument to sqlite3_deserialize(D,S,P,N,M,F) is "temp" then the +** function returns SQLITE_ERROR. +** +** The deserialized database should not be in [WAL mode]. If the database +** is in WAL mode, then any attempt to use the database file will result +** in an [SQLITE_CANTOPEN] error. The application can set the +** [file format version numbers] (bytes 18 and 19) of the input database P +** to 0x01 prior to invoking sqlite3_deserialize(D,S,P,N,M,F) to force the +** database file into rollback mode and work around this limitation. +** +** If sqlite3_deserialize(D,S,P,N,M,F) fails for any reason and if the ** SQLITE_DESERIALIZE_FREEONCLOSE bit is set in argument F, then ** [sqlite3_free()] is invoked on argument P prior to returning. ** -** This interface is only available if SQLite is compiled with the -** [SQLITE_ENABLE_DESERIALIZE] option. +** This interface is omitted if SQLite is compiled with the +** [SQLITE_OMIT_DESERIALIZE] option. */ - SQLITE_API int sqlite3_deserialize( - sqlite3* db, /* The database connection */ - const char* zSchema, /* Which DB to reopen with the deserialization */ - unsigned char* pData, /* The serialized database content */ - sqlite3_int64 szDb, /* Number bytes in the deserialization */ - sqlite3_int64 szBuf, /* Total size of buffer pData[] */ - unsigned mFlags /* Zero or more SQLITE_DESERIALIZE_* flags */ - ); +SQLITE_API int sqlite3_deserialize( + sqlite3 *db, /* The database connection */ + const char *zSchema, /* Which DB to reopen with the deserialization */ + unsigned char *pData, /* The serialized database content */ + sqlite3_int64 szDb, /* Number of bytes in the deserialization */ + sqlite3_int64 szBuf, /* Total size of buffer pData[] */ + unsigned mFlags /* Zero or more SQLITE_DESERIALIZE_* flags */ +); /* ** CAPI3REF: Flags for sqlite3_deserialize() ** -** The following are allowed values for 6th argument (the F argument) to +** The following are allowed values for the 6th argument (the F argument) to ** the [sqlite3_deserialize(D,S,P,N,M,F)] interface. ** ** The SQLITE_DESERIALIZE_FREEONCLOSE means that the database serialization @@ -9367,21 +11270,103 @@ typedef unsigned long long int sqlite_uint64; ** should be treated as read-only. */ #define SQLITE_DESERIALIZE_FREEONCLOSE 1 /* Call sqlite3_free() on close */ -#define SQLITE_DESERIALIZE_RESIZEABLE 2 /* Resize using sqlite3_realloc64() */ -#define SQLITE_DESERIALIZE_READONLY 4 /* Database is read-only */ +#define SQLITE_DESERIALIZE_RESIZEABLE 2 /* Resize using sqlite3_realloc64() */ +#define SQLITE_DESERIALIZE_READONLY 4 /* Database is read-only */ + +/* +** CAPI3REF: Bind array values to the CARRAY table-valued function +** +** The sqlite3_carray_bind_v2(S,I,P,N,F,X,D) interface binds an array value to +** parameter that is the first argument of the [carray() table-valued function]. +** The S parameter is a pointer to the [prepared statement] that uses the +** carray() functions. I is the parameter index to be bound. I must be the +** index of the parameter that is the first argument to the carray() +** table-valued function. P is a pointer to the array to be bound, and N +** is the number of elements in the array. The F argument is one of +** constants [SQLITE_CARRAY_INT32], [SQLITE_CARRAY_INT64], +** [SQLITE_CARRAY_DOUBLE], [SQLITE_CARRAY_TEXT], +** or [SQLITE_CARRAY_BLOB] to indicate the datatype of the array P. +** +** If the X argument is not a NULL pointer or one of the special +** values [SQLITE_STATIC] or [SQLITE_TRANSIENT], then SQLite will invoke +** the function X with argument D when it is finished using the data in P. +** The call to X(D) is a destructor for the array P. The destructor X(D) +** is invoked even if the call to sqlite3_carray_bind_v2() fails. If the X +** parameter is the special-case value [SQLITE_STATIC], then SQLite assumes +** that the data static and the destructor is never invoked. If the X +** parameter is the special-case value [SQLITE_TRANSIENT], then +** sqlite3_carray_bind_v2() makes its own private copy of the data prior +** to returning and never invokes the destructor X. +** +** The sqlite3_carray_bind() function works the same as sqlite3_carray_bind_v2() +** with a D parameter set to P. In other words, +** sqlite3_carray_bind(S,I,P,N,F,X) is same as +** sqlite3_carray_bind_v2(S,I,P,N,F,X,P). +*/ +SQLITE_API int sqlite3_carray_bind_v2( + sqlite3_stmt *pStmt, /* Statement to be bound */ + int i, /* Parameter index */ + void *aData, /* Pointer to array data */ + int nData, /* Number of data elements */ + int mFlags, /* CARRAY flags */ + void (*xDel)(void*), /* Destructor for aData */ + void *pDel /* Optional argument to xDel() */ +); +SQLITE_API int sqlite3_carray_bind( + sqlite3_stmt *pStmt, /* Statement to be bound */ + int i, /* Parameter index */ + void *aData, /* Pointer to array data */ + int nData, /* Number of data elements */ + int mFlags, /* CARRAY flags */ + void (*xDel)(void*) /* Destructor for aData */ +); + +/* +** CAPI3REF: Datatypes for the CARRAY table-valued function +** +** The fifth argument to the [sqlite3_carray_bind()] interface musts be +** one of the following constants, to specify the datatype of the array +** that is being bound into the [carray table-valued function]. +*/ +#define SQLITE_CARRAY_INT32 0 /* Data is 32-bit signed integers */ +#define SQLITE_CARRAY_INT64 1 /* Data is 64-bit signed integers */ +#define SQLITE_CARRAY_DOUBLE 2 /* Data is doubles */ +#define SQLITE_CARRAY_TEXT 3 /* Data is char* */ +#define SQLITE_CARRAY_BLOB 4 /* Data is struct iovec */ + +/* +** Versions of the above #defines that omit the initial SQLITE_, for +** legacy compatibility. +*/ +#define CARRAY_INT32 0 /* Data is 32-bit signed integers */ +#define CARRAY_INT64 1 /* Data is 64-bit signed integers */ +#define CARRAY_DOUBLE 2 /* Data is doubles */ +#define CARRAY_TEXT 3 /* Data is char* */ +#define CARRAY_BLOB 4 /* Data is struct iovec */ /* ** Undo the hack that converts floating point types to integer for ** builds on processors without floating point support. */ #ifdef SQLITE_OMIT_FLOATING_POINT -#undef double +# undef double +#endif + +#if defined(__wasi__) +# undef SQLITE_WASI +# define SQLITE_WASI 1 +# ifndef SQLITE_OMIT_LOAD_EXTENSION +# define SQLITE_OMIT_LOAD_EXTENSION +# endif +# ifndef SQLITE_THREADSAFE +# define SQLITE_THREADSAFE 0 +# endif #endif #ifdef __cplusplus -} /* End of the 'extern "C"' block */ +} /* End of the 'extern "C"' block */ #endif -#endif /* SQLITE3_H */ +/* #endif for SQLITE3_H will be added by mksqlite3.tcl */ /******** Begin file sqlite3rtree.h *********/ /* @@ -9400,63 +11385,66 @@ typedef unsigned long long int sqlite_uint64; #ifndef _SQLITE3RTREE_H_ #define _SQLITE3RTREE_H_ + #ifdef __cplusplus -extern "C" -{ +extern "C" { #endif - typedef struct sqlite3_rtree_geometry sqlite3_rtree_geometry; - typedef struct sqlite3_rtree_query_info sqlite3_rtree_query_info; +typedef struct sqlite3_rtree_geometry sqlite3_rtree_geometry; +typedef struct sqlite3_rtree_query_info sqlite3_rtree_query_info; /* The double-precision datatype used by RTree depends on the ** SQLITE_RTREE_INT_ONLY compile-time option. */ #ifdef SQLITE_RTREE_INT_ONLY - typedef sqlite3_int64 sqlite3_rtree_dbl; + typedef sqlite3_int64 sqlite3_rtree_dbl; #else -typedef double sqlite3_rtree_dbl; + typedef double sqlite3_rtree_dbl; #endif - /* +/* ** Register a geometry callback named zGeom that can be used as part of an ** R-Tree geometry query as follows: ** ** SELECT ... FROM WHERE MATCH $zGeom(... params ...) */ - SQLITE_API int sqlite3_rtree_geometry_callback( - sqlite3* db, - const char* zGeom, - int (*xGeom)(sqlite3_rtree_geometry*, int, sqlite3_rtree_dbl*, int*), - void* pContext); +SQLITE_API int sqlite3_rtree_geometry_callback( + sqlite3 *db, + const char *zGeom, + int (*xGeom)(sqlite3_rtree_geometry*, int, sqlite3_rtree_dbl*,int*), + void *pContext +); - /* + +/* ** A pointer to a structure of the following type is passed as the first ** argument to callbacks registered using rtree_geometry_callback(). */ - struct sqlite3_rtree_geometry - { - void* pContext; /* Copy of pContext passed to s_r_g_c() */ - int nParam; /* Size of array aParam[] */ - sqlite3_rtree_dbl* aParam; /* Parameters passed to SQL geom function */ - void* pUser; /* Callback implementation user data */ - void (*xDelUser)(void*); /* Called by SQLite to clean up pUser */ - }; - - /* -** Register a 2nd-generation geometry callback named zScore that can be +struct sqlite3_rtree_geometry { + void *pContext; /* Copy of pContext passed to s_r_g_c() */ + int nParam; /* Size of array aParam[] */ + sqlite3_rtree_dbl *aParam; /* Parameters passed to SQL geom function */ + void *pUser; /* Callback implementation user data */ + void (*xDelUser)(void *); /* Called by SQLite to clean up pUser */ +}; + +/* +** Register a 2nd-generation geometry callback named zScore that can be ** used as part of an R-Tree geometry query as follows: ** ** SELECT ... FROM WHERE MATCH $zQueryFunc(... params ...) */ - SQLITE_API int sqlite3_rtree_query_callback( - sqlite3* db, - const char* zQueryFunc, - int (*xQueryFunc)(sqlite3_rtree_query_info*), - void* pContext, - void (*xDestructor)(void*)); +SQLITE_API int sqlite3_rtree_query_callback( + sqlite3 *db, + const char *zQueryFunc, + int (*xQueryFunc)(sqlite3_rtree_query_info*), + void *pContext, + void (*xDestructor)(void*) +); - /* -** A pointer to a structure of the following type is passed as the + +/* +** A pointer to a structure of the following type is passed as the ** argument to scored geometry callback registered using ** sqlite3_rtree_query_callback(). ** @@ -9464,39 +11452,39 @@ typedef double sqlite3_rtree_dbl; ** sqlite3_rtree_geometry. This structure is a subclass of ** sqlite3_rtree_geometry. */ - struct sqlite3_rtree_query_info - { - void* pContext; /* pContext from when function registered */ - int nParam; /* Number of function parameters */ - sqlite3_rtree_dbl* aParam; /* value of function parameters */ - void* pUser; /* callback can use this, if desired */ - void (*xDelUser)(void*); /* function to free pUser */ - sqlite3_rtree_dbl* aCoord; /* Coordinates of node or entry to check */ - unsigned int* anQueue; /* Number of pending entries in the queue */ - int nCoord; /* Number of coordinates */ - int iLevel; /* Level of current node or entry */ - int mxLevel; /* The largest iLevel value in the tree */ - sqlite3_int64 iRowid; /* Rowid for current entry */ - sqlite3_rtree_dbl rParentScore; /* Score of parent node */ - int eParentWithin; /* Visibility of parent node */ - int eWithin; /* OUT: Visibility */ - sqlite3_rtree_dbl rScore; /* OUT: Write the score here */ - /* The following fields are only available in 3.8.11 and later */ - sqlite3_value** apSqlParam; /* Original SQL values of parameters */ - }; +struct sqlite3_rtree_query_info { + void *pContext; /* pContext from when function registered */ + int nParam; /* Number of function parameters */ + sqlite3_rtree_dbl *aParam; /* value of function parameters */ + void *pUser; /* callback can use this, if desired */ + void (*xDelUser)(void*); /* function to free pUser */ + sqlite3_rtree_dbl *aCoord; /* Coordinates of node or entry to check */ + unsigned int *anQueue; /* Number of pending entries in the queue */ + int nCoord; /* Number of coordinates */ + int iLevel; /* Level of current node or entry */ + int mxLevel; /* The largest iLevel value in the tree */ + sqlite3_int64 iRowid; /* Rowid for current entry */ + sqlite3_rtree_dbl rParentScore; /* Score of parent node */ + int eParentWithin; /* Visibility of parent node */ + int eWithin; /* OUT: Visibility */ + sqlite3_rtree_dbl rScore; /* OUT: Write the score here */ + /* The following fields are only available in 3.8.11 and later */ + sqlite3_value **apSqlParam; /* Original SQL values of parameters */ +}; /* ** Allowed values for sqlite3_rtree_query.eWithin and .eParentWithin. */ -#define NOT_WITHIN 0 /* Object completely outside of query region */ -#define PARTLY_WITHIN 1 /* Object partially overlaps query region */ -#define FULLY_WITHIN 2 /* Object fully contained within query region */ +#define NOT_WITHIN 0 /* Object completely outside of query region */ +#define PARTLY_WITHIN 1 /* Object partially overlaps query region */ +#define FULLY_WITHIN 2 /* Object fully contained within query region */ + #ifdef __cplusplus -} /* end of the 'extern "C"' block */ +} /* end of the 'extern "C"' block */ #endif -#endif /* ifndef _SQLITE3RTREE_H_ */ +#endif /* ifndef _SQLITE3RTREE_H_ */ /******** End of sqlite3rtree.h *********/ /******** Begin file sqlite3session.h *********/ @@ -9508,27 +11496,27 @@ typedef double sqlite3_rtree_dbl; ** Make sure we can call this stuff from C++. */ #ifdef __cplusplus -extern "C" -{ +extern "C" { #endif - /* + +/* ** CAPI3REF: Session Object Handle ** ** An instance of this object is a [session] that can be used to ** record changes to a database. */ - typedef struct sqlite3_session sqlite3_session; +typedef struct sqlite3_session sqlite3_session; - /* +/* ** CAPI3REF: Changeset Iterator Handle ** ** An instance of this object acts as a cursor for iterating ** over the elements of a [changeset] or [patchset]. */ - typedef struct sqlite3_changeset_iter sqlite3_changeset_iter; +typedef struct sqlite3_changeset_iter sqlite3_changeset_iter; - /* +/* ** CAPI3REF: Create A New Session Object ** CONSTRUCTOR: sqlite3_session ** @@ -9551,7 +11539,7 @@ extern "C" ** is not possible for an application to register a pre-update hook on a ** database handle that has one or more session objects attached. Nor is ** it possible to create a session object attached to a database handle for -** which a pre-update hook is already defined. The results of attempting +** which a pre-update hook is already defined. The results of attempting ** either of these things are undefined. ** ** The session object will be used to create changesets for tables in @@ -9559,28 +11547,74 @@ extern "C" ** attached database. It is not an error if database zDb is not attached ** to the database when the session object is created. */ - SQLITE_API int sqlite3session_create( - sqlite3* db, /* Database handle */ - const char* zDb, /* Name of db (e.g. "main") */ - sqlite3_session** ppSession /* OUT: New session object */ - ); +SQLITE_API int sqlite3session_create( + sqlite3 *db, /* Database handle */ + const char *zDb, /* Name of db (e.g. "main") */ + sqlite3_session **ppSession /* OUT: New session object */ +); - /* +/* ** CAPI3REF: Delete A Session Object ** DESTRUCTOR: sqlite3_session ** -** Delete a session object previously allocated using +** Delete a session object previously allocated using ** [sqlite3session_create()]. Once a session object has been deleted, the ** results of attempting to use pSession with any other session module ** function are undefined. ** ** Session objects must be deleted before the database handle to which they -** are attached is closed. Refer to the documentation for +** are attached is closed. Refer to the documentation for ** [sqlite3session_create()] for details. */ - SQLITE_API void sqlite3session_delete(sqlite3_session* pSession); +SQLITE_API void sqlite3session_delete(sqlite3_session *pSession); - /* +/* +** CAPI3REF: Configure a Session Object +** METHOD: sqlite3_session +** +** This method is used to configure a session object after it has been +** created. At present the only valid values for the second parameter are +** [SQLITE_SESSION_OBJCONFIG_SIZE] and [SQLITE_SESSION_OBJCONFIG_ROWID]. +** +*/ +SQLITE_API int sqlite3session_object_config(sqlite3_session*, int op, void *pArg); + +/* +** CAPI3REF: Options for sqlite3session_object_config +** +** The following values may passed as the the 2nd parameter to +** sqlite3session_object_config(). +** +**
    SQLITE_SESSION_OBJCONFIG_SIZE
    +** This option is used to set, clear or query the flag that enables +** the [sqlite3session_changeset_size()] API. Because it imposes some +** computational overhead, this API is disabled by default. Argument +** pArg must point to a value of type (int). If the value is initially +** 0, then the sqlite3session_changeset_size() API is disabled. If it +** is greater than 0, then the same API is enabled. Or, if the initial +** value is less than zero, no change is made. In all cases the (int) +** variable is set to 1 if the sqlite3session_changeset_size() API is +** enabled following the current call, or 0 otherwise. +** +** It is an error (SQLITE_MISUSE) to attempt to modify this setting after +** the first table has been attached to the session object. +** +**
    SQLITE_SESSION_OBJCONFIG_ROWID
    +** This option is used to set, clear or query the flag that enables +** collection of data for tables with no explicit PRIMARY KEY. +** +** Normally, tables with no explicit PRIMARY KEY are simply ignored +** by the sessions module. However, if this flag is set, it behaves +** as if such tables have a column "_rowid_ INTEGER PRIMARY KEY" inserted +** as their leftmost columns. +** +** It is an error (SQLITE_MISUSE) to attempt to modify this setting after +** the first table has been attached to the session object. +*/ +#define SQLITE_SESSION_OBJCONFIG_SIZE 1 +#define SQLITE_SESSION_OBJCONFIG_ROWID 2 + +/* ** CAPI3REF: Enable Or Disable A Session Object ** METHOD: sqlite3_session ** @@ -9592,15 +11626,15 @@ extern "C" ** the eventual changesets. ** ** Passing zero to this function disables the session. Passing a value -** greater than zero enables it. Passing a value less than zero is a +** greater than zero enables it. Passing a value less than zero is a ** no-op, and may be used to query the current state of the session. ** -** The return value indicates the final state of the session object: 0 if +** The return value indicates the final state of the session object: 0 if ** the session is disabled, or 1 if it is enabled. */ - SQLITE_API int sqlite3session_enable(sqlite3_session* pSession, int bEnable); +SQLITE_API int sqlite3session_enable(sqlite3_session *pSession, int bEnable); - /* +/* ** CAPI3REF: Set Or Clear the Indirect Change Flag ** METHOD: sqlite3_session ** @@ -9610,7 +11644,7 @@ extern "C" **
      **
    • The session object "indirect" flag is set when the change is ** made, or -**
    • The change is made by an SQL trigger or foreign key action +**
    • The change is made by an SQL trigger or foreign key action ** instead of directly as a result of a users SQL statement. **
    ** @@ -9622,33 +11656,33 @@ extern "C" ** flag. If the second argument passed to this function is zero, then the ** indirect flag is cleared. If it is greater than zero, the indirect flag ** is set. Passing a value less than zero does not modify the current value -** of the indirect flag, and may be used to query the current state of the +** of the indirect flag, and may be used to query the current state of the ** indirect flag for the specified session object. ** -** The return value indicates the final state of the indirect flag: 0 if +** The return value indicates the final state of the indirect flag: 0 if ** it is clear, or 1 if it is set. */ - SQLITE_API int sqlite3session_indirect(sqlite3_session* pSession, int bIndirect); +SQLITE_API int sqlite3session_indirect(sqlite3_session *pSession, int bIndirect); - /* +/* ** CAPI3REF: Attach A Table To A Session Object ** METHOD: sqlite3_session ** ** If argument zTab is not NULL, then it is the name of a table to attach -** to the session object passed as the first argument. All subsequent changes -** made to the table while the session object is enabled will be recorded. See +** to the session object passed as the first argument. All subsequent changes +** made to the table while the session object is enabled will be recorded. See ** documentation for [sqlite3session_changeset()] for further details. ** ** Or, if argument zTab is NULL, then changes are recorded for all tables -** in the database. If additional tables are added to the database (by -** executing "CREATE TABLE" statements) after this call is made, changes for +** in the database. If additional tables are added to the database (by +** executing "CREATE TABLE" statements) after this call is made, changes for ** the new tables are also recorded. ** ** Changes can only be recorded for tables that have a PRIMARY KEY explicitly -** defined as part of their CREATE TABLE statement. It does not matter if the +** defined as part of their CREATE TABLE statement. It does not matter if the ** PRIMARY KEY is an "INTEGER PRIMARY KEY" (rowid alias) or not. The PRIMARY ** KEY may consist of a single column, or may be a composite key. -** +** ** It is not an error if the named table does not exist in the database. Nor ** is it an error if the named table does not have a PRIMARY KEY. However, ** no changes will be recorded in either of these scenarios. @@ -9656,29 +11690,29 @@ extern "C" ** Changes are not recorded for individual rows that have NULL values stored ** in one or more of their PRIMARY KEY columns. ** -** SQLITE_OK is returned if the call completes without error. Or, if an error +** SQLITE_OK is returned if the call completes without error. Or, if an error ** occurs, an SQLite error code (e.g. SQLITE_NOMEM) is returned. ** **

    Special sqlite_stat1 Handling

    ** -** As of SQLite version 3.22.0, the "sqlite_stat1" table is an exception to +** As of SQLite version 3.22.0, the "sqlite_stat1" table is an exception to ** some of the rules above. In SQLite, the schema of sqlite_stat1 is: **
    -**        CREATE TABLE sqlite_stat1(tbl,idx,stat)  
    +**        CREATE TABLE sqlite_stat1(tbl,idx,stat)
     **  
    ** -** Even though sqlite_stat1 does not have a PRIMARY KEY, changes are -** recorded for it as if the PRIMARY KEY is (tbl,idx). Additionally, changes +** Even though sqlite_stat1 does not have a PRIMARY KEY, changes are +** recorded for it as if the PRIMARY KEY is (tbl,idx). Additionally, changes ** are recorded for rows for which (idx IS NULL) is true. However, for such ** rows a zero-length blob (SQL value X'') is stored in the changeset or ** patchset instead of a NULL value. This allows such changesets to be ** manipulated by legacy implementations of sqlite3changeset_invert(), ** concat() and similar. ** -** The sqlite3changeset_apply() function automatically converts the +** The sqlite3changeset_apply() function automatically converts the ** zero-length blob back to a NULL value when updating the sqlite_stat1 ** table. However, if the application calls sqlite3changeset_new(), -** sqlite3changeset_old() or sqlite3changeset_conflict on a changeset +** sqlite3changeset_old() or sqlite3changeset_conflict on a changeset ** iterator directly (including on a changeset iterator passed to a ** conflict-handler callback) then the X'' value is returned. The application ** must translate X'' to NULL itself if required. @@ -9688,37 +11722,37 @@ extern "C" ** sqlite3changeset_apply() function silently ignore any modifications to the ** sqlite_stat1 table that are part of a changeset or patchset. */ - SQLITE_API int sqlite3session_attach( - sqlite3_session* pSession, /* Session object */ - const char* zTab /* Table name */ - ); +SQLITE_API int sqlite3session_attach( + sqlite3_session *pSession, /* Session object */ + const char *zTab /* Table name */ +); - /* +/* ** CAPI3REF: Set a table filter on a Session Object. ** METHOD: sqlite3_session ** -** The second argument (xFilter) is the "filter callback". For changes to rows +** The second argument (xFilter) is the "filter callback". For changes to rows ** in tables that are not attached to the Session object, the filter is called -** to determine whether changes to the table's rows should be tracked or not. -** If xFilter returns 0, changes is not tracked. Note that once a table is +** to determine whether changes to the table's rows should be tracked or not. +** If xFilter returns 0, changes are not tracked. Note that once a table is ** attached, xFilter will not be called again. */ - SQLITE_API void sqlite3session_table_filter( - sqlite3_session* pSession, /* Session object */ - int (*xFilter)( - void* pCtx, /* Copy of third arg to _filter_table() */ - const char* zTab /* Table name */ - ), - void* pCtx /* First argument passed to xFilter */ - ); +SQLITE_API void sqlite3session_table_filter( + sqlite3_session *pSession, /* Session object */ + int(*xFilter)( + void *pCtx, /* Copy of third arg to _filter_table() */ + const char *zTab /* Table name */ + ), + void *pCtx /* First argument passed to xFilter */ +); - /* +/* ** CAPI3REF: Generate A Changeset From A Session Object ** METHOD: sqlite3_session ** -** Obtain a changeset containing changes to the tables attached to the -** session object passed as the first argument. If successful, -** set *ppChangeset to point to a buffer containing the changeset +** Obtain a changeset containing changes to the tables attached to the +** session object passed as the first argument. If successful, +** set *ppChangeset to point to a buffer containing the changeset ** and *pnChangeset to the size of the changeset in bytes before returning ** SQLITE_OK. If an error occurs, set both *ppChangeset and *pnChangeset to ** zero and return an SQLite error code. @@ -9733,7 +11767,7 @@ extern "C" ** modifies the values of primary key columns. If such a change is made, it ** is represented in a changeset as a DELETE followed by an INSERT. ** -** Changes are not recorded for rows that have NULL values stored in one or +** Changes are not recorded for rows that have NULL values stored in one or ** more of their PRIMARY KEY columns. If such a row is inserted or deleted, ** no corresponding change is present in the changesets returned by this ** function. If an existing row with one or more NULL values stored in @@ -9786,14 +11820,14 @@ extern "C" **
      **
    • For each record generated by an insert, the database is queried ** for a row with a matching primary key. If one is found, an INSERT -** change is added to the changeset. If no such row is found, no change +** change is added to the changeset. If no such row is found, no change ** is added to the changeset. ** -**
    • For each record generated by an update or delete, the database is +**
    • For each record generated by an update or delete, the database is ** queried for a row with a matching primary key. If such a row is ** found and one or more of the non-primary key fields have been -** modified from their original values, an UPDATE change is added to -** the changeset. Or, if no such row is found in the table, a DELETE +** modified from their original values, an UPDATE change is added to +** the changeset. Or, if no such row is found in the table, a DELETE ** change is added to the changeset. If there is a row with a matching ** primary key in the database, but all fields contain their original ** values, no change is added to the changeset. @@ -9801,7 +11835,7 @@ extern "C" ** ** This means, amongst other things, that if a row is inserted and then later ** deleted while a session object is active, neither the insert nor the delete -** will be present in the changeset. Or if a row is deleted and then later a +** will be present in the changeset. Or if a row is deleted and then later a ** row with the same primary key values inserted while a session object is ** active, the resulting changeset will contain an UPDATE change instead of ** a DELETE and an INSERT. @@ -9810,20 +11844,37 @@ extern "C" ** it does not accumulate records when rows are inserted, updated or deleted. ** This may appear to have some counter-intuitive effects if a single row ** is written to more than once during a session. For example, if a row -** is inserted while a session object is enabled, then later deleted while +** is inserted while a session object is enabled, then later deleted while ** the same session object is disabled, no INSERT record will appear in the ** changeset, even though the delete took place while the session was disabled. -** Or, if one field of a row is updated while a session is disabled, and -** another field of the same row is updated while the session is enabled, the -** resulting changeset will contain an UPDATE change that updates both fields. +** Or, if one field of a row is updated while a session is enabled, and +** then another field of the same row is updated while the session is disabled, +** the resulting changeset will contain an UPDATE change that updates both +** fields. */ - SQLITE_API int sqlite3session_changeset( - sqlite3_session* pSession, /* Session object */ - int* pnChangeset, /* OUT: Size of buffer at *ppChangeset */ - void** ppChangeset /* OUT: Buffer containing changeset */ - ); +SQLITE_API int sqlite3session_changeset( + sqlite3_session *pSession, /* Session object */ + int *pnChangeset, /* OUT: Size of buffer at *ppChangeset */ + void **ppChangeset /* OUT: Buffer containing changeset */ +); - /* +/* +** CAPI3REF: Return An Upper-limit For The Size Of The Changeset +** METHOD: sqlite3_session +** +** By default, this function always returns 0. For it to return +** a useful result, the sqlite3_session object must have been configured +** to enable this API using sqlite3session_object_config() with the +** SQLITE_SESSION_OBJCONFIG_SIZE verb. +** +** When enabled, this function returns an upper limit, in bytes, for the size +** of the changeset that might be produced if sqlite3session_changeset() were +** called. The final changeset size might be equal to or smaller than the +** size in bytes returned by this function. +*/ +SQLITE_API sqlite3_int64 sqlite3session_changeset_size(sqlite3_session *pSession); + +/* ** CAPI3REF: Load The Difference Between Tables Into A Session ** METHOD: sqlite3_session ** @@ -9834,7 +11885,7 @@ extern "C" ** an error). ** ** Argument zFromDb must be the name of a database ("main", "temp" etc.) -** attached to the same database handle as the session object that contains +** attached to the same database handle as the session object that contains ** a table compatible with the table attached to the session by this function. ** A table is considered compatible if it: ** @@ -9850,62 +11901,65 @@ extern "C" ** APIs, tables without PRIMARY KEYs are simply ignored. ** ** This function adds a set of changes to the session object that could be -** used to update the table in database zFrom (call this the "from-table") -** so that its content is the same as the table attached to the session +** used to update the table in database zFrom (call this the "from-table") +** so that its content is the same as the table attached to the session ** object (call this the "to-table"). Specifically: ** **
        -**
      • For each row (primary key) that exists in the to-table but not in +**
      • For each row (primary key) that exists in the to-table but not in ** the from-table, an INSERT record is added to the session object. ** -**
      • For each row (primary key) that exists in the to-table but not in +**
      • For each row (primary key) that exists in the to-table but not in ** the from-table, a DELETE record is added to the session object. ** -**
      • For each row (primary key) that exists in both tables, but features +**
      • For each row (primary key) that exists in both tables, but features ** different non-PK values in each, an UPDATE record is added to the -** session. +** session. **
      ** ** To clarify, if this function is called and then a changeset constructed -** using [sqlite3session_changeset()], then after applying that changeset to -** database zFrom the contents of the two compatible tables would be +** using [sqlite3session_changeset()], then after applying that changeset to +** database zFrom the contents of the two compatible tables would be ** identical. ** -** It an error if database zFrom does not exist or does not contain the -** required compatible table. +** Unless the call to this function is a no-op as described above, it is an +** error if database zFrom does not exist or does not contain the required +** compatible table. ** -** If the operation successful, SQLITE_OK is returned. Otherwise, an SQLite +** If the operation is successful, SQLITE_OK is returned. Otherwise, an SQLite ** error code. In this case, if argument pzErrMsg is not NULL, *pzErrMsg -** may be set to point to a buffer containing an English language error +** may be set to point to a buffer containing an English language error ** message. It is the responsibility of the caller to free this buffer using ** sqlite3_free(). */ - SQLITE_API int sqlite3session_diff( - sqlite3_session* pSession, - const char* zFromDb, - const char* zTbl, - char** pzErrMsg); +SQLITE_API int sqlite3session_diff( + sqlite3_session *pSession, + const char *zFromDb, + const char *zTbl, + char **pzErrMsg +); + - /* +/* ** CAPI3REF: Generate A Patchset From A Session Object ** METHOD: sqlite3_session ** ** The differences between a patchset and a changeset are that: ** **
        -**
      • DELETE records consist of the primary key fields only. The +**
      • DELETE records consist of the primary key fields only. The ** original values of other fields are omitted. -**
      • The original values of any modified fields are omitted from +**
      • The original values of any modified fields are omitted from ** UPDATE records. **
      ** -** A patchset blob may be used with up to date versions of all -** sqlite3changeset_xxx API functions except for sqlite3changeset_invert(), +** A patchset blob may be used with up to date versions of all +** sqlite3changeset_xxx API functions except for sqlite3changeset_invert(), ** which returns SQLITE_CORRUPT if it is passed a patchset. Similarly, ** attempting to use a patchset blob with old versions of the -** sqlite3changeset_xxx APIs also provokes an SQLITE_CORRUPT error. +** sqlite3changeset_xxx APIs also provokes an SQLITE_CORRUPT error. ** -** Because the non-primary key "old.*" fields are omitted, no +** Because the non-primary key "old.*" fields are omitted, no ** SQLITE_CHANGESET_DATA conflicts can be detected or reported if a patchset ** is passed to the sqlite3changeset_apply() API. Other conflict types work ** in the same way as for changesets. @@ -9915,31 +11969,39 @@ extern "C" ** a single table are grouped together, tables appear in the order in which ** they were attached to the session object). */ - SQLITE_API int sqlite3session_patchset( - sqlite3_session* pSession, /* Session object */ - int* pnPatchset, /* OUT: Size of buffer at *ppPatchset */ - void** ppPatchset /* OUT: Buffer containing patchset */ - ); +SQLITE_API int sqlite3session_patchset( + sqlite3_session *pSession, /* Session object */ + int *pnPatchset, /* OUT: Size of buffer at *ppPatchset */ + void **ppPatchset /* OUT: Buffer containing patchset */ +); - /* +/* ** CAPI3REF: Test if a changeset has recorded any changes. ** -** Return non-zero if no changes to attached tables have been recorded by -** the session object passed as the first argument. Otherwise, if one or +** Return non-zero if no changes to attached tables have been recorded by +** the session object passed as the first argument. Otherwise, if one or ** more changes have been recorded, return zero. ** ** Even if this function returns zero, it is possible that calling ** [sqlite3session_changeset()] on the session handle may still return a -** changeset that contains no changes. This can happen when a row in -** an attached table is modified and then later on the original values +** changeset that contains no changes. This can happen when a row in +** an attached table is modified and then later on the original values ** are restored. However, if this function returns non-zero, then it is -** guaranteed that a call to sqlite3session_changeset() will return a +** guaranteed that a call to sqlite3session_changeset() will return a ** changeset containing zero changes. */ - SQLITE_API int sqlite3session_isempty(sqlite3_session* pSession); +SQLITE_API int sqlite3session_isempty(sqlite3_session *pSession); + +/* +** CAPI3REF: Query for the amount of heap memory used by a session object. +** +** This API returns the total amount of heap memory in bytes currently +** used by the session object passed as the only argument. +*/ +SQLITE_API sqlite3_int64 sqlite3session_memory_used(sqlite3_session *pSession); - /* -** CAPI3REF: Create An Iterator To Traverse A Changeset +/* +** CAPI3REF: Create An Iterator To Traverse A Changeset ** CONSTRUCTOR: sqlite3_changeset_iter ** ** Create an iterator used to iterate through the contents of a changeset. @@ -9947,7 +12009,7 @@ extern "C" ** is returned. Otherwise, if an error occurs, *pp is set to zero and an ** SQLite error code is returned. ** -** The following functions can be used to advance and query a changeset +** The following functions can be used to advance and query a changeset ** iterator created by this function: ** **
        @@ -9964,12 +12026,12 @@ extern "C" ** ** Assuming the changeset blob was created by one of the ** [sqlite3session_changeset()], [sqlite3changeset_concat()] or -** [sqlite3changeset_invert()] functions, all changes within the changeset -** that apply to a single table are grouped together. This means that when -** an application iterates through a changeset using an iterator created by -** this function, all changes that relate to a single table are visited -** consecutively. There is no chance that the iterator will visit a change -** the applies to table X, then one for table Y, and then later on visit +** [sqlite3changeset_invert()] functions, all changes within the changeset +** that apply to a single table are grouped together. This means that when +** an application iterates through a changeset using an iterator created by +** this function, all changes that relate to a single table are visited +** consecutively. There is no chance that the iterator will visit a change +** the applies to table X, then one for table Y, and then later on visit ** another change for table X. ** ** The behavior of sqlite3changeset_start_v2() and its streaming equivalent @@ -9979,17 +12041,17 @@ extern "C" ** Note that the sqlite3changeset_start_v2() API is still experimental ** and therefore subject to change. */ - SQLITE_API int sqlite3changeset_start( - sqlite3_changeset_iter** pp, /* OUT: New changeset iterator handle */ - int nChangeset, /* Size of changeset blob in bytes */ - void* pChangeset /* Pointer to blob containing changeset */ - ); - SQLITE_API int sqlite3changeset_start_v2( - sqlite3_changeset_iter** pp, /* OUT: New changeset iterator handle */ - int nChangeset, /* Size of changeset blob in bytes */ - void* pChangeset, /* Pointer to blob containing changeset */ - int flags /* SESSION_CHANGESETSTART_* flags */ - ); +SQLITE_API int sqlite3changeset_start( + sqlite3_changeset_iter **pp, /* OUT: New changeset iterator handle */ + int nChangeset, /* Size of changeset blob in bytes */ + void *pChangeset /* Pointer to blob containing changeset */ +); +SQLITE_API int sqlite3changeset_start_v2( + sqlite3_changeset_iter **pp, /* OUT: New changeset iterator handle */ + int nChangeset, /* Size of changeset blob in bytes */ + void *pChangeset, /* Pointer to blob containing changeset */ + int flags /* SESSION_CHANGESETSTART_* flags */ +); /* ** CAPI3REF: Flags for sqlite3changeset_start_v2 @@ -9997,18 +12059,19 @@ extern "C" ** The following flags may passed via the 4th parameter to ** [sqlite3changeset_start_v2] and [sqlite3changeset_start_v2_strm]: ** -**
        SQLITE_CHANGESETAPPLY_INVERT
        +**
        SQLITE_CHANGESETSTART_INVERT
        ** Invert the changeset while iterating through it. This is equivalent to ** inverting a changeset using sqlite3changeset_invert() before applying it. ** It is an error to specify this flag with a patchset. */ -#define SQLITE_CHANGESETSTART_INVERT 0x0002 +#define SQLITE_CHANGESETSTART_INVERT 0x0002 + - /* +/* ** CAPI3REF: Advance A Changeset Iterator ** METHOD: sqlite3_changeset_iter ** -** This function may only be used with iterators created by function +** This function may only be used with iterators created by the function ** [sqlite3changeset_start()]. If it is called on an iterator passed to ** a conflict-handler callback by [sqlite3changeset_apply()], SQLITE_MISUSE ** is returned and the call has no effect. @@ -10019,17 +12082,17 @@ extern "C" ** point to the first change in the changeset. Each subsequent call advances ** the iterator to point to the next change in the changeset (if any). If ** no error occurs and the iterator points to a valid change after a call -** to sqlite3changeset_next() has advanced it, SQLITE_ROW is returned. +** to sqlite3changeset_next() has advanced it, SQLITE_ROW is returned. ** Otherwise, if all changes in the changeset have already been visited, ** SQLITE_DONE is returned. ** -** If an error occurs, an SQLite error code is returned. Possible error -** codes include SQLITE_CORRUPT (if the changeset buffer is corrupt) or +** If an error occurs, an SQLite error code is returned. Possible error +** codes include SQLITE_CORRUPT (if the changeset buffer is corrupt) or ** SQLITE_NOMEM. */ - SQLITE_API int sqlite3changeset_next(sqlite3_changeset_iter* pIter); +SQLITE_API int sqlite3changeset_next(sqlite3_changeset_iter *pIter); - /* +/* ** CAPI3REF: Obtain The Current Operation From A Changeset Iterator ** METHOD: sqlite3_changeset_iter ** @@ -10039,32 +12102,37 @@ extern "C" ** call to [sqlite3changeset_next()] must have returned [SQLITE_ROW]. If this ** is not the case, this function returns [SQLITE_MISUSE]. ** -** If argument pzTab is not NULL, then *pzTab is set to point to a -** nul-terminated utf-8 encoded string containing the name of the table -** affected by the current change. The buffer remains valid until either -** sqlite3changeset_next() is called on the iterator or until the -** conflict-handler function returns. If pnCol is not NULL, then *pnCol is -** set to the number of columns in the table affected by the change. If -** pbIndirect is not NULL, then *pbIndirect is set to true (1) if the change +** Arguments pOp, pnCol and pzTab may not be NULL. Upon return, three +** outputs are set through these pointers: +** +** *pOp is set to one of [SQLITE_INSERT], [SQLITE_DELETE] or [SQLITE_UPDATE], +** depending on the type of change that the iterator currently points to; +** +** *pnCol is set to the number of columns in the table affected by the change; and +** +** *pzTab is set to point to a nul-terminated utf-8 encoded string containing +** the name of the table affected by the current change. The buffer remains +** valid until either sqlite3changeset_next() is called on the iterator +** or until the conflict-handler function returns. +** +** If pbIndirect is not NULL, then *pbIndirect is set to true (1) if the change ** is an indirect change, or false (0) otherwise. See the documentation for ** [sqlite3session_indirect()] for a description of direct and indirect -** changes. Finally, if pOp is not NULL, then *pOp is set to one of -** [SQLITE_INSERT], [SQLITE_DELETE] or [SQLITE_UPDATE], depending on the -** type of change that the iterator currently points to. +** changes. ** ** If no error occurs, SQLITE_OK is returned. If an error does occur, an ** SQLite error code is returned. The values of the output variables may not ** be trusted in this case. */ - SQLITE_API int sqlite3changeset_op( - sqlite3_changeset_iter* pIter, /* Iterator object */ - const char** pzTab, /* OUT: Pointer to table name */ - int* pnCol, /* OUT: Number of columns in table */ - int* pOp, /* OUT: SQLITE_INSERT, DELETE or UPDATE */ - int* pbIndirect /* OUT: True for an 'indirect' change */ - ); +SQLITE_API int sqlite3changeset_op( + sqlite3_changeset_iter *pIter, /* Iterator object */ + const char **pzTab, /* OUT: Pointer to table name */ + int *pnCol, /* OUT: Number of columns in table */ + int *pOp, /* OUT: SQLITE_INSERT, DELETE or UPDATE */ + int *pbIndirect /* OUT: True for an 'indirect' change */ +); - /* +/* ** CAPI3REF: Obtain The Primary Key Definition Of A Table ** METHOD: sqlite3_changeset_iter ** @@ -10090,20 +12158,20 @@ extern "C" ** SQLITE_OK is returned and the output variables populated as described ** above. */ - SQLITE_API int sqlite3changeset_pk( - sqlite3_changeset_iter* pIter, /* Iterator object */ - unsigned char** pabPK, /* OUT: Array of boolean - true for PK cols */ - int* pnCol /* OUT: Number of entries in output array */ - ); +SQLITE_API int sqlite3changeset_pk( + sqlite3_changeset_iter *pIter, /* Iterator object */ + unsigned char **pabPK, /* OUT: Array of boolean - true for PK cols */ + int *pnCol /* OUT: Number of entries in output array */ +); - /* +/* ** CAPI3REF: Obtain old.* Values From A Changeset Iterator ** METHOD: sqlite3_changeset_iter ** ** The pIter argument passed to this function may either be an iterator ** passed to a conflict-handler by [sqlite3changeset_apply()], or an iterator ** created by [sqlite3changeset_start()]. In the latter case, the most recent -** call to [sqlite3changeset_next()] must have returned SQLITE_ROW. +** call to [sqlite3changeset_next()] must have returned SQLITE_ROW. ** Furthermore, it may only be called if the type of change that the iterator ** currently points to is either [SQLITE_DELETE] or [SQLITE_UPDATE]. Otherwise, ** this function returns [SQLITE_MISUSE] and sets *ppValue to NULL. @@ -10113,28 +12181,28 @@ extern "C" ** [SQLITE_RANGE] is returned and *ppValue is set to NULL. ** ** If successful, this function sets *ppValue to point to a protected -** sqlite3_value object containing the iVal'th value from the vector of +** sqlite3_value object containing the iVal'th value from the vector of ** original row values stored as part of the UPDATE or DELETE change and -** returns SQLITE_OK. The name of the function comes from the fact that this +** returns SQLITE_OK. The name of the function comes from the fact that this ** is similar to the "old.*" columns available to update or delete triggers. ** ** If some other error occurs (e.g. an OOM condition), an SQLite error code ** is returned and *ppValue is set to NULL. */ - SQLITE_API int sqlite3changeset_old( - sqlite3_changeset_iter* pIter, /* Changeset iterator */ - int iVal, /* Column number */ - sqlite3_value** ppValue /* OUT: Old value (or NULL pointer) */ - ); +SQLITE_API int sqlite3changeset_old( + sqlite3_changeset_iter *pIter, /* Changeset iterator */ + int iVal, /* Column number */ + sqlite3_value **ppValue /* OUT: Old value (or NULL pointer) */ +); - /* +/* ** CAPI3REF: Obtain new.* Values From A Changeset Iterator ** METHOD: sqlite3_changeset_iter ** ** The pIter argument passed to this function may either be an iterator ** passed to a conflict-handler by [sqlite3changeset_apply()], or an iterator ** created by [sqlite3changeset_start()]. In the latter case, the most recent -** call to [sqlite3changeset_next()] must have returned SQLITE_ROW. +** call to [sqlite3changeset_next()] must have returned SQLITE_ROW. ** Furthermore, it may only be called if the type of change that the iterator ** currently points to is either [SQLITE_UPDATE] or [SQLITE_INSERT]. Otherwise, ** this function returns [SQLITE_MISUSE] and sets *ppValue to NULL. @@ -10144,24 +12212,24 @@ extern "C" ** [SQLITE_RANGE] is returned and *ppValue is set to NULL. ** ** If successful, this function sets *ppValue to point to a protected -** sqlite3_value object containing the iVal'th value from the vector of +** sqlite3_value object containing the iVal'th value from the vector of ** new row values stored as part of the UPDATE or INSERT change and ** returns SQLITE_OK. If the change is an UPDATE and does not include -** a new value for the requested column, *ppValue is set to NULL and -** SQLITE_OK returned. The name of the function comes from the fact that -** this is similar to the "new.*" columns available to update or delete +** a new value for the requested column, *ppValue is set to NULL and +** SQLITE_OK returned. The name of the function comes from the fact that +** this is similar to the "new.*" columns available to update or delete ** triggers. ** ** If some other error occurs (e.g. an OOM condition), an SQLite error code ** is returned and *ppValue is set to NULL. */ - SQLITE_API int sqlite3changeset_new( - sqlite3_changeset_iter* pIter, /* Changeset iterator */ - int iVal, /* Column number */ - sqlite3_value** ppValue /* OUT: New value (or NULL pointer) */ - ); +SQLITE_API int sqlite3changeset_new( + sqlite3_changeset_iter *pIter, /* Changeset iterator */ + int iVal, /* Column number */ + sqlite3_value **ppValue /* OUT: New value (or NULL pointer) */ +); - /* +/* ** CAPI3REF: Obtain Conflicting Row Values From A Changeset Iterator ** METHOD: sqlite3_changeset_iter ** @@ -10176,20 +12244,20 @@ extern "C" ** [SQLITE_RANGE] is returned and *ppValue is set to NULL. ** ** If successful, this function sets *ppValue to point to a protected -** sqlite3_value object containing the iVal'th value from the +** sqlite3_value object containing the iVal'th value from the ** "conflicting row" associated with the current conflict-handler callback ** and returns SQLITE_OK. ** ** If some other error occurs (e.g. an OOM condition), an SQLite error code ** is returned and *ppValue is set to NULL. */ - SQLITE_API int sqlite3changeset_conflict( - sqlite3_changeset_iter* pIter, /* Changeset iterator */ - int iVal, /* Column number */ - sqlite3_value** ppValue /* OUT: Value from conflicting row */ - ); +SQLITE_API int sqlite3changeset_conflict( + sqlite3_changeset_iter *pIter, /* Changeset iterator */ + int iVal, /* Column number */ + sqlite3_value **ppValue /* OUT: Value from conflicting row */ +); - /* +/* ** CAPI3REF: Determine The Number Of Foreign Key Constraint Violations ** METHOD: sqlite3_changeset_iter ** @@ -10200,12 +12268,13 @@ extern "C" ** ** In all other cases this function returns SQLITE_MISUSE. */ - SQLITE_API int sqlite3changeset_fk_conflicts( - sqlite3_changeset_iter* pIter, /* Changeset iterator */ - int* pnOut /* OUT: Number of FK violations */ - ); +SQLITE_API int sqlite3changeset_fk_conflicts( + sqlite3_changeset_iter *pIter, /* Changeset iterator */ + int *pnOut /* OUT: Number of FK violations */ +); + - /* +/* ** CAPI3REF: Finalize A Changeset Iterator ** METHOD: sqlite3_changeset_iter ** @@ -10219,7 +12288,7 @@ extern "C" ** call has no effect. ** ** If an error was encountered within a call to an sqlite3changeset_xxx() -** function (for example an [SQLITE_CORRUPT] in [sqlite3changeset_next()] or an +** function (for example an [SQLITE_CORRUPT] in [sqlite3changeset_next()] or an ** [SQLITE_NOMEM] in [sqlite3changeset_new()]) then an error code corresponding ** to that error is returned by this function. Otherwise, SQLITE_OK is ** returned. This is to allow the following pattern (pseudo-code): @@ -10231,13 +12300,13 @@ extern "C" ** } ** rc = sqlite3changeset_finalize(); ** if( rc!=SQLITE_OK ){ -** // An error has occurred +** // An error has occurred ** } ** */ - SQLITE_API int sqlite3changeset_finalize(sqlite3_changeset_iter* pIter); +SQLITE_API int sqlite3changeset_finalize(sqlite3_changeset_iter *pIter); - /* +/* ** CAPI3REF: Invert A Changeset ** ** This function is used to "invert" a changeset object. Applying an inverted @@ -10259,25 +12328,25 @@ extern "C" ** zeroed and an SQLite error code returned. ** ** It is the responsibility of the caller to eventually call sqlite3_free() -** on the *ppOut pointer to free the buffer allocation following a successful +** on the *ppOut pointer to free the buffer allocation following a successful ** call to this function. ** ** WARNING/TODO: This function currently assumes that the input is a valid ** changeset. If it is not, the results are undefined. */ - SQLITE_API int sqlite3changeset_invert( - int nIn, const void* pIn, /* Input changeset */ - int* pnOut, void** ppOut /* OUT: Inverse of input */ - ); +SQLITE_API int sqlite3changeset_invert( + int nIn, const void *pIn, /* Input changeset */ + int *pnOut, void **ppOut /* OUT: Inverse of input */ +); - /* +/* ** CAPI3REF: Concatenate Two Changeset Objects ** -** This function is used to concatenate two changesets, A and B, into a +** This function is used to concatenate two changesets, A and B, into a ** single changeset. The result is a changeset equivalent to applying -** changeset A followed by changeset B. +** changeset A followed by changeset B. ** -** This function combines the two input changesets using an +** This function combines the two input changesets using an ** sqlite3_changegroup object. Calling it produces similar results as the ** following code fragment: ** @@ -10296,24 +12365,24 @@ extern "C" ** ** Refer to the sqlite3_changegroup documentation below for details. */ - SQLITE_API int sqlite3changeset_concat( - int nA, /* Number of bytes in buffer pA */ - void* pA, /* Pointer to buffer containing changeset A */ - int nB, /* Number of bytes in buffer pB */ - void* pB, /* Pointer to buffer containing changeset B */ - int* pnOut, /* OUT: Number of bytes in output changeset */ - void** ppOut /* OUT: Buffer containing output changeset */ - ); +SQLITE_API int sqlite3changeset_concat( + int nA, /* Number of bytes in buffer pA */ + void *pA, /* Pointer to buffer containing changeset A */ + int nB, /* Number of bytes in buffer pB */ + void *pB, /* Pointer to buffer containing changeset B */ + int *pnOut, /* OUT: Number of bytes in output changeset */ + void **ppOut /* OUT: Buffer containing output changeset */ +); - /* +/* ** CAPI3REF: Changegroup Handle ** -** A changegroup is an object used to combine two or more +** A changegroup is an object used to combine two or more ** [changesets] or [patchsets] */ - typedef struct sqlite3_changegroup sqlite3_changegroup; +typedef struct sqlite3_changegroup sqlite3_changegroup; - /* +/* ** CAPI3REF: Create A New Changegroup Object ** CONSTRUCTOR: sqlite3_changegroup ** @@ -10324,7 +12393,7 @@ extern "C" ** ** If successful, this function returns SQLITE_OK and populates (*pp) with ** a pointer to a new sqlite3_changegroup object before returning. The caller -** should eventually free the returned object using a call to +** should eventually free the returned object using a call to ** sqlite3changegroup_delete(). If an error occurs, an SQLite error code ** (i.e. SQLITE_NOMEM) is returned and *pp is set to NULL. ** @@ -10336,7 +12405,7 @@ extern "C" **
      • Zero or more changesets (or patchsets) are added to the object ** by calling sqlite3changegroup_add(). ** -**
      • The result of combining all input changesets together is obtained +**
      • The result of combining all input changesets together is obtained ** by the application via a call to sqlite3changegroup_output(). ** **
      • The object is deleted using a call to sqlite3changegroup_delete(). @@ -10345,18 +12414,50 @@ extern "C" ** Any number of calls to add() and output() may be made between the calls to ** new() and delete(), and in any order. ** -** As well as the regular sqlite3changegroup_add() and +** As well as the regular sqlite3changegroup_add() and ** sqlite3changegroup_output() functions, also available are the streaming ** versions sqlite3changegroup_add_strm() and sqlite3changegroup_output_strm(). */ - SQLITE_API int sqlite3changegroup_new(sqlite3_changegroup** pp); +SQLITE_API int sqlite3changegroup_new(sqlite3_changegroup **pp); + +/* +** CAPI3REF: Add a Schema to a Changegroup +** METHOD: sqlite3_changegroup_schema +** +** This method may be used to optionally enforce the rule that the changesets +** added to the changegroup handle must match the schema of database zDb +** ("main", "temp", or the name of an attached database). If +** sqlite3changegroup_add() is called to add a changeset that is not compatible +** with the configured schema, SQLITE_SCHEMA is returned and the changegroup +** object is left in an undefined state. +** +** A changeset schema is considered compatible with the database schema in +** the same way as for sqlite3changeset_apply(). Specifically, for each +** table in the changeset, there exists a database table with: +** +**
          +**
        • The name identified by the changeset, and +**
        • at least as many columns as recorded in the changeset, and +**
        • the primary key columns in the same position as recorded in +** the changeset. +**
        +** +** The output of the changegroup object always has the same schema as the +** database nominated using this function. In cases where changesets passed +** to sqlite3changegroup_add() have fewer columns than the corresponding table +** in the database schema, these are filled in using the default column +** values from the database schema. This makes it possible to combined +** changesets that have different numbers of columns for a single table +** within a changegroup, provided that they are otherwise compatible. +*/ +SQLITE_API int sqlite3changegroup_schema(sqlite3_changegroup*, sqlite3*, const char *zDb); - /* +/* ** CAPI3REF: Add A Changeset To A Changegroup ** METHOD: sqlite3_changegroup ** ** Add all changes within the changeset (or patchset) in buffer pData (size -** nData bytes) to the changegroup. +** nData bytes) to the changegroup. ** ** If the buffer contains a patchset, then all prior calls to this function ** on the same changegroup object must also have specified patchsets. Or, if @@ -10383,7 +12484,7 @@ extern "C" ** changeset was recorded immediately after the changesets already ** added to the changegroup. **
    INSERT UPDATE -** The INSERT change remains in the changegroup. The values in the +** The INSERT change remains in the changegroup. The values in the ** INSERT change are modified as if the row was inserted by the ** existing change and then updated according to the new change. **
    INSERT DELETE @@ -10394,17 +12495,17 @@ extern "C" ** changeset was recorded immediately after the changesets already ** added to the changegroup. **
    UPDATE UPDATE -** The existing UPDATE remains within the changegroup. It is amended -** so that the accompanying values are as if the row was updated once +** The existing UPDATE remains within the changegroup. It is amended +** so that the accompanying values are as if the row was updated once ** by the existing change and then again by the new change. **
    UPDATE DELETE ** The existing UPDATE is replaced by the new DELETE within the ** changegroup. **
    DELETE INSERT ** If one or more of the column values in the row inserted by the -** new change differ from those in the row deleted by the existing +** new change differ from those in the row deleted by the existing ** change, the existing DELETE is replaced by an UPDATE within the -** changegroup. Otherwise, if the inserted row is exactly the same +** changegroup. Otherwise, if the inserted row is exactly the same ** as the deleted row, the existing DELETE is simply discarded. **
    DELETE UPDATE ** The new change is ignored. This case does not occur if the new @@ -10419,17 +12520,46 @@ extern "C" ** If the new changeset contains changes to a table that is already present ** in the changegroup, then the number of columns and the position of the ** primary key columns for the table must be consistent. If this is not the -** case, this function fails with SQLITE_SCHEMA. If the input changeset -** appears to be corrupt and the corruption is detected, SQLITE_CORRUPT is -** returned. Or, if an out-of-memory condition occurs during processing, this -** function returns SQLITE_NOMEM. In all cases, if an error occurs the -** final contents of the changegroup is undefined. +** case, this function fails with SQLITE_SCHEMA. Except, if the changegroup +** object has been configured with a database schema using the +** sqlite3changegroup_schema() API, then it is possible to combine changesets +** with different numbers of columns for a single table, provided that +** they are otherwise compatible. +** +** If the input changeset appears to be corrupt and the corruption is +** detected, SQLITE_CORRUPT is returned. Or, if an out-of-memory condition +** occurs during processing, this function returns SQLITE_NOMEM. +** +** In all cases, if an error occurs the state of the final contents of the +** changegroup is undefined. If no error occurs, SQLITE_OK is returned. +*/ +SQLITE_API int sqlite3changegroup_add(sqlite3_changegroup*, int nData, void *pData); + +/* +** CAPI3REF: Add A Single Change To A Changegroup +** METHOD: sqlite3_changegroup ** -** If no error occurs, SQLITE_OK is returned. +** This function adds the single change currently indicated by the iterator +** passed as the second argument to the changegroup object. The rules for +** adding the change are just as described for [sqlite3changegroup_add()]. +** +** If the change is successfully added to the changegroup, SQLITE_OK is +** returned. Otherwise, an SQLite error code is returned. +** +** The iterator must point to a valid entry when this function is called. +** If it does not, SQLITE_ERROR is returned and no change is added to the +** changegroup. Additionally, the iterator must not have been opened with +** the SQLITE_CHANGESETAPPLY_INVERT flag. In this case SQLITE_ERROR is also +** returned. */ - SQLITE_API int sqlite3changegroup_add(sqlite3_changegroup*, int nData, void* pData); +SQLITE_API int sqlite3changegroup_add_change( + sqlite3_changegroup*, + sqlite3_changeset_iter* +); + + - /* +/* ** CAPI3REF: Obtain A Composite Changeset From A Changegroup ** METHOD: sqlite3_changegroup ** @@ -10449,49 +12579,67 @@ extern "C" ** ** If an error occurs, an SQLite error code is returned and the output ** variables (*pnData) and (*ppData) are set to 0. Otherwise, SQLITE_OK -** is returned and the output variables are set to the size of and a +** is returned and the output variables are set to the size of and a ** pointer to the output buffer, respectively. In this case it is the ** responsibility of the caller to eventually free the buffer using a ** call to sqlite3_free(). */ - SQLITE_API int sqlite3changegroup_output( - sqlite3_changegroup*, - int* pnData, /* OUT: Size of output buffer in bytes */ - void** ppData /* OUT: Pointer to output buffer */ - ); +SQLITE_API int sqlite3changegroup_output( + sqlite3_changegroup*, + int *pnData, /* OUT: Size of output buffer in bytes */ + void **ppData /* OUT: Pointer to output buffer */ +); - /* +/* ** CAPI3REF: Delete A Changegroup Object ** DESTRUCTOR: sqlite3_changegroup */ - SQLITE_API void sqlite3changegroup_delete(sqlite3_changegroup*); +SQLITE_API void sqlite3changegroup_delete(sqlite3_changegroup*); - /* +/* ** CAPI3REF: Apply A Changeset To A Database ** ** Apply a changeset or patchset to a database. These functions attempt to ** update the "main" database attached to handle db with the changes found in -** the changeset passed via the second and third arguments. +** the changeset passed via the second and third arguments. +** +** All changes made by these functions are enclosed in a savepoint transaction. +** If any other error (aside from a constraint failure when attempting to +** write to the target database) occurs, then the savepoint transaction is +** rolled back, restoring the target database to its original state, and an +** SQLite error code returned. Additionally, starting with version 3.51.0, +** an error code and error message that may be accessed using the +** [sqlite3_errcode()] and [sqlite3_errmsg()] APIs are left in the database +** handle. ** ** The fourth argument (xFilter) passed to these functions is the "filter -** callback". If it is not NULL, then for each table affected by at least one -** change in the changeset, the filter callback is invoked with -** the table name as the second argument, and a copy of the context pointer -** passed as the sixth argument as the first. If the "filter callback" -** returns zero, then no attempt is made to apply any changes to the table. -** Otherwise, if the return value is non-zero or the xFilter argument to -** is NULL, all changes related to the table are attempted. -** -** For each table that is not excluded by the filter callback, this function -** tests that the target database contains a compatible table. A table is +** callback". This may be passed NULL, in which case all changes in the +** changeset are applied to the database. For sqlite3changeset_apply() and +** sqlite3_changeset_apply_v2(), if it is not NULL, then it is invoked once +** for each table affected by at least one change in the changeset. In this +** case the table name is passed as the second argument, and a copy of +** the context pointer passed as the sixth argument to apply() or apply_v2() +** as the first. If the "filter callback" returns zero, then no attempt is +** made to apply any changes to the table. Otherwise, if the return value is +** non-zero, all changes related to the table are attempted. +** +** For sqlite3_changeset_apply_v3(), the xFilter callback is invoked once +** per change. The second argument in this case is an sqlite3_changeset_iter +** that may be queried using the usual APIs for the details of the current +** change. If the "filter callback" returns zero in this case, then no attempt +** is made to apply the current change. If it returns non-zero, the change +** is applied. +** +** For each table that is not excluded by the filter callback, this function +** tests that the target database contains a compatible table. A table is ** considered compatible if all of the following are true: ** **
      -**
    • The table has the same name as the name recorded in the +**
    • The table has the same name as the name recorded in the ** changeset, and -**
    • The table has at least as many columns as recorded in the +**
    • The table has at least as many columns as recorded in the ** changeset, and -**
    • The table has primary key columns in the same position as +**
    • The table has primary key columns in the same position as ** recorded in the changeset. **
    ** @@ -10500,35 +12648,35 @@ extern "C" ** via the sqlite3_log() mechanism with the error code SQLITE_SCHEMA. At most ** one such warning is issued for each table in the changeset. ** -** For each change for which there is a compatible table, an attempt is made -** to modify the table contents according to the UPDATE, INSERT or DELETE -** change. If a change cannot be applied cleanly, the conflict handler -** function passed as the fifth argument to sqlite3changeset_apply() may be -** invoked. A description of exactly when the conflict handler is invoked for -** each type of change is below. +** For each change for which there is a compatible table, an attempt is made +** to modify the table contents according to each UPDATE, INSERT or DELETE +** change that is not excluded by a filter callback. If a change cannot be +** applied cleanly, the conflict handler function passed as the fifth argument +** to sqlite3changeset_apply() may be invoked. A description of exactly when +** the conflict handler is invoked for each type of change is below. ** ** Unlike the xFilter argument, xConflict may not be passed NULL. The results ** of passing anything other than a valid function pointer as the xConflict ** argument are undefined. ** ** Each time the conflict handler function is invoked, it must return one -** of [SQLITE_CHANGESET_OMIT], [SQLITE_CHANGESET_ABORT] or +** of [SQLITE_CHANGESET_OMIT], [SQLITE_CHANGESET_ABORT] or ** [SQLITE_CHANGESET_REPLACE]. SQLITE_CHANGESET_REPLACE may only be returned ** if the second argument passed to the conflict handler is either ** SQLITE_CHANGESET_DATA or SQLITE_CHANGESET_CONFLICT. If the conflict-handler ** returns an illegal value, any changes already made are rolled back and -** the call to sqlite3changeset_apply() returns SQLITE_MISUSE. Different +** the call to sqlite3changeset_apply() returns SQLITE_MISUSE. Different ** actions are taken by sqlite3changeset_apply() depending on the value ** returned by each invocation of the conflict-handler function. Refer to -** the documentation for the three +** the documentation for the three ** [SQLITE_CHANGESET_OMIT|available return values] for details. ** **
    **
    DELETE Changes
    -** For each DELETE change, the function checks if the target database -** contains a row with the same primary key value (or values) as the -** original row values stored in the changeset. If it does, and the values -** stored in all non-primary key columns also match the values stored in +** For each DELETE change, the function checks if the target database +** contains a row with the same primary key value (or values) as the +** original row values stored in the changeset. If it does, and the values +** stored in all non-primary key columns also match the values stored in ** the changeset the row is deleted from the target database. ** ** If a row with matching primary key values is found, but one or more of @@ -10557,22 +12705,22 @@ extern "C" ** database table, the trailing fields are populated with their default ** values. ** -** If the attempt to insert the row fails because the database already +** If the attempt to insert the row fails because the database already ** contains a row with the same primary key values, the conflict handler -** function is invoked with the second argument set to +** function is invoked with the second argument set to ** [SQLITE_CHANGESET_CONFLICT]. ** ** If the attempt to insert the row fails because of some other constraint -** violation (e.g. NOT NULL or UNIQUE), the conflict handler function is +** violation (e.g. NOT NULL or UNIQUE), the conflict handler function is ** invoked with the second argument set to [SQLITE_CHANGESET_CONSTRAINT]. -** This includes the case where the INSERT operation is re-attempted because -** an earlier call to the conflict handler function returned +** This includes the case where the INSERT operation is re-attempted because +** an earlier call to the conflict handler function returned ** [SQLITE_CHANGESET_REPLACE]. ** **
    UPDATE Changes
    -** For each UPDATE change, the function checks if the target database -** contains a row with the same primary key value (or values) as the -** original row values stored in the changeset. If it does, and the values +** For each UPDATE change, the function checks if the target database +** contains a row with the same primary key value (or values) as the +** original row values stored in the changeset. If it does, and the values ** stored in all modified non-primary key columns also match the values ** stored in the changeset the row is updated within the target database. ** @@ -10588,28 +12736,22 @@ extern "C" ** the conflict-handler function is invoked with [SQLITE_CHANGESET_NOTFOUND] ** passed as the second argument. ** -** If the UPDATE operation is attempted, but SQLite returns -** SQLITE_CONSTRAINT, the conflict-handler function is invoked with +** If the UPDATE operation is attempted, but SQLite returns +** SQLITE_CONSTRAINT, the conflict-handler function is invoked with ** [SQLITE_CHANGESET_CONSTRAINT] passed as the second argument. -** This includes the case where the UPDATE operation is attempted after +** This includes the case where the UPDATE operation is attempted after ** an earlier call to the conflict handler function returned -** [SQLITE_CHANGESET_REPLACE]. +** [SQLITE_CHANGESET_REPLACE]. **
    ** ** It is safe to execute SQL statements, including those that write to the ** table that the callback related to, from within the xConflict callback. -** This can be used to further customize the applications conflict +** This can be used to further customize the application's conflict ** resolution strategy. ** -** All changes made by these functions are enclosed in a savepoint transaction. -** If any other error (aside from a constraint failure when attempting to -** write to the target database) occurs, then the savepoint transaction is -** rolled back, restoring the target database to its original state, and an -** SQLite error code returned. -** ** If the output parameters (ppRebase) and (pnRebase) are non-NULL and ** the input is a changeset (not a patchset), then sqlite3changeset_apply_v2() -** may set (*ppRebase) to point to a "rebase" that may be used with the +** may set (*ppRebase) to point to a "rebase" that may be used with the ** sqlite3_rebaser APIs buffer before returning. In this case (*pnRebase) ** is set to the size of the buffer in bytes. It is the responsibility of the ** caller to eventually free any such buffer using sqlite3_free(). The buffer @@ -10624,38 +12766,55 @@ extern "C" ** Note that the sqlite3changeset_apply_v2() API is still experimental ** and therefore subject to change. */ - SQLITE_API int sqlite3changeset_apply( - sqlite3* db, /* Apply change to "main" db of this handle */ - int nChangeset, /* Size of changeset in bytes */ - void* pChangeset, /* Changeset blob */ - int (*xFilter)( - void* pCtx, /* Copy of sixth arg to _apply() */ - const char* zTab /* Table name */ - ), - int (*xConflict)( - void* pCtx, /* Copy of sixth arg to _apply() */ - int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ - sqlite3_changeset_iter* p /* Handle describing change and conflict */ - ), - void* pCtx /* First argument passed to xConflict */ - ); - SQLITE_API int sqlite3changeset_apply_v2( - sqlite3* db, /* Apply change to "main" db of this handle */ - int nChangeset, /* Size of changeset in bytes */ - void* pChangeset, /* Changeset blob */ - int (*xFilter)( - void* pCtx, /* Copy of sixth arg to _apply() */ - const char* zTab /* Table name */ - ), - int (*xConflict)( - void* pCtx, /* Copy of sixth arg to _apply() */ - int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ - sqlite3_changeset_iter* p /* Handle describing change and conflict */ - ), - void* pCtx, /* First argument passed to xConflict */ - void** ppRebase, int* pnRebase, /* OUT: Rebase data */ - int flags /* SESSION_CHANGESETAPPLY_* flags */ - ); +SQLITE_API int sqlite3changeset_apply( + sqlite3 *db, /* Apply change to "main" db of this handle */ + int nChangeset, /* Size of changeset in bytes */ + void *pChangeset, /* Changeset blob */ + int(*xFilter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + const char *zTab /* Table name */ + ), + int(*xConflict)( + void *pCtx, /* Copy of sixth arg to _apply() */ + int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ + sqlite3_changeset_iter *p /* Handle describing change and conflict */ + ), + void *pCtx /* First argument passed to xConflict */ +); +SQLITE_API int sqlite3changeset_apply_v2( + sqlite3 *db, /* Apply change to "main" db of this handle */ + int nChangeset, /* Size of changeset in bytes */ + void *pChangeset, /* Changeset blob */ + int(*xFilter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + const char *zTab /* Table name */ + ), + int(*xConflict)( + void *pCtx, /* Copy of sixth arg to _apply() */ + int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ + sqlite3_changeset_iter *p /* Handle describing change and conflict */ + ), + void *pCtx, /* First argument passed to xConflict */ + void **ppRebase, int *pnRebase, /* OUT: Rebase data */ + int flags /* SESSION_CHANGESETAPPLY_* flags */ +); +SQLITE_API int sqlite3changeset_apply_v3( + sqlite3 *db, /* Apply change to "main" db of this handle */ + int nChangeset, /* Size of changeset in bytes */ + void *pChangeset, /* Changeset blob */ + int(*xFilter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + sqlite3_changeset_iter *p /* Handle describing change */ + ), + int(*xConflict)( + void *pCtx, /* Copy of sixth arg to _apply() */ + int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ + sqlite3_changeset_iter *p /* Handle describing change and conflict */ + ), + void *pCtx, /* First argument passed to xConflict */ + void **ppRebase, int *pnRebase, /* OUT: Rebase data */ + int flags /* SESSION_CHANGESETAPPLY_* flags */ +); /* ** CAPI3REF: Flags for sqlite3changeset_apply_v2 @@ -10670,18 +12829,51 @@ extern "C" ** SAVEPOINT is committed if the changeset or patchset is successfully ** applied, or rolled back if an error occurs. Specifying this flag ** causes the sessions module to omit this savepoint. In this case, if the -** caller has an open transaction or savepoint when apply_v2() is called, +** caller has an open transaction or savepoint when apply_v2() is called, ** it may revert the partially applied changeset by rolling it back. ** **
    SQLITE_CHANGESETAPPLY_INVERT
    ** Invert the changeset before applying it. This is equivalent to inverting ** a changeset using sqlite3changeset_invert() before applying it. It is ** an error to specify this flag with a patchset. -*/ -#define SQLITE_CHANGESETAPPLY_NOSAVEPOINT 0x0001 -#define SQLITE_CHANGESETAPPLY_INVERT 0x0002 +** +**
    SQLITE_CHANGESETAPPLY_IGNORENOOP
    +** Do not invoke the conflict handler callback for any changes that +** would not actually modify the database even if they were applied. +** Specifically, this means that the conflict handler is not invoked +** for: +**
      +**
    • a delete change if the row being deleted cannot be found, +**
    • an update change if the modified fields are already set to +** their new values in the conflicting row, or +**
    • an insert change if all fields of the conflicting row match +** the row being inserted. +**
    +** +**
    SQLITE_CHANGESETAPPLY_FKNOACTION
    +** If this flag it set, then all foreign key constraints in the target +** database behave as if they were declared with "ON UPDATE NO ACTION ON +** DELETE NO ACTION", even if they are actually CASCADE, RESTRICT, SET NULL +** or SET DEFAULT. +** +**
    SQLITE_CHANGESETAPPLY_NOUPDATELOOP
    +** Sometimes, a changeset contains two or more update statements such that +** although after applying all updates the database will contain no +** constraint violations, no single update can be applied before the others. +** The simplest example of this is a pair of UPDATEs that have "swapped" +** two column values with a UNIQUE constraint. +**

    +** Usually, sqlite3changeset_apply() and similar functions work hard to try +** to find a way to apply such a changeset. However, if this flag is set, +** then all such updates are considered CONSTRAINT conflicts. +*/ +#define SQLITE_CHANGESETAPPLY_NOSAVEPOINT 0x0001 +#define SQLITE_CHANGESETAPPLY_INVERT 0x0002 +#define SQLITE_CHANGESETAPPLY_IGNORENOOP 0x0004 +#define SQLITE_CHANGESETAPPLY_FKNOACTION 0x0008 +#define SQLITE_CHANGESETAPPLY_NOUPDATELOOP 0x0010 -/* +/* ** CAPI3REF: Constants Passed To The Conflict Handler ** ** Values that may be passed as the second argument to a conflict-handler. @@ -10690,32 +12882,32 @@ extern "C" **

    SQLITE_CHANGESET_DATA
    ** The conflict handler is invoked with CHANGESET_DATA as the second argument ** when processing a DELETE or UPDATE change if a row with the required -** PRIMARY KEY fields is present in the database, but one or more other -** (non primary-key) fields modified by the update do not contain the +** PRIMARY KEY fields is present in the database, but one or more other +** (non primary-key) fields modified by the update do not contain the ** expected "before" values. -** +** ** The conflicting row, in this case, is the database row with the matching ** primary key. -** +** **
    SQLITE_CHANGESET_NOTFOUND
    ** The conflict handler is invoked with CHANGESET_NOTFOUND as the second ** argument when processing a DELETE or UPDATE change if a row with the ** required PRIMARY KEY fields is not present in the database. -** +** ** There is no conflicting row in this case. The results of invoking the ** sqlite3changeset_conflict() API are undefined. -** +** **
    SQLITE_CHANGESET_CONFLICT
    ** CHANGESET_CONFLICT is passed as the second argument to the conflict -** handler while processing an INSERT change if the operation would result +** handler while processing an INSERT change if the operation would result ** in duplicate primary key values. -** +** ** The conflicting row in this case is the database row with the matching ** primary key. ** **
    SQLITE_CHANGESET_FOREIGN_KEY
    ** If foreign key handling is enabled, and applying a changeset leaves the -** database in a state containing foreign key violations, the conflict +** database in a state containing foreign key violations, the conflict ** handler is invoked with CHANGESET_FOREIGN_KEY as the second argument ** exactly once before the changeset is committed. If the conflict handler ** returns CHANGESET_OMIT, the changes, including those that caused the @@ -10725,24 +12917,24 @@ extern "C" ** No current or conflicting row information is provided. The only function ** it is possible to call on the supplied sqlite3_changeset_iter handle ** is sqlite3changeset_fk_conflicts(). -** +** **
    SQLITE_CHANGESET_CONSTRAINT
    -** If any other constraint violation occurs while applying a change (i.e. -** a UNIQUE, CHECK or NOT NULL constraint), the conflict handler is +** If any other constraint violation occurs while applying a change (i.e. +** a UNIQUE, CHECK or NOT NULL constraint), the conflict handler is ** invoked with CHANGESET_CONSTRAINT as the second argument. -** +** ** There is no conflicting row in this case. The results of invoking the ** sqlite3changeset_conflict() API are undefined. ** ** */ -#define SQLITE_CHANGESET_DATA 1 -#define SQLITE_CHANGESET_NOTFOUND 2 -#define SQLITE_CHANGESET_CONFLICT 3 -#define SQLITE_CHANGESET_CONSTRAINT 4 +#define SQLITE_CHANGESET_DATA 1 +#define SQLITE_CHANGESET_NOTFOUND 2 +#define SQLITE_CHANGESET_CONFLICT 3 +#define SQLITE_CHANGESET_CONSTRAINT 4 #define SQLITE_CHANGESET_FOREIGN_KEY 5 -/* +/* ** CAPI3REF: Constants Returned By The Conflict Handler ** ** A conflict handler callback must return one of the following three values. @@ -10750,13 +12942,13 @@ extern "C" **
    **
    SQLITE_CHANGESET_OMIT
    ** If a conflict handler returns this value no special action is taken. The -** change that caused the conflict is not applied. The session module +** change that caused the conflict is not applied. The session module ** continues to the next change in the changeset. ** **
    SQLITE_CHANGESET_REPLACE
    ** This value may only be returned if the second argument to the conflict ** handler was SQLITE_CHANGESET_DATA or SQLITE_CHANGESET_CONFLICT. If this -** is not the case, any changes applied so far are rolled back and the +** is not the case, any changes applied so far are rolled back and the ** call to sqlite3changeset_apply() returns SQLITE_MISUSE. ** ** If CHANGESET_REPLACE is returned by an SQLITE_CHANGESET_DATA conflict @@ -10769,28 +12961,28 @@ extern "C" ** the original row is restored to the database before continuing. ** **
    SQLITE_CHANGESET_ABORT
    -** If this value is returned, any changes applied so far are rolled back +** If this value is returned, any changes applied so far are rolled back ** and the call to sqlite3changeset_apply() returns SQLITE_ABORT. **
    */ -#define SQLITE_CHANGESET_OMIT 0 -#define SQLITE_CHANGESET_REPLACE 1 -#define SQLITE_CHANGESET_ABORT 2 +#define SQLITE_CHANGESET_OMIT 0 +#define SQLITE_CHANGESET_REPLACE 1 +#define SQLITE_CHANGESET_ABORT 2 - /* +/* ** CAPI3REF: Rebasing changesets ** EXPERIMENTAL ** ** Suppose there is a site hosting a database in state S0. And that ** modifications are made that move that database to state S1 and a ** changeset recorded (the "local" changeset). Then, a changeset based -** on S0 is received from another site (the "remote" changeset) and -** applied to the database. The database is then in state +** on S0 is received from another site (the "remote" changeset) and +** applied to the database. The database is then in state ** (S1+"remote"), where the exact state depends on any conflict ** resolution decisions (OMIT or REPLACE) made while applying "remote". -** Rebasing a changeset is to update it to take those conflict +** Rebasing a changeset is to update it to take those conflict ** resolution decisions into account, so that the same conflicts -** do not have to be resolved elsewhere in the network. +** do not have to be resolved elsewhere in the network. ** ** For example, if both the local and remote changesets contain an ** INSERT of the same key on "CREATE TABLE t1(a PRIMARY KEY, b)": @@ -10809,7 +13001,7 @@ extern "C" ** **
    **
    Local INSERT
    -** This may only conflict with a remote INSERT. If the conflict +** This may only conflict with a remote INSERT. If the conflict ** resolution was OMIT, then add an UPDATE change to the rebased ** changeset. Or, if the conflict resolution was REPLACE, add ** nothing to the rebased changeset. @@ -10833,12 +13025,12 @@ extern "C" ** the old.* values are rebased using the new.* values in the remote ** change. Or, if the resolution is REPLACE, then the change is copied ** into the rebased changeset with updates to columns also updated by -** the conflicting remote UPDATE removed. If this means no columns would +** the conflicting remote UPDATE removed. If this means no columns would ** be updated, the change is omitted. **
    ** -** A local change may be rebased against multiple remote changes -** simultaneously. If a single key is modified by multiple remote +** A local change may be rebased against multiple remote changes +** simultaneously. If a single key is modified by multiple remote ** changesets, they are combined as follows before the local changeset ** is rebased: ** @@ -10851,10 +13043,10 @@ extern "C" ** of the OMIT resolutions. ** ** -** Note that conflict resolutions from multiple remote changesets are -** combined on a per-field basis, not per-row. This means that in the -** case of multiple remote UPDATE operations, some fields of a single -** local change may be rebased for REPLACE while others are rebased for +** Note that conflict resolutions from multiple remote changesets are +** combined on a per-field basis, not per-row. This means that in the +** case of multiple remote UPDATE operations, some fields of a single +** local change may be rebased for REPLACE while others are rebased for ** OMIT. ** ** In order to rebase a local changeset, the remote changeset must first @@ -10862,7 +13054,7 @@ extern "C" ** the buffer of rebase information captured. Then: ** **
      -**
    1. An sqlite3_rebaser object is created by calling +**
    2. An sqlite3_rebaser object is created by calling ** sqlite3rebaser_create(). **
    3. The new object is configured with the rebase buffer obtained from ** sqlite3changeset_apply_v2() by calling sqlite3rebaser_configure(). @@ -10875,20 +13067,20 @@ extern "C" ** sqlite3rebaser_delete(). **
    */ - typedef struct sqlite3_rebaser sqlite3_rebaser; +typedef struct sqlite3_rebaser sqlite3_rebaser; - /* +/* ** CAPI3REF: Create a changeset rebaser object. ** EXPERIMENTAL ** ** Allocate a new changeset rebaser object. If successful, set (*ppNew) to ** point to the new object and return SQLITE_OK. Otherwise, if an error -** occurs, return an SQLite error code (e.g. SQLITE_NOMEM) and set (*ppNew) -** to NULL. +** occurs, return an SQLite error code (e.g. SQLITE_NOMEM) and set (*ppNew) +** to NULL. */ - SQLITE_API int sqlite3rebaser_create(sqlite3_rebaser** ppNew); +SQLITE_API int sqlite3rebaser_create(sqlite3_rebaser **ppNew); - /* +/* ** CAPI3REF: Configure a changeset rebaser object. ** EXPERIMENTAL ** @@ -10897,30 +13089,32 @@ extern "C" ** bytes), which must have been obtained from a previous call to ** sqlite3changeset_apply_v2(). */ - SQLITE_API int sqlite3rebaser_configure( - sqlite3_rebaser*, - int nRebase, const void* pRebase); +SQLITE_API int sqlite3rebaser_configure( + sqlite3_rebaser*, + int nRebase, const void *pRebase +); - /* +/* ** CAPI3REF: Rebase a changeset ** EXPERIMENTAL ** ** Argument pIn must point to a buffer containing a changeset nIn bytes ** in size. This function allocates and populates a buffer with a copy -** of the changeset rebased rebased according to the configuration of the +** of the changeset rebased according to the configuration of the ** rebaser object passed as the first argument. If successful, (*ppOut) -** is set to point to the new buffer containing the rebased changeset and +** is set to point to the new buffer containing the rebased changeset and ** (*pnOut) to its size in bytes and SQLITE_OK returned. It is the ** responsibility of the caller to eventually free the new buffer using ** sqlite3_free(). Otherwise, if an error occurs, (*ppOut) and (*pnOut) ** are set to zero and an SQLite error code returned. */ - SQLITE_API int sqlite3rebaser_rebase( - sqlite3_rebaser*, - int nIn, const void* pIn, - int* pnOut, void** ppOut); +SQLITE_API int sqlite3rebaser_rebase( + sqlite3_rebaser*, + int nIn, const void *pIn, + int *pnOut, void **ppOut +); - /* +/* ** CAPI3REF: Delete a changeset rebaser object. ** EXPERIMENTAL ** @@ -10928,30 +13122,30 @@ extern "C" ** should be one call to this function for each successful invocation ** of sqlite3rebaser_create(). */ - SQLITE_API void sqlite3rebaser_delete(sqlite3_rebaser* p); +SQLITE_API void sqlite3rebaser_delete(sqlite3_rebaser *p); - /* +/* ** CAPI3REF: Streaming Versions of API functions. ** -** The six streaming API xxx_strm() functions serve similar purposes to the +** The six streaming API xxx_strm() functions serve similar purposes to the ** corresponding non-streaming API functions: ** ** ** -**
    Streaming functionNon-streaming equivalent
    sqlite3changeset_apply_strm[sqlite3changeset_apply] -**
    sqlite3changeset_apply_strm_v2[sqlite3changeset_apply_v2] -**
    sqlite3changeset_concat_strm[sqlite3changeset_concat] -**
    sqlite3changeset_invert_strm[sqlite3changeset_invert] -**
    sqlite3changeset_start_strm[sqlite3changeset_start] -**
    sqlite3session_changeset_strm[sqlite3session_changeset] -**
    sqlite3session_patchset_strm[sqlite3session_patchset] +**
    sqlite3changeset_apply_strm[sqlite3changeset_apply] +**
    sqlite3changeset_apply_strm_v2[sqlite3changeset_apply_v2] +**
    sqlite3changeset_concat_strm[sqlite3changeset_concat] +**
    sqlite3changeset_invert_strm[sqlite3changeset_invert] +**
    sqlite3changeset_start_strm[sqlite3changeset_start] +**
    sqlite3session_changeset_strm[sqlite3session_changeset] +**
    sqlite3session_patchset_strm[sqlite3session_patchset] **
    ** ** Non-streaming functions that accept changesets (or patchsets) as input -** require that the entire changeset be stored in a single buffer in memory. -** Similarly, those that return a changeset or patchset do so by returning -** a pointer to a single large buffer allocated using sqlite3_malloc(). -** Normally this is convenient. However, if an application running in a +** require that the entire changeset be stored in a single buffer in memory. +** Similarly, those that return a changeset or patchset do so by returning +** a pointer to a single large buffer allocated using sqlite3_malloc(). +** Normally this is convenient. However, if an application running in a ** low-memory environment is required to handle very large changesets, the ** large contiguous memory allocations required can become onerous. ** @@ -10973,12 +13167,12 @@ extern "C" ** ** ** Each time the xInput callback is invoked by the sessions module, the first -** argument passed is a copy of the supplied pIn context pointer. The second -** argument, pData, points to a buffer (*pnData) bytes in size. Assuming no -** error occurs the xInput method should copy up to (*pnData) bytes of data -** into the buffer and set (*pnData) to the actual number of bytes copied -** before returning SQLITE_OK. If the input is completely exhausted, (*pnData) -** should be set to zero to indicate this. Or, if an error occurs, an SQLite +** argument passed is a copy of the supplied pIn context pointer. The second +** argument, pData, points to a buffer (*pnData) bytes in size. Assuming no +** error occurs the xInput method should copy up to (*pnData) bytes of data +** into the buffer and set (*pnData) to the actual number of bytes copied +** before returning SQLITE_OK. If the input is completely exhausted, (*pnData) +** should be set to zero to indicate this. Or, if an error occurs, an SQLite ** error code should be returned. In all cases, if an xInput callback returns ** an error, all processing is abandoned and the streaming API function ** returns a copy of the error code to the caller. @@ -10986,7 +13180,7 @@ extern "C" ** In the case of sqlite3changeset_start_strm(), the xInput callback may be ** invoked by the sessions module at any point during the lifetime of the ** iterator. If such an xInput callback returns an error, the iterator enters -** an error state, whereby all subsequent calls to iterator functions +** an error state, whereby all subsequent calls to iterator functions ** immediately fail with the same error code as returned by xInput. ** ** Similarly, streaming API functions that return changesets (or patchsets) @@ -11016,97 +13210,124 @@ extern "C" ** is immediately abandoned and the streaming API function returns a copy ** of the xOutput error code to the application. ** -** The sessions module never invokes an xOutput callback with the third +** The sessions module never invokes an xOutput callback with the third ** parameter set to a value less than or equal to zero. Other than this, ** no guarantees are made as to the size of the chunks of data returned. */ - SQLITE_API int sqlite3changeset_apply_strm( - sqlite3* db, /* Apply change to "main" db of this handle */ - int (*xInput)(void* pIn, void* pData, int* pnData), /* Input function */ - void* pIn, /* First arg for xInput */ - int (*xFilter)( - void* pCtx, /* Copy of sixth arg to _apply() */ - const char* zTab /* Table name */ - ), - int (*xConflict)( - void* pCtx, /* Copy of sixth arg to _apply() */ - int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ - sqlite3_changeset_iter* p /* Handle describing change and conflict */ - ), - void* pCtx /* First argument passed to xConflict */ - ); - SQLITE_API int sqlite3changeset_apply_v2_strm( - sqlite3* db, /* Apply change to "main" db of this handle */ - int (*xInput)(void* pIn, void* pData, int* pnData), /* Input function */ - void* pIn, /* First arg for xInput */ - int (*xFilter)( - void* pCtx, /* Copy of sixth arg to _apply() */ - const char* zTab /* Table name */ - ), - int (*xConflict)( - void* pCtx, /* Copy of sixth arg to _apply() */ - int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ - sqlite3_changeset_iter* p /* Handle describing change and conflict */ - ), - void* pCtx, /* First argument passed to xConflict */ - void** ppRebase, int* pnRebase, - int flags); - SQLITE_API int sqlite3changeset_concat_strm( - int (*xInputA)(void* pIn, void* pData, int* pnData), - void* pInA, - int (*xInputB)(void* pIn, void* pData, int* pnData), - void* pInB, - int (*xOutput)(void* pOut, const void* pData, int nData), - void* pOut); - SQLITE_API int sqlite3changeset_invert_strm( - int (*xInput)(void* pIn, void* pData, int* pnData), - void* pIn, - int (*xOutput)(void* pOut, const void* pData, int nData), - void* pOut); - SQLITE_API int sqlite3changeset_start_strm( - sqlite3_changeset_iter** pp, - int (*xInput)(void* pIn, void* pData, int* pnData), - void* pIn); - SQLITE_API int sqlite3changeset_start_v2_strm( - sqlite3_changeset_iter** pp, - int (*xInput)(void* pIn, void* pData, int* pnData), - void* pIn, - int flags); - SQLITE_API int sqlite3session_changeset_strm( - sqlite3_session* pSession, - int (*xOutput)(void* pOut, const void* pData, int nData), - void* pOut); - SQLITE_API int sqlite3session_patchset_strm( - sqlite3_session* pSession, - int (*xOutput)(void* pOut, const void* pData, int nData), - void* pOut); - SQLITE_API int sqlite3changegroup_add_strm(sqlite3_changegroup*, - int (*xInput)(void* pIn, void* pData, int* pnData), - void* pIn); - SQLITE_API int sqlite3changegroup_output_strm(sqlite3_changegroup*, - int (*xOutput)(void* pOut, const void* pData, int nData), - void* pOut); - SQLITE_API int sqlite3rebaser_rebase_strm( - sqlite3_rebaser* pRebaser, - int (*xInput)(void* pIn, void* pData, int* pnData), - void* pIn, - int (*xOutput)(void* pOut, const void* pData, int nData), - void* pOut); - - /* +SQLITE_API int sqlite3changeset_apply_strm( + sqlite3 *db, /* Apply change to "main" db of this handle */ + int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */ + void *pIn, /* First arg for xInput */ + int(*xFilter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + const char *zTab /* Table name */ + ), + int(*xConflict)( + void *pCtx, /* Copy of sixth arg to _apply() */ + int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ + sqlite3_changeset_iter *p /* Handle describing change and conflict */ + ), + void *pCtx /* First argument passed to xConflict */ +); +SQLITE_API int sqlite3changeset_apply_v2_strm( + sqlite3 *db, /* Apply change to "main" db of this handle */ + int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */ + void *pIn, /* First arg for xInput */ + int(*xFilter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + const char *zTab /* Table name */ + ), + int(*xConflict)( + void *pCtx, /* Copy of sixth arg to _apply() */ + int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ + sqlite3_changeset_iter *p /* Handle describing change and conflict */ + ), + void *pCtx, /* First argument passed to xConflict */ + void **ppRebase, int *pnRebase, + int flags +); +SQLITE_API int sqlite3changeset_apply_v3_strm( + sqlite3 *db, /* Apply change to "main" db of this handle */ + int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */ + void *pIn, /* First arg for xInput */ + int(*xFilter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + sqlite3_changeset_iter *p + ), + int(*xConflict)( + void *pCtx, /* Copy of sixth arg to _apply() */ + int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ + sqlite3_changeset_iter *p /* Handle describing change and conflict */ + ), + void *pCtx, /* First argument passed to xConflict */ + void **ppRebase, int *pnRebase, + int flags +); +SQLITE_API int sqlite3changeset_concat_strm( + int (*xInputA)(void *pIn, void *pData, int *pnData), + void *pInA, + int (*xInputB)(void *pIn, void *pData, int *pnData), + void *pInB, + int (*xOutput)(void *pOut, const void *pData, int nData), + void *pOut +); +SQLITE_API int sqlite3changeset_invert_strm( + int (*xInput)(void *pIn, void *pData, int *pnData), + void *pIn, + int (*xOutput)(void *pOut, const void *pData, int nData), + void *pOut +); +SQLITE_API int sqlite3changeset_start_strm( + sqlite3_changeset_iter **pp, + int (*xInput)(void *pIn, void *pData, int *pnData), + void *pIn +); +SQLITE_API int sqlite3changeset_start_v2_strm( + sqlite3_changeset_iter **pp, + int (*xInput)(void *pIn, void *pData, int *pnData), + void *pIn, + int flags +); +SQLITE_API int sqlite3session_changeset_strm( + sqlite3_session *pSession, + int (*xOutput)(void *pOut, const void *pData, int nData), + void *pOut +); +SQLITE_API int sqlite3session_patchset_strm( + sqlite3_session *pSession, + int (*xOutput)(void *pOut, const void *pData, int nData), + void *pOut +); +SQLITE_API int sqlite3changegroup_add_strm(sqlite3_changegroup*, + int (*xInput)(void *pIn, void *pData, int *pnData), + void *pIn +); +SQLITE_API int sqlite3changegroup_output_strm(sqlite3_changegroup*, + int (*xOutput)(void *pOut, const void *pData, int nData), + void *pOut +); +SQLITE_API int sqlite3rebaser_rebase_strm( + sqlite3_rebaser *pRebaser, + int (*xInput)(void *pIn, void *pData, int *pnData), + void *pIn, + int (*xOutput)(void *pOut, const void *pData, int nData), + void *pOut +); + +/* ** CAPI3REF: Configure global parameters ** ** The sqlite3session_config() interface is used to make global configuration -** changes to the sessions module in order to tune it to the specific needs +** changes to the sessions module in order to tune it to the specific needs ** of the application. ** ** The sqlite3session_config() interface is not threadsafe. If it is invoked ** while any other thread is inside any other sessions method then the ** results are undefined. Furthermore, if it is invoked after any sessions -** related objects have been created, the results are also undefined. +** related objects have been created, the results are also undefined. ** ** The first argument to the sqlite3session_config() function must be one -** of the SQLITE_SESSION_CONFIG_XXX constants defined below. The +** of the SQLITE_SESSION_CONFIG_XXX constants defined below. The ** interpretation of the (void*) value passed as the second parameter and ** the effect of calling this function depends on the value of the first ** parameter. @@ -11126,13 +13347,239 @@ extern "C" ** This function returns SQLITE_OK if successful, or an SQLite error code ** otherwise. */ - SQLITE_API int sqlite3session_config(int op, void* pArg); +SQLITE_API int sqlite3session_config(int op, void *pArg); /* ** CAPI3REF: Values for sqlite3session_config(). */ #define SQLITE_SESSION_CONFIG_STRMSIZE 1 +/* +** CAPI3REF: Configure a changegroup object +** +** Configure the changegroup object passed as the first argument. +** At present the only valid value for the second parameter is +** [SQLITE_CHANGEGROUP_CONFIG_PATCHSET]. +*/ +SQLITE_API int sqlite3changegroup_config(sqlite3_changegroup*, int, void *pArg); + +/* +** CAPI3REF: Options for sqlite3changegroup_config(). +** +** The following values may be passed as the 2nd parameter to +** sqlite3changegroup_config(). +** +**
    SQLITE_CHANGEGROUP_CONFIG_PATCHSET
    +** A changegroup object generates either a changeset or patchset. Usually, +** this is determined by whether the first call to sqlite3changegroup_add() +** is passed a changeset or a patchset. Or, if the first changes are added +** to the changegroup object using the sqlite3changegroup_change_xxx() +** APIs, then this option may be used to configure whether the changegroup +** object generates a changeset or patchset. +** +** When this option is invoked, parameter pArg must point to a value of +** type int. If the changegroup currently contains zero changes, and the +** value of the int variable is zero or greater than zero, then the +** changegroup is configured to generate a changeset or patchset, +** respectively. It is a no-op, not an error, if the changegroup is not +** configured because it has already started accumulating changes. +** +** Before returning, the int variable is set to 0 if the changegroup is +** configured to generate a changeset, or 1 if it is configured to generate +** a patchset. +*/ +#define SQLITE_CHANGEGROUP_CONFIG_PATCHSET 1 + + +/* +** CAPI3REF: Begin adding a change to a changegroup +** +** This API is used, in concert with other sqlite3changegroup_change_xxx() +** APIs, to add changes to a changegroup object one at a time. To add a +** single change, the caller must: +** +** 1. Invoke sqlite3changegroup_change_begin() to indicate the type of +** change (INSERT, UPDATE or DELETE), the affected table and whether +** or not the change should be marked as indirect. +** +** 2. Invoke sqlite3changegroup_change_int64() or one of the other four +** value functions - _null(), _double(), _text() or _blob() - one or +** more times to specify old.* and new.* values for the change being +** constructed. +** +** 3. Invoke sqlite3changegroup_change_finish() to either finish adding +** the change to the group, or to discard the change altogether. +** +** The first argument to this function must be a pointer to the existing +** changegroup object that the change will be added to. The second argument +** must be SQLITE_INSERT, SQLITE_UPDATE or SQLITE_DELETE. The third is the +** name of the table that the change affects, and the fourth is a boolean +** flag specifying whether the change should be marked as "indirect" (if +** bIndirect is non-zero) or not indirect (if bIndirect is zero). +** +** Following a successful call to this function, this function may not be +** called again on the same changegroup object until after +** sqlite3changegroup_change_finish() has been called. Doing so is an +** SQLITE_MISUSE error. +** +** The changegroup object passed as the first argument must be already +** configured with schema data for the specified table. It may be configured +** either by calling sqlite3changegroup_schema() with a database that contains +** the table, or sqlite3changegroup_add() with a changeset that contains the +** table. If the changegroup object has not been configured with a schema for +** the specified table when this function is called, SQLITE_ERROR is returned. +** +** If successful, SQLITE_OK is returned. Otherwise, if an error occurs, an +** SQLite error code is returned. In this case, if argument pzErr is non-NULL, +** then (*pzErr) may be set to point to a buffer containing a utf-8 formated, +** nul-terminated, English language error message. It is the responsibility +** of the caller to eventually free this buffer using sqlite3_free(). +*/ +SQLITE_API int sqlite3changegroup_change_begin( + sqlite3_changegroup*, + int eOp, + const char *zTab, + int bIndirect, + char **pzErr +); + +/* +** CAPI3REF: Add a 64-bit integer to a changegroup +** +** This function may only be called between a successful call to +** sqlite3changegroup_change_begin() and its matching +** sqlite3changegroup_change_finish() call. If it is called at any +** other time, it is an SQLITE_MISUSE error. Calling this function +** specifies a 64-bit integer value to be used in the change currently being +** added to the changegroup object passed as the first argument. +** +** The second parameter, bNew, specifies whether the value is to be part of +** the new.* (if bNew is non-zero) or old.* (if bNew is zero) record of +** the change under construction. If this does not match the type of change +** specified by the preceding call to sqlite3changegroup_change_begin() (i.e. +** an old.* value for an SQLITE_INSERT change, or a new.* value for an +** SQLITE_DELETE), then SQLITE_ERROR is returned. +** +** The third parameter specifies the column of the old.* or new.* record that +** the value will be a part of. If the specified table has an explicit primary +** key, then this is the index of the table column, numbered from 0 in the order +** specified within the CREATE TABLE statement. Or, if the table uses an +** implicit rowid key, then the column 0 is the rowid and the explicit columns +** are numbered starting from 1. If the iCol parameter is less than 0 or greater +** than the index of the last column in the table, SQLITE_RANGE is returned. +** +** The fourth parameter is the integer value to use as part of the old.* or +** new.* record. +** +** If this call is successful, SQLITE_OK is returned. Otherwise, if an +** error occurs, an SQLite error code is returned. +*/ +SQLITE_API int sqlite3changegroup_change_int64( + sqlite3_changegroup*, + int bNew, + int iCol, + sqlite3_int64 iVal +); + +/* +** CAPI3REF: Add a NULL to a changegroup +** +** This function is similar to sqlite3changegroup_change_int64(). Except that +** it configures the change currently under construction with a NULL value +** instead of a 64-bit integer. +*/ +SQLITE_API int sqlite3changegroup_change_null(sqlite3_changegroup*, int, int); + +/* +** CAPI3REF: Add an double to a changegroup +** +** This function is similar to sqlite3changegroup_change_int64(). Except that +** it configures the change currently being constructed with a real value +** instead of a 64-bit integer. +*/ +SQLITE_API int sqlite3changegroup_change_double(sqlite3_changegroup*, int, int, double); + +/* +** CAPI3REF: Add a text value to a changegroup +** +** This function is similar to sqlite3changegroup_change_int64(). It configures +** the currently accumulated change with a text value instead of a 64-bit +** integer. Parameter pVal points to a buffer containing the text encoded using +** utf-8. Parameter nVal may either be the size of the text value in bytes, or +** else a negative value, in which case the buffer pVal points to is assumed to +** be nul-terminated. +*/ +SQLITE_API int sqlite3changegroup_change_text( + sqlite3_changegroup*, int, int, const char *pVal, int nVal +); + +/* +** CAPI3REF: Add a blob to a changegroup +** +** This function is similar to sqlite3changegroup_change_int64(). It configures +** the currently accumulated change with a blob value instead of a 64-bit +** integer. Parameter pVal points to a buffer containing the blob. Parameter +** nVal is the size of the blob in bytes. +*/ +SQLITE_API int sqlite3changegroup_change_blob( + sqlite3_changegroup*, int, int, const void *pVal, int nVal +); + +/* +** CAPI3REF: Finish adding one-at-at-time changes to a changegroup +** +** This function may only be called following a successful call to +** sqlite3changegroup_change_begin(). Otherwise, it is an SQLITE_MISUSE error. +** +** If parameter bDiscard is non-zero, then the current change is simply +** discarded. In this case this function is always successful and SQLITE_OK +** returned. +** +** If parameter bDiscard is zero, then an attempt is made to add the current +** change to the changegroup. Assuming the changegroup is configured to +** produce a changeset (not a patchset), this requires that: +** +** * If the change is an INSERT or DELETE, then a value must be specified +** for all columns of the new.* or old.* record, respectively. +** +** * If the change is an UPDATE record, then values must be provided for +** the PRIMARY KEY columns of the old.* record, but must not be provided +** for PRIMARY KEY columns of the new.* record. +** +** * If the change is an UPDATE record, then for each non-PRIMARY KEY +** column in the old.* record for which a value has been provided, a +** value must also be provided for the same column in the new.* record. +** Similarly, for each non-PK column in the old.* record for which +** a value is not provided, a value must not be provided for the same +** column in the new.* record. +** +** * All values specified for PRIMARY KEY columns must be non-NULL. +** +** Otherwise, it is an error. +** +** If the changegroup already contains a change for the same row (identified +** by PRIMARY KEY columns), then the current change is combined with the +** existing change in the same way as for sqlite3changegroup_add(). +** +** For a patchset, all of the above rules apply except that it doesn't matter +** whether or not values are provided for the non-PK old.* record columns +** for an UPDATE or DELETE change. This means that code used to produce +** a changeset using the sqlite3changegroup_change_xxx() APIs may also +** be used to produce patchsets. +** +** If the call is successful, SQLITE_OK is returned. Otherwise, if an error +** occurs, an SQLite error code is returned. If an error is returned and +** parameter pzErr is not NULL, then (*pzErr) may be set to point to a buffer +** containing a nul-terminated, utf-8 encoded, English language error message. +** It is the responsibility of the caller to eventually free any such error +** message buffer using sqlite3_free(). +*/ +SQLITE_API int sqlite3changegroup_change_finish( + sqlite3_changegroup*, + int bDiscard, + char **pzErr +); + /* ** Make sure we can call this stuff from C++. */ @@ -11140,7 +13587,7 @@ extern "C" } #endif -#endif /* !defined(__SQLITESESSION_H_) && defined(SQLITE_ENABLE_SESSION) */ +#endif /* !defined(__SQLITESESSION_H_) && defined(SQLITE_ENABLE_SESSION) */ /******** End of sqlite3session.h *********/ /******** Begin file fts5.h *********/ @@ -11156,63 +13603,63 @@ extern "C" ** ****************************************************************************** ** -** Interfaces to extend FTS5. Using the interfaces defined in this file, +** Interfaces to extend FTS5. Using the interfaces defined in this file, ** FTS5 may be extended with: ** ** * custom tokenizers, and ** * custom auxiliary functions. */ + #ifndef _FTS5_H #define _FTS5_H + #ifdef __cplusplus -extern "C" -{ +extern "C" { #endif - /************************************************************************* +/************************************************************************* ** CUSTOM AUXILIARY FUNCTIONS ** ** Virtual table implementations may overload SQL functions by implementing ** the sqlite3_module.xFindFunction() method. */ - typedef struct Fts5ExtensionApi Fts5ExtensionApi; - typedef struct Fts5Context Fts5Context; - typedef struct Fts5PhraseIter Fts5PhraseIter; +typedef struct Fts5ExtensionApi Fts5ExtensionApi; +typedef struct Fts5Context Fts5Context; +typedef struct Fts5PhraseIter Fts5PhraseIter; - typedef void (*fts5_extension_function)( - const Fts5ExtensionApi* pApi, /* API offered by current FTS version */ - Fts5Context* pFts, /* First arg to pass to pApi functions */ - sqlite3_context* pCtx, /* Context for returning result/error */ - int nVal, /* Number of values in apVal[] array */ - sqlite3_value** apVal /* Array of trailing arguments */ - ); +typedef void (*fts5_extension_function)( + const Fts5ExtensionApi *pApi, /* API offered by current FTS version */ + Fts5Context *pFts, /* First arg to pass to pApi functions */ + sqlite3_context *pCtx, /* Context for returning result/error */ + int nVal, /* Number of values in apVal[] array */ + sqlite3_value **apVal /* Array of trailing arguments */ +); - struct Fts5PhraseIter - { - const unsigned char* a; - const unsigned char* b; - }; +struct Fts5PhraseIter { + const unsigned char *a; + const unsigned char *b; +}; - /* +/* ** EXTENSION API FUNCTIONS ** ** xUserData(pFts): -** Return a copy of the context pointer the extension function was -** registered with. +** Return a copy of the pUserData pointer passed to the xCreateFunction() +** API when the extension function was registered. ** ** xColumnTotalSize(pFts, iCol, pnToken): ** If parameter iCol is less than zero, set output variable *pnToken ** to the total number of tokens in the FTS5 table. Or, if iCol is ** non-negative but less than the number of columns in the table, return -** the total number of tokens in column iCol, considering all rows in +** the total number of tokens in column iCol, considering all rows in ** the FTS5 table. ** ** If parameter iCol is greater than or equal to the number of columns ** in the table, SQLITE_RANGE is returned. Or, if an error occurs (e.g. -** an OOM condition or IO error), an appropriate SQLite error code is +** an OOM condition or IO error), an appropriate SQLite error code is ** returned. ** ** xColumnCount(pFts): @@ -11226,15 +13673,18 @@ extern "C" ** ** If parameter iCol is greater than or equal to the number of columns ** in the table, SQLITE_RANGE is returned. Or, if an error occurs (e.g. -** an OOM condition or IO error), an appropriate SQLite error code is +** an OOM condition or IO error), an appropriate SQLite error code is ** returned. ** ** This function may be quite inefficient if used with an FTS5 table ** created with the "columnsize=0" option. ** ** xColumnText: -** This function attempts to retrieve the text of column iCol of the -** current document. If successful, (*pz) is set to point to a buffer +** If parameter iCol is less than zero, or greater than or equal to the +** number of columns in the table, SQLITE_RANGE is returned. +** +** Otherwise, this function attempts to retrieve the text of column iCol of +** the current document. If successful, (*pz) is set to point to a buffer ** containing the text in utf-8 encoding, (*pn) is set to the size in bytes ** (not characters) of the buffer and SQLITE_OK is returned. Otherwise, ** if an error occurs, an SQLite error code is returned and the final values @@ -11244,8 +13694,10 @@ extern "C" ** Returns the number of phrases in the current query expression. ** ** xPhraseSize: -** Returns the number of tokens in phrase iPhrase of the query. Phrases -** are numbered starting from zero. +** If parameter iCol is less than zero, or greater than or equal to the +** number of phrases in the current query, as returned by xPhraseCount, +** 0 is returned. Otherwise, this function returns the number of tokens in +** phrase iPhrase of the query. Phrases are numbered starting from zero. ** ** xInstCount: ** Set *pnInst to the total number of occurrences of all phrases within @@ -11253,23 +13705,24 @@ extern "C" ** an error code (i.e. SQLITE_NOMEM) if an error occurs. ** ** This API can be quite slow if used with an FTS5 table created with the -** "detail=none" or "detail=column" option. If the FTS5 table is created -** with either "detail=none" or "detail=column" and "content=" option +** "detail=none" or "detail=column" option. If the FTS5 table is created +** with either "detail=none" or "detail=column" and "content=" option ** (i.e. if it is a contentless table), then this API always returns 0. ** ** xInst: ** Query for the details of phrase match iIdx within the current row. ** Phrase matches are numbered starting from zero, so the iIdx argument ** should be greater than or equal to zero and smaller than the value -** output by xInstCount(). +** output by xInstCount(). If iIdx is less than zero or greater than +** or equal to the value returned by xInstCount(), SQLITE_RANGE is returned. ** -** Usually, output parameter *piPhrase is set to the phrase number, *piCol +** Otherwise, output parameter *piPhrase is set to the phrase number, *piCol ** to the column in which it occurs and *piOff the token offset of the -** first token of the phrase. Returns SQLITE_OK if successful, or an error -** code (i.e. SQLITE_NOMEM) if an error occurs. +** first token of the phrase. SQLITE_OK is returned if successful, or an +** error code (i.e. SQLITE_NOMEM) if an error occurs. ** ** This API can be quite slow if used with an FTS5 table created with the -** "detail=none" or "detail=column" option. +** "detail=none" or "detail=column" option. ** ** xRowid: ** Returns the rowid of the current row. @@ -11285,13 +13738,17 @@ extern "C" ** ** with $p set to a phrase equivalent to the phrase iPhrase of the ** current query is executed. Any column filter that applies to -** phrase iPhrase of the current query is included in $p. For each -** row visited, the callback function passed as the fourth argument -** is invoked. The context and API objects passed to the callback +** phrase iPhrase of the current query is included in $p. For each +** row visited, the callback function passed as the fourth argument +** is invoked. The context and API objects passed to the callback ** function may be used to access the properties of each matched row. -** Invoking Api.xUserData() returns a copy of the pointer passed as +** Invoking Api.xUserData() returns a copy of the pointer passed as ** the third argument to pUserData. ** +** If parameter iPhrase is less than zero, or greater than or equal to +** the number of phrases in the query, as returned by xPhraseCount(), +** this function returns SQLITE_RANGE. +** ** If the callback function returns any value other than SQLITE_OK, the ** query is abandoned and the xQueryPhrase function returns immediately. ** If the returned value is SQLITE_DONE, xQueryPhrase returns SQLITE_OK. @@ -11304,14 +13761,14 @@ extern "C" ** ** xSetAuxdata(pFts5, pAux, xDelete) ** -** Save the pointer passed as the second argument as the extension functions +** Save the pointer passed as the second argument as the extension function's ** "auxiliary data". The pointer may then be retrieved by the current or any ** future invocation of the same fts5 extension function made as part of ** the same MATCH query using the xGetAuxdata() API. ** ** Each extension function is allocated a single auxiliary data slot for -** each FTS query (MATCH expression). If the extension function is invoked -** more than once for a single FTS query, then all invocations share a +** each FTS query (MATCH expression). If the extension function is invoked +** more than once for a single FTS query, then all invocations share a ** single auxiliary data context. ** ** If there is already an auxiliary data pointer when this function is @@ -11330,7 +13787,7 @@ extern "C" ** ** xGetAuxdata(pFts5, bClear) ** -** Returns the current auxiliary data pointer for the fts5 extension +** Returns the current auxiliary data pointer for the fts5 extension ** function. See the xSetAuxdata() method for details. ** ** If the bClear argument is non-zero, then the auxiliary data is cleared @@ -11350,7 +13807,7 @@ extern "C" ** method, to iterate through all instances of a single query phrase within ** the current row. This is the same information as is accessible via the ** xInstCount/xInst APIs. While the xInstCount/xInst APIs are more convenient -** to use, this API may be faster under some circumstances. To iterate +** to use, this API may be faster under some circumstances. To iterate ** through instances of phrase iPhrase, use the following code: ** ** Fts5PhraseIter iter; @@ -11368,11 +13825,15 @@ extern "C" ** xPhraseFirstColumn() and xPhraseNextColumn() as illustrated below). ** ** This API can be quite slow if used with an FTS5 table created with the -** "detail=none" or "detail=column" option. If the FTS5 table is created -** with either "detail=none" or "detail=column" and "content=" option +** "detail=none" or "detail=column" option. If the FTS5 table is created +** with either "detail=none" or "detail=column" and "content=" option ** (i.e. if it is a contentless table), then this API always iterates ** through an empty set (all calls to xPhraseFirst() set iCol to -1). ** +** In all cases, matches are visited in (column ASC, offset ASC) order. +** i.e. all those in column 0, sorted by offset, followed by those in +** column 1, etc. +** ** xPhraseNext() ** See xPhraseFirst above. ** @@ -11393,67 +13854,154 @@ extern "C" ** } ** ** This API can be quite slow if used with an FTS5 table created with the -** "detail=none" option. If the FTS5 table is created with either -** "detail=none" "content=" option (i.e. if it is a contentless table), -** then this API always iterates through an empty set (all calls to +** "detail=none" option. If the FTS5 table is created with either +** "detail=none" "content=" option (i.e. if it is a contentless table), +** then this API always iterates through an empty set (all calls to ** xPhraseFirstColumn() set iCol to -1). ** ** The information accessed using this API and its companion ** xPhraseFirstColumn() may also be obtained using xPhraseFirst/xPhraseNext ** (or xInst/xInstCount). The chief advantage of this API is that it is ** significantly more efficient than those alternatives when used with -** "detail=column" tables. +** "detail=column" tables. ** ** xPhraseNextColumn() ** See xPhraseFirstColumn above. -*/ - struct Fts5ExtensionApi - { - int iVersion; /* Currently always set to 3 */ - - void* (*xUserData)(Fts5Context*); - - int (*xColumnCount)(Fts5Context*); - int (*xRowCount)(Fts5Context*, sqlite3_int64* pnRow); - int (*xColumnTotalSize)(Fts5Context*, int iCol, sqlite3_int64* pnToken); - - int (*xTokenize)(Fts5Context*, - const char* pText, int nText, /* Text to tokenize */ - void* pCtx, /* Context passed to xToken() */ - int (*xToken)(void*, int, const char*, int, int, int) /* Callback */ - ); - - int (*xPhraseCount)(Fts5Context*); - int (*xPhraseSize)(Fts5Context*, int iPhrase); - - int (*xInstCount)(Fts5Context*, int* pnInst); - int (*xInst)(Fts5Context*, int iIdx, int* piPhrase, int* piCol, int* piOff); - - sqlite3_int64 (*xRowid)(Fts5Context*); - int (*xColumnText)(Fts5Context*, int iCol, const char** pz, int* pn); - int (*xColumnSize)(Fts5Context*, int iCol, int* pnToken); - - int (*xQueryPhrase)(Fts5Context*, int iPhrase, void* pUserData, - int (*)(const Fts5ExtensionApi*, Fts5Context*, void*)); - int (*xSetAuxdata)(Fts5Context*, void* pAux, void (*xDelete)(void*)); - void* (*xGetAuxdata)(Fts5Context*, int bClear); - - int (*xPhraseFirst)(Fts5Context*, int iPhrase, Fts5PhraseIter*, int*, int*); - void (*xPhraseNext)(Fts5Context*, Fts5PhraseIter*, int* piCol, int* piOff); - - int (*xPhraseFirstColumn)(Fts5Context*, int iPhrase, Fts5PhraseIter*, int*); - void (*xPhraseNextColumn)(Fts5Context*, Fts5PhraseIter*, int* piCol); - }; +** +** xQueryToken(pFts5, iPhrase, iToken, ppToken, pnToken) +** This is used to access token iToken of phrase iPhrase of the current +** query. Before returning, output parameter *ppToken is set to point +** to a buffer containing the requested token, and *pnToken to the +** size of this buffer in bytes. +** +** If iPhrase or iToken are less than zero, or if iPhrase is greater than +** or equal to the number of phrases in the query as reported by +** xPhraseCount(), or if iToken is equal to or greater than the number of +** tokens in the phrase, SQLITE_RANGE is returned and *ppToken and *pnToken + are both zeroed. +** +** The output text is not a copy of the query text that specified the +** token. It is the output of the tokenizer module. For tokendata=1 +** tables, this includes any embedded 0x00 and trailing data. +** +** xInstToken(pFts5, iIdx, iToken, ppToken, pnToken) +** This is used to access token iToken of phrase hit iIdx within the +** current row. If iIdx is less than zero or greater than or equal to the +** value returned by xInstCount(), SQLITE_RANGE is returned. Otherwise, +** output variable (*ppToken) is set to point to a buffer containing the +** matching document token, and (*pnToken) to the size of that buffer in +** bytes. +** +** The output text is not a copy of the document text that was tokenized. +** It is the output of the tokenizer module. For tokendata=1 tables, this +** includes any embedded 0x00 and trailing data. +** +** This API may be slow in some cases if the token identified by parameters +** iIdx and iToken matched a prefix token in the query. In most cases, the +** first call to this API for each prefix token in the query is forced +** to scan the portion of the full-text index that matches the prefix +** token to collect the extra data required by this API. If the prefix +** token matches a large number of token instances in the document set, +** this may be a performance problem. +** +** If the user knows in advance that a query may use this API for a +** prefix token, FTS5 may be configured to collect all required data as part +** of the initial querying of the full-text index, avoiding the second scan +** entirely. This also causes prefix queries that do not use this API to +** run more slowly and use more memory. FTS5 may be configured in this way +** either on a per-table basis using the [FTS5 insttoken | 'insttoken'] +** option, or on a per-query basis using the +** [fts5_insttoken | fts5_insttoken()] user function. +** +** This API can be quite slow if used with an FTS5 table created with the +** "detail=none" or "detail=column" option. +** +** xColumnLocale(pFts5, iIdx, pzLocale, pnLocale) +** If parameter iCol is less than zero, or greater than or equal to the +** number of columns in the table, SQLITE_RANGE is returned. +** +** Otherwise, this function attempts to retrieve the locale associated +** with column iCol of the current row. Usually, there is no associated +** locale, and output parameters (*pzLocale) and (*pnLocale) are set +** to NULL and 0, respectively. However, if the fts5_locale() function +** was used to associate a locale with the value when it was inserted +** into the fts5 table, then (*pzLocale) is set to point to a nul-terminated +** buffer containing the name of the locale in utf-8 encoding. (*pnLocale) +** is set to the size in bytes of the buffer, not including the +** nul-terminator. +** +** If successful, SQLITE_OK is returned. Or, if an error occurs, an +** SQLite error code is returned. The final value of the output parameters +** is undefined in this case. +** +** xTokenize_v2: +** Tokenize text using the tokenizer belonging to the FTS5 table. This +** API is the same as the xTokenize() API, except that it allows a tokenizer +** locale to be specified. +*/ +struct Fts5ExtensionApi { + int iVersion; /* Currently always set to 4 */ + + void *(*xUserData)(Fts5Context*); + + int (*xColumnCount)(Fts5Context*); + int (*xRowCount)(Fts5Context*, sqlite3_int64 *pnRow); + int (*xColumnTotalSize)(Fts5Context*, int iCol, sqlite3_int64 *pnToken); + + int (*xTokenize)(Fts5Context*, + const char *pText, int nText, /* Text to tokenize */ + void *pCtx, /* Context passed to xToken() */ + int (*xToken)(void*, int, const char*, int, int, int) /* Callback */ + ); + + int (*xPhraseCount)(Fts5Context*); + int (*xPhraseSize)(Fts5Context*, int iPhrase); + + int (*xInstCount)(Fts5Context*, int *pnInst); + int (*xInst)(Fts5Context*, int iIdx, int *piPhrase, int *piCol, int *piOff); + + sqlite3_int64 (*xRowid)(Fts5Context*); + int (*xColumnText)(Fts5Context*, int iCol, const char **pz, int *pn); + int (*xColumnSize)(Fts5Context*, int iCol, int *pnToken); + + int (*xQueryPhrase)(Fts5Context*, int iPhrase, void *pUserData, + int(*)(const Fts5ExtensionApi*,Fts5Context*,void*) + ); + int (*xSetAuxdata)(Fts5Context*, void *pAux, void(*xDelete)(void*)); + void *(*xGetAuxdata)(Fts5Context*, int bClear); + + int (*xPhraseFirst)(Fts5Context*, int iPhrase, Fts5PhraseIter*, int*, int*); + void (*xPhraseNext)(Fts5Context*, Fts5PhraseIter*, int *piCol, int *piOff); + + int (*xPhraseFirstColumn)(Fts5Context*, int iPhrase, Fts5PhraseIter*, int*); + void (*xPhraseNextColumn)(Fts5Context*, Fts5PhraseIter*, int *piCol); + + /* Below this point are iVersion>=3 only */ + int (*xQueryToken)(Fts5Context*, + int iPhrase, int iToken, + const char **ppToken, int *pnToken + ); + int (*xInstToken)(Fts5Context*, int iIdx, int iToken, const char**, int*); + + /* Below this point are iVersion>=4 only */ + int (*xColumnLocale)(Fts5Context*, int iCol, const char **pz, int *pn); + int (*xTokenize_v2)(Fts5Context*, + const char *pText, int nText, /* Text to tokenize */ + const char *pLocale, int nLocale, /* Locale to pass to tokenizer */ + void *pCtx, /* Context passed to xToken() */ + int (*xToken)(void*, int, const char*, int, int, int) /* Callback */ + ); +}; - /* +/* ** CUSTOM AUXILIARY FUNCTIONS *************************************************************************/ - /************************************************************************* +/************************************************************************* ** CUSTOM TOKENIZERS ** -** Applications may also register custom tokenizer types. A tokenizer -** is registered by providing fts5 with a populated instance of the +** Applications may also register custom tokenizer types. A tokenizer +** is registered by providing fts5 with a populated instance of the ** following structure. All structure methods must be defined, setting ** any member of the fts5_tokenizer struct to NULL leads to undefined ** behaviour. The structure methods are expected to function as follows: @@ -11463,17 +14011,17 @@ extern "C" ** A tokenizer instance is required to actually tokenize text. ** ** The first argument passed to this function is a copy of the (void*) -** pointer provided by the application when the fts5_tokenizer object -** was registered with FTS5 (the third argument to xCreateTokenizer()). +** pointer provided by the application when the fts5_tokenizer_v2 object +** was registered with FTS5 (the third argument to xCreateTokenizer()). ** The second and third arguments are an array of nul-terminated strings ** containing the tokenizer arguments, if any, specified following the ** tokenizer name as part of the CREATE VIRTUAL TABLE statement used ** to create the FTS5 table. ** -** The final argument is an output variable. If successful, (*ppOut) +** The final argument is an output variable. If successful, (*ppOut) ** should be set to point to the new tokenizer handle and SQLITE_OK ** returned. If an error occurs, some value other than SQLITE_OK should -** be returned. In this case, fts5 assumes that the final value of *ppOut +** be returned. In this case, fts5 assumes that the final value of *ppOut ** is undefined. ** ** xDelete: @@ -11482,12 +14030,12 @@ extern "C" ** be invoked exactly once for each successful call to xCreate(). ** ** xTokenize: -** This function is expected to tokenize the nText byte string indicated +** This function is expected to tokenize the nText byte string indicated ** by argument pText. pText may or may not be nul-terminated. The first ** argument passed to this function is a pointer to an Fts5Tokenizer object ** returned by an earlier call to xCreate(). ** -** The second argument indicates the reason that FTS5 is requesting +** The third argument indicates the reason that FTS5 is requesting ** tokenization of the supplied text. This is always one of the following ** four values: ** @@ -11496,8 +14044,8 @@ extern "C" ** determine the set of tokens to add to (or delete from) the ** FTS index. ** -**
  • FTS5_TOKENIZE_QUERY - A MATCH query is being executed -** against the FTS index. The tokenizer is being called to tokenize +**
  • FTS5_TOKENIZE_QUERY - A MATCH query is being executed +** against the FTS index. The tokenizer is being called to tokenize ** a bareword or quoted string specified as part of the query. ** **
  • (FTS5_TOKENIZE_QUERY | FTS5_TOKENIZE_PREFIX) - Same as @@ -11505,12 +14053,19 @@ extern "C" ** followed by a "*" character, indicating that the last token ** returned by the tokenizer will be treated as a token prefix. ** -**
  • FTS5_TOKENIZE_AUX - The tokenizer is being invoked to +**
  • FTS5_TOKENIZE_AUX - The tokenizer is being invoked to ** satisfy an fts5_api.xTokenize() request made by an auxiliary ** function. Or an fts5_api.xColumnSize() request made by the same -** on a columnsize=0 database. +** on a columnsize=0 database. ** ** +** The sixth and seventh arguments passed to xTokenize() - pLocale and +** nLocale - are a pointer to a buffer containing the locale to use for +** tokenization (e.g. "en_US") and its size in bytes, respectively. The +** pLocale buffer is not nul-terminated. pLocale may be passed NULL (in +** which case nLocale is always 0) to indicate that the tokenizer should +** use its default locale. +** ** For each token in the input string, the supplied callback xToken() must ** be invoked. The first argument to it should be a copy of the pointer ** passed as the second argument to xTokenize(). The third and fourth @@ -11520,10 +14075,10 @@ extern "C" ** which the token is derived within the input. ** ** The second argument passed to the xToken() callback ("tflags") should -** normally be set to 0. The exception is if the tokenizer supports +** normally be set to 0. The exception is if the tokenizer supports ** synonyms. In this case see the discussion below for details. ** -** FTS5 assumes the xToken() callback is invoked for each token in the +** FTS5 assumes the xToken() callback is invoked for each token in the ** order that they occur within the input text. ** ** If an xToken() callback returns any value other than SQLITE_OK, then @@ -11534,10 +14089,34 @@ extern "C" ** may abandon the tokenization and return any error code other than ** SQLITE_OK or SQLITE_DONE. ** +** If the tokenizer is registered using an fts5_tokenizer_v2 object, +** then the xTokenize() method has two additional arguments - pLocale +** and nLocale. These specify the locale that the tokenizer should use +** for the current request. If pLocale and nLocale are both 0, then the +** tokenizer should use its default locale. Otherwise, pLocale points to +** an nLocale byte buffer containing the name of the locale to use as utf-8 +** text. pLocale is not nul-terminated. +** +** FTS5_TOKENIZER +** +** There is also an fts5_tokenizer object. This is an older, deprecated, +** version of fts5_tokenizer_v2. It is similar except that: +** +**
      +**
    • There is no "iVersion" field, and +**
    • The xTokenize() method does not take a locale argument. +**
    +** +** Legacy fts5_tokenizer tokenizers must be registered using the +** legacy xCreateTokenizer() function, instead of xCreateTokenizer_v2(). +** +** Tokenizer implementations registered using either API may be retrieved +** using both xFindTokenizer() and xFindTokenizer_v2(). +** ** SYNONYM SUPPORT ** ** Custom tokenizers may also support synonyms. Consider a case in which a -** user wishes to query for a phrase such as "first place". Using the +** user wishes to query for a phrase such as "first place". Using the ** built-in tokenizers, the FTS5 query 'first + place' will match instances ** of "first place" within the document set, but not alternative forms ** such as "1st place". In some applications, it would be better to match @@ -11546,8 +14125,8 @@ extern "C" ** ** There are several ways to approach this in FTS5: ** -**
    1. By mapping all synonyms to a single token. In this case, the -** In the above example, this means that the tokenizer returns the +**
      1. By mapping all synonyms to a single token. In this case, using +** the above example, this means that the tokenizer returns the ** same token for inputs "first" and "1st". Say that token is in ** fact "first", so that when the user inserts the document "I won ** 1st place" entries are added to the index for tokens "i", "won", @@ -11557,34 +14136,34 @@ extern "C" ** **
      2. By querying the index for all synonyms of each query term ** separately. In this case, when tokenizing query text, the -** tokenizer may provide multiple synonyms for a single term -** within the document. FTS5 then queries the index for each +** tokenizer may provide multiple synonyms for a single term +** within the document. FTS5 then queries the index for each ** synonym individually. For example, faced with the query: ** ** ** ... MATCH 'first place' ** ** the tokenizer offers both "1st" and "first" as synonyms for the -** first token in the MATCH query and FTS5 effectively runs a query +** first token in the MATCH query and FTS5 effectively runs a query ** similar to: ** ** ** ... MATCH '(first OR 1st) place' ** ** except that, for the purposes of auxiliary functions, the query -** still appears to contain just two phrases - "(first OR 1st)" +** still appears to contain just two phrases - "(first OR 1st)" ** being treated as a single phrase. ** **
      3. By adding multiple synonyms for a single term to the FTS index. ** Using this method, when tokenizing document text, the tokenizer -** provides multiple synonyms for each token. So that when a +** provides multiple synonyms for each token. So that when a ** document such as "I won first place" is tokenized, entries are ** added to the FTS index for "i", "won", "first", "1st" and ** "place". ** ** This way, even if the tokenizer does not provide synonyms ** when tokenizing query text (it should not - to do so would be -** inefficient), it doesn't matter if the user queries for +** inefficient), it doesn't matter if the user queries for ** 'first + place' or '1st + place', as there are entries in the ** FTS index corresponding to both forms of the first token. **
      @@ -11605,11 +14184,11 @@ extern "C" ** ** It is an error to specify the FTS5_TOKEN_COLOCATED flag the first time ** xToken() is called. Multiple synonyms may be specified for a single token -** by making multiple calls to xToken(FTS5_TOKEN_COLOCATED) in sequence. +** by making multiple calls to xToken(FTS5_TOKEN_COLOCATED) in sequence. ** There is no limit to the number of synonyms that may be provided for a ** single token. ** -** In many cases, method (1) above is the best approach. It does not add +** In many cases, method (1) above is the best approach. It does not add ** extra data to the FTS index or require FTS5 to query for multiple terms, ** so it is efficient in terms of disk space and query speed. However, it ** does not support prefix queries very well. If, as suggested above, the @@ -11621,100 +14200,150 @@ extern "C" ** will not match documents that contain the token "1st" (as the tokenizer ** will probably not map "1s" to any prefix of "first"). ** -** For full prefix support, method (3) may be preferred. In this case, +** For full prefix support, method (3) may be preferred. In this case, ** because the index contains entries for both "first" and "1st", prefix ** queries such as 'fi*' or '1s*' will match correctly. However, because ** extra entries are added to the FTS index, this method uses more space ** within the database. ** ** Method (2) offers a midpoint between (1) and (3). Using this method, -** a query such as '1s*' will match documents that contain the literal +** a query such as '1s*' will match documents that contain the literal ** token "1st", but not "first" (assuming the tokenizer is not able to ** provide synonyms for prefixes). However, a non-prefix query like '1st' ** will match against "1st" and "first". This method does not require -** extra disk space, as no extra entries are added to the FTS index. +** extra disk space, as no extra entries are added to the FTS index. ** On the other hand, it may require more CPU cycles to run MATCH queries, ** as separate queries of the FTS index are required for each synonym. ** ** When using methods (2) or (3), it is important that the tokenizer only -** provide synonyms when tokenizing document text (method (2)) or query -** text (method (3)), not both. Doing so will not cause any errors, but is +** provide synonyms when tokenizing document text (method (3)) or query +** text (method (2)), not both. Doing so will not cause any errors, but is ** inefficient. */ - typedef struct Fts5Tokenizer Fts5Tokenizer; - typedef struct fts5_tokenizer fts5_tokenizer; - struct fts5_tokenizer - { - int (*xCreate)(void*, const char** azArg, int nArg, Fts5Tokenizer** ppOut); - void (*xDelete)(Fts5Tokenizer*); - int (*xTokenize)(Fts5Tokenizer*, - void* pCtx, - int flags, /* Mask of FTS5_TOKENIZE_* flags */ - const char* pText, int nText, - int (*xToken)( - void* pCtx, /* Copy of 2nd argument to xTokenize() */ - int tflags, /* Mask of FTS5_TOKEN_* flags */ - const char* pToken, /* Pointer to buffer containing token */ - int nToken, /* Size of token in bytes */ - int iStart, /* Byte offset of token within input text */ - int iEnd /* Byte offset of end of token within input text */ - )); - }; +typedef struct Fts5Tokenizer Fts5Tokenizer; +typedef struct fts5_tokenizer_v2 fts5_tokenizer_v2; +struct fts5_tokenizer_v2 { + int iVersion; /* Currently always 2 */ + + int (*xCreate)(void*, const char **azArg, int nArg, Fts5Tokenizer **ppOut); + void (*xDelete)(Fts5Tokenizer*); + int (*xTokenize)(Fts5Tokenizer*, + void *pCtx, + int flags, /* Mask of FTS5_TOKENIZE_* flags */ + const char *pText, int nText, + const char *pLocale, int nLocale, + int (*xToken)( + void *pCtx, /* Copy of 2nd argument to xTokenize() */ + int tflags, /* Mask of FTS5_TOKEN_* flags */ + const char *pToken, /* Pointer to buffer containing token */ + int nToken, /* Size of token in bytes */ + int iStart, /* Byte offset of token within input text */ + int iEnd /* Byte offset of end of token within input text */ + ) + ); +}; + +/* +** New code should use the fts5_tokenizer_v2 type to define tokenizer +** implementations. The following type is included for legacy applications +** that still use it. +*/ +typedef struct fts5_tokenizer fts5_tokenizer; +struct fts5_tokenizer { + int (*xCreate)(void*, const char **azArg, int nArg, Fts5Tokenizer **ppOut); + void (*xDelete)(Fts5Tokenizer*); + int (*xTokenize)(Fts5Tokenizer*, + void *pCtx, + int flags, /* Mask of FTS5_TOKENIZE_* flags */ + const char *pText, int nText, + int (*xToken)( + void *pCtx, /* Copy of 2nd argument to xTokenize() */ + int tflags, /* Mask of FTS5_TOKEN_* flags */ + const char *pToken, /* Pointer to buffer containing token */ + int nToken, /* Size of token in bytes */ + int iStart, /* Byte offset of token within input text */ + int iEnd /* Byte offset of end of token within input text */ + ) + ); +}; + /* Flags that may be passed as the third argument to xTokenize() */ -#define FTS5_TOKENIZE_QUERY 0x0001 -#define FTS5_TOKENIZE_PREFIX 0x0002 -#define FTS5_TOKENIZE_DOCUMENT 0x0004 -#define FTS5_TOKENIZE_AUX 0x0008 +#define FTS5_TOKENIZE_QUERY 0x0001 +#define FTS5_TOKENIZE_PREFIX 0x0002 +#define FTS5_TOKENIZE_DOCUMENT 0x0004 +#define FTS5_TOKENIZE_AUX 0x0008 /* Flags that may be passed by the tokenizer implementation back to FTS5 ** as the third argument to the supplied xToken callback. */ -#define FTS5_TOKEN_COLOCATED 0x0001 /* Same position as prev. token */ +#define FTS5_TOKEN_COLOCATED 0x0001 /* Same position as prev. token */ - /* +/* ** END OF CUSTOM TOKENIZERS *************************************************************************/ - /************************************************************************* +/************************************************************************* ** FTS5 EXTENSION REGISTRATION API */ - typedef struct fts5_api fts5_api; - struct fts5_api - { - int iVersion; /* Currently always set to 2 */ - - /* Create a new tokenizer */ - int (*xCreateTokenizer)( - fts5_api* pApi, - const char* zName, - void* pContext, - fts5_tokenizer* pTokenizer, - void (*xDestroy)(void*)); - - /* Find an existing tokenizer */ - int (*xFindTokenizer)( - fts5_api* pApi, - const char* zName, - void** ppContext, - fts5_tokenizer* pTokenizer); - - /* Create a new auxiliary function */ - int (*xCreateFunction)( - fts5_api* pApi, - const char* zName, - void* pContext, - fts5_extension_function xFunction, - void (*xDestroy)(void*)); - }; - - /* +typedef struct fts5_api fts5_api; +struct fts5_api { + int iVersion; /* Currently always set to 3 */ + + /* Create a new tokenizer */ + int (*xCreateTokenizer)( + fts5_api *pApi, + const char *zName, + void *pUserData, + fts5_tokenizer *pTokenizer, + void (*xDestroy)(void*) + ); + + /* Find an existing tokenizer */ + int (*xFindTokenizer)( + fts5_api *pApi, + const char *zName, + void **ppUserData, + fts5_tokenizer *pTokenizer + ); + + /* Create a new auxiliary function */ + int (*xCreateFunction)( + fts5_api *pApi, + const char *zName, + void *pUserData, + fts5_extension_function xFunction, + void (*xDestroy)(void*) + ); + + /* APIs below this point are only available if iVersion>=3 */ + + /* Create a new tokenizer */ + int (*xCreateTokenizer_v2)( + fts5_api *pApi, + const char *zName, + void *pUserData, + fts5_tokenizer_v2 *pTokenizer, + void (*xDestroy)(void*) + ); + + /* Find an existing tokenizer */ + int (*xFindTokenizer_v2)( + fts5_api *pApi, + const char *zName, + void **ppUserData, + fts5_tokenizer_v2 **ppTokenizer + ); +}; + +/* ** END OF REGISTRATION API *************************************************************************/ #ifdef __cplusplus -} /* end of the 'extern "C"' block */ +} /* end of the 'extern "C"' block */ #endif #endif /* _FTS5_H */ /******** End of fts5.h *********/ +#endif /* SQLITE3_H */ diff --git a/src/plugins/splitcbn/combine.cpp b/src/plugins/splitcbn/combine.cpp index 37140ba0..e8567ef8 100644 --- a/src/plugins/splitcbn/combine.cpp +++ b/src/plugins/splitcbn/combine.cpp @@ -9,6 +9,9 @@ #include "lang\lang.rh" #include "combine.h" #include "dialogs.h" +#include "..\..\operation_result.h" + +#include // ***************************************************************************** // @@ -17,6 +20,157 @@ #define BUFSIZE (512 * 1024) +namespace +{ +// Keep staged output beside the requested name so promotion never degrades into +// a cross-volume copy that could expose a partial destination. +static BOOL PromoteFileUtf8Local(const char* stagedName, const char* targetName) +{ + WCHAR* stagedNameW = Utf8AllocWide(stagedName); + WCHAR* targetNameW = Utf8AllocWide(targetName); + if (stagedNameW == NULL || targetNameW == NULL) + { + free(stagedNameW); + free(targetNameW); + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + + // ReplaceFile preserves destination metadata; MoveFileEx handles a target removed after selection. + BOOL ok = ReplaceFileW(targetNameW, stagedNameW, NULL, REPLACEFILE_WRITE_THROUGH, NULL, NULL); + DWORD error = ok ? ERROR_SUCCESS : GetLastError(); + if (!ok && error == ERROR_FILE_NOT_FOUND) + { + ok = MoveFileExW(stagedNameW, targetNameW, MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH); + error = ok ? ERROR_SUCCESS : GetLastError(); + } + free(stagedNameW); + free(targetNameW); + if (!ok) + SetLastError(error); + return ok; +} + +class CScopedSafeFile +{ +public: + CScopedSafeFile() : IsOpen(FALSE) { ZeroMemory(&File, sizeof(File)); } + ~CScopedSafeFile() { Close(); } + + SAFE_FILE* Get() { return &File; } + void Opened() { IsOpen = TRUE; } + void Close() + { + if (IsOpen) + { + SalamanderSafeFile->SafeFileClose(&File); + IsOpen = FALSE; + } + } + +private: + SAFE_FILE File; + BOOL IsOpen; +}; + +class CCombineTemporaryOutput +{ +public: + CCombineTemporaryOutput() : Reserved(FALSE) { Name[0] = 0; } + ~CCombineTemporaryOutput() + { + if (Reserved) + DeleteFileUtf8Local(Name); + } + + COperationResult Reserve(const char* targetName) + { + char targetDirectory[MAX_PATH]; + strncpy_s(targetDirectory, targetName, _TRUNCATE); + if (!SalamanderGeneral->CutDirectory(targetDirectory)) + return COperationResult::Failure(orpPrepareTransactionalTarget, ERROR_INVALID_NAME, NULL, targetName, FALSE); + + DWORD error = ERROR_SUCCESS; + // SalGetTempFileName creates the reservation, closing the race before the writer opens it. + if (!SalamanderGeneral->SalGetTempFileName(targetDirectory, "SCB", Name, TRUE, &error)) + return COperationResult::Failure(orpPrepareTransactionalTarget, + error != ERROR_SUCCESS ? error : ERROR_WRITE_FAULT, + NULL, targetName, FALSE); + + Reserved = TRUE; + return COperationResult::Success(orpPrepareTransactionalTarget, Name, targetName, opeTemporaryTargetReady); + } + + const char* GetName() const { return Name; } + void Commit() { Reserved = FALSE; } + + void Cleanup(COperationResult* result) + { + if (Reserved && !DeleteFileUtf8Local(Name)) + { + // The primary combine failure remains actionable even if temporary cleanup also fails. + result->AppendCleanupError(orcpDeleteUnverifiedTarget, GetLastError(), Name); + return; + } + Reserved = FALSE; + } + +private: + char Name[MAX_PATH]; + BOOL Reserved; +}; + +static COperationResult VerifyCombinedOutput(const char* outputName, const CQuadWord& expectedSize) +{ + HANDLE output = NOHANDLES(CreateFileUtf8Local(outputName, GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL)); + if (output == INVALID_HANDLE_VALUE) + return COperationResult::Failure(orpVerifyDurableCopy, GetLastError(), NULL, outputName, FALSE, + opeTemporaryTargetReady); + + BY_HANDLE_FILE_INFORMATION information; + BOOL verified = GetFileInformationByHandle(output, &information); + DWORD error = verified ? ERROR_SUCCESS : GetLastError(); + if (verified && ((information.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0 || + information.nFileSizeLow != expectedSize.LoDWord || + information.nFileSizeHigh != expectedSize.HiDWord)) + { + verified = FALSE; + error = ERROR_WRITE_FAULT; + } + + // Capture verification before closing; CloseHandle is only cleanup evidence on failure. + COperationResult result = verified ? COperationResult::Success(orpVerifyDurableCopy, NULL, outputName, + opeTemporaryTargetReady) : + COperationResult::Failure(orpVerifyDurableCopy, error, NULL, outputName, + FALSE, opeTemporaryTargetReady); + if (!NOHANDLES(CloseHandle(output))) + { + DWORD closeError = GetLastError(); + if (result.Succeeded()) + result = COperationResult::Failure(orpVerifyDurableCopy, closeError, NULL, outputName, FALSE, + opeTemporaryTargetReady); + else + result.AppendCleanupError(orcpCloseVerificationHandle, closeError, outputName); + } + return result; +} + +static BOOL ReportCombineFailure(const COperationResult& result, int idTitle, int idMessage) +{ + DWORD error = ERROR_SUCCESS; + result.ToLegacyBool(&error); // The plug-in keeps its BOOL contract while retaining the causal Win32 error. + SetLastError(error); + + char diagnostic[512]; + result.BuildDiagnosticSummary(diagnostic, _countof(diagnostic)); + TRACE_E("CombineFiles(): " << diagnostic); + SalamanderGeneral->ShowMessageBox(LoadStr(idMessage), LoadStr(idTitle), MSGBOX_ERROR); + return FALSE; +} +} + BOOL CombineFiles(TIndirectArray& files, LPTSTR targetName, BOOL bOnlyCrc, BOOL bTestCrc, UINT32& Crc, BOOL bTime, FILETIME* origTime, HWND parent, @@ -32,22 +186,34 @@ BOOL CombineFiles(TIndirectArray& files, LPTSTR targetName, int idTitle = bOnlyCrc ? IDS_CRCTITLE : IDS_COMBINE; - // sum sizes of all partial files (while simultaneously checking their accessibility) + // Sum checked 64-bit sizes while simultaneously checking each partial file's accessibility. CQuadWord totalSize = CQuadWord(0, 0); char text[MAX_PATH + 50]; int i; for (i = 0; i < files.Count; i++) { - SAFE_FILE file; - if (!SalamanderSafeFile->SafeFileOpen(&file, files[i], GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING, + CScopedSafeFile file; + if (!SalamanderSafeFile->SafeFileOpen(file.Get(), files[i], GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING, 0, parent, BUTTONS_RETRYCANCEL, NULL, NULL)) { return FALSE; } - CQuadWord size; - size.LoDWord = GetFileSize(file.HFile, &size.HiDWord); + file.Opened(); + CFileOffsetResult sizeResult = SalGetPluginFileSizeEx(SalamanderGeneral, file.Get()->HFile); + if (!sizeResult.Succeeded) + { + COperationResult result = COperationResult::Failure(orpVerifyDurableCopy, sizeResult.Error, + files[i], targetName, FALSE); + return ReportCombineFailure(result, idTitle, IDS_READERROR); + } + CQuadWord size = sizeResult.Value; + if (size.Value > (~(unsigned __int64)0) - totalSize.Value) + { + COperationResult result = COperationResult::Failure(orpVerifyDurableCopy, ERROR_ARITHMETIC_OVERFLOW, + files[i], targetName, FALSE); + return ReportCombineFailure(result, idTitle, IDS_READERROR); + } totalSize += size; - SalamanderSafeFile->SafeFileClose(&file); } // check available free space @@ -60,24 +226,26 @@ BOOL CombineFiles(TIndirectArray& files, LPTSTR targetName, return FALSE; } - // create the output file - SAFE_FILE outfile; + CCombineTemporaryOutput temporaryOutput; + CScopedSafeFile outfile; if (!bOnlyCrc) { - if (SalamanderSafeFile->SafeFileCreate(targetName, GENERIC_WRITE, FILE_SHARE_READ, FILE_ATTRIBUTE_NORMAL, - FALSE, parent, NULL, NULL, NULL, FALSE, NULL, NULL, 0, NULL, &outfile) == INVALID_HANDLE_VALUE) - { + COperationResult reserveResult = temporaryOutput.Reserve(targetName); + if (!reserveResult.Succeeded()) + return ReportCombineFailure(reserveResult, idTitle, IDS_WRITEERROR); + + if (!SalamanderSafeFile->SafeFileOpen(outfile.Get(), temporaryOutput.GetName(), GENERIC_WRITE, FILE_SHARE_READ, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_WRITE_THROUGH, + parent, BUTTONS_RETRYCANCEL, NULL, NULL)) return FALSE; - } + outfile.Opened(); } - // merge the files - char* pBuffer = new char[BUFSIZE]; - if (pBuffer == NULL) + // The buffer is owned for every return path, including cancellation during a SafeFile retry dialog. + std::unique_ptr pBuffer(static_cast(malloc(BUFSIZE)), free); + if (pBuffer.get() == NULL) { SalamanderGeneral->ShowMessageBox(LoadStr(IDS_OUTOFMEM), LoadStr(idTitle), MSGBOX_ERROR); - if (!bOnlyCrc) - SalamanderSafeFile->SafeFileClose(&outfile); return FALSE; } @@ -90,64 +258,131 @@ BOOL CombineFiles(TIndirectArray& files, LPTSTR targetName, CQuadWord totalProgress = CQuadWord(0, 0); int ret = TRUE; + BOOL reportFailure = FALSE; + COperationResult result = COperationResult::Success(orpVerifyDurableCopy, NULL, + bOnlyCrc ? NULL : temporaryOutput.GetName(), + bOnlyCrc ? opeNone : opeTemporaryTargetReady); int j; for (j = 0; j < files.Count; j++) { sprintf(text, "%s %s...", LoadStr(IDS_PROCESSING), files[j]); salamander->ProgressDialogAddText(text, TRUE); - SAFE_FILE file; - if (!SalamanderSafeFile->SafeFileOpen(&file, files[j], GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING, + CScopedSafeFile file; + if (!SalamanderSafeFile->SafeFileOpen(file.Get(), files[j], GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, parent, BUTTONS_RETRYCANCEL, NULL, NULL)) { + result = COperationResult::Failure(orpVerifyDurableCopy, ERROR_CANCELLED, files[j], + bOnlyCrc ? NULL : temporaryOutput.GetName(), FALSE, + bOnlyCrc ? opeNone : opeTemporaryTargetReady); ret = FALSE; break; } + file.Opened(); DWORD numread, numwr; - CQuadWord currentProgress = CQuadWord(0, 0), size; - size.LoDWord = GetFileSize(file.HFile, &size.HiDWord); + CQuadWord currentProgress = CQuadWord(0, 0); + CFileOffsetResult sizeResult = SalGetPluginFileSizeEx(SalamanderGeneral, file.Get()->HFile); + if (!sizeResult.Succeeded) + { + result = COperationResult::Failure(orpVerifyDurableCopy, sizeResult.Error, files[j], + bOnlyCrc ? NULL : temporaryOutput.GetName(), FALSE, + bOnlyCrc ? opeNone : opeTemporaryTargetReady); + reportFailure = TRUE; + ret = FALSE; + break; + } + CQuadWord size = sizeResult.Value; salamander->ProgressSetTotalSize(size, CQuadWord(-1, -1)); salamander->ProgressSetSize(CQuadWord(0, 0), CQuadWord(-1, -1), TRUE); do { - if (!SalamanderSafeFile->SafeFileRead(&file, pBuffer, BUFSIZE, &numread, parent, BUTTONS_RETRYCANCEL, NULL, NULL)) + if (!SalamanderSafeFile->SafeFileRead(file.Get(), pBuffer.get(), BUFSIZE, &numread, parent, BUTTONS_RETRYCANCEL, NULL, NULL)) { + result = COperationResult::Failure(orpVerifyDurableCopy, ERROR_CANCELLED, files[j], + bOnlyCrc ? NULL : temporaryOutput.GetName(), FALSE, + bOnlyCrc ? opeNone : opeTemporaryTargetReady); ret = FALSE; break; } if (!bOnlyCrc && numread) { - if (!SalamanderSafeFile->SafeFileWrite(&outfile, pBuffer, numread, &numwr, parent, BUTTONS_RETRYCANCEL, NULL, NULL)) + if (!SalamanderSafeFile->SafeFileWrite(outfile.Get(), pBuffer.get(), numread, &numwr, + parent, BUTTONS_RETRYCANCEL, NULL, NULL)) { + result = COperationResult::Failure(orpVerifyDurableCopy, ERROR_CANCELLED, files[j], + temporaryOutput.GetName(), FALSE, opeTemporaryTargetReady); ret = FALSE; break; } } - CrcVal = SalamanderGeneral->UpdateCrc32(pBuffer, numread, CrcVal); + CrcVal = SalamanderGeneral->UpdateCrc32(pBuffer.get(), numread, CrcVal); currentProgress += CQuadWord(numread, 0); if (!salamander->ProgressSetSize(currentProgress, totalProgress + currentProgress, TRUE)) { + result = COperationResult::Failure(orpVerifyDurableCopy, ERROR_CANCELLED, files[j], + bOnlyCrc ? NULL : temporaryOutput.GetName(), FALSE, + bOnlyCrc ? opeNone : opeTemporaryTargetReady); ret = FALSE; break; } } while (numread == BUFSIZE); totalProgress += currentProgress; - SalamanderSafeFile->SafeFileClose(&file); if (ret == FALSE) break; } salamander->CloseProgressDialog(); - delete[] pBuffer; if (!bOnlyCrc) { + // Reject a known-bad assembly while it is still staged, before it can replace the destination. + if (ret && bTestCrc && Crc != CrcVal) + { + SalamanderGeneral->ShowMessageBox(LoadStr(IDS_CRCERROR), LoadStr(idTitle), MSGBOX_ERROR); + result = COperationResult::Failure(orpVerifyDurableCopy, ERROR_CRC, NULL, temporaryOutput.GetName(), + FALSE, opeTemporaryTargetReady); + ret = FALSE; + } + if (ret && bTime && !SetFileTime(outfile.Get()->HFile, NULL, NULL, origTime)) + { + DWORD error = GetLastError(); + result = COperationResult::Failure(orpVerifyDurableCopy, error != ERROR_SUCCESS ? error : ERROR_WRITE_FAULT, + NULL, temporaryOutput.GetName(), + FALSE, opeTemporaryTargetReady); + reportFailure = TRUE; + ret = FALSE; + } + if (ret && !FlushFileBuffers(outfile.Get()->HFile)) + { + DWORD error = GetLastError(); + result = COperationResult::Failure(orpVerifyDurableCopy, error != ERROR_SUCCESS ? error : ERROR_WRITE_FAULT, + NULL, temporaryOutput.GetName(), + FALSE, opeTemporaryTargetReady); + reportFailure = TRUE; + ret = FALSE; + } + // Closing before reopening prevents a successful buffered write from becoming a premature commit. + outfile.Close(); if (ret) { - if (bTime) - SetFileTime(outfile.HFile, NULL, NULL, origTime); - SalamanderSafeFile->SafeFileClose(&outfile); + result = VerifyCombinedOutput(temporaryOutput.GetName(), totalSize); + ret = result.Succeeded(); + reportFailure = !ret; + } + if (ret && !PromoteFileUtf8Local(temporaryOutput.GetName(), targetName)) + { + DWORD error = GetLastError(); + result = COperationResult::Failure(orpCommitTransactionalTarget, + error != ERROR_SUCCESS ? error : ERROR_WRITE_FAULT, + temporaryOutput.GetName(), targetName, FALSE, + opeTemporaryTargetReady); + reportFailure = TRUE; + ret = FALSE; + } + if (ret) + { + temporaryOutput.Commit(); char* name = (char*)SalamanderGeneral->SalPathFindFileName(targetName); if (name > targetName) @@ -158,20 +393,13 @@ BOOL CombineFiles(TIndirectArray& files, LPTSTR targetName, } else { - SalamanderSafeFile->SafeFileClose(&outfile); - DeleteFileUtf8Local(targetName); + temporaryOutput.Cleanup(&result); + if (reportFailure) + ReportCombineFailure(result, idTitle, IDS_WRITEERROR); } } - if (!bOnlyCrc) - { - if (ret && bTestCrc && Crc != CrcVal) - { - SalamanderGeneral->ShowMessageBox(LoadStr(IDS_CRCERROR), LoadStr(idTitle), MSGBOX_ERROR); - ret = FALSE; - } - } - else + if (bOnlyCrc) Crc = CrcVal; return ret; diff --git a/src/plugins/tar/fileio.cpp b/src/plugins/tar/fileio.cpp index 01cd5886..203dceb8 100644 --- a/src/plugins/tar/fileio.cpp +++ b/src/plugins/tar/fileio.cpp @@ -136,10 +136,16 @@ CDecompressFile::CDecompressFile(const char* filename, HANDLE file, unsigned cha if (CQuadWord(0, 0) == inputSize) { - // determine the file size - DWORD SizeLow, SizeHigh; - SizeLow = GetFileSize(File, &SizeHigh); - InputSize.Set(SizeLow, SizeHigh); + // Preserve a successful 0xFFFFFFFF low word and capture a size-query failure before parsing offsets. + CFileOffsetResult sizeResult = SalGetPluginFileSizeEx(SalamanderGeneral, File); + if (!sizeResult.Succeeded) + { + Ok = FALSE; + ErrorCode = IDS_ERR_FREAD; + LastError = sizeResult.Error; + } + else + InputSize = sizeResult.Value; } else { @@ -204,7 +210,16 @@ CDecompressFile::FReadBlock(unsigned int number) { DWORD read = (DWORD)(Buffer + BUFSIZE - DataEnd); - if (StreamPos.Value + read > InputSize.Value) + // Reject an invalid stream position before subtracting it from the 64-bit input length. + if (StreamPos.Value > InputSize.Value) + { + Ok = FALSE; + ErrorCode = IDS_ERR_EOF; + LastError = 0; + return NULL; + } + // Compare the remaining 64-bit length without adding to the current offset and wrapping it. + if (read > InputSize.Value - StreamPos.Value) { read = (DWORD)(InputSize.Value - StreamPos.Value); } diff --git a/src/plugins_filesystem.cpp b/src/plugins_filesystem.cpp index 0124ef23..4d6a93be 100644 --- a/src/plugins_filesystem.cpp +++ b/src/plugins_filesystem.cpp @@ -234,26 +234,20 @@ void CPlugins::SetShowInChDrv(int index, BOOL showInChDrv) plugin->ChDrvMenuFSItemVisible = showInChDrv; } -int CPlugins::FindIndexForNewPluginFSTimer(DWORD timeoutAbs) +int CPlugins::FindIndexForNewPluginFSTimer(CMonotonicTimePoint timeoutAbs) { if (PluginFSTimers.Count == 0) return 0; - // all times must refer to the nearest timeout, because only then - // can we sort timeouts that exceed 0xFFFFFFFF - DWORD timeoutAbsBase = PluginFSTimers[0]->AbsTimeout; - if ((int)(timeoutAbs - timeoutAbsBase) < 0) - timeoutAbsBase = timeoutAbs; - timeoutAbs -= timeoutAbsBase; - + // 64-bit monotonic deadlines sort directly; no wrap-relative base is needed. int l = 0, r = PluginFSTimers.Count - 1, m; while (1) { m = (l + r) / 2; - DWORD actTimeoutAbs = PluginFSTimers[m]->AbsTimeout - timeoutAbsBase; + CMonotonicTimePoint actTimeoutAbs = PluginFSTimers[m]->AbsTimeout; if (actTimeoutAbs == timeoutAbs) { - while (++m < PluginFSTimers.Count && PluginFSTimers[m]->AbsTimeout - timeoutAbsBase == timeoutAbs) + while (++m < PluginFSTimers.Count && PluginFSTimers[m]->AbsTimeout == timeoutAbs) ; // return the index after the last identical timer return m; // found } @@ -275,7 +269,8 @@ int CPlugins::FindIndexForNewPluginFSTimer(DWORD timeoutAbs) BOOL CPlugins::AddPluginFSTimer(DWORD relTimeout, CPluginFSInterfaceAbstract* timerOwner, DWORD timerParam) { BOOL ret = FALSE; - CPluginFSTimer* timer = new CPluginFSTimer(GetTickCount() + relTimeout, timerOwner, timerParam, TimerTimeCounter++); + // Keep plug-in callbacks ordered by a 64-bit deadline instead of wrapping tick arithmetic. + CPluginFSTimer* timer = new CPluginFSTimer(CMonotonicClock::DeadlineAfter(relTimeout), timerOwner, timerParam, TimerTimeCounter++); if (timer != NULL) { int index = FindIndexForNewPluginFSTimer(timer->AbsTimeout); @@ -286,15 +281,14 @@ BOOL CPlugins::AddPluginFSTimer(DWORD relTimeout, CPluginFSInterfaceAbstract* ti if (index == 0) // the nearest timeout changed, adjust the Windows timer { - DWORD ti = timer->AbsTimeout - GetTickCount(); - if ((int)ti > 0) // if the new timer hasn't already timed out (the difference can even be negative), adjust or start the Windows timer + DWORD ti = CMonotonicClock::RemainingWin32TimerDelay(timer->AbsTimeout, CMonotonicClock::Now()); + if (ti > 0) // if the new timer has not already timed out, adjust or start the Windows timer { SetTimer(MainWindow->HWindow, IDT_PLUGINFSTIMERS, ti, NULL); } else { - if ((int)ti < 0) - TRACE_E("CPlugins::AddPluginFSTimer(): expired timer was added (" << (int)ti << " ms)"); + TRACE_E("CPlugins::AddPluginFSTimer(): expired timer was added"); KillTimer(MainWindow->HWindow, IDT_PLUGINFSTIMERS); // cancel any Windows timer, it's not needed PostMessage(MainWindow->HWindow, WM_TIMER, IDT_PLUGINFSTIMERS, 0); // process the next timeout as soon as possible } @@ -331,8 +325,8 @@ int CPlugins::KillPluginFSTimer(CPluginFSInterfaceAbstract* timerOwner, BOOL all { if (PluginFSTimers.Count > 0) { - DWORD ti = PluginFSTimers[0]->AbsTimeout - GetTickCount(); - if ((int)ti > 0) // if the new timer hasn't already timed out (the difference can even be negative), adjust or start the Windows timer + DWORD ti = CMonotonicClock::RemainingWin32TimerDelay(PluginFSTimers[0]->AbsTimeout, CMonotonicClock::Now()); + if (ti > 0) // if the new timer has not already timed out, adjust or start the Windows timer { SetTimer(MainWindow->HWindow, IDT_PLUGINFSTIMERS, ti, NULL); } @@ -359,13 +353,13 @@ void CPlugins::HandlePluginFSTimers() { StopTimerHandlerRecursion = TRUE; DWORD startTimerTimeCounter = TimerTimeCounter; - DWORD timeNow = GetTickCount(); + CMonotonicTimePoint timeNow = CMonotonicClock::Now(); int i; for (i = 0; i < PluginFSTimers.Count; i++) { CPluginFSTimer* timer = PluginFSTimers[i]; - if ((int)(timer->AbsTimeout - timeNow) <= 0 && // the timer timed out + if (CMonotonicClock::HasReached(timer->AbsTimeout, timeNow) && // the timer timed out timer->TimerAddedTime < startTimerTimeCounter) // it's an "old" timer (to prevent an infinite loop, we block newly added timers) { PluginFSTimers.Detach(i--); // detach the timer from the array (its timeout occurred = it's "handled") @@ -410,8 +404,8 @@ void CPlugins::HandlePluginFSTimers() if (PluginFSTimers.Count > 0) { - DWORD ti = PluginFSTimers[0]->AbsTimeout - GetTickCount(); - if ((int)ti > 0) // if the new timer hasn't already timed out (the difference can even be negative), adjust or start the Windows timer + DWORD ti = CMonotonicClock::RemainingWin32TimerDelay(PluginFSTimers[0]->AbsTimeout, CMonotonicClock::Now()); + if (ti > 0) // if the new timer has not already timed out, adjust or start the Windows timer SetTimer(MainWindow->HWindow, IDT_PLUGINFSTIMERS, ti, NULL); else PostMessage(MainWindow->HWindow, WM_TIMER, IDT_PLUGINFSTIMERS, 0); // process the next timeout as soon as possible diff --git a/src/plugins_interface.cpp b/src/plugins_interface.cpp index ca9eb0f1..3801de8e 100644 --- a/src/plugins_interface.cpp +++ b/src/plugins_interface.cpp @@ -141,9 +141,11 @@ BOOL SaveIconList(HKEY hKey, const char* valueName, CIconList* iconList) DWORD rawPNGSize; if (iconList->SaveToPNG(&rawPNG, &rawPNGSize)) { - LONG res = RegSetValueEx(hKey, valueName, 0, REG_BINARY, rawPNG, rawPNGSize); + // Route through the host registry boundary so transactional configuration saves + // retain their normal error handling and fault-injection coverage for this value. + BOOL saved = SetValue(hKey, valueName, REG_BINARY, rawPNG, rawPNGSize); free(rawPNG); - return TRUE; + return saved; } else return FALSE; @@ -975,6 +977,38 @@ BOOL CPlugins::ExecuteMenuItem(CFilesWindow* panel, HWND parent, int suid) return FALSE; } +int CPlugins::ResolveMenuItemCommandForTests(HWND parent, const char* dllSuffix, int id) +{ + CPluginData* pluginData = GetPluginDataFromSuffix(dllSuffix); + if (pluginData == NULL) + return 0; + + int pluginIndex = -1; + for (int i = 0; i < Data.Count; i++) + { + if (Data[i] == pluginData) + { + pluginIndex = i; + break; + } + } + if (pluginIndex == -1) + return 0; + + // Build the real plug-in submenu so the returned SUID follows the host's normal dynamic allocation path. + CMenuPopup menu(CML_PLUGINS_SUBMENU); + if (!InitPluginMenuItemsForBar(parent, pluginIndex, &menu)) + return 0; + + for (int i = 0; i < pluginData->MenuItems.Count; i++) + { + CPluginMenuItem* item = pluginData->MenuItems[i]; + if (item->ID == id) + return item->SUID; + } + return 0; +} + BOOL CPlugins::HelpForMenuItem(HWND parent, int suid) { BOOL helpDisplayed; @@ -1889,6 +1923,28 @@ CPlugins::GetPluginData(const CPluginInterfaceForFSAbstract* plugin) return NULL; } +CPluginData* +CPlugins::GetPluginData(const void* pluginInterface) +{ + if (pluginInterface != NULL) + { + for (int i = 0; i < Data.Count; i++) + { + CPluginData* data = Data[i]; + if (data->GetPluginInterface()->GetInterface() == pluginInterface || + data->GetPluginIfaceForArchiver()->GetInterface() == pluginInterface || + data->GetPluginInterfaceForViewer()->GetInterface() == pluginInterface || + data->GetPluginInterfaceForMenuExt()->GetInterface() == pluginInterface || + data->GetPluginInterfaceForFS()->GetInterface() == pluginInterface || + data->GetPluginInterfaceForThumbLoader()->GetInterface() == pluginInterface) + { + return data; + } + } + } + return NULL; +} + CPluginData* CPlugins::GetPluginData(const CPluginInterfaceAbstract* plugin, int* lastIndex) { diff --git a/src/plugins_loading.cpp b/src/plugins_loading.cpp index 8c6de883..b51900cf 100644 --- a/src/plugins_loading.cpp +++ b/src/plugins_loading.cpp @@ -13,9 +13,11 @@ #include "zip.h" #include "pack.h" #include "cache.h" +#include "parserbroker.h" #include #include "dialogs.h" #include "execlog.h" +#include "release_diagnostics.h" CPlugins Plugins; @@ -24,11 +26,99 @@ DWORD CPluginFSInterfaceEncapsulation::PluginFSTime = 1; // zero is used as the // **************************************************************************** -int AlreadyInPlugin = 0; +class CPluginDataLock +{ +public: + CPluginDataLock() { Plugins.EnterDataCS(); } + ~CPluginDataLock() { Plugins.LeaveDataCS(); } -void EnterPlugin() +private: + CPluginDataLock(const CPluginDataLock&); + CPluginDataLock& operator=(const CPluginDataLock&); +}; + +// Keeps transition ownership exception-safe so a re-entrant callback cannot +// strand the state lock while the plug-in subsystem is still shutting down. +class CPluginCallbackTransitionLock +{ +public: + CPluginCallbackTransitionLock(SRWLOCK& transitionLock) : TransitionLock(&transitionLock) + { + AcquireSRWLockExclusive(TransitionLock); + } + + ~CPluginCallbackTransitionLock() + { + ReleaseSRWLockExclusive(TransitionLock); + } + +private: + CPluginCallbackTransitionLock(const CPluginCallbackTransitionLock&); + CPluginCallbackTransitionLock& operator=(const CPluginCallbackTransitionLock&); + + SRWLOCK* TransitionLock; +}; + +// Covers the one callback made before a plug-in interface exists. The entry +// point needs temporary -1 sentinels, but it must never leave them installed +// when the entry point unwinds. +class CPluginEntryScope +{ +public: + CPluginEntryScope(CPluginCallbackState& callbackState, + CPluginInterfaceEncapsulation& pluginIface, + CSalamanderGeneral& salamanderGeneral, int builtForVersion) + : CallbackState(callbackState), PluginIface(pluginIface), SalamanderGeneral(salamanderGeneral), BuiltForVersion(builtForVersion), + EntryReturned(FALSE) + { + CallbackState.Enter(); + CPluginDataLock dataLock; + PluginIface.Init((CPluginInterfaceAbstract*)-1, BuiltForVersion); + SalamanderGeneral.Init((CPluginInterfaceAbstract*)-1); + } + + ~CPluginEntryScope() + { + if (!EntryReturned) + { + // An exception or an early exit cannot leave the loading-only + // interface placeholder visible to the rest of the host. + CPluginDataLock dataLock; + PluginIface.Init(NULL, 0); + } + + CallbackState.Leave(); + SalamanderGeneral.Init(PluginIface.GetInterface()); + } + + void SetReturnedInterface(CPluginInterfaceAbstract* pluginIface) + { + CPluginDataLock dataLock; + PluginIface.Init(pluginIface, BuiltForVersion); + EntryReturned = TRUE; + } + +private: + CPluginEntryScope(const CPluginEntryScope&); + CPluginEntryScope& operator=(const CPluginEntryScope&); + + CPluginCallbackState& CallbackState; + CPluginInterfaceEncapsulation& PluginIface; + CSalamanderGeneral& SalamanderGeneral; + int BuiltForVersion; + BOOL EntryReturned; +}; + +CPluginCallbackState::CPluginCallbackState() + : CallbackDepth(0) { - if (AlreadyInPlugin == 0) + InitializeSRWLock(&TransitionLock); +} + +void CPluginCallbackState::Enter() +{ + CPluginCallbackTransitionLock stateLock(TransitionLock); + if (CallbackDepth.load() == 0) { #ifdef _DEBUG // verification code for the CSalamanderGeneral::GetMsgBoxParent() method @@ -43,18 +133,20 @@ void EnterPlugin() AllowChangeDirectory(FALSE); // we do not want the current directory to change automatically BeginStopRefresh(TRUE); // we do not want panels to refresh while a plug-in is running - AlreadyInPlugin = 1; + CallbackDepth.store(1); } else { // TRACE_I("Warning! Plugin was entered multiple times."); - AlreadyInPlugin++; + CallbackDepth.fetch_add(1); } } -void LeavePlugin() +void CPluginCallbackState::Leave() { - if (AlreadyInPlugin == 1) + CPluginCallbackTransitionLock stateLock(TransitionLock); + int nestingLevel = CallbackDepth.load(); + if (nestingLevel == 1) { // precaution to avoid interrupting panel listing after each ESC in the plugin (mainly // when closing modal dialogs) @@ -62,7 +154,7 @@ void LeavePlugin() EndStopRefresh(TRUE, TRUE); AllowChangeDirectory(TRUE); - AlreadyInPlugin = 0; + CallbackDepth.store(0); if (MainWindow != NULL && MainWindow->NeedToResentDispachChangeNotif && StopRefresh == 0) // if we haven't left stop-refresh mode yet, sending the message is pointless @@ -78,10 +170,71 @@ void LeavePlugin() } else { - if (AlreadyInPlugin == 0) + if (nestingLevel == 0) TRACE_E("Unmatched call to LeavePlugin()!"); else - AlreadyInPlugin--; + CallbackDepth.store(nestingLevel - 1); + } +} + +BOOL CPluginCallbackState::IsEntered() const +{ + return CallbackDepth.load() > 0; +} + +void EnterPlugin() +{ + Plugins.GetCallbackState().Enter(); +} + +void LeavePlugin() +{ + Plugins.GetCallbackState().Leave(); +} + +BOOL IsInPlugin() +{ + return Plugins.GetCallbackState().IsEntered(); +} + +void HandlePluginCallbackFailure(const void* pluginInterface, const char* callback) +{ + CPluginData* plugin = Plugins.GetPluginData(pluginInterface); + if (plugin != NULL) + { + TRACE_E("Plug-in callback failed: " << callback << " (" << plugin->DLLName << " v. " << plugin->Version << ")"); + plugin->LoadOnStart = FALSE; + plugin->ShouldUnload = TRUE; + + // Unloading from an exception filter would free code still present on + // the stack. The UI idle path owns the deferred, recoverable unload. + if (MainWindow != NULL) + PostMessage(MainWindow->HWindow, WM_USER_POSTCMDORUNLOADPLUGIN, + (WPARAM)plugin->GetPluginInterface()->GetInterface(), 0); + } + else + TRACE_E("Plug-in callback failed: " << callback << " (owner not found)"); +} + +int WINAPI HandlePluginCallbackException(const void* pluginInterface, const char* callback, + EXCEPTION_POINTERS* exceptionInfo) +{ + CCallStack::HandleException(exceptionInfo); + HandlePluginCallbackFailure(pluginInterface, callback); + return EXCEPTION_EXECUTE_HANDLER; +} + +BOOL CallPluginCallback(const void* pluginInterface, const char* callback, + CPluginCallbackInvoker* invoker) +{ + __try + { + invoker->Invoke(); + return TRUE; + } + __except (HandlePluginCallbackException(pluginInterface, callback, GetExceptionInformation())) + { + return FALSE; } } @@ -120,7 +273,8 @@ BOOL CPluginFSInterfaceEncapsulation::ListCurrentPath(CSalamanderDirectoryAbstra ExecLogFileListingStart(PluginFSName, TRUE, PluginFSName); EnterPlugin(); //TRACE_I("list path: begin"); - BOOL r = Interface->ListCurrentPath(dir, pluginData, iconsType, forceRefresh); + BOOL r = FALSE; + PLUGIN_CALLBACK(Iface, "ListCurrentPath", r = Interface->ListCurrentPath(dir, pluginData, iconsType, forceRefresh)); //TRACE_I("list path: end"); ExecLogFileListingResult(PluginFSName, r, -1, -1, TRUE, PluginFSName); #ifdef _DEBUG @@ -134,7 +288,7 @@ BOOL CPluginFSInterfaceEncapsulation::ListCurrentPath(CSalamanderDirectoryAbstra } #endif // _DEBUG CALL_STACK_MESSAGE1("CPluginFSInterface::GetSupportedServices()"); - SupportedServices = Interface->GetSupportedServices(); + PLUGIN_CALLBACK(Iface, "GetSupportedServices", SupportedServices = Interface->GetSupportedServices()); LeavePlugin(); return r; } @@ -147,7 +301,8 @@ BOOL CPluginFSInterfaceEncapsulation::GetChangeDriveOrDisconnectItem(const char* if (IsServiceSupported(FS_SERVICE_GETCHANGEDRIVEORDISCONNECTITEM)) { EnterPlugin(); - BOOL r = Interface->GetChangeDriveOrDisconnectItem(fsName, title, icon, destroyIcon); + BOOL r = FALSE; + PLUGIN_CALLBACK(Iface, "GetChangeDriveOrDisconnectItem", r = Interface->GetChangeDriveOrDisconnectItem(fsName, title, icon, destroyIcon)); if (r && icon != NULL && destroyIcon) // add the handle for 'icon' to HANDLES HANDLES_ADD(__htIcon, __hoLoadImage, icon); LeavePlugin(); @@ -165,7 +320,8 @@ CPluginFSInterfaceEncapsulation::GetFSIcon(BOOL& destroyIcon) if (IsServiceSupported(FS_SERVICE_GETFSICON)) { EnterPlugin(); - HICON r = Interface->GetFSIcon(destroyIcon); + HICON r = NULL; + PLUGIN_CALLBACK(Iface, "GetFSIcon", r = Interface->GetFSIcon(destroyIcon)); if (r != NULL && destroyIcon) // add the handle of the returned icon to HANDLES HANDLES_ADD(__htIcon, __hoLoadImage, r); LeavePlugin(); @@ -197,7 +353,7 @@ void CPluginInterfaceForFSEncapsulation::CloseFS(CPluginFSInterfaceAbstract* fs) CALL_STACK_MESSAGE3("CPluginInterfaceForFSEncapsulation::CloseFS() (%s v. %s)", data->DLLName, data->Version); EnterPlugin(); - Interface->CloseFS(fs); + PLUGIN_CALLBACK(Interface, "CloseFS", Interface->CloseFS(fs)); Plugins.KillPluginFSTimer(fs, TRUE, 0); // we must remove timers of the closing FS (they wouldn't deliver and TRACE_E would appear) LeavePlugin(); @@ -214,7 +370,7 @@ void CPluginInterfaceForFSEncapsulation::CloseFS(CPluginFSInterfaceAbstract* fs) void CPluginInterfaceForFSEncapsulation::ExecuteChangeDriveMenuItem(int panel) { EnterPlugin(); - Interface->ExecuteChangeDriveMenuItem(panel); + PLUGIN_CALLBACK(Interface, "ExecuteChangeDriveMenuItem", Interface->ExecuteChangeDriveMenuItem(panel)); LeavePlugin(); } @@ -225,10 +381,11 @@ BOOL CPluginInterfaceForFSEncapsulation::ChangeDriveMenuItemContextMenu(HWND par BOOL& closeMenu, int& postCmd, void*& postCmdParam) { EnterPlugin(); - BOOL r = Interface->ChangeDriveMenuItemContextMenu(parent, panel, x, y, pluginFS, - pluginFSName, pluginFSNameIndex, - isDetachedFS, refreshMenu, - closeMenu, postCmd, postCmdParam); + BOOL r = FALSE; + PLUGIN_CALLBACK(Interface, "ChangeDriveMenuItemContextMenu", r = Interface->ChangeDriveMenuItemContextMenu(parent, panel, x, y, pluginFS, + pluginFSName, pluginFSNameIndex, + isDetachedFS, refreshMenu, + closeMenu, postCmd, postCmdParam)); LeavePlugin(); return r; } @@ -244,7 +401,7 @@ void CPluginInterfaceForFSEncapsulation::ExecuteChangeDrivePostCommand(int panel CALL_STACK_MESSAGE5("CPluginInterfaceForFSEncapsulation::ExecuteChangeDrivePostCommand(%d, %d, ) (%s v. %s)", panel, postCmd, data->DLLName, data->Version); EnterPlugin(); - Interface->ExecuteChangeDrivePostCommand(panel, postCmd, postCmdParam); + PLUGIN_CALLBACK(Interface, "ExecuteChangeDrivePostCommand", Interface->ExecuteChangeDrivePostCommand(panel, postCmd, postCmdParam)); LeavePlugin(); } @@ -261,7 +418,7 @@ void CPluginInterfaceForFSEncapsulation::ExecuteOnFS(int panel, CPluginFSInterfa CALL_STACK_MESSAGE7("CPluginInterfaceForFSEncapsulation::ExecuteOnFS(%d, , %s, %d, , %d) (%s v. %s)", panel, pluginFSName, pluginFSNameIndex, isDir, data->DLLName, data->Version); EnterPlugin(); - Interface->ExecuteOnFS(panel, pluginFS, pluginFSName, pluginFSNameIndex, file, isDir); + PLUGIN_CALLBACK(Interface, "ExecuteOnFS", Interface->ExecuteOnFS(panel, pluginFS, pluginFSName, pluginFSNameIndex, file, isDir)); LeavePlugin(); } @@ -278,7 +435,8 @@ BOOL CPluginInterfaceForFSEncapsulation::DisconnectFS(HWND parent, BOOL isInPane CALL_STACK_MESSAGE7("CPluginInterfaceForFSEncapsulation::DisconnectFS(, %d, %d, , %s, %d) (%s v. %s)", isInPanel, panel, pluginFSName, pluginFSNameIndex, data->DLLName, data->Version); EnterPlugin(); - BOOL ret = Interface->DisconnectFS(parent, isInPanel, panel, pluginFS, pluginFSName, pluginFSNameIndex); + BOOL ret = FALSE; + PLUGIN_CALLBACK(Interface, "DisconnectFS", ret = Interface->DisconnectFS(parent, isInPanel, panel, pluginFS, pluginFSName, pluginFSNameIndex)); LeavePlugin(); return ret; } @@ -295,7 +453,7 @@ void CPluginInterfaceForFSEncapsulation::ConvertPathToInternal(const char* fsNam CALL_STACK_MESSAGE6("CPluginInterfaceForFSEncapsulation::ConvertPathToInternal(%s, %d, %s) (%s v. %s)", fsName, fsNameIndex, fsUserPart, data->DLLName, data->Version); EnterPlugin(); - Interface->ConvertPathToInternal(fsName, fsNameIndex, fsUserPart); + PLUGIN_CALLBACK(Interface, "ConvertPathToInternal", Interface->ConvertPathToInternal(fsName, fsNameIndex, fsUserPart)); LeavePlugin(); } @@ -311,7 +469,7 @@ void CPluginInterfaceForFSEncapsulation::ConvertPathToExternal(const char* fsNam CALL_STACK_MESSAGE6("CPluginInterfaceForFSEncapsulation::ConvertPathToExternal(%s, %d, %s) (%s v. %s)", fsName, fsNameIndex, fsUserPart, data->DLLName, data->Version); EnterPlugin(); - Interface->ConvertPathToExternal(fsName, fsNameIndex, fsUserPart); + PLUGIN_CALLBACK(Interface, "ConvertPathToExternal", Interface->ConvertPathToExternal(fsName, fsNameIndex, fsUserPart)); LeavePlugin(); } @@ -332,7 +490,7 @@ void CPluginInterfaceEncapsulation::ReleasePluginDataInterface(CPluginDataInterf CALL_STACK_MESSAGE3("CPluginInterfaceEncapsulation::ReleasePluginDataInterface() (%s v. %s)", data->DLLName, data->Version); EnterPlugin(); - Interface->ReleasePluginDataInterface(pluginData); + PLUGIN_CALLBACK(Interface, "ReleasePluginDataInterface", Interface->ReleasePluginDataInterface(pluginData)); LeavePlugin(); } @@ -349,7 +507,7 @@ void CPluginDataInterfaceEncapsulation::ReleaseFilesOrDirs(CFilesArray* filesOrD int i; for (i = 0; i < filesOrDirs->Count; i++) { - Interface->ReleasePluginData(filesOrDirs->At(i), areDirs); + PLUGIN_CALLBACK(Plugin, "ReleasePluginData", Interface->ReleasePluginData(filesOrDirs->At(i), areDirs)); } LeavePlugin(); } @@ -443,11 +601,11 @@ CSalamanderDebug::CallWithCallStackEH(unsigned(WINAPI* threadBody)(void*), void* return CallWithCallStackEHBody(DLLName, Version, threadBody, param); #ifndef CALLSTK_DISABLE } - __except (CCallStack::HandleException(GetExceptionInformation())) + __except (HandlePluginCallbackException(Plugins.GetPluginData(DLLName) != NULL ? + Plugins.GetPluginData(DLLName)->GetPluginInterface()->GetInterface() : NULL, + "Plug-in thread", GetExceptionInformation())) { - TRACE_I("Thread address " << threadBody << ": calling ExitProcess(1)."); - // ExitProcess(1); - TerminateProcess(GetCurrentProcess(), 1); // harder exit (this call still performs some operations) + TRACE_I("Thread address " << threadBody << ": plug-in disabled after exception."); return 1; } #endif // CALLSTK_DISABLE @@ -2275,20 +2433,16 @@ BOOL CPluginData::InitDLL(HWND parent, BOOL quiet, BOOL waitCursor, BOOL showUns PluginIsNethood = FALSE; PluginUsesPasswordManager = FALSE; - EnterPlugin(); // for the plugin entry point - Plugins.EnterDataCS(); - PluginIface.Init((CPluginInterfaceAbstract*)-1, BuiltForVersion); // so SetFlagLoadOnSalamanderStart can be used from the entry point - Plugins.LeaveDataCS(); - SalamanderGeneral.Init((CPluginInterfaceAbstract*)-1); // so SetFlagLoadOnSalamanderStart can be used from the entry point - - // !!! CALLING PLUGIN ENTRY-POINT !!! - CPluginInterfaceAbstract* resIface = entry(&salamander); - - Plugins.EnterDataCS(); - PluginIface.Init(resIface, BuiltForVersion); - Plugins.LeaveDataCS(); - LeavePlugin(); - SalamanderGeneral.Init(PluginIface.GetInterface()); + { + CPluginEntryScope pluginEntry(Plugins.GetCallbackState(), PluginIface, + SalamanderGeneral, BuiltForVersion); + + // !!! CALLING PLUGIN ENTRY-POINT !!! + CPluginInterfaceAbstract* resIface = entry(&salamander); + pluginEntry.SetReturnedInterface(resIface); + // Keep only a sanitized DLL leaf name for approved field reports. + RecordReleaseDiagnosticPluginIdentity(DLLName); + } if (!PluginIface.NotEmpty()) { @@ -2830,48 +2984,12 @@ BOOL CPluginData::Remove(HWND parent, int index, BOOL canDelPluginRegKey) } PackerFormatConfig.BuildArray(); - if (SupportLoadSave && canDelPluginRegKey) // if the plugin supports load/save configuration + we can delete its registry key (not an import of the configuration from an older version of Salamander) - { // try to open the private registry key; if successful, delete it, it's no longer needed - BOOL shouldDelete = FALSE; - LoadSaveToRegistryMutex.Enter(); - HKEY salamander; - if (SALAMANDER_ROOT_REG != NULL && - OpenKey(HKEY_CURRENT_USER, SALAMANDER_ROOT_REG, salamander)) - { - HKEY actKey; - if (OpenKey(salamander, SALAMANDER_PLUGINSCONFIG, actKey)) - { - HKEY regKey; - if (OpenKey(actKey, RegKeyName, regKey)) - { - shouldDelete = TRUE; - CloseKey(regKey); - } - CloseKey(actKey); - } - CloseKey(salamander); - } - if (shouldDelete) - { - if (SALAMANDER_ROOT_REG != NULL && - CreateKey(HKEY_CURRENT_USER, SALAMANDER_ROOT_REG, salamander)) // ensure write permissions - { - HKEY actKey; - if (CreateKey(salamander, SALAMANDER_PLUGINSCONFIG, actKey)) - { - HKEY regKey; - if (CreateKey(actKey, RegKeyName, regKey)) - { - ClearKey(regKey); - CloseKey(regKey); - } - DeleteKey(actKey, RegKeyName); - CloseKey(actKey); - } - CloseKey(salamander); - } - } - LoadSaveToRegistryMutex.Leave(); + if (SupportLoadSave && canDelPluginRegKey && MainWindow != NULL) + { + // This plug-in is about to leave the host collection. The following + // debounced full snapshot is built after that removal, so its private key + // disappears without modifying the checksum-protected active generation. + MainWindow->ScheduleConfigSave(); } return TRUE; } @@ -2994,70 +3112,11 @@ void CPluginData::CallLoadOrSaveConfiguration(BOOL load, } else // save { - // The responsive host save pumps messages while registry calls run on its worker. A plug-in - // commit delivered in that interval must join the host's follow-up save instead of modifying - // the same registry subtree through a nested CallLoadOrSaveConfiguration transaction. - if (MainWindow != NULL && MainWindow->ConfigSaveInProgress) - { - MainWindow->ScheduleConfigSave(); - return; - } - - if (SupportLoadSave) // otherwise there is nowhere to save - { - LoadSaveToRegistryMutex.Enter(); - HKEY salamander; - if (SALAMANDER_ROOT_REG != NULL && - OpenKeyAux(NULL, HKEY_CURRENT_USER, SALAMANDER_ROOT_REG, salamander)) // check whether the Salamander key exists at all (otherwise nothing is saved) - { // OpenKeyAux, because we do not want a Load Configuration message - CloseKeyAux(salamander); - if (CreateKey(HKEY_CURRENT_USER, SALAMANDER_ROOT_REG, salamander)) - { - BOOL cfgIsOK = TRUE; - BOOL deleteSALAMANDER_SAVE_IN_PROGRESS = !IsSetSALAMANDER_SAVE_IN_PROGRESS; - if (deleteSALAMANDER_SAVE_IN_PROGRESS) - { - DWORD saveInProgress = 1; - if (GetValueAux(NULL, salamander, SALAMANDER_SAVE_IN_PROGRESS, REG_DWORD, &saveInProgress, sizeof(DWORD))) - { // GetValueAux, because we do not want a Load Configuration message - cfgIsOK = FALSE; // corrupted configuration; saving won't fix it (not all data is stored) - TRACE_E("CPluginData::CallLoadOrSaveConfiguration(): unable to save configuration, configuration key in registry is corrupted, plugin: " << Name); - } - else - { - saveInProgress = 1; - SetValue(salamander, SALAMANDER_SAVE_IN_PROGRESS, REG_DWORD, &saveInProgress, sizeof(DWORD)); - IsSetSALAMANDER_SAVE_IN_PROGRESS = TRUE; - } - } - if (cfgIsOK) - { - HKEY actKey; - if (CreateKey(salamander, SALAMANDER_PLUGINSCONFIG, actKey)) - { - HKEY regKey; - if (CreateKey(actKey, RegKeyName, regKey)) - { - CSalamanderRegistry registry; - { - CALL_STACK_MESSAGE1("3.CPluginData::CallLoadOrSaveConfiguration::loadOrSaveFunc()"); - loadOrSaveFunc(load, regKey, ®istry, param); - } - CloseKey(regKey); - } - CloseKey(actKey); - } - if (deleteSALAMANDER_SAVE_IN_PROGRESS) - { - DeleteValue(salamander, SALAMANDER_SAVE_IN_PROGRESS); - IsSetSALAMANDER_SAVE_IN_PROGRESS = FALSE; - } - } - CloseKey(salamander); - } - } - LoadSaveToRegistryMutex.Leave(); - } + // A plug-in commit (for example, an FTP bookmark edit) is a host configuration + // change. Rebuild the complete staged generation so the plug-in cannot tear the + // active tree with a private in-place write. + if (SupportLoadSave && MainWindow != NULL) + MainWindow->SaveConfig(); } } @@ -3072,63 +3131,9 @@ BOOL CPluginData::Unload(HWND parent, BOOL ask) char buf[MAX_PATH + 300]; BOOL skipUnload = FALSE; if (SupportLoadSave) - { // Persist plug-in state during unload because it can no longer be deferred by a user option. - LoadSaveToRegistryMutex.Enter(); - BOOL salKeyDoesNotExist = FALSE; - HKEY salamander; - if (SALAMANDER_ROOT_REG != NULL && - OpenKeyAux(NULL, HKEY_CURRENT_USER, SALAMANDER_ROOT_REG, salamander)) // check whether the Salamander key exists at all (otherwise nothing is saved) - { // OpenKeyAux because we do not want a Load Configuration message - CloseKeyAux(salamander); - if (CreateKey(HKEY_CURRENT_USER, SALAMANDER_ROOT_REG, salamander)) - { - BOOL cfgIsOK = TRUE; - BOOL deleteSALAMANDER_SAVE_IN_PROGRESS = !IsSetSALAMANDER_SAVE_IN_PROGRESS; - if (deleteSALAMANDER_SAVE_IN_PROGRESS) - { - DWORD saveInProgress = 1; - if (GetValueAux(NULL, salamander, SALAMANDER_SAVE_IN_PROGRESS, REG_DWORD, &saveInProgress, sizeof(DWORD))) - { // GetValueAux because we do not want a Load Configuration message - cfgIsOK = FALSE; // corrupted configuration; saving won't fix it (not all data is stored) - salKeyDoesNotExist = TRUE; - TRACE_E("CPluginData::Unload(): unable to save configuration, configuration key in registry is corrupted, plugin: " << Name); - } - else - { - saveInProgress = 1; - SetValue(salamander, SALAMANDER_SAVE_IN_PROGRESS, REG_DWORD, &saveInProgress, sizeof(DWORD)); - IsSetSALAMANDER_SAVE_IN_PROGRESS = TRUE; - } - } - if (cfgIsOK) - { - HKEY actKey; - if (CreateKey(salamander, SALAMANDER_PLUGINSCONFIG, actKey)) - { - Save(parent, actKey); - CloseKey(actKey); - } - if (deleteSALAMANDER_SAVE_IN_PROGRESS) - { - DeleteValue(salamander, SALAMANDER_SAVE_IN_PROGRESS); - IsSetSALAMANDER_SAVE_IN_PROGRESS = FALSE; - } - } - CloseKey(salamander); - } - else - salKeyDoesNotExist = TRUE; - } - else - salKeyDoesNotExist = TRUE; - LoadSaveToRegistryMutex.Leave(); - - if (ask && salKeyDoesNotExist) - { - sprintf(buf, LoadStr(IDS_PLUGINSAVEFAILED), Name); - skipUnload = SalMessageBox(parent, buf, LoadStr(IDS_QUESTION), - MB_YESNO | MB_ICONQUESTION | MB_DEFBUTTON2) == IDNO; - } + { // Persist plug-in state before release as one complete host generation. + if (MainWindow != NULL) + MainWindow->SaveConfig(parent); if (GlobalSaveWaitWindow != NULL) GlobalSaveWaitWindow->SetProgressPos(++GlobalSaveWaitWindowProgress); } @@ -3603,6 +3608,12 @@ BOOL CPluginData::ListArchive(CFilesWindow* panel, const char* archiveFileName, CPluginDataInterfaceAbstract*& pluginData) { CALL_STACK_MESSAGE4("CPluginData::ListArchive(, %s, ,) (%s v. %s)", archiveFileName, DLLName, Version); + CParserBrokerArchiveMetadata metadata; + if (!ParserBroker.QueryArchiveMetadata(archiveFileName, &metadata)) + { + TRACE_E("Unable to obtain archive metadata in ParserBroker: " << archiveFileName); + return FALSE; + } BOOL ret = FALSE; if (InitDLL(MainWindow->HWindow)) { diff --git a/src/precomp.h b/src/precomp.h index 10802f99..97cf795c 100644 --- a/src/precomp.h +++ b/src/precomp.h @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -56,6 +57,8 @@ #include "trace.h" #include "messages.h" #include "handles.h" +#include "common/monotonic_time.h" +#include "common/thread_owner.h" #include "heap.h" #include "array.h" diff --git a/src/regwork.cpp b/src/regwork.cpp index b877552b..b9a0f62c 100644 --- a/src/regwork.cpp +++ b/src/regwork.cpp @@ -5,9 +5,143 @@ #include "precomp.h" #include "mainwnd.h" +#include CRegistryWorkerThread RegistryWorkerThread; +// The injector deliberately lives at the registry boundary rather than in SaveConfig's +// individual sections. That keeps the test matrix complete when a new subkey/value is +// added to the snapshot and covers work performed by the registry worker as well as +// synchronous calls made while it is already in use. +static volatile LONG ConfigurationWriteFaultInjectionActive = 0; +static volatile LONG ConfigurationWriteFaultAfter = 0; +static volatile LONG ConfigurationWriteCount = 0; +static volatile LONG ConfigurationWriteFaultNextSaveAfter = 0; +static std::string ConfigurationWriteFaultReport; + +// Environment variables are external input. Query their required length first +// and retain an owned value only when it fits the documented Win32 limit. +// Callers deliberately treat malformed or oversized test controls as disabled. +const DWORD ConfigurationFaultEnvironmentMaximum = 32767; + +enum EConfigurationFaultEnvironmentResult +{ + cferSuccess, + cferNotFound, + cferTooLarge, + cferReadError +}; + +static EConfigurationFaultEnvironmentResult ReadConfigurationFaultEnvironment(const char* name, + std::string* value) +{ + value->clear(); + for (;;) + { + SetLastError(ERROR_SUCCESS); + DWORD required = GetEnvironmentVariableA(name, NULL, 0); + if (required == 0) + { + DWORD error = GetLastError(); + if (error == ERROR_ENVVAR_NOT_FOUND) + return cferNotFound; + return error == ERROR_SUCCESS ? cferSuccess : cferReadError; + } + if (required > ConfigurationFaultEnvironmentMaximum) + { + SetLastError(ERROR_INSUFFICIENT_BUFFER); + return cferTooLarge; + } + + std::string buffer(required, '\0'); + DWORD copied = GetEnvironmentVariableA(name, &buffer[0], required); + if (copied < required) + { + buffer.resize(copied); + value->swap(buffer); + return cferSuccess; + } + if (copied > ConfigurationFaultEnvironmentMaximum) + { + SetLastError(ERROR_INSUFFICIENT_BUFFER); + return cferTooLarge; + } + // The process environment may change between the measuring and copy + // calls. Retry with the newly reported required size rather than use a + // truncated value. + } +} + +static BOOL IsIsolatedConfigurationFaultTest() +{ + std::string isolated; + return ReadConfigurationFaultEnvironment("FILEMANAGER_UI_ISOLATED", &isolated) == cferSuccess && + isolated == "1"; +} + +static void RecordConfigurationWrite() +{ + if (InterlockedCompareExchange(&ConfigurationWriteFaultInjectionActive, 0, 0) == 0) + return; + + LONG writeNumber = InterlockedIncrement(&ConfigurationWriteCount); + LONG failAfter = InterlockedCompareExchange(&ConfigurationWriteFaultAfter, 0, 0); + if (failAfter > 0 && writeNumber == failAfter) + TerminateProcess(GetCurrentProcess(), CONFIGURATION_WRITE_FAULT_EXIT_CODE); +} + +void BeginConfigurationWriteFaultInjection() +{ + InterlockedExchange(&ConfigurationWriteCount, 0); + InterlockedExchange(&ConfigurationWriteFaultAfter, 0); + ConfigurationWriteFaultReport.clear(); + + if (!IsIsolatedConfigurationFaultTest()) + return; + + // A UI-test command can arm the explicit commit after startup and cancel-dialog saves have completed. + LONG armedBoundary = InterlockedExchange(&ConfigurationWriteFaultNextSaveAfter, 0); + if (armedBoundary > 0) + InterlockedExchange(&ConfigurationWriteFaultAfter, armedBoundary); + ReadConfigurationFaultEnvironment("FILEMANAGER_CONFIG_FAULT_REPORT", &ConfigurationWriteFaultReport); + InterlockedExchange(&ConfigurationWriteFaultInjectionActive, 1); +} + +void ArmNextConfigurationWriteFault(LONG writeBoundary) +{ + // The private UI protocol validates isolation; one-shot state keeps unrelated later saves safe. + InterlockedExchange(&ConfigurationWriteFaultNextSaveAfter, writeBoundary > 0 ? writeBoundary : 0); +} + +void EndConfigurationWriteFaultInjection() +{ + if (InterlockedExchange(&ConfigurationWriteFaultInjectionActive, 0) == 0) + return; + + if (!ConfigurationWriteFaultReport.empty()) + { + HANDLE report = CreateFileA(ConfigurationWriteFaultReport.c_str(), GENERIC_WRITE, 0, NULL, + CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (report != INVALID_HANDLE_VALUE) + { + char text[32]; + int length = _snprintf_s(text, _countof(text), _TRUNCATE, "%ld", ConfigurationWriteCount); + DWORD written; + if (length > 0) + WriteFile(report, text, (DWORD)length, &written, NULL); + CloseHandle(report); + } + } +} + +LONG FlushConfigurationRegistryKey(HKEY key) +{ + LONG result = RegFlushKey(key); + if (result == ERROR_SUCCESS) + RecordConfigurationWrite(); + return result; +} + // **************************************************************************** BOOL ClearKeyAux(HKEY key) @@ -22,6 +156,7 @@ BOOL ClearKeyAux(HKEY key) HANDLES(RegCloseKey(subKey)); if (!ret || RegDeleteKey(key, name) != ERROR_SUCCESS) return FALSE; + RecordConfigurationWrite(); } else return FALSE; @@ -35,7 +170,10 @@ BOOL ClearKeyAux(HKEY key) break; } else + { + RecordConfigurationWrite(); size = MAX_PATH; + } return TRUE; } @@ -49,7 +187,10 @@ BOOL CreateKeyAux(HWND parent, HKEY hKey, const char* name, HKEY& createdKey, BO KEY_READ | KEY_WRITE, NULL, &createdKey, &createType)); if (res == ERROR_SUCCESS) + { + RecordConfigurationWrite(); return TRUE; + } else { if (!quiet) @@ -106,7 +247,10 @@ void CloseKeyAux(HKEY hKey) BOOL DeleteKeyAux(HKEY hKey, const char* name) { - return RegDeleteKey(hKey, name) == ERROR_SUCCESS; + LONG result = RegDeleteKey(hKey, name); + if (result == ERROR_SUCCESS) + RecordConfigurationWrite(); + return result == ERROR_SUCCESS; } // **************************************************************************** @@ -214,7 +358,10 @@ BOOL SetValueAux(HWND parent, HKEY hKey, const char* name, DWORD type, dataSize = (DWORD)strlen((char*)data) + 1; LONG res = RegSetValueEx(hKey, name, 0, type, (CONST BYTE*)data, dataSize); if (res == ERROR_SUCCESS) + { + RecordConfigurationWrite(); return TRUE; + } else { if (!quiet) @@ -238,7 +385,10 @@ BOOL SetValueAux(HWND parent, HKEY hKey, const char* name, DWORD type, BOOL DeleteValueAux(HKEY hKey, const char* name) { - return RegDeleteValue(hKey, name) == ERROR_SUCCESS; + LONG result = RegDeleteValue(hKey, name); + if (result == ERROR_SUCCESS) + RecordConfigurationWrite(); + return result == ERROR_SUCCESS; } // **************************************************************************** diff --git a/src/regwork.h b/src/regwork.h index 44adb4c4..5fb0d89a 100644 --- a/src/regwork.h +++ b/src/regwork.h @@ -18,6 +18,16 @@ BOOL DeleteKeyAux(HKEY hKey, const char* name); // does not check the type, so it reads REG_DWORD the same as a 4-byte REG_BINARY BOOL GetValueDontCheckTypeAux(HKEY hKey, const char* name, void* buffer, DWORD bufferSize); +// Test-only crash injection for the transactional configuration writer. The scope is +// inert unless FILEMANAGER_UI_ISOLATED=1 is present, so ordinary application launches +// cannot accidentally enable it. The private UI command selects the write boundary and +// FILEMANAGER_CONFIG_FAULT_REPORT receives the number of mutations in a completed save. +void BeginConfigurationWriteFaultInjection(); +void EndConfigurationWriteFaultInjection(); +void ArmNextConfigurationWriteFault(LONG writeBoundary); +LONG FlushConfigurationRegistryKey(HKEY key); +const DWORD CONFIGURATION_WRITE_FAULT_EXIT_CODE = 121; + enum CRegistryWorkType { rwtNone, diff --git a/src/release_diagnostics.cpp b/src/release_diagnostics.cpp new file mode 100644 index 00000000..fc69b8fd --- /dev/null +++ b/src/release_diagnostics.cpp @@ -0,0 +1,172 @@ +// SPDX-FileCopyrightText: 2026 Taskscape Ltd +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "precomp.h" +#include "release_diagnostics.h" + +namespace +{ +const LONG kReleaseDiagnosticCapacity = 128; +const size_t kReleaseDiagnosticDetailLength = 80; + +struct CReleaseDiagnosticEntry +{ + volatile LONG Sequence; + DWORD Tick; + char Category[16]; + char Detail[kReleaseDiagnosticDetailLength]; +}; + +// This fixed-size store is deliberately allocation-free so release diagnostics +// remain available during low-memory, I/O, and shutdown failures. +CReleaseDiagnosticEntry ReleaseDiagnosticEntries[kReleaseDiagnosticCapacity] = {}; +volatile LONG ReleaseDiagnosticNextSequence = 0; + +void CopySanitizedLabel(char* target, size_t targetSize, const char* source) +{ + if (targetSize == 0) + return; + + const char* label = source == NULL ? "unknown" : source; + for (const char* scan = label; *scan != 0; scan++) + if (*scan == '\\' || *scan == '/') + label = scan + 1; + + size_t output = 0; + while (*label != 0 && output + 1 < targetSize) + { + const unsigned char value = (unsigned char)*label++; + target[output++] = (value >= 'a' && value <= 'z') || + (value >= 'A' && value <= 'Z') || + (value >= '0' && value <= '9') || + value == '.' || value == '-' || value == '_' + ? (char)value + : '_'; + } + target[output] = 0; +} + +void RecordReleaseDiagnostic(const char* category, const char* detail) +{ + const LONG sequence = InterlockedIncrement(&ReleaseDiagnosticNextSequence); + CReleaseDiagnosticEntry& entry = ReleaseDiagnosticEntries[(sequence - 1) % kReleaseDiagnosticCapacity]; + + // A negative sequence marks an entry being rewritten, so snapshots never + // export a half-written event while workers record concurrently. + for (;;) + { + const LONG published = InterlockedCompareExchange(&entry.Sequence, 0, 0); + if (published >= 0 && InterlockedCompareExchange(&entry.Sequence, -sequence, published) == published) + break; + YieldProcessor(); + } + entry.Tick = GetTickCount(); + CopySanitizedLabel(entry.Category, _countof(entry.Category), category); + CopySanitizedLabel(entry.Detail, _countof(entry.Detail), detail); + MemoryBarrier(); + InterlockedCompareExchange(&entry.Sequence, sequence, -sequence); +} + +void FormatAndRecord(const char* category, const char* format, int first, int second) +{ + char detail[kReleaseDiagnosticDetailLength]; + _snprintf_s(detail, _countof(detail), _TRUNCATE, format, first, second); + RecordReleaseDiagnostic(category, detail); +} + +void FormatDiagnosticLine(char* target, size_t targetSize, const CReleaseDiagnosticEntry& entry) +{ + _snprintf_s(target, targetSize, _TRUNCATE, "%ld tick=%lu %s: %s", + entry.Sequence, entry.Tick, entry.Category, entry.Detail); +} + +BOOL WriteDiagnosticText(HANDLE file, const char* text) +{ + DWORD written = 0; + const DWORD length = (DWORD)strlen(text); + return WriteFile(file, text, length, &written, NULL) && written == length; +} + +template +void EnumerateReleaseDiagnosticEntries(TConsumer consumer) +{ + const LONG newest = InterlockedCompareExchange(&ReleaseDiagnosticNextSequence, 0, 0); + const LONG oldest = newest > kReleaseDiagnosticCapacity ? newest - kReleaseDiagnosticCapacity + 1 : 1; + for (LONG sequence = oldest; sequence <= newest; sequence++) + { + CReleaseDiagnosticEntry& entry = ReleaseDiagnosticEntries[(sequence - 1) % kReleaseDiagnosticCapacity]; + // Claim the published slot while copying it so a fast producer cannot + // overwrite a character buffer concurrently with this report snapshot. + if (InterlockedCompareExchange(&entry.Sequence, -sequence, sequence) == sequence) + { + CReleaseDiagnosticEntry snapshot = {}; + snapshot.Sequence = sequence; + snapshot.Tick = entry.Tick; + lstrcpyn(snapshot.Category, entry.Category, _countof(snapshot.Category)); + lstrcpyn(snapshot.Detail, entry.Detail, _countof(snapshot.Detail)); + MemoryBarrier(); + InterlockedCompareExchange(&entry.Sequence, sequence, -sequence); + consumer(snapshot); + } + } +} +} // namespace + +void RecordReleaseDiagnosticOperationTransition(int fromState, int toState) +{ + FormatAndRecord("transition", "state_%d_to_%d", fromState, toState); +} + +void RecordReleaseDiagnosticWait(const char* waitName, DWORD result) +{ + char detail[kReleaseDiagnosticDetailLength]; + CopySanitizedLabel(detail, _countof(detail), waitName); + char resultText[20]; + _snprintf_s(resultText, _countof(resultText), _TRUNCATE, "_result_%lu", result); + strncat_s(detail, _countof(detail), resultText, _TRUNCATE); + RecordReleaseDiagnostic("wait", detail); +} + +void RecordReleaseDiagnosticRetry(const char* operationName) +{ + RecordReleaseDiagnostic("retry", operationName); +} + +void RecordReleaseDiagnosticPluginIdentity(const char* pluginName) +{ + RecordReleaseDiagnostic("plugin", pluginName); +} + +void PrintReleaseDiagnosticRingBuffer(FReleaseDiagnosticPrintLine printLine, void* param) +{ + if (printLine == NULL) + return; + + printLine(param, "Release diagnostic ring buffer (sanitized; paths and file names are excluded):", FALSE); + EnumerateReleaseDiagnosticEntries([&](const CReleaseDiagnosticEntry& entry) { + char line[160]; + FormatDiagnosticLine(line, _countof(line), entry); + printLine(param, line, TRUE); + }); + printLine(param, "", FALSE); +} + +BOOL ExportReleaseDiagnosticRingBuffer(const char* fileName) +{ + HANDLE file = CreateFileUtf8(fileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (file == INVALID_HANDLE_VALUE) + return FALSE; + + const char* header = "Open Salamander release diagnostic ring buffer (sanitized; paths and file names are excluded):\r\n"; + BOOL result = WriteDiagnosticText(file, header); + if (result) + EnumerateReleaseDiagnosticEntries([&](const CReleaseDiagnosticEntry& entry) { + char line[164]; + FormatDiagnosticLine(line, _countof(line), entry); + strncat_s(line, _countof(line), "\r\n", _TRUNCATE); + if (!WriteDiagnosticText(file, line)) + result = FALSE; + }); + CloseHandle(file); + return result; +} diff --git a/src/release_diagnostics.h b/src/release_diagnostics.h new file mode 100644 index 00000000..e6883f2b --- /dev/null +++ b/src/release_diagnostics.h @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: 2026 Taskscape Ltd +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +// Release diagnostics retain only fixed, sanitized event labels. They make +// field failures diagnosable without retaining file names, paths, or data. +typedef void (*FReleaseDiagnosticPrintLine)(void* param, const char* txt, BOOL tab); + +void RecordReleaseDiagnosticOperationTransition(int fromState, int toState); +void RecordReleaseDiagnosticWait(const char* waitName, DWORD result); +void RecordReleaseDiagnosticRetry(const char* operationName); +void RecordReleaseDiagnosticPluginIdentity(const char* pluginName); + +void PrintReleaseDiagnosticRingBuffer(FReleaseDiagnosticPrintLine printLine, void* param); +BOOL ExportReleaseDiagnosticRingBuffer(const char* fileName); diff --git a/src/res/down.svg b/src/res/down.svg index 9586b3b6..56b91abf 100644 --- a/src/res/down.svg +++ b/src/res/down.svg @@ -1,68 +1,4 @@ - - - - - - - - - - - - image/svg+xml - - - - - - - - + + + diff --git a/src/res/less.svg b/src/res/less.svg index 8a800631..63bb7945 100644 --- a/src/res/less.svg +++ b/src/res/less.svg @@ -1,72 +1,4 @@ - - - - - - - - - - - - image/svg+xml - - - - - - - - + + + diff --git a/src/res/more.svg b/src/res/more.svg index 6483c502..ca1d4852 100644 --- a/src/res/more.svg +++ b/src/res/more.svg @@ -1,72 +1,4 @@ - - - - - - - - - - - - image/svg+xml - - - - - - - - + + + diff --git a/src/res/right.svg b/src/res/right.svg index 6acf11c8..1079553b 100644 --- a/src/res/right.svg +++ b/src/res/right.svg @@ -1,67 +1,4 @@ - - - - - - - - - - - - image/svg+xml - - - - - - - - + + + diff --git a/src/res/toolbars/Back.svg b/src/res/toolbars/Back.svg index 5fea2c49..b38e33fa 100644 --- a/src/res/toolbars/Back.svg +++ b/src/res/toolbars/Back.svg @@ -1,4 +1,5 @@ + - - + + \ No newline at end of file diff --git a/src/res/toolbars/CalculateDirectorySizes.svg b/src/res/toolbars/CalculateDirectorySizes.svg index d300950c..e9f12267 100644 --- a/src/res/toolbars/CalculateDirectorySizes.svg +++ b/src/res/toolbars/CalculateDirectorySizes.svg @@ -1,8 +1,9 @@ + - - - - - - + + + + + + \ No newline at end of file diff --git a/src/res/toolbars/CalculateOccupiedSpace.svg b/src/res/toolbars/CalculateOccupiedSpace.svg index a9f4e9b3..abfb1cbe 100644 --- a/src/res/toolbars/CalculateOccupiedSpace.svg +++ b/src/res/toolbars/CalculateOccupiedSpace.svg @@ -1,5 +1,6 @@ + - - - + + + \ No newline at end of file diff --git a/src/res/toolbars/ChangeAttributes.svg b/src/res/toolbars/ChangeAttributes.svg index 1ddcf41b..eaaceeec 100644 --- a/src/res/toolbars/ChangeAttributes.svg +++ b/src/res/toolbars/ChangeAttributes.svg @@ -1,5 +1,6 @@ + - - - + + + \ No newline at end of file diff --git a/src/res/toolbars/ChangeCase.svg b/src/res/toolbars/ChangeCase.svg index 1e967ff8..82dd48e1 100644 --- a/src/res/toolbars/ChangeCase.svg +++ b/src/res/toolbars/ChangeCase.svg @@ -1,6 +1,7 @@ + - - - - + + + + \ No newline at end of file diff --git a/src/res/toolbars/ChangeDirectory.svg b/src/res/toolbars/ChangeDirectory.svg index 321ff130..c11324bc 100644 --- a/src/res/toolbars/ChangeDirectory.svg +++ b/src/res/toolbars/ChangeDirectory.svg @@ -1,5 +1,6 @@ + - - - + + + \ No newline at end of file diff --git a/src/res/toolbars/ChangeDrive.svg b/src/res/toolbars/ChangeDrive.svg new file mode 100644 index 00000000..afb46fe5 --- /dev/null +++ b/src/res/toolbars/ChangeDrive.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/res/toolbars/ClipboardCopy.svg b/src/res/toolbars/ClipboardCopy.svg index 27f14ed2..403f5bab 100644 --- a/src/res/toolbars/ClipboardCopy.svg +++ b/src/res/toolbars/ClipboardCopy.svg @@ -1,6 +1,7 @@ + - - - - + + + + \ No newline at end of file diff --git a/src/res/toolbars/ClipboardCut.svg b/src/res/toolbars/ClipboardCut.svg index e6a7c629..42850c49 100644 --- a/src/res/toolbars/ClipboardCut.svg +++ b/src/res/toolbars/ClipboardCut.svg @@ -1,6 +1,7 @@ + - - - - + + + + \ No newline at end of file diff --git a/src/res/toolbars/ClipboardPaste.svg b/src/res/toolbars/ClipboardPaste.svg index 1cdc9fab..e9bc822e 100644 --- a/src/res/toolbars/ClipboardPaste.svg +++ b/src/res/toolbars/ClipboardPaste.svg @@ -1,5 +1,6 @@ + - - - + + + \ No newline at end of file diff --git a/src/res/toolbars/CommandShell.svg b/src/res/toolbars/CommandShell.svg index 47112906..5013f971 100644 --- a/src/res/toolbars/CommandShell.svg +++ b/src/res/toolbars/CommandShell.svg @@ -1,6 +1,7 @@ + - - + + \ No newline at end of file diff --git a/src/res/toolbars/CompareDirectories.svg b/src/res/toolbars/CompareDirectories.svg index acbef107..f15f94fa 100644 --- a/src/res/toolbars/CompareDirectories.svg +++ b/src/res/toolbars/CompareDirectories.svg @@ -1,4 +1,5 @@ + - - + + \ No newline at end of file diff --git a/src/res/toolbars/Configuration.svg b/src/res/toolbars/Configuration.svg index 694d8281..fd273ee7 100644 --- a/src/res/toolbars/Configuration.svg +++ b/src/res/toolbars/Configuration.svg @@ -1,5 +1,6 @@ + - - - + + + \ No newline at end of file diff --git a/src/res/toolbars/ConnectNetworkDrive.svg b/src/res/toolbars/ConnectNetworkDrive.svg index 6d6d7313..b6ebdc2a 100644 --- a/src/res/toolbars/ConnectNetworkDrive.svg +++ b/src/res/toolbars/ConnectNetworkDrive.svg @@ -1,7 +1,8 @@ + - - - - - + + + + + \ No newline at end of file diff --git a/src/res/toolbars/Convert.svg b/src/res/toolbars/Convert.svg index 93de6456..a381ad77 100644 --- a/src/res/toolbars/Convert.svg +++ b/src/res/toolbars/Convert.svg @@ -1,6 +1,7 @@ + - - - - + + + + \ No newline at end of file diff --git a/src/res/toolbars/Copy.svg b/src/res/toolbars/Copy.svg index 27f14ed2..403f5bab 100644 --- a/src/res/toolbars/Copy.svg +++ b/src/res/toolbars/Copy.svg @@ -1,6 +1,7 @@ + - - - - + + + + \ No newline at end of file diff --git a/src/res/toolbars/CreateDirectory.svg b/src/res/toolbars/CreateDirectory.svg index 65720049..93683b4c 100644 --- a/src/res/toolbars/CreateDirectory.svg +++ b/src/res/toolbars/CreateDirectory.svg @@ -1,5 +1,6 @@ + - - - + + + \ No newline at end of file diff --git a/src/res/toolbars/Delete.svg b/src/res/toolbars/Delete.svg index 4adaf380..392aa8ba 100644 --- a/src/res/toolbars/Delete.svg +++ b/src/res/toolbars/Delete.svg @@ -1,7 +1,8 @@ + - - - - - + + + + + \ No newline at end of file diff --git a/src/res/toolbars/Disconnect.svg b/src/res/toolbars/Disconnect.svg index f4b290bc..7a74c2e6 100644 --- a/src/res/toolbars/Disconnect.svg +++ b/src/res/toolbars/Disconnect.svg @@ -1,6 +1,7 @@ + - - - - + + + + \ No newline at end of file diff --git a/src/res/toolbars/DriveInformation.svg b/src/res/toolbars/DriveInformation.svg index 2a3b2c1c..76d8627f 100644 --- a/src/res/toolbars/DriveInformation.svg +++ b/src/res/toolbars/DriveInformation.svg @@ -1,7 +1,8 @@ + - - - - - + + + + + \ No newline at end of file diff --git a/src/res/toolbars/Edit.svg b/src/res/toolbars/Edit.svg index cf56f6e6..33be8ff2 100644 --- a/src/res/toolbars/Edit.svg +++ b/src/res/toolbars/Edit.svg @@ -1,6 +1,7 @@ + - - - - + + + + \ No newline at end of file diff --git a/src/res/toolbars/EditNewFile.svg b/src/res/toolbars/EditNewFile.svg index 58c93d43..26639c71 100644 --- a/src/res/toolbars/EditNewFile.svg +++ b/src/res/toolbars/EditNewFile.svg @@ -1,6 +1,7 @@ + - + - + \ No newline at end of file diff --git a/src/res/toolbars/Email.svg b/src/res/toolbars/Email.svg index 7a715cff..e33af3ba 100644 --- a/src/res/toolbars/Email.svg +++ b/src/res/toolbars/Email.svg @@ -1,4 +1,5 @@ + - - + + \ No newline at end of file diff --git a/src/res/toolbars/Filter.svg b/src/res/toolbars/Filter.svg index 3dd2c87d..b814e879 100644 --- a/src/res/toolbars/Filter.svg +++ b/src/res/toolbars/Filter.svg @@ -1,4 +1,5 @@ + - - + + \ No newline at end of file diff --git a/src/res/toolbars/FindFilesAndDirectories.svg b/src/res/toolbars/FindFilesAndDirectories.svg index ea550aec..60a39da4 100644 --- a/src/res/toolbars/FindFilesAndDirectories.svg +++ b/src/res/toolbars/FindFilesAndDirectories.svg @@ -1,5 +1,6 @@ + - - - + + + \ No newline at end of file diff --git a/src/res/toolbars/Focus.svg b/src/res/toolbars/Focus.svg new file mode 100644 index 00000000..3f904d21 --- /dev/null +++ b/src/res/toolbars/Focus.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/res/toolbars/FocusNameInOtherPanel.svg b/src/res/toolbars/FocusNameInOtherPanel.svg index 94995bc4..3227754a 100644 --- a/src/res/toolbars/FocusNameInOtherPanel.svg +++ b/src/res/toolbars/FocusNameInOtherPanel.svg @@ -1,4 +1,5 @@ + - - + + \ No newline at end of file diff --git a/src/res/toolbars/Forward.svg b/src/res/toolbars/Forward.svg index b4189a4f..f2e1b6e0 100644 --- a/src/res/toolbars/Forward.svg +++ b/src/res/toolbars/Forward.svg @@ -1,4 +1,5 @@ + - - + + \ No newline at end of file diff --git a/src/res/toolbars/GoToHotPath.svg b/src/res/toolbars/GoToHotPath.svg index 033fd636..fda7bd67 100644 --- a/src/res/toolbars/GoToHotPath.svg +++ b/src/res/toolbars/GoToHotPath.svg @@ -1,4 +1,5 @@ + - - - \ No newline at end of file + + + diff --git a/src/res/toolbars/GoToNextSelectedName.svg b/src/res/toolbars/GoToNextSelectedName.svg new file mode 100644 index 00000000..64a60a36 --- /dev/null +++ b/src/res/toolbars/GoToNextSelectedName.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/res/toolbars/GoToPathfromOtherPanel.svg b/src/res/toolbars/GoToPathfromOtherPanel.svg index 71ce300c..682a59d6 100644 --- a/src/res/toolbars/GoToPathfromOtherPanel.svg +++ b/src/res/toolbars/GoToPathfromOtherPanel.svg @@ -1,5 +1,6 @@ + - - - + + + \ No newline at end of file diff --git a/src/res/toolbars/GoToPreviousSelectedName.svg b/src/res/toolbars/GoToPreviousSelectedName.svg new file mode 100644 index 00000000..30d1cedd --- /dev/null +++ b/src/res/toolbars/GoToPreviousSelectedName.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/res/toolbars/GoToShortcutTarget.svg b/src/res/toolbars/GoToShortcutTarget.svg index 248f4cb2..17737a21 100644 --- a/src/res/toolbars/GoToShortcutTarget.svg +++ b/src/res/toolbars/GoToShortcutTarget.svg @@ -1,6 +1,7 @@ + - - - - + + + + \ No newline at end of file diff --git a/src/res/toolbars/HelpContents.svg b/src/res/toolbars/HelpContents.svg index e85393fc..a9ff4ede 100644 --- a/src/res/toolbars/HelpContents.svg +++ b/src/res/toolbars/HelpContents.svg @@ -1,5 +1,6 @@ + - - - + + + \ No newline at end of file diff --git a/src/res/toolbars/HideSelectedNames.svg b/src/res/toolbars/HideSelectedNames.svg index fd178de2..558373f0 100644 --- a/src/res/toolbars/HideSelectedNames.svg +++ b/src/res/toolbars/HideSelectedNames.svg @@ -1,5 +1,6 @@ + - - - + + + \ No newline at end of file diff --git a/src/res/toolbars/HideUnselectedNames.svg b/src/res/toolbars/HideUnselectedNames.svg index 8f58284c..7cc30b4d 100644 --- a/src/res/toolbars/HideUnselectedNames.svg +++ b/src/res/toolbars/HideUnselectedNames.svg @@ -1,4 +1,5 @@ + - - + + \ No newline at end of file diff --git a/src/res/toolbars/InvertSelection.svg b/src/res/toolbars/InvertSelection.svg new file mode 100644 index 00000000..eee65ef3 --- /dev/null +++ b/src/res/toolbars/InvertSelection.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/res/toolbars/LoadSelection.svg b/src/res/toolbars/LoadSelection.svg new file mode 100644 index 00000000..57581d47 --- /dev/null +++ b/src/res/toolbars/LoadSelection.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/res/toolbars/MenuCheck.svg b/src/res/toolbars/MenuCheck.svg new file mode 100644 index 00000000..9f179053 --- /dev/null +++ b/src/res/toolbars/MenuCheck.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/res/toolbars/MenuRadio.svg b/src/res/toolbars/MenuRadio.svg new file mode 100644 index 00000000..f9d9e891 --- /dev/null +++ b/src/res/toolbars/MenuRadio.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/res/toolbars/Modify.svg b/src/res/toolbars/Modify.svg index 9e9e7764..17e17a62 100644 --- a/src/res/toolbars/Modify.svg +++ b/src/res/toolbars/Modify.svg @@ -1,7 +1,8 @@ + - - - - - + + + + + \ No newline at end of file diff --git a/src/res/toolbars/Move.svg b/src/res/toolbars/Move.svg index f7800323..1f4fcca2 100644 --- a/src/res/toolbars/Move.svg +++ b/src/res/toolbars/Move.svg @@ -1,5 +1,6 @@ + - - - + + + \ No newline at end of file diff --git a/src/res/toolbars/MoveItemDown.svg b/src/res/toolbars/MoveItemDown.svg index fc7de1ee..22cf00c7 100644 --- a/src/res/toolbars/MoveItemDown.svg +++ b/src/res/toolbars/MoveItemDown.svg @@ -1,3 +1,4 @@ + - + \ No newline at end of file diff --git a/src/res/toolbars/MoveItemUp.svg b/src/res/toolbars/MoveItemUp.svg index 42869f48..75d2359e 100644 --- a/src/res/toolbars/MoveItemUp.svg +++ b/src/res/toolbars/MoveItemUp.svg @@ -1,3 +1,4 @@ + - + \ No newline at end of file diff --git a/src/res/toolbars/NTFSCompress.svg b/src/res/toolbars/NTFSCompress.svg index 412b656b..90075b96 100644 --- a/src/res/toolbars/NTFSCompress.svg +++ b/src/res/toolbars/NTFSCompress.svg @@ -1,6 +1,7 @@ + - - - - + + + + \ No newline at end of file diff --git a/src/res/toolbars/NTFSUncompress.svg b/src/res/toolbars/NTFSUncompress.svg index 6be0b197..55e84858 100644 --- a/src/res/toolbars/NTFSUncompress.svg +++ b/src/res/toolbars/NTFSUncompress.svg @@ -1,6 +1,7 @@ + - - - - + + + + \ No newline at end of file diff --git a/src/res/toolbars/New.svg b/src/res/toolbars/New.svg index c7e288d7..89624f89 100644 --- a/src/res/toolbars/New.svg +++ b/src/res/toolbars/New.svg @@ -1,3 +1,4 @@ + - + \ No newline at end of file diff --git a/src/res/toolbars/OpenActiveFolder.svg b/src/res/toolbars/OpenActiveFolder.svg new file mode 100644 index 00000000..d97119cb --- /dev/null +++ b/src/res/toolbars/OpenActiveFolder.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/res/toolbars/OpenComputer.svg b/src/res/toolbars/OpenComputer.svg new file mode 100644 index 00000000..6b11838c --- /dev/null +++ b/src/res/toolbars/OpenComputer.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/res/toolbars/OpenControlPanel.svg b/src/res/toolbars/OpenControlPanel.svg new file mode 100644 index 00000000..46691d79 --- /dev/null +++ b/src/res/toolbars/OpenControlPanel.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/res/toolbars/OpenDesktop.svg b/src/res/toolbars/OpenDesktop.svg new file mode 100644 index 00000000..66554f94 --- /dev/null +++ b/src/res/toolbars/OpenDesktop.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/res/toolbars/OpenDocuments.svg b/src/res/toolbars/OpenDocuments.svg new file mode 100644 index 00000000..bc6a69a5 --- /dev/null +++ b/src/res/toolbars/OpenDocuments.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/res/toolbars/OpenFolder.svg b/src/res/toolbars/OpenFolder.svg index 10261815..1d218acc 100644 --- a/src/res/toolbars/OpenFolder.svg +++ b/src/res/toolbars/OpenFolder.svg @@ -1,4 +1,5 @@ + - - + + \ No newline at end of file diff --git a/src/res/toolbars/OpenFonts.svg b/src/res/toolbars/OpenFonts.svg new file mode 100644 index 00000000..7ee34a02 --- /dev/null +++ b/src/res/toolbars/OpenFonts.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/res/toolbars/OpenNameinOtherPanel.svg b/src/res/toolbars/OpenNameinOtherPanel.svg index 733056e3..cd2814ea 100644 --- a/src/res/toolbars/OpenNameinOtherPanel.svg +++ b/src/res/toolbars/OpenNameinOtherPanel.svg @@ -1,4 +1,5 @@ + - - + + \ No newline at end of file diff --git a/src/res/toolbars/OpenNetwork.svg b/src/res/toolbars/OpenNetwork.svg new file mode 100644 index 00000000..5e513c42 --- /dev/null +++ b/src/res/toolbars/OpenNetwork.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/res/toolbars/OpenPrinters.svg b/src/res/toolbars/OpenPrinters.svg new file mode 100644 index 00000000..a557462a --- /dev/null +++ b/src/res/toolbars/OpenPrinters.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/res/toolbars/OpenRecycleBin.svg b/src/res/toolbars/OpenRecycleBin.svg new file mode 100644 index 00000000..1e373da9 --- /dev/null +++ b/src/res/toolbars/OpenRecycleBin.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/res/toolbars/Pack.svg b/src/res/toolbars/Pack.svg index 1e8dc6ba..c39bb6cb 100644 --- a/src/res/toolbars/Pack.svg +++ b/src/res/toolbars/Pack.svg @@ -1,5 +1,6 @@ + - - - + + + \ No newline at end of file diff --git a/src/res/toolbars/ParentDirectory.svg b/src/res/toolbars/ParentDirectory.svg index 103a1c47..ae22fd42 100644 --- a/src/res/toolbars/ParentDirectory.svg +++ b/src/res/toolbars/ParentDirectory.svg @@ -1,4 +1,5 @@ + - + \ No newline at end of file diff --git a/src/res/toolbars/PasteShortcut.svg b/src/res/toolbars/PasteShortcut.svg index 39feedd3..a636a17c 100644 --- a/src/res/toolbars/PasteShortcut.svg +++ b/src/res/toolbars/PasteShortcut.svg @@ -1,6 +1,7 @@ + - - - - + + + + \ No newline at end of file diff --git a/src/res/toolbars/Properties.svg b/src/res/toolbars/Properties.svg index a13fd6ed..26522b4c 100644 --- a/src/res/toolbars/Properties.svg +++ b/src/res/toolbars/Properties.svg @@ -1,6 +1,7 @@ + - - - - + + + + \ No newline at end of file diff --git a/src/res/toolbars/QuickRename.svg b/src/res/toolbars/QuickRename.svg index 19c11ce9..7030944d 100644 --- a/src/res/toolbars/QuickRename.svg +++ b/src/res/toolbars/QuickRename.svg @@ -1,6 +1,7 @@ + - - - - + + + + \ No newline at end of file diff --git a/src/res/toolbars/Refresh.svg b/src/res/toolbars/Refresh.svg index 0a497ace..3caf56d7 100644 --- a/src/res/toolbars/Refresh.svg +++ b/src/res/toolbars/Refresh.svg @@ -1,6 +1,7 @@ + - - - - + + + + \ No newline at end of file diff --git a/src/res/toolbars/RestoreSelection.svg b/src/res/toolbars/RestoreSelection.svg new file mode 100644 index 00000000..e5524734 --- /dev/null +++ b/src/res/toolbars/RestoreSelection.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/res/toolbars/RootDirectory.svg b/src/res/toolbars/RootDirectory.svg index da8d593d..a8b5d39c 100644 --- a/src/res/toolbars/RootDirectory.svg +++ b/src/res/toolbars/RootDirectory.svg @@ -1,5 +1,6 @@ + - - + + \ No newline at end of file diff --git a/src/res/toolbars/SaveSelection.svg b/src/res/toolbars/SaveSelection.svg new file mode 100644 index 00000000..df8e455c --- /dev/null +++ b/src/res/toolbars/SaveSelection.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/res/toolbars/Security.svg b/src/res/toolbars/Security.svg index 8eddc4f5..ff6b6421 100644 --- a/src/res/toolbars/Security.svg +++ b/src/res/toolbars/Security.svg @@ -1,3 +1,4 @@ + - + \ No newline at end of file diff --git a/src/res/toolbars/Select.svg b/src/res/toolbars/Select.svg new file mode 100644 index 00000000..00056c71 --- /dev/null +++ b/src/res/toolbars/Select.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/res/toolbars/SelectAll.svg b/src/res/toolbars/SelectAll.svg index d9122096..34e28ed4 100644 --- a/src/res/toolbars/SelectAll.svg +++ b/src/res/toolbars/SelectAll.svg @@ -1,8 +1,9 @@ + - - - - - - + + + + + + \ No newline at end of file diff --git a/src/res/toolbars/SelectFilesWithSameExtension.svg b/src/res/toolbars/SelectFilesWithSameExtension.svg new file mode 100644 index 00000000..dd9ab052 --- /dev/null +++ b/src/res/toolbars/SelectFilesWithSameExtension.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/res/toolbars/SelectFilesWithSameName.svg b/src/res/toolbars/SelectFilesWithSameName.svg new file mode 100644 index 00000000..52fbb6c6 --- /dev/null +++ b/src/res/toolbars/SelectFilesWithSameName.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/res/toolbars/SharedDirectories.svg b/src/res/toolbars/SharedDirectories.svg index 7fa78459..5c5b0a03 100644 --- a/src/res/toolbars/SharedDirectories.svg +++ b/src/res/toolbars/SharedDirectories.svg @@ -1,4 +1,5 @@ + - - + + \ No newline at end of file diff --git a/src/res/toolbars/ShowHiddenNames.svg b/src/res/toolbars/ShowHiddenNames.svg index 7eb704c5..99ff9c81 100644 --- a/src/res/toolbars/ShowHiddenNames.svg +++ b/src/res/toolbars/ShowHiddenNames.svg @@ -1,4 +1,5 @@ + - - + + \ No newline at end of file diff --git a/src/res/toolbars/SmartColumnMode.svg b/src/res/toolbars/SmartColumnMode.svg index 841b0a35..91a5d3b6 100644 --- a/src/res/toolbars/SmartColumnMode.svg +++ b/src/res/toolbars/SmartColumnMode.svg @@ -1,7 +1,8 @@ + - - - - - + + + + + \ No newline at end of file diff --git a/src/res/toolbars/SortByAttributes.svg b/src/res/toolbars/SortByAttributes.svg index d63f7359..657649c1 100644 --- a/src/res/toolbars/SortByAttributes.svg +++ b/src/res/toolbars/SortByAttributes.svg @@ -1,5 +1,6 @@ + - - - + + + \ No newline at end of file diff --git a/src/res/toolbars/SortByDate.svg b/src/res/toolbars/SortByDate.svg index 9a4794e0..3a58475a 100644 --- a/src/res/toolbars/SortByDate.svg +++ b/src/res/toolbars/SortByDate.svg @@ -1,6 +1,7 @@ + - - - - + + + + \ No newline at end of file diff --git a/src/res/toolbars/SortByExtension.svg b/src/res/toolbars/SortByExtension.svg index cf7747e0..1e747b63 100644 --- a/src/res/toolbars/SortByExtension.svg +++ b/src/res/toolbars/SortByExtension.svg @@ -1,6 +1,7 @@ + - - - - + + + + \ No newline at end of file diff --git a/src/res/toolbars/SortByName.svg b/src/res/toolbars/SortByName.svg index c9ceef8e..7b3f826f 100644 --- a/src/res/toolbars/SortByName.svg +++ b/src/res/toolbars/SortByName.svg @@ -1,6 +1,7 @@ + - - - - + + + + \ No newline at end of file diff --git a/src/res/toolbars/SortBySize.svg b/src/res/toolbars/SortBySize.svg index 44f20836..eebf3a6b 100644 --- a/src/res/toolbars/SortBySize.svg +++ b/src/res/toolbars/SortBySize.svg @@ -1,6 +1,7 @@ + - - - - + + + + \ No newline at end of file diff --git a/src/res/toolbars/Stop.svg b/src/res/toolbars/Stop.svg new file mode 100644 index 00000000..d1ea7d66 --- /dev/null +++ b/src/res/toolbars/Stop.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/res/toolbars/SwapPanels.svg b/src/res/toolbars/SwapPanels.svg index b1effca5..47a098f7 100644 --- a/src/res/toolbars/SwapPanels.svg +++ b/src/res/toolbars/SwapPanels.svg @@ -1,5 +1,6 @@ + - - - + + + \ No newline at end of file diff --git a/src/res/toolbars/Unpack.svg b/src/res/toolbars/Unpack.svg index e06b6ece..d5cfaed6 100644 --- a/src/res/toolbars/Unpack.svg +++ b/src/res/toolbars/Unpack.svg @@ -1,12 +1,13 @@ + - + - + - + - + - + \ No newline at end of file diff --git a/src/res/toolbars/Unselect.svg b/src/res/toolbars/Unselect.svg new file mode 100644 index 00000000..8aa64a3d --- /dev/null +++ b/src/res/toolbars/Unselect.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/res/toolbars/UnselectAll.svg b/src/res/toolbars/UnselectAll.svg index 903c8630..2638be6c 100644 --- a/src/res/toolbars/UnselectAll.svg +++ b/src/res/toolbars/UnselectAll.svg @@ -1,5 +1,6 @@ + - - - + + + \ No newline at end of file diff --git a/src/res/toolbars/UnselectFilesWithSameExtension.svg b/src/res/toolbars/UnselectFilesWithSameExtension.svg new file mode 100644 index 00000000..7c12df77 --- /dev/null +++ b/src/res/toolbars/UnselectFilesWithSameExtension.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/res/toolbars/UnselectFilesWithSameName.svg b/src/res/toolbars/UnselectFilesWithSameName.svg new file mode 100644 index 00000000..c8065286 --- /dev/null +++ b/src/res/toolbars/UnselectFilesWithSameName.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/res/toolbars/UserMenu.svg b/src/res/toolbars/UserMenu.svg index 0f531905..7f1f5129 100644 --- a/src/res/toolbars/UserMenu.svg +++ b/src/res/toolbars/UserMenu.svg @@ -1,8 +1,9 @@ + - - - - - - + + + + + + \ No newline at end of file diff --git a/src/res/toolbars/View.svg b/src/res/toolbars/View.svg index 0a07ccc4..f5fb178d 100644 --- a/src/res/toolbars/View.svg +++ b/src/res/toolbars/View.svg @@ -1,4 +1,5 @@ + - - + + \ No newline at end of file diff --git a/src/res/toolbars/Views.svg b/src/res/toolbars/Views.svg index be4c191d..26e2a577 100644 --- a/src/res/toolbars/Views.svg +++ b/src/res/toolbars/Views.svg @@ -1,6 +1,7 @@ + - - - - + + + + \ No newline at end of file diff --git a/src/res/toolbars/WhatIsThis.svg b/src/res/toolbars/WhatIsThis.svg index 715d8f35..df626faf 100644 --- a/src/res/toolbars/WhatIsThis.svg +++ b/src/res/toolbars/WhatIsThis.svg @@ -1,5 +1,6 @@ + - - - + + + \ No newline at end of file diff --git a/src/retry_policy.h b/src/retry_policy.h new file mode 100644 index 00000000..e851821b --- /dev/null +++ b/src/retry_policy.h @@ -0,0 +1,65 @@ +// SPDX-FileCopyrightText: 2026 Taskscape Ltd +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +// Automatic retries are centralized so transient transport faults are paced +// consistently, while uncertain destructive commits always require a user. +enum ERetryOperationKind +{ + rokReadOnly, + rokDestructiveCommit +}; + +enum +{ + kAutomaticRetryLimit = 3, + kAutomaticRetryBaseDelayMs = 100, + kAutomaticRetryMaximumDelayMs = 1000 +}; + +inline BOOL IsTransientOperationError(DWORD error) +{ + return error == ERROR_SHARING_VIOLATION || error == ERROR_LOCK_VIOLATION || + error == ERROR_BUSY || error == ERROR_NETWORK_BUSY || + error == ERROR_NETNAME_DELETED || error == ERROR_BAD_NET_RESP || + error == ERROR_UNEXP_NET_ERR || error == ERROR_CONNECTION_ABORTED || + error == ERROR_NOT_READY || error == ERROR_SEM_TIMEOUT || + error == ERROR_TIMEOUT; +} + +inline DWORD GetAutomaticRetryDelay(int attempt) +{ + DWORD delay = kAutomaticRetryBaseDelayMs; + for (int retry = 1; retry < attempt && delay < kAutomaticRetryMaximumDelayMs; ++retry) + delay = delay > kAutomaticRetryMaximumDelayMs / 2 ? + kAutomaticRetryMaximumDelayMs : delay * 2; + + // Spread simultaneous network clients across a bounded +/- 25 percent window. + DWORD jitterRange = delay / 2 + 1; + DWORD jitter = (GetTickCount() ^ (attempt * 0x9e3779b9UL)) % jitterRange; + DWORD randomizedDelay = delay - delay / 4 + jitter; + return randomizedDelay > kAutomaticRetryMaximumDelayMs ? + kAutomaticRetryMaximumDelayMs : randomizedDelay; +} + +inline BOOL PrepareAutomaticRetry(DWORD error, int* attempts, ERetryOperationKind kind, + HANDLE cancellationEvent, DWORD* delay) +{ + if (attempts == NULL || delay == NULL || kind == rokDestructiveCommit || + !IsTransientOperationError(error) || + (cancellationEvent != NULL && WaitForSingleObject(cancellationEvent, 0) == WAIT_OBJECT_0) || + *attempts >= kAutomaticRetryLimit) + return FALSE; + + *delay = GetAutomaticRetryDelay(++*attempts); + return TRUE; +} + +inline BOOL WaitForAutomaticRetry(HANDLE cancellationEvent, DWORD delay) +{ + // A cancellation event interrupts backoff instead of leaving the worker asleep. + if (cancellationEvent != NULL) + return WaitForSingleObject(cancellationEvent, delay) == WAIT_TIMEOUT; + return WaitForSingleObject(GetCurrentProcess(), delay) == WAIT_TIMEOUT; +} diff --git a/src/salamand.h b/src/salamand.h index 8dceca35..a39c9d08 100644 --- a/src/salamand.h +++ b/src/salamand.h @@ -1257,6 +1257,12 @@ extern const char* SalamanderConfigurationRoots[]; BOOL GetUpgradeInfo(BOOL* autoImportConfig, char* autoImportConfigFromKey, int autoImportConfigFromKeySize); // description in mainwnd2.cpp BOOL FindLatestConfiguration(BOOL* deleteConfigurations, const char*& loadConfiguration); // description in mainwnd2.cpp BOOL FindLanguageFromPrevVerOfSal(char* slgName); // description in mainwnd2.cpp +// Configuration is stored as two complete generations below this root. Call this after +// selecting a version root so all existing configuration readers use the committed generation. +void SetConfigurationStoreRoot(const char* root); +BOOL SelectCommittedConfigurationGeneration(); +BOOL UsesTransactionalConfigurationStore(); +const char* GetConfigurationSchemaDiagnostic(); // creates and attaches a special class to the edit line/combobox 'ctrlID' that enables // capturing keys and sending the WM_USER_KEYDOWN message to the dialog 'hDialog' diff --git a/src/salbzip2.cpp b/src/salbzip2.cpp index 62907059..5094d1cb 100644 --- a/src/salbzip2.cpp +++ b/src/salbzip2.cpp @@ -11,6 +11,9 @@ // CSalamanderBZIP2 // +// Keep the host on bzip2's low-level streaming API so vendor upgrades retain +// the plug-in archive adapter's existing buffer ownership and result contract. + class CSalamanderBZIP2 : public CSalamanderBZIP2Abstract { public: diff --git a/src/salmon/compress.cpp b/src/salmon/compress.cpp index 8dfc5bb0..450b2dbc 100644 --- a/src/salmon/compress.cpp +++ b/src/salmon/compress.cpp @@ -3,95 +3,335 @@ #include "precomp.h" +#include "thread_owner.h" + +#include +#include +#include + +namespace +{ +const size_t kMaximumCrashReportPathLength = 32767; +const char* const kCompressionWrapperName = "plugins\\7zip\\7zwrapper.dll"; + +void SetCompressionError(CCompressParams* compressParams, const char* format, ...) +{ + va_list arguments; + va_start(arguments, format); + int written = _vsnprintf_s(compressParams->ErrorMessage, sizeof(compressParams->ErrorMessage), + _TRUNCATE, format, arguments); + va_end(arguments); + if (written < 0) + _snprintf_s(compressParams->ErrorMessage, sizeof(compressParams->ErrorMessage), _TRUNCATE, + "Crash report compression error could not be formatted."); +} + +BOOL CopyBoundedCrashReportText(const char* source, size_t sourceCapacity, std::string* destination) +{ + destination->clear(); + if (source == NULL || sourceCapacity == 0) + { + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + + const char* terminator = (const char*)memchr(source, 0, sourceCapacity); + if (terminator == NULL) + { + SetLastError(ERROR_INVALID_DATA); + return FALSE; + } + + const size_t length = terminator - source; + if (length > kMaximumCrashReportPathLength) + { + SetLastError(ERROR_INSUFFICIENT_BUFFER); + return FALSE; + } + destination->assign(source, length); + return TRUE; +} + +BOOL GetModuleFileNameOwned(std::wstring* moduleFileName) +{ + DWORD capacity = MAX_PATH; + for (;;) + { + std::vector buffer(capacity); + DWORD length = GetModuleFileNameW(NULL, &buffer[0], capacity); + if (length == 0) + return FALSE; + if (length < capacity && buffer[length] == 0) + { + moduleFileName->assign(&buffer[0], length); + return TRUE; + } + if (capacity >= kMaximumCrashReportPathLength + 1) + break; + DWORD nextCapacity = capacity * 2; + capacity = nextCapacity > kMaximumCrashReportPathLength + 1 + ? (DWORD)(kMaximumCrashReportPathLength + 1) + : nextCapacity; + } + SetLastError(ERROR_INSUFFICIENT_BUFFER); + return FALSE; +} + +BOOL BuildCompressionWrapperPath(std::wstring* wrapperPath) +{ + std::wstring executablePath; + if (!GetModuleFileNameOwned(&executablePath)) + return FALSE; + + const std::wstring::size_type separator = executablePath.find_last_of(L"\\\\/"); + if (separator == std::wstring::npos) + { + SetLastError(ERROR_BAD_PATHNAME); + return FALSE; + } + + // Salmon is installed in utils, while the trusted wrapper is a sibling + // plugin; canonicalization below removes this explicit parent traversal. + wrapperPath->assign(executablePath, 0, separator + 1); + wrapperPath->append(L"..\\plugins\\7zip\\7zwrapper.dll"); + return TRUE; +} + +class CScopedCompressionLibrary +{ +public: + explicit CScopedCompressionLibrary(HMODULE module) : Module(module) + { + } + + ~CScopedCompressionLibrary() + { + // Keep the wrapper loaded until its exported compression callback has returned. + if (Module != NULL) + HANDLES(FreeLibrary(Module)); + } + + HMODULE Get() const { return Module; } + +private: + CScopedCompressionLibrary(const CScopedCompressionLibrary&); + CScopedCompressionLibrary& operator=(const CScopedCompressionLibrary&); + +private: + HMODULE Module; +}; + +HMODULE LoadCompressionWrapper() +{ + std::wstring wrapperPath; + if (!BuildCompressionWrapperPath(&wrapperPath)) + return NULL; + + CWidePath wideWrapperPath(wrapperPath.c_str()); + const WCHAR* fullPath = wideWrapperPath.GetFullPathForWin32Api(); + if (fullPath == NULL) + return NULL; + + // An absolute application-owned path and constrained dependency search + // prevent a crash directory, CWD, or PATH entry from supplying this DLL. + const DWORD loadFlags = LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | + LOAD_LIBRARY_SEARCH_SYSTEM32 | + LOAD_LIBRARY_SEARCH_USER_DIRS; + HMODULE module = LoadLibraryExW(fullPath, NULL, loadFlags); + const DWORD error = GetLastError(); + HANDLES_ADD_EX(__otMessages, module != NULL, __htLibrary, __hoLoadLibraryEx, + module, error, TRUE); + SetLastError(error); + return module; +} + +BOOL IsCompressionStopRequested(HANDLE stopEvent) +{ + return stopEvent != NULL && WaitForSingleObject(stopEvent, 0) == WAIT_OBJECT_0; +} + +class CCrashCompressionWorker +{ +public: + CCrashCompressionWorker() : CorrelationId(0) + { + } + + BOOL Start(CCompressParams* params, CThreadOwnerEntry entry) + { + if (params == NULL) + { + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + if (Thread.HasThread() && Thread.WaitForCompletion(0) == WAIT_TIMEOUT) + { + SetLastError(ERROR_BUSY); + return FALSE; + } + if (Thread.HasThread()) + Thread.StopAndJoin(0); // reap a completed attempt before its parameters can be reused + + const LONG nextCorrelationId = InterlockedIncrement(&NextCorrelationId); + CorrelationId = nextCorrelationId == 0 ? (DWORD)InterlockedIncrement(&NextCorrelationId) : (DWORD)nextCorrelationId; + params->CorrelationId = CorrelationId; + TRACE_I("Crash-report compression " << CorrelationId << " starting."); + if (!Thread.Start(entry, params, "CrashReportCompression")) + { + TRACE_E("Crash-report compression " << CorrelationId << " could not start: " << GetErrorText(GetLastError())); + CorrelationId = 0; + return FALSE; + } + return TRUE; + } + + BOOL IsRunning() + { + if (!Thread.HasThread()) + return FALSE; + if (Thread.WaitForCompletion(0) == WAIT_TIMEOUT) + return TRUE; + + // Completion owns the last close: reap it before the next report can reuse its params. + Thread.StopAndJoin(0); + CorrelationId = 0; + return FALSE; + } + + void Stop() + { + if (!Thread.HasThread()) + return; + + // Crash-dialog teardown keeps its input parameters alive until this + // deadline-governed join has proved the wrapper callback is finished. + const DWORD result = Thread.StopAndJoin(CThreadShutdownDeadline("crash-report compression")); + if (result == WAIT_TIMEOUT) + TRACE_E("Crash-report compression " << CorrelationId << " exceeded a shutdown deadline."); + else + TRACE_I("Crash-report compression " << CorrelationId << " stopped."); + CorrelationId = 0; + } + +private: + CThreadOwner Thread; + DWORD CorrelationId; + static volatile LONG NextCorrelationId; +}; + +volatile LONG CCrashCompressionWorker::NextCorrelationId = 0; +CCrashCompressionWorker CompressionWorker; +} // namespace + //------------------------------------------------------------------------------------------------ // // CompresBugReports() // -BOOL CompresBugReports(CCompressParams* compressParams) +BOOL CompresBugReports(CCompressParams* compressParams, HANDLE stopEvent) { BOOL ret = FALSE; compressParams->ErrorMessage[0] = 0; - const char* WRAPPER_DLL = "..\\plugins\\7zip\\7zwrapper.dll"; - HINSTANCE h7zwrapper = LoadLibrary(WRAPPER_DLL); - if (h7zwrapper != NULL) + std::string sourceDirectory; + if (!CopyBoundedCrashReportText(BugReportPath, sizeof(BugReportPath), &sourceDirectory) || sourceDirectory.empty()) + { + SetCompressionError(compressParams, "Crash report path is invalid (%lu).", GetLastError()); + return FALSE; + } + + CScopedCompressionLibrary h7zwrapper(LoadCompressionWrapper()); + if (h7zwrapper.Get() != NULL) { typedef BOOL(WINAPI * CompressFiles_t)(const char* archiveName7z, const char* sourceDir, const char* filter, char* errorMessage, int errorMessageSize); CompressFiles_t CompressFiles; - CompressFiles = (CompressFiles_t)GetProcAddress(h7zwrapper, "CompressFiles"); + CompressFiles = (CompressFiles_t)GetProcAddress(h7zwrapper.Get(), "CompressFiles"); if (CompressFiles != NULL) { - char oldCurrentDir[MAX_PATH]; - GetCurrentDirectory(MAX_PATH, oldCurrentDir); - ret = TRUE; - char error[10000]; + std::vector error(10000, 0); for (int i = 0; i < BugReports.Count; i++) { - SetCurrentDirectory(BugReportPath); + if (IsCompressionStopRequested(stopEvent)) + { + TRACE_I("Crash-report compression " << compressParams->CorrelationId << " cancelled before report " << i << "."); + SetLastError(ERROR_CANCELLED); + return FALSE; + } - char mask[MAX_PATH]; - strcpy(mask, BugReports[i].Name); - strcat(mask, ".*"); + std::string reportName; + if (!CopyBoundedCrashReportText(BugReports[i].Name, sizeof(BugReports[i].Name), &reportName) || reportName.empty()) + { + SetCompressionError(compressParams, "Crash report name is invalid (%lu).", GetLastError()); + return FALSE; + } - char archive[MAX_PATH]; - strcpy(archive, BugReports[i].Name); - strcat(archive, ".7Z"); - DeleteFile(archive); // so the subsequent compression does not fail + std::string mask = reportName + ".*"; + std::string archive = sourceDirectory; + if (archive[archive.size() - 1] != '\\') + archive.append("\\"); + archive.append(reportName); + archive.append(".7Z"); + if (archive.size() > kMaximumCrashReportPathLength) + { + SetLastError(ERROR_INSUFFICIENT_BUFFER); + SetCompressionError(compressParams, "Crash report archive path is too long (%lu).", GetLastError()); + return FALSE; + } + + CWidePath archivePath(archive.c_str()); + const WCHAR* archiveApiPath = archivePath.GetFullPathForWin32Api(); + if (archiveApiPath == NULL) + { + SetCompressionError(compressParams, "Crash report archive path is invalid (%lu).", GetLastError()); + return FALSE; + } + DeleteFileW(archiveApiPath); // so the subsequent compression does not fail error[0] = 0; - BOOL res = CompressFiles(archive, BugReportPath, mask, error, 10000); + BOOL res = CompressFiles(archive.c_str(), sourceDirectory.c_str(), mask.c_str(), &error[0], (int)error.size()); + error[error.size() - 1] = 0; if (!res) - lstrcpyn(compressParams->ErrorMessage, error, 2 * MAX_PATH - 1); + lstrcpyn(compressParams->ErrorMessage, &error[0], _countof(compressParams->ErrorMessage)); ret &= res; if (!ReportOldBugs) break; } - - SetCurrentDirectory(oldCurrentDir); } else { - sprintf(compressParams->ErrorMessage, LoadStr(IDS_SALMON_LOAD_FAILED, HLanguage), WRAPPER_DLL); + TRACE_E("Crash-report compression " << compressParams->CorrelationId << " could not resolve CompressFiles: " << GetErrorText(GetLastError())); + SetCompressionError(compressParams, LoadStr(IDS_SALMON_LOAD_FAILED, HLanguage), kCompressionWrapperName); } - FreeLibrary(h7zwrapper); } else { - sprintf(compressParams->ErrorMessage, LoadStr(IDS_SALMON_LOAD_FAILED, HLanguage), WRAPPER_DLL); + const DWORD error = GetLastError(); + TRACE_E("Crash-report compression " << compressParams->CorrelationId << " could not load " << kCompressionWrapperName << ": " << GetErrorText(error)); + SetCompressionError(compressParams, LoadStr(IDS_SALMON_LOAD_FAILED, HLanguage), kCompressionWrapperName); } return ret; } -DWORD WINAPI CompressThreadF(void* param) +DWORD WINAPI CompressThreadF(void* param, HANDLE stopEvent) { CCompressParams* compressParams = (CCompressParams*)param; - compressParams->Result = CompresBugReports(compressParams); - return EXIT_SUCCESS; + compressParams->Result = CompresBugReports(compressParams, stopEvent); + TRACE_I("Crash-report compression " << compressParams->CorrelationId << " completed with result=" << compressParams->Result << "."); + return compressParams->Result ? ERROR_SUCCESS : GetLastError(); } -HANDLE HCompressThread = NULL; - BOOL StartCompressThread(CCompressParams* params) { - if (HCompressThread != NULL) - return FALSE; - DWORD id; - HCompressThread = CreateThread(NULL, 0, CompressThreadF, params, 0, &id); - return HCompressThread != NULL; + return CompressionWorker.Start(params, CompressThreadF); } BOOL IsCompressThreadRunning() { - if (HCompressThread == NULL) - return FALSE; - DWORD res = WaitForSingleObject(HCompressThread, 0); - if (res != WAIT_TIMEOUT) - { - CloseHandle(HCompressThread); - HCompressThread = NULL; - return FALSE; - } - return TRUE; + return CompressionWorker.IsRunning(); +} + +void StopCompressThread() +{ + CompressionWorker.Stop(); } diff --git a/src/salmon/compress.h b/src/salmon/compress.h index 05597cf8..82439aea 100644 --- a/src/salmon/compress.h +++ b/src/salmon/compress.h @@ -7,8 +7,10 @@ struct CCompressParams { BOOL Result; // TRUE if the operation completed successfully, otherwise FALSE + DWORD CorrelationId; // identifies one owned crash-compression attempt in diagnostics char ErrorMessage[2 * MAX_PATH]; // if Result is FALSE, contains the error description }; BOOL StartCompressThread(CCompressParams* params); -BOOL IsCompressThreadRunning(); \ No newline at end of file +BOOL IsCompressThreadRunning(); +void StopCompressThread(); diff --git a/src/salmon/dialogs.cpp b/src/salmon/dialogs.cpp index bd56d352..15f2f5ed 100644 --- a/src/salmon/dialogs.cpp +++ b/src/salmon/dialogs.cpp @@ -401,11 +401,15 @@ CMainDialog::DialogProc(UINT uMsg, WPARAM wParam, LPARAM lParam) case WM_DESTROY: { KillTimer(HWindow, 666); + // The dialog owns CompressParams, so do not release it until its worker + // has observed cancellation and joined through the shared deadline policy. + StopCompressThread(); break; } case WM_CLOSE: { + SendMessage(HWindow, WM_COMMAND, IDCANCEL, 0); return 0; } @@ -491,9 +495,12 @@ CMainDialog::DialogProc(UINT uMsg, WPARAM wParam, LPARAM lParam) } else { - char msg[2 * MAX_PATH]; - sprintf(msg, LoadStr(IDS_SALMON_UPLOADFAILED, HLanguage), UploadParams.ErrorMessage); - MessageBox(HWindow, msg, LoadStr(IDS_SALMON_TITLE, HLanguage), MB_OK | MB_ICONEXCLAMATION | MB_SETFOREGROUND); + if (!UploadParams.Cancelled) + { + char msg[2 * MAX_PATH]; + sprintf(msg, LoadStr(IDS_SALMON_UPLOADFAILED, HLanguage), UploadParams.ErrorMessage); + MessageBox(HWindow, msg, LoadStr(IDS_SALMON_TITLE, HLanguage), MB_OK | MB_ICONEXCLAMATION | MB_SETFOREGROUND); + } CleanBugReportsDirectory(TRUE); // delete the reports, keep only the archives OpenFolder(NULL, BugReportPath); PostQuitMessage(0); @@ -521,7 +528,12 @@ CMainDialog::DialogProc(UINT uMsg, WPARAM wParam, LPARAM lParam) case IDCANCEL: { - if (Compressing || Uploading) + if (Uploading) + { + CancelUploadThread(&UploadParams); + return 0; + } + if (Compressing) { MessageBox(HWindow, LoadStr(IDS_SALMON_WAITFORUPLOAD, HLanguage), LoadStr(IDS_SALMON_TITLE, HLanguage), MB_OK | MB_ICONINFORMATION | MB_SETFOREGROUND); return 0; diff --git a/src/salmon/minidump.cpp b/src/salmon/minidump.cpp index 9e5fc1c1..97f5e0c4 100644 --- a/src/salmon/minidump.cpp +++ b/src/salmon/minidump.cpp @@ -3,21 +3,131 @@ #include "precomp.h" +#include +#include +#include + #pragma warning(push) #pragma warning(disable : 4091) // disable typedef warning without variable declaration #include #pragma warning(pop) -BOOL GenerateMiniDump(CMinidumpParams* minidumpParams, CSalmonSharedMemory* mem, BOOL smallMinidump, BOOL* overSize) +namespace +{ +const size_t kMaximumCrashReportFieldLength = MAX_PATH - 1; +const size_t kMaximumCrashReportPathLength = 32767; + +void SetMiniDumpError(CMinidumpParams* minidumpParams, const char* format, ...) +{ + va_list arguments; + va_start(arguments, format); + int written = _vsnprintf_s(minidumpParams->ErrorMessage, sizeof(minidumpParams->ErrorMessage), + _TRUNCATE, format, arguments); + va_end(arguments); + if (written < 0) + _snprintf_s(minidumpParams->ErrorMessage, sizeof(minidumpParams->ErrorMessage), _TRUNCATE, + "Crash report error could not be formatted."); +} + +BOOL CopyBoundedExternalText(const char* source, size_t sourceCapacity, size_t maximumAcceptedLength, + std::string* destination) +{ + destination->clear(); + if (source == NULL || sourceCapacity == 0) + { + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + + const char* terminator = (const char*)memchr(source, 0, sourceCapacity); + if (terminator == NULL) + { + SetLastError(ERROR_INVALID_DATA); + return FALSE; + } + + size_t length = terminator - source; + if (length > maximumAcceptedLength) + { + SetLastError(ERROR_INSUFFICIENT_BUFFER); + return FALSE; + } + destination->assign(source, length); + return TRUE; +} + +BOOL GetModuleFileNameOwned(std::string* moduleFileName) +{ + DWORD capacity = MAX_PATH; + for (;;) + { + std::vector buffer(capacity); + DWORD length = GetModuleFileNameA(NULL, &buffer[0], capacity); + if (length == 0) + return FALSE; + if (length < capacity && buffer[length] == 0) + { + moduleFileName->assign(&buffer[0], length); + return TRUE; + } + if (capacity >= kMaximumCrashReportPathLength + 1) + break; + DWORD nextCapacity = capacity * 2; + capacity = nextCapacity > kMaximumCrashReportPathLength + 1 + ? (DWORD)(kMaximumCrashReportPathLength + 1) + : nextCapacity; + } + SetLastError(ERROR_INSUFFICIENT_BUFFER); + return FALSE; +} + +BOOL BuildMiniDumpFileName(CSalmonSharedMemory* mem, std::string* dumpFileName) +{ + std::string bugPath; + std::string baseName; + if (!CopyBoundedExternalText(mem->BugPath, sizeof(mem->BugPath), kMaximumCrashReportFieldLength, &bugPath) || + !CopyBoundedExternalText(mem->BaseName, sizeof(mem->BaseName), kMaximumCrashReportFieldLength, &baseName) || + baseName.empty()) + { + return FALSE; + } + + dumpFileName->assign(bugPath); + if (!dumpFileName->empty() && (*dumpFileName)[dumpFileName->size() - 1] != '\\') + dumpFileName->append("\\"); + dumpFileName->append(baseName); + dumpFileName->append(".DMP"); + if (dumpFileName->size() > kMaximumCrashReportPathLength) + { + dumpFileName->clear(); + SetLastError(ERROR_INSUFFICIENT_BUFFER); + return FALSE; + } + return TRUE; +} +} + +// The dump policy is fixed at this boundary so callers cannot accidentally request sensitive-memory capture. +BOOL GenerateMiniDump(CMinidumpParams* minidumpParams, CSalmonSharedMemory* mem, BOOL* overSize) { BOOL ret = FALSE; *overSize = FALSE; - char szPath[MAX_PATH]; - ::GetModuleFileName(NULL, szPath, MAX_PATH); - *(strrchr(szPath, '\\')) = 0; // we are running from utils\\salmon.exe - strcat_s(szPath, "\\dbghelp.dll"); // we want a newer version, at least 6.1, which older W2K/XP do not have + std::string moduleFileName; + if (!GetModuleFileNameOwned(&moduleFileName)) + { + SetMiniDumpError(minidumpParams, "Unable to determine the crash reporter path (%lu).", GetLastError()); + return FALSE; + } + std::string::size_type slash = moduleFileName.find_last_of('\\'); + if (slash == std::string::npos) + { + SetLastError(ERROR_INVALID_DATA); + SetMiniDumpError(minidumpParams, "The crash reporter path is invalid."); + return FALSE; + } + std::string dbgHelpPath = moduleFileName.substr(0, slash + 1) + "dbghelp.dll"; static HMODULE hDbgHelp; - hDbgHelp = LoadLibrary(szPath); + hDbgHelp = LoadLibraryA(dbgHelpPath.c_str()); if (hDbgHelp != NULL) { typedef BOOL(WINAPI * MiniDumpWriteDump_t)(HANDLE, DWORD, HANDLE, MINIDUMP_TYPE, CONST PMINIDUMP_EXCEPTION_INFORMATION, @@ -29,19 +139,19 @@ BOOL GenerateMiniDump(CMinidumpParams* minidumpParams, CSalmonSharedMemory* mem, funcMakeSureDirectoryPathExists = (MakeSureDirectoryPathExists_t)GetProcAddress(hDbgHelp, "MakeSureDirectoryPathExists"); if (funcMiniDumpWriteDump != NULL && funcMakeSureDirectoryPathExists != NULL) { - char szFileName[MAX_PATH]; - strcpy(szFileName, mem->BugPath); // the path ends with a trailing backslash - int bugPathLen = (int)strlen(mem->BugPath); - if (bugPathLen > 0 && mem->BugPath[bugPathLen - 1] != '\\') - strcat(szFileName, "\\"); - strcat(szFileName, mem->BaseName); - strcat(szFileName, ".DMP"); + std::string dumpFileName; + if (!BuildMiniDumpFileName(mem, &dumpFileName)) + { + SetMiniDumpError(minidumpParams, "Crash report fields are invalid or exceed the accepted size (%lu).", + GetLastError()); + return FALSE; + } // the path may not exist yet - create it - funcMakeSureDirectoryPathExists(szFileName); // the file name is ignored + funcMakeSureDirectoryPathExists(dumpFileName.c_str()); // the file name is ignored HANDLE hDumpFile; - hDumpFile = CreateFile(szFileName, GENERIC_READ | GENERIC_WRITE, + hDumpFile = CreateFileA(dumpFileName.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE | FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0); if (hDumpFile != INVALID_HANDLE_VALUE) { @@ -53,28 +163,12 @@ BOOL GenerateMiniDump(CMinidumpParams* minidumpParams, CSalmonSharedMemory* mem, expParam.ExceptionPointers = &ePtrs; expParam.ClientPointers = FALSE; - // great explanation of the flags (better than on MSDN): http://www.debuginfo.com/articles/effminidumps.html#minidumptypes - static MINIDUMP_TYPE dumpType; - // some of the flags require dbghelp.dll 6.1 - that is why Salamander ships its own copy - if (smallMinidump) - { - dumpType = (MINIDUMP_TYPE)(MiniDumpWithProcessThreadData | - MiniDumpWithDataSegs | - MiniDumpWithFullMemoryInfo | - MiniDumpWithThreadInfo | - MiniDumpWithUnloadedModules | - MiniDumpIgnoreInaccessibleMemory); // under no circumstances do we want the function to fail - } - else - { - dumpType = (MINIDUMP_TYPE)(MiniDumpWithPrivateReadWriteMemory | - MiniDumpWithDataSegs | - MiniDumpWithHandleData | - MiniDumpWithFullMemoryInfo | - MiniDumpWithThreadInfo | - MiniDumpWithUnloadedModules | - MiniDumpIgnoreInaccessibleMemory); // under no circumstances do we want the function to fail - } + // Keep crash reports diagnostically useful without serializing arbitrary private writable memory. + static MINIDUMP_TYPE dumpType = (MINIDUMP_TYPE)(MiniDumpWithProcessThreadData | + MiniDumpWithFullMemoryInfo | + MiniDumpWithThreadInfo | + MiniDumpWithUnloadedModules | + MiniDumpIgnoreInaccessibleMemory); BOOL bMiniDumpSuccessful; bMiniDumpSuccessful = funcMiniDumpWriteDump(mem->Process, mem->ProcessId, @@ -89,7 +183,7 @@ BOOL GenerateMiniDump(CMinidumpParams* minidumpParams, CSalmonSharedMemory* mem, { // generation fails on W7 with the x64/Debug build launched from MSVC; if I run it outside MSVC, everything works fine DWORD err = GetLastError(); - sprintf(minidumpParams->ErrorMessage, LoadStr(IDS_SALMON_MINIDUMP_CALL, HLanguage), err); + SetMiniDumpError(minidumpParams, LoadStr(IDS_SALMON_MINIDUMP_CALL, HLanguage), err); } // regardless of whether minidump generation returned TRUE or FALSE, check the size of the produced dump DWORD sizeHigh = 0; @@ -101,17 +195,18 @@ BOOL GenerateMiniDump(CMinidumpParams* minidumpParams, CSalmonSharedMemory* mem, else { DWORD err = GetLastError(); - sprintf(minidumpParams->ErrorMessage, LoadStr(IDS_SALMON_MINIDUMP_CREATE, HLanguage), szFileName, err); + SetMiniDumpError(minidumpParams, LoadStr(IDS_SALMON_MINIDUMP_CREATE, HLanguage), + dumpFileName.c_str(), err); } } else { - sprintf(minidumpParams->ErrorMessage, LoadStr(IDS_SALMON_LOAD_FAILED, HLanguage), szPath); + SetMiniDumpError(minidumpParams, LoadStr(IDS_SALMON_LOAD_FAILED, HLanguage), dbgHelpPath.c_str()); } } else { - sprintf(minidumpParams->ErrorMessage, LoadStr(IDS_SALMON_LOAD_FAILED, HLanguage), szPath); + SetMiniDumpError(minidumpParams, LoadStr(IDS_SALMON_LOAD_FAILED, HLanguage), dbgHelpPath.c_str()); } return ret; @@ -120,49 +215,97 @@ BOOL GenerateMiniDump(CMinidumpParams* minidumpParams, CSalmonSharedMemory* mem, extern BOOL DirExists(const char* dirName); // based on the current time and the short Salamander version, generate a name (without an extension) -// from which the names for the text bug report and for the minidump are derived -void GetReportBaseName(char* name, int nameSize, const char* targetPath, const char* shortName, DWORD64 uid, SYSTEMTIME lt) +// from which the names for the text bug report and for the minidump are derived. The output array is +// a compatibility boundary; parsing and collision probing remain in owned, bounded dynamic strings. +BOOL GetReportBaseName(char* name, int nameSize, const char* targetPath, int targetPathSize, + const char* shortName, int shortNameSize, DWORD64 uid, SYSTEMTIME lt) { - static char year[10]; - static WORD y; + if (name == NULL || nameSize <= 0) + { + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + name[0] = 0; + + if (shortNameSize <= 0 || targetPath != NULL && targetPathSize <= 0) + { + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + + std::string boundedTargetPath; + std::string boundedShortName; + if ((targetPath != NULL && + !CopyBoundedExternalText(targetPath, targetPathSize, kMaximumCrashReportFieldLength, &boundedTargetPath)) || + !CopyBoundedExternalText(shortName, shortNameSize, kMaximumCrashReportFieldLength, &boundedShortName)) + { + return FALSE; + } - y = lt.wYear; - if (y >= 2000 && y < 2100) - sprintf_s(year, "%02u", (BYTE)(y - 2000)); + char year[5]; + if (lt.wYear >= 2000 && lt.wYear < 2100) + sprintf_s(year, "%02u", (BYTE)(lt.wYear - 2000)); else - sprintf_s(year, "%04u", y); + sprintf_s(year, "%04u", lt.wYear); - sprintf_s(name, nameSize, "%I64X-%s-%s%02u%02u-%02u%02u%02u", - uid, shortName, year, lt.wMonth, lt.wDay, lt.wHour, lt.wMinute, lt.wSecond); - CharUpperBuff(name, nameSize); // x64/x86 is lowercase, we want everything uppercased + char uidText[17]; + char timestamp[16]; + sprintf_s(uidText, "%I64X", uid); + sprintf_s(timestamp, "%s%02u%02u-%02u%02u%02u", year, lt.wMonth, lt.wDay, + lt.wHour, lt.wMinute, lt.wSecond); + std::string baseName = std::string(uidText) + "-" + boundedShortName + "-" + timestamp; + if (baseName.size() > kMaximumCrashReportPathLength) + { + SetLastError(ERROR_INSUFFICIENT_BUFFER); + return FALSE; + } + CharUpperBuffA(&baseName[0], (DWORD)baseName.size()); // x64/x86 is lowercase, we want everything uppercased - // if the target path exists, there could be a collision (unlikely thanks to the timestamp in the name) - if (targetPath != NULL && DirExists(targetPath)) + // If the target path exists, there could be a collision (unlikely thanks to the timestamp in the name). + if (!boundedTargetPath.empty() && DirExists(boundedTargetPath.c_str())) { - static char findPath[MAX_PATH]; - static char findMask[MAX_PATH]; int i; for (i = 0; i < 100; i++) // cover 1 - 99, then give up { - strcpy(findMask, name); + std::string findMask(baseName); if (i > 0) - wsprintf(findMask + strlen(findMask), "-%d", i); - lstrcat(findMask, "*"); - lstrcpy(findPath, targetPath); - int findPathLen = lstrlen(findPath); - if (findPathLen > 0 && findPath[findPathLen - 1] != '\\') - lstrcat(findPath, "\\"); - lstrcat(findPath, findMask); + { + char suffix[5]; + sprintf_s(suffix, "-%d", i); + findMask.append(suffix); + } + findMask.append("*"); + std::string findPath(boundedTargetPath); + if (findPath[findPath.size() - 1] != '\\') + findPath.append("\\"); + findPath.append(findMask); + if (findPath.size() > kMaximumCrashReportPathLength) + { + SetLastError(ERROR_INSUFFICIENT_BUFFER); + return FALSE; + } WIN32_FIND_DATA find; - HANDLE hFind = NOHANDLES(FindFirstFile(findPath, &find)); + HANDLE hFind = NOHANDLES(FindFirstFileA(findPath.c_str(), &find)); if (hFind != INVALID_HANDLE_VALUE) NOHANDLES(FindClose(hFind)); else break; // no conflict found } if (i > 0) - sprintf(name + strlen(name), "-%d", i); + { + char suffix[5]; + sprintf_s(suffix, "-%d", i); + baseName.append(suffix); + } } + + if (baseName.size() >= (size_t)nameSize) + { + SetLastError(ERROR_INSUFFICIENT_BUFFER); + return FALSE; + } + memcpy(name, baseName.c_str(), baseName.size() + 1); + return TRUE; } DWORD WINAPI MinidumpThreadF(void* param) @@ -172,23 +315,20 @@ DWORD WINAPI MinidumpThreadF(void* param) SYSTEMTIME lt; GetLocalTime(<); - // char baseName[MAX_PATH]; - GetReportBaseName(SalmonSharedMemory->BaseName, sizeof(SalmonSharedMemory->BaseName), - SalmonSharedMemory->BugPath, SalmonSharedMemory->BugName, - SalmonSharedMemory->UID, lt); - - // generate the minidump - BOOL overSize; - BOOL ret = GenerateMiniDump(minidumpParams, SalmonSharedMemory, FALSE, &overSize); - - if (!ret || overSize) + BOOL overSize = FALSE; + BOOL ret = FALSE; + if (!GetReportBaseName(SalmonSharedMemory->BaseName, sizeof(SalmonSharedMemory->BaseName), + SalmonSharedMemory->BugPath, sizeof(SalmonSharedMemory->BugPath), + SalmonSharedMemory->BugName, sizeof(SalmonSharedMemory->BugName), + SalmonSharedMemory->UID, lt)) { - GetReportBaseName(SalmonSharedMemory->BaseName, sizeof(SalmonSharedMemory->BaseName), - SalmonSharedMemory->BugPath, SalmonSharedMemory->BugName, - SalmonSharedMemory->UID, lt); - - // generate the minidump - ret = GenerateMiniDump(minidumpParams, SalmonSharedMemory, TRUE, &overSize); + SetMiniDumpError(minidumpParams, "Crash report fields are invalid or exceed the accepted size (%lu).", + GetLastError()); + } + else + { + // Privacy is the default: never retry a failed minimal dump with a memory-rich variant. + ret = GenerateMiniDump(minidumpParams, SalmonSharedMemory, &overSize); } // let Salamander know that the minidump has been created diff --git a/src/salmon/salmon.cpp b/src/salmon/salmon.cpp index b51872eb..10a1551a 100644 --- a/src/salmon/salmon.cpp +++ b/src/salmon/salmon.cpp @@ -554,7 +554,8 @@ BOOL GetBugReportNames() *strrchr(tmpName, '.') = 0; // cut it out inside the name strcat(tmpName, "-"); strcat(tmpName, SalmonSharedMemory->BugName); - GetReportBaseName(newName, sizeof(newName), BugReportPath, tmpName, SalmonSharedMemory->UID, lt); + GetReportBaseName(newName, sizeof(newName), BugReportPath, sizeof(BugReportPath), + tmpName, sizeof(tmpName), SalmonSharedMemory->UID, lt); strcat(newName, extName); // new name in the array diff --git a/src/salmon/salmon.h b/src/salmon/salmon.h index 13cfef22..7b62ad3f 100644 --- a/src/salmon/salmon.h +++ b/src/salmon/salmon.h @@ -30,7 +30,8 @@ BOOL GetBugReportNames(); int GetUniqueBugReportCount(); -void GetReportBaseName(char* name, int nameSize, const char* targetPath, const char* shortName, DWORD64 uid, SYSTEMTIME lt); +BOOL GetReportBaseName(char* name, int nameSize, const char* targetPath, int targetPathSize, + const char* shortName, int shortNameSize, DWORD64 uid, SYSTEMTIME lt); BOOL SaveDescriptionAndEmail(); diff --git a/src/salmon/upload.cpp b/src/salmon/upload.cpp index 4471e255..1e213caf 100644 --- a/src/salmon/upload.cpp +++ b/src/salmon/upload.cpp @@ -2,90 +2,507 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "precomp.h" +#include "..\\common\\checked_arithmetic.h" -#include -#define _WINSOCK_DEPRECATED_NO_WARNINGS -#include +#include #include +#include -using namespace std; +BOOL AnalyzeResponse(const char* str, int strLen, CUploadParams* uploadParams); -const char* SERVER_NAME = "reports.taskscape.com"; +namespace +{ +const wchar_t kServerName[] = L"reports.taskscape.com"; +const wchar_t kUploadPath[] = L"/api/v1/crash-reports"; +const char kMultipartBoundary[] = "---------------------------OpenSalamanderCrashReport"; +const DWORD kIoBufferSize = 64 * 1024; +const size_t kMaximumResponseSize = 64 * 1024; +const int kUploadRetryCount = 1; + +CRITICAL_SECTION UploadRequestLock; +BOOL UploadRequestLockInitialized = FALSE; +HINTERNET ActiveUploadRequest = NULL; +HINTERNET ActiveUploadSession = NULL; -BOOL CreateHTTPOutput(CUploadParams* uploadParams, char** buffer, int* bufferSize) +void SetWinHttpError(CUploadParams* uploadParams, DWORD error) { - BOOL ret = FALSE; - char fileNameOnly[MAX_PATH]; - const char* p = strrchr(uploadParams->FileName, '\\'); - if (p == NULL) - p = uploadParams->FileName; - else - p++; - lstrcpy(fileNameOnly, p); + // Preserve actionable failure classes instead of presenting every network + // failure as a generic transport error to the crash-report dialog. + if (error == ERROR_WINHTTP_TIMEOUT) + { + sprintf(uploadParams->ErrorMessage, "Network timeout (%lu).", error); + return; + } + if (error == ERROR_WINHTTP_LOGIN_FAILURE) + { + sprintf(uploadParams->ErrorMessage, "Authentication failure (%lu).", error); + return; + } + if (error == ERROR_WINHTTP_INVALID_SERVER_RESPONSE || error == ERROR_WINHTTP_HEADER_NOT_FOUND || + error == ERROR_WINHTTP_SECURE_FAILURE || error == ERROR_WINHTTP_REDIRECT_FAILED) + { + sprintf(uploadParams->ErrorMessage, "Protocol or TLS failure (%lu).", error); + return; + } + sprintf(uploadParams->ErrorMessage, LoadStr(IDS_SALMON_HTTP_ERROR, HLanguage), error); +} + +void SetHttpStatusError(CUploadParams* uploadParams, DWORD status) +{ + // HTTP authentication and a malformed/server-rejected protocol response + // must remain distinguishable from a deadline so retry advice is sound. + if (status == HTTP_STATUS_DENIED || status == HTTP_STATUS_PROXY_AUTH_REQ) + { + sprintf(uploadParams->ErrorMessage, "Authentication failure (HTTP %lu).", status); + return; + } + sprintf(uploadParams->ErrorMessage, LoadStr(IDS_SALMON_HTTP_STATUS, HLanguage), status); +} + +void FreeProxyConfig(WINHTTP_CURRENT_USER_IE_PROXY_CONFIG* proxyConfig) +{ + if (proxyConfig->lpszAutoConfigUrl != NULL) + GlobalFree(proxyConfig->lpszAutoConfigUrl); + if (proxyConfig->lpszProxy != NULL) + GlobalFree(proxyConfig->lpszProxy); + if (proxyConfig->lpszProxyBypass != NULL) + GlobalFree(proxyConfig->lpszProxyBypass); +} + +// WinHTTP's default proxy configuration supports system-wide explicit proxy +// settings. This additionally honours an explicitly configured per-user +// Internet Settings proxy; PAC and auto-detect settings remain WinHTTP's +// normal responsibility and are deliberately not interpreted by this code. +BOOL ConfigureExplicitProxy(HINTERNET session, DWORD* error) +{ + WINHTTP_CURRENT_USER_IE_PROXY_CONFIG proxyConfig; + ZeroMemory(&proxyConfig, sizeof(proxyConfig)); + if (!WinHttpGetIEProxyConfigForCurrentUser(&proxyConfig)) + { + DWORD proxyError = GetLastError(); + if (proxyError == ERROR_FILE_NOT_FOUND) + return TRUE; + *error = proxyError; + return FALSE; + } + + BOOL result = TRUE; + if (proxyConfig.lpszProxy != NULL && proxyConfig.lpszProxy[0] != L'\0') + { + WINHTTP_PROXY_INFO proxyInfo; + proxyInfo.dwAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY; + proxyInfo.lpszProxy = proxyConfig.lpszProxy; + proxyInfo.lpszProxyBypass = proxyConfig.lpszProxyBypass; + result = WinHttpSetOption(session, WINHTTP_OPTION_PROXY, &proxyInfo, sizeof(proxyInfo)); + if (!result) + *error = GetLastError(); + } + + FreeProxyConfig(&proxyConfig); + return result; +} + +BOOL IsUploadCancelled(const CUploadParams* uploadParams) +{ + return InterlockedCompareExchange((volatile LONG*)&uploadParams->Cancelled, 0, 0) != 0; +} + +void SetUploadError(CUploadParams* uploadParams, DWORD error) +{ + if (IsUploadCancelled(uploadParams)) + { + uploadParams->Cancelled = TRUE; + error = ERROR_CANCELLED; + } + SetWinHttpError(uploadParams, error); +} + +BOOL RegisterActiveUploadSession(HINTERNET session, const CUploadParams* uploadParams) +{ + BOOL registered = FALSE; + EnterCriticalSection(&UploadRequestLock); + if (!IsUploadCancelled(uploadParams)) + { + ActiveUploadSession = session; + registered = TRUE; + } + LeaveCriticalSection(&UploadRequestLock); + + if (!registered) + WinHttpCloseHandle(session); + return registered; +} + +BOOL RegisterActiveUploadRequest(HINTERNET request, const CUploadParams* uploadParams) +{ + BOOL registered = FALSE; + EnterCriticalSection(&UploadRequestLock); + if (!IsUploadCancelled(uploadParams)) + { + ActiveUploadRequest = request; + registered = TRUE; + } + LeaveCriticalSection(&UploadRequestLock); + + if (!registered) + WinHttpCloseHandle(request); + return registered; +} + +void CloseActiveUploadRequest(HINTERNET request) +{ + BOOL closeRequest = FALSE; + EnterCriticalSection(&UploadRequestLock); + if (ActiveUploadRequest == request) + { + ActiveUploadRequest = NULL; + closeRequest = TRUE; + } + LeaveCriticalSection(&UploadRequestLock); + + if (closeRequest) + WinHttpCloseHandle(request); +} + +void CloseActiveUploadSession(HINTERNET session) +{ + BOOL closeSession = FALSE; + EnterCriticalSection(&UploadRequestLock); + if (ActiveUploadSession == session) + { + ActiveUploadSession = NULL; + closeSession = TRUE; + } + LeaveCriticalSection(&UploadRequestLock); + + if (closeSession) + WinHttpCloseHandle(session); +} + +BOOL WriteRequestData(HINTERNET request, const void* data, DWORD size, DWORD* error) +{ + const BYTE* bytes = (const BYTE*)data; + while (size != 0) + { + DWORD written = 0; + if (!WinHttpWriteData(request, bytes, size, &written)) + { + *error = GetLastError(); + return FALSE; + } + if (written == 0) + { + *error = ERROR_WRITE_FAULT; + return FALSE; + } + bytes += written; + size -= written; + } + return TRUE; +} + +BOOL FinishChunkedRequest(HINTERNET request, DWORD* error) +{ + DWORD written = 0; + if (!WinHttpWriteData(request, NULL, 0, &written)) + { + *error = GetLastError(); + return FALSE; + } + return TRUE; +} + +BOOL ReadResponse(HINTERNET request, std::string* response, DWORD* error) +{ + std::vector buffer(kIoBufferSize); + DWORD bufferSize; + if (!CheckedCastSizeToDword(buffer.size(), &bufferSize)) + { + *error = ERROR_ARITHMETIC_OVERFLOW; + return FALSE; + } + for (;;) + { + DWORD available = 0; + if (!WinHttpQueryDataAvailable(request, &available)) + { + *error = GetLastError(); + return FALSE; + } + if (available == 0) + return TRUE; + size_t availableSize; + size_t responseSize; + // Network-provided byte counts must be representable before they are + // combined with owned storage or handed back to a legacy int parser. + if (!CheckedCastDwordToSize(available, &availableSize) || + !CheckedAddSize(response->size(), availableSize, &responseSize) || + responseSize > kMaximumResponseSize) + { + *error = ERROR_INSUFFICIENT_BUFFER; + return FALSE; + } + + DWORD bytesToRead = available < bufferSize ? available : bufferSize; + DWORD read = 0; + if (!WinHttpReadData(request, buffer.data(), bytesToRead, &read)) + { + *error = GetLastError(); + return FALSE; + } + if (read == 0) + { + *error = ERROR_CONNECTION_ABORTED; + return FALSE; + } + response->append(buffer.data(), read); + } +} + +BOOL UploadReportAttempt(CUploadParams* uploadParams, BOOL* retryable) +{ + *retryable = FALSE; + + const char* fileNameOnly = strrchr(uploadParams->FileName, '\\'); + fileNameOnly = fileNameOnly == NULL ? uploadParams->FileName : fileNameOnly + 1; + + char multipartPrefix[3 * MAX_PATH + 512]; + sprintf_s(multipartPrefix, sizeof(multipartPrefix), + "--%s\r\n" + "Content-Disposition: form-data; name=\"taskscapefile\"; filename=\"%s\"\r\n" + "Content-Type: application/octet-stream\r\n\r\n", + kMultipartBoundary, fileNameOnly); + const char multipartSuffix[] = "\r\n--" "---------------------------OpenSalamanderCrashReport" "--\r\n"; + DWORD multipartPrefixLength; + DWORD multipartSuffixLength; + // The multipart framing is sent through DWORD-based WinHTTP APIs, so do + // not allow a future dynamic framing change to truncate its length. + if (!CheckedCastSizeToDword(strlen(multipartPrefix), &multipartPrefixLength) || + !CheckedCastSizeToDword(strlen(multipartSuffix), &multipartSuffixLength)) + { + SetUploadError(uploadParams, ERROR_ARITHMETIC_OVERFLOW); + return FALSE; + } + + HANDLE file = CreateFile(uploadParams->FileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (file == INVALID_HANDLE_VALUE) + { + sprintf(uploadParams->ErrorMessage, LoadStr(IDS_SALMON_FILE_OPEN, HLanguage), uploadParams->FileName); + return FALSE; + } - HANDLE hFile = CreateFile(uploadParams->FileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); - if (hFile != INVALID_HANDLE_VALUE) + BOOL result = FALSE; + LARGE_INTEGER fileSize; + if (!GetFileSizeEx(file, &fileSize) || fileSize.QuadPart < 0) { - DWORD fileSize = GetFileSize(hFile, NULL); - if (fileSize != INVALID_FILE_SIZE) + sprintf(uploadParams->ErrorMessage, LoadStr(IDS_SALMON_FILE_SIZE, HLanguage), uploadParams->FileName); + } + else + { + DWORD error = ERROR_SUCCESS; + HINTERNET session = WinHttpOpen(L"Open Salamander Bug Reporter/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, + WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); + if (session == NULL) + { + SetWinHttpError(uploadParams, GetLastError()); + } + else if (!RegisterActiveUploadSession(session, uploadParams)) + { + SetUploadError(uploadParams, ERROR_CANCELLED); + } + else { - int allocatedSize = (int)(fileSize + 2000); // reserve for HTTP strings - char* header = (char*)malloc(allocatedSize); - ZeroMemory(header, allocatedSize); - if (header != NULL) + // Bound all network operations; normal certificate-chain and hostname + // validation are intentionally left enabled by not changing security flags. + if (!WinHttpSetTimeouts(session, 15000, 15000, 30000, 30000)) + { + SetUploadError(uploadParams, GetLastError()); + } + else if (!ConfigureExplicitProxy(session, &error)) { - char* s = header; - sprintf(s, "POST /upload.php HTTP/1.1\r\n"); - s += strlen(s); - sprintf(s, "Host: %s\r\n", SERVER_NAME); - s += strlen(s); - sprintf(s, "Connection: Keep-Alive\r\n"); - s += strlen(s); - sprintf(s, "Content-Type: multipart/form-data; boundary=---------------------------90721038027008\r\n"); - s += strlen(s); - sprintf(s, "Content-Length: %d\r\n", allocatedSize); - s += strlen(s); // hopefully this imprecision will not cause trouble; we will see - sprintf(s, "\r\n"); - s += strlen(s); - sprintf(s, "-----------------------------90721038027008\r\n"); - s += strlen(s); - sprintf(s, "Content-Disposition: form-data; name=\"taskscapefile\"; filename=\"%s\"\r\n", fileNameOnly); - s += strlen(s); // "protechfile" - sprintf(s, "Content-Type: application/octet-stream\r\n"); - s += strlen(s); - sprintf(s, "\r\n"); - s += strlen(s); - int headerSize = (int)strlen(header); - DWORD dwBytesRead; - if (ReadFile(hFile, s, fileSize, &dwBytesRead, NULL) && dwBytesRead == fileSize) + SetUploadError(uploadParams, error); + } + else + { + HINTERNET connection = WinHttpConnect(session, kServerName, INTERNET_DEFAULT_HTTPS_PORT, 0); + if (connection == NULL) { - s += fileSize; - sprintf(s, "\r\n"); - s += strlen(s); - sprintf(s, "-----------------------------90721038027008--\r\n"); - s += strlen(s); - - *buffer = header; - *bufferSize = (int)(s - header) - 1; - ret = TRUE; + SetUploadError(uploadParams, GetLastError()); } else - sprintf(uploadParams->ErrorMessage, LoadStr(IDS_SALMON_READING_FILE, HLanguage), uploadParams->FileName); + { + HINTERNET request = WinHttpOpenRequest(connection, L"POST", kUploadPath, NULL, WINHTTP_NO_REFERER, + WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_SECURE); + if (request == NULL) + { + SetUploadError(uploadParams, GetLastError()); + } + else + { + if (!RegisterActiveUploadRequest(request, uploadParams)) + { + SetUploadError(uploadParams, ERROR_CANCELLED); + } + else + { + // A fixed HTTPS endpoint never follows a redirect, so a server + // cannot downgrade a report to plaintext HTTP. + DWORD disabledFeatures = WINHTTP_DISABLE_REDIRECTS; + if (!WinHttpSetOption(request, WINHTTP_OPTION_DISABLE_FEATURE, &disabledFeatures, sizeof(disabledFeatures))) + { + SetUploadError(uploadParams, GetLastError()); + } + else + { + // WinHTTP owns the Transfer-Encoding framing. This avoids a + // lossy DWORD Content-Length calculation and permits reports + // larger than 4 GiB without allocating them in memory. + const wchar_t contentType[] = L"Content-Type: multipart/form-data; boundary=---------------------------OpenSalamanderCrashReport\r\nTransfer-Encoding: chunked\r\n"; + if (!WinHttpSendRequest(request, contentType, (DWORD)-1L, WINHTTP_NO_REQUEST_DATA, 0, + WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH, 0)) + { + error = GetLastError(); + // No multipart bytes have been written yet, so this is the + // one point at which repeating a POST cannot duplicate a + // committed crash report. + *retryable = !IsUploadCancelled(uploadParams); + SetUploadError(uploadParams, error); + } + else if (!WriteRequestData(request, multipartPrefix, multipartPrefixLength, &error)) + { + SetUploadError(uploadParams, error); + } + else + { + std::vector buffer(kIoBufferSize); + BOOL writeSucceeded = TRUE; + uint64_t remaining = (uint64_t)fileSize.QuadPart; + DWORD bufferSize; + if (!CheckedCastSizeToDword(buffer.size(), &bufferSize)) + { + error = ERROR_ARITHMETIC_OVERFLOW; + writeSucceeded = FALSE; + } + while (remaining != 0) + { + if (IsUploadCancelled(uploadParams)) + { + error = ERROR_CANCELLED; + writeSucceeded = FALSE; + break; + } + + DWORD read = 0; + DWORD bytesToRead = bufferSize; + if (remaining < bufferSize && !CheckedCastUInt64ToDword(remaining, &bytesToRead)) + { + error = ERROR_ARITHMETIC_OVERFLOW; + writeSucceeded = FALSE; + break; + } + if (!ReadFile(file, buffer.data(), bytesToRead, &read, NULL)) + { + error = GetLastError(); + writeSucceeded = FALSE; + break; + } + if (read == 0) + { + error = ERROR_HANDLE_EOF; + writeSucceeded = FALSE; + break; + } + if (read > remaining) + { + error = ERROR_ARITHMETIC_OVERFLOW; + writeSucceeded = FALSE; + break; + } + remaining -= read; + if (!WriteRequestData(request, buffer.data(), read, &error)) + { + writeSucceeded = FALSE; + break; + } + } + + if (!writeSucceeded) + { + SetUploadError(uploadParams, error); + } + else if (!WriteRequestData(request, multipartSuffix, multipartSuffixLength, &error)) + { + SetUploadError(uploadParams, error); + } + else if (!FinishChunkedRequest(request, &error)) + { + SetUploadError(uploadParams, error); + } + else if (!WinHttpReceiveResponse(request, NULL)) + { + SetUploadError(uploadParams, GetLastError()); + } + else + { + DWORD status = 0; + DWORD statusSize = sizeof(status); + if (!WinHttpQueryHeaders(request, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, + WINHTTP_HEADER_NAME_BY_INDEX, &status, &statusSize, WINHTTP_NO_HEADER_INDEX)) + { + SetUploadError(uploadParams, GetLastError()); + } + else if (status < 200 || status >= 300) + { + SetHttpStatusError(uploadParams, status); + } + else + { + std::string response; + if (!ReadResponse(request, &response, &error)) + SetUploadError(uploadParams, error); + else + { + int responseLength; + if (!CheckedCastSizeToInt(response.size(), &responseLength)) + SetUploadError(uploadParams, ERROR_ARITHMETIC_OVERFLOW); + else + result = AnalyzeResponse(response.c_str(), responseLength, uploadParams); + } + } + } + } + } + } + CloseActiveUploadRequest(request); + } + WinHttpCloseHandle(connection); + } } - else - sprintf(uploadParams->ErrorMessage, LoadStr(IDS_SALMON_OUT_OF_MEMORY, HLanguage)); + CloseActiveUploadSession(session); } - else - sprintf(uploadParams->ErrorMessage, LoadStr(IDS_SALMON_FILE_SIZE, HLanguage), uploadParams->FileName); + } - CloseHandle(hFile); + CloseHandle(file); + return result; +} + +BOOL UploadReport(CUploadParams* uploadParams) +{ + uploadParams->ErrorMessage[0] = 0; + + for (int attempt = 0; attempt <= kUploadRetryCount; attempt++) + { + BOOL retryable = FALSE; + if (UploadReportAttempt(uploadParams, &retryable)) + return TRUE; + if (!retryable || IsUploadCancelled(uploadParams)) + break; } - else - sprintf(uploadParams->ErrorMessage, LoadStr(IDS_SALMON_FILE_OPEN, HLanguage), uploadParams->FileName); - return ret; + return FALSE; } +} // namespace -// taken from PHP at http://php.net/manual/en/features.file-upload.errors.php +// Server response numeric status values retained for compatibility. #define UPLOAD_ERR_OK 0 #define UPLOAD_ERR_INI_SIZE 1 #define UPLOAD_ERR_FORM_SIZE 2 @@ -101,81 +518,62 @@ BOOL GetFilesError(int err, CUploadParams* uploadParams) switch (err) { case UPLOAD_ERR_OK: - { return TRUE; - } case UPLOAD_ERR_INI_SIZE: case UPLOAD_ERR_FORM_SIZE: - { sprintf(uploadParams->ErrorMessage, LoadStr(IDS_SALMON_ERR_INI_SIZE, HLanguage), err); break; - } case UPLOAD_ERR_PARTIAL: - { sprintf(uploadParams->ErrorMessage, LoadStr(IDS_SALMON_ERR_PARTIAL, HLanguage), err); break; - } case UPLOAD_ERR_NO_FILE: - { sprintf(uploadParams->ErrorMessage, LoadStr(IDS_SALMON_ERR_NO_FILE, HLanguage), err); break; - } case UPLOAD_ERR_NO_TMP_DIR: - { sprintf(uploadParams->ErrorMessage, LoadStr(IDS_SALMON_ERR_NO_TMP_DIR, HLanguage), err); break; - } case UPLOAD_ERR_CANT_WRITE: - { sprintf(uploadParams->ErrorMessage, LoadStr(IDS_SALMON_ERR_CANT_WRITE, HLanguage), err); break; - } case UPLOAD_ERR_EXTENSION: - { sprintf(uploadParams->ErrorMessage, LoadStr(IDS_SALMON_ERR_EXTENSION, HLanguage), err); break; - } default: - { sprintf(uploadParams->ErrorMessage, LoadStr(IDS_SALMON_ERR_UNKNOWN, HLanguage), err); break; } - } return FALSE; } BOOL AnalyzeResponse(const char* str, int strLen, CUploadParams* uploadParams) { - // find our response from the PHP script in the form X, where X is - // an error from http://php.net/manual/en/features.file-upload.errors.php - const char* TAG_OPEN = ""; - const char* TAG_CLOSE = ""; - const char* tagOpen = strstr(str, TAG_OPEN); + // The v1 endpoint returns X, where X matches the + // established upload-result values above. The response body is bounded + // before this parser is called. + const char* tagOpen = strstr(str, ""); if (tagOpen != NULL) { - const char* numBegin = tagOpen + strlen(TAG_OPEN); + const char* numBegin = tagOpen + strlen(""); const char* num = numBegin; while (*num >= '0' && *num <= '9' && num - numBegin < 10 && *num != 0) num++; if (num > numBegin) { - const char* tagClose = strstr(num, TAG_CLOSE); + const char* tagClose = strstr(num, ""); if (tagClose == num) { char buff[10]; lstrcpyn(buff, numBegin, (int)(num - numBegin + 1)); - int number = atoi(buff); - return GetFilesError(number, uploadParams); + return GetFilesError(atoi(buff), uploadParams); } - else - sprintf(uploadParams->ErrorMessage, LoadStr(IDS_SALMON_SYNTAX_ERROR_CLOSE, HLanguage)); + sprintf(uploadParams->ErrorMessage, LoadStr(IDS_SALMON_SYNTAX_ERROR_CLOSE, HLanguage)); } else sprintf(uploadParams->ErrorMessage, LoadStr(IDS_SALMON_SYNTAX_ERROR_VALUE, HLanguage)); @@ -188,81 +586,7 @@ BOOL AnalyzeResponse(const char* str, int strLen, CUploadParams* uploadParams) DWORD WINAPI UploadThreadF(void* param) { CUploadParams* uploadParams = (CUploadParams*)param; - uploadParams->Result = FALSE; - - char* buffer; - int bufferSize; - if (CreateHTTPOutput(uploadParams, &buffer, &bufferSize)) - { - // initialize Winsock - WSADATA wsaData = {0}; - int iResult = 0; - iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); - if (iResult == 0) - { - // resolve host name - struct hostent* remoteHost; - remoteHost = gethostbyname(SERVER_NAME); - if (remoteHost != NULL) - { - struct sockaddr_in addr; - addr.sin_family = AF_INET; - addr.sin_port = htons(80); - addr.sin_addr.s_addr = *(unsigned long*)remoteHost->h_addr; - - SOCKET connectSocket; - connectSocket = socket(AF_INET, SOCK_STREAM, 0); - if (connectSocket != INVALID_SOCKET) - { - iResult = connect(connectSocket, (SOCKADDR*)&addr, sizeof(addr)); - if (iResult != SOCKET_ERROR) - { - iResult = send(connectSocket, buffer, bufferSize, 0); - if (iResult != SOCKET_ERROR) - { - // shutdown the connection since no more data will be sent - iResult = shutdown(connectSocket, SD_SEND); - if (iResult != SOCKET_ERROR) - { - // Receive until the peer closes the connection - char recvbuf[4096]; - ZeroMemory(recvbuf, sizeof(recvbuf)); - char* recvptr = recvbuf; - do - { - iResult = recv(connectSocket, recvptr, sizeof(recvbuf) - (int)(recvptr - recvbuf) - 1, 0); - if (iResult > 0) - recvptr += iResult; - else if (iResult == 0) - uploadParams->Result = AnalyzeResponse(recvbuf, (int)(recvptr - recvbuf), uploadParams); - else - sprintf(uploadParams->ErrorMessage, LoadStr(IDS_SALMON_SOCK_ERR_RECV, HLanguage), WSAGetLastError()); - } while (iResult > 0); - } - else - sprintf(uploadParams->ErrorMessage, LoadStr(IDS_SALMON_SOCK_ERR_SHUTDOWN, HLanguage), WSAGetLastError()); - } - else - sprintf(uploadParams->ErrorMessage, LoadStr(IDS_SALMON_SOCK_ERR_SEND, HLanguage), WSAGetLastError()); - } - else - sprintf(uploadParams->ErrorMessage, LoadStr(IDS_SALMON_SOCK_ERR_CONNECT, HLanguage), WSAGetLastError()); - - closesocket(connectSocket); - } - else - sprintf(uploadParams->ErrorMessage, LoadStr(IDS_SALMON_SOCK_ERR_SOCKET, HLanguage), WSAGetLastError()); - } - else - sprintf(uploadParams->ErrorMessage, LoadStr(IDS_SALMON_SOCK_ERR_HOST, HLanguage), WSAGetLastError()); - - WSACleanup(); - } - else - sprintf(uploadParams->ErrorMessage, LoadStr(IDS_SALMON_SOCK_ERR_INIT, HLanguage), iResult); - - free(buffer); - } + uploadParams->Result = UploadReport(uploadParams); return EXIT_SUCCESS; } @@ -272,11 +596,46 @@ BOOL StartUploadThread(CUploadParams* params) { if (HUploadThread != NULL) return FALSE; + if (!UploadRequestLockInitialized) + { + InitializeCriticalSection(&UploadRequestLock); + UploadRequestLockInitialized = TRUE; + } + // Each upload owns a fresh cancellation scope and no handles from a prior run. + EnterCriticalSection(&UploadRequestLock); + ActiveUploadRequest = NULL; + ActiveUploadSession = NULL; + LeaveCriticalSection(&UploadRequestLock); + InterlockedExchange(¶ms->Cancelled, FALSE); DWORD id; HUploadThread = CreateThread(NULL, 0, UploadThreadF, params, 0, &id); return HUploadThread != NULL; } +void CancelUploadThread(CUploadParams* params) +{ + if (params == NULL || HUploadThread == NULL) + return; + + InterlockedExchange(¶ms->Cancelled, TRUE); + + HINTERNET request = NULL; + HINTERNET session = NULL; + EnterCriticalSection(&UploadRequestLock); + request = ActiveUploadRequest; + session = ActiveUploadSession; + ActiveUploadRequest = NULL; + ActiveUploadSession = NULL; + LeaveCriticalSection(&UploadRequestLock); + + // Closing both WinHTTP layers interrupts any current request and prevents + // a pending resolve/connect phase from delaying dialog shutdown. + if (request != NULL) + WinHttpCloseHandle(request); + if (session != NULL) + WinHttpCloseHandle(session); +} + BOOL IsUploadThreadRunning() { if (HUploadThread == NULL) diff --git a/src/salmon/upload.h b/src/salmon/upload.h index 707a07e0..647930da 100644 --- a/src/salmon/upload.h +++ b/src/salmon/upload.h @@ -7,9 +7,11 @@ struct CUploadParams { BOOL Result; // TRUE if the operation completed successfully, otherwise FALSE + volatile LONG Cancelled; // set when the user cancels the active upload char ErrorMessage[2 * MAX_PATH]; // if Result is FALSE, contains the error description char FileName[MAX_PATH]; // full path to the file that should be uploaded }; BOOL StartUploadThread(CUploadParams* params); -BOOL IsUploadThreadRunning(); \ No newline at end of file +BOOL IsUploadThreadRunning(); +void CancelUploadThread(CUploadParams* params); diff --git a/src/shellext/shellext.rc b/src/shellext/shellext.rc index 8eee52c5..374afc22 100644 --- a/src/shellext/shellext.rc +++ b/src/shellext/shellext.rc @@ -6,7 +6,7 @@ #include "..\plugins\shared\spl_vers.h" -#define FILE_SALVER "5.0" // without the build number and platform, e.g. "3.1 beta 1" +#define FILE_SALVER "6.0" // without the build number and platform, e.g. "3.1 beta 1" #define FILE_VER "1.49\0" #define FILE_COPYRIGHT "Copyright © 2003-2023 Taskscape Ltd\0" diff --git a/src/shexreg.h b/src/shexreg.h index 5a5ed73f..ac8553b3 100644 --- a/src/shexreg.h +++ b/src/shexreg.h @@ -102,8 +102,9 @@ DEFINE_GUID(CLSID_ShellExtension, 0xc78b614fL, 0xf3ea, 0x11d2, 0x94, 0xa1, 0x00, // Open Salamander 4.0 beta 1 - 400B1_2 (second attempt to ship "4.0 beta 1") // Open Salamander 4.0 - 400 // Open Salamander 5.0 - 500 +// Open Salamander 6.0 - 600 -#define SALSHEXT_SHAREDNAMESAPPENDIX "500" +#define SALSHEXT_SHAREDNAMESAPPENDIX "600" #ifdef ENABLE_SH_MENU_EXT diff --git a/src/shiconov.cpp b/src/shiconov.cpp index f38f6642..614f16c0 100644 --- a/src/shiconov.cpp +++ b/src/shiconov.cpp @@ -111,6 +111,7 @@ BOOL GetGoogleDrivePath(char* gdPath, int gdPathMax, CSQLite3DynLoadBase** sqlit if (ConvertA2U(sDbPath, -1, widePath, _countof(widePath)) && ConvertU2A(widePath, -1, mbPath, _countof(mbPath), FALSE, CP_UTF8)) { + // Google Drive owns this database, so its data is never a FileManager recovery or write target. int iSts = sqlite3_Dyn->open_v2(mbPath, &pDb, SQLITE_OPEN_READONLY, NULL); if (!iSts) { diff --git a/src/snooper.cpp b/src/snooper.cpp index 6e8b161a..749471de 100644 --- a/src/snooper.cpp +++ b/src/snooper.cpp @@ -23,6 +23,10 @@ HANDLE SharesEvent = NULL; // will be signaled if LanMan Shares change int SnooperSuspended = 0; +volatile LONGLONG SnooperRefreshPosts = 0; +volatile LONGLONG SnooperRefreshWaits = 0; +volatile LONGLONG SnooperSuspendRefreshPosts = 0; + CRITICAL_SECTION TimeCounterSection; // for synchronizing access to MyTimeCounter int MyTimeCounter = 0; // current time @@ -248,6 +252,8 @@ unsigned ThreadSnooperBody(void* /*param*/) // don't call main thread functions HWND wnd = refreshPanels[i]; if (IsWindow(wnd)) { + // Suspended panels are emitted as one batch, never as an unbounded retry stream. + InterlockedIncrement64(&SnooperSuspendRefreshPosts); PostMessage(wnd, WM_USER_S_REFRESH_DIR, FALSE, MyTimeCounter++); } } @@ -324,6 +330,8 @@ unsigned ThreadSnooperBody(void* /*param*/) // don't call main thread functions MainWindowCS.Unlock(); } HANDLES(EnterCriticalSection(&TimeCounterSection)); + // The following acknowledgement wait keeps at most one live snooper refresh outstanding. + InterlockedIncrement64(&SnooperRefreshPosts); PostMessage(WindowArray[index]->HWindow, WM_USER_REFRESH_DIR, TRUE, MyTimeCounter++); HANDLES(LeaveCriticalSection(&TimeCounterSection)); FindNextChangeNotification((HANDLE)ObjectArray[index]); // stornujem tuto zmenu @@ -337,6 +345,7 @@ unsigned ThreadSnooperBody(void* /*param*/) // don't call main thread functions objects[3] = RefreshFinishedEvent; // zprava od hl. threadu o ukonceni r. BOOL refreshNotFinished = TRUE; + InterlockedIncrement64(&SnooperRefreshWaits); while (refreshNotFinished) // pockame na zpracovani { // osetreni vseho krome zmen v adresarich res = WaitForMultipleObjects(4, objects, FALSE, INFINITE); @@ -370,6 +379,7 @@ unsigned ThreadSnooperBody(void* /*param*/) // don't call main thread functions sameHandle = NULL; HANDLES(EnterCriticalSection(&TimeCounterSection)); + InterlockedIncrement64(&SnooperRefreshPosts); PostMessage(WindowArray[index]->HWindow, WM_USER_REFRESH_DIR, TRUE, MyTimeCounter++); HANDLES(LeaveCriticalSection(&TimeCounterSection)); @@ -399,6 +409,15 @@ unsigned ThreadSnooperBody(void* /*param*/) // don't call main thread functions return 0; } +CDirectoryRefreshMetrics GetDirectoryRefreshMetrics() +{ + CDirectoryRefreshMetrics metrics; + metrics.Posted = InterlockedCompareExchange64(&SnooperRefreshPosts, 0, 0); + metrics.Awaited = InterlockedCompareExchange64(&SnooperRefreshWaits, 0, 0); + metrics.SuspendBatched = InterlockedCompareExchange64(&SnooperSuspendRefreshPosts, 0, 0); + return metrics; +} + unsigned ThreadSnooperEH(void* param) { #ifndef CALLSTK_DISABLE @@ -524,7 +543,9 @@ void TerminateThread() if (Thread != NULL) // terminovani threadu cmuchala { SetEvent(TerminateEvent); // pozadame cmuchala o ukonceni cinnosti - WaitForSingleObject(Thread, INFINITE); // pockame az chcipne + // Directory notifications can be delayed by a provider, so retain the + // event and shared counters through cancellation and recovery phases. + CThreadShutdownDeadline("directory snooper").WaitForSafeJoin(Thread); HANDLES(CloseHandle(Thread)); // zavreme handle threadu } if (DataUsageMutex != NULL) @@ -547,13 +568,11 @@ void TerminateThread() if (SafeFindCloseThread != NULL) { - SafeFindCloseTerminate = TRUE; // zakilujeme thread + SafeFindCloseTerminate = TRUE; // request that the closer exits after its current work SetEvent(SafeFindCloseStart); - if (WaitForSingleObject(SafeFindCloseThread, 1000) == WAIT_TIMEOUT) // pockame az se ukonci - { - TerminateThread(SafeFindCloseThread, 666); // nepovedlo se, zabijeme ho natvrdo - WaitForSingleObject(SafeFindCloseThread, INFINITE); // pockame az thread skutecne skonci, nekdy mu to dost trva - } + // The closer owns its critical section until its callback returns; a + // deadline reports a stuck provider but never tears down that state. + CThreadShutdownDeadline("safe notification-handle closer").WaitForSafeJoin(SafeFindCloseThread); HANDLES(CloseHandle(SafeFindCloseThread)); } if (SafeFindCloseStart != NULL) diff --git a/src/snooper.h b/src/snooper.h index fb2f57b7..14119f8e 100644 --- a/src/snooper.h +++ b/src/snooper.h @@ -8,6 +8,17 @@ class CFilesWindow; extern HANDLE RefreshFinishedEvent; extern int SnooperSuspended; +// Refresh notifications are deliberately admitted one at a time; expose that +// pressure so a slow panel can be distinguished from missed directory events. +struct CDirectoryRefreshMetrics +{ + LONGLONG Posted; + LONGLONG Awaited; + LONGLONG SuspendBatched; +}; + +CDirectoryRefreshMetrics GetDirectoryRefreshMetrics(); + void AddDirectory(CFilesWindow* win, const char* path, BOOL registerDevNotification); // new directory for snooper void ChangeDirectory(CFilesWindow* win, const char* newPath, BOOL registerDevNotification); // change of the specified directory void DetachDirectory(CFilesWindow* win, BOOL waitForHandleClosure = FALSE, BOOL closeDevNotifification = TRUE); // uz neni treba cmuchat diff --git a/src/string_resources.cpp b/src/string_resources.cpp index 651b0e79..0c72b2f0 100644 --- a/src/string_resources.cpp +++ b/src/string_resources.cpp @@ -272,6 +272,7 @@ CRITICAL_SECTION SafeWaitMessageTextSection; // for synchronizing access to Safe BOOL SafeWaitMessageCallerSet = FALSE; unsigned SafeWaitMessageCallerID = 0; BOOL SafeWaitWindowClosePressed = FALSE; +HANDLE SafeWaitWindowCancelEvent = NULL; class C__SafeWaitMessageCallerSetSection { @@ -500,6 +501,16 @@ void CreateSafeWaitWindow(const char* message, const char* caption, if (!SafeWaitMessageCallerSet) // only one message (check for availability) { SafeWaitMessageCallerSet = TRUE; + // The UI owns the original event while callers wait on duplicated + // handles, preventing a close-button click from racing teardown. + SafeWaitWindowCancelEvent = HANDLES(CreateEvent(NULL, TRUE, FALSE, NULL)); + if (SafeWaitWindowCancelEvent == NULL) + { + SafeWaitMessageCallerSet = FALSE; + HANDLES(LeaveCriticalSection(&SafeWaitMessageCallerSetSection.cs)); + TRACE_E("Unable to create SafeWait cancellation event."); + return; + } HANDLES(LeaveCriticalSection(&SafeWaitMessageCallerSetSection.cs)); SafeWaitWindowClosePressed = FALSE; SafeWaitMessageCallerID = GetCurrentThreadId(); @@ -509,11 +520,18 @@ void CreateSafeWaitWindow(const char* message, const char* caption, (void*)(UINT_PTR)showCloseButton, 0, &SafeWaitMessageThreadID)); if (thread == NULL) { + HANDLES(EnterCriticalSection(&SafeWaitMessageCallerSetSection.cs)); + SafeWaitMessageCallerSet = FALSE; + HANDLE cancelEvent = SafeWaitWindowCancelEvent; + SafeWaitWindowCancelEvent = NULL; + HANDLES(LeaveCriticalSection(&SafeWaitMessageCallerSetSection.cs)); + HANDLES(CloseHandle(cancelEvent)); TRACE_E("Unable to start ThreadSafeWaitWindow thread."); return; } SetThreadPriority(thread, THREAD_PRIORITY_ABOVE_NORMAL); // so it actually wins against the main thread - AddAuxThread(thread); + // The message thread owns UI resources that must outlive its safe join. + AddAuxThread(thread, FALSE, "safe-wait message loop"); HANDLES(InitializeCriticalSection(&SafeWaitMessageTextSection)); SafeWaitMessageThreadStarted = TRUE; } @@ -550,9 +568,14 @@ void DestroySafeWaitWindow(BOOL killThread) SafeWaitMessageCallerID == GetCurrentThreadId()) // this is the thread that opened it { SafeWaitMessageCallerSet = FALSE; + HANDLE cancelEvent = SafeWaitWindowCancelEvent; + SafeWaitWindowCancelEvent = NULL; HANDLES(LeaveCriticalSection(&SafeWaitMessageCallerSetSection.cs)); SafeWaitMessageCallerID = 0; + if (cancelEvent != NULL) + HANDLES(CloseHandle(cancelEvent)); + if (SafeWaitMessageThreadStarted) // the thread is running; hide the window and possibly destroy it { PostThreadMessage(SafeWaitMessageThreadID, WM_USER_DESTROYWAITWND, killThread, 0); @@ -591,6 +614,40 @@ BOOL UserWantsToCancelSafeWaitWindow() return (GetAsyncKeyState(VK_ESCAPE) & 0x8001) && SalamanderActive() || GetSafeWaitWindowClosePressed(); } +HANDLE GetSafeWaitWindowCancelEvent() +{ + HANDLE duplicate = NULL; + HANDLES(EnterCriticalSection(&SafeWaitMessageCallerSetSection.cs)); + if (SafeWaitMessageCallerSet && + SafeWaitMessageCallerID == GetCurrentThreadId() && + SafeWaitWindowCancelEvent != NULL) + { + // The duplicate gives the caller a stable cancellation handle even if + // its wait window is destroyed while the caller is unwinding. + if (!HANDLES(DuplicateHandle(GetCurrentProcess(), SafeWaitWindowCancelEvent, + GetCurrentProcess(), &duplicate, SYNCHRONIZE, + FALSE, 0))) + { + TRACE_E("Unable to duplicate SafeWait cancellation event: " << GetErrorText(GetLastError())); + duplicate = NULL; + } + } + HANDLES(LeaveCriticalSection(&SafeWaitMessageCallerSetSection.cs)); + return duplicate; +} + +void SignalSafeWaitWindowCancellation() +{ + HANDLES(EnterCriticalSection(&SafeWaitMessageCallerSetSection.cs)); + if (SafeWaitWindowCancelEvent != NULL) + { + // Preserve close-button compatibility while waking signaled waiters + // immediately instead of waiting for their next deadline. + SetEvent(SafeWaitWindowCancelEvent); + } + HANDLES(LeaveCriticalSection(&SafeWaitMessageCallerSetSection.cs)); +} + void ShowSafeWaitWindow(BOOL show) { HANDLES(EnterCriticalSection(&SafeWaitMessageCallerSetSection.cs)); diff --git a/src/svg.cpp b/src/svg.cpp index b586c4e5..eb5da615 100644 --- a/src/svg.cpp +++ b/src/svg.cpp @@ -157,8 +157,13 @@ void RenderSVGImage(NSVGrasterizer* rast, HDC hDC, int x, int y, const char* svg } } - float scale = sysDPIScale / 100; - nsvgRasterize(rast, image, 0, 0, scale, (BYTE*)lpMemBits, iconSize, iconSize, iconSize * 4); + // Fit the vector artwork to the requested image-list cell; DPI is already reflected in iconSize. + float scaleX = (float)iconSize / image->width; + float scaleY = (float)iconSize / image->height; + float scale = min(scaleX, scaleY); + float xOffset = (iconSize - image->width * scale) / 2; + float yOffset = (iconSize - image->height * scale) / 2; + nsvgRasterize(rast, image, xOffset, yOffset, scale, (BYTE*)lpMemBits, iconSize, iconSize, iconSize * 4); nsvgDelete(image); BLENDFUNCTION bf; diff --git a/src/texts.rh2 b/src/texts.rh2 index 7674be6a..0c153715 100644 --- a/src/texts.rh2 +++ b/src/texts.rh2 @@ -1109,6 +1109,11 @@ // This link is volume mount point. Unable to show it in panel.\nPath: %s #define IDS_DIRLINK_MOUNT_POINT 10875 +// Customize Toolbar uses explicit dimensions so translated labels preserve the size contract. +#define IDS_TOOLBAR_ICON_SIZE_SMALL 10876 +#define IDS_TOOLBAR_ICON_SIZE_MEDIUM 10877 +#define IDS_TOOLBAR_ICON_SIZE_LARGE 10878 + // Find File Advanced Dialog #define IDS_FFA_SECONDS 10880 @@ -2660,6 +2665,8 @@ #define IDS_SALMON_BUGREPORT_PROBLEM 14073 #define IDS_SALMON_MINIDUMP_CREATE 14074 #define IDS_SALMON_MINIDUMP_CALL 14075 +#define IDS_SALMON_HTTP_ERROR 14076 +#define IDS_SALMON_HTTP_STATUS 14077 // salmon.exe is not running #define IDS_SALMON_NOT_RUNNING 14100 @@ -2709,6 +2716,9 @@ // shutdown: wait window: Closing Find windows, please wait... #define IDS_CLOSINGFINDWINDOWS 14195 +// Directory listing retained only the bounded, currently usable metadata cache. +#define IDS_DIRECTORYENUMERATIONLIMIT 14196 + //#define CM_TEXTS_MAX 18000 // maximal texts id #endif // __TEXTS_RH2 diff --git a/src/toolbar.h b/src/toolbar.h index 40584603..6451d9db 100644 --- a/src/toolbar.h +++ b/src/toolbar.h @@ -147,6 +147,9 @@ class CToolBar : public CWindow, public CGUIToolBarAbstract virtual void WINAPI SetPadding(const TOOLBAR_PADDING* padding); virtual void WINAPI GetPadding(TOOLBAR_PADDING* padding); + // Applies the proportional Fluent-family spacing used by configurable main-toolbar icon sizes. + void ApplyConfiguredIconSpacing(); + // goes through all items and if they have 'EnablerData' pointer set // compares values (it points to) with actual item state. // If state differs, changes it. @@ -221,11 +224,13 @@ class CTBCustomizeDialog : public CCommonDialog DWORD DragNotify; TBCDDragMode DragMode; int DragIndex; + int SelectedIconSize; // staged until the modal dialog closes so its preview image list remains valid public: CTBCustomizeDialog(CToolBar* toolBar); ~CTBCustomizeDialog(); BOOL Execute(); + int GetSelectedIconSize() const { return SelectedIconSize; } protected: virtual INT_PTR DialogProc(UINT uMsg, WPARAM wParam, LPARAM lParam); diff --git a/src/toolbar_button_defs.cpp b/src/toolbar_button_defs.cpp index 739812ac..56a60f3a 100644 --- a/src/toolbar_button_defs.cpp +++ b/src/toolbar_button_defs.cpp @@ -19,13 +19,13 @@ struct CButtonData { unsigned int ImageIndex : 16; // zero base index unsigned int Shell32ResID : 8; // 0: icon from image list; 1..254: icon resID from shell32.dll; 255: only allocate free space - unsigned int ToolTipResID : 16; // resID se stringem pro tooltip - unsigned int ID : 16; // univerzalni Command - unsigned int LeftID : 16; // Command pro levy panel - unsigned int RightID : 16; // Command pro pravy panel + unsigned int ToolTipResID : 16; // resource ID of the tooltip string + unsigned int ID : 16; // universal command + unsigned int LeftID : 16; // command for the left panel + unsigned int RightID : 16; // command for the right panel unsigned int DropDown : 1; // will it have drop down? unsigned int WholeDropDown : 1; // will it have whole drop down? - unsigned int Check : 1; // jedna se o checkbox? + unsigned int Check : 1; // is this a checkbox? DWORD* Enabler; // control variable for enabling the button DWORD* LeftEnabler; // control variable for enabling the button DWORD* RightEnabler; // control variable for enabling the button @@ -36,7 +36,7 @@ struct CButtonData // // TBButtonEnum // -// Unikatni indexy do pole ToolBarButton - slouzi k adresaci tohoto pole. +// Unique indexes into the ToolBarButton array, used to address its entries. // Items may only be appended to the end of this array. // @@ -45,12 +45,12 @@ struct CButtonData #define TBBE_CREATE_DIR 2 #define TBBE_FIND_FILE 3 #define TBBE_VIEW_MODE 4 // drive brief -//#define TBBE_DETAILED 5 // vyrazeno +//#define TBBE_DETAILED 5 // removed #define TBBE_SORT_NAME 6 #define TBBE_SORT_EXT 7 #define TBBE_SORT_SIZE 8 #define TBBE_SORT_DATE 9 -//#define TBBE_SORT_ATTR 10 // vyrazeno +//#define TBBE_SORT_ATTR 10 // removed #define TBBE_PARENT_DIR 11 #define TBBE_ROOT_DIR 12 #define TBBE_FILTER 13 @@ -97,7 +97,7 @@ struct CButtonData #define TBBE_PERMISSIONS 54 #define TBBE_CONVERT 55 #define TBBE_UNSELECT_ALL 56 -#define TBBE_MENU 57 // vstup do menu +#define TBBE_MENU 57 // menu entry #define TBBE_ALTVIEW 58 #define TBBE_EXIT 59 #define TBBE_OCCUPIEDSPACE 60 @@ -185,22 +185,23 @@ CButtonData ToolBarButtons[TBBE_TERMINATOR] = /*TBBE_CHANGE_ATTR*/ {IDX_TB_CHANGEATTR, 0, IDS_TBTT_CHANGEATTR, CM_CHANGEATTR, 0, 0, 0, 0, 0, &EnablerChangeAttrs, NULL, NULL, "ChangeAttributes"}, /*TBBE_COMPARE_DIR*/ {IDX_TB_COMPAREDIR, 0, IDS_TBTT_COMPAREDIR, CM_COMPAREDIRS, 0, 0, 0, 0, 0, NULL, NULL, NULL, "CompareDirectories"}, /*TBBE_DRIVE_INFO*/ {IDX_TB_DRIVEINFO, 0, IDS_TBTT_DRIVEINFO, CM_DRIVEINFO, 0, 0, 0, 0, 0, &EnablerDriveInfo, NULL, NULL, "DriveInformation"}, - /*TBBE_CHANGE_DRIVE_L*/ {IDX_TB_CHANGEDRIVEL, 255, IDS_TBTT_CHANGEDRIVE, CM_LCHANGEDRIVE, CM_LCHANGEDRIVE, 0, 0, 1, 0, NULL, NULL, NULL, NULL}, + /*TBBE_CHANGE_DRIVE_L*/ {IDX_TB_CHANGEDRIVEL, 0, IDS_TBTT_CHANGEDRIVE, CM_LCHANGEDRIVE, CM_LCHANGEDRIVE, 0, 0, 1, 0, NULL, NULL, NULL, "ChangeDrive"}, /*TBBE_SELECT*/ {IDX_TB_SELECT, 0, IDS_TBTT_SELECT, CM_ACTIVESELECT, 0, 0, 0, 0, 0, NULL, NULL, NULL, "Select"}, /*TBBE_UNSELECT*/ {IDX_TB_UNSELECT, 0, IDS_TBTT_UNSELECT, CM_ACTIVEUNSELECT, 0, 0, 0, 0, 0, &EnablerSelected, NULL, NULL, "Unselect"}, /*TBBE_INVERT_SEL*/ {IDX_TB_INVERTSEL, 0, IDS_TBTT_INVERTSEL, CM_ACTIVEINVERTSEL, 0, 0, 0, 0, 0, NULL, NULL, NULL, "InvertSelection"}, /*TBBE_SELECT_ALL*/ {IDX_TB_SELECTALL, 0, IDS_TBTT_SELECTALL, CM_ACTIVESELECTALL, 0, 0, 0, 0, 0, NULL, NULL, NULL, "SelectAll"}, /*TBBE_PACK*/ {IDX_TB_PACK, 0, IDS_TBTT_PACK, CM_PACK, 0, 0, 0, 0, 0, &EnablerFilesOnDisk, NULL, NULL, "Pack"}, /*TBBE_UNPACK*/ {IDX_TB_UNPACK, 0, IDS_TBTT_UNPACK, CM_UNPACK, 0, 0, 0, 0, 0, &EnablerFileOnDisk, NULL, NULL, "Unpack"}, - /*TBBE_OPEN_ACTIVE*/ {NIB1(IDX_TB_OPENACTIVE), 5, IDS_TBTT_OPENACTIVE, CM_OPENACTUALFOLDER, 0, 0, 0, 0, 0, &EnablerOpenActiveFolder, NULL, NULL, NULL}, - /*TBBE_OPEN_DESKTOP*/ {NIB1(IDX_TB_OPENDESKTOP), 35, IDS_TBTT_OPENDESKTOP, CM_OPENDESKTOP, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL}, - /*TBBE_OPEN_MYCOMP*/ {NIB1(IDX_TB_OPENMYCOMP), 16, IDS_TBTT_OPENMYCOMP, CM_OPENMYCOMP, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL}, - /*TBBE_OPEN_CONTROL*/ {NIB1(IDX_TB_OPENCONTROL), 137, IDS_TBTT_OPENCONTROL, CM_OPENCONROLPANEL, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL}, - /*TBBE_OPEN_PRINTERS*/ {NIB1(IDX_TB_OPENPRINTERS), 138, IDS_TBTT_OPENPRINTERS, CM_OPENPRINTERS, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL}, - /*TBBE_OPEN_NETWORK*/ {NIB1(IDX_TB_OPENNETWORK), 18, IDS_TBTT_OPENNETWORK, CM_OPENNETNEIGHBOR, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL}, - /*TBBE_OPEN_RECYCLE*/ {NIB1(IDX_TB_OPENRECYCLE), 33, IDS_TBTT_OPENRECYCLE, CM_OPENRECYCLEBIN, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL}, - /*TBBE_OPEN_FONTS*/ {NIB1(IDX_TB_OPENFONTS), 39, IDS_TBTT_OPENFONTS, CM_OPENFONTS, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL}, - /*TBBE_CHANGE_DRIVE_R*/ {IDX_TB_CHANGEDRIVER, 255, IDS_TBTT_CHANGEDRIVE, CM_RCHANGEDRIVE, 0, CM_RCHANGEDRIVE, 0, 1, 0, NULL, NULL, NULL, NULL}, + // Core command visuals stay deterministic instead of inheriting version-specific shell32 artwork. + /*TBBE_OPEN_ACTIVE*/ {NIB1(IDX_TB_OPENACTIVE), 0, IDS_TBTT_OPENACTIVE, CM_OPENACTUALFOLDER, 0, 0, 0, 0, 0, &EnablerOpenActiveFolder, NULL, NULL, "OpenActiveFolder"}, + /*TBBE_OPEN_DESKTOP*/ {NIB1(IDX_TB_OPENDESKTOP), 0, IDS_TBTT_OPENDESKTOP, CM_OPENDESKTOP, 0, 0, 0, 0, 0, NULL, NULL, NULL, "OpenDesktop"}, + /*TBBE_OPEN_MYCOMP*/ {NIB1(IDX_TB_OPENMYCOMP), 0, IDS_TBTT_OPENMYCOMP, CM_OPENMYCOMP, 0, 0, 0, 0, 0, NULL, NULL, NULL, "OpenComputer"}, + /*TBBE_OPEN_CONTROL*/ {NIB1(IDX_TB_OPENCONTROL), 0, IDS_TBTT_OPENCONTROL, CM_OPENCONROLPANEL, 0, 0, 0, 0, 0, NULL, NULL, NULL, "OpenControlPanel"}, + /*TBBE_OPEN_PRINTERS*/ {NIB1(IDX_TB_OPENPRINTERS), 0, IDS_TBTT_OPENPRINTERS, CM_OPENPRINTERS, 0, 0, 0, 0, 0, NULL, NULL, NULL, "OpenPrinters"}, + /*TBBE_OPEN_NETWORK*/ {NIB1(IDX_TB_OPENNETWORK), 0, IDS_TBTT_OPENNETWORK, CM_OPENNETNEIGHBOR, 0, 0, 0, 0, 0, NULL, NULL, NULL, "OpenNetwork"}, + /*TBBE_OPEN_RECYCLE*/ {NIB1(IDX_TB_OPENRECYCLE), 0, IDS_TBTT_OPENRECYCLE, CM_OPENRECYCLEBIN, 0, 0, 0, 0, 0, NULL, NULL, NULL, "OpenRecycleBin"}, + /*TBBE_OPEN_FONTS*/ {NIB1(IDX_TB_OPENFONTS), 0, IDS_TBTT_OPENFONTS, CM_OPENFONTS, 0, 0, 0, 0, 0, NULL, NULL, NULL, "OpenFonts"}, + /*TBBE_CHANGE_DRIVE_R*/ {IDX_TB_CHANGEDRIVER, 0, IDS_TBTT_CHANGEDRIVE, CM_RCHANGEDRIVE, 0, CM_RCHANGEDRIVE, 0, 1, 0, NULL, NULL, NULL, "ChangeDrive"}, /*TBBE_HELP_CONTENTS*/ {NIB1(IDX_TB_HELP), 0, IDS_TBTT_HELP, CM_HELP_CONTENTS, 0, 0, 0, 0, 0, NULL, NULL, NULL, "HelpContents"}, /*TBBE_HELP_CONTEXT*/ {NIB1(IDX_TB_CONTEXTHELP), 0, IDS_TBTT_CONTEXTHELP, CM_HELP_CONTEXT, 0, 0, 0, 0, 0, NULL, NULL, NULL, "WhatIsThis"}, /*TBBE_PERMISSIONS*/ {IDX_TB_PERMISSIONS, 0, IDS_TBTT_PERMISSIONS, CM_SEC_PERMISSIONS, 0, 0, 0, 0, 0, &EnablerPermissions, NULL, NULL, "Security"}, @@ -222,8 +223,8 @@ CButtonData ToolBarButtons[TBBE_TERMINATOR] = /*TBBE_HOTPATHSDROP*/ {IDX_TB_HOTPATHS, 0, IDS_TBTT_HOTPATHSDROP, CM_OPENHOTPATHSDROP, 0, 0, 0, 1, 0, NULL, NULL, NULL, "GoToHotPath"}, /*TBBE_USER_MENU*/ {IDX_TB_USERMENU, 0, IDS_TBTT_USERMENU, CM_USERMENU, 0, 0, 0, 0, 0, &EnablerOnDisk, NULL, NULL, "UserMenu"}, /*TBBE_EMAIL*/ {IDX_TB_EMAIL, 0, IDS_TBTT_EMAIL, CM_EMAILFILES, 0, 0, 0, 0, 0, &EnablerFilesOnDisk, NULL, NULL, "Email"}, - /*TBBE_ZOOM_PANEL*/ {0xFFFF, 0, IDS_TBTT_ZOOMPANEL, CM_ACTIVEZOOMPANEL, 0, 0, 0, 0, 0, NULL, NULL, NULL, "SharedDirectories"}, - /*TBBE_SHARES*/ {IDX_TB_SHARED_DIRS, 0, IDS_TBTT_SHARES, CM_SHARES, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL}, + /*TBBE_ZOOM_PANEL*/ {0xFFFF, 0, IDS_TBTT_ZOOMPANEL, CM_ACTIVEZOOMPANEL, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL}, + /*TBBE_SHARES*/ {IDX_TB_SHARED_DIRS, 0, IDS_TBTT_SHARES, CM_SHARES, 0, 0, 0, 0, 0, NULL, NULL, NULL, "SharedDirectories"}, /*TBBE_FULLSCREEN*/ {0xFFFF, 0, IDS_TBTT_FULLSCREEN, CM_FULLSCREEN, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL}, /*TBBE_PREV_SELECTED*/ {IDX_TB_PREV_SELECTED, 0, IDS_TBTT_PREV_SEL, CM_GOTO_PREV_SEL, 0, 0, 0, 0, 0, &EnablerSelGotoPrev, NULL, NULL, "GoToPreviousSelectedName"}, /*TBBE_NEXT_SELECTED*/ {IDX_TB_NEXT_SELECTED, 0, IDS_TBTT_NEXT_SEL, CM_GOTO_NEXT_SEL, 0, 0, 0, 0, 0, &EnablerSelGotoNext, NULL, NULL, "GoToNextSelectedName"}, @@ -246,7 +247,7 @@ CButtonData ToolBarButtons[TBBE_TERMINATOR] = /*TBBE_HIDE_SELECTED*/ {IDX_TB_HIDE_SELECTED, 0, IDS_TBTT_HIDE_SELECTED, CM_HIDE_SELECTED_NAMES, 0, 0, 0, 0, 0, &EnablerSelected, NULL, NULL, "HideSelectedNames"}, /*TBBE_HIDE_UNSELECTED*/ {IDX_TB_HIDE_UNSELECTED, 0, IDS_TBTT_HIDE_UNSELECTED, CM_HIDE_UNSELECTED_NAMES, 0, 0, 0, 0, 0, &EnablerUnselected, NULL, NULL, "HideUnselectedNames"}, /*TBBE_SHOW_ALL*/ {IDX_TB_SHOW_ALL, 0, IDS_TBTT_SHOW_ALL, CM_SHOW_ALL_NAME, 0, 0, 0, 0, 0, &EnablerHiddenNames, NULL, NULL, "ShowHiddenNames"}, - /*TBBE_OPEN_MYDOC*/ {NIB1(IDX_TB_OPENMYDOC), 21, IDS_TBTT_OPENMYDOC, CM_OPENPERSONAL, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL}, + /*TBBE_OPEN_MYDOC*/ {NIB1(IDX_TB_OPENMYDOC), 0, IDS_TBTT_OPENMYDOC, CM_OPENPERSONAL, 0, 0, 0, 0, 0, NULL, NULL, NULL, "OpenDocuments"}, /*TBBE_SMART_COLUMN_MODE*/ {IDX_TB_SMART_COLUMN_MODE, 0, IDS_TBTT_SMARTMODE, CM_ACTIVE_SMARTMODE, CM_LEFT_SMARTMODE, CM_RIGHT_SMARTMODE, 0, 0, 0, NULL, NULL, NULL, "SmartColumnMode"}, }; @@ -256,7 +257,7 @@ CButtonData ToolBarButtons[TBBE_TERMINATOR] = // // Represents all possible buttons that TopToolbar can contain. // Order defines button order in the toolbar configuration dialog and can -// byt libovolne meneno. +// be changed arbitrarily. // DWORD TopToolBarButtons[] = @@ -404,23 +405,26 @@ DWORD RightToolBarButtons[] = void GetSVGIconsMainToolbar(CSVGIcon** svgIcons, int* svgIconsCount) { - static CSVGIcon SVGIcons[TBBE_TERMINATOR]; + // Cover the two find-window-only bitmap slots so every core toolbar/menu icon is SVG-backed. + static CSVGIcon SVGIcons[TBBE_TERMINATOR + 2]; for (auto i = 0; i < TBBE_TERMINATOR; i++) { SVGIcons[i].ImageIndex = ToolBarButtons[i].ImageIndex; SVGIcons[i].SVGName = ToolBarButtons[i].SVGName; } + SVGIcons[TBBE_TERMINATOR] = {IDX_TB_FOCUS, "Focus"}; + SVGIcons[TBBE_TERMINATOR + 1] = {IDX_TB_STOP, "Stop"}; *svgIcons = SVGIcons; - *svgIconsCount = TBBE_TERMINATOR; + *svgIconsCount = _countof(SVGIcons); } //**************************************************************************** // // CreateGrayscaleAndMaskBitmaps // -// Vytvori novou bitmapu o hloubce 24 bitu, nakopiruje do ni zdrojovou -// bitmapu a prevede ji na stupne sedi. Zaroven pripravi druhou bitmapu -// s maskou dle transparentni barvy. +// Creates a new 24-bit bitmap, copies the source bitmap into it, and converts +// it to grayscale. It also prepares a second bitmap containing a mask based +// on the transparent color. // BOOL CreateGrayscaleAndMaskBitmaps(HBITMAP hSource, COLORREF transparent, @@ -434,11 +438,11 @@ BOOL CreateGrayscaleAndMaskBitmaps(HBITMAP hSource, COLORREF transparent, hMask = NULL; HDC hDC = HANDLES(GetDC(NULL)); - // vytahnu rozmery bitmapy + // Retrieve the bitmap dimensions. BITMAPINFO bi; memset(&bi, 0, sizeof(bi)); bi.bmiHeader.biSize = sizeof(bi.bmiHeader); - bi.bmiHeader.biBitCount = 0; // nechceme paletu + bi.bmiHeader.biBitCount = 0; // no palette is required if (!GetDIBits(hDC, hSource, @@ -457,9 +461,9 @@ BOOL CreateGrayscaleAndMaskBitmaps(HBITMAP hSource, COLORREF transparent, goto exitus; } - // pozadovana barevna hloubka je 24 bitu + // The requested color depth is 24 bits. bi.bmiHeader.biSizeImage = ((((bi.bmiHeader.biWidth * 24) + 31) & ~31) >> 3) * bi.bmiHeader.biHeight; - // naalokuju potrebny prostor + // Allocate the required space. lpvBits = malloc(bi.bmiHeader.biSizeImage); if (lpvBits == NULL) { @@ -477,7 +481,7 @@ BOOL CreateGrayscaleAndMaskBitmaps(HBITMAP hSource, COLORREF transparent, bi.bmiHeader.biBitCount = 24; bi.bmiHeader.biCompression = BI_RGB; - // vytahnu vlastni data + // Retrieve the pixel data. if (!GetDIBits(hDC, hSource, 0, bi.bmiHeader.biHeight, @@ -489,7 +493,7 @@ BOOL CreateGrayscaleAndMaskBitmaps(HBITMAP hSource, COLORREF transparent, goto exitus; } - // vytahnu vlastni data pro mask + // Retrieve the mask data. if (!GetDIBits(hDC, hSource, 0, bi.bmiHeader.biHeight, @@ -501,7 +505,7 @@ BOOL CreateGrayscaleAndMaskBitmaps(HBITMAP hSource, COLORREF transparent, goto exitus; } - // prevedu na grayscale + // Convert the image to grayscale. BYTE* rgb; BYTE* rgbMask; rgb = (BYTE*)lpvBits; @@ -524,7 +528,7 @@ BOOL CreateGrayscaleAndMaskBitmaps(HBITMAP hSource, COLORREF transparent, rgbMask += 3; } - // vytvorim novou bitmapu nad grayscale datama + // Create a new bitmap from the grayscale data. hGrayscale = HANDLES(CreateDIBitmap(hDC, &bi.bmiHeader, (LONG)CBM_INIT, @@ -537,7 +541,7 @@ BOOL CreateGrayscaleAndMaskBitmaps(HBITMAP hSource, COLORREF transparent, goto exitus; } - // vytvorim novou bitmapu nad mask datama + // Create a new bitmap from the mask data. hMask = HANDLES(CreateDIBitmap(hDC, &bi.bmiHeader, (LONG)CBM_INIT, @@ -563,7 +567,7 @@ BOOL CreateGrayscaleAndMaskBitmaps(HBITMAP hSource, COLORREF transparent, return ret; } -// JRYFIXME - docasne pro prechod na SVG +// JRYFIXME - temporary during the transition to SVG BOOL CreateGrayscaleAndMaskBitmaps_tmp(HBITMAP hSource, COLORREF transparent, COLORREF bkColorForAlpha, HBITMAP& hGrayscale, HBITMAP& hMask) { @@ -575,11 +579,11 @@ BOOL CreateGrayscaleAndMaskBitmaps_tmp(HBITMAP hSource, COLORREF transparent, CO hMask = NULL; HDC hDC = HANDLES(GetDC(NULL)); - // vytahnu rozmery bitmapy + // Retrieve the bitmap dimensions. BITMAPINFO bi; memset(&bi, 0, sizeof(bi)); bi.bmiHeader.biSize = sizeof(bi.bmiHeader); - bi.bmiHeader.biBitCount = 0; // nechceme paletu + bi.bmiHeader.biBitCount = 0; // no palette is required if (!GetDIBits(hDC, hSource, @@ -598,9 +602,9 @@ BOOL CreateGrayscaleAndMaskBitmaps_tmp(HBITMAP hSource, COLORREF transparent, CO goto exitus; } - // pozadovana barevna hloubka je 24 bitu + // The requested color depth is 24 bits. bi.bmiHeader.biSizeImage = ((((bi.bmiHeader.biWidth * 24) + 31) & ~31) >> 3) * bi.bmiHeader.biHeight; - // naalokuju potrebny prostor + // Allocate the required space. lpvBits = malloc(bi.bmiHeader.biSizeImage); if (lpvBits == NULL) { @@ -618,7 +622,7 @@ BOOL CreateGrayscaleAndMaskBitmaps_tmp(HBITMAP hSource, COLORREF transparent, CO bi.bmiHeader.biBitCount = 24; bi.bmiHeader.biCompression = BI_RGB; - // vytahnu vlastni data + // Retrieve the pixel data. if (!GetDIBits(hDC, hSource, 0, bi.bmiHeader.biHeight, @@ -630,7 +634,7 @@ BOOL CreateGrayscaleAndMaskBitmaps_tmp(HBITMAP hSource, COLORREF transparent, CO goto exitus; } - // vytahnu vlastni data pro mask + // Retrieve the mask data. if (!GetDIBits(hDC, hSource, 0, bi.bmiHeader.biHeight, @@ -642,7 +646,7 @@ BOOL CreateGrayscaleAndMaskBitmaps_tmp(HBITMAP hSource, COLORREF transparent, CO goto exitus; } - // prevedu na grayscale + // Convert the image to grayscale. BYTE* rgb; BYTE* rgbMask; rgb = (BYTE*)lpvBits; @@ -667,7 +671,7 @@ BOOL CreateGrayscaleAndMaskBitmaps_tmp(HBITMAP hSource, COLORREF transparent, CO rgbMask += 3; } - // vytvorim novou bitmapu nad grayscale datama + // Create a new bitmap from the grayscale data. hGrayscale = HANDLES(CreateDIBitmap(hDC, &bi.bmiHeader, (LONG)CBM_INIT, @@ -680,7 +684,7 @@ BOOL CreateGrayscaleAndMaskBitmaps_tmp(HBITMAP hSource, COLORREF transparent, CO goto exitus; } - // vytvorim novou bitmapu nad mask datama + // Create a new bitmap from the mask data. hMask = HANDLES(CreateDIBitmap(hDC, &bi.bmiHeader, (LONG)CBM_INIT, @@ -709,7 +713,7 @@ BOOL CreateGrayscaleAndMaskBitmaps_tmp(HBITMAP hSource, COLORREF transparent, CO void RenderSVGImages(HDC hDC, int iconSize, COLORREF bkColor, const CSVGIcon* svgIcons, int svgIconsCount) { NSVGrasterizer* rast = nsvgCreateRasterizer(); - // JRYFIXME: docasne cteme ze souboru, prejit na spolecne uloziste s toolbars + // JRYFIXME: files are loaded directly for now; move them to storage shared with the toolbars for (int i = 0; i < svgIconsCount; i++) if (svgIcons[i].SVGName != NULL) RenderSVGImage(rast, hDC, svgIcons[i].ImageIndex * iconSize, 0, svgIcons[i].SVGName, iconSize, bkColor, TRUE); @@ -721,15 +725,15 @@ void RenderSVGImages(HDC hDC, int iconSize, COLORREF bkColor, const CSVGIcon* sv // // CreateToolbarBitmaps // -// Vytahne z resID bitmapu, nakopiruje ji do nove bitmapy, ktera je -// barevne kompatibilni s obrazovkou. Potom k teto bitmape pripoji -// ikonky z shell32.dll. Cti transparentni barvu. +// Loads a bitmap from resID and copies it into a new bitmap whose colors are +// compatible with the display. It then appends icons from shell32.dll and +// honors the transparent color. // bkColorForAlpha specifies the color that will show through under the transparent parts of icons (WinXP) // BOOL CreateToolbarBitmaps(HINSTANCE hInstance, int resID, COLORREF transparent, COLORREF bkColorForAlpha, HBITMAP& hMaskBitmap, HBITMAP& hGrayBitmap, HBITMAP& hColorBitmap, BOOL appendIcons, - const CSVGIcon* svgIcons, int svgIconsCount) + const CSVGIcon* svgIcons, int svgIconsCount, int iconSize) { CALL_STACK_MESSAGE5("CreateToolbarBitmaps(%p, %d, %x, %x, , , )", hInstance, resID, transparent, bkColorForAlpha); BOOL ret = FALSE; @@ -742,17 +746,18 @@ BOOL CreateToolbarBitmaps(HINSTANCE hInstance, int resID, COLORREF transparent, hGrayBitmap = NULL; hColorBitmap = NULL; - int iconSize = GetIconSizeForSystemDPI(ICONSIZE_16); // small icon size + // The caller chooses the target size so compact menus and configurable toolbars can share this renderer. int iconCount = 0; + int sourceIconCount = 0; - // Windows XP a novejsi pouzivaji transparentni ikony; protoze je pomoci masky - // zobrazime do teto docasne bitmapy a zajistime, aby pod pruhlednou casti byla - // sediva barva z toolbary a ne nase fialova maskovaci + // Windows XP and later use transparent icons. Render them into this temporary + // bitmap through a mask so the transparent area contains the toolbar's gray + // background instead of the purple masking color. HBITMAP hTmpBitmap = NULL; HDC hTmpMemDC = NULL; HBITMAP hOldTmpBitmap = NULL; - // nactu zdrojovou bitmapu + // Load the source bitmap. HBITMAP hSource; if (resID == IDB_TOOLBAR_256) // dirty hack, this should detect by resource type (RCDATA), or possibly by PNG signature hSource = LoadPNGBitmap(hInstance, MAKEINTRESOURCE(resID), 0); @@ -765,11 +770,11 @@ BOOL CreateToolbarBitmaps(HINSTANCE hInstance, int resID, COLORREF transparent, } hDC = HANDLES(GetDC(NULL)); - // vytahnu rozmery bitmapy + // Retrieve the bitmap dimensions. BITMAPINFO bi; memset(&bi, 0, sizeof(bi)); bi.bmiHeader.biSize = sizeof(bi.bmiHeader); - bi.bmiHeader.biBitCount = 0; // nechceme paletu + bi.bmiHeader.biBitCount = 0; // no palette is required if (!GetDIBits(hDC, hSource, 0, 0, @@ -783,21 +788,17 @@ BOOL CreateToolbarBitmaps(HINSTANCE hInstance, int resID, COLORREF transparent, int tbbe_BMPCOUNT; int i; - tbbe_BMPCOUNT = 0; - if (appendIcons) - { - for (i = 0; i < TBBE_TERMINATOR; i++) - if (ToolBarButtons[i].Shell32ResID != 0) - tbbe_BMPCOUNT++; - } + sourceIconCount = bi.bmiHeader.biWidth / 16; + // Reserve every stable command slot; SVGs now replace the old shell32-dependent tail. + iconCount = appendIcons ? IDX_TB_COUNT : sourceIconCount; + tbbe_BMPCOUNT = iconCount - sourceIconCount; - // pripravim novou bitmapu, do ktere se vejde hSource a ikonky z DLLka - // prodlouzim delku o ikonky z DLL - iconCount = bi.bmiHeader.biWidth / 16 + tbbe_BMPCOUNT; + // Prepare a new bitmap large enough for hSource and the DLL icons. + // Extend its width to accommodate the DLL icons. // hColorBitmap = HANDLES(CreateBitmap(width, height, bh.bV4Planes, bh.bV4BitCount, NULL)); // because CreateBitmap() is suitable only for creating B&W bitmaps (see MSDN) - //prechazime od sal 2.5b7 na rychlou CreateCompatibleBitmap() + // Salamander 2.5b7 and later use the faster CreateCompatibleBitmap(). hColorBitmap = HANDLES(CreateCompatibleBitmap(hDC, iconSize * iconCount, iconSize)); hTgtMemDC = HANDLES(CreateCompatibleDC(NULL)); @@ -805,7 +806,7 @@ BOOL CreateToolbarBitmaps(HINSTANCE hInstance, int resID, COLORREF transparent, hOldTgtBitmap = (HBITMAP)SelectObject(hTgtMemDC, hColorBitmap); hOldSrcBitmap = (HBITMAP)SelectObject(hSrcMemDC, hSource); - // pro prenaseni icon (vcetne transparentnich) + // Used to transfer icons, including transparent ones. hTmpBitmap = HANDLES(CreateBitmap(iconSize, iconSize, 1, 1, NULL)); hTmpMemDC = HANDLES(CreateCompatibleDC(NULL)); hOldTmpBitmap = (HBITMAP)SelectObject(hTmpMemDC, hTmpBitmap); @@ -819,7 +820,7 @@ BOOL CreateToolbarBitmaps(HINSTANCE hInstance, int resID, COLORREF transparent, goto exitus; } - // zahodime zdrojovou bitmapu + // Discard the source bitmap. SelectObject(hSrcMemDC, hOldSrcBitmap); hOldSrcBitmap = NULL; @@ -829,13 +830,13 @@ BOOL CreateToolbarBitmaps(HINSTANCE hInstance, int resID, COLORREF transparent, RenderSVGImages(hTgtMemDC, iconSize, bkColorForAlpha, svgIcons, svgIconsCount); } - // pouzijeme pri BitBlt hTmpMemDC->hTgtMemDC + // Used by BitBlt from hTmpMemDC to hTgtMemDC. //SetBkColor(hTgtMemDC, transparent); //SetTextColor(hTgtMemDC, bkColorForAlpha); if (appendIcons) { - // podojime shell32.dll + // Load the requested icons from shell32.dll. HICON hIcon; for (i = 0; i < TBBE_TERMINATOR; i++) { @@ -853,7 +854,7 @@ BOOL CreateToolbarBitmaps(HINSTANCE hInstance, int resID, COLORREF transparent, } else { - // Documents jsou od WinXP jinde + // The Documents icon moved in Windows XP. if (resID2 == 21) resID2 = 235; @@ -865,10 +866,10 @@ BOOL CreateToolbarBitmaps(HINSTANCE hInstance, int resID, COLORREF transparent, continue; } - // pripravime pozadi pro ikonky s alfa kanalem pod WinXP + // Prepare the background for alpha-channel icons on Windows XP. DrawIconEx(hTmpMemDC, 0, 0, hIcon, iconSize, iconSize, 0, 0, DI_MASK); - // pouzijeme pri BitBlt hTmpMemDC->hTgtMemDC + // Used by BitBlt from hTmpMemDC to hTgtMemDC. SetBkColor(hTgtMemDC, transparent); SetTextColor(hTgtMemDC, bkColorForAlpha); BitBlt(hTgtMemDC, iconSize * ToolBarButtons[i].ImageIndex, 0, iconSize, iconSize, @@ -885,9 +886,9 @@ BOOL CreateToolbarBitmaps(HINSTANCE hInstance, int resID, COLORREF transparent, else { /* - // --- sileny patch BEGIN - // John: pod W2K mi neslapalo pro ikonku 21 (Documents) DrawIconEx s parametrem DI_NORMAL - // z neznameho duvodu saturovalo pruhledny prostor a tim zmenilo barvu 'transparent' + // --- ugly patch BEGIN + // John: on Windows 2000, DrawIconEx with DI_NORMAL did not work for icon 21 (Documents). + // For an unknown reason it saturated the transparent area, changing the 'transparent' color. SetBkColor(hTgtMemDC, RGB(0, 0, 0)); SetTextColor(hTgtMemDC, RGB(255, 255, 255)); BitBlt(hTgtMemDC, ICON16_CX * ToolBarButtons[i].ImageIndex, 0, ICON16_CX, ICON16_CX, @@ -898,7 +899,7 @@ BOOL CreateToolbarBitmaps(HINSTANCE hInstance, int resID, COLORREF transparent, BitBlt(hTgtMemDC, ICON16_CX * ToolBarButtons[i].ImageIndex, 0, ICON16_CX, ICON16_CX, hTmpMemDC, 0, 0, SRCPAINT); - // --- sileny patch END + // --- ugly patch END */ HANDLES(DestroyIcon(hIcon)); } @@ -944,9 +945,9 @@ BOOL CreateToolbarBitmaps(HINSTANCE hInstance, int resID, COLORREF transparent, // // PrepareToolTipText // -// Searches buff for the first occurrence of character '\t'. If the variable is set -// stripHotKey, vlozi na jeho misto terminator a vrati se. Jinak na jeho -// misto vlozi mezeru, zbytek posunu o znak vpravo a ozavorkuje. +// Searches buff for the first '\t'. If stripHotKey is set, replaces it with a +// terminator and returns. Otherwise, replaces it with a space, shifts the +// remainder one character to the right, and encloses it in parentheses. // void PrepareToolTipText(char* buff, BOOL stripHotKey) @@ -992,7 +993,7 @@ BOOL CMainToolBar::FillTII(int tbbeIndex, TLBI_ITEM_INFO2* tii, BOOL fillName) } if (ToolBarButtons[tbbeIndex].ImageIndex == 0xFFFF) { - // stara polozka, ktera v teto verzi byla zrusena + // Legacy item removed in this version. return FALSE; } @@ -1128,8 +1129,8 @@ void CMainToolBar::OnGetToolTip(LPARAM lParam) CFilesWindow* activePanel = MainWindow != NULL ? MainWindow->GetActivePanel() : NULL; BOOL activePanelIsDisk = (activePanel != NULL && activePanel->Is(ptDisk)); if (EnablerPastePath && - (!activePanelIsDisk || !EnablerPasteFiles) && // PasteFiles je prioritni - !EnablerPasteFilesToArcOrFS) // PasteFilesToArcOrFS je prioritni + (!activePanelIsDisk || !EnablerPasteFiles) && // PasteFiles takes precedence + !EnablerPasteFilesToArcOrFS) // PasteFilesToArcOrFS takes precedence { char tail[50]; tail[0] = 0; @@ -1164,7 +1165,7 @@ BOOL CMainToolBar::OnEnumButton(LPARAM lParam) break; } if (tbbeIndex == TBBE_TERMINATOR) - return FALSE; // vsechna tlacitka uz byla natlacena + return FALSE; // all buttons have already been added FillTII(tbbeIndex, tii, TRUE); return TRUE; } @@ -1207,8 +1208,8 @@ void CMainToolBar::SetType(CMainToolBarType type) struct CBottomTBData { DWORD Index; - BYTE TextLen; // pocet zanaku v promenne 'Text' - char Text[BOTTOMTB_TEXT_MAX]; // text bez terminatoru + BYTE TextLen; // number of characters in 'Text' + char Text[BOTTOMTB_TEXT_MAX]; // text without a terminator }; CBottomTBData BottomTBData[btbsCount][12] = @@ -1325,14 +1326,14 @@ CBottomToolBar::CBottomToolBar(HWND hNotifyWindow, CObjectOrigin origin) { CALL_STACK_MESSAGE_NONE State = btbsCount; - Padding.ButtonIconText = 1; // pritahneme text k ikonce - Padding.IconLeft = 2; // prostor pred ikonou - Padding.TextRight = 2; // prostor za textem + Padding.ButtonIconText = 1; // move the text closer to the icon + Padding.IconLeft = 2; // space before the icon + Padding.TextRight = 2; // space after the text } -// naplni v poli BottomTBData promennou 'Text', kterou vycte z resourcu +// Fills the BottomTBData 'Text' field from a resource string. // 'state' specifies the row in the BottomTBData array and 'BottomTBData' denotes the string with texts -// texty pro jednotlive klavesy jsou oddeleny znakem ';' +// Text for individual keys is separated by semicolons. BOOL CBottomToolBar::InitDataResRow(CBottomTBStateEnum state, int textResID) { CALL_STACK_MESSAGE2("CBottomToolBar::InitDataResRow(, %d)", textResID); @@ -1501,7 +1502,7 @@ BOOL CBottomToolBar::SetState(CBottomTBStateEnum state) GetCursorPos(&p); if (WindowFromPoint(p) == HWindow) { - // zajistime obnovu pripadneho tooltipu + // Ensure that any existing tooltip is refreshed. ScreenToClient(HWindow, &p); SetCurrentToolTip(NULL, 0); PostMessage(HWindow, WM_MOUSEMOVE, 0, MAKELPARAM(p.x, p.y)); diff --git a/src/toolbar_core.cpp b/src/toolbar_core.cpp index 3df6b163..c34fff76 100644 --- a/src/toolbar_core.cpp +++ b/src/toolbar_core.cpp @@ -186,8 +186,9 @@ int CToolBar::GetNeededWidth() width = max(width, Padding.IconLeft + ImageWidth + Padding.IconRight); if (HasIcon) { - int iconSize = GetIconSizeForSystemDPI(ICONSIZE_16); - width = max(width, Padding.IconLeft + iconSize + Padding.IconRight); + // Direct HICON items follow the toolbar's configured image width when a list establishes it. + int iconWidth = ImageWidth > 0 ? ImageWidth : GetIconSizeForSystemDPI(ICONSIZE_16); + width = max(width, Padding.IconLeft + iconWidth + Padding.IconRight); } } Refresh(); @@ -246,10 +247,16 @@ int CToolBar::GetNeededHeight() height = 3 + FontHeight + 3; if (Style & TLB_STYLE_IMAGE) { + // Couple vertical breathing room to the same inset that surrounds the icon horizontally. + int iconVerticalPadding = (int)Padding.IconLeft + 1; if (HImageList != NULL) - height = max(height, 3 + ImageHeight + 3); + height = max(height, iconVerticalPadding + ImageHeight + iconVerticalPadding); if (HasIcon) - height = max(height, 3 + GetIconSizeForSystemDPI(ICONSIZE_16) + 3); + { + // Match HICON button height to the configured image-list cell without affecting list-less toolbars. + int iconHeight = ImageHeight > 0 ? ImageHeight : GetIconSizeForSystemDPI(ICONSIZE_16); + height = max(height, iconVerticalPadding + iconHeight + iconVerticalPadding); + } } height += 2 * Padding.ToolBarVertical; @@ -782,6 +789,24 @@ void CToolBar::SetPadding(const TOOLBAR_PADDING* padding) InvalidateRect(HWindow, NULL, FALSE); } +void CToolBar::ApplyConfiguredIconSpacing() +{ + CALL_STACK_MESSAGE1("CToolBar::ApplyConfiguredIconSpacing()"); + + // A one-quarter-icon inset preserves the Fluent proposal's density at 16, 24, and 32 pixels and scales with DPI. + int iconDimension = max(ImageWidth, ImageHeight); + if (iconDimension <= 0) + iconDimension = GetToolbarIconSizeForSystemDPI(); + int edgeSpacing = max(1, (iconDimension + 3) / 4); + + TOOLBAR_PADDING padding = Padding; + padding.ToolBarVertical = 0; + padding.IconLeft = (WORD)edgeSpacing; + padding.IconRight = (WORD)edgeSpacing; + padding.TextRight = (WORD)edgeSpacing; + SetPadding(&padding); +} + /* int CToolBar::SetHotItem(int index) diff --git a/src/toolbar_dnd.cpp b/src/toolbar_dnd.cpp index 44e95b0b..eb2010eb 100644 --- a/src/toolbar_dnd.cpp +++ b/src/toolbar_dnd.cpp @@ -11,6 +11,13 @@ // CTBCustomizeDialog // +// Combo order is kept in one table so display strings cannot drift from persisted pixel values. +static const int CustomizeToolbarIconSizes[] = { + TOOLBAR_ICON_SIZE_SMALL, + TOOLBAR_ICON_SIZE_MEDIUM, + TOOLBAR_ICON_SIZE_LARGE, +}; + CTBCustomizeDialog::CTBCustomizeDialog(CToolBar* toolBar) : CCommonDialog(HLanguage, IDD_CUSTOMIZETOOLBAR, IDD_CUSTOMIZETOOLBAR, toolBar->HWindow), AllItems(50, 20) { @@ -19,6 +26,9 @@ CTBCustomizeDialog::CTBCustomizeDialog(CToolBar* toolBar) DragNotify = 0; DragMode = tbcdDragNone; DragIndex = -1; + SelectedIconSize = IsValidToolbarIconSize(Configuration.ToolbarIconSize) ? + Configuration.ToolbarIconSize : + TOOLBAR_ICON_SIZE_SMALL; } CTBCustomizeDialog::~CTBCustomizeDialog() @@ -398,24 +408,40 @@ CTBCustomizeDialog::DialogProc(UINT uMsg, WPARAM wParam, LPARAM lParam) MakeDragList(HCurrentLB); DragNotify = RegisterWindowMessage(DRAGLISTMSGSTRING); + // Populate localized choices while retaining logical pixel values in the fixed table above. + HWND hIconSize = GetDlgItem(HWindow, IDC_CTB_ICON_SIZE); + int sizeStringIDs[] = { + IDS_TOOLBAR_ICON_SIZE_SMALL, + IDS_TOOLBAR_ICON_SIZE_MEDIUM, + IDS_TOOLBAR_ICON_SIZE_LARGE, + }; + int selectedSizeIndex = 0; + for (int i = 0; i < _countof(CustomizeToolbarIconSizes); i++) + { + SendMessage(hIconSize, CB_ADDSTRING, 0, (LPARAM)LoadStr(sizeStringIDs[i])); + if (CustomizeToolbarIconSizes[i] == SelectedIconSize) + selectedSizeIndex = i; + } + SendMessage(hIconSize, CB_SETCURSEL, selectedSizeIndex, 0); + // vytahneme tlacitka EnumButtons(); FillLists(); EnableControls(); - // napocitame vysku radku - int iconSize = GetIconSizeForSystemDPI(ICONSIZE_16); - int minHeight = max(iconSize, ToolBar->ImageHeight); + // Give Customize Toolbar previews the same proportional breathing room as live buttons. + int iconSize = max(GetIconSizeForSystemDPI(ICONSIZE_16), ToolBar->ImageHeight); + int edgeSpacing = max(1, (iconSize + 3) / 4); + int minHeight = iconSize + 2 * edgeSpacing; HFONT hFont = (HFONT)SendMessage(HAvailableLB, WM_GETFONT, 0, 0); HDC hDC = HANDLES(GetDC(HWindow)); SelectObject(hDC, hFont); TEXTMETRIC tm; HFONT hOldFont = (HFONT)SelectObject(hDC, hFont); GetTextMetrics(hDC, &tm); - minHeight = max(tm.tmHeight, minHeight); + minHeight = max(tm.tmHeight + 2 * edgeSpacing, minHeight); SelectObject(hDC, hOldFont); HANDLES(ReleaseDC(HWindow, hDC)); - minHeight += 2; // nastavime oba listboxy SendMessage(HAvailableLB, LB_SETITEMHEIGHT, 0, minHeight); SendMessage(HCurrentLB, LB_SETITEMHEIGHT, 0, minHeight); @@ -424,6 +450,14 @@ CTBCustomizeDialog::DialogProc(UINT uMsg, WPARAM wParam, LPARAM lParam) case WM_COMMAND: { + if (LOWORD(wParam) == IDC_CTB_ICON_SIZE && HIWORD(wParam) == CBN_SELCHANGE) + { + int selectedIndex = (int)SendMessage((HWND)lParam, CB_GETCURSEL, 0, 0); + // Ignore an invalid transient selection instead of corrupting the persisted preference. + if (selectedIndex >= 0 && selectedIndex < _countof(CustomizeToolbarIconSizes)) + SelectedIconSize = CustomizeToolbarIconSizes[selectedIndex]; + return 0; + } if (HIWORD(wParam) == LBN_SELCHANGE) { EnableControls(); @@ -480,15 +514,17 @@ CTBCustomizeDialog::DialogProc(UINT uMsg, WPARAM wParam, LPARAM lParam) else text = AllItems[index].Name; - int iconSize = GetIconSizeForSystemDPI(ICONSIZE_16); - int imageWidth = max(iconSize, ToolBar->ImageWidth); - imageWidth += 5; + // Direct HICON items are scaled to the same dimensions as image-list-backed commands. + int iconSize = ToolBar->ImageHeight > 0 ? ToolBar->ImageHeight : GetToolbarIconSizeForSystemDPI(); + int edgeSpacing = max(1, (iconSize + 3) / 4); + int imageWidth = max(iconSize, ToolBar->ImageWidth) + 2 * edgeSpacing; + int iconY = r.top + max(0, (r.bottom - r.top - iconSize) / 2); if (!separator) { if ((AllItems[index].Mask & TLBI_MASK_ICON) && AllItems[index].HIcon != NULL) - DrawIconEx(hDC, r.left + 2, r.top + 1, AllItems[index].HIcon, iconSize, iconSize, 0, NULL, DI_NORMAL); + DrawIconEx(hDC, r.left + edgeSpacing, iconY, AllItems[index].HIcon, iconSize, iconSize, 0, NULL, DI_NORMAL); if ((AllItems[index].Mask & TLBI_MASK_IMAGEINDEX) && AllItems[index].ImageIndex != -1) - ImageList_Draw(ToolBar->HImageList, AllItems[index].ImageIndex, hDC, r.left + 2, r.top + 1, ILD_TRANSPARENT); + ImageList_Draw(ToolBar->HImageList, AllItems[index].ImageIndex, hDC, r.left + edgeSpacing, iconY, ILD_TRANSPARENT); } r.left += imageWidth; @@ -538,10 +574,21 @@ void CToolBar::Customize() CTBCustomizeDialog dialog(this); dialog.Execute(); + // Commit the staged size only after the dialog stops drawing from the current image list. + BOOL iconSizeChanged = Configuration.ToolbarIconSize != dialog.GetSelectedIconSize(); + if (iconSizeChanged) + Configuration.ToolbarIconSize = dialog.GetSelectedIconSize(); + // vratime se k puvodnimu nastaveni tlacitek Customizing = FALSE; InvalidateRect(HWindow, NULL, FALSE); // posleme notifikaci o ukonceni konfigurace SendMessage(HNotifyWindow, WM_USER_TBENDADJUST, (WPARAM)HWindow, 0); + + if (iconSizeChanged) + { + // Rebuild full-color SVG image lists and reattach them to every open toolbar. + ColorsChanged(TRUE, FALSE, FALSE); + } } diff --git a/src/toolbar_rendering.cpp b/src/toolbar_rendering.cpp index 899e5294..bd4d3081 100644 --- a/src/toolbar_rendering.cpp +++ b/src/toolbar_rendering.cpp @@ -14,7 +14,6 @@ #define TB_SP_WIDTH 6 // sirka separatoru -#define TB_ICON_TB 3 // pocet bodu nad a pod ikonou, vcetne ramecku #define TB_TEXT_TB 3 void CToolBar::SetFont() @@ -121,6 +120,9 @@ BOOL CToolBar::HitTest(int xPos, int yPos, int& index, BOOL& dropDown) } BOOL vertical = (Style & TLB_STYLE_VERTICAL) != 0; Refresh(); + // Keep the dropdown hit target synchronized with its size-dependent visual inset. + int separatedDropPadding = max(2, (int)Padding.IconLeft / 2); + int separatedDropWidth = SVGArrowDropDown.GetWidth() + 2 * separatedDropPadding; if (xPos >= 0 && xPos <= Width && yPos >= 0 && yPos < Height) { int i; @@ -136,7 +138,7 @@ BOOL CToolBar::HitTest(int xPos, int yPos, int& index, BOOL& dropDown) xPos >= xOffset && xPos < xOffset + item->Width) { if ((item->Style & TLBI_STYLE_SEPARATEDROPDOWN) && - xPos >= item->Offset + item->Width - SVGArrowDropDown.GetWidth() - 4) + xPos >= item->Offset + item->Width - separatedDropWidth) dropDown = TRUE; else dropDown = FALSE; @@ -153,7 +155,7 @@ BOOL CToolBar::HitTest(int xPos, int yPos, int& index, BOOL& dropDown) yPos >= yOffset && yPos < yOffset + item->Height) { if ((item->Style & TLBI_STYLE_SEPARATEDROPDOWN) && - xPos >= item->Offset + item->Width - SVGArrowDropDown.GetWidth() - 4) + xPos >= item->Offset + item->Width - separatedDropWidth) dropDown = TRUE; else dropDown = FALSE; @@ -256,6 +258,10 @@ BOOL CToolBar::Refresh() if (!DirtyItems || HWindow == NULL) return FALSE; BOOL vertical = (Style & TLB_STYLE_VERTICAL) != 0; + // Derive every surrounding gap from the icon inset so all three configured sizes share one visual rhythm. + int separatorWidth = max(TB_SP_WIDTH, 2 * (int)Padding.IconLeft); + int iconVerticalPadding = (int)Padding.IconLeft + 1; + int separatedDropPadding = max(2, (int)Padding.IconLeft / 2); int offset = 0; int maxWidth = 0; int maxHeight = 0; @@ -269,9 +275,9 @@ BOOL CToolBar::Refresh() if (item->Style & TLBI_STYLE_SEPARATOR) { if (vertical) - item->Height = TB_SP_WIDTH; + item->Height = separatorWidth; else - item->Width = TB_SP_WIDTH; + item->Width = separatorWidth; } else { @@ -316,12 +322,13 @@ BOOL CToolBar::Refresh() width += Padding.IconLeft; item->IconX = width; - int iconSize = GetIconSizeForSystemDPI(ICONSIZE_16); - int imgW = item->HIcon != NULL ? iconSize : ImageWidth; - int imgH = item->HIcon != NULL ? iconSize : ImageHeight; + // Direct icons use the same established cell dimensions as image-index-backed buttons. + int defaultIconSize = GetIconSizeForSystemDPI(ICONSIZE_16); + int imgW = item->HIcon != NULL ? (ImageWidth > 0 ? ImageWidth : defaultIconSize) : ImageWidth; + int imgH = item->HIcon != NULL ? (ImageHeight > 0 ? ImageHeight : defaultIconSize) : ImageHeight; width += imgW + Padding.IconRight; - height = max(height, TB_ICON_TB + imgH + TB_ICON_TB); + height = max(height, iconVerticalPadding + imgH + iconVerticalPadding); } if (textPresent) @@ -354,11 +361,11 @@ BOOL CToolBar::Refresh() { if (!(item->Style & TLBI_STYLE_FIXEDWIDTH)) { - item->OutterX = width + 2; - width += 2 + SVGArrowDropDown.GetWidth() + 2; + item->OutterX = width + separatedDropPadding; + width += separatedDropPadding + SVGArrowDropDown.GetWidth() + separatedDropPadding; } else - item->OutterX = width - (2 + SVGArrowDropDown.GetWidth() + 2); // ukousneme s sirky polozky + item->OutterX = width - (separatedDropPadding + SVGArrowDropDown.GetWidth() + separatedDropPadding); // ukousneme s sirky polozky } if (!(item->Style & TLBI_STYLE_FIXEDWIDTH)) @@ -492,9 +499,10 @@ void CToolBar::DrawItem(HDC hDC, int index) iconPresent = TRUE; if (item->HIcon != NULL) { - int iconSize = GetIconSizeForSystemDPI(ICONSIZE_16); - imgW = iconSize; - imgH = iconSize; + // Direct icons scale with this toolbar's image list, falling back only for list-less controls. + int defaultIconSize = GetIconSizeForSystemDPI(ICONSIZE_16); + imgW = ImageWidth > 0 ? ImageWidth : defaultIconSize; + imgH = ImageHeight > 0 ? ImageHeight : defaultIconSize; } else { @@ -512,6 +520,10 @@ void CToolBar::DrawItem(HDC hDC, int index) if (!vertical && (item->Style & TLBI_STYLE_SEPARATEDROPDOWN)) outterDropPresent = TRUE; + // Use the same scalable dropdown width for layout, hit testing, and pressed-state borders. + int separatedDropPadding = max(2, (int)Padding.IconLeft / 2); + int separatedDropWidth = SVGArrowDropDown.GetWidth() + 2 * separatedDropPadding; + RECT r; r.left = 0; r.top = centerOffset; @@ -529,7 +541,7 @@ void CToolBar::DrawItem(HDC hDC, int index) if (!grayed && ((HotIndex == index || item->State & TLBI_STATE_CHECKED) || (item->State & TLBI_STATE_PRESSED))) { if (outterDropPresent) - r.right -= 2 + SVGArrowDropDown.GetWidth() + 2; + r.right -= separatedDropWidth; bodyDown = !Customizing && ((item->State & TLBI_STATE_PRESSED) || (item->State & TLBI_STATE_CHECKED)); dropDown = !Customizing && (item->State & TLBI_STATE_DROPDOWNPRESSED); @@ -574,9 +586,8 @@ void CToolBar::DrawItem(HDC hDC, int index) int offset = bodyDown ? 1 : 0; int x = item->IconX + offset; int y = centerOffset + (item->Height - imgH) / 2 + offset; - int iconSize = GetIconSizeForSystemDPI(ICONSIZE_16); if (item->HIcon != NULL) - DrawIconEx(CacheBitmap->HMemDC, x, y, item->HIcon, iconSize, iconSize, 0, NULL, DI_NORMAL); + DrawIconEx(CacheBitmap->HMemDC, x, y, item->HIcon, imgW, imgH, 0, NULL, DI_NORMAL); else { HIMAGELIST hImageList = HImageList; @@ -584,7 +595,7 @@ void CToolBar::DrawItem(HDC hDC, int index) x, y, checked ? ILD_TRANSPARENT : ILD_NORMAL); } if (item->HOverlay != NULL) - DrawIconEx(CacheBitmap->HMemDC, x, y, item->HOverlay, iconSize, iconSize, 0, NULL, DI_NORMAL); + DrawIconEx(CacheBitmap->HMemDC, x, y, item->HOverlay, imgW, imgH, 0, NULL, DI_NORMAL); } else { @@ -592,9 +603,8 @@ void CToolBar::DrawItem(HDC hDC, int index) int offset = bodyDown ? 1 : 0; int x = item->IconX + offset; int y = centerOffset + (item->Height - imgH) / 2 + offset; - int iconSize = GetIconSizeForSystemDPI(ICONSIZE_16); if (item->HIcon != NULL) - DrawIconEx(CacheBitmap->HMemDC, x, y, item->HIcon, iconSize, iconSize, 0, NULL, DI_NORMAL); + DrawIconEx(CacheBitmap->HMemDC, x, y, item->HIcon, imgW, imgH, 0, NULL, DI_NORMAL); else { HIMAGELIST hImageList = HHotImageList ? HHotImageList : HImageList; @@ -609,7 +619,7 @@ void CToolBar::DrawItem(HDC hDC, int index) // } } if (item->HOverlay != NULL) - DrawIconEx(CacheBitmap->HMemDC, x, y, item->HOverlay, iconSize, iconSize, 0, NULL, DI_NORMAL); + DrawIconEx(CacheBitmap->HMemDC, x, y, item->HOverlay, imgW, imgH, 0, NULL, DI_NORMAL); } } diff --git a/src/translator/translator.cpp b/src/translator/translator.cpp index 4eb2451c..c0609dfb 100644 --- a/src/translator/translator.cpp +++ b/src/translator/translator.cpp @@ -562,9 +562,8 @@ WinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPSTR cmdLine, int cmd TRACE_I("Begin."); HInstance = hInstance; - // configure localized messages for the ALLOCHAN module (used to report out-of-memory issues - // to the user, offer Retry, and fall back to Cancel if termination is required) - SetAllocHandlerMessage(NULL, FRAMEWINDOW_NAME, NULL, NULL); + // Allocation failures now propagate immediately; a modal retry here could + // deadlock while the failed allocation still holds a subsystem lock. SetWinLibStrings("Invalid number!", FRAMEWINDOW_NAME); diff --git a/src/tserver/tserver.cpp b/src/tserver/tserver.cpp index 480b1619..413eca9c 100644 --- a/src/tserver/tserver.cpp +++ b/src/tserver/tserver.cpp @@ -940,8 +940,8 @@ wWinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPWSTR /*cmdLine*/, i //SetTraceProcessNameW(MAINWINDOW_NAME); //SetTraceThreadNameW(L"Main"); - // configure localized messages for the ALLOCHAN module (handles out-of-memory reporting to the user + Retry button + Cancel when everything fails to terminate the software) - SetAllocHandlerMessage(NULL, MAINWINDOW_NAME, NULL, NULL); + // The shared allocator reports failure without a modal retry, which keeps + // this logging process from blocking while a failing thread owns a lock. HWND hPrevWindow = FindWindow(WC_MAINWINDOW, MAINWINDOW_NAME); if (hPrevWindow != NULL) diff --git a/src/vcxproj/salamand.sln b/src/vcxproj/salamand.sln index ab887a04..294e8eac 100644 --- a/src/vcxproj/salamand.sln +++ b/src/vcxproj/salamand.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.9.34310.174 +# Visual Studio Version 18 +VisualStudioVersion = 18.9.12023.133 insiders MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "salamand", "salamand.vcxproj", "{FA82995C-2137-4A0B-96ED-404B08EFC0DD}" EndProject @@ -152,6 +152,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "salextx64", "shellext\salex EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "salopen", "salopen\salopen.vcxproj", "{0606C52C-9DA4-468B-B7A6-5C53A360EA91}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "salbroker", "salbroker\salbroker.vcxproj", "{D9FB52B1-6B50-4F11-9880-9BAA0E099B16}" +EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "salspawn", "salspawn\salspawn.vcxproj", "{C610C287-BC1E-4FAE-9BF4-A1CB68986746}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zip2sfx", "..\plugins\zip\vcxproj\zip2sfx\zip2sfx.vcxproj", "{53D8DB2B-C867-4B87-AB47-68E0ACEB691B}" @@ -948,6 +950,18 @@ Global {0606C52C-9DA4-468B-B7A6-5C53A360EA91}.Utils (Release)|Win32.Build.0 = Release|Win32 {0606C52C-9DA4-468B-B7A6-5C53A360EA91}.Utils (Release)|x64.ActiveCfg = Release|x64 {0606C52C-9DA4-468B-B7A6-5C53A360EA91}.Utils (Release)|x64.Build.0 = Release|x64 + {D9FB52B1-6B50-4F11-9880-9BAA0E099B16}.Debug|Win32.ActiveCfg = Debug|Win32 + {D9FB52B1-6B50-4F11-9880-9BAA0E099B16}.Debug|Win32.Build.0 = Debug|Win32 + {D9FB52B1-6B50-4F11-9880-9BAA0E099B16}.Debug|x64.ActiveCfg = Debug|x64 + {D9FB52B1-6B50-4F11-9880-9BAA0E099B16}.Debug|x64.Build.0 = Debug|x64 + {D9FB52B1-6B50-4F11-9880-9BAA0E099B16}.Release|Win32.ActiveCfg = Release|Win32 + {D9FB52B1-6B50-4F11-9880-9BAA0E099B16}.Release|Win32.Build.0 = Release|Win32 + {D9FB52B1-6B50-4F11-9880-9BAA0E099B16}.Release|x64.ActiveCfg = Release|x64 + {D9FB52B1-6B50-4F11-9880-9BAA0E099B16}.Release|x64.Build.0 = Release|x64 + {D9FB52B1-6B50-4F11-9880-9BAA0E099B16}.Utils (Release)|Win32.ActiveCfg = Release|Win32 + {D9FB52B1-6B50-4F11-9880-9BAA0E099B16}.Utils (Release)|Win32.Build.0 = Release|Win32 + {D9FB52B1-6B50-4F11-9880-9BAA0E099B16}.Utils (Release)|x64.ActiveCfg = Release|x64 + {D9FB52B1-6B50-4F11-9880-9BAA0E099B16}.Utils (Release)|x64.Build.0 = Release|x64 {C610C287-BC1E-4FAE-9BF4-A1CB68986746}.Debug|Win32.ActiveCfg = Debug|Win32 {C610C287-BC1E-4FAE-9BF4-A1CB68986746}.Debug|Win32.Build.0 = Debug|Win32 {C610C287-BC1E-4FAE-9BF4-A1CB68986746}.Debug|x64.ActiveCfg = Debug|x64 diff --git a/src/vcxproj/salamand.vcxproj b/src/vcxproj/salamand.vcxproj index 5cadb6e0..37cd5d5a 100644 --- a/src/vcxproj/salamand.vcxproj +++ b/src/vcxproj/salamand.vcxproj @@ -150,6 +150,8 @@ + + 4244;%(DisableSpecificWarnings) BZ_NO_STDIO;%(PreprocessorDefinitions) @@ -239,6 +241,8 @@ + + @@ -373,6 +377,10 @@ + + + + @@ -601,6 +609,14 @@ + + + + + + + + @@ -671,6 +687,16 @@ + + + + + + + + + + @@ -693,6 +719,16 @@ + + + + + + + + + + @@ -715,6 +751,8 @@ + + @@ -881,6 +919,26 @@ + + + + + + + + + + + + + + + + + + + + @@ -971,12 +1029,19 @@ + + {d9fb52b1-6b50-4f11-9880-9baa0e099b16} + false + {7c7059ba-1fce-4c36-b0b3-d095779cf89f} false + + advapi32.lib;%(AdditionalDependencies) + robocopy ..\res\toolbars "$(OutDir)toolbars" /E /XO /NP /NJS /NJH if %errorlevel% leq 1 exit 0 else exit %errorlevel% @@ -987,4 +1052,4 @@ if %errorlevel% leq 1 exit 0 else exit %errorlevel% - \ No newline at end of file + diff --git a/src/vcxproj/salamand.vcxproj.filters b/src/vcxproj/salamand.vcxproj.filters index b38c1ef4..494eebcc 100644 --- a/src/vcxproj/salamand.vcxproj.filters +++ b/src/vcxproj/salamand.vcxproj.filters @@ -42,6 +42,9 @@ cpp + + cpp + cpp @@ -378,6 +381,12 @@ cpp + + cpp + + + cpp + cpp @@ -411,6 +420,9 @@ common + + common + common @@ -432,12 +444,18 @@ zlib + + zlib + zlib zlib + + zlib + zlib @@ -509,6 +527,9 @@ h + + h + h @@ -692,6 +713,9 @@ h + + h + h @@ -764,6 +788,18 @@ common + + h + + + common + + + common + + + common + @@ -891,4 +927,4 @@ rc - \ No newline at end of file + diff --git a/src/vcxproj/salbroker/salbroker.vcxproj b/src/vcxproj/salbroker/salbroker.vcxproj new file mode 100644 index 00000000..9da372c6 --- /dev/null +++ b/src/vcxproj/salbroker/salbroker.vcxproj @@ -0,0 +1,48 @@ + + + + DebugWin32 + Debugx64 + ReleaseWin32 + Releasex64 + + + 16.0 + {D9FB52B1-6B50-4F11-9880-9BAA0E099B16} + salbroker + 10.0 + + + Applicationtruev145 + Applicationtruev145 + Applicationfalsev145 + Applicationfalsev145 + + + x86 + x64 + $(OPENSAL_BUILD_DIR)salamander\$(Configuration)_$(ShortPlatform)\ + $(MSBuildThisFileDirectory)..\salamander\$(Configuration)_$(ShortPlatform)\ + $(OutDir)Intermediate\$(ProjectName)\ + + + + ..\..;%(AdditionalIncludeDirectories) + WIN32;_WINDOWS;UNICODE;_UNICODE;WINVER=0x0601;_WIN32_WINNT=0x0601;%(PreprocessorDefinitions) + Level3 + MultiThreaded + + + Windows + shell32.lib;ole32.lib;%(AdditionalDependencies) + true + + + _DEBUG;%(PreprocessorDefinitions) + MaxSpeedNDEBUG;%(PreprocessorDefinitions)truetrue + + + + + + diff --git a/src/vcxproj/salmon/salmon_base.props b/src/vcxproj/salmon/salmon_base.props index 3e2a88f3..da4e2161 100644 --- a/src/vcxproj/salmon/salmon_base.props +++ b/src/vcxproj/salmon/salmon_base.props @@ -22,7 +22,7 @@ false - comctl32.lib;Ws2_32.lib;%(AdditionalDependencies) + comctl32.lib;winhttp.lib;%(AdditionalDependencies) ..\..\plugins\shared\libs\$(ShortPlatform);%(AdditionalLibraryDirectories) true Windows diff --git a/src/vcxproj/sqlite/sqlite_base.props b/src/vcxproj/sqlite/sqlite_base.props index cb04623c..8d8465cf 100644 --- a/src/vcxproj/sqlite/sqlite_base.props +++ b/src/vcxproj/sqlite/sqlite_base.props @@ -13,7 +13,8 @@ /wd4456 %(AdditionalOptions) ..\..\common\dep\sqlite;..\..\common;..\..\common\dep;%(AdditionalIncludeDirectories) - _MT;WIN32;_WINDOWS;_USRDLL;WINVER=0x0601;_WIN32_WINNT=0x0601;_WIN32_IE=0x0800;SQLITE_API=extern __declspec(dllexport);SQLITE_WIN32_GETVERSIONEX=0;%(PreprocessorDefinitions) + + _MT;WIN32;_WINDOWS;_USRDLL;WINVER=0x0601;_WIN32_WINNT=0x0601;_WIN32_IE=0x0800;SQLITE_API=extern __declspec(dllexport);SQLITE_WIN32_GETVERSIONEX=0;SQLITE_DQS=0;SQLITE_ENABLE_API_ARMOR;SQLITE_DEFAULT_SYNCHRONOUS=2;SQLITE_DEFAULT_WAL_SYNCHRONOUS=2;SQLITE_DEFAULT_WAL_AUTOCHECKPOINT=1000;%(PreprocessorDefinitions) MultiThreadedDLL false Level3 diff --git a/src/viewer2.cpp b/src/viewer2.cpp index 82455cee..98d56c54 100644 --- a/src/viewer2.cpp +++ b/src/viewer2.cpp @@ -204,7 +204,8 @@ BOOL OpenViewer(const char* name, CViewType mode, int left, int top, int width, { // SetThreadPriority(loop, THREAD_PRIORITY_HIGHEST); } - AddAuxThread(loop); // register the thread among existing viewers (terminate it on exit) + // The viewer loop closes before teardown; its handle remains tracked for a safe join. + AddAuxThread(loop, FALSE, "viewer message loop"); WaitForSingleObject(ViewerContinue, INFINITE); // wait until the thread finishes its startup if (!data.Success) goto ERROR_TV_CREATE; diff --git a/src/worker.cpp b/src/worker.cpp index aecbc229..7b4be681 100644 --- a/src/worker.cpp +++ b/src/worker.cpp @@ -7,6 +7,8 @@ #include "cfgdlg.h" #include "worker.h" #include "execlog.h" +#include "operation_journal.h" +#include "release_diagnostics.h" #include #include @@ -17,6 +19,19 @@ NTFSCONTROLFILE DynNtFsControlFile = NULL; COperationsQueue OperationsQueue; // queue of disk operations +namespace +{ +volatile LONG NextOperationCorrelationSequence = 0; + +void CreateOperationCorrelationId(char* destination, int destinationLen) +{ + // Process, monotonic tick, and an atomic dispatch ordinal stay unique when commands share a tick. + _snprintf_s(destination, destinationLen, _TRUNCATE, "%08lX-%08lX-%08lX", + GetCurrentProcessId(), GetTickCount(), + (DWORD)InterlockedIncrement(&NextOperationCorrelationSequence)); +} +} + WCHAR* SafeConvertAllocUtf8ToWide(const char* src, int len) { __try { @@ -112,6 +127,14 @@ int GetOptimalSyncCopyBufferSize(COperations* script, DWORD opFlags) COperations::COperations(int base, int delta, char* waitInQueueSubject, char* waitInQueueFrom, char* waitInQueueTo) : TDirectArray(base, delta), Sizes(1, 400) { + CancellationEvent = HANDLES(CreateEvent(NULL, TRUE, FALSE, NULL)); + if (CancellationEvent == NULL) + TRACE_E("Unable to create file-operation cancellation event."); + OperationState = opsPlanned; + Journal = NULL; + CurrentItemSequence = -1; + CurrentItemAttempt = 0; + CreateOperationCorrelationId(CorrelationId, _countof(CorrelationId)); TotalSize = CQuadWord(0, 0); CompressedSize = CQuadWord(0, 0); OccupiedSpace = CQuadWord(0, 0); @@ -124,6 +147,8 @@ COperations::COperations(int base, int delta, char* waitInQueueSubject, char* wa SameRootButDiffVolume = FALSE; TargetPathSupADS = FALSE; // TargetPathSupEFS = FALSE; + TargetMetadataFileSystem = mtfsUnknown; + PlannedMetadataLosses = mmlNone; IsCopyOrMoveOperation = FALSE; OverwriteOlder = FALSE; CopySecurity = FALSE; @@ -167,6 +192,193 @@ COperations::COperations(int base, int delta, char* waitInQueueSubject, char* wa LastFileStartTime = GetTickCount(); } +COperations::~COperations() +{ + delete Journal; + if (CancellationEvent != NULL) + HANDLES(CloseHandle(CancellationEvent)); + HANDLES(DeleteCriticalSection(&StatusCS)); +} + +BOOL COperations::BeginJournal() +{ + if (Journal != NULL) + return FALSE; + Journal = new COperationJournal; + if (Journal == NULL || !Journal->Begin(*this)) + { + delete Journal; + Journal = NULL; + TRACE_E("Unable to create durable file-operation journal."); + return FALSE; + } + return TRUE; +} + +int COperations::BeginItemAttempt(int itemIndex) +{ + CurrentItemSequence = itemIndex; + CurrentItemAttempt = 1; + return CurrentItemAttempt; +} + +int COperations::RecordItemRetry() +{ + if (CurrentItemSequence < 0) + return 0; + ++CurrentItemAttempt; + // A synchronous dialog response is part of the worker's item attempt, not a new operation. + if (Journal != NULL) + Journal->RecordRetry(CurrentItemAttempt); + ExecLogFileOperationRetry(CorrelationId, CurrentItemSequence, CurrentItemAttempt); + return CurrentItemAttempt; +} + +BOOL COperations::JournalBeginItem(int itemIndex, const COperation* operation, int attempt) +{ + return Journal == NULL || Journal->BeginItem(itemIndex, operation, attempt); +} + +BOOL COperations::JournalSetTemporaryPath(const char* temporaryPath) +{ + return Journal == NULL || Journal->SetTemporaryPath(temporaryPath); +} + +BOOL COperations::JournalMarkTemporaryReady() +{ + return Journal == NULL || Journal->MarkTemporaryReady(); +} + +void COperations::JournalCompleteItem(BOOL succeeded) +{ + if (Journal != NULL) + Journal->CompleteItem(succeeded); +} + +void COperations::FinishJournal(BOOL failed, BOOL cancelled) +{ + if (Journal != NULL) + Journal->Finish(failed, cancelled); +} + +static void AssertOperationTransition(BOOL valid, EOperationState from, EOperationState to) +{ +#ifdef _DEBUG + if (!valid) + { + TRACE_E("Invalid file-operation state transition: " << from << " -> " << to); + DebugBreak(); + } +#else + UNREFERENCED_PARAMETER(valid); + UNREFERENCED_PARAMETER(from); + UNREFERENCED_PARAMETER(to); +#endif +} + +EOperationState COperations::GetOperationState() const +{ + return (EOperationState)InterlockedCompareExchange(const_cast(&OperationState), opsPlanned, opsPlanned); +} + +BOOL COperations::IsCancellationRequested() const +{ + return CancellationEvent != NULL && WaitForSingleObject(CancellationEvent, 0) == WAIT_OBJECT_0 || + GetOperationState() == opsCancelRequested; +} + +BOOL COperations::Start() +{ + LONG state = InterlockedCompareExchange(&OperationState, opsRunning, opsPlanned); + // Retain the lifecycle edge in release reports without capturing operation paths. + if (state == opsPlanned) + RecordReleaseDiagnosticOperationTransition(state, opsRunning); + AssertOperationTransition(state == opsPlanned, (EOperationState)state, opsRunning); + return state == opsPlanned; +} + +BOOL COperations::RequestCancellation() +{ + for (;;) + { + LONG state = InterlockedCompareExchange(&OperationState, opsPlanned, opsPlanned); + if (state == opsCancelRequested || state == opsStopping) + { + if (CancellationEvent != NULL) + SetEvent(CancellationEvent); + return FALSE; // already requested: deliberately idempotent + } + if (state == opsCompleted || state == opsFailed) + return FALSE; + if (state != opsPlanned && state != opsRunning) + { + AssertOperationTransition(FALSE, (EOperationState)state, opsCancelRequested); + return FALSE; + } + if (InterlockedCompareExchange(&OperationState, opsCancelRequested, state) == state) + { + // Cancellation ordering is often the missing clue in a field hang. + RecordReleaseDiagnosticOperationTransition(state, opsCancelRequested); + if (CancellationEvent != NULL) + SetEvent(CancellationEvent); + return TRUE; + } + } +} + +BOOL COperations::BeginStopping() +{ + for (;;) + { + LONG state = InterlockedCompareExchange(&OperationState, opsPlanned, opsPlanned); + if (state == opsStopping) + return TRUE; + if (state != opsRunning && state != opsCancelRequested) + { + AssertOperationTransition(FALSE, (EOperationState)state, opsStopping); + return FALSE; + } + if (InterlockedCompareExchange(&OperationState, opsStopping, state) == state) + { + // Preserve only the state transition, never the affected file names. + RecordReleaseDiagnosticOperationTransition(state, opsStopping); + return TRUE; + } + } +} + +BOOL COperations::Complete(BOOL failed) +{ + EOperationState target = failed ? opsFailed : opsCompleted; + LONG state = InterlockedCompareExchange(&OperationState, target, opsStopping); + // Completion is retained so reports show whether a worker reached its terminal state. + if (state == opsStopping) + RecordReleaseDiagnosticOperationTransition(state, target); + AssertOperationTransition(state == opsStopping, (EOperationState)state, target); + return state == opsStopping; +} + +BOOL COperations::Fail() +{ + for (;;) + { + LONG state = InterlockedCompareExchange(&OperationState, opsPlanned, opsPlanned); + if (state == opsFailed) + return TRUE; + if (state == opsCompleted) + { + AssertOperationTransition(FALSE, (EOperationState)state, opsFailed); + return FALSE; + } + if (InterlockedCompareExchange(&OperationState, opsFailed, state) == state) + { + // Failure transitions are safe context for an approved diagnostic report. + RecordReleaseDiagnosticOperationTransition(state, opsFailed); + return TRUE; + } + } +} + void COperations::SetTFS(const CQuadWord& TFS) { if (ShowStatus) diff --git a/src/worker.h b/src/worker.h index 4e9d173f..83fe8755 100644 --- a/src/worker.h +++ b/src/worker.h @@ -58,6 +58,19 @@ struct CConvertData // data used by ocConvert class COperations; struct CProgressDlgArrItem; +class COperationJournal; + +#include "operation_plan.h" + +enum EOperationState +{ + opsPlanned, + opsRunning, + opsCancelRequested, + opsStopping, + opsCompleted, + opsFailed +}; struct CStartProgressDialogData { @@ -269,22 +282,6 @@ class CProgressSpeedMeter void BytesReceived(DWORD count, DWORD time, DWORD maxPacketSize); }; -enum COperationCode -{ - ocCopyFile, - ocMoveFile, - ocDeleteFile, - ocCreateDir, - ocMoveDir, - ocDeleteDir, - ocDeleteDirLink, - ocChangeAttrs, // WARNING: requested attributes are stored in TargetName (applies to every type; treat it as a DWORD) - ocCountSize, - ocConvert, - ocLabelForSkipOfCreateDir, // label to jump to when the script skips on ocCreateDirXXX; WARNING: SourceName and TargetName store the LO- and HI-DWORD of the total file sizes (including ADS) contained in the skipped directory; WARNING: Attr stores the ocCreateDirXXX index in the COperations array for that directory - ocCopyDirTime, // Move/Copy: when filterCriteria->PreserveDirTime==TRUE copy the directory timestamps; WARNING: lastWrite is stored in SourceName and Attr (applies to every type; just two DWORDs) -}; - #define OPFL_OVERWROLDERALRTESTED 0x00000001 // the "overwrite older, skip other existing" test has already been performed #define OPFL_AS_ENCRYPTED 0x00000002 // the target file/directory should have the Encrypted attribute set #define OPFL_COPY_ADS 0x00000004 // copy the file's/directory's ADS as well @@ -294,6 +291,73 @@ enum COperationCode #define OPFL_TGTPATH_IS_FAST 0x00000040 // the target path is a disk, USB disk, flash drive, flash-card reader, CD, DVD, or RAM disk (not a network or floppy) #define OPFL_IGNORE_INVALID_NAME 0x00000080 // skip the name validity test (for directories: unchanged name = do not flag as invalid) +// The preservation contract is intentionally kept close to the operation +// model. A cross-volume move is a copy followed by a destructive step, so +// the worker must retain an auditable account of any metadata it could not +// preserve until that step has been explicitly approved. +enum EMetadataPreservation +{ + mpRequired, + mpBestEffort, + mpUnsupported +}; + +enum EMetadataTargetFileSystem +{ + mtfsUnknown, + mtfsNtfs, + mtfsRefs, + mtfsFat, + mtfsSmb +}; + +enum EMetadataOperation +{ + moCopy, + moSameVolumeMove, + moCrossVolumeMove +}; + +enum EMetadataLoss +{ + mmlNone = 0, + mmlLastWriteTime = 0x00000001, + mmlCreationAndAccessTimes = 0x00000002, + mmlAttributes = 0x00000004, + mmlSecurity = 0x00000008, + mmlAlternateDataStreams = 0x00000010, + mmlCompressionAndEncryption = 0x00000020 +}; + +struct CMetadataPreservationContract +{ + EMetadataPreservation Content; + EMetadataPreservation LastWriteTime; + EMetadataPreservation CreationAndAccessTimes; + EMetadataPreservation Attributes; + EMetadataPreservation Security; + EMetadataPreservation AlternateDataStreams; + EMetadataPreservation CompressionAndEncryption; +}; + +struct CMetadataLossRecord +{ + DWORD LossMask; + DWORD AcknowledgedLossMask; + EMetadataTargetFileSystem TargetFileSystem; + char FirstSourceName[MAX_PATH]; + char FirstTargetName[MAX_PATH]; + + void Clear() + { + LossMask = mmlNone; + AcknowledgedLossMask = mmlNone; + TargetFileSystem = mtfsUnknown; + FirstSourceName[0] = 0; + FirstTargetName[0] = 0; + } +}; + struct COperation { COperationCode Opcode; @@ -303,8 +367,28 @@ struct COperation *TargetName; DWORD Attr; DWORD OpFlags; // combination of OPFL_xxx, see above + // These values are captured when the worker starts this item, before any + // confirmation dialog can delay its destructive phase. They are never + // derived from a path string: the helper records the opened object's + // volume serial, file ID, and final handle-resolved path fingerprint. + struct CFileIdentity + { + DWORD State; // 0 = not captured, 1 = absent, 2 = present + DWORD VolumeSerialNumber; + DWORD FileIndexHigh; + DWORD FileIndexLow; + unsigned __int64 FinalPathHash; + } SourceIdentity, TargetIdentity; }; +// Handle-based identity helpers implemented in file_identity.cpp. The +// capture/recheck pairing protects the decision made for a script item from +// path replacement while the progress dialog or copy transfer is running. +BOOL CaptureOperationFileIdentities(COperation* operation, DWORD* error); +BOOL VerifyFileIdentity(const char* path, const COperation::CFileIdentity& expected, DWORD* error); +BOOL VerifyFileHandleIdentity(HANDLE handle, const COperation::CFileIdentity& expected, DWORD* error); +BOOL DeleteFileWithVerifiedIdentity(const char* path, const COperation::CFileIdentity& expected, DWORD* error); + class COperations : public TDirectArray { public: @@ -334,7 +418,9 @@ class COperations : public TDirectArray BOOL CanUseRecycleBin; // can we use the Recycle Bin? (only local fixed drives) BOOL SameRootButDiffVolume; // TRUE if this is a Move between paths with the same root but different volumes (at least one path contains a junction point) BOOL TargetPathSupADS; // TRUE if the copy/move target supports ADS (delete the file's ADS (or the whole files) before overwriting) - // BOOL TargetPathSupEFS; // TRUE if the copy/move target supports EFS (or less generally: it is NTFS rather than FAT) + // BOOL TargetPathSupEFS; // TRUE if the copy/move target supports EFS (or less generally: it is NTFS rather than FAT) + EMetadataTargetFileSystem TargetMetadataFileSystem; // filesystem used to select the explicit preservation contract + DWORD PlannedMetadataLosses; // known losses accepted while building the copy/move script // for Copy/Move operations BOOL IsCopyOrMoveOperation; // TRUE = this is a Copy/Move operation (add it to the queue of disk Copy/Move operations) @@ -353,6 +439,8 @@ class COperations : public TDirectArray BOOL SkipAllCountSizeErrors; // should all subsequent count-size errors be skipped? + char CorrelationId[OPERATION_CORRELATION_ID_LENGTH]; + char WorkPath1[MAX_PATH]; // when non-empty string first path processed (used for change notifications) BOOL WorkPath1InclSubDirs; // TRUE/FALSE = with/without subdirectories (first path) char WorkPath2[MAX_PATH]; // when non-empty string second path processed (used for change notifications) @@ -363,6 +451,15 @@ class COperations : public TDirectArray char* WaitInQueueTo; // text for the "waiting in queue" state: bottom line (To) private: + // Cancellation is intentionally owned by the operation rather than by the + // progress dialog. The event makes a request visible to wait-based code; + // OperationState makes the complete lifecycle observable and atomic. + HANDLE CancellationEvent; + volatile LONG OperationState; + COperationJournal* Journal; + int CurrentItemSequence; + int CurrentItemAttempt; + // for the status line in the progress dialog (Copy and Move only) CRITICAL_SECTION StatusCS; // critical section protecting TransferSpeedMeter, ProgressSpeedMeter, and CTransferSpeedMeter TransferSpeedMeter; // meter for data transfers (Read/WriteFile) @@ -387,7 +484,27 @@ class COperations : public TDirectArray public: COperations(int base, int delta, char* waitInQueueSubject, char* waitInQueueFrom, char* waitInQueueTo); - ~COperations() { HANDLES(DeleteCriticalSection(&StatusCS)); } + ~COperations(); + + BOOL Start(); + BOOL RequestCancellation(); + BOOL BeginStopping(); + BOOL Complete(BOOL failed); + BOOL Fail(); + BOOL IsCancellationRequested() const; + EOperationState GetOperationState() const; + HANDLE GetCancellationEvent() const { return CancellationEvent; } + + BOOL BeginJournal(); + int BeginItemAttempt(int itemIndex); + int RecordItemRetry(); + BOOL JournalBeginItem(int itemIndex, const COperation* operation, int attempt); + BOOL JournalSetTemporaryPath(const char* temporaryPath); + BOOL JournalMarkTemporaryReady(); + void JournalCompleteItem(BOOL succeeded); + void FinishJournal(BOOL failed, BOOL cancelled); + const char* GetCorrelationId() const { return CorrelationId; } + int GetCurrentItemAttempt() const { return CurrentItemAttempt; } void SetWorkPath1(const char* path, BOOL inclSubDirs) { @@ -428,6 +545,28 @@ class COperations : public TDirectArray void GetSpeedLimit(BOOL* useSpeedLimit, DWORD* speedLimit); }; +// Existing copy/delete helpers use the historical '*CancelWorker' spelling. +// Keep that source compatibility while routing every read and request through +// COperations; no shared BOOL storage is exposed across threads. +class COperationCancellation +{ +private: + COperations* Operations; + +public: + COperationCancellation() : Operations(NULL) {} + void Bind(COperations* operations) { Operations = operations; } + COperationCancellation& operator*() { return *this; } + const COperationCancellation& operator*() const { return *this; } + operator BOOL() const { return Operations != NULL && Operations->IsCancellationRequested(); } + COperationCancellation& operator=(BOOL requested) + { + if (requested && Operations != NULL) + Operations->RequestCancellation(); + return *this; + } +}; + class COperationsQueue // queue of disk Copy/Move operations { protected: @@ -478,7 +617,7 @@ extern COperationsQueue OperationsQueue; // queue of disk Copy/Move operations HANDLE StartWorker(COperations* script, HWND hDlg, CChangeAttrsData* attrsData, CConvertData* convertData, HANDLE wContinue, HANDLE workerNotSuspended, - BOOL* cancelWorker, int* operationProgress, int* summaryProgress); + int* operationProgress, int* summaryProgress); void FreeScript(COperations* script); @@ -612,6 +751,8 @@ BOOL CheckFileOrDirADS(const char* fileName, BOOL isDir, CQuadWord* adsSize, wch struct CWorkerData { COperations* Script; + // Preserve the dispatch context independently of the caller's startup data. + char CorrelationId[OPERATION_CORRELATION_ID_LENGTH]; HWND HProgressDlg; void* Buffer; BOOL BufferIsAllocated; @@ -620,15 +761,31 @@ struct CWorkerData HANDLE WContinue; HANDLE WorkerNotSuspended; - BOOL* CancelWorker; int* OperationProgress; int* SummaryProgress; }; +// This result is transferred to CProgressDialog through +// WM_USER_PROGRDLG_WORKERCOMPLETE. It deliberately contains only data the UI +// needs after the worker has released the operation script, so delivery does +// not keep worker-owned state alive or require an acknowledgement event. +struct CWorkerCompletion +{ + EOperationState State; + BOOL CancellationRequested; + char CorrelationId[OPERATION_CORRELATION_ID_LENGTH]; + + CWorkerCompletion(EOperationState state, BOOL cancellationRequested, const char* correlationId) + : State(state), CancellationRequested(cancellationRequested) + { + lstrcpyn(CorrelationId, correlationId, _countof(CorrelationId)); + } +}; + struct CProgressDlgData { HANDLE WorkerNotSuspended; - BOOL* CancelWorker; + COperationCancellation CancelWorker; int* OperationProgress; int* SummaryProgress; @@ -671,6 +828,9 @@ struct CProgressDlgData BOOL IgnoreAllCopyPermErr; BOOL IgnoreAllCopyDirTimeErr; + CMetadataLossRecord MetadataLosses; + BOOL KeepSourceAfterMetadataLoss; + int CnfrmFileOver; int CnfrmDirOver; int CnfrmSHFileOver; @@ -689,6 +849,18 @@ struct CProgressDlgData } }; +// Metadata preservation helpers are implemented in async_copy.cpp. They are +// shared by the worker's file and directory source-deletion paths. +EMetadataTargetFileSystem GetMetadataTargetFileSystem(const char* targetPath); +CMetadataPreservationContract GetMetadataPreservationContract(EMetadataOperation operation, + EMetadataTargetFileSystem targetFileSystem); +void RecordMetadataLoss(CProgressDlgData& dlgData, DWORD lossMask, + const char* sourceName, const char* targetName); +void RecordPlannedMetadataLosses(CProgressDlgData& dlgData, const COperations* script, + const char* sourceName, const char* targetName); +BOOL ConfirmMetadataLossesBeforeSourceDeletion(HWND hProgressDlg, CProgressDlgData& dlgData, + const char* sourceName, const char* targetName); + // Async copy parameter block (defined in async_copy.cpp, used in operations_core.cpp) struct CAsyncCopyParams { @@ -727,7 +899,7 @@ BOOL DoMoveFile(COperation* op, HWND hProgressDlg, void* buffer, CProgressDlgData& dlgData, BOOL copyADS, BOOL copyAsEncrypted, BOOL* setDirTimeAfterMove, CAsyncCopyParams*& asyncPar, BOOL ignInvalidName); -BOOL DoDeleteFile(HWND hProgressDlg, char* name, const CQuadWord& size, COperations* script, +BOOL DoDeleteFile(HWND hProgressDlg, COperation* operation, const CQuadWord& size, COperations* script, CQuadWord& totalDone, DWORD attr, CProgressDlgData& dlgData); BOOL DoCreateDir(HWND hProgressDlg, char* name, DWORD attr, DWORD clearReadonlyMask, CProgressDlgData& dlgData, @@ -735,5 +907,5 @@ BOOL DoCreateDir(HWND hProgressDlg, char* name, DWORD attr, const char* sourceDir, BOOL adsCopy, COperations* script, void* buffer, BOOL& skip, BOOL& alreadyExisted, BOOL createAsEncrypted, BOOL ignInvalidName); -BOOL DoDeleteDir(HWND hProgressDlg, char* name, const CQuadWord& size, COperations* script, +BOOL DoDeleteDir(HWND hProgressDlg, COperation* operation, const CQuadWord& size, COperations* script, CQuadWord& totalDone, DWORD attr, BOOL dontUseRecycleBin, CProgressDlgData& dlgData); diff --git a/testing.md b/testing.md new file mode 100644 index 00000000..a5a1e831 --- /dev/null +++ b/testing.md @@ -0,0 +1,329 @@ +# Automated testing + +Run all commands from the repository root. The test suite has four layers: + +- PowerShell source-contract and native compatibility probes. +- NUnit source-contract and local TLS integration tests. +- FlaUI/UIA3 tests that drive the native Windows application. +- Optional fault-injection, filesystem-topology, and cross-volume characterization lanes. + +Visual Studio 2026 and its developer-command environment are the authoritative native build environment. Most scripts support Windows PowerShell 5.1. The SQLite recovery probe is the exception: use 64-bit PowerShell 7.4 or newer (`pwsh.exe`) for an x64 DLL. + +## Quick start + +### Complete local runner + +Run every automated test whose prerequisites are available on the current machine: + +```powershell +powershell -NoProfile -ExecutionPolicy Bypass ` + -File .\runtests.ps1 +``` + +`runtests.ps1` collects all PowerShell/native probes, both x64 and x86 compatibility variants, the complete NUnit project, and the optional Application Verifier lane. It runs every collected check even after a failure and prints passed, failed, and explicitly skipped checks. UI fixtures remain protected by their isolated-profile requirements. + +The runner uses the CI pull-request base for changed-line ratchets or accepts `-BaseCommit` explicitly; it does not guess from a potentially stale local tracking branch. It discovers an existing Debug or Release x64 SQLite DLL when possible. Supply prerequisites explicitly or require a fully provisioned run with: + +```powershell +.\runtests.ps1 ` + -BaseCommit origin/main ` + -SqliteDll .\src\vcxproj\sqlite\salamander\Debug_x64\utils\sqlite.dll ` + -FailOnSkipped +``` + +Run individual checks with: + +```powershell +powershell -NoProfile -ExecutionPolicy Bypass -File .\tools\verify-operation-completion-protocol.ps1 +powershell -NoProfile -ExecutionPolicy Bypass -File .\tools\verify-durable-copy-commit.ps1 +powershell -NoProfile -ExecutionPolicy Bypass -File .\tools\test-release-input-pinning.ps1 +powershell -NoProfile -ExecutionPolicy Bypass -File .\tools\test-zlib-compatibility.ps1 +powershell -NoProfile -ExecutionPolicy Bypass -File .\tools\test-bzip2-compatibility.ps1 +``` + +The process-scoped execution-policy bypass does not change the machine-wide policy. + +### NUnit contracts and integration tests + +The NUnit project targets .NET 8: + +```powershell +dotnet restore .\tests\FileManager.UiTests\FileManager.UiTests.csproj +dotnet test .\tests\FileManager.UiTests\FileManager.UiTests.csproj ` + --no-restore ` + --logger "console;verbosity=minimal" +``` + +The project declares `true`, so no MSBuild command-line override is needed for discovery. When the normal .NET or NuGet directories are unavailable, redirect generated state before restoring: + +```powershell +$env:DOTNET_CLI_HOME = Join-Path $PWD '.dotnet-cli' +$env:NUGET_PACKAGES = Join-Path $PWD '.nuget-packages' +$env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = '1' +$env:DOTNET_NOLOGO = '1' + +dotnet restore .\tests\FileManager.UiTests\FileManager.UiTests.csproj +dotnet test .\tests\FileManager.UiTests\FileManager.UiTests.csproj ` + --no-restore ` + --logger "console;verbosity=minimal" +``` + +These directories contain generated package and CLI state and must not be committed. + +### Native UI tests + +UI tests intentionally skip unless they run in an interactive disposable Windows profile with a built executable: + +```powershell +$env:FILEMANAGER_UI_ISOLATED = '1' +$env:FILEMANAGER_UI_EXE = 'C:\path\to\salamand.exe' + +dotnet test .\tests\FileManager.UiTests\FileManager.UiTests.csproj ` + --filter 'TestCategory=UI' +``` + +Never set `FILEMANAGER_UI_ISOLATED=1` in a normal developer profile. These tests persist configuration, launch an editor, perform native file operations, and expect the Windows profile to be disposable. + +Optional UI settings: + +| Variable | Enables | +| --- | --- | +| `FILEMANAGER_UI_ARGUMENTS` | Extra application arguments, such as a test-only `-c` configuration file. | +| `FILEMANAGER_UI_FTP_ORGANIZE_COMMAND` | The runtime command ID for FTP **Organize Bookmarks** persistence cases. | +| `FILEMANAGER_UI_CONFIG_FAULT_INJECTION=1` | Exhaustive configuration-write crash recovery. | +| `FILEMANAGER_UI_CROSS_VOLUME_ROOT` | Cross-volume move tests using only a GUID child below the supplied dedicated root. | +| `FILEMANAGER_UI_ADS_UNSUPPORTED_TARGET_ROOT` | Metadata-loss behavior on a different FAT/FAT32/exFAT-like volume. | +| `FILEMANAGER_UI_RECYCLE_BIN=1` | Recycle-bin deletion in an isolated profile using the default recycle-bin setting. | + +Run a specialized lane by category after setting its required environment: + +```powershell +dotnet test .\tests\FileManager.UiTests\FileManager.UiTests.csproj --filter 'TestCategory=FaultInjection' +dotnet test .\tests\FileManager.UiTests\FileManager.UiTests.csproj --filter 'TestCategory=CrossVolume' +dotnet test .\tests\FileManager.UiTests\FileManager.UiTests.csproj --filter 'TestCategory=AlternateDataStreams' +dotnet test .\tests\FileManager.UiTests\FileManager.UiTests.csproj --filter 'TestCategory=Recovery' +dotnet test .\tests\FileManager.UiTests\FileManager.UiTests.csproj --filter 'TestCategory=ReparsePoints' +dotnet test .\tests\FileManager.UiTests\FileManager.UiTests.csproj --filter 'TestCategory=RecycleBin' +dotnet test .\tests\FileManager.UiTests\FileManager.UiTests.csproj --filter 'TestCategory=LockStress' +``` + +See [`tests/FileManager.UiTests/README.md`](tests/FileManager.UiTests/README.md) for additional UI-lane details. + +### cmark-gfm hardening probe + +The cmark-gfm probe invokes `cl.exe`, so start it through Visual Studio 2026: + +```powershell +$vsDevCmd = 'C:\Program Files\Microsoft Visual Studio\18\Community\Common7\Tools\VsDevCmd.bat' +$command = 'call "' + $vsDevCmd + '" -arch=x64 -host_arch=x64 && ' + + 'powershell -NoProfile -ExecutionPolicy Bypass ' + + '-File "' + (Join-Path $PWD 'tools\test-cmark-gfm-hardening.ps1') + '"' +& $env:ComSpec /d /s /c $command +``` + +Adjust the edition or installation path when necessary. + +### SQLite recovery probe + +Build the Debug x64 SQLite target with Visual Studio 2026, then use 64-bit PowerShell 7.4 or newer: + +```powershell +pwsh -NoProfile -ExecutionPolicy Bypass ` + -File .\tools\test-sqlite-recovery.ps1 ` + -SqliteDll .\src\vcxproj\sqlite\salamander\Debug_x64\utils\sqlite.dll +``` + +### Pull-request ratchets + +The changed-line ratchets require the pull request base commit: + +```powershell +.\tools\verify-no-new-terminatethread.ps1 -BaseCommit origin/main +.\tools\verify-no-new-raw-thread-creation.ps1 -BaseCommit origin/main +.\tools\verify-no-new-max-path-buffers.ps1 -BaseCommit origin/main +.\tools\verify-no-new-unsafe-string-calls.ps1 -BaseCommit origin/main +``` + +The toolbar icon coverage check has no parameters: + +```powershell +.\tools\verify-fluent-icon-coverage.ps1 +``` + +## Complete automated test catalog + +### PowerShell and native probes + +| Test | Description | +| --- | --- | +| `verify-operation-completion-protocol.ps1` | Verifies that cancellation requests do not destroy worker-owned state, workers publish owned completion records, and the UI resumes and closes operations through the asynchronous completion protocol. | +| `verify-durable-copy-commit.ps1` | Checks write-through creation, flush/close/verification ordering, transactional replacement, retry paths, and deterministic filesystem fault boundaries for durable copy commits. | +| `test-release-input-pinning.ps1` | Checks that release inputs have a complete lock record, all workflow actions use reviewed immutable commits, and publication consumes the gated immutable installer artifact. | +| `test-zlib-compatibility.ps1` | Compiles the checked-in zlib sources and replays the retained legacy, checksum-error, invalid-deflate, and truncated-stream vectors. Supports `-Architecture x64` and `x86`. | +| `test-bzip2-compatibility.ps1` | Compiles the checked-in bzip2 sources and checks golden and legacy streams, truncation rejection, and the malformed fuzz-vector corpus. Supports `-Architecture x64` and `x86`. | +| `test-cmark-gfm-hardening.ps1` | Compiles the production Markdown renderer, compares retained snapshots, checks safe link and raw-HTML behavior, exercises extension combinations, and enforces input, nesting, node, and output limits. | +| `test-sqlite-recovery.ps1` | Exercises the supplied SQLite DLL with WAL/FULL settings, interrupted transactions, committed-row recovery, integrity checks, and controlled b-tree corruption detection. | +| `verify-no-new-terminatethread.ps1` | Rejects newly added native `TerminateThread` calls while leaving legacy debt to dedicated migrations. | +| `verify-no-new-raw-thread-creation.ps1` | Rejects new first-party `CreateThread` or `_beginthreadex` calls outside the reviewed thread-owner boundaries. | +| `verify-no-new-max-path-buffers.ps1` | Rejects newly added native fixed arrays whose bounds contain `MAX_PATH` unless a documented exemption applies. | +| `verify-no-new-unsafe-string-calls.ps1` | Rejects newly added unchecked `strcpy`, `strcat`, `sprintf`, `lstrcpy`, `lstrcat`, and `wsprintf` calls. | +| `verify-fluent-icon-coverage.ps1` | Ensures every mapped toolbar command has an SVG, core rows no longer use shell fallbacks, SVG colors stay in the approved palette, and the application icon remains separate. | + +The zlib and bzip2 `.hex` files under `tests/` and the cmark-gfm Markdown/HTML snapshots are retained inputs consumed by these probes rather than independent runners. + +### NUnit source-contract and integration tests + +These tests do not drive the FileManager UI and can run in a normal developer profile. + +#### `ApplicationVersionContractTests` + +- `Product_major_is_6_and_build_components_remain_automatic` — keeps native, manifest, configuration, shell-extension, installer, workflow, and README version declarations synchronized at product major 6 while preserving automatic build numbering. + +#### `SChannelTlsIntegrationTests` + +- `LocalTlsServerNegotiatesTheRequiredProtocol(Tls12)` — proves a local SChannel client and server negotiate TLS 1.2. +- `LocalTlsServerNegotiatesTheRequiredProtocol(Tls13)` — proves a local SChannel client and server negotiate TLS 1.3 when supported by the host. +- `SelfSignedServerCertificateIsRejectedWithoutAnExplicitUserException` — proves default validation rejects an untrusted self-signed server certificate. + +#### `ToolbarIconSizeContractTests` + +- `Customize_toolbar_exposes_persisted_small_medium_and_large_icon_sizes` — checks resource, configuration, validation, persistence, and live-change wiring for 16, 24, and 32 pixel toolbar icons. +- `Toolbar_image_lists_scale_independently_from_menu_images_and_app_icon` — keeps toolbar scaling separate from menu image lists and the application icon. +- `Fluent_toolbar_spacing_scales_with_each_configured_icon_size` — verifies padding, separators, customization layout, startup, and live refresh use the configured icon geometry. +- `Go_to_hot_path_uses_a_conservative_saved_location_glyph` — protects the reviewed saved-folder visual and palette for the hot-path command. +- `High_fidelity_reference_catalog_contains_every_runtime_toolbar_icon` — requires the 91-icon SVG reference mirror and the expected 3840×3440 catalog image. + +#### `NativeSafetyRegressionTests` + +- `Unchecked_string_calls_are_ratchet_gated_and_external_boundaries_report_capacity_and_encoding_failures` — keeps unsafe-string ratchets and checked boundary-error reporting in place. +- `New_fixed_max_path_buffers_are_rejected_by_the_changed_lines_ci_ratchet` — keeps the changed-line `MAX_PATH` buffer gate and exemption register wired into CI. +- `Bundled_zlib_is_current_has_retained_compatibility_vectors_and_runs_in_ci` — pins the zlib vendor record, compatibility vectors, probe, and CI hook. +- `Bundled_bzip2_uses_the_verified_release_and_replays_archive_parser_regressions` — pins the bzip2 vendor record, golden/fuzz vectors, probe, and CI hook. +- `Trust_boundary_text_uses_bounded_owned_storage_and_explicit_capacity_failures` — checks bounded storage and explicit failures at native text trust boundaries. +- `External_size_fields_use_checked_arithmetic_before_allocation_or_io` — requires checked arithmetic before external sizes reach allocation or I/O. +- `Transactional_copy_results_preserve_phase_error_paths_retryability_and_partial_effects_for_legacy_dialogs` — preserves structured copy-phase results and their legacy dialog adapters. +- `File_operation_failures_capture_the_primary_error_before_cleanup_and_offer_copyable_context` — protects primary error capture and copyable diagnostic context. +- `Kernel_handle_ownership_is_scoped_and_preserves_legacy_close_failures` — verifies scoped kernel handles without hiding legacy close errors. +- `Scoped_native_resources_protect_file_operations_and_plugin_boundaries` — checks RAII-style native resource protection at operation and plug-in boundaries. +- `Lock_ordering_has_rank_assertions_timeout_diagnostics_and_a_nightly_verifier_lane` — protects ranked locks, timeout diagnostics, and verifier workflow coverage. +- `Thread_owners_keep_worker_lifetime_stop_completion_naming_com_and_exception_policy_together` — keeps worker lifecycle responsibilities in the common thread-owner abstraction. +- `Crash_report_compression_uses_the_restricted_loader_owned_worker_and_shutdown_contract` — verifies crash compression uses restricted loading, owned work, and safe shutdown. +- `Shared_plugin_thread_queue_uses_owned_cooperative_shutdown_without_forced_termination` — protects cooperative shutdown of the shared plug-in queue. +- `Seven_zip_task_dispatch_owns_worker_completion_cancellation_and_progress_subclass_lifetime` — checks 7-Zip worker, cancellation, completion, and progress-window ownership. +- `Shutdown_deadlines_report_named_phases_and_preserve_shared_state_until_safe_join` — protects named shutdown deadlines and state lifetime through joins. +- `Check_path_workers_use_signaled_work_cancellation_and_deadline_waits` — requires event-driven path workers with cancellation and bounded waits. +- `Monotonic_64_bit_timers_cross_the_32_bit_boundary_and_reject_backward_samples` — checks timer wraparound and backward-sample handling. +- `Win32_path_boundaries_use_owned_wide_paths_and_extended_length_syntax` — protects UTF-16 ownership and extended-length Win32 path handling. +- `Destructive_operations_keep_the_handle_identity_guard` — requires identity revalidation before destructive filesystem actions. +- `Reparse_point_policy_never_traverses_or_hydrates_unselected_targets` — protects the no-traversal/no-hydration reparse-point policy. +- `Copy_engine_uses_unambiguous_64_bit_file_size_and_seek_wrappers` — keeps copy sizes and offsets on explicit 64-bit wrappers. +- `Plugin_readers_preserve_full_file_sizes_and_reject_only_their_explicit_caps` — parameterized checks keep plug-in readers from narrowing file sizes outside documented caps. +- `Split_combine_stages_and_verifies_output_before_publishing_it` — requires staged, verified, write-through split/combine publication. +- `File_operation_planning_uses_an_immutable_plan_and_narrow_filesystem_adapter` — protects immutable plans and the injectable filesystem seam. +- `File_operation_correlation_ids_cross_plan_worker_ui_journal_and_log_boundaries` — requires one correlation ID across operation layers. +- `Central_retry_policy_bounds_transient_read_retries_and_blocks_destructive_commits` — keeps retry attempts bounded and destructive commits gated. +- `Transactional_copy_and_move_expose_each_durable_phase_to_a_deterministic_fault_adapter` — protects fault injection at every copy/move durability phase. +- `Native_destructive_operation_characterization_suite_retains_the_required_scenarios` — prevents removal of required copy, move, rename, delete, recovery, ADS, Find, View, and Edit UI scenarios. +- `Crash_report_uploads_use_certificate_validated_https_with_explicit_consent` — requires explicit consent and validated HTTPS for report uploads. +- `Dynamic_library_loads_use_restricted_search_paths` — checks process and call-site DLL search restrictions. +- `Thumbnail_and_archive_metadata_use_the_restartable_parser_broker` — protects brokered parsing for thumbnail and archive metadata. +- `Plugin_entry_scope_and_callback_state_restore_host_state_after_an_unwinding_entry_point` — verifies plug-in entry unwinding restores callback and host state. +- `Delete_manager_callback_registration_invalidates_before_window_teardown_and_rejects_stale_generations` — protects callback-window invalidation and generation checks. +- `Icon_work_pool_bounds_memory_and_prioritizes_visible_current_generation_work` — checks icon queue bounds, prioritization, and stale-generation rejection. +- `Directory_listing_uses_bounded_checkpoints_and_a_retained_metadata_budget` — requires bounded listing checkpoints and metadata budgets. +- `Allocation_emergency_is_noninteractive_and_defers_recovery_to_the_ui_thread` — protects noninteractive allocation failure handling and UI-thread recovery. +- `Background_producers_bound_non_durable_work_and_report_backpressure` — checks bounded queues and backpressure diagnostics. +- `Plugin_callbacks_are_contained_and_the_failing_plugin_is_deferred_for_unload` — protects callback exception containment and deferred unload. +- `Configuration_saves_stage_validate_and_atomically_select_a_generation` — requires staged, validated, atomic configuration generations. +- `Configuration_profiles_are_schema_versioned_migrated_and_validated_before_loading` — protects profile schema migration and validation. +- `Metadata_preservation_contract_records_losses_and_gates_move_source_deletion` — requires metadata-loss records and user gating before source deletion. +- `Security_descriptor_copy_uses_the_privilege_aware_preservation_matrix` — checks privilege-aware security descriptor preservation. +- `Release_diagnostic_ring_is_bounded_sanitized_and_reported_only_through_the_existing_consent_flow` — protects bounded sanitized diagnostics and consent-based reporting. +- `Network_operations_have_phase_deadlines_cancellation_and_failure_classification` — requires phase-specific network deadlines, cancellation, and error classification. +- `Ftp_downloads_stage_identity_validate_resume_and_publish_only_after_a_durable_commit` — protects staged, resumable, identity-checked FTP publication. +- `Ftp_certificate_exceptions_are_endpoint_bound_expiring_and_pinned` — checks endpoint-bound, expiring certificate exceptions and pinning. +- `Bundled_7zip_uses_26_02_and_preserves_upgrade_compatibility_contract` — pins the 7-Zip version and upgrade compatibility boundary. +- `Bundled_sqlite_uses_a_verified_current_amalgamation_and_exercises_the_owned_database_recovery_contract` — pins SQLite provenance, build options, ownership policy, recovery probe, and CI hook. +- `Bundled_cmark_gfm_uses_the_verified_release_and_the_viewer_rejects_unsafe_or_unbounded_rendering` — pins cmark-gfm provenance and the renderer hardening contract. + +### Native UI and filesystem characterization tests + +All fixtures in this section require `FILEMANAGER_UI_ISOLATED=1`, `FILEMANAGER_UI_EXE`, and an interactive desktop unless a stricter requirement is stated. + +#### `BasicUiTests` — risk-based lifecycle matrix + +`Basic_ui_scenario` generates the seven cases below. They are categorized as `LockStress` so the nightly verifier lane can repeat the compact risk matrix independently of the release gate. + +| Scenario family | Cases | Description | +| --- | ---: | --- | +| Main window | 1 | Launches the application and verifies the native main window is usable. | +| Accessibility tree | 1 | Verifies UIA3 can discover the expected accessible descendants. | +| Configuration cancel | 1 | Opens Configuration, cancels, and confirms the main window remains usable. | +| Configuration commit | 1 | Opens Configuration, commits, and confirms normal dialog lifecycle. | +| Configuration persistence | 1 | Changes a visible setting and verifies it after reopening Configuration. | +| Restart after commit | 1 | Commits configuration and verifies the application restarts cleanly. | +| FTP bookmark persistence | 1 | Creates and renames an FTP bookmark, restarts, and verifies the edited bookmark; requires `FILEMANAGER_UI_FTP_ORGANIZE_COMMAND`. | + +#### `FileAccessUiTests` + +- `Find_files_searches_subdirectories_from_the_active_panel` — searches the active panel for a unique nested file and verifies one result. +- `View_file_opens_the_selected_file_in_the_internal_viewer` — opens the selected text file in Salamander's internal viewer. +- `Edit_file_opens_the_selected_file_in_the_configured_editor` — launches the configured external editor with the selected file. + +#### `FileOperationUiTests` + +- `Create_directory_creates_requested_nested_directory` — creates a nested directory through the native dialog. +- `Copy_file_copies_content_to_other_panel` — copies one file, preserves content, and retains the source. +- `Copy_preserves_last_write_time_metadata` — verifies copied last-write time metadata. +- `Copy_preserves_multiple_empty_large_and_edge_named_alternate_data_streams` — copies multiple representative ADS values. +- `Copy_overwrite_replaces_target_streams_and_removes_stale_streams` — overwrites ADS content and removes target-only streams. +- `Copy_retries_a_temporarily_denied_alternate_data_stream_without_losing_it` — releases a locked stream and verifies Retry completes the copy. +- `Copy_overwrite_replaces_the_existing_target_only_after_the_user_confirms` — checks single-file overwrite confirmation. +- `Copy_overwrite_all_applies_the_choice_to_the_complete_conflicting_tree` — applies Overwrite All to every conflicting descendant. +- `Copy_skip_keeps_the_existing_target_and_the_source` — checks single-file Skip behavior. +- `Copy_skip_all_keeps_the_existing_conflicting_tree` — applies Skip All without changing conflicting targets. +- `Copy_file_persists_a_completed_recovery_journal_with_item_intent` — verifies the completed durable journal, immutable plan, states, and correlation ID. +- `Copy_directory_copies_all_descendants_to_other_panel` — copies a complete nested directory tree. +- `Rename_file_renames_without_changing_content` — renames a file and preserves content. +- `Rename_directory_preserves_all_descendants` — renames a directory and retains its tree. +- `Rename_case_only_change_preserves_the_file_and_updates_its_displayed_name` — verifies a case-only directory-entry rename. +- `Rename_overwrite_replaces_the_collision_without_losing_source_metadata` — confirms file overwrite and preserves source timestamp metadata. +- `Move_file_moves_content_to_other_panel` — performs a same-volume file move and removes the source. +- `Move_directory_moves_all_descendants_to_other_panel` — performs a same-volume directory-tree move. +- `Move_overwrite_replaces_the_existing_target_and_removes_the_source` — confirms a move overwrite and source removal. +- `Move_skip_keeps_the_existing_target_and_the_unmoved_source` — checks that Skip retains both versions. +- `Move_overwrite_all_replaces_every_conflict_before_removing_the_source_tree` — verifies all conflicts commit before source-tree removal. +- `Move_skip_all_retains_conflicting_sources_but_moves_nonconflicting_siblings` — retains skipped sources while continuing nonconflicting moves. +- `Delete_file_removes_the_selected_file` — deletes one file. +- `Delete_directory_removes_all_descendants` — deletes a complete directory tree. +- `Delete_mixed_selection_removes_the_selected_file_and_directory_tree` — deletes a selected file and directory together. +- `Delete_to_recycle_bin_removes_the_source_and_creates_a_recoverable_shell_item` — verifies recycle-bin deletion; requires `FILEMANAGER_UI_RECYCLE_BIN=1`. +- `Cancelling_an_in_progress_conflicting_copy_keeps_both_versions_and_records_cancellation` — cancels at a conflict prompt and verifies both files plus the journal cancellation record. +- `Cancelling_operation_dialog_leaves_source_and_target_unchanged` — four cases cover cancelled create, copy, move, and rename dialogs. +- `Create_directory_failure_keeps_existing_file_intact` — verifies a directory/file collision does not change the file. +- `Copy_or_move_failure_does_not_modify_source` — two cases verify invalid copy and move destinations leave sources intact. +- `Rename_overwrite_decline_keeps_the_original_file_and_existing_target` — chooses No at the rename overwrite prompt and retains both files. +- `Rename_directory_collision_keeps_both_directory_trees` — verifies directory rename collisions cannot overwrite either tree. +- `Delete_skip_for_locked_file_keeps_it_and_continues_with_later_items` — skips a sharing violation and deletes later selected items. + +#### Optional UI fixtures + +- `ConfigurationRecoveryUiTests.Every_interrupted_configuration_write_restores_a_complete_profile` — measures every registry write boundary, terminates the process after each mutation, and verifies restart exposes either the complete baseline or complete candidate profile; requires `FILEMANAGER_UI_CONFIG_FAULT_INJECTION=1` and category `FaultInjection`. +- `CrossVolumeMoveCharacterizationUiTests.Move_across_volumes_copies_the_complete_tree_before_removing_the_source` — verifies a cross-volume tree is fully copied before source removal; requires `FILEMANAGER_UI_CROSS_VOLUME_ROOT`. +- `CrossVolumeMoveCharacterizationUiTests.Move_across_ADS_capable_volumes_preserves_multiple_streams_before_removing_the_source` — verifies ADS preservation across two ADS-capable volumes before source removal. +- `AlternateDataStreamsUnsupportedTargetUiTests.Cross_volume_move_to_an_ADS_unsupported_target_keeps_the_source_when_metadata_loss_is_declined` — verifies default data reaches the target while declining ADS loss retains the source; requires `FILEMANAGER_UI_ADS_UNSUPPORTED_TARGET_ROOT`. +- `OperationRecoveryCharacterizationUiTests.Restart_reconciliation_commits_a_fully_written_transactional_target` — seeds a ready temporary target and incomplete journal, then verifies startup reconciliation commits and records it. +- `ReparsePointTopologyUiTests.Copy_does_not_traverse_changed_or_cyclic_junction_targets_outside_the_operation_root` — verifies copy ignores changed and cyclic junction targets. +- `ReparsePointTopologyUiTests.Delete_junction_removes_only_the_link_and_never_its_target` — verifies deleting a junction preserves its target. +- `ReparsePointTopologyUiTests.Copy_does_not_traverse_a_directory_symbolic_link_outside_the_operation_root` — verifies copy ignores an external directory symlink; skips when the host lacks symlink privileges. +- `ToolbarIconSizeUiTests.Customize_toolbar_cycles_all_icon_sizes_and_persists_the_choice_after_restart` — exercises all three icon sizes, verifies live persistence, restarts, and restores the incoming setting. + +## Continuous-integration coverage + +Pull-request CI runs the four changed-line ratchets, operation-completion and durable-copy contracts, zlib and bzip2 probes, cmark-gfm hardening, and the SQLite recovery probe after its Debug x64 build. + +The release workflow gates installer publication on the complete root runner using the dedicated `filemanager-ui` self-hosted environment. It supplies the built executable and SQLite DLL, enables configuration fault injection and Recycle Bin coverage, obtains its two dedicated filesystem roots and FTP command ID from repository variables, and uses `-FailOnSkipped`. The release therefore fails if any runner check fails, any external prerequisite is missing, or any NUnit case reports `Assert.Ignore`/`NotExecuted`. It retrieves Inno Setup only through [`tools/release-inputs.json`](tools/release-inputs.json), checks the locked SHA-256 and Authenticode publisher before installation, then passes an immutable uploaded installer artifact to the `production`-protected publish job. Private PDB artifacts are retained for 180 days and are never attached to the public release. + +Required release repository variables are `FILEMANAGER_UI_CROSS_VOLUME_ROOT`, `FILEMANAGER_UI_ADS_UNSUPPORTED_TARGET_ROOT`, and `FILEMANAGER_UI_FTP_ORGANIZE_COMMAND`. The dedicated runner must provide Visual Studio 2026, .NET 8, PowerShell 7.4+, Application Verifier, an unlocked desktop, symlink creation privileges, an ADS-capable second volume, and an ADS-unsupported volume. + +The nightly native-verifier lane runs the complete `UI` category with Application Verifier's Heaps, Handles, Locks, and Exceptions layers plus full PageHeap. `gflags.exe` is required and both Verifier and PageHeap settings are cleared in `finally` even if a test fails. The existing runner profile remains dedicated; provisioning and destroying a fresh Windows profile or VM is still an external runner-management requirement. diff --git a/tests/FileManager.UiTests/AlternateDataStreamsUnsupportedTargetUiTests.cs b/tests/FileManager.UiTests/AlternateDataStreamsUnsupportedTargetUiTests.cs new file mode 100644 index 00000000..402a5ab6 --- /dev/null +++ b/tests/FileManager.UiTests/AlternateDataStreamsUnsupportedTargetUiTests.cs @@ -0,0 +1,40 @@ +using FileManager.UiTests.Infrastructure; +using NUnit.Framework; + +namespace FileManager.UiTests; + +// This fixture needs a caller-supplied FAT/FAT32/exFAT-like volume. It creates +// and removes only a GUID child below that dedicated root. +[TestFixture] +public sealed class AlternateDataStreamsUnsupportedTargetUiTests : FileOperationUiTestBase +{ + protected override string? TargetVolumeRoot => UiTestSettings.RequireUnsupportedAdsTargetRoot(); + + [Test] + [Category("CrossVolume")] + [Category("AlternateDataStreams")] + public void Cross_volume_move_to_an_ADS_unsupported_target_keeps_the_source_when_metadata_loss_is_declined() + { + Assert.That(Path.GetPathRoot(Workspace.SourceDirectory), Is.Not.EqualTo(Path.GetPathRoot(Workspace.TargetDirectory)), + "The unsupported-target fixture must use different source and target volumes."); + AlternateDataStreams.RequireSupportAt(Workspace.SourceDirectory); + AlternateDataStreams.RequireUnsupportedAt(Workspace.TargetDirectory); + + var source = Workspace.SourcePath("ads-unsupported-target.txt"); + var target = Workspace.TargetPath("ads-unsupported-target.txt"); + File.WriteAllText(source, "ads-unsupported-default-content"); + AlternateDataStreams.Write(source, "must-not-silently-disappear", "source-stream-content"u8.ToArray()); + + ExecuteWithPath(NativeCommands.MoveFiles, "ads-unsupported-target.txt", Workspace.TargetDirectory, commit: true); + ChooseOperationPrompt(WaitForOperationPrompt(7), 7); // IDNO: retain the source when ADS loss is reported + + WaitForFileSystem(() => File.Exists(target), "The unsupported target did not receive the default data stream."); + Assert.Multiple(() => + { + Assert.That(File.ReadAllText(target), Is.EqualTo("ads-unsupported-default-content")); + Assert.That(File.Exists(source), Is.True, "Declining metadata loss deleted the ADS-bearing source."); + AlternateDataStreams.AssertContent(source, "must-not-silently-disappear", "source-stream-content"u8.ToArray()); + AlternateDataStreams.AssertAbsent(target, "must-not-silently-disappear"); + }); + } +} diff --git a/tests/FileManager.UiTests/ApplicationVersionContractTests.cs b/tests/FileManager.UiTests/ApplicationVersionContractTests.cs new file mode 100644 index 00000000..8f2696e9 --- /dev/null +++ b/tests/FileManager.UiTests/ApplicationVersionContractTests.cs @@ -0,0 +1,68 @@ +using NUnit.Framework; +using System.Xml.Linq; + +namespace FileManager.UiTests; + +// This source-level contract keeps the product-major labels synchronized while preserving +// the existing date- and CI-driven build-number construction across native and installer builds. +public sealed class ApplicationVersionContractTests +{ + [Test] + public void Product_major_is_6_and_build_components_remain_automatic() + { + var root = FindRepositoryRoot(); + var versionHeader = File.ReadAllText(Path.Combine(root, "src", "plugins", "shared", "spl_vers.h")); + var buildProps = File.ReadAllText(Path.Combine(root, "src", "Directory.Build.props")); + var installer = File.ReadAllText(Path.Combine(root, "Installer", "setup.iss")); + var workflow = File.ReadAllText(Path.Combine(root, ".github", "workflows", "build-installer.yml")); + var configuration = File.ReadAllText(Path.Combine(root, "src", "mainwnd_config.cpp")); + var constants = File.ReadAllText(Path.Combine(root, "src", "consts.h")); + var manifestPath = Path.Combine(root, "src", "manifest.xml"); + var manifest = XDocument.Load(manifestPath); + XNamespace assemblyNamespace = "urn:schemas-microsoft-com:asm.v1"; + var assemblyVersion = manifest.Root?.Element(assemblyNamespace + "assemblyIdentity")?.Attribute("version")?.Value; + var shellExtension = File.ReadAllText(Path.Combine(root, "src", "shellext", "shellext.rc")); + var shellRegistration = File.ReadAllText(Path.Combine(root, "src", "shexreg.h")); + var readme = File.ReadAllText(Path.Combine(root, "README.md")); + + Assert.Multiple(() => + { + Assert.That(versionHeader, Does.Contain("#define VERSINFO_SALAMANDER_MAJOR 6")); + Assert.That(versionHeader, Does.Contain("VERSINFO_xstr(VERSINFO_SALAMANDER_MAJOR) \".\" VERSINFO_SALAMANDER_BUILDDATE")); + Assert.That(buildProps, Does.Contain("$([System.DateTime]::Now.ToString('yyyyMMdd'))")); + Assert.That(buildProps, Does.Contain("VERSINFO_SALAMANDER_BUILDDATE_DYNAMIC=$(SalamanderBuildDate)")); + + Assert.That(installer, Does.Contain("#define MyAppVersion \"6.0\"")); + Assert.That(installer, Does.Contain("AppVersion={#MyAppVersion}.{#BuildNumber}")); + Assert.That(installer, Does.Contain("OutputBaseFilename=OpenSalamander_{#MyAppVersion}.{#BuildNumber}")); + Assert.That(workflow, Does.Contain("/DBuildNumber=${{ github.run_number }}")); + Assert.That(workflow, Does.Contain("tag_name: 6.0.${{ github.run_number }}")); + Assert.That(workflow, Does.Contain("OpenSalamander_6.0.${{ github.run_number }}.exe")); + + Assert.That(configuration, Does.Contain("const DWORD THIS_CONFIG_VERSION = 105;")); + Assert.That(configuration, Does.Contain("\"Software\\\\Open Salamander\\\\6.0\",")); + Assert.That(configuration, Does.Contain("\"Software\\\\Open Salamander\\\\5.0\",")); + Assert.That(constants, Does.Contain("#define SALCFG_ROOTS_COUNT 84")); + Assert.That(assemblyVersion, Is.EqualTo("6.0.0.0")); + Assert.That(shellExtension, Does.Contain("#define FILE_SALVER \"6.0\"")); + Assert.That(shellRegistration, Does.Contain("#define SALSHEXT_SHAREDNAMESAPPENDIX \"600\"")); + Assert.That(readme, Does.Contain("OpenSalamander_6.0.{build_number}.exe")); + }); + } + + private static string FindRepositoryRoot() + { + var current = new DirectoryInfo(TestContext.CurrentContext.TestDirectory); + while (current is not null) + { + if (Directory.Exists(Path.Combine(current.FullName, ".git"))) + { + return current.FullName; + } + + current = current.Parent; + } + + throw new DirectoryNotFoundException("Could not locate the FileManager repository root."); + } +} diff --git a/tests/FileManager.UiTests/BasicUiScenarios.cs b/tests/FileManager.UiTests/BasicUiScenarios.cs index 108499ee..a6e0d598 100644 --- a/tests/FileManager.UiTests/BasicUiScenarios.cs +++ b/tests/FileManager.UiTests/BasicUiScenarios.cs @@ -21,31 +21,27 @@ internal static IEnumerable All { get { - // Repeating the core lifecycle flows exposes leaked windows, stale handles, and restart regressions. + // The nightly lock stress lane reuses these lifecycle repeats to expose leaked windows, stale handles, and lock defects. foreach (var scenario in CreateScenarios()) yield return new TestCaseData(scenario) .SetName($"UI_{scenario.Number:000}_{scenario.Kind}_{scenario.Description}") - .SetCategory("UI"); + .SetCategory("UI") + .SetCategory("LockStress"); } } private static IEnumerable CreateScenarios() { - var number = 1; - // The distribution keeps the suite at exactly 100 cases while giving persistence paths dedicated coverage. - foreach (var (kind, runs) in new[] - { - (UiScenarioKind.MainWindow, 15), - (UiScenarioKind.AccessibilityTree, 15), - (UiScenarioKind.ConfigurationCancel, 15), - (UiScenarioKind.ConfigurationCommit, 15), - (UiScenarioKind.ConfigurationPersistence, 15), - (UiScenarioKind.RestartAfterCommit, 15), - (UiScenarioKind.FtpBookmarkCreationPersists, 10), - }) + // The main gate samples distinct lifecycle risks once; prolonged repetition belongs to a separately scheduled soak. + return new[] { - for (var repetition = 1; repetition <= runs; repetition++) - yield return new UiScenario(number++, kind, $"Run_{repetition:00}"); - } + new UiScenario(1, UiScenarioKind.MainWindow, "Cold_start"), + new UiScenario(2, UiScenarioKind.AccessibilityTree, "Owner_drawn_accessibility"), + new UiScenario(3, UiScenarioKind.ConfigurationCancel, "Discarded_settings"), + new UiScenario(4, UiScenarioKind.ConfigurationCommit, "Committed_settings"), + new UiScenario(5, UiScenarioKind.ConfigurationPersistence, "Persisted_settings_restart"), + new UiScenario(6, UiScenarioKind.RestartAfterCommit, "Restart_after_settings_commit"), + new UiScenario(7, UiScenarioKind.FtpBookmarkCreationPersists, "Plugin_profile_persistence"), + }; } } diff --git a/tests/FileManager.UiTests/BasicUiTests.cs b/tests/FileManager.UiTests/BasicUiTests.cs index 5ce25cef..bafddecc 100644 --- a/tests/FileManager.UiTests/BasicUiTests.cs +++ b/tests/FileManager.UiTests/BasicUiTests.cs @@ -55,7 +55,7 @@ private void AssertMainWindow() // Basic visibility and focus checks verify that UIA3 attached to the native top-level window. Assert.Multiple(() => { - Assert.That(MainWindow.Properties.NativeWindowHandle.Value, Is.Not.EqualTo(nint.Zero)); + Assert.That(MainWindowHandle, Is.Not.EqualTo(nint.Zero)); Assert.That(MainWindow.Title, Is.Not.Empty); Assert.That(MainWindow.IsEnabled, Is.True); Assert.That(MainWindow.BoundingRectangle.Width, Is.GreaterThan(0)); @@ -85,10 +85,18 @@ private void AssertConfigurationCommitPersistsAfterRestart() RestartFileManager(); var reloadedDialog = OpenConfigurationDialog(); - Assert.That(IsFirstConfigurationCheckBoxChecked(reloadedDialog), Is.EqualTo(!originalState), - "The configuration value was not retained after a committed dialog and restart."); - ToggleFirstConfigurationCheckBox(reloadedDialog); - CloseConfigurationDialog(reloadedDialog, commit: true); + try + { + Assert.That(IsFirstConfigurationCheckBoxChecked(reloadedDialog), Is.EqualTo(!originalState), + "The configuration value was not retained after a committed dialog and restart."); + } + finally + { + // Restore the incoming profile even when the persistence assertion fails, preventing repetition cascades. + if (IsFirstConfigurationCheckBoxChecked(reloadedDialog) != originalState) + ToggleFirstConfigurationCheckBox(reloadedDialog); + CloseConfigurationDialog(reloadedDialog, commit: true); + } } private void AssertFtpBookmarkCreationPersistsAfterRestart(int scenarioNumber) diff --git a/tests/FileManager.UiTests/ConfigurationRecoveryUiTests.cs b/tests/FileManager.UiTests/ConfigurationRecoveryUiTests.cs new file mode 100644 index 00000000..7e50dcec --- /dev/null +++ b/tests/FileManager.UiTests/ConfigurationRecoveryUiTests.cs @@ -0,0 +1,197 @@ +using FileManager.UiTests.Infrastructure; +using Microsoft.Win32; +using NUnit.Framework; + +namespace FileManager.UiTests; + +[TestFixture] +[NonParallelizable] +public sealed class ConfigurationRecoveryUiTests : FileManagerUiTestBase +{ + // Keep this aligned with CONFIGURATION_WRITE_FAULT_EXIT_CODE in src/regwork.h. + private const int ConfigurationWriteFaultExitCode = 121; + private const string ConfigurationRoot = @"Software\Open Salamander\6.0"; + private const string ConfigurationBackupRoot = ConfigurationRoot + ".backup.63A7CD13"; + + [Test] + [Category("FaultInjection")] + public void Every_interrupted_configuration_write_restores_a_complete_profile() + { + UiTestSettings.RequireConfigurationFaultInjection(); + + var reportPath = Path.Combine(Path.GetTempPath(), $"filemanager-config-writes-{Guid.NewGuid():N}.txt"); + RegistryRootsSnapshot? baselineSnapshot = null; + try + { + var baseline = ReadFirstConfigurationCheckBox(); + CommitFirstConfigurationCheckBox(baseline); + RestartFileManager(); + Assert.That(ReadFirstConfigurationCheckBox(), Is.EqualTo(baseline), "The baseline configuration did not survive restart."); + // Freeze the complete host store only after a normal save and restart have validated the baseline. + baselineSnapshot = RegistryRootsSnapshot.Capture(ConfigurationRoot, ConfigurationBackupRoot); + + var candidate = !baseline; + var operationCount = UiTestSettings.LimitConfigurationFaultBoundaries( + MeasureConfigurationWriteBoundaries(baseline, candidate, reportPath)); + + for (var writeBoundary = 1; writeBoundary <= operationCount; writeBoundary++) + AssertRecoveryAtWriteBoundary(writeBoundary, baseline, candidate, baselineSnapshot); + } + finally + { + if (baselineSnapshot is not null) + { + // Leave the disposable account at its verified pre-matrix state even when a boundary assertion fails. + StopCurrentApplication(baselineSnapshot.Restore); + } + if (File.Exists(reportPath)) + File.Delete(reportPath); + } + } + + private int MeasureConfigurationWriteBoundaries(bool baseline, bool candidate, string reportPath) + { + RestartFileManager(new Dictionary + { + ["FILEMANAGER_CONFIG_FAULT_REPORT"] = reportPath, + }); + Assert.That(ReadFirstConfigurationCheckBox(), Is.EqualTo(baseline)); + CommitFirstConfigurationCheckBox(candidate); + + var deadline = DateTime.UtcNow + TimeSpan.FromSeconds(15); + while (DateTime.UtcNow < deadline) + { + if (File.Exists(reportPath) && int.TryParse(File.ReadAllText(reportPath), out var operationCount) && operationCount > 0) + return operationCount; + + Thread.Sleep(50); + } + + Assert.Fail("The configuration writer did not report its registry write-boundary count."); + return 0; + } + + private void AssertRecoveryAtWriteBoundary(int writeBoundary, bool baseline, bool candidate, + RegistryRootsSnapshot baselineSnapshot) + { + // Every boundary starts from byte-for-byte equivalent registry state, independent of the preceding crash result. + RestartFileManager(afterPreviousApplicationStopped: baselineSnapshot.Restore); + Assert.That(ReadFirstConfigurationCheckBox(), Is.EqualTo(baseline), + $"Boundary {writeBoundary} did not begin from the complete baseline profile."); + + // Arm only after the baseline dialog closes so its deferred command save cannot consume this trial. + NativeCommands.ArmNextConfigurationWriteFault(MainWindowHandle, writeBoundary); + var dialog = OpenConfigurationDialog(); + ToggleFirstConfigurationCheckBox(dialog); + CommitConfigurationDialogWithoutWaiting(dialog); + Assert.That(WaitForFileManagerExit(TimeSpan.FromSeconds(15)), Is.EqualTo(ConfigurationWriteFaultExitCode), + $"Configuration fault injection did not stop at write boundary {writeBoundary}."); + + RestartFileManager(); + var restored = ReadFirstConfigurationCheckBox(); + Assert.That(restored == baseline || restored == candidate, Is.True, + $"Restart after write boundary {writeBoundary} exposed a mixed configuration profile."); + } + + private bool ReadFirstConfigurationCheckBox() + { + var dialog = OpenConfigurationDialog(); + var value = IsFirstConfigurationCheckBoxChecked(dialog); + CloseConfigurationDialog(dialog, commit: false); + return value; + } + + private void CommitFirstConfigurationCheckBox(bool expectedValue) + { + var dialog = OpenConfigurationDialog(); + if (IsFirstConfigurationCheckBoxChecked(dialog) != expectedValue) + ToggleFirstConfigurationCheckBox(dialog); + CloseConfigurationDialog(dialog, commit: true); + } + + private sealed class RegistryRootsSnapshot + { + private readonly Dictionary roots; + + private RegistryRootsSnapshot(Dictionary roots) + { + this.roots = roots; + } + + internal static RegistryRootsSnapshot Capture(params string[] rootPaths) + { + using var currentUser = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry64); + var roots = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var rootPath in rootPaths) + { + using var key = currentUser.OpenSubKey(rootPath, writable: false); + // Capture absence as well as content so stale recovery backups cannot leak into another boundary. + roots[rootPath] = key is null ? null : RegistryNode.Capture(key); + } + return new RegistryRootsSnapshot(roots); + } + + internal void Restore() + { + using var currentUser = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry64); + foreach (var (rootPath, snapshot) in roots) + { + currentUser.DeleteSubKeyTree(rootPath, throwOnMissingSubKey: false); + if (snapshot is null) + continue; + + using var key = currentUser.CreateSubKey(rootPath, writable: true) + ?? throw new InvalidOperationException($"Could not recreate registry baseline '{rootPath}'."); + snapshot.Restore(key); + } + } + } + + private sealed class RegistryNode + { + private readonly Dictionary values = new(StringComparer.OrdinalIgnoreCase); + private readonly Dictionary children = new(StringComparer.OrdinalIgnoreCase); + + internal static RegistryNode Capture(RegistryKey key) + { + var node = new RegistryNode(); + foreach (var valueName in key.GetValueNames()) + { + var value = key.GetValue(valueName, null, RegistryValueOptions.DoNotExpandEnvironmentNames) + ?? throw new InvalidOperationException($"Registry value '{key.Name}\\{valueName}' could not be captured."); + // Registry APIs return mutable arrays; clone them so the baseline remains immutable in memory. + node.values[valueName] = new RegistryValue(CloneRegistryValue(value), key.GetValueKind(valueName)); + } + + foreach (var childName in key.GetSubKeyNames()) + { + using var child = key.OpenSubKey(childName, writable: false) + ?? throw new InvalidOperationException($"Registry key '{key.Name}\\{childName}' could not be captured."); + node.children[childName] = Capture(child); + } + return node; + } + + internal void Restore(RegistryKey key) + { + foreach (var (valueName, value) in values) + key.SetValue(valueName, CloneRegistryValue(value.Data), value.Kind); + + foreach (var (childName, childSnapshot) in children) + { + using var child = key.CreateSubKey(childName, writable: true) + ?? throw new InvalidOperationException($"Could not recreate registry baseline '{key.Name}\\{childName}'."); + childSnapshot.Restore(child); + } + } + + private static object CloneRegistryValue(object value) => value switch + { + byte[] bytes => bytes.ToArray(), + string[] strings => strings.ToArray(), + _ => value, + }; + + private sealed record RegistryValue(object Data, RegistryValueKind Kind); + } +} diff --git a/tests/FileManager.UiTests/CrossVolumeMoveCharacterizationUiTests.cs b/tests/FileManager.UiTests/CrossVolumeMoveCharacterizationUiTests.cs new file mode 100644 index 00000000..c6301093 --- /dev/null +++ b/tests/FileManager.UiTests/CrossVolumeMoveCharacterizationUiTests.cs @@ -0,0 +1,58 @@ +using FileManager.UiTests.Infrastructure; +using NUnit.Framework; + +namespace FileManager.UiTests; + +// This fixture only runs when the caller explicitly supplies a dedicated +// second volume. It never deletes the supplied root, only its GUID child. +[TestFixture] +public sealed class CrossVolumeMoveCharacterizationUiTests : FileOperationUiTestBase +{ + protected override string? TargetVolumeRoot => UiTestSettings.RequireCrossVolumeRoot(); + + [Test] + [Category("CrossVolume")] + public void Move_across_volumes_copies_the_complete_tree_before_removing_the_source() + { + Assert.That(Path.GetPathRoot(Workspace.SourceDirectory), Is.Not.EqualTo(Path.GetPathRoot(Workspace.TargetDirectory)), + "The cross-volume fixture must use different source and target volumes."); + + ExecuteWithPath(NativeCommands.MoveFiles, "move-tree", Workspace.TargetDirectory, commit: true); + + var targetPayload = Workspace.TargetPath("move-tree\\nested\\payload.txt"); + WaitForFileSystem(() => File.Exists(targetPayload), "Cross-volume move did not create the complete target tree."); + Assert.Multiple(() => + { + Assert.That(File.ReadAllText(targetPayload), Is.EqualTo("move-tree-content")); + Assert.That(Directory.Exists(Workspace.SourcePath("move-tree")), Is.False, + "Cross-volume move retained the source after the target was committed."); + }); + } + + [Test] + [Category("CrossVolume")] + [Category("AlternateDataStreams")] + public void Move_across_ADS_capable_volumes_preserves_multiple_streams_before_removing_the_source() + { + Assert.That(Path.GetPathRoot(Workspace.SourceDirectory), Is.Not.EqualTo(Path.GetPathRoot(Workspace.TargetDirectory)), + "The cross-volume fixture must use different source and target volumes."); + AlternateDataStreams.RequireSupportAt(Workspace.SourceDirectory); + AlternateDataStreams.RequireSupportAt(Workspace.TargetDirectory); + + var source = Workspace.SourcePath("ads-cross-volume.txt"); + var target = Workspace.TargetPath("ads-cross-volume.txt"); + File.WriteAllText(source, "ads-cross-volume-default-content"); + AlternateDataStreams.Write(source, "first", "first-cross-volume-stream"u8.ToArray()); + AlternateDataStreams.Write(source, "second", "second-cross-volume-stream"u8.ToArray()); + + ExecuteWithPath(NativeCommands.MoveFiles, "ads-cross-volume.txt", Workspace.TargetDirectory, commit: true); + + WaitForFileSystem(() => File.Exists(target), "Cross-volume move did not create the ADS test target."); + Assert.Multiple(() => + { + Assert.That(File.Exists(source), Is.False, "Cross-volume move retained the source after copying ADS."); + AlternateDataStreams.AssertContent(target, "first", "first-cross-volume-stream"u8.ToArray()); + AlternateDataStreams.AssertContent(target, "second", "second-cross-volume-stream"u8.ToArray()); + }); + } +} diff --git a/tests/FileManager.UiTests/FileAccessUiTests.cs b/tests/FileManager.UiTests/FileAccessUiTests.cs new file mode 100644 index 00000000..e711f137 --- /dev/null +++ b/tests/FileManager.UiTests/FileAccessUiTests.cs @@ -0,0 +1,85 @@ +using FileManager.UiTests.Infrastructure; +using FlaUI.Core.AutomationElements; +using NUnit.Framework; + +namespace FileManager.UiTests; + +[TestFixture] +public sealed class FileAccessUiTests : FileOperationUiTestBase +{ + [Test] + public void Find_files_searches_subdirectories_from_the_active_panel() + { + // Stable control IDs make the search deterministic without coupling the test to translated labels. + NativeCommands.Execute(MainWindow.Properties.NativeWindowHandle.Value, NativeCommands.FindFiles); + var findDialog = WaitForWindow(window => + window.Properties.NativeWindowHandle.Value != MainWindow.Properties.NativeWindowHandle.Value && + window.FindFirstDescendant(cf => cf.ByAutomationId("2510")) is not null); + + SetComboValue(findDialog, "2505", "find-target.txt"); + SetComboValue(findDialog, "2501", Workspace.SourceDirectory); + SetCheckBox(findDialog, "2503", expected: true); + SetCheckBox(findDialog, "2508", expected: false); + + var findNow = findDialog.FindFirstDescendant(cf => cf.ByAutomationId("1"))?.AsButton(); + Assert.That(findNow, Is.Not.Null, "Find dialog did not expose its Find Now button."); + findNow!.Invoke(); + + var results = findDialog.FindFirstDescendant(cf => cf.ByAutomationId("2510")); + Assert.That(results, Is.Not.Null, "Find dialog did not expose its results list."); + // The exact mask makes a single virtual-list row sufficient to prove the nested target was found. + WaitForFileSystem(() => NativeCommands.GetListViewItemCount(results!.Properties.NativeWindowHandle.Value) == 1, + "Find did not return the unique nested file."); + findDialog.Close(); + WaitForWindowToClose(findDialog); + } + + [Test] + public void View_file_opens_the_selected_file_in_the_internal_viewer() + { + // The viewer window class is a native invariant independent of its localized caption suffix. + SelectSourceItem("view-file.txt"); + NativeCommands.Execute(MainWindow.Properties.NativeWindowHandle.Value, NativeCommands.ViewFile); + + var viewer = WaitForWindow(window => + string.Equals(window.Properties.ClassName.ValueOrDefault, "Salamander's Viewer Window", StringComparison.Ordinal)); + Assert.That(viewer.Name, Does.Contain("view-file.txt").IgnoreCase, + "Internal viewer caption did not identify the selected file."); + viewer.Close(); + WaitForWindowToClose(viewer); + } + + [Test] + public void Edit_file_opens_the_selected_file_in_the_configured_editor() + { + using var editorProcesses = new TestOwnedProcessScope("notepad"); + // The process scope also cleans a headless default editor when the product defect prevents HWND discovery. + // The default editor is out of process, so the opened file name in its window proves the edit dispatch reached it. + SelectSourceItem("edit-file.txt"); + NativeCommands.Execute(MainWindow.Properties.NativeWindowHandle.Value, NativeCommands.EditFile); + + var editor = WaitForDesktopWindow(window => + window.Properties.ProcessId.ValueOrDefault != Application.ProcessId && + window.Name.Contains("edit-file.txt", StringComparison.OrdinalIgnoreCase), + "Configured editor did not open the selected file."); + editor.Close(); + WaitForWindowToClose(editor); + } + + private static void SetComboValue(Window dialog, string automationId, string value) + { + // Find fields are native combo boxes even when their histories are empty. + var combo = dialog.FindFirstDescendant(cf => cf.ByAutomationId(automationId))?.AsComboBox(); + Assert.That(combo, Is.Not.Null, $"Find dialog did not expose combo box {automationId}."); + combo!.Value = value; + } + + private static void SetCheckBox(Window dialog, string automationId, bool expected) + { + // Persisted Find options are normalized so prior cases cannot change search semantics. + var checkBox = dialog.FindFirstDescendant(cf => cf.ByAutomationId(automationId))?.AsCheckBox(); + Assert.That(checkBox, Is.Not.Null, $"Find dialog did not expose check box {automationId}."); + if (checkBox!.IsChecked != expected) + checkBox.Toggle(); + } +} diff --git a/tests/FileManager.UiTests/FileManager.UiTests.csproj b/tests/FileManager.UiTests/FileManager.UiTests.csproj index 5862e86f..350bb82b 100644 --- a/tests/FileManager.UiTests/FileManager.UiTests.csproj +++ b/tests/FileManager.UiTests/FileManager.UiTests.csproj @@ -2,6 +2,8 @@ net8.0-windows + + true false enable enable diff --git a/tests/FileManager.UiTests/FileOperationUiTests.cs b/tests/FileManager.UiTests/FileOperationUiTests.cs new file mode 100644 index 00000000..9080f9f7 --- /dev/null +++ b/tests/FileManager.UiTests/FileOperationUiTests.cs @@ -0,0 +1,534 @@ +using FileManager.UiTests.Infrastructure; +using FlaUI.Core.AutomationElements; +using NUnit.Framework; +using System.Text.RegularExpressions; + +namespace FileManager.UiTests; + +[TestFixture] +public sealed class FileOperationUiTests : FileOperationUiTestBase +{ + [Test] + public void Create_directory_creates_requested_nested_directory() + { + ExecuteWithPath(NativeCommands.CreateDirectory, string.Empty, "created\\nested", commit: true); + // A disposable profile may ask before creating the missing parent; the test owns that normal interaction. + ConfirmCreateDirectoryParentsIfPrompted(); + + WaitForFileSystem(() => Directory.Exists(Workspace.SourcePath("created\\nested")), + "Create Directory did not create the requested nested directory."); + } + + [Test] + public void Copy_file_copies_content_to_other_panel() + { + ExecuteWithPath(NativeCommands.CopyFiles, "copy-file.txt", Workspace.TargetDirectory, commit: true); + + WaitForFileSystem(() => File.Exists(Workspace.TargetPath("copy-file.txt")), "Copy did not create the destination file."); + Assert.That(File.ReadAllText(Workspace.TargetPath("copy-file.txt")), Is.EqualTo("copy-file-content")); + Assert.That(File.Exists(Workspace.SourcePath("copy-file.txt")), Is.True, "Copy unexpectedly removed the source file."); + } + + [Test] + public void Copy_preserves_last_write_time_metadata() + { + var source = Workspace.SourcePath("copy-file.txt"); + var expectedTimestamp = new DateTime(2024, 03, 21, 12, 34, 56, DateTimeKind.Utc); + File.SetLastWriteTimeUtc(source, expectedTimestamp); + + ExecuteWithPath(NativeCommands.CopyFiles, "copy-file.txt", Workspace.TargetDirectory, commit: true); + + var target = Workspace.TargetPath("copy-file.txt"); + WaitForFileSystem(() => File.Exists(target), "Copy did not create the destination file."); + Assert.That(File.GetLastWriteTimeUtc(target), Is.EqualTo(File.GetLastWriteTimeUtc(source)), + "Copy did not preserve the source last-write metadata."); + } + + [Test] + [Category("AlternateDataStreams")] + public void Copy_preserves_multiple_empty_large_and_edge_named_alternate_data_streams() + { + AlternateDataStreams.RequireSupportAt(Workspace.SourceDirectory); + AlternateDataStreams.RequireSupportAt(Workspace.TargetDirectory); + + var source = Workspace.SourcePath("ads-copy.txt"); + var target = Workspace.TargetPath("ads-copy.txt"); + var large = CreateLargeStreamContent(); + File.WriteAllText(source, "ads-copy-default-content"); + AlternateDataStreams.Write(source, "notes", "named-stream-content"u8.ToArray()); + AlternateDataStreams.Write(source, "empty", []); + AlternateDataStreams.Write(source, "large", large); + AlternateDataStreams.Write(source, "edge name.with.dots", "edge-stream-content"u8.ToArray()); + + ExecuteWithPath(NativeCommands.CopyFiles, "ads-copy.txt", Workspace.TargetDirectory, commit: true); + + WaitForFileSystem(() => File.Exists(target), "Copy did not create the ADS test target."); + Assert.Multiple(() => + { + Assert.That(File.ReadAllText(target), Is.EqualTo("ads-copy-default-content")); + AlternateDataStreams.AssertContent(target, "notes", "named-stream-content"u8.ToArray()); + AlternateDataStreams.AssertContent(target, "empty", []); + AlternateDataStreams.AssertContent(target, "large", large); + AlternateDataStreams.AssertContent(target, "edge name.with.dots", "edge-stream-content"u8.ToArray()); + }); + } + + [Test] + [Category("AlternateDataStreams")] + public void Copy_overwrite_replaces_target_streams_and_removes_stale_streams() + { + AlternateDataStreams.RequireSupportAt(Workspace.SourceDirectory); + AlternateDataStreams.RequireSupportAt(Workspace.TargetDirectory); + + var source = Workspace.SourcePath("ads-overwrite.txt"); + var target = Workspace.TargetPath("ads-overwrite.txt"); + File.WriteAllText(source, "ads-overwrite-source"); + File.WriteAllText(target, "ads-overwrite-target"); + AlternateDataStreams.Write(source, "replacement", "replacement-stream-content"u8.ToArray()); + AlternateDataStreams.Write(target, "replacement", "stale-replacement-content"u8.ToArray()); + AlternateDataStreams.Write(target, "stale", "stale-stream-content"u8.ToArray()); + + ExecuteWithPath(NativeCommands.CopyFiles, "ads-overwrite.txt", Workspace.TargetDirectory, commit: true); + DismissOptionalPrompt(6, TimeSpan.FromSeconds(3), 6); // IDYES when this profile confirms overwrites. + + WaitForFileSystem(() => File.ReadAllText(target) == "ads-overwrite-source", + "Confirmed overwrite did not replace the ADS test target."); + Assert.Multiple(() => + { + AlternateDataStreams.AssertContent(target, "replacement", "replacement-stream-content"u8.ToArray()); + AlternateDataStreams.AssertAbsent(target, "stale"); + }); + } + + [Test] + [Category("AlternateDataStreams")] + public void Copy_retries_a_temporarily_denied_alternate_data_stream_without_losing_it() + { + AlternateDataStreams.RequireSupportAt(Workspace.SourceDirectory); + AlternateDataStreams.RequireSupportAt(Workspace.TargetDirectory); + + var source = Workspace.SourcePath("ads-retry.txt"); + var target = Workspace.TargetPath("ads-retry.txt"); + File.WriteAllText(source, "ads-retry-default-content"); + AlternateDataStreams.Write(source, "temporarily-denied", "retry-stream-content"u8.ToArray()); + + var deniedStream = AlternateDataStreams.LockForRead(source, "temporarily-denied"); + try + { + ExecuteWithPath(NativeCommands.CopyFiles, "ads-retry.txt", Workspace.TargetDirectory, commit: true); + var retryPrompt = WaitForOperationPrompt(4); // IDRETRY + deniedStream.Dispose(); + ChooseOperationPrompt(retryPrompt, 4); + } + finally + { + deniedStream.Dispose(); + } + + WaitForFileSystem(() => File.Exists(target), "Retry did not complete the ADS copy."); + AlternateDataStreams.AssertContent(target, "temporarily-denied", "retry-stream-content"u8.ToArray()); + } + + [Test] + public void Copy_overwrite_replaces_the_existing_target_only_after_the_user_confirms() + { + ExecuteWithPath(NativeCommands.CopyFiles, "overwrite-file.txt", Workspace.TargetDirectory, commit: true); + DismissOptionalPrompt(6, TimeSpan.FromSeconds(3), 6); // IDYES when this profile confirms overwrites. + + WaitForFileSystem(() => File.ReadAllText(Workspace.TargetPath("overwrite-file.txt")) == "overwrite-source-content", + "Confirmed overwrite did not replace the target content."); + Assert.That(File.ReadAllText(Workspace.SourcePath("overwrite-file.txt")), Is.EqualTo("overwrite-source-content")); + } + + [Test] + public void Copy_overwrite_all_applies_the_choice_to_the_complete_conflicting_tree() + { + ExecuteWithPath(NativeCommands.CopyFiles, "overwrite-all-tree", Workspace.TargetDirectory, commit: true); + DismissOptionalPrompt(185, TimeSpan.FromSeconds(3), 185); // IDB_ALL when conflict choices are enabled. + + WaitForFileSystem(() => File.ReadAllText(Workspace.TargetPath("overwrite-all-tree\\nested\\first.txt")) == "overwrite-all-first-source" && + File.ReadAllText(Workspace.TargetPath("overwrite-all-tree\\nested\\second.txt")) == "overwrite-all-second-source", + "Overwrite All did not reconcile every conflicting descendant."); + } + + [Test] + public void Copy_skip_keeps_the_existing_target_and_the_source() + { + ExecuteWithPath(NativeCommands.CopyFiles, "skip-file.txt", Workspace.TargetDirectory, commit: true); + DismissOptionalPrompt(173, TimeSpan.FromSeconds(3), 173); // Semantic checks expose an unavailable Skip choice. + + WaitForFileSystem(() => File.ReadAllText(Workspace.TargetPath("skip-file.txt")) == "skip-target-content", + "Skip unexpectedly modified the conflicting target."); + Assert.That(File.ReadAllText(Workspace.SourcePath("skip-file.txt")), Is.EqualTo("skip-source-content")); + } + + [Test] + public void Copy_skip_all_keeps_the_existing_conflicting_tree() + { + ExecuteWithPath(NativeCommands.CopyFiles, "skip-all-tree", Workspace.TargetDirectory, commit: true); + DismissOptionalPrompt(174, TimeSpan.FromSeconds(3), 174); // Semantic checks expose an unavailable Skip All choice. + + WaitForFileSystem(() => Directory.Exists(Workspace.TargetPath("skip-all-tree")), "Skip All removed the existing target directory."); + Assert.Multiple(() => + { + Assert.That(File.ReadAllText(Workspace.TargetPath("skip-all-tree\\nested\\first.txt")), Is.EqualTo("skip-all-first-target")); + Assert.That(File.ReadAllText(Workspace.TargetPath("skip-all-tree\\nested\\second.txt")), Is.EqualTo("skip-all-second-target")); + Assert.That(File.Exists(Workspace.SourcePath("skip-all-tree\\nested\\first.txt")), Is.True); + }); + } + + [Test] + public void Copy_file_persists_a_completed_recovery_journal_with_item_intent() + { + var source = Workspace.SourcePath("copy-file.txt"); + var target = Workspace.TargetPath("copy-file.txt"); + + ExecuteWithPath(NativeCommands.CopyFiles, "copy-file.txt", Workspace.TargetDirectory, commit: true); + + WaitForFileSystem(() => FindOperationJournalFor(source) is not null, + "Copy did not persist a durable operation journal."); + var journal = FindOperationJournalFor(source)!; + var content = File.ReadAllText(journal); + var planItem = content.Split(new[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries) + .Single(line => line.StartsWith("PLANITEM|0|copy-file|", StringComparison.Ordinal)); + var correlation = Regex.Match(content, @"CORRELATION\|operation=(?[0-9A-F]{8}-[0-9A-F]{8}-[0-9A-F]{8})", + RegexOptions.CultureInvariant); + + Assert.That(content, Does.Contain($"ITEM|").And.Contain("|copy-file|").And.Contain(source).And.Contain(target)); + // A completed copy must retain one dispatch ID in the plan, item, and attempt records for recovery triage. + Assert.That(correlation.Success, Is.True, "The journal must persist a command-dispatch correlation ID."); + Assert.That(content, Does.Contain($"PLAN|1|operation={correlation.Groups["id"].Value}|")); + Assert.That(content, Does.Contain($"|operation={correlation.Groups["id"].Value}|sequence=0|attempt=1")); + Assert.That(content, Does.Contain("PLAN|1|operation=")); + Assert.That(planItem, Does.Contain($"source={source}|target={target}"), + "The immutable plan snapshot must preserve the generated copy intent before execution."); + Assert.That(content, Does.Contain("STATE|").And.Contain("|prepared")); + Assert.That(content, Does.Contain("|committed")); + Assert.That(content, Does.Contain("OPERATION|completed")); + } + + [Test] + public void Copy_directory_copies_all_descendants_to_other_panel() + { + ExecuteWithPath(NativeCommands.CopyFiles, "copy-tree", Workspace.TargetDirectory, commit: true); + + var copiedPayload = Workspace.TargetPath("copy-tree\\nested\\payload.txt"); + WaitForFileSystem(() => File.Exists(copiedPayload), "Copy did not create the directory descendant."); + Assert.That(File.ReadAllText(copiedPayload), Is.EqualTo("copy-tree-content")); + Assert.That(Directory.Exists(Workspace.SourcePath("copy-tree\\nested")), Is.True); + } + + [Test] + public void Rename_file_renames_without_changing_content() + { + ExecuteWithPath(NativeCommands.RenameFile, "rename-file.txt", "renamed-file.txt", commit: true); + + WaitForFileSystem(() => File.Exists(Workspace.SourcePath("renamed-file.txt")), "Rename did not create the requested file name."); + Assert.That(File.Exists(Workspace.SourcePath("rename-file.txt")), Is.False); + Assert.That(File.ReadAllText(Workspace.SourcePath("renamed-file.txt")), Is.EqualTo("rename-file-content")); + } + + [Test] + public void Rename_directory_preserves_all_descendants() + { + ExecuteWithPath(NativeCommands.RenameFile, "rename-tree", "renamed-tree", commit: true); + + var payload = Workspace.SourcePath("renamed-tree\\nested\\payload.txt"); + WaitForFileSystem(() => File.Exists(payload), "Rename did not preserve the directory descendant."); + Assert.That(Directory.Exists(Workspace.SourcePath("rename-tree")), Is.False); + Assert.That(File.ReadAllText(payload), Is.EqualTo("rename-tree-content")); + } + + [Test] + public void Rename_case_only_change_preserves_the_file_and_updates_its_displayed_name() + { + // The native rename path treats a case-only target as the same identity rather than an overwrite collision. + ExecuteWithPath(NativeCommands.RenameFile, "rename-case.txt", "RENAME-CASE.txt", commit: true); + + WaitForFileSystem(() => Directory.EnumerateFiles(Workspace.SourceDirectory) + .Any(path => Path.GetFileName(path) == "RENAME-CASE.txt"), + "Case-only rename did not persist the requested directory-entry casing."); + Assert.That(File.ReadAllText(Workspace.SourcePath("RENAME-CASE.txt")), Is.EqualTo("rename-case-content")); + } + + [Test] + public void Rename_overwrite_replaces_the_collision_without_losing_source_metadata() + { + var source = Workspace.SourcePath("rename-overwrite.txt"); + var target = Workspace.SourcePath("rename-overwrite-target.txt"); + var expectedTimestamp = new DateTime(2023, 11, 03, 08, 15, 00, DateTimeKind.Utc); + File.SetLastWriteTimeUtc(source, expectedTimestamp); + + // Fail at fixture setup instead of timing out on a prompt when the panel-local collision is missing. + Assert.Multiple(() => + { + Assert.That(File.Exists(source), Is.True, "The rename source fixture is missing."); + Assert.That(File.Exists(target), Is.True, "The rename collision fixture is missing from the source panel."); + }); + + ExecuteWithPath(NativeCommands.RenameFile, "rename-overwrite.txt", "rename-overwrite-target.txt", commit: true); + DismissOptionalPrompt(6, TimeSpan.FromSeconds(3), 6); // IDYES when this profile confirms overwrites. + + WaitForFileSystem(() => File.ReadAllText(target) == "rename-overwrite-source-content", + "Confirmed rename overwrite did not replace the collision target."); + Assert.Multiple(() => + { + Assert.That(File.Exists(source), Is.False); + Assert.That(File.GetLastWriteTimeUtc(target), Is.EqualTo(expectedTimestamp)); + }); + } + + [Test] + public void Move_file_moves_content_to_other_panel() + { + Assert.That(Path.GetPathRoot(Workspace.SourceDirectory), Is.EqualTo(Path.GetPathRoot(Workspace.TargetDirectory)), + "The default move fixture characterizes same-volume behavior."); + ExecuteWithPath(NativeCommands.MoveFiles, "move-file.txt", Workspace.TargetDirectory, commit: true); + + var target = Workspace.TargetPath("move-file.txt"); + WaitForFileSystem(() => File.Exists(target), "Move did not create the destination file."); + Assert.That(File.Exists(Workspace.SourcePath("move-file.txt")), Is.False, "Move retained the source file."); + Assert.That(File.ReadAllText(target), Is.EqualTo("move-file-content")); + } + + [Test] + public void Move_directory_moves_all_descendants_to_other_panel() + { + ExecuteWithPath(NativeCommands.MoveFiles, "move-tree", Workspace.TargetDirectory, commit: true); + + var payload = Workspace.TargetPath("move-tree\\nested\\payload.txt"); + WaitForFileSystem(() => File.Exists(payload), "Move did not create the directory descendant."); + Assert.That(Directory.Exists(Workspace.SourcePath("move-tree")), Is.False, "Move retained the source directory."); + Assert.That(File.ReadAllText(payload), Is.EqualTo("move-tree-content")); + } + + [Test] + public void Move_overwrite_replaces_the_existing_target_and_removes_the_source() + { + // Move must apply the confirmed overwrite before deleting the source identity. + ExecuteWithPath(NativeCommands.MoveFiles, "move-overwrite.txt", Workspace.TargetDirectory, commit: true); + DismissOptionalPrompt(6, TimeSpan.FromSeconds(3), 6); // IDYES when this profile confirms overwrites. + + WaitForFileSystem(() => File.ReadAllText(Workspace.TargetPath("move-overwrite.txt")) == "move-overwrite-source-content", + "Confirmed move overwrite did not replace the target content."); + Assert.That(File.Exists(Workspace.SourcePath("move-overwrite.txt")), Is.False, + "Confirmed move overwrite retained the source file."); + } + + [Test] + public void Move_skip_keeps_the_existing_target_and_the_unmoved_source() + { + // A skipped move collision must retain both versions because no target commit occurred. + ExecuteWithPath(NativeCommands.MoveFiles, "move-skip.txt", Workspace.TargetDirectory, commit: true); + DismissOptionalPrompt(173, TimeSpan.FromSeconds(3), 173); // Semantic checks expose an unavailable Skip choice. + + WaitForFileSystem(() => File.ReadAllText(Workspace.TargetPath("move-skip.txt")) == "move-skip-target-content", + "Skipped move unexpectedly modified the conflicting target."); + Assert.That(File.ReadAllText(Workspace.SourcePath("move-skip.txt")), Is.EqualTo("move-skip-source-content")); + } + + [Test] + public void Move_overwrite_all_replaces_every_conflict_before_removing_the_source_tree() + { + // Overwrite All must commit every destination before the move removes the complete source tree. + ExecuteWithPath(NativeCommands.MoveFiles, "move-overwrite-all-tree", Workspace.TargetDirectory, commit: true); + DismissOptionalPrompt(185, TimeSpan.FromSeconds(3), 185); // IDB_ALL when conflict choices are enabled. + + var firstTarget = Workspace.TargetPath("move-overwrite-all-tree\\nested\\first.txt"); + var secondTarget = Workspace.TargetPath("move-overwrite-all-tree\\nested\\second.txt"); + WaitForFileSystem(() => File.Exists(firstTarget) && File.Exists(secondTarget) && + File.ReadAllText(firstTarget) == "move-overwrite-all-first-source" && + File.ReadAllText(secondTarget) == "move-overwrite-all-second-source", + "Move Overwrite All did not replace every conflicting descendant."); + WaitForOperationJournalTerminal(Workspace.SourcePath("move-overwrite-all-tree"), + "Move Overwrite All left its durable operation journal incomplete."); + Assert.That(Directory.Exists(Workspace.SourcePath("move-overwrite-all-tree")), Is.False, + "Move Overwrite All retained the fully committed source tree."); + } + + [Test] + public void Move_skip_all_retains_conflicting_sources_but_moves_nonconflicting_siblings() + { + // Skip All suppresses later conflict prompts without retaining independently committed siblings. + ExecuteWithPath(NativeCommands.MoveFiles, "move-skip-all-tree", Workspace.TargetDirectory, commit: true); + DismissOptionalPrompt(174, TimeSpan.FromSeconds(3), 174); // Semantic checks expose an unavailable Skip All choice. + + WaitForFileSystem(() => File.Exists(Workspace.TargetPath("move-skip-all-tree\\nested\\unique.txt")), + "Move Skip All did not continue with a nonconflicting sibling."); + Assert.Multiple(() => + { + Assert.That(File.ReadAllText(Workspace.TargetPath("move-skip-all-tree\\nested\\first.txt")), + Is.EqualTo("move-skip-all-first-target")); + Assert.That(File.ReadAllText(Workspace.TargetPath("move-skip-all-tree\\nested\\second.txt")), + Is.EqualTo("move-skip-all-second-target")); + Assert.That(File.Exists(Workspace.SourcePath("move-skip-all-tree\\nested\\first.txt")), Is.True); + Assert.That(File.Exists(Workspace.SourcePath("move-skip-all-tree\\nested\\second.txt")), Is.True); + Assert.That(File.Exists(Workspace.SourcePath("move-skip-all-tree\\nested\\unique.txt")), Is.False); + }); + // The visible result can precede the worker's final directory cleanup and durable terminal transition. + WaitForOperationJournalTerminal(Workspace.SourcePath("move-skip-all-tree"), + "Move Skip All left its durable operation journal incomplete."); + } + + [Test] + public void Delete_file_removes_the_selected_file() + { + SelectSourceItem("delete-file.txt"); + NativeCommands.Execute(MainWindowHandle, NativeCommands.DeleteFiles); + ConfirmDeleteIfPrompted(); + + WaitForFileSystem(() => !File.Exists(Workspace.SourcePath("delete-file.txt")), "Delete did not remove the selected file."); + } + + [Test] + public void Delete_directory_removes_all_descendants() + { + SelectSourceItem("delete-tree"); + NativeCommands.Execute(MainWindowHandle, NativeCommands.DeleteFiles); + ConfirmDeleteIfPrompted(); + + WaitForFileSystem(() => !Directory.Exists(Workspace.SourcePath("delete-tree")), "Delete did not remove the selected directory tree."); + } + + [Test] + public void Delete_mixed_selection_removes_the_selected_file_and_directory_tree() + { + // Mixed selection verifies that the delete plan retains both file and recursive-directory intents. + SelectSourceItems("delete-mixed-file.txt", "delete-mixed-tree"); + NativeCommands.Execute(MainWindow.Properties.NativeWindowHandle.Value, NativeCommands.DeleteFiles); + ConfirmDeleteIfPrompted(); + + WaitForFileSystem(() => !File.Exists(Workspace.SourcePath("delete-mixed-file.txt")) && + !Directory.Exists(Workspace.SourcePath("delete-mixed-tree")), + "Delete did not remove every item in the mixed selection."); + } + + [Test] + [Category("RecycleBin")] + public void Delete_to_recycle_bin_removes_the_source_and_creates_a_recoverable_shell_item() + { + UiTestSettings.RequireRecycleBinTest(); + var source = Workspace.SourcePath("recycle-file.txt"); + var volumeRoot = Path.GetPathRoot(source)!; + var itemCountBefore = ShellRecycleBin.GetItemCount(volumeRoot); + + SelectSourceItem("recycle-file.txt"); + NativeCommands.Execute(MainWindowHandle, NativeCommands.DeleteFiles); + ConfirmDeleteIfPrompted(); + + WaitForFileSystem(() => !File.Exists(source), "Recycle-bin delete did not remove the source file."); + WaitForFileSystem(() => ShellRecycleBin.GetItemCount(volumeRoot) > itemCountBefore, + "Delete did not create a recycle-bin item. Ensure the isolated profile uses the default recycle-bin setting."); + } + + [Test] + public void Cancelling_an_in_progress_conflicting_copy_keeps_both_versions_and_records_cancellation() + { + var source = Workspace.SourcePath("cancel-conflict.txt"); + + ExecuteWithPath(NativeCommands.CopyFiles, "cancel-conflict.txt", Workspace.TargetDirectory, commit: true); + DismissOptionalPrompt(2, TimeSpan.FromSeconds(3), 2); // Semantic checks expose an unavailable Cancel choice. + + WaitForFileSystem(() => File.ReadAllText(Workspace.TargetPath("cancel-conflict.txt")) == "cancel-conflict-target-content", + "Cancellation unexpectedly changed the target."); + Assert.That(File.ReadAllText(source), Is.EqualTo("cancel-conflict-source-content")); + WaitForFileSystem(() => + { + var journal = FindOperationJournalFor(source); + return journal is not null && TryReadOperationJournal(journal, out var content) && + content.Contains("OPERATION|cancelled", StringComparison.Ordinal); + }, + "Cancellation was not recorded in the durable operation journal."); + } + + [TestCase(NativeCommands.CreateDirectory, "", "cancelled-directory")] + [TestCase(NativeCommands.CopyFiles, "cancel-copy.txt", "")] + [TestCase(NativeCommands.MoveFiles, "cancel-move.txt", "")] + [TestCase(NativeCommands.RenameFile, "cancel-rename.txt", "cancelled-rename.txt")] + public void Cancelling_operation_dialog_leaves_source_and_target_unchanged(int command, string sourceName, string enteredPath) + { + var path = command is NativeCommands.CopyFiles or NativeCommands.MoveFiles ? Workspace.TargetDirectory : enteredPath; + ExecuteWithPath(command, sourceName, path, commit: false); + + if (sourceName.Length != 0) + Assert.That(File.Exists(Workspace.SourcePath(sourceName)), Is.True, "Cancellation changed the source item."); + if (command == NativeCommands.CreateDirectory) + Assert.That(Directory.Exists(Workspace.SourcePath(enteredPath)), Is.False); + if (command is NativeCommands.CopyFiles or NativeCommands.MoveFiles) + Assert.That(File.Exists(Workspace.TargetPath(sourceName)), Is.False, "Cancellation created a target item."); + if (command == NativeCommands.RenameFile) + Assert.That(File.Exists(Workspace.SourcePath(enteredPath)), Is.False, "Cancellation renamed the source item."); + } + + [Test] + public void Create_directory_failure_keeps_existing_file_intact() + { + SubmitInvalidPathAndCancel(NativeCommands.CreateDirectory, string.Empty, "create-collision"); + + Assert.That(File.ReadAllText(Workspace.SourcePath("create-collision")), Is.EqualTo("create-collision-content")); + } + + [TestCase(NativeCommands.CopyFiles, "copy-file.txt")] + [TestCase(NativeCommands.MoveFiles, "move-file.txt")] + public void Copy_or_move_failure_does_not_modify_source(int command, string sourceName) + { + SubmitInvalidPathAndCancel(command, sourceName, Workspace.TargetPath("blocked-target\\child.txt")); + + Assert.That(File.Exists(Workspace.SourcePath(sourceName)), Is.True); + Assert.That(File.Exists(Workspace.TargetPath("blocked-target")), Is.True); + } + + [Test] + public void Rename_overwrite_decline_keeps_the_original_file_and_existing_target() + { + // IDNO is the rename-specific skip path and must return to the rename dialog without changing either file. + ExecuteWithPath(NativeCommands.RenameFile, "rename-file.txt", "rename-collision.txt", commit: true); + if (DismissOptionalPrompt(7, TimeSpan.FromSeconds(3), 7)) // IDNO + { + var reopenedRenameDialog = WaitForWindow(window => + window.Properties.NativeWindowHandle.Value != MainWindow.Properties.NativeWindowHandle.Value && + window.FindFirstDescendant(cf => cf.ByAutomationId("2"))?.AsButton() is not null); + CloseDialog(reopenedRenameDialog, commit: false); + } + + Assert.That(File.ReadAllText(Workspace.SourcePath("rename-file.txt")), Is.EqualTo("rename-file-content")); + Assert.That(File.ReadAllText(Workspace.SourcePath("rename-collision.txt")), Is.EqualTo("collision-content")); + } + + [Test] + public void Rename_directory_collision_keeps_both_directory_trees() + { + // Directory collisions are rejected rather than offered the file-only overwrite path. + SubmitInvalidPathAndCancel(NativeCommands.RenameFile, "rename-collision-source", "rename-collision-target"); + + Assert.That(File.ReadAllText(Workspace.SourcePath("rename-collision-source\\payload.txt")), + Is.EqualTo("rename-collision-source-content")); + Assert.That(File.ReadAllText(Workspace.SourcePath("rename-collision-target\\payload.txt")), + Is.EqualTo("rename-collision-target-content")); + } + + [Test] + public void Delete_skip_for_locked_file_keeps_it_and_continues_with_later_items() + { + // Handling the worker prompt prevents this case from passing merely because deletion has not completed yet. + using var handle = Workspace.HoldSourceFileOpen("delete-locked.txt"); + SelectSourceItem("delete-locked.txt"); + NativeCommands.Execute(MainWindowHandle, NativeCommands.DeleteFiles); + ConfirmDeleteIfPrompted(); + DismissOptionalPrompt(173, TimeSpan.FromSeconds(3), 173); // Semantic checks expose a missing locked-file Skip path. + + WaitForFileSystem(() => !File.Exists(Workspace.SourcePath("delete-z-after-skip.txt")), + "Delete did not continue with later items after skipping the locked file."); + Assert.That(File.Exists(Workspace.SourcePath("delete-locked.txt")), Is.True, + "Skipping a delete error removed the locked source file."); + } + + private static byte[] CreateLargeStreamContent() + { + var content = new byte[(3 * 1024 * 1024) + 17]; + for (var index = 0; index < content.Length; index++) + content[index] = (byte)(index % 251); + + return content; + } +} diff --git a/tests/FileManager.UiTests/Infrastructure/AlternateDataStreams.cs b/tests/FileManager.UiTests/Infrastructure/AlternateDataStreams.cs new file mode 100644 index 00000000..c0ca7cf6 --- /dev/null +++ b/tests/FileManager.UiTests/Infrastructure/AlternateDataStreams.cs @@ -0,0 +1,80 @@ +using NUnit.Framework; + +namespace FileManager.UiTests.Infrastructure; + +internal static class AlternateDataStreams +{ + internal static void RequireSupportAt(string directory) + { + var file = Path.Combine(directory, $"ads-probe-{Guid.NewGuid():N}.tmp"); + var stream = StreamPath(file, "probe"); + try + { + File.WriteAllBytes(file, [0]); + File.WriteAllBytes(stream, [0x5a]); + + if (!File.ReadAllBytes(stream).SequenceEqual(new byte[] { 0x5a })) + Assert.Ignore($"The filesystem at '{directory}' did not preserve alternate data stream content."); + } + catch (Exception exception) when (exception is IOException or UnauthorizedAccessException or NotSupportedException) + { + Assert.Ignore($"The filesystem at '{directory}' does not support alternate data streams: {exception.Message}"); + } + finally + { + TryDelete(stream); + TryDelete(file); + } + } + + internal static void RequireUnsupportedAt(string directory) + { + var file = Path.Combine(directory, $"ads-probe-{Guid.NewGuid():N}.tmp"); + var stream = StreamPath(file, "probe"); + try + { + File.WriteAllBytes(file, [0]); + File.WriteAllBytes(stream, [0x5a]); + Assert.Ignore($"The configured unsupported ADS target '{directory}' preserves alternate data streams."); + } + catch (Exception exception) when (exception is IOException or UnauthorizedAccessException or NotSupportedException) + { + // The configured target rejected the ADS probe as required by this test lane. + } + finally + { + TryDelete(stream); + TryDelete(file); + } + } + + internal static void Write(string file, string name, byte[] content) => + File.WriteAllBytes(StreamPath(file, name), content); + + internal static void AssertContent(string file, string name, byte[] expected) => + Assert.That(File.ReadAllBytes(StreamPath(file, name)), Is.EqualTo(expected), + $"Alternate data stream '{name}' was not preserved for '{file}'."); + + internal static void AssertAbsent(string file, string name) => + Assert.That(File.Exists(StreamPath(file, name)), Is.False, + $"Alternate data stream '{name}' unexpectedly exists for '{file}'."); + + internal static FileStream LockForRead(string file, string name) => + new(StreamPath(file, name), FileMode.Open, FileAccess.Read, FileShare.None); + + private static string StreamPath(string file, string name) => $"{file}:{name}"; + + private static void TryDelete(string path) + { + try + { + File.Delete(path); + } + catch (IOException) + { + } + catch (UnauthorizedAccessException) + { + } + } +} diff --git a/tests/FileManager.UiTests/Infrastructure/FileManagerUiTestBase.cs b/tests/FileManager.UiTests/Infrastructure/FileManagerUiTestBase.cs index fc200f56..39a1b055 100644 --- a/tests/FileManager.UiTests/Infrastructure/FileManagerUiTestBase.cs +++ b/tests/FileManager.UiTests/Infrastructure/FileManagerUiTestBase.cs @@ -1,6 +1,7 @@ using System.Diagnostics; using FlaUI.Core; using FlaUI.Core.AutomationElements; +using FlaUI.Core.Definitions; using FlaUI.UIA3; using NUnit.Framework; @@ -9,186 +10,434 @@ namespace FileManager.UiTests.Infrastructure; [NonParallelizable] public abstract class FileManagerUiTestBase { + private const int ConfigurationCheckBoxId = 304; // IDC_CLEARREADONLY is persisted on the General page. private readonly List launchedApplications = []; + private readonly HashSet preexistingHelperProcessIds = []; + private long configurationGenerationBeforeOpen; protected UIA3Automation Automation { get; private set; } = null!; protected Application Application { get; private set; } = null!; protected Window MainWindow { get; private set; } = null!; + // Keep the authoritative HWND outside UIA so forced-exit recovery loops never query a disconnected COM element. + protected nint MainWindowHandle { get; private set; } + + protected virtual string ApplicationArguments => UiTestSettings.Arguments; [SetUp] public void StartFileManager() { UiTestSettings.RequireIsolatedProfile(); + // Snapshot helpers before launch so cleanup never terminates a process owned by another test or user session. + preexistingHelperProcessIds.Clear(); + foreach (var helper in Process.GetProcessesByName("salmon")) + { + using (helper) + preexistingHelperProcessIds.Add(helper.Id); + } Automation = new UIA3Automation(); - StartApplication(); + try + { + BeforeFileManagerStarted(); + StartApplication(); + } + catch + { + // NUnit does not guarantee this fixture's TearDown after a failed SetUp, so release launched processes here. + CleanupFixture(); + throw; + } } [TearDown] public void StopFileManager() { - // Killing only processes launched by this fixture prevents a failed UI test from leaking instances. + CleanupFixture(); + } + + private void CleanupFixture() + { + // Run every cleanup layer even if process shutdown or fixture-specific disposal reports a failure. + try + { + StopLaunchedProcesses(); + } + finally + { + try + { + OnAfterFileManagerStopped(); + } + finally + { + // Setup can intentionally skip before UIA3 is created when the isolated profile opt-in is absent. + Automation?.Dispose(); + } + } + } + + private void StopLaunchedProcesses() + { + // Killing only processes launched by this fixture prevents a failed UI test from leaking application instances. foreach (var application in launchedApplications) + StopAndDisposeApplication(application); + launchedApplications.Clear(); + + var expectedHelperPath = Path.Combine(Path.GetDirectoryName(UiTestSettings.ExecutablePath)!, "salmon.exe"); + foreach (var helper in Process.GetProcessesByName("salmon")) { - if (!application.HasExited) - application.Kill(); - application.Dispose(); + using (helper) + { + if (preexistingHelperProcessIds.Contains(helper.Id) || helper.HasExited) + continue; + + // Match the executable directory as well as the PID snapshot before terminating a test-owned helper. + if (!string.Equals(helper.MainModule?.FileName, expectedHelperPath, StringComparison.OrdinalIgnoreCase)) + continue; + + helper.Kill(entireProcessTree: true); + helper.WaitForExit(5000); + } } + } + + protected void RestartFileManager(IReadOnlyDictionary? environment = null, + Action? afterPreviousApplicationStopped = null) + { + StopCurrentApplication(afterPreviousApplicationStopped); + // Fault recovery can restart thousands of crashed processes; recycle COM/UIA state so dead event subscribers cannot accumulate. + Automation.Dispose(); + Automation = new UIA3Automation(); + MainWindowHandle = 0; + MainWindow = null!; - // Setup can intentionally skip before UIA3 is created when the isolated profile opt-in is absent. - if (Automation is not null) - Automation.Dispose(); + StartApplication(environment); } - protected void RestartFileManager() + protected void StopCurrentApplication(Action? afterApplicationStopped = null) { - // Restart coverage verifies that the application remains launchable after a committed configuration dialog. - if (!Application.HasExited) - Application.Kill(); + var application = Application; + StopAndDisposeApplication(application); + launchedApplications.Remove(application); + // External state is replaced only after the process that owns it has exited. + afterApplicationStopped?.Invoke(); + } - StartApplication(); + private static void StopAndDisposeApplication(Application application) + { + // One bounded shutdown path keeps normal teardown and crash-recovery restarts consistent. + if (!application.HasExited) + application.Kill(); + + var deadline = DateTime.UtcNow + TimeSpan.FromSeconds(5); + while (!application.HasExited && DateTime.UtcNow < deadline) + Thread.Sleep(25); + if (!application.HasExited) + throw new InvalidOperationException("FileManager did not exit during test cleanup."); + application.Dispose(); } protected Window OpenConfigurationDialog() { - NativeCommands.OpenConfiguration(MainWindow.Properties.NativeWindowHandle.Value); - return WaitForWindow(window => window.Properties.NativeWindowHandle.Value != MainWindow.Properties.NativeWindowHandle.Value); + configurationGenerationBeforeOpen = NativeCommands.GetConfigurationGeneration(MainWindowHandle); + NativeCommands.Execute(MainWindowHandle, NativeCommands.Configuration); + // CTreePropDialog uses stable custom OK ID 5, tree ID 1, and standard Cancel ID 2. + var dialog = WaitForWindow(window => + window.Properties.NativeWindowHandle.Value != MainWindowHandle && + window.FindFirstDescendant(cf => cf.ByControlType(ControlType.Tree)) is not null && + NativeCommands.HasDialogControl(window.Properties.NativeWindowHandle.Value, 5) && + NativeCommands.HasDialogControl(window.Properties.NativeWindowHandle.Value, 2)); + // Always activate General so configuration tests do not depend on LastFocusedPage from an earlier run. + var dialogHandle = dialog.Properties.NativeWindowHandle.Value; + NativeCommands.SelectFirstTreePage(dialogHandle, 1); + WaitForConfigurationCheckBox(dialogHandle); + return dialog; } protected void CloseConfigurationDialog(Window dialog, bool commit) { - // Standard Win32 property sheets expose IDOK as automation ID 1 and IDCANCEL as automation ID 2. - var buttonId = commit ? "1" : "2"; - var button = dialog.FindFirstDescendant(cf => cf.ByAutomationId(buttonId))?.AsButton(); - Assert.That(button, Is.Not.Null, $"Configuration dialog did not expose button {buttonId}."); - button!.Invoke(); + // The tree-property dialog maps its custom OK control to ID 5 and retains IDCANCEL as ID 2. + NativeCommands.ClickDialogButton(dialog.Properties.NativeWindowHandle.Value, commit ? 5 : 2); WaitForWindowToClose(dialog); + WaitForMainWindowEnabled(); + WaitForConfigurationCompletion(); } - protected bool ToggleFirstConfigurationCheckBox(Window dialog) + protected void CommitConfigurationDialogWithoutWaiting(Window dialog) { - // Toggling a visible option gives the persistence test a real user commit without relying on translated labels. - var checkBox = dialog.FindAllDescendants() - .FirstOrDefault(element => element.ControlType == FlaUI.Core.Definitions.ControlType.CheckBox) - ?.AsCheckBox(); - Assert.That(checkBox, Is.Not.Null, "Configuration dialog did not expose a check box through UI Automation."); + // Fault injection terminates the process during the deferred native save, so waiting for + // the dialog's normal close is not meaningful. The caller waits for the process instead. + NativeCommands.ClickDialogButton(dialog.Properties.NativeWindowHandle.Value, 5); + } - var originalState = checkBox!.IsChecked; - // A three-state check box would not have a deterministic inverse for this basic persistence assertion. - Assert.That(originalState, Is.Not.Null, "Configuration persistence test requires a two-state check box."); - checkBox.Toggle(); - return originalState!.Value; + protected int WaitForFileManagerExit(TimeSpan timeout) + { + var deadline = DateTime.UtcNow + timeout; + while (DateTime.UtcNow < deadline) + { + if (Application.HasExited) + return Application.ExitCode; + + Thread.Sleep(50); + } + + Assert.Fail("FileManager did not terminate at the requested configuration write boundary."); + return 0; + } + + protected bool ToggleFirstConfigurationCheckBox(Window dialog) + { + var dialogHandle = dialog.Properties.NativeWindowHandle.Value; + // Read and change the real HWND state so a stale UIA ToggleState cannot create a false persistence failure. + var originalState = NativeCommands.GetDialogCheckBoxState(dialogHandle, ConfigurationCheckBoxId); + NativeCommands.ToggleDialogCheckBox(dialogHandle, ConfigurationCheckBoxId); + return originalState; } protected bool IsFirstConfigurationCheckBoxChecked(Window dialog) { - // The same visible control is inspected after relaunch to prove the committed value was read from persisted settings. - var checkBox = dialog.FindAllDescendants() - .FirstOrDefault(element => element.ControlType == FlaUI.Core.Definitions.ControlType.CheckBox) - ?.AsCheckBox(); - Assert.That(checkBox, Is.Not.Null, "Configuration dialog did not expose a check box through UI Automation."); - var currentState = checkBox!.IsChecked; - // Match the toggle helper so both sides of the restart assertion use a two-state value. - Assert.That(currentState, Is.Not.Null, "Configuration persistence test requires a two-state check box."); - return currentState!.Value; + // Inspect the same visible native control after relaunch to prove the persisted value was loaded. + return NativeCommands.GetDialogCheckBoxState(dialog.Properties.NativeWindowHandle.Value, + ConfigurationCheckBoxId); } protected Window OpenFtpBookmarksDialog() { - // The host allocates FTP menu IDs dynamically, so the test profile provides the runtime command used to open this dialog. - NativeCommands.Execute(MainWindow.Properties.NativeWindowHandle.Value, UiTestSettings.RequireFtpOrganizeCommand()); - return WaitForWindow(window => window.Properties.NativeWindowHandle.Value != MainWindow.Properties.NativeWindowHandle.Value); + // Resolve the host-owned SUID in the tested process so FTP cases never depend on a manually copied runtime value. + var command = NativeCommands.GetFtpOrganizeBookmarksCommand(MainWindowHandle); + if (command <= 0) + Assert.Ignore("The tested FileManager build did not load the FTP plug-in required by this persistence test."); + NativeCommands.Execute(MainWindowHandle, command); + return WaitForWindow(window => window.Properties.NativeWindowHandle.Value != MainWindowHandle); } protected void CreateFtpBookmark(Window bookmarksDialog, string bookmarkName) { - // These resource IDs are stable plug-in control identities, allowing UIA3 to create a bookmark without display text coupling. - var newButton = bookmarksDialog.FindFirstDescendant(cf => cf.ByAutomationId("572"))?.AsButton(); - Assert.That(newButton, Is.Not.Null, "FTP bookmarks dialog did not expose its New button."); - newButton!.Invoke(); - - var nameDialog = WaitForWindow(window => - window.Properties.NativeWindowHandle.Value != MainWindow.Properties.NativeWindowHandle.Value && - window.Properties.NativeWindowHandle.Value != bookmarksDialog.Properties.NativeWindowHandle.Value); - var nameBox = nameDialog.FindFirstDescendant(cf => cf.ByAutomationId("602"))?.AsTextBox(); - Assert.That(nameBox, Is.Not.Null, "New FTP bookmark dialog did not expose its name field."); - nameBox!.Text = bookmarkName; - - var okButton = nameDialog.FindFirstDescendant(cf => cf.ByAutomationId("1"))?.AsButton(); - Assert.That(okButton, Is.Not.Null, "New FTP bookmark dialog did not expose its OK button."); - okButton!.Invoke(); - WaitForWindowToClose(nameDialog); + EditFtpBookmarkName(bookmarksDialog, 572, bookmarkName, "New"); } protected void RenameFocusedFtpBookmark(Window bookmarksDialog, string bookmarkName) { - // Creating a bookmark focuses it, so Rename exercises the profile-edit commit without relying on list item text. - var renameButton = bookmarksDialog.FindFirstDescendant(cf => cf.ByAutomationId("573"))?.AsButton(); - Assert.That(renameButton, Is.Not.Null, "FTP bookmarks dialog did not expose its Rename button."); - renameButton!.Invoke(); + EditFtpBookmarkName(bookmarksDialog, 573, bookmarkName, "Rename"); + } + private void EditFtpBookmarkName(Window bookmarksDialog, int commandButtonId, string bookmarkName, string operation) + { + // Stable plug-in resource IDs keep create and rename on one locale-independent dialog path. + NativeCommands.ClickDialogButton(bookmarksDialog.Properties.NativeWindowHandle.Value, commandButtonId); var nameDialog = WaitForWindow(window => - window.Properties.NativeWindowHandle.Value != MainWindow.Properties.NativeWindowHandle.Value && + window.Properties.NativeWindowHandle.Value != MainWindowHandle && window.Properties.NativeWindowHandle.Value != bookmarksDialog.Properties.NativeWindowHandle.Value); var nameBox = nameDialog.FindFirstDescendant(cf => cf.ByAutomationId("602"))?.AsTextBox(); - Assert.That(nameBox, Is.Not.Null, "Rename FTP bookmark dialog did not expose its name field."); + Assert.That(nameBox, Is.Not.Null, $"{operation} FTP bookmark dialog did not expose its name field."); nameBox!.Text = bookmarkName; - - var okButton = nameDialog.FindFirstDescendant(cf => cf.ByAutomationId("1"))?.AsButton(); - Assert.That(okButton, Is.Not.Null, "Rename FTP bookmark dialog did not expose its OK button."); - okButton!.Invoke(); + NativeCommands.ClickDialogButton(nameDialog.Properties.NativeWindowHandle.Value, 1); WaitForWindowToClose(nameDialog); } protected void CloseFtpBookmarksDialog(Window bookmarksDialog) { // Close commits the edited bookmark collection in the FTP organizer instead of discarding it with Cancel. - var closeButton = bookmarksDialog.FindFirstDescendant(cf => cf.ByAutomationId("575"))?.AsButton(); - Assert.That(closeButton, Is.Not.Null, "FTP bookmarks dialog did not expose its Close button."); - closeButton!.Invoke(); + NativeCommands.ClickDialogButton(bookmarksDialog.Properties.NativeWindowHandle.Value, 575); WaitForWindowToClose(bookmarksDialog); } - private void StartApplication() + protected virtual void OnAfterFileManagerStopped() + { + } + + // Some native integration scenarios must arrange durable state before the + // executable reaches its startup reconciliation point. + protected virtual void BeforeFileManagerStarted() { - var startInfo = new ProcessStartInfo(UiTestSettings.ExecutablePath, UiTestSettings.Arguments) + } + + protected virtual bool IsExpectedStartupModal(nint windowHandle) + { + return false; + } + + protected Window[] GetFileManagerTopLevelWindows() + { + // Enumerate HWNDs first because UIA can omit native modal dialogs even while their owner is visibly disabled. + return NativeCommands.GetTopLevelWindowHandles(Application.ProcessId) + .Where(windowHandle => NativeCommands.GetWindowClassName(windowHandle) is + "SalamanderMainWindowVer25" or "#32770") + .Select(windowHandle => Automation.FromHandle(windowHandle).AsWindow()) + .ToArray(); + } + + private void StartApplication(IReadOnlyDictionary? environment = null) + { + var startInfo = new ProcessStartInfo(UiTestSettings.ExecutablePath, ApplicationArguments) { UseShellExecute = false, }; + if (environment is not null) + { + foreach (var (name, value) in environment) + startInfo.Environment[name] = value; + } Application = FlaUI.Core.Application.Launch(startInfo); launchedApplications.Add(Application); - Application.WaitWhileMainHandleIsMissing(TimeSpan.FromSeconds(20)); - MainWindow = Application.GetMainWindow(Automation) ?? - throw new AssertionException("FileManager did not expose a UI Automation main window."); + + // A clean profile can show the language selector first, so attach only to the native main-window class. + var timeout = DateTime.UtcNow + TimeSpan.FromSeconds(20); + var confirmedLanguageDialogs = new HashSet(); + var dismissedPathErrors = new HashSet(); + while (DateTime.UtcNow < timeout) + { + // Classify startup HWNDs natively so a broken dialog UIA provider cannot block the entire fixture setup. + var windowHandles = NativeCommands.GetTopLevelWindowHandles(Application.ProcessId); + var languageDialogHandle = windowHandles.FirstOrDefault(windowHandle => + NativeCommands.GetWindowClassName(windowHandle) == "#32770" && + NativeCommands.HasDialogControl(windowHandle, 1031)); + if (languageDialogHandle != 0) + { + if (confirmedLanguageDialogs.Add(languageDialogHandle)) + { + // IDOK accepts the application's preselected fallback without coupling startup to translated labels. + Assert.That(NativeCommands.HasDialogControl(languageDialogHandle, 1), Is.True, + "Language selector did not expose its OK button."); + NativeCommands.ClickDialogButton(languageDialogHandle, 1); + } + } + + var pathErrorDialogHandle = windowHandles.FirstOrDefault(windowHandle => + string.Equals(NativeCommands.GetWindowTitle(windowHandle), "Error Changing Directory", + StringComparison.Ordinal)); + if (pathErrorDialogHandle != 0) + { + if (dismissedPathErrors.Add(pathErrorDialogHandle)) + { + // File-operation cases persist disposable panel paths; acknowledge their next-launch invalid-path notice. + NativeCommands.ClickDialogButton(pathErrorDialogHandle, 1); + } + } + + var mainWindowHandle = windowHandles.FirstOrDefault(windowHandle => + string.Equals(NativeCommands.GetWindowClassName(windowHandle), + "SalamanderMainWindowVer25", StringComparison.Ordinal)); + var expectedStartupModal = windowHandles.Any(IsExpectedStartupModal); + if (mainWindowHandle != 0 && languageDialogHandle == 0 && + (expectedStartupModal || + NativeCommands.IsWindowEnabledForInput(mainWindowHandle) && NativeCommands.IsStartupReady(mainWindowHandle))) + { + // Native enumeration already requires visibility and avoids unsupported IsOffscreen providers. + // The narrowly identified recovery modal appears before the normal readiness handshake and owns its disabled main window. + MainWindowHandle = mainWindowHandle; + MainWindow = Automation.FromHandle(mainWindowHandle).AsWindow(); + return; + } + + if (Application.HasExited) + throw new AssertionException($"FileManager exited before exposing its main window (exit code {Application.ExitCode})."); + + Thread.Sleep(100); + } + + // Preserve timeout diagnostics without re-entering the UIA provider that may have caused startup to stall. + var startupWindows = string.Join(Environment.NewLine, + NativeCommands.GetTopLevelWindowHandles(Application.ProcessId).Select(windowHandle => + $"handle={windowHandle}, class='{NativeCommands.GetWindowClassName(windowHandle)}', " + + $"title='{NativeCommands.GetWindowTitle(windowHandle)}', message='{NativeCommands.GetMessageBoxText(windowHandle)}', " + + $"enabled={NativeCommands.IsWindowEnabledForInput(windowHandle)}, children=[{NativeCommands.DescribeChildControls(windowHandle)}]")); + // Startup inventory separates a missing window from a visible application that never published readiness. + throw new AssertionException($"FileManager did not expose a ready native main window within 20 seconds.{Environment.NewLine}{startupWindows}"); } - private Window WaitForWindow(Func predicate) + protected Window WaitForWindow(Func predicate) { var timeout = DateTime.UtcNow + TimeSpan.FromSeconds(10); while (DateTime.UtcNow < timeout) { - var dialog = Application.GetAllTopLevelWindows(Automation).FirstOrDefault(predicate); + var windows = GetFileManagerTopLevelWindows(); + var nativeAssertion = windows.FirstOrDefault(window => + NativeCommands.DescribeChildControls(window.Properties.NativeWindowHandle.Value) + .Contains("Debug Assertion Failed!", StringComparison.Ordinal)); + if (nativeAssertion is not null) + { + // A native assertion is an application failure, so report it immediately instead of masking it as a synchronization timeout. + Assert.Fail($"FileManager triggered a native debug assertion.{Environment.NewLine}" + + NativeCommands.DescribeChildControls(nativeAssertion.Properties.NativeWindowHandle.Value)); + } + + var dialog = windows.FirstOrDefault(predicate); if (dialog is not null) return dialog; Thread.Sleep(100); } - Assert.Fail("Timed out waiting for the requested FileManager window."); + var visibleWindows = string.Join(Environment.NewLine, GetFileManagerTopLevelWindows().Select(window => + $"handle={window.Properties.NativeWindowHandle.Value}, class='{window.Properties.ClassName.ValueOrDefault}', " + + $"title='{window.Title}', enabled={window.IsEnabled}, offscreen={window.Properties.IsOffscreen.ValueOrDefault}, " + + $"message='{NativeCommands.GetMessageBoxText(window.Properties.NativeWindowHandle.Value)}', " + + $"children=[{NativeCommands.DescribeChildControls(window.Properties.NativeWindowHandle.Value)}]")); + // Native child inventory exposes assertion and error dialogs whose UIA provider omits their message text. + Assert.Fail($"Timed out waiting for the requested FileManager window.{Environment.NewLine}{visibleWindows}"); return null!; } - private static void WaitForWindowToClose(Window dialog) + protected static void WaitForWindowToClose(Window dialog) { + var windowHandle = dialog.Properties.NativeWindowHandle.Value; var timeout = DateTime.UtcNow + TimeSpan.FromSeconds(10); while (DateTime.UtcNow < timeout) { - if (!dialog.IsAvailable) + // UIA can retain the element after native destruction, so observe the actual HWND lifetime. + if (!NativeCommands.IsWindowAvailable(windowHandle)) return; Thread.Sleep(100); } - Assert.Fail("Timed out waiting for the configuration dialog to close."); + Assert.Fail("Timed out waiting for the requested dialog to close."); + } + + private void WaitForMainWindowEnabled() + { + var mainWindowHandle = MainWindowHandle; + var timeout = DateTime.UtcNow + TimeSpan.FromSeconds(10); + while (DateTime.UtcNow < timeout) + { + if (NativeCommands.IsWindowEnabledForInput(mainWindowHandle)) + return; + Thread.Sleep(100); + } + + var remainingWindows = string.Join(Environment.NewLine, GetFileManagerTopLevelWindows().Select(window => + $"handle={window.Properties.NativeWindowHandle.Value}, class='{NativeCommands.GetWindowClassName(window.Properties.NativeWindowHandle.Value)}', " + + $"title='{NativeCommands.GetWindowTitle(window.Properties.NativeWindowHandle.Value)}', enabled={window.IsEnabled}")); + // Report the modal owner that kept the main window disabled after Configuration closed. + Assert.Fail($"FileManager main window remained disabled after the modal dialog closed.{Environment.NewLine}{remainingWindows}"); + } + + private void WaitForConfigurationCompletion() + { + var mainWindowHandle = MainWindowHandle; + var timeout = DateTime.UtcNow + TimeSpan.FromSeconds(10); + while (DateTime.UtcNow < timeout) + { + if (NativeCommands.GetConfigurationGeneration(mainWindowHandle) > configurationGenerationBeforeOpen) + return; + Thread.Sleep(100); + } + + Assert.Fail("Configuration handler did not finish persistence after the dialog closed."); + } + + private static void WaitForConfigurationCheckBox(nint dialogHandle) + { + var timeout = DateTime.UtcNow + TimeSpan.FromSeconds(5); + while (DateTime.UtcNow < timeout) + { + if (NativeCommands.IsDialogControlVisible(dialogHandle, ConfigurationCheckBoxId)) + return; + Thread.Sleep(50); + } + + // A visible stable control proves the General page selection completed before state is read or changed. + Assert.Fail("Configuration dialog did not activate its General page check box."); } } diff --git a/tests/FileManager.UiTests/Infrastructure/FileOperationUiTestBase.cs b/tests/FileManager.UiTests/Infrastructure/FileOperationUiTestBase.cs new file mode 100644 index 00000000..a4aa56fb --- /dev/null +++ b/tests/FileManager.UiTests/Infrastructure/FileOperationUiTestBase.cs @@ -0,0 +1,417 @@ +using FlaUI.Core.AutomationElements; +using FlaUI.Core.Definitions; +using NUnit.Framework; + +namespace FileManager.UiTests.Infrastructure; + +[NonParallelizable] +// Every derived fixture needs the interactive disposable-profile lane selected by the documented UI filter. +[Category("UI")] +public abstract class FileOperationUiTestBase : FileManagerUiTestBase +{ + private FileOperationWorkspace? workspace; + private readonly HashSet journalsBeforeStart = new(StringComparer.OrdinalIgnoreCase); + + protected FileOperationWorkspace Workspace => workspace ??= new FileOperationWorkspace(TargetVolumeRoot); + + // A characterization fixture can opt into a dedicated second volume. The + // default keeps source and target under one disposable temporary root. + protected virtual string? TargetVolumeRoot => null; + + protected override string ApplicationArguments => + $"{UiTestSettings.Arguments} -l \"{Workspace.SourceDirectory}\" -r \"{Workspace.TargetDirectory}\" -p 1"; + + protected override void BeforeFileManagerStarted() + { + journalsBeforeStart.Clear(); + var journalDirectory = GetOperationJournalDirectory(); + if (Directory.Exists(journalDirectory)) + journalsBeforeStart.UnionWith(Directory.EnumerateFiles(journalDirectory, "*.opj")); + } + + protected override void OnAfterFileManagerStopped() + { + try + { + var journalDirectory = GetOperationJournalDirectory(); + if (Directory.Exists(journalDirectory)) + { + // Each nonparallel case removes only journals it created, so a failed worker cannot block the next startup. + foreach (var journal in Directory.EnumerateFiles(journalDirectory, "*.opj") + .Where(path => !journalsBeforeStart.Contains(path))) + File.Delete(journal); + } + } + finally + { + var completedWorkspace = workspace; + // Clear first so a cleanup exception cannot leak this case's partial topology into the next NUnit case. + workspace = null; + completedWorkspace?.Dispose(); + } + } + + protected void SelectSourceItem(string name) + { + // ADS and topology scenarios create sources after startup, so refresh before selecting from the owner-drawn list. + NativeCommands.RefreshLeftPanel(MainWindowHandle); + var list = MainWindow.FindAllDescendants() + .Where(element => string.Equals(element.Properties.ClassName.ValueOrDefault, "SalamanderItemsBox", StringComparison.Ordinal)) + .OrderBy(element => element.BoundingRectangle.Left) + .FirstOrDefault(); + Assert.That(list, Is.Not.Null, "The left file panel did not expose its SalamanderItemsBox control."); + NativeCommands.QuickSearch(list!.Properties.NativeWindowHandle.Value, name); + } + + protected void SelectSourceItems(params string[] names) + { + // Explicit Insert selection exercises commands over mixed and multiple items instead of only the focused fallback. + var list = FindSourceList(); + foreach (var name in names) + { + NativeCommands.QuickSearch(list.Properties.NativeWindowHandle.Value, name); + NativeCommands.ToggleFocusedSelection(list.Properties.NativeWindowHandle.Value); + } + } + + protected Window ExecuteWithPath(int command, string sourceName, string path, bool commit) + { + if (sourceName.Length != 0) + SelectSourceItem(sourceName); + NativeCommands.Execute(MainWindowHandle, command); + var dialog = WaitForOperationEntryDialog(); + SetDialogPath(dialog, path); + CloseDialog(dialog, commit); + return dialog; + } + + protected Window ExecuteAndWaitForDialog(int command, string sourceName) + { + SelectSourceItem(sourceName); + NativeCommands.Execute(MainWindowHandle, command); + return WaitForOperationEntryDialog(); + } + + protected Window WaitForOperationPrompt(int buttonId) + { + return WaitForWindow(window => + window.Properties.NativeWindowHandle.Value != MainWindowHandle && + NativeCommands.HasDialogControl(window.Properties.NativeWindowHandle.Value, buttonId)); + } + + protected static void ChooseOperationPrompt(Window dialog, int buttonId) + { + NativeCommands.ClickDialogButton(dialog.Properties.NativeWindowHandle.Value, buttonId); + WaitForWindowToClose(dialog); + } + + protected void SubmitInvalidPathAndCancel(int command, string sourceName, string path) + { + var operationDialog = ExecuteWithPathWithoutWaitingForClose(command, sourceName, path); + var operationDialogHandle = operationDialog.Properties.NativeWindowHandle.Value; + var failureDialog = WaitForWindow(window => + window.Properties.NativeWindowHandle.Value != MainWindowHandle && + window.Properties.NativeWindowHandle.Value != operationDialogHandle && + (NativeCommands.HasDialogControl(window.Properties.NativeWindowHandle.Value, 1) || + NativeCommands.HasDialogControl(window.Properties.NativeWindowHandle.Value, 2))); + // Error boxes use OK, while overwrite/collision confirmations are cancelled to preserve both filesystem items. + CloseDialog(failureDialog, commit: NativeCommands.HasDialogControl(failureDialog.Properties.NativeWindowHandle.Value, 1)); + // Some validation failures close the entry dialog themselves; cancel it only when its HWND remains alive. + if (NativeCommands.IsWindowAvailable(operationDialogHandle)) + CloseDialog(operationDialog, commit: false); + } + + protected void ConfirmDeleteIfPrompted() + { + DismissOptionalPrompt(6, TimeSpan.FromSeconds(3), 6); // IDYES + } + + protected void ConfirmCreateDirectoryParentsIfPrompted() + { + DismissOptionalPrompt(1, TimeSpan.FromSeconds(3), 1, 2); // IDOK with IDCANCEL distinguishes the missing-parent question. + } + + protected bool DismissOptionalPrompt(int buttonToClick, TimeSpan wait, params int[] requiredControls) + { + var timeout = DateTime.UtcNow + wait; + while (DateTime.UtcNow < timeout) + { + var dialog = GetFileManagerTopLevelWindows() + .FirstOrDefault(window => window.Properties.NativeWindowHandle.Value != MainWindowHandle && + requiredControls.All(controlId => + NativeCommands.HasDialogControl(window.Properties.NativeWindowHandle.Value, controlId))); + if (dialog is not null) + { + // Stable button IDs allow optional confirmations to vary by profile without translated-text coupling. + NativeCommands.ClickDialogButton(dialog.Properties.NativeWindowHandle.Value, buttonToClick); + return true; + } + Thread.Sleep(100); + } + return false; + } + + protected static void CloseDialog(Window dialog, bool commit) + { + NativeCommands.ClickDialogButton(dialog.Properties.NativeWindowHandle.Value, commit ? 1 : 2); + WaitForWindowToClose(dialog); + } + + protected static void WaitForFileSystem(Func predicate, string failureMessage) + { + var timeout = DateTime.UtcNow + TimeSpan.FromSeconds(20); + while (DateTime.UtcNow < timeout) + { + if (predicate()) + return; + Thread.Sleep(100); + } + + Assert.Fail(failureMessage); + } + + protected static void WaitForOperationJournalTerminal(string source, string failureMessage) + { + WaitForFileSystem(() => + { + var journal = FindOperationJournalFor(source); + if (journal is null || !TryReadOperationJournal(journal, out var content)) + return false; + // These are the four terminal states recognized by the native startup reconciler. + return content.Contains("OPERATION|completed", StringComparison.Ordinal) || + content.Contains("OPERATION|cancelled", StringComparison.Ordinal) || + content.Contains("OPERATION|failed", StringComparison.Ordinal) || + content.Contains("OPERATION|reconciled", StringComparison.Ordinal); + }, failureMessage); + } + + protected static string? FindOperationJournalFor(string source) + { + var directory = GetOperationJournalDirectory(); + if (!Directory.Exists(directory)) + return null; + + // Newest-first lookup binds completion checks to the disposable source path unique to this case. + return Directory.EnumerateFiles(directory, "*.opj") + .OrderByDescending(File.GetLastWriteTimeUtc) + .FirstOrDefault(path => TryReadOperationJournal(path, out var content) && + content.Contains(source, StringComparison.Ordinal)); + } + + protected static bool TryReadOperationJournal(string path, out string content) + { + try + { + // The application appends journal records while tests observe them, so readers must share writes and deletion. + using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, + FileShare.ReadWrite | FileShare.Delete); + using var reader = new StreamReader(stream); + content = reader.ReadToEnd(); + return true; + } + catch (IOException) + { + content = string.Empty; + return false; + } + catch (UnauthorizedAccessException) + { + content = string.Empty; + return false; + } + } + + protected Window WaitForDesktopWindow(Func predicate, string failureMessage) + { + // Editors run out of process, so desktop-level UIA is required to observe the configured editor window. + var timeout = DateTime.UtcNow + TimeSpan.FromSeconds(15); + while (DateTime.UtcNow < timeout) + { + var window = Automation.GetDesktop().FindAllChildren() + .Where(element => element.ControlType == ControlType.Window) + .Select(element => element.AsWindow()) + .FirstOrDefault(predicate); + if (window is not null) + return window; + + Thread.Sleep(100); + } + + Assert.Fail(failureMessage); + return null!; + } + + private FlaUI.Core.AutomationElements.AutomationElement FindSourceList() + { + // Panel ordering is stable even when translated labels are unavailable to UI Automation. + var list = MainWindow.FindAllDescendants() + .Where(element => string.Equals(element.Properties.ClassName.ValueOrDefault, "SalamanderItemsBox", StringComparison.Ordinal)) + .OrderBy(element => element.BoundingRectangle.Left) + .FirstOrDefault(); + Assert.That(list, Is.Not.Null, "The left file panel did not expose its SalamanderItemsBox control."); + return list!; + } + + private static void SetDialogPath(Window dialog, string path) + { + var input = dialog.FindAllDescendants() + .FirstOrDefault(element => element.ControlType is ControlType.Edit or ControlType.ComboBox); + Assert.That(input, Is.Not.Null, "The operation dialog did not expose its destination/name input."); + if (input!.ControlType == ControlType.ComboBox) + input.AsComboBox().Value = path; + else + input.AsTextBox().Text = path; + } + + private Window ExecuteWithPathWithoutWaitingForClose(int command, string sourceName, string path) + { + if (sourceName.Length != 0) + SelectSourceItem(sourceName); + NativeCommands.Execute(MainWindowHandle, command); + var dialog = WaitForOperationEntryDialog(); + SetDialogPath(dialog, path); + NativeCommands.ClickDialogButton(dialog.Properties.NativeWindowHandle.Value, 1); + return dialog; + } + + private Window WaitForOperationEntryDialog() + { + // Destination and name dialogs expose editable input plus standard OK/Cancel identities across languages. + return WaitForWindow(window => + window.Properties.NativeWindowHandle.Value != MainWindowHandle && + window.FindAllDescendants().Any(element => element.ControlType is ControlType.Edit or ControlType.ComboBox) && + NativeCommands.HasDialogControl(window.Properties.NativeWindowHandle.Value, 1) && + NativeCommands.HasDialogControl(window.Properties.NativeWindowHandle.Value, 2)); + } + + private static string GetOperationJournalDirectory() => Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Open Salamander", "operation-journals"); +} + +public sealed class FileOperationWorkspace : IDisposable +{ + public FileOperationWorkspace(string? targetVolumeRoot = null) + { + RootDirectory = Path.Combine(Path.GetTempPath(), "FileManager.UiTests", Guid.NewGuid().ToString("N")); + SourceDirectory = Path.Combine(RootDirectory, "source"); + TargetWorkspaceDirectory = targetVolumeRoot is null + ? RootDirectory + : Path.Combine(targetVolumeRoot, "FileManager.UiTests", Guid.NewGuid().ToString("N")); + TargetDirectory = Path.Combine(TargetWorkspaceDirectory, "target"); + Directory.CreateDirectory(SourceDirectory); + Directory.CreateDirectory(TargetDirectory); + + WriteSourceFile("copy-file.txt", "copy-file-content"); + WriteSourceFile("rename-file.txt", "rename-file-content"); + WriteSourceFile("rename-collision.txt", "collision-content"); + // Conflict, case-only, and multi-item seeds distinguish each native rename/move/delete branch. + WriteSourceFile("rename-case.txt", "rename-case-content"); + WriteSourceFile("move-file.txt", "move-file-content"); + WriteSourceFile("move-overwrite.txt", "move-overwrite-source-content"); + WriteSourceFile("move-skip.txt", "move-skip-source-content"); + WriteSourceFile("delete-file.txt", "delete-file-content"); + WriteSourceFile("delete-mixed-file.txt", "delete-mixed-file-content"); + WriteSourceFile("delete-z-after-skip.txt", "delete-after-skip-content"); + WriteSourceFile("cancel-copy.txt", "cancel-copy-content"); + WriteSourceFile("cancel-move.txt", "cancel-move-content"); + WriteSourceFile("cancel-rename.txt", "cancel-rename-content"); + WriteSourceFile("delete-locked.txt", "delete-locked-content"); + WriteSourceFile("overwrite-file.txt", "overwrite-source-content"); + WriteSourceFile("skip-file.txt", "skip-source-content"); + WriteSourceFile("cancel-conflict.txt", "cancel-conflict-source-content"); + WriteSourceFile("rename-overwrite.txt", "rename-overwrite-source-content"); + WriteSourceFile("recycle-file.txt", "recycle-file-content"); + WriteSourceFile("view-file.txt", "view-file-content"); + WriteSourceFile("edit-file.txt", "edit-file-content"); + WriteSourceFile(Path.Combine("find-tree", "find-target.txt"), "find-target-content"); + WriteSourceFile("create-collision", "create-collision-content"); + WriteSourceFile(Path.Combine("copy-tree", "nested", "payload.txt"), "copy-tree-content"); + WriteSourceFile(Path.Combine("rename-tree", "nested", "payload.txt"), "rename-tree-content"); + WriteSourceFile(Path.Combine("move-tree", "nested", "payload.txt"), "move-tree-content"); + WriteSourceFile(Path.Combine("move-overwrite-all-tree", "nested", "first.txt"), "move-overwrite-all-first-source"); + WriteSourceFile(Path.Combine("move-overwrite-all-tree", "nested", "second.txt"), "move-overwrite-all-second-source"); + WriteSourceFile(Path.Combine("move-skip-all-tree", "nested", "first.txt"), "move-skip-all-first-source"); + WriteSourceFile(Path.Combine("move-skip-all-tree", "nested", "second.txt"), "move-skip-all-second-source"); + WriteSourceFile(Path.Combine("move-skip-all-tree", "nested", "unique.txt"), "move-skip-all-unique-source"); + WriteSourceFile(Path.Combine("delete-tree", "nested", "payload.txt"), "delete-tree-content"); + WriteSourceFile(Path.Combine("delete-mixed-tree", "nested", "payload.txt"), "delete-mixed-tree-content"); + WriteSourceFile(Path.Combine("rename-collision-source", "payload.txt"), "rename-collision-source-content"); + WriteSourceFile(Path.Combine("rename-collision-target", "payload.txt"), "rename-collision-target-content"); + WriteSourceFile(Path.Combine("overwrite-all-tree", "nested", "first.txt"), "overwrite-all-first-source"); + WriteSourceFile(Path.Combine("overwrite-all-tree", "nested", "second.txt"), "overwrite-all-second-source"); + WriteSourceFile(Path.Combine("skip-all-tree", "nested", "first.txt"), "skip-all-first-source"); + WriteSourceFile(Path.Combine("skip-all-tree", "nested", "second.txt"), "skip-all-second-source"); + File.WriteAllText(TargetPath("overwrite-file.txt"), "overwrite-target-content"); + File.WriteAllText(TargetPath("skip-file.txt"), "skip-target-content"); + File.WriteAllText(TargetPath("cancel-conflict.txt"), "cancel-conflict-target-content"); + // Rename is panel-local, so its overwrite fixture must collide in the source panel rather than the opposite panel. + File.WriteAllText(SourcePath("rename-overwrite-target.txt"), "rename-overwrite-target-content"); + WriteTargetFile(Path.Combine("overwrite-all-tree", "nested", "first.txt"), "overwrite-all-first-target"); + WriteTargetFile(Path.Combine("overwrite-all-tree", "nested", "second.txt"), "overwrite-all-second-target"); + WriteTargetFile(Path.Combine("skip-all-tree", "nested", "first.txt"), "skip-all-first-target"); + WriteTargetFile(Path.Combine("skip-all-tree", "nested", "second.txt"), "skip-all-second-target"); + // Move-All targets expose whether copied sources are deleted only for committed items. + WriteTargetFile(Path.Combine("move-overwrite-all-tree", "nested", "first.txt"), "move-overwrite-all-first-target"); + WriteTargetFile(Path.Combine("move-overwrite-all-tree", "nested", "second.txt"), "move-overwrite-all-second-target"); + WriteTargetFile(Path.Combine("move-skip-all-tree", "nested", "first.txt"), "move-skip-all-first-target"); + WriteTargetFile(Path.Combine("move-skip-all-tree", "nested", "second.txt"), "move-skip-all-second-target"); + File.WriteAllText(Path.Combine(TargetDirectory, "blocked-target"), "not-a-directory"); + } + + public string RootDirectory { get; } + public string TargetWorkspaceDirectory { get; } + public string SourceDirectory { get; } + public string TargetDirectory { get; } + + public string SourcePath(string relativePath) => Path.Combine(SourceDirectory, relativePath); + public string TargetPath(string relativePath) => Path.Combine(TargetDirectory, relativePath); + + public FileStream HoldSourceFileOpen(string relativePath) => new(SourcePath(relativePath), FileMode.Open, FileAccess.Read, FileShare.None); + + public void Dispose() + { + if (Directory.Exists(RootDirectory)) + DeleteDirectoryTreeWithoutTraversingReparsePoints(RootDirectory); + if (!string.Equals(TargetWorkspaceDirectory, RootDirectory, StringComparison.OrdinalIgnoreCase) && + Directory.Exists(TargetWorkspaceDirectory)) + DeleteDirectoryTreeWithoutTraversingReparsePoints(TargetWorkspaceDirectory); + } + + private void WriteSourceFile(string relativePath, string content) + { + var path = SourcePath(relativePath); + Directory.CreateDirectory(Path.GetDirectoryName(path)!); + File.WriteAllText(path, content); + } + + private void WriteTargetFile(string relativePath, string content) + { + var path = TargetPath(relativePath); + Directory.CreateDirectory(Path.GetDirectoryName(path)!); + File.WriteAllText(path, content); + } + + private static void DeleteDirectoryTreeWithoutTraversingReparsePoints(string directory) + { + foreach (var entry in Directory.EnumerateFileSystemEntries(directory)) + { + var attributes = File.GetAttributes(entry); + if ((attributes & FileAttributes.Directory) != 0) + { + // A junction or directory symlink is a fixture entry, never a cleanup traversal boundary. + if ((attributes & FileAttributes.ReparsePoint) != 0) + Directory.Delete(entry); + else + DeleteDirectoryTreeWithoutTraversingReparsePoints(entry); + } + else + { + if ((attributes & FileAttributes.ReadOnly) != 0) + File.SetAttributes(entry, attributes & ~FileAttributes.ReadOnly); + File.Delete(entry); + } + } + + Directory.Delete(directory); + } +} diff --git a/tests/FileManager.UiTests/Infrastructure/NativeCommands.cs b/tests/FileManager.UiTests/Infrastructure/NativeCommands.cs index 328ad773..6dda2052 100644 --- a/tests/FileManager.UiTests/Infrastructure/NativeCommands.cs +++ b/tests/FileManager.UiTests/Infrastructure/NativeCommands.cs @@ -1,28 +1,347 @@ using System.Runtime.InteropServices; +using System.Text; namespace FileManager.UiTests.Infrastructure; internal static class NativeCommands { + private delegate bool EnumWindowsCallback(nint windowHandle, nint parameter); + // CM_CONFIGURATION is the stable native command used by the Options > Configuration menu item. internal const int Configuration = 686; + private const int LeftPanelRefresh = 724; + // CM_CUSTOMIZETOP opens the shared Customize Toolbar dialog without depending on translated menu text. + internal const int CustomizeTopToolbar = 804; + internal const int CopyFiles = 727; + internal const int MoveFiles = 728; + internal const int DeleteFiles = 729; + internal const int CreateDirectory = 730; + internal const int Open = 732; + // Preserve the upstream discovery/view/edit command surface with the stable native command identities. + internal const int FindFiles = 741; + internal const int ViewFile = 742; + internal const int EditFile = 743; + internal const int RenameFile = 754; + internal const int HelpSearch = 2212; + private const uint WmNull = 0x0000; + private const uint WmClose = 0x0010; + private const uint WmChar = 0x0102; + private const uint WmKeyDown = 0x0100; + private const uint WmKeyUp = 0x0101; private const uint WmCommand = 0x0111; + private const uint BmGetCheck = 0x00F0; + private const uint BmClick = 0x00F5; + private const int BstUnchecked = 0; + private const int BstChecked = 1; + private const uint CbGetCurSel = 0x0147; + private const uint CbSetCurSel = 0x014E; + private const uint LvmGetItemCount = 0x1004; + private const int CbnSelChange = 1; + private const uint WmApp = 0x8000; + private const uint WmUiTestReady = WmApp + 417; + private const uint WmUiTestCommand = WmApp + 418; + private const uint WmUiTestConfigGeneration = WmApp + 419; + private const uint WmUiTestConfigFault = WmApp + 420; + private const uint WmUiTestFtpOrganizeCommand = WmApp + 421; + private const uint TvmGetNextItem = 0x110A; + private const uint TvmSelectItem = 0x110B; + private const int TvgnRoot = 0; + private const int TvgnCaret = 9; + private const uint SmtoAbortIfHung = 0x0002; + private const int VkEscape = 0x1B; + private const int VkInsert = 0x2D; + private const int VkReturn = 0x0D; + + [DllImport("user32.dll", SetLastError = true)] + private static extern nint SendMessageTimeout(nint hWnd, uint msg, nint wParam, nint lParam, + uint flags, uint timeout, out nint result); [DllImport("user32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool PostMessage(nint hWnd, uint msg, nint wParam, nint lParam); - internal static void OpenConfiguration(nint windowHandle) + [DllImport("user32.dll", CharSet = CharSet.Unicode)] + private static extern nint SendMessage(nint hWnd, uint msg, nint wParam, nint lParam); + + [DllImport("user32.dll")] + private static extern nint SetFocus(nint hWnd); + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool EnumWindows(EnumWindowsCallback callback, nint parameter); + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool EnumChildWindows(nint parentHandle, EnumWindowsCallback callback, nint parameter); + + [DllImport("user32.dll")] + private static extern uint GetWindowThreadProcessId(nint windowHandle, out uint processId); + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool IsWindowVisible(nint windowHandle); + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool IsWindow(nint windowHandle); + + [DllImport("user32.dll", CharSet = CharSet.Unicode)] + private static extern int GetClassName(nint windowHandle, StringBuilder className, int maximumCount); + + [DllImport("user32.dll", CharSet = CharSet.Unicode)] + private static extern int GetWindowText(nint windowHandle, StringBuilder text, int maximumCount); + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool IsWindowEnabled(nint windowHandle); + + [DllImport("user32.dll")] + private static extern nint GetDlgItem(nint dialogHandle, int controlId); + + [DllImport("user32.dll")] + private static extern int GetDlgCtrlID(nint controlHandle); + + internal static void Execute(nint windowHandle, int command) { - // Native dispatch avoids hard-coded English menu labels while FlaUI validates the resulting dialog. - if (!PostMessage(windowHandle, WmCommand, Configuration, 0)) - throw new InvalidOperationException("Unable to post the Configuration command to FileManager."); + // The native test protocol acknowledges queueing without blocking this thread inside the resulting modal dialog. + if (SendMessageTimeout(windowHandle, WmUiTestCommand, command, 0, SmtoAbortIfHung, 2000, out var accepted) == 0 || + accepted == 0) + throw new InvalidOperationException($"FileManager did not accept command {command} after reporting startup readiness."); } - internal static void Execute(nint windowHandle, int command) + internal static void RefreshLeftPanel(nint windowHandle) + { + // Mode 1 is restricted natively to CM_LEFTREFRESH and returns only after the new panel listing is available. + if (SendMessageTimeout(windowHandle, WmUiTestCommand, LeftPanelRefresh, 1, SmtoAbortIfHung, 5000, out var refreshed) == 0 || + refreshed == 0) + throw new InvalidOperationException("FileManager did not synchronously refresh the source panel."); + } + + internal static bool IsStartupReady(nint windowHandle) + { + // A bounded synchronous query distinguishes a created main window from a fully initialized application. + return SendMessageTimeout(windowHandle, WmUiTestReady, 0, 0, SmtoAbortIfHung, 250, out var ready) != 0 && + ready != 0; + } + + internal static long GetConfigurationGeneration(nint windowHandle) + { + // The generation advances after the Configuration handler completes persistence and post-dialog refresh work. + return SendMessageTimeout(windowHandle, WmUiTestConfigGeneration, 0, 0, SmtoAbortIfHung, 2000, out var generation) != 0 + ? generation.ToInt64() + : -1; + } + + internal static void ArmNextConfigurationWriteFault(nint windowHandle, int writeBoundary) + { + // One-shot native arming prevents startup or cancel-dialog saves from consuming the requested boundary. + if (writeBoundary <= 0 || + SendMessageTimeout(windowHandle, WmUiTestConfigFault, writeBoundary, 0, SmtoAbortIfHung, 2000, out var armed) == 0 || + armed == 0) + throw new InvalidOperationException($"FileManager did not arm configuration write boundary {writeBoundary}."); + } + + internal static int GetFtpOrganizeBookmarksCommand(nint windowHandle) + { + // Ask the isolated process after start-up because plug-in SUIDs belong to its native menu allocation. + return SendMessageTimeout(windowHandle, WmUiTestFtpOrganizeCommand, 0, 0, SmtoAbortIfHung, 5000, out var command) != 0 + ? checked((int)command.ToInt64()) + : 0; + } + + internal static nint[] GetTopLevelWindowHandles(int processId) + { + return GetVisibleTopLevelWindowHandles() + .Where(windowHandle => + { + GetWindowThreadProcessId(windowHandle, out var ownerProcessId); + return ownerProcessId == (uint)processId; + }) + .ToArray(); + } + + internal static nint[] GetVisibleTopLevelWindowHandles() + { + var handles = new List(); + // Native enumeration retains Win32 dialogs that some UIA providers omit from desktop and application searches. + EnumWindows((windowHandle, _) => + { + if (IsWindowVisible(windowHandle)) + handles.Add(windowHandle); + return true; + }, 0); + return handles.ToArray(); + } + + internal static string GetWindowClassName(nint windowHandle) + { + var className = new StringBuilder(256); + // Native class lookup remains reliable for owner-drawn windows whose UIA ClassName property is unsupported. + return GetClassName(windowHandle, className, className.Capacity) > 0 ? className.ToString() : string.Empty; + } + + internal static string GetWindowTitle(nint windowHandle) + { + var title = new StringBuilder(512); + // Native title lookup avoids UIA property failures while classifying startup-only error dialogs. + return GetWindowText(windowHandle, title, title.Capacity) > 0 ? title.ToString() : string.Empty; + } + + internal static string GetMessageBoxText(nint dialogHandle) + { + var staticTextHandle = GetDlgItem(dialogHandle, -1); + // Standard message boxes expose their body through the IDC_STATIC control even when UIA is unresponsive. + return staticTextHandle != 0 ? GetWindowTitle(staticTextHandle) : string.Empty; + } + + internal static string DescribeChildControls(nint dialogHandle) + { + var controls = new List(); + // Native child inventory keeps startup failures diagnosable without invoking an unresponsive UIA provider. + EnumChildWindows(dialogHandle, (controlHandle, _) => + { + controls.Add($"id={GetDlgCtrlID(controlHandle)}, class='{GetWindowClassName(controlHandle)}', text='{GetWindowTitle(controlHandle)}'"); + return true; + }, 0); + return string.Join("; ", controls); + } + + internal static bool IsWindowAvailable(nint windowHandle) + { + // HWND lifetime is authoritative when UIA retains a stale element after a modal dialog closes. + return IsWindow(windowHandle); + } + + internal static bool IsWindowEnabledForInput(nint windowHandle) + { + // Win32 enabled state provides a stable readiness check after a modal dialog releases its owner. + return IsWindowEnabled(windowHandle); + } + + internal static bool IsWindowResponsive(nint windowHandle) + { + // WM_NULL has no side effects and proves the owning UI thread can still process synchronous messages. + return SendMessageTimeout(windowHandle, WmNull, 0, 0, SmtoAbortIfHung, 250, out _) != 0; + } + + internal static void RequestWindowClose(nint windowHandle) + { + // Tests close only windows they opened, avoiding leaked Help or editor processes after an assertion. + if (windowHandle != 0 && IsWindow(windowHandle)) + PostMessage(windowHandle, WmClose, 0, 0); + } + + internal static void ClickDialogButton(nint dialogHandle, int buttonId) + { + var buttonHandle = GetDlgItem(dialogHandle, buttonId); + // BM_CLICK follows the control's normal notification path when UIA's Invoke pattern is unavailable. + if (buttonHandle == 0 || !PostMessage(buttonHandle, BmClick, 0, 0)) + throw new InvalidOperationException($"Dialog did not accept button command {buttonId}."); + } + + internal static bool HasDialogControl(nint dialogHandle, int controlId) + { + // Resource IDs identify native controls without instantiating a potentially unresponsive UIA provider. + return GetDlgItem(dialogHandle, controlId) != 0; + } + + internal static bool IsDialogControlVisible(nint dialogHandle, int controlId) + { + // Visibility confirms that the requested property page, rather than a hidden child page, owns the control. + return FindDescendantControl(dialogHandle, controlId, requireVisible: true) != 0; + } + + internal static void SelectFirstTreePage(nint dialogHandle, int treeId) + { + var treeHandle = GetDlgItem(dialogHandle, treeId); + var rootItem = treeHandle == 0 ? 0 : SendMessage(treeHandle, TvmGetNextItem, TvgnRoot, 0); + // Selecting the root makes persisted-option tests independent of the user's remembered configuration page. + if (rootItem == 0 || SendMessage(treeHandle, TvmSelectItem, TvgnCaret, rootItem) == 0) + throw new InvalidOperationException("Configuration dialog did not expose a selectable General page."); + } + + internal static bool GetDialogCheckBoxState(nint dialogHandle, int checkBoxId) + { + var checkBoxHandle = FindDescendantControl(dialogHandle, checkBoxId, requireVisible: true); + if (checkBoxHandle == 0) + throw new InvalidOperationException($"Dialog did not expose visible check box {checkBoxId}."); + + // Native button state is authoritative when UIA returns a cached ToggleState after process restart. + return SendMessage(checkBoxHandle, BmGetCheck, 0, 0).ToInt64() switch + { + BstUnchecked => false, + BstChecked => true, + _ => throw new InvalidOperationException($"Dialog check box {checkBoxId} was indeterminate."), + }; + } + + internal static void ToggleDialogCheckBox(nint dialogHandle, int checkBoxId) + { + var checkBoxHandle = FindDescendantControl(dialogHandle, checkBoxId, requireVisible: true); + var originalState = GetDialogCheckBoxState(dialogHandle, checkBoxId); + // Synchronous BM_CLICK makes the subsequent state assertion independent of UIA event delivery. + SendMessage(checkBoxHandle, BmClick, 0, 0); + if (GetDialogCheckBoxState(dialogHandle, checkBoxId) == originalState) + throw new InvalidOperationException($"Dialog check box {checkBoxId} did not change after its native click."); + } + + private static nint FindDescendantControl(nint parentHandle, int controlId, bool requireVisible) + { + nint result = 0; + // Property-page controls are grandchildren of the outer dialog, so search the full native child hierarchy. + EnumChildWindows(parentHandle, (controlHandle, _) => + { + if (GetDlgCtrlID(controlHandle) != controlId || requireVisible && !IsWindowVisible(controlHandle)) + return true; + result = controlHandle; + return false; + }, 0); + return result; + } + + internal static void SelectComboBoxItem(nint dialogHandle, nint comboHandle, int index) + { + // Win32 CB_SETCURSEL does not notify the owner, so mirror a user choice with the required CBN_SELCHANGE command. + var selected = SendMessage(comboHandle, CbSetCurSel, index, 0).ToInt64(); + if (selected != index) + throw new InvalidOperationException($"Combo box did not select item {index}."); + + var command = (CbnSelChange << 16) | (GetDlgCtrlID(comboHandle) & 0xFFFF); + SendMessage(dialogHandle, WmCommand, command, comboHandle); + } + + internal static int GetComboBoxSelection(nint comboHandle) + { + // Query native state because UIA can retain a stale SelectionItem view after a Win32 notification. + return checked((int)SendMessage(comboHandle, CbGetCurSel, 0, 0).ToInt64()); + } + + internal static void QuickSearch(nint listHandle, string name) + { + SetFocus(listHandle); + SendMessage(listHandle, WmKeyDown, VkEscape, 0); + foreach (var character in name) + SendMessage(listHandle, WmChar, character, 1); + } + + internal static void ToggleFocusedSelection(nint listHandle) + { + // Insert is the native panel gesture that selects the focused item while preserving prior selections. + SetFocus(listHandle); + SendMessage(listHandle, WmKeyDown, VkInsert, 0); + } + + internal static int GetListViewItemCount(nint listHandle) + { + // This buffer-free list-view query is safe across processes and supports the virtual Find results control. + return checked((int)SendMessage(listHandle, LvmGetItemCount, 0, 0).ToInt64()); + } + + internal static void PressEnter(nint controlHandle) { - // UIA drives all assertions; this Win32 bridge only reaches commands whose host-assigned IDs have no stable UIA locator. - if (!PostMessage(windowHandle, WmCommand, command, 0)) - throw new InvalidOperationException($"Unable to post command {command} to FileManager."); + // Submit native Help search input without depending on the localized search-button caption. + SetFocus(controlHandle); + SendMessage(controlHandle, WmKeyDown, VkReturn, 0); + SendMessage(controlHandle, WmKeyUp, VkReturn, 0); } } diff --git a/tests/FileManager.UiTests/Infrastructure/ShellRecycleBin.cs b/tests/FileManager.UiTests/Infrastructure/ShellRecycleBin.cs new file mode 100644 index 00000000..3a7c106e --- /dev/null +++ b/tests/FileManager.UiTests/Infrastructure/ShellRecycleBin.cs @@ -0,0 +1,26 @@ +using System.Runtime.InteropServices; + +namespace FileManager.UiTests.Infrastructure; + +internal static class ShellRecycleBin +{ + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + private struct ShQueryRbInfo + { + public int cbSize; + public long i64Size; + public long i64NumItems; + } + + [DllImport("shell32.dll", CharSet = CharSet.Unicode)] + private static extern int SHQueryRecycleBin(string? rootPath, ref ShQueryRbInfo queryInfo); + + internal static long GetItemCount(string volumeRoot) + { + var info = new ShQueryRbInfo { cbSize = Marshal.SizeOf() }; + var result = SHQueryRecycleBin(volumeRoot, ref info); + if (result != 0) + throw new InvalidOperationException($"SHQueryRecycleBin failed for {volumeRoot} (0x{result:X8})."); + return info.i64NumItems; + } +} diff --git a/tests/FileManager.UiTests/Infrastructure/TestOwnedProcessScope.cs b/tests/FileManager.UiTests/Infrastructure/TestOwnedProcessScope.cs new file mode 100644 index 00000000..b8724704 --- /dev/null +++ b/tests/FileManager.UiTests/Infrastructure/TestOwnedProcessScope.cs @@ -0,0 +1,37 @@ +using System.Diagnostics; + +namespace FileManager.UiTests.Infrastructure; + +internal sealed class TestOwnedProcessScope : IDisposable +{ + private readonly string processName; + private readonly HashSet processIdsBefore; + + internal TestOwnedProcessScope(string processName) + { + this.processName = processName; + // PID identity ensures cleanup never terminates an editor that existed before the test command. + processIdsBefore = Process.GetProcessesByName(processName) + .Select(process => + { + using (process) + return process.Id; + }) + .ToHashSet(); + } + + public void Dispose() + { + foreach (var process in Process.GetProcessesByName(processName)) + { + using (process) + { + // A failed editor launch can remain headless, so post-command PID is the cleanup fallback when no HWND exists. + if (processIdsBefore.Contains(process.Id) || process.HasExited) + continue; + process.Kill(entireProcessTree: true); + process.WaitForExit(5000); + } + } + } +} diff --git a/tests/FileManager.UiTests/Infrastructure/UiTestSettings.cs b/tests/FileManager.UiTests/Infrastructure/UiTestSettings.cs index 2501c3ec..33f31a02 100644 --- a/tests/FileManager.UiTests/Infrastructure/UiTestSettings.cs +++ b/tests/FileManager.UiTests/Infrastructure/UiTestSettings.cs @@ -10,15 +10,6 @@ internal static class UiTestSettings internal static string Arguments => Environment.GetEnvironmentVariable("FILEMANAGER_UI_ARGUMENTS") ?? string.Empty; - internal static int RequireFtpOrganizeCommand() - { - // Plug-in menu command IDs are allocated by the host at runtime, so the isolated test runner supplies this ID. - if (!int.TryParse(Environment.GetEnvironmentVariable("FILEMANAGER_UI_FTP_ORGANIZE_COMMAND"), out var command) || command <= 0) - Assert.Ignore("Set FILEMANAGER_UI_FTP_ORGANIZE_COMMAND to the FTP plug-in's runtime Organize Bookmarks command ID to run FTP persistence tests."); - - return command; - } - internal static void RequireIsolatedProfile() { // UI tests save FileManager configuration, so prevent accidental execution against a developer profile. @@ -28,4 +19,72 @@ internal static void RequireIsolatedProfile() if (string.IsNullOrWhiteSpace(ExecutablePath) || !File.Exists(ExecutablePath)) Assert.Ignore("Set FILEMANAGER_UI_EXE to an existing FileManager executable before running UI tests."); } + + internal static void RequireConfigurationFaultInjection() + { + RequireIsolatedProfile(); + if (!string.Equals(Environment.GetEnvironmentVariable("FILEMANAGER_UI_CONFIG_FAULT_INJECTION"), "1", StringComparison.Ordinal)) + Assert.Ignore("Set FILEMANAGER_UI_CONFIG_FAULT_INJECTION=1 to run the exhaustive configuration write-boundary recovery test."); + } + + internal static int LimitConfigurationFaultBoundaries(int measuredCount) + { + // Optional smoke runs may cap the matrix; the unset default remains exhaustive for the dedicated recovery lane. + return int.TryParse(Environment.GetEnvironmentVariable("FILEMANAGER_UI_CONFIG_FAULT_BOUNDARY_LIMIT"), out var limit) && limit > 0 + ? Math.Min(measuredCount, limit) + : measuredCount; + } + + internal static void RequireZipPlugin() + { + RequireIsolatedProfile(); + // The opt-in confirms both deployment and enablement because a disabled plug-in is indistinguishable through the public UI. + if (!string.Equals(Environment.GetEnvironmentVariable("FILEMANAGER_UI_ZIP_PLUGIN"), "1", StringComparison.Ordinal)) + Assert.Ignore("Install and enable the Zip plug-in, then set FILEMANAGER_UI_ZIP_PLUGIN=1 to run ZIP navigation characterization."); + } + + internal static (string SearchTerm, string ExpectedResult) RequireHelpSearchFixture() + { + RequireIsolatedProfile(); + var term = Environment.GetEnvironmentVariable("FILEMANAGER_UI_HELP_SEARCH_TERM"); + var expected = Environment.GetEnvironmentVariable("FILEMANAGER_UI_HELP_EXPECTED_RESULT"); + if (string.IsNullOrWhiteSpace(term) || string.IsNullOrWhiteSpace(expected)) + Assert.Ignore("Deploy salamand.chm and set FILEMANAGER_UI_HELP_SEARCH_TERM plus FILEMANAGER_UI_HELP_EXPECTED_RESULT for its language."); + + return (term!, expected!); + } + + internal static string RequireCrossVolumeRoot() + { + var root = Environment.GetEnvironmentVariable("FILEMANAGER_UI_CROSS_VOLUME_ROOT"); + if (string.IsNullOrWhiteSpace(root) || !Directory.Exists(root)) + Assert.Ignore("Set FILEMANAGER_UI_CROSS_VOLUME_ROOT to an existing dedicated directory on a second volume to run cross-volume move characterization tests."); + + var sourceVolume = Path.GetPathRoot(Path.GetTempPath()); + var targetVolume = Path.GetPathRoot(Path.GetFullPath(root)); + if (string.Equals(sourceVolume, targetVolume, StringComparison.OrdinalIgnoreCase)) + Assert.Ignore("FILEMANAGER_UI_CROSS_VOLUME_ROOT must be on a different volume from the temporary directory."); + + return Path.GetFullPath(root); + } + + internal static string RequireUnsupportedAdsTargetRoot() + { + var root = Environment.GetEnvironmentVariable("FILEMANAGER_UI_ADS_UNSUPPORTED_TARGET_ROOT"); + if (string.IsNullOrWhiteSpace(root) || !Directory.Exists(root)) + Assert.Ignore("Set FILEMANAGER_UI_ADS_UNSUPPORTED_TARGET_ROOT to an existing dedicated directory on an ADS-unsupported volume to run this test."); + + var sourceVolume = Path.GetPathRoot(Path.GetTempPath()); + var targetVolume = Path.GetPathRoot(Path.GetFullPath(root)); + if (string.Equals(sourceVolume, targetVolume, StringComparison.OrdinalIgnoreCase)) + Assert.Ignore("FILEMANAGER_UI_ADS_UNSUPPORTED_TARGET_ROOT must be on a different volume from the temporary directory."); + + return Path.GetFullPath(root); + } + + internal static void RequireRecycleBinTest() + { + if (!string.Equals(Environment.GetEnvironmentVariable("FILEMANAGER_UI_RECYCLE_BIN"), "1", StringComparison.Ordinal)) + Assert.Ignore("Set FILEMANAGER_UI_RECYCLE_BIN=1 in an isolated profile with the default recycle-bin setting enabled to run this test."); + } } diff --git a/tests/FileManager.UiTests/NativeSafetyRegressionTests.cs b/tests/FileManager.UiTests/NativeSafetyRegressionTests.cs new file mode 100644 index 00000000..dc1028b6 --- /dev/null +++ b/tests/FileManager.UiTests/NativeSafetyRegressionTests.cs @@ -0,0 +1,1952 @@ +using NUnit.Framework; +using System.Text.RegularExpressions; + +namespace FileManager.UiTests; + +// These checks intentionally run without the executable. A deterministic +// name-swap race cannot be coordinated through the legacy UI, so this keeps +// the native handle-binding contract from being silently removed while the +// executable-level file-operation suite covers normal delete and overwrite +// behavior. +public sealed class NativeSafetyRegressionTests +{ + [Test] + public void Root_test_runner_collects_every_documented_automated_test_layer() + { + var root = FindRepositoryRoot(); + var runner = File.ReadAllText(Path.Combine(root, "runtests.ps1")); + var releaseWorkflow = File.ReadAllText(Path.Combine(root, ".github", "workflows", "build-installer.yml")); + + // The root command is the advertised entry point, so pin every + // independently executable probe and the complete NUnit/UI collector. + Assert.Multiple(() => + { + Assert.That(runner, Does.Contain("verify-operation-completion-protocol.ps1")); + Assert.That(runner, Does.Contain("verify-durable-copy-commit.ps1")); + Assert.That(runner, Does.Contain("test-zlib-compatibility.ps1")); + Assert.That(runner, Does.Contain("test-bzip2-compatibility.ps1")); + Assert.That(runner, Does.Contain("foreach ($architecture in @('x64', 'x86'))")); + Assert.That(runner, Does.Contain("test-cmark-gfm-hardening.ps1")); + Assert.That(runner, Does.Contain("test-sqlite-recovery.ps1")); + Assert.That(runner, Does.Contain("verify-no-new-terminatethread.ps1")); + Assert.That(runner, Does.Contain("verify-no-new-raw-thread-creation.ps1")); + Assert.That(runner, Does.Contain("verify-no-new-max-path-buffers.ps1")); + Assert.That(runner, Does.Contain("verify-no-new-unsafe-string-calls.ps1")); + Assert.That(runner, Does.Contain("verify-fluent-icon-coverage.ps1")); + Assert.That(runner, Does.Contain("FileManager.UiTests (complete NUnit project)")); + Assert.That(runner, Does.Contain("run-lock-verifier-stress.ps1")); + Assert.That(runner, Does.Contain("FailOnSkipped")); + Assert.That(runner, Does.Contain("@outcome='NotExecuted'")); + // The release workflow must consume the root inventory rather than + // maintaining a second list that can silently lose coverage. + Assert.That(releaseWorkflow, Does.Contain("name: Complete automated release gate")); + Assert.That(releaseWorkflow, Does.Contain(".\\runtests.ps1 -BaseCommit $env:RELEASE_BASE_COMMIT -SqliteDll $env:SQLITE_TEST_DLL -FailOnSkipped")); + Assert.That(releaseWorkflow, Does.Contain("needs: release-tests")); + Assert.That(releaseWorkflow, Does.Contain("fetch-depth: 0")); + Assert.That(releaseWorkflow, Does.Contain("FILEMANAGER_UI_CONFIG_FAULT_INJECTION: '1'")); + Assert.That(releaseWorkflow, Does.Contain("FILEMANAGER_UI_CROSS_VOLUME_ROOT")); + Assert.That(releaseWorkflow, Does.Contain("FILEMANAGER_UI_ADS_UNSUPPORTED_TARGET_ROOT")); + Assert.That(releaseWorkflow, Does.Contain("FILEMANAGER_UI_FTP_ORGANIZE_COMMAND")); + }); + } + + [Test] + public void Unchecked_string_calls_are_ratchet_gated_and_external_boundaries_report_capacity_and_encoding_failures() + { + var root = FindRepositoryRoot(); + var ratchet = File.ReadAllText(Path.Combine(root, "tools", "verify-no-new-unsafe-string-calls.ps1")); + var workflow = File.ReadAllText(Path.Combine(root, ".github", "workflows", "pr-msbuild.yml")); + var strings = File.ReadAllText(Path.Combine(root, "src", "common", "strutils.cpp")); + var declarations = File.ReadAllText(Path.Combine(root, "src", "common", "strutils.h")); + var startup = File.ReadAllText(Path.Combine(root, "src", "app_entry.cpp")); + var refactoring = File.ReadAllText(Path.Combine(root, "refactoring.md")); + + Assert.Multiple(() => + { + Assert.That(ratchet, Does.Contain("git diff --no-ext-diff --unified=0 $BaseCommit HEAD")); + Assert.That(ratchet, Does.Contain("'*.c' '*.cc' '*.cpp' '*.h' '*.hpp'")); + Assert.That(ratchet, Does.Contain("(?:strcpy|strcat|sprintf|lstrcpy")); + Assert.That(ratchet, Does.Contain("exit 1")); + Assert.That(workflow, Does.Contain("verify-no-new-unsafe-string-calls.ps1 -BaseCommit origin/")); + Assert.That(declarations, Does.Contain("enum EBoundedStringResult")); + Assert.That(declarations, Does.Contain("bsrTruncated")); + Assert.That(declarations, Does.Contain("bsrEncodingError")); + Assert.That(declarations, Does.Contain("FormatStringChecked")); + Assert.That(strings, Does.Contain("if (sourceLength >= destinationCapacity)"), + "A string exactly filling the payload capacity must leave room for its terminator."); + Assert.That(strings, Does.Contain("if ((size_t)required >= destinationCapacity)"), + "Formatting must reject a one-character overflow before writing a partial value."); + Assert.That(strings, Does.Contain("WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, source, -1, NULL, 0"), + "UTF-8 capacity is measured after encoding expansion, not from UTF-16 character count."); + Assert.That(strings, Does.Contain("if ((size_t)required > destinationCapacity)")); + Assert.That(strings, Does.Contain("ConvertWideToUtf8Checked(src.cFileName")); + Assert.That(startup, Does.Contain("FormatStringChecked(languageFileName + 1")); + Assert.That(startup, Does.Not.Contain("sprintf(strrchr(path, '\\\\') + 1")); + Assert.That(refactoring, Does.Contain("### 37. Ratchet unchecked string-copy and formatting calls — Implemented")); + }); + } + + [Test] + public void New_fixed_max_path_buffers_are_rejected_by_the_changed_lines_ci_ratchet() + { + var root = FindRepositoryRoot(); + var ratchet = File.ReadAllText(Path.Combine(root, "tools", "verify-no-new-max-path-buffers.ps1")); + var exemptions = File.ReadAllText(Path.Combine(root, "tools", "max-path-buffer-exemptions.md")); + var workflow = File.ReadAllText(Path.Combine(root, ".github", "workflows", "pr-msbuild.yml")); + var refactoring = File.ReadAllText(Path.Combine(root, "refactoring.md")); + + Assert.Multiple(() => + { + Assert.That(ratchet, Does.Contain("git diff --no-ext-diff --unified=0 $BaseCommit HEAD")); + Assert.That(ratchet, Does.Contain("'*.c' '*.cc' '*.cpp' '*.h' '*.hpp'")); + Assert.That(ratchet, Does.Contain("(?:char|WCHAR)")); + Assert.That(ratchet, Does.Contain("MAX_PATH-RATCHET-EXEMPT")); + Assert.That(ratchet, Does.Contain("Test-ApprovedExemption")); + Assert.That(ratchet, Does.Contain("- Reason:")); + Assert.That(ratchet, Does.Contain("- Removal:")); + Assert.That(ratchet, Does.Contain("exit 1")); + Assert.That(exemptions, Does.Contain("No exemptions are currently approved.")); + Assert.That(exemptions, Does.Contain("MAX_PATH-RATCHET-EXEMPT: ID")); + Assert.That(workflow, Does.Contain("verify-no-new-max-path-buffers.ps1 -BaseCommit origin/")); + Assert.That(refactoring, Does.Contain("### 36. Ban new fixed `MAX_PATH` buffers — Implemented")); + }); + } + + [Test] + public void Bundled_zlib_is_current_has_retained_compatibility_vectors_and_runs_in_ci() + { + var root = FindRepositoryRoot(); + var vendorRecord = File.ReadAllText(Path.Combine(root, "src", "common", "dep", "zlib", "VENDOR.md")); + var header = File.ReadAllText(Path.Combine(root, "src", "common", "dep", "zlib", "zlib.h")); + var project = File.ReadAllText(Path.Combine(root, "src", "vcxproj", "salamand.vcxproj")); + var probe = File.ReadAllText(Path.Combine(root, "tools", "zlib_compatibility_probe.c")); + var workflow = File.ReadAllText(Path.Combine(root, ".github", "workflows", "pr-msbuild.yml")); + var refactoring = File.ReadAllText(Path.Combine(root, "refactoring.md")); + + // Keep the vendor provenance, hostile streams, and build path coupled to the production C sources. + Assert.Multiple(() => + { + Assert.That(header, Does.Contain("#define ZLIB_VERSION \"1.3.2\"")); + Assert.That(vendorRecord, Does.Contain("e8bf55f3017aa181690990cb58a994e77885da140609fc8f94abe9b65d2cae28")); + Assert.That(vendorRecord, Does.Contain("every calendar quarter")); + Assert.That(vendorRecord, Does.Contain("security advisory or release")); + Assert.That(project, Does.Contain("..\\common\\dep\\zlib\\infback.c")); + Assert.That(project, Does.Contain("..\\common\\dep\\zlib\\uncompr.c")); + Assert.That(probe, Does.Contain("zlib 1.2.11 output")); + Assert.That(probe, Does.Contain("VerifyRejectedStream")); + Assert.That(File.Exists(Path.Combine(root, "tests", "zlib-vectors", "legacy-zlib-1.2.11.hex")), Is.True); + Assert.That(File.Exists(Path.Combine(root, "tests", "zlib-vectors", "truncated-zlib-stream.hex")), Is.True); + Assert.That(File.Exists(Path.Combine(root, "tests", "zlib-vectors", "bad-adler32-zlib-stream.hex")), Is.True); + Assert.That(File.Exists(Path.Combine(root, "tests", "zlib-vectors", "invalid-deflate-zlib-stream.hex")), Is.True); + Assert.That(workflow, Does.Contain("test-zlib-compatibility.ps1")); + Assert.That(refactoring, Does.Contain("### 63. Upgrade zlib — Implemented")); + }); + } + + [Test] + public void Bundled_bzip2_uses_the_verified_release_and_replays_archive_parser_regressions() + { + var root = FindRepositoryRoot(); + var vendorRecord = File.ReadAllText(Path.Combine(root, "src", "common", "dep", "bzip2", "VENDOR.md")); + var header = File.ReadAllText(Path.Combine(root, "src", "common", "dep", "bzip2", "bzlib.h")); + var decoder = File.ReadAllText(Path.Combine(root, "src", "common", "dep", "bzip2", "decompress.c")); + var project = File.ReadAllText(Path.Combine(root, "src", "vcxproj", "salamand.vcxproj")); + var adapter = File.ReadAllText(Path.Combine(root, "src", "salbzip2.cpp")); + var probe = File.ReadAllText(Path.Combine(root, "tools", "bzip2_compatibility_probe.c")); + var harness = File.ReadAllText(Path.Combine(root, "tools", "test-bzip2-compatibility.ps1")); + var workflow = File.ReadAllText(Path.Combine(root, ".github", "workflows", "pr-msbuild.yml")); + var refactoring = File.ReadAllText(Path.Combine(root, "refactoring.md")); + + // Pin the vendor identity, streaming adapter, retained corpus, and CI gate as one parser boundary. + Assert.Multiple(() => + { + Assert.That(header, Does.Contain("bzip2/libbzip2 version 1.0.8 of 13 July 2019")); + Assert.That(decoder, Does.Contain("if (groupNo >= nSelectors)")); + Assert.That(vendorRecord, Does.Contain("083f5e675d73f3233c7930ebe20425a533feedeaaa9d8cc86831312a6581cefbe6ed0d08d2fa89be81082f2a5abdabca8b3c080bf97218a1bd59dc118a30b9f3")); + Assert.That(vendorRecord, Does.Contain("every calendar quarter")); + Assert.That(project, Does.Contain("4244;%(DisableSpecificWarnings)")); + Assert.That(adapter, Does.Contain("BZ2_bzDecompress(strm)")); + Assert.That(adapter, Does.Contain("buffer ownership and result contract")); + Assert.That(probe, Does.Contain("1.0.8, 13-Jul-2019")); + Assert.That(probe, Does.Contain("BZ_STREAM_END")); + Assert.That(File.Exists(Path.Combine(root, "tests", "bzip2-vectors", "golden-stream.hex")), Is.True); + Assert.That(File.Exists(Path.Combine(root, "tests", "bzip2-vectors", "legacy-stream.hex")), Is.True); + Assert.That(File.Exists(Path.Combine(root, "tests", "bzip2-vectors", "truncated-stream.hex")), Is.True); + Assert.That(Directory.GetFiles(Path.Combine(root, "tests", "bzip2-vectors", "fuzz"), "*.hex").Length, Is.GreaterThanOrEqualTo(5)); + Assert.That(harness, Does.Contain("/DBZ_NO_STDIO")); + Assert.That(harness, Does.Contain("Get-ChildItem -LiteralPath (Join-Path $fixtureDirectory 'fuzz')")); + Assert.That(workflow, Does.Contain("test-bzip2-compatibility.ps1")); + Assert.That(refactoring, Does.Contain("### 64. Upgrade bzip2 — Implemented")); + }); + } + + [Test] + public void Trust_boundary_text_uses_bounded_owned_storage_and_explicit_capacity_failures() + { + var root = FindRepositoryRoot(); + var upload = File.ReadAllText(Path.Combine(root, "src", "salmon", "upload.cpp")); + var controlHeader = File.ReadAllText(Path.Combine(root, "src", "plugins", "ftp", "ctrlcon.h")); + var controlConnection = File.ReadAllText(Path.Combine(root, "src", "plugins", "ftp", "ctrlcon2.cpp")); + var registry = File.ReadAllText(Path.Combine(root, "src", "regwork.cpp")); + var minidump = File.ReadAllText(Path.Combine(root, "src", "salmon", "minidump.cpp")); + var salmonHeader = File.ReadAllText(Path.Combine(root, "src", "salmon", "salmon.h")); + var refactoring = File.ReadAllText(Path.Combine(root, "refactoring.md")); + + Assert.Multiple(() => + { + Assert.That(upload, Does.Contain("const size_t kMaximumResponseSize = 64 * 1024")); + // Checked addition rejects both integer wraparound and a response + // beyond the explicit cap before the owned buffer is extended. + Assert.That(upload, Does.Contain("!CheckedAddSize(response->size(), availableSize, &responseSize)")); + Assert.That(upload, Does.Contain("responseSize > kMaximumResponseSize")); + Assert.That(controlHeader, Does.Contain("CRTLCON_MAXIMUM_REPLY_SIZE (64 * 1024)")); + Assert.That(controlConnection, Does.Contain("if (newSize > CRTLCON_MAXIMUM_REPLY_SIZE)")); + Assert.That(controlConnection, Does.Contain("err = WSAEMSGSIZE")); + Assert.That(registry, Does.Contain("std::string ConfigurationWriteFaultReport")); + Assert.That(registry, Does.Contain("ConfigurationFaultEnvironmentMaximum = 32767")); + Assert.That(registry, Does.Contain("GetEnvironmentVariableA(name, NULL, 0)")); + Assert.That(registry, Does.Contain("return cferTooLarge")); + Assert.That(registry, Does.Contain("CreateFileA(ConfigurationWriteFaultReport.c_str()")); + Assert.That(minidump, Does.Contain("CopyBoundedExternalText")); + Assert.That(minidump, Does.Contain("memchr(source, 0, sourceCapacity)")); + Assert.That(minidump, Does.Contain("kMaximumCrashReportPathLength = 32767")); + Assert.That(minidump, Does.Contain("std::vector buffer(capacity)")); + Assert.That(minidump, Does.Contain("std::string dumpFileName")); + Assert.That(minidump, Does.Contain("ERROR_INVALID_DATA")); + Assert.That(minidump, Does.Not.Contain("char szFileName[MAX_PATH]")); + Assert.That(minidump, Does.Not.Contain("static char findPath[MAX_PATH]")); + Assert.That(salmonHeader, Does.Contain("int targetPathSize")); + Assert.That(salmonHeader, Does.Contain("int shortNameSize")); + Assert.That(refactoring, Does.Contain("### 38. Replace fixed buffers at trust boundaries first — Implemented")); + }); + } + + [Test] + public void External_size_fields_use_checked_arithmetic_before_allocation_or_io() + { + var root = FindRepositoryRoot(); + var arithmetic = File.ReadAllText(Path.Combine(root, "src", "common", "checked_arithmetic.h")); + var upload = File.ReadAllText(Path.Combine(root, "src", "salmon", "upload.cpp")); + var client = File.ReadAllText(Path.Combine(root, "src", "parserbroker.cpp")); + var broker = File.ReadAllText(Path.Combine(root, "src", "parserbroker", "salbroker.cpp")); + var refactoring = File.ReadAllText(Path.Combine(root, "refactoring.md")); + + // These assertions pin the overflow boundaries that adversarial file, + // HTTP, and IPC lengths must traverse before touching a buffer. + Assert.Multiple(() => + { + Assert.That(arithmetic, Does.Contain("CheckedAddUInt64")); + Assert.That(arithmetic, Does.Contain("CheckedMultiplyUInt64")); + Assert.That(arithmetic, Does.Contain("CheckedAddSize")); + Assert.That(arithmetic, Does.Contain("CheckedMultiplySize")); + Assert.That(arithmetic, Does.Contain("CheckedCastUInt64ToDword")); + Assert.That(arithmetic, Does.Contain("CheckedCastSizeToDword")); + Assert.That(arithmetic, Does.Contain("CheckedCastSizeToInt")); + Assert.That(upload, Does.Contain("CheckedCastDwordToSize(available, &availableSize)")); + Assert.That(upload, Does.Contain("CheckedAddSize(response->size(), availableSize, &responseSize)")); + Assert.That(upload, Does.Contain("CheckedCastSizeToDword(strlen(multipartPrefix), &multipartPrefixLength)")); + Assert.That(upload, Does.Contain("CheckedCastUInt64ToDword(remaining, &bytesToRead)")); + Assert.That(upload, Does.Contain("CheckedCastSizeToInt(response.size(), &responseLength)")); + Assert.That(client, Does.Contain("CheckedMultiplyUInt64((uint64_t)(chars - 1), (uint64_t)sizeof(WCHAR), &pathBytes64)")); + Assert.That(client, Does.Contain("CheckedAddDword(prefixLength, pathBytes, &totalPayloadLength)")); + Assert.That(client, Does.Contain("CheckedMultiplyUInt64(thumbnail->Width, thumbnail->Height, &expectedPixelBytes)")); + Assert.That(client, Does.Contain("CheckedAddDword((DWORD)sizeof(*thumbnail), thumbnail->PixelBytes, &expectedResponseLength)")); + Assert.That(broker, Does.Contain("CheckedMultiplyDword((DWORD)bitmapInfo.bmWidth, (DWORD)bitmapInfo.bmHeight, &pixels)")); + Assert.That(broker, Does.Contain("CheckedAddDword((DWORD)sizeof(CParserBrokerThumbnailResponse), pixelBytes, &packedResponseLength)")); + Assert.That(refactoring, Does.Contain("### 39. Use checked arithmetic for sizes, offsets, and allocations — Partially implemented")); + }); + } + + [Test] + public void Transactional_copy_results_preserve_phase_error_paths_retryability_and_partial_effects_for_legacy_dialogs() + { + var root = FindRepositoryRoot(); + var result = File.ReadAllText(Path.Combine(root, "src", "operation_result.h")); + var copy = File.ReadAllText(Path.Combine(root, "src", "async_copy.cpp")); + var refactoring = File.ReadAllText(Path.Combine(root, "refactoring.md")); + + // The native worker keeps the complete outcome until this adapter feeds an + // unchanged BOOL/error pair to the pre-existing progress dialog. + Assert.Multiple(() => + { + Assert.That(result, Does.Contain("enum EOperationResultPhase")); + Assert.That(result, Does.Contain("orpVerifyDurableCopy")); + Assert.That(result, Does.Contain("orpVerifyDestinationIdentity")); + Assert.That(result, Does.Contain("orpCommitTransactionalTarget")); + Assert.That(result, Does.Contain("DWORD Win32Error")); + Assert.That(result, Does.Contain("HRESULT HResult")); + Assert.That(result, Does.Contain("const char* Source")); + Assert.That(result, Does.Contain("const char* Destination")); + Assert.That(result, Does.Contain("BOOL Retryable")); + Assert.That(result, Does.Contain("DWORD PartialEffects")); + Assert.That(result, Does.Contain("opeTemporaryTargetReady")); + Assert.That(result, Does.Contain("opeDestinationCommitted")); + Assert.That(result, Does.Contain("HRESULT_FROM_WIN32(error)")); + Assert.That(result, Does.Contain("enum EOperationCleanupPhase")); + Assert.That(result, Does.Contain("COperationCleanupError CleanupErrors[2]")); + Assert.That(result, Does.Contain("void AppendCleanupError")); + Assert.That(result, Does.Contain("void BuildDiagnosticSummary")); + Assert.That(result, Does.Contain("Cleanup is secondary evidence")); + Assert.That(result, Does.Contain("BOOL ToLegacyBool(DWORD* error) const")); + Assert.That(copy, Does.Contain("static COperationResult CommitTransactionalTargetFile")); + Assert.That(copy, Does.Contain("static COperationResult VerifyDurableCopyCommit")); + Assert.That(copy, Does.Contain("COperationResult verificationResult = VerifyDurableCopyCommit")); + Assert.That(copy, Does.Contain("while (!verificationResult.ToLegacyBool(&verificationError))")); + Assert.That(copy, Does.Contain("COperationResult commitResult = CommitTransactionalTargetFile")); + Assert.That(copy, Does.Contain("while (!commitResult.ToLegacyBool(&err))")); + Assert.That(refactoring, Does.Contain("### 40. Make operation result types explicit — Partially implemented")); + }); + } + + [Test] + public void File_operation_failures_capture_the_primary_error_before_cleanup_and_offer_copyable_context() + { + var root = FindRepositoryRoot(); + var result = File.ReadAllText(Path.Combine(root, "src", "operation_result.h")); + var copy = File.ReadAllText(Path.Combine(root, "src", "async_copy.cpp")); + var refactoring = File.ReadAllText(Path.Combine(root, "refactoring.md")); + + // Keep the primary cause, cleanup evidence, and copyable dialog text coupled at the native boundary. + Assert.Multiple(() => + { + Assert.That(result, Does.Contain("orcpCloseVerificationHandle")); + Assert.That(result, Does.Contain("orcpDeleteUnverifiedTarget")); + Assert.That(result, Does.Contain("CleanupErrorCount < _countof(CleanupErrors)")); + Assert.That(result, Does.Contain("BuildDiagnosticSummary")); + Assert.That(copy, Does.Contain("Capture the verification result before CloseHandle can replace GetLastError.")); + Assert.That(copy, Does.Contain("result.AppendCleanupError(orcpCloseVerificationHandle, cleanupError, targetName)")); + Assert.That(copy, Does.Contain("verificationResult.AppendCleanupError(orcpDeleteUnverifiedTarget, err, op->TargetName)")); + Assert.That(copy, Does.Contain("Diagnostic (copy with Ctrl+C):")); + Assert.That(refactoring, Does.Contain("### 56. Preserve the first actionable error and its context — Partially implemented")); + }); + } + + [Test] + public void Kernel_handle_ownership_is_scoped_and_preserves_legacy_close_failures() + { + var root = FindRepositoryRoot(); + var scopedHandle = File.ReadAllText(Path.Combine(root, "src", "common", "scoped_kernel_handle.h")); + var identity = File.ReadAllText(Path.Combine(root, "src", "file_identity.cpp")); + var refactoring = File.ReadAllText(Path.Combine(root, "refactoring.md")); + + // These source checks pin the RAII seam that protects verified delete + // handles across identity mismatch, mutation failure, and future returns. + Assert.Multiple(() => + { + Assert.That(scopedHandle, Does.Contain("class CScopedKernelHandle")); + Assert.That(scopedHandle, Does.Contain("CScopedKernelHandle(const CScopedKernelHandle&)")); + Assert.That(scopedHandle, Does.Contain("HANDLE Release()")); + Assert.That(scopedHandle, Does.Contain("void Reset(HANDLE handle = INVALID_HANDLE_VALUE)")); + Assert.That(scopedHandle, Does.Contain("BOOL Close(DWORD* error)")); + Assert.That(scopedHandle, Does.Contain("HANDLES(CloseHandle(handle))")); + Assert.That(scopedHandle, Does.Contain("const DWORD error = GetLastError()")); + Assert.That(scopedHandle, Does.Contain("SetLastError(error)")); + Assert.That(identity, Does.Contain("#include \"common/scoped_kernel_handle.h\"")); + Assert.That(identity, Does.Contain("CScopedKernelHandle handle(HANDLES_Q(CreateFileUtf8")); + Assert.That(identity, Does.Contain("CScopedKernelHandle* handle, DWORD* error")); + Assert.That(identity, Does.Contain("handle->Reset(HANDLES_Q(CreateFileUtf8")); + Assert.That(identity, Does.Contain("handle->Get()")); + Assert.That(identity, Does.Contain("handle.Close(&closeError)")); + Assert.That(identity, Does.Not.Contain("CloseHandle(handle)")); + Assert.That(refactoring, Does.Contain("### 41. Adopt RAII for kernel handles in touched code — Implemented")); + }); + } + + [Test] + public void Scoped_native_resources_protect_file_operations_and_plugin_boundaries() + { + var root = FindRepositoryRoot(); + var resources = File.ReadAllText(Path.Combine(root, "src", "common", "scoped_native_resources.h")); + var copy = File.ReadAllText(Path.Combine(root, "src", "async_copy.cpp")); + var broker = File.ReadAllText(Path.Combine(root, "src", "parserbroker.cpp")); + var scripts = File.ReadAllText(Path.Combine(root, "src", "plugins", "automation", "scriptlist.cpp")); + var refactoring = File.ReadAllText(Path.Combine(root, "refactoring.md")); + + // These source checks pin cleanup at operation and plug-in seams where + // future returns or callbacks would otherwise bypass a manual pair. + Assert.Multiple(() => + { + Assert.That(resources, Does.Contain("class CScopedHeapBuffer")); + Assert.That(resources, Does.Contain("class CScopedMappingView")); + Assert.That(resources, Does.Contain("class CScopedCriticalSection")); + Assert.That(resources, Does.Contain("CScopedHeapBuffer(const CScopedHeapBuffer&)")); + Assert.That(resources, Does.Contain("CScopedMappingView(const CScopedMappingView&)")); + Assert.That(resources, Does.Contain("CScopedCriticalSection(const CScopedCriticalSection&)")); + Assert.That(resources, Does.Contain("free(Buffer)")); + Assert.That(resources, Does.Contain("UnmapViewOfFile(View)")); + Assert.That(resources, Does.Contain("EnterCriticalSection(CriticalSection)")); + Assert.That(resources, Does.Contain("LeaveCriticalSection(CriticalSection)")); + Assert.That(resources, Does.Contain("const DWORD error = GetLastError()")); + Assert.That(copy, Does.Contain("CScopedHeapBuffer inputBuffer(malloc(ASYNC_COPY_BUF_SIZE))")); + Assert.That(copy, Does.Contain("CScopedHeapBuffer outputBuffer(malloc(ASYNC_COPY_BUF_SIZE))")); + Assert.That(copy, Does.Not.Contain("free(bufIn);")); + Assert.That(copy, Does.Not.Contain("free(bufOut);")); + Assert.That(broker, Does.Contain("CScopedCriticalSection lock(&Lock, lkrExternalBroker, \"ParserBroker.Lock\")")); + Assert.That(broker, Does.Not.Contain("LeaveCriticalSection(&Lock);")); + Assert.That(scripts, Does.Contain("CScopedMappingView codeView(MapViewOfFile")); + Assert.That(scripts, Does.Not.Contain("UnmapViewOfFile(pszCodeA)")); + Assert.That(refactoring, Does.Contain("### 42. Adopt RAII for memory, mappings, and critical sections — Implemented")); + }); + } + + [Test] + public void Lock_ordering_has_rank_assertions_timeout_diagnostics_and_a_nightly_verifier_lane() + { + var root = FindRepositoryRoot(); + var ordering = File.ReadAllText(Path.Combine(root, "src", "common", "lock_ordering.cpp")); + var orderingHeader = File.ReadAllText(Path.Combine(root, "src", "common", "lock_ordering.h")); + var scopedResources = File.ReadAllText(Path.Combine(root, "src", "common", "scoped_native_resources.h")); + var broker = File.ReadAllText(Path.Combine(root, "src", "parserbroker.cpp")); + var project = File.ReadAllText(Path.Combine(root, "src", "vcxproj", "salamand.vcxproj")); + var architecture = File.ReadAllText(Path.Combine(root, "architecture.md")); + var workflow = File.ReadAllText(Path.Combine(root, ".github", "workflows", "nightly-lock-stress.yml")); + var stressRunner = File.ReadAllText(Path.Combine(root, "tools", "run-lock-verifier-stress.ps1")); + var refactoring = File.ReadAllText(Path.Combine(root, "refactoring.md")); + + // Pin the wrapper, implementation, build registration, runtime lane, and ledger so the rank scheme cannot become documentation-only. + Assert.Multiple(() => + { + Assert.That(orderingHeader, Does.Contain("enum CLockRank")); + Assert.That(orderingHeader, Does.Contain("lkrExternalBroker = 70")); + Assert.That(orderingHeader, Does.Contain("void LockOrderEnter")); + Assert.That(scopedResources, Does.Contain("LockOrderEnter(CriticalSection, Rank, LockName)")); + Assert.That(scopedResources, Does.Contain("LockOrderLeave(CriticalSection, Rank, LockName)")); + Assert.That(ordering, Does.Contain("_ASSERTE(rank > LockOrderStack[LockOrderStackCount - 1].Rank)")); + Assert.That(ordering, Does.Contain("IsRecursiveAcquisition(criticalSection, rank)")); + Assert.That(ordering, Does.Contain("TryEnterCriticalSection(criticalSection)")); + Assert.That(ordering, Does.Contain("GetTickCount64() - waitStarted >= timeoutMilliseconds")); + Assert.That(ordering, Does.Contain("LockOrderStack[LockOrderStackCount - 1]")); + Assert.That(ordering, Does.Contain("EnterCriticalSection(criticalSection)")); + Assert.That(ordering, Does.Contain("waiter=%lu")); + Assert.That(ordering, Does.Contain("owner=%lu")); + Assert.That(ordering, Does.Contain("OutputDebugStringA(message)")); + Assert.That(broker, Does.Contain("lkrExternalBroker, \"ParserBroker.Lock\"")); + Assert.That(project, Does.Contain("..\\common\\lock_ordering.cpp")); + Assert.That(architecture, Does.Contain("### 7.1 Lock ordering contract")); + Assert.That(workflow, Does.Contain("tools\\run-lock-verifier-stress.ps1")); + Assert.That(workflow, Does.Contain("self-hosted")); + // The verifier lane must retain the full diagnostic layers rather than regress to lock-only coverage. + Assert.That(stressRunner, Does.Contain("-enable @layers -for")); + Assert.That(stressRunner, Does.Contain("@('Heaps', 'Handles', 'Locks', 'Exceptions')")); + Assert.That(stressRunner, Does.Contain("/p /enable $targetName /full")); + Assert.That(stressRunner, Does.Contain("-delete settings -for")); + Assert.That(stressRunner, Does.Contain("TestCategory=LockStress")); + Assert.That(stressRunner, Does.Contain("finally")); + Assert.That(stressRunner, Does.Contain("LogOutputDirectory")); + Assert.That(refactoring, Does.Contain("### 47. Document and verify lock ordering — Partially implemented")); + }); + } + + [Test] + public void Thread_owners_keep_worker_lifetime_stop_completion_naming_com_and_exception_policy_together() + { + var root = FindRepositoryRoot(); + var owner = File.ReadAllText(Path.Combine(root, "src", "common", "thread_owner.h")); + var checkPath = File.ReadAllText(Path.Combine(root, "src", "path_checking.cpp")); + var ratchet = File.ReadAllText(Path.Combine(root, "tools", "verify-no-new-raw-thread-creation.ps1")); + var workflow = File.ReadAllText(Path.Combine(root, ".github", "workflows", "pr-msbuild.yml")); + var refactoring = File.ReadAllText(Path.Combine(root, "refactoring.md")); + + // These source contracts preserve the worker boundary where shutdown and + // completion ordering cannot be deterministically driven through the UI. + Assert.Multiple(() => + { + Assert.That(owner, Does.Contain("class CThreadOwner")); + Assert.That(owner, Does.Contain("CThreadOwnerEntry")); + Assert.That(owner, Does.Contain("_beginthreadex")); + Assert.That(owner, Does.Contain("StopEvent")); + Assert.That(owner, Does.Contain("CompletionEvent")); + Assert.That(owner, Does.Contain("SetThreadNameInVCAndTrace")); + Assert.That(owner, Does.Contain("CoInitializeEx")); + Assert.That(owner, Does.Contain("CoUninitialize")); + Assert.That(owner, Does.Contain("catch (...)")); + Assert.That(owner, Does.Contain("SetEvent(launch->CompletionEvent)")); + Assert.That(owner, Does.Contain("StopAndJoin(INFINITE)")); + Assert.That(owner, Does.Contain("caller keeps parameter alive")); + Assert.That(checkPath, Does.Contain("CThreadOwner ThreadCheckPath")); + Assert.That(checkPath, Does.Contain("ThreadCheckPathOwnedF")); + Assert.That(checkPath, Does.Contain("StopAndJoin(CThreadShutdownDeadline(\"check-path worker\"))")); + Assert.That(checkPath, Does.Not.Contain("CreateThread(")); + Assert.That(checkPath, Does.Not.Contain("SetThreadNameInVCAndTrace(\"CheckPath\")")); + Assert.That(ratchet, Does.Contain("git diff --no-ext-diff --unified=0 $BaseCommit HEAD")); + Assert.That(ratchet, Does.Contain("CThreadOwner")); + Assert.That(ratchet, Does.Contain("CreateThread|_beginthreadex")); + Assert.That(workflow, Does.Contain("verify-no-new-raw-thread-creation.ps1 -BaseCommit origin/")); + Assert.That(refactoring, Does.Contain("### 43. Standardize thread creation and ownership — Partially implemented")); + }); + } + + [Test] + public void Crash_report_compression_uses_the_restricted_loader_owned_worker_and_shutdown_contract() + { + var root = FindRepositoryRoot(); + var compression = File.ReadAllText(Path.Combine(root, "src", "salmon", "compress.cpp")); + var compressionHeader = File.ReadAllText(Path.Combine(root, "src", "salmon", "compress.h")); + var dialog = File.ReadAllText(Path.Combine(root, "src", "salmon", "dialogs.cpp")); + + // This crash-path contract prevents diagnostics from depending on CWD, + // raw thread handles, or an unbounded worker during dialog teardown. + Assert.Multiple(() => + { + Assert.That(compression, Does.Contain("CWidePath wideWrapperPath")); + Assert.That(compression, Does.Contain("LoadLibraryExW(fullPath, NULL, loadFlags)")); + Assert.That(compression, Does.Contain("LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR")); + Assert.That(compression, Does.Contain("LOAD_LIBRARY_SEARCH_SYSTEM32")); + Assert.That(compression, Does.Contain("LOAD_LIBRARY_SEARCH_USER_DIRS")); + Assert.That(compression, Does.Contain("CThreadOwner Thread")); + Assert.That(compression, Does.Contain("CThreadShutdownDeadline(\"crash-report compression\")")); + Assert.That(compression, Does.Contain("CorrelationId")); + Assert.That(compression, Does.Contain("GetModuleFileNameOwned")); + Assert.That(compression, Does.Contain("std::string archive")); + Assert.That(compression, Does.Not.Contain("HCompressThread")); + Assert.That(Regex.Matches(compression, @"(?m)^(?!\s*//).*?\b(?:CreateThread|SetCurrentDirectory|strcpy|strcat)\s*\(").Count, Is.Zero); + Assert.That(compressionHeader, Does.Contain("void StopCompressThread()")); + Assert.That(dialog, Does.Contain("StopCompressThread();")); + }); + } + + [Test] + public void Shared_plugin_thread_queue_uses_owned_cooperative_shutdown_without_forced_termination() + { + var root = FindRepositoryRoot(); + var queue = File.ReadAllText(Path.Combine(root, "src", "plugins", "shared", "auxtools.cpp")); + var queueHeader = File.ReadAllText(Path.Combine(root, "src", "plugins", "shared", "auxtools.h")); + var owner = File.ReadAllText(Path.Combine(root, "src", "plugins", "shared", "plugin_thread_owner.h")); + var ftpConsumer = File.ReadAllText(Path.Combine(root, "src", "plugins", "ftp", "fs1.cpp")); + var ratchet = File.ReadAllText(Path.Combine(root, "tools", "verify-no-new-raw-thread-creation.ps1")); + + // Pin the executable cooperative-shutdown contract without coupling safety coverage to editable documentation prose. + Assert.Multiple(() => + { + Assert.That(queueHeader, Does.Contain("CPluginThreadOwner* Owner")); + Assert.That(queueHeader, Does.Contain("CThreadQueueStopBody")); + Assert.That(queueHeader, Does.Contain("CScopedQueueLock")); + Assert.That(owner, Does.Contain("class CPluginThreadOwner")); + Assert.That(owner, Does.Contain("StopEvent")); + Assert.That(owner, Does.Contain("CompletionEvent")); + Assert.That(owner, Does.Contain("catch (...)")); + Assert.That(owner, Does.Contain("StopAndJoin(INFINITE)")); + Assert.That(queue, Does.Contain("CPluginThreadShutdownDeadline")); + Assert.That(queue, Does.Contain("WaitForSafeJoin(thread)")); + Assert.That(queue, Does.Contain("GetTickCount64()")); + Assert.That(queue, Does.Contain("SetEvent(data->Accepted)")); + Assert.That(queue, Does.Not.Contain("TerminateThread(")); + Assert.That(queue, Does.Not.Contain("Sleep(")); + Assert.That(queue, Does.Not.Contain("CreateThread(")); + Assert.That(ftpConsumer, Does.Contain("CThreadQueue AuxThreadQueue(\"FTP Aux\")")); + Assert.That(ftpConsumer, Does.Contain("AuxThreadQueue.KillAll(TRUE, 0, 0)")); + Assert.That(ratchet, Does.Contain("src/plugins/shared/plugin_thread_owner.h")); + }); + } + + [Test] + public void Seven_zip_task_dispatch_owns_worker_completion_cancellation_and_progress_subclass_lifetime() + { + var root = FindRepositoryRoot(); + var threads = File.ReadAllText(Path.Combine(root, "src", "plugins", "7zip", "7zthreads.cpp")); + var pluginOwner = File.ReadAllText(Path.Combine(root, "src", "plugins", "shared", "plugin_thread_owner.h")); + + // Archive callbacks synchronously consult the dialog, so keep their + // message pump while pinning the owned asynchronous completion boundary. + Assert.Multiple(() => + { + Assert.That(threads, Does.Contain("CPluginThreadOwner Worker")); + Assert.That(threads, Does.Contain("WM_7ZIP_TASKCOMPLETE")); + Assert.That(threads, Does.Contain("PostMessage(operation->ProgressWindow, WM_7ZIP, WM_7ZIP_TASKCOMPLETE")); + Assert.That(threads, Does.Contain("CProgressDialogSubclassScope")); + Assert.That(threads, Does.Contain("SetWindowLongPtr(Window, GWLP_WNDPROC, (LONG_PTR)PreviousProcedure)")); + Assert.That(threads, Does.Contain("RequestCancellation")); + Assert.That(threads, Does.Contain("SEVEN_ZIP_TASK_CANCEL_DEADLINE")); + Assert.That(threads, Does.Contain("continuing to pump until its owned completion arrives")); + Assert.That(threads, Does.Not.Contain("::CreateThread(")); + Assert.That(threads, Does.Not.Contain("MsgWaitForMultipleObjects(1, &hThread, FALSE, INFINITE")); + Assert.That(pluginOwner, Does.Contain("class CPluginThreadOwner")); + }); + } + + [Test] + public void Shutdown_deadlines_report_named_phases_and_preserve_shared_state_until_safe_join() + { + var root = FindRepositoryRoot(); + var owner = File.ReadAllText(Path.Combine(root, "src", "common", "thread_owner.h")); + var checkPath = File.ReadAllText(Path.Combine(root, "src", "path_checking.cpp")); + var cache = File.ReadAllText(Path.Combine(root, "src", "cache.cpp")); + var icons = File.ReadAllText(Path.Combine(root, "src", "fileswindow_init.cpp")); + var snooper = File.ReadAllText(Path.Combine(root, "src", "snooper.cpp")); + var callStack = File.ReadAllText(Path.Combine(root, "src", "callstk.cpp")); + var auxiliary = File.ReadAllText(Path.Combine(root, "src", "path_utils.cpp")); + var appEntry = File.ReadAllText(Path.Combine(root, "src", "app_entry.cpp")); + var refactoring = File.ReadAllText(Path.Combine(root, "refactoring.md")); + + // These contracts make stalled shutdown observable without letting a + // caller release shared state while a legacy worker can still use it. + Assert.Multiple(() => + { + Assert.That(owner, Does.Contain("class CThreadShutdownDeadline")); + Assert.That(owner, Does.Contain("DWORD cancellationDeadline = 5000")); + Assert.That(owner, Does.Contain("DWORD recoveryDeadline = 30000")); + Assert.That(owner, Does.Contain("TraceDeadlineBreach(\"cancellation\"")); + Assert.That(owner, Does.Contain("TraceDeadlineBreach(\"operation recovery\"")); + Assert.That(owner, Does.Contain("GetExitCodeThread(worker, &exitCode)")); + Assert.That(owner, Does.Contain("Keeping the process alive for a safe join.")); + Assert.That(owner, Does.Contain("WaitForSingleObject(worker, INFINITE)")); + Assert.That(owner, Does.Contain("StopAndJoin(const CThreadShutdownDeadline& deadline)")); + Assert.That(checkPath, Does.Contain("CThreadShutdownDeadline(\"check-path worker\")")); + Assert.That(cache, Does.Contain("CThreadShutdownDeadline(\"cache-handles worker\")")); + Assert.That(icons, Does.Contain("CThreadShutdownDeadline(\"panel icon reader\")")); + Assert.That(snooper, Does.Contain("CThreadShutdownDeadline(\"directory snooper\")")); + Assert.That(snooper, Does.Contain("CThreadShutdownDeadline(\"safe notification-handle closer\")")); + Assert.That(callStack, Does.Contain("CThreadShutdownDeadline(\"call-stack bug report\")")); + Assert.That(auxiliary, Does.Contain("struct CAuxThread")); + Assert.That(auxiliary, Does.Contain("CThreadShutdownDeadline(auxiliary.Description).WaitForSafeJoin(t)")); + Assert.That(auxiliary, Does.Not.Contain("TerminateThread(t, 666)")); + Assert.That(appEntry, Does.Contain("ShutdownAuxThreads()")); + Assert.That(appEntry, Does.Not.Contain("TerminateAuxThreads()")); + Assert.That(refactoring, Does.Contain("### 44. Define bounded shutdown deadlines without unsafe escalation — Implemented")); + }); + } + + [Test] + public void Check_path_workers_use_signaled_work_cancellation_and_deadline_waits() + { + var root = FindRepositoryRoot(); + var owner = File.ReadAllText(Path.Combine(root, "src", "common", "thread_owner.h")); + var checkPath = File.ReadAllText(Path.Combine(root, "src", "path_checking.cpp")); + var safeWait = File.ReadAllText(Path.Combine(root, "src", "string_resources.cpp")); + var waitWindow = File.ReadAllText(Path.Combine(root, "src", "dialogs_attributes.cpp")); + var refactoring = File.ReadAllText(Path.Combine(root, "refactoring.md")); + + // These source contracts keep cancellation, work completion, and retry + // deadlines as independently signaled wake reasons rather than timing guesses. + Assert.Multiple(() => + { + Assert.That(owner, Does.Contain("HANDLE GetCompletionEvent() const")); + Assert.That(checkPath, Does.Contain("HANDLE waits[] = {CPFirstStart, stopEvent}")); + Assert.That(checkPath, Does.Contain("WaitForMultipleObjects(2, waits, FALSE, INFINITE)")); + Assert.That(checkPath, Does.Contain("ThreadCheckPath[0].RequestStop()")); + Assert.That(checkPath, Does.Contain("WaitForAvailableCheckPathWorker")); + Assert.That(checkPath, Does.Contain("WaitForMultipleObjects(completionCount, completions, FALSE, INFINITE)")); + Assert.That(checkPath, Does.Contain("CreateWaitableTimer(NULL, TRUE, NULL)")); + Assert.That(checkPath, Does.Contain("SetWaitableTimer(deadlineTimer.Get()")); + Assert.That(checkPath, Does.Contain("cpwrWorkCompleted")); + Assert.That(checkPath, Does.Contain("cpwrUserCancelled")); + Assert.That(checkPath, Does.Contain("cpwrDeadlineElapsed")); + Assert.That(checkPath, Does.Contain("GetSafeWaitWindowCancelEvent()")); + Assert.That(checkPath, Does.Not.Contain("Sleep(")); + Assert.That(checkPath, Does.Not.Contain("CPFirstTerminate")); + Assert.That(safeWait, Does.Contain("HANDLE GetSafeWaitWindowCancelEvent()")); + Assert.That(safeWait, Does.Contain("DuplicateHandle(GetCurrentProcess(), SafeWaitWindowCancelEvent")); + Assert.That(safeWait, Does.Contain("void SignalSafeWaitWindowCancellation()")); + Assert.That(waitWindow, Does.Contain("SignalSafeWaitWindowCancellation();")); + Assert.That(refactoring, Does.Contain("### 46. Implemented: replace `Sleep` polling with signaled waits")); + }); + } + + [Test] + public void Monotonic_64_bit_timers_cross_the_32_bit_boundary_and_reject_backward_samples() + { + var root = FindRepositoryRoot(); + var clock = File.ReadAllText(Path.Combine(root, "src", "common", "monotonic_time.h")); + var pluginTimers = File.ReadAllText(Path.Combine(root, "src", "plugins_filesystem.cpp")); + var pluginHeader = File.ReadAllText(Path.Combine(root, "src", "plugins.h")); + var refactoring = File.ReadAllText(Path.Combine(root, "refactoring.md")); + + // These boundary values model the old 49.7-day wrap while the source + // checks keep the native timer queue on the tested 64-bit seam. + static ulong Elapsed(ulong start, ulong now) => now >= start ? now - start : 0; + + Assert.Multiple(() => + { + Assert.That(Elapsed(0xFFFF_FFF0UL, 0x1_0000_0020UL), Is.EqualTo(0x30UL), + "A deadline crossing the old 32-bit wrap must retain its elapsed duration."); + Assert.That(Elapsed(500UL, 400UL), Is.Zero, + "A synthetic backward sample must not fabricate elapsed time."); + Assert.That(0x1_0000_0020UL >= 0x1_0000_0010UL, Is.True, + "64-bit deadlines remain directly orderable after the former wrap boundary."); + Assert.That(clock, Does.Contain("typedef ULONGLONG CMonotonicTimePoint")); + Assert.That(clock, Does.Contain("typedef ULONGLONG CMonotonicDuration")); + Assert.That(clock, Does.Contain("return GetTickCount64();")); + Assert.That(clock, Does.Contain("return now >= start ? now - start : 0;")); + Assert.That(clock, Does.Contain("RemainingWin32TimerDelay")); + Assert.That(pluginHeader, Does.Contain("CMonotonicTimePoint AbsTimeout")); + Assert.That(pluginTimers, Does.Contain("CMonotonicClock::DeadlineAfter(relTimeout)")); + Assert.That(pluginTimers, Does.Contain("CMonotonicClock::HasReached(timer->AbsTimeout, timeNow)")); + Assert.That(pluginTimers, Does.Not.Contain("GetTickCount("), + "The plug-in timer queue must not reintroduce a wrap-prone clock read."); + Assert.That(pluginTimers, Does.Not.Contain("(int)(timer->AbsTimeout - timeNow)")); + Assert.That(refactoring, Does.Contain("### 45. Replace wrap-prone time calculations with monotonic 64-bit time — Partially implemented")); + }); + } + + [Test] + public void Win32_path_boundaries_use_owned_wide_paths_and_extended_length_syntax() + { + var root = FindRepositoryRoot(); + var widePath = File.ReadAllText(Path.Combine(root, "src", "common", "wide_path.h")); + var strings = File.ReadAllText(Path.Combine(root, "src", "common", "strutils.cpp")); + var handles = File.ReadAllText(Path.Combine(root, "src", "common", "handles.cpp")); + var navigation = File.ReadAllText(Path.Combine(root, "src", "fileswindow_navigation.cpp")); + var refactoring = File.ReadAllText(Path.Combine(root, "refactoring.md")); + + Assert.Multiple(() => + { + Assert.That(widePath, Does.Contain("class CWidePath")); + Assert.That(widePath, Does.Contain("GetDisplayPath")); + Assert.That(widePath, Does.Contain("GetPathForWin32Api")); + Assert.That(widePath, Does.Contain("GetFullPathForWin32Api")); + Assert.That(widePath, Does.Contain("GetFullPathNameW(DisplayPath, 0, NULL, NULL)")); + Assert.That(widePath, Does.Contain("extendedUncPrefix")); + Assert.That(widePath, Does.Contain("PrefixExtendedLengthPathIfNeeded")); + Assert.That(widePath, Does.Contain("MB_ERR_INVALID_CHARS")); + Assert.That(widePath, Does.Contain("display spelling separate from the API spelling")); + Assert.That(strings, Does.Contain("CWidePath fileNameW(fileName)")); + Assert.That(strings, Does.Contain("GetPathForWin32Api")); + Assert.That(strings, Does.Not.Contain("CStrStackOrHeap")); + Assert.That(handles, Does.Contain("CWidePath fileNameW(lpFileName)")); + Assert.That(handles, Does.Contain("GetFullPathForWin32Api")); + Assert.That(handles, Does.Not.Contain("Utf8AllocWideHandles")); + Assert.That(navigation, Does.Contain("CWidePath pathW(path)")); + Assert.That(navigation, Does.Not.Contain("CStrStackOrHeap")); + Assert.That(refactoring, Does.Contain("### 35. Introduce a dynamic wide-path abstraction — Implemented")); + }); + } + + [Test] + public void Destructive_operations_keep_the_handle_identity_guard() + { + var root = FindRepositoryRoot(); + var helper = File.ReadAllText(Path.Combine(root, "src", "file_identity.cpp")); + var operations = File.ReadAllText(Path.Combine(root, "src", "operations_core.cpp")); + var copy = File.ReadAllText(Path.Combine(root, "src", "async_copy.cpp")); + + // The mutation API borrows the RAII owner's handle; it must not receive + // or retain ownership of the verified delete handle itself. + Assert.Multiple(() => + { + Assert.That(helper, Does.Contain("FILE_FLAG_OPEN_REPARSE_POINT")); + Assert.That(helper, Does.Contain("GetFileInformationByHandle")); + Assert.That(helper, Does.Contain("GetFinalPathNameByHandleW")); + Assert.That(helper, Does.Contain("OperationExecutionFileSystem().SetFileInformationByHandle(handle.Get(), FileDispositionInfo")); + Assert.That(operations, Does.Contain("CaptureOperationFileIdentities(op, &identityError)")); + // Verification reports its failure through the local address before + // the typed result preserves that code for the legacy dialog path. + Assert.That(copy, Does.Contain("VerifyFileIdentity(targetName, expectedTargetIdentity, &error)")); + Assert.That(copy, Does.Contain("DeleteFileWithVerifiedIdentity(name, operation->SourceIdentity, &err)")); + }); + } + + [Test] + public void Reparse_point_policy_never_traverses_or_hydrates_unselected_targets() + { + var root = FindRepositoryRoot(); + var planner = File.ReadAllText(Path.Combine(root, "src", "fileswindow_operations.cpp")); + var deletion = File.ReadAllText(Path.Combine(root, "src", "operations_core.cpp")); + var navigation = File.ReadAllText(Path.Combine(root, "src", "fileswindow_navigation.cpp")); + var topologyTests = File.ReadAllText(Path.Combine(root, "tests", "FileManager.UiTests", "ReparsePointTopologyUiTests.cs")); + var architecture = File.ReadAllText(Path.Combine(root, "architecture.md")); + var refactoring = File.ReadAllText(Path.Combine(root, "refactoring.md")); + + Assert.Multiple(() => + { + Assert.That(planner, Does.Contain("REPARSE_POINT_POLICY: Directory reparse points are operation")); + Assert.That(planner, Does.Contain("skipping directory reparse point without traversal")); + Assert.That(planner, Does.Contain("containing source directory cannot be removed as part of")); + Assert.That(planner, Does.Contain("REPARSE_POINT_POLICY: File reparse points are not opened by planning")); + Assert.That(planner, Does.Contain("skipping file reparse point without hydration")); + Assert.That(planner, Does.Not.Contain("CConfirmLinkTgtCopyDlg(HWindow, sourcePath"), + "The legacy link-content path would follow a target outside the operation root."); + Assert.That(deletion, Does.Contain("juncData->ReparseTag != IO_REPARSE_TAG_MOUNT_POINT")); + Assert.That(deletion, Does.Contain("juncData->ReparseTag != IO_REPARSE_TAG_SYMLINK")); + Assert.That(deletion, Does.Contain("ERROR_REPARSE_TAG_MISMATCH")); + Assert.That(navigation, Does.Contain("IO_REPARSE_TAG_FILE_PLACEHOLDER")); + Assert.That(topologyTests, Does.Contain("changed-junction")); + Assert.That(topologyTests, Does.Contain("cycle-junction")); + Assert.That(topologyTests, Does.Contain("outside-symlink")); + Assert.That(topologyTests, Does.Contain("Delete_junction_removes_only_the_link_and_never_its_target")); + Assert.That(architecture, Does.Contain("Reparse-point operation policy")); + Assert.That(refactoring, Does.Contain("### 34. Exercise junction, symlink, mount-point, and cloud-placeholder cases — Implemented")); + }); + } + + [Test] + public void Copy_engine_uses_unambiguous_64_bit_file_size_and_seek_wrappers() + { + var root = FindRepositoryRoot(); + var declarations = File.ReadAllText(Path.Combine(root, "src", "plugins", "shared", "spl_com.h")); + var api = File.ReadAllText(Path.Combine(root, "src", "consts.h")); + var wrappers = File.ReadAllText(Path.Combine(root, "src", "path_checking.cpp")); + var copy = File.ReadAllText(Path.Combine(root, "src", "async_copy.cpp")); + + Assert.Multiple(() => + { + Assert.That(declarations, Does.Contain("struct CFileOffsetResult")); + Assert.That(api, Does.Contain("CFileOffsetResult SalGetFileSizeEx")); + Assert.That(api, Does.Contain("CFileOffsetResult SalSetFilePointerEx")); + Assert.That(wrappers, Does.Contain("GetFileSizeEx(file, &size)")); + Assert.That(wrappers, Does.Contain("SetFilePointerEx(file, input, &output, moveMethod)")); + Assert.That(wrappers, Does.Contain("CFileOffsetResult(GetLastError())")); + Assert.That(copy, Does.Contain("SalGetFileSizeEx(")); + Assert.That(copy, Does.Contain("SalSetFilePointerEx(")); + Assert.That(Regex.Matches(copy, @"(?m)^(?!\s*//).*?\bGetFileSize\s*\(").Count, Is.Zero, + "The copy engine must not reintroduce raw GetFileSize calls."); + Assert.That(Regex.Matches(copy, @"(?m)^(?!\s*//).*?\bSetFilePointer\s*\(").Count, Is.Zero, + "The copy engine must not reintroduce raw SetFilePointer calls."); + }); + } + + [TestCase(0xFFFFFFFFUL, true, 0xFFFFFFFFUL, true)] + [TestCase(0xFFFFFFFFUL, true, 100000UL, false)] + [TestCase(0x100000000UL, true, 0xFFFFFFFFUL, false)] + [TestCase(0UL, false, 0xFFFFFFFFUL, false)] + public void Plugin_readers_preserve_full_file_sizes_and_reject_only_their_explicit_caps( + ulong size, bool succeeded, ulong cap, bool expectedAccepted) + { + // Exercise the maximum DWORD, successful sentinel value, over-cap, and failed-query contracts independently. + Assert.That(succeeded && size <= cap, Is.EqualTo(expectedAccepted)); + + var root = FindRepositoryRoot(); + var sdk = File.ReadAllText(Path.Combine(root, "src", "plugins", "shared", "spl_gen.h")); + var checkver = File.ReadAllText(Path.Combine(root, "src", "plugins", "checkver", "data.cpp")); + var peviewer = File.ReadAllText(Path.Combine(root, "src", "plugins", "peviewer", "peviewer.cpp")); + var tar = File.ReadAllText(Path.Combine(root, "src", "plugins", "tar", "fileio.cpp")); + var nethood = File.ReadAllText(Path.Combine(root, "src", "plugins", "nethood", "cache.cpp")); + + Assert.Multiple(() => + { + Assert.That(sdk, Does.Contain("CFileOffsetResult SalGetPluginFileSizeEx")); + Assert.That(checkver, Does.Contain("!sizeResult.Succeeded || sizeResult.Value.Value == 0 || sizeResult.Value.Value > LOADED_SCRIPT_MAX")); + Assert.That(peviewer, Does.Contain("CQuadWord& fileSize")); + Assert.That(peviewer, Does.Contain("fileSize.HiDWord != 0")); + Assert.That(peviewer, Does.Contain("fileSize.HiDWord, fileSize.LoDWord")); + Assert.That(tar, Does.Contain("SalGetPluginFileSizeEx(SalamanderGeneral, File)")); + Assert.That(tar, Does.Contain("StreamPos.Value > InputSize.Value")); + Assert.That(tar, Does.Contain("read > InputSize.Value - StreamPos.Value")); + Assert.That(nethood, Does.Contain("sizeResult.Succeeded && sizeResult.Value.Value <= 1000")); + Assert.That(Regex.Matches(checkver + peviewer + tar + nethood, @"(?m)^(?!\s*//).*?\bGetFileSize\s*\(").Count, Is.Zero, + "Bounded plug-in readers must not reintroduce sentinel-ambiguous GetFileSize calls."); + }); + } + + [Test] + public void Split_combine_stages_and_verifies_output_before_publishing_it() + { + // Keep the plug-in from reintroducing a direct, partial write to the requested destination. + var root = FindRepositoryRoot(); + var combine = File.ReadAllText(Path.Combine(root, "src", "plugins", "splitcbn", "combine.cpp")); + var combineFiles = combine[..combine.IndexOf("// CalculateFileCRC", StringComparison.Ordinal)]; + + Assert.Multiple(() => + { + Assert.That(combine, Does.Contain("SalGetPluginFileSizeEx(SalamanderGeneral")); + Assert.That(Regex.Matches(combineFiles, @"(?m)^(?!\s*//).*?\bGetFileSize\s*\(").Count, Is.Zero, + "Combine totals and progress must not revive sentinel-ambiguous GetFileSize calls."); + Assert.That(combine, Does.Contain("std::unique_ptr")); + Assert.That(combine, Does.Contain("SalGetTempFileName(targetDirectory, \"SCB\"")); + Assert.That(combine, Does.Contain("FILE_FLAG_WRITE_THROUGH")); + Assert.That(combine, Does.Contain("FlushFileBuffers(outfile.Get()->HFile)")); + Assert.That(combine, Does.Contain("VerifyCombinedOutput(temporaryOutput.GetName(), totalSize)")); + Assert.That(combine, Does.Contain("GetFileInformationByHandle(output, &information)")); + Assert.That(combine, Does.Contain("ReplaceFileW(targetNameW, stagedNameW, NULL, REPLACEFILE_WRITE_THROUGH")); + Assert.That(combine, Does.Contain("MoveFileExW(stagedNameW, targetNameW, MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)")); + Assert.That(combine, Does.Contain("COperationResult")); + Assert.That(combine, Does.Contain("result.ToLegacyBool(&error)")); + }); + } + + [Test] + public void File_operation_planning_uses_an_immutable_plan_and_narrow_filesystem_adapter() + { + var root = FindRepositoryRoot(); + var planHeader = File.ReadAllText(Path.Combine(root, "src", "operation_plan.h")); + var plan = File.ReadAllText(Path.Combine(root, "src", "operation_plan.cpp")); + var fileSystem = File.ReadAllText(Path.Combine(root, "src", "file_operation_filesystem.h")); + var nativeFileSystem = File.ReadAllText(Path.Combine(root, "src", "file_operation_filesystem.cpp")); + var planner = File.ReadAllText(Path.Combine(root, "src", "fileswindow_operations.cpp")); + var journal = File.ReadAllText(Path.Combine(root, "src", "operation_journal.cpp")); + var refactoring = File.ReadAllText(Path.Combine(root, "refactoring.md")); + + Assert.Multiple(() => + { + Assert.That(planHeader, Does.Contain("class COperationPlan")); + Assert.That(planHeader, Does.Contain("BOOL Capture(COperations& operations)")); + Assert.That(plan, Does.Contain("snapshots are immutable once exposed to the execution boundary")); + Assert.That(plan, Does.Contain("DuplicatePlanPath")); + Assert.That(plan, Does.Not.Contain("CreateFile(")); + Assert.That(plan, Does.Not.Contain("DeleteFile(")); + Assert.That(fileSystem, Does.Contain("class CFileOperationFileSystem")); + Assert.That(fileSystem, Does.Contain("GetAttributes")); + Assert.That(fileSystem, Does.Contain("GetDiskFreeSpace")); + Assert.That(fileSystem, Does.Contain("SetFileOperationFileSystemForTests")); + Assert.That(nativeFileSystem, Does.Contain("CWin32FileOperationFileSystem")); + Assert.That(planner, Does.Contain("FileOperationFileSystem().GetAttributes")); + Assert.That(planner, Does.Contain("FileOperationFileSystem().GetDiskFreeSpace")); + Assert.That(journal, Does.Contain("AppendGoldenMasterPlan")); + Assert.That(journal, Does.Contain("PLANITEM|%d|%s")); + Assert.That(refactoring, Does.Contain("### 27. Extract a testable file-operation planning seam — Implemented")); + }); + } + + [Test] + public void File_operation_correlation_ids_cross_plan_worker_ui_journal_and_log_boundaries() + { + var root = FindRepositoryRoot(); + var workerHeader = File.ReadAllText(Path.Combine(root, "src", "worker.h")); + var worker = File.ReadAllText(Path.Combine(root, "src", "worker.cpp")); + var workerBody = File.ReadAllText(Path.Combine(root, "src", "operations_core.cpp")); + var plan = File.ReadAllText(Path.Combine(root, "src", "operation_plan.cpp")); + var journal = File.ReadAllText(Path.Combine(root, "src", "operation_journal.cpp")); + var dialogs = File.ReadAllText(Path.Combine(root, "src", "dialogs_file_ops.cpp")); + var log = File.ReadAllText(Path.Combine(root, "src", "execlog.cpp")); + var refactoring = File.ReadAllText(Path.Combine(root, "refactoring.md")); + + // This source-level characterization pins the handoffs that cannot be deterministically faulted from UIA. + Assert.Multiple(() => + { + Assert.That(workerHeader, Does.Contain("OPERATION_CORRELATION_ID_LENGTH")); + Assert.That(worker, Does.Contain("CreateOperationCorrelationId")); + Assert.That(plan, Does.Contain("operations.GetCorrelationId()")); + Assert.That(workerBody, Does.Contain("Worker-%s")); + Assert.That(workerBody, Does.Contain("BeginItemAttempt(i)")); + Assert.That(dialogs, Does.Contain("%s [#%s]")); + Assert.That(dialogs, Does.Contain("Script->RecordItemRetry()")); + Assert.That(journal, Does.Contain("CORRELATION|operation=%s")); + Assert.That(journal, Does.Contain("RETRY|%d|attempt=%d")); + Assert.That(log, Does.Contain("operation=%s, item=%d, attempt=%d")); + Assert.That(refactoring, Does.Contain("### 55. Assign correlation IDs to operations and workers — Implemented")); + }); + } + + [Test] + public void Central_retry_policy_bounds_transient_read_retries_and_blocks_destructive_commits() + { + var root = FindRepositoryRoot(); + var policy = File.ReadAllText(Path.Combine(root, "src", "retry_policy.h")); + var result = File.ReadAllText(Path.Combine(root, "src", "operation_result.h")); + var copy = File.ReadAllText(Path.Combine(root, "src", "async_copy.cpp")); + var refactoring = File.ReadAllText(Path.Combine(root, "refactoring.md")); + + // Source characterization keeps retry pacing and destructive-operation safety independently reviewable. + Assert.Multiple(() => + { + Assert.That(policy, Does.Contain("enum ERetryOperationKind")); + Assert.That(policy, Does.Contain("rokReadOnly")); + Assert.That(policy, Does.Contain("rokDestructiveCommit")); + Assert.That(policy, Does.Contain("ERROR_NETNAME_DELETED")); + Assert.That(policy, Does.Contain("kAutomaticRetryLimit = 3")); + Assert.That(policy, Does.Contain("GetAutomaticRetryDelay")); + Assert.That(policy, Does.Contain("GetTickCount()")); + Assert.That(policy, Does.Contain("WaitForSingleObject(cancellationEvent, delay)")); + Assert.That(policy, Does.Contain("kind == rokDestructiveCommit")); + Assert.That(result, Does.Contain("return IsTransientOperationError(error);")); + Assert.That(copy, Does.Contain("PrepareAutomaticRetry(err, &autoRetryAttemptsSNAP, rokReadOnly")); + Assert.That(copy, Does.Contain("PrepareAutomaticRetry(err, &AutoRetryAttemptsSNAP, rokReadOnly")); + Assert.That(copy, Does.Contain("PrepareAutomaticRetry(err, &autoRetryAttempts, rokDestructiveCommit")); + Assert.That(copy, Does.Contain("PrepareAutomaticRetry(err, &AutoRetryCounter, rokDestructiveCommit")); + Assert.That(copy, Does.Not.Contain("Sleep(100);")); + Assert.That(copy, Does.Not.Contain("Sleep(AutoRetryCounter * 100);")); + Assert.That(refactoring, Does.Contain("### 57. Centralize retry policy — Partially implemented")); + }); + } + + [Test] + public void Transactional_copy_and_move_expose_each_durable_phase_to_a_deterministic_fault_adapter() + { + var root = FindRepositoryRoot(); + var executionHeader = File.ReadAllText(Path.Combine(root, "src", "file_operation_filesystem.h")); + var executionAdapter = File.ReadAllText(Path.Combine(root, "src", "file_operation_filesystem.cpp")); + var copy = File.ReadAllText(Path.Combine(root, "src", "async_copy.cpp")); + var identities = File.ReadAllText(Path.Combine(root, "src", "file_identity.cpp")); + var journal = File.ReadAllText(Path.Combine(root, "src", "operation_journal.cpp")); + var refactoring = File.ReadAllText(Path.Combine(root, "refactoring.md")); + + Assert.Multiple(() => + { + Assert.That(executionHeader, Does.Contain("class COperationExecutionFileSystem")); + Assert.That(executionHeader, Does.Contain("virtual HANDLE CreateFile")); + Assert.That(executionHeader, Does.Contain("virtual BOOL WriteFile")); + Assert.That(executionHeader, Does.Contain("virtual BOOL SetFileTime")); + Assert.That(executionHeader, Does.Contain("virtual BOOL FlushFileBuffers")); + Assert.That(executionHeader, Does.Contain("virtual BOOL ReplaceFile")); + Assert.That(executionHeader, Does.Contain("virtual BOOL SetFileInformationByHandle")); + Assert.That(executionHeader, Does.Contain("SetOperationExecutionFileSystemForTests")); + Assert.That(executionAdapter, Does.Contain("CWin32OperationExecutionFileSystem")); + Assert.That(copy, Does.Contain("OperationExecutionFileSystem().CreateFile")); + Assert.That(copy, Does.Contain("OperationExecutionFileSystem().WriteFile")); + Assert.That(copy, Does.Contain("OperationExecutionFileSystem().SetFileTime")); + Assert.That(copy, Does.Contain("OperationExecutionFileSystem().FlushFileBuffers")); + Assert.That(copy, Does.Contain("OperationExecutionFileSystem().ReplaceFile")); + Assert.That(copy, Does.Contain("OperationExecutionFileSystem().MoveFile")); + Assert.That(identities, Does.Contain("OperationExecutionFileSystem().SetFileInformationByHandle")); + Assert.That(journal, Does.Contain("STATE|%d|prepared")); + Assert.That(journal, Does.Contain("STATE|%d|temporary-ready")); + Assert.That(refactoring, Does.Contain("### 29. Add crash-consistency fault injection at every operation phase — Partially implemented")); + }); + } + + [Test] + public void Native_destructive_operation_characterization_suite_retains_the_required_scenarios() + { + var root = FindRepositoryRoot(); + var operations = File.ReadAllText(Path.Combine(root, "tests", "FileManager.UiTests", "FileOperationUiTests.cs")); + // File access commands live separately from destructive operations but remain part of the required UI characterization set. + var access = File.ReadAllText(Path.Combine(root, "tests", "FileManager.UiTests", "FileAccessUiTests.cs")); + var crossVolume = File.ReadAllText(Path.Combine(root, "tests", "FileManager.UiTests", "CrossVolumeMoveCharacterizationUiTests.cs")); + var recovery = File.ReadAllText(Path.Combine(root, "tests", "FileManager.UiTests", "OperationRecoveryCharacterizationUiTests.cs")); + var workspace = File.ReadAllText(Path.Combine(root, "tests", "FileManager.UiTests", "Infrastructure", "FileOperationUiTestBase.cs")); + var settings = File.ReadAllText(Path.Combine(root, "tests", "FileManager.UiTests", "Infrastructure", "UiTestSettings.cs")); + var ads = File.ReadAllText(Path.Combine(root, "tests", "FileManager.UiTests", "Infrastructure", "AlternateDataStreams.cs")); + var unsupportedAds = File.ReadAllText(Path.Combine(root, "tests", "FileManager.UiTests", "AlternateDataStreamsUnsupportedTargetUiTests.cs")); + var refactoring = File.ReadAllText(Path.Combine(root, "refactoring.md")); + + Assert.Multiple(() => + { + Assert.That(operations, Does.Contain("Copy_overwrite_replaces_the_existing_target_only_after_the_user_confirms")); + Assert.That(operations, Does.Contain("Copy_overwrite_all_applies_the_choice_to_the_complete_conflicting_tree")); + Assert.That(operations, Does.Contain("Copy_skip_keeps_the_existing_target_and_the_source")); + Assert.That(operations, Does.Contain("Copy_skip_all_keeps_the_existing_conflicting_tree")); + Assert.That(operations, Does.Contain("Rename_overwrite_replaces_the_collision_without_losing_source_metadata")); + Assert.That(operations, Does.Contain("Rename_overwrite_decline_keeps_the_original_file_and_existing_target")); + Assert.That(operations, Does.Contain("Rename_case_only_change_preserves_the_file_and_updates_its_displayed_name")); + Assert.That(operations, Does.Contain("The default move fixture characterizes same-volume behavior.")); + Assert.That(operations, Does.Contain("Move_overwrite_replaces_the_existing_target_and_removes_the_source")); + Assert.That(operations, Does.Contain("Move_skip_keeps_the_existing_target_and_the_unmoved_source")); + Assert.That(operations, Does.Contain("Move_overwrite_all_replaces_every_conflict_before_removing_the_source_tree")); + Assert.That(operations, Does.Contain("Move_skip_all_retains_conflicting_sources_but_moves_nonconflicting_siblings")); + Assert.That(operations, Does.Contain("Delete_mixed_selection_removes_the_selected_file_and_directory_tree")); + Assert.That(operations, Does.Contain("Delete_skip_for_locked_file_keeps_it_and_continues_with_later_items")); + Assert.That(access, Does.Contain("Find_files_searches_subdirectories_from_the_active_panel")); + Assert.That(access, Does.Contain("View_file_opens_the_selected_file_in_the_internal_viewer")); + Assert.That(access, Does.Contain("Edit_file_opens_the_selected_file_in_the_configured_editor")); + Assert.That(operations, Does.Contain("Delete_to_recycle_bin_removes_the_source_and_creates_a_recoverable_shell_item")); + Assert.That(operations, Does.Contain("Cancelling_an_in_progress_conflicting_copy_keeps_both_versions_and_records_cancellation")); + Assert.That(crossVolume, Does.Contain("Move_across_volumes_copies_the_complete_tree_before_removing_the_source")); + Assert.That(crossVolume, Does.Contain("RequireCrossVolumeRoot")); + Assert.That(operations, Does.Contain("Copy_preserves_multiple_empty_large_and_edge_named_alternate_data_streams")); + Assert.That(operations, Does.Contain("Copy_overwrite_replaces_target_streams_and_removes_stale_streams")); + Assert.That(operations, Does.Contain("Copy_retries_a_temporarily_denied_alternate_data_stream_without_losing_it")); + Assert.That(crossVolume, Does.Contain("Move_across_ADS_capable_volumes_preserves_multiple_streams_before_removing_the_source")); + Assert.That(unsupportedAds, Does.Contain("Cross_volume_move_to_an_ADS_unsupported_target_keeps_the_source_when_metadata_loss_is_declined")); + Assert.That(ads, Does.Contain("RequireSupportAt")); + Assert.That(ads, Does.Contain("RequireUnsupportedAt")); + Assert.That(settings, Does.Contain("FILEMANAGER_UI_CROSS_VOLUME_ROOT")); + Assert.That(settings, Does.Contain("FILEMANAGER_UI_ADS_UNSUPPORTED_TARGET_ROOT")); + Assert.That(recovery, Does.Contain("Restart_reconciliation_commits_a_fully_written_transactional_target")); + Assert.That(recovery, Does.Contain("STATE|0|temporary-ready")); + Assert.That(workspace, Does.Contain("TargetVolumeRoot")); + Assert.That(workspace, Does.Contain("TargetWorkspaceDirectory")); + Assert.That(refactoring, Does.Contain("### 28. Build native characterization tests for copy, move, delete, and rename — Partially implemented")); + Assert.That(refactoring, Does.Contain("### 32. Test alternate data streams end to end — Implemented")); + }); + } + + [Test] + public void Crash_report_uploads_use_certificate_validated_https_with_explicit_consent() + { + var root = FindRepositoryRoot(); + var upload = File.ReadAllText(Path.Combine(root, "src", "salmon", "upload.cpp")); + var project = File.ReadAllText(Path.Combine(root, "src", "vcxproj", "salmon", "salmon_base.props")); + var dialog = File.ReadAllText(Path.Combine(root, "src", "lang", "lang.rc")); + var reporting = File.ReadAllText(Path.Combine(root, "reporting.md")); + var refactoring = File.ReadAllText(Path.Combine(root, "refactoring.md")); + + Assert.Multiple(() => + { + Assert.That(upload, Does.Contain("WinHttpOpen(")); + Assert.That(upload, Does.Contain("WinHttpConnect(session, kServerName, INTERNET_DEFAULT_HTTPS_PORT")); + Assert.That(upload, Does.Contain("WinHttpOpenRequest(connection, L\"POST\", kUploadPath")); + Assert.That(upload, Does.Contain("WINHTTP_FLAG_SECURE")); + Assert.That(upload, Does.Contain("WINHTTP_DISABLE_REDIRECTS")); + Assert.That(upload, Does.Contain("WinHttpGetIEProxyConfigForCurrentUser")); + Assert.That(upload, Does.Contain("/api/v1/crash-reports")); + Assert.That(upload, Does.Contain("Transfer-Encoding: chunked")); + Assert.That(upload, Does.Contain("WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH")); + Assert.That(upload, Does.Contain("FinishChunkedRequest")); + Assert.That(upload, Does.Contain("CancelUploadThread")); + Assert.That(upload, Does.Not.Contain("DWORD totalLength")); + Assert.That(upload, Does.Not.Contain("WINHTTP_OPTION_SECURITY_FLAGS")); + Assert.That(upload, Does.Not.Contain("winsock2.h")); + Assert.That(upload, Does.Not.Contain("http://")); + Assert.That(Regex.Matches(upload, @"(?m)^(?!\s*//).*?\b(gethostbyname|connect|send|recv)\s*\(").Count, Is.Zero, + "The crash uploader must not reintroduce raw Winsock transport calls."); + Assert.That(project, Does.Contain("winhttp.lib")); + Assert.That(project, Does.Not.Contain("Ws2_32.lib")); + Assert.That(dialog, Does.Contain("Consent: Send Report uploads this crash archive")); + Assert.That(dialog, Does.Contain("over HTTPS")); + Assert.That(reporting, Does.Contain("https://reports.taskscape.com/api/v1/crash-reports")); + Assert.That(reporting, Does.Contain("http://reports.taskscape.com/upload.php")); + Assert.That(reporting, Does.Contain("Transfer-Encoding: chunked")); + Assert.That(refactoring, Does.Contain("### 12. Replace the custom crash uploader with HTTPS WinHTTP — Implemented")); + }); + } + + [Test] + public void Crash_minidumps_default_to_metadata_without_private_memory_or_data_segments() + { + var root = FindRepositoryRoot(); + var minidump = File.ReadAllText(Path.Combine(root, "src", "salmon", "minidump.cpp")); + var reporting = File.ReadAllText(Path.Combine(root, "reporting.md")); + + // Keep the privacy default visible in both the executable policy and user-facing report inventory. + Assert.Multiple(() => + { + Assert.That(minidump, Does.Contain("Keep crash reports diagnostically useful without serializing arbitrary private writable memory.")); + Assert.That(minidump, Does.Not.Contain("MiniDumpWithPrivateReadWriteMemory")); + Assert.That(minidump, Does.Not.Contain("MiniDumpWithDataSegs")); + Assert.That(minidump, Does.Not.Contain("MiniDumpWithHandleData")); + Assert.That(minidump, Does.Contain("Privacy is the default: never retry a failed minimal dump with a memory-rich variant.")); + Assert.That(reporting, Does.Contain("intentionally excludes private writable memory, module data segments, and handle data")); + }); + } + + [Test] + public void Dynamic_library_loads_use_restricted_search_paths() + { + var root = FindRepositoryRoot(); + var startup = File.ReadAllText(Path.Combine(root, "src", "app_entry.cpp")); + var releaseHandles = File.ReadAllText(Path.Combine(root, "src", "common", "handles.h")); + var debugHandles = File.ReadAllText(Path.Combine(root, "src", "common", "handles.cpp")); + var widePath = File.ReadAllText(Path.Combine(root, "src", "common", "wide_path.h")); + var refactoring = File.ReadAllText(Path.Combine(root, "refactoring.md")); + + Assert.Multiple(() => + { + Assert.That(startup, Does.Contain("InitializeDllSearchPaths()")); + Assert.That(startup, Does.Contain("SetDefaultDllDirectories")); + Assert.That(startup, Does.Contain("AddDllDirectory")); + Assert.That(startup, Does.Contain("LOAD_LIBRARY_SEARCH_APPLICATION_DIR")); + Assert.That(startup, Does.Contain("LOAD_LIBRARY_SEARCH_SYSTEM32")); + Assert.That(startup, Does.Contain("LOAD_LIBRARY_SEARCH_USER_DIRS")); + Assert.That(widePath, Does.Contain("GetFullPathNameW")); + Assert.That(releaseHandles, Does.Contain("GetFullPathForWin32Api")); + Assert.That(debugHandles, Does.Contain("GetFullPathForWin32Api")); + Assert.That(releaseHandles, Does.Contain("::LoadLibraryExW(fullPath, NULL, loadFlags)")); + Assert.That(debugHandles, Does.Contain("::LoadLibraryExW(fullPath, NULL, loadFlags)")); + Assert.That(releaseHandles, Does.Contain("LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR")); + Assert.That(debugHandles, Does.Contain("LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR")); + Assert.That(Regex.Matches(releaseHandles, @"(?m)^(?!\s*//).*?\bLoadLibraryW\s*\(").Count, Is.Zero, + "Release builds must not restore unrestricted LoadLibraryW calls."); + Assert.That(Regex.Matches(debugHandles, @"(?m)^(?!\s*//).*?\bLoadLibraryW\s*\(").Count, Is.Zero, + "Debug builds must not restore unrestricted LoadLibraryW calls."); + Assert.That(refactoring, Does.Contain("### 19. Constrain DLL search paths — Implemented")); + }); + } + + [Test] + public void Thumbnail_and_archive_metadata_use_the_restartable_parser_broker() + { + var root = FindRepositoryRoot(); + var protocol = File.ReadAllText(Path.Combine(root, "src", "parserbroker_protocol.h")); + var client = File.ReadAllText(Path.Combine(root, "src", "parserbroker.cpp")); + var broker = File.ReadAllText(Path.Combine(root, "src", "parserbroker", "salbroker.cpp")); + var thumbnails = File.ReadAllText(Path.Combine(root, "src", "fileswindow_init.cpp")); + var archives = File.ReadAllText(Path.Combine(root, "src", "plugins_loading.cpp")); + var installer = File.ReadAllText(Path.Combine(root, "Installer", "setup.iss")); + var refactoring = File.ReadAllText(Path.Combine(root, "refactoring.md")); + + Assert.Multiple(() => + { + Assert.That(protocol, Does.Contain("PARSER_BROKER_VERSION = 1")); + Assert.That(protocol, Does.Contain("PARSER_BROKER_MAX_PAYLOAD")); + Assert.That(protocol, Does.Contain("CParserBrokerMessageHeader")); + Assert.That(protocol, Does.Contain("pbmtThumbnailRequest")); + Assert.That(protocol, Does.Contain("pbmtArchiveMetadataRequest")); + Assert.That(client, Does.Contain("CreateRestrictedToken")); + Assert.That(client, Does.Contain("JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE")); + Assert.That(client, Does.Contain("JOB_OBJECT_LIMIT_PROCESS_MEMORY")); + Assert.That(client, Does.Contain("CancelIoEx")); + // Request serialization is scope-owned and ranked so callback unwinding + // cannot strand the broker lock or obscure its acquisition order. + Assert.That(client, Does.Contain("CScopedCriticalSection lock(&Lock, lkrExternalBroker, \"ParserBroker.Lock\")")); + Assert.That(client, Does.Contain("for (int attempt = 0; attempt != 2; ++attempt)")); + Assert.That(client, Does.Contain("responseHeader.PayloadLength > responseCapacity")); + Assert.That(broker, Does.Contain("SHCreateItemFromParsingName")); + Assert.That(broker, Does.Contain("requestHeader.PayloadLength > PARSER_BROKER_MAX_PAYLOAD")); + Assert.That(thumbnails, Does.Contain("ParserBroker.LoadThumbnail")); + Assert.That(thumbnails, Does.Not.Contain("(*loader)->LoadThumbnail")); + Assert.That(archives, Does.Contain("ParserBroker.QueryArchiveMetadata")); + Assert.That(installer, Does.Contain("salbroker.exe")); + Assert.That(refactoring, Does.Contain("### 21. Move risky parsers and previewers out of process — Partially implemented")); + }); + } + + [Test] + public void Plugin_entry_scope_and_callback_state_restore_host_state_after_an_unwinding_entry_point() + { + var root = FindRepositoryRoot(); + var loader = File.ReadAllText(Path.Combine(root, "src", "plugins_loading.cpp")); + var header = File.ReadAllText(Path.Combine(root, "src", "plugins.h")); + var messages = File.ReadAllText(Path.Combine(root, "src", "mainwnd_messages.cpp")); + var shutdown = File.ReadAllText(Path.Combine(root, "src", "mainwnd_shutdown.cpp")); + var pathUtilities = File.ReadAllText(Path.Combine(root, "src", "path_utils.cpp")); + var refactoring = File.ReadAllText(Path.Combine(root, "refactoring.md")); + + Assert.Multiple(() => + { + Assert.That(loader, Does.Contain("class CPluginEntryScope")); + Assert.That(loader, Does.Contain("~CPluginEntryScope()")); + Assert.That(loader, Does.Contain("CPluginDataLock dataLock")); + Assert.That(loader, Does.Contain("PluginIface.Init(NULL, 0)")); + // Source files may use either Git or Windows line endings; the ordering invariant is platform-independent. + Assert.That(loader.ReplaceLineEndings("\n"), + Does.Contain("CallbackState.Leave();\n SalamanderGeneral.Init(PluginIface.GetInterface());")); + Assert.That(loader, Does.Contain("CPluginEntryScope pluginEntry(Plugins.GetCallbackState(), PluginIface,")); + Assert.That(loader, Does.Contain("pluginEntry.SetReturnedInterface(resIface)")); + Assert.That(loader, Does.Not.Contain("EnterPlugin(); // for the plugin entry point")); + // Callback state must stay owned by CPlugins while the loading scope + // receives only the narrow reference it needs for balanced entry/exit. + Assert.That(header, Does.Contain("class CPluginCallbackState")); + Assert.That(header, Does.Contain("std::atomic CallbackDepth")); + Assert.That(header, Does.Contain("SRWLOCK TransitionLock")); + Assert.That(header, Does.Contain("CPluginCallbackState CallbackState")); + Assert.That(header, Does.Contain("CPluginCallbackState& GetCallbackState()")); + Assert.That(loader, Does.Contain("void CPluginCallbackState::Enter()")); + Assert.That(loader, Does.Contain("void CPluginCallbackState::Leave()")); + Assert.That(loader, Does.Contain("Plugins.GetCallbackState().Enter()")); + Assert.That(loader, Does.Not.Contain("AlreadyInPlugin")); + Assert.That(loader, Does.Not.Contain("PluginNestingStateLock")); + Assert.That(header, Does.Contain("BOOL IsInPlugin();")); + Assert.That(messages, Does.Contain("IsInPlugin() || StopRefresh > 0")); + Assert.That(shutdown, Does.Contain("!endAfterCleanup && IsInPlugin()")); + Assert.That(pathUtilities, Does.Contain("!IsInPlugin()")); + Assert.That(refactoring, Does.Contain("### 22. Implemented: make plug-in entry bookkeeping exception-safe")); + Assert.That(refactoring, Does.Contain("### 48. Reduce unowned global mutable state — Partially implemented")); + }); + } + + [Test] + public void Delete_manager_callback_registration_invalidates_before_window_teardown_and_rejects_stale_generations() + { + var root = FindRepositoryRoot(); + var cacheHeader = File.ReadAllText(Path.Combine(root, "src", "cache.h")); + var cache = File.ReadAllText(Path.Combine(root, "src", "cache.cpp")); + var messages = File.ReadAllText(Path.Combine(root, "src", "mainwnd_messages.cpp")); + var refactoring = File.ReadAllText(Path.Combine(root, "refactoring.md")); + + Assert.Multiple(() => + { + // The delete-manager lock makes target invalidation atomic with worker notification posting. + Assert.That(cacheHeader, Does.Contain("HWND CallbackWindow")); + Assert.That(cacheHeader, Does.Contain("DWORD CallbackGeneration")); + Assert.That(cacheHeader, Does.Contain("RegisterCallbackWindow(HWND hWindow)")); + Assert.That(cacheHeader, Does.Contain("InvalidateCallbackWindow(HWND hWindow)")); + Assert.That(cache, Does.Contain("PostMessage(CallbackWindow, WM_USER_PROCESSDELETEMAN, (WPARAM)CallbackGeneration, 0)")); + Assert.That(cache, Does.Contain("CallbackWindow = NULL;")); + Assert.That(cache, Does.Contain("CallbackGeneration++")); + Assert.That(cache, Does.Contain("CallbackWindow == hWindow && CallbackGeneration == generation")); + Assert.That(cache, Does.Not.Contain("PostMessage(MainWindow->HWindow, WM_USER_PROCESSDELETEMAN, 0, 0)")); + // Dispatch must reject stale messages, and destruction must invalidate before the window tears down children. + Assert.That(messages, Does.Contain("DeleteManager.RegisterCallbackWindow(HWindow);")); + Assert.That(messages, Does.Contain("DeleteManager.IsCurrentCallbackWindow(HWindow, (DWORD)wParam)")); + Assert.That(messages, Does.Contain("DeleteManager.InvalidateCallbackWindow(HWindow);")); + Assert.That(messages.IndexOf("DeleteManager.InvalidateCallbackWindow(HWindow);", StringComparison.Ordinal), + Is.LessThan(messages.IndexOf("SHChangeNotifyRelease();", StringComparison.Ordinal))); + Assert.That(refactoring, Does.Contain("### 49. Protect window and callback lifetimes — Partially implemented")); + }); + } + + [Test] + public void Icon_work_pool_bounds_memory_and_prioritizes_visible_current_generation_work() + { + var root = FindRepositoryRoot(); + var header = File.ReadAllText(Path.Combine(root, "src", "iconpool.h")); + var implementation = File.ReadAllText(Path.Combine(root, "src", "iconpool.cpp")); + var refactoring = File.ReadAllText(Path.Combine(root, "refactoring.md")); + + // The queue contract prevents directory-sized background warming from + // retaining unbounded work or delaying the currently visible panel. + Assert.Multiple(() => + { + Assert.That(header, Does.Contain("#define ICON_POOL_QUEUE_SIZE 64")); + Assert.That(header, Does.Contain("enum EIconWorkPriority")); + Assert.That(header, Does.Contain("iwpVisible")); + Assert.That(header, Does.Contain("DWORD Generation")); + Assert.That(header, Does.Contain("volatile LONG InProgress")); + Assert.That(header, Does.Contain("struct CIconQueueMetrics")); + Assert.That(header, Does.Contain("BackpressureRejected")); + Assert.That(header, Does.Contain("VisiblePreemptions")); + Assert.That(header, Does.Contain("DWORD BeginGeneration()")); + Assert.That(header, Does.Contain("CancelObsoleteGenerations")); + Assert.That(header, Does.Contain("CIconQueueMetrics GetMetrics()")); + Assert.That(implementation, Does.Contain("IsSameWorkItem(queued, *item)")); + Assert.That(implementation, Does.Contain("item->Priority == iwpVisible")); + Assert.That(implementation, Does.Contain("Visible work may reclaim only dormant background slots")); + Assert.That(implementation, Does.Contain("CancelObsoleteGenerations(DWORD currentGeneration)")); + Assert.That(implementation, Does.Contain("candidate.Priority > WorkQueue[selected].Priority")); + Assert.That(implementation, Does.Contain("AllWorkCompletedEvent")); + Assert.That(implementation, Does.Contain("WaitForSingleObject(AllWorkCompletedEvent, timeoutMs)")); + Assert.That(implementation, Does.Not.Contain("QueueHead")); + Assert.That(implementation, Does.Not.Contain("QueueTail")); + Assert.That(implementation, Does.Not.Contain("Sleep(1)")); + Assert.That(refactoring, Does.Contain("### 50. Bound background work queues — Partially implemented")); + }); + } + + [Test] + public void Directory_listing_uses_bounded_checkpoints_and_a_retained_metadata_budget() + { + var root = FindRepositoryRoot(); + var navigation = File.ReadAllText(Path.Combine(root, "src", "fileswindow_navigation.cpp")); + var listBox = File.ReadAllText(Path.Combine(root, "src", "filesbox_rendering.cpp")); + var resourceIds = File.ReadAllText(Path.Combine(root, "src", "texts.rh2")); + var strings = File.ReadAllText(Path.Combine(root, "src", "lang", "texts.rc2")); + var refactoring = File.ReadAllText(Path.Combine(root, "refactoring.md")); + + // Large synthetic directories must encounter predictable cancellation + // checkpoints while the native panel retains no more than its budget. + const int batchSize = 256; + const int retainedBudget = 100000; + var checkpoints = Enumerable.Range(1, 1_000_000).Count(item => item % batchSize == 0); + var retainedItems = 0; + var metadataBudgetReached = false; + foreach (var _ in Enumerable.Range(1, 1_000_000)) + { + if (retainedItems >= retainedBudget - 1) + { + metadataBudgetReached = true; + break; + } + + retainedItems++; + } + + Assert.Multiple(() => + { + Assert.That(checkpoints, Is.EqualTo(1_000_000 / batchSize)); + Assert.That(retainedBudget, Is.GreaterThan(batchSize)); + Assert.That(metadataBudgetReached, Is.True); + Assert.That(retainedItems, Is.EqualTo(retainedBudget - 1)); + Assert.That(navigation, Does.Contain("#define DIRECTORY_ENUMERATION_BATCH_SIZE 256")); + Assert.That(navigation, Does.Contain("#define DIRECTORY_ENUMERATION_MAX_CACHED_ITEMS 100000")); + Assert.That(navigation, Does.Contain("IsDirectoryEnumerationBatchBoundary(NumberOfItemsInCurDir)")); + Assert.That(navigation, Does.Contain("Sleep(0);")); + Assert.That(navigation, Does.Contain("atBatchBoundary || GetTickCount() - lastEscCheckTime >= 200")); + Assert.That(navigation, Does.Contain("UserWantsToCancelSafeWaitWindow()")); + Assert.That(navigation, Does.Contain("Files->Count + Dirs->Count >= DIRECTORY_ENUMERATION_MAX_CACHED_ITEMS - 1")); + Assert.That(navigation, Does.Contain("directoryEnumerationLimitReached = TRUE")); + Assert.That(navigation, Does.Contain("StatusLine->SetText(LoadStr(IDS_DIRECTORYENUMERATIONLIMIT))")); + Assert.That(listBox, Does.Contain("SetItemsCount2(count)")); + Assert.That(listBox, Does.Contain("Parent->VisibleItemsArray.InvalidateArr()")); + Assert.That(resourceIds, Does.Contain("IDS_DIRECTORYENUMERATIONLIMIT")); + Assert.That(strings, Does.Contain("Directory listing was limited to 100,000 items")); + Assert.That(refactoring, Does.Contain("### 51. Set resource budgets for directory enumeration — Implemented")); + }); + } + + [Test] + public void Allocation_emergency_is_noninteractive_and_defers_recovery_to_the_ui_thread() + { + var root = FindRepositoryRoot(); + var allocatorHeader = File.ReadAllText(Path.Combine(root, "src", "common", "allochan.h")); + var allocator = File.ReadAllText(Path.Combine(root, "src", "common", "allochan.cpp")); + var journal = File.ReadAllText(Path.Combine(root, "src", "operation_journal.cpp")); + var operations = File.ReadAllText(Path.Combine(root, "src", "operations_core.cpp")); + var startup = File.ReadAllText(Path.Combine(root, "src", "app_entry.cpp")); + var mainWindowMessages = File.ReadAllText(Path.Combine(root, "src", "mainwnd_messages.cpp")); + var constants = File.ReadAllText(Path.Combine(root, "src", "consts.h")); + var refactoring = File.ReadAllText(Path.Combine(root, "refactoring.md")); + + // OOM cannot be safely synthesized in the executable; these source-level + // checks pin the non-interactive handler and its pre-registered UI handoff. + Assert.Multiple(() => + { + Assert.That(allocatorHeader, Does.Contain("SetAllocEmergencyNotificationWindow")); + Assert.That(allocatorHeader, Does.Contain("IsAllocationEmergencyActive")); + Assert.That(allocator, Does.Contain("const SIZE_T AllocEmergencyReserveSize = 64 * 1024")); + Assert.That(allocator, Does.Contain("HeapAlloc(GetProcessHeap(), 0, AllocEmergencyReserveSize)")); + Assert.That(allocator, Does.Contain("InterlockedCompareExchange(&AllocEmergencyActive, TRUE, FALSE)")); + Assert.That(allocator, Does.Contain("InterlockedExchangePointer((PVOID volatile*)&AllocEmergencyReserve, NULL)")); + Assert.That(allocator, Does.Contain("HeapFree(GetProcessHeap(), 0, reserve)")); + Assert.That(allocator, Does.Contain("NotifyAllocationEmergency()")); + Assert.That(allocator, Does.Contain("ActivateAllocationEmergency();")); + Assert.That(allocator, Does.Contain("PostMessage(window, message, 0, 0)")); + Assert.That(allocator, Does.Contain("return 0;")); + Assert.That(allocator, Does.Not.Contain("MessageBox(")); + Assert.That(allocator, Does.Not.Contain("Sleep(")); + Assert.That(allocator, Does.Not.Contain("while (")); + Assert.That(allocator, Does.Not.Contain("EnterCriticalSection(")); + Assert.That(allocator, Does.Not.Contain("TAllocEmergencyRecoveryCallback")); + Assert.That(allocator, Does.Not.Contain("TerminateProcess(")); + Assert.That(journal, Does.Contain("void COperationJournal::PersistEmergencyShutdownState()")); + Assert.That(journal, Does.Contain("OPERATION|memory-pressure")); + Assert.That(journal, Does.Contain("FILE_FLAG_WRITE_THROUGH")); + Assert.That(startup, Does.Contain("SetAllocEmergencyNotificationWindow(MainWindow->HWindow, WM_USER_ALLOCATION_EMERGENCY)")); + Assert.That(constants, Does.Contain("#define WM_USER_ALLOCATION_EMERGENCY WM_APP + 416")); + Assert.That(mainWindowMessages, Does.Contain("case WM_USER_ALLOCATION_EMERGENCY:")); + Assert.That(mainWindowMessages, Does.Contain("COperationJournal::PersistEmergencyShutdownState();")); + Assert.That(mainWindowMessages, Does.Contain("PostMessage(HWindow, WM_USER_FORCECLOSE_MAINWND, 0, 0);")); + Assert.That(operations, Does.Contain("if (IsAllocationEmergencyActive())")); + Assert.That(operations, Does.Contain("COperationsQueue::AddOperation")); + Assert.That(refactoring, Does.Contain("### 52. Reserve memory for graceful out-of-memory handling — Implemented")); + Assert.That(refactoring, Does.Contain("### 53. Remove modal UI and retry loops from the global allocation handler — Implemented")); + }); + } + + [Test] + public void Background_producers_bound_non_durable_work_and_report_backpressure() + { + var root = FindRepositoryRoot(); + var findHeader = File.ReadAllText(Path.Combine(root, "src", "find.h")); + var find = File.ReadAllText(Path.Combine(root, "src", "find.cpp")); + var findUi = File.ReadAllText(Path.Combine(root, "src", "find_dialog_ui.cpp")); + var brokerHeader = File.ReadAllText(Path.Combine(root, "src", "parserbroker.h")); + var broker = File.ReadAllText(Path.Combine(root, "src", "parserbroker.cpp")); + var ftpHeader = File.ReadAllText(Path.Combine(root, "src", "plugins", "ftp", "operats.h")); + var ftpQueue = File.ReadAllText(Path.Combine(root, "src", "plugins", "ftp", "operats1.cpp")); + var ftpDisk = File.ReadAllText(Path.Combine(root, "src", "plugins", "ftp", "operats5.cpp")); + var snooperHeader = File.ReadAllText(Path.Combine(root, "src", "snooper.h")); + var snooper = File.ReadAllText(Path.Combine(root, "src", "snooper.cpp")); + var refactoring = File.ReadAllText(Path.Combine(root, "refactoring.md")); + + // Each producer uses a policy appropriate to its semantics: discovery work + // may stop or fall back, while durable FTP intent is rejected atomically. + Assert.Multiple(() => + { + Assert.That(findHeader, Does.Contain("ResultLimitReached")); + Assert.That(findHeader, Does.Contain("DirectoryStackHighWaterMark")); + Assert.That(find, Does.Contain("#define FIND_MAX_RESULTS 100000")); + Assert.That(find, Does.Contain("#define FIND_MAX_DEFERRED_DIRECTORIES 4096")); + Assert.That(find, Does.Contain("Find result budget reached")); + Assert.That(find, Does.Contain("dirStackCount < FIND_MAX_DEFERRED_DIRECTORIES")); + Assert.That(find, Does.Contain("DirectoryStackFallbacks++")); + Assert.That(findUi, Does.Contain("GrepData.ResultLimitReached = FALSE")); + Assert.That(brokerHeader, Does.Contain("struct CParserBrokerQueueMetrics")); + Assert.That(brokerHeader, Does.Contain("GetQueueMetrics()")); + Assert.That(broker, Does.Contain("#define PARSER_BROKER_MAX_PENDING_REQUESTS 8")); + Assert.That(broker, Does.Contain("pending > PARSER_BROKER_MAX_PENDING_REQUESTS")); + Assert.That(broker, Does.Contain("InterlockedDecrement(&PendingRequests)")); + Assert.That(ftpHeader, Does.Contain("#define FTP_OPERATION_QUEUE_LIMIT 100000")); + Assert.That(ftpHeader, Does.Contain("#define FTP_DISK_WORK_QUEUE_LIMIT 512")); + Assert.That(ftpHeader, Does.Contain("GetQueueMetrics")); + Assert.That(ftpHeader, Does.Contain("GetWorkQueueMetrics")); + Assert.That(ftpQueue, Does.Contain("Items.Count >= FTP_OPERATION_QUEUE_LIMIT")); + Assert.That(ftpQueue, Does.Contain("bounded operation queue rejected expansion")); + Assert.That(ftpDisk, Does.Contain("Work.Count >= FTP_DISK_WORK_QUEUE_LIMIT")); + Assert.That(ftpDisk, Does.Contain("bounded disk queue rejected work")); + Assert.That(snooperHeader, Does.Contain("struct CDirectoryRefreshMetrics")); + Assert.That(snooper, Does.Contain("GetDirectoryRefreshMetrics()")); + Assert.That(snooper, Does.Contain("SnooperRefreshWaits")); + Assert.That(snooper, Does.Contain("at most one live snooper refresh outstanding")); + Assert.That(refactoring, Does.Contain("Parser broker admission is capped at eight")); + }); + } + + [Test] + public void Plugin_callbacks_are_contained_and_the_failing_plugin_is_deferred_for_unload() + { + var root = FindRepositoryRoot(); + var header = File.ReadAllText(Path.Combine(root, "src", "plugins.h")); + var loader = File.ReadAllText(Path.Combine(root, "src", "plugins_loading.cpp")); + var registry = File.ReadAllText(Path.Combine(root, "src", "plugins_interface.cpp")); + var refactoring = File.ReadAllText(Path.Combine(root, "refactoring.md")); + + Assert.Multiple(() => + { + Assert.That(header, Does.Contain("#define PLUGIN_CALLBACK")); + Assert.That(header, Does.Contain("HandlePluginCallbackException")); + Assert.That(header, Does.Contain("PLUGIN_CALLBACK(Interface, \"Connect\"")); + Assert.That(header, Does.Contain("PLUGIN_CALLBACK(Interface, \"GetInterfaceForFS\"")); + Assert.That(loader, Does.Contain("PLUGIN_CALLBACK(Iface, \"ListCurrentPath\"")); + Assert.That(loader, Does.Contain("PLUGIN_CALLBACK(Interface, \"CloseFS\"")); + Assert.That(loader, Does.Contain("PLUGIN_CALLBACK(Plugin, \"ReleasePluginData\"")); + Assert.That(loader, Does.Contain("plugin->LoadOnStart = FALSE")); + Assert.That(loader, Does.Contain("plugin->ShouldUnload = TRUE")); + Assert.That(loader, Does.Contain("WM_USER_POSTCMDORUNLOADPLUGIN")); + Assert.That(loader, Does.Not.Contain("TerminateProcess(GetCurrentProcess(), 1)")); + Assert.That(registry, Does.Contain("CPlugins::GetPluginData(const void* pluginInterface)")); + Assert.That(refactoring, Does.Contain("### 23. Add failure barriers around every plug-in callback — Implemented")); + }); + } + + [Test] + public void Configuration_saves_stage_validate_and_atomically_select_a_generation() + { + var root = FindRepositoryRoot(); + var configuration = File.ReadAllText(Path.Combine(root, "src", "mainwnd_config.cpp")); + var registryWork = File.ReadAllText(Path.Combine(root, "src", "regwork.cpp")); + var plugins = File.ReadAllText(Path.Combine(root, "src", "plugins_loading.cpp")); + var startup = File.ReadAllText(Path.Combine(root, "src", "app_entry.cpp")); + var architecture = File.ReadAllText(Path.Combine(root, "architecture.md")); + var refactoring = File.ReadAllText(Path.Combine(root, "refactoring.md")); + + Assert.Multiple(() => + { + Assert.That(configuration, Does.Contain("Configuration Generations")); + Assert.That(configuration, Does.Contain("Active Generation")); + Assert.That(configuration, Does.Contain("Transaction Complete")); + Assert.That(configuration, Does.Contain("Transaction Checksum")); + Assert.That(configuration, Does.Contain("CalculateConfigurationChecksum")); + Assert.That(configuration, Does.Contain("IsCommittedConfigurationGeneration")); + Assert.That(configuration, Does.Contain("BeginConfigurationTransaction")); + Assert.That(configuration, Does.Contain("CommitConfigurationTransaction")); + // The wrapper retains RegFlushKey durability while counting writes + // for transactional-save fault injection. + Assert.That(configuration, Does.Contain("FlushConfigurationRegistryKey(generationKey) == ERROR_SUCCESS")); + Assert.That(registryWork, Does.Contain("LONG result = RegFlushKey(key)")); + Assert.That(configuration, Does.Contain("SetValue(storeKey, CONFIGURATION_ACTIVE_GENERATION_REG")); + Assert.That(configuration, Does.Contain("RetirePreviousConfigurationGenerationAfterSuccessfulStartup")); + Assert.That(configuration, Does.Contain("OpenCommittedConfigurationGeneration(storeKey, fallbackGeneration")); + Assert.That(startup, Does.Contain("SetConfigurationStoreRoot(SALAMANDER_ROOT_REG)")); + Assert.That(startup, Does.Contain("SelectCommittedConfigurationGeneration()")); + Assert.That(plugins, Does.Contain("MainWindow->SaveConfig();")); + Assert.That(plugins, Does.Contain("MainWindow->SaveConfig(parent);")); + Assert.That(plugins, Does.Not.Contain("CreateKey(HKEY_CURRENT_USER, SALAMANDER_ROOT_REG"), + "Plug-in commits must not mutate the checksum-protected active generation."); + Assert.That(refactoring, Does.Contain("### 24. Make configuration saves transactional — Implemented")); + Assert.That(architecture, Does.Contain("the root's `Active Generation` DWORD")); + }); + } + + [Test] + public void Configuration_profiles_are_schema_versioned_migrated_and_validated_before_loading() + { + var root = FindRepositoryRoot(); + var configuration = File.ReadAllText(Path.Combine(root, "src", "mainwnd_config.cpp")); + var startup = File.ReadAllText(Path.Combine(root, "src", "app_entry.cpp")); + var architecture = File.ReadAllText(Path.Combine(root, "architecture.md")); + var refactoring = File.ReadAllText(Path.Combine(root, "refactoring.md")); + + Assert.Multiple(() => + { + Assert.That(configuration, Does.Contain("Configuration Schema Version")); + Assert.That(configuration, Does.Contain("CONFIGURATION_SCHEMA_VERSION = 1")); + Assert.That(configuration, Does.Contain("MigrateConfigurationSchema")); + Assert.That(configuration, Does.Contain("ValidateCompleteConfigurationSchema")); + Assert.That(configuration, Does.Contain("ValidateWindowConfigurationSchema")); + Assert.That(configuration, Does.Contain("schemaVersion > CONFIGURATION_SCHEMA_VERSION")); + Assert.That(configuration, Does.Contain("configVersion == THIS_CONFIG_VERSION")); + Assert.That(configuration, Does.Contain("left < right && top < bottom")); + Assert.That(configuration, Does.Contain("(separatedDrives & ~visibleDrives) == 0")); + Assert.That(configuration, Does.Contain("ValidateRequiredDwordRange(viewerKey, \"Tabelator Size\", 1, 30)")); + Assert.That(configuration, Does.Contain("GetConfigurationSchemaDiagnostic")); + Assert.That(configuration, Does.Contain("The default profile will be used.")); + Assert.That(startup, Does.Contain("GetConfigurationSchemaDiagnostic()")); + Assert.That(startup, Does.Contain("Open Salamander Configuration")); + Assert.That(architecture, Does.Contain("Each transactional generation has a schema version.")); + Assert.That(refactoring, Does.Contain("### 25. Version and validate the complete configuration schema — Implemented")); + }); + } + + [Test] + public void Metadata_preservation_contract_records_losses_and_gates_move_source_deletion() + { + var root = FindRepositoryRoot(); + var workerHeader = File.ReadAllText(Path.Combine(root, "src", "worker.h")); + var worker = File.ReadAllText(Path.Combine(root, "src", "worker.cpp")); + var planner = File.ReadAllText(Path.Combine(root, "src", "fileswindow_operations.cpp")); + var copy = File.ReadAllText(Path.Combine(root, "src", "async_copy.cpp")); + var operations = File.ReadAllText(Path.Combine(root, "src", "operations_core.cpp")); + var dialogs = File.ReadAllText(Path.Combine(root, "src", "dialogs_file_ops.cpp")); + var strings = File.ReadAllText(Path.Combine(root, "src", "lang", "texts.rc2")); + var architecture = File.ReadAllText(Path.Combine(root, "architecture.md")); + var refactoring = File.ReadAllText(Path.Combine(root, "refactoring.md")); + + Assert.Multiple(() => + { + Assert.That(workerHeader, Does.Contain("enum EMetadataPreservation")); + Assert.That(workerHeader, Does.Contain("mpRequired")); + Assert.That(workerHeader, Does.Contain("mpBestEffort")); + Assert.That(workerHeader, Does.Contain("mpUnsupported")); + Assert.That(workerHeader, Does.Contain("enum EMetadataTargetFileSystem")); + Assert.That(workerHeader, Does.Contain("mtfsNtfs")); + Assert.That(workerHeader, Does.Contain("mtfsRefs")); + Assert.That(workerHeader, Does.Contain("mtfsFat")); + Assert.That(workerHeader, Does.Contain("mtfsSmb")); + Assert.That(workerHeader, Does.Contain("struct CMetadataLossRecord")); + Assert.That(workerHeader, Does.Contain("CMetadataLossRecord MetadataLosses")); + Assert.That(worker, Does.Contain("PlannedMetadataLosses = mmlNone")); + Assert.That(planner, Does.Contain("GetMetadataTargetFileSystem(targetPath)")); + Assert.That(planner, Does.Contain("script->PlannedMetadataLosses |= mmlAlternateDataStreams")); + Assert.That(planner, Does.Contain("script->PlannedMetadataLosses |= mmlSecurity")); + // The contract must consider the target filesystem as well as the + // operation type before it decides which losses are acceptable. + Assert.That(copy, Does.Contain("GetMetadataPreservationContract(EMetadataOperation operation,")); + Assert.That(copy, Does.Contain("RecordMetadataLoss(dlgData, mmlAlternateDataStreams")); + Assert.That(copy, Does.Contain("RecordMetadataLoss(dlgData, mmlLastWriteTime")); + Assert.That(copy, Does.Contain("RecordMetadataLoss(dlgData, mmlSecurity")); + Assert.That(copy, Does.Contain("RecordPlannedMetadataLosses(dlgData, script, op->SourceName, op->TargetName)")); + Assert.That(copy, Does.Contain("ConfirmMetadataLossesBeforeSourceDeletion(hProgressDlg, dlgData, op->SourceName, op->TargetName)")); + Assert.That(copy.IndexOf("ConfirmMetadataLossesBeforeSourceDeletion(hProgressDlg, dlgData, op->SourceName, op->TargetName)", StringComparison.Ordinal), + Is.LessThan(copy.IndexOf("DeleteFileWithVerifiedIdentity(op->SourceName", StringComparison.Ordinal)), + "A cross-volume move must ask about recorded metadata loss before deleting its source."); + Assert.That(operations, Does.Contain("RecordPlannedMetadataLosses(dlgData, script, op->SourceName, NULL)")); + Assert.That(operations, Does.Contain("ConfirmMetadataLossesBeforeSourceDeletion(hProgressDlg, dlgData, op->SourceName, NULL)")); + Assert.That(dialogs, Does.Contain("case 13:")); + Assert.That(dialogs, Does.Contain("MB_YESNO | MB_DEFBUTTON2")); + Assert.That(strings, Does.Contain("IDS_METADATALOSS_BEFORESOURCEDELETE")); + // Reparse policy precedes this contract, so the documented section + // number advances while the preservation matrix remains required. + Assert.That(architecture, Does.Contain("#### 5.2.2 Metadata preservation contract")); + Assert.That(architecture, Does.Contain("Copy to NTFS")); + Assert.That(architecture, Does.Contain("Copy to ReFS")); + Assert.That(architecture, Does.Contain("Copy to FAT/FAT32/exFAT")); + Assert.That(architecture, Does.Contain("Copy to SMB")); + Assert.That(architecture, Does.Contain("only an explicit **Yes** allows source deletion")); + Assert.That(refactoring, Does.Contain("### 31. Publish an explicit metadata preservation contract — Implemented")); + }); + } + + [Test] + public void Security_descriptor_copy_uses_the_privilege_aware_preservation_matrix() + { + var root = FindRepositoryRoot(); + var copy = File.ReadAllText(Path.Combine(root, "src", "async_copy.cpp")); + var architecture = File.ReadAllText(Path.Combine(root, "architecture.md")); + var refactoring = File.ReadAllText(Path.Combine(root, "refactoring.md")); + + Assert.Multiple(() => + { + Assert.That(copy, Does.Contain("GetNamedSecurityInfoW(targetNameSecW, SE_FILE_OBJECT,")); + Assert.That(copy, Does.Contain("PSID previousOwner = NULL")); + Assert.That(copy, Does.Contain("GainWriteOwnerAccess()")); + Assert.That(copy, Does.Contain("BOOL changingOwnerOrGroup")); + Assert.That(copy, Does.Contain("else if (!changingOwnerOrGroup)")); + Assert.That(copy, Does.Contain("ERROR_PRIVILEGE_NOT_HELD")); + Assert.That(copy, Does.Contain("BOOL attemptedWrite = FALSE")); + Assert.That(copy, Does.Contain("if (!attemptedWrite)")); + Assert.That(copy, Does.Contain("SetDaclWithInheritance(targetNameSecW, srcDACL, inheritedDacl)")); + Assert.That(copy, Does.Contain("IsSecurityDescriptorPreserved(")); + Assert.That(copy, Does.Contain("AreEqualExplicitAces(")); + Assert.That(copy, Does.Contain("INHERITED_ACE")); + Assert.That(copy, Does.Contain("a NULL DACL grants full access and must never be approximated")); + Assert.That(copy, Does.Contain("inaccessible source descriptor: report a best-effort metadata loss without touching the target")); + Assert.That(copy, Does.Contain("inaccessible target descriptor: do not attempt a blind, partial repair")); + Assert.That(copy, Does.Contain("unable to restore the target descriptor after a partial update")); + Assert.That(copy, Does.Not.Contain("AddAccessAllowedAce(allowChPermDACL"), + "The former temporary permissive DACL fallback must not return."); + Assert.That(copy.IndexOf("PSID previousOwner = NULL", StringComparison.Ordinal), + Is.LessThan(copy.IndexOf(" GainWriteOwnerAccess();", StringComparison.Ordinal)), + "The target descriptor must be snapshotted before privilege-dependent mutation."); + Assert.That(architecture, Does.Contain("##### Security descriptor privilege matrix")); + Assert.That(architecture, Does.Contain("`SeRestorePrivilege` enabled")); + Assert.That(architecture, Does.Contain("explicit deny ACEs")); + Assert.That(architecture, Does.Contain("Source or target descriptor inaccessible")); + Assert.That(architecture, Does.Contain("FAT/FAT32/exFAT target")); + Assert.That(architecture, Does.Contain("restore the target snapshot")); + Assert.That(refactoring, Does.Contain("### 33. Verify ACL and ownership preservation under privilege variation — Implemented")); + }); + } + + [Test] + public void Release_diagnostic_ring_is_bounded_sanitized_and_reported_only_through_the_existing_consent_flow() + { + var root = FindRepositoryRoot(); + var diagnostics = File.ReadAllText(Path.Combine(root, "src", "release_diagnostics.cpp")); + var diagnosticsHeader = File.ReadAllText(Path.Combine(root, "src", "release_diagnostics.h")); + var worker = File.ReadAllText(Path.Combine(root, "src", "worker.cpp")); + var operations = File.ReadAllText(Path.Combine(root, "src", "operations_core.cpp")); + var copy = File.ReadAllText(Path.Combine(root, "src", "async_copy.cpp")); + var plugins = File.ReadAllText(Path.Combine(root, "src", "plugins_loading.cpp")); + var callStack = File.ReadAllText(Path.Combine(root, "src", "callstk.cpp")); + var bugReport = File.ReadAllText(Path.Combine(root, "src", "bugreprt.cpp")); + var project = File.ReadAllText(Path.Combine(root, "src", "vcxproj", "salamand.vcxproj")); + var reporting = File.ReadAllText(Path.Combine(root, "reporting.md")); + var refactoring = File.ReadAllText(Path.Combine(root, "refactoring.md")); + + // Pin the bounded storage, safe publication, producer coverage, and + // consent-gated local sidecar so release diagnostics cannot regress to debug-only traces. + Assert.Multiple(() => + { + Assert.That(diagnostics, Does.Contain("kReleaseDiagnosticCapacity = 128")); + Assert.That(diagnostics, Does.Contain("CReleaseDiagnosticEntry ReleaseDiagnosticEntries[kReleaseDiagnosticCapacity]")); + Assert.That(diagnostics, Does.Contain("InterlockedCompareExchange(&entry.Sequence, -sequence, published)")); + Assert.That(diagnostics, Does.Contain("MemoryBarrier()")); + Assert.That(diagnostics, Does.Contain("InterlockedCompareExchange(&entry.Sequence, sequence, -sequence)")); + Assert.That(diagnostics, Does.Contain("CopySanitizedLabel")); + Assert.That(diagnostics, Does.Contain("BOOL WriteDiagnosticText")); + Assert.That(diagnostics, Does.Contain("*scan == '\\\\' || *scan == '/'")); + Assert.That(diagnostics, Does.Contain("RecordReleaseDiagnosticOperationTransition")); + Assert.That(diagnostics, Does.Contain("RecordReleaseDiagnosticWait")); + Assert.That(diagnostics, Does.Contain("RecordReleaseDiagnosticRetry")); + Assert.That(diagnostics, Does.Contain("RecordReleaseDiagnosticPluginIdentity")); + Assert.That(diagnosticsHeader, Does.Contain("ExportReleaseDiagnosticRingBuffer")); + Assert.That(worker, Does.Contain("RecordReleaseDiagnosticOperationTransition")); + Assert.That(operations, Does.Contain("RecordReleaseDiagnosticWait(\"worker_startup\"")); + Assert.That(copy, Does.Contain("RecordReleaseDiagnosticRetry(\"copy_network_read\")")); + Assert.That(plugins, Does.Contain("RecordReleaseDiagnosticPluginIdentity(DLLName)")); + Assert.That(bugReport, Does.Contain("PrintReleaseDiagnosticRingBuffer(PrintLine, param)")); + Assert.That(callStack, Does.Contain("lstrcpyn(extension, \".OPS\"")); + Assert.That(callStack, Does.Contain("ExportReleaseDiagnosticRingBuffer(diagnosticPath)")); + Assert.That(project, Does.Contain("..\\release_diagnostics.cpp")); + Assert.That(project, Does.Contain("..\\release_diagnostics.h")); + Assert.That(reporting, Does.Contain("Release diagnostic ring (`.OPS`)")); + Assert.That(reporting, Does.Contain("View Report** provides the local export without sending it")); + Assert.That(refactoring, Does.Contain("### 54. Add a bounded release-build diagnostic ring buffer — Implemented")); + }); + } + + [Test] + public void Network_operations_have_phase_deadlines_cancellation_and_failure_classification() + { + var root = FindRepositoryRoot(); + var checkver = File.ReadAllText(Path.Combine(root, "src", "plugins", "checkver", "internet.cpp")); + var checkverHeader = File.ReadAllText(Path.Combine(root, "src", "plugins", "checkver", "checkver.h")); + var checkverPlugin = File.ReadAllText(Path.Combine(root, "src", "plugins", "checkver", "checkver.cpp")); + var checkverDialog = File.ReadAllText(Path.Combine(root, "src", "plugins", "checkver", "dialogs.cpp")); + var ftp = File.ReadAllText(Path.Combine(root, "src", "plugins", "ftp", "ctrlcon1.cpp")); + var ftpTls = File.ReadAllText(Path.Combine(root, "src", "plugins", "ftp", "ssl.cpp")); + var upload = File.ReadAllText(Path.Combine(root, "src", "salmon", "upload.cpp")); + var refactoring = File.ReadAllText(Path.Combine(root, "refactoring.md")); + + Assert.Multiple(() => + { + Assert.That(checkver, Does.Contain("kResolveAndConnectTimeoutMs = 15000")); + Assert.That(checkver, Does.Contain("kSendTimeoutMs = 15000")); + Assert.That(checkver, Does.Contain("kReceiveTimeoutMs = 30000")); + Assert.That(checkver, Does.Contain("INTERNET_OPTION_CONNECT_TIMEOUT")); + Assert.That(checkver, Does.Contain("INTERNET_OPTION_SEND_TIMEOUT")); + Assert.That(checkver, Does.Contain("INTERNET_OPTION_DATA_RECEIVE_TIMEOUT")); + Assert.That(checkverHeader, Does.Contain("void CancelDownloadThread()")); + Assert.That(checkver, Does.Contain("InterlockedExchange(&DownloadCancelled, TRUE)")); + Assert.That(checkver, Does.Contain("HINTERNET ActiveDownloadSession = NULL")); + Assert.That(checkver, Does.Contain("HINTERNET ActiveDownloadRequest = NULL")); + Assert.That(checkver, Does.Contain("InternetCloseHandle(request)")); + Assert.That(checkver, Does.Contain("InternetCloseHandle(session)")); + Assert.That(checkverPlugin, Does.Contain("CancelDownloadThread(); // release WinINet waits")); + Assert.That(checkverDialog, Does.Contain("CancelDownloadThread(); // close the active network handle")); + Assert.That(checkver, Does.Contain("ERROR_INTERNET_TIMEOUT")); + Assert.That(checkver, Does.Contain("ERROR_INTERNET_LOGIN_FAILURE")); + Assert.That(checkver, Does.Contain("Protocol or TLS failure")); + Assert.That(ftp, Does.Contain("int resolveTimeout")); + Assert.That(ftp, Does.Contain("int connectTimeout")); + Assert.That(ftp, Does.Contain("int protocolTimeout")); + Assert.That(ftp, Does.Contain("IDS_GETIPTIMEOUT")); + Assert.That(ftp, Does.Contain("IDS_OPENCONTIMEOUT")); + Assert.That(ftp, Does.Contain("CloseSocket(NULL)")); + Assert.That(ftpTls, Does.Contain("DWORD tlsHandshakeTimeout = Config.GetServerRepliesTimeout() * 1000")); + Assert.That(ftpTls, Does.Contain("SO_RCVTIMEO")); + Assert.That(ftpTls, Does.Contain("SO_SNDTIMEO")); + Assert.That(ftpTls, Does.Contain("TLS handshake deadline expired.")); + Assert.That(upload, Does.Contain("WinHttpSetTimeouts(session, 15000, 15000, 30000, 30000)")); + Assert.That(upload, Does.Contain("HINTERNET ActiveUploadSession = NULL")); + Assert.That(upload, Does.Contain("RegisterActiveUploadSession(session, uploadParams)")); + Assert.That(upload, Does.Contain("InterlockedExchange(¶ms->Cancelled, TRUE)")); + Assert.That(upload, Does.Contain("WinHttpCloseHandle(request)")); + Assert.That(upload, Does.Contain("WinHttpCloseHandle(session)")); + Assert.That(upload, Does.Contain("Network timeout")); + Assert.That(upload, Does.Contain("Authentication failure")); + Assert.That(upload, Does.Contain("Protocol or TLS failure")); + Assert.That(refactoring, Does.Contain("### 58. Apply deadlines and cancellation to all network operations — Implemented")); + }); + } + + [Test] + public void Ftp_downloads_stage_identity_validate_resume_and_publish_only_after_a_durable_commit() + { + var root = FindRepositoryRoot(); + var control = File.ReadAllText(Path.Combine(root, "src", "plugins", "ftp", "ctrlcon5.cpp")); + var dataConnection = File.ReadAllText(Path.Combine(root, "src", "plugins", "ftp", "datacon1.cpp")); + var disk = File.ReadAllText(Path.Combine(root, "src", "plugins", "ftp", "operats5.cpp")); + var refactoring = File.ReadAllText(Path.Combine(root, "refactoring.md")); + + // Network loss must leave an explicitly incomplete side file; only a fully verified transfer can replace the cache entry. + Assert.Multiple(() => + { + Assert.That(control, Does.Contain("FtpIncompleteSuffix[] = \".ftp-incomplete\"")); + Assert.That(control, Does.Contain("CFTPTransactionalDownloadMetadata")); + Assert.That(control, Does.Contain("RemoteSize")); + Assert.That(control, Does.Contain("RemoteDateAndTimeValid")); + Assert.That(control, Does.Contain("WriteTransactionalDownloadMetadata")); + Assert.That(control, Does.Contain("FILE_FLAG_WRITE_THROUGH")); + Assert.That(control, Does.Contain("memcmp(&actual, &expected, sizeof(actual)) == 0")); + Assert.That(control, Does.Contain("size.QuadPart < 0 || (unsigned __int64)size.QuadPart > expected.RemoteSize")); + Assert.That(control, Does.Contain("!asciiMode && fileSizeInBytes != CQuadWord(-1, -1)")); + Assert.That(control, Does.Contain("ftpcmdRestartTransfer")); + Assert.That(control, Does.Contain("FTP_D1_PARTIALSUCCESS")); + Assert.That(control, Does.Contain("CommitTransactionalDownload(stagedTargetName, tgtFileName, metadataName")); + Assert.That(control, Does.Contain("FlushFileBuffers(stagedFile)")); + Assert.That(control, Does.Contain("MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH")); + Assert.That(dataConnection, Does.Contain("TgtDiskFileResumeOffset = resumeOffset")); + Assert.That(dataConnection, Does.Contain("DiskWork.AppendToFile = TgtDiskFile == NULL && TgtDiskFileAppend")); + Assert.That(disk, Does.Contain("localWork.AppendToFile ? OPEN_ALWAYS : CREATE_ALWAYS")); + Assert.That(disk, Does.Contain("SetFilePointerEx(f, offset, NULL, FILE_BEGIN)")); + Assert.That(refactoring, Does.Contain("### 59. Make FTP transfers transactional and resumable safely — Partially implemented")); + }); + } + + [Test] + public void Ftp_certificate_exceptions_are_endpoint_bound_expiring_and_pinned() + { + var root = FindRepositoryRoot(); + var tlsHeader = File.ReadAllText(Path.Combine(root, "src", "plugins", "ftp", "ssl.h")); + var tls = File.ReadAllText(Path.Combine(root, "src", "plugins", "ftp", "ssl.cpp")); + var control = File.ReadAllText(Path.Combine(root, "src", "plugins", "ftp", "ctrlcon1.cpp")); + var operationDialog = File.ReadAllText(Path.Combine(root, "src", "plugins", "ftp", "dialogs5.cpp")); + var configuration = File.ReadAllText(Path.Combine(root, "src", "plugins", "ftp", "ftp.cpp")); + var dialogResource = File.ReadAllText(Path.Combine(root, "src", "plugins", "ftp", "lang", "lang.rc")); + var refactoring = File.ReadAllText(Path.Combine(root, "refactoring.md")); + + // Pin every trust dimension so chain failures can be explicitly accepted without making the decision reusable. + Assert.Multiple(() => + { + Assert.That(tlsHeader, Does.Contain("enum CCertificateExceptionScope")); + Assert.That(tlsHeader, Does.Contain("cesSession")); + Assert.That(tlsHeader, Does.Contain("cesPersistent")); + Assert.That(tlsHeader, Does.Contain("MatchesEndpoint(LPCSTR host, unsigned short port) const")); + Assert.That(tlsHeader, Does.Contain("RememberException(CCertificateExceptionScope scope) const")); + Assert.That(tlsHeader, Does.Contain("IsCurrentException() const")); + Assert.That(tls, Does.Contain("#define FTP_CERTIFICATE_EXCEPTION_LIMIT 64")); + Assert.That(tls, Does.Contain("#define FTP_CERTIFICATE_SESSION_EXCEPTION_HOURS 8")); + Assert.That(tls, Does.Contain("#define FTP_CERTIFICATE_PERSISTENT_EXCEPTION_DAYS 30")); + Assert.That(tls, Does.Contain("FTP_CERTIFICATE_EXCEPTION_SPKI")); + Assert.That(tls, Does.Contain("FTP_CERTIFICATE_EXCEPTION_CERTIFICATE")); + Assert.That(tls, Does.Contain("CryptHashPublicKeyInfo")); + Assert.That(tls, Does.Contain("CryptHashCertificate(0, CALG_SHA_256")); + Assert.That(tls, Does.Contain("_stricmp(Records[i].Host, host) == 0 && Records[i].Port == port")); + Assert.That(tls, Does.Contain("memcmp(Records[i].SPKIFingerprint, spkiFingerprint")); + Assert.That(tls, Does.Contain("memcmp(Records[i].CertificateFingerprint, certificateFingerprint")); + Assert.That(tls, Does.Contain("IsExpired(Records[i].ExpiresAt)")); + Assert.That(tls, Does.Contain("record.Scope == cesPersistent")); + Assert.That(tls, Does.Contain("pCertificate->IsVerified() || pCertificate->IsCurrentException()")); + Assert.That(tls, Does.Contain("IsDataConnection || pCertificate->MatchesEndpoint(HostAddress, HostPort)")); + Assert.That(tls, Does.Contain("CertificateExceptions.IsAccepted(HostAddress, HostPort, der, derLength, &endpointKnown)")); + Assert.That(tls, Does.Contain("else if (endpointKnown)")); + Assert.That(control, Does.Contain("unverifiedCert->RememberException(certificateDialog.RememberException() ? cesPersistent : cesSession)")); + Assert.That(operationDialog, Does.Contain("unverifiedCertificate->RememberException(certificateDialog.RememberException() ? cesPersistent : cesSession)")); + Assert.That(configuration, Does.Contain("LoadCertificateExceptions(regKey, registry)")); + Assert.That(configuration, Does.Contain("SaveCertificateExceptions(regKey, registry)")); + Assert.That(dialogResource, Does.Contain("Remember this exception for 30 days")); + Assert.That(refactoring, Does.Contain("### 60. Strengthen FTP certificate exception storage — Implemented")); + }); + } + + [Test] + public void Bundled_7zip_uses_26_02_and_preserves_upgrade_compatibility_contract() + { + var root = FindRepositoryRoot(); + var version = File.ReadAllText(Path.Combine(root, "src", "plugins", "7zip", "7za", "c", "7zVersion.h")); + var project = File.ReadAllText(Path.Combine(root, "src", "plugins", "7zip", "vcxproj", "7ZA", "7za.dll.vcxproj")); + var wrapper = File.ReadAllText(Path.Combine(root, "src", "plugins", "7zip", "7za", "cpp", "7zip", "UI", "7zwrapper", "7zwrapper.cpp")); + var threading = File.ReadAllText(Path.Combine(root, "src", "plugins", "7zip", "7za", "c", "LzFindMt.c")); + var extract = File.ReadAllText(Path.Combine(root, "src", "plugins", "7zip", "7za", "cpp", "7zip", "Archive", "7z", "7zExtract.cpp")); + var openCallback = File.ReadAllText(Path.Combine(root, "src", "plugins", "7zip", "open.h")); + var extractCallback = File.ReadAllText(Path.Combine(root, "src", "plugins", "7zip", "extract.h")); + var updateCallback = File.ReadAllText(Path.Combine(root, "src", "plugins", "7zip", "update.h")); + var retryableStreams = File.ReadAllText(Path.Combine(root, "src", "plugins", "7zip", "FStreams.h")); + var pluginProject = File.ReadAllText(Path.Combine(root, "src", "plugins", "7zip", "vcxproj", "7zip.vcxproj")); + var record = File.ReadAllText(Path.Combine(root, "src", "plugins", "7zip", "doc", "upgrade-26.02.md")); + var refactoring = File.ReadAllText(Path.Combine(root, "refactoring.md")); + + // Pin the upgraded parser and the compatibility gate so later vendor refreshes cannot silently drop it. + Assert.Multiple(() => + { + Assert.That(version, Does.Contain("#define MY_VERSION_NUMBERS \"26.02\"")); + Assert.That(version, Does.Contain("#define MY_DATE \"2026-06-25\"")); + Assert.That(project, Does.Contain("Lzma2DecMt.c")); + Assert.That(project, Does.Contain("ZstdDec.c")); + Assert.That(project, Does.Contain("LzfseDecoder.cpp")); + Assert.That(wrapper, Does.Contain("Z7_IFACES_IMP_UNK_2(IArchiveUpdateCallback2, ICryptoGetTextPassword2)")); + Assert.That(wrapper, Does.Contain("Create_ALWAYS(archiveName)")); + Assert.That(threading, Does.Contain("RunThreadWithCallStackObject")); + Assert.That(extract, Does.Contain("FlushCorrupted")); + Assert.That(openCallback, Does.Contain("Z7_IFACES_IMP_UNK_3(IArchiveOpenCallback")); + Assert.That(extractCallback, Does.Contain("Z7_IFACES_IMP_UNK_2(IArchiveExtractCallback")); + Assert.That(updateCallback, Does.Contain("Z7_IFACE_COM7_IMP(IArchiveUpdateCallback)")); + Assert.That(retryableStreams, Does.Contain("class CRetryableOutFileStream : public IOutStream")); + Assert.That(retryableStreams, Does.Contain("CMyComPtr Stream")); + Assert.That(pluginProject, Does.Contain("filename.cpp")); + Assert.That(pluginProject, Does.Contain("timeutils.cpp")); + Assert.That(record, Does.Contain("C7502DD4557481F52CCF1B3E680329F1FDD207E79A25544AFEB3106325474944")); + Assert.That(record, Does.Contain("fuzz regressions")); + Assert.That(record, Does.Contain("extraction snapshots")); + Assert.That(refactoring, Does.Contain("### 61. Upgrade the bundled 7-Zip code — Partially implemented")); + }); + } + + [Test] + public void Bundled_sqlite_uses_a_verified_current_amalgamation_and_exercises_the_owned_database_recovery_contract() + { + var root = FindRepositoryRoot(); + var amalgamation = File.ReadAllText(Path.Combine(root, "src", "common", "dep", "sqlite", "sqlite3.c")); + var header = File.ReadAllText(Path.Combine(root, "src", "plugins", "shared", "sqlite", "sqlite3.h")); + var project = File.ReadAllText(Path.Combine(root, "src", "vcxproj", "sqlite", "sqlite_base.props")); + var policy = File.ReadAllText(Path.Combine(root, "src", "common", "dep", "sqlite", "readme.txt")); + var externalReader = File.ReadAllText(Path.Combine(root, "src", "shiconov.cpp")); + var recoveryTest = File.ReadAllText(Path.Combine(root, "tools", "test-sqlite-recovery.ps1")); + var workflow = File.ReadAllText(Path.Combine(root, ".github", "workflows", "pr-msbuild.yml")); + var refactoring = File.ReadAllText(Path.Combine(root, "refactoring.md")); + + // Keep the vendored binary, ownership boundary, and executable recovery probe aligned after future upgrades. + Assert.Multiple(() => + { + Assert.That(amalgamation, Does.Contain("#define SQLITE_VERSION \"3.53.4\"")); + Assert.That(amalgamation, Does.Contain("bf7c7f30031888f4e796e429ab3978879485813aaca6f641c7b33e4e09459bcc")); + Assert.That(header, Does.Contain("#define SQLITE_VERSION \"3.53.4\"")); + Assert.That(header, Does.Contain("bf7c7f30031888f4e796e429ab3978879485813aaca6f641c7b33e4e09459bcc")); + Assert.That(project, Does.Contain("SQLITE_DQS=0")); + Assert.That(project, Does.Contain("SQLITE_ENABLE_API_ARMOR")); + Assert.That(project, Does.Contain("SQLITE_DEFAULT_SYNCHRONOUS=2")); + Assert.That(project, Does.Contain("SQLITE_DEFAULT_WAL_SYNCHRONOUS=2")); + Assert.That(project, Does.Contain("SQLITE_DEFAULT_WAL_AUTOCHECKPOINT=1000")); + Assert.That(policy, Does.Contain("628a44cfe82c66aed1ccbbe85a562d2e33ebe64b3288981ed76285612227934e")); + Assert.That(policy, Does.Contain("PRAGMA integrity_check")); + Assert.That(policy, Does.Contain("BEGIN IMMEDIATE")); + // Vendor policy validation must not depend on the checkout's configured line-ending convention. + Assert.That(policy.ReplaceLineEndings("\n"), + Does.Contain("FileManager must not run\nintegrity checks, checkpoints, migrations, or recovery writes against it.")); + Assert.That(externalReader, Does.Contain("SQLITE_OPEN_READONLY")); + Assert.That(externalReader, Does.Contain("never a FileManager recovery or write target")); + Assert.That(recoveryTest, Does.Contain("PRAGMA journal_mode=WAL")); + Assert.That(recoveryTest, Does.Contain("BEGIN IMMEDIATE")); + Assert.That(recoveryTest, Does.Contain("PRAGMA integrity_check")); + Assert.That(recoveryTest, Does.Contain("sqlite3_compileoption_used")); + Assert.That(recoveryTest, Does.Contain("CorruptPage")); + Assert.That(workflow, Does.Contain("test-sqlite-recovery.ps1")); + Assert.That(refactoring, Does.Contain("### 62. Upgrade SQLite and define database recovery behavior — Implemented")); + }); + } + + [Test] + public void Bundled_cmark_gfm_uses_the_verified_release_and_the_viewer_rejects_unsafe_or_unbounded_rendering() + { + var root = FindRepositoryRoot(); + var version = File.ReadAllText(Path.Combine(root, "src", "plugins", "ieviewer", "cmark-gfm", "build", "src", "cmark-gfm_version.h")); + var vendorRecord = File.ReadAllText(Path.Combine(root, "src", "plugins", "ieviewer", "cmark-gfm", "VENDOR.md")); + var renderer = File.ReadAllText(Path.Combine(root, "src", "plugins", "ieviewer", "markdown_rendering.cpp")); + var rendererHeader = File.ReadAllText(Path.Combine(root, "src", "plugins", "ieviewer", "markdown_rendering.h")); + var viewer = File.ReadAllText(Path.Combine(root, "src", "plugins", "ieviewer", "markdown.cpp")); + var probe = File.ReadAllText(Path.Combine(root, "tools", "cmark_gfm_hardening_probe.cpp")); + var harness = File.ReadAllText(Path.Combine(root, "tools", "test-cmark-gfm-hardening.ps1")); + var workflow = File.ReadAllText(Path.Combine(root, ".github", "workflows", "pr-msbuild.yml")); + var refactoring = File.ReadAllText(Path.Combine(root, "refactoring.md")); + + // Pin the vendor identity, safe default, bounded tree/output seam, and CI fuzz gate together. + Assert.Multiple(() => + { + Assert.That(version, Does.Contain("CMARK_GFM_VERSION_STRING \"0.29.0.gfm.13\"")); + Assert.That(vendorRecord, Does.Contain("5abc61798ebd9de5660bc076443c07abad2b8d15dbc11094a3a79644b8ad243a")); + Assert.That(vendorRecord, Does.Contain("CMARK_OPT_UNSAFE")); + Assert.That(rendererHeader, Does.Contain("MarkdownMaximumInputBytes = 1024 * 1024")); + Assert.That(rendererHeader, Does.Contain("MarkdownMaximumNodeCount = 100000")); + Assert.That(rendererHeader, Does.Contain("MarkdownMaximumTreeDepth = 128")); + Assert.That(rendererHeader, Does.Contain("MarkdownMaximumOutputBytes = 4 * 1024 * 1024")); + Assert.That(renderer, Does.Contain("CMARK_OPT_SAFE | CMARK_OPT_VALIDATE_UTF8")); + Assert.That(renderer, Does.Not.Match("const int options[^;]*CMARK_OPT_UNSAFE")); + Assert.That(renderer, Does.Contain("cmark_parser_new(options)")); + Assert.That(renderer, Does.Contain("cmark_render_html(document, options, cmark_parser_get_syntax_extensions(parser))")); + Assert.That(renderer, Does.Contain("CheckTreeBudget")); + Assert.That(viewer, Does.Contain("mmeAllViewerExtensions")); + Assert.That(viewer, Does.Contain("bytes > MarkdownMaximumInputBytes - markdown.size()")); + Assert.That(viewer, Does.Contain("RenderMarkdownToSafeHtml")); + Assert.That(probe, Does.Contain("RunExtensionCombinationFuzz")); + Assert.That(probe, Does.Contain("mrrOutputTooLarge")); + Assert.That(File.Exists(Path.Combine(root, "tests", "cmark-gfm", "snapshots", "basic.html")), Is.True); + Assert.That(File.Exists(Path.Combine(root, "tests", "cmark-gfm", "snapshots", "strikethrough.html")), Is.True); + Assert.That(harness, Does.Contain("markdown_rendering.cpp")); + Assert.That(harness, Does.Contain("cmark_gfm_hardening_probe.cpp")); + Assert.That(workflow, Does.Contain("test-cmark-gfm-hardening.ps1")); + Assert.That(refactoring, Does.Contain("### 65. Upgrade cmark-gfm and harden rendered-content defaults — Implemented")); + }); + } + + private static string FindRepositoryRoot() + { + for (var directory = new DirectoryInfo(AppContext.BaseDirectory); directory is not null; directory = directory.Parent) + { + if (File.Exists(Path.Combine(directory.FullName, "architecture.md")) && + Directory.Exists(Path.Combine(directory.FullName, "src"))) + return directory.FullName; + } + + throw new DirectoryNotFoundException("Could not find the FileManager repository root."); + } +} diff --git a/tests/FileManager.UiTests/OperationRecoveryCharacterizationUiTests.cs b/tests/FileManager.UiTests/OperationRecoveryCharacterizationUiTests.cs new file mode 100644 index 00000000..9060831b --- /dev/null +++ b/tests/FileManager.UiTests/OperationRecoveryCharacterizationUiTests.cs @@ -0,0 +1,80 @@ +using FileManager.UiTests.Infrastructure; +using NUnit.Framework; + +namespace FileManager.UiTests; + +[TestFixture] +public sealed class OperationRecoveryCharacterizationUiTests : FileOperationUiTestBase +{ + private string journalPath = null!; + private string temporaryPath = null!; + private string targetPath = null!; + private HashSet reportsBeforeStartup = null!; + + protected override void BeforeFileManagerStarted() + { + // Capture preexisting journals before this fixture deliberately seeds its recovery input. + base.BeforeFileManagerStarted(); + targetPath = Workspace.TargetPath("restart-reconciled.txt"); + temporaryPath = Workspace.TargetPath("SALCPrestart-reconciled.tmp"); + File.WriteAllText(temporaryPath, "recovered-after-restart"); + + var journalDirectory = GetJournalDirectory(); + Directory.CreateDirectory(journalDirectory); + reportsBeforeStartup = Directory.EnumerateFiles(journalDirectory, "reconciliation-*.txt").ToHashSet(StringComparer.OrdinalIgnoreCase); + journalPath = Path.Combine(journalDirectory, $"operation-characterization-{Guid.NewGuid():N}.opj"); + File.WriteAllText(journalPath, + $"FORMAT|1{Environment.NewLine}" + + $"OPERATION|planned|items=1{Environment.NewLine}" + + $"ITEM|0|copy-file|source|{targetPath}|identity|{Environment.NewLine}" + + $"TEMP|0|{temporaryPath}{Environment.NewLine}" + + $"STATE|0|temporary-ready{Environment.NewLine}"); + } + + protected override bool IsExpectedStartupModal(nint windowHandle) + { + // Stable dialog/control IDs identify the recovery choice without depending on its localized title or text. + return NativeCommands.GetWindowClassName(windowHandle) == "#32770" && + NativeCommands.HasDialogControl(windowHandle, 2477) && + NativeCommands.HasDialogControl(windowHandle, 6) && + NativeCommands.HasDialogControl(windowHandle, 7) && + NativeCommands.HasDialogControl(windowHandle, 2); + } + + [Test] + [Category("Recovery")] + public void Restart_reconciliation_commits_a_fully_written_transactional_target() + { + // The fixture accepts the expected startup modal before its disabled owner would satisfy normal readiness. + ChooseOperationPrompt(WaitForOperationPrompt(6), 6); // IDYES: resume a ready target + var completion = WaitForOperationPrompt(1); // IDOK: recovery summary + ChooseOperationPrompt(completion, 1); + + WaitForFileSystem(() => File.Exists(targetPath), "Restart reconciliation did not commit the ready temporary target."); + Assert.Multiple(() => + { + Assert.That(File.ReadAllText(targetPath), Is.EqualTo("recovered-after-restart")); + Assert.That(File.Exists(temporaryPath), Is.False); + Assert.That(File.ReadAllText(journalPath), Does.Contain("OPERATION|reconciled")); + }); + } + + protected override void OnAfterFileManagerStopped() + { + base.OnAfterFileManagerStopped(); + if (!string.IsNullOrWhiteSpace(journalPath) && File.Exists(journalPath)) + File.Delete(journalPath); + + if (reportsBeforeStartup is not null) + { + foreach (var report in Directory.EnumerateFiles(GetJournalDirectory(), "reconciliation-*.txt")) + { + if (!reportsBeforeStartup.Contains(report)) + File.Delete(report); + } + } + } + + private static string GetJournalDirectory() => Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Open Salamander", "operation-journals"); +} diff --git a/tests/FileManager.UiTests/README.md b/tests/FileManager.UiTests/README.md index a4f7e886..c18e4477 100644 --- a/tests/FileManager.UiTests/README.md +++ b/tests/FileManager.UiTests/README.md @@ -1,6 +1,6 @@ # FileManager UI tests -This project contains 100 basic, parameterized FlaUI/UIA3 NUnit cases for the native FileManager UI. The cases cover application launch, accessibility-tree discovery, Configuration dialog cancel/commit/restart flows, a committed setting verified after restart, and FTP bookmark creation plus edit verified after restart. +This project contains seven primary parameterized FlaUI/UIA3 lifecycle cases plus focused file-operation characterization cases for the native FileManager UI. Repetition belongs to the separately scheduled lock-stress soak. The cases cover application launch, accessibility-tree discovery, Configuration dialog cancel/commit/restart flows, a committed setting verified after restart, FTP bookmark creation plus edit verified after restart, and native disk create/copy/rename/move/delete/find/view/edit commands. The tests intentionally refuse to run unless `FILEMANAGER_UI_ISOLATED=1` is set. The application persists configuration under the current user registry hive, so run them under a dedicated Windows test account or another isolated user profile. @@ -9,7 +9,14 @@ Set these environment variables before running: - `FILEMANAGER_UI_ISOLATED=1` — confirms that the current Windows profile is disposable. - `FILEMANAGER_UI_EXE` — absolute path to `salamand.exe` or a debug build of the executable. - `FILEMANAGER_UI_ARGUMENTS` — optional command-line arguments, for example a test-only `-c` configuration file. -- `FILEMANAGER_UI_FTP_ORGANIZE_COMMAND` — runtime command ID allocated by FileManager for the FTP Client **Organize Bookmarks** menu command. This enables the 10 FTP bookmark persistence cases; without it, only those cases are skipped with an explicit message. +- `FILEMANAGER_UI_CONFIG_FAULT_INJECTION=1` — explicitly enables the exhaustive transactional-configuration crash-recovery lane described below. +- `FILEMANAGER_UI_CONFIG_FAULT_BOUNDARY_LIMIT` — optional positive boundary cap for short fault-injection smoke runs; omit it for the exhaustive lane. +- `FILEMANAGER_UI_CROSS_VOLUME_ROOT` — an existing dedicated directory on a different volume from `%TEMP%`. This enables the cross-volume move characterization fixture; the fixture creates and removes only a GUID-named child below this directory. +- `FILEMANAGER_UI_ADS_UNSUPPORTED_TARGET_ROOT` — an existing dedicated directory on a different volume that does not support alternate data streams (for example FAT/FAT32/exFAT). This enables the ADS metadata-loss decision scenario and the fixture creates and removes only a GUID-named child below this directory. +- `FILEMANAGER_UI_RECYCLE_BIN=1` — explicitly enables the recycle-bin characterization test. It requires the default recycle-bin delete setting in the isolated profile and adds one disposable file to that profile's recycle bin. +- `FILEMANAGER_UI_ZIP_PLUGIN=1` — confirms that the Zip plug-in is installed and enabled for the reported ZIP-navigation characterization test. +- `FILEMANAGER_UI_HELP_SEARCH_TERM` — a search term known to exist in the language-specific deployed `salamand.chm`. +- `FILEMANAGER_UI_HELP_EXPECTED_RESULT` — text expected in the Help Search result for that term. Run the suite on an interactive Windows desktop session: @@ -19,4 +26,37 @@ dotnet test tests/FileManager.UiTests/FileManager.UiTests.csproj --filter TestCa The configuration dialog is opened through its stable native command ID only to avoid locale-dependent menu text. All window discovery, control inspection, focus, dialog lifecycle, and restart assertions use FlaUI/UIA3. -The FTP plug-in menu command has no compile-time host command ID: FileManager allocates it while loading plug-ins. Keep the value in the isolated test environment rather than hard-coding it into the test project. The dialog controls themselves are located by their stable plug-in resource IDs and their persistence is asserted through UIA3 after a full application restart. +File-operation cases create a fresh disposable directory tree under the system temporary directory for every test, start the left and right panels with `-l`/`-r`, and use the host's stable native command IDs. They verify files and nested directory trees after normal operations, copy and move overwrite/skip conflict choices, rename overwrite-decline and case-only behavior, mixed-selection deletion, continuation after a skipped delete error, file search, internal viewing, configured-editor launch, metadata preservation, cancellation after the worker has started, and destination/name failures. ADS cases create named, empty, large, and edge-named streams; exercise overwrite and retry after a temporarily denied stream; and verify cross-volume preservation or explicit source retention when an ADS-unsupported target reports metadata loss. The recovery fixture seeds an incomplete durable journal with a ready transactional sibling file before launch, then verifies the real startup reconciliation flow. The tests never use a caller-supplied directory as their mutation target; cross-volume cases use only a GUID child under the explicitly configured disposable root. + +The FTP plug-in menu command has no compile-time host command ID: FileManager allocates it while loading plug-ins. The isolated test protocol queries that runtime ID from the tested process; when the FTP plug-in is absent, only the dependent cases are skipped with an explicit prerequisite message. The dialog controls themselves are located by their stable plug-in resource IDs and their persistence is asserted through UIA3 after a full application restart. + +## Reported-defect characterization + +`ReportedDefectCharacterizationUiTests` records four product reports without changing application behavior: ZIP navigation after its information dialog, language-specific Help Search results, move-collision overwrite and cancel semantics, and responsiveness plus editor launch through **Files > Edit**. These are regression contracts, so a failed assertion can be the intended evidence that the reported product defect is present; a crashed or stalled test host is never an acceptable result. + +The move and edit cases are self-contained. ZIP is skipped unless `FILEMANAGER_UI_ZIP_PLUGIN=1` explicitly confirms the plug-in is installed and enabled. Help Search is skipped unless a language-specific `help//salamand.chm` is deployed and both Help fixture variables are set. For example: + +```powershell +$env:FILEMANAGER_UI_ISOLATED = '1' +$env:FILEMANAGER_UI_EXE = 'D:\FileManager\src\vcxproj\salamander\Debug_x64\salamand.exe' +$env:FILEMANAGER_UI_ZIP_PLUGIN = '1' +$env:FILEMANAGER_UI_HELP_SEARCH_TERM = '' +$env:FILEMANAGER_UI_HELP_EXPECTED_RESULT = '' +dotnet test tests/FileManager.UiTests/FileManager.UiTests.csproj ` + --filter 'FullyQualifiedName~ReportedDefectCharacterizationUiTests' +``` + +The fixture creates only disposable files under its per-test temporary workspace. It may open the configured external editor, but closes only the editor window identified by its unique test filename. It must not install or reconfigure plug-ins, replace help content, change editor configuration, or modify native application code to make the assertions pass. + +## Transactional configuration fault injection + +The `FaultInjection` category first measures the exact registry-write count of a real configuration commit, then starts a fresh executable for every write boundary. After validating a clean baseline, the fixture keeps an immutable in-memory snapshot of the FileManager configuration and recovery-backup registry roots; while no FileManager process is running, it restores that snapshot before every boundary and again during final cleanup. The native test hook terminates the process immediately after the selected successful registry mutation. A clean restart must show either the complete baseline setting or the complete candidate setting; the test fails if the process did not terminate at the requested boundary or if restart exposes a mixture. + +Run this separately on the disposable profile because it intentionally terminates the executable many times: + +```powershell +$env:FILEMANAGER_UI_CONFIG_FAULT_INJECTION = '1' +dotnet test tests/FileManager.UiTests/FileManager.UiTests.csproj --filter TestCategory=FaultInjection +``` + +The fixture supplies `FILEMANAGER_CONFIG_FAULT_REPORT` to measure a completed save and arms each interruption through the isolated native test protocol; users should not set these internal controls manually. diff --git a/tests/FileManager.UiTests/ReparsePointTopologyUiTests.cs b/tests/FileManager.UiTests/ReparsePointTopologyUiTests.cs new file mode 100644 index 00000000..46981602 --- /dev/null +++ b/tests/FileManager.UiTests/ReparsePointTopologyUiTests.cs @@ -0,0 +1,129 @@ +using System.Diagnostics; +using FileManager.UiTests.Infrastructure; +using NUnit.Framework; + +namespace FileManager.UiTests; + +// The topology is prepared before FileManager starts so panel enumeration sees +// the reparse entries without relying on a refresh race. Every target is a +// child of the fixture's disposable workspace; the target is nevertheless +// outside the selected operation root. +[TestFixture] +[Category("ReparsePoints")] +public sealed class ReparsePointTopologyUiTests : FileOperationUiTestBase +{ + private string operationRoot = null!; + private string firstOutsideTarget = null!; + private string changedOutsideTarget = null!; + private string deleteTarget = null!; + private bool directorySymlinkAvailable; + + protected override void BeforeFileManagerStarted() + { + // Preserve base journal isolation before creating the topology visible at application startup. + base.BeforeFileManagerStarted(); + operationRoot = Workspace.SourcePath("reparse-operation-root"); + firstOutsideTarget = Path.Combine(Workspace.RootDirectory, "outside-first-target"); + changedOutsideTarget = Path.Combine(Workspace.RootDirectory, "outside-changed-target"); + deleteTarget = Path.Combine(Workspace.RootDirectory, "delete-target"); + Directory.CreateDirectory(operationRoot); + Directory.CreateDirectory(firstOutsideTarget); + Directory.CreateDirectory(changedOutsideTarget); + Directory.CreateDirectory(deleteTarget); + File.WriteAllText(Path.Combine(operationRoot, "inside.txt"), "operation-root-content"); + File.WriteAllText(Path.Combine(firstOutsideTarget, "sentinel.txt"), "first-target-content"); + File.WriteAllText(Path.Combine(changedOutsideTarget, "sentinel.txt"), "changed-target-content"); + File.WriteAllText(Path.Combine(deleteTarget, "sentinel.txt"), "delete-target-content"); + + var changedLink = Path.Combine(operationRoot, "changed-junction"); + CreateJunction(changedLink, firstOutsideTarget); + Directory.Delete(changedLink); + CreateJunction(changedLink, changedOutsideTarget); + + // This deliberately points back into the selected tree. A recursive + // planner that follows it never reaches a finite plan. + CreateJunction(Path.Combine(operationRoot, "cycle-junction"), operationRoot); + CreateJunction(Workspace.SourcePath("delete-junction"), deleteTarget); + + try + { + Directory.CreateSymbolicLink(Path.Combine(operationRoot, "outside-symlink"), changedOutsideTarget); + directorySymlinkAvailable = true; + } + catch (Exception exception) when (IsSymbolicLinkPrivilegeUnavailable(exception)) + { + // Junction coverage remains available when Windows reports ERROR_PRIVILEGE_NOT_HELD as IOException or access denied. + } + } + + [Test] + public void Copy_does_not_traverse_changed_or_cyclic_junction_targets_outside_the_operation_root() + { + ExecuteWithPath(NativeCommands.CopyFiles, "reparse-operation-root", Workspace.TargetDirectory, commit: true); + + WaitForFileSystem(() => File.Exists(Workspace.TargetPath("reparse-operation-root\\inside.txt")), + "Copy did not preserve the non-reparse member of the selected operation root."); + + Assert.Multiple(() => + { + Assert.That(File.ReadAllText(Path.Combine(firstOutsideTarget, "sentinel.txt")), Is.EqualTo("first-target-content")); + Assert.That(File.ReadAllText(Path.Combine(changedOutsideTarget, "sentinel.txt")), Is.EqualTo("changed-target-content")); + Assert.That(Directory.Exists(Workspace.TargetPath("reparse-operation-root\\changed-junction")), Is.False, + "Copy must not materialize or traverse a reparse directory target."); + Assert.That(Directory.Exists(Workspace.TargetPath("reparse-operation-root\\cycle-junction")), Is.False, + "Copy must not enter a cyclic reparse directory."); + }); + } + + [Test] + public void Delete_junction_removes_only_the_link_and_never_its_target() + { + SelectSourceItem("delete-junction"); + NativeCommands.Execute(MainWindowHandle, NativeCommands.DeleteFiles); + ConfirmDeleteIfPrompted(); + + WaitForFileSystem(() => !Directory.Exists(Workspace.SourcePath("delete-junction")), + "Delete did not remove the selected junction."); + Assert.That(File.ReadAllText(Path.Combine(deleteTarget, "sentinel.txt")), Is.EqualTo("delete-target-content")); + } + + [Test] + public void Copy_does_not_traverse_a_directory_symbolic_link_outside_the_operation_root() + { + if (!directorySymlinkAvailable) + Assert.Ignore("The current test host does not permit disposable directory symbolic links."); + + ExecuteWithPath(NativeCommands.CopyFiles, "reparse-operation-root", Workspace.TargetDirectory, commit: true); + + WaitForFileSystem(() => File.Exists(Workspace.TargetPath("reparse-operation-root\\inside.txt")), + "Copy did not preserve the non-reparse member of the selected operation root."); + Assert.Multiple(() => + { + Assert.That(File.ReadAllText(Path.Combine(changedOutsideTarget, "sentinel.txt")), Is.EqualTo("changed-target-content")); + Assert.That(Directory.Exists(Workspace.TargetPath("reparse-operation-root\\outside-symlink")), Is.False, + "Copy must not materialize or traverse a directory symbolic-link target."); + }); + } + + private static void CreateJunction(string linkPath, string targetPath) + { + var commandInterpreter = Environment.GetEnvironmentVariable("ComSpec") ?? "cmd.exe"; + using var process = Process.Start(new ProcessStartInfo(commandInterpreter, + $"/d /s /c mklink /J \"{linkPath}\" \"{targetPath}\"") + { + UseShellExecute = false, + CreateNoWindow = true, + }); + Assert.That(process, Is.Not.Null, "The test host could not start cmd.exe to create a disposable junction."); + process!.WaitForExit(); + Assert.That(process.ExitCode, Is.Zero, "Creating the disposable junction failed."); + Assert.That(Directory.Exists(linkPath), Is.True, "The disposable junction was not created."); + } + + private static bool IsSymbolicLinkPrivilegeUnavailable(Exception exception) + { + // .NET maps CreateSymbolicLink privilege failures differently across Windows runtime versions. + return exception is UnauthorizedAccessException || + exception is IOException && (exception.HResult & 0xFFFF) == 1314; // ERROR_PRIVILEGE_NOT_HELD + } +} diff --git a/tests/FileManager.UiTests/ReportedDefectCharacterizationUiTests.cs b/tests/FileManager.UiTests/ReportedDefectCharacterizationUiTests.cs new file mode 100644 index 00000000..42896617 --- /dev/null +++ b/tests/FileManager.UiTests/ReportedDefectCharacterizationUiTests.cs @@ -0,0 +1,223 @@ +using System.IO.Compression; +using FileManager.UiTests.Infrastructure; +using FlaUI.Core.AutomationElements; +using FlaUI.Core.Definitions; +using NUnit.Framework; + +namespace FileManager.UiTests; + +[TestFixture] +[Category("UI")] +[Category("Characterization")] +[Category("KnownDefect")] +public sealed class ReportedDefectCharacterizationUiTests : FileOperationUiTestBase +{ + private const string ZipName = "characterization-open.zip"; + private const string ZipPayloadName = "zip-characterization-payload.txt"; + private const string MoveOverwriteName = "move-overwrite-characterization.txt"; + private const string MoveCancelName = "move-cancel-characterization.txt"; + private readonly string editFileName = $"files-menu-edit-{Guid.NewGuid():N}.txt"; + + protected override void BeforeFileManagerStarted() + { + // Reuse normal file-operation journal isolation before seeding the reported-defect fixtures. + base.BeforeFileManagerStarted(); + // Seed every reported scenario before launch so the first native panel listing contains deterministic fixtures. + CreateZipFixture(); + File.WriteAllText(Workspace.SourcePath(MoveOverwriteName), "move-overwrite-source"); + File.WriteAllText(Workspace.TargetPath(MoveOverwriteName), "move-overwrite-existing-target"); + File.WriteAllText(Workspace.SourcePath(MoveCancelName), "move-cancel-source"); + File.WriteAllText(Workspace.TargetPath(MoveCancelName), "move-cancel-existing-target"); + File.WriteAllText(Workspace.SourcePath(editFileName), "edit-characterization-content"); + } + + [Test] + public void Zip_open_after_information_dialog_navigates_into_archive() + { + UiTestSettings.RequireZipPlugin(); + + SelectSourceItem(ZipName); + NativeCommands.Execute(MainWindowHandle, NativeCommands.Open); + // The reported defect shows an informational OK dialog before returning to an unchanged panel. + DismissOptionalPrompt(1, TimeSpan.FromSeconds(3), 1); + + WaitForCondition( + () => NativeCommands.GetWindowTitle(MainWindowHandle).Contains(ZipName, StringComparison.OrdinalIgnoreCase), + "After accepting the ZIP-open message, FileManager did not navigate into the selected archive."); + + // Quick search proves that navigation exposes archive contents rather than only changing decorative window text. + SelectSourceItem(ZipPayloadName); + } + + [Test] + public void Help_search_returns_the_configured_existing_help_result() + { + var (searchTerm, expectedResult) = UiTestSettings.RequireHelpSearchFixture(); + RequireDeployedMainHelp(); + var windowsBefore = NativeCommands.GetVisibleTopLevelWindowHandles().ToHashSet(); + nint helpWindowHandle = 0; + try + { + NativeCommands.Execute(MainWindowHandle, NativeCommands.HelpSearch); + helpWindowHandle = WaitForNewTopLevelWindow(windowsBefore, + handle => string.Equals(NativeCommands.GetWindowClassName(handle), "HH Parent", StringComparison.Ordinal)); + + var helpWindow = Automation.FromHandle(helpWindowHandle).AsWindow(); + var searchInput = helpWindow.FindAllDescendants() + .FirstOrDefault(element => element.ControlType == ControlType.Edit && element.IsEnabled) + ?.AsTextBox(); + Assert.That(searchInput, Is.Not.Null, "The Help Search panel did not expose an enabled search input."); + + searchInput!.Text = searchTerm; + NativeCommands.PressEnter(searchInput.Properties.NativeWindowHandle.Value); + + WaitForCondition(() => HelpWindowContains(helpWindowHandle, expectedResult), + $"Help Search did not return the configured existing result '{expectedResult}' for '{searchTerm}'."); + } + finally + { + // HTML Help owns a top-level window outside normal FileManager dialog cleanup. + NativeCommands.RequestWindowClose(helpWindowHandle); + } + } + + [Test] + public void Move_overwrite_replaces_existing_target_instead_of_deleting_it() + { + var source = Workspace.SourcePath(MoveOverwriteName); + var target = Workspace.TargetPath(MoveOverwriteName); + + ExecuteWithPath(NativeCommands.MoveFiles, MoveOverwriteName, Workspace.TargetDirectory, commit: true); + DismissOptionalPrompt(6, TimeSpan.FromSeconds(3), 6); // IDYES when this profile confirms overwrites. + + WaitForFileSystem(() => !File.Exists(source), "Confirmed move overwrite did not finish removing the source."); + Assert.Multiple(() => + { + Assert.That(File.Exists(target), Is.True, + "Move overwrite deleted the existing target instead of replacing it."); + Assert.That(File.ReadAllText(target), Is.EqualTo("move-overwrite-source"), + "Move overwrite did not preserve the moved source content at the target."); + }); + } + + [Test] + public void Move_collision_cancel_keeps_both_files_unchanged() + { + var source = Workspace.SourcePath(MoveCancelName); + var target = Workspace.TargetPath(MoveCancelName); + + ExecuteWithPath(NativeCommands.MoveFiles, MoveCancelName, Workspace.TargetDirectory, commit: true); + DismissOptionalPrompt(2, TimeSpan.FromSeconds(3), 2); // Semantic checks expose an unavailable Cancel choice. + + Assert.Multiple(() => + { + Assert.That(File.ReadAllText(source), Is.EqualTo("move-cancel-source"), + "Cancelling a move collision changed or removed the source."); + Assert.That(File.ReadAllText(target), Is.EqualTo("move-cancel-existing-target"), + "Cancelling a move collision changed or removed the existing target."); + }); + } + + [Test] + public void Files_menu_edit_opens_editor_without_freezing_main_window() + { + var windowsBefore = NativeCommands.GetVisibleTopLevelWindowHandles().ToHashSet(); + using var editorProcesses = new TestOwnedProcessScope("notepad"); + nint editorWindowHandle = 0; + try + { + SelectSourceItem(editFileName); + NativeCommands.Execute(MainWindowHandle, NativeCommands.EditFile); + + var deadline = DateTime.UtcNow + TimeSpan.FromSeconds(10); + var lastResponsive = DateTime.UtcNow; + while (DateTime.UtcNow < deadline) + { + if (NativeCommands.IsWindowResponsive(MainWindowHandle)) + lastResponsive = DateTime.UtcNow; + else if (DateTime.UtcNow - lastResponsive > TimeSpan.FromSeconds(2)) + Assert.Fail("Files > Edit left the FileManager main window unresponsive for more than two seconds."); + + editorWindowHandle = NativeCommands.GetVisibleTopLevelWindowHandles() + .FirstOrDefault(handle => !windowsBefore.Contains(handle) && + NativeCommands.GetWindowTitle(handle) + .Contains(editFileName, StringComparison.OrdinalIgnoreCase)); + if (editorWindowHandle != 0) + return; + Thread.Sleep(100); + } + + Assert.Fail("Files > Edit did not open an editor window for the selected file within ten seconds."); + } + finally + { + // The unique file name restricts cleanup to the editor window created by this case. + if (editorWindowHandle == 0) + { + editorWindowHandle = NativeCommands.GetVisibleTopLevelWindowHandles() + .FirstOrDefault(handle => !windowsBefore.Contains(handle) && + NativeCommands.GetWindowTitle(handle) + .Contains(editFileName, StringComparison.OrdinalIgnoreCase)); + } + NativeCommands.RequestWindowClose(editorWindowHandle); + } + } + + private void CreateZipFixture() + { + using var archive = ZipFile.Open(Workspace.SourcePath(ZipName), ZipArchiveMode.Create); + var entry = archive.CreateEntry(ZipPayloadName); + using var writer = new StreamWriter(entry.Open()); + writer.Write("zip-characterization-content"); + } + + private static void RequireDeployedMainHelp() + { + var executableDirectory = Path.GetDirectoryName(UiTestSettings.ExecutablePath)!; + var helpDirectory = Path.Combine(executableDirectory, "help"); + // The search UI cannot be characterized when the selected build has no compiled main help archive. + if (!Directory.Exists(helpDirectory) || + !Directory.EnumerateFiles(helpDirectory, "salamand.chm", SearchOption.AllDirectories).Any()) + Assert.Ignore("Deploy help//salamand.chm beside the tested executable to run Help Search characterization."); + } + + private nint WaitForNewTopLevelWindow(IReadOnlySet windowsBefore, Func predicate) + { + var deadline = DateTime.UtcNow + TimeSpan.FromSeconds(10); + while (DateTime.UtcNow < deadline) + { + var handle = NativeCommands.GetVisibleTopLevelWindowHandles() + .FirstOrDefault(candidate => !windowsBefore.Contains(candidate) && predicate(candidate)); + if (handle != 0) + return handle; + if (!NativeCommands.IsWindowResponsive(MainWindowHandle)) + Assert.Fail("FileManager became unresponsive while opening the requested top-level window."); + Thread.Sleep(100); + } + + Assert.Fail("The requested top-level window did not open within ten seconds."); + return 0; + } + + private bool HelpWindowContains(nint helpWindowHandle, string expectedResult) + { + if (!NativeCommands.IsWindowAvailable(helpWindowHandle)) + return false; + var helpWindow = Automation.FromHandle(helpWindowHandle); + // Result discovery remains language-configurable while still asserting the real rendered Help UI. + return helpWindow.FindAllDescendants() + .Any(element => element.Name.Contains(expectedResult, StringComparison.OrdinalIgnoreCase)); + } + + private static void WaitForCondition(Func predicate, string failureMessage) + { + var deadline = DateTime.UtcNow + TimeSpan.FromSeconds(10); + while (DateTime.UtcNow < deadline) + { + if (predicate()) + return; + Thread.Sleep(100); + } + Assert.Fail(failureMessage); + } +} diff --git a/tests/FileManager.UiTests/SChannelTlsIntegrationTests.cs b/tests/FileManager.UiTests/SChannelTlsIntegrationTests.cs new file mode 100644 index 00000000..ce8d9858 --- /dev/null +++ b/tests/FileManager.UiTests/SChannelTlsIntegrationTests.cs @@ -0,0 +1,143 @@ +using System.Net; +using System.Net.Security; +using System.Net.Sockets; +using System.Security.Authentication; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using NUnit.Framework; + +namespace FileManager.UiTests; + +[TestFixture] +[NonParallelizable] +public sealed class SChannelTlsIntegrationTests +{ + // SChannel credential acquisition can exceed five seconds on a busy CI profile without indicating a protocol hang. + private static readonly TimeSpan HandshakeTimeout = TimeSpan.FromSeconds(15); + + [TestCase(SslProtocols.Tls12)] + [TestCase(SslProtocols.Tls13)] + public async Task LocalTlsServerNegotiatesTheRequiredProtocol(SslProtocols protocol) + { + // Windows 10 SChannel never negotiates TLS 1.3; skip that platform prerequisite instead of waiting for a doomed handshake. + if (protocol == SslProtocols.Tls13 && Environment.OSVersion.Version.Build < 20348) + Assert.Ignore("TLS 1.3 requires Windows 11 or Windows Server 2022 and later."); + + using var timeout = new CancellationTokenSource(HandshakeTimeout); + using var certificate = CreateCertificate(); + using var listener = new TcpListener(IPAddress.Loopback, 0); + listener.Start(); + + var server = AcceptAndAuthenticateAsync(listener, certificate, protocol, timeout.Token); + using var client = new TcpClient(); + await client.ConnectAsync(IPAddress.Loopback, ((IPEndPoint)listener.LocalEndpoint).Port, timeout.Token); + using var stream = new SslStream(client.GetStream(), false, (_, _, _, _) => true); + try + { + await stream.AuthenticateAsClientAsync(new SslClientAuthenticationOptions + { + TargetHost = "localhost", + EnabledSslProtocols = protocol, + CertificateRevocationCheckMode = X509RevocationMode.NoCheck + }, timeout.Token); + + Assert.That(stream.SslProtocol, Is.EqualTo(protocol)); + Assert.That(await server.WaitAsync(timeout.Token), Is.EqualTo(protocol)); + } + catch (OperationCanceledException error) when (timeout.IsCancellationRequested) + { + throw new AssertionException($"The local {protocol} SChannel handshake did not complete within {HandshakeTimeout.TotalSeconds:0} seconds.", error); + } + catch (Exception clientError) + { + try { await server.WaitAsync(timeout.Token); } + catch (OperationCanceledException serverError) when (timeout.IsCancellationRequested) + { + // Preserve the initiating client error while classifying the peer cancellation as the shared handshake deadline. + throw new AssertionException($"The local {protocol} SChannel handshake did not complete within {HandshakeTimeout.TotalSeconds:0} seconds.", + new AggregateException(clientError, serverError)); + } + catch (Exception serverError) { throw new AssertionException($"TLS server failed: {serverError}", clientError); } + throw; + } + finally + { + await StopAndObserveServerAsync(timeout, listener, server); + } + } + + [Test] + public async Task SelfSignedServerCertificateIsRejectedWithoutAnExplicitUserException() + { + using var timeout = new CancellationTokenSource(HandshakeTimeout); + using var certificate = CreateCertificate(); + using var listener = new TcpListener(IPAddress.Loopback, 0); + listener.Start(); + + var server = AcceptAndAuthenticateAsync(listener, certificate, SslProtocols.Tls12, timeout.Token); + using var client = new TcpClient(); + await client.ConnectAsync(IPAddress.Loopback, ((IPEndPoint)listener.LocalEndpoint).Port, timeout.Token); + using var stream = new SslStream(client.GetStream(), false); + + try + { + Assert.That(async () => await stream.AuthenticateAsClientAsync(new SslClientAuthenticationOptions + { + TargetHost = "localhost", + EnabledSslProtocols = SslProtocols.Tls12, + CertificateRevocationCheckMode = X509RevocationMode.NoCheck + }, timeout.Token), Throws.TypeOf()); + + try { await server.WaitAsync(timeout.Token); } + catch (AuthenticationException) { } + } + catch (OperationCanceledException error) when (timeout.IsCancellationRequested) + { + throw new AssertionException($"The self-signed certificate rejection did not complete within {HandshakeTimeout.TotalSeconds:0} seconds.", error); + } + finally + { + await StopAndObserveServerAsync(timeout, listener, server); + } + } + + private static async Task StopAndObserveServerAsync(CancellationTokenSource timeout, TcpListener listener, Task server) + { + // Cancel, close, and observe every peer task so failed handshakes cannot retain a listener or testhost thread. + timeout.Cancel(); + listener.Stop(); + try { await server.WaitAsync(TimeSpan.FromSeconds(1)); } + catch { } + } + + private static async Task AcceptAndAuthenticateAsync(TcpListener listener, X509Certificate2 certificate, + SslProtocols protocol, CancellationToken cancellationToken) + { + using var server = await listener.AcceptTcpClientAsync(cancellationToken); + using var stream = new SslStream(server.GetStream(), false); + await stream.AuthenticateAsServerAsync(new SslServerAuthenticationOptions + { + ServerCertificate = certificate, + EnabledSslProtocols = protocol, + ClientCertificateRequired = false, + CertificateRevocationCheckMode = X509RevocationMode.NoCheck + }, cancellationToken); + return stream.SslProtocol; + } + + private static X509Certificate2 CreateCertificate() + { + using var key = RSA.Create(2048); + var request = new CertificateRequest("CN=localhost", key, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + var san = new SubjectAlternativeNameBuilder(); + san.AddDnsName("localhost"); + request.CertificateExtensions.Add(san.Build()); + request.CertificateExtensions.Add(new X509BasicConstraintsExtension(false, false, 0, false)); + request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.KeyEncipherment, false)); + request.CertificateExtensions.Add(new X509SubjectKeyIdentifierExtension(request.PublicKey, false)); + using var generated = request.CreateSelfSigned(DateTimeOffset.UtcNow.AddDays(-1), DateTimeOffset.UtcNow.AddDays(1)); + // SChannel requires a persisted server key; a machine-key import avoids the user-profile deletion race between cases. + return new X509Certificate2(generated.Export(X509ContentType.Pfx), (string?)null, + X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable); + } +} diff --git a/tests/FileManager.UiTests/ToolbarIconSizeContractTests.cs b/tests/FileManager.UiTests/ToolbarIconSizeContractTests.cs new file mode 100644 index 00000000..1cd207d4 --- /dev/null +++ b/tests/FileManager.UiTests/ToolbarIconSizeContractTests.cs @@ -0,0 +1,169 @@ +using NUnit.Framework; + +namespace FileManager.UiTests; + +// This source-level contract keeps toolbar sizing configurable without coupling +// the new preference to menu row sizing or the application icon resources. +public sealed class ToolbarIconSizeContractTests +{ + [Test] + public void Customize_toolbar_exposes_persisted_small_medium_and_large_icon_sizes() + { + var root = FindRepositoryRoot(); + var resourceIds = File.ReadAllText(Path.Combine(root, "src", "lang", "lang.rh")); + var dialog = File.ReadAllText(Path.Combine(root, "src", "lang", "lang.rc")); + var stringIds = File.ReadAllText(Path.Combine(root, "src", "texts.rh2")); + var strings = File.ReadAllText(Path.Combine(root, "src", "lang", "texts.rc2")); + var configuration = File.ReadAllText(Path.Combine(root, "src", "cfgdlg.h")); + var configurationDefaults = File.ReadAllText(Path.Combine(root, "src", "dialogs_config_general.cpp")); + var configurationStorage = File.ReadAllText(Path.Combine(root, "src", "mainwnd_config.cpp")); + var customize = File.ReadAllText(Path.Combine(root, "src", "toolbar_dnd.cpp")); + + Assert.Multiple(() => + { + Assert.That(resourceIds, Does.Contain("IDC_CTB_ICON_SIZE")); + Assert.That(dialog, Does.Contain("&Icon size:")); + Assert.That(dialog, Does.Contain("CBS_DROPDOWNLIST")); + Assert.That(stringIds, Does.Contain("IDS_TOOLBAR_ICON_SIZE_SMALL")); + Assert.That(stringIds, Does.Contain("IDS_TOOLBAR_ICON_SIZE_MEDIUM")); + Assert.That(stringIds, Does.Contain("IDS_TOOLBAR_ICON_SIZE_LARGE")); + Assert.That(strings, Does.Contain("Small (16x16 pixels)")); + Assert.That(strings, Does.Contain("Medium (24x24 pixels)")); + Assert.That(strings, Does.Contain("Large (32x32 pixels)")); + Assert.That(configuration, Does.Contain("ToolbarIconSize")); + Assert.That(configurationDefaults, Does.Contain("ToolbarIconSize = TOOLBAR_ICON_SIZE_SMALL")); + Assert.That(configurationStorage, Does.Contain("CONFIG_TOOLBARICONSIZE_REG")); + Assert.That(configurationStorage, Does.Contain("IsValidToolbarIconSize")); + Assert.That(customize, Does.Contain("CBN_SELCHANGE")); + Assert.That(customize, Does.Contain("ColorsChanged(TRUE, FALSE, FALSE)")); + }); + } + + [Test] + public void Toolbar_image_lists_scale_independently_from_menu_images_and_app_icon() + { + var root = FindRepositoryRoot(); + var constants = File.ReadAllText(Path.Combine(root, "src", "consts.h")); + var globals = File.ReadAllText(Path.Combine(root, "src", "app_globals.cpp")); + var graphics = File.ReadAllText(Path.Combine(root, "src", "app_entry.cpp")); + var svgRasterizer = File.ReadAllText(Path.Combine(root, "src", "svg.cpp")); + var toolbarCore = File.ReadAllText(Path.Combine(root, "src", "toolbar_core.cpp")); + var toolbarRendering = File.ReadAllText(Path.Combine(root, "src", "toolbar_rendering.cpp")); + var menus = File.ReadAllText(Path.Combine(root, "src", "menu_templates.cpp")); + var applicationResources = File.ReadAllText(Path.Combine(root, "src", "salamand.rc2")); + + Assert.Multiple(() => + { + Assert.That(constants, Does.Contain("TOOLBAR_ICON_SIZE_SMALL = 16")); + Assert.That(constants, Does.Contain("TOOLBAR_ICON_SIZE_MEDIUM = 24")); + Assert.That(constants, Does.Contain("TOOLBAR_ICON_SIZE_LARGE = 32")); + Assert.That(constants, Does.Contain("HGrayMenuImageList")); + Assert.That(constants, Does.Contain("HHotMenuImageList")); + Assert.That(globals, Does.Contain("HGrayMenuImageList = NULL")); + Assert.That(globals, Does.Contain("HHotMenuImageList = NULL")); + Assert.That(graphics, Does.Contain("GetToolbarIconSizeForSystemDPI()")); + Assert.That(graphics, Does.Contain("ImageList_Create(toolbarIconSize, toolbarIconSize")); + Assert.That(graphics, Does.Contain("ImageList_Create(menuIconSize, menuIconSize")); + Assert.That(svgRasterizer, Does.Contain("iconSize / image->width")); + Assert.That(svgRasterizer, Does.Contain("iconSize / image->height")); + Assert.That(svgRasterizer, Does.Contain("(iconSize - image->width * scale) / 2")); + Assert.That(svgRasterizer, Does.Not.Contain("float scale = sysDPIScale / 100;"), + "Configured image-list cells must not retain a system-DPI-only 16-pixel glyph."); + Assert.That(toolbarCore, Does.Contain("ImageWidth > 0 ? ImageWidth : GetIconSizeForSystemDPI")); + Assert.That(toolbarCore, Does.Contain("ImageHeight > 0 ? ImageHeight : GetIconSizeForSystemDPI")); + Assert.That(toolbarRendering, Does.Contain("item->HIcon, imgW, imgH")); + Assert.That(toolbarRendering, Does.Not.Contain("item->HIcon, iconSize, iconSize"), + "Direct HICON buttons must follow their toolbar image dimensions too."); + Assert.That(menus, Does.Contain("HGrayMenuImageList, HHotMenuImageList")); + Assert.That(applicationResources, Does.Contain("IDI_SALAMANDER ICON \"res\\\\salamand.ico\""), + "Toolbar sizing must leave the main application icon resource unchanged."); + }); + } + + [Test] + public void Fluent_toolbar_spacing_scales_with_each_configured_icon_size() + { + var root = FindRepositoryRoot(); + var toolbarInterface = File.ReadAllText(Path.Combine(root, "src", "toolbar.h")); + var toolbarCore = File.ReadAllText(Path.Combine(root, "src", "toolbar_core.cpp")); + var toolbarRendering = File.ReadAllText(Path.Combine(root, "src", "toolbar_rendering.cpp")); + var toolbarCustomize = File.ReadAllText(Path.Combine(root, "src", "toolbar_dnd.cpp")); + var mainWindowCreation = File.ReadAllText(Path.Combine(root, "src", "mainwnd_messages.cpp")); + var mainWindowRefresh = File.ReadAllText(Path.Combine(root, "src", "mainwnd_init.cpp")); + + // The Fluent family keeps a one-quarter-icon inset at 16, 24, and 32 pixels, + // while startup and live size changes must apply the same geometry path. + Assert.Multiple(() => + { + Assert.That(toolbarInterface, Does.Contain("ApplyConfiguredIconSpacing")); + Assert.That(toolbarCore, Does.Contain("(iconDimension + 3) / 4")); + Assert.That(toolbarCore, Does.Contain("Padding.IconLeft + 1")); + Assert.That(toolbarRendering, Does.Contain("max(TB_SP_WIDTH, 2 * (int)Padding.IconLeft)")); + Assert.That(toolbarRendering, Does.Contain("max(2, (int)Padding.IconLeft / 2)")); + Assert.That(toolbarCustomize, Does.Contain("iconSize + 2 * edgeSpacing")); + Assert.That(toolbarCustomize, Does.Contain("r.left + edgeSpacing")); + Assert.That(mainWindowCreation, Does.Contain("TopToolBar->ApplyConfiguredIconSpacing()")); + Assert.That(mainWindowCreation, Does.Contain("MiddleToolBar->ApplyConfiguredIconSpacing()")); + Assert.That(mainWindowRefresh, Does.Contain("TopToolBar->ApplyConfiguredIconSpacing()")); + Assert.That(mainWindowRefresh, Does.Contain("MiddleToolBar->ApplyConfiguredIconSpacing()")); + }); + } + + [Test] + public void Go_to_hot_path_uses_a_conservative_saved_location_glyph() + { + var root = FindRepositoryRoot(); + var hotPathIcon = File.ReadAllText(Path.Combine(root, "src", "res", "toolbars", "GoToHotPath.svg")); + + // The command should read as a saved folder destination, not as the old literal flame metaphor. + Assert.Multiple(() => + { + Assert.That(hotPathIcon, Does.Contain("saved destination")); + Assert.That(hotPathIcon, Does.Contain("fill=\"#FFD666\"")); + Assert.That(hotPathIcon, Does.Contain("fill=\"#0F6CBD\"")); + Assert.That(hotPathIcon, Does.Contain("L11.25 8.25L10 9")); + Assert.That(hotPathIcon, Does.Not.Contain("#F7630C")); + Assert.That(hotPathIcon, Does.Not.Contain("C10.5 14.5 12.5 12.5")); + }); + } + + [Test] + public void High_fidelity_reference_catalog_contains_every_runtime_toolbar_icon() + { + var root = FindRepositoryRoot(); + var runtimeDirectory = Path.Combine(root, "src", "res", "toolbars"); + var referenceDirectory = Path.Combine(root, "design", "icon-reference", "fluent-color-essentials"); + var referenceSvgDirectory = Path.Combine(referenceDirectory, "svg"); + var runtimeNames = Directory.GetFiles(runtimeDirectory, "*.svg").Select(Path.GetFileName).Order().ToArray(); + var referenceNames = Directory.GetFiles(referenceSvgDirectory, "*.svg").Select(Path.GetFileName).Order().ToArray(); + + // The archival set must remain a complete, scalable mirror rather than a hand-picked presentation subset. + Assert.That(referenceNames, Is.EqualTo(runtimeNames)); + Assert.That(referenceNames, Has.Length.EqualTo(91)); + foreach (var name in referenceNames) + { + var svg = File.ReadAllText(Path.Combine(referenceSvgDirectory, name!)); + Assert.That(svg, Does.Contain("width=\"64\" height=\"64\" viewBox=\"0 0 16 16\""), name); + } + + var catalogPath = Path.Combine(referenceDirectory, "fluent-color-essentials-icon-catalog.png"); + var pngHeader = File.ReadAllBytes(catalogPath).Take(24).ToArray(); + int width = (pngHeader[16] << 24) | (pngHeader[17] << 16) | (pngHeader[18] << 8) | pngHeader[19]; + int height = (pngHeader[20] << 24) | (pngHeader[21] << 16) | (pngHeader[22] << 8) | pngHeader[23]; + Assert.Multiple(() => + { + Assert.That(width, Is.EqualTo(3840)); + Assert.That(height, Is.EqualTo(3440)); + }); + } + + private static string FindRepositoryRoot() + { + var current = new DirectoryInfo(TestContext.CurrentContext.TestDirectory); + while (current is not null && !Directory.Exists(Path.Combine(current.FullName, "src"))) + current = current.Parent; + + return current?.FullName + ?? throw new DirectoryNotFoundException("Could not locate the FileManager repository root."); + } +} diff --git a/tests/FileManager.UiTests/ToolbarIconSizeUiTests.cs b/tests/FileManager.UiTests/ToolbarIconSizeUiTests.cs new file mode 100644 index 00000000..56959e5b --- /dev/null +++ b/tests/FileManager.UiTests/ToolbarIconSizeUiTests.cs @@ -0,0 +1,81 @@ +using FileManager.UiTests.Infrastructure; +using FlaUI.Core.AutomationElements; +using NUnit.Framework; + +namespace FileManager.UiTests; + +[TestFixture] +[Category("UI")] +public sealed class ToolbarIconSizeUiTests : FileManagerUiTestBase +{ + [Test] + public void Customize_toolbar_cycles_all_icon_sizes_and_persists_the_choice_after_restart() + { + var dialog = OpenCustomizeToolbarDialog(); + var combo = GetIconSizeCombo(dialog); + var originalIndex = GetSelectedIndex(combo); + + Assert.That(combo.Items.Select(item => item.Name), Is.EqualTo(new[] + { + "Small (16x16 pixels)", + "Medium (24x24 pixels)", + "Large (32x32 pixels)", + })); + + // Closing between choices exercises the native image-list rebuild and layout path for every supported size. + for (var index = 0; index < 3; index++) + { + NativeCommands.SelectComboBoxItem(dialog.Properties.NativeWindowHandle.Value, + combo.Properties.NativeWindowHandle.Value, index); + CloseCustomizeToolbarDialog(dialog); + dialog = OpenCustomizeToolbarDialog(); + combo = GetIconSizeCombo(dialog); + Assert.That(GetSelectedIndex(combo), Is.EqualTo(index)); + } + + CloseCustomizeToolbarDialog(dialog); + Thread.Sleep(500); // The native configuration writer intentionally debounces user changes for 250 ms. + RestartFileManager(); + + dialog = OpenCustomizeToolbarDialog(); + combo = GetIconSizeCombo(dialog); + Assert.That(GetSelectedIndex(combo), Is.EqualTo(2), + "The large toolbar icon preference was not restored after restarting the executable."); + + // Restore the disposable profile to its incoming setting so repeated UI runs remain independent. + NativeCommands.SelectComboBoxItem(dialog.Properties.NativeWindowHandle.Value, + combo.Properties.NativeWindowHandle.Value, originalIndex); + CloseCustomizeToolbarDialog(dialog); + Thread.Sleep(500); + } + + private Window OpenCustomizeToolbarDialog() + { + NativeCommands.Execute(MainWindowHandle, NativeCommands.CustomizeTopToolbar); + return WaitForWindow(window => window.Properties.NativeWindowHandle.Value != MainWindowHandle); + } + + private static ComboBox GetIconSizeCombo(Window dialog) + { + // The resource ID is the stable automation identity for the new native combo box. + var combo = dialog.FindFirstDescendant(cf => cf.ByAutomationId("2748"))?.AsComboBox(); + Assert.That(combo, Is.Not.Null, "Customize Toolbar did not expose the icon-size selector."); + return combo!; + } + + private static int GetSelectedIndex(ComboBox combo) + { + // Native selection is authoritative because Win32 UIA providers may cache SelectionItem state. + var selected = NativeCommands.GetComboBoxSelection(combo.Properties.NativeWindowHandle.Value); + Assert.That(selected, Is.GreaterThanOrEqualTo(0), "The icon-size selector had no selected item."); + return selected; + } + + private static void CloseCustomizeToolbarDialog(Window dialog) + { + var closeButton = dialog.FindFirstDescendant(cf => cf.ByAutomationId("1"))?.AsButton(); + Assert.That(closeButton, Is.Not.Null, "Customize Toolbar did not expose its Close button."); + closeButton!.Invoke(); + WaitForWindowToClose(dialog); + } +} diff --git a/tests/FileManager.UiTests/obj/FileManager.UiTests.csproj.nuget.dgspec.json b/tests/FileManager.UiTests/obj/FileManager.UiTests.csproj.nuget.dgspec.json index 514b9806..2a9d925c 100644 --- a/tests/FileManager.UiTests/obj/FileManager.UiTests.csproj.nuget.dgspec.json +++ b/tests/FileManager.UiTests/obj/FileManager.UiTests.csproj.nuget.dgspec.json @@ -1,35 +1,25 @@ { "format": 1, "restore": { - "C:\\Projects\\FileManager\\tests\\FileManager.UiTests\\FileManager.UiTests.csproj": {} + "D:\\FileManager\\tests\\FileManager.UiTests\\FileManager.UiTests.csproj": {} }, "projects": { - "C:\\Projects\\FileManager\\tests\\FileManager.UiTests\\FileManager.UiTests.csproj": { + "D:\\FileManager\\tests\\FileManager.UiTests\\FileManager.UiTests.csproj": { "version": "1.0.0", "restore": { - "projectUniqueName": "C:\\Projects\\FileManager\\tests\\FileManager.UiTests\\FileManager.UiTests.csproj", + "projectUniqueName": "D:\\FileManager\\tests\\FileManager.UiTests\\FileManager.UiTests.csproj", "projectName": "FileManager.UiTests", - "projectPath": "C:\\Projects\\FileManager\\tests\\FileManager.UiTests\\FileManager.UiTests.csproj", - "packagesPath": "C:\\Users\\mzag\\.nuget\\packages\\", - "outputPath": "C:\\Projects\\FileManager\\tests\\FileManager.UiTests\\obj\\", + "projectPath": "D:\\FileManager\\tests\\FileManager.UiTests\\FileManager.UiTests.csproj", + "packagesPath": "C:\\Users\\plesz\\.nuget\\packages\\", + "outputPath": "D:\\FileManager\\tests\\FileManager.UiTests\\obj\\", "projectStyle": "PackageReference", - "fallbackFolders": [ - "C:\\Program Files\\DevExpress 24.1\\Components\\Offline Packages", - "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages" - ], "configFilePaths": [ - "C:\\Users\\mzag\\AppData\\Roaming\\NuGet\\NuGet.Config", - "C:\\Program Files (x86)\\NuGet\\Config\\DevExpress 24.1.config", - "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config", - "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config" + "C:\\Users\\plesz\\AppData\\Roaming\\NuGet\\NuGet.Config" ], "originalTargetFrameworks": [ "net8.0-windows" ], "sources": { - "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {}, - "C:\\Program Files\\DevExpress 24.1\\Components\\System\\Components\\Packages": {}, - "c:\\program files\\dotnet\\library-packs": {}, "https://api.nuget.org/v3/index.json": {} }, "frameworks": { @@ -85,12 +75,26 @@ ], "assetTargetFallback": true, "warn": true, + "downloadDependencies": [ + { + "name": "Microsoft.AspNetCore.App.Ref", + "version": "[8.0.30, 8.0.30]" + }, + { + "name": "Microsoft.NETCore.App.Ref", + "version": "[8.0.30, 8.0.30]" + }, + { + "name": "Microsoft.WindowsDesktop.App.Ref", + "version": "[8.0.30, 8.0.30]" + } + ], "frameworkReferences": { "Microsoft.NETCore.App": { "privateAssets": "all" } }, - "runtimeIdentifierGraphPath": "c:\\program files\\dotnet\\sdk\\10.0.400-preview.0.26322.102/PortableRuntimeIdentifierGraph.json" + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.400/PortableRuntimeIdentifierGraph.json" } } } diff --git a/tests/FileManager.UiTests/obj/FileManager.UiTests.csproj.nuget.g.props b/tests/FileManager.UiTests/obj/FileManager.UiTests.csproj.nuget.g.props index 46c9af64..bdcc9fee 100644 --- a/tests/FileManager.UiTests/obj/FileManager.UiTests.csproj.nuget.g.props +++ b/tests/FileManager.UiTests/obj/FileManager.UiTests.csproj.nuget.g.props @@ -5,14 +5,12 @@ NuGet $(MSBuildThisFileDirectory)project.assets.json $(UserProfile)\.nuget\packages\ - C:\Users\mzag\.nuget\packages\;C:\Program Files\DevExpress 24.1\Components\Offline Packages;C:\Program Files (x86)\Microsoft Visual Studio\Shared\NuGetPackages + C:\Users\plesz\.nuget\packages\ PackageReference 7.0.0 - - - + @@ -25,6 +23,6 @@ - C:\Users\mzag\.nuget\packages\interop.uiautomationclient\10.19041.0 + C:\Users\plesz\.nuget\packages\interop.uiautomationclient\10.19041.0 \ No newline at end of file diff --git a/tests/FileManager.UiTests/obj/FileManager.UiTests.csproj.nuget.g.targets b/tests/FileManager.UiTests/obj/FileManager.UiTests.csproj.nuget.g.targets deleted file mode 100644 index e622fa32..00000000 --- a/tests/FileManager.UiTests/obj/FileManager.UiTests.csproj.nuget.g.targets +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/tests/FileManager.UiTests/obj/project.assets.json b/tests/FileManager.UiTests/obj/project.assets.json index 34e5a46f..6a94405e 100644 --- a/tests/FileManager.UiTests/obj/project.assets.json +++ b/tests/FileManager.UiTests/obj/project.assets.json @@ -2391,36 +2391,24 @@ ] }, "packageFolders": { - "C:\\Users\\mzag\\.nuget\\packages\\": {}, - "C:\\Program Files\\DevExpress 24.1\\Components\\Offline Packages": {}, - "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages": {} + "C:\\Users\\plesz\\.nuget\\packages\\": {} }, "project": { "version": "1.0.0", "restore": { - "projectUniqueName": "C:\\Projects\\FileManager\\tests\\FileManager.UiTests\\FileManager.UiTests.csproj", + "projectUniqueName": "D:\\FileManager\\tests\\FileManager.UiTests\\FileManager.UiTests.csproj", "projectName": "FileManager.UiTests", - "projectPath": "C:\\Projects\\FileManager\\tests\\FileManager.UiTests\\FileManager.UiTests.csproj", - "packagesPath": "C:\\Users\\mzag\\.nuget\\packages\\", - "outputPath": "C:\\Projects\\FileManager\\tests\\FileManager.UiTests\\obj\\", + "projectPath": "D:\\FileManager\\tests\\FileManager.UiTests\\FileManager.UiTests.csproj", + "packagesPath": "C:\\Users\\plesz\\.nuget\\packages\\", + "outputPath": "D:\\FileManager\\tests\\FileManager.UiTests\\obj\\", "projectStyle": "PackageReference", - "fallbackFolders": [ - "C:\\Program Files\\DevExpress 24.1\\Components\\Offline Packages", - "C:\\Program Files (x86)\\Microsoft Visual Studio\\Shared\\NuGetPackages" - ], "configFilePaths": [ - "C:\\Users\\mzag\\AppData\\Roaming\\NuGet\\NuGet.Config", - "C:\\Program Files (x86)\\NuGet\\Config\\DevExpress 24.1.config", - "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.FallbackLocation.config", - "C:\\Program Files (x86)\\NuGet\\Config\\Microsoft.VisualStudio.Offline.config" + "C:\\Users\\plesz\\AppData\\Roaming\\NuGet\\NuGet.Config" ], "originalTargetFrameworks": [ "net8.0-windows" ], "sources": { - "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackages\\": {}, - "C:\\Program Files\\DevExpress 24.1\\Components\\System\\Components\\Packages": {}, - "c:\\program files\\dotnet\\library-packs": {}, "https://api.nuget.org/v3/index.json": {} }, "frameworks": { @@ -2476,12 +2464,26 @@ ], "assetTargetFallback": true, "warn": true, + "downloadDependencies": [ + { + "name": "Microsoft.AspNetCore.App.Ref", + "version": "[8.0.30, 8.0.30]" + }, + { + "name": "Microsoft.NETCore.App.Ref", + "version": "[8.0.30, 8.0.30]" + }, + { + "name": "Microsoft.WindowsDesktop.App.Ref", + "version": "[8.0.30, 8.0.30]" + } + ], "frameworkReferences": { "Microsoft.NETCore.App": { "privateAssets": "all" } }, - "runtimeIdentifierGraphPath": "c:\\program files\\dotnet\\sdk\\10.0.400-preview.0.26322.102/PortableRuntimeIdentifierGraph.json" + "runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.400/PortableRuntimeIdentifierGraph.json" } } } diff --git a/tests/FileManager.UiTests/obj/project.nuget.cache b/tests/FileManager.UiTests/obj/project.nuget.cache index 006e37a5..98cd1498 100644 --- a/tests/FileManager.UiTests/obj/project.nuget.cache +++ b/tests/FileManager.UiTests/obj/project.nuget.cache @@ -1,41 +1,44 @@ { "version": 2, - "dgSpecHash": "MCBuRYmIkbE=", + "dgSpecHash": "mMB6YJ3PAdo=", "success": true, - "projectFilePath": "C:\\Projects\\FileManager\\tests\\FileManager.UiTests\\FileManager.UiTests.csproj", + "projectFilePath": "D:\\FileManager\\tests\\FileManager.UiTests\\FileManager.UiTests.csproj", "expectedPackageFiles": [ - "C:\\Users\\mzag\\.nuget\\packages\\flaui.core\\5.0.0\\flaui.core.5.0.0.nupkg.sha512", - "C:\\Users\\mzag\\.nuget\\packages\\flaui.uia3\\5.0.0\\flaui.uia3.5.0.0.nupkg.sha512", - "C:\\Users\\mzag\\.nuget\\packages\\interop.uiautomationclient\\10.19041.0\\interop.uiautomationclient.10.19041.0.nupkg.sha512", - "C:\\Users\\mzag\\.nuget\\packages\\microsoft.applicationinsights\\2.22.0\\microsoft.applicationinsights.2.22.0.nupkg.sha512", - "C:\\Users\\mzag\\.nuget\\packages\\microsoft.codecoverage\\17.12.0\\microsoft.codecoverage.17.12.0.nupkg.sha512", - "C:\\Users\\mzag\\.nuget\\packages\\microsoft.net.test.sdk\\17.12.0\\microsoft.net.test.sdk.17.12.0.nupkg.sha512", - "C:\\Users\\mzag\\.nuget\\packages\\microsoft.netcore.platforms\\5.0.0\\microsoft.netcore.platforms.5.0.0.nupkg.sha512", - "C:\\Users\\mzag\\.nuget\\packages\\microsoft.testing.extensions.telemetry\\1.5.3\\microsoft.testing.extensions.telemetry.1.5.3.nupkg.sha512", - "C:\\Users\\mzag\\.nuget\\packages\\microsoft.testing.extensions.trxreport.abstractions\\1.5.3\\microsoft.testing.extensions.trxreport.abstractions.1.5.3.nupkg.sha512", - "C:\\Users\\mzag\\.nuget\\packages\\microsoft.testing.extensions.vstestbridge\\1.5.3\\microsoft.testing.extensions.vstestbridge.1.5.3.nupkg.sha512", - "C:\\Users\\mzag\\.nuget\\packages\\microsoft.testing.platform\\1.5.3\\microsoft.testing.platform.1.5.3.nupkg.sha512", - "C:\\Users\\mzag\\.nuget\\packages\\microsoft.testing.platform.msbuild\\1.5.3\\microsoft.testing.platform.msbuild.1.5.3.nupkg.sha512", - "C:\\Users\\mzag\\.nuget\\packages\\microsoft.testplatform.objectmodel\\17.12.0\\microsoft.testplatform.objectmodel.17.12.0.nupkg.sha512", - "C:\\Users\\mzag\\.nuget\\packages\\microsoft.testplatform.testhost\\17.12.0\\microsoft.testplatform.testhost.17.12.0.nupkg.sha512", - "C:\\Users\\mzag\\.nuget\\packages\\microsoft.win32.registry\\5.0.0\\microsoft.win32.registry.5.0.0.nupkg.sha512", - "C:\\Users\\mzag\\.nuget\\packages\\microsoft.win32.systemevents\\8.0.0\\microsoft.win32.systemevents.8.0.0.nupkg.sha512", - "C:\\Users\\mzag\\.nuget\\packages\\newtonsoft.json\\13.0.1\\newtonsoft.json.13.0.1.nupkg.sha512", - "C:\\Users\\mzag\\.nuget\\packages\\nunit\\4.3.2\\nunit.4.3.2.nupkg.sha512", - "C:\\Users\\mzag\\.nuget\\packages\\nunit3testadapter\\5.0.0\\nunit3testadapter.5.0.0.nupkg.sha512", - "C:\\Users\\mzag\\.nuget\\packages\\system.codedom\\8.0.0\\system.codedom.8.0.0.nupkg.sha512", - "C:\\Users\\mzag\\.nuget\\packages\\system.configuration.configurationmanager\\8.0.1\\system.configuration.configurationmanager.8.0.1.nupkg.sha512", - "C:\\Users\\mzag\\.nuget\\packages\\system.diagnostics.diagnosticsource\\5.0.0\\system.diagnostics.diagnosticsource.5.0.0.nupkg.sha512", - "C:\\Users\\mzag\\.nuget\\packages\\system.diagnostics.eventlog\\8.0.1\\system.diagnostics.eventlog.8.0.1.nupkg.sha512", - "C:\\Users\\mzag\\.nuget\\packages\\system.diagnostics.performancecounter\\8.0.1\\system.diagnostics.performancecounter.8.0.1.nupkg.sha512", - "C:\\Users\\mzag\\.nuget\\packages\\system.drawing.common\\8.0.10\\system.drawing.common.8.0.10.nupkg.sha512", - "C:\\Users\\mzag\\.nuget\\packages\\system.management\\8.0.0\\system.management.8.0.0.nupkg.sha512", - "C:\\Users\\mzag\\.nuget\\packages\\system.reflection.metadata\\1.6.0\\system.reflection.metadata.1.6.0.nupkg.sha512", - "C:\\Users\\mzag\\.nuget\\packages\\system.security.accesscontrol\\5.0.0\\system.security.accesscontrol.5.0.0.nupkg.sha512", - "C:\\Users\\mzag\\.nuget\\packages\\system.security.cryptography.protecteddata\\8.0.0\\system.security.cryptography.protecteddata.8.0.0.nupkg.sha512", - "C:\\Users\\mzag\\.nuget\\packages\\system.security.permissions\\8.0.0\\system.security.permissions.8.0.0.nupkg.sha512", - "C:\\Users\\mzag\\.nuget\\packages\\system.security.principal.windows\\5.0.0\\system.security.principal.windows.5.0.0.nupkg.sha512", - "C:\\Users\\mzag\\.nuget\\packages\\system.windows.extensions\\8.0.0\\system.windows.extensions.8.0.0.nupkg.sha512" + "C:\\Users\\plesz\\.nuget\\packages\\flaui.core\\5.0.0\\flaui.core.5.0.0.nupkg.sha512", + "C:\\Users\\plesz\\.nuget\\packages\\flaui.uia3\\5.0.0\\flaui.uia3.5.0.0.nupkg.sha512", + "C:\\Users\\plesz\\.nuget\\packages\\interop.uiautomationclient\\10.19041.0\\interop.uiautomationclient.10.19041.0.nupkg.sha512", + "C:\\Users\\plesz\\.nuget\\packages\\microsoft.applicationinsights\\2.22.0\\microsoft.applicationinsights.2.22.0.nupkg.sha512", + "C:\\Users\\plesz\\.nuget\\packages\\microsoft.codecoverage\\17.12.0\\microsoft.codecoverage.17.12.0.nupkg.sha512", + "C:\\Users\\plesz\\.nuget\\packages\\microsoft.net.test.sdk\\17.12.0\\microsoft.net.test.sdk.17.12.0.nupkg.sha512", + "C:\\Users\\plesz\\.nuget\\packages\\microsoft.netcore.platforms\\5.0.0\\microsoft.netcore.platforms.5.0.0.nupkg.sha512", + "C:\\Users\\plesz\\.nuget\\packages\\microsoft.testing.extensions.telemetry\\1.5.3\\microsoft.testing.extensions.telemetry.1.5.3.nupkg.sha512", + "C:\\Users\\plesz\\.nuget\\packages\\microsoft.testing.extensions.trxreport.abstractions\\1.5.3\\microsoft.testing.extensions.trxreport.abstractions.1.5.3.nupkg.sha512", + "C:\\Users\\plesz\\.nuget\\packages\\microsoft.testing.extensions.vstestbridge\\1.5.3\\microsoft.testing.extensions.vstestbridge.1.5.3.nupkg.sha512", + "C:\\Users\\plesz\\.nuget\\packages\\microsoft.testing.platform\\1.5.3\\microsoft.testing.platform.1.5.3.nupkg.sha512", + "C:\\Users\\plesz\\.nuget\\packages\\microsoft.testing.platform.msbuild\\1.5.3\\microsoft.testing.platform.msbuild.1.5.3.nupkg.sha512", + "C:\\Users\\plesz\\.nuget\\packages\\microsoft.testplatform.objectmodel\\17.12.0\\microsoft.testplatform.objectmodel.17.12.0.nupkg.sha512", + "C:\\Users\\plesz\\.nuget\\packages\\microsoft.testplatform.testhost\\17.12.0\\microsoft.testplatform.testhost.17.12.0.nupkg.sha512", + "C:\\Users\\plesz\\.nuget\\packages\\microsoft.win32.registry\\5.0.0\\microsoft.win32.registry.5.0.0.nupkg.sha512", + "C:\\Users\\plesz\\.nuget\\packages\\microsoft.win32.systemevents\\8.0.0\\microsoft.win32.systemevents.8.0.0.nupkg.sha512", + "C:\\Users\\plesz\\.nuget\\packages\\newtonsoft.json\\13.0.1\\newtonsoft.json.13.0.1.nupkg.sha512", + "C:\\Users\\plesz\\.nuget\\packages\\nunit\\4.3.2\\nunit.4.3.2.nupkg.sha512", + "C:\\Users\\plesz\\.nuget\\packages\\nunit3testadapter\\5.0.0\\nunit3testadapter.5.0.0.nupkg.sha512", + "C:\\Users\\plesz\\.nuget\\packages\\system.codedom\\8.0.0\\system.codedom.8.0.0.nupkg.sha512", + "C:\\Users\\plesz\\.nuget\\packages\\system.configuration.configurationmanager\\8.0.1\\system.configuration.configurationmanager.8.0.1.nupkg.sha512", + "C:\\Users\\plesz\\.nuget\\packages\\system.diagnostics.diagnosticsource\\5.0.0\\system.diagnostics.diagnosticsource.5.0.0.nupkg.sha512", + "C:\\Users\\plesz\\.nuget\\packages\\system.diagnostics.eventlog\\8.0.1\\system.diagnostics.eventlog.8.0.1.nupkg.sha512", + "C:\\Users\\plesz\\.nuget\\packages\\system.diagnostics.performancecounter\\8.0.1\\system.diagnostics.performancecounter.8.0.1.nupkg.sha512", + "C:\\Users\\plesz\\.nuget\\packages\\system.drawing.common\\8.0.10\\system.drawing.common.8.0.10.nupkg.sha512", + "C:\\Users\\plesz\\.nuget\\packages\\system.management\\8.0.0\\system.management.8.0.0.nupkg.sha512", + "C:\\Users\\plesz\\.nuget\\packages\\system.reflection.metadata\\1.6.0\\system.reflection.metadata.1.6.0.nupkg.sha512", + "C:\\Users\\plesz\\.nuget\\packages\\system.security.accesscontrol\\5.0.0\\system.security.accesscontrol.5.0.0.nupkg.sha512", + "C:\\Users\\plesz\\.nuget\\packages\\system.security.cryptography.protecteddata\\8.0.0\\system.security.cryptography.protecteddata.8.0.0.nupkg.sha512", + "C:\\Users\\plesz\\.nuget\\packages\\system.security.permissions\\8.0.0\\system.security.permissions.8.0.0.nupkg.sha512", + "C:\\Users\\plesz\\.nuget\\packages\\system.security.principal.windows\\5.0.0\\system.security.principal.windows.5.0.0.nupkg.sha512", + "C:\\Users\\plesz\\.nuget\\packages\\system.windows.extensions\\8.0.0\\system.windows.extensions.8.0.0.nupkg.sha512", + "C:\\Users\\plesz\\.nuget\\packages\\microsoft.netcore.app.ref\\8.0.30\\microsoft.netcore.app.ref.8.0.30.nupkg.sha512", + "C:\\Users\\plesz\\.nuget\\packages\\microsoft.windowsdesktop.app.ref\\8.0.30\\microsoft.windowsdesktop.app.ref.8.0.30.nupkg.sha512", + "C:\\Users\\plesz\\.nuget\\packages\\microsoft.aspnetcore.app.ref\\8.0.30\\microsoft.aspnetcore.app.ref.8.0.30.nupkg.sha512" ], "logs": [] } \ No newline at end of file diff --git a/tests/bzip2-vectors/fuzz/invalid-header.hex b/tests/bzip2-vectors/fuzz/invalid-header.hex new file mode 100644 index 00000000..4301dd03 --- /dev/null +++ b/tests/bzip2-vectors/fuzz/invalid-header.hex @@ -0,0 +1 @@ +425a6841 diff --git a/tests/bzip2-vectors/fuzz/mutated-block-header.hex b/tests/bzip2-vectors/fuzz/mutated-block-header.hex new file mode 100644 index 00000000..ffe9f10a --- /dev/null +++ b/tests/bzip2-vectors/fuzz/mutated-block-header.hex @@ -0,0 +1 @@ +425a6839b1415926535995077d1c0000055f80001040001000000088003ee7dd302000486a9ea66a7a689ea64da9b48351934000022b822764e5d282beae707d2664a0aead00d1441d1c49db76d068b902c023dc8a1d468589d2e7e7a92df17724538509095077d1c0 diff --git a/tests/bzip2-vectors/fuzz/mutated-crc.hex b/tests/bzip2-vectors/fuzz/mutated-crc.hex new file mode 100644 index 00000000..aceb0d09 --- /dev/null +++ b/tests/bzip2-vectors/fuzz/mutated-crc.hex @@ -0,0 +1 @@ +425a68393141592653d995077d1c0000055f80001040001000000088003ee7dd302000486a9ea66a7a689ea64da9b48351934000022b822764e5d282beae707d2664a0aead00d1441d1c49db76d068b902c023dc8a1d468589d2e7e7a92df17724538509095077d1c0 diff --git a/tests/bzip2-vectors/fuzz/mutated-footer.hex b/tests/bzip2-vectors/fuzz/mutated-footer.hex new file mode 100644 index 00000000..d8a96646 --- /dev/null +++ b/tests/bzip2-vectors/fuzz/mutated-footer.hex @@ -0,0 +1 @@ +425a683931415926535995077d1c0000055f80001040001000000088003ee7dd302000486a9ea66a7a689ea64da9b48351934000022b822764e5d282beae707d2664a0aead00d1441d1c49db76d068b902c023dc8a1d468589d2e7e7a92df177245385090950f7d1c0 diff --git a/tests/bzip2-vectors/fuzz/mutated-payload.hex b/tests/bzip2-vectors/fuzz/mutated-payload.hex new file mode 100644 index 00000000..29df1ac3 --- /dev/null +++ b/tests/bzip2-vectors/fuzz/mutated-payload.hex @@ -0,0 +1 @@ +425a683931415926535995077d1c0000055f80001040001000000088003ee7dd302000486a9ea66a7a689ea64da9b48351934000822b822764e5d282beae707d2664a0aead00d1441d1c49db76d068b902c023dc8a1d468589d2e7e7a92df17724538509095077d1c0 diff --git a/tests/bzip2-vectors/fuzz/random-bytes.hex b/tests/bzip2-vectors/fuzz/random-bytes.hex new file mode 100644 index 00000000..62847fe1 --- /dev/null +++ b/tests/bzip2-vectors/fuzz/random-bytes.hex @@ -0,0 +1 @@ +00010203040506070809fffe112233445566778899aabbccddeeff00 diff --git a/tests/bzip2-vectors/golden-stream.hex b/tests/bzip2-vectors/golden-stream.hex new file mode 100644 index 00000000..3fedfcba --- /dev/null +++ b/tests/bzip2-vectors/golden-stream.hex @@ -0,0 +1 @@ +425a683931415926535995077d1c0000055f80001040001000000088003ee7dd302000486a9ea66a7a689ea64da9b48351934000022b822764e5d282beae707d2664a0aead00d1441d1c49db76d068b902c023dc8a1d468589d2e7e7a92df17724538509095077d1c0 diff --git a/tests/bzip2-vectors/legacy-stream.hex b/tests/bzip2-vectors/legacy-stream.hex new file mode 100644 index 00000000..07bb089a --- /dev/null +++ b/tests/bzip2-vectors/legacy-stream.hex @@ -0,0 +1 @@ +425a683131415926535917f25e9900000b59804010400010003ae555b0200022268069a30214c269a034c4d4639543da0829bb1bb34cac4818084f88be2ee48a70a1202fe4bd32 diff --git a/tests/bzip2-vectors/truncated-stream.hex b/tests/bzip2-vectors/truncated-stream.hex new file mode 100644 index 00000000..6d48f10b --- /dev/null +++ b/tests/bzip2-vectors/truncated-stream.hex @@ -0,0 +1 @@ +425a683931415926535995077d1c0000055f80001040001000000088003ee7dd302000486a9ea66a7a689ea64da9b48351934000022b822764e5d282beae707d2664a0aead00d1441d1c49db76d068b902c023dc8a1d468589d2e7e7a92df17724538509 diff --git a/tests/cmark-gfm/snapshots/basic.html b/tests/cmark-gfm/snapshots/basic.html new file mode 100644 index 00000000..63f80a74 --- /dev/null +++ b/tests/cmark-gfm/snapshots/basic.html @@ -0,0 +1,2 @@ +

      Snapshot

      +

      Hello world.

      diff --git a/tests/cmark-gfm/snapshots/basic.md b/tests/cmark-gfm/snapshots/basic.md new file mode 100644 index 00000000..64b2eab6 --- /dev/null +++ b/tests/cmark-gfm/snapshots/basic.md @@ -0,0 +1,3 @@ +# Snapshot + +Hello *world*. diff --git a/tests/cmark-gfm/snapshots/strikethrough.html b/tests/cmark-gfm/snapshots/strikethrough.html new file mode 100644 index 00000000..17d44ccf --- /dev/null +++ b/tests/cmark-gfm/snapshots/strikethrough.html @@ -0,0 +1 @@ +

      retained feature

      diff --git a/tests/cmark-gfm/snapshots/strikethrough.md b/tests/cmark-gfm/snapshots/strikethrough.md new file mode 100644 index 00000000..28f2a904 --- /dev/null +++ b/tests/cmark-gfm/snapshots/strikethrough.md @@ -0,0 +1 @@ +~~retained feature~~ diff --git a/tests/zlib-vectors/bad-adler32-zlib-stream.hex b/tests/zlib-vectors/bad-adler32-zlib-stream.hex new file mode 100644 index 00000000..d111ce49 --- /dev/null +++ b/tests/zlib-vectors/bad-adler32-zlib-stream.hex @@ -0,0 +1 @@ +789c05c1d10dc0200805404761a37e7482a7929404c15034b1d3f7ee9a6c744331609d833e954acdc7444a15953cb4b9a5475996b1dee44e88f6c8669a38eae8e5077b711900 diff --git a/tests/zlib-vectors/invalid-deflate-zlib-stream.hex b/tests/zlib-vectors/invalid-deflate-zlib-stream.hex new file mode 100644 index 00000000..a9e9a4e2 --- /dev/null +++ b/tests/zlib-vectors/invalid-deflate-zlib-stream.hex @@ -0,0 +1 @@ +789cffffffff diff --git a/tests/zlib-vectors/legacy-zlib-1.2.11.hex b/tests/zlib-vectors/legacy-zlib-1.2.11.hex new file mode 100644 index 00000000..2ab1cb60 --- /dev/null +++ b/tests/zlib-vectors/legacy-zlib-1.2.11.hex @@ -0,0 +1 @@ +789c05c1d10dc0200805404761a37e7482a7929404c15034b1d3f7ee9a6c744331609d833e954acdc7444a15953cb4b9a5475996b1dee44e88f6c8669a38eae8e5077b7119c3 diff --git a/tests/zlib-vectors/truncated-zlib-stream.hex b/tests/zlib-vectors/truncated-zlib-stream.hex new file mode 100644 index 00000000..2eb17398 --- /dev/null +++ b/tests/zlib-vectors/truncated-zlib-stream.hex @@ -0,0 +1 @@ +789c05c1d10dc0200805404761a37e7482a7929404c15034b1d3f7ee9a6c744331609d833e954acdc7444a15953cb4b9a5475996b1dee44e88f6c8669a38eae8e5077b diff --git a/tools/bzip2_compatibility_probe.c b/tools/bzip2_compatibility_probe.c new file mode 100644 index 00000000..4b6ababe --- /dev/null +++ b/tools/bzip2_compatibility_probe.c @@ -0,0 +1,168 @@ +// This probe exercises the host-facing streaming API so bzip2 vendor updates +// cannot silently accept corrupt archive data outside the adapter's call shape. +#include +#include +#include +#include + +#include "bzlib.h" + +static const unsigned char kGoldenPlaintext[] = + "Open Salamander bzip2 golden archive\n" + "archive adapter streaming compatibility\n"; +static const unsigned char kLegacyPlaintext[] = + "legacy bzip2 archive\0with binary\n"; + +void bz_internal_error(int errorCode) +{ + fprintf(stderr, "Unexpected bzip2 internal error %d.\n", errorCode); + abort(); +} + +static int ReadHexFixture(const char* path, unsigned char** data, size_t* length) +{ + FILE* file = fopen(path, "rb"); + long fileLength; + char* text; + size_t index; + size_t outputLength = 0; + int highNibble = -1; + + if (file == NULL || fseek(file, 0, SEEK_END) != 0 || + (fileLength = ftell(file)) < 0 || fseek(file, 0, SEEK_SET) != 0) + { + if (file != NULL) fclose(file); + return 0; + } + + text = (char*)malloc((size_t)fileLength + 1); + *data = (unsigned char*)malloc((size_t)fileLength / 2 + 1); + if (text == NULL || *data == NULL || + fread(text, 1, (size_t)fileLength, file) != (size_t)fileLength) + { + free(text); + free(*data); + *data = NULL; + fclose(file); + return 0; + } + fclose(file); + + for (index = 0; index < (size_t)fileLength; ++index) + { + int value; + if (isspace((unsigned char)text[index])) continue; + if (!isxdigit((unsigned char)text[index])) goto fail; + value = isdigit((unsigned char)text[index]) ? text[index] - '0' : + tolower((unsigned char)text[index]) - 'a' + 10; + if (highNibble < 0) + highNibble = value; + else + { + (*data)[outputLength++] = (unsigned char)((highNibble << 4) | value); + highNibble = -1; + } + } + free(text); + if (highNibble >= 0) goto fail_data; + *length = outputLength; + return 1; + +fail: + free(text); +fail_data: + free(*data); + *data = NULL; + return 0; +} + +static int DecodeFixture(const char* path, const unsigned char* expected, size_t expectedLength, int mustFinish) +{ + unsigned char* compressed = NULL; + size_t compressedLength = 0; + size_t inputOffset = 0; + size_t outputLength = 0; + unsigned char output[256]; + bz_stream stream; + int result; + int iterations = 0; + int success = 0; + + if (!ReadHexFixture(path, &compressed, &compressedLength)) return 0; + memset(&stream, 0, sizeof(stream)); + if (BZ2_bzDecompressInit(&stream, 0, 0) != BZ_OK) goto cleanup; + + while (++iterations < 4096) + { + unsigned char chunk[13]; + unsigned int outputBefore; + if (stream.avail_in == 0 && inputOffset < compressedLength) + { + size_t inputLength = compressedLength - inputOffset; + if (inputLength > 7) inputLength = 7; + stream.next_in = (char*)compressed + inputOffset; + stream.avail_in = (unsigned int)inputLength; + inputOffset += inputLength; + } + stream.next_out = (char*)chunk; + stream.avail_out = sizeof(chunk); + outputBefore = stream.avail_out; + result = BZ2_bzDecompress(&stream); + if (sizeof(chunk) - stream.avail_out > sizeof(output) - outputLength) goto cleanup; + memcpy(output + outputLength, chunk, sizeof(chunk) - stream.avail_out); + outputLength += sizeof(chunk) - stream.avail_out; + if (result == BZ_STREAM_END) + { + success = mustFinish && outputLength == expectedLength && + memcmp(output, expected, expectedLength) == 0; + goto cleanup; + } + if (result != BZ_OK) + { + success = !mustFinish; + goto cleanup; + } + if (inputOffset == compressedLength && stream.avail_in == 0 && + outputBefore == stream.avail_out) + { + success = !mustFinish; + goto cleanup; + } + } + +cleanup: + BZ2_bzDecompressEnd(&stream); + free(compressed); + return success; +} + +int main(int argc, char* argv[]) +{ + int index; + if (argc < 9) + { + fprintf(stderr, "Expected two golden, one truncation, and five fuzz fixtures.\n"); + return 2; + } + if (strcmp(BZ2_bzlibVersion(), "1.0.8, 13-Jul-2019") != 0) + { + fprintf(stderr, "Expected bzip2 1.0.8, loaded %s.\n", BZ2_bzlibVersion()); + return 1; + } + if (!DecodeFixture(argv[1], kGoldenPlaintext, sizeof(kGoldenPlaintext) - 1, 1) || + !DecodeFixture(argv[2], kLegacyPlaintext, sizeof(kLegacyPlaintext) - 1, 1)) + { + fprintf(stderr, "A golden bzip2 archive did not decode through the streaming API.\n"); + return 1; + } + for (index = 3; index < argc; ++index) + { + if (!DecodeFixture(argv[index], NULL, 0, 0)) + { + fprintf(stderr, "A truncation or fuzz fixture unexpectedly reached BZ_STREAM_END: %s\n", argv[index]); + return 1; + } + } + puts("bzip2 1.0.8 golden archives, truncation, and fuzz corpus passed."); + return 0; +} diff --git a/tools/cmark_gfm_hardening_probe.cpp b/tools/cmark_gfm_hardening_probe.cpp new file mode 100644 index 00000000..ee5e5947 --- /dev/null +++ b/tools/cmark_gfm_hardening_probe.cpp @@ -0,0 +1,148 @@ +// SPDX-FileCopyrightText: 2026 Taskscape Ltd +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include + +#include +#include +#include +#include + +#include "markdown_rendering.h" + +namespace +{ +bool ReadFile(const char* path, std::string* contents) +{ + std::ifstream file(path, std::ios::binary); + if (!file) + return false; + contents->assign(std::istreambuf_iterator(file), std::istreambuf_iterator()); + return true; +} + +void NormalizeSnapshotLineEndings(std::string* contents) +{ + // Git may materialize golden text with CRLF on Windows while cmark emits + // canonical LF, so compare content independently of checkout convention. + size_t output = 0; + for (size_t input = 0; input != contents->size(); ++input) + { + char value = (*contents)[input]; + if (value == '\r') + { + if (input + 1 != contents->size() && (*contents)[input + 1] == '\n') + ++input; + value = '\n'; + } + (*contents)[output++] = value; + } + contents->resize(output); +} + +bool RunSnapshot(const char* markdownPath, const char* htmlPath) +{ + std::string markdown; + std::string expectedHtml; + if (!ReadFile(markdownPath, &markdown) || !ReadFile(htmlPath, &expectedHtml)) + return false; + NormalizeSnapshotLineEndings(&expectedHtml); + + char* html = NULL; + size_t htmlLength = 0; + EMarkdownRenderResult result = RenderMarkdownToSafeHtml(markdown.data(), markdown.size(), mmeAllViewerExtensions, &html, &htmlLength); + bool matches = result == mrrOk && expectedHtml.size() == htmlLength && memcmp(expectedHtml.data(), html, htmlLength) == 0; + FreeRenderedMarkdownHtml(html); + return matches; +} + +unsigned int NextRandom(unsigned int* state) +{ + *state = *state * 1664525u + 1013904223u; + return *state; +} + +bool RunExtensionCombinationFuzz() +{ + static const char Alphabet[] = "#>*_[]()|~`<&:/. -\nabcdefghijklmnopqrstuvwxyz0123456789"; + unsigned int state = 0xC0FFEEu; + for (unsigned int mask = 0; mask <= mmeAllViewerExtensions; ++mask) + { + for (unsigned int iteration = 0; iteration != 128; ++iteration) + { + std::string markdown; + size_t length = 1 + (NextRandom(&state) % 2048); + markdown.reserve(length); + for (size_t i = 0; i != length; ++i) + markdown.push_back(Alphabet[NextRandom(&state) % (sizeof(Alphabet) - 1)]); + + char* html = NULL; + size_t htmlLength = 0; + EMarkdownRenderResult result = RenderMarkdownToSafeHtml(markdown.data(), markdown.size(), mask, &html, &htmlLength); + // Every subset is exercised because cmark extensions share parser and renderer hooks. + bool safe = result != mrrOk || (strstr(html, "href=\"javascript:") == NULL && strstr(html, " list: 'src/plugins/automation/generated', 'src/plugins/shared/lukas', 'src/plugins/ftp', - 'src/plugins/ftp/openssl', 'src/plugins/ieviewer/cmark-gfm/build/src', 'src/plugins/ieviewer/cmark-gfm/build/extensions', 'src/plugins/ieviewer/cmark-gfm/extensions', diff --git a/tools/comments/translation_status.py b/tools/comments/translation_status.py index 28ce748a..a1d392d3 100644 --- a/tools/comments/translation_status.py +++ b/tools/comments/translation_status.py @@ -174,7 +174,6 @@ def _normalize_excluded(entries: Iterable[str]) -> tuple[str, ...]: "src/plugins/7zip/7za/cpp", "src/plugins/automation/generated", "src/plugins/checksum/tomcrypt", - "src/plugins/ftp/openssl", "src/plugins/ieviewer/cmark-gfm", "src/plugins/mmviewer/ogg/vorbis", "src/plugins/mmviewer/wma/wmsdk", diff --git a/tools/max-path-buffer-exemptions.md b/tools/max-path-buffer-exemptions.md new file mode 100644 index 00000000..a2c41625 --- /dev/null +++ b/tools/max-path-buffer-exemptions.md @@ -0,0 +1,19 @@ +# Fixed `MAX_PATH` buffer exemptions + +No exemptions are currently approved. + +## Exception contract + +An exception is permitted only when an API contract makes a fixed caller-owned +buffer unavoidable and a compatibility adapter cannot yet be introduced. The +declaration must carry a trailing `MAX_PATH-RATCHET-EXEMPT: ID` marker, and +this register must contain a matching entry: + +```markdown +### ID +- File: `src/example.cpp` +- Reason: The exact API contract requiring this buffer. +- Removal: The migration issue or concrete condition that removes it. +``` + +Exemptions are reviewed debt, never a mechanism for new ordinary path handling. diff --git a/tools/prepare_installer.ps1 b/tools/prepare_installer.ps1 index ab2ccffc..975aea0a 100644 --- a/tools/prepare_installer.ps1 +++ b/tools/prepare_installer.ps1 @@ -67,14 +67,6 @@ Copy-Exe @("$BuildDir\Release_x64\translator.exe", "$BuildDir\Release_Win32\tran Copy-Exe @("$BuildDir\Release_x64\fcremote.exe") "fcremote.exe" "$StagingDir\" Copy-Exe @("$BuildDir\Release_x64\7zwrapper.exe") "7zwrapper.exe" "$StagingDir\" -# OpenSSL -$opensslCopied1 = Copy-Exe @("utils\libeay32.dll", "external\openssl\libeay32.dll", "libeay32.dll") "libeay32.dll" "$StagingDir\utils\" -$opensslCopied2 = Copy-Exe @("utils\ssleay32.dll", "external\openssl\ssleay32.dll", "ssleay32.dll") "ssleay32.dll" "$StagingDir\utils\" - -if (-not $opensslCopied1 -or -not $opensslCopied2) { - Write-Warning "OpenSSL DLLs not found. FTP encryption might not work." -} - # 3. Copy lang (main app) Copy-Item "Installer\lang\*" "$StagingDir\lang\" -Recurse -ErrorAction SilentlyContinue diff --git a/tools/release-inputs.json b/tools/release-inputs.json new file mode 100644 index 00000000..f8f4f89e --- /dev/null +++ b/tools/release-inputs.json @@ -0,0 +1,11 @@ +{ + "schemaVersion": 1, + "inputs": { + "innoSetup": { + "version": "6.7.3", + "url": "https://github.com/jrsoftware/issrc/releases/download/is-6_7_3/innosetup-6.7.3.exe", + "sha256": "9c73c3bae7ed48d44112a0f48e66742c00090bdb5bef71d9d3c056c66e97b732", + "publisher": "Pyrsys B.V." + } + } +} diff --git a/tools/run-lock-verifier-stress.ps1 b/tools/run-lock-verifier-stress.ps1 new file mode 100644 index 00000000..2fa9a349 --- /dev/null +++ b/tools/run-lock-verifier-stress.ps1 @@ -0,0 +1,83 @@ +[CmdletBinding()] +param( + [Parameter(Mandatory = $true)] + [string] $ExecutablePath, + [Parameter(Mandatory = $true)] + [string] $TestProject, + [Parameter(Mandatory = $true)] + [string] $AppVerifierPath, + [Parameter(Mandatory = $true)] + [string] $LogOutputDirectory, + [ValidateSet('Locks', 'Full')] + [string] $VerifierProfile = 'Locks', + [string] $TestFilter = 'TestCategory=LockStress' +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +if (-not (Test-Path -LiteralPath $ExecutablePath)) { + throw "FileManager executable was not found: $ExecutablePath" +} +if (-not (Test-Path -LiteralPath $TestProject)) { + throw "FileManager UI test project was not found: $TestProject" +} +if (-not (Test-Path -LiteralPath $AppVerifierPath)) { + throw "Application Verifier was not found: $AppVerifierPath" +} + +$targetName = Split-Path -Leaf $ExecutablePath +$gflagsPath = $null +if ($VerifierProfile -eq 'Full') { + # Full PageHeap is deliberately opt-in because it is process-persistent and expensive. + $gflagsCommand = Get-Command gflags.exe -ErrorAction SilentlyContinue | Select-Object -First 1 + if ($null -eq $gflagsCommand) { + throw 'gflags.exe is required for the Full Application Verifier profile.' + } + $gflagsPath = $gflagsCommand.Source +} + +try { + # Verifier configuration is process-persistent, so every enabled layer is removed in finally. + $layers = if ($VerifierProfile -eq 'Full') { @('Heaps', 'Handles', 'Locks', 'Exceptions') } else { @('Locks') } + & $AppVerifierPath -enable @layers -for $targetName + if ($LASTEXITCODE -ne 0) { + throw "Application Verifier failed to enable $($layers -join ', ') for $targetName (exit code $LASTEXITCODE)." + } + + if ($null -ne $gflagsPath) { + & $gflagsPath /p /enable $targetName /full + if ($LASTEXITCODE -ne 0) { + throw "gflags failed to enable full PageHeap for $targetName (exit code $LASTEXITCODE)." + } + } + + # The caller chooses either the compact lock soak or the complete UI category under one verifier profile. + dotnet test $TestProject --filter $TestFilter -- NUnit.NumberOfTestWorkers=0 + if ($LASTEXITCODE -ne 0) { + throw "The Application Verifier lock stress suite failed (exit code $LASTEXITCODE)." + } +} +finally { + if ($null -ne $gflagsPath) { + & $gflagsPath /p /disable $targetName + if ($LASTEXITCODE -ne 0) { + Write-Warning "gflags PageHeap settings could not be removed for $targetName (exit code $LASTEXITCODE)." + } + } + + & $AppVerifierPath -delete settings -for $targetName + if ($LASTEXITCODE -ne 0) { + Write-Warning "Application Verifier settings could not be removed for $targetName (exit code $LASTEXITCODE)." + } + + $verifierLogDirectory = Join-Path $env:USERPROFILE 'AppVerifierLogs' + if (Test-Path -LiteralPath $verifierLogDirectory) { + # Copy per-user verifier output into the workspace so the nightly artifact is independent of the runner account name. + $verifierLogs = Get-ChildItem -LiteralPath $verifierLogDirectory -Force + if ($verifierLogs.Count -ne 0) { + New-Item -ItemType Directory -Path $LogOutputDirectory -Force | Out-Null + Copy-Item -Path (Join-Path $verifierLogDirectory '*') -Destination $LogOutputDirectory -Recurse -Force + } + } +} diff --git a/tools/test-bzip2-compatibility.ps1 b/tools/test-bzip2-compatibility.ps1 new file mode 100644 index 00000000..2614b70f --- /dev/null +++ b/tools/test-bzip2-compatibility.ps1 @@ -0,0 +1,65 @@ +[CmdletBinding()] +param( + [ValidateSet('x64', 'x86')] + [string]$Architecture = 'x64' +) + +$ErrorActionPreference = 'Stop' +$repositoryRoot = Split-Path -Parent $PSScriptRoot +$bzip2Directory = Join-Path $repositoryRoot 'src\common\dep\bzip2' +$fixtureDirectory = Join-Path $repositoryRoot 'tests\bzip2-vectors' +$probeSource = Join-Path $PSScriptRoot 'bzip2_compatibility_probe.c' +$vswhere = Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio\Installer\vswhere.exe' +$vsDevCmdCandidates = @( + # Prefer the repository's authoritative VS 2026 environment when it is installed. + (Join-Path ${env:ProgramW6432} 'Microsoft Visual Studio\18\Insiders\Common7\Tools\VsDevCmd.bat') +) +if (Test-Path -LiteralPath $vswhere) { + $vsInstall = & $vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath + if (-not [string]::IsNullOrWhiteSpace($vsInstall)) { + $vsDevCmdCandidates += Join-Path $vsInstall.Trim() 'Common7\Tools\VsDevCmd.bat' + } +} +$vsDevCmd = $vsDevCmdCandidates | Where-Object { Test-Path -LiteralPath $_ } | Select-Object -First 1 +if ([string]::IsNullOrWhiteSpace($vsDevCmd)) { + throw 'No Visual Studio developer command environment was found.' +} + +$temporaryDirectory = Join-Path ([System.IO.Path]::GetTempPath()) ('OpenSalamander-bzip2-' + [Guid]::NewGuid().ToString('N')) +New-Item -ItemType Directory -Path $temporaryDirectory | Out-Null + +try { + $sourceFiles = @( + 'blocksort.c', 'bzlib.c', 'compress.c', 'crctable.c', + 'decompress.c', 'huffman.c', 'randtable.c' + ) | ForEach-Object { '"' + (Join-Path $bzip2Directory $_) + '"' } + $probeExecutable = Join-Path $temporaryDirectory 'bzip2_compatibility_probe.exe' + $compilerCommand = ( + 'call "' + $vsDevCmd + '" -arch=' + $Architecture + ' -host_arch=x64 && cd /d "' + $temporaryDirectory + '" && ' + + # Upstream's portable integer aliases intentionally trigger these MSVC-only conversion diagnostics. + 'cl /nologo /TC /W4 /WX /wd4100 /wd4244 /wd4245 /DBZ_NO_STDIO /I"' + $bzip2Directory + '" ' + + '/Fe"' + $probeExecutable + '" "' + $probeSource + '" ' + ($sourceFiles -join ' ') + ) + + # Build the checked-in parser, not a system DLL, before replaying retained hostile streams. + & $env:ComSpec /d /c $compilerCommand + if ($LASTEXITCODE -ne 0) { + throw "bzip2 compatibility probe compilation failed with exit code $LASTEXITCODE." + } + + $fuzzFixtures = Get-ChildItem -LiteralPath (Join-Path $fixtureDirectory 'fuzz') -Filter '*.hex' | Sort-Object Name | Select-Object -ExpandProperty FullName + & $probeExecutable ` + (Join-Path $fixtureDirectory 'golden-stream.hex') ` + (Join-Path $fixtureDirectory 'legacy-stream.hex') ` + (Join-Path $fixtureDirectory 'truncated-stream.hex') ` + $fuzzFixtures + if ($LASTEXITCODE -ne 0) { + throw "bzip2 compatibility probe failed with exit code $LASTEXITCODE." + } +} +finally { + if (Test-Path -LiteralPath $temporaryDirectory) { + # The GUID-named directory belongs only to this probe and must not persist between CI runs. + Remove-Item -LiteralPath $temporaryDirectory -Recurse -Force + } +} diff --git a/tools/test-cmark-gfm-hardening.ps1 b/tools/test-cmark-gfm-hardening.ps1 new file mode 100644 index 00000000..594344b5 --- /dev/null +++ b/tools/test-cmark-gfm-hardening.ps1 @@ -0,0 +1,42 @@ +[CmdletBinding()] +param() + +$ErrorActionPreference = 'Stop' +$repositoryRoot = Split-Path -Parent $PSScriptRoot +$workDirectory = Join-Path ([System.IO.Path]::GetTempPath()) ('cmark-gfm-hardening-' + [guid]::NewGuid().ToString('N')) +New-Item -ItemType Directory -Path $workDirectory | Out-Null + +# Compile the same vendor sources and bounded renderer used by the IE Viewer. +$cmarkRoot = Join-Path $repositoryRoot 'src\plugins\ieviewer\cmark-gfm' +$sourceFiles = @( + (Get-ChildItem -LiteralPath (Join-Path $cmarkRoot 'src') -Filter '*.c' | Where-Object Name -ne 'main.c' | Select-Object -ExpandProperty FullName), + (Get-ChildItem -LiteralPath (Join-Path $cmarkRoot 'extensions') -Filter '*.c' | Select-Object -ExpandProperty FullName), + (Join-Path $repositoryRoot 'src\plugins\ieviewer\markdown_rendering.cpp'), + (Join-Path $repositoryRoot 'tools\cmark_gfm_hardening_probe.cpp') +) +$includeDirectories = @( + (Join-Path $repositoryRoot 'src\plugins\ieviewer'), + (Join-Path $cmarkRoot 'src'), + (Join-Path $cmarkRoot 'extensions'), + (Join-Path $cmarkRoot 'build\src'), + (Join-Path $cmarkRoot 'build\extensions') +) +$arguments = @('/nologo', '/std:c++latest', '/EHsc', '/W4', '/WX', '/wd4100', '/D_CRT_SECURE_NO_WARNINGS', '/DCMARK_GFM_STATIC_DEFINE', '/DCMARK_GFM_EXTENSIONS_STATIC_DEFINE') +$arguments += $includeDirectories | ForEach-Object { '/I' + $_ } +$arguments += $sourceFiles +$arguments += '/Fe:' + (Join-Path $workDirectory 'cmark_gfm_hardening_probe.exe') + +Push-Location $workDirectory +try +{ + & cl.exe @arguments + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + & (Join-Path $workDirectory 'cmark_gfm_hardening_probe.exe') $repositoryRoot + exit $LASTEXITCODE +} +finally +{ + Pop-Location + # The GUID-named directory belongs only to this probe and must not persist between CI runs. + Remove-Item -LiteralPath $workDirectory -Recurse -Force +} diff --git a/tools/test-release-input-pinning.ps1 b/tools/test-release-input-pinning.ps1 new file mode 100644 index 00000000..77759f4c --- /dev/null +++ b/tools/test-release-input-pinning.ps1 @@ -0,0 +1,73 @@ +[CmdletBinding()] +param() + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$repositoryRoot = Split-Path -Parent $PSScriptRoot +$lockPath = Join-Path $PSScriptRoot 'release-inputs.json' +if (-not (Test-Path -LiteralPath $lockPath -PathType Leaf)) { + throw "Release input lock file is missing: $lockPath" +} + +# Treat release-tool coordinates as data so reviewing an update requires all identity fields. +$lock = Get-Content -LiteralPath $lockPath -Raw | ConvertFrom-Json +if ($lock.schemaVersion -ne 1) { + throw "Unsupported release-input lock schema version: $($lock.schemaVersion)" +} + +$inno = $lock.inputs.innoSetup +if ([string]::IsNullOrWhiteSpace($inno.version) -or + $inno.url -notmatch '^https://github\.com/jrsoftware/issrc/releases/download/' -or + $inno.sha256 -notmatch '^[0-9a-f]{64}$' -or + [string]::IsNullOrWhiteSpace($inno.publisher)) { + throw 'The Inno Setup release input must declare version, immutable release URL, SHA-256, and publisher.' +} + +$workflowPaths = Get-ChildItem -LiteralPath (Join-Path $repositoryRoot '.github\workflows') -Filter '*.yml' -File +if ($workflowPaths.Count -eq 0) { + throw 'No GitHub Actions workflows were found to verify.' +} + +$mutableReferences = [System.Collections.Generic.List[string]]::new() +foreach ($workflowPath in $workflowPaths) { + $lineNumber = 0 + foreach ($line in Get-Content -LiteralPath $workflowPath.FullName) { + $lineNumber++ + if ($line -match '^\s*uses:\s*[^@\s]+@([^\s#]+)') { + $revision = $Matches[1] + # Full commit identifiers make action execution independent of moved tags and branches. + if ($revision -notmatch '^[0-9a-f]{40}$') { + $mutableReferences.Add("$($workflowPath.Name):$lineNumber uses mutable revision '$revision'.") + } + } + } +} + +if ($mutableReferences.Count -ne 0) { + $mutableReferences | ForEach-Object { Write-Error $_ } + throw 'All GitHub Actions references must use reviewed full commit SHAs.' +} + +$releaseWorkflow = Get-Content -LiteralPath (Join-Path $repositoryRoot '.github\workflows\build-installer.yml') -Raw +foreach ($requiredPattern in @( + 'needs: release-tests', + 'needs: build', + 'environment: production', + 'contents: write', + 'Get-FileHash', + 'Get-AuthenticodeSignature', + 'release-installer-\$\{\{ github\.sha \}\}', + 'private-symbols-\$\{\{ github\.sha \}\}' +)) { + if ($releaseWorkflow -notmatch $requiredPattern) { + throw "Release workflow is missing required immutable-release contract: $requiredPattern" + } +} + +$rootRunner = Get-Content -LiteralPath (Join-Path $repositoryRoot 'runtests.ps1') -Raw +if ($rootRunner -notmatch 'test-release-input-pinning\.ps1') { + throw 'The aggregate test runner must execute the release-input pinning contract.' +} + +Write-Host "Release input pinning passed for Inno Setup $($inno.version) and $($workflowPaths.Count) workflows." diff --git a/tools/test-sqlite-recovery.ps1 b/tools/test-sqlite-recovery.ps1 new file mode 100644 index 00000000..e4cffd86 --- /dev/null +++ b/tools/test-sqlite-recovery.ps1 @@ -0,0 +1,229 @@ +[CmdletBinding()] +param( + [Parameter(Mandatory = $true)] + [string]$SqliteDll +) + +$ErrorActionPreference = 'Stop' +$sqliteDllPath = (Resolve-Path -LiteralPath $SqliteDll).Path + +if ((Split-Path -Leaf $sqliteDllPath) -ne 'sqlite.dll') { + throw "Expected sqlite.dll, received '$sqliteDllPath'." +} + +# An x64 DLL cannot be exercised by a 32-bit PowerShell host; relay to the installed x64 host without changing the test target. +if (-not [Environment]::Is64BitProcess -and $sqliteDllPath -match '(?i)(Debug|Release)_x64') { + $x64PowerShell = 'C:\Program Files\PowerShell\7\pwsh.exe' + if (-not (Test-Path -LiteralPath $x64PowerShell)) { + throw "The x64 SQLite probe requires '$x64PowerShell'." + } + + & $x64PowerShell -NoProfile -File $PSCommandPath -SqliteDll $sqliteDllPath + exit $LASTEXITCODE +} + +# Embed the resolved build artifact so the probe cannot bind an ambient sqlite.dll from PATH. +$sqliteDllForPInvoke = $sqliteDllPath.Replace('\', '\\') + +$nativeSource = @' +using System; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; + +public static class SqliteRecoveryProbe +{ + private const int SQLITE_OK = 0; + private const int SQLITE_ROW = 100; + private const int SQLITE_OPEN_READWRITE = 0x00000002; + private const int SQLITE_OPEN_CREATE = 0x00000004; + + static SqliteRecoveryProbe() + { + NativeLibrary.SetDllImportResolver(typeof(SqliteRecoveryProbe).Assembly, ResolveSqlite); + } + + private static IntPtr ResolveSqlite(string libraryName, Assembly assembly, DllImportSearchPath? searchPath) + { + // The dynamically compiled probe has no assembly path, so return the explicit build artifact before PowerShell's loader runs. + return libraryName == "sqlite3.dll" ? NativeLibrary.Load("__SQLITE_DLL_PATH__") : IntPtr.Zero; + } + + [DllImport("sqlite3.dll", CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr sqlite3_libversion(); + + [DllImport("sqlite3.dll", CallingConvention = CallingConvention.Cdecl)] + private static extern int sqlite3_compileoption_used(string option); + + [DllImport("sqlite3.dll", CallingConvention = CallingConvention.Cdecl)] + private static extern int sqlite3_open_v2(string filename, out IntPtr database, int flags, IntPtr vfs); + + [DllImport("sqlite3.dll", CallingConvention = CallingConvention.Cdecl)] + private static extern int sqlite3_close(IntPtr database); + + [DllImport("sqlite3.dll", CallingConvention = CallingConvention.Cdecl)] + private static extern int sqlite3_exec(IntPtr database, string sql, IntPtr callback, IntPtr callbackArgument, out IntPtr error); + + [DllImport("sqlite3.dll", CallingConvention = CallingConvention.Cdecl)] + private static extern int sqlite3_prepare_v2(IntPtr database, string sql, int length, out IntPtr statement, IntPtr tail); + + [DllImport("sqlite3.dll", CallingConvention = CallingConvention.Cdecl)] + private static extern int sqlite3_step(IntPtr statement); + + [DllImport("sqlite3.dll", CallingConvention = CallingConvention.Cdecl)] + private static extern int sqlite3_finalize(IntPtr statement); + + [DllImport("sqlite3.dll", CallingConvention = CallingConvention.Cdecl)] + private static extern int sqlite3_column_int(IntPtr statement, int column); + + [DllImport("sqlite3.dll", CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr sqlite3_column_text(IntPtr statement, int column); + + [DllImport("sqlite3.dll", CallingConvention = CallingConvention.Cdecl)] + private static extern void sqlite3_free(IntPtr pointer); + + public static void Run(string databasePath) + { + var version = Marshal.PtrToStringUTF8(sqlite3_libversion()); + if (version != "3.53.4") throw new InvalidOperationException("Expected SQLite 3.53.4, loaded " + version + "."); + RequireCompileOption("DQS=0"); + RequireCompileOption("ENABLE_API_ARMOR"); + RequireCompileOption("DEFAULT_SYNCHRONOUS=2"); + RequireCompileOption("DEFAULT_WAL_SYNCHRONOUS=2"); + RequireCompileOption("DEFAULT_WAL_AUTOCHECKPOINT=1000"); + + IntPtr database = IntPtr.Zero; + try + { + Open(databasePath, out database); + Execute(database, "PRAGMA journal_mode=WAL;"); + Execute(database, "PRAGMA synchronous=FULL;"); + Execute(database, "PRAGMA wal_autocheckpoint=1000;"); + Execute(database, "CREATE TABLE committed_rows (id INTEGER PRIMARY KEY, payload BLOB NOT NULL);"); + Execute(database, "INSERT INTO committed_rows VALUES (1, randomblob(2048));"); + Execute(database, "BEGIN IMMEDIATE;"); + Execute(database, "INSERT INTO committed_rows VALUES (2, randomblob(2048));"); + } + finally + { + if (database != IntPtr.Zero) Require(sqlite3_close(database), "close interrupted transaction"); + } + + try + { + Open(databasePath, out database); + if (QueryInt(database, "SELECT count(*) FROM committed_rows;") != 1) + throw new InvalidOperationException("Interrupted transaction exposed an uncommitted row."); + RequireIntegrityCheck(database, true); + Execute(database, "PRAGMA wal_checkpoint(TRUNCATE);"); + } + finally + { + if (database != IntPtr.Zero) Require(sqlite3_close(database), "close recovered database"); + database = IntPtr.Zero; + } + + var corruptPath = databasePath + ".corrupt"; + File.Copy(databasePath, corruptPath, true); + CorruptPage(corruptPath); + + try + { + Open(corruptPath, out database); + RequireIntegrityCheck(database, false); + } + finally + { + if (database != IntPtr.Zero) Require(sqlite3_close(database), "close corrupted database"); + } + } + + private static void Open(string databasePath, out IntPtr database) + { + database = IntPtr.Zero; + Require(sqlite3_open_v2(databasePath, out database, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, IntPtr.Zero), "open database"); + } + + private static void Execute(IntPtr database, string sql) + { + IntPtr error; + var result = sqlite3_exec(database, sql, IntPtr.Zero, IntPtr.Zero, out error); + if (error != IntPtr.Zero) sqlite3_free(error); + Require(result, sql); + } + + private static int QueryInt(IntPtr database, string sql) + { + IntPtr statement; + Require(sqlite3_prepare_v2(database, sql, -1, out statement, IntPtr.Zero), sql); + try + { + if (sqlite3_step(statement) != SQLITE_ROW) throw new InvalidOperationException(sql + " did not return a row."); + return sqlite3_column_int(statement, 0); + } + finally + { + Require(sqlite3_finalize(statement), "finalize integer query"); + } + } + + private static void RequireIntegrityCheck(IntPtr database, bool expectedClean) + { + IntPtr statement; + var prepared = sqlite3_prepare_v2(database, "PRAGMA integrity_check;", -1, out statement, IntPtr.Zero); + var clean = false; + if (prepared == SQLITE_OK) + { + try + { + if (sqlite3_step(statement) == SQLITE_ROW) + clean = Marshal.PtrToStringUTF8(sqlite3_column_text(statement, 0)) == "ok"; + } + finally + { + sqlite3_finalize(statement); + } + } + + if (clean != expectedClean) + throw new InvalidOperationException(expectedClean ? "Integrity check did not return ok." : "CorruptPage was not detected by integrity_check."); + } + + private static void CorruptPage(string databasePath) + { + // Page two is the first user-table b-tree page for this single-table fixture; an invalid page type must be detected. + using (var stream = new FileStream(databasePath, FileMode.Open, FileAccess.Write, FileShare.None)) + { + if (stream.Length < 8192) throw new InvalidOperationException("Database fixture is too small to corrupt page two."); + stream.Position = 4096; + stream.WriteByte(0); + stream.Flush(true); + } + } + + private static void RequireCompileOption(string option) + { + // Verify the loaded DLL, rather than only the project text, retained the documented recovery profile. + if (sqlite3_compileoption_used(option) != 1) throw new InvalidOperationException("Missing SQLite compile option " + option + "."); + } + + private static void Require(int result, string operation) + { + if (result != SQLITE_OK) throw new InvalidOperationException(operation + " returned SQLite error " + result + "."); + } +} +'@ + +Add-Type -TypeDefinition $nativeSource.Replace('__SQLITE_DLL_PATH__', $sqliteDllForPInvoke) + +$testDirectory = Join-Path $env:TEMP ("filemanager-sqlite-recovery-" + [guid]::NewGuid().ToString('N')) +New-Item -ItemType Directory -Path $testDirectory | Out-Null + +try { + # This test owns its GUID-named database, so interruption and page corruption cannot affect user data. + [SqliteRecoveryProbe]::Run((Join-Path $testDirectory 'owned.db')) + Write-Output 'SQLite WAL recovery and corrupt-page detection passed.' +} +finally { + Remove-Item -LiteralPath $testDirectory -Recurse -Force -ErrorAction SilentlyContinue +} diff --git a/tools/test-zlib-compatibility.ps1 b/tools/test-zlib-compatibility.ps1 new file mode 100644 index 00000000..bd7970bb --- /dev/null +++ b/tools/test-zlib-compatibility.ps1 @@ -0,0 +1,62 @@ +[CmdletBinding()] +param( + [ValidateSet('x64', 'x86')] + [string]$Architecture = 'x64' +) + +$ErrorActionPreference = 'Stop' +$repositoryRoot = Split-Path -Parent $PSScriptRoot +$zlibDirectory = Join-Path $repositoryRoot 'src\common\dep\zlib' +$fixtureDirectory = Join-Path $repositoryRoot 'tests\zlib-vectors' +$probeSource = Join-Path $PSScriptRoot 'zlib_compatibility_probe.c' +$vswhere = Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio\Installer\vswhere.exe' +$vsDevCmdCandidates = @( + # Prefer the repository's authoritative VS 2026 environment when it is installed. + (Join-Path ${env:ProgramW6432} 'Microsoft Visual Studio\18\Insiders\Common7\Tools\VsDevCmd.bat') +) +if (Test-Path -LiteralPath $vswhere) { + $vsInstall = & $vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath + if (-not [string]::IsNullOrWhiteSpace($vsInstall)) { + $vsDevCmdCandidates += Join-Path $vsInstall.Trim() 'Common7\Tools\VsDevCmd.bat' + } +} +$vsDevCmd = $vsDevCmdCandidates | Where-Object { Test-Path -LiteralPath $_ } | Select-Object -First 1 +if ([string]::IsNullOrWhiteSpace($vsDevCmd)) { + throw 'No Visual Studio developer command environment was found.' +} + +$temporaryDirectory = Join-Path ([System.IO.Path]::GetTempPath()) ('OpenSalamander-zlib-' + [Guid]::NewGuid().ToString('N')) +New-Item -ItemType Directory -Path $temporaryDirectory | Out-Null + +try { + $sourceFiles = @( + 'adler32.c', 'compress.c', 'crc32.c', 'deflate.c', 'infback.c', + 'inffast.c', 'inflate.c', 'inftrees.c', 'trees.c', 'uncompr.c', 'zutil.c' + ) | ForEach-Object { '"' + (Join-Path $zlibDirectory $_) + '"' } + $probeExecutable = Join-Path $temporaryDirectory 'zlib_compatibility_probe.exe' + $compilerCommand = ( + 'call "' + $vsDevCmd + '" -arch=' + $Architecture + ' -host_arch=x64 && cd /d "' + $temporaryDirectory + '" && ' + + 'cl /nologo /TC /W4 /I"' + $zlibDirectory + '" ' + + '/Fe"' + $probeExecutable + '" "' + $probeSource + '" ' + ($sourceFiles -join ' ') + ) + + # Build the checked-in sources, not a system DLL, so compatibility follows the product's vendor boundary. + & $env:ComSpec /d /c $compilerCommand + if ($LASTEXITCODE -ne 0) { + throw "zlib compatibility probe compilation failed with exit code $LASTEXITCODE." + } + + & $probeExecutable ` + (Join-Path $fixtureDirectory 'legacy-zlib-1.2.11.hex') ` + (Join-Path $fixtureDirectory 'truncated-zlib-stream.hex') ` + (Join-Path $fixtureDirectory 'bad-adler32-zlib-stream.hex') ` + (Join-Path $fixtureDirectory 'invalid-deflate-zlib-stream.hex') + if ($LASTEXITCODE -ne 0) { + throw "zlib compatibility probe failed with exit code $LASTEXITCODE." + } +} +finally { + if (Test-Path -LiteralPath $temporaryDirectory) { + Remove-Item -LiteralPath $temporaryDirectory -Recurse -Force + } +} diff --git a/tools/verify-durable-copy-commit.ps1 b/tools/verify-durable-copy-commit.ps1 new file mode 100644 index 00000000..06d070dc --- /dev/null +++ b/tools/verify-durable-copy-commit.ps1 @@ -0,0 +1,63 @@ +[CmdletBinding()] +param() + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +$repositoryRoot = Split-Path -Parent $PSScriptRoot +$copyEngine = Get-Content -Raw (Join-Path $repositoryRoot 'src\async_copy.cpp') + +function Require-Index { + param( + [string] $Needle, + [int] $StartAt = 0 + ) + + $index = $copyEngine.IndexOf($Needle, $StartAt, [StringComparison]::Ordinal) + if ($index -lt 0) { + throw "Missing durable-copy commit invariant: $Needle" + } + return $index +} + +$copyStart = Require-Index 'BOOL DoCopyFile(' +$moveStart = Require-Index 'BOOL DoMoveFile(' +$copyBody = $copyEngine.Substring($copyStart, $moveStart - $copyStart) + +if ($copyBody -notmatch 'DWORD fileAttrs\s*=\s*asyncPar->GetOverlappedFlag\(\)\s*\|\s*FILE_FLAG_SEQUENTIAL_SCAN\s*\|\s*FILE_FLAG_WRITE_THROUGH') { + throw 'Copy targets no longer request write-through creation.' +} + +# The production path uses the injectable filesystem boundary so native builds +# and deterministic fault tests exercise the same durable-flush decision. +$flush = Require-Index 'if (!OperationExecutionFileSystem().FlushFileBuffers(out))' $copyStart +$close = Require-Index 'if (!HANDLES(CloseHandle(out))' $flush +$verify = Require-Index 'COperationResult verificationResult = VerifyDurableCopyCommit(op->TargetName, op->FileSize);' $close +$verifyRetry = Require-Index 'while (!verificationResult.ToLegacyBool(&verificationError))' $verify +# Structured results retain phase diagnostics while their legacy BOOL adapters +# preserve the existing retry dialogs and the durable commit ordering contract. +$replace = Require-Index 'COperationResult commitResult = CommitTransactionalTargetFile(requestedTargetName, op->TargetName,' $verifyRetry +$replaceRetry = Require-Index 'while (!commitResult.ToLegacyBool(&err))' $replace +if ($flush -ge $close -or $close -ge $verify -or $verify -ge $verifyRetry -or + $verifyRetry -ge $replace -or $replace -ge $replaceRetry) { + throw 'Flush, successful close, metadata verification, and overwrite commit are no longer ordered durably.' +} + +$moveBody = $copyEngine.Substring($moveStart) +$copyThenDelete = $moveBody.IndexOf('BOOL notError = DoCopyFile(', [StringComparison]::Ordinal) +$fullHash = $moveBody.IndexOf('while (suspiciousIoRetry && !VerifyFullFileContentSha256(op->SourceName, op->TargetName, &err))', [StringComparison]::Ordinal) +# Identity-verified deletion prevents a path replacement race after the source +# digest is accepted and before a cross-volume move removes the original file. +$deleteSource = $moveBody.IndexOf('if (DeleteFileWithVerifiedIdentity(op->SourceName, op->SourceIdentity, &err))', [StringComparison]::Ordinal) +if ($copyThenDelete -lt 0 -or $fullHash -lt 0 -or $deleteSource -lt 0 -or + $copyThenDelete -ge $fullHash -or $fullHash -ge $deleteSource) { + throw 'Cross-volume move no longer verifies a retried copy before deleting its source.' +} + +if ($copyEngine -notmatch 'static BOOL VerifyFullFileContentSha256\(' -or + $copyEngine -notmatch 'BCRYPT_SHA256_ALGORITHM' -or + $copyEngine -notmatch '\*suspiciousIoRetry\s*=\s*TRUE') { + throw 'Cross-volume move full SHA-256 verification is incomplete.' +} + +Write-Host 'Durable copy and cross-volume move verification checks passed.' diff --git a/tools/verify-fluent-icon-coverage.ps1 b/tools/verify-fluent-icon-coverage.ps1 new file mode 100644 index 00000000..7adc16eb --- /dev/null +++ b/tools/verify-fluent-icon-coverage.ps1 @@ -0,0 +1,88 @@ +param() + +$ErrorActionPreference = 'Stop' + +# Guard the modern icon migration against silent bitmap/shell fallbacks and accidental app-icon edits. +$repoRoot = Split-Path -Parent $PSScriptRoot +$toolbarDefinitionsPath = Join-Path $repoRoot 'src\toolbar_button_defs.cpp' +$toolbarDirectory = Join-Path $repoRoot 'src\res\toolbars' +$errors = [System.Collections.Generic.List[string]]::new() + +$definitionText = [System.IO.File]::ReadAllText($toolbarDefinitionsPath) +$mappedNames = [regex]::Matches( + $definitionText, + '(?m)^\s*(?:/\*.*?\*/\s*)?\{[^\r\n]*IDX_TB_[^\r\n]*"([A-Za-z0-9]+)"\s*\},?\s*$') | + ForEach-Object { $_.Groups[1].Value } +$mappedNames += 'Focus', 'Stop' +$mappedNames = $mappedNames | Sort-Object -Unique + +foreach ($name in $mappedNames) +{ + $path = Join-Path $toolbarDirectory ($name + '.svg') + if (-not (Test-Path -LiteralPath $path)) + { + $errors.Add("Mapped toolbar icon is missing: $name.svg") + } +} + +$shellBackedRows = [regex]::Matches( + $definitionText, + '(?m)^\s*(?:/\*.*?\*/\s*)?\{(?:NIB1\(IDX_TB_[^)]+\)|IDX_TB_[^,]+),\s*([0-9]+),') | + Where-Object { $_.Groups[1].Value -ne '0' } +foreach ($row in $shellBackedRows) +{ + $errors.Add("Core toolbar row still depends on a non-zero shell resource: $($row.Value.Trim())") +} + +$allowedColors = @( + '#0F6CBD', '#115EA3', '#DDEBF7', + '#424242', '#616161', '#F3F2F1', + '#D89B00', '#FFD666', '#E1C699', '#D2B48C', '#C19A6B', '#8E562E', + '#107C10', '#C50F1F', '#F7630C', '#FFFFFF' +) + +foreach ($file in Get-ChildItem -LiteralPath $toolbarDirectory -Filter '*.svg') +{ + $content = [System.IO.File]::ReadAllText($file.FullName) + if (-not $content.StartsWith('